From 1f81fe28cd7d22c7f47d66a2f935174b3d443805 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Fri, 7 Aug 2026 23:55:17 -0700 Subject: [PATCH] Initial check in of the engine recreation effort. --- readme.md | 4 + src/.gitignore | 13 + src/CMakeLists.txt | 41 + src/CMakePresets.json | 50 + src/MayaImport/maya_main.h | 50 + src/aas/AASBuild_local.h | 145 + src/aas/AASCluster.h | 58 + src/aas/AASFile.cpp | 902 + src/aas/AASFile.h | 509 + src/aas/AASFileManager.h | 46 + src/aas/AASFile_legacy.inc | 1316 + src/aas/AASFile_local.h | 188 + src/aas/AASReach.h | 63 + src/aas/Brush.h | 227 + src/aas/BrushBSP.h | 229 + src/aas/aasfile_optimize.cpp | 150 + src/aas/aasfile_sample.cpp | 604 + src/aas/aasfilemanager.cpp | 72 + src/bse/BSE.h | 627 + src/bse/BSEInterface.h | 127 + src/bse/BSE_Bounds.cpp | 159 + src/bse/BSE_Effect.cpp | 355 + src/bse/BSE_EffectTemplate.cpp | 354 + src/bse/BSE_Electricity.cpp | 264 + src/bse/BSE_Envelope.h | 238 + src/bse/BSE_Envelopes.cpp | 172 + src/bse/BSE_Light.cpp | 134 + src/bse/BSE_Manager.cpp | 292 + src/bse/BSE_ParseParticle2.cpp | 410 + src/bse/BSE_Particle.cpp | 685 + src/bse/BSE_Particle.h | 790 + src/bse/BSE_Render.cpp | 411 + src/bse/BSE_Segment.cpp | 544 + src/bse/BSE_SegmentTemplate.cpp | 333 + src/bse/BSE_SpawnDomains.cpp | 352 + src/bse/BSE_SpawnDomains.h | 241 + src/cm/CollisionModel.cpp | 95 + src/cm/CollisionModel.h | 173 + src/cm/CollisionModel_local.h | 878 + src/cm/collisionmodel_contacts.cpp | 61 + src/cm/collisionmodel_contacts_legacy.inc | 71 + src/cm/collisionmodel_contents.cpp | 435 + src/cm/collisionmodel_contents_legacy.inc | 637 + src/cm/collisionmodel_debug.cpp | 536 + src/cm/collisionmodel_files.cpp | 646 + src/cm/collisionmodel_load.cpp | 1266 + src/cm/collisionmodel_load_legacy.inc | 3689 +++ src/cm/collisionmodel_rotate.cpp | 1745 ++ src/cm/collisionmodel_trace.cpp | 162 + src/cm/collisionmodel_trace_legacy.inc | 252 + src/cm/collisionmodel_translate.cpp | 237 + src/cm/collisionmodel_translate_legacy.inc | 1116 + src/curl/include/Makefile.am | 5 + src/curl/include/Makefile.in | 495 + src/curl/include/README | 38 + src/curl/include/curl/Makefile.am | 8 + src/curl/include/curl/Makefile.in | 429 + src/curl/include/curl/curl.h | 1306 + src/curl/include/curl/easy.h | 92 + src/curl/include/curl/mprintf.h | 78 + src/curl/include/curl/multi.h | 234 + src/curl/include/curl/stdcheaders.h | 58 + src/curl/include/curl/types.h | 52 + src/engine/CMakeLists.txt | 442 + src/framework/BuildDefines.h | 97 + src/framework/BuildVersion.h | 27 + src/framework/CVarSystem.h | 439 + src/framework/CmdSystem.h | 249 + src/framework/Common.h | 387 + src/framework/Compressor.h | 69 + src/framework/Console.h | 68 + src/framework/DeclFX.h | 109 + src/framework/DeclPDA.h | 166 + src/framework/DeclParticle.h | 214 + src/framework/DeclPlayerModel.cpp | 163 + src/framework/DeclPlayerModel.h | 65 + src/framework/DemoChecksum.h | 35 + src/framework/DemoFile.h | 87 + src/framework/EditField.h | 75 + src/framework/EventLoop.h | 84 + src/framework/File.h | 390 + src/framework/FileSystem.h | 464 + src/framework/KeyInput.h | 217 + src/framework/Session.h | 117 + src/framework/Session_local.h | 382 + src/framework/Unzip.h | 342 + src/framework/UsercmdGen.h | 264 + src/framework/async/AsyncClient.h | 216 + src/framework/async/AsyncNetwork.h | 215 + src/framework/async/AsyncServer.h | 258 + src/framework/async/MsgChannel.h | 206 + src/framework/async/NetworkSystem.h | 184 + src/framework/async/ServerScan.h | 166 + src/framework/async/asyncclient.cpp | 2334 ++ src/framework/async/asyncnetwork.cpp | 500 + src/framework/async/asyncserver.cpp | 2852 +++ src/framework/async/msgchannel.cpp | 790 + src/framework/async/networksystem.cpp | 303 + src/framework/async/serverscan.cpp | 618 + src/framework/cmdsystem.cpp | 783 + src/framework/common.cpp | 3244 +++ src/framework/compressor.cpp | 2585 ++ src/framework/console.cpp | 1188 + src/framework/cvarsystem.cpp | 1413 ++ src/framework/declAF.h | 296 + src/framework/declEntityDef.h | 70 + src/framework/declLipSync.cpp | 151 + src/framework/declLipSync.h | 97 + src/framework/declManager.h | 482 + src/framework/declMatType.cpp | 181 + src/framework/declMatType.h | 76 + src/framework/declPlayback.cpp | 484 + src/framework/declPlayback.h | 208 + src/framework/declSkin.h | 98 + src/framework/declTable.h | 82 + src/framework/declaf.cpp | 1805 ++ src/framework/declentitydef.cpp | 177 + src/framework/declmanager.cpp | 2766 +++ src/framework/declpda.cpp | 680 + src/framework/declskin.cpp | 187 + src/framework/decltable.cpp | 212 + src/framework/demofile.cpp | 324 + src/framework/editfield.cpp | 599 + src/framework/eventloop.cpp | 270 + src/framework/file.cpp | 1462 ++ src/framework/filesystem.cpp | 4767 ++++ src/framework/keyinput.cpp | 785 + src/framework/licensee.h | 153 + src/framework/session.cpp | 3304 +++ src/framework/session_menu.cpp | 1663 ++ src/framework/unzip.cpp | 4494 ++++ src/framework/usercmdgen.cpp | 1235 + src/game/AF.cpp | 1359 ++ src/game/AF.h | 120 + src/game/AFEntity.cpp | 3226 +++ src/game/AFEntity.h | 531 + src/game/Actor.cpp | 3879 +++ src/game/Actor.h | 450 + src/game/BrittleFracture.cpp | 1378 ++ src/game/BrittleFracture.h | 126 + src/game/Camera.cpp | 2232 ++ src/game/Camera.h | 319 + src/game/Effect.cpp | 487 + src/game/Effect.h | 90 + src/game/Entity.cpp | 6813 ++++++ src/game/Entity.h | 872 + src/game/FreeView.cpp | 189 + src/game/FreeView.h | 66 + src/game/Game.def | 2 + src/game/Game.h | 742 + src/game/GameEdit.cpp | 1984 ++ src/game/GameEdit.h | 119 + src/game/Game_Debug.cpp | 396 + src/game/Game_Debug.h | 119 + src/game/Game_Log.cpp | 251 + src/game/Game_Log.h | 63 + src/game/Game_local.cpp | 8532 +++++++ src/game/Game_local.h | 1436 ++ src/game/Game_network.cpp | 3539 +++ src/game/Healing_Station.cpp | 233 + src/game/Healing_Station.h | 69 + src/game/IK.cpp | 1085 + src/game/IK.h | 178 + src/game/Icon.cpp | 120 + src/game/Icon.h | 58 + src/game/IconManager.cpp | 229 + src/game/IconManager.h | 54 + src/game/Instance.cpp | 257 + src/game/Instance.h | 71 + src/game/Item.cpp | 2399 ++ src/game/Item.h | 386 + src/game/Light.cpp | 1509 ++ src/game/Light.h | 170 + src/game/LipSync.cpp | 491 + src/game/LipSync.h | 111 + src/game/Misc.cpp | 4410 ++++ src/game/Misc.h | 997 + src/game/Moveable.cpp | 1371 ++ src/game/Moveable.h | 203 + src/game/Mover.cpp | 6000 +++++ src/game/Mover.h | 730 + src/game/MultiplayerGame.cpp | 9234 +++++++ src/game/MultiplayerGame.h | 952 + src/game/Playback.cpp | 441 + src/game/Player.cpp | 14101 +++++++++++ src/game/Player.h | 1356 ++ src/game/PlayerView.cpp | 808 + src/game/PlayerView.h | 167 + src/game/Player_Cheats.cpp | 40 + src/game/Player_States.cpp | 943 + src/game/Projectile.cpp | 2148 ++ src/game/Projectile.h | 382 + src/game/Pvs.cpp | 1462 ++ src/game/Pvs.h | 127 + src/game/SecurityCamera.cpp | 602 + src/game/SecurityCamera.h | 94 + src/game/Sound.cpp | 394 + src/game/Sound.h | 81 + src/game/SplineMover.cpp | 3313 +++ src/game/SplineMover.h | 518 + src/game/Target.cpp | 2345 ++ src/game/Target.h | 727 + src/game/TramGate.cpp | 335 + src/game/TramGate.h | 75 + src/game/Trigger.cpp | 1569 ++ src/game/Trigger.h | 311 + src/game/Weapon.cpp | 3358 +++ src/game/Weapon.h | 516 + src/game/WorldSpawn.cpp | 131 + src/game/WorldSpawn.h | 51 + src/game/ai/AAS.cpp | 486 + src/game/ai/AAS.h | 186 + src/game/ai/AAS_Find.cpp | 364 + src/game/ai/AAS_Find.h | 136 + src/game/ai/AAS_debug.cpp | 932 + src/game/ai/AAS_local.h | 216 + src/game/ai/AAS_pathing.cpp | 757 + src/game/ai/AAS_routing.cpp | 1447 ++ src/game/ai/AAS_tactical.cpp | 1789 ++ src/game/ai/AAS_tactical.h | 93 + src/game/ai/AI.cpp | 5175 ++++ src/game/ai/AI.h | 1397 ++ src/game/ai/AI_Actions.cpp | 615 + src/game/ai/AI_Announcements.cpp | 578 + src/game/ai/AI_Debug.cpp | 354 + src/game/ai/AI_Manager.cpp | 790 + src/game/ai/AI_Manager.h | 260 + src/game/ai/AI_Medic.cpp | 860 + src/game/ai/AI_Medic.h | 116 + src/game/ai/AI_Move.cpp | 4856 ++++ src/game/ai/AI_Move.h | 319 + src/game/ai/AI_States.cpp | 2236 ++ src/game/ai/AI_Tactical.cpp | 1078 + src/game/ai/AI_Tactical.h | 158 + src/game/ai/AI_Util.cpp | 1001 + src/game/ai/AI_Util.h | 256 + src/game/ai/AI_events.cpp | 1169 + src/game/ai/AI_pathing.cpp | 1649 ++ src/game/ai/Monster_Berserker.cpp | 488 + src/game/ai/Monster_BossBuddy.cpp | 685 + src/game/ai/Monster_BossMakron.cpp | 2371 ++ src/game/ai/Monster_ConvoyGround.cpp | 626 + src/game/ai/Monster_ConvoyHover.cpp | 654 + src/game/ai/Monster_FailedTransfer.cpp | 145 + src/game/ai/Monster_Fatty.cpp | 443 + src/game/ai/Monster_Gladiator.cpp | 925 + src/game/ai/Monster_Grunt.cpp | 341 + src/game/ai/Monster_Gunner.cpp | 471 + src/game/ai/Monster_Harvester.cpp | 1214 + src/game/ai/Monster_HarvesterDispersal.cpp | 418 + src/game/ai/Monster_HeavyHoverTank.cpp | 557 + src/game/ai/Monster_IronMaiden.cpp | 554 + src/game/ai/Monster_LightTank.cpp | 633 + src/game/ai/Monster_NetworkGuardian.cpp | 845 + src/game/ai/Monster_RepairBot.cpp | 399 + src/game/ai/Monster_Scientist.cpp | 99 + src/game/ai/Monster_Sentry.cpp | 435 + src/game/ai/Monster_SlimyTransfer.cpp | 228 + src/game/ai/Monster_StreamProtector.cpp | 561 + src/game/ai/Monster_StroggFlyer.cpp | 386 + src/game/ai/Monster_StroggHover.cpp | 1471 ++ src/game/ai/Monster_StroggMarine.cpp | 776 + src/game/ai/Monster_TeleportDropper.cpp | 559 + src/game/ai/Monster_Turret.cpp | 227 + src/game/ai/Monster_TurretFlying.cpp | 320 + src/game/ai/VehicleAI.cpp | 266 + src/game/ai/VehicleAI.h | 120 + src/game/anim/Anim.cpp | 1205 + src/game/anim/Anim.h | 825 + src/game/anim/Anim_Blend.cpp | 6061 +++++ src/game/anim/Anim_Import.cpp | 627 + src/game/anim/Anim_Testmodel.cpp | 964 + src/game/anim/Anim_Testmodel.h | 94 + src/game/client/ClientAFEntity.cpp | 720 + src/game/client/ClientAFEntity.h | 150 + src/game/client/ClientEffect.cpp | 521 + src/game/client/ClientEffect.h | 126 + src/game/client/ClientEntity.cpp | 675 + src/game/client/ClientEntity.h | 246 + src/game/client/ClientModel.cpp | 474 + src/game/client/ClientModel.h | 116 + src/game/client/ClientMoveable.cpp | 403 + src/game/client/ClientMoveable.h | 94 + src/game/gamesys/Callbacks.cpp | 2624 ++ src/game/gamesys/Class.cpp | 1458 ++ src/game/gamesys/Class.h | 550 + src/game/gamesys/DebugGraph.cpp | 92 + src/game/gamesys/DebugGraph.h | 37 + src/game/gamesys/Event.cpp | 837 + src/game/gamesys/Event.h | 208 + src/game/gamesys/NoGameTypeInfo.h | 79 + src/game/gamesys/SaveGame.cpp | 2125 ++ src/game/gamesys/SaveGame.h | 206 + src/game/gamesys/State.cpp | 448 + src/game/gamesys/State.h | 181 + src/game/gamesys/SysCmds.cpp | 3268 +++ src/game/gamesys/SysCmds.h | 34 + src/game/gamesys/SysCvar.cpp | 671 + src/game/gamesys/SysCvar.h | 448 + src/game/mp/Buying.cpp | 92 + src/game/mp/Buying.h | 61 + src/game/mp/CTF.cpp | 332 + src/game/mp/CTF.h | 108 + src/game/mp/GameState.cpp | 3134 +++ src/game/mp/GameState.h | 507 + src/game/mp/Tourney.cpp | 1045 + src/game/mp/Tourney.h | 180 + src/game/mp/VoiceComms.cpp | 220 + src/game/mp/stats/StatEvent.cpp | 355 + src/game/mp/stats/StatEvent.h | 385 + src/game/mp/stats/StatManager.cpp | 1369 ++ src/game/mp/stats/StatManager.h | 373 + src/game/mp/stats/StatWindow.cpp | 486 + src/game/mp/stats/StatWindow.h | 60 + src/game/physics/Clip.cpp | 2190 ++ src/game/physics/Clip.h | 430 + src/game/physics/Force.cpp | 89 + src/game/physics/Force.h | 62 + src/game/physics/Force_Constant.cpp | 132 + src/game/physics/Force_Constant.h | 67 + src/game/physics/Force_Drag.cpp | 154 + src/game/physics/Force_Drag.h | 70 + src/game/physics/Force_Field.cpp | 295 + src/game/physics/Force_Field.h | 115 + src/game/physics/Force_Spring.cpp | 186 + src/game/physics/Force_Spring.h | 74 + src/game/physics/Physics.cpp | 79 + src/game/physics/Physics.h | 202 + src/game/physics/Physics_AF.cpp | 8102 +++++++ src/game/physics/Physics_AF.h | 1011 + src/game/physics/Physics_Actor.cpp | 396 + src/game/physics/Physics_Actor.h | 112 + src/game/physics/Physics_Base.cpp | 895 + src/game/physics/Physics_Base.h | 183 + src/game/physics/Physics_Monster.cpp | 861 + src/game/physics/Physics_Monster.h | 152 + src/game/physics/Physics_Parametric.cpp | 1214 + src/game/physics/Physics_Parametric.h | 186 + src/game/physics/Physics_Particle.cpp | 1046 + src/game/physics/Physics_Particle.h | 176 + src/game/physics/Physics_Player.cpp | 2333 ++ src/game/physics/Physics_Player.h | 221 + src/game/physics/Physics_RigidBody.cpp | 1646 ++ src/game/physics/Physics_RigidBody.h | 195 + src/game/physics/Physics_Static.cpp | 905 + src/game/physics/Physics_Static.h | 178 + src/game/physics/Physics_StaticMulti.cpp | 1111 + src/game/physics/Physics_StaticMulti.h | 166 + src/game/physics/Physics_VehicleMonster.cpp | 60 + src/game/physics/Physics_VehicleMonster.h | 48 + src/game/physics/Push.cpp | 1526 ++ src/game/physics/Push.h | 109 + src/game/script/ScriptFuncUtility.cpp | 493 + src/game/script/ScriptFuncUtility.h | 95 + src/game/script/Script_Compiler.cpp | 2666 +++ src/game/script/Script_Compiler.h | 275 + src/game/script/Script_Interpreter.cpp | 1914 ++ src/game/script/Script_Interpreter.h | 277 + src/game/script/Script_Program.cpp | 2537 ++ src/game/script/Script_Program.h | 820 + src/game/script/Script_Thread.cpp | 2332 ++ src/game/script/Script_Thread.h | 388 + src/game/spawner.cpp | 624 + src/game/spawner.h | 144 + src/game/vehicle/Vehicle.cpp | 1706 ++ src/game/vehicle/Vehicle.h | 440 + src/game/vehicle/VehicleAnimated.cpp | 260 + src/game/vehicle/VehicleAnimated.h | 73 + src/game/vehicle/VehicleController.cpp | 325 + src/game/vehicle/VehicleController.h | 93 + src/game/vehicle/VehicleDriver.cpp | 859 + src/game/vehicle/VehicleDriver.h | 209 + src/game/vehicle/VehicleMonster.cpp | 212 + src/game/vehicle/VehicleMonster.h | 69 + src/game/vehicle/VehicleParts.cpp | 2410 ++ src/game/vehicle/VehicleParts.h | 468 + src/game/vehicle/VehiclePosition.cpp | 869 + src/game/vehicle/VehicleRigid.cpp | 187 + src/game/vehicle/VehicleRigid.h | 69 + src/game/vehicle/VehicleSpline.cpp | 147 + src/game/vehicle/VehicleSpline.h | 58 + src/game/vehicle/VehicleStatic.cpp | 142 + src/game/vehicle/VehicleStatic.h | 59 + src/game/vehicle/Vehicle_DropPod.cpp | 201 + src/game/vehicle/Vehicle_Walker.cpp | 617 + src/game/weapon/WeaponBlaster.cpp | 511 + src/game/weapon/WeaponDarkMatterGun.cpp | 494 + src/game/weapon/WeaponGauntlet.cpp | 538 + src/game/weapon/WeaponGrenadeLauncher.cpp | 226 + src/game/weapon/WeaponHyperblaster.cpp | 319 + src/game/weapon/WeaponLightningGun.cpp | 997 + src/game/weapon/WeaponMachinegun.cpp | 351 + src/game/weapon/WeaponNailgun.cpp | 953 + src/game/weapon/WeaponNapalmGun.cpp | 467 + src/game/weapon/WeaponRailgun.cpp | 294 + src/game/weapon/WeaponRocketLauncher.cpp | 607 + src/game/weapon/WeaponShotgun.cpp | 313 + src/idlib/AutoPtr.h | 88 + src/idlib/Base64.cpp | 256 + src/idlib/Base64.h | 107 + src/idlib/BitMsg.cpp | 1484 ++ src/idlib/BitMsg.h | 890 + src/idlib/CMakeLists.txt | 78 + src/idlib/CmdArgs.cpp | 193 + src/idlib/CmdArgs.h | 69 + src/idlib/Dict.cpp | 844 + src/idlib/Dict.h | 332 + src/idlib/Heap.cpp | 2338 ++ src/idlib/Heap.h | 1191 + src/idlib/LangDict.cpp | 313 + src/idlib/LangDict.h | 73 + src/idlib/Lexer.cpp | 3967 ++++ src/idlib/Lexer.h | 489 + src/idlib/LexerFactory.cpp | 77 + src/idlib/LexerFactory.h | 45 + src/idlib/Lib.cpp | 591 + src/idlib/Lib.h | 354 + src/idlib/MapFile.h | 290 + src/idlib/Parser.cpp | 3243 +++ src/idlib/Parser.h | 277 + src/idlib/Str.cpp | 2255 ++ src/idlib/Str.h | 1123 + src/idlib/TextCompiler.cpp | 26 + src/idlib/TextCompiler.h | 162 + src/idlib/Timer.cpp | 156 + src/idlib/Timer.h | 219 + src/idlib/TimingCollection.cpp | 421 + src/idlib/TimingCollection.h | 124 + src/idlib/Token.cpp | 175 + src/idlib/Token.h | 262 + src/idlib/algorithms/MultifieldSort.h | 224 + src/idlib/bv/Bounds.cpp | 491 + src/idlib/bv/Bounds.h | 472 + src/idlib/bv/Box.cpp | 843 + src/idlib/bv/Box.h | 293 + src/idlib/bv/Frustum.cpp | 2849 +++ src/idlib/bv/Frustum.h | 261 + src/idlib/bv/Sphere.cpp | 151 + src/idlib/bv/Sphere.h | 271 + src/idlib/containers/BTree.h | 519 + src/idlib/containers/BinSearch.h | 134 + src/idlib/containers/HashIndex.cpp | 233 + src/idlib/containers/HashIndex.h | 439 + src/idlib/containers/HashTable.h | 444 + src/idlib/containers/Hierarchy.h | 358 + src/idlib/containers/LinkList.h | 357 + src/idlib/containers/List.h | 1255 + src/idlib/containers/ListGame.h | 54 + src/idlib/containers/Pair.h | 82 + src/idlib/containers/PlaneSet.h | 77 + src/idlib/containers/Queue.h | 196 + src/idlib/containers/Stack.h | 82 + src/idlib/containers/StaticList.h | 544 + src/idlib/containers/StrList.h | 219 + src/idlib/containers/StrPool.h | 248 + src/idlib/containers/VectorSet.h | 266 + src/idlib/containers/rvBlockPool.h | 131 + src/idlib/geometry/DrawVert.h | 176 + src/idlib/geometry/JointTransform.cpp | 83 + src/idlib/geometry/JointTransform.h | 363 + src/idlib/geometry/Surface.cpp | 926 + src/idlib/geometry/Surface.h | 229 + src/idlib/geometry/Surface_Patch.cpp | 687 + src/idlib/geometry/Surface_Patch.h | 142 + src/idlib/geometry/Surface_Polytope.cpp | 332 + src/idlib/geometry/Surface_Polytope.h | 66 + src/idlib/geometry/Surface_SweptSpline.cpp | 219 + src/idlib/geometry/Surface_SweptSpline.h | 89 + src/idlib/geometry/TraceModel.cpp | 1657 ++ src/idlib/geometry/TraceModel.h | 197 + src/idlib/geometry/Winding.cpp | 1647 ++ src/idlib/geometry/Winding.h | 420 + src/idlib/geometry/Winding2D.cpp | 750 + src/idlib/geometry/Winding2D.h | 165 + src/idlib/geometry/rvVertex.h | 105 + src/idlib/hashing/CRC16.cpp | 141 + src/idlib/hashing/CRC16.h | 42 + src/idlib/hashing/CRC32.cpp | 190 + src/idlib/hashing/CRC32.h | 42 + src/idlib/hashing/CRC8.cpp | 137 + src/idlib/hashing/CRC8.h | 42 + src/idlib/hashing/Honeyman.cpp | 139 + src/idlib/hashing/Honeyman.h | 43 + src/idlib/hashing/MD4.cpp | 282 + src/idlib/hashing/MD4.h | 39 + src/idlib/hashing/MD5.cpp | 295 + src/idlib/hashing/MD5.h | 39 + src/idlib/mapfile.cpp | 1389 ++ src/idlib/math/Angles.cpp | 252 + src/idlib/math/Angles.h | 329 + src/idlib/math/Complex.cpp | 37 + src/idlib/math/Complex.h | 344 + src/idlib/math/Curve.h | 2595 ++ src/idlib/math/Extrapolate.h | 237 + src/idlib/math/FFT.cpp | 169 + src/idlib/math/FFT.h | 52 + src/idlib/math/Interpolate.h | 490 + src/idlib/math/Lcp.cpp | 1640 ++ src/idlib/math/Lcp.h | 73 + src/idlib/math/Mat3x4.h | 309 + src/idlib/math/Math.cpp | 296 + src/idlib/math/Math.h | 1069 + src/idlib/math/Matrix.cpp | 8252 +++++++ src/idlib/math/Matrix.h | 3039 +++ src/idlib/math/Ode.cpp | 351 + src/idlib/math/Ode.h | 142 + src/idlib/math/Plane.cpp | 150 + src/idlib/math/Plane.h | 387 + src/idlib/math/Pluecker.cpp | 82 + src/idlib/math/Pluecker.h | 405 + src/idlib/math/Polynomial.cpp | 238 + src/idlib/math/Polynomial.h | 625 + src/idlib/math/Quat.cpp | 251 + src/idlib/math/Quat.h | 401 + src/idlib/math/Radians.cpp | 159 + src/idlib/math/Radians.h | 276 + src/idlib/math/Random.h | 154 + src/idlib/math/Rotation.cpp | 153 + src/idlib/math/Rotation.h | 207 + src/idlib/math/Simd.cpp | 4372 ++++ src/idlib/math/Simd.h | 253 + src/idlib/math/Simd_3DNow.cpp | 293 + src/idlib/math/Simd_3DNow.h | 46 + src/idlib/math/Simd_AltiVec.cpp | 11183 +++++++++ src/idlib/math/Simd_AltiVec.h | 256 + src/idlib/math/Simd_InstructionMacros.h | 523 + src/idlib/math/Simd_MMX.cpp | 607 + src/idlib/math/Simd_MMX.h | 47 + src/idlib/math/Simd_SSE.cpp | 19825 ++++++++++++++++ src/idlib/math/Simd_SSE.h | 152 + src/idlib/math/Simd_SSE2.cpp | 2234 ++ src/idlib/math/Simd_SSE2.h | 63 + src/idlib/math/Simd_SSE3.cpp | 460 + src/idlib/math/Simd_SSE3.h | 48 + src/idlib/math/Simd_generic.cpp | 3798 +++ src/idlib/math/Simd_generic.h | 181 + src/idlib/math/Vector.cpp | 488 + src/idlib/math/Vector.h | 2516 ++ src/idlib/precompiled.h | 435 + src/idlib/rvHeap.cpp | 1580 ++ src/idlib/rvHeap.h | 401 + src/idlib/rvHeapArena.cpp | 327 + src/idlib/rvHeapArena.h | 163 + src/idlib/rvMemSys.cpp | 1292 + src/idlib/rvMemSys.h | 168 + src/idlib/threads/AutoCrit.h | 246 + src/mpgame/AF.cpp | 1359 ++ src/mpgame/AF.h | 120 + src/mpgame/AFEntity.cpp | 3226 +++ src/mpgame/AFEntity.h | 531 + src/mpgame/Actor.cpp | 3877 +++ src/mpgame/Actor.h | 450 + src/mpgame/BrittleFracture.cpp | 1378 ++ src/mpgame/BrittleFracture.h | 126 + src/mpgame/Camera.cpp | 2232 ++ src/mpgame/Camera.h | 319 + src/mpgame/Effect.cpp | 487 + src/mpgame/Effect.h | 90 + src/mpgame/Entity.cpp | 6815 ++++++ src/mpgame/Entity.h | 872 + src/mpgame/GameEdit.cpp | 1984 ++ src/mpgame/GameEdit.h | 119 + src/mpgame/Game_Debug.cpp | 396 + src/mpgame/Game_Debug.h | 119 + src/mpgame/Game_Log.cpp | 251 + src/mpgame/Game_Log.h | 63 + src/mpgame/Game_local.cpp | 8665 +++++++ src/mpgame/Game_local.h | 1520 ++ src/mpgame/Game_network.cpp | 4002 ++++ src/mpgame/Healing_Station.cpp | 233 + src/mpgame/Healing_Station.h | 69 + src/mpgame/IK.cpp | 1085 + src/mpgame/IK.h | 178 + src/mpgame/Icon.cpp | 120 + src/mpgame/Icon.h | 58 + src/mpgame/IconManager.cpp | 229 + src/mpgame/IconManager.h | 54 + src/mpgame/Instance.cpp | 225 + src/mpgame/Instance.h | 71 + src/mpgame/Item.cpp | 2410 ++ src/mpgame/Item.h | 387 + src/mpgame/Lagometer.cpp | 83 + src/mpgame/Lagometer.h | 45 + src/mpgame/Light.cpp | 1509 ++ src/mpgame/Light.h | 170 + src/mpgame/LipSync.cpp | 491 + src/mpgame/LipSync.h | 111 + src/mpgame/Misc.cpp | 4407 ++++ src/mpgame/Misc.h | 997 + src/mpgame/Moveable.cpp | 1371 ++ src/mpgame/Moveable.h | 203 + src/mpgame/Mover.cpp | 5997 +++++ src/mpgame/Mover.h | 730 + src/mpgame/MultiplayerGame.cpp | 9299 ++++++++ src/mpgame/MultiplayerGame.h | 972 + src/mpgame/Playback.cpp | 441 + src/mpgame/Player.cpp | 14180 +++++++++++ src/mpgame/Player.h | 1345 ++ src/mpgame/PlayerView.cpp | 805 + src/mpgame/PlayerView.h | 166 + src/mpgame/Player_Cheats.cpp | 40 + src/mpgame/Player_States.cpp | 965 + src/mpgame/Projectile.cpp | 2308 ++ src/mpgame/Projectile.h | 387 + src/mpgame/Pvs.cpp | 1462 ++ src/mpgame/Pvs.h | 127 + src/mpgame/SecurityCamera.cpp | 602 + src/mpgame/SecurityCamera.h | 94 + src/mpgame/Sound.cpp | 394 + src/mpgame/Sound.h | 81 + src/mpgame/SplineMover.cpp | 3313 +++ src/mpgame/SplineMover.h | 518 + src/mpgame/Target.cpp | 2345 ++ src/mpgame/Target.h | 727 + src/mpgame/TramGate.cpp | 335 + src/mpgame/TramGate.h | 75 + src/mpgame/Trigger.cpp | 1639 ++ src/mpgame/Trigger.h | 316 + src/mpgame/Weapon.cpp | 3370 +++ src/mpgame/Weapon.h | 516 + src/mpgame/WorldSpawn.cpp | 131 + src/mpgame/WorldSpawn.h | 51 + src/mpgame/ai/AAS.cpp | 486 + src/mpgame/ai/AAS.h | 186 + src/mpgame/ai/AAS_Find.cpp | 364 + src/mpgame/ai/AAS_Find.h | 136 + src/mpgame/ai/AAS_debug.cpp | 932 + src/mpgame/ai/AAS_local.h | 216 + src/mpgame/ai/AAS_pathing.cpp | 757 + src/mpgame/ai/AAS_routing.cpp | 1447 ++ src/mpgame/ai/AAS_tactical.cpp | 1789 ++ src/mpgame/ai/AAS_tactical.h | 93 + src/mpgame/ai/AI.cpp | 5175 ++++ src/mpgame/ai/AI.h | 1397 ++ src/mpgame/ai/AI_Actions.cpp | 615 + src/mpgame/ai/AI_Announcements.cpp | 578 + src/mpgame/ai/AI_Debug.cpp | 354 + src/mpgame/ai/AI_Manager.cpp | 790 + src/mpgame/ai/AI_Manager.h | 260 + src/mpgame/ai/AI_Medic.cpp | 860 + src/mpgame/ai/AI_Medic.h | 116 + src/mpgame/ai/AI_Move.cpp | 4856 ++++ src/mpgame/ai/AI_Move.h | 319 + src/mpgame/ai/AI_States.cpp | 2236 ++ src/mpgame/ai/AI_Tactical.cpp | 1078 + src/mpgame/ai/AI_Tactical.h | 158 + src/mpgame/ai/AI_Util.cpp | 1001 + src/mpgame/ai/AI_Util.h | 256 + src/mpgame/ai/AI_events.cpp | 1169 + src/mpgame/ai/AI_pathing.cpp | 1649 ++ src/mpgame/ai/Monster_Berserker.cpp | 488 + src/mpgame/ai/Monster_BossBuddy.cpp | 685 + src/mpgame/ai/Monster_BossMakron.cpp | 2371 ++ src/mpgame/ai/Monster_ConvoyGround.cpp | 626 + src/mpgame/ai/Monster_ConvoyHover.cpp | 654 + src/mpgame/ai/Monster_FailedTransfer.cpp | 145 + src/mpgame/ai/Monster_Fatty.cpp | 443 + src/mpgame/ai/Monster_Gladiator.cpp | 925 + src/mpgame/ai/Monster_Grunt.cpp | 341 + src/mpgame/ai/Monster_Gunner.cpp | 471 + src/mpgame/ai/Monster_Harvester.cpp | 1214 + src/mpgame/ai/Monster_HarvesterDispersal.cpp | 418 + src/mpgame/ai/Monster_HeavyHoverTank.cpp | 557 + src/mpgame/ai/Monster_IronMaiden.cpp | 554 + src/mpgame/ai/Monster_LightTank.cpp | 633 + src/mpgame/ai/Monster_NetworkGuardian.cpp | 845 + src/mpgame/ai/Monster_RepairBot.cpp | 399 + src/mpgame/ai/Monster_Scientist.cpp | 99 + src/mpgame/ai/Monster_Sentry.cpp | 435 + src/mpgame/ai/Monster_SlimyTransfer.cpp | 228 + src/mpgame/ai/Monster_StreamProtector.cpp | 561 + src/mpgame/ai/Monster_StroggFlyer.cpp | 386 + src/mpgame/ai/Monster_StroggHover.cpp | 1471 ++ src/mpgame/ai/Monster_StroggMarine.cpp | 776 + src/mpgame/ai/Monster_TeleportDropper.cpp | 559 + src/mpgame/ai/Monster_Turret.cpp | 227 + src/mpgame/ai/Monster_TurretFlying.cpp | 320 + src/mpgame/ai/VehicleAI.cpp | 266 + src/mpgame/ai/VehicleAI.h | 120 + src/mpgame/anim/Anim.cpp | 1205 + src/mpgame/anim/Anim.h | 825 + src/mpgame/anim/Anim_Blend.cpp | 6039 +++++ src/mpgame/anim/Anim_Import.cpp | 627 + src/mpgame/anim/Anim_Testmodel.cpp | 964 + src/mpgame/anim/Anim_Testmodel.h | 94 + src/mpgame/client/ClientAFEntity.cpp | 720 + src/mpgame/client/ClientAFEntity.h | 150 + src/mpgame/client/ClientEffect.cpp | 521 + src/mpgame/client/ClientEffect.h | 126 + src/mpgame/client/ClientEntity.cpp | 675 + src/mpgame/client/ClientEntity.h | 243 + src/mpgame/client/ClientModel.cpp | 474 + src/mpgame/client/ClientModel.h | 116 + src/mpgame/client/ClientMoveable.cpp | 403 + src/mpgame/client/ClientMoveable.h | 94 + src/mpgame/gamesys/Callbacks.cpp | 2624 ++ src/mpgame/gamesys/Class.cpp | 1458 ++ src/mpgame/gamesys/Class.h | 550 + src/mpgame/gamesys/DebugGraph.cpp | 92 + src/mpgame/gamesys/DebugGraph.h | 37 + src/mpgame/gamesys/Event.cpp | 837 + src/mpgame/gamesys/Event.h | 208 + src/mpgame/gamesys/NoGameTypeInfo.h | 79 + src/mpgame/gamesys/SaveGame.cpp | 2124 ++ src/mpgame/gamesys/SaveGame.h | 206 + src/mpgame/gamesys/State.cpp | 448 + src/mpgame/gamesys/State.h | 181 + src/mpgame/gamesys/SysCmds.cpp | 3283 +++ src/mpgame/gamesys/SysCmds.h | 34 + src/mpgame/gamesys/SysCvar.cpp | 709 + src/mpgame/gamesys/SysCvar.h | 452 + src/mpgame/mp/Buying.cpp | 92 + src/mpgame/mp/Buying.h | 61 + src/mpgame/mp/CTF.cpp | 332 + src/mpgame/mp/CTF.h | 108 + src/mpgame/mp/GameState.cpp | 3157 +++ src/mpgame/mp/GameState.h | 507 + src/mpgame/mp/Tourney.cpp | 1045 + src/mpgame/mp/Tourney.h | 180 + src/mpgame/mp/VoiceComms.cpp | 220 + src/mpgame/mp/stats/StatEvent.cpp | 355 + src/mpgame/mp/stats/StatEvent.h | 385 + src/mpgame/mp/stats/StatManager.cpp | 1369 ++ src/mpgame/mp/stats/StatManager.h | 373 + src/mpgame/mp/stats/StatWindow.cpp | 485 + src/mpgame/mp/stats/StatWindow.h | 60 + src/mpgame/mpgame.def | 2 + src/mpgame/physics/Clip.cpp | 2180 ++ src/mpgame/physics/Clip.h | 430 + src/mpgame/physics/Force.cpp | 89 + src/mpgame/physics/Force.h | 62 + src/mpgame/physics/Force_Constant.cpp | 132 + src/mpgame/physics/Force_Constant.h | 67 + src/mpgame/physics/Force_Drag.cpp | 154 + src/mpgame/physics/Force_Drag.h | 70 + src/mpgame/physics/Force_Field.cpp | 295 + src/mpgame/physics/Force_Field.h | 115 + src/mpgame/physics/Force_Spring.cpp | 186 + src/mpgame/physics/Force_Spring.h | 74 + src/mpgame/physics/Physics.cpp | 79 + src/mpgame/physics/Physics.h | 202 + src/mpgame/physics/Physics_AF.cpp | 8099 +++++++ src/mpgame/physics/Physics_AF.h | 1011 + src/mpgame/physics/Physics_Actor.cpp | 390 + src/mpgame/physics/Physics_Actor.h | 112 + src/mpgame/physics/Physics_Base.cpp | 895 + src/mpgame/physics/Physics_Base.h | 183 + src/mpgame/physics/Physics_Monster.cpp | 861 + src/mpgame/physics/Physics_Monster.h | 152 + src/mpgame/physics/Physics_Parametric.cpp | 1214 + src/mpgame/physics/Physics_Parametric.h | 186 + src/mpgame/physics/Physics_Particle.cpp | 1051 + src/mpgame/physics/Physics_Particle.h | 176 + src/mpgame/physics/Physics_Player.cpp | 2281 ++ src/mpgame/physics/Physics_Player.h | 221 + src/mpgame/physics/Physics_RigidBody.cpp | 1646 ++ src/mpgame/physics/Physics_RigidBody.h | 195 + src/mpgame/physics/Physics_Static.cpp | 905 + src/mpgame/physics/Physics_Static.h | 178 + src/mpgame/physics/Physics_StaticMulti.cpp | 1111 + src/mpgame/physics/Physics_StaticMulti.h | 166 + src/mpgame/physics/Physics_VehicleMonster.cpp | 60 + src/mpgame/physics/Physics_VehicleMonster.h | 48 + src/mpgame/physics/Push.cpp | 1526 ++ src/mpgame/physics/Push.h | 109 + src/mpgame/script/ScriptFuncUtility.cpp | 493 + src/mpgame/script/ScriptFuncUtility.h | 95 + src/mpgame/script/Script_Compiler.cpp | 2666 +++ src/mpgame/script/Script_Compiler.h | 275 + src/mpgame/script/Script_Interpreter.cpp | 1914 ++ src/mpgame/script/Script_Interpreter.h | 277 + src/mpgame/script/Script_Program.cpp | 2559 ++ src/mpgame/script/Script_Program.h | 823 + src/mpgame/script/Script_Thread.cpp | 2332 ++ src/mpgame/script/Script_Thread.h | 388 + src/mpgame/spawner.cpp | 624 + src/mpgame/spawner.h | 144 + src/mpgame/vehicle/Vehicle.cpp | 1706 ++ src/mpgame/vehicle/Vehicle.h | 440 + src/mpgame/vehicle/VehicleAnimated.cpp | 260 + src/mpgame/vehicle/VehicleAnimated.h | 73 + src/mpgame/vehicle/VehicleController.cpp | 325 + src/mpgame/vehicle/VehicleController.h | 93 + src/mpgame/vehicle/VehicleDriver.cpp | 859 + src/mpgame/vehicle/VehicleDriver.h | 209 + src/mpgame/vehicle/VehicleMonster.cpp | 212 + src/mpgame/vehicle/VehicleMonster.h | 69 + src/mpgame/vehicle/VehicleParts.cpp | 2410 ++ src/mpgame/vehicle/VehicleParts.h | 468 + src/mpgame/vehicle/VehiclePosition.cpp | 869 + src/mpgame/vehicle/VehicleRigid.cpp | 187 + src/mpgame/vehicle/VehicleRigid.h | 69 + src/mpgame/vehicle/VehicleSpline.cpp | 147 + src/mpgame/vehicle/VehicleSpline.h | 58 + src/mpgame/vehicle/VehicleStatic.cpp | 142 + src/mpgame/vehicle/VehicleStatic.h | 59 + src/mpgame/vehicle/Vehicle_DropPod.cpp | 201 + src/mpgame/vehicle/Vehicle_Walker.cpp | 617 + src/mpgame/weapon/WeaponBlaster.cpp | 511 + src/mpgame/weapon/WeaponDarkMatterGun.cpp | 494 + src/mpgame/weapon/WeaponGauntlet.cpp | 539 + src/mpgame/weapon/WeaponGrenadeLauncher.cpp | 226 + src/mpgame/weapon/WeaponHyperblaster.cpp | 319 + src/mpgame/weapon/WeaponLightningGun.cpp | 1007 + src/mpgame/weapon/WeaponMachinegun.cpp | 351 + src/mpgame/weapon/WeaponNailgun.cpp | 953 + src/mpgame/weapon/WeaponNapalmGun.cpp | 467 + src/mpgame/weapon/WeaponRailgun.cpp | 294 + src/mpgame/weapon/WeaponRocketLauncher.cpp | 607 + src/mpgame/weapon/WeaponShotgun.cpp | 313 + src/openal/docs/ChangeLog | 378 + src/openal/docs/Makefile | 148 + src/openal/docs/alc-context.sgml | 846 + src/openal/docs/api-reference.html | 15 + src/openal/docs/app-annotations.sgml | 297 + src/openal/docs/app-constants.sgml | 43 + src/openal/docs/app-extensionprocess.sgml | 36 + src/openal/docs/app-extensions.sgml | 754 + src/openal/docs/architecture.html | 16 + src/openal/docs/chp-buffers.sgml | 691 + src/openal/docs/chp-introduction.sgml | 304 + src/openal/docs/chp-multichannel.sgml | 261 + src/openal/docs/chp-operation.sgml | 977 + src/openal/docs/chp-queueing.sgml | 2 + src/openal/docs/chp-rendering.sgml | 2076 ++ src/openal/docs/chp-state.sgml | 730 + src/openal/docs/ent-examples.sgml | 5 + src/openal/docs/ent-extensions.sgml | 3 + src/openal/docs/ent-marks-annotated.sgml | 12 + src/openal/docs/ent-marks-full.sgml | 12 + src/openal/docs/ent-marks-reference.sgml | 12 + src/openal/docs/ent-marks-specification.sgml | 12 + src/openal/docs/ent-names.sgml | 43 + src/openal/docs/index.html | 21 + src/openal/docs/oalspecs.sgml | 109 + src/openal/docs/sec-bookinfo.sgml | 53 + src/openal/docs/specification.html | 18 + src/openal/docs/white-paper.html | 16 + src/openal/idal.cpp | 185 + src/openal/idal.h | 89 + src/openal/idal.py | 103 + src/openal/include/al.h | 515 + src/openal/include/alc.h | 115 + src/openal/include/alctypes.h | 149 + src/openal/include/altypes.h | 357 + src/openal/include/alu.h | 58 + src/openal/include/eax2.h | 369 + src/openal/include/eax3.h | 571 + src/openal/include/eax4.h | 1599 ++ src/openal/include/eax5.h | 1816 ++ src/openal/include/eaxac3.h | 256 + src/openal/include/eaxman.h | 195 + src/openal/include/efxlib.h | 85 + src/openal/osx/OpenAL.framework/Headers | 1 + src/openal/osx/OpenAL.framework/OpenAL | 1 + src/openal/osx/OpenAL.framework/Resources | 1 + .../OpenAL.framework/Versions/A/Headers/al.h | 522 + .../OpenAL.framework/Versions/A/Headers/alc.h | 112 + .../Versions/A/Headers/alctypes.h | 189 + .../Versions/A/Headers/altypes.h | 350 + .../Versions/A/Headers/alut.h | 79 + .../osx/OpenAL.framework/Versions/A/OpenAL | Bin 0 -> 256696 bytes .../Resources/English.lproj/InfoPlist.strings | Bin 0 -> 486 bytes .../Versions/A/Resources/Info.plist | 26 + .../Versions/A/Resources/READ_ME | 86 + .../Versions/A/Resources/pbdevelopment.plist | 8 + .../osx/OpenAL.framework/Versions/Current | 1 + src/openal/stubs.cpp | 131 + src/renderer/Cinematic.h | 156 + src/renderer/GuiModel.h | 65 + src/renderer/Image.h | 514 + src/renderer/Image_stats.cpp | 94 + src/renderer/Interaction.h | 180 + src/renderer/Material.h | 915 + src/renderer/MegaTexture.h | 95 + src/renderer/Model.h | 591 + src/renderer/ModelDecal.h | 122 + src/renderer/ModelManager.h | 133 + src/renderer/ModelOverlay.h | 89 + src/renderer/Model_ase.h | 91 + src/renderer/Model_local.h | 393 + src/renderer/Model_lwo.h | 672 + src/renderer/Model_ma.h | 141 + src/renderer/Model_md3.h | 142 + src/renderer/RenderSystem.h | 399 + src/renderer/RenderWorld.h | 652 + src/renderer/RenderWorld_local.h | 296 + src/renderer/Shaders.cpp | 489 + src/renderer/Shaders.h | 134 + src/renderer/VertexCache.h | 139 + src/renderer/cg_explicit.h | 862 + src/renderer/cinematic.cpp | 1837 ++ src/renderer/draw_arb.cpp | 530 + src/renderer/draw_arb2.cpp | 868 + src/renderer/draw_common.cpp | 1769 ++ src/renderer/draw_nv10.cpp | 651 + src/renderer/draw_nv20.cpp | 876 + src/renderer/draw_r200.cpp | 509 + src/renderer/glext.h | 5953 +++++ src/renderer/guimodel.cpp | 651 + src/renderer/image_files.cpp | 1176 + src/renderer/image_init.cpp | 2250 ++ src/renderer/image_load.cpp | 2243 ++ src/renderer/image_process.cpp | 616 + src/renderer/image_program.cpp | 642 + src/renderer/interaction.cpp | 1304 + src/renderer/jpeg-6/jcapimin.c | 252 + src/renderer/jpeg-6/jcapistd.c | 185 + src/renderer/jpeg-6/jccoefct.c | 472 + src/renderer/jpeg-6/jccolor.c | 483 + src/renderer/jpeg-6/jcdctmgr.c | 412 + src/renderer/jpeg-6/jchuff.c | 870 + src/renderer/jpeg-6/jchuff.h | 58 + src/renderer/jpeg-6/jcinit.c | 96 + src/renderer/jpeg-6/jcmainct.c | 317 + src/renderer/jpeg-6/jcmarker.c | 663 + src/renderer/jpeg-6/jcmaster.c | 602 + src/renderer/jpeg-6/jcomapi.c | 118 + src/renderer/jpeg-6/jconfig.h | 65 + src/renderer/jpeg-6/jcparam.c | 599 + src/renderer/jpeg-6/jcphuff.c | 853 + src/renderer/jpeg-6/jcprepct.c | 395 + src/renderer/jpeg-6/jcsample.c | 543 + src/renderer/jpeg-6/jdapimin.c | 422 + src/renderer/jpeg-6/jdapistd.c | 299 + src/renderer/jpeg-6/jdatadst.c | 175 + src/renderer/jpeg-6/jdatasrc.c | 228 + src/renderer/jpeg-6/jdcoefct.c | 749 + src/renderer/jpeg-6/jdcolor.c | 391 + src/renderer/jpeg-6/jdct.h | 200 + src/renderer/jpeg-6/jddctmgr.c | 294 + src/renderer/jpeg-6/jdhuff.c | 598 + src/renderer/jpeg-6/jdhuff.h | 226 + src/renderer/jpeg-6/jdinput.c | 405 + src/renderer/jpeg-6/jdmainct.c | 536 + src/renderer/jpeg-6/jdmarker.c | 1076 + src/renderer/jpeg-6/jdmaster.c | 581 + src/renderer/jpeg-6/jdmerge.c | 424 + src/renderer/jpeg-6/jdphuff.c | 666 + src/renderer/jpeg-6/jdpostct.c | 314 + src/renderer/jpeg-6/jdsample.c | 502 + src/renderer/jpeg-6/jdtrans.c | 146 + src/renderer/jpeg-6/jerror.c | 258 + src/renderer/jpeg-6/jerror.h | 297 + src/renderer/jpeg-6/jfdctflt.c | 192 + src/renderer/jpeg-6/jfdctfst.c | 248 + src/renderer/jpeg-6/jfdctint.c | 307 + src/renderer/jpeg-6/jidctflt.c | 265 + src/renderer/jpeg-6/jidctfst.c | 391 + src/renderer/jpeg-6/jidctint.c | 412 + src/renderer/jpeg-6/jidctred.c | 421 + src/renderer/jpeg-6/jinclude.h | 115 + src/renderer/jpeg-6/jmemmgr.c | 1139 + src/renderer/jpeg-6/jmemnobs.c | 127 + src/renderer/jpeg-6/jmemsys.h | 206 + src/renderer/jpeg-6/jmorecfg.h | 376 + src/renderer/jpeg-6/jpegint.h | 412 + src/renderer/jpeg-6/jpeglib.h | 1075 + src/renderer/jpeg-6/jquant1.c | 880 + src/renderer/jpeg-6/jquant2.c | 1334 ++ src/renderer/jpeg-6/jutils.c | 199 + src/renderer/jpeg-6/jversion.h | 38 + src/renderer/material.cpp | 2746 +++ src/renderer/megatexture.cpp | 909 + src/renderer/model.cpp | 2385 ++ src/renderer/model_ase.cpp | 908 + src/renderer/model_beam.cpp | 210 + src/renderer/model_liquid.cpp | 529 + src/renderer/model_lwo.cpp | 4134 ++++ src/renderer/model_ma.cpp | 1103 + src/renderer/model_md3.cpp | 367 + src/renderer/model_md5.cpp | 1313 + src/renderer/model_sprite.cpp | 197 + src/renderer/modeldecal.cpp | 504 + src/renderer/modelmanager.cpp | 735 + src/renderer/modeloverlay.cpp | 362 + src/renderer/qgl.h | 655 + src/renderer/qgl_linked.h | 369 + src/renderer/renderentity.cpp | 159 + src/renderer/rendersystem.cpp | 1186 + src/renderer/rendersystem_init.cpp | 2372 ++ src/renderer/renderworld.cpp | 2495 ++ src/renderer/renderworld_demo.cpp | 766 + src/renderer/renderworld_load.cpp | 714 + src/renderer/renderworld_portals.cpp | 1113 + src/renderer/rvGLSLShader.cpp | 230 + src/renderer/rvIndexBuffer.cpp | 283 + src/renderer/rvIndexBuffer.h | 81 + src/renderer/rvMesh.cpp | 1108 + src/renderer/rvMesh.h | 149 + src/renderer/rvPrimBatch.cpp | 1429 ++ src/renderer/rvPrimBatch.h | 146 + src/renderer/rvRenderModelMD5R.cpp | 1548 ++ src/renderer/rvRenderModelMD5R.h | 151 + src/renderer/rvSpecial.cpp | 620 + src/renderer/rvSpecial.h | 94 + src/renderer/rvTexRenderTarget.cpp | 318 + src/renderer/rvTexRenderTarget.h | 87 + src/renderer/rvVertexBuffer.cpp | 646 + src/renderer/rvVertexBuffer.h | 148 + src/renderer/rvVertexBufferCopy.cpp | 373 + src/renderer/rvVertexFormat.cpp | 366 + src/renderer/rvVertexFormat.h | 142 + src/renderer/simplex.h | 596 + src/renderer/tr_backend.cpp | 694 + src/renderer/tr_deform.cpp | 1332 ++ src/renderer/tr_font.cpp | 660 + src/renderer/tr_guisurf.cpp | 272 + src/renderer/tr_light.cpp | 1540 ++ src/renderer/tr_lightrun.cpp | 872 + src/renderer/tr_local.h | 1851 ++ src/renderer/tr_main.cpp | 1140 + src/renderer/tr_orderindexes.cpp | 214 + src/renderer/tr_polytope.cpp | 107 + src/renderer/tr_render.cpp | 965 + src/renderer/tr_rendertools.cpp | 2395 ++ src/renderer/tr_shadowbounds.cpp | 634 + src/renderer/tr_stencilshadow.cpp | 1336 ++ src/renderer/tr_subview.cpp | 571 + src/renderer/tr_trace.cpp | 461 + src/renderer/tr_trisurf.cpp | 2319 ++ src/renderer/tr_turboshadow.cpp | 335 + src/renderer/vertexcache.cpp | 560 + src/renderer/wglext.h | 636 + src/sound/default/snd_cache.cpp | 779 + src/sound/default/snd_common.cpp | 41 + src/sound/default/snd_decoder.cpp | 596 + src/sound/default/snd_efxfile.cpp | 228 + src/sound/default/snd_emitter.cpp | 1372 ++ src/sound/default/snd_reverb.cpp | 40 + src/sound/default/snd_system.cpp | 1629 ++ src/sound/default/snd_voice.cpp | 41 + src/sound/default/snd_wavefile.cpp | 354 + src/sound/default/snd_world.cpp | 2247 ++ src/sound/default/win_snd.cpp | 847 + src/sound/oggvorbis/ogg/ogg.h | 226 + src/sound/oggvorbis/ogg/os_types.h | 169 + src/sound/oggvorbis/oggsrc/bitwise.c | 806 + src/sound/oggvorbis/oggsrc/framing.c | 1771 ++ src/sound/oggvorbis/vorbis/codec.h | 264 + src/sound/oggvorbis/vorbis/vorbisenc.h | 117 + src/sound/oggvorbis/vorbis/vorbisfile.h | 168 + src/sound/oggvorbis/vorbissrc/analysis.c | 143 + src/sound/oggvorbis/vorbissrc/backends.h | 169 + src/sound/oggvorbis/vorbissrc/bitrate.c | 555 + src/sound/oggvorbis/vorbissrc/bitrate.h | 108 + src/sound/oggvorbis/vorbissrc/block.c | 995 + .../books/coupled/res_books_stereo.h | 19104 +++++++++++++++ .../vorbissrc/books/floor/floor_books.h | 1479 ++ .../books/uncoupled/res_books_uncoupled.h | 10931 +++++++++ src/sound/oggvorbis/vorbissrc/codebook.c | 639 + src/sound/oggvorbis/vorbissrc/codebook.h | 184 + .../oggvorbis/vorbissrc/codec_internal.h | 158 + src/sound/oggvorbis/vorbissrc/envelope.c | 406 + src/sound/oggvorbis/vorbissrc/envelope.h | 105 + src/sound/oggvorbis/vorbissrc/floor0.c | 247 + src/sound/oggvorbis/vorbissrc/floor1.c | 1113 + src/sound/oggvorbis/vorbissrc/highlevel.h | 82 + src/sound/oggvorbis/vorbissrc/info.c | 627 + src/sound/oggvorbis/vorbissrc/lookup.c | 118 + src/sound/oggvorbis/vorbissrc/lookup.h | 56 + src/sound/oggvorbis/vorbissrc/lookup_data.h | 213 + src/sound/oggvorbis/vorbissrc/lpc.c | 173 + src/sound/oggvorbis/vorbissrc/lpc.h | 53 + src/sound/oggvorbis/vorbissrc/lsp.c | 479 + src/sound/oggvorbis/vorbissrc/lsp.h | 52 + src/sound/oggvorbis/vorbissrc/mapping0.c | 787 + src/sound/oggvorbis/vorbissrc/masking.h | 809 + src/sound/oggvorbis/vorbissrc/mdct.c | 588 + src/sound/oggvorbis/vorbissrc/mdct.h | 107 + src/sound/oggvorbis/vorbissrc/misc.h | 76 + .../oggvorbis/vorbissrc/modes/floor_all.h | 250 + .../oggvorbis/vorbissrc/modes/psych_11.h | 75 + .../oggvorbis/vorbissrc/modes/psych_16.h | 153 + .../oggvorbis/vorbissrc/modes/psych_44.h | 556 + src/sound/oggvorbis/vorbissrc/modes/psych_8.h | 126 + .../oggvorbis/vorbissrc/modes/residue_16.h | 187 + .../oggvorbis/vorbissrc/modes/residue_44.h | 278 + .../oggvorbis/vorbissrc/modes/residue_44u.h | 320 + .../oggvorbis/vorbissrc/modes/residue_8.h | 121 + .../oggvorbis/vorbissrc/modes/setup_11.h | 165 + .../oggvorbis/vorbissrc/modes/setup_16.h | 173 + .../oggvorbis/vorbissrc/modes/setup_22.h | 152 + .../oggvorbis/vorbissrc/modes/setup_32.h | 270 + .../oggvorbis/vorbissrc/modes/setup_44.h | 200 + .../oggvorbis/vorbissrc/modes/setup_44u.h | 153 + src/sound/oggvorbis/vorbissrc/modes/setup_8.h | 170 + src/sound/oggvorbis/vorbissrc/modes/setup_X.h | 353 + src/sound/oggvorbis/vorbissrc/os.h | 171 + src/sound/oggvorbis/vorbissrc/psy.c | 1172 + src/sound/oggvorbis/vorbissrc/psy.h | 199 + src/sound/oggvorbis/vorbissrc/registry.c | 70 + src/sound/oggvorbis/vorbissrc/registry.h | 56 + src/sound/oggvorbis/vorbissrc/res0.c | 930 + src/sound/oggvorbis/vorbissrc/scales.h | 112 + src/sound/oggvorbis/vorbissrc/sharedbook.c | 758 + src/sound/oggvorbis/vorbissrc/smallft.c | 1278 + src/sound/oggvorbis/vorbissrc/smallft.h | 58 + src/sound/oggvorbis/vorbissrc/synthesis.c | 194 + src/sound/oggvorbis/vorbissrc/vorbisenc.c | 1158 + src/sound/oggvorbis/vorbissrc/vorbisfile.c | 2005 ++ src/sound/oggvorbis/vorbissrc/window.h | 50 + src/sound/oggvorbis/vorbissrc/windowvb.c | 2160 ++ src/sound/snd_local.h | 1283 + src/sound/snd_shader.cpp | 708 + src/sound/sound.h | 556 + src/sys/AutoVersion.h | 37 + src/sys/linux/qgl_enforce.h | 370 + src/sys/osx/Resources/Game.plist | 22 + src/sys/osx/Resources/Info.plist | 30 + .../game.xcodeproj/project.pbxproj | 1245 + .../idlib.xcodeproj/project.pbxproj | 905 + .../mpgame.xcodeproj/project.pbxproj | 1242 + src/sys/osx/apple_bool.h | 41 + src/sys/osx/q4sdk.xcodeproj/project.pbxproj | 231 + src/sys/scons/SConscript.game | 44 + src/sys/scons/SConscript.idlib | 41 + src/sys/scons/scons_utils.py | 194 + src/sys/sys_local.cpp | 272 + src/sys/sys_local.h | 120 + src/sys/sys_public.h | 976 + src/sys/win32/eax.h | 557 + src/sys/win32/rc/AFEditor_resource.h | 365 + src/sys/win32/rc/Common_resource.h | 54 + src/sys/win32/rc/CreateResourceIDs.h | 38 + src/sys/win32/rc/Debugger_resource.h | 86 + src/sys/win32/rc/DeclEditor_resource.h | 63 + src/sys/win32/rc/GuiEd_resource.h | 203 + src/sys/win32/rc/MaterialEditor_Resource.h | 126 + src/sys/win32/rc/PDAEditor_resource.h | 72 + src/sys/win32/rc/ParticleEditor_resource.h | 147 + src/sys/win32/rc/PropTree_resource.h | 49 + src/sys/win32/rc/Radiant_resource.h | 884 + src/sys/win32/rc/ScriptEditor_resource.h | 43 + src/sys/win32/rc/SoundEditor_resource.h | 84 + src/sys/win32/rc/doom_resource.h | 40 + src/sys/win32/win_cpu.cpp | 926 + src/sys/win32/win_glimp.cpp | 1237 + src/sys/win32/win_input.cpp | 1066 + src/sys/win32/win_local.h | 182 + src/sys/win32/win_main.cpp | 1631 ++ src/sys/win32/win_net.cpp | 1194 + src/sys/win32/win_qgl.cpp | 2825 +++ src/sys/win32/win_shared.cpp | 805 + src/sys/win32/win_syscon.cpp | 553 + src/sys/win32/win_taskkeyhook.cpp | 152 + src/sys/win32/win_wndproc.cpp | 452 + src/tests/abi/BSE_ABI.cpp | 53 + src/tests/abi/RendererRaven_ABI.cpp | 72 + src/tools/reconstruction/CMakeLists.txt | 22 + .../Export-CompilandSourceMap.ps1 | 72 + .../Export-EvidenceManifest.ps1 | 44 + .../Export-ReconstructionLedger.ps1 | 104 + src/tools/reconstruction/Seed-DoomHeaders.ps1 | 53 + .../Seed-DoomImplementations.ps1 | 121 + src/tools/reconstruction/pdb_inventory.cpp | 611 + src/ui/BindWindow.h | 50 + src/ui/ChoiceWindow.h | 78 + src/ui/DeviceContext.h | 186 + src/ui/EditWindow.h | 92 + src/ui/FieldWindow.h | 50 + src/ui/GameBearShootWindow.h | 129 + src/ui/GameBustOutWindow.h | 182 + src/ui/GameSSDWindow.h | 612 + src/ui/GameWindow.h | 34 + src/ui/GuiScript.h | 101 + src/ui/ListGUI.h | 63 + src/ui/ListGUILocal.h | 71 + src/ui/ListWindow.h | 96 + src/ui/MarkerWindow.h | 70 + src/ui/Rectangle.h | 218 + src/ui/RegExp.h | 107 + src/ui/RegExp_old.h | 83 + src/ui/RenderWindow.h | 71 + src/ui/SimpleWindow.h | 102 + src/ui/SliderWindow.h | 89 + src/ui/UserInterface.h | 239 + src/ui/UserInterfaceLocal.h | 162 + src/ui/Window.h | 476 + src/ui/Winvar.h | 936 + src/ui/bindwindow.cpp | 124 + src/ui/choicewindow.cpp | 393 + src/ui/devicecontext.cpp | 1195 + src/ui/editwindow.cpp | 645 + src/ui/fieldwindow.cpp | 101 + src/ui/gamewindow.cpp | 48 + src/ui/guiscript.cpp | 709 + src/ui/listgui.cpp | 197 + src/ui/listwindow.cpp | 644 + src/ui/markerwindow.cpp | 355 + src/ui/regexp.cpp | 398 + src/ui/renderwindow.cpp | 208 + src/ui/simplewindow.cpp | 490 + src/ui/sliderwindow.cpp | 412 + src/ui/userinterface.cpp | 708 + src/ui/window.cpp | 4368 ++++ src/ui/winvar.cpp | 79 + 1197 files changed, 911699 insertions(+) create mode 100644 src/.gitignore create mode 100644 src/CMakeLists.txt create mode 100644 src/CMakePresets.json create mode 100644 src/MayaImport/maya_main.h create mode 100644 src/aas/AASBuild_local.h create mode 100644 src/aas/AASCluster.h create mode 100644 src/aas/AASFile.cpp create mode 100644 src/aas/AASFile.h create mode 100644 src/aas/AASFileManager.h create mode 100644 src/aas/AASFile_legacy.inc create mode 100644 src/aas/AASFile_local.h create mode 100644 src/aas/AASReach.h create mode 100644 src/aas/Brush.h create mode 100644 src/aas/BrushBSP.h create mode 100644 src/aas/aasfile_optimize.cpp create mode 100644 src/aas/aasfile_sample.cpp create mode 100644 src/aas/aasfilemanager.cpp create mode 100644 src/bse/BSE.h create mode 100644 src/bse/BSEInterface.h create mode 100644 src/bse/BSE_Bounds.cpp create mode 100644 src/bse/BSE_Effect.cpp create mode 100644 src/bse/BSE_EffectTemplate.cpp create mode 100644 src/bse/BSE_Electricity.cpp create mode 100644 src/bse/BSE_Envelope.h create mode 100644 src/bse/BSE_Envelopes.cpp create mode 100644 src/bse/BSE_Light.cpp create mode 100644 src/bse/BSE_Manager.cpp create mode 100644 src/bse/BSE_ParseParticle2.cpp create mode 100644 src/bse/BSE_Particle.cpp create mode 100644 src/bse/BSE_Particle.h create mode 100644 src/bse/BSE_Render.cpp create mode 100644 src/bse/BSE_Segment.cpp create mode 100644 src/bse/BSE_SegmentTemplate.cpp create mode 100644 src/bse/BSE_SpawnDomains.cpp create mode 100644 src/bse/BSE_SpawnDomains.h create mode 100644 src/cm/CollisionModel.cpp create mode 100644 src/cm/CollisionModel.h create mode 100644 src/cm/CollisionModel_local.h create mode 100644 src/cm/collisionmodel_contacts.cpp create mode 100644 src/cm/collisionmodel_contacts_legacy.inc create mode 100644 src/cm/collisionmodel_contents.cpp create mode 100644 src/cm/collisionmodel_contents_legacy.inc create mode 100644 src/cm/collisionmodel_debug.cpp create mode 100644 src/cm/collisionmodel_files.cpp create mode 100644 src/cm/collisionmodel_load.cpp create mode 100644 src/cm/collisionmodel_load_legacy.inc create mode 100644 src/cm/collisionmodel_rotate.cpp create mode 100644 src/cm/collisionmodel_trace.cpp create mode 100644 src/cm/collisionmodel_trace_legacy.inc create mode 100644 src/cm/collisionmodel_translate.cpp create mode 100644 src/cm/collisionmodel_translate_legacy.inc create mode 100644 src/curl/include/Makefile.am create mode 100644 src/curl/include/Makefile.in create mode 100644 src/curl/include/README create mode 100644 src/curl/include/curl/Makefile.am create mode 100644 src/curl/include/curl/Makefile.in create mode 100644 src/curl/include/curl/curl.h create mode 100644 src/curl/include/curl/easy.h create mode 100644 src/curl/include/curl/mprintf.h create mode 100644 src/curl/include/curl/multi.h create mode 100644 src/curl/include/curl/stdcheaders.h create mode 100644 src/curl/include/curl/types.h create mode 100644 src/engine/CMakeLists.txt create mode 100644 src/framework/BuildDefines.h create mode 100644 src/framework/BuildVersion.h create mode 100644 src/framework/CVarSystem.h create mode 100644 src/framework/CmdSystem.h create mode 100644 src/framework/Common.h create mode 100644 src/framework/Compressor.h create mode 100644 src/framework/Console.h create mode 100644 src/framework/DeclFX.h create mode 100644 src/framework/DeclPDA.h create mode 100644 src/framework/DeclParticle.h create mode 100644 src/framework/DeclPlayerModel.cpp create mode 100644 src/framework/DeclPlayerModel.h create mode 100644 src/framework/DemoChecksum.h create mode 100644 src/framework/DemoFile.h create mode 100644 src/framework/EditField.h create mode 100644 src/framework/EventLoop.h create mode 100644 src/framework/File.h create mode 100644 src/framework/FileSystem.h create mode 100644 src/framework/KeyInput.h create mode 100644 src/framework/Session.h create mode 100644 src/framework/Session_local.h create mode 100644 src/framework/Unzip.h create mode 100644 src/framework/UsercmdGen.h create mode 100644 src/framework/async/AsyncClient.h create mode 100644 src/framework/async/AsyncNetwork.h create mode 100644 src/framework/async/AsyncServer.h create mode 100644 src/framework/async/MsgChannel.h create mode 100644 src/framework/async/NetworkSystem.h create mode 100644 src/framework/async/ServerScan.h create mode 100644 src/framework/async/asyncclient.cpp create mode 100644 src/framework/async/asyncnetwork.cpp create mode 100644 src/framework/async/asyncserver.cpp create mode 100644 src/framework/async/msgchannel.cpp create mode 100644 src/framework/async/networksystem.cpp create mode 100644 src/framework/async/serverscan.cpp create mode 100644 src/framework/cmdsystem.cpp create mode 100644 src/framework/common.cpp create mode 100644 src/framework/compressor.cpp create mode 100644 src/framework/console.cpp create mode 100644 src/framework/cvarsystem.cpp create mode 100644 src/framework/declAF.h create mode 100644 src/framework/declEntityDef.h create mode 100644 src/framework/declLipSync.cpp create mode 100644 src/framework/declLipSync.h create mode 100644 src/framework/declManager.h create mode 100644 src/framework/declMatType.cpp create mode 100644 src/framework/declMatType.h create mode 100644 src/framework/declPlayback.cpp create mode 100644 src/framework/declPlayback.h create mode 100644 src/framework/declSkin.h create mode 100644 src/framework/declTable.h create mode 100644 src/framework/declaf.cpp create mode 100644 src/framework/declentitydef.cpp create mode 100644 src/framework/declmanager.cpp create mode 100644 src/framework/declpda.cpp create mode 100644 src/framework/declskin.cpp create mode 100644 src/framework/decltable.cpp create mode 100644 src/framework/demofile.cpp create mode 100644 src/framework/editfield.cpp create mode 100644 src/framework/eventloop.cpp create mode 100644 src/framework/file.cpp create mode 100644 src/framework/filesystem.cpp create mode 100644 src/framework/keyinput.cpp create mode 100644 src/framework/licensee.h create mode 100644 src/framework/session.cpp create mode 100644 src/framework/session_menu.cpp create mode 100644 src/framework/unzip.cpp create mode 100644 src/framework/usercmdgen.cpp create mode 100644 src/game/AF.cpp create mode 100644 src/game/AF.h create mode 100644 src/game/AFEntity.cpp create mode 100644 src/game/AFEntity.h create mode 100644 src/game/Actor.cpp create mode 100644 src/game/Actor.h create mode 100644 src/game/BrittleFracture.cpp create mode 100644 src/game/BrittleFracture.h create mode 100644 src/game/Camera.cpp create mode 100644 src/game/Camera.h create mode 100644 src/game/Effect.cpp create mode 100644 src/game/Effect.h create mode 100644 src/game/Entity.cpp create mode 100644 src/game/Entity.h create mode 100644 src/game/FreeView.cpp create mode 100644 src/game/FreeView.h create mode 100644 src/game/Game.def create mode 100644 src/game/Game.h create mode 100644 src/game/GameEdit.cpp create mode 100644 src/game/GameEdit.h create mode 100644 src/game/Game_Debug.cpp create mode 100644 src/game/Game_Debug.h create mode 100644 src/game/Game_Log.cpp create mode 100644 src/game/Game_Log.h create mode 100644 src/game/Game_local.cpp create mode 100644 src/game/Game_local.h create mode 100644 src/game/Game_network.cpp create mode 100644 src/game/Healing_Station.cpp create mode 100644 src/game/Healing_Station.h create mode 100644 src/game/IK.cpp create mode 100644 src/game/IK.h create mode 100644 src/game/Icon.cpp create mode 100644 src/game/Icon.h create mode 100644 src/game/IconManager.cpp create mode 100644 src/game/IconManager.h create mode 100644 src/game/Instance.cpp create mode 100644 src/game/Instance.h create mode 100644 src/game/Item.cpp create mode 100644 src/game/Item.h create mode 100644 src/game/Light.cpp create mode 100644 src/game/Light.h create mode 100644 src/game/LipSync.cpp create mode 100644 src/game/LipSync.h create mode 100644 src/game/Misc.cpp create mode 100644 src/game/Misc.h create mode 100644 src/game/Moveable.cpp create mode 100644 src/game/Moveable.h create mode 100644 src/game/Mover.cpp create mode 100644 src/game/Mover.h create mode 100644 src/game/MultiplayerGame.cpp create mode 100644 src/game/MultiplayerGame.h create mode 100644 src/game/Playback.cpp create mode 100644 src/game/Player.cpp create mode 100644 src/game/Player.h create mode 100644 src/game/PlayerView.cpp create mode 100644 src/game/PlayerView.h create mode 100644 src/game/Player_Cheats.cpp create mode 100644 src/game/Player_States.cpp create mode 100644 src/game/Projectile.cpp create mode 100644 src/game/Projectile.h create mode 100644 src/game/Pvs.cpp create mode 100644 src/game/Pvs.h create mode 100644 src/game/SecurityCamera.cpp create mode 100644 src/game/SecurityCamera.h create mode 100644 src/game/Sound.cpp create mode 100644 src/game/Sound.h create mode 100644 src/game/SplineMover.cpp create mode 100644 src/game/SplineMover.h create mode 100644 src/game/Target.cpp create mode 100644 src/game/Target.h create mode 100644 src/game/TramGate.cpp create mode 100644 src/game/TramGate.h create mode 100644 src/game/Trigger.cpp create mode 100644 src/game/Trigger.h create mode 100644 src/game/Weapon.cpp create mode 100644 src/game/Weapon.h create mode 100644 src/game/WorldSpawn.cpp create mode 100644 src/game/WorldSpawn.h create mode 100644 src/game/ai/AAS.cpp create mode 100644 src/game/ai/AAS.h create mode 100644 src/game/ai/AAS_Find.cpp create mode 100644 src/game/ai/AAS_Find.h create mode 100644 src/game/ai/AAS_debug.cpp create mode 100644 src/game/ai/AAS_local.h create mode 100644 src/game/ai/AAS_pathing.cpp create mode 100644 src/game/ai/AAS_routing.cpp create mode 100644 src/game/ai/AAS_tactical.cpp create mode 100644 src/game/ai/AAS_tactical.h create mode 100644 src/game/ai/AI.cpp create mode 100644 src/game/ai/AI.h create mode 100644 src/game/ai/AI_Actions.cpp create mode 100644 src/game/ai/AI_Announcements.cpp create mode 100644 src/game/ai/AI_Debug.cpp create mode 100644 src/game/ai/AI_Manager.cpp create mode 100644 src/game/ai/AI_Manager.h create mode 100644 src/game/ai/AI_Medic.cpp create mode 100644 src/game/ai/AI_Medic.h create mode 100644 src/game/ai/AI_Move.cpp create mode 100644 src/game/ai/AI_Move.h create mode 100644 src/game/ai/AI_States.cpp create mode 100644 src/game/ai/AI_Tactical.cpp create mode 100644 src/game/ai/AI_Tactical.h create mode 100644 src/game/ai/AI_Util.cpp create mode 100644 src/game/ai/AI_Util.h create mode 100644 src/game/ai/AI_events.cpp create mode 100644 src/game/ai/AI_pathing.cpp create mode 100644 src/game/ai/Monster_Berserker.cpp create mode 100644 src/game/ai/Monster_BossBuddy.cpp create mode 100644 src/game/ai/Monster_BossMakron.cpp create mode 100644 src/game/ai/Monster_ConvoyGround.cpp create mode 100644 src/game/ai/Monster_ConvoyHover.cpp create mode 100644 src/game/ai/Monster_FailedTransfer.cpp create mode 100644 src/game/ai/Monster_Fatty.cpp create mode 100644 src/game/ai/Monster_Gladiator.cpp create mode 100644 src/game/ai/Monster_Grunt.cpp create mode 100644 src/game/ai/Monster_Gunner.cpp create mode 100644 src/game/ai/Monster_Harvester.cpp create mode 100644 src/game/ai/Monster_HarvesterDispersal.cpp create mode 100644 src/game/ai/Monster_HeavyHoverTank.cpp create mode 100644 src/game/ai/Monster_IronMaiden.cpp create mode 100644 src/game/ai/Monster_LightTank.cpp create mode 100644 src/game/ai/Monster_NetworkGuardian.cpp create mode 100644 src/game/ai/Monster_RepairBot.cpp create mode 100644 src/game/ai/Monster_Scientist.cpp create mode 100644 src/game/ai/Monster_Sentry.cpp create mode 100644 src/game/ai/Monster_SlimyTransfer.cpp create mode 100644 src/game/ai/Monster_StreamProtector.cpp create mode 100644 src/game/ai/Monster_StroggFlyer.cpp create mode 100644 src/game/ai/Monster_StroggHover.cpp create mode 100644 src/game/ai/Monster_StroggMarine.cpp create mode 100644 src/game/ai/Monster_TeleportDropper.cpp create mode 100644 src/game/ai/Monster_Turret.cpp create mode 100644 src/game/ai/Monster_TurretFlying.cpp create mode 100644 src/game/ai/VehicleAI.cpp create mode 100644 src/game/ai/VehicleAI.h create mode 100644 src/game/anim/Anim.cpp create mode 100644 src/game/anim/Anim.h create mode 100644 src/game/anim/Anim_Blend.cpp create mode 100644 src/game/anim/Anim_Import.cpp create mode 100644 src/game/anim/Anim_Testmodel.cpp create mode 100644 src/game/anim/Anim_Testmodel.h create mode 100644 src/game/client/ClientAFEntity.cpp create mode 100644 src/game/client/ClientAFEntity.h create mode 100644 src/game/client/ClientEffect.cpp create mode 100644 src/game/client/ClientEffect.h create mode 100644 src/game/client/ClientEntity.cpp create mode 100644 src/game/client/ClientEntity.h create mode 100644 src/game/client/ClientModel.cpp create mode 100644 src/game/client/ClientModel.h create mode 100644 src/game/client/ClientMoveable.cpp create mode 100644 src/game/client/ClientMoveable.h create mode 100644 src/game/gamesys/Callbacks.cpp create mode 100644 src/game/gamesys/Class.cpp create mode 100644 src/game/gamesys/Class.h create mode 100644 src/game/gamesys/DebugGraph.cpp create mode 100644 src/game/gamesys/DebugGraph.h create mode 100644 src/game/gamesys/Event.cpp create mode 100644 src/game/gamesys/Event.h create mode 100644 src/game/gamesys/NoGameTypeInfo.h create mode 100644 src/game/gamesys/SaveGame.cpp create mode 100644 src/game/gamesys/SaveGame.h create mode 100644 src/game/gamesys/State.cpp create mode 100644 src/game/gamesys/State.h create mode 100644 src/game/gamesys/SysCmds.cpp create mode 100644 src/game/gamesys/SysCmds.h create mode 100644 src/game/gamesys/SysCvar.cpp create mode 100644 src/game/gamesys/SysCvar.h create mode 100644 src/game/mp/Buying.cpp create mode 100644 src/game/mp/Buying.h create mode 100644 src/game/mp/CTF.cpp create mode 100644 src/game/mp/CTF.h create mode 100644 src/game/mp/GameState.cpp create mode 100644 src/game/mp/GameState.h create mode 100644 src/game/mp/Tourney.cpp create mode 100644 src/game/mp/Tourney.h create mode 100644 src/game/mp/VoiceComms.cpp create mode 100644 src/game/mp/stats/StatEvent.cpp create mode 100644 src/game/mp/stats/StatEvent.h create mode 100644 src/game/mp/stats/StatManager.cpp create mode 100644 src/game/mp/stats/StatManager.h create mode 100644 src/game/mp/stats/StatWindow.cpp create mode 100644 src/game/mp/stats/StatWindow.h create mode 100644 src/game/physics/Clip.cpp create mode 100644 src/game/physics/Clip.h create mode 100644 src/game/physics/Force.cpp create mode 100644 src/game/physics/Force.h create mode 100644 src/game/physics/Force_Constant.cpp create mode 100644 src/game/physics/Force_Constant.h create mode 100644 src/game/physics/Force_Drag.cpp create mode 100644 src/game/physics/Force_Drag.h create mode 100644 src/game/physics/Force_Field.cpp create mode 100644 src/game/physics/Force_Field.h create mode 100644 src/game/physics/Force_Spring.cpp create mode 100644 src/game/physics/Force_Spring.h create mode 100644 src/game/physics/Physics.cpp create mode 100644 src/game/physics/Physics.h create mode 100644 src/game/physics/Physics_AF.cpp create mode 100644 src/game/physics/Physics_AF.h create mode 100644 src/game/physics/Physics_Actor.cpp create mode 100644 src/game/physics/Physics_Actor.h create mode 100644 src/game/physics/Physics_Base.cpp create mode 100644 src/game/physics/Physics_Base.h create mode 100644 src/game/physics/Physics_Monster.cpp create mode 100644 src/game/physics/Physics_Monster.h create mode 100644 src/game/physics/Physics_Parametric.cpp create mode 100644 src/game/physics/Physics_Parametric.h create mode 100644 src/game/physics/Physics_Particle.cpp create mode 100644 src/game/physics/Physics_Particle.h create mode 100644 src/game/physics/Physics_Player.cpp create mode 100644 src/game/physics/Physics_Player.h create mode 100644 src/game/physics/Physics_RigidBody.cpp create mode 100644 src/game/physics/Physics_RigidBody.h create mode 100644 src/game/physics/Physics_Static.cpp create mode 100644 src/game/physics/Physics_Static.h create mode 100644 src/game/physics/Physics_StaticMulti.cpp create mode 100644 src/game/physics/Physics_StaticMulti.h create mode 100644 src/game/physics/Physics_VehicleMonster.cpp create mode 100644 src/game/physics/Physics_VehicleMonster.h create mode 100644 src/game/physics/Push.cpp create mode 100644 src/game/physics/Push.h create mode 100644 src/game/script/ScriptFuncUtility.cpp create mode 100644 src/game/script/ScriptFuncUtility.h create mode 100644 src/game/script/Script_Compiler.cpp create mode 100644 src/game/script/Script_Compiler.h create mode 100644 src/game/script/Script_Interpreter.cpp create mode 100644 src/game/script/Script_Interpreter.h create mode 100644 src/game/script/Script_Program.cpp create mode 100644 src/game/script/Script_Program.h create mode 100644 src/game/script/Script_Thread.cpp create mode 100644 src/game/script/Script_Thread.h create mode 100644 src/game/spawner.cpp create mode 100644 src/game/spawner.h create mode 100644 src/game/vehicle/Vehicle.cpp create mode 100644 src/game/vehicle/Vehicle.h create mode 100644 src/game/vehicle/VehicleAnimated.cpp create mode 100644 src/game/vehicle/VehicleAnimated.h create mode 100644 src/game/vehicle/VehicleController.cpp create mode 100644 src/game/vehicle/VehicleController.h create mode 100644 src/game/vehicle/VehicleDriver.cpp create mode 100644 src/game/vehicle/VehicleDriver.h create mode 100644 src/game/vehicle/VehicleMonster.cpp create mode 100644 src/game/vehicle/VehicleMonster.h create mode 100644 src/game/vehicle/VehicleParts.cpp create mode 100644 src/game/vehicle/VehicleParts.h create mode 100644 src/game/vehicle/VehiclePosition.cpp create mode 100644 src/game/vehicle/VehicleRigid.cpp create mode 100644 src/game/vehicle/VehicleRigid.h create mode 100644 src/game/vehicle/VehicleSpline.cpp create mode 100644 src/game/vehicle/VehicleSpline.h create mode 100644 src/game/vehicle/VehicleStatic.cpp create mode 100644 src/game/vehicle/VehicleStatic.h create mode 100644 src/game/vehicle/Vehicle_DropPod.cpp create mode 100644 src/game/vehicle/Vehicle_Walker.cpp create mode 100644 src/game/weapon/WeaponBlaster.cpp create mode 100644 src/game/weapon/WeaponDarkMatterGun.cpp create mode 100644 src/game/weapon/WeaponGauntlet.cpp create mode 100644 src/game/weapon/WeaponGrenadeLauncher.cpp create mode 100644 src/game/weapon/WeaponHyperblaster.cpp create mode 100644 src/game/weapon/WeaponLightningGun.cpp create mode 100644 src/game/weapon/WeaponMachinegun.cpp create mode 100644 src/game/weapon/WeaponNailgun.cpp create mode 100644 src/game/weapon/WeaponNapalmGun.cpp create mode 100644 src/game/weapon/WeaponRailgun.cpp create mode 100644 src/game/weapon/WeaponRocketLauncher.cpp create mode 100644 src/game/weapon/WeaponShotgun.cpp create mode 100644 src/idlib/AutoPtr.h create mode 100644 src/idlib/Base64.cpp create mode 100644 src/idlib/Base64.h create mode 100644 src/idlib/BitMsg.cpp create mode 100644 src/idlib/BitMsg.h create mode 100644 src/idlib/CMakeLists.txt create mode 100644 src/idlib/CmdArgs.cpp create mode 100644 src/idlib/CmdArgs.h create mode 100644 src/idlib/Dict.cpp create mode 100644 src/idlib/Dict.h create mode 100644 src/idlib/Heap.cpp create mode 100644 src/idlib/Heap.h create mode 100644 src/idlib/LangDict.cpp create mode 100644 src/idlib/LangDict.h create mode 100644 src/idlib/Lexer.cpp create mode 100644 src/idlib/Lexer.h create mode 100644 src/idlib/LexerFactory.cpp create mode 100644 src/idlib/LexerFactory.h create mode 100644 src/idlib/Lib.cpp create mode 100644 src/idlib/Lib.h create mode 100644 src/idlib/MapFile.h create mode 100644 src/idlib/Parser.cpp create mode 100644 src/idlib/Parser.h create mode 100644 src/idlib/Str.cpp create mode 100644 src/idlib/Str.h create mode 100644 src/idlib/TextCompiler.cpp create mode 100644 src/idlib/TextCompiler.h create mode 100644 src/idlib/Timer.cpp create mode 100644 src/idlib/Timer.h create mode 100644 src/idlib/TimingCollection.cpp create mode 100644 src/idlib/TimingCollection.h create mode 100644 src/idlib/Token.cpp create mode 100644 src/idlib/Token.h create mode 100644 src/idlib/algorithms/MultifieldSort.h create mode 100644 src/idlib/bv/Bounds.cpp create mode 100644 src/idlib/bv/Bounds.h create mode 100644 src/idlib/bv/Box.cpp create mode 100644 src/idlib/bv/Box.h create mode 100644 src/idlib/bv/Frustum.cpp create mode 100644 src/idlib/bv/Frustum.h create mode 100644 src/idlib/bv/Sphere.cpp create mode 100644 src/idlib/bv/Sphere.h create mode 100644 src/idlib/containers/BTree.h create mode 100644 src/idlib/containers/BinSearch.h create mode 100644 src/idlib/containers/HashIndex.cpp create mode 100644 src/idlib/containers/HashIndex.h create mode 100644 src/idlib/containers/HashTable.h create mode 100644 src/idlib/containers/Hierarchy.h create mode 100644 src/idlib/containers/LinkList.h create mode 100644 src/idlib/containers/List.h create mode 100644 src/idlib/containers/ListGame.h create mode 100644 src/idlib/containers/Pair.h create mode 100644 src/idlib/containers/PlaneSet.h create mode 100644 src/idlib/containers/Queue.h create mode 100644 src/idlib/containers/Stack.h create mode 100644 src/idlib/containers/StaticList.h create mode 100644 src/idlib/containers/StrList.h create mode 100644 src/idlib/containers/StrPool.h create mode 100644 src/idlib/containers/VectorSet.h create mode 100644 src/idlib/containers/rvBlockPool.h create mode 100644 src/idlib/geometry/DrawVert.h create mode 100644 src/idlib/geometry/JointTransform.cpp create mode 100644 src/idlib/geometry/JointTransform.h create mode 100644 src/idlib/geometry/Surface.cpp create mode 100644 src/idlib/geometry/Surface.h create mode 100644 src/idlib/geometry/Surface_Patch.cpp create mode 100644 src/idlib/geometry/Surface_Patch.h create mode 100644 src/idlib/geometry/Surface_Polytope.cpp create mode 100644 src/idlib/geometry/Surface_Polytope.h create mode 100644 src/idlib/geometry/Surface_SweptSpline.cpp create mode 100644 src/idlib/geometry/Surface_SweptSpline.h create mode 100644 src/idlib/geometry/TraceModel.cpp create mode 100644 src/idlib/geometry/TraceModel.h create mode 100644 src/idlib/geometry/Winding.cpp create mode 100644 src/idlib/geometry/Winding.h create mode 100644 src/idlib/geometry/Winding2D.cpp create mode 100644 src/idlib/geometry/Winding2D.h create mode 100644 src/idlib/geometry/rvVertex.h create mode 100644 src/idlib/hashing/CRC16.cpp create mode 100644 src/idlib/hashing/CRC16.h create mode 100644 src/idlib/hashing/CRC32.cpp create mode 100644 src/idlib/hashing/CRC32.h create mode 100644 src/idlib/hashing/CRC8.cpp create mode 100644 src/idlib/hashing/CRC8.h create mode 100644 src/idlib/hashing/Honeyman.cpp create mode 100644 src/idlib/hashing/Honeyman.h create mode 100644 src/idlib/hashing/MD4.cpp create mode 100644 src/idlib/hashing/MD4.h create mode 100644 src/idlib/hashing/MD5.cpp create mode 100644 src/idlib/hashing/MD5.h create mode 100644 src/idlib/mapfile.cpp create mode 100644 src/idlib/math/Angles.cpp create mode 100644 src/idlib/math/Angles.h create mode 100644 src/idlib/math/Complex.cpp create mode 100644 src/idlib/math/Complex.h create mode 100644 src/idlib/math/Curve.h create mode 100644 src/idlib/math/Extrapolate.h create mode 100644 src/idlib/math/FFT.cpp create mode 100644 src/idlib/math/FFT.h create mode 100644 src/idlib/math/Interpolate.h create mode 100644 src/idlib/math/Lcp.cpp create mode 100644 src/idlib/math/Lcp.h create mode 100644 src/idlib/math/Mat3x4.h create mode 100644 src/idlib/math/Math.cpp create mode 100644 src/idlib/math/Math.h create mode 100644 src/idlib/math/Matrix.cpp create mode 100644 src/idlib/math/Matrix.h create mode 100644 src/idlib/math/Ode.cpp create mode 100644 src/idlib/math/Ode.h create mode 100644 src/idlib/math/Plane.cpp create mode 100644 src/idlib/math/Plane.h create mode 100644 src/idlib/math/Pluecker.cpp create mode 100644 src/idlib/math/Pluecker.h create mode 100644 src/idlib/math/Polynomial.cpp create mode 100644 src/idlib/math/Polynomial.h create mode 100644 src/idlib/math/Quat.cpp create mode 100644 src/idlib/math/Quat.h create mode 100644 src/idlib/math/Radians.cpp create mode 100644 src/idlib/math/Radians.h create mode 100644 src/idlib/math/Random.h create mode 100644 src/idlib/math/Rotation.cpp create mode 100644 src/idlib/math/Rotation.h create mode 100644 src/idlib/math/Simd.cpp create mode 100644 src/idlib/math/Simd.h create mode 100644 src/idlib/math/Simd_3DNow.cpp create mode 100644 src/idlib/math/Simd_3DNow.h create mode 100644 src/idlib/math/Simd_AltiVec.cpp create mode 100644 src/idlib/math/Simd_AltiVec.h create mode 100644 src/idlib/math/Simd_InstructionMacros.h create mode 100644 src/idlib/math/Simd_MMX.cpp create mode 100644 src/idlib/math/Simd_MMX.h create mode 100644 src/idlib/math/Simd_SSE.cpp create mode 100644 src/idlib/math/Simd_SSE.h create mode 100644 src/idlib/math/Simd_SSE2.cpp create mode 100644 src/idlib/math/Simd_SSE2.h create mode 100644 src/idlib/math/Simd_SSE3.cpp create mode 100644 src/idlib/math/Simd_SSE3.h create mode 100644 src/idlib/math/Simd_generic.cpp create mode 100644 src/idlib/math/Simd_generic.h create mode 100644 src/idlib/math/Vector.cpp create mode 100644 src/idlib/math/Vector.h create mode 100644 src/idlib/precompiled.h create mode 100644 src/idlib/rvHeap.cpp create mode 100644 src/idlib/rvHeap.h create mode 100644 src/idlib/rvHeapArena.cpp create mode 100644 src/idlib/rvHeapArena.h create mode 100644 src/idlib/rvMemSys.cpp create mode 100644 src/idlib/rvMemSys.h create mode 100644 src/idlib/threads/AutoCrit.h create mode 100644 src/mpgame/AF.cpp create mode 100644 src/mpgame/AF.h create mode 100644 src/mpgame/AFEntity.cpp create mode 100644 src/mpgame/AFEntity.h create mode 100644 src/mpgame/Actor.cpp create mode 100644 src/mpgame/Actor.h create mode 100644 src/mpgame/BrittleFracture.cpp create mode 100644 src/mpgame/BrittleFracture.h create mode 100644 src/mpgame/Camera.cpp create mode 100644 src/mpgame/Camera.h create mode 100644 src/mpgame/Effect.cpp create mode 100644 src/mpgame/Effect.h create mode 100644 src/mpgame/Entity.cpp create mode 100644 src/mpgame/Entity.h create mode 100644 src/mpgame/GameEdit.cpp create mode 100644 src/mpgame/GameEdit.h create mode 100644 src/mpgame/Game_Debug.cpp create mode 100644 src/mpgame/Game_Debug.h create mode 100644 src/mpgame/Game_Log.cpp create mode 100644 src/mpgame/Game_Log.h create mode 100644 src/mpgame/Game_local.cpp create mode 100644 src/mpgame/Game_local.h create mode 100644 src/mpgame/Game_network.cpp create mode 100644 src/mpgame/Healing_Station.cpp create mode 100644 src/mpgame/Healing_Station.h create mode 100644 src/mpgame/IK.cpp create mode 100644 src/mpgame/IK.h create mode 100644 src/mpgame/Icon.cpp create mode 100644 src/mpgame/Icon.h create mode 100644 src/mpgame/IconManager.cpp create mode 100644 src/mpgame/IconManager.h create mode 100644 src/mpgame/Instance.cpp create mode 100644 src/mpgame/Instance.h create mode 100644 src/mpgame/Item.cpp create mode 100644 src/mpgame/Item.h create mode 100644 src/mpgame/Lagometer.cpp create mode 100644 src/mpgame/Lagometer.h create mode 100644 src/mpgame/Light.cpp create mode 100644 src/mpgame/Light.h create mode 100644 src/mpgame/LipSync.cpp create mode 100644 src/mpgame/LipSync.h create mode 100644 src/mpgame/Misc.cpp create mode 100644 src/mpgame/Misc.h create mode 100644 src/mpgame/Moveable.cpp create mode 100644 src/mpgame/Moveable.h create mode 100644 src/mpgame/Mover.cpp create mode 100644 src/mpgame/Mover.h create mode 100644 src/mpgame/MultiplayerGame.cpp create mode 100644 src/mpgame/MultiplayerGame.h create mode 100644 src/mpgame/Playback.cpp create mode 100644 src/mpgame/Player.cpp create mode 100644 src/mpgame/Player.h create mode 100644 src/mpgame/PlayerView.cpp create mode 100644 src/mpgame/PlayerView.h create mode 100644 src/mpgame/Player_Cheats.cpp create mode 100644 src/mpgame/Player_States.cpp create mode 100644 src/mpgame/Projectile.cpp create mode 100644 src/mpgame/Projectile.h create mode 100644 src/mpgame/Pvs.cpp create mode 100644 src/mpgame/Pvs.h create mode 100644 src/mpgame/SecurityCamera.cpp create mode 100644 src/mpgame/SecurityCamera.h create mode 100644 src/mpgame/Sound.cpp create mode 100644 src/mpgame/Sound.h create mode 100644 src/mpgame/SplineMover.cpp create mode 100644 src/mpgame/SplineMover.h create mode 100644 src/mpgame/Target.cpp create mode 100644 src/mpgame/Target.h create mode 100644 src/mpgame/TramGate.cpp create mode 100644 src/mpgame/TramGate.h create mode 100644 src/mpgame/Trigger.cpp create mode 100644 src/mpgame/Trigger.h create mode 100644 src/mpgame/Weapon.cpp create mode 100644 src/mpgame/Weapon.h create mode 100644 src/mpgame/WorldSpawn.cpp create mode 100644 src/mpgame/WorldSpawn.h create mode 100644 src/mpgame/ai/AAS.cpp create mode 100644 src/mpgame/ai/AAS.h create mode 100644 src/mpgame/ai/AAS_Find.cpp create mode 100644 src/mpgame/ai/AAS_Find.h create mode 100644 src/mpgame/ai/AAS_debug.cpp create mode 100644 src/mpgame/ai/AAS_local.h create mode 100644 src/mpgame/ai/AAS_pathing.cpp create mode 100644 src/mpgame/ai/AAS_routing.cpp create mode 100644 src/mpgame/ai/AAS_tactical.cpp create mode 100644 src/mpgame/ai/AAS_tactical.h create mode 100644 src/mpgame/ai/AI.cpp create mode 100644 src/mpgame/ai/AI.h create mode 100644 src/mpgame/ai/AI_Actions.cpp create mode 100644 src/mpgame/ai/AI_Announcements.cpp create mode 100644 src/mpgame/ai/AI_Debug.cpp create mode 100644 src/mpgame/ai/AI_Manager.cpp create mode 100644 src/mpgame/ai/AI_Manager.h create mode 100644 src/mpgame/ai/AI_Medic.cpp create mode 100644 src/mpgame/ai/AI_Medic.h create mode 100644 src/mpgame/ai/AI_Move.cpp create mode 100644 src/mpgame/ai/AI_Move.h create mode 100644 src/mpgame/ai/AI_States.cpp create mode 100644 src/mpgame/ai/AI_Tactical.cpp create mode 100644 src/mpgame/ai/AI_Tactical.h create mode 100644 src/mpgame/ai/AI_Util.cpp create mode 100644 src/mpgame/ai/AI_Util.h create mode 100644 src/mpgame/ai/AI_events.cpp create mode 100644 src/mpgame/ai/AI_pathing.cpp create mode 100644 src/mpgame/ai/Monster_Berserker.cpp create mode 100644 src/mpgame/ai/Monster_BossBuddy.cpp create mode 100644 src/mpgame/ai/Monster_BossMakron.cpp create mode 100644 src/mpgame/ai/Monster_ConvoyGround.cpp create mode 100644 src/mpgame/ai/Monster_ConvoyHover.cpp create mode 100644 src/mpgame/ai/Monster_FailedTransfer.cpp create mode 100644 src/mpgame/ai/Monster_Fatty.cpp create mode 100644 src/mpgame/ai/Monster_Gladiator.cpp create mode 100644 src/mpgame/ai/Monster_Grunt.cpp create mode 100644 src/mpgame/ai/Monster_Gunner.cpp create mode 100644 src/mpgame/ai/Monster_Harvester.cpp create mode 100644 src/mpgame/ai/Monster_HarvesterDispersal.cpp create mode 100644 src/mpgame/ai/Monster_HeavyHoverTank.cpp create mode 100644 src/mpgame/ai/Monster_IronMaiden.cpp create mode 100644 src/mpgame/ai/Monster_LightTank.cpp create mode 100644 src/mpgame/ai/Monster_NetworkGuardian.cpp create mode 100644 src/mpgame/ai/Monster_RepairBot.cpp create mode 100644 src/mpgame/ai/Monster_Scientist.cpp create mode 100644 src/mpgame/ai/Monster_Sentry.cpp create mode 100644 src/mpgame/ai/Monster_SlimyTransfer.cpp create mode 100644 src/mpgame/ai/Monster_StreamProtector.cpp create mode 100644 src/mpgame/ai/Monster_StroggFlyer.cpp create mode 100644 src/mpgame/ai/Monster_StroggHover.cpp create mode 100644 src/mpgame/ai/Monster_StroggMarine.cpp create mode 100644 src/mpgame/ai/Monster_TeleportDropper.cpp create mode 100644 src/mpgame/ai/Monster_Turret.cpp create mode 100644 src/mpgame/ai/Monster_TurretFlying.cpp create mode 100644 src/mpgame/ai/VehicleAI.cpp create mode 100644 src/mpgame/ai/VehicleAI.h create mode 100644 src/mpgame/anim/Anim.cpp create mode 100644 src/mpgame/anim/Anim.h create mode 100644 src/mpgame/anim/Anim_Blend.cpp create mode 100644 src/mpgame/anim/Anim_Import.cpp create mode 100644 src/mpgame/anim/Anim_Testmodel.cpp create mode 100644 src/mpgame/anim/Anim_Testmodel.h create mode 100644 src/mpgame/client/ClientAFEntity.cpp create mode 100644 src/mpgame/client/ClientAFEntity.h create mode 100644 src/mpgame/client/ClientEffect.cpp create mode 100644 src/mpgame/client/ClientEffect.h create mode 100644 src/mpgame/client/ClientEntity.cpp create mode 100644 src/mpgame/client/ClientEntity.h create mode 100644 src/mpgame/client/ClientModel.cpp create mode 100644 src/mpgame/client/ClientModel.h create mode 100644 src/mpgame/client/ClientMoveable.cpp create mode 100644 src/mpgame/client/ClientMoveable.h create mode 100644 src/mpgame/gamesys/Callbacks.cpp create mode 100644 src/mpgame/gamesys/Class.cpp create mode 100644 src/mpgame/gamesys/Class.h create mode 100644 src/mpgame/gamesys/DebugGraph.cpp create mode 100644 src/mpgame/gamesys/DebugGraph.h create mode 100644 src/mpgame/gamesys/Event.cpp create mode 100644 src/mpgame/gamesys/Event.h create mode 100644 src/mpgame/gamesys/NoGameTypeInfo.h create mode 100644 src/mpgame/gamesys/SaveGame.cpp create mode 100644 src/mpgame/gamesys/SaveGame.h create mode 100644 src/mpgame/gamesys/State.cpp create mode 100644 src/mpgame/gamesys/State.h create mode 100644 src/mpgame/gamesys/SysCmds.cpp create mode 100644 src/mpgame/gamesys/SysCmds.h create mode 100644 src/mpgame/gamesys/SysCvar.cpp create mode 100644 src/mpgame/gamesys/SysCvar.h create mode 100644 src/mpgame/mp/Buying.cpp create mode 100644 src/mpgame/mp/Buying.h create mode 100644 src/mpgame/mp/CTF.cpp create mode 100644 src/mpgame/mp/CTF.h create mode 100644 src/mpgame/mp/GameState.cpp create mode 100644 src/mpgame/mp/GameState.h create mode 100644 src/mpgame/mp/Tourney.cpp create mode 100644 src/mpgame/mp/Tourney.h create mode 100644 src/mpgame/mp/VoiceComms.cpp create mode 100644 src/mpgame/mp/stats/StatEvent.cpp create mode 100644 src/mpgame/mp/stats/StatEvent.h create mode 100644 src/mpgame/mp/stats/StatManager.cpp create mode 100644 src/mpgame/mp/stats/StatManager.h create mode 100644 src/mpgame/mp/stats/StatWindow.cpp create mode 100644 src/mpgame/mp/stats/StatWindow.h create mode 100644 src/mpgame/mpgame.def create mode 100644 src/mpgame/physics/Clip.cpp create mode 100644 src/mpgame/physics/Clip.h create mode 100644 src/mpgame/physics/Force.cpp create mode 100644 src/mpgame/physics/Force.h create mode 100644 src/mpgame/physics/Force_Constant.cpp create mode 100644 src/mpgame/physics/Force_Constant.h create mode 100644 src/mpgame/physics/Force_Drag.cpp create mode 100644 src/mpgame/physics/Force_Drag.h create mode 100644 src/mpgame/physics/Force_Field.cpp create mode 100644 src/mpgame/physics/Force_Field.h create mode 100644 src/mpgame/physics/Force_Spring.cpp create mode 100644 src/mpgame/physics/Force_Spring.h create mode 100644 src/mpgame/physics/Physics.cpp create mode 100644 src/mpgame/physics/Physics.h create mode 100644 src/mpgame/physics/Physics_AF.cpp create mode 100644 src/mpgame/physics/Physics_AF.h create mode 100644 src/mpgame/physics/Physics_Actor.cpp create mode 100644 src/mpgame/physics/Physics_Actor.h create mode 100644 src/mpgame/physics/Physics_Base.cpp create mode 100644 src/mpgame/physics/Physics_Base.h create mode 100644 src/mpgame/physics/Physics_Monster.cpp create mode 100644 src/mpgame/physics/Physics_Monster.h create mode 100644 src/mpgame/physics/Physics_Parametric.cpp create mode 100644 src/mpgame/physics/Physics_Parametric.h create mode 100644 src/mpgame/physics/Physics_Particle.cpp create mode 100644 src/mpgame/physics/Physics_Particle.h create mode 100644 src/mpgame/physics/Physics_Player.cpp create mode 100644 src/mpgame/physics/Physics_Player.h create mode 100644 src/mpgame/physics/Physics_RigidBody.cpp create mode 100644 src/mpgame/physics/Physics_RigidBody.h create mode 100644 src/mpgame/physics/Physics_Static.cpp create mode 100644 src/mpgame/physics/Physics_Static.h create mode 100644 src/mpgame/physics/Physics_StaticMulti.cpp create mode 100644 src/mpgame/physics/Physics_StaticMulti.h create mode 100644 src/mpgame/physics/Physics_VehicleMonster.cpp create mode 100644 src/mpgame/physics/Physics_VehicleMonster.h create mode 100644 src/mpgame/physics/Push.cpp create mode 100644 src/mpgame/physics/Push.h create mode 100644 src/mpgame/script/ScriptFuncUtility.cpp create mode 100644 src/mpgame/script/ScriptFuncUtility.h create mode 100644 src/mpgame/script/Script_Compiler.cpp create mode 100644 src/mpgame/script/Script_Compiler.h create mode 100644 src/mpgame/script/Script_Interpreter.cpp create mode 100644 src/mpgame/script/Script_Interpreter.h create mode 100644 src/mpgame/script/Script_Program.cpp create mode 100644 src/mpgame/script/Script_Program.h create mode 100644 src/mpgame/script/Script_Thread.cpp create mode 100644 src/mpgame/script/Script_Thread.h create mode 100644 src/mpgame/spawner.cpp create mode 100644 src/mpgame/spawner.h create mode 100644 src/mpgame/vehicle/Vehicle.cpp create mode 100644 src/mpgame/vehicle/Vehicle.h create mode 100644 src/mpgame/vehicle/VehicleAnimated.cpp create mode 100644 src/mpgame/vehicle/VehicleAnimated.h create mode 100644 src/mpgame/vehicle/VehicleController.cpp create mode 100644 src/mpgame/vehicle/VehicleController.h create mode 100644 src/mpgame/vehicle/VehicleDriver.cpp create mode 100644 src/mpgame/vehicle/VehicleDriver.h create mode 100644 src/mpgame/vehicle/VehicleMonster.cpp create mode 100644 src/mpgame/vehicle/VehicleMonster.h create mode 100644 src/mpgame/vehicle/VehicleParts.cpp create mode 100644 src/mpgame/vehicle/VehicleParts.h create mode 100644 src/mpgame/vehicle/VehiclePosition.cpp create mode 100644 src/mpgame/vehicle/VehicleRigid.cpp create mode 100644 src/mpgame/vehicle/VehicleRigid.h create mode 100644 src/mpgame/vehicle/VehicleSpline.cpp create mode 100644 src/mpgame/vehicle/VehicleSpline.h create mode 100644 src/mpgame/vehicle/VehicleStatic.cpp create mode 100644 src/mpgame/vehicle/VehicleStatic.h create mode 100644 src/mpgame/vehicle/Vehicle_DropPod.cpp create mode 100644 src/mpgame/vehicle/Vehicle_Walker.cpp create mode 100644 src/mpgame/weapon/WeaponBlaster.cpp create mode 100644 src/mpgame/weapon/WeaponDarkMatterGun.cpp create mode 100644 src/mpgame/weapon/WeaponGauntlet.cpp create mode 100644 src/mpgame/weapon/WeaponGrenadeLauncher.cpp create mode 100644 src/mpgame/weapon/WeaponHyperblaster.cpp create mode 100644 src/mpgame/weapon/WeaponLightningGun.cpp create mode 100644 src/mpgame/weapon/WeaponMachinegun.cpp create mode 100644 src/mpgame/weapon/WeaponNailgun.cpp create mode 100644 src/mpgame/weapon/WeaponNapalmGun.cpp create mode 100644 src/mpgame/weapon/WeaponRailgun.cpp create mode 100644 src/mpgame/weapon/WeaponRocketLauncher.cpp create mode 100644 src/mpgame/weapon/WeaponShotgun.cpp create mode 100644 src/openal/docs/ChangeLog create mode 100644 src/openal/docs/Makefile create mode 100644 src/openal/docs/alc-context.sgml create mode 100644 src/openal/docs/api-reference.html create mode 100644 src/openal/docs/app-annotations.sgml create mode 100644 src/openal/docs/app-constants.sgml create mode 100644 src/openal/docs/app-extensionprocess.sgml create mode 100644 src/openal/docs/app-extensions.sgml create mode 100644 src/openal/docs/architecture.html create mode 100644 src/openal/docs/chp-buffers.sgml create mode 100644 src/openal/docs/chp-introduction.sgml create mode 100644 src/openal/docs/chp-multichannel.sgml create mode 100644 src/openal/docs/chp-operation.sgml create mode 100644 src/openal/docs/chp-queueing.sgml create mode 100644 src/openal/docs/chp-rendering.sgml create mode 100644 src/openal/docs/chp-state.sgml create mode 100644 src/openal/docs/ent-examples.sgml create mode 100644 src/openal/docs/ent-extensions.sgml create mode 100644 src/openal/docs/ent-marks-annotated.sgml create mode 100644 src/openal/docs/ent-marks-full.sgml create mode 100644 src/openal/docs/ent-marks-reference.sgml create mode 100644 src/openal/docs/ent-marks-specification.sgml create mode 100644 src/openal/docs/ent-names.sgml create mode 100644 src/openal/docs/index.html create mode 100644 src/openal/docs/oalspecs.sgml create mode 100644 src/openal/docs/sec-bookinfo.sgml create mode 100644 src/openal/docs/specification.html create mode 100644 src/openal/docs/white-paper.html create mode 100644 src/openal/idal.cpp create mode 100644 src/openal/idal.h create mode 100644 src/openal/idal.py create mode 100644 src/openal/include/al.h create mode 100644 src/openal/include/alc.h create mode 100644 src/openal/include/alctypes.h create mode 100644 src/openal/include/altypes.h create mode 100644 src/openal/include/alu.h create mode 100644 src/openal/include/eax2.h create mode 100644 src/openal/include/eax3.h create mode 100644 src/openal/include/eax4.h create mode 100644 src/openal/include/eax5.h create mode 100644 src/openal/include/eaxac3.h create mode 100644 src/openal/include/eaxman.h create mode 100644 src/openal/include/efxlib.h create mode 100644 src/openal/osx/OpenAL.framework/Headers create mode 100644 src/openal/osx/OpenAL.framework/OpenAL create mode 100644 src/openal/osx/OpenAL.framework/Resources create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Headers/al.h create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Headers/alc.h create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Headers/alctypes.h create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Headers/altypes.h create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Headers/alut.h create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/OpenAL create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Resources/English.lproj/InfoPlist.strings create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Resources/Info.plist create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Resources/READ_ME create mode 100644 src/openal/osx/OpenAL.framework/Versions/A/Resources/pbdevelopment.plist create mode 100644 src/openal/osx/OpenAL.framework/Versions/Current create mode 100644 src/openal/stubs.cpp create mode 100644 src/renderer/Cinematic.h create mode 100644 src/renderer/GuiModel.h create mode 100644 src/renderer/Image.h create mode 100644 src/renderer/Image_stats.cpp create mode 100644 src/renderer/Interaction.h create mode 100644 src/renderer/Material.h create mode 100644 src/renderer/MegaTexture.h create mode 100644 src/renderer/Model.h create mode 100644 src/renderer/ModelDecal.h create mode 100644 src/renderer/ModelManager.h create mode 100644 src/renderer/ModelOverlay.h create mode 100644 src/renderer/Model_ase.h create mode 100644 src/renderer/Model_local.h create mode 100644 src/renderer/Model_lwo.h create mode 100644 src/renderer/Model_ma.h create mode 100644 src/renderer/Model_md3.h create mode 100644 src/renderer/RenderSystem.h create mode 100644 src/renderer/RenderWorld.h create mode 100644 src/renderer/RenderWorld_local.h create mode 100644 src/renderer/Shaders.cpp create mode 100644 src/renderer/Shaders.h create mode 100644 src/renderer/VertexCache.h create mode 100644 src/renderer/cg_explicit.h create mode 100644 src/renderer/cinematic.cpp create mode 100644 src/renderer/draw_arb.cpp create mode 100644 src/renderer/draw_arb2.cpp create mode 100644 src/renderer/draw_common.cpp create mode 100644 src/renderer/draw_nv10.cpp create mode 100644 src/renderer/draw_nv20.cpp create mode 100644 src/renderer/draw_r200.cpp create mode 100644 src/renderer/glext.h create mode 100644 src/renderer/guimodel.cpp create mode 100644 src/renderer/image_files.cpp create mode 100644 src/renderer/image_init.cpp create mode 100644 src/renderer/image_load.cpp create mode 100644 src/renderer/image_process.cpp create mode 100644 src/renderer/image_program.cpp create mode 100644 src/renderer/interaction.cpp create mode 100644 src/renderer/jpeg-6/jcapimin.c create mode 100644 src/renderer/jpeg-6/jcapistd.c create mode 100644 src/renderer/jpeg-6/jccoefct.c create mode 100644 src/renderer/jpeg-6/jccolor.c create mode 100644 src/renderer/jpeg-6/jcdctmgr.c create mode 100644 src/renderer/jpeg-6/jchuff.c create mode 100644 src/renderer/jpeg-6/jchuff.h create mode 100644 src/renderer/jpeg-6/jcinit.c create mode 100644 src/renderer/jpeg-6/jcmainct.c create mode 100644 src/renderer/jpeg-6/jcmarker.c create mode 100644 src/renderer/jpeg-6/jcmaster.c create mode 100644 src/renderer/jpeg-6/jcomapi.c create mode 100644 src/renderer/jpeg-6/jconfig.h create mode 100644 src/renderer/jpeg-6/jcparam.c create mode 100644 src/renderer/jpeg-6/jcphuff.c create mode 100644 src/renderer/jpeg-6/jcprepct.c create mode 100644 src/renderer/jpeg-6/jcsample.c create mode 100644 src/renderer/jpeg-6/jdapimin.c create mode 100644 src/renderer/jpeg-6/jdapistd.c create mode 100644 src/renderer/jpeg-6/jdatadst.c create mode 100644 src/renderer/jpeg-6/jdatasrc.c create mode 100644 src/renderer/jpeg-6/jdcoefct.c create mode 100644 src/renderer/jpeg-6/jdcolor.c create mode 100644 src/renderer/jpeg-6/jdct.h create mode 100644 src/renderer/jpeg-6/jddctmgr.c create mode 100644 src/renderer/jpeg-6/jdhuff.c create mode 100644 src/renderer/jpeg-6/jdhuff.h create mode 100644 src/renderer/jpeg-6/jdinput.c create mode 100644 src/renderer/jpeg-6/jdmainct.c create mode 100644 src/renderer/jpeg-6/jdmarker.c create mode 100644 src/renderer/jpeg-6/jdmaster.c create mode 100644 src/renderer/jpeg-6/jdmerge.c create mode 100644 src/renderer/jpeg-6/jdphuff.c create mode 100644 src/renderer/jpeg-6/jdpostct.c create mode 100644 src/renderer/jpeg-6/jdsample.c create mode 100644 src/renderer/jpeg-6/jdtrans.c create mode 100644 src/renderer/jpeg-6/jerror.c create mode 100644 src/renderer/jpeg-6/jerror.h create mode 100644 src/renderer/jpeg-6/jfdctflt.c create mode 100644 src/renderer/jpeg-6/jfdctfst.c create mode 100644 src/renderer/jpeg-6/jfdctint.c create mode 100644 src/renderer/jpeg-6/jidctflt.c create mode 100644 src/renderer/jpeg-6/jidctfst.c create mode 100644 src/renderer/jpeg-6/jidctint.c create mode 100644 src/renderer/jpeg-6/jidctred.c create mode 100644 src/renderer/jpeg-6/jinclude.h create mode 100644 src/renderer/jpeg-6/jmemmgr.c create mode 100644 src/renderer/jpeg-6/jmemnobs.c create mode 100644 src/renderer/jpeg-6/jmemsys.h create mode 100644 src/renderer/jpeg-6/jmorecfg.h create mode 100644 src/renderer/jpeg-6/jpegint.h create mode 100644 src/renderer/jpeg-6/jpeglib.h create mode 100644 src/renderer/jpeg-6/jquant1.c create mode 100644 src/renderer/jpeg-6/jquant2.c create mode 100644 src/renderer/jpeg-6/jutils.c create mode 100644 src/renderer/jpeg-6/jversion.h create mode 100644 src/renderer/material.cpp create mode 100644 src/renderer/megatexture.cpp create mode 100644 src/renderer/model.cpp create mode 100644 src/renderer/model_ase.cpp create mode 100644 src/renderer/model_beam.cpp create mode 100644 src/renderer/model_liquid.cpp create mode 100644 src/renderer/model_lwo.cpp create mode 100644 src/renderer/model_ma.cpp create mode 100644 src/renderer/model_md3.cpp create mode 100644 src/renderer/model_md5.cpp create mode 100644 src/renderer/model_sprite.cpp create mode 100644 src/renderer/modeldecal.cpp create mode 100644 src/renderer/modelmanager.cpp create mode 100644 src/renderer/modeloverlay.cpp create mode 100644 src/renderer/qgl.h create mode 100644 src/renderer/qgl_linked.h create mode 100644 src/renderer/renderentity.cpp create mode 100644 src/renderer/rendersystem.cpp create mode 100644 src/renderer/rendersystem_init.cpp create mode 100644 src/renderer/renderworld.cpp create mode 100644 src/renderer/renderworld_demo.cpp create mode 100644 src/renderer/renderworld_load.cpp create mode 100644 src/renderer/renderworld_portals.cpp create mode 100644 src/renderer/rvGLSLShader.cpp create mode 100644 src/renderer/rvIndexBuffer.cpp create mode 100644 src/renderer/rvIndexBuffer.h create mode 100644 src/renderer/rvMesh.cpp create mode 100644 src/renderer/rvMesh.h create mode 100644 src/renderer/rvPrimBatch.cpp create mode 100644 src/renderer/rvPrimBatch.h create mode 100644 src/renderer/rvRenderModelMD5R.cpp create mode 100644 src/renderer/rvRenderModelMD5R.h create mode 100644 src/renderer/rvSpecial.cpp create mode 100644 src/renderer/rvSpecial.h create mode 100644 src/renderer/rvTexRenderTarget.cpp create mode 100644 src/renderer/rvTexRenderTarget.h create mode 100644 src/renderer/rvVertexBuffer.cpp create mode 100644 src/renderer/rvVertexBuffer.h create mode 100644 src/renderer/rvVertexBufferCopy.cpp create mode 100644 src/renderer/rvVertexFormat.cpp create mode 100644 src/renderer/rvVertexFormat.h create mode 100644 src/renderer/simplex.h create mode 100644 src/renderer/tr_backend.cpp create mode 100644 src/renderer/tr_deform.cpp create mode 100644 src/renderer/tr_font.cpp create mode 100644 src/renderer/tr_guisurf.cpp create mode 100644 src/renderer/tr_light.cpp create mode 100644 src/renderer/tr_lightrun.cpp create mode 100644 src/renderer/tr_local.h create mode 100644 src/renderer/tr_main.cpp create mode 100644 src/renderer/tr_orderindexes.cpp create mode 100644 src/renderer/tr_polytope.cpp create mode 100644 src/renderer/tr_render.cpp create mode 100644 src/renderer/tr_rendertools.cpp create mode 100644 src/renderer/tr_shadowbounds.cpp create mode 100644 src/renderer/tr_stencilshadow.cpp create mode 100644 src/renderer/tr_subview.cpp create mode 100644 src/renderer/tr_trace.cpp create mode 100644 src/renderer/tr_trisurf.cpp create mode 100644 src/renderer/tr_turboshadow.cpp create mode 100644 src/renderer/vertexcache.cpp create mode 100644 src/renderer/wglext.h create mode 100644 src/sound/default/snd_cache.cpp create mode 100644 src/sound/default/snd_common.cpp create mode 100644 src/sound/default/snd_decoder.cpp create mode 100644 src/sound/default/snd_efxfile.cpp create mode 100644 src/sound/default/snd_emitter.cpp create mode 100644 src/sound/default/snd_reverb.cpp create mode 100644 src/sound/default/snd_system.cpp create mode 100644 src/sound/default/snd_voice.cpp create mode 100644 src/sound/default/snd_wavefile.cpp create mode 100644 src/sound/default/snd_world.cpp create mode 100644 src/sound/default/win_snd.cpp create mode 100644 src/sound/oggvorbis/ogg/ogg.h create mode 100644 src/sound/oggvorbis/ogg/os_types.h create mode 100644 src/sound/oggvorbis/oggsrc/bitwise.c create mode 100644 src/sound/oggvorbis/oggsrc/framing.c create mode 100644 src/sound/oggvorbis/vorbis/codec.h create mode 100644 src/sound/oggvorbis/vorbis/vorbisenc.h create mode 100644 src/sound/oggvorbis/vorbis/vorbisfile.h create mode 100644 src/sound/oggvorbis/vorbissrc/analysis.c create mode 100644 src/sound/oggvorbis/vorbissrc/backends.h create mode 100644 src/sound/oggvorbis/vorbissrc/bitrate.c create mode 100644 src/sound/oggvorbis/vorbissrc/bitrate.h create mode 100644 src/sound/oggvorbis/vorbissrc/block.c create mode 100644 src/sound/oggvorbis/vorbissrc/books/coupled/res_books_stereo.h create mode 100644 src/sound/oggvorbis/vorbissrc/books/floor/floor_books.h create mode 100644 src/sound/oggvorbis/vorbissrc/books/uncoupled/res_books_uncoupled.h create mode 100644 src/sound/oggvorbis/vorbissrc/codebook.c create mode 100644 src/sound/oggvorbis/vorbissrc/codebook.h create mode 100644 src/sound/oggvorbis/vorbissrc/codec_internal.h create mode 100644 src/sound/oggvorbis/vorbissrc/envelope.c create mode 100644 src/sound/oggvorbis/vorbissrc/envelope.h create mode 100644 src/sound/oggvorbis/vorbissrc/floor0.c create mode 100644 src/sound/oggvorbis/vorbissrc/floor1.c create mode 100644 src/sound/oggvorbis/vorbissrc/highlevel.h create mode 100644 src/sound/oggvorbis/vorbissrc/info.c create mode 100644 src/sound/oggvorbis/vorbissrc/lookup.c create mode 100644 src/sound/oggvorbis/vorbissrc/lookup.h create mode 100644 src/sound/oggvorbis/vorbissrc/lookup_data.h create mode 100644 src/sound/oggvorbis/vorbissrc/lpc.c create mode 100644 src/sound/oggvorbis/vorbissrc/lpc.h create mode 100644 src/sound/oggvorbis/vorbissrc/lsp.c create mode 100644 src/sound/oggvorbis/vorbissrc/lsp.h create mode 100644 src/sound/oggvorbis/vorbissrc/mapping0.c create mode 100644 src/sound/oggvorbis/vorbissrc/masking.h create mode 100644 src/sound/oggvorbis/vorbissrc/mdct.c create mode 100644 src/sound/oggvorbis/vorbissrc/mdct.h create mode 100644 src/sound/oggvorbis/vorbissrc/misc.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/floor_all.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/psych_11.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/psych_16.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/psych_44.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/psych_8.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/residue_16.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/residue_44.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/residue_44u.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/residue_8.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_11.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_16.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_22.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_32.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_44.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_44u.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_8.h create mode 100644 src/sound/oggvorbis/vorbissrc/modes/setup_X.h create mode 100644 src/sound/oggvorbis/vorbissrc/os.h create mode 100644 src/sound/oggvorbis/vorbissrc/psy.c create mode 100644 src/sound/oggvorbis/vorbissrc/psy.h create mode 100644 src/sound/oggvorbis/vorbissrc/registry.c create mode 100644 src/sound/oggvorbis/vorbissrc/registry.h create mode 100644 src/sound/oggvorbis/vorbissrc/res0.c create mode 100644 src/sound/oggvorbis/vorbissrc/scales.h create mode 100644 src/sound/oggvorbis/vorbissrc/sharedbook.c create mode 100644 src/sound/oggvorbis/vorbissrc/smallft.c create mode 100644 src/sound/oggvorbis/vorbissrc/smallft.h create mode 100644 src/sound/oggvorbis/vorbissrc/synthesis.c create mode 100644 src/sound/oggvorbis/vorbissrc/vorbisenc.c create mode 100644 src/sound/oggvorbis/vorbissrc/vorbisfile.c create mode 100644 src/sound/oggvorbis/vorbissrc/window.h create mode 100644 src/sound/oggvorbis/vorbissrc/windowvb.c create mode 100644 src/sound/snd_local.h create mode 100644 src/sound/snd_shader.cpp create mode 100644 src/sound/sound.h create mode 100644 src/sys/AutoVersion.h create mode 100644 src/sys/linux/qgl_enforce.h create mode 100644 src/sys/osx/Resources/Game.plist create mode 100644 src/sys/osx/Resources/Info.plist create mode 100644 src/sys/osx/Subprojects/game.xcodeproj/project.pbxproj create mode 100644 src/sys/osx/Subprojects/idlib.xcodeproj/project.pbxproj create mode 100644 src/sys/osx/Subprojects/mpgame.xcodeproj/project.pbxproj create mode 100644 src/sys/osx/apple_bool.h create mode 100644 src/sys/osx/q4sdk.xcodeproj/project.pbxproj create mode 100644 src/sys/scons/SConscript.game create mode 100644 src/sys/scons/SConscript.idlib create mode 100644 src/sys/scons/scons_utils.py create mode 100644 src/sys/sys_local.cpp create mode 100644 src/sys/sys_local.h create mode 100644 src/sys/sys_public.h create mode 100644 src/sys/win32/eax.h create mode 100644 src/sys/win32/rc/AFEditor_resource.h create mode 100644 src/sys/win32/rc/Common_resource.h create mode 100644 src/sys/win32/rc/CreateResourceIDs.h create mode 100644 src/sys/win32/rc/Debugger_resource.h create mode 100644 src/sys/win32/rc/DeclEditor_resource.h create mode 100644 src/sys/win32/rc/GuiEd_resource.h create mode 100644 src/sys/win32/rc/MaterialEditor_Resource.h create mode 100644 src/sys/win32/rc/PDAEditor_resource.h create mode 100644 src/sys/win32/rc/ParticleEditor_resource.h create mode 100644 src/sys/win32/rc/PropTree_resource.h create mode 100644 src/sys/win32/rc/Radiant_resource.h create mode 100644 src/sys/win32/rc/ScriptEditor_resource.h create mode 100644 src/sys/win32/rc/SoundEditor_resource.h create mode 100644 src/sys/win32/rc/doom_resource.h create mode 100644 src/sys/win32/win_cpu.cpp create mode 100644 src/sys/win32/win_glimp.cpp create mode 100644 src/sys/win32/win_input.cpp create mode 100644 src/sys/win32/win_local.h create mode 100644 src/sys/win32/win_main.cpp create mode 100644 src/sys/win32/win_net.cpp create mode 100644 src/sys/win32/win_qgl.cpp create mode 100644 src/sys/win32/win_shared.cpp create mode 100644 src/sys/win32/win_syscon.cpp create mode 100644 src/sys/win32/win_taskkeyhook.cpp create mode 100644 src/sys/win32/win_wndproc.cpp create mode 100644 src/tests/abi/BSE_ABI.cpp create mode 100644 src/tests/abi/RendererRaven_ABI.cpp create mode 100644 src/tools/reconstruction/CMakeLists.txt create mode 100644 src/tools/reconstruction/Export-CompilandSourceMap.ps1 create mode 100644 src/tools/reconstruction/Export-EvidenceManifest.ps1 create mode 100644 src/tools/reconstruction/Export-ReconstructionLedger.ps1 create mode 100644 src/tools/reconstruction/Seed-DoomHeaders.ps1 create mode 100644 src/tools/reconstruction/Seed-DoomImplementations.ps1 create mode 100644 src/tools/reconstruction/pdb_inventory.cpp create mode 100644 src/ui/BindWindow.h create mode 100644 src/ui/ChoiceWindow.h create mode 100644 src/ui/DeviceContext.h create mode 100644 src/ui/EditWindow.h create mode 100644 src/ui/FieldWindow.h create mode 100644 src/ui/GameBearShootWindow.h create mode 100644 src/ui/GameBustOutWindow.h create mode 100644 src/ui/GameSSDWindow.h create mode 100644 src/ui/GameWindow.h create mode 100644 src/ui/GuiScript.h create mode 100644 src/ui/ListGUI.h create mode 100644 src/ui/ListGUILocal.h create mode 100644 src/ui/ListWindow.h create mode 100644 src/ui/MarkerWindow.h create mode 100644 src/ui/Rectangle.h create mode 100644 src/ui/RegExp.h create mode 100644 src/ui/RegExp_old.h create mode 100644 src/ui/RenderWindow.h create mode 100644 src/ui/SimpleWindow.h create mode 100644 src/ui/SliderWindow.h create mode 100644 src/ui/UserInterface.h create mode 100644 src/ui/UserInterfaceLocal.h create mode 100644 src/ui/Window.h create mode 100644 src/ui/Winvar.h create mode 100644 src/ui/bindwindow.cpp create mode 100644 src/ui/choicewindow.cpp create mode 100644 src/ui/devicecontext.cpp create mode 100644 src/ui/editwindow.cpp create mode 100644 src/ui/fieldwindow.cpp create mode 100644 src/ui/gamewindow.cpp create mode 100644 src/ui/guiscript.cpp create mode 100644 src/ui/listgui.cpp create mode 100644 src/ui/listwindow.cpp create mode 100644 src/ui/markerwindow.cpp create mode 100644 src/ui/regexp.cpp create mode 100644 src/ui/renderwindow.cpp create mode 100644 src/ui/simplewindow.cpp create mode 100644 src/ui/sliderwindow.cpp create mode 100644 src/ui/userinterface.cpp create mode 100644 src/ui/window.cpp create mode 100644 src/ui/winvar.cpp diff --git a/readme.md b/readme.md index aba2efd..afde0d2 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,9 @@ # Quake 4 The Awakening Release +Src folder - The alpha release contained the PDB file and helped us recreate the Quake 4 The Awakening Engine. +The game code however didn't come with a PDB file so the game code included is only for the retail game. +YOU CAN RUN THE GAME WITHOUT THE SOURCE CODE RECREATION TO RUN THE EXPANSION PACK. + You will need a copy of the retail game(you can buy it on steam). Come join us on discord! diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..8bba011 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,13 @@ +/out/ +/.vs/ +*.user +*.suo +*.VC.db +*.VC.opendb +*.obj +*.pch +*.pdb +*.ilk +*.lib +*.exp + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..d201aca --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.25) + +project(Quake4Reconstruction LANGUAGES C CXX) + +if(NOT WIN32) + message(FATAL_ERROR "The initial reconstruction target is 32-bit Windows") +endif() + +if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4) + message(FATAL_ERROR "Configure with a Win32/x86 generator; the recovered ABI is 32-bit") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +option(Q4_BUILD_IDLIB "Build the Quake 4 SDK idlib seed" ON) +option(Q4_BUILD_ENGINE_SEED "Compile the PDB-proven Doom engine seed" ON) +option(Q4_BUILD_RECON_TOOLS "Build reconstruction inventory tools" ON) + +if(MSVC) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) + # The target was built by VC 7.1. Keep its language behavior while using a + # current compiler; stricter conformance is enabled only after ABI parity. + add_compile_options(/W3 /MP /permissive /Zc:strictStrings- /Zc:forScope-) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + +if(Q4_BUILD_IDLIB) + add_subdirectory(idlib) +endif() + +if(Q4_BUILD_ENGINE_SEED) + add_subdirectory(engine) +endif() + +if(Q4_BUILD_RECON_TOOLS) + add_subdirectory(tools/reconstruction) +endif() + +enable_testing() diff --git a/src/CMakePresets.json b/src/CMakePresets.json new file mode 100644 index 0000000..87257ec --- /dev/null +++ b/src/CMakePresets.json @@ -0,0 +1,50 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + "configurePresets": [ + { + "name": "windows-x86-debug", + "displayName": "Windows x86 Debug", + "generator": "Visual Studio 18 2026", + "architecture": "Win32", + "binaryDir": "${sourceDir}/out/build/windows-x86-debug" + }, + { + "name": "windows-x86-release", + "displayName": "Windows x86 Release", + "generator": "Visual Studio 18 2026", + "architecture": "Win32", + "binaryDir": "${sourceDir}/out/build/windows-x86-release", + "cacheVariables": { + "CMAKE_CONFIGURATION_TYPES": "Release" + } + } + ], + "buildPresets": [ + { + "name": "windows-x86-debug", + "configurePreset": "windows-x86-debug", + "configuration": "Debug" + }, + { + "name": "windows-x86-release", + "configurePreset": "windows-x86-release", + "configuration": "Release" + } + ], + "testPresets": [ + { + "name": "windows-x86-debug", + "configurePreset": "windows-x86-debug", + "configuration": "Debug", + "output": { + "outputOnFailure": true + } + } + ] +} + diff --git a/src/MayaImport/maya_main.h b/src/MayaImport/maya_main.h new file mode 100644 index 0000000..fc92293 --- /dev/null +++ b/src/MayaImport/maya_main.h @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MAYA_MAIN_H__ +#define __MAYA_MAIN_H__ + +/* +============================================================== + + Maya Import + +============================================================== +*/ + +// RAVEN BEGIN +// rhummer: unify allocation strategy to try to eliminate some of our crashes +#ifdef RV_UNIFIED_ALLOCATOR +typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys, void *(*allocator)(size_t size), void (*deallocator)(void *), size_t (*msize)(void *) ); +#else +typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys ); +#endif +// RAVEN END +// RAVEN BEGIN +// bdube: default src and dest ospath's +typedef const char *( *exporterInterface_t )( const char *src_ospath, const char* dst_ospath, const char *commandline ); +// RAVEN END +typedef void ( *exporterShutdown_t )( void ); + +#endif /* !__MAYA_MAIN_H__ */ diff --git a/src/aas/AASBuild_local.h b/src/aas/AASBuild_local.h new file mode 100644 index 0000000..c96d92c --- /dev/null +++ b/src/aas/AASBuild_local.h @@ -0,0 +1,145 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AASBUILD_LOCAL_H__ +#define __AASBUILD_LOCAL_H__ + +#include "AASFile.h" +#include "AASFile_local.h" + +#include "Brush.h" +#include "BrushBSP.h" +#include "AASReach.h" +#include "AASCluster.h" + + +//=============================================================== +// +// idAASBuild +// +//=============================================================== + +typedef struct aasProcNode_s { + idPlane plane; + int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid +} aasProcNode_t; + + +class idLedge { + +public: + idVec3 start; + idVec3 end; + idBrushBSPNode * node; + int numExpandedPlanes; + int numSplitPlanes; + int numPlanes; + idPlane planes[8]; + +public: + idLedge( void ); + idLedge( const idVec3 &v1, const idVec3 &v2, const idVec3 &gravityDir, idBrushBSPNode *n ); + void AddPoint( const idVec3 &v ); + void CreateBevels( const idVec3 &gravityDir ); + void Expand( const idBounds &bounds, float maxStepHeight ); + idWinding * ChopWinding( const idWinding *winding ) const; + bool PointBetweenBounds( const idVec3 &v ) const; +}; + + +class idAASBuild { + +public: + idAASBuild( void ); + ~idAASBuild( void ); + bool Build( const idStr &fileName, const idAASSettings *settings ); + bool BuildReachability( const idStr &fileName, const idAASSettings *settings ); + void Shutdown( void ); + +private: + const idAASSettings * aasSettings; + idAASFileLocal * file; + aasProcNode_t * procNodes; + int numProcNodes; + int numGravitationalSubdivisions; + int numMergedLeafNodes; + int numLedgeSubdivisions; + idList ledgeList; + idBrushMap * ledgeMap; + +private: // map loading + void ParseProcNodes( idLexer *src ); + bool LoadProcBSP( const char *name, ID_TIME_T minFileTime ); + void DeleteProcBSP( void ); + bool ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ); + void ClipBrushSidesWithProcBSP( idBrushList &brushList ); + int ContentsForAAS( int contents ); + idBrushList AddBrushesForMapBrush( const idMapBrush *mapBrush, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ); + idBrushList AddBrushesForMapPatch( const idMapPatch *mapPatch, const idVec3 &origin, const idMat3 &axis, int entityNum, int primitiveNum, idBrushList brushList ); + idBrushList AddBrushesForMapEntity( const idMapEntity *mapEnt, int entityNum, idBrushList brushList ); + idBrushList AddBrushesForMapFile( const idMapFile * mapFile, idBrushList brushList ); + bool CheckForEntities( const idMapFile *mapFile, idStrList &entityClassNames ) const; + void ChangeMultipleBoundingBoxContents_r( idBrushBSPNode *node, int mask ); + +private: // gravitational subdivision + void SetPortalFlags_r( idBrushBSPNode *node ); + bool PortalIsGap( idBrushBSPPortal *portal, int side ); + void GravSubdivLeafNode( idBrushBSPNode *node ); + void GravSubdiv_r( idBrushBSPNode *node ); + void GravitationalSubdivision( idBrushBSP &bsp ); + +private: // ledge subdivision + void LedgeSubdivFlood_r( idBrushBSPNode *node, const idLedge *ledge ); + void LedgeSubdivLeafNodes_r( idBrushBSPNode *node, const idLedge *ledge ); + void LedgeSubdiv( idBrushBSPNode *root ); + bool IsLedgeSide_r( idBrushBSPNode *node, idFixedWinding *w, const idPlane &plane, const idVec3 &normal, const idVec3 &origin, const float radius ); + void AddLedge( const idVec3 &v1, const idVec3 &v2, idBrushBSPNode *node ); + void FindLeafNodeLedges( idBrushBSPNode *root, idBrushBSPNode *node ); + void FindLedges_r( idBrushBSPNode *root, idBrushBSPNode *node ); + void LedgeSubdivision( idBrushBSP &bsp ); + void WriteLedgeMap( const idStr &fileName, const idStr &ext ); + +private: // merging + bool AllGapsLeadToOtherNode( idBrushBSPNode *nodeWithGaps, idBrushBSPNode *otherNode ); + bool MergeWithAdjacentLeafNodes( idBrushBSP &bsp, idBrushBSPNode *node ); + void MergeLeafNodes_r( idBrushBSP &bsp, idBrushBSPNode *node ); + void MergeLeafNodes( idBrushBSP &bsp ); + +private: // storing file + void SetupHash( void ); + void ShutdownHash( void ); + void ClearHash( const idBounds &bounds ); + int HashVec( const idVec3 &vec ); + bool GetVertex( const idVec3 &v, int *vertexNum ); + bool GetEdge( const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ); + bool GetFaceForPortal( idBrushBSPPortal *portal, int side, int *faceNum ); + bool GetAreaForLeafNode( idBrushBSPNode *node, int *areaNum ); + int StoreTree_r( idBrushBSPNode *node ); + void GetSizeEstimate_r( idBrushBSPNode *parent, idBrushBSPNode *node, struct sizeEstimate_s &size ); + void SetSizeEstimate( const idBrushBSP &bsp, idAASFileLocal *file ); + bool StoreFile( const idBrushBSP &bsp ); + +}; + +#endif /* !__AASBUILD_LOCAL_H__ */ diff --git a/src/aas/AASCluster.h b/src/aas/AASCluster.h new file mode 100644 index 0000000..a07a3d4 --- /dev/null +++ b/src/aas/AASCluster.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AASCLUSTER_H__ +#define __AASCLUSTER_H__ + +/* +=============================================================================== + + Area Clustering + +=============================================================================== +*/ + +class idAASCluster { + +public: + bool Build( idAASFileLocal *file ); + bool BuildSingleCluster( idAASFileLocal *file ); + +private: + idAASFileLocal * file; + bool noFaceFlood; + +private: + bool UpdatePortal( int areaNum, int clusterNum ); + bool FloodClusterAreas_r( int areaNum, int clusterNum ); + void RemoveAreaClusterNumbers( void ); + void NumberClusterAreas( int clusterNum ); + bool FindClusters( void ); + void CreatePortals( void ); + bool TestPortals( void ); + void ReportEfficiency( void ); + void RemoveInvalidPortals( void ); +}; + +#endif /* !__AASCLUSTER_H__ */ diff --git a/src/aas/AASFile.cpp b/src/aas/AASFile.cpp new file mode 100644 index 0000000..7891fb9 --- /dev/null +++ b/src/aas/AASFile.cpp @@ -0,0 +1,902 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#if defined( Q4_AAS_LEGACY_SEED ) +#include "AASFile_legacy.inc" +#else + +/* +=========================================================================== + +Quake 4 source reconstruction + +The SDK exposes these methods through idAASFile but does not include their +engine implementation. The matching PDB supplies the class layout and method +signatures; the retail Hex-Rays listing supplies the non-trivial behavior. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "AASFile.h" +#include "AASFile_local.h" + +static bool Reachability_Write( idFile *file, const idReachability *reach ) { + file->WriteFloatString( "\t\t%d %d (%f %f %f) (%f %f %f) %d %d", + reach->travelType, reach->toAreaNum, + reach->start.x, reach->start.y, reach->start.z, + reach->end.x, reach->end.y, reach->end.z, + reach->edgeNum, reach->travelTime ); + return true; +} + +static bool Reachability_Read( Lexer &src, idReachability &reach ) { + reach.travelType = src.ParseInt(); + reach.toAreaNum = src.ParseInt(); + if ( !src.Parse1DMatrix( 3, reach.start.ToFloatPtr() ) || + !src.Parse1DMatrix( 3, reach.end.ToFloatPtr() ) ) { + return false; + } + reach.edgeNum = src.ParseInt(); + reach.travelTime = src.ParseInt(); + return true; +} + +static bool Reachability_Special_Write( idFile *file, const idReachability_Special *reach ) { + file->WriteFloatString( "\n\t\t{\n" ); + for ( int i = 0; i < reach->dict.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = reach->dict.GetKeyVal( i ); + file->WriteFloatString( "\t\t\t\"%s\" \"%s\"\n", kv->GetKey().c_str(), kv->GetValue().c_str() ); + } + file->WriteFloatString( "\t\t}" ); + return true; +} + +static bool Reachability_Special_Read( Lexer &src, idReachability_Special &reach ) { + idToken key; + idToken value; + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + while ( src.ReadToken( &key ) ) { + if ( key == "}" ) { + return true; + } + if ( !src.ExpectTokenType( TT_STRING, 0, &value ) ) { + return false; + } + reach.dict.Set( key, value ); + } + return false; +} + +void idReachability::CopyBase( idReachability &reach ) { + travelType = reach.travelType; + toAreaNum = reach.toAreaNum; + start = reach.start; + end = reach.end; + edgeNum = reach.edgeNum; + travelTime = reach.travelTime; +} + +idAASSettings::idAASSettings() { + numBoundingBoxes = 1; + boundingBoxes[0] = idBounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + usePatches = writeBrushMap = playerFlood = noOptimize = false; + allowSwimReachabilities = allowFlyReachabilities = false; + generateAllFaces = generateTacticalFeatures = false; + iAASOnly = 0; + fileExtension = "aas48"; + gravity.Set( 0, 0, -1066 ); + gravityDir = gravity; + gravityValue = gravityDir.Normalize(); + invGravityDir = -gravityDir; + maxStepHeight = 14.0f; + maxBarrierHeight = 32.0f; + maxWaterJumpHeight = 20.0f; + maxFallHeight = 64.0f; + minFloorCos = 0.7f; + tt_barrierJump = tt_startCrouching = tt_waterJump = tt_startWalkOffLedge = 100; + debugColor.Set( 1, 0, 0, 1 ); + debugDraw = true; +} + +bool idAASSettings::WriteToFile( idFile *file ) const { + file->WriteFloatString( "{\n" ); + file->WriteFloatString( "\tbboxes\n\t{\n" ); + for ( int i = 0; i < numBoundingBoxes; i++ ) { + file->WriteFloatString( "\t\t(%f %f %f)-(%f %f %f)\n", + boundingBoxes[i][0].x, boundingBoxes[i][0].y, boundingBoxes[i][0].z, + boundingBoxes[i][1].x, boundingBoxes[i][1].y, boundingBoxes[i][1].z ); + } + file->WriteFloatString( "\t}\n" ); + file->WriteFloatString( "\tusePatches = %d\n", usePatches ); + file->WriteFloatString( "\twriteBrushMap = %d\n", writeBrushMap ); + file->WriteFloatString( "\tplayerFlood = %d\n", playerFlood ); + file->WriteFloatString( "\tallowSwimReachabilities = %d\n", allowSwimReachabilities ); + file->WriteFloatString( "\tallowFlyReachabilities = %d\n", allowFlyReachabilities ); + file->WriteFloatString( "\tgenerateTacticalFeatures = %d\n", generateTacticalFeatures ); + file->WriteFloatString( "\tfileExtension = \"%s\"\n", fileExtension.c_str() ); + file->WriteFloatString( "\tgravity = (%f %f %f)\n", gravity.x, gravity.y, gravity.z ); + file->WriteFloatString( "\tmaxStepHeight = %f\n", maxStepHeight ); + file->WriteFloatString( "\tmaxBarrierHeight = %f\n", maxBarrierHeight ); + file->WriteFloatString( "\tmaxWaterJumpHeight = %f\n", maxWaterJumpHeight ); + file->WriteFloatString( "\tmaxFallHeight = %f\n", maxFallHeight ); + file->WriteFloatString( "\tminFloorCos = %f\n", minFloorCos ); + file->WriteFloatString( "\ttt_barrierJump = %d\n", tt_barrierJump ); + file->WriteFloatString( "\ttt_startCrouching = %d\n", tt_startCrouching ); + file->WriteFloatString( "\ttt_waterJump = %d\n", tt_waterJump ); + file->WriteFloatString( "\ttt_startWalkOffLedge = %d\n", tt_startWalkOffLedge ); + file->WriteFloatString( "}\n" ); + return true; +} + +bool idAASSettings::ParseBool( Lexer &src, bool &value ) { + if ( !src.ExpectTokenString( "=" ) ) return false; + value = src.ParseBool(); + return true; +} + +bool idAASSettings::ParseInt( Lexer &src, int &value ) { + if ( !src.ExpectTokenString( "=" ) ) return false; + value = src.ParseInt(); + return true; +} + +bool idAASSettings::ParseFloat( Lexer &src, float &value ) { + if ( !src.ExpectTokenString( "=" ) ) return false; + value = src.ParseFloat(); + return true; +} + +bool idAASSettings::ParseVector( Lexer &src, idVec3 &value ) { + return src.ExpectTokenString( "=" ) && src.Parse1DMatrix( 3, value.ToFloatPtr() ); +} + +bool idAASSettings::ParseBBoxes( Lexer &src ) { + idToken token; + numBoundingBoxes = 0; + if ( !src.ExpectTokenString( "{" ) ) return false; + while ( src.ReadToken( &token ) ) { + if ( token == "}" ) return numBoundingBoxes > 0; + if ( numBoundingBoxes >= MAX_AAS_BOUNDING_BOXES ) { + src.Error( "more than %d AAS bounding boxes", MAX_AAS_BOUNDING_BOXES ); + return false; + } + src.UnreadToken( &token ); + idBounds bounds; + if ( !src.Parse1DMatrix( 3, bounds[0].ToFloatPtr() ) || + !src.ExpectTokenString( "-" ) || + !src.Parse1DMatrix( 3, bounds[1].ToFloatPtr() ) ) return false; + boundingBoxes[numBoundingBoxes++] = bounds; + } + return false; +} + +bool idAASSettings::FromParser( Lexer &src ) { + idToken token; + if ( !src.ExpectTokenString( "{" ) ) return false; + while ( src.ReadToken( &token ) ) { + if ( token == "}" ) return numBoundingBoxes > 0; + if ( token == "bboxes" ) { if ( !ParseBBoxes( src ) ) return false; } + else if ( token == "usePatches" ) { if ( !ParseBool( src, usePatches ) ) return false; } + else if ( token == "writeBrushMap" ) { if ( !ParseBool( src, writeBrushMap ) ) return false; } + else if ( token == "playerFlood" ) { if ( !ParseBool( src, playerFlood ) ) return false; } + else if ( token == "noOptimize" ) { if ( !ParseBool( src, noOptimize ) ) return false; } + else if ( token == "allowSwimReachabilities" ) { if ( !ParseBool( src, allowSwimReachabilities ) ) return false; } + else if ( token == "allowFlyReachabilities" ) { if ( !ParseBool( src, allowFlyReachabilities ) ) return false; } + else if ( token == "generateAllFaces" ) { if ( !ParseBool( src, generateAllFaces ) ) return false; } + else if ( token == "generateTacticalFeatures" ) { if ( !ParseBool( src, generateTacticalFeatures ) ) return false; } + else if ( token == "iAASOnly" ) { if ( !ParseInt( src, iAASOnly ) ) return false; } + else if ( token == "fileExtension" ) { if ( !src.ExpectTokenString( "=" ) || !src.ReadToken( &token ) ) return false; fileExtension = token; } + else if ( token == "gravity" ) { if ( !ParseVector( src, gravity ) ) return false; gravityDir = gravity; gravityValue = gravityDir.Normalize(); invGravityDir = -gravityDir; } + else if ( token == "maxStepHeight" ) { if ( !ParseFloat( src, maxStepHeight ) ) return false; } + else if ( token == "maxBarrierHeight" ) { if ( !ParseFloat( src, maxBarrierHeight ) ) return false; } + else if ( token == "maxWaterJumpHeight" ) { if ( !ParseFloat( src, maxWaterJumpHeight ) ) return false; } + else if ( token == "maxFallHeight" ) { if ( !ParseFloat( src, maxFallHeight ) ) return false; } + else if ( token == "minFloorCos" ) { if ( !ParseFloat( src, minFloorCos ) ) return false; } + else if ( token == "tt_barrierJump" ) { if ( !ParseInt( src, tt_barrierJump ) ) return false; } + else if ( token == "tt_startCrouching" ) { if ( !ParseInt( src, tt_startCrouching ) ) return false; } + else if ( token == "tt_waterJump" ) { if ( !ParseInt( src, tt_waterJump ) ) return false; } + else if ( token == "tt_startWalkOffLedge" ) { if ( !ParseInt( src, tt_startWalkOffLedge ) ) return false; } + else if ( token == "debugDraw" ) { if ( !ParseBool( src, debugDraw ) ) return false; } + else if ( token == "debugColor" ) { idVec3 rgb; if ( !ParseVector( src, rgb ) ) return false; debugColor.Set( rgb.x, rgb.y, rgb.z, 1 ); } + else src.Warning( "unknown AAS setting '%s'", token.c_str() ); + } + return false; +} + +idAASFileLocal::idAASFileLocal() { + planeList.SetGranularity( 4096 ); + vertices.SetGranularity( 4096 ); + edges.SetGranularity( 4096 ); + edgeIndex.SetGranularity( 4096 ); + faces.SetGranularity( 1024 ); + faceIndex.SetGranularity( 4096 ); + areas.SetGranularity( 1024 ); + nodes.SetGranularity( 1024 ); + portals.SetGranularity( 1024 ); + portalIndex.SetGranularity( 4096 ); + clusters.SetGranularity( 1024 ); + featureIndex.SetGranularity( 4096 ); + features.SetGranularity( 1024 ); + crc = 0; + memset( isDummy, 0, sizeof( isDummy ) ); +} + +idAASFileLocal::~idAASFileLocal() { + DeleteReachabilities(); +} + +void idAASFileLocal::Init() { + aasEdge_t edge; + aasFace_t face; + aasArea_t area; + aasNode_t node; + memset( &edge, 0, sizeof( edge ) ); + memset( &face, 0, sizeof( face ) ); + memset( &area, 0, sizeof( area ) ); + memset( &node, 0, sizeof( node ) ); + edges.Append( edge ); + faces.Append( face ); + areas.Append( area ); + nodes.Append( node ); +} + +void idAASFileLocal::SetSizes( sizeEstimate_t size ) { + planeList.Resize( size.numNodes / 2, 1024 ); + vertices.Resize( size.numEdgeIndexes / 3, 1024 ); + edges.Resize( size.numEdgeIndexes / 2, 1024 ); + edgeIndex.Resize( size.numEdgeIndexes, 4096 ); + faces.Resize( size.numFaceIndexes, 1024 ); + faceIndex.Resize( size.numFaceIndexes, 4096 ); + areas.Resize( size.numAreas, 1024 ); + nodes.Resize( size.numNodes, 1024 ); + featureIndex.Resize( 2048, 2048 ); + features.Resize( 2048, 2048 ); +} + +bool idAASFileLocal::ParseIndex( Lexer &src, idList &indexes, int dummyIndex ) { + const int count = src.ParseInt(); + indexes.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( int i = 0; i < count; i++ ) { + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) { + return false; + } + const aasIndex_t index = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) { + return false; + } + indexes.Append( index ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) { + isDummy[dummyIndex] = true; + } + return true; +} + +bool idAASFileLocal::ParsePlanes( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + planeList.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( int i = 0; i < count; i++ ) { + idVec4 value; + src.ParseInt(); + if ( !src.Parse1DMatrix( 4, value.ToFloatPtr() ) ) { + return false; + } + idPlane plane; + plane.SetNormal( value.ToVec3() ); + plane.SetDist( value.w ); + planeList.Append( plane ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) { + isDummy[dummyIndex] = true; + } + return true; +} + +bool idAASFileLocal::ParseVertices( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + vertices.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( int i = 0; i < count; i++ ) { + aasVertex_t vertex; + src.ParseInt(); + if ( !src.Parse1DMatrix( 3, vertex.ToFloatPtr() ) ) { + return false; + } + vertices.Append( vertex ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) { + isDummy[dummyIndex] = true; + } + return true; +} + +bool idAASFileLocal::ParseEdges( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + edges.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( int i = 0; i < count; i++ ) { + aasEdge_t edge; + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) return false; + edge.vertexNum[0] = src.ParseInt(); + edge.vertexNum[1] = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) return false; + edges.Append( edge ); + } + if ( !src.ExpectTokenString( "}" ) ) return false; + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true; + return true; +} + +bool idAASFileLocal::ParseFaces( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + faces.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) return false; + for ( int i = 0; i < count; i++ ) { + aasFace_t face; + memset( &face, 0, sizeof( face ) ); + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) return false; + face.planeNum = src.ParseInt(); + face.flags = src.ParseInt(); + face.areas[0] = src.ParseInt(); + face.areas[1] = src.ParseInt(); + face.firstEdge = src.ParseInt(); + face.numEdges = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) return false; + faces.Append( face ); + } + if ( !src.ExpectTokenString( "}" ) ) return false; + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true; + return true; +} + +bool idAASFileLocal::ParseReachabilities( Lexer &src, int areaNum ) { + aasArea_t &area = areas[areaNum]; + const int count = src.ParseInt(); + if ( !src.ExpectTokenString( "{" ) ) return false; + area.reach = NULL; + area.rev_reach = NULL; + area.travelFlags = AreaContentsTravelFlags( areaNum ); + for ( int i = 0; i < count; i++ ) { + idReachability base; + memset( &base, 0, sizeof( base ) ); + if ( !Reachability_Read( src, base ) ) return false; + idReachability *reach; + if ( base.travelType == TFL_SPECIAL ) { + idReachability_Special *special = new idReachability_Special; + if ( !Reachability_Special_Read( src, *special ) ) { + delete special; + return false; + } + reach = special; + } else { + reach = new idReachability; + } + memset( static_cast( reach ), 0, sizeof( idReachability ) ); + reach->CopyBase( base ); + reach->fromAreaNum = areaNum; + reach->next = area.reach; + area.reach = reach; + } + return src.ExpectTokenString( "}" ) != 0; +} + +bool idAASFileLocal::ParseAreas( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + areas.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) return false; + for ( int i = 0; i < count; i++ ) { + aasArea_t area; + memset( &area, 0, sizeof( area ) ); + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) return false; + area.flags = src.ParseInt(); + area.contents = src.ParseInt(); + area.firstFace = src.ParseInt(); + area.numFaces = src.ParseInt(); + area.cluster = src.ParseInt(); + area.clusterAreaNum = src.ParseInt(); + if ( version != "1.07" ) { + area.numFeatures = src.ParseInt(); + area.firstFeature = src.ParseInt(); + } + if ( !src.ExpectTokenString( ")" ) ) return false; + areas.Append( area ); + if ( !ParseReachabilities( src, i ) ) return false; + } + if ( !src.ExpectTokenString( "}" ) ) return false; + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true; + LinkReversedReachability(); + return true; +} + +bool idAASFileLocal::ParseNodes( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + nodes.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) return false; + for ( int i = 0; i < count; i++ ) { + aasNode_t node; + memset( &node, 0, sizeof( node ) ); + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) return false; + node.planeNum = src.ParseInt(); + node.children[0] = src.ParseInt(); + node.children[1] = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) return false; + nodes.Append( node ); + } + if ( !src.ExpectTokenString( "}" ) ) return false; + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true; + return true; +} + +bool idAASFileLocal::ParsePortals( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + portals.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) return false; + for ( int i = 0; i < count; i++ ) { + aasPortal_t portal; + memset( &portal, 0, sizeof( portal ) ); + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) return false; + portal.areaNum = src.ParseInt(); + portal.clusters[0] = src.ParseInt(); + portal.clusters[1] = src.ParseInt(); + portal.clusterAreaNum[0] = src.ParseInt(); + portal.clusterAreaNum[1] = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) return false; + portals.Append( portal ); + } + if ( !src.ExpectTokenString( "}" ) ) return false; + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true; + return true; +} + +bool idAASFileLocal::ParseClusters( Lexer &src, int dummyIndex ) { + const int count = src.ParseInt(); + clusters.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) return false; + for ( int i = 0; i < count; i++ ) { + aasCluster_t cluster; + memset( &cluster, 0, sizeof( cluster ) ); + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) return false; + cluster.numAreas = src.ParseInt(); + cluster.numReachableAreas = src.ParseInt(); + cluster.firstPortal = src.ParseInt(); + cluster.numPortals = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) return false; + clusters.Append( cluster ); + } + if ( !src.ExpectTokenString( "}" ) ) return false; + if ( count == 0 && dummyIndex >= 0 && dummyIndex < 11 ) isDummy[dummyIndex] = true; + return true; +} + +bool idAASFileLocal::ParseFeatures( Lexer &src ) { + const int count = src.ParseInt(); + features.Resize( count ); + if ( !src.ExpectTokenString( "{" ) ) return false; + for ( int i = 0; i < count; i++ ) { + aasFeature_t feature; + memset( &feature, 0, sizeof( feature ) ); + src.ParseInt(); + if ( !src.ExpectTokenString( "(" ) ) return false; + feature.flags = src.ParseInt(); + feature.height = src.ParseInt(); + feature.normalx = src.ParseInt(); + feature.normaly = src.ParseInt(); + feature.x = src.ParseInt(); + feature.y = src.ParseInt(); + feature.z = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) return false; + features.Append( feature ); + } + return src.ExpectTokenString( "}" ) != 0; +} + +bool idAASFileLocal::Load( const idStr &fileName, unsigned int mapFileCRC ) { + Lexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGESCAPECHARS | + LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES ); + idToken token; + name = fileName; + crc = mapFileCRC; + common->Printf( "[Load AAS]\nloading %s\n", name.c_str() ); + if ( !src.LoadFile( name.c_str() ) ) { + return false; + } + if ( !src.ExpectTokenString( AAS_FILEID ) ) { + common->Warning( "Not an AAS file: '%s'", name.c_str() ); + return false; + } + if ( !src.ReadToken( &token ) ) { + return false; + } + version = token; + if ( version != AAS_FILEVERSION && version != "1.07" ) { + common->Warning( "AAS file '%s' has version %s instead of %s", name.c_str(), version.c_str(), AAS_FILEVERSION ); + } + if ( !src.ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) { + common->Warning( "AAS file '%s' has no map file CRC", name.c_str() ); + return false; + } + const unsigned int fileCRC = token.GetUnsignedLongValue(); + if ( mapFileCRC && fileCRC != mapFileCRC ) { + common->Warning( "AAS file '%s' is out of date", name.c_str() ); + return false; + } + Clear(); + while ( src.ReadToken( &token ) ) { + bool parsed = false; + if ( token == "settings" ) parsed = settings.FromParser( src ); + else if ( token == "planes" ) parsed = ParsePlanes( src, 0 ); + else if ( token == "vertices" ) parsed = ParseVertices( src, 1 ); + else if ( token == "edges" ) parsed = ParseEdges( src, 2 ); + else if ( token == "edgeIndex" ) parsed = ParseIndex( src, edgeIndex, 3 ); + else if ( token == "faces" ) parsed = ParseFaces( src, 4 ); + else if ( token == "faceIndex" ) parsed = ParseIndex( src, faceIndex, 5 ); + else if ( token == "areas" ) parsed = ParseAreas( src, 6 ); + else if ( token == "nodes" ) parsed = ParseNodes( src, 7 ); + else if ( token == "portals" ) parsed = ParsePortals( src, 8 ); + else if ( token == "portalIndex" ) parsed = ParseIndex( src, portalIndex, 9 ); + else if ( token == "clusters" ) parsed = ParseClusters( src, 10 ); + else if ( token == "featureIndex" ) parsed = ParseIndex( src, featureIndex, 11 ); + else if ( token == "features" ) parsed = ParseFeatures( src ); + else { + src.Error( "idAASFileLocal::Load: bad token \"%s\"", token.c_str() ); + return false; + } + if ( !parsed ) { + return false; + } + } + FinishAreas(); + if ( !IsDummyFile( mapFileCRC ) ) { + if ( !vertices.Num() || !edges.Num() || !faces.Num() ) { + common->Warning( "error loading %s (invalid data)", fileName.c_str() ); + return false; + } + const int depth = MaxTreeDepth(); + if ( depth > MAX_AAS_TREE_DEPTH ) { + common->Warning( "idAASFileLocal::Load: tree depth = %d", depth ); + } + common->Printf( "done loading %s (size %d).\n", fileName.c_str(), MemorySize() ); + } + return !src.HadError(); +} + +bool idAASFileLocal::Write( const idStr &fileName, unsigned int mapFileCRC ) { + common->Printf( "[Write AAS]\nwriting %s\n", fileName.c_str() ); + name = fileName; + crc = mapFileCRC; + idFile *file = fileSystem->OpenFileWrite( fileName, "fs_devpath" ); + if ( !file ) { + common->Warning( "Error opening %s", fileName.c_str() ); + return false; + } + file->WriteFloatString( "%s \"%s\"\n\n%u\n\n", AAS_FILEID, AAS_FILEVERSION, mapFileCRC ); + file->WriteFloatString( "settings\n" ); + settings.WriteToFile( file ); + file->WriteFloatString( "planes %d {\n", planeList.Num() ); + for ( int i = 0; i < planeList.Num(); i++ ) file->WriteFloatString( "\t%d ( %f %f %f %f )\n", i, planeList[i].Normal().x, planeList[i].Normal().y, planeList[i].Normal().z, planeList[i].Dist() ); + file->WriteFloatString( "}\nvertices %d {\n", vertices.Num() ); + for ( int i = 0; i < vertices.Num(); i++ ) file->WriteFloatString( "\t%d ( %f %f %f )\n", i, vertices[i].x, vertices[i].y, vertices[i].z ); + file->WriteFloatString( "}\nedges %d {\n", edges.Num() ); + for ( int i = 0; i < edges.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d )\n", i, edges[i].vertexNum[0], edges[i].vertexNum[1] ); + file->WriteFloatString( "}\nedgeIndex %d {\n", edgeIndex.Num() ); + for ( int i = 0; i < edgeIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, edgeIndex[i] ); + file->WriteFloatString( "}\nfaces %d {\n", faces.Num() ); + for ( int i = 0; i < faces.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d %d %d )\n", i, faces[i].planeNum, faces[i].flags, faces[i].areas[0], faces[i].areas[1], faces[i].firstEdge, faces[i].numEdges ); + file->WriteFloatString( "}\nfaceIndex %d {\n", faceIndex.Num() ); + for ( int i = 0; i < faceIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, faceIndex[i] ); + file->WriteFloatString( "}\nareas %d {\n", areas.Num() ); + for ( int i = 0; i < areas.Num(); i++ ) { + int reachCount = 0; + for ( idReachability *reach = areas[i].reach; reach; reach = reach->next ) reachCount++; + file->WriteFloatString( "\t%d ( %d %d %d %d %d %d %d %d ) %d {\n", i, areas[i].flags, areas[i].contents, areas[i].firstFace, areas[i].numFaces, areas[i].cluster, areas[i].clusterAreaNum, areas[i].numFeatures, areas[i].firstFeature, reachCount ); + for ( idReachability *reach = areas[i].reach; reach; reach = reach->next ) { + Reachability_Write( file, reach ); + if ( reach->travelType == TFL_SPECIAL ) Reachability_Special_Write( file, static_cast( reach ) ); + file->WriteFloatString( "\n" ); + } + file->WriteFloatString( "\t}\n" ); + } + file->WriteFloatString( "}\nnodes %d {\n", nodes.Num() ); + for ( int i = 0; i < nodes.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d )\n", i, nodes[i].planeNum, nodes[i].children[0], nodes[i].children[1] ); + file->WriteFloatString( "}\nportals %d {\n", portals.Num() ); + for ( int i = 0; i < portals.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d %d )\n", i, portals[i].areaNum, portals[i].clusters[0], portals[i].clusters[1], portals[i].clusterAreaNum[0], portals[i].clusterAreaNum[1] ); + file->WriteFloatString( "}\nportalIndex %d {\n", portalIndex.Num() ); + for ( int i = 0; i < portalIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, portalIndex[i] ); + file->WriteFloatString( "}\nclusters %d {\n", clusters.Num() ); + for ( int i = 0; i < clusters.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d )\n", i, clusters[i].numAreas, clusters[i].numReachableAreas, clusters[i].firstPortal, clusters[i].numPortals ); + file->WriteFloatString( "}\n" ); + if ( features.Num() ) { + file->WriteFloatString( "featureIndex %d {\n", featureIndex.Num() ); + for ( int i = 0; i < featureIndex.Num(); i++ ) file->WriteFloatString( "\t%d ( %d )\n", i, featureIndex[i] ); + file->WriteFloatString( "}\nfeatures %d {\n", features.Num() ); + for ( int i = 0; i < features.Num(); i++ ) file->WriteFloatString( "\t%d ( %d %d %d %d %d %d %d )\n", i, features[i].flags, features[i].height, features[i].normalx, features[i].normaly, features[i].x, features[i].y, features[i].z ); + file->WriteFloatString( "}\n" ); + } + fileSystem->CloseFile( file ); + common->Printf( "done.\n" ); + return true; +} + +int idAASFileLocal::MemorySize() const { + return planeList.Size() + vertices.Size() + edges.Size() + edgeIndex.Size() + + faces.Size() + faceIndex.Size() + areas.Size() + nodes.Size() + portals.Size() + + portalIndex.Size() + clusters.Size() + featureIndex.Size() + features.Size() + + sizeof( idReachability_Walk ) * NumReachabilities(); +} + +idAASFile *idAASFileLocal::CreateNew( void ) { + return new idAASFileLocal; +} + +idAASSettings *idAASFileLocal::CreateAASSettings( void ) { + return new idAASSettings; +} + +idReachability *idAASFileLocal::CreateReachability( int type ) { + switch ( type ) { + case RE_WALK: return new idReachability_Walk; + case RE_WALKOFFLEDGE: return new idReachability_WalkOffLedge; + case RE_FLY: return new idReachability_Fly; + case RE_SWIM: return new idReachability_Swim; + case RE_WATERJUMP: return new idReachability_WaterJump; + case RE_BARRIERJUMP: return new idReachability_BarrierJump; + case RE_SPECIAL: return new idReachability_Special; + default: return NULL; + } +} + +bool idAASFileLocal::FromParser( idAASSettings *edit, Lexer &src ) { + return edit->FromParser( src ); +} + +const char *idAASFileLocal::GetName( void ) const { + return name.c_str(); +} + +unsigned int idAASFileLocal::GetCRC( void ) const { + return crc; +} + +int idAASFileLocal::GetNumPlanes( void ) const { return planeList.Num(); } +idPlane &idAASFileLocal::GetPlane( int index ) { return planeList[index]; } +int idAASFileLocal::FindPlane( const idPlane &plane, const float normalEps, const float distEps ) { return planeList.FindPlane( plane, normalEps, distEps ); } + +int idAASFileLocal::GetNumVertices( void ) const { return vertices.Num(); } +aasVertex_t &idAASFileLocal::GetVertex( int index ) { return vertices[index]; } +int idAASFileLocal::AppendVertex( aasVertex_t &vertex ) { return vertices.Append( vertex ); } + +int idAASFileLocal::GetNumEdges( void ) const { return edges.Num(); } +aasEdge_t &idAASFileLocal::GetEdge( int index ) { return edges[index]; } +int idAASFileLocal::AppendEdge( aasEdge_t &edge ) { return edges.Append( edge ); } + +int idAASFileLocal::GetNumEdgeIndexes( void ) const { return edgeIndex.Num(); } +aasIndex_t &idAASFileLocal::GetEdgeIndex( int index ) { return edgeIndex[index]; } +int idAASFileLocal::AppendEdgeIndex( aasIndex_t &index ) { return edgeIndex.Append( index ); } + +int idAASFileLocal::GetNumFaces( void ) const { return faces.Num(); } +aasFace_t &idAASFileLocal::GetFace( int index ) { return faces[index]; } +int idAASFileLocal::AppendFace( aasFace_t &face ) { return faces.Append( face ); } + +int idAASFileLocal::GetNumFaceIndexes( void ) const { return faceIndex.Num(); } +aasIndex_t &idAASFileLocal::GetFaceIndex( int index ) { return faceIndex[index]; } +int idAASFileLocal::AppendFaceIndex( aasIndex_t &index ) { return faceIndex.Append( index ); } + +int idAASFileLocal::GetNumAreas( void ) const { return areas.Num(); } +aasArea_t &idAASFileLocal::GetArea( int index ) { return areas[index]; } +int idAASFileLocal::AppendArea( aasArea_t &area ) { return areas.Append( area ); } + +int idAASFileLocal::GetNumNodes( void ) const { return nodes.Num(); } +aasNode_t &idAASFileLocal::GetNode( int index ) { return nodes[index]; } +int idAASFileLocal::AppendNode( aasNode_t &node ) { return nodes.Append( node ); } +void idAASFileLocal::SetNumNodes( int num ) { nodes.SetNum( num ); } + +int idAASFileLocal::GetNumPortals( void ) const { return portals.Num(); } +aasPortal_t &idAASFileLocal::GetPortal( int index ) { return portals[index]; } +int idAASFileLocal::AppendPortal( aasPortal_t &portal ) { return portals.Append( portal ); } + +int idAASFileLocal::GetNumPortalIndexes( void ) const { return portalIndex.Num(); } +aasIndex_t &idAASFileLocal::GetPortalIndex( int index ) { return portalIndex[index]; } +int idAASFileLocal::AppendPortalIndex( aasIndex_t &index, int clusterNum ) { + clusters[clusterNum].numPortals++; + return portalIndex.Append( index ); +} + +int idAASFileLocal::GetNumClusters( void ) const { return clusters.Num(); } +aasCluster_t &idAASFileLocal::GetCluster( int index ) { return clusters[index]; } +int idAASFileLocal::AppendCluster( aasCluster_t &cluster ) { return clusters.Append( cluster ); } + +void idAASFileLocal::ClearTactical( void ) { + featureIndex.Clear(); + features.Clear(); +} + +int idAASFileLocal::GetNumFeatureIndexes( void ) const { return featureIndex.Num(); } +aasIndex_t &idAASFileLocal::GetFeatureIndex( int index ) { return featureIndex[index]; } +int idAASFileLocal::AppendFeatureIndex( aasIndex_t &index ) { return featureIndex.Append( index ); } + +int idAASFileLocal::GetNumFeatures( void ) const { return features.Num(); } +aasFeature_t &idAASFileLocal::GetFeature( int index ) { return features[index]; } +int idAASFileLocal::AppendFeature( aasFeature_t &feature ) { return features.Append( feature ); } + +idAASSettings &idAASFileLocal::GetSettings( void ) { return settings; } +void idAASFileLocal::SetSettings( const idAASSettings &in ) { settings = in; } + +void idAASFileLocal::SetPortalMaxTravelTime( int index, int time ) { portals[index].maxAreaTravelTime = time; } +void idAASFileLocal::SetAreaTravelFlag( int index, int flag ) { areas[index].travelFlags |= flag; } +void idAASFileLocal::RemoveAreaTravelFlag( int index, int flag ) { areas[index].travelFlags &= ~flag; } + +size_t idAASFileLocal::GetMemorySize( void ) { + int numReachableAreas = 0; + int routingMemory = 0; + for ( int i = 0; i < clusters.Num(); i++ ) { + numReachableAreas += clusters[i].numReachableAreas; + routingMemory += Square( clusters[i].numReachableAreas ); + } + return MemorySize() + routingMemory + 3 * numReachableAreas * portals.Num(); +} + +bool idAASFileLocal::IsDummyFile( unsigned int mapFileCRC ) { + for ( int i = 0; i < 11; i++ ) { + if ( !isDummy[i] ) { + return false; + } + } + return crc == mapFileCRC; +} + +const idDict &idAASFileLocal::GetReachabilitySpecialDict( idReachability *reach ) const { + return static_cast( reach )->dict; +} + +void idAASFileLocal::SetReachabilitySpecialDictKeyValue( idReachability *reach, const char *key, const char *value ) { + static_cast( reach )->dict.Set( key, value ); +} + +void idAASFileLocal::Clear() { + DeleteReachabilities(); + planeList.Clear(); + vertices.Clear(); + edges.Clear(); + edgeIndex.Clear(); + faces.Clear(); + faceIndex.Clear(); + areas.Clear(); + nodes.Clear(); + portals.Clear(); + portalIndex.Clear(); + clusters.Clear(); + ClearTactical(); + memset( isDummy, 0, sizeof( isDummy ) ); +} + +void idAASFileLocal::LinkReversedReachability() { + for ( int i = 0; i < areas.Num(); i++ ) { + areas[i].rev_reach = NULL; + } + for ( int i = 0; i < areas.Num(); i++ ) { + for ( idReachability *reach = areas[i].reach; reach; reach = reach->next ) { + if ( reach->toAreaNum <= 0 || reach->toAreaNum >= areas.Num() ) { + continue; + } + reach->rev_next = areas[reach->toAreaNum].rev_reach; + areas[reach->toAreaNum].rev_reach = reach; + } + } +} + +void idAASFileLocal::FinishAreas() { + for ( int i = 0; i < areas.Num(); i++ ) { + areas[i].center = AreaReachableGoal( i ); + areas[i].bounds = AreaBounds( i ); + } +} + +int idAASFileLocal::NumReachabilities() const { + int count = 0; + for ( int i = 0; i < areas.Num(); i++ ) { + for ( const idReachability *reach = areas[i].reach; reach; reach = reach->next ) { + count++; + } + } + return count; +} + +void idAASFileLocal::PrintInfo() const { + common->Printf( "%6d KB file size\n", MemorySize() >> 10 ); + common->Printf( "%6d areas\n", areas.Num() ); + common->Printf( "%6d max tree depth\n", MaxTreeDepth() ); + ReportRoutingEfficiency(); +} + +void idAASFileLocal::ReportRoutingEfficiency() const { + int reachableAreas = 0; + int total = 0; + for ( int i = 0; i < clusters.Num(); i++ ) { + const int count = clusters[i].numReachableAreas; + reachableAreas += count; + total += count * count; + } + total += reachableAreas * portals.Num(); + common->Printf( "%6d reachable areas\n", reachableAreas ); + common->Printf( "%6d reachabilities\n", NumReachabilities() ); + common->Printf( "%6d KB max routing cache\n", ( total * 3 ) >> 10 ); +} + +void idAASFileLocal::DeleteReachabilities() { + for ( int i = 0; i < areas.Num(); i++ ) { + idReachability *reach = areas[i].reach; + while ( reach ) { + idReachability *next = reach->next; + delete reach; + reach = next; + } + areas[i].reach = NULL; + areas[i].rev_reach = NULL; + } +} + +void idAASFileLocal::DeleteClusters() { + aasPortal_t portal; + aasCluster_t cluster; + memset( &portal, 0, sizeof( portal ) ); + memset( &cluster, 0, sizeof( cluster ) ); + portals.Clear(); + portalIndex.Clear(); + clusters.Clear(); + portals.Append( portal ); + clusters.Append( cluster ); +} + +#endif diff --git a/src/aas/AASFile.h b/src/aas/AASFile.h new file mode 100644 index 0000000..a844081 --- /dev/null +++ b/src/aas/AASFile.h @@ -0,0 +1,509 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AASFILE_H__ +#define __AASFILE_H__ + +/* +=============================================================================== + + AAS File + +=============================================================================== +*/ + +#define AAS_FILEID "DewmAAS" +#define AAS_FILEVERSION "1.08" + +// travel flags +#define TFL_INVALID BIT(0) // not valid +#define TFL_WALK BIT(1) // walking +#define TFL_CROUCH BIT(2) // crouching +#define TFL_WALKOFFLEDGE BIT(3) // walking of a ledge +#define TFL_BARRIERJUMP BIT(4) // jumping onto a barrier +#define TFL_JUMP BIT(5) // jumping +#define TFL_LADDER BIT(6) // climbing a ladder +#define TFL_SWIM BIT(7) // swimming +#define TFL_WATERJUMP BIT(8) // jump out of the water +#define TFL_TELEPORT BIT(9) // teleportation +#define TFL_ELEVATOR BIT(10) // travel by elevator +#define TFL_FLY BIT(11) // fly +#define TFL_SPECIAL BIT(12) // special +#define TFL_WATER BIT(21) // travel through water +#define TFL_AIR BIT(22) // travel through air + +// face flags +#define FACE_SOLID BIT(0) // solid at the other side +#define FACE_LADDER BIT(1) // ladder surface +#define FACE_FLOOR BIT(2) // standing on floor when on this face +#define FACE_LIQUID BIT(3) // face seperating two areas with liquid +#define FACE_LIQUIDSURFACE BIT(4) // face seperating liquid and air + +// area flags +#define AREA_FLOOR BIT(0) // AI can stand on the floor in this area +#define AREA_GAP BIT(1) // area has a gap +#define AREA_LEDGE BIT(2) // if entered the AI bbox partly floats above a ledge +#define AREA_LADDER BIT(3) // area contains one or more ladder faces +#define AREA_LIQUID BIT(4) // area contains a liquid +#define AREA_CROUCH BIT(5) // AI cannot walk but can only crouch in this area +#define AREA_REACHABLE_WALK BIT(6) // area is reachable by walking or swimming +#define AREA_REACHABLE_FLY BIT(7) // area is reachable by flying + +// area contents flags +#define AREACONTENTS_SOLID BIT(0) // solid, not a valid area +#define AREACONTENTS_WATER BIT(1) // area contains water +#define AREACONTENTS_CLUSTERPORTAL BIT(2) // area is a cluster portal +#define AREACONTENTS_OBSTACLE BIT(3) // area contains (part of) a dynamic obstacle +#define AREACONTENTS_TELEPORTER BIT(4) // area contains (part of) a teleporter trigger + +// bits for different bboxes +#define AREACONTENTS_BBOX_BIT 24 + + +// RAVEN BEGIN +// cdr: AASTactical + +// feature bits +#define FEATURE_COVER BIT(0) // provides cover +#define FEATURE_LOOK_LEFT BIT(1) // attack by leaning left +#define FEATURE_LOOK_RIGHT BIT(2) // attack by leaning right +#define FEATURE_LOOK_OVER BIT(3) // attack by leaning over the cover +#define FEATURE_CORNER_LEFT BIT(4) // is a left corner +#define FEATURE_CORNER_RIGHT BIT(5) // is a right corner +#define FEATURE_PINCH BIT(6) // is a tight area connecting two larger areas +#define FEATURE_VANTAGE BIT(7) // provides a good view of the sampled area as a whole + +// forward reference of sensor object +struct rvAASTacticalSensor; +struct rvMarker; + +// RAVEN END + + +#define MAX_REACH_PER_AREA 256 +#define MAX_AAS_TREE_DEPTH 128 + +#define MAX_AAS_BOUNDING_BOXES 4 + +typedef enum { + + RE_WALK, + RE_WALKOFFLEDGE, + RE_FLY, + RE_SWIM, + RE_WATERJUMP, + RE_BARRIERJUMP, + RE_SPECIAL +}; + + +// reachability to another area +class idReachability { +public: + void CopyBase( idReachability &reach ); + + int travelType; // type of travel required to get to the area + short toAreaNum; // number of the reachable area + short fromAreaNum; // number of area the reachability starts + idVec3 start; // start point of inter area movement + idVec3 end; // end point of inter area movement + int edgeNum; // edge crossed by this reachability + unsigned short travelTime; // travel time of the inter area movement + byte number; // reachability number within the fromAreaNum (must be < 256) + byte disableCount; // number of times this reachability has been disabled + idReachability * next; // next reachability in list + idReachability * rev_next; // next reachability in reversed list + unsigned short * areaTravelTimes; // travel times within the fromAreaNum from reachabilities that lead towards this area +}; + +class idReachability_Walk : public idReachability { +}; + +class idReachability_BarrierJump : public idReachability { +}; + +class idReachability_WaterJump : public idReachability { +}; + +class idReachability_WalkOffLedge : public idReachability { +}; + +class idReachability_Swim : public idReachability { +}; + +class idReachability_Fly : public idReachability { +}; + +class idReachability_Special : public idReachability { + friend class idAASFileLocal; +public: + idDict dict; +}; + +// index +typedef int aasIndex_t; + +// vertex +typedef idVec3 aasVertex_t; + +// edge +typedef struct aasEdge_s { + int vertexNum[2]; // numbers of the vertexes of this edge +} aasEdge_t; + +// area boundary face +typedef struct aasFace_s { + unsigned short planeNum; // number of the plane this face is on + unsigned short flags; // face flags + int numEdges; // number of edges in the boundary of the face + int firstEdge; // first edge in the edge index + short areas[2]; // area at the front and back of this face +} aasFace_t; + +// area with a boundary of faces +typedef struct aasArea_s { + int numFaces; // number of faces used for the boundary of the area + int firstFace; // first face in the face index used for the boundary of the area + idBounds bounds; // bounds of the area + idVec3 center; // center of the area an AI can move towards + float ceiling; // top of the area + unsigned short flags; // several area flags + unsigned short contents; // contents of the area + short cluster; // cluster the area belongs to, if negative it's a portal + short clusterAreaNum; // number of the area in the cluster + int travelFlags; // travel flags for traveling through this area + idReachability * reach; // reachabilities that start from this area + idReachability * rev_reach; // reachabilities that lead to this area + + // RAVEN BEGIN + // cdr: AASTactical + unsigned short numFeatures; // number of features in this area + unsigned short firstFeature; // first feature in the feature index within this area + + // cdr: Obstacle Avoidance + rvMarker* firstMarker; // first obstacle avoidance threat in this area (0 if none) + // RAVEN END +} aasArea_t; + +// nodes of the bsp tree +typedef struct aasNode_s { + unsigned short planeNum; // number of the plane that splits the subspace at this node + int children[2]; // child nodes, zero is solid, negative is -(area number) +} aasNode_t; + +// cluster portal +typedef struct aasPortal_s { + short areaNum; // number of the area that is the actual portal + short clusters[2]; // number of cluster at the front and back of the portal + short clusterAreaNum[2]; // number of this portal area in the front and back cluster + unsigned short maxAreaTravelTime; // maximum travel time through the portal area +} aasPortal_t; + +// cluster +typedef struct aasCluster_s { + int numAreas; // number of areas in the cluster + int numReachableAreas; // number of areas with reachabilities + int numPortals; // number of cluster portals + int firstPortal; // first cluster portal in the index +} aasCluster_t; + +// RAVEN BEGIN +// cdr: AASTactical +typedef struct aasFeature_s { + short x; // 2 Bytes + short y; // 2 Bytes + short z; // 2 Bytes + unsigned short flags; // 2 Bytes + unsigned char normalx; // 1 Byte + unsigned char normaly; // 1 Byte + unsigned char height; // 1 Byte + unsigned char weight; // 1 Byte + + idVec3& Normal(); + idVec3& Origin(); + + void DrawDebugInfo( int index=-1 ); + int GetLookPos( idVec3& lookPos, const idVec3& aimAtOrigin, const float leanDistance=16.0f ); +} aasFeature_t; //-------------------------------- + // 12 Bytes +// RAVEN END + +// trace through the world +typedef struct aasTrace_s { + // parameters + int flags; // areas with these flags block the trace + int travelFlags; // areas with these travel flags block the trace + int maxAreas; // size of the 'areas' array + int getOutOfSolid; // trace out of solid if the trace starts in solid + // output + float fraction; // fraction of trace completed + idVec3 endpos; // end position of trace + int planeNum; // plane hit + int lastAreaNum; // number of last area the trace went through + int blockingAreaNum; // area that could not be entered + int numAreas; // number of areas the trace went through + int * areas; // array to store areas the trace went through + idVec3 * points; // points where the trace entered each new area + aasTrace_s( void ) { areas = NULL; points = NULL; getOutOfSolid = false; flags = travelFlags = maxAreas = 0; } +} aasTrace_t; + +// settings +class idAASSettings { +public: + // collision settings + int numBoundingBoxes; + idBounds boundingBoxes[MAX_AAS_BOUNDING_BOXES]; + bool usePatches; + bool writeBrushMap; + bool playerFlood; + bool noOptimize; + bool allowSwimReachabilities; + bool allowFlyReachabilities; +// RAVEN BEGIN +// bkreimeier + bool generateAllFaces; +// cdr: AASTactical + bool generateTacticalFeatures; +// scork: AASOnly numbers + int iAASOnly; // 0, else 32,48,96,250 or -1 for all +// RAVEN END + idStr fileExtension; + // physics settings + idVec3 gravity; + idVec3 gravityDir; + idVec3 invGravityDir; + float gravityValue; + float maxStepHeight; + float maxBarrierHeight; + float maxWaterJumpHeight; + float maxFallHeight; + float minFloorCos; + // fixed travel times + int tt_barrierJump; + int tt_startCrouching; + int tt_waterJump; + int tt_startWalkOffLedge; + +// RAVEN BEGIN +// rjohnson: added more debug drawing + idVec4 debugColor; + bool debugDraw; +// RAVEN END + +public: + idAASSettings( void ); + + bool FromFile( const idStr &fileName ); +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + bool FromParser( Lexer &src ); +// RAVEN END + bool FromDict( const char *name, const idDict *dict ); + bool WriteToFile( idFile *fp ) const; + bool ValidForBounds( const idBounds &bounds ) const; + bool ValidEntity( const char *classname, bool* needFlyReachabilities=NULL ) const; + +// RAVEN BEGIN + float Radius( float scale=1.0f ) const; +// RAVEN END + + +private: +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + bool ParseBool( Lexer &src, bool &b ); + bool ParseInt( Lexer &src, int &i ); + bool ParseFloat( Lexer &src, float &f ); + bool ParseVector( Lexer &src, idVec3 &vec ); + bool ParseBBoxes( Lexer &src ); +// RAVEN END + +#if defined( Q4_RECON_SEED ) + // Doom 3 seed overloads retained until the parser bodies are ported to + // Raven's Lexer abstraction. +public: + bool ParseBool( idLexer &src, bool &b ); + bool ParseInt( idLexer &src, int &i ); + bool ParseFloat( idLexer &src, float &f ); + bool ParseVector( idLexer &src, idVec3 &vec ); + bool ParseBBoxes( idLexer &src ); + bool FromParser( idLexer &src ); + bool ValidEntity( const char *classname ) const; +#endif +}; + + +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + the cluster number set to the negative portal number +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +- portal zero is a dummy +- cluster zero is a dummy + +*/ +typedef struct sizeEstimate_s { + int numEdgeIndexes; + int numFaceIndexes; + int numAreas; + int numNodes; +} sizeEstimate_t; + + +class idAASFile { +public: + virtual ~idAASFile( void ) {} +// RAVEN BEGIN +// jscott: made pure virtual + virtual class idAASFile * CreateNew( void ) = 0; + virtual class idAASSettings * CreateAASSettings( void ) = 0; + virtual class idReachability * CreateReachability( int type ) = 0; +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + virtual bool FromParser( class idAASSettings *edit, Lexer &src ) = 0; +// RAVEN END + + virtual const char * GetName( void ) const = 0; + virtual unsigned int GetCRC( void ) const = 0; + virtual void SetSizes( sizeEstimate_t size ) = 0; + + virtual int GetNumPlanes( void ) const = 0; + virtual idPlane & GetPlane( int index ) = 0; + virtual int FindPlane( const idPlane &plane, const float normalEps, const float distEps ) = 0; + + virtual int GetNumVertices( void ) const = 0; + virtual aasVertex_t & GetVertex( int index ) = 0; + virtual int AppendVertex( aasVertex_t &vert ) = 0; + + virtual int GetNumEdges( void ) const = 0; + virtual aasEdge_t & GetEdge( int index ) = 0; + virtual int AppendEdge( aasEdge_t &edge ) = 0; + + virtual int GetNumEdgeIndexes( void ) const = 0; + virtual aasIndex_t & GetEdgeIndex( int index ) = 0; + virtual int AppendEdgeIndex( aasIndex_t &edgeIdx ) = 0; + + virtual int GetNumFaces( void ) const = 0; + virtual aasFace_t & GetFace( int index ) = 0; + virtual int AppendFace( aasFace_t &face ) = 0; + + virtual int GetNumFaceIndexes( void ) const = 0; + virtual aasIndex_t & GetFaceIndex( int index ) = 0; + virtual int AppendFaceIndex( aasIndex_t &faceIdx ) = 0; + + virtual int GetNumAreas( void ) const = 0; + virtual aasArea_t & GetArea( int index ) = 0; + virtual int AppendArea( aasArea_t &area ) = 0; + + virtual int GetNumNodes( void ) const = 0; + virtual aasNode_t & GetNode( int index ) = 0; + virtual int AppendNode( aasNode_t &node ) = 0; + virtual void SetNumNodes( int num ) = 0; + + virtual int GetNumPortals( void ) const = 0; + virtual aasPortal_t & GetPortal( int index ) = 0; + virtual int AppendPortal( aasPortal_t &portal ) = 0; + + virtual int GetNumPortalIndexes( void ) const = 0; + virtual aasIndex_t & GetPortalIndex( int index ) = 0; + virtual int AppendPortalIndex( aasIndex_t &portalIdx, int clusterNum ) = 0; + + virtual int GetNumClusters( void ) const = 0; + virtual aasCluster_t & GetCluster( int index ) = 0; + virtual int AppendCluster( aasCluster_t &cluster ) = 0; + + // RAVEN BEGIN + // cdr: AASTactical + virtual void ClearTactical( void ) = 0; + + virtual int GetNumFeatureIndexes( void ) const = 0; + virtual aasIndex_t & GetFeatureIndex( int index ) = 0; + virtual int AppendFeatureIndex( aasIndex_t &featureIdx ) = 0; + + virtual int GetNumFeatures( void ) const = 0; + virtual aasFeature_t & GetFeature( int index ) = 0; + virtual int AppendFeature( aasFeature_t &cluster ) = 0; + // RAVEN END + + virtual idAASSettings & GetSettings( void ) = 0; + virtual void SetSettings( const idAASSettings &in ) = 0; + + virtual void SetPortalMaxTravelTime( int index, int time ) = 0; + virtual void SetAreaTravelFlag( int index, int flag ) = 0; + virtual void RemoveAreaTravelFlag( int index, int flag ) = 0; +// RAVEN END + + virtual idVec3 EdgeCenter( int edgeNum ) const = 0; + virtual idVec3 FaceCenter( int faceNum ) const = 0; + virtual idVec3 AreaCenter( int areaNum ) const = 0; + + virtual idBounds EdgeBounds( int edgeNum ) const = 0; + virtual idBounds FaceBounds( int faceNum ) const = 0; + virtual idBounds AreaBounds( int areaNum ) const = 0; + + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags, const int excludeTravelFlags ) const = 0; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const = 0; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &point ) const = 0; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + virtual void PrintInfo( void ) const = 0; +// RAVEN BEGIN +// jscott: added + virtual size_t GetMemorySize( void ) = 0; + + virtual void Init( void ) = 0; + virtual bool Load( const idStr &fileName, unsigned int mapFileCRC ) = 0; + virtual bool Write( const idStr &fileName, unsigned int mapFileCRC ) = 0; + virtual void Clear( void ) = 0; + virtual void FinishAreas( void ) = 0; + virtual void ReportRoutingEfficiency( void ) const = 0; + virtual void LinkReversedReachability( void ) = 0; + virtual void DeleteReachabilities( void ) = 0; + virtual void DeleteClusters( void ) = 0; + virtual void Optimize( void ) = 0; + virtual bool IsDummyFile( unsigned int mapFileCRC ) = 0; +// RAVEN END + + virtual const idDict & GetReachabilitySpecialDict( idReachability *reach ) const = 0; + virtual void SetReachabilitySpecialDictKeyValue( idReachability *reach, const char *key, const char *value ) = 0; +}; + +// RAVEN BEGIN +extern idAASFile *AASFile; +// RAVEN END + +#endif /* !__AASFILE_H__ */ diff --git a/src/aas/AASFileManager.h b/src/aas/AASFileManager.h new file mode 100644 index 0000000..2643522 --- /dev/null +++ b/src/aas/AASFileManager.h @@ -0,0 +1,46 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AASFILEMANAGER_H__ +#define __AASFILEMANAGER_H__ + +/* +=============================================================================== + + AAS File Manager + +=============================================================================== +*/ + +class idAASFileManager { +public: + virtual ~idAASFileManager( void ) {} + + virtual idAASFile * LoadAAS( const char *fileName, unsigned int mapFileCRC ) = 0; + virtual void FreeAAS( idAASFile *file ) = 0; +}; + +extern idAASFileManager * AASFileManager; + +#endif /* !__AASFILEMANAGER_H__ */ diff --git a/src/aas/AASFile_legacy.inc b/src/aas/AASFile_legacy.inc new file mode 100644 index 0000000..36ea070 --- /dev/null +++ b/src/aas/AASFile_legacy.inc @@ -0,0 +1,1316 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Doom 3 baseline retained for the Q4_AAS_LEGACY_SEED build. + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "AASFile.h" +#include "AASFile_local.h" + + +/* +=============================================================================== + + idReachability + +=============================================================================== +*/ + +/* +================ +Reachability_Write +================ +*/ +bool Reachability_Write( idFile *fp, idReachability *reach ) { + fp->WriteFloatString( "\t\t%d %d (%f %f %f) (%f %f %f) %d %d", + (int) reach->travelType, (int) reach->toAreaNum, reach->start.x, reach->start.y, reach->start.z, + reach->end.x, reach->end.y, reach->end.z, reach->edgeNum, (int) reach->travelTime ); + return true; +} + +/* +================ +Reachability_Read +================ +*/ +bool Reachability_Read( idLexer &src, idReachability *reach ) { + reach->travelType = src.ParseInt(); + reach->toAreaNum = src.ParseInt(); + src.Parse1DMatrix( 3, reach->start.ToFloatPtr() ); + src.Parse1DMatrix( 3, reach->end.ToFloatPtr() ); + reach->edgeNum = src.ParseInt(); + reach->travelTime = src.ParseInt(); + return true; +} + +/* +================ +idReachability::CopyBase +================ +*/ +void idReachability::CopyBase( idReachability &reach ) { + travelType = reach.travelType; + toAreaNum = reach.toAreaNum; + start = reach.start; + end = reach.end; + edgeNum = reach.edgeNum; + travelTime = reach.travelTime; +} + + +/* +=============================================================================== + + idReachability_Special + +=============================================================================== +*/ + +/* +================ +Reachability_Special_Write +================ +*/ +bool Reachability_Special_Write( idFile *fp, idReachability_Special *reach ) { + int i; + const idKeyValue *keyValue; + + fp->WriteFloatString( "\n\t\t{\n" ); + for ( i = 0; i < reach->dict.GetNumKeyVals(); i++ ) { + keyValue = reach->dict.GetKeyVal( i ); + fp->WriteFloatString( "\t\t\t\"%s\" \"%s\"\n", keyValue->GetKey().c_str(), keyValue->GetValue().c_str() ); + } + fp->WriteFloatString( "\t\t}\n" ); + + return true; +} + +/* +================ +Reachability_Special_Read +================ +*/ +bool Reachability_Special_Read( idLexer &src, idReachability_Special *reach ) { + idToken key, value; + + src.ExpectTokenString( "{" ); + while( src.ReadToken( &key ) ) { + if ( key == "}" ) { + return true; + } + src.ExpectTokenType( TT_STRING, 0, &value ); + reach->dict.Set( key, value ); + } + return false; +} + +/* +=============================================================================== + + idAASSettings + +=============================================================================== +*/ + +/* +============ +idAASSettings::idAASSettings +============ +*/ +idAASSettings::idAASSettings( void ) { + numBoundingBoxes = 1; + boundingBoxes[0] = idBounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + usePatches = false; + writeBrushMap = false; + playerFlood = false; + noOptimize = false; + allowSwimReachabilities = false; + allowFlyReachabilities = false; + fileExtension = "aas48"; + // physics settings + gravity = idVec3( 0, 0, -1066 ); + gravityDir = gravity; + gravityValue = gravityDir.Normalize(); + invGravityDir = -gravityDir; + maxStepHeight = 14.0f; + maxBarrierHeight = 32.0f; + maxWaterJumpHeight = 20.0f; + maxFallHeight = 64.0f; + minFloorCos = 0.7f; + // fixed travel times + tt_barrierJump = 100; + tt_startCrouching = 100; + tt_waterJump = 100; + tt_startWalkOffLedge = 100; +} + +/* +============ +idAASSettings::ParseBool +============ +*/ +bool idAASSettings::ParseBool( idLexer &src, bool &b ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + b = src.ParseBool(); + return true; +} + +/* +============ +idAASSettings::ParseInt +============ +*/ +bool idAASSettings::ParseInt( idLexer &src, int &i ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + i = src.ParseInt(); + return true; +} + +/* +============ +idAASSettings::ParseFloat +============ +*/ +bool idAASSettings::ParseFloat( idLexer &src, float &f ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + f = src.ParseFloat(); + return true; +} + +/* +============ +idAASSettings::ParseVector +============ +*/ +bool idAASSettings::ParseVector( idLexer &src, idVec3 &vec ) { + if ( !src.ExpectTokenString( "=" ) ) { + return false; + } + return ( src.Parse1DMatrix( 3, vec.ToFloatPtr() ) != 0 ); +} + +/* +============ +idAASSettings::ParseBBoxes +============ +*/ +bool idAASSettings::ParseBBoxes( idLexer &src ) { + idToken token; + idBounds bounds; + + numBoundingBoxes = 0; + + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + while( src.ReadToken( &token ) ) { + if ( token == "}" ) { + return true; + } + src.UnreadToken( &token ); + src.Parse1DMatrix( 3, bounds[0].ToFloatPtr() ); + if ( !src.ExpectTokenString( "-" ) ) { + return false; + } + src.Parse1DMatrix( 3, bounds[1].ToFloatPtr() ); + + boundingBoxes[numBoundingBoxes++] = bounds; + } + return false; +} + +/* +============ +idAASSettings::FromParser +============ +*/ +bool idAASSettings::FromParser( idLexer &src ) { + idToken token; + + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + + // parse the file + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( token == "bboxes" ) { + if ( !ParseBBoxes( src ) ) { return false; } + } + else if ( token == "usePatches" ) { + if ( !ParseBool( src, usePatches ) ) { return false; } + } + else if ( token == "writeBrushMap" ) { + if ( !ParseBool( src, writeBrushMap ) ) { return false; } + } + else if ( token == "playerFlood" ) { + if ( !ParseBool( src, playerFlood ) ) { return false; } + } + else if ( token == "allowSwimReachabilities" ) { + if ( !ParseBool( src, allowSwimReachabilities ) ) { return false; } + } + else if ( token == "allowFlyReachabilities" ) { + if ( !ParseBool( src, allowFlyReachabilities ) ) { return false; } + } + else if ( token == "fileExtension" ) { + src.ExpectTokenString( "=" ); + src.ExpectTokenType( TT_STRING, 0, &token ); + fileExtension = token; + } + else if ( token == "gravity" ) { + ParseVector( src, gravity ); + gravityDir = gravity; + gravityValue = gravityDir.Normalize(); + invGravityDir = -gravityDir; + } + else if ( token == "maxStepHeight" ) { + if ( !ParseFloat( src, maxStepHeight ) ) { return false; } + } + else if ( token == "maxBarrierHeight" ) { + if ( !ParseFloat( src, maxBarrierHeight ) ) { return false; } + } + else if ( token == "maxWaterJumpHeight" ) { + if ( !ParseFloat( src, maxWaterJumpHeight ) ) { return false; } + } + else if ( token == "maxFallHeight" ) { + if ( !ParseFloat( src, maxFallHeight ) ) { return false; } + } + else if ( token == "minFloorCos" ) { + if ( !ParseFloat( src, minFloorCos ) ) { return false; } + } + else if ( token == "tt_barrierJump" ) { + if ( !ParseInt( src, tt_barrierJump ) ) { return false; } + } + else if ( token == "tt_startCrouching" ) { + if ( !ParseInt( src, tt_startCrouching ) ) { return false; } + } + else if ( token == "tt_waterJump" ) { + if ( !ParseInt( src, tt_waterJump ) ) { return false; } + } + else if ( token == "tt_startWalkOffLedge" ) { + if ( !ParseInt( src, tt_startWalkOffLedge ) ) { return false; } + } + else { + src.Error( "invalid token '%s'", token.c_str() ); + } + } + + if ( numBoundingBoxes <= 0 ) { + src.Error( "no valid bounding box" ); + } + + return true; +} + +/* +============ +idAASSettings::FromFile +============ +*/ +bool idAASSettings::FromFile( const idStr &fileName ) { + idLexer src( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idStr name; + + name = fileName; + + common->Printf( "loading %s\n", name.c_str() ); + + if ( !src.LoadFile( name ) ) { + common->Error( "WARNING: couldn't load %s\n", name.c_str() ); + return false; + } + + if ( !src.ExpectTokenString( "settings" ) ) { + common->Error( "%s is not a settings file", name.c_str() ); + return false; + } + + if ( !FromParser( src ) ) { + common->Error( "failed to parse %s", name.c_str() ); + return false; + } + + return true; +} + +/* +============ +idAASSettings::FromDict +============ +*/ +bool idAASSettings::FromDict( const char *name, const idDict *dict ) { + idBounds bounds; + + if ( !dict->GetVector( "mins", "0 0 0", bounds[ 0 ] ) ) { + common->Error( "Missing 'mins' in entityDef '%s'", name ); + } + if ( !dict->GetVector( "maxs", "0 0 0", bounds[ 1 ] ) ) { + common->Error( "Missing 'maxs' in entityDef '%s'", name ); + } + + numBoundingBoxes = 1; + boundingBoxes[0] = bounds; + + if ( !dict->GetBool( "usePatches", "0", usePatches ) ) { + common->Error( "Missing 'usePatches' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "writeBrushMap", "0", writeBrushMap ) ) { + common->Error( "Missing 'writeBrushMap' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "playerFlood", "0", playerFlood ) ) { + common->Error( "Missing 'playerFlood' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "allowSwimReachabilities", "0", allowSwimReachabilities ) ) { + common->Error( "Missing 'allowSwimReachabilities' in entityDef '%s'", name ); + } + + if ( !dict->GetBool( "allowFlyReachabilities", "0", allowFlyReachabilities ) ) { + common->Error( "Missing 'allowFlyReachabilities' in entityDef '%s'", name ); + } + + if ( !dict->GetString( "fileExtension", "", fileExtension ) ) { + common->Error( "Missing 'fileExtension' in entityDef '%s'", name ); + } + + if ( !dict->GetVector( "gravity", "0 0 -1066", gravity ) ) { + common->Error( "Missing 'gravity' in entityDef '%s'", name ); + } + gravityDir = gravity; + gravityValue = gravityDir.Normalize(); + invGravityDir = -gravityDir; + + if ( !dict->GetFloat( "maxStepHeight", "0", maxStepHeight ) ) { + common->Error( "Missing 'maxStepHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "maxBarrierHeight", "0", maxBarrierHeight ) ) { + common->Error( "Missing 'maxBarrierHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "maxWaterJumpHeight", "0", maxWaterJumpHeight ) ) { + common->Error( "Missing 'maxWaterJumpHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "maxFallHeight", "0", maxFallHeight ) ) { + common->Error( "Missing 'maxFallHeight' in entityDef '%s'", name ); + } + + if ( !dict->GetFloat( "minFloorCos", "0", minFloorCos ) ) { + common->Error( "Missing 'minFloorCos' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_barrierJump", "0", tt_barrierJump ) ) { + common->Error( "Missing 'tt_barrierJump' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_startCrouching", "0", tt_startCrouching ) ) { + common->Error( "Missing 'tt_startCrouching' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_waterJump", "0", tt_waterJump ) ) { + common->Error( "Missing 'tt_waterJump' in entityDef '%s'", name ); + } + + if ( !dict->GetInt( "tt_startWalkOffLedge", "0", tt_startWalkOffLedge ) ) { + common->Error( "Missing 'tt_startWalkOffLedge' in entityDef '%s'", name ); + } + + return true; +} + + +/* +============ +idAASSettings::WriteToFile +============ +*/ +bool idAASSettings::WriteToFile( idFile *fp ) const { + int i; + + fp->WriteFloatString( "{\n" ); + fp->WriteFloatString( "\tbboxes\n\t{\n" ); + for ( i = 0; i < numBoundingBoxes; i++ ) { + fp->WriteFloatString( "\t\t(%f %f %f)-(%f %f %f)\n", boundingBoxes[i][0].x, boundingBoxes[i][0].y, + boundingBoxes[i][0].z, boundingBoxes[i][1].x, boundingBoxes[i][1].y, boundingBoxes[i][1].z ); + } + fp->WriteFloatString( "\t}\n" ); + fp->WriteFloatString( "\tusePatches = %d\n", usePatches ); + fp->WriteFloatString( "\twriteBrushMap = %d\n", writeBrushMap ); + fp->WriteFloatString( "\tplayerFlood = %d\n", playerFlood ); + fp->WriteFloatString( "\tallowSwimReachabilities = %d\n", allowSwimReachabilities ); + fp->WriteFloatString( "\tallowFlyReachabilities = %d\n", allowFlyReachabilities ); + fp->WriteFloatString( "\tfileExtension = \"%s\"\n", fileExtension.c_str() ); + fp->WriteFloatString( "\tgravity = (%f %f %f)\n", gravity.x, gravity.y, gravity.z ); + fp->WriteFloatString( "\tmaxStepHeight = %f\n", maxStepHeight ); + fp->WriteFloatString( "\tmaxBarrierHeight = %f\n", maxBarrierHeight ); + fp->WriteFloatString( "\tmaxWaterJumpHeight = %f\n", maxWaterJumpHeight ); + fp->WriteFloatString( "\tmaxFallHeight = %f\n", maxFallHeight ); + fp->WriteFloatString( "\tminFloorCos = %f\n", minFloorCos ); + fp->WriteFloatString( "\ttt_barrierJump = %d\n", tt_barrierJump ); + fp->WriteFloatString( "\ttt_startCrouching = %d\n", tt_startCrouching ); + fp->WriteFloatString( "\ttt_waterJump = %d\n", tt_waterJump ); + fp->WriteFloatString( "\ttt_startWalkOffLedge = %d\n", tt_startWalkOffLedge ); + fp->WriteFloatString( "}\n" ); + return true; +} + +/* +============ +idAASSettings::ValidForBounds +============ +*/ +bool idAASSettings::ValidForBounds( const idBounds &bounds ) const { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[0][i] < boundingBoxes[0][0][i] ) { + return false; + } + if ( bounds[1][i] > boundingBoxes[0][1][i] ) { + return false; + } + } + return true; +} + +/* +============ +idAASSettings::ValidEntity +============ +*/ +bool idAASSettings::ValidEntity( const char *classname ) const { + idStr use_aas; + idVec3 size; + idBounds bounds; + + if ( playerFlood ) { + if ( !strcmp( classname, "info_player_start" ) || !strcmp( classname , "info_player_deathmatch" ) || !strcmp( classname, "func_teleporter" ) ) { + return true; + } + } + + const idDeclEntityDef *decl = static_cast( declManager->FindType( DECL_ENTITYDEF, classname, false ) ); + if ( decl && decl->dict.GetString( "use_aas", NULL, use_aas ) && !fileExtension.Icmp( use_aas ) ) { + if ( decl->dict.GetVector( "mins", NULL, bounds[0] ) ) { + decl->dict.GetVector( "maxs", NULL, bounds[1] ); + } else if ( decl->dict.GetVector( "size", NULL, size ) ) { + bounds[ 0 ].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[ 1 ].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + } + + if ( !ValidForBounds( bounds ) ) { + common->Error( "%s cannot use %s\n", classname, fileExtension.c_str() ); + } + + return true; + } + + return false; +} + + +/* +=============================================================================== + + idAASFileLocal + +=============================================================================== +*/ + +#define AAS_LIST_GRANULARITY 1024 +#define AAS_INDEX_GRANULARITY 4096 +#define AAS_PLANE_GRANULARITY 4096 +#define AAS_VERTEX_GRANULARITY 4096 +#define AAS_EDGE_GRANULARITY 4096 + +/* +================ +idAASFileLocal::idAASFileLocal +================ +*/ +idAASFileLocal::idAASFileLocal( void ) { + planeList.SetGranularity( AAS_PLANE_GRANULARITY ); + vertices.SetGranularity( AAS_VERTEX_GRANULARITY ); + edges.SetGranularity( AAS_EDGE_GRANULARITY ); + edgeIndex.SetGranularity( AAS_INDEX_GRANULARITY ); + faces.SetGranularity( AAS_LIST_GRANULARITY ); + faceIndex.SetGranularity( AAS_INDEX_GRANULARITY ); + areas.SetGranularity( AAS_LIST_GRANULARITY ); + nodes.SetGranularity( AAS_LIST_GRANULARITY ); + portals.SetGranularity( AAS_LIST_GRANULARITY ); + portalIndex.SetGranularity( AAS_INDEX_GRANULARITY ); + clusters.SetGranularity( AAS_LIST_GRANULARITY ); + featureIndex.SetGranularity( AAS_INDEX_GRANULARITY ); + features.SetGranularity( AAS_LIST_GRANULARITY ); + memset( isDummy, 0, sizeof( isDummy ) ); +} + +/* +================ +idAASFileLocal::~idAASFileLocal +================ +*/ +idAASFileLocal::~idAASFileLocal( void ) { + int i; + idReachability *reach, *next; + + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = next ) { + next = reach->next; + delete reach; + } + } +} + +/* +================ +idAASFileLocal::Clear +================ +*/ +void idAASFileLocal::Clear( void ) { + planeList.Clear(); + vertices.Clear(); + edges.Clear(); + edgeIndex.Clear(); + faces.Clear(); + faceIndex.Clear(); + areas.Clear(); + nodes.Clear(); + portals.Clear(); + portalIndex.Clear(); + clusters.Clear(); + ClearTactical(); +} + +/* +================ +idAASFileLocal::Write +================ +*/ +bool idAASFileLocal::Write( const idStr &fileName, unsigned int mapFileCRC ) { + int i, num; + idFile *aasFile; + idReachability *reach; + + common->Printf( "[Write AAS]\n" ); + common->Printf( "writing %s\n", fileName.c_str() ); + + name = fileName; + crc = mapFileCRC; + + aasFile = fileSystem->OpenFileWrite( fileName, "fs_devpath" ); + if ( !aasFile ) { + common->Error( "Error opening %s", fileName.c_str() ); + return false; + } + + aasFile->WriteFloatString( "%s \"%s\"\n\n", AAS_FILEID, AAS_FILEVERSION ); + aasFile->WriteFloatString( "%u\n\n", mapFileCRC ); + + // write out the settings + aasFile->WriteFloatString( "settings\n" ); + settings.WriteToFile( aasFile ); + + // write out planes + aasFile->WriteFloatString( "planes %d {\n", planeList.Num() ); + for ( i = 0; i < planeList.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %f %f %f %f )\n", i, + planeList[i].Normal().x, planeList[i].Normal().y, planeList[i].Normal().z, planeList[i].Dist() ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out vertices + aasFile->WriteFloatString( "vertices %d {\n", vertices.Num() ); + for ( i = 0; i < vertices.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %f %f %f )\n", i, vertices[i].x, vertices[i].y, vertices[i].z ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out edges + aasFile->WriteFloatString( "edges %d {\n", edges.Num() ); + for ( i = 0; i < edges.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d )\n", i, edges[i].vertexNum[0], edges[i].vertexNum[1] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out edgeIndex + aasFile->WriteFloatString( "edgeIndex %d {\n", edgeIndex.Num() ); + for ( i = 0; i < edgeIndex.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d )\n", i, edgeIndex[i] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out faces + aasFile->WriteFloatString( "faces %d {\n", faces.Num() ); + for ( i = 0; i < faces.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d %d )\n", i, faces[i].planeNum, faces[i].flags, + faces[i].areas[0], faces[i].areas[1], faces[i].firstEdge, faces[i].numEdges ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out faceIndex + aasFile->WriteFloatString( "faceIndex %d {\n", faceIndex.Num() ); + for ( i = 0; i < faceIndex.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d )\n", i, faceIndex[i] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out areas + aasFile->WriteFloatString( "areas %d {\n", areas.Num() ); + for ( i = 0; i < areas.Num(); i++ ) { + for ( num = 0, reach = areas[i].reach; reach; reach = reach->next ) { + num++; + } + aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d %d ) %d {\n", i, areas[i].flags, areas[i].contents, + areas[i].firstFace, areas[i].numFaces, areas[i].cluster, areas[i].clusterAreaNum, num ); + for ( reach = areas[i].reach; reach; reach = reach->next ) { + Reachability_Write( aasFile, reach ); + switch( reach->travelType ) { + case TFL_SPECIAL: + Reachability_Special_Write( aasFile, static_cast(reach) ); + break; + } + aasFile->WriteFloatString( "\n" ); + } + aasFile->WriteFloatString( "\t}\n" ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out nodes + aasFile->WriteFloatString( "nodes %d {\n", nodes.Num() ); + for ( i = 0; i < nodes.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d )\n", i, nodes[i].planeNum, nodes[i].children[0], nodes[i].children[1] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out portals + aasFile->WriteFloatString( "portals %d {\n", portals.Num() ); + for ( i = 0; i < portals.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d %d %d )\n", i, portals[i].areaNum, portals[i].clusters[0], + portals[i].clusters[1], portals[i].clusterAreaNum[0], portals[i].clusterAreaNum[1] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out portalIndex + aasFile->WriteFloatString( "portalIndex %d {\n", portalIndex.Num() ); + for ( i = 0; i < portalIndex.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d )\n", i, portalIndex[i] ); + } + aasFile->WriteFloatString( "}\n" ); + + // write out clusters + aasFile->WriteFloatString( "clusters %d {\n", clusters.Num() ); + for ( i = 0; i < clusters.Num(); i++ ) { + aasFile->WriteFloatString( "\t%d ( %d %d %d %d )\n", i, clusters[i].numAreas, clusters[i].numReachableAreas, + clusters[i].firstPortal, clusters[i].numPortals ); + } + aasFile->WriteFloatString( "}\n" ); + + // close file + fileSystem->CloseFile( aasFile ); + + common->Printf( "done.\n" ); + + return true; +} + +/* +================ +idAASFileLocal::ParseIndex +================ +*/ +bool idAASFileLocal::ParseIndex( idLexer &src, idList &indexes ) { + int numIndexes, i; + aasIndex_t index; + + numIndexes = src.ParseInt(); + indexes.Resize( numIndexes ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numIndexes; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + index = src.ParseInt(); + src.ExpectTokenString( ")" ); + indexes.Append( index ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParsePlanes +================ +*/ +bool idAASFileLocal::ParsePlanes( idLexer &src ) { + int numPlanes, i; + idPlane plane; + idVec4 vec; + + numPlanes = src.ParseInt(); + planeList.Resize( numPlanes ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numPlanes; i++ ) { + src.ParseInt(); + if ( !src.Parse1DMatrix( 4, vec.ToFloatPtr() ) ) { + return false; + } + plane.SetNormal( vec.ToVec3() ); + plane.SetDist( vec[3] ); + planeList.Append( plane ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseVertices +================ +*/ +bool idAASFileLocal::ParseVertices( idLexer &src ) { + int numVertices, i; + idVec3 vec; + + numVertices = src.ParseInt(); + vertices.Resize( numVertices ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numVertices; i++ ) { + src.ParseInt(); + if ( !src.Parse1DMatrix( 3, vec.ToFloatPtr() ) ) { + return false; + } + vertices.Append( vec ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseEdges +================ +*/ +bool idAASFileLocal::ParseEdges( idLexer &src ) { + int numEdges, i; + aasEdge_t edge; + + numEdges = src.ParseInt(); + edges.Resize( numEdges ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numEdges; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + edge.vertexNum[0] = src.ParseInt(); + edge.vertexNum[1] = src.ParseInt(); + src.ExpectTokenString( ")" ); + edges.Append( edge ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseFaces +================ +*/ +bool idAASFileLocal::ParseFaces( idLexer &src ) { + int numFaces, i; + aasFace_t face; + + numFaces = src.ParseInt(); + faces.Resize( numFaces ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numFaces; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + face.planeNum = src.ParseInt(); + face.flags = src.ParseInt(); + face.areas[0] = src.ParseInt(); + face.areas[1] = src.ParseInt(); + face.firstEdge = src.ParseInt(); + face.numEdges = src.ParseInt(); + src.ExpectTokenString( ")" ); + faces.Append( face ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseReachabilities +================ +*/ +bool idAASFileLocal::ParseReachabilities( idLexer &src, int areaNum ) { + int num, j; + aasArea_t *area; + idReachability reach, *newReach; + idReachability_Special *special; + + area = &areas[areaNum]; + + num = src.ParseInt(); + src.ExpectTokenString( "{" ); + area->reach = NULL; + area->rev_reach = NULL; + area->travelFlags = AreaContentsTravelFlags( areaNum ); + for ( j = 0; j < num; j++ ) { + Reachability_Read( src, &reach ); + switch( reach.travelType ) { + case TFL_SPECIAL: + newReach = special = new idReachability_Special(); + Reachability_Special_Read( src, special ); + break; + default: + newReach = new idReachability(); + break; + } + newReach->CopyBase( reach ); + newReach->fromAreaNum = areaNum; + newReach->next = area->reach; + area->reach = newReach; + } + src.ExpectTokenString( "}" ); + return true; +} + +/* +================ +idAASFileLocal::LinkReversedReachability +================ +*/ +void idAASFileLocal::LinkReversedReachability( void ) { + int i; + idReachability *reach; + + // link reversed reachabilities + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = reach->next ) { + reach->rev_next = areas[reach->toAreaNum].rev_reach; + areas[reach->toAreaNum].rev_reach = reach; + } + } +} + +/* +================ +idAASFileLocal::ParseAreas +================ +*/ +bool idAASFileLocal::ParseAreas( idLexer &src ) { + int numAreas, i; + aasArea_t area; + + numAreas = src.ParseInt(); + areas.Resize( numAreas ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numAreas; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + area.flags = src.ParseInt(); + area.contents = src.ParseInt(); + area.firstFace = src.ParseInt(); + area.numFaces = src.ParseInt(); + area.cluster = src.ParseInt(); + area.clusterAreaNum = src.ParseInt(); + src.ExpectTokenString( ")" ); + areas.Append( area ); + ParseReachabilities( src, i ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + + LinkReversedReachability(); + + return true; +} + +/* +================ +idAASFileLocal::ParseNodes +================ +*/ +bool idAASFileLocal::ParseNodes( idLexer &src ) { + int numNodes, i; + aasNode_t node; + + numNodes = src.ParseInt(); + nodes.Resize( numNodes ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numNodes; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + node.planeNum = src.ParseInt(); + node.children[0] = src.ParseInt(); + node.children[1] = src.ParseInt(); + src.ExpectTokenString( ")" ); + nodes.Append( node ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParsePortals +================ +*/ +bool idAASFileLocal::ParsePortals( idLexer &src ) { + int numPortals, i; + aasPortal_t portal; + + numPortals = src.ParseInt(); + portals.Resize( numPortals ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numPortals; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + portal.areaNum = src.ParseInt(); + portal.clusters[0] = src.ParseInt(); + portal.clusters[1] = src.ParseInt(); + portal.clusterAreaNum[0] = src.ParseInt(); + portal.clusterAreaNum[1] = src.ParseInt(); + src.ExpectTokenString( ")" ); + portals.Append( portal ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::ParseClusters +================ +*/ +bool idAASFileLocal::ParseClusters( idLexer &src ) { + int numClusters, i; + aasCluster_t cluster; + + numClusters = src.ParseInt(); + clusters.Resize( numClusters ); + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + for ( i = 0; i < numClusters; i++ ) { + src.ParseInt(); + src.ExpectTokenString( "(" ); + cluster.numAreas = src.ParseInt(); + cluster.numReachableAreas = src.ParseInt(); + cluster.firstPortal = src.ParseInt(); + cluster.numPortals = src.ParseInt(); + src.ExpectTokenString( ")" ); + clusters.Append( cluster ); + } + if ( !src.ExpectTokenString( "}" ) ) { + return false; + } + return true; +} + +/* +================ +idAASFileLocal::FinishAreas +================ +*/ +void idAASFileLocal::FinishAreas( void ) { + int i; + + for ( i = 0; i < areas.Num(); i++ ) { + areas[i].center = AreaReachableGoal( i ); + areas[i].bounds = AreaBounds( i ); + } +} + +/* +================ +idAASFileLocal::Load +================ +*/ +bool idAASFileLocal::Load( const idStr &fileName, unsigned int mapFileCRC ) { + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES ); + idToken token; + int depth; + unsigned int c; + + name = fileName; + crc = mapFileCRC; + + common->Printf( "[Load AAS]\n" ); + common->Printf( "loading %s\n", name.c_str() ); + + if ( !src.LoadFile( name ) ) { + return false; + } + + if ( !src.ExpectTokenString( AAS_FILEID ) ) { + common->Warning( "Not an AAS file: '%s'", name.c_str() ); + return false; + } + + if ( !src.ReadToken( &token ) || token != AAS_FILEVERSION ) { + common->Warning( "AAS file '%s' has version %s instead of %s", name.c_str(), token.c_str(), AAS_FILEVERSION ); + return false; + } + + if ( !src.ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) { + common->Warning( "AAS file '%s' has no map file CRC", name.c_str() ); + return false; + } + + c = token.GetUnsignedLongValue(); + if ( mapFileCRC && c != mapFileCRC ) { + common->Warning( "AAS file '%s' is out of date", name.c_str() ); + return false; + } + + // clear the file in memory + Clear(); + + // parse the file + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "settings" ) { + if ( !settings.FromParser( src ) ) { return false; } + } + else if ( token == "planes" ) { + if ( !ParsePlanes( src ) ) { return false; } + } + else if ( token == "vertices" ) { + if ( !ParseVertices( src ) ) { return false; } + } + else if ( token == "edges" ) { + if ( !ParseEdges( src ) ) { return false; } + } + else if ( token == "edgeIndex" ) { + if ( !ParseIndex( src, edgeIndex ) ) { return false; } + } + else if ( token == "faces" ) { + if ( !ParseFaces( src ) ) { return false; } + } + else if ( token == "faceIndex" ) { + if ( !ParseIndex( src, faceIndex ) ) { return false; } + } + else if ( token == "areas" ) { + if ( !ParseAreas( src ) ) { return false; } + } + else if ( token == "nodes" ) { + if ( !ParseNodes( src ) ) { return false; } + } + else if ( token == "portals" ) { + if ( !ParsePortals( src ) ) { return false; } + } + else if ( token == "portalIndex" ) { + if ( !ParseIndex( src, portalIndex ) ) { return false; } + } + else if ( token == "clusters" ) { + if ( !ParseClusters( src ) ) { return false; } + } + else { + src.Error( "idAASFileLocal::Load: bad token \"%s\"", token.c_str() ); + return false; + } + } + + FinishAreas(); + + depth = MaxTreeDepth(); + if ( depth > MAX_AAS_TREE_DEPTH ) { + src.Error( "idAASFileLocal::Load: tree depth = %d", depth ); + } + + common->Printf( "done.\n" ); + + return true; +} + +/* +================ +idAASFileLocal::MemorySize +================ +*/ +int idAASFileLocal::MemorySize( void ) const { + int size; + + size = planeList.Size(); + size += vertices.Size(); + size += edges.Size(); + size += edgeIndex.Size(); + size += faces.Size(); + size += faceIndex.Size(); + size += areas.Size(); + size += nodes.Size(); + size += portals.Size(); + size += portalIndex.Size(); + size += clusters.Size(); + size += sizeof( idReachability_Walk ) * NumReachabilities(); + + return size; +} + +/* +================ +idAASFileLocal::PrintInfo +================ +*/ +void idAASFileLocal::PrintInfo( void ) const { + common->Printf( "%6d KB file size\n", MemorySize() >> 10 ); + common->Printf( "%6d areas\n", areas.Num() ); + common->Printf( "%6d max tree depth\n", MaxTreeDepth() ); + ReportRoutingEfficiency(); +} + +/* +================ +idAASFileLocal::NumReachabilities +================ +*/ +int idAASFileLocal::NumReachabilities( void ) const { + int i, num; + idReachability *reach; + + num = 0; + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = reach->next ) { + num++; + } + } + return num; +} + +/* +================ +idAASFileLocal::ReportRoutingEfficiency +================ +*/ +void idAASFileLocal::ReportRoutingEfficiency( void ) const { + int numReachableAreas, total, i, n; + + numReachableAreas = 0; + total = 0; + for ( i = 0; i < clusters.Num(); i++ ) { + n = clusters[i].numReachableAreas; + numReachableAreas += n; + total += n * n; + } + total += numReachableAreas * portals.Num(); + + common->Printf( "%6d reachable areas\n", numReachableAreas ); + common->Printf( "%6d reachabilities\n", NumReachabilities() ); + common->Printf( "%6d KB max routing cache\n", ( total * 3 ) >> 10 ); +} + +/* +================ +idAASFileLocal::DeleteReachabilities +================ +*/ +void idAASFileLocal::DeleteReachabilities( void ) { + int i; + idReachability *reach, *nextReach; + + for ( i = 0; i < areas.Num(); i++ ) { + for ( reach = areas[i].reach; reach; reach = nextReach ) { + nextReach = reach->next; + delete reach; + } + areas[i].reach = NULL; + areas[i].rev_reach = NULL; + } +} + +/* +================ +idAASFileLocal::DeleteClusters +================ +*/ +void idAASFileLocal::DeleteClusters( void ) { + aasPortal_t portal; + aasCluster_t cluster; + + portals.Clear(); + portalIndex.Clear(); + clusters.Clear(); + + // first portal is a dummy + memset( &portal, 0, sizeof( portal ) ); + portals.Append( portal ); + + // first cluster is a dummy + memset( &cluster, 0, sizeof( portal ) ); + clusters.Append( cluster ); +} diff --git a/src/aas/AASFile_local.h b/src/aas/AASFile_local.h new file mode 100644 index 0000000..972ac94 --- /dev/null +++ b/src/aas/AASFile_local.h @@ -0,0 +1,188 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AASFILELOCAL_H__ +#define __AASFILELOCAL_H__ + +/* +=============================================================================== + + AAS File Local + +=============================================================================== +*/ + +class idAASFileLocal : public idAASFile { + friend class idAASBuild; + friend class idAASReach; + friend class idAASCluster; +public: + idAASFileLocal( void ); + virtual ~idAASFileLocal( void ); + + virtual idAASFile * CreateNew( void ); + virtual idAASSettings * CreateAASSettings( void ); + virtual idReachability * CreateReachability( int type ); + virtual bool FromParser( idAASSettings *edit, Lexer &src ); + virtual const char * GetName( void ) const; + virtual unsigned int GetCRC( void ) const; + virtual void SetSizes( sizeEstimate_t size ); + + virtual int GetNumPlanes( void ) const; + virtual idPlane & GetPlane( int index ); + virtual int FindPlane( const idPlane &plane, const float normalEps, const float distEps ); + virtual int GetNumVertices( void ) const; + virtual aasVertex_t & GetVertex( int index ); + virtual int AppendVertex( aasVertex_t &vertex ); + virtual int GetNumEdges( void ) const; + virtual aasEdge_t & GetEdge( int index ); + virtual int AppendEdge( aasEdge_t &edge ); + virtual int GetNumEdgeIndexes( void ) const; + virtual aasIndex_t & GetEdgeIndex( int index ); + virtual int AppendEdgeIndex( aasIndex_t &index ); + virtual int GetNumFaces( void ) const; + virtual aasFace_t & GetFace( int index ); + virtual int AppendFace( aasFace_t &face ); + virtual int GetNumFaceIndexes( void ) const; + virtual aasIndex_t & GetFaceIndex( int index ); + virtual int AppendFaceIndex( aasIndex_t &index ); + virtual int GetNumAreas( void ) const; + virtual aasArea_t & GetArea( int index ); + virtual int AppendArea( aasArea_t &area ); + virtual int GetNumNodes( void ) const; + virtual aasNode_t & GetNode( int index ); + virtual int AppendNode( aasNode_t &node ); + virtual void SetNumNodes( int num ); + virtual int GetNumPortals( void ) const; + virtual aasPortal_t & GetPortal( int index ); + virtual int AppendPortal( aasPortal_t &portal ); + virtual int GetNumPortalIndexes( void ) const; + virtual aasIndex_t & GetPortalIndex( int index ); + virtual int AppendPortalIndex( aasIndex_t &index, int clusterNum ); + virtual int GetNumClusters( void ) const; + virtual aasCluster_t & GetCluster( int index ); + virtual int AppendCluster( aasCluster_t &cluster ); + + virtual void ClearTactical( void ); + virtual int GetNumFeatureIndexes( void ) const; + virtual aasIndex_t & GetFeatureIndex( int index ); + virtual int AppendFeatureIndex( aasIndex_t &index ); + virtual int GetNumFeatures( void ) const; + virtual aasFeature_t & GetFeature( int index ); + virtual int AppendFeature( aasFeature_t &feature ); + + virtual idAASSettings & GetSettings( void ); + virtual void SetSettings( const idAASSettings &in ); + virtual void SetPortalMaxTravelTime( int index, int time ); + virtual void SetAreaTravelFlag( int index, int flag ); + virtual void RemoveAreaTravelFlag( int index, int flag ); + + virtual idVec3 EdgeCenter( int edgeNum ) const; + virtual idVec3 FaceCenter( int faceNum ) const; + virtual idVec3 AreaCenter( int areaNum ) const; + virtual idBounds EdgeBounds( int edgeNum ) const; + virtual idBounds FaceBounds( int faceNum ) const; + virtual idBounds AreaBounds( int areaNum ) const; + virtual int PointAreaNum( const idVec3 &origin ) const; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags, const int excludeTravelFlags ) const; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &point ) const; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual void PrintInfo( void ) const; + virtual size_t GetMemorySize( void ); + + virtual void Init( void ); + virtual bool Load( const idStr &fileName, unsigned int mapFileCRC ); + virtual bool Write( const idStr &fileName, unsigned int mapFileCRC ); + virtual void Clear( void ); + virtual void FinishAreas( void ); + virtual void ReportRoutingEfficiency( void ) const; + virtual void LinkReversedReachability( void ); + virtual void DeleteReachabilities( void ); + virtual void DeleteClusters( void ); + virtual void Optimize( void ); + virtual bool IsDummyFile( unsigned int mapFileCRC ); + virtual const idDict & GetReachabilitySpecialDict( idReachability *reach ) const; + virtual void SetReachabilitySpecialDictKeyValue( idReachability *reach, const char *key, const char *value ); + + int MemorySize( void ) const; + +private: + bool ParseIndex( Lexer &src, idList &indexes, int numIndexes ); + bool ParsePlanes( Lexer &src, int numPlanes ); + bool ParseVertices( Lexer &src, int numVertices ); + bool ParseEdges( Lexer &src, int numEdges ); + bool ParseFaces( Lexer &src, int numFaces ); + bool ParseReachabilities( Lexer &src, int areaNum ); + bool ParseAreas( Lexer &src, int numAreas ); + bool ParseNodes( Lexer &src, int numNodes ); + bool ParsePortals( Lexer &src, int numPortals ); + bool ParseClusters( Lexer &src, int numClusters ); + bool ParseFeatures( Lexer &src ); + +#if defined( Q4_RECON_SEED ) + bool ParseIndex( idLexer &src, idList &indexes ); + bool ParsePlanes( idLexer &src ); + bool ParseVertices( idLexer &src ); + bool ParseEdges( idLexer &src ); + bool ParseFaces( idLexer &src ); + bool ParseReachabilities( idLexer &src, int areaNum ); + bool ParseAreas( idLexer &src ); + bool ParseNodes( idLexer &src ); + bool ParsePortals( idLexer &src ); + bool ParseClusters( idLexer &src ); +#endif + + int BoundsReachableAreaNum_r( int nodeNum, const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const; + void MaxTreeDepth_r( int nodeNum, int &depth, int &maxDepth ) const; + int MaxTreeDepth( void ) const; + int AreaContentsTravelFlags( int areaNum ) const; + idVec3 AreaReachableGoal( int areaNum ) const; + int NumReachabilities( void ) const; + + idStr name; + idStr version; + unsigned int crc; + bool isDummy[11]; + idPlaneSet planeList; + idList vertices; + idList edges; + idList edgeIndex; + idList faces; + idList faceIndex; + idList areas; + idList nodes; + idList portals; + idList portalIndex; + idList clusters; + idList featureIndex; + idList features; + idAASSettings settings; +}; + +#if defined( _WIN32 ) +static_assert( sizeof( idAASFileLocal ) == 0x230, "idAASFileLocal ABI drift" ); +#endif + +#endif /* !__AASFILELOCAL_H__ */ diff --git a/src/aas/AASReach.h b/src/aas/AASReach.h new file mode 100644 index 0000000..d868bc8 --- /dev/null +++ b/src/aas/AASReach.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AASREACH_H__ +#define __AASREACH_H__ + +/* +=============================================================================== + + Reachabilities + +=============================================================================== +*/ + +class idAASReach { + +public: + bool Build( const idMapFile *mapFile, idAASFileLocal *file ); + +private: + const idMapFile * mapFile; + idAASFileLocal * file; + int numReachabilities; + bool allowSwimReachabilities; + bool allowFlyReachabilities; + +private: // reachability + void FlagReachableAreas( idAASFileLocal *file ); + bool ReachabilityExists( int fromAreaNum, int toAreaNum ); + bool CanSwimInArea( int areaNum ); + bool AreaHasFloor( int areaNum ); + bool AreaIsClusterPortal( int areaNum ); + void AddReachabilityToArea( idReachability *reach, int areaNum ); + void Reachability_Fly( int areaNum ); + void Reachability_Swim( int areaNum ); + void Reachability_EqualFloorHeight( int areaNum ); + bool Reachability_Step_Barrier_WaterJump_WalkOffLedge( int fromAreaNum, int toAreaNum ); + void Reachability_WalkOffLedge( int areaNum ); + +}; + +#endif /* !__AASREACH_H__ */ diff --git a/src/aas/Brush.h b/src/aas/Brush.h new file mode 100644 index 0000000..993c4e1 --- /dev/null +++ b/src/aas/Brush.h @@ -0,0 +1,227 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BRUSH_H__ +#define __BRUSH_H__ + +/* +=============================================================================== + + Brushes + +=============================================================================== +*/ + + +#define BRUSH_PLANESIDE_FRONT 1 +#define BRUSH_PLANESIDE_BACK 2 +#define BRUSH_PLANESIDE_BOTH ( BRUSH_PLANESIDE_FRONT | BRUSH_PLANESIDE_BACK ) +#define BRUSH_PLANESIDE_FACING 4 + +class idBrush; +class idBrushList; + +void DisplayRealTimeString( char *string, ... ) id_attribute((format(printf,1,2))); + + +//=============================================================== +// +// idBrushSide +// +//=============================================================== + +#define SFL_SPLIT 0x0001 +#define SFL_BEVEL 0x0002 +#define SFL_USED_SPLITTER 0x0004 +#define SFL_TESTED_SPLITTER 0x0008 + +class idBrushSide { + + friend class idBrush; + +public: + idBrushSide( void ); + idBrushSide( const idPlane &plane, int planeNum ); + ~idBrushSide( void ); + + int GetFlags( void ) const { return flags; } + void SetFlag( int flag ) { flags |= flag; } + void RemoveFlag( int flag ) { flags &= ~flag; } + const idPlane & GetPlane( void ) const { return plane; } + void SetPlaneNum( int num ) { planeNum = num; } + int GetPlaneNum( void ) { return planeNum; } + const idWinding * GetWinding( void ) const { return winding; } + idBrushSide * Copy( void ) const; + int Split( const idPlane &splitPlane, idBrushSide **front, idBrushSide **back ) const; + +private: + int flags; + int planeNum; + idPlane plane; + idWinding * winding; +}; + + +//=============================================================== +// +// idBrush +// +//=============================================================== + +#define BFL_NO_VALID_SPLITTERS 0x0001 + +class idBrush { + + friend class idBrushList; + +public: + idBrush( void ); + ~idBrush( void ); + + int GetFlags( void ) const { return flags; } + void SetFlag( int flag ) { flags |= flag; } + void RemoveFlag( int flag ) { flags &= ~flag; } + void SetEntityNum( int num ) { entityNum = num; } + void SetPrimitiveNum( int num ) { primitiveNum = num; } + void SetContents( int contents ) { this->contents = contents; } + int GetContents( void ) const { return contents; } + const idBounds & GetBounds( void ) const { return bounds; } + float GetVolume( void ) const; + int GetNumSides( void ) const { return sides.Num(); } + idBrushSide * GetSide( int i ) const { return sides[i]; } + void SetPlaneSide( int s ) { planeSide = s; } + void SavePlaneSide( void ) { savedPlaneSide = planeSide; } + int GetSavedPlaneSide( void ) const { return savedPlaneSide; } + bool FromSides( idList &sideList ); + bool FromWinding( const idWinding &w, const idPlane &windingPlane ); + bool FromBounds( const idBounds &bounds ); + void Transform( const idVec3 &origin, const idMat3 &axis ); + idBrush * Copy( void ) const; + bool TryMerge( const idBrush *brush, const idPlaneSet &planeList ); + // returns true if the brushes did intersect + bool Subtract( const idBrush *b, idBrushList &list ) const; + // split the brush into a front and back brush + int Split( const idPlane &plane, int planeNum, idBrush **front, idBrush **back ) const; + // expand the brush for an axial bounding box + void ExpandForAxialBox( const idBounds &bounds ); + // next brush in list + idBrush * Next( void ) const { return next; } + +private: + mutable idBrush * next; // next brush in list + int entityNum; // entity number in editor + int primitiveNum; // primitive number in editor + int flags; // brush flags + bool windingsValid; // set when side windings are valid + int contents; // contents of brush + int planeSide; // side of a plane this brush is on + int savedPlaneSide; // saved plane side + idBounds bounds; // brush bounds + idList sides; // list with sides + +private: + bool CreateWindings( void ); + void BoundBrush( const idBrush *original = NULL ); + void AddBevelsForAxialBox( void ); + bool RemoveSidesWithoutWinding( void ); +}; + + +//=============================================================== +// +// idBrushList +// +//=============================================================== + +class idBrushList { +public: + idBrushList( void ); + ~idBrushList( void ); + + int Num( void ) const { return numBrushes; } + int NumSides( void ) const { return numBrushSides; } + idBrush * Head( void ) const { return head; } + idBrush * Tail( void ) const { return tail; } + void Clear( void ) { head = tail = NULL; numBrushes = 0; } + bool IsEmpty( void ) const { return (numBrushes == 0); } + idBounds GetBounds( void ) const; + // add brush to the tail of the list + void AddToTail( idBrush *brush ); + // add list to the tail of the list + void AddToTail( idBrushList &list ); + // add brush to the front of the list + void AddToFront( idBrush *brush ); + // add list to the front of the list + void AddToFront( idBrushList &list ); + // remove the brush from the list + void Remove( idBrush *brush ); + // remove the brush from the list and delete the brush + void Delete( idBrush *brush); + // returns a copy of the brush list + idBrushList * Copy( void ) const; + // delete all brushes in the list + void Free( void ); + // split the brushes in the list into two lists + void Split( const idPlane &plane, int planeNum, idBrushList &frontList, idBrushList &backList, bool useBrushSavedPlaneSide = false ); + // chop away all brush overlap + void Chop( bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ) ); + // merge brushes + void Merge( bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) ); + // set the given flag on all brush sides facing the plane + void SetFlagOnFacingBrushSides( const idPlane &plane, int flag ); + // get a list with planes for all brushes in the list + void CreatePlaneList( idPlaneSet &planeList ) const; + // write a brush map with the brushes in the list + void WriteBrushMap( const idStr &fileName, const idStr &ext ) const; + +private: + idBrush * head; + idBrush * tail; + int numBrushes; + int numBrushSides; +}; + + +//=============================================================== +// +// idBrushMap +// +//=============================================================== + +class idBrushMap { + +public: + idBrushMap( const idStr &fileName, const idStr &ext ); + ~idBrushMap( void ); + void SetTexture( const idStr &textureName ) { texture = textureName; } + void WriteBrush( const idBrush *brush ); + void WriteBrushList( const idBrushList &brushList ); + +private: + idFile * fp; + idStr texture; + int brushCount; +}; + +#endif /* !__BRUSH_H__ */ diff --git a/src/aas/BrushBSP.h b/src/aas/BrushBSP.h new file mode 100644 index 0000000..e61f6cd --- /dev/null +++ b/src/aas/BrushBSP.h @@ -0,0 +1,229 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BRUSHBSP_H__ +#define __BRUSHBSP_H__ + +/* +=============================================================================== + + BrushBSP + +=============================================================================== +*/ + +class idBrushBSP; +class idBrushBSPNode; +class idBrushBSPPortal; + + +//=============================================================== +// +// idBrushBSPPortal +// +//=============================================================== + +class idBrushBSPPortal { + + friend class idBrushBSP; + friend class idBrushBSPNode; + +public: + idBrushBSPPortal( void ); + ~idBrushBSPPortal( void ); + void AddToNodes( idBrushBSPNode *front, idBrushBSPNode *back ); + void RemoveFromNode( idBrushBSPNode *l ); + void Flip( void ); + int Split( const idPlane &splitPlane, idBrushBSPPortal **front, idBrushBSPPortal **back ); + idWinding * GetWinding( void ) const { return winding; } + const idPlane & GetPlane( void ) const { return plane; } + void SetFaceNum( int num ) { faceNum = num; } + int GetFaceNum( void ) const { return faceNum; } + int GetFlags( void ) const { return flags; } + void SetFlag( int flag ) { flags |= flag; } + void RemoveFlag( int flag ) { flags &= ~flag; } + idBrushBSPPortal * Next( int side ) const { return next[side]; } + idBrushBSPNode * GetNode( int side ) const { return nodes[side]; } + +private: + idPlane plane; // portal plane + int planeNum; // number of plane this portal is on + idWinding * winding; // portal winding + idBrushBSPNode * nodes[2]; // nodes this portal seperates + idBrushBSPPortal * next[2]; // next portal in list for both nodes + int flags; // portal flags + int faceNum; // number of the face created for this portal +}; + + +//=============================================================== +// +// idBrushBSPNode +// +//=============================================================== + +#define NODE_VISITED BIT(30) +#define NODE_DONE BIT(31) + +class idBrushBSPNode { + + friend class idBrushBSP; + friend class idBrushBSPPortal; + +public: + idBrushBSPNode( void ); + ~idBrushBSPNode( void ); + void SetContentsFromBrushes( void ); + idBounds GetPortalBounds( void ); + idBrushBSPNode * GetChild( int index ) const { return children[index]; } + idBrushBSPNode * GetParent( void ) const { return parent; } + void SetContents( int contents ) { this->contents = contents; } + int GetContents( void ) const { return contents; } + const idPlane & GetPlane( void ) const { return plane; } + idBrushBSPPortal * GetPortals( void ) const { return portals; } + void SetAreaNum( int num ) { areaNum = num; } + int GetAreaNum( void ) const { return areaNum; } + int GetFlags( void ) const { return flags; } + void SetFlag( int flag ) { flags |= flag; } + void RemoveFlag( int flag ) { flags &= ~flag; } + bool TestLeafNode( void ); + // remove the flag from nodes found by flooding through portals to nodes with the flag set + void RemoveFlagFlood( int flag ); + // recurse down the tree and remove the flag from all visited nodes + void RemoveFlagRecurse( int flag ); + // first recurse down the tree and flood from there + void RemoveFlagRecurseFlood( int flag ); + // returns side of the plane the node is on + int PlaneSide( const idPlane &plane, float epsilon = ON_EPSILON ) const; + // split the leaf node with a plane + bool Split( const idPlane &splitPlane, int splitPlaneNum ); + + +private: + idPlane plane; // split plane if this is not a leaf node + idBrush * volume; // node volume + int contents; // node contents + idBrushList brushList; // list with brushes for this node + idBrushBSPNode * parent; // parent of this node + idBrushBSPNode * children[2]; // both are NULL if this is a leaf node + idBrushBSPPortal * portals; // portals of this node + int flags; // node flags + int areaNum; // number of the area created for this node + int occupied; // true when portal is occupied +}; + + +//=============================================================== +// +// idBrushBSP +// +//=============================================================== + +class idBrushBSP { + +public: + idBrushBSP( void ); + ~idBrushBSP( void ); + // build a bsp tree from a set of brushes + void Build( idBrushList brushList, int skipContents, + bool (*ChopAllowed)( idBrush *b1, idBrush *b2 ), + bool (*MergeAllowed)( idBrush *b1, idBrush *b2 ) ); + // remove splits in subspaces with the given contents + void PruneTree( int contents ); + // portalize the bsp tree + void Portalize( void ); + // remove subspaces outside the map not reachable by entities + bool RemoveOutside( const idMapFile *mapFile, int contents, const idStrList &classNames ); + // write file with a trace going through a leak + void LeakFile( const idStr &fileName ); + // try to merge portals + void MergePortals( int skipContents ); + // try to merge the two leaf nodes at either side of the portal + bool TryMergeLeafNodes( idBrushBSPPortal *portal, int side ); + void PruneMergedTree_r( idBrushBSPNode *node ); + // melt portal windings + void MeltPortals( int skipContents ); + // write a map file with a brush for every leaf node that has the given contents + void WriteBrushMap( const idStr &fileName, const idStr &ext, int contents ); + // bounds for the whole tree + const idBounds & GetTreeBounds( void ) const { return treeBounds; } + // root node of the tree + idBrushBSPNode * GetRootNode( void ) const { return root; } + +private: + idBrushBSPNode * root; + idBrushBSPNode * outside; + idBounds treeBounds; + idPlaneSet portalPlanes; + int numGridCells; + int numSplits; + int numGridCellSplits; + int numPrunedSplits; + int numPortals; + int solidLeafNodes; + int outsideLeafNodes; + int insideLeafNodes; + int numMergedPortals; + int numInsertedPoints; + idVec3 leakOrigin; + int brushMapContents; + idBrushMap * brushMap; + + bool (*BrushChopAllowed)( idBrush *b1, idBrush *b2 ); + bool (*BrushMergeAllowed)( idBrush *b1, idBrush *b2 ); + +private: + void RemoveMultipleLeafNodeReferences_r( idBrushBSPNode *node ); + void Free_r( idBrushBSPNode *node ); + void IncreaseNumSplits( void ); + bool IsValidSplitter( const idBrushSide *side ); + int BrushSplitterStats( const idBrush *brush, int planeNum, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &stats ); + int FindSplitter( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, struct splitterStats_s &bestStats ); + void SetSplitterUsed( idBrushBSPNode *node, int planeNum ); + idBrushBSPNode * BuildBrushBSP_r( idBrushBSPNode *node, const idPlaneSet &planeList, bool *testedPlanes, int skipContents ); + idBrushBSPNode * ProcessGridCell( idBrushBSPNode *node, int skipContents ); + void BuildGrid_r( idList &gridCells, idBrushBSPNode *node ); + void PruneTree_r( idBrushBSPNode *node, int contents ); + void MakeOutsidePortals( void ); + idWinding * BaseWindingForNode( idBrushBSPNode *node ); + void MakeNodePortal( idBrushBSPNode *node ); + void SplitNodePortals( idBrushBSPNode *node ); + void MakeTreePortals_r( idBrushBSPNode *node ); + void FloodThroughPortals_r( idBrushBSPNode *node, int contents, int depth ); + bool FloodFromOrigin( const idVec3 &origin, int contents ); + bool FloodFromEntities( const idMapFile *mapFile, int contents, const idStrList &classNames ); + void RemoveOutside_r( idBrushBSPNode *node, int contents ); + void SetPortalPlanes_r( idBrushBSPNode *node, idPlaneSet &planeList ); + void SetPortalPlanes( void ); + void MergePortals_r( idBrushBSPNode *node, int skipContents ); + void MergeLeafNodePortals( idBrushBSPNode *node, int skipContents ); + void UpdateTreeAfterMerge_r( idBrushBSPNode *node, const idBounds &bounds, idBrushBSPNode *oldNode, idBrushBSPNode *newNode ); + void RemoveLeafNodeColinearPoints( idBrushBSPNode *node ); + void RemoveColinearPoints_r( idBrushBSPNode *node, int skipContents ); + void MeltFlood_r( idBrushBSPNode *node, int skipContents, idBounds &bounds, idVectorSet &vertexList ); + void MeltLeafNodePortals( idBrushBSPNode *node, int skipContents, idVectorSet &vertexList ); + void MeltPortals_r( idBrushBSPNode *node, int skipContents, idVectorSet &vertexList ); +}; + +#endif /* !__BRUSHBSP_H__ */ diff --git a/src/aas/aasfile_optimize.cpp b/src/aas/aasfile_optimize.cpp new file mode 100644 index 0000000..177665a --- /dev/null +++ b/src/aas/aasfile_optimize.cpp @@ -0,0 +1,150 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "AASFile.h" +#include "AASFile_local.h" + + +//=============================================================== +// +// optimize file +// +//=============================================================== + +/* +================ +idAASFileLocal::Optimize +================ +*/ +void idAASFileLocal::Optimize( void ) { + int i, j, k, faceNum, edgeNum, areaFirstFace, faceFirstEdge; + aasArea_t *area; + aasFace_t *face; + aasEdge_t *edge; + idReachability *reach; + idList vertexRemap; + idList edgeRemap; + idList faceRemap; + idList newVertices; + idList newEdges; + idList newEdgeIndex; + idList newFaces; + idList newFaceIndex; + + vertexRemap.AssureSize( vertices.Num(), -1 ); + edgeRemap.AssureSize( edges.Num(), 0 ); + faceRemap.AssureSize( faces.Num(), 0 ); + + newVertices.Resize( vertices.Num() ); + newEdges.Resize( edges.Num() ); + newEdges.SetNum( 1, false ); + newEdgeIndex.Resize( edgeIndex.Num() ); + newFaces.Resize( faces.Num() ); + newFaces.SetNum( 1, false ); + newFaceIndex.Resize( faceIndex.Num() ); + + for ( i = 0; i < areas.Num(); i++ ) { + area = &areas[i]; + + areaFirstFace = newFaceIndex.Num(); + for ( j = 0; j < area->numFaces; j++ ) { + faceNum = faceIndex[area->firstFace + j]; + face = &faces[ abs(faceNum) ]; + + // store face + if ( !faceRemap[ abs(faceNum) ] ) { + faceRemap[ abs(faceNum) ] = newFaces.Num(); + newFaces.Append( *face ); + + // don't store edges for faces we don't care about + if ( !( face->flags & ( FACE_FLOOR|FACE_LADDER ) ) ) { + + newFaces[ newFaces.Num()-1 ].firstEdge = 0; + newFaces[ newFaces.Num()-1 ].numEdges = 0; + + } else { + + // store edges + faceFirstEdge = newEdgeIndex.Num(); + for ( k = 0; k < face->numEdges; k++ ) { + edgeNum = edgeIndex[ face->firstEdge + k ]; + edge = &edges[ abs(edgeNum) ]; + + if ( !edgeRemap[ abs(edgeNum) ] ) { + if ( edgeNum < 0 ) { + edgeRemap[ abs(edgeNum) ] = -newEdges.Num(); + } + else { + edgeRemap[ abs(edgeNum) ] = newEdges.Num(); + } + + // remap vertices if not yet remapped + if ( vertexRemap[ edge->vertexNum[0] ] == -1 ) { + vertexRemap[ edge->vertexNum[0] ] = newVertices.Num(); + newVertices.Append( vertices[ edge->vertexNum[0] ] ); + } + if ( vertexRemap[ edge->vertexNum[1] ] == -1 ) { + vertexRemap[ edge->vertexNum[1] ] = newVertices.Num(); + newVertices.Append( vertices[ edge->vertexNum[1] ] ); + } + + newEdges.Append( *edge ); + newEdges[ newEdges.Num()-1 ].vertexNum[0] = vertexRemap[ edge->vertexNum[0] ]; + newEdges[ newEdges.Num()-1 ].vertexNum[1] = vertexRemap[ edge->vertexNum[1] ]; + } + + newEdgeIndex.Append( edgeRemap[ abs(edgeNum) ] ); + } + + newFaces[ newFaces.Num()-1 ].firstEdge = faceFirstEdge; + newFaces[ newFaces.Num()-1 ].numEdges = newEdgeIndex.Num() - faceFirstEdge; + } + } + + if ( faceNum < 0 ) { + newFaceIndex.Append( -faceRemap[ abs(faceNum) ] ); + } else { + newFaceIndex.Append( faceRemap[ abs(faceNum) ] ); + } + } + + area->firstFace = areaFirstFace; + area->numFaces = newFaceIndex.Num() - areaFirstFace; + + // remap the reachability edges + for ( reach = area->reach; reach; reach = reach->next ) { + reach->edgeNum = abs( edgeRemap[reach->edgeNum] ); + } + } + + // store new list + vertices = newVertices; + edges = newEdges; + edgeIndex = newEdgeIndex; + faces = newFaces; + faceIndex = newFaceIndex; +} diff --git a/src/aas/aasfile_sample.cpp b/src/aas/aasfile_sample.cpp new file mode 100644 index 0000000..d0d95e2 --- /dev/null +++ b/src/aas/aasfile_sample.cpp @@ -0,0 +1,604 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "AASFile.h" +#include "AASFile_local.h" + + +//=============================================================== +// +// Environment Sampling +// +//=============================================================== + +/* +================ +idAASFileLocal::EdgeCenter +================ +*/ +idVec3 idAASFileLocal::EdgeCenter( int edgeNum ) const { + const aasEdge_t *edge; + edge = &edges[edgeNum]; + return ( vertices[edge->vertexNum[0]] + vertices[edge->vertexNum[1]] ) * 0.5f; +} + +/* +================ +idAASFileLocal::FaceCenter +================ +*/ +idVec3 idAASFileLocal::FaceCenter( int faceNum ) const { + int i, edgeNum; + const aasFace_t *face; + const aasEdge_t *edge; + idVec3 center; + + center = vec3_origin; + + face = &faces[faceNum]; + if ( face->numEdges > 0 ) { + for ( i = 0; i < face->numEdges; i++ ) { + edgeNum = edgeIndex[ face->firstEdge + i ]; + edge = &edges[ abs( edgeNum ) ]; + center += vertices[ edge->vertexNum[ INTSIGNBITSET(edgeNum) ] ]; + } + center /= face->numEdges; + } + return center; +} + +/* +================ +idAASFileLocal::AreaCenter +================ +*/ +idVec3 idAASFileLocal::AreaCenter( int areaNum ) const { + int i, faceNum; + const aasArea_t *area; + idVec3 center; + + center = vec3_origin; + + area = &areas[areaNum]; + if ( area->numFaces > 0 ) { + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + center += FaceCenter( abs(faceNum) ); + } + center /= area->numFaces; + } + return center; +} + +/* +============ +idAASFileLocal::AreaReachableGoal +============ +*/ +idVec3 idAASFileLocal::AreaReachableGoal( int areaNum ) const { + int i, faceNum, numFaces; + const aasArea_t *area; + idVec3 center; + idVec3 start, end; + aasTrace_t trace; + + area = &areas[areaNum]; + + if ( !(area->flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) || (area->flags & AREA_LIQUID) ) { + return AreaCenter( areaNum ); + } + + center = vec3_origin; + + numFaces = 0; + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + if ( !(faces[abs(faceNum)].flags & FACE_FLOOR) ) { + continue; + } + center += FaceCenter( abs(faceNum) ); + numFaces++; + } + if ( numFaces > 0 ) { + center /= numFaces; + } + center[2] += 1.0f; + end = center; + end[2] -= 1024; + Trace( trace, center, end ); + + return trace.endpos; +} + +/* +================ +idAASFileLocal::EdgeBounds +================ +*/ +idBounds idAASFileLocal::EdgeBounds( int edgeNum ) const { + const aasEdge_t *edge; + idBounds bounds; + + edge = &edges[ abs( edgeNum ) ]; + bounds[0] = bounds[1] = vertices[ edge->vertexNum[0] ]; + bounds += vertices[ edge->vertexNum[1] ]; + return bounds; +} + +/* +================ +idAASFileLocal::FaceBounds +================ +*/ +idBounds idAASFileLocal::FaceBounds( int faceNum ) const { + int i, edgeNum; + const aasFace_t *face; + const aasEdge_t *edge; + idBounds bounds; + + face = &faces[faceNum]; + bounds.Clear(); + + for ( i = 0; i < face->numEdges; i++ ) { + edgeNum = edgeIndex[ face->firstEdge + i ]; + edge = &edges[ abs( edgeNum ) ]; + bounds.AddPoint( vertices[ edge->vertexNum[ INTSIGNBITSET(edgeNum) ] ] ); + } + return bounds; +} + +/* +================ +idAASFileLocal::AreaBounds +================ +*/ +idBounds idAASFileLocal::AreaBounds( int areaNum ) const { + int i, faceNum; + const aasArea_t *area; + idBounds bounds; + + area = &areas[areaNum]; + bounds.Clear(); + + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + bounds += FaceBounds( abs(faceNum) ); + } + return bounds; +} + +/* +============ +idAASFileLocal::PointAreaNum +============ +*/ +int idAASFileLocal::PointAreaNum( const idVec3 &origin ) const { + int nodeNum; + const aasNode_t *node; + + nodeNum = 1; + do { + node = &nodes[nodeNum]; + if ( planeList[node->planeNum].Side( origin ) == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else { + nodeNum = node->children[0]; + } + if ( nodeNum < 0 ) { + return -nodeNum; + } + } while( nodeNum ); + + return 0; +} + +/* +============ +idAASFileLocal::PointReachableAreaNum +============ +*/ +int idAASFileLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags, const int excludeTravelFlags ) const { + int areaList[32], areaNum, i; + idVec3 start, end, pointList[32]; + aasTrace_t trace; + idBounds bounds; + float frac; + + start = origin; + + trace.areas = areaList; + trace.points = pointList; + trace.maxAreas = sizeof( areaList ) / sizeof( int ); + trace.getOutOfSolid = true; + + areaNum = PointAreaNum( start ); + if ( areaNum ) { + if ( ( areas[areaNum].flags & areaFlags ) && ( ( areas[areaNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return areaNum; + } + } + else { + // trace up + end = start; + end[2] += 32.0f; + Trace( trace, start, end ); + if ( trace.numAreas >= 1 ) { + if ( ( areas[0].flags & areaFlags ) && ( ( areas[0].travelFlags & excludeTravelFlags ) == 0 ) ) { + return areaList[0]; + } + start = pointList[0]; + start[2] += 1.0f; + } + } + + // trace down + end = start; + end[2] -= 32.0f; + Trace( trace, start, end ); + if ( trace.lastAreaNum ) { + if ( ( areas[trace.lastAreaNum].flags & areaFlags ) && ( ( areas[trace.lastAreaNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return trace.lastAreaNum; + } + start = trace.endpos; + } + + // expand bounds until an area is found + for ( i = 1; i <= 12; i++ ) { + frac = i * ( 1.0f / 12.0f ); + bounds[0] = origin + searchBounds[0] * frac; + bounds[1] = origin + searchBounds[1] * frac; + areaNum = BoundsReachableAreaNum( bounds, areaFlags, excludeTravelFlags ); + if ( areaNum && ( areas[areaNum].flags & areaFlags ) && ( ( areas[areaNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return areaNum; + } + } + return 0; +} + +/* +============ +idAASFileLocal::BoundsReachableAreaNum_r +============ +*/ +int idAASFileLocal::BoundsReachableAreaNum_r( int nodeNum, const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const { + int res; + const aasNode_t *node; + + while( nodeNum ) { + if ( nodeNum < 0 ) { + if ( ( areas[-nodeNum].flags & areaFlags ) && ( ( areas[-nodeNum].travelFlags & excludeTravelFlags ) == 0 ) ) { + return -nodeNum; + } + return 0; + } + node = &nodes[nodeNum]; + res = bounds.PlaneSide( planeList[node->planeNum] ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + nodeNum = BoundsReachableAreaNum_r( node->children[1], bounds, areaFlags, excludeTravelFlags ); + if ( nodeNum ) { + return nodeNum; + } + nodeNum = node->children[0]; + } + } + + return 0; +} + +/* +============ +idAASFileLocal::BoundsReachableAreaNum +============ +*/ +int idAASFileLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags, const int excludeTravelFlags ) const { + + return BoundsReachableAreaNum_r( 1, bounds, areaFlags, excludeTravelFlags ); +} + +/* +============ +idAASFileLocal::PushPointIntoAreaNum +============ +*/ +void idAASFileLocal::PushPointIntoAreaNum( int areaNum, idVec3 &point ) const { + int i, faceNum; + const aasArea_t *area; + const aasFace_t *face; + + area = &areas[areaNum]; + + // push the point to the right side of all area face planes + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = faceIndex[area->firstFace + i]; + face = &faces[abs( faceNum )]; + + const idPlane &plane = planeList[face->planeNum ^ INTSIGNBITSET( faceNum )]; + float dist = plane.Distance( point ); + + // project the point onto the face plane if it is on the wrong side + if ( dist < 0.0f ) { + point -= dist * plane.Normal(); + } + } +} + +/* +============ +idAASFileLocal::Trace +============ +*/ +#define TRACEPLANE_EPSILON 0.125f + +typedef struct aasTraceStack_s +{ + idVec3 start; + idVec3 end; + int planeNum; + int nodeNum; +} aasTraceStack_t; + +bool idAASFileLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { + int side, nodeNum, tmpPlaneNum; + double front, back, frac; + idVec3 cur_start, cur_end, cur_mid, v1, v2; + aasTraceStack_t tracestack[MAX_AAS_TREE_DEPTH]; + aasTraceStack_t *tstack_p; + const aasNode_t *node; + const idPlane *plane; + + trace.numAreas = 0; + trace.lastAreaNum = 0; + trace.blockingAreaNum = 0; + + tstack_p = tracestack; + tstack_p->start = start; + tstack_p->end = end; + tstack_p->planeNum = 0; + tstack_p->nodeNum = 1; //start with the root of the tree + tstack_p++; + + while( 1 ) { + + tstack_p--; + // if the trace stack is empty + if ( tstack_p < tracestack ) { + if ( !trace.lastAreaNum ) { + // completely in solid + trace.fraction = 0.0f; + trace.endpos = start; + } + else { + // nothing was hit + trace.fraction = 1.0f; + trace.endpos = end; + } + trace.planeNum = 0; + return false; + } + + // number of the current node to test the line against + nodeNum = tstack_p->nodeNum; + + // if it is an area + if ( nodeNum < 0) { + // if can't enter the area + if ( ( areas[-nodeNum].flags & trace.flags ) || ( areas[-nodeNum].travelFlags & trace.travelFlags ) ) { + if ( !trace.lastAreaNum ) { + trace.fraction = 0.0f; + v1 = vec3_origin; + } else { + v1 = end - start; + v2 = tstack_p->start - start; + trace.fraction = v2.Length() / v1.Length(); + } + trace.endpos = tstack_p->start; + trace.blockingAreaNum = -nodeNum; + trace.planeNum = tstack_p->planeNum; + // always take the plane with normal facing towards the trace start + plane = &planeList[trace.planeNum]; + if ( v1 * plane->Normal() > 0.0f ) { + trace.planeNum ^= 1; + } + return true; + } + trace.lastAreaNum = -nodeNum; + if ( trace.numAreas < trace.maxAreas ) { + if ( trace.areas ) { + trace.areas[trace.numAreas] = -nodeNum; + } + if ( trace.points ) { + trace.points[trace.numAreas] = tstack_p->start; + } + trace.numAreas++; + } + continue; + } + + // if it is a solid leaf + if ( !nodeNum ) { + if ( !trace.lastAreaNum ) { + trace.fraction = 0.0f; + v1 = vec3_origin; + } else { + v1 = end - start; + v2 = tstack_p->start - start; + trace.fraction = v2.Length() / v1.Length(); + } + trace.endpos = tstack_p->start; + trace.blockingAreaNum = 0; // hit solid leaf + trace.planeNum = tstack_p->planeNum; + // always take the plane with normal facing towards the trace start + plane = &planeList[trace.planeNum]; + if ( v1 * plane->Normal() > 0.0f ) { + trace.planeNum ^= 1; + } + if ( !trace.lastAreaNum && trace.getOutOfSolid ) { + continue; + } + else { + return true; + } + } + + // the node to test against + node = &nodes[nodeNum]; + // start point of current line to test against node + cur_start = tstack_p->start; + // end point of the current line to test against node + cur_end = tstack_p->end; + // the current node plane + plane = &planeList[node->planeNum]; + + front = plane->Distance( cur_start ); + back = plane->Distance( cur_end ); + + // if the whole to be traced line is totally at the front of this node + // only go down the tree with the front child + if ( front >= -ON_EPSILON && back >= -ON_EPSILON ) { + // keep the current start and end point on the stack and go down the tree with the front child + tstack_p->nodeNum = node->children[0]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + } + // if the whole to be traced line is totally at the back of this node + // only go down the tree with the back child + else if ( front < ON_EPSILON && back < ON_EPSILON ) { + // keep the current start and end point on the stack and go down the tree with the back child + tstack_p->nodeNum = node->children[1]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + } + // go down the tree both at the front and back of the node + else { + tmpPlaneNum = tstack_p->planeNum; + // calculate the hit point with the node plane + // put the cross point TRACEPLANE_EPSILON on the near side + if (front < 0) { + frac = (front + TRACEPLANE_EPSILON) / ( front - back ); + } + else { + frac = (front - TRACEPLANE_EPSILON) / ( front - back ); + } + + if (frac < 0) { + frac = 0.001f; //0 + } + else if (frac > 1) { + frac = 0.999f; //1 + } + + cur_mid = cur_start + ( cur_end - cur_start ) * frac; + + // side the front part of the line is on + side = front < 0; + + // first put the end part of the line on the stack (back side) + tstack_p->start = cur_mid; + tstack_p->planeNum = node->planeNum; + tstack_p->nodeNum = node->children[!side]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + // now put the part near the start of the line on the stack so we will + // continue with that part first. + tstack_p->start = cur_start; + tstack_p->end = cur_mid; + tstack_p->planeNum = tmpPlaneNum; + tstack_p->nodeNum = node->children[side]; + tstack_p++; + if ( tstack_p >= &tracestack[MAX_AAS_TREE_DEPTH] ) { + common->Error("idAASFileLocal::Trace: stack overflow\n" ); + return false; + } + } + } + return false; +} + +/* +============ +idAASLocal::AreaContentsTravelFlags +============ +*/ +int idAASFileLocal::AreaContentsTravelFlags( int areaNum ) const { + if ( areas[areaNum].contents & AREACONTENTS_WATER ) { + return TFL_WATER; + } + return TFL_AIR; +} + +/* +============ +idAASFileLocal::MaxTreeDepth_r +============ +*/ +void idAASFileLocal::MaxTreeDepth_r( int nodeNum, int &depth, int &maxDepth ) const { + const aasNode_t *node; + + if ( nodeNum <= 0 ) { + return; + } + + depth++; + if ( depth > maxDepth ) { + maxDepth = depth; + } + + node = &nodes[nodeNum]; + MaxTreeDepth_r( node->children[0], depth, maxDepth ); + MaxTreeDepth_r( node->children[1], depth, maxDepth ); + + depth--; +} + +/* +============ +idAASFileLocal::MaxTreeDepth +============ +*/ +int idAASFileLocal::MaxTreeDepth( void ) const { + int depth, maxDepth; + + depth = maxDepth = 0; + MaxTreeDepth_r( 1, depth, maxDepth ); + return maxDepth; +} diff --git a/src/aas/aasfilemanager.cpp b/src/aas/aasfilemanager.cpp new file mode 100644 index 0000000..0df4652 --- /dev/null +++ b/src/aas/aasfilemanager.cpp @@ -0,0 +1,72 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "AASFile.h" +#include "AASFile_local.h" + +/* +=============================================================================== + + AAS File Manager + +=============================================================================== +*/ + +class idAASFileManagerLocal : public idAASFileManager { +public: + virtual ~idAASFileManagerLocal( void ) {} + + virtual idAASFile * LoadAAS( const char *fileName, unsigned int mapFileCRC ); + virtual void FreeAAS( idAASFile *file ); +}; + +idAASFileManagerLocal AASFileManagerLocal; +idAASFileManager * AASFileManager = &AASFileManagerLocal; + + +/* +================ +idAASFileManagerLocal::LoadAAS +================ +*/ +idAASFile *idAASFileManagerLocal::LoadAAS( const char *fileName, unsigned int mapFileCRC ) { + idAASFileLocal *file = new idAASFileLocal(); + if ( !file->Load( fileName, mapFileCRC ) ) { + delete file; + return NULL; + } + return file; +} + +/* +================ +idAASFileManagerLocal::FreeAAS +================ +*/ +void idAASFileManagerLocal::FreeAAS( idAASFile *file ) { + delete file; +} diff --git a/src/bse/BSE.h b/src/bse/BSE.h new file mode 100644 index 0000000..9b553a7 --- /dev/null +++ b/src/bse/BSE.h @@ -0,0 +1,627 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2007 Id Software, Inc. +// + + +#ifndef _BSE_H_INC_ +#define _BSE_H_INC_ + +#include "BSEInterface.h" +#include "BSE_Particle.h" + +// BSE - Basic System for Effects + +// Notes: +// All times are floats of seconds +// All tints are floats of 0.0 to 1.0 +// All effects are presumed to be in the "base/effects" folder and have the extension of BSE_EFFECT_EXTENSION +// All angles are in fractions of a circle - 1 means 1 full circle +// All effect files are case insensitive +// Will not have different shaders to be randomly chosen - need to keep the tesses to a minimum (except possibly for decals) + +// Defined classes +class rvSegment; +class rvSegmentTemplate; +class rvDeclEffect; +class rvBSE; +class rvBSEManagerLocal; + +// Referenced classes +class rvRenderModelBSE; +class idRenderModel; +class rvParticle; +class idPlayerView; + +const float WORLD_SIZE = ( 128.0f * 1024.0f ); +const float BSE_LARGEST = ( 512.0f ); +const float BSE_TESS_COST = ( 20.0f ); // The expense of a new tess +const float BSE_PHYSICS_COST = ( 80.0f ); // The expense of 1 particle having physics + +const float BSE_PARTICLE_TEXCOORDSCALE = ( 0.01f ); + + + +const unsigned int MEMORY_BLOCK_SIZE = ( 0x100000 ); +const unsigned int BSE_ELEC_MAX_BOLTS = ( 200 ); + +typedef enum eBSEPerfCounter +{ + PERF_NUM_BSE, + PERF_NUM_TRACES, + PERF_NUM_PARTICLES, + PERF_NUM_TEXELS, + PERF_NUM_SEGMENTS, + NUM_PERF_COUNTERS +}; + +typedef enum eBSESegment +{ + SEG_NONE = 0, + SEG_EFFECT, // Spawns another effect inheriting data from owner + SEG_EMITTER, // Spawns particles at a rate + SEG_SPAWNER, // Spawns particles instantly + SEG_TRAIL, // Leaves a trail of particles + SEG_SOUND, // Plays a sound + SEG_DECAL, // Leaves an idDecal + SEG_LIGHT, // Displays a 3D light + SEG_DELAY, // A control segment for looping + SEG_DOUBLEVISION, // Triggers the double vision effect + SEG_SHAKE, // Triggers a screen shake + SEG_TUNNEL, // Triggers the id tunnel vision effect + SEG_COUNT +}; + +typedef enum eBSETrail +{ + TRAIL_NONE = 0, + TRAIL_BURN, + TRAIL_MOTION, + TRAIL_PARTICLE, + TRAIL_COUNT +}; + +enum { SFLAG_EXPIRED = BIT( 0 ), + SFLAG_SOUNDPLAYING = BIT( 1 ), + SFLAG_HASMOTIONTRAIL = BIT( 2 ), +}; + +// ============================================== +// Effect class +// ============================================== + +class rvSegment +{ +public: + friend class rvBSE; + rvSegment( void ) { mFlags = 0; mParticles = NULL; mUsedHead = NULL; mFreeHead = NULL; mParticleCount = 0; mLoopParticleCount = 0; } + ~rvSegment( void ); + + void SetFlag( bool on, int flag ) { on ? mFlags |= flag : mFlags &= ~flag; } + + bool GetExpired( void ) const { return( ( bool )( mFlags & SFLAG_EXPIRED ) ); } + bool GetSoundPlaying( void ) const { return( !!( mFlags & SFLAG_SOUNDPLAYING ) ); } + bool GetHasMotionTrail( void ) const { return( !!( mFlags & SFLAG_HASMOTIONTRAIL ) ); } + + void SetExpired( bool expired ) { SetFlag( expired, SFLAG_EXPIRED ); } + void SetSoundPlaying( bool soundPlaying ) { SetFlag( soundPlaying, SFLAG_SOUNDPLAYING ); } + void SetHasMotionTrail( bool hmt ) { SetFlag( hmt, SFLAG_HASMOTIONTRAIL ); } + + int GetMotionTrailCount( void ) { return( mActiveCount ); } + + rvSegmentTemplate *GetSegmentTemplate( void ); + const rvDeclEffect *GetEffectDecl( void ) { return( mEffectDecl ); } + + bool GetLocked( void ); + bool Active( void ); + + void PlayEffect( rvBSE *effect, rvSegmentTemplate *st ); + void InitTime( rvBSE *effect, rvSegmentTemplate *st, float time ); + void ResetTime( rvBSE *effect, float time ); + void Init( rvBSE *effect, const rvDeclEffect *decl, int segmentTemplateHandle, float time ); + void InitParticles( rvBSE *effect ); + void RefreshParticles( rvBSE *effect, rvSegmentTemplate *st ); + bool Check( rvBSE *effect, float time ); + void Handle( rvBSE *effect, float time ); + void UpdateGenericParticles( rvBSE *effect, rvSegmentTemplate *st, float time ); + void UpdateSimpleParticles( float time ); + void UpdateElectricityParticles( float time ); + bool UpdateParticles( rvBSE *effect, float time ); + float EvaluateCost( void ); + float AttenuateDuration( rvBSE *effect, rvSegmentTemplate *st ); + float AttenuateInterval( rvBSE *effect, rvSegmentTemplate *st ); + float AttenuateCount( rvBSE *effect, rvSegmentTemplate *st, float min, float max ); + void ValidateSpawnRates( void ); + void AddToParticleCount( rvBSE *effect, int count, int loopCount, float duration ); + void GetSecondsPerParticle( rvBSE *effect, rvSegmentTemplate *st, rvParticleTemplate *pt ); + void CalcTrailCounts( rvBSE *effect, rvSegmentTemplate *st, rvParticleTemplate *pt, float duration ); + void CalcCounts( rvBSE *effect, float time ); + void EmitSmokeParticles( rvBSE *effect, rvSegmentTemplate *st, rvParticle *particle, float time ); + s_channelType GetSoundChannel( void ) { return static_cast< s_channelType >( mSegmentTemplateHandle ) + SCHANNEL_ONE; } + + void CreateDecal( rvBSE *effect, float time ); + void InitLight( rvBSE *effect, rvSegmentTemplate *st, float time ); + bool HandleLight( rvBSE *effect, rvSegmentTemplate *st, float time ); + + rvParticle *InitParticleArray( rvBSE *effect ); + rvParticle *GetFreeParticle( rvBSE *effect ); + void InsertParticle( rvParticle *particle, rvSegmentTemplate *st ); + rvParticle *SpawnParticle( rvBSE *effect, rvSegmentTemplate *st, float birthTime, const idVec3 &initPos = vec3_origin, const idMat3 &initAxis = mat3_identity ); + void SpawnParticles( rvBSE *effect, rvSegmentTemplate *st, float birthTime, int count ); + + void AllocateSurface( rvBSE *effect, idRenderModel *model ); + void ClearSurface( rvBSE *effect, idRenderModel *model ); + void RenderMotion( rvBSE *effect, const struct renderEffect_s *owner, rvRenderModelBSE *model, rvParticleTemplate *pt, float time ); + void RenderTrail( rvBSE *effect, const struct renderEffect_s *owner, rvRenderModelBSE *model, float time ); + void Render( rvBSE *effect, const struct renderEffect_s *owner, rvRenderModelBSE *model, float time ); + void Rewind( rvBSE *effect ); + void Advance( rvBSE *effect ); +protected: + void Sort( const idVec3 &eyePos ); + + // Fixed at spawn time + int mSegmentTemplateHandle; + const rvDeclEffect *mEffectDecl; + + float mSegStartTime; // Start time of segment + float mSegEndTime; + + idVec2 mSecondsPerParticle; // How quickly the particles are spawned in an emitter + idVec2 mCount; // The count of particles from a spawner + + int mParticleCount; // For getting the amount of memory to alloc for tris data + int mLoopParticleCount; + + float mSoundVolume; + float mFreqShift; + + // Dynamically altered during effect + int mFlags; + float mLastTime; // Last time the segment was serviced + int mActiveCount; // Number of active particles + + rvParticle *mFreeHead; // Linked list of particles + rvParticle *mUsedHead; + rvParticle *mParticles; + + static float mSegmentBaseCosts[SEG_COUNT]; +}; + +enum { STFLAG_ENABLED = BIT( 0 ), + STFLAG_LOCKED = BIT( 1 ), + STFLAG_HASPARTICLES = BIT( 2 ), + STFLAG_HASPHYSICS = BIT( 3 ), + STFLAG_IGNORE_DURATION = BIT( 4 ), + STFLAG_INFINITE_DURATION = BIT( 5 ), + STFLAG_ATTENUATE_EMITTER = BIT( 6 ), + STFLAG_INVERSE_ATTENUATE = BIT( 7 ), + STFLAG_TEMPORARY = BIT( 8 ), + STFLAG_USEMATCOLOR = BIT( 9 ), + STFLAG_DEPTH_SORT = BIT( 10 ), + STFLAG_INVERSE_DRAWORDER = BIT( 11 ), + STFLAG_ORIENTATE_IDENTITY = BIT( 12 ), + STFLAG_COMPLEX = BIT( 13 ), + STFLAG_CALCULATE_DURATION = BIT( 14 ), +}; + +class rvSegmentTemplate +{ +public: + friend class rvSegment; + friend class rvBSE; + friend class rvSegmentTemplateWrapper; + friend class rvDeclEffectEditLocal; + + rvSegmentTemplate( void ); + ~rvSegmentTemplate( void ) {} + + // Support copying of a template + rvSegmentTemplate( const rvSegmentTemplate& copy ) { *this = copy; } + + rvSegmentTemplate &operator = ( const rvSegmentTemplate © ); + bool operator == ( const rvSegmentTemplate& a ) const { return Compare ( a ); } + bool operator != ( const rvSegmentTemplate& a ) const { return !Compare ( a ); } + + void SetFlag( bool on, int flag ) { on ? mFlags |= flag : mFlags &= ~flag; } + + bool GetEnabled( void ) const { return( !!( mFlags & STFLAG_ENABLED ) ); } + bool GetLocked( void ) const { return( !!( mFlags & STFLAG_LOCKED ) ); } + bool GetHasParticles( void ) const { return( !!( mFlags & STFLAG_HASPARTICLES ) ); } + bool GetHasPhysics( void ) const { return( !!( mFlags & STFLAG_HASPHYSICS ) ); } + bool GetIgnoreDuration( void ) const { return( !!( mFlags & STFLAG_IGNORE_DURATION ) ); } + bool GetInfiniteDuration( void ) const { return( !!( mFlags & STFLAG_INFINITE_DURATION ) ); } + bool GetAttenuateEmitter( void ) const { return( !!( mFlags & STFLAG_ATTENUATE_EMITTER ) ); } + bool GetInverseAttenuate( void ) const { return( !!( mFlags & STFLAG_INVERSE_ATTENUATE ) ); } + bool GetUseMaterialColor( void ) const { return( !!( mFlags & STFLAG_USEMATCOLOR ) ); } + bool GetTemporary( void ) const { return( !!( mFlags & STFLAG_TEMPORARY ) ); } + bool GetDepthSort( void ) const { return( !!( mFlags & STFLAG_DEPTH_SORT ) ); } + bool GetInverseDrawOrder( void ) const { return( !!( mFlags & STFLAG_INVERSE_DRAWORDER ) ); } + bool GetOrientateIdentity( void ) const { return( !!( mFlags & STFLAG_ORIENTATE_IDENTITY ) ); } + bool GetComplexParticle( void ) const { return( !!( mFlags & STFLAG_COMPLEX ) ); } + bool GetCalculateDuration( void ) const { return( !!( mFlags & STFLAG_CALCULATE_DURATION ) ); } + + void SetEnabled( bool enabled ) { SetFlag( enabled, STFLAG_ENABLED ); } + void SetLocked( bool locked ) { SetFlag( locked, STFLAG_LOCKED ); } + void SetHasParticles( bool hasParticles ) { SetFlag( hasParticles, STFLAG_HASPARTICLES ); } + void SetHasPhysics( bool hasPhysics ) { SetFlag( hasPhysics, STFLAG_HASPHYSICS ); } + void SetIgnoreDuration( bool ignoreDuration ) { SetFlag( ignoreDuration, STFLAG_IGNORE_DURATION ); } + void SetInfiniteDuration( bool infiniteDuration ) { SetFlag( infiniteDuration, STFLAG_INFINITE_DURATION ); } + void SetAttenuateEmitter( bool attenuate ) { SetFlag( attenuate, STFLAG_ATTENUATE_EMITTER ); } + void SetInverseAttenuate( bool attenuate ) { SetFlag( attenuate, STFLAG_INVERSE_ATTENUATE ); } + void SetUseMaterialColor( bool attenuate ) { SetFlag( attenuate, STFLAG_USEMATCOLOR ); } + void SetTemporary( bool persistent ) { SetFlag( persistent, STFLAG_TEMPORARY ); } + void SetDepthSort( bool locked ) { SetFlag( locked, STFLAG_DEPTH_SORT ); } + void SetInverseDrawOrder( bool inverseDrawOrder ) { SetFlag( inverseDrawOrder, STFLAG_INVERSE_DRAWORDER ); } + void SetOrientateIdentity( bool orientateIdentity ) { SetFlag( orientateIdentity, STFLAG_ORIENTATE_IDENTITY ); } + void SetComplexParticle( bool enabled ) { SetFlag( enabled, STFLAG_COMPLEX ); } + void SetCalculateDuration( bool calculateDuration ) { SetFlag( calculateDuration, STFLAG_CALCULATE_DURATION ); } + + const idStr &GetSegmentName( void ) const { return( mSegmentName ); } + + const rvDeclEffect *GetEffectDecl( void ) { return( mDeclEffect ); } + + void EvaluateTrailSegment( rvDeclEffect *et ); + int GetTrailSegmentIndex( void ) const { return( mTrailSegmentIndex ); } + + int GetType( void ) const { return( mSegType ); } + bool GetSmoker( void ); + bool Parse( rvDeclEffect *et, int segmentType, idLexer *lexer ); + void Init( rvDeclEffect *decl ); + float GetStartTime( void ) const { return( rvRandom::flrand( mLocalStartTime[0], mLocalStartTime[1] ) ); } + float GetDuration( void ) const { return( rvRandom::flrand( mLocalDuration[0], mLocalDuration[1] ) ); } + void SetMinDuration( rvDeclEffect *effect ); + void SetMaxDuration( rvDeclEffect *effect ); + bool GetSoundLooping( void ); + float GetSoundVolume( void ) const { return( rvRandom::flrand( mSoundVolume[0], mSoundVolume[1] ) ); } + float GetFreqShift( void ) const { return( rvRandom::flrand( mFreqShift[0], mFreqShift[1] ) ); } + float GetDensity( void ) const { return( rvRandom::flrand( mDensity[0], mDensity[1] ) ); } + float GetMaxDensity( void ) const { return( mDensity[1] ); } + float GetParticleCap( void ) const { return( mParticleCap ); } + float GetMaxCount( void ) const { return( mCount[1] ); } + int GetTexelCount( void ); + bool Finish( rvDeclEffect *et ); + void CreateParticleTemplate( rvDeclEffect *et, idLexer *lexer, int particleType ); + rvParticleTemplate *GetParticleTemplate( void ) { return( &mParticleTemplate ); } + const rvParticleTemplate*GetParticleTemplate( void ) const { return( &mParticleTemplate ); } + float CalculateBounds( void ); + bool DetailCull( void ) const; + + float EvaluateCost( int activeParticles ) const; + + void Duplicate( const rvSegmentTemplate © ); + bool Compare( const rvSegmentTemplate& a ) const; + +private: + rvDeclEffect *mDeclEffect; + + // Common parms + idStr mSegmentName; + int mFlags; + int mSegType; // SEG_ enum + idVec2 mLocalStartTime; // Start time of segment wrt effect + idVec2 mLocalDuration; // Min and max duration + idVec2 mAttenuation; // How effect fades off to the distance + float mParticleCap; + float mScale; + float mDetail; // bse_scale value that culls out this segment + + // Emitter parms + rvParticleTemplate mParticleTemplate; + idVec2 mCount; // The count of particles from a spawner + idVec2 mDensity; // Sets count or rate based on volume, area or length + int mTrailSegmentIndex; // Segment containing the trail info + + int mNumEffects; + const rvDeclEffect *mEffects[BSE_NUM_SPAWNABLE]; // Effect to play on certain conditions + + const idSoundShader *mSoundShader; + idVec2 mSoundVolume; // Starting volume of sound in decibels + idVec2 mFreqShift; // Frequency shift of sound + + rvBSE *mBSEEffect; + +}; + +// ============================================== +// ============================================== + +enum { ETFLAG_HAS_SOUND = BIT( 0 ), + ETFLAG_USES_ENDORIGIN = BIT( 1 ), + ETFLAG_ATTENUATES = BIT( 2 ), + ETFLAG_EDITOR_MODIFIED = BIT( 3 ), + ETFLAG_USES_MATERIAL_COLOR = BIT( 4 ), + ETFLAG_ORIENTATE_IDENTITY = BIT( 5 ), + ETFLAG_USES_AMBIENT_CUBEMAP = BIT( 6 ), + ETFLAG_HAS_PHYSICS = BIT( 7 ), +}; + +class rvDeclEffect : public idDecl +{ +public: + friend class rvBSE; + friend class rvEffectTemplateWrapper; + friend class rvBSEManagerLocal; + + rvDeclEffect( void ) { Init(); } + rvDeclEffect( const rvDeclEffect& copy ); + virtual ~rvDeclEffect( void ) { } + + bool operator== ( const rvDeclEffect& comp ) const { return Compare ( comp ); } + bool operator!= ( const rvDeclEffect& comp ) const { return !Compare ( comp ); } + + rvDeclEffect& operator= ( const rvDeclEffect © ); + + virtual bool SetDefaultText( void ); + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual size_t Size( void ) const; + virtual bool Validate( const char *text, int textLength, idStr &report ) const; + + static void CacheFromDict( const idDict& dict ); + + void SetFlag( bool on, int flag ) { on ? mFlags |= flag : mFlags &= ~flag; } + + bool GetEditorModified( void ) const { return( !!( mFlags & ETFLAG_EDITOR_MODIFIED ) ); } + bool GetHasSound( void ) const { return( !!( mFlags & ETFLAG_HAS_SOUND ) ); } + bool GetHasPhysics( void ) const { return( !!( mFlags & ETFLAG_HAS_PHYSICS ) ); } + bool GetUsesEndOrigin( void ) const { return( !!( mFlags & ETFLAG_USES_ENDORIGIN ) ); } + bool GetAttenuates( void ) const { return( !!( mFlags & ETFLAG_ATTENUATES ) ); } + bool GetOrientateIdentity( void ) const { return( !!( mFlags & ETFLAG_ORIENTATE_IDENTITY ) ); } + bool GetUsesAmbientCubeMap( void ) const { return( !!( mFlags & ETFLAG_USES_AMBIENT_CUBEMAP ) ); } + + void SetEditorModified( bool modified ) { base->EnsureNotPurged ( ); SetFlag( modified, ETFLAG_EDITOR_MODIFIED ); } + void SetHasSound( bool hasSound ) { SetFlag( hasSound, ETFLAG_HAS_SOUND ); } + void SetHasPhysics( bool hasPhysics ) { SetFlag( hasPhysics, ETFLAG_HAS_PHYSICS ); } + void SetUsesEndOrigin( bool usesEndOrigin ) { SetFlag( usesEndOrigin, ETFLAG_USES_ENDORIGIN ); } + void SetAttenuates( bool attenuates ) { SetFlag( attenuates, ETFLAG_ATTENUATES ); } + void SetOrientateIdentity( bool orientateIdentity ) { SetFlag( orientateIdentity, ETFLAG_ORIENTATE_IDENTITY ); } + void SetUsesAmbientCubeMap( bool usesAmbientCubeMap ) { SetFlag( usesAmbientCubeMap, ETFLAG_USES_AMBIENT_CUBEMAP ); } + + rvSegmentTemplate *GetSegmentTemplate( int i ); + const rvSegmentTemplate *GetSegmentTemplate( int i ) const { return( &mSegmentTemplates[i] ); } + rvSegmentTemplate *GetSegmentTemplate( const char *name ); + int AddSegment( rvSegmentTemplate &segment ); + void DeleteSegment( int index ); + + int GetTrailSegmentIndex( const idStr &name ); + + void SetMinDuration( float duration ); + float GetMinDuration( void ) const { return( mMinDuration ); } + void SetMaxDuration( float duration ); + float GetMaxDuration( void ) const { return( mMaxDuration ); } + + float GetSize( void ) const { return( mSize ); } + void SetSize( float size ) { mSize = size; } + + int GetNumSegmentTemplates( void ) const { return( mSegmentTemplates.Num() ); } + void Init( void ); + void Finish( void ); + float CalculateBounds( void ); + void CopyData( const rvDeclEffect © ); + void CreateEditorOriginal( void ); + void DeleteEditorOriginal( void ); + bool CompareToEditorOriginal( void ); + void Revert( void ); + + void IncPlayCount( void ) const { mPlayCount++; } + int GetPlayCount( void ) const { return( mPlayCount ); } + void IncLoopCount( void ) const { mLoopCount++; } + int GetLoopCount( void ) const { return( mLoopCount ); } + +private: + bool Compare( const rvDeclEffect& comp ) const; + + int mFlags; + float mMinDuration; // Minimum possible duration of the effect + float mMaxDuration; // Maximum possible duration of the effect + float mSize; + idList mSegmentTemplates; + rvDeclEffect *mEditorOriginal; + + mutable int mPlayCount; // For profiling + mutable int mLoopCount; // For profiling +}; + +enum { EFLAG_LOOPING = BIT( 0 ), + EFLAG_HASENDORIGIN = BIT( 1 ), + EFLAG_ENDORIGINCHANGED = BIT( 2 ), + EFLAG_STOPPED = BIT( 3 ), + EFLAG_ORIENTATE_IDENTITY = BIT( 4 ), + EFLAG_AMBIENT = BIT( 5 ), +}; + +const int LIGHTID_EFFECT_LIGHT = 300; + +class rvBSE +{ +public: + friend class rvSegment; + rvBSE( void ) { mFlags = 0; } + ~rvBSE( void ) {} + + void Init( rvDeclEffect *declEffect, struct renderEffect_s *parms, float time ); + void Destroy( void ); + + void SetFlag( bool on, int flag ) { on ? mFlags |= flag : mFlags &= ~flag; } + + bool GetLooping( void ) const { return( !!( mFlags & EFLAG_LOOPING ) ); } + bool GetHasEndOrigin( void ) const { return( !!( mFlags & EFLAG_HASENDORIGIN ) ); } + bool GetEndOriginChanged( void ) const { return( !!( mFlags & EFLAG_ENDORIGINCHANGED ) ); } + bool GetStopped( void ) const { return( !!( mFlags & EFLAG_STOPPED ) ); } + bool GetOrientateIdentity( void ) const { return( !!( mFlags & EFLAG_ORIENTATE_IDENTITY ) ); } + + void SetLooping( bool looping ) { SetFlag( looping, EFLAG_LOOPING ); } + void SetHasEndOrigin( bool hasEndOrigin ) { SetFlag( hasEndOrigin, EFLAG_HASENDORIGIN ); } + void SetEndOriginChanged( bool endOriginChanged ) { SetFlag( endOriginChanged, EFLAG_ENDORIGINCHANGED ); } + void SetStopped( bool stopped ) { SetFlag( stopped, EFLAG_STOPPED ); } + void SetOrientateIdentity( bool orientateIdentity ) { SetFlag( orientateIdentity, EFLAG_ORIENTATE_IDENTITY ); } + + void SetDuration( float time ); + float GetDuration( void ) const { return( mDuration ); } + float GetStartTime( void ) const { return mStartTime; } + bool Expired( float time ) const { return( time > mStartTime + mDuration ); } + + void SetAttenuation( float atten ) { mAttenuation = atten; } + float GetAttenuation( rvSegmentTemplate *st ) const; + float GetOriginAttenuation( rvSegmentTemplate *st ) const; + void UpdateAttenuation( void ); + + idSoundEmitter *GetReferenceSound( int index ) const; + int GetReferenceSoundHandle( void ) const { return mReferenceSoundHandle; } + + float GetRed( void ) const { return( mTint[0] ); } + float GetGreen( void ) const { return( mTint[1] ); } + float GetBlue( void ) const { return( mTint[2] ); } + float GetAlpha( void ) const { return( mTint[3] ); } + + float GetBrightness( void ) const { return( mBrightness ); } + void SetBrightness( float bright ) { mBrightness = bright; } + const idVec2 &GetSpriteSize( void ) const { return mSpriteSize; } + + bool CanInterpolate( void ) { return( mCurrentTime - mLastTime > BSE_TIME_EPSILON ); } + void UpdateFromOwner( renderEffect_t *parms, float time, bool init = false ); + void LoopInstant( float time ); + void LoopLooping( float time ); + bool Service( renderEffect_t *parms, float time ); + void UpdateSoundEmitter( rvSegmentTemplate *st, rvSegment *seg ); + + void DisplayDebugInfo( const struct renderEffect_s *parms, const struct viewDef_s *view, idBounds &bounds ); + idRenderModel *Render( const struct renderEffect_s *owner, const struct viewDef_s *view ); + const char *GetDeclName( void ); + rvSegment *GetTrailSegment( int child ) { return( &mSegments[child] ); } + rvSegment *GetTrailSegment( const idStr &name ); + + const idVec3 &GetViewOrg( void ) const { return( mViewOrg ); } + const idMat3 &GetViewAxis( void ) const { return( mViewAxis ); } + + const idVec3 &GetGravity( void ) const { return( mGravity ); } + const idVec3 &GetGravityDir( void ) const { return( mGravityDir ); } + + const idVec3 &GetOriginalOrigin( void ) const { return( mOriginalOrigin ); } + const idVec3 &GetOriginalEndOrigin( void ) const { return( mOriginalEndOrigin ); } + const idMat3 &GetOriginalAxis( void ) const { return( mOriginalAxis ); } + + const idVec3 &GetCurrentOrigin( void ) const { return( mCurrentOrigin ); } + const idVec3 &GetCurrentVelocity( void ) const { return( mCurrentVelocity ); } + const idVec3 &GetCurrentEndOrigin( void ) const { return( mCurrentEndOrigin ); } + const idVec3 GetInterpolatedOffset( float time ) const; + const idMat3 &GetCurrentAxis( void ) const { return( mCurrentAxis ); } + const idMat3 &GetCurrentAxisTransposed( void ) const { return( mCurrentAxisTransposed ); } + const idBounds &GetCurrentLocalBounds( void ) const { return( mCurrentLocalBounds ); } + const idBounds &GetCurrentWorldBounds( void ) const { return( mCurrentWorldBounds ); } + + void UpdateSegments( float time ); + float EvaluateCost( int segment = -1 ); + +private: + // Fixed at spawn time + const rvDeclEffect *mDeclEffect; + int mReferenceSoundHandle; + + idVec3 mOriginalOrigin; // Origin in world space + idVec3 mOriginalEndOrigin; + idMat3 mOriginalAxis; // Original axis of orientation + + float mStartTime; // World start time of effect + float mLastTime; // Last time the effect was serviced + float mDuration; // Duration of the effect - including any randomness + + // Dynamically altered + int mFlags; + float mAttenuation; // Forced attenuation settable in code + float mOriginDistanceToCamera; // Distance from effect origin to view origin + float mShortestDistanceToCamera; // Closest point on effects bounds to view origin + idVec4 mTint; // Overridable tint + idVec2 mSpriteSize; + float mBrightness; // Overall brightness of effect + float mCost; // Best guess at how expensive the effect is + + idVec3 mViewOrg; + idMat3 mViewAxis; + idVec3 mGravity; + idVec3 mGravityDir; + + idVec3 mLastOrigin; + + float mCurrentTime; + idVec3 mCurrentOrigin; // Current origin in world space + idVec3 mCurrentVelocity; // Current velocity in world space + idVec3 mCurrentEndOrigin; // Current end origin in world space + idMat3 mCurrentAxis; // Current axis of orientation + idMat3 mCurrentAxisTransposed; // Current axis of orientation transposed + idBounds mCurrentLocalBounds; // Current local bounds + idBounds mCurrentWorldBounds; // Current world bounds + + idList mSegments; +}; + +// ============================================== +// ============================================== + +class rvBSEManagerLocal : public rvBSEManager +{ +public: + rvBSEManagerLocal( void ) {} + ~rvBSEManagerLocal( void ) {} + + virtual bool Init( void ); + virtual bool Shutdown( void ); + + virtual bool PlayEffect( rvRenderEffectLocal *def, float time ); + virtual bool ServiceEffect( rvRenderEffectLocal *def, float time ); + virtual void StopEffect( rvRenderEffectLocal *def ); + virtual void FreeEffect( rvRenderEffectLocal *def ); + virtual float EffectDuration( const rvRenderEffectLocal *def ); + virtual bool CheckDefForSound( const renderEffect_t *def ); + + virtual void BeginLevelLoad( void ); + virtual void EndLevelLoad( void ); + + virtual void StartFrame( void ); + virtual void EndFrame( void ); + virtual bool Filtered( const char *name, effectCategory_t category ); + + virtual void UpdateRateTimes( void ); + virtual bool CanPlayRateLimited( effectCategory_t category ); + + void SetDoubleVisionParms( float time, float scale ); + void SetShakeParms( float time, float scale ); + void SetTunnelParms( float time, float scale ); + + void Stats( const idCmdArgs &args ); + +public: + static idBlockAlloc effects; + static idVec3 mCubeNormals[6]; + static idMat3 mModelToBSE; + static idList mTraceModels; + static const char *mSegmentNames[SEG_COUNT]; + static int mPerfCounters[NUM_PERF_COUNTERS]; + static float mEffectRates[EC_MAX]; + static idCVar *g_decals; +}; + +#endif // _BSE_H_INC_ diff --git a/src/bse/BSEInterface.h b/src/bse/BSEInterface.h new file mode 100644 index 0000000..7cf6665 --- /dev/null +++ b/src/bse/BSEInterface.h @@ -0,0 +1,127 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _BSE_INTERFACE_H_INC_ +#define _BSE_INTERFACE_H_INC_ + +#define BSE_EFFECT_EXTENSION "fx" + +enum +{ + VIEWEFFECT_DOUBLEVISION = 0, + VIEWEFFECT_SHAKE, + VIEWEFFECT_TUNNEL +}; + +typedef enum { + EC_IGNORE = 0, + EC_IMPACT, + EC_IMPACT_PARTICLES, + + EC_MAX, +} effectCategory_t; + +extern idCVar bse_enabled; +extern idCVar bse_render; +extern idCVar bse_debug; +extern idCVar bse_showBounds; +extern idCVar bse_physics; +extern idCVar bse_debris; +extern idCVar bse_scale; +extern idCVar bse_singleEffect; +extern idCVar bse_maxParticles; + +// Interface to the effects system + +class rvBSEManager +{ +public: + virtual ~rvBSEManager( void ) {} + + virtual bool Init( void ) = 0; + virtual bool Shutdown( void ) = 0; + + virtual bool PlayEffect( class rvRenderEffectLocal *def, float time ) = 0; + virtual bool ServiceEffect( class rvRenderEffectLocal *def, float time ) = 0; + virtual void StopEffect( rvRenderEffectLocal *def ) = 0; + virtual void FreeEffect( rvRenderEffectLocal *def ) = 0; + virtual float EffectDuration( const rvRenderEffectLocal *def ) = 0; + + virtual bool CheckDefForSound( const renderEffect_t *def ) = 0; + + virtual void BeginLevelLoad( void ) = 0; + virtual void EndLevelLoad( void ) = 0; + + virtual void StartFrame( void ) = 0; + virtual void EndFrame( void ) = 0; + virtual bool Filtered( const char *name, effectCategory_t category ) = 0; + + virtual void UpdateRateTimes( void ) = 0; + virtual bool CanPlayRateLimited( effectCategory_t category ) = 0; +}; + +extern rvBSEManager *bse; + +class rvDeclEffectEdit +{ +public: + virtual ~rvDeclEffectEdit() {} + virtual void Finish( class rvDeclEffect *edit ) = 0; + virtual class rvSegmentTemplate *GetSegmentTemplate( class rvDeclEffect *edit, const char *name ) = 0; + virtual class rvSegmentTemplate *GetSegmentTemplate( class rvDeclEffect *edit, int i ) = 0; + virtual void CopyData( class rvDeclEffect *edit, class rvDeclEffect *copy ) = 0; + virtual int AddSegment( class rvDeclEffect *edit, class rvSegmentTemplate *add ) = 0; + virtual void DeleteSegment( class rvDeclEffect *edit, int index ) = 0; + virtual void SwapSegments( class rvSegmentTemplate *seg1, class rvSegmentTemplate *seg2 ) = 0; + + virtual void CreateEditorOriginal( class rvDeclEffect *edit ) = 0; + virtual void DeleteEditorOriginal( class rvDeclEffect *edit ) = 0; + virtual bool CompareToEditorOriginal( class rvDeclEffect *edit ) = 0; + virtual void RevertToEditorOriginal( class rvDeclEffect *edit ) = 0; + + virtual void Init( class rvSegmentTemplate *edit, class rvDeclEffect *effect ) = 0; + virtual bool Parse( class rvSegmentTemplate *edit, class rvDeclEffect *effect, int type, class idLexer *lexer ) = 0; + virtual void Finish( class rvSegmentTemplate *edit, class rvDeclEffect *effect ) = 0; + virtual bool Compare( class rvSegmentTemplate *edit, const class rvSegmentTemplate *other ) const = 0; + virtual void SetName( class rvSegmentTemplate *edit, const char *name ) = 0; + + virtual void Finish( class rvParticleTemplate *edit ) = 0; + virtual bool Compare( class rvParticleTemplate *edit, const class rvParticleTemplate *other ) const = 0; + virtual void Init( class rvParticleTemplate *edit ) = 0; + virtual void FixupParms( class rvParticleTemplate *edit, class rvParticleParms *parms ) = 0; + virtual void SetMaterialName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetModelName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetEntityDefName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetTrailTypeName( class rvParticleTemplate *edit, const char *name ) = 0; + virtual void SetTrailMaterialName( class rvParticleTemplate *edit, const char *name ) = 0; + + virtual bool Compare( class rvParticleParms *edit, const class rvParticleParms *other ) const = 0; + + virtual void CalcRate( class rvEnvParms *edit, float *rate, float duration, int count ) = 0; + virtual void Evaluate3( class rvEnvParms *edit, float time, const float *start, const float *rate, const float *end, float *dest ) = 0; +}; + +extern rvDeclEffectEdit *declEffectEdit; + +#endif // _BSE_INTERFACE_H_INC_ diff --git a/src/bse/BSE_Bounds.cpp b/src/bse/BSE_Bounds.cpp new file mode 100644 index 0000000..706a1b8 --- /dev/null +++ b/src/bse/BSE_Bounds.cpp @@ -0,0 +1,159 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" + +/* +=============================================================================== + + Quake 4 BSE conservative-bound evaluation + + Function ownership and signatures come from BSE_Bounds.obj in quake4.pdb. + The sampled corner evaluation below follows the address-matched retail + executable body. + +=============================================================================== +*/ + +void rvParticleTemplate::EvaluateSimplePosition( idVec3 &pos, float time, + float lifeTime, idVec3 &initPos, idVec3 &velocity, + idVec3 &acceleration, idVec3 &friction ) { + pos = initPos + velocity * time; + const float halfTimeSquared = time * time * 0.5f; + pos += acceleration * halfTimeSquared; + + const float frictionScale = + ( expf( ( lifeTime - halfTimeSquared ) / lifeTime ) - 1.0f ) * + halfTimeSquared * halfTimeSquared * ( 1.0f / 3.0f ); + pos += friction * frictionScale; +} + +float rvParticleTemplate::GetMaxParmValue( rvParticleParms &spawn, + rvParticleParms &death, rvEnvParms &envelope ) { + idBounds bounds; + spawn.GetMinsMaxs( bounds[0], bounds[1] ); + + float envelopeMin; + float envelopeMax; + if ( envelope.GetMinMax( envelopeMin, envelopeMax ) ) { + bounds[0] *= envelopeMin; + bounds[1] *= envelopeMax; + + idBounds deathBounds; + death.GetMinsMaxs( deathBounds[0], deathBounds[1] ); + deathBounds[0] *= envelopeMin; + deathBounds[1] *= envelopeMax; + for ( int axis = 0; axis < 3; ++axis ) { + if ( deathBounds[0][axis] < bounds[0][axis] ) { + bounds[0][axis] = deathBounds[0][axis]; + } + if ( deathBounds[1][axis] > bounds[1][axis] ) { + bounds[1][axis] = deathBounds[1][axis]; + } + } + } + + const float minLength = bounds[0].Length(); + const float maxLength = bounds[1].Length(); + return maxLength > minLength ? maxLength : minLength; +} + +float rvParticleTemplate::GetFurthestDistance( void ) { + idBounds spawnPosition; + idBounds velocity; + idBounds acceleration; + idBounds friction; + idBounds offset; + mSpawnPosition.GetMinsMaxs( spawnPosition[0], spawnPosition[1] ); + mSpawnVelocity.GetMinsMaxs( velocity[0], velocity[1] ); + mSpawnAcceleration.GetMinsMaxs( acceleration[0], acceleration[1] ); + mSpawnFriction.GetMinsMaxs( friction[0], friction[1] ); + mSpawnOffset.GetMinsMaxs( offset[0], offset[1] ); + + idVec3 gravity( 0.0f, 0.0f, + -( game->IsMultiplayer() ? cvarSystem->GetCVarFloat( "g_mp_gravity" ) + : cvarSystem->GetCVarFloat( "g_gravity" ) ) ); + acceleration[0] -= gravity * mGravity[0]; + acceleration[1] += gravity * mGravity[1]; + + idBounds result; + result.Zero(); + for ( int sample = 0; sample < 8; ++sample ) { + const float time = sample * mDuration[1] * 0.125f; + for ( int positionSide = 0; positionSide < 2; ++positionSide ) { + for ( int velocitySide = 0; velocitySide < 2; ++velocitySide ) { + for ( int accelerationSide = 0; accelerationSide < 2; ++accelerationSide ) { + for ( int frictionSide = 0; frictionSide < 2; ++frictionSide ) { + idVec3 position; + EvaluateSimplePosition( position, time, mDuration[1], + spawnPosition[positionSide], velocity[velocitySide], + acceleration[accelerationSide], friction[frictionSide] ); + result.AddPoint( position ); + } + } + } + } + } + + const float minLength = result[0].Length() * 0.5f; + const float maxLength = result[1].Length() * 0.5f; + return maxLength > minLength ? maxLength : minLength; +} + +float rvSegmentTemplate::CalculateBounds( void ) { + switch ( mSegType ) { + case SEG_EMITTER: + case SEG_SPAWNER: + case SEG_LIGHT: { + const float maxSize = mParticleTemplate.GetMaxSize(); + const float maxDistance = mParticleTemplate.GetFurthestDistance(); + float maxLength = 0.0f; + if ( mParticleTemplate.GetType() == PTYPE_LINE || + mParticleTemplate.GetType() == PTYPE_ELECTRICITY ) { + maxLength = mParticleTemplate.GetMaxLength(); + } + return mParticleTemplate.GetMaxOffset() + maxLength + maxDistance + maxSize; + } + + case SEG_DECAL: + return mParticleTemplate.GetMaxSize(); + + default: + return 8.0f; + } +} + +float rvDeclEffect::CalculateBounds( void ) { + float size = 0.0f; + for ( int i = 0; i < mSegmentTemplates.Num(); ++i ) { + const float segmentSize = mSegmentTemplates[i].CalculateBounds(); + if ( segmentSize > size ) { + size = segmentSize; + } + } + return ceilf( size ); +} diff --git a/src/bse/BSE_Effect.cpp b/src/bse/BSE_Effect.cpp new file mode 100644 index 0000000..8b7efbf --- /dev/null +++ b/src/bse/BSE_Effect.cpp @@ -0,0 +1,355 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" +#include "../renderer/tr_local.h" +#include "../renderer/Model_local.h" + +// Retail compilation unit: BSE_Effect.obj. Implementations are recovered here. + +idSoundEmitter *rvBSE::GetReferenceSound( int worldId ) const { + return soundSystem->EmitterForIndex( worldId, mReferenceSoundHandle ); +} + +float rvBSE::GetAttenuation( rvSegmentTemplate *segmentTemplate ) const { + if ( ( segmentTemplate->mAttenuation.x <= 0.0f && + segmentTemplate->mAttenuation.y <= 0.0f ) || + segmentTemplate->mAttenuation.x + 1.0f > mShortestDistanceToCamera ) { + return mAttenuation; + } + if ( segmentTemplate->mAttenuation.y - 1.0f >= mShortestDistanceToCamera ) { + return ( 1.0f - ( mShortestDistanceToCamera - + segmentTemplate->mAttenuation.x ) / + ( segmentTemplate->mAttenuation.y - segmentTemplate->mAttenuation.x ) ) * + mAttenuation; + } + return 0.0f; +} + +float rvBSE::GetOriginAttenuation( rvSegmentTemplate *segmentTemplate ) const { + if ( ( segmentTemplate->mAttenuation.x <= 0.0f && + segmentTemplate->mAttenuation.y <= 0.0f ) || + segmentTemplate->mAttenuation.x + 1.0f > mOriginDistanceToCamera ) { + return segmentTemplate->mScale * mAttenuation; + } + if ( segmentTemplate->mAttenuation.y - 1.0f >= mOriginDistanceToCamera ) { + return ( 1.0f - ( mOriginDistanceToCamera - + segmentTemplate->mAttenuation.x ) / + ( segmentTemplate->mAttenuation.y - segmentTemplate->mAttenuation.x ) ) * + segmentTemplate->mScale * mAttenuation; + } + return 0.0f; +} + +void rvBSE::UpdateSoundEmitter( rvSegmentTemplate *segmentTemplate, + rvSegment *segment ) { + soundShaderParms_t parms; + memset( &parms, 0, sizeof( parms ) ); + idSoundEmitter *emitter = GetReferenceSound( 1 ); + if ( emitter == NULL ) { + return; + } + if ( mFlags & EFLAG_STOPPED ) { + if ( segmentTemplate->GetSoundLooping() && segment->GetSoundPlaying() ) { + emitter->StopSound( segment->GetSoundChannel() ); + } + } else { + parms.volume = segment->mSoundVolume; + parms.frequencyShift = segment->mFreqShift; + emitter->UpdateEmitter( mCurrentOrigin, mCurrentVelocity, 0, &parms ); + } +} + +const idVec3 rvBSE::GetInterpolatedOffset( float time ) const { + idVec3 result = vec3_origin; + const float delta = mCurrentTime - mLastTime; + if ( delta >= BSE_TIME_EPSILON ) { + result = ( mCurrentOrigin - mLastOrigin ) * + ( 1.0f - ( time - mLastTime ) / delta ); + } + return result; +} + +void rvBSE::SetDuration( float time ) { + if ( time > mDuration ) { + mDuration = time; + } +} + +const char *rvBSE::GetDeclName( void ) { + return mDeclEffect->GetName(); +} + +void rvBSE::UpdateFromOwner( renderEffect_t *parms, float time, bool init ) { + mLastTime = mCurrentTime; + mLastOrigin = mCurrentOrigin; + mCurrentTime = time; + mCurrentOrigin = parms->origin; + mCurrentAxis = parms->axis; + mCurrentAxisTransposed = mCurrentAxis.Transpose(); + + const float timeDelta = mCurrentTime - mLastTime; + if ( timeDelta > BSE_TIME_EPSILON ) { + mCurrentVelocity = ( mCurrentOrigin - mLastOrigin ) / timeDelta; + } + + mGravity = parms->gravity; + mGravityDir = mGravity; + mGravityDir.Normalize(); + + const float size = mDeclEffect->GetSize(); + mCurrentWorldBounds.AddPoint( mCurrentOrigin + idVec3( size, size, size ) ); + mCurrentWorldBounds.AddPoint( mCurrentOrigin - idVec3( size, size, size ) ); + + if ( GetHasEndOrigin() && mDeclEffect->GetUsesEndOrigin() && + ( init || mCurrentEndOrigin != parms->endOrigin || + mCurrentOrigin != mLastOrigin ) ) { + mCurrentEndOrigin = parms->endOrigin; + mCurrentWorldBounds.AddPoint( mCurrentEndOrigin + idVec3( size, size, size ) ); + mCurrentWorldBounds.AddPoint( mCurrentEndOrigin - idVec3( size, size, size ) ); + SetEndOriginChanged( true ); + } + + mCurrentLocalBounds.Clear(); + for ( int corner = 0; corner < 8; ++corner ) { + idVec3 point( mCurrentWorldBounds[( corner & 1 ) != 0].x, + mCurrentWorldBounds[( corner & 2 ) != 0].y, + mCurrentWorldBounds[( corner & 4 ) != 0].z ); + point -= mCurrentOrigin; + point *= mCurrentAxisTransposed; + mCurrentLocalBounds.AddPoint( point ); + } + + mTint.Set( parms->shaderParms[SHADERPARM_RED], + parms->shaderParms[SHADERPARM_GREEN], + parms->shaderParms[SHADERPARM_BLUE], + parms->shaderParms[SHADERPARM_ALPHA] ); + mBrightness = parms->shaderParms[SHADERPARM_BRIGHTNESS]; + mSpriteSize.Set( parms->shaderParms[SHADERPARM_SPRITE_WIDTH], + parms->shaderParms[SHADERPARM_SPRITE_HEIGHT] ); + mAttenuation = parms->attenuation; +} + +void rvBSE::UpdateAttenuation( void ) { + if ( !mDeclEffect->GetAttenuates() ) { + return; + } + + idVec3 viewOrigin; + idMat3 viewAxis; + game->GetPlayerView( viewOrigin, viewAxis ); + mOriginDistanceToCamera = idMath::ClampFloat( 1.0f, 131072.0f, + mCurrentOrigin.Dist( viewOrigin ) ); + idVec3 localOrigin = viewOrigin - mCurrentOrigin; + localOrigin *= mCurrentAxis; + mShortestDistanceToCamera = idMath::ClampFloat( 1.0f, 131072.0f, + mCurrentLocalBounds.ShortestDistance( localOrigin ) ); +} + +void rvBSE::LoopInstant( float time ) { + if ( mDuration != 0.0f ) { + return; + } + mStartTime += mDeclEffect->GetMaxDuration() + 0.5f; + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].ResetTime( this, mStartTime ); + } + if ( bse_debug.GetInteger() == 2 ) { + common->Printf( "BSE: Looping duration: %g\n", + mDeclEffect->GetMaxDuration() + 0.5f ); + } + mDeclEffect->IncLoopCount(); +} + +void rvBSE::LoopLooping( float time ) { + if ( mDuration == 0.0f ) { + return; + } + mStartTime += mDuration; + mDuration = 0.0f; + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].ResetTime( this, mStartTime ); + } + if ( bse_debug.GetInteger() == 2 ) { + common->Printf( "BSE: Looping duration: %g\n", mDuration ); + } + mDeclEffect->IncLoopCount(); +} + +bool rvBSE::Service( renderEffect_t *parms, float time ) { + UpdateFromOwner( parms, time ); + UpdateAttenuation(); + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].Check( this, time ); + } + if ( !GetStopped() && parms->loop && mStartTime + mDuration < time ) { + LoopLooping( time ); + } + + bool active = false; + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + if ( mSegments[segment].UpdateParticles( this, time ) ) { + active = true; + } + } + SetEndOriginChanged( false ); + if ( GetStopped() ) { + return !active; + } + if ( parms->loop ) { + if ( mStartTime + mDuration < time ) { + LoopInstant( time ); + } + return false; + } + return mStartTime + mDuration < time; +} + +float rvBSE::EvaluateCost( int segment ) { + if ( segment != -1 ) { + return mSegments[segment].EvaluateCost(); + } + mCost = 0.0f; + for ( int index = 0; index < mSegments.Num(); ++index ) { + mCost += mSegments[index].EvaluateCost(); + } + return mCost; +} + +void rvBSE::DisplayDebugInfo( const renderEffect_t *parms, + const viewDef_t *view, idBounds &bounds ) { + if ( bse_debug.GetInteger() != 0 ) { + const idStr text = va( "(%g) (%g) (%g)\n%s", mCost, + mDeclEffect->GetSize(), parms->shaderParms[SHADERPARM_BRIGHTNESS], + GetDeclName() ); + view->renderWorld->DebugAxis( parms->origin, parms->axis ); + view->renderWorld->DrawText( text.c_str(), parms->origin, 0.3f, + colorCyan, view->renderView.viewaxis, 1 ); + } + if ( bse_showBounds.GetInteger() != 0 ) { + idBounds geometryWorldBounds; + geometryWorldBounds.FromTransformedBounds( bounds, parms->origin, parms->axis ); + view->renderWorld->DebugBounds( colorGreen, mCurrentWorldBounds ); + if ( geometryWorldBounds[0].x < mCurrentWorldBounds[0].x || + geometryWorldBounds[0].y < mCurrentWorldBounds[0].y || + geometryWorldBounds[0].z < mCurrentWorldBounds[0].z || + geometryWorldBounds[1].x > mCurrentWorldBounds[1].x || + geometryWorldBounds[1].y > mCurrentWorldBounds[1].y || + geometryWorldBounds[1].z > mCurrentWorldBounds[1].z ) { + view->renderWorld->DebugBounds( colorRed, geometryWorldBounds ); + } + } +} + +idRenderModel *rvBSE::Render( const renderEffect_t *owner, + const viewDef_t *view ) { + if ( !bse_render.GetBool() ) { + return NULL; + } + + rvRenderModelBSE *model = new rvRenderModelBSE; + mViewAxis = view->renderView.viewaxis; + mViewOrg = view->renderView.vieworg; + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + if ( mSegments[segment].Active() ) { + mSegments[segment].Render( this, owner, model, view->floatTime ); + mSegments[segment].RenderTrail( this, owner, model, view->floatTime ); + } + } + DisplayDebugInfo( owner, view, model->bounds ); + return model; +} + +void rvBSE::Destroy( void ) { + idSoundEmitter *emitter = GetReferenceSound( 1 ); + if ( emitter != NULL ) { + emitter->StopSound( SCHANNEL_ANY ); + soundSystem->FreeSoundEmitter( 1, emitter->Handle(), true ); + } + mSegments.Clear(); +} + +void rvBSE::UpdateSegments( float time ) { + mSegments.SetNum( mDeclEffect->GetNumSegmentTemplates() ); + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].Init( this, mDeclEffect, segment, time ); + } + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].CalcCounts( this, time ); + } + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].InitParticles( this ); + } + + if ( GetLooping() && ( mFlags & BIT( 4 ) ) && mDuration != 0.0f ) { + while ( time - mDuration * 2.0f > mStartTime ) { + mStartTime += mDuration; + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].Advance( this ); + } + } + mCurrentTime -= mDuration * 2.0f; + mStartTime -= mDuration * 2.0f; + for ( int segment = 0; segment < mSegments.Num(); ++segment ) { + mSegments[segment].Rewind( this ); + } + } +} + +void rvBSE::Init( rvDeclEffect *declEffect, renderEffect_t *parms, float time ) { + mFlags = 0; + mDeclEffect = declEffect; + mStartTime = parms->startTime; + mLastTime = time; + mDuration = 0.0f; + mAttenuation = 1.0f; + mCost = 0.0f; + SetLooping( parms->loop ); + SetFlag( parms->ambient, BIT( 4 ) ); + + mCurrentLocalBounds.Zero(); + mCurrentLocalBounds.ExpandSelf( declEffect->GetSize() ); + mOriginalOrigin = parms->origin; + mOriginalEndOrigin.Zero(); + mOriginalAxis = parms->axis; + mCurrentWorldBounds = mCurrentLocalBounds; + mCurrentWorldBounds.TranslateSelf( mOriginalOrigin ); + if ( parms->hasEndOrigin ) { + SetHasEndOrigin( true ); + mOriginalEndOrigin = parms->endOrigin; + mCurrentEndOrigin = parms->endOrigin; + } + mCurrentTime = time; + mCurrentOrigin = mOriginalOrigin; + mCurrentVelocity.Zero(); + UpdateFromOwner( parms, time, true ); + mReferenceSoundHandle = parms->referenceSoundHandle; + UpdateSegments( time ); + mOriginDistanceToCamera = 0.0f; + mShortestDistanceToCamera = 0.0f; + mSpriteSize.Zero(); +} diff --git a/src/bse/BSE_EffectTemplate.cpp b/src/bse/BSE_EffectTemplate.cpp new file mode 100644 index 0000000..7bc141e --- /dev/null +++ b/src/bse/BSE_EffectTemplate.cpp @@ -0,0 +1,354 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" + +// Retail compilation unit: BSE_EffectTemplate.obj. Implementations are recovered here. + +const char *rvDeclEffect::DefaultDefinition( void ) const { + return "{\n}\n"; +} + +void rvDeclEffect::SetMinDuration( float duration ) { + if ( duration > mMinDuration ) { + mMinDuration = duration; + } +} + +void rvDeclEffect::SetMaxDuration( float duration ) { + if ( duration > mMaxDuration ) { + mMaxDuration = duration; + } +} + +bool rvDeclEffect::Validate( const char *text, int textLength, + idStr &report ) const { + if ( textLength == 0 ) { + return true; + } + idDecl *decl = declManager->AllocateDecl( DECL_EFFECT ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl->base; + delete decl; + (void)report; + return valid; +} + +void rvDeclEffect::DeleteEditorOriginal( void ) { + delete mEditorOriginal; + mEditorOriginal = NULL; +} + +rvParticleTemplate::rvParticleTemplate( void ) { +} + +rvSegmentTemplate::rvSegmentTemplate( void ) { + declEffectEdit->Init( this, NULL ); + SetEnabled( true ); +} + +bool rvDeclEffect::SetDefaultText( void ) { + char generated[1024]; + idStr::snPrintf( generated, sizeof( generated ), + "effect %s // IMPLICITLY GENERATED\n%s", GetName(), DefaultDefinition() ); + SetText( generated ); + return true; +} + +rvSegmentTemplate *rvDeclEffect::GetSegmentTemplate( const char *name ) { + for ( int index = mSegmentTemplates.Num() - 1; index >= 0; --index ) { + if ( mSegmentTemplates[index].GetSegmentName().Icmp( name ) == 0 ) { + return &mSegmentTemplates[index]; + } + } + return NULL; +} + +rvSegmentTemplate *rvDeclEffect::GetSegmentTemplate( int index ) { + if ( index >= mSegmentTemplates.Num() ) { + return NULL; + } + return &mSegmentTemplates[index]; +} + +bool rvDeclEffect::Compare( const rvDeclEffect &other ) const { + if ( mSegmentTemplates.Num() != other.mSegmentTemplates.Num() ) { + return false; + } + for ( int index = 0; index < mSegmentTemplates.Num(); ++index ) { + if ( !mSegmentTemplates[index].Compare( other.mSegmentTemplates[index] ) ) { + return false; + } + } + return true; +} + +int rvDeclEffect::GetTrailSegmentIndex( const idStr &name ) { + for ( int index = 0; index < mSegmentTemplates.Num(); ++index ) { + if ( mSegmentTemplates[index].GetSegmentName().Icmp( name ) == 0 ) { + return index; + } + } + common->Warning( "^4BSE:^1 Unable to find segment '%s'", name.c_str() ); + return -1; +} + +size_t rvDeclEffect::Size( void ) const { + return sizeof( *this ) + mSegmentTemplates.Allocated(); +} + +bool rvDeclEffect::CompareToEditorOriginal( void ) { + return Compare( *mEditorOriginal ); +} + +rvParticleTemplate &rvParticleTemplate::operator=( + const rvParticleTemplate © ) { + if ( this == © ) { + return *this; + } + mFlags = copy.mFlags; + mType = copy.mType; + mMaterial = copy.mMaterial; + mMaterialName = copy.mMaterialName; + mModelName = copy.mModelName; + mEntityDefName = copy.mEntityDefName; + mTraceModelIndex = copy.mTraceModelIndex; + mGravity = copy.mGravity; + mSoundVolume = copy.mSoundVolume; + mFreqShift = copy.mFreqShift; + mTiling = copy.mTiling; + mTrailType = copy.mTrailType; + mTrailTypeName = copy.mTrailTypeName; + mTrailMaterial = copy.mTrailMaterial; + mTrailMaterialName = copy.mTrailMaterialName; + mTrailTime = copy.mTrailTime; + mTrailCount = copy.mTrailCount; + mBounce = copy.mBounce; + mDuration = copy.mDuration; + mCentre = copy.mCentre; + mNumForks = copy.mNumForks; + mForkSizeMins = copy.mForkSizeMins; + mForkSizeMaxs = copy.mForkSizeMaxs; + mJitterSize = copy.mJitterSize; + mJitterRate = copy.mJitterRate; + mJitterTable = copy.mJitterTable; + mNumSizeParms = copy.mNumSizeParms; + mNumRotateParms = copy.mNumRotateParms; + mVertexCount = copy.mVertexCount; + mIndexCount = copy.mIndexCount; + mSpawnPosition = copy.mSpawnPosition; + mSpawnDirection = copy.mSpawnDirection; + mSpawnVelocity = copy.mSpawnVelocity; + mSpawnAcceleration = copy.mSpawnAcceleration; + mSpawnFriction = copy.mSpawnFriction; + mSpawnTint = copy.mSpawnTint; + mSpawnFade = copy.mSpawnFade; + mSpawnSize = copy.mSpawnSize; + mSpawnRotate = copy.mSpawnRotate; + mSpawnAngle = copy.mSpawnAngle; + mSpawnOffset = copy.mSpawnOffset; + mSpawnLength = copy.mSpawnLength; + mTintEnvelope = copy.mTintEnvelope; + mFadeEnvelope = copy.mFadeEnvelope; + mSizeEnvelope = copy.mSizeEnvelope; + mRotateEnvelope = copy.mRotateEnvelope; + mAngleEnvelope = copy.mAngleEnvelope; + mOffsetEnvelope = copy.mOffsetEnvelope; + mLengthEnvelope = copy.mLengthEnvelope; + mDeathTint = copy.mDeathTint; + mDeathFade = copy.mDeathFade; + mDeathSize = copy.mDeathSize; + mDeathRotate = copy.mDeathRotate; + mDeathAngle = copy.mDeathAngle; + mDeathOffset = copy.mDeathOffset; + mDeathLength = copy.mDeathLength; + mNumImpactEffects = copy.mNumImpactEffects; + mNumTimeoutEffects = copy.mNumTimeoutEffects; + for ( int effect = 0; effect < BSE_NUM_SPAWNABLE; ++effect ) { + mImpactEffects[effect] = copy.mImpactEffects[effect]; + mTimeoutEffects[effect] = copy.mTimeoutEffects[effect]; + } + return *this; +} + +rvSegmentTemplate &rvSegmentTemplate::operator=( + const rvSegmentTemplate © ) { + if ( this == © ) { + return *this; + } + mDeclEffect = copy.mDeclEffect; + mSegmentName = copy.mSegmentName; + mFlags = copy.mFlags; + mSegType = copy.mSegType; + mLocalStartTime = copy.mLocalStartTime; + mLocalDuration = copy.mLocalDuration; + mAttenuation = copy.mAttenuation; + mParticleCap = copy.mParticleCap; + mScale = copy.mScale; + mDetail = copy.mDetail; + mParticleTemplate = copy.mParticleTemplate; + mCount = copy.mCount; + mDensity = copy.mDensity; + mTrailSegmentIndex = copy.mTrailSegmentIndex; + mNumEffects = copy.mNumEffects; + for ( int effect = 0; effect < BSE_NUM_SPAWNABLE; ++effect ) { + mEffects[effect] = copy.mEffects[effect]; + } + mSoundShader = copy.mSoundShader; + mSoundVolume = copy.mSoundVolume; + mFreqShift = copy.mFreqShift; + mBSEEffect = copy.mBSEEffect; + return *this; +} + +void rvDeclEffect::FreeData( void ) { + mSegmentTemplates.Clear(); +} + +void rvDeclEffect::Init( void ) { + mEditorOriginal = NULL; + mFlags = 0; + mMinDuration = 0.0f; + mMaxDuration = 0.0f; + mSize = BSE_LARGEST; + mPlayCount = 0; + mLoopCount = 0; + mSegmentTemplates.Clear(); +} + +void rvDeclEffect::CopyData( const rvDeclEffect © ) { + mFlags = copy.mFlags; + mSegmentTemplates = copy.mSegmentTemplates; + mSize = copy.mSize; + mMinDuration = copy.mMinDuration; + mMaxDuration = copy.mMaxDuration; +} + +rvDeclEffect::rvDeclEffect( const rvDeclEffect © ) { + Init(); + CopyData( copy ); +} + +void rvDeclEffect::Finish( void ) { + mFlags &= ~ETFLAG_HAS_SOUND; + mMinDuration = 0.0f; + mMaxDuration = 0.0f; + const int segmentCount = mSegmentTemplates.Num(); + mSegmentTemplates.Resize( segmentCount ); + mSegmentTemplates.SetNum( segmentCount, false ); + for ( int index = 0; index < mSegmentTemplates.Num(); ++index ) { + rvSegmentTemplate &segment = mSegmentTemplates[index]; + segment.Finish( this ); + if ( segment.GetType() == SEG_SOUND ) { + mFlags |= ETFLAG_HAS_SOUND; + } + if ( segment.GetParticleTemplate()->UsesEndOrigin() ) { + mFlags |= ETFLAG_USES_ENDORIGIN; + } + if ( segment.GetAttenuateEmitter() ) { + mFlags |= ETFLAG_ATTENUATES; + } + segment.EvaluateTrailSegment( this ); + segment.SetMinDuration( this ); + segment.SetMaxDuration( this ); + } + mSize = CalculateBounds(); +} + +void rvDeclEffect::CreateEditorOriginal( void ) { + delete mEditorOriginal; + mEditorOriginal = new rvDeclEffect( *this ); +} + +void rvDeclEffect::Revert( void ) { + CopyData( *mEditorOriginal ); + CreateEditorOriginal(); +} + +bool rvDeclEffect::Parse( const char *text, const int textLength, + bool noCaching ) { + (void)noCaching; + idLexer lexer; + idToken token; + lexer.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + lexer.SetFlags( DECL_LEXER_FLAGS ); + lexer.SkipUntilString( "{" ); + if ( !lexer.ReadToken( &token ) ) { + return false; + } + + rvSegmentTemplate segment; + while ( token.Cmp( "}" ) != 0 ) { + segment.Init( this ); + if ( token.Icmp( "size" ) == 0 ) { + mSize = lexer.ParseFloat(); + } else { + int type = SEG_NONE; + if ( token.Icmp( "effect" ) == 0 ) { + type = SEG_EFFECT; + } else if ( token.Icmp( "emitter" ) == 0 ) { + type = SEG_EMITTER; + } else if ( token.Icmp( "spawner" ) == 0 ) { + type = SEG_SPAWNER; + } else if ( token.Icmp( "trail" ) == 0 ) { + type = SEG_TRAIL; + } else if ( token.Icmp( "sound" ) == 0 ) { + type = SEG_SOUND; + } else if ( token.Icmp( "decal" ) == 0 ) { + type = SEG_DECAL; + } else if ( token.Icmp( "light" ) == 0 ) { + type = SEG_LIGHT; + } else if ( token.Icmp( "delay" ) == 0 ) { + type = SEG_DELAY; + } else if ( token.Icmp( "doubleVision" ) == 0 ) { + type = SEG_DOUBLEVISION; + } else if ( token.Icmp( "shake" ) == 0 ) { + type = SEG_SHAKE; + } else if ( token.Icmp( "tunnel" ) == 0 ) { + type = SEG_TUNNEL; + } + + if ( type == SEG_NONE ) { + common->Warning( + "^4BSE:^1 Invalid segment type '%s' (file: %s, line: %d)", + token.c_str(), lexer.GetFileName(), lexer.GetLineNum() ); + lexer.SkipBracedSection( true ); + } else { + segment.Parse( this, type, &lexer ); + if ( segment.Finish( this ) ) { + mSegmentTemplates.Append( segment ); + } + } + } + if ( !lexer.ReadToken( &token ) ) { + return false; + } + } + Finish(); + return true; +} diff --git a/src/bse/BSE_Electricity.cpp b/src/bse/BSE_Electricity.cpp new file mode 100644 index 0000000..c9837e9 --- /dev/null +++ b/src/bse/BSE_Electricity.cpp @@ -0,0 +1,264 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" + +/* +=============================================================================== + + Electricity particle rendering + + The work structure layout and all method ownership/signatures come from + BSE_Electricity.obj in quake4.pdb. The bodies follow the address-matched + retail executable. + +=============================================================================== +*/ + +struct SElecWork { + float fraction; + float step; + idVec4 tint; + float alpha; + float size; + idVec3 length; + idVec3 forward; + idVec3 viewPos; + srfTriangles_t *tri; + idVec3 coords[BSE_ELEC_MAX_BOLTS]; +}; + +assert_sizeof( SElecWork, 2472 ); + +int rvElectricityParticle::GetBoltCount( float length ) { + return idMath::ClampInt( 3, BSE_ELEC_MAX_BOLTS, + static_cast( idMath::Ceil( length * ( 1.0f / 16.0f ) ) ) ); +} + +int rvElectricityParticle::Update( float time ) { + idVec3 length; + EvaluateLength( time - mStartTime, length ); + mNumBolts = GetBoltCount( length.Length() ); + return mNumBolts; +} + +void rvElectricityParticle::RenderLineSegment( const rvBSE *effect, + SElecWork *work, idVec3 start, float startFraction ) { + idDrawVert *vertices = &work->tri->verts[work->tri->numVerts]; + const dword color = HandleTint( effect, work->tint, work->alpha ); + idVec3 offset = work->length.Cross( work->viewPos ); + offset.Normalize(); + offset *= work->size; + const float textureFraction = + startFraction * work->step + work->fraction; + + vertices[0].xyz = start + offset; + vertices[0].SetColor( color ); + vertices[0].st.Set( textureFraction, 0.0f ); + vertices[1].xyz = start - offset; + vertices[1].SetColor( color ); + vertices[1].st.Set( textureFraction, 1.0f ); + work->tri->numVerts += 2; +} + +void rvElectricityParticle::ApplyShape( const rvBSE *effect, + SElecWork *work, idVec3 start, idVec3 end, int count, + float startFraction, float endFraction ) { + int nextCount = count - 1; + while ( count >= 1 ) { + const float downScale = rvRandom::flrand( 0.05f, 0.09f ); + const float leftScale = rvRandom::flrand( 0.05f, 0.09f ); + const float firstFraction = rvRandom::flrand( 0.56f, 0.76f ); + const float firstDown = rvRandom::flrand( + -downScale - 0.02f, 0.02f - downScale ); + const float secondDown = rvRandom::flrand( + -leftScale - 0.02f, 0.02f - leftScale ); + const float secondFraction = rvRandom::flrand( 0.23f, 0.43f ); + + idVec3 forward = end - start; + const float shapeLength = forward.Normalize() * 0.7f; + idVec3 left( -forward.y, forward.x, 0.0f ); + if ( left.Normalize() == 0.0f ) { + left.Set( 1.0f, 0.0f, 0.0f ); + } + const idVec3 down = left.Cross( forward ); + const idVec3 point1 = start * firstFraction + + end * ( 1.0f - firstFraction ) + + left * ( leftScale * shapeLength ) + + down * ( downScale * shapeLength ); + const idVec3 point2 = start * secondFraction + + end * ( 1.0f - secondFraction ) + + left * ( secondDown * shapeLength ) + + down * ( firstDown * shapeLength ); + + const float fraction1 = startFraction * ( 2.0f / 3.0f ) + + endFraction * ( 1.0f / 3.0f ); + ApplyShape( effect, work, start, point1, nextCount, + startFraction, fraction1 ); + const float fraction2 = startFraction * ( 1.0f / 3.0f ) + + endFraction * ( 2.0f / 3.0f ); + ApplyShape( effect, work, point1, point2, nextCount, + fraction1, fraction2 ); + start = point2; + startFraction = fraction2; + --count; + --nextCount; + } + RenderLineSegment( effect, work, start, startFraction ); +} + +void rvElectricityParticle::RenderBranch( const rvBSE *effect, + SElecWork *work, idVec3 start, idVec3 end ) { + work->forward.Normalize(); + idVec3 left( -work->forward.y, work->forward.x, 0.0f ); + if ( left.Normalize() == 0.0f ) { + left.Set( 1.0f, 0.0f, 0.0f ); + } + const idVec3 down = left.Cross( work->forward ); + float fraction = work->step; + const int firstVertex = work->tri->numVerts; + idVec3 old = start; + idVec3 offset( 0.0f, 0.0f, 0.0f ); + int count = 0; + bool evaluate = true; + while ( true ) { + if ( count < BSE_ELEC_MAX_BOLTS ) { + work->coords[count] = old; + } + ++count; + if ( fraction >= 1.0f - work->step * 0.5f ) { + fraction = 1.0f; + evaluate = false; + } + offset += work->forward * rvRandom::flrand( + -mJitterSize.x, mJitterSize.x ); + offset += left * rvRandom::flrand( -mJitterSize.y, mJitterSize.y ); + offset += down * rvRandom::flrand( -mJitterSize.z, mJitterSize.z ); + const float jitter = mJitterTable != NULL + ? mJitterTable->TableLookup( fraction ) : 1.0f; + const idVec3 current = start + ( end - start ) * fraction + + offset * jitter; + work->fraction = fraction - work->step; + ApplyShape( effect, work, old, current, 2, 0.0f, 1.0f ); + old = current; + fraction += work->step; + if ( !evaluate ) { + if ( count < BSE_ELEC_MAX_BOLTS ) { + work->coords[count] = current; + } + RenderLineSegment( effect, work, current, 1.0f ); + break; + } + } + + int index = work->tri->numIndexes; + for ( int vertex = firstVertex; vertex < work->tri->numVerts - 2; + vertex += 2 ) { + work->tri->indexes[index++] = vertex; + work->tri->indexes[index++] = vertex + 1; + work->tri->indexes[index++] = vertex + 3; + work->tri->indexes[index++] = vertex; + work->tri->indexes[index++] = vertex + 3; + work->tri->indexes[index++] = vertex + 2; + } + work->tri->numIndexes = index; +} + +void rvElectricityParticle::SetupElectricity( rvParticleTemplate *particleTemplate ) { + mNumForks = particleTemplate->mNumForks; + mSeed = rvRandom::Init(); + mForkSizeMins = particleTemplate->mForkSizeMins; + mForkSizeMaxs = particleTemplate->mForkSizeMaxs; + mJitterSize = particleTemplate->mJitterSize; + mLastJitter = 0.0f; + mJitterRate = particleTemplate->mJitterRate; + mJitterTable = particleTemplate->mJitterTable; +} + +bool rvElectricityParticle::Render( const rvBSE *effect, const idMat3 &view, + srfTriangles_t *tri, float time, float override ) { + float evaluationTime; + if ( !GetEvaluationTime( time, evaluationTime ) ) { + return false; + } + + SElecWork work; + EvaluateTint( evaluationTime, work.tint ); + EvaluateSize( evaluationTime, &work.size ); + EvaluateLength( evaluationTime, work.length ); + idVec3 position; + EvaluatePosition( effect, position, time - mMotionStartTime ); + if ( !GetLocked() ) { + work.length *= mInitAxis * effect->GetCurrentAxis(); + } + if ( GetGeneratedLine() ) { + idVec3 velocity; + EvaluateVelocity( effect, velocity, time - mMotionStartTime ); + velocity.Normalize(); + work.length = velocity * work.length.Length(); + } + const float mainLength = work.length.Length(); + if ( mainLength < 0.1f ) { + return false; + } + + const int savedSeed = rvRandom::irand( 0, 0x7FFF ); + if ( mLastJitter + mJitterRate <= time ) { + mLastJitter = time; + mSeed = rvRandom::Init(); + } + rvRandom::Init( mSeed ); + work.step = mTextureScale / static_cast( mNumBolts ); + work.viewPos = view[0]; + work.tri = tri; + work.alpha = override; + work.forward = work.length; + const idVec3 end = position + work.length; + RenderBranch( effect, &work, position, end ); + + const int forkCount = Min( mNumForks, 16 ); + idVec3 forkPoints[16]; + for ( int i = 0; i < forkCount; ++i ) { + const int bolt = rvRandom::irand( 1, mNumBolts - 1 ); + forkPoints[i] = work.coords[bolt]; + } + for ( int i = 0; i < forkCount; ++i ) { + const idVec3 &forkStart = forkPoints[i]; + idVec3 forkEnd = ( position + forkStart + work.length ) * 0.5f; + forkEnd.x += rvRandom::flrand( mForkSizeMins.x, mForkSizeMaxs.x ); + forkEnd.y += rvRandom::flrand( mForkSizeMins.y, mForkSizeMaxs.y ); + forkEnd.z += rvRandom::flrand( mForkSizeMins.z, mForkSizeMaxs.z ); + work.forward = forkEnd - forkStart; + const float forkLength = work.forward.Length(); + if ( forkLength > 1.0f && forkLength < mainLength ) { + work.step = 1.0f / static_cast( GetBoltCount( forkLength ) ); + RenderBranch( effect, &work, forkStart, forkEnd ); + } + } + rvRandom::Init( savedSeed ); + return true; +} diff --git a/src/bse/BSE_Envelope.h b/src/bse/BSE_Envelope.h new file mode 100644 index 0000000..785a6cf --- /dev/null +++ b/src/bse/BSE_Envelope.h @@ -0,0 +1,238 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2007 Id Software, Inc. +// + + +#ifndef _BSE_ENVELOPE_H_INC_ +#define _BSE_ENVELOPE_H_INC_ + +#include "../framework/DeclTable.h" + +class rvEnvParms +{ +public: + friend class rvEnvParms1; + friend class rvEnvParms2; + friend class rvEnvParms3; + + rvEnvParms( void ) { Init(); } + rvEnvParms( const rvEnvParms © ) { *this = copy; } + ~rvEnvParms( void ) {} + + void SetDefaultType( void ); + void SetType( const char *type ) { mTable = declManager->FindTable( type ); } + void SetType( const idDeclTable *tabl ) { mTable = tabl; } + + idVec3 &GetOffsetRef( void ) { return( mEnvOffset ); } + const idVec3 &GetOffsetRef( void ) const { return( mEnvOffset ); } + idVec3 &GetRateRef( void ) { return( mRate ); } + const idVec3 &GetRateRef( void ) const { return( mRate ); } + const idDeclTable *GetType( void ) const { return mTable; } + void SetIsCount( bool isCount ) { mIsCount = isCount; } + + bool GetIsCount( void ) const { return( mIsCount != 0 ); } + void CalcRate( float *rate, float duration, int count ); + + void Init( void ); + bool GetMinMax( float &min, float &max ); + + void Evaluate3( const float time, const float *start, const float *rate, const float *end, float *dest ); + void Evaluate( class rvEnvParms1Particle &env, float time, float oneOverDuration, float *v ); + void Evaluate( class rvEnvParms2Particle &env, float time, float oneOverDuration, float *v ); + void Evaluate( class rvEnvParms3Particle &env, float time, float oneOverDuration, float *v ); + + void operator= ( const rvEnvParms © ) { + mTable = copy.mTable; + mIsCount = copy.mIsCount; + mEnvOffset = copy.mEnvOffset; + mRate = copy.mRate; + } + bool operator== ( const rvEnvParms &comp ) const { return( Compare( comp ) ); } + bool operator!= ( const rvEnvParms &comp ) const { return( !Compare( comp ) ); } + +private: + bool Compare( const rvEnvParms &comp ) const; + + const idDeclTable *mTable; + bool mIsCount; + idVec3 mEnvOffset; + idVec3 mRate; +}; + +class rvEnvParms1 +{ +public: + friend class rvEnvParms; + + rvEnvParms1( void ) {} + ~rvEnvParms1( void ) {} + + void Init( const rvEnvParms & copy, float duration ); + void Evaluate( const float time, float *dest ); + + float *GetStart( void ) { return( &mStart ); } + float *GetEnd( void ) { return( &mEnd ); } + + void Scale( const float constant ) { mStart *= constant; mEnd *= constant; } + void Transform( const idVec3 normal ) {} + void Rotate( const rvAngles &angles ); +private: + const idDeclTable *mTable; + float mEnvOffset; + + float mStart; + float mRate; + float mEnd; +}; + +class rvEnvParms2 +{ +public: + friend class rvEnvParms; + + rvEnvParms2( void ) {} + ~rvEnvParms2( void ) {} + + void Init( const rvEnvParms & copy, float duration ); + void Evaluate( const float time, float *dest ); + + float *GetStart( void ) { return( mStart.ToFloatPtr() ); } + float *GetEnd( void ) { return( mEnd.ToFloatPtr() ); } + + void Scale( const float constant ) { mStart *= constant; mEnd *= constant; } + void Transform( const idVec3 normal ) {} + void Rotate( const rvAngles &angles ); +private: + const idDeclTable *mTable; + idVec2 mEnvOffset; + + bool mFixedRateAndOffset; + idVec2 mStart; + idVec2 mRate; + idVec2 mEnd; +}; + +class rvEnvParms3 +{ +public: + friend class rvEnvParms; + + rvEnvParms3( void ) {} + ~rvEnvParms3( void ) {} + + void Init( const rvEnvParms & copy, float duration ); + void Evaluate( const float time, float *dest ); + + float *GetStart( void ) { return( mStart.ToFloatPtr() ); } + float *GetEnd( void ) { return( mEnd.ToFloatPtr() ); } + + void Scale( const float constant ); + void Transform( const idVec3 normal ) { mStart *= normal.ToMat3(); mEnd *= normal.ToMat3(); } + void Rotate( const rvAngles &angles ); +private: + const idDeclTable *mTable; + idVec3 mEnvOffset; + + bool mFixedRateAndOffset; + idVec3 mStart; + idVec3 mRate; + idVec3 mEnd; +}; + +class rvEnvParms1Particle +{ +public: + friend class rvEnvParms; + friend class rvParticle; + + rvEnvParms1Particle( void ) {} + ~rvEnvParms1Particle( void ) {} + + void Init( const rvEnvParms & copy, float duration ); + void Evaluate( const rvEnvParms & params, const float time, float oneOverDuration, float *dest ); + + float *GetStart( void ) { return( &mStart ); } + float *GetEnd( void ) { return( &mEnd ); } + + void Scale( const float constant ) { mStart *= constant; mEnd *= constant; } + void Transform( const idVec3 normal ) {} + void Rotate( const rvAngles &angles ); +private: + float mStart; + float mEnd; +}; + +class rvEnvParms2Particle +{ +public: + friend class rvEnvParms; + friend class rvParticle; + + rvEnvParms2Particle( void ) {} + ~rvEnvParms2Particle( void ) {} + + void Init( const rvEnvParms & copy, float duration ); + void Evaluate( const rvEnvParms & params, const float time, float oneOverDuration, float *dest ); + + float *GetStart( void ) { return( mStart.ToFloatPtr() ); } + float *GetEnd( void ) { return( mEnd.ToFloatPtr() ); } + + void Scale( const float constant ) { mStart *= constant; mEnd *= constant; } + void Transform( const idVec3 normal ) {} + void Rotate( const rvAngles &angles ); +private: + idVec2 mStart; + idVec2 mEnd; +}; + +class rvEnvParms3Particle +{ +public: + friend class rvEnvParms; + friend class rvParticle; + friend class rvDebrisParticle; + friend class rvLineParticle; + friend class rvElectricityParticle; + + rvEnvParms3Particle( void ) {} + ~rvEnvParms3Particle( void ) {} + + void Init( const rvEnvParms & copy, float duration ); + void Evaluate( const rvEnvParms & params, const float time, float oneOverDuration, float *dest ); + + float *GetStart( void ) { return( mStart.ToFloatPtr() ); } + float *GetEnd( void ) { return( mEnd.ToFloatPtr() ); } + + void Scale( const float constant ) { mStart *= constant; mEnd *= constant; } + void Transform( const idMat3 &m ) { mStart *= m; mEnd *= m; } + void Transform( const idVec3 normal ) { idMat3 const m = normal.ToMat3(); mStart *= m; mEnd *= m; } + void Rotate( const rvAngles &angles ); +private: + idVec3 mStart; + idVec3 mEnd; +}; + +#endif // _BSE_ENVELOPE_H_INC_ diff --git a/src/bse/BSE_Envelopes.cpp b/src/bse/BSE_Envelopes.cpp new file mode 100644 index 0000000..1032dd5 --- /dev/null +++ b/src/bse/BSE_Envelopes.cpp @@ -0,0 +1,172 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "BSE_Envelope.h" + +/* +=============================================================================== + + Quake 4 BSE envelopes + + Compilation-unit boundary and signatures are taken from BSE_Envelopes.obj + in quake4.pdb. Control flow is reconstructed from the matching retail RVAs. + +=============================================================================== +*/ + +void rvEnvParms1::Evaluate( const float time, float *dest ) { + if ( mTable != NULL ) { + const float lookup = mTable->TableLookup( time * mRate + mEnvOffset ); + *dest = mStart + ( mEnd - mStart ) * lookup; + } else { + *dest = mStart; + } +} + +void rvEnvParms::SetDefaultType( void ) { + if ( mTable == NULL ) { + mTable = declManager->FindTable( "linear", true ); + mIsCount = true; + } +} + +void rvEnvParms2::Evaluate( const float time, float *dest ) { + if ( mTable == NULL ) { + dest[0] = mStart[0]; + dest[1] = mStart[1]; + return; + } + + float lookup = mTable->TableLookup( time * mRate[0] + mEnvOffset[0] ); + dest[0] = mStart[0] + ( mEnd[0] - mStart[0] ) * lookup; + if ( !mFixedRateAndOffset ) { + lookup = mTable->TableLookup( time * mRate[1] + mEnvOffset[1] ); + } + dest[1] = mStart[1] + ( mEnd[1] - mStart[1] ) * lookup; +} + +void rvEnvParms3::Evaluate( const float time, float *dest ) { + if ( mTable == NULL ) { + dest[0] = mStart[0]; + dest[1] = mStart[1]; + dest[2] = mStart[2]; + return; + } + + float lookup = mTable->TableLookup( time * mRate[0] + mEnvOffset[0] ); + dest[0] = mStart[0] + ( mEnd[0] - mStart[0] ) * lookup; + if ( mFixedRateAndOffset ) { + dest[1] = mStart[1] + ( mEnd[1] - mStart[1] ) * lookup; + } else { + lookup = mTable->TableLookup( time * mRate[1] + mEnvOffset[1] ); + dest[1] = mStart[1] + ( mEnd[1] - mStart[1] ) * lookup; + lookup = mTable->TableLookup( time * mRate[2] + mEnvOffset[2] ); + } + dest[2] = mStart[2] + ( mEnd[2] - mStart[2] ) * lookup; +} + +void rvEnvParms::Init( void ) { + mTable = NULL; + mIsCount = true; + mEnvOffset.Zero(); + mRate.Set( 1.0f, 1.0f, 1.0f ); +} + +void rvEnvParms1::Init( const rvEnvParms ©, float duration ) { + mTable = copy.mTable; + mEnvOffset = copy.mEnvOffset[0]; + mRate = copy.mIsCount ? copy.mRate[0] / duration : copy.mRate[0]; +} + +void rvEnvParms2::Init( const rvEnvParms ©, float duration ) { + mTable = copy.mTable; + mEnvOffset.Set( copy.mEnvOffset[0], copy.mEnvOffset[1] ); + if ( copy.mIsCount ) { + const float inverseDuration = 1.0f / duration; + mRate.Set( copy.mRate[0] * inverseDuration, copy.mRate[1] * inverseDuration ); + } else { + mRate.Set( copy.mRate[0], copy.mRate[1] ); + } + mFixedRateAndOffset = mRate[0] == mRate[1] && mEnvOffset[0] == mEnvOffset[1]; +} + +void rvEnvParms3::Init( const rvEnvParms ©, float duration ) { + mTable = copy.mTable; + mEnvOffset = copy.mEnvOffset; + if ( copy.mIsCount ) { + mRate = copy.mRate * ( 1.0f / duration ); + } else { + mRate = copy.mRate; + } + mFixedRateAndOffset = + mRate[0] == mRate[1] && mRate[0] == mRate[2] && + mEnvOffset[0] == mEnvOffset[1] && mEnvOffset[0] == mEnvOffset[2]; +} + +void rvEnvParms3::Rotate( const rvAngles &angles ) { + mStart[0] += angles.pitch; + mStart[1] += angles.yaw; + mStart[2] += angles.roll; + mEnd[0] += angles.pitch; + mEnd[1] += angles.yaw; + mEnd[2] += angles.roll; +} + +bool rvEnvParms::GetMinMax( float &min, float &max ) { + if ( mTable == NULL ) { + min = max = 0.0f; + return false; + } + min = mTable->GetMinValue(); + max = mTable->GetMaxValue(); + return true; +} + +bool rvEnvParms::Compare( const rvEnvParms &comp ) const { + return mTable == comp.mTable && + mIsCount == comp.mIsCount && + mRate == comp.mRate && + mEnvOffset == comp.mEnvOffset; +} + +void rvEnvParms::CalcRate( float *rate, float duration, int count ) { + for ( int i = 0; i < count; ++i ) { + rate[i] = mIsCount ? mRate[i] / duration : mRate[i]; + } +} + +void rvEnvParms::Evaluate3( const float time, const float *start, const float *rate, + const float *end, float *dest ) { + if ( mTable == NULL ) { + memcpy( dest, start, sizeof( float ) * 3 ); + return; + } + for ( int i = 0; i < 3; ++i ) { + const float lookup = mTable->TableLookup( time * rate[i] + mEnvOffset[i] ); + dest[i] = start[i] + ( end[i] - start[i] ) * lookup; + } +} diff --git a/src/bse/BSE_Light.cpp b/src/bse/BSE_Light.cpp new file mode 100644 index 0000000..5a892ec --- /dev/null +++ b/src/bse/BSE_Light.cpp @@ -0,0 +1,134 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" + +/* +=============================================================================== + + Quake 4 BSE dynamic lights + + Function ownership, signatures, and object layouts come from + BSE_Light.obj in quake4.pdb. Control flow is reconstructed from the + address-matched retail executable bodies. + +=============================================================================== +*/ + +bool rvLightParticle::Destroy( void ) { + if ( mLightDefHandle != -1 ) { + session->rw->FreeLightDef( mLightDefHandle ); + mLightDefHandle = -1; + } + return true; +} + +void rvSegment::InitLight( rvBSE *effect, rvSegmentTemplate *st, float time ) { + if ( mUsedHead == NULL ) { + SpawnParticle( effect, st, time, vec3_origin, mat3_identity ); + mUsedHead->InitLight( effect, st, time ); + } +} + +bool rvSegment::HandleLight( rvBSE *effect, rvSegmentTemplate *st, float time ) { + if ( mUsedHead == NULL ) { + return false; + } + + const bool infinite = st->GetInfiniteDuration(); + mUsedHead->PresentLight( effect, time, infinite ); + if ( infinite || !mUsedHead->Expired( time ) ) { + return false; + } + + mUsedHead->Destroy(); + mFreeHead = mUsedHead; + mUsedHead = NULL; + return true; +} + +bool rvLightParticle::InitLight( rvBSE *effect, rvSegmentTemplate *st, float time ) { + float evalTime; + if ( !GetEvaluationTime( time, evalTime, false ) ) { + return false; + } + + memset( &mLight, 0, sizeof( mLight ) ); + + idVec4 tint; + EvaluateTint( evalTime, tint ); + idVec3 size; + EvaluateSize( evalTime, size.ToFloatPtr() ); + + idVec3 position; + EvaluatePosition( effect, position, evalTime - mMotionStartTime ); + mLight.origin = effect->GetCurrentOrigin() + position * effect->GetCurrentAxis(); + mLight.lightRadius.Set( + size[0] < 1.0f ? 1.0f : size[0], + size[1] < 1.0f ? 1.0f : size[1], + size[2] < 1.0f ? 1.0f : size[2] ); + mLight.axis = effect->GetCurrentAxis(); + mLight.shaderParms[SHADERPARM_RED] = tint[0]; + mLight.shaderParms[SHADERPARM_GREEN] = tint[1]; + mLight.shaderParms[SHADERPARM_BLUE] = tint[2]; + + const rvParticleTemplate *particleTemplate = st->GetParticleTemplate(); + mLight.shader = particleTemplate->GetMaterial(); + mLight.pointLight = true; + mLight.detailLevel = 10.0f; + mLight.noShadows = !particleTemplate->GetShadows(); + mLight.noSpecular = !particleTemplate->GetSpecular(); + mLightDefHandle = session->rw->AddLightDef( &mLight ); + return true; +} + +bool rvLightParticle::PresentLight( rvBSE *effect, float time, bool infinite ) { + float evalTime; + if ( !GetEvaluationTime( time, evalTime, infinite ) ) { + return false; + } + + idVec4 tint; + EvaluateTint( evalTime, tint ); + idVec3 size; + EvaluateSize( evalTime, size.ToFloatPtr() ); + + idVec3 position; + EvaluatePosition( effect, position, evalTime - mMotionStartTime ); + mLight.origin = effect->GetCurrentOrigin() + position * effect->GetCurrentAxis(); + mLight.lightRadius.Set( + size[0] < 1.0f ? 1.0f : size[0], + size[1] < 1.0f ? 1.0f : size[1], + size[2] < 1.0f ? 1.0f : size[2] ); + mLight.axis = effect->GetCurrentAxis(); + mLight.shaderParms[SHADERPARM_RED] = tint[0]; + mLight.shaderParms[SHADERPARM_GREEN] = tint[1]; + mLight.shaderParms[SHADERPARM_BLUE] = tint[2]; + + session->rw->UpdateLightDef( mLightDefHandle, &mLight ); + return true; +} diff --git a/src/bse/BSE_Manager.cpp b/src/bse/BSE_Manager.cpp new file mode 100644 index 0000000..9ccbc18 --- /dev/null +++ b/src/bse/BSE_Manager.cpp @@ -0,0 +1,292 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" +#include "../game/Game.h" +#include "../renderer/tr_local.h" + +// Retail compilation unit: BSE_Manager.obj. + +idCVar bse_enabled( "bse_enabled", "1", CVAR_BOOL, "set to false to disable all effects" ); +idCVar bse_render( "bse_render", "1", CVAR_BOOL, "disable effect rendering" ); +idCVar bse_debug( "bse_debug", "0", CVAR_INTEGER, "display debug info about effect" ); +idCVar bse_showBounds( "bse_showBounds", "0", CVAR_INTEGER, "display debug bounding boxes effect" ); +idCVar bse_physics( "bse_physics", "1", CVAR_BOOL, "disable effect physics" ); +idCVar bse_debris( "bse_debris", "1", CVAR_BOOL, "disable effect debris" ); +idCVar bse_scale( "bse_scale", "1", CVAR_FLOAT, "effect scalability amount" ); +idCVar bse_singleEffect( "bse_singleEffect", "", 0, "set to the name of the effect that is only played" ); +idCVar bse_maxParticles( "bse_maxParticles", "2048", CVAR_INTEGER, "max number of particles allowed per segment" ); +static idCVar bse_rateLimit( "bse_rateLimit", "1.0", CVAR_ARCHIVE | CVAR_FLOAT, "rate limit for spawned effects" ); +static idCVar bse_rateCost( "bse_rateCost", "1.0", CVAR_ARCHIVE | CVAR_FLOAT, "rate cost multiplier for spawned effects" ); + +idBlockAlloc rvBSEManagerLocal::effects; +idVec3 rvBSEManagerLocal::mCubeNormals[6] = { + idVec3( -1.0f, 0.0f, 0.0f ), idVec3( 0.0f, -1.0f, 0.0f ), idVec3( 0.0f, 0.0f, -1.0f ), + idVec3( 1.0f, 0.0f, 0.0f ), idVec3( 0.0f, 1.0f, 0.0f ), idVec3( 0.0f, 0.0f, 1.0f ) +}; +idMat3 rvBSEManagerLocal::mModelToBSE( + idVec3( 0.0f, 1.0f, 0.0f ), idVec3( 0.0f, 0.0f, 1.0f ), idVec3( 1.0f, 0.0f, 0.0f ) ); +idList rvBSEManagerLocal::mTraceModels; +const char *rvBSEManagerLocal::mSegmentNames[SEG_COUNT] = { + "None", "Effect", "Emitter", "Spawner", "Trail", "Sound", "Decal", "Light", + "Delay", "DoubleVision", "ScreenShake", "TunnelVision" +}; +int rvBSEManagerLocal::mPerfCounters[NUM_PERF_COUNTERS]; +float rvBSEManagerLocal::mEffectRates[EC_MAX]; +idCVar *rvBSEManagerLocal::g_decals = NULL; + +class rvDeclEffectEditLocal : public rvDeclEffectEdit { +public: + virtual void Finish( rvDeclEffect *edit ) { edit->Finish(); } + virtual rvSegmentTemplate *GetSegmentTemplate( rvDeclEffect *edit, const char *name ) { return edit->GetSegmentTemplate( name ); } + virtual rvSegmentTemplate *GetSegmentTemplate( rvDeclEffect *edit, int i ) { return edit->GetSegmentTemplate( i ); } + virtual void CopyData( rvDeclEffect *edit, rvDeclEffect *copy ) { edit->CopyData( *copy ); } + virtual int AddSegment( rvDeclEffect *edit, rvSegmentTemplate *add ) { return edit->AddSegment( *add ); } + virtual void DeleteSegment( rvDeclEffect *edit, int index ) { edit->DeleteSegment( index ); } + virtual void SwapSegments( rvSegmentTemplate *seg1, rvSegmentTemplate *seg2 ) { + rvSegmentTemplate temp( *seg1 ); + *seg1 = *seg2; + *seg2 = temp; + } + virtual void CreateEditorOriginal( rvDeclEffect *edit ) { edit->CreateEditorOriginal(); } + virtual void DeleteEditorOriginal( rvDeclEffect *edit ) { if ( edit ) { edit->DeleteEditorOriginal(); } } + virtual bool CompareToEditorOriginal( rvDeclEffect *edit ) { return edit->CompareToEditorOriginal(); } + virtual void RevertToEditorOriginal( rvDeclEffect *edit ) { edit->Revert(); } + virtual void Init( rvSegmentTemplate *edit, rvDeclEffect *effect ) { edit->Init( effect ); } + virtual bool Parse( rvSegmentTemplate *edit, rvDeclEffect *effect, int type, idLexer *lexer ) { return edit->Parse( effect, type, lexer ); } + virtual void Finish( rvSegmentTemplate *edit, rvDeclEffect *effect ) { edit->Finish( effect ); } + virtual bool Compare( rvSegmentTemplate *edit, const rvSegmentTemplate *other ) const { return *edit == *other; } + virtual void SetName( rvSegmentTemplate *edit, const char *name ) { edit->mSegmentName = name; } + virtual void Finish( rvParticleTemplate *edit ) { edit->Finish(); } + virtual bool Compare( rvParticleTemplate *edit, const rvParticleTemplate *other ) const { return *edit == *other; } + virtual void Init( rvParticleTemplate *edit ) { edit->Init(); } + virtual void FixupParms( rvParticleTemplate *edit, rvParticleParms *parms ) { edit->FixupParms( *parms ); } + virtual void SetMaterialName( rvParticleTemplate *edit, const char *name ) { edit->mMaterialName = name; } + virtual void SetModelName( rvParticleTemplate *edit, const char *name ) { edit->mModelName = name; } + virtual void SetEntityDefName( rvParticleTemplate *edit, const char *name ) { edit->mEntityDefName = name; } + virtual void SetTrailTypeName( rvParticleTemplate *edit, const char *name ) { edit->mTrailTypeName = name; } + virtual void SetTrailMaterialName( rvParticleTemplate *edit, const char *name ) { edit->mTrailMaterialName = name; } + virtual bool Compare( rvParticleParms *edit, const rvParticleParms *other ) const { return *edit == *other; } + virtual void CalcRate( rvEnvParms *edit, float *rate, float duration, int count ) { edit->CalcRate( rate, duration, count ); } + virtual void Evaluate3( rvEnvParms *edit, float time, const float *start, const float *rate, const float *end, float *dest ) { + edit->Evaluate3( time, start, rate, end, dest ); + } +}; + +static rvBSEManagerLocal bseLocal; +static rvDeclEffectEditLocal declEffectEditLocal; +rvBSEManager *bse = &bseLocal; +rvDeclEffectEdit *declEffectEdit = &declEffectEditLocal; + +static void BSE_Stats_f( const idCmdArgs &args ) { + bseLocal.Stats( args ); +} + +void rvDeclEffect::DeleteSegment( int index ) { + if ( index >= 0 && index < mSegmentTemplates.Num() ) { + mSegmentTemplates.RemoveIndex( index ); + } +} + +int rvDeclEffect::AddSegment( rvSegmentTemplate &segment ) { + return mSegmentTemplates.Append( segment ); +} + +bool rvBSEManagerLocal::Init() { + common->Printf( "----------------- BSE Init ------------------\n" ); + declManager->FindEffect( "_default" ); + declManager->FindMaterial( "_default" ); + declManager->FindMaterial( "gfx/effects/particles_shapes/motionblur" ); + declManager->FindType( DECL_TABLE, "halfsintable" ); + renderModelManager->FindModel( "_default" ); + g_decals = cvarSystem->Find( "g_decals" ); + memset( mEffectRates, 0, sizeof( mEffectRates ) ); + cmdSystem->AddCommand( "bseStats", BSE_Stats_f, 0, + "Dumps the stats of every registered effect - use all to force parse every effect" ); + common->Printf( "--------- BSE Created Successfully ----------\n" ); + return true; +} + +bool rvBSEManagerLocal::Shutdown() { + common->Printf( "--------------- BSE Shutdown ----------------\n" ); + for ( int i = 0; i < mTraceModels.Num(); ++i ) { + delete mTraceModels[i]; + } + mTraceModels.Clear(); + effects.Shutdown(); + memset( mEffectRates, 0, sizeof( mEffectRates ) ); + common->Printf( "---------------------------------------------\n" ); + return true; +} + +bool rvBSEManagerLocal::PlayEffect( rvRenderEffectLocal *def, float time ) { + if ( !def || !def->parms.declEffect || !bse_enabled.GetBool() ) { + return false; + } + const rvDeclEffect *decl = static_cast( def->parms.declEffect ); + if ( Filtered( decl->GetName(), EC_IGNORE ) ) { + return false; + } + decl->IncPlayCount(); + def->effect = effects.Alloc(); + def->effect->Init( const_cast( decl ), &def->parms, time ); + return true; +} + +bool rvBSEManagerLocal::ServiceEffect( rvRenderEffectLocal *def, float time ) { + if ( !def || !def->effect ) { + return true; + } + const char *name = def->parms.declEffect ? def->parms.declEffect->GetName() : ""; + if ( Filtered( name, EC_IGNORE ) || def->effect->Service( &def->parms, time ) ) { + return true; + } + def->referenceBounds = def->effect->GetCurrentLocalBounds(); + if ( com_debugHudActive ) { + ++mPerfCounters[PERF_NUM_BSE]; + } + if ( bse_debug.GetInteger() ) { + def->effect->EvaluateCost(); + } + return false; +} + +void rvBSEManagerLocal::StopEffect( rvRenderEffectLocal *def ) { + if ( def && def->index >= 0 && def->effect ) { + def->effect->SetStopped( true ); + } +} + +void rvBSEManagerLocal::FreeEffect( rvRenderEffectLocal *def ) { + if ( def && def->effect ) { + def->effect->Destroy(); + effects.Free( def->effect ); + def->effect = NULL; + } +} + +float rvBSEManagerLocal::EffectDuration( const rvRenderEffectLocal *def ) { + return ( def && def->index >= 0 && def->effect ) ? def->effect->GetDuration() : 0.0f; +} + +bool rvBSEManagerLocal::CheckDefForSound( const renderEffect_t *def ) { + const rvDeclEffect *decl = def ? static_cast( def->declEffect ) : NULL; + return decl && decl->GetHasSound(); +} + +void rvBSEManagerLocal::BeginLevelLoad() { +} + +void rvBSEManagerLocal::EndLevelLoad() { + memset( mEffectRates, 0, sizeof( mEffectRates ) ); +} + +void rvBSEManagerLocal::StartFrame() { + if ( com_debugHudActive ) { + memset( mPerfCounters, 0, sizeof( mPerfCounters ) ); + } +} + +void rvBSEManagerLocal::EndFrame() { + if ( com_debugHudActive && game ) { + game->DebugSetInt( "fx_num_active", mPerfCounters[PERF_NUM_BSE] ); + game->DebugSetInt( "fx_num_particles", mPerfCounters[PERF_NUM_PARTICLES] ); + game->DebugSetInt( "fx_num_traces", mPerfCounters[PERF_NUM_TRACES] ); + game->DebugSetFloat( "fx_num_texels", mPerfCounters[PERF_NUM_TEXELS] / ( 1024.0f * 1024.0f ) ); + game->DebugSetInt( "fx_num_segments", mPerfCounters[PERF_NUM_SEGMENTS] ); + } +} + +bool rvBSEManagerLocal::Filtered( const char *name, effectCategory_t category ) { + const char *single = bse_singleEffect.GetString(); + if ( single[0] && ( !name || !strstr( name, single ) ) ) { + return true; + } + return !CanPlayRateLimited( category ); +} + +void rvBSEManagerLocal::UpdateRateTimes() { + for ( int i = 0; i < EC_MAX; ++i ) { + mEffectRates[i] = Max( 0.0f, mEffectRates[i] - 0.1f ); + } +} + +bool rvBSEManagerLocal::CanPlayRateLimited( effectCategory_t category ) { + static const float effectCosts[EC_MAX] = { 0.0f, 0.1f, 0.1f }; + const float limit = bse_rateLimit.GetFloat(); + if ( category == EC_IGNORE || limit <= 0.1f ) { + return true; + } + float &rate = mEffectRates[category]; + const float cost = effectCosts[category] * bse_rateCost.GetFloat(); + if ( limit * 0.5f < rate && cost + rate > rvRandom::flrand( 0.0f, limit ) ) { + return false; + } + rate += cost; + return true; +} + +void rvBSEManagerLocal::SetDoubleVisionParms( float time, float scale ) { + if ( game ) { game->StartViewEffect( VIEWEFFECT_DOUBLEVISION, time, scale ); } +} + +void rvBSEManagerLocal::SetShakeParms( float time, float scale ) { + if ( game ) { game->StartViewEffect( VIEWEFFECT_SHAKE, time, scale ); } +} + +void rvBSEManagerLocal::SetTunnelParms( float time, float scale ) { + if ( game ) { game->StartViewEffect( VIEWEFFECT_TUNNEL, time, scale ); } +} + +void rvBSEManagerLocal::Stats( const idCmdArgs &args ) { + const bool loadAll = args.Argc() > 1 && !idStr::Icmp( args.Argv( 1 ), "all" ); + int loaded = 0; + int neverReferenced = 0; + int segments = 0; + int particleSegments = 0; + int particles = 0; + const int numEffects = declManager->GetNumDecls( DECL_EFFECT ); + common->Printf( "... processing %d registered effects\n", numEffects ); + for ( int i = 1; i < numEffects; ++i ) { + const rvDeclEffect *effect = static_cast( declManager->DeclByIndex( DECL_EFFECT, i, loadAll ) ); + if ( !effect ) { continue; } + ++loaded; + if ( effect->GetState() != DS_PARSED ) { ++neverReferenced; } + for ( int j = 0; j < effect->GetNumSegmentTemplates(); ++j ) { + const rvSegmentTemplate *segment = effect->GetSegmentTemplate( j ); + ++segments; + if ( segment->GetHasParticles() ) { + ++particleSegments; + particles += idMath::Ftoi( segment->GetMaxCount() ); + } + } + } + common->Printf( "%d segments in %d loaded effects (%d never referenced)\n", segments, loaded, neverReferenced ); + common->Printf( "%.2f segments per effect\n", loaded ? (float)segments / loaded : 0.0f ); + common->Printf( "%.2f of segments have particles\n", segments ? (float)particleSegments / segments : 0.0f ); + common->Printf( "%.2f particles per segment with particles\n", particleSegments ? (float)particles / particleSegments : 0.0f ); +} diff --git a/src/bse/BSE_ParseParticle2.cpp b/src/bse/BSE_ParseParticle2.cpp new file mode 100644 index 0000000..7989e4e --- /dev/null +++ b/src/bse/BSE_ParseParticle2.cpp @@ -0,0 +1,410 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" + +// Retail compilation unit: BSE_ParseParticle2.obj. + +bool rvParticleTemplate::UsesEndOrigin() { + return ( mSpawnPosition.mFlags & PPFLAG_USEENDORIGIN ) != 0 || + ( mSpawnLength.mFlags & PPFLAG_USEENDORIGIN ) != 0; +} + +bool rvParticleTemplate::GetVector( idLexer *lexer, int count, idVec3 &result ) { + result.Zero(); + for ( int i = 0; i < count && i < 3; ++i ) { + if ( i && !lexer->ExpectTokenString( "," ) ) { + return false; + } + result[i] = lexer->ParseFloat(); + } + return true; +} + +void rvParticleTemplate::SetParameterCounts() { + int sizeParms; + int rotateParms; + int sizeSpawnType; + switch ( mType ) { + case PTYPE_SPRITE: + case PTYPE_DECAL: + sizeParms = 2; rotateParms = 1; sizeSpawnType = SPF_ONE_2; break; + case PTYPE_LINE: + case PTYPE_ELECTRICITY: + case PTYPE_LINKED: + sizeParms = 1; rotateParms = 0; sizeSpawnType = SPF_ONE_1; break; + case PTYPE_ORIENTED: + sizeParms = 2; rotateParms = 3; sizeSpawnType = SPF_ONE_2; break; + case PTYPE_MODEL: + sizeParms = 3; rotateParms = 3; sizeSpawnType = SPF_ONE_3; break; + case PTYPE_LIGHT: + sizeParms = 3; rotateParms = 0; sizeSpawnType = SPF_ONE_3; break; + case PTYPE_ORIENTEDLINKED: + sizeParms = 0; rotateParms = 3; sizeSpawnType = SPF_ONE_3; break; + default: + return; + } + mNumSizeParms = sizeParms; + mNumRotateParms = rotateParms; + mSpawnSize.mSpawnType = sizeSpawnType; + mSpawnRotate.mSpawnType = rotateParms; + mDeathSize.mSpawnType = sizeSpawnType; + mDeathRotate.mSpawnType = rotateParms; +} + +void rvParticleTemplate::FixupParms( rvParticleParms &parms ) { + const int count = parms.mSpawnType & 3; + if ( count <= 0 ) { + return; + } + bool constant = true; + for ( int i = 0; i < count; ++i ) { + if ( parms.mMins[i] != parms.mMaxs[i] ) { + constant = false; + break; + } + } + if ( constant ) { + bool allZero = true; + bool allOne = true; + for ( int i = 0; i < count; ++i ) { + allZero &= parms.mMins[i] == 0.0f; + allOne &= parms.mMins[i] == 1.0f; + } + parms.mSpawnType = ( allZero ? SPF_NONE_0 : allOne ? SPF_ONE_0 : SPF_POINT_0 ) + count; + } + for ( int i = count; i < 3; ++i ) { + parms.mMins[i] = parms.mMaxs[i] = 0.0f; + } + if ( parms.mSpawnType < SPF_LINEAR_0 ) { + parms.mMaxs = parms.mMins; + } + if ( parms.mFlags & PPFLAG_USEENDORIGIN ) { + const int base = parms.mSpawnType & ~3; + if ( base <= SPF_POINT_0 ) { + parms.mSpawnType = SPF_LINEAR_0 + count; + } + } +} + +void rvParticleTemplate::Init() { + mFlags = 0; + mType = PTYPE_NONE; + mMaterialName = "_default"; + mMaterial = declManager->FindMaterial( mMaterialName ); + mModelName = "_default"; + mEntityDefName.Clear(); + mTraceModelIndex = -1; + mGravity.Zero(); + mSoundVolume.Zero(); + mFreqShift.Zero(); + mTiling = 8.0f; + mTrailType = TRAIL_NONE; + mTrailTypeName.Clear(); + mTrailMaterialName = "gfx/effects/particles_shapes/motionblur"; + mTrailMaterial = declManager->FindMaterial( mTrailMaterialName ); + mTrailTime.Zero(); + mTrailCount.Zero(); + mBounce = 0.0f; + mDuration.Set( BSE_TIME_EPSILON, BSE_TIME_EPSILON ); + mCentre.Zero(); + mNumForks = 0; + mForkSizeMins.Set( -20.0f, -20.0f, -20.0f ); + mForkSizeMaxs = -mForkSizeMins; + mJitterSize.Set( 3.0f, 7.0f, 7.0f ); + mJitterRate = 0.0f; + mJitterTable = declManager->FindTable( "halfsintable" ); + mNumSizeParms = 2; + mNumRotateParms = 1; + mVertexCount = 4; + mIndexCount = 6; + + mSpawnPosition.Init( SPF_NONE_3 ); + mSpawnDirection.Init( SPF_NONE_3 ); + mSpawnVelocity.Init( SPF_NONE_3 ); + mSpawnAcceleration.Init( SPF_NONE_3 ); + mSpawnFriction.Init( SPF_NONE_3 ); + mSpawnTint.Init( SPF_ONE_3 ); + mSpawnFade.Init( SPF_ONE_1 ); + mSpawnSize.Init( SPF_ONE_3 ); + mSpawnRotate.Init( SPF_NONE_3 ); + mSpawnAngle.Init( SPF_NONE_3 ); + mSpawnOffset.Init( SPF_NONE_3 ); + mSpawnLength.Init( SPF_NONE_3 ); + + mTintEnvelope.Init(); + mFadeEnvelope.Init(); + mSizeEnvelope.Init(); + mRotateEnvelope.Init(); + mAngleEnvelope.Init(); + mOffsetEnvelope.Init(); + mLengthEnvelope.Init(); + + mDeathTint.Init( SPF_NONE_3 ); + mDeathFade.Init( SPF_NONE_1 ); + mDeathSize.Init( SPF_ONE_3 ); + mDeathRotate.Init( SPF_NONE_3 ); + mDeathAngle.Init( SPF_NONE_3 ); + mDeathOffset.Init( SPF_NONE_3 ); + mDeathLength.Init( SPF_NONE_3 ); + + mNumImpactEffects = 0; + mNumTimeoutEffects = 0; + memset( mImpactEffects, 0, sizeof( mImpactEffects ) ); + memset( mTimeoutEffects, 0, sizeof( mTimeoutEffects ) ); +} + +idTraceModel *rvParticleTemplate::GetTraceModel() const { + return mTraceModelIndex >= 0 && mTraceModelIndex < rvBSEManagerLocal::mTraceModels.Num() + ? rvBSEManagerLocal::mTraceModels[mTraceModelIndex] : NULL; +} + +int rvParticleTemplate::GetTrailCount() const { + return Max( 0, idMath::Ftoi( rvRandom::flrand( mTrailCount.x, mTrailCount.y ) ) ); +} + +float rvParticleTemplate::GetSpawnVolume( rvBSE *effect ) { + float x = mSpawnPosition.mMaxs.x - mSpawnPosition.mMins.x; + if ( mSpawnPosition.mFlags & PPFLAG_USEENDORIGIN ) { + x = ( effect->GetOriginalEndOrigin() - effect->GetOriginalOrigin() ).Length() - mSpawnPosition.mMins.x; + } + return ( x + ( mSpawnPosition.mMaxs.y - mSpawnPosition.mMins.y ) + + ( mSpawnPosition.mMaxs.z - mSpawnPosition.mMins.z ) ) * BSE_PARTICLE_TEXCOORDSCALE; +} + +float rvParticleTemplate::CostTrail( float cost ) const { + switch ( mTrailType ) { + case TRAIL_BURN: return cost * mTrailCount.y * 2.0f; + case TRAIL_MOTION: return cost * mTrailCount.y * 1.5f + BSE_TESS_COST; + default: return cost; + } +} + +bool rvParticleTemplate::Compare( const rvParticleTemplate &a ) const { + if ( mFlags != a.mFlags || mType != a.mType || mMaterialName != a.mMaterialName || + mModelName != a.mModelName || mEntityDefName != a.mEntityDefName || + mGravity != a.mGravity || mSoundVolume != a.mSoundVolume || mFreqShift != a.mFreqShift || + mTiling != a.mTiling || mTrailType != a.mTrailType || mTrailTypeName != a.mTrailTypeName || + mTrailMaterialName != a.mTrailMaterialName || mTrailTime != a.mTrailTime || + mTrailCount != a.mTrailCount || mBounce != a.mBounce || mDuration != a.mDuration || + mNumForks != a.mNumForks || mForkSizeMins != a.mForkSizeMins || + mForkSizeMaxs != a.mForkSizeMaxs || mJitterSize != a.mJitterSize || + mJitterRate != a.mJitterRate || mJitterTable != a.mJitterTable || + mNumSizeParms != a.mNumSizeParms || mNumRotateParms != a.mNumRotateParms || + mVertexCount != a.mVertexCount || mIndexCount != a.mIndexCount ) { + return false; + } + if ( mSpawnPosition != a.mSpawnPosition || mSpawnDirection != a.mSpawnDirection || + mSpawnVelocity != a.mSpawnVelocity || mSpawnAcceleration != a.mSpawnAcceleration || + mSpawnFriction != a.mSpawnFriction || mSpawnTint != a.mSpawnTint || + mSpawnFade != a.mSpawnFade || mSpawnSize != a.mSpawnSize || + mSpawnRotate != a.mSpawnRotate || mSpawnAngle != a.mSpawnAngle || + mSpawnOffset != a.mSpawnOffset || mSpawnLength != a.mSpawnLength || + mDeathTint != a.mDeathTint || mDeathFade != a.mDeathFade || + mDeathSize != a.mDeathSize || mDeathRotate != a.mDeathRotate || + mDeathAngle != a.mDeathAngle || mDeathOffset != a.mDeathOffset || + mDeathLength != a.mDeathLength || mTintEnvelope != a.mTintEnvelope || + mFadeEnvelope != a.mFadeEnvelope || mSizeEnvelope != a.mSizeEnvelope || + mRotateEnvelope != a.mRotateEnvelope || mAngleEnvelope != a.mAngleEnvelope || + mOffsetEnvelope != a.mOffsetEnvelope || mLengthEnvelope != a.mLengthEnvelope || + mNumImpactEffects != a.mNumImpactEffects || mNumTimeoutEffects != a.mNumTimeoutEffects ) { + return false; + } + for ( int i = 0; i < mNumImpactEffects; ++i ) { + if ( mImpactEffects[i] != a.mImpactEffects[i] ) { return false; } + } + for ( int i = 0; i < mNumTimeoutEffects; ++i ) { + if ( mTimeoutEffects[i] != a.mTimeoutEffects[i] ) { return false; } + } + return true; +} + +void rvParticleTemplate::Finish() { + mFlags |= PTFLAG_PARSED; + if ( mTrailType == TRAIL_NONE || mTrailType == TRAIL_PARTICLE ) { + mTrailTime.Zero(); + mTrailCount.Zero(); + } + switch ( mType ) { + case PTYPE_SPRITE: + case PTYPE_LINE: + mVertexCount = 4; + mIndexCount = 6; + if ( mTrailCount.y != 0.0f && mTrailType == TRAIL_BURN ) { + mVertexCount *= GetMaxTrailCount(); + mIndexCount *= GetMaxTrailCount(); + } + break; + case PTYPE_DECAL: + case PTYPE_LIGHT: + case PTYPE_LINKED: + mVertexCount = 4; + mIndexCount = 6; + break; + case PTYPE_MODEL: { + idRenderModel *model = renderModelManager->FindModel( mModelName ); + const modelSurface_t *surface = model && model->NumSurfaces() ? model->Surface( 0 ) : NULL; + if ( surface ) { + mMaterial = surface->shader; + if ( mMaterial ) { mMaterialName = mMaterial->GetName(); } + if ( surface->geometry ) { + mVertexCount = surface->geometry->numVerts; + mIndexCount = surface->geometry->numIndexes; + idTraceModel *traceModel = new idTraceModel( surface->geometry->bounds ); + mTraceModelIndex = rvBSEManagerLocal::mTraceModels.Append( traceModel ); + } + } + break; + } + case PTYPE_ELECTRICITY: + mVertexCount = 4 * ( 5 * mNumForks + 5 ); + mIndexCount = 60 * ( mNumForks + 1 ); + break; + case PTYPE_ORIENTEDLINKED: + mVertexCount = mIndexCount = 0; + break; + default: + break; + } + if ( mDuration.x > mDuration.y ) { idSwap( mDuration.x, mDuration.y ); } + if ( mGravity.x > mGravity.y ) { idSwap( mGravity.x, mGravity.y ); } + if ( mTrailTime.x > mTrailTime.y ) { idSwap( mTrailTime.x, mTrailTime.y ); } + if ( mTrailCount.x > mTrailCount.y ) { idSwap( mTrailCount.x, mTrailCount.y ); } + mCentre.Zero(); + if ( !GetGeneratedNormal() ) { + const int domain = mSpawnPosition.mSpawnType & ~3; + if ( domain == SPF_POINT_0 ) { + mCentre = mSpawnPosition.mMins; + } else if ( domain >= SPF_LINEAR_0 ) { + mCentre = ( mSpawnPosition.mMins + mSpawnPosition.mMaxs ) * 0.5f; + } + } +} + +static void BSE_ParseRange( idLexer *lexer, idVec2 &range, float minValue, float maxValue ) { + range.x = idMath::ClampFloat( minValue, maxValue, lexer->ParseFloat() ); + lexer->ExpectTokenString( "," ); + range.y = idMath::ClampFloat( minValue, maxValue, lexer->ParseFloat() ); +} + +bool rvParticleTemplate::Parse( rvDeclEffect *effect, idLexer *lexer ) { + idToken token; + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + while ( token.Cmp( "}" ) != 0 ) { + if ( token.Icmp( "start" ) == 0 ) { + lexer->SkipBracedSection(); + } else if ( token.Icmp( "end" ) == 0 ) { + lexer->SkipBracedSection(); + } else if ( token.Icmp( "motion" ) == 0 ) { + lexer->SkipBracedSection(); + } else if ( token.Icmp( "generatedNormal" ) == 0 ) { + SetGeneratedNormal( true ); + } else if ( token.Icmp( "generatedOriginNormal" ) == 0 ) { + SetGeneratedOriginNormal( true ); + } else if ( token.Icmp( "flipNormal" ) == 0 ) { + SetFlippedNormal( true ); + } else if ( token.Icmp( "generatedLine" ) == 0 ) { + SetGeneratedLine( true ); + } else if ( token.Icmp( "persist" ) == 0 ) { + SetPersist( true ); + } else if ( token.Icmp( "tiling" ) == 0 ) { + SetTiled( true ); + mTiling = idMath::ClampFloat( BSE_TIME_EPSILON, 1024.0f, lexer->ParseFloat() ); + } else if ( token.Icmp( "duration" ) == 0 ) { + BSE_ParseRange( lexer, mDuration, BSE_TIME_EPSILON, 60.0f ); + } else if ( token.Icmp( "gravity" ) == 0 ) { + BSE_ParseRange( lexer, mGravity, -WORLD_SIZE, WORLD_SIZE ); + } else if ( token.Icmp( "trailType" ) == 0 ) { + lexer->ReadToken( &token ); + if ( token.Icmp( "burn" ) == 0 ) { mTrailType = TRAIL_BURN; } + else if ( token.Icmp( "motion" ) == 0 ) { mTrailType = TRAIL_MOTION; } + else { mTrailType = TRAIL_PARTICLE; mTrailTypeName = token; } + } else if ( token.Icmp( "trailMaterial" ) == 0 ) { + lexer->ReadToken( &token ); + mTrailMaterialName = token; + mTrailMaterial = declManager->FindMaterial( token ); + } else if ( token.Icmp( "trailTime" ) == 0 ) { + BSE_ParseRange( lexer, mTrailTime, 0.0f, BSE_MAX_DURATION ); + } else if ( token.Icmp( "trailCount" ) == 0 ) { + BSE_ParseRange( lexer, mTrailCount, 0.0f, (float)MAX_PARTICLES ); + } else if ( token.Icmp( "material" ) == 0 ) { + lexer->ReadToken( &token ); + mMaterialName = token; + mMaterial = declManager->FindMaterial( token ); + } else if ( token.Icmp( "entityDef" ) == 0 ) { + lexer->ReadToken( &token ); + mEntityDefName = token; + } else if ( token.Icmp( "fork" ) == 0 ) { + mNumForks = idMath::ClampInt( 0, BSE_MAX_FORKS, lexer->ParseInt() ); + } else if ( token.Icmp( "forkMins" ) == 0 ) { + GetVector( lexer, 3, mForkSizeMins ); + } else if ( token.Icmp( "forkMaxs" ) == 0 ) { + GetVector( lexer, 3, mForkSizeMaxs ); + } else if ( token.Icmp( "jitterSize" ) == 0 ) { + GetVector( lexer, 3, mJitterSize ); + } else if ( token.Icmp( "jitterRate" ) == 0 ) { + mJitterRate = lexer->ParseFloat(); + } else if ( token.Icmp( "jitterTable" ) == 0 ) { + lexer->ReadToken( &token ); + mJitterTable = declManager->FindTable( token ); + } else if ( token.Icmp( "blend" ) == 0 ) { + lexer->ReadToken( &token ); + SetAdditive( token.Icmp( "add" ) == 0 ); + } else if ( token.Icmp( "shadows" ) == 0 ) { + SetShadows( true ); + } else if ( token.Icmp( "specular" ) == 0 ) { + SetSpecular( true ); + } else if ( token.Icmp( "model" ) == 0 ) { + lexer->ReadToken( &token ); + mModelName = token; + renderModelManager->FindModel( token ); + } else if ( token.Icmp( "impact" ) == 0 || token.Icmp( "timeout" ) == 0 ) { + const bool impact = token.Icmp( "impact" ) == 0; + if ( !lexer->ExpectTokenString( "{" ) ) { return false; } + while ( lexer->ReadToken( &token ) && token.Cmp( "}" ) != 0 ) { + if ( token.Icmp( "effect" ) == 0 && lexer->ReadToken( &token ) ) { + if ( impact && mNumImpactEffects < BSE_NUM_SPAWNABLE ) { mImpactEffects[mNumImpactEffects++] = declManager->FindEffect( token ); } + else if ( !impact && mNumTimeoutEffects < BSE_NUM_SPAWNABLE ) { mTimeoutEffects[mNumTimeoutEffects++] = declManager->FindEffect( token ); } + } else if ( impact && token.Icmp( "remove" ) == 0 ) { + SetDeleteOnImpact( lexer->ParseInt() != 0 ); + } else if ( impact && token.Icmp( "bounce" ) == 0 ) { + mBounce = lexer->ParseFloat(); + } + } + } else { + common->Warning( "^4BSE:^1 Invalid particle keyword '%s' in '%s' (file: %s, line: %d)", + token.c_str(), effect ? effect->GetName() : "", lexer->GetFileName(), lexer->GetLineNum() ); + } + if ( !lexer->ReadToken( &token ) ) { + return false; + } + } + Finish(); + return true; +} diff --git a/src/bse/BSE_Particle.cpp b/src/bse/BSE_Particle.cpp new file mode 100644 index 0000000..ba66fce --- /dev/null +++ b/src/bse/BSE_Particle.cpp @@ -0,0 +1,685 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" +#include "../framework/Session.h" +#include "../game/Game.h" + +// Retail compilation unit: BSE_Particle.obj. Implementations are recovered here. + +void rvParticle::SetOriginUsingEndOrigin( rvBSE *effect, + rvParticleTemplate *pt, idVec3 *normal, idVec3 *centre ) { + pt->mSpawnPosition.Spawn( mInitPos.ToFloatPtr(), pt->mSpawnPosition, + normal, centre ); + + rvParticleParms temp = pt->mSpawnPosition; + temp.mMins.x = mInitPos.x; + temp.mMaxs.x = ( effect->GetCurrentEndOrigin() - + effect->GetOriginalOrigin() ).Length(); + mInitPos.x = mFraction; + temp.Spawn( mInitPos.ToFloatPtr(), temp, normal, centre ); +} + +void rvParticle::HandleEndOrigin( rvBSE *effect, rvParticleTemplate *pt, + idVec3 *normal, idVec3 *centre ) { + mInitPos.x = mFraction; + if ( effect->GetHasEndOrigin() && + ( pt->mSpawnPosition.mFlags & PPFLAG_USEENDORIGIN ) ) { + SetOriginUsingEndOrigin( effect, pt, normal, centre ); + } else { + pt->mSpawnPosition.Spawn( mInitPos.ToFloatPtr(), + pt->mSpawnPosition, normal, centre ); + } +} + +void rvParticle::SetLengthUsingEndOrigin( rvBSE *effect, + rvParticleParms &parms, float *length ) { + rvParticleParms temp = parms; + const float endOriginLength = ( effect->GetCurrentEndOrigin() - + effect->GetCurrentOrigin() ).Length(); + temp.mFlags = 0; + temp.mMins.x += endOriginLength; + temp.mMaxs.x += endOriginLength; + temp.Spawn( length, temp ); +} + +void rvParticle::FinishSpawn( rvBSE *effect, rvSegment *segment, + float birthTime, float fraction, const idVec3 &initOffset, + const idMat3 &initAxis ) { + rvSegmentTemplate *segmentTemplate = segment->GetSegmentTemplate(); + if ( segmentTemplate == NULL ) { + return; + } + rvParticleTemplate *pt = segmentTemplate->GetParticleTemplate(); + + mFlags = pt->mFlags; + SetLocked( segment->GetLocked() ); + pt->mSpawnVelocity.Spawn( mVelocity.ToFloatPtr(), pt->mSpawnVelocity ); + pt->mSpawnAcceleration.Spawn( mAcceleration.ToFloatPtr(), + pt->mSpawnAcceleration ); + pt->mSpawnFriction.Spawn( mFriction.ToFloatPtr(), pt->mSpawnFriction ); + mFraction = fraction; + mTextureScale = 1.0f; + + idVec3 *normal = NULL; + idVec3 *centre = NULL; + if ( pt->GetGeneratedOriginNormal() ) { + normal = &mNormal; + } else if ( pt->GetGeneratedNormal() ) { + normal = &mNormal; + centre = &pt->mCentre; + } else if ( pt->GetCalculatedNormal() ) { + pt->mSpawnDirection.Spawn( mNormal.ToFloatPtr(), + pt->mSpawnDirection ); + } else { + mNormal.Set( 1.0f, 0.0f, 0.0f ); + } + HandleEndOrigin( effect, pt, normal, centre ); + + if ( pt->GetLinked() ) { + mVelocity = initAxis * mVelocity; + } + + rvAngles orientation = mNormal.ToRadians(); + if ( pt->GetGeneratedNormal() || pt->GetGeneratedOriginNormal() ) { + mNormal.Normalize(); + idMat3 normalAxis; + mNormal.ToMat3( normalAxis ); + mVelocity = normalAxis * mVelocity; + TransformLength( mNormal ); + } + if ( pt->GetFlippedNormal() ) { + mVelocity = -mVelocity; + ScaleLength( -1.0f ); + } + + if ( mVelocity.LengthSqr() != 0.0f ) { + mNormal = mVelocity; + mNormal.Normalize(); + } + idMat3 motionAxis; + mNormal.ToMat3( motionAxis ); + mAcceleration = motionAxis * mAcceleration; + if ( mAcceleration.LengthSqr() != 0.0f ) { + mNormal = mAcceleration; + mNormal.Normalize(); + } + mNormal.ToMat3( motionAxis ); + mFriction = motionAxis * mFriction; + + if ( !GetLocked() ) { + mInitAxis = effect->GetCurrentAxis(); + mInitEffectPos = effect->GetCurrentOrigin(); + mInitPos -= mInitAxis.Transpose() * + effect->GetInterpolatedOffset( birthTime ); + } + if ( pt->GetLinked() ) { + mInitPos += initOffset; + } + + pt->mSpawnTint.Spawn( mTintEnv.GetStart(), pt->mSpawnTint ); + pt->mSpawnFade.Spawn( mFadeEnv.GetStart(), pt->mSpawnFade ); + pt->mSpawnSize.Spawn( GetInitSize(), pt->mSpawnSize ); + pt->mSpawnRotate.Spawn( GetInitRotation(), pt->mSpawnRotate ); + pt->mSpawnAngle.Spawn( mAngleEnv.GetStart(), pt->mSpawnAngle ); + pt->mSpawnOffset.Spawn( mOffsetEnv.GetStart(), pt->mSpawnOffset ); + pt->mDeathTint.Spawn( mTintEnv.GetEnd(), pt->mDeathTint ); + pt->mDeathFade.Spawn( mFadeEnv.GetEnd(), pt->mDeathFade ); + pt->mDeathSize.Spawn( GetDestSize(), pt->mDeathSize ); + pt->mDeathRotate.Spawn( GetDestRotation(), pt->mDeathRotate ); + pt->mDeathAngle.Spawn( mAngleEnv.GetEnd(), pt->mDeathAngle ); + pt->mDeathOffset.Spawn( mOffsetEnv.GetEnd(), pt->mDeathOffset ); + if ( pt->mSpawnOffset.mSpawnType != SPF_NONE_3 || + pt->mDeathOffset.mSpawnType != SPF_NONE_3 ) { + SetHasOffset( true ); + } + + pt->mDeathTint.HandleRelativeParms( mTintEnv.GetEnd(), + mTintEnv.GetStart(), 3 ); + pt->mDeathFade.HandleRelativeParms( mFadeEnv.GetEnd(), + mFadeEnv.GetStart(), 1 ); + pt->mDeathSize.HandleRelativeParms( GetDestSize(), GetInitSize(), + pt->mNumSizeParms ); + pt->mDeathRotate.HandleRelativeParms( GetDestRotation(), + GetInitRotation(), pt->mNumRotateParms ); + pt->mDeathAngle.HandleRelativeParms( mAngleEnv.GetEnd(), + mAngleEnv.GetStart(), 3 ); + pt->mDeathOffset.HandleRelativeParms( mOffsetEnv.GetEnd(), + mOffsetEnv.GetStart(), 3 ); + + ScaleRotation( idMath::TWO_PI ); + ScaleAngle( idMath::TWO_PI ); + HandleOrientation( orientation ); + mTrailTime = pt->GetTrailTime(); + mTrailCount = pt->GetTrailCount(); + SetModel( pt->mModelName.c_str() ); + SetupElectricity( pt ); + + const float attenuation = effect->GetAttenuation( segmentTemplate ); + AttenuateFade( attenuation, pt->mSpawnFade ); + AttenuateSize( attenuation, pt->mSpawnSize ); + + const float duration = pt->GetDuration(); + mStartTime = birthTime; + mEndTime = birthTime + duration; + mMotionStartTime = birthTime; + mLastTrailTime = birthTime; + mTintEnv.Init( pt->mTintEnvelope, duration ); + mFadeEnv.Init( pt->mFadeEnvelope, duration ); + InitSizeEnv( pt->mSizeEnvelope, duration ); + InitRotationEnv( pt->mRotateEnvelope, duration ); + mAngleEnv.Init( pt->mAngleEnvelope, duration ); + mOffsetEnv.Init( pt->mOffsetEnvelope, duration ); + + const idVec3 gravity = pt->GetGravity() * effect->GetGravity(); + mAcceleration += effect->GetCurrentAxisTransposed() * gravity; +} + +void rvLineParticle::FinishSpawn( rvBSE *effect, rvSegment *segment, + float birthTime, float fraction, const idVec3 &initOffset, + const idMat3 &initAxis ) { + rvSegmentTemplate *segmentTemplate = segment->GetSegmentTemplate(); + if ( segmentTemplate == NULL ) { + return; + } + rvParticleTemplate *pt = segmentTemplate->GetParticleTemplate(); + if ( effect->GetHasEndOrigin() && + ( pt->mSpawnLength.mFlags & PPFLAG_USEENDORIGIN ) ) { + SetLengthUsingEndOrigin( effect, pt->mSpawnLength, GetInitLength() ); + } else { + pt->mSpawnLength.Spawn( GetInitLength(), pt->mSpawnLength ); + } + if ( effect->GetHasEndOrigin() && + ( pt->mDeathLength.mFlags & PPFLAG_USEENDORIGIN ) ) { + SetLengthUsingEndOrigin( effect, pt->mDeathLength, GetDestLength() ); + } else { + pt->mDeathLength.Spawn( GetDestLength(), pt->mDeathLength ); + } + + rvParticle::FinishSpawn( effect, segment, birthTime, fraction, + initOffset, initAxis ); + pt->mDeathLength.HandleRelativeParms( GetDestLength(), + GetInitLength(), 3 ); + HandleTiling( pt ); + AttenuateLength( effect->GetAttenuation( segmentTemplate ), + pt->mSpawnLength ); + mLengthEnv.Init( pt->mLengthEnvelope, GetDuration() ); +} + +void rvLinkedParticle::FinishSpawn( rvBSE *effect, rvSegment *segment, + float birthTime, float fraction, const idVec3 &initOffset, + const idMat3 &initAxis ) { + rvSegmentTemplate *segmentTemplate = segment->GetSegmentTemplate(); + if ( segmentTemplate == NULL ) { + return; + } + rvParticle::FinishSpawn( effect, segment, birthTime, fraction, + initOffset, initAxis ); + HandleTiling( segmentTemplate->GetParticleTemplate() ); +} + +void rvDebrisParticle::FinishSpawn( rvBSE *effect, rvSegment *segment, + float birthTime, float fraction, const idVec3 &initOffset, + const idMat3 &initAxis ) { + (void)initOffset; + (void)initAxis; + if ( !bse_debris.GetBool() || session->readDemo != NULL ) { + return; + } + rvSegmentTemplate *segmentTemplate = segment->GetSegmentTemplate(); + if ( segmentTemplate == NULL ) { + return; + } + rvParticleTemplate *pt = segmentTemplate->GetParticleTemplate(); + mFlags = pt->mFlags; + pt->mSpawnVelocity.Spawn( mVelocity.ToFloatPtr(), pt->mSpawnVelocity ); + mFraction = fraction; + mTextureScale = 1.0f; + if ( pt->GetGeneratedOriginNormal() ) { + HandleEndOrigin( effect, pt, &mNormal, NULL ); + } else if ( pt->GetGeneratedNormal() ) { + HandleEndOrigin( effect, pt, &mNormal, &pt->mCentre ); + } else { + HandleEndOrigin( effect, pt ); + mNormal.Set( 1.0f, 0.0f, 0.0f ); + } + if ( pt->GetGeneratedNormal() || pt->GetGeneratedOriginNormal() ) { + mNormal.Normalize(); + idMat3 normalAxis; + mNormal.ToMat3( normalAxis ); + mVelocity = normalAxis * mVelocity; + TransformLength( mNormal ); + } + if ( pt->GetFlippedNormal() ) { + mVelocity = -mVelocity; + ScaleLength( -1.0f ); + } + if ( mVelocity.LengthSqr() != 0.0f ) { + mNormal = mVelocity; + mNormal.Normalize(); + } + mFriction.Zero(); + mInitAxis = effect->GetCurrentAxis(); + mInitEffectPos = effect->GetCurrentOrigin(); + mInitPos -= mInitAxis.Transpose() * + effect->GetInterpolatedOffset( birthTime ); + + pt->mSpawnRotate.Spawn( GetInitRotation(), pt->mSpawnRotate ); + pt->mSpawnAngle.Spawn( mAngleEnv.GetStart(), pt->mSpawnAngle ); + pt->mDeathRotate.Spawn( GetDestRotation(), pt->mDeathRotate ); + ScaleRotation( idMath::TWO_PI ); + ScaleAngle( idMath::TWO_PI ); + InitRotationEnv( pt->mRotateEnvelope, 1.0f ); + mAngleEnv.Init( pt->mAngleEnvelope, 1.0f ); + mStartTime = birthTime; + mMotionStartTime = birthTime; + mEndTime = birthTime; + mLastTrailTime = birthTime; + mTrailTime = 0.0f; + mTrailCount = 0; + mAcceleration.Zero(); + const idVec3 gravity = pt->GetGravity() * effect->GetGravity(); + mAcceleration += effect->GetCurrentAxisTransposed() * gravity; + + const idVec3 origin = effect->GetOriginalOrigin() + + effect->GetOriginalAxis() * mInitPos; + const idMat3 axis = effect->GetCurrentAxis(); + const idVec3 velocity = axis * mVelocity; + const float *angular = GetDestRotation(); + const idVec3 angularVelocity( angular[0], angular[1], angular[2] ); + game->SpawnClientMoveable( pt->mEntityDefName.c_str(), + static_cast( ( mEndTime - mStartTime ) * 1000.0f ), origin, + axis, velocity, angularVelocity ); +} + +void rvLineParticle::Refresh( rvBSE *effect, rvSegmentTemplate *st, + rvParticleTemplate *pt ) { + (void)st; + if ( effect->GetHasEndOrigin() && + ( pt->mSpawnLength.mFlags & PPFLAG_USEENDORIGIN ) ) { + SetLengthUsingEndOrigin( effect, pt->mSpawnLength, GetInitLength() ); + } else { + pt->mSpawnLength.Spawn( GetInitLength(), pt->mSpawnLength ); + } + if ( effect->GetHasEndOrigin() && + ( pt->mDeathLength.mFlags & PPFLAG_USEENDORIGIN ) ) { + SetLengthUsingEndOrigin( effect, pt->mDeathLength, GetDestLength() ); + } else { + pt->mDeathLength.Spawn( GetDestLength(), pt->mDeathLength ); + } + pt->mDeathLength.HandleRelativeParms( GetDestLength(), + GetInitLength(), 3 ); + HandleTiling( pt ); + mLengthEnv.Init( pt->mLengthEnvelope, GetDuration() ); +} + +void rvParticle::Attenuate( float atten, rvParticleParms &parms, rvEnvParms1 &result ) { + if ( parms.mFlags & PPFLAG_ATTENUATE ) { + const float scale = ( parms.mFlags & PPFLAG_INV_ATTENUATE ) ? 1.0f - atten : atten; + *result.GetStart() *= scale; + *result.GetEnd() *= scale; + } +} + +void rvLineParticle::HandleTiling( rvParticleTemplate *pt ) { + if ( mFlags & PTFLAG_TILED ) { + const float *length = GetInitLength(); + mTextureScale = idVec3( length[0], length[1], length[2] ).Length() / pt->mTiling; + } +} + +void rvLinkedParticle::HandleTiling( rvParticleTemplate *pt ) { + if ( mFlags & PTFLAG_TILED ) { + mTextureScale = pt->mTiling; + } +} + +rvParticle *rvSpriteParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvSpriteParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvDecalParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvDecalParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvLineParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvLineParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvOrientedParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvOrientedParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvModelParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvModelParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvElectricityParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvElectricityParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvLightParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvLightParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvLinkedParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvLinkedParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +rvParticle *rvDebrisParticle::GetArrayEntry( int i ) const { + return i < 0 ? NULL : const_cast( this ) + i; +} + +int rvDebrisParticle::GetArrayIndex( rvParticle *p ) const { + return p == NULL ? -1 : static_cast( static_cast( p ) - this ); +} + +void rvParticle::EvaluateVelocity( const rvBSE *, idVec3 &velocity, float time ) { + if ( mFlags & PTFLAG_STATIONARY ) { + velocity.Set( 1.0f, 0.0f, 0.0f ); + return; + } + + velocity = mVelocity + time * mAcceleration; + const float duration = mEndTime - mStartTime; + const float frictionScale = + ( idMath::Exp( ( duration - time ) / duration ) * ( 1.0f - time / 3.0f ) + 1.0f ) * + time * time * 0.5f; + velocity += frictionScale * mFriction; +} + +void rvEnvParms3::Scale( const float constant ) { + mStart *= constant; + mEnd *= constant; +} + +void rvParticle::ScaleAngle( float constant ) { + mAngleEnv.Scale( constant ); +} + +void rvParticle::AttenuateFade( float atten, rvParticleParms &parms ) { + Attenuate( atten, parms, mFadeEnv ); +} + +void rvParticle::Attenuate( float atten, rvParticleParms &parms, rvEnvParms3 &result ) { + if ( parms.mFlags & PPFLAG_ATTENUATE ) { + result.Scale( ( parms.mFlags & PPFLAG_INV_ATTENUATE ) ? 1.0f - atten : atten ); + } +} + +void rvParticle::Attenuate( float atten, rvParticleParms &parms, rvEnvParms2 &result ) { + if ( parms.mFlags & PPFLAG_ATTENUATE ) { + result.Scale( ( parms.mFlags & PPFLAG_INV_ATTENUATE ) ? 1.0f - atten : atten ); + } +} + +static void RV_GetParticleTint( rvEnvParms3 &tintEnv, rvEnvParms1 &fadeEnv, idVec4 &tint ) { + const float *rgb = tintEnv.GetStart(); + tint.Set( rgb[0], rgb[1], rgb[2], *fadeEnv.GetStart() ); +} + +void rvSpriteParticle::GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ) { + RV_GetParticleTint( mTintEnv, mFadeEnv, tint ); + const float *initialSize = mSizeEnv.GetStart(); + size.Set( initialSize[0], initialSize[1], 0.0f ); + rotate.Set( *mRotationEnv.GetStart(), 0.0f, 0.0f ); +} + +void rvLineParticle::GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ) { + RV_GetParticleTint( mTintEnv, mFadeEnv, tint ); + size.Set( *mSizeEnv.GetStart(), 0.0f, 0.0f ); + rotate.Zero(); +} + +void rvOrientedParticle::GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ) { + RV_GetParticleTint( mTintEnv, mFadeEnv, tint ); + const float *initialSize = mSizeEnv.GetStart(); + size.Set( initialSize[0], initialSize[1], 0.0f ); + const float *initialRotation = mRotationEnv.GetStart(); + rotate.Set( initialRotation[0], initialRotation[1], initialRotation[2] ); +} + +void rvModelParticle::GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ) { + RV_GetParticleTint( mTintEnv, mFadeEnv, tint ); + const float *initialSize = mSizeEnv.GetStart(); + size.Set( initialSize[0], initialSize[1], initialSize[2] ); + const float *initialRotation = mRotationEnv.GetStart(); + rotate.Set( initialRotation[0], initialRotation[1], initialRotation[2] ); +} + +void rvLightParticle::GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ) { + RV_GetParticleTint( mTintEnv, mFadeEnv, tint ); + const float *initialSize = mSizeEnv.GetStart(); + size.Set( initialSize[0], initialSize[1], initialSize[2] ); + rotate.Zero(); +} + +void rvParticle::Bounce( rvBSE *effect, rvParticleTemplate *pt, + idVec3 endPos, idVec3 normal, float time ) { + idVec3 oldVelocity; + EvaluateVelocity( effect, oldVelocity, time - mMotionStartTime ); + oldVelocity = effect->GetCurrentAxis() * oldVelocity; + const idVec3 reflected = ( oldVelocity - + 2.0f * ( oldVelocity * normal ) * normal ) * pt->mBounce; + mVelocity = effect->GetCurrentAxisTransposed() * reflected; + mMotionStartTime = time; + mInitPos = effect->GetCurrentAxisTransposed() * + ( endPos - effect->GetCurrentOrigin() ); + + if ( mVelocity.LengthSqr() < BSE_BOUNCE_LIMIT && + normal * effect->GetGravityDir() < -idMath::SQRT_1OVER2 ) { + SetStationary( true ); + mVelocity.Zero(); + } +} + +void rvParticle::EvaluatePosition( const rvBSE *effect, idVec3 &pos, + float time ) { + if ( GetStationary() ) { + pos = mInitPos; + } else { + pos = mInitPos + time * mVelocity; + if ( GetHasOffset() ) { + rvAngles angle; + idVec3 offset; + mAngleEnv.Evaluate( time, angle.ToFloatPtr() ); + mOffsetEnv.Evaluate( time, offset.ToFloatPtr() ); + idMat3 angleAxis; + angle.ToMat3( angleAxis ); + pos += angleAxis * offset; + } + + const float halfTimeSquared = time * time * 0.5f; + pos += halfTimeSquared * mAcceleration; + if ( mFriction.LengthSqr() != 0.0f ) { + const float duration = mEndTime - mStartTime; + const float frictionScale = halfTimeSquared * + ( idMath::Exp( ( duration - halfTimeSquared ) / duration ) - 1.0f ) * + halfTimeSquared * ( 1.0f / 3.0f ); + pos += frictionScale * mFriction; + } + } + + if ( !GetLocked() ) { + const idMat3 axisDelta = mInitAxis * + effect->GetCurrentAxis().Transpose(); + pos = axisDelta * pos; + pos += effect->GetCurrentAxisTransposed() * + ( mInitEffectPos - effect->GetCurrentOrigin() ); + } +} + +void rvParticle::CheckTimeoutEffect( rvBSE *effect, + rvSegmentTemplate *st, float time ) { + if ( !st->GetEnabled() ) { + return; + } + rvParticleTemplate *pt = st->GetParticleTemplate(); + if ( pt->mNumTimeoutEffects == 0 ) { + return; + } + + const int random = rvRandom::irand( 0, pt->mNumTimeoutEffects - 1 ); + idVec3 position; + idVec3 velocity; + EvaluatePosition( effect, position, time - mMotionStartTime ); + EvaluateVelocity( effect, velocity, time - mMotionStartTime ); + velocity.Normalize(); + velocity = effect->GetCurrentAxis() * velocity; + position = effect->GetCurrentOrigin() + + effect->GetCurrentAxis() * position; + idMat3 axis; + velocity.ToMat3( axis ); + game->PlayEffect( pt->mTimeoutEffects[random], position, axis ); +} + +void rvParticle::CalcImpactPoint( idVec3 &endPos, const idVec3 &origin, + const idVec3 &motion, const idBounds &bounds, + const idVec3 &normal ) { + endPos = origin; + const idVec3 extents = bounds[1] - bounds[0]; + if ( extents.x == 0.0f || extents.y == 0.0f || extents.z == 0.0f || + motion.LengthSqr() == 0.0f ) { + return; + } + + idVec3 work( motion.x / extents.x, motion.y / extents.y, + motion.z / extents.z ); + work.Normalize(); + int dominantAxis = 0; + if ( idMath::Fabs( work.y ) >= idMath::Fabs( work.x ) && + idMath::Fabs( work.y ) >= idMath::Fabs( work.z ) ) { + dominantAxis = 1; + } else if ( idMath::Fabs( work.z ) >= idMath::Fabs( work.x ) && + idMath::Fabs( work.z ) >= idMath::Fabs( work.y ) ) { + dominantAxis = 2; + } + const float scale = 0.5f / idMath::Fabs( work[dominantAxis] ); + work.x *= extents.x * scale; + work.y *= extents.y * scale; + work.z *= extents.z * scale; + endPos += work + normal * BSE_SURFACE_OFFSET; +} + +void rvParticle::EmitSmokeParticles( rvBSE *effect, rvSegment *child, + float time ) { + const float futureTime = time + BSE_FUTURE; + while ( mLastTrailTime < futureTime ) { + rvSegmentTemplate *childTemplate = child->GetSegmentTemplate(); + if ( mLastTrailTime >= mStartTime && mLastTrailTime < mEndTime ) { + idVec3 position; + idVec3 velocity; + EvaluatePosition( effect, position, mLastTrailTime - mStartTime ); + EvaluateVelocity( effect, velocity, mLastTrailTime - mStartTime ); + velocity.Normalize(); + idMat3 axis; + velocity.ToMat3( axis ); + child->SpawnParticle( effect, childTemplate, mLastTrailTime, + position, axis ); + } + mLastTrailTime += child->AttenuateInterval( effect, childTemplate ); + } +} + +bool rvParticle::RunPhysics( rvBSE *effect, rvSegmentTemplate *st, + float time ) { + if ( !bse_physics.GetBool() || session->readDemo != NULL || + GetStationary() || !st->GetEnabled() ) { + return false; + } + rvParticleTemplate *pt = st->GetParticleTemplate(); + if ( !pt->GetHasPhysics() || + time - mMotionStartTime < BSE_PHYSICS_TIME_SAMPLE ) { + return false; + } + + idVec3 source; + idVec3 dest; + EvaluatePosition( effect, source, + time - mMotionStartTime - BSE_PHYSICS_TIME_SAMPLE ); + EvaluatePosition( effect, dest, time - mMotionStartTime ); + source = effect->GetCurrentOrigin() + effect->GetCurrentAxis() * source; + dest = effect->GetCurrentOrigin() + effect->GetCurrentAxis() * dest; + + trace_t trace; + idTraceModel *traceModel = pt->GetTraceModel(); + game->Translation( trace, source, dest, traceModel, + CONTENTS_SOLID | CONTENTS_RENDERMODEL ); + if ( trace.fraction >= 1.0f ) { + return false; + } + + if ( pt->mNumImpactEffects != 0 && + bse->CanPlayRateLimited( EC_IMPACT_PARTICLES ) ) { + idVec3 impactPoint = trace.endpos; + if ( traceModel != NULL ) { + const idVec3 motion = ( dest - source ) * trace.fraction; + CalcImpactPoint( impactPoint, trace.endpos, motion, + traceModel->bounds, trace.c.normal ); + } + const int random = rvRandom::irand( 0, pt->mNumImpactEffects - 1 ); + idMat3 impactAxis; + trace.c.normal.ToMat3( impactAxis ); + game->PlayEffect( pt->mImpactEffects[random], impactPoint, impactAxis ); + } + if ( pt->mBounce != 0.0f ) { + Bounce( effect, pt, trace.endpos, trace.c.normal, time ); + } + return pt->GetDeleteOnImpact(); +} diff --git a/src/bse/BSE_Particle.h b/src/bse/BSE_Particle.h new file mode 100644 index 0000000..62ae6a4 --- /dev/null +++ b/src/bse/BSE_Particle.h @@ -0,0 +1,790 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2007 Id Software, Inc. +// + + +#ifndef _BSE_PARTICLE_H_INC_ +#define _BSE_PARTICLE_H_INC_ + +#include "BSE_Envelope.h" +#include "BSE_SpawnDomains.h" +#include "../renderer/ModelManager.h" +#include "../renderer/RenderWorld.h" + +#define BSE_FUTURE ( 0.016f ) // How far into the future to check for particle spawning +#define BSE_TIME_EPSILON ( 0.002f ) // Edge condition checks +#define BSE_PHYSICS_TIME_SAMPLE ( 0.1f ) // Number of seconds to check the position delta for physics +#define BSE_MINIMUM_TRACE_DIST ( 16.0f ) // The square of the distance below which physics will not check +#define BSE_SURFACE_OFFSET ( 2.0f ) // Amount a collision is pushed back along the normal +#define BSE_BOUNCE_LIMIT ( 2500.0f ) // Square of velocity below which a particle stops bouncing +#define BSE_TRACE_OFFSET ( 0.02f ) // How much the bounce should move back from the endpos +#define MAX_PARTICLES ( 2048 ) // Max number of particles attached to an effect (arbitrary sanity check) +#define BSE_DENSITY_FACTOR ( 50.0f ) + +#define BSE_MAX_FORKS ( 16 ) + +#define BSE_MAX_DURATION ( 60.0f * 5.0f ) // 5 Minutes + +#define BSE_NUM_SPAWNABLE ( 4 ) // Number of random effects to choose from + +enum +{ + PTYPE_NONE = 0, // A non sprite - for sound and vision segments + PTYPE_SPRITE, // Simple 2D alpha blended quad + PTYPE_LINE, // 2D alpha blended line + PTYPE_ORIENTED, // 2D particle oriented in 3D - alpha blended + PTYPE_DECAL, // Hook into id's decal system + PTYPE_MODEL, // Model - must only have 1 surface + PTYPE_LIGHT, // Dynamic light - very expensive + PTYPE_ELECTRICITY, // A bolt of electricity + PTYPE_LINKED, // A series of linked lines + PTYPE_ORIENTEDLINKED, + PTYPE_DEBRIS, // A client side moveable entity spawned in the game + PTYPE_COUNT +}; + +// Defined classes +class rvParticle; +class rvSpriteParticle; +class rvLineParticle; +class rvOrientedParticle; +class rvElectricityParticle; +class rvDecalParticle; +class rvModelParticle; +class rvLightParticle; +class rvLinkedParticle; +class sdOrientedLinkedParticle; +class rvDebrisParticle; + +class rvParticleTemplate; +class rvParticleParms; + +// Referenced classes +class rvBSE; +class rvDeclEffect; +class rvSegment; +class rvSegmentTemplate; + +#define PTFLAG_STATIONARY BIT( 0 ) +#define PTFLAG_LOCKED BIT( 1 ) +#define PTFLAG_HAS_OFFSET BIT( 2 ) + +#define PTFLAG_PARSED BIT( 8 ) +#define PTFLAG_HAS_PHYSICS BIT( 9 ) +#define PTFLAG_DELETE_ON_IMPACT BIT( 10 ) +#define PTFLAG_GENERATED_NORMAL BIT( 11 ) +#define PTFLAG_GENERATED_ORG_NORMAL BIT( 12 ) +#define PTFLAG_FLIPPED_NORMAL BIT( 13 ) +#define PTFLAG_CALCED_NORMAL BIT( 14 ) +#define PTFLAG_ADDITIVE BIT( 15 ) +#define PTFLAG_GENERATED_LINE BIT( 16 ) +#define PTFLAG_SHADOWS BIT( 17 ) +#define PTFLAG_SPECULAR BIT( 18 ) +#define PTFLAG_LINKED BIT( 19 ) +#define PTFLAG_TILED BIT( 20 ) +#define PTFLAG_PERSIST BIT( 21 ) +#define PTFLAG_USELIGHTNING_AXIS BIT( 22 ) +#define PTFLAG_FADE_IN BIT( 23 ) +#define PTFLAG_USE_MATERIAL_COLOR BIT( 24 ) +#define PTFLAG_PARENTVEL BIT( 25 ) +#define PTFLAG_HAS_LINEHIT BIT( 26 ) +#define PTFLAG_INITED BIT( 27 ) + +// ============================================== +// Particle types (from multiple elements) +// ============================================== +class rvParticle +{ +public: + friend class rvParticleTemplate; + friend class rvSegment; + + rvParticle( void ) {} + virtual ~rvParticle( void ) {} + + void SetFlag( bool on, int flag ) { on ? mFlags |= flag : mFlags &= ~flag; } + bool GetFlag( int flag ) const { return ( mFlags & flag ) != 0 ; } + + int GetStationary( void ) const { return( ( mFlags & PTFLAG_STATIONARY ) ); } + int GetLocked( void ) const { return( ( mFlags & PTFLAG_LOCKED ) ); } + int GetHasOffset( void ) const { return( ( mFlags & PTFLAG_HAS_OFFSET ) ); } + + int GetGeneratedLine( void ) const { return( ( mFlags & PTFLAG_GENERATED_LINE ) ); } + int GetAdditive( void ) const { return( ( mFlags & PTFLAG_ADDITIVE ) ); } + int GetTiled( void ) const { return( ( mFlags & PTFLAG_TILED ) ); } + int GetPersist( void ) const { return( ( mFlags & PTFLAG_PERSIST ) ); } + + void SetStationary( bool stopped ) { SetFlag( stopped, PTFLAG_STATIONARY ); } + void SetLocked( bool locked ) { SetFlag( locked, PTFLAG_LOCKED ); } + void SetHasOffset( bool hasOffset ) { SetFlag( hasOffset, PTFLAG_HAS_OFFSET ); } + + void SetNext( rvParticle *next ) { mNext = next; } + rvParticle *GetNext( void ) const { return( mNext ); } + + ID_INLINE float GetDuration( void ) const { return( mEndTime - mStartTime ); } + void ExtendLife( float time ) { mEndTime = time; } + bool Expired( float time ) const { return( time >= mEndTime - BSE_TIME_EPSILON ); } + + void CalcImpactPoint( idVec3 &endPos, const idVec3 &origin, const idVec3 &motion, const idBounds &bounds, const idVec3 &normal ); + void SetOriginUsingEndOrigin( rvBSE *effect, rvParticleTemplate *pt, idVec3 *normal, idVec3 *centre ); + void HandleEndOrigin( rvBSE *effect, rvParticleTemplate *pt, idVec3 *normal = NULL, idVec3 *centre = NULL ); + void SetLengthUsingEndOrigin( rvBSE *effect, rvParticleParms &parms, float *length ); + void HandleEndLength( rvBSE *effect, rvParticleTemplate *pt, rvParticleParms &parms, float *length ); + void Bounce( rvBSE *effect, rvParticleTemplate *pt, idVec3 endPos, idVec3 normal, float time ); + bool RunPhysics( rvBSE *effect, rvSegmentTemplate *pt, float time ); + void CheckTimeoutEffect( rvBSE *effect, rvSegmentTemplate *st, float time ); + int HandleTint( const rvBSE *effect, idVec4 &colour, float alpha ); + void RenderQuadTrail( const rvBSE *effect, srfTriangles_t *tri, idVec3 offset, float fraction, idVec4 &colour, idVec3 &pos, bool first ); + void EmitSmokeParticles( rvBSE *effect, rvSegment *child, float time ); + void ScaleAngle( float constant ); + float GetEndTime( void ) const { return( mEndTime ); } + + virtual rvParticle *GetArrayEntry( int i ) const { assert( 0 ); return( NULL ); } + virtual int GetArrayIndex( rvParticle *p ) const { assert( 0 ); return( 0 ); } + + bool GetEvaluationTime( float time, float &evalTime, bool infinite = false ); + void EvaluatePosition( const rvBSE *effect, idVec3 &pos, float time ); + void EvaluateVelocity( const rvBSE *effect, idVec3 &velocity, float time ); + + ID_INLINE void EvaluateTint( const float time, idVec4 &dest ) { + mTintEnv.Evaluate( time, dest.ToFloatPtr() ); + mFadeEnv.Evaluate( time, &dest[3] ); + } + + ID_INLINE void EvaluateAngle( const float time, rvAngles &dest ) { + mAngleEnv.Evaluate( time, dest.ToFloatPtr() ); + } + + ID_INLINE void EvaluateOffset( const float time, idVec3 &dest ) { + mOffsetEnv.Evaluate( time, dest.ToFloatPtr() ); + } + + + virtual void EvaluateSize( float time, float *dest ) { assert( 0 ); } + virtual void EvaluateRotation( float time, float *dest ) { assert( 0 ); } + virtual void EvaluateLength( float time, idVec3 &dest ) { assert( 0 ); } + + + void InitTintEnv( rvEnvParms &env, float duration ) { mTintEnv.Init( env, duration ); } + void InitFadeEnv( rvEnvParms &env, float duration ) { mFadeEnv.Init( env, duration ); } + void InitAngleEnv( rvEnvParms &env, float duration ) { mAngleEnv.Init( env, duration ); } + void InitOffsetEnv( rvEnvParms &env, float duration ) { mOffsetEnv.Init( env, duration ); } + virtual void InitSizeEnv( rvEnvParms &env, float duration ) { assert( 0 ); } + virtual void InitRotationEnv( rvEnvParms &env, float duration ) { assert( 0 ); } + virtual void InitLengthEnv( rvEnvParms &env, float duration ) { assert( 0 ); } + + virtual float *GetInitSize( void ) { assert( 0 ); return( NULL ); } + virtual float *GetDestSize( void ) { assert( 0 ); return( NULL ); } + + virtual float *GetInitRotation( void ) { assert( 0 ); return( NULL ); } + virtual float *GetDestRotation( void ) { assert( 0 ); return( NULL ); } + virtual void ScaleRotation( float constant ) {} + + virtual float *GetInitLength( void ) { assert( 0 ); return( NULL ); } + virtual float *GetDestLength( void ) { assert( 0 ); return( NULL ); } + + void Attenuate( float atten, rvParticleParms &parms, rvEnvParms1 &result ); + void Attenuate( float atten, rvParticleParms &parms, rvEnvParms2 &result ); + void Attenuate( float atten, rvParticleParms &parms, rvEnvParms3 &result ); + void AttenuateFade( float atten, rvParticleParms &parms ); + virtual void AttenuateSize( float atten, rvParticleParms &parms ) {} + virtual void AttenuateLength( float atten, rvParticleParms &parms ) {} + + virtual void TransformLength( idVec3 normal ) {} + virtual void ScaleLength( float constant ) {} + virtual void GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ) { assert( 0 ); } + virtual void HandleTiling( rvParticleTemplate *pt ) {} + + virtual void HandleOrientation( rvAngles &angles ) {} + virtual void FinishSpawn( rvBSE *effect, rvSegment *segment, float birthTime, float fraction = 0.0f, const idVec3 &initOffset = vec3_origin, const idMat3 &initAxis = mat3_identity ); + virtual int Update( float time ) { return( 1 ); } + virtual bool Render( const rvBSE *effect, const idMat3 &view, srfTriangles_t *tri, float time, float override = 1.0f ) { return( false ); } + void DoRenderBurnTrail( rvBSE *effect, const idMat3 &view, srfTriangles_t *tri, float time ); + virtual void RenderBurnTrail( rvBSE *effect, const idMat3 &view, srfTriangles_t *tri, float time ) {} + virtual void RenderMotion( rvBSE *effect, srfTriangles_t *tri, const struct renderEffect_s *owner, float time ); + virtual bool InitLight( rvBSE *effect, rvSegmentTemplate *st, float time ) { return( false ); } + virtual bool PresentLight( rvBSE *effect, float time, bool infinite ) { return( false ); } + virtual bool Destroy( void ) { return( false ); } + virtual void SetModel( const char *modelName ) {} + virtual void SetupElectricity( rvParticleTemplate *pt ) {} + virtual void Refresh( rvBSE *effect, rvSegmentTemplate *st, rvParticleTemplate *pt ) {} + +protected: + // Can be altered in UpdateParticles + class rvParticle *mNext; + float mMotionStartTime; // World start time for motion calcs - reset on a bounce + + // could be stored as a delta from start + float mLastTrailTime; // Last time a smoke particle was emitted + + // need to restructure flags + int mFlags; + + // Fixed at spawn time + float mStartTime; // World start time of particle in seconds + float mEndTime; // End of particle's life + + float mTrailTime; // Length of trail + int mTrailCount; // Number of particles in trail + + // always between 0 and 1 inc + float mFraction; // Fraction along the line + + float mTextureScale; // Multiplier for the texture coord + + idVec3 mInitEffectPos; // Initial effect position when the particle is spawned + idMat3 mInitAxis; // Initial effect axis when the particle is spawned + + idVec3 mInitPos; // Position at time = 0 + idVec3 mNormal; + idVec3 mVelocity; // Initial velocity + idVec3 mAcceleration; // Initial acceleration + idVec3 mFriction; + + rvEnvParms3 mTintEnv; + rvEnvParms1 mFadeEnv; + rvEnvParms3 mAngleEnv; + rvEnvParms3 mOffsetEnv; +}; + +class rvSpriteParticle : public rvParticle +{ +public: + friend class rvParticleTemplate; + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateSize( float time, float *dest ) { + mSizeEnv.Evaluate( time, dest ); + } + virtual void EvaluateRotation( float time, float *dest ) { + mRotationEnv.Evaluate( time, dest ); + } + virtual void EvaluateLength( float time, idVec3 &dest ) {} + //virtual void EvaluateLength( rvEnvParms *length, const float time, float oneOverDuration, idVec3 &dest ) {} + + virtual void InitSizeEnv( rvEnvParms &env, float duration ) { mSizeEnv.Init( env, duration ); } + virtual float *GetInitSize( void ) { return( mSizeEnv.GetStart() ); } + virtual float *GetDestSize( void ) { return( mSizeEnv.GetEnd() ); } + + virtual void InitRotationEnv( rvEnvParms &env, float duration ) { mRotationEnv.Init( env, duration ); } + virtual float *GetInitRotation( void ) { return( mRotationEnv.GetStart() ); } + virtual float *GetDestRotation( void ) { return( mRotationEnv.GetEnd() ); } + + virtual void ScaleRotation( float constant ) { mRotationEnv.Scale( constant ); } + + virtual void AttenuateSize( float atten, rvParticleParms &parms ) { Attenuate( atten, parms, mSizeEnv ); } + + virtual void GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ); + virtual bool Render( const rvBSE *effect, const idMat3 &view, srfTriangles_t* tri, float time, float override = 1.0f ); + virtual void RenderBurnTrail( rvBSE *effect, const idMat3 &view, srfTriangles_t* tri, float time ) { DoRenderBurnTrail( effect, view, tri, time ); } + +protected: + // Fixed at spawn time + rvEnvParms2 mSizeEnv; + rvEnvParms1 mRotationEnv; +}; + +class rvLineParticle : public rvParticle +{ +public: + friend class rvParticleTemplate; + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateSize( float time, float *dest ) { + mSizeEnv.Evaluate( time, dest ); + } + virtual void EvaluateLength( float time, idVec3 &dest ) { + mLengthEnv.Evaluate( time, dest.ToFloatPtr() ); + } + + virtual void InitSizeEnv( rvEnvParms &env, float duration ) { mSizeEnv.Init( env, duration ); } + virtual float *GetInitSize( void ) { return( mSizeEnv.GetStart() ); } + virtual float *GetDestSize( void ) { return( mSizeEnv.GetEnd() ); } + + virtual void InitRotationEnv( rvEnvParms &env, float duration ) {} + virtual float *GetInitRotation( void ) { return( NULL ); } + virtual float *GetDestRotation( void ) { return( NULL ); } + virtual void SetRotation( void ) {} + + virtual void InitLengthEnv( rvEnvParms &env, float duration ) { mLengthEnv.Init( env, duration ); } + virtual float *GetInitLength( void ) { return( mLengthEnv.GetStart() ); } + virtual float *GetDestLength( void ) { return( mLengthEnv.GetEnd() ); } + virtual void TransformLength( idVec3 normal ) { mLengthEnv.Transform( normal ); } + virtual void ScaleLength( float constant ) { mLengthEnv.Scale( constant ); } + + virtual void AttenuateSize( float atten, rvParticleParms &parms ) { Attenuate( atten, parms, mSizeEnv ); } + virtual void AttenuateLength( float atten, rvParticleParms &parms ) { Attenuate( atten, parms, mLengthEnv ); } + + virtual void GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ); + virtual void HandleTiling( rvParticleTemplate *pt ); + virtual void FinishSpawn( rvBSE *effect, rvSegment *segment, float birthTime, float fraction = 0.0f, const idVec3 &initOffset = vec3_origin, const idMat3 &initAxis = mat3_identity ); + virtual bool Render( const rvBSE *effect, const idMat3 &view, srfTriangles_t* tri, float time, float override = 1.0f ); + virtual void RenderBurnTrail( rvBSE *effect, const idMat3 &view, srfTriangles_t* tri, float time ) { DoRenderBurnTrail( effect, view, tri, time ); } + virtual void Refresh( rvBSE *effect, rvSegmentTemplate *st, rvParticleTemplate *pt ); + +protected: + // Fixed at spawn time + rvEnvParms1 mSizeEnv; + rvEnvParms3 mLengthEnv; +}; + +class rvOrientedParticle : public rvSpriteParticle +{ +public: + friend class rvParticleTemplate; + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateRotation( float time, float *dest ) { + mRotationEnv.Evaluate( time, dest ); + } + virtual void EvaluateLength( float time, idVec3 &dest ) {} + + virtual void InitRotationEnv( rvEnvParms &env, float duration ) { mRotationEnv.Init( env, duration ); } + virtual float *GetInitRotation( void ) { return( mRotationEnv.GetStart() ); } + virtual float *GetDestRotation( void ) { return( mRotationEnv.GetEnd() ); } + + virtual void ScaleRotation( float constant ) { mRotationEnv.Scale( constant ); } + + virtual void GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ); + virtual void HandleOrientation( rvAngles &angles ) { mRotationEnv.Rotate( angles ); } + virtual bool Render( const rvBSE *effect, const idMat3 &view, srfTriangles_t* tri, float time, float override = 1.0f ); + +private: + // Fixed at spawn time + rvEnvParms3 mRotationEnv; +}; + +class rvElectricityParticle : public rvLineParticle +{ +public: + friend class rvParticleTemplate; + + int GetBoltCount( float length ); + void RenderBranch( const rvBSE *effect, struct SElecWork *work, idVec3 start, idVec3 end ); + void RenderLineSegment( const rvBSE *effect, struct SElecWork *work, idVec3 start, float startFraction ); + void ApplyShape( const rvBSE *effect, struct SElecWork *work, idVec3 start, idVec3 end, int count, float startFraction, float endFraction ); + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual int Update( float time ); + virtual bool Render( const rvBSE *effect, const idMat3 &view, srfTriangles_t *tri, float time, float override = 1.0f ); + + virtual void SetupElectricity( rvParticleTemplate *pt ); + +private: + // Alterable + int mNumBolts; + + // Fixed at spawn time + int mNumForks; + int mSeed; + idVec3 mForkSizeMins; + idVec3 mForkSizeMaxs; + idVec3 mJitterSize; + float mLastJitter; + float mJitterRate; + const idDeclTable *mJitterTable; +}; + +class rvDecalParticle : public rvSpriteParticle +{ +public: + friend class rvParticleTemplate; + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateSize( float time, float *dest ) {} + virtual void EvaluateLength( float time, idVec3 &dest ) {} + + virtual void InitSizeEnv( rvEnvParms &env, float duration ) {} + virtual void InitRotationEnv( rvEnvParms &env, float duration ) {} +private: +}; + +class rvModelParticle : public rvParticle +{ +public: + friend class rvParticleTemplate; + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateSize( float time, float *dest ) { + mSizeEnv.Evaluate( time, dest ); + } + virtual void EvaluateRotation( float time, float *dest ) { + mRotationEnv.Evaluate( time, dest ); + } + virtual void EvaluateLength( float time, idVec3 &dest ) {} + + virtual void InitSizeEnv( rvEnvParms &env, float duration ) { mSizeEnv.Init( env, duration ); } + virtual float *GetInitSize( void ) { return( mSizeEnv.GetStart() ); } + virtual float *GetDestSize( void ) { return( mSizeEnv.GetEnd() ); } + + virtual void InitRotationEnv( rvEnvParms &env, float duration ) { mRotationEnv.Init( env, duration ); } + virtual float *GetInitRotation( void ) { return( mRotationEnv.GetStart() ); } + virtual float *GetDestRotation( void ) { return( mRotationEnv.GetEnd() ); } + + virtual void GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ); + virtual bool Render( const rvBSE *effect, const idMat3 &view, srfTriangles_t* tri, float time, float override = 1.0f ); + virtual void SetModel( const char *modelName ); + + virtual void AttenuateSize( float atten, rvParticleParms &parms ) { Attenuate( atten, parms, mSizeEnv ); } + +private: + // Fixed at spawn time + rvEnvParms3 mSizeEnv; + rvEnvParms3 mRotationEnv; + + const idRenderModel *mModel; +}; + +class rvLightParticle : public rvParticle +{ +public: + friend class rvParticleTemplate; + + rvLightParticle( void ) { mLightDefHandle = -1; } + ~rvLightParticle( void ) { Destroy(); } + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateSize( float time, float *dest ) { + mSizeEnv.Evaluate( time, dest ); + } + virtual void EvaluateLength( float time, idVec3 &dest ) {} + + virtual void InitSizeEnv( rvEnvParms &env, float duration ) { mSizeEnv.Init( env, duration ); } + virtual float *GetInitSize( void ) { return( mSizeEnv.GetStart() ); } + virtual float *GetDestSize( void ) { return( mSizeEnv.GetEnd() ); } + + virtual void InitRotationEnv( rvEnvParms &env, float duration ) {} + virtual float *GetInitRotation( void ) { return( NULL ); } + virtual float *GetDestRotation( void ) { return( NULL ); } + + virtual void AttenuateSize( float atten, rvParticleParms &parms ) { Attenuate( atten, parms, mSizeEnv ); } + + virtual void GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ); + virtual bool InitLight( rvBSE *effect, rvSegmentTemplate *st, float time ); + virtual bool PresentLight( rvBSE *effect, float time, bool infinite ); + virtual bool Destroy( void ); + +private: + // Fixed at spawn time + rvEnvParms3 mSizeEnv; + + // Alterable + qhandle_t mLightDefHandle; + renderLight_t mLight; +}; + +class rvLinkedParticle : public rvParticle +{ +public: + friend class rvParticleTemplate; + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateSize( float time, float *dest ) { + mSizeEnv.Evaluate( time, dest ); + } + virtual void EvaluateLength( float time, idVec3 &dest ) {} + + virtual void InitSizeEnv( rvEnvParms &env, float duration ) { mSizeEnv.Init( env, duration ); } + virtual float *GetInitSize( void ) { return( mSizeEnv.GetStart() ); } + virtual float *GetDestSize( void ) { return( mSizeEnv.GetEnd() ); } + + virtual void InitRotationEnv( rvEnvParms &env, float duration ) {} + virtual float *GetInitRotation( void ) { return( NULL ); } + virtual float *GetDestRotation( void ) { return( NULL ); } + + virtual void AttenuateSize( float atten, rvParticleParms &parms ) { Attenuate( atten, parms, mSizeEnv ); } + + virtual void FinishSpawn( rvBSE *effect, rvSegment *segment, float birthTime, float fraction = 0.0f, const idVec3 &initOffset = vec3_origin, const idMat3 &initAxis = mat3_identity ); + virtual void GetSpawnInfo( idVec4 &tint, idVec3 &size, idVec3 &rotate ) {} + virtual void HandleTiling( rvParticleTemplate *pt ); + virtual bool Render( const rvBSE *effect, const idMat3 &view, srfTriangles_t* tri, float time, float override = 1.0f ); + +private: + // Fixed at spawn time + rvEnvParms1 mSizeEnv; +}; + +class sdOrientedLinkedParticle : public rvLinkedParticle +{ +public: + virtual bool Render( const rvBSE *effect, rvParticleTemplate *pt, const idMat3 &view, srfTriangles_t* tri, float time, float override = 1.0f ); +}; + +class rvDebrisParticle : public rvParticle +{ +public: + friend class rvParticleTemplate; + + virtual rvParticle *GetArrayEntry( int i ) const; + virtual int GetArrayIndex( rvParticle *p ) const; + + virtual void EvaluateRotation( float time, float *dest ) { mRotationEnv.Evaluate( time, dest ); } + + virtual void InitRotationEnv( rvEnvParms &env, float duration ) { mRotationEnv.Init( env, duration ); } + virtual float *GetInitRotation( void ) { return( mRotationEnv.GetStart() ); } + virtual float *GetDestRotation( void ) { return( mRotationEnv.GetEnd() ); } + + virtual void ScaleRotation( float constant ) { mRotationEnv.Scale( constant ); } + virtual void FinishSpawn( rvBSE *effect, rvSegment *segment, float birthTime, float fraction = 0.0f, const idVec3 &initOffset = vec3_origin, const idMat3 &initAxis = mat3_identity ); + +private: + // Fixed at spawn time + rvEnvParms3 mRotationEnv; +}; + +// ================================================================================================ + +struct rvTrailInfo { + short mTrailType; + byte mStatic; + byte mPad; + idStr mTrailTypeName; + const idMaterial *mTrailMaterial; + idVec2 mTrailTime; // Length of trial in seconds + idVec2 mTrailCount; // Number of particles in trail + float mTrailScale; // Width of the motion trails will be particleSize scaled by this + + rvTrailInfo() : mStatic(0) { + } +}; + +struct rvElectricityInfo { + int mNumForks; // Number of forks for + byte mStatic; + byte mPad; + idVec3 mForkSizeMins; + idVec3 mForkSizeMaxs; + idVec3 mJitterSize; // Amount of jitter for the electricity + float mJitterRate; + const idDeclTable *mJitterTable; // The envelope for the jitter in the lightning bolt + + rvElectricityInfo() : mStatic(0) { + } +}; + +class rvParticleTemplate +{ +public: + friend class rvParticle; + friend class rvSpriteParticle; + friend class rvLineParticle; + friend class rvOrientedParticle; + friend class rvElectricityParticle; + friend class rvModelParticle; + friend class rvLightParticle; + friend class rvDebrisParticle; + friend class rvParticleTemplateWrapper; + friend class rvDeclEffectEditLocal; + friend class rvLinkedParticle; + friend class sdOrientedLinkedParticle; + + rvParticleTemplate( void ); + ~rvParticleTemplate( void ) {} + + bool operator== ( const rvParticleTemplate& a ) const { return( Compare( a ) ); } + bool operator!= ( const rvParticleTemplate& a ) const { return( !Compare( a ) ); } + + void SetFlag( bool on, int flag ) { on ? mFlags |= flag : mFlags &= ~flag; } + bool GetFlag( int flag ) const { return ( mFlags & flag ) != 0; } + bool GetParsed( void ) const { return( !!( mFlags & PTFLAG_PARSED ) ); } + bool GetHasPhysics( void ) const { return( !!( mFlags & PTFLAG_HAS_PHYSICS ) ); } + bool GetHasLineHit( void ) const { return( !!( mFlags & PTFLAG_HAS_LINEHIT ) ); } + bool GetDeleteOnImpact( void ) const { return( !!( mFlags & PTFLAG_DELETE_ON_IMPACT ) ); } + bool GetGeneratedNormal( void ) const { return( !!( mFlags & PTFLAG_GENERATED_NORMAL ) ); } + bool GetGeneratedOriginNormal( void ) const { return( !!( mFlags & PTFLAG_GENERATED_ORG_NORMAL ) ); } + bool GetFlippedNormal( void ) const { return( !!( mFlags & PTFLAG_FLIPPED_NORMAL ) ); } + bool GetCalculatedNormal( void ) const { return( !!( mFlags & PTFLAG_CALCED_NORMAL ) ); } + bool GetAdditive( void ) const { return( !!( mFlags & PTFLAG_ADDITIVE ) ); } + bool GetGeneratedLine( void ) const { return( !!( mFlags & PTFLAG_GENERATED_LINE ) ); } + bool GetShadows( void ) const { return( !!( mFlags & PTFLAG_SHADOWS ) ); } + bool GetSpecular( void ) const { return( !!( mFlags & PTFLAG_SPECULAR ) ); } + bool GetLinked( void ) const { return( !!( mFlags & PTFLAG_LINKED ) ); } + bool GetTiled( void ) const { return( !!( mFlags & PTFLAG_TILED ) ); } + bool GetPersist( void ) const { return( !!( mFlags & PTFLAG_PERSIST ) ); } + bool GetParentVelocity( void ) const { return( !!( mFlags & PTFLAG_PARENTVEL ) ); } + + void SetParsed( bool parsed ) { SetFlag( parsed, PTFLAG_PARSED ); } + void SetHasPhysics( bool hasPhysics ) { SetFlag( hasPhysics, PTFLAG_HAS_PHYSICS ); } + void SetHasLineHit( bool hasLH ) { SetFlag( hasLH, PTFLAG_HAS_LINEHIT ); } + void SetDeleteOnImpact( bool deleteOnImpact ) { SetFlag( deleteOnImpact, PTFLAG_DELETE_ON_IMPACT ); } + void SetGeneratedNormal( bool generatedNormal ) { SetFlag( generatedNormal, PTFLAG_GENERATED_NORMAL ); } + void SetGeneratedOriginNormal( bool generatedNormal ) { SetFlag( generatedNormal, PTFLAG_GENERATED_ORG_NORMAL ); } + void SetFlippedNormal( bool flippedNormal ) { SetFlag( flippedNormal, PTFLAG_FLIPPED_NORMAL ); } + void SetCalculatedNormal( bool calcedNormal ) { SetFlag( calcedNormal, PTFLAG_CALCED_NORMAL ); } + void SetAdditive( bool additive ) { SetFlag( additive, PTFLAG_ADDITIVE ); } + void SetGeneratedLine( bool generatedLine ) { SetFlag( generatedLine, PTFLAG_GENERATED_LINE ); } + void SetShadows( bool shadows ) { SetFlag( shadows, PTFLAG_SHADOWS ); } + void SetSpecular( bool specular ) { SetFlag( specular, PTFLAG_SPECULAR ); } + void SetLinked( bool linked ) { SetFlag( linked, PTFLAG_LINKED ); } + void SetTiled( bool tiled ) { SetFlag( tiled, PTFLAG_TILED ); } + void SetPersist( bool tiled ) { SetFlag( tiled, PTFLAG_PERSIST ); } + void SetParentVelocity( bool pv ) { SetFlag( pv, PTFLAG_PARENTVEL ); } + + void SetType( int type ) { mType = type; } + int GetType( void ) const { return( mType ); } + + const idMaterial *GetMaterial( void ) const { return( mMaterial ); } + void SetMaterial( idMaterial *material ) { mMaterial = material; } + + idRenderModel *GetModel( void ) const { return renderModelManager->FindModel( mModelName ); } + idTraceModel *GetTraceModel( void ) const; + + const char *GetEntityDefName( void ) const { return( mEntityDefName ); } + + float GetGravity( void ) const { return( rvRandom::flrand( mGravity[0], mGravity[1] ) ); } + float GetTiling( void ) const { return( mTiling ); } + int GetTrailType( void ) const { return( mTrailType ); } + const idMaterial *GetTrailMaterial( void ) const { return( mTrailMaterial ); } + float GetTrailTime( void ) const { return( rvRandom::flrand( mTrailTime[0], mTrailTime[1] ) ); } + float GetMaxTrailTime( void ) const { return( mTrailTime[1] ); } + int GetTrailCount( void ) const; + int GetMaxTrailCount( void ) const { return( ( int )ceilf( mTrailCount[1] ) + 1 ); } + float GetDuration( void ) const { return( rvRandom::flrand( mDuration[0], mDuration[1] ) ); } + float GetMaxDuration( void ) const { return( mDuration[1] ); } + int GetNumTimeoutEffects( void ) const { return( mNumTimeoutEffects ); } + bool HasTrail( void ) const { return( mTrailType != 0 && !mTrailTypeName.IsEmpty() ); } + + int GetVertexCount( void ) const { return( mVertexCount ); } + int GetIndexCount( void ) const { return( mIndexCount ); } + + float GetMaxParmValue( rvParticleParms &spawn, rvParticleParms &death, rvEnvParms &envelope ); + ID_INLINE float GetMaxSize( void ) { return GetMaxParmValue( mSpawnSize, mDeathSize, mSizeEnvelope ); } + ID_INLINE float GetMaxOffset( void ) { return GetMaxParmValue( mSpawnOffset, mDeathOffset, mOffsetEnvelope ); } + ID_INLINE float GetMaxLength( void ) { return GetMaxParmValue( mSpawnLength, mDeathLength, mLengthEnvelope ); } + void EvaluateSimplePosition( idVec3 &pos, float time, float lifeTime, idVec3 &initPos, idVec3 &velocity, idVec3 &acceleration, idVec3 &friction ); + float GetFurthestDistance( void ); + float CostTrail( float cost ) const; + const idStr &GetTrailTypeName( void ) const { return( mTrailTypeName ); } + float GetSpawnVolume( rvBSE *effect ); + bool UsesEndOrigin( void ); + bool GetVector( idLexer *src, int count, idVec3 &result ); + void SetParameterCounts( void ); + bool ParseMotionParms( idLexer *src, int count, rvEnvParms &parms ); + bool ParseSpawnParms( rvDeclEffect *effect, idLexer *src, rvParticleParms &parms, int count ); + bool ParseMotionDomains( rvDeclEffect *effect, idLexer *src ); + bool CheckCommonParms( idLexer *src, rvParticleParms &parms ); + bool ParseSpawnDomains( rvDeclEffect *effect, idLexer *src ); + bool ParseDeathDomains( rvDeclEffect *effect, idLexer *src ); + bool ParseImpact( rvDeclEffect *effect, idLexer *src ); + bool ParseTimeout( rvDeclEffect *effect, idLexer *src ); + bool ParseBlendParms( rvDeclEffect *effect, idLexer *src ); + bool Parse( rvDeclEffect *effect, idLexer *src ); + void Init( void ); + void Finish( void ); + rvParticleTemplate &operator=( const rvParticleTemplate © ); +private: + bool Compare( const rvParticleTemplate& a ) const; + void FixupParms( rvParticleParms &parms ); + + int mFlags; + int mType; // Type of particle + + const idMaterial *mMaterial; + idStr mMaterialName; + idStr mModelName; + idStr mEntityDefName; + int mTraceModelIndex; + + idVec2 mGravity; + idVec2 mSoundVolume; + idVec2 mFreqShift; + float mTiling; + int mTrailType; + idStr mTrailTypeName; + const idMaterial *mTrailMaterial; + idStr mTrailMaterialName; + idVec2 mTrailTime; + idVec2 mTrailCount; + float mBounce; + idVec2 mDuration; + idVec3 mCentre; // Centre of bounds for normal generation + int mNumForks; + idVec3 mForkSizeMins; + idVec3 mForkSizeMaxs; + idVec3 mJitterSize; + float mJitterRate; + const idDeclTable *mJitterTable; + int mNumSizeParms; + int mNumRotateParms; + int mVertexCount; + int mIndexCount; + + // Spawn info + rvParticleParms mSpawnPosition; + rvParticleParms mSpawnDirection; + rvParticleParms mSpawnVelocity; + rvParticleParms mSpawnAcceleration; + rvParticleParms mSpawnFriction; + rvParticleParms mSpawnTint; + rvParticleParms mSpawnFade; + rvParticleParms mSpawnSize; + rvParticleParms mSpawnRotate; + rvParticleParms mSpawnAngle; + rvParticleParms mSpawnOffset; + rvParticleParms mSpawnLength; + + // Motion info + rvEnvParms mTintEnvelope; + rvEnvParms mFadeEnvelope; + rvEnvParms mSizeEnvelope; + rvEnvParms mRotateEnvelope; + rvEnvParms mAngleEnvelope; + rvEnvParms mOffsetEnvelope; + rvEnvParms mLengthEnvelope; + + // Death (end condition) info + rvParticleParms mDeathTint; + rvParticleParms mDeathFade; + rvParticleParms mDeathSize; + rvParticleParms mDeathRotate; + rvParticleParms mDeathAngle; + rvParticleParms mDeathOffset; + rvParticleParms mDeathLength; + + // Misc info + int mNumImpactEffects; + const rvDeclEffect *mImpactEffects[BSE_NUM_SPAWNABLE]; + int mNumTimeoutEffects; + const rvDeclEffect *mTimeoutEffects[BSE_NUM_SPAWNABLE]; +}; + +#endif //_BSE_PARTICLE_H_INC_ diff --git a/src/bse/BSE_Render.cpp b/src/bse/BSE_Render.cpp new file mode 100644 index 0000000..98203d0 --- /dev/null +++ b/src/bse/BSE_Render.cpp @@ -0,0 +1,411 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" +#include "../renderer/tr_local.h" +#include "../renderer/rvMesh.h" + +// Retail compilation unit: BSE_Render.obj. Implementations are recovered here. + +bool rvParticle::GetEvaluationTime( float time, float &evalTime, bool infinite ) { + evalTime = time - mStartTime; + if ( time >= mEndTime - BSE_TIME_EPSILON ) { + evalTime = mEndTime - mStartTime - BSE_TIME_EPSILON; + } + + if ( infinite ) { + return true; + } + return time > mStartTime - BSE_TIME_EPSILON && time < mEndTime; +} + +int rvParticle::HandleTint( const rvBSE *effect, idVec4 &colour, float alpha ) { + idVec4 tint; + if ( mFlags & PTFLAG_ADDITIVE ) { + const float particleAlpha = colour.w * alpha; + tint.Set( effect->GetRed() * colour.x * particleAlpha, + effect->GetGreen() * colour.y * particleAlpha, + effect->GetBlue() * colour.z * particleAlpha, 1.0f ); + } else { + tint.Set( effect->GetRed() * colour.x, + effect->GetGreen() * colour.y, + effect->GetBlue() * colour.z, + effect->GetAlpha() * colour.w * alpha ); + } + + const float brightness = effect->GetBrightness(); + tint.x *= brightness; + tint.y *= brightness; + tint.z *= brightness; + return static_cast( PackColor( tint ) ); +} + +void rvParticle::RenderQuadTrail( const rvBSE *effect, srfTriangles_t *tri, + idVec3 offset, float fraction, idVec4 &colour, idVec3 &pos, bool first ) { + const dword color = HandleTint( effect, colour, 1.0f ); + idDrawVert *vert = &tri->verts[tri->numVerts]; + + vert[0].xyz = pos + offset; + vert[0].st.Set( 0.0f, fraction ); + vert[0].SetColor( color ); + vert[1].xyz = pos - offset; + vert[1].st.Set( 1.0f, fraction ); + vert[1].SetColor( color ); + + if ( !first ) { + int *indexes = &tri->indexes[tri->numIndexes]; + indexes[0] = tri->numVerts - 2; + indexes[1] = tri->numVerts - 1; + indexes[2] = tri->numVerts; + indexes[3] = tri->numVerts - 1; + indexes[4] = tri->numVerts; + indexes[5] = tri->numVerts + 1; + tri->numIndexes += 6; + } + tri->numVerts += 2; +} + +void rvParticle::RenderMotion( rvBSE *effect, srfTriangles_t *tri, + const renderEffect_t *owner, float time ) { + if ( mTrailCount == 0 ) { + return; + } + + const float trailStart = Max( time - mTrailTime, mStartTime ); + const float delta = time - trailStart; + if ( delta <= BSE_TIME_EPSILON ) { + return; + } + + idVec4 colour; + EvaluateTint( time - mStartTime, colour ); + const float fade = colour.w; + idVec3 size; + EvaluateSize( time - mStartTime, size.ToFloatPtr() ); + + idVec3 position; + EvaluatePosition( effect, position, time - mMotionStartTime ); + idVec3 localView = ( effect->GetViewOrg() - owner->origin ) * owner->axis; + localView -= mInitPos; + idVec3 offset = mVelocity.Cross( localView ); + if ( offset.LengthSqr() == 0.0f ) { + offset = idVec3( 0.0f, 1.0f, 0.0f ).Cross( localView ); + } + offset.Normalize(); + offset *= size.x * 0.5f; + + for ( int trail = 0; trail < mTrailCount; ++trail ) { + const float fraction = static_cast( trail ) / + static_cast( mTrailCount ); + colour.w = ( 1.0f - fraction ) * fade; + RenderQuadTrail( effect, tri, offset, fraction, colour, position, trail == 0 ); + EvaluatePosition( effect, position, + time - fraction * delta - mMotionStartTime ); + } + + colour.w = 0.0f; + RenderQuadTrail( effect, tri, offset, 1.0f, colour, position, false ); +} + +bool rvSpriteParticle::Render( const rvBSE *effect, const idMat3 &view, + srfTriangles_t *tri, float time, float override ) { + float evalTime; + if ( !GetEvaluationTime( time, evalTime ) ) { + return false; + } + + idVec4 tint; + EvaluateTint( evalTime, tint ); + idVec2 size; + EvaluateSize( evalTime, size.ToFloatPtr() ); + float rotation; + EvaluateRotation( evalTime, &rotation ); + idVec3 position; + EvaluatePosition( effect, position, time - mMotionStartTime ); + const dword color = HandleTint( effect, tint, override ); + + const idVec2 &spriteSize = effect->GetSpriteSize(); + if ( spriteSize.x > FLT_EPSILON || spriteSize.y > FLT_EPSILON ) { + size = spriteSize; + } + + float sine; + float cosine; + idMath::SinCos( rotation, sine, cosine ); + const idVec3 left = ( cosine * view[1] - sine * view[2] ) * size.x; + const idVec3 up = ( sine * view[1] + cosine * view[2] ) * size.y; + idDrawVert *vert = &tri->verts[tri->numVerts]; + + vert[0].xyz = position - left; + vert[0].st.Set( 0.0f, 0.0f ); + vert[0].normal = position; + vert[0].SetColor( color ); + vert[1].xyz = position - up; + vert[1].st.Set( 1.0f, 0.0f ); + vert[1].normal = position; + vert[1].SetColor( color ); + vert[2].xyz = position + left; + vert[2].st.Set( 1.0f, 1.0f ); + vert[2].normal = position; + vert[2].SetColor( color ); + vert[3].xyz = position + up; + vert[3].st.Set( 0.0f, 1.0f ); + vert[3].normal = position; + vert[3].SetColor( color ); + + int *indexes = &tri->indexes[tri->numIndexes]; + indexes[0] = tri->numVerts; + indexes[1] = tri->numVerts + 1; + indexes[2] = tri->numVerts + 2; + indexes[3] = tri->numVerts; + indexes[4] = tri->numVerts + 2; + indexes[5] = tri->numVerts + 3; + tri->numVerts += 4; + tri->numIndexes += 6; + return true; +} + +bool rvLineParticle::Render( const rvBSE *effect, const idMat3 &view, + srfTriangles_t *tri, float time, float override ) { + float evalTime; + if ( !GetEvaluationTime( time, evalTime ) ) { + return false; + } + + idVec4 tint; + EvaluateTint( evalTime, tint ); + float size; + EvaluateSize( evalTime, &size ); + idVec3 length; + EvaluateLength( evalTime, length ); + idVec3 position; + EvaluatePosition( effect, position, time - mMotionStartTime ); + + if ( !( mFlags & PTFLAG_LOCKED ) ) { + length *= mInitAxis * effect->GetCurrentAxis(); + } + if ( mFlags & PTFLAG_GENERATED_LINE ) { + idVec3 velocity; + EvaluateVelocity( effect, velocity, time - mMotionStartTime ); + velocity.Normalize(); + length = velocity * length.Length(); + } + + const idVec3 end = position + length; + const idVec3 localView = view[0] - ( position + length * 0.5f ); + idVec3 offset = length.Cross( localView ); + offset.Normalize(); + offset *= size; + const dword color = HandleTint( effect, tint, override ); + idDrawVert *vert = &tri->verts[tri->numVerts]; + + vert[0].xyz = position + offset; + vert[0].st.Set( 0.0f, 0.0f ); + vert[0].normal = position; + vert[0].SetColor( color ); + vert[1].xyz = position - offset; + vert[1].st.Set( 0.0f, 1.0f ); + vert[1].normal = position; + vert[1].SetColor( color ); + vert[2].xyz = end - offset; + vert[2].st.Set( mTextureScale, 1.0f ); + vert[2].normal = position; + vert[2].SetColor( color ); + vert[3].xyz = end + offset; + vert[3].st.Set( mTextureScale, 0.0f ); + vert[3].normal = position; + vert[3].SetColor( color ); + + int *indexes = &tri->indexes[tri->numIndexes]; + indexes[0] = tri->numVerts; + indexes[1] = tri->numVerts + 1; + indexes[2] = tri->numVerts + 2; + indexes[3] = tri->numVerts; + indexes[4] = tri->numVerts + 2; + indexes[5] = tri->numVerts + 3; + tri->numVerts += 4; + tri->numIndexes += 6; + return true; +} + +bool rvOrientedParticle::Render( const rvBSE *effect, const idMat3 &view, + srfTriangles_t *tri, float time, float override ) { + float evalTime; + if ( !GetEvaluationTime( time, evalTime ) ) { + return false; + } + + idVec4 tint; + EvaluateTint( evalTime, tint ); + idVec2 size; + EvaluateSize( evalTime, size.ToFloatPtr() ); + idVec3 rotation; + EvaluateRotation( evalTime, rotation.ToFloatPtr() ); + idVec3 position; + EvaluatePosition( effect, position, time - mMotionStartTime ); + idMat3 transform; + rvAngles( rotation ).ToMat3( transform ); + const idVec3 left = -transform[1] * size.x; + const idVec3 up = transform[2] * size.y; + const dword color = HandleTint( effect, tint, override ); + idDrawVert *vert = &tri->verts[tri->numVerts]; + + vert[0].xyz = position - left; + vert[0].st.Set( 0.0f, 0.0f ); + vert[0].normal = position; + vert[0].SetColor( color ); + vert[1].xyz = position - up; + vert[1].st.Set( 1.0f, 0.0f ); + vert[1].normal = position; + vert[1].SetColor( color ); + vert[2].xyz = position + left; + vert[2].st.Set( 1.0f, 1.0f ); + vert[2].normal = position; + vert[2].SetColor( color ); + vert[3].xyz = position + up; + vert[3].st.Set( 0.0f, 1.0f ); + vert[3].normal = position; + vert[3].SetColor( color ); + + int *indexes = &tri->indexes[tri->numIndexes]; + indexes[0] = tri->numVerts; + indexes[1] = tri->numVerts + 1; + indexes[2] = tri->numVerts + 2; + indexes[3] = tri->numVerts; + indexes[4] = tri->numVerts + 2; + indexes[5] = tri->numVerts + 3; + tri->numVerts += 4; + tri->numIndexes += 6; + return true; +} + +bool rvModelParticle::Render( const rvBSE *effect, const idMat3 &view, + srfTriangles_t *tri, float time, float override ) { + float evalTime; + if ( !GetEvaluationTime( time, evalTime ) || mModel == NULL ) { + return false; + } + + idVec4 tint; + EvaluateTint( evalTime, tint ); + idVec3 size; + EvaluateSize( evalTime, size.ToFloatPtr() ); + idVec3 rotation; + EvaluateRotation( evalTime, rotation.ToFloatPtr() ); + idVec3 position; + EvaluatePosition( effect, position, time - mMotionStartTime ); + const dword color = HandleTint( effect, tint, override ); + + idMat3 transform; + rvAngles( rotation ).ToMat3( transform ); + if ( !( mFlags & PTFLAG_LOCKED ) ) { + transform *= mInitAxis * effect->GetCurrentAxis(); + } + + const modelSurface_t *surface = mModel->Surface( 0 ); + if ( surface == NULL || surface->geometry == NULL ) { + return true; + } + srfTriangles_t *source = surface->geometry; + if ( source->primBatchMesh != NULL ) { + const idMat4 modelTransform( + transform[0][0] * size.x, transform[1][0] * size.x, + transform[2][0] * size.x, position.x, + transform[0][1] * size.y, transform[1][1] * size.y, + transform[2][1] * size.y, position.y, + transform[0][2] * size.z, transform[1][2] * size.z, + transform[2][2] * size.z, position.z, + 0.0f, 0.0f, 0.0f, 1.0f ); + unsigned char colorAdd[4]; + memcpy( colorAdd, &color, sizeof( colorAdd ) ); + rvMesh *mesh = source->primBatchMesh; + mesh->TransformTriangles( &tri->verts[tri->numVerts], + &tri->indexes[tri->numIndexes], modelTransform, 32, colorAdd, + tri->numVerts ); + tri->numVerts += mesh->m_numDrawVertices; + tri->numIndexes += mesh->m_numDrawIndices; + return true; + } + + idDrawVert *destVerts = &tri->verts[tri->numVerts]; + for ( int vertex = 0; vertex < source->numVerts; ++vertex ) { + destVerts[vertex] = source->verts[vertex]; + destVerts[vertex].SetColor( color ); + destVerts[vertex].xyz *= transform; + destVerts[vertex].xyz.x *= size.x; + destVerts[vertex].xyz.y *= size.y; + destVerts[vertex].xyz.z *= size.z; + destVerts[vertex].xyz += position; + } + for ( int index = 0; index < source->numIndexes; ++index ) { + tri->indexes[tri->numIndexes + index] = + tri->numVerts + source->indexes[index]; + } + tri->numVerts += source->numVerts; + tri->numIndexes += source->numIndexes; + return true; +} + +bool rvLinkedParticle::Render( const rvBSE *effect, const idMat3 &view, + srfTriangles_t *tri, float time, float override ) { + float evalTime; + if ( !GetEvaluationTime( time, evalTime ) ) { + return false; + } + + idVec4 tint; + EvaluateTint( evalTime, tint ); + float size; + EvaluateSize( evalTime, &size ); + idVec3 position; + EvaluatePosition( effect, position, time - mMotionStartTime ); + const idVec3 offset = view[1] * size; + const dword color = HandleTint( effect, tint, override ); + idDrawVert *vert = &tri->verts[tri->numVerts]; + + vert[0].xyz = position + offset; + vert[0].st.Set( mFraction * mTextureScale, 0.0f ); + vert[0].normal = position; + vert[0].SetColor( color ); + vert[1].xyz = position - offset; + vert[1].st.Set( mFraction * mTextureScale, 1.0f ); + vert[1].normal = position; + vert[1].SetColor( color ); + + if ( tri->numVerts > 0 ) { + int *indexes = &tri->indexes[tri->numIndexes]; + indexes[0] = tri->numVerts - 2; + indexes[1] = tri->numVerts - 1; + indexes[2] = tri->numVerts; + indexes[3] = tri->numVerts - 1; + indexes[4] = tri->numVerts + 1; + indexes[5] = tri->numVerts; + tri->numIndexes += 6; + } + tri->numVerts += 2; + return true; +} diff --git a/src/bse/BSE_Segment.cpp b/src/bse/BSE_Segment.cpp new file mode 100644 index 0000000..401990a --- /dev/null +++ b/src/bse/BSE_Segment.cpp @@ -0,0 +1,544 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" +#include "../game/Game.h" +#include "../renderer/tr_local.h" +#include "../renderer/Model_local.h" + +// Retail compilation unit: BSE_Segment.obj. + +float rvSegment::mSegmentBaseCosts[SEG_COUNT] = { + 0.0f, 5.0f, 20.0f, 20.0f, 5.0f, 50.0f, 20.0f, 100.0f, 1.0f, 50.0f, 5.0f, 50.0f +}; + +void rvModelParticle::SetModel( const char *modelName ) { + mModel = renderModelManager->FindModel( modelName ); +} + +void rvParticle::DoRenderBurnTrail( rvBSE *effect, const idMat3 &view, srfTriangles_t *tri, float time ) { + if ( mTrailCount <= 0 || mTrailTime <= 0.0f ) { return; } + const float delta = mTrailTime / mTrailCount; + for ( int i = 1; i <= mTrailCount; ++i ) { + const float trailTime = time - i * delta; + if ( trailTime >= mStartTime && trailTime < mEndTime ) { + Render( effect, view, tri, trailTime, (float)( mTrailCount - i ) / mTrailCount ); + } + } +} + +rvSegment::~rvSegment() { + if ( !mParticles ) { + return; + } + rvSegmentTemplate *st = GetSegmentTemplate(); + const int type = st ? st->GetParticleTemplate()->GetType() : PTYPE_SPRITE; + switch ( type ) { + case PTYPE_LINE: delete[] static_cast( mParticles ); break; + case PTYPE_ORIENTED: delete[] static_cast( mParticles ); break; + case PTYPE_DECAL: delete[] static_cast( mParticles ); break; + case PTYPE_MODEL: delete[] static_cast( mParticles ); break; + case PTYPE_LIGHT: delete[] static_cast( mParticles ); break; + case PTYPE_ELECTRICITY: delete[] static_cast( mParticles ); break; + case PTYPE_LINKED: delete[] static_cast( mParticles ); break; + case PTYPE_DEBRIS: delete[] static_cast( mParticles ); break; + default: delete[] static_cast( mParticles ); break; + } + mParticles = NULL; + mFreeHead = NULL; + mUsedHead = NULL; +} + +rvSegmentTemplate *rvSegment::GetSegmentTemplate() { + return mEffectDecl ? const_cast( mEffectDecl )->GetSegmentTemplate( mSegmentTemplateHandle ) : NULL; +} + +bool rvSegment::GetLocked() { + rvSegmentTemplate *st = GetSegmentTemplate(); + return st && st->GetLocked(); +} + +void rvSegment::ValidateSpawnRates() { + mSecondsPerParticle.y = idMath::ClampFloat( BSE_TIME_EPSILON, BSE_MAX_DURATION, mSecondsPerParticle.y ); + mSecondsPerParticle.x = idMath::ClampFloat( mSecondsPerParticle.y, BSE_MAX_DURATION, mSecondsPerParticle.x ); +} + +void rvSegment::GetSecondsPerParticle( rvBSE *effect, rvSegmentTemplate *st, rvParticleTemplate *pt ) { + if ( st->mDensity.y == 0.0f ) { + mCount = st->mCount; + } else { + const float volume = idMath::ClampFloat( BSE_TIME_EPSILON, 1000.0f, pt->GetSpawnVolume( effect ) ); + mCount.Set( volume * st->mDensity.x, volume * st->mDensity.y ); + if ( st->mParticleCap != 0.0f ) { + mCount.x = idMath::ClampFloat( 1.0f, st->mParticleCap, mCount.x ); + mCount.y = idMath::ClampFloat( 1.0f, st->mParticleCap, mCount.y ); + } + } + if ( st->mSegType == SEG_EMITTER || st->mSegType == SEG_TRAIL ) { + if ( mCount.x != 0.0f ) { mSecondsPerParticle.x = 1.0f / mCount.x; } + if ( mCount.y != 0.0f ) { mSecondsPerParticle.y = 1.0f / mCount.y; } + ValidateSpawnRates(); + } +} + +void rvSegment::InitTime( rvBSE *effect, rvSegmentTemplate *st, float time ) { + SetExpired( false ); + mSegStartTime = st->GetStartTime() + time; + mSegEndTime = st->GetDuration() + mSegStartTime; + if ( !st->GetIgnoreDuration() || ( !effect->GetLooping() && !st->GetSoundLooping() ) ) { + effect->SetDuration( mSegEndTime - time ); + } +} + +void rvSegment::Init( rvBSE *effect, const rvDeclEffect *decl, int segmentTemplateHandle, float time ) { + mFlags = 0; + mEffectDecl = decl; + mSegmentTemplateHandle = segmentTemplateHandle; + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st ) { return; } + mLastTime = time; + mActiveCount = 0; + mSecondsPerParticle.Zero(); + mCount.Set( 1.0f, 1.0f ); + mSoundVolume = 0.0f; + mParticles = NULL; + mFreeHead = NULL; + mUsedHead = NULL; + mFreqShift = 1.0f; + mParticleCount = 0; + mLoopParticleCount = 0; + InitTime( effect, st, effect->GetStartTime() ); + GetSecondsPerParticle( effect, st, st->GetParticleTemplate() ); + st->mBSEEffect = effect; +} + +void rvSegment::ResetTime( rvBSE *effect, float time ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( st && !st->GetInfiniteDuration() ) { + InitTime( effect, st, time ); + } +} + +void rvSegment::Rewind( rvBSE *effect ) { + if ( effect->GetDuration() == mSegEndTime - mSegStartTime ) { + mSegStartTime -= effect->GetDuration() * 2.0f; + mLastTime = mSegStartTime; + } +} + +void rvSegment::Advance( rvBSE *effect ) { + mSegStartTime += effect->GetDuration(); + mLastTime = mSegStartTime; +} + +bool rvSegment::Active() { + rvSegmentTemplate *st = GetSegmentTemplate(); + return st && st->GetHasParticles() && st->GetEnabled() && mActiveCount != 0; +} + +rvParticle *rvSegment::InitParticleArray( rvBSE *effect ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st ) { return NULL; } + int count = effect->GetLooping() ? mLoopParticleCount : mParticleCount; + count = idMath::ClampInt( 0, bse_maxParticles.GetInteger(), count ); + if ( count == 0 ) { mFreeHead = NULL; return NULL; } + rvParticle *particles = NULL; + switch ( st->GetParticleTemplate()->GetType() ) { + case PTYPE_LINE: particles = new rvLineParticle[count]; break; + case PTYPE_ORIENTED: particles = new rvOrientedParticle[count]; break; + case PTYPE_DECAL: particles = new rvDecalParticle[count]; break; + case PTYPE_MODEL: particles = new rvModelParticle[count]; break; + case PTYPE_LIGHT: particles = new rvLightParticle[count]; break; + case PTYPE_ELECTRICITY: particles = new rvElectricityParticle[count]; break; + case PTYPE_LINKED: particles = new rvLinkedParticle[count]; break; + case PTYPE_DEBRIS: particles = new rvDebrisParticle[count]; break; + default: particles = new rvSpriteParticle[count]; break; + } + for ( int i = 0; i < count - 1; ++i ) { + particles->GetArrayEntry( i )->SetNext( particles->GetArrayEntry( i + 1 ) ); + } + particles->GetArrayEntry( count - 1 )->SetNext( NULL ); + mFreeHead = particles; + mUsedHead = NULL; + return particles; +} + +void rvSegment::InitParticles( rvBSE *effect ) { + if ( GetSegmentTemplate() ) { + mParticles = InitParticleArray( effect ); + mActiveCount = 0; + } +} + +rvParticle *rvSegment::GetFreeParticle( rvBSE *effect ) { + rvParticle *particle = mFreeHead; + if ( particle ) { + mFreeHead = particle->GetNext(); + } + return particle; +} + +void rvSegment::InsertParticle( rvParticle *particle, rvSegmentTemplate *st ) { + if ( st->GetTemporary() ) { return; } + ++mActiveCount; + if ( st->GetUseMaterialColor() ) { + particle->SetNext( mUsedHead ); + mUsedHead = particle; + return; + } + rvParticle *previous = NULL; + rvParticle *current = mUsedHead; + while ( current && particle->mEndTime > current->mEndTime ) { + previous = current; + current = current->GetNext(); + } + particle->SetNext( current ); + if ( previous ) { previous->SetNext( particle ); } else { mUsedHead = particle; } +} + +rvParticle *rvSegment::SpawnParticle( rvBSE *effect, rvSegmentTemplate *st, float birthTime, + const idVec3 &initPos, const idMat3 &initAxis ) { + if ( !GetSegmentTemplate() ) { return NULL; } + rvParticle *particle = st->GetTemporary() ? mParticles : GetFreeParticle( effect ); + if ( !particle ) { return NULL; } + particle->FinishSpawn( effect, this, birthTime, 0.0f, initPos, initAxis ); + InsertParticle( particle, st ); + return particle; +} + +void rvSegment::SpawnParticles( rvBSE *effect, rvSegmentTemplate *st, float birthTime, int count ) { + count = Min( count, bse_maxParticles.GetInteger() ); + for ( int i = 0; i < count; ++i ) { + rvParticle *particle = st->GetTemporary() ? mParticles : GetFreeParticle( effect ); + if ( !particle ) { break; } + particle->FinishSpawn( effect, this, birthTime, count ? (float)i / count : 0.0f ); + InsertParticle( particle, st ); + } +} + +float rvSegment::EvaluateCost() { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st || !st->GetEnabled() ) { return 0.0f; } + float cost = mSegmentBaseCosts[st->GetType()]; + const rvParticleTemplate *pt = st->GetParticleTemplate(); + if ( pt->GetType() != PTYPE_NONE ) { + cost += pt->CostTrail( (float)mActiveCount ); + if ( pt->GetHasPhysics() ) { cost += mActiveCount * BSE_PHYSICS_COST; } + } + return cost; +} + +float rvSegment::AttenuateDuration( rvBSE *effect, rvSegmentTemplate *st ) { + return effect->GetAttenuation( st ) * ( mSegEndTime - mSegStartTime ); +} + +float rvSegment::AttenuateInterval( rvBSE *effect, rvSegmentTemplate *st ) { + float rate = ( mSecondsPerParticle.y - mSecondsPerParticle.x ) * bse_scale.GetFloat() + mSecondsPerParticle.x; + rate = idMath::ClampFloat( mSecondsPerParticle.y, mSecondsPerParticle.x, rate ); + if ( !st->GetAttenuateEmitter() ) { return rate; } + float attenuation = effect->GetAttenuation( st ); + if ( st->GetInverseAttenuate() ) { attenuation = 1.0f - attenuation; } + return attenuation >= BSE_TIME_EPSILON ? rate / attenuation : 1.0f; +} + +float rvSegment::AttenuateCount( rvBSE *effect, rvSegmentTemplate *st, float min, float max ) { + const float scaledMax = ( max - min ) * bse_scale.GetFloat() + min; + float count = idMath::ClampFloat( min, max, rvRandom::flrand( min, scaledMax ) ); + if ( st->GetAttenuateEmitter() ) { + float attenuation = effect->GetAttenuation( st ); + if ( st->GetInverseAttenuate() ) { attenuation = 1.0f - attenuation; } + count *= attenuation; + } + return count; +} + +void rvSegment::AddToParticleCount( rvBSE *effect, int count, int loopCount, float duration ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st ) { return; } + duration = Max( duration, st->GetParticleTemplate()->GetMaxDuration() ); + const int multiplier = mSecondsPerParticle.y > 0.0f ? (int)ceilf( ( duration + BSE_FUTURE ) / mSecondsPerParticle.y ) + 1 : 1; + mParticleCount += count * multiplier; + mLoopParticleCount += loopCount * multiplier; +} + +void rvSegment::CalcTrailCounts( rvBSE *effect, rvSegmentTemplate *st, rvParticleTemplate *pt, float duration ) { + if ( st->mTrailSegmentIndex >= 0 && st->mTrailSegmentIndex < effect->mSegments.Num() ) { + effect->mSegments[st->mTrailSegmentIndex].AddToParticleCount( effect, mParticleCount, mLoopParticleCount, duration ); + } +} + +void rvSegment::CalcCounts( rvBSE *effect, float time ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st || st->GetType() == SEG_TRAIL || st->GetParticleTemplate()->GetType() == PTYPE_NONE ) { return; } + const float particleDuration = st->GetParticleTemplate()->GetMaxDuration() + BSE_FUTURE; + float duration = 0.0f; + switch ( st->GetType() ) { + case SEG_EMITTER: + duration = Min( particleDuration, st->mLocalDuration.y ) + BSE_FUTURE; + mParticleCount = mLoopParticleCount = (int)ceilf( duration / mSecondsPerParticle.y ) + 1; + break; + case SEG_SPAWNER: + mParticleCount = mLoopParticleCount = (int)ceilf( mCount.y ); + break; + case SEG_DECAL: + case SEG_LIGHT: + mParticleCount = mLoopParticleCount = 1; + break; + default: + mParticleCount = mLoopParticleCount = 0; + break; + } + if ( st->GetHasParticles() && ( st->GetType() == SEG_EMITTER || st->GetType() == SEG_SPAWNER ) ) { + CalcTrailCounts( effect, st, st->GetParticleTemplate(), duration ); + } + if ( !effect->GetLooping() ) { + effect->SetDuration( mSegEndTime - time + st->GetParticleTemplate()->GetMaxDuration() ); + } +} + +void rvSegment::UpdateSimpleParticles( float time ) { + while ( mUsedHead && mUsedHead->Expired( time ) ) { + rvParticle *expired = mUsedHead; + mUsedHead = expired->GetNext(); + expired->SetNext( mFreeHead ); + mFreeHead = expired; + --mActiveCount; + } +} + +void rvSegment::UpdateElectricityParticles( float time ) { + mActiveCount = 0; + for ( rvParticle *particle = mUsedHead; particle; particle = particle->GetNext() ) { + mActiveCount += particle->Update( time ); + } +} + +void rvSegment::RefreshParticles( rvBSE *effect, rvSegmentTemplate *st ) { + if ( st->GetParticleTemplate()->UsesEndOrigin() ) { + for ( rvParticle *particle = mUsedHead; particle; particle = particle->GetNext() ) { + particle->Refresh( effect, st, st->GetParticleTemplate() ); + } + } +} + +void rvSegment::Handle( rvBSE *effect, float time ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st || time < mSegStartTime ) { return; } + switch ( st->GetType() ) { + case SEG_EMITTER: + case SEG_SPAWNER: + if ( effect->GetEndOriginChanged() ) { RefreshParticles( effect, st ); } + break; + case SEG_SOUND: effect->UpdateSoundEmitter( st, this ); break; + case SEG_LIGHT: if ( st->GetEnabled() ) { HandleLight( effect, st, time ); } break; + default: break; + } +} + +void rvSegment::UpdateGenericParticles( rvBSE *effect, rvSegmentTemplate *st, float time ) { + rvParticle *previous = NULL; + rvParticle *particle = mUsedHead; + while ( particle ) { + rvParticle *next = particle->GetNext(); + bool remove = particle->Expired( time ); + if ( st->GetHasPhysics() ) { remove = particle->RunPhysics( effect, st, time ); } + if ( effect->GetStopped() && !particle->GetPersist() ) { remove = true; } + if ( remove ) { + particle->CheckTimeoutEffect( effect, st, time ); + particle->Destroy(); + if ( previous ) { previous->SetNext( next ); } else { mUsedHead = next; } + particle->SetNext( mFreeHead ); + mFreeHead = particle; + --mActiveCount; + } else { + previous = particle; + } + particle = next; + } +} + +bool rvSegment::UpdateParticles( rvBSE *effect, float time ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st ) { return false; } + Handle( effect, time ); + if ( effect->GetStopped() || st->GetHasPhysics() ) { UpdateGenericParticles( effect, st, time ); } + else { UpdateSimpleParticles( time ); } + if ( st->GetParticleTemplate()->GetType() == PTYPE_ELECTRICITY ) { UpdateElectricityParticles( time ); } + if ( com_debugHudActive ) { + rvBSEManagerLocal::mPerfCounters[PERF_NUM_PARTICLES] += mActiveCount; + if ( mUsedHead ) { rvBSEManagerLocal::mPerfCounters[PERF_NUM_TEXELS] += st->GetTexelCount(); } + } + return mUsedHead != NULL; +} + +void rvSegment::PlayEffect( rvBSE *effect, rvSegmentTemplate *st ) { + if ( game && st->mNumEffects > 0 ) { + game->PlayEffect( st->mEffects[rvRandom::irand( 0, st->mNumEffects - 1 )], + effect->GetCurrentOrigin(), effect->GetCurrentAxis() ); + } +} + +void rvSegment::CreateDecal( rvBSE *effect, float time ) { + // Decal geometry is projected by the render world; spawning the temporary + // particle preserves the retail timing and randomized particle parameters. + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( bse_render.GetBool() && st ) { SpawnParticle( effect, st, time ); } +} + +bool rvSegment::Check( rvBSE *effect, float time ) { + float spawnTime = mLastTime; + mLastTime = time; + if ( GetExpired() || effect->GetStopped() ) { return true; } + if ( time < mSegStartTime ) { return false; } + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st || st->DetailCull() ) { SetExpired( true ); return true; } + switch ( st->GetType() ) { + case SEG_EFFECT: + if ( st->GetEnabled() ) { PlayEffect( effect, st ); } + SetExpired( true ); + break; + case SEG_EMITTER: { + if ( !effect->CanInterpolate() ) { break; } + const float end = Min( time + BSE_FUTURE, mSegEndTime ); + while ( spawnTime < end ) { + if ( spawnTime >= mSegStartTime ) { SpawnParticle( effect, st, spawnTime ); } + spawnTime += AttenuateInterval( effect, st ); + } + mLastTime = spawnTime; + if ( end >= mSegEndTime - BSE_TIME_EPSILON ) { SetExpired( true ); } + break; + } + case SEG_SPAWNER: + SpawnParticles( effect, st, mSegStartTime, idMath::Ftoi( AttenuateCount( effect, st, mCount.x, mCount.y ) ) ); + SetExpired( true ); + break; + case SEG_SOUND: { + if ( st->GetEnabled() ) { + idSoundEmitter *emitter = effect->GetReferenceSound( 1 ); + mSoundVolume = st->GetSoundVolume(); + mFreqShift = st->GetFreqShift(); + effect->UpdateSoundEmitter( st, this ); + if ( emitter && st->mSoundShader ) { + emitter->StartSound( st->mSoundShader, GetSoundChannel(), rvRandom::flrand( 0.0f, 1.0f ), st->GetSoundLooping() ? SSF_LOOPING : 0 ); + SetSoundPlaying( st->GetSoundLooping() ); + } + } + SetExpired( true ); + break; + } + case SEG_DECAL: + if ( st->GetEnabled() && ( !rvBSEManagerLocal::g_decals || rvBSEManagerLocal::g_decals->GetBool() ) ) { CreateDecal( effect, mSegStartTime ); } + SetExpired( true ); + break; + case SEG_LIGHT: + if ( st->GetEnabled() ) { InitLight( effect, st, mSegStartTime ); } + SetExpired( true ); + break; + case SEG_DOUBLEVISION: + if ( st->GetEnabled() ) { static_cast( bse )->SetDoubleVisionParms( mSegStartTime + AttenuateDuration( effect, st ), effect->GetOriginAttenuation( st ) ); } + SetExpired( true ); + break; + case SEG_SHAKE: + if ( st->GetEnabled() ) { static_cast( bse )->SetShakeParms( mSegStartTime + AttenuateDuration( effect, st ), effect->GetOriginAttenuation( st ) ); } + SetExpired( true ); + break; + case SEG_TUNNEL: + if ( st->GetEnabled() ) { static_cast( bse )->SetTunnelParms( mSegStartTime + AttenuateDuration( effect, st ), effect->GetOriginAttenuation( st ) ); } + SetExpired( true ); + break; + default: + SetExpired( true ); + break; + } + return GetExpired(); +} + +void rvSegment::RenderMotion( rvBSE *effect, const renderEffect_t *owner, rvRenderModelBSE *model, + rvParticleTemplate *pt, float time ) { + if ( !model || !pt || !mUsedHead ) { return; } + const int trails = Max( 0, pt->GetMaxTrailCount() - 1 ); + const int count = mActiveCount * ( trails + 1 ); + srfTriangles_t *tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, 2 * count + 2 ); + R_AllocStaticTriSurfIndexes( tri, 12 * count ); + for ( rvParticle *particle = mUsedHead; particle; particle = particle->GetNext() ) { + particle->RenderMotion( effect, tri, owner, time ); + } + R_BoundTriSurf( tri ); + modelSurface_t surface; + memset( &surface, 0, sizeof( surface ) ); + surface.shader = pt->GetTrailMaterial(); + surface.geometry = tri; + model->AddSurface( surface ); +} + +void rvSegment::RenderTrail( rvBSE *effect, const renderEffect_t *owner, rvRenderModelBSE *model, float time ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( st ) { + rvParticleTemplate *pt = st->GetParticleTemplate(); + if ( pt->GetMaxTrailCount() >= 0 && pt->GetMaxTrailTime() >= BSE_TIME_EPSILON && pt->GetTrailType() == TRAIL_MOTION ) { + RenderMotion( effect, owner, model, pt, time ); + } + } +} + +void rvSegment::Render( rvBSE *effect, const renderEffect_t *owner, rvRenderModelBSE *model, float time ) { + rvSegmentTemplate *st = GetSegmentTemplate(); + if ( !st || !owner || !model || !mUsedHead ) { return; } + rvParticleTemplate *pt = st->GetParticleTemplate(); + const int numVerts = mActiveCount * pt->GetVertexCount(); + if ( numVerts <= 0 || numVerts * (int)sizeof( idDrawVert ) > (int)MEMORY_BLOCK_SIZE ) { return; } + srfTriangles_t *tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, numVerts ); + R_AllocStaticTriSurfIndexes( tri, mActiveCount * pt->GetIndexCount() ); + const idMat3 inverseAxis = owner->axis.Transpose(); + idMat3 view; + view[0] = ( effect->GetViewOrg() - owner->origin ) * inverseAxis; + view[1] = effect->GetViewAxis()[1] * inverseAxis; + view[2] = effect->GetViewAxis()[2] * inverseAxis; + for ( rvParticle *particle = mUsedHead; particle; particle = particle->GetNext() ) { + if ( st->GetInfiniteDuration() ) { particle->ExtendLife( time + 1.0f ); } + if ( particle->Render( effect, view, tri, time ) && pt->GetTrailType() == TRAIL_BURN ) { + particle->RenderBurnTrail( effect, view, tri, time ); + } + } + R_BoundTriSurf( tri ); + modelSurface_t surface; + memset( &surface, 0, sizeof( surface ) ); + surface.shader = pt->GetMaterial(); + surface.geometry = tri; + model->AddSurface( surface ); +} + +void rvSegment::AllocateSurface( rvBSE *effect, idRenderModel *model ) { +} + +void rvSegment::ClearSurface( rvBSE *effect, idRenderModel *model ) { +} + +void rvSegment::Sort( const idVec3 &eyePos ) { +} diff --git a/src/bse/BSE_SegmentTemplate.cpp b/src/bse/BSE_SegmentTemplate.cpp new file mode 100644 index 0000000..313a0c9 --- /dev/null +++ b/src/bse/BSE_SegmentTemplate.cpp @@ -0,0 +1,333 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" + +// Retail compilation unit: BSE_SegmentTemplate.obj. Implementations are recovered here. + +void rvSegmentTemplate::CreateParticleTemplate( rvDeclEffect *effect, + idLexer *lexer, int particleType ) { + mParticleTemplate.Init(); + mParticleTemplate.SetType( particleType ); + mParticleTemplate.SetParameterCounts(); + mParticleTemplate.Parse( effect, lexer ); +} + +void rvSegmentTemplate::Init( rvDeclEffect *decl ) { + mDeclEffect = decl; + mFlags = STFLAG_ENABLED; + mSegType = SEG_NONE; + mLocalStartTime.Zero(); + mLocalDuration.Zero(); + mAttenuation.Zero(); + mParticleCap = 0.0f; + mDetail = 0.0f; + mScale = 1.0f; + mCount.Set( 1.0f, 1.0f ); + mDensity.Zero(); + mTrailSegmentIndex = -1; + mNumEffects = 0; + for ( int effect = 0; effect < BSE_NUM_SPAWNABLE; ++effect ) { + mEffects[effect] = NULL; + } + mSoundShader = NULL; + mSoundVolume.Zero(); + mFreqShift.Set( 1.0f, 1.0f ); + mParticleTemplate.Init(); + mBSEEffect = NULL; +} + +int rvSegmentTemplate::GetTexelCount( void ) { + const idMaterial *material = mParticleTemplate.GetMaterial(); + return material != NULL ? material->GetTexelCount() : 0; +} + +bool rvSegmentTemplate::GetSmoker( void ) { + return mParticleTemplate.GetTrailType() == TRAIL_PARTICLE; +} + +bool rvSegmentTemplate::Compare( const rvSegmentTemplate &other ) const { + if ( mSegmentName.Cmp( other.mSegmentName ) != 0 ) { + return false; + } + if ( ( mFlags ^ other.mFlags ) & ~STFLAG_ENABLED ) { + return false; + } + if ( mSegType != other.mSegType ) { + return false; + } + if ( mSegType != SEG_TRAIL && + ( mLocalStartTime != other.mLocalStartTime || + mLocalDuration != other.mLocalDuration ) ) { + return false; + } + if ( mScale != other.mScale || mDetail != other.mDetail || + mAttenuation != other.mAttenuation ) { + return false; + } + if ( mDensity.y == 0.0f ) { + if ( mCount != other.mCount ) { + return false; + } + } else if ( mDensity != other.mDensity || mParticleCap != other.mParticleCap ) { + return false; + } + if ( mTrailSegmentIndex != other.mTrailSegmentIndex || + mNumEffects != other.mNumEffects ) { + return false; + } + for ( int effect = 0; effect < mNumEffects; ++effect ) { + if ( mEffects[effect] != other.mEffects[effect] ) { + return false; + } + } + if ( mSoundShader != other.mSoundShader || + mSoundVolume != other.mSoundVolume || + mFreqShift != other.mFreqShift || mDetail != other.mDetail ) { + return false; + } + return mParticleTemplate == other.mParticleTemplate; +} + +void rvSegmentTemplate::SetMaxDuration( rvDeclEffect *effect ) { + if ( mFlags & STFLAG_IGNORE_DURATION ) { + return; + } + effect->SetMaxDuration( mLocalStartTime.x + mLocalDuration.x ); + if ( mParticleTemplate.GetType() != PTYPE_NONE ) { + effect->SetMaxDuration( mParticleTemplate.GetMaxDuration() + + mLocalStartTime.x + mLocalDuration.x ); + } +} + +bool rvSegmentTemplate::GetSoundLooping( void ) { + return mSoundShader != NULL && mSoundShader->IsLooping(); +} + +bool rvSegmentTemplate::Finish( rvDeclEffect *effect ) { + if ( mLocalStartTime.x >= mLocalStartTime.y ) { + idSwap( mLocalStartTime.x, mLocalStartTime.y ); + } + if ( mLocalDuration.x >= mLocalDuration.y ) { + idSwap( mLocalDuration.x, mLocalDuration.y ); + } + if ( mCount.x >= mCount.y ) { + idSwap( mCount.x, mCount.y ); + } + if ( mDensity.x >= mDensity.y ) { + idSwap( mDensity.x, mDensity.y ); + } + if ( mAttenuation.x >= mAttenuation.y ) { + idSwap( mAttenuation.x, mAttenuation.y ); + } + + if ( mParticleTemplate.GetType() != PTYPE_NONE ) { + mParticleTemplate.Finish(); + mParticleTemplate.SetLinked( false ); + } + + switch ( mSegType ) { + case SEG_EMITTER: + mFlags |= STFLAG_HASPARTICLES; + if ( mParticleTemplate.GetType() == PTYPE_NONE || + ( mFlags & STFLAG_INFINITE_DURATION ) ) { + return false; + } + break; + case SEG_SPAWNER: + mFlags |= STFLAG_HASPARTICLES; + if ( mParticleTemplate.GetType() == PTYPE_NONE ) { + return false; + } + break; + case SEG_TRAIL: + mFlags |= STFLAG_HASPARTICLES; + mLocalStartTime.Zero(); + mLocalDuration.Zero(); + if ( mParticleTemplate.GetType() == PTYPE_NONE ) { + return false; + } + mParticleTemplate.SetLinked( true ); + break; + case SEG_SOUND: + mFlags |= STFLAG_IGNORE_DURATION; + break; + case SEG_DECAL: + mFlags &= ~STFLAG_HASPARTICLES; + mFlags |= STFLAG_TEMPORARY; + break; + case SEG_DOUBLEVISION: + case SEG_SHAKE: + case SEG_TUNNEL: + case SEG_COUNT: + if ( mAttenuation.y > 0.0f ) { + mFlags |= STFLAG_ATTENUATE_EMITTER; + } + mFlags &= ~STFLAG_HASPARTICLES; + break; + default: + mFlags &= ~STFLAG_HASPARTICLES; + break; + } + + const int particleType = mParticleTemplate.GetType(); + if ( particleType == PTYPE_DEBRIS ) { + mFlags &= ~STFLAG_HASPARTICLES; + mFlags |= STFLAG_TEMPORARY; + } + if ( ( mFlags & STFLAG_INFINITE_DURATION ) || + mParticleTemplate.GetTrailType() == TRAIL_PARTICLE || + mParticleTemplate.GetHasPhysics() || + mParticleTemplate.GetNumTimeoutEffects() != 0 || + particleType == PTYPE_ELECTRICITY || particleType == PTYPE_LIGHT ) { + // In the retail executable the complex-update flag occupies bit 9. + mFlags |= BIT( 9 ); + } + return true; +} + +void rvSegmentTemplate::EvaluateTrailSegment( rvDeclEffect *effect ) { + if ( !mParticleTemplate.GetTrailTypeName().IsEmpty() && + mParticleTemplate.GetTrailType() != TRAIL_NONE ) { + mTrailSegmentIndex = effect->GetTrailSegmentIndex( + mParticleTemplate.GetTrailTypeName() ); + } +} + +bool rvSegmentTemplate::DetailCull( void ) const { + return mDetail != 0.0f && bse_scale.GetFloat() < mDetail; +} + +void rvSegmentTemplate::SetMinDuration( rvDeclEffect *effect ) { + if ( !( mFlags & STFLAG_IGNORE_DURATION ) && !GetSoundLooping() ) { + effect->SetMinDuration( mLocalDuration.x + mLocalStartTime.x ); + } +} + +bool rvSegmentTemplate::Parse( rvDeclEffect *effect, int segmentType, + idLexer *lexer ) { + mSegType = segmentType; + idToken token; + if ( !lexer->ReadToken( &token ) ) { + return false; + } + if ( token.Icmp( "{" ) != 0 ) { + mSegmentName = token; + } else { + mSegmentName = va( "unnamed%d", effect->GetNumSegmentTemplates() ); + lexer->UnreadToken( &token ); + } + if ( !lexer->ExpectTokenString( "{" ) || !lexer->ReadToken( &token ) ) { + return false; + } + + while ( token.Cmp( "}" ) != 0 ) { + if ( token.Icmp( "count" ) == 0 || token.Icmp( "rate" ) == 0 ) { + mCount.x = lexer->ParseFloat(); + lexer->ExpectTokenString( "," ); + mCount.y = lexer->ParseFloat(); + } else if ( token.Icmp( "density" ) == 0 ) { + mDensity.x = lexer->ParseFloat(); + lexer->ExpectTokenString( "," ); + mDensity.y = lexer->ParseFloat(); + } else if ( token.Icmp( "particleCap" ) == 0 ) { + mParticleCap = lexer->ParseFloat(); + } else if ( token.Icmp( "start" ) == 0 ) { + mLocalStartTime.x = lexer->ParseFloat(); + lexer->ExpectTokenString( "," ); + mLocalStartTime.y = lexer->ParseFloat(); + } else if ( token.Icmp( "duration" ) == 0 ) { + mLocalDuration.x = lexer->ParseFloat(); + lexer->ExpectTokenString( "," ); + mLocalDuration.y = lexer->ParseFloat(); + } else if ( token.Icmp( "detail" ) == 0 ) { + mDetail = lexer->ParseFloat(); + } else if ( token.Icmp( "soundShader" ) == 0 ) { + lexer->ReadToken( &token ); + mSoundShader = declManager->FindSound( token.c_str() ); + const float length = mSoundShader->GetTimeLength(); + mLocalDuration.Set( length, length ); + } else if ( token.Icmp( "volume" ) == 0 ) { + mSoundVolume.x = lexer->ParseFloat(); + lexer->ExpectTokenString( "," ); + mSoundVolume.y = lexer->ParseFloat(); + } else if ( token.Icmp( "freqShift" ) == 0 ) { + mFreqShift.x = lexer->ParseFloat(); + lexer->ExpectTokenString( "," ); + mFreqShift.y = lexer->ParseFloat(); + } else if ( token.Icmp( "effect" ) == 0 ) { + lexer->ReadToken( &token ); + if ( mNumEffects >= BSE_NUM_SPAWNABLE ) { + common->Warning( "Unable to add effect '%s' - too many effects", + token.c_str() ); + } else { + mEffects[mNumEffects++] = declManager->FindEffect( token.c_str() ); + } + } else if ( token.Icmp( "channel" ) == 0 ) { + lexer->ReadToken( &token ); + } else if ( token.Icmp( "scale" ) == 0 ) { + mScale = lexer->ParseFloat(); + } else if ( token.Icmp( "attenuateEmitter" ) == 0 ) { + mFlags |= STFLAG_ATTENUATE_EMITTER; + } else if ( token.Icmp( "inverseAttenuateEmitter" ) == 0 ) { + mFlags |= STFLAG_ATTENUATE_EMITTER | STFLAG_INVERSE_ATTENUATE; + } else if ( token.Icmp( "attenuation" ) == 0 ) { + mAttenuation.x = lexer->ParseFloat(); + lexer->ExpectTokenString( "," ); + mAttenuation.y = lexer->ParseFloat(); + } else if ( token.Icmp( "locked" ) == 0 ) { + mFlags |= STFLAG_LOCKED; + } else if ( token.Icmp( "constant" ) == 0 ) { + mFlags |= STFLAG_INFINITE_DURATION; + } else if ( token.Icmp( "sprite" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_SPRITE ); + } else if ( token.Icmp( "line" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_LINE ); + } else if ( token.Icmp( "oriented" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_ORIENTED ); + } else if ( token.Icmp( "decal" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_DECAL ); + } else if ( token.Icmp( "model" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_MODEL ); + } else if ( token.Icmp( "light" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_LIGHT ); + } else if ( token.Icmp( "electricity" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_ELECTRICITY ); + } else if ( token.Icmp( "linked" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_LINKED ); + } else if ( token.Icmp( "debris" ) == 0 ) { + CreateParticleTemplate( effect, lexer, PTYPE_DEBRIS ); + } else { + common->Warning( "^4BSE:^1 Invalid segment parameter '%s' (file: %s, line: %d)", + token.c_str(), lexer->GetFileName(), lexer->GetLineNum() ); + } + if ( !lexer->ReadToken( &token ) ) { + return false; + } + } + return true; +} diff --git a/src/bse/BSE_SpawnDomains.cpp b/src/bse/BSE_SpawnDomains.cpp new file mode 100644 index 0000000..df5606a --- /dev/null +++ b/src/bse/BSE_SpawnDomains.cpp @@ -0,0 +1,352 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "BSE.h" +#include "BSE_Particle.h" + +/* +=============================================================================== + + Quake 4 BSE spawn domains + + Compilation-unit boundary and signatures are taken from + BSE_SpawnDomains.obj in quake4.pdb. The domain equations follow the retail + Hex-Rays control flow at RVAs 0x001B8C00 through 0x001BA45A. + +=============================================================================== +*/ + +TSpawnFunc rvParticleParms::spawnFunctions[SPF_COUNT] = { + SpawnStub, SpawnNone1, SpawnNone2, SpawnNone3, + SpawnStub, SpawnOne1, SpawnOne2, SpawnOne3, + SpawnStub, SpawnPoint1, SpawnPoint2, SpawnPoint3, + SpawnStub, SpawnLinear1, SpawnLinear2, SpawnLinear3, + SpawnStub, SpawnBox1, SpawnBox2, SpawnBox3, + SpawnStub, SpawnSurfaceBox1, SpawnSurfaceBox2, SpawnSurfaceBox3, + SpawnStub, SpawnSphere1, SpawnSphere2, SpawnSphere3, + SpawnStub, SpawnSurfaceSphere1, SpawnSurfaceSphere2, SpawnSurfaceSphere3, + SpawnStub, SpawnStub, SpawnStub, SpawnCylinder3, + SpawnStub, SpawnStub, SpawnStub, SpawnSurfaceCylinder3, + SpawnStub, SpawnStub, SpawnSpiral2, SpawnSpiral3, + SpawnStub, SpawnStub, SpawnStub, SpawnModel3 +}; + +bool rvParticleParms::Compare( const rvParticleParms &comp ) const { + return mSpawnType == comp.mSpawnType && mFlags == comp.mFlags && + mMins.Compare( comp.mMins, 0.001f ) && mMaxs.Compare( comp.mMaxs, 0.001f ); +} + +void SpawnGetNormal( idVec3 *normal, idVec3 &result, const idVec3 *centre ) { + if ( normal == NULL ) { + return; + } + *normal = centre != NULL ? result - *centre : result; + normal->Normalize(); +} + +void SpawnStub( float *, const rvParticleParms &, idVec3 *, const idVec3 * ) { +} + +void SpawnNone1( float *result, const rvParticleParms &, idVec3 *, const idVec3 * ) { + result[0] = 0.0f; +} + +void SpawnNone2( float *result, const rvParticleParms &, idVec3 *, const idVec3 * ) { + result[0] = result[1] = 0.0f; +} + +void SpawnNone3( float *result, const rvParticleParms &, idVec3 *normal, const idVec3 *centre ) { + result[0] = result[1] = result[2] = 0.0f; + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnOne1( float *result, const rvParticleParms &, idVec3 *, const idVec3 * ) { + result[0] = 1.0f; +} + +void SpawnOne2( float *result, const rvParticleParms &, idVec3 *, const idVec3 * ) { + result[0] = result[1] = 1.0f; +} + +void SpawnOne3( float *result, const rvParticleParms &, idVec3 *normal, const idVec3 *centre ) { + result[0] = result[1] = result[2] = 1.0f; + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnPoint1( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + result[0] = parms.mMins[0]; +} + +void SpawnPoint2( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + result[0] = parms.mMins[0]; + result[1] = parms.mMins[1]; +} + +void SpawnPoint3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + *reinterpret_cast( result ) = parms.mMins; + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnLinear1( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + result[0] = rvRandom::flrand( parms.mMins[0], parms.mMaxs[0] ); +} + +void SpawnLinear2( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + const float fraction = ( parms.mFlags & PPFLAG_LINEARSPACING ) ? result[0] : rvRandom::flrand( 0.0f, 1.0f ); + result[0] = parms.mMins[0] + ( parms.mMaxs[0] - parms.mMins[0] ) * fraction; + result[1] = parms.mMins[1] + ( parms.mMaxs[1] - parms.mMins[1] ) * fraction; +} + +void SpawnLinear3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + const float fraction = ( parms.mFlags & PPFLAG_LINEARSPACING ) ? result[0] : rvRandom::flrand( 0.0f, 1.0f ); + for ( int i = 0; i < 3; ++i ) { + result[i] = parms.mMins[i] + ( parms.mMaxs[i] - parms.mMins[i] ) * fraction; + } + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnBox1( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + result[0] = rvRandom::flrand( parms.mMins[0], parms.mMaxs[0] ); +} + +void SpawnBox2( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + result[0] = rvRandom::flrand( parms.mMins[0], parms.mMaxs[0] ); + result[1] = rvRandom::flrand( parms.mMins[1], parms.mMaxs[1] ); +} + +void SpawnBox3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + for ( int i = 0; i < 3; ++i ) { + result[i] = rvRandom::flrand( parms.mMins[i], parms.mMaxs[i] ); + } + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnSurfaceBox1( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + result[0] = rvRandom::irand( 0, 1 ) == 0 ? parms.mMins[0] : parms.mMaxs[0]; +} + +void SpawnSurfaceBox2( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + const int side = rvRandom::irand( 0, 3 ); + result[0] = ( side & 1 ) ? rvRandom::flrand( parms.mMins[0], parms.mMaxs[0] ) : ( side == 0 ? parms.mMins[0] : parms.mMaxs[0] ); + result[1] = ( side & 1 ) ? ( side == 1 ? parms.mMins[1] : parms.mMaxs[1] ) : rvRandom::flrand( parms.mMins[1], parms.mMaxs[1] ); +} + +void SpawnSurfaceBox3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + const int side = rvRandom::irand( 0, 5 ); + for ( int i = 0; i < 3; ++i ) { + result[i] = rvRandom::flrand( parms.mMins[i], parms.mMaxs[i] ); + } + const int axis = side % 3; + result[axis] = side < 3 ? parms.mMins[axis] : parms.mMaxs[axis]; + if ( normal != NULL ) { + if ( centre != NULL ) { + *normal = rvBSEManagerLocal::mCubeNormals[side]; + } else { + *normal = *reinterpret_cast( result ); + normal->Normalize(); + } + } +} + +void SpawnSphere1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + SpawnBox1( result, parms, normal, centre ); +} + +static idVec3 RandomDirection( int dimensions ) { + idVec3 direction( rvRandom::flrand( -1.0f, 1.0f ), rvRandom::flrand( -1.0f, 1.0f ), + dimensions == 3 ? rvRandom::flrand( -1.0f, 1.0f ) : 0.0f ); + direction.Normalize(); + return direction; +} + +void SpawnSphere2( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + const idVec3 direction = RandomDirection( 2 ); + for ( int i = 0; i < 2; ++i ) { + const float origin = ( parms.mMins[i] + parms.mMaxs[i] ) * 0.5f; + const float radius = ( parms.mMaxs[i] - parms.mMins[i] ) * 0.5f; + result[i] = origin + direction[i] * rvRandom::flrand( 0.0f, radius ); + } +} + +void SpawnSphere3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + const idVec3 direction = RandomDirection( 3 ); + for ( int i = 0; i < 3; ++i ) { + const float origin = ( parms.mMins[i] + parms.mMaxs[i] ) * 0.5f; + const float radius = ( parms.mMaxs[i] - parms.mMins[i] ) * 0.5f; + result[i] = origin + direction[i] * rvRandom::flrand( 0.0f, radius ); + } + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnSurfaceSphere1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + SpawnSurfaceBox1( result, parms, normal, centre ); +} + +void SpawnSurfaceSphere2( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + const idVec3 direction = RandomDirection( 2 ); + for ( int i = 0; i < 2; ++i ) { + result[i] = ( parms.mMins[i] + parms.mMaxs[i] ) * 0.5f + + direction[i] * ( parms.mMaxs[i] - parms.mMins[i] ) * 0.5f; + } +} + +void SpawnSurfaceSphere3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + const idVec3 direction = RandomDirection( 3 ); + for ( int i = 0; i < 3; ++i ) { + result[i] = ( parms.mMins[i] + parms.mMaxs[i] ) * 0.5f + + direction[i] * ( parms.mMaxs[i] - parms.mMins[i] ) * 0.5f; + } + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnCylinder3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + const idVec3 direction = RandomDirection( 2 ); + const float length = parms.mMaxs[0] - parms.mMins[0]; + const float along = rvRandom::flrand( 0.0f, length ); + const float taper = length != 0.0f && ( parms.mFlags & PPFLAG_CONE ) ? along / length : 1.0f; + result[0] = parms.mMins[0] + along; + for ( int i = 1; i < 3; ++i ) { + const float origin = ( parms.mMins[i] + parms.mMaxs[i] ) * 0.5f; + const float radius = ( parms.mMaxs[i] - parms.mMins[i] ) * 0.5f * taper; + result[i] = origin + direction[i - 1] * rvRandom::flrand( 0.0f, radius ); + } + SpawnGetNormal( normal, *reinterpret_cast( result ), centre ); +} + +void SpawnSurfaceCylinder3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + const idVec3 direction = RandomDirection( 2 ); + const float length = parms.mMaxs[0] - parms.mMins[0]; + const float along = rvRandom::flrand( 0.0f, length ); + const float taper = length != 0.0f && ( parms.mFlags & PPFLAG_CONE ) ? along / length : 1.0f; + result[0] = parms.mMins[0] + along; + result[1] = ( parms.mMins[1] + parms.mMaxs[1] ) * 0.5f + direction[0] * ( parms.mMaxs[1] - parms.mMins[1] ) * 0.5f * taper; + result[2] = ( parms.mMins[2] + parms.mMaxs[2] ) * 0.5f + direction[1] * ( parms.mMaxs[2] - parms.mMins[2] ) * 0.5f * taper; + if ( normal != NULL ) { + if ( centre == NULL ) { + *normal = *reinterpret_cast( result ); + } else if ( taper == 1.0f ) { + normal->Set( 0.0f, direction[0], direction[1] ); + } else if ( length == 0.0f ) { + normal->Set( 1.0f, 0.0f, 0.0f ); + } else { + normal->Set( -1.0f / length, direction[0], direction[1] ); + } + normal->Normalize(); + } +} + +void SpawnSpiral2( float *result, const rvParticleParms &parms, idVec3 *, const idVec3 * ) { + result[0] = ( parms.mFlags & PPFLAG_LINEARSPACING ) + ? parms.mMins[0] + ( parms.mMaxs[0] - parms.mMins[0] ) * result[0] + : rvRandom::flrand( parms.mMins[0], parms.mMaxs[0] ); + result[1] = cosf( idMath::TWO_PI * result[0] / parms.mRange ) * rvRandom::flrand( parms.mMins[1], parms.mMaxs[1] ); +} + +void SpawnSpiral3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + result[0] = ( parms.mFlags & PPFLAG_LINEARSPACING ) + ? parms.mMins[0] + ( parms.mMaxs[0] - parms.mMins[0] ) * result[0] + : rvRandom::flrand( parms.mMins[0], parms.mMaxs[0] ); + const float y = rvRandom::flrand( parms.mMins[1], parms.mMaxs[1] ); + const float z = rvRandom::flrand( parms.mMins[2], parms.mMaxs[2] ); + const float angle = idMath::TWO_PI * result[0] / parms.mRange; + const float c = cosf( angle ); + const float s = sinf( angle ); + result[1] = c * y - s * z; + result[2] = c * z + s * y; + if ( normal != NULL ) { + normal->Set( centre == NULL ? result[0] : 0.0f, result[1], result[2] ); + normal->Normalize(); + } +} + +void rvParticleParms::HandleRelativeParms( float *death, float *init, int count ) { + if ( mFlags & PPFLAG_RELATIVE ) { + for ( int i = 0; i < count; ++i ) { + death[i] += init[i]; + } + } +} + +void rvParticleParms::GetMinsMaxs( idVec3 &mins, idVec3 &maxs ) { + mins.Zero(); + maxs.Zero(); + const int dimensions = mSpawnType & 3; + if ( mSpawnType >= SPF_ONE_0 && mSpawnType <= SPF_ONE_3 ) { + for ( int i = 0; i < dimensions; ++i ) { + mins[i] = maxs[i] = 1.0f; + } + } else if ( mSpawnType >= SPF_POINT_0 && mSpawnType <= SPF_POINT_3 ) { + for ( int i = 0; i < dimensions; ++i ) { + mins[i] = maxs[i] = mMins[i]; + } + } else if ( mSpawnType >= SPF_LINEAR_0 ) { + for ( int i = 0; i < dimensions; ++i ) { + mins[i] = mMins[i]; + maxs[i] = mMaxs[i]; + } + } +} + +void SpawnModel3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { + idRenderModel *model = static_cast( parms.mMisc ); + if ( model == NULL || model->NumSurfaces() == 0 ) { + result[0] = result[1] = result[2] = 0.0f; + if ( normal != NULL ) { + normal->Zero(); + } + return; + } + const modelSurface_t *surface = model->Surface( rvRandom::irand( 0, model->NumSurfaces() - 1 ) ); + const srfTriangles_t *geometry = surface != NULL ? surface->geometry : NULL; + if ( geometry == NULL || geometry->numIndexes < 3 ) { + result[0] = result[1] = result[2] = 0.0f; + return; + } + const int triangle = rvRandom::irand( 0, geometry->numIndexes / 3 - 1 ); + idVec3 weights( rvRandom::flrand( 0.0f, 1.0f ), rvRandom::flrand( 0.0f, 1.0f ), rvRandom::flrand( 0.0f, 1.0f ) ); + weights.Normalize(); + const idDrawVert &a = geometry->verts[geometry->indexes[triangle * 3 + 0]]; + const idDrawVert &b = geometry->verts[geometry->indexes[triangle * 3 + 1]]; + const idDrawVert &c = geometry->verts[geometry->indexes[triangle * 3 + 2]]; + idVec3 point = ( a.xyz * weights[0] + b.xyz * weights[1] + c.xyz * weights[2] ) * rvBSEManagerLocal::mModelToBSE; + if ( normal != NULL ) { + if ( centre != NULL ) { + *normal = ( b.xyz - a.xyz ).Cross( c.xyz - a.xyz ); + } else { + *normal = point; + } + *normal *= rvBSEManagerLocal::mModelToBSE; + normal->Normalize(); + } + const idBounds &bounds = geometry->bounds; + for ( int i = 0; i < 3; ++i ) { + const float sourceSize = bounds[1][i] - bounds[0][i]; + const float scale = sourceSize != 0.0f ? ( parms.mMaxs[i] - parms.mMins[i] ) / sourceSize : 0.0f; + const float sourceCenter = ( bounds[0][i] + bounds[1][i] ) * 0.5f; + const float targetCenter = ( parms.mMins[i] + parms.mMaxs[i] ) * 0.5f; + result[i] = ( point[i] - sourceCenter ) * scale + targetCenter; + } +} diff --git a/src/bse/BSE_SpawnDomains.h b/src/bse/BSE_SpawnDomains.h new file mode 100644 index 0000000..5fa3012 --- /dev/null +++ b/src/bse/BSE_SpawnDomains.h @@ -0,0 +1,241 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2007 Id Software, Inc. +// + +#pragma once +#ifndef _BSE_SPAWN_DOMAINS_H_INC_ +#define _BSE_SPAWN_DOMAINS_H_INC_ + +enum +{ + SPF_NONE_0 = 0, + SPF_NONE_1, + SPF_NONE_2, + SPF_NONE_3, + + SPF_ONE_0, + SPF_ONE_1, + SPF_ONE_2, + SPF_ONE_3, + + SPF_POINT_0, + SPF_POINT_1, + SPF_POINT_2, + SPF_POINT_3, + + SPF_LINEAR_0, + SPF_LINEAR_1, + SPF_LINEAR_2, + SPF_LINEAR_3, + + SPF_BOX_0, + SPF_BOX_1, + SPF_BOX_2, + SPF_BOX_3, + + SPF_SURFACE_BOX_0, + SPF_SURFACE_BOX_1, + SPF_SURFACE_BOX_2, + SPF_SURFACE_BOX_3, + + SPF_SPHERE_0, + SPF_SPHERE_1, + SPF_SPHERE_2, + SPF_SPHERE_3, + + SPF_SURFACE_SPHERE_0, + SPF_SURFACE_SPHERE_1, + SPF_SURFACE_SPHERE_2, + SPF_SURFACE_SPHERE_3, + + SPF_CYLINDER_0, + SPF_CYLINDER_1, + SPF_CYLINDER_2, + SPF_CYLINDER_3, + + SPF_SURFACE_CYLINDER_0, + SPF_SURFACE_CYLINDER_1, + SPF_SURFACE_CYLINDER_2, + SPF_SURFACE_CYLINDER_3, + + SPF_SPIRAL_0, + SPF_SPIRAL_1, + SPF_SPIRAL_2, + SPF_SPIRAL_3, + + SPF_MODEL_0, + SPF_MODEL_1, + SPF_MODEL_2, + SPF_MODEL_3, + + SPF_COUNT +}; + +typedef void ( *TSpawnFunc )( float *, const class rvParticleParms &, idVec3 *, const idVec3 * ); + +#define PPFLAG_SURFACE ( 1 << 0 ) +#define PPFLAG_USEENDORIGIN ( 1 << 1 ) +#define PPFLAG_CONE ( 1 << 2 ) +#define PPFLAG_RELATIVE ( 1 << 3 ) +#define PPFLAG_LINEARSPACING ( 1 << 4 ) +#define PPFLAG_ATTENUATE ( 1 << 5 ) +#define PPFLAG_INV_ATTENUATE ( 1 << 6 ) + +class idRenderModel; + +// Parameters that define how a particle spawns. These are generic to all fields +struct sdModelInfo { + static const int NUM_SURF_REMAP = 10; + idRenderModel *model; + int surfRemap[NUM_SURF_REMAP]; //Will have a nuber of elements set to a certain surface's index based on the number of triangles in that surface + // so surfaces with only like a few triangles don't have very dense particles generated for them. + + void CalculateSurfRemap( void ); +}; + +class rvParticleParms +{ + friend class rvParticleTemplate; + friend class rvParticle; + friend class rvLineParticle; + friend void SpawnStub( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnNone1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnNone2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnNone3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnOne1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnOne2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnOne3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnPoint1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnPoint2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnPoint3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnLinear1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnLinear2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnLinear3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnBox1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnBox2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnBox3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnSurfaceBox1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSurfaceBox2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSurfaceBox3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnSphere1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSphere2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSphere3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnSurfaceSphere1( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSurfaceSphere2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSurfaceSphere3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnCylinder3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSurfaceCylinder3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnSpiral2( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + friend void SpawnSpiral3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + + friend void SpawnModel3( float *result, const rvParticleParms &parms, idVec3 *normal, const idVec3 * ); + +public: + rvParticleParms( void ) { Init(); } + ~rvParticleParms( void ) {} + + void operator= ( const rvParticleParms &other ) { + memcpy( this, &other, sizeof( rvParticleParms ) ); + } + + bool operator== ( const rvParticleParms &comp ) const { return( Compare( comp ) ); } + bool operator!= ( const rvParticleParms &comp ) const { return( !Compare( comp ) ); } + + void Init( int spawnType = SPF_NONE_0 ) { mSpawnType = spawnType; mFlags = 0; mRange = 0.0f; mMisc = NULL; mMins.Zero(); mMaxs.Zero(); } + ID_INLINE void Spawn( float *dest, const rvParticleParms &parms, idVec3 *normal, const idVec3 *centre ) { ( *spawnFunctions[mSpawnType] )( dest, parms, normal, centre ); } + ID_INLINE void Spawn( float *dest, const rvParticleParms &parms ) { ( *spawnFunctions[mSpawnType] )( dest, parms, NULL, NULL ); } + void HandleRelativeParms( float *death, float *init, int count ); + void GetMinsMaxs( idVec3 &mins, idVec3 &maxs ); + + ID_INLINE int GetFlags( void ) const { return mFlags; } + ID_INLINE const idVec3 & GetMins( void ) const { return mMins; } + ID_INLINE const idVec3 & GetMaxs( void ) const { return mMaxs; } + +private: + int mSpawnType; // Type of spawn domain (eg. box, spiral) + int mFlags; // PPFLAG_ + float mRange; // Repeat length of spiral + void *mMisc; // Domain-specific auxiliary data. + idVec3 mMins; // Box mins, line start etc + idVec3 mMaxs; // Box maxs, line end etc + bool Compare( const rvParticleParms &comp ) const; + + static TSpawnFunc spawnFunctions[SPF_COUNT]; +}; + +void SpawnStub( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnNone1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnNone2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnNone3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnOne1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnOne2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnOne3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnPoint1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnPoint2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnPoint3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnLinear1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnLinear2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnLinear3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnBox1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnBox2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnBox3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnSurfaceBox1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSurfaceBox2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSurfaceBox3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnSphere1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSphere2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSphere3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnSurfaceSphere1( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSurfaceSphere2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSurfaceSphere3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnCylinder3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSurfaceCylinder3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnSpiral2( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); +void SpawnSpiral3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +void SpawnModel3( float *result, const rvParticleParms &parms, idVec3 *normal = NULL, const idVec3 * = NULL ); + +#endif // _BSE_SPAWN_DOMAINS_H_INC_ diff --git a/src/cm/CollisionModel.cpp b/src/cm/CollisionModel.cpp new file mode 100644 index 0000000..a27f06e --- /dev/null +++ b/src/cm/CollisionModel.cpp @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=========================================================================== + +Quake 4 source reconstruction + +This translation unit is reconstructed from the matching retail PDB and the +Hex-Rays listing for quake4.exe. Function RVAs in the retail image: + + 0x10001000 idCollisionModelLocal::GetContents + 0x10001050 idCollisionModelLocal::GetBounds + 0x10001080 idCollisionModelLocal::GetVertex + 0x100010D0 idCollisionModelLocal::GetEdge + 0x100011C0 idCollisionModelLocal::GetPolygon + 0x10039510 idCollisionModelLocal::GetName + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +const char *idCollisionModelLocal::GetName( void ) const { + return name.c_str(); +} + +bool idCollisionModelLocal::GetBounds( idBounds &modelBounds ) const { + modelBounds = bounds; + return true; +} + +bool idCollisionModelLocal::GetContents( int &modelContents ) const { + modelContents = contents; + return true; +} + +bool idCollisionModelLocal::GetVertex( int vertexNum, idVec3 &vertex ) const { + if ( vertexNum < 0 || vertexNum >= numVertices ) { + common->Printf( "idCollisionModelLocal::GetModelVertex: invalid vertex number\n" ); + return false; + } + + vertex = vertices[vertexNum].p; + return true; +} + +bool idCollisionModelLocal::GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const { + const int absoluteEdgeNum = abs( edgeNum ); + if ( absoluteEdgeNum >= numEdges ) { + common->Printf( "idCollisionModelLocal::GetModelEdge: invalid edge number\n" ); + return false; + } + + start = vertices[edges[absoluteEdgeNum].vertexNum[0]].p; + end = vertices[edges[absoluteEdgeNum].vertexNum[1]].p; + return true; +} + +bool idCollisionModelLocal::GetPolygon( int polygonNum, idFixedWinding &winding ) const { + const cm_polygon_t &polygon = polygons[polygonNum]; + + winding.Clear(); + for ( int i = 0; i < polygon.numEdges; i++ ) { + const int edgeNum = polygon.edges[i]; + const int vertexNum = edges[abs( edgeNum )].vertexNum[INTSIGNBITSET( edgeNum )]; + winding += vertices[vertexNum].p; + } + + return true; +} diff --git a/src/cm/CollisionModel.h b/src/cm/CollisionModel.h new file mode 100644 index 0000000..0fd8522 --- /dev/null +++ b/src/cm/CollisionModel.h @@ -0,0 +1,173 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __COLLISIONMODELMANAGER_H__ +#define __COLLISIONMODELMANAGER_H__ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + + Short translations are the least expensive. Retrieving contact points is + about as cheap as a short translation. Position tests are more expensive + and rotations are most expensive. + + There is no position test at the start of a translation or rotation. In other + words if a translation with start != end or a rotation with angle != 0 starts + in solid, this goes unnoticed and the collision result is undefined. + + A translation with start == end or a rotation with angle == 0 performs + a position test and fills in the trace_t structure accordingly. + +=============================================================================== +*/ + +// contact type +enum contactType_t { + CONTACT_NONE, // no contact + CONTACT_EDGE, // trace model edge hits model edge + CONTACT_MODELVERTEX, // model vertex hits trace model polygon + CONTACT_TRMVERTEX // trace model vertex hits model polygon +}; + +// contact info +struct contactInfo_t { + contactType_t type; // contact type + idVec3 point; // point of contact + idVec3 normal; // contact plane normal + float dist; // contact plane distance + float separation; // contact feature separation at initial position + int contents; // contents at other side of surface + const idMaterial * material; // surface material + int modelFeature; // contact feature on model + int trmFeature; // contact feature on trace model + int entityNum; // entity the contact surface is a part of + int id; // id of clip model the contact surface is part of +// RAVEN BEGIN +// jscott: for material type code + const rvDeclMatType *materialType; // material type of texture (possibly indirected though a hit map) +// RAVEN END +}; + +// trace result +struct trace_t { + float fraction; // fraction of movement completed, 1.0 = didn't hit anything + idVec3 endpos; // final position of trace model + idMat3 endAxis; // final axis of trace model + contactInfo_t c; // contact information, only valid if fraction < 1.0 +}; + +#define WORLD_MODEL_NAME "worldMap" // name of world model +#define CM_CLIP_EPSILON 0.25f // always stay this distance away from any model +#define CM_BOX_EPSILON 1.0f // should always be larger than clip epsilon +#define CM_MAX_TRACE_DIST 4096.0f // maximum distance a trace model may be traced, point traces are unlimited + +// collision model +class idCollisionModel { +public: + virtual ~idCollisionModel() { } + // Returns the name of the model. + virtual const char * GetName( void ) const = 0; + // Gets the bounds of the model. + virtual bool GetBounds( idBounds &bounds ) const = 0; + // Gets all contents flags of brushes and polygons of the model ored together. + virtual bool GetContents( int &contents ) const = 0; + // Gets a vertex of the model. + virtual bool GetVertex( int vertexNum, idVec3 &vertex ) const = 0; + // Gets an edge of the model. + virtual bool GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Gets a polygon of the model. + virtual bool GetPolygon( int polygonNum, idFixedWinding &winding ) const = 0; +}; + +// collision model manager +class idCollisionModelManager { +public: + virtual ~idCollisionModelManager( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // Loads collision models from a map file. + virtual void LoadMap( const idMapFile *mapFile, bool forceReload ) = 0; + // Frees all the collision models for the given map. + virtual void FreeMap( const char *mapName ) = 0; + + // Loads a collision model. + virtual idCollisionModel * LoadModel( const char *mapName, const char *modelName ) = 0; +// RAVEN BEGIN +// mwhitlock: conversion from idRenderModel to MD5R fixes (to keep redundant collision surfaces out of the MD5R files). + virtual idCollisionModel * ExtractCollisionModel( idRenderModel *renderModel, const char *modelName ) = 0; +// RAVEN END + // Precaches a collision model. + virtual void PreCacheModel( const char *mapName, const char *modelName ) = 0; + // Free the given model. + virtual void FreeModel( idCollisionModel *model ) = 0; + // Purge all unused models. + virtual void PurgeModels( void ) = 0; + + // Sets up a trace model for collision with other trace models. + virtual idCollisionModel * ModelFromTrm( const char *mapName, const char *modelName, const idTraceModel &trm, const idMaterial *material ) = 0; + // Creates a trace model from a collision model, returns true if succesfull. + virtual bool TrmFromModel( const char *mapName, const char *modelName, idTraceModel &trm ) = 0; + // Creates a trace model for each primitive of the collision model, returns the number of trace models. + virtual int CompoundTrmFromModel( const char *mapName, const char *modelName, idTraceModel *trms, int maxTrms ) = 0; + + // Translates a trace model and reports the first collision if any. + virtual void Translation( trace_t *results, const idVec3 &start, const idVec3 &end, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Rotates a trace model and reports the first collision if any. + virtual void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Returns the contents touched by the trace model or 0 if the trace model is in free space. + virtual int Contents( const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + // Stores all contact points of the trace model with the model, returns the number of contacts. + virtual int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0; + + // Tests collision detection. + virtual void DebugOutput( const idVec3 &viewOrigin, const idMat3 &viewAxis ) = 0; + // Draws a model. + virtual void DrawModel( idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const idMat3 &viewAxis, const float radius ) = 0; + // Lists all loaded models. + virtual void ListModels( void ) = 0; + // Prints model information, use -1 for accumulated model info. + virtual void ModelInfo( int num ) = 0; + virtual void PrintMemInfo( MemInfo *mi ) = 0; + virtual bool IsLoaded( void ) = 0; + + // Writes a collision model file for the given map entity. + virtual bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true ) = 0; +}; + +extern idCollisionModelManager *collisionModelManager; + +#endif /* !__COLLISIONMODELMANAGER_H__ */ diff --git a/src/cm/CollisionModel_local.h b/src/cm/CollisionModel_local.h new file mode 100644 index 0000000..338c939 --- /dev/null +++ b/src/cm/CollisionModel_local.h @@ -0,0 +1,878 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "CollisionModel.h" + +class Lexer; + +#define MIN_NODE_SIZE 64.0f +#define MAX_NODE_POLYGONS 128 +#define CM_MAX_POLYGON_EDGES 64 +#define CIRCLE_APPROXIMATION_LENGTH 64.0f + +#define MAX_SUBMODELS 2048 +#define TRACE_MODEL_HANDLE MAX_SUBMODELS + +#define VERTEX_HASH_BOXSIZE (1<<6) // must be power of 2 +#define VERTEX_HASH_SIZE (VERTEX_HASH_BOXSIZE*VERTEX_HASH_BOXSIZE) +#define EDGE_HASH_SIZE (1<<14) + +#define NODE_BLOCK_SIZE_SMALL 8 +#define NODE_BLOCK_SIZE_LARGE 256 +#define REFERENCE_BLOCK_SIZE_SMALL 8 +#define REFERENCE_BLOCK_SIZE_LARGE 256 + +#define MAX_WINDING_LIST 128 // quite a few are generated at times +#define INTEGRAL_EPSILON 0.01f +#define VERTEX_EPSILON 0.1f +#define CHOP_EPSILON 0.1f + + +typedef struct cm_windingList_s { + int numWindings; // number of windings + idFixedWinding w[MAX_WINDING_LIST]; // windings + idVec3 normal; // normal for all windings + idBounds bounds; // bounds of all windings in list + idVec3 origin; // origin for radius + float radius; // radius relative to origin for all windings + int contents; // winding surface contents + int primitiveNum; // number of primitive the windings came from +} cm_windingList_t; + +/* +=============================================================================== + +Collision model + +=============================================================================== +*/ + +typedef struct cm_vertex_s { + int checkcount; // for multi-check avoidance + idVec3 p; // vertex point + unsigned long side; // each bit tells at which side this vertex passes one of the trace model edges + unsigned long sideSet; // each bit tells if sidedness for the trace model edge has been calculated yet +} cm_vertex_t; + +typedef struct cm_edge_s { + int checkcount; // for multi-check avoidance + unsigned short internal; // a trace model can never collide with internal edges + unsigned short numUsers; // number of polygons using this edge + unsigned long side; // each bit tells at which side of this edge one of the trace model vertices passes + unsigned long sideSet; // each bit tells if sidedness for the trace model vertex has been calculated yet + int vertexNum[2]; // start and end point of edge + idVec3 normal; // edge normal +} cm_edge_t; + +typedef struct cm_polygonBlock_s { + int bytesRemaining; + byte * next; +} cm_polygonBlock_t; + +typedef struct cm_polygon_s { + idBounds bounds; // polygon bounds + idVec2 texBounds[3]; // texture-coordinate bounds for material type lookup + int checkcount; // for multi-check avoidance + int contents; // contents behind polygon + const idMaterial * material; // material + int primitiveNum; // source primitive number + idPlane plane; // polygon plane + int numEdges; // number of edges + int * edges; // indexes into the model polygon-edge array +} cm_polygon_t; + +typedef struct cm_polygonRef_s { + cm_polygon_t * p; // pointer to polygon + struct cm_polygonRef_s *next; // next polygon in chain +} cm_polygonRef_t; + +typedef struct cm_polygonRefBlock_s { + cm_polygonRef_t * nextRef; // next polygon reference in block + struct cm_polygonRefBlock_s *next; // next block with polygon references +} cm_polygonRefBlock_t; + +typedef struct cm_brushBlock_s { + int bytesRemaining; + byte * next; +} cm_brushBlock_t; + +typedef struct cm_brush_s { + idBounds bounds; // brush bounds + int checkcount; // for multi-check avoidance + int contents; // contents of brush + const idMaterial * material; // material + int primitiveNum; // number of brush primitive + int numPlanes; // number of bounding planes + idPlane * planes; // planes in the model brush-plane array +} cm_brush_t; + +typedef struct cm_brushRef_s { + cm_brush_t * b; // pointer to brush + struct cm_brushRef_s * next; // next brush in chain +} cm_brushRef_t; + +typedef struct cm_brushRefBlock_s { + cm_brushRef_t * nextRef; // next brush reference in block + struct cm_brushRefBlock_s *next; // next block with brush references +} cm_brushRefBlock_t; + +typedef struct cm_node_s { + int planeType; // node axial plane type + float planeDist; // node plane distance + cm_polygonRef_t * polygons; // polygons in node + cm_brushRef_t * brushes; // brushes in node + struct cm_node_s * parent; // parent of this node + struct cm_node_s * children[2]; // node children +} cm_node_t; + +typedef struct cm_nodeBlock_s { + cm_node_t * nextNode; // next node in block + struct cm_nodeBlock_s *next; // next block with nodes +} cm_nodeBlock_t; + +#if defined( _WIN32 ) +static_assert( sizeof( cm_vertex_t ) == 0x18, "cm_vertex_t ABI drift" ); +static_assert( sizeof( cm_edge_t ) == 0x24, "cm_edge_t ABI drift" ); +static_assert( sizeof( cm_polygon_t ) == 0x58, "cm_polygon_t ABI drift" ); +static_assert( sizeof( cm_brush_t ) == 0x30, "cm_brush_t ABI drift" ); +static_assert( sizeof( cm_node_t ) == 0x1C, "cm_node_t ABI drift" ); +#endif + +typedef struct cm_model_s { + idStr name; // model name + idBounds bounds; // model bounds + int contents; // all contents of the model ored together + bool isConvex; // set if model is convex + // model geometry + int maxVertices; // size of vertex array + int numVertices; // number of vertices + cm_vertex_t * vertices; // array with all vertices used by the model + int maxEdges; // size of edge array + int numEdges; // number of edges + cm_edge_t * edges; // array with all edges used by the model + cm_node_t * node; // first node of spatial subdivision + // blocks with allocated memory + cm_nodeBlock_t * nodeBlocks; // list with blocks of nodes + cm_polygonRefBlock_t * polygonRefBlocks; // list with blocks of polygon references + cm_brushRefBlock_t * brushRefBlocks; // list with blocks of brush references + cm_polygonBlock_t * polygonBlock; // memory block with all polygons + cm_brushBlock_t * brushBlock; // memory block with all brushes + // statistics + int numPolygons; + int polygonMemory; + int numBrushes; + int brushMemory; + int numNodes; + int numBrushRefs; + int numPolygonRefs; + int numInternalEdges; + int numSharpEdges; + int numRemovedPolys; + int numMergedPolys; + int usedMemory; +} cm_model_t; + +/* +=============================================================================== + + Quake 4 collision model object + + The retail executable replaced Doom 3's integer cmHandle_t API with an + idCollisionModel object. This layout is taken from the matching retail PDB + (idCollisionModelLocal, size 0xC8 on Win32). The older cm_model_t above is + kept temporarily while the seeded Doom 3 manager implementation is ported + one translation unit at a time. + +=============================================================================== +*/ + +class idCollisionModelLocal : public idCollisionModel { +public: + virtual ~idCollisionModelLocal( void ); + virtual const char * GetName( void ) const; + virtual bool GetBounds( idBounds &bounds ) const; + virtual bool GetContents( int &contents ) const; + virtual bool GetVertex( int vertexNum, idVec3 &vertex ) const; + virtual bool GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const; + virtual bool GetPolygon( int polygonNum, idFixedWinding &winding ) const; + + idStr name; + int refCount; + int fileTime; + idBounds bounds; + int contents; + bool isTraceModel; + bool isConvex; + int maxVertices; + int numVertices; + cm_vertex_t * vertices; + int maxEdges; + int numEdges; + cm_edge_t * edges; + int maxPolygonEdges; + int numPolygonEdges; + int * polygonEdges; + int maxPolygons; + int numPolygons; + cm_polygon_t * polygons; + int maxBrushPlanes; + int numBrushPlanes; + idPlane * brushPlanes; + int maxBrushes; + int numBrushes; + cm_brush_t * brushes; + int numNodes; + cm_node_t * node; + cm_nodeBlock_t * nodeBlocks; + cm_polygonRefBlock_t * polygonRefBlocks; + cm_brushRefBlock_t * brushRefBlocks; + int numPrimitives; + int numBrushRefs; + int numPolygonRefs; + int numInternalEdges; + int numSharpEdges; + int numRemovedPolys; + int numMergedPolys; + int usedMemory; +}; + +#if defined( _WIN32 ) +static_assert( sizeof( idCollisionModelLocal ) == 0xC8, "idCollisionModelLocal ABI drift" ); +#endif + +/* +=============================================================================== + +Data used during collision detection calculations + +=============================================================================== +*/ + +#if defined( Q4_CM_LEGACY_SEED ) + +typedef struct cm_trmVertex_s { + int used; // true if this vertex is used for collision detection + idVec3 p; // vertex position + idVec3 endp; // end point of vertex after movement + int polygonSide; // side of polygon this vertex is on (rotational collision) + idPluecker pl; // pluecker coordinate for vertex movement + idVec3 rotationOrigin; // rotation origin for this vertex + idBounds rotationBounds; // rotation bounds for this vertex +} cm_trmVertex_t; + +typedef struct cm_trmEdge_s { + int used; // true when vertex is used for collision detection + idVec3 start; // start of edge + idVec3 end; // end of edge + int vertexNum[2]; // indexes into cm_traceWork_t->vertices + idPluecker pl; // pluecker coordinate for edge + idVec3 cross; // (z,-y,x) of cross product between edge dir and movement dir + idBounds rotationBounds; // rotation bounds for this edge + idPluecker plzaxis; // pluecker coordinate for rotation about the z-axis + unsigned short bitNum; // vertex bit number +} cm_trmEdge_t; + +typedef struct cm_trmPolygon_s { + int used; + idPlane plane; // polygon plane + int numEdges; // number of edges + int edges[MAX_TRACEMODEL_POLYEDGES]; // index into cm_traceWork_t->edges + idBounds rotationBounds; // rotation bounds for this polygon +} cm_trmPolygon_t; + +typedef struct cm_traceWork_s { + int numVerts; + cm_trmVertex_t vertices[MAX_TRACEMODEL_VERTS]; // trm vertices + int numEdges; + cm_trmEdge_t edges[MAX_TRACEMODEL_EDGES+1]; // trm edges + int numPolys; + cm_trmPolygon_t polys[MAX_TRACEMODEL_POLYS]; // trm polygons + cm_model_t *model; // model colliding with + idVec3 start; // start of trace + idVec3 end; // end of trace + idVec3 dir; // trace direction + idBounds bounds; // bounds of full trace + idBounds size; // bounds of transformed trm relative to start + idVec3 extents; // largest of abs(size[0]) and abs(size[1]) for BSP trace + int contents; // ignore polygons that do not have any of these contents flags + trace_t trace; // collision detection result + + bool rotation; // true if calculating rotational collision + bool pointTrace; // true if only tracing a point + bool positionTest; // true if not tracing but doing a position test + bool isConvex; // true if the trace model is convex + bool axisIntersectsTrm; // true if the rotation axis intersects the trace model + bool getContacts; // true if retrieving contacts + bool quickExit; // set to quickly stop the collision detection calculations + + idVec3 origin; // origin of rotation in model space + idVec3 axis; // rotation axis in model space + idMat3 matrix; // rotates axis of rotation to the z-axis + float angle; // angle for rotational collision + float maxTan; // max tangent of half the positive angle used instead of fraction + float radius; // rotation radius of trm start + idRotation modelVertexRotation; // inverse rotation for model vertices + + contactInfo_t *contacts; // array with contacts + int maxContacts; // max size of contact array + int numContacts; // number of contacts found + + idPlane heartPlane1; // polygons should be near anough the trace heart planes + float maxDistFromHeartPlane1; + idPlane heartPlane2; + float maxDistFromHeartPlane2; + idPluecker polygonEdgePlueckerCache[CM_MAX_POLYGON_EDGES]; + idPluecker polygonVertexPlueckerCache[CM_MAX_POLYGON_EDGES]; + idVec3 polygonRotationOriginCache[CM_MAX_POLYGON_EDGES]; +} cm_traceWork_t; + +#else + +// Retail Quake 4 layouts. These deliberately differ from Doom 3's trace +// workspace above; member order and padding come from the matching PDB. +typedef struct cm_trmVertex_t { + idVec3 p; + int used; + idVec3 endp; + int pad0; + idPluecker pl; + idVec3 rotationOrigin; + int pad1; + idBounds rotationBounds; +} cm_trmVertex_t; + +typedef struct cm_trmEdge_t { + short used; + short bitNum; + idVec3 cross; + int vertexNum[2]; + idPluecker pl; + idPluecker plzaxis; + idBounds rotationBounds; +} cm_trmEdge_t; + +typedef struct cm_trmPolygon_t { + idPlane plane; + int used; + int numEdges; + int edges[MAX_TRACEMODEL_POLYEDGES]; + idBounds rotationBounds; +} cm_trmPolygon_t; + +typedef struct cm_traceWork_t { + cm_trmVertex_t vertices[MAX_TRACEMODEL_VERTS]; + cm_trmEdge_t edges[MAX_TRACEMODEL_EDGES + 1]; + cm_trmPolygon_t polys[MAX_TRACEMODEL_POLYS]; + int numVerts; + int numEdges; + int numPolys; + idVec3 start; + idVec3 end; + idVec3 dir; + idBounds traceBounds; + idBounds trmBounds; + idVec3 trmExtents; + int contents; + idCollisionModelLocal *model; + idPlane heartPlane1; + idPlane heartPlane2; + float maxDistFromHeartPlane1; + float maxDistFromHeartPlane2; + idVec3 origin; + idVec3 axis; + idMat3 matrix; + float angle; + float maxTan; + float radius; + float maxErr; + idRotation modelVertexRotation; + bool rotation; + bool pointTrace; + bool positionTest; + bool isConvex; + bool axisIntersectsTrm; + bool getContacts; + bool quickExit; + trace_t trace; + contactInfo_t * contacts; + int maxContacts; + int numContacts; + idPluecker polygonEdgePlueckerCache[CM_MAX_POLYGON_EDGES]; + idPluecker polygonVertexPlueckerCache[CM_MAX_POLYGON_EDGES]; +} cm_traceWork_t; + +#if defined( _WIN32 ) +static_assert( sizeof( cm_trmVertex_t ) == 0x60, "cm_trmVertex_t ABI drift" ); +static_assert( sizeof( cm_trmEdge_t ) == 0x60, "cm_trmEdge_t ABI drift" ); +static_assert( sizeof( cm_trmPolygon_t ) == 0x70, "cm_trmPolygon_t ABI drift" ); +static_assert( offsetof( cm_traceWork_t, numVerts ) == 0x1F60, "cm_traceWork_t::numVerts ABI drift" ); +static_assert( offsetof( cm_traceWork_t, model ) == 0x1FD0, "cm_traceWork_t::model ABI drift" ); +static_assert( offsetof( cm_traceWork_t, trace ) == 0x2094, "cm_traceWork_t::trace ABI drift" ); +static_assert( offsetof( cm_traceWork_t, polygonEdgePlueckerCache ) == 0x2114, "cm_traceWork_t cache ABI drift" ); +static_assert( sizeof( cm_traceWork_t ) == 0x2D14, "cm_traceWork_t ABI drift" ); +#endif + +#endif + +/* +=============================================================================== + +Collision Map + +=============================================================================== +*/ + +typedef struct cm_procNode_s { + idPlane plane; + int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid +} cm_procNode_t; + +#ifndef Q4_CM_MODEL_ONLY + +// Compatibility surface for compiling the Doom 3 seed while each manager +// translation unit is converted to Quake 4's object-based public API. +#if defined( Q4_CM_LEGACY_SEED ) +typedef int cmHandle_t; +#endif + +#if !defined( Q4_CM_LEGACY_SEED ) +class idCollisionModelManagerLocal : public idCollisionModelManager { +public: + idCollisionModelManagerLocal( void ); + virtual ~idCollisionModelManagerLocal( void ); + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void LoadMap( const idMapFile *mapFile, bool forceReload ); + virtual void FreeMap( const char *mapName ); + virtual idCollisionModel *LoadModel( const char *mapName, const char *modelName ); + virtual idCollisionModel *ExtractCollisionModel( idRenderModel *renderModel, const char *modelName ); + virtual void PreCacheModel( const char *mapName, const char *modelName ); + virtual void FreeModel( idCollisionModel *model ); + virtual void PurgeModels( void ); + virtual idCollisionModel *ModelFromTrm( const char *mapName, const char *modelName, const idTraceModel &trm, const idMaterial *material ); + virtual bool TrmFromModel( const char *mapName, const char *modelName, idTraceModel &trm ); + virtual int CompoundTrmFromModel( const char *mapName, const char *modelName, idTraceModel *trms, int maxTrms ); + virtual void Translation( trace_t *results, const idVec3 &start, const idVec3 &end, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual int Contents( const idVec3 &start, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual void DebugOutput( const idVec3 &viewOrigin, const idMat3 &viewAxis ); + virtual void DrawModel( idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis, const idVec3 &viewOrigin, const idMat3 &viewAxis, const float radius ); + virtual void ListModels( void ); + virtual void ModelInfo( int num ); + virtual void PrintMemInfo( MemInfo *mi ); + virtual bool IsLoaded( void ); + virtual bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true ); + + bool TrmFromModel( const idCollisionModelLocal *model, idTraceModel &trm ); + int CompoundTrmFromModel( const idCollisionModelLocal *model, idTraceModel *trms, int maxTrms ); + void PolygonFromWinding( idCollisionModelLocal *model, idFixedWinding *winding, const idPlane &plane, const idMaterial *material, int primitiveNum ); + +private: + void Clear( void ); + void ClearModel( idCollisionModelLocal *model ); + idCollisionModelLocal *AllocModel( void ); + cm_node_t * AllocNode( idCollisionModelLocal *model, int blockSize ); + cm_polygonRef_t *AllocPolygonReference( idCollisionModelLocal *model, int blockSize ); + cm_brushRef_t * AllocBrushReference( idCollisionModelLocal *model, int blockSize ); + cm_polygon_t * AllocPolygon( idCollisionModelLocal *model, int numEdges ); + cm_brush_t * AllocBrush( idCollisionModelLocal *model, int numPlanes ); + void CopyPolygon( cm_polygon_t *dest, cm_polygon_t *src ); + void RemapEdges( cm_node_t *node, int *edgeRemap ); + void RemapPolygonReferences_r( cm_node_t *node, cm_polygon_t *polygon, cm_polygon_t *newPolygon ); + void RemapBrushReferences_r( cm_node_t *node, cm_brush_t *brush, cm_brush_t *newBrush ); + void RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *polygon ); + void RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *brush ); + void FreeTree_r( idCollisionModelLocal *model, cm_node_t *headNode, cm_node_t *node ); + void FreeModelMemory( idCollisionModelLocal *model ); + void AddToMapModelReferenceCounts( const char *mapName, int add ); + void PrintModelInfo( const idCollisionModelLocal *model ); + void AccumulateModelInfo( idCollisionModelLocal *model ); + void ClearHash( idBounds &bounds ); + void SetupHash( void ); + void ShutdownHash( void ); + void MergeTreePolygons( idCollisionModelLocal *model, cm_node_t *node, bool mergePrimitives ); + void FindInternalEdges( idCollisionModelLocal *model, cm_node_t *node ); + void CalculateEdgeNormals( idCollisionModelLocal *model, cm_node_t *node ); + void OptimizeArrays( idCollisionModelLocal *model ); + void FinishModel( idCollisionModelLocal *model, bool mergePrimitives ); + bool TrmFromModel_r( idTraceModel &trm, cm_node_t *node, int primitiveNum ); + cm_node_t * PointNode( const idVec3 &point, idCollisionModelLocal *model ); + int PointContents( const idVec3 point, idCollisionModelLocal *model ); + int TransformedPointContents( const idVec3 &point, idCollisionModelLocal *model, const idVec3 &origin, const idMat3 &modelAxis ); + int ContentsTrm( trace_t *results, const idVec3 &start, const idTraceModel *trm, const idMat3 &trmAxis, + int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void SetupTrm( cm_traceWork_t *traceWork, const idTraceModel *trm ); + void TraceThroughModel( cm_traceWork_t *traceWork ); + void TraceThroughAxialBSPTree_r( cm_traceWork_t *traceWork, cm_node_t *node, + float p1f, float p2f, idVec3 &p1, idVec3 &p2 ); + void TraceTrmThroughNode( cm_traceWork_t *traceWork, cm_node_t *node ); + bool TestTrmVertsInBrush( cm_traceWork_t *traceWork, cm_brush_t *brush ); + bool TestTrmInPolygon( cm_traceWork_t *traceWork, cm_polygon_t *polygon ); + bool RotateTrmThroughPolygon( cm_traceWork_t *traceWork, cm_polygon_t *polygon ); + bool TranslateTrmThroughPolygon( cm_traceWork_t *traceWork, cm_polygon_t *polygon ); + void CM_GetCollisionPointTexCoords( idVec2 &texCoord, cm_traceWork_t *traceWork, cm_polygon_t *polygon ); + void CM_GetMaterialType( cm_traceWork_t *traceWork, cm_polygon_t *polygon ); + bool IsRenderModelName( const char *name ); + const char * GetFullModelName( const char *mapName, const char *modelName ); + idCollisionModelLocal *FindModel( const char *name ); + + // CollisionModel_rotate.obj + int CollisionBetweenEdgeBounds( cm_traceWork_t *tw, const idVec3 &va, const idVec3 &vb, + const idVec3 &vc, const idVec3 &vd, float tanHalfAngle, + idVec3 &collisionPoint, idVec3 &collisionNormal ); + void RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ); + int RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, const idVec3 &point, const idVec3 &endPoint, + const idPlane &plane, float angle, const idVec3 &origin, + float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir ); + void RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *vertex, int vertexNum ); + void RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmPoly, cm_polygon_t *poly, + cm_vertex_t *vertex, idVec3 &rotationOrigin ); + void Rotation180( trace_t *results, const idVec3 &rotationOrigin, const idVec3 &axis, + float startAngle, float endAngle, const idVec3 &start, const idTraceModel *trm, + const idMat3 &trmAxis, int contentMask, idCollisionModel *model, + const idVec3 &modelOrigin, const idMat3 &modelAxis ); + + // CollisionModel_files.obj + void WriteNodes( idFile *file, cm_node_t *node ); + cm_node_t * ParseNodes( Lexer *lexer, idCollisionModelLocal *model, cm_node_t *parent ); + void WritePolygons( idFile *file, cm_node_t *node ); + void WriteBrushes( idFile *file, cm_node_t *node ); + void ParseVertices( Lexer *lexer, idCollisionModelLocal *model ); + void ParseEdges( Lexer *lexer, idCollisionModelLocal *model ); + void ParsePolygons( Lexer *lexer, idCollisionModelLocal *model ); + void ParseBrushes( Lexer *lexer, idCollisionModelLocal *model ); + void WriteCollisionModel( idFile *file, idCollisionModelLocal *model ); + void WriteCollisionModelsToFile( const char *filename, unsigned int mapFileCRC ); + bool ParseCollisionModel( Lexer *lexer, const char *filename, unsigned int mapFileCRC ); + bool LoadCollisionModelFile( const char *filename, unsigned int mapFileCRC ); + + // CollisionModel_debug.obj + const char * StringFromContents( int contents ) const; + int ContentsFromString( const char *string ) const; + void DrawEdge( idCollisionModelLocal *model, int edgeNum, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const idMat3 &viewAxis, float radius ); + void DrawPolygon( idCollisionModelLocal *model, cm_polygon_t *polygon, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const idMat3 &viewAxis, float radius ); + void DrawNodePolygons( idCollisionModelLocal *model, cm_node_t *node, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const idMat3 &viewAxis, float radius ); + void DebugTranslationFailure( const idVec3 &viewOrigin, const idMat3 &viewAxis ); + void DebugRotationFailure( const idVec3 &viewOrigin, const idMat3 &viewAxis ); + void SpeedTest( const idVec3 &viewOrigin ); + +private: + idStr mapName; + unsigned int mapFileTime; + int loaded; + idList models; + int checkCount; + bool getContacts; + contactInfo_t * contacts; + int maxContacts; + int numContacts; + int numProcNodes; + cm_procNode_t * procNodes; + cm_windingList_t *cm_windingList; + cm_windingList_t *cm_outList; + cm_windingList_t *cm_tmpList; + idHashIndex * cm_vertexHash; + idHashIndex * cm_edgeHash; + idBounds cm_modelBounds; + int cm_vertexShift; +}; + +#if defined( _WIN32 ) +static_assert( sizeof( idCollisionModelManagerLocal ) == 0x88, "idCollisionModelManagerLocal ABI drift" ); +#endif + +#else +class idCollisionModelManagerLocal : public idCollisionModelManager { +public: +#if defined( Q4_CM_LEGACY_SEED ) + // Quake 4 virtual surface. Implementations replace the legacy overloads + // incrementally; declaring them here also keeps the seed class concrete. + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void LoadMap( const idMapFile *mapFile, bool forceReload ); + virtual void FreeMap( const char *mapName ); + virtual idCollisionModel *LoadModel( const char *mapName, const char *modelName ); + virtual idCollisionModel *ExtractCollisionModel( idRenderModel *renderModel, const char *modelName ); + virtual void PreCacheModel( const char *mapName, const char *modelName ); + virtual void FreeModel( idCollisionModel *model ); + virtual void PurgeModels( void ); + virtual idCollisionModel *ModelFromTrm( const char *mapName, const char *modelName, const idTraceModel &trm, const idMaterial *material ); + virtual bool TrmFromModel( const char *mapName, const char *modelName, idTraceModel &trm ); + virtual int CompoundTrmFromModel( const char *mapName, const char *modelName, idTraceModel *trms, int maxTrms ); + virtual void Translation( trace_t *results, const idVec3 &start, const idVec3 &end, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual int Contents( const idVec3 &start, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + virtual void DebugOutput( const idVec3 &viewOrigin, const idMat3 &viewAxis ); + virtual void DrawModel( idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis, const idVec3 &viewOrigin, const idMat3 &viewAxis, const float radius ); + virtual void PrintMemInfo( MemInfo *mi ); + virtual bool IsLoaded( void ); +#endif + // load collision models from a map file + void LoadMap( const idMapFile *mapFile ); + // frees all the collision models + void FreeMap( void ); + + // get clip handle for model + cmHandle_t LoadModel( const char *modelName, const bool precache ); + // sets up a trace model for collision with other trace models + cmHandle_t SetupTrmModel( const idTraceModel &trm, const idMaterial *material ); + // create trace model from a collision model, returns true if succesfull + bool TrmFromModel( const char *modelName, idTraceModel &trm ); + + // name of the model + const char * GetModelName( cmHandle_t model ) const; + // bounds of the model + bool GetModelBounds( cmHandle_t model, idBounds &bounds ) const; + // all contents flags of brushes and polygons ored together + bool GetModelContents( cmHandle_t model, int &contents ) const; + // get the vertex of a model + bool GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const; + // get the edge of a model + bool GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const; + // get the polygon of a model + bool GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const; + + // translates a trm and reports the first collision if any + void Translation( trace_t *results, const idVec3 &start, const idVec3 &end, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // rotates a trm and reports the first collision if any + void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // returns the contents the trm is stuck in or 0 if the trm is in free space + int Contents( const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // stores all contact points of the trm with the model, returns the number of contacts + int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // test collision detection + void DebugOutput( const idVec3 &origin ); + // draw a model + void DrawModel( cmHandle_t model, const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin, const float radius ); + // print model information, use -1 handle for accumulated model info + void ModelInfo( cmHandle_t model ); + // list all loaded models + void ListModels( void ); + // write a collision model file for the map entity + bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true ); + +private: // CollisionMap_translate.cpp + int TranslateEdgeThroughEdge( idVec3 &cross, idPluecker &l1, idPluecker &l2, float *fraction ); + void TranslateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ); + void TranslateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int bitNum ); + void TranslatePointThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v ); + void TranslateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &endp, idPluecker &pl ); + bool TranslateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ); + void SetupTranslationHeartPlanes( cm_traceWork_t *tw ); + void SetupTrm( cm_traceWork_t *tw, const idTraceModel *trm ); + +private: // CollisionMap_rotate.cpp + int CollisionBetweenEdgeBounds( cm_traceWork_t *tw, const idVec3 &va, const idVec3 &vb, + const idVec3 &vc, const idVec3 &vd, float tanHalfAngle, + idVec3 &collisionPoint, idVec3 &collisionNormal ); + int RotateEdgeThroughEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + const float minTan, float &tanHalfAngle ); + int EdgeFurthestFromEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + float &tanHalfAngle, float &dir ); + void RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ); + int RotatePointThroughPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, const float minTan, float &tanHalfAngle ); + int PointFurthestFromPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, float &tanHalfAngle, float &dir ); + int RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, const idVec3 &point, const idVec3 &endPoint, + const idPlane &plane, const float angle, const idVec3 &origin, + float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir ); + void RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int vertexNum); + void RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, + cm_vertex_t *v, idVec3 &rotationOrigin ); + bool RotateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ); + void BoundsForRotation( const idVec3 &origin, const idVec3 &axis, const idVec3 &start, const idVec3 &end, idBounds &bounds ); + void Rotation180( trace_t *results, const idVec3 &rorg, const idVec3 &axis, + const float startAngle, const float endAngle, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ); + +private: // CollisionMap_contents.cpp + bool TestTrmVertsInBrush( cm_traceWork_t *tw, cm_brush_t *b ); + bool TestTrmInPolygon( cm_traceWork_t *tw, cm_polygon_t *p ); + cm_node_t * PointNode( const idVec3 &p, cm_model_t *model ); + int PointContents( const idVec3 p, cmHandle_t model ); + int TransformedPointContents( const idVec3 &p, cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ); + int ContentsTrm( trace_t *results, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + +private: // CollisionMap_trace.cpp + void TraceTrmThroughNode( cm_traceWork_t *tw, cm_node_t *node ); + void TraceThroughAxialBSPTree_r( cm_traceWork_t *tw, cm_node_t *node, float p1f, float p2f, idVec3 &p1, idVec3 &p2); + void TraceThroughModel( cm_traceWork_t *tw ); + void RecurseProcBSP_r( trace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 ); + +private: // CollisionMap_load.cpp + void Clear( void ); + void FreeTrmModelStructure( void ); + // model deallocation + void RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *p ); + void RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *b ); + void FreeNode( cm_node_t *node ); + void FreePolygonReference( cm_polygonRef_t *pref ); + void FreeBrushReference( cm_brushRef_t *bref ); + void FreePolygon( cm_model_t *model, cm_polygon_t *poly ); + void FreeBrush( cm_model_t *model, cm_brush_t *brush ); + void FreeTree_r( cm_model_t *model, cm_node_t *headNode, cm_node_t *node ); + void FreeModel( cm_model_t *model ); + // merging polygons + void ReplacePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *p1, cm_polygon_t *p2, cm_polygon_t *newp ); + cm_polygon_t * TryMergePolygons( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ); + bool MergePolygonWithTreePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ); + void MergeTreePolygons( cm_model_t *model, cm_node_t *node ); + // finding internal edges + bool PointInsidePolygon( cm_model_t *model, cm_polygon_t *p, idVec3 &v ); + void FindInternalEdgesOnPolygon( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ); + void FindInternalPolygonEdges( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ); + void FindInternalEdges( cm_model_t *model, cm_node_t *node ); + void FindContainedEdges( cm_model_t *model, cm_polygon_t *p ); + // loading of proc BSP tree + void ParseProcNodes( idLexer *src ); + void LoadProcBSP( const char *name ); + // removal of contained polygons + int R_ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ); + int ChoppedAwayByProcBSP( const idFixedWinding &w, const idPlane &plane, int contents ); + void ChopWindingListWithBrush( cm_windingList_t *list, cm_brush_t *b ); + void R_ChopWindingListWithTreeBrushes( cm_windingList_t *list, cm_node_t *node ); + idFixedWinding *WindingOutsideBrushes( idFixedWinding *w, const idPlane &plane, int contents, int patch, cm_node_t *headNode ); + // creation of axial BSP tree + cm_model_t * AllocModel( void ); + cm_node_t * AllocNode( cm_model_t *model, int blockSize ); + cm_polygonRef_t*AllocPolygonReference( cm_model_t *model, int blockSize ); + cm_brushRef_t * AllocBrushReference( cm_model_t *model, int blockSize ); + cm_polygon_t * AllocPolygon( cm_model_t *model, int numEdges ); + cm_brush_t * AllocBrush( cm_model_t *model, int numPlanes ); + void AddPolygonToNode( cm_model_t *model, cm_node_t *node, cm_polygon_t *p ); + void AddBrushToNode( cm_model_t *model, cm_node_t *node, cm_brush_t *b ); + void SetupTrmModelStructure( void ); + void R_FilterPolygonIntoTree( cm_model_t *model, cm_node_t *node, cm_polygonRef_t *pref, cm_polygon_t *p ); + void R_FilterBrushIntoTree( cm_model_t *model, cm_node_t *node, cm_brushRef_t *pref, cm_brush_t *b ); + cm_node_t * R_CreateAxialBSPTree( cm_model_t *model, cm_node_t *node, const idBounds &bounds ); + cm_node_t * CreateAxialBSPTree( cm_model_t *model, cm_node_t *node ); + // creation of raw polygons + void SetupHash(void); + void ShutdownHash(void); + void ClearHash( idBounds &bounds ); + int HashVec(const idVec3 &vec); + int GetVertex( cm_model_t *model, const idVec3 &v, int *vertexNum ); + int GetEdge( cm_model_t *model, const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ); + void CreatePolygon( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ); + void PolygonFromWinding( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ); + void CalculateEdgeNormals( cm_model_t *model, cm_node_t *node ); + void CreatePatchPolygons( cm_model_t *model, idSurface_Patch &mesh, const idMaterial *material, int primitiveNum ); + void ConvertPatch( cm_model_t *model, const idMapPatch *patch, int primitiveNum ); + void ConvertBrushSides( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ); + void ConvertBrush( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ); + void PrintModelInfo( const cm_model_t *model ); + void AccumulateModelInfo( cm_model_t *model ); + void RemapEdges( cm_node_t *node, int *edgeRemap ); + void OptimizeArrays( cm_model_t *model ); + void FinishModel( cm_model_t *model ); + void BuildModels( const idMapFile *mapFile ); + cmHandle_t FindModel( const char *name ); + cm_model_t * CollisionModelForMapEntity( const idMapEntity *mapEnt ); // brush/patch model from .map + cm_model_t * LoadRenderModel( const char *fileName ); // ASE/LWO models + bool TrmFromModel_r( idTraceModel &trm, cm_node_t *node ); + bool TrmFromModel( const cm_model_t *model, idTraceModel &trm ); + +private: // CollisionMap_files.cpp + // writing + void WriteNodes( idFile *fp, cm_node_t *node ); + int CountPolygonMemory( cm_node_t *node ) const; + void WritePolygons( idFile *fp, cm_node_t *node ); + int CountBrushMemory( cm_node_t *node ) const; + void WriteBrushes( idFile *fp, cm_node_t *node ); + void WriteCollisionModel( idFile *fp, cm_model_t *model ); + void WriteCollisionModelsToFile( const char *filename, int firstModel, int lastModel, unsigned int mapFileCRC ); + // loading + cm_node_t * ParseNodes( idLexer *src, cm_model_t *model, cm_node_t *parent ); + void ParseVertices( idLexer *src, cm_model_t *model ); + void ParseEdges( idLexer *src, cm_model_t *model ); + void ParsePolygons( idLexer *src, cm_model_t *model ); + void ParseBrushes( idLexer *src, cm_model_t *model ); + bool ParseCollisionModel( idLexer *src ); + bool LoadCollisionModelFile( const char *name, unsigned int mapFileCRC ); + +private: // CollisionMap_debug + int ContentsFromString( const char *string ) const; + const char * StringFromContents( const int contents ) const; + void DrawEdge( cm_model_t *model, int edgeNum, const idVec3 &origin, const idMat3 &axis ); + void DrawPolygon( cm_model_t *model, cm_polygon_t *p, const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin ); + void DrawNodePolygons( cm_model_t *model, cm_node_t *node, const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin, const float radius ); + +private: // collision map data + idStr mapName; + ID_TIME_T mapFileTime; + int loaded; + // for multi-check avoidance + int checkCount; + // models + int maxModels; + int numModels; + cm_model_t ** models; + // polygons and brush for trm model + cm_polygonRef_t*trmPolygons[MAX_TRACEMODEL_POLYS]; + cm_brushRef_t * trmBrushes[1]; + const idMaterial *trmMaterial; + // for data pruning + int numProcNodes; + cm_procNode_t * procNodes; + // for retrieving contact points + bool getContacts; + contactInfo_t * contacts; + int maxContacts; + int numContacts; +}; +#endif /* Q4_CM_LEGACY_SEED */ + +// for debugging +extern idCVar cm_debugCollision; +#endif diff --git a/src/cm/collisionmodel_contacts.cpp b/src/cm/collisionmodel_contacts.cpp new file mode 100644 index 0000000..ebda1c1 --- /dev/null +++ b/src/cm/collisionmodel_contacts.cpp @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#if defined( Q4_CM_LEGACY_SEED ) +#include "collisionmodel_contacts_legacy.inc" +#else + +/* +=========================================================================== + +Quake 4 object-based contact entry point reconstructed in the exact +CollisionModel_contacts.obj source owner identified by quake4.pdb. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +int idCollisionModelManagerLocal::Contacts( contactInfo_t *contactList, const int contactLimit, + const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + contacts = contactList; + maxContacts = contactLimit; + getContacts = true; + numContacts = 0; + + const idVec3 end = start + depth * dir.SubVec3( 0 ); + trace_t results; + Translation( &results, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + + getContacts = false; + maxContacts = 0; + return numContacts; +} + +#endif diff --git a/src/cm/collisionmodel_contacts_legacy.inc b/src/cm/collisionmodel_contacts_legacy.inc new file mode 100644 index 0000000..3381925 --- /dev/null +++ b/src/cm/collisionmodel_contacts_legacy.inc @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Retrieving contacts + +=============================================================================== +*/ + +/* +================== +idCollisionModelManagerLocal::Contacts +================== +*/ +int idCollisionModelManagerLocal::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ) { + trace_t results; + idVec3 end; + + // same as Translation but instead of storing the first collision we store all collisions as contacts + idCollisionModelManagerLocal::getContacts = true; + idCollisionModelManagerLocal::contacts = contacts; + idCollisionModelManagerLocal::maxContacts = maxContacts; + idCollisionModelManagerLocal::numContacts = 0; + end = start + dir.SubVec3(0) * depth; + idCollisionModelManagerLocal::Translation( &results, start, end, trm, trmAxis, contentMask, model, origin, modelAxis ); + if ( dir.SubVec3(1).LengthSqr() != 0.0f ) { + // FIXME: rotational contacts + } + idCollisionModelManagerLocal::getContacts = false; + idCollisionModelManagerLocal::maxContacts = 0; + + return idCollisionModelManagerLocal::numContacts; +} diff --git a/src/cm/collisionmodel_contents.cpp b/src/cm/collisionmodel_contents.cpp new file mode 100644 index 0000000..3fd7574 --- /dev/null +++ b/src/cm/collisionmodel_contents.cpp @@ -0,0 +1,435 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#if defined( Q4_CM_LEGACY_SEED ) +#include "collisionmodel_contents_legacy.inc" +#else + +/* +=========================================================================== + +Quake 4 source reconstruction + +Compact collision-contents entry points recovered from the matching retail +PDB and address-matched Hex-Rays bodies. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +cm_node_t *idCollisionModelManagerLocal::PointNode( const idVec3 &point, idCollisionModelLocal *model ) { + cm_node_t *node = model->node; + while ( node->planeType != -1 ) { + node = point[node->planeType] > node->planeDist ? node->children[0] : node->children[1]; + } + return node; +} + +int idCollisionModelManagerLocal::PointContents( const idVec3 point, idCollisionModelLocal *model ) { + cm_node_t *node = PointNode( point, model ); + for ( cm_brushRef_t *ref = node->brushes; ref != NULL; ref = ref->next ) { + cm_brush_t *brush = ref->b; + int i; + for ( i = 0; i < 3; i++ ) { + if ( point[i] < brush->bounds[0][i] || point[i] > brush->bounds[1][i] ) { + break; + } + } + if ( i < 3 ) { + continue; + } + + for ( i = 0; i < brush->numPlanes; i++ ) { + if ( brush->planes[i].Distance( point ) >= 0.0f ) { + break; + } + } + if ( i >= brush->numPlanes ) { + return brush->contents; + } + } + return 0; +} + +int idCollisionModelManagerLocal::TransformedPointContents( const idVec3 &point, idCollisionModelLocal *model, + const idVec3 &origin, const idMat3 &modelAxis ) { + idVec3 localPoint = point - origin; + if ( modelAxis.IsRotated() ) { + localPoint *= modelAxis; + } + return PointContents( localPoint, model ); +} + +bool idCollisionModelManagerLocal::TestTrmVertsInBrush( cm_traceWork_t *traceWork, cm_brush_t *brush ) { + if ( brush->checkcount == checkCount ) { + return false; + } + brush->checkcount = checkCount; + if ( !( brush->contents & traceWork->contents ) + || !brush->bounds.IntersectsBounds( traceWork->traceBounds ) ) { + return false; + } + + const int numVertices = traceWork->pointTrace ? 1 : traceWork->numVerts; + for ( int vertex = 0; vertex < numVertices; vertex++ ) { + const idVec3 &point = traceWork->vertices[vertex].p; + int bestPlane = 0; + float bestDistance = -idMath::INFINITY; + int plane; + for ( plane = 0; plane < brush->numPlanes; plane++ ) { + const float distance = brush->planes[plane].Distance( point ); + if ( distance >= 0.0f ) { + break; + } + if ( distance > bestDistance ) { + bestDistance = distance; + bestPlane = plane; + } + } + if ( plane >= brush->numPlanes ) { + traceWork->trace.fraction = 0.0f; + traceWork->trace.c.type = CONTACT_TRMVERTEX; + traceWork->trace.c.normal = brush->planes[bestPlane].Normal(); + traceWork->trace.c.dist = brush->planes[bestPlane].Dist(); + traceWork->trace.c.contents = brush->contents; + traceWork->trace.c.material = brush->material; + traceWork->trace.c.point = point; + traceWork->trace.c.modelFeature = 0; + traceWork->trace.c.trmFeature = vertex; + CM_GetMaterialType( traceWork, NULL ); + return true; + } + } + return false; +} + +bool idCollisionModelManagerLocal::TestTrmInPolygon( cm_traceWork_t *traceWork, cm_polygon_t *polygon ) { + if ( polygon->checkcount == checkCount ) { + return false; + } + polygon->checkcount = checkCount; + if ( !( polygon->contents & traceWork->contents ) + || !polygon->bounds.IntersectsBounds( traceWork->traceBounds ) ) { + return false; + } + + switch ( traceWork->traceBounds.PlaneSide( polygon->plane ) ) { + case PLANESIDE_CROSS: + break; + case PLANESIDE_FRONT: + if ( traceWork->model->isConvex ) { + traceWork->quickExit = true; + return true; + } + default: + return false; + } + + if ( traceWork->isConvex ) { + for ( int i = 0; i < polygon->numEdges; i++ ) { + const int edgeNum = polygon->edges[i]; + cm_edge_t *edge = &traceWork->model->edges[abs( edgeNum )]; + if ( edge->checkcount == checkCount ) { + continue; + } + for ( int j = 0; j < 2; j++ ) { + cm_vertex_t *vertex = &traceWork->model->vertices[edge->vertexNum[j]]; + if ( vertex->checkcount == checkCount ) { + continue; + } + int bestPlane = 0; + float bestDistance = -idMath::INFINITY; + int plane; + for ( plane = 0; plane < traceWork->numPolys; plane++ ) { + const float distance = traceWork->polys[plane].plane.Distance( vertex->p ); + if ( distance >= 0.0f ) { + break; + } + if ( distance > bestDistance ) { + bestDistance = distance; + bestPlane = plane; + } + } + if ( plane >= traceWork->numPolys ) { + traceWork->trace.fraction = 0.0f; + traceWork->trace.c.type = CONTACT_MODELVERTEX; + traceWork->trace.c.normal = -traceWork->polys[bestPlane].plane.Normal(); + traceWork->trace.c.dist = -traceWork->polys[bestPlane].plane.Dist(); + traceWork->trace.c.contents = polygon->contents; + traceWork->trace.c.material = polygon->material; + traceWork->trace.c.point = vertex->p; + traceWork->trace.c.modelFeature = edge->vertexNum[j]; + traceWork->trace.c.trmFeature = 0; + CM_GetMaterialType( traceWork, polygon ); + return true; + } + } + } + } + + for ( int i = 0; i < polygon->numEdges; i++ ) { + const int edgeNum = polygon->edges[i]; + cm_edge_t *edge = &traceWork->model->edges[abs( edgeNum )]; + if ( edge->checkcount != checkCount ) { + edge->sideSet = 0; + } + traceWork->polygonEdgePlueckerCache[i].FromLine( + traceWork->model->vertices[edge->vertexNum[0]].p, + traceWork->model->vertices[edge->vertexNum[1]].p ); + cm_vertex_t *vertex = &traceWork->model->vertices[edge->vertexNum[INTSIGNBITSET( edgeNum )]]; + if ( vertex->checkcount != checkCount ) { + vertex->sideSet = 0; + } + vertex->checkcount = checkCount; + } + + int sides[MAX_TRACEMODEL_VERTS]; + for ( int i = 0; i < traceWork->numVerts; i++ ) { + sides[i] = polygon->plane.Distance( traceWork->vertices[i].p ) < 0.0f ? -1 : 1; + } + + for ( int i = 1; i <= traceWork->numEdges; i++ ) { + cm_trmEdge_t *trmEdge = &traceWork->edges[i]; + if ( sides[trmEdge->vertexNum[0]] == sides[trmEdge->vertexNum[1]] ) { + continue; + } + const int flip = INTSIGNBITSET( sides[trmEdge->vertexNum[0]] ); + int j; + for ( j = 0; j < polygon->numEdges; j++ ) { + const int edgeNum = polygon->edges[j]; + cm_edge_t *edge = &traceWork->model->edges[abs( edgeNum )]; + if ( !( edge->sideSet & ( 1u << i ) ) ) { + const float side = trmEdge->pl.PermutedInnerProduct( traceWork->polygonEdgePlueckerCache[j] ); + edge->side = ( edge->side & ~( 1u << i ) ) | ( FLOATSIGNBITSET( side ) << i ); + edge->sideSet |= 1u << i; + } + if ( INTSIGNBITSET( edgeNum ) ^ ( ( edge->side >> i ) & 1 ) ^ flip ) { + break; + } + } + if ( j >= polygon->numEdges ) { + traceWork->trace.fraction = 0.0f; + traceWork->trace.c.type = CONTACT_EDGE; + traceWork->trace.c.normal = polygon->plane.Normal(); + traceWork->trace.c.dist = polygon->plane.Dist(); + traceWork->trace.c.contents = polygon->contents; + traceWork->trace.c.material = polygon->material; + traceWork->trace.c.point = traceWork->vertices[trmEdge->vertexNum[!flip]].p; + traceWork->trace.c.modelFeature = reinterpret_cast( polygon ); + traceWork->trace.c.trmFeature = i; + CM_GetMaterialType( traceWork, polygon ); + return true; + } + } + + for ( int i = 0; i < polygon->numEdges; i++ ) { + const int edgeNum = polygon->edges[i]; + cm_edge_t *edge = &traceWork->model->edges[abs( edgeNum )]; + if ( edge->checkcount == checkCount ) { + continue; + } + edge->checkcount = checkCount; + for ( int j = 0; j < traceWork->numPolys; j++ ) { + cm_vertex_t *v1 = &traceWork->model->vertices[edge->vertexNum[0]]; + if ( !( v1->sideSet & ( 1u << j ) ) ) { + const float side = traceWork->polys[j].plane.Distance( v1->p ); + v1->side = side < 0.0f ? v1->side | ( 1u << j ) : v1->side & ~( 1u << j ); + v1->sideSet |= 1u << j; + } + cm_vertex_t *v2 = &traceWork->model->vertices[edge->vertexNum[1]]; + if ( !( v2->sideSet & ( 1u << j ) ) ) { + const float side = traceWork->polys[j].plane.Distance( v2->p ); + v2->side = side < 0.0f ? v2->side | ( 1u << j ) : v2->side & ~( 1u << j ); + v2->sideSet |= 1u << j; + } + if ( !( ( ( v1->side ^ v2->side ) >> j ) & 1 ) ) { + continue; + } + const int flip = ( v1->side >> j ) & 1; + int k; + for ( k = 0; k < traceWork->polys[j].numEdges; k++ ) { + const int trmEdgeNum = traceWork->polys[j].edges[k]; + cm_trmEdge_t *trmEdge = &traceWork->edges[abs( trmEdgeNum )]; + const int bitNum = abs( trmEdgeNum ); + if ( !( edge->sideSet & ( 1u << bitNum ) ) ) { + const float side = trmEdge->pl.PermutedInnerProduct( traceWork->polygonEdgePlueckerCache[i] ); + edge->side = ( edge->side & ~( 1u << bitNum ) ) | ( FLOATSIGNBITSET( side ) << bitNum ); + edge->sideSet |= 1u << bitNum; + } + if ( INTSIGNBITSET( trmEdgeNum ) ^ ( ( edge->side >> bitNum ) & 1 ) ^ flip ) { + break; + } + } + if ( k >= traceWork->polys[j].numEdges ) { + traceWork->trace.fraction = 0.0f; + traceWork->trace.c.type = CONTACT_EDGE; + traceWork->trace.c.normal = -traceWork->polys[j].plane.Normal(); + traceWork->trace.c.dist = -traceWork->polys[j].plane.Dist(); + traceWork->trace.c.contents = polygon->contents; + traceWork->trace.c.material = polygon->material; + traceWork->trace.c.point = traceWork->model->vertices[edge->vertexNum[!flip]].p; + traceWork->trace.c.modelFeature = edgeNum; + traceWork->trace.c.trmFeature = j; + CM_GetMaterialType( traceWork, polygon ); + return true; + } + } + } + return false; +} + +int idCollisionModelManagerLocal::ContentsTrm( trace_t *results, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *collisionModel, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idCollisionModelLocal *model = static_cast( collisionModel ); + + if ( trm == NULL || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f + && trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f + && trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) { + results->c.contents = TransformedPointContents( start, model, modelOrigin, modelAxis ); + results->fraction = results->c.contents == 0; + results->endpos = start; + results->endAxis = trmAxis; + return results->c.contents; + } + + checkCount++; + ALIGN16( cm_traceWork_t traceWork ); + traceWork.contents = contentMask; + traceWork.trace.fraction = 1.0f; + traceWork.trace.c.contents = 0; + traceWork.trace.c.type = CONTACT_NONE; + traceWork.trace.c.material = NULL; + traceWork.trace.c.materialType = NULL; + traceWork.isConvex = trm->isConvex; + traceWork.model = model; + traceWork.rotation = false; + traceWork.positionTest = true; + traceWork.pointTrace = false; + traceWork.quickExit = false; + traceWork.numContacts = 0; + traceWork.start = start - modelOrigin; + traceWork.end = traceWork.start; + + const bool modelRotated = modelAxis.IsRotated(); + idMat3 inverseModelAxis; + if ( modelRotated ) { + inverseModelAxis = modelAxis.Transpose(); + } + + SetupTrm( &traceWork, trm ); + const bool trmRotated = trmAxis.IsRotated(); + + if ( trmRotated ) { + for ( int i = 0; i < traceWork.numVerts; i++ ) { + traceWork.vertices[i].p *= trmAxis; + } + } + for ( int i = 0; i < traceWork.numVerts; i++ ) { + traceWork.vertices[i].p += traceWork.start; + } + if ( modelRotated ) { + for ( int i = 0; i < traceWork.numVerts; i++ ) { + traceWork.vertices[i].p *= inverseModelAxis; + } + } + + if ( trmRotated ) { + const idVec3 offset = trm->offset * trmAxis; + traceWork.start += offset; + traceWork.end += offset; + } else { + traceWork.start += trm->offset; + traceWork.end += trm->offset; + } + if ( modelRotated ) { + traceWork.start *= inverseModelAxis; + traceWork.end *= inverseModelAxis; + } + + traceWork.trmBounds.Clear(); + for ( int i = 0; i < traceWork.numVerts; i++ ) { + traceWork.trmBounds.AddPoint( traceWork.vertices[i].p - traceWork.start ); + } + + for ( int i = 1; i <= traceWork.numEdges; i++ ) { + traceWork.edges[i].pl.FromLine( + traceWork.vertices[traceWork.edges[i].vertexNum[0]].p, + traceWork.vertices[traceWork.edges[i].vertexNum[1]].p ); + } + + if ( trmRotated && modelRotated ) { + const idMat3 axis = trmAxis * inverseModelAxis; + for ( int i = 0; i < traceWork.numPolys; i++ ) { + traceWork.polys[i].plane *= axis; + } + } else if ( trmRotated ) { + for ( int i = 0; i < traceWork.numPolys; i++ ) { + traceWork.polys[i].plane *= trmAxis; + } + } else if ( modelRotated ) { + for ( int i = 0; i < traceWork.numPolys; i++ ) { + traceWork.polys[i].plane *= inverseModelAxis; + } + } + for ( int i = 0; i < traceWork.numPolys; i++ ) { + const cm_trmEdge_t &edge = traceWork.edges[abs( traceWork.polys[i].edges[0] )]; + traceWork.polys[i].plane.FitThroughPoint( traceWork.vertices[edge.vertexNum[0]].p ); + } + + for ( int i = 0; i < 3; i++ ) { + if ( traceWork.start[i] < traceWork.end[i] ) { + traceWork.traceBounds[0][i] = traceWork.start[i] + traceWork.trmBounds[0][i] - CM_BOX_EPSILON; + traceWork.traceBounds[1][i] = traceWork.end[i] + traceWork.trmBounds[1][i] + CM_BOX_EPSILON; + } else { + traceWork.traceBounds[0][i] = traceWork.end[i] + traceWork.trmBounds[0][i] - CM_BOX_EPSILON; + traceWork.traceBounds[1][i] = traceWork.start[i] + traceWork.trmBounds[1][i] + CM_BOX_EPSILON; + } + traceWork.trmExtents[i] = Max( idMath::Fabs( traceWork.trmBounds[0][i] ), + idMath::Fabs( traceWork.trmBounds[1][i] ) ) + CM_BOX_EPSILON; + } + + TraceThroughModel( &traceWork ); + *results = traceWork.trace; + results->fraction = results->c.contents == 0; + results->endpos = start; + results->endAxis = trmAxis; + return results->c.contents; +} + +int idCollisionModelManagerLocal::Contents( const idVec3 &start, const idTraceModel *trm, + const idMat3 &trmAxis, int contentMask, idCollisionModel *model, + const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + if ( model == NULL ) { + return 0; + } + trace_t results; + return ContentsTrm( &results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +#endif diff --git a/src/cm/collisionmodel_contents_legacy.inc b/src/cm/collisionmodel_contents_legacy.inc new file mode 100644 index 0000000..6a9dd7c --- /dev/null +++ b/src/cm/collisionmodel_contents_legacy.inc @@ -0,0 +1,637 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Contents test + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::TestTrmVertsInBrush + + returns true if any of the trm vertices is inside the brush +================ +*/ +bool idCollisionModelManagerLocal::TestTrmVertsInBrush( cm_traceWork_t *tw, cm_brush_t *b ) { + int i, j, numVerts, bestPlane; + float d, bestd; + idVec3 *p; + + if ( b->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + b->checkcount = idCollisionModelManagerLocal::checkCount; + + if ( !(b->contents & tw->contents) ) { + return false; + } + + // if the brush bounds don't intersect the trace bounds + if ( !b->bounds.IntersectsBounds( tw->bounds ) ) { + return false; + } + + if ( tw->pointTrace ) { + numVerts = 1; + } + else { + numVerts = tw->numVerts; + } + + for ( j = 0; j < numVerts; j++ ) { + p = &tw->vertices[j].p; + + // see if the point is inside the brush + bestPlane = 0; + bestd = -idMath::INFINITY; + for ( i = 0; i < b->numPlanes; i++ ) { + d = b->planes[i].Distance( *p ); + if ( d >= 0.0f ) { + break; + } + if ( d > bestd ) { + bestd = d; + bestPlane = i; + } + } + if ( i >= b->numPlanes ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.normal = b->planes[bestPlane].Normal(); + tw->trace.c.dist = b->planes[bestPlane].Dist(); + tw->trace.c.contents = b->contents; + tw->trace.c.material = b->material; + tw->trace.c.point = *p; + tw->trace.c.modelFeature = 0; + tw->trace.c.trmFeature = j; + return true; + } + } + return false; +} + +/* +================ +CM_SetTrmEdgeSidedness +================ +*/ +#define CM_SetTrmEdgeSidedness( edge, bpl, epl, bitNum ) { \ + if ( !(edge->sideSet & (1<side = (edge->side & ~(1<sideSet |= (1 << bitNum); \ + } \ +} + +/* +================ +CM_SetTrmPolygonSidedness +================ +*/ +#define CM_SetTrmPolygonSidedness( v, plane, bitNum ) { \ + if ( !((v)->sideSet & (1<p ); \ + /* cannot use float sign bit because it is undetermined when fl == 0.0f */ \ + if ( fl < 0.0f ) { \ + (v)->side |= (1 << bitNum); \ + } \ + else { \ + (v)->side &= ~(1 << bitNum); \ + } \ + (v)->sideSet |= (1 << bitNum); \ + } \ +} + +/* +================ +idCollisionModelManagerLocal::TestTrmInPolygon + + returns true if the trm intersects the polygon +================ +*/ +bool idCollisionModelManagerLocal::TestTrmInPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) { + int i, j, k, edgeNum, flip, trmEdgeNum, bitNum, bestPlane; + int sides[MAX_TRACEMODEL_VERTS]; + float d, bestd; + cm_trmEdge_t *trmEdge; + cm_edge_t *edge; + cm_vertex_t *v, *v1, *v2; + + // if already checked this polygon + if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + p->checkcount = idCollisionModelManagerLocal::checkCount; + + // if this polygon does not have the right contents behind it + if ( !(p->contents & tw->contents) ) { + return false; + } + + // if the polygon bounds don't intersect the trace bounds + if ( !p->bounds.IntersectsBounds( tw->bounds ) ) { + return false; + } + + // bounds should cross polygon plane + switch( tw->bounds.PlaneSide( p->plane ) ) { + case PLANESIDE_CROSS: + break; + case PLANESIDE_FRONT: + if ( tw->model->isConvex ) { + tw->quickExit = true; + return true; + } + default: + return false; + } + + // if the trace model is convex + if ( tw->isConvex ) { + // test if any polygon vertices are inside the trm + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // if this edge is already tested + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + + for ( j = 0; j < 2; j++ ) { + v = &tw->model->vertices[edge->vertexNum[j]]; + // if this vertex is already tested + if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + + bestPlane = 0; + bestd = -idMath::INFINITY; + for ( k = 0; k < tw->numPolys; k++ ) { + d = tw->polys[k].plane.Distance( v->p ); + if ( d >= 0.0f ) { + break; + } + if ( d > bestd ) { + bestd = d; + bestPlane = k; + } + } + if ( k >= tw->numPolys ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_MODELVERTEX; + tw->trace.c.normal = -tw->polys[bestPlane].plane.Normal(); + tw->trace.c.dist = -tw->polys[bestPlane].plane.Dist(); + tw->trace.c.contents = p->contents; + tw->trace.c.material = p->material; + tw->trace.c.point = v->p; + tw->trace.c.modelFeature = edge->vertexNum[j]; + tw->trace.c.trmFeature = 0; + return true; + } + } + } + } + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // reset sidedness cache if this is the first time we encounter this edge + if ( edge->checkcount != idCollisionModelManagerLocal::checkCount ) { + edge->sideSet = 0; + } + // pluecker coordinate for edge + tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[edge->vertexNum[0]].p, + tw->model->vertices[edge->vertexNum[1]].p ); + v = &tw->model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]]; + // reset sidedness cache if this is the first time we encounter this vertex + if ( v->checkcount != idCollisionModelManagerLocal::checkCount ) { + v->sideSet = 0; + } + v->checkcount = idCollisionModelManagerLocal::checkCount; + } + + // get side of polygon for each trm vertex + for ( i = 0; i < tw->numVerts; i++ ) { + d = p->plane.Distance( tw->vertices[i].p ); + sides[i] = d < 0.0f ? -1 : 1; + } + + // test if any trm edges go through the polygon + for ( i = 1; i <= tw->numEdges; i++ ) { + // if the trm edge does not cross the polygon plane + if ( sides[tw->edges[i].vertexNum[0]] == sides[tw->edges[i].vertexNum[1]] ) { + continue; + } + // check from which side to which side the trm edge goes + flip = INTSIGNBITSET( sides[tw->edges[i].vertexNum[0]] ); + // test if trm edge goes through the polygon between the polygon edges + for ( j = 0; j < p->numEdges; j++ ) { + edgeNum = p->edges[j]; + edge = tw->model->edges + abs(edgeNum); +#if 1 + CM_SetTrmEdgeSidedness( edge, tw->edges[i].pl, tw->polygonEdgePlueckerCache[j], i ); + if ( INTSIGNBITSET(edgeNum) ^ ((edge->side >> i) & 1) ^ flip ) { + break; + } +#else + d = tw->edges[i].pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[j] ); + if ( flip ) { + d = -d; + } + if ( edgeNum > 0 ) { + if ( d <= 0.0f ) { + break; + } + } + else { + if ( d >= 0.0f ) { + break; + } + } +#endif + } + if ( j >= p->numEdges ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.normal = p->plane.Normal(); + tw->trace.c.dist = p->plane.Dist(); + tw->trace.c.contents = p->contents; + tw->trace.c.material = p->material; + tw->trace.c.point = tw->vertices[tw->edges[i].vertexNum[ !flip ]].p; + tw->trace.c.modelFeature = *reinterpret_cast(&p); + tw->trace.c.trmFeature = i; + return true; + } + } + + // test if any polygon edges go through the trm polygons + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = tw->model->edges + abs(edgeNum); + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + edge->checkcount = idCollisionModelManagerLocal::checkCount; + + for ( j = 0; j < tw->numPolys; j++ ) { +#if 1 + v1 = tw->model->vertices + edge->vertexNum[0]; + CM_SetTrmPolygonSidedness( v1, tw->polys[j].plane, j ); + v2 = tw->model->vertices + edge->vertexNum[1]; + CM_SetTrmPolygonSidedness( v2, tw->polys[j].plane, j ); + // if the polygon edge does not cross the trm polygon plane + if ( !(((v1->side ^ v2->side) >> j) & 1) ) { + continue; + } + flip = (v1->side >> j) & 1; +#else + float d1, d2; + + v1 = tw->model->vertices + edge->vertexNum[0]; + d1 = tw->polys[j].plane.Distance( v1->p ); + v2 = tw->model->vertices + edge->vertexNum[1]; + d2 = tw->polys[j].plane.Distance( v2->p ); + // if the polygon edge does not cross the trm polygon plane + if ( (d1 >= 0.0f && d2 >= 0.0f) || (d1 <= 0.0f && d2 <= 0.0f) ) { + continue; + } + flip = false; + if ( d1 < 0.0f ) { + flip = true; + } +#endif + // test if polygon edge goes through the trm polygon between the trm polygon edges + for ( k = 0; k < tw->polys[j].numEdges; k++ ) { + trmEdgeNum = tw->polys[j].edges[k]; + trmEdge = tw->edges + abs(trmEdgeNum); +#if 1 + bitNum = abs(trmEdgeNum); + CM_SetTrmEdgeSidedness( edge, trmEdge->pl, tw->polygonEdgePlueckerCache[i], bitNum ); + if ( INTSIGNBITSET(trmEdgeNum) ^ ((edge->side >> bitNum) & 1) ^ flip ) { + break; + } +#else + d = trmEdge->pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ); + if ( flip ) { + d = -d; + } + if ( trmEdgeNum > 0 ) { + if ( d <= 0.0f ) { + break; + } + } + else { + if ( d >= 0.0f ) { + break; + } + } +#endif + } + if ( k >= tw->polys[j].numEdges ) { + tw->trace.fraction = 0.0f; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.normal = -tw->polys[j].plane.Normal(); + tw->trace.c.dist = -tw->polys[j].plane.Dist(); + tw->trace.c.contents = p->contents; + tw->trace.c.material = p->material; + tw->trace.c.point = tw->model->vertices[edge->vertexNum[ !flip ]].p; + tw->trace.c.modelFeature = edgeNum; + tw->trace.c.trmFeature = j; + return true; + } + } + } + return false; +} + +/* +================ +idCollisionModelManagerLocal::PointNode +================ +*/ +cm_node_t *idCollisionModelManagerLocal::PointNode( const idVec3 &p, cm_model_t *model ) { + cm_node_t *node; + + node = model->node; + while ( node->planeType != -1 ) { + if (p[node->planeType] > node->planeDist) { + node = node->children[0]; + } + else { + node = node->children[1]; + } + + assert( node != NULL ); + } + return node; +} + +/* +================ +idCollisionModelManagerLocal::PointContents +================ +*/ +int idCollisionModelManagerLocal::PointContents( const idVec3 p, cmHandle_t model ) { + int i; + float d; + cm_node_t *node; + cm_brushRef_t *bref; + cm_brush_t *b; + idPlane *plane; + + node = idCollisionModelManagerLocal::PointNode( p, idCollisionModelManagerLocal::models[model] ); + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + // test if the point is within the brush bounds + for ( i = 0; i < 3; i++ ) { + if ( p[i] < b->bounds[0][i] ) { + break; + } + if ( p[i] > b->bounds[1][i] ) { + break; + } + } + if ( i < 3 ) { + continue; + } + // test if the point is inside the brush + plane = b->planes; + for ( i = 0; i < b->numPlanes; i++, plane++ ) { + d = plane->Distance( p ); + if ( d >= 0 ) { + break; + } + } + if ( i >= b->numPlanes ) { + return b->contents; + } + } + return 0; +} + +/* +================== +idCollisionModelManagerLocal::TransformedPointContents +================== +*/ +int idCollisionModelManagerLocal::TransformedPointContents( const idVec3 &p, cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ) { + idVec3 p_l; + + // subtract origin offset + p_l = p - origin; + if ( modelAxis.IsRotated() ) { + p_l *= modelAxis; + } + return idCollisionModelManagerLocal::PointContents( p_l, model ); +} + + +/* +================== +idCollisionModelManagerLocal::ContentsTrm +================== +*/ +int idCollisionModelManagerLocal::ContentsTrm( trace_t *results, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + int i; + bool model_rotated, trm_rotated; + idMat3 invModelAxis, tmpAxis; + idVec3 dir; + ALIGN16( cm_traceWork_t tw ); + + // fast point case + if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f && + trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f && + trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) { + + results->c.contents = idCollisionModelManagerLocal::TransformedPointContents( start, model, modelOrigin, modelAxis ); + results->fraction = ( results->c.contents == 0 ); + results->endpos = start; + results->endAxis = trmAxis; + + return results->c.contents; + } + + idCollisionModelManagerLocal::checkCount++; + + tw.trace.fraction = 1.0f; + tw.trace.c.contents = 0; + tw.trace.c.type = CONTACT_NONE; + tw.contents = contentMask; + tw.isConvex = true; + tw.rotation = false; + tw.positionTest = true; + tw.pointTrace = false; + tw.quickExit = false; + tw.numContacts = 0; + tw.model = idCollisionModelManagerLocal::models[model]; + tw.start = start - modelOrigin; + tw.end = tw.start; + + model_rotated = modelAxis.IsRotated(); + if ( model_rotated ) { + invModelAxis = modelAxis.Transpose(); + } + + // setup trm structure + idCollisionModelManagerLocal::SetupTrm( &tw, trm ); + + trm_rotated = trmAxis.IsRotated(); + + // calculate vertex positions + if ( trm_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around the start position + tw.vertices[i].p *= trmAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // set trm at start position + tw.vertices[i].p += tw.start; + } + if ( model_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around model instead of rotating the model + tw.vertices[i].p *= invModelAxis; + } + } + + // add offset to start point + if ( trm_rotated ) { + dir = trm->offset * trmAxis; + tw.start += dir; + tw.end += dir; + } else { + tw.start += trm->offset; + tw.end += trm->offset; + } + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + tw.end *= invModelAxis; + } + + + // setup trm vertices + tw.size.Clear(); + for ( i = 0; i < tw.numVerts; i++ ) { + // get axial trm size after rotations + tw.size.AddPoint( tw.vertices[i].p - tw.start ); + } + + // setup trm edges + for ( i = 1; i <= tw.numEdges; i++ ) { + // edge start, end and pluecker coordinate + tw.edges[i].start = tw.vertices[tw.edges[i].vertexNum[0]].p; + tw.edges[i].end = tw.vertices[tw.edges[i].vertexNum[1]].p; + tw.edges[i].pl.FromLine( tw.edges[i].start, tw.edges[i].end ); + } + + // setup trm polygons + if ( trm_rotated & model_rotated ) { + tmpAxis = trmAxis * invModelAxis; + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane *= tmpAxis; + } + } else if ( trm_rotated ) { + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane *= trmAxis; + } + } else if ( model_rotated ) { + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane *= invModelAxis; + } + } + for ( i = 0; i < tw.numPolys; i++ ) { + tw.polys[i].plane.FitThroughPoint( tw.edges[abs(tw.polys[i].edges[0])].start ); + } + + // bounds for full trace, a little bit larger for epsilons + for ( i = 0; i < 3; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i] + CM_BOX_EPSILON; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i] + CM_BOX_EPSILON; + } + if ( idMath::Fabs(tw.size[0][i]) > idMath::Fabs(tw.size[1][i]) ) { + tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + CM_BOX_EPSILON; + } else { + tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + CM_BOX_EPSILON; + } + } + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + *results = tw.trace; + results->fraction = ( results->c.contents == 0 ); + results->endpos = start; + results->endAxis = trmAxis; + + return results->c.contents; +} + +/* +================== +idCollisionModelManagerLocal::Contents +================== +*/ +int idCollisionModelManagerLocal::Contents( const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + trace_t results; + + if ( model < 0 || model > idCollisionModelManagerLocal::maxModels || model > MAX_SUBMODELS ) { + common->Printf("idCollisionModelManagerLocal::Contents: invalid model handle\n"); + return 0; + } + if ( !idCollisionModelManagerLocal::models || !idCollisionModelManagerLocal::models[model] ) { + common->Printf("idCollisionModelManagerLocal::Contents: invalid model\n"); + return 0; + } + + return ContentsTrm( &results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} diff --git a/src/cm/collisionmodel_debug.cpp b/src/cm/collisionmodel_debug.cpp new file mode 100644 index 0000000..daf0b49 --- /dev/null +++ b/src/cm/collisionmodel_debug.cpp @@ -0,0 +1,536 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + + +/* +=============================================================================== + +Visualisation code + +=============================================================================== +*/ + +const char *cm_contentsNameByIndex[] = { + "none", // 0 + "solid", // 1 + "opaque", // 2 + "water", // 3 + "playerclip", // 4 + "monsterclip", // 5 + "moveableclip", // 6 + "ikclip", // 7 + "blood", // 8 + "body", // 9 + "corpse", // 10 + "trigger", // 11 + "aas_solid", // 12 + "aas_obstacle", // 13 + "flashlight_trigger", // 14 + NULL +}; + +int cm_contentsFlagByIndex[] = { + -1, // 0 + CONTENTS_SOLID, // 1 + CONTENTS_OPAQUE, // 2 + CONTENTS_WATER, // 3 + CONTENTS_PLAYERCLIP, // 4 + CONTENTS_MONSTERCLIP, // 5 + CONTENTS_MOVEABLECLIP, // 6 + CONTENTS_IKCLIP, // 7 + CONTENTS_BLOOD, // 8 + CONTENTS_BODY, // 9 + CONTENTS_CORPSE, // 10 + CONTENTS_TRIGGER, // 11 + CONTENTS_AAS_SOLID, // 12 + CONTENTS_AAS_OBSTACLE, // 13 + CONTENTS_FLASHLIGHT_TRIGGER, // 14 + 0 +}; + +idCVar cm_drawMask( "cm_drawMask", "none", CVAR_GAME, "collision mask", cm_contentsNameByIndex, idCmdSystem::ArgCompletion_String ); +idCVar cm_drawColor( "cm_drawColor", "1 0 0 .5", CVAR_GAME, "color used to draw the collision models" ); +idCVar cm_drawFilled( "cm_drawFilled", "0", CVAR_GAME | CVAR_BOOL, "draw filled polygons" ); +idCVar cm_drawInternal( "cm_drawInternal", "1", CVAR_GAME | CVAR_BOOL, "draw internal edges green" ); +idCVar cm_drawNormals( "cm_drawNormals", "0", CVAR_GAME | CVAR_BOOL, "draw polygon and edge normals" ); +idCVar cm_backFaceCull( "cm_backFaceCull", "0", CVAR_GAME | CVAR_BOOL, "cull back facing polygons" ); +idCVar cm_debugCollision( "cm_debugCollision", "0", CVAR_GAME | CVAR_BOOL, "debug the collision detection" ); + +static idVec4 cm_color; + +#if !defined( Q4_CM_LEGACY_SEED ) + +int idCollisionModelManagerLocal::ContentsFromString( const char *string ) const { + int contents = 0; + idLexer lexer( string != NULL ? string : "", string != NULL ? idStr::Length( string ) : 0, "ContentsFromString" ); + idToken token; + while ( lexer.ReadToken( &token ) ) { + if ( token == "," ) continue; + for ( int i = 1; cm_contentsNameByIndex[i] != NULL; ++i ) if ( !token.Icmp( cm_contentsNameByIndex[i] ) ) { contents |= cm_contentsFlagByIndex[i]; break; } + } + return contents; +} + +const char *idCollisionModelManagerLocal::StringFromContents( int contents ) const { + static char contentsString[MAX_STRING_CHARS]; + int length = 0; + contentsString[0] = '\0'; + for ( int i = 1; cm_contentsFlagByIndex[i] != 0; ++i ) if ( contents & cm_contentsFlagByIndex[i] ) { + if ( length > 0 ) length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, "," ); + length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, "%s", cm_contentsNameByIndex[i] ); + } + return contentsString; +} + +void idCollisionModelManagerLocal::DrawEdge( idCollisionModelLocal *model, int edgeNum, + const idVec3 &modelOrigin, const idMat3 &modelAxis, const idVec3 &viewOrigin, + const idMat3 &viewAxis, float radius ) {} +void idCollisionModelManagerLocal::DrawPolygon( idCollisionModelLocal *model, cm_polygon_t *polygon, + const idVec3 &modelOrigin, const idMat3 &modelAxis, const idVec3 &viewOrigin, + const idMat3 &viewAxis, float radius ) {} +void idCollisionModelManagerLocal::DrawNodePolygons( idCollisionModelLocal *model, cm_node_t *node, + const idVec3 &modelOrigin, const idMat3 &modelAxis, const idVec3 &viewOrigin, + const idMat3 &viewAxis, float radius ) {} +void idCollisionModelManagerLocal::DebugTranslationFailure( const idVec3 &viewOrigin, const idMat3 &viewAxis ) {} +void idCollisionModelManagerLocal::DebugRotationFailure( const idVec3 &viewOrigin, const idMat3 &viewAxis ) {} +void idCollisionModelManagerLocal::SpeedTest( const idVec3 &viewOrigin ) {} + +void idCollisionModelManagerLocal::DrawModel( idCollisionModel *model, const idVec3 &modelOrigin, + const idMat3 &modelAxis, const idVec3 &viewOrigin, const idMat3 &viewAxis, float radius ) { + if ( model == NULL ) return; + idCollisionModelLocal *localModel = static_cast( model ); + DrawNodePolygons( localModel, localModel->node, modelOrigin, modelAxis, viewOrigin, viewAxis, radius ); +} + +void idCollisionModelManagerLocal::DebugOutput( const idVec3 &viewOrigin, const idMat3 &viewAxis ) { + if ( cm_drawMask.IsModified() ) cm_drawMask.ClearModified(); +} + +#else + +/* +================ +idCollisionModelManagerLocal::ContentsFromString +================ +*/ +int idCollisionModelManagerLocal::ContentsFromString( const char *string ) const { + int i, contents = 0; + idLexer src( string, idStr::Length( string ), "ContentsFromString" ); + idToken token; + + while( src.ReadToken( &token ) ) { + if ( token == "," ) { + continue; + } + for ( i = 1; cm_contentsNameByIndex[i] != NULL; i++ ) { + if ( token.Icmp( cm_contentsNameByIndex[i] ) == 0 ) { + contents |= cm_contentsFlagByIndex[i]; + break; + } + } + } + + return contents; +} + +/* +================ +idCollisionModelManagerLocal::StringFromContents +================ +*/ +const char *idCollisionModelManagerLocal::StringFromContents( const int contents ) const { + int i, length = 0; + static char contentsString[MAX_STRING_CHARS]; + + contentsString[0] = '\0'; + + for ( i = 1; cm_contentsFlagByIndex[i] != 0; i++ ) { + if ( contents & cm_contentsFlagByIndex[i] ) { + if ( length != 0 ) { + length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, "," ); + } + length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, cm_contentsNameByIndex[i] ); + } + } + + return contentsString; +} + +/* +================ +idCollisionModelManagerLocal::DrawEdge +================ +*/ +void idCollisionModelManagerLocal::DrawEdge( cm_model_t *model, int edgeNum, const idVec3 &origin, const idMat3 &axis ) { + int side; + cm_edge_t *edge; + idVec3 start, end, mid; + bool isRotated; + + isRotated = axis.IsRotated(); + + edge = model->edges + abs(edgeNum); + side = edgeNum < 0; + + start = model->vertices[edge->vertexNum[side]].p; + end = model->vertices[edge->vertexNum[!side]].p; + if ( isRotated ) { + start *= axis; + end *= axis; + } + start += origin; + end += origin; + + if ( edge->internal ) { + if ( cm_drawInternal.GetBool() ) { + session->rw->DebugArrow( colorGreen, start, end, 1 ); + } + } else { + if ( edge->numUsers > 2 ) { + session->rw->DebugArrow( colorBlue, start, end, 1 ); + } else { + session->rw->DebugArrow( cm_color, start, end, 1 ); + } + } + + if ( cm_drawNormals.GetBool() ) { + mid = (start + end) * 0.5f; + if ( isRotated ) { + end = mid + 5 * (axis * edge->normal); + } else { + end = mid + 5 * edge->normal; + } + session->rw->DebugArrow( colorCyan, mid, end, 1 ); + } +} + +/* +================ +idCollisionModelManagerLocal::DrawPolygon +================ +*/ +void idCollisionModelManagerLocal::DrawPolygon( cm_model_t *model, cm_polygon_t *p, const idVec3 &origin, const idMat3 &axis, const idVec3 &viewOrigin ) { + int i, edgeNum; + cm_edge_t *edge; + idVec3 center, end, dir; + + if ( cm_backFaceCull.GetBool() ) { + edgeNum = p->edges[0]; + edge = model->edges + abs(edgeNum); + dir = model->vertices[edge->vertexNum[0]].p - viewOrigin; + if ( dir * p->plane.Normal() > 0.0f ) { + return; + } + } + + if ( cm_drawNormals.GetBool() ) { + center = vec3_origin; + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + center += model->vertices[edge->vertexNum[edgeNum < 0]].p; + } + center *= (1.0f / p->numEdges); + if ( axis.IsRotated() ) { + center = center * axis + origin; + end = center + 5 * (axis * p->plane.Normal()); + } else { + center += origin; + end = center + 5 * p->plane.Normal(); + } + session->rw->DebugArrow( colorMagenta, center, end, 1 ); + } + + if ( cm_drawFilled.GetBool() ) { + idFixedWinding winding; + for ( i = p->numEdges - 1; i >= 0; i-- ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + winding += origin + model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p * axis; + } + session->rw->DebugPolygon( cm_color, winding ); + } else { + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + if ( edge->checkcount == checkCount ) { + continue; + } + edge->checkcount = checkCount; + DrawEdge( model, edgeNum, origin, axis ); + } + } +} + +/* +================ +idCollisionModelManagerLocal::DrawNodePolygons +================ +*/ +void idCollisionModelManagerLocal::DrawNodePolygons( cm_model_t *model, cm_node_t *node, + const idVec3 &origin, const idMat3 &axis, + const idVec3 &viewOrigin, const float radius ) { + int i; + cm_polygon_t *p; + cm_polygonRef_t *pref; + + while (1) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + if ( radius ) { + // polygon bounds should overlap with trace bounds + for ( i = 0; i < 3; i++ ) { + if ( p->bounds[0][i] > viewOrigin[i] + radius ) { + break; + } + if ( p->bounds[1][i] < viewOrigin[i] - radius ) { + break; + } + } + if ( i < 3 ) { + continue; + } + } + if ( p->checkcount == checkCount ) { + continue; + } + if ( !( p->contents & cm_contentsFlagByIndex[cm_drawMask.GetInteger()] ) ) { + continue; + } + + DrawPolygon( model, p, origin, axis, viewOrigin ); + p->checkcount = checkCount; + } + if ( node->planeType == -1 ) { + break; + } + if ( radius && viewOrigin[node->planeType] > node->planeDist + radius ) { + node = node->children[0]; + } else if ( radius && viewOrigin[node->planeType] < node->planeDist - radius ) { + node = node->children[1]; + } else { + DrawNodePolygons( model, node->children[1], origin, axis, viewOrigin, radius ); + node = node->children[0]; + } + } +} + +/* +================ +idCollisionModelManagerLocal::DrawModel +================ +*/ +void idCollisionModelManagerLocal::DrawModel( cmHandle_t handle, const idVec3 &modelOrigin, const idMat3 &modelAxis, + const idVec3 &viewOrigin, const float radius ) { + + cm_model_t *model; + idVec3 viewPos; + + if ( handle < 0 && handle >= numModels ) { + return; + } + + if ( cm_drawColor.IsModified() ) { + sscanf( cm_drawColor.GetString(), "%f %f %f %f", &cm_color.x, &cm_color.y, &cm_color.z, &cm_color.w ); + cm_drawColor.ClearModified(); + } + + model = models[ handle ]; + viewPos = (viewOrigin - modelOrigin) * modelAxis.Transpose(); + checkCount++; + DrawNodePolygons( model, model->node, modelOrigin, modelAxis, viewPos, radius ); +} + +/* +=============================================================================== + +Speed test code + +=============================================================================== +*/ + +static idCVar cm_testCollision( "cm_testCollision", "0", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testRotation( "cm_testRotation", "1", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testModel( "cm_testModel", "0", CVAR_GAME | CVAR_INTEGER, "" ); +static idCVar cm_testTimes( "cm_testTimes", "1000", CVAR_GAME | CVAR_INTEGER, "" ); +static idCVar cm_testRandomMany( "cm_testRandomMany", "0", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testOrigin( "cm_testOrigin", "0 0 0", CVAR_GAME, "" ); +static idCVar cm_testReset( "cm_testReset", "0", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testBox( "cm_testBox", "-16 -16 0 16 16 64", CVAR_GAME, "" ); +static idCVar cm_testBoxRotation( "cm_testBoxRotation", "0 0 0", CVAR_GAME, "" ); +static idCVar cm_testWalk( "cm_testWalk", "1", CVAR_GAME | CVAR_BOOL, "" ); +static idCVar cm_testLength( "cm_testLength", "1024", CVAR_GAME | CVAR_FLOAT, "" ); +static idCVar cm_testRadius( "cm_testRadius", "64", CVAR_GAME | CVAR_FLOAT, "" ); +static idCVar cm_testAngle( "cm_testAngle", "60", CVAR_GAME | CVAR_FLOAT, "" ); + +static int total_translation; +static int min_translation = 999999; +static int max_translation = -999999; +static int num_translation = 0; +static int total_rotation; +static int min_rotation = 999999; +static int max_rotation = -999999; +static int num_rotation = 0; +static idVec3 start; +static idVec3 *testend; + +#include "../sys/sys_public.h" + +void idCollisionModelManagerLocal::DebugOutput( const idVec3 &origin ) { + int i, k, t; + char buf[128]; + idVec3 end; + idAngles boxAngles; + idMat3 modelAxis, boxAxis; + idBounds bounds; + trace_t trace; + + if ( !cm_testCollision.GetBool() ) { + return; + } + + testend = (idVec3 *) Mem_Alloc( cm_testTimes.GetInteger() * sizeof(idVec3) ); + + if ( cm_testReset.GetBool() || ( cm_testWalk.GetBool() && !start.Compare( start ) ) ) { + total_translation = total_rotation = 0; + min_translation = min_rotation = 999999; + max_translation = max_rotation = -999999; + num_translation = num_rotation = 0; + cm_testReset.SetBool( false ); + } + + if ( cm_testWalk.GetBool() ) { + start = origin; + cm_testOrigin.SetString( va( "%1.2f %1.2f %1.2f", start[0], start[1], start[2] ) ); + } else { + sscanf( cm_testOrigin.GetString(), "%f %f %f", &start[0], &start[1], &start[2] ); + } + + sscanf( cm_testBox.GetString(), "%f %f %f %f %f %f", &bounds[0][0], &bounds[0][1], &bounds[0][2], + &bounds[1][0], &bounds[1][1], &bounds[1][2] ); + sscanf( cm_testBoxRotation.GetString(), "%f %f %f", &boxAngles[0], &boxAngles[1], &boxAngles[2] ); + boxAxis = boxAngles.ToMat3(); + modelAxis.Identity(); + + idTraceModel itm( bounds ); + idRandom random( 0 ); + idTimer timer; + + if ( cm_testRandomMany.GetBool() ) { + // if many traces in one random direction + for ( i = 0; i < 3; i++ ) { + testend[0][i] = start[i] + random.CRandomFloat() * cm_testLength.GetFloat(); + } + for ( k = 1; k < cm_testTimes.GetInteger(); k++ ) { + testend[k] = testend[0]; + } + } else { + // many traces each in a different random direction + for ( k = 0; k < cm_testTimes.GetInteger(); k++ ) { + for ( i = 0; i < 3; i++ ) { + testend[k][i] = start[i] + random.CRandomFloat() * cm_testLength.GetFloat(); + } + } + } + + // translational collision detection + timer.Clear(); + timer.Start(); + for ( i = 0; i < cm_testTimes.GetInteger(); i++ ) { + Translation( &trace, start, testend[i], &itm, boxAxis, CONTENTS_SOLID|CONTENTS_PLAYERCLIP, cm_testModel.GetInteger(), vec3_origin, modelAxis ); + } + timer.Stop(); + t = timer.Milliseconds(); + if ( t < min_translation ) min_translation = t; + if ( t > max_translation ) max_translation = t; + num_translation++; + total_translation += t; + if ( cm_testTimes.GetInteger() > 9999 ) { + sprintf( buf, "%3dK", (int ) ( cm_testTimes.GetInteger() / 1000 ) ); + } else { + sprintf( buf, "%4d", cm_testTimes.GetInteger() ); + } + common->Printf("%s translations: %4d milliseconds, (min = %d, max = %d, av = %1.1f)\n", buf, t, min_translation, max_translation, (float) total_translation / num_translation ); + + if ( cm_testRandomMany.GetBool() ) { + // if many traces in one random direction + for ( i = 0; i < 3; i++ ) { + testend[0][i] = start[i] + random.CRandomFloat() * cm_testRadius.GetFloat(); + } + for ( k = 1; k < cm_testTimes.GetInteger(); k++ ) { + testend[k] = testend[0]; + } + } else { + // many traces each in a different random direction + for ( k = 0; k < cm_testTimes.GetInteger(); k++ ) { + for ( i = 0; i < 3; i++ ) { + testend[k][i] = start[i] + random.CRandomFloat() * cm_testRadius.GetFloat(); + } + } + } + + if ( cm_testRotation.GetBool() ) { + // rotational collision detection + idVec3 vec( random.CRandomFloat(), random.CRandomFloat(), random.RandomFloat() ); + vec.Normalize(); + idRotation rotation( vec3_origin, vec, cm_testAngle.GetFloat() ); + + timer.Clear(); + timer.Start(); + for ( i = 0; i < cm_testTimes.GetInteger(); i++ ) { + rotation.SetOrigin( testend[i] ); + Rotation( &trace, start, rotation, &itm, boxAxis, CONTENTS_SOLID|CONTENTS_PLAYERCLIP, cm_testModel.GetInteger(), vec3_origin, modelAxis ); + } + timer.Stop(); + t = timer.Milliseconds(); + if ( t < min_rotation ) min_rotation = t; + if ( t > max_rotation ) max_rotation = t; + num_rotation++; + total_rotation += t; + if ( cm_testTimes.GetInteger() > 9999 ) { + sprintf( buf, "%3dK", (int ) ( cm_testTimes.GetInteger() / 1000 ) ); + } else { + sprintf( buf, "%4d", cm_testTimes.GetInteger() ); + } + common->Printf("%s rotation: %4d milliseconds, (min = %d, max = %d, av = %1.1f)\n", buf, t, min_rotation, max_rotation, (float) total_rotation / num_rotation ); + } + + Mem_Free( testend ); + testend = NULL; +} + +#endif // !Q4_CM_LEGACY_SEED diff --git a/src/cm/collisionmodel_files.cpp b/src/cm/collisionmodel_files.cpp new file mode 100644 index 0000000..b4e0d7b --- /dev/null +++ b/src/cm/collisionmodel_files.cpp @@ -0,0 +1,646 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +#if !defined( Q4_CM_LEGACY_SEED ) + +void idCollisionModelManagerLocal::WriteNodes( idFile *file, cm_node_t *node ) {} +cm_node_t *idCollisionModelManagerLocal::ParseNodes( Lexer *lexer, idCollisionModelLocal *model, cm_node_t *parent ) { return NULL; } +void idCollisionModelManagerLocal::WritePolygons( idFile *file, cm_node_t *node ) {} +void idCollisionModelManagerLocal::WriteBrushes( idFile *file, cm_node_t *node ) {} +void idCollisionModelManagerLocal::ParseVertices( Lexer *lexer, idCollisionModelLocal *model ) {} +void idCollisionModelManagerLocal::ParseEdges( Lexer *lexer, idCollisionModelLocal *model ) {} +void idCollisionModelManagerLocal::ParsePolygons( Lexer *lexer, idCollisionModelLocal *model ) {} +void idCollisionModelManagerLocal::ParseBrushes( Lexer *lexer, idCollisionModelLocal *model ) {} +void idCollisionModelManagerLocal::WriteCollisionModel( idFile *file, idCollisionModelLocal *model ) {} + +void idCollisionModelManagerLocal::WriteCollisionModelsToFile( const char *filename, unsigned int mapFileCRC ) { + // Retail ownership and signature are restored here. Serialization remains + // disabled until the Q4 array-based model format is fully validated. +} + +bool idCollisionModelManagerLocal::WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, + const char *filename, bool testTraceModel ) { + return false; +} + +bool idCollisionModelManagerLocal::ParseCollisionModel( Lexer *lexer, const char *filename, unsigned int mapFileCRC ) { + return false; +} + +bool idCollisionModelManagerLocal::LoadCollisionModelFile( const char *filename, unsigned int mapFileCRC ) { + return false; +} + +#else + +#define CM_FILE_EXT "cm" +#define CM_FILEID "CM" +#define CM_FILEVERSION "1.00" + + +/* +=============================================================================== + +Writing of collision model file + +=============================================================================== +*/ + +void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node ); +int CM_GetNodeContents( cm_node_t *node ); + + +/* +================ +idCollisionModelManagerLocal::WriteNodes +================ +*/ +void idCollisionModelManagerLocal::WriteNodes( idFile *fp, cm_node_t *node ) { + fp->WriteFloatString( "\t( %d %f )\n", node->planeType, node->planeDist ); + if ( node->planeType != -1 ) { + WriteNodes( fp, node->children[0] ); + WriteNodes( fp, node->children[1] ); + } +} + +/* +================ +idCollisionModelManagerLocal::CountPolygonMemory +================ +*/ +int idCollisionModelManagerLocal::CountPolygonMemory( cm_node_t *node ) const { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int memory; + + memory = 0; + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + + memory += sizeof( cm_polygon_t ) + ( p->numEdges - 1 ) * sizeof( p->edges[0] ); + } + if ( node->planeType != -1 ) { + memory += CountPolygonMemory( node->children[0] ); + memory += CountPolygonMemory( node->children[1] ); + } + return memory; +} + +/* +================ +idCollisionModelManagerLocal::WritePolygons +================ +*/ +void idCollisionModelManagerLocal::WritePolygons( idFile *fp, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int i; + + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + fp->WriteFloatString( "\t%d (", p->numEdges ); + for ( i = 0; i < p->numEdges; i++ ) { + fp->WriteFloatString( " %d", p->edges[i] ); + } + fp->WriteFloatString( " ) ( %f %f %f ) %f", p->plane.Normal()[0], p->plane.Normal()[1], p->plane.Normal()[2], p->plane.Dist() ); + fp->WriteFloatString( " ( %f %f %f )", p->bounds[0][0], p->bounds[0][1], p->bounds[0][2] ); + fp->WriteFloatString( " ( %f %f %f )", p->bounds[1][0], p->bounds[1][1], p->bounds[1][2] ); + fp->WriteFloatString( " \"%s\"\n", p->material->GetName() ); + } + if ( node->planeType != -1 ) { + WritePolygons( fp, node->children[0] ); + WritePolygons( fp, node->children[1] ); + } +} + +/* +================ +idCollisionModelManagerLocal::CountBrushMemory +================ +*/ +int idCollisionModelManagerLocal::CountBrushMemory( cm_node_t *node ) const { + cm_brushRef_t *bref; + cm_brush_t *b; + int memory; + + memory = 0; + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + if ( b->checkcount == checkCount ) { + continue; + } + b->checkcount = checkCount; + + memory += sizeof( cm_brush_t ) + ( b->numPlanes - 1 ) * sizeof( b->planes[0] ); + } + if ( node->planeType != -1 ) { + memory += CountBrushMemory( node->children[0] ); + memory += CountBrushMemory( node->children[1] ); + } + return memory; +} + +/* +================ +idCollisionModelManagerLocal::WriteBrushes +================ +*/ +void idCollisionModelManagerLocal::WriteBrushes( idFile *fp, cm_node_t *node ) { + cm_brushRef_t *bref; + cm_brush_t *b; + int i; + + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + if ( b->checkcount == checkCount ) { + continue; + } + b->checkcount = checkCount; + fp->WriteFloatString( "\t%d {\n", b->numPlanes ); + for ( i = 0; i < b->numPlanes; i++ ) { + fp->WriteFloatString( "\t\t( %f %f %f ) %f\n", b->planes[i].Normal()[0], b->planes[i].Normal()[1], b->planes[i].Normal()[2], b->planes[i].Dist() ); + } + fp->WriteFloatString( "\t} ( %f %f %f )", b->bounds[0][0], b->bounds[0][1], b->bounds[0][2] ); + fp->WriteFloatString( " ( %f %f %f ) \"%s\"\n", b->bounds[1][0], b->bounds[1][1], b->bounds[1][2], StringFromContents( b->contents ) ); + } + if ( node->planeType != -1 ) { + WriteBrushes( fp, node->children[0] ); + WriteBrushes( fp, node->children[1] ); + } +} + +/* +================ +idCollisionModelManagerLocal::WriteCollisionModel +================ +*/ +void idCollisionModelManagerLocal::WriteCollisionModel( idFile *fp, cm_model_t *model ) { + int i, polygonMemory, brushMemory; + + fp->WriteFloatString( "collisionModel \"%s\" {\n", model->name.c_str() ); + // vertices + fp->WriteFloatString( "\tvertices { /* numVertices = */ %d\n", model->numVertices ); + for ( i = 0; i < model->numVertices; i++ ) { + fp->WriteFloatString( "\t/* %d */ ( %f %f %f )\n", i, model->vertices[i].p[0], model->vertices[i].p[1], model->vertices[i].p[2] ); + } + fp->WriteFloatString( "\t}\n" ); + // edges + fp->WriteFloatString( "\tedges { /* numEdges = */ %d\n", model->numEdges ); + for ( i = 0; i < model->numEdges; i++ ) { + fp->WriteFloatString( "\t/* %d */ ( %d %d ) %d %d\n", i, model->edges[i].vertexNum[0], model->edges[i].vertexNum[1], model->edges[i].internal, model->edges[i].numUsers ); + } + fp->WriteFloatString( "\t}\n" ); + // nodes + fp->WriteFloatString( "\tnodes {\n" ); + WriteNodes( fp, model->node ); + fp->WriteFloatString( "\t}\n" ); + // polygons + checkCount++; + polygonMemory = CountPolygonMemory( model->node ); + fp->WriteFloatString( "\tpolygons /* polygonMemory = */ %d {\n", polygonMemory ); + checkCount++; + WritePolygons( fp, model->node ); + fp->WriteFloatString( "\t}\n" ); + // brushes + checkCount++; + brushMemory = CountBrushMemory( model->node ); + fp->WriteFloatString( "\tbrushes /* brushMemory = */ %d {\n", brushMemory ); + checkCount++; + WriteBrushes( fp, model->node ); + fp->WriteFloatString( "\t}\n" ); + // closing brace + fp->WriteFloatString( "}\n" ); +} + +/* +================ +idCollisionModelManagerLocal::WriteCollisionModelsToFile +================ +*/ +void idCollisionModelManagerLocal::WriteCollisionModelsToFile( const char *filename, int firstModel, int lastModel, unsigned int mapFileCRC ) { + int i; + idFile *fp; + idStr name; + + name = filename; + name.SetFileExtension( CM_FILE_EXT ); + + common->Printf( "writing %s\n", name.c_str() ); + // _D3XP was saving to fs_cdpath + fp = fileSystem->OpenFileWrite( name, "fs_devpath" ); + if ( !fp ) { + common->Warning( "idCollisionModelManagerLocal::WriteCollisionModelsToFile: Error opening file %s\n", name.c_str() ); + return; + } + + // write file id and version + fp->WriteFloatString( "%s \"%s\"\n\n", CM_FILEID, CM_FILEVERSION ); + // write the map file crc + fp->WriteFloatString( "%u\n\n", mapFileCRC ); + + // write the collision models + for ( i = firstModel; i < lastModel; i++ ) { + WriteCollisionModel( fp, models[ i ] ); + } + + fileSystem->CloseFile( fp ); +} + +/* +================ +idCollisionModelManagerLocal::WriteCollisionModelForMapEntity +================ +*/ +bool idCollisionModelManagerLocal::WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel ) { + idFile *fp; + idStr name; + cm_model_t *model; + + SetupHash(); + model = CollisionModelForMapEntity( mapEnt ); + model->name = filename; + + name = filename; + name.SetFileExtension( CM_FILE_EXT ); + + common->Printf( "writing %s\n", name.c_str() ); + fp = fileSystem->OpenFileWrite( name, "fs_devpath" ); + if ( !fp ) { + common->Printf( "idCollisionModelManagerLocal::WriteCollisionModelForMapEntity: Error opening file %s\n", name.c_str() ); + FreeModel( model ); + return false; + } + + // write file id and version + fp->WriteFloatString( "%s \"%s\"\n\n", CM_FILEID, CM_FILEVERSION ); + // write the map file crc + fp->WriteFloatString( "%u\n\n", 0 ); + + // write the collision model + WriteCollisionModel( fp, model ); + + fileSystem->CloseFile( fp ); + + if ( testTraceModel ) { + idTraceModel trm; + TrmFromModel( model, trm ); + } + + FreeModel( model ); + + return true; +} + + +/* +=============================================================================== + +Loading of collision model file + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::ParseVertices +================ +*/ +void idCollisionModelManagerLocal::ParseVertices( idLexer *src, cm_model_t *model ) { + int i; + + src->ExpectTokenString( "{" ); + model->numVertices = src->ParseInt(); + model->maxVertices = model->numVertices; + model->vertices = (cm_vertex_t *) Mem_Alloc( model->maxVertices * sizeof( cm_vertex_t ) ); + for ( i = 0; i < model->numVertices; i++ ) { + src->Parse1DMatrix( 3, model->vertices[i].p.ToFloatPtr() ); + model->vertices[i].side = 0; + model->vertices[i].sideSet = 0; + model->vertices[i].checkcount = 0; + } + src->ExpectTokenString( "}" ); +} + +/* +================ +idCollisionModelManagerLocal::ParseEdges +================ +*/ +void idCollisionModelManagerLocal::ParseEdges( idLexer *src, cm_model_t *model ) { + int i; + + src->ExpectTokenString( "{" ); + model->numEdges = src->ParseInt(); + model->maxEdges = model->numEdges; + model->edges = (cm_edge_t *) Mem_Alloc( model->maxEdges * sizeof( cm_edge_t ) ); + for ( i = 0; i < model->numEdges; i++ ) { + src->ExpectTokenString( "(" ); + model->edges[i].vertexNum[0] = src->ParseInt(); + model->edges[i].vertexNum[1] = src->ParseInt(); + src->ExpectTokenString( ")" ); + model->edges[i].side = 0; + model->edges[i].sideSet = 0; + model->edges[i].internal = src->ParseInt(); + model->edges[i].numUsers = src->ParseInt(); + model->edges[i].normal = vec3_origin; + model->edges[i].checkcount = 0; + model->numInternalEdges += model->edges[i].internal; + } + src->ExpectTokenString( "}" ); +} + +/* +================ +idCollisionModelManagerLocal::ParseNodes +================ +*/ +cm_node_t *idCollisionModelManagerLocal::ParseNodes( idLexer *src, cm_model_t *model, cm_node_t *parent ) { + cm_node_t *node; + + model->numNodes++; + node = AllocNode( model, model->numNodes < NODE_BLOCK_SIZE_SMALL ? NODE_BLOCK_SIZE_SMALL : NODE_BLOCK_SIZE_LARGE ); + node->brushes = NULL; + node->polygons = NULL; + node->parent = parent; + src->ExpectTokenString( "(" ); + node->planeType = src->ParseInt(); + node->planeDist = src->ParseFloat(); + src->ExpectTokenString( ")" ); + if ( node->planeType != -1 ) { + node->children[0] = ParseNodes( src, model, node ); + node->children[1] = ParseNodes( src, model, node ); + } + return node; +} + +/* +================ +idCollisionModelManagerLocal::ParsePolygons +================ +*/ +void idCollisionModelManagerLocal::ParsePolygons( idLexer *src, cm_model_t *model ) { + cm_polygon_t *p; + int i, numEdges; + idVec3 normal; + idToken token; + + if ( src->CheckTokenType( TT_NUMBER, 0, &token ) ) { + model->polygonBlock = (cm_polygonBlock_t *) Mem_Alloc( sizeof( cm_polygonBlock_t ) + token.GetIntValue() ); + model->polygonBlock->bytesRemaining = token.GetIntValue(); + model->polygonBlock->next = ( (byte *) model->polygonBlock ) + sizeof( cm_polygonBlock_t ); + } + + src->ExpectTokenString( "{" ); + while ( !src->CheckTokenString( "}" ) ) { + // parse polygon + numEdges = src->ParseInt(); + p = AllocPolygon( model, numEdges ); + p->numEdges = numEdges; + src->ExpectTokenString( "(" ); + for ( i = 0; i < p->numEdges; i++ ) { + p->edges[i] = src->ParseInt(); + } + src->ExpectTokenString( ")" ); + src->Parse1DMatrix( 3, normal.ToFloatPtr() ); + p->plane.SetNormal( normal ); + p->plane.SetDist( src->ParseFloat() ); + src->Parse1DMatrix( 3, p->bounds[0].ToFloatPtr() ); + src->Parse1DMatrix( 3, p->bounds[1].ToFloatPtr() ); + src->ExpectTokenType( TT_STRING, 0, &token ); + // get material + p->material = declManager->FindMaterial( token ); + p->contents = p->material->GetContentFlags(); + p->checkcount = 0; + // filter polygon into tree + R_FilterPolygonIntoTree( model, model->node, NULL, p ); + } +} + +/* +================ +idCollisionModelManagerLocal::ParseBrushes +================ +*/ +void idCollisionModelManagerLocal::ParseBrushes( idLexer *src, cm_model_t *model ) { + cm_brush_t *b; + int i, numPlanes; + idVec3 normal; + idToken token; + + if ( src->CheckTokenType( TT_NUMBER, 0, &token ) ) { + model->brushBlock = (cm_brushBlock_t *) Mem_Alloc( sizeof( cm_brushBlock_t ) + token.GetIntValue() ); + model->brushBlock->bytesRemaining = token.GetIntValue(); + model->brushBlock->next = ( (byte *) model->brushBlock ) + sizeof( cm_brushBlock_t ); + } + + src->ExpectTokenString( "{" ); + while ( !src->CheckTokenString( "}" ) ) { + // parse brush + numPlanes = src->ParseInt(); + b = AllocBrush( model, numPlanes ); + b->numPlanes = numPlanes; + src->ExpectTokenString( "{" ); + for ( i = 0; i < b->numPlanes; i++ ) { + src->Parse1DMatrix( 3, normal.ToFloatPtr() ); + b->planes[i].SetNormal( normal ); + b->planes[i].SetDist( src->ParseFloat() ); + } + src->ExpectTokenString( "}" ); + src->Parse1DMatrix( 3, b->bounds[0].ToFloatPtr() ); + src->Parse1DMatrix( 3, b->bounds[1].ToFloatPtr() ); + src->ReadToken( &token ); + if ( token.type == TT_NUMBER ) { + b->contents = token.GetIntValue(); // old .cm files use a single integer + } else { + b->contents = ContentsFromString( token ); + } + b->checkcount = 0; + b->primitiveNum = 0; + // filter brush into tree + R_FilterBrushIntoTree( model, model->node, NULL, b ); + } +} + +/* +================ +idCollisionModelManagerLocal::ParseCollisionModel +================ +*/ +bool idCollisionModelManagerLocal::ParseCollisionModel( idLexer *src ) { + cm_model_t *model; + idToken token; + + if ( numModels >= MAX_SUBMODELS ) { + common->Error( "LoadModel: no free slots" ); + return false; + } + model = AllocModel(); + models[numModels ] = model; + numModels++; + // parse the file + src->ExpectTokenType( TT_STRING, 0, &token ); + model->name = token; + src->ExpectTokenString( "{" ); + while ( !src->CheckTokenString( "}" ) ) { + + src->ReadToken( &token ); + + if ( token == "vertices" ) { + ParseVertices( src, model ); + continue; + } + + if ( token == "edges" ) { + ParseEdges( src, model ); + continue; + } + + if ( token == "nodes" ) { + src->ExpectTokenString( "{" ); + model->node = ParseNodes( src, model, NULL ); + src->ExpectTokenString( "}" ); + continue; + } + + if ( token == "polygons" ) { + ParsePolygons( src, model ); + continue; + } + + if ( token == "brushes" ) { + ParseBrushes( src, model ); + continue; + } + + src->Error( "ParseCollisionModel: bad token \"%s\"", token.c_str() ); + } + // calculate edge normals + checkCount++; + CalculateEdgeNormals( model, model->node ); + // get model bounds from brush and polygon bounds + CM_GetNodeBounds( &model->bounds, model->node ); + // get model contents + model->contents = CM_GetNodeContents( model->node ); + // total memory used by this model + model->usedMemory = model->numVertices * sizeof(cm_vertex_t) + + model->numEdges * sizeof(cm_edge_t) + + model->polygonMemory + + model->brushMemory + + model->numNodes * sizeof(cm_node_t) + + model->numPolygonRefs * sizeof(cm_polygonRef_t) + + model->numBrushRefs * sizeof(cm_brushRef_t); + + return true; +} + +/* +================ +idCollisionModelManagerLocal::LoadCollisionModelFile +================ +*/ +bool idCollisionModelManagerLocal::LoadCollisionModelFile( const char *name, unsigned int mapFileCRC ) { + idStr fileName; + idToken token; + idLexer *src; + unsigned int crc; + + // load it + fileName = name; + fileName.SetFileExtension( CM_FILE_EXT ); + src = new idLexer( fileName ); + src->SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if ( !src->IsLoaded() ) { + delete src; + return false; + } + + if ( !src->ExpectTokenString( CM_FILEID ) ) { + common->Warning( "%s is not an CM file.", fileName.c_str() ); + delete src; + return false; + } + + if ( !src->ReadToken( &token ) || token != CM_FILEVERSION ) { + common->Warning( "%s has version %s instead of %s", fileName.c_str(), token.c_str(), CM_FILEVERSION ); + delete src; + return false; + } + + if ( !src->ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) { + common->Warning( "%s has no map file CRC", fileName.c_str() ); + delete src; + return false; + } + + crc = token.GetUnsignedLongValue(); + if ( mapFileCRC && crc != mapFileCRC ) { + common->Printf( "%s is out of date\n", fileName.c_str() ); + delete src; + return false; + } + + // parse the file + while ( 1 ) { + if ( !src->ReadToken( &token ) ) { + break; + } + + if ( token == "collisionModel" ) { + if ( !ParseCollisionModel( src ) ) { + delete src; + return false; + } + continue; + } + + src->Error( "idCollisionModelManagerLocal::LoadCollisionModelFile: bad token \"%s\"", token.c_str() ); + } + + delete src; + + return true; +} + +#endif // !Q4_CM_LEGACY_SEED diff --git a/src/cm/collisionmodel_load.cpp b/src/cm/collisionmodel_load.cpp new file mode 100644 index 0000000..64588c9 --- /dev/null +++ b/src/cm/collisionmodel_load.cpp @@ -0,0 +1,1266 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#if defined( Q4_CM_LEGACY_SEED ) +#include "collisionmodel_load_legacy.inc" +#else + +/* +=========================================================================== + +Quake 4 source reconstruction + +Public collision-manager methods recovered from the matching PDB and the +address-matched Hex-Rays bodies in quake4.exe. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +static char q4FullCollisionModelName[MAX_OSPATH]; + +idCollisionModelManagerLocal collisionModelManagerLocal; +idCollisionModelManager *collisionModelManager = &collisionModelManagerLocal; + +idCollisionModelLocal::~idCollisionModelLocal( void ) { +} + +idCollisionModelManagerLocal::idCollisionModelManagerLocal( void ) { +} + +idCollisionModelManagerLocal::~idCollisionModelManagerLocal( void ) { +} + +void idCollisionModelManagerLocal::Init( void ) { + Clear(); +} + +void idCollisionModelManagerLocal::Shutdown( void ) { + Clear(); +} + +void idCollisionModelManagerLocal::ClearModel( idCollisionModelLocal *model ) { + model->refCount = 0; + model->fileTime = -1; + model->bounds.Clear(); + model->contents = 0; + model->isTraceModel = false; + model->isConvex = false; + model->maxVertices = 0; + model->numVertices = 0; + model->vertices = NULL; + model->maxEdges = 0; + model->numEdges = 0; + model->edges = NULL; + model->maxPolygonEdges = 0; + model->numPolygonEdges = 0; + model->polygonEdges = NULL; + model->maxPolygons = 0; + model->numPolygons = 0; + model->polygons = NULL; + model->maxBrushPlanes = 0; + model->numBrushPlanes = 0; + model->brushPlanes = NULL; + model->maxBrushes = 0; + model->numBrushes = 0; + model->brushes = NULL; + model->numNodes = 0; + model->node = NULL; + model->nodeBlocks = NULL; + model->polygonRefBlocks = NULL; + model->brushRefBlocks = NULL; + model->numPrimitives = 0; + model->numBrushRefs = 0; + model->numPolygonRefs = 0; + model->numInternalEdges = 0; + model->numSharpEdges = 0; + model->numRemovedPolys = 0; + model->numMergedPolys = 0; + model->usedMemory = 0; +} + +idCollisionModelLocal *idCollisionModelManagerLocal::AllocModel( void ) { + idCollisionModelLocal *model = new idCollisionModelLocal; + ClearModel( model ); + return model; +} + +cm_node_t *idCollisionModelManagerLocal::AllocNode( idCollisionModelLocal *model, int blockSize ) { + cm_nodeBlock_t *block = model->nodeBlocks; + if ( block == NULL || block->nextNode == NULL ) { + block = static_cast( Mem_ClearedAlloc( + sizeof( cm_nodeBlock_t ) + blockSize * sizeof( cm_node_t ), MA_CM ) ); + block->nextNode = reinterpret_cast( block + 1 ); + block->next = model->nodeBlocks; + model->nodeBlocks = block; + + cm_node_t *node = block->nextNode; + for ( int i = 0; i < blockSize - 1; i++, node++ ) { + node->parent = node + 1; + } + node->parent = NULL; + } + + cm_node_t *node = block->nextNode; + block->nextNode = node->parent; + node->parent = NULL; + return node; +} + +cm_polygonRef_t *idCollisionModelManagerLocal::AllocPolygonReference( idCollisionModelLocal *model, int blockSize ) { + cm_polygonRefBlock_t *block = model->polygonRefBlocks; + if ( block == NULL || block->nextRef == NULL ) { + block = static_cast( Mem_Alloc( + sizeof( cm_polygonRefBlock_t ) + blockSize * sizeof( cm_polygonRef_t ), MA_CM ) ); + block->nextRef = reinterpret_cast( block + 1 ); + block->next = model->polygonRefBlocks; + model->polygonRefBlocks = block; + + cm_polygonRef_t *ref = block->nextRef; + for ( int i = 0; i < blockSize - 1; i++, ref++ ) { + ref->next = ref + 1; + } + ref->next = NULL; + } + + cm_polygonRef_t *ref = block->nextRef; + block->nextRef = ref->next; + return ref; +} + +cm_brushRef_t *idCollisionModelManagerLocal::AllocBrushReference( idCollisionModelLocal *model, int blockSize ) { + cm_brushRefBlock_t *block = model->brushRefBlocks; + if ( block == NULL || block->nextRef == NULL ) { + block = static_cast( Mem_Alloc( + sizeof( cm_brushRefBlock_t ) + blockSize * sizeof( cm_brushRef_t ), MA_CM ) ); + block->nextRef = reinterpret_cast( block + 1 ); + block->next = model->brushRefBlocks; + model->brushRefBlocks = block; + + cm_brushRef_t *ref = block->nextRef; + for ( int i = 0; i < blockSize - 1; i++, ref++ ) { + ref->next = ref + 1; + } + ref->next = NULL; + } + + cm_brushRef_t *ref = block->nextRef; + block->nextRef = ref->next; + return ref; +} + +cm_polygon_t *idCollisionModelManagerLocal::AllocPolygon( idCollisionModelLocal *model, int numEdges ) { + cm_polygon_t *polygon; + if ( model->polygons != NULL || model->polygonEdges != NULL ) { + polygon = &model->polygons[model->numPolygons++]; + polygon->numEdges = numEdges; + polygon->edges = &model->polygonEdges[model->numPolygonEdges]; + model->numPolygonEdges += numEdges; + } else { + model->numPolygons++; + model->numPolygonEdges += numEdges; + polygon = static_cast( Mem_Alloc( + sizeof( cm_polygon_t ) + numEdges * sizeof( int ), MA_CM ) ); + polygon->edges = reinterpret_cast( polygon + 1 ); + } + return polygon; +} + +cm_brush_t *idCollisionModelManagerLocal::AllocBrush( idCollisionModelLocal *model, int numPlanes ) { + cm_brush_t *brush; + if ( model->brushes != NULL || model->brushPlanes != NULL ) { + brush = &model->brushes[model->numBrushes++]; + brush->numPlanes = numPlanes; + brush->planes = &model->brushPlanes[model->numBrushPlanes]; + model->numBrushPlanes += numPlanes; + } else { + model->numBrushes++; + model->numBrushPlanes += numPlanes; + brush = static_cast( Mem_Alloc( + sizeof( cm_brush_t ) + numPlanes * sizeof( idPlane ), MA_CM ) ); + brush->planes = reinterpret_cast( brush + 1 ); + } + return brush; +} + +int CM_GetNodeContents( cm_node_t *node ) { + int contents = 0; + while ( true ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + contents |= ref->p->contents; + } + for ( cm_brushRef_t *ref = node->brushes; ref != NULL; ref = ref->next ) { + contents |= ref->b->contents; + } + if ( node->planeType == -1 ) { + break; + } + contents |= CM_GetNodeContents( node->children[1] ); + node = node->children[0]; + } + return contents; +} + +int CM_GetModelMemory( idCollisionModelLocal *model ) { + return model->numPolygonEdges * sizeof( int ) + + model->numEdges * sizeof( cm_edge_t ) + + model->numNodes * sizeof( cm_node_t ) + + model->numBrushRefs * sizeof( cm_brushRef_t ) + + model->numPolygonRefs * sizeof( cm_polygonRef_t ) + + model->numPolygons * sizeof( cm_polygon_t ) + + model->numVertices * sizeof( cm_vertex_t ) + + model->numBrushes * sizeof( cm_brush_t ) + + model->numBrushPlanes * sizeof( idPlane ); +} + +void idCollisionModelManagerLocal::RemapEdges( cm_node_t *node, int *edgeRemap ) { + while ( true ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + cm_polygon_t *polygon = ref->p; + if ( polygon->checkcount == checkCount ) { + continue; + } + polygon->checkcount = checkCount; + for ( int i = 0; i < polygon->numEdges; i++ ) { + const int edge = polygon->edges[i]; + polygon->edges[i] = edge >= 0 ? edgeRemap[edge] : -edgeRemap[-edge]; + } + } + if ( node->planeType == -1 ) { + break; + } + RemapEdges( node->children[1], edgeRemap ); + node = node->children[0]; + } +} + +void idCollisionModelManagerLocal::RemapPolygonReferences_r( cm_node_t *node, cm_polygon_t *polygon, cm_polygon_t *newPolygon ) { + while ( node != NULL ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + if ( ref->p == polygon ) { + ref->p = newPolygon; + break; + } + } + if ( node->planeType == -1 ) { + return; + } + if ( polygon->bounds[0][node->planeType] <= node->planeDist ) { + if ( polygon->bounds[1][node->planeType] >= node->planeDist ) { + RemapPolygonReferences_r( node->children[1], polygon, newPolygon ); + } + node = node->children[0]; + } else { + node = node->children[1]; + } + } +} + +void idCollisionModelManagerLocal::RemapBrushReferences_r( cm_node_t *node, cm_brush_t *brush, cm_brush_t *newBrush ) { + while ( node != NULL ) { + for ( cm_brushRef_t *ref = node->brushes; ref != NULL; ref = ref->next ) { + if ( ref->b == brush ) { + ref->b = newBrush; + break; + } + } + if ( node->planeType == -1 ) { + return; + } + if ( brush->bounds[0][node->planeType] <= node->planeDist ) { + if ( brush->bounds[1][node->planeType] >= node->planeDist ) { + RemapBrushReferences_r( node->children[1], brush, newBrush ); + } + node = node->children[0]; + } else { + node = node->children[1]; + } + } +} + +void CM_R_GetNodeBounds( idBounds *bounds, cm_node_t *node ) { + while ( true ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + bounds->AddBounds( ref->p->bounds ); + } + for ( cm_brushRef_t *ref = node->brushes; ref != NULL; ref = ref->next ) { + bounds->AddBounds( ref->b->bounds ); + } + if ( node->planeType == -1 ) { + break; + } + CM_R_GetNodeBounds( bounds, node->children[1] ); + node = node->children[0]; + } +} + +void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node ) { + bounds->Clear(); + CM_R_GetNodeBounds( bounds, node ); + if ( bounds->IsCleared() ) { + bounds->Zero(); + } +} + +void idCollisionModelManagerLocal::ClearHash( idBounds &bounds ) { + cm_vertexHash->Clear(); + cm_edgeHash->Clear(); + cm_modelBounds = bounds; + + const float maxSize = Max( bounds[1].x - bounds[0].x, bounds[1].y - bounds[0].y ); + const int hashBoxSize = static_cast( maxSize / VERTEX_HASH_BOXSIZE ); + if ( hashBoxSize <= 1 ) { + cm_vertexShift = 1; + return; + } + cm_vertexShift = 0; + while ( ( 1 << cm_vertexShift ) < hashBoxSize ) { + cm_vertexShift++; + } +} + +void idCollisionModelManagerLocal::SetupHash( void ) { + if ( cm_vertexHash == NULL ) { + cm_vertexHash = new idHashIndex( VERTEX_HASH_SIZE, 1024 ); + } + if ( cm_edgeHash == NULL ) { + cm_edgeHash = new idHashIndex( EDGE_HASH_SIZE, 1024 ); + } + if ( cm_windingList == NULL ) { + cm_windingList = new cm_windingList_t; + } + if ( cm_outList == NULL ) { + cm_outList = new cm_windingList_t; + } + if ( cm_tmpList == NULL ) { + cm_tmpList = new cm_windingList_t; + } +} + +void idCollisionModelManagerLocal::ShutdownHash( void ) { + delete cm_vertexHash; + cm_vertexHash = NULL; + delete cm_edgeHash; + cm_edgeHash = NULL; + delete cm_tmpList; + cm_tmpList = NULL; + delete cm_outList; + cm_outList = NULL; + delete cm_windingList; + cm_windingList = NULL; +} + +void idCollisionModelManagerLocal::FinishModel( idCollisionModelLocal *model, bool mergePrimitives ) { + checkCount++; + MergeTreePolygons( model, model->node, mergePrimitives ); + checkCount++; + FindInternalEdges( model, model->node ); + checkCount++; + CalculateEdgeNormals( model, model->node ); + OptimizeArrays( model ); + CM_GetNodeBounds( &model->bounds, model->node ); + model->contents = CM_GetNodeContents( model->node ); + model->usedMemory = CM_GetModelMemory( model ); +} + +void idCollisionModelManagerLocal::MergeTreePolygons( idCollisionModelLocal *model, + cm_node_t *node, bool mergePrimitives ) { + // Quake 4 permits callers (notably render-model extraction) to preserve + // primitive boundaries. The array-based representation remains valid + // without a merge; shared edges are still classified below. + if ( node == NULL || !mergePrimitives ) { + return; + } + // Preserve authored polygons. This avoids changing primitive numbers, + // which CompoundTrmFromModel uses to split compound hulls. +} + +static bool CM_Q4PointInsidePolygon( const idCollisionModelLocal *model, + const cm_polygon_t *polygon, const idVec3 &point ) { + for ( int i = 0; i < polygon->numEdges; ++i ) { + const int signedEdge = polygon->edges[i]; + const cm_edge_t &edge = model->edges[abs( signedEdge )]; + const idVec3 &a = model->vertices[edge.vertexNum[INTSIGNBITSET( signedEdge )]].p; + const idVec3 &b = model->vertices[edge.vertexNum[INTSIGNBITNOTSET( signedEdge )]].p; + if ( ( point - a ) * ( ( b - a ).Cross( polygon->plane.Normal() ) ) > VERTEX_EPSILON ) { + return false; + } + } + return true; +} + +static void CM_Q4MarkInternalPair( idCollisionModelLocal *model, + cm_polygon_t *first, cm_polygon_t *second ) { + if ( first == second || first->contents != second->contents || + !first->bounds.IntersectsBounds( second->bounds.Expand( VERTEX_EPSILON ) ) ) { + return; + } + for ( int i = 0; i < first->numEdges; ++i ) { + const int signedEdge = first->edges[i]; + cm_edge_t &edge = model->edges[abs( signedEdge )]; + if ( edge.internal ) { + continue; + } + for ( int j = 0; j < second->numEdges; ++j ) { + if ( second->edges[j] == -signedEdge && edge.numUsers <= 2 ) { + edge.internal = true; + ++model->numInternalEdges; + break; + } + } + if ( edge.internal ) { + continue; + } + const idVec3 &a = model->vertices[edge.vertexNum[INTSIGNBITSET( signedEdge )]].p; + const idVec3 &b = model->vertices[edge.vertexNum[INTSIGNBITNOTSET( signedEdge )]].p; + if ( idMath::Fabs( second->plane.Distance( a ) ) <= VERTEX_EPSILON && + idMath::Fabs( second->plane.Distance( b ) ) <= VERTEX_EPSILON && + CM_Q4PointInsidePolygon( model, second, a ) && CM_Q4PointInsidePolygon( model, second, b ) ) { + edge.internal = true; + ++model->numInternalEdges; + } + } +} + +void idCollisionModelManagerLocal::FindInternalEdges( idCollisionModelLocal *model, cm_node_t *node ) { + if ( model == NULL || node == NULL ) { + return; + } + // The optimized array owns each polygon once, so pairwise classification + // is deterministic even when the BSP holds multiple references. + for ( int i = 0; i < model->numPolygons; ++i ) { + for ( int j = i + 1; j < model->numPolygons; ++j ) { + CM_Q4MarkInternalPair( model, &model->polygons[i], &model->polygons[j] ); + } + } +} + +#define Q4_SHARP_EDGE_DOT -0.7f +void idCollisionModelManagerLocal::CalculateEdgeNormals( idCollisionModelLocal *model, cm_node_t *node ) { + while ( node != NULL ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + cm_polygon_t *polygon = ref->p; + if ( polygon == NULL || polygon->checkcount == checkCount ) { + continue; + } + polygon->checkcount = checkCount; + for ( int i = 0; i < polygon->numEdges; ++i ) { + const int edgeNum = polygon->edges[i]; + cm_edge_t &edge = model->edges[abs( edgeNum )]; + if ( edge.normal == vec3_zero ) { + if ( edge.numUsers == 1 ) { + const idVec3 direction = model->vertices[edge.vertexNum[edgeNum < 0]].p - + model->vertices[edge.vertexNum[edgeNum > 0]].p; + edge.normal = polygon->plane.Normal().Cross( direction ); + edge.normal.Normalize(); + } else { + edge.normal = polygon->plane.Normal(); + } + } else { + const float dot = edge.normal * polygon->plane.Normal(); + if ( dot < Q4_SHARP_EDGE_DOT ) { + const idVec3 direction = model->vertices[edge.vertexNum[edgeNum > 0]].p - + model->vertices[edge.vertexNum[edgeNum < 0]].p; + edge.normal = edge.normal.Cross( direction ) + polygon->plane.Normal().Cross( -direction ); + const float length = edge.normal.Length(); + if ( length > 0.0f ) { + edge.normal *= ( 0.5f / ( 0.5f + 0.5f * Q4_SHARP_EDGE_DOT ) ) / length; + } + ++model->numSharpEdges; + } else { + const float scale = 0.5f / ( 0.5f + 0.5f * dot ); + edge.normal = scale * ( edge.normal + polygon->plane.Normal() ); + } + } + } + } + if ( node->planeType == -1 ) { + break; + } + CalculateEdgeNormals( model, node->children[1] ); + node = node->children[0]; + } +} + +void idCollisionModelManagerLocal::OptimizeArrays( idCollisionModelLocal *model ) { + if ( model == NULL || model->numVertices <= 0 || model->numEdges <= 0 ) { + return; + } + const int remapCount = Max( model->numVertices, model->numEdges ); + int *remap = static_cast( Mem_ClearedAlloc( remapCount * sizeof( int ), MA_CM ) ); + for ( int i = 1; i < model->numEdges; ++i ) { + if ( model->edges[i].numUsers != 0 ) { + remap[model->edges[i].vertexNum[0]] = 1; + remap[model->edges[i].vertexNum[1]] = 1; + } + } + int newVertices = 0; + for ( int i = 0; i < model->numVertices; ++i ) { + if ( remap[i] ) { + remap[i] = newVertices; + model->vertices[newVertices++] = model->vertices[i]; + } + } + for ( int i = 1; i < model->numEdges; ++i ) { + model->edges[i].vertexNum[0] = remap[model->edges[i].vertexNum[0]]; + model->edges[i].vertexNum[1] = remap[model->edges[i].vertexNum[1]]; + } + memset( remap, 0, remapCount * sizeof( int ) ); + int newEdges = 1; + for ( int i = 1; i < model->numEdges; ++i ) { + if ( model->edges[i].numUsers != 0 ) { + remap[i] = newEdges; + model->edges[newEdges++] = model->edges[i]; + } + } + ++checkCount; + RemapEdges( model->node, remap ); + model->numVertices = newVertices; + model->numEdges = newEdges; + Mem_Free( remap ); + + cm_vertex_t *oldVertices = model->vertices; + model->vertices = static_cast( Mem_ClearedAlloc( model->numVertices * sizeof( cm_vertex_t ), MA_CM ) ); + memcpy( model->vertices, oldVertices, model->numVertices * sizeof( cm_vertex_t ) ); + Mem_Free( oldVertices ); + cm_edge_t *oldEdges = model->edges; + model->edges = static_cast( Mem_ClearedAlloc( model->numEdges * sizeof( cm_edge_t ), MA_CM ) ); + memcpy( model->edges, oldEdges, model->numEdges * sizeof( cm_edge_t ) ); + Mem_Free( oldEdges ); + model->maxVertices = model->numVertices; + model->maxEdges = model->numEdges; +} +#undef Q4_SHARP_EDGE_DOT + +void idCollisionModelManagerLocal::CopyPolygon( cm_polygon_t *dest, cm_polygon_t *src ) { + int *destEdges = dest->edges; + *dest = *src; + dest->edges = destEdges; + memcpy( dest->edges, src->edges, src->numEdges * sizeof( dest->edges[0] ) ); +} + +void idCollisionModelManagerLocal::RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *polygon ) { + while ( node != NULL ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + if ( ref->p == polygon ) { + ref->p = NULL; + break; + } + } + + if ( node->planeType == -1 ) { + return; + } + if ( polygon->bounds[0][node->planeType] <= node->planeDist ) { + if ( polygon->bounds[1][node->planeType] >= node->planeDist ) { + RemovePolygonReferences_r( node->children[1], polygon ); + } + node = node->children[0]; + } else { + node = node->children[1]; + } + } +} + +void idCollisionModelManagerLocal::RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *brush ) { + while ( node != NULL ) { + for ( cm_brushRef_t *ref = node->brushes; ref != NULL; ref = ref->next ) { + if ( ref->b == brush ) { + ref->b = NULL; + break; + } + } + + if ( node->planeType == -1 ) { + return; + } + if ( brush->bounds[0][node->planeType] <= node->planeDist ) { + if ( brush->bounds[1][node->planeType] >= node->planeDist ) { + RemoveBrushReferences_r( node->children[1], brush ); + } + node = node->children[0]; + } else { + node = node->children[1]; + } + } +} + +void idCollisionModelManagerLocal::FreeTree_r( idCollisionModelLocal *model, cm_node_t *headNode, cm_node_t *node ) { + while ( node->polygons != NULL ) { + cm_polygonRef_t *ref = node->polygons; + if ( ref->p != NULL ) { + cm_polygon_t *polygon = ref->p; + RemovePolygonReferences_r( headNode, polygon ); + if ( model->polygons == NULL && model->polygonEdges == NULL ) { + model->numPolygons--; + model->numPolygonEdges -= polygon->numEdges; + Mem_Free( polygon ); + } + } + node->polygons = ref->next; + } + + while ( node->brushes != NULL ) { + cm_brushRef_t *ref = node->brushes; + if ( ref->b != NULL ) { + cm_brush_t *brush = ref->b; + RemoveBrushReferences_r( headNode, brush ); + if ( model->brushes == NULL && model->brushPlanes == NULL ) { + model->numBrushes--; + model->numBrushPlanes -= brush->numPlanes; + Mem_Free( brush ); + } + } + node->brushes = ref->next; + } + + if ( node->planeType != -1 ) { + FreeTree_r( model, headNode, node->children[0] ); + node->children[0] = NULL; + FreeTree_r( model, headNode, node->children[1] ); + node->children[1] = NULL; + } +} + +void idCollisionModelManagerLocal::FreeModelMemory( idCollisionModelLocal *model ) { + if ( model->node != NULL ) { + FreeTree_r( model, model->node, model->node ); + } + + for ( cm_polygonRefBlock_t *block = model->polygonRefBlocks; block != NULL; ) { + cm_polygonRefBlock_t *next = block->next; + Mem_Free( block ); + block = next; + } + for ( cm_brushRefBlock_t *block = model->brushRefBlocks; block != NULL; ) { + cm_brushRefBlock_t *next = block->next; + Mem_Free( block ); + block = next; + } + for ( cm_nodeBlock_t *block = model->nodeBlocks; block != NULL; ) { + cm_nodeBlock_t *next = block->next; + Mem_Free( block ); + block = next; + } + + Mem_Free16( model->polygonEdges ); + Mem_Free16( model->polygons ); + Mem_Free16( model->brushPlanes ); + Mem_Free16( model->brushes ); + Mem_Free( model->edges ); + Mem_Free( model->vertices ); + ClearModel( model ); +} + +void idCollisionModelManagerLocal::AddToMapModelReferenceCounts( const char *mapName, int add ) { + idStr name = mapName; + name.StripFileExtension(); + if ( name.Length() == 0 ) { + return; + } + for ( int i = 0; i < models.Num(); i++ ) { + if ( idStr::IcmpnPath( models[i]->name.c_str(), name.c_str(), name.Length() ) == 0 ) { + models[i]->refCount += add; + } + } +} + +void idCollisionModelManagerLocal::PrintModelInfo( const idCollisionModelLocal *model ) { + common->Printf( "%6i vertices (%i KB)\n", model->numVertices, model->numVertices * sizeof( cm_vertex_t ) >> 10 ); + common->Printf( "%6i edges (%i KB)\n", model->numEdges, model->numEdges * sizeof( cm_edge_t ) >> 10 ); + common->Printf( "%6i polygons (%i KB)\n", model->numPolygons, model->numPolygons * sizeof( cm_polygon_t ) >> 10 ); + common->Printf( "%6i polygon edges (%i KB)\n", model->numPolygonEdges, model->numPolygonEdges * sizeof( int ) >> 10 ); + common->Printf( "%6i brushes (%i KB)\n", model->numBrushes, model->numBrushes * sizeof( cm_brush_t ) >> 10 ); + common->Printf( "%6i brush planes (%i KB)\n", model->numBrushPlanes, model->numBrushPlanes * sizeof( idPlane ) >> 10 ); + common->Printf( "%6i nodes (%i KB)\n", model->numNodes, model->numNodes * sizeof( cm_node_t ) >> 10 ); + common->Printf( "%6i polygon refs (%i KB)\n", model->numPolygonRefs, model->numPolygonRefs * sizeof( cm_polygonRef_t ) >> 10 ); + common->Printf( "%6i brush refs (%i KB)\n", model->numBrushRefs, model->numBrushRefs * sizeof( cm_brushRef_t ) >> 10 ); + common->Printf( "%6i internal edges\n", model->numInternalEdges ); + common->Printf( "%6i sharp edges\n", model->numSharpEdges ); + common->Printf( "%6i contained polygons removed\n", model->numRemovedPolys ); + common->Printf( "%6i polygons merged\n", model->numMergedPolys ); + common->Printf( "%6i KB total memory used\n", model->usedMemory >> 10 ); +} + +void idCollisionModelManagerLocal::AccumulateModelInfo( idCollisionModelLocal *total ) { + ClearModel( total ); + for ( int i = 0; i < models.Num(); i++ ) { + const idCollisionModelLocal *model = models[i]; + total->numVertices += model->numVertices; + total->numEdges += model->numEdges; + total->numPolygonEdges += model->numPolygonEdges; + total->numPolygons += model->numPolygons; + total->numBrushes += model->numBrushes; + total->numBrushPlanes += model->numBrushPlanes; + total->numNodes += model->numNodes; + total->numBrushRefs += model->numBrushRefs; + total->numPolygonRefs += model->numPolygonRefs; + total->numInternalEdges += model->numInternalEdges; + total->numSharpEdges += model->numSharpEdges; + total->numRemovedPolys += model->numRemovedPolys; + total->numMergedPolys += model->numMergedPolys; + total->usedMemory += model->usedMemory; + } +} + +bool idCollisionModelManagerLocal::IsLoaded( void ) { + return loaded != 0; +} + +idCollisionModelLocal *idCollisionModelManagerLocal::FindModel( const char *name ) { + for ( int i = 0; i < models.Num(); i++ ) { + if ( idStr::Icmp( models[i]->name.c_str(), name ) == 0 ) { + return models[i]; + } + } + return NULL; +} + +bool idCollisionModelManagerLocal::IsRenderModelName( const char *modelName ) { + return idStr::IcmpnPath( modelName, "models/", 7 ) == 0 + || idStr::IcmpnPath( modelName, "gfx/", 4 ) == 0; +} + +const char *idCollisionModelManagerLocal::GetFullModelName( const char *mapName, const char *modelName ) { + if ( !IsRenderModelName( modelName ) ) { + idStr name = mapName; + name.StripFileExtension(); + if ( idStr::IcmpnPath( modelName, name.c_str(), name.Length() ) != 0 ) { + idStr::snPrintf( q4FullCollisionModelName, sizeof( q4FullCollisionModelName ), "%s/%s", name.c_str(), modelName ); + return q4FullCollisionModelName; + } + } + return modelName; +} + +void idCollisionModelManagerLocal::PreCacheModel( const char *mapName, const char *modelName ) { + idStr name = GetFullModelName( mapName, modelName ); + if ( FindModel( name.c_str() ) == NULL + && LoadModel( mapName, name.c_str() ) != NULL + && FindModel( name.c_str() ) == NULL ) { + common->Warning( "idCollisionModelManagerLocal::PreCacheModel: collision file for '%s' contains different model", + name.c_str() ); + } +} + +void idCollisionModelManagerLocal::FreeMap( const char *mapName ) { + AddToMapModelReferenceCounts( mapName, -1 ); +} + +bool idCollisionModelManagerLocal::TrmFromModel( const char *mapName, const char *modelName, idTraceModel &trm ) { + idCollisionModelLocal *model = static_cast( LoadModel( mapName, modelName ) ); + if ( model != NULL ) { + return TrmFromModel( model, trm ); + } + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s not found.\n", modelName ); + return false; +} + +int idCollisionModelManagerLocal::CompoundTrmFromModel( const char *mapName, const char *modelName, + idTraceModel *trms, int maxTrms ) { + idCollisionModelLocal *model = static_cast( LoadModel( mapName, modelName ) ); + if ( model != NULL ) { + return CompoundTrmFromModel( model, trms, maxTrms ); + } + common->Printf( "idCollisionModelManagerLocal::CompoundTrmFromModel: model %s not found.\n", modelName ); + return 0; +} + +void idCollisionModelManagerLocal::PurgeModels( void ) { + for ( int i = 0; i < models.Num(); i++ ) { + if ( models[i]->refCount <= 0 ) { + FreeModelMemory( models[i] ); + } + } +} + +void idCollisionModelManagerLocal::ModelInfo( int num ) { + if ( num >= 0 ) { + PrintModelInfo( models[num] ); + return; + } + idCollisionModelLocal total; + AccumulateModelInfo( &total ); + PrintModelInfo( &total ); +} + +void idCollisionModelManagerLocal::ListModels( void ) { + int totalMemory = 0; + for ( int i = 0; i < models.Num(); i++ ) { + common->Printf( "%4d: %5d KB %s\n", i, models[i]->usedMemory >> 10, models[i]->name.c_str() ); + totalMemory += models[i]->usedMemory; + } + common->Printf( "%4d KB in %d models\n", totalMemory >> 10, models.Num() ); +} + +void idCollisionModelManagerLocal::PrintMemInfo( MemInfo *mi ) { + idFile *file = fileSystem->OpenFileWrite( mi->filebase + "_coll.txt", "fs_savepath", false ); + if ( file == NULL ) { + return; + } + + int totalMemory = 0; + for ( int i = 0; i < models.Num(); i++ ) { + totalMemory += models[i]->usedMemory; + file->Printf( "%8d: %s\n", models[i]->usedMemory, models[i]->name.c_str() ); + } + mi->collAssetsTotal = totalMemory; + mi->collAssetsCount = models.Num(); + file->Printf( "\nTotal collision model bytes allocated: %s (%s items)\n", + idStr::FormatNumber( totalMemory ).c_str(), idStr::FormatNumber( models.Num() ).c_str() ); + fileSystem->CloseFile( file ); +} + +void idCollisionModelManagerLocal::FreeModel( idCollisionModel *model ) { + if ( model == NULL ) { + return; + } + + idCollisionModelLocal *localModel = static_cast( model ); + if ( localModel->isTraceModel ) { + FreeModelMemory( localModel ); + delete localModel; + } else if ( IsRenderModelName( localModel->name.c_str() ) && localModel->refCount > 0 ) { + localModel->refCount--; + } +} + +void idCollisionModelManagerLocal::Clear( void ) { + mapName.Clear(); + mapFileTime = 0; + loaded = 0; + checkCount = 0; + + for ( int i = 0; i < models.Num(); i++ ) { + delete models[i]; + } + models.Clear(); + + getContacts = false; + contacts = NULL; + maxContacts = 0; + numContacts = 0; + numProcNodes = 0; + procNodes = NULL; + cm_windingList = NULL; + cm_outList = NULL; + cm_tmpList = NULL; + cm_vertexHash = NULL; + cm_edgeHash = NULL; + cm_modelBounds.Clear(); + cm_vertexShift = 0; +} + +bool idCollisionModelManagerLocal::TrmFromModel_r( idTraceModel &trm, cm_node_t *node, int primitiveNum ) { + while ( node != NULL ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + cm_polygon_t *polygon = ref->p; + if ( polygon == NULL || polygon->checkcount == checkCount || + ( primitiveNum >= 0 && polygon->primitiveNum != primitiveNum ) ) { + continue; + } + polygon->checkcount = checkCount; + if ( trm.numPolys >= MAX_TRACEMODEL_POLYS || polygon->numEdges > MAX_TRACEMODEL_POLYEDGES ) { + return false; + } + traceModelPoly_t &dest = trm.polys[trm.numPolys++]; + dest.bounds = polygon->bounds; + dest.normal = polygon->plane.Normal(); + dest.dist = polygon->plane.Dist(); + dest.numEdges = polygon->numEdges; + for ( int i = 0; i < polygon->numEdges; ++i ) { + dest.edges[i] = polygon->edges[i]; + } + } + if ( node->planeType == -1 ) { + return true; + } + if ( !TrmFromModel_r( trm, node->children[1], primitiveNum ) ) { + return false; + } + node = node->children[0]; + } + return true; +} + +bool idCollisionModelManagerLocal::TrmFromModel( const idCollisionModelLocal *model, idTraceModel &trm ) { + if ( model == NULL || model->node == NULL ) { + return false; + } + if ( model->numVertices > MAX_TRACEMODEL_VERTS || model->numEdges > MAX_TRACEMODEL_EDGES + 1 ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s is too complex.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + + trm.type = TRM_CUSTOM; + trm.numVerts = 0; + trm.numEdges = 1; + trm.numPolys = 0; + trm.bounds.Clear(); + ++checkCount; + if ( !TrmFromModel_r( trm, model->node, -1 ) ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many polygons.\n", model->name.c_str() ); + return false; + } + for ( int i = 0; i < model->numVertices; ++i ) { + trm.verts[i] = model->vertices[i].p; + trm.bounds.AddPoint( trm.verts[i] ); + } + trm.numVerts = model->numVertices; + for ( int i = 0; i < model->numEdges; ++i ) { + trm.edges[i].v[0] = model->edges[i].vertexNum[0]; + trm.edges[i].v[1] = model->edges[i].vertexNum[1]; + } + trm.numEdges = Max( 0, model->numEdges - 1 ); + if ( !trm.IsClosedSurface() ) { + common->Warning( "idCollisionModelManagerLocal::TrmFromModel: model %s has dangling edges", model->name.c_str() ); + return false; + } + trm.offset = trm.bounds.GetCenter(); + trm.GenerateEdgeNormals(); + trm.TestConvexity(); + trm.ClearUnused(); + return true; +} + +int idCollisionModelManagerLocal::CompoundTrmFromModel( const idCollisionModelLocal *model, + idTraceModel *trms, int maxTrms ) { + if ( model == NULL || trms == NULL || maxTrms <= 0 ) { + return 0; + } + int numTrms = 0; + for ( int primitive = 0; primitive < model->numPrimitives && numTrms < maxTrms; ++primitive ) { + idTraceModel &trm = trms[numTrms]; + trm.type = TRM_CUSTOM; + trm.numVerts = 0; + trm.numEdges = 0; + trm.numPolys = 0; + trm.bounds.Clear(); + ++checkCount; + if ( !TrmFromModel_r( trm, model->node, primitive ) ) { + continue; + } + if ( trm.numPolys == 0 ) { + continue; + } + + int edgeRemap[MAX_TRACEMODEL_EDGES + 1]; + int vertexRemap[MAX_TRACEMODEL_VERTS]; + memset( edgeRemap, 0, sizeof( edgeRemap ) ); + memset( vertexRemap, -1, sizeof( vertexRemap ) ); + for ( int p = 0; p < trm.numPolys; ++p ) { + for ( int e = 0; e < trm.polys[p].numEdges; ++e ) { + const int oldSigned = trm.polys[p].edges[e]; + const int oldEdge = abs( oldSigned ); + if ( oldEdge <= 0 || oldEdge >= model->numEdges ) { + continue; + } + if ( edgeRemap[oldEdge] == 0 ) { + if ( trm.numEdges >= MAX_TRACEMODEL_EDGES ) { + return numTrms; + } + const cm_edge_t &sourceEdge = model->edges[oldEdge]; + const int newEdge = ++trm.numEdges; + edgeRemap[oldEdge] = newEdge; + for ( int side = 0; side < 2; ++side ) { + const int oldVertex = sourceEdge.vertexNum[side]; + if ( vertexRemap[oldVertex] < 0 ) { + if ( trm.numVerts >= MAX_TRACEMODEL_VERTS ) { + return numTrms; + } + vertexRemap[oldVertex] = trm.numVerts; + trm.verts[trm.numVerts++] = model->vertices[oldVertex].p; + } + trm.edges[newEdge].v[side] = vertexRemap[oldVertex]; + } + } + trm.polys[p].edges[e] = oldSigned < 0 ? -edgeRemap[oldEdge] : edgeRemap[oldEdge]; + } + } + for ( int v = 0; v < trm.numVerts; ++v ) { + trm.bounds.AddPoint( trm.verts[v] ); + } + if ( !trm.IsClosedSurface() ) { + continue; + } + trm.offset = trm.bounds.GetCenter(); + trm.GenerateEdgeNormals(); + trm.TestConvexity(); + trm.ClearUnused(); + ++numTrms; + } + return numTrms; +} + +idCollisionModel *idCollisionModelManagerLocal::ModelFromTrm( const char *sourceMapName, + const char *modelName, const idTraceModel &trm, const idMaterial *material ) { + idCollisionModelLocal *model = AllocModel(); + model->name = GetFullModelName( sourceMapName ? sourceMapName : "", modelName ? modelName : "traceModel" ); + model->fileTime = -1; + model->isTraceModel = true; + model->isConvex = trm.isConvex; + model->numPrimitives = 1; + model->maxVertices = model->numVertices = trm.numVerts; + model->maxEdges = model->numEdges = trm.numEdges + 1; + model->maxPolygons = model->numPolygons = trm.numPolys; + for ( int i = 0; i < trm.numPolys; ++i ) { + model->maxPolygonEdges += trm.polys[i].numEdges; + } + model->numPolygonEdges = model->maxPolygonEdges; + model->vertices = static_cast( Mem_ClearedAlloc( model->maxVertices * sizeof( cm_vertex_t ), MA_CM ) ); + model->edges = static_cast( Mem_ClearedAlloc( model->maxEdges * sizeof( cm_edge_t ), MA_CM ) ); + model->polygonEdges = static_cast( Mem_Alloc16( model->maxPolygonEdges * sizeof( int ), MA_CM ) ); + model->polygons = static_cast( Mem_Alloc16( model->maxPolygons * sizeof( cm_polygon_t ), MA_CM ) ); + memset( model->polygons, 0, model->maxPolygons * sizeof( cm_polygon_t ) ); + model->node = AllocNode( model, NODE_BLOCK_SIZE_SMALL ); + model->node->planeType = -1; + model->bounds = trm.bounds; + for ( int i = 0; i < trm.numVerts; ++i ) { + model->vertices[i].p = trm.verts[i]; + } + for ( int i = 0; i <= trm.numEdges; ++i ) { + model->edges[i].vertexNum[0] = trm.edges[i].v[0]; + model->edges[i].vertexNum[1] = trm.edges[i].v[1]; + } + const idMaterial *useMaterial = material ? material : declManager->FindMaterial( "_default" ); + int edgeOffset = 0; + for ( int i = 0; i < trm.numPolys; ++i ) { + cm_polygon_t &polygon = model->polygons[i]; + polygon.bounds = trm.polys[i].bounds; + polygon.plane.SetNormal( trm.polys[i].normal ); + polygon.plane.SetDist( trm.polys[i].dist ); + polygon.material = useMaterial; + polygon.contents = useMaterial->GetContentFlags(); + polygon.primitiveNum = 0; + polygon.numEdges = trm.polys[i].numEdges; + polygon.edges = model->polygonEdges + edgeOffset; + memcpy( polygon.edges, trm.polys[i].edges, polygon.numEdges * sizeof( int ) ); + edgeOffset += polygon.numEdges; + cm_polygonRef_t *ref = AllocPolygonReference( model, REFERENCE_BLOCK_SIZE_SMALL ); + ref->p = &polygon; + ref->next = model->node->polygons; + model->node->polygons = ref; + ++model->numPolygonRefs; + } + model->contents = useMaterial->GetContentFlags(); + model->usedMemory = CM_GetModelMemory( model ); + return model; +} + +void idCollisionModelManagerLocal::PolygonFromWinding( idCollisionModelLocal *model, + idFixedWinding *winding, const idPlane &plane, const idMaterial *material, int primitiveNum ) { + if ( model == NULL || winding == NULL || winding->GetNumPoints() < 3 ) { + return; + } + // Render-model extraction supplies exact capacities. The MD5R path can + // arrive through rvPrimBatch without a sizing pass, so reserve a bounded + // working set on first use. + if ( model->vertices == NULL ) { + model->maxVertices = 65536; + model->maxEdges = 65536; + model->maxPolygons = 65536; + model->maxPolygonEdges = 196608; + model->vertices = static_cast( Mem_ClearedAlloc( model->maxVertices * sizeof( cm_vertex_t ), MA_CM ) ); + model->edges = static_cast( Mem_ClearedAlloc( model->maxEdges * sizeof( cm_edge_t ), MA_CM ) ); + model->polygons = static_cast( Mem_Alloc16( model->maxPolygons * sizeof( cm_polygon_t ), MA_CM ) ); + model->polygonEdges = static_cast( Mem_Alloc16( model->maxPolygonEdges * sizeof( int ), MA_CM ) ); + memset( model->polygons, 0, model->maxPolygons * sizeof( cm_polygon_t ) ); + model->numEdges = 1; + } + const int count = winding->GetNumPoints(); + if ( model->numPolygons >= model->maxPolygons || model->numPolygonEdges + count > model->maxPolygonEdges ) { + common->Warning( "PolygonFromWinding: capacity exceeded for %s", model->name.c_str() ); + return; + } + cm_polygon_t *polygon = &model->polygons[model->numPolygons++]; + polygon->bounds.Clear(); + polygon->plane = plane; + polygon->material = material ? material : declManager->FindMaterial( "_default" ); + polygon->contents = polygon->material->GetContentFlags(); + polygon->primitiveNum = primitiveNum; + polygon->numEdges = count; + polygon->edges = model->polygonEdges + model->numPolygonEdges; + model->numPolygonEdges += count; + for ( int i = 0; i < 3; ++i ) { + polygon->texBounds[i].Set( (*winding)[Min( i, count - 1 )].s, (*winding)[Min( i, count - 1 )].t ); + } + for ( int i = 0; i < count; ++i ) { + const idVec3 &point = (*winding)[i].ToVec3(); + polygon->bounds.AddPoint( point ); + int vertex[2]; + const idVec3 edgePoints[2] = { point, (*winding)[( i + 1 ) % count].ToVec3() }; + for ( int side = 0; side < 2; ++side ) { + vertex[side] = -1; + for ( int v = 0; v < model->numVertices; ++v ) { + if ( model->vertices[v].p.Compare( edgePoints[side], VERTEX_EPSILON ) ) { + vertex[side] = v; + break; + } + } + if ( vertex[side] < 0 && model->numVertices < model->maxVertices ) { + vertex[side] = model->numVertices; + model->vertices[model->numVertices++].p = edgePoints[side]; + } + } + int edgeNum = 0; + for ( int e = 1; e < model->numEdges; ++e ) { + if ( model->edges[e].vertexNum[0] == vertex[0] && model->edges[e].vertexNum[1] == vertex[1] ) { + edgeNum = e; + break; + } + if ( model->edges[e].vertexNum[0] == vertex[1] && model->edges[e].vertexNum[1] == vertex[0] ) { + edgeNum = -e; + break; + } + } + if ( edgeNum == 0 && model->numEdges < model->maxEdges ) { + edgeNum = model->numEdges++; + model->edges[edgeNum].vertexNum[0] = vertex[0]; + model->edges[edgeNum].vertexNum[1] = vertex[1]; + } + polygon->edges[i] = edgeNum; + ++model->edges[abs( edgeNum )].numUsers; + } + if ( model->node == NULL ) { + model->node = AllocNode( model, NODE_BLOCK_SIZE_SMALL ); + model->node->planeType = -1; + } + cm_polygonRef_t *ref = AllocPolygonReference( model, REFERENCE_BLOCK_SIZE_LARGE ); + ref->p = polygon; + ref->next = model->node->polygons; + model->node->polygons = ref; + ++model->numPolygonRefs; + model->numPrimitives = Max( model->numPrimitives, abs( primitiveNum ) + 1 ); +} + +idCollisionModel *idCollisionModelManagerLocal::ExtractCollisionModel( idRenderModel *renderModel, const char *modelName ) { + if ( renderModel == NULL ) { + return NULL; + } + const idStr fullName = GetFullModelName( "", modelName ? modelName : renderModel->Name() ); + idCollisionModelLocal *existing = FindModel( fullName ); + if ( existing != NULL && existing->fileTime != -1 ) { + ++existing->refCount; + return existing; + } + + int triangles = 0; + for ( int i = 0; i < renderModel->NumSurfaces(); ++i ) { + const modelSurface_t *surface = renderModel->Surface( i ); + if ( surface && surface->geometry && surface->shader && surface->shader->GetContentFlags() != 0 ) { + triangles += surface->geometry->numIndexes / 3; + } + } + if ( triangles == 0 ) { + return NULL; + } + idCollisionModelLocal *model = AllocModel(); + model->name = fullName; + model->fileTime = 0; + model->refCount = 1; + model->maxVertices = triangles * 3; + model->maxEdges = triangles * 3 + 1; + model->maxPolygons = triangles; + model->maxPolygonEdges = triangles * 3; + model->vertices = static_cast( Mem_ClearedAlloc( model->maxVertices * sizeof( cm_vertex_t ), MA_CM ) ); + model->edges = static_cast( Mem_ClearedAlloc( model->maxEdges * sizeof( cm_edge_t ), MA_CM ) ); + model->polygons = static_cast( Mem_Alloc16( model->maxPolygons * sizeof( cm_polygon_t ), MA_CM ) ); + model->polygonEdges = static_cast( Mem_Alloc16( model->maxPolygonEdges * sizeof( int ), MA_CM ) ); + memset( model->polygons, 0, model->maxPolygons * sizeof( cm_polygon_t ) ); + model->numEdges = 1; + model->node = AllocNode( model, NODE_BLOCK_SIZE_LARGE ); + model->node->planeType = -1; + for ( int i = 0; i < renderModel->NumSurfaces(); ++i ) { + const modelSurface_t *surface = renderModel->Surface( i ); + if ( surface == NULL || surface->geometry == NULL || surface->shader == NULL || surface->shader->GetContentFlags() == 0 ) { + continue; + } + for ( int index = 0; index + 2 < surface->geometry->numIndexes; index += 3 ) { + idFixedWinding winding; + for ( int corner = 0; corner < 3; ++corner ) { + const idDrawVert &vertex = surface->geometry->verts[surface->geometry->indexes[index + 2 - corner]]; + winding += vertex.xyz; + winding[corner].s = vertex.st.x; + winding[corner].t = vertex.st.y; + } + idPlane plane; + winding.GetPlane( plane ); + if ( plane.Normal().LengthSqr() > 0.0f ) { + PolygonFromWinding( model, &winding, -plane, surface->shader, i ); + } + } + } + model->isConvex = false; + FinishModel( model, false ); + models.Append( model ); + return model; +} + +idCollisionModel *idCollisionModelManagerLocal::LoadModel( const char *sourceMapName, const char *modelName ) { + const char *fullName = GetFullModelName( sourceMapName ? sourceMapName : "", modelName ); + idCollisionModelLocal *model = FindModel( fullName ); + if ( model != NULL && model->fileTime != -1 ) { + if ( IsRenderModelName( model->name.c_str() ) ) { + ++model->refCount; + } + return model; + } + if ( LoadCollisionModelFile( fullName, 0 ) ) { + model = FindModel( fullName ); + if ( model != NULL ) { + return model; + } + } + if ( IsRenderModelName( modelName ) ) { + idRenderModel *renderModel = renderModelManager->FindModel( modelName ); + if ( renderModel != NULL && !renderModel->IsDefaultModel() ) { + return ExtractCollisionModel( renderModel, fullName ); + } + } + return NULL; +} + +void idCollisionModelManagerLocal::LoadMap( const idMapFile *mapFile, bool forceReload ) { + if ( mapFile == NULL ) { + common->Error( "idCollisionModelManagerLocal::LoadMap: NULL mapFile" ); + } + const idStr worldName = GetFullModelName( mapFile->GetName(), "worldMap" ); + idCollisionModelLocal *world = FindModel( worldName ); + if ( world == NULL || world->fileTime == -1 || forceReload ) { + SetupHash(); + LoadCollisionModelFile( mapFile->GetName(), mapFile->GetGeometryCRC() ); + ShutdownHash(); + } + loaded = 1; + mapName = mapFile->GetName(); + mapFileTime = mapFile->GetGeometryCRC(); + AddToMapModelReferenceCounts( mapFile->GetName(), 1 ); +} + +#endif diff --git a/src/cm/collisionmodel_load_legacy.inc b/src/cm/collisionmodel_load_legacy.inc new file mode 100644 index 0000000..d24f928 --- /dev/null +++ b/src/cm/collisionmodel_load_legacy.inc @@ -0,0 +1,3689 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + + It is more important to minimize the number of collision polygons + than it is to minimize the number of edges used for collision + detection (total edges - internal edges). + + Stitching the world tends to minimize the number of edges used + for collision detection (more internal edges). However stitching + also results in more collision polygons which usually makes a + stitched world slower. + + In an average map over 30% of all edges is internal. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + + +idCollisionModelManagerLocal collisionModelManagerLocal; +idCollisionModelManager * collisionModelManager = &collisionModelManagerLocal; + +cm_windingList_t * cm_windingList; +cm_windingList_t * cm_outList; +cm_windingList_t * cm_tmpList; + +idHashIndex * cm_vertexHash; +idHashIndex * cm_edgeHash; + +idBounds cm_modelBounds; +int cm_vertexShift; + + +/* +=============================================================================== + +Proc BSP tree for data pruning + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::ParseProcNodes +================ +*/ +void idCollisionModelManagerLocal::ParseProcNodes( idLexer *src ) { + int i; + + src->ExpectTokenString( "{" ); + + numProcNodes = src->ParseInt(); + if ( numProcNodes < 0 ) { + src->Error( "ParseProcNodes: bad numProcNodes" ); + } + procNodes = (cm_procNode_t *)Mem_ClearedAlloc( numProcNodes * sizeof( cm_procNode_t ) ); + + for ( i = 0; i < numProcNodes; i++ ) { + cm_procNode_t *node; + + node = &procNodes[i]; + + src->Parse1DMatrix( 4, node->plane.ToFloatPtr() ); + node->children[0] = src->ParseInt(); + node->children[1] = src->ParseInt(); + } + + src->ExpectTokenString( "}" ); +} + +/* +================ +idCollisionModelManagerLocal::LoadProcBSP + + FIXME: if the nodes would be at the start of the .proc file it would speed things up considerably +================ +*/ +void idCollisionModelManagerLocal::LoadProcBSP( const char *name ) { + idStr filename; + idToken token; + idLexer *src; + + // load it + filename = name; + filename.SetFileExtension( PROC_FILE_EXT ); + src = new idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if ( !src->IsLoaded() ) { + common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: couldn't load %s", filename.c_str() ); + delete src; + return; + } + + if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) { + common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID ); + delete src; + return; + } + + // parse the file + while ( 1 ) { + if ( !src->ReadToken( &token ) ) { + break; + } + + if ( token == "model" ) { + src->SkipBracedSection(); + continue; + } + + if ( token == "shadowModel" ) { + src->SkipBracedSection(); + continue; + } + + if ( token == "interAreaPortals" ) { + src->SkipBracedSection(); + continue; + } + + if ( token == "nodes" ) { + ParseProcNodes( src ); + break; + } + + src->Error( "idCollisionModelManagerLocal::LoadProcBSP: bad token \"%s\"", token.c_str() ); + } + + delete src; +} + +/* +=============================================================================== + +Free map + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::Clear +================ +*/ +void idCollisionModelManagerLocal::Clear( void ) { + mapName.Clear(); + mapFileTime = 0; + loaded = 0; + checkCount = 0; + maxModels = 0; + numModels = 0; + models = NULL; + memset( trmPolygons, 0, sizeof( trmPolygons ) ); + trmBrushes[0] = NULL; + trmMaterial = NULL; + numProcNodes = 0; + procNodes = NULL; + getContacts = false; + contacts = NULL; + maxContacts = 0; + numContacts = 0; +} + +/* +================ +idCollisionModelManagerLocal::RemovePolygonReferences_r +================ +*/ +void idCollisionModelManagerLocal::RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *p ) { + cm_polygonRef_t *pref; + + while( node ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( pref->p == p ) { + pref->p = NULL; + // cannot return here because we can have links down the tree due to polygon merging + //return; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( p->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( p->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + RemovePolygonReferences_r( node->children[1], p ); + node = node->children[0]; + } + } +} + +/* +================ +idCollisionModelManagerLocal::RemoveBrushReferences_r +================ +*/ +void idCollisionModelManagerLocal::RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *b ) { + cm_brushRef_t *bref; + + while( node ) { + for ( bref = node->brushes; bref; bref = bref->next ) { + if ( bref->b == b ) { + bref->b = NULL; + return; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( b->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( b->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + RemoveBrushReferences_r( node->children[1], b ); + node = node->children[0]; + } + } +} + +/* +================ +idCollisionModelManagerLocal::FreeNode +================ +*/ +void idCollisionModelManagerLocal::FreeNode( cm_node_t *node ) { + // don't free the node here + // the nodes are allocated in blocks which are freed when the model is freed +} + +/* +================ +idCollisionModelManagerLocal::FreePolygonReference +================ +*/ +void idCollisionModelManagerLocal::FreePolygonReference( cm_polygonRef_t *pref ) { + // don't free the polygon reference here + // the polygon references are allocated in blocks which are freed when the model is freed +} + +/* +================ +idCollisionModelManagerLocal::FreeBrushReference +================ +*/ +void idCollisionModelManagerLocal::FreeBrushReference( cm_brushRef_t *bref ) { + // don't free the brush reference here + // the brush references are allocated in blocks which are freed when the model is freed +} + +/* +================ +idCollisionModelManagerLocal::FreePolygon +================ +*/ +void idCollisionModelManagerLocal::FreePolygon( cm_model_t *model, cm_polygon_t *poly ) { + model->numPolygons--; + model->polygonMemory -= sizeof( cm_polygon_t ) + ( poly->numEdges - 1 ) * sizeof( poly->edges[0] ); + if ( model->polygonBlock == NULL ) { + Mem_Free( poly ); + } +} + +/* +================ +idCollisionModelManagerLocal::FreeBrush +================ +*/ +void idCollisionModelManagerLocal::FreeBrush( cm_model_t *model, cm_brush_t *brush ) { + model->numBrushes--; + model->brushMemory -= sizeof( cm_brush_t ) + ( brush->numPlanes - 1 ) * sizeof( brush->planes[0] ); + if ( model->brushBlock == NULL ) { + Mem_Free( brush ); + } +} + +/* +================ +idCollisionModelManagerLocal::FreeTree_r +================ +*/ +void idCollisionModelManagerLocal::FreeTree_r( cm_model_t *model, cm_node_t *headNode, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + cm_brushRef_t *bref; + cm_brush_t *b; + + // free all polygons at this node + for ( pref = node->polygons; pref; pref = node->polygons ) { + p = pref->p; + if ( p ) { + // remove all other references to this polygon + RemovePolygonReferences_r( headNode, p ); + FreePolygon( model, p ); + } + node->polygons = pref->next; + FreePolygonReference( pref ); + } + // free all brushes at this node + for ( bref = node->brushes; bref; bref = node->brushes ) { + b = bref->b; + if ( b ) { + // remove all other references to this brush + RemoveBrushReferences_r( headNode, b ); + FreeBrush( model, b ); + } + node->brushes = bref->next; + FreeBrushReference( bref ); + } + // recurse down the tree + if ( node->planeType != -1 ) { + FreeTree_r( model, headNode, node->children[0] ); + node->children[0] = NULL; + FreeTree_r( model, headNode, node->children[1] ); + node->children[1] = NULL; + } + FreeNode( node ); +} + +/* +================ +idCollisionModelManagerLocal::FreeModel +================ +*/ +void idCollisionModelManagerLocal::FreeModel( cm_model_t *model ) { + cm_polygonRefBlock_t *polygonRefBlock, *nextPolygonRefBlock; + cm_brushRefBlock_t *brushRefBlock, *nextBrushRefBlock; + cm_nodeBlock_t *nodeBlock, *nextNodeBlock; + + // free the tree structure + if ( model->node ) { + FreeTree_r( model, model->node, model->node ); + } + // free blocks with polygon references + for ( polygonRefBlock = model->polygonRefBlocks; polygonRefBlock; polygonRefBlock = nextPolygonRefBlock ) { + nextPolygonRefBlock = polygonRefBlock->next; + Mem_Free( polygonRefBlock ); + } + // free blocks with brush references + for ( brushRefBlock = model->brushRefBlocks; brushRefBlock; brushRefBlock = nextBrushRefBlock ) { + nextBrushRefBlock = brushRefBlock->next; + Mem_Free( brushRefBlock ); + } + // free blocks with nodes + for ( nodeBlock = model->nodeBlocks; nodeBlock; nodeBlock = nextNodeBlock ) { + nextNodeBlock = nodeBlock->next; + Mem_Free( nodeBlock ); + } + // free block allocated polygons + Mem_Free( model->polygonBlock ); + // free block allocated brushes + Mem_Free( model->brushBlock ); + // free edges + Mem_Free( model->edges ); + // free vertices + Mem_Free( model->vertices ); + // free the model + delete model; +} + +/* +================ +idCollisionModelManagerLocal::FreeMap +================ +*/ +void idCollisionModelManagerLocal::FreeMap( void ) { + int i; + + if ( !loaded ) { + Clear(); + return; + } + + for ( i = 0; i < maxModels; i++ ) { + if ( !models[i] ) { + continue; + } + FreeModel( models[i] ); + } + + FreeTrmModelStructure(); + + Mem_Free( models ); + + Clear(); + + ShutdownHash(); +} + +/* +================ +idCollisionModelManagerLocal::FreeTrmModelStructure +================ +*/ +void idCollisionModelManagerLocal::FreeTrmModelStructure( void ) { + int i; + + assert( models ); + if ( !models[MAX_SUBMODELS] ) { + return; + } + + for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) { + FreePolygon( models[MAX_SUBMODELS], trmPolygons[i]->p ); + } + FreeBrush( models[MAX_SUBMODELS], trmBrushes[0]->b ); + + models[MAX_SUBMODELS]->node->polygons = NULL; + models[MAX_SUBMODELS]->node->brushes = NULL; + FreeModel( models[MAX_SUBMODELS] ); +} + + +/* +=============================================================================== + +Edge normals + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::CalculateEdgeNormals +================ +*/ +#define SHARP_EDGE_DOT -0.7f + +void idCollisionModelManagerLocal::CalculateEdgeNormals( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + cm_edge_t *edge; + float dot, s; + int i, edgeNum; + idVec3 dir; + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs( edgeNum ); + if ( edge->normal[0] == 0.0f && edge->normal[1] == 0.0f && edge->normal[2] == 0.0f ) { + // if the edge is only used by this polygon + if ( edge->numUsers == 1 ) { + dir = model->vertices[ edge->vertexNum[edgeNum < 0]].p - model->vertices[ edge->vertexNum[edgeNum > 0]].p; + edge->normal = p->plane.Normal().Cross( dir ); + edge->normal.Normalize(); + } else { + // the edge is used by more than one polygon + edge->normal = p->plane.Normal(); + } + } else { + dot = edge->normal * p->plane.Normal(); + // if the two planes make a very sharp edge + if ( dot < SHARP_EDGE_DOT ) { + // max length normal pointing outside both polygons + dir = model->vertices[ edge->vertexNum[edgeNum > 0]].p - model->vertices[ edge->vertexNum[edgeNum < 0]].p; + edge->normal = edge->normal.Cross( dir ) + p->plane.Normal().Cross( -dir ); + edge->normal *= ( 0.5f / ( 0.5f + 0.5f * SHARP_EDGE_DOT ) ) / edge->normal.Length(); + model->numSharpEdges++; + } else { + s = 0.5f / ( 0.5f + 0.5f * dot ); + edge->normal = s * ( edge->normal + p->plane.Normal() ); + } + } + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + CalculateEdgeNormals( model, node->children[1] ); + node = node->children[0]; + } +} + +/* +=============================================================================== + +Trace model to general collision model + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::AllocModel +================ +*/ +cm_model_t *idCollisionModelManagerLocal::AllocModel( void ) { + cm_model_t *model; + + model = new cm_model_t; + model->contents = 0; + model->isConvex = false; + model->maxVertices = 0; + model->numVertices = 0; + model->vertices = NULL; + model->maxEdges = 0; + model->numEdges = 0; + model->edges= NULL; + model->node = NULL; + model->nodeBlocks = NULL; + model->polygonRefBlocks = NULL; + model->brushRefBlocks = NULL; + model->polygonBlock = NULL; + model->brushBlock = NULL; + model->numPolygons = model->polygonMemory = + model->numBrushes = model->brushMemory = + model->numNodes = model->numBrushRefs = + model->numPolygonRefs = model->numInternalEdges = + model->numSharpEdges = model->numRemovedPolys = + model->numMergedPolys = model->usedMemory = 0; + + return model; +} + +/* +================ +idCollisionModelManagerLocal::AllocNode +================ +*/ +cm_node_t *idCollisionModelManagerLocal::AllocNode( cm_model_t *model, int blockSize ) { + int i; + cm_node_t *node; + cm_nodeBlock_t *nodeBlock; + + if ( !model->nodeBlocks || !model->nodeBlocks->nextNode ) { + nodeBlock = (cm_nodeBlock_t *) Mem_ClearedAlloc( sizeof( cm_nodeBlock_t ) + blockSize * sizeof(cm_node_t) ); + nodeBlock->nextNode = (cm_node_t *) ( ( (byte *) nodeBlock ) + sizeof( cm_nodeBlock_t ) ); + nodeBlock->next = model->nodeBlocks; + model->nodeBlocks = nodeBlock; + node = nodeBlock->nextNode; + for ( i = 0; i < blockSize - 1; i++ ) { + node->parent = node + 1; + node = node->parent; + } + node->parent = NULL; + } + + node = model->nodeBlocks->nextNode; + model->nodeBlocks->nextNode = node->parent; + node->parent = NULL; + + return node; +} + +/* +================ +idCollisionModelManagerLocal::AllocPolygonReference +================ +*/ +cm_polygonRef_t *idCollisionModelManagerLocal::AllocPolygonReference( cm_model_t *model, int blockSize ) { + int i; + cm_polygonRef_t *pref; + cm_polygonRefBlock_t *prefBlock; + + if ( !model->polygonRefBlocks || !model->polygonRefBlocks->nextRef ) { + prefBlock = (cm_polygonRefBlock_t *) Mem_Alloc( sizeof( cm_polygonRefBlock_t ) + blockSize * sizeof(cm_polygonRef_t) ); + prefBlock->nextRef = (cm_polygonRef_t *) ( ( (byte *) prefBlock ) + sizeof( cm_polygonRefBlock_t ) ); + prefBlock->next = model->polygonRefBlocks; + model->polygonRefBlocks = prefBlock; + pref = prefBlock->nextRef; + for ( i = 0; i < blockSize - 1; i++ ) { + pref->next = pref + 1; + pref = pref->next; + } + pref->next = NULL; + } + + pref = model->polygonRefBlocks->nextRef; + model->polygonRefBlocks->nextRef = pref->next; + + return pref; +} + +/* +================ +idCollisionModelManagerLocal::AllocBrushReference +================ +*/ +cm_brushRef_t *idCollisionModelManagerLocal::AllocBrushReference( cm_model_t *model, int blockSize ) { + int i; + cm_brushRef_t *bref; + cm_brushRefBlock_t *brefBlock; + + if ( !model->brushRefBlocks || !model->brushRefBlocks->nextRef ) { + brefBlock = (cm_brushRefBlock_t *) Mem_Alloc( sizeof(cm_brushRefBlock_t) + blockSize * sizeof(cm_brushRef_t) ); + brefBlock->nextRef = (cm_brushRef_t *) ( ( (byte *) brefBlock ) + sizeof(cm_brushRefBlock_t) ); + brefBlock->next = model->brushRefBlocks; + model->brushRefBlocks = brefBlock; + bref = brefBlock->nextRef; + for ( i = 0; i < blockSize - 1; i++ ) { + bref->next = bref + 1; + bref = bref->next; + } + bref->next = NULL; + } + + bref = model->brushRefBlocks->nextRef; + model->brushRefBlocks->nextRef = bref->next; + + return bref; +} + +/* +================ +idCollisionModelManagerLocal::AllocPolygon +================ +*/ +cm_polygon_t *idCollisionModelManagerLocal::AllocPolygon( cm_model_t *model, int numEdges ) { + cm_polygon_t *poly; + int size; + + size = sizeof( cm_polygon_t ) + ( numEdges - 1 ) * sizeof( poly->edges[0] ); + model->numPolygons++; + model->polygonMemory += size; + if ( model->polygonBlock && model->polygonBlock->bytesRemaining >= size ) { + poly = (cm_polygon_t *) model->polygonBlock->next; + model->polygonBlock->next += size; + model->polygonBlock->bytesRemaining -= size; + } else { + poly = (cm_polygon_t *) Mem_Alloc( size ); + } + return poly; +} + +/* +================ +idCollisionModelManagerLocal::AllocBrush +================ +*/ +cm_brush_t *idCollisionModelManagerLocal::AllocBrush( cm_model_t *model, int numPlanes ) { + cm_brush_t *brush; + int size; + + size = sizeof( cm_brush_t ) + ( numPlanes - 1 ) * sizeof( brush->planes[0] ); + model->numBrushes++; + model->brushMemory += size; + if ( model->brushBlock && model->brushBlock->bytesRemaining >= size ) { + brush = (cm_brush_t *) model->brushBlock->next; + model->brushBlock->next += size; + model->brushBlock->bytesRemaining -= size; + } else { + brush = (cm_brush_t *) Mem_Alloc( size ); + } + return brush; +} + +/* +================ +idCollisionModelManagerLocal::AddPolygonToNode +================ +*/ +void idCollisionModelManagerLocal::AddPolygonToNode( cm_model_t *model, cm_node_t *node, cm_polygon_t *p ) { + cm_polygonRef_t *pref; + + pref = AllocPolygonReference( model, model->numPolygonRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE ); + pref->p = p; + pref->next = node->polygons; + node->polygons = pref; + model->numPolygonRefs++; +} + +/* +================ +idCollisionModelManagerLocal::AddBrushToNode +================ +*/ +void idCollisionModelManagerLocal::AddBrushToNode( cm_model_t *model, cm_node_t *node, cm_brush_t *b ) { + cm_brushRef_t *bref; + + bref = AllocBrushReference( model, model->numBrushRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE ); + bref->b = b; + bref->next = node->brushes; + node->brushes = bref; + model->numBrushRefs++; +} + +/* +================ +idCollisionModelManagerLocal::SetupTrmModelStructure +================ +*/ +void idCollisionModelManagerLocal::SetupTrmModelStructure( void ) { + int i; + cm_node_t *node; + cm_model_t *model; + + // setup model + model = AllocModel(); + + assert( models ); + models[MAX_SUBMODELS] = model; + // create node to hold the collision data + node = (cm_node_t *) AllocNode( model, 1 ); + node->planeType = -1; + model->node = node; + // allocate vertex and edge arrays + model->numVertices = 0; + model->maxVertices = MAX_TRACEMODEL_VERTS; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) ); + model->numEdges = 0; + model->maxEdges = MAX_TRACEMODEL_EDGES+1; + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) ); + // create a material for the trace model polygons + trmMaterial = declManager->FindMaterial( "_tracemodel", false ); + if ( !trmMaterial ) { + common->FatalError( "_tracemodel material not found" ); + } + + // allocate polygons + for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) { + trmPolygons[i] = AllocPolygonReference( model, MAX_TRACEMODEL_POLYS ); + trmPolygons[i]->p = AllocPolygon( model, MAX_TRACEMODEL_POLYEDGES ); + trmPolygons[i]->p->bounds.Clear(); + trmPolygons[i]->p->plane.Zero(); + trmPolygons[i]->p->checkcount = 0; + trmPolygons[i]->p->contents = -1; // all contents + trmPolygons[i]->p->material = trmMaterial; + trmPolygons[i]->p->numEdges = 0; + } + // allocate brush for position test + trmBrushes[0] = AllocBrushReference( model, 1 ); + trmBrushes[0]->b = AllocBrush( model, MAX_TRACEMODEL_POLYS ); + trmBrushes[0]->b->primitiveNum = 0; + trmBrushes[0]->b->bounds.Clear(); + trmBrushes[0]->b->checkcount = 0; + trmBrushes[0]->b->contents = -1; // all contents + trmBrushes[0]->b->numPlanes = 0; +} + +/* +================ +idCollisionModelManagerLocal::SetupTrmModel + +Trace models (item boxes, etc) are converted to collision models on the fly, using the last model slot +as a reusable temporary buffer +================ +*/ +cmHandle_t idCollisionModelManagerLocal::SetupTrmModel( const idTraceModel &trm, const idMaterial *material ) { + int i, j; + cm_vertex_t *vertex; + cm_edge_t *edge; + cm_polygon_t *poly; + cm_model_t *model; + const traceModelVert_t *trmVert; + const traceModelEdge_t *trmEdge; + const traceModelPoly_t *trmPoly; + + assert( models ); + + if ( material == NULL ) { + material = trmMaterial; + } + + model = models[MAX_SUBMODELS]; + model->node->brushes = NULL; + model->node->polygons = NULL; + // if not a valid trace model + if ( trm.type == TRM_INVALID || !trm.numPolys ) { + return TRACE_MODEL_HANDLE; + } + // vertices + model->numVertices = trm.numVerts; + vertex = model->vertices; + trmVert = trm.verts; + for ( i = 0; i < trm.numVerts; i++, vertex++, trmVert++ ) { + vertex->p = *trmVert; + vertex->sideSet = 0; + } + // edges + model->numEdges = trm.numEdges; + edge = model->edges + 1; + trmEdge = trm.edges + 1; + for ( i = 0; i < trm.numEdges; i++, edge++, trmEdge++ ) { + edge->vertexNum[0] = trmEdge->v[0]; + edge->vertexNum[1] = trmEdge->v[1]; + edge->normal = trmEdge->normal; + edge->internal = false; + edge->sideSet = 0; + } + // polygons + model->numPolygons = trm.numPolys; + trmPoly = trm.polys; + for ( i = 0; i < trm.numPolys; i++, trmPoly++ ) { + poly = trmPolygons[i]->p; + poly->numEdges = trmPoly->numEdges; + for ( j = 0; j < trmPoly->numEdges; j++ ) { + poly->edges[j] = trmPoly->edges[j]; + } + poly->plane.SetNormal( trmPoly->normal ); + poly->plane.SetDist( trmPoly->dist ); + poly->bounds = trmPoly->bounds; + poly->material = material; + // link polygon at node + trmPolygons[i]->next = model->node->polygons; + model->node->polygons = trmPolygons[i]; + } + // if the trace model is convex + if ( trm.isConvex ) { + // setup brush for position test + trmBrushes[0]->b->numPlanes = trm.numPolys; + for ( i = 0; i < trm.numPolys; i++ ) { + trmBrushes[0]->b->planes[i] = trmPolygons[i]->p->plane; + } + trmBrushes[0]->b->bounds = trm.bounds; + // link brush at node + trmBrushes[0]->next = model->node->brushes; + model->node->brushes = trmBrushes[0]; + } + // model bounds + model->bounds = trm.bounds; + // convex + model->isConvex = trm.isConvex; + + return TRACE_MODEL_HANDLE; +} + +/* +=============================================================================== + +Optimisation, removal of polygons contained within brushes or solid + +=============================================================================== +*/ + +/* +============ +idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP +============ +*/ +int idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) { + int res; + idFixedWinding back; + cm_procNode_t *node; + float dist; + + do { + node = procNodes + nodeNum; + dist = node->plane.Normal() * origin + node->plane[3]; + if ( dist > radius ) { + res = SIDE_FRONT; + } + else if ( dist < -radius ) { + res = SIDE_BACK; + } + else { + res = w->Split( &back, node->plane, CHOP_EPSILON ); + } + if ( res == SIDE_FRONT ) { + nodeNum = node->children[0]; + } + else if ( res == SIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == SIDE_ON ) { + // continue with the side the winding faces + if ( node->plane.Normal() * normal > 0.0f ) { + nodeNum = node->children[0]; + } + else { + nodeNum = node->children[1]; + } + } + else { + // if either node is not solid + if ( node->children[0] < 0 || node->children[1] < 0 ) { + return false; + } + // only recurse if the node is not solid + if ( node->children[1] > 0 ) { + if ( !R_ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) { + return false; + } + } + nodeNum = node->children[0]; + } + } while ( nodeNum > 0 ); + if ( nodeNum < 0 ) { + return false; + } + return true; +} + +/* +============ +idCollisionModelManagerLocal::ChoppedAwayByProcBSP +============ +*/ +int idCollisionModelManagerLocal::ChoppedAwayByProcBSP( const idFixedWinding &w, const idPlane &plane, int contents ) { + idFixedWinding neww; + idBounds bounds; + float radius; + idVec3 origin; + + // if the .proc file has no BSP tree + if ( procNodes == NULL ) { + return false; + } + // don't chop if the polygon is not solid + if ( !(contents & CONTENTS_SOLID) ) { + return false; + } + // make a local copy of the winding + neww = w; + neww.GetBounds( bounds ); + origin = (bounds[1] - bounds[0]) * 0.5f; + radius = origin.Length() + CHOP_EPSILON; + origin = bounds[0] + origin; + // + return R_ChoppedAwayByProcBSP( 0, &neww, plane.Normal(), origin, radius ); +} + +/* +============= +idCollisionModelManagerLocal::ChopWindingWithBrush + + returns the least number of winding fragments outside the brush +============= +*/ +void idCollisionModelManagerLocal::ChopWindingListWithBrush( cm_windingList_t *list, cm_brush_t *b ) { + int i, k, res, startPlane, planeNum, bestNumWindings; + idFixedWinding back, front; + idPlane plane; + bool chopped; + int sidedness[MAX_POINTS_ON_WINDING]; + float dist; + + if ( b->numPlanes > MAX_POINTS_ON_WINDING ) { + return; + } + + // get sidedness for the list of windings + for ( i = 0; i < b->numPlanes; i++ ) { + plane = -b->planes[i]; + + dist = plane.Distance( list->origin ); + if ( dist > list->radius ) { + sidedness[i] = SIDE_FRONT; + } + else if ( dist < -list->radius ) { + sidedness[i] = SIDE_BACK; + } + else { + sidedness[i] = list->bounds.PlaneSide( plane ); + if ( sidedness[i] == PLANESIDE_FRONT ) { + sidedness[i] = SIDE_FRONT; + } + else if ( sidedness[i] == PLANESIDE_BACK ) { + sidedness[i] = SIDE_BACK; + } + else { + sidedness[i] = SIDE_CROSS; + } + } + } + + cm_outList->numWindings = 0; + for ( k = 0; k < list->numWindings; k++ ) { + // + startPlane = 0; + bestNumWindings = 1 + b->numPlanes; + chopped = false; + do { + front = list->w[k]; + cm_tmpList->numWindings = 0; + for ( planeNum = startPlane, i = 0; i < b->numPlanes; i++, planeNum++ ) { + + if ( planeNum >= b->numPlanes ) { + planeNum = 0; + } + + res = sidedness[planeNum]; + + if ( res == SIDE_CROSS ) { + plane = -b->planes[planeNum]; + res = front.Split( &back, plane, CHOP_EPSILON ); + } + + // NOTE: disabling this can create gaps at places where Z-fighting occurs + // Z-fighting should not occur but what if there is a decal brush side + // with exactly the same size as another brush side ? + // only leave windings on a brush if the winding plane and brush side plane face the same direction + if ( res == SIDE_ON && list->primitiveNum >= 0 && (list->normal * b->planes[planeNum].Normal()) > 0 ) { + // return because all windings in the list will be on this brush side plane + return; + } + + if ( res == SIDE_BACK ) { + if ( cm_outList->numWindings >= MAX_WINDING_LIST ) { + common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST ); + return; + } + // winding and brush didn't intersect, store the original winding + cm_outList->w[cm_outList->numWindings] = list->w[k]; + cm_outList->numWindings++; + chopped = false; + break; + } + + if ( res == SIDE_CROSS ) { + if ( cm_tmpList->numWindings >= MAX_WINDING_LIST ) { + common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST ); + return; + } + // store the front winding in the temporary list + cm_tmpList->w[cm_tmpList->numWindings] = back; + cm_tmpList->numWindings++; + chopped = true; + } + + // if already found a start plane which generates less fragments + if ( cm_tmpList->numWindings >= bestNumWindings ) { + break; + } + } + + // find the best start plane to get the least number of fragments outside the brush + if ( cm_tmpList->numWindings < bestNumWindings ) { + bestNumWindings = cm_tmpList->numWindings; + // store windings from temporary list in the out list + for ( i = 0; i < cm_tmpList->numWindings; i++ ) { + if ( cm_outList->numWindings + i >= MAX_WINDING_LIST ) { + common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST ); + return; + } + cm_outList->w[cm_outList->numWindings+i] = cm_tmpList->w[i]; + } + // if only one winding left then we can't do any better + if ( bestNumWindings == 1 ) { + break; + } + } + + // try the next start plane + startPlane++; + + } while ( chopped && startPlane < b->numPlanes ); + // + if ( chopped ) { + cm_outList->numWindings += bestNumWindings; + } + } + for ( k = 0; k < cm_outList->numWindings; k++ ) { + list->w[k] = cm_outList->w[k]; + } + list->numWindings = cm_outList->numWindings; +} + +/* +============ +idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes +============ +*/ +void idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes( cm_windingList_t *list, cm_node_t *node ) { + int i; + cm_brushRef_t *bref; + cm_brush_t *b; + + while( 1 ) { + for ( bref = node->brushes; bref; bref = bref->next ) { + b = bref->b; + // if we checked this brush already + if ( b->checkcount == checkCount ) { + continue; + } + b->checkcount = checkCount; + // if the windings in the list originate from this brush + if ( b->primitiveNum == list->primitiveNum ) { + continue; + } + // if brush has a different contents + if ( b->contents != list->contents ) { + continue; + } + // brush bounds and winding list bounds should overlap + for ( i = 0; i < 3; i++ ) { + if ( list->bounds[0][i] > b->bounds[1][i] ) { + break; + } + if ( list->bounds[1][i] < b->bounds[0][i] ) { + break; + } + } + if ( i < 3 ) { + continue; + } + // chop windings in the list with brush + ChopWindingListWithBrush( list, b ); + // if all windings are chopped away we're done + if ( !list->numWindings ) { + return; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( list->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( list->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + R_ChopWindingListWithTreeBrushes( list, node->children[1] ); + if ( !list->numWindings ) { + return; + } + node = node->children[0]; + } + } +} + +/* +============ +idCollisionModelManagerLocal::WindingOutsideBrushes + + Returns one winding which is not fully contained in brushes. + We always favor less polygons over a stitched world. + If the winding is partly contained and the contained pieces can be chopped off + without creating multiple winding fragments then the chopped winding is returned. +============ +*/ +idFixedWinding *idCollisionModelManagerLocal::WindingOutsideBrushes( idFixedWinding *w, const idPlane &plane, int contents, int primitiveNum, cm_node_t *headNode ) { + int i, windingLeft; + + cm_windingList->bounds.Clear(); + for ( i = 0; i < w->GetNumPoints(); i++ ) { + cm_windingList->bounds.AddPoint( (*w)[i].ToVec3() ); + } + + cm_windingList->origin = (cm_windingList->bounds[1] - cm_windingList->bounds[0]) * 0.5; + cm_windingList->radius = cm_windingList->origin.Length() + CHOP_EPSILON; + cm_windingList->origin = cm_windingList->bounds[0] + cm_windingList->origin; + cm_windingList->bounds[0] -= idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON ); + cm_windingList->bounds[1] += idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON ); + + cm_windingList->w[0] = *w; + cm_windingList->numWindings = 1; + cm_windingList->normal = plane.Normal(); + cm_windingList->contents = contents; + cm_windingList->primitiveNum = primitiveNum; + // + checkCount++; + R_ChopWindingListWithTreeBrushes( cm_windingList, headNode ); + // + if ( !cm_windingList->numWindings ) { + return NULL; + } + if ( cm_windingList->numWindings == 1 ) { + return &cm_windingList->w[0]; + } + // if not the world model + if ( numModels != 0 ) { + return w; + } + // check if winding fragments would be chopped away by the proc BSP tree + windingLeft = -1; + for ( i = 0; i < cm_windingList->numWindings; i++ ) { + if ( !ChoppedAwayByProcBSP( cm_windingList->w[i], plane, contents ) ) { + if ( windingLeft >= 0 ) { + return w; + } + windingLeft = i; + } + } + if ( windingLeft >= 0 ) { + return &cm_windingList->w[windingLeft]; + } + return NULL; +} + +/* +=============================================================================== + +Merging polygons + +=============================================================================== +*/ + +/* +============= +idCollisionModelManagerLocal::ReplacePolygons + + does not allow for a node to have multiple references to the same polygon +============= +*/ +void idCollisionModelManagerLocal::ReplacePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *p1, cm_polygon_t *p2, cm_polygon_t *newp ) { + cm_polygonRef_t *pref, *lastpref, *nextpref; + cm_polygon_t *p; + bool linked; + + while( 1 ) { + linked = false; + lastpref = NULL; + for ( pref = node->polygons; pref; pref = nextpref ) { + nextpref = pref->next; + // + p = pref->p; + // if this polygon reference should change + if ( p == p1 || p == p2 ) { + // if the new polygon is already linked at this node + if ( linked ) { + if ( lastpref ) { + lastpref->next = nextpref; + } + else { + node->polygons = nextpref; + } + FreePolygonReference( pref ); + model->numPolygonRefs--; + } + else { + pref->p = newp; + linked = true; + lastpref = pref; + } + } + else { + lastpref = pref; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( p1->bounds[0][node->planeType] > node->planeDist && p2->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( p1->bounds[1][node->planeType] < node->planeDist && p2->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + ReplacePolygons( model, node->children[1], p1, p2, newp ); + node = node->children[0]; + } + } +} + +/* +============= +idCollisionModelManagerLocal::TryMergePolygons +============= +*/ +#define CONTINUOUS_EPSILON 0.005f +#define NORMAL_EPSILON 0.01f + +cm_polygon_t *idCollisionModelManagerLocal::TryMergePolygons( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) { + int i, j, nexti, prevj; + int p1BeforeShare, p1AfterShare, p2BeforeShare, p2AfterShare; + int newEdges[CM_MAX_POLYGON_EDGES], newNumEdges; + int edgeNum, edgeNum1, edgeNum2, newEdgeNum1, newEdgeNum2; + cm_edge_t *edge; + cm_polygon_t *newp; + idVec3 delta, normal; + float dot; + bool keep1, keep2; + + if ( p1->material != p2->material ) { + return NULL; + } + if ( idMath::Fabs( p1->plane.Dist() - p2->plane.Dist() ) > NORMAL_EPSILON ) { + return NULL; + } + for ( i = 0; i < 3; i++ ) { + if ( idMath::Fabs( p1->plane.Normal()[i] - p2->plane.Normal()[i] ) > NORMAL_EPSILON ) { + return NULL; + } + if ( p1->bounds[0][i] > p2->bounds[1][i] ) { + return NULL; + } + if ( p1->bounds[1][i] < p2->bounds[0][i] ) { + return NULL; + } + } + // this allows for merging polygons with multiple shared edges + // polygons with multiple shared edges probably never occur tho ;) + p1BeforeShare = p1AfterShare = p2BeforeShare = p2AfterShare = -1; + for ( i = 0; i < p1->numEdges; i++ ) { + nexti = (i+1)%p1->numEdges; + for ( j = 0; j < p2->numEdges; j++ ) { + prevj = (j+p2->numEdges-1)%p2->numEdges; + // + if ( abs(p1->edges[i]) != abs(p2->edges[j]) ) { + // if the next edge of p1 and the previous edge of p2 are the same + if ( abs(p1->edges[nexti]) == abs(p2->edges[prevj]) ) { + // if both polygons don't use the edge in the same direction + if ( p1->edges[nexti] != p2->edges[prevj] ) { + p1BeforeShare = i; + p2AfterShare = j; + } + break; + } + } + // if both polygons don't use the edge in the same direction + else if ( p1->edges[i] != p2->edges[j] ) { + // if the next edge of p1 and the previous edge of p2 are not the same + if ( abs(p1->edges[nexti]) != abs(p2->edges[prevj]) ) { + p1AfterShare = nexti; + p2BeforeShare = prevj; + break; + } + } + } + } + if ( p1BeforeShare < 0 || p1AfterShare < 0 || p2BeforeShare < 0 || p2AfterShare < 0 ) { + return NULL; + } + + // check if the new polygon would still be convex + edgeNum = p1->edges[p1BeforeShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p; + normal = p1->plane.Normal().Cross( delta ); + normal.Normalize(); + + edgeNum = p2->edges[p2AfterShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p; + + dot = delta * normal; + if (dot < -CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep1 = (bool)(dot > CONTINUOUS_EPSILON); + + edgeNum = p2->edges[p2BeforeShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p; + normal = p1->plane.Normal().Cross( delta ); + normal.Normalize(); + + edgeNum = p1->edges[p1AfterShare]; + edge = model->edges + abs(edgeNum); + delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p - + model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p; + + dot = delta * normal; + if (dot < -CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep2 = (bool)(dot > CONTINUOUS_EPSILON); + + newEdgeNum1 = newEdgeNum2 = 0; + // get new edges if we need to replace colinear ones + if ( !keep1 ) { + edgeNum1 = p1->edges[p1BeforeShare]; + edgeNum2 = p2->edges[p2AfterShare]; + GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INTSIGNBITSET(edgeNum1)]].p, + model->vertices[model->edges[abs(edgeNum2)].vertexNum[INTSIGNBITNOTSET(edgeNum2)]].p, + &newEdgeNum1, -1 ); + if ( newEdgeNum1 == 0 ) { + keep1 = true; + } + } + if ( !keep2 ) { + edgeNum1 = p2->edges[p2BeforeShare]; + edgeNum2 = p1->edges[p1AfterShare]; + GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INTSIGNBITSET(edgeNum1)]].p, + model->vertices[model->edges[abs(edgeNum2)].vertexNum[INTSIGNBITNOTSET(edgeNum2)]].p, + &newEdgeNum2, -1 ); + if ( newEdgeNum2 == 0 ) { + keep2 = true; + } + } + // set edges for new polygon + newNumEdges = 0; + if ( !keep2 ) { + newEdges[newNumEdges++] = newEdgeNum2; + } + if ( p1AfterShare < p1BeforeShare ) { + for ( i = p1AfterShare + (!keep2); i <= p1BeforeShare - (!keep1); i++ ) { + newEdges[newNumEdges++] = p1->edges[i]; + } + } + else { + for ( i = p1AfterShare + (!keep2); i < p1->numEdges; i++ ) { + newEdges[newNumEdges++] = p1->edges[i]; + } + for ( i = 0; i <= p1BeforeShare - (!keep1); i++ ) { + newEdges[newNumEdges++] = p1->edges[i]; + } + } + if ( !keep1 ) { + newEdges[newNumEdges++] = newEdgeNum1; + } + if ( p2AfterShare < p2BeforeShare ) { + for ( i = p2AfterShare + (!keep1); i <= p2BeforeShare - (!keep2); i++ ) { + newEdges[newNumEdges++] = p2->edges[i]; + } + } + else { + for ( i = p2AfterShare + (!keep1); i < p2->numEdges; i++ ) { + newEdges[newNumEdges++] = p2->edges[i]; + } + for ( i = 0; i <= p2BeforeShare - (!keep2); i++ ) { + newEdges[newNumEdges++] = p2->edges[i]; + } + } + + newp = AllocPolygon( model, newNumEdges ); + memcpy( newp, p1, sizeof(cm_polygon_t) ); + memcpy( newp->edges, newEdges, newNumEdges * sizeof(int) ); + newp->numEdges = newNumEdges; + newp->checkcount = 0; + // increase usage count for the edges of this polygon + for ( i = 0; i < newp->numEdges; i++ ) { + if ( !keep1 && newp->edges[i] == newEdgeNum1 ) { + continue; + } + if ( !keep2 && newp->edges[i] == newEdgeNum2 ) { + continue; + } + model->edges[abs(newp->edges[i])].numUsers++; + } + // create new bounds from the merged polygons + newp->bounds = p1->bounds + p2->bounds; + + return newp; +} + +/* +============= +idCollisionModelManagerLocal::MergePolygonWithTreePolygons +============= +*/ +bool idCollisionModelManagerLocal::MergePolygonWithTreePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) { + int i; + cm_polygonRef_t *pref; + cm_polygon_t *p, *newp; + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // + if ( p == polygon ) { + continue; + } + // + newp = TryMergePolygons( model, polygon, p ); + // if polygons were merged + if ( newp ) { + model->numMergedPolys++; + // replace links to the merged polygons with links to the new polygon + ReplacePolygons( model, model->node, polygon, p, newp ); + // decrease usage count for edges of both merged polygons + for ( i = 0; i < polygon->numEdges; i++ ) { + model->edges[abs(polygon->edges[i])].numUsers--; + } + for ( i = 0; i < p->numEdges; i++ ) { + model->edges[abs(p->edges[i])].numUsers--; + } + // free merged polygons + FreePolygon( model, polygon ); + FreePolygon( model, p ); + + return true; + } + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( polygon->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( polygon->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + if ( MergePolygonWithTreePolygons( model, node->children[1], polygon ) ) { + return true; + } + node = node->children[0]; + } + } + return false; +} + +/* +============= +idCollisionModelManagerLocal::MergeTreePolygons + + try to merge any two polygons with the same surface flags and the same contents +============= +*/ +void idCollisionModelManagerLocal::MergeTreePolygons( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + bool merge; + + while( 1 ) { + do { + merge = false; + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + // try to merge this polygon with other polygons in the tree + if ( MergePolygonWithTreePolygons( model, model->node, p ) ) { + merge = true; + break; + } + } + } while (merge); + // if leaf node + if ( node->planeType == -1 ) { + break; + } + MergeTreePolygons( model, node->children[1] ); + node = node->children[0]; + } +} + +/* +=============================================================================== + +Find internal edges + +=============================================================================== +*/ + +/* + + if (two polygons have the same contents) + if (the normals of the two polygon planes face towards each other) + if (an edge is shared between the polygons) + if (the edge is not shared in the same direction) + then this is an internal edge + else + if (this edge is on the plane of the other polygon) + if (this edge if fully inside the winding of the other polygon) + then this edge is an internal edge + +*/ + +/* +============= +idCollisionModelManagerLocal::PointInsidePolygon +============= +*/ +bool idCollisionModelManagerLocal::PointInsidePolygon( cm_model_t *model, cm_polygon_t *p, idVec3 &v ) { + int i, edgeNum; + idVec3 *v1, *v2, dir1, dir2, vec; + cm_edge_t *edge; + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + // + v1 = &model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p; + v2 = &model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p; + dir1 = (*v2) - (*v1); + vec = v - (*v1); + dir2 = dir1.Cross( p->plane.Normal() ); + if ( vec * dir2 > VERTEX_EPSILON ) { + return false; + } + } + return true; +} + +/* +============= +idCollisionModelManagerLocal::FindInternalEdgesOnPolygon +============= +*/ +void idCollisionModelManagerLocal::FindInternalEdgesOnPolygon( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) { + int i, j, k, edgeNum; + cm_edge_t *edge; + idVec3 *v1, *v2, dir1, dir2; + float d; + + // bounds of polygons should overlap or touch + for ( i = 0; i < 3; i++ ) { + if ( p1->bounds[0][i] > p2->bounds[1][i] ) { + return; + } + if ( p1->bounds[1][i] < p2->bounds[0][i] ) { + return; + } + } + // + // FIXME: doubled geometry causes problems + // + for ( i = 0; i < p1->numEdges; i++ ) { + edgeNum = p1->edges[i]; + edge = model->edges + abs(edgeNum); + // if already an internal edge + if ( edge->internal ) { + continue; + } + // + v1 = &model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p; + v2 = &model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p; + // if either of the two vertices is outside the bounds of the other polygon + for ( k = 0; k < 3; k++ ) { + d = p2->bounds[1][k] + VERTEX_EPSILON; + if ( (*v1)[k] > d || (*v2)[k] > d ) { + break; + } + d = p2->bounds[0][k] - VERTEX_EPSILON; + if ( (*v1)[k] < d || (*v2)[k] < d ) { + break; + } + } + if ( k < 3 ) { + continue; + } + // + k = abs(edgeNum); + for ( j = 0; j < p2->numEdges; j++ ) { + if ( k == abs(p2->edges[j]) ) { + break; + } + } + // if the edge is shared between the two polygons + if ( j < p2->numEdges ) { + // if the edge is used by more than 2 polygons + if ( edge->numUsers > 2 ) { + // could still be internal but we'd have to test all polygons using the edge + continue; + } + // if the edge goes in the same direction for both polygons + if ( edgeNum == p2->edges[j] ) { + // the polygons can lay ontop of each other or one can obscure the other + continue; + } + } + // the edge was not shared + else { + // both vertices should be on the plane of the other polygon + d = p2->plane.Distance( *v1 ); + if ( idMath::Fabs(d) > VERTEX_EPSILON ) { + continue; + } + d = p2->plane.Distance( *v2 ); + if ( idMath::Fabs(d) > VERTEX_EPSILON ) { + continue; + } + } + // the two polygon plane normals should face towards each other + dir1 = (*v2) - (*v1); + dir2 = p1->plane.Normal().Cross( dir1 ); + if ( p2->plane.Normal() * dir2 < 0 ) { + //continue; + break; + } + // if the edge was not shared + if ( j >= p2->numEdges ) { + // both vertices of the edge should be inside the winding of the other polygon + if ( !PointInsidePolygon( model, p2, *v1 ) ) { + continue; + } + if ( !PointInsidePolygon( model, p2, *v2 ) ) { + continue; + } + } + // we got another internal edge + edge->internal = true; + model->numInternalEdges++; + } +} + +/* +============= +idCollisionModelManagerLocal::FindInternalPolygonEdges +============= +*/ +void idCollisionModelManagerLocal::FindInternalPolygonEdges( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + + if ( polygon->material->GetCullType() == CT_TWO_SIDED || polygon->material->ShouldCreateBackSides() ) { + return; + } + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // + // FIXME: use some sort of additional checkcount because currently + // polygons can be checked multiple times + // + // if the polygons don't have the same contents + if ( p->contents != polygon->contents ) { + continue; + } + if ( p == polygon ) { + continue; + } + FindInternalEdgesOnPolygon( model, polygon, p ); + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + if ( polygon->bounds[0][node->planeType] > node->planeDist ) { + node = node->children[0]; + } + else if ( polygon->bounds[1][node->planeType] < node->planeDist ) { + node = node->children[1]; + } + else { + FindInternalPolygonEdges( model, node->children[1], polygon ); + node = node->children[0]; + } + } +} + +/* +============= +idCollisionModelManagerLocal::FindContainedEdges +============= +*/ +void idCollisionModelManagerLocal::FindContainedEdges( cm_model_t *model, cm_polygon_t *p ) { + int i, edgeNum; + cm_edge_t *edge; + idFixedWinding w; + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + edge = model->edges + abs(edgeNum); + if ( edge->internal ) { + continue; + } + w.Clear(); + w += model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p; + w += model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p; + if ( ChoppedAwayByProcBSP( w, p->plane, p->contents ) ) { + edge->internal = true; + } + } +} + +/* +============= +idCollisionModelManagerLocal::FindInternalEdges +============= +*/ +void idCollisionModelManagerLocal::FindInternalEdges( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + + while( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + + FindInternalPolygonEdges( model, model->node, p ); + + //FindContainedEdges( model, p ); + } + // if leaf node + if ( node->planeType == -1 ) { + break; + } + FindInternalEdges( model, node->children[1] ); + node = node->children[0]; + } +} + +/* +=============================================================================== + +Spatial subdivision + +=============================================================================== +*/ + +/* +================ +CM_FindSplitter +================ +*/ +static int CM_FindSplitter( const cm_node_t *node, const idBounds &bounds, int *planeType, float *planeDist ) { + int i, j, type, axis[3], polyCount; + float dist, t, bestt, size[3]; + cm_brushRef_t *bref; + cm_polygonRef_t *pref; + const cm_node_t *n; + bool forceSplit = false; + + for ( i = 0; i < 3; i++ ) { + size[i] = bounds[1][i] - bounds[0][i]; + axis[i] = i; + } + // sort on largest axis + for ( i = 0; i < 2; i++ ) { + if ( size[i] < size[i+1] ) { + t = size[i]; + size[i] = size[i+1]; + size[i+1] = t; + j = axis[i]; + axis[i] = axis[i+1]; + axis[i+1] = j; + i = -1; + } + } + // if the node is too small for further splits + if ( size[0] < MIN_NODE_SIZE ) { + polyCount = 0; + for ( pref = node->polygons; pref; pref = pref->next) { + polyCount++; + } + if ( polyCount > MAX_NODE_POLYGONS ) { + forceSplit = true; + } + } + // find an axial aligned splitter + for ( i = 0; i < 3; i++ ) { + // start with the largest axis first + type = axis[i]; + bestt = size[i]; + // if the node is small anough in this axis direction + if ( !forceSplit && bestt < MIN_NODE_SIZE ) { + break; + } + // find an axial splitter from the brush bounding boxes + // also try brushes from parent nodes + for ( n = node; n; n = n->parent ) { + for ( bref = n->brushes; bref; bref = bref->next) { + for ( j = 0; j < 2; j++ ) { + dist = bref->b->bounds[j][type]; + // if the splitter is already used or outside node bounds + if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) { + continue; + } + // find the most centered splitter + t = abs((bounds[1][type] - dist) - (dist - bounds[0][type])); + if ( t < bestt ) { + bestt = t; + *planeType = type; + *planeDist = dist; + } + } + } + } + // find an axial splitter from the polygon bounding boxes + // also try brushes from parent nodes + for ( n = node; n; n = n->parent ) { + for ( pref = n->polygons; pref; pref = pref->next) { + for ( j = 0; j < 2; j++ ) { + dist = pref->p->bounds[j][type]; + // if the splitter is already used or outside node bounds + if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) { + continue; + } + // find the most centered splitter + t = abs((bounds[1][type] - dist) - (dist - bounds[0][type])); + if ( t < bestt ) { + bestt = t; + *planeType = type; + *planeDist = dist; + } + } + } + } + // if we found a splitter on the largest axis + if ( bestt < size[i] ) { + // if forced split due to lots of polygons + if ( forceSplit ) { + return true; + } + // don't create splitters real close to the bounds + if ( bounds[1][type] - *planeDist > (MIN_NODE_SIZE*0.5f) && + *planeDist - bounds[0][type] > (MIN_NODE_SIZE*0.5f) ) { + return true; + } + } + } + return false; +} + +/* +================ +CM_R_InsideAllChildren +================ +*/ +static int CM_R_InsideAllChildren( cm_node_t *node, const idBounds &bounds ) { + assert(node != NULL); + if ( node->planeType != -1 ) { + if ( bounds[0][node->planeType] >= node->planeDist ) { + return false; + } + if ( bounds[1][node->planeType] <= node->planeDist ) { + return false; + } + if ( !CM_R_InsideAllChildren( node->children[0], bounds ) ) { + return false; + } + if ( !CM_R_InsideAllChildren( node->children[1], bounds ) ) { + return false; + } + } + return true; +} + +/* +================ +idCollisionModelManagerLocal::R_FilterPolygonIntoTree +================ +*/ +void idCollisionModelManagerLocal::R_FilterPolygonIntoTree( cm_model_t *model, cm_node_t *node, cm_polygonRef_t *pref, cm_polygon_t *p ) { + assert(node != NULL); + while ( node->planeType != -1 ) { + if ( CM_R_InsideAllChildren( node, p->bounds ) ) { + break; + } + if ( p->bounds[0][node->planeType] >= node->planeDist ) { + node = node->children[0]; + } + else if ( p->bounds[1][node->planeType] <= node->planeDist ) { + node = node->children[1]; + } + else { + R_FilterPolygonIntoTree( model, node->children[1], NULL, p ); + node = node->children[0]; + } + } + if ( pref ) { + pref->next = node->polygons; + node->polygons = pref; + } + else { + AddPolygonToNode( model, node, p ); + } +} + +/* +================ +idCollisionModelManagerLocal::R_FilterBrushIntoTree +================ +*/ +void idCollisionModelManagerLocal::R_FilterBrushIntoTree( cm_model_t *model, cm_node_t *node, cm_brushRef_t *pref, cm_brush_t *b ) { + assert(node != NULL); + while ( node->planeType != -1 ) { + if ( CM_R_InsideAllChildren( node, b->bounds ) ) { + break; + } + if ( b->bounds[0][node->planeType] >= node->planeDist ) { + node = node->children[0]; + } + else if ( b->bounds[1][node->planeType] <= node->planeDist ) { + node = node->children[1]; + } + else { + R_FilterBrushIntoTree( model, node->children[1], NULL, b ); + node = node->children[0]; + } + } + if ( pref ) { + pref->next = node->brushes; + node->brushes = pref; + } + else { + AddBrushToNode( model, node, b ); + } +} + +/* +================ +idCollisionModelManagerLocal::R_CreateAxialBSPTree + + a brush or polygon is linked in the node closest to the root where + the brush or polygon is inside all children +================ +*/ +cm_node_t *idCollisionModelManagerLocal::R_CreateAxialBSPTree( cm_model_t *model, cm_node_t *node, const idBounds &bounds ) { + int planeType; + float planeDist; + cm_polygonRef_t *pref, *nextpref, *prevpref; + cm_brushRef_t *bref, *nextbref, *prevbref; + cm_node_t *frontNode, *backNode, *n; + idBounds frontBounds, backBounds; + + if ( !CM_FindSplitter( node, bounds, &planeType, &planeDist ) ) { + node->planeType = -1; + return node; + } + // create two child nodes + frontNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE ); + memset( frontNode, 0, sizeof(cm_node_t) ); + frontNode->parent = node; + frontNode->planeType = -1; + // + backNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE ); + memset( backNode, 0, sizeof(cm_node_t) ); + backNode->parent = node; + backNode->planeType = -1; + // + model->numNodes += 2; + // set front node bounds + frontBounds = bounds; + frontBounds[0][planeType] = planeDist; + // set back node bounds + backBounds = bounds; + backBounds[1][planeType] = planeDist; + // + node->planeType = planeType; + node->planeDist = planeDist; + node->children[0] = frontNode; + node->children[1] = backNode; + // filter polygons and brushes down the tree if necesary + for ( n = node; n; n = n->parent ) { + prevpref = NULL; + for ( pref = n->polygons; pref; pref = nextpref) { + nextpref = pref->next; + // if polygon is not inside all children + if ( !CM_R_InsideAllChildren( n, pref->p->bounds ) ) { + // filter polygon down the tree + R_FilterPolygonIntoTree( model, n, pref, pref->p ); + if ( prevpref ) { + prevpref->next = nextpref; + } + else { + n->polygons = nextpref; + } + } + else { + prevpref = pref; + } + } + prevbref = NULL; + for ( bref = n->brushes; bref; bref = nextbref) { + nextbref = bref->next; + // if brush is not inside all children + if ( !CM_R_InsideAllChildren( n, bref->b->bounds ) ) { + // filter brush down the tree + R_FilterBrushIntoTree( model, n, bref, bref->b ); + if ( prevbref ) { + prevbref->next = nextbref; + } + else { + n->brushes = nextbref; + } + } + else { + prevbref = bref; + } + } + } + R_CreateAxialBSPTree( model, frontNode, frontBounds ); + R_CreateAxialBSPTree( model, backNode, backBounds ); + return node; +} + +/* +int cm_numSavedPolygonLinks; +int cm_numSavedBrushLinks; + +int CM_R_CountChildren( cm_node_t *node ) { + if ( node->planeType == -1 ) { + return 0; + } + return 2 + CM_R_CountChildren(node->children[0]) + CM_R_CountChildren(node->children[1]); +} + +void CM_R_TestOptimisation( cm_node_t *node ) { + int polyCount, brushCount, numChildren; + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + if ( node->planeType == -1 ) { + return; + } + polyCount = 0; + for ( pref = node->polygons; pref; pref = pref->next) { + polyCount++; + } + brushCount = 0; + for ( bref = node->brushes; bref; bref = bref->next) { + brushCount++; + } + if ( polyCount || brushCount ) { + numChildren = CM_R_CountChildren( node ); + cm_numSavedPolygonLinks += (numChildren - 1) * polyCount; + cm_numSavedBrushLinks += (numChildren - 1) * brushCount; + } + CM_R_TestOptimisation( node->children[0] ); + CM_R_TestOptimisation( node->children[1] ); +} +*/ + +/* +================ +idCollisionModelManagerLocal::CreateAxialBSPTree +================ +*/ +cm_node_t *idCollisionModelManagerLocal::CreateAxialBSPTree( cm_model_t *model, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + idBounds bounds; + + // get head node bounds + bounds.Clear(); + for ( pref = node->polygons; pref; pref = pref->next) { + bounds += pref->p->bounds; + } + for ( bref = node->brushes; bref; bref = bref->next) { + bounds += bref->b->bounds; + } + + // create axial BSP tree from head node + node = R_CreateAxialBSPTree( model, node, bounds ); + + return node; +} + +/* +=============================================================================== + +Raw polygon and brush data + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::SetupHash +================ +*/ +void idCollisionModelManagerLocal::SetupHash( void ) { + if ( !cm_vertexHash ) { + cm_vertexHash = new idHashIndex( VERTEX_HASH_SIZE, 1024 ); + } + if ( !cm_edgeHash ) { + cm_edgeHash = new idHashIndex( EDGE_HASH_SIZE, 1024 ); + } + // init variables used during loading and optimization + if ( !cm_windingList ) { + cm_windingList = new cm_windingList_t; + } + if ( !cm_outList ) { + cm_outList = new cm_windingList_t; + } + if ( !cm_tmpList ) { + cm_tmpList = new cm_windingList_t; + } +} + +/* +================ +idCollisionModelManagerLocal::ShutdownHash +================ +*/ +void idCollisionModelManagerLocal::ShutdownHash( void ) { + delete cm_vertexHash; + cm_vertexHash = NULL; + delete cm_edgeHash; + cm_edgeHash = NULL; + delete cm_tmpList; + cm_tmpList = NULL; + delete cm_outList; + cm_outList = NULL; + delete cm_windingList; + cm_windingList = NULL; +} + +/* +================ +idCollisionModelManagerLocal::ClearHash +================ +*/ +void idCollisionModelManagerLocal::ClearHash( idBounds &bounds ) { + int i; + float f, max; + + cm_vertexHash->Clear(); + cm_edgeHash->Clear(); + + cm_modelBounds = bounds; + max = bounds[1].x - bounds[0].x; + f = bounds[1].y - bounds[0].y; + if ( f > max ) { + max = f; + } + cm_vertexShift = (float) max / VERTEX_HASH_BOXSIZE; + for ( i = 0; (1<> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1); + y = (((int)(vec[1] - cm_modelBounds[0].y + 0.5 )) >> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1); + + assert (x >= 0 && x < VERTEX_HASH_BOXSIZE && y >= 0 && y < VERTEX_HASH_BOXSIZE); + + return y * VERTEX_HASH_BOXSIZE + x; + */ + int x, y, z; + + x = (((int) (vec[0] - cm_modelBounds[0].x + 0.5)) + 2) >> 2; + y = (((int) (vec[1] - cm_modelBounds[0].y + 0.5)) + 2) >> 2; + z = (((int) (vec[2] - cm_modelBounds[0].z + 0.5)) + 2) >> 2; + return (x + y * VERTEX_HASH_BOXSIZE + z) & (VERTEX_HASH_SIZE-1); +} + +/* +================ +idCollisionModelManagerLocal::GetVertex +================ +*/ +int idCollisionModelManagerLocal::GetVertex( cm_model_t *model, const idVec3 &v, int *vertexNum ) { + int i, hashKey, vn; + idVec3 vert, *p; + + for (i = 0; i < 3; i++) { + if ( idMath::Fabs(v[i] - idMath::Rint(v[i])) < INTEGRAL_EPSILON ) + vert[i] = idMath::Rint(v[i]); + else + vert[i] = v[i]; + } + + hashKey = HashVec( vert ); + + for (vn = cm_vertexHash->First( hashKey ); vn >= 0; vn = cm_vertexHash->Next( vn ) ) { + p = &model->vertices[vn].p; + // first compare z-axis because hash is based on x-y plane + if (idMath::Fabs(vert[2] - (*p)[2]) < VERTEX_EPSILON && + idMath::Fabs(vert[0] - (*p)[0]) < VERTEX_EPSILON && + idMath::Fabs(vert[1] - (*p)[1]) < VERTEX_EPSILON ) + { + *vertexNum = vn; + return true; + } + } + + if ( model->numVertices >= model->maxVertices ) { + cm_vertex_t *oldVertices; + + // resize vertex array + model->maxVertices = (float) model->maxVertices * 1.5f + 1; + oldVertices = model->vertices; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) ); + memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) ); + Mem_Free( oldVertices ); + + cm_vertexHash->ResizeIndex( model->maxVertices ); + } + model->vertices[model->numVertices].p = vert; + model->vertices[model->numVertices].checkcount = 0; + *vertexNum = model->numVertices; + // add vertice to hash + cm_vertexHash->Add( hashKey, model->numVertices ); + // + model->numVertices++; + return false; +} + +/* +================ +idCollisionModelManagerLocal::GetEdge +================ +*/ +int idCollisionModelManagerLocal::GetEdge( cm_model_t *model, const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ) { + int v2num, hashKey, e; + int found, *vertexNum; + + // the first edge is a dummy + if ( model->numEdges == 0 ) { + model->numEdges = 1; + } + + if ( v1num != -1 ) { + found = 1; + } + else { + found = GetVertex( model, v1, &v1num ); + } + found &= GetVertex( model, v2, &v2num ); + // if both vertices are the same or snapped onto each other + if ( v1num == v2num ) { + *edgeNum = 0; + return true; + } + hashKey = cm_edgeHash->GenerateKey( v1num, v2num ); + // if both vertices where already stored + if (found) { + for (e = cm_edgeHash->First( hashKey ); e >= 0; e = cm_edgeHash->Next( e ) ) + { + // NOTE: only allow at most two users that use the edge in opposite direction + if ( model->edges[e].numUsers != 1 ) { + continue; + } + + vertexNum = model->edges[e].vertexNum; + if ( vertexNum[0] == v2num ) { + if ( vertexNum[1] == v1num ) { + // negative for a reversed edge + *edgeNum = -e; + break; + } + } + /* + else if ( vertexNum[0] == v1num ) { + if ( vertexNum[1] == v2num ) { + *edgeNum = e; + break; + } + } + */ + } + // if edge found in hash + if ( e >= 0 ) { + model->edges[e].numUsers++; + return true; + } + } + if ( model->numEdges >= model->maxEdges ) { + cm_edge_t *oldEdges; + + // resize edge array + model->maxEdges = (float) model->maxEdges * 1.5f + 1; + oldEdges = model->edges; + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) ); + memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) ); + Mem_Free( oldEdges ); + + cm_edgeHash->ResizeIndex( model->maxEdges ); + } + // setup edge + model->edges[model->numEdges].vertexNum[0] = v1num; + model->edges[model->numEdges].vertexNum[1] = v2num; + model->edges[model->numEdges].internal = false; + model->edges[model->numEdges].checkcount = 0; + model->edges[model->numEdges].numUsers = 1; // used by one polygon atm + model->edges[model->numEdges].normal.Zero(); + // + *edgeNum = model->numEdges; + // add edge to hash + cm_edgeHash->Add( hashKey, model->numEdges ); + + model->numEdges++; + + return false; +} + +/* +================ +idCollisionModelManagerLocal::CreatePolygon +================ +*/ +void idCollisionModelManagerLocal::CreatePolygon( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) { + int i, j, edgeNum, v1num; + int numPolyEdges, polyEdges[MAX_POINTS_ON_WINDING]; + idBounds bounds; + cm_polygon_t *p; + + // turn the winding into a sequence of edges + numPolyEdges = 0; + v1num = -1; // first vertex unknown + for ( i = 0, j = 1; i < w->GetNumPoints(); i++, j++ ) { + if ( j >= w->GetNumPoints() ) { + j = 0; + } + GetEdge( model, (*w)[i].ToVec3(), (*w)[j].ToVec3(), &polyEdges[numPolyEdges], v1num ); + if ( polyEdges[numPolyEdges] ) { + // last vertex of this edge is the first vertex of the next edge + v1num = model->edges[ abs(polyEdges[numPolyEdges]) ].vertexNum[ INTSIGNBITNOTSET(polyEdges[numPolyEdges]) ]; + // this edge is valid so keep it + numPolyEdges++; + } + } + // should have at least 3 edges + if ( numPolyEdges < 3 ) { + return; + } + // the polygon is invalid if some edge is found twice + for ( i = 0; i < numPolyEdges; i++ ) { + for ( j = i+1; j < numPolyEdges; j++ ) { + if ( abs(polyEdges[i]) == abs(polyEdges[j]) ) { + return; + } + } + } + // don't overflow max edges + if ( numPolyEdges > CM_MAX_POLYGON_EDGES ) { + common->Warning( "idCollisionModelManagerLocal::CreatePolygon: polygon has more than %d edges", numPolyEdges ); + numPolyEdges = CM_MAX_POLYGON_EDGES; + } + + w->GetBounds( bounds ); + + p = AllocPolygon( model, numPolyEdges ); + p->numEdges = numPolyEdges; + p->contents = material->GetContentFlags(); + p->material = material; + p->checkcount = 0; + p->plane = plane; + p->bounds = bounds; + for ( i = 0; i < numPolyEdges; i++ ) { + edgeNum = polyEdges[i]; + p->edges[i] = edgeNum; + } + R_FilterPolygonIntoTree( model, model->node, NULL, p ); +} + +/* +================ +idCollisionModelManagerLocal::PolygonFromWinding + + NOTE: for patches primitiveNum < 0 and abs(primitiveNum) is the real number +================ +*/ +void idCollisionModelManagerLocal::PolygonFromWinding( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) { + int contents; + + contents = material->GetContentFlags(); + + // if this polygon is part of the world model + if ( numModels == 0 ) { + // if the polygon is fully chopped away by the proc bsp tree + if ( ChoppedAwayByProcBSP( *w, plane, contents ) ) { + model->numRemovedPolys++; + return; + } + } + + // get one winding that is not or only partly contained in brushes + w = WindingOutsideBrushes( w, plane, contents, primitiveNum, model->node ); + + // if the polygon is fully contained within a brush + if ( !w ) { + model->numRemovedPolys++; + return; + } + + if ( w->IsHuge() ) { + common->Warning( "idCollisionModelManagerLocal::PolygonFromWinding: model %s primitive %d is degenerate", model->name.c_str(), abs(primitiveNum) ); + return; + } + + CreatePolygon( model, w, plane, material, primitiveNum ); + + if ( material->GetCullType() == CT_TWO_SIDED || material->ShouldCreateBackSides() ) { + w->ReverseSelf(); + CreatePolygon( model, w, -plane, material, primitiveNum ); + } +} + +/* +================= +idCollisionModelManagerLocal::CreatePatchPolygons +================= +*/ +void idCollisionModelManagerLocal::CreatePatchPolygons( cm_model_t *model, idSurface_Patch &mesh, const idMaterial *material, int primitiveNum ) { + int i, j; + float dot; + int v1, v2, v3, v4; + idFixedWinding w; + idPlane plane; + idVec3 d1, d2; + + for ( i = 0; i < mesh.GetWidth() - 1; i++ ) { + for ( j = 0; j < mesh.GetHeight() - 1; j++ ) { + + v1 = j * mesh.GetWidth() + i; + v2 = v1 + 1; + v3 = v1 + mesh.GetWidth() + 1; + v4 = v1 + mesh.GetWidth(); + + d1 = mesh[v2].xyz - mesh[v1].xyz; + d2 = mesh[v3].xyz - mesh[v1].xyz; + plane.SetNormal( d1.Cross(d2) ); + if ( plane.Normalize() != 0.0f ) { + plane.FitThroughPoint( mesh[v1].xyz ); + dot = plane.Distance( mesh[v4].xyz ); + // if we can turn it into a quad + if ( idMath::Fabs(dot) < 0.1f ) { + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v2].xyz; + w += mesh[v3].xyz; + w += mesh[v4].xyz; + + PolygonFromWinding( model, &w, plane, material, -primitiveNum ); + continue; + } + else { + // create one of the triangles + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v2].xyz; + w += mesh[v3].xyz; + + PolygonFromWinding( model, &w, plane, material, -primitiveNum ); + } + } + // create the other triangle + d1 = mesh[v3].xyz - mesh[v1].xyz; + d2 = mesh[v4].xyz - mesh[v1].xyz; + plane.SetNormal( d1.Cross(d2) ); + if ( plane.Normalize() != 0.0f ) { + plane.FitThroughPoint( mesh[v1].xyz ); + + w.Clear(); + w += mesh[v1].xyz; + w += mesh[v3].xyz; + w += mesh[v4].xyz; + + PolygonFromWinding( model, &w, plane, material, -primitiveNum ); + } + } + } +} + +/* +================= +CM_EstimateVertsAndEdges +================= +*/ +static void CM_EstimateVertsAndEdges( const idMapEntity *mapEnt, int *numVerts, int *numEdges ) { + int j, width, height; + + *numVerts = *numEdges = 0; + for ( j = 0; j < mapEnt->GetNumPrimitives(); j++ ) { + const idMapPrimitive *mapPrim; + mapPrim = mapEnt->GetPrimitive(j); + if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) { + // assume maximum tesselation without adding verts + width = static_cast(mapPrim)->GetWidth(); + height = static_cast(mapPrim)->GetHeight(); + *numVerts += width * height; + *numEdges += (width-1) * height + width * (height-1) + (width-1) * (height-1); + continue; + } + if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) { + // assume cylinder with a polygon with (numSides - 2) edges ontop and on the bottom + *numVerts += (static_cast(mapPrim)->GetNumSides() - 2) * 2; + *numEdges += (static_cast(mapPrim)->GetNumSides() - 2) * 3; + continue; + } + } +} + +/* +================= +idCollisionModelManagerLocal::ConverPatch +================= +*/ +void idCollisionModelManagerLocal::ConvertPatch( cm_model_t *model, const idMapPatch *patch, int primitiveNum ) { + const idMaterial *material; + idSurface_Patch *cp; + + material = declManager->FindMaterial( patch->GetMaterial() ); + if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + return; + } + + // copy the patch + cp = new idSurface_Patch( *patch ); + + // if the patch has an explicit number of subdivisions use it to avoid cracks + if ( patch->GetExplicitlySubdivided() ) { + cp->SubdivideExplicit( patch->GetHorzSubdivisions(), patch->GetVertSubdivisions(), false, true ); + } else { + cp->Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false ); + } + + // create collision polygons for the patch + CreatePatchPolygons( model, *cp, material, primitiveNum ); + + delete cp; +} + +/* +================ +idCollisionModelManagerLocal::ConvertBrushSides +================ +*/ +void idCollisionModelManagerLocal::ConvertBrushSides( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) { + int i, j; + idMapBrushSide *mapSide; + idFixedWinding w; + idPlane *planes; + const idMaterial *material; + + // fix degenerate planes + planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) ); + for ( i = 0; i < mapBrush->GetNumSides(); i++ ) { + planes[i] = mapBrush->GetSide(i)->GetPlane(); + planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON ); + } + + // create a collision polygon for each brush side + for ( i = 0; i < mapBrush->GetNumSides(); i++ ) { + mapSide = mapBrush->GetSide(i); + material = declManager->FindMaterial( mapSide->GetMaterial() ); + if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + continue; + } + w.BaseForPlane( -planes[i] ); + for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) { + if ( i == j ) { + continue; + } + w.ClipInPlace( -planes[j], 0 ); + } + + if ( w.GetNumPoints() ) { + PolygonFromWinding( model, &w, planes[i], material, primitiveNum ); + } + } +} + +/* +================ +idCollisionModelManagerLocal::ConvertBrush +================ +*/ +void idCollisionModelManagerLocal::ConvertBrush( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) { + int i, j, contents; + idBounds bounds; + idMapBrushSide *mapSide; + cm_brush_t *brush; + idPlane *planes; + idFixedWinding w; + const idMaterial *material = NULL; + + contents = 0; + bounds.Clear(); + + // fix degenerate planes + planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) ); + for ( i = 0; i < mapBrush->GetNumSides(); i++ ) { + planes[i] = mapBrush->GetSide(i)->GetPlane(); + planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON ); + } + + // we are only getting the bounds for the brush so there's no need + // to create a winding for the last brush side + for ( i = 0; i < mapBrush->GetNumSides() - 1; i++ ) { + mapSide = mapBrush->GetSide(i); + material = declManager->FindMaterial( mapSide->GetMaterial() ); + contents |= ( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ); + w.BaseForPlane( -planes[i] ); + for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) { + if ( i == j ) { + continue; + } + w.ClipInPlace( -planes[j], 0 ); + } + + for ( j = 0; j < w.GetNumPoints(); j++ ) { + bounds.AddPoint( w[j].ToVec3() ); + } + } + if ( !contents ) { + return; + } + // create brush for position test + brush = AllocBrush( model, mapBrush->GetNumSides() ); + brush->checkcount = 0; + brush->contents = contents; + brush->material = material; + brush->primitiveNum = primitiveNum; + brush->bounds = bounds; + brush->numPlanes = mapBrush->GetNumSides(); + for (i = 0; i < mapBrush->GetNumSides(); i++) { + brush->planes[i] = planes[i]; + } + AddBrushToNode( model, model->node, brush ); +} + +/* +================ +CM_CountNodeBrushes +================ +*/ +static int CM_CountNodeBrushes( const cm_node_t *node ) { + int count; + cm_brushRef_t *bref; + + count = 0; + for ( bref = node->brushes; bref; bref = bref->next ) { + count++; + } + return count; +} + +/* +================ +CM_R_GetModelBounds +================ +*/ +static void CM_R_GetNodeBounds( idBounds *bounds, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + bounds->AddPoint( pref->p->bounds[0] ); + bounds->AddPoint( pref->p->bounds[1] ); + } + for ( bref = node->brushes; bref; bref = bref->next ) { + bounds->AddPoint( bref->b->bounds[0] ); + bounds->AddPoint( bref->b->bounds[1] ); + } + if ( node->planeType == -1 ) { + break; + } + CM_R_GetNodeBounds( bounds, node->children[1] ); + node = node->children[0]; + } +} + +/* +================ +CM_GetNodeBounds +================ +*/ +void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node ) { + bounds->Clear(); + CM_R_GetNodeBounds( bounds, node ); + if ( bounds->IsCleared() ) { + bounds->Zero(); + } +} + +/* +================ +CM_GetNodeContents +================ +*/ +int CM_GetNodeContents( cm_node_t *node ) { + int contents; + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + contents = 0; + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + contents |= pref->p->contents; + } + for ( bref = node->brushes; bref; bref = bref->next ) { + contents |= bref->b->contents; + } + if ( node->planeType == -1 ) { + break; + } + contents |= CM_GetNodeContents( node->children[1] ); + node = node->children[0]; + } + return contents; +} + +/* +================== +idCollisionModelManagerLocal::RemapEdges +================== +*/ +void idCollisionModelManagerLocal::RemapEdges( cm_node_t *node, int *edgeRemap ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int i; + + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + // if we checked this polygon already + if ( p->checkcount == checkCount ) { + continue; + } + p->checkcount = checkCount; + for ( i = 0; i < p->numEdges; i++ ) { + if ( p->edges[i] < 0 ) { + p->edges[i] = -edgeRemap[ abs(p->edges[i]) ]; + } + else { + p->edges[i] = edgeRemap[ p->edges[i] ]; + } + } + } + if ( node->planeType == -1 ) { + break; + } + + RemapEdges( node->children[1], edgeRemap ); + node = node->children[0]; + } +} + +/* +================== +idCollisionModelManagerLocal::OptimizeArrays + + due to polygon merging and polygon removal the vertex and edge array + can have a lot of unused entries. +================== +*/ +void idCollisionModelManagerLocal::OptimizeArrays( cm_model_t *model ) { + int i, newNumVertices, newNumEdges, *v; + int *remap; + cm_edge_t *oldEdges; + cm_vertex_t *oldVertices; + + remap = (int *) Mem_ClearedAlloc( Max( model->numVertices, model->numEdges ) * sizeof( int ) ); + // get all used vertices + for ( i = 0; i < model->numEdges; i++ ) { + remap[ model->edges[i].vertexNum[0] ] = true; + remap[ model->edges[i].vertexNum[1] ] = true; + } + // create remap index and move vertices + newNumVertices = 0; + for ( i = 0; i < model->numVertices; i++ ) { + if ( remap[ i ] ) { + remap[ i ] = newNumVertices; + model->vertices[ newNumVertices ] = model->vertices[ i ]; + newNumVertices++; + } + } + model->numVertices = newNumVertices; + // change edge vertex indexes + for ( i = 1; i < model->numEdges; i++ ) { + v = model->edges[i].vertexNum; + v[0] = remap[ v[0] ]; + v[1] = remap[ v[1] ]; + } + + // create remap index and move edges + newNumEdges = 1; + for ( i = 1; i < model->numEdges; i++ ) { + // if the edge is used + if ( model->edges[ i ].numUsers ) { + remap[ i ] = newNumEdges; + model->edges[ newNumEdges ] = model->edges[ i ]; + newNumEdges++; + } + } + // change polygon edge indexes + checkCount++; + RemapEdges( model->node, remap ); + model->numEdges = newNumEdges; + + Mem_Free( remap ); + + // realloc vertices + oldVertices = model->vertices; + if ( oldVertices ) { + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->numVertices * sizeof(cm_vertex_t) ); + memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) ); + Mem_Free( oldVertices ); + } + + // realloc edges + oldEdges = model->edges; + if ( oldEdges ) { + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->numEdges * sizeof(cm_edge_t) ); + memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) ); + Mem_Free( oldEdges ); + } +} + +/* +================ +idCollisionModelManagerLocal::FinishModel +================ +*/ +void idCollisionModelManagerLocal::FinishModel( cm_model_t *model ) { + // try to merge polygons + checkCount++; + MergeTreePolygons( model, model->node ); + // find internal edges (no mesh can ever collide with internal edges) + checkCount++; + FindInternalEdges( model, model->node ); + // calculate edge normals + checkCount++; + CalculateEdgeNormals( model, model->node ); + + //common->Printf( "%s vertex hash spread is %d\n", model->name.c_str(), cm_vertexHash->GetSpread() ); + //common->Printf( "%s edge hash spread is %d\n", model->name.c_str(), cm_edgeHash->GetSpread() ); + + // remove all unused vertices and edges + OptimizeArrays( model ); + // get model bounds from brush and polygon bounds + CM_GetNodeBounds( &model->bounds, model->node ); + // get model contents + model->contents = CM_GetNodeContents( model->node ); + // total memory used by this model + model->usedMemory = model->numVertices * sizeof(cm_vertex_t) + + model->numEdges * sizeof(cm_edge_t) + + model->polygonMemory + + model->brushMemory + + model->numNodes * sizeof(cm_node_t) + + model->numPolygonRefs * sizeof(cm_polygonRef_t) + + model->numBrushRefs * sizeof(cm_brushRef_t); +} + +/* +================ +idCollisionModelManagerLocal::LoadRenderModel +================ +*/ +cm_model_t *idCollisionModelManagerLocal::LoadRenderModel( const char *fileName ) { + int i, j; + idRenderModel *renderModel; + const modelSurface_t *surf; + idFixedWinding w; + cm_node_t *node; + cm_model_t *model; + idPlane plane; + idBounds bounds; + bool collisionSurface; + idStr extension; + + // only load ASE and LWO models + idStr( fileName ).ExtractFileExtension( extension ); + if ( ( extension.Icmp( "ase" ) != 0 ) && ( extension.Icmp( "lwo" ) != 0 ) && ( extension.Icmp( "ma" ) != 0 ) ) { + return NULL; + } + + if ( !renderModelManager->CheckModel( fileName ) ) { + return NULL; + } + + renderModel = renderModelManager->FindModel( fileName ); + + model = AllocModel(); + model->name = fileName; + node = AllocNode( model, NODE_BLOCK_SIZE_SMALL ); + node->planeType = -1; + model->node = node; + + model->maxVertices = 0; + model->numVertices = 0; + model->maxEdges = 0; + model->numEdges = 0; + + bounds = renderModel->Bounds( NULL ); + + collisionSurface = false; + for ( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + if ( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) { + collisionSurface = true; + } + } + + for ( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + // if this surface has no contents + if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + continue; + } + // if the model has a collision surface and this surface is not a collision surface + if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) { + continue; + } + // get max verts and edges + model->maxVertices += surf->geometry->numVerts; + model->maxEdges += surf->geometry->numIndexes; + } + + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) ); + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) ); + + // setup hash to speed up finding shared vertices and edges + SetupHash(); + + cm_vertexHash->ResizeIndex( model->maxVertices ); + cm_edgeHash->ResizeIndex( model->maxEdges ); + + ClearHash( bounds ); + + for ( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + // if this surface has no contents + if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) { + continue; + } + // if the model has a collision surface and this surface is not a collision surface + if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) { + continue; + } + + for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) { + w.Clear(); + w += surf->geometry->verts[ surf->geometry->indexes[ j + 2 ] ].xyz; + w += surf->geometry->verts[ surf->geometry->indexes[ j + 1 ] ].xyz; + w += surf->geometry->verts[ surf->geometry->indexes[ j + 0 ] ].xyz; + w.GetPlane( plane ); + plane = -plane; + PolygonFromWinding( model, &w, plane, surf->shader, 1 ); + } + } + + // create a BSP tree for the model + model->node = CreateAxialBSPTree( model, model->node ); + + model->isConvex = false; + + FinishModel( model ); + + // shutdown the hash + ShutdownHash(); + + common->Printf( "loaded collision model %s\n", model->name.c_str() ); + + return model; +} + +/* +================ +idCollisionModelManagerLocal::CollisionModelForMapEntity +================ +*/ +cm_model_t *idCollisionModelManagerLocal::CollisionModelForMapEntity( const idMapEntity *mapEnt ) { + cm_model_t *model; + idBounds bounds; + const char *name; + int i, brushCount; + + // if the entity has no primitives + if ( mapEnt->GetNumPrimitives() < 1 ) { + return NULL; + } + + // get a name for the collision model + mapEnt->epairs.GetString( "model", "", &name ); + if ( !name[0] ) { + mapEnt->epairs.GetString( "name", "", &name ); + if ( !name[0] ) { + if ( !numModels ) { + // first model is always the world + name = "worldMap"; + } + else { + name = "unnamed inline model"; + } + } + } + + model = AllocModel(); + model->node = AllocNode( model, NODE_BLOCK_SIZE_SMALL ); + + CM_EstimateVertsAndEdges( mapEnt, &model->maxVertices, &model->maxEdges ); + model->numVertices = 0; + model->numEdges = 0; + model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) ); + model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) ); + + cm_vertexHash->ResizeIndex( model->maxVertices ); + cm_edgeHash->ResizeIndex( model->maxEdges ); + + model->name = name; + model->isConvex = false; + + // convert brushes + for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) { + idMapPrimitive *mapPrim; + + mapPrim = mapEnt->GetPrimitive(i); + if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) { + ConvertBrush( model, static_cast(mapPrim), i ); + continue; + } + } + + // create an axial bsp tree for the model if it has more than just a bunch brushes + brushCount = CM_CountNodeBrushes( model->node ); + if ( brushCount > 4 ) { + model->node = CreateAxialBSPTree( model, model->node ); + } else { + model->node->planeType = -1; + } + + // get bounds for hash + if ( brushCount ) { + CM_GetNodeBounds( &bounds, model->node ); + } else { + bounds[0].Set( -256, -256, -256 ); + bounds[1].Set( 256, 256, 256 ); + } + + // different models do not share edges and vertices with each other, so clear the hash + ClearHash( bounds ); + + // create polygons from patches and brushes + for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) { + idMapPrimitive *mapPrim; + + mapPrim = mapEnt->GetPrimitive(i); + if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) { + ConvertPatch( model, static_cast(mapPrim), i ); + continue; + } + if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) { + ConvertBrushSides( model, static_cast(mapPrim), i ); + continue; + } + } + + FinishModel( model ); + + return model; +} + +/* +================ +idCollisionModelManagerLocal::FindModel +================ +*/ +cmHandle_t idCollisionModelManagerLocal::FindModel( const char *name ) { + int i; + + // check if this model is already loaded + for ( i = 0; i < numModels; i++ ) { + if ( !models[i]->name.Icmp( name ) ) { + break; + } + } + // if the model is already loaded + if ( i < numModels ) { + return i; + } + return -1; +} + +/* +================== +idCollisionModelManagerLocal::PrintModelInfo +================== +*/ +void idCollisionModelManagerLocal::PrintModelInfo( const cm_model_t *model ) { + common->Printf( "%6i vertices (%i KB)\n", model->numVertices, (model->numVertices * sizeof(cm_vertex_t))>>10 ); + common->Printf( "%6i edges (%i KB)\n", model->numEdges, (model->numEdges * sizeof(cm_edge_t))>>10 ); + common->Printf( "%6i polygons (%i KB)\n", model->numPolygons, model->polygonMemory>>10 ); + common->Printf( "%6i brushes (%i KB)\n", model->numBrushes, model->brushMemory>>10 ); + common->Printf( "%6i nodes (%i KB)\n", model->numNodes, (model->numNodes * sizeof(cm_node_t))>>10 ); + common->Printf( "%6i polygon refs (%i KB)\n", model->numPolygonRefs, (model->numPolygonRefs * sizeof(cm_polygonRef_t))>>10 ); + common->Printf( "%6i brush refs (%i KB)\n", model->numBrushRefs, (model->numBrushRefs * sizeof(cm_brushRef_t))>>10 ); + common->Printf( "%6i internal edges\n", model->numInternalEdges ); + common->Printf( "%6i sharp edges\n", model->numSharpEdges ); + common->Printf( "%6i contained polygons removed\n", model->numRemovedPolys ); + common->Printf( "%6i polygons merged\n", model->numMergedPolys ); + common->Printf( "%6i KB total memory used\n", model->usedMemory>>10 ); +} + +/* +================ +idCollisionModelManagerLocal::AccumulateModelInfo +================ +*/ +void idCollisionModelManagerLocal::AccumulateModelInfo( cm_model_t *model ) { + int i; + + memset( model, 0, sizeof( *model ) ); + // accumulate statistics of all loaded models + for ( i = 0; i < numModels; i++ ) { + model->numVertices += models[i]->numVertices; + model->numEdges += models[i]->numEdges; + model->numPolygons += models[i]->numPolygons; + model->polygonMemory += models[i]->polygonMemory; + model->numBrushes += models[i]->numBrushes; + model->brushMemory += models[i]->brushMemory; + model->numNodes += models[i]->numNodes; + model->numBrushRefs += models[i]->numBrushRefs; + model->numPolygonRefs += models[i]->numPolygonRefs; + model->numInternalEdges += models[i]->numInternalEdges; + model->numSharpEdges += models[i]->numSharpEdges; + model->numRemovedPolys += models[i]->numRemovedPolys; + model->numMergedPolys += models[i]->numMergedPolys; + model->usedMemory += models[i]->usedMemory; + } +} + +/* +================ +idCollisionModelManagerLocal::ModelInfo +================ +*/ +void idCollisionModelManagerLocal::ModelInfo( cmHandle_t model ) { + cm_model_t modelInfo; + + if ( model == -1 ) { + AccumulateModelInfo( &modelInfo ); + PrintModelInfo( &modelInfo ); + return; + } + if ( model < 0 || model > MAX_SUBMODELS || model > maxModels ) { + common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model handle\n" ); + return; + } + if ( !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model\n" ); + return; + } + + PrintModelInfo( models[model] ); +} + +/* +================ +idCollisionModelManagerLocal::ListModels +================ +*/ +void idCollisionModelManagerLocal::ListModels( void ) { + int i, totalMemory; + + totalMemory = 0; + for ( i = 0; i < numModels; i++ ) { + common->Printf( "%4d: %5d KB %s\n", i, (models[i]->usedMemory>>10), models[i]->name.c_str() ); + totalMemory += models[i]->usedMemory; + } + common->Printf( "%4d KB in %d models\n", (totalMemory>>10), numModels ); +} + +/* +================ +idCollisionModelManagerLocal::BuildModels +================ +*/ +void idCollisionModelManagerLocal::BuildModels( const idMapFile *mapFile ) { + int i; + const idMapEntity *mapEnt; + + idTimer timer; + timer.Start(); + + if ( !LoadCollisionModelFile( mapFile->GetName(), mapFile->GetGeometryCRC() ) ) { + + if ( !mapFile->GetNumEntities() ) { + return; + } + + // load the .proc file bsp for data optimisation + LoadProcBSP( mapFile->GetName() ); + + // convert brushes and patches to collision data + for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { + mapEnt = mapFile->GetEntity(i); + + if ( numModels >= MAX_SUBMODELS ) { + common->Error( "idCollisionModelManagerLocal::BuildModels: more than %d collision models", MAX_SUBMODELS ); + break; + } + models[numModels] = CollisionModelForMapEntity( mapEnt ); + if ( models[ numModels] ) { + numModels++; + } + } + + // free the proc bsp which is only used for data optimization + Mem_Free( procNodes ); + procNodes = NULL; + + // write the collision models to a file + WriteCollisionModelsToFile( mapFile->GetName(), 0, numModels, mapFile->GetGeometryCRC() ); + } + + timer.Stop(); + + // print statistics on collision data + cm_model_t model; + AccumulateModelInfo( &model ); + common->Printf( "collision data:\n" ); + common->Printf( "%6i models\n", numModels ); + PrintModelInfo( &model ); + common->Printf( "%.0f msec to load collision data.\n", timer.Milliseconds() ); +} + + +/* +================ +idCollisionModelManagerLocal::LoadMap +================ +*/ +void idCollisionModelManagerLocal::LoadMap( const idMapFile *mapFile ) { + + if ( mapFile == NULL ) { + common->Error( "idCollisionModelManagerLocal::LoadMap: NULL mapFile" ); + } + + // check whether we can keep the current collision map based on the mapName and mapFileTime + if ( loaded ) { + if ( mapName.Icmp( mapFile->GetName() ) == 0 ) { + if ( mapFile->GetFileTime() == mapFileTime ) { + common->DPrintf( "Using loaded version\n" ); + return; + } + common->DPrintf( "Reloading modified map\n" ); + } + FreeMap(); + } + + // clear the collision map + Clear(); + + // models + maxModels = MAX_SUBMODELS; + numModels = 0; + models = (cm_model_t **) Mem_ClearedAlloc( (maxModels+1) * sizeof(cm_model_t *) ); + + // setup hash to speed up finding shared vertices and edges + SetupHash(); + + // setup trace model structure + SetupTrmModelStructure(); + + // build collision models + BuildModels( mapFile ); + + // save name and time stamp + mapName = mapFile->GetName(); + mapFileTime = mapFile->GetFileTime(); + loaded = true; + + // shutdown the hash + ShutdownHash(); +} + +/* +=================== +idCollisionModelManagerLocal::GetModelName +=================== +*/ +const char *idCollisionModelManagerLocal::GetModelName( cmHandle_t model ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" ); + return ""; + } + return models[model]->name.c_str(); +} + +/* +=================== +idCollisionModelManagerLocal::GetModelBounds +=================== +*/ +bool idCollisionModelManagerLocal::GetModelBounds( cmHandle_t model, idBounds &bounds ) const { + + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" ); + return false; + } + + bounds = models[model]->bounds; + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelContents +=================== +*/ +bool idCollisionModelManagerLocal::GetModelContents( cmHandle_t model, int &contents ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelContents: invalid model handle\n" ); + return false; + } + + contents = models[model]->contents; + + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelVertex +=================== +*/ +bool idCollisionModelManagerLocal::GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid model handle\n" ); + return false; + } + + if ( vertexNum < 0 || vertexNum >= models[model]->numVertices ) { + common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid vertex number\n" ); + return false; + } + + vertex = models[model]->vertices[vertexNum].p; + + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelEdge +=================== +*/ +bool idCollisionModelManagerLocal::GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const { + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid model handle\n" ); + return false; + } + + edgeNum = abs( edgeNum ); + if ( edgeNum >= models[model]->numEdges ) { + common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid edge number\n" ); + return false; + } + + start = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[0]].p; + end = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[1]].p; + + return true; +} + +/* +=================== +idCollisionModelManagerLocal::GetModelPolygon +=================== +*/ +bool idCollisionModelManagerLocal::GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const { + int i, edgeNum; + cm_polygon_t *poly; + + if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) { + common->Printf( "idCollisionModelManagerLocal::GetModelPolygon: invalid model handle\n" ); + return false; + } + + poly = *reinterpret_cast(&polygonNum); + winding.Clear(); + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + winding += models[model]->vertices[ models[model]->edges[abs(edgeNum)].vertexNum[INTSIGNBITSET(edgeNum)] ].p; + } + + return true; +} + +/* +================== +idCollisionModelManagerLocal::LoadModel +================== +*/ +cmHandle_t idCollisionModelManagerLocal::LoadModel( const char *modelName, const bool precache ) { + int handle; + + handle = FindModel( modelName ); + if ( handle >= 0 ) { + return handle; + } + + if ( numModels >= MAX_SUBMODELS ) { + common->Error( "idCollisionModelManagerLocal::LoadModel: no free slots\n" ); + return 0; + } + + // try to load a .cm file + if ( LoadCollisionModelFile( modelName, 0 ) ) { + handle = FindModel( modelName ); + if ( handle >= 0 ) { + return handle; + } else { + common->Warning( "idCollisionModelManagerLocal::LoadModel: collision file for '%s' contains different model", modelName ); + } + } + + // if only precaching .cm files do not waste memory converting render models + if ( precache ) { + return 0; + } + + // try to load a .ASE or .LWO model and convert it to a collision model + models[numModels] = LoadRenderModel( modelName ); + if ( models[numModels] != NULL ) { + numModels++; + return ( numModels - 1 ); + } + + return 0; +} + +/* +================== +idCollisionModelManagerLocal::TrmFromModel_r +================== +*/ +bool idCollisionModelManagerLocal::TrmFromModel_r( idTraceModel &trm, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_polygon_t *p; + int i; + + while ( 1 ) { + for ( pref = node->polygons; pref; pref = pref->next ) { + p = pref->p; + + if ( p->checkcount == checkCount ) { + continue; + } + + p->checkcount = checkCount; + + if ( trm.numPolys >= MAX_TRACEMODEL_POLYS ) { + return false; + } + // copy polygon properties + trm.polys[ trm.numPolys ].bounds = p->bounds; + trm.polys[ trm.numPolys ].normal = p->plane.Normal(); + trm.polys[ trm.numPolys ].dist = p->plane.Dist(); + trm.polys[ trm.numPolys ].numEdges = p->numEdges; + // copy edge index + for ( i = 0; i < p->numEdges; i++ ) { + trm.polys[ trm.numPolys ].edges[ i ] = p->edges[ i ]; + } + trm.numPolys++; + } + if ( node->planeType == -1 ) { + break; + } + if ( !TrmFromModel_r( trm, node->children[1] ) ) { + return false; + } + node = node->children[0]; + } + return true; +} + +/* +================== +idCollisionModelManagerLocal::TrmFromModel + + NOTE: polygon merging can merge colinear edges and as such might cause dangling edges. +================== +*/ +bool idCollisionModelManagerLocal::TrmFromModel( const cm_model_t *model, idTraceModel &trm ) { + int i, j, numEdgeUsers[MAX_TRACEMODEL_EDGES+1]; + + // if the model has too many vertices to fit in a trace model + if ( model->numVertices > MAX_TRACEMODEL_VERTS ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many vertices.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + + // plus one because the collision model accounts for the first unused edge + if ( model->numEdges > MAX_TRACEMODEL_EDGES+1 ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many edges.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + + trm.type = TRM_CUSTOM; + trm.numVerts = 0; + trm.numEdges = 1; + trm.numPolys = 0; + trm.bounds.Clear(); + + // copy polygons + checkCount++; + if ( !TrmFromModel_r( trm, model->node ) ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many polygons.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + + // copy vertices + for ( i = 0; i < model->numVertices; i++ ) { + trm.verts[ i ] = model->vertices[ i ].p; + trm.bounds.AddPoint( trm.verts[ i ] ); + } + trm.numVerts = model->numVertices; + + // copy edges + for ( i = 0; i < model->numEdges; i++ ) { + trm.edges[ i ].v[0] = model->edges[ i ].vertexNum[0]; + trm.edges[ i ].v[1] = model->edges[ i ].vertexNum[1]; + } + // minus one because the collision model accounts for the first unused edge + trm.numEdges = model->numEdges - 1; + + // each edge should be used exactly twice + memset( numEdgeUsers, 0, sizeof(numEdgeUsers) ); + for ( i = 0; i < trm.numPolys; i++ ) { + for ( j = 0; j < trm.polys[i].numEdges; j++ ) { + numEdgeUsers[ abs( trm.polys[i].edges[j] ) ]++; + } + } + for ( i = 1; i <= trm.numEdges; i++ ) { + if ( numEdgeUsers[i] != 2 ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has dangling edges, the model has to be an enclosed hull.\n", model->name.c_str() ); + PrintModelInfo( model ); + return false; + } + } + + // assume convex + trm.isConvex = true; + // check if really convex + for ( i = 0; i < trm.numPolys; i++ ) { + // to be convex no vertices should be in front of any polygon plane + for ( j = 0; j < trm.numVerts; j++ ) { + if ( trm.polys[ i ].normal * trm.verts[ j ] - trm.polys[ i ].dist > 0.01f ) { + trm.isConvex = false; + break; + } + } + if ( j < trm.numVerts ) { + break; + } + } + + // offset to center of model + trm.offset = trm.bounds.GetCenter(); + + trm.GenerateEdgeNormals(); + + return true; +} + +/* +================== +idCollisionModelManagerLocal::TrmFromModel +================== +*/ +bool idCollisionModelManagerLocal::TrmFromModel( const char *modelName, idTraceModel &trm ) { + cmHandle_t handle; + + handle = LoadModel( modelName, false ); + if ( !handle ) { + common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s not found.\n", modelName ); + return false; + } + + return TrmFromModel( models[ handle ], trm ); +} diff --git a/src/cm/collisionmodel_rotate.cpp b/src/cm/collisionmodel_rotate.cpp new file mode 100644 index 0000000..0849e57 --- /dev/null +++ b/src/cm/collisionmodel_rotate.cpp @@ -0,0 +1,1745 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +#if !defined( Q4_CM_LEGACY_SEED ) + +int idCollisionModelManagerLocal::CollisionBetweenEdgeBounds( cm_traceWork_t *tw, + const idVec3 &va, const idVec3 &vb, const idVec3 &vc, const idVec3 &vd, + float tanHalfAngle, idVec3 &collisionPoint, idVec3 &collisionNormal ) { + return 0; +} + +void idCollisionModelManagerLocal::RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ) {} + +int idCollisionModelManagerLocal::RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, + const idVec3 &point, const idVec3 &endPoint, const idPlane &plane, float angle, + const idVec3 &origin, float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir ) { + return 0; +} + +void idCollisionModelManagerLocal::RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *vertex, int vertexNum ) {} +void idCollisionModelManagerLocal::RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmPoly, cm_polygon_t *poly, cm_vertex_t *vertex, idVec3 &rotationOrigin ) {} +bool idCollisionModelManagerLocal::RotateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *polygon ) { return false; } + +bool CM_RayIntersectsConvexTraceModel( const cm_traceWork_t *tw, const idVec3 &start, const idVec3 &dir ) { + return false; +} + +void idCollisionModelManagerLocal::Rotation180( trace_t *results, const idVec3 &rotationOrigin, + const idVec3 &axis, float startAngle, float endAngle, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idRotation rotation( rotationOrigin, axis, endAngle - startAngle ); + Rotation( results, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +void idCollisionModelManagerLocal::Rotation( trace_t *results, const idVec3 &start, + const idRotation &rotation, const idTraceModel *trm, const idMat3 &trmAxis, + int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + if ( results == NULL ) return; + memset( &results->c, 0, sizeof( results->c ) ); + results->fraction = 1.0f; + results->endpos = rotation * start; + results->endAxis = trmAxis * rotation.ToMat3(); + + // Preserve solid-start behavior until the rotational edge solver is fully + // transcribed: position contents are exact and prevent tunnelling out of a + // model when a rotation begins embedded. + if ( model != NULL && Contents( start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ) & contentMask ) { + results->fraction = 0.0f; + results->endpos = start; + results->endAxis = trmAxis; + } +} + +#else + +/* +=============================================================================== + +Collision detection for rotational motion + +=============================================================================== +*/ + +// epsilon for round-off errors in epsilon calculations +#define CM_PL_RANGE_EPSILON 1e-4f +// if the collision point is this close to the rotation axis it is not considered a collision +#define ROTATION_AXIS_EPSILON (CM_CLIP_EPSILON*0.25f) + + +/* +================ +CM_RotatePoint + + rotates a point about an arbitrary axis using the tangent of half the rotation angle +================ +*/ +void CM_RotatePoint( idVec3 &point, const idVec3 &origin, const idVec3 &axis, const float tanHalfAngle ) { + double d, t, s, c; + idVec3 proj, v1, v2; + + point -= origin; + proj = axis * ( point * axis ); + v1 = point - proj; + v2 = axis.Cross( v1 ); + + // r = tan( a / 2 ); + // sin(a) = 2*r/(1+r*r); + // cos(a) = (1-r*r)/(1+r*r); + t = tanHalfAngle * tanHalfAngle; + d = 1.0f / ( 1.0f + t ); + s = 2.0f * tanHalfAngle * d; + c = ( 1.0f - t ) * d; + + point = v1 * c - v2 * s + proj + origin; +} + +/* +================ +CM_RotateEdge + + rotates an edge about an arbitrary axis using the tangent of half the rotation angle +================ +*/ +void CM_RotateEdge( idVec3 &start, idVec3 &end, const idVec3 &origin, const idVec3 &axis, const float tanHalfAngle ) { + double d, t, s, c; + idVec3 proj, v1, v2; + + // r = tan( a / 2 ); + // sin(a) = 2*r/(1+r*r); + // cos(a) = (1-r*r)/(1+r*r); + t = tanHalfAngle * tanHalfAngle; + d = 1.0f / ( 1.0f + t ); + s = 2.0f * tanHalfAngle * d; + c = ( 1.0f - t ) * d; + + start -= origin; + proj = axis * ( start * axis ); + v1 = start - proj; + v2 = axis.Cross( v1 ); + start = v1 * c - v2 * s + proj + origin; + + end -= origin; + proj = axis * ( end * axis ); + v1 = end - proj; + v2 = axis.Cross( v1 ); + end = v1 * c - v2 * s + proj + origin; +} + +/* +================ +idCollisionModelManagerLocal::CollisionBetweenEdgeBounds + + verifies if the collision of two edges occurs between the edge bounds + also calculates the collision point and collision plane normal if the collision occurs between the bounds +================ +*/ +int idCollisionModelManagerLocal::CollisionBetweenEdgeBounds( cm_traceWork_t *tw, const idVec3 &va, const idVec3 &vb, + const idVec3 &vc, const idVec3 &vd, float tanHalfAngle, + idVec3 &collisionPoint, idVec3 &collisionNormal ) { + float d1, d2, d; + idVec3 at, bt, dir, dir1, dir2; + idPluecker pl1, pl2; + + at = va; + bt = vb; + if ( tanHalfAngle != 0.0f ) { + CM_RotateEdge( at, bt, tw->origin, tw->axis, tanHalfAngle ); + } + + dir1 = (at - tw->origin).Cross( tw->axis ); + dir2 = (bt - tw->origin).Cross( tw->axis ); + if ( dir1 * dir1 > dir2 * dir2 ) { + dir = dir1; + } + else { + dir = dir2; + } + if ( tw->angle < 0.0f ) { + dir = -dir; + } + + pl1.FromLine( at, bt ); + pl2.FromRay( vc, dir ); + d1 = pl1.PermutedInnerProduct( pl2 ); + pl2.FromRay( vd, dir ); + d2 = pl1.PermutedInnerProduct( pl2 ); + if ( ( d1 > 0.0f && d2 > 0.0f ) || ( d1 < 0.0f && d2 < 0.0f ) ) { + return false; + } + + pl1.FromLine( vc, vd ); + pl2.FromRay( at, dir ); + d1 = pl1.PermutedInnerProduct( pl2 ); + pl2.FromRay( bt, dir ); + d2 = pl1.PermutedInnerProduct( pl2 ); + if ( ( d1 > 0.0f && d2 > 0.0f ) || ( d1 < 0.0f && d2 < 0.0f ) ) { + return false; + } + + // collision point on the edge at-bt + dir1 = (vd - vc).Cross( dir ); + d = dir1 * vc; + d1 = dir1 * at - d; + d2 = dir1 * bt - d; + if ( d1 == d2 ) { + return false; + } + collisionPoint = at + ( d1 / (d1 - d2) ) * ( bt - at ); + + // normal is cross product of the rotated edge va-vb and the edge vc-vd + collisionNormal.Cross( bt-at, vd-vc ); + + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotateEdgeThroughEdge + + calculates the tangent of half the rotation angle at which the edges collide +================ +*/ +int idCollisionModelManagerLocal::RotateEdgeThroughEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + const float minTan, float &tanHalfAngle ) { + double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 ct, dt; + idPluecker pl2; + + /* + + a = start of line being rotated + b = end of line being rotated + pl1 = pluecker coordinate for line (a - b) + pl2 = pluecker coordinate for edge we might collide with (c - d) + t = rotation angle around the z-axis + solve pluecker inner product for t of rotating line a-b and line l2 + + // start point of rotated line during rotation + an[0] = a[0] * cos(t) + a[1] * sin(t) + an[1] = a[0] * -sin(t) + a[1] * cos(t) + an[2] = a[2]; + // end point of rotated line during rotation + bn[0] = b[0] * cos(t) + b[1] * sin(t) + bn[1] = b[0] * -sin(t) + b[1] * cos(t) + bn[2] = b[2]; + + pl1[0] = a[0] * b[1] - b[0] * a[1]; + pl1[1] = a[0] * b[2] - b[0] * a[2]; + pl1[2] = a[0] - b[0]; + pl1[3] = a[1] * b[2] - b[1] * a[2]; + pl1[4] = a[2] - b[2]; + pl1[5] = b[1] - a[1]; + + v[0] = (a[0] * cos(t) + a[1] * sin(t)) * (b[0] * -sin(t) + b[1] * cos(t)) - (b[0] * cos(t) + b[1] * sin(t)) * (a[0] * -sin(t) + a[1] * cos(t)); + v[1] = (a[0] * cos(t) + a[1] * sin(t)) * b[2] - (b[0] * cos(t) + b[1] * sin(t)) * a[2]; + v[2] = (a[0] * cos(t) + a[1] * sin(t)) - (b[0] * cos(t) + b[1] * sin(t)); + v[3] = (a[0] * -sin(t) + a[1] * cos(t)) * b[2] - (b[0] * -sin(t) + b[1] * cos(t)) * a[2]; + v[4] = a[2] - b[2]; + v[5] = (b[0] * -sin(t) + b[1] * cos(t)) - (a[0] * -sin(t) + a[1] * cos(t)); + + pl2[0] * v[4] + pl2[1] * v[5] + pl2[2] * v[3] + pl2[4] * v[0] + pl2[5] * v[1] + pl2[3] * v[2] = 0; + + v[0] = (a[0] * cos(t) + a[1] * sin(t)) * (b[0] * -sin(t) + b[1] * cos(t)) - (b[0] * cos(t) + b[1] * sin(t)) * (a[0] * -sin(t) + a[1] * cos(t)); + v[0] = (a[1] * b[1] - a[0] * b[0]) * cos(t) * sin(t) + (a[0] * b[1] + a[1] * b[0] * cos(t)^2) - (a[1] * b[0]) - ((b[1] * a[1] - b[0] * a[0]) * cos(t) * sin(t) + (b[0] * a[1] + b[1] * a[0]) * cos(t)^2 - (b[1] * a[0])) + v[0] = - (a[1] * b[0]) - ( - (b[1] * a[0])) + v[0] = (b[1] * a[0]) - (a[1] * b[0]) + + v[0] = (a[0]*b[1]) - (a[1]*b[0]); + v[1] = (a[0]*b[2] - b[0]*a[2]) * cos(t) + (a[1]*b[2] - b[1]*a[2]) * sin(t); + v[2] = (a[0]-b[0]) * cos(t) + (a[1]-b[1]) * sin(t); + v[3] = (b[0]*a[2] - a[0]*b[2]) * sin(t) + (a[1]*b[2] - b[1]*a[2]) * cos(t); + v[4] = a[2] - b[2]; + v[5] = (a[0]-b[0]) * sin(t) + (b[1]-a[1]) * cos(t); + + v[0] = (a[0]*b[1]) - (a[1]*b[0]); + v[1] = (a[0]*b[2] - b[0]*a[2]) * cos(t) + (a[1]*b[2] - b[1]*a[2]) * sin(t); + v[2] = (a[0]-b[0]) * cos(t) - (b[1]-a[1]) * sin(t); + v[3] = (a[0]*b[2] - b[0]*a[2]) * -sin(t) + (a[1]*b[2] - b[1]*a[2]) * cos(t); + v[4] = a[2] - b[2]; + v[5] = (a[0]-b[0]) * sin(t) + (b[1]-a[1]) * cos(t); + + v[0] = pl1[0]; + v[1] = pl1[1] * cos(t) + pl1[3] * sin(t); + v[2] = pl1[2] * cos(t) - pl1[5] * sin(t); + v[3] = pl1[3] * cos(t) - pl1[1] * sin(t); + v[4] = pl1[4]; + v[5] = pl1[5] * cos(t) + pl1[2] * sin(t); + + pl2[0] * v[4] + pl2[1] * v[5] + pl2[2] * v[3] + pl2[4] * v[0] + pl2[5] * v[1] + pl2[3] * v[2] = 0; + + 0 = pl2[0] * pl1[4] + + pl2[1] * (pl1[5] * cos(t) + pl1[2] * sin(t)) + + pl2[2] * (pl1[3] * cos(t) - pl1[1] * sin(t)) + + pl2[4] * pl1[0] + + pl2[5] * (pl1[1] * cos(t) + pl1[3] * sin(t)) + + pl2[3] * (pl1[2] * cos(t) - pl1[5] * sin(t)); + + v2 * cos(t) + v1 * sin(t) + v0 = 0; + + // rotation about the z-axis + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + // rotation about the x-axis + //v0 = pl2[3] * pl1[2] + pl2[2] * pl1[3]; + //v1 = -pl2[5] * pl1[0] + pl2[4] * pl1[1] - pl2[1] * pl1[4] + pl2[0] * pl1[5]; + //v2 = pl2[4] * pl1[0] + pl2[5] * pl1[1] + pl2[0] * pl1[4] + pl2[1] * pl1[5]; + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + v1 * 2 * r / (1 + r*r) + v2 * (1 - r*r) / (1 + r*r) + v0 = 0 + (v1 * 2 * r + v2 * (1 - r*r)) / (1 + r*r) = -v0 + (v1 * 2 * r + v2 - v2 * r*r) / (1 + r*r) = -v0 + v1 * 2 * r + v2 - v2 * r*r = -v0 * (1 + r*r) + v1 * 2 * r + v2 - v2 * r*r = -v0 + -v0 * r*r + (v0 - v2) * r * r + (2 * v1) * r + (v0 + v2) = 0; + + MrE gives Pluecker a banana.. good monkey + + */ + + tanHalfAngle = tw->maxTan; + + // transform rotation axis to z-axis + ct = (vc - tw->origin) * tw->matrix; + dt = (vd - tw->origin) * tw->matrix; + + pl2.FromLine( ct, dt ); + + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + a = v0 - v2; + b = v1; + c = v0 + v2; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( tw->angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + // get smallest tangent for which a collision occurs + if ( frac1 >= minTan && frac1 < tanHalfAngle ) { + tanHalfAngle = frac1; + } + if ( frac2 >= minTan && frac2 < tanHalfAngle ) { + tanHalfAngle = frac2; + } + + if ( tw->angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::EdgeFurthestFromEdge + + calculates the direction of motion at the initial position, where dir < 0 means the edges move towards each other + if the edges move away from each other the tangent of half the rotation angle at which + the edges are furthest apart is also calculated +================ +*/ +int idCollisionModelManagerLocal::EdgeFurthestFromEdge( cm_traceWork_t *tw, const idPluecker &pl1, + const idVec3 &vc, const idVec3 &vd, + float &tanHalfAngle, float &dir ) { + double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 ct, dt; + idPluecker pl2; + + /* + + v2 * cos(t) + v1 * sin(t) + v0 = 0; + + // rotation about the z-axis + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + derivative: + v1 * cos(t) - v2 * sin(t) = 0; + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + -v2 * 2 * r / (1 + r*r) + v1 * (1 - r*r)/(1+r*r); + -v2 * 2 * r + v1 * (1 - r*r) / (1 + r*r) = 0; + -v2 * 2 * r + v1 * (1 - r*r) = 0; + (-v1) * r * r + (-2 * v2) * r + (v1) = 0; + + */ + + tanHalfAngle = 0.0f; + + // transform rotation axis to z-axis + ct = (vc - tw->origin) * tw->matrix; + dt = (vd - tw->origin) * tw->matrix; + + pl2.FromLine( ct, dt ); + + v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0]; + v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5]; + v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2]; + + // get the direction of motion at the initial position + c = v0 + v2; + if ( tw->angle > 0.0f ) { + if ( c > 0.0f ) { + dir = v1; + } + else { + dir = -v1; + } + } + else { + if ( c > 0.0f ) { + dir = -v1; + } + else { + dir = v1; + } + } + // negative direction means the edges move towards each other at the initial position + if ( dir <= 0.0f ) { + return true; + } + + a = -v1; + b = -v2; + c = v1; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( tw->angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + if ( frac1 < 0.0f && frac2 < 0.0f ) { + return false; + } + + if ( frac1 > frac2 ) { + tanHalfAngle = frac1; + } + else { + tanHalfAngle = frac2; + } + + if ( tw->angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotateTrmEdgeThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ) { + int i, j, edgeNum; + float f1, f2, startTan, dir, tanHalfAngle; + cm_edge_t *edge; + cm_vertex_t *v1, *v2; + idVec3 collisionPoint, collisionNormal, origin, epsDir; + idPluecker epsPl; + idBounds bounds; + + // if the trm is convex and the rotation axis intersects the trm + if ( tw->isConvex && tw->axisIntersectsTrm ) { + // if both points are behind the polygon the edge cannot collide within a 180 degrees rotation + if ( tw->vertices[trmEdge->vertexNum[0]].polygonSide & tw->vertices[trmEdge->vertexNum[1]].polygonSide ) { + return; + } + } + + // if the trace model edge rotation bounds do not intersect the polygon bounds + if ( !trmEdge->rotationBounds.IntersectsBounds( poly->bounds ) ) { + return; + } + + // edge rotation bounds should cross polygon plane + if ( trmEdge->rotationBounds.PlaneSide( poly->plane ) != SIDE_CROSS ) { + return; + } + + // check edges for a collision + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + + // if this edge is already checked + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + + // can never collide with internal edges + if ( edge->internal ) { + continue; + } + + v1 = tw->model->vertices + edge->vertexNum[INTSIGNBITSET(edgeNum)]; + v2 = tw->model->vertices + edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]; + + // edge bounds + for ( j = 0; j < 3; j++ ) { + if ( v1->p[j] > v2->p[j] ) { + bounds[0][j] = v2->p[j]; + bounds[1][j] = v1->p[j]; + } + else { + bounds[0][j] = v1->p[j]; + bounds[1][j] = v2->p[j]; + } + } + + // if the trace model edge rotation bounds do not intersect the polygon edge bounds + if ( !trmEdge->rotationBounds.IntersectsBounds( bounds ) ) { + continue; + } + + f1 = trmEdge->pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ); + + // pluecker coordinate for epsilon expanded edge + epsDir = edge->normal * (CM_CLIP_EPSILON+CM_PL_RANGE_EPSILON); + epsPl.FromLine( tw->model->vertices[edge->vertexNum[0]].p + epsDir, + tw->model->vertices[edge->vertexNum[1]].p + epsDir ); + + f2 = trmEdge->pl.PermutedInnerProduct( epsPl ); + + // if the rotating edge is inbetween the polygon edge and the epsilon expanded edge + if ( ( f1 < 0.0f && f2 > 0.0f ) || ( f1 > 0.0f && f2 < 0.0f ) ) { + + if ( !EdgeFurthestFromEdge( tw, trmEdge->plzaxis, v1->p, v2->p, startTan, dir ) ) { + continue; + } + + if ( dir <= 0.0f ) { + // moving towards the polygon edge so stop immediately + tanHalfAngle = 0.0f; + } + else if ( idMath::Fabs( startTan ) >= tw->maxTan ) { + // never going to get beyond the start tangent during the current rotation + continue; + } + else { + // collide with the epsilon expanded edge + if ( !RotateEdgeThroughEdge(tw, trmEdge->plzaxis, v1->p + epsDir, v2->p + epsDir, idMath::Fabs( startTan ), tanHalfAngle ) ) { + tanHalfAngle = startTan; + } + } + } + else { + // collide with the epsilon expanded edge + epsDir = edge->normal * CM_CLIP_EPSILON; + if ( !RotateEdgeThroughEdge(tw, trmEdge->plzaxis, v1->p + epsDir, v2->p + epsDir, 0.0f, tanHalfAngle ) ) { + continue; + } + } + + if ( idMath::Fabs( tanHalfAngle ) >= tw->maxTan ) { + continue; + } + + // check if the collision is between the edge bounds + if ( !CollisionBetweenEdgeBounds( tw, trmEdge->start, trmEdge->end, v1->p, v2->p, + tanHalfAngle, collisionPoint, collisionNormal ) ) { + continue; + } + + // allow rotation if the rotation axis goes through the collisionPoint + origin = tw->origin + tw->axis * ( tw->axis * ( collisionPoint - tw->origin ) ); + if ( ( collisionPoint - origin ).LengthSqr() < ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) { + continue; + } + + // fill in trace structure + tw->maxTan = idMath::Fabs( tanHalfAngle ); + tw->trace.c.normal = collisionNormal; + tw->trace.c.normal.Normalize(); + tw->trace.c.dist = tw->trace.c.normal * v1->p; + // make sure the collision plane faces the trace model + if ( (tw->trace.c.normal * trmEdge->start) - tw->trace.c.dist < 0 ) { + tw->trace.c.normal = -tw->trace.c.normal; + tw->trace.c.dist = -tw->trace.c.dist; + } + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.modelFeature = edgeNum; + tw->trace.c.trmFeature = trmEdge - tw->edges; + tw->trace.c.point = collisionPoint; + // if no collision can be closer + if ( tw->maxTan == 0.0f ) { + break; + } + } +} + +/* +================ +idCollisionModelManagerLocal::RotatePointThroughPlane + + calculates the tangent of half the rotation angle at which the point collides with the plane +================ +*/ +int idCollisionModelManagerLocal::RotatePointThroughPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, const float minTan, float &tanHalfAngle ) { + double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 p, normal; + + /* + + p[0] = point[0] * cos(t) + point[1] * sin(t) + p[1] = point[0] * -sin(t) + point[1] * cos(t) + p[2] = point[2]; + + normal[0] * (p[0] * cos(t) + p[1] * sin(t)) + + normal[1] * (p[0] * -sin(t) + p[1] * cos(t)) + + normal[2] * p[2] + dist = 0 + + normal[0] * p[0] * cos(t) + normal[0] * p[1] * sin(t) + + -normal[1] * p[0] * sin(t) + normal[1] * p[1] * cos(t) + + normal[2] * p[2] + dist = 0 + + v2 * cos(t) + v1 * sin(t) + v0 + + // rotation about the z-axis + v0 = normal[2] * p[2] + dist + v1 = normal[0] * p[1] - normal[1] * p[0] + v2 = normal[0] * p[0] + normal[1] * p[1] + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + v1 * 2 * r / (1 + r*r) + v2 * (1 - r*r) / (1 + r*r) + v0 = 0 + (v1 * 2 * r + v2 * (1 - r*r)) / (1 + r*r) = -v0 + (v1 * 2 * r + v2 - v2 * r*r) / (1 + r*r) = -v0 + v1 * 2 * r + v2 - v2 * r*r = -v0 * (1 + r*r) + v1 * 2 * r + v2 - v2 * r*r = -v0 + -v0 * r*r + (v0 - v2) * r * r + (2 * v1) * r + (v0 + v2) = 0; + + */ + + tanHalfAngle = tw->maxTan; + + // transform rotation axis to z-axis + p = (point - tw->origin) * tw->matrix; + d = plane[3] + plane.Normal() * tw->origin; + normal = plane.Normal() * tw->matrix; + + v0 = normal[2] * p[2] + d; + v1 = normal[0] * p[1] - normal[1] * p[0]; + v2 = normal[0] * p[0] + normal[1] * p[1]; + + a = v0 - v2; + b = v1; + c = v0 + v2; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + // get smallest tangent for which a collision occurs + if ( frac1 >= minTan && frac1 < tanHalfAngle ) { + tanHalfAngle = frac1; + } + if ( frac2 >= minTan && frac2 < tanHalfAngle ) { + tanHalfAngle = frac2; + } + + if ( angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::PointFurthestFromPlane + + calculates the direction of motion at the initial position, where dir < 0 means the point moves towards the plane + if the point moves away from the plane the tangent of half the rotation angle at which + the point is furthest away from the plane is also calculated +================ +*/ +int idCollisionModelManagerLocal::PointFurthestFromPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane, + const float angle, float &tanHalfAngle, float &dir ) { + + double v1, v2, a, b, c, d, sqrtd, q, frac1, frac2; + idVec3 p, normal; + + /* + + v2 * cos(t) + v1 * sin(t) + v0 = 0; + + // rotation about the z-axis + v0 = normal[2] * p[2] + dist + v1 = normal[0] * p[1] - normal[1] * p[0] + v2 = normal[0] * p[0] + normal[1] * p[1] + + derivative: + v1 * cos(t) - v2 * sin(t) = 0; + + r = tan(t / 2); + sin(t) = 2*r/(1+r*r); + cos(t) = (1-r*r)/(1+r*r); + + -v2 * 2 * r / (1 + r*r) + v1 * (1 - r*r)/(1+r*r); + -v2 * 2 * r + v1 * (1 - r*r) / (1 + r*r) = 0; + -v2 * 2 * r + v1 * (1 - r*r) = 0; + (-v1) * r * r + (-2 * v2) * r + (v1) = 0; + + */ + + tanHalfAngle = 0.0f; + + // transform rotation axis to z-axis + p = (point - tw->origin) * tw->matrix; + normal = plane.Normal() * tw->matrix; + + v1 = normal[0] * p[1] - normal[1] * p[0]; + v2 = normal[0] * p[0] + normal[1] * p[1]; + + // the point will always start at the front of the plane, therefore v0 + v2 > 0 is always true + if ( angle < 0.0f ) { + dir = -v1; + } + else { + dir = v1; + } + // negative direction means the point moves towards the plane at the initial position + if ( dir <= 0.0f ) { + return true; + } + + a = -v1; + b = -v2; + c = v1; + if ( a == 0.0f ) { + if ( b == 0.0f ) { + return false; + } + frac1 = -c / ( 2.0f * b ); + frac2 = 1e10; // = tan( idMath::HALF_PI ) + } + else { + d = b * b - c * a; + if ( d <= 0.0f ) { + return false; + } + sqrtd = sqrt( d ); + if ( b > 0.0f ) { + q = - b + sqrtd; + } + else { + q = - b - sqrtd; + } + frac1 = q / a; + frac2 = c / q; + } + + if ( angle < 0.0f ) { + frac1 = -frac1; + frac2 = -frac2; + } + + if ( frac1 < 0.0f && frac2 < 0.0f ) { + return false; + } + + if ( frac1 > frac2 ) { + tanHalfAngle = frac1; + } + else { + tanHalfAngle = frac2; + } + + if ( angle < 0.0f ) { + tanHalfAngle = -tanHalfAngle; + } + + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotatePointThroughEpsilonPlane +================ +*/ +int idCollisionModelManagerLocal::RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, const idVec3 &point, const idVec3 &endPoint, + const idPlane &plane, const float angle, const idVec3 &origin, + float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir ) { + float d, dir, startTan; + idVec3 vec, startDir; + idPlane epsPlane; + + // epsilon expanded plane + epsPlane = plane; + epsPlane.SetDist( epsPlane.Dist() + CM_CLIP_EPSILON ); + + // if the rotation sphere at the rotation origin is too far away from the polygon plane + d = epsPlane.Distance( origin ); + vec = point - origin; + if ( d * d > vec * vec ) { + return false; + } + + // calculate direction of motion at vertex start position + startDir = ( point - origin ).Cross( tw->axis ); + if ( angle < 0.0f ) { + startDir = -startDir; + } + // if moving away from plane at start position + if ( startDir * epsPlane.Normal() >= 0.0f ) { + // if end position is outside epsilon range + d = epsPlane.Distance( endPoint ); + if ( d >= 0.0f ) { + return false; // no collision + } + // calculate direction of motion at vertex end position + endDir = ( endPoint - origin ).Cross( tw->axis ); + if ( angle < 0.0f ) { + endDir = -endDir; + } + // if also moving away from plane at end position + if ( endDir * epsPlane.Normal() > 0.0f ) { + return false; // no collision + } + } + + // if the start position is in the epsilon range + d = epsPlane.Distance( point ); + if ( d <= CM_PL_RANGE_EPSILON ) { + + // calculate tangent of half the rotation for which the vertex is furthest away from the plane + if ( !PointFurthestFromPlane( tw, point, plane, angle, startTan, dir ) ) { + return false; + } + + if ( dir <= 0.0f ) { + // moving towards the polygon plane so stop immediately + tanHalfAngle = 0.0f; + } + else if ( idMath::Fabs( startTan ) >= tw->maxTan ) { + // never going to get beyond the start tangent during the current rotation + return false; + } + else { + // calculate collision with epsilon expanded plane + if ( !RotatePointThroughPlane( tw, point, epsPlane, angle, idMath::Fabs( startTan ), tanHalfAngle ) ) { + tanHalfAngle = startTan; + } + } + } + else { + // calculate collision with epsilon expanded plane + if ( !RotatePointThroughPlane( tw, point, epsPlane, angle, 0.0f, tanHalfAngle ) ) { + return false; + } + } + + // calculate collision point + collisionPoint = point; + if ( tanHalfAngle != 0.0f ) { + CM_RotatePoint( collisionPoint, tw->origin, tw->axis, tanHalfAngle ); + } + // calculate direction of motion at collision point + endDir = ( collisionPoint - origin ).Cross( tw->axis ); + if ( angle < 0.0f ) { + endDir = -endDir; + } + return true; +} + +/* +================ +idCollisionModelManagerLocal::RotateTrmVertexThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int vertexNum ) { + int i; + float tanHalfAngle; + idVec3 endDir, collisionPoint; + idPluecker pl; + + // if the trm vertex is behind the polygon plane it cannot collide with the polygon within a 180 degrees rotation + if ( tw->isConvex && tw->axisIntersectsTrm && v->polygonSide ) { + return; + } + + // if the trace model vertex rotation bounds do not intersect the polygon bounds + if ( !v->rotationBounds.IntersectsBounds( poly->bounds ) ) { + return; + } + + // vertex rotation bounds should cross polygon plane + if ( v->rotationBounds.PlaneSide( poly->plane ) != SIDE_CROSS ) { + return; + } + + // rotate the vertex through the epsilon plane + if ( !RotatePointThroughEpsilonPlane( tw, v->p, v->endp, poly->plane, tw->angle, v->rotationOrigin, + tanHalfAngle, collisionPoint, endDir ) ) { + return; + } + + if ( idMath::Fabs( tanHalfAngle ) < tw->maxTan ) { + // verify if 'collisionPoint' moving along 'endDir' moves between polygon edges + pl.FromRay( collisionPoint, endDir ); + for ( i = 0; i < poly->numEdges; i++ ) { + if ( poly->edges[i] < 0 ) { + if ( pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ) > 0.0f ) { + return; + } + } + else { + if ( pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ) < 0.0f ) { + return; + } + } + } + tw->maxTan = idMath::Fabs( tanHalfAngle ); + // collision plane is the polygon plane + tw->trace.c.normal = poly->plane.Normal(); + tw->trace.c.dist = poly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.modelFeature = *reinterpret_cast(&poly); + tw->trace.c.trmFeature = v - tw->vertices; + tw->trace.c.point = collisionPoint; + } +} + +/* +================ +idCollisionModelManagerLocal::RotateVertexThroughTrmPolygon +================ +*/ +void idCollisionModelManagerLocal::RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &rotationOrigin ) { + int i, edgeNum; + float tanHalfAngle; + idVec3 dir, endp, endDir, collisionPoint; + idPluecker pl; + cm_trmEdge_t *edge; + + // if the polygon vertex is behind the trm plane it cannot collide with the trm polygon within a 180 degrees rotation + if ( tw->isConvex && tw->axisIntersectsTrm && trmpoly->plane.Distance( v->p ) < 0.0f ) { + return; + } + + // if the model vertex is outside the trm polygon rotation bounds + if ( !trmpoly->rotationBounds.ContainsPoint( v->p ) ) { + return; + } + + // if the rotation axis goes through the polygon vertex + dir = v->p - rotationOrigin; + if ( dir * dir < ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) { + return; + } + + // calculate vertex end position + endp = v->p; + tw->modelVertexRotation.RotatePoint( endp ); + + // rotate the vertex through the epsilon plane + if ( !RotatePointThroughEpsilonPlane( tw, v->p, endp, trmpoly->plane, -tw->angle, rotationOrigin, + tanHalfAngle, collisionPoint, endDir ) ) { + return; + } + + if ( idMath::Fabs( tanHalfAngle ) < tw->maxTan ) { + // verify if 'collisionPoint' moving along 'endDir' moves between polygon edges + pl.FromRay( collisionPoint, endDir ); + for ( i = 0; i < trmpoly->numEdges; i++ ) { + edgeNum = trmpoly->edges[i]; + edge = tw->edges + abs(edgeNum); + if ( edgeNum < 0 ) { + if ( pl.PermutedInnerProduct( edge->pl ) > 0.0f ) { + return; + } + } + else { + if ( pl.PermutedInnerProduct( edge->pl ) < 0.0f ) { + return; + } + } + } + tw->maxTan = idMath::Fabs( tanHalfAngle ); + // collision plane is the flipped trm polygon plane + tw->trace.c.normal = -trmpoly->plane.Normal(); + tw->trace.c.dist = tw->trace.c.normal * v->p; + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_MODELVERTEX; + tw->trace.c.modelFeature = v - tw->model->vertices; + tw->trace.c.trmFeature = trmpoly - tw->polys; + tw->trace.c.point = v->p; + } +} + +/* +================ +idCollisionModelManagerLocal::RotateTrmThroughPolygon + + returns true if the polygon blocks the complete rotation +================ +*/ +bool idCollisionModelManagerLocal::RotateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) { + int i, j, k, edgeNum; + float d; + cm_trmVertex_t *bv; + cm_trmEdge_t *be; + cm_trmPolygon_t *bp; + cm_vertex_t *v; + cm_edge_t *e; + idVec3 *rotationOrigin; + + // if already checked this polygon + if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + p->checkcount = idCollisionModelManagerLocal::checkCount; + + // if this polygon does not have the right contents behind it + if ( !(p->contents & tw->contents) ) { + return false; + } + + // if the the trace bounds do not intersect the polygon bounds + if ( !tw->bounds.IntersectsBounds( p->bounds ) ) { + return false; + } + + // back face culling + if ( tw->isConvex ) { + // if the center of the convex trm is behind the polygon plane + if ( p->plane.Distance( tw->start ) < 0.0f ) { + // if the rotation axis intersects the trace model + if ( tw->axisIntersectsTrm ) { + return false; + } + else { + // if the direction of motion at the start and end position of the + // center of the trm both go towards or away from the polygon plane + // or if the intersections of the rotation axis with the expanded heart planes + // are both in front of the polygon plane + } + } + } + + // if the polygon is too far from the first heart plane + d = p->bounds.PlaneDistance( tw->heartPlane1 ); + if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane1 ) { + return false; + } + + // rotation bounds should cross polygon plane + switch( tw->bounds.PlaneSide( p->plane ) ) { + case PLANESIDE_CROSS: + break; + case PLANESIDE_FRONT: + if ( tw->model->isConvex ) { + tw->quickExit = true; + return true; + } + default: + return false; + } + + for ( i = 0; i < tw->numVerts; i++ ) { + bv = tw->vertices + i; + // calculate polygon side this vertex is on + d = p->plane.Distance( bv->p ); + bv->polygonSide = FLOATSIGNBITSET( d ); + } + + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + v = tw->model->vertices + e->vertexNum[INTSIGNBITSET(edgeNum)]; + + // pluecker coordinate for edge + tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[e->vertexNum[0]].p, + tw->model->vertices[e->vertexNum[1]].p ); + + // calculate rotation origin projected into rotation plane through the vertex + tw->polygonRotationOriginCache[i] = tw->origin + tw->axis * ( tw->axis * ( v->p - tw->origin ) ); + } + // copy first to last so we can easily cycle through + tw->polygonRotationOriginCache[p->numEdges] = tw->polygonRotationOriginCache[0]; + + // fast point rotation + if ( tw->pointTrace ) { + RotateTrmVertexThroughPolygon( tw, p, &tw->vertices[0], 0 ); + } + else { + // rotate trm vertices through polygon + for ( i = 0; i < tw->numVerts; i++ ) { + bv = tw->vertices + i; + if ( bv->used ) { + RotateTrmVertexThroughPolygon( tw, p, bv, i ); + } + } + + // rotate trm edges through polygon + for ( i = 1; i <= tw->numEdges; i++ ) { + be = tw->edges + i; + if ( be->used ) { + RotateTrmEdgeThroughPolygon( tw, p, be ); + } + } + + // rotate all polygon vertices through the trm + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + + if ( e->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set edge check count + e->checkcount = idCollisionModelManagerLocal::checkCount; + // can never collide with internal edges + if ( e->internal ) { + continue; + } + // got to check both vertices because we skip internal edges + for ( k = 0; k < 2; k++ ) { + + v = tw->model->vertices + e->vertexNum[k ^ INTSIGNBITSET(edgeNum)]; + + // if this vertex is already checked + if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set vertex check count + v->checkcount = idCollisionModelManagerLocal::checkCount; + + // if the vertex is outside the trm rotation bounds + if ( !tw->bounds.ContainsPoint( v->p ) ) { + continue; + } + + rotationOrigin = &tw->polygonRotationOriginCache[i+k]; + + for ( j = 0; j < tw->numPolys; j++ ) { + bp = tw->polys + j; + if ( bp->used ) { + RotateVertexThroughTrmPolygon( tw, bp, p, v, *rotationOrigin ); + } + } + } + } + } + + return ( tw->maxTan == 0.0f ); +} + +/* +================ +idCollisionModelManagerLocal::BoundsForRotation + + only for rotations < 180 degrees +================ +*/ +void idCollisionModelManagerLocal::BoundsForRotation( const idVec3 &origin, const idVec3 &axis, const idVec3 &start, const idVec3 &end, idBounds &bounds ) { + int i; + float radiusSqr; + idVec3 v1, v2; + + radiusSqr = ( start - origin ).LengthSqr(); + v1 = ( start - origin ).Cross( axis ); + v2 = ( end - origin ).Cross( axis ); + + for ( i = 0; i < 3; i++ ) { + // if the derivative changes sign along this axis during the rotation from start to end + if ( ( v1[i] > 0.0f && v2[i] < 0.0f ) || ( v1[i] < 0.0f && v2[i] > 0.0f ) ) { + if ( ( 0.5f * (start[i] + end[i]) - origin[i] ) > 0.0f ) { + bounds[0][i] = Min( start[i], end[i] ); + bounds[1][i] = origin[i] + idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + } + else { + bounds[0][i] = origin[i] - idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + bounds[1][i] = Max( start[i], end[i] ); + } + } + else if ( start[i] > end[i] ) { + bounds[0][i] = end[i]; + bounds[1][i] = start[i]; + } + else { + bounds[0][i] = start[i]; + bounds[1][i] = end[i]; + } + // expand for epsilons + bounds[0][i] -= CM_BOX_EPSILON; + bounds[1][i] += CM_BOX_EPSILON; + } +} + +/* +================ +idCollisionModelManagerLocal::Rotation180 +================ +*/ +void idCollisionModelManagerLocal::Rotation180( trace_t *results, const idVec3 &rorg, const idVec3 &axis, + const float startAngle, const float endAngle, const idVec3 &start, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + int i, j, edgeNum; + float d, maxErr, initialTan; + bool model_rotated, trm_rotated; + idVec3 dir, dir1, dir2, tmp, vr, vup, org, at, bt; + idMat3 invModelAxis, endAxis, tmpAxis; + idRotation startRotation, endRotation; + idPluecker plaxis; + cm_trmPolygon_t *poly; + cm_trmEdge_t *edge; + cm_trmVertex_t *vert; + ALIGN16( static cm_traceWork_t tw ); + + if ( model < 0 || model > MAX_SUBMODELS || model > idCollisionModelManagerLocal::maxModels ) { + common->Printf("idCollisionModelManagerLocal::Rotation180: invalid model handle\n"); + return; + } + if ( !idCollisionModelManagerLocal::models[model] ) { + common->Printf("idCollisionModelManagerLocal::Rotation180: invalid model\n"); + return; + } + + idCollisionModelManagerLocal::checkCount++; + + tw.trace.fraction = 1.0f; + tw.trace.c.contents = 0; + tw.trace.c.type = CONTACT_NONE; + tw.contents = contentMask; + tw.isConvex = true; + tw.rotation = true; + tw.positionTest = false; + tw.axisIntersectsTrm = false; + tw.quickExit = false; + tw.angle = endAngle - startAngle; + assert( tw.angle > -180.0f && tw.angle < 180.0f ); + tw.maxTan = initialTan = idMath::Fabs( tan( ( idMath::PI / 360.0f ) * tw.angle ) ); + tw.model = idCollisionModelManagerLocal::models[model]; + tw.start = start - modelOrigin; + // rotation axis, axis is assumed to be normalized + tw.axis = axis; + assert( tw.axis[0] * tw.axis[0] + tw.axis[1] * tw.axis[1] + tw.axis[2] * tw.axis[2] > 0.99f ); + // rotation origin projected into rotation plane through tw.start + tw.origin = rorg - modelOrigin; + d = (tw.axis * tw.origin) - ( tw.axis * tw.start ); + tw.origin = tw.origin - d * tw.axis; + // radius of rotation + tw.radius = ( tw.start - tw.origin ).Length(); + // maximum error of the circle approximation traced through the axial BSP tree + d = tw.radius * tw.radius - (CIRCLE_APPROXIMATION_LENGTH*CIRCLE_APPROXIMATION_LENGTH*0.25f); + if ( d > 0.0f ) { + maxErr = tw.radius - idMath::Sqrt( d ); + } else { + maxErr = tw.radius; + } + + model_rotated = modelAxis.IsRotated(); + if ( model_rotated ) { + invModelAxis = modelAxis.Transpose(); + tw.axis *= invModelAxis; + tw.origin *= invModelAxis; + } + + startRotation.Set( tw.origin, tw.axis, startAngle ); + endRotation.Set( tw.origin, tw.axis, endAngle ); + + // create matrix which rotates the rotation axis to the z-axis + tw.axis.NormalVectors( vr, vup ); + tw.matrix[0][0] = vr[0]; + tw.matrix[1][0] = vr[1]; + tw.matrix[2][0] = vr[2]; + tw.matrix[0][1] = -vup[0]; + tw.matrix[1][1] = -vup[1]; + tw.matrix[2][1] = -vup[2]; + tw.matrix[0][2] = tw.axis[0]; + tw.matrix[1][2] = tw.axis[1]; + tw.matrix[2][2] = tw.axis[2]; + + // if optimized point trace + if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f && + trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f && + trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) { + + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + } + tw.end = tw.start; + // if we start at a specific angle + if ( startAngle != 0.0f ) { + startRotation.RotatePoint( tw.start ); + } + // calculate end position of rotation + endRotation.RotatePoint( tw.end ); + + // calculate rotation origin projected into rotation plane through the vertex + tw.numVerts = 1; + tw.vertices[0].p = tw.start; + tw.vertices[0].endp = tw.end; + tw.vertices[0].used = true; + tw.vertices[0].rotationOrigin = tw.origin + tw.axis * ( tw.axis * ( tw.vertices[0].p - tw.origin ) ); + BoundsForRotation( tw.vertices[0].rotationOrigin, tw.axis, tw.start, tw.end, tw.vertices[0].rotationBounds ); + // rotation bounds + tw.bounds = tw.vertices[0].rotationBounds; + tw.numEdges = tw.numPolys = 0; + + // collision with single point + tw.pointTrace = true; + + // extents is set to maximum error of the circle approximation traced through the axial BSP tree + tw.extents[0] = tw.extents[1] = tw.extents[2] = maxErr + CM_BOX_EPSILON; + + // setup rotation heart plane + tw.heartPlane1.SetNormal( tw.axis ); + tw.heartPlane1.FitThroughPoint( tw.start ); + tw.maxDistFromHeartPlane1 = CM_BOX_EPSILON; + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + // store results + *results = tw.trace; + results->endpos = start; + if ( tw.maxTan == initialTan ) { + results->fraction = 1.0f; + } else { + results->fraction = idMath::Fabs( atan( tw.maxTan ) * ( 2.0f * 180.0f / idMath::PI ) / tw.angle ); + } + assert( results->fraction <= 1.0f ); + endRotation.Set( rorg, axis, startAngle + (endAngle-startAngle) * results->fraction ); + endRotation.RotatePoint( results->endpos ); + results->endAxis.Identity(); + + if ( results->fraction < 1.0f ) { + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } + return; + } + + tw.pointTrace = false; + + // setup trm structure + idCollisionModelManagerLocal::SetupTrm( &tw, trm ); + + trm_rotated = trmAxis.IsRotated(); + + // calculate vertex positions + if ( trm_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around the start position + tw.vertices[i].p *= trmAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // set trm at start position + tw.vertices[i].p += tw.start; + } + if ( model_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + tw.vertices[i].p *= invModelAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + tw.vertices[i].endp = tw.vertices[i].p; + } + // if we start at a specific angle + if ( startAngle != 0.0f ) { + for ( i = 0; i < tw.numVerts; i++ ) { + startRotation.RotatePoint( tw.vertices[i].p ); + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // end position of vertex + endRotation.RotatePoint( tw.vertices[i].endp ); + } + + // add offset to start point + if ( trm_rotated ) { + tw.start += trm->offset * trmAxis; + } else { + tw.start += trm->offset; + } + // if the model is rotated + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + } + tw.end = tw.start; + // if we start at a specific angle + if ( startAngle != 0.0f ) { + startRotation.RotatePoint( tw.start ); + } + // calculate end position of rotation + endRotation.RotatePoint( tw.end ); + + // setup trm vertices + for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) { + // calculate rotation origin projected into rotation plane through the vertex + vert->rotationOrigin = tw.origin + tw.axis * ( tw.axis * ( vert->p - tw.origin ) ); + // calculate rotation bounds for this vertex + BoundsForRotation( vert->rotationOrigin, tw.axis, vert->p, vert->endp, vert->rotationBounds ); + // if the rotation axis goes through the vertex then the vertex is not used + d = ( vert->p - vert->rotationOrigin ).LengthSqr(); + if ( d > ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) { + vert->used = true; + } + } + + // setup trm edges + for ( edge = tw.edges + 1, i = 1; i <= tw.numEdges; i++, edge++ ) { + // if the rotation axis goes through both the edge vertices then the edge is not used + if ( tw.vertices[edge->vertexNum[0]].used | tw.vertices[edge->vertexNum[1]].used ) { + edge->used = true; + } + // edge start, end and pluecker coordinate + edge->start = tw.vertices[edge->vertexNum[0]].p; + edge->end = tw.vertices[edge->vertexNum[1]].p; + edge->pl.FromLine( edge->start, edge->end ); + // pluecker coordinate for edge being rotated about the z-axis + at = ( edge->start - tw.origin ) * tw.matrix; + bt = ( edge->end - tw.origin ) * tw.matrix; + edge->plzaxis.FromLine( at, bt ); + // get edge rotation bounds from the rotation bounds of both vertices + edge->rotationBounds = tw.vertices[edge->vertexNum[0]].rotationBounds; + edge->rotationBounds.AddBounds( tw.vertices[edge->vertexNum[1]].rotationBounds ); + // used to calculate if the rotation axis intersects the trm + edge->bitNum = 0; + } + + tw.bounds.Clear(); + + // rotate trm polygon planes + if ( trm_rotated & model_rotated ) { + tmpAxis = trmAxis * invModelAxis; + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= tmpAxis; + } + } else if ( trm_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= trmAxis; + } + } else if ( model_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= invModelAxis; + } + } + + // setup trm polygons + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->used = true; + // set trm polygon plane distance + poly->plane.FitThroughPoint( tw.edges[abs(poly->edges[0])].start ); + // get polygon bounds from edge bounds + poly->rotationBounds.Clear(); + for ( j = 0; j < poly->numEdges; j++ ) { + // add edge rotation bounds to polygon rotation bounds + edge = &tw.edges[abs( poly->edges[j] )]; + poly->rotationBounds.AddBounds( edge->rotationBounds ); + } + // get trace bounds from polygon bounds + tw.bounds.AddBounds( poly->rotationBounds ); + } + + // extents including the maximum error of the circle approximation traced through the axial BSP tree + for ( i = 0; i < 3; i++ ) { + tw.size[0][i] = tw.bounds[0][i] - tw.start[i]; + tw.size[1][i] = tw.bounds[1][i] - tw.start[i]; + if ( idMath::Fabs( tw.size[0][i] ) > idMath::Fabs( tw.size[1][i] ) ) { + tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + maxErr + CM_BOX_EPSILON; + } else { + tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + maxErr + CM_BOX_EPSILON; + } + } + + // for back-face culling + if ( tw.isConvex ) { + if ( tw.start == tw.origin ) { + tw.axisIntersectsTrm = true; + } else { + // determine if the rotation axis intersects the trm + plaxis.FromRay( tw.origin, tw.axis ); + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + // back face cull polygons + if ( poly->plane.Normal() * tw.axis > 0.0f ) { + continue; + } + // test if the axis goes between the polygon edges + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = tw.edges + abs(edgeNum); + if ( !(edge->bitNum & 2) ) { + d = plaxis.PermutedInnerProduct( edge->pl ); + edge->bitNum = FLOATSIGNBITSET( d ) | 2; + } + if ( ( edge->bitNum ^ INTSIGNBITSET( edgeNum ) ) & 1 ) { + break; + } + } + if ( j >= poly->numEdges ) { + tw.axisIntersectsTrm = true; + break; + } + } + } + } + + // setup rotation heart plane + tw.heartPlane1.SetNormal( tw.axis ); + tw.heartPlane1.FitThroughPoint( tw.start ); + tw.maxDistFromHeartPlane1 = 0.0f; + for ( i = 0; i < tw.numVerts; i++ ) { + d = idMath::Fabs( tw.heartPlane1.Distance( tw.vertices[i].p ) ); + if ( d > tw.maxDistFromHeartPlane1 ) { + tw.maxDistFromHeartPlane1 = d; + } + } + tw.maxDistFromHeartPlane1 += CM_BOX_EPSILON; + + // inverse rotation to rotate model vertices towards trace model + tw.modelVertexRotation.Set( tw.origin, tw.axis, -tw.angle ); + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + // store results + *results = tw.trace; + results->endpos = start; + if ( tw.maxTan == initialTan ) { + results->fraction = 1.0f; + } else { + results->fraction = idMath::Fabs( atan( tw.maxTan ) * ( 2.0f * 180.0f / idMath::PI ) / tw.angle ); + } + assert( results->fraction <= 1.0f ); + endRotation.Set( rorg, axis, startAngle + (endAngle-startAngle) * results->fraction ); + endRotation.RotatePoint( results->endpos ); + results->endAxis = trmAxis * endRotation.ToMat3(); + + if ( results->fraction < 1.0f ) { + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } +} + +/* +================ +idCollisionModelManagerLocal::Rotation +================ +*/ +#ifdef _DEBUG +static int entered = 0; +#endif + +void idCollisionModelManagerLocal::Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idVec3 tmp; + float maxa, stepa, a, lasta; + + assert( ((byte *)&start) < ((byte *)results) || ((byte *)&start) > (((byte *)results) + sizeof( trace_t )) ); + assert( ((byte *)&trmAxis) < ((byte *)results) || ((byte *)&trmAxis) > (((byte *)results) + sizeof( trace_t )) ); + + memset( results, 0, sizeof( *results ) ); + + // if special position test + if ( rotation.GetAngle() == 0.0f ) { + idCollisionModelManagerLocal::ContentsTrm( results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + return; + } + +#ifdef _DEBUG + bool startsolid = false; + // test whether or not stuck to begin with + if ( cm_debugCollision.GetBool() ) { + if ( !entered ) { + entered = 1; + // if already messed up to begin with + if ( idCollisionModelManagerLocal::Contents( start, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + startsolid = true; + } + entered = 0; + } + } +#endif + + if ( rotation.GetAngle() >= 180.0f || rotation.GetAngle() <= -180.0f) { + if ( rotation.GetAngle() >= 360.0f ) { + maxa = 360.0f; + stepa = 120.0f; // three steps strictly < 180 degrees + } else if ( rotation.GetAngle() <= -360.0f ) { + maxa = -360.0f; + stepa = -120.0f; // three steps strictly < 180 degrees + } else { + maxa = rotation.GetAngle(); + stepa = rotation.GetAngle() * 0.5f; // two steps strictly < 180 degrees + } + for ( lasta = 0.0f, a = stepa; fabs( a ) < fabs( maxa ) + 1.0f; lasta = a, a += stepa ) { + // partial rotation + idCollisionModelManagerLocal::Rotation180( results, rotation.GetOrigin(), rotation.GetVec(), lasta, a, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + // if there is a collision + if ( results->fraction < 1.0f ) { + // fraction of total rotation + results->fraction = (lasta + stepa * results->fraction) / rotation.GetAngle(); + return; + } + } + results->fraction = 1.0f; + return; + } + + idCollisionModelManagerLocal::Rotation180( results, rotation.GetOrigin(), rotation.GetVec(), 0.0f, rotation.GetAngle(), start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + +#ifdef _DEBUG + // test for missed collisions + if ( cm_debugCollision.GetBool() ) { + if ( !entered ) { + entered = 1; + // if the trm is stuck in the model + if ( idCollisionModelManagerLocal::Contents( results->endpos, trm, results->endAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + trace_t tr; + + // test where the trm is stuck in the model + idCollisionModelManagerLocal::Contents( results->endpos, trm, results->endAxis, -1, model, modelOrigin, modelAxis ); + // re-run collision detection to find out where it failed + idCollisionModelManagerLocal::Rotation( &tr, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + entered = 0; + } + } +#endif +} + +#endif // !Q4_CM_LEGACY_SEED diff --git a/src/cm/collisionmodel_trace.cpp b/src/cm/collisionmodel_trace.cpp new file mode 100644 index 0000000..9a22ecb --- /dev/null +++ b/src/cm/collisionmodel_trace.cpp @@ -0,0 +1,162 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#if defined( Q4_CM_LEGACY_SEED ) +#include "collisionmodel_trace_legacy.inc" +#else + +/* +=========================================================================== + +Quake 4 source reconstruction + +Retail object-based collision trace traversal recovered from the matching +PDB and address-matched Hex-Rays body. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +void idCollisionModelManagerLocal::TraceTrmThroughNode( cm_traceWork_t *traceWork, cm_node_t *node ) { + if ( traceWork->positionTest ) { + if ( traceWork->trace.fraction == 0.0f ) { + return; + } + for ( cm_brushRef_t *ref = node->brushes; ref != NULL; ref = ref->next ) { + if ( TestTrmVertsInBrush( traceWork, ref->b ) ) { + return; + } + } + if ( traceWork->pointTrace ) { + return; + } + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + if ( TestTrmInPolygon( traceWork, ref->p ) ) { + return; + } + } + } else if ( traceWork->rotation ) { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + if ( RotateTrmThroughPolygon( traceWork, ref->p ) ) { + return; + } + } + } else { + for ( cm_polygonRef_t *ref = node->polygons; ref != NULL; ref = ref->next ) { + if ( TranslateTrmThroughPolygon( traceWork, ref->p ) ) { + return; + } + } + } +} + +void idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( cm_traceWork_t *traceWork, + cm_node_t *node, float p1f, float p2f, idVec3 &p1, idVec3 &p2 ) { + if ( node == NULL || traceWork->quickExit || traceWork->trace.fraction <= p1f ) { + return; + } + + if ( node->polygons != NULL || ( traceWork->positionTest && node->brushes != NULL ) ) { + TraceTrmThroughNode( traceWork, node ); + } + if ( traceWork->positionTest && traceWork->trace.fraction == 0.0f ) { + return; + } + if ( node->planeType == -1 ) { + return; + } + + const float t1 = p1[node->planeType] - node->planeDist; + const float t2 = p2[node->planeType] - node->planeDist; + const float offset = traceWork->trmExtents[node->planeType]; + if ( t1 >= offset && t2 >= offset ) { + TraceThroughAxialBSPTree_r( traceWork, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < -offset && t2 < -offset ) { + TraceThroughAxialBSPTree_r( traceWork, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + int side; + float fraction; + float fraction2; + if ( t1 < t2 ) { + const float inverseDistance = 1.0f / ( t1 - t2 ); + side = 1; + fraction2 = ( t1 + offset ) * inverseDistance; + fraction = ( t1 - offset ) * inverseDistance; + } else if ( t1 > t2 ) { + const float inverseDistance = 1.0f / ( t1 - t2 ); + side = 0; + fraction2 = ( t1 - offset ) * inverseDistance; + fraction = ( t1 + offset ) * inverseDistance; + } else { + side = 0; + fraction = 1.0f; + fraction2 = 0.0f; + } + + fraction = Max( 0.0f, Min( 1.0f, fraction ) ); + idVec3 mid = p1 + fraction * ( p2 - p1 ); + float midFraction = p1f + ( p2f - p1f ) * fraction; + TraceThroughAxialBSPTree_r( traceWork, node->children[side], p1f, midFraction, p1, mid ); + + fraction2 = Max( 0.0f, Min( 1.0f, fraction2 ) ); + mid = p1 + fraction2 * ( p2 - p1 ); + midFraction = p1f + ( p2f - p1f ) * fraction2; + TraceThroughAxialBSPTree_r( traceWork, node->children[side ^ 1], midFraction, p2f, mid, p2 ); +} + +void idCollisionModelManagerLocal::TraceThroughModel( cm_traceWork_t *traceWork ) { + if ( !traceWork->rotation ) { + TraceThroughAxialBSPTree_r( traceWork, traceWork->model->node, + 0.0f, 1.0f, traceWork->start, traceWork->end ); + return; + } + + const float distance = traceWork->radius * DEG2RAD( traceWork->angle ); + idVec3 start = traceWork->start; + if ( distance > CIRCLE_APPROXIMATION_LENGTH ) { + const int numSteps = static_cast( distance / CIRCLE_APPROXIMATION_LENGTH ); + for ( int i = 0; i < numSteps; i++ ) { + idRotation rotation( traceWork->origin, traceWork->axis, + traceWork->angle * static_cast( i + 1 ) / numSteps ); + idVec3 end = start * rotation; + TraceThroughAxialBSPTree_r( traceWork, traceWork->model->node, 0.0f, 1.0f, start, end ); + if ( traceWork->trace.fraction < 1.0f ) { + return; + } + start = end; + } + } + TraceThroughAxialBSPTree_r( traceWork, traceWork->model->node, + 0.0f, 1.0f, start, traceWork->end ); +} + +#endif diff --git a/src/cm/collisionmodel_trace_legacy.inc b/src/cm/collisionmodel_trace_legacy.inc new file mode 100644 index 0000000..b8edbf2 --- /dev/null +++ b/src/cm/collisionmodel_trace_legacy.inc @@ -0,0 +1,252 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Trace through the spatial subdivision + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::TraceTrmThroughNode +================ +*/ +void idCollisionModelManagerLocal::TraceTrmThroughNode( cm_traceWork_t *tw, cm_node_t *node ) { + cm_polygonRef_t *pref; + cm_brushRef_t *bref; + + // position test + if ( tw->positionTest ) { + // if already stuck in solid + if ( tw->trace.fraction == 0.0f ) { + return; + } + // test if any of the trm vertices is inside a brush + for ( bref = node->brushes; bref; bref = bref->next ) { + if ( idCollisionModelManagerLocal::TestTrmVertsInBrush( tw, bref->b ) ) { + return; + } + } + // if just testing a point we're done + if ( tw->pointTrace ) { + return; + } + // test if the trm is stuck in any polygons + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( idCollisionModelManagerLocal::TestTrmInPolygon( tw, pref->p ) ) { + return; + } + } + } + else if ( tw->rotation ) { + // rotate through all polygons in this leaf + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( idCollisionModelManagerLocal::RotateTrmThroughPolygon( tw, pref->p ) ) { + return; + } + } + } + else { + // trace through all polygons in this leaf + for ( pref = node->polygons; pref; pref = pref->next ) { + if ( idCollisionModelManagerLocal::TranslateTrmThroughPolygon( tw, pref->p ) ) { + return; + } + } + } +} + +/* +================ +idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r +================ +*/ +//#define NO_SPATIAL_SUBDIVISION + +void idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( cm_traceWork_t *tw, cm_node_t *node, float p1f, float p2f, idVec3 &p1, idVec3 &p2) { + float t1, t2, offset; + float frac, frac2; + float idist; + idVec3 mid; + int side; + float midf; + + if ( !node ) { + return; + } + + if ( tw->quickExit ) { + return; // stop immediately + } + + if ( tw->trace.fraction <= p1f ) { + return; // already hit something nearer + } + + // if we need to test this node for collisions + if ( node->polygons || (tw->positionTest && node->brushes) ) { + // trace through node with collision data + idCollisionModelManagerLocal::TraceTrmThroughNode( tw, node ); + } + // if already stuck in solid + if ( tw->positionTest && tw->trace.fraction == 0.0f ) { + return; + } + // if this is a leaf node + if ( node->planeType == -1 ) { + return; + } +#ifdef NO_SPATIAL_SUBDIVISION + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[0], p1f, p2f, p1, p2 ); + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[1], p1f, p2f, p1, p2 ); + return; +#endif + // distance from plane for trace start and end + t1 = p1[node->planeType] - node->planeDist; + t2 = p2[node->planeType] - node->planeDist; + // adjust the plane distance appropriately for mins/maxs + offset = tw->extents[node->planeType]; + // see which sides we need to consider + if ( t1 >= offset && t2 >= offset ) { + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[0], p1f, p2f, p1, p2 ); + return; + } + + if ( t1 < -offset && t2 < -offset ) { + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + if ( t1 < t2 ) { + idist = 1.0f / (t1-t2); + side = 1; + frac2 = (t1 + offset) * idist; + frac = (t1 - offset) * idist; + } else if (t1 > t2) { + idist = 1.0f / (t1-t2); + side = 0; + frac2 = (t1 - offset) * idist; + frac = (t1 + offset) * idist; + } else { + side = 0; + frac = 1.0f; + frac2 = 0.0f; + } + + // move up to the node + if ( frac < 0.0f ) { + frac = 0.0f; + } + else if ( frac > 1.0f ) { + frac = 1.0f; + } + + midf = p1f + (p2f - p1f)*frac; + + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[side], p1f, midf, p1, mid ); + + + // go past the node + if ( frac2 < 0.0f ) { + frac2 = 0.0f; + } + else if ( frac2 > 1.0f ) { + frac2 = 1.0f; + } + + midf = p1f + (p2f - p1f)*frac2; + + mid[0] = p1[0] + frac2*(p2[0] - p1[0]); + mid[1] = p1[1] + frac2*(p2[1] - p1[1]); + mid[2] = p1[2] + frac2*(p2[2] - p1[2]); + + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[side^1], midf, p2f, mid, p2 ); +} + +/* +================ +idCollisionModelManagerLocal::TraceThroughModel +================ +*/ +void idCollisionModelManagerLocal::TraceThroughModel( cm_traceWork_t *tw ) { + float d; + int i, numSteps; + idVec3 start, end; + idRotation rot; + + if ( !tw->rotation ) { + // trace through spatial subdivision and then through leafs + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, tw->start, tw->end ); + } + else { + // approximate the rotation with a series of straight line movements + // total length covered along circle + d = tw->radius * DEG2RAD( tw->angle ); + // if more than one step + if ( d > CIRCLE_APPROXIMATION_LENGTH ) { + // number of steps for the approximation + numSteps = (int) (CIRCLE_APPROXIMATION_LENGTH / d); + // start of approximation + start = tw->start; + // trace circle approximation steps through the BSP tree + for ( i = 0; i < numSteps; i++ ) { + // calculate next point on approximated circle + rot.Set( tw->origin, tw->axis, tw->angle * ((float) (i+1) / numSteps) ); + end = start * rot; + // trace through spatial subdivision and then through leafs + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, start, end ); + // no need to continue if something was hit already + if ( tw->trace.fraction < 1.0f ) { + return; + } + start = end; + } + } + else { + start = tw->start; + } + // last step of the approximation + idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, start, tw->end ); + } +} diff --git a/src/cm/collisionmodel_translate.cpp b/src/cm/collisionmodel_translate.cpp new file mode 100644 index 0000000..8f0b224 --- /dev/null +++ b/src/cm/collisionmodel_translate.cpp @@ -0,0 +1,237 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#if defined( Q4_CM_LEGACY_SEED ) +#include "collisionmodel_translate_legacy.inc" +#else + +/* +=========================================================================== + +Quake 4 object-based translation helpers reconstructed in the exact +CollisionModel_translate.obj source owner identified by quake4.pdb. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +void idCollisionModelManagerLocal::SetupTrm( cm_traceWork_t *traceWork, const idTraceModel *trm ) { + traceWork->numVerts = trm->numVerts; + for ( int i = 0; i < trm->numVerts; i++ ) { + traceWork->vertices[i].p = trm->verts[i]; + traceWork->vertices[i].used = false; + } + + traceWork->numEdges = trm->numEdges; + for ( int i = 1; i <= trm->numEdges; i++ ) { + traceWork->edges[i].vertexNum[0] = trm->edges[i].v[0]; + traceWork->edges[i].vertexNum[1] = trm->edges[i].v[1]; + traceWork->edges[i].used = false; + } + + traceWork->numPolys = trm->numPolys; + for ( int i = 0; i < trm->numPolys; i++ ) { + traceWork->polys[i].numEdges = trm->polys[i].numEdges; + for ( int j = 0; j < trm->polys[i].numEdges; j++ ) { + traceWork->polys[i].edges[j] = trm->polys[i].edges[j]; + } + traceWork->polys[i].plane.SetNormal( trm->polys[i].normal ); + traceWork->polys[i].used = false; + } + traceWork->isConvex = trm->isConvex; +} + +void idCollisionModelManagerLocal::CM_GetCollisionPointTexCoords( idVec2 &texCoord, + cm_traceWork_t *traceWork, cm_polygon_t *polygon ) { + texCoord.Set( 0.5f, 0.5f ); + const idVec3 size = polygon->bounds[1] - polygon->bounds[0]; + int dimension = size.y > size.x ? 1 : 0; + if ( size.z > size[dimension] ) { + dimension = 2; + } + + idVec3 triangle[3]; + triangle[0] = polygon->bounds[0]; + triangle[1] = polygon->bounds[1]; + triangle[2] = polygon->bounds[0]; + triangle[2][dimension] = polygon->bounds[1][dimension]; + const float area = idMath::BarycentricTriangleArea( polygon->plane.Normal(), + triangle[0], triangle[1], triangle[2] ); + if ( area != 0.0f ) { + idMath::BarycentricEvaluate( texCoord, traceWork->trace.c.point, + polygon->plane.Normal(), area, triangle, polygon->texBounds ); + } +} + +void idCollisionModelManagerLocal::CM_GetMaterialType( cm_traceWork_t *traceWork, cm_polygon_t *polygon ) { + const idMaterial *material = traceWork->trace.c.material; + traceWork->trace.c.materialType = NULL; + if ( material == NULL ) { + return; + } + if ( polygon != NULL && material->GetMaterialTypeArray() != NULL ) { + idVec2 texCoord; + CM_GetCollisionPointTexCoords( texCoord, traceWork, polygon ); + traceWork->trace.c.materialType = material->GetMaterialType( texCoord ); + } else { + traceWork->trace.c.materialType = material->GetMaterialType(); + } +} + +bool idCollisionModelManagerLocal::TranslateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *polygon ) { + if ( polygon == NULL || polygon->checkcount == checkCount ) { + return false; + } + polygon->checkcount = checkCount; + if ( !( polygon->contents & tw->contents ) || + !tw->traceBounds.IntersectsBounds( polygon->bounds ) || + polygon->plane.Normal() * tw->dir >= 0.0f ) { + return false; + } + + // Find the trace-model support point closest to the polygon plane. + idVec3 support; + for ( int axis = 0; axis < 3; ++axis ) { + support[axis] = polygon->plane[axis] >= 0.0f ? tw->trmBounds[0][axis] : tw->trmBounds[1][axis]; + } + const float supportDistance = support * polygon->plane.Normal(); + const float startDistance = polygon->plane.Distance( tw->start ) + supportDistance; + const float endDistance = polygon->plane.Distance( tw->end ) + supportDistance; + if ( startDistance <= CM_CLIP_EPSILON || endDistance >= startDistance ) { + return false; + } + float fraction = ( startDistance - CM_CLIP_EPSILON ) / ( startDistance - endDistance ); + fraction = idMath::ClampFloat( 0.0f, tw->trace.fraction, fraction ); + const idVec3 contactPoint = tw->start + fraction * tw->dir + support; + const idBounds expanded = polygon->bounds.Expand( CM_BOX_EPSILON ); + if ( !expanded.ContainsPoint( contactPoint ) ) { + return false; + } + + tw->trace.fraction = fraction; + tw->trace.c.normal = polygon->plane.Normal(); + tw->trace.c.dist = polygon->plane.Dist(); + tw->trace.c.contents = polygon->contents; + tw->trace.c.material = polygon->material; + tw->trace.c.type = tw->pointTrace ? CONTACT_TRMVERTEX : CONTACT_EDGE; + tw->trace.c.point = contactPoint; + tw->trace.c.modelFeature = polygon->primitiveNum; + tw->trace.c.trmFeature = 0; + CM_GetMaterialType( tw, polygon ); + if ( tw->getContacts && tw->contacts != NULL && tw->numContacts < tw->maxContacts ) { + tw->contacts[tw->numContacts++] = tw->trace.c; + } + return fraction == 0.0f; +} + +void idCollisionModelManagerLocal::Translation( trace_t *results, const idVec3 &start, + const idVec3 &end, const idTraceModel *trm, const idMat3 &trmAxis, + int contentMask, idCollisionModel *collisionModel, const idVec3 &modelOrigin, + const idMat3 &modelAxis ) { + memset( results, 0, sizeof( *results ) ); + results->fraction = 1.0f; + results->endpos = end; + results->endAxis = trmAxis; + if ( collisionModel == NULL ) { + return; + } + if ( start == end ) { + ContentsTrm( results, start, trm, trmAxis, contentMask, + collisionModel, modelOrigin, modelAxis ); + return; + } + const bool pointTrace = trm == NULL || trm->bounds.GetVolume() <= 0.0f; + if ( !pointTrace && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { + results->fraction = 0.0f; + results->endpos = start; + results->c.point = start; + common->Printf( "idCollisionModelManagerLocal::Translation: huge translation\n" ); + return; + } + + ++checkCount; + ALIGN16( cm_traceWork_t tw ); + memset( &tw, 0, sizeof( tw ) ); + tw.trace.fraction = 1.0f; + tw.trace.c.type = CONTACT_NONE; + tw.contents = contentMask; + tw.model = static_cast( collisionModel ); + tw.rotation = false; + tw.positionTest = false; + tw.pointTrace = pointTrace; + tw.isConvex = trm == NULL || trm->isConvex; + tw.getContacts = getContacts; + tw.contacts = contacts; + tw.maxContacts = maxContacts; + tw.start = start - modelOrigin; + tw.end = end - modelOrigin; + + idMat3 inverseModelAxis = mat3_identity; + const bool modelRotated = modelAxis.IsRotated(); + if ( modelRotated ) { + inverseModelAxis = modelAxis.Transpose(); + tw.start *= inverseModelAxis; + tw.end *= inverseModelAxis; + } + tw.dir = tw.end - tw.start; + if ( pointTrace ) { + tw.trmBounds.Zero(); + } else { + idMat3 localTraceAxis = trmAxis; + if ( modelRotated ) { + localTraceAxis *= inverseModelAxis; + } + tw.trmBounds.FromTransformedBounds( trm->bounds, vec3_origin, localTraceAxis ); + } + for ( int i = 0; i < 3; ++i ) { + if ( tw.start[i] < tw.end[i] ) { + tw.traceBounds[0][i] = tw.start[i] + tw.trmBounds[0][i] - CM_BOX_EPSILON; + tw.traceBounds[1][i] = tw.end[i] + tw.trmBounds[1][i] + CM_BOX_EPSILON; + } else { + tw.traceBounds[0][i] = tw.end[i] + tw.trmBounds[0][i] - CM_BOX_EPSILON; + tw.traceBounds[1][i] = tw.start[i] + tw.trmBounds[1][i] + CM_BOX_EPSILON; + } + tw.trmExtents[i] = Max( idMath::Fabs( tw.trmBounds[0][i] ), + idMath::Fabs( tw.trmBounds[1][i] ) ) + CM_BOX_EPSILON; + } + TraceThroughModel( &tw ); + *results = tw.trace; + results->endpos = start + results->fraction * ( end - start ); + results->endAxis = trmAxis; + if ( results->fraction < 1.0f ) { + if ( modelRotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } + numContacts = tw.numContacts; +} + +#endif diff --git a/src/cm/collisionmodel_translate_legacy.inc b/src/cm/collisionmodel_translate_legacy.inc new file mode 100644 index 0000000..febf740 --- /dev/null +++ b/src/cm/collisionmodel_translate_legacy.inc @@ -0,0 +1,1116 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Trace model vs. polygonal model collision detection. + +=============================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "CollisionModel_local.h" + +/* +=============================================================================== + +Collision detection for translational motion + +=============================================================================== +*/ + +/* +================ +idCollisionModelManagerLocal::TranslateEdgeThroughEdge + + calculates fraction of the translation completed at which the edges collide +================ +*/ +ID_INLINE int idCollisionModelManagerLocal::TranslateEdgeThroughEdge( idVec3 &cross, idPluecker &l1, idPluecker &l2, float *fraction ) { + + float d, t; + + /* + + a = start of line + b = end of line + dir = movement direction + l1 = pluecker coordinate for line + l2 = pluecker coordinate for edge we might collide with + a+dir = start of line after movement + b+dir = end of line after movement + t = scale factor + solve pluecker inner product for t of line (a+t*dir : b+t*dir) and line l2 + + v[0] = (a[0]+t*dir[0]) * (b[1]+t*dir[1]) - (b[0]+t*dir[0]) * (a[1]+t*dir[1]); + v[1] = (a[0]+t*dir[0]) * (b[2]+t*dir[2]) - (b[0]+t*dir[0]) * (a[2]+t*dir[2]); + v[2] = (a[0]+t*dir[0]) - (b[0]+t*dir[0]); + v[3] = (a[1]+t*dir[1]) * (b[2]+t*dir[2]) - (b[1]+t*dir[1]) * (a[2]+t*dir[2]); + v[4] = (a[2]+t*dir[2]) - (b[2]+t*dir[2]); + v[5] = (b[1]+t*dir[1]) - (a[1]+t*dir[1]); + + l2[0] * v[4] + l2[1] * v[5] + l2[2] * v[3] + l2[4] * v[0] + l2[5] * v[1] + l2[3] * v[2] = 0; + + solve t + + v[0] = (a[0]+t*dir[0]) * (b[1]+t*dir[1]) - (b[0]+t*dir[0]) * (a[1]+t*dir[1]); + v[0] = (a[0]*b[1]) + a[0]*t*dir[1] + b[1]*t*dir[0] + (t*t*dir[0]*dir[1]) - + ((b[0]*a[1]) + b[0]*t*dir[1] + a[1]*t*dir[0] + (t*t*dir[0]*dir[1])); + v[0] = a[0]*b[1] + a[0]*t*dir[1] + b[1]*t*dir[0] - b[0]*a[1] - b[0]*t*dir[1] - a[1]*t*dir[0]; + + v[1] = (a[0]+t*dir[0]) * (b[2]+t*dir[2]) - (b[0]+t*dir[0]) * (a[2]+t*dir[2]); + v[1] = (a[0]*b[2]) + a[0]*t*dir[2] + b[2]*t*dir[0] + (t*t*dir[0]*dir[2]) - + ((b[0]*a[2]) + b[0]*t*dir[2] + a[2]*t*dir[0] + (t*t*dir[0]*dir[2])); + v[1] = a[0]*b[2] + a[0]*t*dir[2] + b[2]*t*dir[0] - b[0]*a[2] - b[0]*t*dir[2] - a[2]*t*dir[0]; + + v[2] = (a[0]+t*dir[0]) - (b[0]+t*dir[0]); + v[2] = a[0] - b[0]; + + v[3] = (a[1]+t*dir[1]) * (b[2]+t*dir[2]) - (b[1]+t*dir[1]) * (a[2]+t*dir[2]); + v[3] = (a[1]*b[2]) + a[1]*t*dir[2] + b[2]*t*dir[1] + (t*t*dir[1]*dir[2]) - + ((b[1]*a[2]) + b[1]*t*dir[2] + a[2]*t*dir[1] + (t*t*dir[1]*dir[2])); + v[3] = a[1]*b[2] + a[1]*t*dir[2] + b[2]*t*dir[1] - b[1]*a[2] - b[1]*t*dir[2] - a[2]*t*dir[1]; + + v[4] = (a[2]+t*dir[2]) - (b[2]+t*dir[2]); + v[4] = a[2] - b[2]; + + v[5] = (b[1]+t*dir[1]) - (a[1]+t*dir[1]); + v[5] = b[1] - a[1]; + + + v[0] = a[0]*b[1] + a[0]*t*dir[1] + b[1]*t*dir[0] - b[0]*a[1] - b[0]*t*dir[1] - a[1]*t*dir[0]; + v[1] = a[0]*b[2] + a[0]*t*dir[2] + b[2]*t*dir[0] - b[0]*a[2] - b[0]*t*dir[2] - a[2]*t*dir[0]; + v[2] = a[0] - b[0]; + v[3] = a[1]*b[2] + a[1]*t*dir[2] + b[2]*t*dir[1] - b[1]*a[2] - b[1]*t*dir[2] - a[2]*t*dir[1]; + v[4] = a[2] - b[2]; + v[5] = b[1] - a[1]; + + v[0] = (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) * t + a[0]*b[1] - b[0]*a[1]; + v[1] = (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) * t + a[0]*b[2] - b[0]*a[2]; + v[2] = a[0] - b[0]; + v[3] = (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]) * t + a[1]*b[2] - b[1]*a[2]; + v[4] = a[2] - b[2]; + v[5] = b[1] - a[1]; + + l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) * t + l2[4] * (a[0]*b[1] - b[0]*a[1]) + + l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) * t + l2[5] * (a[0]*b[2] - b[0]*a[2]) + + l2[3] * (a[0] - b[0]) + + l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]) * t + l2[2] * (a[1]*b[2] - b[1]*a[2]) + + l2[0] * (a[2] - b[2]) + + l2[1] * (b[1] - a[1]) = 0 + + t = (- l2[4] * (a[0]*b[1] - b[0]*a[1]) - + l2[5] * (a[0]*b[2] - b[0]*a[2]) - + l2[3] * (a[0] - b[0]) - + l2[2] * (a[1]*b[2] - b[1]*a[2]) - + l2[0] * (a[2] - b[2]) - + l2[1] * (b[1] - a[1])) / + (l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) + + l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) + + l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1])); + + d = l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) + + l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) + + l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]); + + t = - ( l2[4] * (a[0]*b[1] - b[0]*a[1]) + + l2[5] * (a[0]*b[2] - b[0]*a[2]) + + l2[3] * (a[0] - b[0]) + + l2[2] * (a[1]*b[2] - b[1]*a[2]) + + l2[0] * (a[2] - b[2]) + + l2[1] * (b[1] - a[1])); + t /= d; + + MrE pats Pluecker on the head.. good monkey + + edgeDir = a - b; + d = l2[4] * (edgeDir[0]*dir[1] - edgeDir[1]*dir[0]) + + l2[5] * (edgeDir[0]*dir[2] - edgeDir[2]*dir[0]) + + l2[2] * (edgeDir[1]*dir[2] - edgeDir[2]*dir[1]); + */ + + d = l2[4] * cross[0] + l2[5] * cross[1] + l2[2] * cross[2]; + + if ( d == 0.0f ) { + *fraction = 1.0f; + // no collision ever + return false; + } + + t = -l1.PermutedInnerProduct( l2 ); + // if the lines cross each other to begin with + if ( t == 0.0f ) { + *fraction = 0.0f; + return true; + } + // fraction of movement at the time the lines cross each other + *fraction = t / d; + return true; +} + +/* +================ +CM_AddContact +================ +*/ +ID_INLINE void CM_AddContact( cm_traceWork_t *tw ) { + + if ( tw->numContacts >= tw->maxContacts ) { + return; + } + // copy contact information from trace_t + tw->contacts[tw->numContacts] = tw->trace.c; + tw->numContacts++; + // set fraction back to 1 to find all other contacts + tw->trace.fraction = 1.0f; +} + +/* +================ +CM_SetVertexSidedness + + stores for the given model vertex at which side of one of the trm edges it passes +================ +*/ +ID_INLINE void CM_SetVertexSidedness( cm_vertex_t *v, const idPluecker &vpl, const idPluecker &epl, const int bitNum ) { + if ( !(v->sideSet & (1<side = (v->side & ~(1<sideSet |= (1 << bitNum); + } +} + +/* +================ +CM_SetEdgeSidedness + + stores for the given model edge at which side one of the trm vertices +================ +*/ +ID_INLINE void CM_SetEdgeSidedness( cm_edge_t *edge, const idPluecker &vpl, const idPluecker &epl, const int bitNum ) { + if ( !(edge->sideSet & (1<side = (edge->side & ~(1<sideSet |= (1 << bitNum); + } +} + +/* +================ +idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ) { + int i, edgeNum; + float f1, f2, dist, d1, d2; + idVec3 start, end, normal; + cm_edge_t *edge; + cm_vertex_t *v1, *v2; + idPluecker *pl, epsPl; + + // check edges for a collision + for ( i = 0; i < poly->numEdges; i++) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // if this edge is already checked + if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // can never collide with internal edges + if ( edge->internal ) { + continue; + } + pl = &tw->polygonEdgePlueckerCache[i]; + // get the sides at which the trm edge vertices pass the polygon edge + CM_SetEdgeSidedness( edge, *pl, tw->vertices[trmEdge->vertexNum[0]].pl, trmEdge->vertexNum[0] ); + CM_SetEdgeSidedness( edge, *pl, tw->vertices[trmEdge->vertexNum[1]].pl, trmEdge->vertexNum[1] ); + // if the trm edge start and end vertex do not pass the polygon edge at different sides + if ( !(((edge->side >> trmEdge->vertexNum[0]) ^ (edge->side >> trmEdge->vertexNum[1])) & 1) ) { + continue; + } + // get the sides at which the polygon edge vertices pass the trm edge + v1 = tw->model->vertices + edge->vertexNum[INTSIGNBITSET(edgeNum)]; + CM_SetVertexSidedness( v1, tw->polygonVertexPlueckerCache[i], trmEdge->pl, trmEdge->bitNum ); + v2 = tw->model->vertices + edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]; + CM_SetVertexSidedness( v2, tw->polygonVertexPlueckerCache[i+1], trmEdge->pl, trmEdge->bitNum ); + // if the polygon edge start and end vertex do not pass the trm edge at different sides + if ( !((v1->side ^ v2->side) & (1<bitNum)) ) { + continue; + } + // if there is no possible collision between the trm edge and the polygon edge + if ( !idCollisionModelManagerLocal::TranslateEdgeThroughEdge( trmEdge->cross, trmEdge->pl, *pl, &f1 ) ) { + continue; + } + // if moving away from edge + if ( f1 < 0.0f ) { + continue; + } + + // pluecker coordinate for epsilon expanded edge + epsPl.FromLine( tw->model->vertices[edge->vertexNum[0]].p + edge->normal * CM_CLIP_EPSILON, + tw->model->vertices[edge->vertexNum[1]].p + edge->normal * CM_CLIP_EPSILON ); + // calculate collision fraction with epsilon expanded edge + if ( !idCollisionModelManagerLocal::TranslateEdgeThroughEdge( trmEdge->cross, trmEdge->pl, epsPl, &f2 ) ) { + continue; + } + // if no collision with epsilon edge or moving away from edge + if ( f2 > 1.0f || f1 < f2 ) { + continue; + } + + if ( f2 < 0.0f ) { + f2 = 0.0f; + } + + if ( f2 < tw->trace.fraction ) { + tw->trace.fraction = f2; + // create plane with normal vector orthogonal to both the polygon edge and the trm edge + start = tw->model->vertices[edge->vertexNum[0]].p; + end = tw->model->vertices[edge->vertexNum[1]].p; + tw->trace.c.normal = ( end - start ).Cross( trmEdge->end - trmEdge->start ); + // FIXME: do this normalize when we know the first collision + tw->trace.c.normal.Normalize(); + tw->trace.c.dist = tw->trace.c.normal * start; + // make sure the collision plane faces the trace model + if ( tw->trace.c.normal * trmEdge->start - tw->trace.c.dist < 0.0f ) { + tw->trace.c.normal = -tw->trace.c.normal; + tw->trace.c.dist = -tw->trace.c.dist; + } + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_EDGE; + tw->trace.c.modelFeature = edgeNum; + tw->trace.c.trmFeature = trmEdge - tw->edges; + // calculate collision point + normal[0] = trmEdge->cross[2]; + normal[1] = -trmEdge->cross[1]; + normal[2] = trmEdge->cross[0]; + dist = normal * trmEdge->start; + d1 = normal * start - dist; + d2 = normal * end - dist; + f1 = d1 / ( d1 - d2 ); + //assert( f1 >= 0.0f && f1 <= 1.0f ); + tw->trace.c.point = start + f1 * ( end - start ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + } + } + } +} + +/* +================ +CM_TranslationPlaneFraction +================ +*/ + +#if 0 + +float CM_TranslationPlaneFraction( idPlane &plane, idVec3 &start, idVec3 &end ) { + float d1, d2; + + d2 = plane.Distance( end ); + // if the end point is closer to the plane than an epsilon we still take it for a collision + if ( d2 >= CM_CLIP_EPSILON ) { + return 1.0f; + } + d1 = plane.Distance( start ); + + // if completely behind the polygon + if ( d1 <= 0.0f ) { + return 1.0f; + } + // leaves polygon + if ( d1 <= d2 ) { + return 1.0f; + } + return (d1-CM_CLIP_EPSILON) / (d1-d2); +} + +#else + +float CM_TranslationPlaneFraction( idPlane &plane, idVec3 &start, idVec3 &end ) { + float d1, d2, d2eps; + + d2 = plane.Distance( end ); + // if the end point is closer to the plane than an epsilon we still take it for a collision + // if ( d2 >= CM_CLIP_EPSILON ) { + d2eps = d2 - CM_CLIP_EPSILON; + if ( FLOATSIGNBITNOTSET(d2eps) ) { + return 1.0f; + } + d1 = plane.Distance( start ); + + // if completely behind the polygon + if ( FLOATSIGNBITSET(d1) ) { + return 1.0f; + } + // if going towards the front of the plane and + // the start and end point are not at equal distance from the plane + // if ( d1 > d2 ) + d2 = d1 - d2; + if ( d2 <= 0.0f ) { + return 1.0f; + } + return (d1-CM_CLIP_EPSILON) / d2; +} + +#endif + +/* +================ +idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int bitNum ) { + int i, edgeNum; + float f; + cm_edge_t *edge; + + f = CM_TranslationPlaneFraction( poly->plane, v->p, v->endp ); + if ( f < tw->trace.fraction ) { + + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + CM_SetEdgeSidedness( edge, tw->polygonEdgePlueckerCache[i], v->pl, bitNum ); + if ( INTSIGNBITSET(edgeNum) ^ ((edge->side >> bitNum) & 1) ) { + return; + } + } + if ( f < 0.0f ) { + f = 0.0f; + } + tw->trace.fraction = f; + // collision plane is the polygon plane + tw->trace.c.normal = poly->plane.Normal(); + tw->trace.c.dist = poly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.modelFeature = *reinterpret_cast(&poly); + tw->trace.c.trmFeature = v - tw->vertices; + tw->trace.c.point = v->p + tw->trace.fraction * ( v->endp - v->p ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + // no need to store the trm vertex more than once as a contact + v->used = false; + } + } +} + +/* +================ +idCollisionModelManagerLocal::TranslatePointThroughPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslatePointThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v ) { + int i, edgeNum; + float f; + cm_edge_t *edge; + idPluecker pl; + + f = CM_TranslationPlaneFraction( poly->plane, v->p, v->endp ); + if ( f < tw->trace.fraction ) { + + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + edge = tw->model->edges + abs(edgeNum); + // if we didn't yet calculate the sidedness for this edge + if ( edge->checkcount != idCollisionModelManagerLocal::checkCount ) { + float fl; + edge->checkcount = idCollisionModelManagerLocal::checkCount; + pl.FromLine(tw->model->vertices[edge->vertexNum[0]].p, tw->model->vertices[edge->vertexNum[1]].p); + fl = v->pl.PermutedInnerProduct( pl ); + edge->side = FLOATSIGNBITSET(fl); + } + // if the point passes the edge at the wrong side + //if ( (edgeNum > 0) == edge->side ) { + if ( INTSIGNBITSET(edgeNum) ^ edge->side ) { + return; + } + } + if ( f < 0.0f ) { + f = 0.0f; + } + tw->trace.fraction = f; + // collision plane is the polygon plane + tw->trace.c.normal = poly->plane.Normal(); + tw->trace.c.dist = poly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_TRMVERTEX; + tw->trace.c.modelFeature = *reinterpret_cast(&poly); + tw->trace.c.trmFeature = v - tw->vertices; + tw->trace.c.point = v->p + tw->trace.fraction * ( v->endp - v->p ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + // no need to store the trm vertex more than once as a contact + v->used = false; + } + } +} + +/* +================ +idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon +================ +*/ +void idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &endp, idPluecker &pl ) { + int i, edgeNum; + float f; + cm_trmEdge_t *edge; + + f = CM_TranslationPlaneFraction( trmpoly->plane, v->p, endp ); + if ( f < tw->trace.fraction ) { + + for ( i = 0; i < trmpoly->numEdges; i++ ) { + edgeNum = trmpoly->edges[i]; + edge = tw->edges + abs(edgeNum); + + CM_SetVertexSidedness( v, pl, edge->pl, edge->bitNum ); + if ( INTSIGNBITSET(edgeNum) ^ ((v->side >> edge->bitNum) & 1) ) { + return; + } + } + if ( f < 0.0f ) { + f = 0.0f; + } + tw->trace.fraction = f; + // collision plane is the inverse trm polygon plane + tw->trace.c.normal = -trmpoly->plane.Normal(); + tw->trace.c.dist = -trmpoly->plane.Dist(); + tw->trace.c.contents = poly->contents; + tw->trace.c.material = poly->material; + tw->trace.c.type = CONTACT_MODELVERTEX; + tw->trace.c.modelFeature = v - tw->model->vertices; + tw->trace.c.trmFeature = trmpoly - tw->polys; + tw->trace.c.point = v->p + tw->trace.fraction * ( endp - v->p ); + // if retrieving contacts + if ( tw->getContacts ) { + CM_AddContact( tw ); + } + } +} + +/* +================ +idCollisionModelManagerLocal::TranslateTrmThroughPolygon + + returns true if the polygon blocks the complete translation +================ +*/ +bool idCollisionModelManagerLocal::TranslateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) { + int i, j, k, edgeNum; + float fraction, d; + idVec3 endp; + idPluecker *pl; + cm_trmVertex_t *bv; + cm_trmEdge_t *be; + cm_trmPolygon_t *bp; + cm_vertex_t *v; + cm_edge_t *e; + + // if already checked this polygon + if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) { + return false; + } + p->checkcount = idCollisionModelManagerLocal::checkCount; + + // if this polygon does not have the right contents behind it + if ( !(p->contents & tw->contents) ) { + return false; + } + + // if the the trace bounds do not intersect the polygon bounds + if ( !tw->bounds.IntersectsBounds( p->bounds ) ) { + return false; + } + + // only collide with the polygon if approaching at the front + if ( ( p->plane.Normal() * tw->dir ) > 0.0f ) { + return false; + } + + // if the polygon is too far from the first heart plane + d = p->bounds.PlaneDistance( tw->heartPlane1 ); + if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane1 ) { + return false; + } + + // if the polygon is too far from the second heart plane + d = p->bounds.PlaneDistance( tw->heartPlane2 ); + if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane2 ) { + return false; + } + fraction = tw->trace.fraction; + + // fast point trace + if ( tw->pointTrace ) { + idCollisionModelManagerLocal::TranslatePointThroughPolygon( tw, p, &tw->vertices[0] ); + } + else { + + // trace bounds should cross polygon plane + switch ( tw->bounds.PlaneSide( p->plane ) ) { + case PLANESIDE_CROSS: + break; + case PLANESIDE_FRONT: + if ( tw->model->isConvex ) { + tw->quickExit = true; + return true; + } + default: + return false; + } + + // calculate pluecker coordinates for the polygon edges and polygon vertices + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + // reset sidedness cache if this is the first time we encounter this edge during this trace + if ( e->checkcount != idCollisionModelManagerLocal::checkCount ) { + e->sideSet = 0; + } + // pluecker coordinate for edge + tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[e->vertexNum[0]].p, + tw->model->vertices[e->vertexNum[1]].p ); + + v = &tw->model->vertices[e->vertexNum[INTSIGNBITSET(edgeNum)]]; + // reset sidedness cache if this is the first time we encounter this vertex during this trace + if ( v->checkcount != idCollisionModelManagerLocal::checkCount ) { + v->sideSet = 0; + } + // pluecker coordinate for vertex movement vector + tw->polygonVertexPlueckerCache[i].FromRay( v->p, -tw->dir ); + } + // copy first to last so we can easily cycle through for the edges + tw->polygonVertexPlueckerCache[p->numEdges] = tw->polygonVertexPlueckerCache[0]; + + // trace trm vertices through polygon + for ( i = 0; i < tw->numVerts; i++ ) { + bv = tw->vertices + i; + if ( bv->used ) { + idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon( tw, p, bv, i ); + } + } + + // trace trm edges through polygon + for ( i = 1; i <= tw->numEdges; i++ ) { + be = tw->edges + i; + if ( be->used ) { + idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon( tw, p, be); + } + } + + // trace all polygon vertices through the trm + for ( i = 0; i < p->numEdges; i++ ) { + edgeNum = p->edges[i]; + e = tw->model->edges + abs(edgeNum); + + if ( e->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set edge check count + e->checkcount = idCollisionModelManagerLocal::checkCount; + // can never collide with internal edges + if ( e->internal ) { + continue; + } + // got to check both vertices because we skip internal edges + for ( k = 0; k < 2; k++ ) { + + v = tw->model->vertices + e->vertexNum[k ^ INTSIGNBITSET(edgeNum)]; + // if this vertex is already checked + if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) { + continue; + } + // set vertex check count + v->checkcount = idCollisionModelManagerLocal::checkCount; + + // if the vertex is outside the trace bounds + if ( !tw->bounds.ContainsPoint( v->p ) ) { + continue; + } + + // vertex end point after movement + endp = v->p - tw->dir; + // pluecker coordinate for vertex movement vector + pl = &tw->polygonVertexPlueckerCache[i+k]; + + for ( j = 0; j < tw->numPolys; j++ ) { + bp = tw->polys + j; + if ( bp->used ) { + idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon( tw, bp, p, v, endp, *pl ); + } + } + } + } + } + + // if there was a collision with this polygon and we are not retrieving contacts + if ( tw->trace.fraction < fraction && !tw->getContacts ) { + fraction = tw->trace.fraction; + endp = tw->start + fraction * tw->dir; + // decrease bounds + for ( i = 0; i < 3; i++ ) { + if ( tw->start[i] < endp[i] ) { + tw->bounds[0][i] = tw->start[i] + tw->size[0][i] - CM_BOX_EPSILON; + tw->bounds[1][i] = endp[i] + tw->size[1][i] + CM_BOX_EPSILON; + } + else { + tw->bounds[0][i] = endp[i] + tw->size[0][i] - CM_BOX_EPSILON; + tw->bounds[1][i] = tw->start[i] + tw->size[1][i] + CM_BOX_EPSILON; + } + } + } + + return ( tw->trace.fraction == 0.0f ); +} + +/* +================ +idCollisionModelManagerLocal::SetupTrm +================ +*/ +void idCollisionModelManagerLocal::SetupTrm( cm_traceWork_t *tw, const idTraceModel *trm ) { + int i, j; + + // vertices + tw->numVerts = trm->numVerts; + for ( i = 0; i < trm->numVerts; i++ ) { + tw->vertices[i].p = trm->verts[i]; + tw->vertices[i].used = false; + } + // edges + tw->numEdges = trm->numEdges; + for ( i = 1; i <= trm->numEdges; i++ ) { + tw->edges[i].vertexNum[0] = trm->edges[i].v[0]; + tw->edges[i].vertexNum[1] = trm->edges[i].v[1]; + tw->edges[i].used = false; + } + // polygons + tw->numPolys = trm->numPolys; + for ( i = 0; i < trm->numPolys; i++ ) { + tw->polys[i].numEdges = trm->polys[i].numEdges; + for ( j = 0; j < trm->polys[i].numEdges; j++ ) { + tw->polys[i].edges[j] = trm->polys[i].edges[j]; + } + tw->polys[i].plane.SetNormal( trm->polys[i].normal ); + tw->polys[i].used = false; + } + // is the trace model convex or not + tw->isConvex = trm->isConvex; +} + +/* +================ +idCollisionModelManagerLocal::SetupTranslationHeartPlanes +================ +*/ +void idCollisionModelManagerLocal::SetupTranslationHeartPlanes( cm_traceWork_t *tw ) { + idVec3 dir, normal1, normal2; + + // calculate trace heart planes + dir = tw->dir; + dir.Normalize(); + dir.NormalVectors( normal1, normal2 ); + tw->heartPlane1.SetNormal( normal1 ); + tw->heartPlane1.FitThroughPoint( tw->start ); + tw->heartPlane2.SetNormal( normal2 ); + tw->heartPlane2.FitThroughPoint( tw->start ); +} + +/* +================ +idCollisionModelManagerLocal::Translation +================ +*/ +#ifdef _DEBUG +static int entered = 0; +#endif + +void idCollisionModelManagerLocal::Translation( trace_t *results, const idVec3 &start, const idVec3 &end, + const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, + cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + + int i, j; + float dist; + bool model_rotated, trm_rotated; + idVec3 dir1, dir2, dir; + idMat3 invModelAxis, tmpAxis; + cm_trmPolygon_t *poly; + cm_trmEdge_t *edge; + cm_trmVertex_t *vert; + ALIGN16( static cm_traceWork_t tw ); + + assert( ((byte *)&start) < ((byte *)results) || ((byte *)&start) >= (((byte *)results) + sizeof( trace_t )) ); + assert( ((byte *)&end) < ((byte *)results) || ((byte *)&end) >= (((byte *)results) + sizeof( trace_t )) ); + assert( ((byte *)&trmAxis) < ((byte *)results) || ((byte *)&trmAxis) >= (((byte *)results) + sizeof( trace_t )) ); + + memset( results, 0, sizeof( *results ) ); + + if ( model < 0 || model > MAX_SUBMODELS || model > idCollisionModelManagerLocal::maxModels ) { + common->Printf("idCollisionModelManagerLocal::Translation: invalid model handle\n"); + return; + } + if ( !idCollisionModelManagerLocal::models[model] ) { + common->Printf("idCollisionModelManagerLocal::Translation: invalid model\n"); + return; + } + + // if case special position test + if ( start[0] == end[0] && start[1] == end[1] && start[2] == end[2] ) { + idCollisionModelManagerLocal::ContentsTrm( results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + return; + } + +#ifdef _DEBUG + bool startsolid = false; + // test whether or not stuck to begin with + if ( cm_debugCollision.GetBool() ) { + if ( !entered && !idCollisionModelManagerLocal::getContacts ) { + entered = 1; + // if already messed up to begin with + if ( idCollisionModelManagerLocal::Contents( start, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + startsolid = true; + } + entered = 0; + } + } +#endif + + idCollisionModelManagerLocal::checkCount++; + + tw.trace.fraction = 1.0f; + tw.trace.c.contents = 0; + tw.trace.c.type = CONTACT_NONE; + tw.contents = contentMask; + tw.isConvex = true; + tw.rotation = false; + tw.positionTest = false; + tw.quickExit = false; + tw.getContacts = idCollisionModelManagerLocal::getContacts; + tw.contacts = idCollisionModelManagerLocal::contacts; + tw.maxContacts = idCollisionModelManagerLocal::maxContacts; + tw.numContacts = 0; + tw.model = idCollisionModelManagerLocal::models[model]; + tw.start = start - modelOrigin; + tw.end = end - modelOrigin; + tw.dir = end - start; + + model_rotated = modelAxis.IsRotated(); + if ( model_rotated ) { + invModelAxis = modelAxis.Transpose(); + } + + // if optimized point trace + if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f && + trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f && + trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) { + + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + tw.end *= invModelAxis; + tw.dir *= invModelAxis; + } + + // trace bounds + for ( i = 0; i < 3; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.end[i] + CM_BOX_EPSILON; + } + else { + tw.bounds[0][i] = tw.end[i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.start[i] + CM_BOX_EPSILON; + } + } + tw.extents[0] = tw.extents[1] = tw.extents[2] = CM_BOX_EPSILON; + tw.size.Zero(); + + // setup trace heart planes + idCollisionModelManagerLocal::SetupTranslationHeartPlanes( &tw ); + tw.maxDistFromHeartPlane1 = CM_BOX_EPSILON; + tw.maxDistFromHeartPlane2 = CM_BOX_EPSILON; + // collision with single point + tw.numVerts = 1; + tw.vertices[0].p = tw.start; + tw.vertices[0].endp = tw.vertices[0].p + tw.dir; + tw.vertices[0].pl.FromRay( tw.vertices[0].p, tw.dir ); + tw.numEdges = tw.numPolys = 0; + tw.pointTrace = true; + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + // store results + *results = tw.trace; + results->endpos = start + results->fraction * (end - start); + results->endAxis = mat3_identity; + + if ( results->fraction < 1.0f ) { + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } + idCollisionModelManagerLocal::numContacts = tw.numContacts; + return; + } + + // the trace fraction is too inaccurate to describe translations over huge distances + if ( tw.dir.LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { + results->fraction = 0.0f; + results->endpos = start; + results->endAxis = trmAxis; + results->c.normal = vec3_origin; + results->c.material = NULL; + results->c.point = start; + if ( session->rw ) { + session->rw->DebugArrow( colorRed, start, end, 1 ); + } + common->Printf( "idCollisionModelManagerLocal::Translation: huge translation\n" ); + return; + } + + tw.pointTrace = false; + tw.size.Clear(); + + // setup trm structure + idCollisionModelManagerLocal::SetupTrm( &tw, trm ); + + trm_rotated = trmAxis.IsRotated(); + + // calculate vertex positions + if ( trm_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around the start position + tw.vertices[i].p *= trmAxis; + } + } + for ( i = 0; i < tw.numVerts; i++ ) { + // set trm at start position + tw.vertices[i].p += tw.start; + } + if ( model_rotated ) { + for ( i = 0; i < tw.numVerts; i++ ) { + // rotate trm around model instead of rotating the model + tw.vertices[i].p *= invModelAxis; + } + } + + // add offset to start point + if ( trm_rotated ) { + dir = trm->offset * trmAxis; + tw.start += dir; + tw.end += dir; + } else { + tw.start += trm->offset; + tw.end += trm->offset; + } + if ( model_rotated ) { + // rotate trace instead of model + tw.start *= invModelAxis; + tw.end *= invModelAxis; + tw.dir *= invModelAxis; + } + + // rotate trm polygon planes + if ( trm_rotated & model_rotated ) { + tmpAxis = trmAxis * invModelAxis; + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= tmpAxis; + } + } else if ( trm_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= trmAxis; + } + } else if ( model_rotated ) { + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + poly->plane *= invModelAxis; + } + } + + // setup trm polygons + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + // if the trm poly plane is facing in the movement direction + dist = poly->plane.Normal() * tw.dir; + if ( dist > 0.0f || ( !trm->isConvex && dist == 0.0f ) ) { + // this trm poly and it's edges and vertices need to be used for collision + poly->used = true; + for ( j = 0; j < poly->numEdges; j++ ) { + edge = &tw.edges[abs( poly->edges[j] )]; + edge->used = true; + tw.vertices[edge->vertexNum[0]].used = true; + tw.vertices[edge->vertexNum[1]].used = true; + } + } + } + + // setup trm vertices + for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) { + if ( !vert->used ) { + continue; + } + // get axial trm size after rotations + tw.size.AddPoint( vert->p - tw.start ); + // calculate the end position of each vertex for a full trace + vert->endp = vert->p + tw.dir; + // pluecker coordinate for vertex movement line + vert->pl.FromRay( vert->p, tw.dir ); + } + + // setup trm edges + for ( edge = tw.edges + 1, i = 1; i <= tw.numEdges; i++, edge++ ) { + if ( !edge->used ) { + continue; + } + // edge start, end and pluecker coordinate + edge->start = tw.vertices[edge->vertexNum[0]].p; + edge->end = tw.vertices[edge->vertexNum[1]].p; + edge->pl.FromLine( edge->start, edge->end ); + // calculate normal of plane through movement plane created by the edge + dir = edge->start - edge->end; + edge->cross[0] = dir[0] * tw.dir[1] - dir[1] * tw.dir[0]; + edge->cross[1] = dir[0] * tw.dir[2] - dir[2] * tw.dir[0]; + edge->cross[2] = dir[1] * tw.dir[2] - dir[2] * tw.dir[1]; + // bit for vertex sidedness bit cache + edge->bitNum = i; + } + + // set trm plane distances + for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) { + if ( poly->used ) { + poly->plane.FitThroughPoint( tw.edges[abs(poly->edges[0])].start ); + } + } + + // bounds for full trace, a little bit larger for epsilons + for ( i = 0; i < 3; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i] + CM_BOX_EPSILON; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i] - CM_BOX_EPSILON; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i] + CM_BOX_EPSILON; + } + if ( idMath::Fabs( tw.size[0][i] ) > idMath::Fabs( tw.size[1][i] ) ) { + tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + CM_BOX_EPSILON; + } else { + tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + CM_BOX_EPSILON; + } + } + + // setup trace heart planes + idCollisionModelManagerLocal::SetupTranslationHeartPlanes( &tw ); + tw.maxDistFromHeartPlane1 = 0; + tw.maxDistFromHeartPlane2 = 0; + // calculate maximum trm vertex distance from both heart planes + for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) { + if ( !vert->used ) { + continue; + } + dist = idMath::Fabs( tw.heartPlane1.Distance( vert->p ) ); + if ( dist > tw.maxDistFromHeartPlane1 ) { + tw.maxDistFromHeartPlane1 = dist; + } + dist = idMath::Fabs( tw.heartPlane2.Distance( vert->p ) ); + if ( dist > tw.maxDistFromHeartPlane2 ) { + tw.maxDistFromHeartPlane2 = dist; + } + } + // for epsilons + tw.maxDistFromHeartPlane1 += CM_BOX_EPSILON; + tw.maxDistFromHeartPlane2 += CM_BOX_EPSILON; + + // trace through the model + idCollisionModelManagerLocal::TraceThroughModel( &tw ); + + // if we're getting contacts + if ( tw.getContacts ) { + // move all contacts to world space + if ( model_rotated ) { + for ( i = 0; i < tw.numContacts; i++ ) { + tw.contacts[i].normal *= modelAxis; + tw.contacts[i].point *= modelAxis; + } + } + if ( modelOrigin != vec3_origin ) { + for ( i = 0; i < tw.numContacts; i++ ) { + tw.contacts[i].point += modelOrigin; + tw.contacts[i].dist += modelOrigin * tw.contacts[i].normal; + } + } + idCollisionModelManagerLocal::numContacts = tw.numContacts; + } else { + // store results + *results = tw.trace; + results->endpos = start + results->fraction * ( end - start ); + results->endAxis = trmAxis; + + if ( results->fraction < 1.0f ) { + // if the fraction is tiny the actual movement could end up zero + if ( results->fraction > 0.0f && results->endpos.Compare( start ) ) { + results->fraction = 0.0f; + } + // rotate trace plane normal if there was a collision with a rotated model + if ( model_rotated ) { + results->c.normal *= modelAxis; + results->c.point *= modelAxis; + } + results->c.point += modelOrigin; + results->c.dist += modelOrigin * results->c.normal; + } + } + +#ifdef _DEBUG + // test for missed collisions + if ( cm_debugCollision.GetBool() ) { + if ( !entered && !idCollisionModelManagerLocal::getContacts ) { + entered = 1; + // if the trm is stuck in the model + if ( idCollisionModelManagerLocal::Contents( results->endpos, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) { + trace_t tr; + + // test where the trm is stuck in the model + idCollisionModelManagerLocal::Contents( results->endpos, trm, trmAxis, -1, model, modelOrigin, modelAxis ); + // re-run collision detection to find out where it failed + idCollisionModelManagerLocal::Translation( &tr, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + entered = 0; + } + } +#endif +} diff --git a/src/curl/include/Makefile.am b/src/curl/include/Makefile.am new file mode 100644 index 0000000..3b24860 --- /dev/null +++ b/src/curl/include/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = curl + +EXTRA_DIST = README + +AUTOMAKE_OPTIONS = foreign no-dependencies diff --git a/src/curl/include/Makefile.in b/src/curl/include/Makefile.in new file mode 100644 index 0000000..1e57ab8 --- /dev/null +++ b/src/curl/include/Makefile.in @@ -0,0 +1,495 @@ +# Makefile.in generated by automake 1.8.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = .. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +host_triplet = @host@ +subdir = include +DIST_COMMON = README $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/lib/config.h \ + $(top_builddir)/src/config.h +CONFIG_CLEAN_FILES = +depcomp = +am__depfiles_maybe = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ + html-recursive info-recursive install-data-recursive \ + install-exec-recursive install-info-recursive \ + install-recursive installcheck-recursive installdirs-recursive \ + pdf-recursive ps-recursive uninstall-info-recursive \ + uninstall-recursive +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CABUNDLE_FALSE = @CABUNDLE_FALSE@ +CABUNDLE_TRUE = @CABUNDLE_TRUE@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CURL_CA_BUNDLE = @CURL_CA_BUNDLE@ +CURL_DISABLE_DICT = @CURL_DISABLE_DICT@ +CURL_DISABLE_FILE = @CURL_DISABLE_FILE@ +CURL_DISABLE_FTP = @CURL_DISABLE_FTP@ +CURL_DISABLE_GOPHER = @CURL_DISABLE_GOPHER@ +CURL_DISABLE_HTTP = @CURL_DISABLE_HTTP@ +CURL_DISABLE_LDAP = @CURL_DISABLE_LDAP@ +CURL_DISABLE_TELNET = @CURL_DISABLE_TELNET@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +HAVE_ARES = @HAVE_ARES@ +HAVE_LIBZ = @HAVE_LIBZ@ +HAVE_LIBZ_FALSE = @HAVE_LIBZ_FALSE@ +HAVE_LIBZ_TRUE = @HAVE_LIBZ_TRUE@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +IPV6_ENABLED = @IPV6_ENABLED@ +KRB4_ENABLED = @KRB4_ENABLED@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@ +MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@ +MAKEINFO = @MAKEINFO@ +MANOPT = @MANOPT@ +MIMPURE_FALSE = @MIMPURE_FALSE@ +MIMPURE_TRUE = @MIMPURE_TRUE@ +NO_UNDEFINED_FALSE = @NO_UNDEFINED_FALSE@ +NO_UNDEFINED_TRUE = @NO_UNDEFINED_TRUE@ +NROFF = @NROFF@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_ENABLED = @OPENSSL_ENABLED@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKGADD_NAME = @PKGADD_NAME@ +PKGADD_PKG = @PKGADD_PKG@ +PKGADD_VENDOR = @PKGADD_VENDOR@ +PKGCONFIG = @PKGCONFIG@ +RANDOM_FILE = @RANDOM_FILE@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +USE_MANUAL_FALSE = @USE_MANUAL_FALSE@ +USE_MANUAL_TRUE = @USE_MANUAL_TRUE@ +VERSION = @VERSION@ +VERSIONNUM = @VERSIONNUM@ +YACC = @YACC@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_AS = @ac_ct_AS@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DLLTOOL = @ac_ct_DLLTOOL@ +ac_ct_F77 = @ac_ct_F77@ +ac_ct_OBJDUMP = @ac_ct_OBJDUMP@ +ac_ct_RANLIB = @ac_ct_RANLIB@ +ac_ct_STRIP = @ac_ct_STRIP@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +SUBDIRS = curl +EXTRA_DIST = README +AUTOMAKE_OPTIONS = foreign no-dependencies +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: + +# This directory's subdirectories are mostly independent; you can cd +# into them and run `make' without going through this Makefile. +# To change the values of `make' variables: instead of editing Makefiles, +# (1) if the variable is set in `config.status', edit `config.status' +# (which will cause the Makefiles to be regenerated when you run `make'); +# (2) otherwise, pass the desired values on the `make' command line. +$(RECURSIVE_TARGETS): + @set fnord $$MAKEFLAGS; amf=$$2; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || case "$$amf" in *=*) exit 1;; *k*) fail=yes;; *) exit 1;; esac; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +mostlyclean-recursive clean-recursive distclean-recursive \ +maintainer-clean-recursive: + @set fnord $$MAKEFLAGS; amf=$$2; \ + dot_seen=no; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + rev=''; for subdir in $$list; do \ + if test "$$subdir" = "."; then :; else \ + rev="$$subdir $$rev"; \ + fi; \ + done; \ + rev="$$rev ."; \ + target=`echo $@ | sed s/-recursive//`; \ + for subdir in $$rev; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || case "$$amf" in *=*) exit 1;; *k*) fail=yes;; *) exit 1;; esac; \ + done && test -z "$$fail" +tags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) tags); \ + done +ctags-recursive: + list='$(SUBDIRS)'; for subdir in $$list; do \ + test "$$subdir" = . || (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) ctags); \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + if (etags --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + else \ + include_option=--include; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -f $$subdir/TAGS && \ + tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(ETAGS_ARGS)$$tags$$unique" \ + || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique +ctags: CTAGS +CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test -d "$(distdir)/$$subdir" \ + || mkdir "$(distdir)/$$subdir" \ + || exit 1; \ + (cd $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="../$(top_distdir)" \ + distdir="../$(distdir)/$$subdir" \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-libtool \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +info: info-recursive + +info-am: + +install-data-am: + +install-exec-am: + +install-info: install-info-recursive + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-info-am + +uninstall-info: uninstall-info-recursive + +.PHONY: $(RECURSIVE_TARGETS) CTAGS GTAGS all all-am check check-am \ + clean clean-generic clean-libtool clean-recursive ctags \ + ctags-recursive distclean distclean-generic distclean-libtool \ + distclean-recursive distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-exec install-exec-am install-info \ + install-info-am install-man install-strip installcheck \ + installcheck-am installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic maintainer-clean-recursive \ + mostlyclean mostlyclean-generic mostlyclean-libtool \ + mostlyclean-recursive pdf pdf-am ps ps-am tags tags-recursive \ + uninstall uninstall-am uninstall-info-am + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/curl/include/README b/src/curl/include/README new file mode 100644 index 0000000..f8482ba --- /dev/null +++ b/src/curl/include/README @@ -0,0 +1,38 @@ + _ _ ____ _ + ___| | | | _ \| | + / __| | | | |_) | | + | (__| |_| | _ <| |___ + \___|\___/|_| \_\_____| + +Include files for libcurl, external users. + +They're all placed in the curl subdirectory here for better fit in any kind +of environment. You should include files from here using... + + #include + +... style and point the compiler's include path to the directory holding the +curl subdirectory. It makes it more likely to survive future modifications. + +NOTE FOR LIBCURL HACKERS + +All the include files in this tree are written and intended to be installed on +a system that may serve multiple platforms and multiple applications, all +using libcurl (possibly even different libcurl installations using different +versions). Therefore, all header files in here must obey these rules: + +* They cannot depend on or use configure-generated results from libcurl's or + curl's directories. Other applications may not run configure as (lib)curl + does, and using platform dependent info here may break other platforms. + +* We cannot assume anything else but very basic compiler features being + present. While libcurl requires an ANSI C compiler to build, some of the + earlier ANSI compilers clearly can't deal with some preprocessor operators. + +* Newlines must remain unix-style for older compilers' sake. + +* Comments must be written in the old-style /* unnested C-fashion */ + +To figure out how to do good and portable checks for features, operating +systems or specific hardwarare, a very good resource is Bjorn Reese's +collection at http://predef.sf.net/ diff --git a/src/curl/include/curl/Makefile.am b/src/curl/include/curl/Makefile.am new file mode 100644 index 0000000..c45332b --- /dev/null +++ b/src/curl/include/curl/Makefile.am @@ -0,0 +1,8 @@ +pkginclude_HEADERS = \ + curl.h \ + easy.h \ + mprintf.h \ + stdcheaders.h \ + types.h \ + multi.h +pkgincludedir= $(includedir)/curl diff --git a/src/curl/include/curl/Makefile.in b/src/curl/include/curl/Makefile.in new file mode 100644 index 0000000..837374c --- /dev/null +++ b/src/curl/include/curl/Makefile.in @@ -0,0 +1,429 @@ +# Makefile.in generated by automake 1.8.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +top_builddir = ../.. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +host_triplet = @host@ +subdir = include/curl +DIST_COMMON = $(pkginclude_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/lib/config.h \ + $(top_builddir)/src/config.h +CONFIG_CLEAN_FILES = +SOURCES = +DIST_SOURCES = +am__installdirs = "$(DESTDIR)$(pkgincludedir)" +pkgincludeHEADERS_INSTALL = $(INSTALL_HEADER) +HEADERS = $(pkginclude_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +pkgincludedir = $(includedir)/curl +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AS = @AS@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CABUNDLE_FALSE = @CABUNDLE_FALSE@ +CABUNDLE_TRUE = @CABUNDLE_TRUE@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CURL_CA_BUNDLE = @CURL_CA_BUNDLE@ +CURL_DISABLE_DICT = @CURL_DISABLE_DICT@ +CURL_DISABLE_FILE = @CURL_DISABLE_FILE@ +CURL_DISABLE_FTP = @CURL_DISABLE_FTP@ +CURL_DISABLE_GOPHER = @CURL_DISABLE_GOPHER@ +CURL_DISABLE_HTTP = @CURL_DISABLE_HTTP@ +CURL_DISABLE_LDAP = @CURL_DISABLE_LDAP@ +CURL_DISABLE_TELNET = @CURL_DISABLE_TELNET@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +HAVE_ARES = @HAVE_ARES@ +HAVE_LIBZ = @HAVE_LIBZ@ +HAVE_LIBZ_FALSE = @HAVE_LIBZ_FALSE@ +HAVE_LIBZ_TRUE = @HAVE_LIBZ_TRUE@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +IPV6_ENABLED = @IPV6_ENABLED@ +KRB4_ENABLED = @KRB4_ENABLED@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAINTAINER_MODE_FALSE = @MAINTAINER_MODE_FALSE@ +MAINTAINER_MODE_TRUE = @MAINTAINER_MODE_TRUE@ +MAKEINFO = @MAKEINFO@ +MANOPT = @MANOPT@ +MIMPURE_FALSE = @MIMPURE_FALSE@ +MIMPURE_TRUE = @MIMPURE_TRUE@ +NO_UNDEFINED_FALSE = @NO_UNDEFINED_FALSE@ +NO_UNDEFINED_TRUE = @NO_UNDEFINED_TRUE@ +NROFF = @NROFF@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_ENABLED = @OPENSSL_ENABLED@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKGADD_NAME = @PKGADD_NAME@ +PKGADD_PKG = @PKGADD_PKG@ +PKGADD_VENDOR = @PKGADD_VENDOR@ +PKGCONFIG = @PKGCONFIG@ +RANDOM_FILE = @RANDOM_FILE@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +USE_MANUAL_FALSE = @USE_MANUAL_FALSE@ +USE_MANUAL_TRUE = @USE_MANUAL_TRUE@ +VERSION = @VERSION@ +VERSIONNUM = @VERSIONNUM@ +YACC = @YACC@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_AS = @ac_ct_AS@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DLLTOOL = @ac_ct_DLLTOOL@ +ac_ct_F77 = @ac_ct_F77@ +ac_ct_OBJDUMP = @ac_ct_OBJDUMP@ +ac_ct_RANLIB = @ac_ct_RANLIB@ +ac_ct_STRIP = @ac_ct_STRIP@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +pkginclude_HEADERS = \ + curl.h \ + easy.h \ + mprintf.h \ + stdcheaders.h \ + types.h \ + multi.h + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign include/curl/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --foreign include/curl/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: +install-pkgincludeHEADERS: $(pkginclude_HEADERS) + @$(NORMAL_INSTALL) + test -z "$(pkgincludedir)" || $(mkdir_p) "$(DESTDIR)$(pkgincludedir)" + @list='$(pkginclude_HEADERS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " $(pkgincludeHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + $(pkgincludeHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +uninstall-pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginclude_HEADERS)'; for p in $$list; do \ + f="`echo $$p | sed -e 's|^.*/||'`"; \ + echo " rm -f '$(DESTDIR)$(pkgincludedir)/$$f'"; \ + rm -f "$(DESTDIR)$(pkgincludedir)/$$f"; \ + done + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(ETAGS_ARGS)$$tags$$unique" \ + || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(pkgincludedir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-libtool \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: install-pkgincludeHEADERS + +install-exec-am: + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-info-am uninstall-pkgincludeHEADERS + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool ctags distclean distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-exec install-exec-am install-info \ + install-info-am install-man install-pkgincludeHEADERS \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-info-am \ + uninstall-pkgincludeHEADERS + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/curl/include/curl/curl.h b/src/curl/include/curl/curl.h new file mode 100644 index 0000000..17e29b6 --- /dev/null +++ b/src/curl/include/curl/curl.h @@ -0,0 +1,1306 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CURL_CURL_H +#define __CURL_CURL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: curl.h,v 1.243 2004/03/12 08:55:50 bagder Exp $ + ***************************************************************************/ + +/* If you have problems, all libcurl docs and details are found here: + http://curl.haxx.se/libcurl/ +*/ + +/* This is the version number of the libcurl package from which this header + file origins: */ +#define LIBCURL_VERSION "7.11.1" + +/* This is the numeric version of the libcurl version number, meant for easier + parsing and comparions by programs. The LIBCURL_VERSION_NUM define will + always follow this syntax: + + 0xXXYYZZ + + Where XX, YY and ZZ are the main version, release and patch numbers in + hexadecimal. All three numbers are always represented using two digits. 1.2 + would appear as "0x010200" while version 9.11.7 appears as "0x090b07". + + This 6-digit hexadecimal number does not show pre-release number, and it is + always a greater number in a more recent release. It makes comparisons with + greater than and less than work. +*/ +#define LIBCURL_VERSION_NUM 0x70B01 + +/* The numeric version number is also available "in parts" by using these + defines: */ +#define LIBCURL_VERSION_MAJOR 7 +#define LIBCURL_VERSION_MINOR 11 +#define LIBCURL_VERSION_PATCH 1 + +#include +#include + +/* The include stuff here below is mainly for time_t! */ +#ifdef vms +# include +# include +#else +#if !__MACH__ && __MWERKS__ +# include +#else +# include +#endif +# include +#endif /* defined (vms) */ + +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * We want the typedef curl_off_t setup for large file support on all + * platforms. We also provide a CURL_FORMAT_OFF_T define to use in *printf + * format strings when outputting a variable of type curl_off_t. + */ +#if defined(_MSC_VER) +/* MSVC */ + typedef signed __int64 curl_off_t; +#define CURL_FORMAT_OFF_T "%I64d" +#else /* MSC_VER */ +#if (defined(__GNUC__) && defined(WIN32)) || defined(__WATCOMC__) +/* gcc on windows or Watcom */ + typedef long long curl_off_t; +#define CURL_FORMAT_OFF_T "%I64d" +#else /* GCC or Watcom on Windows */ + +/* "normal" POSIX approach, do note that this does not necessarily mean that + the type is >32 bits, see the SIZEOF_CURL_OFF_T define for that! */ + typedef off_t curl_off_t; + +/* Check a range of defines to detect large file support. On Linux it seems + none of these are set by default, so if you don't explicitly switches on + large file support, this define will be made for "small file" support. */ +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 0 /* to prevent warnings in the check below */ +#define UNDEF_FILE_OFFSET_BITS +#endif +#ifndef FILESIZEBITS +#define FILESIZEBITS 0 /* to prevent warnings in the check below */ +#define UNDEF_FILESIZEBITS +#endif + +#if defined(_LARGE_FILES) || (_FILE_OFFSET_BITS > 32) || (FILESIZEBITS > 32) \ + || defined(_LARGEFILE_SOURCE) || defined(_LARGEFILE64_SOURCE) + /* For now, we assume at least one of these to be set for large files to + work! */ +#define CURL_FORMAT_OFF_T "%lld" +#else /* LARGE_FILE support */ +#define CURL_FORMAT_OFF_T "%ld" +#endif +#endif /* GCC or Watcom on Windows */ +#endif /* MSC_VER */ + +#ifdef UNDEF_FILE_OFFSET_BITS +/* this was defined above for our checks, undefine it again */ +#undef _FILE_OFFSET_BITS +#endif + +#ifdef UNDEF_FILESIZEBITS +/* this was defined above for our checks, undefine it again */ +#undef FILESIZEBITS +#endif + +struct curl_httppost { + struct curl_httppost *next; /* next entry in the list */ + char *name; /* pointer to allocated name */ + long namelength; /* length of name length */ + char *contents; /* pointer to allocated data contents */ + long contentslength; /* length of contents field */ + char *buffer; /* pointer to allocated buffer contents */ + long bufferlength; /* length of buffer field */ + char *contenttype; /* Content-Type */ + struct curl_slist* contentheader; /* list of extra headers for this form */ + struct curl_httppost *more; /* if one field name has more than one + file, this link should link to following + files */ + long flags; /* as defined below */ +#define HTTPPOST_FILENAME (1<<0) /* specified content is a file name */ +#define HTTPPOST_READFILE (1<<1) /* specified content is a file name */ +#define HTTPPOST_PTRNAME (1<<2) /* name is only stored pointer + do not free in formfree */ +#define HTTPPOST_PTRCONTENTS (1<<3) /* contents is only stored pointer + do not free in formfree */ +#define HTTPPOST_BUFFER (1<<4) /* upload file from buffer */ +#define HTTPPOST_PTRBUFFER (1<<5) /* upload file from pointer contents */ + + char *showfilename; /* The file name to show. If not set, the + actual file name will be used (if this + is a file part) */ +}; + +typedef int (*curl_progress_callback)(void *clientp, + double dltotal, + double dlnow, + double ultotal, + double ulnow); + + /* Tests have proven that 20K is a very bad buffer size for uploads on + Windows, while 16K for some odd reason performed a lot better. */ +#define CURL_MAX_WRITE_SIZE 16384 + +typedef size_t (*curl_write_callback)(char *buffer, + size_t size, + size_t nitems, + void *outstream); + +typedef size_t (*curl_read_callback)(char *buffer, + size_t size, + size_t nitems, + void *instream); + + /* not used since 7.10.8, will be removed in a future release */ +typedef int (*curl_passwd_callback)(void *clientp, + const char *prompt, + char *buffer, + int buflen); + +/* the kind of data that is passed to information_callback*/ +typedef enum { + CURLINFO_TEXT = 0, + CURLINFO_HEADER_IN, /* 1 */ + CURLINFO_HEADER_OUT, /* 2 */ + CURLINFO_DATA_IN, /* 3 */ + CURLINFO_DATA_OUT, /* 4 */ + CURLINFO_END +} curl_infotype; + +typedef int (*curl_debug_callback) + (CURL *handle, /* the handle/transfer this concerns */ + curl_infotype type, /* what kind of data */ + char *data, /* points to the data */ + size_t size, /* size of the data pointed to */ + void *userptr); /* whatever the user please */ + +/* All possible error codes from all sorts of curl functions. Future versions + may return other values, stay prepared. + + Always add new return codes last. Never *EVER* remove any. The return + codes must remain the same! + */ + +typedef enum { + CURLE_OK = 0, + CURLE_UNSUPPORTED_PROTOCOL, /* 1 */ + CURLE_FAILED_INIT, /* 2 */ + CURLE_URL_MALFORMAT, /* 3 */ + CURLE_URL_MALFORMAT_USER, /* 4 */ + CURLE_COULDNT_RESOLVE_PROXY, /* 5 */ + CURLE_COULDNT_RESOLVE_HOST, /* 6 */ + CURLE_COULDNT_CONNECT, /* 7 */ + CURLE_FTP_WEIRD_SERVER_REPLY, /* 8 */ + CURLE_FTP_ACCESS_DENIED, /* 9 */ + CURLE_FTP_USER_PASSWORD_INCORRECT, /* 10 */ + CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ + CURLE_FTP_WEIRD_USER_REPLY, /* 12 */ + CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */ + CURLE_FTP_WEIRD_227_FORMAT, /* 14 */ + CURLE_FTP_CANT_GET_HOST, /* 15 */ + CURLE_FTP_CANT_RECONNECT, /* 16 */ + CURLE_FTP_COULDNT_SET_BINARY, /* 17 */ + CURLE_PARTIAL_FILE, /* 18 */ + CURLE_FTP_COULDNT_RETR_FILE, /* 19 */ + CURLE_FTP_WRITE_ERROR, /* 20 */ + CURLE_FTP_QUOTE_ERROR, /* 21 */ + CURLE_HTTP_RETURNED_ERROR, /* 22 */ + CURLE_WRITE_ERROR, /* 23 */ + CURLE_MALFORMAT_USER, /* 24 - user name is illegally specified */ + CURLE_FTP_COULDNT_STOR_FILE, /* 25 - failed FTP upload */ + CURLE_READ_ERROR, /* 26 - could open/read from file */ + CURLE_OUT_OF_MEMORY, /* 27 */ + CURLE_OPERATION_TIMEOUTED, /* 28 - the timeout time was reached */ + CURLE_FTP_COULDNT_SET_ASCII, /* 29 - TYPE A failed */ + CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ + CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ + CURLE_FTP_COULDNT_GET_SIZE, /* 32 - the SIZE command failed */ + CURLE_HTTP_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ + CURLE_HTTP_POST_ERROR, /* 34 */ + CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_FILE_COULDNT_READ_FILE, /* 37 */ + CURLE_LDAP_CANNOT_BIND, /* 38 */ + CURLE_LDAP_SEARCH_FAILED, /* 39 */ + CURLE_LIBRARY_NOT_FOUND, /* 40 */ + CURLE_FUNCTION_NOT_FOUND, /* 41 */ + CURLE_ABORTED_BY_CALLBACK, /* 42 */ + CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ + CURLE_BAD_CALLING_ORDER, /* 44 */ + CURLE_HTTP_PORT_FAILED, /* 45 - HTTP Interface operation failed */ + CURLE_BAD_PASSWORD_ENTERED, /* 46 - my_getpass() returns fail */ + CURLE_TOO_MANY_REDIRECTS , /* 47 - catch endless re-direct loops */ + CURLE_UNKNOWN_TELNET_OPTION, /* 48 - User specified an unknown option */ + CURLE_TELNET_OPTION_SYNTAX , /* 49 - Malformed telnet option */ + CURLE_OBSOLETE, /* 50 - removed after 7.7.3 */ + CURLE_SSL_PEER_CERTIFICATE, /* 51 - peer's certificate wasn't ok */ + CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ + CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ + CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as + default */ + CURLE_SEND_ERROR, /* 55 - failed sending network data */ + CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ + CURLE_SHARE_IN_USE, /* 57 - share is in use */ + CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ + CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ + CURLE_SSL_CACERT, /* 60 - problem with the CA cert (path?) */ + CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized transfer encoding */ + CURLE_LDAP_INVALID_URL, /* 62 - Invalid LDAP URL */ + CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ + CURLE_FTP_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ + + CURL_LAST /* never use! */ +} CURLcode; + +typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, /* easy handle */ + void *ssl_ctx, /* actually an + OpenSSL SSL_CTX */ + void *userptr); + +/* Make a spelling correction for the operation timed-out define */ +#define CURLE_OPERATION_TIMEDOUT CURLE_OPERATION_TIMEOUTED +#define CURLE_HTTP_NOT_FOUND CURLE_HTTP_RETURNED_ERROR + +typedef enum { + CURLPROXY_HTTP = 0, + CURLPROXY_SOCKS4 = 4, + CURLPROXY_SOCKS5 = 5 +} curl_proxytype; + +#define CURLAUTH_NONE 0 /* nothing */ +#define CURLAUTH_BASIC (1<<0) /* Basic (default) */ +#define CURLAUTH_DIGEST (1<<1) /* Digest */ +#define CURLAUTH_GSSNEGOTIATE (1<<2) /* GSS-Negotiate */ +#define CURLAUTH_NTLM (1<<3) /* NTLM */ +#define CURLAUTH_ANY ~0 /* all types set */ +#define CURLAUTH_ANYSAFE (~CURLAUTH_BASIC) + +/* this was the error code 50 in 7.7.3 and a few earlier versions, this + is no longer used by libcurl but is instead #defined here only to not + make programs break */ +#define CURLE_ALREADY_COMPLETE 99999 + +/* This is just to make older programs not break: */ +#define CURLE_FTP_PARTIAL_FILE CURLE_PARTIAL_FILE +#define CURLE_FTP_BAD_DOWNLOAD_RESUME CURLE_BAD_DOWNLOAD_RESUME + +#define CURL_ERROR_SIZE 256 + +typedef enum { + CURLFTPSSL_NONE, /* do not attempt to use SSL */ + CURLFTPSSL_TRY, /* try using SSL, proceed anyway otherwise */ + CURLFTPSSL_CONTROL, /* SSL for the control connection or fail */ + CURLFTPSSL_ALL, /* SSL for all communication or fail */ + CURLFTPSSL_LAST /* not an option, never use */ +} curl_ftpssl; + +/* long may be 32 or 64 bits, but we should never depend on anything else + but 32 */ +#define CURLOPTTYPE_LONG 0 +#define CURLOPTTYPE_OBJECTPOINT 10000 +#define CURLOPTTYPE_FUNCTIONPOINT 20000 +#define CURLOPTTYPE_OFF_T 30000 + +/* name is uppercase CURLOPT_, + type is one of the defined CURLOPTTYPE_ + number is unique identifier */ +#ifdef CINIT +#undef CINIT +#endif +/* + * Figure out if we can use the ## operator, which is supported by ISO/ANSI C + * and C++. Some compilers support it without setting __STDC__ or __cplusplus + * so we need to carefully check for them too. We don't use configure-checks + * for these since we want these headers to remain generic and working for all + * platforms. + */ +#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) || \ + defined(__HP_aCC) || defined(__BORLANDC__) + /* This compiler is believed to have an ISO compatible preprocessor */ +#define CURL_ISOCPP +#else + /* This compiler is believed NOT to have an ISO compatible preprocessor */ +#undef CURL_ISOCPP +#endif + +#ifdef CURL_ISOCPP +#define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define LONG CURLOPTTYPE_LONG +#define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT +#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT +#define OFF_T CURLOPTTYPE_OFF_T +#define CINIT(name,type,number) CURLOPT_/**/name = type + number +#endif + +/* + * This macro-mania below setups the CURLOPT_[what] enum, to be used with + * curl_easy_setopt(). The first argument in the CINIT() macro is the [what] + * word. + */ + +typedef enum { + /* This is the FILE * or void * the regular output should be written to. */ + CINIT(FILE, OBJECTPOINT, 1), + + /* The full URL to get/put */ + CINIT(URL, OBJECTPOINT, 2), + + /* Port number to connect to, if other than default. */ + CINIT(PORT, LONG, 3), + + /* Name of proxy to use. */ + CINIT(PROXY, OBJECTPOINT, 4), + + /* "name:password" to use when fetching. */ + CINIT(USERPWD, OBJECTPOINT, 5), + + /* "name:password" to use with proxy. */ + CINIT(PROXYUSERPWD, OBJECTPOINT, 6), + + /* Range to get, specified as an ASCII string. */ + CINIT(RANGE, OBJECTPOINT, 7), + + /* not used */ + + /* Specified file stream to upload from (use as input): */ + CINIT(INFILE, OBJECTPOINT, 9), + + /* Buffer to receive error messages in, must be at least CURL_ERROR_SIZE + * bytes big. If this is not used, error messages go to stderr instead: */ + CINIT(ERRORBUFFER, OBJECTPOINT, 10), + + /* Function that will be called to store the output (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), + + /* Function that will be called to read the input (instead of fread). The + * parameters will use fread() syntax, make sure to follow them. */ + CINIT(READFUNCTION, FUNCTIONPOINT, 12), + + /* Time-out the read operation after this amount of seconds */ + CINIT(TIMEOUT, LONG, 13), + + /* If the CURLOPT_INFILE is used, this can be used to inform libcurl about + * how large the file being sent really is. That allows better error + * checking and better verifies that the upload was succcessful. -1 means + * unknown size. + * + * For large file support, there is also a _LARGE version of the key + * which takes an off_t type, allowing platforms with larger off_t + * sizes to handle larger files. See below for INFILESIZE_LARGE. + */ + CINIT(INFILESIZE, LONG, 14), + + /* POST input fields. */ + CINIT(POSTFIELDS, OBJECTPOINT, 15), + + /* Set the referer page (needed by some CGIs) */ + CINIT(REFERER, OBJECTPOINT, 16), + + /* Set the FTP PORT string (interface name, named or numerical IP address) + Use i.e '-' to use default address. */ + CINIT(FTPPORT, OBJECTPOINT, 17), + + /* Set the User-Agent string (examined by some CGIs) */ + CINIT(USERAGENT, OBJECTPOINT, 18), + + /* If the download receives less than "low speed limit" bytes/second + * during "low speed time" seconds, the operations is aborted. + * You could i.e if you have a pretty high speed connection, abort if + * it is less than 2000 bytes/sec during 20 seconds. + */ + + /* Set the "low speed limit" */ + CINIT(LOW_SPEED_LIMIT, LONG , 19), + + /* Set the "low speed time" */ + CINIT(LOW_SPEED_TIME, LONG, 20), + + /* Set the continuation offset. + * + * Note there is also a _LARGE version of this key which uses + * off_t types, allowing for large file offsets on platforms which + * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. + */ + CINIT(RESUME_FROM, LONG, 21), + + /* Set cookie in request: */ + CINIT(COOKIE, OBJECTPOINT, 22), + + /* This points to a linked list of headers, struct curl_slist kind */ + CINIT(HTTPHEADER, OBJECTPOINT, 23), + + /* This points to a linked list of post entries, struct HttpPost */ + CINIT(HTTPPOST, OBJECTPOINT, 24), + + /* name of the file keeping your private SSL-certificate */ + CINIT(SSLCERT, OBJECTPOINT, 25), + + /* password for the SSL-private key, keep this for compatibility */ + CINIT(SSLCERTPASSWD, OBJECTPOINT, 26), + /* password for the SSL private key */ + CINIT(SSLKEYPASSWD, OBJECTPOINT, 26), + + /* send TYPE parameter? */ + CINIT(CRLF, LONG, 27), + + /* send linked-list of QUOTE commands */ + CINIT(QUOTE, OBJECTPOINT, 28), + + /* send FILE * or void * to store headers to, if you use a callback it + is simply passed to the callback unmodified */ + CINIT(WRITEHEADER, OBJECTPOINT, 29), + + /* point to a file to read the initial cookies from, also enables + "cookie awareness" */ + CINIT(COOKIEFILE, OBJECTPOINT, 31), + + /* What version to specifly try to use. + See CURL_SSLVERSION defines below. */ + CINIT(SSLVERSION, LONG, 32), + + /* What kind of HTTP time condition to use, see defines */ + CINIT(TIMECONDITION, LONG, 33), + + /* Time to use with the above condition. Specified in number of seconds + since 1 Jan 1970 */ + CINIT(TIMEVALUE, LONG, 34), + + /* 35 = OBSOLETE */ + + /* Custom request, for customizing the get command like + HTTP: DELETE, TRACE and others + FTP: to use a different list command + */ + CINIT(CUSTOMREQUEST, OBJECTPOINT, 36), + + /* HTTP request, for odd commands like DELETE, TRACE and others */ + CINIT(STDERR, OBJECTPOINT, 37), + + /* 38 is not used */ + + /* send linked-list of post-transfer QUOTE commands */ + CINIT(POSTQUOTE, OBJECTPOINT, 39), + + /* Pass a pointer to string of the output using full variable-replacement + as described elsewhere. */ + CINIT(WRITEINFO, OBJECTPOINT, 40), + + CINIT(VERBOSE, LONG, 41), /* talk a lot */ + CINIT(HEADER, LONG, 42), /* throw the header out too */ + CINIT(NOPROGRESS, LONG, 43), /* shut off the progress meter */ + CINIT(NOBODY, LONG, 44), /* use HEAD to get http document */ + CINIT(FAILONERROR, LONG, 45), /* no output on http error codes >= 300 */ + CINIT(UPLOAD, LONG, 46), /* this is an upload */ + CINIT(POST, LONG, 47), /* HTTP POST method */ + CINIT(FTPLISTONLY, LONG, 48), /* Use NLST when listing ftp dir */ + + CINIT(FTPAPPEND, LONG, 50), /* Append instead of overwrite on upload! */ + + /* Specify whether to read the user+password from the .netrc or the URL. + * This must be one of the CURL_NETRC_* enums below. */ + CINIT(NETRC, LONG, 51), + + CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */ + + CINIT(TRANSFERTEXT, LONG, 53), /* transfer data in text/ASCII format */ + CINIT(PUT, LONG, 54), /* PUT the input file */ + + /* 55 = OBSOLETE */ + + /* Function that will be called instead of the internal progress display + * function. This function should be defined as the curl_progress_callback + * prototype defines. */ + CINIT(PROGRESSFUNCTION, FUNCTIONPOINT, 56), + + /* Data passed to the progress callback */ + CINIT(PROGRESSDATA, OBJECTPOINT, 57), + + /* We want the referer field set automatically when following locations */ + CINIT(AUTOREFERER, LONG, 58), + + /* Port of the proxy, can be set in the proxy string as well with: + "[host]:[port]" */ + CINIT(PROXYPORT, LONG, 59), + + /* size of the POST input data, if strlen() is not good to use */ + CINIT(POSTFIELDSIZE, LONG, 60), + + /* tunnel non-http operations through a HTTP proxy */ + CINIT(HTTPPROXYTUNNEL, LONG, 61), + + /* Set the interface string to use as outgoing network interface */ + CINIT(INTERFACE, OBJECTPOINT, 62), + + /* Set the krb4 security level, this also enables krb4 awareness. This is a + * string, 'clear', 'safe', 'confidential' or 'private'. If the string is + * set but doesn't match one of these, 'private' will be used. */ + CINIT(KRB4LEVEL, OBJECTPOINT, 63), + + /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ + CINIT(SSL_VERIFYPEER, LONG, 64), + + /* The CApath or CAfile used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + CINIT(CAINFO, OBJECTPOINT, 65), + + /* 66 = OBSOLETE */ + /* 67 = OBSOLETE */ + + /* Maximum number of http redirects to follow */ + CINIT(MAXREDIRS, LONG, 68), + + /* Pass a pointer to a time_t to get a possible date of the requested + document! Pass a NULL to shut it off. */ + CINIT(FILETIME, OBJECTPOINT, 69), + + /* This points to a linked list of telnet options */ + CINIT(TELNETOPTIONS, OBJECTPOINT, 70), + + /* Max amount of cached alive connections */ + CINIT(MAXCONNECTS, LONG, 71), + + /* What policy to use when closing connections when the cache is filled + up */ + CINIT(CLOSEPOLICY, LONG, 72), + + /* 73 = OBSOLETE */ + + /* Set to explicitly use a new connection for the upcoming transfer. + Do not use this unless you're absolutely sure of this, as it makes the + operation slower and is less friendly for the network. */ + CINIT(FRESH_CONNECT, LONG, 74), + + /* Set to explicitly forbid the upcoming transfer's connection to be re-used + when done. Do not use this unless you're absolutely sure of this, as it + makes the operation slower and is less friendly for the network. */ + CINIT(FORBID_REUSE, LONG, 75), + + /* Set to a file name that contains random data for libcurl to use to + seed the random engine when doing SSL connects. */ + CINIT(RANDOM_FILE, OBJECTPOINT, 76), + + /* Set to the Entropy Gathering Daemon socket pathname */ + CINIT(EGDSOCKET, OBJECTPOINT, 77), + + /* Time-out connect operations after this amount of seconds, if connects + are OK within this time, then fine... This only aborts the connect + phase. [Only works on unix-style/SIGALRM operating systems] */ + CINIT(CONNECTTIMEOUT, LONG, 78), + + /* Function that will be called to store headers (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + CINIT(HEADERFUNCTION, FUNCTIONPOINT, 79), + + /* Set this to force the HTTP request to get back to GET. Only really usable + if POST, PUT or a custom request have been used first. + */ + CINIT(HTTPGET, LONG, 80), + + /* Set if we should verify the Common name from the peer certificate in ssl + * handshake, set 1 to check existence, 2 to ensure that it matches the + * provided hostname. */ + CINIT(SSL_VERIFYHOST, LONG, 81), + + /* Specify which file name to write all known cookies in after completed + operation. Set file name to "-" (dash) to make it go to stdout. */ + CINIT(COOKIEJAR, OBJECTPOINT, 82), + + /* Specify which SSL ciphers to use */ + CINIT(SSL_CIPHER_LIST, OBJECTPOINT, 83), + + /* Specify which HTTP version to use! This must be set to one of the + CURL_HTTP_VERSION* enums set below. */ + CINIT(HTTP_VERSION, LONG, 84), + + /* Specificly switch on or off the FTP engine's use of the EPSV command. By + default, that one will always be attempted before the more traditional + PASV command. */ + CINIT(FTP_USE_EPSV, LONG, 85), + + /* type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") */ + CINIT(SSLCERTTYPE, OBJECTPOINT, 86), + + /* name of the file keeping your private SSL-key */ + CINIT(SSLKEY, OBJECTPOINT, 87), + + /* type of the file keeping your private SSL-key ("DER", "PEM", "ENG") */ + CINIT(SSLKEYTYPE, OBJECTPOINT, 88), + + /* crypto engine for the SSL-sub system */ + CINIT(SSLENGINE, OBJECTPOINT, 89), + + /* set the crypto engine for the SSL-sub system as default + the param has no meaning... + */ + CINIT(SSLENGINE_DEFAULT, LONG, 90), + + /* Non-zero value means to use the global dns cache */ + CINIT(DNS_USE_GLOBAL_CACHE, LONG, 91), /* To become OBSOLETE soon */ + + /* DNS cache timeout */ + CINIT(DNS_CACHE_TIMEOUT, LONG, 92), + + /* send linked-list of pre-transfer QUOTE commands (Wesley Laxton)*/ + CINIT(PREQUOTE, OBJECTPOINT, 93), + + /* set the debug function */ + CINIT(DEBUGFUNCTION, FUNCTIONPOINT, 94), + + /* set the data for the debug function */ + CINIT(DEBUGDATA, OBJECTPOINT, 95), + + /* mark this as start of a cookie session */ + CINIT(COOKIESESSION, LONG, 96), + + /* The CApath directory used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + CINIT(CAPATH, OBJECTPOINT, 97), + + /* Instruct libcurl to use a smaller receive buffer */ + CINIT(BUFFERSIZE, LONG, 98), + + /* Instruct libcurl to not use any signal/alarm handlers, even when using + timeouts. This option is useful for multi-threaded applications. + See libcurl-the-guide for more background information. */ + CINIT(NOSIGNAL, LONG, 99), + + /* Provide a CURLShare for mutexing non-ts data */ + CINIT(SHARE, OBJECTPOINT, 100), + + /* indicates type of proxy. accepted values are CURLPROXY_HTTP (default), + CURLPROXY_SOCKS4 and CURLPROXY_SOCKS5. */ + CINIT(PROXYTYPE, LONG, 101), + + /* Set the Accept-Encoding string. Use this to tell a server you would like + the response to be compressed. */ + CINIT(ENCODING, OBJECTPOINT, 102), + + /* Set pointer to private data */ + CINIT(PRIVATE, OBJECTPOINT, 103), + + /* Set aliases for HTTP 200 in the HTTP Response header */ + CINIT(HTTP200ALIASES, OBJECTPOINT, 104), + + /* Continue to send authentication (user+password) when following locations, + even when hostname changed. This can potentionally send off the name + and password to whatever host the server decides. */ + CINIT(UNRESTRICTED_AUTH, LONG, 105), + + /* Specificly switch on or off the FTP engine's use of the EPRT command ( it + also disables the LPRT attempt). By default, those ones will always be + attempted before the good old traditional PORT command. */ + CINIT(FTP_USE_EPRT, LONG, 106), + + /* Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_USERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + CINIT(HTTPAUTH, LONG, 107), + + /* Set the ssl context callback function, currently only for OpenSSL ssl_ctx + in second argument. The function must be matching the + curl_ssl_ctx_callback proto. */ + CINIT(SSL_CTX_FUNCTION, FUNCTIONPOINT, 108), + + /* Set the userdata for the ssl context callback function's third + argument */ + CINIT(SSL_CTX_DATA, OBJECTPOINT, 109), + + /* FTP Option that causes missing dirs to be created on the remote server */ + CINIT(FTP_CREATE_MISSING_DIRS, LONG, 110), + + /* Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_PROXYUSERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + CINIT(PROXYAUTH, LONG, 111), + + /* FTP option that changes the timeout, in seconds, associated with + getting a response. This is different from transfer timeout time and + essentially places a demand on the FTP server to acknowledge commands + in a timely manner. */ + CINIT(FTP_RESPONSE_TIMEOUT, LONG , 112), + + /* Set this option to one of the CURL_IPRESOLVE_* defines (see below) to + tell libcurl to resolve names to those IP versions only. This only has + affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */ + CINIT(IPRESOLVE, LONG, 113), + + /* Set this option to limit the size of a file that will be downloaded from + an HTTP or FTP server. + + Note there is also _LARGE version which adds large file support for + platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ + CINIT(MAXFILESIZE, LONG, 114), + + /* See the comment for INFILESIZE above, but in short, specifies + * the size of the file being uploaded. -1 means unknown. + */ + CINIT(INFILESIZE_LARGE, OFF_T, 115), + + /* Sets the continuation offset. There is also a LONG version of this; + * look above for RESUME_FROM. + */ + CINIT(RESUME_FROM_LARGE, OFF_T, 116), + + /* Sets the maximum size of data that will be downloaded from + * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. + */ + CINIT(MAXFILESIZE_LARGE, OFF_T, 117), + + /* Set this option to the file name of your .netrc file you want libcurl + to parse (using the CURLOPT_NETRC option). If not set, libcurl will do + a poor attempt to find the user's home directory and check for a .netrc + file in there. */ + CINIT(NETRC_FILE, OBJECTPOINT, 118), + + /* Enable SSL/TLS for FTP, pick one of: + CURLFTPSSL_TRY - try using SSL, proceed anyway otherwise + CURLFTPSSL_CONTROL - SSL for the control connection or fail + CURLFTPSSL_ALL - SSL for all communication or fail + */ + CINIT(FTP_SSL, LONG, 119), + + /* The _LARGE version of the standard POSTFIELDSIZE option */ + CINIT(POSTFIELDSIZE_LARGE, OFF_T, 120), + + CURLOPT_LASTENTRY /* the last unused */ +} CURLoption; + + /* Below here follows defines for the CURLOPT_IPRESOLVE option. If a host + name resolves addresses using more than one IP protocol version, this + option might be handy to force libcurl to use a specific IP version. */ +#define CURL_IPRESOLVE_WHATEVER 0 /* default, resolves addresses to all IP + versions that your system allows */ +#define CURL_IPRESOLVE_V4 1 /* resolve to ipv4 addresses */ +#define CURL_IPRESOLVE_V6 2 /* resolve to ipv6 addresses */ + + /* three convenient "aliases" that follow the name scheme better */ +#define CURLOPT_WRITEDATA CURLOPT_FILE +#define CURLOPT_READDATA CURLOPT_INFILE +#define CURLOPT_HEADERDATA CURLOPT_WRITEHEADER + +#ifndef CURL_NO_OLDIES /* define this to test if your app builds with all + the obsolete stuff removed! */ +#define CURLOPT_HTTPREQUEST 0 +#define CURLOPT_FTPASCII CURLOPT_TRANSFERTEXT +#define CURLOPT_MUTE 0 +#define CURLOPT_PASSWDFUNCTION 0 +#define CURLOPT_PASSWDDATA 0 +#define CURLOPT_CLOSEFUNCTION 0 + +#else +/* This is set if CURL_NO_OLDIES is defined at compile-time */ +#define curl_formparse "curl_formparse is obsolete" +#undef CURLOPT_DNS_USE_GLOBAL_CACHE /* soon obsolete */ +#endif + + + /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ +enum { + CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd + like the library to choose the best possible + for us! */ + CURL_HTTP_VERSION_1_0, /* please use HTTP 1.0 in the request */ + CURL_HTTP_VERSION_1_1, /* please use HTTP 1.1 in the request */ + + CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ +}; + + /* These enums are for use with the CURLOPT_NETRC option. */ +enum CURL_NETRC_OPTION { + CURL_NETRC_IGNORED, /* The .netrc will never be read. + * This is the default. */ + CURL_NETRC_OPTIONAL, /* A user:password in the URL will be preferred + * to one in the .netrc. */ + CURL_NETRC_REQUIRED, /* A user:password in the URL will be ignored. + * Unless one is set programmatically, the .netrc + * will be queried. */ + CURL_NETRC_LAST +}; + +enum { + CURL_SSLVERSION_DEFAULT, + CURL_SSLVERSION_TLSv1, + CURL_SSLVERSION_SSLv2, + CURL_SSLVERSION_SSLv3, + + CURL_SSLVERSION_LAST /* never use, keep last */ +}; + + +typedef enum { + CURL_TIMECOND_NONE, + + CURL_TIMECOND_IFMODSINCE, + CURL_TIMECOND_IFUNMODSINCE, + CURL_TIMECOND_LASTMOD, + + CURL_TIMECOND_LAST +} curl_TimeCond; + +#ifdef __BEOS__ +#include +#endif + + +/* These functions are in libcurl, they're here for portable reasons and they + are used by the 'curl' client. They really should be moved to some kind of + "portability library" since it has nothing to do with file transfers and + might be usable to other programs... + + NOTE: they return TRUE if the strings match *case insensitively*. + */ +extern int (curl_strequal)(const char *s1, const char *s2); +extern int (curl_strnequal)(const char *s1, const char *s2, size_t n); + +#ifdef CURL_OLDSTYLE +/* DEPRECATED function to build formdata. Stop using this, it will cease + to exist. */ +int curl_formparse(char *, struct curl_httppost **, + struct curl_httppost **_post); +#endif + +/* name is uppercase CURLFORM_ */ +#ifdef CFINIT +#undef CFINIT +#endif + +#ifdef CURL_ISOCPP +#define CFINIT(name) CURLFORM_ ## name +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define CFINIT(name) CURLFORM_/**/name +#endif + +typedef enum { + CFINIT(NOTHING), /********* the first one is unused ************/ + + /* */ + CFINIT(COPYNAME), + CFINIT(PTRNAME), + CFINIT(NAMELENGTH), + CFINIT(COPYCONTENTS), + CFINIT(PTRCONTENTS), + CFINIT(CONTENTSLENGTH), + CFINIT(FILECONTENT), + CFINIT(ARRAY), + CFINIT(OBSOLETE), + CFINIT(FILE), + + CFINIT(BUFFER), + CFINIT(BUFFERPTR), + CFINIT(BUFFERLENGTH), + + CFINIT(CONTENTTYPE), + CFINIT(CONTENTHEADER), + CFINIT(FILENAME), + CFINIT(END), + CFINIT(OBSOLETE2), + + CURLFORM_LASTENTRY /* the last unusued */ +} CURLformoption; + +#undef CFINIT /* done */ + +/* structure to be used as parameter for CURLFORM_ARRAY */ +struct curl_forms { + CURLformoption option; + const char *value; +}; + +/* use this for multipart formpost building */ +/* Returns code for curl_formadd() + * + * Returns: + * CURL_FORMADD_OK on success + * CURL_FORMADD_MEMORY if the FormInfo allocation fails + * CURL_FORMADD_OPTION_TWICE if one option is given twice for one Form + * CURL_FORMADD_NULL if a null pointer was given for a char + * CURL_FORMADD_MEMORY if the allocation of a FormInfo struct failed + * CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used + * CURL_FORMADD_INCOMPLETE if the some FormInfo is not complete (or error) + * CURL_FORMADD_MEMORY if a HttpPost struct cannot be allocated + * CURL_FORMADD_MEMORY if some allocation for string copying failed. + * CURL_FORMADD_ILLEGAL_ARRAY if an illegal option is used in an array + * + ***************************************************************************/ +typedef enum { + CURL_FORMADD_OK, /* first, no error */ + + CURL_FORMADD_MEMORY, + CURL_FORMADD_OPTION_TWICE, + CURL_FORMADD_NULL, + CURL_FORMADD_UNKNOWN_OPTION, + CURL_FORMADD_INCOMPLETE, + CURL_FORMADD_ILLEGAL_ARRAY, + + CURL_FORMADD_LAST /* last */ +} CURLFORMcode; + +/* + * NAME curl_formadd() + * + * DESCRIPTION + * + * Pretty advanved function for building multi-part formposts. Each invoke + * adds one part that together construct a full post. Then use + * CURLOPT_HTTPPOST to send it off to libcurl. + */ +CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...); + +/* + * NAME curl_formfree() + * + * DESCRIPTION + * + * Free a multipart formpost previously built with curl_formadd(). + */ +void curl_formfree(struct curl_httppost *form); + +/* + * NAME curl_getenv() + * + * DESCRIPTION + * + * Returns a malloc()'ed string that MUST be curl_free()ed after usage is + * complete. + */ +char *curl_getenv(const char *variable); + +/* + * NAME curl_version() + * + * DESCRIPTION + * + * Returns a static ascii string of the libcurl version. + */ +char *curl_version(void); + +/* + * NAME curl_escape() + * + * DESCRIPTION + * + * Escapes URL strings (converts all letters consider illegal in URLs to their + * %XX versions). This function returns a new allocated string or NULL if an + * error occurred. + */ +char *curl_escape(const char *string, int length); + +/* + * NAME curl_unescape() + * + * DESCRIPTION + * + * Unescapes URL encoding in strings (converts all %XX codes to their 8bit + * versions). This function returns a new allocated string or NULL if an error + * occurred. + */ +char *curl_unescape(const char *string, int length); + +/* + * NAME curl_free() + * + * DESCRIPTION + * + * Provided for de-allocation in the same translation unit that did the + * allocation. Added in libcurl 7.10 + */ +void curl_free(void *p); + +/* + * NAME curl_global_init() + * + * DESCRIPTION + * + * curl_global_init() should be invoked exactly once for each application that + * uses libcurl + */ +CURLcode curl_global_init(long flags); + +/* + * NAME curl_global_cleanup() + * + * DESCRIPTION + * + * curl_global_cleanup() should be invoked exactly once for each application + * that uses libcurl + */ +void curl_global_cleanup(void); + +/* linked-list structure for the CURLOPT_QUOTE option (and other) */ +struct curl_slist { + char *data; + struct curl_slist *next; +}; + +/* + * NAME curl_slist_append() + * + * DESCRIPTION + * + * Appends a string to a linked list. If no list exists, it will be created + * first. Returns the new list, after appending. + */ +struct curl_slist *curl_slist_append(struct curl_slist *, const char *); + +/* + * NAME curl_slist_free_all() + * + * DESCRIPTION + * + * free a previously built curl_slist. + */ +void curl_slist_free_all(struct curl_slist *); + +/* + * NAME curl_getdate() + * + * DESCRIPTION + * + * Returns the time, in seconds since 1 Jan 1970 of the time string given in + * the first argument. The time argument in the second parameter is for cases + * where the specified time is relative now, like 'two weeks' or 'tomorrow' + * etc. + */ +time_t curl_getdate(const char *p, const time_t *now); + + +#define CURLINFO_STRING 0x100000 +#define CURLINFO_LONG 0x200000 +#define CURLINFO_DOUBLE 0x300000 +#define CURLINFO_MASK 0x0fffff +#define CURLINFO_TYPEMASK 0xf00000 + +typedef enum { + CURLINFO_NONE, /* first, never use this */ + CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, + CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3, + CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4, + CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5, + CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6, + CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, + CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, + CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, + CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, + CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11, + CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12, + CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, + CURLINFO_FILETIME = CURLINFO_LONG + 14, + CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16, + CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17, + CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, + CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19, + CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20, + CURLINFO_PRIVATE = CURLINFO_STRING + 21, + CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22, + CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23, + CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24, + /* Fill in new entries below here! */ + + CURLINFO_LASTONE = 23 +} CURLINFO; + +/* CURLINFO_RESPONSE_CODE is the new name for the option previously known as + CURLINFO_HTTP_CODE */ +#define CURLINFO_HTTP_CODE CURLINFO_RESPONSE_CODE + +typedef enum { + CURLCLOSEPOLICY_NONE, /* first, never use this */ + + CURLCLOSEPOLICY_OLDEST, + CURLCLOSEPOLICY_LEAST_RECENTLY_USED, + CURLCLOSEPOLICY_LEAST_TRAFFIC, + CURLCLOSEPOLICY_SLOWEST, + CURLCLOSEPOLICY_CALLBACK, + + CURLCLOSEPOLICY_LAST /* last, never use this */ +} curl_closepolicy; + +#define CURL_GLOBAL_SSL (1<<0) +#define CURL_GLOBAL_WIN32 (1<<1) +#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32) +#define CURL_GLOBAL_NOTHING 0 +#define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL + + +/***************************************************************************** + * Setup defines, protos etc for the sharing stuff. + */ + +/* Different data locks for a single share */ +typedef enum { + CURL_LOCK_DATA_NONE = 0, + /* CURL_LOCK_DATA_SHARE is used internaly to say that + * the locking is just made to change the internal state of the share + * itself. + */ + CURL_LOCK_DATA_SHARE, + CURL_LOCK_DATA_COOKIE, + CURL_LOCK_DATA_DNS, + CURL_LOCK_DATA_SSL_SESSION, + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_LAST +} curl_lock_data; + +/* Different lock access types */ +typedef enum { + CURL_LOCK_ACCESS_NONE = 0, /* unspecified action */ + CURL_LOCK_ACCESS_SHARED = 1, /* for read perhaps */ + CURL_LOCK_ACCESS_SINGLE = 2, /* for write perhaps */ + CURL_LOCK_ACCESS_LAST /* never use */ +} curl_lock_access; + +typedef void (*curl_lock_function)(CURL *handle, + curl_lock_data data, + curl_lock_access locktype, + void *userptr); +typedef void (*curl_unlock_function)(CURL *handle, + curl_lock_data data, + void *userptr); + +typedef void CURLSH; + +typedef enum { + CURLSHE_OK, /* all is fine */ + CURLSHE_BAD_OPTION, /* 1 */ + CURLSHE_IN_USE, /* 2 */ + CURLSHE_INVALID, /* 3 */ + CURLSHE_LAST /* never use */ +} CURLSHcode; + +typedef enum { + CURLSHOPT_NONE, /* don't use */ + CURLSHOPT_SHARE, /* specify a data type to share */ + CURLSHOPT_UNSHARE, /* specify shich data type to stop sharing */ + CURLSHOPT_LOCKFUNC, /* pass in a 'curl_lock_function' pointer */ + CURLSHOPT_UNLOCKFUNC, /* pass in a 'curl_unlock_function' pointer */ + CURLSHOPT_USERDATA, /* pass in a user data pointer used in the lock/unlock + callback functions */ + CURLSHOPT_LAST /* never use */ +} CURLSHoption; + +CURLSH *curl_share_init(void); +CURLSHcode curl_share_setopt(CURLSH *, CURLSHoption option, ...); +CURLSHcode curl_share_cleanup(CURLSH *); + +/**************************************************************************** + * Structures for querying information about the curl library at runtime. + */ + +typedef enum { + CURLVERSION_FIRST, + CURLVERSION_SECOND, + CURLVERSION_LAST /* never actually use this */ +} CURLversion; + +/* The 'CURLVERSION_NOW' is the symbolic name meant to be used by + basicly all programs ever, that want to get version information. It is + meant to be a built-in version number for what kind of struct the caller + expects. If the struct ever changes, we redfine the NOW to another enum + from above. */ +#define CURLVERSION_NOW CURLVERSION_SECOND + +typedef struct { + CURLversion age; /* age of the returned struct */ + const char *version; /* LIBCURL_VERSION */ + unsigned int version_num; /* LIBCURL_VERSION_NUM */ + const char *host; /* OS/host/cpu/machine when configured */ + int features; /* bitmask, see defines below */ + char *ssl_version; /* human readable string */ + long ssl_version_num; /* number */ + const char *libz_version; /* human readable string */ + /* protocols is terminated by an entry with a NULL protoname */ + const char **protocols; + + /* The fields below this were added in CURLVERSION_SECOND */ + const char *ares; + int ares_num; +} curl_version_info_data; + +#define CURL_VERSION_IPV6 (1<<0) +#define CURL_VERSION_KERBEROS4 (1<<1) +#define CURL_VERSION_SSL (1<<2) +#define CURL_VERSION_LIBZ (1<<3) +#define CURL_VERSION_NTLM (1<<4) +#define CURL_VERSION_GSSNEGOTIATE (1<<5) +#define CURL_VERSION_DEBUG (1<<6) /* built with debug capabilities */ +#define CURL_VERSION_ASYNCHDNS (1<<7) +#define CURL_VERSION_SPNEGO (1<<8) +#define CURL_VERSION_LARGEFILE (1<<9) /* supports files bigger than 2GB */ + +/* + * NAME curl_version_info() + * + * DESCRIPTION + * + * This function returns a pointer to a static copy of the version info + * struct. See above. + */ +curl_version_info_data *curl_version_info(CURLversion); + +#ifdef __cplusplus +} +#endif + +/* unfortunately, the easy.h and multi.h include files need options and info + stuff before they can be included! */ +#include "easy.h" /* nothing in curl is fun without the easy stuff */ +#include "multi.h" + +#endif /* __CURL_CURL_H */ diff --git a/src/curl/include/curl/easy.h b/src/curl/include/curl/easy.h new file mode 100644 index 0000000..f2c428b --- /dev/null +++ b/src/curl/include/curl/easy.h @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CURL_EASY_H +#define __CURL_EASY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: easy.h,v 1.9 2004/01/07 09:19:34 bagder Exp $ + ***************************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif + +CURL *curl_easy_init(void); +CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); +CURLcode curl_easy_perform(CURL *curl); +void curl_easy_cleanup(CURL *curl); + +/* + * NAME curl_easy_getinfo() + * + * DESCRIPTION + * + * Request internal information from the curl session with this function. The + * third argument MUST be a pointer to a long, a pointer to a char * or a + * pointer to a double (as the documentation describes elsewhere). The data + * pointed to will be filled in accordingly and can be relied upon only if the + * function returns CURLE_OK. This function is intended to get used *AFTER* a + * performed transfer, all results from this function are undefined until the + * transfer is completed. + */ +CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); + + +/* + * NAME curl_easy_duphandle() + * + * DESCRIPTION + * + * Creates a new curl session handle with the same options set for the handle + * passed in. Duplicating a handle could only be a matter of cloning data and + * options, internal state info and things like persistant connections cannot + * be transfered. It is useful in multithreaded applications when you can run + * curl_easy_duphandle() for each new thread to avoid a series of identical + * curl_easy_setopt() invokes in every thread. + */ +CURL* curl_easy_duphandle(CURL *curl); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/curl/include/curl/mprintf.h b/src/curl/include/curl/mprintf.h new file mode 100644 index 0000000..2cb9ab2 --- /dev/null +++ b/src/curl/include/curl/mprintf.h @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: mprintf.h,v 1.8 2004/01/07 09:19:34 bagder Exp $ + ***************************************************************************/ + +#ifndef H_MPRINTF +#define H_MPRINTF + +#include +#include /* needed for FILE */ + +int curl_mprintf(const char *format, ...); +int curl_mfprintf(FILE *fd, const char *format, ...); +int curl_msprintf(char *buffer, const char *format, ...); +int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...); +int curl_mvprintf(const char *format, va_list args); +int curl_mvfprintf(FILE *fd, const char *format, va_list args); +int curl_mvsprintf(char *buffer, const char *format, va_list args); +int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, va_list args); +char *curl_maprintf(const char *format, ...); +char *curl_mvaprintf(const char *format, va_list args); + +#ifdef _MPRINTF_REPLACE +# define printf curl_mprintf +# define fprintf curl_mfprintf +# define sprintf curl_msprintf +# define snprintf curl_msnprintf +# define vprintf curl_mvprintf +# define vfprintf curl_mvfprintf +# define vsprintf curl_mvsprintf +# define vsnprintf curl_mvsnprintf +# define aprintf curl_maprintf +# define vaprintf curl_mvaprintf +#endif + +#endif /* H_MPRINTF */ diff --git a/src/curl/include/curl/multi.h b/src/curl/include/curl/multi.h new file mode 100644 index 0000000..4c16ddd --- /dev/null +++ b/src/curl/include/curl/multi.h @@ -0,0 +1,234 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CURL_MULTI_H +#define __CURL_MULTI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: multi.h,v 1.17 2004/03/17 12:46:45 bagder Exp $ + ***************************************************************************/ +/* + This is meant to be the "external" header file. Don't give away any + internals here! + + This document presents a mixture of ideas from at least: + - Daniel Stenberg + - Steve Dekorte + - Sterling Hughes + - Ben Greear + + ------------------------------------------- + GOALS + + o Enable a "pull" interface. The application that uses libcurl decides where + and when to ask libcurl to get/send data. + + o Enable multiple simultaneous transfers in the same thread without making it + complicated for the application. + + o Enable the application to select() on its own file descriptors and curl's + file descriptors simultaneous easily. + +*/ +#if defined(_WIN32) && !defined(WIN32) +/* Chris Lewis mentioned that he doesn't get WIN32 defined, only _WIN32 so we + make this adjustment to catch this. */ +#define WIN32 1 +#endif + +#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__) +#include +#else + +/* HP-UX systems version 9, 10 and 11 lack sys/select.h and so does oldish + libc5-based Linux systems. Only include it on system that are known to + require it! */ +#if defined(_AIX) || defined(NETWARE) +#include +#endif + +#include +#include +#include +#endif + +#include "curl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void CURLM; + +typedef enum { + CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */ + CURLM_OK, + CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ + CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ + CURLM_LAST +} CURLMcode; + +typedef enum { + CURLMSG_NONE, /* first, not used */ + CURLMSG_DONE, /* This easy handle has completed. 'result' contains + the CURLcode of the transfer */ + CURLMSG_LAST /* last, not used */ +} CURLMSG; + +struct CURLMsg { + CURLMSG msg; /* what this message means */ + CURL *easy_handle; /* the handle it concerns */ + union { + void *whatever; /* message-specific data */ + CURLcode result; /* return code for transfer */ + } data; +}; +typedef struct CURLMsg CURLMsg; + +/* + * Name: curl_multi_init() + * + * Desc: inititalize multi-style curl usage + * Returns: a new CURLM handle to use in all 'curl_multi' functions. + */ +CURLM *curl_multi_init(void); + +/* + * Name: curl_multi_add_handle() + * + * Desc: add a standard curl handle to the multi stack + * Returns: CURLMcode type, general multi error code. + */ +CURLMcode curl_multi_add_handle(CURLM *multi_handle, + CURL *curl_handle); + + /* + * Name: curl_multi_remove_handle() + * + * Desc: removes a curl handle from the multi stack again + * Returns: CURLMcode type, general multi error code. + */ +CURLMcode curl_multi_remove_handle(CURLM *multi_handle, + CURL *curl_handle); + + /* + * Name: curl_multi_fdset() + * + * Desc: Ask curl for its fd_set sets. The app can use these to select() or + * poll() on. We want curl_multi_perform() called as soon as one of + * them are ready. + * Returns: CURLMcode type, general multi error code. + */ +CURLMcode curl_multi_fdset(CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd); + + /* + * Name: curl_multi_perform() + * + * Desc: When the app thinks there's data available for curl it calls this + * function to read/write whatever there is right now. This returns + * as soon as the reads and writes are done. This function does not + * require that there actually is data available for reading or that + * data can be written, it can be called just in case. It returns + * the number of handles that still transfer data in the second + * argument's integer-pointer. + * + * Returns: CURLMcode type, general multi error code. *NOTE* that this only + * returns errors etc regarding the whole multi stack. There might + * still have occurred problems on invidual transfers even when this + * returns OK. + */ +CURLMcode curl_multi_perform(CURLM *multi_handle, + int *running_handles); + + /* + * Name: curl_multi_cleanup() + * + * Desc: Cleans up and removes a whole multi stack. It does not free or + * touch any individual easy handles in any way. We need to define + * in what state those handles will be if this function is called + * in the middle of a transfer. + * Returns: CURLMcode type, general multi error code. + */ +CURLMcode curl_multi_cleanup(CURLM *multi_handle); + +/* + * Name: curl_multi_info_read() + * + * Desc: Ask the multi handle if there's any messages/informationals from + * the individual transfers. Messages include informationals such as + * error code from the transfer or just the fact that a transfer is + * completed. More details on these should be written down as well. + * + * Repeated calls to this function will return a new struct each + * time, until a special "end of msgs" struct is returned as a signal + * that there is no more to get at this point. + * + * The data the returned pointer points to will not survive calling + * curl_multi_cleanup(). + * + * The 'CURLMsg' struct is meant to be very simple and only contain + * very basic informations. If more involved information is wanted, + * we will provide the particular "transfer handle" in that struct + * and that should/could/would be used in subsequent + * curl_easy_getinfo() calls (or similar). The point being that we + * must never expose complex structs to applications, as then we'll + * undoubtably get backwards compatibility problems in the future. + * + * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out + * of structs. It also writes the number of messages left in the + * queue (after this read) in the integer the second argument points + * to. + */ +CURLMsg *curl_multi_info_read(CURLM *multi_handle, + int *msgs_in_queue); + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif diff --git a/src/curl/include/curl/stdcheaders.h b/src/curl/include/curl/stdcheaders.h new file mode 100644 index 0000000..17d3ec4 --- /dev/null +++ b/src/curl/include/curl/stdcheaders.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __STDC_HEADERS_H +#define __STDC_HEADERS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: stdcheaders.h,v 1.8 2004/01/07 09:19:34 bagder Exp $ + ***************************************************************************/ + +#include + +size_t fread (void *, size_t, size_t, FILE *); +size_t fwrite (const void *, size_t, size_t, FILE *); + +int strcasecmp(const char *, const char *); +int strncasecmp(const char *, const char *, size_t); + +#endif diff --git a/src/curl/include/curl/types.h b/src/curl/include/curl/types.h new file mode 100644 index 0000000..8d96e56 --- /dev/null +++ b/src/curl/include/curl/types.h @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CURL_TYPES_H +#define __CURL_TYPES_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2004, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id: types.h,v 1.7 2004/01/07 09:19:34 bagder Exp $ + ***************************************************************************/ +typedef void CURL; +typedef void CURLconnect; + +#endif /* __CURL_TYPES_H */ diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt new file mode 100644 index 0000000..bbfebc8 --- /dev/null +++ b/src/engine/CMakeLists.txt @@ -0,0 +1,442 @@ +function(q4_add_engine_object target) + add_library(${target} OBJECT ${ARGN}) + target_include_directories(${target} + PRIVATE + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/renderer/jpeg-6 + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/ogg/include + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/vorbis/include + "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)/Include" + ) + target_compile_definitions(${target} + PRIVATE + WIN32 + _WINDOWS + _LOAD_DLL + _USE_32BIT_TIME_T + Q4_NO_PUNKBUSTER + Q4_RECON_SEED + Q4_DISABLE_TOOLS + Q4_DISABLE_GL_LOGGING + Q4_DISABLE_TASKKEY_HOOK + $<$:_DEBUG> + $<$:NDEBUG;_FINAL> + ) + set_target_properties(${target} PROPERTIES FOLDER "Engine/Seed") +endfunction() + +file(GLOB_RECURSE Q4_AAS_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/aas/*.cpp +) +set(Q4_AAS_FILE_SOURCE ${PROJECT_SOURCE_DIR}/aas/AASFile.cpp) + +file(GLOB_RECURSE Q4_CM_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/cm/*.cpp +) +set(Q4_CM_MODEL_SOURCE ${PROJECT_SOURCE_DIR}/cm/CollisionModel.cpp) +set(Q4_CM_LOAD_SOURCE ${PROJECT_SOURCE_DIR}/cm/collisionmodel_load.cpp) +set(Q4_CM_CONTACTS_SOURCE ${PROJECT_SOURCE_DIR}/cm/collisionmodel_contacts.cpp) +set(Q4_CM_CONTENTS_SOURCE ${PROJECT_SOURCE_DIR}/cm/collisionmodel_contents.cpp) +set(Q4_CM_TRACE_SOURCE ${PROJECT_SOURCE_DIR}/cm/collisionmodel_trace.cpp) +set(Q4_CM_TRANSLATE_SOURCE ${PROJECT_SOURCE_DIR}/cm/collisionmodel_translate.cpp) +list(REMOVE_ITEM Q4_CM_SOURCES ${Q4_CM_MODEL_SOURCE}) + +file(GLOB Q4_FRAMEWORK_TOP_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/framework/*.cpp) +set(Q4_FRAMEWORK_SESSION_SOURCES + ${PROJECT_SOURCE_DIR}/framework/common.cpp + ${PROJECT_SOURCE_DIR}/framework/session.cpp + ${PROJECT_SOURCE_DIR}/framework/session_menu.cpp +) +set(Q4_FRAMEWORK_EDITFIELD_SOURCE ${PROJECT_SOURCE_DIR}/framework/editfield.cpp) +set(Q4_FRAMEWORK_CONSOLE_SOURCE ${PROJECT_SOURCE_DIR}/framework/console.cpp) +set(Q4_FRAMEWORK_USERCMD_SOURCE ${PROJECT_SOURCE_DIR}/framework/usercmdgen.cpp) +set(Q4_FRAMEWORK_CONSOLE_SOURCES + ${Q4_FRAMEWORK_EDITFIELD_SOURCE} + ${Q4_FRAMEWORK_CONSOLE_SOURCE} + ${Q4_FRAMEWORK_USERCMD_SOURCE} +) +file(GLOB Q4_FRAMEWORK_ASYNC_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/framework/async/*.cpp) +set(Q4_FRAMEWORK_MSGCHANNEL_SOURCE ${PROJECT_SOURCE_DIR}/framework/async/msgchannel.cpp) +list(REMOVE_ITEM Q4_FRAMEWORK_ASYNC_SOURCES ${Q4_FRAMEWORK_MSGCHANNEL_SOURCE}) +set(Q4_FRAMEWORK_CORE_SOURCES ${Q4_FRAMEWORK_TOP_SOURCES}) +list(REMOVE_ITEM Q4_FRAMEWORK_CORE_SOURCES ${Q4_FRAMEWORK_SESSION_SOURCES} ${Q4_FRAMEWORK_CONSOLE_SOURCES}) + +file(GLOB_RECURSE Q4_UI_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/ui/*.cpp +) + +# Retail quake4.exe renderer compilation units. Keep this explicit: the PDB +# records one object for each entry and the list doubles as a boundary audit. +set(Q4_RENDERER_SOURCES + ${PROJECT_SOURCE_DIR}/renderer/Cinematic.cpp + ${PROJECT_SOURCE_DIR}/renderer/draw_arb.cpp + ${PROJECT_SOURCE_DIR}/renderer/draw_arb2.cpp + ${PROJECT_SOURCE_DIR}/renderer/draw_common.cpp + ${PROJECT_SOURCE_DIR}/renderer/draw_nv10.cpp + ${PROJECT_SOURCE_DIR}/renderer/draw_nv20.cpp + ${PROJECT_SOURCE_DIR}/renderer/draw_r200.cpp + ${PROJECT_SOURCE_DIR}/renderer/GuiModel.cpp + ${PROJECT_SOURCE_DIR}/renderer/Image_files.cpp + ${PROJECT_SOURCE_DIR}/renderer/Image_init.cpp + ${PROJECT_SOURCE_DIR}/renderer/Image_load.cpp + ${PROJECT_SOURCE_DIR}/renderer/Image_process.cpp + ${PROJECT_SOURCE_DIR}/renderer/Image_program.cpp + ${PROJECT_SOURCE_DIR}/renderer/Image_stats.cpp + ${PROJECT_SOURCE_DIR}/renderer/Interaction.cpp + ${PROJECT_SOURCE_DIR}/renderer/Material.cpp + ${PROJECT_SOURCE_DIR}/renderer/MegaTexture.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model.cpp + ${PROJECT_SOURCE_DIR}/renderer/ModelDecal.cpp + ${PROJECT_SOURCE_DIR}/renderer/ModelManager.cpp + ${PROJECT_SOURCE_DIR}/renderer/ModelOverlay.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_ase.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_beam.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_liquid.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_lwo.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_ma.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_md3.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_md5.cpp + ${PROJECT_SOURCE_DIR}/renderer/Model_sprite.cpp + ${PROJECT_SOURCE_DIR}/renderer/RenderEntity.cpp + ${PROJECT_SOURCE_DIR}/renderer/RenderSystem.cpp + ${PROJECT_SOURCE_DIR}/renderer/RenderSystem_init.cpp + ${PROJECT_SOURCE_DIR}/renderer/RenderWorld.cpp + ${PROJECT_SOURCE_DIR}/renderer/RenderWorld_demo.cpp + ${PROJECT_SOURCE_DIR}/renderer/RenderWorld_load.cpp + ${PROJECT_SOURCE_DIR}/renderer/RenderWorld_portals.cpp + ${PROJECT_SOURCE_DIR}/renderer/Shaders.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvGLSLShader.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvIndexBuffer.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvMesh.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvPrimBatch.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvRenderModelMD5R.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvSpecial.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvTexRenderTarget.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvVertexBuffer.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvVertexBufferCopy.cpp + ${PROJECT_SOURCE_DIR}/renderer/rvVertexFormat.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_backend.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_deform.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_font.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_guisurf.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_light.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_lightrun.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_main.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_orderIndexes.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_polytope.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_render.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_rendertools.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_shadowbounds.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_stencilshadow.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_subview.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_trace.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_trisurf.cpp + ${PROJECT_SOURCE_DIR}/renderer/tr_turboshadow.cpp + ${PROJECT_SOURCE_DIR}/renderer/VertexCache.cpp +) + +# Retail BSE object boundaries recovered from quake4.pdb. +set(Q4_BSE_SOURCES + ${PROJECT_SOURCE_DIR}/bse/BSE_Bounds.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Effect.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_EffectTemplate.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Electricity.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Envelopes.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Light.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Manager.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_ParseParticle2.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Particle.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Render.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_Segment.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_SegmentTemplate.cpp + ${PROJECT_SOURCE_DIR}/bse/BSE_SpawnDomains.cpp +) +file(GLOB Q4_JPEG_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/renderer/jpeg-6/*.c) + +file(GLOB_RECURSE Q4_SOUND_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/sound/*.cpp) +file(GLOB_RECURSE Q4_OGGVORBIS_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/*.c +) + +file(GLOB_RECURSE Q4_SYS_SOURCES CONFIGURE_DEPENDS + ${PROJECT_SOURCE_DIR}/sys/*.cpp +) + +q4_add_engine_object(q4_engine_aas_file ${Q4_AAS_FILE_SOURCE}) +q4_add_engine_object(q4_engine_aas ${Q4_AAS_SOURCES}) +target_compile_definitions(q4_engine_aas PRIVATE Q4_AAS_LEGACY_SEED) +q4_add_engine_object(q4_engine_cm_model ${Q4_CM_MODEL_SOURCE}) +target_compile_definitions(q4_engine_cm_model PRIVATE Q4_CM_MODEL_ONLY) +q4_add_engine_object(q4_engine_cm_load ${Q4_CM_LOAD_SOURCE}) +q4_add_engine_object(q4_engine_cm_contacts ${Q4_CM_CONTACTS_SOURCE}) +q4_add_engine_object(q4_engine_cm_contents ${Q4_CM_CONTENTS_SOURCE}) +q4_add_engine_object(q4_engine_cm_trace ${Q4_CM_TRACE_SOURCE}) +q4_add_engine_object(q4_engine_cm_translate ${Q4_CM_TRANSLATE_SOURCE}) +q4_add_engine_object(q4_engine_cm_seed ${Q4_CM_SOURCES}) +target_compile_definitions(q4_engine_cm_seed PRIVATE Q4_CM_LEGACY_SEED) +set(Q4_FRAMEWORK_CORE_TARGETS) +foreach(source IN LISTS Q4_FRAMEWORK_CORE_SOURCES) + get_filename_component(stem ${source} NAME_WE) + set(target q4_engine_framework_core_${stem}) + q4_add_engine_object(${target} ${source}) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) + list(APPEND Q4_FRAMEWORK_CORE_TARGETS ${target}) +endforeach() +add_custom_target(q4_engine_framework_core DEPENDS ${Q4_FRAMEWORK_CORE_TARGETS}) +set_target_properties(q4_engine_framework_core PROPERTIES FOLDER "Engine/Seed") +q4_add_engine_object(q4_engine_framework_editfield ${Q4_FRAMEWORK_EDITFIELD_SOURCE}) +q4_add_engine_object(q4_engine_framework_console_impl ${Q4_FRAMEWORK_CONSOLE_SOURCE}) +q4_add_engine_object(q4_engine_framework_usercmd ${Q4_FRAMEWORK_USERCMD_SOURCE}) +add_custom_target(q4_engine_framework_console + DEPENDS + q4_engine_framework_editfield + q4_engine_framework_console_impl + q4_engine_framework_usercmd +) +set_target_properties(q4_engine_framework_console PROPERTIES FOLDER "Engine/Seed") +set(Q4_FRAMEWORK_ASYNC_TARGETS) +foreach(source IN LISTS Q4_FRAMEWORK_ASYNC_SOURCES) + get_filename_component(stem ${source} NAME_WE) + set(target q4_engine_framework_async_${stem}) + q4_add_engine_object(${target} ${source}) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) + list(APPEND Q4_FRAMEWORK_ASYNC_TARGETS ${target}) +endforeach() +add_custom_target(q4_engine_framework_async DEPENDS ${Q4_FRAMEWORK_ASYNC_TARGETS}) +set_target_properties(q4_engine_framework_async PROPERTIES FOLDER "Engine/Seed") +q4_add_engine_object(q4_engine_framework_msgchannel ${Q4_FRAMEWORK_MSGCHANNEL_SOURCE}) +target_compile_definitions(q4_engine_framework_msgchannel PRIVATE Q4_RECON_SDK_MSGQUEUE) +q4_add_engine_object(q4_engine_framework_session ${Q4_FRAMEWORK_SESSION_SOURCES}) +foreach(target q4_engine_framework_editfield q4_engine_framework_console_impl q4_engine_framework_usercmd q4_engine_framework_session) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) +endforeach() +set(Q4_UI_TARGETS) +foreach(source IN LISTS Q4_UI_SOURCES) + get_filename_component(stem ${source} NAME_WE) + set(target q4_engine_ui_${stem}) + q4_add_engine_object(${target} ${source}) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) + list(APPEND Q4_UI_TARGETS ${target}) +endforeach() +add_custom_target(q4_engine_ui DEPENDS ${Q4_UI_TARGETS}) +set_target_properties(q4_engine_ui PROPERTIES FOLDER "Engine/Seed") + +set(Q4_RENDERER_TARGETS) +foreach(source IN LISTS Q4_RENDERER_SOURCES) + get_filename_component(stem ${source} NAME_WE) + set(target q4_engine_renderer_${stem}) + q4_add_engine_object(${target} ${source}) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) + list(APPEND Q4_RENDERER_TARGETS ${target}) +endforeach() +add_custom_target(q4_engine_renderer DEPENDS ${Q4_RENDERER_TARGETS}) +set_target_properties(q4_engine_renderer PROPERTIES FOLDER "Engine/Seed") +set(Q4_BSE_TARGETS) +foreach(source IN LISTS Q4_BSE_SOURCES) + get_filename_component(stem ${source} NAME_WE) + string(TOLOWER ${stem} stem_lower) + set(target q4_engine_bse_${stem_lower}) + q4_add_engine_object(${target} ${source}) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) + list(APPEND Q4_BSE_TARGETS ${target}) +endforeach() +add_custom_target(q4_engine_bse DEPENDS ${Q4_BSE_TARGETS}) +set_target_properties(q4_engine_bse PROPERTIES FOLDER "Engine/Seed") +q4_add_engine_object(q4_bse_abi ${PROJECT_SOURCE_DIR}/tests/abi/BSE_ABI.cpp) +target_compile_definitions(q4_bse_abi PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) +set_target_properties(q4_bse_abi PROPERTIES FOLDER "Reconstruction/ABI") +q4_add_engine_object(q4_renderer_raven_abi ${PROJECT_SOURCE_DIR}/tests/abi/RendererRaven_ABI.cpp) +target_compile_definitions(q4_renderer_raven_abi PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) +set_target_properties(q4_renderer_raven_abi PROPERTIES FOLDER "Reconstruction/ABI") +q4_add_engine_object(q4_thirdparty_jpeg ${Q4_JPEG_SOURCES}) +target_include_directories(q4_thirdparty_jpeg PRIVATE ${PROJECT_SOURCE_DIR}/renderer/jpeg-6) +set_target_properties(q4_thirdparty_jpeg PROPERTIES FOLDER "ThirdParty") +set(Q4_SOUND_TARGETS) +foreach(source IN LISTS Q4_SOUND_SOURCES) + get_filename_component(stem ${source} NAME_WE) + set(target q4_engine_sound_${stem}) + q4_add_engine_object(${target} ${source}) + target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/sound ${PROJECT_SOURCE_DIR}/sys/win32) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE Q4_RECONSTRUCTED_SOUND) + list(APPEND Q4_SOUND_TARGETS ${target}) +endforeach() +add_custom_target(q4_engine_sound DEPENDS ${Q4_SOUND_TARGETS}) +set_target_properties(q4_engine_sound PROPERTIES FOLDER "Engine/Seed") +q4_add_engine_object(q4_thirdparty_oggvorbis ${Q4_OGGVORBIS_SOURCES}) +target_include_directories(q4_thirdparty_oggvorbis PRIVATE + ${PROJECT_SOURCE_DIR}/sound/OggVorbis + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/ogg + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/vorbis + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/vorbissrc +) +set_target_properties(q4_thirdparty_oggvorbis PROPERTIES FOLDER "ThirdParty") +set(Q4_SYS_TARGETS) +foreach(source IN LISTS Q4_SYS_SOURCES) + get_filename_component(stem ${source} NAME_WE) + set(target q4_engine_sys_${stem}) + q4_add_engine_object(${target} ${source}) + target_compile_definitions(${target} PRIVATE Q4_RECON_ENGINE_PRIVATE Q4_RECON_SDK_MSGQUEUE) + list(APPEND Q4_SYS_TARGETS ${target}) +endforeach() +add_custom_target(q4_engine_sys DEPENDS ${Q4_SYS_TARGETS}) +set_target_properties(q4_engine_sys PROPERTIES FOLDER "Engine/Seed") + +add_custom_target(q4_engine_seed + DEPENDS + q4_engine_aas + q4_engine_aas_file + q4_engine_cm_model + q4_engine_cm_load + q4_engine_cm_contacts + q4_engine_cm_contents + q4_engine_cm_trace + q4_engine_cm_translate + q4_engine_cm_seed + q4_engine_framework_core + q4_engine_framework_console + q4_engine_framework_async + q4_engine_framework_msgchannel + q4_engine_framework_session + q4_engine_ui + q4_engine_renderer + q4_engine_bse + q4_thirdparty_jpeg + q4_engine_sound + q4_thirdparty_oggvorbis + q4_engine_sys +) +set_target_properties(q4_engine_seed PROPERTIES FOLDER "Engine") + +add_custom_target(q4_reconstruction_foundation + DEPENDS + q4_idlib + q4_engine_aas + q4_engine_aas_file + q4_engine_cm_model + q4_engine_cm_load + q4_engine_cm_contacts + q4_engine_cm_contents + q4_engine_cm_trace + q4_engine_cm_translate + q4_engine_cm_seed + q4_engine_framework_console + q4_engine_framework_core_cmdsystem + q4_engine_framework_core_compressor + q4_engine_framework_core_cvarsystem + q4_engine_framework_core_declaf + q4_engine_framework_core_declentitydef + q4_engine_framework_core_declmanager + q4_engine_framework_core_declpda + q4_engine_framework_core_declskin + q4_engine_framework_core_decltable + q4_engine_framework_core_demofile + q4_engine_framework_core_eventloop + q4_engine_framework_core_file + q4_engine_framework_core_filesystem + q4_engine_framework_core_keyinput + q4_engine_framework_core_unzip + q4_engine_framework_async_asyncnetwork + q4_engine_framework_async_networksystem + q4_engine_framework_msgchannel + q4_engine_ui + q4_thirdparty_jpeg + q4_thirdparty_oggvorbis +) +set_target_properties(q4_reconstruction_foundation PROPERTIES FOLDER "Reconstruction") + +# Full reconstructed Win32 engine integration target. The per-compiland +# object targets above remain the source-ownership/ABI audit surface; this +# target compiles the same recovered sources into a runnable executable. +# Editor/dmap tool exports and CURL are intentionally disabled until their +# engine-facing interfaces have been reconstructed from the retail PDB. +set(Q4_OPENAL_LOADER_SOURCES + ${PROJECT_SOURCE_DIR}/openal/idal.cpp +) + +add_executable(q4xp WIN32 + ${Q4_AAS_SOURCES} + ${Q4_CM_MODEL_SOURCE} + ${Q4_CM_SOURCES} + ${Q4_FRAMEWORK_TOP_SOURCES} + ${Q4_FRAMEWORK_ASYNC_SOURCES} + ${Q4_FRAMEWORK_MSGCHANNEL_SOURCE} + ${Q4_UI_SOURCES} + ${Q4_RENDERER_SOURCES} + ${Q4_BSE_SOURCES} + ${Q4_JPEG_SOURCES} + ${Q4_SOUND_SOURCES} + ${Q4_OGGVORBIS_SOURCES} + ${Q4_OPENAL_LOADER_SOURCES} + ${Q4_SYS_SOURCES} +) + +target_include_directories(q4xp PRIVATE + ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/renderer/jpeg-6 + ${PROJECT_SOURCE_DIR}/sound + ${PROJECT_SOURCE_DIR}/sys/win32 + ${PROJECT_SOURCE_DIR}/sound/OggVorbis + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/ogg + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/ogg/include + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/vorbis + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/vorbis/include + ${PROJECT_SOURCE_DIR}/sound/OggVorbis/vorbissrc + "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)/Include" +) + +target_compile_definitions(q4xp PRIVATE + WIN32 + _WINDOWS + _LOAD_DLL + _USE_32BIT_TIME_T + Q4_NO_PUNKBUSTER + Q4_RECON_SEED + Q4_RECON_ENGINE_PRIVATE + Q4_RECON_SDK_MSGQUEUE + Q4_DISABLE_TASKKEY_HOOK + Q4_DISABLE_TOOLS + Q4_DISABLE_GL_LOGGING + Q4_RECONSTRUCTED_SOUND + ID_ENABLE_CURL=0 + $<$:_DEBUG> + $<$:NDEBUG;_FINAL> +) + +target_link_directories(q4xp PRIVATE + "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)/Lib/x86" + ${PROJECT_SOURCE_DIR}/openal/lib +) + +target_link_libraries(q4xp PRIVATE + q4_idlib + opengl32 + winmm + ws2_32 + iphlpapi + dbghelp + comctl32 + dinput8 + dsound + dxguid + version +) + +if(MSVC) + # Keep an address map and symbols beside the reconstructed executable so + # retail-startup crashes can be resolved without enabling the tools DLL. + target_compile_options(q4xp PRIVATE /Zi) + target_link_options(q4xp PRIVATE + /DEBUG:FULL + /PDB:E:/projects/Quake4Alpha/q4xp.pdb + /MAP:E:/projects/Quake4Alpha/q4xp.map + ) +endif() + +set_target_properties(q4xp PROPERTIES + OUTPUT_NAME q4xp + RUNTIME_OUTPUT_DIRECTORY "E:/projects/Quake4Alpha" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "E:/projects/Quake4Alpha" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "E:/projects/Quake4Alpha" + PDB_OUTPUT_DIRECTORY "E:/projects/Quake4Alpha" + FOLDER "Engine" +) diff --git a/src/framework/BuildDefines.h b/src/framework/BuildDefines.h new file mode 100644 index 0000000..412808a --- /dev/null +++ b/src/framework/BuildDefines.h @@ -0,0 +1,97 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Preprocessor settings for compiling different versions. + +=============================================================================== +*/ + +// useful for network debugging, turns off 'LAN' checks, all IPs are classified 'internet' +#ifndef ID_NOLANADDRESS + #define ID_NOLANADDRESS 0 +#endif + +// let .dds be loaded from FS without altering pure state. only for developement. +#ifndef ID_PURE_ALLOWDDS + #define ID_PURE_ALLOWDDS 0 +#endif + +// build an exe with no CVAR_CHEAT controls +#ifndef ID_ALLOW_CHEATS + #define ID_ALLOW_CHEATS 0 +#endif + +#ifndef ID_ENABLE_CURL + #if !defined( _XENON ) + #define ID_ENABLE_CURL 1 + #else + #define ID_ENABLE_CURL 0 + #endif +#endif + +// fake a pure client. useful to connect an all-debug client to a server +#ifndef ID_FAKE_PURE + #define ID_FAKE_PURE 0 +#endif + +// don't do backtraces in release builds. +// atm, we have no useful way to reconstruct the trace, so let's leave it off +#define ID_BT_STUB +#ifndef ID_BT_STUB + #if defined( __linux__ ) + #if defined( _DEBUG ) + #define ID_BT_STUB + #endif + #else + #define ID_BT_STUB + #endif +#endif + +#ifndef ID_ENFORCE_KEY +# if !defined( ID_DEDICATED ) && !defined( ID_DEMO_BUILD ) +# define ID_ENFORCE_KEY 0 +# else +// twhitaker: just leave it undefined +// TTimo: that breaks the ability to control it from command line settings with !win32 builds, but I can live with it +//# define ID_ENFORCE_KEY 0 +# endif +#endif + +// verify checksums in clientinfo traffic +// NOTE: this makes the network protocol incompatible +#ifndef ID_CLIENTINFO_TAGS + #define ID_CLIENTINFO_TAGS 0 +#endif + +// if this is defined, the executable positively won't work with any paks other +// than the demo pak, even if productid is present. +//#define ID_DEMO_BUILD + +#if !defined( _WIN32 ) + // DOA? didn't see the pbuffer code used at all through the code + #define TMP_PBUFFSTUB +#endif diff --git a/src/framework/BuildVersion.h b/src/framework/BuildVersion.h new file mode 100644 index 0000000..370b364 --- /dev/null +++ b/src/framework/BuildVersion.h @@ -0,0 +1,27 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// that one is no longer updated with the Q4 source in SVN +// but is used in some of the single player mishmush so I don't want to touch it +const int BUILD_NUMBER = 1283; diff --git a/src/framework/CVarSystem.h b/src/framework/CVarSystem.h new file mode 100644 index 0000000..3b0c967 --- /dev/null +++ b/src/framework/CVarSystem.h @@ -0,0 +1,439 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __CVARSYSTEM_H__ +#define __CVARSYSTEM_H__ + +/* +=============================================================================== + + Console Variables (CVars) are used to hold scalar or string variables + that can be changed or displayed at the console as well as accessed + directly in code. + + CVars are mostly used to hold settings that can be changed from the + console or saved to and loaded from configuration files. CVars are also + occasionally used to communicate information between different modules + of the program. + + CVars are restricted from having the same names as console commands to + keep the console interface from being ambiguous. + + CVars can be accessed from the console in three ways: + cvarName prints the current value + cvarName X sets the value to X if the variable exists + set cvarName X as above, but creates the CVar if not present + + CVars may be declared in the global namespace, in classes and in functions. + However declarations in classes and functions should always be static to + save space and time. Making CVars static does not change their + functionality due to their global nature. + + CVars should be contructed only through one of the constructors with name, + value, flags and description. The name, value and description parameters + to the constructor have to be static strings, do not use va() or the like + functions returning a string. + + CVars may be declared multiple times using the same name string. However, + they will all reference the same value and changing the value of one CVar + changes the value of all CVars with the same name. + + CVars should always be declared with the correct type flag: CVAR_BOOL, + CVAR_INTEGER or CVAR_FLOAT. If no such flag is specified the CVar + defaults to type string. If the CVAR_BOOL flag is used there is no need + to specify an argument auto-completion function because the CVar gets + one assigned automatically. + + CVars are automatically range checked based on their type and any min/max + or valid string set specified in the constructor. + + CVars are always considered cheats except when CVAR_NOCHEAT, CVAR_INIT, + CVAR_ROM, CVAR_ARCHIVE, CVAR_USERINFO, CVAR_SERVERINFO, CVAR_NETWORKSYNC + is set. + +=============================================================================== +*/ + +typedef enum { + CVAR_ALL = -1, // all flags + CVAR_BOOL = BIT(0), // variable is a boolean + CVAR_INTEGER = BIT(1), // variable is an integer + CVAR_FLOAT = BIT(2), // variable is a float + CVAR_SYSTEM = BIT(3), // system variable + CVAR_RENDERER = BIT(4), // renderer variable + CVAR_SOUND = BIT(5), // sound variable + CVAR_GUI = BIT(6), // gui variable + CVAR_GAME = BIT(7), // game variable + CVAR_TOOL = BIT(8), // tool variable + CVAR_USERINFO = BIT(9), // sent to servers, available to menu + CVAR_SERVERINFO = BIT(10), // sent from servers, available to menu + CVAR_NETWORKSYNC = BIT(11), // cvar is synced from the server to clients + CVAR_STATIC = BIT(12), // statically declared, not user created + CVAR_CHEAT = BIT(13), // variable is considered a cheat + CVAR_NOCHEAT = BIT(14), // variable is not considered a cheat + CVAR_INIT = BIT(15), // can only be set from the command-line + CVAR_ROM = BIT(16), // display only, cannot be set by user at all + CVAR_ARCHIVE = BIT(17), // set to cause it to be saved to a config file + CVAR_MODIFIED = BIT(18), // set when the variable is modified + CVAR_INFO = BIT(19), // sent as part of the MOTD packet + CVAR_NORESET = BIT(20), // don't reset the contents on cvar system restart + CVAR_CASE_SENSITIVE = BIT(21), // a change in case of the string contents sets the modified flag + CVAR_SPECIAL_CONCAT = BIT(22), // special concatination of the incoming string to the cvar system, will remove space between ^ and the code that is produced by tokenzier + CVAR_STRIPTRAILING = BIT(23), // always strip trailing / on that cvar + CVAR_REPEATERINFO = BIT(24), // sent from repeaters, available to menu +} cvarFlags_t; + + +/* +=============================================================================== + + idCVar + +=============================================================================== +*/ + +class idCVar { +public: + // Never use the default constructor. + idCVar( void ) { assert( typeid( this ) != typeid( idCVar ) ); } + + // Always use one of the following constructors. + idCVar( const char *name, const char *value, int flags, const char *description, + argCompletion_t valueCompletion = NULL ); + idCVar( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, argCompletion_t valueCompletion = NULL ); + idCVar( const char *name, const char *value, int flags, const char *description, + const char **valueStrings, argCompletion_t valueCompletion = NULL ); + + virtual ~idCVar( void ) {} + + const char * GetName( void ) const { return internalVar->name; } + int GetFlags( void ) const { return internalVar->flags; } + const char * GetDescription( void ) const { return internalVar->description; } + float GetMinValue( void ) const { return internalVar->valueMin; } + float GetMaxValue( void ) const { return internalVar->valueMax; } + const char ** GetValueStrings( void ) const { return valueStrings; } + argCompletion_t GetValueCompletion( void ) const { return valueCompletion; } + + bool IsModified( void ) const { return ( internalVar->flags & CVAR_MODIFIED ) != 0; } + void SetModified( void ) { internalVar->flags |= CVAR_MODIFIED; } + void ClearModified( void ) { internalVar->flags &= ~CVAR_MODIFIED; } + + const char * GetString( void ) const { return internalVar->value; } + bool GetBool( void ) const { return ( internalVar->integerValue != 0 ); } + int GetInteger( void ) const { return internalVar->integerValue; } + float GetFloat( void ) const { return internalVar->floatValue; } + + void SetString( const char *value ) { internalVar->InternalSetString( value ); } + void SetBool( const bool value ) { internalVar->InternalSetBool( value ); } + void SetInteger( const int value ) { internalVar->InternalSetInteger( value ); } + void SetFloat( const float value ) { internalVar->InternalSetFloat( value ); } + + void SetInternalVar( idCVar *cvar ) { internalVar = cvar; } + + void SetFlag( const cvarFlags_t flag ) { internalVar->flags |= flag; } + void RemoveFlag( const cvarFlags_t flag ) { internalVar->flags &= ~flag; } + + static void RegisterStaticVars( void ); + +protected: + const char * name; // name + const char * value; // value + const char * description; // description + int flags; // CVAR_? flags + float valueMin; // minimum value + float valueMax; // maximum value + const char ** valueStrings; // valid value strings + argCompletion_t valueCompletion; // value auto-completion function + int integerValue; // atoi( string ) + float floatValue; // atof( value ) + idCVar * internalVar; // internal cvar + idCVar * next; // next statically declared cvar + +private: + void Init( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, const char **valueStrings, argCompletion_t valueCompletion ); + + virtual void InternalSetString( const char *newValue ) {} + virtual void InternalSetBool( const bool newValue ) {} + virtual void InternalSetInteger( const int newValue ) {} + virtual void InternalSetFloat( const float newValue ) {} + + static idCVar * staticVars; +}; + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + argCompletion_t valueCompletion ) { + if ( !valueCompletion && ( flags & CVAR_BOOL ) ) { + valueCompletion = idCmdSystem::ArgCompletion_Boolean; + } + Init( name, value, flags, description, 1, -1, NULL, valueCompletion ); +} + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, argCompletion_t valueCompletion ) { + Init( name, value, flags, description, valueMin, valueMax, NULL, valueCompletion ); +} + +ID_INLINE idCVar::idCVar( const char *name, const char *value, int flags, const char *description, + const char **valueStrings, argCompletion_t valueCompletion ) { + Init( name, value, flags, description, 1, -1, valueStrings, valueCompletion ); +} + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + +/* +=============================================================================== + + idCVarHelp + +=============================================================================== +*/ + +typedef enum { + CVARHELP_ALL = -1, // all categories + CVARHELP_GAME = BIT(0), // game menu + CVARHELP_RENDERER = BIT(1), // renderer menu + CVARHELP_PHYSICS = BIT(2), // physics menu + CVARHELP_SOUND = BIT(3), // sound menu + CVARHELP_AI = BIT(4), // AI menu +} cvarHelpCategory_t; + + +class idCVarHelp { +public: + // Never use the default constructor. + idCVarHelp( void ) { assert( typeid(this) != typeid(idCVarHelp) ); } + + // Always use one of the following constructors. + idCVarHelp( const char *cvarName, const char *friendlyName, const char *choices, const char *values, cvarHelpCategory_t category ); + + idCVarHelp( const idCVarHelp © ); + + const char * GetCVarName( void ) const { return cvarName; } + const char * GetFriendlyName( void ) const { return friendlyName; } + const char * GetChoices( void ) const { return choices; } + const char * GetValues( void ) const { return values; } + const cvarHelpCategory_t GetCategory( void ) const { return category; } + const idCVarHelp * GetNext( void ) const { return next; } + + void SetNext( const idCVarHelp *value ) { next = value; } + + static void RegisterStatics( void ); + +private: + const char * cvarName; // the cvar this help belongs to + const char * friendlyName; // a textual name for the cvar impaired + const char * choices; // a textual list of choices for the cvar impaired + const char * values; // the list of values that goes with the choices + cvarHelpCategory_t category; // the category(s) this cvar should appear under + const idCVarHelp * next; // next statically declared cvar helper + + static idCVarHelp * staticCVarHelps; + static idCVarHelp * staticCVarHelpsTail; +}; + +ID_INLINE idCVarHelp::idCVarHelp( const idCVarHelp © ) { + cvarName = copy.cvarName; + friendlyName = copy.friendlyName; + choices = copy.choices; + values = copy.values; + category = copy.category; + next = NULL; +} + +// RAVEN END + +/* +=============================================================================== + + idCVarSystem + +=============================================================================== +*/ + +class idCVarSystem { +public: + virtual ~idCVarSystem( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual bool IsInitialized( void ) const = 0; + + // Registers a CVar. + virtual void Register( idCVar *cvar ) = 0; + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + // Registers a CVarHelp. + virtual void Register( const idCVarHelp *cvarHelp ) = 0; + virtual idCVarHelp * GetHelps( cvarHelpCategory_t category ) = 0; +// RAVEN END + + // Finds the CVar with the given name. + // Returns NULL if there is no CVar with the given name. + virtual idCVar * Find( const char *name ) = 0; + + // Sets the value of a CVar by name. + virtual void SetCVarString( const char *name, const char *value, int flags = 0 ) = 0; + virtual void SetCVarBool( const char *name, const bool value, int flags = 0 ) = 0; + virtual void SetCVarInteger( const char *name, const int value, int flags = 0 ) = 0; + virtual void SetCVarFloat( const char *name, const float value, int flags = 0 ) = 0; + + // Gets the value of a CVar by name. + virtual const char * GetCVarString( const char *name ) const = 0; + virtual bool GetCVarBool( const char *name ) const = 0; + virtual int GetCVarInteger( const char *name ) const = 0; + virtual float GetCVarFloat( const char *name ) const = 0; + + // Called by the command system when argv(0) doesn't match a known command. + // Returns true if argv(0) is a variable reference and prints or changes the CVar. + virtual bool Command( const idCmdArgs &args ) = 0; + + // Command and argument completion using callback for each valid string. + virtual void CommandCompletion( void(*callback)( const char *s ) ) = 0; + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) = 0; + + // Sets/gets/clears modified flags that tell what kind of CVars have changed. + virtual void SetModifiedFlags( int flags ) = 0; + virtual int GetModifiedFlags( void ) const = 0; + virtual void ClearModifiedFlags( int flags ) = 0; + + // Resets variables with one of the given flags set. + virtual void ResetFlaggedVariables( int flags ) = 0; + + // Removes auto-completion from the flagged variables. + virtual void RemoveFlaggedAutoCompletion( int flags ) = 0; + + // Writes variables with one of the given flags set to the given file. + virtual void WriteFlaggedVariables( int flags, const char *setCmd, idFile *f ) const = 0; +// RAVEN BEGIN +// nrausch: memory cvar support + virtual unsigned int WriteFlaggedVariables( int flags, const char *setCmd, byte *buf, unsigned int bufSize ) const = 0; + virtual void ApplyFlaggedVariables( byte *buf, unsigned int bufSize ) = 0; + virtual idStr WriteFlaggedVariables( int flags ) const = 0; +// RAVEN END + + // Moves CVars to and from dictionaries. + virtual const idDict * MoveCVarsToDict( int flags ) const = 0; + virtual void SetCVarsFromDict( const idDict &dict ) = 0; +}; + +extern idCVarSystem * cvarSystem; + + +/* +=============================================================================== + + CVar Registration + + Each DLL using CVars has to declare a private copy of the static variable + idCVar::staticVars like this: idCVar * idCVar::staticVars = NULL; + Furthermore idCVar::RegisterStaticVars() has to be called after the + cvarSystem pointer is set when the DLL is first initialized. + +=============================================================================== +*/ + +ID_INLINE void idCVar::Init( const char *name, const char *value, int flags, const char *description, + float valueMin, float valueMax, const char **valueStrings, argCompletion_t valueCompletion ) { + this->name = name; + this->value = value; + this->flags = flags; + this->description = description; + this->flags = flags | CVAR_STATIC; + this->valueMin = valueMin; + this->valueMax = valueMax; + this->valueStrings = valueStrings; + this->valueCompletion = valueCompletion; + this->integerValue = 0; + this->floatValue = 0.0f; + this->internalVar = this; + if ( staticVars != (idCVar *)0xFFFFFFFF ) { + this->next = staticVars; + staticVars = this; + } else { + cvarSystem->Register( this ); + } +} + +ID_INLINE void idCVar::RegisterStaticVars( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CVAR); +// RAVEN END + if ( staticVars != (idCVar *)0xFFFFFFFF ) { + for ( idCVar *cvar = staticVars; cvar; cvar = cvar->next ) { + cvarSystem->Register( cvar ); + } + staticVars = (idCVar *)0xFFFFFFFF; + } +} + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui +ID_INLINE idCVarHelp::idCVarHelp( const char *cvarName, const char *friendlyName, const char *choices, const char *values, cvarHelpCategory_t category ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CVAR); +// RAVEN END + this->cvarName = cvarName; + this->friendlyName = friendlyName; + this->choices = choices; + this->values = values; + this->category = category; + this->next = NULL; + + if ( staticCVarHelps != (idCVarHelp *)0xFFFFFFFF ) { + if ( !staticCVarHelpsTail ) { + staticCVarHelps = this; + } else { + staticCVarHelpsTail->next = this; + } + staticCVarHelpsTail = this; + staticCVarHelpsTail->next = NULL; + } else { + cvarSystem->Register( this ); + } +} + +ID_INLINE void idCVarHelp::RegisterStatics( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CVAR); +// RAVEN END + if ( staticCVarHelps != (idCVarHelp *)0xFFFFFFFF ) { + for ( const idCVarHelp *cvarHelp = staticCVarHelps; cvarHelp; cvarHelp = cvarHelp->next ) { + cvarSystem->Register( cvarHelp ); + } + staticCVarHelps = (idCVarHelp *)0xFFFFFFFF; + } +} +// RAVEN END + +#endif /* !__CVARSYSTEM_H__ */ diff --git a/src/framework/CmdSystem.h b/src/framework/CmdSystem.h new file mode 100644 index 0000000..2f633c8 --- /dev/null +++ b/src/framework/CmdSystem.h @@ -0,0 +1,249 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CMDSYSTEM_H__ +#define __CMDSYSTEM_H__ + +/* +=============================================================================== + + Console command execution and command text buffering. + + Any number of commands can be added in a frame from several different + sources. Most commands come from either key bindings or console line input, + but entire text files can be execed. + + Command execution takes a null terminated string, breaks it into tokens, + then searches for a command or variable that matches the first token. + +=============================================================================== +*/ + +// command flags +typedef enum { + CMD_FL_ALL = -1, + CMD_FL_CHEAT = BIT(0), // command is considered a cheat + CMD_FL_SYSTEM = BIT(1), // system command + CMD_FL_RENDERER = BIT(2), // renderer command + CMD_FL_SOUND = BIT(3), // sound command + CMD_FL_GAME = BIT(4), // game command + CMD_FL_TOOL = BIT(5) // tool command +} cmdFlags_t; + +// parameters for command buffer stuffing +typedef enum { + CMD_EXEC_NOW, // don't return until completed + CMD_EXEC_INSERT, // insert at current position, but don't run yet + CMD_EXEC_APPEND // add to end of the command buffer (normal case) +} cmdExecution_t; + +// command function +typedef void (*cmdFunction_t)( const idCmdArgs &args ); + +// argument completion function +typedef void (*argCompletion_t)( const idCmdArgs &args, void(*callback)( const char *s ) ); + + +class idCmdSystem { +public: + virtual ~idCmdSystem( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // Registers a command and the function to call for it. + virtual void AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion = NULL ) = 0; + // Removes a command. + virtual void RemoveCommand( const char *cmdName ) = 0; + // Remove all commands with one of the flags set. + virtual void RemoveFlaggedCommands( int flags ) = 0; + + // Command and argument completion using callback for each valid string. + virtual void CommandCompletion( void(*callback)( const char *s ) ) = 0; + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) = 0; + + // Adds command text to the command buffer, does not add a final \n + virtual void BufferCommandText( cmdExecution_t exec, const char *text ) = 0; + // Pulls off \n \r or ; terminated lines of text from the command buffer and + // executes the commands. Stops when the buffer is empty. + // Normally called once per frame, but may be explicitly invoked. + virtual void ExecuteCommandBuffer( void ) = 0; + + // Base for path/file auto-completion. + virtual void ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ) = 0; + +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual void ArgCompletion_Models( const idCmdArgs &args, void(*callback)( const char *s ), bool strogg, bool marine ) = 0; +#endif + + // Base for decl name auto-completion. + virtual void ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ) = 0; + + // Adds to the command buffer in tokenized form ( CMD_EXEC_NOW or CMD_EXEC_APPEND only ) + virtual void BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ) = 0; + + // Restore these cvars when the next reloadEngine is done +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual void SetupCVarsForReloadEngine( const idDict &dict ) = 0; +#endif + // Setup a reloadEngine to happen on next command run, and give a command to execute after reload + virtual void SetupReloadEngine( const idCmdArgs &args ) = 0; + virtual bool PostReloadEngine( void ) = 0; + + // There is a cache of the last completion operation that may need to be cleared sometimes +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual void ClearCompletion( void ) = 0; +#endif + + // Default argument completion functions. + static void ArgCompletion_Boolean( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_Integer( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_String( const idCmdArgs &args, void(*callback)( const char *s ) ); + template + static void ArgCompletion_Decl( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_FileName( const idCmdArgs &args, void(*callback)( const char *s ) ); + +// RAVEN BEGIN +// mekberg: added + static void ArgCompletion_GuiName( const idCmdArgs &args, void(*callback)( const char *s ) ); +// RAVEN END + + static void ArgCompletion_MapName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ModelName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_SoundName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ImageName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_VideoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +#ifndef Q4_RECON_ENGINE_PRIVATE + static void ArgCompletion_ForceModel( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ForceModelStrogg( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_ForceModelMarine( const idCmdArgs &args, void(*callback)( const char *s ) ); +#endif +// RAVEN BEGIN +// nrausch: standalone video support + static void ArgCompletion_StandaloneVideoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +// rjohnson: netdemo completion + static void ArgCompletion_NetDemoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +// RAVEN END + static void ArgCompletion_ConfigName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_SaveGame( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void ArgCompletion_DemoName( const idCmdArgs &args, void(*callback)( const char *s ) ); +}; + +extern idCmdSystem * cmdSystem; + + +ID_INLINE void idCmdSystem::ArgCompletion_Boolean( const idCmdArgs &args, void(*callback)( const char *s ) ) { + callback( va( "%s 0", args.Argv( 0 ) ) ); + callback( va( "%s 1", args.Argv( 0 ) ) ); +} + +template ID_STATIC_TEMPLATE ID_INLINE void idCmdSystem::ArgCompletion_Integer( const idCmdArgs &args, void(*callback)( const char *s ) ) { + for ( int i = min; i <= max; i++ ) { + callback( va( "%s %d", args.Argv( 0 ), i ) ); + } +} + +template ID_STATIC_TEMPLATE ID_INLINE void idCmdSystem::ArgCompletion_String( const idCmdArgs &args, void(*callback)( const char *s ) ) { + for ( int i = 0; strings[i]; i++ ) { + callback( va( "%s %s", args.Argv( 0 ), strings[i] ) ); + } +} + +template ID_STATIC_TEMPLATE ID_INLINE void idCmdSystem::ArgCompletion_Decl( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_DeclName( args, callback, type ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_FileName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", true, "", NULL ); +} + +// RAVEN BEGIN +// mekberg: added +ID_INLINE void idCmdSystem::ArgCompletion_GuiName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "guis/", false, ".gui", NULL ); +} +// RAVEN END + +ID_INLINE void idCmdSystem::ArgCompletion_MapName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "maps/", true, ".map", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ModelName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_SoundName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "sound/", false, ".wav", ".ogg", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ImageName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", false, ".tga", ".dds", ".jpg", ".pcx", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_VideoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "video/", false, ".roq", NULL ); +} + +#ifndef Q4_RECON_ENGINE_PRIVATE +ID_INLINE void idCmdSystem::ArgCompletion_ForceModel( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_Models( args, callback, true, true ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ForceModelStrogg( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_Models( args, callback, true, false ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_ForceModelMarine( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_Models( args, callback, false, true ); +} +#endif + +// RAVEN BEGIN +// nrausch: standalone video support +ID_INLINE void idCmdSystem::ArgCompletion_StandaloneVideoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "video/", false, ".wmv", NULL ); +} + +// rjohnson: netdemo completion +extern char netDemoExtension[16]; +ID_INLINE void idCmdSystem::ArgCompletion_NetDemoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "demos/", true, netDemoExtension, NULL ); +} +// RAVEN END + +ID_INLINE void idCmdSystem::ArgCompletion_ConfigName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "/", true, ".cfg", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_SaveGame( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "SaveGames/", true, ".save", NULL ); +} + +ID_INLINE void idCmdSystem::ArgCompletion_DemoName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "demos/", true, ".demo", NULL ); +} + +#endif /* !__CMDSYSTEM_H__ */ diff --git a/src/framework/Common.h b/src/framework/Common.h new file mode 100644 index 0000000..82d8acf --- /dev/null +++ b/src/framework/Common.h @@ -0,0 +1,387 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +/* +============================================================== + + Common + +============================================================== +*/ + + +// RAVEN BEGIN +// mekberg: added more save types +typedef enum { + ST_REGULAR, + ST_QUICK, + ST_AUTO, + ST_CHECKPOINT, +} saveType_t; +// RAVEN END + +typedef enum { + EDITOR_NONE = 0, + EDITOR_RADIANT = BIT(1), + EDITOR_GUI = BIT(2), + EDITOR_DEBUGGER = BIT(3), + EDITOR_SCRIPT = BIT(4), + EDITOR_LIGHT = BIT(5), + EDITOR_SOUND = BIT(6), + EDITOR_DECL = BIT(7), + EDITOR_AF = BIT(8), + EDITOR_PDA = BIT(9), + EDITOR_FX = BIT(10), + EDITOR_REVERB = BIT(11), + EDITOR_PLAYBACKS = BIT(12), + EDITOR_MODVIEW = BIT(13), + EDITOR_LOGVIEW = BIT(14), + EDITOR_ENTVIEW = BIT(15), + EDITOR_MATERIAL = BIT(16), + + // Just flags to prevent caching of unneeded assets + EDITOR_AAS = BIT(17), + EDITOR_RENDERBUMP = BIT(18), + EDITOR_SPAWN_GUI = BIT(19), + + // Specifies that a decl validation run is happening + EDITOR_DECL_VALIDATING = BIT(20), + + EDITOR_ALL = -1 +}; +// RAVEN END + +#define MAX_OUTPUT_HISTORY 16 + +#define STRTABLE_ID "#str_" +#define STRTABLE_ID_LENGTH 5 + +extern idCVar com_version; +extern idCVar com_skipRenderer; +extern idCVar com_asyncSound; +extern idCVar com_machineSpec; +extern idCVar com_purgeAll; +extern idCVar com_developer; +extern idCVar com_allowConsole; +extern idCVar com_speeds; +extern idCVar com_showFPS; +extern idCVar com_showMemoryUsage; +extern idCVar com_showAsyncStats; +extern idCVar com_showSoundDecoders; +extern idCVar com_makingBuild; +extern idCVar com_skipUltraQuality; +extern idCVar com_updateLoadSize; +extern idCVar com_videoRam; + +#ifdef Q4_RECON_ENGINE_PRIVATE +// Transitional mirror used until the private rvTools interface is restored. +extern int com_editors; +extern bool com_editorActive; +extern idCVar com_asyncInput; +#endif + +// RAVEN BEGIN +// ksergent: added bundler +extern idCVar com_Bundler; + +#ifndef _XENON +// nrausch: generate rdf's for xenon load screens +extern idCVar com_MakeLoadScreens; +#endif + +// rjohnson: added quick load +extern idCVar com_QuickLoad; +// rjohnson: added limits stuff +extern idCVar com_Limits; +// jsinger: added build for binary lexer +extern idCVar com_BinaryWrite; +extern idCVar com_BinaryRead; +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +extern idCVar com_BinaryDeclRead; +#endif +// jsinger: added to support loading of all decls from a single file +#ifdef RV_SINGLE_DECL_FILE +extern idCVar com_SingleDeclFile; +extern idCVar com_WriteSingleDeclFIle; +#endif +// jshepard: warning suppresion +extern idCVar com_uniqueWarnings; +// amccarthy: show Mem_Alloc tag statistics +extern idCVar com_showMemAllocTags; +#if defined(_XENON) +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr bundles. +extern idCVar com_showXenTexCacheStats; +extern idCVar com_showXenHardwareTimers; +// ksergent: show thread usage. +extern idCVar com_showXenThreadUsage; +#endif // _XENON +extern idCVar sys_lang; +// RAVEN END + +extern int time_gameFrame; // game logic time +extern int time_gameDraw; // game present time +extern int time_frontend; // renderer frontend time +extern int time_backend; // renderer backend time +// RAVEN BEGIN +extern int time_waiting; // game logic time +// RAVEN END + +extern int com_frameTime; // time for the current frame in milliseconds +extern volatile int com_ticNumber; // 60 hz tics, incremented by async function + +// RAVEN BEGIN +// bdube: added timing dict +extern bool com_debugHudActive; // The debug hud is active in the game +// RAVEN END + +#ifdef _WINDOWS +const char DMAP_MSGID[] = "DMAPOutput"; +const char DMAP_DONE[] = "DMAPDone"; +#endif + +// RAVEN BEGIN +// bdube: forward declarations +class idInterpreter; +class idProgram; + +// converted to a class so the idStr gets constructed +class MemInfo { +public: + MemInfo( void ); + + idStr filebase; + + int total; + int assetTotals; + + // asset totals + int imageAssetsTotal; + int modelAssetsTotal; + int soundAssetsTotal; +// RAVEN BEGIN + int collAssetsTotal; + int animsAssetsTotal; + int aasAssetsTotal; + + int imageAssetsCount; + int modelAssetsCount; + int soundAssetsCount; + int collAssetsCount; + int animsAssetsCount; + int aasAssetsCount; +}; +// RAVEN END + +class idCommon { +public: + virtual ~idCommon( void ) {} + + // Initialize everything. + // if the OS allows, pass argc/argv directly (without executable name) + // otherwise pass the command line in a single string (without executable name) + virtual void Init( int argc, const char **argv, const char *cmdline ) = 0; + + // Shuts down everything. + virtual void Shutdown( void ) = 0; + + // Shuts down everything. + virtual void Quit( void ) = 0; + + // Returns true if common initialization is complete. + virtual bool IsInitialized( void ) const = 0; + + // Called repeatedly as the foreground thread for rendering and game logic. + virtual void Frame( void ) = 0; + + // Called repeatedly by blocking function calls with GUI interactivity. + virtual void GUIFrame( bool execCmd, bool network ) = 0; + + // Called 60 times a second from a background thread for sound mixing, + // and input generation. Not called until idCommon::Init() has completed. + virtual void Async( void ) = 0; + + // Checks for and removes command line "+set var arg" constructs. + // If match is NULL, all set commands will be executed, otherwise + // only a set with the exact name. Only used during startup. + // set once to clear the cvar from +set for early init code + virtual void StartupVariable( const char *match, bool once ) = 0; + +// RAVEN BEGIN + virtual int GetUserCmdHz( void ) const = 0; + + virtual int GetUserCmdMSec( void ) const = 0; + + // Returns com_frameTime - which is 0 if a command is added to the command line + virtual int GetFrameTime( void ) const = 0; + + // returns if the game is processing the last frame when it processes multiple frames + virtual bool IsRenderableGameFrame( void ) const = 0; + + virtual void SetRenderableGameFrame( bool in ) = 0; + + // returns the last message from common->Error + virtual const char *GetErrorMessage( void ) const = 0; + + // Initializes a tool with the given dictionary. + virtual void InitTool( const int tool, const idDict *dict ) = 0; + + // Returns true if an editor has focus + virtual bool IsToolActive( void ) const = 0; + + // Returns an interface to source control + virtual class rvISourceControl *GetSourceControl( void ) = 0; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added the following method to initialize each of the memory heaps +#ifdef _RV_MEM_SYS_SUPPORT + virtual void InitHeaps( void ) = 0; // initializes each of the memory heaps for use + virtual void ShutdownHeaps( void ) = 0; // shuts down each of the memory heaps from further use +#endif +// RAVEN END + + // Activates or deactivates a tool. + virtual void ActivateTool( bool active ) = 0; + + // Writes the user's configuration to a file + virtual void WriteConfigToFile( const char *filename ) = 0; + + // Writes cvars with the given flags to a file. + virtual void WriteFlaggedCVarsToFile( const char *filename, int flags, const char *setCmd ) = 0; + +// RAVEN BEGIN +// bdube: new exports + // Modview thinks in the middle of a game frame + virtual void ModViewThink ( void ) = 0; + +// rjohnson: added option for guis to always think + virtual void RunAlwaysThinkGUIs ( int time ) = 0; + + // Debbugger hook to check if a breakpoint has been hit + virtual void DebuggerCheckBreakpoint ( idInterpreter* interpreter, idProgram* program, int instructionPointer ) = 0; + +// scork: need to test if validating to catch some model errors that would stop the validation and convert to warnings... + virtual bool DoingDeclValidation( void ) = 0; +// scork: guess + virtual void SetCrashReportAutoSendString( const char *psString ) = 0; + + virtual void LoadToolsDLL( void ) = 0; + virtual void UnloadToolsDLL( void ) = 0; +// RAVEN END + + // Begins redirection of console output to the given buffer. + virtual void BeginRedirect( char *buffer, int buffersize, void (*flush)( const char * ), bool rcon = false ) = 0; + + // Stops redirection of console output. + virtual void EndRedirect( void ) = 0; + + // Update the screen with every message printed. + virtual void SetRefreshOnPrint( bool set ) = 0; + + // Prints message to the console, which may cause a screen update if com_refreshOnPrint is set. + virtual void Printf( const char *fmt, ... )id_attribute((format(printf,2,3))) = 0; + + // Same as Printf, with a more usable API - Printf pipes to this. + virtual void VPrintf( const char *fmt, va_list arg ) = 0; + + // Prints message that only shows up if the "developer" cvar is set, + // and NEVER forces a screen update, which could cause reentrancy problems. + virtual void DPrintf( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Prints WARNING %s message and adds the warning message to a queue for printing later on. + virtual void Warning( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Prints WARNING %s message in yellow that only shows up if the "developer" cvar is set. + virtual void DWarning( const char *fmt, ...) id_attribute((format(printf,2,3))) = 0; + + // Prints all queued warnings. + virtual void PrintWarnings( void ) = 0; + + // Removes all queued warnings. + virtual void ClearWarnings( const char *reason ) = 0; + + // Issues a C++ throw. Normal errors just abort to the game loop, + // which is appropriate for media or dynamic logic errors. + virtual void Error( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Fatal errors quit all the way to a system dialog box, which is appropriate for + // static internal errors or cases where the system may be corrupted. + virtual void FatalError( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + +// RAVEN BEGIN + // Brings up notepad with the warnings generated while running the game + virtual void DumpWarnings( void ) = 0; + + // Returns the localised string of the token, of the token if it does not begin with #str_ + virtual const char * GetLocalizedString( const char *token, int langIndex = -1 ) = 0; + + // Returns the localised string at position 'index' + virtual const idLangKeyValue * GetLocalizedString( int index, int langIndex = -1 ) = 0; + + // Returns the number of languages the game found + virtual int GetNumLanguages( void ) const = 0; + + // Returns the number of strings in the English langdict + virtual int GetNumLocalizedStrings( void ) const = 0; + + // Returns the name of the language + virtual const char * GetLanguage( int index ) const = 0; + + // Returns whether the language has VO + virtual bool LanguageHasVO( int index ) const = 0; + + // Returns key bound to the command + virtual const char * KeysFromBinding( const char *bind ) = 0; + + // Returns the binding bound to the key + virtual const char * BindingFromKey( const char *key ) = 0; + + // Directly sample a button. + virtual int ButtonState( int key ) = 0; + + // Directly sample a keystate. + virtual int KeyState( int key ) = 0; + +// mekberg: added + virtual int GetRModeForMachineSpec( int machineSpec ) const = 0; + virtual void SetDesiredMachineSpec( int machineSpec ) = 0; +// RAVEN END + + // returns true if we are currently executing an rcon operation + virtual bool IsRCon( void ) const = 0; + +#ifdef Q4_RECON_ENGINE_PRIVATE + // Engine-only compatibility tail for Doom 3 seed call sites. Retail + // Quake 4 callers use the indexed localization methods above. + virtual const idLangDict *GetLanguageDict( void ) = 0; +#endif +}; + +extern idCommon * common; + +#endif /* !__COMMON_H__ */ diff --git a/src/framework/Compressor.h b/src/framework/Compressor.h new file mode 100644 index 0000000..cadd937 --- /dev/null +++ b/src/framework/Compressor.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __COMPRESSOR_H__ +#define __COMPRESSOR_H__ + +/* +=============================================================================== + + idCompressor is a layer ontop of idFile which provides lossless data + compression. The compressor can be used as a regular file and multiple + compressors can be stacked ontop of each other. + +=============================================================================== +*/ + +class idCompressor : public idFile_Common { +public: + // compressor allocation + static idCompressor * AllocNoCompression( void ); + static idCompressor * AllocBitStream( void ); + static idCompressor * AllocRunLength( void ); + static idCompressor * AllocRunLength_ZeroBased( void ); + static idCompressor * AllocHuffman( void ); + static idCompressor * AllocArithmetic( void ); + static idCompressor * AllocLZSS( void ); + static idCompressor * AllocLZSS_WordAligned( void ); + static idCompressor * AllocLZW( void ); + + // initialization + virtual void Init( idFile *f, bool compress, int wordLength ) = 0; + virtual void FinishCompress( void ) = 0; + virtual float GetCompressionRatio( void ) const = 0; + + // common idFile interface + virtual const char * GetName( void ) = 0; + virtual const char * GetFullPath( void ) = 0; + virtual int Read( void *outData, int outLength ) = 0; + virtual int Write( const void *inData, int inLength ) = 0; + virtual int Length( void ) = 0; + virtual ID_TIME_T Timestamp( void ) = 0; + virtual int Tell( void ) = 0; + virtual void ForceFlush( void ) = 0; + virtual void Flush( void ) = 0; + virtual int Seek( long offset, fsOrigin_t origin ) = 0; +}; + +#endif /* !__COMPRESSOR_H__ */ diff --git a/src/framework/Console.h b/src/framework/Console.h new file mode 100644 index 0000000..370f67f --- /dev/null +++ b/src/framework/Console.h @@ -0,0 +1,68 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CONSOLE_H__ +#define __CONSOLE_H__ + +/* +=============================================================================== + + The console is strictly for development and advanced users. It should + never be used to convey actual game information to the user, which should + always be done through a GUI. + + The force options are for the editor console display window, which + doesn't respond to pull up / pull down + +=============================================================================== +*/ + +class idConsole { +public: + virtual ~idConsole( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // can't be combined with Init, because Init happens before renderer is started + virtual void LoadGraphics() = 0; + + virtual bool ProcessEvent( const struct sysEvent_s *event, bool forceAccept ) = 0; + + // the system code can release the mouse pointer when the console is active + virtual bool Active( void ) = 0; + + // clear the timers on any recent prints that are displayed in the notify lines + virtual void ClearNotifyLines( void ) = 0; + + // some console commands, like timeDemo, will force the console closed before they start + virtual void Close( void ) = 0; + + virtual void Draw( bool forceFullScreen ) = 0; + virtual void Print( const char *text ) = 0; +}; + +extern idConsole * console; // statically initialized to an idConsoleLocal + +#endif /* !__CONSOLE_H__ */ diff --git a/src/framework/DeclFX.h b/src/framework/DeclFX.h new file mode 100644 index 0000000..42c1ab9 --- /dev/null +++ b/src/framework/DeclFX.h @@ -0,0 +1,109 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DECLFX_H__ +#define __DECLFX_H__ + +/* +=============================================================================== + + idDeclFX + +=============================================================================== +*/ + +enum { + FX_LIGHT, + FX_PARTICLE, + FX_DECAL, + FX_MODEL, + FX_SOUND, + FX_SHAKE, + FX_ATTACHLIGHT, + FX_ATTACHENTITY, + FX_LAUNCH, + FX_SHOCKWAVE +}; + +// +// single fx structure +// +typedef struct { + int type; + int sibling; + + idStr data; + idStr name; + idStr fire; + + float delay; + float duration; + float restart; + float size; + float fadeInTime; + float fadeOutTime; + float shakeTime; + float shakeAmplitude; + float shakeDistance; + float shakeImpulse; + float lightRadius; + float rotate; + float random1; + float random2; + + idVec3 lightColor; + idVec3 offset; + idMat3 axis; + + bool soundStarted; + bool shakeStarted; + bool shakeFalloff; + bool shakeIgnoreMaster; + bool bindParticles; + bool explicitAxis; + bool noshadows; + bool particleTrackVelocity; + bool trackOrigin; +} idFXSingleAction; + +// +// grouped fx structures +// +class idDeclFX : public idDecl { +public: + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + idListevents; + idStr joint; + +private: + void ParseSingleFXAction( idLexer &src, idFXSingleAction& FXAction ); +}; + +#endif /* !__DECLFX_H__ */ diff --git a/src/framework/DeclPDA.h b/src/framework/DeclPDA.h new file mode 100644 index 0000000..1b7771e --- /dev/null +++ b/src/framework/DeclPDA.h @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLPDA_H__ +#define __DECLPDA_H__ + +/* +=============================================================================== + + idDeclPDA + +=============================================================================== +*/ + + +class idDeclEmail : public idDecl { +public: + idDeclEmail() {} + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + const char * GetFrom() const { return from; } + const char * GetBody() const { return text; } + const char * GetSubject() const { return subject; } + const char * GetDate() const { return date; } + const char * GetTo() const { return to; } + const char * GetImage() const { return image; } + +private: + idStr text; + idStr subject; + idStr date; + idStr to; + idStr from; + idStr image; +}; + + +class idDeclVideo : public idDecl { +public: + idDeclVideo() {}; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + const char * GetRoq() const { return video; } + const char * GetWave() const { return audio; } + const char * GetVideoName() const { return videoName; } + const char * GetInfo() const { return info; } + const char * GetPreview() const { return preview; } + +private: + idStr preview; + idStr video; + idStr videoName; + idStr info; + idStr audio; +}; + + +class idDeclAudio : public idDecl { +public: + idDeclAudio() {}; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + const char * GetAudioName() const { return audioName; } + const char * GetWave() const { return audio; } + const char * GetInfo() const { return info; } + const char * GetPreview() const { return preview; } + +private: + idStr audio; + idStr audioName; + idStr info; + idStr preview; +}; + + +class idDeclPDA : public idDecl { +public: + idDeclPDA() { originalEmails = originalVideos = 0; }; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + virtual void List( void ) const; + + virtual void AddVideo( const char *name, bool unique = true ) const; + virtual void AddAudio( const char *name, bool unique = true ) const; + virtual void AddEmail( const char *name, bool unique = true ) const; + virtual void RemoveAddedEmailsAndVideos() const; + + virtual const int GetNumVideos() const; + virtual const int GetNumAudios() const; + virtual const int GetNumEmails() const; + virtual const idDeclVideo *GetVideoByIndex( int index ) const; + virtual const idDeclAudio *GetAudioByIndex( int index ) const; + virtual const idDeclEmail *GetEmailByIndex( int index ) const; + + virtual void SetSecurity( const char *sec ) const; + + const char * GetPdaName() const { return pdaName; } + const char * GetSecurity() const {return security; } + const char * GetFullName() const { return fullName; } + const char * GetIcon() const { return icon; } + const char * GetPost() const { return post; } + const char * GetID() const { return id; } + const char * GetTitle() const { return title; } + +private: + mutable idStrList videos; + mutable idStrList audios; + mutable idStrList emails; + idStr pdaName; + idStr fullName; + idStr icon; + idStr id; + idStr post; + idStr title; + mutable idStr security; + mutable int originalEmails; + mutable int originalVideos; +}; + +#endif /* !__DECLPDA_H__ */ diff --git a/src/framework/DeclParticle.h b/src/framework/DeclParticle.h new file mode 100644 index 0000000..406c986 --- /dev/null +++ b/src/framework/DeclParticle.h @@ -0,0 +1,214 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DECLPARTICLE_H__ +#define __DECLPARTICLE_H__ + +/* +=============================================================================== + + idDeclParticle + +=============================================================================== +*/ + +class idParticleParm { +public: + idParticleParm( void ) { table = NULL; from = to = 0.0f; } + + const idDeclTable * table; + float from; + float to; + + float Eval( float frac, idRandom &rand ) const; + float Integrate( float frac, idRandom &rand ) const; +}; + + +typedef enum { + PDIST_RECT, // ( sizeX sizeY sizeZ ) + PDIST_CYLINDER, // ( sizeX sizeY sizeZ ) + PDIST_SPHERE // ( sizeX sizeY sizeZ ringFraction ) + // a ringFraction of zero allows the entire sphere, 0.9 would only + // allow the outer 10% of the sphere +} prtDistribution_t; + +typedef enum { + PDIR_CONE, // parm0 is the solid cone angle + PDIR_OUTWARD // direction is relative to offset from origin, parm0 is an upward bias +} prtDirection_t; + +typedef enum { + PPATH_STANDARD, + PPATH_HELIX, // ( sizeX sizeY sizeZ radialSpeed climbSpeed ) + PPATH_FLIES, + PPATH_ORBIT, + PPATH_DRIP +} prtCustomPth_t; + +typedef enum { + POR_VIEW, + POR_AIMED, // angle and aspect are disregarded + POR_X, + POR_Y, + POR_Z +} prtOrientation_t; + +typedef struct renderEntity_s renderEntity_t; +typedef struct renderView_s renderView_t; + +typedef struct { + const renderEntity_t * renderEnt; // for shaderParms, etc + const renderView_t * renderView; + int index; // particle number in the system + float frac; // 0.0 to 1.0 + idRandom random; + idVec3 origin; // dynamic smoke particles can have individual origins and axis + idMat3 axis; + + + float age; // in seconds, calculated as fraction * stage->particleLife + idRandom originalRandom; // needed so aimed particles can reset the random for another origin calculation + float animationFrameFrac; // set by ParticleTexCoords, used to make the cross faded version +} particleGen_t; + + +// +// single particle stage +// +class idParticleStage { +public: + idParticleStage( void ); + virtual ~idParticleStage( void ) {} + + void Default(); + virtual int NumQuadsPerParticle() const; // includes trails and cross faded animations + // returns the number of verts created, which will range from 0 to 4*NumQuadsPerParticle() + virtual int CreateParticle( particleGen_t *g, idDrawVert *verts ) const; + + void ParticleOrigin( particleGen_t *g, idVec3 &origin ) const; + int ParticleVerts( particleGen_t *g, const idVec3 origin, idDrawVert *verts ) const; + void ParticleTexCoords( particleGen_t *g, idDrawVert *verts ) const; + void ParticleColors( particleGen_t *g, idDrawVert *verts ) const; + + const char * GetCustomPathName(); + const char * GetCustomPathDesc(); + int NumCustomPathParms(); + void SetCustomPathType( const char *p ); + void operator=( const idParticleStage &src ); + + + //------------------------------ + + const idMaterial * material; + + int totalParticles; // total number of particles, although some may be invisible at a given time + float cycles; // allows things to oneShot ( 1 cycle ) or run for a set number of cycles + // on a per stage basis + + int cycleMsec; // ( particleLife + deadTime ) in msec + + float spawnBunching; // 0.0 = all come out at first instant, 1.0 = evenly spaced over cycle time + float particleLife; // total seconds of life for each particle + float timeOffset; // time offset from system start for the first particle to spawn + float deadTime; // time after particleLife before respawning + + //------------------------------- // standard path parms + + prtDistribution_t distributionType; + float distributionParms[4]; + + prtDirection_t directionType; + float directionParms[4]; + + idParticleParm speed; + float gravity; // can be negative to float up + bool worldGravity; // apply gravity in world space + bool randomDistribution; // randomly orient the quad on emission ( defaults to true ) + bool entityColor; // force color from render entity ( fadeColor is still valid ) + + //------------------------------ // custom path will completely replace the standard path calculations + + prtCustomPth_t customPathType; // use custom C code routines for determining the origin + float customPathParms[8]; + + //-------------------------------- + + idVec3 offset; // offset from origin to spawn all particles, also applies to customPath + + int animationFrames; // if > 1, subdivide the texture S axis into frames and crossfade + float animationRate; // frames per second + + float initialAngle; // in degrees, random angle is used if zero ( default ) + idParticleParm rotationSpeed; // half the particles will have negative rotation speeds + + prtOrientation_t orientation; // view, aimed, or axis fixed + float orientationParms[4]; + + idParticleParm size; + idParticleParm aspect; // greater than 1 makes the T axis longer + + idVec4 color; + idVec4 fadeColor; // either 0 0 0 0 for additive, or 1 1 1 0 for blended materials + float fadeInFraction; // in 0.0 to 1.0 range + float fadeOutFraction; // in 0.0 to 1.0 range + float fadeIndexFraction; // in 0.0 to 1.0 range, causes later index smokes to be more faded + + bool hidden; // for editor use + //----------------------------------- + + float boundsExpansion; // user tweak to fix poorly calculated bounds + + idBounds bounds; // derived +}; + + +// +// group of particle stages +// +class idDeclParticle : public idDecl { +public: + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength ); + virtual void FreeData( void ); + + bool Save( const char *fileName = NULL ); + + idListstages; + idBounds bounds; + float depthHack; + +private: + bool RebuildTextSource( void ); + void GetStageBounds( idParticleStage *stage ); + idParticleStage * ParseParticleStage( idLexer &src ); + void ParseParms( idLexer &src, float *parms, int maxParms ); + void ParseParametric( idLexer &src, idParticleParm *parm ); + void WriteStage( idFile *f, idParticleStage *stage ); + void WriteParticleParm( idFile *f, idParticleParm *parm, const char *name ); +}; + +#endif /* !__DECLPARTICLE_H__ */ diff --git a/src/framework/DeclPlayerModel.cpp b/src/framework/DeclPlayerModel.cpp new file mode 100644 index 0000000..7191c68 --- /dev/null +++ b/src/framework/DeclPlayerModel.cpp @@ -0,0 +1,163 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=========================================================================== + + Quake 4 player-model declaration implementation. + + This declaration is part of the SDK-facing framework contract and is + registered by game code as DECL_PLAYER_MODEL. It does not appear in the + retail executable PDB because its concrete owner lives on the game side of + that boundary; the implementation remains here beside its SDK header. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeclPlayerModel.h" + +rvDeclPlayerModel::rvDeclPlayerModel() { + headOffset.Zero(); +} + +const char *rvDeclPlayerModel::DefaultDefinition( void ) const { + return "{ model \"\" head \"\" headOffset ( 0 0 0 ) uiHead \"\" team \"\" skin \"\" description \"\" sounds { } }"; +} + +void rvDeclPlayerModel::FreeData( void ) { + model.Clear(); + head.Clear(); + headOffset.Zero(); + uiHead.Clear(); + team.Clear(); + skin.Clear(); + description.Clear(); + sounds.Clear(); +} + +static bool ParsePlayerModelString( idLexer &src, idStr &value ) { + idToken token; + if ( !src.ReadToken( &token ) ) { + return false; + } + value = token; + return true; +} + +static bool ParsePlayerModelOffset( idLexer &src, idVec3 &offset ) { + if ( src.CheckTokenString( "(" ) ) { + offset.x = src.ParseFloat(); + offset.y = src.ParseFloat(); + offset.z = src.ParseFloat(); + return src.ExpectTokenString( ")" ) != 0; + } + offset.x = src.ParseFloat(); + src.CheckTokenString( "," ); + offset.y = src.ParseFloat(); + src.CheckTokenString( "," ); + offset.z = src.ParseFloat(); + return true; +} + +bool rvDeclPlayerModel::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + const char *fileName = base ? GetFileName() : "*playerModel validation*"; + const int lineNumber = base ? GetLineNum() : 1; + src.LoadMemory( text, textLength, fileName, lineNumber ); + src.SetFlags( DECL_LEXER_FLAGS ); + if ( !src.SkipUntilString( "{" ) ) { + return false; + } + + while ( src.ReadToken( &token ) ) { + if ( token == "}" ) { + return !src.HadError(); + } + if ( !token.Icmp( "model" ) ) { + if ( !ParsePlayerModelString( src, model ) ) return false; + } else if ( !token.Icmp( "head" ) ) { + if ( !ParsePlayerModelString( src, head ) ) return false; + } else if ( !token.Icmp( "headOffset" ) ) { + if ( !ParsePlayerModelOffset( src, headOffset ) ) return false; + } else if ( !token.Icmp( "uiHead" ) ) { + if ( !ParsePlayerModelString( src, uiHead ) ) return false; + } else if ( !token.Icmp( "team" ) ) { + if ( !ParsePlayerModelString( src, team ) ) return false; + } else if ( !token.Icmp( "skin" ) ) { + if ( !ParsePlayerModelString( src, skin ) ) return false; + } else if ( !token.Icmp( "description" ) ) { + if ( !ParsePlayerModelString( src, description ) ) return false; + } else if ( !token.Icmp( "sound" ) ) { + idToken key; + idToken value; + if ( !src.ReadToken( &key ) || !src.ReadToken( &value ) ) return false; + sounds.Set( key, value ); + } else if ( !token.Icmp( "sounds" ) ) { + if ( !src.ExpectTokenString( "{" ) ) return false; + idToken key; + while ( src.ReadToken( &key ) && key != "}" ) { + idToken value; + if ( !src.ReadToken( &value ) ) return false; + sounds.Set( key, value ); + } + } else { + src.Warning( "unknown playerModel parameter '%s'", token.c_str() ); + idStr ignored; + src.ParseRestOfLine( ignored ); + } + } + return false; +} + +size_t rvDeclPlayerModel::Size( void ) const { + return sizeof( *this ) + model.Allocated() + head.Allocated() + uiHead.Allocated() + team.Allocated() + skin.Allocated() + description.Allocated() + sounds.Allocated(); +} + +void rvDeclPlayerModel::Print( void ) { + common->Printf( "model: %s\n", model.c_str() ); + common->Printf( "head: %s\n", head.c_str() ); + common->Printf( "headOffset: %s\n", headOffset.ToString() ); + common->Printf( "uiHead: %s\n", uiHead.c_str() ); + common->Printf( "team: %s\n", team.c_str() ); + common->Printf( "skin: %s\n", skin.c_str() ); + common->Printf( "description: %s\n", description.c_str() ); + sounds.Print(); +} + +bool rvDeclPlayerModel::Validate( const char *text, int textLength, idStr &report ) const { + rvDeclPlayerModel validation; + const bool valid = validation.Parse( text, textLength, false ); + if ( !valid ) { + report = "playerModel declaration did not parse"; + } + return valid; +} + +#if defined( _WIN32 ) +static_assert( sizeof( rvDeclPlayerModel ) == 256, "rvDeclPlayerModel ABI drift" ); +#endif diff --git a/src/framework/DeclPlayerModel.h b/src/framework/DeclPlayerModel.h new file mode 100644 index 0000000..13edab8 --- /dev/null +++ b/src/framework/DeclPlayerModel.h @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// DeclPlayerModel.h +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#ifndef __DECLPLAYERMODEL_H__ +#define __DECLPLAYERMODEL_H__ + +/* +=============================================================================== + +rvDeclPlayerModel + +=============================================================================== +*/ + +class rvDeclPlayerModel : public idDecl { +public: + rvDeclPlayerModel(); + + idStr model; + idStr head; + idVec3 headOffset; + idStr uiHead; + idStr team; + idStr skin; + idStr description; + idDict sounds; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ); + + virtual bool RebuildTextSource( void ) { return( false ); } + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +}; + +#endif diff --git a/src/framework/DemoChecksum.h b/src/framework/DemoChecksum.h new file mode 100644 index 0000000..1c93119 --- /dev/null +++ b/src/framework/DemoChecksum.h @@ -0,0 +1,35 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Pak file checksum for demo build. + +=============================================================================== +*/ + +// every time a new demo pk4 file is built, this checksum must be updated. +// the easiest way to get it is to just run the game and see what it spits out +#define DEMO_PAK_CHECKSUM 0xfe75bbef diff --git a/src/framework/DemoFile.h b/src/framework/DemoFile.h new file mode 100644 index 0000000..bc48f67 --- /dev/null +++ b/src/framework/DemoFile.h @@ -0,0 +1,87 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DEMOFILE_H__ +#define __DEMOFILE_H__ + +/* +=============================================================================== + + Demo file + +=============================================================================== +*/ + +typedef enum { + DS_FINISHED, + DS_RENDER, + DS_SOUND, + DS_VERSION +} demoSystem_t; + +// The retail PDB records idFile_Common as the direct base. This is also what +// supplies the endian-aware idFile helpers that a demo stream does not +// override itself. +class idDemoFile : public idFile_Common { +public: + idDemoFile(); + ~idDemoFile(); + + const char * GetName( void ) { return (f?f->GetName():""); } + const char * GetFullPath( void ) { return (f?f->GetFullPath():""); } + + void SetLog( bool b, const char *p ); + void Log( const char *p ); + bool OpenForReading( const char *fileName ); + bool OpenForWriting( const char *fileName ); + void Close(); + + const char * ReadHashString(); + void WriteHashString( const char *str ); + + void ReadDict( idDict &dict ); + void WriteDict( const idDict &dict ); + + int Read( void *buffer, int len ); + int Write( const void *buffer, int len ); + +private: + static idCompressor *AllocCompressor( int type ); + + bool writing; + byte * fileImage; + idFile * f; + idCompressor * compressor; + + idList demoStrings; + idFile * fLog; + bool log; + idStr logStr; + + static idCVar com_logDemos; + static idCVar com_compressDemos; + static idCVar com_preloadDemos; +}; + +#endif /* !__DEMOFILE_H__ */ diff --git a/src/framework/EditField.h b/src/framework/EditField.h new file mode 100644 index 0000000..f0c8975 --- /dev/null +++ b/src/framework/EditField.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __EDITFIELD_H__ +#define __EDITFIELD_H__ + +/* +=============================================================================== + + Edit field + +=============================================================================== +*/ + +const int MAX_EDIT_LINE = 256; + +typedef struct autoComplete_s { + bool valid; + int length; + char completionString[MAX_EDIT_LINE]; + char currentMatch[MAX_EDIT_LINE]; + int matchCount; + int matchIndex; + int findMatchIndex; +} autoComplete_t; + +class idEditField { +public: + idEditField(); + ~idEditField(); + + void Clear( void ); + void SetWidthInChars( int w ); + void SetCursor( int c ); + int GetCursor( void ) const; + void ClearAutoComplete( void ); + int GetAutoCompleteLength( void ) const; + void AutoComplete( void ); + void CharEvent( int c ); + void KeyDownEvent( int key ); + void Paste( void ); + char * GetBuffer( void ); + void Draw( int x, int y, int width, bool showCursor, const idMaterial *material ); + void SetBuffer( const char *buffer ); + +private: + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; + autoComplete_t autoComplete; +}; + +#endif /* !__EDITFIELD_H__ */ diff --git a/src/framework/EventLoop.h b/src/framework/EventLoop.h new file mode 100644 index 0000000..430ff6e --- /dev/null +++ b/src/framework/EventLoop.h @@ -0,0 +1,84 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __EVENTLOOP_H__ +#define __EVENTLOOP_H__ + +/* +=============================================================================== + + The event loop receives events from the system and dispatches them to + the various parts of the engine. The event loop also handles journaling. + The file system copies .cfg files to the journaled file. + +=============================================================================== +*/ + +const int MAX_PUSHED_EVENTS = 64; + +class idEventLoop { +public: + idEventLoop( void ); + ~idEventLoop( void ); + + void Init( void ); + + // Closes the journal file if needed. + void Shutdown( void ); + + // It is possible to get an event at the beginning of a frame that + // has a time stamp lower than the last event from the previous frame. + sysEvent_t GetEvent( void ); + + // Dispatches all pending events and returns the current time. + int RunEventLoop( bool commandExecution = true ); + + // Gets the current time in a way that will be journaled properly, + // as opposed to Sys_Milliseconds(), which always reads a real timer. + int Milliseconds( void ); + + // Returns the journal level, 1 = record, 2 = play back. + int JournalLevel( void ) const; + + // Journal file. + idFile * com_journalFile; + idFile * com_journalDataFile; + +private: + // all events will have this subtracted from their time + int initialTimeOffset; + + int com_pushedEventsHead, com_pushedEventsTail; + sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + + static idCVar com_journal; + + sysEvent_t GetRealEvent( void ); + void ProcessEvent( sysEvent_t ev ); + void PushEvent( sysEvent_t *event ); +}; + +extern idEventLoop *eventLoop; + +#endif /* !__EVENTLOOP_H__ */ diff --git a/src/framework/File.h b/src/framework/File.h new file mode 100644 index 0000000..92739b0 --- /dev/null +++ b/src/framework/File.h @@ -0,0 +1,390 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __FILE_H__ +#define __FILE_H__ + +/* +============================================================== + + File Streams. + +============================================================== +*/ + +// mode parm for Seek +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +class idFileSystemLocal; + +// RAVEN BEGIN +// jscott: made idFile a pure virtual interface +class idFile { +public: + virtual ~idFile( void ) {} + // Get the name of the file. + virtual const char * GetName( void ) = 0; + // Get the full file path. + virtual const char * GetFullPath( void ) = 0; + // Read data from the file to the buffer. + virtual int Read( void *buffer, int len ) = 0; + // Write data from the buffer to the file. + virtual int Write( const void *buffer, int len ) = 0; + // Returns the length of the file. + virtual int Length( void ) = 0; + // Return a time value for reload operations. + virtual unsigned int Timestamp( void ) = 0; + // Returns offset in file. + virtual int Tell( void ) = 0; + // Forces flush on files being writting to. + virtual void ForceFlush( void ) = 0; + // Causes any buffered data to be written to the file. + virtual void Flush( void ) = 0; + // Seek on a file. + virtual int Seek( long offset, fsOrigin_t origin ) = 0; + // Go back to the beginning of the file. + virtual void Rewind( void ) = 0; + // Like fprintf. + virtual int Printf( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + // Like fprintf but with argument pointer + virtual int VPrintf( const char *fmt, va_list arg ) = 0; + // Write a string with high precision floating point numbers to the file. + virtual int WriteFloatString( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + // Endian portable alternatives to Read(...) + virtual int ReadInt( int &value ) = 0; + virtual int ReadUnsignedInt( unsigned int &value ) = 0; + virtual int ReadShort( short &value ) = 0; + virtual int ReadUnsignedShort( unsigned short &value ) = 0; + virtual int ReadChar( char &value ) = 0; + virtual int ReadUnsignedChar( unsigned char &value ) = 0; + virtual int ReadFloat( float &value ) = 0; + virtual int ReadBool( bool &value ) = 0; + virtual int ReadString( idStr &string ) = 0; + virtual int ReadVec2( idVec2 &vec ) = 0; + virtual int ReadVec3( idVec3 &vec ) = 0; + virtual int ReadVec4( idVec4 &vec ) = 0; + virtual int ReadVec5( idVec5 &vec ) = 0; + virtual int ReadVec6( idVec6 &vec ) = 0; + virtual int ReadMat3( idMat3 &mat ) = 0; + virtual int ReadBounds( idBounds &bounds ) = 0; + + // Endian portable alternatives to Write(...) + virtual int WriteInt( const int value ) = 0; + virtual int WriteUnsignedInt( const unsigned int value ) = 0; + virtual int WriteShort( const short value ) = 0; + virtual int WriteUnsignedShort( unsigned short value ) = 0; + virtual int WriteChar( const char value ) = 0; + virtual int WriteUnsignedChar( const unsigned char value ) = 0; + virtual int WriteFloat( const float value ) = 0; + virtual int WriteBool( const bool value ) = 0; + virtual int WriteString( const char *string ) = 0; + virtual int WriteVec2( const idVec2 &vec ) = 0; + virtual int WriteVec3( const idVec3 &vec ) = 0; + virtual int WriteVec4( const idVec4 &vec ) = 0; + virtual int WriteVec5( const idVec5 &vec ) = 0; + virtual int WriteVec6( const idVec6 &vec ) = 0; + virtual int WriteMat3( const idMat3 &mat ) = 0; + virtual int WriteBounds( const idBounds &bounds ) = 0; + + // dluetscher: added method to write a structure array that is made up of numerics (floats, ints) from the given storage + virtual void WriteNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage, const char *prepend ) = 0; + + // jscott: for savegame and demo file syncing + virtual void WriteSyncId( void ) = 0; + virtual void ReadSyncId( const char *detail = "unspecified", const char *classname = NULL ) = 0; +}; + +class idFile_Common : public idFile +{ +public: + virtual ~idFile_Common( void ) {} + // Get the name of the file. + virtual const char * GetName( void ) { return( "" ); } + // Get the full file path. + virtual const char * GetFullPath( void ) { return( "" ); } + // Read data from the file to the buffer. + virtual int Read( void *buffer, int len ); + // Write data from the buffer to the file. + virtual int Write( const void *buffer, int len ); + // Returns the length of the file. + virtual int Length( void ) { return( 0 ); } + // Return a time value for reload operations. + virtual unsigned int Timestamp( void ) { return( 0 ); } + // Returns offset in file. + virtual int Tell( void ) { return( 0 ); } + // Forces flush on files being writting to. + virtual void ForceFlush( void ) {} + // Causes any buffered data to be written to the file. + virtual void Flush( void ) {} + // Seek on a file. + virtual int Seek( long offset, fsOrigin_t origin ) { return( -1 ); } + // Go back to the beginning of the file. + virtual void Rewind( void ); + // Like fprintf. + virtual int Printf( const char *fmt, ... ) id_attribute((format(printf,2,3))); + // Like fprintf but with argument pointer + virtual int VPrintf( const char *fmt, va_list arg ); + // Write a string with high precision floating point numbers to the file. + virtual int WriteFloatString( const char *fmt, ... ) id_attribute((format(printf,2,3))); + + // Endian portable alternatives to Read(...) + virtual int ReadInt( int &value ); + virtual int ReadUnsignedInt( unsigned int &value ); + virtual int ReadShort( short &value ); + virtual int ReadUnsignedShort( unsigned short &value ); + virtual int ReadChar( char &value ); + virtual int ReadUnsignedChar( unsigned char &value ); + virtual int ReadFloat( float &value ); + virtual int ReadBool( bool &value ); + virtual int ReadString( idStr &string ); + virtual int ReadVec2( idVec2 &vec ); + virtual int ReadVec3( idVec3 &vec ); + virtual int ReadVec4( idVec4 &vec ); + virtual int ReadVec5( idVec5 &vec ); + virtual int ReadVec6( idVec6 &vec ); + virtual int ReadMat3( idMat3 &mat ); + virtual int ReadBounds( idBounds &bounds ); + + // Endian portable alternatives to Write(...) + virtual int WriteInt( const int value ); + virtual int WriteUnsignedInt( const unsigned int value ); + virtual int WriteShort( const short value ); + virtual int WriteUnsignedShort( unsigned short value ); + virtual int WriteChar( const char value ); + virtual int WriteUnsignedChar( const unsigned char value ); + virtual int WriteFloat( const float value ); + virtual int WriteBool( const bool value ); + virtual int WriteString( const char *string ); + virtual int WriteVec2( const idVec2 &vec ); + virtual int WriteVec3( const idVec3 &vec ); + virtual int WriteVec4( const idVec4 &vec ); + virtual int WriteVec5( const idVec5 &vec ); + virtual int WriteVec6( const idVec6 &vec ); + virtual int WriteMat3( const idMat3 &mat ); + virtual int WriteBounds( const idBounds &bounds ); + + // dluetscher: added method to write a structure array that is made up of numerics (floats, ints) from the given storage + virtual void WriteNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage, const char *prepend ); + + // jscott: for savegame and demo file syncing + virtual void WriteSyncId( void ); + virtual void ReadSyncId( const char *detail = "unspecific", const char *classname = NULL ); +}; + +class idFile_Memory : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_Memory( void ); // file for writing without name + idFile_Memory( const char *name ); // file for writing + idFile_Memory( const char *name, char *data, int length ); // file for writing + idFile_Memory( const char *name, const char *data, int length ); // file for reading + virtual ~idFile_Memory( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return name.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + virtual void Rewind( void ); + + // changes memory file to read only + virtual void MakeReadOnly( void ); + // clear the file + virtual void Clear( bool freeMemory = true ); + // set data for reading + void SetData( const char *data, int length ); + // returns const pointer to the memory buffer + const char * GetDataPtr( void ) const { return filePtr; } + // set the file granularity + void SetGranularity( int g ) { assert( g > 0 ); granularity = g; } + +private: + idStr name; // name of the file + int mode; // open mode + int maxSize; // maximum size of file + int fileSize; // size of the file + int allocated; // allocated size + int granularity; // file granularity + char * filePtr; // buffer holding the file data + char * curPtr; // current read/write pointer +}; + + +// RAVEN BEGIN +class idFile_BitMsg : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_BitMsg( idBitMsg &msg ); + idFile_BitMsg( const idBitMsg &msg ); + virtual ~idFile_BitMsg( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return name.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + +private: + idStr name; // name of the file + int mode; // open mode + idBitMsg * msg; +}; + + +// RAVEN BEGIN +class idFile_Permanent : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_Permanent( void ); + virtual ~idFile_Permanent( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return fullPath.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + + // returns file pointer + FILE * GetFilePtr( void ) { return o; } + +private: + idStr name; // relative path of the file - relative path + idStr fullPath; // full file path - OS path + int mode; // open mode + int fileSize; // size of the file + FILE * o; // file handle + bool handleSync; // true if written data is immediately flushed +}; + +class idFile_ASCII : public idFile_Permanent +{ + int inside; + +public: + idFile_ASCII( void ); + + virtual int Read( void *buffer, int len ); + // Write data from the buffer to the file. + virtual int Write( const void *buffer, int len ); + + // Endian portable alternatives to Read(...) + virtual int ReadInt( int &value ); + virtual int ReadUnsignedInt( unsigned int &value ); + virtual int ReadShort( short &value ); + virtual int ReadUnsignedShort( unsigned short &value ); + virtual int ReadChar( char &value ); + virtual int ReadUnsignedChar( unsigned char &value ); + virtual int ReadFloat( float &value ); + virtual int ReadBool( bool &value ); + virtual int ReadString( idStr &string ); + virtual int ReadVec2( idVec2 &vec ); + virtual int ReadVec3( idVec3 &vec ); + virtual int ReadVec4( idVec4 &vec ); + virtual int ReadVec5( idVec5 &vec ); + virtual int ReadVec6( idVec6 &vec ); + virtual int ReadMat3( idMat3 &mat ); + + // Endian portable alternatives to Write(...) + virtual int WriteInt( const int value ); + virtual int WriteUnsignedInt( const unsigned int value ); + virtual int WriteShort( const short value ); + virtual int WriteUnsignedShort( unsigned short value ); + virtual int WriteChar( const char value ); + virtual int WriteUnsignedChar( const unsigned char value ); + virtual int WriteFloat( const float value ); + virtual int WriteBool( const bool value ); + virtual int WriteString( const char *string ); + virtual int WriteVec2( const idVec2 &vec ); + virtual int WriteVec3( const idVec3 &vec ); + virtual int WriteVec4( const idVec4 &vec ); + virtual int WriteVec5( const idVec5 &vec ); + virtual int WriteVec6( const idVec6 &vec ); + virtual int WriteMat3( const idMat3 &mat ); + + // dluetscher: added method to write a structure array that is made up of numerics (floats, ints) from the given storage + virtual void WriteNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage, const char *prepend ); + + // jscott: for savegame and demo file syncing + virtual void WriteSyncId( void ); + virtual void ReadSyncId( const char *detail = "unspecific", const char *classname = NULL ); +}; + +// RAVEN BEGIN +class idFile_InZip : public idFile_Common { +// RAVEN END + friend class idFileSystemLocal; + +public: + idFile_InZip( void ); + virtual ~idFile_InZip( void ); + + virtual const char * GetName( void ) { return name.c_str(); } + virtual const char * GetFullPath( void ) { return fullPath.c_str(); } + virtual int Read( void *buffer, int len ); + virtual int Write( const void *buffer, int len ); + virtual int Length( void ); + virtual unsigned int Timestamp( void ); + virtual int Tell( void ); + virtual void ForceFlush( void ); + virtual void Flush( void ); + virtual int Seek( long offset, fsOrigin_t origin ); + +private: + idStr name; // name of the file in the pak + idStr fullPath; // full file path including pak file name + int zipFilePos; // zip file info position in pak + int fileSize; // size of the file + void * z; // unzip info +}; + +#endif /* !__FILE_H__ */ diff --git a/src/framework/FileSystem.h b/src/framework/FileSystem.h new file mode 100644 index 0000000..6f8eedc --- /dev/null +++ b/src/framework/FileSystem.h @@ -0,0 +1,464 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __FILESYSTEM_H__ +#define __FILESYSTEM_H__ + +/* +=============================================================================== + + File System + + No stdio calls should be used by any part of the game, because of all sorts + of directory and separator char issues. Throughout the game a forward slash + should be used as a separator. The file system takes care of the conversion + to an OS specific separator. The file system treats all file and directory + names as case insensitive. + + The following cvars store paths used by the file system: + + "fs_basepath" path to local install, read-only + "fs_savepath" path to config, save game, etc. files, read & write + "fs_cdpath" path to cd, read-only + "fs_devpath" path to files created during development, read & write + + The base path for file saving can be set to "fs_savepath" or "fs_devpath". + +=============================================================================== +*/ + +static const unsigned FILE_NOT_FOUND_TIMESTAMP = 0xFFFFFFFF; +static const int MAX_PURE_PAKS = 128; +// master server can keep server updated with a list of allowed paks per OS +static const int MAX_GAMEPAK_PER_OS = 10; +static const int MAX_OSPATH = 256; + +// modes for OpenFileByMode. used as bit mask internally +typedef enum { + FS_READ = 0, + FS_WRITE = 1, + FS_APPEND = 2 +} fsMode_t; + +typedef enum { + PURE_OK, // we are good to connect as-is + PURE_RESTART, // restart required + PURE_MISSING, // pak files missing on the client + PURE_NODLL, // no DLL could be extracted + // PURE_DIFFERENT, // differing checksums +} fsPureReply_t; + +typedef enum { + DLTYPE_URL, + DLTYPE_FILE +} dlType_t; + +typedef enum { + DL_WAIT, // waiting in the list for beginning of the download + DL_INPROGRESS, // in progress + DL_DONE, // download completed, success + DL_ABORTING, // this one can be set during a download, it will force the next progress callback to abort - then will go to DL_FAILED + DL_FAILED +} dlStatus_t; + +typedef enum { + FILE_EXEC, + FILE_OPEN +} dlMime_t; + +typedef enum { + FIND_NO, + FIND_YES, + FIND_ADDON +} findFile_t; + +typedef struct urlDownload_s { + idStr url; +#ifndef Q4_RECON_ENGINE_PRIVATE + idStr urlAuth; + idStr referer; +#endif + char dlerror[ MAX_STRING_CHARS ]; + int dltotal; + int dlnow; + int dlstatus; + dlStatus_t status; +} urlDownload_t; + +typedef struct fileDownload_s { + int position; + int length; + void * buffer; +} fileDownload_t; + +typedef struct proxyDownload_s { + idStr proxyUrl; + idStr proxyAuth; +} proxyDownload_t; + +// RAVEN BEGIN +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr +// bundles. +#if defined(_XENON) +#include "Xtl.h" +#endif +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr +// bundles. +#if defined(_XENON) + +// Define this to enable background streaming via Xenon's native file I/O +// routines (unbuffered reads). +#define _XENON_USE_NATIVE_FILEIO + +typedef struct backgroundDownload_s +{ + static const int MAX_SEGMENTS = 2; + + struct backgroundDownload_s *next; // Set by the fileSystem. + dlType_t opcode; + idFile * f; + int numSegments; + fileDownload_t file[MAX_SEGMENTS]; + urlDownload_t url; + volatile bool completed; + double ticksToLoad; // Used for performance profiling. + +public: + void Clear(void) + { + numSegments=0; + completed=false; + f=0; + } + + void AddSegment(int position, int length, void* buffer) + { + assert(position>0); + assert(length>0); + assert(buffer!=0); + assert(numSegments game pak checksum + virtual bool UpdateGamePakChecksums( void ) = 0; + // 0-terminated list of pak checksums + // if pureChecksums[ 0 ] == 0, all data sources will be allowed + // otherwise, only pak files that match one of the checksums will be checked for files + // with the sole exception of .cfg files. + // the function tries to configure pure mode from the paks already referenced and this new list + // it returns wether the switch was successfull, and sets the missing checksums + // use fs_debug 1 to verbose +#ifdef Q4_RECON_ENGINE_PRIVATE + // The matching retail PDB predates the per-OS 1.4 SDK extension. + virtual fsPureReply_t SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum ) = 0; +#else + virtual fsPureReply_t SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum[ MAX_GAMEPAK_PER_OS ], int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum, int *restartGamePakChecksu ) = 0; +#endif + // fills a 0-terminated list of pak checksums for a client + // if OS is -1, give the current game pak checksum. if >= 0, lookup the game pak table (server only) + // indexes > 0 may be provided on servers when the master is giving additional game paks to let in + virtual void GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int gamePakChecksum[ MAX_GAMEPAK_PER_OS ] ) = 0; + // before doing a restart, force the pure list and the search order + // if the given checksum list can't be completely processed and set, will error out + virtual void SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum ) = 0; + // equivalent to calling SetPureServerChecksums with an empty list + virtual void ClearPureChecksums( void ) = 0; + // get a mask of supported OSes. if not pure, returns -1 + virtual unsigned int GetOSMask( void ) = 0; + // Reads a complete file. + // Returns the length of the file, or -1 on failure. + // A null buffer will just return the file length without loading. + // A null timestamp will be ignored. + // As a quick check for existance. -1 length == not present. + // A 0 byte will always be appended at the end, so string ops are safe. + // The buffer should be considered read-only, because it may be cached for other uses. + virtual int ReadFile( const char *relativePath, void **buffer, unsigned *timestamp = NULL ) = 0; + // Frees the memory allocated by ReadFile. + virtual void FreeFile( void *buffer ) = 0; + // Writes a complete file, will create any needed subdirectories. + // Returns the length of the file, or -1 on failure. + virtual int WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" ) = 0; + // Removes the given file. +// RAVEN BEGIN +// rjohnson: can specify the path cvar + virtual void RemoveFile( const char *relativePath, const char *basePath = "fs_savepath" ) = 0; +// bdube: added method + // Removes the given file and returns the filesystem status of the removal + virtual int RemoveExplicitFile ( const char *OSPath ) = 0; + +// mekberg: is file loading allowed? + virtual void SetIsFileLoadingAllowed(bool mode) = 0; + +// dluetscher: returns file loading status + virtual bool GetIsFileLoadingAllowed() const = 0; + +// amccarthy: set the current asset log name. + virtual void SetAssetLogName(const char *logName) = 0; +// amccarthy: write out a list of all files loaded. + virtual void WriteAssetLog() = 0; +// jnewquist: clear list of all files loaded. + virtual void ClearAssetLog() = 0; +// jnewquist: Accessor for asset log name (with filter) + virtual const char* GetAssetLogName() = 0; + +// jscott: new functions for tools + virtual idFile * GetNewFileMemory( void ) = 0; + virtual idFile * GetNewFilePermanent( void ) = 0; + +// RAVEN END + // Opens a file for reading. + virtual idFile * OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL ) = 0; + // Opens a file for writing, will create any needed subdirectories. + virtual idFile * OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath", bool ASCII = false ) = 0; + // Opens a file for writing at the end. + virtual idFile * OpenFileAppend( const char *filename, bool sync = false, const char *basePath = "fs_basepath" ) = 0; + // Opens a file for reading, writing, or appending depending on the value of mode. + virtual idFile * OpenFileByMode( const char *relativePath, fsMode_t mode ) = 0; + // Opens a file for reading from a full OS path. + virtual idFile * OpenExplicitFileRead( const char *OSPath ) = 0; + // Opens a file for writing to a full OS path. + virtual idFile * OpenExplicitFileWrite( const char *OSPath ) = 0; + // Closes a file. + virtual void CloseFile( idFile *f ) = 0; + // Returns immediately, performing the read from a background thread. + virtual void BackgroundDownload( backgroundDownload_t *bgl ) = 0; + // resets the bytes read counter + virtual void ResetReadCount( void ) = 0; + // retrieves the current read count + virtual int GetReadCount( void ) = 0; + // adds to the read count + virtual void AddToReadCount( int c ) = 0; + // look for a dynamic module + virtual void FindDLL( const char *basename, char dllPath[ MAX_OSPATH ], bool updateChecksum ) = 0; + // case sensitive filesystems use an internal directory cache + // the cache is cleared when calling OpenFileWrite and RemoveFile + // in some cases you may need to use this directly + virtual void ClearDirCache( void ) = 0; + + // lookup a relative path, return the size or 0 if not found +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual int RelativeDownloadPathForChecksum( int checksum, char path[ MAX_STRING_CHARS ] ) = 0; +#endif + + // verify the file can be downloaded, lookup a relative path, return the size or 0 if not found + virtual int ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isGamePak ) = 0; + + // verify the file can be downloaded, lookup an absolute (OS) path, return the size or 0 if not found +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual int ValidateDownloadPakForRelativePath( const char *relativePath, char path[ MAX_STRING_CHARS ], bool &isGamePakReturn ) = 0; +#endif + + virtual idFile * MakeTemporaryFile( void ) = 0; + + // make downloaded pak files known so pure negociation works next time + virtual int AddZipFile( const char *path ) = 0; + +// RAVEN BEGIN +// nrausch: explicit pak add/removal +#if defined( _XENON ) || defined( Q4_RECON_ENGINE_PRIVATE ) + #ifndef Q4_RECON_ENGINE_PRIVATE + virtual bool AddDownloadedPak( const char *path ) = 0; + virtual void RemoveDownloadedPak( const char *path ) = 0; + #endif + + virtual bool AddExplicitPak( const char *path ) = 0; + virtual void RemoveExplicitPak( const char *path ) = 0; + virtual bool IsPakLoaded( const char *path ) = 0; +#endif +// RAVEN END + + // look for a file in the loaded paks or the addon paks + // if the file is found in addons, FS's internal structures are ready for a reloadEngine + virtual findFile_t FindFile( const char *path ) = 0; + + // get map/addon decls and take into account addon paks that are not on the search list + // the decl 'name' is in the "path" entry of the dict + virtual int GetNumMaps() = 0; +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual int GetMapDeclIndex( const char *mapName ) = 0; +#endif + virtual const idDict * GetMapDecl( int i ) = 0; +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual const idDict * GetMapDecl( const char *mapName ) = 0; +#endif + virtual void FindMapScreenshot( const char *path, char *buf, int len ) = 0; + +// RAVEN BEGIN +// rjohnson: added new functions + // Converts a full OS path to an import path. + virtual bool OSpathToImportPath( const char *osPath, idStr &iPath, bool stripTemp = false ) = 0; + // Opens a file for reading from the fs_importpath directory + virtual idFile * OpenImportFileRead( const char *filename ) = 0; + // Copy a file + virtual void CopyOSFile( const char *fromOSPath, const char *toOSPath ) = 0; + virtual void CopyOSFile( idFile *src, const char *toOSPath ) = 0; +// RAVEN END + + // demo functions - only for use by the core ( and not DLL boundary / memory safe anyway ) + // ReadDemoHeader: returns -1 if a reloadEngine is requested before trying again, 0 if we failed with no backup, and 1 if we can continue + // if demo_enforceFS is 0, will always ret 1 + virtual void WriteDemoHeader( idFile *file ) = 0; + virtual int ReadDemoHeader( idFile *file ) = 0; + +#ifndef Q4_RECON_ENGINE_PRIVATE + // These interfaces were added after the executable represented by quake4.pdb. + // new in 1.3 + // indicates if the filesystem is currently running with pak files restrictions or addons + virtual bool IsRunningWithRestrictions( void ) = 0; + + // new in 1.4 + virtual void ReadCodePakLists( const idBitMsg &msg ) = 0; + virtual bool HaveCodePakLists( void ) const = 0; + + // pick best language - used by the core to pick a good default language based on present zpaks + virtual void SelectDefaultLanguage( void ) = 0; + + virtual void ClearAddonList( void ) = 0; +#endif +}; + +extern idFileSystem * fileSystem; + +#endif /* !__FILESYSTEM_H__ */ diff --git a/src/framework/KeyInput.h b/src/framework/KeyInput.h new file mode 100644 index 0000000..0133a75 --- /dev/null +++ b/src/framework/KeyInput.h @@ -0,0 +1,217 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __KEYINPUT_H__ +#define __KEYINPUT_H__ + +/* +=============================================================================== + + Key Input + +=============================================================================== +*/ + +// these are the key numbers that are used by the key system +// normal keys should be passed as lowercased ascii +// Some high ascii (> 127) characters that are mapped directly to keys on +// western european keyboards are inserted in this table so that those keys +// are bindable (otherwise they get bound as one of the special keys in this +// table) +typedef enum { + K_TAB = 9, + K_ENTER = 13, + K_ESCAPE = 27, + K_SPACE = 32, + + K_BACKSPACE = 127, + + K_COMMAND = 128, + K_CAPSLOCK, + K_SCROLL, + K_POWER, + K_PAUSE, + + K_UPARROW = 133, + K_DOWNARROW, + K_LEFTARROW, + K_RIGHTARROW, + + // The 3 windows keys + K_LWIN = 137, + K_RWIN, + K_MENU, + + K_ALT = 140, + K_CTRL, + K_SHIFT, + K_INS, + K_DEL, + K_PGDN, + K_PGUP, + K_HOME, + K_END, + + K_F1 = 149, + K_F2, + K_F3, + K_F4, + K_F5, + K_F6, + K_F7, + K_F8, + K_F9, + K_F10, + K_F11, + K_F12, + K_INVERTED_EXCLAMATION = 161, // upside down ! + K_F13, + K_F14, + K_F15, + + K_KP_HOME = 165, + K_KP_UPARROW, + K_KP_PGUP, + K_KP_LEFTARROW, + K_KP_5, + K_KP_RIGHTARROW, + K_KP_END, + K_KP_DOWNARROW, + K_KP_PGDN, + K_KP_ENTER, + K_KP_INS, + K_KP_DEL, + K_KP_SLASH, + K_SUPERSCRIPT_TWO = 178, // superscript 2 + K_KP_MINUS, + K_ACUTE_ACCENT = 180, // accute accent + K_KP_PLUS, + K_KP_NUMLOCK, + K_KP_STAR, + K_KP_EQUALS, + + K_MASCULINE_ORDINATOR = 186, + // K_MOUSE enums must be contiguous (no char codes in the middle) + K_MOUSE1 = 187, + K_MOUSE2, + K_MOUSE3, + K_MOUSE4, + K_MOUSE5, + K_MOUSE6, + K_MOUSE7, + K_MOUSE8, + + K_MWHEELDOWN = 195, + K_MWHEELUP, + + K_JOY1 = 197, + K_JOY2, + K_JOY3, + K_JOY4, + K_JOY5, + K_JOY6, + K_JOY7, + K_JOY8, + K_JOY9, + K_JOY10, + K_JOY11, + K_JOY12, + K_JOY13, + K_JOY14, + K_JOY15, + K_JOY16, + K_JOY17, + K_JOY18, + K_JOY19, + K_JOY20, + K_JOY21, + K_JOY22, + K_JOY23, + K_JOY24, + K_JOY25, + K_JOY26, + K_JOY27, + K_GRAVE_A = 224, // lowercase a with grave accent + K_JOY28, + K_JOY29, + K_JOY30, + K_JOY31, + K_JOY32, + + K_AUX1 = 230, + K_CEDILLA_C = 231, // lowercase c with Cedilla + K_GRAVE_E = 232, // lowercase e with grave accent + K_AUX2, + K_AUX3, + K_AUX4, + K_GRAVE_I = 236, // lowercase i with grave accent + K_AUX5, + K_AUX6, + K_AUX7, + K_AUX8, + K_TILDE_N = 241, // lowercase n with tilde + K_GRAVE_O = 242, // lowercase o with grave accent + K_AUX9, + K_AUX10, + K_AUX11, + K_AUX12, + K_AUX13, + K_AUX14, + K_GRAVE_U = 249, // lowercase u with grave accent + K_AUX15, + K_AUX16, + + K_PRINT_SCR = 252, // SysRq / PrintScr + K_RIGHT_ALT = 253, // used by some languages as "Alt-Gr" + K_LAST_KEY = 254 // this better be < 256! +} keyNum_t; + + +class idKeyInput { +public: + static void Init( void ); + static void Shutdown( void ); + + static void ArgCompletion_KeyName( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void PreliminaryKeyEvent( int keyNum, bool down ); + static bool IsDown( int keyNum ); + static int GetUsercmdAction( int keyNum ); + static bool GetOverstrikeMode( void ); + static void SetOverstrikeMode( bool state ); + static void ClearStates( void ); + static int StringToKeyNum( const char *str ); + static const char * KeyNumToString( int keyNum, bool localized ); + + static void SetBinding( int keyNum, const char *binding ); + static const char * GetBinding( int keyNum ); + static bool UnbindBinding( const char *bind ); + static int NumBinds( const char *binding ); + static bool ExecKeyBinding( int keyNum ); + static const char * KeysFromBinding( const char *bind ); + static const char * BindingFromKey( const char *key ); + static bool KeyIsBoundTo( int keyNum, const char *binding ); + static void WriteBindings( idFile *f ); +}; + +#endif /* !__KEYINPUT_H__ */ diff --git a/src/framework/Session.h b/src/framework/Session.h new file mode 100644 index 0000000..d839bd7 --- /dev/null +++ b/src/framework/Session.h @@ -0,0 +1,117 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SESSION_H__ +#define __SESSION_H__ + +/* +=============================================================================== + + The session is the glue that holds games together between levels. + +=============================================================================== +*/ + +// needed by the gui system for the load game menu +typedef struct { + short health; + short heartRate; + short stamina; + short combat; +} logStats_t; + +static const int MAX_LOGGED_STATS = 60 * 120; // log every half second + +typedef enum { + MSG_OK, + MSG_ABORT, + MSG_OKCANCEL, + MSG_YESNO, + MSG_PROMPT, + MSG_CDKEY, + MSG_INFO, + MSG_WAIT +} msgBoxType_t; + +typedef const char * (*HandleGuiCommand_t)( const char * ); + +class idSession { +public: + virtual ~idSession() {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual void Stop( void ) = 0; + virtual void UpdateScreen( bool outOfSequence = true ) = 0; + virtual void PacifierUpdate( void ) = 0; + virtual void QuickMapChange( const char *mapName ) = 0; + virtual bool GetMapSpawned( void ) = 0; + virtual bool GetLoadingSaveGame( void ) = 0; + virtual idRenderWorld *GetGameRenderWorld( void ) = 0; + virtual void Frame( void ) = 0; + virtual bool IsMultiplayer( void ) = 0; + virtual bool ProcessEvent( const sysEvent_t *event ) = 0; + virtual void StartMenu( const char *menuName = NULL ) = 0; + virtual void SetGUI( idUserInterface *gui, HandleGuiCommand_t handle ) = 0; + virtual idUserInterface *GetActiveGUI( void ) = 0; + virtual int GetDesiredSoundWorld( bool menuSoundWorld, bool &newSoundWorld ) = 0; + virtual const char *KeysFromBinding( const char *bind ) = 0; + virtual void GuiFrameEvents( void ) = 0; + virtual const char *MessageBox( msgBoxType_t type, const char *message, const char *title = NULL, bool wait = false, const char *fire_yes = NULL, const char *fire_no = NULL, bool network = false ) = 0; + virtual void StopBox( void ) = 0; + virtual void DownloadProgressBox( backgroundDownload_t *bgl, const char *title, int progress_start = 0, int progress_end = 100 ) = 0; + virtual void TimeHitch( int msec ) = 0; + virtual void ReadCDKey( void ) = 0; + virtual void WriteCDKey( void ) = 0; + virtual const char *GetCDKey( void ) = 0; + virtual bool CheckKey( const char *key, bool netConnect, bool offlineValid ) = 0; + virtual bool CDKeysAreValid( bool strict ) = 0; + virtual void ClearCDKey( bool valid ) = 0; + virtual void SetCDKeyGuiVars( void ) = 0; + virtual bool WaitingForGameAuth( void ) = 0; + virtual void CDKeysAuthReply( bool valid, const char *authMessage ) = 0; + virtual void EmitClientAuth( void ) = 0; + virtual void UpdateFriends( void ) = 0; + virtual void KillLoadingGUI( void ) = 0; + virtual void GetCurrentMapName( idStr &mapName ) = 0; + virtual int GetSaveGameVersion( void ) = 0; + virtual void PrecacheMPSettingsMenu( void ) = 0; + virtual void SetLoadingText( const char *text ) = 0; + virtual void AddLoadingIcon( const char *icon ) = 0; + virtual void SetMainMenuGuiVars( void ) = 0; + + // PDB layout: 0x14 bytes on Win32, with the vtable pointer at offset 0. + idRenderWorld * rw; + idDemoFile * readDemo; + idDemoFile * writeDemo; + int renderdemoVersion; +}; + +#if defined( _WIN32 ) +static_assert( sizeof( idSession ) == 0x14, "idSession ABI drift" ); +#endif + +extern idSession * session; + +#endif /* !__SESSION_H__ */ diff --git a/src/framework/Session_local.h b/src/framework/Session_local.h new file mode 100644 index 0000000..122aec1 --- /dev/null +++ b/src/framework/Session_local.h @@ -0,0 +1,382 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SESSIONLOCAL_H__ +#define __SESSIONLOCAL_H__ + +/* + +IsConnectedToServer(); +IsGameLoaded(); +IsGuiActive(); +IsPlayingRenderDemo(); + +if connected to a server + if handshaking + if map loading + if in game +else if a game loaded + if in load game menu + if main menu up +else if playing render demo +else + if error dialog + full console + +*/ + +typedef struct { + usercmd_t cmd; + int consistencyHash; +} logCmd_t; + +struct fileTIME_T { + int index; + ID_TIME_T timeStamp; + + operator int() const { return timeStamp; } +}; + +typedef struct { + idDict serverInfo; + idDict syncedCVars; + idDict userInfo[MAX_ASYNC_CLIENTS]; + idDict persistentPlayerInfo[MAX_ASYNC_CLIENTS]; + usercmd_t mapSpawnUsercmd[MAX_ASYNC_CLIENTS]; // needed for tracking delta angles +} mapSpawnData_t; + +typedef enum { + TD_NO, + TD_YES, + TD_YES_THEN_QUIT +} timeDemo_t; + +const int USERCMD_PER_DEMO_FRAME = 2; +const int CONNECT_TRANSMIT_TIME = 1000; +const int MAX_LOGGED_USERCMDS = 60*60*60; // one hour of single player, 15 minutes of four player + +class idSessionLocal : public idSession { +public: + + idSessionLocal(); + virtual ~idSessionLocal(); + + virtual void Init(); + + virtual void Shutdown(); + + virtual void Stop(); + + virtual void UpdateScreen( bool outOfSequence = true ); + + virtual void PacifierUpdate(); + virtual void QuickMapChange( const char *mapName ); + virtual bool GetMapSpawned( void ); + virtual bool GetLoadingSaveGame( void ); + virtual idRenderWorld *GetGameRenderWorld( void ); + + virtual void Frame(); + + virtual bool IsMultiplayer(); + + virtual bool ProcessEvent( const sysEvent_t *event ); + + virtual void StartMenu( const char *menuName = NULL ); + virtual void ExitMenu(); + virtual void GuiFrameEvents(); + virtual void SetGUI( idUserInterface *gui, HandleGuiCommand_t handle ); + virtual idUserInterface *GetActiveGUI( void ); + virtual int GetDesiredSoundWorld( bool menuWorld, bool &newSoundWorld ); + virtual const char *KeysFromBinding( const char *bind ); + + virtual const char *MessageBox( msgBoxType_t type, const char *message, const char *title = NULL, bool wait = false, const char *fire_yes = NULL, const char *fire_no = NULL, bool network = false ); + virtual void StopBox( void ); + virtual void DownloadProgressBox( backgroundDownload_t *bgl, const char *title, int progress_start = 0, int progress_end = 100 ); + virtual void SetPlayingSoundWorld(); + + virtual void TimeHitch( int msec ); + + virtual void ReadCDKey( void ); + virtual void WriteCDKey( void ); + virtual const char *GetCDKey( void ); + virtual bool CheckKey( const char *key, bool netConnect, bool offlineValid ); + virtual bool CDKeysAreValid( bool strict ); + virtual void ClearCDKey( bool valid ); + virtual void SetCDKeyGuiVars( void ); + virtual bool WaitingForGameAuth( void ); + virtual void CDKeysAuthReply( bool valid, const char *auth_msg ); + virtual void EmitClientAuth( void ); + virtual void UpdateFriends( void ); + virtual void KillLoadingGUI( void ); + + virtual int GetSaveGameVersion( void ); + virtual void GetCurrentMapName( idStr &mapName ); + virtual void PrecacheMPSettingsMenu( void ); + virtual void SetLoadingText( const char *text ); + virtual void AddLoadingIcon( const char *icon ); + virtual void SetMainMenuGuiVars( void ); + + // Doom 3 seed compatibility overloads used only by unreconstructed engine + // call sites; the virtual surface above is the retail Quake 4 interface. + const char * GetCDKey( bool expansionKey ); + bool CheckKey( const char *key, bool netConnect, bool offlineValid[2] ); + void ClearCDKey( bool valid[2] ); + + //===================================== + + int GetLocalClientNum(); + + void MoveToNewMap( const char *mapName ); + + // loads a map and starts a new game on it + void StartNewGame( const char *mapName, bool devmap = false ); + void PlayIntroGui(); + + void LoadSession( const char *name ); + void SaveSession( const char *name ); + + // called by Draw when the scene to scene wipe is still running + void DrawWipeModel(); + void StartWipe( const char *materialName, bool hold = false); + void CompleteWipe(); + void ClearWipe(); + + void ShowLoadingGui(); + + void ScrubSaveGameFileName( idStr &saveFileName ) const; + idStr GetAutoSaveName( const char *mapName ) const; + + bool LoadGame(const char *saveName); + bool SaveGame(const char *saveName, bool autosave = false); + + const char *GetAuthMsg( void ); + + //===================================== + + static idCVar com_showAngles; + static idCVar com_showTics; + static idCVar com_minTics; + static idCVar com_fixedTic; + static idCVar com_showDemo; + static idCVar com_skipGameDraw; + static idCVar com_aviDemoWidth; + static idCVar com_aviDemoHeight; + static idCVar com_aviDemoSamples; + static idCVar com_aviDemoTics; + static idCVar com_wipeSeconds; + static idCVar com_guid; + + static idCVar gui_configServerRate; + + int timeHitch; + idRenderWorld * rw; + int sw; + idDemoFile * readDemo; + idDemoFile * writeDemo; + int renderdemoVersion; + + bool menuActive; + int menuSoundWorld; // SOUNDWORLD_MENU in Quake 4's id-based sound API + + bool insideExecuteMapChange; // draw loading screen and update + // screen on prints + int bytesNeededForMapLoad; // + + // we don't want to redraw the loading screen for every single + // console print that happens + int lastPacifierTime; + + // this is the information required to be set before ExecuteMapChange() is called, + // which can be saved off at any time with the following commands so it can all be played back + mapSpawnData_t mapSpawnData; + idStr currentMapName; // for checking reload on same level + bool mapSpawned; // cleared on Stop() + + int numClients; // from serverInfo + + int logIndex; + logCmd_t loggedUsercmds[MAX_LOGGED_USERCMDS]; + int statIndex; + logStats_t loggedStats[MAX_LOGGED_STATS]; + int lastSaveIndex; + // each game tic, numClients usercmds will be added, until full + + bool insideUpdateScreen; // true while inside ::UpdateScreen() + + bool loadingSaveGame; // currently loading map from a SaveGame + idFile * savegameFile; // this is the savegame file to load from + int savegameVersion; + + idFile * cmdDemoFile; // if non-zero, we are reading commands from a file + + int latchedTicNumber; // set to com_ticNumber each frame + int lastGameTic; // while latchedTicNumber > lastGameTic, run game frames + int lastDemoTic; + bool syncNextGameFrame; + + + bool aviCaptureMode; // if true, screenshots will be taken and sound captured + idStr aviDemoShortName; // + float aviDemoFrameCount; + int aviTicStart; + + timeDemo_t timeDemo; + int timeDemoStartTime; + int numDemoFrames; // for timeDemo and demoShot + int demoTimeOffset; + renderView_t currentDemoRenderView; + // the next one will be read when + // com_frameTime + demoTimeOffset > currentDemoRenderView. + + // TODO: make this private (after sync networking removal and idnet tweaks) + idUserInterface * guiActive; + HandleGuiCommand_t guiHandle; + + idUserInterface * guiInGame; + idUserInterface * guiMainMenu; + idListGUI * guiMainMenu_MapList; // easy map list handling + idUserInterface * guiRestartMenu; + idUserInterface * guiLoading; + idUserInterface * guiIntro; + idUserInterface * guiGameOver; + idUserInterface * guiTest; + idUserInterface * guiTakeNotes; + + idUserInterface * guiMsg; + idUserInterface * guiMsgRestore; // store the calling GUI for restore + idStr msgFireBack[ 2 ]; + bool msgRunning; + int msgRetIndex; + bool msgIgnoreButtons; + + bool waitingOnBind; + + const idMaterial * whiteMaterial; + + const idMaterial * wipeMaterial; + int wipeStartTic; + int wipeStopTic; + bool wipeHold; + +#if defined( ID_CONSOLE_LOCK ) + int emptyDrawCount; // watchdog to force the main menu to restart +#endif + + //===================================== + void Clear(); + + void DrawCmdGraph(); + void Draw(); + + void WriteCmdDemo( const char *name, bool save = false); + void StartPlayingCmdDemo( const char *demoName); + void TimeCmdDemo( const char *demoName); + void SaveCmdDemoToFile(idFile *file); + void LoadCmdDemoFromFile(idFile *file); + void StartRecordingRenderDemo( const char *name ); + void StopRecordingRenderDemo(); + void StartPlayingRenderDemo( idStr name ); + void StopPlayingRenderDemo(); + void CompressDemoFile( const char *scheme, const char *name ); + void TimeRenderDemo( const char *name, bool twice = false ); + void AVIRenderDemo( const char *name ); + void AVICmdDemo( const char *name ); + void AVIGame( const char *name ); + void BeginAVICapture( const char *name ); + void EndAVICapture(); + + void AdvanceRenderDemo( bool singleFrameOnly ); + void RunGameTic(); + + void FinishCmdLoad(); + void LoadLoadingGui(const char *mapName); + + void DemoShot( const char *name ); + + void TestGUI( const char *name ); + + int GetBytesNeededForMapLoad( const char *mapName ); + void SetBytesNeededForMapLoad( const char *mapName, int bytesNeeded ); + + void ExecuteMapChange( bool noFadeWipe = false ); + void UnloadMap(); + + // return true if we actually waiting on an auth reply + bool MaybeWaitOnCDKey( void ); + + //------------------ + // Session_menu.cpp + + idStrList loadGameList; + idStrList modsList; + + idUserInterface * GetActiveMenu(); + + void DispatchCommand( idUserInterface *gui, const char *menuCommand, bool doIngame = true ); + void MenuEvent( const sysEvent_t *event ); + bool HandleSaveGameMenuCommand( idCmdArgs &args, int &icmd ); + void HandleInGameCommands( const char *menuCommand ); + void HandleMainMenuCommands( const char *menuCommand ); + void HandleChatMenuCommands( const char *menuCommand ); + void HandleIntroMenuCommands( const char *menuCommand ); + void HandleRestartMenuCommands( const char *menuCommand ); + void HandleMsgCommands( const char *menuCommand ); + void HandleNoteCommands( const char *menuCommand ); + void GetSaveGameList( idStrList &fileList, idList &fileTimes ); + void TakeNotes( const char * p, bool extended = false ); + void UpdateMPLevelShot( void ); + + void SetSaveGameGuiVars( void ); + void SetModsMenuGuiVars( void ); + void SetMainMenuSkin( void ); + void SetPbMenuGuiVars( void ); + +private: + bool BoxDialogSanityCheck( void ); + void EmitGameAuth( void ); + + typedef enum { + CDKEY_UNKNOWN, // need to perform checks on the key + CDKEY_INVALID, // that key is wrong + CDKEY_OK, // valid + CDKEY_CHECKING, // sent a check request ( gameAuth only ) + CDKEY_NA // does not apply, xp key when xp is not present + } cdKeyState_t; + + static const int CDKEY_BUF_LEN = 17; + static const int CDKEY_AUTH_TIMEOUT = 5000; + + char cdkey[ CDKEY_BUF_LEN ]; + cdKeyState_t cdkey_state; + char xpkey[ CDKEY_BUF_LEN ]; + cdKeyState_t xpkey_state; + int authEmitTimeout; + bool authWaitBox; + + idStr authMsg; +}; + +extern idSessionLocal sessLocal; + +#endif /* !__SESSIONLOCAL_H__ */ diff --git a/src/framework/Unzip.h b/src/framework/Unzip.h new file mode 100644 index 0000000..9265967 --- /dev/null +++ b/src/framework/Unzip.h @@ -0,0 +1,342 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __UNZIP_H__ +#define __UNZIP_H__ + + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef void* unzFile; +#endif + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + unsigned int tm_sec; /* seconds after the minute - [0,59] */ + unsigned int tm_min; /* minutes after the hour - [0,59] */ + unsigned int tm_hour; /* hours since midnight - [0,23] */ + unsigned int tm_mday; /* day of the month - [1,31] */ + unsigned int tm_mon; /* months since January - [0,11] */ + unsigned int tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + unsigned long number_entry; /* total number of entries in the central dir on this disk */ + unsigned long size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + unsigned long version; /* version made by 2 unsigned chars */ + unsigned long version_needed; /* version needed to extract 2 unsigned chars */ + unsigned long flag; /* general purpose bit flag 2 unsigned chars */ + unsigned long compression_method; /* compression method 2 unsigned chars */ + unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */ + unsigned long crc; /* crc-32 4 unsigned chars */ + unsigned long compressed_size; /* compressed size 4 unsigned chars */ + unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */ + unsigned long size_filename; /* filename length 2 unsigned chars */ + unsigned long size_file_extra; /* extra field length 2 unsigned chars */ + unsigned long size_file_comment; /* file comment length 2 unsigned chars */ + + unsigned long disk_num_start; /* disk number start 2 unsigned chars */ + unsigned long internal_fa; /* internal file attributes 2 unsigned chars */ + unsigned long external_fa; /* external file attributes 4 unsigned chars */ + + tm_unz tmu_date; +} unz_file_info; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */ +} unz_file_info_internal; + +typedef void* (*alloc_func) (void* opaque, unsigned int items, unsigned int size); +typedef void (*free_func) (void* opaque, void* address); + +struct internal_state; + +typedef struct z_stream_s { + unsigned char *next_in; /* next input unsigned char */ + unsigned int avail_in; /* number of unsigned chars available at next_in */ + unsigned long total_in; /* total nb of input unsigned chars read so */ + + unsigned char *next_out; /* next output unsigned char should be put there */ + unsigned int avail_out; /* remaining free space at next_out */ + unsigned long total_out; /* total nb of unsigned chars output so */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + unsigned char* opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: ascii or binary */ + unsigned long adler; /* adler32 value of the uncompressed data */ + unsigned long reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream *z_streamp; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/ + unsigned long stream_initialised; /* flag set if stream structure is initialised*/ + + unsigned long offset_local_extrafield;/* offset of the static extra field */ + unsigned int size_local_extrafield;/* size of the static extra field */ + unsigned long pos_local_extrafield; /* position in the static extra field in read*/ + + unsigned long crc32; /* crc32 of all data uncompressed */ + unsigned long crc32_wait; /* crc32 we must obtain after decompress all */ + unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */ + unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/ + FILE* file; /* io structore of the zipfile */ + unsigned long compression_method; /* compression method (0==store) */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + FILE* file; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ + unsigned long num_file; /* number of the current file in the zipfile*/ + unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/ + unsigned long current_file_ok; /* flag about the usability of the current file*/ + unsigned long central_pos; /* position of the beginning of the central dir*/ + + unsigned long size_central_dir; /* size of the central directory */ + unsigned long offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ +} unz_s; + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +#define UNZ_CASESENSITIVE 1 +#define UNZ_NOTCASESENSITIVE 2 +#define UNZ_OSDEFAULTCASE 0 + +extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + +extern unzFile unzOpen (const char *path); +extern unzFile unzReOpen (const char* path, unzFile file); + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer + "zlib/zlib111.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern int unzClose (unzFile file); + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info); + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf); + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of unsigned char copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int unzGoToFirstFile (unzFile file); + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int unzGoToNextFile (unzFile file); + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ); + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ); + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity); + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize); + +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int unzOpenCurrentFile (unzFile file); + +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int unzCloseCurrentFile (unzFile file); + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + + +extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len); + +/* + Read unsigned chars from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of unsigned char copied if somes unsigned chars are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern long unztell(unzFile file); + +/* + Give the current position in uncompressed data +*/ + +extern int unzeof (unzFile file); + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len); + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of unsigned chars copied in buf, or (if <0) + the error code +*/ + +#endif /* __UNZIP_H__ */ diff --git a/src/framework/UsercmdGen.h b/src/framework/UsercmdGen.h new file mode 100644 index 0000000..b4a7820 --- /dev/null +++ b/src/framework/UsercmdGen.h @@ -0,0 +1,264 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __USERCMDGEN_H__ +#define __USERCMDGEN_H__ + +/* +=============================================================================== + + Samples a set of user commands from player input. + +=============================================================================== +*/ + +const int USERCMD_HZ_SP = 60; // 60 frames per second +const int USERCMD_MSEC_SP = 1000 / USERCMD_HZ_SP; + +//const int USERCMD_HZ_MP = 30; // 30 frames per second +//const int USERCMD_MSEC_MP = 1000 / USERCMD_HZ_MP; + +//const int USERCMD_HZ_MP = 60; // 60 frames per second +//const int USERCMD_MSEC_MP = 1000 / USERCMD_HZ_MP; + +// usercmd_t->button bits +const int BUTTON_ATTACK = BIT(0); +const int BUTTON_RUN = BIT(1); +const int BUTTON_ZOOM = BIT(2); +const int BUTTON_SCORES = BIT(3); +const int BUTTON_MLOOK = BIT(4); +// RAVEN BEGIN +// ddynerman: stats +const int BUTTON_INGAMESTATS = BIT(5); +// jscott: for voicechat +const int BUTTON_VOICECHAT = BIT(6); +// ddynerman: tourney display +const int BUTTON_TOURNEY = BIT(7); +// twhitaker: strafe +const int BUTTON_STRAFE = BIT(8); +// RAVEN END + + +// usercmd_t->impulse commands +const int IMPULSE_0 = 0; // weap 0 +const int IMPULSE_1 = 1; // weap 1 +const int IMPULSE_2 = 2; // weap 2 +const int IMPULSE_3 = 3; // weap 3 +const int IMPULSE_4 = 4; // weap 4 +const int IMPULSE_5 = 5; // weap 5 +const int IMPULSE_6 = 6; // weap 6 +const int IMPULSE_7 = 7; // weap 7 +const int IMPULSE_8 = 8; // weap 8 +const int IMPULSE_9 = 9; // weap 9 +const int IMPULSE_10 = 10; // weap 10 +const int IMPULSE_11 = 11; // weap 11 +const int IMPULSE_12 = 12; // weap 12 +const int IMPULSE_13 = 13; // weap reload +const int IMPULSE_14 = 14; // weap next +const int IMPULSE_15 = 15; // weap prev +const int IMPULSE_16 = 16; // +const int IMPULSE_17 = 17; // ready to play ( toggles ui_ready ) +const int IMPULSE_18 = 18; // center view +const int IMPULSE_19 = 19; // show PDA/INV/MAP +const int IMPULSE_20 = 20; // toggle team ( toggles ui_team ) +const int IMPULSE_21 = 21; // tourney toggle waiting room/spec +const int IMPULSE_22 = 22; // spectate +const int IMPULSE_23 = 23; // +const int IMPULSE_24 = 24; // +const int IMPULSE_25 = 25; // +const int IMPULSE_26 = 26; // +const int IMPULSE_27 = 27; // +const int IMPULSE_28 = 28; // vote yes +const int IMPULSE_29 = 29; // vote no +const int IMPULSE_40 = 40; // repeast last radio chatter + +// RAVEN BEGIN +// bdube: added flashlight +const int IMPULSE_50 = 50; // activate flashlight +const int IMPULSE_51 = 51; // switch to last weapon +// ddynerman: mp stats +const int IMPULSE_52 = 52; // mp statistics +// RAVEN END + + +// RAVEN BEGIN +// asalmon: impulses for weapons combos for xbox +#ifdef _XBOX +const int IMPULSE_70 = 70; //Weapon switch up +const int IMPULSE_71 = 71; //Weapon switch down +const int IMPULSE_72 = 72; //Weapon switch right +const int IMPULSE_73 = 73; //Weapon Switch left +#endif +//RAVEN END + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +const int IMPULSE_100 = 100; // Buy weapon_shotgun +const int IMPULSE_101 = 101; // Buy weapon_machinegun +const int IMPULSE_102 = 102; // Buy weapon_hyperblaster +const int IMPULSE_103 = 103; // Buy weapon_grenadelauncher +const int IMPULSE_104 = 104; // Buy weapon_nailgun +const int IMPULSE_105 = 105; // Buy weapon_rocketlauncher +const int IMPULSE_106 = 106; // Buy weapon_railgun +const int IMPULSE_107 = 107; // Buy weapon_lightninggun +const int IMPULSE_108 = 108; // UNUSED +const int IMPULSE_109 = 109; // Buy weapon_napalmgun +const int IMPULSE_110 = 110; // Buy weapon_dmg +const int IMPULSE_111 = 111; // UNUSED +const int IMPULSE_112 = 112; // UNUSED +const int IMPULSE_113 = 113; // UNUSED +const int IMPULSE_114 = 114; // UNUSED +const int IMPULSE_115 = 115; // UNUSED +const int IMPULSE_116 = 116; // UNUSED +const int IMPULSE_117 = 117; // UNUSED +const int IMPULSE_118 = 118; // Buy item_armor_small +const int IMPULSE_119 = 119; // Buy item_armor_large +const int IMPULSE_120 = 120; // Buy ammorefill +const int IMPULSE_121 = 121; // UNUSED +const int IMPULSE_122 = 122; // UNUSED +const int IMPULSE_123 = 123; // Buy team powerup: ammo_regen +const int IMPULSE_124 = 124; // Buy team powerup: health_regen +const int IMPULSE_125 = 125; // Buy team powerup: damage_boost +const int IMPULSE_126 = 126; // UNUSED +const int IMPULSE_127 = 127; // UNUSED +// RITUAL END + + +const int KEY_MOVESPEED = 127; + +// usercmd_t->flags +const int UCF_IMPULSE_SEQUENCE = 0x0001; // toggled every time an impulse command is sent + +class usercmd_t { +public: + int gameFrame; // frame number + int gameTime; // game time + int realTime; // real game time + int duplicateCount; // duplication count for networking +// RAVEN BEGIN +// ddynerman: expand buttons to 2 bytes + short buttons; // buttons +// RAVEN END + signed char forwardmove; // forward/backward movement + signed char rightmove; // left/right movement + signed char upmove; // up/down movement + short angles[3]; // view angles + short mx; // mouse delta x + short my; // mouse delta y + signed char impulse; // impulse command + byte flags; // additional flags + int sequence; // just for debugging + +public: + void ByteSwap(); // on big endian systems, byte swap the shorts and ints + bool operator==( const usercmd_t &rhs ) const; +}; + +typedef enum { + INHIBIT_SESSION = 0, + INHIBIT_ASYNC +} inhibit_t; + +const int MAX_BUFFERED_USERCMD = 64; + +class idUsercmdGen { +public: + virtual ~idUsercmdGen( void ) {} + + // Sets up all the cvars and console commands. + virtual void Init( void ) = 0; + + // Prepares for a new map. + virtual void InitForNewMap( void ) = 0; + + // Shut down. + virtual void Shutdown( void ) = 0; + + // Clears all key states and face straight. + virtual void Clear( void ) = 0; + + // Clears view angles. + virtual void ClearAngles( void ) = 0; + + // When the console is down or the menu is up, only emit default usercmd, so the player isn't moving around. + // Each subsystem (session and game) may want an inhibit will OR the requests. + virtual void InhibitUsercmd( inhibit_t subsystem, bool inhibit ) = 0; + + // Returns a buffered command for the given game tic. + virtual usercmd_t TicCmd( int ticNumber ) = 0; + + // Called async at regular intervals. + virtual void UsercmdInterrupt( void ) = 0; + + // Set a value that can safely be referenced by UsercmdInterrupt() for each key binding. + virtual int CommandStringUsercmdData( const char *cmdString ) = 0; + + // Returns the number of user commands. + virtual int GetNumUserCommands( void ) = 0; + + // Returns the name of a user command via index. + virtual const char *GetUserCommandName( int index ) = 0; + + // Continuously modified, never reset. For full screen guis. + virtual void MouseState( int *x, int *y, int *button, bool *down ) = 0; + + // Directly sample a button. + virtual int ButtonState( int key ) = 0; + + // Directly sample a keystate. + virtual int KeyState( int key ) = 0; + + // Directly sample a usercmd. The retail executable predates the SDK + // rename of this virtual. +#ifdef Q4_RECON_ENGINE_PRIVATE + virtual usercmd_t GetDirectUsercmd( void ) = 0; +#else + virtual usercmd_t GetUsercmd( void ) = 0; +#endif + +//RAVEN BEGIN +//asalmon: slow down the joystick movement for aim assist on xenon + virtual void SetSlowJoystick(int slow) = 0; + +// nrausch: Stuff an arbitrary impulse in, which will override any other impulse this cycle + virtual void StuffImpulse( int impulse ) = 0; + +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual void StuffKey( const char *keyName, bool down ) = 0; +#endif + +#ifdef _XENON + virtual void GetInputs( int &y, int &p, int &r, int &f ) = 0; +#endif + +//RAVEN END +}; + +extern idUsercmdGen *usercmdGen; + +#endif /* !__USERCMDGEN_H__ */ diff --git a/src/framework/async/AsyncClient.h b/src/framework/async/AsyncClient.h new file mode 100644 index 0000000..2c6a253 --- /dev/null +++ b/src/framework/async/AsyncClient.h @@ -0,0 +1,216 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __ASYNCCLIENT_H__ +#define __ASYNCCLIENT_H__ + +/* +=============================================================================== + + Network Client for asynchronous networking. + +=============================================================================== +*/ + +typedef enum { + CS_DISCONNECTED, + CS_PURERESTART, + CS_CHALLENGING, + CS_CONNECTING, + CS_CONNECTED, + CS_INGAME +} clientState_t; + +typedef enum { + AUTHKEY_BADKEY, + AUTHKEY_GUID +} authKeyMsg_t; + +typedef enum { + AUTHKEY_BAD_INVALID, + AUTHKEY_BAD_BANNED, + AUTHKEY_BAD_INUSE, + AUTHKEY_BAD_MSG +} authBadKeyStatus_t; + +typedef enum { + UPDATE_NONE, + UPDATE_SENT, + UPDATE_READY, + UPDATE_DLING, + UPDATE_DONE +} clientUpdateState_t; + +typedef struct { + idStr url; + idStr filename; + int size; + int checksum; +} pakDlEntry_t; + +class idAsyncClient { +public: + idAsyncClient(); + + void Shutdown( void ); + bool InitPort( void ); + void ClosePort( void ); + void ConnectToServer( const netadr_t adr ); + void ConnectToServer( const char *address ); + void Reconnect( void ); + void DisconnectFromServer( void ); + void GetServerInfo( const netadr_t adr ); + void GetServerInfo( const char *address ); + void GetLANServers( void ); + void GetNETServers( void ); + void ListServers( void ); + void ClearServers( void ); + void RemoteConsole( const char *command ); + bool IsPortInitialized() { return clientPort.GetPort() != 0; } + + bool IsActive( void ) const { return active; } + int GetLocalClientNum( void ) const { return clientNum; } + int GetPrediction( void ) const; + int GetTimeSinceLastPacket( void ) const; + int GetOutgoingRate( void ) const; + int GetIncomingRate( void ) const; + float GetOutgoingCompression( void ) const; + float GetIncomingCompression( void ) const; + float GetIncomingPacketLoss( void ) const; + netadr_t GetServerAddress( void ) const; + void GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const; + int GetPredictedFrames( void ) const { return lastFrameDelta; } + + void RunFrame( void ); + void SendReliableGameMessage( const idBitMsg &msg ); + + void SendVersionCheck( bool fromMenu = false ); + // pass NULL for the keys you don't care to auth for + // returns false if internet link doesn't appear to be available + bool SendAuthCheck( const char *cdkey, const char *xpkey ); + + void PacifierUpdate( void ); + + idServerScan serverList; + +private: + bool active; // true if client is active + int realTime; // absolute time + + int clientTime; // client local time + idPort clientPort; // UDP port + int clientId; // client identification + int clientDataChecksum; // checksum of the data used by the client + int clientNum; // client number on server + clientState_t clientState; // client state + int clientPrediction; // how far the client predicts ahead + int clientPredictTime; // prediction time used to send user commands + + netadr_t serverAddress; // IP address of server + int serverId; // server identification + int serverChallenge; // challenge from server + int serverMessageSequence; // sequence number of last server message + + netadr_t lastRconAddress; // last rcon address we emitted to + int lastRconTime; // when last rcon emitted + + idMsgChannel channel; // message channel to server + int lastConnectTime; // last time a connect message was sent + int lastEmptyTime; // last time an empty message was sent + int lastPacketTime; // last time a packet was received from the server + int lastSnapshotTime; // last time a snapshot was received + + int snapshotSequence; // sequence number of the last received snapshot + int snapshotGameFrame; // game frame number of the last received snapshot + int snapshotGameTime; // game time of the last received snapshot + + int gameInitId; // game initialization identification + int gameFrame; // local game frame + int gameTime; // local game time + int gameTimeResidual; // left over time from previous frame + + usercmd_t userCmds[MAX_USERCMD_BACKUP][MAX_ASYNC_CLIENTS]; + + idUserInterface * guiNetMenu; + + clientUpdateState_t updateState; + int updateSentTime; + idStr updateMSG; + idStr updateURL; + bool updateDirectDownload; + idStr updateFile; + dlMime_t updateMime; + idStr updateFallback; + bool showUpdateMessage; + + backgroundDownload_t backgroundDownload; + int dltotal; + int dlnow; + + int lastFrameDelta; + + int dlRequest; // randomized number to keep track of the requests + int dlChecksums[ MAX_PURE_PAKS ]; // 0-terminated, first element is the game pak checksum or 0 + int dlCount; // total number of paks we request download for ( including the game pak ) + idListdlList; // list of paks to download, with url and name + int currentDlSize; + int totalDlSize; // for partial progress stuff + + void Clear( void ); + void ClearPendingPackets( void ); + void DuplicateUsercmds( int frame, int time ); + void SendUserInfoToServer( void ); + void SendEmptyToServer( bool force = false, bool mapLoad = false ); + void SendPingResponseToServer( int time ); + void SendUsercmdsToServer( void ); + void InitGame( int serverGameInitId, int serverGameFrame, int serverGameTime, const idDict &serverSI ); + void ProcessUnreliableServerMessage( const idBitMsg &msg ); + void ProcessReliableServerMessages( void ); + void ProcessChallengeResponseMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessConnectResponseMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessDisconnectMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessInfoResponseMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessPrintMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessServersListMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessAuthKeyMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessVersionMessage( const netadr_t from, const idBitMsg &msg ); + void ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessMessage( const netadr_t from, idBitMsg &msg ); + void SetupConnection( void ); + void ProcessPureMessage( const netadr_t from, const idBitMsg &msg ); + bool ValidatePureServerChecksums( const netadr_t from, const idBitMsg &msg ); + void ProcessReliableMessagePure( const idBitMsg &msg ); + static const char* HandleGuiCommand( const char *cmd ); + const char* HandleGuiCommandInternal( const char *cmd ); + void SendVersionDLUpdate( int state ); + void HandleDownloads( void ); + void Idle( void ); + int UpdateTime( int clamp ); + void ReadLocalizedServerString( const idBitMsg &msg, char* out, int maxLen ); + bool CheckTimeout( void ); + void ProcessDownloadInfoMessage( const netadr_t from, const idBitMsg &msg ); + int GetDownloadRequest( const int checksums[ MAX_PURE_PAKS ], int count, int gamePakChecksum ); +}; + +#endif /* !__ASYNCCLIENT_H__ */ diff --git a/src/framework/async/AsyncNetwork.h b/src/framework/async/AsyncNetwork.h new file mode 100644 index 0000000..90b26d5 --- /dev/null +++ b/src/framework/async/AsyncNetwork.h @@ -0,0 +1,215 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __ASYNCNETWORK_H__ +#define __ASYNCNETWORK_H__ + +/* +DOOM III gold: 33 +1.1 beta patch: 34 +1.1 patch: 35 +1.2 XP: 36-39 +1.3 patch: 40 +1.3.1: 41 +*/ +const int ASYNC_PROTOCOL_MINOR = 41; +const int ASYNC_PROTOCOL_VERSION = ( ASYNC_PROTOCOL_MAJOR << 16 ) + ASYNC_PROTOCOL_MINOR; +#define MAJOR_VERSION(v) ( v >> 16 ) + +const int MAX_ASYNC_CLIENTS = 32; + +const int MAX_USERCMD_BACKUP = 256; +const int MAX_USERCMD_DUPLICATION = 25; +const int MAX_USERCMD_RELAY = 10; + +// index 0 is hardcoded to be the idnet master +// which leaves 4 to user customization +const int MAX_MASTER_SERVERS = 5; + +const int MAX_NICKLEN = 32; + +// max number of servers that will be scanned for at a single IP address +const int MAX_SERVER_PORTS = 8; + +// special game init ids +const int GAME_INIT_ID_INVALID = -1; +const int GAME_INIT_ID_MAP_LOAD = -2; + + +#include "MsgChannel.h" +#include "AsyncServer.h" +#include "ServerScan.h" +#include "AsyncClient.h" + +/* +=============================================================================== + + Asynchronous Networking. + +=============================================================================== +*/ + + +// unreliable server -> client messages +enum { + SERVER_UNRELIABLE_MESSAGE_EMPTY = 0, + SERVER_UNRELIABLE_MESSAGE_PING, + SERVER_UNRELIABLE_MESSAGE_GAMEINIT, + SERVER_UNRELIABLE_MESSAGE_SNAPSHOT +}; + +// reliable server -> client messages +enum { + SERVER_RELIABLE_MESSAGE_PURE = 0, + SERVER_RELIABLE_MESSAGE_RELOAD, + SERVER_RELIABLE_MESSAGE_CLIENTINFO, + SERVER_RELIABLE_MESSAGE_SYNCEDCVARS, + SERVER_RELIABLE_MESSAGE_PRINT, + SERVER_RELIABLE_MESSAGE_DISCONNECT, + SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT, + SERVER_RELIABLE_MESSAGE_GAME, + SERVER_RELIABLE_MESSAGE_ENTERGAME +}; + +// unreliable client -> server messages +enum { + CLIENT_UNRELIABLE_MESSAGE_EMPTY = 0, + CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE, + CLIENT_UNRELIABLE_MESSAGE_USERCMD +}; + +// reliable client -> server messages +enum { + CLIENT_RELIABLE_MESSAGE_PURE = 0, + CLIENT_RELIABLE_MESSAGE_CLIENTINFO, + CLIENT_RELIABLE_MESSAGE_PRINT, + CLIENT_RELIABLE_MESSAGE_DISCONNECT, + CLIENT_RELIABLE_MESSAGE_GAME +}; + +// server print messages +enum { + SERVER_PRINT_MISC = 0, + SERVER_PRINT_BADPROTOCOL, + SERVER_PRINT_RCON, + SERVER_PRINT_GAMEDENY, + SERVER_PRINT_BADCHALLENGE +}; + +enum { + SERVER_DL_REDIRECT = 1, + SERVER_DL_LIST, + SERVER_DL_NONE +}; + +enum { + SERVER_PAK_NO = 0, + SERVER_PAK_YES, + SERVER_PAK_END +}; + +typedef struct master_s { + idCVar * var; + netadr_t address; + bool resolved; +} master_t; + + +class idAsyncNetwork { +public: + idAsyncNetwork(); + + static void Init( void ); + static void Shutdown( void ); + static bool IsActive( void ) { return ( server.IsActive() || client.IsActive() ); } + static void RunFrame( void ); + + static void WriteUserCmdDelta( idBitMsg &msg, const usercmd_t &cmd, const usercmd_t *base ); + static void ReadUserCmdDelta( const idBitMsg &msg, usercmd_t &cmd, const usercmd_t *base ); + + static bool DuplicateUsercmd( const usercmd_t &previousUserCmd, usercmd_t ¤tUserCmd, int frame, int time ); + static bool UsercmdInputChanged( const usercmd_t &previousUserCmd, const usercmd_t ¤tUserCmd ); + + // returns true if the corresponding master is set to something (and could be resolved) + static bool GetMasterAddress( int index, netadr_t &adr ); + // get the hardcoded idnet master, equivalent to GetMasterAddress( 0, .. ) + static netadr_t GetMasterAddress( void ); + + static void GetNETServers( ); + + static void ExecuteSessionCommand( const char *sessCmd ); + + static idAsyncServer server; + static idAsyncClient client; + + static idCVar verbose; // verbose output + static idCVar allowCheats; // allow cheats + static idCVar serverDedicated; // if set run a dedicated server + static idCVar serverSnapshotDelay; // number of milliseconds between snapshots + static idCVar serverMaxClientRate; // maximum outgoing rate to clients + static idCVar clientMaxRate; // maximum rate from server requested by client + static idCVar serverMaxUsercmdRelay; // maximum number of usercmds relayed to other clients + static idCVar serverZombieTimeout; // time out in seconds for zombie clients + static idCVar serverClientTimeout; // time out in seconds for connected clients + static idCVar clientServerTimeout; // time out in seconds for server + static idCVar serverDrawClient; // the server draws the view of this client + static idCVar serverRemoteConsolePassword; // remote console password + static idCVar clientPrediction; // how many additional milliseconds the clients runs ahead + static idCVar clientMaxPrediction; // max milliseconds into the future a client can run prediction + static idCVar clientUsercmdBackup; // how many usercmds the client sends from previous frames + static idCVar clientRemoteConsoleAddress; // remote console address + static idCVar clientRemoteConsolePassword; // remote console password + static idCVar master0; // idnet master server + static idCVar master1; // 1st master server + static idCVar master2; // 2nd master server + static idCVar master3; // 3rd master server + static idCVar master4; // 4th master server + static idCVar LANServer; // LAN mode + static idCVar serverReloadEngine; // reload engine on map change instead of growing the referenced paks + static idCVar serverAllowServerMod; // let a pure server start with a different game code than what is referenced in game code + static idCVar idleServer; // serverinfo reply, indicates all clients are idle + static idCVar clientDownload; // preferred download policy + + // same message used for offline check and network reply + static void BuildInvalidKeyMsg( idStr &msg, bool valid ); + +private: + static int realTime; + static master_t masters[ MAX_MASTER_SERVERS]; // master1 etc. + + static void SpawnServer_f( const idCmdArgs &args ); + static void NextMap_f( const idCmdArgs &args ); + static void Connect_f( const idCmdArgs &args ); + static void Reconnect_f( const idCmdArgs &args ); + static void GetServerInfo_f( const idCmdArgs &args ); + static void GetLANServers_f( const idCmdArgs &args ); + static void ListServers_f( const idCmdArgs &args ); + static void RemoteConsole_f( const idCmdArgs &args ); + static void Heartbeat_f( const idCmdArgs &args ); + static void Kick_f( const idCmdArgs &args ); + static void CheckNewVersion_f( const idCmdArgs &args ); + static void UpdateUI_f( const idCmdArgs &args ); +}; + +#endif /* !__ASYNCNETWORK_H__ */ diff --git a/src/framework/async/AsyncServer.h b/src/framework/async/AsyncServer.h new file mode 100644 index 0000000..e37efeb --- /dev/null +++ b/src/framework/async/AsyncServer.h @@ -0,0 +1,258 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __ASYNCSERVER_H__ +#define __ASYNCSERVER_H__ + +/* +=============================================================================== + + Network Server for asynchronous networking. + +=============================================================================== +*/ + +// MAX_CHALLENGES is made large to prevent a denial of service attack that could cycle +// all of them out before legitimate users connected +const int MAX_CHALLENGES = 1024; + +// if we don't hear from authorize server, assume it is down +const int AUTHORIZE_TIMEOUT = 5000; + +// states for the server's authorization process +typedef enum { + CDK_WAIT = 0, // we are waiting for a confirm/deny from auth + // this is subject to timeout if we don't hear from auth + // or a permanent wait if auth said so + CDK_OK, + CDK_ONLYLAN, + CDK_PUREWAIT, + CDK_PUREOK, + CDK_MAXSTATES +} authState_t; + +// states from the auth server, while the client is in CDK_WAIT +typedef enum { + AUTH_NONE = 0, // no reply yet + AUTH_OK, // this client is good + AUTH_WAIT, // wait - keep sending me srvAuth though + AUTH_DENY, // denied - don't send me anything about this client anymore + AUTH_MAXSTATES +} authReply_t; + +// message from auth to be forwarded back to the client +// some are locally hardcoded to save space, auth has the possibility to send a custom reply +typedef enum { + AUTH_REPLY_WAITING = 0, // waiting on an initial reply from auth + AUTH_REPLY_UNKNOWN, // client unknown to auth + AUTH_REPLY_DENIED, // access denied + AUTH_REPLY_PRINT, // custom message + AUTH_REPLY_SRVWAIT, // auth server replied and tells us he's working on it + AUTH_REPLY_MAXSTATES +} authReplyMsg_t; + +typedef struct challenge_s { + netadr_t address; // client address + int clientId; // client identification + int challenge; // challenge code + int time; // time the challenge was created + int pingTime; // time the challenge response was sent to client + bool connected; // true if the client is connected + authState_t authState; // local state regarding the client + authReply_t authReply; // cd key check replies + authReplyMsg_t authReplyMsg; // default auth messages + idStr authReplyPrint; // custom msg + char guid[12]; // guid + int OS; +} challenge_t; + +typedef enum { + SCS_FREE, // can be reused for a new connection + SCS_ZOMBIE, // client has been disconnected, but don't reuse connection for a couple seconds + SCS_PUREWAIT, // client needs to update it's pure checksums before we can go further + SCS_CONNECTED, // client is connected + SCS_INGAME // client is in the game +} serverClientState_t; + +typedef struct serverClient_s { + int OS; + int clientId; + serverClientState_t clientState; + int clientPrediction; + int clientAheadTime; + int clientRate; + int clientPing; + + int gameInitSequence; + int gameFrame; + int gameTime; + + idMsgChannel channel; + int lastConnectTime; + int lastEmptyTime; + int lastPingTime; + int lastSnapshotTime; + int lastPacketTime; + int lastInputTime; + int snapshotSequence; + int acknowledgeSnapshotSequence; + int numDuplicatedUsercmds; + + char guid[12]; // Even Balance - M. Quinn + +} serverClient_t; + + +class idAsyncServer { +public: + idAsyncServer(); + + bool InitPort( void ); + void ClosePort( void ); + void Spawn( void ); + void Kill( void ); + void ExecuteMapChange( void ); + + int GetPort( void ) const; + netadr_t GetBoundAdr( void ) const; + bool IsActive( void ) const { return active; } + int GetDelay( void ) const { return gameTimeResidual; } + int GetOutgoingRate( void ) const; + int GetIncomingRate( void ) const; + bool IsClientInGame( int clientNum ) const; + int GetClientPing( int clientNum ) const; + int GetClientPrediction( int clientNum ) const; + int GetClientTimeSinceLastPacket( int clientNum ) const; + int GetClientTimeSinceLastInput( int clientNum ) const; + int GetClientOutgoingRate( int clientNum ) const; + int GetClientIncomingRate( int clientNum ) const; + float GetClientOutgoingCompression( int clientNum ) const; + float GetClientIncomingCompression( int clientNum ) const; + float GetClientIncomingPacketLoss( int clientNum ) const; + netadr_t GetClientAddress( int clientNum ) const; + const char * GetClientGUID( int clientNum ) const; + void GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const; + int GetNumClients( void ) const; + int GetNumIdleClients( void ) const; + int GetLocalClientNum( void ) const { return localClientNum; } + + void RunFrame( void ); + void ProcessConnectionLessMessages( void ); + void RemoteConsoleOutput( const char *string ); + void SendReliableGameMessage( int clientNum, const idBitMsg &msg ); + void SendReliableGameMessageExcluding( int clientNum, const idBitMsg &msg ); + void LocalClientSendReliableMessage( const idBitMsg &msg ); + + void MasterHeartbeat( bool force = false ); + void DropClient( int clientNum, const char *reason ); + + void PacifierUpdate( void ); + + void UpdateUI( int clientNum ); + + void UpdateAsyncStatsAvg( void ); + void GetAsyncStatsAvgMsg( idStr &msg ); + + void PrintLocalServerInfo( void ); + +private: + bool active; // true if server is active + int realTime; // absolute time + + int serverTime; // local server time + idPort serverPort; // UDP port + int serverId; // server identification + int serverDataChecksum; // checksum of the data used by the server + int localClientNum; // local client on listen server + + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + serverClient_t clients[MAX_ASYNC_CLIENTS]; // clients + usercmd_t userCmds[MAX_USERCMD_BACKUP][MAX_ASYNC_CLIENTS]; + + int gameInitId; // game initialization identification + int gameFrame; // local game frame + int gameTime; // local game time + int gameTimeResidual; // left over time from previous frame + + netadr_t rconAddress; + + int nextHeartbeatTime; + int nextAsyncStatsTime; + + bool serverReloadingEngine; // flip-flop to not loop over when net_serverReloadEngine is on + + bool noRconOutput; // for default rcon response when command is silent + + int lastAuthTime; // global for auth server timeout + + // track the max outgoing rate over the last few secs to watch for spikes + // dependent on net_serverSnapshotDelay. 50ms, for a 3 seconds backlog -> 60 samples + static const int stats_numsamples = 60; + int stats_outrate[ stats_numsamples ]; + int stats_current; + int stats_average_sum; + int stats_max; + int stats_max_index; + + void PrintOOB( const netadr_t to, int opcode, const char *string ); + void DuplicateUsercmds( int frame, int time ); + void ClearClient( int clientNum ); + void InitClient( int clientNum, int clientId, int clientRate ); + void InitLocalClient( int clientNum ); + void BeginLocalClient( void ); + void LocalClientInput( void ); + void CheckClientTimeouts( void ); + void SendPrintBroadcast( const char *string ); + void SendPrintToClient( int clientNum, const char *string ); + void SendUserInfoBroadcast( int userInfoNum, const idDict &info, bool sendToAll = false ); + void SendUserInfoToClient( int clientNum, int userInfoNum, const idDict &info ); + void SendSyncedCvarsBroadcast( const idDict &cvars ); + void SendSyncedCvarsToClient( int clientNum, const idDict &cvars ); + void SendApplySnapshotToClient( int clientNum, int sequence ); + bool SendEmptyToClient( int clientNum, bool force = false ); + bool SendPingToClient( int clientNum ); + void SendGameInitToClient( int clientNum ); + bool SendSnapshotToClient( int clientNum ); + void ProcessUnreliableClientMessage( int clientNum, const idBitMsg &msg ); + void ProcessReliableClientMessages( int clientNum ); + void ProcessChallengeMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessConnectMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessRemoteConsoleMessage( const netadr_t from, const idBitMsg &msg ); + void ProcessGetInfoMessage( const netadr_t from, const idBitMsg &msg ); + bool ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ); + bool ProcessMessage( const netadr_t from, idBitMsg &msg ); + void ProcessAuthMessage( const idBitMsg &msg ); + bool SendPureServerMessage( const netadr_t to, int OS ); // returns false if no pure paks on the list + void ProcessPureMessage( const netadr_t from, const idBitMsg &msg ); + int ValidateChallenge( const netadr_t from, int challenge, int clientId ); // returns -1 if validate failed + bool SendReliablePureToClient( int clientNum ); + void ProcessReliablePure( int clientNum, const idBitMsg &msg ); + bool VerifyChecksumMessage( int clientNum, const netadr_t *from, const idBitMsg &msg, idStr &reply, int OS ); // if from is NULL, clientNum is used for error messages + void SendReliableMessage( int clientNum, const idBitMsg &msg ); // checks for overflow and disconnects the faulty client + int UpdateTime( int clamp ); + void SendEnterGameToClient( int clientNum ); + void ProcessDownloadRequestMessage( const netadr_t from, const idBitMsg &msg ); +}; + +#endif /* !__ASYNCSERVER_H__ */ diff --git a/src/framework/async/MsgChannel.h b/src/framework/async/MsgChannel.h new file mode 100644 index 0000000..aba2397 --- /dev/null +++ b/src/framework/async/MsgChannel.h @@ -0,0 +1,206 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MSGCHANNEL_H__ +#define __MSGCHANNEL_H__ + + +/* +=============================================================================== + + Network channel. + + Handles message fragmentation and out of order / duplicate suppression. + Unreliable messages are not garrenteed to arrive but when they do, they + arrive in order and without duplicates. Reliable messages always arrive, + and they also arrive in order without duplicates. Reliable messages piggy + back on unreliable messages. As such an unreliable message stream is + required for the reliable messages to be delivered. + +=============================================================================== +*/ + +#define MAX_MESSAGE_SIZE 16384 // max length of a message, which may + // be fragmented into multiple packets +#define CONNECTIONLESS_MESSAGE_ID -1 // id for connectionless messages +#define CONNECTIONLESS_MESSAGE_ID_MASK 0x7FFF // value to mask away connectionless message id + +#define MAX_MSG_QUEUE_SIZE 16384 // must be a power of 2 + +class idCompressor; + +#if !defined( Q4_RECON_SDK_MSGQUEUE ) +class idMsgQueue { +public: + idMsgQueue(); + + void Init( int sequence ); + + bool Add( const byte *data, const int size ); + bool Get( byte *data, int &size ); + int GetTotalSize( void ) const; + int GetSpaceLeft( void ) const; + int GetFirst( void ) const { return first; } + int GetLast( void ) const { return last; } + void CopyToBuffer( byte *buf ) const; + +private: + byte buffer[MAX_MSG_QUEUE_SIZE]; + int first; // sequence number of first message in queue + int last; // sequence number of last message in queue + int startIndex; // index pointing to the first byte of the first message + int endIndex; // index pointing to the first byte after the last message + + void WriteByte( byte b ); + byte ReadByte( void ); + void WriteShort( int s ); + int ReadShort( void ); + void WriteLong( int l ); + int ReadLong( void ); + void WriteData( const byte *data, const int size ); + void ReadData( byte *data, const int size ); +}; +#endif + + +class idMsgChannel { +public: + idMsgChannel(); + + void Init( const netadr_t adr, const int id ); + void Shutdown( void ); + void ResetRate( void ); + + // Sets the maximum outgoing rate. + void SetMaxOutgoingRate( int rate ) { maxRate = rate; } + + // Gets the maximum outgoing rate. + int GetMaxOutgoingRate( void ) { return maxRate; } + + // Returns the address of the entity at the other side of the channel. + netadr_t GetRemoteAddress( void ) const { return remoteAddress; } + + // Returns the average outgoing rate over the last second. + int GetOutgoingRate( void ) const { return outgoingRateBytes; } + + // Returns the average incoming rate over the last second. + int GetIncomingRate( void ) const { return incomingRateBytes; } + + // Returns the average outgoing compression ratio over the last second. + float GetOutgoingCompression( void ) const { return outgoingCompression; } + + // Returns the average incoming compression ratio over the last second. + float GetIncomingCompression( void ) const { return incomingCompression; } + + // Returns the average incoming packet loss over the last 5 seconds. + float GetIncomingPacketLoss( void ) const; + + // Returns true if the channel is ready to send new data based on the maximum rate. + bool ReadyToSend( const int time ) const; + + // Sends an unreliable message, in order and without duplicates. + int SendMessage( idPort &port, const int time, const idBitMsg &msg ); + + // Sends the next fragment if the last message was too large to send at once. + void SendNextFragment( idPort &port, const int time ); + + // Returns true if there are unsent fragments left. + bool UnsentFragmentsLeft( void ) const { return unsentFragments; } + + // Processes the incoming message. Returns true when a complete message + // is ready for further processing. In that case the read pointer of msg + // points to the first byte ready for reading, and sequence is set to + // the sequence number of the message. + bool Process( const netadr_t from, int time, idBitMsg &msg, int &sequence ); + + // Sends a reliable message, in order and without duplicates. + bool SendReliableMessage( const idBitMsg &msg ); + + // Returns true if a new reliable message is available and stores the message. + bool GetReliableMessage( idBitMsg &msg ); + + // Removes any pending outgoing or incoming reliable messages. + void ClearReliableMessages( void ); + +private: + netadr_t remoteAddress; // address of remote host + int id; // our identification used instead of port number + int maxRate; // maximum number of bytes that may go out per second + idCompressor * compressor; // compressor used for data compression + + // variables to control the outgoing rate + int lastSendTime; // last time data was sent out + int lastDataBytes; // bytes left to send at last send time + + // variables to keep track of the rate + int outgoingRateTime; + int outgoingRateBytes; + int incomingRateTime; + int incomingRateBytes; + + // variables to keep track of the compression ratio + float outgoingCompression; + float incomingCompression; + + // variables to keep track of the incoming packet loss + float incomingReceivedPackets; + float incomingDroppedPackets; + int incomingPacketLossTime; + + // sequencing variables + int outgoingSequence; + int incomingSequence; + + // outgoing fragment buffer + bool unsentFragments; + int unsentFragmentStart; + byte unsentBuffer[MAX_MESSAGE_SIZE]; + idBitMsg unsentMsg; + + // incoming fragment assembly buffer + int fragmentSequence; + int fragmentLength; + byte fragmentBuffer[MAX_MESSAGE_SIZE]; + + // reliable messages + idMsgQueue reliableSend; + idMsgQueue reliableReceive; + +private: + void WriteMessageData( idBitMsg &out, const idBitMsg &msg ); + bool ReadMessageData( idBitMsg &out, const idBitMsg &msg ); + + void UpdateOutgoingRate( const int time, const int size ); + void UpdateIncomingRate( const int time, const int size ); + + void UpdatePacketLoss( const int time, const int numReceived, const int numDropped ); +}; + +#if defined( _WIN32 ) && defined( Q4_RECON_SDK_MSGQUEUE ) +static_assert( sizeof( idBitMsg ) == 0x24, "idBitMsg ABI drift" ); +static_assert( sizeof( idMsgQueue ) == 0x4010, "idMsgQueue ABI drift" ); +static_assert( sizeof( idMsgChannel ) == 0x100A0, "idMsgChannel ABI drift" ); +#endif + +#endif /* !__MSGCHANNEL_H__ */ diff --git a/src/framework/async/NetworkSystem.h b/src/framework/async/NetworkSystem.h new file mode 100644 index 0000000..5318beb --- /dev/null +++ b/src/framework/async/NetworkSystem.h @@ -0,0 +1,184 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __NETWORKSYSTEM_H__ +#define __NETWORKSYSTEM_H__ + + +/* +=============================================================================== + + Network System. + +=============================================================================== +*/ + +typedef struct { + idStr nickname; + idStr clan; + short ping; + int rate; +} scannedClient_t; + +typedef struct { + netadr_t adr; + idDict serverInfo; + int ping; + + int clients; + + int OSMask; + +//RAVEN BEGIN +// shouchard: added favorite flag + bool favorite; // true if this has been marked by a user as a favorite + bool dedicated; +// shouchard: added performance filtered flag + bool performanceFiltered; // true if the client machine is too wimpy to have good performance +//RAVEN END +} scannedServer_t; + +typedef enum { + SC_NONE = -1, + SC_FAVORITE, + SC_LOCKED, + SC_DEDICATED, + SC_PB, + SC_NAME, + SC_PING, + SC_REPEATER, + SC_PLAYERS, + SC_GAMETYPE, + SC_MAP, + SC_ALL, + NUM_SC +} sortColumn_t; + +typedef struct { + sortColumn_t column; + idList::cmp_t* compareFn; + idList::filter_t* filterFn; + const char* description; +} sortInfo_t; + +class idNetworkSystem { +public: + virtual ~idNetworkSystem( void ) {} + +#ifdef Q4_RECON_ENGINE_PRIVATE + // Interface and ordering recovered from quake4.exe's 24-slot vtable. + virtual void ServerSendReliableMessage( int clientNum, const idBitMsg &msg ); + virtual void ServerSendReliableMessageExcluding( int clientNum, const idBitMsg &msg ); + virtual int ServerGetClientPing( int clientNum ); + virtual int ServerGetClientPrediction( int clientNum ); + virtual int ServerGetClientTimeSinceLastPacket( int clientNum ); + virtual int ServerGetClientTimeSinceLastInput( int clientNum ); + virtual int ServerGetClientOutgoingRate( int clientNum ); + virtual int ServerGetClientIncomingRate( int clientNum ); + virtual float ServerGetClientIncomingPacketLoss( int clientNum ); + virtual void ClientSendReliableMessage( const idBitMsg &msg ); + virtual int ClientGetPrediction( void ); + virtual int ClientGetTimeSinceLastPacket( void ); + virtual int ClientGetOutgoingRate( void ); + virtual int ClientGetIncomingRate( void ); + virtual float ClientGetIncomingPacketLoss( void ); + virtual const char * GetServerAddress( void ); + virtual const char * GetClientAddress( int clientNum ); + virtual void AddFriend( int clientNum ); + virtual void RemoveFriend( int clientNum ); + virtual void SetLoadingText( const char *loadingText ); + virtual void AddLoadingIcon( const char *icon ); + virtual const char * GetClientGUID( int clientNum ); + virtual void GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const; +#else + virtual void Shutdown( void ); + + virtual void ServerSendReliableMessage( int clientNum, const idBitMsg &msg, bool inhibitRepeater = false ); + virtual void RepeaterSendReliableMessage( int clientNum, const idBitMsg &msg, bool inhibitHeader = false, int including = -1 ); + virtual void RepeaterSendReliableMessageExcluding( int excluding, const idBitMsg &msg, bool inhibitHeader = false, int clientNum = -1 ); // NOTE: Message is sent to all viewers if clientNum is -1; excluding is used for playback. + virtual void ServerSendReliableMessageExcluding( int clientNum, const idBitMsg &msg, bool inhibitRepeater = false ); + virtual int ServerGetClientPing( int clientNum ); + virtual int ServerGetClientTimeSinceLastPacket( int clientNum ); + virtual int ServerGetClientTimeSinceLastInput( int clientNum ); + virtual int ServerGetClientOutgoingRate( int clientNum ); + virtual int ServerGetClientIncomingRate( int clientNum ); + virtual float ServerGetClientIncomingPacketLoss( int clientNum ); + virtual int ServerGetClientNum( int clientId ); + virtual int ServerGetServerTime( void ); + + // returns the new clientNum or -1 if there weren't any free. + virtual int ServerConnectBot( void ); + + virtual int RepeaterGetClientNum( int clientId ); + + virtual void ClientSendReliableMessage( const idBitMsg &msg ); + virtual int ClientGetPrediction( void ); + virtual int ClientGetTimeSinceLastPacket( void ); + virtual int ClientGetOutgoingRate( void ); + virtual int ClientGetIncomingRate( void ); + virtual float ClientGetIncomingPacketLoss( void ); +// RAVEN BEGIN +// ddynerman: added some utility functions + // uses a static buffer, copy it before calling in game again + virtual const char* GetServerAddress( void ); + virtual const char* GetClientAddress( int clientNum ); + virtual void AddFriend( int clientNum ); + virtual void RemoveFriend( int clientNum ); + // for MP games + virtual void SetLoadingText( const char* loadingText ); + virtual void AddLoadingIcon( const char* icon ); + virtual const char* GetClientGUID( int clientNum ); +// RAVEN END + + virtual void GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const; + + // server browser + virtual int GetNumScannedServers( void ); + virtual const scannedServer_t* GetScannedServerInfo( int serverNum ); + virtual const scannedClient_t* GetScannedServerClientInfo( int serverNum, int clientNum ); + virtual void AddSortFunction( const sortInfo_t &sortInfo ); + virtual bool RemoveSortFunction( const sortInfo_t &sortInfo ); + virtual void UseSortFunction( const sortInfo_t &sortInfo, bool use = true ); + virtual bool SortFunctionIsActive( const sortInfo_t &sortInfo ); + + // returns true if enabled + virtual bool HTTPEnable( bool enable ); + + virtual void ClientSetServerInfo( const idDict &serverSI ); + virtual void RepeaterSetInfo( const idDict &info ); + + virtual const char* GetViewerGUID( int clientNum ); + +private: + scannedServer_t scannedServer; + scannedClient_t scannedClient; +#endif +}; + +extern idNetworkSystem * networkSystem; + +#endif /* !__NETWORKSYSTEM_H__ */ diff --git a/src/framework/async/ServerScan.h b/src/framework/async/ServerScan.h new file mode 100644 index 0000000..52ffdf6 --- /dev/null +++ b/src/framework/async/ServerScan.h @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SERVERSCAN_H__ +#define __SERVERSCAN_H__ + +/* +=============================================================================== + + Scan for servers, on the LAN or from a list + Update a listDef GUI through usage of idListGUI class + When updating large lists of servers, sends out getInfo in small batches to avoid congestion + +=============================================================================== +*/ + +// storage for incoming servers / server scan +typedef struct { + netadr_t adr; + int id; + int time; +} inServer_t; + +// the menu gui uses a hard-coded control type to display a list of network games +typedef struct { + netadr_t adr; + idDict serverInfo; + int ping; + int id; // idnet mode sends an id for each server in list + int clients; + char nickname[ MAX_NICKLEN ][ MAX_ASYNC_CLIENTS ]; + short pings[ MAX_ASYNC_CLIENTS ]; + int rate[ MAX_ASYNC_CLIENTS ]; + int OSMask; + int challenge; +} networkServer_t; + +typedef enum { + SORT_PING, + SORT_SERVERNAME, + SORT_PLAYERS, + SORT_GAMETYPE, + SORT_MAP, + SORT_GAME +} serverSort_t; + +class idServerScan : public idList { +public: + idServerScan( ); + + int InfoResponse( networkServer_t &server ); + + // add an internet server - ( store a numeric id along with it ) + void AddServer( int id, const char *srv ); + + // we are going to feed server entries to be pinged + // if timeout is true, use a timeout once we start AddServer to trigger EndServers and decide the scan is done + void StartServers( bool timeout ); + // we are done filling up the list of server entries + void EndServers( ); + + // scan the current list of servers - used for refreshes and while receiving a fresh list + void NetScan( ); + + // clear + void Clear( ); + + // called each game frame. Updates the scanner state, takes care of ongoing scans + void RunFrame( ); + + typedef enum { + IDLE = 0, + WAIT_ON_INIT, + LAN_SCAN, + NET_SCAN + } scan_state_t; + + scan_state_t GetState() { return scan_state; } + void SetState( scan_state_t ); + + bool GetBestPing( networkServer_t &serv ); + + // prepare for a LAN scan. idAsyncClient does the network job (UDP broadcast), we do the storage + void SetupLANScan( ); + + void GUIConfig( idUserInterface *pGUI, const char *name ); + // update the GUI fields with information about the currently selected server + void GUIUpdateSelected( void ); + + void Shutdown( ); + + void ApplyFilter( ); + + // there is an internal toggle, call twice with same sort to switch + void SetSorting( serverSort_t sort ); + + int GetChallenge( ); + +private: + static const int MAX_PINGREQUESTS = 32; // how many servers to query at once + static const int REPLY_TIMEOUT = 999; // how long should we wait for a reply from a game server + static const int INCOMING_TIMEOUT = 1500; // when we got an incoming server list, how long till we decide the list is done + static const int REFRESH_START = 10000; // how long to wait when sending the initial refresh request + + scan_state_t scan_state; + + bool incoming_net; // set to true while new servers are fed through AddServer + bool incoming_useTimeout; + int incoming_lastTime; + + int lan_pingtime; // holds the time of LAN scan + + // servers we're waiting for a reply from + // won't exceed MAX_PINGREQUESTS elements + // holds index of net_servers elements, indexed by 'from' string + idDict net_info; + + idList net_servers; + // where we are in net_servers list for getInfo emissions ( NET_SCAN only ) + // we may either be waiting on MAX_PINGREQUESTS, or for net_servers to grow some more ( through AddServer ) + int cur_info; + + idUserInterface *m_pGUI; + idListGUI * listGUI; + + serverSort_t m_sort; + bool m_sortAscending; + idList m_sortedServers; // use ascending for the walking order + + idStr screenshot; + int challenge; // challenge for current scan + + int endWaitTime; // when to stop waiting on a port init + +private: + void LocalClear( ); // we need to clear some internal data as well + + void EmitGetInfo( netadr_t &serv ); + void GUIAdd( int id, const networkServer_t server ); + bool IsFiltered( const networkServer_t server ); + + static int Cmp( const int *a, const int *b ); +}; + +#endif /* !__SERVERSCAN_H__ */ diff --git a/src/framework/async/asyncclient.cpp b/src/framework/async/asyncclient.cpp new file mode 100644 index 0000000..51c89b7 --- /dev/null +++ b/src/framework/async/asyncclient.cpp @@ -0,0 +1,2334 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AsyncNetwork.h" + +#include "../Session_local.h" + +const int SETUP_CONNECTION_RESEND_TIME = 1000; +const int EMPTY_RESEND_TIME = 500; +const int PREDICTION_FAST_ADJUST = 4; + + +/* +================== +idAsyncClient::idAsyncClient +================== +*/ +idAsyncClient::idAsyncClient( void ) { + guiNetMenu = NULL; + updateState = UPDATE_NONE; + Clear(); +} + +/* +================== +idAsyncClient::Clear +================== +*/ +void idAsyncClient::Clear( void ) { + active = false; + realTime = 0; + clientTime = 0; + clientId = 0; + clientDataChecksum = 0; + clientNum = 0; + clientState = CS_DISCONNECTED; + clientPrediction = 0; + clientPredictTime = 0; + serverId = 0; + serverChallenge = 0; + serverMessageSequence = 0; + lastConnectTime = -9999; + lastEmptyTime = -9999; + lastPacketTime = -9999; + lastSnapshotTime = -9999; + snapshotGameFrame = 0; + snapshotGameTime = 0; + snapshotSequence = 0; + gameInitId = GAME_INIT_ID_INVALID; + gameFrame = 0; + gameTimeResidual = 0; + gameTime = 0; + memset( userCmds, 0, sizeof( userCmds ) ); + backgroundDownload.completed = true; + lastRconTime = 0; + showUpdateMessage = false; + lastFrameDelta = 0; + + dlRequest = -1; + dlCount = -1; + memset( dlChecksums, 0, sizeof( int ) * MAX_PURE_PAKS ); + currentDlSize = 0; + totalDlSize = 0; +} + +/* +================== +idAsyncClient::Shutdown +================== +*/ +void idAsyncClient::Shutdown( void ) { + guiNetMenu = NULL; + updateMSG.Clear(); + updateURL.Clear(); + updateFile.Clear(); + updateFallback.Clear(); + backgroundDownload.url.url.Clear(); + dlList.Clear(); +} + +/* +================== +idAsyncClient::InitPort +================== +*/ +bool idAsyncClient::InitPort( void ) { + // if this is the first time we connect to a server, open the UDP port + if ( !clientPort.GetPort() ) { + if ( !clientPort.InitForPort( PORT_ANY ) ) { + common->Printf( "Couldn't open client network port.\n" ); + return false; + } + } + // maintain it valid between connects and ui manager reloads + guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true ); + + return true; +} + +/* +================== +idAsyncClient::ClosePort +================== +*/ +void idAsyncClient::ClosePort( void ) { + clientPort.Close(); +} + +/* +================== +idAsyncClient::ClearPendingPackets +================== +*/ +void idAsyncClient::ClearPendingPackets( void ) { + int size; + byte msgBuf[MAX_MESSAGE_SIZE]; + netadr_t from; + + while( clientPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) { + } +} + +/* +================== +idAsyncClient::HandleGuiCommandInternal +================== +*/ +const char* idAsyncClient::HandleGuiCommandInternal( const char *cmd ) { + if ( !idStr::Cmp( cmd, "abort" ) || !idStr::Cmp( cmd, "pure_abort" ) ) { + common->DPrintf( "connection aborted\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + return ""; + } else { + common->DWarning( "idAsyncClient::HandleGuiCommand: unknown cmd %s", cmd ); + } + return NULL; +} + +/* +================== +idAsyncClient::HandleGuiCommand +================== +*/ +const char* idAsyncClient::HandleGuiCommand( const char *cmd ) { + return idAsyncNetwork::client.HandleGuiCommandInternal( cmd ); +} + +/* +================== +idAsyncClient::ConnectToServer +================== +*/ +void idAsyncClient::ConnectToServer( const netadr_t adr ) { + // shutdown any current game. that includes network disconnect + session->Stop(); + + if ( !InitPort() ) { + return; + } + + if ( cvarSystem->GetCVarBool( "net_serverDedicated" ) ) { + common->Printf( "Can't connect to a server as dedicated\n" ); + return; + } + + // trash any currently pending packets + ClearPendingPackets(); + + serverAddress = adr; + + // clear the client state + Clear(); + + // get a pseudo random client id, but don't use the id which is reserved for connectionless packets + clientId = Sys_Milliseconds() & CONNECTIONLESS_MESSAGE_ID_MASK; + + // calculate a checksum on some of the essential data used + clientDataChecksum = declManager->GetChecksum(); + + // start challenging the server + clientState = CS_CHALLENGING; + + active = true; + + guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true ); + guiNetMenu->SetStateString( "status", va( common->GetLanguageDict()->GetString( "#str_06749" ), Sys_NetAdrToString( adr ) ) ); + session->SetGUI( guiNetMenu, HandleGuiCommand ); +} + +/* +================== +idAsyncClient::Reconnect +================== +*/ +void idAsyncClient::Reconnect( void ) { + ConnectToServer( serverAddress ); +} + +/* +================== +idAsyncClient::ConnectToServer +================== +*/ +void idAsyncClient::ConnectToServer( const char *address ) { + int serverNum; + netadr_t adr; + + if ( idStr::IsNumeric( address ) ) { + serverNum = atoi( address ); + if ( serverNum < 0 || serverNum >= serverList.Num() ) { + session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06733" ), serverNum ), common->GetLanguageDict()->GetString( "#str_06735" ), true ); + return; + } + adr = serverList[ serverNum ].adr; + } else { + if ( !Sys_StringToNetAdr( address, &adr, true ) ) { + session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_06734" ), address ), common->GetLanguageDict()->GetString( "#str_06735" ), true ); + return; + } + } + if ( !adr.port ) { + adr.port = PORT_SERVER; + } + + common->Printf( "\"%s\" resolved to %s\n", address, Sys_NetAdrToString( adr ) ); + + ConnectToServer( adr ); +} + +/* +================== +idAsyncClient::DisconnectFromServer +================== +*/ +void idAsyncClient::DisconnectFromServer( void ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( clientState >= CS_CONNECTED ) { + // if we were actually connected, clear the pure list + fileSystem->ClearPureChecksums(); + + // send reliable disconnect to server + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( CLIENT_RELIABLE_MESSAGE_DISCONNECT ); + msg.WriteString( "disconnect" ); + + if ( !channel.SendReliableMessage( msg ) ) { + common->Error( "client->server reliable messages overflow\n" ); + } + + SendEmptyToServer( true ); + SendEmptyToServer( true ); + SendEmptyToServer( true ); + } + + if ( clientState != CS_PURERESTART ) { + channel.Shutdown(); + clientState = CS_DISCONNECTED; + } + + active = false; +} + +/* +================== +idAsyncClient::GetServerInfo +================== +*/ +void idAsyncClient::GetServerInfo( const netadr_t adr ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( !InitPort() ) { + return; + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "getInfo" ); + msg.WriteLong( serverList.GetChallenge() ); // challenge + + clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() ); +} + +/* +================== +idAsyncClient::GetServerInfo +================== +*/ +void idAsyncClient::GetServerInfo( const char *address ) { + netadr_t adr; + + if ( address && *address != '\0' ) { + if ( !Sys_StringToNetAdr( address, &adr, true ) ) { + common->Printf( "Couldn't get server address for \"%s\"\n", address ); + return; + } + } else if ( active ) { + adr = serverAddress; + } else if ( idAsyncNetwork::server.IsActive() ) { + // used to be a Sys_StringToNetAdr( "localhost", &adr, true ); and send a packet over loopback + // but this breaks with net_ip ( typically, for multi-homed servers ) + idAsyncNetwork::server.PrintLocalServerInfo(); + return; + } else { + common->Printf( "no server found\n" ); + return; + } + + if ( !adr.port ) { + adr.port = PORT_SERVER; + } + + GetServerInfo( adr ); +} + +/* +================== +idAsyncClient::GetLANServers +================== +*/ +void idAsyncClient::GetLANServers( void ) { + int i; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + netadr_t broadcastAddress; + + if ( !InitPort() ) { + return; + } + + idAsyncNetwork::LANServer.SetBool( true ); + + serverList.SetupLANScan(); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "getInfo" ); + msg.WriteLong( serverList.GetChallenge() ); + + broadcastAddress.type = NA_BROADCAST; + for ( i = 0; i < MAX_SERVER_PORTS; i++ ) { + broadcastAddress.port = PORT_SERVER + i; + clientPort.SendPacket( broadcastAddress, msg.GetData(), msg.GetSize() ); + } +} + +/* +================== +idAsyncClient::GetNETServers +================== +*/ +void idAsyncClient::GetNETServers( void ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + idAsyncNetwork::LANServer.SetBool( false ); + + // NetScan only clears GUI and results, not the stored list + serverList.Clear( ); + serverList.NetScan( ); + serverList.StartServers( true ); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "getServers" ); + msg.WriteLong( ASYNC_PROTOCOL_VERSION ); + msg.WriteString( cvarSystem->GetCVarString( "fs_game" ) ); + msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_password" ), 2 ); + msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_players" ), 2 ); + msg.WriteBits( cvarSystem->GetCVarInteger( "gui_filter_gameType" ), 2 ); + + netadr_t adr; + if ( idAsyncNetwork::GetMasterAddress( 0, adr ) ) { + clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() ); + } +} + +/* +================== +idAsyncClient::ListServers +================== +*/ +void idAsyncClient::ListServers( void ) { + int i; + + for ( i = 0; i < serverList.Num(); i++ ) { + common->Printf( "%3d: %s %dms (%s)\n", i, serverList[i].serverInfo.GetString( "si_name" ), serverList[ i ].ping, Sys_NetAdrToString( serverList[i].adr ) ); + } +} + +/* +================== +idAsyncClient::ClearServers +================== +*/ +void idAsyncClient::ClearServers( void ) { + serverList.Clear(); +} + +/* +================== +idAsyncClient::RemoteConsole +================== +*/ +void idAsyncClient::RemoteConsole( const char *command ) { + netadr_t adr; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( !InitPort() ) { + return; + } + + if ( active ) { + adr = serverAddress; + } else { + Sys_StringToNetAdr( idAsyncNetwork::clientRemoteConsoleAddress.GetString(), &adr, true ); + } + + if ( !adr.port ) { + adr.port = PORT_SERVER; + } + + lastRconAddress = adr; + lastRconTime = realTime; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "rcon" ); + msg.WriteString( idAsyncNetwork::clientRemoteConsolePassword.GetString() ); + msg.WriteString( command ); + + clientPort.SendPacket( adr, msg.GetData(), msg.GetSize() ); +} + +/* +================== +idAsyncClient::GetPrediction +================== +*/ +int idAsyncClient::GetPrediction( void ) const { + if ( clientState < CS_CONNECTED ) { + return -1; + } else { + return clientPrediction; + } +} + +/* +================== +idAsyncClient::GetTimeSinceLastPacket +================== +*/ +int idAsyncClient::GetTimeSinceLastPacket( void ) const { + if ( clientState < CS_CONNECTED ) { + return -1; + } else { + return clientTime - lastPacketTime; + } +} + +/* +================== +idAsyncClient::GetOutgoingRate +================== +*/ +int idAsyncClient::GetOutgoingRate( void ) const { + if ( clientState < CS_CONNECTED ) { + return -1; + } else { + return channel.GetOutgoingRate(); + } +} + +/* +================== +idAsyncClient::GetIncomingRate +================== +*/ +int idAsyncClient::GetIncomingRate( void ) const { + if ( clientState < CS_CONNECTED ) { + return -1; + } else { + return channel.GetIncomingRate(); + } +} + +/* +================== +idAsyncClient::GetOutgoingCompression +================== +*/ +float idAsyncClient::GetOutgoingCompression( void ) const { + if ( clientState < CS_CONNECTED ) { + return 0.0f; + } else { + return channel.GetOutgoingCompression(); + } +} + +/* +================== +idAsyncClient::GetIncomingCompression +================== +*/ +float idAsyncClient::GetIncomingCompression( void ) const { + if ( clientState < CS_CONNECTED ) { + return 0.0f; + } else { + return channel.GetIncomingCompression(); + } +} + +/* +================== +idAsyncClient::GetIncomingPacketLoss +================== +*/ +float idAsyncClient::GetIncomingPacketLoss( void ) const { + if ( clientState < CS_CONNECTED ) { + return 0.0f; + } else { + return channel.GetIncomingPacketLoss(); + } +} + +/* +================== +idAsyncClient::GetServerAddress +idAsyncClient::GetTrafficStats +================== +*/ +netadr_t idAsyncClient::GetServerAddress( void ) const { + return serverAddress; +} + +void idAsyncClient::GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const { + clientPort.GetTrafficStats( bytesSent, packetsSent, bytesReceived, packetsReceived ); +} + +/* +================== +idAsyncClient::DuplicateUsercmds +================== +*/ +void idAsyncClient::DuplicateUsercmds( int frame, int time ) { + int i, previousIndex, currentIndex; + + previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 ); + currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 ); + + // duplicate previous user commands if no new commands are available for a client + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + idAsyncNetwork::DuplicateUsercmd( userCmds[previousIndex][i], userCmds[currentIndex][i], frame, time ); + } +} + +/* +================== +idAsyncClient::SendUserInfoToServer +================== +*/ +void idAsyncClient::SendUserInfoToServer( void ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + idDict info; + + if ( clientState < CS_CONNECTED ) { + return; + } + + info = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO ); + + // send reliable client info to server + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( CLIENT_RELIABLE_MESSAGE_CLIENTINFO ); + msg.WriteDeltaDict( info, &sessLocal.mapSpawnData.userInfo[ clientNum ] ); + + if ( !channel.SendReliableMessage( msg ) ) { + common->Error( "client->server reliable messages overflow\n" ); + } + + sessLocal.mapSpawnData.userInfo[clientNum] = info; +} + +/* +================== +idAsyncClient::SendEmptyToServer +================== +*/ +void idAsyncClient::SendEmptyToServer( bool force, bool mapLoad ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( lastEmptyTime > realTime ) { + lastEmptyTime = realTime; + } + + if ( !force && ( realTime - lastEmptyTime < EMPTY_RESEND_TIME ) ) { + return; + } + + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "sending empty to server, gameInitId = %d\n", mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId ); + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( serverMessageSequence ); + msg.WriteLong( mapLoad ? GAME_INIT_ID_MAP_LOAD : gameInitId ); + msg.WriteLong( snapshotSequence ); + msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_EMPTY ); + + channel.SendMessage( clientPort, clientTime, msg ); + + while( channel.UnsentFragmentsLeft() ) { + channel.SendNextFragment( clientPort, clientTime ); + } + + lastEmptyTime = realTime; +} + +/* +================== +idAsyncClient::SendPingResponseToServer +================== +*/ +void idAsyncClient::SendPingResponseToServer( int time ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( idAsyncNetwork::verbose.GetInteger() == 2 ) { + common->Printf( "sending ping response to server, gameInitId = %d\n", gameInitId ); + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( serverMessageSequence ); + msg.WriteLong( gameInitId ); + msg.WriteLong( snapshotSequence ); + msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE ); + msg.WriteLong( time ); + + channel.SendMessage( clientPort, clientTime, msg ); + while( channel.UnsentFragmentsLeft() ) { + channel.SendNextFragment( clientPort, clientTime ); + } +} + +/* +================== +idAsyncClient::SendUsercmdsToServer +================== +*/ +void idAsyncClient::SendUsercmdsToServer( void ) { + int i, numUsercmds, index; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + usercmd_t * last; + + if ( idAsyncNetwork::verbose.GetInteger() == 2 ) { + common->Printf( "sending usercmd to server: gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime ); + } + + // generate user command for this client + index = gameFrame & ( MAX_USERCMD_BACKUP - 1 ); + userCmds[index][clientNum] = usercmdGen->GetDirectUsercmd(); + userCmds[index][clientNum].gameFrame = gameFrame; + userCmds[index][clientNum].gameTime = gameTime; + + // send the user commands to the server + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( serverMessageSequence ); + msg.WriteLong( gameInitId ); + msg.WriteLong( snapshotSequence ); + msg.WriteByte( CLIENT_UNRELIABLE_MESSAGE_USERCMD ); + msg.WriteShort( clientPrediction ); + + numUsercmds = idMath::ClampInt( 0, 10, idAsyncNetwork::clientUsercmdBackup.GetInteger() ) + 1; + + // write the user commands + msg.WriteLong( gameFrame ); + msg.WriteByte( numUsercmds ); + for ( last = NULL, i = gameFrame - numUsercmds + 1; i <= gameFrame; i++ ) { + index = i & ( MAX_USERCMD_BACKUP - 1 ); + idAsyncNetwork::WriteUserCmdDelta( msg, userCmds[index][clientNum], last ); + last = &userCmds[index][clientNum]; + } + + channel.SendMessage( clientPort, clientTime, msg ); + while( channel.UnsentFragmentsLeft() ) { + channel.SendNextFragment( clientPort, clientTime ); + } +} + +/* +================== +idAsyncClient::InitGame +================== +*/ +void idAsyncClient::InitGame( int serverGameInitId, int serverGameFrame, int serverGameTime, const idDict &serverSI ) { + gameInitId = serverGameInitId; + gameFrame = snapshotGameFrame = serverGameFrame; + gameTime = snapshotGameTime = serverGameTime; + gameTimeResidual = 0; + memset( userCmds, 0, sizeof( userCmds ) ); + + for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + sessLocal.mapSpawnData.userInfo[ i ].Clear(); + } + + sessLocal.mapSpawnData.serverInfo = serverSI; +} + +/* +================== +idAsyncClient::ProcessUnreliableServerMessage +================== +*/ +void idAsyncClient::ProcessUnreliableServerMessage( const idBitMsg &msg ) { + int i, j, index, id, numDuplicatedUsercmds, aheadOfServer, numUsercmds, delta; + int serverGameInitId, serverGameFrame, serverGameTime; + idDict serverSI; + usercmd_t *last; + bool pureWait; + + serverGameInitId = msg.ReadLong(); + + id = msg.ReadByte(); + switch( id ) { + case SERVER_UNRELIABLE_MESSAGE_EMPTY: { + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "received empty message from server\n" ); + } + break; + } + case SERVER_UNRELIABLE_MESSAGE_PING: { + if ( idAsyncNetwork::verbose.GetInteger() == 2 ) { + common->Printf( "received ping message from server\n" ); + } + SendPingResponseToServer( msg.ReadLong() ); + break; + } + case SERVER_UNRELIABLE_MESSAGE_GAMEINIT: { + serverGameFrame = msg.ReadLong(); + serverGameTime = msg.ReadLong(); + msg.ReadDeltaDict( serverSI, NULL ); + pureWait = serverSI.GetBool( "si_pure" ); + + InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI ); + + channel.ResetRate(); + + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "received gameinit, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime ); + } + + // mute sound + soundSystem->SetMute( true ); + + // ensure chat icon goes away when the GUI is changed... + //cvarSystem->SetCVarBool( "ui_chat", false ); + + if ( pureWait ) { + guiNetMenu = uiManager->FindGui( "guis/netmenu.gui", true, false, true ); + session->SetGUI( guiNetMenu, HandleGuiCommand ); + session->MessageBox( MSG_ABORT, common->GetLanguageDict()->GetString ( "#str_04317" ), common->GetLanguageDict()->GetString ( "#str_04318" ), false, "pure_abort" ); + } else { + // load map + session->SetGUI( NULL, NULL ); + sessLocal.ExecuteMapChange(); + } + + break; + } + case SERVER_UNRELIABLE_MESSAGE_SNAPSHOT: { + // if the snapshot is from a different game + if ( serverGameInitId != gameInitId ) { + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "ignoring snapshot with != gameInitId\n" ); + } + break; + } + + snapshotSequence = msg.ReadLong(); + snapshotGameFrame = msg.ReadLong(); + snapshotGameTime = msg.ReadLong(); + numDuplicatedUsercmds = msg.ReadByte(); + aheadOfServer = msg.ReadShort(); + + // read the game snapshot + game->ClientReadSnapshot( clientNum, snapshotSequence, snapshotGameFrame, snapshotGameTime, numDuplicatedUsercmds, aheadOfServer, msg ); + + // read user commands of other clients from the snapshot + for ( last = NULL, i = msg.ReadByte(); i < MAX_ASYNC_CLIENTS; i = msg.ReadByte() ) { + numUsercmds = msg.ReadByte(); + if ( numUsercmds > MAX_USERCMD_RELAY ) { + common->Error( "snapshot %d contains too many user commands for client %d", snapshotSequence, i ); + break; + } + for ( j = 0; j < numUsercmds; j++ ) { + index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 ); + idAsyncNetwork::ReadUserCmdDelta( msg, userCmds[index][i], last ); + userCmds[index][i].gameFrame = snapshotGameFrame + j; + userCmds[index][i].duplicateCount = 0; + last = &userCmds[index][i]; + } + // clear all user commands after the ones just read from the snapshot + for ( j = numUsercmds; j < MAX_USERCMD_BACKUP; j++ ) { + index = ( snapshotGameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 ); + userCmds[index][i].gameFrame = 0; + userCmds[index][i].gameTime = 0; + } + } + + // if this is the first snapshot after a game init was received + if ( clientState == CS_CONNECTED ) { + gameTimeResidual = 0; + clientState = CS_INGAME; + assert( !sessLocal.GetActiveMenu( ) ); + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "received first snapshot, gameInitId = %d, gameFrame %d gameTime %d\n", gameInitId, snapshotGameFrame, snapshotGameTime ); + } + } + + // if the snapshot is newer than the clients current game time + if ( gameTime < snapshotGameTime || gameTime > snapshotGameTime + idAsyncNetwork::clientMaxPrediction.GetInteger() ) { + gameFrame = snapshotGameFrame; + gameTime = snapshotGameTime; + gameTimeResidual = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), gameTimeResidual ); + clientPredictTime = idMath::ClampInt( -idAsyncNetwork::clientMaxPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPredictTime ); + } + + // adjust the client prediction time based on the snapshot time + clientPrediction -= ( 1 - ( INTSIGNBITSET( aheadOfServer - idAsyncNetwork::clientPrediction.GetInteger() ) << 1 ) ); + clientPrediction = idMath::ClampInt( idAsyncNetwork::clientPrediction.GetInteger(), idAsyncNetwork::clientMaxPrediction.GetInteger(), clientPrediction ); + delta = gameTime - ( snapshotGameTime + clientPrediction ); + clientPredictTime -= ( delta / PREDICTION_FAST_ADJUST ) + ( 1 - ( INTSIGNBITSET( delta ) << 1 ) ); + + lastSnapshotTime = clientTime; + + if ( idAsyncNetwork::verbose.GetInteger() == 2 ) { + common->Printf( "received snapshot, gameInitId = %d, gameFrame = %d, gameTime = %d\n", gameInitId, gameFrame, gameTime ); + } + + if ( numDuplicatedUsercmds && ( idAsyncNetwork::verbose.GetInteger() == 2 ) ) { + common->Printf( "server duplicated %d user commands before snapshot %d\n", numDuplicatedUsercmds, snapshotGameFrame ); + } + break; + } + default: { + common->Printf( "unknown unreliable server message %d\n", id ); + break; + } + } +} + +/* +================== +idAsyncClient::ProcessReliableMessagePure +================== +*/ +void idAsyncClient::ProcessReliableMessagePure( const idBitMsg &msg ) { + idBitMsg outMsg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + int inChecksums[ MAX_PURE_PAKS ]; + int i; + int gamePakChecksum; + int serverGameInitId; + + session->SetGUI( NULL, NULL ); + + serverGameInitId = msg.ReadLong(); + + if ( serverGameInitId != gameInitId ) { + common->DPrintf( "ignoring pure server checksum from an outdated gameInitId (%d)\n", serverGameInitId ); + return; + } + + if ( !ValidatePureServerChecksums( serverAddress, msg ) ) { + + return; + } + + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "received new pure server info. ExecuteMapChange and report back\n" ); + } + + // it is now ok to load the next map with updated pure checksums + sessLocal.ExecuteMapChange( true ); + + // upon receiving our pure list, the server will send us SCS_INGAME and we'll start getting snapshots + fileSystem->GetPureServerChecksums( inChecksums, -1, &gamePakChecksum ); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_PURE ); + + outMsg.WriteLong( gameInitId ); + + i = 0; + while ( inChecksums[ i ] ) { + outMsg.WriteLong( inChecksums[ i++ ] ); + } + outMsg.WriteLong( 0 ); + outMsg.WriteLong( gamePakChecksum ); + + if ( !channel.SendReliableMessage( outMsg ) ) { + common->Error( "client->server reliable messages overflow\n" ); + } +} + +/* +=============== +idAsyncClient::ReadLocalizedServerString +=============== +*/ +void idAsyncClient::ReadLocalizedServerString( const idBitMsg &msg, char *out, int maxLen ) { + msg.ReadString( out, maxLen ); + // look up localized string. if the message is not an #str_ format, we'll just get it back unchanged + idStr::snPrintf( out, maxLen - 1, "%s", common->GetLanguageDict()->GetString( out ) ); +} + +/* +================== +idAsyncClient::ProcessReliableServerMessages +================== +*/ +void idAsyncClient::ProcessReliableServerMessages( void ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + byte id; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + + while ( channel.GetReliableMessage( msg ) ) { + id = msg.ReadByte(); + switch( id ) { + case SERVER_RELIABLE_MESSAGE_CLIENTINFO: { + int clientNum; + clientNum = msg.ReadByte(); + + idDict &info = sessLocal.mapSpawnData.userInfo[ clientNum ]; + bool haveBase = ( msg.ReadBits( 1 ) != 0 ); + +#if ID_CLIENTINFO_TAGS + int checksum = info.Checksum(); + int srv_checksum = msg.ReadLong(); + if ( checksum != srv_checksum ) { + common->DPrintf( "SERVER_RELIABLE_MESSAGE_CLIENTINFO %d (haveBase: %s): != checksums srv: 0x%x local: 0x%x\n", clientNum, haveBase ? "true" : "false", checksum, srv_checksum ); + info.Print(); + } else { + common->DPrintf( "SERVER_RELIABLE_MESSAGE_CLIENTINFO %d (haveBase: %s): checksums ok 0x%x\n", clientNum, haveBase ? "true" : "false", checksum ); + } +#endif + + if ( haveBase ) { + msg.ReadDeltaDict( info, &info ); + } else { + msg.ReadDeltaDict( info, NULL ); + } + + // server forces us to a different userinfo + if ( clientNum == idAsyncClient::clientNum ) { + common->DPrintf( "local user info modified by server\n" ); + cvarSystem->SetCVarsFromDict( info ); + cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); // don't emit back + } + game->SetUserInfo( clientNum, info, true ); + break; + } + case SERVER_RELIABLE_MESSAGE_SYNCEDCVARS: { + idDict &info = sessLocal.mapSpawnData.syncedCVars; + msg.ReadDeltaDict( info, &info ); + cvarSystem->SetCVarsFromDict( info ); + if ( !idAsyncNetwork::allowCheats.GetBool() ) { + cvarSystem->ResetFlaggedVariables( CVAR_CHEAT ); + } + break; + } + case SERVER_RELIABLE_MESSAGE_PRINT: { + char string[MAX_STRING_CHARS]; + msg.ReadString( string, MAX_STRING_CHARS ); + common->Printf( "%s\n", string ); + break; + } + case SERVER_RELIABLE_MESSAGE_DISCONNECT: { + int clientNum; + char string[MAX_STRING_CHARS]; + clientNum = msg.ReadLong( ); + ReadLocalizedServerString( msg, string, MAX_STRING_CHARS ); + if ( clientNum == idAsyncClient::clientNum ) { + session->Stop(); + session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04319" ), true ); + session->StartMenu(); + } else { + common->Printf( "client %d %s\n", clientNum, string ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "addChatLine \"%s^0 %s\"", sessLocal.mapSpawnData.userInfo[ clientNum ].GetString( "ui_name" ), string ) ); + sessLocal.mapSpawnData.userInfo[ clientNum ].Clear(); + } + break; + } + case SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT: { + int sequence; + sequence = msg.ReadLong(); + if ( !game->ClientApplySnapshot( clientNum, sequence ) ) { + session->Stop(); + common->Error( "couldn't apply snapshot %d", sequence ); + } + break; + } + case SERVER_RELIABLE_MESSAGE_PURE: { + ProcessReliableMessagePure( msg ); + break; + } + case SERVER_RELIABLE_MESSAGE_RELOAD: { + if ( idAsyncNetwork::verbose.GetBool() ) { + common->Printf( "got MESSAGE_RELOAD from server\n" ); + } + // simply reconnect, so that if the server restarts in pure mode we can get the right list and avoid spurious reloads + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" ); + break; + } + case SERVER_RELIABLE_MESSAGE_ENTERGAME: { + SendUserInfoToServer(); + game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true ); + cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); + break; + } + default: { + // pass reliable message on to game code + game->ClientProcessReliableMessage( clientNum, msg ); + break; + } + } + } +} + +/* +================== +idAsyncClient::ProcessChallengeResponseMessage +================== +*/ +void idAsyncClient::ProcessChallengeResponseMessage( const netadr_t from, const idBitMsg &msg ) { + char serverGame[ MAX_STRING_CHARS ], serverGameBase[ MAX_STRING_CHARS ]; + + if ( clientState != CS_CHALLENGING ) { + common->Printf( "Unwanted challenge response received.\n" ); + return; + } + + serverChallenge = msg.ReadLong(); + serverId = msg.ReadShort(); + msg.ReadString( serverGameBase, MAX_STRING_CHARS ); + msg.ReadString( serverGame, MAX_STRING_CHARS ); + + // the server is running a different game... we need to reload in the correct fs_game + // even pure pak checks would fail if we didn't, as there are files we may not even see atm + // NOTE: we could read the pure list from the server at the same time and set it up for the restart + // ( if the client can restart directly with the right pak order, then we avoid an extra reloadEngine later.. ) + if ( idStr::Icmp( cvarSystem->GetCVarString( "fs_game_base" ), serverGameBase ) || + idStr::Icmp( cvarSystem->GetCVarString( "fs_game" ), serverGame ) ) { + common->Printf( "The server is running a different mod (%s-%s). Restarting..\n", serverGameBase, serverGame ); + cvarSystem->SetCVarString( "fs_game_base", serverGameBase ); + cvarSystem->SetCVarString( "fs_game", serverGame ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" ); + return; + } + + common->Printf( "received challenge response 0x%x from %s\n", serverChallenge, Sys_NetAdrToString( from ) ); + + // start sending connect packets instead of challenge request packets + clientState = CS_CONNECTING; + lastConnectTime = -9999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + serverAddress = from; +} + +/* +================== +idAsyncClient::ProcessConnectResponseMessage +================== +*/ +void idAsyncClient::ProcessConnectResponseMessage( const netadr_t from, const idBitMsg &msg ) { + int serverGameInitId, serverGameFrame, serverGameTime; + idDict serverSI; + + if ( clientState >= CS_CONNECTED ) { + common->Printf( "Duplicate connect received.\n" ); + return; + } + if ( clientState != CS_CONNECTING ) { + common->Printf( "Connect response packet while not connecting.\n" ); + return; + } + if ( !Sys_CompareNetAdrBase( from, serverAddress ) ) { + common->Printf( "Connect response from a different server.\n" ); + common->Printf( "%s should have been %s\n", Sys_NetAdrToString( from ), Sys_NetAdrToString( serverAddress ) ); + return; + } + + common->Printf( "received connect response from %s\n", Sys_NetAdrToString( from ) ); + + channel.Init( from, clientId ); + clientNum = msg.ReadLong(); + clientState = CS_CONNECTED; + lastPacketTime = -9999; + + serverGameInitId = msg.ReadLong(); + serverGameFrame = msg.ReadLong(); + serverGameTime = msg.ReadLong(); + msg.ReadDeltaDict( serverSI, NULL ); + + InitGame( serverGameInitId, serverGameFrame, serverGameTime, serverSI ); + + // load map + session->SetGUI( NULL, NULL ); + sessLocal.ExecuteMapChange(); + + clientPredictTime = clientPrediction = idMath::ClampInt( 0, idAsyncNetwork::clientMaxPrediction.GetInteger(), clientTime - lastConnectTime ); +} + +/* +================== +idAsyncClient::ProcessDisconnectMessage +================== +*/ +void idAsyncClient::ProcessDisconnectMessage( const netadr_t from, const idBitMsg &msg ) { + if ( clientState == CS_DISCONNECTED ) { + common->Printf( "Disconnect packet while not connected.\n" ); + return; + } + if ( !Sys_CompareNetAdrBase( from, serverAddress ) ) { + common->Printf( "Disconnect packet from unknown server.\n" ); + return; + } + session->Stop(); + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04320" ), NULL, true ); + session->StartMenu(); +} + +/* +================== +idAsyncClient::ProcessInfoResponseMessage +================== +*/ +void idAsyncClient::ProcessInfoResponseMessage( const netadr_t from, const idBitMsg &msg ) { + int i, protocol, index; + networkServer_t serverInfo; + bool verbose = false; + + if ( from.type == NA_LOOPBACK || cvarSystem->GetCVarBool( "developer" ) ) { + verbose = true; + } + + serverInfo.clients = 0; + serverInfo.adr = from; + serverInfo.challenge = msg.ReadLong(); // challenge + protocol = msg.ReadLong(); + if ( protocol != ASYNC_PROTOCOL_VERSION ) { + common->Printf( "server %s ignored - protocol %d.%d, expected %d.%d\n", Sys_NetAdrToString( serverInfo.adr ), protocol >> 16, protocol & 0xffff, ASYNC_PROTOCOL_MAJOR, ASYNC_PROTOCOL_MINOR ); + return; + } + msg.ReadDeltaDict( serverInfo.serverInfo, NULL ); + + if ( verbose ) { + common->Printf( "server IP = %s\n", Sys_NetAdrToString( serverInfo.adr ) ); + serverInfo.serverInfo.Print(); + } + for ( i = msg.ReadByte(); i < MAX_ASYNC_CLIENTS; i = msg.ReadByte() ) { + serverInfo.pings[ serverInfo.clients ] = msg.ReadShort(); + serverInfo.rate[ serverInfo.clients ] = msg.ReadLong(); + msg.ReadString( serverInfo.nickname[ serverInfo.clients ], MAX_NICKLEN ); + if ( verbose ) { + common->Printf( "client %2d: %s, ping = %d, rate = %d\n", i, serverInfo.nickname[ serverInfo.clients ], serverInfo.pings[ serverInfo.clients ], serverInfo.rate[ serverInfo.clients ] ); + } + serverInfo.clients++; + } + serverInfo.OSMask = msg.ReadLong(); + index = serverList.InfoResponse( serverInfo ); + + common->Printf( "%d: server %s - protocol %d.%d - %s\n", index, Sys_NetAdrToString( serverInfo.adr ), protocol >> 16, protocol & 0xffff, serverInfo.serverInfo.GetString( "si_name" ) ); +} + +/* +================== +idAsyncClient::ProcessPrintMessage +================== +*/ +void idAsyncClient::ProcessPrintMessage( const netadr_t from, const idBitMsg &msg ) { + char string[ MAX_STRING_CHARS ]; + int opcode; + int game_opcode = ALLOW_YES; + const char *retpass; + + opcode = msg.ReadLong(); + if ( opcode == SERVER_PRINT_GAMEDENY ) { + game_opcode = msg.ReadLong(); + } + ReadLocalizedServerString( msg, string, MAX_STRING_CHARS ); + common->Printf( "%s\n", string ); + guiNetMenu->SetStateString( "status", string ); + if ( opcode == SERVER_PRINT_GAMEDENY ) { + if ( game_opcode == ALLOW_BADPASS ) { + retpass = session->MessageBox( MSG_PROMPT, common->GetLanguageDict()->GetString ( "#str_04321" ), string, true, "passprompt_ok" ); + ClearPendingPackets(); + guiNetMenu->SetStateString( "status", common->GetLanguageDict()->GetString ( "#str_04322" )); + if ( retpass ) { + // #790 + cvarSystem->SetCVarString( "password", "" ); + cvarSystem->SetCVarString( "password", retpass ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + } + } else if ( game_opcode == ALLOW_NO ) { + session->MessageBox( MSG_OK, string, common->GetLanguageDict()->GetString ( "#str_04323" ), true ); + ClearPendingPackets(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + } + // ALLOW_NOTYET just keeps running as usual. The GUI has an abort button + } else if ( opcode == SERVER_PRINT_BADCHALLENGE && clientState >= CS_CONNECTING ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" ); + } +} + +/* +================== +idAsyncClient::ProcessServersListMessage +================== +*/ +void idAsyncClient::ProcessServersListMessage( const netadr_t from, const idBitMsg &msg ) { + if ( !Sys_CompareNetAdrBase( idAsyncNetwork::GetMasterAddress(), from ) ) { + common->DPrintf( "received a server list from %s - not a valid master\n", Sys_NetAdrToString( from ) ); + return; + } + while ( msg.GetRemainingData() ) { + int a,b,c,d; + a = msg.ReadByte(); b = msg.ReadByte(); c = msg.ReadByte(); d = msg.ReadByte(); + serverList.AddServer( serverList.Num(), va( "%i.%i.%i.%i:%i", a, b, c, d, msg.ReadShort() ) ); + } +} + +/* +================== +idAsyncClient::ProcessAuthKeyMessage +================== +*/ +void idAsyncClient::ProcessAuthKeyMessage( const netadr_t from, const idBitMsg &msg ) { + authKeyMsg_t authMsg; + char read_string[ MAX_STRING_CHARS ]; + const char *retkey; + authBadKeyStatus_t authBadStatus; + int key_index; + bool valid[ 2 ]; + idStr auth_msg; + + if ( clientState != CS_CONNECTING && !session->WaitingForGameAuth() ) { + common->Printf( "clientState != CS_CONNECTING, not waiting for game auth, authKey ignored\n" ); + return; + } + + authMsg = (authKeyMsg_t)msg.ReadByte(); + if ( authMsg == AUTHKEY_BADKEY ) { + valid[ 0 ] = valid[ 1 ] = true; + key_index = 0; + authBadStatus = (authBadKeyStatus_t)msg.ReadByte(); + switch ( authBadStatus ) { + case AUTHKEY_BAD_INVALID: + valid[ 0 ] = ( msg.ReadByte() == 1 ); + valid[ 1 ] = ( msg.ReadByte() == 1 ); + idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid ); + break; + case AUTHKEY_BAD_BANNED: + key_index = msg.ReadByte(); + auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 6 + key_index ) ); + auth_msg += "\n"; + auth_msg += common->GetLanguageDict()->GetString( "#str_04304" ); + valid[ key_index ] = false; + break; + case AUTHKEY_BAD_INUSE: + key_index = msg.ReadByte(); + auth_msg = common->GetLanguageDict()->GetString( va( "#str_0719%1d", 8 + key_index ) ); + auth_msg += "\n"; + auth_msg += common->GetLanguageDict()->GetString( "#str_04304" ); + valid[ key_index ] = false; + break; + case AUTHKEY_BAD_MSG: + // a general message explaining why this key is denied + // no specific use for this atm. let's not clear the keys either + msg.ReadString( read_string, MAX_STRING_CHARS ); + auth_msg = read_string; + break; + } + common->DPrintf( "auth deny: %s\n", auth_msg.c_str() ); + + // keys to be cleared. applies to both net connect and game auth + session->ClearCDKey( valid ); + + // get rid of the bad key - at least that's gonna annoy people who stole a fake key + if ( clientState == CS_CONNECTING ) { + while ( 1 ) { + // here we use the auth status message + retkey = session->MessageBox( MSG_CDKEY, auth_msg, common->GetLanguageDict()->GetString( "#str_04325" ), true ); + if ( retkey ) { + if ( session->CheckKey( retkey, true, valid ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" ); + } else { + // build a more precise message about the offline check failure + idAsyncNetwork::BuildInvalidKeyMsg( auth_msg, valid ); + session->MessageBox( MSG_OK, auth_msg.c_str(), common->GetLanguageDict()->GetString( "#str_04327" ), true ); + continue; + } + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + } + break; + } + } else { + // forward the auth status information to the session code + session->CDKeysAuthReply( false, auth_msg ); + } + } else { + msg.ReadString( read_string, MAX_STRING_CHARS ); + cvarSystem->SetCVarString( "com_guid", read_string ); + common->Printf( "guid set to %s\n", read_string ); + session->CDKeysAuthReply( true, NULL ); + } +} + +/* +================== +idAsyncClient::ProcessVersionMessage +================== +*/ +void idAsyncClient::ProcessVersionMessage( const netadr_t from, const idBitMsg &msg ) { + char string[ MAX_STRING_CHARS ]; + + if ( updateState != UPDATE_SENT ) { + common->Printf( "ProcessVersionMessage: version reply, != UPDATE_SENT\n" ); + return; + } + + common->Printf( "A new version is available\n" ); + msg.ReadString( string, MAX_STRING_CHARS ); + updateMSG = string; + updateDirectDownload = ( msg.ReadByte() != 0 ); + msg.ReadString( string, MAX_STRING_CHARS ); + updateURL = string; + updateMime = (dlMime_t)msg.ReadByte(); + msg.ReadString( string, MAX_STRING_CHARS ); + updateFallback = string; + updateState = UPDATE_READY; +} + +/* +================== +idAsyncClient::ValidatePureServerChecksums +================== +*/ +bool idAsyncClient::ValidatePureServerChecksums( const netadr_t from, const idBitMsg &msg ) { + int i, numChecksums, numMissingChecksums; + int inChecksums[ MAX_PURE_PAKS ]; + int inGamePakChecksum; + int missingChecksums[ MAX_PURE_PAKS ]; + int missingGamePakChecksum; + idBitMsg dlmsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + // read checksums + // pak checksums, in a 0-terminated list + numChecksums = 0; + do { + i = msg.ReadLong( ); + inChecksums[ numChecksums++ ] = i; + // just to make sure a broken message doesn't crash us + if ( numChecksums >= MAX_PURE_PAKS ) { + common->Warning( "MAX_PURE_PAKS ( %d ) exceeded in idAsyncClient::ProcessPureMessage\n", MAX_PURE_PAKS ); + return false; + } + } while ( i ); + inChecksums[ numChecksums ] = 0; + inGamePakChecksum = msg.ReadLong(); + + fsPureReply_t reply = fileSystem->SetPureServerChecksums( inChecksums, inGamePakChecksum, missingChecksums, &missingGamePakChecksum ); + switch ( reply ) { + case PURE_RESTART: + // need to restart the filesystem with a different pure configuration + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + // restart with the right FS configuration and get back to the server + clientState = CS_PURERESTART; + fileSystem->SetRestartChecksums( inChecksums, inGamePakChecksum ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" ); + return false; + case PURE_MISSING: { + + idStr checksums; + + i = 0; + while ( missingChecksums[ i ] ) { + checksums += va( "0x%x ", missingChecksums[ i++ ] ); + } + numMissingChecksums = i; + + if ( idAsyncNetwork::clientDownload.GetInteger() == 0 ) { + // never any downloads + idStr message = va( common->GetLanguageDict()->GetString( "#str_07210" ), Sys_NetAdrToString( from ) ); + + if ( numMissingChecksums > 0 ) { + message += va( common->GetLanguageDict()->GetString( "#str_06751" ), numMissingChecksums, checksums.c_str() ); + } + if ( missingGamePakChecksum ) { + message += va( common->GetLanguageDict()->GetString( "#str_06750" ), missingGamePakChecksum ); + } + + common->Printf( message ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + session->MessageBox( MSG_OK, message, common->GetLanguageDict()->GetString( "#str_06735" ), true ); + } else { + if ( clientState >= CS_CONNECTED ) { + // we are already connected, reconnect to negociate the paks in connectionless mode + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reconnect" ); + return false; + } + // ask the server to send back download info + common->DPrintf( "missing %d paks: %s\n", numMissingChecksums + ( missingGamePakChecksum ? 1 : 0 ), checksums.c_str() ); + if ( missingGamePakChecksum ) { + common->DPrintf( "game code pak: 0x%x\n", missingGamePakChecksum ); + } + // store the requested downloads + GetDownloadRequest( missingChecksums, numMissingChecksums, missingGamePakChecksum ); + // build the download request message + // NOTE: in a specific function? + dlmsg.Init( msgBuf, sizeof( msgBuf ) ); + dlmsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + dlmsg.WriteString( "downloadRequest" ); + dlmsg.WriteLong( serverChallenge ); + dlmsg.WriteShort( clientId ); + // used to make sure the server replies to the same download request + dlmsg.WriteLong( dlRequest ); + // special case the code pak - if we have a 0 checksum then we don't need to download it + dlmsg.WriteLong( missingGamePakChecksum ); + // 0-terminated list of missing paks + i = 0; + while ( missingChecksums[ i ] ) { + dlmsg.WriteLong( missingChecksums[ i++ ] ); + } + dlmsg.WriteLong( 0 ); + clientPort.SendPacket( from, dlmsg.GetData(), dlmsg.GetSize() ); + } + + return false; + } + case PURE_NODLL: + common->Printf( common->GetLanguageDict()->GetString( "#str_07211" ), Sys_NetAdrToString( from ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + return false; + default: + return true; + } + return true; +} + +/* +================== +idAsyncClient::ProcessPureMessage +================== +*/ +void idAsyncClient::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) { + idBitMsg outMsg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + int i; + int inChecksums[ MAX_PURE_PAKS ]; + int gamePakChecksum; + + if ( clientState != CS_CONNECTING ) { + common->Printf( "clientState != CS_CONNECTING, pure msg ignored\n" ); + return; + } + + if ( !ValidatePureServerChecksums( from, msg ) ) { + return; + } + + fileSystem->GetPureServerChecksums( inChecksums, -1, &gamePakChecksum ); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "pureClient" ); + outMsg.WriteLong( serverChallenge ); + outMsg.WriteShort( clientId ); + i = 0; + while ( inChecksums[ i ] ) { + outMsg.WriteLong( inChecksums[ i++ ] ); + } + outMsg.WriteLong( 0 ); + outMsg.WriteLong( gamePakChecksum ); + clientPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); +} + +/* +================== +idAsyncClient::ConnectionlessMessage +================== +*/ +void idAsyncClient::ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ) { + char string[MAX_STRING_CHARS*2]; // M. Quinn - Even Balance - PB packets can go beyond 1024 + + msg.ReadString( string, sizeof( string ) ); + + // info response from a server, are accepted from any source + if ( idStr::Icmp( string, "infoResponse" ) == 0 ) { + ProcessInfoResponseMessage( from, msg ); + return; + } + + // from master server: + if ( Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress( ) ) ) { + // server list + if ( idStr::Icmp( string, "servers" ) == 0 ) { + ProcessServersListMessage( from, msg ); + return; + } + + if ( idStr::Icmp( string, "authKey" ) == 0 ) { + ProcessAuthKeyMessage( from, msg ); + return; + } + + if ( idStr::Icmp( string, "newVersion" ) == 0 ) { + ProcessVersionMessage( from, msg ); + return; + } + } + + // ignore if not from the current/last server + if ( !Sys_CompareNetAdrBase( from, serverAddress ) && ( lastRconTime + 10000 < realTime || !Sys_CompareNetAdrBase( from, lastRconAddress ) ) ) { + common->DPrintf( "got message '%s' from bad source: %s\n", string, Sys_NetAdrToString( from ) ); + return; + } + + // challenge response from the server we are connecting to + if ( idStr::Icmp( string, "challengeResponse" ) == 0 ) { + ProcessChallengeResponseMessage( from, msg ); + return; + } + + // connect response from the server we are connecting to + if ( idStr::Icmp( string, "connectResponse" ) == 0 ) { + ProcessConnectResponseMessage( from, msg ); + return; + } + + // a disconnect message from the server, which will happen if the server + // dropped the connection but is still getting packets from this client + if ( idStr::Icmp( string, "disconnect" ) == 0 ) { + ProcessDisconnectMessage( from, msg ); + return; + } + + // print request from server + if ( idStr::Icmp( string, "print" ) == 0 ) { + ProcessPrintMessage( from, msg ); + return; + } + + // server pure list + if ( idStr::Icmp( string, "pureServer" ) == 0 ) { + ProcessPureMessage( from, msg ); + return; + } + + if ( idStr::Icmp( string, "downloadInfo" ) == 0 ) { + ProcessDownloadInfoMessage( from, msg ); + } + + if ( idStr::Icmp( string, "authrequired" ) == 0 ) { + // server telling us that he's expecting an auth mode connect, just in case we're trying to connect in LAN mode + if ( idAsyncNetwork::LANServer.GetBool() ) { + common->Warning( "server %s requests master authorization for this client. Turning off LAN mode\n", Sys_NetAdrToString( from ) ); + idAsyncNetwork::LANServer.SetBool( false ); + } + } + + common->DPrintf( "ignored message from %s: %s\n", Sys_NetAdrToString( from ), string ); +} + +/* +================= +idAsyncClient::ProcessMessage +================= +*/ +void idAsyncClient::ProcessMessage( const netadr_t from, idBitMsg &msg ) { + int id; + + id = msg.ReadShort(); + + // check for a connectionless packet + if ( id == CONNECTIONLESS_MESSAGE_ID ) { + ConnectionlessMessage( from, msg ); + return; + } + + if ( clientState < CS_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg.GetRemainingData() < 4 ) { + common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) ); + return; + } + + // is this a packet from the server + if ( !Sys_CompareNetAdrBase( from, channel.GetRemoteAddress() ) || id != serverId ) { + common->DPrintf( "%s: sequenced server packet without connection\n", Sys_NetAdrToString( from ) ); + return; + } + + if ( !channel.Process( from, clientTime, msg, serverMessageSequence ) ) { + return; // out of order, duplicated, fragment, etc. + } + + lastPacketTime = clientTime; + ProcessReliableServerMessages(); + ProcessUnreliableServerMessage( msg ); +} + +/* +================== +idAsyncClient::SetupConnection +================== +*/ +void idAsyncClient::SetupConnection( void ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( clientTime - lastConnectTime < SETUP_CONNECTION_RESEND_TIME ) { + return; + } + + if ( clientState == CS_CHALLENGING ) { + common->Printf( "sending challenge to %s\n", Sys_NetAdrToString( serverAddress ) ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "challenge" ); + msg.WriteLong( clientId ); + clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() ); + } else if ( clientState == CS_CONNECTING ) { + common->Printf( "sending connect to %s with challenge 0x%x\n", Sys_NetAdrToString( serverAddress ), serverChallenge ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "connect" ); + msg.WriteLong( ASYNC_PROTOCOL_VERSION ); +#if ID_FAKE_PURE + // fake win32 OS - might need to adapt depending on the case + msg.WriteShort( 0 ); +#else + msg.WriteShort( BUILD_OS_ID ); +#endif + msg.WriteLong( clientDataChecksum ); + msg.WriteLong( serverChallenge ); + msg.WriteShort( clientId ); + msg.WriteLong( cvarSystem->GetCVarInteger( "net_clientMaxRate" ) ); + msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) ); + msg.WriteString( cvarSystem->GetCVarString( "password" ) ); + // do not make the protocol depend on PB + msg.WriteShort( 0 ); + clientPort.SendPacket( serverAddress, msg.GetData(), msg.GetSize() ); + + if ( idAsyncNetwork::LANServer.GetBool() ) { + common->Printf( "net_LANServer is set, connecting in LAN mode\n" ); + } else { + // emit a cd key authorization request + // modified at protocol 1.37 for XP key addition + msg.BeginWriting(); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "clAuth" ); + msg.WriteLong( ASYNC_PROTOCOL_VERSION ); + msg.WriteNetadr( serverAddress ); + // if we don't have a com_guid, this will request a direct reply from auth with it + msg.WriteByte( cvarSystem->GetCVarString( "com_guid" )[0] ? 1 : 0 ); + // send the main key, and flag an extra byte to add XP key + msg.WriteString( session->GetCDKey() ); + clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() ); + } + } else { + return; + } + + lastConnectTime = clientTime; +} + +/* +================== +idAsyncClient::SendReliableGameMessage +================== +*/ +void idAsyncClient::SendReliableGameMessage( const idBitMsg &msg ) { + idBitMsg outMsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( clientState < CS_INGAME ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( CLIENT_RELIABLE_MESSAGE_GAME ); + outMsg.WriteData( msg.GetData(), msg.GetSize() ); + if ( !channel.SendReliableMessage( outMsg ) ) { + common->Error( "client->server reliable messages overflow\n" ); + } +} + +/* +================== +idAsyncClient::Idle +================== +*/ +void idAsyncClient::Idle( void ) { + // also need to read mouse for the connecting guis + usercmdGen->GetDirectUsercmd(); + + SendEmptyToServer(); +} + +/* +================== +idAsyncClient::UpdateTime +================== +*/ +int idAsyncClient::UpdateTime( int clamp ) { + int time, msec; + + time = Sys_Milliseconds(); + msec = idMath::ClampInt( 0, clamp, time - realTime ); + realTime = time; + clientTime += msec; + return msec; +} + +/* +================== +idAsyncClient::RunFrame +================== +*/ +void idAsyncClient::RunFrame( void ) { + const int userCmdMSec = common->GetUserCmdMSec(); + int msec, size; + bool newPacket; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + netadr_t from; + + msec = UpdateTime( 100 ); + + if ( !clientPort.GetPort() ) { + return; + } + + // handle ongoing pk4 downloads and patch downloads + HandleDownloads(); + + gameTimeResidual += msec; + + // spin in place processing incoming packets until enough time lapsed to run a new game frame + do { + + do { + + // blocking read with game time residual timeout + newPacket = clientPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), userCmdMSec - ( gameTimeResidual + clientPredictTime ) - 1 ); + if ( newPacket ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.SetSize( size ); + msg.BeginReading(); + ProcessMessage( from, msg ); + } + + msec = UpdateTime( 100 ); + gameTimeResidual += msec; + + } while( newPacket ); + + } while( gameTimeResidual + clientPredictTime < userCmdMSec ); + + // update server list + serverList.RunFrame(); + + if ( clientState == CS_DISCONNECTED ) { + usercmdGen->GetDirectUsercmd(); + gameTimeResidual = userCmdMSec - 1; + clientPredictTime = 0; + return; + } + + if ( clientState == CS_PURERESTART ) { + clientState = CS_DISCONNECTED; + Reconnect(); + gameTimeResidual = userCmdMSec - 1; + clientPredictTime = 0; + return; + } + + // if not connected setup a connection + if ( clientState < CS_CONNECTED ) { + // also need to read mouse for the connecting guis + usercmdGen->GetDirectUsercmd(); + SetupConnection(); + gameTimeResidual = userCmdMSec - 1; + clientPredictTime = 0; + return; + } + + if ( CheckTimeout() ) { + return; + } + + // if not yet in the game send empty messages to keep data flowing through the channel + if ( clientState < CS_INGAME ) { + Idle(); + gameTimeResidual = 0; + return; + } + + // check for user info changes + if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) { + game->ThrottleUserInfo( ); + SendUserInfoToServer( ); + game->SetUserInfo( clientNum, sessLocal.mapSpawnData.userInfo[ clientNum ], true ); + cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); + } + + if ( gameTimeResidual + clientPredictTime >= userCmdMSec ) { + lastFrameDelta = 0; + } + + // generate user commands for the predicted time + while ( gameTimeResidual + clientPredictTime >= userCmdMSec ) { + + // send the user commands of this client to the server + SendUsercmdsToServer(); + + // update time + gameFrame++; + gameTime += userCmdMSec; + gameTimeResidual -= userCmdMSec; + + // run from the snapshot up to the local game frame + while ( snapshotGameFrame < gameFrame ) { + + lastFrameDelta++; + + // duplicate usercmds for clients if no new ones are available + DuplicateUsercmds( snapshotGameFrame, snapshotGameTime ); + + // indicate the last prediction frame before a render + bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < userCmdMSec ); + + // run client prediction + gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame ); + + idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand ); + + snapshotGameFrame++; + snapshotGameTime += userCmdMSec; + } + } +} + +/* +================== +idAsyncClient::PacifierUpdate +================== +*/ +void idAsyncClient::PacifierUpdate( void ) { + if ( !IsActive() ) { + return; + } + realTime = Sys_Milliseconds(); + SendEmptyToServer( false, true ); +} + +/* +================== +idAsyncClient::SendVersionCheck +================== +*/ +void idAsyncClient::SendVersionCheck( bool fromMenu ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( updateState != UPDATE_NONE && !fromMenu ) { + common->DPrintf( "up-to-date check was already performed\n" ); + return; + } + + InitPort(); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "versionCheck" ); + msg.WriteLong( ASYNC_PROTOCOL_VERSION ); + msg.WriteShort( BUILD_OS_ID ); + msg.WriteString( cvarSystem->GetCVarString( "si_version" ) ); + msg.WriteString( cvarSystem->GetCVarString( "com_guid" ) ); + clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() ); + + common->DPrintf( "sent a version check request\n" ); + + updateState = UPDATE_SENT; + updateSentTime = clientTime; + showUpdateMessage = fromMenu; +} + +/* +================== +idAsyncClient::SendVersionDLUpdate + +sending those packets is not strictly necessary. just a way to tell the update server +about what is going on. allows the update server to have a more precise view of the overall +network load for the updates +================== +*/ +void idAsyncClient::SendVersionDLUpdate( int state ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "versionDL" ); + msg.WriteLong( ASYNC_PROTOCOL_VERSION ); + msg.WriteShort( state ); + clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() ); +} + +/* +================== +idAsyncClient::HandleDownloads +================== +*/ +void idAsyncClient::HandleDownloads( void ) { + + if ( updateState == UPDATE_SENT && clientTime > updateSentTime + 2000 ) { + // timing out on no reply + updateState = UPDATE_DONE; + if ( showUpdateMessage ) { + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04839" ), common->GetLanguageDict()->GetString ( "#str_04837" ), true ); + showUpdateMessage = false; + } + common->DPrintf( "No update available\n" ); + } else if ( backgroundDownload.completed ) { + // only enter these if the download slot is free + if ( updateState == UPDATE_READY ) { + // + if ( session->MessageBox( MSG_YESNO, updateMSG, common->GetLanguageDict()->GetString ( "#str_04330" ), true, "yes" )[0] ) { + if ( !updateDirectDownload ) { + sys->OpenURL( updateURL, true ); + updateState = UPDATE_DONE; + } else { + + // we're just creating the file at toplevel inside fs_savepath + updateURL.ExtractFileName( updateFile ); + idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->OpenFileWrite( updateFile ) ); + dltotal = 0; + dlnow = 0; + + backgroundDownload.completed = false; + backgroundDownload.opcode = DLTYPE_URL; + backgroundDownload.f = f; + backgroundDownload.url.status = DL_WAIT; + backgroundDownload.url.dlnow = 0; + backgroundDownload.url.dltotal = 0; + backgroundDownload.url.url = updateURL; + fileSystem->BackgroundDownload( &backgroundDownload ); + + updateState = UPDATE_DLING; + SendVersionDLUpdate( 0 ); + session->DownloadProgressBox( &backgroundDownload, va( "Downloading %s\n", updateFile.c_str() ) ); + updateState = UPDATE_DONE; + if ( backgroundDownload.url.status == DL_DONE ) { + SendVersionDLUpdate( 1 ); + idStr fullPath = f->GetFullPath(); + fileSystem->CloseFile( f ); + if ( session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString ( "#str_04331" ), common->GetLanguageDict()->GetString ( "#str_04332" ), true, "yes" )[0] ) { + if ( updateMime == FILE_EXEC ) { + sys->StartProcess( fullPath, true ); + } else { + sys->OpenURL( va( "file://%s", fullPath.c_str() ), true ); + } + } else { + session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString ( "#str_04333" ), fullPath.c_str() ), common->GetLanguageDict()->GetString ( "#str_04334" ), true ); + } + } else { + if ( backgroundDownload.url.dlerror[ 0 ] ) { + common->Warning( "update download failed. curl error: %s", backgroundDownload.url.dlerror ); + } + SendVersionDLUpdate( 2 ); + idStr name = f->GetName(); + fileSystem->CloseFile( f ); + fileSystem->RemoveFile( name ); + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04335" ), common->GetLanguageDict()->GetString ( "#str_04336" ), true ); + if ( updateFallback.Length() ) { + sys->OpenURL( updateFallback.c_str(), true ); + } else { + common->Printf( "no fallback URL\n" ); + } + } + } + } else { + updateState = UPDATE_DONE; + } + } else if ( dlList.Num() ) { + + int numPaks = dlList.Num(); + int pakCount = 1; + int progress_start, progress_end; + currentDlSize = 0; + + do { + + if ( dlList[ 0 ].url[ 0 ] == '\0' ) { + // ignore empty files + dlList.RemoveIndex( 0 ); + continue; + } + common->Printf( "start download for %s\n", dlList[ 0 ].url.c_str() ); + + idFile_Permanent *f = static_cast< idFile_Permanent *>( fileSystem->MakeTemporaryFile( ) ); + if ( !f ) { + common->Warning( "could not create temporary file" ); + dlList.Clear(); + return; + } + + backgroundDownload.completed = false; + backgroundDownload.opcode = DLTYPE_URL; + backgroundDownload.f = f; + backgroundDownload.url.status = DL_WAIT; + backgroundDownload.url.dlnow = 0; + backgroundDownload.url.dltotal = dlList[ 0 ].size; + backgroundDownload.url.url = dlList[ 0 ].url; + fileSystem->BackgroundDownload( &backgroundDownload ); + idStr dltitle; + // "Downloading %s" + sprintf( dltitle, common->GetLanguageDict()->GetString( "#str_07213" ), dlList[ 0 ].filename.c_str() ); + if ( numPaks > 1 ) { + dltitle += va( " (%d/%d)", pakCount, numPaks ); + } + if ( totalDlSize ) { + progress_start = (int)( (float)currentDlSize * 100.0f / (float)totalDlSize ); + progress_end = (int)( (float)( currentDlSize + dlList[ 0 ].size ) * 100.0f / (float)totalDlSize ); + } else { + progress_start = 0; + progress_end = 100; + } + session->DownloadProgressBox( &backgroundDownload, dltitle, progress_start, progress_end ); + if ( backgroundDownload.url.status == DL_DONE ) { + idFile *saveas; + const int CHUNK_SIZE = 1024 * 1024; + byte *buf; + int remainlen; + int readlen; + int retlen; + int checksum; + + common->Printf( "file downloaded\n" ); + idStr finalPath = cvarSystem->GetCVarString( "fs_savepath" ); + finalPath.AppendPath( dlList[ 0 ].filename ); + fileSystem->CreateOSPath( finalPath ); + // do the final copy ourselves so we do by small chunks in case the file is big + saveas = fileSystem->OpenExplicitFileWrite( finalPath ); + buf = (byte*)Mem_Alloc( CHUNK_SIZE ); + f->Seek( 0, FS_SEEK_END ); + remainlen = f->Tell(); + f->Seek( 0, FS_SEEK_SET ); + while ( remainlen ) { + readlen = Min( remainlen, CHUNK_SIZE ); + retlen = f->Read( buf, readlen ); + if ( retlen != readlen ) { + common->FatalError( "short read %d of %d in idFileSystem::HandleDownload", retlen, readlen ); + } + retlen = saveas->Write( buf, readlen ); + if ( retlen != readlen ) { + common->FatalError( "short write %d of %d in idFileSystem::HandleDownload", retlen, readlen ); + } + remainlen -= readlen; + } + fileSystem->CloseFile( f ); + fileSystem->CloseFile( saveas ); + common->Printf( "saved as %s\n", finalPath.c_str() ); + Mem_Free( buf ); + + // add that file to our paks list + checksum = fileSystem->AddZipFile( dlList[ 0 ].filename ); + + // verify the checksum to be what the server says + if ( !checksum || checksum != dlList[ 0 ].checksum ) { + // "pak is corrupted ( checksum 0x%x, expected 0x%x )" + session->MessageBox( MSG_OK, va( common->GetLanguageDict()->GetString( "#str_07214" ) , checksum, dlList[0].checksum ), "Download failed", true ); + fileSystem->RemoveFile( dlList[ 0 ].filename ); + dlList.Clear(); + return; + } + + currentDlSize += dlList[ 0 ].size; + + } else { + common->Warning( "download failed: %s", dlList[ 0 ].url.c_str() ); + if ( backgroundDownload.url.dlerror[ 0 ] ) { + common->Warning( "curl error: %s", backgroundDownload.url.dlerror ); + } + // "The download failed or was cancelled" + // "Download failed" + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07215" ), common->GetLanguageDict()->GetString( "#str_07216" ), true ); + dlList.Clear(); + return; + } + + pakCount++; + dlList.RemoveIndex( 0 ); + } while ( dlList.Num() ); + + // all downloads successful - do the dew + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reconnect\n" ); + } + } +} + +/* +=============== +idAsyncClient::SendAuthCheck +=============== +*/ +bool idAsyncClient::SendAuthCheck( const char *cdkey, const char *xpkey ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + msg.WriteString( "gameAuth" ); + msg.WriteLong( ASYNC_PROTOCOL_VERSION ); + msg.WriteByte( cdkey ? 1 : 0 ); + msg.WriteString( cdkey ? cdkey : "" ); + msg.WriteByte( xpkey ? 1 : 0 ); + msg.WriteString( xpkey ? xpkey : "" ); + InitPort(); + clientPort.SendPacket( idAsyncNetwork::GetMasterAddress(), msg.GetData(), msg.GetSize() ); + return true; +} + +/* +=============== +idAsyncClient::CheckTimeout +=============== +*/ +bool idAsyncClient::CheckTimeout( void ) { + if ( lastPacketTime > 0 && ( lastPacketTime + idAsyncNetwork::clientServerTimeout.GetInteger()*1000 < clientTime ) ) { + session->StopBox(); + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04328" ), common->GetLanguageDict()->GetString ( "#str_04329" ), true ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + return true; + } + return false; +} + +/* +=============== +idAsyncClient::ProcessDownloadInfoMessage +=============== +*/ +void idAsyncClient::ProcessDownloadInfoMessage( const netadr_t from, const idBitMsg &msg ) { + char buf[ MAX_STRING_CHARS ]; + int srvDlRequest = msg.ReadLong(); + int infoType = msg.ReadByte(); + int pakDl; + int pakIndex; + + pakDlEntry_t entry; + bool gotAllFiles = true; + idStr sizeStr; + bool gotGame = false; + + if ( dlRequest == -1 || srvDlRequest != dlRequest ) { + common->Warning( "bad download id from server, ignored" ); + return; + } + // mark the dlRequest as dead now whatever how we process it + dlRequest = -1; + + if ( infoType == SERVER_DL_REDIRECT ) { + msg.ReadString( buf, MAX_STRING_CHARS ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + // "You are missing required pak files to connect to this server.\nThe server gave a web page though:\n%s\nDo you want to go there now?" + // "Missing required files" + if ( session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07217" ), buf ), + common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) { + sys->OpenURL( buf, true ); + } + } else if ( infoType == SERVER_DL_LIST ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + if ( dlList.Num() ) { + common->Warning( "tried to process a download list while already busy downloading things" ); + return; + } + // read the URLs, check against what we requested, prompt for download + pakIndex = -1; + totalDlSize = 0; + do { + pakIndex++; + pakDl = msg.ReadByte(); + if ( pakDl == SERVER_PAK_YES ) { + if ( pakIndex == 0 ) { + gotGame = true; + } + msg.ReadString( buf, MAX_STRING_CHARS ); + entry.filename = buf; + msg.ReadString( buf, MAX_STRING_CHARS ); + entry.url = buf; + entry.size = msg.ReadLong(); + // checksums are not transmitted, we read them from the dl request we sent + entry.checksum = dlChecksums[ pakIndex ]; + totalDlSize += entry.size; + dlList.Append( entry ); + common->Printf( "download %s from %s ( 0x%x )\n", entry.filename.c_str(), entry.url.c_str(), entry.checksum ); + } else if ( pakDl == SERVER_PAK_NO ) { + msg.ReadString( buf, MAX_STRING_CHARS ); + entry.filename = buf; + entry.url = ""; + entry.size = 0; + entry.checksum = 0; + dlList.Append( entry ); + // first pak is game pak, only fail it if we actually requested it + if ( pakIndex != 0 || dlChecksums[ 0 ] != 0 ) { + common->Printf( "no download offered for %s ( 0x%x )\n", entry.filename.c_str(), dlChecksums[ pakIndex ] ); + gotAllFiles = false; + } + } else { + assert( pakDl == SERVER_PAK_END ); + } + } while ( pakDl != SERVER_PAK_END ); + if ( dlList.Num() < dlCount ) { + common->Printf( "%d files were ignored by the server\n", dlCount - dlList.Num() ); + gotAllFiles = false; + } + sizeStr.BestUnit( "%.2f", totalDlSize, MEASURE_SIZE ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + if ( totalDlSize == 0 ) { + // was no downloadable stuff for us + // "Can't connect to the pure server: no downloads offered" + // "Missing required files" + dlList.Clear(); + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07219" ), common->GetLanguageDict()->GetString( "#str_07218" ), true ); + return; + } + bool asked = false; + if ( gotGame ) { + asked = true; + // "You need to download game code to connect to this server. Are you sure? You should only answer yes if you trust the server administrators." + // "Missing game binaries" + if ( !session->MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString( "#str_07220" ), common->GetLanguageDict()->GetString( "#str_07221" ), true, "yes" )[ 0 ] ) { + dlList.Clear(); + return; + } + } + if ( !gotAllFiles ) { + asked = true; + // "The server only offers to download some of the files required to connect ( %s ). Download anyway?" + // "Missing required files" + if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07222" ), sizeStr.c_str() ), + common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) { + dlList.Clear(); + return; + } + } + if ( !asked && idAsyncNetwork::clientDownload.GetInteger() == 1 ) { + // "You need to download some files to connect to this server ( %s ), proceed?" + // "Missing required files" + if ( !session->MessageBox( MSG_YESNO, va( common->GetLanguageDict()->GetString( "#str_07224" ), sizeStr.c_str() ), + common->GetLanguageDict()->GetString( "#str_07218" ), true, "yes" )[ 0 ] ) { + dlList.Clear(); + return; + } + } + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + // "You are missing some files to connect to this server, and the server doesn't provide downloads." + // "Missing required files" + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07223" ), common->GetLanguageDict()->GetString( "#str_07218" ), true ); + } +} + +/* +=============== +idAsyncClient::GetDownloadRequest +=============== +*/ +int idAsyncClient::GetDownloadRequest( const int checksums[ MAX_PURE_PAKS ], int count, int gamePakChecksum ) { + assert( !checksums[ count ] ); // 0-terminated + if ( memcmp( dlChecksums + 1, checksums, sizeof( int ) * count ) || gamePakChecksum != dlChecksums[ 0 ] ) { + idRandom newreq; + + dlChecksums[ 0 ] = gamePakChecksum; + memcpy( dlChecksums + 1, checksums, sizeof( int ) * MAX_PURE_PAKS ); + + newreq.SetSeed( Sys_Milliseconds() ); + dlRequest = newreq.RandomInt(); + dlCount = count + ( gamePakChecksum ? 1 : 0 ); + return dlRequest; + } + // this is the same dlRequest, we haven't heard from the server. keep the same id + return dlRequest; +} diff --git a/src/framework/async/asyncnetwork.cpp b/src/framework/async/asyncnetwork.cpp new file mode 100644 index 0000000..c6a1498 --- /dev/null +++ b/src/framework/async/asyncnetwork.cpp @@ -0,0 +1,500 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AsyncNetwork.h" + +idAsyncServer idAsyncNetwork::server; +idAsyncClient idAsyncNetwork::client; + +idCVar idAsyncNetwork::verbose( "net_verbose", "0", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "1 = verbose output, 2 = even more verbose output", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar idAsyncNetwork::allowCheats( "net_allowCheats", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_NETWORKSYNC, "Allow cheats in network game" ); +#ifdef ID_DEDICATED +// dedicated executable can only have a value of 1 for net_serverDedicated +idCVar idAsyncNetwork::serverDedicated( "net_serverDedicated", "1", CVAR_SERVERINFO | CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_ROM, "" ); +#else +idCVar idAsyncNetwork::serverDedicated( "net_serverDedicated", "0", CVAR_SERVERINFO | CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "1 = text console dedicated server, 2 = graphical dedicated server", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +#endif +idCVar idAsyncNetwork::serverSnapshotDelay( "net_serverSnapshotDelay", "50", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "delay between snapshots in milliseconds" ); +idCVar idAsyncNetwork::serverMaxClientRate( "net_serverMaxClientRate", "16000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE | CVAR_NOCHEAT, "maximum rate to a client in bytes/sec" ); +idCVar idAsyncNetwork::clientMaxRate( "net_clientMaxRate", "16000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE | CVAR_NOCHEAT, "maximum rate requested by client from server in bytes/sec" ); +idCVar idAsyncNetwork::serverMaxUsercmdRelay( "net_serverMaxUsercmdRelay", "5", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of usercmds from other clients the server relays to a client", 1, MAX_USERCMD_RELAY, idCmdSystem::ArgCompletion_Integer<1,MAX_USERCMD_RELAY> ); +idCVar idAsyncNetwork::serverZombieTimeout( "net_serverZombieTimeout", "5", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "disconnected client timeout in seconds" ); +idCVar idAsyncNetwork::serverClientTimeout( "net_serverClientTimeout", "40", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "client time out in seconds" ); +idCVar idAsyncNetwork::clientServerTimeout( "net_clientServerTimeout", "40", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "server time out in seconds" ); +idCVar idAsyncNetwork::serverDrawClient( "net_serverDrawClient", "-1", CVAR_SYSTEM | CVAR_INTEGER, "number of client for which to draw view on server" ); +idCVar idAsyncNetwork::serverRemoteConsolePassword( "net_serverRemoteConsolePassword", "", CVAR_SYSTEM | CVAR_NOCHEAT, "remote console password" ); +idCVar idAsyncNetwork::clientPrediction( "net_clientPrediction", "16", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "additional client side prediction in milliseconds" ); +idCVar idAsyncNetwork::clientMaxPrediction( "net_clientMaxPrediction", "1000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." ); +idCVar idAsyncNetwork::clientUsercmdBackup( "net_clientUsercmdBackup", "5", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "number of usercmds to resend" ); +idCVar idAsyncNetwork::clientRemoteConsoleAddress( "net_clientRemoteConsoleAddress", "localhost", CVAR_SYSTEM | CVAR_NOCHEAT, "remote console address" ); +idCVar idAsyncNetwork::clientRemoteConsolePassword( "net_clientRemoteConsolePassword", "", CVAR_SYSTEM | CVAR_NOCHEAT, "remote console password" ); +idCVar idAsyncNetwork::master0( "net_master0", IDNET_HOST ":" IDNET_MASTER_PORT, CVAR_SYSTEM | CVAR_ROM, "idnet master server address" ); +idCVar idAsyncNetwork::master1( "net_master1", "", CVAR_SYSTEM | CVAR_ARCHIVE, "1st master server address" ); +idCVar idAsyncNetwork::master2( "net_master2", "", CVAR_SYSTEM | CVAR_ARCHIVE, "2nd master server address" ); +idCVar idAsyncNetwork::master3( "net_master3", "", CVAR_SYSTEM | CVAR_ARCHIVE, "3rd master server address" ); +idCVar idAsyncNetwork::master4( "net_master4", "", CVAR_SYSTEM | CVAR_ARCHIVE, "4th master server address" ); +idCVar idAsyncNetwork::LANServer( "net_LANServer", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_NOCHEAT, "config LAN games only - affects clients and servers" ); +idCVar idAsyncNetwork::serverReloadEngine( "net_serverReloadEngine", "0", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "perform a full reload on next map restart (including flushing referenced pak files) - decreased if > 0" ); +idCVar idAsyncNetwork::serverAllowServerMod( "net_serverAllowServerMod", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_NOCHEAT, "allow server-side mods" ); +idCVar idAsyncNetwork::idleServer( "si_idleServer", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_INIT | CVAR_SERVERINFO, "game clients are idle" ); +idCVar idAsyncNetwork::clientDownload( "net_clientDownload", "1", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE, "client pk4 downloads policy: 0 - never, 1 - ask, 2 - always (will still prompt for binary code)" ); + +int idAsyncNetwork::realTime; +master_t idAsyncNetwork::masters[ MAX_MASTER_SERVERS ]; + +/* +================== +idAsyncNetwork::idAsyncNetwork +================== +*/ +idAsyncNetwork::idAsyncNetwork( void ) { +} + +/* +================== +idAsyncNetwork::Init +================== +*/ +void idAsyncNetwork::Init( void ) { + + realTime = 0; + + memset( masters, 0, sizeof( masters ) ); + masters[0].var = &master0; + masters[1].var = &master1; + masters[2].var = &master2; + masters[3].var = &master3; + masters[4].var = &master4; + +#ifndef ID_DEMO_BUILD + cmdSystem->AddCommand( "spawnServer", SpawnServer_f, CMD_FL_SYSTEM, "spawns a server", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "nextMap", NextMap_f, CMD_FL_SYSTEM, "loads the next map on the server" ); + cmdSystem->AddCommand( "connect", Connect_f, CMD_FL_SYSTEM, "connects to a server" ); + cmdSystem->AddCommand( "reconnect", Reconnect_f, CMD_FL_SYSTEM, "reconnect to the last server we tried to connect to" ); + cmdSystem->AddCommand( "serverInfo", GetServerInfo_f, CMD_FL_SYSTEM, "shows server info" ); + cmdSystem->AddCommand( "LANScan", GetLANServers_f, CMD_FL_SYSTEM, "scans LAN for servers" ); + cmdSystem->AddCommand( "listServers", ListServers_f, CMD_FL_SYSTEM, "lists scanned servers" ); + cmdSystem->AddCommand( "rcon", RemoteConsole_f, CMD_FL_SYSTEM, "sends remote console command to server" ); + cmdSystem->AddCommand( "heartbeat", Heartbeat_f, CMD_FL_SYSTEM, "send a heartbeat to the the master servers" ); + cmdSystem->AddCommand( "kick", Kick_f, CMD_FL_SYSTEM, "kick a client by connection number" ); + cmdSystem->AddCommand( "checkNewVersion", CheckNewVersion_f, CMD_FL_SYSTEM, "check if a new version of the game is available" ); + cmdSystem->AddCommand( "updateUI", UpdateUI_f, CMD_FL_SYSTEM, "internal - cause a sync down of game-modified userinfo" ); +#endif +} + +/* +================== +idAsyncNetwork::GetMasterAddress +================== +*/ +netadr_t idAsyncNetwork::GetMasterAddress( void ) { + netadr_t ret; + GetMasterAddress( 0, ret ); + return masters[ 0 ].address; +} + +/* +================== +idAsyncNetwork::GetMasterAddress +================== +*/ +bool idAsyncNetwork::GetMasterAddress( int index, netadr_t &adr ) { + if ( !masters[ index ].var ) { + return false; + } + if ( masters[ index ].var->GetString()[0] == '\0' ) { + return false; + } + if ( !masters[ index ].resolved || masters[ index ].var->IsModified() ) { + masters[ index ].var->ClearModified(); + if ( !Sys_StringToNetAdr( masters[ index ].var->GetString(), &masters[ index ].address, true ) ) { + common->Printf( "Failed to resolve master%d: %s\n", index, masters[ index ].var->GetString() ); + memset( &masters[ index ].address, 0, sizeof( netadr_t ) ); + masters[ index ].resolved = true; + return false; + } + if ( masters[ index ].address.port == 0 ) { + masters[ index ].address.port = atoi( IDNET_MASTER_PORT ); + } + masters[ index ].resolved = true; + } + adr = masters[ index ].address; + return true; +} + +/* +================== +idAsyncNetwork::Shutdown +================== +*/ +void idAsyncNetwork::Shutdown( void ) { + client.serverList.Shutdown(); + client.DisconnectFromServer(); + client.ClearServers(); + client.ClosePort(); + server.Kill(); + server.ClosePort(); +} + +/* +================== +idAsyncNetwork::RunFrame +================== +*/ +void idAsyncNetwork::RunFrame( void ) { + if ( console->Active() ) { + Sys_GrabMouseCursor( false ); + usercmdGen->InhibitUsercmd( INHIBIT_ASYNC, true ); + } else { + Sys_GrabMouseCursor( true ); + usercmdGen->InhibitUsercmd( INHIBIT_ASYNC, false ); + } + client.RunFrame(); + server.RunFrame(); +} + +/* +================== +idAsyncNetwork::WriteUserCmdDelta +================== +*/ +void idAsyncNetwork::WriteUserCmdDelta( idBitMsg &msg, const usercmd_t &cmd, const usercmd_t *base ) { + if ( base ) { + msg.WriteDeltaLongCounter( base->gameTime, cmd.gameTime ); + msg.WriteDeltaByte( base->buttons, cmd.buttons ); + msg.WriteDeltaShort( base->mx, cmd.mx ); + msg.WriteDeltaShort( base->my, cmd.my ); + msg.WriteDeltaChar( base->forwardmove, cmd.forwardmove ); + msg.WriteDeltaChar( base->rightmove, cmd.rightmove ); + msg.WriteDeltaChar( base->upmove, cmd.upmove ); + msg.WriteDeltaShort( base->angles[0], cmd.angles[0] ); + msg.WriteDeltaShort( base->angles[1], cmd.angles[1] ); + msg.WriteDeltaShort( base->angles[2], cmd.angles[2] ); + return; + } + + msg.WriteLong( cmd.gameTime ); + msg.WriteByte( cmd.buttons ); + msg.WriteShort( cmd.mx ); + msg.WriteShort( cmd.my ); + msg.WriteChar( cmd.forwardmove ); + msg.WriteChar( cmd.rightmove ); + msg.WriteChar( cmd.upmove ); + msg.WriteShort( cmd.angles[0] ); + msg.WriteShort( cmd.angles[1] ); + msg.WriteShort( cmd.angles[2] ); +} + +/* +================== +idAsyncNetwork::ReadUserCmdDelta +================== +*/ +void idAsyncNetwork::ReadUserCmdDelta( const idBitMsg &msg, usercmd_t &cmd, const usercmd_t *base ) { + memset( &cmd, 0, sizeof( cmd ) ); + + if ( base ) { + cmd.gameTime = msg.ReadDeltaLongCounter( base->gameTime ); + cmd.buttons = msg.ReadDeltaByte( base->buttons ); + cmd.mx = msg.ReadDeltaShort( base->mx ); + cmd.my = msg.ReadDeltaShort( base->my ); + cmd.forwardmove = msg.ReadDeltaChar( base->forwardmove ); + cmd.rightmove = msg.ReadDeltaChar( base->rightmove ); + cmd.upmove = msg.ReadDeltaChar( base->upmove ); + cmd.angles[0] = msg.ReadDeltaShort( base->angles[0] ); + cmd.angles[1] = msg.ReadDeltaShort( base->angles[1] ); + cmd.angles[2] = msg.ReadDeltaShort( base->angles[2] ); + return; + } + + cmd.gameTime = msg.ReadLong(); + cmd.buttons = msg.ReadByte(); + cmd.mx = msg.ReadShort(); + cmd.my = msg.ReadShort(); + cmd.forwardmove = msg.ReadChar(); + cmd.rightmove = msg.ReadChar(); + cmd.upmove = msg.ReadChar(); + cmd.angles[0] = msg.ReadShort(); + cmd.angles[1] = msg.ReadShort(); + cmd.angles[2] = msg.ReadShort(); +} + +/* +================== +idAsyncNetwork::DuplicateUsercmd +================== +*/ +bool idAsyncNetwork::DuplicateUsercmd( const usercmd_t &previousUserCmd, usercmd_t ¤tUserCmd, int frame, int time ) { + + if ( currentUserCmd.gameTime <= previousUserCmd.gameTime ) { + + currentUserCmd = previousUserCmd; + currentUserCmd.gameFrame = frame; + currentUserCmd.gameTime = time; + currentUserCmd.duplicateCount++; + + if ( currentUserCmd.duplicateCount > MAX_USERCMD_DUPLICATION ) { + currentUserCmd.buttons &= ~BUTTON_ATTACK; + if ( abs( currentUserCmd.forwardmove ) > 2 ) currentUserCmd.forwardmove >>= 1; + if ( abs( currentUserCmd.rightmove ) > 2 ) currentUserCmd.rightmove >>= 1; + if ( abs( currentUserCmd.upmove ) > 2 ) currentUserCmd.upmove >>= 1; + } + + return true; + } + return false; +} + +/* +================== +idAsyncNetwork::UsercmdInputChanged +================== +*/ +bool idAsyncNetwork::UsercmdInputChanged( const usercmd_t &previousUserCmd, const usercmd_t ¤tUserCmd ) { + return previousUserCmd.buttons != currentUserCmd.buttons || + previousUserCmd.forwardmove != currentUserCmd.forwardmove || + previousUserCmd.rightmove != currentUserCmd.rightmove || + previousUserCmd.upmove != currentUserCmd.upmove || + previousUserCmd.angles[0] != currentUserCmd.angles[0] || + previousUserCmd.angles[1] != currentUserCmd.angles[1] || + previousUserCmd.angles[2] != currentUserCmd.angles[2]; +} + +/* +================== +idAsyncNetwork::SpawnServer_f +================== +*/ +void idAsyncNetwork::SpawnServer_f( const idCmdArgs &args ) { + + if(args.Argc() > 1) { + cvarSystem->SetCVarString("si_map", args.Argv(1)); + } + + // don't let a server spawn with singleplayer game type - it will crash + if ( idStr::Icmp( cvarSystem->GetCVarString( "si_gameType" ), "singleplayer" ) == 0 ) { + cvarSystem->SetCVarString( "si_gameType", "deathmatch" ); + } + // make sure the current system state is compatible with net_serverDedicated + switch ( cvarSystem->GetCVarInteger( "net_serverDedicated" ) ) { + case 0: + case 2: + if ( !renderSystem->IsOpenGLRunning() ) { + common->Warning( "OpenGL is not running, net_serverDedicated == %d", cvarSystem->GetCVarInteger( "net_serverDedicated" ) ); + } + break; + case 1: + if ( renderSystem->IsOpenGLRunning() ) { + sys->ShowConsole( 1, false ); + renderSystem->ShutdownOpenGL(); + } + soundSystem->SetMute( true ); + soundSystem->ShutdownHW(); + break; + } + // use serverMapRestart if we already have a running server + if ( server.IsActive() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "serverMapRestart" ); + } else { + server.Spawn(); + } +} + +/* +================== +idAsyncNetwork::NextMap_f +================== +*/ +void idAsyncNetwork::NextMap_f( const idCmdArgs &args ) { + server.ExecuteMapChange(); +} + +/* +================== +idAsyncNetwork::Connect_f +================== +*/ +void idAsyncNetwork::Connect_f( const idCmdArgs &args ) { + if ( server.IsActive() ) { + common->Printf( "already running a server\n" ); + return; + } + if ( args.Argc() != 2 ) { + common->Printf( "USAGE: connect \n" ); + return; + } + client.ConnectToServer( args.Argv( 1 ) ); +} + +/* +================== +idAsyncNetwork::Reconnect_f +================== +*/ +void idAsyncNetwork::Reconnect_f( const idCmdArgs &args ) { + client.Reconnect(); +} + +/* +================== +idAsyncNetwork::GetServerInfo_f +================== +*/ +void idAsyncNetwork::GetServerInfo_f( const idCmdArgs &args ) { + client.GetServerInfo( args.Argv( 1 ) ); +} + +/* +================== +idAsyncNetwork::GetLANServers_f +================== +*/ +void idAsyncNetwork::GetLANServers_f( const idCmdArgs &args ) { + client.GetLANServers(); +} + +/* +================== +idAsyncNetwork::ListServers_f +================== +*/ +void idAsyncNetwork::ListServers_f( const idCmdArgs &args ) { + client.ListServers(); +} + +/* +================== +idAsyncNetwork::RemoteConsole_f +================== +*/ +void idAsyncNetwork::RemoteConsole_f( const idCmdArgs &args ) { + client.RemoteConsole( args.Args() ); +} + +/* +================== +idAsyncNetwork::Heartbeat_f +================== +*/ +void idAsyncNetwork::Heartbeat_f( const idCmdArgs &args ) { + if ( !server.IsActive() ) { + common->Printf( "server is not running\n" ); + return; + } + server.MasterHeartbeat( true ); +} + +/* +================== +idAsyncNetwork::Kick_f +================== +*/ +void idAsyncNetwork::Kick_f( const idCmdArgs &args ) { + idStr clientId; + int iclient; + + if ( !server.IsActive() ) { + common->Printf( "server is not running\n" ); + return; + } + + clientId = args.Argv( 1 ); + if ( !clientId.IsNumeric() ) { + common->Printf( "usage: kick \n" ); + return; + } + iclient = atoi( clientId ); + + if ( server.GetLocalClientNum() == iclient ) { + common->Printf( "can't kick the host\n" ); + return; + } + + server.DropClient( iclient, "#str_07134" ); +} + +/* +================== +idAsyncNetwork::GetNETServers +================== +*/ +void idAsyncNetwork::GetNETServers( ) { + client.GetNETServers(); +} + +/* +================== +idAsyncNetwork::CheckNewVersion_f +================== +*/ +void idAsyncNetwork::CheckNewVersion_f( const idCmdArgs &args ) { + client.SendVersionCheck(); +} + +/* +================== +idAsyncNetwork::ExecuteSessionCommand +================== +*/ +void idAsyncNetwork::ExecuteSessionCommand( const char *sessCmd ) { + if ( sessCmd[ 0 ] ) { + if ( !idStr::Icmp( sessCmd, "game_startmenu" ) ) { + session->SetGUI( game->StartMenu(), NULL ); + } + } +} + +/* +================= +idAsyncNetwork::UpdateUI_f +================= +*/ +void idAsyncNetwork::UpdateUI_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Warning( "idAsyncNetwork::UpdateUI_f: wrong arguments\n" ); + return; + } + if ( !server.IsActive() ) { + common->Warning( "idAsyncNetwork::UpdateUI_f: server is not active\n" ); + return; + } + int clientNum = atoi( args.Args( 1 ) ); + server.UpdateUI( clientNum ); +} + +/* +=============== +idAsyncNetwork::BuildInvalidKeyMsg +=============== +*/ +void idAsyncNetwork::BuildInvalidKeyMsg( idStr &msg, bool valid ) { + if ( !valid ) { + msg += common->GetLocalizedString( "#str_107194" ); + } + msg += "\n"; + msg += common->GetLocalizedString( "#str_104304" ); +} diff --git a/src/framework/async/asyncserver.cpp b/src/framework/async/asyncserver.cpp new file mode 100644 index 0000000..2886b21 --- /dev/null +++ b/src/framework/async/asyncserver.cpp @@ -0,0 +1,2852 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AsyncNetwork.h" + +#include "../Session_local.h" + +const int MIN_RECONNECT_TIME = 2000; +const int EMPTY_RESEND_TIME = 500; +const int PING_RESEND_TIME = 500; +const int NOINPUT_IDLE_TIME = 30000; + +const int HEARTBEAT_MSEC = 5*60*1000; + +// must be kept in sync with authReplyMsg_t +const char* authReplyMsg[] = { + // "Waiting for authorization", + "#str_07204", + // "Client unknown to auth", + "#str_07205", + // "Access denied - CD Key in use", + "#str_07206", + // "Auth custom message", // placeholder - we propagate a message from the master + "#str_07207", + // "Authorize Server - Waiting for client" + "#str_07208" +}; + +const char* authReplyStr[] = { + "AUTH_NONE", + "AUTH_OK", + "AUTH_WAIT", + "AUTH_DENY" +}; + +/* +================== +idAsyncServer::idAsyncServer +================== +*/ +idAsyncServer::idAsyncServer( void ) { + int i; + + active = false; + realTime = 0; + serverTime = 0; + serverId = 0; + serverDataChecksum = 0; + localClientNum = -1; + gameInitId = 0; + gameFrame = 0; + gameTime = 0; + gameTimeResidual = 0; + memset( challenges, 0, sizeof( challenges ) ); + memset( userCmds, 0, sizeof( userCmds ) ); + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + ClearClient( i ); + } + serverReloadingEngine = false; + nextHeartbeatTime = 0; + nextAsyncStatsTime = 0; + noRconOutput = true; + lastAuthTime = 0; + + memset( stats_outrate, 0, sizeof( stats_outrate ) ); + stats_current = 0; + stats_average_sum = 0; + stats_max = 0; + stats_max_index = 0; +} + +/* +================== +idAsyncServer::InitPort +================== +*/ +bool idAsyncServer::InitPort( void ) { + int lastPort; + + // if this is the first time we have spawned a server, open the UDP port + if ( !serverPort.GetPort() ) { + if ( cvarSystem->GetCVarInteger( "net_port" ) != 0 ) { + if ( !serverPort.InitForPort( cvarSystem->GetCVarInteger( "net_port" ) ) ) { + common->Printf( "Unable to open server on port %d (net_port)\n", cvarSystem->GetCVarInteger( "net_port" ) ); + return false; + } + } else { + // scan for multiple ports, in case other servers are running on this IP already + for ( lastPort = 0; lastPort < NUM_SERVER_PORTS; lastPort++ ) { + if ( serverPort.InitForPort( PORT_SERVER + lastPort ) ) { + break; + } + } + if ( lastPort >= NUM_SERVER_PORTS ) { + common->Printf( "Unable to open server network port.\n" ); + return false; + } + } + } + + return true; +} + +/* +================== +idAsyncServer::ClosePort +================== +*/ +void idAsyncServer::ClosePort( void ) { + int i; + + serverPort.Close(); + for ( i = 0; i < MAX_CHALLENGES; i++ ) { + challenges[ i ].authReplyPrint.Clear(); + } +} + +/* +================== +idAsyncServer::Spawn +================== +*/ +void idAsyncServer::Spawn( void ) { + int i, size; + byte msgBuf[MAX_MESSAGE_SIZE]; + netadr_t from; + + // shutdown any current game + session->Stop(); + + if ( active ) { + return; + } + + if ( !InitPort() ) { + return; + } + + // trash any currently pending packets + while( serverPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) { + } + + // reset cheats cvars + if ( !idAsyncNetwork::allowCheats.GetBool() ) { + cvarSystem->ResetFlaggedVariables( CVAR_CHEAT ); + } + + memset( challenges, 0, sizeof( challenges ) ); + memset( userCmds, 0, sizeof( userCmds ) ); + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + ClearClient( i ); + } + + common->Printf( "Server spawned on port %i.\n", serverPort.GetPort() ); + + // calculate a checksum on some of the essential data used + serverDataChecksum = declManager->GetChecksum(); + + // get a pseudo random server id, but don't use the id which is reserved for connectionless packets + serverId = Sys_Milliseconds() & CONNECTIONLESS_MESSAGE_ID_MASK; + + active = true; + + nextHeartbeatTime = 0; + nextAsyncStatsTime = 0; + + ExecuteMapChange(); +} + +/* +================== +idAsyncServer::Kill +================== +*/ +void idAsyncServer::Kill( void ) { + int i, j; + + if ( !active ) { + return; + } + + // drop all clients + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + DropClient( i, "#str_07135" ); + } + + // send some empty messages to the zombie clients to make sure they disconnect + for ( j = 0; j < 4; j++ ) { + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState == SCS_ZOMBIE ) { + if ( clients[i].channel.UnsentFragmentsLeft() ) { + clients[i].channel.SendNextFragment( serverPort, serverTime ); + } else { + SendEmptyToClient( i, true ); + } + } + } + Sys_Sleep( 10 ); + } + + // reset any pureness + fileSystem->ClearPureChecksums(); + + active = false; + + // shutdown any current game + session->Stop(); +} + +/* +================== +idAsyncServer::ExecuteMapChange +================== +*/ +void idAsyncServer::ExecuteMapChange( void ) { + int i; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + idStr mapName; + findFile_t ff; + bool addonReload = false; + char bestGameType[ MAX_STRING_CHARS ]; + + assert( active ); + + // reset any pureness + fileSystem->ClearPureChecksums(); + + // make sure the map/gametype combo is good + idStr::Copynz( bestGameType, cvarSystem->GetCVarString( "si_gametype" ), sizeof( bestGameType ) ); + + // initialize map settings + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" ); + + sprintf( mapName, "maps/%s", sessLocal.mapSpawnData.serverInfo.GetString( "si_map" ) ); + mapName.SetFileExtension( ".map" ); + ff = fileSystem->FindFile( mapName ); + switch( ff ) { + case FIND_NO: + common->Printf( "Can't find map %s\n", mapName.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + return; + case FIND_ADDON: + // NOTE: we have no problem with addon dependencies here because if the map is in + // an addon pack that's already on search list, then all it's deps are assumed to be on search as well + common->Printf( "map %s is in an addon pak - reloading\n", mapName.c_str() ); + addonReload = true; + break; + default: + break; + } + + // if we are asked to do a full reload, the strategy is completely different + if ( !serverReloadingEngine && ( addonReload || idAsyncNetwork::serverReloadEngine.GetInteger() != 0 ) ) { + if ( idAsyncNetwork::serverReloadEngine.GetInteger() != 0 ) { + common->Printf( "net_serverReloadEngine enabled - doing a full reload\n" ); + } + // tell the clients to reconnect + // FIXME: shouldn't they wait for the new pure list, then reload? + // in a lot of cases this is going to trigger two reloadEngines for the clients + // one to restart, the other one to set paks right ( with addon for instance ) + // can fix by reconnecting without reloading and waiting for the server to tell.. + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[ i ].clientState >= SCS_PUREWAIT && i != localClientNum ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_RELOAD ); + SendReliableMessage( i, msg ); + clients[ i ].clientState = SCS_ZOMBIE; // so we don't bother sending a disconnect + } + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reloadEngine" ); + serverReloadingEngine = true; // don't get caught in endless loop + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + // decrease feature + if ( idAsyncNetwork::serverReloadEngine.GetInteger() > 0 ) { + idAsyncNetwork::serverReloadEngine.SetInteger( idAsyncNetwork::serverReloadEngine.GetInteger() - 1 ); + } + return; + } + serverReloadingEngine = false; + + serverTime = 0; + + // initialize game id and time + gameInitId ^= Sys_Milliseconds(); // NOTE: make sure the gameInitId is always a positive number because negative numbers have special meaning + gameFrame = 0; + gameTime = 0; + gameTimeResidual = 0; + memset( userCmds, 0, sizeof( userCmds ) ); + + if ( idAsyncNetwork::serverDedicated.GetInteger() == 0 ) { + InitLocalClient( 0 ); + } else { + localClientNum = -1; + } + + // re-initialize all connected clients for the new map + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState >= SCS_PUREWAIT && i != localClientNum ) { + + InitClient( i, clients[i].clientId, clients[i].clientRate ); + + SendGameInitToClient( i ); + + if ( sessLocal.mapSpawnData.serverInfo.GetBool( "si_pure" ) ) { + clients[ i ].clientState = SCS_PUREWAIT; + } + } + } + + // setup the game pak checksums + // since this is not dependant on si_pure we catch anything bad before loading map + if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) ) { + if ( !fileSystem->UpdateGamePakChecksums( ) ) { + session->MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04337" ), common->GetLanguageDict()->GetString ( "#str_04338" ), true ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + return; + } + } + + // load map + sessLocal.ExecuteMapChange(); + + if ( localClientNum >= 0 ) { + BeginLocalClient(); + } else { + game->SetLocalClient( -1 ); + } + + if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) ) { + // lock down the pak list + fileSystem->UpdatePureServerChecksums( ); + // tell the clients so they can work out their pure lists + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[ i ].clientState == SCS_PUREWAIT ) { + if ( !SendReliablePureToClient( i ) ) { + clients[ i ].clientState = SCS_CONNECTED; + } + } + } + } + + // serverTime gets reset, force a heartbeat so timings restart + MasterHeartbeat( true ); +} + +/* +================== +idAsyncServer::GetPort +================== +*/ +int idAsyncServer::GetPort( void ) const { + return serverPort.GetPort(); +} + +/* +=============== +idAsyncServer::GetBoundAdr +=============== +*/ +netadr_t idAsyncServer::GetBoundAdr( void ) const { + netadr_t address; + memset( &address, 0, sizeof( address ) ); + address.type = NA_IP; + address.ip[0] = 127; + address.ip[3] = 1; + address.port = serverPort.GetPort(); + return address; +} + +/* +================== +idAsyncServer::GetOutgoingRate +================== +*/ +int idAsyncServer::GetOutgoingRate( void ) const { + int i, rate; + + rate = 0; + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + const serverClient_t &client = clients[i]; + + if ( client.clientState >= SCS_CONNECTED ) { + rate += client.channel.GetOutgoingRate(); + } + } + return rate; +} + +/* +================== +idAsyncServer::GetIncomingRate +================== +*/ +int idAsyncServer::GetIncomingRate( void ) const { + int i, rate; + + rate = 0; + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + const serverClient_t &client = clients[i]; + + if ( client.clientState >= SCS_CONNECTED ) { + rate += client.channel.GetIncomingRate(); + } + } + return rate; +} + +/* +================== +idAsyncServer::IsClientInGame +================== +*/ +bool idAsyncServer::IsClientInGame( int clientNum ) const { + return ( clients[clientNum].clientState >= SCS_INGAME ); +} + +/* +================== +idAsyncServer::GetClientPing +================== +*/ +int idAsyncServer::GetClientPing( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return 99999; + } else { + return client.clientPing; + } +} + +/* +================== +idAsyncServer::GetClientPrediction +================== +*/ +int idAsyncServer::GetClientPrediction( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return 99999; + } else { + return client.clientPrediction; + } +} + +/* +================== +idAsyncServer::GetClientTimeSinceLastPacket +================== +*/ +int idAsyncServer::GetClientTimeSinceLastPacket( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return 99999; + } else { + return serverTime - client.lastPacketTime; + } +} + +/* +================== +idAsyncServer::GetClientTimeSinceLastInput +================== +*/ +int idAsyncServer::GetClientTimeSinceLastInput( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return 99999; + } else { + return serverTime - client.lastInputTime; + } +} + +/* +================== +idAsyncServer::GetClientOutgoingRate +================== +*/ +int idAsyncServer::GetClientOutgoingRate( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return -1; + } else { + return client.channel.GetOutgoingRate(); + } +} + +/* +================== +idAsyncServer::GetClientIncomingRate +================== +*/ +int idAsyncServer::GetClientIncomingRate( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return -1; + } else { + return client.channel.GetIncomingRate(); + } +} + +/* +================== +idAsyncServer::GetClientOutgoingCompression +================== +*/ +float idAsyncServer::GetClientOutgoingCompression( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return 0.0f; + } else { + return client.channel.GetOutgoingCompression(); + } +} + +/* +================== +idAsyncServer::GetClientIncomingCompression +================== +*/ +float idAsyncServer::GetClientIncomingCompression( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return 0.0f; + } else { + return client.channel.GetIncomingCompression(); + } +} + +/* +================== +idAsyncServer::GetClientIncomingPacketLoss +================== +*/ +float idAsyncServer::GetClientIncomingPacketLoss( int clientNum ) const { + const serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return 0.0f; + } else { + return client.channel.GetIncomingPacketLoss(); + } +} + +/* +================== +idAsyncServer::GetClientAddress +idAsyncServer::GetClientGUID +idAsyncServer::GetTrafficStats +================== +*/ +netadr_t idAsyncServer::GetClientAddress( int clientNum ) const { + netadr_t badAddress; + memset( &badAddress, 0, sizeof( badAddress ) ); + badAddress.type = NA_BAD; + if ( clientNum < 0 || clientNum >= MAX_ASYNC_CLIENTS ) { + return badAddress; + } + return clients[clientNum].channel.GetRemoteAddress(); +} + +const char *idAsyncServer::GetClientGUID( int clientNum ) const { + if ( clientNum < 0 || clientNum >= MAX_ASYNC_CLIENTS ) { + return NULL; + } + return clients[clientNum].guid; +} + +void idAsyncServer::GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const { + serverPort.GetTrafficStats( bytesSent, packetsSent, bytesReceived, packetsReceived ); +} + +/* +================== +idAsyncServer::GetNumClients +================== +*/ +int idAsyncServer::GetNumClients( void ) const { + int ret = 0; + for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[ i ].clientState >= SCS_CONNECTED ) { + ret++; + } + } + return ret; +} + +/* +================== +idAsyncServer::GetNumIdleClients +================== +*/ +int idAsyncServer::GetNumIdleClients( void ) const { + int ret = 0; + for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[ i ].clientState >= SCS_CONNECTED ) { + if ( serverTime - clients[ i ].lastInputTime > NOINPUT_IDLE_TIME ) { + ret++; + } + } + } + return ret; +} + +/* +================== +idAsyncServer::DuplicateUsercmds +================== +*/ +void idAsyncServer::DuplicateUsercmds( int frame, int time ) { + int i, previousIndex, currentIndex; + + previousIndex = ( frame - 1 ) & ( MAX_USERCMD_BACKUP - 1 ); + currentIndex = frame & ( MAX_USERCMD_BACKUP - 1 ); + + // duplicate previous user commands if no new commands are available for a client + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState == SCS_FREE ) { + continue; + } + + if ( idAsyncNetwork::DuplicateUsercmd( userCmds[previousIndex][i], userCmds[currentIndex][i], frame, time ) ) { + clients[i].numDuplicatedUsercmds++; + } + } +} + +/* +================== +idAsyncServer::ClearClient +================== +*/ +void idAsyncServer::ClearClient( int clientNum ) { + serverClient_t &client = clients[clientNum]; + client.clientId = 0; + client.clientState = SCS_FREE; + client.clientPrediction = 0; + client.clientAheadTime = 0; + client.clientRate = 0; + client.clientPing = 0; + client.gameInitSequence = 0; + client.gameFrame = 0; + client.gameTime = 0; + client.channel.Shutdown(); + client.lastConnectTime = 0; + client.lastEmptyTime = 0; + client.lastPingTime = 0; + client.lastSnapshotTime = 0; + client.lastPacketTime = 0; + client.lastInputTime = 0; + client.snapshotSequence = 0; + client.acknowledgeSnapshotSequence = 0; + client.numDuplicatedUsercmds = 0; +} + +/* +================== +idAsyncServer::InitClient +================== +*/ +void idAsyncServer::InitClient( int clientNum, int clientId, int clientRate ) { + int i; + + // clear the user info + sessLocal.mapSpawnData.userInfo[ clientNum ].Clear(); // always start with a clean base + + // clear the server client + serverClient_t &client = clients[clientNum]; + client.clientId = clientId; + client.clientState = SCS_CONNECTED; + client.clientPrediction = 0; + client.clientAheadTime = 0; + client.gameInitSequence = -1; + client.gameFrame = 0; + client.gameTime = 0; + client.channel.ResetRate(); + client.clientRate = clientRate ? clientRate : idAsyncNetwork::serverMaxClientRate.GetInteger(); + client.channel.SetMaxOutgoingRate( Min( idAsyncNetwork::serverMaxClientRate.GetInteger(), client.clientRate ) ); + client.clientPing = 0; + client.lastConnectTime = serverTime; + client.lastEmptyTime = serverTime; + client.lastPingTime = serverTime; + client.lastSnapshotTime = serverTime; + client.lastPacketTime = serverTime; + client.lastInputTime = serverTime; + client.acknowledgeSnapshotSequence = 0; + client.numDuplicatedUsercmds = 0; + + // clear the user commands + for ( i = 0; i < MAX_USERCMD_BACKUP; i++ ) { + memset( &userCmds[i][clientNum], 0, sizeof( userCmds[i][clientNum] ) ); + } + + // let the game know a player connected + game->ServerClientConnect( clientNum, client.guid ); +} + +/* +================== +idAsyncServer::InitLocalClient +================== +*/ +void idAsyncServer::InitLocalClient( int clientNum ) { + netadr_t badAddress; + + localClientNum = clientNum; + InitClient( clientNum, 0, 0 ); + memset( &badAddress, 0, sizeof( badAddress ) ); + badAddress.type = NA_BAD; + clients[clientNum].channel.Init( badAddress, serverId ); + clients[clientNum].clientState = SCS_INGAME; + sessLocal.mapSpawnData.userInfo[clientNum] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO ); +} + +/* +================== +idAsyncServer::BeginLocalClient +================== +*/ +void idAsyncServer::BeginLocalClient( void ) { + game->SetLocalClient( localClientNum ); + game->SetUserInfo( localClientNum, sessLocal.mapSpawnData.userInfo[localClientNum], false ); + game->ServerClientBegin( localClientNum ); +} + +/* +================== +idAsyncServer::LocalClientInput +================== +*/ +void idAsyncServer::LocalClientInput( void ) { + int index; + + if ( localClientNum < 0 ) { + return; + } + + index = gameFrame & ( MAX_USERCMD_BACKUP - 1 ); + userCmds[index][localClientNum] = usercmdGen->GetDirectUsercmd(); + userCmds[index][localClientNum].gameFrame = gameFrame; + userCmds[index][localClientNum].gameTime = gameTime; + if ( idAsyncNetwork::UsercmdInputChanged( userCmds[( gameFrame - 1 ) & ( MAX_USERCMD_BACKUP - 1 )][localClientNum], userCmds[index][localClientNum] ) ) { + clients[localClientNum].lastInputTime = serverTime; + } + clients[localClientNum].gameFrame = gameFrame; + clients[localClientNum].gameTime = gameTime; + clients[localClientNum].lastPacketTime = serverTime; +} + +/* +================== +idAsyncServer::DropClient +================== +*/ +void idAsyncServer::DropClient( int clientNum, const char *reason ) { + int i; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + serverClient_t &client = clients[clientNum]; + + if ( client.clientState <= SCS_ZOMBIE ) { + return; + } + + if ( client.clientState >= SCS_PUREWAIT && clientNum != localClientNum ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_DISCONNECT ); + msg.WriteLong( clientNum ); + msg.WriteString( reason ); + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + // clientNum so SCS_PUREWAIT client gets it's own disconnect msg + if ( i == clientNum || clients[i].clientState >= SCS_CONNECTED ) { + SendReliableMessage( i, msg ); + } + } + } + + reason = common->GetLanguageDict()->GetString( reason ); + common->Printf( "client %d %s\n", clientNum, reason ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "addChatLine \"%s^0 %s\"", sessLocal.mapSpawnData.userInfo[ clientNum ].GetString( "ui_name" ), reason ) ); + + // remove the player from the game + game->ServerClientDisconnect( clientNum ); + + client.clientState = SCS_ZOMBIE; +} + +/* +================== +idAsyncServer::SendReliableMessage +================== +*/ +void idAsyncServer::SendReliableMessage( int clientNum, const idBitMsg &msg ) { + if ( clientNum == localClientNum ) { + return; + } + if ( !clients[ clientNum ].channel.SendReliableMessage( msg ) ) { + clients[ clientNum ].channel.ClearReliableMessages(); + DropClient( clientNum, "#str_07136" ); + } +} + +/* +================== +idAsyncServer::CheckClientTimeouts +================== +*/ +void idAsyncServer::CheckClientTimeouts( void ) { + int i, zombieTimeout, clientTimeout; + + zombieTimeout = serverTime - idAsyncNetwork::serverZombieTimeout.GetInteger() * 1000; + clientTimeout = serverTime - idAsyncNetwork::serverClientTimeout.GetInteger() * 1000; + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + serverClient_t &client = clients[i]; + + if ( i == localClientNum ) { + continue; + } + + if ( client.lastPacketTime > serverTime ) { + client.lastPacketTime = serverTime; + continue; + } + + if ( client.clientState == SCS_ZOMBIE && client.lastPacketTime < zombieTimeout ) { + client.channel.Shutdown(); + client.clientState = SCS_FREE; + continue; + } + + if ( client.clientState >= SCS_PUREWAIT && client.lastPacketTime < clientTimeout ) { + DropClient( i, "#str_07137" ); + continue; + } + } +} + +/* +================== +idAsyncServer::SendPrintBroadcast +================== +*/ +void idAsyncServer::SendPrintBroadcast( const char *string ) { + int i; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_PRINT ); + msg.WriteString( string ); + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState >= SCS_CONNECTED ) { + SendReliableMessage( i, msg ); + } + } +} + +/* +================== +idAsyncServer::SendPrintToClient +================== +*/ +void idAsyncServer::SendPrintToClient( int clientNum, const char *string ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + serverClient_t &client = clients[clientNum]; + + if ( client.clientState < SCS_CONNECTED ) { + return; + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_PRINT ); + msg.WriteString( string ); + + SendReliableMessage( clientNum, msg ); +} + +/* +================== +idAsyncServer::SendUserInfoBroadcast +================== +*/ +void idAsyncServer::SendUserInfoBroadcast( int userInfoNum, const idDict &info, bool sendToAll ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + const idDict *gameInfo; + bool gameModifiedInfo; + + gameInfo = game->SetUserInfo( userInfoNum, info, false ); + if ( gameInfo ) { + gameModifiedInfo = true; + } else { + gameModifiedInfo = false; + gameInfo = &info; + } + + if ( userInfoNum == localClientNum ) { + common->DPrintf( "local user info modified by server\n" ); + cvarSystem->SetCVarsFromDict( *gameInfo ); + cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); // don't emit back + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_CLIENTINFO ); + msg.WriteByte( userInfoNum ); + if ( gameModifiedInfo || sendToAll ) { + msg.WriteBits( 0, 1 ); + } else { + msg.WriteBits( 1, 1 ); + } + +#if ID_CLIENTINFO_TAGS + msg.WriteLong( sessLocal.mapSpawnData.userInfo[userInfoNum].Checksum() ); + common->DPrintf( "broadcast for client %d: 0x%x\n", userInfoNum, sessLocal.mapSpawnData.userInfo[userInfoNum].Checksum() ); + sessLocal.mapSpawnData.userInfo[userInfoNum].Print(); +#endif + + if ( gameModifiedInfo || sendToAll ) { + msg.WriteDeltaDict( *gameInfo, NULL ); + } else { + msg.WriteDeltaDict( *gameInfo, &sessLocal.mapSpawnData.userInfo[userInfoNum] ); + } + + for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState >= SCS_CONNECTED && ( sendToAll || i != userInfoNum || gameModifiedInfo ) ) { + SendReliableMessage( i, msg ); + } + } + + sessLocal.mapSpawnData.userInfo[userInfoNum] = *gameInfo; +} + +/* +================== +idAsyncServer::UpdateUI +if the game modifies userInfo, it will call this through command system +we then need to get the info from the game, and broadcast to clients +( using DeltaDict and our current mapSpawnData as a base ) +================== +*/ +void idAsyncServer::UpdateUI( int clientNum ) { + const idDict *info = game->GetUserInfo( clientNum ); + + if ( !info ) { + common->Warning( "idAsyncServer::UpdateUI: no info from game\n" ); + return; + } + + SendUserInfoBroadcast( clientNum, *info, true ); +} + +/* +================== +idAsyncServer::SendUserInfoToClient +================== +*/ +void idAsyncServer::SendUserInfoToClient( int clientNum, int userInfoNum, const idDict &info ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( clients[clientNum].clientState < SCS_CONNECTED ) { + return; + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_CLIENTINFO ); + msg.WriteByte( userInfoNum ); + msg.WriteBits( 0, 1 ); + +#if ID_CLIENTINFO_TAGS + msg.WriteLong( 0 ); + common->DPrintf( "user info %d to client %d: NULL base\n", userInfoNum, clientNum ); +#endif + + msg.WriteDeltaDict( info, NULL ); + + SendReliableMessage( clientNum, msg ); +} + +/* +================== +idAsyncServer::SendSyncedCvarsBroadcast +================== +*/ +void idAsyncServer::SendSyncedCvarsBroadcast( const idDict &cvars ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + int i; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_SYNCEDCVARS ); + msg.WriteDeltaDict( cvars, &sessLocal.mapSpawnData.syncedCVars ); + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState >= SCS_CONNECTED ) { + SendReliableMessage( i, msg ); + } + } + + sessLocal.mapSpawnData.syncedCVars = cvars; +} + +/* +================== +idAsyncServer::SendSyncedCvarsToClient +================== +*/ +void idAsyncServer::SendSyncedCvarsToClient( int clientNum, const idDict &cvars ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( clients[clientNum].clientState < SCS_CONNECTED ) { + return; + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_SYNCEDCVARS ); + msg.WriteDeltaDict( cvars, NULL ); + + SendReliableMessage( clientNum, msg ); +} + +/* +================== +idAsyncServer::SendApplySnapshotToClient +================== +*/ +void idAsyncServer::SendApplySnapshotToClient( int clientNum, int sequence ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_APPLYSNAPSHOT ); + msg.WriteLong( sequence ); + + SendReliableMessage( clientNum, msg ); +} + +/* +================== +idAsyncServer::SendEmptyToClient +================== +*/ +bool idAsyncServer::SendEmptyToClient( int clientNum, bool force ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + serverClient_t &client = clients[clientNum]; + + if ( client.lastEmptyTime > realTime ) { + client.lastEmptyTime = realTime; + } + + if ( !force && ( realTime - client.lastEmptyTime < EMPTY_RESEND_TIME ) ) { + return false; + } + + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "sending empty to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime ); + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( gameInitId ); + msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_EMPTY ); + + client.channel.SendMessage( serverPort, serverTime, msg ); + + client.lastEmptyTime = realTime; + + return true; +} + +/* +================== +idAsyncServer::SendPingToClient +================== +*/ +bool idAsyncServer::SendPingToClient( int clientNum ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + serverClient_t &client = clients[clientNum]; + + if ( client.lastPingTime > realTime ) { + client.lastPingTime = realTime; + } + + if ( realTime - client.lastPingTime < PING_RESEND_TIME ) { + return false; + } + + if ( idAsyncNetwork::verbose.GetInteger() == 2 ) { + common->Printf( "pinging client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime ); + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( gameInitId ); + msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_PING ); + msg.WriteLong( realTime ); + + client.channel.SendMessage( serverPort, serverTime, msg ); + + client.lastPingTime = realTime; + + return true; +} + +/* +================== +idAsyncServer::SendGameInitToClient +================== +*/ +void idAsyncServer::SendGameInitToClient( int clientNum ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "sending gameinit to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime ); + } + + serverClient_t &client = clients[clientNum]; + + // clear the unsent fragments. might flood winsock but that's ok + while( client.channel.UnsentFragmentsLeft() ) { + client.channel.SendNextFragment( serverPort, serverTime ); + } + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( gameInitId ); + msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_GAMEINIT ); + msg.WriteLong( gameFrame ); + msg.WriteLong( gameTime ); + msg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL ); + client.gameInitSequence = client.channel.SendMessage( serverPort, serverTime, msg ); +} + +/* +================== +idAsyncServer::SendSnapshotToClient +================== +*/ +bool idAsyncServer::SendSnapshotToClient( int clientNum ) { + int i, j, index, numUsercmds; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + usercmd_t * last; + dword clientInPVS[( MAX_ASYNC_CLIENTS + 31 ) >> 5]; + + serverClient_t &client = clients[clientNum]; + + if ( serverTime - client.lastSnapshotTime < idAsyncNetwork::serverSnapshotDelay.GetInteger() ) { + return false; + } + + if ( idAsyncNetwork::verbose.GetInteger() == 2 ) { + common->Printf( "sending snapshot to client %d: gameInitId = %d, gameFrame = %d, gameTime = %d\n", clientNum, gameInitId, gameFrame, gameTime ); + } + + // how far is the client ahead of the server minus the packet delay + client.clientAheadTime = client.gameTime - ( gameTime + gameTimeResidual ); + + // write the snapshot + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteLong( gameInitId ); + msg.WriteByte( SERVER_UNRELIABLE_MESSAGE_SNAPSHOT ); + msg.WriteLong( client.snapshotSequence ); + msg.WriteLong( gameFrame ); + msg.WriteLong( gameTime ); + msg.WriteByte( idMath::ClampChar( client.numDuplicatedUsercmds ) ); + msg.WriteShort( idMath::ClampShort( client.clientAheadTime ) ); + + // write the game snapshot + game->ServerWriteSnapshot( clientNum, client.snapshotSequence, msg, clientInPVS, MAX_ASYNC_CLIENTS, gameFrame ); + + // write the latest user commands from the other clients in the PVS to the snapshot + for ( last = NULL, i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + serverClient_t &client = clients[i]; + + if ( client.clientState == SCS_FREE || i == clientNum ) { + continue; + } + + // if the client is not in the PVS + if ( !( clientInPVS[i >> 5] & ( 1 << ( i & 31 ) ) ) ) { + continue; + } + + int maxRelay = idMath::ClampInt( 1, MAX_USERCMD_RELAY, idAsyncNetwork::serverMaxUsercmdRelay.GetInteger() ); + + // Max( 1, to always send at least one cmd, which we know we have because we call DuplicateUsercmds in RunFrame + numUsercmds = Max( 1, Min( client.gameFrame, gameFrame + maxRelay ) - gameFrame ); + msg.WriteByte( i ); + msg.WriteByte( numUsercmds ); + for ( j = 0; j < numUsercmds; j++ ) { + index = ( gameFrame + j ) & ( MAX_USERCMD_BACKUP - 1 ); + idAsyncNetwork::WriteUserCmdDelta( msg, userCmds[index][i], last ); + last = &userCmds[index][i]; + } + } + msg.WriteByte( MAX_ASYNC_CLIENTS ); + + client.channel.SendMessage( serverPort, serverTime, msg ); + + client.lastSnapshotTime = serverTime; + client.snapshotSequence++; + client.numDuplicatedUsercmds = 0; + + return true; +} + +/* +================== +idAsyncServer::ProcessUnreliableClientMessage +================== +*/ +void idAsyncServer::ProcessUnreliableClientMessage( int clientNum, const idBitMsg &msg ) { + int i, id, acknowledgeSequence, clientGameInitId, clientGameFrame, numUsercmds, index; + usercmd_t *last; + + serverClient_t &client = clients[clientNum]; + + if ( client.clientState == SCS_ZOMBIE ) { + return; + } + + acknowledgeSequence = msg.ReadLong(); + clientGameInitId = msg.ReadLong(); + + // while loading a map the client may send empty messages to keep the connection alive + if ( clientGameInitId == GAME_INIT_ID_MAP_LOAD ) { + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "ignore unreliable msg from client %d, gameInitId == ID_MAP_LOAD\n", clientNum ); + } + return; + } + + // check if the client is in the right game + if ( clientGameInitId != gameInitId ) { + if ( acknowledgeSequence > client.gameInitSequence ) { + // the client is connected but not in the right game + client.clientState = SCS_CONNECTED; + + // send game init to client + SendGameInitToClient( clientNum ); + + if ( sessLocal.mapSpawnData.serverInfo.GetBool( "si_pure" ) ) { + client.clientState = SCS_PUREWAIT; + if ( !SendReliablePureToClient( clientNum ) ) { + client.clientState = SCS_CONNECTED; + } + } + } else if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "ignore unreliable msg from client %d, wrong gameInit, old sequence\n", clientNum ); + } + return; + } + + client.acknowledgeSnapshotSequence = msg.ReadLong(); + + if ( client.clientState == SCS_CONNECTED ) { + + // the client is in the right game + client.clientState = SCS_INGAME; + + // send the user info of other clients + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState >= SCS_CONNECTED && i != clientNum ) { + SendUserInfoToClient( clientNum, i, sessLocal.mapSpawnData.userInfo[i] ); + } + } + + // send synchronized cvars to client + SendSyncedCvarsToClient( clientNum, sessLocal.mapSpawnData.syncedCVars ); + + SendEnterGameToClient( clientNum ); + + // get the client running in the game + game->ServerClientBegin( clientNum ); + + // write any reliable messages to initialize the client game state + game->ServerWriteInitialReliableMessages( clientNum ); + } else if ( client.clientState == SCS_INGAME ) { + + // apply the last snapshot the client received + if ( game->ServerApplySnapshot( clientNum, client.acknowledgeSnapshotSequence ) ) { + SendApplySnapshotToClient( clientNum, client.acknowledgeSnapshotSequence ); + } + } + + // process the unreliable message + id = msg.ReadByte(); + switch( id ) { + case CLIENT_UNRELIABLE_MESSAGE_EMPTY: { + if ( idAsyncNetwork::verbose.GetInteger() ) { + common->Printf( "received empty message for client %d\n", clientNum ); + } + break; + } + case CLIENT_UNRELIABLE_MESSAGE_PINGRESPONSE: { + client.clientPing = realTime - msg.ReadLong(); + break; + } + case CLIENT_UNRELIABLE_MESSAGE_USERCMD: { + + client.clientPrediction = msg.ReadShort(); + + // read user commands + clientGameFrame = msg.ReadLong(); + numUsercmds = msg.ReadByte(); + for ( last = NULL, i = clientGameFrame - numUsercmds + 1; i <= clientGameFrame; i++ ) { + index = i & ( MAX_USERCMD_BACKUP - 1 ); + idAsyncNetwork::ReadUserCmdDelta( msg, userCmds[index][clientNum], last ); + userCmds[index][clientNum].gameFrame = i; + userCmds[index][clientNum].duplicateCount = 0; + if ( idAsyncNetwork::UsercmdInputChanged( userCmds[( i - 1 ) & ( MAX_USERCMD_BACKUP - 1 )][clientNum], userCmds[index][clientNum] ) ) { + client.lastInputTime = serverTime; + } + last = &userCmds[index][clientNum]; + } + + if ( last ) { + client.gameFrame = last->gameFrame; + client.gameTime = last->gameTime; + } + + if ( idAsyncNetwork::verbose.GetInteger() == 2 ) { + common->Printf( "received user command for client %d, gameInitId = %d, gameFrame, %d gameTime %d\n", clientNum, clientGameInitId, client.gameFrame, client.gameTime ); + } + break; + } + default: { + common->Printf( "unknown unreliable message %d from client %d\n", id, clientNum ); + break; + } + } +} + +/* +================== +idAsyncServer::ProcessReliableClientMessages +================== +*/ +void idAsyncServer::ProcessReliableClientMessages( int clientNum ) { + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + byte id; + + serverClient_t &client = clients[clientNum]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + + while ( client.channel.GetReliableMessage( msg ) ) { + id = msg.ReadByte(); + switch( id ) { + case CLIENT_RELIABLE_MESSAGE_CLIENTINFO: { + idDict info; + msg.ReadDeltaDict( info, &sessLocal.mapSpawnData.userInfo[clientNum] ); + SendUserInfoBroadcast( clientNum, info ); + break; + } + case CLIENT_RELIABLE_MESSAGE_PRINT: { + char string[MAX_STRING_CHARS]; + msg.ReadString( string, sizeof( string ) ); + common->Printf( "%s\n", string ); + break; + } + case CLIENT_RELIABLE_MESSAGE_DISCONNECT: { + DropClient( clientNum, "#str_07138" ); + break; + } + case CLIENT_RELIABLE_MESSAGE_PURE: { + // we get this message once the client has successfully updated it's pure list + ProcessReliablePure( clientNum, msg ); + break; + } + default: { + // pass reliable message on to game code + game->ServerProcessReliableMessage( clientNum, msg ); + break; + } + } + } +} + +/* +================== +idAsyncServer::ProcessAuthMessage +================== +*/ +void idAsyncServer::ProcessAuthMessage( const idBitMsg &msg ) { + netadr_t client_from; + char client_guid[ 12 ], string[ MAX_STRING_CHARS ]; + int i, clientId; + authReply_t reply; + authReplyMsg_t replyMsg = AUTH_REPLY_WAITING; + idStr replyPrintMsg; + + reply = (authReply_t)msg.ReadByte(); + if ( reply <= 0 || reply >= AUTH_MAXSTATES ) { + common->DPrintf( "auth: invalid reply %d\n", reply ); + return; + } + clientId = msg.ReadShort( ); + msg.ReadNetadr( &client_from ); + msg.ReadString( client_guid, sizeof( client_guid ) ); + if ( reply != AUTH_OK ) { + replyMsg = (authReplyMsg_t)msg.ReadByte(); + if ( replyMsg <= 0 || replyMsg >= AUTH_REPLY_MAXSTATES ) { + common->DPrintf( "auth: invalid reply msg %d\n", replyMsg ); + return; + } + if ( replyMsg == AUTH_REPLY_PRINT ) { + msg.ReadString( string, MAX_STRING_CHARS ); + replyPrintMsg = string; + } + } + + lastAuthTime = serverTime; + + // no message parsing below + + for ( i = 0; i < MAX_CHALLENGES; i++ ) { + if ( !challenges[i].connected && challenges[ i ].clientId == clientId ) { + // return if something is wrong + // break if we have found a valid auth + if ( !strlen( challenges[ i ].guid ) ) { + common->DPrintf( "auth: client %s has no guid yet\n", Sys_NetAdrToString( challenges[ i ].address ) ); + return; + } + if ( idStr::Cmp( challenges[ i ].guid, client_guid ) ) { + common->DPrintf( "auth: client %s %s not matched, auth server says guid %s\n", Sys_NetAdrToString( challenges[ i ].address ), challenges[i].guid, client_guid ); + return; + } + if ( !Sys_CompareNetAdrBase( client_from, challenges[i].address ) ) { + // let auth work when server and master don't see the same IP + common->DPrintf( "auth: matched guid '%s' for != IPs %s and %s\n", client_guid, Sys_NetAdrToString( client_from ), Sys_NetAdrToString( challenges[i].address ) ); + } + break; + } + } + if ( i >= MAX_CHALLENGES ) { + common->DPrintf( "auth: failed client lookup %s %s\n", Sys_NetAdrToString( client_from ), client_guid ); + return; + } + + if ( challenges[ i ].authState != CDK_WAIT ) { + common->DWarning( "auth: challenge 0x%x %s authState %d != CDK_WAIT", challenges[ i ].challenge, Sys_NetAdrToString( challenges[ i ].address ), challenges[ i ].authState ); + return; + } + + idStr::snPrintf( challenges[ i ].guid, 12, client_guid ); + if ( reply == AUTH_OK ) { + challenges[ i ].authState = CDK_OK; + common->Printf( "client %s %s is authed\n", Sys_NetAdrToString( client_from ), client_guid ); + } else { + const char *msg; + if ( replyMsg != AUTH_REPLY_PRINT ) { + msg = authReplyMsg[ replyMsg ]; + } else { + msg = replyPrintMsg.c_str(); + } + // maybe localize it + const char *l_msg = common->GetLanguageDict()->GetString( msg ); + common->DPrintf( "auth: client %s %s - %s %s\n", Sys_NetAdrToString( client_from ), client_guid, authReplyStr[ reply ], l_msg ); + challenges[ i ].authReply = reply; + challenges[ i ].authReplyMsg = replyMsg; + challenges[ i ].authReplyPrint = replyPrintMsg; + } +} + +/* +================== +idAsyncServer::ProcessChallengeMessage +================== +*/ +void idAsyncServer::ProcessChallengeMessage( const netadr_t from, const idBitMsg &msg ) { + int i, clientId, oldest, oldestTime; + idBitMsg outMsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + clientId = msg.ReadLong(); + + oldest = 0; + oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + for ( i = 0; i < MAX_CHALLENGES; i++ ) { + if ( !challenges[i].connected && Sys_CompareNetAdrBase( from, challenges[i].address ) && clientId == challenges[i].clientId ) { + break; + } + if ( challenges[i].time < oldestTime ) { + oldestTime = challenges[i].time; + oldest = i; + } + } + + if ( i >= MAX_CHALLENGES ) { + // this is the first time this client has asked for a challenge + i = oldest; + challenges[i].address = from; + challenges[i].clientId = clientId; + challenges[i].challenge = ( (rand() << 16) ^ rand() ) ^ serverTime; + challenges[i].time = serverTime; + challenges[i].connected = false; + challenges[i].authState = CDK_WAIT; + challenges[i].authReply = AUTH_NONE; + challenges[i].authReplyMsg = AUTH_REPLY_WAITING; + challenges[i].authReplyPrint = ""; + challenges[i].guid[0] = '\0'; + } + challenges[i].pingTime = serverTime; + + common->Printf( "sending challenge 0x%x to %s\n", challenges[i].challenge, Sys_NetAdrToString( from ) ); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "challengeResponse" ); + outMsg.WriteLong( challenges[i].challenge ); + outMsg.WriteShort( serverId ); + outMsg.WriteString( cvarSystem->GetCVarString( "fs_game_base" ) ); + outMsg.WriteString( cvarSystem->GetCVarString( "fs_game" ) ); + + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + + if ( Sys_IsLANAddress( from ) ) { + // no CD Key check for LAN clients + challenges[i].authState = CDK_OK; + } else { + if ( idAsyncNetwork::LANServer.GetBool() ) { + common->Printf( "net_LANServer is enabled. Client %s is not a LAN address, will be rejected\n", Sys_NetAdrToString( from ) ); + challenges[ i ].authState = CDK_ONLYLAN; + } else { + // emit a cd key confirmation request + outMsg.BeginWriting(); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "srvAuth" ); + outMsg.WriteLong( ASYNC_PROTOCOL_VERSION ); + outMsg.WriteNetadr( from ); + outMsg.WriteLong( -1 ); // this identifies "challenge" auth vs "connect" auth + serverPort.SendPacket( idAsyncNetwork::GetMasterAddress(), outMsg.GetData(), outMsg.GetSize() ); + } + } +} + +/* +================== +idAsyncServer::SendPureServerMessage +================== +*/ +bool idAsyncServer::SendPureServerMessage( const netadr_t to, int OS ) { + idBitMsg outMsg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + int serverChecksums[ MAX_PURE_PAKS ]; + int gamePakChecksum; + int i; + + fileSystem->GetPureServerChecksums( serverChecksums, OS, &gamePakChecksum ); + if ( !serverChecksums[ 0 ] ) { + // happens if you run fully expanded assets with si_pure 1 + common->Warning( "pure server has no pak files referenced" ); + return false; + } + common->DPrintf( "client %s: sending pure pak list\n", Sys_NetAdrToString( to ) ); + + // send our list of required paks + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "pureServer" ); + + i = 0; + while ( serverChecksums[ i ] ) { + outMsg.WriteLong( serverChecksums[ i++ ] ); + } + outMsg.WriteLong( 0 ); + + // write the pak checksum for game code + outMsg.WriteLong( gamePakChecksum ); + + serverPort.SendPacket( to, outMsg.GetData(), outMsg.GetSize() ); + return true; +} + +/* +================== +idAsyncServer::SendReliablePureToClient +================== +*/ +bool idAsyncServer::SendReliablePureToClient( int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + int serverChecksums[ MAX_PURE_PAKS ]; + int i; + int gamePakChecksum; + + fileSystem->GetPureServerChecksums( serverChecksums, clients[ clientNum ].OS, &gamePakChecksum ); + if ( !serverChecksums[ 0 ] ) { + // happens if you run fully expanded assets with si_pure 1 + common->Warning( "pure server has no pak files referenced" ); + return false; + } + + common->DPrintf( "client %d: sending pure pak list (reliable channel) @ gameInitId %d\n", clientNum, gameInitId ); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_PURE ); + + msg.WriteLong( gameInitId ); + + i = 0; + while ( serverChecksums[ i ] ) { + msg.WriteLong( serverChecksums[ i++ ] ); + } + msg.WriteLong( 0 ); + msg.WriteLong( gamePakChecksum ); + + SendReliableMessage( clientNum, msg ); + + return true; +} + +/* +================== +idAsyncServer::ValidateChallenge +================== +*/ +int idAsyncServer::ValidateChallenge( const netadr_t from, int challenge, int clientId ) { + int i; + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + const serverClient_t &client = clients[i]; + + if ( client.clientState == SCS_FREE ) { + continue; + } + if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) && + ( clientId == client.clientId || from.port == client.channel.GetRemoteAddress().port ) ) { + if ( serverTime - client.lastConnectTime < MIN_RECONNECT_TIME ) { + common->Printf( "%s: reconnect rejected : too soon\n", Sys_NetAdrToString( from ) ); + return -1; + } + break; + } + } + + for ( i = 0; i < MAX_CHALLENGES; i++ ) { + if ( Sys_CompareNetAdrBase( from, challenges[i].address ) && from.port == challenges[i].address.port ) { + if ( challenge == challenges[i].challenge ) { + break; + } + } + } + if ( i == MAX_CHALLENGES ) { + PrintOOB( from, SERVER_PRINT_BADCHALLENGE, "#str_04840" ); + return -1; + } + return i; +} + +/* +================== +idAsyncServer::ProcessConnectMessage +================== +*/ +void idAsyncServer::ProcessConnectMessage( const netadr_t from, const idBitMsg &msg ) { + int clientNum, protocol, clientDataChecksum, challenge, clientId, ping, clientRate; + idBitMsg outMsg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + char guid[ 12 ]; + char password[ 17 ]; + int i, ichallenge, islot, OS, numClients; + + protocol = msg.ReadLong(); + OS = msg.ReadShort(); + + // check the protocol version + if ( protocol != ASYNC_PROTOCOL_VERSION ) { + // that's a msg back to a client, we don't know about it's localization, so send english + PrintOOB( from, SERVER_PRINT_BADPROTOCOL, va( "server uses protocol %d.%d\n", ASYNC_PROTOCOL_MAJOR, ASYNC_PROTOCOL_MINOR ) ); + return; + } + + clientDataChecksum = msg.ReadLong(); + challenge = msg.ReadLong(); + clientId = msg.ReadShort(); + clientRate = msg.ReadLong(); + + // check the client data - only for non pure servers + if ( !sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && clientDataChecksum != serverDataChecksum ) { + PrintOOB( from, SERVER_PRINT_MISC, "#str_04842" ); + return; + } + + if ( ( ichallenge = ValidateChallenge( from, challenge, clientId ) ) == -1 ) { + return; + } + challenges[ ichallenge ].OS = OS; + + msg.ReadString( guid, sizeof( guid ) ); + + switch ( challenges[ ichallenge ].authState ) { + case CDK_PUREWAIT: + SendPureServerMessage( from, OS ); + return; + case CDK_ONLYLAN: + common->DPrintf( "%s: not a lan client\n", Sys_NetAdrToString( from ) ); + PrintOOB( from, SERVER_PRINT_MISC, "#str_04843" ); + return; + case CDK_WAIT: + if ( challenges[ ichallenge ].authReply == AUTH_NONE && Min( serverTime - lastAuthTime, serverTime - challenges[ ichallenge ].time ) > AUTHORIZE_TIMEOUT ) { + common->DPrintf( "%s: Authorize server timed out\n", Sys_NetAdrToString( from ) ); + break; // will continue with the connecting process + } + const char *msg, *l_msg; + if ( challenges[ ichallenge ].authReplyMsg != AUTH_REPLY_PRINT ) { + msg = authReplyMsg[ challenges[ ichallenge ].authReplyMsg ]; + } else { + msg = challenges[ ichallenge ].authReplyPrint.c_str(); + } + l_msg = common->GetLanguageDict()->GetString( msg ); + + common->DPrintf( "%s: %s\n", Sys_NetAdrToString( from ), l_msg ); + + if ( challenges[ ichallenge ].authReplyMsg == AUTH_REPLY_UNKNOWN || challenges[ ichallenge ].authReplyMsg == AUTH_REPLY_WAITING ) { + // the client may be trying to connect to us in LAN mode, and the server disagrees + // let the client know so it would switch to authed connection + idBitMsg outMsg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "authrequired" ); + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + } + + PrintOOB( from, SERVER_PRINT_MISC, msg ); + + // update the guid in the challenges + idStr::snPrintf( challenges[ ichallenge ].guid, sizeof( challenges[ ichallenge ].guid ), guid ); + + // once auth replied denied, stop sending further requests + if ( challenges[ ichallenge ].authReply != AUTH_DENY ) { + // emit a cd key confirmation request + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "srvAuth" ); + outMsg.WriteLong( ASYNC_PROTOCOL_VERSION ); + outMsg.WriteNetadr( from ); + outMsg.WriteLong( clientId ); + outMsg.WriteString( guid ); + serverPort.SendPacket( idAsyncNetwork::GetMasterAddress(), outMsg.GetData(), outMsg.GetSize() ); + } + return; + default: + assert( challenges[ ichallenge ].authState == CDK_OK || challenges[ ichallenge ].authState == CDK_PUREOK ); + } + + numClients = 0; + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + serverClient_t &client = clients[ i ]; + if ( client.clientState >= SCS_PUREWAIT ) { + numClients++; + } + } + + // game may be passworded, client banned by IP or GUID + // if authState == CDK_PUREOK, the check was already performed once before entering pure checks + // but meanwhile, the max players may have been reached + msg.ReadString( password, sizeof( password ) ); + char reason[MAX_STRING_CHARS]; + allowReply_t reply = game->ServerAllowClient( clientId, numClients, Sys_NetAdrToString( from ), guid, password, "", reason ); + if ( reply != ALLOW_YES ) { + common->DPrintf( "game denied connection for %s\n", Sys_NetAdrToString( from ) ); + + // SERVER_PRINT_GAMEDENY passes the game opcode through. Don't use PrintOOB + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "print" ); + outMsg.WriteLong( SERVER_PRINT_GAMEDENY ); + outMsg.WriteLong( reply ); + outMsg.WriteString( reason ); + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + + return; + } + + // enter pure checks if necessary + if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && challenges[ ichallenge ].authState != CDK_PUREOK ) { + if ( SendPureServerMessage( from, OS ) ) { + challenges[ ichallenge ].authState = CDK_PUREWAIT; + return; + } + } + + // push back decl checksum here when running pure. just an additional safe check + if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) && clientDataChecksum != serverDataChecksum ) { + PrintOOB( from, SERVER_PRINT_MISC, "#str_04844" ); + return; + } + + ping = serverTime - challenges[ ichallenge ].pingTime; + common->Printf( "challenge from %s connecting with %d ping\n", Sys_NetAdrToString( from ), ping ); + challenges[ ichallenge ].connected = true; + + // find a slot for the client + for ( islot = 0; islot < 3; islot++ ) { + for ( clientNum = 0; clientNum < MAX_ASYNC_CLIENTS; clientNum++ ) { + serverClient_t &client = clients[ clientNum ]; + + if ( islot == 0 ) { + // if this slot uses the same IP and port + if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) && + ( clientId == client.clientId || from.port == client.channel.GetRemoteAddress().port ) ) { + break; + } + } else if ( islot == 1 ) { + // if this client is not connected and the slot uses the same IP + if ( client.clientState >= SCS_PUREWAIT ) { + continue; + } + if ( Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) ) { + break; + } + } else if ( islot == 2 ) { + // if this slot is free + if ( client.clientState == SCS_FREE ) { + break; + } + } + } + + if ( clientNum < MAX_ASYNC_CLIENTS ) { + // initialize + clients[ clientNum ].channel.Init( from, serverId ); + clients[ clientNum ].OS = OS; + strncpy( clients[ clientNum ].guid, guid, 12 ); + clients[ clientNum ].guid[11] = 0; + break; + } + } + + // if no free spots available + if ( clientNum >= MAX_ASYNC_CLIENTS ) { + PrintOOB( from, SERVER_PRINT_MISC, "#str_04845" ); + return; + } + + common->Printf( "sending connect response to %s\n", Sys_NetAdrToString( from ) ); + + // send connect response message + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "connectResponse" ); + outMsg.WriteLong( clientNum ); + outMsg.WriteLong( gameInitId ); + outMsg.WriteLong( gameFrame ); + outMsg.WriteLong( gameTime ); + outMsg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL ); + + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + + InitClient( clientNum, clientId, clientRate ); + + clients[clientNum].gameInitSequence = 1; + clients[clientNum].snapshotSequence = 1; + + // clear the challenge struct so a reconnect from this client IP starts clean + memset( &challenges[ ichallenge ], 0, sizeof( challenge_t ) ); +} + +/* +================== +idAsyncServer::VerifyChecksumMessage +================== +*/ +bool idAsyncServer::VerifyChecksumMessage( int clientNum, const netadr_t *from, const idBitMsg &msg, idStr &reply, int OS ) { + int i, numChecksums; + int checksums[ MAX_PURE_PAKS ]; + int gamePakChecksum; + int serverChecksums[ MAX_PURE_PAKS ]; + int serverGamePakChecksum; + + // pak checksums, in a 0-terminated list + numChecksums = 0; + do { + i = msg.ReadLong( ); + checksums[ numChecksums++ ] = i; + // just to make sure a broken client doesn't crash us + if ( numChecksums >= MAX_PURE_PAKS ) { + common->Warning( "MAX_PURE_PAKS ( %d ) exceeded in idAsyncServer::ProcessPureMessage\n", MAX_PURE_PAKS ); + sprintf( reply, "#str_07144" ); + return false; + } + } while ( i ); + numChecksums--; + + // code pak checksum + gamePakChecksum = msg.ReadLong( ); + + fileSystem->GetPureServerChecksums( serverChecksums, OS, &serverGamePakChecksum ); + assert( serverChecksums[ 0 ] ); + + // compare the lists + if ( serverGamePakChecksum != gamePakChecksum ) { + common->Printf( "client %s: invalid game code pak ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), gamePakChecksum ); + sprintf( reply, "#str_07145" ); + return false; + } + for ( i = 0; serverChecksums[ i ] != 0; i++ ) { + if ( checksums[ i ] != serverChecksums[ i ] ) { + common->DPrintf( "client %s: pak missing ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), serverChecksums[ i ] ); + sprintf( reply, "pak missing ( 0x%x )\n", serverChecksums[ i ] ); + return false; + } + } + if ( checksums[ i ] != 0 ) { + common->DPrintf( "client %s: extra pak file referenced ( 0x%x )\n", from ? Sys_NetAdrToString( *from ) : va( "%d", clientNum ), checksums[ i ] ); + sprintf( reply, "extra pak file referenced ( 0x%x )\n", checksums[ i ] ); + return false; + } + return true; +} + +/* +================== +idAsyncServer::ProcessPureMessage +================== +*/ +void idAsyncServer::ProcessPureMessage( const netadr_t from, const idBitMsg &msg ) { + int iclient, challenge, clientId; + idStr reply; + + challenge = msg.ReadLong(); + clientId = msg.ReadShort(); + + if ( ( iclient = ValidateChallenge( from, challenge, clientId ) ) == -1 ) { + return; + } + + if ( challenges[ iclient ].authState != CDK_PUREWAIT ) { + common->DPrintf( "client %s: got pure message, not in CDK_PUREWAIT\n", Sys_NetAdrToString( from ) ); + return; + } + + if ( !VerifyChecksumMessage( iclient, &from, msg, reply, challenges[ iclient ].OS ) ) { + PrintOOB( from, SERVER_PRINT_MISC, reply ); + return; + } + + common->DPrintf( "client %s: passed pure checks\n", Sys_NetAdrToString( from ) ); + challenges[ iclient ].authState = CDK_PUREOK; // next connect message will get the client through completely +} + +/* +================== +idAsyncServer::ProcessReliablePure +================== +*/ +void idAsyncServer::ProcessReliablePure( int clientNum, const idBitMsg &msg ) { + idStr reply; + idBitMsg outMsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + int clientGameInitId; + + clientGameInitId = msg.ReadLong(); + if ( clientGameInitId != gameInitId ) { + common->DPrintf( "client %d: ignoring reliable pure from an old gameInit (%d)\n", clientNum, clientGameInitId ); + return; + } + + if ( clients[ clientNum ].clientState != SCS_PUREWAIT ) { + // should not happen unless something is very wrong. still, don't let this crash us, just get rid of the client + common->DPrintf( "client %d: got reliable pure while != SCS_PUREWAIT, sending a reload\n", clientNum ); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_RELOAD ); + SendReliableMessage( clientNum, msg ); + // go back to SCS_CONNECTED to sleep on the client until it goes away for a reconnect + clients[ clientNum ].clientState = SCS_CONNECTED; + return; + } + + if ( !VerifyChecksumMessage( clientNum, NULL, msg, reply, clients[ clientNum ].OS ) ) { + DropClient( clientNum, reply ); + return; + } + common->DPrintf( "client %d: passed pure checks (reliable channel)\n", clientNum ); + clients[ clientNum ].clientState = SCS_CONNECTED; +} + +/* +================== +idAsyncServer::RemoteConsoleOutput +================== +*/ +void idAsyncServer::RemoteConsoleOutput( const char *string ) { + noRconOutput = false; + PrintOOB( rconAddress, SERVER_PRINT_RCON, string ); +} + +/* +================== +RConRedirect +================== +*/ +void RConRedirect( const char *string ) { + idAsyncNetwork::server.RemoteConsoleOutput( string ); +} + +/* +================== +idAsyncServer::ProcessRemoteConsoleMessage +================== +*/ +void idAsyncServer::ProcessRemoteConsoleMessage( const netadr_t from, const idBitMsg &msg ) { + idBitMsg outMsg; + byte msgBuf[952]; + char string[MAX_STRING_CHARS]; + + if ( idAsyncNetwork::serverRemoteConsolePassword.GetString()[0] == '\0' ) { + PrintOOB( from, SERVER_PRINT_MISC, "#str_04846" ); + return; + } + + msg.ReadString( string, sizeof( string ) ); + + if ( idStr::Icmp( string, idAsyncNetwork::serverRemoteConsolePassword.GetString() ) != 0 ) { + PrintOOB( from, SERVER_PRINT_MISC, "#str_04847" ); + return; + } + + msg.ReadString( string, sizeof( string ) ); + + common->Printf( "rcon from %s: %s\n", Sys_NetAdrToString( from ), string ); + + rconAddress = from; + noRconOutput = true; + common->BeginRedirect( (char *)msgBuf, sizeof( msgBuf ), RConRedirect ); + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, string ); + + common->EndRedirect(); + + if ( noRconOutput ) { + PrintOOB( rconAddress, SERVER_PRINT_RCON, "#str_04848" ); + } +} + +/* +================== +idAsyncServer::ProcessGetInfoMessage +================== +*/ +void idAsyncServer::ProcessGetInfoMessage( const netadr_t from, const idBitMsg &msg ) { + int i, challenge; + idBitMsg outMsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + if ( !IsActive() ) { + return; + } + + common->DPrintf( "Sending info response to %s\n", Sys_NetAdrToString( from ) ); + + challenge = msg.ReadLong(); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "infoResponse" ); + outMsg.WriteLong( challenge ); + outMsg.WriteLong( ASYNC_PROTOCOL_VERSION ); + outMsg.WriteDeltaDict( sessLocal.mapSpawnData.serverInfo, NULL ); + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + serverClient_t &client = clients[i]; + + if ( client.clientState < SCS_CONNECTED ) { + continue; + } + + outMsg.WriteByte( i ); + outMsg.WriteShort( client.clientPing ); + outMsg.WriteLong( client.channel.GetMaxOutgoingRate() ); + outMsg.WriteString( sessLocal.mapSpawnData.userInfo[i].GetString( "ui_name", "Player" ) ); + } + outMsg.WriteByte( MAX_ASYNC_CLIENTS ); + outMsg.WriteLong( fileSystem->GetOSMask() ); + + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); +} + +/* +=============== +idAsyncServer::PrintLocalServerInfo +see (client) "getInfo" -> (server) "infoResponse" -> (client)ProcessGetInfoMessage +=============== +*/ +void idAsyncServer::PrintLocalServerInfo( void ) { + int i; + + common->Printf( "server '%s' IP = %s\nprotocol %d.%d OS mask 0x%x\n", + sessLocal.mapSpawnData.serverInfo.GetString( "si_name" ), + Sys_NetAdrToString( GetBoundAdr() ), + ASYNC_PROTOCOL_MAJOR, + ASYNC_PROTOCOL_MINOR, + fileSystem->GetOSMask() ); + sessLocal.mapSpawnData.serverInfo.Print(); + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + serverClient_t &client = clients[i]; + if ( client.clientState < SCS_CONNECTED ) { + continue; + } + common->Printf( "client %2d: %s, ping = %d, rate = %d\n", i, + sessLocal.mapSpawnData.userInfo[i].GetString( "ui_name", "Player" ), + client.clientPing, client.channel.GetMaxOutgoingRate() ); + } +} + +/* +================== +idAsyncServer::ConnectionlessMessage +================== +*/ +bool idAsyncServer::ConnectionlessMessage( const netadr_t from, const idBitMsg &msg ) { + char string[MAX_STRING_CHARS*2]; // M. Quinn - Even Balance - PB Packets need more than 1024 + + msg.ReadString( string, sizeof( string ) ); + + // info request + if ( idStr::Icmp( string, "getInfo" ) == 0 ) { + ProcessGetInfoMessage( from, msg ); + return false; + } + + // remote console + if ( idStr::Icmp( string, "rcon" ) == 0 ) { + ProcessRemoteConsoleMessage( from, msg ); + return true; + } + + if ( !active ) { + PrintOOB( from, SERVER_PRINT_MISC, "#str_04849" ); + return false; + } + + // challenge from a client + if ( idStr::Icmp( string, "challenge" ) == 0 ) { + ProcessChallengeMessage( from, msg ); + return false; + } + + // connect from a client + if ( idStr::Icmp( string, "connect" ) == 0 ) { + ProcessConnectMessage( from, msg ); + return false; + } + + // pure mesasge from a client + if ( idStr::Icmp( string, "pureClient" ) == 0 ) { + ProcessPureMessage( from, msg ); + return false; + } + + // download request + if ( idStr::Icmp( string, "downloadRequest" ) == 0 ) { + ProcessDownloadRequestMessage( from, msg ); + } + + // auth server + if ( idStr::Icmp( string, "auth" ) == 0 ) { + if ( !Sys_CompareNetAdrBase( from, idAsyncNetwork::GetMasterAddress() ) ) { + common->Printf( "auth: bad source %s\n", Sys_NetAdrToString( from ) ); + return false; + } + if ( idAsyncNetwork::LANServer.GetBool() ) { + common->Printf( "auth message from master. net_LANServer is enabled, ignored.\n" ); + } + ProcessAuthMessage( msg ); + return false; + } + + return false; +} + +/* +================== +idAsyncServer::ProcessMessage +================== +*/ +bool idAsyncServer::ProcessMessage( const netadr_t from, idBitMsg &msg ) { + int i, id, sequence; + idBitMsg outMsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + id = msg.ReadShort(); + + // check for a connectionless message + if ( id == CONNECTIONLESS_MESSAGE_ID ) { + return ConnectionlessMessage( from, msg ); + } + + if ( msg.GetRemainingData() < 4 ) { + common->DPrintf( "%s: tiny packet\n", Sys_NetAdrToString( from ) ); + return false; + } + + // find out which client the message is from + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + serverClient_t &client = clients[i]; + + if ( client.clientState == SCS_FREE ) { + continue; + } + + // This does not compare the UDP port, because some address translating + // routers will change that at arbitrary times. + if ( !Sys_CompareNetAdrBase( from, client.channel.GetRemoteAddress() ) || id != client.clientId ) { + continue; + } + + // make sure it is a valid, in sequence packet + if ( !client.channel.Process( from, serverTime, msg, sequence ) ) { + return false; // out of order, duplicated, fragment, etc. + } + + // zombie clients still need to do the channel processing to make sure they don't + // need to retransmit the final reliable message, but they don't do any other processing + if ( client.clientState == SCS_ZOMBIE ) { + return false; + } + + client.lastPacketTime = serverTime; + + ProcessReliableClientMessages( i ); + ProcessUnreliableClientMessage( i, msg ); + + return false; + } + + // if we received a sequenced packet from an address we don't recognize, + // send an out of band disconnect packet to it + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "disconnect" ); + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + + return false; +} + +/* +================== +idAsyncServer::SendReliableGameMessage +================== +*/ +void idAsyncServer::SendReliableGameMessage( int clientNum, const idBitMsg &msg ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_GAME ); + outMsg.WriteData( msg.GetData(), msg.GetSize() ); + + if ( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS ) { + if ( clients[clientNum].clientState == SCS_INGAME ) { + SendReliableMessage( clientNum, outMsg ); + } + return; + } + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState != SCS_INGAME ) { + continue; + } + SendReliableMessage( i, outMsg ); + } +} + +/* +================== +idAsyncServer::LocalClientSendReliableMessageExcluding +================== +*/ +void idAsyncServer::SendReliableGameMessageExcluding( int clientNum, const idBitMsg &msg ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_MESSAGE_SIZE]; + + assert( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS ); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( SERVER_RELIABLE_MESSAGE_GAME ); + outMsg.WriteData( msg.GetData(), msg.GetSize() ); + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( i == clientNum ) { + continue; + } + if ( clients[i].clientState != SCS_INGAME ) { + continue; + } + SendReliableMessage( i, outMsg ); + } +} + +/* +================== +idAsyncServer::LocalClientSendReliableMessage +================== +*/ +void idAsyncServer::LocalClientSendReliableMessage( const idBitMsg &msg ) { + if ( localClientNum < 0 ) { + common->Printf( "LocalClientSendReliableMessage: no local client\n" ); + return; + } + game->ServerProcessReliableMessage( localClientNum, msg ); +} + +/* +================== +idAsyncServer::ProcessConnectionLessMessages +================== +*/ +void idAsyncServer::ProcessConnectionLessMessages( void ) { + int size, id; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + netadr_t from; + + if ( !serverPort.GetPort() ) { + return; + } + + while( serverPort.GetPacket( from, msgBuf, size, sizeof( msgBuf ) ) ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.SetSize( size ); + msg.BeginReading(); + id = msg.ReadShort(); + if ( id == CONNECTIONLESS_MESSAGE_ID ) { + ConnectionlessMessage( from, msg ); + } + } +} + +/* +================== +idAsyncServer::UpdateTime +================== +*/ +int idAsyncServer::UpdateTime( int clamp ) { + int time, msec; + + time = Sys_Milliseconds(); + msec = idMath::ClampInt( 0, clamp, time - realTime ); + realTime = time; + serverTime += msec; + return msec; +} + +/* +================== +idAsyncServer::RunFrame +================== +*/ +void idAsyncServer::RunFrame( void ) { + const int userCmdMSec = common->GetUserCmdMSec(); + int i, msec, size; + bool newPacket; + idBitMsg msg; + byte msgBuf[MAX_MESSAGE_SIZE]; + netadr_t from; + int outgoingRate, incomingRate; + float outgoingCompression, incomingCompression; + + msec = UpdateTime( 100 ); + + if ( !serverPort.GetPort() ) { + return; + } + + if ( !active ) { + ProcessConnectionLessMessages(); + return; + } + + gameTimeResidual += msec; + + // spin in place processing incoming packets until enough time lapsed to run a new game frame + do { + + do { + + // blocking read with game time residual timeout + newPacket = serverPort.GetPacketBlocking( from, msgBuf, size, sizeof( msgBuf ), userCmdMSec - gameTimeResidual - 1 ); + if ( newPacket ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.SetSize( size ); + msg.BeginReading(); + if ( ProcessMessage( from, msg ) ) { + return; // return because rcon was used + } + } + + msec = UpdateTime( 100 ); + gameTimeResidual += msec; + + } while( newPacket ); + + } while( gameTimeResidual < userCmdMSec ); + + // send heart beat to master servers + MasterHeartbeat(); + + // check for clients that timed out + CheckClientTimeouts(); + + if ( idAsyncNetwork::idleServer.GetBool() == ( !GetNumClients() || GetNumIdleClients() != GetNumClients() ) ) { + idAsyncNetwork::idleServer.SetBool( !idAsyncNetwork::idleServer.GetBool() ); + // the need to propagate right away, only this + sessLocal.mapSpawnData.serverInfo.Set( "si_idleServer", idAsyncNetwork::idleServer.GetString() ); + game->SetServerInfo( sessLocal.mapSpawnData.serverInfo ); + } + + // make sure the time doesn't wrap + if ( serverTime > 0x70000000 ) { + ExecuteMapChange(); + return; + } + + // check for synchronized cvar changes + if ( cvarSystem->GetModifiedFlags() & CVAR_NETWORKSYNC ) { + idDict newCvars; + newCvars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC ); + SendSyncedCvarsBroadcast( newCvars ); + cvarSystem->ClearModifiedFlags( CVAR_NETWORKSYNC ); + } + + // check for user info changes of the local client + if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) { + if ( localClientNum >= 0 ) { + idDict newInfo; + game->ThrottleUserInfo( ); + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO ); + SendUserInfoBroadcast( localClientNum, newInfo ); + } + cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); + } + + // advance the server game + while( gameTimeResidual >= userCmdMSec ) { + + // sample input for the local client + LocalClientInput(); + + // duplicate usercmds for clients if no new ones are available + DuplicateUsercmds( gameFrame, gameTime ); + + // advance game + const bool lastCatchupFrame = gameTimeResidual < userCmdMSec * 2; + gameReturn_t ret = game->RunFrame( userCmds[gameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], com_editors, lastCatchupFrame, gameFrame ); + + idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand ); + + // update time + gameFrame++; + gameTime += userCmdMSec; + gameTimeResidual -= userCmdMSec; + } + + // duplicate usercmds so there is always at least one available to send with snapshots + DuplicateUsercmds( gameFrame, gameTime ); + + // send snapshots to connected clients + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + serverClient_t &client = clients[i]; + + if ( client.clientState == SCS_FREE || i == localClientNum ) { + continue; + } + + // modify maximum rate if necesary + if ( idAsyncNetwork::serverMaxClientRate.IsModified() ) { + client.channel.SetMaxOutgoingRate( Min( client.clientRate, idAsyncNetwork::serverMaxClientRate.GetInteger() ) ); + } + + // if the channel is not yet ready to send new data + if ( !client.channel.ReadyToSend( serverTime ) ) { + continue; + } + + // send additional message fragments if the last message was too large to send at once + if ( client.channel.UnsentFragmentsLeft() ) { + client.channel.SendNextFragment( serverPort, serverTime ); + continue; + } + + if ( client.clientState == SCS_INGAME ) { + if ( !SendSnapshotToClient( i ) ) { + SendPingToClient( i ); + } + } else { + SendEmptyToClient( i ); + } + } + + if ( com_showAsyncStats.GetBool() ) { + + UpdateAsyncStatsAvg(); + + // dedicated will verbose to console + if ( idAsyncNetwork::serverDedicated.GetBool() && serverTime >= nextAsyncStatsTime ) { + common->Printf( "delay = %d msec, total outgoing rate = %d KB/s, total incoming rate = %d KB/s\n", GetDelay(), + GetOutgoingRate() >> 10, GetIncomingRate() >> 10 ); + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + + outgoingRate = GetClientOutgoingRate( i ); + incomingRate = GetClientIncomingRate( i ); + outgoingCompression = GetClientOutgoingCompression( i ); + incomingCompression = GetClientIncomingCompression( i ); + + if ( outgoingRate != -1 && incomingRate != -1 ) { + common->Printf( "client %d: out rate = %d B/s (% -2.1f%%), in rate = %d B/s (% -2.1f%%)\n", + i, outgoingRate, outgoingCompression, incomingRate, incomingCompression ); + } + } + + idStr msg; + GetAsyncStatsAvgMsg( msg ); + common->Printf( va( "%s\n", msg.c_str() ) ); + + nextAsyncStatsTime = serverTime + 1000; + } + } + + idAsyncNetwork::serverMaxClientRate.ClearModified(); +} + +/* +================== +idAsyncServer::PacifierUpdate +================== +*/ +void idAsyncServer::PacifierUpdate( void ) { + int i; + + if ( !IsActive() ) { + return; + } + realTime = Sys_Milliseconds(); + ProcessConnectionLessMessages(); + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + if ( clients[i].clientState >= SCS_PUREWAIT ) { + if ( clients[i].channel.UnsentFragmentsLeft() ) { + clients[i].channel.SendNextFragment( serverPort, serverTime ); + } else { + SendEmptyToClient( i ); + } + } + } +} + +/* +================== +idAsyncServer::PrintOOB +================== +*/ +void idAsyncServer::PrintOOB( const netadr_t to, int opcode, const char *string ) { + idBitMsg outMsg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "print" ); + outMsg.WriteLong( opcode ); + outMsg.WriteString( string ); + serverPort.SendPacket( to, outMsg.GetData(), outMsg.GetSize() ); +} + +/* +================== +idAsyncServer::MasterHeartbeat +================== +*/ +void idAsyncServer::MasterHeartbeat( bool force ) { + if ( idAsyncNetwork::LANServer.GetBool() ) { + if ( force ) { + common->Printf( "net_LANServer is enabled. Not sending heartbeats\n" ); + } + return; + } + if ( force ) { + nextHeartbeatTime = 0; + } + // not yet + if ( serverTime < nextHeartbeatTime ) { + return; + } + nextHeartbeatTime = serverTime + HEARTBEAT_MSEC; + for ( int i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) { + netadr_t adr; + if ( idAsyncNetwork::GetMasterAddress( i, adr ) ) { + common->Printf( "Sending heartbeat to %s\n", Sys_NetAdrToString( adr ) ); + idBitMsg outMsg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "heartbeat" ); + serverPort.SendPacket( adr, outMsg.GetData(), outMsg.GetSize() ); + } + } +} + +/* +=============== +idAsyncServer::SendEnterGameToClient +=============== +*/ +void idAsyncServer::SendEnterGameToClient( int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( SERVER_RELIABLE_MESSAGE_ENTERGAME ); + SendReliableMessage( clientNum, msg ); +} + +/* +=============== +idAsyncServer::UpdateAsyncStatsAvg +=============== +*/ +void idAsyncServer::UpdateAsyncStatsAvg( void ) { + stats_average_sum -= stats_outrate[ stats_current ]; + stats_outrate[ stats_current ] = idAsyncNetwork::server.GetOutgoingRate(); + if ( stats_outrate[ stats_current ] > stats_max ) { + stats_max = stats_outrate[ stats_current ]; + stats_max_index = stats_current; + } else if ( stats_current == stats_max_index ) { + // find the new max + int i; + stats_max = 0; + for ( i = 0; i < stats_numsamples ; i++ ) { + if ( stats_outrate[ i ] > stats_max ) { + stats_max = stats_outrate[ i ]; + stats_max_index = i; + } + } + } + stats_average_sum += stats_outrate[ stats_current ]; + stats_current++; stats_current %= stats_numsamples; +} + +/* +=============== +idAsyncServer::GetAsyncStatsAvgMsg +=============== +*/ +void idAsyncServer::GetAsyncStatsAvgMsg( idStr &msg ) { + sprintf( msg, "avrg out: %d B/s - max %d B/s ( over %d ms )", stats_average_sum / stats_numsamples, stats_max, idAsyncNetwork::serverSnapshotDelay.GetInteger() * stats_numsamples ); +} + +/* +=============== +idAsyncServer::ProcessDownloadRequestMessage +=============== +*/ +void idAsyncServer::ProcessDownloadRequestMessage( const netadr_t from, const idBitMsg &msg ) { + int challenge, clientId, iclient, numPaks, i; + int dlGamePak; + int dlPakChecksum; + int dlSize[ MAX_PURE_PAKS ]; // sizes + idStrList pakNames; // relative path + idStrList pakURLs; // game URLs + char pakbuf[ MAX_STRING_CHARS ]; + idStr paklist; + byte msgBuf[ MAX_MESSAGE_SIZE ]; + byte tmpBuf[ MAX_MESSAGE_SIZE ]; + idBitMsg outMsg, tmpMsg; + int dlRequest; + int voidSlots = 0; // to count and verbose the right number of paks requested for downloads + + challenge = msg.ReadLong(); + clientId = msg.ReadShort(); + dlRequest = msg.ReadLong(); + + if ( ( iclient = ValidateChallenge( from, challenge, clientId ) ) == -1 ) { + return; + } + + if ( challenges[ iclient ].authState != CDK_PUREWAIT ) { + common->DPrintf( "client %s: got download request message, not in CDK_PUREWAIT\n", Sys_NetAdrToString( from ) ); + return; + } + + // the first token of the pak names list passed to the game will be empty if no game pak is requested + dlGamePak = msg.ReadLong(); + if ( dlGamePak ) { + if ( !( dlSize[ 0 ] = fileSystem->ValidateDownloadPakForChecksum( dlGamePak, pakbuf, true ) ) ) { + common->Warning( "client requested unknown game pak 0x%x", dlGamePak ); + pakbuf[ 0 ] = '\0'; + voidSlots++; + } + } else { + pakbuf[ 0 ] = '\0'; + voidSlots++; + } + pakNames.Append( pakbuf ); + numPaks = 1; + + // read the checksums, build path names and pass that to the game code + dlPakChecksum = msg.ReadLong(); + while ( dlPakChecksum ) { + if ( !( dlSize[ numPaks ] = fileSystem->ValidateDownloadPakForChecksum( dlPakChecksum, pakbuf, false ) ) ) { + // we pass an empty token to the game so our list doesn't get offset + common->Warning( "client requested an unknown pak 0x%x", dlPakChecksum ); + pakbuf[ 0 ] = '\0'; + voidSlots++; + } + pakNames.Append( pakbuf ); + numPaks++; + dlPakChecksum = msg.ReadLong(); + } + + for ( i = 0; i < pakNames.Num(); i++ ) { + if ( i > 0 ) { + paklist += ";"; + } + paklist += pakNames[ i ].c_str(); + } + + // read the message and pass it to the game code + common->DPrintf( "got download request for %d paks - %s\n", numPaks - voidSlots, paklist.c_str() ); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteShort( CONNECTIONLESS_MESSAGE_ID ); + outMsg.WriteString( "downloadInfo" ); + outMsg.WriteLong( dlRequest ); + if ( !game->DownloadRequest( Sys_NetAdrToString( from ), challenges[ iclient ].guid, paklist.c_str(), pakbuf ) ) { + common->DPrintf( "game: no downloads\n" ); + outMsg.WriteByte( SERVER_DL_NONE ); + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + return; + } + + char *token, *next; + int type = 0; + + token = pakbuf; + next = strchr( token, ';' ); + while ( token ) { + if ( next ) { + *next = '\0'; + } + + if ( type == 0 ) { + type = atoi( token ); + } else if ( type == SERVER_DL_REDIRECT ) { + common->DPrintf( "download request: redirect to URL %s\n", token ); + outMsg.WriteByte( SERVER_DL_REDIRECT ); + outMsg.WriteString( token ); + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + return; + } else if ( type == SERVER_DL_LIST ) { + pakURLs.Append( token ); + } else { + common->DPrintf( "wrong op type %d\n", type ); + next = token = NULL; + } + + if ( next ) { + token = next + 1; + next = strchr( token, ';' ); + } else { + token = NULL; + } + } + + if ( type == SERVER_DL_LIST ) { + int totalDlSize = 0; + int numActualPaks = 0; + + // put the answer packet together + outMsg.WriteByte( SERVER_DL_LIST ); + + tmpMsg.Init( tmpBuf, MAX_MESSAGE_SIZE ); + + for ( i = 0; i < pakURLs.Num(); i++ ) { + tmpMsg.BeginWriting(); + if ( !dlSize[ i ] || !pakURLs[ i ].Length() ) { + // still send the relative path so the client knows what it missed + tmpMsg.WriteByte( SERVER_PAK_NO ); + tmpMsg.WriteString( pakNames[ i ] ); + } else { + totalDlSize += dlSize[ i ]; + numActualPaks++; + tmpMsg.WriteByte( SERVER_PAK_YES ); + tmpMsg.WriteString( pakNames[ i ] ); + tmpMsg.WriteString( pakURLs[ i ] ); + tmpMsg.WriteLong( dlSize[ i ] ); + } + + // keep last 5 bytes for an 'end of message' - SERVER_PAK_END and the totalDlSize long + if ( outMsg.GetRemainingSpace() - tmpMsg.GetSize() > 5 ) { + outMsg.WriteData( tmpMsg.GetData(), tmpMsg.GetSize() ); + } else { + outMsg.WriteByte( SERVER_PAK_END ); + break; + } + } + if ( i == pakURLs.Num() ) { + // put a closure even if size not exceeded + outMsg.WriteByte( SERVER_PAK_END ); + } + common->DPrintf( "download request: download %d paks, %d bytes\n", numActualPaks, totalDlSize ); + + serverPort.SendPacket( from, outMsg.GetData(), outMsg.GetSize() ); + } +} diff --git a/src/framework/async/msgchannel.cpp b/src/framework/async/msgchannel.cpp new file mode 100644 index 0000000..1e330c7 --- /dev/null +++ b/src/framework/async/msgchannel.cpp @@ -0,0 +1,790 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "MsgChannel.h" +#include "../Compressor.h" + +/* + +packet header +------------- +2 bytes id +4 bytes outgoing sequence. high bit will be set if this is a fragmented message. +2 bytes optional fragment start byte if fragment bit is set. +2 bytes optional fragment length if fragment bit is set. if < FRAGMENT_SIZE, this is the last fragment. + +If the id is -1, the packet should be handled as an out-of-band +message instead of as part of the message channel. + +All fragments will have the same sequence numbers. + +*/ + + +#define MAX_PACKETLEN 1400 // max size of a network packet +#define FRAGMENT_SIZE (MAX_PACKETLEN - 100) +#define FRAGMENT_BIT (1<<31) + +idCVar net_channelShowPackets( "net_channelShowPackets", "0", CVAR_SYSTEM | CVAR_BOOL, "show all packets" ); +idCVar net_channelShowDrop( "net_channelShowDrop", "0", CVAR_SYSTEM | CVAR_BOOL, "show dropped packets" ); + +#if !defined( Q4_RECON_SDK_MSGQUEUE ) +/* +=============== +idMsgQueue::idMsgQueue +=============== +*/ +idMsgQueue::idMsgQueue( void ) { + Init( 0 ); +} + +/* +=============== +idMsgQueue::Init +=============== +*/ +void idMsgQueue::Init( int sequence ) { + first = last = sequence; + startIndex = endIndex = 0; +} + +/* +=============== +idMsgQueue::Add +=============== +*/ +bool idMsgQueue::Add( const byte *data, const int size ) { + if ( GetSpaceLeft() < size + 8 ) { + return false; + } + int sequence = last; + WriteShort( size ); + WriteLong( sequence ); + WriteData( data, size ); + last++; + return true; +} + +/* +=============== +idMsgQueue::Get +=============== +*/ +bool idMsgQueue::Get( byte *data, int &size ) { + if ( first == last ) { + size = 0; + return false; + } + int sequence; + size = ReadShort(); + sequence = ReadLong(); + ReadData( data, size ); + assert( sequence == first ); + first++; + return true; +} + +/* +=============== +idMsgQueue::GetTotalSize +=============== +*/ +int idMsgQueue::GetTotalSize( void ) const { + if ( startIndex <= endIndex ) { + return ( endIndex - startIndex ); + } else { + return ( sizeof( buffer ) - startIndex + endIndex ); + } +} + +/* +=============== +idMsgQueue::GetSpaceLeft +=============== +*/ +int idMsgQueue::GetSpaceLeft( void ) const { + if ( startIndex <= endIndex ) { + return sizeof( buffer ) - ( endIndex - startIndex ) - 1; + } else { + return ( startIndex - endIndex ) - 1; + } +} + +/* +=============== +idMsgQueue::CopyToBuffer +=============== +*/ +void idMsgQueue::CopyToBuffer( byte *buf ) const { + if ( startIndex <= endIndex ) { + memcpy( buf, buffer + startIndex, endIndex - startIndex ); + } else { + memcpy( buf, buffer + startIndex, sizeof( buffer ) - startIndex ); + memcpy( buf + sizeof( buffer ) - startIndex, buffer, endIndex ); + } +} + +/* +=============== +idMsgQueue::WriteByte +=============== +*/ +void idMsgQueue::WriteByte( byte b ) { + buffer[endIndex] = b; + endIndex = ( endIndex + 1 ) & ( MAX_MSG_QUEUE_SIZE - 1 ); +} + +/* +=============== +idMsgQueue::ReadByte +=============== +*/ +byte idMsgQueue::ReadByte( void ) { + byte b = buffer[startIndex]; + startIndex = ( startIndex + 1 ) & ( MAX_MSG_QUEUE_SIZE - 1 ); + return b; +} + +/* +=============== +idMsgQueue::WriteShort +=============== +*/ +void idMsgQueue::WriteShort( int s ) { + WriteByte( ( s >> 0 ) & 255 ); + WriteByte( ( s >> 8 ) & 255 ); +} + +/* +=============== +idMsgQueue::ReadShort +=============== +*/ +int idMsgQueue::ReadShort( void ) { + return ReadByte() | ( ReadByte() << 8 ); +} + +/* +=============== +idMsgQueue::WriteLong +=============== +*/ +void idMsgQueue::WriteLong( int l ) { + WriteByte( ( l >> 0 ) & 255 ); + WriteByte( ( l >> 8 ) & 255 ); + WriteByte( ( l >> 16 ) & 255 ); + WriteByte( ( l >> 24 ) & 255 ); +} + +/* +=============== +idMsgQueue::ReadLong +=============== +*/ +int idMsgQueue::ReadLong( void ) { + return ReadByte() | ( ReadByte() << 8 ) | ( ReadByte() << 16 ) | ( ReadByte() << 24 ); +} + +/* +=============== +idMsgQueue::WriteData +=============== +*/ +void idMsgQueue::WriteData( const byte *data, const int size ) { + for ( int i = 0; i < size; i++ ) { + WriteByte( data[i] ); + } +} + +/* +=============== +idMsgQueue::ReadData +=============== +*/ +void idMsgQueue::ReadData( byte *data, const int size ) { + if ( data ) { + for ( int i = 0; i < size; i++ ) { + data[i] = ReadByte(); + } + } else { + for ( int i = 0; i < size; i++ ) { + ReadByte(); + } + } +} + +#endif + + +/* +=============== +idMsgChannel::idMsgChannel +=============== +*/ +idMsgChannel::idMsgChannel() { + id = -1; +} + +/* +============== +idMsgChannel::Init + + Opens a channel to a remote system. +============== +*/ +void idMsgChannel::Init( const netadr_t adr, const int id ) { + this->remoteAddress = adr; + this->id = id; + this->maxRate = 50000; + this->compressor = idCompressor::AllocRunLength_ZeroBased(); + + lastSendTime = 0; + lastDataBytes = 0; + outgoingRateTime = 0; + outgoingRateBytes = 0; + incomingRateTime = 0; + incomingRateBytes = 0; + incomingReceivedPackets = 0.0f; + incomingDroppedPackets = 0.0f; + incomingPacketLossTime = 0; + outgoingCompression = 0.0f; + incomingCompression = 0.0f; + outgoingSequence = 1; + incomingSequence = 0; + unsentFragments = false; + unsentFragmentStart = 0; + fragmentSequence = 0; + fragmentLength = 0; + reliableSend.Init( 1 ); + reliableReceive.Init( 0 ); +} + +/* +=============== +idMsgChannel::Shutdown +================ +*/ +void idMsgChannel::Shutdown( void ) { + delete compressor; + compressor = NULL; +} + +/* +================= +idMsgChannel::ResetRate +================= +*/ +void idMsgChannel::ResetRate( void ) { + lastSendTime = 0; + lastDataBytes = 0; + outgoingRateTime = 0; + outgoingRateBytes = 0; + incomingRateTime = 0; + incomingRateBytes = 0; +} + +/* +================= +idMsgChannel::ReadyToSend +================= +*/ +bool idMsgChannel::ReadyToSend( const int time ) const { + int deltaTime; + + if ( !maxRate ) { + return true; + } + deltaTime = time - lastSendTime; + if ( deltaTime > 1000 ) { + return true; + } + return ( ( lastDataBytes - ( deltaTime * maxRate ) / 1000 ) <= 0 ); +} + +/* +=============== +idMsgChannel::WriteMessageData +================ +*/ +void idMsgChannel::WriteMessageData( idBitMsg &out, const idBitMsg &msg ) { + idBitMsg tmp; + byte tmpBuf[MAX_MESSAGE_SIZE]; + + tmp.Init( tmpBuf, sizeof( tmpBuf ) ); + + // write acknowledgement of last received reliable message + tmp.WriteLong( reliableReceive.GetLast() ); + + // write reliable messages + reliableSend.CopyToBuffer( tmp.GetData() + tmp.GetSize() ); + tmp.SetSize( tmp.GetSize() + reliableSend.GetTotalSize() ); + tmp.WriteShort( 0 ); + + // write data + tmp.WriteData( msg.GetData(), msg.GetSize() ); + + // write message size + out.WriteShort( tmp.GetSize() ); + + // compress message + idFile_BitMsg file( out ); + compressor->Init( &file, true, 3 ); + compressor->Write( tmp.GetData(), tmp.GetSize() ); + compressor->FinishCompress(); + outgoingCompression = compressor->GetCompressionRatio(); +} + +/* +=============== +idMsgChannel::ReadMessageData +================ +*/ +bool idMsgChannel::ReadMessageData( idBitMsg &out, const idBitMsg &msg ) { + int reliableAcknowledge, reliableMessageSize, reliableSequence; + + // read message size + out.SetSize( msg.ReadShort() ); + + // decompress message + idFile_BitMsg file( msg ); + compressor->Init( &file, false, 3 ); + compressor->Read( out.GetData(), out.GetSize() ); + incomingCompression = compressor->GetCompressionRatio(); + out.BeginReading(); + + // read acknowledgement of sent reliable messages + reliableAcknowledge = out.ReadLong(); + + // remove acknowledged reliable messages + while( reliableSend.GetFirst() <= reliableAcknowledge ) { + if ( !reliableSend.Get( NULL, 0, reliableMessageSize, true ) ) { + break; + } + } + + // read reliable messages + reliableMessageSize = out.ReadShort(); + while( reliableMessageSize != 0 ) { + if ( reliableMessageSize <= 0 || reliableMessageSize > out.GetSize() - out.GetReadCount() ) { + common->Printf( "%s: bad reliable message\n", Sys_NetAdrToString( remoteAddress ) ); + return false; + } + reliableSequence = out.ReadLong(); + if ( reliableSequence == reliableReceive.GetLast() + 1 ) { + reliableReceive.Add( out.GetData() + out.GetReadCount(), reliableMessageSize, true ); + } + out.ReadData( NULL, reliableMessageSize ); + reliableMessageSize = out.ReadShort(); + } + + return true; +} + +/* +================= +idMsgChannel::SendNextFragment + + Sends one fragment of the current message. +================= +*/ +void idMsgChannel::SendNextFragment( idPort &port, const int time ) { + idBitMsg msg; + byte msgBuf[MAX_PACKETLEN]; + int fragLength; + + if ( remoteAddress.type == NA_BAD ) { + return; + } + + if ( !unsentFragments ) { + return; + } + + // write the packet + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( id ); + msg.WriteLong( outgoingSequence | FRAGMENT_BIT ); + + fragLength = FRAGMENT_SIZE; + if ( unsentFragmentStart + fragLength > unsentMsg.GetSize() ) { + fragLength = unsentMsg.GetSize() - unsentFragmentStart; + } + + msg.WriteShort( unsentFragmentStart ); + msg.WriteShort( fragLength ); + msg.WriteData( unsentMsg.GetData() + unsentFragmentStart, fragLength ); + + // send the packet + port.SendPacket( remoteAddress, msg.GetData(), msg.GetSize() ); + + // update rate control variables + UpdateOutgoingRate( time, msg.GetSize() ); + + if ( net_channelShowPackets.GetBool() ) { + common->Printf( "%d send %4i : s = %i fragment = %i,%i\n", id, msg.GetSize(), outgoingSequence - 1, unsentFragmentStart, fragLength ); + } + + unsentFragmentStart += fragLength; + + // this exit condition is a little tricky, because a packet + // that is exactly the fragment length still needs to send + // a second packet of zero length so that the other side + // can tell there aren't more to follow + if ( unsentFragmentStart == unsentMsg.GetSize() && fragLength != FRAGMENT_SIZE ) { + outgoingSequence++; + unsentFragments = false; + } +} + +/* +=============== +idMsgChannel::SendMessage + + Sends a message to a connection, fragmenting if necessary + A 0 length will still generate a packet. +================ +*/ +int idMsgChannel::SendMessage( idPort &port, const int time, const idBitMsg &msg ) { + int totalLength; + + if ( remoteAddress.type == NA_BAD ) { + return -1; + } + + if ( unsentFragments ) { + common->Error( "idMsgChannel::SendMessage: called with unsent fragments left" ); + return -1; + } + + totalLength = 4 + reliableSend.GetTotalSize() + 4 + msg.GetSize(); + + if ( totalLength > MAX_MESSAGE_SIZE ) { + common->Printf( "idMsgChannel::SendMessage: message too large, length = %i\n", totalLength ); + return -1; + } + + unsentMsg.Init( unsentBuffer, sizeof( unsentBuffer ) ); + unsentMsg.BeginWriting(); + + // fragment large messages + if ( totalLength >= FRAGMENT_SIZE ) { + unsentFragments = true; + unsentFragmentStart = 0; + + // write out the message data + WriteMessageData( unsentMsg, msg ); + + // send the first fragment now + SendNextFragment( port, time ); + + return outgoingSequence; + } + + // write the header + unsentMsg.WriteShort( id ); + unsentMsg.WriteLong( outgoingSequence ); + + // write out the message data + WriteMessageData( unsentMsg, msg ); + + // send the packet + port.SendPacket( remoteAddress, unsentMsg.GetData(), unsentMsg.GetSize() ); + + // update rate control variables + UpdateOutgoingRate( time, unsentMsg.GetSize() ); + + if ( net_channelShowPackets.GetBool() ) { + common->Printf( "%d send %4i : s = %i ack = %i\n", id, unsentMsg.GetSize(), outgoingSequence - 1, incomingSequence ); + } + + outgoingSequence++; + + return ( outgoingSequence - 1 ); +} + +/* +================= +idMsgChannel::Process + + Returns false if the message should not be processed due to being out of order or a fragment. + + msg must be large enough to hold MAX_MESSAGE_SIZE, because if this is the final + fragment of a multi-part message, the entire thing will be copied out. +================= +*/ +bool idMsgChannel::Process( const netadr_t from, int time, idBitMsg &msg, int &sequence ) { + int fragStart, fragLength, dropped; + bool fragmented; + idBitMsg fragMsg; + + // the IP port can't be used to differentiate them, because + // some address translating routers periodically change UDP + // port assignments + if ( remoteAddress.port != from.port ) { + common->Printf( "idMsgChannel::Process: fixing up a translated port\n" ); + remoteAddress.port = from.port; + } + + // update incoming rate + UpdateIncomingRate( time, msg.GetSize() ); + + // get sequence numbers + sequence = msg.ReadLong(); + + // check for fragment information + if ( sequence & FRAGMENT_BIT ) { + sequence &= ~FRAGMENT_BIT; + fragmented = true; + } else { + fragmented = false; + } + + // read the fragment information + if ( fragmented ) { + fragStart = msg.ReadShort(); + fragLength = msg.ReadShort(); + } else { + fragStart = 0; // stop warning message + fragLength = 0; + } + + if ( net_channelShowPackets.GetBool() ) { + if ( fragmented ) { + common->Printf( "%d recv %4i : s = %i fragment = %i,%i\n", id, msg.GetSize(), sequence, fragStart, fragLength ); + } else { + common->Printf( "%d recv %4i : s = %i\n", id, msg.GetSize(), sequence ); + } + } + + // + // discard out of order or duplicated packets + // + if ( sequence <= incomingSequence ) { + if ( net_channelShowDrop.GetBool() || net_channelShowPackets.GetBool() ) { + common->Printf( "%s: out of order packet %i at %i\n", Sys_NetAdrToString( remoteAddress ), sequence, incomingSequence ); + } + return false; + } + + // + // dropped packets don't keep this message from being used + // + dropped = sequence - (incomingSequence+1); + if ( dropped > 0 ) { + if ( net_channelShowDrop.GetBool() || net_channelShowPackets.GetBool() ) { + common->Printf( "%s: dropped %i packets at %i\n", Sys_NetAdrToString( remoteAddress ), dropped, sequence ); + } + UpdatePacketLoss( time, 0, dropped ); + } + + // + // if the message is fragmented + // + if ( fragmented ) { + // make sure we have the correct sequence number + if ( sequence != fragmentSequence ) { + fragmentSequence = sequence; + fragmentLength = 0; + } + + // if we missed a fragment, dump the message + if ( fragStart != fragmentLength ) { + if ( net_channelShowDrop.GetBool() || net_channelShowPackets.GetBool() ) { + common->Printf( "%s: dropped a message fragment at seq %d\n", Sys_NetAdrToString( remoteAddress ), sequence ); + } + // we can still keep the part that we have so far, + // so we don't need to clear fragmentLength + UpdatePacketLoss( time, 0, 1 ); + return false; + } + + // copy the fragment to the fragment buffer + if ( fragLength < 0 || fragLength > msg.GetRemainingData() || fragmentLength + fragLength > sizeof( fragmentBuffer ) ) { + if ( net_channelShowDrop.GetBool() || net_channelShowPackets.GetBool() ) { + common->Printf( "%s: illegal fragment length\n", Sys_NetAdrToString( remoteAddress ) ); + } + UpdatePacketLoss( time, 0, 1 ); + return false; + } + + memcpy( fragmentBuffer + fragmentLength, msg.GetData() + msg.GetReadCount(), fragLength ); + + fragmentLength += fragLength; + + UpdatePacketLoss( time, 1, 0 ); + + // if this wasn't the last fragment, don't process anything + if ( fragLength == FRAGMENT_SIZE ) { + return false; + } + + } else { + memcpy( fragmentBuffer, msg.GetData() + msg.GetReadCount(), msg.GetRemainingData() ); + fragmentLength = msg.GetRemainingData(); + UpdatePacketLoss( time, 1, 0 ); + } + + fragMsg.Init( fragmentBuffer, fragmentLength ); + fragMsg.SetSize( fragmentLength ); + fragMsg.BeginReading(); + + incomingSequence = sequence; + + // read the message data + if ( !ReadMessageData( msg, fragMsg ) ) { + return false; + } + + return true; +} + +/* +================= +idMsgChannel::SendReliableMessage +================= +*/ +bool idMsgChannel::SendReliableMessage( const idBitMsg &msg ) { + bool result; + + assert( remoteAddress.type != NA_BAD ); + if ( remoteAddress.type == NA_BAD ) { + return false; + } + result = reliableSend.Add( msg.GetData(), msg.GetSize(), true ); + if ( !result ) { + common->Warning( "idMsgChannel::SendReliableMessage: overflowed" ); + return false; + } + return result; +} + +/* +================= +idMsgChannel::GetReliableMessage +================= +*/ +bool idMsgChannel::GetReliableMessage( idBitMsg &msg ) { + int size; + bool result; + + result = reliableReceive.Get( msg.GetData(), msg.GetMaxSize(), size, true ); + msg.SetSize( size ); + msg.BeginReading(); + return result; +} + +/* +=============== +idMsgChannel::ClearReliableMessages +================ +*/ +void idMsgChannel::ClearReliableMessages( void ) { + reliableSend.Init( 1 ); + reliableReceive.Init( 0 ); +} + +/* +================= +idMsgChannel::UpdateOutgoingRate +================= +*/ +void idMsgChannel::UpdateOutgoingRate( const int time, const int size ) { + // update the outgoing rate control variables + int deltaTime = time - lastSendTime; + if ( deltaTime > 1000 ) { + lastDataBytes = 0; + } else { + lastDataBytes -= ( deltaTime * maxRate ) / 1000; + if ( lastDataBytes < 0 ) { + lastDataBytes = 0; + } + } + lastDataBytes += size; + lastSendTime = time; + + // update outgoing rate variables + if ( time - outgoingRateTime > 1000 ) { + outgoingRateBytes -= outgoingRateBytes * ( time - outgoingRateTime - 1000 ) / 1000; + if ( outgoingRateBytes < 0 ) { + outgoingRateBytes = 0; + } + } + outgoingRateTime = time - 1000; + outgoingRateBytes += size; +} + +/* +================= +idMsgChannel::UpdateIncomingRate +================= +*/ +void idMsgChannel::UpdateIncomingRate( const int time, const int size ) { + // update incoming rate variables + if ( time - incomingRateTime > 1000 ) { + incomingRateBytes -= incomingRateBytes * ( time - incomingRateTime - 1000 ) / 1000; + if ( incomingRateBytes < 0 ) { + incomingRateBytes = 0; + } + } + incomingRateTime = time - 1000; + incomingRateBytes += size; +} + +/* +================= +idMsgChannel::UpdatePacketLoss +================= +*/ +void idMsgChannel::UpdatePacketLoss( const int time, const int numReceived, const int numDropped ) { + // update incoming packet loss variables + if ( time - incomingPacketLossTime > 5000 ) { + float scale = ( time - incomingPacketLossTime - 5000 ) * ( 1.0f / 5000.0f ); + incomingReceivedPackets -= incomingReceivedPackets * scale; + if ( incomingReceivedPackets < 0.0f ) { + incomingReceivedPackets = 0.0f; + } + incomingDroppedPackets -= incomingDroppedPackets * scale; + if ( incomingDroppedPackets < 0.0f ) { + incomingDroppedPackets = 0.0f; + } + } + incomingPacketLossTime = time - 5000; + incomingReceivedPackets += numReceived; + incomingDroppedPackets += numDropped; +} + +/* +================= +idMsgChannel::GetIncomingPacketLoss +================= +*/ +float idMsgChannel::GetIncomingPacketLoss( void ) const { + if ( incomingReceivedPackets == 0.0f && incomingDroppedPackets == 0.0f ) { + return 0.0f; + } + return incomingDroppedPackets * 100.0f / ( incomingReceivedPackets + incomingDroppedPackets ); +} diff --git a/src/framework/async/networksystem.cpp b/src/framework/async/networksystem.cpp new file mode 100644 index 0000000..1b03919 --- /dev/null +++ b/src/framework/async/networksystem.cpp @@ -0,0 +1,303 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "NetworkSystem.h" + +idNetworkSystem networkSystemLocal; +idNetworkSystem * networkSystem = &networkSystemLocal; + +// The retail implementation stores this state in Raven's server-scan object. +// Keep the ABI-neutral state here until that UI scanner is reconstructed. +static bool networkFriendClients[MAX_ASYNC_CLIENTS]; + + +/* +================== +idNetworkSystem::ServerSendReliableMessage +================== +*/ +void idNetworkSystem::ServerSendReliableMessage( int clientNum, const idBitMsg &msg ) { + if ( idAsyncNetwork::server.IsActive() ) { + idAsyncNetwork::server.SendReliableGameMessage( clientNum, msg ); + } +} + +/* +================== +idNetworkSystem::ServerSendReliableMessageExcluding +================== +*/ +void idNetworkSystem::ServerSendReliableMessageExcluding( int clientNum, const idBitMsg &msg ) { + if ( idAsyncNetwork::server.IsActive() ) { + idAsyncNetwork::server.SendReliableGameMessageExcluding( clientNum, msg ); + } +} + +/* +================== +idNetworkSystem::ServerGetClientPing +================== +*/ +int idNetworkSystem::ServerGetClientPing( int clientNum ) { + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientPing( clientNum ); + } + return 0; +} + +/* +================== +idNetworkSystem::ServerGetClientPrediction +================== +*/ +int idNetworkSystem::ServerGetClientPrediction( int clientNum ) { + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientPrediction( clientNum ); + } + return 0; +} + +/* +================== +idNetworkSystem::ServerGetClientTimeSinceLastPacket +================== +*/ +int idNetworkSystem::ServerGetClientTimeSinceLastPacket( int clientNum ) { + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientTimeSinceLastPacket( clientNum ); + } + return 0; +} + +/* +================== +idNetworkSystem::ServerGetClientTimeSinceLastInput +================== +*/ +int idNetworkSystem::ServerGetClientTimeSinceLastInput( int clientNum ) { + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientTimeSinceLastInput( clientNum ); + } + return 0; +} + +/* +================== +idNetworkSystem::ServerGetClientOutgoingRate +================== +*/ +int idNetworkSystem::ServerGetClientOutgoingRate( int clientNum ) { + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientOutgoingRate( clientNum ); + } + return 0; +} + +/* +================== +idNetworkSystem::ServerGetClientIncomingRate +================== +*/ +int idNetworkSystem::ServerGetClientIncomingRate( int clientNum ) { + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientIncomingRate( clientNum ); + } + return 0; +} + +/* +================== +idNetworkSystem::ServerGetClientIncomingPacketLoss +================== +*/ +float idNetworkSystem::ServerGetClientIncomingPacketLoss( int clientNum ) { + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientIncomingPacketLoss( clientNum ); + } + return 0.0f; +} + +/* +================== +idNetworkSystem::ClientSendReliableMessage +================== +*/ +void idNetworkSystem::ClientSendReliableMessage( const idBitMsg &msg ) { + if ( idAsyncNetwork::client.IsActive() ) { + idAsyncNetwork::client.SendReliableGameMessage( msg ); + } else if ( idAsyncNetwork::server.IsActive() ) { + idAsyncNetwork::server.LocalClientSendReliableMessage( msg ); + } +} + +/* +================== +idNetworkSystem::ClientGetPrediction +================== +*/ +int idNetworkSystem::ClientGetPrediction( void ) { + if ( idAsyncNetwork::client.IsActive() ) { + return idAsyncNetwork::client.GetPrediction(); + } + return 0; +} + +/* +================== +idNetworkSystem::ClientGetTimeSinceLastPacket +================== +*/ +int idNetworkSystem::ClientGetTimeSinceLastPacket( void ) { + if ( idAsyncNetwork::client.IsActive() ) { + return idAsyncNetwork::client.GetTimeSinceLastPacket(); + } + return 0; +} + +/* +================== +idNetworkSystem::ClientGetOutgoingRate +================== +*/ +int idNetworkSystem::ClientGetOutgoingRate( void ) { + if ( idAsyncNetwork::client.IsActive() ) { + return idAsyncNetwork::client.GetOutgoingRate(); + } + return 0; +} + +/* +================== +idNetworkSystem::ClientGetIncomingRate +================== +*/ +int idNetworkSystem::ClientGetIncomingRate( void ) { + if ( idAsyncNetwork::client.IsActive() ) { + return idAsyncNetwork::client.GetIncomingRate(); + } + return 0; +} + +/* +================== +idNetworkSystem::ClientGetIncomingPacketLoss +================== +*/ +float idNetworkSystem::ClientGetIncomingPacketLoss( void ) { + if ( idAsyncNetwork::client.IsActive() ) { + return idAsyncNetwork::client.GetIncomingPacketLoss(); + } + return 0.0f; +} + +/* +================== +idNetworkSystem::GetServerAddress +================== +*/ +const char *idNetworkSystem::GetServerAddress( void ) { + if ( idAsyncNetwork::client.IsActive() ) { + return Sys_NetAdrToString( idAsyncNetwork::client.GetServerAddress() ); + } + if ( idAsyncNetwork::server.IsActive() ) { + return Sys_NetAdrToString( idAsyncNetwork::server.GetBoundAdr() ); + } + return ""; +} + +/* +================== +idNetworkSystem::GetClientAddress +================== +*/ +const char *idNetworkSystem::GetClientAddress( int clientNum ) { + if ( !idAsyncNetwork::server.IsActive() ) { + return ""; + } + return Sys_NetAdrToString( idAsyncNetwork::server.GetClientAddress( clientNum ) ); +} + +/* +================== +idNetworkSystem::AddFriend +idNetworkSystem::RemoveFriend +================== +*/ +void idNetworkSystem::AddFriend( int clientNum ) { + if ( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS ) { + networkFriendClients[clientNum] = true; + } +} + +void idNetworkSystem::RemoveFriend( int clientNum ) { + if ( clientNum >= 0 && clientNum < MAX_ASYNC_CLIENTS ) { + networkFriendClients[clientNum] = false; + } +} + +/* +================== +idNetworkSystem::SetLoadingText +idNetworkSystem::AddLoadingIcon +================== +*/ +void idNetworkSystem::SetLoadingText( const char *loadingText ) { + session->SetLoadingText( loadingText ); +} + +void idNetworkSystem::AddLoadingIcon( const char *icon ) { + session->AddLoadingIcon( icon ); +} + +/* +================== +idNetworkSystem::GetClientGUID +================== +*/ +const char *idNetworkSystem::GetClientGUID( int clientNum ) { + if ( clientNum < 0 || clientNum >= MAX_ASYNC_CLIENTS ) { + common->Warning( "idNetworkSystem::GetClientGUID() - Invalid clientNum '%d'\n", clientNum ); + return NULL; + } + if ( idAsyncNetwork::server.IsActive() ) { + return idAsyncNetwork::server.GetClientGUID( clientNum ); + } + return game->GetGuidByClientNum( clientNum ); +} + +/* +================== +idNetworkSystem::GetTrafficStats +================== +*/ +void idNetworkSystem::GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const { + if ( idAsyncNetwork::server.IsActive() ) { + idAsyncNetwork::server.GetTrafficStats( bytesSent, packetsSent, bytesReceived, packetsReceived ); + } else { + idAsyncNetwork::client.GetTrafficStats( bytesSent, packetsSent, bytesReceived, packetsReceived ); + } +} diff --git a/src/framework/async/serverscan.cpp b/src/framework/async/serverscan.cpp new file mode 100644 index 0000000..4b336a6 --- /dev/null +++ b/src/framework/async/serverscan.cpp @@ -0,0 +1,618 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +idCVar gui_filter_password( "gui_filter_password", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Password filter" ); +idCVar gui_filter_players( "gui_filter_players", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Players filter" ); +idCVar gui_filter_gameType( "gui_filter_gameType", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Gametype filter" ); +idCVar gui_filter_idle( "gui_filter_idle", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Idle servers filter" ); +idCVar gui_filter_game( "gui_filter_game", "0", CVAR_GUI | CVAR_INTEGER | CVAR_ARCHIVE, "Game filter" ); + +const char* l_gameTypes[] = { + "Deathmatch", + "Tourney", + "Team DM", + "Last Man", + "CTF", + NULL +}; + +static idServerScan *l_serverScan = NULL; + +/* +================ +idServerScan::idServerScan +================ +*/ +idServerScan::idServerScan( ) { + m_pGUI = NULL; + m_sort = SORT_PING; + m_sortAscending = true; + challenge = 0; + LocalClear(); +} + +/* +================ +idServerScan::LocalClear +================ +*/ +void idServerScan::LocalClear( ) { + scan_state = IDLE; + incoming_net = false; + lan_pingtime = -1; + net_info.Clear(); + net_servers.Clear(); + cur_info = 0; + if ( listGUI ) { + listGUI->Clear(); + } + incoming_useTimeout = false; + m_sortedServers.Clear(); +} + +/* +================ +idServerScan::Clear +================ +*/ +void idServerScan::Clear( ) { + LocalClear(); + idList::Clear(); +} + +/* +================ +idServerScan::Shutdown +================ +*/ +void idServerScan::Shutdown( ) { + m_pGUI = NULL; + if ( listGUI ) { + listGUI->Config( NULL, NULL ); + uiManager->FreeListGUI( listGUI ); + listGUI = NULL; + } + screenshot.Clear(); +} + +/* +================ +idServerScan::SetupLANScan +================ +*/ +void idServerScan::SetupLANScan( ) { + Clear(); + GUIUpdateSelected(); + scan_state = LAN_SCAN; + challenge++; + lan_pingtime = Sys_Milliseconds(); + common->DPrintf( "SetupLANScan with challenge %d\n", challenge ); +} + +/* +================ +idServerScan::InfoResponse +================ +*/ +int idServerScan::InfoResponse( networkServer_t &server ) { + if ( scan_state == IDLE ) { + return false; + } + + idStr serv = Sys_NetAdrToString( server.adr ); + + if ( server.challenge != challenge ) { + common->DPrintf( "idServerScan::InfoResponse - ignoring response from %s, wrong challenge %d.", serv.c_str(), server.challenge ); + return false; + } + + if ( scan_state == NET_SCAN ) { + const idKeyValue *info = net_info.FindKey( serv.c_str() ); + if ( !info ) { + common->DPrintf( "idServerScan::InfoResponse NET_SCAN: reply from unknown %s\n", serv.c_str() ); + return false; + } + int id = atoi( info->GetValue() ); + net_info.Delete( serv.c_str() ); + inServer_t iserv = net_servers[ id ]; + server.ping = Sys_Milliseconds() - iserv.time; + server.id = iserv.id; + } else { + server.ping = Sys_Milliseconds() - lan_pingtime; + server.id = 0; + + // check for duplicate servers + for ( int i = 0; i < Num() ; i++ ) { + if ( memcmp( &(*this)[ i ].adr, &server.adr, sizeof(netadr_t) ) == 0 ) { + common->DPrintf( "idServerScan::InfoResponse LAN_SCAN: duplicate server %s\n", serv.c_str() ); + return true; + } + } + } + + const char *si_map = server.serverInfo.GetString( "si_map" ); + const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, si_map, false ); + const idDeclEntityDef *mapDef = static_cast< const idDeclEntityDef * >( mapDecl ); + if ( mapDef ) { + const char *mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", si_map ) ); + server.serverInfo.Set( "si_mapName", mapName ); + } else { + server.serverInfo.Set( "si_mapName", si_map ); + } + + int index = Append( server ); + // for now, don't maintain sorting when adding new info response servers + m_sortedServers.Append( Num()-1 ); + if ( listGUI->IsConfigured( ) && !IsFiltered( server ) ) { + GUIAdd( Num()-1, server ); + } + if ( listGUI->GetSelection( NULL, 0 ) == ( Num()-1 ) ) { + GUIUpdateSelected(); + } + + return index; +} + +/* +================ +idServerScan::AddServer +================ +*/ +void idServerScan::AddServer( int id, const char *srv ) { + inServer_t s; + + incoming_net = true; + incoming_lastTime = Sys_Milliseconds() + INCOMING_TIMEOUT; + s.id = id; + + // using IPs, not hosts + if ( !Sys_StringToNetAdr( srv, &s.adr, false ) ) { + common->DPrintf( "idServerScan::AddServer: failed to parse server %s\n", srv ); + return; + } + if ( !s.adr.port ) { + s.adr.port = PORT_SERVER; + } + + net_servers.Append( s ); +} + +/* +================ +idServerScan::EndServers +================ +*/ +void idServerScan::EndServers( ) { + incoming_net = false; + l_serverScan = this; + m_sortedServers.Sort( idServerScan::Cmp ); + ApplyFilter(); +} + +/* +================ +idServerScan::StartServers +================ +*/ +void idServerScan::StartServers( bool timeout ) { + incoming_net = true; + incoming_useTimeout = timeout; + incoming_lastTime = Sys_Milliseconds() + REFRESH_START; +} + +/* +================ +idServerScan::EmitGetInfo +================ +*/ +void idServerScan::EmitGetInfo( netadr_t &serv ) { + idAsyncNetwork::client.GetServerInfo( serv ); +} + +/* +=============== +idServerScan::GetChallenge +=============== +*/ +int idServerScan::GetChallenge( ) { + return challenge; +} + +/* +================ +idServerScan::NetScan +================ +*/ +void idServerScan::NetScan( ) { + if ( !idAsyncNetwork::client.IsPortInitialized() ) { + // if the port isn't open, initialize it, but wait for a short + // time to let the OS do whatever magic things it needs to do... + idAsyncNetwork::client.InitPort(); + // start the scan one second from now... + scan_state = WAIT_ON_INIT; + endWaitTime = Sys_Milliseconds() + 1000; + return; + } + + // make sure the client port is open + idAsyncNetwork::client.InitPort(); + + scan_state = NET_SCAN; + challenge++; + + idList::Clear(); + m_sortedServers.Clear(); + cur_info = 0; + net_info.Clear(); + listGUI->Clear(); + GUIUpdateSelected(); + common->DPrintf( "NetScan with challenge %d\n", challenge ); + + while ( cur_info < Min( net_servers.Num(), MAX_PINGREQUESTS ) ) { + netadr_t serv = net_servers[ cur_info ].adr; + EmitGetInfo( serv ); + net_servers[ cur_info ].time = Sys_Milliseconds(); + net_info.SetInt( Sys_NetAdrToString( serv ), cur_info ); + cur_info++; + } +} + +/* +=============== +idServerScan::ServerScanFrame +=============== +*/ +void idServerScan::RunFrame( ) { + if ( scan_state == IDLE ) { + return; + } + + if ( scan_state == WAIT_ON_INIT ) { + if ( Sys_Milliseconds() >= endWaitTime ) { + scan_state = IDLE; + NetScan(); + } + return; + } + + int timeout_limit = Sys_Milliseconds() - REPLY_TIMEOUT; + + if ( scan_state == LAN_SCAN ) { + if ( timeout_limit > lan_pingtime ) { + common->Printf( "Scanned for servers on the LAN\n" ); + scan_state = IDLE; + } + return; + } + + // if scan_state == NET_SCAN + + // check for timeouts + int i = 0; + while ( i < net_info.GetNumKeyVals() ) { + if ( timeout_limit > net_servers[ atoi( net_info.GetKeyVal( i )->GetValue().c_str() ) ].time ) { + common->DPrintf( "timeout %s\n", net_info.GetKeyVal( i )->GetKey().c_str() ); + net_info.Delete( net_info.GetKeyVal( i )->GetKey().c_str() ); + } else { + i++; + } + } + + // possibly send more queries + while ( cur_info < net_servers.Num() && net_info.GetNumKeyVals() < MAX_PINGREQUESTS ) { + netadr_t serv = net_servers[ cur_info ].adr; + EmitGetInfo( serv ); + net_servers[ cur_info ].time = Sys_Milliseconds(); + net_info.SetInt( Sys_NetAdrToString( serv ), cur_info ); + cur_info++; + } + + // update state + if ( ( !incoming_net || ( incoming_useTimeout && Sys_Milliseconds() > incoming_lastTime ) ) && net_info.GetNumKeyVals() == 0 ) { + EndServers(); + // the list is complete, we are no longer waiting for any getInfo replies + common->Printf( "Scanned %d servers.\n", cur_info ); + scan_state = IDLE; + } +} + +/* +=============== +idServerScan::GetBestPing +=============== +*/ +bool idServerScan::GetBestPing( networkServer_t &serv ) { + int i, ic; + ic = Num(); + if ( !ic ) { + return false; + } + serv = (*this)[ 0 ]; + for ( i = 0 ; i < ic ; i++ ) { + if ( (*this)[ i ].ping < serv.ping ) { + serv = (*this)[ i ]; + } + } + return true; +} + +/* +================ +idServerScan::GUIConfig +================ +*/ +void idServerScan::GUIConfig( idUserInterface *pGUI, const char *name ) { + m_pGUI = pGUI; + if ( listGUI == NULL ) { + listGUI = uiManager->AllocListGUI(); + } + listGUI->Config( pGUI, name ); +} + +/* +================ +idServerScan::GUIUpdateSelected +================ +*/ +void idServerScan::GUIUpdateSelected( void ) { + char screenshot[ MAX_STRING_CHARS ]; + + if ( !m_pGUI ) { + return; + } + int i = listGUI->GetSelection( NULL, 0 ); + if ( i == -1 || i >= Num() ) { + m_pGUI->SetStateString( "server_name", "" ); + m_pGUI->SetStateString( "player1", "" ); + m_pGUI->SetStateString( "player2", "" ); + m_pGUI->SetStateString( "player3", "" ); + m_pGUI->SetStateString( "player4", "" ); + m_pGUI->SetStateString( "player5", "" ); + m_pGUI->SetStateString( "player6", "" ); + m_pGUI->SetStateString( "player7", "" ); + m_pGUI->SetStateString( "player8", "" ); + m_pGUI->SetStateString( "server_map", "" ); + m_pGUI->SetStateString( "browser_levelshot", "" ); + m_pGUI->SetStateString( "server_gameType", "" ); + m_pGUI->SetStateString( "server_IP", "" ); + m_pGUI->SetStateString( "server_passworded", "" ); + } else { + m_pGUI->SetStateString( "server_name", (*this)[ i ].serverInfo.GetString( "si_name" ) ); + for ( int j = 0; j < 8; j++ ) { + if ( (*this)[i].clients > j ) { + m_pGUI->SetStateString( va( "player%i", j + 1 ) , (*this)[ i ].nickname[ j ] ); + } else { + m_pGUI->SetStateString( va( "player%i", j + 1 ) , "" ); + } + } + m_pGUI->SetStateString( "server_map", (*this)[ i ].serverInfo.GetString( "si_mapName" ) ); + fileSystem->FindMapScreenshot( (*this)[ i ].serverInfo.GetString( "si_map" ), screenshot, MAX_STRING_CHARS ); + m_pGUI->SetStateString( "browser_levelshot", screenshot ); + m_pGUI->SetStateString( "server_gameType", (*this)[ i ].serverInfo.GetString( "si_gameType" ) ); + m_pGUI->SetStateString( "server_IP", Sys_NetAdrToString( (*this)[ i ].adr ) ); + if ( (*this)[ i ].serverInfo.GetBool( "si_usePass" ) ) { + m_pGUI->SetStateString( "server_passworded", "PASSWORD REQUIRED" ); + } else { + m_pGUI->SetStateString( "server_passworded", "" ); + } + } +} + +/* +================ +idServerScan::GUIAdd +================ +*/ +void idServerScan::GUIAdd( int id, const networkServer_t server ) { + idStr name = server.serverInfo.GetString( "si_name", GAME_NAME " Server" ); + bool d3xp = false; + bool mod = false; + + if ( !idStr::Icmp( server.serverInfo.GetString( "fs_game" ), "d3xp" ) || + !idStr::Icmp( server.serverInfo.GetString( "fs_game_base" ), "d3xp" ) ) { + d3xp = true; + } + if ( server.serverInfo.GetString( "fs_game" )[ 0 ] != '\0' ) { + mod = true; + } + + name += "\t"; + if ( server.serverInfo.GetString( "sv_punkbuster" )[ 0 ] == '1' ) { + name += "mtr_PB"; + } + + name += "\t"; + if ( d3xp ) { + // FIXME: even for a 'D3XP mod' + // could have a specific icon for this case + name += "mtr_doom3XPIcon"; + } else if ( mod ) { + name += "mtr_doom3Mod"; + } else { + name += "mtr_doom3Icon"; + } + name += "\t"; + name += va( "%i/%i\t", server.clients, server.serverInfo.GetInt( "si_maxPlayers" ) ); + name += ( server.ping > -1 ) ? va( "%i\t", server.ping ) : "na\t"; + name += server.serverInfo.GetString( "si_gametype" ); + name += "\t"; + name += server.serverInfo.GetString( "si_mapName" ); + name += "\t"; + listGUI->Add( id, name ); +} + +/* +================ +idServerScan::ApplyFilter +================ +*/ +void idServerScan::ApplyFilter( ) { + int i; + networkServer_t serv; + idStr s; + + listGUI->SetStateChanges( false ); + listGUI->Clear(); + for ( i = m_sortAscending ? 0 : m_sortedServers.Num() - 1; + m_sortAscending ? i < m_sortedServers.Num() : i >= 0; + m_sortAscending ? i++ : i-- ) { + serv = (*this)[ m_sortedServers[ i ] ]; + if ( !IsFiltered( serv ) ) { + GUIAdd( m_sortedServers[ i ], serv ); + } + } + GUIUpdateSelected(); + listGUI->SetStateChanges( true ); +} + +/* +================ +idServerScan::IsFiltered +================ +*/ +bool idServerScan::IsFiltered( const networkServer_t server ) { + int i; + const idKeyValue *keyval; + + // OS support filter +#if 0 + // filter out pure servers that won't provide checksumed game code for client OS + keyval = server.serverInfo.FindKey( "si_pure" ); + if ( keyval && !idStr::Cmp( keyval->GetValue(), "1" ) ) { + if ( ( server.OSMask & ( 1 << BUILD_OS_ID ) ) == 0 ) { + return true; + } + } +#else + if ( ( server.OSMask & ( 1 << BUILD_OS_ID ) ) == 0 ) { + return true; + } +#endif + // password filter + keyval = server.serverInfo.FindKey( "si_usePass" ); + if ( keyval && gui_filter_password.GetInteger() == 1 ) { + // show passworded only + if ( keyval->GetValue()[ 0 ] == '0' ) { + return true; + } + } else if ( keyval && gui_filter_password.GetInteger() == 2 ) { + // show no password only + if ( keyval->GetValue()[ 0 ] != '0' ) { + return true; + } + } + // players filter + keyval = server.serverInfo.FindKey( "si_maxPlayers" ); + if ( keyval ) { + if ( gui_filter_players.GetInteger() == 1 && server.clients == atoi( keyval->GetValue() ) ) { + return true; + } else if ( gui_filter_players.GetInteger() == 2 && ( !server.clients || server.clients == atoi( keyval->GetValue() ) ) ) { + return true; + } + } + // gametype filter + keyval = server.serverInfo.FindKey( "si_gameType" ); + if ( keyval && gui_filter_gameType.GetInteger() ) { + i = 0; + while ( l_gameTypes[ i ] ) { + if ( !keyval->GetValue().Icmp( l_gameTypes[ i ] ) ) { + break; + } + i++; + } + if ( l_gameTypes[ i ] && i != gui_filter_gameType.GetInteger() -1 ) { + return true; + } + } + // idle server filter + keyval = server.serverInfo.FindKey( "si_idleServer" ); + if ( keyval && !gui_filter_idle.GetInteger() ) { + if ( !keyval->GetValue().Icmp( "1" ) ) { + return true; + } + } + + return false; +} + +/* +================ +idServerScan::Cmp +================ +*/ + +int idServerScan::Cmp( const int *a, const int *b ) { + networkServer_t serv1, serv2; + idStr s1, s2; + int ret; + + serv1 = (*l_serverScan)[ *a ]; + serv2 = (*l_serverScan)[ *b ]; + switch ( l_serverScan->m_sort ) { + case SORT_PING: + ret = serv1.ping < serv2.ping ? -1 : ( serv1.ping > serv2.ping ? 1 : 0 ); + return ret; + case SORT_SERVERNAME: + serv1.serverInfo.GetString( "si_name", "", s1 ); + serv2.serverInfo.GetString( "si_name", "", s2 ); + return s1.Icmp( s2 ); + case SORT_PLAYERS: + ret = serv1.clients < serv2.clients ? -1 : ( serv1.clients > serv2.clients ? 1 : 0 ); + return ret; + case SORT_GAMETYPE: + serv1.serverInfo.GetString( "si_gameType", "", s1 ); + serv2.serverInfo.GetString( "si_gameType", "", s2 ); + return s1.Icmp( s2 ); + case SORT_MAP: + serv1.serverInfo.GetString( "si_mapName", "", s1 ); + serv2.serverInfo.GetString( "si_mapName", "", s2 ); + return s1.Icmp( s2 ); + case SORT_GAME: + serv1.serverInfo.GetString( "fs_game", "", s1 ); + serv2.serverInfo.GetString( "fs_game", "", s2 ); + return s1.Icmp( s2 ); + } + return 0; +} + +/* +================ +idServerScan::SetSorting +================ +*/ +void idServerScan::SetSorting( serverSort_t sort ) { + l_serverScan = this; + if ( sort == m_sort ) { + m_sortAscending = !m_sortAscending; + } else { + m_sort = sort; + m_sortAscending = true; // is the default for any new sort + m_sortedServers.Sort( idServerScan::Cmp ); + } + // trigger a redraw + ApplyFilter(); +} diff --git a/src/framework/cmdsystem.cpp b/src/framework/cmdsystem.cpp new file mode 100644 index 0000000..4417eb3 --- /dev/null +++ b/src/framework/cmdsystem.cpp @@ -0,0 +1,783 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +=============================================================================== + + idCmdSystemLocal + +=============================================================================== +*/ + +typedef struct commandDef_s { + struct commandDef_s * next; + char * name; + cmdFunction_t function; + argCompletion_t argCompletion; + int flags; + char * description; +} commandDef_t; + + +class idCmdSystemLocal : public idCmdSystem { +public: + virtual void Init( void ); + virtual void Shutdown( void ); + + virtual void AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion = NULL ); + virtual void RemoveCommand( const char *cmdName ); + virtual void RemoveFlaggedCommands( int flags ); + + virtual void CommandCompletion( void(*callback)( const char *s ) ); + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ); + + virtual void BufferCommandText( cmdExecution_t exec, const char *text ); + virtual void ExecuteCommandBuffer( void ); + + virtual void ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ); + virtual void ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ); + + virtual void BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ); + + virtual void SetupReloadEngine( const idCmdArgs &args ); + virtual bool PostReloadEngine( void ); + + void SetWait( int numFrames ) { wait = numFrames; } + commandDef_t * GetCommands( void ) const { return commands; } + +private: + static const int MAX_CMD_BUFFER = 0x10000; + + commandDef_t * commands; + + int wait; + int textLength; + byte textBuf[MAX_CMD_BUFFER]; + + idStr completionString; + idStrList completionParms; + + // piggybacks on the text buffer, avoids tokenize again and screwing it up + idList tokenizedCmds; + + // a command stored to be executed after a reloadEngine and all associated commands have been processed + idCmdArgs postReload; + +private: + void ExecuteTokenizedString( const idCmdArgs &args ); + void ExecuteCommandText( const char *text ); + void InsertCommandText( const char *text ); + void AppendCommandText( const char *text ); + + static void ListByFlags( const idCmdArgs &args, cmdFlags_t flags ); + static void List_f( const idCmdArgs &args ); + static void SystemList_f( const idCmdArgs &args ); + static void RendererList_f( const idCmdArgs &args ); + static void SoundList_f( const idCmdArgs &args ); + static void GameList_f( const idCmdArgs &args ); + static void ToolList_f( const idCmdArgs &args ); + static void Exec_f( const idCmdArgs &args ); + static void Vstr_f( const idCmdArgs &args ); + static void Echo_f( const idCmdArgs &args ); + static void Parse_f( const idCmdArgs &args ); + static void Wait_f( const idCmdArgs &args ); + static void PrintMemInfo_f( const idCmdArgs &args ); +}; + +#if defined( _WIN32 ) && defined( Q4_RECON_ENGINE_PRIVATE ) +static_assert( sizeof( idCmdSystemLocal ) == 0x10954, "idCmdSystemLocal ABI drift" ); +#endif + +idCmdSystemLocal cmdSystemLocal; +idCmdSystem * cmdSystem = &cmdSystemLocal; + + +/* +============ +idCmdSystemLocal::ListByFlags +============ +*/ +// NOTE: the const wonkyness is required to make msvc happy +template<> +ID_INLINE int idListSortCompare( const commandDef_t * const *a, const commandDef_t * const *b ) { + return idStr::Icmp( (*a)->name, (*b)->name ); +} + +void idCmdSystemLocal::ListByFlags( const idCmdArgs &args, cmdFlags_t flags ) { + int i; + idStr match; + const commandDef_t *cmd; + idList cmdList; + + if ( args.Argc() > 1 ) { + match = args.Args( 1, -1 ); + match.Replace( " ", "" ); + } else { + match = ""; + } + + for ( cmd = cmdSystemLocal.GetCommands(); cmd; cmd = cmd->next ) { + if ( !( cmd->flags & flags ) ) { + continue; + } + if ( match.Length() && idStr( cmd->name ).Filter( match, false ) == 0 ) { + continue; + } + + cmdList.Append( cmd ); + } + + cmdList.Sort(); + + for ( i = 0; i < cmdList.Num(); i++ ) { + cmd = cmdList[i]; + + common->Printf( " %-21s %s\n", cmd->name, cmd->description ); + } + + common->Printf( "%i commands\n", cmdList.Num() ); +} + +/* +============ +idCmdSystemLocal::List_f +============ +*/ +void idCmdSystemLocal::List_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_ALL ); +} + +/* +============ +idCmdSystemLocal::SystemList_f +============ +*/ +void idCmdSystemLocal::SystemList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_SYSTEM ); +} + +/* +============ +idCmdSystemLocal::RendererList_f +============ +*/ +void idCmdSystemLocal::RendererList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_RENDERER ); +} + +/* +============ +idCmdSystemLocal::SoundList_f +============ +*/ +void idCmdSystemLocal::SoundList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_SOUND ); +} + +/* +============ +idCmdSystemLocal::GameList_f +============ +*/ +void idCmdSystemLocal::GameList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_GAME ); +} + +/* +============ +idCmdSystemLocal::ToolList_f +============ +*/ +void idCmdSystemLocal::ToolList_f( const idCmdArgs &args ) { + idCmdSystemLocal::ListByFlags( args, CMD_FL_TOOL ); +} + +/* +=============== +idCmdSystemLocal::Exec_f +=============== +*/ +void idCmdSystemLocal::Exec_f( const idCmdArgs &args ) { + char * f; + int len; + idStr filename; + + if ( args.Argc () != 2 ) { + common->Printf( "exec : execute a script file\n" ); + return; + } + + filename = args.Argv(1); + filename.DefaultFileExtension( ".cfg" ); + len = fileSystem->ReadFile( filename, reinterpret_cast(&f), NULL ); + if ( !f ) { + common->Printf( "couldn't exec %s\n", args.Argv(1) ); + return; + } + common->Printf( "execing %s\n", args.Argv(1) ); + + cmdSystemLocal.BufferCommandText( CMD_EXEC_INSERT, f ); + + fileSystem->FreeFile( f ); +} + +/* +=============== +idCmdSystemLocal::Vstr_f + +Inserts the current value of a cvar as command text +=============== +*/ +void idCmdSystemLocal::Vstr_f( const idCmdArgs &args ) { + const char *v; + + if ( args.Argc () != 2 ) { + common->Printf( "vstr : execute a variable command\n" ); + return; + } + + v = cvarSystem->GetCVarString( args.Argv( 1 ) ); + + cmdSystemLocal.BufferCommandText( CMD_EXEC_APPEND, va( "%s\n", v ) ); +} + +/* +=============== +idCmdSystemLocal::Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void idCmdSystemLocal::Echo_f( const idCmdArgs &args ) { + int i; + + for ( i = 1; i < args.Argc(); i++ ) { + common->Printf( "%s ", args.Argv( i ) ); + } + common->Printf( "\n" ); +} + +/* +============ +idCmdSystemLocal::Wait_f + +Causes execution of the remainder of the command buffer to be delayed until next frame. +============ +*/ +void idCmdSystemLocal::Wait_f( const idCmdArgs &args ) { + if ( args.Argc() == 2 ) { + cmdSystemLocal.SetWait( atoi( args.Argv( 1 ) ) ); + } else { + cmdSystemLocal.SetWait( 1 ); + } +} + +/* +============ +idCmdSystemLocal::Parse_f + +This just prints out how the rest of the line was parsed, as a debugging tool. +============ +*/ +void idCmdSystemLocal::Parse_f( const idCmdArgs &args ) { + int i; + + for ( i = 0; i < args.Argc(); i++ ) { + common->Printf( "%i: %s\n", i, args.Argv(i) ); + } +} + +/* +============ +idCmdSystemLocal::Init +============ +*/ +void idCmdSystemLocal::Init( void ) { + + AddCommand( "listCmds", List_f, CMD_FL_SYSTEM, "lists commands" ); + AddCommand( "listSystemCmds", SystemList_f, CMD_FL_SYSTEM, "lists system commands" ); + AddCommand( "listRendererCmds", RendererList_f, CMD_FL_SYSTEM, "lists renderer commands" ); + AddCommand( "listSoundCmds", SoundList_f, CMD_FL_SYSTEM, "lists sound commands" ); + AddCommand( "listGameCmds", GameList_f, CMD_FL_SYSTEM, "lists game commands" ); + AddCommand( "listToolCmds", ToolList_f, CMD_FL_SYSTEM, "lists tool commands" ); + AddCommand( "exec", Exec_f, CMD_FL_SYSTEM, "executes a config file", ArgCompletion_ConfigName ); + AddCommand( "vstr", Vstr_f, CMD_FL_SYSTEM, "inserts the current value of a cvar as command text" ); + AddCommand( "echo", Echo_f, CMD_FL_SYSTEM, "prints text" ); + AddCommand( "parse", Parse_f, CMD_FL_SYSTEM, "prints tokenized string" ); + AddCommand( "wait", Wait_f, CMD_FL_SYSTEM, "delays remaining buffered commands one or more frames" ); + + completionString = "*"; + + textLength = 0; +} + +/* +============ +idCmdSystemLocal::Shutdown +============ +*/ +void idCmdSystemLocal::Shutdown( void ) { + commandDef_t *cmd; + + for ( cmd = commands; cmd; cmd = commands ) { + commands = commands->next; + Mem_Free( cmd->name ); + Mem_Free( cmd->description ); + delete cmd; + } + + completionString.Clear(); + completionParms.Clear(); + tokenizedCmds.Clear(); + postReload.Clear(); +} + +/* +============ +idCmdSystemLocal::AddCommand +============ +*/ +void idCmdSystemLocal::AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion ) { + commandDef_t *cmd; + + // fail if the command already exists + for ( cmd = commands; cmd; cmd = cmd->next ) { + if ( idStr::Cmp( cmdName, cmd->name ) == 0 ) { + if ( function != cmd->function ) { + common->Printf( "idCmdSystemLocal::AddCommand: %s already defined\n", cmdName ); + } + return; + } + } + + cmd = new commandDef_t; + cmd->name = Mem_CopyString( cmdName ); + cmd->function = function; + cmd->argCompletion = argCompletion; + cmd->flags = flags; + cmd->description = Mem_CopyString( description ); + cmd->next = commands; + commands = cmd; +} + +/* +============ +idCmdSystemLocal::RemoveCommand +============ +*/ +void idCmdSystemLocal::RemoveCommand( const char *cmdName ) { + commandDef_t *cmd, **last; + + for ( last = &commands, cmd = *last; cmd; cmd = *last ) { + if ( idStr::Cmp( cmdName, cmd->name ) == 0 ) { + *last = cmd->next; + Mem_Free( cmd->name ); + Mem_Free( cmd->description ); + delete cmd; + return; + } + last = &cmd->next; + } +} + +/* +============ +idCmdSystemLocal::RemoveFlaggedCommands +============ +*/ +void idCmdSystemLocal::RemoveFlaggedCommands( int flags ) { + commandDef_t *cmd, **last; + + for ( last = &commands, cmd = *last; cmd; cmd = *last ) { + if ( cmd->flags & flags ) { + *last = cmd->next; + Mem_Free( cmd->name ); + Mem_Free( cmd->description ); + delete cmd; + continue; + } + last = &cmd->next; + } +} + +/* +============ +idCmdSystemLocal::CommandCompletion +============ +*/ +void idCmdSystemLocal::CommandCompletion( void(*callback)( const char *s ) ) { + commandDef_t *cmd; + + for ( cmd = commands; cmd; cmd = cmd->next ) { + callback( cmd->name ); + } +} + +/* +============ +idCmdSystemLocal::ArgCompletion +============ +*/ +void idCmdSystemLocal::ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) { + commandDef_t *cmd; + idCmdArgs args; + + args.TokenizeString( cmdString, false ); + + for ( cmd = commands; cmd; cmd = cmd->next ) { + if ( !cmd->argCompletion ) { + continue; + } + if ( idStr::Icmp( args.Argv( 0 ), cmd->name ) == 0 ) { + cmd->argCompletion( args, callback ); + break; + } + } +} + +/* +============ +idCmdSystemLocal::ExecuteTokenizedString +============ +*/ +void idCmdSystemLocal::ExecuteTokenizedString( const idCmdArgs &args ) { + commandDef_t *cmd, **prev; + + // execute the command line + if ( !args.Argc() ) { + return; // no tokens + } + + // check registered command functions + for ( prev = &commands; *prev; prev = &cmd->next ) { + cmd = *prev; + if ( idStr::Icmp( args.Argv( 0 ), cmd->name ) == 0 ) { + // rearrange the links so that the command will be + // near the head of the list next time it is used + *prev = cmd->next; + cmd->next = commands; + commands = cmd; + + if ( ( cmd->flags & (CMD_FL_CHEAT|CMD_FL_TOOL) ) && session && session->IsMultiplayer() && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { + common->Printf( "Command '%s' not valid in multiplayer mode.\n", cmd->name ); + return; + } + // perform the action + if ( !cmd->function ) { + break; + } else { + cmd->function( args ); + } + return; + } + } + + // check cvars + if ( cvarSystem->Command( args ) ) { + return; + } + + common->Printf( "Unknown command '%s'\n", args.Argv( 0 ) ); +} + +/* +============ +idCmdSystemLocal::ExecuteCommandText + +Tokenizes, then executes. +============ +*/ +void idCmdSystemLocal::ExecuteCommandText( const char *text ) { + ExecuteTokenizedString( idCmdArgs( text, false ) ); +} + +/* +============ +idCmdSystemLocal::InsertCommandText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void idCmdSystemLocal::InsertCommandText( const char *text ) { + int len; + int i; + + len = strlen( text ) + 1; + if ( len + textLength > (int)sizeof( textBuf ) ) { + common->Printf( "idCmdSystemLocal::InsertText: buffer overflow\n" ); + return; + } + + // move the existing command text + for ( i = textLength - 1; i >= 0; i-- ) { + textBuf[ i + len ] = textBuf[ i ]; + } + + // copy the new text in + memcpy( textBuf, text, len - 1 ); + + // add a \n + textBuf[ len - 1 ] = '\n'; + + textLength += len; +} + +/* +============ +idCmdSystemLocal::AppendCommandText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void idCmdSystemLocal::AppendCommandText( const char *text ) { + int l; + + l = strlen( text ); + + if ( textLength + l >= (int)sizeof( textBuf ) ) { + common->Printf( "idCmdSystemLocal::AppendText: buffer overflow\n" ); + return; + } + memcpy( textBuf + textLength, text, l ); + textLength += l; +} + +/* +============ +idCmdSystemLocal::BufferCommandText +============ +*/ +void idCmdSystemLocal::BufferCommandText( cmdExecution_t exec, const char *text ) { + switch( exec ) { + case CMD_EXEC_NOW: { + ExecuteCommandText( text ); + break; + } + case CMD_EXEC_INSERT: { + InsertCommandText( text ); + break; + } + case CMD_EXEC_APPEND: { + AppendCommandText( text ); + break; + } + default: { + common->FatalError( "idCmdSystemLocal::BufferCommandText: bad exec type" ); + } + } +} + +/* +============ +idCmdSystemLocal::BufferCommandArgs +============ +*/ +void idCmdSystemLocal::BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ) { + switch ( exec ) { + case CMD_EXEC_NOW: { + ExecuteTokenizedString( args ); + break; + } + case CMD_EXEC_APPEND: { + AppendCommandText( "_execTokenized\n" ); + tokenizedCmds.Append( args ); + break; + } + default: { + common->FatalError( "idCmdSystemLocal::BufferCommandArgs: bad exec type" ); + } + } +} + +/* +============ +idCmdSystemLocal::ExecuteCommandBuffer +============ +*/ +void idCmdSystemLocal::ExecuteCommandBuffer( void ) { + int i; + char * text; + int quotes; + idCmdArgs args; + + while( textLength ) { + + if ( wait ) { + // skip out while text still remains in buffer, leaving it for next frame + wait--; + break; + } + + // find a \n or ; line break + text = (char *)textBuf; + + quotes = 0; + for ( i = 0; i < textLength; i++ ) { + if ( text[i] == '"' ) { + quotes++; + } + if ( !( quotes & 1 ) && text[i] == ';' ) { + break; // don't break if inside a quoted string + } + if ( text[i] == '\n' || text[i] == '\r' ) { + break; + } + } + + text[i] = 0; + + if ( !idStr::Cmp( text, "_execTokenized" ) ) { + args = tokenizedCmds[ 0 ]; + tokenizedCmds.RemoveIndex( 0 ); + } else { + args.TokenizeString( text, false ); + } + + // delete the text from the command buffer and move remaining commands down + // this is necessary because commands (exec) can insert data at the + // beginning of the text buffer + + if ( i == textLength ) { + textLength = 0; + } else { + i++; + textLength -= i; + memmove( text, text+i, textLength ); + } + + // execute the command line that we have already tokenized + ExecuteTokenizedString( args ); + } +} + +/* +============ +idCmdSystemLocal::ArgCompletion_FolderExtension +============ +*/ +void idCmdSystemLocal::ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ) { + int i; + idStr string; + const char *extension; + va_list argPtr; + + string = args.Argv( 0 ); + string += " "; + string += args.Argv( 1 ); + + if ( string.Icmp( completionString ) != 0 ) { + idStr parm, path; + idFileList *names; + + completionString = string; + completionParms.Clear(); + + parm = args.Argv( 1 ); + parm.ExtractFilePath( path ); + if ( stripFolder || path.Length() == 0 ) { + path = folder + path; + } + path.StripTrailing( '/' ); + + // list folders + names = fileSystem->ListFiles( path, "/", true, true ); + for ( i = 0; i < names->GetNumFiles(); i++ ) { + idStr name = names->GetFile( i ); + if ( stripFolder ) { + name.Strip( folder ); + } else { + name.Strip( "/" ); + } + name = args.Argv( 0 ) + ( " " + name ) + "/"; + completionParms.Append( name ); + } + fileSystem->FreeFileList( names ); + + // list files + va_start( argPtr, stripFolder ); + for ( extension = va_arg( argPtr, const char * ); extension; extension = va_arg( argPtr, const char * ) ) { + names = fileSystem->ListFiles( path, extension, true, true ); + for ( i = 0; i < names->GetNumFiles(); i++ ) { + idStr name = names->GetFile( i ); + if ( stripFolder ) { + name.Strip( folder ); + } else { + name.Strip( "/" ); + } + name = args.Argv( 0 ) + ( " " + name ); + completionParms.Append( name ); + } + fileSystem->FreeFileList( names ); + } + va_end( argPtr ); + } + for ( i = 0; i < completionParms.Num(); i++ ) { + callback( completionParms[i] ); + } +} + +/* +============ +idCmdSystemLocal::ArgCompletion_DeclName +============ +*/ +void idCmdSystemLocal::ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ) { + int i, num; + + if ( declManager == NULL ) { + return; + } + num = declManager->GetNumDecls( (declType_t)type ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( (declType_t)type, i , false )->GetName() ); + } +} + +/* +============ +idCmdSystemLocal::SetupReloadEngine +============ +*/ +void idCmdSystemLocal::SetupReloadEngine( const idCmdArgs &args ) { + BufferCommandText( CMD_EXEC_APPEND, "reloadEngine\n" ); + postReload = args; +} + +/* +============ +idCmdSystemLocal::PostReloadEngine +============ +*/ +bool idCmdSystemLocal::PostReloadEngine( void ) { + if ( !postReload.Argc() ) { + return false; + } + BufferCommandArgs( CMD_EXEC_APPEND, postReload ); + postReload.Clear(); + return true; +} diff --git a/src/framework/common.cpp b/src/framework/common.cpp new file mode 100644 index 0000000..e962cc7 --- /dev/null +++ b/src/framework/common.cpp @@ -0,0 +1,3244 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "../renderer/Image.h" + +#define MAX_PRINT_MSG_SIZE 4096 +#define MAX_WARNING_LIST 256 + +typedef enum { + ERP_NONE, + ERP_FATAL, // exit the entire game with a popup window + ERP_DROP, // print to console and disconnect from game + ERP_DISCONNECT // don't kill server +} errorParm_t; + +#if defined( _DEBUG ) + #define BUILD_DEBUG "-debug" +#else + #define BUILD_DEBUG "" +#endif + +struct version_s { + version_s( void ) { sprintf( string, "%s.%d%s %s %s %s", ENGINE_VERSION, BUILD_NUMBER, BUILD_DEBUG, BUILD_STRING, __DATE__, __TIME__ ); } + char string[256]; +} version; + +idCVar com_version( "si_version", version.string, CVAR_SYSTEM|CVAR_ROM|CVAR_SERVERINFO, "engine version" ); +idCVar com_skipRenderer( "com_skipRenderer", "0", CVAR_BOOL|CVAR_SYSTEM, "skip the renderer completely" ); +idCVar com_machineSpec( "com_machineSpec", "-1", CVAR_INTEGER | CVAR_ARCHIVE | CVAR_SYSTEM, "hardware classification, -1 = not detected, 0 = low quality, 1 = medium quality, 2 = high quality, 3 = ultra quality" ); +idCVar com_purgeAll( "com_purgeAll", "0", CVAR_BOOL | CVAR_ARCHIVE | CVAR_SYSTEM, "purge everything between level loads" ); +idCVar com_memoryMarker( "com_memoryMarker", "-1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_INIT, "used as a marker for memory stats" ); +idCVar com_preciseTic( "com_preciseTic", "1", CVAR_BOOL|CVAR_SYSTEM, "run one game tick every async thread update" ); +idCVar com_asyncInput( "com_asyncInput", "0", CVAR_BOOL|CVAR_SYSTEM, "sample input from the async thread" ); +#define ASYNCSOUND_INFO "0: mix sound inline, 1: memory mapped async mix, 2: callback mixing, 3: write async mix" +#if defined( MACOS_X ) +idCVar com_asyncSound( "com_asyncSound", "2", CVAR_INTEGER|CVAR_SYSTEM|CVAR_ROM, ASYNCSOUND_INFO ); +#elif defined( __linux__ ) +idCVar com_asyncSound( "com_asyncSound", "3", CVAR_INTEGER|CVAR_SYSTEM|CVAR_ROM, ASYNCSOUND_INFO ); +#else +idCVar com_asyncSound( "com_asyncSound", "1", CVAR_INTEGER|CVAR_SYSTEM, ASYNCSOUND_INFO, 0, 1 ); +#endif +idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "force generic platform independent SIMD" ); +idCVar com_developer( "developer", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "developer mode" ); +idCVar com_allowConsole( "com_allowConsole", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "allow toggling console with the tilde key" ); +idCVar com_speeds( "com_speeds", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "show engine timings" ); +idCVar com_showFPS( "com_showFPS", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_ARCHIVE|CVAR_NOCHEAT, "show frames rendered per second" ); +idCVar com_showMemoryUsage( "com_showMemoryUsage", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "show total and per frame memory usage" ); +idCVar com_showAsyncStats( "com_showAsyncStats", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "show async network stats" ); +idCVar com_showSoundDecoders( "com_showSoundDecoders", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "show sound decoders" ); +idCVar com_timestampPrints( "com_timestampPrints", "0", CVAR_SYSTEM, "print time with each console print, 1 = msec, 2 = sec", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar com_timescale( "timescale", "1", CVAR_SYSTEM | CVAR_FLOAT, "scales the time", 0.1f, 10.0f ); +idCVar com_logFile( "logFile", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "1 = buffer log, 2 = flush after each print", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar com_logFileName( "logFileName", "qconsole.log", CVAR_SYSTEM | CVAR_NOCHEAT, "name of log file, if empty, qconsole.log will be used" ); +idCVar com_makingBuild( "com_makingBuild", "0", CVAR_BOOL | CVAR_SYSTEM, "1 when making a build" ); +idCVar com_updateLoadSize( "com_updateLoadSize", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "update the load size after loading a map" ); +idCVar com_videoRam( "com_videoRam", "64", CVAR_INTEGER | CVAR_SYSTEM | CVAR_NOCHEAT | CVAR_ARCHIVE, "holds the last amount of detected video ram" ); + +idCVar com_product_lang_ext( "com_product_lang_ext", "1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "Extension to use when creating language files." ); + +// com_speeds times +int time_gameFrame; +int time_gameDraw; +int time_frontend; // renderSystem frontend time +int time_backend; // renderSystem backend time + +int com_frameTime; // time for the current frame in milliseconds +int com_frameNumber; // variable frame number +volatile int com_ticNumber; // 60 hz tics +int com_editors; // currently opened editor(s) +bool com_editorActive; // true if an editor has focus +bool com_debugHudActive; // true while the in-game debug HUD is active + +#ifdef _WIN32 +HWND com_hwndMsg = NULL; +bool com_outputMsg = false; +unsigned int com_msgID = -1; +#endif + +#if defined( __DOOM_DLL__ ) || defined( Q4_RECON_ENGINE_PRIVATE ) +idGame * game = NULL; +idGameEdit * gameEdit = NULL; +#endif + +// writes si_version to the config file - in a kinda obfuscated way +//#define ID_WRITE_VERSION + +class idCommonLocal : public idCommon { +public: + idCommonLocal( void ); + + virtual void Init( int argc, const char **argv, const char *cmdline ); + virtual void Shutdown( void ); + virtual void Quit( void ); + virtual bool IsInitialized( void ) const; + virtual void Frame( void ); + virtual void GUIFrame( bool execCmd, bool network ); + virtual void Async( void ); + virtual void StartupVariable( const char *match, bool once ); + virtual int GetUserCmdHz( void ) const; + virtual int GetUserCmdMSec( void ) const; + virtual int GetFrameTime( void ) const; + virtual bool IsRenderableGameFrame( void ) const; + virtual void SetRenderableGameFrame( bool in ); + virtual const char * GetErrorMessage( void ) const; + virtual void InitTool( const int tool, const idDict *dict ); + virtual bool IsToolActive( void ) const; + virtual rvISourceControl *GetSourceControl( void ); + virtual void ActivateTool( bool active ); + virtual void WriteConfigToFile( const char *filename ); + virtual void WriteFlaggedCVarsToFile( const char *filename, int flags, const char *setCmd ); + virtual void ModViewThink( void ); + virtual void RunAlwaysThinkGUIs( int time ); + virtual void DebuggerCheckBreakpoint( idInterpreter *interpreter, idProgram *program, int instructionPointer ); + virtual bool DoingDeclValidation( void ); + virtual void SetCrashReportAutoSendString( const char *string ); + virtual void LoadToolsDLL( void ); + virtual void UnloadToolsDLL( void ); + virtual void BeginRedirect( char *buffer, int buffersize, void (*flush)( const char * ), bool rcon = false ); + virtual void EndRedirect( void ); + virtual void SetRefreshOnPrint( bool set ); + virtual void Printf( const char *fmt, ... ) id_attribute((format(printf,2,3))); + virtual void VPrintf( const char *fmt, va_list arg ); + virtual void DPrintf( const char *fmt, ... ) id_attribute((format(printf,2,3))); + virtual void Warning( const char *fmt, ... ) id_attribute((format(printf,2,3))); + virtual void DWarning( const char *fmt, ...) id_attribute((format(printf,2,3))); + virtual void PrintWarnings( void ); + virtual void ClearWarnings( const char *reason ); + virtual void Error( const char *fmt, ... ) id_attribute((format(printf,2,3))); + virtual void FatalError( const char *fmt, ... ) id_attribute((format(printf,2,3))); + virtual void DumpWarnings( void ); + virtual const char * GetLocalizedString( const char *token, int langIndex = -1 ); + virtual const idLangKeyValue *GetLocalizedString( int index, int langIndex = -1 ); + virtual int GetNumLanguages( void ) const; + virtual int GetNumLocalizedStrings( void ) const; + virtual const char * GetLanguage( int index ) const; + virtual bool LanguageHasVO( int index ) const; + + virtual const char * KeysFromBinding( const char *bind ); + virtual const char * BindingFromKey( const char *key ); + + virtual int ButtonState( int key ); + virtual int KeyState( int key ); + virtual int GetRModeForMachineSpec( int machineSpec ) const; + virtual void SetDesiredMachineSpec( int machineSpec ); + virtual bool IsRCon( void ) const; + + virtual const idLangDict * GetLanguageDict( void ); + + void InitGame( void ); + void ShutdownGame( bool reloading ); + + // localization + void InitLanguageDict( void ); + void LocalizeGui( const char *fileName, idLangDict &langDict ); + void LocalizeMapData( const char *fileName, idLangDict &langDict ); + void LocalizeSpecificMapData( const char *fileName, idLangDict &langDict, const idLangDict &replaceArgs ); + + void SetMachineSpec( void ); + +private: + void InitCommands( void ); + void InitRenderSystem( void ); + void InitSIMD( void ); + bool AddStartupCommands( void ); + void ParseCommandLine( int argc, const char **argv ); + void ClearCommandLine( void ); + bool SafeMode( void ); + void CheckToolMode( void ); + void CloseLogFile( void ); + void WriteConfiguration( void ); + void SingleAsyncTic( void ); + void LoadGameDLL( void ); + void UnloadGameDLL( void ); + void PrintLoadingMessage( const char *msg ); + void FilterLangList( idStrList* list, idStr lang ); + + bool com_fullyInitialized; + bool com_refreshOnPrint; // update the screen every print for dmap + int com_errorEntered; // 0, ERP_DROP, etc + bool com_shuttingDown; + + idFile * logFile; + + char errorMessage[MAX_PRINT_MSG_SIZE]; + + char * rd_buffer; + int rd_buffersize; + void (*rd_flush)( const char *buffer ); + bool rd_rcon; + + idStr warningCaption; + idStrList warningList; + idStrList errorList; + + int gameDLL; + int desiredMachineSpec; + bool com_renderableGameFrame; + + idLangDict languageDict; + +#ifdef ID_WRITE_VERSION + idCompressor * config_compressor; +#endif +}; + +idCommonLocal commonLocal; +idCommon * common = &commonLocal; + +MemInfo::MemInfo( void ) { + total = 0; + assetTotals = 0; + imageAssetsTotal = 0; + modelAssetsTotal = 0; + soundAssetsTotal = 0; + collAssetsTotal = 0; + animsAssetsTotal = 0; + aasAssetsTotal = 0; + imageAssetsCount = 0; + modelAssetsCount = 0; + soundAssetsCount = 0; + collAssetsCount = 0; + animsAssetsCount = 0; + aasAssetsCount = 0; +} + + +/* +================== +idCommonLocal::idCommonLocal +================== +*/ +idCommonLocal::idCommonLocal( void ) { + com_fullyInitialized = false; + com_refreshOnPrint = false; + com_errorEntered = 0; + com_shuttingDown = false; + + logFile = NULL; + + strcpy( errorMessage, "" ); + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; + rd_rcon = false; + + gameDLL = 0; + desiredMachineSpec = -1; + com_renderableGameFrame = true; + +#ifdef ID_WRITE_VERSION + config_compressor = NULL; +#endif +} + +int idCommonLocal::GetUserCmdHz( void ) const { + return 60; +} + +int idCommonLocal::GetUserCmdMSec( void ) const { + return 16; +} + +int idCommonLocal::GetFrameTime( void ) const { + return com_frameTime; +} + +bool idCommonLocal::IsRenderableGameFrame( void ) const { + return com_renderableGameFrame; +} + +void idCommonLocal::SetRenderableGameFrame( bool in ) { + com_renderableGameFrame = in; +} + +const char *idCommonLocal::GetErrorMessage( void ) const { + return errorMessage; +} + +bool idCommonLocal::IsToolActive( void ) const { + return com_editorActive; +} + +rvISourceControl *idCommonLocal::GetSourceControl( void ) { + return NULL; +} + +void idCommonLocal::ModViewThink( void ) { +} + +void idCommonLocal::RunAlwaysThinkGUIs( int time ) { +} + +void idCommonLocal::DebuggerCheckBreakpoint( idInterpreter *interpreter, idProgram *program, int instructionPointer ) { +} + +bool idCommonLocal::DoingDeclValidation( void ) { + return false; +} + +void idCommonLocal::SetCrashReportAutoSendString( const char *string ) { +} + +void idCommonLocal::LoadToolsDLL( void ) { +} + +void idCommonLocal::UnloadToolsDLL( void ) { + com_editors = 0; + com_editorActive = false; +} + +int idCommonLocal::GetRModeForMachineSpec( int machineSpec ) const { + if ( machineSpec == 3 ) { + return 5; + } + if ( machineSpec == 2 ) { + return 4; + } + return 3; +} + +void idCommonLocal::SetDesiredMachineSpec( int machineSpec ) { + desiredMachineSpec = machineSpec; +} + +bool idCommonLocal::IsRCon( void ) const { + return rd_rcon; +} + +/* +================== +idCommonLocal::BeginRedirect +================== +*/ +void idCommonLocal::BeginRedirect( char *buffer, int buffersize, void (*flush)( const char *), bool rcon ) { + if ( !buffer || !buffersize || !flush ) { + return; + } + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + rd_rcon = rcon; + + *rd_buffer = 0; +} + +/* +================== +idCommonLocal::EndRedirect +================== +*/ +void idCommonLocal::EndRedirect( void ) { + if ( rd_flush && rd_buffer[ 0 ] ) { + rd_flush( rd_buffer ); + } + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; + rd_rcon = false; +} + +#ifdef _WIN32 + +/* +================== +EnumWindowsProc +================== +*/ +BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam ) { + char buff[1024]; + + ::GetWindowText( hwnd, buff, sizeof( buff ) ); + if ( idStr::Icmpn( buff, EDITOR_WINDOWTEXT, strlen( EDITOR_WINDOWTEXT ) ) == 0 ) { + com_hwndMsg = hwnd; + return FALSE; + } + return TRUE; +} + +/* +================== +FindEditor +================== +*/ +bool FindEditor( void ) { + com_hwndMsg = NULL; + EnumWindows( EnumWindowsProc, 0 ); + return !( com_hwndMsg == NULL ); +} + +#endif + +/* +================== +idCommonLocal::CloseLogFile +================== +*/ +void idCommonLocal::CloseLogFile( void ) { + if ( logFile ) { + com_logFile.SetBool( false ); // make sure no further VPrintf attempts to open the log file again + fileSystem->CloseFile( logFile ); + logFile = NULL; + } +} + +/* +================== +idCommonLocal::SetRefreshOnPrint +================== +*/ +void idCommonLocal::SetRefreshOnPrint( bool set ) { + com_refreshOnPrint = set; +} + +/* +================== +idCommonLocal::VPrintf + +A raw string should NEVER be passed as fmt, because of "%f" type crashes. +================== +*/ +void idCommonLocal::VPrintf( const char *fmt, va_list args ) { + char msg[MAX_PRINT_MSG_SIZE]; + int timeLength; + static bool logFileFailed = false; + + // if the cvar system is not initialized + if ( !cvarSystem->IsInitialized() ) { + return; + } + + // optionally put a timestamp at the beginning of each print, + // so we can see how long different init sections are taking + if ( com_timestampPrints.GetInteger() ) { + int t = Sys_Milliseconds(); + if ( com_timestampPrints.GetInteger() == 1 ) { + t /= 1000; + } + sprintf( msg, "[%i]", t ); + timeLength = strlen( msg ); + } else { + timeLength = 0; + } + + // don't overflow + if ( idStr::vsnPrintf( msg+timeLength, MAX_PRINT_MSG_SIZE-timeLength-1, fmt, args ) < 0 ) { + msg[sizeof(msg)-2] = '\n'; msg[sizeof(msg)-1] = '\0'; // avoid output garbling + Sys_Printf( "idCommon::VPrintf: truncated to %d characters\n", strlen(msg)-1 ); + } + + if ( rd_buffer ) { + if ( (int)( strlen( msg ) + strlen( rd_buffer ) ) > ( rd_buffersize - 1 ) ) { + rd_flush( rd_buffer ); + *rd_buffer = 0; + } + strcat( rd_buffer, msg ); + return; + } + + // echo to console buffer + console->Print( msg ); + + // remove any color codes + idStr::RemoveEscapes( msg ); + + // echo to dedicated console and early console + Sys_Printf( "%s", msg ); + + // print to script debugger server + // DebuggerServerPrint( msg ); + +#if 0 // !@# +#if defined(_DEBUG) && defined(WIN32) + if ( strlen( msg ) < 512 ) { + TRACE( msg ); + } +#endif +#endif + + // logFile + if ( com_logFile.GetInteger() && !logFileFailed && fileSystem->IsInitialized() ) { + static bool recursing; + + if ( !logFile && !recursing ) { + struct tm *newtime; + time_t aclock; + const char *fileName = com_logFileName.GetString()[0] ? com_logFileName.GetString() : "qconsole.log"; + + // fileSystem->OpenFileWrite can cause recursive prints into here + recursing = true; + + logFile = fileSystem->OpenFileWrite( fileName ); + if ( !logFile ) { + logFileFailed = true; + FatalError( "failed to open log file '%s'\n", fileName ); + } + + recursing = false; + + if ( com_logFile.GetInteger() > 1 ) { + // force it to not buffer so we get valid + // data even if we are crashing + logFile->ForceFlush(); + } + + time( &aclock ); + newtime = localtime( &aclock ); + Printf( "log file '%s' opened on %s\n", fileName, asctime( newtime ) ); + } + if ( logFile ) { + logFile->Write( msg, strlen( msg ) ); + logFile->Flush(); // ForceFlush doesn't help a whole lot + } + } + + // don't trigger any updates if we are in the process of doing a fatal error + if ( com_errorEntered != ERP_FATAL ) { + // update the console if we are in a long-running command, like dmap + if ( com_refreshOnPrint ) { + session->UpdateScreen(); + } + + // let session redraw the animated loading screen if necessary + session->PacifierUpdate(); + } + +#ifdef _WIN32 + + if ( com_outputMsg ) { + if ( com_msgID == -1 ) { + com_msgID = ::RegisterWindowMessage( DMAP_MSGID ); + if ( !FindEditor() ) { + com_outputMsg = false; + } else { + Sys_ShowWindow( false ); + } + } + if ( com_hwndMsg ) { + ATOM atom = ::GlobalAddAtom( msg ); + ::PostMessage( com_hwndMsg, com_msgID, 0, static_cast(atom) ); + } + } + +#endif +} + +/* +================== +idCommonLocal::Printf + +Both client and server can use this, and it will output to the appropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +================== +*/ +void idCommonLocal::Printf( const char *fmt, ... ) { + va_list argptr; + va_start( argptr, fmt ); + VPrintf( fmt, argptr ); + va_end( argptr ); +} + +/* +================== +idCommonLocal::DPrintf + +prints message that only shows up if the "developer" cvar is set +================== +*/ +void idCommonLocal::DPrintf( const char *fmt, ... ) { + va_list argptr; + char msg[MAX_PRINT_MSG_SIZE]; + + if ( !cvarSystem->IsInitialized() || !com_developer.GetBool() ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); + va_end( argptr ); + msg[sizeof(msg)-1] = '\0'; + + // never refresh the screen, which could cause reentrency problems + bool temp = com_refreshOnPrint; + com_refreshOnPrint = false; + + Printf( S_COLOR_RED"%s", msg ); + + com_refreshOnPrint = temp; +} + +/* +================== +idCommonLocal::DWarning + +prints warning message in yellow that only shows up if the "developer" cvar is set +================== +*/ +void idCommonLocal::DWarning( const char *fmt, ... ) { + va_list argptr; + char msg[MAX_PRINT_MSG_SIZE]; + + if ( !com_developer.GetBool() ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); + va_end( argptr ); + msg[sizeof(msg)-1] = '\0'; + + Printf( S_COLOR_YELLOW"WARNING: %s\n", msg ); +} + +/* +================== +idCommonLocal::Warning + +prints WARNING %s and adds the warning message to a queue to be printed later on +================== +*/ +void idCommonLocal::Warning( const char *fmt, ... ) { + va_list argptr; + char msg[MAX_PRINT_MSG_SIZE]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, sizeof(msg), fmt, argptr ); + va_end( argptr ); + msg[sizeof(msg)-1] = 0; + + Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", msg ); + + if ( warningList.Num() < MAX_WARNING_LIST ) { + warningList.AddUnique( msg ); + } +} + +/* +================== +idCommonLocal::PrintWarnings +================== +*/ +void idCommonLocal::PrintWarnings( void ) { + int i; + + if ( !warningList.Num() ) { + return; + } + + warningList.Sort(); + + Printf( "------------- Warnings ---------------\n" ); + Printf( "during %s...\n", warningCaption.c_str() ); + + for ( i = 0; i < warningList.Num(); i++ ) { + Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "%s\n", warningList[i].c_str() ); + } + if ( warningList.Num() ) { + if ( warningList.Num() >= MAX_WARNING_LIST ) { + Printf( "more than %d warnings\n", MAX_WARNING_LIST ); + } else { + Printf( "%d warnings\n", warningList.Num() ); + } + } +} + +/* +================== +idCommonLocal::ClearWarnings +================== +*/ +void idCommonLocal::ClearWarnings( const char *reason ) { + warningCaption = reason; + warningList.Clear(); +} + +/* +================== +idCommonLocal::DumpWarnings +================== +*/ +void idCommonLocal::DumpWarnings( void ) { + int i; + idFile *warningFile; + + if ( !warningList.Num() ) { + return; + } + + warningFile = fileSystem->OpenFileWrite( "warnings.txt", "fs_savepath" ); + if ( warningFile ) { + + warningFile->Printf( "------------- Warnings ---------------\n\n" ); + warningFile->Printf( "during %s...\n", warningCaption.c_str() ); + warningList.Sort(); + for ( i = 0; i < warningList.Num(); i++ ) { + warningList[i].RemoveEscapes(); + warningFile->Printf( "WARNING: %s\n", warningList[i].c_str() ); + } + if ( warningList.Num() >= MAX_WARNING_LIST ) { + warningFile->Printf( "\nmore than %d warnings!\n", MAX_WARNING_LIST ); + } else { + warningFile->Printf( "\n%d warnings.\n", warningList.Num() ); + } + + warningFile->Printf( "\n\n-------------- Errors ---------------\n\n" ); + errorList.Sort(); + for ( i = 0; i < errorList.Num(); i++ ) { + errorList[i].RemoveEscapes(); + warningFile->Printf( "ERROR: %s", errorList[i].c_str() ); + } + + warningFile->ForceFlush(); + + fileSystem->CloseFile( warningFile ); + +#if defined(_WIN32) && !defined(_DEBUG) + idStr osPath; + osPath = fileSystem->RelativePathToOSPath( "warnings.txt", "fs_savepath" ); + WinExec( va( "Notepad.exe %s", osPath.c_str() ), SW_SHOW ); +#endif + } +} + +/* +================== +idCommonLocal::Error +================== +*/ +void idCommonLocal::Error( const char *fmt, ... ) { + va_list argptr; + static int lastErrorTime; + static int errorCount; + int currentTime; + + int code = ERP_DROP; + + // always turn this off after an error + com_refreshOnPrint = false; + + // when we are running automated scripts, make sure we + // know if anything failed + if ( cvarSystem->GetCVarInteger( "fs_copyfiles" ) ) { + code = ERP_FATAL; + } + + // if we don't have GL running, make it a fatal error + if ( !renderSystem->IsOpenGLRunning() ) { + code = ERP_FATAL; + } + + // if we got a recursive error, make it fatal + if ( com_errorEntered ) { + // if we are recursively erroring while exiting + // from a fatal error, just kill the entire + // process immediately, which will prevent a + // full screen rendering window covering the + // error dialog + if ( com_errorEntered == ERP_FATAL ) { + Sys_Quit(); + } + code = ERP_FATAL; + } + + // if we are getting a solid stream of ERP_DROP, do an ERP_FATAL + currentTime = Sys_Milliseconds(); + if ( currentTime - lastErrorTime < 100 ) { + if ( ++errorCount > 3 ) { + code = ERP_FATAL; + } + } else { + errorCount = 0; + } + lastErrorTime = currentTime; + + com_errorEntered = code; + + va_start (argptr,fmt); + idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); + va_end (argptr); + errorMessage[sizeof(errorMessage)-1] = '\0'; + + // copy the error message to the clip board + Sys_SetClipboardData( errorMessage ); + + // add the message to the error list + errorList.AddUnique( errorMessage ); + + // Dont shut down the session for gui editor or debugger + if ( !( com_editors & ( EDITOR_GUI | EDITOR_DEBUGGER ) ) ) { + session->Stop(); + } + + if ( code == ERP_DISCONNECT ) { + com_errorEntered = 0; + throw idException( errorMessage ); + // The gui editor doesnt want thing to com_error so it handles exceptions instead + } else if( com_editors & ( EDITOR_GUI | EDITOR_DEBUGGER ) ) { + com_errorEntered = 0; + throw idException( errorMessage ); + } else if ( code == ERP_DROP ) { + Printf( "********************\nERROR: %s\n********************\n", errorMessage ); + com_errorEntered = 0; + throw idException( errorMessage ); + } else { + Printf( "********************\nERROR: %s\n********************\n", errorMessage ); + } + + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); + } + + Shutdown(); + + Sys_Error( "%s", errorMessage ); +} + +/* +================== +idCommonLocal::FatalError + +Dump out of the game to a system dialog +================== +*/ +void idCommonLocal::FatalError( const char *fmt, ... ) { + va_list argptr; + + // if we got a recursive error, make it fatal + if ( com_errorEntered ) { + // if we are recursively erroring while exiting + // from a fatal error, just kill the entire + // process immediately, which will prevent a + // full screen rendering window covering the + // error dialog + + Sys_Printf( "FATAL: recursed fatal error:\n%s\n", errorMessage ); + + va_start( argptr, fmt ); + idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); + va_end( argptr ); + errorMessage[sizeof(errorMessage)-1] = '\0'; + + Sys_Printf( "%s\n", errorMessage ); + + // write the console to a log file? + Sys_Quit(); + } + com_errorEntered = ERP_FATAL; + + va_start( argptr, fmt ); + idStr::vsnPrintf( errorMessage, sizeof(errorMessage), fmt, argptr ); + va_end( argptr ); + errorMessage[sizeof(errorMessage)-1] = '\0'; + + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); + } + + Sys_SetFatalError( errorMessage ); + + Shutdown(); + + Sys_Error( "%s", errorMessage ); +} + +/* +================== +idCommonLocal::Quit +================== +*/ +void idCommonLocal::Quit( void ) { + +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_RADIANT ) { + RadiantInit(); + return; + } +#endif + + // don't try to shutdown if we are in a recursive error + if ( !com_errorEntered ) { + Shutdown(); + } + + Sys_Quit(); +} + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters separate the commandLine string into multiple console +command lines. + +All of these are valid: + +doom +set test blah +map test +doom set test blah+map test +doom set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +idCmdArgs com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +idCommonLocal::ParseCommandLine +================== +*/ +void idCommonLocal::ParseCommandLine( int argc, const char **argv ) { + int i, current_count; + + com_numConsoleLines = 0; + current_count = 0; + // API says no program path + for ( i = 0; i < argc; i++ ) { + if ( argv[ i ][ 0 ] == '+' ) { + com_numConsoleLines++; + com_consoleLines[ com_numConsoleLines-1 ].AppendArg( argv[ i ] + 1 ); + } else { + if ( !com_numConsoleLines ) { + com_numConsoleLines++; + } + com_consoleLines[ com_numConsoleLines-1 ].AppendArg( argv[ i ] ); + } + } +} + +/* +================== +idCommonLocal::ClearCommandLine +================== +*/ +void idCommonLocal::ClearCommandLine( void ) { + com_numConsoleLines = 0; +} + +/* +================== +idCommonLocal::SafeMode + +Check for "safe" on the command line, which will +skip loading of config file (DoomConfig.cfg) +================== +*/ +bool idCommonLocal::SafeMode( void ) { + int i; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + if ( !idStr::Icmp( com_consoleLines[ i ].Argv(0), "safe" ) + || !idStr::Icmp( com_consoleLines[ i ].Argv(0), "cvar_restart" ) ) { + com_consoleLines[ i ].Clear(); + return true; + } + } + return false; +} + +/* +================== +idCommonLocal::CheckToolMode + +Check for "renderbump", "dmap", or "editor" on the command line, +and force fullscreen off in those cases +================== +*/ +void idCommonLocal::CheckToolMode( void ) { + int i; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + if ( !idStr::Icmp( com_consoleLines[ i ].Argv(0), "guieditor" ) ) { + com_editors |= EDITOR_GUI; + } + else if ( !idStr::Icmp( com_consoleLines[ i ].Argv(0), "debugger" ) ) { + com_editors |= EDITOR_DEBUGGER; + } + else if ( !idStr::Icmp( com_consoleLines[ i ].Argv(0), "editor" ) ) { + com_editors |= EDITOR_RADIANT; + } + // Nerve: Add support for the material editor + else if ( !idStr::Icmp( com_consoleLines[ i ].Argv(0), "materialEditor" ) ) { + com_editors |= EDITOR_MATERIAL; + } + + if ( !idStr::Icmp( com_consoleLines[ i ].Argv(0), "renderbump" ) + || !idStr::Icmp( com_consoleLines[ i ].Argv(0), "editor" ) + || !idStr::Icmp( com_consoleLines[ i ].Argv(0), "guieditor" ) + || !idStr::Icmp( com_consoleLines[ i ].Argv(0), "debugger" ) + || !idStr::Icmp( com_consoleLines[ i ].Argv(0), "dmap" ) + || !idStr::Icmp( com_consoleLines[ i ].Argv(0), "materialEditor" ) + ) { + cvarSystem->SetCVarBool( "r_fullscreen", false ); + return; + } + } +} + +/* +================== +idCommonLocal::StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets should +be after execing the config and default. +================== +*/ +void idCommonLocal::StartupVariable( const char *match, bool once ) { + int i; + const char *s; + + i = 0; + while ( i < com_numConsoleLines ) { + if ( strcmp( com_consoleLines[ i ].Argv( 0 ), "set" ) ) { + i++; + continue; + } + + s = com_consoleLines[ i ].Argv(1); + + if ( !match || !idStr::Icmp( s, match ) ) { + cvarSystem->SetCVarString( s, com_consoleLines[ i ].Argv( 2 ) ); + if ( once ) { + // kill the line + int j = i + 1; + while ( j < com_numConsoleLines ) { + com_consoleLines[ j - 1 ] = com_consoleLines[ j ]; + j++; + } + com_numConsoleLines--; + continue; + } + } + i++; + } +} + +/* +================== +idCommonLocal::AddStartupCommands + +Adds command line parameters as script statements +Commands are separated by + signs + +Returns true if any late commands were added, which +will keep the demoloop from immediately starting +================== +*/ +bool idCommonLocal::AddStartupCommands( void ) { + int i; + bool added; + + added = false; + // quote every token, so args with semicolons can work + for ( i = 0; i < com_numConsoleLines; i++ ) { + if ( !com_consoleLines[i].Argc() ) { + continue; + } + + // set commands won't override menu startup + if ( idStr::Icmpn( com_consoleLines[i].Argv(0), "set", 3 ) ) { + added = true; + } + // directly as tokenized so nothing gets screwed + cmdSystem->BufferCommandArgs( CMD_EXEC_APPEND, com_consoleLines[i] ); + } + + return added; +} + +/* +================= +idCommonLocal::InitTool +================= +*/ +void idCommonLocal::InitTool( const int tool, const idDict *dict ) { +#ifdef ID_ALLOW_TOOLS + if ( tool & EDITOR_SOUND ) { + SoundEditorInit( dict ); + } else if ( tool & EDITOR_LIGHT ) { + LightEditorInit( dict ); + } else if ( tool & EDITOR_PARTICLE ) { + ParticleEditorInit( dict ); + } else if ( tool & EDITOR_AF ) { + AFEditorInit( dict ); + } +#endif +} + +/* +================== +idCommonLocal::ActivateTool + +Activates or Deactivates a tool +================== +*/ +void idCommonLocal::ActivateTool( bool active ) { + com_editorActive = active; + Sys_GrabMouseCursor( !active ); +} + +/* +================== +idCommonLocal::WriteFlaggedCVarsToFile +================== +*/ +void idCommonLocal::WriteFlaggedCVarsToFile( const char *filename, int flags, const char *setCmd ) { + idFile *f; + + f = fileSystem->OpenFileWrite( filename ); + if ( !f ) { + Printf( "Couldn't write %s.\n", filename ); + return; + } + cvarSystem->WriteFlaggedVariables( flags, setCmd, f ); + fileSystem->CloseFile( f ); +} + +/* +================== +idCommonLocal::WriteConfigToFile +================== +*/ +void idCommonLocal::WriteConfigToFile( const char *filename ) { + idFile *f; +#ifdef ID_WRITE_VERSION + ID_TIME_T t; + char *curtime; + idStr runtag; + idFile_Memory compressed( "compressed" ); + idBase64 out; +#endif + + f = fileSystem->OpenFileWrite( filename ); + if ( !f ) { + Printf ("Couldn't write %s.\n", filename ); + return; + } + +#ifdef ID_WRITE_VERSION + assert( config_compressor ); + t = time( NULL ); + curtime = ctime( &t ); + sprintf( runtag, "%s - %s", cvarSystem->GetCVarString( "si_version" ), curtime ); + config_compressor->Init( &compressed, true, 8 ); + config_compressor->Write( runtag.c_str(), runtag.Length() ); + config_compressor->FinishCompress( ); + out.Encode( (const byte *)compressed.GetDataPtr(), compressed.Length() ); + f->Printf( "// %s\n", out.c_str() ); +#endif + + idKeyInput::WriteBindings( f ); + cvarSystem->WriteFlaggedVariables( CVAR_ARCHIVE, "seta", f ); + fileSystem->CloseFile( f ); +} + +/* +=============== +idCommonLocal::WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void idCommonLocal::WriteConfiguration( void ) { + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) { + return; + } + + if ( !( cvarSystem->GetModifiedFlags() & CVAR_ARCHIVE ) ) { + return; + } + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + + // disable printing out the "Writing to:" message + bool developer = com_developer.GetBool(); + com_developer.SetBool( false ); + + WriteConfigToFile( CONFIG_FILE ); + session->WriteCDKey( ); + + // restore the developer cvar + com_developer.SetBool( developer ); +} + +/* +=============== +KeysFromBinding() +Returns the key bound to the command +=============== +*/ +const char* idCommonLocal::KeysFromBinding( const char *bind ) { + return idKeyInput::KeysFromBinding( bind ); +} + +/* +=============== +BindingFromKey() +Returns the binding bound to key +=============== +*/ +const char* idCommonLocal::BindingFromKey( const char *key ) { + return idKeyInput::BindingFromKey( key ); +} + +/* +=============== +ButtonState() +Returns the state of the button +=============== +*/ +int idCommonLocal::ButtonState( int key ) { + return usercmdGen->ButtonState(key); +} + +/* +=============== +ButtonState() +Returns the state of the key +=============== +*/ +int idCommonLocal::KeyState( int key ) { + return usercmdGen->KeyState(key); +} + +//============================================================================ + +#ifdef ID_ALLOW_TOOLS +/* +================== +Com_Editor_f + + we can start the editor dynamically, but we won't ever get back +================== +*/ +static void Com_Editor_f( const idCmdArgs &args ) { + RadiantInit(); +} + +/* +============= +Com_ScriptDebugger_f +============= +*/ +static void Com_ScriptDebugger_f( const idCmdArgs &args ) { + // Make sure it wasnt on the command line + if ( !( com_editors & EDITOR_DEBUGGER ) ) { + common->Printf( "Script debugger is currently disabled\n" ); + // DebuggerClientLaunch(); + } +} + +/* +============= +Com_EditGUIs_f +============= +*/ +static void Com_EditGUIs_f( const idCmdArgs &args ) { + GUIEditorInit(); +} + +/* +============= +Com_MaterialEditor_f +============= +*/ +static void Com_MaterialEditor_f( const idCmdArgs &args ) { + // Turn off sounds + soundSystem->SetMute( true ); + MaterialEditorInit(); +} +#endif // ID_ALLOW_TOOLS + +/* +============ +idCmdSystemLocal::PrintMemInfo_f + +This prints out memory debugging data +============ +*/ +static void PrintMemInfo_f( const idCmdArgs &args ) { + MemInfo mi; + session->GetCurrentMapName( mi.filebase ); + + renderSystem->PrintMemInfo( &mi ); // textures and models + soundSystem->PrintMemInfo( &mi ); // sounds + + common->Printf( " Used image memory: %s bytes\n", idStr::FormatNumber( mi.imageAssetsTotal ).c_str() ); + mi.assetTotals += mi.imageAssetsTotal; + + common->Printf( " Used model memory: %s bytes\n", idStr::FormatNumber( mi.modelAssetsTotal ).c_str() ); + mi.assetTotals += mi.modelAssetsTotal; + + common->Printf( " Used sound memory: %s bytes\n", idStr::FormatNumber( mi.soundAssetsTotal ).c_str() ); + mi.assetTotals += mi.soundAssetsTotal; + + common->Printf( " Used asset memory: %s bytes\n", idStr::FormatNumber( mi.assetTotals ).c_str() ); + + // write overview file + idFile *f; + + f = fileSystem->OpenFileAppend( "maps/printmeminfo.txt" ); + if ( !f ) { + return; + } + + f->Printf( "total(%s ) image(%s ) model(%s ) sound(%s ): %s\n", idStr::FormatNumber( mi.assetTotals ).c_str(), idStr::FormatNumber( mi.imageAssetsTotal ).c_str(), + idStr::FormatNumber( mi.modelAssetsTotal ).c_str(), idStr::FormatNumber( mi.soundAssetsTotal ).c_str(), mi.filebase.c_str() ); + + fileSystem->CloseFile( f ); +} + +#ifdef ID_ALLOW_TOOLS +/* +================== +Com_EditLights_f +================== +*/ +static void Com_EditLights_f( const idCmdArgs &args ) { + LightEditorInit( NULL ); + cvarSystem->SetCVarInteger( "g_editEntityMode", 1 ); +} + +/* +================== +Com_EditSounds_f +================== +*/ +static void Com_EditSounds_f( const idCmdArgs &args ) { + SoundEditorInit( NULL ); + cvarSystem->SetCVarInteger( "g_editEntityMode", 2 ); +} + +/* +================== +Com_EditDecls_f +================== +*/ +static void Com_EditDecls_f( const idCmdArgs &args ) { + DeclBrowserInit( NULL ); +} + +/* +================== +Com_EditAFs_f +================== +*/ +static void Com_EditAFs_f( const idCmdArgs &args ) { + AFEditorInit( NULL ); +} + +/* +================== +Com_EditParticles_f +================== +*/ +static void Com_EditParticles_f( const idCmdArgs &args ) { + ParticleEditorInit( NULL ); +} + +/* +================== +Com_EditScripts_f +================== +*/ +static void Com_EditScripts_f( const idCmdArgs &args ) { + ScriptEditorInit( NULL ); +} + +/* +================== +Com_EditPDAs_f +================== +*/ +static void Com_EditPDAs_f( const idCmdArgs &args ) { + PDAEditorInit( NULL ); +} +#endif // ID_ALLOW_TOOLS + +/* +================== +Com_Error_f + +Just throw a fatal error to test error shutdown procedures. +================== +*/ +static void Com_Error_f( const idCmdArgs &args ) { + if ( !com_developer.GetBool() ) { + commonLocal.Printf( "error may only be used in developer mode\n" ); + return; + } + + if ( args.Argc() > 1 ) { + commonLocal.FatalError( "Testing fatal error" ); + } else { + commonLocal.Error( "Testing drop error" ); + } +} + +/* +================== +Com_Freeze_f + +Just freeze in place for a given number of seconds to test error recovery. +================== +*/ +static void Com_Freeze_f( const idCmdArgs &args ) { + float s; + int start, now; + + if ( args.Argc() != 2 ) { + commonLocal.Printf( "freeze \n" ); + return; + } + + if ( !com_developer.GetBool() ) { + commonLocal.Printf( "freeze may only be used in developer mode\n" ); + return; + } + + s = atof( args.Argv(1) ); + + start = eventLoop->Milliseconds(); + + while ( 1 ) { + now = eventLoop->Milliseconds(); + if ( ( now - start ) * 0.001f > s ) { + break; + } + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( const idCmdArgs &args ) { + if ( !com_developer.GetBool() ) { + commonLocal.Printf( "crash may only be used in developer mode\n" ); + return; + } + + * ( int * ) 0 = 0x12345678; +} + +/* +================= +Com_Quit_f +================= +*/ +static void Com_Quit_f( const idCmdArgs &args ) { + commonLocal.Quit(); +} + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( const idCmdArgs &args ) { + idStr filename; + + if ( args.Argc() != 2 ) { + commonLocal.Printf( "Usage: writeconfig \n" ); + return; + } + + filename = args.Argv(1); + filename.DefaultFileExtension( ".cfg" ); + commonLocal.Printf( "Writing %s.\n", filename.c_str() ); + commonLocal.WriteConfigToFile( filename ); +} + +/* +================= +Com_SetMachineSpecs_f +================= +*/ +void Com_SetMachineSpec_f( const idCmdArgs &args ) { + commonLocal.SetMachineSpec(); +} + +/* +================= +Com_ExecMachineSpecs_f +================= +*/ +#ifdef MACOS_X +void OSX_GetVideoCard( int& outVendorId, int& outDeviceId ); +bool OSX_GetCPUIdentification( int& cpuId, bool& oldArchitecture ); +#endif +void Com_ExecMachineSpec_f( const idCmdArgs &args ) { + if ( com_machineSpec.GetInteger() == 3 ) { + cvarSystem->SetCVarInteger( "image_anisotropy", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_lodbias", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_forceDownSize", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_roundDown", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_preload", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useAllFormats", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecular", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBump", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecularLimit", 64, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBumpLimit", 256, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_usePrecompressedTextures", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downsize", 0 , CVAR_ARCHIVE ); + cvarSystem->SetCVarString( "image_filter", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_anisotropy", 8, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useCompression", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_ignoreHighQuality", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "s_maxSoundsPerShader", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_mode", 5, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useNormalCompression", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_multiSamples", 0, CVAR_ARCHIVE ); + } else if ( com_machineSpec.GetInteger() == 2 ) { + cvarSystem->SetCVarString( "image_filter", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_anisotropy", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_lodbias", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_forceDownSize", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_roundDown", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_preload", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useAllFormats", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecular", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBump", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecularLimit", 64, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBumpLimit", 256, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_usePrecompressedTextures", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downsize", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_anisotropy", 8, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useCompression", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_ignoreHighQuality", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "s_maxSoundsPerShader", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useNormalCompression", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_mode", 4, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_multiSamples", 0, CVAR_ARCHIVE ); + } else if ( com_machineSpec.GetInteger() == 1 ) { + cvarSystem->SetCVarString( "image_filter", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_anisotropy", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_lodbias", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSize", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_forceDownSize", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_roundDown", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_preload", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useCompression", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useAllFormats", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_usePrecompressedTextures", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecular", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBump", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecularLimit", 64, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBumpLimit", 256, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useNormalCompression", 2, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_mode", 3, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_multiSamples", 0, CVAR_ARCHIVE ); + } else { + cvarSystem->SetCVarString( "image_filter", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_anisotropy", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_lodbias", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_roundDown", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_preload", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useAllFormats", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_usePrecompressedTextures", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSize", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_anisotropy", 0, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useCompression", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_ignoreHighQuality", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "s_maxSoundsPerShader", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecular", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBump", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecularLimit", 64, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBumpLimit", 256, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_mode", 3 , CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_useNormalCompression", 2, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "r_multiSamples", 0, CVAR_ARCHIVE ); + } + + if ( Sys_GetVideoRam() < 128 ) { + cvarSystem->SetCVarBool( "image_ignoreHighQuality", true, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSize", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeLimit", 256, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecular", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecularLimit", 64, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBump", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeBumpLimit", 256, CVAR_ARCHIVE ); + } + + if ( Sys_GetSystemRam() < 512 ) { + cvarSystem->SetCVarBool( "image_ignoreHighQuality", true, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "s_maxSoundsPerShader", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSize", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeLimit", 256, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecular", 1, CVAR_ARCHIVE ); + cvarSystem->SetCVarInteger( "image_downSizeSpecularLimit", 64, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "com_purgeAll", true, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "r_forceLoadImages", true, CVAR_ARCHIVE ); + } else { + cvarSystem->SetCVarBool( "com_purgeAll", false, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "r_forceLoadImages", false, CVAR_ARCHIVE ); + } + + bool oldCard = false; + bool nv10or20 = false; + renderSystem->GetCardCaps( oldCard, nv10or20 ); + if ( oldCard ) { + cvarSystem->SetCVarBool( "g_decals", false, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "g_projectileLights", false, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "g_doubleVision", false, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "g_muzzleFlash", false, CVAR_ARCHIVE ); + } else { + cvarSystem->SetCVarBool( "g_decals", true, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "g_projectileLights", true, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "g_doubleVision", true, CVAR_ARCHIVE ); + cvarSystem->SetCVarBool( "g_muzzleFlash", true, CVAR_ARCHIVE ); + } + if ( nv10or20 ) { + cvarSystem->SetCVarInteger( "image_useNormalCompression", 1, CVAR_ARCHIVE ); + } + +#if MACOS_X + // On low settings, G4 systems & 64MB FX5200/NV34 Systems should default shadows off + bool oldArch; + int vendorId, deviceId, cpuId; + OSX_GetVideoCard( vendorId, deviceId ); + OSX_GetCPUIdentification( cpuId, oldArch ); + bool isFX5200 = vendorId == 0x10DE && ( deviceId & 0x0FF0 ) == 0x0320; + if ( ( oldArch || ( isFX5200 && Sys_GetVideoRam() < 128 ) ) && com_machineSpec.GetInteger() == 0 ) { + cvarSystem->SetCVarBool( "r_shadows", false, CVAR_ARCHIVE ); + } else { + cvarSystem->SetCVarBool( "r_shadows", true, CVAR_ARCHIVE ); + } +#endif +} + +/* +================= +Com_ReloadEngine_f +================= +*/ +void Com_ReloadEngine_f( const idCmdArgs &args ) { + bool menu = false; + + if ( !commonLocal.IsInitialized() ) { + return; + } + + if ( args.Argc() > 1 && idStr::Icmp( args.Argv( 1 ), "menu" ) == 0 ) { + menu = true; + } + + common->Printf( "============= ReloadEngine start =============\n" ); + if ( !menu ) { + Sys_ShowConsole( 1, false ); + } + commonLocal.ShutdownGame( true ); + commonLocal.InitGame(); + if ( !menu && !idAsyncNetwork::serverDedicated.GetBool() ) { + Sys_ShowConsole( 0, false ); + } + common->Printf( "============= ReloadEngine end ===============\n" ); + + if ( !cmdSystem->PostReloadEngine() ) { + if ( menu ) { + session->StartMenu( ); + } + } +} + +/* +=============== +idCommonLocal::GetLanguageDict +=============== +*/ +const idLangDict *idCommonLocal::GetLanguageDict( void ) { + return &languageDict; +} + +const char *idCommonLocal::GetLocalizedString( const char *token, int langIndex ) { + return languageDict.GetString( token ); +} + +const idLangKeyValue *idCommonLocal::GetLocalizedString( int index, int langIndex ) { + if ( index < 0 || index >= languageDict.GetNumKeyVals() ) { + return NULL; + } + return languageDict.GetKeyVal( index ); +} + +int idCommonLocal::GetNumLanguages( void ) const { + return 1; +} + +int idCommonLocal::GetNumLocalizedStrings( void ) const { + return languageDict.GetNumKeyVals(); +} + +const char *idCommonLocal::GetLanguage( int index ) const { + return index == 0 ? sys_lang.GetString() : ""; +} + +bool idCommonLocal::LanguageHasVO( int index ) const { + return index == 0; +} + +/* +=============== +idCommonLocal::FilterLangList +=============== +*/ +void idCommonLocal::FilterLangList( idStrList* list, idStr lang ) { + + idStr temp; + for( int i = 0; i < list->Num(); i++ ) { + temp = (*list)[i]; + temp = temp.Right(temp.Length()-strlen("strings/")); + temp = temp.Left(lang.Length()); + if(idStr::Icmp(temp, lang) != 0) { + list->RemoveIndex(i); + i--; + } + } +} + +/* +=============== +idCommonLocal::InitLanguageDict +=============== +*/ +void idCommonLocal::InitLanguageDict( void ) { + idStr fileName; + languageDict.Clear(); + + //D3XP: Instead of just loading a single lang file for each language + //we are going to load all files that begin with the language name + //similar to the way pak files work. So you can place english001.lang + //to add new strings to the english language dictionary + idFileList* langFiles; + langFiles = fileSystem->ListFilesTree( "strings", ".lang", true ); + + idStrList langList = langFiles->GetList(); + + StartupVariable( "sys_lang", false ); // let it be set on the command line - this is needed because this init happens very early + idStr langName = cvarSystem->GetCVarString( "sys_lang" ); + + //Loop through the list and filter + idStrList currentLangList = langList; + FilterLangList(¤tLangList, langName); + + if ( currentLangList.Num() == 0 ) { + // reset cvar to default and try to load again + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "reset sys_lang" ); + langName = cvarSystem->GetCVarString( "sys_lang" ); + currentLangList = langList; + FilterLangList(¤tLangList, langName); + } + + for( int i = 0; i < currentLangList.Num(); i++ ) { + //common->Printf("%s\n", currentLangList[i].c_str()); + languageDict.Load( currentLangList[i], false ); + } + + fileSystem->FreeFileList(langFiles); + + Sys_InitScanTable(); +} + +/* +=============== +idCommonLocal::LocalizeSpecificMapData +=============== +*/ +void idCommonLocal::LocalizeSpecificMapData( const char *fileName, idLangDict &langDict, const idLangDict &replaceArgs ) { + idStr out, ws, work; + + idMapFile map; + if ( map.Parse( fileName, false, false ) ) { + int count = map.GetNumEntities(); + for ( int i = 0; i < count; i++ ) { + idMapEntity *ent = map.GetEntity( i ); + if ( ent ) { + for ( int j = 0; j < replaceArgs.GetNumKeyVals(); j++ ) { + const idLangKeyValue *kv = replaceArgs.GetKeyVal( j ); + const char *temp = ent->epairs.GetString( kv->key ); + if ( temp && *temp ) { + idStr val = kv->value; + if ( val == temp ) { + ent->epairs.Set( kv->key, langDict.AddString( temp ) ); + } + } + } + } + } + map.Write( fileName, ".map" ); + } +} + +/* +=============== +idCommonLocal::LocalizeMapData +=============== +*/ +void idCommonLocal::LocalizeMapData( const char *fileName, idLangDict &langDict ) { + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + common->SetRefreshOnPrint( true ); + + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + common->Printf( "Processing %s\n", fileName ); + idStr mapFileName; + idToken token, token2; + idLangDict replaceArgs; + while ( src.ReadToken( &token ) ) { + mapFileName = token; + replaceArgs.Clear(); + src.ExpectTokenString( "{" ); + while ( src.ReadToken( &token) ) { + if ( token == "}" ) { + break; + } + if ( src.ReadToken( &token2 ) ) { + if ( token2 == "}" ) { + break; + } + replaceArgs.AddKeyVal( token, token2 ); + } + } + common->Printf( " localizing map %s...\n", mapFileName.c_str() ); + LocalizeSpecificMapData( mapFileName, langDict, replaceArgs ); + } + } + fileSystem->FreeFile( (void*)buffer ); + } + + common->SetRefreshOnPrint( false ); +} + +/* +=============== +idCommonLocal::LocalizeGui +=============== +*/ +void idCommonLocal::LocalizeGui( const char *fileName, idLangDict &langDict ) { + idStr out, ws, work; + const char *buffer = NULL; + out.Empty(); + int k; + char ch; + char slash = '\\'; + char tab = 't'; + char nl = 'n'; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + idFile *outFile = fileSystem->OpenFileWrite( fileName ); + common->Printf( "Processing %s\n", fileName ); + session->UpdateScreen(); + idToken token; + while( src.ReadToken( &token ) ) { + src.GetLastWhiteSpace( ws ); + out += ws; + if ( token.type == TT_STRING ) { + out += va( "\"%s\"", token.c_str() ); + } else { + out += token; + } + if ( out.Length() > 200000 ) { + outFile->Write( out.c_str(), out.Length() ); + out = ""; + } + work = token.Right( 6 ); + if ( token.Icmp( "text" ) == 0 || work.Icmp( "::text" ) == 0 || token.Icmp( "choices" ) == 0 ) { + if ( src.ReadToken( &token ) ) { + // see if already exists, if so save that id to this position in this file + // otherwise add this to the list and save the id to this position in this file + src.GetLastWhiteSpace( ws ); + out += ws; + token = langDict.AddString( token ); + out += "\""; + for ( k = 0; k < token.Length(); k++ ) { + ch = token[k]; + if ( ch == '\t' ) { + out += slash; + out += tab; + } else if ( ch == '\n' || ch == '\r' ) { + out += slash; + out += nl; + } else { + out += ch; + } + } + out += "\""; + } + } else if ( token.Icmp( "comment" ) == 0 ) { + if ( src.ReadToken( &token ) ) { + // need to write these out by hand to preserve any \n's + // see if already exists, if so save that id to this position in this file + // otherwise add this to the list and save the id to this position in this file + src.GetLastWhiteSpace( ws ); + out += ws; + out += "\""; + for ( k = 0; k < token.Length(); k++ ) { + ch = token[k]; + if ( ch == '\t' ) { + out += slash; + out += tab; + } else if ( ch == '\n' || ch == '\r' ) { + out += slash; + out += nl; + } else { + out += ch; + } + } + out += "\""; + } + } + } + outFile->Write( out.c_str(), out.Length() ); + fileSystem->CloseFile( outFile ); + } + fileSystem->FreeFile( (void*)buffer ); + } +} + +/* +================= +ReloadLanguage_f +================= +*/ +void Com_ReloadLanguage_f( const idCmdArgs &args ) { + commonLocal.InitLanguageDict(); +} + +typedef idHashTable ListHash; +void LoadMapLocalizeData(ListHash& listHash) { + + idStr fileName = "map_localize.cfg"; + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + idStr classname; + idToken token; + + + + while ( src.ReadToken( &token ) ) { + classname = token; + src.ExpectTokenString( "{" ); + + idStrList list; + while ( src.ReadToken( &token) ) { + if ( token == "}" ) { + break; + } + list.Append(token); + } + + listHash.Set(classname, list); + } + } + fileSystem->FreeFile( (void*)buffer ); + } + +} + +void LoadGuiParmExcludeList(idStrList& list) { + + idStr fileName = "guiparm_exclude.cfg"; + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + if ( fileSystem->ReadFile( fileName, (void**)&buffer ) > 0 ) { + src.LoadMemory( buffer, strlen(buffer), fileName ); + if ( src.IsLoaded() ) { + idStr classname; + idToken token; + + + + while ( src.ReadToken( &token ) ) { + list.Append(token); + } + } + fileSystem->FreeFile( (void*)buffer ); + } +} + +bool TestMapVal(idStr& str) { + //Already Localized? + if(str.Find("#str_") != -1) { + return false; + } + + return true; +} + +bool TestGuiParm(const char* parm, const char* value, idStrList& excludeList) { + + idStr testVal = value; + + //Already Localized? + if(testVal.Find("#str_") != -1) { + return false; + } + + //Numeric + if(testVal.IsNumeric()) { + return false; + } + + //Contains :: + if(testVal.Find("::") != -1) { + return false; + } + + //Contains / + if(testVal.Find("/") != -1) { + return false; + } + + if(excludeList.Find(testVal)) { + return false; + } + + return true; +} + +void GetFileList(const char* dir, const char* ext, idStrList& list) { + + //Recurse Subdirectories + idStrList dirList; + Sys_ListFiles(dir, "/", dirList); + for(int i = 0; i < dirList.Num(); i++) { + if(dirList[i] == "." || dirList[i] == "..") { + continue; + } + idStr fullName = va("%s/%s", dir, dirList[i].c_str()); + GetFileList(fullName, ext, list); + } + + idStrList fileList; + Sys_ListFiles(dir, ext, fileList); + for(int i = 0; i < fileList.Num(); i++) { + idStr fullName = va("%s/%s", dir, fileList[i].c_str()); + list.Append(fullName); + } +} + +int LocalizeMap(const char* mapName, idLangDict &langDict, ListHash& listHash, idStrList& excludeList, bool writeFile) { + + common->Printf("Localizing Map '%s'\n", mapName); + + int strCount = 0; + + idMapFile map; + if ( map.Parse(mapName, false, false ) ) { + int count = map.GetNumEntities(); + for ( int j = 0; j < count; j++ ) { + idMapEntity *ent = map.GetEntity( j ); + if ( ent ) { + + idStr classname = ent->epairs.GetString("classname"); + + //Hack: for info_location + bool hasLocation = false; + + idStrList* list; + listHash.Get(classname, &list); + if(list) { + + for(int k = 0; k < list->Num(); k++) { + + idStr val = ent->epairs.GetString((*list)[k], ""); + + if(val.Length() && classname == "info_location" && (*list)[k] == "location") { + hasLocation = true; + } + + if(val.Length() && TestMapVal(val)) { + + if(!hasLocation || (*list)[k] == "location") { + //Localize it!!! + strCount++; + ent->epairs.Set( (*list)[k], langDict.AddString( val ) ); + } + } + } + } + + listHash.Get("all", &list); + if(list) { + for(int k = 0; k < list->Num(); k++) { + idStr val = ent->epairs.GetString((*list)[k], ""); + if(val.Length() && TestMapVal(val)) { + //Localize it!!! + strCount++; + ent->epairs.Set( (*list)[k], langDict.AddString( val ) ); + } + } + } + + //Localize the gui_parms + const idKeyValue* kv = ent->epairs.MatchPrefix("gui_parm"); + while( kv ) { + if(TestGuiParm(kv->GetKey(), kv->GetValue(), excludeList)) { + //Localize It! + strCount++; + ent->epairs.Set( kv->GetKey(), langDict.AddString( kv->GetValue() ) ); + } + kv = ent->epairs.MatchPrefix( "gui_parm", kv ); + } + } + } + if(writeFile && strCount > 0) { + //Before we write the map file lets make a backup of the original + idStr file = fileSystem->RelativePathToOSPath(mapName); + idStr bak = file.Left(file.Length() - 4); + bak.Append(".bak_loc"); + ::CopyFileA( file.c_str(), bak.c_str(), FALSE ); + + map.Write( mapName, ".map" ); + } + } + + common->Printf("Count: %d\n", strCount); + return strCount; +} + +/* +================= +LocalizeMaps_f +================= +*/ +void Com_LocalizeMaps_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "Usage: localizeMaps \n" ); + return; + } + + int strCount = 0; + + bool count = false; + bool dictUpdate = false; + bool write = false; + + if ( idStr::Icmp( args.Argv(1), "count" ) == 0 ) { + count = true; + } else if ( idStr::Icmp( args.Argv(1), "dictupdate" ) == 0 ) { + count = true; + dictUpdate = true; + } else if ( idStr::Icmp( args.Argv(1), "all" ) == 0 ) { + count = true; + dictUpdate = true; + write = true; + } else { + common->Printf( "Invalid Command\n" ); + common->Printf( "Usage: localizeMaps \n" ); + return; + + } + + idLangDict strTable; + idStr filename = va("strings/english%.3i.lang", com_product_lang_ext.GetInteger()); + if(strTable.Load( filename ) == false) { + //This is a new file so set the base index + strTable.SetBaseID(com_product_lang_ext.GetInteger()*100000); + } + + common->SetRefreshOnPrint( true ); + + ListHash listHash; + LoadMapLocalizeData(listHash); + + idStrList excludeList; + LoadGuiParmExcludeList(excludeList); + + if(args.Argc() == 3) { + strCount += LocalizeMap(args.Argv(2), strTable, listHash, excludeList, write); + } else { + idStrList files; + GetFileList("z:/d3xp/d3xp/maps/game", "*.map", files); + for ( int i = 0; i < files.Num(); i++ ) { + idStr file = fileSystem->OSPathToRelativePath(files[i]); + strCount += LocalizeMap(file, strTable, listHash, excludeList, write); + } + } + + if(count) { + common->Printf("Localize String Count: %d\n", strCount); + } + + common->SetRefreshOnPrint( false ); + + if(dictUpdate) { + strTable.Save( filename ); + } +} + +/* +================= +LocalizeGuis_f +================= +*/ +void Com_LocalizeGuis_f( const idCmdArgs &args ) { + + if ( args.Argc() != 2 ) { + common->Printf( "Usage: localizeGuis \n" ); + return; + } + + idLangDict strTable; + + idStr filename = va("strings/english%.3i.lang", com_product_lang_ext.GetInteger()); + if(strTable.Load( filename ) == false) { + //This is a new file so set the base index + strTable.SetBaseID(com_product_lang_ext.GetInteger()*100000); + } + + idFileList *files; + if ( idStr::Icmp( args.Argv(1), "all" ) == 0 ) { + idStr game = cvarSystem->GetCVarString( "fs_game" ); + if(game.Length()) { + files = fileSystem->ListFilesTree( "guis", "*.gui", true, game ); + } else { + files = fileSystem->ListFilesTree( "guis", "*.gui", true ); + } + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + commonLocal.LocalizeGui( files->GetFile( i ), strTable ); + } + fileSystem->FreeFileList( files ); + + if(game.Length()) { + files = fileSystem->ListFilesTree( "guis", "*.pd", true, game ); + } else { + files = fileSystem->ListFilesTree( "guis", "*.pd", true, "d3xp" ); + } + + for ( int i = 0; i < files->GetNumFiles(); i++ ) { + commonLocal.LocalizeGui( files->GetFile( i ), strTable ); + } + fileSystem->FreeFileList( files ); + + } else { + commonLocal.LocalizeGui( args.Argv(1), strTable ); + } + strTable.Save( filename ); +} + +void Com_LocalizeGuiParmsTest_f( const idCmdArgs &args ) { + + common->SetRefreshOnPrint( true ); + + idFile *localizeFile = fileSystem->OpenFileWrite( "gui_parm_localize.csv" ); + idFile *noLocalizeFile = fileSystem->OpenFileWrite( "gui_parm_nolocalize.csv" ); + + idStrList excludeList; + LoadGuiParmExcludeList(excludeList); + + idStrList files; + GetFileList("z:/d3xp/d3xp/maps/game", "*.map", files); + + for ( int i = 0; i < files.Num(); i++ ) { + + common->Printf("Testing Map '%s'\n", files[i].c_str()); + idMapFile map; + + idStr file = fileSystem->OSPathToRelativePath(files[i]); + if ( map.Parse(file, false, false ) ) { + int count = map.GetNumEntities(); + for ( int j = 0; j < count; j++ ) { + idMapEntity *ent = map.GetEntity( j ); + if ( ent ) { + const idKeyValue* kv = ent->epairs.MatchPrefix("gui_parm"); + while( kv ) { + if(TestGuiParm(kv->GetKey(), kv->GetValue(), excludeList)) { + idStr out = va("%s,%s,%s\r\n", kv->GetValue().c_str(), kv->GetKey().c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + } else { + idStr out = va("%s,%s,%s\r\n", kv->GetValue().c_str(), kv->GetKey().c_str(), file.c_str()); + noLocalizeFile->Write( out.c_str(), out.Length() ); + } + kv = ent->epairs.MatchPrefix( "gui_parm", kv ); + } + } + } + } + } + + fileSystem->CloseFile( localizeFile ); + fileSystem->CloseFile( noLocalizeFile ); + + common->SetRefreshOnPrint( false ); +} + + +void Com_LocalizeMapsTest_f( const idCmdArgs &args ) { + + ListHash listHash; + LoadMapLocalizeData(listHash); + + + common->SetRefreshOnPrint( true ); + + idFile *localizeFile = fileSystem->OpenFileWrite( "map_localize.csv" ); + + idStrList files; + GetFileList("z:/d3xp/d3xp/maps/game", "*.map", files); + + for ( int i = 0; i < files.Num(); i++ ) { + + common->Printf("Testing Map '%s'\n", files[i].c_str()); + idMapFile map; + + idStr file = fileSystem->OSPathToRelativePath(files[i]); + if ( map.Parse(file, false, false ) ) { + int count = map.GetNumEntities(); + for ( int j = 0; j < count; j++ ) { + idMapEntity *ent = map.GetEntity( j ); + if ( ent ) { + + //Temp code to get a list of all entity key value pairs + /*idStr classname = ent->epairs.GetString("classname"); + if(classname == "worldspawn" || classname == "func_static" || classname == "light" || classname == "speaker" || classname.Left(8) == "trigger_") { + continue; + } + for( int i = 0; i < ent->epairs.GetNumKeyVals(); i++) { + const idKeyValue* kv = ent->epairs.GetKeyVal(i); + idStr out = va("%s,%s,%s,%s\r\n", classname.c_str(), kv->GetKey().c_str(), kv->GetValue().c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + }*/ + + idStr classname = ent->epairs.GetString("classname"); + + //Hack: for info_location + bool hasLocation = false; + + idStrList* list; + listHash.Get(classname, &list); + if(list) { + + for(int k = 0; k < list->Num(); k++) { + + idStr val = ent->epairs.GetString((*list)[k], ""); + + if(classname == "info_location" && (*list)[k] == "location") { + hasLocation = true; + } + + if(val.Length() && TestMapVal(val)) { + + if(!hasLocation || (*list)[k] == "location") { + idStr out = va("%s,%s,%s\r\n", val.c_str(), (*list)[k].c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + } + } + } + } + + listHash.Get("all", &list); + if(list) { + for(int k = 0; k < list->Num(); k++) { + idStr val = ent->epairs.GetString((*list)[k], ""); + if(val.Length() && TestMapVal(val)) { + idStr out = va("%s,%s,%s\r\n", val.c_str(), (*list)[k].c_str(), file.c_str()); + localizeFile->Write( out.c_str(), out.Length() ); + } + } + } + } + } + } + } + + fileSystem->CloseFile( localizeFile ); + + common->SetRefreshOnPrint( false ); +} + +/* +================= +Com_StartBuild_f +================= +*/ +void Com_StartBuild_f( const idCmdArgs &args ) { + globalImages->StartBuild(); +} + +/* +================= +Com_FinishBuild_f +================= +*/ +void Com_FinishBuild_f( const idCmdArgs &args ) { + if ( game ) { + game->CacheDictionaryMedia( NULL ); + } + globalImages->FinishBuild( ( args.Argc() > 1 ) ); +} + +/* +============== +Com_Help_f +============== +*/ +void Com_Help_f( const idCmdArgs &args ) { + common->Printf( "\nCommonly used commands:\n" ); + common->Printf( " spawnServer - start the server.\n" ); + common->Printf( " disconnect - shut down the server.\n" ); + common->Printf( " listCmds - list all console commands.\n" ); + common->Printf( " listCVars - list all console variables.\n" ); + common->Printf( " kick - kick a client by number.\n" ); + common->Printf( " gameKick - kick a client by name.\n" ); + common->Printf( " serverNextMap - immediately load next map.\n" ); + common->Printf( " serverMapRestart - restart the current map.\n" ); + common->Printf( " serverForceReady - force all players to ready status.\n" ); + common->Printf( "\nCommonly used variables:\n" ); + common->Printf( " si_name - server name (change requires a restart to see)\n" ); + common->Printf( " si_gametype - type of game.\n" ); + common->Printf( " si_fragLimit - max kills to win (or lives in Last Man Standing).\n" ); + common->Printf( " si_timeLimit - maximum time a game will last.\n" ); + common->Printf( " si_warmup - do pre-game warmup.\n" ); + common->Printf( " si_pure - pure server.\n" ); + common->Printf( " g_mapCycle - name of .scriptcfg file for cycling maps.\n" ); + common->Printf( "See mapcycle.scriptcfg for an example of a mapcyle script.\n\n" ); +} + +/* +================= +idCommonLocal::InitCommands +================= +*/ +void idCommonLocal::InitCommands( void ) { + cmdSystem->AddCommand( "error", Com_Error_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "causes an error" ); + cmdSystem->AddCommand( "crash", Com_Crash_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "causes a crash" ); + cmdSystem->AddCommand( "freeze", Com_Freeze_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "freezes the game for a number of seconds" ); + cmdSystem->AddCommand( "quit", Com_Quit_f, CMD_FL_SYSTEM, "quits the game" ); + cmdSystem->AddCommand( "exit", Com_Quit_f, CMD_FL_SYSTEM, "exits the game" ); + cmdSystem->AddCommand( "writeConfig", Com_WriteConfig_f, CMD_FL_SYSTEM, "writes a config file" ); + cmdSystem->AddCommand( "reloadEngine", Com_ReloadEngine_f, CMD_FL_SYSTEM, "reloads the engine down to including the file system" ); + cmdSystem->AddCommand( "setMachineSpec", Com_SetMachineSpec_f, CMD_FL_SYSTEM, "detects system capabilities and sets com_machineSpec to appropriate value" ); + cmdSystem->AddCommand( "execMachineSpec", Com_ExecMachineSpec_f, CMD_FL_SYSTEM, "execs the appropriate config files and sets cvars based on com_machineSpec" ); + +#if !defined( ID_DEMO_BUILD ) && !defined( ID_DEDICATED ) && !defined( Q4_DISABLE_TOOLS ) + // compilers + cmdSystem->AddCommand( "dmap", Dmap_f, CMD_FL_TOOL, "compiles a map", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "renderbump", RenderBump_f, CMD_FL_TOOL, "renders a bump map", idCmdSystem::ArgCompletion_ModelName ); + cmdSystem->AddCommand( "renderbumpFlat", RenderBumpFlat_f, CMD_FL_TOOL, "renders a flat bump map", idCmdSystem::ArgCompletion_ModelName ); + cmdSystem->AddCommand( "runAAS", RunAAS_f, CMD_FL_TOOL, "compiles an AAS file for a map", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "runAASDir", RunAASDir_f, CMD_FL_TOOL, "compiles AAS files for all maps in a folder", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "runReach", RunReach_f, CMD_FL_TOOL, "calculates reachability for an AAS file", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "roq", RoQFileEncode_f, CMD_FL_TOOL, "encodes a roq file" ); +#endif + +#ifdef ID_ALLOW_TOOLS + // editors + cmdSystem->AddCommand( "editor", Com_Editor_f, CMD_FL_TOOL, "launches the level editor Radiant" ); + cmdSystem->AddCommand( "editLights", Com_EditLights_f, CMD_FL_TOOL, "launches the in-game Light Editor" ); + cmdSystem->AddCommand( "editSounds", Com_EditSounds_f, CMD_FL_TOOL, "launches the in-game Sound Editor" ); + cmdSystem->AddCommand( "editDecls", Com_EditDecls_f, CMD_FL_TOOL, "launches the in-game Declaration Editor" ); + cmdSystem->AddCommand( "editAFs", Com_EditAFs_f, CMD_FL_TOOL, "launches the in-game Articulated Figure Editor" ); + cmdSystem->AddCommand( "editParticles", Com_EditParticles_f, CMD_FL_TOOL, "launches the in-game Particle Editor" ); + cmdSystem->AddCommand( "editScripts", Com_EditScripts_f, CMD_FL_TOOL, "launches the in-game Script Editor" ); + cmdSystem->AddCommand( "editGUIs", Com_EditGUIs_f, CMD_FL_TOOL, "launches the GUI Editor" ); + cmdSystem->AddCommand( "editPDAs", Com_EditPDAs_f, CMD_FL_TOOL, "launches the in-game PDA Editor" ); + cmdSystem->AddCommand( "debugger", Com_ScriptDebugger_f, CMD_FL_TOOL, "launches the Script Debugger" ); + + //BSM Nerve: Add support for the material editor + cmdSystem->AddCommand( "materialEditor", Com_MaterialEditor_f, CMD_FL_TOOL, "launches the Material Editor" ); +#endif + + cmdSystem->AddCommand( "printMemInfo", PrintMemInfo_f, CMD_FL_SYSTEM, "prints memory debugging data" ); + + // idLib commands + cmdSystem->AddCommand( "memoryDump", Mem_Dump_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "creates a memory dump" ); + cmdSystem->AddCommand( "memoryDumpCompressed", Mem_DumpCompressed_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "creates a compressed memory dump" ); + cmdSystem->AddCommand( "showStringMemory", idStr::ShowMemoryUsage_f, CMD_FL_SYSTEM, "shows memory used by strings" ); + cmdSystem->AddCommand( "showDictMemory", idDict::ShowMemoryUsage_f, CMD_FL_SYSTEM, "shows memory used by dictionaries" ); + cmdSystem->AddCommand( "listDictKeys", idDict::ListKeys_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "lists all keys used by dictionaries" ); + cmdSystem->AddCommand( "listDictValues", idDict::ListValues_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "lists all values used by dictionaries" ); + cmdSystem->AddCommand( "testSIMD", idSIMD::Test_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "test SIMD code" ); + + // localization + cmdSystem->AddCommand( "localizeGuis", Com_LocalizeGuis_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "localize guis" ); + cmdSystem->AddCommand( "localizeMaps", Com_LocalizeMaps_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "localize maps" ); + cmdSystem->AddCommand( "reloadLanguage", Com_ReloadLanguage_f, CMD_FL_SYSTEM, "reload language dict" ); + + //D3XP Localization + cmdSystem->AddCommand( "localizeGuiParmsTest", Com_LocalizeGuiParmsTest_f, CMD_FL_SYSTEM, "Create test files that show gui parms localized and ignored." ); + cmdSystem->AddCommand( "localizeMapsTest", Com_LocalizeMapsTest_f, CMD_FL_SYSTEM, "Create test files that shows which strings will be localized." ); + + // build helpers + cmdSystem->AddCommand( "startBuild", Com_StartBuild_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "prepares to make a build" ); + cmdSystem->AddCommand( "finishBuild", Com_FinishBuild_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "finishes the build process" ); + +#ifdef ID_DEDICATED + cmdSystem->AddCommand( "help", Com_Help_f, CMD_FL_SYSTEM, "shows help" ); +#endif +} + +/* +================= +idCommonLocal::InitRenderSystem +================= +*/ +void idCommonLocal::InitRenderSystem( void ) { + if ( com_skipRenderer.GetBool() ) { + return; + } + + renderSystem->InitOpenGL(); + PrintLoadingMessage( common->GetLocalizedString( "#str_104343" ) ); +} + +/* +================= +idCommonLocal::PrintLoadingMessage +================= +*/ +void idCommonLocal::PrintLoadingMessage( const char *msg ) { + if ( !( msg && *msg ) ) { + return; + } + renderSystem->BeginFrame( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight() ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, declManager->FindMaterial( "gfx/splashScreen" ) ); + int len = strlen( msg ); + renderSystem->DrawSmallStringExt( ( 640 - len * SMALLCHAR_WIDTH ) / 2, 410, msg, idVec4( 0.0f, 0.81f, 0.94f, 1.0f ), true, declManager->FindMaterial( "gfx/2d/bigchars" ) ); + renderSystem->EndFrame( NULL, NULL ); +} + +/* +================= +idCommonLocal::InitSIMD +================= +*/ +void idCommonLocal::InitSIMD( void ) { + idSIMD::InitProcessor( "doom", com_forceGenericSIMD.GetBool() ); + com_forceGenericSIMD.ClearModified(); +} + +/* +================= +idCommonLocal::Frame +================= +*/ +void idCommonLocal::Frame( void ) { + try { + + // pump all the events + Sys_GenerateEvents(); + + // write config file if anything changed + WriteConfiguration(); + + // change SIMD implementation if required + if ( com_forceGenericSIMD.IsModified() ) { + InitSIMD(); + } + + eventLoop->RunEventLoop(); + + com_frameTime = com_ticNumber * GetUserCmdMSec(); + + idAsyncNetwork::RunFrame(); + + if ( idAsyncNetwork::IsActive() ) { + if ( idAsyncNetwork::serverDedicated.GetInteger() != 1 ) { + session->GuiFrameEvents(); + session->UpdateScreen( false ); + } + } else { + session->Frame(); + + // normal, in-sequence screen update + session->UpdateScreen( false ); + } + + // report timing information + if ( com_speeds.GetBool() ) { + static int lastTime; + int nowTime = Sys_Milliseconds(); + int com_frameMsec = nowTime - lastTime; + lastTime = nowTime; + Printf( "frame:%i all:%3i gfr:%3i rf:%3i bk:%3i\n", com_frameNumber, com_frameMsec, time_gameFrame, time_frontend, time_backend ); + time_gameFrame = 0; + time_gameDraw = 0; + } + + com_frameNumber++; + + // set idLib frame number for frame based memory dumps + idLib::frameNumber = com_frameNumber; + + // the FPU stack better be empty at this point or some bad code or compiler bug left values on the stack + if ( !Sys_FPU_StackIsEmpty() ) { + Printf( Sys_FPU_GetState() ); + FatalError( "idCommon::Frame: the FPU stack is not empty at the end of the frame\n" ); + } + } + + catch( idException & ) { + return; // an ERP_DROP was thrown + } +} + +/* +================= +idCommonLocal::GUIFrame +================= +*/ +void idCommonLocal::GUIFrame( bool execCmd, bool network ) { + Sys_GenerateEvents(); + eventLoop->RunEventLoop( execCmd ); // and execute any commands + com_frameTime = com_ticNumber * GetUserCmdMSec(); + if ( network ) { + idAsyncNetwork::RunFrame(); + } + session->Frame(); + session->UpdateScreen( false ); +} + +/* +================= +idCommonLocal::SingleAsyncTic + +The system will asyncronously call this function 60 times a second to +handle the time-critical functions that we don't want limited to +the frame rate: + +sound mixing +user input generation (conditioned by com_asyncInput) +packet server operation +packet client operation + +We are not using thread safe libraries, so any functionality put here must +be VERY VERY careful about what it calls. +================= +*/ + +typedef struct { + int milliseconds; // should always be incremeting by 60hz + int deltaMsec; // should always be 16 + int timeConsumed; // msec spent in Com_AsyncThread() + int clientPacketsReceived; + int serverPacketsReceived; + int mostRecentServerPacketSequence; +} asyncStats_t; + +static const int MAX_ASYNC_STATS = 1024; +asyncStats_t com_asyncStats[MAX_ASYNC_STATS]; // indexed by com_ticNumber +int prevAsyncMsec; +int lastTicMsec; + +void idCommonLocal::SingleAsyncTic( void ) { + // main thread code can prevent this from happening while modifying + // critical data structures + Sys_EnterCriticalSection(); + + asyncStats_t *stat = &com_asyncStats[com_ticNumber & (MAX_ASYNC_STATS-1)]; + memset( stat, 0, sizeof( *stat ) ); + stat->milliseconds = Sys_Milliseconds(); + stat->deltaMsec = stat->milliseconds - com_asyncStats[(com_ticNumber - 1) & (MAX_ASYNC_STATS-1)].milliseconds; + + if ( usercmdGen && com_asyncInput.GetBool() ) { + usercmdGen->UsercmdInterrupt(); + } + + switch ( com_asyncSound.GetInteger() ) { + case 1: + soundSystem->AsyncUpdate( stat->milliseconds ); + break; + case 3: + soundSystem->AsyncUpdateWrite( stat->milliseconds ); + break; + } + + // we update com_ticNumber after all the background tasks + // have completed their work for this tic + com_ticNumber++; + + stat->timeConsumed = Sys_Milliseconds() - stat->milliseconds; + + Sys_LeaveCriticalSection(); +} + +/* +================= +idCommonLocal::Async +================= +*/ +void idCommonLocal::Async( void ) { + if ( com_shuttingDown ) { + return; + } + + int msec = Sys_Milliseconds(); + if ( !lastTicMsec ) { + lastTicMsec = msec - GetUserCmdMSec(); + } + + if ( !com_preciseTic.GetBool() ) { + // just run a single tic, even if the exact msec isn't precise + SingleAsyncTic(); + return; + } + + int ticMsec = GetUserCmdMSec(); + + // the number of msec per tic can be varies with the timescale cvar + float timescale = com_timescale.GetFloat(); + if ( timescale != 1.0f ) { + ticMsec /= timescale; + if ( ticMsec < 1 ) { + ticMsec = 1; + } + } + + // don't skip too many + if ( timescale == 1.0f ) { + if ( lastTicMsec + 10 * GetUserCmdMSec() < msec ) { + lastTicMsec = msec - 10 * GetUserCmdMSec(); + } + } + + while ( lastTicMsec + ticMsec <= msec ) { + SingleAsyncTic(); + lastTicMsec += ticMsec; + } +} + +/* +================= +idCommonLocal::LoadGameDLL +================= +*/ +void idCommonLocal::LoadGameDLL( void ) { +#ifdef __DOOM_DLL__ + char dllPath[ MAX_OSPATH ]; + + gameImport_t gameImport; + gameExport_t gameExport; + GetGameAPI_t GetGameAPI; + + fileSystem->FindDLL( "game", dllPath, true ); + + if ( !dllPath[ 0 ] ) { + common->FatalError( "couldn't find game dynamic library" ); + return; + } + common->DPrintf( "Loading game DLL: '%s'\n", dllPath ); + gameDLL = sys->DLL_Load( dllPath ); + if ( !gameDLL ) { + common->FatalError( "couldn't load game dynamic library" ); + return; + } + + GetGameAPI = (GetGameAPI_t) Sys_DLL_GetProcAddress( gameDLL, "GetGameAPI" ); + if ( !GetGameAPI ) { + Sys_DLL_Unload( gameDLL ); + gameDLL = NULL; + common->FatalError( "couldn't find game DLL API" ); + return; + } + + gameImport.version = GAME_API_VERSION; + gameImport.sys = ::sys; + gameImport.common = ::common; + gameImport.cmdSystem = ::cmdSystem; + gameImport.cvarSystem = ::cvarSystem; + gameImport.fileSystem = ::fileSystem; + gameImport.networkSystem = ::networkSystem; + gameImport.renderSystem = ::renderSystem; + gameImport.soundSystem = ::soundSystem; + gameImport.renderModelManager = ::renderModelManager; + gameImport.uiManager = ::uiManager; + gameImport.declManager = ::declManager; + gameImport.AASFileManager = ::AASFileManager; + gameImport.collisionModelManager = ::collisionModelManager; + + gameExport = *GetGameAPI( &gameImport ); + + if ( gameExport.version != GAME_API_VERSION ) { + Sys_DLL_Unload( gameDLL ); + gameDLL = NULL; + common->FatalError( "wrong game DLL API version" ); + return; + } + + game = gameExport.game; + gameEdit = gameExport.gameEdit; + +#endif + + // initialize the game object + if ( game != NULL ) { + #ifdef RV_UNIFIED_ALLOCATOR + game->Init( Memory::Allocate, Memory::Free, Memory::MSize ); + #else + game->Init(); + #endif + } +} + +/* +================= +idCommonLocal::UnloadGameDLL +================= +*/ +void idCommonLocal::UnloadGameDLL( void ) { + + // shut down the game object + if ( game != NULL ) { + game->Shutdown(); + } + +#ifdef __DOOM_DLL__ + + if ( gameDLL ) { + Sys_DLL_Unload( gameDLL ); + gameDLL = NULL; + } + game = NULL; + gameEdit = NULL; + +#endif +} + +/* +================= +idCommonLocal::IsInitialized +================= +*/ +bool idCommonLocal::IsInitialized( void ) const { + return com_fullyInitialized; +} + +/* +================= +idCommonLocal::SetMachineSpec +================= +*/ +void idCommonLocal::SetMachineSpec( void ) { + cpuid_t cpu = Sys_GetProcessorId(); + double ghz = Sys_ClockTicksPerSecond() * 0.000000001f; + int vidRam = Sys_GetVideoRam(); + int sysRam = Sys_GetSystemRam(); + bool oldCard = false; + bool nv10or20 = false; + + renderSystem->GetCardCaps( oldCard, nv10or20 ); + + Printf( "Detected\n \t%.2f GHz CPU\n\t%i MB of System memory\n\t%i MB of Video memory on %s\n\n", ghz, sysRam, vidRam, ( oldCard ) ? "a less than optimal video architecture" : "an optimal video architecture" ); + + if ( ghz >= 2.75f && vidRam >= 512 && sysRam >= 1024 && !oldCard ) { + Printf( "This system qualifies for Ultra quality!\n" ); + com_machineSpec.SetInteger( 3 ); + } else if ( ghz >= ( ( cpu & CPUID_AMD ) ? 1.9f : 2.19f ) && vidRam >= 256 && sysRam >= 512 && !oldCard ) { + Printf( "This system qualifies for High quality!\n" ); + com_machineSpec.SetInteger( 2 ); + } else if ( ghz >= ( ( cpu & CPUID_AMD ) ? 1.1f : 1.25f ) && vidRam >= 128 && sysRam >= 384 ) { + Printf( "This system qualifies for Medium quality.\n" ); + com_machineSpec.SetInteger( 1 ); + } else { + Printf( "This system qualifies for Low quality.\n" ); + com_machineSpec.SetInteger( 0 ); + } + com_videoRam.SetInteger( vidRam ); +} + +/* +================= +idCommonLocal::Init +================= +*/ +void idCommonLocal::Init( int argc, const char **argv, const char *cmdline ) { + try { + + // set interface pointers used by idLib + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = cvarSystem; + idLib::fileSystem = fileSystem; + + // initialize idLib + idLib::Init(); + + // clear warning buffer + ClearWarnings( GAME_NAME " initialization" ); + + // parse command line options + idCmdArgs args; + if ( cmdline ) { + // tokenize if the OS doesn't do it for us + args.TokenizeString( cmdline, true ); + argv = args.GetArgs( &argc ); + } + ParseCommandLine( argc, argv ); + + // init console command system + cmdSystem->Init(); + + // init CVar system + cvarSystem->Init(); + + // start file logging right away, before early console or whatever + StartupVariable( "win_outputDebugString", false ); + + // register all static CVars + idCVar::RegisterStaticVars(); + + // print engine version + Printf( "%s\n", version.string ); + + // initialize key input/binding, done early so bind command exists + idKeyInput::Init(); + + // init the console so we can take prints + console->Init(); + + // get architecture info + Sys_Init(); + + // initialize networking + Sys_InitNetworking(); + + // override cvars from command line + StartupVariable( NULL, false ); + + if ( !idAsyncNetwork::serverDedicated.GetInteger() && Sys_AlreadyRunning() ) { + Sys_Quit(); + } + + // initialize processor specific SIMD implementation + InitSIMD(); + + // init commands + InitCommands(); + +#ifdef ID_WRITE_VERSION + config_compressor = idCompressor::AllocArithmetic(); +#endif + + // game specific initialization + InitGame(); + + // don't add startup commands if no CD key is present +#if ID_ENFORCE_KEY + if ( !session->CDKeysAreValid( false ) || !AddStartupCommands() ) { +#else + if ( !AddStartupCommands() ) { +#endif + // if the user didn't give any commands, run default action + session->StartMenu( "playIntro" ); + } + + Printf( "--- Common Initialization Complete ---\n" ); + + // print all warnings queued during initialization + PrintWarnings(); + +#ifdef ID_DEDICATED + Printf( "\nType 'help' for dedicated server info.\n\n" ); +#endif + + // remove any prints from the notify lines + console->ClearNotifyLines(); + + ClearCommandLine(); + + com_fullyInitialized = true; + } + + catch( idException & ) { + Sys_Error( "Error during initialization" ); + } +} + + +/* +================= +idCommonLocal::Shutdown +================= +*/ +void idCommonLocal::Shutdown( void ) { + + com_shuttingDown = true; + + idAsyncNetwork::server.Kill(); + idAsyncNetwork::client.Shutdown(); + + // game specific shut down + ShutdownGame( false ); + + // shut down non-portable system services + Sys_Shutdown(); + + // shut down the console + console->Shutdown(); + + // shut down the key system + idKeyInput::Shutdown(); + + // shut down the cvar system + cvarSystem->Shutdown(); + + // shut down the console command system + cmdSystem->Shutdown(); + +#ifdef ID_WRITE_VERSION + delete config_compressor; + config_compressor = NULL; +#endif + + // free any buffered warning messages + ClearWarnings( GAME_NAME " shutdown" ); + warningCaption.Clear(); + errorList.Clear(); + + // free language dictionary + languageDict.Clear(); + + // enable leak test + Mem_EnableLeakTest( "doom" ); + + // shutdown idLib + idLib::ShutDown(); +} + +/* +================= +idCommonLocal::InitGame +================= +*/ +void idCommonLocal::InitGame( void ) { + // initialize the file system + fileSystem->Init(); + + // initialize the declaration manager + declManager->Init(); + + // force r_fullscreen 0 if running a tool + CheckToolMode(); + + idFile *file = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( CONFIG_SPEC, "fs_savepath" ) ); + bool sysDetect = ( file == NULL ); + if ( file ) { + fileSystem->CloseFile( file ); + } else { + file = fileSystem->OpenFileWrite( CONFIG_SPEC ); + fileSystem->CloseFile( file ); + } + + idCmdArgs args; + if ( sysDetect ) { + SetMachineSpec(); + Com_ExecMachineSpec_f( args ); + } + + // initialize the renderSystem data structures, but don't start OpenGL yet + renderSystem->Init(); + + // initialize string database right off so we can use it for loading messages + InitLanguageDict(); + + // load the font, etc + console->LoadGraphics(); + + // init journalling, etc + eventLoop->Init(); + + // exec the startup scripts + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec editor.cfg\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec default.cfg\n" ); + + // skip the config file if "safe" is on the command line + if ( !SafeMode() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec " CONFIG_FILE "\n" ); + } + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "exec autoexec.cfg\n" ); + + // reload the language dictionary now that we've loaded config files + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reloadLanguage\n" ); + + // run cfg execution + cmdSystem->ExecuteCommandBuffer(); + + // re-override anything from the config files with command line args + StartupVariable( NULL, false ); + + // if any archived cvars are modified after this, we will trigger a writing of the config file + cvarSystem->ClearModifiedFlags( CVAR_ARCHIVE ); + + // cvars are initialized, but not the rendering system. Allow preference startup dialog + Sys_DoPreferences(); + + // init the user command input code + usercmdGen->Init(); + + PrintLoadingMessage( common->GetLocalizedString( "#str_104346" ) ); + + // start the sound system, but don't do any hardware operations yet + soundSystem->Init(); + + PrintLoadingMessage( common->GetLocalizedString( "#str_104347" ) ); + + // init async network + idAsyncNetwork::Init(); + +#ifdef ID_DEDICATED + idAsyncNetwork::server.InitPort(); + cvarSystem->SetCVarBool( "s_noSound", true ); +#else + if ( idAsyncNetwork::serverDedicated.GetInteger() == 1 ) { + idAsyncNetwork::server.InitPort(); + cvarSystem->SetCVarBool( "s_noSound", true ); + } else { + // init OpenGL, which will open a window and connect sound and input hardware + PrintLoadingMessage( common->GetLocalizedString( "#str_104348" ) ); + InitRenderSystem(); + } +#endif + + PrintLoadingMessage( common->GetLocalizedString( "#str_104349" ) ); + + // initialize the user interfaces + uiManager->Init(); + + // startup the script debugger + // DebuggerServerInit(); + + PrintLoadingMessage( common->GetLocalizedString( "#str_104350" ) ); + + // load the game dll + LoadGameDLL(); + + PrintLoadingMessage( common->GetLocalizedString( "#str_104351" ) ); + + // init the session + session->Init(); + + // have to do this twice.. first one sets the correct r_mode for the renderer init + // this time around the backend is all setup correct.. a bit fugly but do not want + // to mess with all the gl init at this point.. an old vid card will never qualify for + if ( sysDetect ) { + SetMachineSpec(); + Com_ExecMachineSpec_f( args ); + cvarSystem->SetCVarInteger( "s_numberOfSpeakers", 6 ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "s_restart\n" ); + cmdSystem->ExecuteCommandBuffer(); + } +} + +/* +================= +idCommonLocal::ShutdownGame +================= +*/ +void idCommonLocal::ShutdownGame( bool reloading ) { + + // kill sound first + soundSystem->StopAllSounds( SOUNDWORLD_ANY ); + soundSystem->ClearBuffer(); + + // shutdown the script debugger + // DebuggerServerShutdown(); + + idAsyncNetwork::client.Shutdown(); + + // shut down the session + session->Shutdown(); + + // shut down the user interfaces + uiManager->Shutdown(); + + // shut down the sound system + soundSystem->Shutdown(); + + // shut down async networking + idAsyncNetwork::Shutdown(); + + // shut down the user command input code + usercmdGen->Shutdown(); + + // shut down the event loop + eventLoop->Shutdown(); + + // shut down the renderSystem + renderSystem->Shutdown(); + + // shutdown the decl manager + declManager->Shutdown(); + + // unload the game dll + UnloadGameDLL(); + + // dump warnings to "warnings.txt" +#ifdef DEBUG + DumpWarnings(); +#endif + // only shut down the log file after all output is done + CloseLogFile(); + + // shut down the file system + fileSystem->Shutdown( reloading ); +} diff --git a/src/framework/compressor.cpp b/src/framework/compressor.cpp new file mode 100644 index 0000000..44b063b --- /dev/null +++ b/src/framework/compressor.cpp @@ -0,0 +1,2585 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + + +/* +================================================================================= + + idCompressor_None + +================================================================================= +*/ + +class idCompressor_None : public idCompressor { +public: + idCompressor_None( void ); + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress( void ); + float GetCompressionRatio( void ) const; + + const char * GetName( void ); + const char * GetFullPath( void ); + int Read( void *outData, int outLength ); + int Write( const void *inData, int inLength ); + int Length( void ); + ID_TIME_T Timestamp( void ); + int Tell( void ); + void ForceFlush( void ); + void Flush( void ); + int Seek( long offset, fsOrigin_t origin ); + +protected: + idFile * file; + bool compress; +}; + +/* +================ +idCompressor_None::idCompressor_None +================ +*/ +idCompressor_None::idCompressor_None( void ) { + file = NULL; + compress = true; +} + +/* +================ +idCompressor_None::Init +================ +*/ +void idCompressor_None::Init( idFile *f, bool compress, int wordLength ) { + this->file = f; + this->compress = compress; +} + +/* +================ +idCompressor_None::FinishCompress +================ +*/ +void idCompressor_None::FinishCompress( void ) { +} + +/* +================ +idCompressor_None::GetCompressionRatio +================ +*/ +float idCompressor_None::GetCompressionRatio( void ) const { + return 0.0f; +} + +/* +================ +idCompressor_None::GetName +================ +*/ +const char *idCompressor_None::GetName( void ) { + if ( file ) { + return file->GetName(); + } else { + return ""; + } +} + +/* +================ +idCompressor_None::GetFullPath +================ +*/ +const char *idCompressor_None::GetFullPath( void ) { + if ( file ) { + return file->GetFullPath(); + } else { + return ""; + } +} + +/* +================ +idCompressor_None::Write +================ +*/ +int idCompressor_None::Write( const void *inData, int inLength ) { + if ( compress == false || inLength <= 0 ) { + return 0; + } + return file->Write( inData, inLength ); +} + +/* +================ +idCompressor_None::Read +================ +*/ +int idCompressor_None::Read( void *outData, int outLength ) { + if ( compress == true || outLength <= 0 ) { + return 0; + } + return file->Read( outData, outLength ); +} + +/* +================ +idCompressor_None::Length +================ +*/ +int idCompressor_None::Length( void ) { + if ( file ) { + return file->Length(); + } else { + return 0; + } +} + +/* +================ +idCompressor_None::Timestamp +================ +*/ +ID_TIME_T idCompressor_None::Timestamp( void ) { + if ( file ) { + return file->Timestamp(); + } else { + return 0; + } +} + +/* +================ +idCompressor_None::Tell +================ +*/ +int idCompressor_None::Tell( void ) { + if ( file ) { + return file->Tell(); + } else { + return 0; + } +} + +/* +================ +idCompressor_None::ForceFlush +================ +*/ +void idCompressor_None::ForceFlush( void ) { + if ( file ) { + file->ForceFlush(); + } +} + +/* +================ +idCompressor_None::Flush +================ +*/ +void idCompressor_None::Flush( void ) { + if ( file ) { + file->ForceFlush(); + } +} + +/* +================ +idCompressor_None::Seek +================ +*/ +int idCompressor_None::Seek( long offset, fsOrigin_t origin ) { + common->Error( "cannot seek on idCompressor" ); + return -1; +} + + +/* +================================================================================= + + idCompressor_BitStream + + Base class for bit stream compression. + +================================================================================= +*/ + +class idCompressor_BitStream : public idCompressor_None { +public: + idCompressor_BitStream( void ) {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress( void ); + float GetCompressionRatio( void ) const; + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +protected: + byte buffer[65536]; + int wordLength; + + int readTotalBytes; + int readLength; + int readByte; + int readBit; + const byte * readData; + + int writeTotalBytes; + int writeLength; + int writeByte; + int writeBit; + byte * writeData; + +protected: + void InitCompress( const void *inData, const int inLength ); + void InitDecompress( void *outData, int outLength ); + void WriteBits( int value, int numBits ); + int ReadBits( int numBits ); + void UnreadBits( int numBits ); + int Compare( const byte *src1, int bitPtr1, const byte *src2, int bitPtr2, int maxBits ) const; +}; + +/* +================ +idCompressor_BitStream::Init +================ +*/ +void idCompressor_BitStream::Init( idFile *f, bool compress, int wordLength ) { + + assert( wordLength >= 1 && wordLength <= 32 ); + + this->file = f; + this->compress = compress; + this->wordLength = wordLength; + + readTotalBytes = 0; + readLength = 0; + readByte = 0; + readBit = 0; + readData = NULL; + + writeTotalBytes = 0; + writeLength = 0; + writeByte = 0; + writeBit = 0; + writeData = NULL; +} + +/* +================ +idCompressor_BitStream::InitCompress +================ +*/ +ID_INLINE void idCompressor_BitStream::InitCompress( const void *inData, const int inLength ) { + + readLength = inLength; + readByte = 0; + readBit = 0; + readData = (const byte *) inData; + + if ( !writeLength ) { + writeLength = sizeof( buffer ); + writeByte = 0; + writeBit = 0; + writeData = buffer; + } +} + +/* +================ +idCompressor_BitStream::InitDecompress +================ +*/ +ID_INLINE void idCompressor_BitStream::InitDecompress( void *outData, int outLength ) { + + if ( !readLength ) { + readLength = file->Read( buffer, sizeof( buffer ) ); + readByte = 0; + readBit = 0; + readData = buffer; + } + + writeLength = outLength; + writeByte = 0; + writeBit = 0; + writeData = (byte *) outData; +} + +/* +================ +idCompressor_BitStream::WriteBits +================ +*/ +void idCompressor_BitStream::WriteBits( int value, int numBits ) { + int put, fraction; + + // Short circuit for writing single bytes at a time + if ( writeBit == 0 && numBits == 8 && writeByte < writeLength ) { + writeByte++; + writeTotalBytes++; + writeData[writeByte - 1] = value; + return; + } + + + while( numBits ) { + if ( writeBit == 0 ) { + if ( writeByte >= writeLength ) { + if ( writeData == buffer ) { + file->Write( buffer, writeByte ); + writeByte = 0; + } else { + put = numBits; + writeBit = put & 7; + writeByte += ( put >> 3 ) + ( writeBit != 0 ); + writeTotalBytes += ( put >> 3 ) + ( writeBit != 0 ); + return; + } + } + writeData[writeByte] = 0; + writeByte++; + writeTotalBytes++; + } + put = 8 - writeBit; + if ( put > numBits ) { + put = numBits; + } + fraction = value & ( ( 1 << put ) - 1 ); + writeData[writeByte - 1] |= fraction << writeBit; + numBits -= put; + value >>= put; + writeBit = ( writeBit + put ) & 7; + } +} + +/* +================ +idCompressor_BitStream::ReadBits +================ +*/ +int idCompressor_BitStream::ReadBits( int numBits ) { + int value, valueBits, get, fraction; + + value = 0; + valueBits = 0; + + // Short circuit for reading single bytes at a time + if ( readBit == 0 && numBits == 8 && readByte < readLength ) { + readByte++; + readTotalBytes++; + return readData[readByte - 1]; + } + + while ( valueBits < numBits ) { + if ( readBit == 0 ) { + if ( readByte >= readLength ) { + if ( readData == buffer ) { + readLength = file->Read( buffer, sizeof( buffer ) ); + readByte = 0; + } else { + get = numBits - valueBits; + readBit = get & 7; + readByte += ( get >> 3 ) + ( readBit != 0 ); + readTotalBytes += ( get >> 3 ) + ( readBit != 0 ); + return value; + } + } + readByte++; + readTotalBytes++; + } + get = 8 - readBit; + if ( get > (numBits - valueBits) ) { + get = (numBits - valueBits); + } + fraction = readData[readByte - 1]; + fraction >>= readBit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + valueBits += get; + readBit = ( readBit + get ) & 7; + } + + return value; +} + +/* +================ +idCompressor_BitStream::UnreadBits +================ +*/ +void idCompressor_BitStream::UnreadBits( int numBits ) { + readByte -= ( numBits >> 3 ); + readTotalBytes -= ( numBits >> 3 ); + if ( readBit == 0 ) { + readBit = 8 - ( numBits & 7 ); + } else { + readBit -= numBits & 7; + if ( readBit <= 0 ) { + readByte--; + readTotalBytes--; + readBit = ( readBit + 8 ) & 7; + } + } + if ( readByte < 0 ) { + readByte = 0; + readBit = 0; + } +} + +/* +================ +idCompressor_BitStream::Compare +================ +*/ +int idCompressor_BitStream::Compare( const byte *src1, int bitPtr1, const byte *src2, int bitPtr2, int maxBits ) const { + int i; + + // If the two bit pointers are aligned then we can use a faster comparison + if ( ( bitPtr1 & 7 ) == (bitPtr2 & 7 ) && maxBits > 16 ) { + const byte *p1 = &src1[bitPtr1 >> 3]; + const byte *p2 = &src2[bitPtr2 >> 3]; + + int bits = 0; + + int bitsRemain = maxBits; + + // Compare the first couple bits (if any) + if ( bitPtr1 & 7 ) { + for ( i = (bitPtr1 & 7); i < 8; i++, bits++ ) { + if ( ( ( *p1 >> i ) ^ ( *p2 >> i ) ) & 1 ) { + return bits; + } + bitsRemain--; + } + p1++; + p2++; + } + + int remain = bitsRemain >> 3; + + // Compare the middle bytes as ints + while ( remain >= 4 && (*(const int *)p1 == *(const int *)p2) ) { + p1 += 4; + p2 += 4; + remain -= 4; + bits += 32; + } + + // Compare the remaining bytes + while ( remain > 0 && (*p1 == *p2) ) { + p1++; + p2++; + remain--; + bits += 8; + } + + // Compare the last couple of bits (if any) + int finalBits = 8; + if ( remain == 0 ) { + finalBits = ( bitsRemain & 7 ); + } + for ( i = 0; i < finalBits; i++, bits++ ) { + if ( ( ( *p1 >> i ) ^ ( *p2 >> i ) ) & 1 ) { + return bits; + } + } + + assert(bits == maxBits); + return bits; + } else { + for ( i = 0; i < maxBits; i++ ) { + if ( ( ( src1[bitPtr1 >> 3] >> ( bitPtr1 & 7 ) ) ^ ( src2[bitPtr2 >> 3] >> ( bitPtr2 & 7 ) ) ) & 1 ) { + break; + } + bitPtr1++; + bitPtr2++; + } + return i; +} +} + +/* +================ +idCompressor_BitStream::Write +================ +*/ +int idCompressor_BitStream::Write( const void *inData, int inLength ) { + int i; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + for ( i = 0; i < inLength; i++ ) { + WriteBits( ReadBits( 8 ), 8 ); + } + return i; +} + +/* +================ +idCompressor_BitStream::FinishCompress +================ +*/ +void idCompressor_BitStream::FinishCompress( void ) { + if ( compress == false ) { + return; + } + + if ( writeByte ) { + file->Write( buffer, writeByte ); + } + writeLength = 0; + writeByte = 0; + writeBit = 0; +} + +/* +================ +idCompressor_BitStream::Read +================ +*/ +int idCompressor_BitStream::Read( void *outData, int outLength ) { + int i; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + for ( i = 0; i < outLength && readLength >= 0; i++ ) { + WriteBits( ReadBits( 8 ), 8 ); + } + return i; +} + +/* +================ +idCompressor_BitStream::GetCompressionRatio +================ +*/ +float idCompressor_BitStream::GetCompressionRatio( void ) const { + if ( compress ) { + return ( readTotalBytes - writeTotalBytes ) * 100.0f / readTotalBytes; + } else { + return ( writeTotalBytes - readTotalBytes ) * 100.0f / writeTotalBytes; + } +} + + +/* +================================================================================= + + idCompressor_RunLength + + The following algorithm implements run length compression with an arbitrary + word size. + +================================================================================= +*/ + +class idCompressor_RunLength : public idCompressor_BitStream { +public: + idCompressor_RunLength( void ) {} + + void Init( idFile *f, bool compress, int wordLength ); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: + int runLengthCode; +}; + +/* +================ +idCompressor_RunLength::Init +================ +*/ +void idCompressor_RunLength::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_BitStream::Init( f, compress, wordLength ); + runLengthCode = ( 1 << wordLength ) - 1; +} + +/* +================ +idCompressor_RunLength::Write +================ +*/ +int idCompressor_RunLength::Write( const void *inData, int inLength ) { + int bits, nextBits, count; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + while( readByte <= readLength ) { + count = 1; + bits = ReadBits( wordLength ); + for ( nextBits = ReadBits( wordLength ); nextBits == bits; nextBits = ReadBits( wordLength ) ) { + count++; + if ( count >= ( 1 << wordLength ) ) { + if ( count >= ( 1 << wordLength ) + 3 || bits == runLengthCode ) { + break; + } + } + } + if ( nextBits != bits ) { + UnreadBits( wordLength ); + } + if ( count > 3 || bits == runLengthCode ) { + WriteBits( runLengthCode, wordLength ); + WriteBits( bits, wordLength ); + if ( bits != runLengthCode ) { + count -= 3; + } + WriteBits( count - 1, wordLength ); + } else { + while( count-- ) { + WriteBits( bits, wordLength ); + } + } + } + + return inLength; +} + +/* +================ +idCompressor_RunLength::Read +================ +*/ +int idCompressor_RunLength::Read( void *outData, int outLength ) { + int bits, count; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + while( writeByte <= writeLength && readLength >= 0 ) { + bits = ReadBits( wordLength ); + if ( bits == runLengthCode ) { + bits = ReadBits( wordLength ); + count = ReadBits( wordLength ) + 1; + if ( bits != runLengthCode ) { + count += 3; + } + while( count-- ) { + WriteBits( bits, wordLength ); + } + } else { + WriteBits( bits, wordLength ); + } + } + + return writeByte; +} + + +/* +================================================================================= + + idCompressor_RunLength_ZeroBased + + The following algorithm implements run length compression with an arbitrary + word size for data with a lot of zero bits. + +================================================================================= +*/ + +class idCompressor_RunLength_ZeroBased : public idCompressor_BitStream { +public: + idCompressor_RunLength_ZeroBased( void ) {} + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: +}; + +/* +================ +idCompressor_RunLength_ZeroBased::Write +================ +*/ +int idCompressor_RunLength_ZeroBased::Write( const void *inData, int inLength ) { + int bits, count; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + while( readByte <= readLength ) { + count = 0; + for ( bits = ReadBits( wordLength ); bits == 0 && count < ( 1 << wordLength ); bits = ReadBits( wordLength ) ) { + count++; + } + if ( count ) { + WriteBits( 0, wordLength ); + WriteBits( count - 1, wordLength ); + UnreadBits( wordLength ); + } else { + WriteBits( bits, wordLength ); + } + } + + return inLength; +} + +/* +================ +idCompressor_RunLength_ZeroBased::Read +================ +*/ +int idCompressor_RunLength_ZeroBased::Read( void *outData, int outLength ) { + int bits, count; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + while( writeByte <= writeLength && readLength >= 0 ) { + bits = ReadBits( wordLength ); + if ( bits == 0 ) { + count = ReadBits( wordLength ) + 1; + while( count-- > 0 ) { + WriteBits( 0, wordLength ); + } + } else { + WriteBits( bits, wordLength ); + } + } + + return writeByte; +} + + +/* +================================================================================= + + idCompressor_Huffman + + The following algorithm is based on the adaptive Huffman algorithm described + in Sayood's Data Compression book. The ranks are not actually stored, but + implicitly defined by the location of a node within a doubly-linked list + +================================================================================= +*/ + +const int HMAX = 256; // Maximum symbol +const int NYT = HMAX; // NYT = Not Yet Transmitted +const int INTERNAL_NODE = HMAX + 1; // internal node + +typedef struct nodetype { + struct nodetype *left, *right, *parent; // tree structure + struct nodetype *next, *prev; // doubly-linked list + struct nodetype **head; // highest ranked node in block + int weight; + int symbol; +} huffmanNode_t; + +class idCompressor_Huffman : public idCompressor_None { +public: + idCompressor_Huffman( void ) {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress( void ); + float GetCompressionRatio( void ) const; + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: + byte seq[65536]; + int bloc; + int blocMax; + int blocIn; + int blocNode; + int blocPtrs; + + int compressedSize; + int unCompressedSize; + + huffmanNode_t * tree; + huffmanNode_t * lhead; + huffmanNode_t * ltail; + huffmanNode_t * loc[HMAX+1]; + huffmanNode_t **freelist; + + huffmanNode_t nodeList[768]; + huffmanNode_t * nodePtrs[768]; + +private: + void AddRef( byte ch ); + int Receive( huffmanNode_t *node, int *ch ); + void Transmit( int ch, byte *fout ); + void PutBit( int bit, byte *fout, int *offset ); + int GetBit( byte *fout, int *offset ); + + void Add_bit( char bit, byte *fout ); + int Get_bit(); + huffmanNode_t **Get_ppnode(); + void Free_ppnode( huffmanNode_t **ppnode ); + void Swap( huffmanNode_t *node1, huffmanNode_t *node2 ); + void Swaplist( huffmanNode_t *node1, huffmanNode_t *node2 ); + void Increment( huffmanNode_t *node ); + void Send( huffmanNode_t *node, huffmanNode_t *child, byte *fout ); +}; + +/* +================ +idCompressor_Huffman::Init +================ +*/ +void idCompressor_Huffman::Init( idFile *f, bool compress, int wordLength ) { + int i; + + this->file = f; + this->compress = compress; + bloc = 0; + blocMax = 0; + blocIn = 0; + blocNode = 0; + blocPtrs = 0; + compressedSize = 0; + unCompressedSize = 0; + + tree = NULL; + lhead = NULL; + ltail = NULL; + for( i = 0; i < (HMAX+1); i++ ) { + loc[i] = NULL; + } + freelist = NULL; + + for( i = 0; i < 768; i++ ) { + memset( &nodeList[i], 0, sizeof(huffmanNode_t) ); + nodePtrs[i] = NULL; + } + + if ( compress ) { + // Add the NYT (not yet transmitted) node into the tree/list + tree = lhead = loc[NYT] = &nodeList[blocNode++]; + tree->symbol = NYT; + tree->weight = 0; + lhead->next = lhead->prev = NULL; + tree->parent = tree->left = tree->right = NULL; + loc[NYT] = tree; + } else { + // Initialize the tree & list with the NYT node + tree = lhead = ltail = loc[NYT] = &nodeList[blocNode++]; + tree->symbol = NYT; + tree->weight = 0; + lhead->next = lhead->prev = NULL; + tree->parent = tree->left = tree->right = NULL; + } +} + +/* +================ +idCompressor_Huffman::PutBit +================ +*/ +void idCompressor_Huffman::PutBit( int bit, byte *fout, int *offset) { + bloc = *offset; + if ( (bloc&7) == 0 ) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; + *offset = bloc; +} + +/* +================ +idCompressor_Huffman::GetBit +================ +*/ +int idCompressor_Huffman::GetBit( byte *fin, int *offset) { + int t; + bloc = *offset; + t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; + bloc++; + *offset = bloc; + return t; +} + +/* +================ +idCompressor_Huffman::Add_bit + + Add a bit to the output file (buffered) +================ +*/ +void idCompressor_Huffman::Add_bit( char bit, byte *fout ) { + if ( (bloc&7) == 0 ) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; +} + +/* +================ +idCompressor_Huffman::Get_bit + + Get one bit from the input file (buffered) +================ +*/ +int idCompressor_Huffman::Get_bit() { + int t; + int wh = bloc >> 3; + int whb = wh>> 16; + if ( whb != blocIn ) { + blocMax += file->Read( seq, sizeof( seq ) ); + blocIn++; + } + wh &= 0xffff; + t = ( seq[wh] >> ( bloc & 7 ) ) & 0x1; + bloc++; + return t; +} + +/* +================ +idCompressor_Huffman::Get_ppnode +================ +*/ +huffmanNode_t **idCompressor_Huffman::Get_ppnode() { + huffmanNode_t **tppnode; + if ( !freelist ) { + return &nodePtrs[blocPtrs++]; + } else { + tppnode = freelist; + freelist = (huffmanNode_t **)*tppnode; + return tppnode; + } +} + +/* +================ +idCompressor_Huffman::Free_ppnode +================ +*/ +void idCompressor_Huffman::Free_ppnode( huffmanNode_t **ppnode ) { + *ppnode = (huffmanNode_t *)freelist; + freelist = ppnode; +} + +/* +================ +idCompressor_Huffman::Swap + + Swap the location of the given two nodes in the tree. +================ +*/ +void idCompressor_Huffman::Swap( huffmanNode_t *node1, huffmanNode_t *node2 ) { + huffmanNode_t *par1, *par2; + + par1 = node1->parent; + par2 = node2->parent; + + if ( par1 ) { + if ( par1->left == node1 ) { + par1->left = node2; + } else { + par1->right = node2; + } + } else { + tree = node2; + } + + if ( par2 ) { + if ( par2->left == node2 ) { + par2->left = node1; + } else { + par2->right = node1; + } + } else { + tree = node1; + } + + node1->parent = par2; + node2->parent = par1; +} + +/* +================ +idCompressor_Huffman::Swaplist + + Swap the given two nodes in the linked list (update ranks) +================ +*/ +void idCompressor_Huffman::Swaplist( huffmanNode_t *node1, huffmanNode_t *node2 ) { + huffmanNode_t *par1; + + par1 = node1->next; + node1->next = node2->next; + node2->next = par1; + + par1 = node1->prev; + node1->prev = node2->prev; + node2->prev = par1; + + if ( node1->next == node1 ) { + node1->next = node2; + } + if ( node2->next == node2 ) { + node2->next = node1; + } + if ( node1->next ) { + node1->next->prev = node1; + } + if ( node2->next ) { + node2->next->prev = node2; + } + if ( node1->prev ) { + node1->prev->next = node1; + } + if ( node2->prev ) { + node2->prev->next = node2; + } +} + +/* +================ +idCompressor_Huffman::Increment +================ +*/ +void idCompressor_Huffman::Increment( huffmanNode_t *node ) { + huffmanNode_t *lnode; + + if ( !node ) { + return; + } + + if ( node->next != NULL && node->next->weight == node->weight ) { + lnode = *node->head; + if ( lnode != node->parent ) { + Swap( lnode, node ); + } + Swaplist( lnode, node ); + } + if ( node->prev && node->prev->weight == node->weight ) { + *node->head = node->prev; + } else { + *node->head = NULL; + Free_ppnode( node->head ); + } + node->weight++; + if ( node->next && node->next->weight == node->weight ) { + node->head = node->next->head; + } else { + node->head = Get_ppnode(); + *node->head = node; + } + if ( node->parent ) { + Increment( node->parent ); + if ( node->prev == node->parent ) { + Swaplist( node, node->parent ); + if ( *node->head == node ) { + *node->head = node->parent; + } + } + } +} + +/* +================ +idCompressor_Huffman::AddRef +================ +*/ +void idCompressor_Huffman::AddRef( byte ch ) { + huffmanNode_t *tnode, *tnode2; + if ( loc[ch] == NULL ) { /* if this is the first transmission of this node */ + tnode = &nodeList[blocNode++]; + tnode2 = &nodeList[blocNode++]; + + tnode2->symbol = INTERNAL_NODE; + tnode2->weight = 1; + tnode2->next = lhead->next; + if ( lhead->next ) { + lhead->next->prev = tnode2; + if ( lhead->next->weight == 1 ) { + tnode2->head = lhead->next->head; + } else { + tnode2->head = Get_ppnode(); + *tnode2->head = tnode2; + } + } else { + tnode2->head = Get_ppnode(); + *tnode2->head = tnode2; + } + lhead->next = tnode2; + tnode2->prev = lhead; + + tnode->symbol = ch; + tnode->weight = 1; + tnode->next = lhead->next; + if ( lhead->next ) { + lhead->next->prev = tnode; + if ( lhead->next->weight == 1 ) { + tnode->head = lhead->next->head; + } else { + /* this should never happen */ + tnode->head = Get_ppnode(); + *tnode->head = tnode2; + } + } else { + /* this should never happen */ + tnode->head = Get_ppnode(); + *tnode->head = tnode; + } + lhead->next = tnode; + tnode->prev = lhead; + tnode->left = tnode->right = NULL; + + if ( lhead->parent ) { + if ( lhead->parent->left == lhead ) { /* lhead is guaranteed to by the NYT */ + lhead->parent->left = tnode2; + } else { + lhead->parent->right = tnode2; + } + } else { + tree = tnode2; + } + + tnode2->right = tnode; + tnode2->left = lhead; + + tnode2->parent = lhead->parent; + lhead->parent = tnode->parent = tnode2; + + loc[ch] = tnode; + + Increment( tnode2->parent ); + } else { + Increment( loc[ch] ); + } +} + +/* +================ +idCompressor_Huffman::Receive + + Get a symbol. +================ +*/ +int idCompressor_Huffman::Receive( huffmanNode_t *node, int *ch ) { + while ( node && node->symbol == INTERNAL_NODE ) { + if ( Get_bit() ) { + node = node->right; + } else { + node = node->left; + } + } + if ( !node ) { + return 0; + } + return (*ch = node->symbol); +} + +/* +================ +idCompressor_Huffman::Send + + Send the prefix code for this node. +================ +*/ +void idCompressor_Huffman::Send( huffmanNode_t *node, huffmanNode_t *child, byte *fout ) { + if ( node->parent ) { + Send( node->parent, node, fout ); + } + if ( child ) { + if ( node->right == child ) { + Add_bit( 1, fout ); + } else { + Add_bit( 0, fout ); + } + } +} + +/* +================ +idCompressor_Huffman::Transmit + + Send a symbol. +================ +*/ +void idCompressor_Huffman::Transmit( int ch, byte *fout ) { + int i; + if ( loc[ch] == NULL ) { + /* huffmanNode_t hasn't been transmitted, send a NYT, then the symbol */ + Transmit( NYT, fout ); + for ( i = 7; i >= 0; i-- ) { + Add_bit( (char)((ch >> i) & 0x1), fout ); + } + } else { + Send( loc[ch], NULL, fout ); + } +} + +/* +================ +idCompressor_Huffman::Write +================ +*/ +int idCompressor_Huffman::Write( const void *inData, int inLength ) { + int i, ch; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + for ( i = 0; i < inLength; i++ ) { + ch = ((const byte *)inData)[i]; + Transmit( ch, seq ); /* Transmit symbol */ + AddRef( (byte)ch ); /* Do update */ + int b = (bloc>>3); + if ( b > 32768 ) { + file->Write( seq, b ); + seq[0] = seq[b]; + bloc &= 7; + compressedSize += b; + } + } + + unCompressedSize += i; + return i; +} + +/* +================ +idCompressor_Huffman::FinishCompress +================ +*/ +void idCompressor_Huffman::FinishCompress( void ) { + + if ( compress == false ) { + return; + } + + bloc += 7; + int str = (bloc>>3); + if ( str ) { + file->Write( seq, str ); + compressedSize += str; + } +} + +/* +================ +idCompressor_Huffman::Read +================ +*/ +int idCompressor_Huffman::Read( void *outData, int outLength ) { + int i, j, ch; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + if ( bloc == 0 ) { + blocMax = file->Read( seq, sizeof( seq ) ); + blocIn = 0; + } + + for ( i = 0; i < outLength; i++ ) { + ch = 0; + // don't overflow reading from the file + if ( ( bloc >> 3 ) > blocMax ) { + break; + } + Receive( tree, &ch ); /* Get a character */ + if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */ + ch = 0; + for ( j = 0; j < 8; j++ ) { + ch = ( ch << 1 ) + Get_bit(); + } + } + + ((byte *)outData)[i] = ch; /* Write symbol */ + AddRef( (byte) ch ); /* Increment node */ + } + + compressedSize = bloc >> 3; + unCompressedSize += i; + return i; +} + +/* +================ +idCompressor_Huffman::GetCompressionRatio +================ +*/ +float idCompressor_Huffman::GetCompressionRatio( void ) const { + return ( unCompressedSize - compressedSize ) * 100.0f / unCompressedSize; +} + + +/* +================================================================================= + + idCompressor_Arithmetic + + The following algorithm is based on the Arithmetic Coding methods described + by Mark Nelson. The probability table is implicitly stored. + +================================================================================= +*/ + +const int AC_WORD_LENGTH = 8; +const int AC_NUM_BITS = 16; +const int AC_MSB_SHIFT = 15; +const int AC_MSB2_SHIFT = 14; +const int AC_MSB_MASK = 0x8000; +const int AC_MSB2_MASK = 0x4000; +const int AC_HIGH_INIT = 0xffff; +const int AC_LOW_INIT = 0x0000; + +class idCompressor_Arithmetic : public idCompressor_BitStream { +public: + idCompressor_Arithmetic( void ) {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress( void ); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +private: + typedef struct acProbs_s { + unsigned int low; + unsigned int high; + } acProbs_t; + + typedef struct acSymbol_s { + unsigned int low; + unsigned int high; + int position; + } acSymbol_t; + + acProbs_t probabilities[1<position; + + probabilities[ x ].high++; + + for( i = x + 1; i < (1< 0 ) { + mid = len >> 1; + if ( count >= probabilities[offset+mid].high ) { + offset += mid; + len -= mid; + res = 1; + } + else if ( count < probabilities[offset+mid].low ) { + len -= mid; + res = 0; + } else { + return offset+mid; + } + } + return offset+res; + +#else + + int j; + + for( j = 0; j < (1<= probabilities[ j ].low && count < probabilities[ j ].high ) { + return j; + } + } + + assert( false ); + + return 0; + +#endif +} + +/* +================ +idCompressor_Arithmetic::SymbolFromCount +================ +*/ +int idCompressor_Arithmetic::SymbolFromCount( unsigned int count, acSymbol_t* symbol ) { + int p = ProbabilityForCount( count ); + symbol->low = probabilities[ p ].low; + symbol->high = probabilities[ p ].high; + symbol->position = p; + return p; +} + +/* +================ +idCompressor_Arithmetic::RemoveSymbolFromStream +================ +*/ +void idCompressor_Arithmetic::RemoveSymbolFromStream( acSymbol_t* symbol ) { + long range; + + range = ( long )( high - low ) + 1; + high = low + ( unsigned short )( ( range * symbol->high ) / scale - 1 ); + low = low + ( unsigned short )( ( range * symbol->low ) / scale ); + + while( true ) { + + if ( ( high & AC_MSB_MASK ) == ( low & AC_MSB_MASK ) ) { + + } else if( ( low & AC_MSB2_MASK ) == AC_MSB2_MASK && ( high & AC_MSB2_MASK ) == 0 ) { + code ^= AC_MSB2_MASK; + low &= AC_MSB2_MASK - 1; + high |= AC_MSB2_MASK; + } else { + UpdateProbabilities( symbol ); + return; + } + + low <<= 1; + high <<= 1; + high |= 1; + code <<= 1; + code |= ReadBits( 1 ); + } +} + +/* +================ +idCompressor_Arithmetic::GetBit +================ +*/ +int idCompressor_Arithmetic::GetBit( void ) { + int getbit; + + if( symbolBit <= 0 ) { + // read a new symbol out + acSymbol_t symbol; + symbolBuffer = SymbolFromCount( GetCurrentCount(), &symbol ); + RemoveSymbolFromStream( &symbol ); + symbolBit = AC_WORD_LENGTH; + } + + getbit = ( symbolBuffer >> ( AC_WORD_LENGTH - symbolBit ) ) & 1; + symbolBit--; + + return getbit; +} + +/* +================ +idCompressor_Arithmetic::EncodeSymbol +================ +*/ +void idCompressor_Arithmetic::EncodeSymbol( acSymbol_t* symbol ) { + unsigned int range; + + // rescale high and low for the new symbol. + range = ( high - low ) + 1; + high = low + ( unsigned short )(( range * symbol->high ) / scale - 1 ); + low = low + ( unsigned short )(( range * symbol->low ) / scale ); + + while( true ) { + if ( ( high & AC_MSB_MASK ) == ( low & AC_MSB_MASK ) ) { + // the high digits of low and high have converged, and can be written to the stream + WriteBits( high >> AC_MSB_SHIFT, 1 ); + + while( underflowBits > 0 ) { + + WriteBits( ~high >> AC_MSB_SHIFT, 1 ); + + underflowBits--; + } + } else if ( ( low & AC_MSB2_MASK ) && !( high & AC_MSB2_MASK ) ) { + // underflow is in danger of happening, 2nd digits are converging but 1st digits don't match + underflowBits += 1; + low &= AC_MSB2_MASK - 1; + high |= AC_MSB2_MASK; + } else { + UpdateProbabilities( symbol ); + return; + } + + low <<= 1; + high <<= 1; + high |= 1; + } +} + +/* +================ +idCompressor_Arithmetic::CharToSymbol +================ +*/ +void idCompressor_Arithmetic::CharToSymbol( byte c, acSymbol_t* symbol ) { + symbol->low = probabilities[ c ].low; + symbol->high = probabilities[ c ].high; + symbol->position = c; +} + +/* +================ +idCompressor_Arithmetic::PutBit +================ +*/ +void idCompressor_Arithmetic::PutBit( int putbit ) { + symbolBuffer |= ( putbit & 1 ) << symbolBit; + symbolBit++; + + if( symbolBit >= AC_WORD_LENGTH ) { + acSymbol_t symbol; + + CharToSymbol( symbolBuffer, &symbol ); + EncodeSymbol( &symbol ); + + symbolBit = 0; + symbolBuffer = 0; + } +} + +/* +================ +idCompressor_Arithmetic::WriteOverflowBits +================ +*/ +void idCompressor_Arithmetic::WriteOverflowBits( void ) { + + WriteBits( low >> AC_MSB2_SHIFT, 1 ); + + underflowBits++; + while( underflowBits-- > 0 ) { + WriteBits( ~low >> AC_MSB2_SHIFT, 1 ); + } +} + +/* +================ +idCompressor_Arithmetic::Write +================ +*/ +int idCompressor_Arithmetic::Write( const void *inData, int inLength ) { + int i, j; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + InitCompress( inData, inLength ); + + for( i = 0; i < inLength; i++ ) { + if ( ( readTotalBytes & ( ( 1 << 14 ) - 1 ) ) == 0 ) { + if ( readTotalBytes ) { + WriteOverflowBits(); + WriteBits( 0, 15 ); + while( writeBit ) { + WriteBits( 0, 1 ); + } + WriteBits( 255, 8 ); + } + InitProbabilities(); + } + for ( j = 0; j < 8; j++ ) { + PutBit( ReadBits( 1 ) ); + } + } + + return inLength; +} + +/* +================ +idCompressor_Arithmetic::FinishCompress +================ +*/ +void idCompressor_Arithmetic::FinishCompress( void ) { + if ( compress == false ) { + return; + } + + WriteOverflowBits(); + + idCompressor_BitStream::FinishCompress(); +} + +/* +================ +idCompressor_Arithmetic::Read +================ +*/ +int idCompressor_Arithmetic::Read( void *outData, int outLength ) { + int i, j; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + InitDecompress( outData, outLength ); + + for( i = 0; i < outLength && readLength >= 0; i++ ) { + if ( ( writeTotalBytes & ( ( 1 << 14 ) - 1 ) ) == 0 ) { + if ( writeTotalBytes ) { + while( readBit ) { + ReadBits( 1 ); + } + while( ReadBits( 8 ) == 0 && readLength > 0 ) { + } + } + InitProbabilities(); + for ( j = 0; j < AC_NUM_BITS; j++ ) { + code <<= 1; + code |= ReadBits( 1 ); + } + } + for ( j = 0; j < 8; j++ ) { + WriteBits( GetBit(), 1 ); + } + } + + return i; +} + + +/* +================================================================================= + + idCompressor_LZSS + + In 1977 Abraham Lempel and Jacob Ziv presented a dictionary based scheme for + text compression called LZ77. For any new text LZ77 outputs an offset/length + pair to previously seen text and the next new byte after the previously seen + text. + + In 1982 James Storer and Thomas Szymanski presented a modification on the work + of Lempel and Ziv called LZSS. LZ77 always outputs an offset/length pair, even + if a match is only one byte long. An offset/length pair usually takes more than + a single byte to store and the compression is not optimal for small match sizes. + LZSS uses a bit flag which tells whether the following data is a literal (byte) + or an offset/length pair. + + The following algorithm is an implementation of LZSS with arbitrary word size. + +================================================================================= +*/ + +const int LZSS_BLOCK_SIZE = 65535; +const int LZSS_HASH_BITS = 10; +const int LZSS_HASH_SIZE = ( 1 << LZSS_HASH_BITS ); +const int LZSS_HASH_MASK = ( 1 << LZSS_HASH_BITS ) - 1; +const int LZSS_OFFSET_BITS = 11; +const int LZSS_LENGTH_BITS = 5; + +class idCompressor_LZSS : public idCompressor_BitStream { +public: + idCompressor_LZSS( void ) {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress( void ); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +protected: + int offsetBits; + int lengthBits; + int minMatchWords; + + byte block[LZSS_BLOCK_SIZE]; + int blockSize; + int blockIndex; + + int hashTable[LZSS_HASH_SIZE]; + int hashNext[LZSS_BLOCK_SIZE * 8]; + +protected: + bool FindMatch( int startWord, int startValue, int &wordOffset, int &numWords ); + void AddToHash( int index, int hash ); + int GetWordFromBlock( int wordOffset ) const; + virtual void CompressBlock( void ); + virtual void DecompressBlock( void ); +}; + +/* +================ +idCompressor_LZSS::Init +================ +*/ +void idCompressor_LZSS::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_BitStream::Init( f, compress, wordLength ); + + offsetBits = LZSS_OFFSET_BITS; + lengthBits = LZSS_LENGTH_BITS; + + minMatchWords = ( offsetBits + lengthBits + wordLength ) / wordLength; + blockSize = 0; + blockIndex = 0; +} + +/* +================ +idCompressor_LZSS::FindMatch +================ +*/ +bool idCompressor_LZSS::FindMatch( int startWord, int startValue, int &wordOffset, int &numWords ) { + int i, n, hash, bottom, maxBits; + + wordOffset = startWord; + numWords = minMatchWords - 1; + + bottom = Max( 0, startWord - ( ( 1 << offsetBits ) - 1 ) ); + maxBits = ( blockSize << 3 ) - startWord * wordLength; + + hash = startValue & LZSS_HASH_MASK; + for ( i = hashTable[hash]; i >= bottom; i = hashNext[i] ) { + n = Compare( block, i * wordLength, block, startWord * wordLength, Min( maxBits, ( startWord - i ) * wordLength ) ); + if ( n > numWords * wordLength ) { + numWords = n / wordLength; + wordOffset = i; + if ( numWords > ( ( 1 << lengthBits ) - 1 + minMatchWords ) - 1 ) { + numWords = ( ( 1 << lengthBits ) - 1 + minMatchWords ) - 1; + break; + } + } + } + + return ( numWords >= minMatchWords ); +} + +/* +================ +idCompressor_LZSS::AddToHash +================ +*/ +void idCompressor_LZSS::AddToHash( int index, int hash ) { + hashNext[index] = hashTable[hash]; + hashTable[hash] = index; +} + +/* +================ +idCompressor_LZSS::GetWordFromBlock +================ +*/ +int idCompressor_LZSS::GetWordFromBlock( int wordOffset ) const { + int blockBit, blockByte, value, valueBits, get, fraction; + + blockBit = ( wordOffset * wordLength ) & 7; + blockByte = ( wordOffset * wordLength ) >> 3; + if ( blockBit != 0 ) { + blockByte++; + } + + value = 0; + valueBits = 0; + + while ( valueBits < wordLength ) { + if ( blockBit == 0 ) { + if ( blockByte >= LZSS_BLOCK_SIZE ) { + return value; + } + blockByte++; + } + get = 8 - blockBit; + if ( get > (wordLength - valueBits) ) { + get = (wordLength - valueBits); + } + fraction = block[blockByte - 1]; + fraction >>= blockBit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + valueBits += get; + blockBit = ( blockBit + get ) & 7; + } + + return value; +} + +/* +================ +idCompressor_LZSS::CompressBlock +================ +*/ +void idCompressor_LZSS::CompressBlock( void ) { + int i, startWord, startValue, wordOffset, numWords; + + InitCompress( block, blockSize ); + + memset( hashTable, -1, sizeof( hashTable ) ); + memset( hashNext, -1, sizeof( hashNext ) ); + + startWord = 0; + while( readByte < readLength ) { + startValue = ReadBits( wordLength ); + if ( FindMatch( startWord, startValue, wordOffset, numWords ) ) { + WriteBits( 1, 1 ); + WriteBits( startWord - wordOffset, offsetBits ); + WriteBits( numWords - minMatchWords, lengthBits ); + UnreadBits( wordLength ); + for ( i = 0; i < numWords; i++ ) { + startValue = ReadBits( wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } else { + WriteBits( 0, 1 ); + WriteBits( startValue, wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } + + blockSize = 0; +} + +/* +================ +idCompressor_LZSS::DecompressBlock +================ +*/ +void idCompressor_LZSS::DecompressBlock( void ) { + int i, offset, startWord, numWords; + + InitDecompress( block, LZSS_BLOCK_SIZE ); + + startWord = 0; + while( writeByte < writeLength && readLength >= 0 ) { + if ( ReadBits( 1 ) ) { + offset = startWord - ReadBits( offsetBits ); + numWords = ReadBits( lengthBits ) + minMatchWords; + for ( i = 0; i < numWords; i++ ) { + WriteBits( GetWordFromBlock( offset + i ), wordLength ); + startWord++; + } + } else { + WriteBits( ReadBits( wordLength ), wordLength ); + startWord++; + } + } + + blockSize = Min( writeByte, LZSS_BLOCK_SIZE ); +} + +/* +================ +idCompressor_LZSS::Write +================ +*/ +int idCompressor_LZSS::Write( const void *inData, int inLength ) { + int i, n; + + if ( compress == false || inLength <= 0 ) { + return 0; + } + + for ( n = i = 0; i < inLength; i += n ) { + n = LZSS_BLOCK_SIZE - blockSize; + if ( inLength - i >= n ) { + memcpy( block + blockSize, ((const byte *)inData) + i, n ); + blockSize = LZSS_BLOCK_SIZE; + CompressBlock(); + blockSize = 0; + } else { + memcpy( block + blockSize, ((const byte *)inData) + i, inLength - i ); + n = inLength - i; + blockSize += n; + } + } + + return inLength; +} + +/* +================ +idCompressor_LZSS::FinishCompress +================ +*/ +void idCompressor_LZSS::FinishCompress( void ) { + if ( compress == false ) { + return; + } + if ( blockSize ) { + CompressBlock(); + } + idCompressor_BitStream::FinishCompress(); +} + +/* +================ +idCompressor_LZSS::Read +================ +*/ +int idCompressor_LZSS::Read( void *outData, int outLength ) { + int i, n; + + if ( compress == true || outLength <= 0 ) { + return 0; + } + + if ( !blockSize ) { + DecompressBlock(); + } + + for ( n = i = 0; i < outLength; i += n ) { + if ( !blockSize ) { + return i; + } + n = blockSize - blockIndex; + if ( outLength - i >= n ) { + memcpy( ((byte *)outData) + i, block + blockIndex, n ); + DecompressBlock(); + blockIndex = 0; + } else { + memcpy( ((byte *)outData) + i, block + blockIndex, outLength - i ); + n = outLength - i; + blockIndex += n; + } + } + + return outLength; +} + +/* +================================================================================= + + idCompressor_LZSS_WordAligned + + Outputs word aligned compressed data. + +================================================================================= +*/ + +class idCompressor_LZSS_WordAligned : public idCompressor_LZSS { +public: + idCompressor_LZSS_WordAligned( void ) {} + + void Init( idFile *f, bool compress, int wordLength ); +private: + virtual void CompressBlock( void ); + virtual void DecompressBlock( void ); +}; + +/* +================ +idCompressor_LZSS_WordAligned::Init +================ +*/ +void idCompressor_LZSS_WordAligned::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_LZSS::Init( f, compress, wordLength ); + + offsetBits = 2 * wordLength; + lengthBits = wordLength; + + minMatchWords = ( offsetBits + lengthBits + wordLength ) / wordLength; + blockSize = 0; + blockIndex = 0; +} + +/* +================ +idCompressor_LZSS_WordAligned::CompressBlock +================ +*/ +void idCompressor_LZSS_WordAligned::CompressBlock( void ) { + int i, startWord, startValue, wordOffset, numWords; + + InitCompress( block, blockSize ); + + memset( hashTable, -1, sizeof( hashTable ) ); + memset( hashNext, -1, sizeof( hashNext ) ); + + startWord = 0; + while( readByte < readLength ) { + startValue = ReadBits( wordLength ); + if ( FindMatch( startWord, startValue, wordOffset, numWords ) ) { + WriteBits( numWords - ( minMatchWords - 1 ), lengthBits ); + WriteBits( startWord - wordOffset, offsetBits ); + UnreadBits( wordLength ); + for ( i = 0; i < numWords; i++ ) { + startValue = ReadBits( wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } else { + WriteBits( 0, lengthBits ); + WriteBits( startValue, wordLength ); + AddToHash( startWord, startValue & LZSS_HASH_MASK ); + startWord++; + } + } + + blockSize = 0; +} + +/* +================ +idCompressor_LZSS_WordAligned::DecompressBlock +================ +*/ +void idCompressor_LZSS_WordAligned::DecompressBlock( void ) { + int i, offset, startWord, numWords; + + InitDecompress( block, LZSS_BLOCK_SIZE ); + + startWord = 0; + while( writeByte < writeLength && readLength >= 0 ) { + numWords = ReadBits( lengthBits ); + if ( numWords ) { + numWords += ( minMatchWords - 1 ); + offset = startWord - ReadBits( offsetBits ); + for ( i = 0; i < numWords; i++ ) { + WriteBits( GetWordFromBlock( offset + i ), wordLength ); + startWord++; + } + } else { + WriteBits( ReadBits( wordLength ), wordLength ); + startWord++; + } + } + + blockSize = Min( writeByte, LZSS_BLOCK_SIZE ); +} + +/* +================================================================================= + + idCompressor_LZW + + http://www.unisys.com/about__unisys/lzw + http://www.dogma.net/markn/articles/lzw/lzw.htm + http://www.cs.cf.ac.uk/Dave/Multimedia/node214.html + http://www.cs.duke.edu/csed/curious/compression/lzw.html + http://oldwww.rasip.fer.hr/research/compress/algorithms/fund/lz/lzw.html + + This is the same compression scheme used by GIF with the exception that + the EOI and clear codes are not explicitly stored. Instead EOI happens + when the input stream runs dry and CC happens when the table gets to big. + + This is a derivation of LZ78, but the dictionary starts with all single + character values so only code words are output. It is similar in theory + to LZ77, but instead of using the previous X bytes as a lookup table, a table + is built as the stream is read. The compressor and decompressor use the + same formula, so the tables should be exactly alike. The only catch is the + decompressor is always one step behind the compressor and may get a code not + yet in the table. In this case, it is easy to determine what the next code + is going to be (it will be the previous string plus the first byte of the + previous string). + + The dictionary can be any size, but 12 bits seems to produce best results for + most sample data. The code size is variable. It starts with the minimum + number of bits required to store the dictionary and automatically increases + as the dictionary gets bigger (it starts at 9 bits and grows to 10 bits when + item 512 is added, 11 bits when 1024 is added, etc...) once the the dictionary + is filled (4096 items for a 12 bit dictionary), the whole thing is cleared and + the process starts over again. + + The compressor increases the bit size after it adds the item, while the + decompressor does before it adds the item. The difference is subtle, but + it's because decompressor being one step behind. Otherwise, the decompressor + would read 512 with only 9 bits. + + If "Hello" is in the dictionary, then "Hell", "Hel", "He" and "H" will be too. + We use this to our advantage by storing the index of the previous code, and + the value of the last character. This means when we traverse through the + dictionary, we get the characters in reverse. + + Dictionary entries 0-255 are always going to have the values 0-255 + +================================================================================= +*/ + +class idCompressor_LZW : public idCompressor_BitStream { +public: + idCompressor_LZW( void ) {} + + void Init( idFile *f, bool compress, int wordLength ); + void FinishCompress( void ); + + int Write( const void *inData, int inLength ); + int Read( void *outData, int outLength ); + +protected: + int AddToDict( int w, int k ); + int Lookup( int w, int k ); + + bool BumpBits(); + + int WriteChain( int code ); + void DecompressBlock(); + + static const int LZW_BLOCK_SIZE = 32767; + static const int LZW_START_BITS = 9; + static const int LZW_FIRST_CODE = (1 << (LZW_START_BITS-1)); + static const int LZW_DICT_BITS = 12; + static const int LZW_DICT_SIZE = 1 << LZW_DICT_BITS; + + // Dictionary data + struct { + int k; + int w; + } dictionary[LZW_DICT_SIZE]; + idHashIndex index; + + int nextCode; + int codeBits; + + // Block data + byte block[LZW_BLOCK_SIZE]; + int blockSize; + int blockIndex; + + // Used by the compressor + int w; + + // Used by the decompressor + int oldCode; +}; + +/* +================ +idCompressor_LZW::Init +================ +*/ +void idCompressor_LZW::Init( idFile *f, bool compress, int wordLength ) { + idCompressor_BitStream::Init( f, compress, wordLength ); + + for ( int i=0; i= n ) { + memcpy( ((byte *)outData) + i, block + blockIndex, n ); + DecompressBlock(); + blockIndex = 0; + } else { + memcpy( ((byte *)outData) + i, block + blockIndex, outLength - i ); + n = outLength - i; + blockIndex += n; + } + } + + return outLength; +} + +/* +================ +idCompressor_LZW::Lookup +================ +*/ +int idCompressor_LZW::Lookup( int w, int k ) { + int j; + + if ( w == -1 ) { + return k; + } else { + for ( j = index.First( w ^ k ); j >= 0 ; j = index.Next( j ) ) { + if ( dictionary[ j ].k == k && dictionary[ j ].w == w ) { + return j; + } + } + } + + return -1; +} + +/* +================ +idCompressor_LZW::AddToDict +================ +*/ +int idCompressor_LZW::AddToDict( int w, int k ) { + dictionary[ nextCode ].k = k; + dictionary[ nextCode ].w = w; + index.Add( w ^ k, nextCode ); + return nextCode++; +} + +/* +================ +idCompressor_LZW::BumpBits + +Possibly increments codeBits +Returns true if the dictionary was cleared +================ +*/ +bool idCompressor_LZW::BumpBits() { + if ( nextCode == ( 1 << codeBits ) ) { + codeBits ++; + if ( codeBits > LZW_DICT_BITS ) { + nextCode = LZW_FIRST_CODE; + codeBits = LZW_START_BITS; + index.Clear(); + return true; + } + } + return false; +} + +/* +================ +idCompressor_LZW::FinishCompress +================ +*/ +void idCompressor_LZW::FinishCompress( void ) { + WriteBits( w, codeBits ); + idCompressor_BitStream::FinishCompress(); +} + +/* +================ +idCompressor_LZW::Write +================ +*/ +int idCompressor_LZW::Write( const void *inData, int inLength ) { + int i; + + InitCompress( inData, inLength ); + + for ( i = 0; i < inLength; i++ ) { + int k = ReadBits( 8 ); + + int code = Lookup(w, k); + if ( code >= 0 ) { + w = code; + } else { + WriteBits( w, codeBits ); + if ( !BumpBits() ) { + AddToDict( w, k ); + } + w = k; + } + } + + return inLength; +} + + +/* +================ +idCompressor_LZW::WriteChain +The chain is stored backwards, so we have to write it to a buffer then output the buffer in reverse +================ +*/ +int idCompressor_LZW::WriteChain( int code ) { + byte chain[LZW_DICT_SIZE]; + int firstChar = 0; + int i = 0; + do { + assert( i < LZW_DICT_SIZE-1 && code >= 0 ); + chain[i++] = dictionary[code].k; + code = dictionary[code].w; + } while ( code >= 0 ); + firstChar = chain[--i]; + for ( ; i >= 0; i-- ) { + WriteBits( chain[i], 8 ); + } + return firstChar; +} + +/* +================ +idCompressor_LZW::DecompressBlock +================ +*/ +void idCompressor_LZW::DecompressBlock() { + int code, firstChar; + + InitDecompress( block, LZW_BLOCK_SIZE ); + + while( writeByte < writeLength - LZW_DICT_SIZE && readLength > 0 ) { + assert( codeBits <= LZW_DICT_BITS ); + + code = ReadBits( codeBits ); + if ( readLength == 0 ) { + break; + } + + if ( oldCode == -1 ) { + assert( code < 256 ); + WriteBits( code, 8 ); + oldCode = code; + firstChar = code; + continue; + } + + if ( code >= nextCode ) { + assert( code == nextCode ); + firstChar = WriteChain( oldCode ); + WriteBits( firstChar, 8 ); + } else { + firstChar = WriteChain( code ); + } + AddToDict( oldCode, firstChar ); + if ( BumpBits() ) { + oldCode = -1; + } else { + oldCode = code; + } + } + + blockSize = Min( writeByte, LZW_BLOCK_SIZE ); +} + +/* +================================================================================= + + idCompressor + +================================================================================= +*/ + +#if defined( _WIN32 ) && defined( Q4_RECON_ENGINE_PRIVATE ) +static_assert( sizeof( idCompressor ) == 0x4, "idCompressor ABI drift" ); +static_assert( sizeof( idCompressor_None ) == 0xC, "idCompressor_None ABI drift" ); +static_assert( sizeof( idCompressor_BitStream ) == 0x10038, "idCompressor_BitStream ABI drift" ); +static_assert( sizeof( idCompressor_RunLength ) == 0x1003C, "idCompressor_RunLength ABI drift" ); +static_assert( sizeof( idCompressor_RunLength_ZeroBased ) == 0x10038, "idCompressor_RunLength_ZeroBased ABI drift" ); +static_assert( sizeof( idCompressor_Huffman ) == 0x1703C, "idCompressor_Huffman ABI drift" ); +static_assert( sizeof( idCompressor_Arithmetic ) == 0x10850, "idCompressor_Arithmetic ABI drift" ); +static_assert( sizeof( idCompressor_LZSS ) == 0x22102C, "idCompressor_LZSS ABI drift" ); +static_assert( sizeof( idCompressor_LZSS_WordAligned ) == 0x22102C, "idCompressor_LZSS_WordAligned ABI drift" ); +static_assert( sizeof( idCompressor_LZW ) == 0x2006C, "idCompressor_LZW ABI drift" ); +#endif + +/* +================ +idCompressor::AllocNoCompression +================ +*/ +idCompressor * idCompressor::AllocNoCompression( void ) { + return new idCompressor_None(); +} + +/* +================ +idCompressor::AllocBitStream +================ +*/ +idCompressor * idCompressor::AllocBitStream( void ) { + return new idCompressor_BitStream(); +} + +/* +================ +idCompressor::AllocRunLength +================ +*/ +idCompressor * idCompressor::AllocRunLength( void ) { + return new idCompressor_RunLength(); +} + +/* +================ +idCompressor::AllocRunLength_ZeroBased +================ +*/ +idCompressor * idCompressor::AllocRunLength_ZeroBased( void ) { + return new idCompressor_RunLength_ZeroBased(); +} + +/* +================ +idCompressor::AllocHuffman +================ +*/ +idCompressor * idCompressor::AllocHuffman( void ) { + return new idCompressor_Huffman(); +} + +/* +================ +idCompressor::AllocArithmetic +================ +*/ +idCompressor * idCompressor::AllocArithmetic( void ) { + return new idCompressor_Arithmetic(); +} + +/* +================ +idCompressor::AllocLZSS +================ +*/ +idCompressor * idCompressor::AllocLZSS( void ) { + return new idCompressor_LZSS(); +} + +/* +================ +idCompressor::AllocLZSS_WordAligned +================ +*/ +idCompressor * idCompressor::AllocLZSS_WordAligned( void ) { + return new idCompressor_LZSS_WordAligned(); +} + +/* +================ +idCompressor::AllocLZW +================ +*/ +idCompressor * idCompressor::AllocLZW( void ) { + return new idCompressor_LZW(); +} diff --git a/src/framework/console.cpp b/src/framework/console.cpp new file mode 100644 index 0000000..988fde6 --- /dev/null +++ b/src/framework/console.cpp @@ -0,0 +1,1188 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +void SCR_DrawTextLeftAlign( float &y, const char *text, ... ) id_attribute((format(printf,2,3))); +void SCR_DrawTextRightAlign( float &y, const char *text, ... ) id_attribute((format(printf,2,3))); + +#define LINE_WIDTH 78 +#define NUM_CON_TIMES 4 +#define CON_TEXTSIZE 0x30000 +#define TOTAL_LINES (CON_TEXTSIZE / LINE_WIDTH) +#define CONSOLE_FIRSTREPEAT 200 +#define CONSOLE_REPEAT 100 + +#define COMMAND_HISTORY 64 + +// the console will query the cvar and command systems for +// command completion information + +class idConsoleLocal : public idConsole { +public: + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void LoadGraphics( void ); + virtual bool ProcessEvent( const sysEvent_t *event, bool forceAccept ); + virtual bool Active( void ); + virtual void ClearNotifyLines( void ); + virtual void Close( void ); + virtual void Print( const char *text ); + virtual void Draw( bool forceFullScreen ); + + void Dump( const char *toFile ); + void Clear(); + + //============================ + + const idMaterial * charSetShader; + +private: + void KeyDownEvent( int key ); + + void Linefeed(); + + void PageUp(); + void PageDown(); + void Top(); + void Bottom(); + + void DrawInput(); + void DrawNotify(); + void DrawSolidConsole( float frac ); + + void Scroll(); + void SetDisplayFraction( float frac ); + void UpdateDisplayFraction( void ); + + //============================ + + bool keyCatching; + + short text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + int lastKeyEvent; // time of last key event for scroll delay + int nextKeyEvent; // keyboard repeat rate + + float displayFrac; // approaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display + int fracTime; // time of last displayFrac update + + int vislines; // in scanlines + + int times[NUM_CON_TIMES]; // cls.realtime time the line was generated + // for transparent notify lines + idVec4 color; + + idEditField historyEditLines[COMMAND_HISTORY]; + + int nextHistoryLine;// the last line in the history buffer, not masked + int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + + idEditField consoleField; + + static idCVar con_speed; + static idCVar con_notifyTime; + static idCVar con_noPrint; + + const idMaterial * whiteShader; + const idMaterial * consoleShader; +}; + +static idConsoleLocal localConsole; +idConsole *console = &localConsole; + +idCVar idConsoleLocal::con_speed( "con_speed", "3", CVAR_SYSTEM, "speed at which the console moves up and down" ); +idCVar idConsoleLocal::con_notifyTime( "con_notifyTime", "3", CVAR_SYSTEM, "time messages are displayed onscreen when console is pulled up" ); +#ifdef DEBUG +idCVar idConsoleLocal::con_noPrint( "con_noPrint", "0", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" ); +#else +idCVar idConsoleLocal::con_noPrint( "con_noPrint", "1", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "print on the console but not onscreen when console is pulled up" ); +#endif + + + +/* +============================================================================= + + Misc stats + +============================================================================= +*/ + +/* +================== +SCR_DrawTextLeftAlign +================== +*/ +void SCR_DrawTextLeftAlign( float &y, const char *text, ... ) { + char string[MAX_STRING_CHARS]; + va_list argptr; + va_start( argptr, text ); + idStr::vsnPrintf( string, sizeof( string ), text, argptr ); + va_end( argptr ); + renderSystem->DrawSmallStringExt( 0, y + 2, string, colorWhite, true, localConsole.charSetShader ); + y += SMALLCHAR_HEIGHT + 4; +} + +/* +================== +SCR_DrawTextRightAlign +================== +*/ +void SCR_DrawTextRightAlign( float &y, const char *text, ... ) { + char string[MAX_STRING_CHARS]; + va_list argptr; + va_start( argptr, text ); + int i = idStr::vsnPrintf( string, sizeof( string ), text, argptr ); + va_end( argptr ); + renderSystem->DrawSmallStringExt( 635 - i * SMALLCHAR_WIDTH, y + 2, string, colorWhite, true, localConsole.charSetShader ); + y += SMALLCHAR_HEIGHT + 4; +} + + + + +/* +================== +SCR_DrawFPS +================== +*/ +#define FPS_FRAMES 4 +float SCR_DrawFPS( float y ) { + char *s; + int w; + static int previousTimes[FPS_FRAMES]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = Sys_Milliseconds(); + frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + if ( index > FPS_FRAMES ) { + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 10000 * FPS_FRAMES / total; + fps = (fps + 5)/10; + + s = va( "%ifps", fps ); + w = strlen( s ) * BIGCHAR_WIDTH; + + renderSystem->DrawBigStringExt( 635 - w, idMath::FtoiFast( y ) + 2, s, colorWhite, true, localConsole.charSetShader); + } + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================== +SCR_DrawMemoryUsage +================== +*/ +float SCR_DrawMemoryUsage( float y ) { + memoryStats_t allocs, frees; + + Mem_GetStats( allocs ); + SCR_DrawTextRightAlign( y, "total allocated memory: %4d, %4dkB", allocs.num, allocs.totalSize>>10 ); + + Mem_GetFrameStats( allocs, frees ); + SCR_DrawTextRightAlign( y, "frame alloc: %4d, %4dkB frame free: %4d, %4dkB", allocs.num, allocs.totalSize>>10, frees.num, frees.totalSize>>10 ); + + Mem_ClearFrameStats(); + + return y; +} + +/* +================== +SCR_DrawAsyncStats +================== +*/ +float SCR_DrawAsyncStats( float y ) { + int i, outgoingRate, incomingRate; + float outgoingCompression, incomingCompression; + + if ( idAsyncNetwork::server.IsActive() ) { + + SCR_DrawTextRightAlign( y, "server delay = %d msec", idAsyncNetwork::server.GetDelay() ); + SCR_DrawTextRightAlign( y, "total outgoing rate = %d KB/s", idAsyncNetwork::server.GetOutgoingRate() >> 10 ); + SCR_DrawTextRightAlign( y, "total incoming rate = %d KB/s", idAsyncNetwork::server.GetIncomingRate() >> 10 ); + + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + + outgoingRate = idAsyncNetwork::server.GetClientOutgoingRate( i ); + incomingRate = idAsyncNetwork::server.GetClientIncomingRate( i ); + outgoingCompression = idAsyncNetwork::server.GetClientOutgoingCompression( i ); + incomingCompression = idAsyncNetwork::server.GetClientIncomingCompression( i ); + + if ( outgoingRate != -1 && incomingRate != -1 ) { + SCR_DrawTextRightAlign( y, "client %d: out rate = %d B/s (% -2.1f%%), in rate = %d B/s (% -2.1f%%)", + i, outgoingRate, outgoingCompression, incomingRate, incomingCompression ); + } + } + + idStr msg; + idAsyncNetwork::server.GetAsyncStatsAvgMsg( msg ); + SCR_DrawTextRightAlign( y, msg.c_str() ); + + } else if ( idAsyncNetwork::client.IsActive() ) { + + outgoingRate = idAsyncNetwork::client.GetOutgoingRate(); + incomingRate = idAsyncNetwork::client.GetIncomingRate(); + outgoingCompression = idAsyncNetwork::client.GetOutgoingCompression(); + incomingCompression = idAsyncNetwork::client.GetIncomingCompression(); + + if ( outgoingRate != -1 && incomingRate != -1 ) { + SCR_DrawTextRightAlign( y, "out rate = %d B/s (% -2.1f%%), in rate = %d B/s (% -2.1f%%)", + outgoingRate, outgoingCompression, incomingRate, incomingCompression ); + } + + SCR_DrawTextRightAlign( y, "packet loss = %d%%, client prediction = %d", + (int)idAsyncNetwork::client.GetIncomingPacketLoss(), idAsyncNetwork::client.GetPrediction() ); + + SCR_DrawTextRightAlign( y, "predicted frames: %d", idAsyncNetwork::client.GetPredictedFrames() ); + + } + + return y; +} + +/* +================== +SCR_DrawSoundDecoders +================== +*/ +float SCR_DrawSoundDecoders( float y ) { + int index, numActiveDecoders; + soundDecoderInfo_t decoderInfo; + + index = -1; + numActiveDecoders = 0; + while( ( index = soundSystem->GetSoundDecoderInfo( index, decoderInfo ) ) != -1 ) { + int localTime = decoderInfo.current44kHzTime - decoderInfo.start44kHzTime; + int sampleTime = decoderInfo.num44kHzSamples / decoderInfo.numChannels; + int percent; + if ( localTime > sampleTime ) { + if ( decoderInfo.looping ) { + percent = ( localTime % sampleTime ) * 100 / sampleTime; + } else { + percent = 100; + } + } else { + percent = localTime * 100 / sampleTime; + } + SCR_DrawTextLeftAlign( y, "%3d: %3d%% (%1.2f) %s: %s (%dkB)", numActiveDecoders, percent, decoderInfo.lastVolume, decoderInfo.format.c_str(), decoderInfo.name.c_str(), decoderInfo.numBytes >> 10 ); + numActiveDecoders++; + } + return y; +} + +//========================================================================= + +/* +============== +Con_Clear_f +============== +*/ +static void Con_Clear_f( const idCmdArgs &args ) { + localConsole.Clear(); +} + +/* +============== +Con_Dump_f +============== +*/ +static void Con_Dump_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Printf( "usage: conDump \n" ); + return; + } + + idStr fileName = args.Argv(1); + fileName.DefaultFileExtension(".txt"); + + common->Printf( "Dumped console text to %s.\n", fileName.c_str() ); + + localConsole.Dump( fileName.c_str() ); +} + +/* +============== +idConsoleLocal::Init +============== +*/ +void idConsoleLocal::Init( void ) { + int i; + + keyCatching = false; + + lastKeyEvent = -1; + nextKeyEvent = CONSOLE_FIRSTREPEAT; + + consoleField.Clear(); + + consoleField.SetWidthInChars( LINE_WIDTH ); + + for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) { + historyEditLines[i].Clear(); + historyEditLines[i].SetWidthInChars( LINE_WIDTH ); + } + + cmdSystem->AddCommand( "clear", Con_Clear_f, CMD_FL_SYSTEM, "clears the console" ); + cmdSystem->AddCommand( "conDump", Con_Dump_f, CMD_FL_SYSTEM, "dumps the console text to a file" ); +} + +/* +============== +idConsoleLocal::Shutdown +============== +*/ +void idConsoleLocal::Shutdown( void ) { + cmdSystem->RemoveCommand( "clear" ); + cmdSystem->RemoveCommand( "conDump" ); +} + +/* +============== +LoadGraphics + +Can't be combined with init, because init happens before +the renderSystem is initialized +============== +*/ +void idConsoleLocal::LoadGraphics() { + charSetShader = declManager->FindMaterial( "gfx/2d/bigchars" ); + whiteShader = declManager->FindMaterial( "_white" ); + consoleShader = declManager->FindMaterial( "console" ); +} + +/* +================ +idConsoleLocal::Active +================ +*/ +bool idConsoleLocal::Active( void ) { + return keyCatching; +} + +/* +================ +idConsoleLocal::ClearNotifyLines +================ +*/ +void idConsoleLocal::ClearNotifyLines() { + int i; + + for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { + times[i] = 0; + } +} + +/* +================ +idConsoleLocal::Close +================ +*/ +void idConsoleLocal::Close() { + keyCatching = false; + SetDisplayFraction( 0 ); + displayFrac = 0; // don't scroll to that point, go immediately + ClearNotifyLines(); +} + +/* +================ +idConsoleLocal::Clear +================ +*/ +void idConsoleLocal::Clear() { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + text[i] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' '; + } + + Bottom(); // go to end +} + +/* +================ +idConsoleLocal::Dump + +Save the console contents out to a file +================ +*/ +void idConsoleLocal::Dump( const char *fileName ) { + int l, x, i; + short * line; + idFile *f; + char buffer[LINE_WIDTH + 3]; + + f = fileSystem->OpenFileWrite( fileName ); + if ( !f ) { + common->Warning( "couldn't open %s", fileName ); + return; + } + + // skip empty lines + l = current - TOTAL_LINES + 1; + if ( l < 0 ) { + l = 0; + } + for ( ; l <= current ; l++ ) + { + line = text + ( l % TOTAL_LINES ) * LINE_WIDTH; + for ( x = 0; x < LINE_WIDTH; x++ ) + if ( ( line[x] & 0xff ) > ' ' ) + break; + if ( x != LINE_WIDTH ) + break; + } + + // write the remaining lines + for ( ; l <= current; l++ ) { + line = text + ( l % TOTAL_LINES ) * LINE_WIDTH; + for( i = 0; i < LINE_WIDTH; i++ ) { + buffer[i] = line[i] & 0xff; + } + for ( x = LINE_WIDTH-1; x >= 0; x-- ) { + if ( buffer[x] <= ' ' ) { + buffer[x] = 0; + } else { + break; + } + } + buffer[x+1] = '\r'; + buffer[x+2] = '\n'; + buffer[x+3] = 0; + f->Write( buffer, strlen( buffer ) ); + } + + fileSystem->CloseFile( f ); +} + +/* +================ +idConsoleLocal::PageUp +================ +*/ +void idConsoleLocal::PageUp( void ) { + display -= 2; + if ( current - display >= TOTAL_LINES ) { + display = current - TOTAL_LINES + 1; + } +} + +/* +================ +idConsoleLocal::PageDown +================ +*/ +void idConsoleLocal::PageDown( void ) { + display += 2; + if ( display > current ) { + display = current; + } +} + +/* +================ +idConsoleLocal::Top +================ +*/ +void idConsoleLocal::Top( void ) { + display = 0; +} + +/* +================ +idConsoleLocal::Bottom +================ +*/ +void idConsoleLocal::Bottom( void ) { + display = current; +} + + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +/* +==================== +KeyDownEvent + +Handles history and console scrollback +==================== +*/ +void idConsoleLocal::KeyDownEvent( int key ) { + + // Execute F key bindings + if ( key >= K_F1 && key <= K_F12 ) { + idKeyInput::ExecKeyBinding( key ); + return; + } + + // ctrl-L clears screen + if ( key == 'l' && idKeyInput::IsDown( K_CTRL ) ) { + Clear(); + return; + } + + // enter finishes the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + + common->Printf ( "]%s\n", consoleField.GetBuffer() ); + + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, consoleField.GetBuffer() ); // valid command + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" ); + + // copy line to history buffer + historyEditLines[nextHistoryLine % COMMAND_HISTORY] = consoleField; + nextHistoryLine++; + historyLine = nextHistoryLine; + + consoleField.Clear(); + consoleField.SetWidthInChars( LINE_WIDTH ); + + session->UpdateScreen();// force an update, because the command + // may take some time + return; + } + + // command completion + + if ( key == K_TAB ) { + consoleField.AutoComplete(); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + if ( ( key == K_UPARROW ) || + ( ( tolower(key) == 'p' ) && idKeyInput::IsDown( K_CTRL ) ) ) { + if ( nextHistoryLine - historyLine < COMMAND_HISTORY && historyLine > 0 ) { + historyLine--; + } + consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + if ( ( key == K_DOWNARROW ) || + ( ( tolower( key ) == 'n' ) && idKeyInput::IsDown( K_CTRL ) ) ) { + if ( historyLine == nextHistoryLine ) { + return; + } + historyLine++; + consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + // console scrolling + if ( key == K_PGUP ) { + PageUp(); + lastKeyEvent = eventLoop->Milliseconds(); + nextKeyEvent = CONSOLE_FIRSTREPEAT; + return; + } + + if ( key == K_PGDN ) { + PageDown(); + lastKeyEvent = eventLoop->Milliseconds(); + nextKeyEvent = CONSOLE_FIRSTREPEAT; + return; + } + + if ( key == K_MWHEELUP ) { + PageUp(); + return; + } + + if ( key == K_MWHEELDOWN ) { + PageDown(); + return; + } + + // ctrl-home = top of console + if ( key == K_HOME && idKeyInput::IsDown( K_CTRL ) ) { + Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == K_END && idKeyInput::IsDown( K_CTRL ) ) { + Bottom(); + return; + } + + // pass to the normal editline routine + consoleField.KeyDownEvent( key ); +} + +/* +============== +Scroll +deals with scrolling text because we don't have key repeat +============== +*/ +void idConsoleLocal::Scroll( ) { + if (lastKeyEvent == -1 || (lastKeyEvent+200) > eventLoop->Milliseconds()) { + return; + } + // console scrolling + if ( idKeyInput::IsDown( K_PGUP ) ) { + PageUp(); + nextKeyEvent = CONSOLE_REPEAT; + return; + } + + if ( idKeyInput::IsDown( K_PGDN ) ) { + PageDown(); + nextKeyEvent = CONSOLE_REPEAT; + return; + } +} + +/* +============== +SetDisplayFraction + +Causes the console to start opening the desired amount. +============== +*/ +void idConsoleLocal::SetDisplayFraction( float frac ) { + finalFrac = frac; + fracTime = com_frameTime; +} + +/* +============== +UpdateDisplayFraction + +Scrolls the console up or down based on conspeed +============== +*/ +void idConsoleLocal::UpdateDisplayFraction( void ) { + if ( con_speed.GetFloat() <= 0.1f ) { + fracTime = com_frameTime; + displayFrac = finalFrac; + return; + } + + // scroll towards the destination height + if ( finalFrac < displayFrac ) { + displayFrac -= con_speed.GetFloat() * ( com_frameTime - fracTime ) * 0.001f; + if ( finalFrac > displayFrac ) { + displayFrac = finalFrac; + } + fracTime = com_frameTime; + } else if ( finalFrac > displayFrac ) { + displayFrac += con_speed.GetFloat() * ( com_frameTime - fracTime ) * 0.001f; + if ( finalFrac < displayFrac ) { + displayFrac = finalFrac; + } + fracTime = com_frameTime; + } +} + +/* +============== +ProcessEvent +============== +*/ +bool idConsoleLocal::ProcessEvent( const sysEvent_t *event, bool forceAccept ) { + bool consoleKey; + consoleKey = event->evType == SE_KEY && ( event->evValue == Sys_GetConsoleKey( false ) || event->evValue == Sys_GetConsoleKey( true ) ); + +#ifdef ID_CONSOLE_LOCK + // If the console's not already down, and we have it turned off, check for ctrl+alt + if ( !keyCatching && !com_allowConsole.GetBool() ) { + if ( !idKeyInput::IsDown( K_CTRL ) || !idKeyInput::IsDown( K_ALT ) ) { + consoleKey = false; + } + } +#endif + + // we always catch the console key event + if ( !forceAccept && consoleKey ) { + // ignore up events + if ( event->evValue2 == 0 ) { + return true; + } + + consoleField.ClearAutoComplete(); + + // a down event will toggle the destination lines + if ( keyCatching ) { + Close(); + Sys_GrabMouseCursor( true ); + cvarSystem->SetCVarBool( "ui_chat", false ); + } else { + consoleField.Clear(); + keyCatching = true; + if ( idKeyInput::IsDown( K_SHIFT ) ) { + // if the shift key is down, don't open the console as much + SetDisplayFraction( 0.2f ); + } else { + SetDisplayFraction( 0.5f ); + } + cvarSystem->SetCVarBool( "ui_chat", true ); + } + return true; + } + + // if we aren't key catching, dump all the other events + if ( !forceAccept && !keyCatching ) { + return false; + } + + // handle key and character events + if ( event->evType == SE_CHAR ) { + // never send the console key as a character + if ( event->evValue != Sys_GetConsoleKey( false ) && event->evValue != Sys_GetConsoleKey( true ) ) { + consoleField.CharEvent( event->evValue ); + } + return true; + } + + if ( event->evType == SE_KEY ) { + // ignore up key events + if ( event->evValue2 == 0 ) { + return true; + } + + KeyDownEvent( event->evValue ); + return true; + } + + // we don't handle things like mouse, joystick, and network packets + return false; +} + +/* +============================================================================== + +PRINTING + +============================================================================== +*/ + +/* +=============== +Linefeed +=============== +*/ +void idConsoleLocal::Linefeed() { + int i; + + // mark time for transparent overlay + if ( current >= 0 ) { + times[current % NUM_CON_TIMES] = com_frameTime; + } + + x = 0; + if ( display == current ) { + display++; + } + current++; + for ( i = 0; i < LINE_WIDTH; i++ ) { + text[(current%TOTAL_LINES)*LINE_WIDTH+i] = (idStr::ColorIndex(C_COLOR_CYAN)<<8) | ' '; + } +} + + +/* +================ +Print + +Handles cursor positioning, line wrapping, etc +================ +*/ +void idConsoleLocal::Print( const char *txt ) { + int y; + int c, l; + int color; + +#ifdef ID_ALLOW_TOOLS + RadiantPrint( txt ); + + if( com_editors & EDITOR_MATERIAL ) { + MaterialEditorPrintConsole(txt); + } +#endif + + color = idStr::ColorIndex( C_COLOR_CYAN ); + + while ( (c = *(const unsigned char*)txt) != 0 ) { + int escapeType; + const int escapeLength = idStr::IsEscape( txt, &escapeType ); + if ( escapeLength ) { + if ( !( escapeType & S_ESCAPE_COLORINDEX ) ) { + txt += escapeLength; + continue; + } + if ( *( txt + 1 ) == C_COLOR_DEFAULT ) { + color = idStr::ColorIndex( C_COLOR_CYAN ); + } else { + color = idStr::ColorIndex( *( txt + 1 ) ); + } + txt += escapeLength; + continue; + } + + y = current % TOTAL_LINES; + + // if we are about to print a new word, check to see + // if we should wrap to the new line + if ( c > ' ' && ( x == 0 || text[y*LINE_WIDTH+x-1] <= ' ' ) ) { + // count word length + for (l=0 ; l< LINE_WIDTH ; l++) { + if ( txt[l] <= ' ') { + break; + } + } + + // word wrap + if (l != LINE_WIDTH && (x + l >= LINE_WIDTH) ) { + Linefeed(); + } + } + + txt++; + + switch( c ) { + case '\n': + Linefeed (); + break; + case '\t': + do { + text[y*LINE_WIDTH+x] = (color << 8) | ' '; + x++; + if ( x >= LINE_WIDTH ) { + Linefeed(); + x = 0; + } + } while ( x & 3 ); + break; + case '\r': + x = 0; + break; + default: // display character and advance + text[y*LINE_WIDTH+x] = (color << 8) | c; + x++; + if ( x >= LINE_WIDTH ) { + Linefeed(); + x = 0; + } + break; + } + } + + + // mark time for transparent overlay + if ( current >= 0 ) { + times[current % NUM_CON_TIMES] = com_frameTime; + } +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +DrawInput + +Draw the editline after a ] prompt +================ +*/ +void idConsoleLocal::DrawInput() { + int y, autoCompleteLength; + + y = vislines - ( SMALLCHAR_HEIGHT * 2 ); + + if ( consoleField.GetAutoCompleteLength() != 0 ) { + autoCompleteLength = strlen( consoleField.GetBuffer() ) - consoleField.GetAutoCompleteLength(); + + if ( autoCompleteLength > 0 ) { + renderSystem->SetColor4( .8f, .2f, .2f, .45f ); + + renderSystem->DrawStretchPic( 2 * SMALLCHAR_WIDTH + consoleField.GetAutoCompleteLength() * SMALLCHAR_WIDTH, + y + 2, autoCompleteLength * SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT - 2, 0, 0, 0, 0, whiteShader ); + + } + } + + renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); + + renderSystem->DrawSmallChar( 1 * SMALLCHAR_WIDTH, y, ']', localConsole.charSetShader ); + + consoleField.Draw(2 * SMALLCHAR_WIDTH, y, SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, true, charSetShader ); +} + + +/* +================ +DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void idConsoleLocal::DrawNotify() { + int x, v; + short *text_p; + int i; + int time; + int currentColor; + + if ( con_noPrint.GetBool() ) { + return; + } + + currentColor = idStr::ColorIndex( C_COLOR_WHITE ); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + + v = 0; + for ( i = current-NUM_CON_TIMES+1; i <= current; i++ ) { + if ( i < 0 ) { + continue; + } + time = times[i % NUM_CON_TIMES]; + if ( time == 0 ) { + continue; + } + time = com_frameTime - time; + if ( time > con_notifyTime.GetFloat() * 1000 ) { + continue; + } + text_p = text + (i % TOTAL_LINES)*LINE_WIDTH; + + for ( x = 0; x < LINE_WIDTH; x++ ) { + if ( ( text_p[x] & 0xff ) == ' ' ) { + continue; + } + if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) { + currentColor = idStr::ColorIndex(text_p[x]>>8); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + } + renderSystem->DrawSmallChar( (x+1)*SMALLCHAR_WIDTH, v, text_p[x] & 0xff, localConsole.charSetShader ); + } + + v += SMALLCHAR_HEIGHT; + } + + renderSystem->SetColor( colorCyan ); +} + +/* +================ +DrawSolidConsole + +Draws the console with the solid background +================ +*/ +void idConsoleLocal::DrawSolidConsole( float frac ) { + int i, x; + float y; + int rows; + short *text_p; + int row; + int lines; + int currentColor; + + lines = idMath::FtoiFast( SCREEN_HEIGHT * frac ); + if ( lines <= 0 ) { + return; + } + + if ( lines > SCREEN_HEIGHT ) { + lines = SCREEN_HEIGHT; + } + + // draw the background + y = frac * SCREEN_HEIGHT - 2; + if ( y < 1.0f ) { + y = 0.0f; + } else { + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, y, 0, 1.0f - displayFrac, 1, 1, consoleShader ); + } + + renderSystem->SetColor( colorCyan ); + renderSystem->DrawStretchPic( 0, y, SCREEN_WIDTH, 2, 0, 0, 0, 0, whiteShader ); + renderSystem->SetColor( colorWhite ); + + // draw the version number + + renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); + + idStr version = va( "%s %s %s V%s Build %u", "GSS_TEXT_NAME", "Quake4", "Release", "0.13.0.7", 1834u ); + i = version.Length(); + + for ( x = 0; x < i; x++ ) { + renderSystem->DrawSmallChar( SCREEN_WIDTH - ( i - x ) * SMALLCHAR_WIDTH, + (lines-(SMALLCHAR_HEIGHT+SMALLCHAR_HEIGHT/2)), version[x], localConsole.charSetShader ); + + } + + + // draw the text + vislines = lines; + rows = (lines-SMALLCHAR_WIDTH)/SMALLCHAR_WIDTH; // rows of text to draw + + y = lines - (SMALLCHAR_HEIGHT*3); + + // draw from the bottom up + if ( display != current ) { + // draw arrows to show the buffer is backscrolled + renderSystem->SetColor( idStr::ColorForIndex( C_COLOR_CYAN ) ); + for ( x = 0; x < LINE_WIDTH; x += 4 ) { + renderSystem->DrawSmallChar( (x+1)*SMALLCHAR_WIDTH, idMath::FtoiFast( y ), '^', localConsole.charSetShader ); + } + y -= SMALLCHAR_HEIGHT; + rows--; + } + + row = display; + + if ( x == 0 ) { + row--; + } + + currentColor = idStr::ColorIndex( C_COLOR_WHITE ); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + + for ( i = 0; i < rows; i++, y -= SMALLCHAR_HEIGHT, row-- ) { + if ( row < 0 ) { + break; + } + if ( current - row >= TOTAL_LINES ) { + // past scrollback wrap point + continue; + } + + text_p = text + (row % TOTAL_LINES)*LINE_WIDTH; + + for ( x = 0; x < LINE_WIDTH; x++ ) { + if ( ( text_p[x] & 0xff ) == ' ' ) { + continue; + } + + if ( idStr::ColorIndex(text_p[x]>>8) != currentColor ) { + currentColor = idStr::ColorIndex(text_p[x]>>8); + renderSystem->SetColor( idStr::ColorForIndex( currentColor ) ); + } + renderSystem->DrawSmallChar( (x+1)*SMALLCHAR_WIDTH, idMath::FtoiFast( y ), text_p[x] & 0xff, localConsole.charSetShader ); + } + } + + // draw the input prompt, user text, and cursor if desired + DrawInput(); + + renderSystem->SetColor( colorCyan ); +} + + +/* +============== +Draw + +ForceFullScreen is used by the editor +============== +*/ +void idConsoleLocal::Draw( bool forceFullScreen ) { + float y = 0.0f; + + if ( !charSetShader ) { + return; + } + + if ( forceFullScreen ) { + // if we are forced full screen because of a disconnect, + // we want the console closed when we go back to a session state + Close(); + // we are however catching keyboard input + keyCatching = true; + } + + Scroll(); + + UpdateDisplayFraction(); + + if ( forceFullScreen ) { + DrawSolidConsole( 1.0f ); + } else if ( displayFrac ) { + DrawSolidConsole( displayFrac ); + } else { + // only draw the notify lines if the developer cvar is set, + // or we are a debug build + if ( !con_noPrint.GetBool() ) { + DrawNotify(); + } + } + + if ( com_showFPS.GetBool() ) { + y = SCR_DrawFPS( 0 ); + } + + if ( com_showMemoryUsage.GetBool() ) { + y = SCR_DrawMemoryUsage( y ); + } + + if ( com_showAsyncStats.GetBool() ) { + y = SCR_DrawAsyncStats( y ); + } + + if ( com_showSoundDecoders.GetBool() ) { + y = SCR_DrawSoundDecoders( y ); + } +} diff --git a/src/framework/cvarsystem.cpp b/src/framework/cvarsystem.cpp new file mode 100644 index 0000000..6b39f44 --- /dev/null +++ b/src/framework/cvarsystem.cpp @@ -0,0 +1,1413 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar * idCVar::staticVars = NULL; + +/* +=============================================================================== + + idInternalCVar + +=============================================================================== +*/ + +class idInternalCVar : public idCVar { + friend class idCVarSystemLocal; +public: + idInternalCVar( void ); + idInternalCVar( const char *newName, const char *newValue, int newFlags ); + idInternalCVar( const idCVar *cvar ); + virtual ~idInternalCVar( void ); + + const char ** CopyValueStrings( const char **strings ); + void Update( const idCVar *cvar ); + void UpdateValue( void ); + void UpdateCheat( void ); + void Set( const char *newValue, bool force, bool fromServer ); + void Reset( void ); + +private: + idStr nameString; // name + idStr resetString; // resetting will change to this value + idStr valueString; // value + idStr descriptionString; // description + + virtual void InternalSetString( const char *newValue ); + virtual void InternalServerSetString( const char *newValue ); + virtual void InternalSetBool( const bool newValue ); + virtual void InternalSetInteger( const int newValue ); + virtual void InternalSetFloat( const float newValue ); +}; + +/* +============ +idInternalCVar::idInternalCVar +============ +*/ +idInternalCVar::idInternalCVar( void ) { +} + +/* +============ +idInternalCVar::idInternalCVar +============ +*/ +idInternalCVar::idInternalCVar( const char *newName, const char *newValue, int newFlags ) { + nameString = newName; + name = nameString.c_str(); + valueString = newValue; + value = valueString.c_str(); + resetString = newValue; + descriptionString = ""; + description = descriptionString.c_str(); + flags = ( newFlags & ~CVAR_STATIC ) | CVAR_MODIFIED; + valueMin = 1; + valueMax = -1; + valueStrings = NULL; + valueCompletion = 0; + UpdateValue(); + UpdateCheat(); + internalVar = this; +} + +/* +============ +idInternalCVar::idInternalCVar +============ +*/ +idInternalCVar::idInternalCVar( const idCVar *cvar ) { + nameString = cvar->GetName(); + name = nameString.c_str(); + valueString = cvar->GetString(); + value = valueString.c_str(); + resetString = cvar->GetString(); + descriptionString = cvar->GetDescription(); + description = descriptionString.c_str(); + flags = cvar->GetFlags() | CVAR_MODIFIED; + valueMin = cvar->GetMinValue(); + valueMax = cvar->GetMaxValue(); + valueStrings = CopyValueStrings( cvar->GetValueStrings() ); + valueCompletion = cvar->GetValueCompletion(); + UpdateValue(); + UpdateCheat(); + internalVar = this; +} + +/* +============ +idInternalCVar::~idInternalCVar +============ +*/ +idInternalCVar::~idInternalCVar( void ) { + Mem_Free( valueStrings ); + valueStrings = NULL; +} + + +/* +============ +idInternalCVar::CopyValueStrings +============ +*/ +const char **idInternalCVar::CopyValueStrings( const char **strings ) { + int i, totalLength; + const char **ptr; + char *str; + + if ( !strings ) { + return NULL; + } + + totalLength = 0; + for ( i = 0; strings[i] != NULL; i++ ) { + totalLength += idStr::Length( strings[i] ) + 1; + } + + ptr = (const char **) Mem_Alloc( ( i + 1 ) * sizeof( char * ) + totalLength ); + str = (char *) (((byte *)ptr) + ( i + 1 ) * sizeof( char * ) ); + + for ( i = 0; strings[i] != NULL; i++ ) { + ptr[i] = str; + strcpy( str, strings[i] ); + str += idStr::Length( strings[i] ) + 1; + } + ptr[i] = NULL; + + return ptr; +} + +/* +============ +idInternalCVar::Update +============ +*/ +void idInternalCVar::Update( const idCVar *cvar ) { + + // if this is a statically declared variable + if ( cvar->GetFlags() & CVAR_STATIC ) { + + if ( flags & CVAR_STATIC ) { + + // the code has more than one static declaration of the same variable, make sure they have the same properties + if ( resetString.Icmp( cvar->GetString() ) != 0 ) { + common->Warning( "CVar '%s' declared multiple times with different initial value", nameString.c_str() ); + } + if ( ( flags & (CVAR_BOOL|CVAR_INTEGER|CVAR_FLOAT) ) != ( cvar->GetFlags() & (CVAR_BOOL|CVAR_INTEGER|CVAR_FLOAT) ) ) { + common->Warning( "CVar '%s' declared multiple times with different type", nameString.c_str() ); + } + if ( valueMin != cvar->GetMinValue() || valueMax != cvar->GetMaxValue() ) { + common->Warning( "CVar '%s' declared multiple times with different minimum/maximum", nameString.c_str() ); + } + + } + + // the code is now specifying a variable that the user already set a value for, take the new value as the reset value + resetString = cvar->GetString(); + descriptionString = cvar->GetDescription(); + description = descriptionString.c_str(); + valueMin = cvar->GetMinValue(); + valueMax = cvar->GetMaxValue(); + Mem_Free( valueStrings ); + valueStrings = CopyValueStrings( cvar->GetValueStrings() ); + valueCompletion = cvar->GetValueCompletion(); + UpdateValue(); + cvarSystem->SetModifiedFlags( cvar->GetFlags() ); + } + + flags |= cvar->GetFlags(); + + UpdateCheat(); + + // only allow one non-empty reset string without a warning + if ( resetString.Length() == 0 ) { + resetString = cvar->GetString(); + } else if ( cvar->GetString()[0] && resetString.Cmp( cvar->GetString() ) != 0 ) { + common->Warning( "cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", nameString.c_str(), resetString.c_str(), cvar->GetString() ); + } +} + +/* +============ +idInternalCVar::UpdateValue +============ +*/ +void idInternalCVar::UpdateValue( void ) { + bool clamped = false; + + if ( flags & CVAR_BOOL ) { + integerValue = ( atoi( value ) != 0 ); + floatValue = integerValue; + if ( idStr::Icmp( value, "0" ) != 0 && idStr::Icmp( value, "1" ) != 0 ) { + valueString = idStr( (bool)( integerValue != 0 ) ); + value = valueString.c_str(); + } + } else if ( flags & CVAR_INTEGER ) { + integerValue = (int)atoi( value ); + if ( valueMin < valueMax ) { + if ( integerValue < valueMin ) { + integerValue = (int)valueMin; + clamped = true; + } else if ( integerValue > valueMax ) { + integerValue = (int)valueMax; + clamped = true; + } + } + if ( clamped || !idStr::IsNumeric( value ) || idStr::FindChar( value, '.' ) ) { + valueString = idStr( integerValue ); + value = valueString.c_str(); + } + floatValue = (float)integerValue; + } else if ( flags & CVAR_FLOAT ) { + floatValue = (float)atof( value ); + if ( valueMin < valueMax ) { + if ( floatValue < valueMin ) { + floatValue = valueMin; + clamped = true; + } else if ( floatValue > valueMax ) { + floatValue = valueMax; + clamped = true; + } + } + if ( clamped || !idStr::IsNumeric( value ) ) { + valueString = idStr( floatValue ); + value = valueString.c_str(); + } + integerValue = (int)floatValue; + } else { + if ( valueStrings && valueStrings[0] ) { + integerValue = 0; + for ( int i = 0; valueStrings[i]; i++ ) { + if ( valueString.Icmp( valueStrings[i] ) == 0 ) { + integerValue = i; + break; + } + } + valueString = valueStrings[integerValue]; + value = valueString.c_str(); + floatValue = (float)integerValue; + } else if ( valueString.Length() < 32 ) { + floatValue = (float)atof( value ); + integerValue = (int)floatValue; + } else { + floatValue = 0.0f; + integerValue = 0; + } + } +} + +/* +============ +idInternalCVar::UpdateCheat +============ +*/ +void idInternalCVar::UpdateCheat( void ) { + // all variables are considered cheats except for a few types + if ( flags & ( CVAR_NOCHEAT | CVAR_INIT | CVAR_ROM | CVAR_ARCHIVE | CVAR_USERINFO | CVAR_SERVERINFO | CVAR_NETWORKSYNC ) ) { + flags &= ~CVAR_CHEAT; + } else { + flags |= CVAR_CHEAT; + } +} + +/* +============ +idInternalCVar::Set +============ +*/ +void idInternalCVar::Set( const char *newValue, bool force, bool fromServer ) { + if ( session && session->IsMultiplayer() && !fromServer ) { +#ifndef ID_TYPEINFO + if ( ( flags & CVAR_NETWORKSYNC ) && idAsyncNetwork::client.IsActive() ) { + common->Printf( "%s is a synced over the network and cannot be changed on a multiplayer client.\n", nameString.c_str() ); +#if ID_ALLOW_CHEATS + common->Printf( "ID_ALLOW_CHEATS override!\n" ); +#else + return; +#endif + } +#endif + if ( ( flags & CVAR_CHEAT ) && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { + common->Printf( "%s cannot be changed in multiplayer.\n", nameString.c_str() ); +#if ID_ALLOW_CHEATS + common->Printf( "ID_ALLOW_CHEATS override!\n" ); +#else + return; +#endif + } + } + + if ( !newValue ) { + newValue = resetString.c_str(); + } + + if ( !force ) { + if ( flags & CVAR_ROM ) { + common->Printf( "%s is read only.\n", nameString.c_str() ); + return; + } + + if ( flags & CVAR_INIT ) { + common->Printf( "%s is write protected.\n", nameString.c_str() ); + return; + } + } + + if ( valueString.Icmp( newValue ) == 0 ) { + return; + } + + valueString = newValue; + value = valueString.c_str(); + UpdateValue(); + + SetModified(); + cvarSystem->SetModifiedFlags( flags ); +} + +/* +============ +idInternalCVar::Reset +============ +*/ +void idInternalCVar::Reset( void ) { + valueString = resetString; + value = valueString.c_str(); + UpdateValue(); +} + +/* +============ +idInternalCVar::InternalSetString +============ +*/ +void idInternalCVar::InternalSetString( const char *newValue ) { + Set( newValue, true, false ); +} + +/* +=============== +idInternalCVar::InternalServerSetString +=============== +*/ +void idInternalCVar::InternalServerSetString( const char *newValue ) { + Set( newValue, true, true ); +} + +/* +============ +idInternalCVar::InternalSetBool +============ +*/ +void idInternalCVar::InternalSetBool( const bool newValue ) { + Set( idStr( newValue ), true, false ); +} + +/* +============ +idInternalCVar::InternalSetInteger +============ +*/ +void idInternalCVar::InternalSetInteger( const int newValue ) { + Set( idStr( newValue ), true, false ); +} + +/* +============ +idInternalCVar::InternalSetFloat +============ +*/ +void idInternalCVar::InternalSetFloat( const float newValue ) { + Set( idStr( newValue ), true, false ); +} + + +/* +=============================================================================== + + idCVarSystemLocal + +=============================================================================== +*/ + +class idCVarSystemLocal : public idCVarSystem { +public: + idCVarSystemLocal( void ); + + virtual ~idCVarSystemLocal( void ) {} + + virtual void Init( void ); + virtual void Shutdown( void ); + virtual bool IsInitialized( void ) const; + + virtual void Register( idCVar *cvar ); + virtual void Register( const idCVarHelp *cvarHelp ); + virtual idCVarHelp * GetHelps( cvarHelpCategory_t category ); + + virtual idCVar * Find( const char *name ); + + virtual void SetCVarString( const char *name, const char *value, int flags = 0 ); + virtual void SetCVarBool( const char *name, const bool value, int flags = 0 ); + virtual void SetCVarInteger( const char *name, const int value, int flags = 0 ); + virtual void SetCVarFloat( const char *name, const float value, int flags = 0 ); + + virtual const char * GetCVarString( const char *name ) const; + virtual bool GetCVarBool( const char *name ) const; + virtual int GetCVarInteger( const char *name ) const; + virtual float GetCVarFloat( const char *name ) const; + + virtual bool Command( const idCmdArgs &args ); + + virtual void CommandCompletion( void(*callback)( const char *s ) ); + virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ); + + virtual void SetModifiedFlags( int flags ); + virtual int GetModifiedFlags( void ) const; + virtual void ClearModifiedFlags( int flags ); + + virtual void ResetFlaggedVariables( int flags ); + virtual void RemoveFlaggedAutoCompletion( int flags ); + virtual void WriteFlaggedVariables( int flags, const char *setCmd, idFile *f ) const; + virtual unsigned int WriteFlaggedVariables( int flags, const char *setCmd, byte *buf, unsigned int bufSize ) const; + virtual void ApplyFlaggedVariables( byte *buf, unsigned int bufSize ); + virtual idStr WriteFlaggedVariables( int flags ) const; + + virtual const idDict * MoveCVarsToDict( int flags ) const; + virtual void SetCVarsFromDict( const idDict &dict ); + + void RegisterInternal( idCVar *cvar ); + idInternalCVar * FindInternal( const char *name ) const; + void SetInternal( const char *name, const char *value, int flags ); + +private: + bool initialized; + idList cvars; + idHashIndex cvarHash; + int modifiedFlags; + idList cvarHelps; + // use a static dictionary to MoveCVarsToDict can be used from game + static idDict moveCVarsToDict; + + +private: + static void Toggle_f( const idCmdArgs &args ); + static void Set_f( const idCmdArgs &args ); + static void SetS_f( const idCmdArgs &args ); + static void SetU_f( const idCmdArgs &args ); + static void SetT_f( const idCmdArgs &args ); + static void SetA_f( const idCmdArgs &args ); + static void Reset_f( const idCmdArgs &args ); + static void ListByFlags( const idCmdArgs &args, cvarFlags_t flags ); + static void List_f( const idCmdArgs &args ); + static void Restart_f( const idCmdArgs &args ); +}; + +#if defined( _WIN32 ) && defined( Q4_RECON_ENGINE_PRIVATE ) +static_assert( sizeof( idCVarSystemLocal ) == 0x48, "idCVarSystemLocal ABI drift" ); +#endif + +idCVarSystemLocal localCVarSystem; +idCVarSystem * cvarSystem = &localCVarSystem; + +idDict idCVarSystemLocal::moveCVarsToDict; + +#define NUM_COLUMNS 77 // 78 - 1 +#define NUM_NAME_CHARS 33 +#define NUM_DESCRIPTION_CHARS ( NUM_COLUMNS - NUM_NAME_CHARS ) +#define FORMAT_STRING "%-32s " + +const char *CreateColumn( const char *text, int columnWidth, const char *indent, idStr &string ) { + int i, lastLine; + + string.Clear(); + for ( lastLine = i = 0; text[i] != '\0'; i++ ) { + if ( i - lastLine >= columnWidth || text[i] == '\n' ) { + while( i > 0 && text[i] > ' ' && text[i] != '/' && text[i] != ',' && text[i] != '\\' ) { + i--; + } + while( lastLine < i ) { + string.Append( text[lastLine++] ); + } + string.Append( indent ); + lastLine++; + } + } + while( lastLine < i ) { + string.Append( text[lastLine++] ); + } + return string.c_str(); +} + +/* +============ +idCVarSystemLocal::FindInternal +============ +*/ +idInternalCVar *idCVarSystemLocal::FindInternal( const char *name ) const { + int hash = cvarHash.GenerateKey( name, false ); + for ( int i = cvarHash.First( hash ); i != -1; i = cvarHash.Next( i ) ) { + if ( cvars[i]->nameString.Icmp( name ) == 0 ) { + return cvars[i]; + } + } + return NULL; +} + +/* +============ +idCVarSystemLocal::SetInternal +============ +*/ +void idCVarSystemLocal::SetInternal( const char *name, const char *value, int flags ) { + int hash; + idInternalCVar *internal; + + internal = FindInternal( name ); + + if ( internal ) { + internal->InternalSetString( value ); + internal->flags |= flags & ~CVAR_STATIC; + internal->UpdateCheat(); + } else { + internal = new idInternalCVar( name, value, flags ); + hash = cvarHash.GenerateKey( internal->nameString.c_str(), false ); + cvarHash.Add( hash, cvars.Append( internal ) ); + } +} + +/* +============ +idCVarSystemLocal::idCVarSystemLocal +============ +*/ +idCVarSystemLocal::idCVarSystemLocal( void ) { + initialized = false; + modifiedFlags = 0; +} + +/* +============ +idCVarSystemLocal::Init +============ +*/ +void idCVarSystemLocal::Init( void ) { + + modifiedFlags = 0; + + cmdSystem->AddCommand( "toggle", Toggle_f, CMD_FL_SYSTEM, "toggles a cvar" ); + cmdSystem->AddCommand( "set", Set_f, CMD_FL_SYSTEM, "sets a cvar" ); + cmdSystem->AddCommand( "sets", SetS_f, CMD_FL_SYSTEM, "sets a cvar and flags it as server info" ); + cmdSystem->AddCommand( "setu", SetU_f, CMD_FL_SYSTEM, "sets a cvar and flags it as user info" ); + cmdSystem->AddCommand( "sett", SetT_f, CMD_FL_SYSTEM, "sets a cvar and flags it as tool" ); + cmdSystem->AddCommand( "seta", SetA_f, CMD_FL_SYSTEM, "sets a cvar and flags it as archive" ); + cmdSystem->AddCommand( "reset", Reset_f, CMD_FL_SYSTEM, "resets a cvar" ); + cmdSystem->AddCommand( "listCvars", List_f, CMD_FL_SYSTEM, "lists cvars" ); + cmdSystem->AddCommand( "cvar_restart", Restart_f, CMD_FL_SYSTEM, "restart the cvar system" ); + + initialized = true; +} + +/* +============ +idCVarSystemLocal::Shutdown +============ +*/ +void idCVarSystemLocal::Shutdown( void ) { + cvars.DeleteContents( true ); + cvarHash.Free(); + moveCVarsToDict.Clear(); + initialized = false; + cvarHelps.DeleteContents( true ); +} + +/* +============ +idCVarSystemLocal::IsInitialized +============ +*/ +bool idCVarSystemLocal::IsInitialized( void ) const { + return initialized; +} + +/* +============ +idCVarSystemLocal::Register +============ +*/ +void idCVarSystemLocal::Register( idCVar *cvar ) { + int hash; + idInternalCVar *internal; + + cvar->SetInternalVar( cvar ); + + internal = FindInternal( cvar->GetName() ); + + if ( internal ) { + internal->Update( cvar ); + } else { + internal = new idInternalCVar( cvar ); + hash = cvarHash.GenerateKey( internal->nameString.c_str(), false ); + cvarHash.Add( hash, cvars.Append( internal ) ); + } + + cvar->SetInternalVar( internal ); +} + +void idCVarSystemLocal::Register( const idCVarHelp *cvarHelp ) { + cvarHelps.Append( new idCVarHelp( *cvarHelp ) ); +} + +idCVarHelp *idCVarSystemLocal::GetHelps( cvarHelpCategory_t category ) { + idCVarHelp *first = NULL; + idCVarHelp *last = NULL; + for ( int i = 0; i < cvarHelps.Num(); i++ ) { + idCVarHelp *help = cvarHelps[i]; + if ( !( category & help->GetCategory() ) ) { + continue; + } + if ( last ) { + last->SetNext( help ); + } else { + first = help; + } + last = help; + last->SetNext( NULL ); + } + return first; +} + +/* +============ +idCVarSystemLocal::Find +============ +*/ +idCVar *idCVarSystemLocal::Find( const char *name ) { + return FindInternal( name ); +} + +/* +============ +idCVarSystemLocal::SetCVarString +============ +*/ +void idCVarSystemLocal::SetCVarString( const char *name, const char *value, int flags ) { + SetInternal( name, value, flags ); +} + +/* +============ +idCVarSystemLocal::SetCVarBool +============ +*/ +void idCVarSystemLocal::SetCVarBool( const char *name, const bool value, int flags ) { + SetInternal( name, idStr( value ), flags ); +} + +/* +============ +idCVarSystemLocal::SetCVarInteger +============ +*/ +void idCVarSystemLocal::SetCVarInteger( const char *name, const int value, int flags ) { + SetInternal( name, idStr( value ), flags ); +} + +/* +============ +idCVarSystemLocal::SetCVarFloat +============ +*/ +void idCVarSystemLocal::SetCVarFloat( const char *name, const float value, int flags ) { + SetInternal( name, idStr( value ), flags ); +} + +/* +============ +idCVarSystemLocal::GetCVarString +============ +*/ +const char *idCVarSystemLocal::GetCVarString( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetString(); + } + return ""; +} + +/* +============ +idCVarSystemLocal::GetCVarBool +============ +*/ +bool idCVarSystemLocal::GetCVarBool( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetBool(); + } + return false; +} + +/* +============ +idCVarSystemLocal::GetCVarInteger +============ +*/ +int idCVarSystemLocal::GetCVarInteger( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetInteger(); + } + return 0; +} + +/* +============ +idCVarSystemLocal::GetCVarFloat +============ +*/ +float idCVarSystemLocal::GetCVarFloat( const char *name ) const { + idInternalCVar *internal = FindInternal( name ); + if ( internal ) { + return internal->GetFloat(); + } + return 0.0f; +} + +/* +============ +idCVarSystemLocal::Command +============ +*/ +bool idCVarSystemLocal::Command( const idCmdArgs &args ) { + idInternalCVar *internal; + + internal = FindInternal( args.Argv( 0 ) ); + + if ( internal == NULL ) { + return false; + } + + if ( args.Argc() == 1 ) { + // print the variable + common->Printf( "\"%s\" is:\"%s\"" S_COLOR_WHITE " default:\"%s\"\n", + internal->nameString.c_str(), internal->valueString.c_str(), internal->resetString.c_str() ); + if ( idStr::Length( internal->GetDescription() ) > 0 ) { + common->Printf( S_COLOR_WHITE "%s\n", internal->GetDescription() ); + } + } else { + // set the value + internal->Set( args.Args(), false, false ); + } + return true; +} + +/* +============ +idCVarSystemLocal::CommandCompletion +============ +*/ +void idCVarSystemLocal::CommandCompletion( void(*callback)( const char *s ) ) { + for( int i = 0; i < cvars.Num(); i++ ) { + callback( cvars[i]->GetName() ); + } +} + +/* +============ +idCVarSystemLocal::ArgCompletion +============ +*/ +void idCVarSystemLocal::ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) { + idCmdArgs args; + + args.TokenizeString( cmdString, false ); + + for( int i = 0; i < cvars.Num(); i++ ) { + if ( !cvars[i]->valueCompletion ) { + continue; + } + if ( idStr::Icmp( args.Argv( 0 ), cvars[i]->nameString.c_str() ) == 0 ) { + cvars[i]->valueCompletion( args, callback ); + break; + } + } +} + +/* +============ +idCVarSystemLocal::SetModifiedFlags +============ +*/ +void idCVarSystemLocal::SetModifiedFlags( int flags ) { + modifiedFlags |= flags; +} + +/* +============ +idCVarSystemLocal::GetModifiedFlags +============ +*/ +int idCVarSystemLocal::GetModifiedFlags( void ) const { + return modifiedFlags; +} + +/* +============ +idCVarSystemLocal::ClearModifiedFlags +============ +*/ +void idCVarSystemLocal::ClearModifiedFlags( int flags ) { + modifiedFlags &= ~flags; +} + +/* +============ +idCVarSystemLocal::ResetFlaggedVariables +============ +*/ +void idCVarSystemLocal::ResetFlaggedVariables( int flags ) { + for( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + cvar->Set( NULL, true, true ); + } + } +} + +/* +============ +idCVarSystemLocal::RemoveFlaggedAutoCompletion +============ +*/ +void idCVarSystemLocal::RemoveFlaggedAutoCompletion( int flags ) { + for( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + cvar->valueCompletion = NULL; + } + } +} + +/* +============ +idCVarSystemLocal::WriteFlaggedVariables + +Appends lines containing "set variable value" for all variables +with the "flags" flag set to true. +============ +*/ +void idCVarSystemLocal::WriteFlaggedVariables( int flags, const char *setCmd, idFile *f ) const { + for( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + f->Printf( "%s %s \"%s\"\n", setCmd, cvar->GetName(), cvar->GetString() ); + } + } +} + +unsigned int idCVarSystemLocal::WriteFlaggedVariables( int flags, const char *setCmd, byte *buf, unsigned int bufSize ) const { + byte *write = buf; + byte *end = buf + bufSize; + (void)setCmd; + + for ( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( !( cvar->GetFlags() & flags ) ) { + continue; + } + + const char *name = cvar->GetName(); + while ( *name && write < end ) { + *write++ = *name++; + } + if ( write >= end ) { + return write - buf; + } + *write++ = 0; + + const int cvarFlags = cvar->GetFlags(); + if ( cvarFlags & CVAR_BOOL ) { + if ( write >= end ) { + return write - buf; + } + *write++ = cvar->GetBool(); + } else if ( cvarFlags & CVAR_INTEGER ) { + if ( end - write < sizeof( int ) ) { + return write - buf; + } + const int value = cvar->GetInteger(); + memcpy( write, &value, sizeof( value ) ); + write += sizeof( value ); + } else if ( cvarFlags & CVAR_FLOAT ) { + if ( end - write < sizeof( float ) ) { + return write - buf; + } + const float value = cvar->GetFloat(); + memcpy( write, &value, sizeof( value ) ); + write += sizeof( value ); + } else { + const char *value = cvar->GetString(); + while ( *value && write < end ) { + *write++ = *value++; + } + if ( write >= end ) { + return write - buf; + } + *write++ = 0; + } + } + + if ( write < end ) { + *write++ = 0; + } + return write - buf; +} + +void idCVarSystemLocal::ApplyFlaggedVariables( byte *buf, unsigned int bufSize ) { + byte *read = buf; + byte *end = buf + bufSize; + while ( read < end && *read ) { + const char *name = reinterpret_cast( read ); + while ( read < end && *read ) { + read++; + } + if ( read >= end ) { + break; + } + read++; + + idCVar *cvar = Find( name ); + if ( !cvar ) { + break; + } + const int cvarFlags = cvar->GetFlags(); + if ( cvarFlags & CVAR_BOOL ) { + if ( read >= end ) { + break; + } + cvar->SetBool( ( *read++ & 1 ) != 0 ); + } else if ( cvarFlags & CVAR_INTEGER ) { + if ( end - read < sizeof( int ) ) { + break; + } + int value; + memcpy( &value, read, sizeof( value ) ); + read += sizeof( value ); + cvar->SetInteger( value ); + } else if ( cvarFlags & CVAR_FLOAT ) { + if ( end - read < sizeof( float ) ) { + break; + } + float value; + memcpy( &value, read, sizeof( value ) ); + read += sizeof( value ); + cvar->SetFloat( value ); + } else { + const char *value = reinterpret_cast( read ); + while ( read < end && *read ) { + read++; + } + if ( read >= end ) { + break; + } + read++; + cvar->SetString( value ); + } + } +} + +idStr idCVarSystemLocal::WriteFlaggedVariables( int flags ) const { + idStr infoString; + for ( int i = 0; i < cvars.Num(); i++ ) { + idInternalCVar *cvar = cvars[i]; + if ( !( cvar->GetFlags() & flags ) ) { + continue; + } + infoString += "\\"; + infoString += cvar->GetName(); + infoString += "\\"; + infoString += cvar->GetString(); + } + return infoString; +} + +/* +============ +idCVarSystemLocal::MoveCVarsToDict +============ +*/ +const idDict* idCVarSystemLocal::MoveCVarsToDict( int flags ) const { + moveCVarsToDict.Clear(); + for( int i = 0; i < cvars.Num(); i++ ) { + idCVar *cvar = cvars[i]; + if ( cvar->GetFlags() & flags ) { + moveCVarsToDict.Set( cvar->GetName(), cvar->GetString() ); + } + } + return &moveCVarsToDict; +} + +/* +============ +idCVarSystemLocal::SetCVarsFromDict +============ +*/ +void idCVarSystemLocal::SetCVarsFromDict( const idDict &dict ) { + idInternalCVar *internal; + + for( int i = 0; i < dict.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = dict.GetKeyVal( i ); + internal = FindInternal( kv->GetKey() ); + if ( internal ) { + internal->InternalServerSetString( kv->GetValue() ); + } + } +} + +/* +============ +idCVarSystemLocal::Toggle_f +============ +*/ +void idCVarSystemLocal::Toggle_f( const idCmdArgs &args ) { + int argc, i; + float current, set; + const char *text; + + argc = args.Argc(); + if ( argc < 2 ) { + common->Printf ("usage:\n" + " toggle - toggles between 0 and 1\n" + " toggle - toggles between 0 and \n" + " toggle [string 1] [string 2]...[string n] - cycles through all strings\n"); + return; + } + + idInternalCVar *cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + + if ( cvar == NULL ) { + common->Warning( "Toggle_f: cvar \"%s\" not found", args.Argv( 1 ) ); + return; + } + + if ( argc > 3 ) { + // cycle through multiple values + text = cvar->GetString(); + for( i = 2; i < argc; i++ ) { + if ( !idStr::Icmp( text, args.Argv( i ) ) ) { + // point to next value + i++; + break; + } + } + if ( i >= argc ) { + i = 2; + } + + common->Printf( "set %s = %s\n", args.Argv(1), args.Argv( i ) ); + cvar->Set( va("%s", args.Argv( i ) ), false, false ); + } else { + // toggle between 0 and 1 + current = cvar->GetFloat(); + if ( argc == 3 ) { + set = atof( args.Argv( 2 ) ); + } else { + set = 1.0f; + } + if ( current == 0.0f ) { + current = set; + } else { + current = 0.0f; + } + common->Printf( "set %s = %f\n", args.Argv(1), current ); + cvar->Set( idStr( current ), false, false ); + } +} + +/* +============ +idCVarSystemLocal::Set_f +============ +*/ +void idCVarSystemLocal::Set_f( const idCmdArgs &args ) { + const char *str; + + str = args.Args( 2, args.Argc() - 1 ); + localCVarSystem.SetCVarString( args.Argv(1), str ); +} + +/* +============ +idCVarSystemLocal::SetS_f +============ +*/ +void idCVarSystemLocal::SetS_f( const idCmdArgs &args ) { + idInternalCVar *cvar; + + Set_f( args ); + cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + if ( !cvar ) { + return; + } + cvar->flags |= CVAR_SERVERINFO | CVAR_ARCHIVE; +} + +/* +============ +idCVarSystemLocal::SetU_f +============ +*/ +void idCVarSystemLocal::SetU_f( const idCmdArgs &args ) { + idInternalCVar *cvar; + + Set_f( args ); + cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + if ( !cvar ) { + return; + } + cvar->flags |= CVAR_USERINFO | CVAR_ARCHIVE; +} + +/* +============ +idCVarSystemLocal::SetT_f +============ +*/ +void idCVarSystemLocal::SetT_f( const idCmdArgs &args ) { + idInternalCVar *cvar; + + Set_f( args ); + cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + if ( !cvar ) { + return; + } + cvar->flags |= CVAR_TOOL; +} + +/* +============ +idCVarSystemLocal::SetA_f +============ +*/ +void idCVarSystemLocal::SetA_f( const idCmdArgs &args ) { + idInternalCVar *cvar; + + Set_f( args ); + cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + if ( !cvar ) { + return; + } + + // FIXME: enable this for ship, so mods can store extra data + // but during development we don't want obsolete cvars to continue + // to be saved +// cvar->flags |= CVAR_ARCHIVE; +} + +/* +============ +idCVarSystemLocal::Reset_f +============ +*/ +void idCVarSystemLocal::Reset_f( const idCmdArgs &args ) { + idInternalCVar *cvar; + + if ( args.Argc() != 2 ) { + common->Printf ("usage: reset \n"); + return; + } + cvar = localCVarSystem.FindInternal( args.Argv( 1 ) ); + if ( !cvar ) { + return; + } + + cvar->Reset(); +} + +/* +============ +idCVarSystemLocal::ListByFlags +============ +*/ +// NOTE: the const wonkyness is required to make msvc happy +template<> +ID_INLINE int idListSortCompare( const idInternalCVar * const *a, const idInternalCVar * const *b ) { + return idStr::Icmp( (*a)->GetName(), (*b)->GetName() ); +} + +void idCVarSystemLocal::ListByFlags( const idCmdArgs &args, cvarFlags_t flags ) { + int i, argNum; + idStr match, indent, string; + const idInternalCVar *cvar; + idListcvarList; + + enum { + SHOW_VALUE, + SHOW_DESCRIPTION, + SHOW_TYPE, + SHOW_FLAGS + } show; + + argNum = 1; + show = SHOW_VALUE; + + if ( idStr::Icmp( args.Argv( argNum ), "-" ) == 0 || idStr::Icmp( args.Argv( argNum ), "/" ) == 0 ) { + if ( idStr::Icmp( args.Argv( argNum + 1 ), "help" ) == 0 || idStr::Icmp( args.Argv( argNum + 1 ), "?" ) == 0 ) { + argNum = 3; + show = SHOW_DESCRIPTION; + } else if ( idStr::Icmp( args.Argv( argNum + 1 ), "type" ) == 0 || idStr::Icmp( args.Argv( argNum + 1 ), "range" ) == 0 ) { + argNum = 3; + show = SHOW_TYPE; + } else if ( idStr::Icmp( args.Argv( argNum + 1 ), "flags" ) == 0 ) { + argNum = 3; + show = SHOW_FLAGS; + } + } + + if ( args.Argc() > argNum ) { + match = args.Args( argNum, -1 ); + match.Replace( " ", "" ); + } else { + match = ""; + } + + for ( i = 0; i < localCVarSystem.cvars.Num(); i++ ) { + cvar = localCVarSystem.cvars[i]; + + if ( !( cvar->GetFlags() & flags ) ) { + continue; + } + + if ( match.Length() && !cvar->nameString.Filter( match, false ) ) { + continue; + } + + cvarList.Append( cvar ); + } + + cvarList.Sort(); + + switch( show ) { + case SHOW_VALUE: { + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + common->Printf( FORMAT_STRING S_COLOR_WHITE "\"%s\"\n", cvar->nameString.c_str(), cvar->valueString.c_str() ); + } + break; + } + case SHOW_DESCRIPTION: { + indent.Fill( ' ', NUM_NAME_CHARS ); + indent.Insert( "\n", 0 ); + + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + common->Printf( FORMAT_STRING S_COLOR_WHITE "%s\n", cvar->nameString.c_str(), CreateColumn( cvar->GetDescription(), NUM_DESCRIPTION_CHARS, indent, string ) ); + } + break; + } + case SHOW_TYPE: { + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + if ( cvar->GetFlags() & CVAR_BOOL ) { + common->Printf( FORMAT_STRING S_COLOR_CYAN "bool\n", cvar->GetName() ); + } else if ( cvar->GetFlags() & CVAR_INTEGER ) { + if ( cvar->GetMinValue() < cvar->GetMaxValue() ) { + common->Printf( FORMAT_STRING S_COLOR_GREEN "int " S_COLOR_WHITE "[%d, %d]\n", cvar->GetName(), (int) cvar->GetMinValue(), (int) cvar->GetMaxValue() ); + } else { + common->Printf( FORMAT_STRING S_COLOR_GREEN "int\n", cvar->GetName() ); + } + } else if ( cvar->GetFlags() & CVAR_FLOAT ) { + if ( cvar->GetMinValue() < cvar->GetMaxValue() ) { + common->Printf( FORMAT_STRING S_COLOR_RED "float " S_COLOR_WHITE "[%s, %s]\n", cvar->GetName(), idStr( cvar->GetMinValue() ).c_str(), idStr( cvar->GetMaxValue() ).c_str() ); + } else { + common->Printf( FORMAT_STRING S_COLOR_RED "float\n", cvar->GetName() ); + } + } else if ( cvar->GetValueStrings() ) { + common->Printf( FORMAT_STRING S_COLOR_WHITE "string " S_COLOR_WHITE "[", cvar->GetName() ); + for ( int j = 0; cvar->GetValueStrings()[j] != NULL; j++ ) { + if ( j ) { + common->Printf( S_COLOR_WHITE ", %s", cvar->GetValueStrings()[j] ); + } else { + common->Printf( S_COLOR_WHITE "%s", cvar->GetValueStrings()[j] ); + } + } + common->Printf( S_COLOR_WHITE "]\n" ); + } else { + common->Printf( FORMAT_STRING S_COLOR_WHITE "string\n", cvar->GetName() ); + } + } + break; + } + case SHOW_FLAGS: { + for ( i = 0; i < cvarList.Num(); i++ ) { + cvar = cvarList[i]; + common->Printf( FORMAT_STRING, cvar->GetName() ); + string = ""; + if ( cvar->GetFlags() & CVAR_BOOL ) { + string += S_COLOR_CYAN "B "; + } else if ( cvar->GetFlags() & CVAR_INTEGER ) { + string += S_COLOR_GREEN "I "; + } else if ( cvar->GetFlags() & CVAR_FLOAT ) { + string += S_COLOR_RED "F "; + } else { + string += S_COLOR_WHITE "S "; + } + if ( cvar->GetFlags() & CVAR_SYSTEM ) { + string += S_COLOR_WHITE "SYS "; + } else if ( cvar->GetFlags() & CVAR_RENDERER ) { + string += S_COLOR_WHITE "RNDR "; + } else if ( cvar->GetFlags() & CVAR_SOUND ) { + string += S_COLOR_WHITE "SND "; + } else if ( cvar->GetFlags() & CVAR_GUI ) { + string += S_COLOR_WHITE "GUI "; + } else if ( cvar->GetFlags() & CVAR_GAME ) { + string += S_COLOR_WHITE "GAME "; + } else if ( cvar->GetFlags() & CVAR_TOOL ) { + string += S_COLOR_WHITE "TOOL "; + } else { + string += S_COLOR_WHITE " "; + } + string += ( cvar->GetFlags() & CVAR_USERINFO ) ? "UI " : " "; + string += ( cvar->GetFlags() & CVAR_SERVERINFO ) ? "SI " : " "; + string += ( cvar->GetFlags() & CVAR_STATIC ) ? "ST " : " "; + string += ( cvar->GetFlags() & CVAR_CHEAT ) ? "CH " : " "; + string += ( cvar->GetFlags() & CVAR_INIT ) ? "IN " : " "; + string += ( cvar->GetFlags() & CVAR_ROM ) ? "RO " : " "; + string += ( cvar->GetFlags() & CVAR_ARCHIVE ) ? "AR " : " "; + string += ( cvar->GetFlags() & CVAR_MODIFIED ) ? "MO " : " "; + string += "\n"; + common->Printf( string ); + } + break; + } + } + + common->Printf( "\n%i cvars listed\n\n", cvarList.Num() ); + common->Printf( "listCvar [search string] = list cvar values\n" + "listCvar -help [search string] = list cvar descriptions\n" + "listCvar -type [search string] = list cvar types\n" + "listCvar -flags [search string] = list cvar flags\n" ); +} + +/* +============ +idCVarSystemLocal::List_f +============ +*/ +void idCVarSystemLocal::List_f( const idCmdArgs &args ) { + ListByFlags( args, CVAR_ALL ); +} + +/* +============ +idCVarSystemLocal::Restart_f +============ +*/ +void idCVarSystemLocal::Restart_f( const idCmdArgs &args ) { + int i, hash; + idInternalCVar *cvar; + + for ( i = 0; i < localCVarSystem.cvars.Num(); i++ ) { + cvar = localCVarSystem.cvars[i]; + + // don't mess with rom values + if ( cvar->flags & ( CVAR_ROM | CVAR_INIT ) ) { + continue; + } + + // throw out any variables the user created + if ( !( cvar->flags & CVAR_STATIC ) ) { + hash = localCVarSystem.cvarHash.GenerateKey( cvar->nameString, false ); + delete cvar; + localCVarSystem.cvars.RemoveIndex( i ); + localCVarSystem.cvarHash.RemoveIndex( hash, i ); + i--; + continue; + } + + cvar->Reset(); + } +} diff --git a/src/framework/declAF.h b/src/framework/declAF.h new file mode 100644 index 0000000..576f14c --- /dev/null +++ b/src/framework/declAF.h @@ -0,0 +1,296 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLAF_H__ +#define __DECLAF_H__ + +/* +=============================================================================== + + Articulated Figure + +=============================================================================== +*/ + +class idDeclAF; + +typedef enum { + DECLAF_CONSTRAINT_INVALID, + DECLAF_CONSTRAINT_FIXED, + DECLAF_CONSTRAINT_BALLANDSOCKETJOINT, + DECLAF_CONSTRAINT_UNIVERSALJOINT, + DECLAF_CONSTRAINT_HINGE, + DECLAF_CONSTRAINT_SLIDER, + DECLAF_CONSTRAINT_SPRING +} declAFConstraintType_t; + +typedef enum { + DECLAF_JOINTMOD_AXIS, + DECLAF_JOINTMOD_ORIGIN, + DECLAF_JOINTMOD_BOTH +} declAFJointMod_t; + +typedef bool (*getJointTransform_t)( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idAFVector : public Serializable<'AFV '> { +public: + // Serialization methods + void Write(SerialOutputStream &stream) const; + void AddReferences() const; + idAFVector(SerialInputStream &stream); + + +#else +class idAFVector { +public: +#endif + enum idAFVectorType_t { +// RAVEN END + VEC_COORDS = 0, + VEC_JOINT, + VEC_BONECENTER, + VEC_BONEDIR + } type; + idStr joint1; + idStr joint2; + +public: + idAFVector( void ); + + bool Parse( idLexer &src ); + bool Finish( const char *fileName, const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const; + bool Write( idFile *f ) const; + const char * ToString( idStr &str, const int precision = 8 ); + const idVec3 & ToVec3( void ) const { return vec; } + idVec3 & ToVec3( void ) { return vec; } + +private: + mutable idVec3 vec; + bool negate; +}; + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclAF_Body : public Serializable<'DAFB'> { +public: + + // Serializable Methods + void Write( SerialOutputStream &stream ) const; + idDeclAF_Body( SerialInputStream &stream ); + void AddReferences() const; +#else +class idDeclAF_Body { +#endif +public: + idDeclAF_Body(); +// RAVEN END + idStr name; + idStr jointName; + declAFJointMod_t jointMod; + int modelType; + idAFVector v1, v2; + int numSides; + float width; + float density; + idAFVector origin; + idAngles angles; + int contents; + int clipMask; + bool selfCollision; + idMat3 inertiaScale; + float linearFriction; + float angularFriction; + float contactFriction; + idStr containedJoints; + idAFVector frictionDirection; + idAFVector contactMotorDirection; +public: + void SetDefault( const idDeclAF *file ); +}; + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclAF_Constraint : public Serializable<'DAFC'> { +public: + // Serializable Methods + void Write( SerialOutputStream &stream ) const; + idDeclAF_Constraint( SerialInputStream &stream ); + void AddReferences() const; +#else +class idDeclAF_Constraint { +public: +#endif + idDeclAF_Constraint(); +// RAVEN END + idStr name; + idStr body1; + idStr body2; + declAFConstraintType_t type; + float friction; + float stretch; + float compress; + float damping; + float restLength; + float minLength; + float maxLength; + idAFVector anchor; + idAFVector anchor2; + idAFVector shaft[2]; + idAFVector axis; +// RAVEN BEGIN +// jsinger: added declAFLimitType_t to support serialization/deserialization of binary decls + enum declAFLimitType_t { + LIMIT_NONE = -1, + LIMIT_CONE, + LIMIT_PYRAMID + } limit; +// RAVEN END + idAFVector limitAxis; + float limitAngles[3]; + +public: + void SetDefault( const idDeclAF *file ); +}; + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclAF : public idDecl, public Serializable<'DAF '> { +public: + idDeclAF( SerialInputStream &stream ); + void Write( SerialOutputStream &stream ) const; + void AddReferences() const; +#else +class idDeclAF : public idDecl { +#endif +// RAVEN END + friend class idAFFileManager; +public: + idDeclAF( void ); + virtual ~idDeclAF( void ); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + virtual void Finish( const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const; + + bool Save( void ); + + void NewBody( const char *name ); + void RenameBody( const char *oldName, const char *newName ); + void DeleteBody( const char *name ); + + void NewConstraint( const char *name ); + void RenameConstraint( const char *oldName, const char *newName ); + void DeleteConstraint( const char *name ); + + static int ContentsFromString( const char *str ); + static const char * ContentsToString( const int contents, idStr &str ); + + static declAFJointMod_t JointModFromString( const char *str ); + static const char * JointModToString( declAFJointMod_t jointMod ); + +public: + bool modified; + idStr model; + idStr skin; + float defaultLinearFriction; + float defaultAngularFriction; + float defaultContactFriction; + float defaultConstraintFriction; + float totalMass; + idVec2 suspendVelocity; + idVec2 suspendAcceleration; + float noMoveTime; + float noMoveTranslation; + float noMoveRotation; + float minMoveTime; + float maxMoveTime; + int contents; + int clipMask; + bool selfCollision; +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + bool fastEval; +// RAVEN END + idList bodies; + idList constraints; + +private: + bool ParseContents( idLexer &src, int &c ) const; + bool ParseBody( idLexer &src ); + bool ParseFixed( idLexer &src ); + bool ParseBallAndSocketJoint( idLexer &src ); + bool ParseUniversalJoint( idLexer &src ); + bool ParseHinge( idLexer &src ); + bool ParseSlider( idLexer &src ); + bool ParseSpring( idLexer &src ); + bool ParseSettings( idLexer &src ); + + bool WriteBody( idFile *f, const idDeclAF_Body &body ) const; + bool WriteFixed( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteBallAndSocketJoint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteUniversalJoint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteHinge( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSlider( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSpring( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteConstraint( idFile *f, const idDeclAF_Constraint &c ) const; + bool WriteSettings( idFile *f ) const; + + bool RebuildTextSource( void ); +}; + +// RAVEN BEGIN +class rvDeclAFEdit +{ +public: + virtual ~rvDeclAFEdit() { } + virtual bool Save( idDeclAF *edit ) = 0; + virtual void NewBody( idDeclAF *edit, char const *name ) = 0; + virtual void RenameBody( idDeclAF *edit, char const *oldName, char const *newName ) = 0; + virtual void DeleteBody( idDeclAF *edit, char const *name ) = 0; + virtual void NewConstraint( idDeclAF *edit, char const *name ) = 0; + virtual void RenameConstraint( idDeclAF *edit, char const *oldName, char const *newName ) = 0; + virtual void DeleteConstraint( idDeclAF *edit, char const *name ) = 0; +}; + +extern rvDeclAFEdit *declAFEdit; +// RAVEN END + +#endif /* !__DECLAF_H__ */ diff --git a/src/framework/declEntityDef.h b/src/framework/declEntityDef.h new file mode 100644 index 0000000..a7cacfb --- /dev/null +++ b/src/framework/declEntityDef.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLENTITYDEF_H__ +#define __DECLENTITYDEF_H__ + +/* +=============================================================================== + + idDeclEntityDef + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclEntityDef : public idDecl, public Serializable<'DED '> { +public: + virtual void AddReferences() const; + virtual void Write(SerialOutputStream &stream) const; + idDeclEntityDef(SerialInputStream &stream); +#else +class idDeclEntityDef : public idDecl { +#endif +// RAVEN END +public: + idDeclEntityDef(); + idDict dict; + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition() const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + +}; + +#endif /* !__DECLENTITYDEF_H__ */ diff --git a/src/framework/declLipSync.cpp b/src/framework/declLipSync.cpp new file mode 100644 index 0000000..0893f35 --- /dev/null +++ b/src/framework/declLipSync.cpp @@ -0,0 +1,151 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=========================================================================== + + Quake 4 lip-sync declaration implementation. + Function ownership and public ABI are recovered from quake4.pdb + (declLipSync.obj); behavior follows the retail Hex-Rays listing. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "declLipSync.h" + +class rvDeclLipSyncEditLocal : public rvDeclLipSyncEdit { +public: + virtual void SetLipSyncDescription( rvDeclLipSync *edit, const char *desc ) { + edit->SetDescription( desc ); + } + virtual void SetLipSyncTranscribeText( rvDeclLipSync *edit, const char *text ) { + edit->SetTranscribeText( text ); + } + virtual void SetLipSyncData( rvDeclLipSync *edit, const char *lsd, const char *lang ) { + edit->SetLipSyncData( lsd, lang ); + } +}; + +static rvDeclLipSyncEditLocal declLipSyncEditLocal; +rvDeclLipSyncEdit *declLipSyncEdit = &declLipSyncEditLocal; + +const char *rvDeclLipSync::DefaultDefinition( void ) const { + return "{ description \"\" }"; +} + +bool rvDeclLipSync::Validate( const char *text, int textLength, idStr &report ) const { + idDecl *decl = declManager->AllocateDecl( DECL_LIPSYNC ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl; + return valid; +} + +void rvDeclLipSync::FreeData( void ) { + mDescription.Clear(); + mTranscribeText.Clear(); + mHMM.Clear(); + mLipSyncData.Clear(); +} + +void rvDeclLipSync::SetLipSyncData( const char *lsd, const char *lang ) { + if ( strchr( lsd, '%' ) ) { + common->Warning( "SetLipSyncData: language %s for lipsync '%s' has invalid character %% in it", lang, GetName() ); + return; + } + mLipSyncData.Set( lang, lsd ); +} + +bool rvDeclLipSync::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + idToken lang; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( 6300 ); + if ( !src.SkipUntilString( "{" ) ) { + return false; + } + + mHMM = "male"; + while ( src.ReadToken( &token ) ) { + if ( token == "}" ) { + return true; + } + if ( !token.Icmp( "description" ) ) { + if ( !src.ReadToken( &token ) ) { + return false; + } + mDescription = token; + } else if ( !token.Icmp( "text" ) ) { + if ( !src.ReadToken( &token ) ) { + return false; + } + mTranscribeText = token; + } else if ( !token.Icmp( "hmm" ) ) { + if ( !src.ReadToken( &token ) ) { + return false; + } + mHMM = token; + } else if ( !token.Icmp( "visemes" ) ) { + if ( !src.ReadToken( &lang ) || !src.ReadToken( &token ) ) { + return false; + } + SetLipSyncData( token.c_str(), lang.c_str() ); + } + } + return false; +} + +bool rvDeclLipSync::RebuildTextSource( void ) { + idFile_Memory file; + file.WriteFloatString( "\r\nlipSync %s\r\n{\r\n", GetName() ); + if ( mDescription.Length() ) { + file.WriteFloatString( "\tdescription\t\"%s\"\r\n", mDescription.c_str() ); + } + if ( mTranscribeText.Length() ) { + file.WriteFloatString( "\ttext\t\t\"%s\"\r\n", mTranscribeText.c_str() ); + } + if ( mHMM.Icmp( "male" ) ) { + file.WriteFloatString( "\thmm\t\t\"%s\"\r\n", mHMM.c_str() ); + } + for ( int i = 0; i < mLipSyncData.GetNumKeyVals(); i++ ) { + const idKeyValue *keyValue = mLipSyncData.GetKeyVal( i ); + file.WriteFloatString( "\tvisemes\t\"%s\"\t\"%s\"\r\n", keyValue->GetKey().c_str(), keyValue->GetValue().c_str() ); + } + file.WriteFloatString( "}\r\n\r\n" ); + SetText( file.GetDataPtr() ); + return true; +} + +size_t rvDeclLipSync::Size( void ) const { + return sizeof( *this ) + mDescription.Allocated() + mTranscribeText.Allocated() + mHMM.Allocated() + mLipSyncData.Allocated(); +} + +#if defined( _WIN32 ) && !defined( RV_BINARYDECLS ) +static_assert( sizeof( rvDeclLipSync ) == 148, "rvDeclLipSync ABI drift" ); +#endif diff --git a/src/framework/declLipSync.h b/src/framework/declLipSync.h new file mode 100644 index 0000000..d1631c6 --- /dev/null +++ b/src/framework/declLipSync.h @@ -0,0 +1,97 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DECLLIPSYNC_H__ +#define __DECLLIPSYNC_H__ + +// A way of cross referencing strings and sound shaders +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class rvDeclLipSync : public idDecl, public Serializable<'RDLS'> +{ +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + rvDeclLipSync( SerialInputStream &stream ); +#else +class rvDeclLipSync : public idDecl +{ +#endif +// RAVEN END +public: + rvDeclLipSync( void ) {} + ~rvDeclLipSync( void ) {} + + void SetDescription( const char *desc ) { mDescription = desc; } + const idStr &GetDescription( void ) const { return( mDescription ); } + + void SetHMM( idStr &hmm ) { mHMM = hmm; } + const idStr &GetHMM( void ) const { return( mHMM ); } + + void SetTranscribeText( const char *text ) { mTranscribeText = text; } + const char *GetTranscribeText( int langIndex = -1 ) const { return( common->GetLocalizedString( mTranscribeText, langIndex ) ); } + const char *GetRawTranscribeText( void ) const { return( mTranscribeText.c_str() ); } + + void SetLipSyncData( const char *lsd, const char *lang ); + const char *GetLipSyncData( int langIdx = -1 ) const; + + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual bool RebuildTextSource( void ); + virtual size_t Size( void ) const; + + // scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; + +private: + idStr mDescription; + idStr mTranscribeText; + idStr mHMM; + idDict mLipSyncData; +}; + +ID_INLINE const char *rvDeclLipSync::GetLipSyncData( int langIdx ) const +{ + if( common->LanguageHasVO( langIdx ) ) + { + return( mLipSyncData.GetString( common->GetLanguage( langIdx ) ) ); + } + + return( mLipSyncData.GetString( "english" ) ); +} + +class rvDeclLipSyncEdit +{ +public: + virtual ~rvDeclLipSyncEdit() { } + virtual void SetLipSyncDescription( rvDeclLipSync *edit, const char *desc ) = 0; + virtual void SetLipSyncTranscribeText( rvDeclLipSync *edit, const char *text ) = 0; + virtual void SetLipSyncData( rvDeclLipSync *edit, const char *lsd, const char *lang ) = 0; +}; + +extern rvDeclLipSyncEdit *declLipSyncEdit; + +#endif // __DECLLIPSYNC_H__ diff --git a/src/framework/declManager.h b/src/framework/declManager.h new file mode 100644 index 0000000..d2dff6d --- /dev/null +++ b/src/framework/declManager.h @@ -0,0 +1,482 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLMANAGER_H__ +#define __DECLMANAGER_H__ + +/* +=============================================================================== + + Declaration Manager + + All "small text" data types, like materials, sound shaders, fx files, + entity defs, etc. are managed uniformly, allowing reloading, purging, + listing, printing, etc. All "large text" data types that never have more + than one declaration in a given file, like maps, models, AAS files, etc. + are not handled here. + + A decl will never, ever go away once it is created. The manager is + garranteed to always return the same decl pointer for a decl type/name + combination. The index of a decl in the per type list also stays the + same throughout the lifetime of the engine. Although the pointer to + a decl always stays the same, one should never maintain pointers to + data inside decls. The data stored in a decl is not garranteed to stay + the same for more than one engine frame. + + The decl indexes of explicitely defined decls are garrenteed to be + consistent based on the parsed decl files. However, the indexes of + implicit decls may be different based on the order in which levels + are loaded. + + The decl namespaces are separate for each type. Comments for decls go + above the text definition to keep them associated with the proper decl. + + During decl parsing, errors should never be issued, only warnings + followed by a call to MakeDefault(). + +=============================================================================== +*/ + +typedef enum { + DECL_TABLE = 0, + DECL_MATERIAL, + DECL_SKIN, + DECL_SOUND, + DECL_ENTITYDEF, + DECL_MODELDEF, +// RAVEN BEGIN +// jscott: added new decls + DECL_MATERIALTYPE, + DECL_LIPSYNC, + DECL_PLAYBACK, + DECL_EFFECT, +// rjohnson: camera is now contained in a def for frame commands + DECL_CAMERADEF, +// jscott: don't use these +// DECL_FX, +// DECL_PARTICLE, +// RAVEN END + DECL_AF, + DECL_PDA, + DECL_VIDEO, + DECL_AUDIO, + DECL_EMAIL, + DECL_MODELEXPORT, + DECL_MAPDEF, + + // new decl types can be added here + DECL_PLAYER_MODEL, + + DECL_MAX_TYPES = 32 +} declType_t; + +typedef enum { + DS_UNPARSED, + DS_DEFAULTED, // set if a parse failed due to an error, or the lack of any source + DS_PARSED +} declState_t; + +const int DECL_LEXER_FLAGS = LEXFL_NOSTRINGCONCAT | // multiple strings seperated by whitespaces are not concatenated + LEXFL_NOSTRINGESCAPECHARS | // no escape characters inside strings + LEXFL_ALLOWPATHNAMES | // allow path seperators in names + LEXFL_ALLOWMULTICHARLITERALS | // allow multi character literals + LEXFL_ALLOWBACKSLASHSTRINGCONCAT | // allow multiple strings seperated by '\' to be concatenated + LEXFL_NOFATALERRORS; // just set a flag instead of fatal erroring + + +class idDeclBase { +public: + virtual ~idDeclBase() {}; + virtual const char * GetName( void ) const = 0; + virtual declType_t GetType( void ) const = 0; + virtual declState_t GetState( void ) const = 0; + virtual bool IsImplicit( void ) const = 0; + virtual bool IsValid( void ) const = 0; + virtual void Invalidate( void ) = 0; + virtual void Reload( void ) = 0; + virtual void EnsureNotPurged( void ) = 0; + virtual int Index( void ) const = 0; + virtual int GetLineNum( void ) const = 0; + virtual const char * GetFileName( void ) const = 0; + virtual void GetText( char *text ) const = 0; + virtual int GetTextLength( void ) const = 0; +// RAVEN BEGIN + virtual int GetCompressedLength( void ) const = 0; +// RAVEN END + virtual void SetText( const char *text ) = 0; + virtual bool ReplaceSourceFileText( void ) = 0; + virtual bool SourceFileChanged( void ) const = 0; + virtual void MakeDefault( void ) = 0; + virtual bool EverReferenced( void ) const = 0; +// RAVEN BEGIN + virtual void SetReferencedThisLevel( void ) = 0; +// RAVEN END + virtual bool SetDefaultText( void ) = 0; + virtual const char * DefaultDefinition( void ) const = 0; + virtual bool Parse( const char *text, const int textLength, bool noCaching ) = 0; + virtual void FreeData( void ) = 0; + virtual size_t Size( void ) const = 0; + virtual void List( void ) const = 0; + virtual void Print( void ) const = 0; +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: Validation call for detailed error-reporting + virtual bool Validate( const char *psText, int iLength, idStr &strReportTo ) const = 0; +// RAVEN END +}; + +// RAVEN BEGIN +// jscott: for guides +#define MAX_GUIDE_PARMS 20 +#define MAX_GUIDE_SHADER_SIZE 20480 + +class rvDeclGuide +{ +private: + idStr mName; + idStr mParms[MAX_GUIDE_PARMS]; + idStr mDefinition; + int mNumParms; + +public: + rvDeclGuide( idStr &name ); + ~rvDeclGuide( void ); + + const char *GetName( void ) const { return( mName.c_str() ); } + int GetNumParms( void ) const { return( mNumParms ); } + const char *GetParm( int index ) const { assert( index < mNumParms ); return( mParms[index].c_str() ); } + + void SetParm( int index, const char *value ); + void RemoveOuterBracing( void ); + void Parse( idLexer *src ); + bool Evaluate( idLexer *src, idStr &definition ); +}; +// RAVEN END + +class idDecl { +public: + // The constructor should initialize variables such that + // an immediate call to FreeData() does no harm. + idDecl( void ) { base = NULL; } + virtual ~idDecl( void ) {}; + + // Returns the name of the decl. + const char * GetName( void ) const { return base->GetName(); } + + // Returns the decl type. + declType_t GetType( void ) const { return base->GetType(); } + + // Returns the decl state which is usefull for finding out if a decl defaulted. + declState_t GetState( void ) const { return base->GetState(); } + + // Returns true if the decl was defaulted or the text was created with a call to SetDefaultText. + bool IsImplicit( void ) const { return base->IsImplicit(); } + + // The only way non-manager code can have an invalid decl is if the *ByIndex() + // call was used with forceParse = false to walk the lists to look at names + // without touching the media. + bool IsValid( void ) const { return base->IsValid(); } + + // Sets state back to unparsed. + // Used by decl editors to undo any changes to the decl. + void Invalidate( void ) { base->Invalidate(); } + + // if a pointer might possible be stale from a previous level, + // call this to have it re-parsed + void EnsureNotPurged( void ) { base->EnsureNotPurged(); } + + // Returns the index in the per-type list. + int Index( void ) const { return base->Index(); } + + // Returns the line number the decl starts. + int GetLineNum( void ) const { return base->GetLineNum(); } + + // Returns the name of the file in which the decl is defined. + const char * GetFileName( void ) const { return base->GetFileName(); } + + // Returns the decl text. + void GetText( char *text ) const { base->GetText( text ); } + + // Returns the length of the decl text. + int GetTextLength( void ) const { return base->GetTextLength(); } + + // Returns the compressed length of the decl text. + int GetCompressedLength( void ) const { return( base->GetCompressedLength() ); } + + // Sets new decl text. + void SetText( const char *text ) { base->SetText( text ); } + + // Saves out new text for the decl. + // Used by decl editors to replace the decl text in the source file. + bool ReplaceSourceFileText( void ) { return base->ReplaceSourceFileText(); } + + // Returns true if the source file changed since it was loaded and parsed. + bool SourceFileChanged( void ) const { return base->SourceFileChanged(); } + + // Frees data and makes the decl a default. + void MakeDefault( void ) { base->MakeDefault(); } + + // Returns true if the decl was ever referenced. + bool EverReferenced( void ) const { return base->EverReferenced(); } + +public: + // Sets textSource to a default text if necessary. + // This may be overridden to provide a default definition based on the + // decl name. For instance materials may default to an implicit definition + // using a texture with the same name as the decl. + virtual bool SetDefaultText( void ) { return base->SetDefaultText(); } + + // Each declaration type must have a default string that it is guaranteed + // to parse acceptably. When a decl is not explicitly found, is purged, or + // has an error while parsing, MakeDefault() will do a FreeData(), then a + // Parse() with DefaultDefinition(). The defaultDefintion should start with + // an open brace and end with a close brace. + virtual const char * DefaultDefinition( void ) const { return base->DefaultDefinition(); } + + // The manager will have already parsed past the type, name and opening brace. + // All necessary media will be touched before return. + // The manager will have called FreeData() before issuing a Parse(). + // The subclass can call MakeDefault() internally at any point if + // there are parse errors. + virtual bool Parse( const char *text, const int textLength, bool noCaching ) { return base->Parse( text, textLength, noCaching ); } + + // Frees any pointers held by the subclass. This may be called before + // any Parse(), so the constructor must have set sane values. The decl will be + // invalid after issuing this call, but it will always be immediately followed + // by a Parse() + virtual void FreeData( void ) { base->FreeData(); } + + // Returns the size of the decl in memory. + virtual size_t Size( void ) const { return base->Size(); } + + // If this isn't overridden, it will just print the decl name. + // The manager will have printed 7 characters on the line already, + // containing the reference state and index number. + virtual void List( void ) const { base->List(); } + + // The print function will already have dumped the text source + // and common data, subclasses can override this to dump more + // explicit data. + virtual void Print( void ) const { base->Print(); } + +// RAVEN BEGIN + // Rebuilds the text source of the decl for saving + virtual bool RebuildTextSource( void ) { return( base->RebuildTextSource() ); } + + // Marks this decl as referenced this level + virtual void SetReferencedThisLevel( void ) { base->SetReferencedThisLevel(); } + +// scork: for detailed error reporting + virtual bool Validate( const char *psText, int iLength, idStr &strReportTo ) const { return base->Validate( psText, iLength, strReportTo ); } +// RAVEN END + +public: + idDeclBase * base; +}; + + +template< class type > +ID_INLINE idDecl *idDeclAllocator( void ) { + return new type; +} + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +template< class type > +ID_INLINE SerializableBase *idDeclStreamAllocator( SerialInputStream &stream ) { + type *ptr = new type(stream); + + return dynamic_cast(ptr); +} +#endif + + +class idMaterial; +class idDeclTable; +class idDeclSkin; +class idSoundShader; + +// RAVEN BEGIN +// jscott: new decl types +class rvDeclMatType; +class rvDeclLipSync; +class rvDeclPlayback; +class rvDeclEffect; +class rvDeclPlayerModel; +// RAVEN END + +class idDeclManager { +public: + virtual ~idDeclManager( void ) {} + + virtual void SetInsideLoad( bool var ) = 0; + virtual bool GetInsideLoad( void ) = 0; + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual void Reload( bool force ) = 0; + + virtual void BeginLevelLoad() = 0; + virtual void EndLevelLoad() = 0; + + // Registers a new decl type. +// RAVEN BEGIN +// jsinger: Added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + virtual void RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)( void ), SerializableBase *(*streamAllocator)( SerialInputStream &stream ) ) = 0; +#else + virtual void RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)( void ) ) = 0; +#endif +// jsinger: Added to support loading all decls from a single file +#ifdef RV_SINGLE_DECL_FILE + virtual void StartLoadingDecls() = 0; + virtual void FinishLoadingDecls() = 0; + virtual void LoadDeclsFromFile() = 0; + virtual void WriteDeclFile() = 0; + virtual void FlushDecls() = 0; +#endif +// RAVEN END + +// RAVEN BEGIN +// jscott: for timing + // Registers a new folder with decl files. + virtual void RegisterDeclFolderWrapper( const char *folder, const char *extension, declType_t defaultType, bool unique = false, bool norecurse = false ) = 0; +// RAVEN END + + // Returns a checksum for all loaded decl text. + virtual int GetChecksum( void ) const = 0; + + // Returns the number of decl types. + virtual int GetNumDeclTypes( void ) const = 0; + + // Returns the type name for a decl type. + virtual const char * GetDeclNameFromType( declType_t type ) const = 0; + + // Returns the decl type for a type name. + virtual declType_t GetDeclTypeFromName( const char *typeName ) const = 0; + + // If makeDefault is true, a default decl of appropriate type will be created + // if an explicit one isn't found. If makeDefault is false, NULL will be returned + // if the decl wasn't explcitly defined. + virtual const idDecl * FindType( declType_t type, const char *name, bool makeDefault = true, bool noCaching = false ) = 0; + + virtual const idDecl* FindDeclWithoutParsing( declType_t type, const char *name, bool makeDefault = true ) = 0; + + virtual void ReloadFile( const char* filename, bool force ) = 0; + + // Returns the number of decls of the given type. + virtual int GetNumDecls( declType_t type ) = 0; + + // The complete lists of decls can be walked to populate editor browsers. + // If forceParse is set false, you can get the decl to check name / filename / etc. + // without causing it to parse the source and load media. + virtual const idDecl * DeclByIndex( declType_t type, int index, bool forceParse = true ) = 0; + + // List and print decls. + virtual void ListType( const idCmdArgs &args, declType_t type ) = 0; + virtual void PrintType( const idCmdArgs &args, declType_t type ) = 0; + + // Creates a new default decl of the given type with the given name in + // the given file used by editors to create a new decls. + virtual idDecl * CreateNewDecl( declType_t type, const char *name, const char *fileName ) = 0; + + // BSM - Added for the material editors rename capabilities + virtual bool RenameDecl( declType_t type, const char* oldName, const char* newName ) = 0; + + // When media files are loaded, a reference line can be printed at a + // proper indentation if decl_show is set + virtual void MediaPrint( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0; + + virtual void WritePrecacheCommands( idFile *f ) = 0; + +// RAVEN BEGIN +// jscott: precache any guide (template) files + virtual void ParseGuides( void ) = 0; + virtual void ShutdownGuides( void ) = 0; + virtual bool EvaluateGuide( idStr &name, idLexer *src, idStr &definition ) = 0; + virtual bool EvaluateInlineGuide( idStr &name, idStr &definition ) = 0; +// RAVEN END + // Convenience functions for specific types. + virtual const idMaterial * FindMaterial( const char *name, bool makeDefault = true ) = 0; + virtual const idDeclTable * FindTable( const char *name, bool makeDefault = true ) = 0; + virtual const idDeclSkin * FindSkin( const char *name, bool makeDefault = true ) = 0; + virtual const idSoundShader * FindSound( const char *name, bool makeDefault = true ) = 0; +// RAVEN BEGIN +// jscott: for new Raven decls + virtual const rvDeclMatType * FindMaterialType( const char *name, bool makeDefault = true ) = 0; + virtual const rvDeclLipSync * FindLipSync( const char *name, bool makeDefault = true ) = 0; + virtual const rvDeclPlayback * FindPlayback( const char *name, bool makeDefault = true ) = 0; + virtual const rvDeclEffect * FindEffect( const char *name, bool makeDefault = true ) = 0; +// RAVEN END + + virtual const idMaterial * MaterialByIndex( int index, bool forceParse = true ) = 0; + virtual const idDeclTable * TableByIndex( int index, bool forceParse = true ) = 0; + virtual const idDeclSkin * SkinByIndex( int index, bool forceParse = true ) = 0; + virtual const idSoundShader * SoundByIndex( int index, bool forceParse = true ) = 0; +// RAVEN BEGIN +// jscott: for new Raven decls + virtual const rvDeclMatType * MaterialTypeByIndex( int index, bool forceParse = true ) = 0; + virtual const rvDeclLipSync * LipSyncByIndex( int index, bool forceParse = true ) = 0; + virtual const rvDeclPlayback * PlaybackByIndex( int index, bool forceParse = true ) = 0; + virtual const rvDeclEffect * EffectByIndex( int index, bool forceParse = true ) = 0; + + virtual void StartPlaybackRecord( rvDeclPlayback *playback ) = 0; + virtual bool SetPlaybackData( rvDeclPlayback *playback, int now, int control, class rvDeclPlaybackData *pbd ) = 0; + virtual bool GetPlaybackData( const rvDeclPlayback *playback, int control, int now, int last, class rvDeclPlaybackData *pbd ) = 0; + virtual bool FinishPlayback( rvDeclPlayback *playback ) = 0; + + virtual idStr GetNewName( declType_t type, const char *base ) = 0; + virtual const char * GetDeclTypeName( declType_t type ) = 0; + virtual size_t ListDeclSummary( const idCmdArgs &args ) = 0; + virtual void RemoveDeclFile( const char *file ) = 0; +// scork: Validation call for detailed error-reporting + virtual bool Validate( declType_t type, int iIndex, idStr &strReportTo ) = 0; + virtual idDecl * AllocateDecl( declType_t type ) = 0; + +#if defined(_XENON) +// mwhitlock: Xenon texture streaming + virtual void SetLightMaterialList(idList* materialList) = 0; + virtual void SetEntityMaterialList(idList* materialList) = 0; + virtual void PurgeType( declType_t type ) = 0; +#endif +// RAVEN END +}; + +extern idDeclManager * declManager; + +template< declType_t type > +ID_INLINE void idListDecls_f( const idCmdArgs &args ) { + declManager->ListType( args, type ); +} + +template< declType_t type > +ID_INLINE void idPrintDecls_f( const idCmdArgs &args ) { + declManager->PrintType( args, type ); +} + +#endif /* !__DECLMANAGER_H__ */ diff --git a/src/framework/declMatType.cpp b/src/framework/declMatType.cpp new file mode 100644 index 0000000..aa43131 --- /dev/null +++ b/src/framework/declMatType.cpp @@ -0,0 +1,181 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=========================================================================== + + Quake 4 material-type declaration implementation. + Function ownership and ABI are recovered from quake4.pdb + (declMatType.obj). + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "declMatType.h" + +extern void R_LoadImage( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp, bool makePowerOf2 ); +extern void R_StaticFree( void *data ); + +static idCVar mat_useHitMaterials( "mat_useHitMaterials", "1", CVAR_RENDERER | CVAR_BOOL, "use prebuilt .hit material lookup images" ); +static idCVar mat_writeHitMaterials( "mat_writeHitMaterials", "0", CVAR_RENDERER | CVAR_BOOL, "write generated .hit material lookup images" ); + +static const rvDeclMatType *MT_FindMaterialTypeByTint( unsigned int tint ) { + const int count = declManager->GetNumDecls( DECL_MATERIALTYPE ); + for ( int i = 0; i < count; i++ ) { + const rvDeclMatType *materialType = declManager->MaterialTypeByIndex( i, true ); + if ( materialType && static_cast( materialType->GetTint() ) == tint ) { + return materialType; + } + } + return NULL; +} + +static const rvDeclMatType *MT_FindMaterialTypeByClosestTint( unsigned int tint ) { + const byte *wanted = reinterpret_cast( &tint ); + const rvDeclMatType *best = NULL; + int bestDistance = 0x01000001; + const int count = declManager->GetNumDecls( DECL_MATERIALTYPE ); + for ( int i = 0; i < count; i++ ) { + const rvDeclMatType *candidate = declManager->MaterialTypeByIndex( i, true ); + if ( !candidate ) { + continue; + } + const unsigned int candidateTint = static_cast( candidate->GetTint() ); + const byte *current = reinterpret_cast( &candidateTint ); + const int red = static_cast( wanted[0] ) - current[0]; + const int green = static_cast( wanted[1] ) - current[1]; + const int blue = static_cast( wanted[2] ) - current[2]; + const int distance = red * red + green * green + blue * blue; + if ( distance < bestDistance ) { + bestDistance = distance; + best = candidate; + } + } + return best; +} + +const char *rvDeclMatType::DefaultDefinition( void ) const { + return "{ description \"\" rgb 0,0,0 }"; +} + +bool rvDeclMatType::Validate( const char *text, int textLength, idStr &report ) const { + idDecl *decl = declManager->AllocateDecl( DECL_MATERIALTYPE ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl; + return valid; +} + +void rvDeclMatType::FreeData( void ) { + mDescription.Clear(); +} + +size_t rvDeclMatType::Size( void ) const { + return sizeof( *this ) + mDescription.Allocated(); +} + +bool rvDeclMatType::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( 6300 ); + if ( !src.SkipUntilString( "{" ) ) { + return false; + } + + while ( src.ReadToken( &token ) ) { + if ( token == "}" ) { + return true; + } + if ( !token.Icmp( "rgb" ) ) { + mTint[0] = static_cast( src.ParseInt() ); + src.ExpectTokenString( "," ); + mTint[1] = static_cast( src.ParseInt() ); + src.ExpectTokenString( "," ); + mTint[2] = static_cast( src.ParseInt() ); + mTint[3] = 255; + } else if ( !token.Icmp( "description" ) ) { + if ( !src.ReadToken( &token ) ) { + return false; + } + mDescription = token; + } + } + return false; +} + +byte *MT_GetMaterialTypeArray( idStr image, int &width, int &height ) { + if ( mat_useHitMaterials.GetBool() ) { + image.SetFileExtension( ".hit" ); + idFile *file = fileSystem->OpenFileRead( image ); + if ( file ) { + file->ReadInt( height ); + file->ReadInt( width ); + const int byteCount = width * height; + byte *array = static_cast( Mem_ClearedAlloc( byteCount, MA_MATERIAL ) ); + file->Read( array, byteCount ); + fileSystem->CloseFile( file ); + return array; + } + image.StripFileExtension(); + } + + byte *pixels = NULL; + R_LoadImage( image, &pixels, &width, &height, NULL, false ); + if ( !pixels ) { + common->Warning( "Failed to load hit material image %s", image.c_str() ); + return NULL; + } + + const int pixelCount = width * height; + byte *array = static_cast( Mem_ClearedAlloc( pixelCount, MA_MATERIAL ) ); + for ( int i = 0; i < pixelCount; i++ ) { + const unsigned int tint = reinterpret_cast( pixels )[i]; + const rvDeclMatType *materialType = MT_FindMaterialTypeByTint( tint ); + if ( !materialType ) { + materialType = MT_FindMaterialTypeByClosestTint( tint ); + } + array[i] = materialType ? static_cast( materialType->Index() ) : 0; + } + R_StaticFree( pixels ); + + if ( mat_writeHitMaterials.GetBool() ) { + image.SetFileExtension( ".hit" ); + idFile *file = fileSystem->OpenFileWrite( image ); + if ( file ) { + file->WriteInt( height ); + file->WriteInt( width ); + file->Write( array, pixelCount ); + fileSystem->CloseFile( file ); + } + } + return array; +} + +#if defined( _WIN32 ) && !defined( RV_BINARYDECLS ) +static_assert( sizeof( rvDeclMatType ) == 44, "rvDeclMatType ABI drift" ); +#endif diff --git a/src/framework/declMatType.h b/src/framework/declMatType.h new file mode 100644 index 0000000..71cc73a --- /dev/null +++ b/src/framework/declMatType.h @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DECLMATTYPE_H__ +#define __DECLMATTYPE_H__ + +// Defines a material type - such as concrete, metal, glass etc +#ifdef RV_BINARYDECLS +class rvDeclMatType : public idDecl, public Serializable<'RDMT'> +{ +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + rvDeclMatType( SerialInputStream &stream ); +#else +class rvDeclMatType : public idDecl +{ +public: +#endif + rvDeclMatType( void ) { *( ulong *)mTint = 0; } + ~rvDeclMatType( void ) {} + + void SetDescription( idStr &desc ) { mDescription = desc; } + const idStr &GetDescription( void ) const { return( mDescription ); } + + void SetTint( byte tint[4] ) { *( ulong *)mTint = *( ulong *)tint; } + int GetTint( void ) const { return( *( int *)mTint ); } + + float GetRed( void ) const { return( mTint[0] / 255.0f ); } + float GetGreen( void ) const { return( mTint[1] / 255.0f ); } + float GetBlue( void ) const { return( mTint[2] / 255.0f ); } + + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual size_t Size( void ) const; + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + +private: + + idStr mDescription; + byte mTint[4]; +}; + +byte *MT_GetMaterialTypeArray( idStr image, int &width, int &height ); + +#endif // __DECLMATTYPE_H__ diff --git a/src/framework/declPlayback.cpp b/src/framework/declPlayback.cpp new file mode 100644 index 0000000..8b7606b --- /dev/null +++ b/src/framework/declPlayback.cpp @@ -0,0 +1,484 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=========================================================================== + + Quake 4 playback declaration implementation. + Function ownership and ABI are recovered from quake4.pdb + (declPlayback.obj); parsing and playback behavior follow the retail dump. + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "declPlayback.h" + +class rvDeclPlaybackEditLocal : public rvDeclPlaybackEdit { +public: + virtual bool Finish( rvDeclPlayback *edit, float desiredDuration ) { + return edit->Finish( desiredDuration ); + } + virtual void SetOrigin( rvDeclPlayback *edit ) { + edit->SetOrigin(); + } + virtual void SetOrigin( rvDeclPlayback *edit, idVec3 &origin ) { + edit->SetOrigin( origin ); + } + virtual void Copy( rvDeclPlayback *edit, rvDeclPlayback *copy ) { + edit->Copy( copy ); + } +}; + +static rvDeclPlaybackEditLocal declPlaybackEditLocal; +rvDeclPlaybackEdit *declPlaybackEdit = &declPlaybackEditLocal; + +rvDeclPlayback::rvDeclPlayback( void ) { + flags = 0; + frameRate = 15.0f; + duration = 0.0f; + origin.Zero(); + bounds.Clear(); + points.SetOrder( 4 ); + angles.SetOrder( 4 ); +} + +rvDeclPlayback::~rvDeclPlayback( void ) { + FreeData(); +} + +const char *rvDeclPlayback::DefaultDefinition( void ) const { + return "{ sequence { } data { } }"; +} + +bool rvDeclPlayback::Validate( const char *text, int textLength, idStr &report ) const { + idDecl *decl = declManager->AllocateDecl( DECL_PLAYBACK ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl; + return valid; +} + +void rvDeclPlayback::FreeData( void ) { + points.Clear(); + angles.Clear(); + buttons.Clear(); +} + +void rvDeclPlayback::ParseSample( idLexer *src, idVec3 &pos, idAngles &ang ) { + idToken token; + while ( src->ReadToken( &token ) ) { + if ( token == "}" ) { + return; + } + if ( !token.Icmp( "pos" ) ) { + pos.x = src->ParseFloat(); + src->ExpectTokenString( "," ); + pos.y = src->ParseFloat(); + src->ExpectTokenString( "," ); + pos.z = src->ParseFloat(); + } else if ( !token.Icmp( "ang" ) ) { + ang.pitch = src->ParseFloat(); + src->ExpectTokenString( "," ); + ang.yaw = src->ParseFloat(); + ang.roll = 0.0f; + } else if ( !token.Icmp( "rotate" ) ) { + ang.pitch = src->ParseFloat(); + src->ExpectTokenString( "," ); + ang.yaw = src->ParseFloat(); + src->ExpectTokenString( "," ); + ang.roll = src->ParseFloat(); + } else if ( !token.Icmp( "down" ) || !token.Icmp( "up" ) || !token.Icmp( "impulse" ) ) { + src->ParseInt(); + } + } +} + +bool rvDeclPlayback::ParseSequence( idLexer *src ) { + idToken token; + src->ExpectTokenString( "sequence" ); + src->ExpectTokenString( "{" ); + while ( src->ReadToken( &token ) ) { + if ( token == "}" ) { + return true; + } + if ( !token.Icmp( "destination" ) ) { + idStr ignored; + src->ParseRestOfLine( ignored ); + } else if ( !token.Icmp( "origin" ) ) { + origin.x = src->ParseFloat(); + src->ExpectTokenString( "," ); + origin.y = src->ParseFloat(); + src->ExpectTokenString( "," ); + origin.z = src->ParseFloat(); + } else if ( !token.Icmp( "framerate" ) ) { + frameRate = src->ParseFloat(); + } else { + src->Error( "Invalid token %s in file %s.\n", token.c_str(), GetName() ); + } + } + return false; +} + +bool rvDeclPlayback::ParseData( idLexer *src ) { + idToken token; + float time = 0.0f; + idVec3 pos = vec3_origin; + idAngles ang = ang_zero; + if ( !src->ExpectTokenString( "{" ) ) { + return false; + } + while ( src->ReadToken( &token ) ) { + if ( token == "}" ) { + duration = time; + return true; + } + if ( token == "{" ) { + ParseSample( src, pos, ang ); + points.AddValue( time, pos ); + angles.AddValue( time, ang ); + time += ( frameRate > 0.0f ) ? 1.0f / frameRate : 0.0f; + } + } + return false; +} + +void rvDeclPlayback::ParseButton( idLexer *src, byte &button, rvButtonState &state ) { + idToken token; + const float time = src->ParseFloat(); + byte impulse = 0; + while ( src->ReadToken( &token ) ) { + if ( token == "}" ) { + break; + } + if ( !token.Icmp( "down" ) ) { + button |= static_cast( src->ParseInt() ); + } else if ( !token.Icmp( "up" ) ) { + button &= ~static_cast( src->ParseInt() ); + } else if ( !token.Icmp( "impulse" ) ) { + impulse = static_cast( src->ParseInt() ); + } + } + state.Init( time, button, impulse ); +} + +bool rvDeclPlayback::ParseButtons( idLexer *src ) { + idToken token; + byte button = 0; + if ( !src->ExpectTokenString( "{" ) ) { + return false; + } + while ( src->ReadToken( &token ) ) { + if ( token == "}" ) { + return true; + } + if ( token == "{" ) { + rvButtonState state; + ParseButton( src, button, state ); + buttons.Append( state ); + } + } + return false; +} + +bool rvDeclPlayback::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( 6300 ); + if ( !src.SkipUntilString( "{" ) || !ParseSequence( &src ) ) { + return false; + } + while ( src.ReadToken( &token ) ) { + if ( token == "}" ) { + SetOrigin(); + return true; + } + if ( !token.Icmp( "data" ) ) { + if ( !ParseData( &src ) ) { + return false; + } + } else if ( !token.Icmp( "buttons" ) ) { + if ( !ParseButtons( &src ) ) { + return false; + } + } + } + return false; +} + +void rvDeclPlayback::WriteSequence( idFile_Memory &file ) { + file.WriteFloatString( "\tsequence\n\t{\n" ); + file.WriteFloatString( "\t\torigin\t\t%.1f,%.1f,%.1f\n", origin.x, origin.y, origin.z ); + file.WriteFloatString( "\t\tframeRate\t%.1f\n", frameRate ); + file.WriteFloatString( "\t}\n" ); +} + +void rvDeclPlayback::WriteData( idFile_Memory &file ) { + const int count = Max( points.GetNumValues(), angles.GetNumValues() ); + if ( count == 0 || !( flags & ( PBFL_GET_POSITION | PBFL_GET_ANGLES ) ) ) { + return; + } + file.WriteFloatString( "\tdata\n\t{\n" ); + idVec3 oldPos = vec3_origin; + idAngles oldAng = ang_zero; + for ( int i = 0; i < count; i++ ) { + file.WriteFloatString( "\t\t{ " ); + if ( ( flags & PBFL_GET_POSITION ) && i < points.GetNumValues() ) { + const idVec3 pos = points.GetValue( i ); + if ( ( pos - oldPos ).LengthSqr() > Square( 0.0625f ) ) { + file.WriteFloatString( "pos %.1f,%.1f,%.1f ", pos.x, pos.y, pos.z ); + oldPos = pos; + } + } + if ( ( flags & PBFL_GET_ANGLES ) && i < angles.GetNumValues() ) { + const idAngles ang = angles.GetValue( i ); + const idAngles delta = ang - oldAng; + if ( delta.pitch * delta.pitch + delta.yaw * delta.yaw + delta.roll * delta.roll > Square( 0.0625f ) ) { + if ( ang.roll == 0.0f ) { + file.WriteFloatString( "ang %.1f,%.1f ", ang.pitch, ang.yaw ); + } else { + file.WriteFloatString( "rotate %.1f,%.1f,%.1f ", ang.pitch, ang.yaw, ang.roll ); + } + oldAng = ang; + } + } + file.WriteFloatString( "}\n" ); + } + file.WriteFloatString( "\t}\n" ); +} + +void rvDeclPlayback::WriteButtons( idFile_Memory &file ) { + if ( buttons.Num() == 0 || !( flags & PBFL_GET_BUTTONS ) ) { + return; + } + file.WriteFloatString( "\tbuttons\n\t{\n" ); + byte previous = 0; + for ( int i = 0; i < buttons.Num(); i++ ) { + const rvButtonState &state = buttons[i]; + const byte changed = previous ^ state.state; + if ( !changed && !state.impulse ) { + continue; + } + file.WriteFloatString( "\t\t{ %.3g ", state.time ); + if ( state.state & changed ) { + file.WriteFloatString( "down %d ", state.state & changed ); + } + if ( previous & changed ) { + file.WriteFloatString( "up %d ", previous & changed ); + } + if ( state.impulse ) { + file.WriteFloatString( "impulse %d ", state.impulse ); + } + file.WriteFloatString( "}\n" ); + previous = state.state; + } + file.WriteFloatString( "\t}\n" ); +} + +bool rvDeclPlayback::RebuildTextSource( void ) { + idFile_Memory file; + file.WriteFloatString( "\nplayback %s\n{\n", GetName() ); + WriteSequence( file ); + WriteData( file ); + WriteButtons( file ); + file.WriteFloatString( "}\n\n" ); + SetText( file.GetDataPtr() ); + return true; +} + +void rvDeclPlayback::SetOrigin( void ) { + if ( points.GetNumValues() == 0 ) { + bounds.Clear(); + return; + } + const idVec3 first = points.GetValue( 0 ); + origin += first; + bounds.Clear(); + for ( int i = 0; i < points.GetNumValues(); i++ ) { + const idVec3 local = points.GetValue( i ) - first; + points.SetValue( i, local ); + bounds.AddPoint( local ); + } +} + +void rvDeclPlayback::Start( void ) { + origin.Zero(); + bounds.Clear(); + duration = 0.0f; + points.Clear(); + angles.Clear(); + buttons.Clear(); + points.SetGranularity( 60 ); + angles.SetGranularity( 60 ); + buttons.SetGranularity( 60 ); +} + +void rvDeclPlayback::Copy( rvDeclPlayback *playback ) { + if ( !playback ) { + return; + } + flags = playback->flags & ~PBFL_ED_MASK; + frameRate = playback->frameRate; + duration = playback->duration; + origin = playback->origin; + bounds = playback->bounds; + points = playback->points; + angles = playback->angles; + buttons = playback->buttons; +} + +bool rvDeclPlayback::SetCurrentData( float localTime, int control, rvDeclPlaybackData *data ) { + if ( !data ) { + return false; + } + if ( control & PBFL_GET_POSITION ) { + points.AddValue( localTime, data->GetPosition() ); + } + if ( control & PBFL_GET_ANGLES ) { + angles.AddValue( localTime, data->GetAngles() ); + } + if ( control & PBFL_GET_BUTTONS ) { + rvButtonState state; + state.Init( localTime, data->GetButtons(), data->GetImpulse() ); + buttons.Append( state ); + } + duration = Max( duration, localTime ); + return true; +} + +bool rvDeclPlayback::GetCurrentOffset( float localTime, idVec3 &pos ) const { + if ( points.GetNumValues() == 0 ) { + pos.Zero(); + return false; + } + pos = points.GetCurrentValue( localTime ); + return true; +} + +bool rvDeclPlayback::GetCurrentAngles( float localTime, idAngles &ang ) const { + if ( angles.GetNumValues() == 0 ) { + ang.Zero(); + return false; + } + ang = angles.GetCurrentValue( localTime ); + return true; +} + +bool rvDeclPlayback::GetCurrentData( int control, float localTime, float lastTime, rvDeclPlaybackData *data ) const { + if ( !data || points.GetNumValues() == 0 ) { + return true; + } + if ( control & PBFL_GET_POSITION ) { + data->SetPosition( points.GetCurrentValue( localTime ) + origin ); + } + if ( control & ( PBFL_GET_VELOCITY | PBFL_GET_ANGLES_FROM_VEL ) ) { + data->SetVelocity( points.GetCurrentFirstDerivative( localTime ) ); + } + if ( control & PBFL_GET_ACCELERATION ) { + data->SetAcceleration( points.GetCurrentSecondDerivative( localTime ) ); + } + if ( control & PBFL_GET_ANGLES ) { + data->SetAngles( angles.GetCurrentValue( localTime ) ); + } else if ( control & PBFL_GET_ANGLES_FROM_VEL ) { + data->SetAngles( data->GetVelocity().ToAngles() ); + } + if ( control & PBFL_GET_BUTTONS ) { + byte previous = 0; + for ( int i = 0; i < buttons.Num(); i++ ) { + const rvButtonState &state = buttons[i]; + if ( state.time > localTime ) { + break; + } + if ( state.time > lastTime ) { + const byte changed = previous ^ state.state; + if ( state.state & changed ) { + data->SetChanged( state.state & changed ); + data->CallCallback( PBCB_BUTTON_DOWN, state.time ); + } + if ( previous & changed ) { + data->SetChanged( previous & changed ); + data->CallCallback( PBCB_BUTTON_UP, state.time ); + } + if ( state.impulse ) { + data->SetImpulse( state.impulse ); + data->CallCallback( PBCB_IMPULSE, state.time ); + } + } + previous = state.state; + } + data->SetButtons( previous ); + } + return localTime > duration; +} + +bool rvDeclPlayback::Finish( float desiredDuration ) { + SetOrigin(); + rvDeclPlayback source; + source.Copy( this ); + FreeData(); + flags = source.flags; + frameRate = source.frameRate; + origin = source.origin; + bounds = source.bounds; + const float timeScale = ( desiredDuration >= 0.0f && desiredDuration > 0.0f ) ? source.duration / desiredDuration : 1.0f; + duration = ( desiredDuration >= 0.0f ) ? desiredDuration : source.duration; + + byte previous = 0; + for ( int i = 0; i < source.buttons.Num(); i++ ) { + const rvButtonState &oldState = source.buttons[i]; + if ( oldState.state != previous || oldState.impulse ) { + rvButtonState newState = oldState; + newState.time /= timeScale; + buttons.Append( newState ); + previous = oldState.state; + } + } + + if ( frameRate <= 0.0f ) { + return false; + } + const float step = 1.0f / frameRate; + for ( float newTime = 0.0f; newTime <= duration + step * 0.5f; newTime += step ) { + const float oldTime = Min( source.duration, newTime * timeScale ); + if ( source.points.GetNumValues() ) { + points.AddValue( newTime, source.points.GetCurrentValue( oldTime ) ); + } + if ( source.angles.GetNumValues() ) { + angles.AddValue( newTime, source.angles.GetCurrentValue( oldTime ) ); + } + } + return true; +} + +size_t rvDeclPlayback::Size( void ) const { + return sizeof( *this ) + buttons.Allocated() + sizeof( idVec3 ) * points.GetNumValues() + sizeof( idAngles ) * angles.GetNumValues(); +} + +#if defined( _WIN32 ) && !defined( RV_BINARYDECLS ) +static_assert( sizeof( rvDeclPlaybackData ) == 60, "rvDeclPlaybackData ABI drift" ); +static_assert( sizeof( rvDeclPlayback ) == 184, "rvDeclPlayback ABI drift" ); +#endif diff --git a/src/framework/declPlayback.h b/src/framework/declPlayback.h new file mode 100644 index 0000000..ab1da91 --- /dev/null +++ b/src/framework/declPlayback.h @@ -0,0 +1,208 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DECLPLAYBACK_H__ +#define __DECLPLAYBACK_H__ + +// WARNING: These must stay mirrored in both Q4Monster.state +#define PBFL_GET_POSITION BIT( 0 ) // Stored in playback file +#define PBFL_GET_ANGLES BIT( 1 ) +#define PBFL_GET_BUTTONS BIT( 2 ) + +#define PBFL_GET_VELOCITY BIT( 4 ) // Derived from data +#define PBFL_GET_ACCELERATION BIT( 5 ) +#define PBFL_GET_ANGLES_FROM_VEL BIT( 6 ) + +#define PBFL_AT_DEST BIT( 7 ) +#define PBFL_RELATIVE_POSITION BIT( 8 ) + +#define PBFL_ED_MODIFIED BIT( 29 ) +#define PBFL_ED_NEW BIT( 30 ) +#define PBFL_ED_CHECKEDIN BIT( 31 ) + +#define PBFL_ED_MASK ( PBFL_ED_MODIFIED | PBFL_ED_NEW | PBFL_ED_CHECKEDIN ) + +#define PBCB_NONE 0 +#define PBCB_BUTTON_DOWN 1 +#define PBCB_BUTTON_UP 2 +#define PBCB_IMPULSE 3 + +typedef void ( *pbCallback_t )( int type, float time, const void *data ); + +class rvDeclPlaybackData +{ +public: + void Init( void ) { entity = NULL; Callback = NULL; position.Zero(); velocity.Zero(); acceleration.Zero(); angles.Zero(); changed = 0; button = 0; impulse = 0; } + + void SetPosition( const idVec3 &pos ) { position = pos; } + void SetVelocity( const idVec3 &vel ) { velocity = vel; } + void SetAcceleration( const idVec3 &accel ) { acceleration = accel; } + void SetAngles( const idAngles &ang ) { angles = ang; } + void SetChanged( const byte chg ) { changed = chg; } + void SetButtons( const byte btn ) { button = btn; } + void SetImpulse( const byte imp ) { impulse = imp; } + + const idVec3 &GetPosition( void ) const { return( position ); } + const idVec3 &GetVelocity( void ) const { return( velocity ); } + const idVec3 &GetAcceleration( void ) const { return( acceleration ); } + const idAngles &GetAngles( void ) const { return( angles ); } + byte GetChanged( void ) const { return( changed ); } + byte GetButtons( void ) const { return( button ); } + byte GetImpulse( void ) const { return( impulse ); } + + class idEntity *GetEntity( void ) const { return( entity ); } + + void SetCallback( class idEntity *ent, pbCallback_t cb ) { entity = ent; Callback = cb; } + void CallCallback( int type, float time ) { if( Callback ) { Callback( type, time, this ); } } +private: + class idEntity *entity; + pbCallback_t Callback; + + idVec3 position; + idVec3 velocity; + idVec3 acceleration; + idAngles angles; + byte changed; + byte button; + byte impulse; +}; + +class rvButtonState +{ +public: + void Init( float t, byte b = 0, byte i = 0 ) { time = t; state = b; impulse = i; } + + float time; + byte state; + byte impulse; +}; + +#ifdef RV_BINARYDECLS +class rvDeclPlayback : public idDecl, public Serializable<'RDP '> +{ +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + rvDeclPlayback( SerialInputStream &stream ); +#else +class rvDeclPlayback : public idDecl +{ +#endif +public: + rvDeclPlayback( void ); + ~rvDeclPlayback( void ); + + void SetFlag( bool on, int flag ) { on ? flags |= flag : flags &= ~flag; } + + bool GetHasPositions( void ) const { return( !!( flags & PBFL_GET_POSITION ) ); } + bool GetHasAngles( void ) const { return( !!( flags & PBFL_GET_ANGLES ) ); } + bool GetHasButtons( void ) const { return( !!( flags & PBFL_GET_BUTTONS ) ); } + bool GetEditorModified( void ) const { return( !!( flags & PBFL_ED_MODIFIED ) ); } + bool GetEditorNew( void ) const { return( !!( flags & PBFL_ED_NEW ) ); } + bool GetEditorCheckedIn( void ) const { return( !!( flags & PBFL_ED_CHECKEDIN ) ); } + + void SetHasPositions( bool pos ) { SetFlag( pos, PBFL_GET_POSITION ); } + void SetHasAngles( bool ang ) { SetFlag( ang, PBFL_GET_ANGLES ); } + void SetHasButtons( bool btn ) { SetFlag( btn, PBFL_GET_BUTTONS ); } + void SetEditorModified( bool em ) { SetFlag( em, PBFL_ED_MODIFIED ); } + void SetEditorNew( bool en ) { SetFlag( en, PBFL_ED_NEW ); } + void SetEditorCheckedIn( bool eci ) { SetFlag( eci, PBFL_ED_CHECKEDIN ); } + + int GetFlags( void ) const { return( flags ); } + void SetFlags( int in ) { flags = in; } + + float GetFrameRate( void ) const { return( frameRate ); } + void SetFrameRate( float in ) { frameRate = in; } + + idVec3 GetOrigin( void ) const { return( origin ); } + void SetOrigin( idVec3 &in ) { origin = in; } + + float GetDuration( void ) const { return( duration ); } + void SetDuration( float dur ) { duration = dur; } + + idBounds GetBounds( void ) const { return( bounds ); } + + void ParseSample( idLexer *src, idVec3 &pos, idAngles &ang ); + void WriteData( idFile_Memory &f ); + void WriteButtons( idFile_Memory &f ); + void WriteSequence( idFile_Memory &f ); + + bool ParseData( idLexer *src ); + void ParseButton( idLexer *src, byte &button, rvButtonState &state ); + bool ParseSequence( idLexer *src ); + bool ParseButtons( idLexer *src ); + + void Copy( rvDeclPlayback *pb ); + void SetOrigin( void ); + void Start( void ); + bool Finish( float desiredDuration = -1.0f ); + + bool SetCurrentData( float localTime, int control, rvDeclPlaybackData *pbd ); + bool GetCurrentOffset( float localTime, idVec3 &pos ) const; + bool GetCurrentAngles( float localTime, idAngles &ang ) const; + bool GetCurrentData( int control, float localTime, float lastTime, rvDeclPlaybackData *pbd ) const; + + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual bool RebuildTextSource( void ); + virtual size_t Size( void ) const; + +// RAVEN BEGIN +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + + idCurve_UniformCubicBSpline &GetPoints( void ) { return( points ); } + idCurve_UniformCubicBSpline &GetAngles( void ) { return( angles ); } + idList &GetButtons( void ) { return( buttons ); } + +private: + + int flags; + float frameRate; + float duration; + idVec3 origin; + idBounds bounds; + + idCurve_UniformCubicBSpline points; + idCurve_UniformCubicBSpline angles; + idList buttons; +}; + +class rvDeclPlaybackEdit +{ +public: + virtual ~rvDeclPlaybackEdit() {} + virtual bool Finish( rvDeclPlayback *edit, float desiredDuration ) = 0; + virtual void SetOrigin( rvDeclPlayback *edit ) = 0; + virtual void SetOrigin( rvDeclPlayback *edit, idVec3 &origin ) = 0; + virtual void Copy( rvDeclPlayback *edit, rvDeclPlayback *copy ) = 0; +}; + +extern rvDeclPlaybackEdit *declPlaybackEdit; + +#endif // __DECLPLAYBACK_H__ diff --git a/src/framework/declSkin.h b/src/framework/declSkin.h new file mode 100644 index 0000000..a088cd8 --- /dev/null +++ b/src/framework/declSkin.h @@ -0,0 +1,98 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLSKIN_H__ +#define __DECLSKIN_H__ + +/* +=============================================================================== + + idDeclSkin + +=============================================================================== +*/ + +typedef struct { + const idMaterial * from; // 0 == any unmatched shader + const idMaterial * to; +} skinMapping_t; + +// RAVEN BEGIN +// jsinger: allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclSkin : public idDecl, public Serializable<'DSKN'> { +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream) const; + virtual void AddReferences() const; + idDeclSkin(SerialInputStream &stream); +#else +class idDeclSkin : public idDecl { +#endif +public: + idDeclSkin(); + virtual size_t Size( void ) const; + virtual bool SetDefaultText( void ); + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + void StreamAllSkinTargets(bool inBackground); + void GetSkinTargetsList(idList& outList) const; +#endif +// RAVEN END + const idMaterial * RemapShaderBySkin( const idMaterial *shader ) const; + + // model associations are just for the preview dialog in the editor +// RAVEN BEGIN +// jscott: inlined for access from tools dll + const int GetNumModelAssociations() const { return( associatedModels.Num() ); } +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: validation member for more detailed error-checks + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + const char * GetAssociatedModel( int index ) const; + +private: + idList mappings; + idStrList associatedModels; +}; + +// RAVEN BEGIN +// jscott: inlined for access from tools dll +ID_INLINE const char *idDeclSkin::GetAssociatedModel( int index ) const { + if ( index >= 0 && index < associatedModels.Num() ) { + return associatedModels[ index ]; + } + return ""; +} +// RAVEN END + +#endif /* !__DECLSKIN_H__ */ diff --git a/src/framework/declTable.h b/src/framework/declTable.h new file mode 100644 index 0000000..5354431 --- /dev/null +++ b/src/framework/declTable.h @@ -0,0 +1,82 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __DECLTABLE_H__ +#define __DECLTABLE_H__ + +/* +=============================================================================== + + tables are used to map a floating point input value to a floating point + output value, with optional wrap / clamp and interpolation + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclTable : public idDecl, public Serializable<'DTAB'> { +public: +// jsinger: allow exporting of this decl type in a preparsed form + virtual void Write( SerialOutputStream &stream) const; + virtual void AddReferences() const; + idDeclTable( SerialInputStream &stream); +#else +class idDeclTable : public idDecl { +#endif +public: + idDeclTable(); + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: for BSE + float GetMaxValue( void ) const { return( maxValue ); } + float GetMinValue( void ) const { return( minValue ); } +// bdube: made virtual so it can be accessed in game + virtual float TableLookup( float index ) const; +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + +private: + bool clamp; + bool snap; +// RAVEN BEGIN +// jscott: for BSE + float minValue; + float maxValue; +// RAVEN END + idList values; +}; + +#endif /* !__DECLTABLE_H__ */ diff --git a/src/framework/declaf.cpp b/src/framework/declaf.cpp new file mode 100644 index 0000000..461831c --- /dev/null +++ b/src/framework/declaf.cpp @@ -0,0 +1,1805 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( idDeclAF ) == 0xB0, "retail idDeclAF ABI drift" ); +static_assert( sizeof( idDeclAF_Body ) == 0x260, "retail idDeclAF_Body ABI drift" ); +static_assert( sizeof( idDeclAF_Constraint ) == 0x288, "retail idDeclAF_Constraint ABI drift" ); +#endif + +/* +=============================================================================== + + idDeclAF + +=============================================================================== +*/ + +/* +================ +idAFVector::idAFVector +================ +*/ +idAFVector::idAFVector( void ) { + type = VEC_COORDS; + vec.Zero(); + negate = false; +} + +/* +================ +idDeclAF_Body::idDeclAF_Body +================ +*/ +idDeclAF_Body::idDeclAF_Body() { + jointMod = DECLAF_JOINTMOD_AXIS; + modelType = TRM_BOX; + numSides = 0; + width = 0.0f; + density = 0.0f; + angles.Zero(); + contents = 0; + clipMask = 0; + selfCollision = false; + inertiaScale.Zero(); + linearFriction = 0.0f; + angularFriction = 0.0f; + contactFriction = 0.0f; +} + +/* +================ +idDeclAF_Constraint::idDeclAF_Constraint +================ +*/ +idDeclAF_Constraint::idDeclAF_Constraint() { + type = DECLAF_CONSTRAINT_INVALID; + friction = 0.0f; + stretch = 0.0f; + compress = 0.0f; + damping = 0.0f; + restLength = 0.0f; + minLength = 0.0f; + maxLength = 0.0f; + limit = LIMIT_NONE; + limitAngles[0] = 0.0f; + limitAngles[1] = 0.0f; + limitAngles[2] = 0.0f; +} + +/* +================ +idAFVector::Parse +================ +*/ +bool idAFVector::Parse( idLexer &src ) { + idToken token; + + if ( !src.ReadToken( &token ) ) { + return false; + } + + if ( token == "-" ) { + negate = true; + if ( !src.ReadToken( &token ) ) { + return false; + } + } + else { + negate = false; + } + + if ( token == "(" ) { + type = idAFVector::VEC_COORDS; + vec.x = src.ParseFloat(); + src.ExpectTokenString( "," ); + vec.y = src.ParseFloat(); + src.ExpectTokenString( "," ); + vec.z = src.ParseFloat(); + src.ExpectTokenString( ")" ); + } + else if ( token == "joint" ) { + type = idAFVector::VEC_JOINT; + src.ExpectTokenString( "(" ); + src.ReadToken( &token ); + joint1 = token; + src.ExpectTokenString( ")" ); + } + else if ( token == "bonecenter" ) { + type = idAFVector::VEC_BONECENTER; + src.ExpectTokenString( "(" ); + src.ReadToken( &token ); + joint1 = token; + src.ExpectTokenString( "," ); + src.ReadToken( &token ); + joint2 = token; + src.ExpectTokenString( ")" ); + } + else if ( token == "bonedir" ) { + type = idAFVector::VEC_BONEDIR; + src.ExpectTokenString( "(" ); + src.ReadToken( &token ); + joint1 = token; + src.ExpectTokenString( "," ); + src.ReadToken( &token ); + joint2 = token; + src.ExpectTokenString( ")" ); + } + else { + src.Error( "unknown token %s in vector", token.c_str() ); + return false; + } + + return true; +} + +/* +================ +idAFVector::Finish +================ +*/ +bool idAFVector::Finish( const char *fileName, const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const { + idMat3 axis; + idVec3 start, end; + + switch( type ) { + case idAFVector::VEC_COORDS: { + break; + } + case idAFVector::VEC_JOINT: { + if ( !GetJointTransform( model, frame, joint1, vec, axis ) ) { + common->Warning( "invalid joint %s in joint() in '%s'", joint1.c_str(), fileName ); + vec.Zero(); + } + break; + } + case idAFVector::VEC_BONECENTER: { + if ( !GetJointTransform( model, frame, joint1, start, axis ) ) { + common->Warning( "invalid joint %s in bonecenter() in '%s'", joint1.c_str(), fileName ); + start.Zero(); + } + if ( !GetJointTransform( model, frame, joint2, end, axis ) ) { + common->Warning( "invalid joint %s in bonecenter() in '%s'", joint2.c_str(), fileName ); + end.Zero(); + } + vec = ( start + end ) * 0.5f; + break; + } + case idAFVector::VEC_BONEDIR: { + if ( !GetJointTransform( model, frame, joint1, start, axis ) ) { + common->Warning( "invalid joint %s in bonedir() in '%s'", joint1.c_str(), fileName ); + start.Zero(); + } + if ( !GetJointTransform( model, frame, joint2, end, axis ) ) { + common->Warning( "invalid joint %s in bonedir() in '%s'", joint2.c_str(), fileName ); + end.Zero(); + } + vec = ( end - start ); + break; + } + default: { + vec.Zero(); + break; + } + } + + if ( negate ) { + vec = -vec; + } + + return true; +} + +/* +================ +idAFVector::Write +================ +*/ +bool idAFVector::Write( idFile *f ) const { + + if ( negate ) { + f->WriteFloatString( "-" ); + } + switch( type ) { + case idAFVector::VEC_COORDS: { + f->WriteFloatString( "( %f, %f, %f )", vec.x, vec.y, vec.z ); + break; + } + case idAFVector::VEC_JOINT: { + f->WriteFloatString( "joint( \"%s\" )", joint1.c_str() ); + break; + } + case idAFVector::VEC_BONECENTER: { + f->WriteFloatString( "bonecenter( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + case idAFVector::VEC_BONEDIR: { + f->WriteFloatString( "bonedir( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + default: { + break; + } + } + return true; +} + +/* +================ +idAFVector::ToString +================ +*/ +const char *idAFVector::ToString( idStr &str, const int precision ) { + + switch( type ) { + case idAFVector::VEC_COORDS: { + char format[128]; + sprintf( format, "( %%.%df, %%.%df, %%.%df )", precision, precision, precision ); + sprintf( str, format, vec.x, vec.y, vec.z ); + break; + } + case idAFVector::VEC_JOINT: { + sprintf( str, "joint( \"%s\" )", joint1.c_str() ); + break; + } + case idAFVector::VEC_BONECENTER: { + sprintf( str, "bonecenter( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + case idAFVector::VEC_BONEDIR: { + sprintf( str, "bonedir( \"%s\", \"%s\" )", joint1.c_str(), joint2.c_str() ); + break; + } + default: { + break; + } + } + if ( negate ) { + str = "-" + str; + } + return str.c_str(); +} + +/* +================ +idDeclAF_Body::SetDefault +================ +*/ +void idDeclAF_Body::SetDefault( const idDeclAF *file ) { + name = "noname"; + modelType = TRM_BOX; + v1.type = idAFVector::VEC_COORDS; + v1.ToVec3().x = v1.ToVec3().y = v1.ToVec3().z = -10.0f; + v2.type = idAFVector::VEC_COORDS; + v2.ToVec3().x = v2.ToVec3().y = v2.ToVec3().z = 10.0f; + numSides = 3; + origin.ToVec3().Zero(); + angles.Zero(); + density = 0.2f; + inertiaScale.Identity(); + linearFriction = file->defaultLinearFriction; + angularFriction = file->defaultAngularFriction; + contactFriction = file->defaultContactFriction; + contents = file->contents; + clipMask = file->clipMask; + selfCollision = file->selfCollision; + frictionDirection.ToVec3().Zero(); + contactMotorDirection.ToVec3().Zero(); + jointName = "origin"; + jointMod = DECLAF_JOINTMOD_AXIS; + containedJoints = "*origin"; +} + +/* +================ +idDeclAF_Constraint::SetDefault +================ +*/ +void idDeclAF_Constraint::SetDefault( const idDeclAF *file ) { + name = "noname"; + type = DECLAF_CONSTRAINT_UNIVERSALJOINT; + if ( file->bodies.Num() ) { + body1 = file->bodies[0]->name; + } + else { + body1 = "world"; + } + body2 = "world"; + friction = file->defaultConstraintFriction; + anchor.ToVec3().Zero(); + anchor2.ToVec3().Zero(); + axis.ToVec3().Set( 1.0f, 0.0f, 0.0f ); + shaft[0].ToVec3().Set( 0.0f, 0.0f, -1.0f ); + shaft[1].ToVec3().Set( 0.0f, 0.0f, 1.0f ); + limit = idDeclAF_Constraint::LIMIT_NONE; + limitAngles[0] = + limitAngles[1] = + limitAngles[2] = 0.0f; + limitAxis.ToVec3().Set( 0.0f, 0.0f, -1.0f ); +} + +/* +================ +idDeclAF::WriteBody +================ +*/ +bool idDeclAF::WriteBody( idFile *f, const idDeclAF_Body &body ) const { + idStr str; + + f->WriteFloatString( "\nbody \"%s\" {\n", body.name.c_str() ); + f->WriteFloatString( "\tjoint \"%s\"\n", body.jointName.c_str() ); + f->WriteFloatString( "\tmod %s\n", JointModToString( body.jointMod ) ); + switch( body.modelType ) { + case TRM_BOX: { + f->WriteFloatString( "\tmodel box( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( " )\n" ); + break; + } + case TRM_OCTAHEDRON: { + f->WriteFloatString( "\tmodel octahedron( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( " )\n" ); + break; + } + case TRM_DODECAHEDRON: { + f->WriteFloatString( "\tmodel dodecahedron( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( " )\n" ); + break; + } + case TRM_CYLINDER: { + f->WriteFloatString( "\tmodel cylinder( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( ", %d )\n", body.numSides ); + break; + } + case TRM_CONE: { + f->WriteFloatString( "\tmodel cone( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( ", %d )\n", body.numSides ); + break; + } + case TRM_BONE: { + f->WriteFloatString( "\tmodel bone( " ); + body.v1.Write( f ); + f->WriteFloatString( ", " ); + body.v2.Write( f ); + f->WriteFloatString( ", %f )\n", body.width ); + break; + } + default: + assert( 0 ); + break; + } + f->WriteFloatString( "\torigin " ); + body.origin.Write( f ); + f->WriteFloatString( "\n" ); + if ( body.angles != ang_zero ) { + f->WriteFloatString( "\tangles ( %f, %f, %f )\n", body.angles.pitch, body.angles.yaw, body.angles.roll ); + } + f->WriteFloatString( "\tdensity %f\n", body.density ); + if ( body.inertiaScale != mat3_identity ) { + const idMat3 &ic = body.inertiaScale; + f->WriteFloatString( "\tinertiaScale (%f %f %f %f %f %f %f %f %f)\n", + ic[0][0], ic[0][1], ic[0][2], + ic[1][0], ic[1][1], ic[1][2], + ic[2][0], ic[2][1], ic[2][2] ); + } + if ( body.linearFriction != -1 ) { + f->WriteFloatString( "\tfriction %f, %f, %f\n", body.linearFriction, body.angularFriction, body.contactFriction ); + } + f->WriteFloatString( "\tcontents %s\n", ContentsToString( body.contents, str ) ); + f->WriteFloatString( "\tclipMask %s\n", ContentsToString( body.clipMask, str ) ); + f->WriteFloatString( "\tselfCollision %d\n", body.selfCollision ); + if ( body.frictionDirection.ToVec3() != vec3_origin ) { + f->WriteFloatString( "\tfrictionDirection " ); + body.frictionDirection.Write( f ); + f->WriteFloatString( "\n" ); + } + if ( body.contactMotorDirection.ToVec3() != vec3_origin ) { + f->WriteFloatString( "\tcontactMotorDirection " ); + body.contactMotorDirection.Write( f ); + f->WriteFloatString( "\n" ); + } + f->WriteFloatString( "\tcontainedJoints \"%s\"\n", body.containedJoints.c_str() ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteFixed +================ +*/ +bool idDeclAF::WriteFixed( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nfixed \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteBallAndSocketJoint +================ +*/ +bool idDeclAF::WriteBallAndSocketJoint( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nballAndSocketJoint \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) { + f->WriteFloatString( "\tconeLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f, ", c.limitAngles[0] ); + c.shaft[0].Write( f ); + f->WriteFloatString( "\n" ); + } + else if ( c.limit == idDeclAF_Constraint::LIMIT_PYRAMID ) { + f->WriteFloatString( "\tpyramidLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f, %f, %f, ", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] ); + c.shaft[0].Write( f ); + f->WriteFloatString( "\n" ); + } + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteUniversalJoint +================ +*/ +bool idDeclAF::WriteUniversalJoint( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nuniversalJoint \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tshafts " ); + c.shaft[0].Write( f ); + f->WriteFloatString( ", " ); + c.shaft[1].Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) { + f->WriteFloatString( "\tconeLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f\n", c.limitAngles[0] ); + } + else if ( c.limit == idDeclAF_Constraint::LIMIT_PYRAMID ) { + f->WriteFloatString( "\tpyramidLimit " ); + c.limitAxis.Write( f ); + f->WriteFloatString( ", %f, %f, %f\n", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] ); + } + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteHinge +================ +*/ +bool idDeclAF::WriteHinge( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nhinge \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\taxis " ); + c.axis.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + if ( c.limit == idDeclAF_Constraint::LIMIT_CONE ) { + f->WriteFloatString( "\tlimit " ); + f->WriteFloatString( "%f, %f, %f", c.limitAngles[0], c.limitAngles[1], c.limitAngles[2] ); + f->WriteFloatString( "\n" ); + } + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteSlider +================ +*/ +bool idDeclAF::WriteSlider( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nslider \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\taxis " ); + c.axis.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteSpring +================ +*/ +bool idDeclAF::WriteSpring( idFile *f, const idDeclAF_Constraint &c ) const { + f->WriteFloatString( "\nspring \"%s\" {\n", c.name.c_str() ); + f->WriteFloatString( "\tbody1 \"%s\"\n", c.body1.c_str() ); + f->WriteFloatString( "\tbody2 \"%s\"\n", c.body2.c_str() ); + f->WriteFloatString( "\tanchor1 " ); + c.anchor.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tanchor2 " ); + c.anchor2.Write( f ); + f->WriteFloatString( "\n" ); + f->WriteFloatString( "\tfriction %f\n", c.friction ); + f->WriteFloatString( "\tstretch %f\n", c.stretch ); + f->WriteFloatString( "\tcompress %f\n", c.compress ); + f->WriteFloatString( "\tdamping %f\n", c.damping ); + f->WriteFloatString( "\trestLength %f\n", c.restLength ); + f->WriteFloatString( "\tminLength %f\n", c.minLength ); + f->WriteFloatString( "\tmaxLength %f\n", c.maxLength ); + f->WriteFloatString( "}\n" ); + return true; +} + +/* +================ +idDeclAF::WriteConstraint +================ +*/ +bool idDeclAF::WriteConstraint( idFile *f, const idDeclAF_Constraint &c ) const { + switch( c.type ) { + case DECLAF_CONSTRAINT_FIXED: + return WriteFixed( f, c ); + case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT: + return WriteBallAndSocketJoint( f, c ); + case DECLAF_CONSTRAINT_UNIVERSALJOINT: + return WriteUniversalJoint( f, c ); + case DECLAF_CONSTRAINT_HINGE: + return WriteHinge( f, c ); + case DECLAF_CONSTRAINT_SLIDER: + return WriteSlider( f, c ); + case DECLAF_CONSTRAINT_SPRING: + return WriteSpring( f, c ); + default: + break; + } + return false; +} + +/* +================ +idDeclAF::WriteSettings +================ +*/ +bool idDeclAF::WriteSettings( idFile *f ) const { + idStr str; + + f->WriteFloatString( "\nsettings {\n" ); + f->WriteFloatString( "\tmodel \"%s\"\n", model.c_str() ); + f->WriteFloatString( "\tskin \"%s\"\n", skin.c_str() ); + f->WriteFloatString( "\tfriction %f, %f, %f, %f\n", defaultLinearFriction, defaultAngularFriction, defaultContactFriction, defaultConstraintFriction ); + f->WriteFloatString( "\tsuspendSpeed %f, %f, %f, %f\n", suspendVelocity[0], suspendVelocity[1], suspendAcceleration[0], suspendAcceleration[1] ); + f->WriteFloatString( "\tnoMoveTime %f\n", noMoveTime ); + f->WriteFloatString( "\tnoMoveTranslation %f\n", noMoveTranslation ); + f->WriteFloatString( "\tnoMoveRotation %f\n", noMoveRotation ); + f->WriteFloatString( "\tminMoveTime %f\n", minMoveTime ); + f->WriteFloatString( "\tmaxMoveTime %f\n", maxMoveTime ); + f->WriteFloatString( "\ttotalMass %f\n", totalMass ); + f->WriteFloatString( "\tcontents %s\n", ContentsToString( contents, str ) ); + f->WriteFloatString( "\tclipMask %s\n", ContentsToString( clipMask, str ) ); + f->WriteFloatString( "\tselfCollision %d\n", selfCollision ); + f->WriteFloatString( "}\n" ); + return true; +} + + +/* +================ +idDeclAF::RebuildTextSource +================ +*/ +bool idDeclAF::RebuildTextSource( void ) { + int i; + idFile_Memory f; + + f.WriteFloatString("\n\n/*\n" + "\tGenerated by the Articulated Figure Editor.\n" + "\tDo not edit directly but launch the game and type 'editAFs' on the console.\n" + "*/\n" ); + + f.WriteFloatString( "\narticulatedFigure %s {\n", GetName() ); + + if ( !WriteSettings( &f ) ) { + return false; + } + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !WriteBody( &f, *bodies[i] ) ) { + return false; + } + } + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( !WriteConstraint( &f, *constraints[i] ) ) { + return false; + } + } + + f.WriteFloatString( "\n}" ); + + SetText( f.GetDataPtr() ); + + return true; +} + +/* +================ +idDeclAF::Save +================ +*/ +bool idDeclAF::Save( void ) { + RebuildTextSource(); + ReplaceSourceFileText(); + modified = false; + return true; +} + +/* +================ +idDeclAF::ContentsFromString +================ +*/ +int idDeclAF::ContentsFromString( const char *str ) { + int c; + idToken token; + idLexer src( str, idStr::Length( str ), "idDeclAF::ContentsFromString" ); + + c = 0; + while( src.ReadToken( &token ) ) { + if ( token.Icmp( "none" ) == 0 ) { + c = 0; + } + else if ( token.Icmp( "solid" ) == 0 ) { + c |= CONTENTS_SOLID; + } + else if ( token.Icmp( "body" ) == 0 ) { + c |= CONTENTS_BODY; + } + else if ( token.Icmp( "corpse" ) == 0 ) { + c |= CONTENTS_CORPSE; + } + else if ( token.Icmp( "playerclip" ) == 0 ) { + c |= CONTENTS_PLAYERCLIP; + } + else if ( token.Icmp( "monsterclip" ) == 0 ) { + c |= CONTENTS_MONSTERCLIP; + } + else if ( token == "," ) { + continue; + } + else { + return c; + } + } + return c; +} + +/* +================ +idDeclAF::ContentsToString +================ +*/ +const char *idDeclAF::ContentsToString( const int contents, idStr &str ) { + str = ""; + if ( contents & CONTENTS_SOLID ) { + if ( str.Length() ) str += ", "; + str += "solid"; + } + if ( contents & CONTENTS_BODY ) { + if ( str.Length() ) str += ", "; + str += "body"; + } + if ( contents & CONTENTS_CORPSE ) { + if ( str.Length() ) str += ", "; + str += "corpse"; + } + if ( contents & CONTENTS_PLAYERCLIP ) { + if ( str.Length() ) str += ", "; + str += "playerclip"; + } + if ( contents & CONTENTS_MONSTERCLIP ) { + if ( str.Length() ) str += ", "; + str += "monsterclip"; + } + if ( str[0] == '\0' ) { + str = "none"; + } + return str.c_str(); +} + +/* +================ +idDeclAF::JointModFromString +================ +*/ +declAFJointMod_t idDeclAF::JointModFromString( const char *str ) { + if ( idStr::Icmp( str, "orientation" ) == 0 ) { + return DECLAF_JOINTMOD_AXIS; + } + if ( idStr::Icmp( str, "position" ) == 0 ) { + return DECLAF_JOINTMOD_ORIGIN; + } + if ( idStr::Icmp( str, "both" ) == 0 ) { + return DECLAF_JOINTMOD_BOTH; + } + return DECLAF_JOINTMOD_AXIS; +} + +/* +================ +idDeclAF::JointModToString +================ +*/ +const char * idDeclAF::JointModToString( declAFJointMod_t jointMod ) { + switch( jointMod ) { + case DECLAF_JOINTMOD_AXIS: { + return "orientation"; + } + case DECLAF_JOINTMOD_ORIGIN: { + return "position"; + } + case DECLAF_JOINTMOD_BOTH: { + return "both"; + } + } + return "orientation"; +} + +/* +================= +idDeclAF::Size +================= +*/ +size_t idDeclAF::Size( void ) const { + return sizeof( idDeclAF ) + model.Allocated() + skin.Allocated() + + bodies.Allocated() + constraints.Allocated(); +} + +/* +================ +idDeclAF::ParseContents +================ +*/ +bool idDeclAF::ParseContents( idLexer &src, int &c ) const { + idToken token; + idStr str; + + while( src.ReadToken( &token ) ) { + str += token; + if ( !src.CheckTokenString( "," ) ) { + break; + } + str += ","; + } + c = ContentsFromString( str ); + return true; +} + +/* +================ +idDeclAF::ParseBody +================ +*/ +bool idDeclAF::ParseBody( idLexer &src ) { + bool hasJoint = false; + idToken token; + idAFVector angles; + idDeclAF_Body *body = new idDeclAF_Body; + + bodies.Alloc() = body; + + body->SetDefault( this ); + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + body->name = token; + if ( !body->name.Icmp( "origin" ) || !body->name.Icmp( "world" ) ) { + src.Error( "a body may not be named \"origin\" or \"world\"" ); + return false; + } + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "model" ) ) { + if ( !src.ExpectTokenType( TT_NAME, 0, &token ) ) { + return false; + } + if ( !token.Icmp( "box" ) ) { + body->modelType = TRM_BOX; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "octahedron" ) ) { + body->modelType = TRM_OCTAHEDRON; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "dodecahedron" ) ) { + body->modelType = TRM_DODECAHEDRON; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "cylinder" ) ) { + body->modelType = TRM_CYLINDER; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + body->numSides = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "cone" ) ) { + body->modelType = TRM_CONE; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + body->numSides = src.ParseInt(); + if ( !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "bone" ) ) { + body->modelType = TRM_BONE; + if ( !src.ExpectTokenString( "(" ) || + !body->v1.Parse( src ) || + !src.ExpectTokenString( "," ) || + !body->v2.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + body->width = src.ParseFloat(); + if ( !src.ExpectTokenString( ")" ) ) { + return false; + } + } else if ( !token.Icmp( "custom" ) ) { + src.Error( "custom models not yet implemented" ); + return false; + } else { + src.Error( "unkown model type %s", token.c_str() ); + return false; + } + } else if ( !token.Icmp( "origin" ) ) { + if ( !body->origin.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "angles" ) ) { + if ( !angles.Parse( src ) ) { + return false; + } + body->angles = idAngles( angles.ToVec3().x, angles.ToVec3().y, angles.ToVec3().z ); + } else if ( !token.Icmp( "joint" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + body->jointName = token; + hasJoint = true; + } else if ( !token.Icmp( "mod" ) ) { + if ( !src.ExpectAnyToken( &token ) ) { + return false; + } + body->jointMod = JointModFromString( token.c_str() ); + } else if ( !token.Icmp( "density" ) ) { + body->density = src.ParseFloat(); + } else if ( !token.Icmp( "inertiaScale" ) ) { + src.Parse1DMatrix( 9, body->inertiaScale[0].ToFloatPtr() ); + } else if ( !token.Icmp( "friction" ) ) { + body->linearFriction = src.ParseFloat(); + src.ExpectTokenString( "," ); + body->angularFriction = src.ParseFloat(); + src.ExpectTokenString( "," ); + body->contactFriction = src.ParseFloat(); + } else if ( !token.Icmp( "contents" ) ) { + ParseContents( src, body->contents ); + } else if ( !token.Icmp( "clipMask" ) ) { + ParseContents( src, body->clipMask ); + } else if ( !token.Icmp( "selfCollision" ) ) { + body->selfCollision = src.ParseBool(); + } else if ( !token.Icmp( "containedjoints" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + body->containedJoints = token; + } else if ( !token.Icmp( "frictionDirection" ) ) { + if ( !body->frictionDirection.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "contactMotorDirection" ) ) { + if ( !body->contactMotorDirection.Parse( src ) ) { + return false; + } + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in body", token.c_str() ); + return false; + } + } + + if ( body->modelType == TRM_INVALID ) { + src.Error( "no model set for body" ); + return false; + } + + if ( !hasJoint ) { + src.Error( "no joint set for body" ); + return false; + } + + body->clipMask |= CONTENTS_MOVEABLECLIP; + + return true; +} + +/* +================ +idDeclAF::ParseFixed +================ +*/ +bool idDeclAF::ParseFixed( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_FIXED; + constraint->name = token; + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in ball and socket joint", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseBallAndSocketJoint +================ +*/ +bool idDeclAF::ParseBallAndSocketJoint( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_BALLANDSOCKETJOINT; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + constraint->anchor.ToVec3().Zero(); + constraint->shaft[0].ToVec3().Zero(); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "conelimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) || + !constraint->shaft[0].Parse( src ) ) { + return false; + } + constraint->limit = idDeclAF_Constraint::LIMIT_CONE; + } else if ( !token.Icmp( "pyramidlimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[2] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) || + !constraint->shaft[0].Parse( src ) ) { + return false; + } + constraint->limit = idDeclAF_Constraint::LIMIT_PYRAMID; + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in ball and socket joint", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseUniversalJoint +================ +*/ +bool idDeclAF::ParseUniversalJoint( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_UNIVERSALJOINT; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + constraint->anchor.ToVec3().Zero(); + constraint->shaft[0].ToVec3().Zero(); + constraint->shaft[1].ToVec3().Zero(); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "shafts" ) ) { + if ( !constraint->shaft[0].Parse( src ) || + !src.ExpectTokenString( "," ) || + !constraint->shaft[1].Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "conelimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + constraint->limit = idDeclAF_Constraint::LIMIT_CONE; + } else if ( !token.Icmp( "pyramidlimit" ) ) { + if ( !constraint->limitAxis.Parse( src ) || + !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[2] = src.ParseFloat(); + constraint->limit = idDeclAF_Constraint::LIMIT_PYRAMID; + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in universal joint", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseHinge +================ +*/ +bool idDeclAF::ParseHinge( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_HINGE; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + constraint->anchor.ToVec3().Zero(); + constraint->axis.ToVec3().Zero(); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "axis" ) ) { + if ( !constraint->axis.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "limit" ) ) { + constraint->limitAngles[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + constraint->limitAngles[2] = src.ParseFloat(); + constraint->limit = idDeclAF_Constraint::LIMIT_CONE; + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in hinge", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseSlider +================ +*/ +bool idDeclAF::ParseSlider( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_SLIDER; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "axis" ) ) { + if ( !constraint->axis.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in slider", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseSpring +================ +*/ +bool idDeclAF::ParseSpring( idLexer &src ) { + idToken token; + idDeclAF_Constraint *constraint = new idDeclAF_Constraint; + + constraint->SetDefault( this ); + constraints.Alloc() = constraint; + + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) || + !src.ExpectTokenString( "{" ) ) { + return false; + } + + constraint->type = DECLAF_CONSTRAINT_SPRING; + constraint->limit = idDeclAF_Constraint::LIMIT_NONE; + constraint->name = token; + constraint->friction = 0.5f; + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "body1" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body1 = token; + } else if ( !token.Icmp( "body2" ) ) { + src.ExpectTokenType( TT_STRING, 0, &token ); + constraint->body2 = token; + } else if ( !token.Icmp( "anchor1" ) ) { + if ( !constraint->anchor.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "anchor2" ) ) { + if ( !constraint->anchor2.Parse( src ) ) { + return false; + } + } else if ( !token.Icmp( "friction" ) ) { + constraint->friction = src.ParseFloat(); + } else if ( !token.Icmp( "stretch" ) ) { + constraint->stretch = src.ParseFloat(); + } else if ( !token.Icmp( "compress" ) ) { + constraint->compress = src.ParseFloat(); + } else if ( !token.Icmp( "damping" ) ) { + constraint->damping = src.ParseFloat(); + } else if ( !token.Icmp( "restLength" ) ) { + constraint->restLength = src.ParseFloat(); + } else if ( !token.Icmp( "minLength" ) ) { + constraint->minLength = src.ParseFloat(); + } else if ( !token.Icmp( "maxLength" ) ) { + constraint->maxLength = src.ParseFloat(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in spring", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::ParseSettings +================ +*/ +bool idDeclAF::ParseSettings( idLexer &src ) { + idToken token; + + if ( !src.ExpectTokenString( "{" ) ) { + return false; + } + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "mesh" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + } else if ( !token.Icmp( "anim" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + } else if ( !token.Icmp( "model" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + model = token; + } else if ( !token.Icmp( "skin" ) ) { + if ( !src.ExpectTokenType( TT_STRING, 0, &token ) ) { + return false; + } + skin = token; + } else if ( !token.Icmp( "friction" ) ) { + + defaultLinearFriction = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + defaultAngularFriction = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + defaultContactFriction = src.ParseFloat(); + if ( src.CheckTokenString( "," ) ) { + defaultConstraintFriction = src.ParseFloat(); + } + } else if ( !token.Icmp( "totalMass" ) ) { + totalMass = src.ParseFloat(); + } else if ( !token.Icmp( "suspendSpeed" ) ) { + + suspendVelocity[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + suspendVelocity[1] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + suspendAcceleration[0] = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + return false; + } + suspendAcceleration[1] = src.ParseFloat(); + } else if ( !token.Icmp( "noMoveTime" ) ) { + noMoveTime = src.ParseFloat(); + } else if ( !token.Icmp( "noMoveTranslation" ) ) { + noMoveTranslation = src.ParseFloat(); + } else if ( !token.Icmp( "noMoveRotation" ) ) { + noMoveRotation = src.ParseFloat(); + } else if ( !token.Icmp( "minMoveTime" ) ) { + minMoveTime = src.ParseFloat(); + } else if ( !token.Icmp( "maxMoveTime" ) ) { + maxMoveTime = src.ParseFloat(); + } else if ( !token.Icmp( "contents" ) ) { + ParseContents( src, contents ); + } else if ( !token.Icmp( "clipMask" ) ) { + ParseContents( src, clipMask ); + } else if ( !token.Icmp( "selfCollision" ) ) { + selfCollision = src.ParseBool(); + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown token %s in settings", token.c_str() ); + return false; + } + } + + return true; +} + +/* +================ +idDeclAF::Parse +================ +*/ +bool idDeclAF::Parse( const char *text, const int textLength, bool noCaching ) { + int i, j; + idLexer src; + idToken token; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + while( src.ReadToken( &token ) ) { + + if ( !token.Icmp( "settings" ) ) { + if ( !ParseSettings( src ) ) { + return false; + } + } else if ( !token.Icmp( "body" ) ) { + if ( !ParseBody( src ) ) { + return false; + } + } else if ( !token.Icmp( "fixed" ) ) { + if ( !ParseFixed( src ) ) { + return false; + } + } else if ( !token.Icmp( "ballAndSocketJoint" ) ) { + if ( !ParseBallAndSocketJoint( src ) ) { + return false; + } + } else if ( !token.Icmp( "universalJoint" ) ) { + if ( !ParseUniversalJoint( src ) ) { + return false; + } + } else if ( !token.Icmp( "hinge" ) ) { + if ( !ParseHinge( src ) ) { + return false; + } + } else if ( !token.Icmp( "slider" ) ) { + if ( !ParseSlider( src ) ) { + return false; + } + } else if ( !token.Icmp( "spring" ) ) { + if ( !ParseSpring( src ) ) { + return false; + } + } else if ( token == "}" ) { + break; + } else { + src.Error( "unknown keyword %s", token.c_str() ); + return false; + } + } + + for ( i = 0; i < bodies.Num(); i++ ) { + // check for multiple bodies with the same name + for ( j = i+1; j < bodies.Num(); j++ ) { + if ( bodies[i]->name == bodies[j]->name ) { + src.Error( "two bodies with the same name \"%s\"", bodies[i]->name.c_str() ); + } + } + } + + for ( i = 0; i < constraints.Num(); i++ ) { + // check for multiple constraints with the same name + for ( j = i+1; j < constraints.Num(); j++ ) { + if ( constraints[i]->name == constraints[j]->name ) { + src.Error( "two constraints with the same name \"%s\"", constraints[i]->name.c_str() ); + } + } + // check if there are two valid bodies set + if ( constraints[i]->body1 == "" ) { + src.Error( "no valid body1 specified for constraint '%s'", constraints[i]->name.c_str() ); + } + if ( constraints[i]->body2 == "" ) { + src.Error( "no valid body2 specified for constraint '%s'", constraints[i]->name.c_str() ); + } + } + + // make sure the body which modifies the origin comes first + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->jointName == "origin" ) { + if ( i != 0 ) { + idDeclAF_Body *b = bodies[0]; + bodies[0] = bodies[i]; + bodies[i] = b; + } + break; + } + } + + return true; +} + +/* +================ +idDeclAF::Validate +================ +*/ +bool idDeclAF::Validate( const char *text, int textLength, idStr &report ) const { + idDecl *decl = declManager->AllocateDecl( DECL_AF ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl->base; + delete decl; + (void)report; + return valid; +} + +/* +================ +idDeclAF::DefaultDefinition +================ +*/ +const char *idDeclAF::DefaultDefinition( void ) const { + return + "{\n" + "\t" "settings {\n" + "\t\t" "model \"\"\n" + "\t\t" "skin \"\"\n" + "\t\t" "friction 0.01, 0.01, 0.8, 0.5\n" + "\t\t" "suspendSpeed 20, 30, 40, 60\n" + "\t\t" "noMoveTime 1\n" + "\t\t" "noMoveTranslation 10\n" + "\t\t" "noMoveRotation 10\n" + "\t\t" "minMoveTime -1\n" + "\t\t" "maxMoveTime -1\n" + "\t\t" "totalMass -1\n" + "\t\t" "contents corpse\n" + "\t\t" "clipMask solid, corpse\n" + "\t\t" "selfCollision 1\n" + "\t" "}\n" + "\t" "body \"body\" {\n" + "\t\t" "joint \"origin\"\n" + "\t\t" "mod orientation\n" + "\t\t" "model box( ( -10, -10, -10 ), ( 10, 10, 10 ) )\n" + "\t\t" "origin ( 0, 0, 0 )\n" + "\t\t" "density 0.2\n" + "\t\t" "friction 0.01, 0.01, 0.8\n" + "\t\t" "contents corpse\n" + "\t\t" "clipMask solid, corpse\n" + "\t\t" "selfCollision 1\n" + "\t\t" "containedJoints \"*origin\"\n" + "\t" "}\n" + "}\n"; +} + +/* +================ +idDeclAF::FreeData +================ +*/ +void idDeclAF::FreeData( void ) { + modified = false; + defaultLinearFriction = 0.01f; + defaultAngularFriction = 0.01f; + defaultContactFriction = 0.8f; + defaultConstraintFriction = 0.5f; + totalMass = -1; + suspendVelocity.Set( 20.0f, 30.0f ); + suspendAcceleration.Set( 40.0f, 60.0f ); + noMoveTime = 1.0f; + noMoveTranslation = 10.0f; + noMoveRotation = 10.0f; + minMoveTime = -1.0f; + maxMoveTime = -1.0f; + selfCollision = true; + contents = CONTENTS_CORPSE; + clipMask = CONTENTS_SOLID | CONTENTS_CORPSE; + bodies.DeleteContents( true ); + constraints.DeleteContents( true ); +} + +/* +================ +idDeclAF::Finish +================ +*/ +void idDeclAF::Finish( const getJointTransform_t GetJointTransform, const idJointMat *frame, void *model ) const { + int i; + + const char *name = GetName(); + for ( i = 0; i < bodies.Num(); i++ ) { + idDeclAF_Body *body = bodies[i]; + body->v1.Finish( name, GetJointTransform, frame, model ); + body->v2.Finish( name, GetJointTransform, frame, model ); + body->origin.Finish( name, GetJointTransform, frame, model ); + body->frictionDirection.Finish( name, GetJointTransform, frame, model ); + body->contactMotorDirection.Finish( name, GetJointTransform, frame, model ); + } + for ( i = 0; i < constraints.Num(); i++ ) { + idDeclAF_Constraint *constraint = constraints[i]; + constraint->anchor.Finish( name, GetJointTransform, frame, model ); + constraint->anchor2.Finish( name, GetJointTransform, frame, model ); + constraint->shaft[0].Finish( name, GetJointTransform, frame, model ); + constraint->shaft[1].Finish( name, GetJointTransform, frame, model ); + constraint->axis.Finish( name, GetJointTransform, frame, model ); + constraint->limitAxis.Finish( name, GetJointTransform, frame, model ); + } +} + +/* +================ +idDeclAF::NewBody +================ +*/ +void idDeclAF::NewBody( const char *name ) { + idDeclAF_Body *body; + + body = new idDeclAF_Body(); + body->SetDefault( this ); + body->name = name; + bodies.Append( body ); +} + +/* +================ +idDeclAF::RenameBody + + rename the body with the given name and rename + all constraint body references +================ +*/ +void idDeclAF::RenameBody( const char *oldName, const char *newName ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->name.Icmp( oldName ) == 0 ) { + bodies[i]->name = newName; + break; + } + } + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->body1.Icmp( oldName ) == 0 ) { + constraints[i]->body1 = newName; + } else if ( constraints[i]->body2.Icmp( oldName ) == 0 ) { + constraints[i]->body2 = newName; + } + } +} + +/* +================ +idDeclAF::DeleteBody + + delete the body with the given name and delete + all constraints that reference the body +================ +*/ +void idDeclAF::DeleteBody( const char *name ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->name.Icmp( name ) == 0 ) { + delete bodies[i]; + bodies.RemoveIndex( i ); + break; + } + } + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->body1.Icmp( name ) == 0 || + constraints[i]->body2.Icmp( name ) == 0 ) { + delete constraints[i]; + constraints.RemoveIndex( i ); + i--; + } + } +} + +/* +================ +idDeclAF::NewConstraint +================ +*/ +void idDeclAF::NewConstraint( const char *name ) { + idDeclAF_Constraint *constraint; + + constraint = new idDeclAF_Constraint; + constraint->SetDefault( this ); + constraint->name = name; + constraints.Append( constraint ); +} + +/* +================ +idDeclAF::RenameConstraint +================ +*/ +void idDeclAF::RenameConstraint( const char *oldName, const char *newName ) { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( oldName ) == 0 ) { + constraints[i]->name = newName; + return; + } + } +} + +/* +================ +idDeclAF::DeleteConstraint +================ +*/ +void idDeclAF::DeleteConstraint( const char *name ) { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( name ) == 0 ) { + delete constraints[i]; + constraints.RemoveIndex( i ); + return; + } + } +} + +/* +================ +idDeclAF::idDeclAF +================ +*/ +idDeclAF::idDeclAF( void ) { + FreeData(); +} + +/* +================ +idDeclAF::~idDeclAF +================ +*/ +idDeclAF::~idDeclAF( void ) { + bodies.DeleteContents( true ); + constraints.DeleteContents( true ); +} diff --git a/src/framework/declentitydef.cpp b/src/framework/declentitydef.cpp new file mode 100644 index 0000000..9e065bf --- /dev/null +++ b/src/framework/declentitydef.cpp @@ -0,0 +1,177 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( idDeclEntityDef ) == 0x34, "retail idDeclEntityDef ABI drift" ); +#endif + +/* +================= +idDeclEntityDef::idDeclEntityDef +================= +*/ +idDeclEntityDef::idDeclEntityDef( void ) { +} + +/* +================= +idDeclEntityDef::Size +================= +*/ +size_t idDeclEntityDef::Size( void ) const { + return sizeof( idDeclEntityDef ) + dict.Allocated(); +} + +/* +================ +idDeclEntityDef::FreeData +================ +*/ +void idDeclEntityDef::FreeData( void ) { + dict.Clear(); +} + +/* +================ +idDeclEntityDef::Parse +================ +*/ +bool idDeclEntityDef::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token, token2; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + while (1) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + if ( token.type != TT_STRING ) { + src.Warning( "Expected quoted string, but found '%s'", token.c_str() ); + MakeDefault(); + return false; + } + + if ( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + if ( dict.FindKey( token ) ) { + src.Warning( "'%s' already defined", token.c_str() ); + } + dict.Set( token, token2 ); + } + + // we always automatically set a "classname" key to our name + dict.Set( "classname", GetName() ); + + // "inherit" keys will cause all values from another entityDef to be copied into this one + // if they don't conflict. We can't have circular recursions, because each entityDef will + // never be parsed mroe than once + + // find all of the dicts first, because copying inherited values will modify the dict + idList defList; + + while ( 1 ) { + const idKeyValue *kv; + kv = dict.MatchPrefix( "inherit", NULL ); + if ( !kv ) { + break; + } + + const idDeclEntityDef *copy = static_cast( declManager->FindType( DECL_ENTITYDEF, kv->GetValue(), true, noCaching ) ); + if ( !copy ) { + src.Warning( "Unknown entityDef '%s' inherited by '%s'", kv->GetValue().c_str(), GetName() ); + } else { + defList.Append( copy ); + } + + // delete this key/value pair + dict.Delete( kv->GetKey() ); + } + + // now copy over the inherited key / value pairs + for ( int i = 0 ; i < defList.Num() ; i++ ) { + dict.SetDefaults( &defList[ i ]->dict ); + } + + // precache all referenced media + // do this as long as we arent in modview + const int noCacheTools = EDITOR_RADIANT | EDITOR_MODVIEW | EDITOR_AAS | + EDITOR_SPAWN_GUI | EDITOR_DECL_VALIDATING; + if ( !noCaching && !( com_editors & noCacheTools ) ) { + game->CacheDictionaryMedia( &dict ); + } + + return true; +} + +/* +================ +idDeclEntityDef::DefaultDefinition +================ +*/ +const char *idDeclEntityDef::DefaultDefinition( void ) const { + return + "{\n" + "\t" "\"DEFAULTED\"\t\"1\"\n" + "}"; +} + +/* +================ +idDeclEntityDef::Validate +================ +*/ +bool idDeclEntityDef::Validate( const char *text, int textLength, idStr &report ) const { + idDecl *decl = declManager->AllocateDecl( DECL_ENTITYDEF ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl->base; + delete decl; + (void)report; + return valid; +} + +/* +================ +idDeclEntityDef::Print + +Dumps all key/value pairs, including inherited ones +================ +*/ +void idDeclEntityDef::Print( void ) { + dict.Print(); +} diff --git a/src/framework/declmanager.cpp b/src/framework/declmanager.cpp new file mode 100644 index 0000000..692185e --- /dev/null +++ b/src/framework/declmanager.cpp @@ -0,0 +1,2766 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* + +GUIs and script remain separately parsed + +Following a parse, all referenced media (and other decls) will have been touched. + +sinTable and cosTable are required for the rotate material keyword to function + +A new FindType on a purged decl will cause it to be reloaded, but a stale pointer to a purged +decl will look like a defaulted decl. + +Moving a decl from one file to another will not be handled correctly by a reload, the material +will be defaulted. + +NULL or empty decl names will always return NULL + Should probably make a default decl for this + +Decls are initially created without a textSource +A parse without textSource set should always just call MakeDefault() +A parse that has an error should internally call MakeDefault() +A purge does nothing to a defaulted decl + +Should we have a "purged" media state separate from the "defaulted" media state? + +reloading over a decl name that was defaulted + +reloading over a decl name that was valid + +missing reload over a previously explicit definition + +*/ + +#define USE_COMPRESSED_DECLS +//#define GET_HUFFMAN_FREQUENCIES + +class idDeclType { +public: + idStr typeName; + declType_t type; + idDecl * (*allocator)( void ); +}; + +class idDeclFolder { +public: + idStr folder; + idStr extension; + declType_t defaultType; +}; + +class idDeclFile; +class rvDeclGuide; + +class idDeclLocal : public idDeclBase { + friend class idDeclFile; + friend class idDeclManagerLocal; + +public: + idDeclLocal(); + virtual ~idDeclLocal() {}; + virtual const char * GetName( void ) const; + virtual declType_t GetType( void ) const; + virtual declState_t GetState( void ) const; + virtual bool IsImplicit( void ) const; + virtual bool IsValid( void ) const; + virtual void Invalidate( void ); + virtual void Reload( void ); + virtual void EnsureNotPurged( void ); + virtual int Index( void ) const; + virtual int GetLineNum( void ) const; + virtual const char * GetFileName( void ) const; + virtual size_t Size( void ) const; + virtual void GetText( char *text ) const; + virtual int GetTextLength( void ) const; + virtual int GetCompressedLength( void ) const; + virtual void SetText( const char *text ); + virtual bool ReplaceSourceFileText( void ); + virtual bool SourceFileChanged( void ) const; + virtual void MakeDefault( void ); + virtual bool EverReferenced( void ) const; + virtual void SetReferencedThisLevel( void ); + +protected: + virtual bool SetDefaultText( void ); + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void List( void ) const; + virtual void Print( void ) const; + virtual bool RebuildTextSource( void ) { return false; } + virtual bool Validate( const char *text, int textLength, idStr &report ) const; + +protected: + void AllocateSelf( void ); + + // Parses the decl definition. + // After calling parse, a decl will be guaranteed usable. + void ParseLocal( bool noCaching = false ); + + // Does a MakeDefualt, but flags the decl so that it + // will Parse() the next time the decl is found. + void Purge( void ); + + // Set textSource possible with compression. + void SetTextLocal( const char *text, const int length ); + +private: + idDecl * self; + + idStr name; // name of the decl + char * textSource; // decl text definition + int textLength; // length of textSource + int compressedLength; // compressed length + idDeclFile * sourceFile; // source file in which the decl was defined + int sourceTextOffset; // offset in source file to decl text + int sourceTextLength; // length of decl text in source file + int sourceLine; // this is where the actual declaration token starts + int checksum; // checksum of the decl text + declType_t type; // decl type + declState_t declState; // decl state + int index; // index in the per-type list + + bool parsedOutsideLevelLoad; // these decls will never be purged + bool everReferenced; // set to true if the decl was ever used + bool referencedThisLevel; // set to true when the decl is used for the current level + bool redefinedInReload; // used during file reloading to make sure a decl that has + // its source removed will be defaulted + idDeclLocal * nextInFile; // next decl in the decl file + bool needsPrecache; +}; + +class idDeclFile { +public: + idDeclFile(); + idDeclFile( const char *fileName, declType_t defaultType ); + + void Reload( bool force ); + int LoadAndParse( bool unique = false ); + +public: + idStr fileName; + declType_t defaultType; + + ID_TIME_T timestamp; + int checksum; + int fileSize; + int numLines; + + idDeclLocal * decls; +}; + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( rvDeclGuide ) == 0x2C4, "retail rvDeclGuide ABI drift" ); +#endif + +class idDeclManagerLocal : public idDeclManager { + friend class idDeclLocal; + +public: + virtual void SetInsideLoad( bool var ); + virtual bool GetInsideLoad( void ); + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void Reload( bool force ); + virtual void BeginLevelLoad(); + virtual void EndLevelLoad(); + virtual void RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)( void ) ); +#ifdef RV_SINGLE_DECL_FILE + virtual void StartLoadingDecls( void ); + virtual void FinishLoadingDecls( void ); + virtual void LoadDeclsFromFile( void ); + virtual void WriteDeclFile( void ); + virtual void FlushDecls( void ); +#endif + virtual void RegisterDeclFolderWrapper( const char *folder, const char *extension, declType_t defaultType, bool unique = false, bool norecurse = false ); + void RegisterDeclFolder( const char *folder, const char *extension, declType_t defaultType, bool unique = false, bool norecurse = false ); + virtual int GetChecksum( void ) const; + virtual int GetNumDeclTypes( void ) const; + virtual int GetNumDecls( declType_t type ); + virtual const char * GetDeclNameFromType( declType_t type ) const; + virtual declType_t GetDeclTypeFromName( const char *typeName ) const; + virtual const idDecl * FindType( declType_t type, const char *name, bool makeDefault = true, bool noCaching = false ); + virtual const idDecl * DeclByIndex( declType_t type, int index, bool forceParse = true ); + + virtual const idDecl* FindDeclWithoutParsing( declType_t type, const char *name, bool makeDefault = true ); + virtual void ReloadFile( const char* filename, bool force ); + + virtual void ListType( const idCmdArgs &args, declType_t type ); + virtual void PrintType( const idCmdArgs &args, declType_t type ); + + virtual idDecl * CreateNewDecl( declType_t type, const char *name, const char *fileName ); + + //BSM Added for the material editors rename capabilities + virtual bool RenameDecl( declType_t type, const char* oldName, const char* newName ); + + virtual void MediaPrint( const char *fmt, ... ) id_attribute((format(printf,2,3))); + virtual void WritePrecacheCommands( idFile *f ); + virtual void ParseGuides( void ); + virtual void ShutdownGuides( void ); + virtual bool EvaluateGuide( idStr &name, idLexer *src, idStr &definition ); + virtual bool EvaluateInlineGuide( idStr &name, idStr &definition ); + + virtual const idMaterial * FindMaterial( const char *name, bool makeDefault = true ); + virtual const idDeclTable * FindTable( const char *name, bool makeDefault = true ); + virtual const idDeclSkin * FindSkin( const char *name, bool makeDefault = true ); + virtual const idSoundShader * FindSound( const char *name, bool makeDefault = true ); + virtual const rvDeclMatType * FindMaterialType( const char *name, bool makeDefault = true ); + virtual const rvDeclLipSync * FindLipSync( const char *name, bool makeDefault = true ); + virtual const rvDeclPlayback * FindPlayback( const char *name, bool makeDefault = true ); + virtual const rvDeclEffect * FindEffect( const char *name, bool makeDefault = true ); + + virtual const idMaterial * MaterialByIndex( int index, bool forceParse = true ); + virtual const idDeclTable * TableByIndex( int index, bool forceParse = true ); + virtual const idDeclSkin * SkinByIndex( int index, bool forceParse = true ); + virtual const idSoundShader * SoundByIndex( int index, bool forceParse = true ); + virtual const rvDeclMatType * MaterialTypeByIndex( int index, bool forceParse = true ); + virtual const rvDeclLipSync * LipSyncByIndex( int index, bool forceParse = true ); + virtual const rvDeclPlayback * PlaybackByIndex( int index, bool forceParse = true ); + virtual const rvDeclEffect * EffectByIndex( int index, bool forceParse = true ); + + virtual void StartPlaybackRecord( rvDeclPlayback *playback ); + virtual bool SetPlaybackData( rvDeclPlayback *playback, int now, int control, rvDeclPlaybackData *pbd ); + virtual bool GetPlaybackData( const rvDeclPlayback *playback, int control, int now, int last, rvDeclPlaybackData *pbd ); + virtual bool FinishPlayback( rvDeclPlayback *playback ); + virtual idStr GetNewName( declType_t type, const char *base ); + virtual const char * GetDeclTypeName( declType_t type ); + virtual size_t ListDeclSummary( const idCmdArgs &args ); + virtual void RemoveDeclFile( const char *file ); + virtual bool Validate( declType_t type, int index, idStr &report ); + virtual idDecl * AllocateDecl( declType_t type ); + +public: + static void MakeNameCanonical( const char *name, char *result, int maxLength ); + idDeclLocal * FindTypeWithoutParsing( declType_t type, const char *name, bool makeDefault = true, int indexToStoreAt = -1 ); + + idDeclType * GetDeclType( int type ) const { return declTypes[type]; } + const idDeclFile * GetImplicitDeclFile( void ) const { return &implicitDecls; } + +private: + idFile * mSingleDeclFile; + idList declTypes; + idList declFolders; + + idList loadedFiles; + idHashIndex hashTables[DECL_MAX_TYPES]; + idList linearLists[DECL_MAX_TYPES]; + idDeclFile implicitDecls; // this holds all the decls that were created because explicit + // text definitions were not found. Decls that became default + // because of a parse error are not in this list. + idHashTable guideTable; + int checksum; // checksum of all loaded decl text + int indent; // for MediaPrint + bool insideLevelLoad; + + static idCVar decl_show; + +private: + static void ListDecls_f( const idCmdArgs &args ); + static void ListAllDecls_f( const idCmdArgs &args ); + static void ReloadDecls_f( const idCmdArgs &args ); + static void ReloadFile_f( const idCmdArgs &args ); + static void ResaveDecl_f( const idCmdArgs &args ); + static void TouchDecl_f( const idCmdArgs &args ); + int GetTotalTextMemory( declType_t type ); + rvDeclGuide * GetNewGuide( idLexer *src, idStr &file ); +}; + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( idDeclType ) == 0x28, "retail idDeclType ABI drift" ); +static_assert( sizeof( idDeclFolder ) == 0x44, "retail idDeclFolder ABI drift" ); +static_assert( sizeof( idDeclFile ) == 0x38, "retail idDeclFile ABI drift" ); +static_assert( sizeof( idDeclLocal ) == 0x60, "retail idDeclLocal ABI drift" ); +static_assert( sizeof( idDeclManagerLocal ) == 0x60C, "retail idDeclManagerLocal ABI drift" ); +#endif + +idCVar idDeclManagerLocal::decl_show( "decl_show", "0", CVAR_SYSTEM, "set to 1 to print parses, 2 to also print references", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + +idDeclManagerLocal declManagerLocal; +idDeclManager * declManager = &declManagerLocal; + +void idDeclManagerLocal::SetInsideLoad( bool var ) { + insideLevelLoad = var; +} + +bool idDeclManagerLocal::GetInsideLoad( void ) { + return insideLevelLoad; +} + +/* +==================================================================================== + + decl text huffman compression + +==================================================================================== +*/ + +const int MAX_HUFFMAN_SYMBOLS = 256; + +typedef struct huffmanNode_s { + int symbol; + int frequency; + struct huffmanNode_s * next; + struct huffmanNode_s * children[2]; +} huffmanNode_t; + +typedef struct huffmanCode_s { + unsigned long bits[8]; + int numBits; +} huffmanCode_t; + +// compression ratio = 64% +static int huffmanFrequencies[] = { + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00078fb6, 0x000352a7, 0x00000002, 0x00000001, 0x0002795e, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00049600, 0x000000dd, 0x00018732, 0x0000005a, 0x00000007, 0x00000092, 0x0000000a, 0x00000919, + 0x00002dcf, 0x00002dda, 0x00004dfc, 0x0000039a, 0x000058be, 0x00002d13, 0x00014d8c, 0x00023c60, + 0x0002ddb0, 0x0000d1fc, 0x000078c4, 0x00003ec7, 0x00003113, 0x00006b59, 0x00002499, 0x0000184a, + 0x0000250b, 0x00004e38, 0x000001ca, 0x00000011, 0x00000020, 0x000023da, 0x00000012, 0x00000091, + 0x0000000b, 0x00000b14, 0x0000035d, 0x0000137e, 0x000020c9, 0x00000e11, 0x000004b4, 0x00000737, + 0x000006b8, 0x00001110, 0x000006b3, 0x000000fe, 0x00000f02, 0x00000d73, 0x000005f6, 0x00000be4, + 0x00000d86, 0x0000014d, 0x00000d89, 0x0000129b, 0x00000db3, 0x0000015a, 0x00000167, 0x00000375, + 0x00000028, 0x00000112, 0x00000018, 0x00000678, 0x0000081a, 0x00000677, 0x00000003, 0x00018112, + 0x00000001, 0x000441ee, 0x000124b0, 0x0001fa3f, 0x00026125, 0x0005a411, 0x0000e50f, 0x00011820, + 0x00010f13, 0x0002e723, 0x00003518, 0x00005738, 0x0002cc26, 0x0002a9b7, 0x0002db81, 0x0003b5fa, + 0x000185d2, 0x00001299, 0x00030773, 0x0003920d, 0x000411cd, 0x00018751, 0x00005fbd, 0x000099b0, + 0x00009242, 0x00007cf2, 0x00002809, 0x00005a1d, 0x00000001, 0x00005a1d, 0x00000001, 0x00000001, + + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, + 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, +}; + +static huffmanCode_t huffmanCodes[MAX_HUFFMAN_SYMBOLS]; +static huffmanNode_t *huffmanTree = NULL; +static int totalUncompressedLength = 0; +static int totalCompressedLength = 0; +static int maxHuffmanBits = 0; + + +/* +================ +ClearHuffmanFrequencies +================ +*/ +void ClearHuffmanFrequencies( void ) { + int i; + + for( i = 0; i < MAX_HUFFMAN_SYMBOLS; i++ ) { + huffmanFrequencies[i] = 1; + } +} + +/* +================ +InsertHuffmanNode +================ +*/ +huffmanNode_t *InsertHuffmanNode( huffmanNode_t *firstNode, huffmanNode_t *node ) { + huffmanNode_t *n, *lastNode; + + lastNode = NULL; + for ( n = firstNode; n; n = n->next ) { + if ( node->frequency <= n->frequency ) { + break; + } + lastNode = n; + } + if ( lastNode ) { + node->next = lastNode->next; + lastNode->next = node; + } else { + node->next = firstNode; + firstNode = node; + } + return firstNode; +} + +/* +================ +BuildHuffmanCode_r +================ +*/ +void BuildHuffmanCode_r( huffmanNode_t *node, huffmanCode_t code, huffmanCode_t codes[MAX_HUFFMAN_SYMBOLS] ) { + if ( node->symbol == -1 ) { + huffmanCode_t newCode = code; + assert( code.numBits < sizeof( codes[0].bits ) * 8 ); + newCode.numBits++; + if ( code.numBits > maxHuffmanBits ) { + maxHuffmanBits = newCode.numBits; + } + BuildHuffmanCode_r( node->children[0], newCode, codes ); + newCode.bits[code.numBits >> 5] |= 1 << ( code.numBits & 31 ); + BuildHuffmanCode_r( node->children[1], newCode, codes ); + } else { + assert( code.numBits <= sizeof( codes[0].bits ) * 8 ); + codes[node->symbol] = code; + } +} + +/* +================ +FreeHuffmanTree_r +================ +*/ +void FreeHuffmanTree_r( huffmanNode_t *node ) { + if ( node->symbol == -1 ) { + FreeHuffmanTree_r( node->children[0] ); + FreeHuffmanTree_r( node->children[1] ); + } + delete node; +} + +/* +================ +HuffmanHeight_r +================ +*/ +int HuffmanHeight_r( huffmanNode_t *node ) { + if ( node == NULL ) { + return -1; + } + int left = HuffmanHeight_r( node->children[0] ); + int right = HuffmanHeight_r( node->children[1] ); + if ( left > right ) { + return left + 1; + } + return right + 1; +} + +/* +================ +SetupHuffman +================ +*/ +void SetupHuffman( void ) { + int i, height; + huffmanNode_t *firstNode, *node; + huffmanCode_t code; + + firstNode = NULL; + for( i = 0; i < MAX_HUFFMAN_SYMBOLS; i++ ) { + node = new huffmanNode_t; + node->symbol = i; + node->frequency = huffmanFrequencies[i]; + node->next = NULL; + node->children[0] = NULL; + node->children[1] = NULL; + firstNode = InsertHuffmanNode( firstNode, node ); + } + + for( i = 1; i < MAX_HUFFMAN_SYMBOLS; i++ ) { + node = new huffmanNode_t; + node->symbol = -1; + node->frequency = firstNode->frequency + firstNode->next->frequency; + node->next = NULL; + node->children[0] = firstNode; + node->children[1] = firstNode->next; + firstNode = InsertHuffmanNode( firstNode->next->next, node ); + } + + maxHuffmanBits = 0; + memset( &code, 0, sizeof( code ) ); + BuildHuffmanCode_r( firstNode, code, huffmanCodes ); + + huffmanTree = firstNode; + + height = HuffmanHeight_r( firstNode ); + assert( maxHuffmanBits == height ); +} + +/* +================ +ShutdownHuffman +================ +*/ +void ShutdownHuffman( void ) { + if ( huffmanTree ) { + FreeHuffmanTree_r( huffmanTree ); + } +} + +/* +================ +HuffmanCompressText +================ +*/ +int HuffmanCompressText( const char *text, int textLength, byte *compressed, int maxCompressedSize ) { + int i, j; + idBitMsg msg; + + totalUncompressedLength += textLength; + + msg.Init( compressed, maxCompressedSize ); + msg.BeginWriting(); + for ( i = 0; i < textLength; i++ ) { + const huffmanCode_t &code = huffmanCodes[(unsigned char)text[i]]; + for ( j = 0; j < ( code.numBits >> 5 ); j++ ) { + msg.WriteBits( code.bits[j], 32 ); + } + if ( code.numBits & 31 ) { + msg.WriteBits( code.bits[j], code.numBits & 31 ); + } + } + + totalCompressedLength += msg.GetSize(); + + return msg.GetSize(); +} + +/* +================ +HuffmanDecompressText +================ +*/ +int HuffmanDecompressText( char *text, int textLength, const byte *compressed, int compressedSize ) { + int i, bit; + idBitMsg msg; + huffmanNode_t *node; + + msg.Init( compressed, compressedSize ); + msg.SetSize( compressedSize ); + msg.BeginReading(); + for ( i = 0; i < textLength; i++ ) { + node = huffmanTree; + do { + bit = msg.ReadBits( 1 ); + node = node->children[bit]; + } while( node->symbol == -1 ); + text[i] = node->symbol; + } + text[i] = '\0'; + return msg.GetReadCount(); +} + +/* +================ +ListHuffmanFrequencies_f +================ +*/ +void ListHuffmanFrequencies_f( const idCmdArgs &args ) { + int i; + float compression; + compression = !totalUncompressedLength ? 100 : 100 * totalCompressedLength / totalUncompressedLength; + common->Printf( "// compression ratio = %d%%\n", (int)compression ); + common->Printf( "static int huffmanFrequencies[] = {\n" ); + for( i = 0; i < MAX_HUFFMAN_SYMBOLS; i += 8 ) { + common->Printf( "\t0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x,\n", + huffmanFrequencies[i+0], huffmanFrequencies[i+1], + huffmanFrequencies[i+2], huffmanFrequencies[i+3], + huffmanFrequencies[i+4], huffmanFrequencies[i+5], + huffmanFrequencies[i+6], huffmanFrequencies[i+7]); + } + common->Printf( "}\n" ); +} + +/* +==================================================================================== + + idDeclFile + +==================================================================================== +*/ + +/* +================ +idDeclFile::idDeclFile +================ +*/ +idDeclFile::idDeclFile( const char *fileName, declType_t defaultType ) { + this->fileName = fileName; + this->defaultType = defaultType; + this->timestamp = 0; + this->checksum = 0; + this->fileSize = 0; + this->numLines = 0; + this->decls = NULL; +} + +/* +================ +idDeclFile::idDeclFile +================ +*/ +idDeclFile::idDeclFile() { + this->fileName = ""; + this->defaultType = DECL_MAX_TYPES; + this->timestamp = 0; + this->checksum = 0; + this->fileSize = 0; + this->numLines = 0; + this->decls = NULL; +} + +/* +================ +idDeclFile::Reload + +ForceReload will cause it to reload even if the timestamp hasn't changed +================ +*/ +void idDeclFile::Reload( bool force ) { + // check for an unchanged timestamp + if ( !force && timestamp != 0 ) { + ID_TIME_T testTimeStamp; + fileSystem->ReadFile( fileName, NULL, &testTimeStamp ); + + if ( testTimeStamp == timestamp ) { + return; + } + } + + // parse the text + LoadAndParse( false ); +} + +/* +================ +idDeclFile::LoadAndParse + +This is used during both the initial load, and any reloads +================ +*/ +int c_savedMemory = 0; + +int idDeclFile::LoadAndParse( bool unique ) { + int i, numTypes; + idLexer src; + idToken token; + int startMarker; + char * buffer; + int length, size; + int sourceLine; + idStr name; + idStr definition; + idStr stripped = fileName; + idDeclLocal *newDecl; + bool reparse; + stripped.StripFileExtension(); + + // load the text + common->DPrintf( "...loading '%s'\n", fileName.c_str() ); + length = fileSystem->ReadFile( fileName, (void **)&buffer, ×tamp ); + if ( length == -1 ) { + common->FatalError( "couldn't load %s", fileName.c_str() ); + return 0; + } + + if ( !src.LoadMemory( buffer, length, fileName ) ) { + common->Error( "Couldn't parse %s", fileName.c_str() ); + Mem_Free( buffer ); + return 0; + } + + // mark all the defs that were from the last reload of this file + for ( idDeclLocal *decl = decls; decl; decl = decl->nextInFile ) { + decl->redefinedInReload = false; + } + + src.SetFlags( DECL_LEXER_FLAGS ); + + checksum = MD5_BlockChecksum( buffer, length ); + + fileSize = length; + + // scan through, identifying each individual declaration + while( 1 ) { + + startMarker = src.GetFileOffset(); + sourceLine = src.GetLineNum(); + + // parse the decl type name + if ( !src.ReadToken( &token ) ) { + break; + } + + bool guide = false; + if ( token.Icmp( "guide" ) == 0 ) { + guide = true; + if ( !src.ReadToken( &token ) ) { + src.Warning( "Guide without definition at end of file" ); + break; + } + } + + declType_t identifiedType = DECL_MAX_TYPES; + + // get the decl type from the type name + numTypes = declManagerLocal.GetNumDeclTypes(); + for ( i = 0; i < numTypes; i++ ) { + idDeclType *typeInfo = declManagerLocal.GetDeclType( i ); + if ( typeInfo && typeInfo->typeName.Icmp( token ) == 0 ) { + identifiedType = (declType_t) typeInfo->type; + break; + } + } + + if ( i >= numTypes ) { + + if ( token.Icmp( "{" ) == 0 ) { + + // if we ever see an open brace, we somehow missed the [type] prefix + src.Warning( "Missing decl name" ); + src.SkipBracedSection( false ); + continue; + + } else { + + if ( defaultType == DECL_MAX_TYPES ) { + src.Warning( "No type" ); + continue; + } + src.UnreadToken( &token ); + // use the default type + identifiedType = defaultType; + } + } + + // now parse the name + if ( !src.ReadToken( &token ) ) { + src.Warning( "Type without definition at end of file" ); + break; + } + + if ( !token.Icmp( "{" ) ) { + // if we ever see an open brace, we somehow missed the [type] prefix + src.Warning( "Missing decl name" ); + src.SkipBracedSection( false ); + continue; + } + + // FIXME: export decls are only used by the model exporter, they are skipped here for now + if ( identifiedType == DECL_MODELEXPORT ) { + src.SkipBracedSection(); + continue; + } + + name = token; + + if ( unique && stripped.Cmp( name ) != 0 ) { + common->Warning( "%s must be in a file of the same name!", name.c_str() ); + } + + definition.Clear(); + if ( guide ) { + if ( !declManagerLocal.EvaluateGuide( name, &src, definition ) ) { + continue; + } + } else if ( !src.ParseBracedSectionExact( definition, -1 ) ) { + src.Warning( "Type without definition at end of file" ); + break; + } + declManagerLocal.EvaluateInlineGuide( name, definition ); + size = src.GetFileOffset() - startMarker; + + // look it up, possibly getting a newly created default decl + reparse = false; + newDecl = declManagerLocal.FindTypeWithoutParsing( identifiedType, name, false ); + if ( newDecl ) { + // update the existing copy + if ( newDecl->sourceFile != this || newDecl->redefinedInReload ) { + src.Warning( "%s '%s' previously defined at %s:%i", declManagerLocal.GetDeclNameFromType( identifiedType ), + name.c_str(), newDecl->sourceFile->fileName.c_str(), newDecl->sourceLine ); + continue; + } + if ( newDecl->declState != DS_UNPARSED ) { + reparse = true; + } + } else { + // allow it to be created as a default, then add it to the per-file list + newDecl = declManagerLocal.FindTypeWithoutParsing( identifiedType, name, true ); + newDecl->nextInFile = this->decls; + this->decls = newDecl; + } + + newDecl->redefinedInReload = true; + + if ( newDecl->textSource ) { + Mem_Free( newDecl->textSource ); + newDecl->textSource = NULL; + } + + newDecl->SetTextLocal( definition.c_str(), definition.Length() ); + newDecl->sourceFile = this; + newDecl->sourceTextOffset = startMarker; + newDecl->sourceTextLength = size; + newDecl->sourceLine = sourceLine; + newDecl->declState = DS_UNPARSED; + + // if it is currently in use, reparse it immedaitely + if ( reparse ) { + newDecl->ParseLocal(); + } + } + + numLines = src.GetLineNum(); + + Mem_Free( buffer ); + + // any defs that weren't redefinedInReload should now be defaulted + for ( idDeclLocal *decl = decls ; decl ; decl = decl->nextInFile ) { + if ( decl->redefinedInReload == false ) { + decl->MakeDefault(); + decl->sourceTextOffset = decl->sourceFile->fileSize; + decl->sourceTextLength = 0; + decl->sourceLine = decl->sourceFile->numLines; + } + } + + return checksum; +} + +/* +==================================================================================== + + idDeclManagerLocal + +==================================================================================== +*/ + +const char *listDeclStrings[] = { "current", "all", "ever", NULL }; + +/* +=================== +idDeclManagerLocal::Init +=================== +*/ +void idDeclManagerLocal::Init( void ) { + + common->Printf( "----- Initializing Decls -----\n" ); + + mSingleDeclFile = NULL; + checksum = 0; + indent = 0; + insideLevelLoad = false; + ParseGuides(); + +#ifdef USE_COMPRESSED_DECLS + SetupHuffman(); +#endif + +#ifdef GET_HUFFMAN_FREQUENCIES + ClearHuffmanFrequencies(); +#endif + + // decls used throughout the engine + RegisterDeclType( "table", DECL_TABLE, idDeclAllocator ); + RegisterDeclType( "material", DECL_MATERIAL, idDeclAllocator ); + RegisterDeclType( "skin", DECL_SKIN, idDeclAllocator ); + RegisterDeclType( "sound", DECL_SOUND, idDeclAllocator ); + + RegisterDeclType( "entityDef", DECL_ENTITYDEF, idDeclAllocator ); + RegisterDeclType( "mapDef", DECL_MAPDEF, idDeclAllocator ); + RegisterDeclType( "materialType", DECL_MATERIALTYPE, idDeclAllocator ); + RegisterDeclType( "lipSync", DECL_LIPSYNC, idDeclAllocator ); + RegisterDeclType( "playback", DECL_PLAYBACK, idDeclAllocator ); + RegisterDeclType( "articulatedFigure", DECL_AF, idDeclAllocator ); + RegisterDeclType( "pda", DECL_PDA, idDeclAllocator ); + RegisterDeclType( "email", DECL_EMAIL, idDeclAllocator ); + RegisterDeclType( "video", DECL_VIDEO, idDeclAllocator ); + RegisterDeclType( "audio", DECL_AUDIO, idDeclAllocator ); + + RegisterDeclFolder( "materials", ".mtr", DECL_MATERIAL ); + RegisterDeclFolder( "skins", ".skin", DECL_SKIN ); + RegisterDeclFolder( "sound", ".sndshd", DECL_SOUND ); + + // add console commands + cmdSystem->AddCommand( "listDecls", ListDecls_f, CMD_FL_SYSTEM, "lists all decls" ); + + cmdSystem->AddCommand( "reloadDecls", ReloadDecls_f, CMD_FL_SYSTEM, "reloads decls" ); + cmdSystem->AddCommand( "touch", TouchDecl_f, CMD_FL_SYSTEM, "touches a decl" ); + + cmdSystem->AddCommand( "listTables", idListDecls_f, CMD_FL_SYSTEM, "lists tables", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listMaterials", idListDecls_f, CMD_FL_SYSTEM, "lists materials", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listSkins", idListDecls_f, CMD_FL_SYSTEM, "lists skins", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listSoundShaders", idListDecls_f, CMD_FL_SYSTEM, "lists sound shaders", idCmdSystem::ArgCompletion_String ); + + cmdSystem->AddCommand( "listEntityDefs", idListDecls_f, CMD_FL_SYSTEM, "lists entity defs", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listMaterialTypes", idListDecls_f, CMD_FL_SYSTEM, "lists material types", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listLipsyncs", idListDecls_f, CMD_FL_SYSTEM, "lists lip sync decls", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listPlaybacks", idListDecls_f, CMD_FL_SYSTEM, "lists playback decls", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listEffects", idListDecls_f, CMD_FL_SYSTEM, "lists effect decls", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listAF", idListDecls_f, CMD_FL_SYSTEM, "lists articulated figures", idCmdSystem::ArgCompletion_String); + + cmdSystem->AddCommand( "listPDAs", idListDecls_f, CMD_FL_SYSTEM, "lists PDAs", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listEmails", idListDecls_f, CMD_FL_SYSTEM, "lists Emails", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listVideos", idListDecls_f, CMD_FL_SYSTEM, "lists Videos", idCmdSystem::ArgCompletion_String ); + cmdSystem->AddCommand( "listAudios", idListDecls_f, CMD_FL_SYSTEM, "lists Audios", idCmdSystem::ArgCompletion_String ); + + cmdSystem->AddCommand( "printTable", idPrintDecls_f, CMD_FL_SYSTEM, "prints a table", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printMaterial", idPrintDecls_f, CMD_FL_SYSTEM, "prints a material", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printSkin", idPrintDecls_f, CMD_FL_SYSTEM, "prints a skin", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printSoundShader", idPrintDecls_f, CMD_FL_SYSTEM, "prints a sound shader", idCmdSystem::ArgCompletion_Decl ); + + cmdSystem->AddCommand( "printEntityDef", idPrintDecls_f, CMD_FL_SYSTEM, "prints an entity def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printMaterialTypes", idPrintDecls_f, CMD_FL_SYSTEM, "prints material types", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printLipsyncs", idPrintDecls_f, CMD_FL_SYSTEM, "prints lip syncs", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printPlaybacks", idPrintDecls_f, CMD_FL_SYSTEM, "prints playbacks", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printEffects", idPrintDecls_f, CMD_FL_SYSTEM, "prints effects", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printAF", idPrintDecls_f, CMD_FL_SYSTEM, "prints an articulated figure", idCmdSystem::ArgCompletion_Decl ); + + cmdSystem->AddCommand( "printPDA", idPrintDecls_f, CMD_FL_SYSTEM, "prints an PDA", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printEmail", idPrintDecls_f, CMD_FL_SYSTEM, "prints an Email", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printVideo", idPrintDecls_f, CMD_FL_SYSTEM, "prints a Audio", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printAudio", idPrintDecls_f, CMD_FL_SYSTEM, "prints an Video", idCmdSystem::ArgCompletion_Decl ); + + cmdSystem->AddCommand( "listHuffmanFrequencies", ListHuffmanFrequencies_f, CMD_FL_SYSTEM, "lists decl text character frequencies" ); + + common->Printf( "------------------------------\n" ); +} + +/* +=================== +idDeclManagerLocal::Shutdown +=================== +*/ +void idDeclManagerLocal::Shutdown( void ) { + int i, j; + idDeclLocal *decl; + + // free decls + for ( i = 0; i < DECL_MAX_TYPES; i++ ) { + for ( j = 0; j < linearLists[i].Num(); j++ ) { + decl = linearLists[i][j]; + if ( decl->self != NULL ) { + decl->self->FreeData(); + delete decl->self; + } + if ( decl->textSource ) { + Mem_Free( decl->textSource ); + decl->textSource = NULL; + } + delete decl; + } + linearLists[i].Clear(); + hashTables[i].Free(); + } + + // free decl files + loadedFiles.DeleteContents( true ); + + // free the decl types and folders + declTypes.DeleteContents( true ); + declFolders.DeleteContents( true ); + +#ifdef USE_COMPRESSED_DECLS + ShutdownHuffman(); +#endif +} + +/* +=================== +idDeclManagerLocal::Reload +=================== +*/ +void idDeclManagerLocal::Reload( bool force ) { + for ( int i = 0; i < loadedFiles.Num(); i++ ) { + loadedFiles[i]->Reload( force ); + } +} + +/* +=================== +idDeclManagerLocal::BeginLevelLoad +=================== +*/ +void idDeclManagerLocal::BeginLevelLoad() { + insideLevelLoad = true; + + // clear all the referencedThisLevel flags and purge all the data + // so the next reference will cause a reparse + for ( int i = 0; i < DECL_MAX_TYPES; i++ ) { + int num = linearLists[i].Num(); + for ( int j = 0 ; j < num ; j++ ) { + idDeclLocal *decl = linearLists[i][j]; + decl->Purge(); + } + } +} + +/* +=================== +idDeclManagerLocal::EndLevelLoad +=================== +*/ +void idDeclManagerLocal::EndLevelLoad() { + insideLevelLoad = false; + + // we don't need to do anything here, but the image manager, model manager, + // and sound sample manager will need to free media that was not referenced +} + +#ifdef RV_SINGLE_DECL_FILE +/* +=================== +idDeclManagerLocal::StartLoadingDecls + +The retail cache name is derived from the asset-log leaf and uses .decls. +=================== +*/ +void idDeclManagerLocal::StartLoadingDecls( void ) { + const char *assetLog = fileSystem->GetAssetLogName(); + const char *leaf = assetLog ? strchr( assetLog, '/' ) : NULL; + idStr fileName = leaf ? leaf + 1 : ( assetLog ? assetLog : "" ); + fileName.SetFileExtension( ".decls" ); + mSingleDeclFile = fileName.Length() ? fileSystem->OpenFileRead( fileName ) : NULL; +} + +void idDeclManagerLocal::FinishLoadingDecls( void ) { + if ( mSingleDeclFile != NULL ) { + fileSystem->CloseFile( mSingleDeclFile ); + mSingleDeclFile = NULL; + } +} + +void idDeclManagerLocal::LoadDeclsFromFile( void ) { + // Transitional: source decl registration remains authoritative until the + // retail .decls record format and idDeclFile::LoadAndParse(idFile*) land. + if ( mSingleDeclFile != NULL ) { + common->DPrintf( "compiled decl cache detected; using source decls during reconstruction\n" ); + } +} + +void idDeclManagerLocal::WriteDeclFile( void ) { + common->Warning( "compiled .decls cache writing is not reconstructed yet" ); +} + +void idDeclManagerLocal::FlushDecls( void ) { + for ( int i = loadedFiles.Num() - 1; i >= 0; --i ) { + for ( idDeclLocal *decl = loadedFiles[i]->decls; decl != NULL; decl = decl->nextInFile ) { + if ( !decl->parsedOutsideLevelLoad ) { + decl->referencedThisLevel = false; + decl->MakeDefault(); + decl->declState = DS_UNPARSED; + } + } + } +} +#endif + +/* +=================== +idDeclManagerLocal::RegisterDeclType +=================== +*/ +void idDeclManagerLocal::RegisterDeclType( const char *typeName, declType_t type, idDecl *(*allocator)( void ) ) { + idDeclType *declType; + + if ( type < declTypes.Num() && declTypes[(int)type] ) { + common->Warning( "idDeclManager::RegisterDeclType: type '%s' already exists", typeName ); + return; + } + + declType = new idDeclType; + declType->typeName = typeName; + declType->type = type; + declType->allocator = allocator; + + if ( (int)type + 1 > declTypes.Num() ) { + declTypes.AssureSize( (int)type + 1, NULL ); + } + declTypes[type] = declType; +} + +/* +=================== +idDeclManagerLocal::RegisterDeclFolder +=================== +*/ +void idDeclManagerLocal::RegisterDeclFolder( const char *folder, const char *extension, declType_t defaultType, bool unique, bool norecurse ) { + int i, j; + idStr fileName; + idDeclFolder *declFolder; + idFileList *fileList; + idDeclFile *df; + + // check whether this folder / extension combination already exists + for ( i = 0; i < declFolders.Num(); i++ ) { + if ( declFolders[i]->folder.Icmp( folder ) == 0 && declFolders[i]->extension.Icmp( extension ) == 0 ) { + break; + } + } + if ( i < declFolders.Num() ) { + declFolder = declFolders[i]; + } else { + declFolder = new idDeclFolder; + declFolder->folder = folder; + declFolder->extension = extension; + declFolder->defaultType = defaultType; + declFolders.Append( declFolder ); + } + + // Scan this directory only. The retail decl manager performs its own + // recursion below; ListFilesTree already returns paths rooted at `folder` + // and prepending the folder again produces materials/materials/... + fileList = fileSystem->ListFiles( declFolder->folder, declFolder->extension, true ); + // load and parse decl files + for ( i = 0; i < fileList->GetNumFiles(); i++ ) { + fileName = declFolder->folder + "/" + fileList->GetFile( i ); + + // check whether this file has already been loaded + for ( j = 0; j < loadedFiles.Num(); j++ ) { + if ( fileName.Icmp( loadedFiles[j]->fileName ) == 0 ) { + break; + } + } + if ( j < loadedFiles.Num() ) { + df = loadedFiles[j]; + } else { + df = new idDeclFile( fileName, defaultType ); + loadedFiles.Append( df ); + } + df->LoadAndParse( unique ); + } + + fileSystem->FreeFileList( fileList ); + + if ( !norecurse ) { + idFileList *folderList = fileSystem->ListFiles( folder, "/", true ); + for ( i = 0; i < folderList->GetNumFiles(); ++i ) { + const char *subFolder = folderList->GetFile( i ); + if ( subFolder[0] == '\0' || subFolder[0] == '.' || subFolder[1] == '.' ) { + continue; + } + fileName = folder; + fileName += "/"; + fileName += subFolder; + RegisterDeclFolder( fileName, extension, defaultType, unique, false ); + } + fileSystem->FreeFileList( folderList ); + } +} + +/* +=================== +idDeclManagerLocal::GetTotalTextMemory +=================== +*/ +int idDeclManagerLocal::GetTotalTextMemory( declType_t type ) { + int total = 0; + const int num = GetNumDecls( type ); + for ( int i = 0; i < num; ++i ) { + const idDecl *decl = DeclByIndex( type, i, false ); + if ( decl != NULL && decl->base != NULL ) { + total += decl->base->GetCompressedLength(); + } + } + return total; +} + +/* +=================== +idDeclManagerLocal::RegisterDeclFolderWrapper +=================== +*/ +void idDeclManagerLocal::RegisterDeclFolderWrapper( const char *folder, const char *extension, declType_t defaultType, bool unique, bool norecurse ) { + const int start = Sys_Milliseconds(); + RegisterDeclFolder( folder, extension, defaultType, unique, norecurse ); + const int memory = GetTotalTextMemory( defaultType ); + common->Printf( "%dms to load %dk of %s\n", Sys_Milliseconds() - start, ( memory + 1023 ) / 1024, GetDeclTypeName( defaultType ) ); +} + +/* +=================== +idDeclManagerLocal::GetChecksum +=================== +*/ +int idDeclManagerLocal::GetChecksum( void ) const { + int i, j, total, num; + int *checksumData; + + // get the total number of decls + total = 0; + for ( i = 0; i < DECL_MAX_TYPES; i++ ) { + total += linearLists[i].Num(); + } + + checksumData = (int *) _alloca16( total * 2 * sizeof( int ) ); + + total = 0; + for ( i = 0; i < DECL_MAX_TYPES; i++ ) { + declType_t type = (declType_t) i; + + // FIXME: not particularly pretty but PDAs and associated decls are localized and should not be checksummed + if ( type == DECL_PDA || type == DECL_VIDEO || type == DECL_AUDIO || type == DECL_EMAIL ) { + continue; + } + + num = linearLists[i].Num(); + for ( j = 0; j < num; j++ ) { + idDeclLocal *decl = linearLists[i][j]; + + if ( decl->sourceFile == &implicitDecls ) { + continue; + } + + checksumData[total*2+0] = total; + checksumData[total*2+1] = decl->checksum; + total++; + } + } + + LittleRevBytes( checksumData, sizeof(int), total * 2 ); + return MD5_BlockChecksum( checksumData, total * 2 * sizeof( int ) ); +} + +/* +=================== +idDeclManagerLocal::GetNumDeclTypes +=================== +*/ +int idDeclManagerLocal::GetNumDeclTypes( void ) const { + return declTypes.Num(); +} + +/* +=================== +idDeclManagerLocal::GetDeclNameFromType +=================== +*/ +const char * idDeclManagerLocal::GetDeclNameFromType( declType_t type ) const { + int typeIndex = (int)type; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::GetDeclNameFromType: bad type: %i", typeIndex ); + } + return declTypes[typeIndex]->typeName; +} + +/* +=================== +idDeclManagerLocal::GetDeclTypeFromName +=================== +*/ +declType_t idDeclManagerLocal::GetDeclTypeFromName( const char *typeName ) const { + int i; + + for ( i = 0; i < declTypes.Num(); i++ ) { + if ( declTypes[i] && declTypes[i]->typeName.Icmp( typeName ) == 0 ) { + return (declType_t)declTypes[i]->type; + } + } + return DECL_MAX_TYPES; +} + +/* +================= +idDeclManagerLocal::FindType + +External users will always cause the decl to be parsed before returning +================= +*/ +const idDecl *idDeclManagerLocal::FindType( declType_t type, const char *name, bool makeDefault, bool noCaching ) { + idDeclLocal *decl; + + if ( !name || !name[0] ) { + name = "_emptyName"; + //common->Warning( "idDeclManager::FindType: empty %s name", GetDeclType( (int)type )->typeName.c_str() ); + } + + decl = FindTypeWithoutParsing( type, name, makeDefault, -1 ); + if ( !decl ) { + return NULL; + } + + decl->AllocateSelf(); + + // if it hasn't been parsed yet, parse it now + if ( decl->declState == DS_UNPARSED ) { + decl->ParseLocal( noCaching ); + } + + // mark it as referenced + decl->referencedThisLevel = true; + decl->everReferenced = true; + if ( insideLevelLoad ) { + decl->parsedOutsideLevelLoad = false; + } + + return decl->self; +} + +/* +=============== +idDeclManagerLocal::FindDeclWithoutParsing +=============== +*/ +const idDecl* idDeclManagerLocal::FindDeclWithoutParsing( declType_t type, const char *name, bool makeDefault) { + idDeclLocal* decl; + decl = FindTypeWithoutParsing(type, name, makeDefault); + if(decl) { + return decl->self; + } + return NULL; +} + +/* +=============== +idDeclManagerLocal::ReloadFile +=============== +*/ +void idDeclManagerLocal::ReloadFile( const char* filename, bool force ) { + for ( int i = 0; i < loadedFiles.Num(); i++ ) { + if(!loadedFiles[i]->fileName.Icmp(filename)) { + checksum ^= loadedFiles[i]->checksum; + loadedFiles[i]->Reload( force ); + checksum ^= loadedFiles[i]->checksum; + } + } +} + +/* +=================== +idDeclManagerLocal::GetNumDecls +=================== +*/ +int idDeclManagerLocal::GetNumDecls( declType_t type ) { + int typeIndex = (int)type; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::GetNumDecls: bad type: %i", typeIndex ); + } + return linearLists[ typeIndex ].Num(); +} + +/* +=================== +idDeclManagerLocal::DeclByIndex +=================== +*/ +const idDecl *idDeclManagerLocal::DeclByIndex( declType_t type, int index, bool forceParse ) { + int typeIndex = (int)type; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::DeclByIndex: bad type: %i", typeIndex ); + } + if ( index < 0 || index >= linearLists[ typeIndex ].Num() ) { + common->Error( "idDeclManager::DeclByIndex: out of range" ); + } + idDeclLocal *decl = linearLists[ typeIndex ][ index ]; + + decl->AllocateSelf(); + + if ( forceParse && decl->declState == DS_UNPARSED ) { + decl->ParseLocal(); + } + + return decl->self; +} + +/* +=================== +idDeclManagerLocal::ListType + +list* +Lists decls currently referenced + +list* ever +Lists decls that have been referenced at least once since app launched + +list* all +Lists every decl declared, even if it hasn't been referenced or parsed + +FIXME: alphabetized, wildcards? +=================== +*/ +void idDeclManagerLocal::ListType( const idCmdArgs &args, declType_t type ) { + bool all, ever; + + if ( !idStr::Icmp( args.Argv( 1 ), "all" ) ) { + all = true; + } else { + all = false; + } + if ( !idStr::Icmp( args.Argv( 1 ), "ever" ) ) { + ever = true; + } else { + ever = false; + } + + common->Printf( "--------------------\n" ); + int printed = 0; + int count = linearLists[ (int)type ].Num(); + for ( int i = 0 ; i < count ; i++ ) { + idDeclLocal *decl = linearLists[ (int)type ][ i ]; + + if ( !all && decl->declState == DS_UNPARSED ) { + continue; + } + + if ( !all && !ever && !decl->referencedThisLevel ) { + continue; + } + + if ( decl->referencedThisLevel ) { + common->Printf( "*" ); + } else if ( decl->everReferenced ) { + common->Printf( "." ); + } else { + common->Printf( " " ); + } + if ( decl->declState == DS_DEFAULTED ) { + common->Printf( "D" ); + } else { + common->Printf( " " ); + } + common->Printf( "%4i: ", decl->index ); + printed++; + if ( decl->declState == DS_UNPARSED ) { + // doesn't have any type specific data yet + common->Printf( "%s\n", decl->GetName() ); + } else { + decl->self->List(); + } + } + + common->Printf( "--------------------\n" ); + common->Printf( "%i of %i %s\n", printed, count, declTypes[type]->typeName.c_str() ); +} + +/* +=================== +idDeclManagerLocal::PrintType +=================== +*/ +void idDeclManagerLocal::PrintType( const idCmdArgs &args, declType_t type ) { + // individual decl types may use additional command parameters + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: Print [type specific parms]\n" ); + return; + } + + // look it up, skipping the public path so it won't parse or reference + idDeclLocal *decl = FindTypeWithoutParsing( type, args.Argv( 1 ), false ); + if ( !decl ) { + common->Printf( "%s '%s' not found.\n", declTypes[ type ]->typeName.c_str(), args.Argv( 1 ) ); + return; + } + + // print information common to all decls + common->Printf( "%s %s:\n", declTypes[ type ]->typeName.c_str(), decl->name.c_str() ); + common->Printf( "source: %s:%i\n", decl->sourceFile->fileName.c_str(), decl->sourceLine ); + common->Printf( "----------\n" ); + if ( decl->textSource != NULL ) { + char *declText = (char *)_alloca( decl->textLength + 1 ); + decl->GetText( declText ); + common->Printf( "%s\n", declText ); + } else { + common->Printf( "NO SOURCE\n" ); + } + common->Printf( "----------\n" ); + switch( decl->declState ) { + case DS_UNPARSED: + common->Printf( "Unparsed.\n" ); + break; + case DS_DEFAULTED: + common->Printf( "\n" ); + break; + case DS_PARSED: + common->Printf( "Parsed.\n" ); + break; + } + + if ( decl->referencedThisLevel ) { + common->Printf( "Currently referenced this level.\n" ); + } else if ( decl->everReferenced ) { + common->Printf( "Referenced in a previous level.\n" ); + } else { + common->Printf( "Never referenced.\n" ); + } + + // allow type-specific data to be printed + if ( decl->self != NULL ) { + decl->self->Print(); + } +} + +/* +=================== +idDeclManagerLocal::CreateNewDecl +=================== +*/ +idDecl *idDeclManagerLocal::CreateNewDecl( declType_t type, const char *name, const char *_fileName ) { + int typeIndex = (int)type; + int i, hash; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::CreateNewDecl: bad type: %i", typeIndex ); + } + + char canonicalName[MAX_STRING_CHARS]; + + MakeNameCanonical( name, canonicalName, sizeof( canonicalName ) ); + + idStr fileName = _fileName; + fileName.BackSlashesToSlashes(); + + // see if it already exists + hash = hashTables[typeIndex].GenerateKey( canonicalName, false ); + for ( i = hashTables[typeIndex].First( hash ); i >= 0; i = hashTables[typeIndex].Next( i ) ) { + if ( linearLists[typeIndex][i]->name.Icmp( canonicalName ) == 0 ) { + linearLists[typeIndex][i]->AllocateSelf(); + return linearLists[typeIndex][i]->self; + } + } + + idDeclFile *sourceFile; + + // find existing source file or create a new one + for ( i = 0; i < loadedFiles.Num(); i++ ) { + if ( loadedFiles[i]->fileName.Icmp( fileName ) == 0 ) { + break; + } + } + if ( i < loadedFiles.Num() ) { + sourceFile = loadedFiles[i]; + } else { + sourceFile = new idDeclFile( fileName, type ); + loadedFiles.Append( sourceFile ); + } + + idDeclLocal *decl = new idDeclLocal; + decl->name = canonicalName; + decl->type = type; + decl->declState = DS_UNPARSED; + decl->AllocateSelf(); + idStr header = declTypes[typeIndex]->typeName; + idStr defaultText = decl->self->DefaultDefinition(); + + + int size = header.Length() + 1 + idStr::Length( canonicalName ) + 1 + defaultText.Length(); + char *declText = ( char * ) _alloca( size + 1 ); + + memcpy( declText, header, header.Length() ); + declText[header.Length()] = ' '; + memcpy( declText + header.Length() + 1, canonicalName, idStr::Length( canonicalName ) ); + declText[header.Length() + 1 + idStr::Length( canonicalName )] = ' '; + memcpy( declText + header.Length() + 1 + idStr::Length( canonicalName ) + 1, defaultText, defaultText.Length() + 1 ); + + decl->SetTextLocal( declText, size ); + decl->sourceFile = sourceFile; + decl->sourceTextOffset = sourceFile->fileSize; + decl->sourceTextLength = 0; + decl->sourceLine = sourceFile->numLines; + + decl->ParseLocal(); + + // add this decl to the source file list + decl->nextInFile = sourceFile->decls; + sourceFile->decls = decl; + + // add it to the hash table and linear list + decl->index = linearLists[typeIndex].Num(); + hashTables[typeIndex].Add( hash, linearLists[typeIndex].Append( decl ) ); + + return decl->self; +} + +/* +=============== +idDeclManagerLocal::RenameDecl +=============== +*/ +bool idDeclManagerLocal::RenameDecl( declType_t type, const char* oldName, const char* newName ) { + + char canonicalOldName[MAX_STRING_CHARS]; + MakeNameCanonical( oldName, canonicalOldName, sizeof( canonicalOldName )); + + char canonicalNewName[MAX_STRING_CHARS]; + MakeNameCanonical( newName, canonicalNewName, sizeof( canonicalNewName ) ); + + idDeclLocal *decl = NULL; + + // make sure it already exists + int typeIndex = (int)type; + int i, hash; + hash = hashTables[typeIndex].GenerateKey( canonicalOldName, false ); + for ( i = hashTables[typeIndex].First( hash ); i >= 0; i = hashTables[typeIndex].Next( i ) ) { + if ( linearLists[typeIndex][i]->name.Icmp( canonicalOldName ) == 0 ) { + decl = linearLists[typeIndex][i]; + break; + } + } + if(!decl) + return false; + + //if ( !hashTables[(int)type].Get( canonicalOldName, &declPtr ) ) + // return false; + + //decl = *declPtr; + + //Change the name + decl->name = canonicalNewName; + + + // add it to the hash table + //hashTables[(int)decl->type].Set( decl->name, decl ); + int newhash = hashTables[typeIndex].GenerateKey( canonicalNewName, false ); + hashTables[typeIndex].Add( newhash, decl->index ); + + //Remove the old hash item + hashTables[typeIndex].Remove(hash, decl->index); + + return true; +} + +/* +=================== +idDeclManagerLocal::MediaPrint + +This is just used to nicely indent media caching prints +=================== +*/ +void idDeclManagerLocal::MediaPrint( const char *fmt, ... ) { + if ( !decl_show.GetInteger() ) { + return; + } + for ( int i = 0 ; i < indent ; i++ ) { + common->Printf( " " ); + } + va_list argptr; + char buffer[1024]; + va_start (argptr,fmt); + idStr::vsnPrintf( buffer, sizeof(buffer), fmt, argptr ); + va_end (argptr); + buffer[sizeof(buffer)-1] = '\0'; + + common->Printf( "%s", buffer ); +} + +/* +=================== +idDeclManagerLocal::WritePrecacheCommands +=================== +*/ +void idDeclManagerLocal::WritePrecacheCommands( idFile *f ) { + for ( int i = 0; i < declTypes.Num(); i++ ) { + int num; + + if ( declTypes[i] == NULL ) { + continue; + } + + num = linearLists[i].Num(); + + for ( int j = 0 ; j < num ; j++ ) { + idDeclLocal *decl = linearLists[i][j]; + + if ( !decl->referencedThisLevel ) { + continue; + } + + char str[1024]; + sprintf( str, "touch %s %s\n", declTypes[i]->typeName.c_str(), decl->GetName() ); + common->Printf( "%s", str ); + f->Printf( "%s", str ); + } + } +} + +/* +==================================================================================== + + Declaration guides + +==================================================================================== +*/ + +rvDeclGuide::rvDeclGuide( idStr &name ) : mName( name ), mNumParms( 0 ) { +} + +rvDeclGuide::~rvDeclGuide( void ) { +} + +void rvDeclGuide::SetParm( int index, const char *value ) { + if ( index >= 0 && index < MAX_GUIDE_PARMS ) { + mParms[index] = value; + } +} + +void rvDeclGuide::Parse( idLexer *src ) { + idToken token; + int commas = 0; + + src->ExpectTokenString( "(" ); + mNumParms = 0; + while ( src->ReadToken( &token ) && token != ")" ) { + if ( token == "," ) { + ++commas; + continue; + } + if ( mNumParms < MAX_GUIDE_PARMS ) { + mParms[mNumParms] = token; + } + ++mNumParms; + } + if ( commas != mNumParms - 1 ) { + src->Warning( "Guide name '%s' only contains %d commas for %d args, expecting %d! Typo?\n", + mName.c_str(), commas, mNumParms, mNumParms - 1 ); + } + src->ParseBracedSectionExact( mDefinition, -1 ); +} + +void rvDeclGuide::RemoveOuterBracing( void ) { + const int open = mDefinition.Find( '{' ); + const int close = mDefinition.Last( '}' ); + if ( open >= 0 && close > open ) { + mDefinition = mDefinition.Mid( open + 1, close - open - 1 ); + } +} + +bool rvDeclGuide::Evaluate( idLexer *src, idStr &definition ) { + idToken token; + idStr values[MAX_GUIDE_PARMS]; + int numValues = 0; + + if ( !src->ExpectTokenString( "(" ) ) { + return false; + } + while ( numValues < MAX_GUIDE_PARMS && src->ReadToken( &token ) ) { + if ( token == ")" ) { + break; + } + if ( token == "," ) { + continue; + } + values[numValues++] = token; + } + if ( numValues != mNumParms ) { + return false; + } + + definition = mDefinition; + for ( int i = 0; i < mNumParms; ++i ) { + definition.Replace( mParms[i], values[i] ); + } + return true; +} + +rvDeclGuide *idDeclManagerLocal::GetNewGuide( idLexer *src, idStr &file ) { + idToken name; + if ( !src->ReadToken( &name ) ) { + return NULL; + } + rvDeclGuide **existing = NULL; + if ( guideTable.Get( name.c_str(), &existing ) ) { + common->Printf( "Guide file '%s' contains duplicate guide '%s'\n", file.c_str(), name.c_str() ); + src->SkipBracedSection( true ); + return NULL; + } + idStr guideName = name; + rvDeclGuide *guide = new rvDeclGuide( guideName ); + guide->Parse( src ); + return guide; +} + +void idDeclManagerLocal::ParseGuides( void ) { + common->Printf( "Loading guides.... " ); + guideTable.DeleteContents(); + + idFileList *guideFiles = fileSystem->ListFiles( "guides", ".guide", true ); + idLexer src; + src.SetFlags( DECL_LEXER_FLAGS ); + for ( int i = 0; i < guideFiles->GetNumFiles(); ++i ) { + idStr file = "guides/"; + file += guideFiles->GetFile( i ); + if ( !src.LoadFile( file ) ) { + fileSystem->FreeFileList( guideFiles ); + common->FatalError( "Couldn't load %s", file.c_str() ); + return; + } + + idToken token; + while ( src.ReadToken( &token ) ) { + const bool inlineGuide = token.Icmp( "inlineGuide" ) == 0; + if ( !inlineGuide && token.Icmp( "guide" ) != 0 ) { + common->Printf( "Guide file '%s' has unknown token '%s'\n", file.c_str(), token.c_str() ); + break; + } + rvDeclGuide *guide = GetNewGuide( &src, file ); + if ( guide == NULL ) { + continue; + } + if ( inlineGuide ) { + guide->RemoveOuterBracing(); + } + rvDeclGuide *value = guide; + guideTable.Set( guide->GetName(), value ); + } + src.FreeSource(); + } + fileSystem->FreeFileList( guideFiles ); + common->Printf( "%d loaded\n", guideTable.Num() ); +} + +void idDeclManagerLocal::ShutdownGuides( void ) { + guideTable.DeleteContents(); +} + +bool idDeclManagerLocal::EvaluateGuide( idStr &name, idLexer *src, idStr &definition ) { + idToken guideName; + definition.Clear(); + if ( !src->ReadToken( &guideName ) ) { + return false; + } + rvDeclGuide **guide = NULL; + if ( !guideTable.Get( guideName.c_str(), &guide ) || guide == NULL || *guide == NULL ) { + src->Warning( "Guide name '%s' not found in '%s'", guideName.c_str(), name.c_str() ); + src->SkipRestOfLine(); + return false; + } + return ( *guide )->Evaluate( src, definition ); +} + +bool idDeclManagerLocal::EvaluateInlineGuide( idStr &name, idStr &definition ) { + int scanOffset = 0; + for ( ;; ) { + idStr source = definition; + idLexer lexer; + lexer.LoadMemory( source.c_str(), source.Length(), name.c_str(), 1 ); + lexer.SetFlags( DECL_LEXER_FLAGS ); + + idToken token; + bool expandedOne = false; + while ( lexer.ReadToken( &token ) ) { + const int tokenEnd = lexer.GetFileOffset(); + const int tokenStart = tokenEnd - token.Length(); + if ( tokenStart < scanOffset || token.Icmp( "inlineGuide" ) != 0 ) { + continue; + } + idToken guideName; + if ( !lexer.ReadToken( &guideName ) ) { + return false; + } + rvDeclGuide **guide = NULL; + if ( !guideTable.Get( guideName.c_str(), &guide ) || guide == NULL || *guide == NULL ) { + return false; + } + idStr expanded; + if ( !( *guide )->Evaluate( &lexer, expanded ) ) { + return false; + } + const int consumedEnd = lexer.GetFileOffset(); + definition = source.Left( tokenStart ); + definition += expanded; + definition += source.Right( source.Length() - consumedEnd ); + scanOffset = tokenStart + expanded.Length(); + expandedOne = true; + break; + } + if ( !expandedOne ) { + return true; + } + } +} + +/********************************************************************/ + +const idMaterial *idDeclManagerLocal::FindMaterial( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_MATERIAL, name, makeDefault ) ); +} + +const idDeclTable *idDeclManagerLocal::FindTable( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_TABLE, name, makeDefault ) ); +} + +const idMaterial *idDeclManagerLocal::MaterialByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_MATERIAL, index, forceParse ) ); +} + +const idDeclTable *idDeclManagerLocal::TableByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_TABLE, index, forceParse ) ); +} + +/********************************************************************/ + +const idDeclSkin *idDeclManagerLocal::FindSkin( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_SKIN, name, makeDefault ) ); +} + +const idDeclSkin *idDeclManagerLocal::SkinByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_SKIN, index, forceParse ) ); +} + +/********************************************************************/ + +const idSoundShader *idDeclManagerLocal::FindSound( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_SOUND, name, makeDefault ) ); +} + +const idSoundShader *idDeclManagerLocal::SoundByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_SOUND, index, forceParse ) ); +} + +const rvDeclMatType *idDeclManagerLocal::FindMaterialType( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_MATERIALTYPE, name, makeDefault ) ); +} + +const rvDeclMatType *idDeclManagerLocal::MaterialTypeByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_MATERIALTYPE, index, forceParse ) ); +} + +const rvDeclLipSync *idDeclManagerLocal::FindLipSync( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_LIPSYNC, name, makeDefault ) ); +} + +const rvDeclLipSync *idDeclManagerLocal::LipSyncByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_LIPSYNC, index, forceParse ) ); +} + +const rvDeclPlayback *idDeclManagerLocal::FindPlayback( const char *name, bool makeDefault ) { + return static_cast( FindType( DECL_PLAYBACK, name, makeDefault ) ); +} + +const rvDeclPlayback *idDeclManagerLocal::PlaybackByIndex( int index, bool forceParse ) { + return static_cast( DeclByIndex( DECL_PLAYBACK, index, forceParse ) ); +} + +const rvDeclEffect *idDeclManagerLocal::FindEffect( const char *name, bool makeDefault ) { + return reinterpret_cast( FindType( DECL_EFFECT, name, makeDefault ) ); +} + +const rvDeclEffect *idDeclManagerLocal::EffectByIndex( int index, bool forceParse ) { + return reinterpret_cast( DeclByIndex( DECL_EFFECT, index, forceParse ) ); +} + +void idDeclManagerLocal::StartPlaybackRecord( rvDeclPlayback *playback ) { + playback->Start(); +} + +bool idDeclManagerLocal::SetPlaybackData( rvDeclPlayback *playback, int now, int control, rvDeclPlaybackData *pbd ) { + return playback->SetCurrentData( now * 0.001f, control, pbd ); +} + +bool idDeclManagerLocal::GetPlaybackData( const rvDeclPlayback *playback, int control, int now, int last, rvDeclPlaybackData *pbd ) { + if ( playback == NULL ) { + return true; + } + return playback->GetCurrentData( control, now * 0.001f, last * 0.001f, pbd ); +} + +bool idDeclManagerLocal::FinishPlayback( rvDeclPlayback *playback ) { + return playback->Finish( -1.0f ); +} + +idStr idDeclManagerLocal::GetNewName( declType_t type, const char *base ) { + idStr name; + GetNumDecls( type ); + for ( int suffix = 1; suffix < 1024; ++suffix ) { + name = va( "%s%d", base, suffix ); + if ( FindType( type, name, false, false ) == NULL ) { + return name; + } + } + return "*unknown*"; +} + +const char *idDeclManagerLocal::GetDeclTypeName( declType_t type ) { + return declTypes[type]->typeName.c_str(); +} + +size_t idDeclManagerLocal::ListDeclSummary( const idCmdArgs &args ) { + (void)args; + size_t totalStructs = 0; + int totalDecls = 0; + for ( int i = 0; i < declTypes.Num(); ++i ) { + if ( declTypes[i] == NULL ) { + continue; + } + const int num = linearLists[i].Num(); + totalDecls += num; + for ( int j = 0; j < num; ++j ) { + idDeclLocal *decl = linearLists[i][j]; + totalStructs += decl->Size(); + if ( decl->self != NULL ) { + totalStructs += decl->self->Size(); + } + } + } + common->Printf( "Decls - %dK in %d decl structs\n", totalStructs >> 10, totalDecls ); + return totalStructs >> 10; +} + +void idDeclManagerLocal::RemoveDeclFile( const char *file ) { + for ( int i = 0; i < loadedFiles.Num(); ++i ) { + if ( loadedFiles[i]->fileName.Icmp( file ) == 0 ) { + loadedFiles.RemoveIndex( i ); + return; + } + } +} + +bool idDeclManagerLocal::Validate( declType_t type, int index, idStr &report ) { + const idDecl *decl = DeclByIndex( type, index, false ); + if ( decl == NULL || decl->base == NULL ) { + report = "invalid declaration"; + return false; + } + const int length = decl->base->GetTextLength(); + char *text = (char *)_alloca( length + 1 ); + decl->base->GetText( text ); + return decl->Validate( text, length, report ); +} + +idDecl *idDeclManagerLocal::AllocateDecl( declType_t type ) { + idDeclLocal *base = new idDeclLocal; + idDecl *decl = declTypes[type]->allocator(); + decl->base = base; + base->self = decl; + base->type = type; + return decl; +} + +/* +=================== +idDeclManagerLocal::MakeNameCanonical +=================== +*/ +void idDeclManagerLocal::MakeNameCanonical( const char *name, char *result, int maxLength ) { + int i, lastDot; + + lastDot = -1; + for ( i = 0; i < maxLength && name[i] != '\0'; i++ ) { + int c = name[i]; + if ( c == '\\' ) { + result[i] = '/'; + } else if ( c == '.' ) { + lastDot = i; + result[i] = c; + } else { + result[i] = idStr::ToLower( c ); + } + } + if ( lastDot != -1 ) { + result[lastDot] = '\0'; + } else { + result[i] = '\0'; + } +} + +/* +================ +idDeclManagerLocal::ListDecls_f +================ +*/ +void idDeclManagerLocal::ListDecls_f( const idCmdArgs &args ) { + int i, j; + int totalDecls = 0; + int totalText = 0; + int totalStructs = 0; + + for ( i = 0; i < declManagerLocal.declTypes.Num(); i++ ) { + int size, num; + + if ( declManagerLocal.declTypes[i] == NULL ) { + continue; + } + + num = declManagerLocal.linearLists[i].Num(); + totalDecls += num; + + size = 0; + for ( j = 0; j < num; j++ ) { + size += declManagerLocal.linearLists[i][j]->Size(); + if ( declManagerLocal.linearLists[i][j]->self != NULL ) { + size += declManagerLocal.linearLists[i][j]->self->Size(); + } + } + totalStructs += size; + + common->Printf( "%4ik %4i %s\n", size >> 10, num, declManagerLocal.declTypes[i]->typeName.c_str() ); + } + + for ( i = 0 ; i < declManagerLocal.loadedFiles.Num() ; i++ ) { + idDeclFile *df = declManagerLocal.loadedFiles[i]; + totalText += df->fileSize; + } + + common->Printf( "%i total decls is %i decl files\n", totalDecls, declManagerLocal.loadedFiles.Num() ); + common->Printf( "%iKB in text, %iKB in structures\n", totalText >> 10, totalStructs >> 10 ); +} + +/* +=================== +idDeclManagerLocal::ReloadDecls_f + +Reload will not find any new files created in the directories, it +will only reload existing files. + +A reload will never cause anything to be purged. +=================== +*/ +void idDeclManagerLocal::ReloadDecls_f( const idCmdArgs &args ) { + bool force; + + if ( !idStr::Icmp( args.Argv( 1 ), "all" ) ) { + force = true; + common->Printf( "reloading all decl files:\n" ); + } else { + force = false; + common->Printf( "reloading changed decl files:\n" ); + } + + soundSystem->SetMute( true ); + + declManagerLocal.Reload( force ); + + soundSystem->SetMute( false ); +} + +/* +=================== +idDeclManagerLocal::TouchDecl_f +=================== +*/ +void idDeclManagerLocal::TouchDecl_f( const idCmdArgs &args ) { + int i; + + if ( args.Argc() != 3 ) { + common->Printf( "usage: touch \n" ); + common->Printf( "valid types: " ); + for ( int i = 0 ; i < declManagerLocal.declTypes.Num() ; i++ ) { + if ( declManagerLocal.declTypes[i] ) { + common->Printf( "%s ", declManagerLocal.declTypes[i]->typeName.c_str() ); + } + } + common->Printf( "\n" ); + return; + } + + for ( i = 0; i < declManagerLocal.declTypes.Num(); i++ ) { + if ( declManagerLocal.declTypes[i] && declManagerLocal.declTypes[i]->typeName.Icmp( args.Argv( 1 ) ) == 0 ) { + break; + } + } + if ( i >= declManagerLocal.declTypes.Num() ) { + common->Printf( "unknown decl type '%s'\n", args.Argv( 1 ) ); + return; + } + + const idDecl *decl = declManagerLocal.FindType( (declType_t)i, args.Argv( 2 ), false ); + if ( !decl ) { + common->Printf( "%s '%s' not found\n", declManagerLocal.declTypes[i]->typeName.c_str(), args.Argv( 2 ) ); + } +} + +/* +=================== +idDeclManagerLocal::FindTypeWithoutParsing + +This finds or creats the decl, but does not cause a parse. This is only used internally. +=================== +*/ +idDeclLocal *idDeclManagerLocal::FindTypeWithoutParsing( declType_t type, const char *name, bool makeDefault, int indexToStoreAt ) { + int typeIndex = (int)type; + int i, hash; + + if ( typeIndex < 0 || typeIndex >= declTypes.Num() || declTypes[typeIndex] == NULL ) { + common->FatalError( "idDeclManager::FindTypeWithoutParsing: bad type: %i", typeIndex ); + } + + char canonicalName[MAX_STRING_CHARS]; + + MakeNameCanonical( name, canonicalName, sizeof( canonicalName ) ); + + // see if it already exists + hash = hashTables[typeIndex].GenerateKey( canonicalName, false ); + for ( i = hashTables[typeIndex].First( hash ); i >= 0; i = hashTables[typeIndex].Next( i ) ) { + if ( linearLists[typeIndex][i]->name.Icmp( canonicalName ) == 0 ) { + // only print these when decl_show is set to 2, because it can be a lot of clutter + if ( decl_show.GetInteger() > 1 ) { + MediaPrint( "referencing %s %s\n", declTypes[ type ]->typeName.c_str(), name ); + } + return linearLists[typeIndex][i]; + } + } + + if ( !makeDefault ) { + return NULL; + } + + idDeclLocal *decl = new idDeclLocal; + decl->self = NULL; + decl->name = canonicalName; + decl->type = type; + decl->declState = DS_UNPARSED; + decl->textSource = NULL; + decl->textLength = 0; + decl->sourceFile = &implicitDecls; + decl->referencedThisLevel = false; + decl->everReferenced = false; + decl->parsedOutsideLevelLoad = !insideLevelLoad; + + // add it to the linear list and hash table + decl->index = linearLists[typeIndex].Num(); + (void)indexToStoreAt; + hashTables[typeIndex].Add( hash, linearLists[typeIndex].Append( decl ) ); + + return decl; +} + + +/* +==================================================================================== + + idDeclLocal + +==================================================================================== +*/ + +/* +================= +idDeclLocal::idDeclLocal +================= +*/ +idDeclLocal::idDeclLocal( void ) { + name = "unnamed"; + textSource = NULL; + textLength = 0; + compressedLength = 0; + sourceFile = NULL; + sourceTextOffset = 0; + sourceTextLength = 0; + sourceLine = 0; + checksum = 0; + type = DECL_ENTITYDEF; + index = 0; + declState = DS_UNPARSED; + parsedOutsideLevelLoad = false; + referencedThisLevel = false; + everReferenced = false; + redefinedInReload = false; + nextInFile = NULL; + needsPrecache = false; +} + +/* +================= +idDeclLocal::GetName +================= +*/ +const char *idDeclLocal::GetName( void ) const { + return name.c_str(); +} + +/* +================= +idDeclLocal::GetType +================= +*/ +declType_t idDeclLocal::GetType( void ) const { + return type; +} + +/* +================= +idDeclLocal::GetState +================= +*/ +declState_t idDeclLocal::GetState( void ) const { + return declState; +} + +/* +================= +idDeclLocal::IsImplicit +================= +*/ +bool idDeclLocal::IsImplicit( void ) const { + return ( sourceFile == declManagerLocal.GetImplicitDeclFile() ); +} + +/* +================= +idDeclLocal::IsValid +================= +*/ +bool idDeclLocal::IsValid( void ) const { + return ( declState != DS_UNPARSED ); +} + +/* +================= +idDeclLocal::Invalidate +================= +*/ +void idDeclLocal::Invalidate( void ) { + declState = DS_UNPARSED; +} + +/* +================= +idDeclLocal::EnsureNotPurged +================= +*/ +void idDeclLocal::EnsureNotPurged( void ) { + if ( declState == DS_UNPARSED ) { + ParseLocal(); + } +} + +/* +================= +idDeclLocal::Index +================= +*/ +int idDeclLocal::Index( void ) const { + return index; +} + +/* +================= +idDeclLocal::GetLineNum +================= +*/ +int idDeclLocal::GetLineNum( void ) const { + return sourceLine; +} + +/* +================= +idDeclLocal::GetFileName +================= +*/ +const char *idDeclLocal::GetFileName( void ) const { + return ( sourceFile ) ? sourceFile->fileName.c_str() : "*invalid*"; +} + +/* +================= +idDeclLocal::Size +================= +*/ +size_t idDeclLocal::Size( void ) const { + return sizeof( idDecl ) + name.Allocated(); +} + +/* +================= +idDeclLocal::GetText +================= +*/ +void idDeclLocal::GetText( char *text ) const { +#ifdef USE_COMPRESSED_DECLS + HuffmanDecompressText( text, textLength, (byte *)textSource, compressedLength ); +#else + memcpy( text, textSource, textLength+1 ); +#endif +} + +/* +================= +idDeclLocal::GetTextLength +================= +*/ +int idDeclLocal::GetTextLength( void ) const { + return textLength; +} + +/* +================= +idDeclLocal::GetCompressedLength +================= +*/ +int idDeclLocal::GetCompressedLength( void ) const { + return compressedLength; +} + +/* +================= +idDeclLocal::SetText +================= +*/ +void idDeclLocal::SetText( const char *text ) { + SetTextLocal( text, idStr::Length( text ) ); +} + +/* +================= +idDeclLocal::SetTextLocal +================= +*/ +void idDeclLocal::SetTextLocal( const char *text, const int length ) { + + Mem_Free( textSource ); + + checksum = MD5_BlockChecksum( text, length ); + +#ifdef GET_HUFFMAN_FREQUENCIES + for( int i = 0; i < length; i++ ) { + huffmanFrequencies[((const unsigned char *)text)[i]]++; + } +#endif + +#ifdef USE_COMPRESSED_DECLS + int maxBytesPerCode = ( maxHuffmanBits + 7 ) >> 3; + byte *compressed = (byte *)_alloca( length * maxBytesPerCode ); + compressedLength = HuffmanCompressText( text, length, compressed, length * maxBytesPerCode ); + textSource = (char *)Mem_Alloc( compressedLength ); + memcpy( textSource, compressed, compressedLength ); +#else + compressedLength = length; + textSource = (char *) Mem_Alloc( length + 1 ); + memcpy( textSource, text, length ); + textSource[length] = '\0'; +#endif + textLength = length; +} + +/* +================= +idDeclLocal::ReplaceSourceFileText +================= +*/ +bool idDeclLocal::ReplaceSourceFileText( void ) { + int oldFileLength, newFileLength; + char *buffer; + idFile *file; + + common->Printf( "Writing \'%s\' to \'%s\'...\n", GetName(), GetFileName() ); + + if ( sourceFile == &declManagerLocal.implicitDecls ) { + common->Warning( "Can't save implicit declaration %s.", GetName() ); + return false; + } + + // get length and allocate buffer to hold the file + oldFileLength = sourceFile->fileSize; + newFileLength = oldFileLength - sourceTextLength + textLength; + buffer = (char *) Mem_Alloc( Max( newFileLength, oldFileLength ) ); + + // read original file + if ( sourceFile->fileSize ) { + + file = fileSystem->OpenFileRead( GetFileName() ); + if ( !file ) { + Mem_Free( buffer ); + common->Warning( "Couldn't open %s for reading.", GetFileName() ); + return false; + } + + if ( file->Length() != sourceFile->fileSize || file->Timestamp() != sourceFile->timestamp ) { + Mem_Free( buffer ); + common->Warning( "The file %s has been modified outside of the engine.", GetFileName() ); + return false; + } + + file->Read( buffer, oldFileLength ); + fileSystem->CloseFile( file ); + + if ( MD5_BlockChecksum( buffer, oldFileLength ) != sourceFile->checksum ) { + Mem_Free( buffer ); + common->Warning( "The file %s has been modified outside of the engine.", GetFileName() ); + return false; + } + } + + // insert new text + char *declText = (char *) _alloca( textLength + 1 ); + GetText( declText ); + memmove( buffer + sourceTextOffset + textLength, buffer + sourceTextOffset + sourceTextLength, oldFileLength - sourceTextOffset - sourceTextLength ); + memcpy( buffer + sourceTextOffset, declText, textLength ); + + // write out new file + file = fileSystem->OpenFileWrite( GetFileName(), "fs_devpath" ); + if ( !file ) { + Mem_Free( buffer ); + common->Warning( "Couldn't open %s for writing.", GetFileName() ); + return false; + } + file->Write( buffer, newFileLength ); + fileSystem->CloseFile( file ); + + // set new file size, checksum and timestamp + sourceFile->fileSize = newFileLength; + sourceFile->checksum = MD5_BlockChecksum( buffer, newFileLength ); + fileSystem->ReadFile( GetFileName(), NULL, &sourceFile->timestamp ); + + // free buffer + Mem_Free( buffer ); + + // move all decls in the same file + for ( idDeclLocal *decl = sourceFile->decls; decl; decl = decl->nextInFile ) { + if (decl->sourceTextOffset > sourceTextOffset) { + decl->sourceTextOffset += textLength - sourceTextLength; + } + } + + // set new size of text in source file + sourceTextLength = textLength; + + return true; +} + +/* +================= +idDeclLocal::SourceFileChanged +================= +*/ +bool idDeclLocal::SourceFileChanged( void ) const { + int newLength; + ID_TIME_T newTimestamp; + + if ( sourceFile->fileSize <= 0 ) { + return false; + } + + newLength = fileSystem->ReadFile( GetFileName(), NULL, &newTimestamp ); + + if ( newLength != sourceFile->fileSize || newTimestamp != sourceFile->timestamp ) { + return true; + } + + return false; +} + +/* +================= +idDeclLocal::MakeDefault +================= +*/ +void idDeclLocal::MakeDefault() { + static int recursionLevel; + const char *defaultText; + + declManagerLocal.MediaPrint( "DEFAULTED\n" ); + declState = DS_DEFAULTED; + + AllocateSelf(); + + defaultText = self->DefaultDefinition(); + + // a parse error inside a DefaultDefinition() string could + // cause an infinite loop, but normal default definitions could + // still reference other default definitions, so we can't + // just dump out on the first recursion + if ( ++recursionLevel > 100 ) { + common->FatalError( "idDecl::MakeDefault: bad DefaultDefinition(): %s", defaultText ); + } + + // always free data before parsing + self->FreeData(); + + // parse + self->Parse( defaultText, strlen( defaultText ), false ); + + // we could still eventually hit the recursion if we have enough Error() calls inside Parse... + --recursionLevel; +} + +/* +================= +idDeclLocal::SetDefaultText +================= +*/ +bool idDeclLocal::SetDefaultText( void ) { + return false; +} + +/* +================= +idDeclLocal::DefaultDefinition +================= +*/ +const char *idDeclLocal::DefaultDefinition() const { + return "{ }"; +} + +/* +================= +idDeclLocal::Parse +================= +*/ +bool idDeclLocal::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + src.SkipBracedSection( false ); + return true; +} + +/* +================= +idDeclLocal::FreeData +================= +*/ +void idDeclLocal::FreeData() { +} + +/* +================= +idDeclLocal::List +================= +*/ +void idDeclLocal::List() const { + common->Printf( "%s\n", GetName() ); +} + +/* +================= +idDeclLocal::Print +================= +*/ +void idDeclLocal::Print() const { +} + +/* +================= +idDeclLocal::Reload +================= +*/ +void idDeclLocal::Reload( void ) { + this->sourceFile->Reload( false ); +} + +/* +================= +idDeclLocal::AllocateSelf +================= +*/ +void idDeclLocal::AllocateSelf( void ) { + if ( self == NULL ) { + self = declManagerLocal.GetDeclType( (int)type )->allocator(); + self->base = this; + } +} + +/* +================= +idDeclLocal::ParseLocal +================= +*/ +void idDeclLocal::ParseLocal( bool noCaching ) { + bool generatedDefaultText = false; + + AllocateSelf(); + + // always free data before parsing + self->FreeData(); + + declManagerLocal.MediaPrint( "parsing %s %s\n", declManagerLocal.declTypes[type]->typeName.c_str(), name.c_str() ); + + // if no text source try to generate default text + if ( textSource == NULL ) { + generatedDefaultText = self->SetDefaultText(); + } + + // indent for DEFAULTED or media file references + declManagerLocal.indent++; + + // no text immediately causes a MakeDefault() + if ( textSource == NULL ) { + MakeDefault(); + declManagerLocal.indent--; + return; + } + + declState = DS_PARSED; + + // parse + char *declText = (char *) _alloca( ( GetTextLength() + 1 ) * sizeof( char ) ); + GetText( declText ); + self->Parse( declText, GetTextLength(), noCaching ); + + // free generated text + if ( generatedDefaultText ) { + Mem_Free( textSource ); + textSource = 0; + textLength = 0; + } + + declManagerLocal.indent--; +} + +/* +================= +idDeclLocal::Purge +================= +*/ +void idDeclLocal::Purge( void ) { + // never purge things that were referenced outside level load, + // like the console and menu graphics + if ( parsedOutsideLevelLoad ) { + return; + } + + referencedThisLevel = false; + MakeDefault(); + + // the next Find() for this will re-parse the real data + declState = DS_UNPARSED; +} + +/* +================= +idDeclLocal::EverReferenced +================= +*/ +bool idDeclLocal::EverReferenced( void ) const { + return everReferenced; +} + +/* +================= +idDeclLocal::SetReferencedThisLevel +================= +*/ +void idDeclLocal::SetReferencedThisLevel( void ) { + referencedThisLevel = true; + everReferenced = true; +} + +/* +================= +idDeclLocal::Validate +================= +*/ +bool idDeclLocal::Validate( const char *text, int textLength, idStr &report ) const { + (void)text; + (void)textLength; + report = "_"; + return true; +} diff --git a/src/framework/declpda.cpp b/src/framework/declpda.cpp new file mode 100644 index 0000000..af1c07f --- /dev/null +++ b/src/framework/declpda.cpp @@ -0,0 +1,680 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( idDeclPDA ) == 0x120, "retail idDeclPDA ABI drift" ); +static_assert( sizeof( idDeclEmail ) == 0xC8, "retail idDeclEmail ABI drift" ); +static_assert( sizeof( idDeclVideo ) == 0xA8, "retail idDeclVideo ABI drift" ); +static_assert( sizeof( idDeclAudio ) == 0x88, "retail idDeclAudio ABI drift" ); +#endif + +/* +================= +idDeclPDA::Size +================= +*/ +size_t idDeclPDA::Size( void ) const { + return sizeof( idDeclPDA ) + videos.Allocated() + audios.Allocated() + emails.Allocated() + + pdaName.Allocated() + fullName.Allocated() + icon.Allocated() + id.Allocated() + + post.Allocated() + title.Allocated() + security.Allocated(); +} + +/* +=============== +idDeclPDA::Print +=============== +*/ +void idDeclPDA::Print( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclPDA::List +=============== +*/ +void idDeclPDA::List( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclPDA::Parse +================ +*/ +bool idDeclPDA::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "name") ) { + src.ReadToken( &token ); + pdaName = token; + continue; + } + + if ( !token.Icmp( "fullname") ) { + src.ReadToken( &token ); + fullName = token; + continue; + } + + if ( !token.Icmp( "icon") ) { + src.ReadToken( &token ); + icon = token; + continue; + } + + if ( !token.Icmp( "id") ) { + src.ReadToken( &token ); + id = token; + continue; + } + + if ( !token.Icmp( "post") ) { + src.ReadToken( &token ); + post = token; + continue; + } + + if ( !token.Icmp( "title") ) { + src.ReadToken( &token ); + title = token; + continue; + } + + if ( !token.Icmp( "security") ) { + src.ReadToken( &token ); + security = token; + continue; + } + + if ( !token.Icmp( "pda_email") ) { + src.ReadToken( &token ); + emails.Append( token ); + declManager->FindType(DECL_EMAIL, token); + continue; + } + + if ( !token.Icmp( "pda_audio") ) { + src.ReadToken( &token ); + audios.Append( token ); + declManager->FindType(DECL_AUDIO, token); + continue; + } + + if ( !token.Icmp( "pda_video") ) { + src.ReadToken( &token ); + videos.Append( token ); + declManager->FindType(DECL_VIDEO, token); + continue; + } + + } + + if ( src.HadError() ) { + src.Warning( "PDA decl '%s' had a parse error", GetName() ); + return false; + } + + originalVideos = videos.Num(); + originalEmails = emails.Num(); + return true; +} + +/* +=================== +idDeclPDA::DefaultDefinition +=================== +*/ +const char *idDeclPDA::DefaultDefinition( void ) const { + return + "{\n" + "\t" "name \"default pda\"\n" + "}"; +} + +/* +=================== +idDeclPDA::FreeData +=================== +*/ +void idDeclPDA::FreeData( void ) { + videos.Clear(); + audios.Clear(); + emails.Clear(); + originalEmails = 0; + originalVideos = 0; +} + +/* +================= +idDeclPDA::AddVideo +================= +*/ +void idDeclPDA::AddVideo( const char *name, bool unique ) const { + if ( unique && ( videos.Find( name ) != NULL ) ) { + return; + } + if ( declManager->FindType( DECL_VIDEO, name, false ) == NULL ) { + common->Printf( "Video %s not found\n", name ); + return; + } + videos.Append( name ); +} + +/* +================= +idDeclPDA::AddAudio +================= +*/ +void idDeclPDA::AddAudio( const char *name, bool unique ) const { + if ( unique && ( audios.Find( name ) != NULL ) ) { + return; + } + if ( declManager->FindType( DECL_AUDIO, name, false ) == NULL ) { + common->Printf( "Audio log %s not found\n", name ); + return; + } + audios.Append( name ); +} + +/* +================= +idDeclPDA::AddEmail +================= +*/ +void idDeclPDA::AddEmail( const char *name, bool unique ) const { + if ( unique && ( emails.Find( name ) != NULL ) ) { + return; + } + if ( declManager->FindType( DECL_EMAIL, name, false ) == NULL ) { + common->Printf( "Email %s not found\n", name ); + return; + } + emails.Append( name ); +} + +/* +================= +idDeclPDA::RemoveAddedEmailsAndVideos +================= +*/ +void idDeclPDA::RemoveAddedEmailsAndVideos() const { + int num = emails.Num(); + if ( originalEmails < num ) { + while ( num && num > originalEmails ) { + emails.RemoveIndex( --num ); + } + } + num = videos.Num(); + if ( originalVideos < num ) { + while ( num && num > originalVideos ) { + videos.RemoveIndex( --num ); + } + } +} + +/* +================= +idDeclPDA::SetSecurity +================= +*/ +void idDeclPDA::SetSecurity( const char *sec ) const { + security = sec; +} + +/* +================= +idDeclPDA::GetNumVideos +================= +*/ +const int idDeclPDA::GetNumVideos() const { + return videos.Num(); +} + +/* +================= +idDeclPDA::GetNumAudios +================= +*/ +const int idDeclPDA::GetNumAudios() const { + return audios.Num(); +} + +/* +================= +idDeclPDA::GetNumEmails +================= +*/ +const int idDeclPDA::GetNumEmails() const { + return emails.Num(); +} + +/* +================= +idDeclPDA::GetVideoByIndex +================= +*/ +const idDeclVideo* idDeclPDA::GetVideoByIndex( int index ) const { + if ( index >= 0 && index < videos.Num() ) { + return static_cast< const idDeclVideo* >( declManager->FindType( DECL_VIDEO, videos[index], false ) ); + } + return NULL; +} + +/* +================= +idDeclPDA::GetAudioByIndex +================= +*/ +const idDeclAudio* idDeclPDA::GetAudioByIndex( int index ) const { + if ( index >= 0 && index < audios.Num() ) { + return static_cast< const idDeclAudio* >( declManager->FindType( DECL_AUDIO, audios[index], false ) ); + } + return NULL; +} + +/* +================= +idDeclPDA::GetEmailByIndex +================= +*/ +const idDeclEmail* idDeclPDA::GetEmailByIndex( int index ) const { + if ( index >= 0 && index < emails.Num() ) { + return static_cast< const idDeclEmail* >( declManager->FindType( DECL_EMAIL, emails[index], false ) ); + } + return NULL; +} + +/* +================= +idDeclEmail::Size +================= +*/ +size_t idDeclEmail::Size( void ) const { + return sizeof( idDeclEmail ) + text.Allocated() + subject.Allocated() + date.Allocated() + + to.Allocated() + from.Allocated() + image.Allocated(); +} + +/* +=============== +idDeclEmail::Print +=============== +*/ +void idDeclEmail::Print( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclEmail::List +=============== +*/ +void idDeclEmail::List( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclEmail::Parse +================ +*/ +bool idDeclEmail::Parse( const char *_text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + (void)noCaching; + + src.LoadMemory( _text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT | LEXFL_NOFATALERRORS ); + src.SkipUntilString( "{" ); + + text = ""; + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "subject") ) { + src.ReadToken( &token ); + subject = token; + continue; + } + + if ( !token.Icmp( "to") ) { + src.ReadToken( &token ); + to = token; + continue; + } + + if ( !token.Icmp( "from") ) { + src.ReadToken( &token ); + from = token; + continue; + } + + if ( !token.Icmp( "date") ) { + src.ReadToken( &token ); + date = token; + continue; + } + + if ( !token.Icmp( "text") ) { + src.ReadToken( &token ); + if ( token != "{" ) { + src.Warning( "Email decl '%s' had a parse error", GetName() ); + return false; + } + while ( src.ReadToken( &token ) && token != "}" ) { + text += token.c_str(); + } + continue; + } + + if ( !token.Icmp( "image") ) { + src.ReadToken( &token ); + image = token; + continue; + } + } + + if ( src.HadError() ) { + src.Warning( "Email decl '%s' had a parse error", GetName() ); + return false; + } + return true; +} + +/* +=================== +idDeclEmail::DefaultDefinition +=================== +*/ +const char *idDeclEmail::DefaultDefinition( void ) const { + return + "{\n" + "\t" "{\n" + "\t\t" "to\t5Mail recipient\n" + "\t\t" "subject\t5Nothing\n" + "\t\t" "from\t5No one\n" + "\t" "}\n" + "}"; +} + +/* +=================== +idDeclEmail::FreeData +=================== +*/ +void idDeclEmail::FreeData( void ) { +} + +/* +================= +idDeclVideo::Size +================= +*/ +size_t idDeclVideo::Size( void ) const { + return sizeof( idDeclVideo ) + preview.Allocated() + video.Allocated() + + videoName.Allocated() + info.Allocated() + audio.Allocated(); +} + +/* +=============== +idDeclVideo::Print +=============== +*/ +void idDeclVideo::Print( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclVideo::List +=============== +*/ +void idDeclVideo::List( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclVideo::Parse +================ +*/ +bool idDeclVideo::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT | LEXFL_NOFATALERRORS ); + src.SkipUntilString( "{" ); + + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "name") ) { + src.ReadToken( &token ); + videoName = token; + continue; + } + + if ( !token.Icmp( "preview") ) { + src.ReadToken( &token ); + preview = token; + continue; + } + + if ( !token.Icmp( "video") ) { + src.ReadToken( &token ); + video = token; + declManager->FindMaterial( video ); + continue; + } + + if ( !token.Icmp( "info") ) { + src.ReadToken( &token ); + info = token; + continue; + } + + if ( !token.Icmp( "audio") ) { + src.ReadToken( &token ); + audio = token; + declManager->FindSound(audio); + continue; + } + + } + + if ( src.HadError() ) { + src.Warning( "Video decl '%s' had a parse error", GetName() ); + return false; + } + return true; +} + +/* +=================== +idDeclVideo::DefaultDefinition +=================== +*/ +const char *idDeclVideo::DefaultDefinition( void ) const { + return + "{\n" + "\t" "{\n" + "\t\t" "name\t5Default Video\n" + "\t" "}\n" + "}"; +} + +/* +=================== +idDeclVideo::FreeData +=================== +*/ +void idDeclVideo::FreeData( void ) { +} + +/* +================= +idDeclAudio::Size +================= +*/ +size_t idDeclAudio::Size( void ) const { + return sizeof( idDeclAudio ) + audio.Allocated() + audioName.Allocated() + + info.Allocated() + preview.Allocated(); +} + +/* +=============== +idDeclAudio::Print +=============== +*/ +void idDeclAudio::Print( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +=============== +idDeclAudio::List +=============== +*/ +void idDeclAudio::List( void ) const { + common->Printf( "Implement me\n" ); +} + +/* +================ +idDeclAudio::Parse +================ +*/ +bool idDeclAudio::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT | LEXFL_NOFATALERRORS ); + src.SkipUntilString( "{" ); + + // scan through, identifying each individual parameter + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( !token.Icmp( "name") ) { + src.ReadToken( &token ); + audioName = token; + continue; + } + + if ( !token.Icmp( "audio") ) { + src.ReadToken( &token ); + audio = token; + declManager->FindSound(audio); + continue; + } + + if ( !token.Icmp( "info") ) { + src.ReadToken( &token ); + info = token; + continue; + } + + if ( !token.Icmp( "preview") ) { + src.ReadToken( &token ); + preview = token; + continue; + } + + } + + if ( src.HadError() ) { + src.Warning( "Audio decl '%s' had a parse error", GetName() ); + return false; + } + return true; +} + +/* +=================== +idDeclAudio::DefaultDefinition +=================== +*/ +const char *idDeclAudio::DefaultDefinition( void ) const { + return + "{\n" + "\t" "{\n" + "\t\t" "name\t5Default Audio\n" + "\t" "}\n" + "}"; +} + +/* +=================== +idDeclAudio::FreeData +=================== +*/ +void idDeclAudio::FreeData( void ) { +} diff --git a/src/framework/declskin.cpp b/src/framework/declskin.cpp new file mode 100644 index 0000000..a90427d --- /dev/null +++ b/src/framework/declskin.cpp @@ -0,0 +1,187 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( idDeclSkin ) == 0x28, "retail idDeclSkin ABI drift" ); +#endif + +/* +================= +idDeclSkin::idDeclSkin +================= +*/ +idDeclSkin::idDeclSkin( void ) { +} + +/* +================= +idDeclSkin::Size +================= +*/ +size_t idDeclSkin::Size( void ) const { + return sizeof( idDeclSkin ) + mappings.Allocated() + associatedModels.Allocated(); +} + +/* +================ +idDeclSkin::FreeData +================ +*/ +void idDeclSkin::FreeData( void ) { + mappings.Clear(); +} + +/* +================ +idDeclSkin::Parse +================ +*/ +bool idDeclSkin::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token, token2; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + associatedModels.Clear(); + + while (1) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + if ( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + if ( !token.Icmp( "model" ) ) { + associatedModels.Append( token2 ); + continue; + } + + skinMapping_t map; + + if ( !token.Icmp( "*" ) ) { + // wildcard + map.from = NULL; + } else { + map.from = declManager->FindMaterial( token ); + } + + map.to = declManager->FindMaterial( token2 ); + + mappings.Append( map ); + } + + return false; +} + +/* +================ +idDeclSkin::SetDefaultText +================ +*/ +bool idDeclSkin::SetDefaultText( void ) { + // if there exists a material with the same name + if ( declManager->FindType( DECL_MATERIAL, GetName() ) ) { + char generated[2048]; + + idStr::snPrintf( generated, sizeof( generated ), + "skin %s // IMPLICITLY GENERATED\n" + "{\n" + "_default %s\n" + "}\n", GetName(), GetName() ); + SetText( generated ); + return true; + } else { + return false; + } +} + +/* +================ +idDeclSkin::DefaultDefinition +================ +*/ +const char *idDeclSkin::DefaultDefinition( void ) const { + return + "{\n" + "\t" "\"*\"\t\"_default\"\n" + "}"; +} + +/* +================ +idDeclSkin::Validate +================ +*/ +bool idDeclSkin::Validate( const char *text, int textLength, idStr &report ) const { + idDecl *decl = declManager->AllocateDecl( DECL_SKIN ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl->base; + delete decl; + (void)report; + return valid; +} + +/* +=============== +RemapShaderBySkin +=============== +*/ +const idMaterial *idDeclSkin::RemapShaderBySkin( const idMaterial *shader ) const { + int i; + + if ( !shader ) { + return NULL; + } + + // never remap surfaces that were originally nodraw, like collision hulls + if ( !shader->IsDrawn() ) { + return shader; + } + + for ( i = 0; i < mappings.Num() ; i++ ) { + const skinMapping_t *map = &mappings[i]; + + // NULL = wildcard match + if ( !map->from || map->from == shader ) { + return map->to; + } + } + + // didn't find a match or wildcard, so stay the same + return shader; +} diff --git a/src/framework/decltable.cpp b/src/framework/decltable.cpp new file mode 100644 index 0000000..8855b32 --- /dev/null +++ b/src/framework/decltable.cpp @@ -0,0 +1,212 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( idDeclTable ) == 0x24, "retail idDeclTable ABI drift" ); +#endif + +/* +================= +idDeclTable::idDeclTable +================= +*/ +idDeclTable::idDeclTable( void ) { +} + +/* +================= +idDeclTable::TableLookup +================= +*/ +float idDeclTable::TableLookup( float index ) const { + int iIndex; + float iFrac; + + int domain = values.Num() - 1; + + if ( domain <= 1 ) { + return 1.0f; + } + + if ( clamp ) { + index *= (domain-1); + if ( index >= domain - 1 ) { + return values[domain - 1]; + } else if ( index <= 0 ) { + return values[0]; + } + iIndex = idMath::Ftoi( index ); + iFrac = index - iIndex; + } else { + index *= domain; + + if ( index < 0 ) { + index += domain * idMath::Ceil( -index / domain ); + } + + iIndex = idMath::FtoiFast( idMath::Floor( index ) ); + iFrac = index - iIndex; + iIndex = iIndex % domain; + if ( iIndex < 0 || iFrac < 0.0f ) { + iIndex = 0; + iFrac = 0.0f; + } + } + + if ( !snap ) { + // we duplicated the 0 index at the end at creation time, so we + // don't need to worry about wrapping the filter + return values[iIndex] * ( 1.0f - iFrac ) + values[iIndex + 1] * iFrac; + } + + return values[iIndex]; +} + +/* +================= +idDeclTable::Size +================= +*/ +size_t idDeclTable::Size( void ) const { + return sizeof( idDeclTable ) + values.Allocated(); +} + +/* +================= +idDeclTable::FreeData +================= +*/ +void idDeclTable::FreeData( void ) { + snap = false; + clamp = false; + values.Clear(); +} + +/* +================= +idDeclTable::DefaultDefinition +================= +*/ +const char *idDeclTable::DefaultDefinition( void ) const { + return "{ { 0 } }"; +} + +/* +================= +idDeclTable::Validate +================= +*/ +bool idDeclTable::Validate( const char *text, int textLength, idStr &report ) const { + idDecl *decl = declManager->AllocateDecl( DECL_TABLE ); + const bool valid = decl->Parse( text, textLength, false ); + decl->FreeData(); + delete decl->base; + delete decl; + (void)report; + return valid; +} + +/* +================= +idDeclTable::Parse +================= +*/ +bool idDeclTable::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + float v; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + snap = false; + clamp = false; + minValue = idMath::INFINITY; + maxValue = -1.0e30f; + values.Clear(); + + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( token == "}" ) { + break; + } + + if ( token.Icmp( "snap" ) == 0 ) { + snap = true; + } else if ( token.Icmp( "clamp" ) == 0 ) { + clamp = true; + } else if ( token.Icmp( "{" ) == 0 ) { + + while ( 1 ) { + bool errorFlag; + + v = src.ParseFloat( &errorFlag ); + if ( errorFlag ) { + // we got something non-numeric + MakeDefault(); + return false; + } + + values.Append( v ); + if ( v < minValue ) { + minValue = v; + } + if ( v > maxValue ) { + maxValue = v; + } + + src.ReadToken( &token ); + if ( token == "}" ) { + break; + } + if ( token == "," ) { + continue; + } + src.Warning( "expected comma or brace" ); + MakeDefault(); + return false; + } + + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // copy the 0 element to the end, so lerping doesn't + // need to worry about the wrap case + float val = values[0]; // template bug requires this to not be in the Append()? + values.Append( val ); + + return true; +} diff --git a/src/framework/demofile.cpp b/src/framework/demofile.cpp new file mode 100644 index 0000000..4cdc300 --- /dev/null +++ b/src/framework/demofile.cpp @@ -0,0 +1,324 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar idDemoFile::com_logDemos( "com_logDemos", "0", CVAR_SYSTEM | CVAR_BOOL, "Write demo.log with debug information in it" ); +idCVar idDemoFile::com_compressDemos( "com_compressDemos", "1", CVAR_SYSTEM | CVAR_INTEGER | CVAR_ARCHIVE, "Compression scheme for demo files\n0: None (Fast, large files)\n1: LZW (Fast to compress, Fast to decompress, medium/small files)\n2: LZSS (Slow to compress, Fast to decompress, small files)\n3: Huffman (Fast to compress, Slow to decompress, medium files)\nSee also: The 'CompressDemo' command" ); +idCVar idDemoFile::com_preloadDemos( "com_preloadDemos", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_ARCHIVE, "Load the whole demo in to RAM before running it" ); + +#define DEMO_MAGIC GAME_NAME " RDEMO" + +/* +================ +idDemoFile::idDemoFile +================ +*/ +idDemoFile::idDemoFile() { + f = NULL; + fLog = NULL; + log = false; + fileImage = NULL; + compressor = NULL; + writing = false; +} + +/* +================ +idDemoFile::~idDemoFile +================ +*/ +idDemoFile::~idDemoFile() { + Close(); +} + +/* +================ +idDemoFile::AllocCompressor +================ +*/ +idCompressor *idDemoFile::AllocCompressor( int type ) { + switch ( type ) { + case 0: return idCompressor::AllocNoCompression(); + default: + case 1: return idCompressor::AllocLZW(); + case 2: return idCompressor::AllocLZSS(); + case 3: return idCompressor::AllocHuffman(); + } +} + +/* +================ +idDemoFile::OpenForReading +================ +*/ +bool idDemoFile::OpenForReading( const char *fileName ) { + static const int magicLen = sizeof(DEMO_MAGIC) / sizeof(DEMO_MAGIC[0]); + char magicBuffer[magicLen]; + int compression; + int fileLength; + + Close(); + + f = fileSystem->OpenFileRead( fileName ); + if ( !f ) { + return false; + } + + fileLength = f->Length(); + + if ( com_preloadDemos.GetBool() ) { + fileImage = (byte *)Mem_Alloc( fileLength ); + f->Read( fileImage, fileLength ); + fileSystem->CloseFile( f ); + f = new idFile_Memory( va( "preloaded(%s)", fileName ), (const char *)fileImage, fileLength ); + } + + if ( com_logDemos.GetBool() ) { + fLog = fileSystem->OpenFileWrite( "demoread.log" ); + } + + writing = false; + + f->Read(magicBuffer, magicLen); + if ( memcmp(magicBuffer, DEMO_MAGIC, magicLen) == 0 ) { + f->ReadInt( compression ); + } else { + // Ideally we would error out if the magic string isn't there, + // but for backwards compatibility we are going to assume it's just an uncompressed demo file + compression = 0; + f->Rewind(); + } + + compressor = AllocCompressor( compression ); + compressor->Init( f, false, 8 ); + + return true; +} + +/* +================ +idDemoFile::SetLog +================ +*/ +void idDemoFile::SetLog(bool b, const char *p) { + log = b; + if (p) { + logStr = p; + } +} + +/* +================ +idDemoFile::Log +================ +*/ +void idDemoFile::Log(const char *p) { + if ( fLog && p && *p ) { + fLog->Write( p, strlen(p) ); + } +} + +/* +================ +idDemoFile::OpenForWriting +================ +*/ +bool idDemoFile::OpenForWriting( const char *fileName ) { + Close(); + + f = fileSystem->OpenFileWrite( fileName ); + if ( f == NULL ) { + return false; + } + + if ( com_logDemos.GetBool() ) { + fLog = fileSystem->OpenFileWrite( "demowrite.log" ); + } + + writing = true; + + f->Write(DEMO_MAGIC, sizeof(DEMO_MAGIC)); + f->WriteInt( com_compressDemos.GetInteger() ); + f->Flush(); + + compressor = AllocCompressor( com_compressDemos.GetInteger() ); + compressor->Init( f, true, 8 ); + + return true; +} + +/* +================ +idDemoFile::Close +================ +*/ +void idDemoFile::Close() { + if ( writing && compressor ) { + compressor->FinishCompress(); + } + + if ( f ) { + fileSystem->CloseFile( f ); + f = NULL; + } + if ( fLog ) { + fileSystem->CloseFile( fLog ); + fLog = NULL; + } + if ( fileImage ) { + Mem_Free( fileImage ); + fileImage = NULL; + } + if ( compressor ) { + delete compressor; + compressor = NULL; + } + + demoStrings.DeleteContents( true ); +} + +/* +================ +idDemoFile::ReadHashString +================ +*/ +const char *idDemoFile::ReadHashString() { + int index; + + if ( log && fLog ) { + const char *text = va( "%s > Reading hash string\n", logStr.c_str() ); + fLog->Write( text, strlen( text ) ); + } + + ReadInt( index ); + + if ( index == -1 ) { + // read a new string for the table + idStr *str = new idStr; + + idStr data; + ReadString( data ); + *str = data; + + demoStrings.Append( str ); + + return *str; + } + + if ( index < -1 || index >= demoStrings.Num() ) { + Close(); + common->Error( "demo hash index out of range" ); + } + + return demoStrings[index]->c_str(); +} + +/* +================ +idDemoFile::WriteHashString +================ +*/ +void idDemoFile::WriteHashString( const char *str ) { + if ( log && fLog ) { + const char *text = va( "%s > Writing hash string\n", logStr.c_str() ); + fLog->Write( text, strlen( text ) ); + } + // see if it is already in the has table + for ( int i = 0 ; i < demoStrings.Num() ; i++ ) { + if ( !strcmp( demoStrings[i]->c_str(), str ) ) { + WriteInt( i ); + return; + } + } + + // add it to our table and the demo table + idStr *copy = new idStr( str ); +//common->Printf( "hash:%i = %s\n", demoStrings.Num(), str ); + demoStrings.Append( copy ); + int cmd = -1; + WriteInt( cmd ); + WriteString( str ); +} + +/* +================ +idDemoFile::ReadDict +================ +*/ +void idDemoFile::ReadDict( idDict &dict ) { + int i, c; + idStr key, val; + + dict.Clear(); + ReadInt( c ); + for ( i = 0; i < c; i++ ) { + key = ReadHashString(); + val = ReadHashString(); + dict.Set( key, val ); + } +} + +/* +================ +idDemoFile::WriteDict +================ +*/ +void idDemoFile::WriteDict( const idDict &dict ) { + int i, c; + + c = dict.GetNumKeyVals(); + WriteInt( c ); + for ( i = 0; i < c; i++ ) { + WriteHashString( dict.GetKeyVal( i )->GetKey() ); + WriteHashString( dict.GetKeyVal( i )->GetValue() ); + } +} + +/* + ================ + idDemoFile::Read + ================ + */ +int idDemoFile::Read( void *buffer, int len ) { + int read = compressor->Read( buffer, len ); + if ( read == 0 && len >= 4 ) { + *(demoSystem_t *)buffer = DS_FINISHED; + } + return read; +} + +/* + ================ + idDemoFile::Write + ================ + */ +int idDemoFile::Write( const void *buffer, int len ) { + return compressor->Write( buffer, len ); +} + + + + diff --git a/src/framework/editfield.cpp b/src/framework/editfield.cpp new file mode 100644 index 0000000..ef180f5 --- /dev/null +++ b/src/framework/editfield.cpp @@ -0,0 +1,599 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +static autoComplete_t globalAutoComplete; + +/* +=============== +FindMatches +=============== +*/ +static void FindMatches( const char *s ) { + int i; + + if ( idStr::Icmpn( s, globalAutoComplete.completionString, strlen( globalAutoComplete.completionString ) ) != 0 ) { + return; + } + globalAutoComplete.matchCount++; + if ( globalAutoComplete.matchCount == 1 ) { + idStr::Copynz( globalAutoComplete.currentMatch, s, sizeof( globalAutoComplete.currentMatch ) ); + return; + } + + // cut currentMatch to the amount common with s + for ( i = 0; s[i]; i++ ) { + if ( tolower( globalAutoComplete.currentMatch[i] ) != tolower( s[i] ) ) { + globalAutoComplete.currentMatch[i] = 0; + break; + } + } + globalAutoComplete.currentMatch[i] = 0; +} + +/* +=============== +FindIndexMatch +=============== +*/ +static void FindIndexMatch( const char *s ) { + + if ( idStr::Icmpn( s, globalAutoComplete.completionString, strlen( globalAutoComplete.completionString ) ) != 0 ) { + return; + } + + if( globalAutoComplete.findMatchIndex == globalAutoComplete.matchIndex ) { + idStr::Copynz( globalAutoComplete.currentMatch, s, sizeof( globalAutoComplete.currentMatch ) ); + } + + globalAutoComplete.findMatchIndex++; +} + +/* +=============== +PrintMatches +=============== +*/ +static void PrintMatches( const char *s ) { + if ( idStr::Icmpn( s, globalAutoComplete.currentMatch, strlen( globalAutoComplete.currentMatch ) ) == 0 ) { + common->Printf( " %s\n", s ); + } +} + +/* +=============== +PrintCvarMatches +=============== +*/ +static void PrintCvarMatches( const char *s ) { + if ( idStr::Icmpn( s, globalAutoComplete.currentMatch, strlen( globalAutoComplete.currentMatch ) ) == 0 ) { + common->Printf( " %s" S_COLOR_WHITE " = \"%s\"\n", s, cvarSystem->GetCVarString( s ) ); + } +} + +/* +=============== +idEditField::idEditField +=============== +*/ +idEditField::idEditField() { + widthInChars = 0; + Clear(); +} + +/* +=============== +idEditField::~idEditField +=============== +*/ +idEditField::~idEditField() { +} + +/* +=============== +idEditField::Clear +=============== +*/ +void idEditField::Clear( void ) { + buffer[0] = 0; + cursor = 0; + scroll = 0; + autoComplete.length = 0; + autoComplete.valid = false; +} + +/* +=============== +idEditField::SetWidthInChars +=============== +*/ +void idEditField::SetWidthInChars( int w ) { + assert( w <= MAX_EDIT_LINE ); + widthInChars = w; +} + +/* +=============== +idEditField::SetCursor +=============== +*/ +void idEditField::SetCursor( int c ) { + assert( c <= MAX_EDIT_LINE ); + cursor = c; +} + +/* +=============== +idEditField::GetCursor +=============== +*/ +int idEditField::GetCursor( void ) const { + return cursor; +} + +/* +=============== +idEditField::ClearAutoComplete +=============== +*/ +void idEditField::ClearAutoComplete( void ) { + if ( autoComplete.length > 0 && autoComplete.length <= (int) strlen( buffer ) ) { + buffer[autoComplete.length] = '\0'; + if ( cursor > autoComplete.length ) { + cursor = autoComplete.length; + } + } + autoComplete.length = 0; + autoComplete.valid = false; +} + +/* +=============== +idEditField::GetAutoCompleteLength +=============== +*/ +int idEditField::GetAutoCompleteLength( void ) const { + return autoComplete.length; +} + +/* +=============== +idEditField::AutoComplete +=============== +*/ +void idEditField::AutoComplete( void ) { + char completionArgString[MAX_EDIT_LINE]; + idCmdArgs args; + + if ( !autoComplete.valid ) { + args.TokenizeString( buffer, false ); + idStr::Copynz( autoComplete.completionString, args.Argv( 0 ), sizeof( autoComplete.completionString ) ); + idStr::Copynz( completionArgString, args.Args(), sizeof( completionArgString ) ); + autoComplete.matchCount = 0; + autoComplete.matchIndex = 0; + autoComplete.currentMatch[0] = 0; + + if ( strlen( autoComplete.completionString ) == 0 ) { + return; + } + + globalAutoComplete = autoComplete; + + cmdSystem->CommandCompletion( FindMatches ); + cvarSystem->CommandCompletion( FindMatches ); + + autoComplete = globalAutoComplete; + + if ( autoComplete.matchCount == 0 ) { + return; // no matches + } + + // when there's only one match or there's an argument + if ( autoComplete.matchCount == 1 || completionArgString[0] != '\0' ) { + + /// try completing arguments + idStr::Append( autoComplete.completionString, sizeof( autoComplete.completionString ), " " ); + idStr::Append( autoComplete.completionString, sizeof( autoComplete.completionString ), completionArgString ); + autoComplete.matchCount = 0; + + globalAutoComplete = autoComplete; + + cmdSystem->ArgCompletion( autoComplete.completionString, FindMatches ); + cvarSystem->ArgCompletion( autoComplete.completionString, FindMatches ); + + autoComplete = globalAutoComplete; + + idStr::snPrintf( buffer, sizeof( buffer ), "%s", autoComplete.currentMatch ); + + if ( autoComplete.matchCount == 0 ) { + // no argument matches + idStr::Append( buffer, sizeof( buffer ), " " ); + idStr::Append( buffer, sizeof( buffer ), completionArgString ); + SetCursor( strlen( buffer ) ); + return; + } + } else { + + // multiple matches, complete to shortest + idStr::snPrintf( buffer, sizeof( buffer ), "%s", autoComplete.currentMatch ); + if ( strlen( completionArgString ) ) { + idStr::Append( buffer, sizeof( buffer ), " " ); + idStr::Append( buffer, sizeof( buffer ), completionArgString ); + } + } + + autoComplete.length = strlen( buffer ); + autoComplete.valid = ( autoComplete.matchCount != 1 ); + SetCursor( autoComplete.length ); + + common->Printf( "]%s\n", buffer ); + + // run through again, printing matches + globalAutoComplete = autoComplete; + + cmdSystem->CommandCompletion( PrintMatches ); + cmdSystem->ArgCompletion( autoComplete.completionString, PrintMatches ); + cvarSystem->CommandCompletion( PrintCvarMatches ); + cvarSystem->ArgCompletion( autoComplete.completionString, PrintMatches ); + + } else if ( autoComplete.matchCount != 1 ) { + + // get the next match and show instead + autoComplete.matchIndex++; + if ( autoComplete.matchIndex == autoComplete.matchCount ) { + autoComplete.matchIndex = 0; + } + autoComplete.findMatchIndex = 0; + + globalAutoComplete = autoComplete; + + cmdSystem->CommandCompletion( FindIndexMatch ); + cmdSystem->ArgCompletion( autoComplete.completionString, FindIndexMatch ); + cvarSystem->CommandCompletion( FindIndexMatch ); + cvarSystem->ArgCompletion( autoComplete.completionString, FindIndexMatch ); + + autoComplete = globalAutoComplete; + + // and print it + idStr::snPrintf( buffer, sizeof( buffer ), autoComplete.currentMatch ); + if ( autoComplete.length > (int)strlen( buffer ) ) { + autoComplete.length = strlen( buffer ); + } + SetCursor( autoComplete.length ); + } +} + +/* +=============== +idEditField::CharEvent +=============== +*/ +void idEditField::CharEvent( int ch ) { + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Paste(); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Clear(); + return; + } + + len = strlen( buffer ); + + if ( ch == 'h' - 'a' + 1 || ch == K_BACKSPACE ) { // ctrl-h is backspace + if ( cursor > 0 ) { + memmove( buffer + cursor - 1, buffer + cursor, len + 1 - cursor ); + cursor--; + if ( cursor < scroll ) { + scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + cursor = 0; + scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + cursor = len; + scroll = cursor - widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + + if ( idKeyInput::GetOverstrikeMode() ) { + if ( cursor == MAX_EDIT_LINE - 1 ) { + return; + } + buffer[cursor] = ch; + cursor++; + } else { // insert mode + if ( len == MAX_EDIT_LINE - 1 ) { + return; // all full + } + memmove( buffer + cursor + 1, buffer + cursor, len + 1 - cursor ); + buffer[cursor] = ch; + cursor++; + } + + + if ( cursor >= widthInChars ) { + scroll++; + } + + if ( cursor == len + 1 ) { + buffer[cursor] = 0; + } +} + +/* +=============== +idEditField::KeyDownEvent +=============== +*/ +void idEditField::KeyDownEvent( int key ) { + int len; + + // shift-insert is paste + if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && idKeyInput::IsDown( K_SHIFT ) ) { + ClearAutoComplete(); + Paste(); + return; + } + + len = strlen( buffer ); + + if ( key == K_DEL ) { + if ( autoComplete.length ) { + ClearAutoComplete(); + } else if ( cursor < len ) { + memmove( buffer + cursor, buffer + cursor + 1, len - cursor ); + } + return; + } + + if ( key == K_RIGHTARROW ) { + if ( idKeyInput::IsDown( K_CTRL ) ) { + // skip to next word + while( ( cursor < len ) && ( buffer[ cursor ] != ' ' ) ) { + cursor++; + } + + while( ( cursor < len ) && ( buffer[ cursor ] == ' ' ) ) { + cursor++; + } + } else { + cursor++; + } + + if ( cursor > len ) { + cursor = len; + } + + if ( cursor >= scroll + widthInChars ) { + scroll = cursor - widthInChars + 1; + } + + if ( autoComplete.length > 0 ) { + autoComplete.length = cursor; + } + return; + } + + if ( key == K_LEFTARROW ) { + if ( idKeyInput::IsDown( K_CTRL ) ) { + // skip to previous word + while( ( cursor > 0 ) && ( buffer[ cursor - 1 ] == ' ' ) ) { + cursor--; + } + + while( ( cursor > 0 ) && ( buffer[ cursor - 1 ] != ' ' ) ) { + cursor--; + } + } else { + cursor--; + } + + if ( cursor < 0 ) { + cursor = 0; + } + if ( cursor < scroll ) { + scroll = cursor; + } + + if ( autoComplete.length ) { + autoComplete.length = cursor; + } + return; + } + + if ( key == K_HOME || ( tolower( key ) == 'a' && idKeyInput::IsDown( K_CTRL ) ) ) { + cursor = 0; + scroll = 0; + if ( autoComplete.length ) { + autoComplete.length = cursor; + autoComplete.valid = false; + } + return; + } + + if ( key == K_END || ( tolower( key ) == 'e' && idKeyInput::IsDown( K_CTRL ) ) ) { + cursor = len; + if ( cursor >= scroll + widthInChars ) { + scroll = cursor - widthInChars + 1; + } + if ( autoComplete.length ) { + autoComplete.length = cursor; + autoComplete.valid = false; + } + return; + } + + if ( key == K_INS ) { + idKeyInput::SetOverstrikeMode( !idKeyInput::GetOverstrikeMode() ); + return; + } + + // clear autocompletion buffer on normal key input + if ( key != K_CAPSLOCK && key != K_ALT && key != K_CTRL && key != K_SHIFT ) { + ClearAutoComplete(); + } +} + +/* +=============== +idEditField::Paste +=============== +*/ +void idEditField::Paste( void ) { + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0; i < pasteLen; i++ ) { + CharEvent( cbd[i] ); + } + + Mem_Free( cbd ); +} + +/* +=============== +idEditField::GetBuffer +=============== +*/ +char *idEditField::GetBuffer( void ) { + return buffer; +} + +/* +=============== +idEditField::SetBuffer +=============== +*/ +void idEditField::SetBuffer( const char *buf ) { + Clear(); + idStr::Copynz( buffer, buf, sizeof( buffer ) ); + SetCursor( strlen( buffer ) ); +} + +/* +=============== +idEditField::Draw +=============== +*/ +void idEditField::Draw( int x, int y, int width, bool showCursor, const idMaterial *shader ) { + int len; + int drawLen; + int prestep; + int cursorChar; + char str[MAX_EDIT_LINE]; + int size; + + size = SMALLCHAR_WIDTH; + + drawLen = widthInChars; + len = strlen( buffer ) + 1; + + // guarantee that cursor will be visible + if ( len <= drawLen ) { + prestep = 0; + } else { + if ( scroll + drawLen > len ) { + scroll = len - drawLen; + if ( scroll < 0 ) { + scroll = 0; + } + } + prestep = scroll; + + // Quake 4 supports several two-byte escape classes, not just colors. + const int escapeLength = idStr::IsEscape( buffer + prestep ); + if ( escapeLength ) { + prestep += escapeLength; + } + } + + if ( prestep + drawLen > len ) { + drawLen = len - prestep; + } + + // extract characters from the field at + if ( drawLen >= MAX_EDIT_LINE ) { + common->Error( "drawLen >= MAX_EDIT_LINE" ); + } + + memcpy( str, buffer + prestep, drawLen ); + str[ drawLen ] = 0; + + // draw it + renderSystem->DrawSmallStringExt( x, y, str, colorWhite, false, shader ); + + // draw the cursor + if ( !showCursor ) { + return; + } + + if ( (int)( com_ticNumber >> 4 ) & 1 ) { + return; // off blink + } + + if ( idKeyInput::GetOverstrikeMode() ) { + cursorChar = 11; + } else { + cursorChar = 10; + } + + // Move the cursor back to account for color codes + for ( int i = 0; i < cursor; i++ ) { + const int escapeLength = idStr::IsEscape( &str[i] ); + if ( escapeLength ) { + i += escapeLength - 1; + prestep += escapeLength; + } + } + + renderSystem->DrawSmallChar( x + ( cursor - prestep ) * size, y, cursorChar, shader ); +} diff --git a/src/framework/eventloop.cpp b/src/framework/eventloop.cpp new file mode 100644 index 0000000..0b9f25e --- /dev/null +++ b/src/framework/eventloop.cpp @@ -0,0 +1,270 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +idCVar idEventLoop::com_journal( "com_journal", "0", CVAR_INIT|CVAR_SYSTEM, "1 = record journal, 2 = play back journal", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + +idEventLoop eventLoopLocal; +idEventLoop *eventLoop = &eventLoopLocal; + + +/* +================= +idEventLoop::idEventLoop +================= +*/ +idEventLoop::idEventLoop( void ) { + com_journalFile = NULL; + com_journalDataFile = NULL; + initialTimeOffset = 0; +} + +/* +================= +idEventLoop::~idEventLoop +================= +*/ +idEventLoop::~idEventLoop( void ) { +} + +/* +================= +idEventLoop::GetRealEvent +================= +*/ +sysEvent_t idEventLoop::GetRealEvent( void ) { + int r; + sysEvent_t ev; + + // either get an event from the system or the journal file + if ( com_journal.GetInteger() == 2 ) { + r = com_journalFile->Read( &ev, sizeof(ev) ); + if ( r != sizeof(ev) ) { + common->FatalError( "Error reading from journal file" ); + } + if ( ev.evPtrLength ) { + ev.evPtr = Mem_ClearedAlloc( ev.evPtrLength ); + r = com_journalFile->Read( ev.evPtr, ev.evPtrLength ); + if ( r != ev.evPtrLength ) { + common->FatalError( "Error reading from journal file" ); + } + } + } else { + ev = Sys_GetEvent(); + + // write the journal value out if needed + if ( com_journal.GetInteger() == 1 ) { + r = com_journalFile->Write( &ev, sizeof(ev) ); + if ( r != sizeof(ev) ) { + common->FatalError( "Error writing to journal file" ); + } + if ( ev.evPtrLength ) { + r = com_journalFile->Write( ev.evPtr, ev.evPtrLength ); + if ( r != ev.evPtrLength ) { + common->FatalError( "Error writing to journal file" ); + } + } + } + } + + return ev; +} + +/* +================= +idEventLoop::PushEvent +================= +*/ +void idEventLoop::PushEvent( sysEvent_t *event ) { + sysEvent_t *ev; + static bool printedWarning; + + ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) { + + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) { + printedWarning = true; + common->Printf( "WARNING: Com_PushEvent overflow\n" ); + } + + if ( ev->evPtr ) { + Mem_Free( ev->evPtr ); + } + com_pushedEventsTail++; + } else { + printedWarning = false; + } + + *ev = *event; + com_pushedEventsHead++; +} + +/* +================= +idEventLoop::GetEvent +================= +*/ +sysEvent_t idEventLoop::GetEvent( void ) { + if ( com_pushedEventsHead > com_pushedEventsTail ) { + com_pushedEventsTail++; + return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; + } + return GetRealEvent(); +} + +/* +================= +idEventLoop::ProcessEvent +================= +*/ +void idEventLoop::ProcessEvent( sysEvent_t ev ) { + // track key up / down states + if ( ev.evType == SE_KEY ) { + idKeyInput::PreliminaryKeyEvent( ev.evValue, ( ev.evValue2 != 0 ) ); + } + + if ( ev.evType == SE_CONSOLE ) { + // from a text console outside the game window + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, (char *)ev.evPtr ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" ); + } else { + session->ProcessEvent( &ev ); + } + + // free any block data + if ( ev.evPtr ) { + Mem_Free( ev.evPtr ); + } +} + +/* +=============== +idEventLoop::RunEventLoop +=============== +*/ +int idEventLoop::RunEventLoop( bool commandExecution ) { + sysEvent_t ev; + + while ( 1 ) { + + if ( commandExecution ) { + // execute any bound commands before processing another event + cmdSystem->ExecuteCommandBuffer(); + } + + ev = GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) { + return 0; + } + ProcessEvent( ev ); + } + + return 0; // never reached +} + +/* +============= +idEventLoop::Init +============= +*/ +void idEventLoop::Init( void ) { + + initialTimeOffset = Sys_Milliseconds(); + + common->StartupVariable( "journal", false ); + + if ( com_journal.GetInteger() == 1 ) { + common->Printf( "Journaling events\n" ); + com_journalFile = fileSystem->OpenFileWrite( "journal.dat" ); + com_journalDataFile = fileSystem->OpenFileWrite( "journaldata.dat" ); + } else if ( com_journal.GetInteger() == 2 ) { + common->Printf( "Replaying journaled events\n" ); + com_journalFile = fileSystem->OpenFileRead( "journal.dat" ); + com_journalDataFile = fileSystem->OpenFileRead( "journaldata.dat" ); + } + + if ( !com_journalFile || !com_journalDataFile ) { + com_journal.SetInteger( 0 ); + com_journalFile = 0; + com_journalDataFile = 0; + common->Printf( "Couldn't open journal files\n" ); + } +} + +/* +============= +idEventLoop::Shutdown +============= +*/ +void idEventLoop::Shutdown( void ) { + if ( com_journalFile ) { + fileSystem->CloseFile( com_journalFile ); + com_journalFile = NULL; + } + if ( com_journalDataFile ) { + fileSystem->CloseFile( com_journalDataFile ); + com_journalDataFile = NULL; + } +} + +/* +================ +idEventLoop::Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int idEventLoop::Milliseconds( void ) { +#if 1 // FIXME! + return Sys_Milliseconds() - initialTimeOffset; +#else + sysEvent_t ev; + + // get events and push them until we get a null event with the current time + do { + + ev = Com_GetRealEvent(); + if ( ev.evType != SE_NONE ) { + Com_PushEvent( &ev ); + } + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +#endif +} + +/* +================ +idEventLoop::JournalLevel +================ +*/ +int idEventLoop::JournalLevel( void ) const { + return com_journal.GetInteger(); +} diff --git a/src/framework/file.cpp b/src/framework/file.cpp new file mode 100644 index 0000000..eb24c57 --- /dev/null +++ b/src/framework/file.cpp @@ -0,0 +1,1462 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Unzip.h" + +#define MAX_PRINT_MSG 4096 + +/* +================= +FS_WriteFloatString +================= +*/ +int FS_WriteFloatString( char *buf, const char *fmt, va_list argPtr ) { + long i; + unsigned long u; + double f; + char *str; + int index; + idStr tmp, format; + + index = 0; + + while( *fmt ) { + switch( *fmt ) { + case '%': + format = ""; + format += *fmt++; + while ( (*fmt >= '0' && *fmt <= '9') || + *fmt == '.' || *fmt == '-' || *fmt == '+' || *fmt == '#') { + format += *fmt++; + } + format += *fmt; + switch( *fmt ) { + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + f = va_arg( argPtr, double ); + if ( format.Length() <= 2 ) { + // high precision floating point number without trailing zeros + sprintf( tmp, "%1.10f", f ); + tmp.StripTrailing( '0' ); + tmp.StripTrailing( '.' ); + index += sprintf( buf+index, "%s", tmp.c_str() ); + } + else { + index += sprintf( buf+index, format.c_str(), f ); + } + break; + case 'd': + case 'i': + i = va_arg( argPtr, long ); + index += sprintf( buf+index, format.c_str(), i ); + break; + case 'u': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'o': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'x': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'X': + u = va_arg( argPtr, unsigned long ); + index += sprintf( buf+index, format.c_str(), u ); + break; + case 'c': + i = va_arg( argPtr, long ); + index += sprintf( buf+index, format.c_str(), (char) i ); + break; + case 's': + str = va_arg( argPtr, char * ); + index += sprintf( buf+index, format.c_str(), str ); + break; + case '%': + index += sprintf( buf+index, format.c_str() ); + break; + default: + common->Error( "FS_WriteFloatString: invalid format %s", format.c_str() ); + break; + } + fmt++; + break; + case '\\': + fmt++; + switch( *fmt ) { + case 't': + index += sprintf( buf+index, "\t" ); + break; + case 'v': + index += sprintf( buf+index, "\v" ); + break; + case 'n': + index += sprintf( buf+index, "\n" ); + break; + case '\\': + index += sprintf( buf+index, "\\" ); + break; + default: + common->Error( "FS_WriteFloatString: unknown escape character \'%c\'", *fmt ); + break; + } + fmt++; + break; + default: + index += sprintf( buf+index, "%c", *fmt ); + fmt++; + break; + } + } + + return index; +} + +/* +================================================================================= + +idFile + +================================================================================= +*/ + +/* +================= +idFile::GetName +================= +*/ +const char *idFile::GetName( void ) { + return ""; +} + +/* +================= +idFile::GetFullPath +================= +*/ +const char *idFile::GetFullPath( void ) { + return ""; +} + +/* +================= +idFile::Read +================= +*/ +int idFile_Common::Read( void *buffer, int len ) { + common->FatalError( "idFile::Read: cannot read from idFile" ); + return 0; +} + +/* +================= +idFile::Write +================= +*/ +int idFile_Common::Write( const void *buffer, int len ) { + common->FatalError( "idFile::Write: cannot write to idFile" ); + return 0; +} + +/* +================= +idFile::Length +================= +*/ +int idFile::Length( void ) { + return 0; +} + +/* +================= +idFile::Timestamp +================= +*/ +ID_TIME_T idFile::Timestamp( void ) { + return 0; +} + +/* +================= +idFile::Tell +================= +*/ +int idFile::Tell( void ) { + return 0; +} + +/* +================= +idFile::ForceFlush +================= +*/ +void idFile::ForceFlush( void ) { +} + +/* +================= +idFile::Flush +================= +*/ +void idFile::Flush( void ) { +} + +/* +================= +idFile::Seek +================= +*/ +int idFile::Seek( long offset, fsOrigin_t origin ) { + return -1; +} + +/* +================= +idFile::Rewind +================= +*/ +void idFile_Common::Rewind( void ) { + Seek( 0, FS_SEEK_SET ); +} + +/* +================= +idFile::Printf +================= +*/ +int idFile_Common::Printf( const char *fmt, ... ) { + char buf[MAX_PRINT_MSG]; + int length; + va_list argptr; + + va_start( argptr, fmt ); + length = idStr::vsnPrintf( buf, MAX_PRINT_MSG-1, fmt, argptr ); + va_end( argptr ); + + // so notepad formats the lines correctly + idStr work( buf ); + work.Replace( "\n", "\r\n" ); + + return Write( work.c_str(), work.Length() ); +} + +/* +================= +idFile::VPrintf +================= +*/ +int idFile_Common::VPrintf( const char *fmt, va_list args ) { + char buf[MAX_PRINT_MSG]; + int length; + + length = idStr::vsnPrintf( buf, MAX_PRINT_MSG-1, fmt, args ); + return Write( buf, length ); +} + +/* +================= +idFile::WriteFloatString +================= +*/ +int idFile_Common::WriteFloatString( const char *fmt, ... ) { + char buf[MAX_PRINT_MSG]; + int len; + va_list argPtr; + + va_start( argPtr, fmt ); + len = FS_WriteFloatString( buf, fmt, argPtr ); + va_end( argPtr ); + + return Write( buf, len ); +} + +/* + ================= + idFile::ReadInt + ================= + */ +int idFile_Common::ReadInt( int &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleLong(value); + return result; +} + +/* + ================= + idFile::ReadUnsignedInt + ================= + */ +int idFile_Common::ReadUnsignedInt( unsigned int &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleLong(value); + return result; +} + +/* + ================= + idFile::ReadShort + ================= + */ +int idFile_Common::ReadShort( short &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleShort(value); + return result; +} + +/* + ================= + idFile::ReadUnsignedShort + ================= + */ +int idFile_Common::ReadUnsignedShort( unsigned short &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleShort(value); + return result; +} + +/* + ================= + idFile::ReadChar + ================= + */ +int idFile_Common::ReadChar( char &value ) { + return Read( &value, sizeof( value ) ); +} + +/* + ================= + idFile::ReadUnsignedChar + ================= + */ +int idFile_Common::ReadUnsignedChar( unsigned char &value ) { + return Read( &value, sizeof( value ) ); +} + +/* + ================= + idFile::ReadFloat + ================= + */ +int idFile_Common::ReadFloat( float &value ) { + int result = Read( &value, sizeof( value ) ); + value = LittleFloat(value); + return result; +} + +/* + ================= + idFile::ReadBool + ================= + */ +int idFile_Common::ReadBool( bool &value ) { + unsigned char c; + int result = ReadUnsignedChar( c ); + value = c ? true : false; + return result; +} + +/* + ================= + idFile::ReadString + ================= + */ +int idFile_Common::ReadString( idStr &string ) { + int len; + int result = 0; + + ReadInt( len ); + if ( len >= 0 ) { + string.Fill( ' ', len ); + result = Read( &string[ 0 ], len ); + } + return result; +} + +/* + ================= + idFile::ReadVec2 + ================= + */ +int idFile_Common::ReadVec2( idVec2 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadVec3 + ================= + */ +int idFile_Common::ReadVec3( idVec3 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadVec4 + ================= + */ +int idFile_Common::ReadVec4( idVec4 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile_Common::ReadVec5 + ================= + */ +int idFile_Common::ReadVec5( idVec5 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadVec6 + ================= + */ +int idFile_Common::ReadVec6( idVec6 &vec ) { + int result = Read( &vec, sizeof( vec ) ); + LittleRevBytes( &vec, sizeof(float), sizeof(vec)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::ReadMat3 + ================= + */ +int idFile_Common::ReadMat3( idMat3 &mat ) { + int result = Read( &mat, sizeof( mat ) ); + LittleRevBytes( &mat, sizeof(float), sizeof(mat)/sizeof(float) ); + return result; +} + +/* + ================= + idFile_Common::ReadBounds + ================= + */ +int idFile_Common::ReadBounds( idBounds &bounds ) { + int result = Read( &bounds, sizeof( bounds ) ); + LittleRevBytes( &bounds, sizeof(float), sizeof(bounds)/sizeof(float) ); + return result; +} + +/* + ================= + idFile::WriteInt + ================= + */ +int idFile_Common::WriteInt( const int value ) { + int v = LittleLong(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteUnsignedInt + ================= + */ +int idFile_Common::WriteUnsignedInt( const unsigned int value ) { + unsigned int v = LittleLong(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteShort + ================= + */ +int idFile_Common::WriteShort( const short value ) { + short v = LittleShort(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteUnsignedShort + ================= + */ +int idFile_Common::WriteUnsignedShort( const unsigned short value ) { + unsigned short v = LittleShort(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteChar + ================= + */ +int idFile_Common::WriteChar( const char value ) { + return Write( &value, sizeof( value ) ); +} + +/* + ================= + idFile::WriteUnsignedChar + ================= + */ +int idFile_Common::WriteUnsignedChar( const unsigned char value ) { + return Write( &value, sizeof( value ) ); +} + +/* + ================= + idFile::WriteFloat + ================= + */ +int idFile_Common::WriteFloat( const float value ) { + float v = LittleFloat(value); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteBool + ================= + */ +int idFile_Common::WriteBool( const bool value ) { + unsigned char c = value; + return WriteUnsignedChar( c ); +} + +/* + ================= + idFile::WriteString + ================= + */ +int idFile_Common::WriteString( const char *value ) { + int len; + + len = strlen( value ); + WriteInt( len ); + return Write( value, len ); +} + +/* + ================= + idFile::WriteVec2 + ================= + */ +int idFile_Common::WriteVec2( const idVec2 &vec ) { + idVec2 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteVec3 + ================= + */ +int idFile_Common::WriteVec3( const idVec3 &vec ) { + idVec3 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteVec4 + ================= + */ +int idFile_Common::WriteVec4( const idVec4 &vec ) { + idVec4 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile_Common::WriteVec5 + ================= + */ +int idFile_Common::WriteVec5( const idVec5 &vec ) { + idVec5 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteVec6 + ================= + */ +int idFile_Common::WriteVec6( const idVec6 &vec ) { + idVec6 v = vec; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile::WriteMat3 + ================= + */ +int idFile_Common::WriteMat3( const idMat3 &mat ) { + idMat3 v = mat; + LittleRevBytes(&v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile_Common::WriteBounds + ================= + */ +int idFile_Common::WriteBounds( const idBounds &bounds ) { + idBounds v = bounds; + LittleRevBytes( &v, sizeof(float), sizeof(v)/sizeof(float) ); + return Write( &v, sizeof( v ) ); +} + +/* + ================= + idFile_Common::WriteNumericStructArray + + PDB owner: framework/file.cpp. The retail routine treats each element as a + four-byte integer unless the TT_FLOAT bit is set and emits one text row per + structure. + ================= + */ +void idFile_Common::WriteNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage, const char *prepend ) { + byte *cursor = arrayStorage; + for ( int arrayIndex = 0; arrayIndex < arrayCount; arrayIndex++ ) { + WriteFloatString( "%s", prepend ); + for ( int elementIndex = 0; elementIndex < numStructElements; elementIndex++, cursor += sizeof( int ) ) { + if ( tokenSubTypeStructElements[elementIndex] & TT_FLOAT ) { + WriteFloatString( "%f ", *reinterpret_cast( cursor ) ); + } else { + WriteFloatString( "%u ", *reinterpret_cast( cursor ) ); + } + } + WriteFloatString( "\n" ); + } +} + +/* + ================= + idFile_Common::WriteSyncId / ReadSyncId + ================= + */ +static const int FILE_SYNC_ID = 518508557; + +void idFile_Common::WriteSyncId( void ) { + WriteInt( FILE_SYNC_ID ); +} + +void idFile_Common::ReadSyncId( const char *detail, const char *classname ) { + int value = 0; + ReadInt( value ); + if ( value != FILE_SYNC_ID ) { + if ( classname ) { + common->Error( "File synchronisation problem: %s in class %s", detail, classname ); + } else { + common->Error( "File synchronisation problem: %s", detail ); + } + } +} + +/* +================================================================================= + +idFile_Memory + +================================================================================= +*/ + + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory( void ) { + name = "*unknown*"; + maxSize = 0; + fileSize = 0; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_WRITE ); + filePtr = NULL; + curPtr = NULL; +} + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory( const char *name ) { + this->name = name; + maxSize = 0; + fileSize = 0; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_WRITE ); + filePtr = NULL; + curPtr = NULL; +} + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory( const char *name, char *data, int length ) { + this->name = name; + maxSize = length; + fileSize = 0; + allocated = length; + granularity = 16384; + + mode = ( 1 << FS_WRITE ); + filePtr = data; + curPtr = data; +} + +/* +================= +idFile_Memory::idFile_Memory +================= +*/ +idFile_Memory::idFile_Memory( const char *name, const char *data, int length ) { + this->name = name; + maxSize = 0; + fileSize = length; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_READ ); + filePtr = const_cast(data); + curPtr = const_cast(data); +} + +/* +================= +idFile_Memory::~idFile_Memory +================= +*/ +idFile_Memory::~idFile_Memory( void ) { + if ( filePtr && allocated > 0 && maxSize == 0 ) { + Mem_Free( filePtr ); + } +} + +/* +================= +idFile_Memory::Read +================= +*/ +int idFile_Memory::Read( void *buffer, int len ) { + + if ( !( mode & ( 1 << FS_READ ) ) ) { + common->FatalError( "idFile_Memory::Read: %s not opened in read mode", name.c_str() ); + return 0; + } + + if ( curPtr + len > filePtr + fileSize ) { + len = filePtr + fileSize - curPtr; + } + memcpy( buffer, curPtr, len ); + curPtr += len; + return len; +} + +/* +================= +idFile_Memory::Write +================= +*/ +int idFile_Memory::Write( const void *buffer, int len ) { + + if ( !( mode & ( 1 << FS_WRITE ) ) ) { + common->FatalError( "idFile_Memory::Write: %s not opened in write mode", name.c_str() ); + return 0; + } + + int alloc = curPtr + len + 1 - filePtr - allocated; // need room for len+1 + if ( alloc > 0 ) { + if ( maxSize != 0 ) { + common->Error( "idFile_Memory::Write: exceeded maximum size %d", maxSize ); + return 0; + } + int extra = granularity * ( 1 + alloc / granularity ); + char *newPtr = (char *) Mem_Alloc( allocated + extra ); + if ( allocated ) { + memcpy( newPtr, filePtr, allocated ); + } + allocated += extra; + curPtr = newPtr + ( curPtr - filePtr ); + if ( filePtr ) { + Mem_Free( filePtr ); + } + filePtr = newPtr; + } + memcpy( curPtr, buffer, len ); + curPtr += len; + fileSize += len; + filePtr[ fileSize ] = 0; // len + 1 + return len; +} + +/* +================= +idFile_Memory::Length +================= +*/ +int idFile_Memory::Length( void ) { + return fileSize; +} + +/* +================= +idFile_Memory::Timestamp +================= +*/ +ID_TIME_T idFile_Memory::Timestamp( void ) { + return 0; +} + +/* +================= +idFile_Memory::Tell +================= +*/ +int idFile_Memory::Tell( void ) { + return ( curPtr - filePtr ); +} + +/* +================= +idFile_Memory::Rewind +================= +*/ +void idFile_Memory::Rewind( void ) { + mode = ( 1 << FS_READ ); + Seek( 0, FS_SEEK_SET ); +} + +/* +================= +idFile_Memory::ForceFlush +================= +*/ +void idFile_Memory::ForceFlush( void ) { +} + +/* +================= +idFile_Memory::Flush +================= +*/ +void idFile_Memory::Flush( void ) { +} + +/* +================= +idFile_Memory::Seek + + returns zero on success and -1 on failure +================= +*/ +int idFile_Memory::Seek( long offset, fsOrigin_t origin ) { + + switch( origin ) { + case FS_SEEK_CUR: { + curPtr += offset; + break; + } + case FS_SEEK_END: { + curPtr = filePtr + fileSize - offset; + break; + } + case FS_SEEK_SET: { + curPtr = filePtr + offset; + break; + } + default: { + common->FatalError( "idFile_Memory::Seek: bad origin for %s\n", name.c_str() ); + return -1; + } + } + if ( curPtr < filePtr ) { + curPtr = filePtr; + return -1; + } + if ( curPtr > filePtr + fileSize ) { + curPtr = filePtr + fileSize; + return -1; + } + return 0; +} + +/* +================= +idFile_Memory::MakeReadOnly +================= +*/ +void idFile_Memory::MakeReadOnly( void ) { + mode = ( 1 << FS_READ ); + Rewind(); +} + +/* +================= +idFile_Memory::Clear +================= +*/ +void idFile_Memory::Clear( bool freeMemory ) { + fileSize = 0; + granularity = 16384; + if ( freeMemory ) { + allocated = 0; + Mem_Free( filePtr ); + filePtr = NULL; + curPtr = NULL; + } else { + curPtr = filePtr; + } +} + +/* +================= +idFile_Memory::SetData +================= +*/ +void idFile_Memory::SetData( const char *data, int length ) { + maxSize = 0; + fileSize = length; + allocated = 0; + granularity = 16384; + + mode = ( 1 << FS_READ ); + filePtr = const_cast(data); + curPtr = const_cast(data); +} + + +/* +================================================================================= + +idFile_BitMsg + +================================================================================= +*/ + +/* +================= +idFile_BitMsg::idFile_BitMsg +================= +*/ +idFile_BitMsg::idFile_BitMsg( idBitMsg &msg ) { + name = "*unknown*"; + mode = ( 1 << FS_WRITE ); + this->msg = &msg; +} + +/* +================= +idFile_BitMsg::idFile_BitMsg +================= +*/ +idFile_BitMsg::idFile_BitMsg( const idBitMsg &msg ) { + name = "*unknown*"; + mode = ( 1 << FS_READ ); + this->msg = const_cast(&msg); +} + +/* +================= +idFile_BitMsg::~idFile_BitMsg +================= +*/ +idFile_BitMsg::~idFile_BitMsg( void ) { +} + +/* +================= +idFile_BitMsg::Read +================= +*/ +int idFile_BitMsg::Read( void *buffer, int len ) { + + if ( !( mode & ( 1 << FS_READ ) ) ) { + common->FatalError( "idFile_BitMsg::Read: %s not opened in read mode", name.c_str() ); + return 0; + } + + return msg->ReadData( buffer, len ); +} + +/* +================= +idFile_BitMsg::Write +================= +*/ +int idFile_BitMsg::Write( const void *buffer, int len ) { + + if ( !( mode & ( 1 << FS_WRITE ) ) ) { + common->FatalError( "idFile_Memory::Write: %s not opened in write mode", name.c_str() ); + return 0; + } + + msg->WriteData( buffer, len ); + return len; +} + +/* +================= +idFile_BitMsg::Length +================= +*/ +int idFile_BitMsg::Length( void ) { + return msg->GetSize(); +} + +/* +================= +idFile_BitMsg::Timestamp +================= +*/ +ID_TIME_T idFile_BitMsg::Timestamp( void ) { + return 0; +} + +/* +================= +idFile_BitMsg::Tell +================= +*/ +int idFile_BitMsg::Tell( void ) { + if ( mode & FS_READ ) { + return msg->GetReadCount(); + } else { + return msg->GetSize(); + } +} + +/* +================= +idFile_BitMsg::ForceFlush +================= +*/ +void idFile_BitMsg::ForceFlush( void ) { +} + +/* +================= +idFile_BitMsg::Flush +================= +*/ +void idFile_BitMsg::Flush( void ) { +} + +/* +================= +idFile_BitMsg::Seek + + returns zero on success and -1 on failure +================= +*/ +int idFile_BitMsg::Seek( long offset, fsOrigin_t origin ) { + return -1; +} + + +/* +================================================================================= + +idFile_Permanent + +================================================================================= +*/ + +/* +================= +idFile_Permanent::idFile_Permanent +================= +*/ +idFile_Permanent::idFile_Permanent( void ) { + name = "invalid"; + o = NULL; + mode = 0; + fileSize = 0; + handleSync = false; +} + +/* +================= +idFile_Permanent::~idFile_Permanent +================= +*/ +idFile_Permanent::~idFile_Permanent( void ) { + if ( o ) { + fclose( o ); + } +} + +/* +================= +idFile_Permanent::Read + +Properly handles partial reads +================= +*/ +int idFile_Permanent::Read( void *buffer, int len ) { + int block, remaining; + int read; + byte * buf; + int tries; + + if ( !(mode & ( 1 << FS_READ ) ) ) { + common->FatalError( "idFile_Permanent::Read: %s not opened in read mode", name.c_str() ); + return 0; + } + + if ( !o ) { + return 0; + } + + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while( remaining ) { + block = remaining; + read = fread( buf, 1, block, o ); + if ( read == 0 ) { + // we might have been trying to read from a CD, which + // sometimes returns a 0 read on windows + if ( !tries ) { + tries = 1; + } + else { + fileSystem->AddToReadCount( len - remaining ); + return len-remaining; + } + } + + if ( read == -1 ) { + common->FatalError( "idFile_Permanent::Read: -1 bytes read from %s", name.c_str() ); + } + + remaining -= read; + buf += read; + } + fileSystem->AddToReadCount( len ); + return len; +} + +/* +================= +idFile_Permanent::Write + +Properly handles partial writes +================= +*/ +int idFile_Permanent::Write( const void *buffer, int len ) { + int block, remaining; + int written; + byte * buf; + int tries; + + if ( !( mode & ( 1 << FS_WRITE ) ) ) { + common->FatalError( "idFile_Permanent::Write: %s not opened in write mode", name.c_str() ); + return 0; + } + + if ( !o ) { + return 0; + } + + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while( remaining ) { + block = remaining; + written = fwrite( buf, 1, block, o ); + if ( written == 0 ) { + if ( !tries ) { + tries = 1; + } + else { + common->Printf( "idFile_Permanent::Write: 0 bytes written to %s\n", name.c_str() ); + return 0; + } + } + + if ( written == -1 ) { + common->Printf( "idFile_Permanent::Write: -1 bytes written to %s\n", name.c_str() ); + return 0; + } + + remaining -= written; + buf += written; + fileSize += written; + } + if ( handleSync ) { + fflush( o ); + } + return len; +} + +/* +================= +idFile_Permanent::ForceFlush +================= +*/ +void idFile_Permanent::ForceFlush( void ) { + setvbuf( o, NULL, _IONBF, 0 ); +} + +/* +================= +idFile_Permanent::Flush +================= +*/ +void idFile_Permanent::Flush( void ) { + fflush( o ); +} + +/* +================= +idFile_Permanent::Tell +================= +*/ +int idFile_Permanent::Tell( void ) { + return ftell( o ); +} + +/* +================ +idFile_Permanent::Length +================ +*/ +int idFile_Permanent::Length( void ) { + return fileSize; +} + +/* +================ +idFile_Permanent::Timestamp +================ +*/ +ID_TIME_T idFile_Permanent::Timestamp( void ) { + return Sys_FileTimeStamp( o ); +} + +/* +================= +idFile_Permanent::Seek + + returns zero on success and -1 on failure +================= +*/ +int idFile_Permanent::Seek( long offset, fsOrigin_t origin ) { + int _origin; + + switch( origin ) { + case FS_SEEK_CUR: { + _origin = SEEK_CUR; + break; + } + case FS_SEEK_END: { + _origin = SEEK_END; + break; + } + case FS_SEEK_SET: { + _origin = SEEK_SET; + break; + } + default: { + _origin = SEEK_CUR; + common->FatalError( "idFile_Permanent::Seek: bad origin for %s\n", name.c_str() ); + break; + } + } + + return fseek( o, offset, _origin ); +} + + +/* +================================================================================= + +idFile_InZip + +================================================================================= +*/ + +/* +================= +idFile_InZip::idFile_InZip +================= +*/ +idFile_InZip::idFile_InZip( void ) { + name = "invalid"; + zipFilePos = 0; + fileSize = 0; + memset( &z, 0, sizeof( z ) ); +} + +/* +================= +idFile_InZip::~idFile_InZip +================= +*/ +idFile_InZip::~idFile_InZip( void ) { + unzCloseCurrentFile( z ); + unzClose( z ); +} + +/* +================= +idFile_InZip::Read + +Properly handles partial reads +================= +*/ +int idFile_InZip::Read( void *buffer, int len ) { + int l = unzReadCurrentFile( z, buffer, len ); + fileSystem->AddToReadCount( l ); + return l; +} + +/* +================= +idFile_InZip::Write +================= +*/ +int idFile_InZip::Write( const void *buffer, int len ) { + common->FatalError( "idFile_InZip::Write: cannot write to the zipped file %s", name.c_str() ); + return 0; +} + +/* +================= +idFile_InZip::ForceFlush +================= +*/ +void idFile_InZip::ForceFlush( void ) { + common->FatalError( "idFile_InZip::ForceFlush: cannot flush the zipped file %s", name.c_str() ); +} + +/* +================= +idFile_InZip::Flush +================= +*/ +void idFile_InZip::Flush( void ) { + common->FatalError( "idFile_InZip::Flush: cannot flush the zipped file %s", name.c_str() ); +} + +/* +================= +idFile_InZip::Tell +================= +*/ +int idFile_InZip::Tell( void ) { + return unztell( z ); +} + +/* +================ +idFile_InZip::Length +================ +*/ +int idFile_InZip::Length( void ) { + return fileSize; +} + +/* +================ +idFile_InZip::Timestamp +================ +*/ +ID_TIME_T idFile_InZip::Timestamp( void ) { + return 0; +} + +/* +================= +idFile_InZip::Seek + + returns zero on success and -1 on failure +================= +*/ +#define ZIP_SEEK_BUF_SIZE (1<<15) + +int idFile_InZip::Seek( long offset, fsOrigin_t origin ) { + int res, i; + char *buf; + + switch( origin ) { + case FS_SEEK_END: { + offset = fileSize - offset; + } + case FS_SEEK_SET: { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition( z, zipFilePos ); + unzOpenCurrentFile( z ); + if ( offset <= 0 ) { + return 0; + } + } + case FS_SEEK_CUR: { + buf = (char *) _alloca16( ZIP_SEEK_BUF_SIZE ); + for ( i = 0; i < ( offset - ZIP_SEEK_BUF_SIZE ); i += ZIP_SEEK_BUF_SIZE ) { + res = unzReadCurrentFile( z, buf, ZIP_SEEK_BUF_SIZE ); + if ( res < ZIP_SEEK_BUF_SIZE ) { + return -1; + } + } + res = i + unzReadCurrentFile( z, buf, offset - i ); + return ( res == offset ) ? 0 : -1; + } + default: { + common->FatalError( "idFile_InZip::Seek: bad origin for %s\n", name.c_str() ); + break; + } + } + return -1; +} diff --git a/src/framework/filesystem.cpp b/src/framework/filesystem.cpp new file mode 100644 index 0000000..3076eda --- /dev/null +++ b/src/framework/filesystem.cpp @@ -0,0 +1,4767 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Unzip.h" + +#ifndef PATHSEPERATOR_CHAR +#define PATHSEPERATOR_CHAR '\\' +#define PATHSEPERATOR_STR "\\" +#endif + +#ifdef WIN32 + #include // for _read +#else + #if !__MACH__ && __MWERKS__ + #include + #include + #else + #include + #include + #endif + #include +#endif + +#if ID_ENABLE_CURL + #include "../curl/include/curl/curl.h" +#endif + +/* +============================================================================= + +DOOM FILESYSTEM + +All of Doom's data access is through a hierarchical file system, but the contents of +the file system can be transparently merged from several sources. + +A "relativePath" is a reference to game file data, which must include a terminating zero. +"..", "\\", and ":" are explicitly illegal in qpaths to prevent any references +outside the Doom directory system. + +The "base path" is the path to the directory holding all the game directories and +usually the executable. It defaults to the current directory, but can be overridden +with "+set fs_basepath c:\doom" on the command line. The base path cannot be modified +at all after startup. + +The "save path" is the path to the directory where game files will be saved. It defaults +to the base path, but can be overridden with a "+set fs_savepath c:\doom" on the +command line. Any files that are created during the game (demos, screenshots, etc.) will +be created reletive to the save path. + +The "cd path" is the path to an alternate hierarchy that will be searched if a file +is not located in the base path. A user can do a partial install that copies some +data to a base path created on their hard drive and leave the rest on the cd. It defaults +to the current directory, but it can be overridden with "+set fs_cdpath g:\doom" on the +command line. + +The "dev path" is the path to an alternate hierarchy where the editors and tools used +during development (Radiant, AF editor, dmap, runAAS) will write files to. It defaults to +the cd path, but can be overridden with a "+set fs_devpath c:\doom" on the command line. + +If a user runs the game directly from a CD, the base path would be on the CD. This +should still function correctly, but all file writes will fail (harmlessly). + +The "base game" is the directory under the paths where data comes from by default, and +can be either "base" or "demo". + +The "current game" may be the same as the base game, or it may be the name of another +directory under the paths that should be searched for files before looking in the base +game. The game directory is set with "+set fs_game myaddon" on the command line. This is +the basis for addons. + +No other directories outside of the base game and current game will ever be referenced by +filesystem functions. + +To save disk space and speed up file loading, directory trees can be collapsed into zip +files. The files use a ".pk4" extension to prevent users from unzipping them accidentally, +but otherwise they are simply normal zip files. A game directory can have multiple zip +files of the form "pak0.pk4", "pak1.pk4", etc. Zip files are searched in decending order +from the highest number to the lowest, and will always take precedence over the filesystem. +This allows a pk4 distributed as a patch to override all existing data. + +Because we will have updated executables freely available online, there is no point to +trying to restrict demo / oem versions of the game with code changes. Demo / oem versions +should be exactly the same executables as release versions, but with different data that +automatically restricts where game media can come from to prevent add-ons from working. + +After the paths are initialized, Doom will look for the product.txt file. If not found +and verified, the game will run in restricted mode. In restricted mode, only files +contained in demo/pak0.pk4 will be available for loading, and only if the zip header is +verified to not have been modified. A single exception is made for DoomConfig.cfg. Files +can still be written out in restricted mode, so screenshots and demos are allowed. +Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even +if there is a valid product.txt under the basepath or cdpath. + +If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd +path, it will be copied over to the save path. This is a development aid to help build +test releases and to copy working sets of files. + +If the "fs_copyfiles" cvar is set to 2, any file found in fs_cdpath that is newer than +it's fs_savepath version will be copied to fs_savepath (in addition to the fs_copyfiles 1 +behaviour). + +If the "fs_copyfiles" cvar is set to 3, files from both basepath and cdpath will be copied +over to the save path. This is useful when copying working sets of files mainly from base +path with an additional cd path (which can be a slower network drive for instance). + +If the "fs_copyfiles" cvar is set to 4, files that exist in the cd path but NOT the base path +will be copied to the save path + +NOTE: fs_copyfiles and case sensitivity. On fs_caseSensitiveOS 0 filesystems ( win32 ), the +copied files may change casing when copied over. + +The relative path "sound/newstuff/test.wav" would be searched for in the following places: + +for save path, dev path, base path, cd path: + for current game, base game: + search directory + search zip files + +downloaded files, to be written to save path + current game's directory + +The filesystem can be safely shutdown and reinitialized with different +basedir / cddir / game combinations, but all other subsystems that rely on it +(sound, video) must also be forced to restart. + + +"fs_caseSensitiveOS": +This cvar is set on operating systems that use case sensitive filesystems (Linux and OSX) +It is a common situation to have the media reference filenames, whereas the file on disc +only matches in a case-insensitive way. When "fs_caseSensitiveOS" is set, the filesystem +will always do a case insensitive search. +IMPORTANT: This only applies to files, and not to directories. There is no case-insensitive +matching of directories. All directory names should be lowercase, when "com_developer" is 1, +the filesystem will warn when it catches bad directory situations (regardless of the +"fs_caseSensitiveOS" setting) +When bad casing in directories happen and "fs_caseSensitiveOS" is set, BuildOSPath will +attempt to correct the situation by forcing the path to lowercase. This assumes the media +is stored all lowercase. + +"additional mod path search": +fs_game_base can be used to set an additional search path +in search order, fs_game, fs_game_base, BASEGAME +for instance to base a mod of D3 + D3XP assets, fs_game mymod, fs_game_base d3xp + +============================================================================= +*/ + + + +// define to fix special-cases for GetPackStatus so that files that shipped in +// the wrong place for Doom 3 don't break pure servers. +#define DOOM3_PURE_SPECIAL_CASES + +typedef bool (*pureExclusionFunc_t)( const struct pureExclusion_s &excl, int l, const idStr &name ); + +typedef struct pureExclusion_s { + int nameLen; + int extLen; + const char * name; + const char * ext; + pureExclusionFunc_t func; +} pureExclusion_t; + +bool excludeExtension( const pureExclusion_t &excl, int l, const idStr &name ) { + if ( l > excl.extLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) ) { + return true; + } + return false; +} + +bool excludePathPrefixAndExtension( const pureExclusion_t &excl, int l, const idStr &name ) { + if ( l > excl.nameLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) && !name.IcmpPrefixPath( excl.name ) ) { + return true; + } + return false; +} + +bool excludeFullName( const pureExclusion_t &excl, int l, const idStr &name ) { + if ( l == excl.nameLen && !name.Icmp( excl.name ) ) { + return true; + } + return false; +} + +static pureExclusion_t pureExclusions[] = { + { 0, 0, NULL, "/", excludeExtension }, + { 0, 0, NULL, "\\", excludeExtension }, + { 0, 0, NULL, ".pda", excludeExtension }, + { 0, 0, NULL, ".gui", excludeExtension }, + { 0, 0, NULL, ".pd", excludeExtension }, + { 0, 0, NULL, ".lang", excludeExtension }, + { 0, 0, "sound/VO", ".ogg", excludePathPrefixAndExtension }, + { 0, 0, "sound/VO", ".wav", excludePathPrefixAndExtension }, +#if defined DOOM3_PURE_SPECIAL_CASES + // add any special-case files or paths for pure servers here + { 0, 0, "sound/ed/marscity/vo_intro_cutscene.ogg", NULL, excludeFullName }, + { 0, 0, "sound/weapons/soulcube/energize_01.ogg", NULL, excludeFullName }, + { 0, 0, "sound/xian/creepy/vocal_fx", ".ogg", excludePathPrefixAndExtension }, + { 0, 0, "sound/xian/creepy/vocal_fx", ".wav", excludePathPrefixAndExtension }, + { 0, 0, "sound/feedback", ".ogg", excludePathPrefixAndExtension }, + { 0, 0, "sound/feedback", ".wav", excludePathPrefixAndExtension }, + { 0, 0, "guis/assets/mainmenu/chnote.tga", NULL, excludeFullName }, + { 0, 0, "sound/levels/alphalabs2/uac_better_place.ogg", NULL, excludeFullName }, + { 0, 0, "gfx/2d/bigchars.tga", NULL, excludeFullName }, + { 0, 0, "dds/gfx/2d/bigchars.dds", NULL, excludeFullName }, + { 0, 0, "fonts", ".tga", excludePathPrefixAndExtension }, + { 0, 0, "dds/fonts", ".dds", excludePathPrefixAndExtension }, + { 0, 0, "default.cfg", NULL, excludeFullName }, + // russian zpak001.pk4 + { 0, 0, "fonts", ".dat", excludePathPrefixAndExtension }, + { 0, 0, "guis/temp.guied", NULL, excludeFullName }, +#endif + { 0, 0, NULL, NULL, NULL } +}; + +// ensures that lengths for pure exclusions are correct +class idInitExclusions { +public: + idInitExclusions() { + for ( int i = 0; pureExclusions[i].func != NULL; i++ ) { + if ( pureExclusions[i].name ) { + pureExclusions[i].nameLen = idStr::Length( pureExclusions[i].name ); + } + if ( pureExclusions[i].ext ) { + pureExclusions[i].extLen = idStr::Length( pureExclusions[i].ext ); + } + } + } +}; + +static idInitExclusions initExclusions; + +#define MAX_ZIPPED_FILE_NAME 2048 +#define FILE_HASH_SIZE 1024 + +typedef struct fileInPack_s { + idStr name; // name of the file + unsigned long pos; // file info position in zip + struct fileInPack_s * next; // next file in the hash +} fileInPack_t; + +typedef enum { + BINARY_UNKNOWN = 0, + BINARY_YES, + BINARY_NO +} binaryStatus_t; + +typedef enum { + PURE_UNKNOWN = 0, // need to run the pak through GetPackStatus + PURE_NEUTRAL, // neutral regarding pureness. gets in the pure list if referenced + PURE_ALWAYS, // always referenced - for pak* named files, unless NEVER + PURE_NEVER // VO paks. may be referenced, won't be in the pure lists +} pureStatus_t; + +typedef struct { + idList depends; + idList mapDecls; +} addonInfo_t; + +typedef struct { + idStr pakFilename; // c:\doom\base\pak0.pk4 + unzFile handle; + int checksum; + int numfiles; + int length; + bool referenced; + binaryStatus_t binary; + bool addon; // this is an addon pack - addon_search tells if it's 'active' + bool addon_search; // is in the search list + addonInfo_t *addon_info; + pureStatus_t pureStatus; + bool isNew; // for downloaded paks + fileInPack_t *hashTable[FILE_HASH_SIZE]; + fileInPack_t *buildBuffer; +} pack_t; + +typedef struct { + idStr path; // c:\doom + idStr gamedir; // base +} directory_t; + +typedef struct searchpath_s { + pack_t * pack; // only one of pack / dir will be non NULL + directory_t * dir; + struct searchpath_s *next; +} searchpath_t; + +// search flags when opening a file +#define FSFLAG_SEARCH_DIRS ( 1 << 0 ) +#define FSFLAG_SEARCH_PAKS ( 1 << 1 ) +#define FSFLAG_PURE_NOREF ( 1 << 2 ) +#define FSFLAG_BINARY_ONLY ( 1 << 3 ) +#define FSFLAG_SEARCH_ADDONS ( 1 << 4 ) + +// 3 search path (fs_savepath fs_basepath fs_cdpath) +// + .jpg and .tga +#define MAX_CACHED_DIRS 6 + +// how many OSes to handle game paks for ( we don't have to know them precisely ) +#define MAX_GAME_OS 6 +#define BINARY_CONFIG "binary.conf" +#define ADDON_CONFIG "addon.conf" + +class idDEntry : public idStrList { +public: + idDEntry() {} + virtual ~idDEntry() {} + + bool Matches( const char *directory, const char *extension ) const; + void Init( const char *directory, const char *extension, const idStrList &list ); + void Clear( void ); + +private: + idStr directory; + idStr extension; +}; + +class idFileSystemLocal : public idFileSystem { +public: + idFileSystemLocal( void ); + + virtual void Init( void ); + virtual void StartBackgroundDownloadThread( void ); + virtual void Restart( void ); + virtual void Shutdown( bool reloading ); + virtual bool IsInitialized( void ) const; + virtual bool PerformingCopyFiles( void ) const; + virtual idModList * ListMods( void ); + virtual void FreeModList( idModList *modList ); + virtual idFileList * ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL ); + virtual idFileList * ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL ); + virtual void FreeFileList( idFileList *fileList ); + virtual const char * OSPathToRelativePath( const char *OSPath ); + virtual const char * RelativePathToOSPath( const char *relativePath, const char *basePath ); + virtual const char * BuildOSPath( const char *base, const char *game, const char *relativePath ); + virtual void CreateOSPath( const char *OSPath ); + virtual bool FileIsInPAK( const char *relativePath ); + virtual void UpdatePureServerChecksums( void ); + virtual bool UpdateGamePakChecksums( void ); + virtual fsPureReply_t SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum ); +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual fsPureReply_t SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum[ MAX_GAMEPAK_PER_OS ], int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum, int *restartGamePakChecksum ); +#endif + virtual void GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *gamePakChecksum ); + virtual void SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum ); + virtual void ClearPureChecksums( void ); + virtual unsigned int GetOSMask( void ); + virtual int ReadFile( const char *relativePath, void **buffer, unsigned *timestamp ); + virtual void FreeFile( void *buffer ); + virtual int WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" ); + virtual void RemoveFile( const char *relativePath, const char *basePath = "fs_savepath" ); + virtual int RemoveExplicitFile( const char *OSPath ); + virtual void SetIsFileLoadingAllowed( bool mode ); + virtual bool GetIsFileLoadingAllowed( void ) const; + virtual void SetAssetLogName( const char *logName ); + virtual void WriteAssetLog( void ); + virtual void ClearAssetLog( void ); + virtual const char * GetAssetLogName( void ); + virtual idFile * GetNewFileMemory( void ); + virtual idFile * GetNewFilePermanent( void ); + virtual idFile * OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak = NULL, bool allowCopyFiles = true, const char* gamedir = NULL ); + virtual idFile * OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL ); + virtual idFile * OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath", bool ASCII = false ); + virtual idFile * OpenFileAppend( const char *relativePath, bool sync = false, const char *basePath = "fs_basepath" ); + virtual idFile * OpenFileByMode( const char *relativePath, fsMode_t mode ); + virtual idFile * OpenExplicitFileRead( const char *OSPath ); + virtual idFile * OpenExplicitFileWrite( const char *OSPath ); + virtual void CloseFile( idFile *f ); + virtual void BackgroundDownload( backgroundDownload_t *bgl ); + virtual void ResetReadCount( void ) { readCount = 0; } + virtual void AddToReadCount( int c ) { readCount += c; } + virtual int GetReadCount( void ) { return readCount; } + virtual void FindDLL( const char *basename, char dllPath[ MAX_OSPATH ], bool updateChecksum ); + virtual void ClearDirCache( void ); +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual int RelativeDownloadPathForChecksum( int checksum, char path[ MAX_STRING_CHARS ] ); +#endif + bool HasD3XP( void ); + bool RunningD3XP( void ); + void CopyFile( const char *fromOSPath, const char *toOSPath ); + virtual int ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary ); +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual int ValidateDownloadPakForRelativePath( const char *relativePath, char path[ MAX_STRING_CHARS ], bool &isGamePakReturn ); +#endif + virtual idFile * MakeTemporaryFile( void ); + virtual int AddZipFile( const char *path ); + virtual bool AddExplicitPak( const char *path ); + virtual void RemoveExplicitPak( const char *path ); + virtual bool IsPakLoaded( const char *path ); + virtual findFile_t FindFile( const char *path ); + virtual int GetNumMaps(); +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual int GetMapDeclIndex( const char *mapName ); +#endif + virtual const idDict * GetMapDecl( int i ); +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual const idDict * GetMapDecl( const char *mapName ); +#endif + virtual void FindMapScreenshot( const char *path, char *buf, int len ); + virtual bool OSpathToImportPath( const char *osPath, idStr &iPath, bool stripTemp = false ); + virtual idFile * OpenImportFileRead( const char *filename ); + virtual void CopyOSFile( const char *fromOSPath, const char *toOSPath ); + virtual void CopyOSFile( idFile *src, const char *toOSPath ); + virtual void WriteDemoHeader( idFile *file ); + virtual int ReadDemoHeader( idFile *file ); +#ifndef Q4_RECON_ENGINE_PRIVATE + virtual bool IsRunningWithRestrictions( void ); + virtual void ReadCodePakLists( const idBitMsg &msg ); + virtual bool HaveCodePakLists( void ) const; + virtual void SelectDefaultLanguage( void ); + virtual void ClearAddonList( void ); +#endif + bool FilenameCompare( const char *s1, const char *s2 ) const; + + static void Dir_f( const idCmdArgs &args ); + static void DirTree_f( const idCmdArgs &args ); + static void Path_f( const idCmdArgs &args ); + static void TouchFile_f( const idCmdArgs &args ); + static void TouchFileList_f( const idCmdArgs &args ); + +private: + friend dword BackgroundDownloadThread( void *parms ); + + searchpath_t * searchPaths; + int readCount; // total bytes read + int loadCount; // total files read + int loadStack; // total files in memory + idStr gameFolder; // this will be a single name without separators + + searchpath_t *addonPaks; // not loaded up, but we saw them + + idDict mapDict; // for GetMapDecl + bool isFileLoadingAllowed; + idStr currentAssetLog; + idStr currentAssetLogUnfiltered; + idStrList assetLog; + + static idCVar fs_debug; + static idCVar fs_restrict; + static idCVar fs_copyfiles; + static idCVar fs_basepath; + static idCVar fs_savepath; + static idCVar fs_cdpath; + static idCVar fs_devpath; + static idCVar fs_game; + static idCVar fs_game_base; + static idCVar fs_importpath; + static idCVar fs_caseSensitiveOS; + static idCVar fs_searchAddons; + + backgroundDownload_t * backgroundDownloads; + backgroundDownload_t defaultBackgroundDownload; + xthreadInfo backgroundThread; + + idList serverPaks; + bool loadedFileFromDir; // set to true once a file was loaded from a directory - can't switch to pure anymore + idList restartChecksums; // used during a restart to set things in right order + idList addonChecksums; // list of checksums that should go to the search list directly ( for restarts ) + int restartGamePakChecksum; + int gameDLLChecksum; // the checksum of the last loaded game DLL + int gamePakChecksum; // the checksum of the pak holding the loaded game DLL + + int gamePakForOS[ MAX_GAME_OS ]; + + idDEntry dir_cache[ MAX_CACHED_DIRS ]; // fifo + int dir_cache_index; + int dir_cache_count; + + int d3xp; // 0: didn't check, -1: not installed, 1: installed + +private: + void ReplaceSeparators( idStr &path, char sep = PATHSEPERATOR_CHAR ); + long HashFileName( const char *fname ) const; + int ListOSFiles( const char *directory, const char *extension, idStrList &list ); + FILE * OpenOSFile( const char *name, const char *mode, idStr *caseSensitiveName = NULL ); + FILE * OpenOSFileCorrectName( idStr &path, const char *mode ); + int DirectFileLength( FILE *o ); + void CopyFile( idFile *src, const char *toOSPath ); + int AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const; + void GetExtensionList( const char *extension, idStrList &extensionList ) const; + int GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir = NULL ); + + int GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir = NULL ); + pack_t * LoadZipFile( const char *zipfile ); + void AddGameDirectory( const char *path, const char *dir ); + void SetupGameDirectories( const char *gameName ); + void Startup( void ); + void SetRestrictions( void ); + // some files can be obtained from directories without compromising si_pure + bool FileAllowedFromDir( const char *path ); + // searches all the paks, no pure check + pack_t * GetPackForChecksum( int checksum, bool searchAddons = false ); + // searches all the paks, no pure check + pack_t * FindPakForFileChecksum( const char *relativePath, int fileChecksum, bool bReference ); + idFile_InZip * ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath ); + int GetFileChecksum( idFile *file ); + pureStatus_t GetPackStatus( pack_t *pak ); + addonInfo_t * ParseAddonDef( const char *buf, const int len ); + void FollowAddonDependencies( pack_t *pak ); + + static size_t CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream ); + // curl_progress_callback in curl.h + static int CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ); +}; + +#if defined(Q4_RECON_ENGINE_PRIVATE) && defined(_M_IX86) +static_assert( sizeof( idFileSystemLocal ) == 0x774, "retail idFileSystemLocal ABI drift" ); +#endif + +idCVar idFileSystemLocal::fs_restrict( "fs_restrict", "", CVAR_SYSTEM | CVAR_INIT | CVAR_BOOL, "" ); +idCVar idFileSystemLocal::fs_debug( "fs_debug", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar idFileSystemLocal::fs_copyfiles( "fs_copyfiles", "0", CVAR_SYSTEM | CVAR_INIT | CVAR_INTEGER, "", 0, 4, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar idFileSystemLocal::fs_basepath( "fs_basepath", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar idFileSystemLocal::fs_savepath( "fs_savepath", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar idFileSystemLocal::fs_cdpath( "fs_cdpath", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar idFileSystemLocal::fs_devpath( "fs_devpath", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar idFileSystemLocal::fs_game( "fs_game", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "mod path" ); +idCVar idFileSystemLocal::fs_game_base( "fs_game_base", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "alternate mod path, searched after the main fs_game path, before the basedir" ); +idCVar idFileSystemLocal::fs_importpath( "fs_importpath", "", CVAR_SYSTEM | CVAR_INIT, "import path for development assets" ); +#ifdef WIN32 +idCVar idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +#else +idCVar idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "1", CVAR_SYSTEM | CVAR_BOOL, "" ); +#endif +idCVar idFileSystemLocal::fs_searchAddons( "fs_searchAddons", "0", CVAR_SYSTEM | CVAR_BOOL, "search all addon pk4s ( disables addon functionality )" ); + +idFileSystemLocal fileSystemLocal; +idFileSystem * fileSystem = &fileSystemLocal; + +/* +================ +idFileSystemLocal::idFileSystemLocal +================ +*/ +idFileSystemLocal::idFileSystemLocal( void ) { + searchPaths = NULL; + readCount = 0; + loadCount = 0; + loadStack = 0; + isFileLoadingAllowed = true; + dir_cache_index = 0; + dir_cache_count = 0; + d3xp = 0; + loadedFileFromDir = false; + restartGamePakChecksum = 0; + memset( &backgroundThread, 0, sizeof( backgroundThread ) ); + addonPaks = NULL; +} + +/* +================ +idFileSystemLocal::HashFileName + +return a hash value for the filename +================ +*/ +long idFileSystemLocal::HashFileName( const char *fname ) const { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while( fname[i] != '\0' ) { + letter = idStr::ToLower( fname[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter == '\\' ) { + letter = '/'; // damn path names + } + hash += (long)(letter) * (i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +=========== +idFileSystemLocal::FilenameCompare + +Ignore case and separator char distinctions +=========== +*/ +bool idFileSystemLocal::FilenameCompare( const char *s1, const char *s2 ) const { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( c1 >= 'a' && c1 <= 'z' ) { + c1 -= ('a' - 'A'); + } + if ( c2 >= 'a' && c2 <= 'z' ) { + c2 -= ('a' - 'A'); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if ( c1 != c2 ) { + return true; // strings not equal + } + } while( c1 ); + + return false; // strings are equal +} + +/* +================ +idFileSystemLocal::OpenOSFile +optional caseSensitiveName is set to case sensitive file name as found on disc (fs_caseSensitiveOS only) +================ +*/ +FILE *idFileSystemLocal::OpenOSFile( const char *fileName, const char *mode, idStr *caseSensitiveName ) { + int i; + FILE *fp; + idStr fpath, entry; + idStrList list; + +#ifndef __MWERKS__ +#ifndef WIN32 + // some systems will let you fopen a directory + struct stat buf; + if ( stat( fileName, &buf ) != -1 && !S_ISREG(buf.st_mode) ) { + return NULL; + } +#endif +#endif + fp = fopen( fileName, mode ); + if ( !fp && fs_caseSensitiveOS.GetBool() ) { + fpath = fileName; + fpath.StripFilename(); + fpath.StripTrailing( PATHSEPERATOR_CHAR ); + if ( ListOSFiles( fpath, NULL, list ) == -1 ) { + return NULL; + } + + for ( i = 0; i < list.Num(); i++ ) { + entry = fpath + PATHSEPERATOR_CHAR + list[i]; + if ( !entry.Icmp( fileName ) ) { + fp = fopen( entry, mode ); + if ( fp ) { + if ( caseSensitiveName ) { + *caseSensitiveName = entry; + caseSensitiveName->StripPath(); + } + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystemLocal::OpenFileRead: changed %s to %s\n", fileName, entry.c_str() ); + } + break; + } else { + // not supposed to happen if ListOSFiles is doing it's job correctly + common->Warning( "idFileSystemLocal::OpenFileRead: fs_caseSensitiveOS 1 could not open %s", entry.c_str() ); + } + } + } + } else if ( caseSensitiveName ) { + *caseSensitiveName = fileName; + caseSensitiveName->StripPath(); + } + return fp; +} + +/* +================ +idFileSystemLocal::OpenOSFileCorrectName +================ +*/ +FILE *idFileSystemLocal::OpenOSFileCorrectName( idStr &path, const char *mode ) { + idStr caseName; + FILE *f = OpenOSFile( path.c_str(), mode, &caseName ); + if ( f ) { + path.StripFilename(); + path += PATHSEPERATOR_STR; + path += caseName; + } + return f; +} + +/* +================ +idFileSystemLocal::DirectFileLength +================ +*/ +int idFileSystemLocal::DirectFileLength( FILE *o ) { + int pos; + int end; + + pos = ftell( o ); + fseek( o, 0, SEEK_END ); + end = ftell( o ); + fseek( o, pos, SEEK_SET ); + return end; +} + +/* +================ +idFileSystemLocal::RemoveExplicitFile + +The retail implementation deliberately bypasses the virtual-file-system +search paths: the caller supplied a complete operating-system path. +================ +*/ +int idFileSystemLocal::RemoveExplicitFile( const char *OSPath ) { + return remove( OSPath ); +} + +/* +================ +idFileSystemLocal::SetIsFileLoadingAllowed +idFileSystemLocal::GetIsFileLoadingAllowed +================ +*/ +void idFileSystemLocal::SetIsFileLoadingAllowed( bool mode ) { + isFileLoadingAllowed = mode; +} + +bool idFileSystemLocal::GetIsFileLoadingAllowed( void ) const { + return isFileLoadingAllowed; +} + +/* +================ +idFileSystemLocal::SetAssetLogName +idFileSystemLocal::WriteAssetLog +idFileSystemLocal::ClearAssetLog +idFileSystemLocal::GetAssetLogName +================ +*/ +void idFileSystemLocal::SetAssetLogName( const char *logName ) { + currentAssetLog = "assetlogs/"; + currentAssetLog += logName ? logName : ""; + currentAssetLog.StripFileExtension(); + currentAssetLogUnfiltered = currentAssetLog; + + const char *entityFilter = cvarSystem->GetCVarString( "si_entityFilter" ); + if ( entityFilter && entityFilter[0] ) { + currentAssetLog += "-"; + currentAssetLog += entityFilter; + } + + common->Printf( "Asset log: %s\n", currentAssetLog.c_str() ); +} + +void idFileSystemLocal::WriteAssetLog( void ) { + idStr filename = currentAssetLog; + filename.SetFileExtension( "txt" ); + + idFile *file = OpenFileWrite( filename.c_str(), "fs_savepath", false ); + if ( !file ) { + common->Warning( "Couldn't write %s.\n", filename.c_str() ); + return; + } + + for ( int i = 0; i < assetLog.Num(); i++ ) { + file->Write( assetLog[i].c_str(), assetLog[i].Length() ); + file->Write( "\n", 1 ); + } + file->Flush(); + CloseFile( file ); + assetLog.Clear(); +} + +void idFileSystemLocal::ClearAssetLog( void ) { + assetLog.Clear(); +} + +const char *idFileSystemLocal::GetAssetLogName( void ) { + return currentAssetLog.c_str(); +} + +/* +================ +idFileSystemLocal::GetNewFileMemory +idFileSystemLocal::GetNewFilePermanent +================ +*/ +idFile *idFileSystemLocal::GetNewFileMemory( void ) { + return new idFile_Memory; +} + +idFile *idFileSystemLocal::GetNewFilePermanent( void ) { + return new idFile_Permanent; +} + +/* +============ +idFileSystemLocal::CreateOSPath + +Creates any directories needed to store the given filename +============ +*/ +void idFileSystemLocal::CreateOSPath( const char *OSPath ) { + char *ofs; + + // make absolutely sure that it can't back up the path + // FIXME: what about c: ? + if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { +#ifdef _DEBUG + common->DPrintf( "refusing to create relative path \"%s\"\n", OSPath ); +#endif + return; + } + + idStr path( OSPath ); + for( ofs = &path[ 1 ]; *ofs ; ofs++ ) { + if ( *ofs == PATHSEPERATOR_CHAR ) { + // create the directory + *ofs = 0; + Sys_Mkdir( path ); + *ofs = PATHSEPERATOR_CHAR; + } + } +} + +/* +================= +idFileSystemLocal::CopyFile + +Copy a fully specified file from one place to another +================= +*/ +void idFileSystemLocal::CopyFile( const char *fromOSPath, const char *toOSPath ) { + FILE *f; + int len; + byte *buf; + + common->Printf( "copy %s to %s\n", fromOSPath, toOSPath ); + f = OpenOSFile( fromOSPath, "rb" ); + if ( !f ) { + return; + } + fseek( f, 0, SEEK_END ); + len = ftell( f ); + fseek( f, 0, SEEK_SET ); + + buf = (byte *)Mem_Alloc( len ); + if ( fread( buf, 1, len, f ) != (unsigned int)len ) { + common->FatalError( "short read in idFileSystemLocal::CopyFile()\n" ); + } + fclose( f ); + + CreateOSPath( toOSPath ); + f = OpenOSFile( toOSPath, "wb" ); + if ( !f ) { + common->Printf( "could not create destination file\n" ); + Mem_Free( buf ); + return; + } + if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) { + common->FatalError( "short write in idFileSystemLocal::CopyFile()\n" ); + } + fclose( f ); + Mem_Free( buf ); +} + +/* +================= +idFileSystemLocal::CopyFile +================= +*/ +void idFileSystemLocal::CopyFile( idFile *src, const char *toOSPath ) { + FILE *f; + int len; + byte *buf; + + common->Printf( "copy %s to %s\n", src->GetName(), toOSPath ); + src->Seek( 0, FS_SEEK_END ); + len = src->Tell(); + src->Seek( 0, FS_SEEK_SET ); + + buf = (byte *)Mem_Alloc( len ); + if ( src->Read( buf, len ) != len ) { + common->FatalError( "Short read in idFileSystemLocal::CopyFile()\n" ); + } + + CreateOSPath( toOSPath ); + f = OpenOSFile( toOSPath, "wb" ); + if ( !f ) { + common->Printf( "could not create destination file\n" ); + Mem_Free( buf ); + return; + } + if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) { + common->FatalError( "Short write in idFileSystemLocal::CopyFile()\n" ); + } + fclose( f ); + Mem_Free( buf ); +} + +/* +================= +idFileSystemLocal::CopyOSFile + +Quake 4 exposes the two internal Doom 3 copy helpers through the public +filesystem interface for the editor/import pipeline. +================= +*/ +void idFileSystemLocal::CopyOSFile( const char *fromOSPath, const char *toOSPath ) { + FILE *source; + FILE *destination; + int len; + byte *buffer; + + common->Printf( "copy %s to %s\n", fromOSPath, toOSPath ); + source = OpenOSFile( fromOSPath, "rb" ); + if ( !source ) { + return; + } + + len = DirectFileLength( source ); + buffer = (byte *)Mem_Alloc( len ); + if ( fread( buffer, 1, len, source ) != (unsigned int)len ) { + common->FatalError( "short read in idFileSystemLocal::CopyOSFile()\n" ); + } + fclose( source ); + + CreateOSPath( toOSPath ); +#ifdef WIN32 + // The original import path could overwrite checked-in, read-only game data. + if ( strstr( toOSPath, "/game/" ) ) { + DWORD attributes = GetFileAttributesA( toOSPath ); + if ( attributes != INVALID_FILE_ATTRIBUTES && ( attributes & FILE_ATTRIBUTE_READONLY ) ) { + SetFileAttributesA( toOSPath, attributes & ~FILE_ATTRIBUTE_READONLY ); + } + } +#endif + destination = OpenOSFile( toOSPath, "wb" ); + if ( !destination ) { + common->Printf( "could not create destination file\n" ); + Mem_Free( buffer ); + return; + } + if ( fwrite( buffer, 1, len, destination ) != (unsigned int)len ) { + common->FatalError( "Quake 4 cannot run due to insufficient disk space.\n" ); + } + fclose( destination ); + Mem_Free( buffer ); +} + +void idFileSystemLocal::CopyOSFile( idFile *src, const char *toOSPath ) { + FILE *destination; + int len; + byte *buffer; + + common->Printf( "copy %s to %s\n", src->GetName(), toOSPath ); + src->Seek( 0, FS_SEEK_END ); + len = src->Tell(); + src->Seek( 0, FS_SEEK_SET ); + + buffer = (byte *)Mem_Alloc( len ); + if ( src->Read( buffer, len ) != len ) { + common->FatalError( "Short read in idFileSystemLocal::CopyOSFile\n" ); + } + + CreateOSPath( toOSPath ); + destination = OpenOSFile( toOSPath, "wb" ); + if ( !destination ) { + common->Printf( "could not create destination file\n" ); + Mem_Free( buffer ); + return; + } + if ( fwrite( buffer, 1, len, destination ) != (unsigned int)len ) { + common->FatalError( "Quake 4 cannot run due to insufficient disk space.\n" ); + } + fclose( destination ); + Mem_Free( buffer ); +} + +/* +==================== +idFileSystemLocal::ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +void idFileSystemLocal::ReplaceSeparators( idStr &path, char sep ) { + char *s; + + for( s = &path[ 0 ]; *s ; s++ ) { + if ( *s == '/' || *s == '\\' ) { + *s = sep; + } + } +} + +/* +=================== +idFileSystemLocal::BuildOSPath +=================== +*/ +const char *idFileSystemLocal::BuildOSPath( const char *base, const char *game, const char *relativePath ) { + static char OSPath[MAX_STRING_CHARS]; + idStr newPath; + + if ( fs_caseSensitiveOS.GetBool() || com_developer.GetBool() ) { + // extract the path, make sure it's all lowercase + idStr testPath, fileName; + + sprintf( testPath, "%s/%s", game , relativePath ); + testPath.StripFilename(); + + if ( testPath.HasUpper() ) { + + common->Warning( "Non-portable: path contains uppercase characters: %s", testPath.c_str() ); + + // attempt a fixup on the fly + if ( fs_caseSensitiveOS.GetBool() ) { + testPath.ToLower(); + fileName = relativePath; + fileName.StripPath(); + sprintf( newPath, "%s/%s/%s", base, testPath.c_str(), fileName.c_str() ); + ReplaceSeparators( newPath ); + common->DPrintf( "Fixed up to %s\n", newPath.c_str() ); + idStr::Copynz( OSPath, newPath, sizeof( OSPath ) ); + return OSPath; + } + } + } + + idStr strBase = base; + strBase.StripTrailing( '/' ); + strBase.StripTrailing( '\\' ); + sprintf( newPath, "%s/%s/%s", strBase.c_str(), game, relativePath ); + ReplaceSeparators( newPath ); + idStr::Copynz( OSPath, newPath, sizeof( OSPath ) ); + return OSPath; +} + +/* +================ +idFileSystemLocal::OSPathToRelativePath + +takes a full OS path, as might be found in data from a media creation +program, and converts it to a relativePath by stripping off directories + +Returns false if the osPath tree doesn't match any of the existing +search paths. + +================ +*/ +const char *idFileSystemLocal::OSPathToRelativePath( const char *OSPath ) { + static char relativePath[MAX_STRING_CHARS]; + char *s, *base; + + // skip a drive letter? + + // search for anything with "base" in it + // Ase files from max may have the form of: + // "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga" + // which won't match any of our drive letter based search paths + bool ignoreWarning = false; +#ifdef ID_DEMO_BUILD + base = strstr( OSPath, BASE_GAMEDIR ); + idStr tempStr = OSPath; + tempStr.ToLower(); + if ( ( strstr( tempStr, "//" ) || strstr( tempStr, "w:" ) ) && strstr( tempStr, "/doom/base/") ) { + // will cause a warning but will load the file. ase models have + // hard coded doom/base/ in the material names + base = strstr( OSPath, "base" ); + ignoreWarning = true; + } +#else + // look for the first complete directory name + base = (char *)strstr( OSPath, BASE_GAMEDIR ); + while ( base ) { + char c1 = '\0', c2; + if ( base > OSPath ) { + c1 = *(base - 1); + } + c2 = *( base + strlen( BASE_GAMEDIR ) ); + if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) { + break; + } + base = strstr( base + 1, BASE_GAMEDIR ); + } +#endif + // fs_game and fs_game_base support - look for first complete name with a mod path + // ( fs_game searched before fs_game_base ) + const char *fsgame = NULL; + int igame = 0; + for ( igame = 0; igame < 2; igame++ ) { + if ( igame == 0 ) { + fsgame = fs_game.GetString(); + } else if ( igame == 1 ) { + fsgame = fs_game_base.GetString(); + } + if ( base == NULL && fsgame && strlen( fsgame ) ) { + base = (char *)strstr( OSPath, fsgame ); + while ( base ) { + char c1 = '\0', c2; + if ( base > OSPath ) { + c1 = *(base - 1); + } + c2 = *( base + strlen( fsgame ) ); + if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) { + break; + } + base = strstr( base + 1, fsgame ); + } + } + } + + if ( base ) { + s = strstr( base, "/" ); + if ( !s ) { + s = strstr( base, "\\" ); + } + if ( s ) { + strcpy( relativePath, s + 1 ); + if ( fs_debug.GetInteger() > 1 ) { + common->Printf( "idFileSystem::OSPathToRelativePath: %s becomes %s\n", OSPath, relativePath ); + } + return relativePath; + } + } + + if ( !ignoreWarning ) { + common->Warning( "idFileSystem::OSPathToRelativePath failed on %s", OSPath ); + } + strcpy( relativePath, "" ); + return relativePath; +} + +/* +===================== +idFileSystemLocal::RelativePathToOSPath + +Returns a fully qualified path that can be used with stdio libraries +===================== +*/ +const char *idFileSystemLocal::RelativePathToOSPath( const char *relativePath, const char *basePath ) { + const char *path = cvarSystem->GetCVarString( basePath ); + if ( !path[0] ) { + path = fs_savepath.GetString(); + } + return BuildOSPath( path, gameFolder, relativePath ); +} + +/* +================= +idFileSystemLocal::RemoveFile +================= +*/ +void idFileSystemLocal::RemoveFile( const char *relativePath, const char *basePath ) { + idStr OSPath; + + const char *root = cvarSystem->GetCVarString( basePath ); + if ( !root || !root[0] ) { + root = fs_savepath.GetString(); + } + OSPath = BuildOSPath( root, gameFolder, relativePath ); + remove( OSPath ); + + ClearDirCache(); +} + +/* +================ +idFileSystemLocal::FileIsInPAK +================ +*/ +bool idFileSystemLocal::FileIsInPAK( const char *relativePath ) { + searchpath_t *search; + pack_t *pak; + fileInPack_t *pakFile; + long hash; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( !relativePath ) { + common->FatalError( "idFileSystemLocal::FileIsInPAK: NULL 'relativePath' parameter passed\n" ); + } + + // qpaths are not supposed to have a leading slash + if ( relativePath[0] == '/' || relativePath[0] == '\\' ) { + relativePath++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) { + return false; + } + + // + // search through the path, one element at a time + // + + hash = HashFileName( relativePath ); + + for ( search = searchPaths; search; search = search->next ) { + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + + // disregard if it doesn't match one of the allowed pure pak files - or is a localization file + if ( serverPaks.Num() ) { + GetPackStatus( search->pack ); + if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) { + continue; // not on the pure server pak list + } + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FilenameCompare( pakFile->name, relativePath ) ) { + return true; + } + pakFile = pakFile->next; + } while( pakFile != NULL ); + } + } + return false; +} + +/* +============ +idFileSystemLocal::ReadFile + +Filename are relative to the search path +a null buffer will just return the file length and time without loading +timestamp can be NULL if not required +============ +*/ +int idFileSystemLocal::ReadFile( const char *relativePath, void **buffer, unsigned *timestamp ) { + idFile * f; + byte * buf; + int len; + bool isConfig; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( !relativePath || !relativePath[0] ) { + common->FatalError( "idFileSystemLocal::ReadFile with empty name\n" ); + } + + if ( timestamp ) { + *timestamp = FILE_NOT_FOUND_TIMESTAMP; + } + + if ( buffer ) { + *buffer = NULL; + } + + buf = NULL; // quiet compiler warning + + // if this is a .cfg file and we are playing back a journal, read + // it from the journal file + if ( strstr( relativePath, ".cfg" ) == relativePath + strlen( relativePath ) - 4 ) { + isConfig = true; + if ( eventLoop && eventLoop->JournalLevel() == 2 ) { + int r; + + loadCount++; + loadStack++; + + common->DPrintf( "Loading %s from journal file.\n", relativePath ); + len = 0; + r = eventLoop->com_journalDataFile->Read( &len, sizeof( len ) ); + if ( r != sizeof( len ) ) { + *buffer = NULL; + return -1; + } + buf = (byte *)Mem_ClearedAlloc(len+1); + *buffer = buf; + r = eventLoop->com_journalDataFile->Read( buf, len ); + if ( r != len ) { + common->FatalError( "Read from journalDataFile failed" ); + } + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + + return len; + } + } else { + isConfig = false; + } + + // look for it in the filesystem or pack files + f = OpenFileRead( relativePath, ( buffer != NULL ) ); + if ( f == NULL ) { + if ( buffer ) { + *buffer = NULL; + } + return -1; + } + len = f->Length(); + + if ( timestamp ) { + *timestamp = f->Timestamp(); + } + + if ( !buffer ) { + CloseFile( f ); + return len; + } + + loadCount++; + loadStack++; + + buf = (byte *)Mem_ClearedAlloc(len+1); + *buffer = buf; + + f->Read( buf, len ); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + CloseFile( f ); + + // if we are journalling and it is a config file, write it to the journal file + if ( isConfig && eventLoop && eventLoop->JournalLevel() == 1 ) { + common->DPrintf( "Writing %s to journal file.\n", relativePath ); + eventLoop->com_journalDataFile->Write( &len, sizeof( len ) ); + eventLoop->com_journalDataFile->Write( buf, len ); + eventLoop->com_journalDataFile->Flush(); + } + + return len; +} + +/* +============= +idFileSystemLocal::FreeFile +============= +*/ +void idFileSystemLocal::FreeFile( void *buffer ) { + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + if ( !buffer ) { + common->FatalError( "idFileSystemLocal::FreeFile( NULL )" ); + } + loadStack--; + + Mem_Free( buffer ); +} + +/* +============ +idFileSystemLocal::WriteFile + +Filenames are relative to the search path +============ +*/ +int idFileSystemLocal::WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath ) { + idFile *f; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( !relativePath || !buffer ) { + common->FatalError( "idFileSystemLocal::WriteFile: NULL parameter" ); + } + + f = idFileSystemLocal::OpenFileWrite( relativePath, basePath ); + if ( !f ) { + common->Printf( "Failed to open %s\n", relativePath ); + return -1; + } + + size = f->Write( buffer, size ); + + CloseFile( f ); + + return size; +} + +/* +================= +idFileSystemLocal::ParseAddonDef +================= +*/ +addonInfo_t *idFileSystemLocal::ParseAddonDef( const char *buf, const int len ) { + idLexer src; + idToken token, token2; + addonInfo_t *info; + + src.LoadMemory( buf, len, "" ); + src.SetFlags( DECL_LEXER_FLAGS ); + if ( !src.SkipUntilString( "addonDef" ) ) { + src.Warning( "ParseAddonDef: no addonDef" ); + return NULL; + } + if ( !src.ReadToken( &token ) ) { + src.Warning( "Expected {" ); + return NULL; + } + info = new addonInfo_t; + // read addonDef + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + delete info; + return NULL; + } + if ( !token.Icmp( "}" ) ) { + break; + } + if ( token.type != TT_STRING ) { + src.Warning( "Expected quoted string, but found '%s'", token.c_str() ); + delete info; + return NULL; + } + int checksum; + if ( sscanf( token.c_str(), "0x%x", &checksum ) != 1 && sscanf( token.c_str(), "%x", &checksum ) != 1 ) { + src.Warning( "Could not parse checksum '%s'", token.c_str() ); + delete info; + return NULL; + } + info->depends.Append( checksum ); + } + // read any number of mapDef entries + while ( 1 ) { + if ( !src.SkipUntilString( "mapDef" ) ) { + return info; + } + if ( !src.ReadToken( &token ) ) { + src.Warning( "Expected map path" ); + info->mapDecls.DeleteContents( true ); + delete info; + return NULL; + } + idDict *dict = new idDict; + dict->Set( "path", token.c_str() ); + if ( !src.ReadToken( &token ) ) { + src.Warning( "Expected {" ); + info->mapDecls.DeleteContents( true ); + delete dict; + delete info; + return NULL; + } + while ( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + if ( !token.Icmp( "}" ) ) { + break; + } + if ( token.type != TT_STRING ) { + src.Warning( "Expected quoted string, but found '%s'", token.c_str() ); + info->mapDecls.DeleteContents( true ); + delete dict; + delete info; + return NULL; + } + + if ( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + info->mapDecls.DeleteContents( true ); + delete dict; + delete info; + return NULL; + } + + if ( dict->FindKey( token ) ) { + src.Warning( "'%s' already defined", token.c_str() ); + } + dict->Set( token, token2 ); + } + info->mapDecls.Append( dict ); + } + assert( false ); + return NULL; +} + +/* +================= +idFileSystemLocal::LoadZipFile +================= +*/ +pack_t *idFileSystemLocal::LoadZipFile( const char *zipfile ) { + fileInPack_t * buildBuffer; + pack_t * pack; + unzFile uf; + int err; + unz_global_info gi; + char filename_inzip[MAX_ZIPPED_FILE_NAME]; + unz_file_info file_info; + int i; + long hash; + int fs_numHeaderLongs; + int * fs_headerLongs; + FILE *f; + int len; + int confHash; + fileInPack_t *pakFile; + + f = OpenOSFile( zipfile, "rb" ); + if ( !f ) { + return NULL; + } + fseek( f, 0, SEEK_END ); + len = ftell( f ); + fclose( f ); + + fs_numHeaderLongs = 0; + + uf = unzOpen( zipfile ); + err = unzGetGlobalInfo( uf, &gi ); + + if ( err != UNZ_OK ) { + return NULL; + } + + buildBuffer = new fileInPack_t[gi.number_entry]; + pack = new pack_t; + for( i = 0; i < FILE_HASH_SIZE; i++ ) { + pack->hashTable[i] = NULL; + } + + pack->pakFilename = zipfile; + pack->handle = uf; + pack->numfiles = gi.number_entry; + pack->buildBuffer = buildBuffer; + pack->referenced = false; + pack->binary = BINARY_UNKNOWN; + pack->addon = false; + pack->addon_search = false; + pack->addon_info = NULL; + pack->pureStatus = PURE_UNKNOWN; + pack->isNew = false; + + pack->length = len; + + unzGoToFirstFile(uf); + fs_headerLongs = (int *)Mem_ClearedAlloc( gi.number_entry * sizeof(int) ); + for ( i = 0; i < (int)gi.number_entry; i++ ) { + err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0 ); + if ( err != UNZ_OK ) { + break; + } + if ( file_info.uncompressed_size > 0 ) { + fs_headerLongs[fs_numHeaderLongs++] = LittleLong( file_info.crc ); + } + hash = HashFileName( filename_inzip ); + buildBuffer[i].name = filename_inzip; + buildBuffer[i].name.ToLower(); + buildBuffer[i].name.BackSlashesToSlashes(); + // store the file position in the zip + unzGetCurrentFileInfoPosition( uf, &buildBuffer[i].pos ); + // add the file to the hash + buildBuffer[i].next = pack->hashTable[hash]; + pack->hashTable[hash] = &buildBuffer[i]; + // go to the next file in the zip + unzGoToNextFile(uf); + } + + // check if this is an addon pak + pack->addon = false; + confHash = HashFileName( ADDON_CONFIG ); + for ( pakFile = pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) { + if ( !FilenameCompare( pakFile->name, ADDON_CONFIG ) ) { + pack->addon = true; + idFile_InZip *file = ReadFileFromZip( pack, pakFile, ADDON_CONFIG ); + // may be just an empty file if you don't bother about the mapDef + if ( file && file->Length() ) { + char *buf; + buf = new char[ file->Length() + 1 ]; + file->Read( (void *)buf, file->Length() ); + buf[ file->Length() ] = '\0'; + pack->addon_info = ParseAddonDef( buf, file->Length() ); + delete[] buf; + } + if ( file ) { + CloseFile( file ); + } + break; + } + } + + pack->checksum = MD4_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs ); + pack->checksum = LittleLong( pack->checksum ); + + Mem_Free( fs_headerLongs ); + + return pack; +} + +/* +=============== +idFileSystemLocal::AddZipFile +adds a downloaded pak file to the list so we can work out what we have and what we still need +the isNew flag is set to true, indicating that we cannot add this pak to the search lists without a restart +=============== +*/ +int idFileSystemLocal::AddZipFile( const char *path ) { + idStr fullpath = fs_savepath.GetString(); + pack_t *pak; + searchpath_t *search, *last; + + fullpath.AppendPath( path ); + pak = LoadZipFile( fullpath ); + if ( !pak ) { + common->Warning( "AddZipFile %s failed\n", path ); + return 0; + } + // insert the pak at the end of the search list - temporary until we restart + pak->isNew = true; + search = new searchpath_t; + search->dir = NULL; + search->pack = pak; + search->next = NULL; + last = searchPaths; + while ( last->next ) { + last = last->next; + } + last->next = search; + common->Printf( "Appended pk4 %s with checksum 0x%x\n", pak->pakFilename.c_str(), pak->checksum ); + return pak->checksum; +} + +/* +=============== +idFileSystemLocal::AddExplicitPak +idFileSystemLocal::RemoveExplicitPak +idFileSystemLocal::IsPakLoaded + +Retail Quake 4 exposes these on Win32 as well as Xenon. Unlike AddZipFile, +the supplied path is already absolute and the pack is immediately searchable. +=============== +*/ +bool idFileSystemLocal::IsPakLoaded( const char *path ) { + for ( searchpath_t *search = searchPaths; search; search = search->next ) { + if ( search->pack && !search->pack->pakFilename.Cmp( path ) ) { + return true; + } + } + return false; +} + +bool idFileSystemLocal::AddExplicitPak( const char *path ) { + if ( IsPakLoaded( path ) ) { + return true; + } + + pack_t *pak = LoadZipFile( path ); + if ( !pak ) { + common->Warning( "AddZipFile %s failed\n", path ); + return false; + } + + searchpath_t *search = new searchpath_t; + search->pack = pak; + search->dir = NULL; + search->next = NULL; + if ( !searchPaths ) { + searchPaths = search; + } else { + searchpath_t *last = searchPaths; + while ( last->next ) { + last = last->next; + } + last->next = search; + } + + common->Printf( "Appended pk4 %s with checksum 0x%x\n", pak->pakFilename.c_str(), pak->checksum ); + return true; +} + +void idFileSystemLocal::RemoveExplicitPak( const char *path ) { + searchpath_t *previous = NULL; + searchpath_t *search = searchPaths; + while ( search ) { + if ( search->pack && !search->pack->pakFilename.Cmp( path ) ) { + break; + } + previous = search; + search = search->next; + } + if ( !search ) { + return; + } + + pack_t *pak = search->pack; + unzClose( pak->handle ); + delete [] pak->buildBuffer; + if ( pak->addon_info ) { + pak->addon_info->mapDecls.DeleteContents( true ); + delete pak->addon_info; + } + delete pak; + + if ( previous ) { + previous->next = search->next; + } else { + searchPaths = search->next; + } + delete search; +} + +/* +=============== +idFileSystemLocal::AddUnique +=============== +*/ +int idFileSystemLocal::AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const { + int i, hashKey; + + hashKey = hashIndex.GenerateKey( name ); + for ( i = hashIndex.First( hashKey ); i >= 0; i = hashIndex.Next( i ) ) { + if ( list[i].Icmp( name ) == 0 ) { + return i; + } + } + i = list.Append( name ); + hashIndex.Add( hashKey, i ); + return i; +} + +/* +=============== +idFileSystemLocal::GetExtensionList +=============== +*/ +void idFileSystemLocal::GetExtensionList( const char *extension, idStrList &extensionList ) const { + int s, e, l; + + l = idStr::Length( extension ); + s = 0; + while( 1 ) { + e = idStr::FindChar( extension, '|', s, l ); + if ( e != -1 ) { + extensionList.Append( idStr( extension, s, e ) ); + s = e + 1; + } else { + extensionList.Append( idStr( extension, s, l ) ); + break; + } + } +} + +/* +=============== +idFileSystemLocal::GetFileList + +Does not clear the list first so this can be used to progressively build a file list. +When 'sort' is true only the new files added to the list are sorted. +=============== +*/ +int idFileSystemLocal::GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir ) { + searchpath_t * search; + fileInPack_t * buildBuffer; + int i, j; + int pathLength; + int length; + const char * name; + pack_t * pak; + idStr work; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( !extensions.Num() ) { + return 0; + } + + if ( !relativePath ) { + return 0; + } + pathLength = strlen( relativePath ); + if ( pathLength ) { + pathLength++; // for the trailing '/' + } + + // search through the path, one element at a time, adding to list + for( search = searchPaths; search != NULL; search = search->next ) { + if ( search->dir ) { + if(gamedir && strlen(gamedir)) { + if(search->dir->gamedir != gamedir) { + continue; + } + } + + idStrList sysFiles; + idStr netpath; + + netpath = BuildOSPath( search->dir->path, search->dir->gamedir, relativePath ); + + for ( i = 0; i < extensions.Num(); i++ ) { + + // scan for files in the filesystem + ListOSFiles( netpath, extensions[i], sysFiles ); + + // if we are searching for directories, remove . and .. + if ( extensions[i][0] == '/' && extensions[i][1] == 0 ) { + sysFiles.Remove( "." ); + sysFiles.Remove( ".." ); + } + + for( j = 0; j < sysFiles.Num(); j++ ) { + // unique the match + if ( fullRelativePath ) { + work = relativePath; + work += "/"; + work += sysFiles[j]; + AddUnique( work, list, hashIndex ); + } + else { + AddUnique( sysFiles[j], list, hashIndex ); + } + } + } + } else if ( search->pack ) { + // look through all the pak file elements + + // exclude any extra packs if we have server paks to search + if ( serverPaks.Num() ) { + GetPackStatus( search->pack ); + if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) { + continue; // not on the pure server pak list + } + } + + pak = search->pack; + buildBuffer = pak->buildBuffer; + for( i = 0; i < pak->numfiles; i++ ) { + + length = buildBuffer[i].name.Length(); + + // if the name is not long anough to at least contain the path + if ( length <= pathLength ) { + continue; + } + + name = buildBuffer[i].name; + + + // check for a path match without the trailing '/' + if ( pathLength && idStr::Icmpn( name, relativePath, pathLength - 1 ) != 0 ) { + continue; + } + + // ensure we have a path, and not just a filename containing the path + if ( name[ pathLength ] == '\0' || name[pathLength - 1] != '/' ) { + continue; + } + + // make sure the file is not in a subdirectory + for ( j = pathLength; name[j+1] != '\0'; j++ ) { + if ( name[j] == '/' ) { + break; + } + } + if ( name[j+1] ) { + continue; + } + + // check for extension match + for ( j = 0; j < extensions.Num(); j++ ) { + if ( length >= extensions[j].Length() && extensions[j].Icmp( name + length - extensions[j].Length() ) == 0 ) { + break; + } + } + if ( j >= extensions.Num() ) { + continue; + } + + // unique the match + if ( fullRelativePath ) { + work = relativePath; + work += "/"; + work += name + pathLength; + work.StripTrailing( '/' ); + AddUnique( work, list, hashIndex ); + } else { + work = name + pathLength; + work.StripTrailing( '/' ); + AddUnique( work, list, hashIndex ); + } + } + } + } + + return list.Num(); +} + +/* +=============== +idFileSystemLocal::ListFiles +=============== +*/ +idFileList *idFileSystemLocal::ListFiles( const char *relativePath, const char *extension, bool sort, bool fullRelativePath, const char* gamedir ) { + idHashIndex hashIndex( 4096, 4096 ); + idStrList extensionList; + + idFileList *fileList = new idFileList; + fileList->basePath = relativePath; + + GetExtensionList( extension, extensionList ); + + GetFileList( relativePath, extensionList, fileList->list, hashIndex, fullRelativePath, gamedir ); + + if ( sort ) { + idStrListSortPaths( fileList->list ); + } + + return fileList; +} + +/* +=============== +idFileSystemLocal::GetFileListTree +=============== +*/ +int idFileSystemLocal::GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir ) { + int i; + idStrList slash, folders( 128 ); + idHashIndex folderHashIndex( 1024, 128 ); + + // recurse through the subdirectories + slash.Append( "/" ); + GetFileList( relativePath, slash, folders, folderHashIndex, true, gamedir ); + for ( i = 0; i < folders.Num(); i++ ) { + if ( folders[i][0] == '.' ) { + continue; + } + if ( folders[i].Icmp( relativePath ) == 0 ){ + continue; + } + GetFileListTree( folders[i], extensions, list, hashIndex, gamedir ); + } + + // list files in the current directory + GetFileList( relativePath, extensions, list, hashIndex, true, gamedir ); + + return list.Num(); +} + +/* +=============== +idFileSystemLocal::ListFilesTree +=============== +*/ +idFileList *idFileSystemLocal::ListFilesTree( const char *relativePath, const char *extension, bool sort, const char* gamedir ) { + idHashIndex hashIndex( 4096, 4096 ); + idStrList extensionList; + + idFileList *fileList = new idFileList(); + fileList->basePath = relativePath; + fileList->list.SetGranularity( 4096 ); + + GetExtensionList( extension, extensionList ); + + GetFileListTree( relativePath, extensionList, fileList->list, hashIndex, gamedir ); + + if ( sort ) { + idStrListSortPaths( fileList->list ); + } + + return fileList; +} + +/* +=============== +idFileSystemLocal::FreeFileList +=============== +*/ +void idFileSystemLocal::FreeFileList( idFileList *fileList ) { + delete fileList; +} + +/* +=============== +idFileSystemLocal::ListMods +=============== +*/ +idModList *idFileSystemLocal::ListMods( void ) { + int i; + const int MAX_DESCRIPTION = 256; + char desc[ MAX_DESCRIPTION ]; + + idStrList dirs; + idStrList pk4s; + + idModList *list = new idModList; + + const char *search[ 4 ]; + int isearch; + + search[0] = fs_savepath.GetString(); + search[1] = fs_devpath.GetString(); + search[2] = fs_basepath.GetString(); + search[3] = fs_cdpath.GetString(); + + for ( isearch = 0; isearch < 4; isearch++ ) { + + dirs.Clear(); + pk4s.Clear(); + + // scan for directories + ListOSFiles( search[ isearch ], "/", dirs ); + + dirs.Remove( "." ); + dirs.Remove( ".." ); + dirs.Remove( "base" ); + dirs.Remove( "pb" ); + + // see if there are any pk4 files in each directory + for( i = 0; i < dirs.Num(); i++ ) { + idStr gamepath = BuildOSPath( search[ isearch ], dirs[ i ], "" ); + ListOSFiles( gamepath, ".pk4", pk4s ); + if ( pk4s.Num() ) { + if ( !list->mods.Find( dirs[ i ] ) ) { + // D3 1.3 #31, only list d3xp if the pak is present + if ( dirs[ i ].Icmp( "d3xp" ) || HasD3XP() ) { + list->mods.Append( dirs[ i ] ); + } + } + } + } + } + + list->mods.Sort(); + + // read the descriptions for each mod - search all paths + for ( i = 0; i < list->mods.Num(); i++ ) { + + for ( isearch = 0; isearch < 4; isearch++ ) { + + idStr descfile = BuildOSPath( search[ isearch ], list->mods[ i ], "description.txt" ); + FILE *f = OpenOSFile( descfile, "r" ); + if ( f ) { + if ( fgets( desc, MAX_DESCRIPTION, f ) ) { + list->descriptions.Append( desc ); + fclose( f ); + break; + } else { + common->DWarning( "Error reading %s", descfile.c_str() ); + fclose( f ); + continue; + } + } + } + + if ( isearch == 4 ) { + list->descriptions.Append( list->mods[ i ] ); + } + } + + list->mods.Insert( "" ); + list->descriptions.Insert( "Doom 3" ); + + assert( list->mods.Num() == list->descriptions.Num() ); + + return list; +} + +/* +=============== +idFileSystemLocal::FreeModList +=============== +*/ +void idFileSystemLocal::FreeModList( idModList *modList ) { + delete modList; +} + +/* +=============== +idDEntry::Matches +=============== +*/ +bool idDEntry::Matches(const char *directory, const char *extension) const { + if ( !idDEntry::directory.Icmp( directory ) && !idDEntry::extension.Icmp( extension ) ) { + return true; + } + return false; +} + +/* +=============== +idDEntry::Init +=============== +*/ +void idDEntry::Init( const char *directory, const char *extension, const idStrList &list ) { + idDEntry::directory = directory; + idDEntry::extension = extension; + idStrList::operator=(list); +} + +/* +=============== +idDEntry::Clear +=============== +*/ +void idDEntry::Clear( void ) { + directory.Clear(); + extension.Clear(); + idStrList::Clear(); +} + +/* +=============== +idFileSystemLocal::ListOSFiles + + call to the OS for a listing of files in an OS directory + optionally, perform some caching of the entries +=============== +*/ +int idFileSystemLocal::ListOSFiles( const char *directory, const char *extension, idStrList &list ) { + int i, j, ret; + + if ( !extension ) { + extension = ""; + } + + if ( !fs_caseSensitiveOS.GetBool() ) { + return Sys_ListFiles( directory, extension, list ); + } + + // try in cache + i = dir_cache_index - 1; + while( i >= dir_cache_index - dir_cache_count ) { + j = (i+MAX_CACHED_DIRS) % MAX_CACHED_DIRS; + if ( dir_cache[j].Matches( directory, extension ) ) { + if ( fs_debug.GetInteger() ) { + //common->Printf( "idFileSystemLocal::ListOSFiles: cache hit: %s\n", directory ); + } + list = dir_cache[j]; + return list.Num(); + } + i--; + } + + if ( fs_debug.GetInteger() ) { + //common->Printf( "idFileSystemLocal::ListOSFiles: cache miss: %s\n", directory ); + } + + ret = Sys_ListFiles( directory, extension, list ); + + if ( ret == -1 ) { + return -1; + } + + // push a new entry + dir_cache[dir_cache_index].Init( directory, extension, list ); + dir_cache_index = (++dir_cache_index) % MAX_CACHED_DIRS; + if ( dir_cache_count < MAX_CACHED_DIRS ) { + dir_cache_count++; + } + + return ret; +} + +/* +================ +idFileSystemLocal::Dir_f +================ +*/ +void idFileSystemLocal::Dir_f( const idCmdArgs &args ) { + idStr relativePath; + idStr extension; + idFileList *fileList; + int i; + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + common->Printf( "usage: dir [extension]\n" ); + return; + } + + if ( args.Argc() == 2 ) { + relativePath = args.Argv( 1 ); + extension = ""; + } + else { + relativePath = args.Argv( 1 ); + extension = args.Argv( 2 ); + if ( extension[0] != '.' ) { + common->Warning( "extension should have a leading dot" ); + } + } + relativePath.BackSlashesToSlashes(); + relativePath.StripTrailing( '/' ); + + common->Printf( "Listing of %s/*%s\n", relativePath.c_str(), extension.c_str() ); + common->Printf( "---------------\n" ); + + fileList = fileSystemLocal.ListFiles( relativePath, extension ); + + for ( i = 0; i < fileList->GetNumFiles(); i++ ) { + common->Printf( "%s\n", fileList->GetFile( i ) ); + } + common->Printf( "%d files\n", fileList->list.Num() ); + + fileSystemLocal.FreeFileList( fileList ); +} + +/* +================ +idFileSystemLocal::DirTree_f +================ +*/ +void idFileSystemLocal::DirTree_f( const idCmdArgs &args ) { + idStr relativePath; + idStr extension; + idFileList *fileList; + int i; + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + common->Printf( "usage: dirtree [extension]\n" ); + return; + } + + if ( args.Argc() == 2 ) { + relativePath = args.Argv( 1 ); + extension = ""; + } + else { + relativePath = args.Argv( 1 ); + extension = args.Argv( 2 ); + if ( extension[0] != '.' ) { + common->Warning( "extension should have a leading dot" ); + } + } + relativePath.BackSlashesToSlashes(); + relativePath.StripTrailing( '/' ); + + common->Printf( "Listing of %s/*%s /s\n", relativePath.c_str(), extension.c_str() ); + common->Printf( "---------------\n" ); + + fileList = fileSystemLocal.ListFilesTree( relativePath, extension ); + + for ( i = 0; i < fileList->GetNumFiles(); i++ ) { + common->Printf( "%s\n", fileList->GetFile( i ) ); + } + common->Printf( "%d files\n", fileList->list.Num() ); + + fileSystemLocal.FreeFileList( fileList ); +} + +/* +============ +idFileSystemLocal::Path_f +============ +*/ +void idFileSystemLocal::Path_f( const idCmdArgs &args ) { + searchpath_t *sp; + int i; + idStr status; + + common->Printf( "Current search path:\n" ); + for ( sp = fileSystemLocal.searchPaths; sp; sp = sp->next ) { + if ( sp->pack ) { + if ( com_developer.GetBool() ) { + sprintf( status, "%s (%i files - 0x%x %s", sp->pack->pakFilename.c_str(), sp->pack->numfiles, sp->pack->checksum, sp->pack->referenced ? "referenced" : "not referenced" ); + if ( sp->pack->addon ) { + status += " - addon)\n"; + } else { + status += ")\n"; + } + common->Printf( status.c_str() ); + } else { + common->Printf( "%s (%i files)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles ); + } + if ( fileSystemLocal.serverPaks.Num() ) { + if ( fileSystemLocal.serverPaks.Find( sp->pack ) ) { + common->Printf( " on the pure list\n" ); + } else { + common->Printf( " not on the pure list\n" ); + } + } + } else { + common->Printf( "%s/%s\n", sp->dir->path.c_str(), sp->dir->gamedir.c_str() ); + } + } + common->Printf( "game DLL: 0x%x in pak: 0x%x\n", fileSystemLocal.gameDLLChecksum, fileSystemLocal.gamePakChecksum ); +#if ID_FAKE_PURE + common->Printf( "Note: ID_FAKE_PURE is enabled\n" ); +#endif + for( i = 0; i < MAX_GAME_OS; i++ ) { + if ( fileSystemLocal.gamePakForOS[ i ] ) { + common->Printf( "OS %d - pak 0x%x\n", i, fileSystemLocal.gamePakForOS[ i ] ); + } + } + // show addon packs that are *not* in the search lists + common->Printf( "Addon pk4s:\n" ); + for ( sp = fileSystemLocal.addonPaks; sp; sp = sp->next ) { + if ( com_developer.GetBool() ) { + common->Printf( "%s (%i files - 0x%x)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles, sp->pack->checksum ); + } else { + common->Printf( "%s (%i files)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles ); + } + } +} + +/* +============ +idFileSystemLocal::GetOSMask +============ +*/ +unsigned int idFileSystemLocal::GetOSMask( void ) { + int i, ret = 0; + for( i = 0; i < MAX_GAME_OS; i++ ) { + if ( fileSystemLocal.gamePakForOS[ i ] ) { + ret |= ( 1 << i ); + } + } + if ( !ret ) { + return -1; + } + return ret; +} + +/* +============ +idFileSystemLocal::TouchFile_f + +The only purpose of this function is to allow game script files to copy +arbitrary files furing an "fs_copyfiles 1" run. +============ +*/ +void idFileSystemLocal::TouchFile_f( const idCmdArgs &args ) { + idFile *f; + + if ( args.Argc() != 2 ) { + common->Printf( "Usage: touchFile \n" ); + return; + } + + f = fileSystemLocal.OpenFileRead( args.Argv( 1 ) ); + if ( f ) { + fileSystemLocal.CloseFile( f ); + } +} + +/* +============ +idFileSystemLocal::TouchFileList_f + +Takes a text file and touches every file in it, use one file per line. +============ +*/ +void idFileSystemLocal::TouchFileList_f( const idCmdArgs &args ) { + + if ( args.Argc() != 2 ) { + common->Printf( "Usage: touchFileList \n" ); + return; + } + + const char *buffer = NULL; + idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + if ( fileSystem->ReadFile( args.Argv( 1 ), ( void** )&buffer, NULL ) && buffer ) { + src.LoadMemory( buffer, strlen( buffer ), args.Argv( 1 ) ); + if ( src.IsLoaded() ) { + idToken token; + while( src.ReadToken( &token ) ) { + common->Printf( "%s\n", token.c_str() ); + session->UpdateScreen(); + idFile *f = fileSystemLocal.OpenFileRead( token ); + if ( f ) { + fileSystemLocal.CloseFile( f ); + } + } + } + } + +} + + +/* +================ +idFileSystemLocal::AddGameDirectory + +Sets gameFolder, adds the directory to the head of the search paths, then loads any pk4 files. +================ +*/ +void idFileSystemLocal::AddGameDirectory( const char *path, const char *dir ) { + int i; + searchpath_t * search; + pack_t * pak; + idStr pakfile; + idStrList pakfiles; + + // check if the search path already exists + for ( search = searchPaths; search; search = search->next ) { + // if this element is a pak file + if ( !search->dir ) { + continue; + } + if ( search->dir->path.Cmp( path ) == 0 && search->dir->gamedir.Cmp( dir ) == 0 ) { + return; + } + } + + gameFolder = dir; + + // + // add the directory to the search path + // + search = new searchpath_t; + search->dir = new directory_t; + search->pack = NULL; + + search->dir->path = path; + search->dir->gamedir = dir; + search->next = searchPaths; + searchPaths = search; + + // find all pak files in this directory + pakfile = BuildOSPath( path, dir, "" ); + pakfile[ pakfile.Length() - 1 ] = 0; // strip the trailing slash + + ListOSFiles( pakfile, ".pk4", pakfiles ); + + // sort them so that later alphabetic matches override + // earlier ones. This makes pak1.pk4 override pak0.pk4 + pakfiles.Sort(); + + for ( i = 0; i < pakfiles.Num(); i++ ) { + pakfile = BuildOSPath( path, dir, pakfiles[i] ); + pak = LoadZipFile( pakfile ); + if ( !pak ) { + continue; + } + // insert the pak after the directory it comes from + search = new searchpath_t; + search->dir = NULL; + search->pack = pak; + search->next = searchPaths->next; + searchPaths->next = search; + common->Printf( "Loaded pk4 %s with checksum 0x%x\n", pakfile.c_str(), pak->checksum ); + } +} + +/* +================ +idFileSystemLocal::SetupGameDirectories + + Takes care of the correct search order. +================ +*/ +void idFileSystemLocal::SetupGameDirectories( const char *gameName ) { + // setup cdpath + if ( fs_cdpath.GetString()[0] ) { + AddGameDirectory( fs_cdpath.GetString(), gameName ); + } + + // setup basepath + if ( fs_basepath.GetString()[0] ) { + AddGameDirectory( fs_basepath.GetString(), gameName ); + } + + // setup devpath + if ( fs_devpath.GetString()[0] ) { + AddGameDirectory( fs_devpath.GetString(), gameName ); + } + + // setup savepath + if ( fs_savepath.GetString()[0] ) { + AddGameDirectory( fs_savepath.GetString(), gameName ); + } +} + +/* +=============== +idFileSystemLocal::FollowDependencies +=============== +*/ +void idFileSystemLocal::FollowAddonDependencies( pack_t *pak ) { + assert( pak ); + if ( !pak->addon_info || !pak->addon_info->depends.Num() ) { + return; + } + int i, num = pak->addon_info->depends.Num(); + for ( i = 0; i < num; i++ ) { + pack_t *deppak = GetPackForChecksum( pak->addon_info->depends[ i ], true ); + if ( deppak ) { + // make sure it hasn't been marked for search already + if ( !deppak->addon_search ) { + // must clean addonChecksums as we go + int addon_index = addonChecksums.FindIndex( deppak->checksum ); + if ( addon_index >= 0 ) { + addonChecksums.RemoveIndex( addon_index ); + } + deppak->addon_search = true; + common->Printf( "Addon pk4 %s 0x%x depends on pak %s 0x%x, will be searched\n", + pak->pakFilename.c_str(), pak->checksum, + deppak->pakFilename.c_str(), deppak->checksum ); + FollowAddonDependencies( deppak ); + } + } else { + common->Printf( "Addon pk4 %s 0x%x depends on unknown pak 0x%x\n", + pak->pakFilename.c_str(), pak->checksum, pak->addon_info->depends[ i ] ); + } + } +} + +/* +================ +idFileSystemLocal::Startup +================ +*/ +void idFileSystemLocal::Startup( void ) { + searchpath_t **search; + int i; + pack_t *pak; + int addon_index; + + common->Printf( "------ Initializing File System ------\n" ); + + if ( restartChecksums.Num() ) { + common->Printf( "restarting in pure mode with %d pak files\n", restartChecksums.Num() ); + } + if ( addonChecksums.Num() ) { + common->Printf( "restarting filesystem with %d addon pak file(s) to include\n", addonChecksums.Num() ); + } + + SetupGameDirectories( BASE_GAMEDIR ); + + // fs_game_base override + if ( fs_game_base.GetString()[0] && + idStr::Icmp( fs_game_base.GetString(), BASE_GAMEDIR ) ) { + SetupGameDirectories( fs_game_base.GetString() ); + } + + // fs_game override + if ( fs_game.GetString()[0] && + idStr::Icmp( fs_game.GetString(), BASE_GAMEDIR ) && + idStr::Icmp( fs_game.GetString(), fs_game_base.GetString() ) ) { + SetupGameDirectories( fs_game.GetString() ); + } + + // currently all addons are in the search list - deal with filtering out and dependencies now + // scan through and deal with dependencies + search = &searchPaths; + while ( *search ) { + if ( !( *search )->pack || !( *search )->pack->addon ) { + search = &( ( *search )->next ); + continue; + } + pak = ( *search )->pack; + if ( fs_searchAddons.GetBool() ) { + // when we have fs_searchAddons on we should never have addonChecksums + assert( !addonChecksums.Num() ); + pak->addon_search = true; + search = &( ( *search )->next ); + continue; + } + addon_index = addonChecksums.FindIndex( pak->checksum ); + if ( addon_index >= 0 ) { + assert( !pak->addon_search ); // any pak getting flagged as addon_search should also have been removed from addonChecksums already + pak->addon_search = true; + addonChecksums.RemoveIndex( addon_index ); + FollowAddonDependencies( pak ); + } + search = &( ( *search )->next ); + } + + // now scan to filter out addons not marked addon_search + search = &searchPaths; + while ( *search ) { + if ( !( *search )->pack || !( *search )->pack->addon ) { + search = &( ( *search )->next ); + continue; + } + assert( !( *search )->dir ); + pak = ( *search )->pack; + if ( pak->addon_search ) { + common->Printf( "Addon pk4 %s with checksum 0x%x is on the search list\n", + pak->pakFilename.c_str(), pak->checksum ); + search = &( ( *search )->next ); + } else { + // remove from search list, put in addons list + searchpath_t *paksearch = *search; + *search = ( *search )->next; + paksearch->next = addonPaks; + addonPaks = paksearch; + common->Printf( "Addon pk4 %s with checksum 0x%x is on addon list\n", + pak->pakFilename.c_str(), pak->checksum ); + } + } + + // all addon paks found and accounted for + assert( !addonChecksums.Num() ); + addonChecksums.Clear(); // just in case + + if ( restartChecksums.Num() ) { + search = &searchPaths; + while ( *search ) { + if ( !( *search )->pack ) { + search = &( ( *search )->next ); + continue; + } + if ( ( i = restartChecksums.FindIndex( ( *search )->pack->checksum ) ) != -1 ) { + if ( i == 0 ) { + // this pak is the next one in the pure search order + serverPaks.Append( ( *search )->pack ); + restartChecksums.RemoveIndex( 0 ); + if ( !restartChecksums.Num() ) { + break; // early out, we're done + } + search = &( ( *search )->next ); + continue; + } else { + // this pak will be on the pure list, but order is not right yet + searchpath_t *aux; + aux = ( *search )->next; + if ( !aux ) { + // last of the list can't be swapped back + if ( fs_debug.GetBool() ) { + common->Printf( "found pure checksum %x at index %d, but the end of search path is reached\n", ( *search )->pack->checksum, i ); + idStr checks; + checks.Clear(); + for ( i = 0; i < serverPaks.Num(); i++ ) { + checks += va( "%p ", serverPaks[ i ] ); + } + common->Printf( "%d pure paks - %s \n", serverPaks.Num(), checks.c_str() ); + checks.Clear(); + for ( i = 0; i < restartChecksums.Num(); i++ ) { + checks += va( "%x ", restartChecksums[ i ] ); + } + common->Printf( "%d paks left - %s\n", restartChecksums.Num(), checks.c_str() ); + } + common->FatalError( "Failed to restart with pure mode restrictions for server connect" ); + } + // put this search path at the end of the list + searchpath_t *search_end; + search_end = ( *search )->next; + while ( search_end->next ) { + search_end = search_end->next; + } + search_end->next = *search; + *search = ( *search )->next; + search_end->next->next = NULL; + continue; + } + } + // this pak is not on the pure list + search = &( ( *search )->next ); + } + // the list must be empty + if ( restartChecksums.Num() ) { + if ( fs_debug.GetBool() ) { + idStr checks; + checks.Clear(); + for ( i = 0; i < serverPaks.Num(); i++ ) { + checks += va( "%p ", serverPaks[ i ] ); + } + common->Printf( "%d pure paks - %s \n", serverPaks.Num(), checks.c_str() ); + checks.Clear(); + for ( i = 0; i < restartChecksums.Num(); i++ ) { + checks += va( "%x ", restartChecksums[ i ] ); + } + common->Printf( "%d paks left - %s\n", restartChecksums.Num(), checks.c_str() ); + } + common->FatalError( "Failed to restart with pure mode restrictions for server connect" ); + } + // also the game pak checksum + // we could check if the game pak is actually present, but we would not be restarting if there wasn't one @ first pure check + gamePakChecksum = restartGamePakChecksum; + } + + // add our commands + cmdSystem->AddCommand( "dir", Dir_f, CMD_FL_SYSTEM, "lists a folder", idCmdSystem::ArgCompletion_FileName ); + cmdSystem->AddCommand( "dirtree", DirTree_f, CMD_FL_SYSTEM, "lists a folder with subfolders" ); + cmdSystem->AddCommand( "path", Path_f, CMD_FL_SYSTEM, "lists search paths" ); + cmdSystem->AddCommand( "touchFile", TouchFile_f, CMD_FL_SYSTEM, "touches a file" ); + cmdSystem->AddCommand( "touchFileList", TouchFileList_f, CMD_FL_SYSTEM, "touches a list of files" ); + + // print the current search paths + Path_f( idCmdArgs() ); + + common->Printf( "file system initialized.\n" ); + common->Printf( "--------------------------------------\n" ); +} + +/* +=================== +idFileSystemLocal::SetRestrictions + +Looks for product keys and restricts media add on ability +if the full version is not found +=================== +*/ +void idFileSystemLocal::SetRestrictions( void ) { +#ifdef ID_DEMO_BUILD + common->Printf( "\nRunning in restricted demo mode.\n\n" ); + // make sure that the pak file has the header checksum we expect + searchpath_t *search; + for ( search = searchPaths; search; search = search->next ) { + if ( search->pack ) { + // a tiny attempt to keep the checksum from being scannable from the exe + if ( ( search->pack->checksum ^ 0x84268436u ) != ( DEMO_PAK_CHECKSUM ^ 0x84268436u ) ) { + common->FatalError( "Corrupted %s: 0x%x", search->pack->pakFilename.c_str(), search->pack->checksum ); + } + } + } + cvarSystem->SetCVarBool( "fs_restrict", true ); +#endif +} + +/* +===================== +idFileSystemLocal::UpdatePureServerChecksums +===================== +*/ +void idFileSystemLocal::UpdatePureServerChecksums( void ) { + searchpath_t *search; + int i; + pureStatus_t status; + + serverPaks.Clear(); + for ( search = searchPaths; search; search = search->next ) { + // is the element a referenced pak file? + if ( !search->pack ) { + continue; + } + status = GetPackStatus( search->pack ); + if ( status == PURE_NEVER ) { + continue; + } + if ( status == PURE_NEUTRAL && !search->pack->referenced ) { + continue; + } + serverPaks.Append( search->pack ); + if ( serverPaks.Num() >= MAX_PURE_PAKS ) { + common->FatalError( "MAX_PURE_PAKS ( %d ) exceeded\n", MAX_PURE_PAKS ); + } + } + if ( fs_debug.GetBool() ) { + idStr checks; + for ( i = 0; i < serverPaks.Num(); i++ ) { + checks += va( "%x ", serverPaks[ i ]->checksum ); + } + common->Printf( "set pure list - %d paks ( %s)\n", serverPaks.Num(), checks.c_str() ); + } +} + +/* +===================== +idFileSystemLocal::UpdateGamePakChecksums +===================== +*/ +bool idFileSystemLocal::UpdateGamePakChecksums( void ) { + searchpath_t *search; + fileInPack_t *pakFile; + int confHash; + idFile *confFile; + char *buf; + idLexer *lexConf; + idToken token; + int id; + + confHash = HashFileName( BINARY_CONFIG ); + + memset( gamePakForOS, 0, sizeof( gamePakForOS ) ); + for ( search = searchPaths; search; search = search->next ) { + if ( !search->pack ) { + continue; + } + search->pack->binary = BINARY_NO; + for ( pakFile = search->pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) { + if ( !FilenameCompare( pakFile->name, BINARY_CONFIG ) ) { + search->pack->binary = BINARY_YES; + confFile = ReadFileFromZip( search->pack, pakFile, BINARY_CONFIG ); + buf = new char[ confFile->Length() + 1 ]; + confFile->Read( (void *)buf, confFile->Length() ); + buf[ confFile->Length() ] = '\0'; + lexConf = new idLexer( buf, confFile->Length(), confFile->GetFullPath() ); + while ( lexConf->ReadToken( &token ) ) { + if ( token.IsNumeric() ) { + id = atoi( token ); + if ( id < MAX_GAME_OS && !gamePakForOS[ id ] ) { + if ( fs_debug.GetBool() ) { + common->Printf( "Adding game pak checksum for OS %d: %s 0x%x\n", id, confFile->GetFullPath(), search->pack->checksum ); + } + gamePakForOS[ id ] = search->pack->checksum; + } + } + } + CloseFile( confFile ); + delete lexConf; + delete[] buf; + } + } + } + + // some sanity checks on the game code references + // make sure that at least the local OS got a pure reference + if ( !gamePakForOS[ BUILD_OS_ID ] ) { + common->Warning( "No game code pak reference found for the local OS" ); + return false; + } + + if ( !cvarSystem->GetCVarBool( "net_serverAllowServerMod" ) && + gamePakChecksum != gamePakForOS[ BUILD_OS_ID ] ) { + common->Warning( "The current game code doesn't match pak files (net_serverAllowServerMod is off)" ); + return false; + } + + return true; +} + +/* +===================== +idFileSystemLocal::GetPackForChecksum +===================== +*/ +pack_t* idFileSystemLocal::GetPackForChecksum( int checksum, bool searchAddons ) { + searchpath_t *search; + for ( search = searchPaths; search; search = search->next ) { + if ( !search->pack ) { + continue; + } + if ( search->pack->checksum == checksum ) { + return search->pack; + } + } + if ( searchAddons ) { + for ( search = addonPaks; search; search = search->next ) { + assert( search->pack && search->pack->addon ); + if ( search->pack->checksum == checksum ) { + return search->pack; + } + } + } + return NULL; +} + +/* +=============== +idFileSystemLocal::ValidateDownloadPakForChecksum +=============== +*/ +int idFileSystemLocal::ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary ) { + int i; + idStrList testList; + idStr name; + idStr relativePath; + bool pakBinary; + pack_t *pak = GetPackForChecksum( checksum ); + + if ( !pak ) { + return 0; + } + + // validate this pak for a potential download + // ignore pak*.pk4 for download. those are reserved to distribution and cannot be downloaded + name = pak->pakFilename; + name.StripPath(); + if ( strstr( name.c_str(), "pak" ) == name.c_str() ) { + common->DPrintf( "%s is not a donwloadable pak\n", pak->pakFilename.c_str() ); + return 0; + } + // check the binary + // a pure server sets the binary flag when starting the game + assert( pak->binary != BINARY_UNKNOWN ); + pakBinary = ( pak->binary == BINARY_YES ) ? true : false; + if ( isBinary != pakBinary ) { + common->DPrintf( "%s binary flag mismatch\n", pak->pakFilename.c_str() ); + return 0; + } + + // extract a path that includes the fs_game: != OSPathToRelativePath + testList.Append( fs_savepath.GetString() ); + testList.Append( fs_devpath.GetString() ); + testList.Append( fs_basepath.GetString() ); + testList.Append( fs_cdpath.GetString() ); + for ( i = 0; i < testList.Num(); i ++ ) { + if ( testList[ i ].Length() && !testList[ i ].Icmpn( pak->pakFilename, testList[ i ].Length() ) ) { + relativePath = pak->pakFilename.c_str() + testList[ i ].Length() + 1; + break; + } + } + if ( i == testList.Num() ) { + common->Warning( "idFileSystem::ValidateDownloadPak: failed to extract relative path for %s", pak->pakFilename.c_str() ); + return 0; + } + idStr::Copynz( path, relativePath, MAX_STRING_CHARS ); + return pak->length; +} + +/* +===================== +idFileSystemLocal::ClearPureChecksums +===================== +*/ +void idFileSystemLocal::ClearPureChecksums( void ) { + common->DPrintf( "Cleared pure server lock\n" ); + serverPaks.Clear(); +} + +/* +===================== +idFileSystemLocal::SetPureServerChecksums +set the pure paks according to what the server asks +if that's not possible, identify why and build an answer +can be: + loadedFileFromDir - some files were loaded from directories instead of paks (a restart in pure pak-only is required) + missing/wrong checksums - some pak files would need to be installed/updated (downloaded for instance) + some pak files currently referenced are not referenced by the server + wrong order - if the pak order doesn't match, means some stuff could have been loaded from somewhere else +server referenced files are prepended to the list if possible ( that doesn't break pureness ) +DLL: + the checksum of the pak containing the DLL is maintained seperately, the server can send different replies by OS +===================== +*/ +fsPureReply_t idFileSystemLocal::SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int _gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum ) { + pack_t *pack; + int i, j, imissing; + bool success = true; + bool canPrepend = true; + char dllName[MAX_OSPATH]; + int dllHash; + fileInPack_t * pakFile; + + sys->DLL_GetFileName( "game", dllName, MAX_OSPATH ); + dllHash = HashFileName( dllName ); + + imissing = 0; + missingChecksums[ 0 ] = 0; + assert( missingGamePakChecksum ); + *missingGamePakChecksum = 0; + + if ( pureChecksums[ 0 ] == 0 ) { + ClearPureChecksums(); + return PURE_OK; + } + + if ( !serverPaks.Num() ) { + // there was no pure lockdown yet - lock to what we already have + UpdatePureServerChecksums(); + } + i = 0; j = 0; + while ( pureChecksums[ i ] ) { + if ( j < serverPaks.Num() && serverPaks[ j ]->checksum == pureChecksums[ i ] ) { + canPrepend = false; // once you start matching into the list there is no prepending anymore + i++; j++; // the pak is matched, is in the right order, continue.. + } else { + pack = GetPackForChecksum( pureChecksums[ i ], true ); + if ( pack && pack->addon && !pack->addon_search ) { + // this is an addon pack, and it's not on our current search list + // setting success to false meaning that a restart including this addon is required + if ( fs_debug.GetBool() ) { + common->Printf( "pak %s checksumed 0x%x is on addon list. Restart required.\n", pack->pakFilename.c_str(), pack->checksum ); + } + success = false; + } + if ( pack && pack->isNew ) { + // that's a downloaded pack, we will need to restart + if ( fs_debug.GetBool() ) { + common->Printf( "pak %s checksumed 0x%x is a newly downloaded file. Restart required.\n", pack->pakFilename.c_str(), pack->checksum ); + } + success = false; + } + if ( pack ) { + if ( canPrepend ) { + // we still have a chance + if ( fs_debug.GetBool() ) { + common->Printf( "prepend pak %s checksumed 0x%x at index %d\n", pack->pakFilename.c_str(), pack->checksum, j ); + } + // NOTE: there is a light possibility this adds at the end of the list if UpdatePureServerChecksums didn't set anything + serverPaks.Insert( pack, j ); + i++; j++; // continue.. + } else { + success = false; + if ( fs_debug.GetBool() ) { + // verbose the situation + if ( serverPaks.Find( pack ) ) { + common->Printf( "pak %s checksumed 0x%x is in the pure list at wrong index. Current index is %d, found at %d\n", pack->pakFilename.c_str(), pack->checksum, j, serverPaks.FindIndex( pack ) ); + } else { + common->Printf( "pak %s checksumed 0x%x can't be added to pure list because of search order\n", pack->pakFilename.c_str(), pack->checksum ); + } + } + i++; // advance server checksums only + } + } else { + // didn't find a matching checksum + success = false; + missingChecksums[ imissing++ ] = pureChecksums[ i ]; + missingChecksums[ imissing ] = 0; + if ( fs_debug.GetBool() ) { + common->Printf( "checksum not found - 0x%x\n", pureChecksums[ i ] ); + } + i++; // advance the server checksums only + } + } + } + while ( j < serverPaks.Num() ) { + success = false; // just in case some extra pak files are referenced at the end of our local list + if ( fs_debug.GetBool() ) { + common->Printf( "pak %s checksumed 0x%x is an extra reference at the end of local pure list\n", serverPaks[ j ]->pakFilename.c_str(), serverPaks[ j ]->checksum ); + } + j++; + } + + // DLL checksuming + if ( !_gamePakChecksum ) { + // server doesn't have knowledge of code we can use ( OS issue ) + return PURE_NODLL; + } + assert( gameDLLChecksum ); +#if ID_FAKE_PURE + gamePakChecksum = _gamePakChecksum; +#endif + if ( _gamePakChecksum != gamePakChecksum ) { + // current DLL is wrong, search for a pak with the approriate checksum + // ( search all paks, the pure list is not relevant here ) + pack = GetPackForChecksum( _gamePakChecksum ); + if ( !pack ) { + if ( fs_debug.GetBool() ) { + common->Printf( "missing the game code pak ( 0x%x )\n", _gamePakChecksum ); + } + // if there are other paks missing they have also been marked above + *missingGamePakChecksum = _gamePakChecksum; + return PURE_MISSING; + } + // if assets paks are missing, don't try any of the DLL restart / NODLL + if ( imissing ) { + return PURE_MISSING; + } + // we have a matching pak + if ( fs_debug.GetBool() ) { + common->Printf( "server's game code pak candidate is '%s' ( 0x%x )\n", pack->pakFilename.c_str(), pack->checksum ); + } + // make sure there is a valid DLL for us + if ( pack->hashTable[ dllHash ] ) { + for ( pakFile = pack->hashTable[ dllHash ]; pakFile; pakFile = pakFile->next ) { + if ( !FilenameCompare( pakFile->name, dllName ) ) { + gamePakChecksum = _gamePakChecksum; // this will be used to extract the DLL in pure mode FindDLL + return PURE_RESTART; + } + } + } + common->Warning( "media is misconfigured. server claims pak '%s' ( 0x%x ) has media for us, but '%s' is not found\n", pack->pakFilename.c_str(), pack->checksum, dllName ); + return PURE_NODLL; + } + + // we reply to missing after DLL check so it can be part of the list + if ( imissing ) { + return PURE_MISSING; + } + + // one last check + if ( loadedFileFromDir ) { + success = false; + if ( fs_debug.GetBool() ) { + common->Printf( "SetPureServerChecksums: there are files loaded from dir\n" ); + } + } + return ( success ? PURE_OK : PURE_RESTART ); +} + +#ifndef Q4_RECON_ENGINE_PRIVATE +fsPureReply_t idFileSystemLocal::SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksums[ MAX_GAMEPAK_PER_OS ], int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum, int *restartGamePakChecksumOut ) { + if ( restartGamePakChecksumOut != NULL ) { + *restartGamePakChecksumOut = restartGamePakChecksum; + } + return SetPureServerChecksums( pureChecksums, gamePakChecksums ? gamePakChecksums[0] : 0, missingChecksums, missingGamePakChecksum ); +} +#endif + +/* +===================== +idFileSystemLocal::GetPureServerChecksums +===================== +*/ +void idFileSystemLocal::GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *_gamePakChecksum ) { + int i; + + for ( i = 0; i < serverPaks.Num(); i++ ) { + checksums[ i ] = serverPaks[ i ]->checksum; + } + checksums[ i ] = 0; + if ( _gamePakChecksum ) { + if ( OS >= 0 ) { + *_gamePakChecksum = gamePakForOS[ OS ]; + } else { + *_gamePakChecksum = gamePakChecksum; + } + } +} + +/* +===================== +idFileSystemLocal::SetRestartChecksums +===================== +*/ +void idFileSystemLocal::SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum ) { + int i; + pack_t *pack; + + restartChecksums.Clear(); + i = 0; + while ( pureChecksums[ i ] ) { + pack = GetPackForChecksum( pureChecksums[ i ], true ); + if ( !pack ) { + common->FatalError( "SetRestartChecksums failed: no pak for checksum 0x%x\n", pureChecksums[i] ); + } + if ( pack->addon && addonChecksums.FindIndex( pack->checksum ) < 0 ) { + // can't mark it pure if we're not even gonna search it :-) + addonChecksums.Append( pack->checksum ); + } + restartChecksums.Append( pureChecksums[ i ] ); + i++; + } + restartGamePakChecksum = gamePakChecksum; +} + +/* +================ +idFileSystemLocal::Init + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void idFileSystemLocal::Init( void ) { + // allow command line parms to override our defaults + // we have to specially handle this, because normal command + // line variable sets don't happen until after the filesystem + // has already been initialized + common->StartupVariable( "fs_basepath", false ); + common->StartupVariable( "fs_savepath", false ); + common->StartupVariable( "fs_cdpath", false ); + common->StartupVariable( "fs_devpath", false ); + common->StartupVariable( "fs_game", false ); + common->StartupVariable( "fs_game_base", false ); + common->StartupVariable( "fs_copyfiles", false ); + common->StartupVariable( "fs_restrict", false ); + common->StartupVariable( "fs_searchAddons", false ); + +#if !ID_ALLOW_D3XP + if ( fs_game.GetString()[0] && !idStr::Icmp( fs_game.GetString(), "d3xp" ) ) { + fs_game.SetString( NULL ); + } + if ( fs_game_base.GetString()[0] && !idStr::Icmp( fs_game_base.GetString(), "d3xp" ) ) { + fs_game_base.SetString( NULL ); + } +#endif + + if ( fs_basepath.GetString()[0] == '\0' ) { + fs_basepath.SetString( Sys_DefaultBasePath() ); + } + if ( fs_savepath.GetString()[0] == '\0' ) { + fs_savepath.SetString( Sys_DefaultSavePath() ); + } + if ( fs_cdpath.GetString()[0] == '\0' ) { + fs_cdpath.SetString( Sys_DefaultCDPath() ); + } + + if ( fs_devpath.GetString()[0] == '\0' ) { +#ifdef WIN32 + fs_devpath.SetString( fs_cdpath.GetString()[0] ? fs_cdpath.GetString() : fs_basepath.GetString() ); +#else + fs_devpath.SetString( fs_savepath.GetString() ); +#endif + } + + // try to start up normally + Startup( ); + + // see if we are going to allow add-ons + SetRestrictions(); + + // spawn a thread to handle background file reads + StartBackgroundDownloadThread(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + // Dedicated servers can run with no outside files at all + if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) { + common->FatalError( "Couldn't load default.cfg" ); + } +} + +/* +================ +idFileSystemLocal::Restart +================ +*/ +void idFileSystemLocal::Restart( void ) { + // free anything we currently have loaded + Shutdown( true ); + + Startup( ); + + // see if we are going to allow add-ons + SetRestrictions(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) { + common->FatalError( "Couldn't load default.cfg" ); + } +} + +/* +================ +idFileSystemLocal::Shutdown + +Frees all resources and closes all files +================ +*/ +void idFileSystemLocal::Shutdown( bool reloading ) { + searchpath_t *sp, *next, *loop; + + gameFolder.Clear(); + + serverPaks.Clear(); + if ( !reloading ) { + restartChecksums.Clear(); + addonChecksums.Clear(); + } + loadedFileFromDir = false; + gameDLLChecksum = 0; + gamePakChecksum = 0; + + ClearDirCache(); + + // free everything - loop through searchPaths and addonPaks + for ( loop = searchPaths; loop; loop == searchPaths ? loop = addonPaks : loop = NULL ) { + for ( sp = loop; sp; sp = next ) { + next = sp->next; + + if ( sp->pack ) { + unzClose( sp->pack->handle ); + delete [] sp->pack->buildBuffer; + if ( sp->pack->addon_info ) { + sp->pack->addon_info->mapDecls.DeleteContents( true ); + delete sp->pack->addon_info; + } + delete sp->pack; + } + if ( sp->dir ) { + delete sp->dir; + } + delete sp; + } + } + + // any FS_ calls will now be an error until reinitialized + searchPaths = NULL; + addonPaks = NULL; + + cmdSystem->RemoveCommand( "path" ); + cmdSystem->RemoveCommand( "dir" ); + cmdSystem->RemoveCommand( "dirtree" ); + cmdSystem->RemoveCommand( "touchFile" ); + + mapDict.Clear(); +} + +/* +================ +idFileSystemLocal::IsInitialized +================ +*/ +bool idFileSystemLocal::IsInitialized( void ) const { + return ( searchPaths != NULL ); +} + + +/* +================================================================================= + +Opening files + +================================================================================= +*/ + +/* +=========== +idFileSystemLocal::FileAllowedFromDir +=========== +*/ +bool idFileSystemLocal::FileAllowedFromDir( const char *path ) { + unsigned int l; + + l = strlen( path ); + + if ( !strcmp( path + l - 4, ".cfg" ) // for config files + || !strcmp( path + l - 4, ".dat" ) // for journal files + || !strcmp( path + l - 4, ".dll" ) // dynamic modules are handled a different way for pure + || !strcmp( path + l - 3, ".so" ) + || ( l > 6 && !strcmp( path + l - 6, ".dylib" ) ) + || ( l > 10 && !strcmp( path + l - 10, ".scriptcfg" ) ) // configuration script, such as map cycle +#if ID_PURE_ALLOWDDS + || !strcmp( path + l - 4, ".dds" ) +#endif + ) { + // note: cd and xp keys, as well as config.spec are opened through an explicit OS path and don't hit this + return true; + } + // savegames + if ( strstr( path, "savegames" ) == path && + ( !strcmp( path + l - 4, ".tga" ) || !strcmp( path + l -4, ".txt" ) || !strcmp( path + l - 5, ".save" ) ) ) { + return true; + } + // screen shots + if ( strstr( path, "screenshots" ) == path && !strcmp( path + l - 4, ".tga" ) ) { + return true; + } + // objective tgas + if ( strstr( path, "maps/game" ) == path && + !strcmp( path + l - 4, ".tga" ) ) { + return true; + } + // splash screens extracted from addons + if ( strstr( path, "guis/assets/splash/addon" ) == path && + !strcmp( path + l -4, ".tga" ) ) { + return true; + } + + return false; +} + +/* +=========== +idFileSystemLocal::GetPackStatus +=========== +*/ +pureStatus_t idFileSystemLocal::GetPackStatus( pack_t *pak ) { + int i, l, hashindex; + fileInPack_t *file; + bool abrt; + idStr name; + + if ( pak->pureStatus != PURE_UNKNOWN ) { + return pak->pureStatus; + } + + // check content for PURE_NEVER + i = 0; + file = pak->buildBuffer; + for ( hashindex = 0; hashindex < FILE_HASH_SIZE; hashindex++ ) { + abrt = false; + file = pak->hashTable[ hashindex ]; + while ( file ) { + abrt = true; + l = file->name.Length(); + for ( int j = 0; pureExclusions[j].func != NULL; j++ ) { + if ( pureExclusions[j].func( pureExclusions[j], l, file->name ) ) { + abrt = false; + break; + } + } + if ( abrt ) { + common->DPrintf( "pak '%s' candidate for pure: '%s'\n", pak->pakFilename.c_str(), file->name.c_str() ); + break; + } + file = file->next; + i++; + } + if ( abrt ) { + break; + } + } + if ( i == pak->numfiles ) { + pak->pureStatus = PURE_NEVER; + return PURE_NEVER; + } + + // check pak name for PURE_ALWAYS + pak->pakFilename.ExtractFileName( name ); + if ( !name.IcmpPrefixPath( "pak" ) ) { + pak->pureStatus = PURE_ALWAYS; + return PURE_ALWAYS; + } + + pak->pureStatus = PURE_NEUTRAL; + return PURE_NEUTRAL; +} + +/* +=========== +idFileSystemLocal::ReadFileFromZip +=========== +*/ +idFile_InZip * idFileSystemLocal::ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath ) { + unz_s * zfi; + FILE * fp; + idFile_InZip *file = new idFile_InZip(); + + // open a new file on the pakfile + file->z = unzReOpen( pak->pakFilename, pak->handle ); + if ( file->z == NULL ) { + common->FatalError( "Couldn't reopen %s", pak->pakFilename.c_str() ); + } + file->name = relativePath; + file->fullPath = pak->pakFilename + "/" + relativePath; + zfi = (unz_s *)file->z; + // in case the file was new + fp = zfi->file; + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition( pak->handle, pakFile->pos ); + // copy the file info into the unzip structure + memcpy( zfi, pak->handle, sizeof(unz_s) ); + // we copy this back into the structure + zfi->file = fp; + // open the file in the zip + unzOpenCurrentFile( file->z ); + file->zipFilePos = pakFile->pos; + file->fileSize = zfi->cur_file_info.uncompressed_size; + return file; +} + +/* +=========== +idFileSystemLocal::OpenFileReadFlags + +Finds the file in the search path, following search flag recommendations +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +idFile *idFileSystemLocal::OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak, bool allowCopyFiles, const char* gamedir ) { + searchpath_t * search; + idStr netpath; + pack_t * pak; + fileInPack_t * pakFile; + directory_t * dir; + long hash; + FILE * fp; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( !relativePath ) { + common->FatalError( "idFileSystemLocal::OpenFileRead: NULL 'relativePath' parameter passed\n" ); + } + + if ( foundInPak ) { + *foundInPak = NULL; + } + + // qpaths are not supposed to have a leading slash + if ( relativePath[0] == '/' || relativePath[0] == '\\' ) { + relativePath++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) { + return NULL; + } + + // edge case + if ( relativePath[0] == '\0' ) { + return NULL; + } + + // make sure the doomkey file is only readable by game at initialization + // any other time the key should only be accessed in memory using the provided functions + if( common->IsInitialized() && idStr::Icmp( relativePath, CDKEY_FILE ) == 0 ) { + return NULL; + } + + // + // search through the path, one element at a time + // + + hash = HashFileName( relativePath ); + + for ( search = searchPaths; search; search = search->next ) { + if ( search->dir && ( searchFlags & FSFLAG_SEARCH_DIRS ) ) { + // check a file in the directory tree + + // if we are running restricted, the only files we + // will allow to come from the directory are .cfg files + if ( fs_restrict.GetBool() || serverPaks.Num() ) { + if ( !FileAllowedFromDir( relativePath ) ) { + continue; + } + } + + dir = search->dir; + + if(gamedir && strlen(gamedir)) { + if(dir->gamedir != gamedir) { + continue; + } + } + + netpath = BuildOSPath( dir->path, dir->gamedir, relativePath ); + fp = OpenOSFileCorrectName( netpath, "rb" ); + if ( !fp ) { + continue; + } + + idFile_Permanent *file = new idFile_Permanent(); + file->o = fp; + file->name = relativePath; + file->fullPath = netpath; + file->mode = ( 1 << FS_READ ); + file->fileSize = DirectFileLength( file->o ); + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s/%s')\n", relativePath, dir->path.c_str(), dir->gamedir.c_str() ); + } + + if ( !loadedFileFromDir && !FileAllowedFromDir( relativePath ) ) { + if ( restartChecksums.Num() ) { + common->FatalError( "'%s' loaded from directory: Failed to restart with pure mode restrictions for server connect", relativePath ); + } + common->DPrintf( "filesystem: switching to pure mode will require a restart. '%s' loaded from directory.\n", relativePath ); + loadedFileFromDir = true; + } + + // if fs_copyfiles is set + if ( allowCopyFiles && fs_copyfiles.GetInteger() ) { + + idStr copypath; + idStr name; + copypath = BuildOSPath( fs_savepath.GetString(), dir->gamedir, relativePath ); + netpath.ExtractFileName( name ); + copypath.StripFilename( ); + copypath += PATHSEPERATOR_STR; + copypath += name; + + bool isFromCDPath = !dir->path.Cmp( fs_cdpath.GetString() ); + bool isFromSavePath = !dir->path.Cmp( fs_savepath.GetString() ); + bool isFromBasePath = !dir->path.Cmp( fs_basepath.GetString() ); + + switch ( fs_copyfiles.GetInteger() ) { + case 1: + // copy from cd path only + if ( isFromCDPath ) { + CopyFile( netpath, copypath ); + } + break; + case 2: + // from cd path + timestamps + if ( isFromCDPath ) { + CopyFile( netpath, copypath ); + } else if ( isFromSavePath || isFromBasePath ) { + idStr sourcepath; + sourcepath = BuildOSPath( fs_cdpath.GetString(), dir->gamedir, relativePath ); + FILE *f1 = OpenOSFile( sourcepath, "r" ); + if ( f1 ) { + ID_TIME_T t1 = Sys_FileTimeStamp( f1 ); + fclose( f1 ); + FILE *f2 = OpenOSFile( copypath, "r" ); + if ( f2 ) { + ID_TIME_T t2 = Sys_FileTimeStamp( f2 ); + fclose( f2 ); + if ( t1 > t2 ) { + CopyFile( sourcepath, copypath ); + } + } + } + } + break; + case 3: + if ( isFromCDPath || isFromBasePath ) { + CopyFile( netpath, copypath ); + } + break; + case 4: + if ( isFromCDPath && !isFromBasePath ) { + CopyFile( netpath, copypath ); + } + break; + } + } + + return file; + } else if ( search->pack && ( searchFlags & FSFLAG_SEARCH_PAKS ) ) { + + if ( !search->pack->hashTable[hash] ) { + continue; + } + + // disregard if it doesn't match one of the allowed pure pak files + if ( serverPaks.Num() ) { + GetPackStatus( search->pack ); + if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) { + continue; // not on the pure server pak list + } + } + + // look through all the pak file elements + pak = search->pack; + + if ( searchFlags & FSFLAG_BINARY_ONLY ) { + // make sure this pak is tagged as a binary file + if ( pak->binary == BINARY_UNKNOWN ) { + int confHash; + fileInPack_t *pakFile; + confHash = HashFileName( BINARY_CONFIG ); + pak->binary = BINARY_NO; + for ( pakFile = search->pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) { + if ( !FilenameCompare( pakFile->name, BINARY_CONFIG ) ) { + pak->binary = BINARY_YES; + break; + } + } + } + if ( pak->binary == BINARY_NO ) { + continue; // not a binary pak, skip + } + } + + for ( pakFile = pak->hashTable[hash]; pakFile; pakFile = pakFile->next ) { + // case and separator insensitive comparisons + if ( !FilenameCompare( pakFile->name, relativePath ) ) { + idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath ); + + if ( foundInPak ) { + *foundInPak = pak; + } + + if ( !pak->referenced && !( searchFlags & FSFLAG_PURE_NOREF ) ) { + // mark this pak referenced + if ( fs_debug.GetInteger( ) ) { + common->Printf( "idFileSystem::OpenFileRead: %s -> adding %s to referenced paks\n", relativePath, pak->pakFilename.c_str() ); + } + pak->referenced = true; + } + + if ( fs_debug.GetInteger( ) ) { + common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s')\n", relativePath, pak->pakFilename.c_str() ); + } + return file; + } + } + } + } + + if ( searchFlags & FSFLAG_SEARCH_ADDONS ) { + for ( search = addonPaks; search; search = search->next ) { + assert( search->pack ); + fileInPack_t *pakFile; + pak = search->pack; + for ( pakFile = pak->hashTable[hash]; pakFile; pakFile = pakFile->next ) { + if ( !FilenameCompare( pakFile->name, relativePath ) ) { + idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath ); + if ( foundInPak ) { + *foundInPak = pak; + } + // we don't toggle pure on paks found in addons - they can't be used without a reloadEngine anyway + if ( fs_debug.GetInteger( ) ) { + common->Printf( "idFileSystem::OpenFileRead: %s (found in addon pk4 '%s')\n", relativePath, search->pack->pakFilename.c_str() ); + } + return file; + } + } + } + } + + if ( fs_debug.GetInteger( ) ) { + common->Printf( "Can't find %s\n", relativePath ); + } + + return NULL; +} + +/* +=========== +idFileSystemLocal::OpenFileRead +=========== +*/ +idFile *idFileSystemLocal::OpenFileRead( const char *relativePath, bool allowCopyFiles, const char* gamedir ) { + return OpenFileReadFlags( relativePath, FSFLAG_SEARCH_DIRS | FSFLAG_SEARCH_PAKS, NULL, allowCopyFiles, gamedir ); +} + +/* +=========== +idFileSystemLocal::OpenFileWrite +=========== +*/ +idFile *idFileSystemLocal::OpenFileWrite( const char *relativePath, const char *basePath, bool ASCII ) { + (void)ASCII; + const char *path; + idStr OSpath; + idFile_Permanent *f; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + path = cvarSystem->GetCVarString( basePath ); + if ( !path[0] ) { + path = fs_savepath.GetString(); + } + + OSpath = BuildOSPath( path, gameFolder, relativePath ); + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenFileWrite: %s\n", OSpath.c_str() ); + } + + // if the dir we are writing to is in our current list, it will be outdated + // so just flush everything + ClearDirCache(); + + common->DPrintf( "writing to: %s\n", OSpath.c_str() ); + CreateOSPath( OSpath ); + + f = new idFile_Permanent(); + f->o = OpenOSFile( OSpath, "wb" ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = relativePath; + f->fullPath = OSpath; + f->mode = ( 1 << FS_WRITE ); + f->handleSync = false; + f->fileSize = 0; + + return f; +} + +/* +=========== +idFileSystemLocal::OpenExplicitFileRead +=========== +*/ +idFile *idFileSystemLocal::OpenExplicitFileRead( const char *OSPath ) { + idFile_Permanent *f; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenExplicitFileRead: %s\n", OSPath ); + } + + common->DPrintf( "idFileSystem::OpenExplicitFileRead - reading from: %s\n", OSPath ); + + f = new idFile_Permanent(); + f->o = OpenOSFile( OSPath, "rb" ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = OSPath; + f->fullPath = OSPath; + f->mode = ( 1 << FS_READ ); + f->handleSync = false; + f->fileSize = DirectFileLength( f->o ); + + return f; +} + +/* +=========== +idFileSystemLocal::OpenExplicitFileWrite +=========== +*/ +idFile *idFileSystemLocal::OpenExplicitFileWrite( const char *OSPath ) { + idFile_Permanent *f; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenExplicitFileWrite: %s\n", OSPath ); + } + + common->DPrintf( "writing to: %s\n", OSPath ); + CreateOSPath( OSPath ); + + f = new idFile_Permanent(); + f->o = OpenOSFile( OSPath, "wb" ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = OSPath; + f->fullPath = OSPath; + f->mode = ( 1 << FS_WRITE ); + f->handleSync = false; + f->fileSize = 0; + + return f; +} + +/* +=========== +idFileSystemLocal::OpenFileAppend +=========== +*/ +idFile *idFileSystemLocal::OpenFileAppend( const char *relativePath, bool sync, const char *basePath ) { + const char *path; + idStr OSpath; + idFile_Permanent *f; + + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + + path = cvarSystem->GetCVarString( basePath ); + if ( !path[0] ) { + path = fs_savepath.GetString(); + } + + OSpath = BuildOSPath( path, gameFolder, relativePath ); + CreateOSPath( OSpath ); + + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystem::OpenFileAppend: %s\n", OSpath.c_str() ); + } + + f = new idFile_Permanent(); + f->o = OpenOSFile( OSpath, "ab" ); + if ( !f->o ) { + delete f; + return NULL; + } + f->name = relativePath; + f->fullPath = OSpath; + f->mode = ( 1 << FS_WRITE ) + ( 1 << FS_APPEND ); + f->handleSync = sync; + f->fileSize = DirectFileLength( f->o ); + + return f; +} + +/* +================ +idFileSystemLocal::OpenFileByMode +================ +*/ +idFile *idFileSystemLocal::OpenFileByMode( const char *relativePath, fsMode_t mode ) { + if ( mode == FS_READ ) { + return OpenFileRead( relativePath ); + } + if ( mode == FS_WRITE ) { + return OpenFileWrite( relativePath ); + } + if ( mode == FS_APPEND ) { + return OpenFileAppend( relativePath, true ); + } + common->FatalError( "idFileSystemLocal::OpenFileByMode: bad mode" ); + return NULL; +} + +/* +============== +idFileSystemLocal::CloseFile +============== +*/ +void idFileSystemLocal::CloseFile( idFile *f ) { + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + delete f; +} + + +/* +================================================================================= + +back ground loading + +================================================================================= +*/ + +/* +================= +idFileSystemLocal::CurlWriteFunction +================= +*/ +size_t idFileSystemLocal::CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream ) { + backgroundDownload_t *bgl = (backgroundDownload_t *)stream; + if ( !bgl->f ) { + return size * nmemb; + } + #ifdef _WIN32 + return _write( _fileno( static_cast(bgl->f)->GetFilePtr() ), ptr, size * nmemb ); + #else + return fwrite( ptr, size, nmemb, static_cast(bgl->f)->GetFilePtr() ); + #endif +} + +/* +================= +idFileSystemLocal::CurlProgressFunction +================= +*/ +int idFileSystemLocal::CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ) { + backgroundDownload_t *bgl = (backgroundDownload_t *)clientp; + if ( bgl->url.status == DL_ABORTING ) { + return 1; + } + bgl->url.dltotal = dltotal; + bgl->url.dlnow = dlnow; + return 0; +} + +/* +=================== +BackgroundDownload + +Reads part of a file from a background thread. +=================== +*/ +dword BackgroundDownloadThread( void *parms ) { + while( 1 ) { + Sys_EnterCriticalSection(); + backgroundDownload_t *bgl = fileSystemLocal.backgroundDownloads; + if ( !bgl ) { + Sys_LeaveCriticalSection(); + Sys_WaitForEvent(); + continue; + } + // remove this from the list + fileSystemLocal.backgroundDownloads = bgl->next; + Sys_LeaveCriticalSection(); + + bgl->next = NULL; + + if ( bgl->opcode == DLTYPE_FILE ) { + // use the low level read function, because fread may allocate memory + #if defined(WIN32) + _read( _fileno( static_cast(bgl->f)->GetFilePtr() ), bgl->file.buffer, bgl->file.length ); + #else + fread( bgl->file.buffer, bgl->file.length, 1, static_cast(bgl->f)->GetFilePtr() ); + #endif + bgl->completed = true; + } else { +#if ID_ENABLE_CURL + // DLTYPE_URL + // use a local buffer for curl error since the size define is local + char error_buf[ CURL_ERROR_SIZE ]; + bgl->url.dlerror[ 0 ] = '\0'; + CURL *session = curl_easy_init(); + CURLcode ret; + if ( !session ) { + bgl->url.dlstatus = CURLE_FAILED_INIT; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_ERRORBUFFER, error_buf ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_URL, bgl->url.url.c_str() ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_FAILONERROR, 1 ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_WRITEFUNCTION, idFileSystemLocal::CurlWriteFunction ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_WRITEDATA, bgl ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_NOPROGRESS, 0 ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_PROGRESSFUNCTION, idFileSystemLocal::CurlProgressFunction ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + ret = curl_easy_setopt( session, CURLOPT_PROGRESSDATA, bgl ); + if ( ret ) { + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + bgl->url.dlnow = 0; + bgl->url.dltotal = 0; + bgl->url.status = DL_INPROGRESS; + ret = curl_easy_perform( session ); + if ( ret ) { + Sys_Printf( "curl_easy_perform failed: %s\n", error_buf ); + idStr::Copynz( bgl->url.dlerror, error_buf, MAX_STRING_CHARS ); + bgl->url.dlstatus = ret; + bgl->url.status = DL_FAILED; + bgl->completed = true; + continue; + } + bgl->url.status = DL_DONE; + bgl->completed = true; +#else + bgl->url.status = DL_FAILED; + bgl->completed = true; +#endif + } + } + return 0; +} + +/* +================= +idFileSystemLocal::StartBackgroundReadThread +================= +*/ +void idFileSystemLocal::StartBackgroundDownloadThread() { + if ( !backgroundThread.threadHandle ) { + Sys_CreateThread( (xthread_t)BackgroundDownloadThread, NULL, THREAD_NORMAL, backgroundThread, "backgroundDownload", g_threads, &g_thread_count ); + if ( !backgroundThread.threadHandle ) { + common->Warning( "idFileSystemLocal::StartBackgroundDownloadThread: failed" ); + } + } else { + common->Printf( "background thread already running\n" ); + } +} + +/* +================= +idFileSystemLocal::BackgroundDownload +================= +*/ +void idFileSystemLocal::BackgroundDownload( backgroundDownload_t *bgl ) { + if ( bgl->opcode == DLTYPE_FILE ) { + if ( dynamic_cast(bgl->f) ) { + // add the bgl to the background download list + Sys_EnterCriticalSection(); + bgl->next = backgroundDownloads; + backgroundDownloads = bgl; + Sys_TriggerEvent(); + Sys_LeaveCriticalSection(); + } else { + // read zipped file directly + bgl->f->Seek( bgl->file.position, FS_SEEK_SET ); + bgl->f->Read( bgl->file.buffer, bgl->file.length ); + bgl->completed = true; + } + } else { + Sys_EnterCriticalSection(); + bgl->next = backgroundDownloads; + backgroundDownloads = bgl; + Sys_TriggerEvent(); + Sys_LeaveCriticalSection(); + } +} + +/* +================= +idFileSystemLocal::PerformingCopyFiles +================= +*/ +bool idFileSystemLocal::PerformingCopyFiles( void ) const { + return fs_copyfiles.GetInteger() > 0; +} + +/* +================= +idFileSystemLocal::FindPakForFileChecksum +================= +*/ +pack_t *idFileSystemLocal::FindPakForFileChecksum( const char *relativePath, int findChecksum, bool bReference ) { + searchpath_t *search; + pack_t *pak; + fileInPack_t *pakFile; + int hash; + assert( !serverPaks.Num() ); + hash = HashFileName( relativePath ); + for ( search = searchPaths; search; search = search->next ) { + if ( search->pack && search->pack->hashTable[ hash ] ) { + pak = search->pack; + for ( pakFile = pak->hashTable[ hash ]; pakFile; pakFile = pakFile->next ) { + if ( !FilenameCompare( pakFile->name, relativePath ) ) { + idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath ); + if ( findChecksum == GetFileChecksum( file ) ) { + if ( fs_debug.GetBool() ) { + common->Printf( "found '%s' with checksum 0x%x in pak '%s'\n", relativePath, findChecksum, pak->pakFilename.c_str() ); + } + if ( bReference ) { + pak->referenced = true; + // FIXME: use dependencies for pak references + } + CloseFile( file ); + return pak; + } else if ( fs_debug.GetBool() ) { + common->Printf( "'%s' in pak '%s' has != checksum %x\n", relativePath, pak->pakFilename.c_str(), GetFileChecksum( file ) ); + } + CloseFile( file ); + } + } + } + } + if ( fs_debug.GetBool() ) { + common->Printf( "no pak file found for '%s' checksumed %x\n", relativePath, findChecksum ); + } + return NULL; +} + +/* +================= +idFileSystemLocal::GetFileChecksum +================= +*/ +int idFileSystemLocal::GetFileChecksum( idFile *file ) { + int len, ret; + byte *buf; + + file->Seek( 0, FS_SEEK_END ); + len = file->Tell(); + file->Seek( 0, FS_SEEK_SET ); + buf = (byte *)Mem_Alloc( len ); + if ( file->Read( buf, len ) != len ) { + common->FatalError( "Short read in idFileSystemLocal::GetFileChecksum()\n" ); + } + ret = MD4_BlockChecksum( buf, len ); + Mem_Free( buf ); + return ret; +} + +/* +================= +idFileSystemLocal::FindDLL +================= +*/ +void idFileSystemLocal::FindDLL( const char *name, char _dllPath[ MAX_OSPATH ], bool updateChecksum ) { + idFile *dllFile = NULL; + char dllName[MAX_OSPATH]; + idStr dllPath; + int dllHash; + pack_t *inPak; + pack_t *pak; + fileInPack_t *pakFile; + + sys->DLL_GetFileName( name, dllName, MAX_OSPATH ); + dllHash = HashFileName( dllName ); + +#if ID_FAKE_PURE + if ( 1 ) { +#else + if ( !serverPaks.Num() ) { +#endif + // from executable directory first - this is handy for developement + dllPath = Sys_EXEPath( ); + dllPath.StripFilename( ); + dllPath.AppendPath( dllName ); + dllFile = OpenExplicitFileRead( dllPath ); + } + if ( !dllFile ) { + if ( !serverPaks.Num() ) { + // not running in pure mode, try to extract from a pak file first + dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_PAKS | FSFLAG_PURE_NOREF | FSFLAG_BINARY_ONLY, &inPak ); + if ( dllFile ) { + common->Printf( "found DLL in pak file: %s\n", dllFile->GetFullPath() ); + dllPath = RelativePathToOSPath( dllName, "fs_savepath" ); + CopyFile( dllFile, dllPath ); + CloseFile( dllFile ); + dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS ); + if ( !dllFile ) { + common->Error( "DLL extraction to fs_savepath failed\n" ); + } else if ( updateChecksum ) { + gameDLLChecksum = GetFileChecksum( dllFile ); + gamePakChecksum = inPak->checksum; + updateChecksum = false; // don't try again below + } + } else { + // didn't find a source in a pak file, try in the directory + dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS ); + if ( dllFile ) { + if ( updateChecksum ) { + gameDLLChecksum = GetFileChecksum( dllFile ); + // see if we can mark a pak file + pak = FindPakForFileChecksum( dllName, gameDLLChecksum, false ); + pak ? gamePakChecksum = pak->checksum : gamePakChecksum = 0; + updateChecksum = false; + } + } + } + } else { + // we are in pure mode. this path to be reached only for game DLL situations + // with a code pak checksum given by server + assert( gamePakChecksum ); + assert( updateChecksum ); + pak = GetPackForChecksum( gamePakChecksum ); + if ( !pak ) { + // not supposed to happen, bug in pure code? + common->Warning( "FindDLL in pure mode: game pak not found ( 0x%x )\n", gamePakChecksum ); + } else { + // extract and copy + for ( pakFile = pak->hashTable[dllHash]; pakFile; pakFile = pakFile->next ) { + if ( !FilenameCompare( pakFile->name, dllName ) ) { + dllFile = ReadFileFromZip( pak, pakFile, dllName ); + common->Printf( "found DLL in game pak file: %s\n", pak->pakFilename.c_str() ); + dllPath = RelativePathToOSPath( dllName, "fs_savepath" ); + CopyFile( dllFile, dllPath ); + CloseFile( dllFile ); + dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS ); + if ( !dllFile ) { + common->Error( "DLL extraction to fs_savepath failed\n" ); + } else { + gameDLLChecksum = GetFileChecksum( dllFile ); + updateChecksum = false; // don't try again below + } + } + } + } + } + } + if ( updateChecksum ) { + if ( dllFile ) { + gameDLLChecksum = GetFileChecksum( dllFile ); + } else { + gameDLLChecksum = 0; + } + gamePakChecksum = 0; + } + if ( dllFile ) { + dllPath = dllFile->GetFullPath( ); + CloseFile( dllFile ); + dllFile = NULL; + } else { + dllPath = ""; + } + idStr::snPrintf( _dllPath, MAX_OSPATH, dllPath.c_str() ); +} + +/* +================ +idFileSystemLocal::ClearDirCache +================ +*/ +void idFileSystemLocal::ClearDirCache( void ) { + int i; + + dir_cache_index = 0; + dir_cache_count = 0; + for( i = 0; i < MAX_CACHED_DIRS; i++ ) { + dir_cache[ i ].Clear(); + } +} + +/* +=============== +idFileSystemLocal::HasD3XP +=============== +*/ +bool idFileSystemLocal::HasD3XP( void ) { + int i; + idStrList dirs, pk4s; + idStr gamepath; + + if ( d3xp == -1 ) { + return false; + } else if ( d3xp == 1 ) { + return true; + } + +#if 0 + // check for a d3xp directory with a pk4 file + // copied over from ListMods - only looks in basepath + ListOSFiles( fs_basepath.GetString(), "/", dirs ); + for ( i = 0; i < dirs.Num(); i++ ) { + if ( dirs[i].Icmp( "d3xp" ) == 0 ) { + gamepath = BuildOSPath( fs_basepath.GetString(), dirs[ i ], "" ); + ListOSFiles( gamepath, ".pk4", pk4s ); + if ( pk4s.Num() ) { + d3xp = 1; + return true; + } + } + } +#elif ID_ALLOW_D3XP + // check for d3xp's d3xp/pak000.pk4 in any search path + // checking wether the pak is loaded by checksum wouldn't be enough: + // we may have a different fs_game right now but still need to reply that it's installed + const char *search[4]; + idFile *pakfile; + search[0] = fs_savepath.GetString(); + search[1] = fs_devpath.GetString(); + search[2] = fs_basepath.GetString(); + search[3] = fs_cdpath.GetString(); + for ( i = 0; i < 4; i++ ) { + pakfile = OpenExplicitFileRead( BuildOSPath( search[ i ], "d3xp", "pak000.pk4" ) ); + if ( pakfile ) { + CloseFile( pakfile ); + d3xp = 1; + return true; + } + } +#endif + +#if ID_ALLOW_D3XP + // if we didn't find a pk4 file then the user might have unpacked so look for default.cfg file + // that's the old way mostly used during developement. don't think it hurts to leave it there + ListOSFiles( fs_basepath.GetString(), "/", dirs ); + for ( i = 0; i < dirs.Num(); i++ ) { + if ( dirs[i].Icmp( "d3xp" ) == 0 ) { + + gamepath = BuildOSPath( fs_savepath.GetString(), dirs[ i ], "default.cfg" ); + idFile* cfg = OpenExplicitFileRead(gamepath); + if(cfg) { + CloseFile(cfg); + d3xp = 1; + return true; + } + } + } +#endif + d3xp = -1; + return false; +} + +/* +=============== +idFileSystemLocal::RunningD3XP +=============== +*/ +bool idFileSystemLocal::RunningD3XP( void ) { + // TODO: mark the checksum of the gold XP and check for it being referenced ( for double mod support ) + // a simple fs_game check should be enough for now.. + if ( !idStr::Icmp( fs_game.GetString(), "d3xp" ) || + !idStr::Icmp( fs_game_base.GetString(), "d3xp" ) ) { + return true; + } + return false; +} + +/* +=============== +idFileSystemLocal::MakeTemporaryFile +=============== +*/ +idFile * idFileSystemLocal::MakeTemporaryFile( void ) { + FILE *f = tmpfile(); + if ( !f ) { + common->Warning( "idFileSystem::MakeTemporaryFile failed: %s", strerror( errno ) ); + return NULL; + } + idFile_Permanent *file = new idFile_Permanent(); + file->o = f; + file->name = ""; + file->fullPath = ""; + file->mode = ( 1 << FS_READ ) + ( 1 << FS_WRITE ); + file->fileSize = 0; + return file; +} + +/* +=============== +idFileSystemLocal::FindFile +=============== +*/ +findFile_t idFileSystemLocal::FindFile( const char *path ) { + const bool scheduleAddons = true; + pack_t *pak; + idFile *f = OpenFileReadFlags( path, FSFLAG_SEARCH_DIRS | FSFLAG_SEARCH_PAKS | FSFLAG_SEARCH_ADDONS, &pak ); + if ( !f ) { + return FIND_NO; + } + if ( !pak ) { + // found in FS, not even in paks + return FIND_YES; + } + // marking addons for inclusion on reload - may need to do that even when already in the search path + if ( scheduleAddons && pak->addon && addonChecksums.FindIndex( pak->checksum ) < 0 ) { + addonChecksums.Append( pak->checksum ); + } + // an addon that's not on search list yet? that will require a restart + if ( pak->addon && !pak->addon_search ) { + delete f; + return FIND_ADDON; + } + delete f; + return FIND_YES; +} + +/* +=============== +idFileSystemLocal::GetNumMaps +account for actual decls and for addon maps +=============== +*/ +int idFileSystemLocal::GetNumMaps() { + int i; + searchpath_t *search = NULL; + int ret = declManager->GetNumDecls( DECL_MAPDEF ); + + // add to this all addon decls - coming from all addon packs ( searched or not ) + for ( i = 0; i < 2; i++ ) { + if ( i == 0 ) { + search = searchPaths; + } else if ( i == 1 ) { + search = addonPaks; + } + for ( ; search ; search = search->next ) { + if ( !search->pack || !search->pack->addon || !search->pack->addon_info ) { + continue; + } + ret += search->pack->addon_info->mapDecls.Num(); + } + } + return ret; +} + +/* +=============== +idFileSystemLocal::GetMapDecl +retrieve the decl dictionary, add a 'path' value +=============== +*/ +const idDict * idFileSystemLocal::GetMapDecl( int idecl ) { + int i; + const idDecl *mapDecl; + const idDeclEntityDef *mapDef; + int numdecls = declManager->GetNumDecls( DECL_MAPDEF ); + searchpath_t *search = NULL; + + if ( idecl < numdecls ) { + mapDecl = declManager->DeclByIndex( DECL_MAPDEF, idecl ); + mapDef = static_cast( mapDecl ); + if ( !mapDef ) { + common->Error( "idFileSystemLocal::GetMapDecl %d: not found\n", idecl ); + } + mapDict = mapDef->dict; + mapDict.Set( "path", mapDef->GetName() ); + return &mapDict; + } + idecl -= numdecls; + for ( i = 0; i < 2; i++ ) { + if ( i == 0 ) { + search = searchPaths; + } else if ( i == 1 ) { + search = addonPaks; + } + for ( ; search ; search = search->next ) { + if ( !search->pack || !search->pack->addon || !search->pack->addon_info ) { + continue; + } + // each addon may have a bunch of map decls + if ( idecl < search->pack->addon_info->mapDecls.Num() ) { + mapDict = *search->pack->addon_info->mapDecls[ idecl ]; + return &mapDict; + } + idecl -= search->pack->addon_info->mapDecls.Num(); + assert( idecl >= 0 ); + } + } + return NULL; +} + +/* +=============== +idFileSystemLocal::FindMapScreenshot +=============== +*/ +void idFileSystemLocal::FindMapScreenshot( const char *path, char *buf, int len ) { + idFile *file; + idStr mapname = path; + + mapname.StripPath(); + mapname.StripFileExtension(); + + idStr::snPrintf( buf, len, "guis/assets/splash/%s.tga", mapname.c_str() ); + if ( ReadFile( buf, NULL, NULL ) == -1 ) { + // try to extract from an addon + file = OpenFileReadFlags( buf, FSFLAG_SEARCH_ADDONS ); + if ( file ) { + // save it out to an addon splash directory + int dlen = file->Length(); + char *data = new char[ dlen ]; + file->Read( data, dlen ); + CloseFile( file ); + idStr::snPrintf( buf, len, "guis/assets/splash/addon/%s.tga", mapname.c_str() ); + WriteFile( buf, data, dlen ); + delete[] data; + } else { + idStr::Copynz( buf, "guis/assets/splash/pdtempa", len ); + } + } +} + +/* +=============== +idFileSystemLocal::OSpathToImportPath +=============== +*/ +bool idFileSystemLocal::OSpathToImportPath( const char *osPath, idStr &iPath, bool stripTemp ) { + const char *importPath = fs_importpath.GetString(); + const int importPathLength = idStr::Length( importPath ); + + if ( idStr::Icmpn( osPath, importPath, importPathLength ) ) { + iPath = osPath; + return false; + } + + const char *relative = osPath + importPathLength; + if ( relative[0] == '/' || relative[0] == '\\' ) { + relative++; + } + if ( stripTemp ) { + if ( !idStr::Icmpn( relative, "work", 4 ) ) { + relative += 4; + } + if ( !idStr::Icmpn( relative, "final", 5 ) ) { + relative += 5; + } + if ( relative[0] == '/' || relative[0] == '\\' ) { + relative++; + } + } + + iPath = relative; + return true; +} + +/* +=============== +idFileSystemLocal::OpenImportFileRead +=============== +*/ +idFile *idFileSystemLocal::OpenImportFileRead( const char *filename ) { + if ( !searchPaths ) { + common->FatalError( "Filesystem call made without initialization\n" ); + } + if ( fs_debug.GetInteger() ) { + common->Printf( "idFileSystemLocal::OpenImportFileRead: %s\n", filename ); + } + + idStr finalName; + finalName.AppendPath( fs_importpath.GetString() ); + finalName.AppendPath( filename ); + common->DPrintf( "reading from: %s\n", finalName.c_str() ); + + idFile_Permanent *file = new idFile_Permanent; + file->o = OpenOSFile( finalName.c_str(), "rb" ); + if ( !file->o ) { + delete file; + return NULL; + } + file->name = finalName; + file->fullPath = finalName; + file->mode = ( 1 << FS_READ ); + file->handleSync = false; + file->fileSize = DirectFileLength( file->o ); + return file; +} + +/* +=============== +idFileSystemLocal::WriteDemoHeader +=============== +*/ +void idFileSystemLocal::WriteDemoHeader( idFile *file ) { + file->WriteString( fs_game_base.GetString() ); + file->WriteString( fs_game.GetString() ); + file->WriteInt( serverPaks.Num() ); + + if ( !serverPaks.Num() ) { + return; + } + + for ( int i = 0; i < serverPaks.Num(); i++ ) { + file->WriteString( serverPaks[i]->pakFilename.c_str() ); + file->WriteInt( serverPaks[i]->checksum ); + } + + UpdateGamePakChecksums(); + // The original format records the producer OS before the six-entry table. + file->WriteInt( BUILD_OS_ID ); + for ( int os = 0; os < MAX_GAME_OS; os++ ) { + pack_t *gamePak = NULL; + if ( gamePakForOS[os] ) { + for ( searchpath_t *search = searchPaths; search; search = search->next ) { + if ( search->pack && search->pack->checksum == gamePakForOS[os] ) { + gamePak = search->pack; + break; + } + } + } + file->WriteString( gamePak ? gamePak->pakFilename.c_str() : "" ); + file->WriteInt( gamePak ? gamePak->checksum : 0 ); + } +} + +/* +=============== +idFileSystemLocal::ReadDemoHeader + +Return values are part of the demo playback protocol: -1 requests a +filesystem restart, 0 rejects playback, and 1 continues. +=============== +*/ +int idFileSystemLocal::ReadDemoHeader( idFile *file ) { + idStr recordedGameBase; + idStr recordedGame; + idStr pakName; + idStr gamePakNames[MAX_GAME_OS]; + int pakChecksums[MAX_PURE_PAKS]; + int missingChecksums[MAX_PURE_PAKS]; + int gamePakChecksums[MAX_GAME_OS]; + int recordedOS = 0; + int numPaks = 0; + int storedPaks = 0; + + memset( pakChecksums, 0, sizeof( pakChecksums ) ); + memset( missingChecksums, 0, sizeof( missingChecksums ) ); + memset( gamePakChecksums, 0, sizeof( gamePakChecksums ) ); + + file->ReadString( recordedGameBase ); + file->ReadString( recordedGame ); + common->DPrintf( "fs_game_base: '%s'\n", recordedGameBase.c_str() ); + common->DPrintf( "fs_game: '%s'\n", recordedGame.c_str() ); + file->ReadInt( numPaks ); + + if ( numPaks > 0 ) { + for ( int i = 0; i < numPaks; i++ ) { + int checksum; + file->ReadString( pakName ); + file->ReadInt( checksum ); + common->DPrintf( "pak '%s' 0x%x\n", pakName.c_str(), checksum ); + if ( storedPaks < MAX_PURE_PAKS - 1 ) { + pakChecksums[storedPaks++] = checksum; + } + } + pakChecksums[storedPaks] = 0; + + file->ReadInt( recordedOS ); + common->DPrintf( "record on an OS id: %d, local playback on OS id %d\n", recordedOS, BUILD_OS_ID ); + for ( int os = 0; os < MAX_GAME_OS; os++ ) { + file->ReadString( gamePakNames[os] ); + file->ReadInt( gamePakChecksums[os] ); + if ( gamePakChecksums[os] ) { + common->DPrintf( "game pak OS %d: '%s' 0x%x\n", os, gamePakNames[os].c_str(), gamePakChecksums[os] ); + } + } + } else { + common->DPrintf( "demo wasn't recorded in pure mode\n" ); + } + + const bool enforce = cvarSystem->GetCVarBool( "demo_enforceFS" ); + if ( recordedGameBase.Icmp( fs_game_base.GetString() ) || recordedGame.Icmp( fs_game.GetString() ) ) { + common->Warning( "fs_game_base '%s' or fs_game '%s' are not matching the current configuration ( '%s' '%s' )", + recordedGameBase.c_str(), recordedGame.c_str(), fs_game_base.GetString(), fs_game.GetString() ); + if ( enforce ) { + common->Printf( "demo_enforceFS is enabled, need to reload engine before demo playback\n" ); + fs_game_base.SetString( recordedGameBase.c_str() ); + fs_game.SetString( recordedGame.c_str() ); + if ( numPaks > 0 ) { + int gameChecksum = gamePakChecksums[BUILD_OS_ID]; + if ( !gameChecksum ) { + common->Warning( "no game pak for this OS was present at recording" ); + gameChecksum = gameDLLChecksum; + } + SetRestartChecksums( pakChecksums, gameChecksum ); + } + return -1; + } + common->Printf( "demo_enforceFS is off, demo might crash or generally not playback correctly\n" ); + } + + if ( numPaks <= 0 ) { + return 1; + } + + UpdateGamePakChecksums(); + int gameChecksum = gamePakChecksums[BUILD_OS_ID]; + if ( !gameChecksum ) { + common->Warning( "no game pak for this OS was present at recording" ); + gameChecksum = gameDLLChecksum; + } + int missingGamePakChecksum = 0; + const fsPureReply_t reply = SetPureServerChecksums( pakChecksums, gameChecksum, missingChecksums, &missingGamePakChecksum ); + + switch ( reply ) { + case PURE_OK: + if ( !enforce ) { + ClearPureChecksums(); + } + return 1; + + case PURE_RESTART: + common->Warning( "a filesystem restart is needed to playback this demo" ); + if ( !enforce ) { + common->Printf( "demo_enforceFS is off, demo might crash or generally not playback correctly\n" ); + return 1; + } + common->Printf( "demo_enforceFS is enabled, need to reload engine before demo playback\n" ); + SetRestartChecksums( pakChecksums, gameChecksum ); + return -1; + + case PURE_MISSING: { + idStr checksums; + int missingCount = 0; + for ( ; missingCount < MAX_PURE_PAKS && missingChecksums[missingCount]; missingCount++ ) { + checksums += va( "0x%x ", missingChecksums[missingCount] ); + } + common->Warning( "missing %d pak files from initial demo recording: %s", missingCount, checksums.c_str() ); + if ( missingGamePakChecksum ) { + common->Warning( "can't match the game code used to record this demo" ); + } + if ( !enforce ) { + common->Printf( "demo_enforceFS is off, demo might crash or generally not playback correctly\n" ); + return 1; + } + common->Printf( "demo_enforceFS is enabled, aborting playback\n" ); + return 0; + } + + case PURE_NODLL: + common->Warning( "can't match the game code used to record this demo" ); + if ( !enforce ) { + common->Printf( "demo_enforceFS is off, demo might crash or generally not playback correctly\n" ); + return 1; + } + common->Printf( "demo_enforceFS is enabled, aborting playback\n" ); + return 0; + } + + return 1; +} diff --git a/src/framework/keyinput.cpp b/src/framework/keyinput.cpp new file mode 100644 index 0000000..ca1ffbd --- /dev/null +++ b/src/framework/keyinput.cpp @@ -0,0 +1,785 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +typedef struct { + char *name; + int keynum; + char *strId; // localized string id +} keyname_t; + +// keys that can be set without a special name +static const char unnamedkeys[] = "*,-=./[\\]1234567890abcdefghijklmnopqrstuvwxyz"; + +#if MACOS_X +const char* OSX_GetLocalizedString( const char* ); +#endif + +// names not in this list can either be lowercase ascii, or '0xnn' hex sequences +keyname_t keynames[] = +{ + {"TAB", K_TAB, "#str_07018"}, + {"ENTER", K_ENTER, "#str_07019"}, + {"ESCAPE", K_ESCAPE, "#str_07020"}, + {"SPACE", K_SPACE, "#str_07021"}, + {"BACKSPACE", K_BACKSPACE, "#str_07022"}, + {"UPARROW", K_UPARROW, "#str_07023"}, + {"DOWNARROW", K_DOWNARROW, "#str_07024"}, + {"LEFTARROW", K_LEFTARROW, "#str_07025"}, + {"RIGHTARROW", K_RIGHTARROW, "#str_07026"}, + + {"ALT", K_ALT, "#str_07027"}, + {"RIGHTALT", K_RIGHT_ALT, "#str_07027"}, + {"CTRL", K_CTRL, "#str_07028"}, + {"SHIFT", K_SHIFT, "#str_07029"}, + + {"LWIN", K_LWIN, "#str_07030"}, + {"RWIN", K_RWIN, "#str_07031"}, + {"MENU", K_MENU, "#str_07032"}, + + {"COMMAND", K_COMMAND, "#str_07033"}, + + {"CAPSLOCK", K_CAPSLOCK, "#str_07034"}, + {"SCROLL", K_SCROLL, "#str_07035"}, + {"PRINTSCREEN", K_PRINT_SCR, "#str_07179"}, + + {"F1", K_F1, "#str_07036"}, + {"F2", K_F2, "#str_07037"}, + {"F3", K_F3, "#str_07038"}, + {"F4", K_F4, "#str_07039"}, + {"F5", K_F5, "#str_07040"}, + {"F6", K_F6, "#str_07041"}, + {"F7", K_F7, "#str_07042"}, + {"F8", K_F8, "#str_07043"}, + {"F9", K_F9, "#str_07044"}, + {"F10", K_F10, "#str_07045"}, + {"F11", K_F11, "#str_07046"}, + {"F12", K_F12, "#str_07047"}, + + {"INS", K_INS, "#str_07048"}, + {"DEL", K_DEL, "#str_07049"}, + {"PGDN", K_PGDN, "#str_07050"}, + {"PGUP", K_PGUP, "#str_07051"}, + {"HOME", K_HOME, "#str_07052"}, + {"END", K_END, "#str_07053"}, + + {"MOUSE1", K_MOUSE1, "#str_07054"}, + {"MOUSE2", K_MOUSE2, "#str_07055"}, + {"MOUSE3", K_MOUSE3, "#str_07056"}, + {"MOUSE4", K_MOUSE4, "#str_07057"}, + {"MOUSE5", K_MOUSE5, "#str_07058"}, + {"MOUSE6", K_MOUSE6, "#str_07059"}, + {"MOUSE7", K_MOUSE7, "#str_07060"}, + {"MOUSE8", K_MOUSE8, "#str_07061"}, + + {"MWHEELUP", K_MWHEELUP, "#str_07131"}, + {"MWHEELDOWN", K_MWHEELDOWN, "#str_07132"}, + + {"JOY1", K_JOY1, "#str_07062"}, + {"JOY2", K_JOY2, "#str_07063"}, + {"JOY3", K_JOY3, "#str_07064"}, + {"JOY4", K_JOY4, "#str_07065"}, + {"JOY5", K_JOY5, "#str_07066"}, + {"JOY6", K_JOY6, "#str_07067"}, + {"JOY7", K_JOY7, "#str_07068"}, + {"JOY8", K_JOY8, "#str_07069"}, + {"JOY9", K_JOY9, "#str_07070"}, + {"JOY10", K_JOY10, "#str_07071"}, + {"JOY11", K_JOY11, "#str_07072"}, + {"JOY12", K_JOY12, "#str_07073"}, + {"JOY13", K_JOY13, "#str_07074"}, + {"JOY14", K_JOY14, "#str_07075"}, + {"JOY15", K_JOY15, "#str_07076"}, + {"JOY16", K_JOY16, "#str_07077"}, + {"JOY17", K_JOY17, "#str_07078"}, + {"JOY18", K_JOY18, "#str_07079"}, + {"JOY19", K_JOY19, "#str_07080"}, + {"JOY20", K_JOY20, "#str_07081"}, + {"JOY21", K_JOY21, "#str_07082"}, + {"JOY22", K_JOY22, "#str_07083"}, + {"JOY23", K_JOY23, "#str_07084"}, + {"JOY24", K_JOY24, "#str_07085"}, + {"JOY25", K_JOY25, "#str_07086"}, + {"JOY26", K_JOY26, "#str_07087"}, + {"JOY27", K_JOY27, "#str_07088"}, + {"JOY28", K_JOY28, "#str_07089"}, + {"JOY29", K_JOY29, "#str_07090"}, + {"JOY30", K_JOY30, "#str_07091"}, + {"JOY31", K_JOY31, "#str_07092"}, + {"JOY32", K_JOY32, "#str_07093"}, + + {"AUX1", K_AUX1, "#str_07094"}, + {"AUX2", K_AUX2, "#str_07095"}, + {"AUX3", K_AUX3, "#str_07096"}, + {"AUX4", K_AUX4, "#str_07097"}, + {"AUX5", K_AUX5, "#str_07098"}, + {"AUX6", K_AUX6, "#str_07099"}, + {"AUX7", K_AUX7, "#str_07100"}, + {"AUX8", K_AUX8, "#str_07101"}, + {"AUX9", K_AUX9, "#str_07102"}, + {"AUX10", K_AUX10, "#str_07103"}, + {"AUX11", K_AUX11, "#str_07104"}, + {"AUX12", K_AUX12, "#str_07105"}, + {"AUX13", K_AUX13, "#str_07106"}, + {"AUX14", K_AUX14, "#str_07107"}, + {"AUX15", K_AUX15, "#str_07108"}, + {"AUX16", K_AUX16, "#str_07109"}, + + {"KP_HOME", K_KP_HOME, "#str_07110"}, + {"KP_UPARROW", K_KP_UPARROW, "#str_07111"}, + {"KP_PGUP", K_KP_PGUP, "#str_07112"}, + {"KP_LEFTARROW", K_KP_LEFTARROW, "#str_07113"}, + {"KP_5", K_KP_5, "#str_07114"}, + {"KP_RIGHTARROW", K_KP_RIGHTARROW, "#str_07115"}, + {"KP_END", K_KP_END, "#str_07116"}, + {"KP_DOWNARROW", K_KP_DOWNARROW, "#str_07117"}, + {"KP_PGDN", K_KP_PGDN, "#str_07118"}, + {"KP_ENTER", K_KP_ENTER, "#str_07119"}, + {"KP_INS", K_KP_INS, "#str_07120"}, + {"KP_DEL", K_KP_DEL, "#str_07121"}, + {"KP_SLASH", K_KP_SLASH, "#str_07122"}, + {"KP_MINUS", K_KP_MINUS, "#str_07123"}, + {"KP_PLUS", K_KP_PLUS, "#str_07124"}, + {"KP_NUMLOCK", K_KP_NUMLOCK, "#str_07125"}, + {"KP_STAR", K_KP_STAR, "#str_07126"}, + {"KP_EQUALS", K_KP_EQUALS, "#str_07127"}, + + {"PAUSE", K_PAUSE, "#str_07128"}, + + {"SEMICOLON", ';', "#str_07129"}, // because a raw semicolon separates commands + {"APOSTROPHE", '\'', "#str_07130"}, // because a raw apostrophe messes with parsing + + {NULL, 0, NULL} +}; + + + +static const int MAX_KEYS = 256; + +class idKey { +public: + idKey( void ) { down = false; repeats = 0; usercmdAction = 0; } + bool down; + int repeats; // if > 1, it is autorepeating + idStr binding; + int usercmdAction; // for testing by the asyncronous usercmd generation +}; + +bool key_overstrikeMode = false; +idKey * keys = NULL; + +#define ID_DOOM_LEGACY + +#ifdef ID_DOOM_LEGACY + +char * cheatCodes[] = { + "iddqd", // Invincibility + "idkfa", // All weapons, keys, ammo, and 200% armor + "idfa", // Reset ammunition + "idspispopd", // Walk through walls + "idclip", // Walk through walls + "idchoppers", // Chainsaw +/* + "idbeholds", // Berserker strength + "idbeholdv", // Temporary invincibility + "idbeholdi", // Temporary invisibility + "idbeholda", // Full automap + "idbeholdr", // Anti-radiation suit + "idbeholdl", // Light amplification visor + "idclev", // Level select + "iddt", // Toggle full map; full map and objects; normal map + "idmypos", // Display coordinates and heading + "idmus", // Change music to indicated level + "fhhall", // Kill all enemies in level + "fhshh", // Invisible to enemies until attack +*/ + NULL +}; +char lastKeys[32]; +int lastKeyIndex; + +#endif + +/* +=================== +idKeyInput::ArgCompletion_KeyName +=================== +*/ +void idKeyInput::ArgCompletion_KeyName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + keyname_t *kn; + int i; + + for( i = 0; i < sizeof( unnamedkeys ) - 1; i++ ) { + callback( va( "%s %c", args.Argv( 0 ), unnamedkeys[ i ] ) ); + } + + for ( kn = keynames; kn->name; kn++ ) { + callback( va( "%s %s", args.Argv( 0 ), kn->name ) ); + } +} + +/* +=================== +idKeyInput::GetOverstrikeMode +=================== +*/ +bool idKeyInput::GetOverstrikeMode( void ) { + return key_overstrikeMode; +} + +/* +=================== +idKeyInput::SetOverstrikeMode +=================== +*/ +void idKeyInput::SetOverstrikeMode( bool state ) { + key_overstrikeMode = state; +} + +/* +=================== +idKeyInput::IsDown +=================== +*/ +bool idKeyInput::IsDown( int keynum ) { + if ( keynum == -1 ) { + return false; + } + + return keys[keynum].down; +} + +/* +=================== +idKeyInput::StringToKeyNum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers +to be configured even if they don't have defined names. +=================== +*/ +int idKeyInput::StringToKeyNum( const char *str ) { + keyname_t *kn; + + if ( !str || !str[0] ) { + return -1; + } + if ( !str[1] ) { + return (unsigned char)(str[0]); + } + + // check for hex code + if ( str[0] == '0' && str[1] == 'x' && strlen( str ) == 4 ) { + int n1, n2; + + n1 = str[2]; + if ( n1 >= '0' && n1 <= '9' ) { + n1 -= '0'; + } else if ( n1 >= 'a' && n1 <= 'f' ) { + n1 = n1 - 'a' + 10; + } else { + n1 = 0; + } + + n2 = str[3]; + if ( n2 >= '0' && n2 <= '9' ) { + n2 -= '0'; + } else if ( n2 >= 'a' && n2 <= 'f' ) { + n2 = n2 - 'a' + 10; + } else { + n2 = 0; + } + + return n1 * 16 + n2; + } + + // scan for a text match + for ( kn = keynames; kn->name; kn++ ) { + if ( !idStr::Icmp( str, kn->name ) ) { + return kn->keynum; + } + } + + return -1; +} + +/* +=================== +idKeyInput::KeyNumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +const char *idKeyInput::KeyNumToString( int keynum, bool localized ) { + keyname_t *kn; + static char tinystr[5]; + int i, j; + + if ( keynum == -1 ) { + return ""; + } + + if ( keynum < 0 || keynum > 255 ) { + return ""; + } + + // check for printable ascii (don't use characters that need command escaping) + if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' && keynum != '\'' && keynum != '\\' ) { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // check for a key string + for ( kn = keynames; kn->name; kn++ ) { + if ( keynum == kn->keynum ) { + if ( !localized || kn->strId[0] != '#' ) { + return kn->name; + } else { +#if MACOS_X + + switch ( kn->keynum ) { + case K_ENTER: + case K_BACKSPACE: + case K_ALT: + case K_INS: + case K_PRINT_SCR: + return OSX_GetLocalizedString( kn->name ); + break; + default : + return common->GetLocalizedString( kn->strId ); break; + } +#else + return common->GetLocalizedString( kn->strId ); +#endif + } + } + } + + // localized key names preserve all high-ASCII key values + if ( localized && keynum >= 128 ) { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // make a hex string + i = keynum >> 4; + j = keynum & 15; + + tinystr[0] = '0'; + tinystr[1] = 'x'; + tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; + tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; + tinystr[4] = 0; + + return tinystr; +} + +/* +=================== +idKeyInput::SetBinding +=================== +*/ +void idKeyInput::SetBinding( int keynum, const char *binding ) { + if ( keynum == -1 ) { + return; + } + + // Clear out all button states so we aren't stuck forever thinking this key is held down + usercmdGen->Clear(); + + // allocate memory for new binding + keys[keynum].binding = binding; + + // find the action for the async command generation + keys[keynum].usercmdAction = usercmdGen->CommandStringUsercmdData( binding ); + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvarSystem->SetModifiedFlags( CVAR_ARCHIVE ); +} + + +/* +=================== +idKeyInput::GetBinding +=================== +*/ +const char *idKeyInput::GetBinding( int keynum ) { + if ( keynum == -1 ) { + return ""; + } + + return keys[ keynum ].binding; +} + +/* +=================== +idKeyInput::GetUsercmdAction +=================== +*/ +int idKeyInput::GetUsercmdAction( int keynum ) { + return keys[ keynum ].usercmdAction; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f( const idCmdArgs &args ) { + int b; + + if ( args.Argc() != 2 ) { + common->Printf( "unbind : remove commands from a key\n" ); + return; + } + + b = idKeyInput::StringToKeyNum( args.Argv(1) ); + if ( b == -1 ) { + // If it wasn't a key, it could be a command + if ( !idKeyInput::UnbindBinding( args.Argv(1) ) ) { + common->Printf( "\"%s\" isn't a valid key\n", args.Argv(1) ); + } + } else { + idKeyInput::SetBinding( b, "" ); + } +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f( const idCmdArgs &args ) { + int i; + + for ( i = 0; i < MAX_KEYS; i++ ) { + idKeyInput::SetBinding( i, "" ); + } +} + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f( const idCmdArgs &args ) { + int i, c, b; + char cmd[MAX_STRING_CHARS]; + + c = args.Argc(); + + if ( c < 2 ) { + common->Printf( "bind [command] : attach a command to a key\n" ); + return; + } + b = idKeyInput::StringToKeyNum( args.Argv(1) ); + if ( b == -1 ) { + common->Printf( "\"%s\" isn't a valid key\n", args.Argv(1) ); + return; + } + + if ( c == 2 ) { + if ( keys[b].binding.Length() ) { + common->Printf( "\"%s\" = \"%s\"\n", args.Argv(1), keys[b].binding.c_str() ); + } + else { + common->Printf( "\"%s\" is not bound\n", args.Argv(1) ); + } + return; + } + + // copy the rest of the command line + cmd[0] = 0; // start out with a null string + for ( i = 2; i < c; i++ ) { + strcat( cmd, args.Argv( i ) ); + if ( i != (c-1) ) { + strcat( cmd, " " ); + } + } + + idKeyInput::SetBinding( b, cmd ); +} + +/* +============ +Key_BindUnBindTwo_f + +binds keynum to bindcommand and unbinds if there are already two binds on the key +============ +*/ +void Key_BindUnBindTwo_f( const idCmdArgs &args ) { + int c = args.Argc(); + if ( c < 3 ) { + common->Printf( "bindunbindtwo [command]\n" ); + return; + } + int key = atoi( args.Argv( 1 ) ); + idStr bind = args.Argv( 2 ); + if ( idKeyInput::NumBinds( bind ) >= 2 && !idKeyInput::KeyIsBoundTo( key, bind ) ) { + idKeyInput::UnbindBinding( bind ); + } + idKeyInput::SetBinding( key, bind ); +} + + + +/* +============ +idKeyInput::WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void idKeyInput::WriteBindings( idFile *f ) { + int i; + + f->Printf( "unbindall\n" ); + + for ( i = 0; i < MAX_KEYS; i++ ) { + if ( keys[i].binding.Length() ) { + const char *name = KeyNumToString( i, false ); + + // handle the escape character nicely + if ( !strcmp( name, "\\" ) ) { + f->Printf( "bind \"\\\" \"%s\"\n", keys[i].binding.c_str() ); + } else { + f->Printf( "bind \"%s\" \"%s\"\n", KeyNumToString( i, false ), keys[i].binding.c_str() ); + } + } + } +} + +/* +============ +Key_ListBinds_f +============ +*/ +void Key_ListBinds_f( const idCmdArgs &args ) { + int i; + + for ( i = 0; i < MAX_KEYS; i++ ) { + if ( keys[i].binding.Length() ) { + common->Printf( "%s \"%s\"\n", idKeyInput::KeyNumToString( i, false ), keys[i].binding.c_str() ); + } + } +} + +/* +============ +idKeyInput::KeysFromBinding +returns the localized name of the key for the binding +============ +*/ +const char *idKeyInput::KeysFromBinding( const char *bind ) { + int i; + static char keyName[8][128]; + static int keyNum; + char *result; + + keyName[keyNum][0] = '\0'; + if ( bind && *bind ) { + for ( i = 0; i < MAX_KEYS; i++ ) { + if ( keys[i].binding.Icmp( bind ) == 0 ) { + if ( keyName[keyNum][0] != '\0' ) { + idStr::Append( keyName[keyNum], sizeof( keyName[keyNum] ), common->GetLocalizedString( "#str_107183" ) ); + } + idStr::Append( keyName[keyNum], sizeof( keyName[keyNum] ), KeyNumToString( i, true ) ); + } + } + } + if ( keyName[keyNum][0] == '\0' ) { + idStr::Copynz( keyName[keyNum], common->GetLocalizedString( "#str_107133" ), sizeof( keyName[keyNum] ) ); + } + idStr::ToLower( keyName[keyNum] ); + result = keyName[keyNum]; + keyNum = ( keyNum + 1 ) & 7; + return result; +} + +/* +============ +idKeyInput::BindingFromKey +returns the binding for the localized name of the key +============ +*/ +const char *idKeyInput::BindingFromKey( const char *key ) { + const int keyNum = idKeyInput::StringToKeyNum( key ); + if ( keyNum<0 || keyNum >= MAX_KEYS ) { + return NULL; + } + return keys[keyNum].binding.c_str(); +} + +/* +============ +idKeyInput::UnbindBinding +============ +*/ +bool idKeyInput::UnbindBinding( const char *binding ) { + bool unbound = false; + int i; + + if ( binding && *binding ) { + for ( i = 0; i < MAX_KEYS; i++ ) { + if ( keys[i].binding.Icmp( binding ) == 0 ) { + SetBinding( i, "" ); + unbound = true; + } + } + } + return unbound; +} + +/* +============ +idKeyInput::NumBinds +============ +*/ +int idKeyInput::NumBinds( const char *binding ) { + int i, count = 0; + + if ( binding && *binding ) { + for ( i = 0; i < MAX_KEYS; i++ ) { + if ( keys[i].binding.Icmp( binding ) == 0 ) { + count++; + } + } + } + return count; +} + +/* +============ +idKeyInput::KeyIsBountTo +============ +*/ +bool idKeyInput::KeyIsBoundTo( int keynum, const char *binding ) { + if ( keynum >= 0 && keynum < MAX_KEYS ) { + return ( keys[keynum].binding.Icmp( binding ) == 0 ); + } + return false; +} + +/* +=================== +idKeyInput::PreliminaryKeyEvent + +Tracks global key up/down state +Called by the system for both key up and key down events +=================== +*/ +void idKeyInput::PreliminaryKeyEvent( int keynum, bool down ) { + keys[keynum].down = down; + +#ifdef ID_DOOM_LEGACY + if ( down ) { + lastKeys[ 0 + ( lastKeyIndex & 15 )] = keynum; + lastKeys[16 + ( lastKeyIndex & 15 )] = keynum; + lastKeyIndex = ( lastKeyIndex + 1 ) & 15; + for ( int i = 0; cheatCodes[i] != NULL; i++ ) { + int l = strlen( cheatCodes[i] ); + assert( l <= 16 ); + if ( idStr::Icmpn( lastKeys + 16 + ( lastKeyIndex & 15 ) - l, cheatCodes[i], l ) == 0 ) { + common->Printf( "your memory serves you well!\n" ); + break; + } + } + } +#endif +} + +/* +================= +idKeyInput::ExecKeyBinding +================= +*/ +bool idKeyInput::ExecKeyBinding( int keynum ) { + // commands that are used by the async thread + // don't add text + if ( keys[keynum].usercmdAction ) { + return false; + } + + // send the bound action + if ( keys[keynum].binding.Length() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, keys[keynum].binding.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "\n" ); + } + return true; +} + +/* +=================== +idKeyInput::ClearStates +=================== +*/ +void idKeyInput::ClearStates( void ) { + int i; + + for ( i = 0; i < MAX_KEYS; i++ ) { + if ( keys[i].down ) { + PreliminaryKeyEvent( i, false ); + } + keys[i].down = false; + } + + // clear the usercommand states + usercmdGen->Clear(); +} + +/* +=================== +idKeyInput::Init +=================== +*/ +void idKeyInput::Init( void ) { + + keys = new idKey[MAX_KEYS]; + + // register our functions + cmdSystem->AddCommand( "bind", Key_Bind_f, CMD_FL_SYSTEM, "binds a command to a key", idKeyInput::ArgCompletion_KeyName ); + cmdSystem->AddCommand( "bindunbindtwo", Key_BindUnBindTwo_f, CMD_FL_SYSTEM, "binds a key but unbinds it first if there are more than two binds" ); + cmdSystem->AddCommand( "unbind", Key_Unbind_f, CMD_FL_SYSTEM, "unbinds any command from a key", idKeyInput::ArgCompletion_KeyName ); + cmdSystem->AddCommand( "unbindall", Key_Unbindall_f, CMD_FL_SYSTEM, "unbinds any commands from all keys" ); + cmdSystem->AddCommand( "listBinds", Key_ListBinds_f, CMD_FL_SYSTEM, "lists key bindings" ); +} + +/* +=================== +idKeyInput::Shutdown +=================== +*/ +void idKeyInput::Shutdown( void ) { + delete [] keys; + keys = NULL; +} diff --git a/src/framework/licensee.h b/src/framework/licensee.h new file mode 100644 index 0000000..9b70489 --- /dev/null +++ b/src/framework/licensee.h @@ -0,0 +1,153 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + Definitions for information that is related to a licensee's game name and location. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: this is the name of the game we are making +#define GAME_NAME "Quake4" // appears on window titles and errors +#define ENGINE_VERSION "Quake4" +#define GAME_ICON "q4icon.bmp" + +// jnewquist: build type +#if defined(_DEBUG) +#define GAME_BUILD_TYPE "Debug" +#elif defined(_MPBETA) +#define GAME_BUILD_TYPE "MPBeta" +#elif defined(_FINAL) +#define GAME_BUILD_TYPE "" +#elif defined(_RELEASE) +#define GAME_BUILD_TYPE "" +#endif + +// paths +#define CD_BASEDIR "Quake4" +#define BASE_GAMEDIR "q4base" +#define BASE_MPGAMEDIR "q4mp" +#define DEMO_GAMEDIR "demo" + +// filenames +#define CD_EXE "Quake4.exe" + +#ifdef _XENON +#define CONFIG_FILE "save:/Quake4Config.cfg" +#else +#define CONFIG_FILE "Quake4Config.cfg" +#endif + +// base folder where the source code lives +#define SOURCE_CODE_BASE_FOLDER "code" + +#define DEVELOPER_DOMAIN "ravensoft.com" +// RAVEN END + + +// RAVEN BEGIN +// rjohnson: changed the host to our temp address +// default idnet host address +#ifndef IDNET_HOST + #define IDNET_HOST "q4master.idsoftware.com" +#endif +// RAVEN END + +// default idnet master port +#ifndef IDNET_MASTER_PORT + #define IDNET_MASTER_PORT "27650" +#endif + +#ifndef MOTD_HOST + #define MOTD_HOST "q4m-test.ravensoft.com" +#endif + +#ifndef MOTD_PORT + #define MOTD_PORT "27700" +#endif + +// default network server port +#ifndef PORT_SERVER +//RAVEN BEGIN +#define PORT_SERVER 28004 +//RAVEN END +#endif + +// Q4TV default network repeater port +#ifndef PORT_REPEATER +#define PORT_REPEATER 28104 +#endif + +#ifndef PORT_HTTP +#define PORT_HTTP 28004 +#endif + +// broadcast scan this many ports after PORT_SERVER so a single machine can run multiple servers +#define NUM_SERVER_PORTS 4 + +// see ASYNC_PROTOCOL_VERSION +// use a different major for each game +// RAVEN BEGIN +// ddynerman: rev ASYNC_PROTOCOL_MAJOR to 2 for Quake 4 +#define ASYNC_PROTOCOL_MAJOR 2 +// RAVEN END + +// Savegame Version +// Update when you can no longer maintain compatibility with previous savegames. +// For testing, we're using the build number to ensure no one ever tries to load a stale savegame +#define SAVEGAME_VERSION VERSION_BUILD_NUMBER +#define RENDERDEMO_VERSION SAVEGAME_VERSION + +// editor info +#define EDITOR_WINDOWTEXT "QuakeEdit" + +// win32 info +#define WIN32_CONSOLE_CLASS "Quake 4 WinConsole" +#define WIN32_SPLASH_CLASS "Quake 4 Splash" +#define WIN32_WINDOW_CLASS_NAME "Quake4" +#define WIN32_FAKE_WINDOW_CLASS_NAME "QUAKE4_WGL_FAKE" + +#ifdef __linux__ + #define DEFAULT_BASE_PATH "/usr/local/games/quake4" +#elif defined( MACOS_X ) + #define DEFAULT_BASE_PATH "/Applications/Quake4" +#endif + +// CD Key file info +#define CDKEY_FILE "quake4key" +#define CDKEY_TEXT "\n// Do not give this file to ANYONE.\n" \ + "// id Software, Raven Software or Activision will NOT ask you to send this file to them.\n" + +// FIXME: Update to Doom +// Product ID. Stored in "productid.txt". +// This file is copyright 1999 Id Software, and may not be duplicated except during a licensed installation of the full commercial version of Quake 3:Arena +#undef PRODUCT_ID +#define PRODUCT_ID 220, 129, 255, 108, 244, 163, 171, 55, 133, 65, 199, 36, 140, 222, 53, 99, 65, 171, 175, 232, 236, 193, 210, 250, 169, 104, 231, 231, 21, 201, 170, 208, 135, 175, 130, 136, 85, 215, 71, 23, 96, 32, 96, 83, 44, 240, 219, 138, 184, 215, 73, 27, 196, 247, 55, 139, 148, 68, 78, 203, 213, 238, 139, 23, 45, 205, 118, 186, 236, 230, 231, 107, 212, 1, 10, 98, 30, 20, 116, 180, 216, 248, 166, 35, 45, 22, 215, 229, 35, 116, 250, 167, 117, 3, 57, 55, 201, 229, 218, 222, 128, 12, 141, 149, 32, 110, 168, 215, 184, 53, 31, 147, 62, 12, 138, 67, 132, 54, 125, 6, 221, 148, 140, 4, 21, 44, 198, 3, 126, 12, 100, 236, 61, 42, 44, 251, 15, 135, 14, 134, 89, 92, 177, 246, 152, 106, 124, 78, 118, 80, 28, 42 +#undef PRODUCT_ID_LENGTH +#define PRODUCT_ID_LENGTH 152 + +#define CONFIG_SPEC "config.spec" diff --git a/src/framework/session.cpp b/src/framework/session.cpp new file mode 100644 index 0000000..6a0f7c7 --- /dev/null +++ b/src/framework/session.cpp @@ -0,0 +1,3304 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "../sys/AutoVersion.h" + +#include "Session_local.h" + +idCVar idSessionLocal::com_showAngles( "com_showAngles", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar idSessionLocal::com_minTics( "com_minTics", "1", CVAR_SYSTEM, "" ); +idCVar idSessionLocal::com_showTics( "com_showTics", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar idSessionLocal::com_fixedTic( "com_fixedTic", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 10 ); +idCVar idSessionLocal::com_showDemo( "com_showDemo", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar idSessionLocal::com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar idSessionLocal::com_aviDemoSamples( "com_aviDemoSamples", "16", CVAR_SYSTEM, "" ); +idCVar idSessionLocal::com_aviDemoWidth( "com_aviDemoWidth", "256", CVAR_SYSTEM, "" ); +idCVar idSessionLocal::com_aviDemoHeight( "com_aviDemoHeight", "256", CVAR_SYSTEM, "" ); +idCVar idSessionLocal::com_aviDemoTics( "com_aviDemoTics", "2", CVAR_SYSTEM | CVAR_INTEGER, "", 1, 60 ); +idCVar idSessionLocal::com_wipeSeconds( "com_wipeSeconds", "1", CVAR_SYSTEM, "" ); +idCVar idSessionLocal::com_guid( "com_guid", "", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_ROM, "" ); + +idSessionLocal sessLocal; +idSession *session = &sessLocal; + +// these must be kept up to date with window Levelshot in guis/mainmenu.gui +const int PREVIEW_X = 211; +const int PREVIEW_Y = 31; +const int PREVIEW_WIDTH = 398; +const int PREVIEW_HEIGHT = 298; + +void RandomizeStack( void ) { + // attempt to force uninitialized stack memory bugs + int bytes = 4000000; + byte *buf = (byte *)_alloca( bytes ); + + int fill = rand()&255; + for ( int i = 0 ; i < bytes ; i++ ) { + buf[i] = fill; + } +} + +/* +================= +Session_RescanSI_f +================= +*/ +void Session_RescanSI_f( const idCmdArgs &args ) { + sessLocal.mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + if ( game && idAsyncNetwork::server.IsActive() ) { + game->SetServerInfo( sessLocal.mapSpawnData.serverInfo ); + } +} + +/* +================== +Session_Map_f + +Restart the server on a different map +================== +*/ +static void Session_Map_f( const idCmdArgs &args ) { + idStr map, string; + findFile_t ff; + idCmdArgs rl_args; + + map = args.Argv(1); + if ( !map.Length() ) { + return; + } + map.StripFileExtension(); + + // make sure the level exists before trying to change, so that + // a typo at the server console won't end the game + // handle addon packs through reloadEngine + sprintf( string, "maps/%s.map", map.c_str() ); + ff = fileSystem->FindFile( string ); + switch ( ff ) { + case FIND_NO: + common->Printf( "Can't find map %s\n", string.c_str() ); + return; + case FIND_ADDON: + common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() ); + rl_args.AppendArg( "map" ); + rl_args.AppendArg( map ); + cmdSystem->SetupReloadEngine( rl_args ); + return; + default: + break; + } + + cvarSystem->SetCVarBool( "developer", false ); + sessLocal.StartNewGame( map, true ); +} + +/* +================== +Session_DevMap_f + +Restart the server on a different map in developer mode +================== +*/ +static void Session_DevMap_f( const idCmdArgs &args ) { + idStr map, string; + findFile_t ff; + idCmdArgs rl_args; + + map = args.Argv(1); + if ( !map.Length() ) { + return; + } + map.StripFileExtension(); + + // make sure the level exists before trying to change, so that + // a typo at the server console won't end the game + // handle addon packs through reloadEngine + sprintf( string, "maps/%s.map", map.c_str() ); + ff = fileSystem->FindFile( string ); + switch ( ff ) { + case FIND_NO: + common->Printf( "Can't find map %s\n", string.c_str() ); + return; + case FIND_ADDON: + common->Printf( "map %s is in an addon pak - reloading\n", string.c_str() ); + rl_args.AppendArg( "devmap" ); + rl_args.AppendArg( map ); + cmdSystem->SetupReloadEngine( rl_args ); + return; + default: + break; + } + + cvarSystem->SetCVarBool( "developer", true ); + sessLocal.StartNewGame( map, true ); +} + +/* +================== +Session_TestMap_f +================== +*/ +static void Session_TestMap_f( const idCmdArgs &args ) { + idStr map, string; + + map = args.Argv(1); + if ( !map.Length() ) { + return; + } + map.StripFileExtension(); + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "disconnect" ); + + sprintf( string, "dmap maps/%s.map", map.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, string ); + + sprintf( string, "devmap %s", map.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, string ); +} + +/* +================== +Sess_WritePrecache_f +================== +*/ +static void Sess_WritePrecache_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Printf( "USAGE: writePrecache \n" ); + return; + } + idStr str = args.Argv(1); + str.DefaultFileExtension( ".cfg" ); + idFile *f = fileSystem->OpenFileWrite( str ); + declManager->WritePrecacheCommands( f ); + renderModelManager->WritePrecacheCommands( f ); + uiManager->WritePrecacheCommands( f ); + + fileSystem->CloseFile( f ); +} + +/* +=============== +idSessionLocal::MaybeWaitOnCDKey +=============== +*/ +bool idSessionLocal::MaybeWaitOnCDKey( void ) { + if ( authEmitTimeout > 0 ) { + authWaitBox = true; + sessLocal.MessageBox( MSG_WAIT, common->GetLanguageDict()->GetString( "#str_07191" ), NULL, true, NULL, NULL, true ); + return true; + } + return false; +} + +/* +=================== +Session_PromptKey_f +=================== +*/ +static void Session_PromptKey_f( const idCmdArgs &args ) { + const char *retkey; + bool valid[ 2 ]; + static bool recursed = false; + + if ( recursed ) { + common->Warning( "promptKey recursed - aborted" ); + return; + } + recursed = true; + + do { + // in case we're already waiting for an auth to come back to us ( may happen exceptionally ) + if ( sessLocal.MaybeWaitOnCDKey() ) { + if ( sessLocal.CDKeysAreValid( true ) ) { + recursed = false; + return; + } + } + // the auth server may have replied and set an error message, otherwise use a default + const char *prompt_msg = sessLocal.GetAuthMsg(); + if ( prompt_msg[ 0 ] == '\0' ) { + prompt_msg = common->GetLanguageDict()->GetString( "#str_04308" ); + } + retkey = sessLocal.MessageBox( MSG_CDKEY, prompt_msg, common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true ); + if ( retkey ) { + if ( sessLocal.CheckKey( retkey, false, valid ) ) { + // if all went right, then we may have sent an auth request to the master ( unless the prompt is used during a net connect ) + bool canExit = true; + if ( sessLocal.MaybeWaitOnCDKey() ) { + // wait on auth reply, and got denied, prompt again + if ( !sessLocal.CDKeysAreValid( true ) ) { + // server says key is invalid - MaybeWaitOnCDKey was interrupted by a CDKeysAuthReply call, which has set the right error message + // the invalid keys have also been cleared in the process + sessLocal.MessageBox( MSG_OK, sessLocal.GetAuthMsg(), common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true ); + canExit = false; + } + } + if ( canExit ) { + // make sure that's saved on file + sessLocal.WriteCDKey(); + sessLocal.MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_04307" ), common->GetLanguageDict()->GetString( "#str_04305" ), true, NULL, NULL, true ); + break; + } + } else { + // offline check sees key invalid + // build a message about keys being wrong. do not attempt to change the current key state though + // ( the keys may be valid, but user would have clicked on the dialog anyway, that kind of thing ) + idStr msg; + idAsyncNetwork::BuildInvalidKeyMsg( msg, valid ); + sessLocal.MessageBox( MSG_OK, msg, common->GetLanguageDict()->GetString( "#str_04310" ), true, NULL, NULL, true ); + } + } else if ( args.Argc() == 2 && idStr::Icmp( args.Argv(1), "force" ) == 0 ) { + // cancelled in force mode + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + cmdSystem->ExecuteCommandBuffer(); + } + } while ( retkey ); + recursed = false; +} + +/* +=============================================================================== + +SESSION LOCAL + +=============================================================================== +*/ + +/* +=============== +idSessionLocal::Clear +=============== +*/ +void idSessionLocal::Clear() { + + insideUpdateScreen = false; + insideExecuteMapChange = false; + + loadingSaveGame = false; + savegameFile = NULL; + savegameVersion = 0; + + currentMapName.Clear(); + aviDemoShortName.Clear(); + msgFireBack[ 0 ].Clear(); + msgFireBack[ 1 ].Clear(); + + timeHitch = 0; + + rw = NULL; + sw = SOUNDWORLD_GAME; + menuSoundWorld = SOUNDWORLD_MENU; + readDemo = NULL; + writeDemo = NULL; + renderdemoVersion = 0; + cmdDemoFile = NULL; + + syncNextGameFrame = false; + mapSpawned = false; + guiActive = NULL; + aviCaptureMode = false; + timeDemo = TD_NO; + waitingOnBind = false; + lastPacifierTime = 0; + + msgRunning = false; + guiMsgRestore = NULL; + msgIgnoreButtons = false; + + bytesNeededForMapLoad = 0; + +#if defined( ID_CONSOLE_LOCK ) + emptyDrawCount = 0; +#endif + ClearWipe(); + + loadGameList.Clear(); + modsList.Clear(); + + authEmitTimeout = 0; + authWaitBox = false; + + authMsg.Clear(); +} + +/* +=============== +idSessionLocal::idSessionLocal +=============== +*/ +idSessionLocal::idSessionLocal() { + guiInGame = guiMainMenu = guiIntro \ + = guiRestartMenu = guiLoading = guiGameOver = guiActive \ + = guiTest = guiMsg = guiMsgRestore = guiTakeNotes = NULL; + + menuSoundWorld = SOUNDWORLD_MENU; + + Clear(); +} + +/* +=============== +idSessionLocal::~idSessionLocal +=============== +*/ +idSessionLocal::~idSessionLocal() { +} + +/* +=============== +idSessionLocal::Stop + +called on errors and game exits +=============== +*/ +void idSessionLocal::Stop() { + ClearWipe(); + + // clear mapSpawned and demo playing flags + UnloadMap(); + + // disconnect async client + idAsyncNetwork::client.DisconnectFromServer(); + + // kill async server + idAsyncNetwork::server.Kill(); + + soundSystem->StopAllSounds( sw ); + + insideUpdateScreen = false; + insideExecuteMapChange = false; + + // drop all guis + SetGUI( NULL, NULL ); +} + +/* +=============== +idSessionLocal::Shutdown +=============== +*/ +void idSessionLocal::Shutdown() { + int i; + + if ( aviCaptureMode ) { + EndAVICapture(); + } + + Stop(); + + if ( rw ) { + delete rw; + rw = NULL; + } + + soundSystem->StopAllSounds( sw ); + soundSystem->StopAllSounds( menuSoundWorld ); + + mapSpawnData.serverInfo.Clear(); + mapSpawnData.syncedCVars.Clear(); + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + mapSpawnData.userInfo[i].Clear(); + mapSpawnData.persistentPlayerInfo[i].Clear(); + } + + if ( guiMainMenu_MapList != NULL ) { + guiMainMenu_MapList->Shutdown(); + uiManager->FreeListGUI( guiMainMenu_MapList ); + guiMainMenu_MapList = NULL; + } + + Clear(); +} + +/* +=============== +idSessionLocal::IsMultiplayer +=============== +*/ +bool idSessionLocal::IsMultiplayer() { + return idAsyncNetwork::IsActive(); +} + +void idSessionLocal::QuickMapChange( const char *mapName ) { + MoveToNewMap( mapName ); +} + +bool idSessionLocal::GetMapSpawned( void ) { + return mapSpawned; +} + +bool idSessionLocal::GetLoadingSaveGame( void ) { + return loadingSaveGame; +} + +idRenderWorld *idSessionLocal::GetGameRenderWorld( void ) { + return rw; +} + +/* +================ +idSessionLocal::StartWipe + +Draws and captures the current state, then starts a wipe with that image +================ +*/ +void idSessionLocal::StartWipe( const char *_wipeMaterial, bool hold ) { + console->Close(); + + // render the current screen into a texture for the wipe model + renderSystem->CropRenderSize( 640, 480, true ); + + Draw(); + + renderSystem->CaptureRenderToImage( "_scratch"); + renderSystem->UnCrop(); + + wipeMaterial = declManager->FindMaterial( _wipeMaterial, false ); + + wipeStartTic = com_ticNumber; + wipeStopTic = wipeStartTic + 1000.0f / common->GetUserCmdMSec() * com_wipeSeconds.GetFloat(); + wipeHold = hold; +} + +/* +================ +idSessionLocal::CompleteWipe +================ +*/ +void idSessionLocal::CompleteWipe() { + if ( com_ticNumber == 0 ) { + // if the async thread hasn't started, we would hang here + wipeStopTic = 0; + UpdateScreen( true ); + return; + } + while ( com_ticNumber < wipeStopTic ) { +#if defined( ID_CONSOLE_LOCK ) + emptyDrawCount = 0; +#endif + UpdateScreen( true ); + } +} + +/* +================ +idSessionLocal::ShowLoadingGui +================ +*/ +void idSessionLocal::ShowLoadingGui() { + if ( com_ticNumber == 0 ) { + return; + } + console->Close(); + + // introduced in D3XP code. don't think it actually fixes anything, but doesn't hurt either +#if 1 + // Try and prevent the while loop from being skipped over (long hitch on the main thread?) + int stop = Sys_Milliseconds() + 1000; + int force = 10; + while ( Sys_Milliseconds() < stop || force-- > 0 ) { + com_frameTime = com_ticNumber * common->GetUserCmdMSec(); + session->Frame(); + session->UpdateScreen( false ); + } +#else + int stop = com_ticNumber + 1000.0f / common->GetUserCmdMSec() * 1.0f; + while ( com_ticNumber < stop ) { + com_frameTime = com_ticNumber * common->GetUserCmdMSec(); + session->Frame(); + session->UpdateScreen( false ); + } +#endif +} + + + +/* +================ +idSessionLocal::ClearWipe +================ +*/ +void idSessionLocal::ClearWipe( void ) { + wipeHold = false; + wipeStopTic = 0; + wipeStartTic = wipeStopTic + 1; +} + +/* +================ +Session_TestGUI_f +================ +*/ +static void Session_TestGUI_f( const idCmdArgs &args ) { + sessLocal.TestGUI( args.Argv(1) ); +} + +/* +================ +idSessionLocal::TestGUI +================ +*/ +void idSessionLocal::TestGUI( const char *guiName ) { + if ( guiName && *guiName ) { + guiTest = uiManager->FindGui( guiName, true, false, true ); + } else { + guiTest = NULL; + } +} + +/* +================ +FindUnusedFileName +================ +*/ +static idStr FindUnusedFileName( const char *format ) { + int i; + char filename[1024]; + + for ( i = 0 ; i < 999 ; i++ ) { + sprintf( filename, format, i ); + int len = fileSystem->ReadFile( filename, NULL, NULL ); + if ( len <= 0 ) { + return filename; // file doesn't exist + } + } + + return filename; +} + +/* +================ +Session_DemoShot_f +================ +*/ +static void Session_DemoShot_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + idStr filename = FindUnusedFileName( "demos/shot%03i.demo" ); + sessLocal.DemoShot( filename ); + } else { + sessLocal.DemoShot( va( "demos/shot_%s.demo", args.Argv(1) ) ); + } +} + +/* +================ +Session_RecordDemo_f +================ +*/ +static void Session_RecordDemo_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + idStr filename = FindUnusedFileName( "demos/demo%03i.demo" ); + sessLocal.StartRecordingRenderDemo( filename ); + } else { + sessLocal.StartRecordingRenderDemo( va( "demos/%s.demo", args.Argv(1) ) ); + } +} + +/* +================ +Session_CompressDemo_f +================ +*/ +static void Session_CompressDemo_f( const idCmdArgs &args ) { + if ( args.Argc() == 2 ) { + sessLocal.CompressDemoFile( "2", args.Argv(1) ); + } else if ( args.Argc() == 3 ) { + sessLocal.CompressDemoFile( args.Argv(2), args.Argv(1) ); + } else { + common->Printf("use: CompressDemo [scheme]\nscheme is the same as com_compressDemo, defaults to 2" ); + } +} + +/* +================ +Session_StopRecordingDemo_f +================ +*/ +static void Session_StopRecordingDemo_f( const idCmdArgs &args ) { + sessLocal.StopRecordingRenderDemo(); +} + +/* +================ +Session_PlayDemo_f +================ +*/ +static void Session_PlayDemo_f( const idCmdArgs &args ) { + if ( args.Argc() >= 2 ) { + sessLocal.StartPlayingRenderDemo( va( "demos/%s", args.Argv(1) ) ); + } +} + +/* +================ +Session_TimeDemo_f +================ +*/ +static void Session_TimeDemo_f( const idCmdArgs &args ) { + if ( args.Argc() >= 2 ) { + sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), ( args.Argc() > 2 ) ); + } +} + +/* +================ +Session_TimeDemoQuit_f +================ +*/ +static void Session_TimeDemoQuit_f( const idCmdArgs &args ) { + sessLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ) ); + if ( sessLocal.timeDemo == TD_YES ) { + // this allows hardware vendors to automate some testing + sessLocal.timeDemo = TD_YES_THEN_QUIT; + } +} + +/* +================ +Session_AVIDemo_f +================ +*/ +static void Session_AVIDemo_f( const idCmdArgs &args ) { + sessLocal.AVIRenderDemo( va( "demos/%s", args.Argv(1) ) ); +} + +/* +================ +Session_AVIGame_f +================ +*/ +static void Session_AVIGame_f( const idCmdArgs &args ) { + sessLocal.AVIGame( args.Argv(1) ); +} + +/* +================ +Session_AVICmdDemo_f +================ +*/ +static void Session_AVICmdDemo_f( const idCmdArgs &args ) { + sessLocal.AVICmdDemo( args.Argv(1) ); +} + +/* +================ +Session_WriteCmdDemo_f +================ +*/ +static void Session_WriteCmdDemo_f( const idCmdArgs &args ) { + if ( args.Argc() == 1 ) { + idStr filename = FindUnusedFileName( "demos/cmdDemo%03i.cdemo" ); + sessLocal.WriteCmdDemo( filename ); + } else if ( args.Argc() == 2 ) { + sessLocal.WriteCmdDemo( va( "demos/%s.cdemo", args.Argv( 1 ) ) ); + } else { + common->Printf( "usage: writeCmdDemo [demoName]\n" ); + } +} + +/* +================ +Session_PlayCmdDemo_f +================ +*/ +static void Session_PlayCmdDemo_f( const idCmdArgs &args ) { + sessLocal.StartPlayingCmdDemo( args.Argv(1) ); +} + +/* +================ +Session_TimeCmdDemo_f +================ +*/ +static void Session_TimeCmdDemo_f( const idCmdArgs &args ) { + sessLocal.TimeCmdDemo( args.Argv(1) ); +} + +/* +================ +Session_Disconnect_f +================ +*/ +static void Session_Disconnect_f( const idCmdArgs &args ) { + sessLocal.Stop(); + sessLocal.StartMenu(); + if ( soundSystem ) { + soundSystem->SetMute( false ); + } +} + +#ifdef ID_DEMO_BUILD +/* +================ +Session_EndOfDemo_f +================ +*/ +static void Session_EndOfDemo_f( const idCmdArgs &args ) { + sessLocal.Stop(); + sessLocal.StartMenu(); + if ( soundSystem ) { + soundSystem->SetMute( false ); + } + if ( sessLocal.guiActive ) { + sessLocal.guiActive->HandleNamedEvent( "endOfDemo" ); + } +} +#endif + +/* +================ +Session_ExitCmdDemo_f +================ +*/ +static void Session_ExitCmdDemo_f( const idCmdArgs &args ) { + if ( !sessLocal.cmdDemoFile ) { + common->Printf( "not reading from a cmdDemo\n" ); + return; + } + fileSystem->CloseFile( sessLocal.cmdDemoFile ); + common->Printf( "Command demo exited at logIndex %i\n", sessLocal.logIndex ); + sessLocal.cmdDemoFile = NULL; +} + +/* +================ +idSessionLocal::StartRecordingRenderDemo +================ +*/ +void idSessionLocal::StartRecordingRenderDemo( const char *demoName ) { + if ( writeDemo ) { + // allow it to act like a toggle + StopRecordingRenderDemo(); + return; + } + + if ( !demoName[0] ) { + common->Printf( "idSessionLocal::StartRecordingRenderDemo: no name specified\n" ); + return; + } + + console->Close(); + + writeDemo = new idDemoFile; + if ( !writeDemo->OpenForWriting( demoName ) ) { + common->Printf( "error opening %s\n", demoName ); + delete writeDemo; + writeDemo = NULL; + return; + } + + common->Printf( "recording to %s\n", writeDemo->GetName() ); + + writeDemo->WriteChar( DS_VERSION ); + writeDemo->WriteInt( RENDERDEMO_VERSION ); + + // if we are in a map already, dump the current state + soundSystem->StartWritingDemo( sw, writeDemo ); + rw->StartWritingDemo( writeDemo ); +} + +/* +================ +idSessionLocal::StopRecordingRenderDemo +================ +*/ +void idSessionLocal::StopRecordingRenderDemo() { + if ( !writeDemo ) { + common->Printf( "idSessionLocal::StopRecordingRenderDemo: not recording\n" ); + return; + } + soundSystem->StopWritingDemo( sw ); + rw->StopWritingDemo(); + + writeDemo->Close(); + common->Printf( "stopped recording %s.\n", writeDemo->GetName() ); + delete writeDemo; + writeDemo = NULL; +} + +/* +================ +idSessionLocal::StopPlayingRenderDemo + +Reports timeDemo numbers and finishes any avi recording +================ +*/ +void idSessionLocal::StopPlayingRenderDemo() { + if ( !readDemo ) { + timeDemo = TD_NO; + return; + } + + // Record the stop time before doing anything that could be time consuming + int timeDemoStopTime = Sys_Milliseconds(); + + EndAVICapture(); + + readDemo->Close(); + + soundSystem->StopAllSounds( sw ); + soundSystem->SetActiveSoundWorld( true ); + + common->Printf( "stopped playing %s.\n", readDemo->GetName() ); + delete readDemo; + readDemo = NULL; + + if ( timeDemo ) { + // report the stats + float demoSeconds = ( timeDemoStopTime - timeDemoStartTime ) * 0.001f; + float demoFPS = numDemoFrames / demoSeconds; + idStr message = va( "%i frames rendered in %3.1f seconds = %3.1f fps\n", numDemoFrames, demoSeconds, demoFPS ); + + common->Printf( message ); + if ( timeDemo == TD_YES_THEN_QUIT ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + } else { + soundSystem->SetMute( true ); + MessageBox( MSG_OK, message, "Time Demo Results", true ); + soundSystem->SetMute( false ); + } + timeDemo = TD_NO; + } +} + +/* +================ +idSessionLocal::DemoShot + +A demoShot is a single frame demo +================ +*/ +void idSessionLocal::DemoShot( const char *demoName ) { + StartRecordingRenderDemo( demoName ); + + // force draw one frame + UpdateScreen(); + + StopRecordingRenderDemo(); +} + +/* +================ +idSessionLocal::StartPlayingRenderDemo +================ +*/ +void idSessionLocal::StartPlayingRenderDemo( idStr demoName ) { + if ( !demoName[0] ) { + common->Printf( "idSessionLocal::StartPlayingRenderDemo: no name specified\n" ); + return; + } + + // make sure localSound / GUI intro music shuts up + soundSystem->StopAllSounds( sw ); + soundSystem->PlayShaderDirectly( sw, "", 0 ); + soundSystem->StopAllSounds( menuSoundWorld ); + soundSystem->PlayShaderDirectly( menuSoundWorld, "", 0 ); + + // exit any current game + Stop(); + + // automatically put the console away + console->Close(); + + // bring up the loading screen manually, since demos won't + // call ExecuteMapChange() + guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true ); + guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_02087" ) ); + readDemo = new idDemoFile; + demoName.DefaultFileExtension( ".demo" ); + if ( !readDemo->OpenForReading( demoName ) ) { + common->Printf( "couldn't open %s\n", demoName.c_str() ); + delete readDemo; + readDemo = NULL; + Stop(); + StartMenu(); + soundSystem->SetMute( false ); + return; + } + + insideExecuteMapChange = true; + UpdateScreen(); + insideExecuteMapChange = false; + guiLoading->SetStateString( "demo", "" ); + + // setup default render demo settings + // that's default for <= Doom3 v1.1 + renderdemoVersion = 1; + savegameVersion = 16; + + AdvanceRenderDemo( true ); + + numDemoFrames = 1; + + lastDemoTic = -1; + timeDemoStartTime = Sys_Milliseconds(); +} + +/* +================ +idSessionLocal::TimeRenderDemo +================ +*/ +void idSessionLocal::TimeRenderDemo( const char *demoName, bool twice ) { + idStr demo = demoName; + + // no sound in time demos + soundSystem->SetMute( true ); + + StartPlayingRenderDemo( demo ); + + if ( twice && readDemo ) { + // cycle through once to precache everything + guiLoading->SetStateString( "demo", common->GetLanguageDict()->GetString( "#str_04852" ) ); + guiLoading->StateChanged( com_frameTime ); + while ( readDemo ) { + insideExecuteMapChange = true; + UpdateScreen(); + insideExecuteMapChange = false; + AdvanceRenderDemo( true ); + } + guiLoading->SetStateString( "demo", "" ); + StartPlayingRenderDemo( demo ); + } + + + if ( !readDemo ) { + return; + } + + timeDemo = TD_YES; +} + + +/* +================ +idSessionLocal::BeginAVICapture +================ +*/ +void idSessionLocal::BeginAVICapture( const char *demoName ) { + idStr name = demoName; + name.ExtractFileBase( aviDemoShortName ); + aviCaptureMode = true; + aviDemoFrameCount = 0; + aviTicStart = 0; + soundSystem->AVIOpen( sw, va( "demos/%s/", aviDemoShortName.c_str() ), aviDemoShortName.c_str() ); +} + +/* +================ +idSessionLocal::EndAVICapture +================ +*/ +void idSessionLocal::EndAVICapture() { + if ( !aviCaptureMode ) { + return; + } + + soundSystem->AVIClose( sw ); + + // write a .roqParam file so the demo can be converted to a roq file + idFile *f = fileSystem->OpenFileWrite( va( "demos/%s/%s.roqParam", + aviDemoShortName.c_str(), aviDemoShortName.c_str() ) ); + f->Printf( "INPUT_DIR demos/%s\n", aviDemoShortName.c_str() ); + f->Printf( "FILENAME demos/%s/%s.RoQ\n", aviDemoShortName.c_str(), aviDemoShortName.c_str() ); + f->Printf( "\nINPUT\n" ); + f->Printf( "%s_*.tga [00000-%05i]\n", aviDemoShortName.c_str(), (int)( aviDemoFrameCount-1 ) ); + f->Printf( "END_INPUT\n" ); + delete f; + + common->Printf( "captured %i frames for %s.\n", ( int )aviDemoFrameCount, aviDemoShortName.c_str() ); + + aviCaptureMode = false; +} + + +/* +================ +idSessionLocal::AVIRenderDemo +================ +*/ +void idSessionLocal::AVIRenderDemo( const char *_demoName ) { + idStr demoName = _demoName; // copy off from va() buffer + + StartPlayingRenderDemo( demoName ); + if ( !readDemo ) { + return; + } + + BeginAVICapture( demoName.c_str() ) ; + + // I don't understand why I need to do this twice, something + // strange with the nvidia swapbuffers? + UpdateScreen(); +} + +/* +================ +idSessionLocal::AVICmdDemo +================ +*/ +void idSessionLocal::AVICmdDemo( const char *demoName ) { + StartPlayingCmdDemo( demoName ); + + BeginAVICapture( demoName ) ; +} + +/* +================ +idSessionLocal::AVIGame + +Start AVI recording the current game session +================ +*/ +void idSessionLocal::AVIGame( const char *demoName ) { + if ( aviCaptureMode ) { + EndAVICapture(); + return; + } + + if ( !mapSpawned ) { + common->Printf( "No map spawned.\n" ); + } + + if ( !demoName || !demoName[0] ) { + idStr filename = FindUnusedFileName( "demos/game%03i.game" ); + demoName = filename.c_str(); + + // write a one byte stub .game file just so the FindUnusedFileName works, + fileSystem->WriteFile( demoName, demoName, 1 ); + } + + BeginAVICapture( demoName ) ; +} + +/* +================ +idSessionLocal::CompressDemoFile +================ +*/ +void idSessionLocal::CompressDemoFile( const char *scheme, const char *demoName ) { + idStr fullDemoName = "demos/"; + fullDemoName += demoName; + fullDemoName.DefaultFileExtension( ".demo" ); + idStr compressedName = fullDemoName; + compressedName.StripFileExtension(); + compressedName.Append( "_compressed.demo" ); + + int savedCompression = cvarSystem->GetCVarInteger("com_compressDemos"); + bool savedPreload = cvarSystem->GetCVarBool("com_preloadDemos"); + cvarSystem->SetCVarBool( "com_preloadDemos", false ); + cvarSystem->SetCVarInteger("com_compressDemos", atoi(scheme) ); + + idDemoFile demoread, demowrite; + if ( !demoread.OpenForReading( fullDemoName ) ) { + common->Printf( "Could not open %s for reading\n", fullDemoName.c_str() ); + return; + } + if ( !demowrite.OpenForWriting( compressedName ) ) { + common->Printf( "Could not open %s for writing\n", compressedName.c_str() ); + demoread.Close(); + cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); + cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); + return; + } + common->SetRefreshOnPrint( true ); + common->Printf( "Compressing %s to %s...\n", fullDemoName.c_str(), compressedName.c_str() ); + + static const int bufferSize = 65535; + char buffer[bufferSize]; + int bytesRead; + while ( 0 != (bytesRead = demoread.Read( buffer, bufferSize ) ) ) { + demowrite.Write( buffer, bytesRead ); + common->Printf( "." ); + } + + demoread.Close(); + demowrite.Close(); + + cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); + cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); + + common->Printf( "Done\n" ); + common->SetRefreshOnPrint( false ); + +} + + +/* +=============== +idSessionLocal::StartNewGame +=============== +*/ +void idSessionLocal::StartNewGame( const char *mapName, bool devmap ) { +#ifdef ID_DEDICATED + common->Printf( "Dedicated servers cannot start singleplayer games.\n" ); + return; +#else +#if ID_ENFORCE_KEY + // strict check. don't let a game start without a definitive answer + if ( !CDKeysAreValid( true ) ) { + bool prompt = true; + if ( MaybeWaitOnCDKey() ) { + // check again, maybe we just needed more time + if ( CDKeysAreValid( true ) ) { + // can continue directly + prompt = false; + } + } + if ( prompt ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "promptKey force" ); + cmdSystem->ExecuteCommandBuffer(); + } + } +#endif + if ( idAsyncNetwork::server.IsActive() ) { + common->Printf("Server running, use si_map / serverMapRestart\n"); + return; + } + if ( idAsyncNetwork::client.IsActive() ) { + common->Printf("Client running, disconnect from server first\n"); + return; + } + + // clear the userInfo so the player starts out with the defaults + mapSpawnData.userInfo[0].Clear(); + mapSpawnData.persistentPlayerInfo[0].Clear(); + mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO ); + + mapSpawnData.serverInfo.Clear(); + mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" ); + + // set the devmap key so any play testing items will be given at + // spawn time to set approximately the right weapons and ammo + if(devmap) { + mapSpawnData.serverInfo.Set( "devmap", "1" ); + } + + mapSpawnData.syncedCVars.Clear(); + mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC ); + + MoveToNewMap( mapName ); +#endif +} + +/* +=============== +idSessionLocal::GetAutoSaveName +=============== +*/ +idStr idSessionLocal::GetAutoSaveName( const char *mapName ) const { + const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); + const idDeclEntityDef *mapDef = static_cast( mapDecl ); + if ( mapDef ) { + mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) ); + } + // Fixme: Localization + return va( "^3AutoSave:^0 %s", mapName ); +} + +/* +=============== +idSessionLocal::MoveToNewMap + +Leaves the existing userinfo and serverinfo +=============== +*/ +void idSessionLocal::MoveToNewMap( const char *mapName ) { + mapSpawnData.serverInfo.Set( "si_map", mapName ); + + ExecuteMapChange(); + + if ( !mapSpawnData.serverInfo.GetBool("devmap") ) { + // Autosave at the beginning of the level + SaveGame( GetAutoSaveName( mapName ), true ); + } + + SetGUI( NULL, NULL ); +} + +/* +============== +SaveCmdDemoFromFile +============== +*/ +void idSessionLocal::SaveCmdDemoToFile( idFile *file ) { + + mapSpawnData.serverInfo.WriteToFileHandle( file ); + + for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) { + mapSpawnData.userInfo[i].WriteToFileHandle( file ); + mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( file ); + } + + file->Write( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) ); + + if ( numClients < 1 ) { + numClients = 1; + } + file->Write( loggedUsercmds, numClients * logIndex * sizeof( loggedUsercmds[0] ) ); +} + +/* +============== +idSessionLocal::LoadCmdDemoFromFile +============== +*/ +void idSessionLocal::LoadCmdDemoFromFile( idFile *file ) { + + mapSpawnData.serverInfo.ReadFromFileHandle( file ); + + for ( int i = 0 ; i < MAX_ASYNC_CLIENTS ; i++ ) { + mapSpawnData.userInfo[i].ReadFromFileHandle( file ); + mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( file ); + } + file->Read( &mapSpawnData.mapSpawnUsercmd, sizeof( mapSpawnData.mapSpawnUsercmd ) ); +} + +/* +============== +idSessionLocal::WriteCmdDemo + +Dumps the accumulated commands for the current level. +This should still work after disconnecting from a level +============== +*/ +void idSessionLocal::WriteCmdDemo( const char *demoName, bool save ) { + + if ( !demoName[0] ) { + common->Printf( "idSessionLocal::WriteCmdDemo: no name specified\n" ); + return; + } + + idStr statsName; + if (save) { + statsName = demoName; + statsName.StripFileExtension(); + statsName.DefaultFileExtension(".stats"); + } + + common->Printf( "writing save data to %s\n", demoName ); + + idFile *cmdDemoFile = fileSystem->OpenFileWrite( demoName ); + if ( !cmdDemoFile ) { + common->Printf( "Couldn't open for writing %s\n", demoName ); + return; + } + + if ( save ) { + cmdDemoFile->Write( &logIndex, sizeof( logIndex ) ); + } + + SaveCmdDemoToFile( cmdDemoFile ); + + if ( save ) { + idFile *statsFile = fileSystem->OpenFileWrite( statsName ); + if ( statsFile ) { + statsFile->Write( &statIndex, sizeof( statIndex ) ); + statsFile->Write( loggedStats, numClients * statIndex * sizeof( loggedStats[0] ) ); + fileSystem->CloseFile( statsFile ); + } + } + + fileSystem->CloseFile( cmdDemoFile ); +} + +/* +=============== +idSessionLocal::FinishCmdLoad +=============== +*/ +void idSessionLocal::FinishCmdLoad() { +} + +/* +=============== +idSessionLocal::StartPlayingCmdDemo +=============== +*/ +void idSessionLocal::StartPlayingCmdDemo(const char *demoName) { + // exit any current game + Stop(); + + idStr fullDemoName = "demos/"; + fullDemoName += demoName; + fullDemoName.DefaultFileExtension( ".cdemo" ); + cmdDemoFile = fileSystem->OpenFileRead(fullDemoName); + + if ( cmdDemoFile == NULL ) { + common->Printf( "Couldn't open %s\n", fullDemoName.c_str() ); + return; + } + + guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true ); + //cmdDemoFile->Read(&loadGameTime, sizeof(loadGameTime)); + + LoadCmdDemoFromFile(cmdDemoFile); + + // start the map + ExecuteMapChange(); + + cmdDemoFile = fileSystem->OpenFileRead(fullDemoName); + + // have to do this twice as the execmapchange clears the cmddemofile + LoadCmdDemoFromFile(cmdDemoFile); + + // run one frame to get the view angles correct + RunGameTic(); +} + +/* +=============== +idSessionLocal::TimeCmdDemo +=============== +*/ +void idSessionLocal::TimeCmdDemo( const char *demoName ) { + StartPlayingCmdDemo( demoName ); + ClearWipe(); + UpdateScreen(); + + int startTime = Sys_Milliseconds(); + int count = 0; + int minuteStart, minuteEnd; + float sec; + + // run all the frames in sequence + minuteStart = startTime; + + while( cmdDemoFile ) { + RunGameTic(); + count++; + + if ( count / 3600 != ( count - 1 ) / 3600 ) { + minuteEnd = Sys_Milliseconds(); + sec = ( minuteEnd - minuteStart ) / 1000.0; + minuteStart = minuteEnd; + common->Printf( "minute %i took %3.1f seconds\n", count / 3600, sec ); + UpdateScreen(); + } + } + + int endTime = Sys_Milliseconds(); + sec = ( endTime - startTime ) / 1000.0; + common->Printf( "%i seconds of game, replayed in %5.1f seconds\n", count / 60, sec ); +} + +/* +=============== +idSessionLocal::UnloadMap + +Performs cleanup that needs to happen between maps, or when a +game is exited. +Exits with mapSpawned = false +=============== +*/ +void idSessionLocal::UnloadMap() { + StopPlayingRenderDemo(); + + // end the current map in the game + if ( game ) { + game->MapShutdown(); + } + + if ( cmdDemoFile ) { + fileSystem->CloseFile( cmdDemoFile ); + cmdDemoFile = NULL; + } + + if ( writeDemo ) { + StopRecordingRenderDemo(); + } + + mapSpawned = false; +} + +/* +=============== +idSessionLocal::LoadLoadingGui +=============== +*/ +void idSessionLocal::LoadLoadingGui( const char *mapName ) { + // load / program a gui to stay up on the screen while loading + idStr stripped = mapName; + stripped.StripFileExtension(); + stripped.StripPath(); + + char guiMap[ MAX_STRING_CHARS ]; + strncpy( guiMap, va( "guis/map/%s.gui", stripped.c_str() ), MAX_STRING_CHARS ); + // give the gamecode a chance to override + const char *gameLoadingGui = game->GetLoadingGui( mapName ); + if ( gameLoadingGui != NULL && gameLoadingGui[0] != '\0' ) { + idStr::Copynz( guiMap, gameLoadingGui, sizeof( guiMap ) ); + } + + if ( uiManager->CheckGui( guiMap ) ) { + guiLoading = uiManager->FindGui( guiMap, true, false, true ); + } else { + guiLoading = uiManager->FindGui( "guis/map/loading.gui", true, false, true ); + } + guiLoading->SetStateFloat( "map_loading", 0.0f ); +} + +/* +=============== +idSessionLocal::GetBytesNeededForMapLoad +=============== +*/ +int idSessionLocal::GetBytesNeededForMapLoad( const char *mapName ) { + const idDecl *mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); + const idDeclEntityDef *mapDef = static_cast( mapDecl ); + if ( mapDef ) { + return mapDef->dict.GetInt( va("size%d", Max( 0, com_machineSpec.GetInteger() ) ) ); + } else { + if ( com_machineSpec.GetInteger() < 2 ) { + return 200 * 1024 * 1024; + } else { + return 400 * 1024 * 1024; + } + } +} + +/* +=============== +idSessionLocal::SetBytesNeededForMapLoad +=============== +*/ +void idSessionLocal::SetBytesNeededForMapLoad( const char *mapName, int bytesNeeded ) { + idDecl *mapDecl = const_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + idDeclEntityDef *mapDef = static_cast( mapDecl ); + + if ( com_updateLoadSize.GetBool() && mapDef ) { + // we assume that if com_updateLoadSize is true then the file is writable + + mapDef->dict.SetInt( va("size%d", com_machineSpec.GetInteger()), bytesNeeded ); + + idStr declText = "\nmapDef "; + declText += mapDef->GetName(); + declText += " {\n"; + for (int i=0; idict.GetNumKeyVals(); i++) { + const idKeyValue *kv = mapDef->dict.GetKeyVal( i ); + if ( kv && (kv->GetKey().Cmp("classname") != 0 ) ) { + declText += "\t\"" + kv->GetKey() + "\"\t\t\"" + kv->GetValue() + "\"\n"; + } + } + declText += "}"; + mapDef->SetText( declText ); + mapDef->ReplaceSourceFileText(); + } +} + +/* +=============== +idSessionLocal::ExecuteMapChange + +Performs the initialization of a game based on mapSpawnData, used for both single +player and multiplayer, but not for renderDemos, which don't +create a game at all. +Exits with mapSpawned = true +=============== +*/ +void idSessionLocal::ExecuteMapChange( bool noFadeWipe ) { + int i; + bool reloadingSameMap; + + // close console and remove any prints from the notify lines + console->Close(); + + if ( IsMultiplayer() ) { + // make sure the mp GUI isn't up, or when players get back in the + // map, mpGame's menu and the gui will be out of sync. + SetGUI( NULL, NULL ); + } + + // mute sound + soundSystem->SetMute( true ); + + // clear all menu sounds + soundSystem->StopAllSounds( menuSoundWorld ); + + // unpause the game sound world + // NOTE: we UnPause again later down. not sure this is needed + soundSystem->SetActiveSoundWorld( true ); + + if ( !noFadeWipe ) { + // capture the current screen and start a wipe + StartWipe( "wipeMaterial", true ); + + // immediately complete the wipe to fade out the level transition + // run the wipe to completion + CompleteWipe(); + } + + // extract the map name from serverinfo + idStr mapString = mapSpawnData.serverInfo.GetString( "si_map" ); + + idStr fullMapName = "maps/"; + fullMapName += mapString; + fullMapName.StripFileExtension(); + + // shut down the existing game if it is running + UnloadMap(); + + // don't do the deferred caching if we are reloading the same map + if ( fullMapName == currentMapName ) { + reloadingSameMap = true; + } else { + reloadingSameMap = false; + currentMapName = fullMapName; + } + + // note which media we are going to need to load + if ( !reloadingSameMap ) { + declManager->BeginLevelLoad(); + renderSystem->BeginLevelLoad(); + soundSystem->BeginLevelLoad( mapString.c_str() ); + } + + uiManager->BeginLevelLoad(); + uiManager->Reload( true ); + + // set the loading gui that we will wipe to + LoadLoadingGui( mapString ); + + // cause prints to force screen updates as a pacifier, + // and draw the loading gui instead of game draws + insideExecuteMapChange = true; + + // if this works out we will probably want all the sizes in a def file although this solution will + // work for new maps etc. after the first load. we can also drop the sizes into the default.cfg + fileSystem->ResetReadCount(); + if ( !reloadingSameMap ) { + bytesNeededForMapLoad = GetBytesNeededForMapLoad( mapString.c_str() ); + } else { + bytesNeededForMapLoad = 30 * 1024 * 1024; + } + + ClearWipe(); + + // let the loading gui spin for 1 second to animate out + ShowLoadingGui(); + + // note any warning prints that happen during the load process + common->ClearWarnings( mapString ); + + // release the mouse cursor + // before we do this potentially long operation + Sys_GrabMouseCursor( false ); + + // if net play, we get the number of clients during mapSpawnInfo processing + if ( !idAsyncNetwork::IsActive() ) { + numClients = 1; + } + + int start = Sys_Milliseconds(); + + common->Printf( "--------- Map Initialization ---------\n" ); + common->Printf( "Map: %s\n", mapString.c_str() ); + + // let the renderSystem load all the geometry + if ( !rw->InitFromMap( fullMapName ) ) { + common->Error( "couldn't load %s", fullMapName.c_str() ); + } + + // for the synchronous networking we needed to roll the angles over from + // level to level, but now we can just clear everything + usercmdGen->InitForNewMap(); + memset( &mapSpawnData.mapSpawnUsercmd, 0, sizeof( mapSpawnData.mapSpawnUsercmd ) ); + + // set the user info + for ( i = 0; i < numClients; i++ ) { + game->SetUserInfo( i, mapSpawnData.userInfo[i], idAsyncNetwork::client.IsActive() ); + game->SetPersistentPlayerInfo( i, mapSpawnData.persistentPlayerInfo[i] ); + } + + // load and spawn all other entities ( from a savegame possibly ) + if ( loadingSaveGame && savegameFile ) { + if ( game->InitFromSaveGame( fullMapName + ".map", rw, savegameFile ) == false ) { + // If the loadgame failed, restart the map with the player persistent data + loadingSaveGame = false; + fileSystem->CloseFile( savegameFile ); + savegameFile = NULL; + + game->SetServerInfo( mapSpawnData.serverInfo ); + game->InitFromNewMap( fullMapName + ".map", rw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() ); + } + } else { + game->SetServerInfo( mapSpawnData.serverInfo ); + game->InitFromNewMap( fullMapName + ".map", rw, idAsyncNetwork::server.IsActive(), idAsyncNetwork::client.IsActive(), Sys_Milliseconds() ); + } + + if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) { + // spawn players + for ( i = 0; i < numClients; i++ ) { + game->SpawnPlayer( i ); + } + } + + // actually purge/load the media + if ( !reloadingSameMap ) { + renderSystem->EndLevelLoad(); + soundSystem->EndLevelLoad( mapString.c_str() ); + declManager->EndLevelLoad(); + SetBytesNeededForMapLoad( mapString.c_str(), fileSystem->GetReadCount() ); + } + uiManager->EndLevelLoad(); + + if ( !idAsyncNetwork::IsActive() && !loadingSaveGame ) { + // run a few frames to allow everything to settle + for ( i = 0; i < 10; i++ ) { + game->RunFrame( mapSpawnData.mapSpawnUsercmd, com_editors, true, lastGameTic ); + } + } + + common->Printf ("-----------------------------------\n"); + + int msec = Sys_Milliseconds() - start; + common->Printf( "%6d msec to load %s\n", msec, mapString.c_str() ); + + // let the renderSystem generate interactions now that everything is spawned + rw->GenerateAllInteractions(); + + common->PrintWarnings(); + + if ( guiLoading && bytesNeededForMapLoad ) { + float pct = guiLoading->State().GetFloat( "map_loading" ); + if ( pct < 0.0f ) { + pct = 0.0f; + } + while ( pct < 1.0f ) { + guiLoading->SetStateFloat( "map_loading", pct ); + guiLoading->StateChanged( com_frameTime ); + Sys_GenerateEvents(); + UpdateScreen(); + pct += 0.05f; + } + } + + // capture the current screen and start a wipe + StartWipe( "wipe2Material" ); + + usercmdGen->Clear(); + + // start saving commands for possible writeCmdDemo usage + logIndex = 0; + statIndex = 0; + lastSaveIndex = 0; + + // don't bother spinning over all the tics we spent loading + lastGameTic = latchedTicNumber = com_ticNumber; + + // remove any prints from the notify lines + console->ClearNotifyLines(); + + // stop drawing the laoding screen + insideExecuteMapChange = false; + + Sys_SetPhysicalWorkMemory( -1, -1 ); + + // set the game sound world for playback + soundSystem->SetActiveSoundWorld( true ); + + // restart entity sound playback + soundSystem->SetMute( false ); + + // we are valid for game draws now + mapSpawned = true; + Sys_ClearEvents(); +} + +/* +=============== +LoadGame_f +=============== +*/ +void LoadGame_f( const idCmdArgs &args ) { + console->Close(); + if ( args.Argc() < 2 || idStr::Icmp(args.Argv(1), "quick" ) == 0 ) { + idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" ); + sessLocal.LoadGame( saveName ); + } else { + sessLocal.LoadGame( args.Argv(1) ); + } +} + +/* +=============== +SaveGame_f +=============== +*/ +void SaveGame_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 || idStr::Icmp( args.Argv(1), "quick" ) == 0 ) { + idStr saveName = common->GetLanguageDict()->GetString( "#str_07178" ); + if ( sessLocal.SaveGame( saveName ) ) { + common->Printf( "%s\n", saveName.c_str() ); + } + } else { + if ( sessLocal.SaveGame( args.Argv(1) ) ) { + common->Printf( "Saved %s\n", args.Argv(1) ); + } + } +} + +/* +=============== +TakeViewNotes_f +=============== +*/ +void TakeViewNotes_f( const idCmdArgs &args ) { + const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : ""; + sessLocal.TakeNotes( p ); +} + +/* +=============== +TakeViewNotes2_f +=============== +*/ +void TakeViewNotes2_f( const idCmdArgs &args ) { + const char *p = ( args.Argc() > 1 ) ? args.Argv( 1 ) : ""; + sessLocal.TakeNotes( p, true ); +} + +/* +=============== +idSessionLocal::TakeNotes +=============== +*/ +void idSessionLocal::TakeNotes( const char *p, bool extended ) { + if ( !mapSpawned ) { + common->Printf( "No map loaded!\n" ); + return; + } + + if ( extended ) { + guiTakeNotes = uiManager->FindGui( "guis/takeNotes2.gui", true, false, true ); + +#if 0 + const char *people[] = { + "Nobody", "Adam", "Brandon", "David", "PHook", "Jay", "Jake", + "PatJ", "Brett", "Ted", "Darin", "Brian", "Sean" + }; +#else + const char *people[] = { + "Tim", "Kenneth", "Robert", + "Matt", "Mal", "Jerry", "Steve", "Pat", + "Xian", "Ed", "Fred", "James", "Eric", "Andy", "Seneca", "Patrick", "Kevin", + "MrElusive", "Jim", "Brian", "John", "Adrian", "Nobody" + }; +#endif + const int numPeople = sizeof( people ) / sizeof( people[0] ); + + idListGUI * guiList_people = uiManager->AllocListGUI(); + guiList_people->Config( guiTakeNotes, "person" ); + for ( int i = 0; i < numPeople; i++ ) { + guiList_people->Push( people[i] ); + } + uiManager->FreeListGUI( guiList_people ); + + } else { + guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true ); + } + + SetGUI( guiTakeNotes, NULL ); + guiActive->SetStateString( "note", "" ); + guiActive->SetStateString( "notefile", p ); + guiActive->SetStateBool( "extended", extended ); + guiActive->Activate( true, com_frameTime ); +} + +/* +=============== +Session_Hitch_f +=============== +*/ +void Session_Hitch_f( const idCmdArgs &args ) { + soundSystem->SetMute( true ); + Sys_EnterCriticalSection(); + if ( args.Argc() == 2 ) { + Sys_Sleep( atoi(args.Argv(1)) ); + } else { + Sys_Sleep( 100 ); + } + Sys_LeaveCriticalSection(); + soundSystem->SetMute( false ); +} + +/* +=============== +idSessionLocal::ScrubSaveGameFileName + +Turns a bad file name into a good one or your money back +=============== +*/ +void idSessionLocal::ScrubSaveGameFileName( idStr &saveFileName ) const { + int i; + idStr inFileName; + + inFileName = saveFileName; + inFileName.RemoveEscapes(); + inFileName.StripFileExtension(); + + saveFileName.Clear(); + + int len = inFileName.Length(); + for ( i = 0; i < len; i++ ) { + if ( strchr( "',.~!@#$%^&*()[]{}<>\\|/=?+;:-\'\"", inFileName[i] ) ) { + // random junk + saveFileName += '_'; + } else if ( (const unsigned char)inFileName[i] >= 128 ) { + // high ascii chars + saveFileName += '_'; + } else if ( inFileName[i] == ' ' ) { + saveFileName += '_'; + } else { + saveFileName += inFileName[i]; + } + } +} + +/* +=============== +idSessionLocal::SaveGame +=============== +*/ +bool idSessionLocal::SaveGame( const char *saveName, bool autosave ) { +#ifdef ID_DEDICATED + common->Printf( "Dedicated servers cannot save games.\n" ); + return false; +#else + int i; + idStr gameFile, previewFile, descriptionFile, mapName; + + if ( !mapSpawned ) { + common->Printf( "Not playing a game.\n" ); + return false; + } + + if ( IsMultiplayer() ) { + common->Printf( "Can't save during net play.\n" ); + return false; + } + + if ( game->GetPersistentPlayerInfo( 0 ).GetInt( "health" ) <= 0 ) { + MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04311" ), common->GetLanguageDict()->GetString ( "#str_04312" ), true ); + common->Printf( "You must be alive to save the game\n" ); + return false; + } + + if ( Sys_GetDriveFreeSpace( cvarSystem->GetCVarString( "fs_savepath" ) ) < 25 ) { + MessageBox( MSG_OK, common->GetLanguageDict()->GetString ( "#str_04313" ), common->GetLanguageDict()->GetString ( "#str_04314" ), true ); + common->Printf( "Not enough drive space to save the game\n" ); + return false; + } + + const bool pauseSound = true; + soundSystem->SetMute( true ); + + // setup up filenames and paths + gameFile = saveName; + ScrubSaveGameFileName( gameFile ); + + gameFile = "savegames/" + gameFile; + gameFile.SetFileExtension( ".save" ); + + previewFile = gameFile; + previewFile.SetFileExtension( ".tga" ); + + descriptionFile = gameFile; + descriptionFile.SetFileExtension( ".txt" ); + + // Open savegame file + idFile *fileOut = fileSystem->OpenFileWrite( gameFile ); + if ( fileOut == NULL ) { + common->Warning( "Failed to open save file '%s'\n", gameFile.c_str() ); + if ( pauseSound ) { + soundSystem->SetMute( false ); + } + return false; + } + + // Write SaveGame Header: + // Game Name / Version / Map Name / Persistant Player Info + + // game + const char *gamename = GAME_NAME; + fileOut->WriteString( gamename ); + + // version + fileOut->WriteInt( SAVEGAME_VERSION ); + + // map + mapName = mapSpawnData.serverInfo.GetString( "si_map" ); + fileOut->WriteString( mapName ); + + // persistent player info + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i ); + mapSpawnData.persistentPlayerInfo[i].WriteToFileHandle( fileOut ); + } + + // let the game save its state + game->SaveGame( fileOut ); + + // close the sava game file + fileSystem->CloseFile( fileOut ); + + // Write screenshot + if ( !autosave ) { + renderSystem->CropRenderSize( 320, 240, false ); + game->Draw( 0 ); + renderSystem->CaptureRenderToFile( previewFile, true ); + renderSystem->UnCrop(); + } + + // Write description, which is just a text file with + // the unclean save name on line 1, map name on line 2, screenshot on line 3 + idFile *fileDesc = fileSystem->OpenFileWrite( descriptionFile ); + if ( fileDesc == NULL ) { + common->Warning( "Failed to open description file '%s'\n", descriptionFile.c_str() ); + if ( pauseSound ) { + soundSystem->SetMute( false ); + } + return false; + } + + idStr description = saveName; + description.Replace( "\\", "\\\\" ); + description.Replace( "\"", "\\\"" ); + + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLanguageDict()->GetString( mapDef->dict.GetString( "name", mapName ) ); + } + + fileDesc->Printf( "\"%s\"\n", description.c_str() ); + fileDesc->Printf( "\"%s\"\n", mapName.c_str()); + + if ( autosave ) { + idStr sshot = mapSpawnData.serverInfo.GetString( "si_map" ); + sshot.StripPath(); + sshot.StripFileExtension(); + fileDesc->Printf( "\"guis/assets/autosave/%s\"\n", sshot.c_str() ); + } else { + fileDesc->Printf( "\"\"\n" ); + } + + fileSystem->CloseFile( fileDesc ); + + if ( pauseSound ) { + soundSystem->SetMute( false ); + } + + syncNextGameFrame = true; + + + return true; +#endif +} + +/* +=============== +idSessionLocal::LoadGame +=============== +*/ +bool idSessionLocal::LoadGame( const char *saveName ) { +#ifdef ID_DEDICATED + common->Printf( "Dedicated servers cannot load games.\n" ); + return false; +#else + int i; + idStr in, loadFile, saveMap, gamename; + + if ( IsMultiplayer() ) { + common->Printf( "Can't load during net play.\n" ); + return false; + } + + //Hide the dialog box if it is up. + StopBox(); + + loadFile = saveName; + ScrubSaveGameFileName( loadFile ); + loadFile.SetFileExtension( ".save" ); + + in = "savegames/"; + in += loadFile; + + // Open savegame file + // only allow loads from the game directory because we don't want a base game to load + idStr game = cvarSystem->GetCVarString( "fs_game" ); + savegameFile = fileSystem->OpenFileRead( in, true, game.Length() ? game : NULL ); + + if ( savegameFile == NULL ) { + common->Warning( "Couldn't open savegame file %s", in.c_str() ); + return false; + } + + loadingSaveGame = true; + + // Read in save game header + // Game Name / Version / Map Name / Persistant Player Info + + // game + savegameFile->ReadString( gamename ); + + // if this isn't a savegame for the correct game, abort loadgame + if ( gamename != GAME_NAME ) { + common->Warning( "Attempted to load an invalid savegame: %s", in.c_str() ); + + loadingSaveGame = false; + fileSystem->CloseFile( savegameFile ); + savegameFile = NULL; + return false; + } + + // version + savegameFile->ReadInt( savegameVersion ); + + // map + savegameFile->ReadString( saveMap ); + + // persistent player info + for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) { + mapSpawnData.persistentPlayerInfo[i].ReadFromFileHandle( savegameFile ); + } + + // check the version, if it doesn't match, cancel the loadgame, + // but still load the map with the persistant playerInfo from the header + // so that the player doesn't lose too much progress. + if ( savegameVersion != SAVEGAME_VERSION && + !( savegameVersion == 16 && SAVEGAME_VERSION == 17 ) ) { // handle savegame v16 in v17 + common->Warning( "Savegame Version mismatch: aborting loadgame and starting level with persistent data" ); + loadingSaveGame = false; + fileSystem->CloseFile( savegameFile ); + savegameFile = NULL; + } + + common->DPrintf( "loading a v%d savegame\n", savegameVersion ); + + if ( saveMap.Length() > 0 ) { + + // Start loading map + mapSpawnData.serverInfo.Clear(); + + mapSpawnData.serverInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + mapSpawnData.serverInfo.Set( "si_gameType", "singleplayer" ); + + mapSpawnData.serverInfo.Set( "si_map", saveMap ); + + mapSpawnData.syncedCVars.Clear(); + mapSpawnData.syncedCVars = *cvarSystem->MoveCVarsToDict( CVAR_NETWORKSYNC ); + + mapSpawnData.mapSpawnUsercmd[0] = usercmdGen->TicCmd( latchedTicNumber ); + // make sure no buttons are pressed + mapSpawnData.mapSpawnUsercmd[0].buttons = 0; + + ExecuteMapChange(); + + SetGUI( NULL, NULL ); + } + + if ( loadingSaveGame ) { + fileSystem->CloseFile( savegameFile ); + loadingSaveGame = false; + savegameFile = NULL; + } + + return true; +#endif +} + +/* +=============== +idSessionLocal::ProcessEvent +=============== +*/ +bool idSessionLocal::ProcessEvent( const sysEvent_t *event ) { + // hitting escape anywhere brings up the menu + if ( !guiActive && event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE ) { + console->Close(); + if ( game ) { + idUserInterface *gui = NULL; + escReply_t op; + op = game->HandleESC( &gui ); + if ( op == ESC_IGNORE ) { + return true; + } else if ( op == ESC_GUI ) { + SetGUI( gui, NULL ); + return true; + } + } + StartMenu(); + return true; + } + + // let the pull-down console take it if desired + if ( console->ProcessEvent( event, false ) ) { + return true; + } + + // if we are testing a GUI, send all events to it + if ( guiTest ) { + // hitting escape exits the testgui + if ( event->evType == SE_KEY && event->evValue2 == 1 && event->evValue == K_ESCAPE ) { + guiTest = NULL; + return true; + } + + static const char *cmd; + cmd = guiTest->HandleEvent( event, com_frameTime ); + if ( cmd && cmd[0] ) { + common->Printf( "testGui event returned: '%s'\n", cmd ); + } + return true; + } + + // menus / etc + if ( guiActive ) { + MenuEvent( event ); + return true; + } + + // if we aren't in a game, force the console to take it + if ( !mapSpawned ) { + console->ProcessEvent( event, true ); + return true; + } + + // in game, exec bindings for all key downs + if ( event->evType == SE_KEY && event->evValue2 == 1 ) { + idKeyInput::ExecKeyBinding( event->evValue ); + return true; + } + + return false; +} + +/* +=============== +idSessionLocal::DrawWipeModel + +Draw the fade material over everything that has been drawn +=============== +*/ +void idSessionLocal::DrawWipeModel() { + int latchedTic = com_ticNumber; + + if ( wipeStartTic >= wipeStopTic ) { + return; + } + + if ( !wipeHold && latchedTic >= wipeStopTic ) { + return; + } + + float fade = ( float )( latchedTic - wipeStartTic ) / ( wipeStopTic - wipeStartTic ); + renderSystem->SetColor4( 1, 1, 1, fade ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, wipeMaterial ); +} + +/* +=============== +idSessionLocal::AdvanceRenderDemo +=============== +*/ +void idSessionLocal::AdvanceRenderDemo( bool singleFrameOnly ) { + if ( lastDemoTic == -1 ) { + lastDemoTic = latchedTicNumber - 1; + } + + int skipFrames = 0; + + if ( !aviCaptureMode && !timeDemo && !singleFrameOnly ) { + skipFrames = ( (latchedTicNumber - lastDemoTic) / USERCMD_PER_DEMO_FRAME ) - 1; + // never skip too many frames, just let it go into slightly slow motion + if ( skipFrames > 4 ) { + skipFrames = 4; + } + lastDemoTic = latchedTicNumber - latchedTicNumber % USERCMD_PER_DEMO_FRAME; + } else { + // always advance a single frame with avidemo and timedemo + lastDemoTic = latchedTicNumber; + } + + while( skipFrames > -1 ) { + char ds = DS_FINISHED; + + readDemo->ReadChar( ds ); + if ( ds == DS_FINISHED ) { + if ( numDemoFrames != 1 ) { + // if the demo has a single frame (a demoShot), continuously replay + // the renderView that has already been read + Stop(); + StartMenu(); + } + break; + } + if ( ds == DS_RENDER ) { + if ( rw->ProcessDemoCommand( readDemo, ¤tDemoRenderView, NULL, &demoTimeOffset ) ) { + // a view is ready to render + skipFrames--; + numDemoFrames++; + } + continue; + } + if ( ds == DS_SOUND ) { + soundSystem->ProcessDemoCommand( sw, readDemo ); + continue; + } + // appears in v1.2, with savegame format 17 + if ( ds == DS_VERSION ) { + readDemo->ReadInt( renderdemoVersion ); + common->Printf( "reading a v%d render demo\n", renderdemoVersion ); + // set the savegameVersion to current for render demo paths that share the savegame paths + savegameVersion = SAVEGAME_VERSION; + continue; + } + common->Error( "Bad render demo token" ); + } + + if ( com_showDemo.GetBool() ) { + common->Printf( "frame:%i DemoTic:%i latched:%i skip:%i\n", numDemoFrames, lastDemoTic, latchedTicNumber, skipFrames ); + } + +} + +/* +=============== +idSessionLocal::DrawCmdGraph + +Graphs yaw angle for testing smoothness +=============== +*/ +static const int ANGLE_GRAPH_HEIGHT = 128; +static const int ANGLE_GRAPH_STRETCH = 3; +void idSessionLocal::DrawCmdGraph() { + if ( !com_showAngles.GetBool() ) { + return; + } + renderSystem->SetColor4( 0.1f, 0.1f, 0.1f, 1.0f ); + renderSystem->DrawStretchPic( 0, 480-ANGLE_GRAPH_HEIGHT, MAX_BUFFERED_USERCMD*ANGLE_GRAPH_STRETCH, ANGLE_GRAPH_HEIGHT, 0, 0, 1, 1, whiteMaterial ); + renderSystem->SetColor4( 0.9f, 0.9f, 0.9f, 1.0f ); + for ( int i = 0 ; i < MAX_BUFFERED_USERCMD-4 ; i++ ) { + usercmd_t cmd = usercmdGen->TicCmd( latchedTicNumber - (MAX_BUFFERED_USERCMD-4) + i ); + int h = cmd.angles[1]; + h >>= 8; + h &= (ANGLE_GRAPH_HEIGHT-1); + renderSystem->DrawStretchPic( i* ANGLE_GRAPH_STRETCH, 480-h, 1, h, 0, 0, 1, 1, whiteMaterial ); + } +} + +/* +=============== +idSessionLocal::PacifierUpdate +=============== +*/ +void idSessionLocal::PacifierUpdate() { + if ( !insideExecuteMapChange ) { + return; + } + + // never do pacifier screen updates while inside the + // drawing code, or we can have various recursive problems + if ( insideUpdateScreen ) { + return; + } + + int time = eventLoop->Milliseconds(); + + if ( time - lastPacifierTime < 100 ) { + return; + } + lastPacifierTime = time; + + if ( guiLoading && bytesNeededForMapLoad ) { + float n = fileSystem->GetReadCount(); + float pct = ( n / bytesNeededForMapLoad ); + // pct = idMath::ClampFloat( 0.0f, 100.0f, pct ); + guiLoading->SetStateFloat( "map_loading", pct ); + guiLoading->StateChanged( com_frameTime ); + } + + Sys_GenerateEvents(); + + UpdateScreen(); + + idAsyncNetwork::client.PacifierUpdate(); + idAsyncNetwork::server.PacifierUpdate(); +} + +/* +=============== +idSessionLocal::Draw +=============== +*/ +void idSessionLocal::Draw() { + bool fullConsole = false; + + if ( insideExecuteMapChange ) { + if ( guiLoading ) { + guiLoading->Redraw( com_frameTime ); + } + if ( guiActive == guiMsg ) { + guiMsg->Redraw( com_frameTime ); + } + } else if ( guiTest ) { + // if testing a gui, clear the screen and draw it + // clear the background, in case the tested gui is transparent + // NOTE that you can't use this for aviGame recording, it will tick at real com_frameTime between screenshots.. + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); + guiTest->Redraw( com_frameTime ); + } else if ( guiActive && !guiActive->State().GetBool( "gameDraw" ) ) { + + // draw the frozen gui in the background + if ( guiActive == guiMsg && guiMsgRestore ) { + guiMsgRestore->Redraw( com_frameTime ); + } + + // draw the menus full screen + if ( guiActive == guiTakeNotes && !com_skipGameDraw.GetBool() ) { + game->Draw( GetLocalClientNum() ); + } + + guiActive->Redraw( com_frameTime ); + } else if ( readDemo ) { + rw->RenderScene( ¤tDemoRenderView ); + renderSystem->DrawDemoPics(); + } else if ( mapSpawned ) { + bool gameDraw = false; + // normal drawing for both single and multi player + if ( !com_skipGameDraw.GetBool() && GetLocalClientNum() >= 0 ) { + // draw the game view + int start = Sys_Milliseconds(); + gameDraw = game->Draw( GetLocalClientNum() ); + int end = Sys_Milliseconds(); + time_gameDraw += ( end - start ); // note time used for com_speeds + } + if ( !gameDraw ) { + renderSystem->SetColor( colorBlack ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); + } + + // save off the 2D drawing from the game + if ( writeDemo ) { + renderSystem->WriteDemoPics(); + } + } else { +#if defined( ID_CONSOLE_LOCK ) + if ( com_allowConsole.GetBool() ) { + console->Draw( true ); + } else { + emptyDrawCount++; + if ( emptyDrawCount > 5 ) { + // it's best if you can avoid triggering the watchgod by doing the right thing somewhere else + assert( false ); + common->Warning( "idSession: triggering mainmenu watchdog" ); + emptyDrawCount = 0; + StartMenu(); + } + renderSystem->SetColor4( 0, 0, 0, 1 ); + renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); + } +#else + // draw the console full screen - this should only ever happen in developer builds + console->Draw( true ); +#endif + fullConsole = true; + } + +#if defined( ID_CONSOLE_LOCK ) + if ( !fullConsole && emptyDrawCount ) { + common->DPrintf( "idSession: %d empty frame draws\n", emptyDrawCount ); + emptyDrawCount = 0; + } + fullConsole = false; +#endif + + // draw the wipe material on top of this if it hasn't completed yet + DrawWipeModel(); + + // draw debug graphs + DrawCmdGraph(); + + // draw the half console / notify console on top of everything + if ( !fullConsole ) { + console->Draw( false ); + } +} + +/* +=============== +idSessionLocal::UpdateScreen +=============== +*/ +void idSessionLocal::UpdateScreen( bool outOfSequence ) { + +#ifdef _WIN32 + + if ( com_editors ) { + if ( !Sys_IsWindowVisible() ) { + return; + } + } +#endif + + if ( insideUpdateScreen ) { + return; +// common->FatalError( "idSessionLocal::UpdateScreen: recursively called" ); + } + + insideUpdateScreen = true; + + // if this is a long-operation update and we are in windowed mode, + // release the mouse capture back to the desktop + if ( outOfSequence ) { + Sys_GrabMouseCursor( false ); + } + + renderSystem->BeginFrame( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight() ); + + // draw everything + Draw(); + + if ( com_speeds.GetBool() ) { + renderSystem->EndFrame( &time_frontend, &time_backend ); + } else { + renderSystem->EndFrame( NULL, NULL ); + } + + insideUpdateScreen = false; +} + +/* +=============== +idSessionLocal::Frame +=============== +*/ +void idSessionLocal::Frame() { + + if ( com_asyncSound.GetInteger() == 0 ) { + soundSystem->AsyncUpdate( Sys_Milliseconds() ); + } + + // Editors that completely take over the game + if ( com_editorActive && ( com_editors & ( EDITOR_RADIANT | EDITOR_GUI ) ) ) { + return; + } + + // if the console is down, we don't need to hold + // the mouse cursor + if ( console->Active() || com_editorActive ) { + Sys_GrabMouseCursor( false ); + } else { + Sys_GrabMouseCursor( true ); + } + + // save the screenshot and audio from the last draw if needed + if ( aviCaptureMode ) { + idStr name; + + name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviTicStart ); + + float ratio = 30.0f / ( 1000.0f / common->GetUserCmdMSec() / com_aviDemoTics.GetInteger() ); + aviDemoFrameCount += ratio; + if ( aviTicStart + 1 != ( int )aviDemoFrameCount ) { + // skipped frames so write them out + int c = aviDemoFrameCount - aviTicStart; + while ( c-- ) { + renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); + name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), ++aviTicStart ); + } + } + aviTicStart = aviDemoFrameCount; + + // remove any printed lines at the top before taking the screenshot + console->ClearNotifyLines(); + + // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1 + renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); + } + + // at startup, we may be backwards + if ( latchedTicNumber > com_ticNumber ) { + latchedTicNumber = com_ticNumber; + } + + // se how many tics we should have before continuing + int minTic = latchedTicNumber + 1; + if ( com_minTics.GetInteger() > 1 ) { + minTic = lastGameTic + com_minTics.GetInteger(); + } + + if ( readDemo ) { + if ( !timeDemo && numDemoFrames != 1 ) { + minTic = lastDemoTic + USERCMD_PER_DEMO_FRAME; + } else { + // timedemos and demoshots will run as fast as they can, other demos + // will not run more than 30 hz + minTic = latchedTicNumber; + } + } else if ( writeDemo ) { + minTic = lastGameTic + USERCMD_PER_DEMO_FRAME; // demos are recorded at 30 hz + } + + // fixedTic lets us run a forced number of usercmd each frame without timing + if ( com_fixedTic.GetInteger() ) { + minTic = latchedTicNumber; + } + + // FIXME: deserves a cleanup and abstraction +#if defined( _WIN32 ) + // Spin in place if needed. The game should yield the cpu if + // it is running over 60 hz, because there is fundamentally + // nothing useful for it to do. + while( 1 ) { + latchedTicNumber = com_ticNumber; + if ( latchedTicNumber >= minTic ) { + break; + } + Sys_Sleep( 1 ); + } +#else + while( 1 ) { + latchedTicNumber = com_ticNumber; + if ( latchedTicNumber >= minTic ) { + break; + } + Sys_WaitForEvent( TRIGGER_EVENT_ONE ); + } +#endif + + if ( authEmitTimeout ) { + // waiting for a game auth + if ( Sys_Milliseconds() > authEmitTimeout ) { + // expired with no reply + // means that if a firewall is blocking the master, we will let through + common->DPrintf( "no reply from auth\n" ); + if ( authWaitBox ) { + // close the wait box + StopBox(); + authWaitBox = false; + } + if ( cdkey_state == CDKEY_CHECKING ) { + cdkey_state = CDKEY_OK; + } + if ( xpkey_state == CDKEY_CHECKING ) { + xpkey_state = CDKEY_OK; + } + // maintain this empty as it's set by auth denials + authMsg.Empty(); + authEmitTimeout = 0; + SetCDKeyGuiVars(); + } + } + + // send frame and mouse events to active guis + GuiFrameEvents(); + + // advance demos + if ( readDemo ) { + AdvanceRenderDemo( false ); + return; + } + + //------------ single player game tics -------------- + + if ( !mapSpawned || guiActive ) { + if ( !com_asyncInput.GetBool() ) { + // early exit, won't do RunGameTic .. but still need to update mouse position for GUIs + usercmdGen->GetDirectUsercmd(); + } + } + + if ( !mapSpawned ) { + return; + } + + if ( guiActive ) { + lastGameTic = latchedTicNumber; + return; + } + + // in message box / GUIFrame, idSessionLocal::Frame is used for GUI interactivity + // but we early exit to avoid running game frames + if ( idAsyncNetwork::IsActive() ) { + return; + } + + // check for user info changes + if ( cvarSystem->GetModifiedFlags() & CVAR_USERINFO ) { + mapSpawnData.userInfo[0] = *cvarSystem->MoveCVarsToDict( CVAR_USERINFO ); + game->SetUserInfo( 0, mapSpawnData.userInfo[0], false ); + cvarSystem->ClearModifiedFlags( CVAR_USERINFO ); + } + + // see how many usercmds we are going to run + int numCmdsToRun = latchedTicNumber - lastGameTic; + + // don't let a long onDemand sound load unsync everything + if ( timeHitch ) { + int skip = timeHitch / common->GetUserCmdMSec(); + lastGameTic += skip; + numCmdsToRun -= skip; + timeHitch = 0; + } + + // don't get too far behind after a hitch + if ( numCmdsToRun > 10 ) { + lastGameTic = latchedTicNumber - 10; + } + + // never use more than USERCMD_PER_DEMO_FRAME, + // which makes it go into slow motion when recording + if ( writeDemo ) { + int fixedTic = USERCMD_PER_DEMO_FRAME; + // we should have waited long enough + if ( numCmdsToRun < fixedTic ) { + common->Error( "idSessionLocal::Frame: numCmdsToRun < fixedTic" ); + } + // we may need to dump older commands + lastGameTic = latchedTicNumber - fixedTic; + } else if ( com_fixedTic.GetInteger() > 0 ) { + // this may cause commands run in a previous frame to + // be run again if we are going at above the real time rate + lastGameTic = latchedTicNumber - com_fixedTic.GetInteger(); + } else if ( aviCaptureMode ) { + lastGameTic = latchedTicNumber - com_aviDemoTics.GetInteger(); + } + + // force only one game frame update this frame. the game code requests this after skipping cinematics + // so we come back immediately after the cinematic is done instead of a few frames later which can + // cause sounds played right after the cinematic to not play. + if ( syncNextGameFrame ) { + lastGameTic = latchedTicNumber - 1; + syncNextGameFrame = false; + } + + // create client commands, which will be sent directly + // to the game + if ( com_showTics.GetBool() ) { + common->Printf( "%i ", latchedTicNumber - lastGameTic ); + } + + int gameTicsToRun = latchedTicNumber - lastGameTic; + int i; + for ( i = 0 ; i < gameTicsToRun ; i++ ) { + RunGameTic(); + if ( !mapSpawned ) { + // exited game play + break; + } + if ( syncNextGameFrame ) { + // long game frame, so break out and continue executing as if there was no hitch + break; + } + } +} + +/* +================ +idSessionLocal::RunGameTic +================ +*/ +void idSessionLocal::RunGameTic() { + logCmd_t logCmd; + usercmd_t cmd; + + // if we are doing a command demo, read or write from the file + if ( cmdDemoFile ) { + if ( !cmdDemoFile->Read( &logCmd, sizeof( logCmd ) ) ) { + common->Printf( "Command demo completed at logIndex %i\n", logIndex ); + fileSystem->CloseFile( cmdDemoFile ); + cmdDemoFile = NULL; + if ( aviCaptureMode ) { + EndAVICapture(); + Shutdown(); + } + // we fall out of the demo to normal commands + // the impulse and chat character toggles may not be correct, and the view + // angle will definitely be wrong + } else { + cmd = logCmd.cmd; + cmd.ByteSwap(); + logCmd.consistencyHash = LittleLong( logCmd.consistencyHash ); + } + } + + // if we didn't get one from the file, get it locally + if ( !cmdDemoFile ) { + // get a locally created command + if ( com_asyncInput.GetBool() ) { + cmd = usercmdGen->TicCmd( lastGameTic ); + } else { + cmd = usercmdGen->GetDirectUsercmd(); + } + lastGameTic++; + } + + // run the game logic every player move + int start = Sys_Milliseconds(); + gameReturn_t ret = game->RunFrame( &cmd, com_editors, common->IsRenderableGameFrame(), lastGameTic ); + + int end = Sys_Milliseconds(); + time_gameFrame += end - start; // note time used for com_speeds + + // check for constency failure from a recorded command + if ( cmdDemoFile ) { + if ( ret.consistencyHash != logCmd.consistencyHash ) { + common->Printf( "Consistency failure on logIndex %i\n", logIndex ); + Stop(); + return; + } + } + + // save the cmd for cmdDemo archiving + if ( logIndex < MAX_LOGGED_USERCMDS ) { + loggedUsercmds[logIndex].cmd = cmd; + // save the consistencyHash for demo playback verification + loggedUsercmds[logIndex].consistencyHash = ret.consistencyHash; + if (logIndex % 30 == 0 && statIndex < MAX_LOGGED_STATS) { + loggedStats[statIndex].health = ret.health; + loggedStats[statIndex].heartRate = ret.heartRate; + loggedStats[statIndex].stamina = ret.stamina; + loggedStats[statIndex].combat = ret.combat; + statIndex++; + } + logIndex++; + } + + syncNextGameFrame = ret.syncNextGameFrame; + + if ( ret.sessionCommand[0] ) { + idCmdArgs args; + + args.TokenizeString( ret.sessionCommand, false ); + + if ( !idStr::Icmp( args.Argv(0), "map" ) ) { + // get current player states + for ( int i = 0 ; i < numClients ; i++ ) { + mapSpawnData.persistentPlayerInfo[i] = game->GetPersistentPlayerInfo( i ); + } + // clear the devmap key on serverinfo, so player spawns + // won't get the map testing items + mapSpawnData.serverInfo.Delete( "devmap" ); + + // go to the next map + MoveToNewMap( args.Argv(1) ); + } else if ( !idStr::Icmp( args.Argv(0), "devmap" ) ) { + mapSpawnData.serverInfo.Set( "devmap", "1" ); + MoveToNewMap( args.Argv(1) ); + } else if ( !idStr::Icmp( args.Argv(0), "died" ) ) { + // restart on the same map + UnloadMap(); + SetGUI(guiRestartMenu, NULL); + } else if ( !idStr::Icmp( args.Argv(0), "disconnect" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_INSERT, "stoprecording ; disconnect" ); + } else if ( !idStr::Icmp( args.Argv(0), "endOfDemo" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "endOfDemo" ); + } + } +} + +/* +=============== +idSessionLocal::Init + +Called in an orderly fashion at system startup, +so commands, cvars, files, etc are all available +=============== +*/ +void idSessionLocal::Init() { + + common->Printf( "-------- Initializing Session --------\n" ); + + cmdSystem->AddCommand( "writePrecache", Sess_WritePrecache_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "writes precache commands" ); + +#ifndef ID_DEDICATED + cmdSystem->AddCommand( "map", Session_Map_f, CMD_FL_SYSTEM, "loads a map", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "devmap", Session_DevMap_f, CMD_FL_SYSTEM, "loads a map in developer mode", idCmdSystem::ArgCompletion_MapName ); + cmdSystem->AddCommand( "testmap", Session_TestMap_f, CMD_FL_SYSTEM, "tests a map", idCmdSystem::ArgCompletion_MapName ); + + cmdSystem->AddCommand( "writeCmdDemo", Session_WriteCmdDemo_f, CMD_FL_SYSTEM, "writes a command demo" ); + cmdSystem->AddCommand( "playCmdDemo", Session_PlayCmdDemo_f, CMD_FL_SYSTEM, "plays back a command demo" ); + cmdSystem->AddCommand( "timeCmdDemo", Session_TimeCmdDemo_f, CMD_FL_SYSTEM, "times a command demo" ); + cmdSystem->AddCommand( "exitCmdDemo", Session_ExitCmdDemo_f, CMD_FL_SYSTEM, "exits a command demo" ); + cmdSystem->AddCommand( "aviCmdDemo", Session_AVICmdDemo_f, CMD_FL_SYSTEM, "writes AVIs for a command demo" ); + cmdSystem->AddCommand( "aviGame", Session_AVIGame_f, CMD_FL_SYSTEM, "writes AVIs for the current game" ); + + cmdSystem->AddCommand( "recordDemo", Session_RecordDemo_f, CMD_FL_SYSTEM, "records a demo" ); + cmdSystem->AddCommand( "stopRecording", Session_StopRecordingDemo_f, CMD_FL_SYSTEM, "stops demo recording" ); + cmdSystem->AddCommand( "playDemo", Session_PlayDemo_f, CMD_FL_SYSTEM, "plays back a demo", idCmdSystem::ArgCompletion_DemoName ); + cmdSystem->AddCommand( "timeDemo", Session_TimeDemo_f, CMD_FL_SYSTEM, "times a demo", idCmdSystem::ArgCompletion_DemoName ); + cmdSystem->AddCommand( "timeDemoQuit", Session_TimeDemoQuit_f, CMD_FL_SYSTEM, "times a demo and quits", idCmdSystem::ArgCompletion_DemoName ); + cmdSystem->AddCommand( "aviDemo", Session_AVIDemo_f, CMD_FL_SYSTEM, "writes AVIs for a demo", idCmdSystem::ArgCompletion_DemoName ); + cmdSystem->AddCommand( "compressDemo", Session_CompressDemo_f, CMD_FL_SYSTEM, "compresses a demo file", idCmdSystem::ArgCompletion_DemoName ); +#endif + + cmdSystem->AddCommand( "disconnect", Session_Disconnect_f, CMD_FL_SYSTEM, "disconnects from a game" ); + +#ifdef ID_DEMO_BUILD + cmdSystem->AddCommand( "endOfDemo", Session_EndOfDemo_f, CMD_FL_SYSTEM, "ends the demo version of the game" ); +#endif + + cmdSystem->AddCommand( "demoShot", Session_DemoShot_f, CMD_FL_SYSTEM, "writes a screenshot for a demo" ); + cmdSystem->AddCommand( "testGUI", Session_TestGUI_f, CMD_FL_SYSTEM, "tests a gui" ); + +#ifndef ID_DEDICATED + cmdSystem->AddCommand( "saveGame", SaveGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "saves a game" ); + cmdSystem->AddCommand( "loadGame", LoadGame_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "loads a game", idCmdSystem::ArgCompletion_SaveGame ); +#endif + + cmdSystem->AddCommand( "takeViewNotes", TakeViewNotes_f, CMD_FL_SYSTEM, "take notes about the current map from the current view" ); + cmdSystem->AddCommand( "takeViewNotes2", TakeViewNotes2_f, CMD_FL_SYSTEM, "extended take view notes" ); + + cmdSystem->AddCommand( "rescanSI", Session_RescanSI_f, CMD_FL_SYSTEM, "internal - rescan serverinfo cvars and tell game" ); + + cmdSystem->AddCommand( "promptKey", Session_PromptKey_f, CMD_FL_SYSTEM, "prompt and sets the CD Key" ); + + cmdSystem->AddCommand( "hitch", Session_Hitch_f, CMD_FL_SYSTEM|CMD_FL_CHEAT, "hitches the game" ); + + // the same idRenderWorld will be used for all games + // and demos, insuring that level specific models + // will be freed + rw = renderSystem->AllocRenderWorld(); + sw = SOUNDWORLD_GAME; + menuSoundWorld = SOUNDWORLD_MENU; + soundSystem->SetRenderWorld( rw ); + + // we have a single instance of the main menu +#ifndef ID_DEMO_BUILD + guiMainMenu = uiManager->FindGui( "guis/mainmenu.gui", true, false, true ); +#else + guiMainMenu = uiManager->FindGui( "guis/demo_mainmenu.gui", true, false, true ); +#endif + guiMainMenu_MapList = uiManager->AllocListGUI(); + guiMainMenu_MapList->Config( guiMainMenu, "mapList" ); + idAsyncNetwork::client.serverList.GUIConfig( guiMainMenu, "serverList" ); + guiRestartMenu = uiManager->FindGui( "guis/restart.gui", true, false, true ); + guiGameOver = uiManager->FindGui( "guis/gameover.gui", true, false, true ); + guiMsg = uiManager->FindGui( "guis/msg.gui", true, false, true ); + guiTakeNotes = uiManager->FindGui( "guis/takeNotes.gui", true, false, true ); + guiIntro = uiManager->FindGui( "guis/intro.gui", true, false, true ); + + whiteMaterial = declManager->FindMaterial( "_white" ); + + guiInGame = NULL; + guiTest = NULL; + + guiActive = NULL; + guiHandle = NULL; + + ReadCDKey(); + + common->Printf( "session initialized\n" ); + common->Printf( "--------------------------------------\n" ); +} + +/* +=============== +idSessionLocal::GetLocalClientNum +=============== +*/ +int idSessionLocal::GetLocalClientNum() { + if ( idAsyncNetwork::client.IsActive() ) { + return idAsyncNetwork::client.GetLocalClientNum(); + } else if ( idAsyncNetwork::server.IsActive() ) { + if ( idAsyncNetwork::serverDedicated.GetInteger() == 0 ) { + return 0; + } else if ( idAsyncNetwork::server.IsClientInGame( idAsyncNetwork::serverDrawClient.GetInteger() ) ) { + return idAsyncNetwork::serverDrawClient.GetInteger(); + } else { + return -1; + } + } else { + return 0; + } +} + +/* +=============== +idSessionLocal::SetPlayingSoundWorld +=============== +*/ +void idSessionLocal::SetPlayingSoundWorld() { + soundSystem->SetActiveSoundWorld( true ); +} + +/* +=============== +idSessionLocal::TimeHitch + +this is used by the sound system when an OnDemand sound is loaded, so the game action +doesn't advance and get things out of sync +=============== +*/ +void idSessionLocal::TimeHitch( int msec ) { + timeHitch += msec; +} + +/* +================= +idSessionLocal::ReadCDKey +================= +*/ +void idSessionLocal::ReadCDKey( void ) { + idStr filename; + idFile *f; + char buffer[32]; + + cdkey_state = CDKEY_UNKNOWN; + + filename = "../" BASE_GAMEDIR "/" CDKEY_FILE; + f = fileSystem->OpenExplicitFileRead( fileSystem->RelativePathToOSPath( filename, "fs_savepath" ) ); + if ( !f ) { + common->Printf( "Couldn't read %s.\n", filename.c_str() ); + cdkey[ 0 ] = '\0'; + } else { + memset( buffer, 0, sizeof(buffer) ); + f->Read( buffer, CDKEY_BUF_LEN - 1 ); + fileSystem->CloseFile( f ); + idStr::Copynz( cdkey, buffer, CDKEY_BUF_LEN ); + } + + xpkey[0] = '\0'; + xpkey_state = CDKEY_NA; +} + +/* +================ +idSessionLocal::WriteCDKey +================ +*/ +void idSessionLocal::WriteCDKey( void ) { + idStr filename; + idFile *f; + const char *OSPath; + + filename = "../" BASE_GAMEDIR "/" CDKEY_FILE; + // OpenFileWrite advertises creating directories to the path if needed, but that won't work with a '..' in the path + // occasionally on windows, but mostly on Linux and OSX, the fs_savepath/base may not exist in full + OSPath = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_savepath" ), BASE_GAMEDIR, CDKEY_FILE ); + fileSystem->CreateOSPath( OSPath ); + f = fileSystem->OpenFileWrite( filename ); + if ( !f ) { + common->Printf( "Couldn't write %s.\n", filename.c_str() ); + return; + } + f->Printf( "%s%s", cdkey, CDKEY_TEXT ); + fileSystem->CloseFile( f ); + +} + +/* +=============== +idSessionLocal::ClearKey +=============== +*/ +void idSessionLocal::ClearCDKey( bool valid[ 2 ] ) { + if ( !valid[ 0 ] ) { + memset( cdkey, 0, CDKEY_BUF_LEN ); + cdkey_state = CDKEY_UNKNOWN; + } else if ( cdkey_state == CDKEY_CHECKING ) { + // if a key was in checking and not explicitely asked for clearing, put it back to ok + cdkey_state = CDKEY_OK; + } + if ( !valid[ 1 ] ) { + memset( xpkey, 0, CDKEY_BUF_LEN ); + xpkey_state = CDKEY_UNKNOWN; + } else if ( xpkey_state == CDKEY_CHECKING ) { + xpkey_state = CDKEY_OK; + } + WriteCDKey( ); +} + +void idSessionLocal::ClearCDKey( bool valid ) { + bool validity[2] = { valid, true }; + ClearCDKey( validity ); +} + +/* +================ +idSessionLocal::GetCDKey +================ +*/ +const char *idSessionLocal::GetCDKey( bool xp ) { + if ( !xp ) { + return cdkey; + } + if ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING ) { + return xpkey; + } + return NULL; +} + +const char *idSessionLocal::GetCDKey( void ) { + return GetCDKey( false ); +} + +// digits to letters table +#define CDKEY_DIGITS "TWSBJCGD7PA23RLH" + +/* +=============== +idSessionLocal::EmitGameAuth +we toggled some key state to CDKEY_CHECKING. send a standalone auth packet to validate +=============== +*/ +void idSessionLocal::EmitGameAuth( void ) { + // make sure the auth reply is empty, we use it to indicate an auth reply + authMsg.Empty(); + if ( idAsyncNetwork::client.SendAuthCheck( cdkey_state == CDKEY_CHECKING ? cdkey : NULL, xpkey_state == CDKEY_CHECKING ? xpkey : NULL ) ) { + authEmitTimeout = Sys_Milliseconds() + CDKEY_AUTH_TIMEOUT; + common->DPrintf( "authing with the master..\n" ); + } else { + // net is not available + common->DPrintf( "sendAuthCheck failed\n" ); + if ( cdkey_state == CDKEY_CHECKING ) { + cdkey_state = CDKEY_OK; + } + if ( xpkey_state == CDKEY_CHECKING ) { + xpkey_state = CDKEY_OK; + } + } +} + +/* +================ +idSessionLocal::CheckKey +the function will only modify keys to _OK or _CHECKING if the offline checks are passed +if the function returns false, the offline checks failed, and offline_valid holds which keys are bad +================ +*/ +bool idSessionLocal::CheckKey( const char *key, bool netConnect, bool offline_valid[ 2 ] ) { + char lkey[ 2 ][ CDKEY_BUF_LEN ]; + char l_chk[ 2 ][ 3 ]; + char s_chk[ 3 ]; + int imax,i_key; + unsigned int checksum, chk8; + bool edited_key[ 2 ]; + + // make sure have a right input string + assert( strlen( key ) == ( CDKEY_BUF_LEN - 1 ) * 2 + 4 + 3 + 4 ); + + edited_key[ 0 ] = ( key[0] == '1' ); + idStr::Copynz( lkey[0], key + 2, CDKEY_BUF_LEN ); + idStr::ToUpper( lkey[0] ); + idStr::Copynz( l_chk[0], key + CDKEY_BUF_LEN + 2, 3 ); + idStr::ToUpper( l_chk[0] ); + edited_key[ 1 ] = ( key[ CDKEY_BUF_LEN + 2 + 3 ] == '1' ); + idStr::Copynz( lkey[1], key + CDKEY_BUF_LEN + 7, CDKEY_BUF_LEN ); + idStr::ToUpper( lkey[1] ); + idStr::Copynz( l_chk[1], key + CDKEY_BUF_LEN * 2 + 7, 3 ); + idStr::ToUpper( l_chk[1] ); + + imax = 1; + offline_valid[ 0 ] = offline_valid[ 1 ] = true; + for( i_key = 0; i_key < imax; i_key++ ) { + // check that the characters are from the valid set + int i; + for ( i = 0; i < CDKEY_BUF_LEN - 1; i++ ) { + if ( !strchr( CDKEY_DIGITS, lkey[i_key][i] ) ) { + offline_valid[ i_key ] = false; + continue; + } + } + + if ( edited_key[ i_key ] ) { + // verify the checksum for edited keys only + checksum = CRC32_BlockChecksum( lkey[i_key], CDKEY_BUF_LEN - 1 ); + chk8 = ( checksum & 0xff ) ^ ( ( ( checksum & 0xff00 ) >> 8 ) ^ ( ( ( checksum & 0xff0000 ) >> 16 ) ^ ( ( checksum & 0xff000000 ) >> 24 ) ) ); + idStr::snPrintf( s_chk, 3, "%02X", chk8 ); + if ( idStr::Icmp( l_chk[i_key], s_chk ) != 0 ) { + offline_valid[ i_key ] = false; + continue; + } + } + } + + if ( !offline_valid[ 0 ] || !offline_valid[1] ) { + return false; + } + + // offline checks passed, we'll return true and optionally emit key check requests + // the function should only modify the key states if the offline checks passed successfully + + // set the keys, don't send a game auth if we are net connecting + idStr::Copynz( cdkey, lkey[0], CDKEY_BUF_LEN ); + netConnect ? cdkey_state = CDKEY_OK : cdkey_state = CDKEY_CHECKING; + xpkey_state = CDKEY_NA; + if ( !netConnect ) { + EmitGameAuth(); + } + SetCDKeyGuiVars(); + + return true; +} + +bool idSessionLocal::CheckKey( const char *key, bool netConnect, bool offlineValid ) { + bool valid[2] = { offlineValid, true }; + return CheckKey( key, netConnect, valid ); +} + +/* +=============== +idSessionLocal::CDKeysAreValid +checking that the key is present and uses only valid characters +if d3xp is installed, check for a valid xpkey as well +emit an auth packet to the master if possible and needed +=============== +*/ +bool idSessionLocal::CDKeysAreValid( bool strict ) { + int i; + bool emitAuth = false; + + if ( cdkey_state == CDKEY_UNKNOWN ) { + if ( strlen( cdkey ) != CDKEY_BUF_LEN - 1 ) { + cdkey_state = CDKEY_INVALID; + } else { + for ( i = 0; i < CDKEY_BUF_LEN-1; i++ ) { + if ( !strchr( CDKEY_DIGITS, cdkey[i] ) ) { + cdkey_state = CDKEY_INVALID; + break; + } + } + } + if ( cdkey_state == CDKEY_UNKNOWN ) { + cdkey_state = CDKEY_CHECKING; + emitAuth = true; + } + } + xpkey_state = CDKEY_NA; + if ( emitAuth ) { + EmitGameAuth(); + } + // make sure to keep the mainmenu gui up to date in case we made state changes + SetCDKeyGuiVars(); + if ( strict ) { + return cdkey_state == CDKEY_OK && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_NA ); + } else { + return ( cdkey_state == CDKEY_OK || cdkey_state == CDKEY_CHECKING ) && ( xpkey_state == CDKEY_OK || xpkey_state == CDKEY_CHECKING || xpkey_state == CDKEY_NA ); + } +} + +/* +=============== +idSessionLocal::WaitingForGameAuth +=============== +*/ +bool idSessionLocal::WaitingForGameAuth( void ) { + return authEmitTimeout != 0; +} + +/* +=============== +idSessionLocal::CDKeysAuthReply +=============== +*/ +void idSessionLocal::CDKeysAuthReply( bool valid, const char *auth_msg ) { + assert( authEmitTimeout > 0 ); + if ( authWaitBox ) { + // close the wait box + StopBox(); + authWaitBox = false; + } + if ( !valid ) { + common->DPrintf( "auth key is invalid\n" ); + authMsg = auth_msg; + if ( cdkey_state == CDKEY_CHECKING ) { + cdkey_state = CDKEY_INVALID; + } + if ( xpkey_state == CDKEY_CHECKING ) { + xpkey_state = CDKEY_INVALID; + } + } else { + common->DPrintf( "client is authed in\n" ); + if ( cdkey_state == CDKEY_CHECKING ) { + cdkey_state = CDKEY_OK; + } + if ( xpkey_state == CDKEY_CHECKING ) { + xpkey_state = CDKEY_OK; + } + } + authEmitTimeout = 0; + SetCDKeyGuiVars(); +} + +/* +=============== +idSessionLocal::GetCurrentMapName +=============== +*/ +void idSessionLocal::GetCurrentMapName( idStr &mapName ) { + mapName = currentMapName; +} + +void idSessionLocal::EmitClientAuth( void ) { + EmitGameAuth(); +} + +void idSessionLocal::UpdateFriends( void ) { +} + +void idSessionLocal::KillLoadingGUI( void ) { + if ( guiActive == guiLoading ) { + SetGUI( NULL, NULL ); + } +} + +void idSessionLocal::PrecacheMPSettingsMenu( void ) { + SetMainMenuGuiVars(); +} + +void idSessionLocal::SetLoadingText( const char *text ) { + if ( guiLoading != NULL ) { + guiLoading->SetStateString( "loadingText", text != NULL ? text : "" ); + } +} + +void idSessionLocal::AddLoadingIcon( const char *icon ) { + if ( guiLoading != NULL && icon != NULL ) { + guiLoading->SetStateString( "loadingIcon", icon ); + } +} + +/* +=============== +idSessionLocal::GetSaveGameVersion +=============== +*/ +int idSessionLocal::GetSaveGameVersion( void ) { + return savegameVersion; +} + +/* +=============== +idSessionLocal::GetAuthMsg +=============== +*/ +const char *idSessionLocal::GetAuthMsg( void ) { + return authMsg.c_str(); +} diff --git a/src/framework/session_menu.cpp b/src/framework/session_menu.cpp new file mode 100644 index 0000000..48b50d5 --- /dev/null +++ b/src/framework/session_menu.cpp @@ -0,0 +1,1663 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Session_local.h" + +idCVar idSessionLocal::gui_configServerRate( "gui_configServerRate", "0", CVAR_GUI | CVAR_ARCHIVE | CVAR_ROM | CVAR_INTEGER, "" ); + +// implements the setup for, and commands from, the main menu + +/* +============== +idSessionLocal::GetActiveMenu +============== +*/ +idUserInterface *idSessionLocal::GetActiveMenu( void ) { + return guiActive; +} + +idUserInterface *idSessionLocal::GetActiveGUI( void ) { + return guiActive; +} + +int idSessionLocal::GetDesiredSoundWorld( bool on, bool &newSoundWorld ) { + newSoundWorld = false; + if ( !on ) { + return SOUNDWORLD_NONE; + } + if ( common->IsToolActive() ) { + return SOUNDWORLD_EDITOR; + } + if ( guiActive == guiMainMenu ) { + newSoundWorld = true; + return SOUNDWORLD_MENU; + } + return SOUNDWORLD_GAME; +} + +const char *idSessionLocal::KeysFromBinding( const char *bind ) { + return idKeyInput::KeysFromBinding( bind ); +} + +/* +============== +idSessionLocal::StartMainMenu +============== +*/ +void idSessionLocal::StartMenu( const char *menuName ) { + if ( guiActive == guiMainMenu ) { + return; + } + + if ( readDemo ) { + // if we're playing a demo, esc kills it + UnloadMap(); + } + + // pause the game sound world + soundSystem->SetMute( true ); + + // start playing the menu sounds + soundSystem->SetActiveSoundWorld( true ); + + SetGUI( guiMainMenu, NULL ); + guiMainMenu->HandleNamedEvent( menuName != NULL ? menuName : "Init" ); + guiMainMenu->SetStateString( "game_list", common->GetLocalizedString( "#str_107212" ) ); + + console->Close(); + +} + +/* +================= +idSessionLocal::SetGUI +================= +*/ +void idSessionLocal::SetGUI( idUserInterface *gui, HandleGuiCommand_t handle ) { + const char *cmd; + + guiActive = gui; + guiHandle = handle; + if ( guiMsgRestore ) { + common->DPrintf( "idSessionLocal::SetGUI: cleared an active message box\n" ); + guiMsgRestore = NULL; + } + if ( !guiActive ) { + return; + } + + if ( guiActive == guiMainMenu ) { + SetSaveGameGuiVars(); + SetMainMenuGuiVars(); + } else if ( guiActive == guiRestartMenu ) { + SetSaveGameGuiVars(); + } + + sysEvent_t ev; + memset( &ev, 0, sizeof( ev ) ); + ev.evType = SE_NONE; + + cmd = guiActive->HandleEvent( &ev, com_frameTime ); + guiActive->Activate( true, com_frameTime ); +} + +/* +=============== +idSessionLocal::ExitMenu +=============== +*/ +void idSessionLocal::ExitMenu( void ) { + guiActive = NULL; + + // go back to the game sounds + soundSystem->SetActiveSoundWorld( true ); + + // unpause the game sound world + soundSystem->SetMute( false ); +} + +/* +=============== +idListSaveGameCompare +=============== +*/ +ID_INLINE int idListSaveGameCompare( const fileTIME_T *a, const fileTIME_T *b ) { + return b->timeStamp - a->timeStamp; +} + +/* +=============== +idSessionLocal::GetSaveGameList +=============== +*/ +void idSessionLocal::GetSaveGameList( idStrList &fileList, idList &fileTimes ) { + int i; + idFileList *files; + + // NOTE: no fs_game_base for savegames + idStr game = cvarSystem->GetCVarString( "fs_game" ); + if( game.Length() ) { + files = fileSystem->ListFiles( "savegames", ".save", false, false, game ); + } else { + files = fileSystem->ListFiles( "savegames", ".save" ); + } + + fileList = files->GetList(); + fileSystem->FreeFileList( files ); + + for ( i = 0; i < fileList.Num(); i++ ) { + ID_TIME_T timeStamp; + + fileSystem->ReadFile( "savegames/" + fileList[i], NULL, &timeStamp ); + fileList[i].StripLeading( '/' ); + fileList[i].StripFileExtension(); + + fileTIME_T ft; + ft.index = i; + ft.timeStamp = timeStamp; + fileTimes.Append( ft ); + } + + fileTimes.Sort( idListSaveGameCompare ); +} + +/* +=============== +idSessionLocal::SetSaveGameGuiVars +=============== +*/ +void idSessionLocal::SetSaveGameGuiVars( void ) { + int i; + idStr name; + idStrList fileList; + idList fileTimes; + + loadGameList.Clear(); + fileList.Clear(); + fileTimes.Clear(); + + GetSaveGameList( fileList, fileTimes ); + + loadGameList.SetNum( fileList.Num() ); + for ( i = 0; i < fileList.Num(); i++ ) { + loadGameList[i] = fileList[fileTimes[i].index]; + + idLexer src(LEXFL_NOERRORS|LEXFL_NOSTRINGCONCAT); + if ( src.LoadFile( va("savegames/%s.txt", loadGameList[i].c_str()) ) ) { + idToken tok; + src.ReadToken( &tok ); + name = tok; + } else { + name = loadGameList[i]; + } + + name += "\t"; + + idStr date = Sys_TimeStampToStr( fileTimes[i].timeStamp ); + name += date; + + guiActive->SetStateString( va("loadgame_item_%i", i), name); + } + guiActive->DeleteStateVar( va("loadgame_item_%i", fileList.Num()) ); + + guiActive->SetStateString( "loadgame_sel_0", "-1" ); + guiActive->SetStateString( "loadgame_shot", "guis/assets/blankLevelShot" ); + +} + +/* +=============== +idSessionLocal::SetModsMenuGuiVars +=============== +*/ +void idSessionLocal::SetModsMenuGuiVars( void ) { + int i; + idModList *list = fileSystem->ListMods(); + + modsList.SetNum( list->GetNumMods() ); + + // Build the gui list + for ( i = 0; i < list->GetNumMods(); i++ ) { + guiActive->SetStateString( va("modsList_item_%i", i), list->GetDescription( i ) ); + modsList[i] = list->GetMod( i ); + } + guiActive->DeleteStateVar( va("modsList_item_%i", list->GetNumMods()) ); + guiActive->SetStateString( "modsList_sel_0", "-1" ); + + fileSystem->FreeModList( list ); +} + + +/* +=============== +idSessionLocal::SetMainMenuSkin +=============== +*/ +void idSessionLocal::SetMainMenuSkin( void ) { + // skins + idStr str = cvarSystem->GetCVarString( "mod_validSkins" ); + idStr uiSkin = cvarSystem->GetCVarString( "ui_skin" ); + idStr skin; + int skinId = 1; + int count = 1; + while ( str.Length() ) { + int n = str.Find( ";" ); + if ( n >= 0 ) { + skin = str.Left( n ); + str = str.Right( str.Length() - n - 1 ); + } else { + skin = str; + str = ""; + } + if ( skin.Icmp( uiSkin ) == 0 ) { + skinId = count; + } + count++; + } + + for ( int i = 0; i < count; i++ ) { + guiMainMenu->SetStateInt( va( "skin%i", i+1 ), 0 ); + } + guiMainMenu->SetStateInt( va( "skin%i", skinId ), 1 ); +} + +/* +=============== +idSessionLocal::SetPbMenuGuiVars +=============== +*/ +void idSessionLocal::SetPbMenuGuiVars( void ) { +} + +/* +=============== +idSessionLocal::SetMainMenuGuiVars +=============== +*/ +void idSessionLocal::SetMainMenuGuiVars( void ) { + + guiMainMenu->SetStateString( "serverlist_sel_0", "-1" ); + guiMainMenu->SetStateString( "serverlist_selid_0", "-1" ); + + guiMainMenu->SetStateInt( "com_machineSpec", com_machineSpec.GetInteger() ); + + // "inetGame" will hold a hand-typed inet address, which is not archived to a cvar + guiMainMenu->SetStateString( "inetGame", "" ); + + // key bind names + guiMainMenu->SetKeyBindingNames(); + + // flag for in-game menu + if ( mapSpawned ) { + guiMainMenu->SetStateString( "inGame", IsMultiplayer() ? "2" : "1" ); + } else { + guiMainMenu->SetStateString( "inGame", "0" ); + } + + SetCDKeyGuiVars( ); +#ifdef ID_DEMO_BUILD + guiMainMenu->SetStateString( "nightmare", "0" ); +#else + guiMainMenu->SetStateString( "nightmare", cvarSystem->GetCVarBool( "g_nightmare" ) ? "1" : "0" ); +#endif + guiMainMenu->SetStateString( "browser_levelshot", "guis/assets/splash/pdtempa" ); + + SetMainMenuSkin(); + // Mods Menu + SetModsMenuGuiVars(); + + guiMsg->SetStateString( "visible_hasxp", "0" ); + +#if defined( __linux__ ) + guiMainMenu->SetStateString( "driver_prompt", "1" ); +#else + guiMainMenu->SetStateString( "driver_prompt", "0" ); +#endif + + SetPbMenuGuiVars(); +} + +/* +============== +idSessionLocal::HandleSaveGameMenuCommands +============== +*/ +bool idSessionLocal::HandleSaveGameMenuCommand( idCmdArgs &args, int &icmd ) { + + const char *cmd = args.Argv(icmd-1); + + if ( !idStr::Icmp( cmd, "loadGame" ) ) { + int choice = guiActive->State().GetInt("loadgame_sel_0"); + if ( choice >= 0 && choice < loadGameList.Num() ) { + sessLocal.LoadGame( loadGameList[choice] ); + } + return true; + } + + if ( !idStr::Icmp( cmd, "saveGame" ) ) { + const char *saveGameName = guiActive->State().GetString("saveGameName"); + if ( saveGameName && saveGameName[0] ) { + + // First see if the file already exists unless they pass '1' to authorize the overwrite + if ( icmd == args.Argc() || atoi(args.Argv( icmd++ )) == 0 ) { + idStr saveFileName = saveGameName; + sessLocal.ScrubSaveGameFileName( saveFileName ); + saveFileName = "savegames/" + saveFileName; + saveFileName.SetFileExtension(".save"); + + idStr game = cvarSystem->GetCVarString( "fs_game" ); + idFile *file; + if(game.Length()) { + file = fileSystem->OpenFileRead( saveFileName, true, game ); + } else { + file = fileSystem->OpenFileRead( saveFileName ); + } + + if ( file != NULL ) { + fileSystem->CloseFile( file ); + + // The file exists, see if it's an autosave + saveFileName.SetFileExtension(".txt"); + idLexer src(LEXFL_NOERRORS|LEXFL_NOSTRINGCONCAT); + if ( src.LoadFile( saveFileName ) ) { + idToken tok; + src.ReadToken( &tok ); // Name + src.ReadToken( &tok ); // Map + src.ReadToken( &tok ); // Screenshot + if ( !tok.IsEmpty() ) { + // NOTE: base/ gui doesn't handle that one + guiActive->HandleNamedEvent( "autosaveOverwriteError" ); + return true; + } + } + guiActive->HandleNamedEvent( "saveGameOverwrite" ); + return true; + } + } + + sessLocal.SaveGame( saveGameName ); + SetSaveGameGuiVars( ); + guiActive->StateChanged( com_frameTime ); + } + return true; + } + + if ( !idStr::Icmp( cmd, "deleteGame" ) ) { + int choice = guiActive->State().GetInt( "loadgame_sel_0" ); + if ( choice >= 0 && choice < loadGameList.Num() ) { + fileSystem->RemoveFile( va("savegames/%s.save", loadGameList[choice].c_str()) ); + fileSystem->RemoveFile( va("savegames/%s.tga", loadGameList[choice].c_str()) ); + fileSystem->RemoveFile( va("savegames/%s.txt", loadGameList[choice].c_str()) ); + SetSaveGameGuiVars( ); + guiActive->StateChanged( com_frameTime ); + } + return true; + } + + if ( !idStr::Icmp( cmd, "updateSaveGameInfo" ) ) { + int choice = guiActive->State().GetInt( "loadgame_sel_0" ); + if ( choice >= 0 && choice < loadGameList.Num() ) { + const idMaterial *material; + + idStr saveName, description, screenshot; + idLexer src(LEXFL_NOERRORS|LEXFL_NOSTRINGCONCAT); + if ( src.LoadFile( va("savegames/%s.txt", loadGameList[choice].c_str()) ) ) { + idToken tok; + + src.ReadToken( &tok ); + saveName = tok; + + src.ReadToken( &tok ); + description = tok; + + src.ReadToken( &tok ); + screenshot = tok; + + } else { + saveName = loadGameList[choice]; + description = loadGameList[choice]; + screenshot = ""; + } + if ( screenshot.Length() == 0 ) { + screenshot = va("savegames/%s.tga", loadGameList[choice].c_str()); + } + material = declManager->FindMaterial( screenshot ); + if ( material ) { + material->ReloadImages( false ); + } + guiActive->SetStateString( "loadgame_shot", screenshot ); + + saveName.RemoveEscapes(); + guiActive->SetStateString( "saveGameName", saveName ); + guiActive->SetStateString( "saveGameDescription", description ); + + ID_TIME_T timeStamp; + fileSystem->ReadFile( va("savegames/%s.save", loadGameList[choice].c_str()), NULL, &timeStamp ); + idStr date = Sys_TimeStampToStr(timeStamp); + int tab = date.Find( '\t' ); + idStr time = date.Right( date.Length() - tab - 1); + guiActive->SetStateString( "saveGameDate", date.Left( tab ) ); + guiActive->SetStateString( "saveGameTime", time ); + } + return true; + } + + return false; +} + +/* +============== +idSessionLocal::HandleRestartMenuCommands + +Executes any commands returned by the gui +============== +*/ +void idSessionLocal::HandleRestartMenuCommands( const char *menuCommand ) { + // execute the command from the menu + int icmd; + idCmdArgs args; + + args.TokenizeString( menuCommand, false ); + + for( icmd = 0; icmd < args.Argc(); ) { + const char *cmd = args.Argv( icmd++ ); + + if ( HandleSaveGameMenuCommand( args, icmd ) ) { + continue; + } + + if ( !idStr::Icmp( cmd, "restart" ) ) { + if ( !LoadGame( GetAutoSaveName( mapSpawnData.serverInfo.GetString("si_map") ) ) ) { + // If we can't load the autosave then just restart the map + MoveToNewMap( mapSpawnData.serverInfo.GetString("si_map") ); + } + continue; + } + + if ( !idStr::Icmp( cmd, "quit" ) ) { + ExitMenu(); + common->Quit(); + return; + } + + if ( !idStr::Icmp ( cmd, "exec" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, args.Argv( icmd++ ) ); + continue; + } + + if ( !idStr::Icmp( cmd, "play" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr snd = args.Argv(icmd++); + soundSystem->PlayShaderDirectly( sw, snd ); + } + continue; + } + } +} + +/* +============== +idSessionLocal::HandleIntroMenuCommands + +Executes any commands returned by the gui +============== +*/ +void idSessionLocal::HandleIntroMenuCommands( const char *menuCommand ) { + // execute the command from the menu + int i; + idCmdArgs args; + + args.TokenizeString( menuCommand, false ); + + for( i = 0; i < args.Argc(); ) { + const char *cmd = args.Argv( i++ ); + + if ( !idStr::Icmp( cmd, "startGame" ) ) { + soundSystem->StopAllSounds( menuSoundWorld ); + ExitMenu(); + continue; + } + + if ( !idStr::Icmp( cmd, "play" ) ) { + if ( args.Argc() - i >= 1 ) { + idStr snd = args.Argv(i++); + soundSystem->PlayShaderDirectly( menuSoundWorld, snd ); + } + continue; + } + } +} + +/* +============== +idSessionLocal::UpdateMPLevelShot +============== +*/ +void idSessionLocal::UpdateMPLevelShot( void ) { + char screenshot[ MAX_STRING_CHARS ]; + fileSystem->FindMapScreenshot( cvarSystem->GetCVarString( "si_map" ), screenshot, MAX_STRING_CHARS ); + guiMainMenu->SetStateString( "current_levelshot", screenshot ); +} + +/* +============== +idSessionLocal::HandleMainMenuCommands + +Executes any commands returned by the gui +============== +*/ +void idSessionLocal::HandleMainMenuCommands( const char *menuCommand ) { + // execute the command from the menu + int icmd; + idCmdArgs args; + + args.TokenizeString( menuCommand, false ); + + for( icmd = 0; icmd < args.Argc(); ) { + const char *cmd = args.Argv( icmd++ ); + + if ( HandleSaveGameMenuCommand( args, icmd ) ) { + continue; + } + + // always let the game know the command is being run + if ( game ) { + game->HandleMainMenuCommands( cmd, guiActive ); + } + + if ( !idStr::Icmp( cmd, "startGame" ) ) { + cvarSystem->SetCVarInteger( "g_skill", guiMainMenu->State().GetInt( "skill" ) ); + if ( icmd < args.Argc() ) { + StartNewGame( args.Argv( icmd++ ) ); + } else { +#ifndef ID_DEMO_BUILD + StartNewGame( "game/mars_city1" ); +#else + StartNewGame( "game/demo_mars_city1" ); +#endif + } + // need to do this here to make sure com_frameTime is correct or the gui activates with a time that + // is "however long map load took" time in the past + common->GUIFrame( false, false ); + SetGUI( guiIntro, NULL ); + guiIntro->StateChanged( com_frameTime, true ); + // stop playing the game sounds + soundSystem->SetActiveSoundWorld( true ); + + continue; + } + + if ( !idStr::Icmp( cmd, "quit" ) ) { + ExitMenu(); + common->Quit(); + return; + } + + if ( !idStr::Icmp( cmd, "loadMod" ) ) { + int choice = guiActive->State().GetInt( "modsList_sel_0" ); + if ( choice >= 0 && choice < modsList.Num() ) { + cvarSystem->SetCVarString( "fs_game", modsList[ choice ] ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "reloadEngine menu\n" ); + } + } + + if ( !idStr::Icmp( cmd, "UpdateServers" ) ) { + if ( guiActive->State().GetBool( "lanSet" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "LANScan" ); + } else { + idAsyncNetwork::GetNETServers(); + } + continue; + } + + if ( !idStr::Icmp( cmd, "RefreshServers" ) ) { + if ( guiActive->State().GetBool( "lanSet" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "LANScan" ); + } else { + idAsyncNetwork::client.serverList.NetScan( ); + } + continue; + } + + if ( !idStr::Icmp( cmd, "FilterServers" ) ) { + idAsyncNetwork::client.serverList.ApplyFilter( ); + continue; + } + + if ( !idStr::Icmp( cmd, "sortServerName" ) ) { + idAsyncNetwork::client.serverList.SetSorting( SORT_SERVERNAME ); + continue; + } + + if ( !idStr::Icmp( cmd, "sortGame" ) ) { + idAsyncNetwork::client.serverList.SetSorting( SORT_GAME ); + continue; + } + + if ( !idStr::Icmp( cmd, "sortPlayers" ) ) { + idAsyncNetwork::client.serverList.SetSorting( SORT_PLAYERS ); + continue; + } + + if ( !idStr::Icmp( cmd, "sortPing" ) ) { + idAsyncNetwork::client.serverList.SetSorting( SORT_PING ); + continue; + } + + if ( !idStr::Icmp( cmd, "sortGameType" ) ) { + idAsyncNetwork::client.serverList.SetSorting( SORT_GAMETYPE ); + continue; + } + + if ( !idStr::Icmp( cmd, "sortMap" ) ) { + idAsyncNetwork::client.serverList.SetSorting( SORT_MAP ); + continue; + } + + if ( !idStr::Icmp( cmd, "serverList" ) ) { + idAsyncNetwork::client.serverList.GUIUpdateSelected(); + continue; + } + + if ( !idStr::Icmp( cmd, "LANConnect" ) ) { + int sel = guiActive->State().GetInt( "serverList_selid_0" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "Connect %d\n", sel ) ); + return; + } + + if ( !idStr::Icmp( cmd, "MAPScan" ) ) { + const char *gametype = cvarSystem->GetCVarString( "si_gameType" ); + if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) { + gametype = "Deathmatch"; + } + + int i, num; + idStr si_map = cvarSystem->GetCVarString("si_map"); + const idDict *dict; + + guiMainMenu_MapList->Clear(); + guiMainMenu_MapList->SetSelection( 0 ); + num = fileSystem->GetNumMaps(); + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if ( dict && dict->GetBool( gametype ) ) { + const char *mapName = dict->GetString( "name" ); + if ( mapName[ 0 ] == '\0' ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLanguageDict()->GetString( mapName ); + guiMainMenu_MapList->Add( i, mapName ); + if ( !si_map.Icmp( dict->GetString( "path" ) ) ) { + guiMainMenu_MapList->SetSelection( guiMainMenu_MapList->Num() - 1 ); + } + } + } + i = guiMainMenu_MapList->GetSelection( NULL, 0 ); + if ( i >= 0 ) { + dict = fileSystem->GetMapDecl( i); + } else { + dict = NULL; + } + cvarSystem->SetCVarString( "si_map", ( dict ? dict->GetString( "path" ) : "" ) ); + + // set the current level shot + UpdateMPLevelShot(); + continue; + } + + if ( !idStr::Icmp( cmd, "click_mapList" ) ) { + int mapNum = guiMainMenu_MapList->GetSelection( NULL, 0 ); + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + if ( dict ) { + cvarSystem->SetCVarString( "si_map", dict->GetString( "path" ) ); + } + UpdateMPLevelShot(); + continue; + } + + if ( !idStr::Icmp( cmd, "inetConnect" ) ) { + const char *s = guiMainMenu->State().GetString( "inetGame" ); + + if ( !s || s[0] == 0 ) { + // don't put the menu away if there isn't a valid selection + continue; + } + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "connect %s", s ) ); + return; + } + + if ( !idStr::Icmp( cmd, "startMultiplayer" ) ) { + int dedicated = guiActive->State().GetInt( "dedicated" ); + cvarSystem->SetCVarBool( "net_LANServer", guiActive->State().GetBool( "server_type" ) ); + if ( gui_configServerRate.GetInteger() > 0 ) { + // guess the best rate for upstream, number of internet clients + if ( gui_configServerRate.GetInteger() == 5 || cvarSystem->GetCVarBool( "net_LANServer" ) ) { + cvarSystem->SetCVarInteger( "net_serverMaxClientRate", 25600 ); + } else { + // internet players + int n_clients = cvarSystem->GetCVarInteger( "si_maxPlayers" ); + if ( !dedicated ) { + n_clients--; + } + int maxclients = 0; + switch ( gui_configServerRate.GetInteger() ) { + case 1: + // 128 kbits + cvarSystem->SetCVarInteger( "net_serverMaxClientRate", 8000 ); + maxclients = 2; + break; + case 2: + // 256 kbits + cvarSystem->SetCVarInteger( "net_serverMaxClientRate", 9500 ); + maxclients = 3; + break; + case 3: + // 384 kbits + cvarSystem->SetCVarInteger( "net_serverMaxClientRate", 10500 ); + maxclients = 4; + break; + case 4: + // 512 and above.. + cvarSystem->SetCVarInteger( "net_serverMaxClientRate", 14000 ); + maxclients = 4; + break; + } + if ( n_clients > maxclients ) { + if ( MessageBox( MSG_OKCANCEL, va( common->GetLanguageDict()->GetString( "#str_04315" ), dedicated ? maxclients : Min( 8, maxclients + 1 ) ), common->GetLanguageDict()->GetString( "#str_04316" ), true, "OK" )[ 0 ] == '\0' ) { + continue; + } + cvarSystem->SetCVarInteger( "si_maxPlayers", dedicated ? maxclients : Min( 8, maxclients + 1 ) ); + } + } + } + + if ( !dedicated && !cvarSystem->GetCVarBool( "net_LANServer" ) && cvarSystem->GetCVarInteger("si_maxPlayers") > 4 ) { + // "Dedicated server mode is recommended for internet servers with more than 4 players. Continue in listen mode?" + if ( !MessageBox( MSG_YESNO, common->GetLanguageDict()->GetString ( "#str_00100625" ), common->GetLanguageDict()->GetString ( "#str_00100626" ), true, "yes" )[ 0 ] ) { + continue; + } + } + + if ( dedicated ) { + cvarSystem->SetCVarInteger( "net_serverDedicated", 1 ); + } else { + cvarSystem->SetCVarInteger( "net_serverDedicated", 0 ); + } + + + + ExitMenu(); + // may trigger a reloadEngine - APPEND + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "SpawnServer\n" ); + return; + } + + if ( !idStr::Icmp( cmd, "mpSkin")) { + idStr skin; + if ( args.Argc() - icmd >= 1 ) { + skin = args.Argv( icmd++ ); + cvarSystem->SetCVarString( "ui_skin", skin ); + SetMainMenuSkin(); + } + continue; + } + + if ( !idStr::Icmp( cmd, "close" ) ) { + // if we aren't in a game, the menu can't be closed + if ( mapSpawned ) { + ExitMenu(); + } + continue; + } + + if ( !idStr::Icmp( cmd, "resetdefaults" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "exec default.cfg" ); + guiMainMenu->SetKeyBindingNames(); + continue; + } + + + if ( !idStr::Icmp( cmd, "bind" ) ) { + if ( args.Argc() - icmd >= 2 ) { + int key = atoi( args.Argv( icmd++ ) ); + idStr bind = args.Argv( icmd++ ); + if ( idKeyInput::NumBinds( bind ) >= 2 && !idKeyInput::KeyIsBoundTo( key, bind ) ) { + idKeyInput::UnbindBinding( bind ); + } + idKeyInput::SetBinding( key, bind ); + guiMainMenu->SetKeyBindingNames(); + } + continue; + } + + if ( !idStr::Icmp( cmd, "play" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr snd = args.Argv( icmd++ ); + int channel = 1; + if ( snd.Length() == 1 ) { + channel = atoi( snd ); + snd = args.Argv( icmd++ ); + } + soundSystem->PlayShaderDirectly( menuSoundWorld, snd, channel ); + + } + continue; + } + + if ( !idStr::Icmp( cmd, "music" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr snd = args.Argv( icmd++ ); + soundSystem->PlayShaderDirectly( menuSoundWorld, snd, 2 ); + } + continue; + } + + // triggered from mainmenu or mpmain + if ( !idStr::Icmp( cmd, "sound" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + if ( !vcmd.Length() || !vcmd.Icmp( "speakers" ) ) { + int old = cvarSystem->GetCVarInteger( "s_numberOfSpeakers" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "s_restart\n" ); + if ( old != cvarSystem->GetCVarInteger( "s_numberOfSpeakers" ) ) { +#ifdef _WIN32 + MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_04142" ), common->GetLanguageDict()->GetString( "#str_04141" ), true ); +#else + // a message that doesn't mention the windows control panel + MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07230" ), common->GetLanguageDict()->GetString( "#str_04141" ), true ); +#endif + } + } + if ( !vcmd.Icmp( "eax" ) ) { + if ( cvarSystem->GetCVarBool( "s_useEAXReverb" ) ) { + int eax = soundSystem->IsEAXAvailable(); + switch ( eax ) { + case 2: + // OpenAL subsystem load failed + MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07238" ), common->GetLanguageDict()->GetString( "#str_07231" ), true ); + break; + case 1: + // when you restart + MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_04137" ), common->GetLanguageDict()->GetString( "#str_07231" ), true ); + break; + case -1: + cvarSystem->SetCVarBool( "s_useEAXReverb", false ); + // disabled + MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07233" ), common->GetLanguageDict()->GetString( "#str_07231" ), true ); + break; + case 0: + cvarSystem->SetCVarBool( "s_useEAXReverb", false ); + // not available + MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_07232" ), common->GetLanguageDict()->GetString( "#str_07231" ), true ); + break; + } + } else { + // also turn off OpenAL so we fully go back to legacy mixer + cvarSystem->SetCVarBool( "s_useOpenAL", false ); + // when you restart + MessageBox( MSG_OK, common->GetLanguageDict()->GetString( "#str_04137" ), common->GetLanguageDict()->GetString( "#str_07231" ), true ); + } + } + if ( !vcmd.Icmp( "drivar" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "s_restart\n" ); + } + continue; + } + + if ( !idStr::Icmp( cmd, "video" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + int oldSpec = com_machineSpec.GetInteger(); + + if ( idStr::Icmp( vcmd, "low" ) == 0 ) { + com_machineSpec.SetInteger( 0 ); + } else if ( idStr::Icmp( vcmd, "medium" ) == 0 ) { + com_machineSpec.SetInteger( 1 ); + } else if ( idStr::Icmp( vcmd, "high" ) == 0 ) { + com_machineSpec.SetInteger( 2 ); + } else if ( idStr::Icmp( vcmd, "ultra" ) == 0 ) { + com_machineSpec.SetInteger( 3 ); + } else if ( idStr::Icmp( vcmd, "recommended" ) == 0 ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" ); + } + + if ( oldSpec != com_machineSpec.GetInteger() ) { + guiActive->SetStateInt( "com_machineSpec", com_machineSpec.GetInteger() ); + guiActive->StateChanged( com_frameTime ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "execMachineSpec\n" ); + } + + if ( idStr::Icmp( vcmd, "restart" ) == 0) { + guiActive->HandleNamedEvent( "cvar write render" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart\n" ); + } + + continue; + } + + if ( !idStr::Icmp( cmd, "clearBind" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idKeyInput::UnbindBinding( args.Argv( icmd++ ) ); + guiMainMenu->SetKeyBindingNames(); + } + continue; + } + + // FIXME: obsolete + if ( !idStr::Icmp( cmd, "chatdone" ) ) { + idStr temp = guiActive->State().GetString( "chattext" ); + temp += "\r"; + guiActive->SetStateString( "chattext", "" ); + continue; + } + + if ( !idStr::Icmp ( cmd, "exec" ) ) { + + //Backup the language so we can restore it after defaults. + idStr lang = cvarSystem->GetCVarString("sys_lang"); + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, args.Argv( icmd++ ) ); + if ( idStr::Icmp( "cvar_restart", args.Argv( icmd - 1 ) ) == 0 ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "exec default.cfg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" ); + + //Make sure that any r_brightness changes take effect + float bright = cvarSystem->GetCVarFloat("r_brightness"); + cvarSystem->SetCVarFloat("r_brightness", 0.0f); + cvarSystem->SetCVarFloat("r_brightness", bright); + + //Force user info modified after a reset to defaults + cvarSystem->SetModifiedFlags(CVAR_USERINFO); + + guiActive->SetStateInt( "com_machineSpec", com_machineSpec.GetInteger() ); + + //Restore the language + cvarSystem->SetCVarString("sys_lang", lang); + + } + continue; + } + + if ( !idStr::Icmp ( cmd, "loadBinds" ) ) { + guiMainMenu->SetKeyBindingNames(); + continue; + } + + if ( !idStr::Icmp( cmd, "systemCvars" ) ) { + guiActive->HandleNamedEvent( "cvar read render" ); + guiActive->HandleNamedEvent( "cvar read sound" ); + continue; + } + + if ( !idStr::Icmp( cmd, "SetCDKey" ) ) { + // we can't do this from inside the HandleMainMenuCommands code, otherwise the message box stuff gets confused + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "promptKey\n" ); + continue; + } + + if ( !idStr::Icmp( cmd, "CheckUpdate" ) ) { + idAsyncNetwork::client.SendVersionCheck(); + continue; + } + + if ( !idStr::Icmp( cmd, "CheckUpdate2" ) ) { + idAsyncNetwork::client.SendVersionCheck( true ); + continue; + } + + if ( !idStr::Icmp( cmd, "checkKeys" ) ) { +#if ID_ENFORCE_KEY + // not a strict check so you silently auth in the background without bugging the user + if ( !session->CDKeysAreValid( false ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "promptKey force" ); + cmdSystem->ExecuteCommandBuffer(); + } +#endif + continue; + } + + // triggered from mainmenu or mpmain + if ( !idStr::Icmp( cmd, "punkbuster" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + // filtering PB based on enabled/disabled + idAsyncNetwork::client.serverList.ApplyFilter( ); + SetPbMenuGuiVars(); + continue; + } + } +} + +/* +============== +idSessionLocal::HandleChatMenuCommands + +Executes any commands returned by the gui +============== +*/ +void idSessionLocal::HandleChatMenuCommands( const char *menuCommand ) { + // execute the command from the menu + int i; + idCmdArgs args; + + args.TokenizeString( menuCommand, false ); + + for ( i = 0; i < args.Argc(); ) { + const char *cmd = args.Argv( i++ ); + + if ( idStr::Icmp( cmd, "chatactive" ) == 0 ) { + //chat.chatMode = CHAT_GLOBAL; + continue; + } + if ( idStr::Icmp( cmd, "chatabort" ) == 0 ) { + //chat.chatMode = CHAT_NONE; + continue; + } + if ( idStr::Icmp( cmd, "netready" ) == 0 ) { + bool b = cvarSystem->GetCVarBool( "ui_ready" ); + cvarSystem->SetCVarBool( "ui_ready", !b ); + continue; + } + if ( idStr::Icmp( cmd, "netstart" ) == 0 ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "netcommand start\n" ); + continue; + } + } +} + +/* +============== +idSessionLocal::HandleInGameCommands + +Executes any commands returned by the gui +============== +*/ +void idSessionLocal::HandleInGameCommands( const char *menuCommand ) { + // execute the command from the menu + idCmdArgs args; + + args.TokenizeString( menuCommand, false ); + + const char *cmd = args.Argv( 0 ); + if ( !idStr::Icmp( cmd, "close" ) ) { + if ( guiActive ) { + sysEvent_t ev; + ev.evType = SE_NONE; + const char *cmd; + cmd = guiActive->HandleEvent( &ev, com_frameTime ); + guiActive->Activate( false, com_frameTime ); + guiActive = NULL; + } + } +} + +/* +============== +idSessionLocal::DispatchCommand +============== +*/ +void idSessionLocal::DispatchCommand( idUserInterface *gui, const char *menuCommand, bool doIngame ) { + + if ( !gui ) { + gui = guiActive; + } + + if ( gui == guiMainMenu ) { + HandleMainMenuCommands( menuCommand ); + return; + } else if ( gui == guiIntro) { + HandleIntroMenuCommands( menuCommand ); + } else if ( gui == guiMsg ) { + HandleMsgCommands( menuCommand ); + } else if ( gui == guiTakeNotes ) { + HandleNoteCommands( menuCommand ); + } else if ( gui == guiRestartMenu ) { + HandleRestartMenuCommands( menuCommand ); + } else if ( game && guiActive && guiActive->State().GetBool( "gameDraw" ) ) { + const char *cmd = game->HandleGuiCommands( menuCommand ); + if ( !cmd ) { + guiActive = NULL; + } else if ( idStr::Icmp( cmd, "main" ) == 0 ) { + StartMenu(); + } else if ( strstr( cmd, "sound " ) == cmd ) { + // pipe the GUI sound commands not handled by the game to the main menu code + HandleMainMenuCommands( cmd ); + } + } else if ( guiHandle ) { + if ( (*guiHandle)( menuCommand ) ) { + return; + } + } else if ( !doIngame ) { + common->DPrintf( "idSessionLocal::DispatchCommand: no dispatch found for command '%s'\n", menuCommand ); + } + + if ( doIngame ) { + HandleInGameCommands( menuCommand ); + } +} + + +/* +============== +idSessionLocal::MenuEvent + +Executes any commands returned by the gui +============== +*/ +void idSessionLocal::MenuEvent( const sysEvent_t *event ) { + const char *menuCommand; + + if ( guiActive == NULL ) { + return; + } + + menuCommand = guiActive->HandleEvent( event, com_frameTime ); + + if ( !menuCommand || !menuCommand[0] ) { + // If the menu didn't handle the event, and it's a key down event for an F key, run the bind + if ( event->evType == SE_KEY && event->evValue2 == 1 && event->evValue >= K_F1 && event->evValue <= K_F12 ) { + idKeyInput::ExecKeyBinding( event->evValue ); + } + return; + } + + DispatchCommand( guiActive, menuCommand ); +} + +/* +================= +idSessionLocal::GuiFrameEvents +================= +*/ +void idSessionLocal::GuiFrameEvents() { + const char *cmd; + sysEvent_t ev; + idUserInterface *gui; + + // stop generating move and button commands when a local console or menu is active + // running here so SP, async networking and no game all go through it + if ( console->Active() || guiActive ) { + usercmdGen->InhibitUsercmd( INHIBIT_SESSION, true ); + } else { + usercmdGen->InhibitUsercmd( INHIBIT_SESSION, false ); + } + + if ( guiTest ) { + gui = guiTest; + } else if ( guiActive ) { + gui = guiActive; + } else { + return; + } + + memset( &ev, 0, sizeof( ev ) ); + + ev.evType = SE_NONE; + cmd = gui->HandleEvent( &ev, com_frameTime ); + if ( cmd && cmd[0] ) { + DispatchCommand( guiActive, cmd ); + } +} + +/* +================= +idSessionLocal::BoxDialogSanityCheck +================= +*/ +bool idSessionLocal::BoxDialogSanityCheck( void ) { + if ( !common->IsInitialized() ) { + common->DPrintf( "message box sanity check: !common->IsInitialized()\n" ); + return false; + } + if ( !guiMsg ) { + return false; + } + if ( guiMsgRestore ) { + common->DPrintf( "message box sanity check: recursed\n" ); + return false; + } + if ( cvarSystem->GetCVarInteger( "net_serverDedicated" ) ) { + common->DPrintf( "message box sanity check: not compatible with dedicated server\n" ); + return false; + } + return true; +} + +/* +================= +idSessionLocal::MessageBox +================= +*/ +const char* idSessionLocal::MessageBox( msgBoxType_t type, const char *message, const char *title, bool wait, const char *fire_yes, const char *fire_no, bool network ) { + + common->DPrintf( "MessageBox: %s - %s\n", title ? title : "", message ? message : "" ); + + if ( !BoxDialogSanityCheck() ) { + return NULL; + } + + guiMsg->SetStateString( "title", title ? title : "" ); + guiMsg->SetStateString( "message", message ? message : "" ); + if ( type == MSG_WAIT ) { + guiMsg->SetStateString( "visible_msgbox", "0" ); + guiMsg->SetStateString( "visible_waitbox", "1" ); + } else { + guiMsg->SetStateString( "visible_msgbox", "1" ); + guiMsg->SetStateString( "visible_waitbox", "0" ); + } + + guiMsg->SetStateString( "visible_entry", "0" ); + guiMsg->SetStateString( "visible_cdkey", "0" ); + switch ( type ) { + case MSG_INFO: + guiMsg->SetStateString( "mid", "" ); + guiMsg->SetStateString( "visible_mid", "0" ); + guiMsg->SetStateString( "visible_left", "0" ); + guiMsg->SetStateString( "visible_right", "0" ); + break; + case MSG_OK: + guiMsg->SetStateString( "mid", common->GetLanguageDict()->GetString( "#str_04339" ) ); + guiMsg->SetStateString( "visible_mid", "1" ); + guiMsg->SetStateString( "visible_left", "0" ); + guiMsg->SetStateString( "visible_right", "0" ); + break; + case MSG_ABORT: + guiMsg->SetStateString( "mid", common->GetLanguageDict()->GetString( "#str_04340" ) ); + guiMsg->SetStateString( "visible_mid", "1" ); + guiMsg->SetStateString( "visible_left", "0" ); + guiMsg->SetStateString( "visible_right", "0" ); + break; + case MSG_OKCANCEL: + guiMsg->SetStateString( "left", common->GetLanguageDict()->GetString( "#str_04339" ) ); + guiMsg->SetStateString( "right", common->GetLanguageDict()->GetString( "#str_04340" ) ); + guiMsg->SetStateString( "visible_mid", "0" ); + guiMsg->SetStateString( "visible_left", "1" ); + guiMsg->SetStateString( "visible_right", "1" ); + break; + case MSG_YESNO: + guiMsg->SetStateString( "left", common->GetLanguageDict()->GetString( "#str_04341" ) ); + guiMsg->SetStateString( "right", common->GetLanguageDict()->GetString( "#str_04342" ) ); + guiMsg->SetStateString( "visible_mid", "0" ); + guiMsg->SetStateString( "visible_left", "1" ); + guiMsg->SetStateString( "visible_right", "1" ); + break; + case MSG_PROMPT: + guiMsg->SetStateString( "left", common->GetLanguageDict()->GetString( "#str_04339" ) ); + guiMsg->SetStateString( "right", common->GetLanguageDict()->GetString( "#str_04340" ) ); + guiMsg->SetStateString( "visible_mid", "0" ); + guiMsg->SetStateString( "visible_left", "1" ); + guiMsg->SetStateString( "visible_right", "1" ); + guiMsg->SetStateString( "visible_entry", "1" ); + guiMsg->HandleNamedEvent( "Prompt" ); + break; + case MSG_CDKEY: + guiMsg->SetStateString( "left", common->GetLanguageDict()->GetString( "#str_04339" ) ); + guiMsg->SetStateString( "right", common->GetLanguageDict()->GetString( "#str_04340" ) ); + guiMsg->SetStateString( "visible_msgbox", "0" ); + guiMsg->SetStateString( "visible_cdkey", "1" ); + guiMsg->SetStateString( "visible_hasxp", "0" ); + // the current cdkey / xpkey values may have bad/random data in them + // it's best to avoid printing them completely, unless the key is good + if ( cdkey_state == CDKEY_OK ) { + guiMsg->SetStateString( "str_cdkey", cdkey ); + guiMsg->SetStateString( "visible_cdchk", "0" ); + } else { + guiMsg->SetStateString( "str_cdkey", "" ); + guiMsg->SetStateString( "visible_cdchk", "1" ); + } + guiMsg->SetStateString( "str_cdchk", "" ); + if ( xpkey_state == CDKEY_OK ) { + guiMsg->SetStateString( "str_xpkey", xpkey ); + guiMsg->SetStateString( "visible_xpchk", "0" ); + } else { + guiMsg->SetStateString( "str_xpkey", "" ); + guiMsg->SetStateString( "visible_xpchk", "1" ); + } + guiMsg->SetStateString( "str_xpchk", "" ); + guiMsg->HandleNamedEvent( "CDKey" ); + break; + case MSG_WAIT: + break; + default: + common->Printf( "idSessionLocal::MessageBox: unknown msg box type\n" ); + } + msgFireBack[ 0 ] = fire_yes ? fire_yes : ""; + msgFireBack[ 1 ] = fire_no ? fire_no : ""; + guiMsgRestore = guiActive; + guiActive = guiMsg; + guiMsg->SetCursor( 325, 290 ); + guiActive->Activate( true, com_frameTime ); + msgRunning = true; + msgRetIndex = -1; + + if ( wait ) { + // play one frame ignoring events so we don't get confused by parasite button releases + msgIgnoreButtons = true; + common->GUIFrame( true, network ); + msgIgnoreButtons = false; + while ( msgRunning ) { + common->GUIFrame( true, network ); + } + if ( msgRetIndex < 0 ) { + // MSG_WAIT and other StopBox calls + return NULL; + } + if ( type == MSG_PROMPT ) { + if ( msgRetIndex == 0 ) { + guiMsg->State().GetString( "str_entry", "", msgFireBack[ 0 ] ); + return msgFireBack[ 0 ].c_str(); + } else { + return NULL; + } + } else if ( type == MSG_CDKEY ) { + if ( msgRetIndex == 0 ) { + // the visible_ values distinguish looking at a valid key, or editing it + sprintf( msgFireBack[ 0 ], "%1s;%16s;%2s;%1s;%16s;%2s", + guiMsg->State().GetString( "visible_cdchk" ), + guiMsg->State().GetString( "str_cdkey" ), + guiMsg->State().GetString( "str_cdchk" ), + guiMsg->State().GetString( "visible_xpchk" ), + guiMsg->State().GetString( "str_xpkey" ), + guiMsg->State().GetString( "str_xpchk" ) ); + return msgFireBack[ 0 ].c_str(); + } else { + return NULL; + } + } else { + return msgFireBack[ msgRetIndex ].c_str(); + } + } + return NULL; +} + +/* +================= +idSessionLocal::DownloadProgressBox +================= +*/ +void idSessionLocal::DownloadProgressBox( backgroundDownload_t *bgl, const char *title, int progress_start, int progress_end ) { + int dlnow = 0, dltotal = 0; + int startTime = Sys_Milliseconds(); + int lapsed; + idStr sNow, sTotal, sBW, sETA, sMsg; + + if ( !BoxDialogSanityCheck() ) { + return; + } + + guiMsg->SetStateString( "visible_msgbox", "1" ); + guiMsg->SetStateString( "visible_waitbox", "0" ); + + guiMsg->SetStateString( "visible_entry", "0" ); + guiMsg->SetStateString( "visible_cdkey", "0" ); + + guiMsg->SetStateString( "mid", "Cancel" ); + guiMsg->SetStateString( "visible_mid", "1" ); + guiMsg->SetStateString( "visible_left", "0" ); + guiMsg->SetStateString( "visible_right", "0" ); + + guiMsg->SetStateString( "title", title ); + guiMsg->SetStateString( "message", "Connecting.." ); + + guiMsgRestore = guiActive; + guiActive = guiMsg; + msgRunning = true; + + while ( 1 ) { + while ( msgRunning ) { + common->GUIFrame( true, false ); + if ( bgl->completed ) { + guiActive = guiMsgRestore; + guiMsgRestore = NULL; + return; + } else if ( bgl->url.dltotal != dltotal || bgl->url.dlnow != dlnow ) { + dltotal = bgl->url.dltotal; + dlnow = bgl->url.dlnow; + lapsed = Sys_Milliseconds() - startTime; + sNow.BestUnit( "%.2f", dlnow, MEASURE_SIZE ); + if ( lapsed > 2000 ) { + sBW.BestUnit( "%.1f", ( 1000.0f * dlnow ) / lapsed, MEASURE_BANDWIDTH ); + } else { + sBW = "-- KB/s"; + } + if ( dltotal ) { + sTotal.BestUnit( "%.2f", dltotal, MEASURE_SIZE ); + if ( lapsed < 2000 ) { + sprintf( sMsg, "%s / %s", sNow.c_str(), sTotal.c_str() ); + } else { + sprintf( sETA, "%.0f sec", ( (float)dltotal / (float)dlnow - 1.0f ) * lapsed / 1000 ); + sprintf( sMsg, "%s / %s ( %s - %s )", sNow.c_str(), sTotal.c_str(), sBW.c_str(), sETA.c_str() ); + } + } else { + if ( lapsed < 2000 ) { + sMsg = sNow; + } else { + sprintf( sMsg, "%s - %s", sNow.c_str(), sBW.c_str() ); + } + } + if ( dltotal ) { + guiMsg->SetStateString( "progress", va( "%d", progress_start + dlnow * ( progress_end - progress_start ) / dltotal ) ); + } else { + guiMsg->SetStateString( "progress", "0" ); + } + guiMsg->SetStateString( "message", sMsg.c_str() ); + } + } + // abort was used - tell the downloader and wait till final stop + bgl->url.status = DL_ABORTING; + guiMsg->SetStateString( "title", "Aborting.." ); + guiMsg->SetStateString( "visible_mid", "0" ); + // continue looping + guiMsgRestore = guiActive; + guiActive = guiMsg; + msgRunning = true; + } +} + +/* +================= +idSessionLocal::StopBox +================= +*/ +void idSessionLocal::StopBox() { + if ( guiActive == guiMsg ) { + HandleMsgCommands( "stop" ); + } +} + +/* +================= +idSessionLocal::HandleMsgCommands +================= +*/ +void idSessionLocal::HandleMsgCommands( const char *menuCommand ) { + assert( guiActive == guiMsg ); + // "stop" works even on first frame + if ( idStr::Icmp( menuCommand, "stop" ) == 0 ) { + // force hiding the current dialog + guiActive = guiMsgRestore; + guiMsgRestore = NULL; + msgRunning = false; + msgRetIndex = -1; + } + if ( msgIgnoreButtons ) { + common->DPrintf( "MessageBox HandleMsgCommands 1st frame ignore\n" ); + return; + } + if ( idStr::Icmp( menuCommand, "mid" ) == 0 || idStr::Icmp( menuCommand, "left" ) == 0 ) { + guiActive = guiMsgRestore; + guiMsgRestore = NULL; + msgRunning = false; + msgRetIndex = 0; + DispatchCommand( guiActive, msgFireBack[ 0 ].c_str() ); + } else if ( idStr::Icmp( menuCommand, "right" ) == 0 ) { + guiActive = guiMsgRestore; + guiMsgRestore = NULL; + msgRunning = false; + msgRetIndex = 1; + DispatchCommand( guiActive, msgFireBack[ 1 ].c_str() ); + } +} + +/* +================= +idSessionLocal::HandleNoteCommands +================= +*/ +#define NOTEDATFILE "C:/notenumber.dat" + +void idSessionLocal::HandleNoteCommands( const char *menuCommand ) { + guiActive = NULL; + + if ( idStr::Icmp( menuCommand, "note" ) == 0 && mapSpawned ) { + + idFile *file = NULL; + for ( int tries = 0; tries < 10; tries++ ) { + file = fileSystem->OpenExplicitFileRead( NOTEDATFILE ); + if ( file != NULL ) { + break; + } + Sys_Sleep( 500 ); + } + int noteNumber = 1000; + if ( file ) { + file->Read( ¬eNumber, 4 ); + fileSystem->CloseFile( file ); + } + + int i; + idStr str, noteNum, shotName, workName, fileName = "viewnotes/"; + idStrList fileList; + + const char *severity = NULL; + const char *p = guiTakeNotes->State().GetString( "notefile" ); + if ( p == NULL || *p == '\0' ) { + p = cvarSystem->GetCVarString( "ui_name" ); + } + + bool extended = guiTakeNotes->State().GetBool( "extended" ); + if ( extended ) { + if ( guiTakeNotes->State().GetInt( "severity" ) == 1 ) { + severity = "WishList_Viewnotes/"; + } else { + severity = "MustFix_Viewnotes/"; + } + fileName += severity; + + const idDecl *mapDecl = declManager->FindType(DECL_ENTITYDEF, mapSpawnData.serverInfo.GetString( "si_map" ), false ); + const idDeclEntityDef *mapInfo = static_cast(mapDecl); + + if ( mapInfo ) { + fileName += mapInfo->dict.GetString( "devname" ); + } else { + fileName += mapSpawnData.serverInfo.GetString( "si_map" ); + fileName.StripFileExtension(); + } + + int count = guiTakeNotes->State().GetInt( "person_numsel" ); + if ( count == 0 ) { + fileList.Append( fileName + "/Nobody" ); + } else { + for ( i = 0; i < count; i++ ) { + int person = guiTakeNotes->State().GetInt( va( "person_sel_%i", i ) ); + workName = fileName + "/"; + workName += guiTakeNotes->State().GetString( va( "person_item_%i", person ), "Nobody" ); + fileList.Append( workName ); + } + } + } else { + fileName += "maps/"; + fileName += mapSpawnData.serverInfo.GetString( "si_map" ); + fileName.StripFileExtension(); + fileList.Append( fileName ); + } + + bool bCon = cvarSystem->GetCVarBool( "con_noPrint" ); + cvarSystem->SetCVarBool( "con_noPrint", true ); + for ( i = 0; i < fileList.Num(); i++ ) { + workName = fileList[i]; + workName += "/"; + workName += p; + int workNote = noteNumber; + R_ScreenshotFilename( workNote, workName, ".tga", shotName ); + + noteNum = shotName; + noteNum.StripPath(); + noteNum.StripFileExtension(); + + if ( severity && *severity ) { + workName = severity; + workName += "viewNotes"; + } + + sprintf( str, "recordViewNotes \"%s\" \"%s\" \"%s\"\n", workName.c_str(), noteNum.c_str(), guiTakeNotes->State().GetString( "note" ) ); + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, str ); + cmdSystem->ExecuteCommandBuffer(); + + UpdateScreen(); + renderSystem->TakeScreenshot( renderSystem->GetScreenWidth(), renderSystem->GetScreenHeight(), shotName, 1, NULL ); + } + noteNumber++; + + for ( int tries = 0; tries < 10; tries++ ) { + file = fileSystem->OpenExplicitFileWrite( "p:/viewnotes/notenumber.dat" ); + if ( file != NULL ) { + break; + } + Sys_Sleep( 500 ); + } + if ( file ) { + file->Write( ¬eNumber, 4 ); + fileSystem->CloseFile( file ); + } + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "closeViewNotes\n" ); + cvarSystem->SetCVarBool( "con_noPrint", bCon ); + } +} + +/* +=============== +idSessionLocal::SetCDKeyGuiVars +=============== +*/ +void idSessionLocal::SetCDKeyGuiVars( void ) { + if ( !guiMainMenu ) { + return; + } + guiMainMenu->SetStateString( "str_d3key_state", common->GetLanguageDict()->GetString( va( "#str_071%d", 86 + cdkey_state ) ) ); + guiMainMenu->SetStateString( "str_xpkey_state", common->GetLanguageDict()->GetString( va( "#str_071%d", 86 + xpkey_state ) ) ); +} diff --git a/src/framework/unzip.cpp b/src/framework/unzip.cpp new file mode 100644 index 0000000..84ac974 --- /dev/null +++ b/src/framework/unzip.cpp @@ -0,0 +1,4494 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Unzip.h" + +/* unzip.h -- IO for uncompress .zip files using zlib + Version 0.15 beta, Mar 19th, 1998, + + Copyright (C) 1998 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Encryption and multi volume ZipFile (span) are not supported. + Old compressions used by old PKZip 1.x are not supported + + THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE + CAN CHANGE IN FUTURE VERSION !! + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ +/* for more info about .ZIP format, see + ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip */ + +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.1.3, July 9th, 1998 + + Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: unzip.c,v 1.2 1999/09/07 20:51:25 zoid Exp $ */ + +#ifndef _ZCONF_H +#define _ZCONF_H + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +#define OF(args) args +#endif + +typedef unsigned char Byte; /* 8 bits */ +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ +typedef Byte *voidp; + +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#endif /* _ZCONF_H */ + +#define ZLIB_VERSION "1.1.3" + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +/* Allowed flush values; see deflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_ASCII 1 +#define Z_UNKNOWN 2 +/* Possible values of the data_type field */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +const char * zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +int deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +int deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + the compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + 0.1% larger than avail_in plus 12 bytes. If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so (that is, total_in bytes). + + deflate() may update data_type if it can make a good guess about + the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). +*/ + + +int deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +int inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +int inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may some + introduce some output latency (reading input without producing any output) + except when forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much + output as possible to the output buffer. The flushing behavior of inflate is + not specified for values of the flush parameter other than Z_SYNC_FLUSH + and Z_FINISH, but the current implementation actually flushes as much output + as possible anyway. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster routine + may be used for the single inflate() call. + + If a preset dictionary is needed at this point (see inflateSetDictionary + below), inflate sets strm-adler to the adler32 checksum of the + dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise + it sets strm->adler to the adler32 checksum of all output produced + so (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or + an error code as described below. At the end of the stream, inflate() + checks that its computed adler32 checksum is equal to that saved by the + compressor and returns Z_STREAM_END only if the checksum is correct. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect + adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent + (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if no progress is possible or if there was not + enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR + case, the application may then call inflateSync to look for a good + compression block. +*/ + + +int inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +int deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match). Filtered data consists mostly of small values with a + somewhat random distribution. In this case, the compression algorithm is + tuned to compress them better. The effect of Z_FILTERED is to force more + Huffman coding and less string matching; it is somewhat intermediate + between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects + the compression ratio but not the correctness of the compressed output even + if it is not set appropriately. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +int deflateSetDictionary OF((z_streamp strm, + const Byte *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. + + Upon return of this function, strm->adler is set to the Adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The Adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +int deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +int deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +int deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +/* +int inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. If a compressed stream with a larger window size is given as + input, inflate() will return with the error code Z_DATA_ERROR instead of + trying to allocate a larger window. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative + memLevel). msg is set to null if there is no error message. inflateInit2 + does not perform any decompression apart from reading the zlib header if + present: this will be done by inflate(). (So next_in and avail_in may be + modified, but next_out and avail_out are unchanged.) +*/ + +int inflateSetDictionary OF((z_streamp strm, + const Byte *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate + if this call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the Adler32 value returned by this call of + inflate. The compressor and decompressor must use exactly the same + dictionary (see deflateSetDictionary). + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect Adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +int inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +int inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +int compress OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least 0.1% larger than + sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +int compress2 OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +int uncompress OF((Byte *dest, uLong *destLen, + const Byte *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ + + +typedef voidp gzFile; + +gzFile gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h". (See the description + of deflateInit2 for more information about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +gzFile gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +int gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +int gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +int gzwrite OF((gzFile file, + const voidp buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +int gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ + +int gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +char * gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +int gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +int gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +int gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +long gzseek OF((gzFile file, + long offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +int gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +long gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +int gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +int gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +const char * gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +uLong adler32 OF((uLong adler, const Byte *buf, uInt len)); + +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +uLong crc32 OF((uLong crc, const Byte *buf, uInt len)); +/* + Update a running crc with the bytes buf[0..len-1] and return the updated + crc. If buf is NULL, this function returns the required initial value + for the crc. Pre- and post-conditioning (one's complement) is performed + within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +int deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +int inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +int deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +int inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + + +const char * zError OF((int err)); +int inflateSyncPoint OF((z_streamp z)); +const uLong * get_crc_table OF((void)); + +typedef unsigned char uch; +typedef unsigned short ush; +typedef unsigned long ulg; + +extern const char *z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#ifdef HAVE_STRERROR + extern char *strerror OF((int)); +# define zstrerror(errnum) strerror(errnum) +#else +# define zstrerror(errnum) "" +#endif + +#define zmemcpy memcpy +#define zmemcmp memcmp +#define zmemzero(dest, len) memset(dest, 0, len) + +/* Diagnostic functions */ +#ifdef _ZIP_DEBUG_ + int z_verbose = 0; +# define Assert(cond,msg) assert(cond); + //{if(!(cond)) Sys_Error(msg);} +# define Trace(x) {if (z_verbose>=0) Sys_Error x ;} +# define Tracev(x) {if (z_verbose>0) Sys_Error x ;} +# define Tracevv(x) {if (z_verbose>1) Sys_Error x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) Sys_Error x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) Sys_Error x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +typedef uLong (*check_func) OF((uLong check, const Byte *buf, uInt len)); +voidp zcalloc OF((voidp opaque, unsigned items, unsigned size)); +void zcfree OF((voidp opaque, voidp ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidp)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + + +#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ + !defined(CASESENSITIVITYDEFAULT_NO) +#define CASESENSITIVITYDEFAULT_NO +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (65536) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (Mem_Alloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Mem_Free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + +/* +static int unzlocal_getByte(FILE *fin,int *pi) +{ + unsigned char c; + int err = fread(&c, 1, 1, fin); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ferror(fin)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} +*/ + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +static int unzlocal_getShort (FILE* fin, uLong *pX) +{ + short v; + + fread( &v, sizeof(v), 1, fin ); + + *pX = LittleShort( v); + return UNZ_OK; + +/* + uLong x ; + int i; + int err; + + err = unzlocal_getByte(fin,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +*/ +} + +static int unzlocal_getLong (FILE *fin, uLong *pX) +{ + int v; + + fread( &v, sizeof(v), 1, fin ); + + *pX = LittleLong( v); + return UNZ_OK; + +/* + uLong x ; + int i; + int err; + + err = unzlocal_getByte(fin,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unzlocal_getByte(fin,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +*/ +} + + +/* My own strcmpi / strcasecmp */ +static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity) +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#define BUFREADCOMMENT (0x400) + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +static uLong unzlocal_SearchCentralDir(FILE *fin) +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (fseek(fin,0,SEEK_END) != 0) + return 0; + + + uSizeFile = ftell( fin ); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (fseek(fin,uReadPos,SEEK_SET)!=0) + break; + + if (fread(buf,(uInt)uReadSize,1,fin)!=1) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +extern unzFile unzReOpen (const char* path, unzFile file) +{ + unz_s *s; + FILE * fin; + + fin=fopen(path,"rb"); + if (fin==NULL) + return NULL; + + s=(unz_s*)ALLOC(sizeof(unz_s)); + memcpy(s, (unz_s*)file, sizeof(unz_s)); + + s->file = fin; + s->pfile_in_zip_read = NULL; + + return (unzFile)s; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer + "zlib/zlib109.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile unzOpen (const char* path) +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + FILE * fin ; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + fin=fopen(path,"rb"); + if (fin==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(fin); + if (central_pos==0) + err=UNZ_ERRNO; + + if (fseek(fin,central_pos,SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(fin,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + fclose(s->file); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info) +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +static int unzlocal_GetCurrentFileInfoInternal (unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (fread(szFileName,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extrafile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (fread(extraField,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentfile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (fread(szComment,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int unzGetCurrentFileInfo ( unzFile file, unz_file_info *pfile_info, + char *szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int unzGoToNextFile (unzFile file) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ) +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + *pos = s->pos_in_central_dir; + return UNZ_OK; +} + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return UNZ_OK; +} + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz_s* s; + int err; + + + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + return err; +} + + +/* + Read the static header of the current zipfile + Check the coherency of the static header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in static header + (filename and size of extra field data) +*/ +static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, + uLong *poffset_local_extrafield, + uInt *psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (fseek(s->file,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int unzOpenCurrentFile (unzFile file) +{ + int err=UNZ_OK; + int Store; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the static extra field */ + uInt size_local_extrafield; /* size of the static extra field */ + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + ALLOC(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + Store = s->cur_file_info.compression_method==0; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->file=s->file; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if (!Store) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidp)0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + + s->pfile_in_zip_read = pfile_in_zip_read_info; + return UNZ_OK; +} + + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Byte*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if (len>pfile_in_zip_read_info->rest_read_uncompressed) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed) + if (fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + if (fread(pfile_in_zip_read_info->read_buffer,uReadThis,1, + pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Byte*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if (pfile_in_zip_read_info->compression_method==0) + { + uInt uDoCopy,i ; + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Byte *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern long unztell (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (long)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int unzeof (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the static-header version of the extra field (sometimes, there is + more info in the static-header version than in the central-header) + + if buf==NULL, it return the size of the static extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (fread(buf,(uInt)size_to_read,1,pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (fseek(s->file,s->central_pos+22,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (fread(szComment,(uInt)uReadThis,1,s->file)!=1) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: unzip.c,v 1.2 1999/09/07 20:51:25 zoid Exp $ */ + +#ifdef DYNAMIC_CRC_TABLE + +static int crc_table_empty = 1; +static uLong crc_table[256]; +static void make_crc_table OF((void)); + +/* + Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all + the information needed to generate CRC's on data a byte at a time for all + combinations of CRC register values and incoming bytes. +*/ +static void make_crc_table() +{ + uLong c; + int n, k; + uLong poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static const Byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* make exclusive-or pattern from polynomial (0xedb88320L) */ + poly = 0L; + for (n = 0; n < sizeof(p)/sizeof(Byte); n++) + poly |= 1L << (31 - p[n]); + + for (n = 0; n < 256; n++) + { + c = (uLong)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[n] = c; + } + crc_table_empty = 0; +} +#else +/* ======================================================================== + * Table of CRC-32's of all single-byte values (made by make_crc_table) + */ +static const uLong crc_table[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; +#endif + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const uLong * get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) make_crc_table(); +#endif + return (const uLong *)crc_table; +} + +/* ========================================================================= */ +#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8); +#define DO2(buf) DO1(buf); DO1(buf); +#define DO4(buf) DO2(buf); DO2(buf); +#define DO8(buf) DO4(buf); DO4(buf); + +/* ========================================================================= */ +uLong crc32(uLong crc, const Byte *buf, uInt len) +{ + if (buf == Z_NULL) return 0L; +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif + crc = crc ^ 0xffffffffL; + while (len >= 8) + { + DO8(buf); + len -= 8; + } + if (len) do { + DO1(buf); + } while (--len); + return crc ^ 0xffffffffL; +} + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_blocks_state; +typedef struct inflate_blocks_state inflate_blocks_statef; + +extern inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +extern int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +extern void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLong *)); /* check value on output */ + +extern int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +extern void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Byte *d, /* dictionary */ + uInt n)); /* dictionary length */ + +extern int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* Table for deflate from PKZIP's appnote.txt. */ +static const uInt border[] = { /* Order of the bit length code lengths */ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Huffman code lookup table entry--this entry is four bytes for machines + that have 16-bit pointers (e.g. PC's in the small or medium model). */ + +typedef struct inflate_huft_s inflate_huft; + +struct inflate_huft_s { + union { + struct { + Byte Exop; /* number of extra bits or operation */ + Byte Bits; /* number of bits in this code or subcode */ + } what; + uInt pad; /* pad structure to a power of 2 (4 bytes for */ + } word; /* 16-bit, 8 bytes for 32-bit int's) */ + uInt base; /* literal, length base, distance base, + or table offset */ +}; + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1004 huft structures (850 for length/literals + and 154 for distances, the latter actually the result of an + exhaustive search). The actual maximum is not known, but the + value below is more than safe. */ +#define MANY 1440 + +extern int inflate_trees_bits OF(( + uInt *, /* 19 code lengths */ + uInt *, /* bits tree desired/actual depth */ + inflate_huft * *, /* bits tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_dynamic OF(( + uInt, /* number of literal/length codes */ + uInt, /* number of distance codes */ + uInt *, /* that many (total) code lengths */ + uInt *, /* literal desired/actual bit depth */ + uInt *, /* distance desired/actual bit depth */ + inflate_huft * *, /* literal/length tree result */ + inflate_huft * *, /* distance tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_fixed OF(( + uInt *, /* literal desired/actual bit depth */ + uInt *, /* distance desired/actual bit depth */ + inflate_huft * *, /* literal/length tree result */ + inflate_huft * *, /* distance tree result */ + z_streamp)); /* for memory allocation */ + + +/* infcodes.h -- header to use infcodes.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_codes_state; +typedef struct inflate_codes_state inflate_codes_statef; + +extern inflate_codes_statef *inflate_codes_new OF(( + uInt, uInt, + inflate_huft *, inflate_huft *, + z_streamp )); + +extern int inflate_codes OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +extern void inflate_codes_free OF(( + inflate_codes_statef *, + z_streamp )); + +/* infutil.h -- types and macros common to blocks and codes + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFUTIL_H +#define _INFUTIL_H + +typedef enum { + TYPE, /* get type bits (3, including end bit) */ + LENS, /* get lengths for stored */ + STORED, /* processing stored block */ + TABLE, /* get table lengths */ + BTREE, /* get bit lengths tree for a dynamic block */ + DTREE, /* get length, distance trees for a dynamic block */ + CODES, /* processing fixed or dynamic block */ + DRY, /* output remaining window bytes */ + DONE, /* finished last block, done */ + BAD} /* got a data error--stuck here */ +inflate_block_mode; + +/* inflate blocks semi-private state */ +struct inflate_blocks_state { + + /* mode */ + inflate_block_mode mode; /* current inflate_block mode */ + + /* mode dependent information */ + union { + uInt left; /* if STORED, bytes left to copy */ + struct { + uInt table; /* table lengths (14 bits) */ + uInt index; /* index into blens (or border) */ + uInt *blens; /* bit lengths of codes */ + uInt bb; /* bit length tree depth */ + inflate_huft *tb; /* bit length decoding tree */ + } trees; /* if DTREE, decoding info for trees */ + struct { + inflate_codes_statef + *codes; + } decode; /* if CODES, current state */ + } sub; /* submode */ + uInt last; /* true if this block is the last block */ + + /* mode independent information */ + uInt bitk; /* bits in bit buffer */ + uLong bitb; /* bit buffer */ + inflate_huft *hufts; /* single malloc for tree space */ + Byte *window; /* sliding window */ + Byte *end; /* one byte after sliding window */ + Byte *read; /* window read pointer */ + Byte *write; /* window write pointer */ + check_func checkfn; /* check function */ + uLong check; /* check on output */ + +}; + + +/* defines for inflate input/output */ +/* update pointers and return */ +#define UPDBITS {s->bitb=b;s->bitk=k;} +#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} +#define UPDOUT {s->write=q;} +#define UPDATE {UPDBITS UPDIN UPDOUT} +#define LEAVE {UPDATE return inflate_flush(s,z,r);} +/* get bytes and bits */ +#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} +#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} +#define NEXTBYTE (n--,*p++) +#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<>=(j);k-=(j);} +/* output bytes */ +#define WAVAIL (uInt)(qread?s->read-q-1:s->end-q) +#define LOADOUT {q=s->write;m=(uInt)WAVAIL;} +#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} +#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} +#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} +#define OUTBYTE(a) {*q++=(Byte)(a);m--;} +/* load static pointers */ +#define LOAD {LOADIN LOADOUT} + +/* masks for lower bits (size given to avoid silly warnings with Visual C++) */ +static /* And'ing with mask[n] masks the lower n bits */ +uInt inflate_mask[17] = { + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +/* copy as much as possible from the sliding window to the output area */ +extern int inflate_flush OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +#endif + + +/* + Notes beyond the 1.93a appnote.txt: + + 1. Distance pointers never point before the beginning of the output + stream. + 2. Distance pointers can point back across blocks, up to 32k away. + 3. There is an implied maximum of 7 bits for the bit length table and + 15 bits for the actual data. + 4. If only one code exists, then it is encoded using one bit. (Zero + would be more efficient, but perhaps a little confusing.) If two + codes exist, they are coded using one bit each (0 and 1). + 5. There is no way of sending zero distance codes--a dummy must be + sent if there are none. (History: a pre 2.0 version of PKZIP would + store blocks with no distance codes, but this was discovered to be + too harsh a criterion.) Valid only for 1.93a. 2.04c does allow + zero distance codes, which is sent as one code of zero bits in + length. + 6. There are up to 286 literal/length codes. Code 256 represents the + end-of-block. Note however that the static length tree defines + 288 codes just to fill out the Huffman codes. Codes 286 and 287 + cannot be used though, since there is no length base or extra bits + defined for them. Similarily, there are up to 30 distance codes. + However, static trees define 32 codes (all 5 bits) to fill out the + Huffman codes, but the last two had better not show up in the data. + 7. Unzip can check dynamic Huffman blocks for complete code sets. + The exception is that a single code would not be complete (see #4). + 8. The five bits following the block type is really the number of + literal codes sent minus 257. + 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits + (1+6+6). Therefore, to output three times the length, you output + three codes (1+1+1), whereas to output four times the same length, + you only need two codes (1+3). Hmm. + 10. In the tree reconstruction algorithm, Code = Code + Increment + only if BitLength(i) is not zero. (Pretty obvious.) + 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) + 12. Note: length code 284 can represent 227-258, but length code 285 + really is 258. The last length deserves its own, short code + since it gets used a lot in very redundant files. The length + 258 is special since 258 - 3 (the min match length) is 255. + 13. The literal/length and distance code bit lengths are read as a + single stream of lengths. It is possible (and advantageous) for + a repeat code (16, 17, or 18) to go across the boundary between + the two sets of lengths. + */ + + +void inflate_blocks_reset(inflate_blocks_statef *s, z_streamp z, uLong *c) +{ + if (c != Z_NULL) + *c = s->check; + if (s->mode == BTREE || s->mode == DTREE) + ZFREE(z, s->sub.trees.blens); + if (s->mode == CODES) + inflate_codes_free(s->sub.decode.codes, z); + s->mode = TYPE; + s->bitk = 0; + s->bitb = 0; + s->read = s->write = s->window; + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(0L, (const Byte *)Z_NULL, 0); + Tracev(("inflate: blocks reset\n")); +} + + +inflate_blocks_statef *inflate_blocks_new(z_streamp z, check_func c, uInt w) +{ + inflate_blocks_statef *s; + + if ((s = (inflate_blocks_statef *)ZALLOC + (z,1,sizeof(struct inflate_blocks_state))) == Z_NULL) + return s; + if ((s->hufts = + (inflate_huft *)ZALLOC(z, sizeof(inflate_huft), MANY)) == Z_NULL) + { + ZFREE(z, s); + return Z_NULL; + } + if ((s->window = (Byte *)ZALLOC(z, 1, w)) == Z_NULL) + { + ZFREE(z, s->hufts); + ZFREE(z, s); + return Z_NULL; + } + s->end = s->window + w; + s->checkfn = c; + s->mode = TYPE; + Tracev(("inflate: blocks allocated\n")); + inflate_blocks_reset(s, z, Z_NULL); + return s; +} + + +int inflate_blocks(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt t; /* temporary storage */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input based on current state */ + while (1) switch (s->mode) + { + case TYPE: + NEEDBITS(3) + t = (uInt)b & 7; + s->last = t & 1; + switch (t >> 1) + { + case 0: /* stored */ + Tracev(("inflate: stored block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + t = k & 7; /* go to byte boundary */ + DUMPBITS(t) + s->mode = LENS; /* get length of stored block */ + break; + case 1: /* fixed */ + Tracev(("inflate: fixed codes block%s\n", + s->last ? " (last)" : "")); + { + uInt bl, bd; + inflate_huft *tl, *td; + + inflate_trees_fixed(&bl, &bd, &tl, &td, z); + s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z); + if (s->sub.decode.codes == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + } + DUMPBITS(3) + s->mode = CODES; + break; + case 2: /* dynamic */ + Tracev(("inflate: dynamic codes block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + s->mode = TABLE; + break; + case 3: /* illegal */ + DUMPBITS(3) + s->mode = BAD; + z->msg = (char*)"invalid block type"; + r = Z_DATA_ERROR; + LEAVE + } + break; + case LENS: + NEEDBITS(32) + if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) + { + s->mode = BAD; + z->msg = (char*)"invalid stored block lengths"; + r = Z_DATA_ERROR; + LEAVE + } + s->sub.left = (uInt)b & 0xffff; + b = k = 0; /* dump bits */ + Tracev(("inflate: stored length %u\n", s->sub.left)); + s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE); + break; + case STORED: + if (n == 0) + LEAVE + NEEDOUT + t = s->sub.left; + if (t > n) t = n; + if (t > m) t = m; +#ifdef MACOS_X // Optimization + if (t>64) { + zmemcpy(q, p, t); + } + else { + int t1; + for (t1=0; t1sub.left -= t) != 0) + break; + Tracev(("inflate: stored end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + s->mode = s->last ? DRY : TYPE; + break; + case TABLE: + NEEDBITS(14) + s->sub.trees.table = t = (uInt)b & 0x3fff; +#ifndef PKZIP_BUG_WORKAROUND + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + s->mode = BAD; + z->msg = (char*)"too many length or distance symbols"; + r = Z_DATA_ERROR; + LEAVE + } +#endif + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if ((s->sub.trees.blens = (uInt*)ZALLOC(z, t, sizeof(uInt))) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + DUMPBITS(14) + s->sub.trees.index = 0; + Tracev(("inflate: table sizes ok\n")); + s->mode = BTREE; + case BTREE: + while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10)) + { + NEEDBITS(3) + s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7; + DUMPBITS(3) + } + while (s->sub.trees.index < 19) + s->sub.trees.blens[border[s->sub.trees.index++]] = 0; + s->sub.trees.bb = 7; + t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb, + &s->sub.trees.tb, s->hufts, z); + if (t != Z_OK) + { + ZFREE(z, s->sub.trees.blens); + r = t; + if (r == Z_DATA_ERROR) + s->mode = BAD; + LEAVE + } + s->sub.trees.index = 0; + Tracev(("inflate: bits tree ok\n")); + s->mode = DTREE; + case DTREE: + while (t = s->sub.trees.table, + s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) + { + inflate_huft *h; + uInt i, j, c; + + t = s->sub.trees.bb; + NEEDBITS(t) + h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]); + t = h->bits; + c = h->base; + if (c < 16) + { + DUMPBITS(t) + s->sub.trees.blens[s->sub.trees.index++] = c; + } + else /* c == 16..18 */ + { + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + NEEDBITS(t + i) + DUMPBITS(t) + j += (uInt)b & inflate_mask[i]; + DUMPBITS(i) + i = s->sub.trees.index; + t = s->sub.trees.table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)) + { + ZFREE(z, s->sub.trees.blens); + s->mode = BAD; + z->msg = (char*)"invalid bit length repeat"; + r = Z_DATA_ERROR; + LEAVE + } + c = c == 16 ? s->sub.trees.blens[i - 1] : 0; + do { + s->sub.trees.blens[i++] = c; + } while (--j); + s->sub.trees.index = i; + } + } + s->sub.trees.tb = Z_NULL; + { + uInt bl, bd; + inflate_huft *tl, *td; + inflate_codes_statef *c; + + bl = 9; /* must be <= 9 for lookahead assumptions */ + bd = 6; /* must be <= 9 for lookahead assumptions */ + t = s->sub.trees.table; + t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), + s->sub.trees.blens, &bl, &bd, &tl, &td, + s->hufts, z); + ZFREE(z, s->sub.trees.blens); + if (t != Z_OK) + { + if (t == (uInt)Z_DATA_ERROR) + s->mode = BAD; + r = t; + LEAVE + } + Tracev(("inflate: trees ok\n")); + if ((c = inflate_codes_new(bl, bd, tl, td, z)) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + s->sub.decode.codes = c; + } + s->mode = CODES; + case CODES: + UPDATE + if ((r = inflate_codes(s, z, r)) != Z_STREAM_END) + return inflate_flush(s, z, r); + r = Z_OK; + inflate_codes_free(s->sub.decode.codes, z); + LOAD + Tracev(("inflate: codes end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + if (!s->last) + { + s->mode = TYPE; + break; + } + s->mode = DRY; + case DRY: + FLUSH + if (s->read != s->write) + LEAVE + s->mode = DONE; + case DONE: + r = Z_STREAM_END; + LEAVE + case BAD: + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +} + + +int inflate_blocks_free(inflate_blocks_statef *s, z_streamp z) +{ + inflate_blocks_reset(s, z, Z_NULL); + ZFREE(z, s->window); + ZFREE(z, s->hufts); + ZFREE(z, s); + Tracev(("inflate: blocks freed\n")); + return Z_OK; +} + + +void inflate_set_dictionary(inflate_blocks_statef *s, const Byte *d, uInt n) +{ + zmemcpy(s->window, d, n); + s->read = s->write = s->window + n; +} + + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. + * IN assertion: s != Z_NULL + */ +int inflate_blocks_sync_point(inflate_blocks_statef *s) +{ + return s->mode == LENS; +} + +/* copy as much as possible from the sliding window to the output area */ +int inflate_flush(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt n; + Byte *p; + Byte *q; + + /* static copies of source and destination pointers */ + p = z->next_out; + q = s->read; + + /* compute number of bytes to copy as as end of window */ + n = (uInt)((q <= s->write ? s->write : s->end) - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy as as end of window */ +#ifdef MACOS_X // Optimization + if (n>64) { + zmemcpy(p, q, n); + } + else { + int t1; + for (t1=0; t1end) + { + /* wrap pointers */ + q = s->window; + if (s->write == s->end) + s->write = s->window; + + /* compute bytes to copy */ + n = (uInt)(s->write - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy */ +#ifdef MACOS_X // Optimization + if (n>64) { + zmemcpy(p, q, n); + } + else { + int t1; + for (t1=0; t1next_out = p; + s->read = q; + + /* done */ + return r; +} + +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +const char inflate_copyright[] = + " inflate 1.1.3 Copyright 1995-1998 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + + +static int huft_build OF(( + uInt *, /* code lengths in bits */ + uInt, /* number of codes */ + uInt, /* number of "simple" codes */ + const uInt *, /* list of base values for non-simple codes */ + const uInt *, /* list of extra bits for non-simple codes */ + inflate_huft **, /* result: starting table */ + uInt *, /* maximum lookup bits (returns actual) */ + inflate_huft *, /* space for trees */ + uInt *, /* hufts used in space */ + uInt * )); /* space for values */ + +/* Tables for deflate from PKZIP's appnote.txt. */ +static const uInt cplens[31] = { /* Copy lengths for literal codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + /* see note #13 above about 258 */ +static const uInt cplext[31] = { /* Extra bits for literal codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112}; /* 112==invalid */ +static const uInt cpdist[30] = { /* Copy offsets for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; +static const uInt cpdext[30] = { /* Extra bits for distance codes */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + +/* + Huffman code decoding is performed using a multi-level table lookup. + The fastest way to decode is to simply build a lookup table whose + size is determined by the longest code. However, the time it takes + to build this table can also be a factor if the data being decoded + is not very long. The most common codes are necessarily the + shortest codes, so those codes dominate the decoding time, and hence + the speed. The idea is you can have a shorter table that decodes the + shorter, more probable codes, and then point to subsidiary tables for + the longer codes. The time it costs to decode the longer codes is + then traded against the time it takes to make longer tables. + + This results of this trade are in the variables lbits and dbits + below. lbits is the number of bits the first level table for literal/ + length codes can decode in one step, and dbits is the same thing for + the distance codes. Subsequent tables are also less than or equal to + those sizes. These values may be adjusted either when all of the + codes are shorter than that, in which case the longest code length in + bits is used, or when the shortest code is *longer* than the requested + table size, in which case the length of the shortest code in bits is + used. + + There are two different values for the two tables, since they code a + different number of possibilities each. The literal/length table + codes 286 possible values, or in a flat code, a little over eight + bits. The distance table codes 30 possible values, or a little less + than five bits, flat. The optimum values for speed end up being + about one bit more than those, so lbits is 8+1 and dbits is 5+1. + The optimum values may differ though from machine to machine, and + possibly even between compilers. Your mileage may vary. + */ + + +/* If BMAX needs to be larger than 16, then h and x[] should be uLong. */ +#define BMAX 15 /* maximum bit length of any code */ + +static int huft_build(uInt *b, uInt n, uInt s, const uInt *d, const uInt *e, inflate_huft ** t, uInt *m, inflate_huft *hp, uInt *hn, uInt *v) +//uInt *b; /* code lengths in bits (all assumed <= BMAX) */ +//uInt n; /* number of codes (assumed <= 288) */ +//uInt s; /* number of simple-valued codes (0..s-1) */ +//const uInt *d; /* list of base values for non-simple codes */ +//const uInt *e; /* list of extra bits for non-simple codes */ +//inflate_huft ** t; /* result: starting table */ +//uInt *m; /* maximum lookup bits, returns actual */ +//inflate_huft *hp; /* space for trees */ +//uInt *hn; /* hufts used in space */ +//uInt *v; /* working area: values in order of bit length */ +/* Given a list of code lengths and a maximum table size, make a set of + tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + if the given code set is incomplete (the tables are still built in this + case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + lengths), or Z_MEM_ERROR if not enough memory. */ +{ + + uInt a; /* counter for codes of length k */ + uInt c[BMAX+1]; /* bit length count table */ + uInt f; /* i repeats in table every f entries */ + int g; /* maximum code length */ + int h; /* table level */ + register uInt i; /* counter, current code */ + register uInt j; /* counter */ + register int k; /* number of bits in current code */ + int l; /* bits per table (returned in m) */ + uInt mask; /* (1 << w) - 1, to avoid cc -O bug on HP */ + register uInt *p; /* pointer into c[], b[], or v[] */ + inflate_huft *q; /* points to current table */ + struct inflate_huft_s r; /* table entry for structure assignment */ + inflate_huft *u[BMAX]; /* table stack */ + register int w; /* bits before this table == (l * h) */ + uInt x[BMAX+1]; /* bit offsets, then code stack */ + uInt *xp; /* pointer into x */ + int y; /* number of dummy codes added */ + uInt z; /* number of entries in current table */ + + + /* Generate counts for each bit length */ + p = c; +#define C0 *p++ = 0; +#define C2 C0 C0 C0 C0 +#define C4 C2 C2 C2 C2 + C4 /* clear c[]--assume BMAX+1 is 16 */ + p = b; i = n; + do { + c[*p++]++; /* assume all entries <= BMAX */ + } while (--i); + if (c[0] == n) /* null input--all zero length codes */ + { + *t = (inflate_huft *)Z_NULL; + *m = 0; + return Z_OK; + } + + + /* Find minimum and maximum length, bound *m by those */ + l = *m; + for (j = 1; j <= BMAX; j++) + if (c[j]) + break; + k = j; /* minimum code length */ + if ((uInt)l < j) + l = j; + for (i = BMAX; i; i--) + if (c[i]) + break; + g = i; /* maximum code length */ + if ((uInt)l > i) + l = i; + *m = l; + + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) + if ((y -= c[j]) < 0) + return Z_DATA_ERROR; + if ((y -= c[i]) < 0) + return Z_DATA_ERROR; + c[i] += y; + + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; xp = x + 2; + while (--i) { /* note that i == g from above */ + *xp++ = (j += *p++); + } + + + /* Make a table of values in order of bit lengths */ + p = b; i = 0; + do { + if ((j = *p++) != 0) + v[x[j]++] = i; + } while (++i < n); + n = x[g]; /* set n to length of v */ + + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + h = -1; /* no tables yet--level -1 */ + w = -l; /* bits decoded == (l * h) */ + u[0] = (inflate_huft *)Z_NULL; /* just to keep compilers happy */ + q = (inflate_huft *)Z_NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) + { + a = c[k]; + while (a--) + { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > w + l) + { + h++; + w += l; /* previous table always l bits */ + + /* compute minimum size table less than or equal to l bits */ + z = g - w; + z = z > (uInt)l ? l : z; /* table size upper limit */ + if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ + { /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + if (j < z) + while (++j < z) /* try smaller tables up to z bits */ + { + if ((f <<= 1) <= *++xp) + break; /* enough codes to use up j bits */ + f -= *xp; /* else deduct codes from patterns */ + } + } + z = 1 << j; /* table entries for j-bit table */ + + /* allocate new table */ + if (*hn + z > MANY) /* (note: doesn't matter for fixed) */ + return Z_MEM_ERROR; /* not enough memory */ + u[h] = q = hp + *hn; + *hn += z; + + /* connect to last table, if there is one */ + if (h) + { + x[h] = i; /* save pattern for backing up */ + r.bits = (Byte)l; /* bits to dump before this table */ + r.exop = (Byte)j; /* bits in this table */ + j = i >> (w - l); + r.base = (uInt)(q - u[h-1] - j); /* offset to this table */ + u[h-1][j] = r; /* connect to last table */ + } + else + *t = q; /* first table is returned result */ + } + + /* set up table entry in r */ + r.bits = (Byte)(k - w); + if (p >= v + n) + r.exop = 128 + 64; /* out of values--invalid code */ + else if (*p < s) + { + r.exop = (Byte)(*p < 256 ? 0 : 32 + 64); /* 256 is end-of-block */ + r.base = *p++; /* simple code is just the value */ + } + else + { + r.exop = (Byte)(e[*p - s] + 16 + 64);/* non-simple--look up in lists */ + r.base = d[*p++ - s]; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) + q[j] = r; + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i & j; j >>= 1) + i ^= j; + i ^= j; + + /* backup over finished tables */ + mask = (1 << w) - 1; /* needed on HP, cc -O bug */ + while ((i & mask) != x[h]) + { + h--; /* don't need to update q */ + w -= l; + mask = (1 << w) - 1; + } + } + } + + + /* Return Z_BUF_ERROR if we were given an incomplete table */ + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; +} + + +int inflate_trees_bits(uInt *c, uInt *bb, inflate_huft * *tb, inflate_huft *hp, z_streamp z) +//uInt *c; /* 19 code lengths */ +//uInt *bb; /* bits tree desired/actual depth */ +//inflate_huft * *tb; /* bits tree result */ +//inflate_huft *hp; /* space for trees */ +//z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uInt *v; /* work area for huft_build */ + + if ((v = (uInt*)ZALLOC(z, 19, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + r = huft_build(c, 19, 19, (uInt*)Z_NULL, (uInt*)Z_NULL, + tb, bb, hp, &hn, v); + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed dynamic bit lengths tree"; + else if (r == Z_BUF_ERROR || *bb == 0) + { + z->msg = (char*)"incomplete dynamic bit lengths tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +} + + +int inflate_trees_dynamic(uInt nl, uInt nd, uInt *c, uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, inflate_huft *hp, z_streamp z) +//uInt nl; /* number of literal/length codes */ +//uInt nd; /* number of distance codes */ +//uInt *c; /* that many (total) code lengths */ +//uInt *bl; /* literal desired/actual bit depth */ +//uInt *bd; /* distance desired/actual bit depth */ +//inflate_huft * *tl; /* literal/length tree result */ +//inflate_huft * *td; /* distance tree result */ +//inflate_huft *hp; /* space for trees */ +//z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uInt *v; /* work area for huft_build */ + + /* allocate work area */ + if ((v = (uInt*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + + /* build literal/length tree */ + r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v); + if (r != Z_OK || *bl == 0) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed literal/length tree"; + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"incomplete literal/length tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; + } + + /* build distance tree */ + r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v); + if (r != Z_OK || (*bd == 0 && nl > 257)) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed distance tree"; + else if (r == Z_BUF_ERROR) { +#ifdef PKZIP_BUG_WORKAROUND + r = Z_OK; + } +#else + z->msg = (char*)"incomplete distance tree"; + r = Z_DATA_ERROR; + } + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"empty distance tree with lengths"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +#endif + } + + /* done */ + ZFREE(z, v); + return Z_OK; +} + +/* inffixed.h -- table for decoding fixed codes + * Generated automatically by the maketree.c program + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +static uInt fixed_bl = 9; +static uInt fixed_bd = 5; +static inflate_huft fixed_tl[] = { + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},192}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},160}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},224}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},144}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},208}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},176}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},240}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},200}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},168}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},232}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},152}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},216}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},184}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},248}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},196}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},164}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},228}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},148}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},212}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},180}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},244}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},204}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},172}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},236}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},156}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},220}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},188}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},252}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},194}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},162}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},226}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},146}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},210}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},178}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},242}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},202}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},170}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},234}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},154}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},218}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},186}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},250}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},198}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},166}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},230}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},150}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},214}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},182}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},246}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},206}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},174}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},238}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},158}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},222}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},190}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},254}, + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},193}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},161}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},225}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},145}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},209}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},177}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},241}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},201}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},169}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},233}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},153}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},217}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},185}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},249}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},197}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},165}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},229}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},149}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},213}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},181}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},245}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},205}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},173}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},237}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},157}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},221}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},189}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},253}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},195}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},163}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},227}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},147}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},211}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},179}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},243}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},203}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},171}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},235}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},155}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},219}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},187}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},251}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},199}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},167}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},231}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},151}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},215}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},183}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},247}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},207}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},175}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},239}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},159}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},223}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},191}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},255} + }; +static inflate_huft fixed_td[] = { + {{{80,5}},1}, {{{87,5}},257}, {{{83,5}},17}, {{{91,5}},4097}, + {{{81,5}},5}, {{{89,5}},1025}, {{{85,5}},65}, {{{93,5}},16385}, + {{{80,5}},3}, {{{88,5}},513}, {{{84,5}},33}, {{{92,5}},8193}, + {{{82,5}},9}, {{{90,5}},2049}, {{{86,5}},129}, {{{192,5}},24577}, + {{{80,5}},2}, {{{87,5}},385}, {{{83,5}},25}, {{{91,5}},6145}, + {{{81,5}},7}, {{{89,5}},1537}, {{{85,5}},97}, {{{93,5}},24577}, + {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289}, + {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577} + }; + +int inflate_trees_fixed(uInt *bl, uInt *bd, inflate_huft * *tl, inflate_huft * *td, z_streamp z) +//uInt *bl; /* literal desired/actual bit depth */ +//uInt *bd; /* distance desired/actual bit depth */ +//inflate_huft * *tl; /* literal/length tree result */ +//inflate_huft * *td; /* distance tree result */ +//z_streamp z; /* for memory allocation */ +{ + *bl = fixed_bl; + *bd = fixed_bd; + *tl = fixed_tl; + *td = fixed_td; + return Z_OK; +} + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* macros for bit input with no checking and for returning unused bytes */ +#define GRABBITS(j) {while(k<(j)){b|=((uLong)NEXTBYTE)<avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;} + +/* Called with number of bytes left to write in window at least 258 + (the maximum string length) and number of input bytes available + at least ten. The ten bytes are six bytes for the longest length/ + distance pair plus four bytes for overloading the bit buffer. */ + +int inflate_fast(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, inflate_blocks_statef *s, z_streamp z) +{ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + uInt ml; /* mask for literal/length tree */ + uInt md; /* mask for distance tree */ + uInt c; /* bytes to copy */ + uInt d; /* distance back to copy from */ + Byte *r; /* copy source pointer */ + + /* load input, output, bit values */ + LOAD + + /* initialize masks */ + ml = inflate_mask[bl]; + md = inflate_mask[bd]; + + /* do until not enough input or output space for fast loop */ + do { /* assume called with m >= 258 && n >= 10 */ + /* get literal/length code */ + GRABBITS(20) /* max bits for literal/length code */ + if ((e = (t = tl + ((uInt)b & ml))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + continue; + } + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits for length */ + e &= 15; + c = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv(("inflate: * length %u\n", c)); + + /* decode distance base of block to copy */ + GRABBITS(15); /* max bits for distance code */ + e = (t = td + ((uInt)b & md))->exop; + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits to add to distance base */ + e &= 15; + GRABBITS(e) /* get extra bits (up to 13) */ + d = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv(("inflate: * distance %u\n", d)); + + /* do the copy */ + m -= c; + if ((uInt)(q - s->window) >= d) /* offset before dest */ + { /* just copy */ + r = q - d; + *q++ = *r++; c--; /* minimum count is three, */ + *q++ = *r++; c--; /* so unroll loop a little */ + } + else /* else offset after destination */ + { + e = d - (uInt)(q - s->window); /* bytes from offset to end */ + r = s->end - e; /* pointer to offset */ + if (c > e) /* if source crosses, */ + { + c -= e; /* copy to end of window */ + do { + *q++ = *r++; + } while (--e); + r = s->window; /* copy rest from start of window */ + } + } + do { /* copy all or what's left */ + *q++ = *r++; + } while (--c); + break; + } + else if ((e & 64) == 0) + { + t += t->base; + e = (t += ((uInt)b & inflate_mask[e]))->exop; + } + else + { + z->msg = (char*)"invalid distance code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + break; + } + if ((e & 64) == 0) + { + t += t->base; + if ((e = (t += ((uInt)b & inflate_mask[e]))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + break; + } + } + else if (e & 32) + { + Tracevv(("inflate: * end of block\n")); + UNGRAB + UPDATE + return Z_STREAM_END; + } + else + { + z->msg = (char*)"invalid literal/length code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + } while (m >= 258 && n >= 10); + + /* not enough input or output--restore pointers and return */ + UNGRAB + UPDATE + return Z_OK; +} + +/* infcodes.c -- process literals and length/distance pairs + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +typedef enum { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + START, /* x: set up for LEN */ + LEN, /* i: get length/literal/eob next */ + LENEXT, /* i: getting length extra (have base) */ + DIST, /* i: get distance next */ + DISTEXT, /* i: getting distance extra */ + COPY, /* o: copying bytes in window, waiting for space */ + LIT, /* o: got literal, waiting for output space */ + WASH, /* o: got eob, possibly still output waiting */ + END, /* x: got eob and all data flushed */ + BADCODE} /* x: got error */ +inflate_codes_mode; + +/* inflate codes private state */ +struct inflate_codes_state { + + /* mode */ + inflate_codes_mode mode; /* current inflate_codes mode */ + + /* mode dependent information */ + uInt len; + union { + struct { + inflate_huft *tree; /* pointer into tree */ + uInt need; /* bits needed */ + } code; /* if LEN or DIST, where in tree */ + uInt lit; /* if LIT, literal */ + struct { + uInt get; /* bits to get for extra */ + uInt dist; /* distance back to copy from */ + } copy; /* if EXT or COPY, where and how much */ + } sub; /* submode */ + + /* mode independent information */ + Byte lbits; /* ltree bits decoded per branch */ + Byte dbits; /* dtree bits decoder per branch */ + inflate_huft *ltree; /* literal/length/eob tree */ + inflate_huft *dtree; /* distance tree */ + +}; + + +inflate_codes_statef *inflate_codes_new(uInt bl, uInt bd, inflate_huft *tl, inflate_huft *td, z_streamp z) +{ + inflate_codes_statef *c; + + if ((c = (inflate_codes_statef *) + ZALLOC(z,1,sizeof(struct inflate_codes_state))) != Z_NULL) + { + c->mode = START; + c->lbits = (Byte)bl; + c->dbits = (Byte)bd; + c->ltree = tl; + c->dtree = td; + Tracev(("inflate: codes new\n")); + } + return c; +} + + +int inflate_codes(inflate_blocks_statef *s, z_streamp z, int r) +{ + uInt j; /* temporary storage */ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Byte *p; /* input data pointer */ + uInt n; /* bytes available there */ + Byte *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + Byte *f; /* pointer to copy strings from */ + inflate_codes_statef *c = s->sub.decode.codes; /* codes state */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input and output based on current state */ + while (1) switch (c->mode) + { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + case START: /* x: set up for LEN */ +#ifndef SLOW + if (m >= 258 && n >= 10) + { + UPDATE + r = inflate_fast(c->lbits, c->dbits, c->ltree, c->dtree, s, z); + LOAD + if (r != Z_OK) + { + c->mode = r == Z_STREAM_END ? WASH : BADCODE; + break; + } + } +#endif /* !SLOW */ + c->sub.code.need = c->lbits; + c->sub.code.tree = c->ltree; + c->mode = LEN; + case LEN: /* i: get length/literal/eob next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e == 0) /* literal */ + { + c->sub.lit = t->base; + Tracevv((t->base >= 0x20 && t->base < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", t->base)); + c->mode = LIT; + break; + } + if (e & 16) /* length */ + { + c->sub.copy.get = e & 15; + c->len = t->base; + c->mode = LENEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + if (e & 32) /* end of block */ + { + Tracevv(("inflate: end of block\n")); + c->mode = WASH; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid literal/length code"; + r = Z_DATA_ERROR; + LEAVE + case LENEXT: /* i: getting length extra (have base) */ + j = c->sub.copy.get; + NEEDBITS(j) + c->len += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + c->sub.code.need = c->dbits; + c->sub.code.tree = c->dtree; + Tracevv(("inflate: length %u\n", c->len)); + c->mode = DIST; + case DIST: /* i: get distance next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e & 16) /* distance */ + { + c->sub.copy.get = e & 15; + c->sub.copy.dist = t->base; + c->mode = DISTEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid distance code"; + r = Z_DATA_ERROR; + LEAVE + case DISTEXT: /* i: getting distance extra */ + j = c->sub.copy.get; + NEEDBITS(j) + c->sub.copy.dist += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + Tracevv(("inflate: distance %u\n", c->sub.copy.dist)); + c->mode = COPY; + case COPY: /* o: copying bytes in window, waiting for space */ +#ifndef __TURBOC__ /* Turbo C bug for following expression */ + f = (uInt)(q - s->window) < c->sub.copy.dist ? + s->end - (c->sub.copy.dist - (q - s->window)) : + q - c->sub.copy.dist; +#else + f = q - c->sub.copy.dist; + if ((uInt)(q - s->window) < c->sub.copy.dist) + f = s->end - (c->sub.copy.dist - (uInt)(q - s->window)); +#endif + while (c->len) + { + NEEDOUT + OUTBYTE(*f++) + if (f == s->end) + f = s->window; + c->len--; + } + c->mode = START; + break; + case LIT: /* o: got literal, waiting for output space */ + NEEDOUT + OUTBYTE(c->sub.lit) + c->mode = START; + break; + case WASH: /* o: got eob, possibly more output */ + if (k > 7) /* return unused byte, if any */ + { + Assert(k < 16, "inflate_codes grabbed too many bytes") + k -= 8; + n++; + p--; /* can always return one */ + } + FLUSH + if (s->read != s->write) + LEAVE + c->mode = END; + case END: + r = Z_STREAM_END; + LEAVE + case BADCODE: /* x: got error */ + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} + + +void inflate_codes_free(inflate_codes_statef *c, z_streamp z) +{ + ZFREE(z, c); + Tracev(("inflate: codes free\n")); +} + +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#define BASE 65521L /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#undef DO1 +#undef DO2 +#undef DO4 +#undef DO8 + +#define DO1(buf,i) {s1 += buf[i]; s2 += s1;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* ========================================================================= */ +uLong adler32(uLong adler, const Byte *buf, uInt len) +{ + unsigned long s1 = adler & 0xffff; + unsigned long s2 = (adler >> 16) & 0xffff; + int k; + + if (buf == Z_NULL) return 1L; + + while (len > 0) { + k = len < NMAX ? len : NMAX; + len -= k; + while (k >= 16) { + DO16(buf); + buf += 16; + k -= 16; + } + if (k != 0) do { + s1 += *buf++; + s2 += s1; + } while (--k); + s1 %= BASE; + s2 %= BASE; + } + return (s2 << 16) | s1; +} + +/* @(#) $Id: unzip.c,v 1.2 1999/09/07 20:51:25 zoid Exp $ */ + +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +extern inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +extern int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +extern void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLong *)); /* check value on output */ + +extern int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +extern void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Byte *d, /* dictionary */ + uInt n)); /* dictionary length */ + +extern int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); + +typedef enum { + imMETHOD, /* waiting for method byte */ + imFLAG, /* waiting for flag byte */ + imDICT4, /* four dictionary check bytes to go */ + imDICT3, /* three dictionary check bytes to go */ + imDICT2, /* two dictionary check bytes to go */ + imDICT1, /* one dictionary check byte to go */ + imDICT0, /* waiting for inflateSetDictionary */ + imBLOCKS, /* decompressing blocks */ + imCHECK4, /* four check bytes to go */ + imCHECK3, /* three check bytes to go */ + imCHECK2, /* two check bytes to go */ + imCHECK1, /* one check byte to go */ + imDONE, /* finished check, done */ + imBAD} /* got an error--stay here */ +inflate_mode; + +/* inflate private state */ +struct internal_state { + + /* mode */ + inflate_mode mode; /* current inflate mode */ + + /* mode dependent information */ + union { + uInt method; /* if FLAGS, method byte */ + struct { + uLong was; /* computed check value */ + uLong need; /* stream check value */ + } check; /* if CHECK, check values to compare */ + uInt marker; /* if BAD, inflateSync's marker bytes count */ + } sub; /* submode */ + + /* mode independent information */ + int nowrap; /* flag for no wrapper */ + uInt wbits; /* log2(window size) (8..15, defaults to 15) */ + inflate_blocks_statef + *blocks; /* current inflate_blocks state */ + +}; + + +int inflateReset(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + z->total_in = z->total_out = 0; + z->msg = Z_NULL; + z->state->mode = z->state->nowrap ? imBLOCKS : imMETHOD; + inflate_blocks_reset(z->state->blocks, z, Z_NULL); + Tracev(("inflate: reset\n")); + return Z_OK; +} + + +int inflateEnd(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->blocks != Z_NULL) + inflate_blocks_free(z->state->blocks, z); + ZFREE(z, z->state); + z->state = Z_NULL; + Tracev(("inflate: end\n")); + return Z_OK; +} + + + +int inflateInit2_(z_streamp z, int w, const char *version, int stream_size) +{ + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != sizeof(z_stream)) + return Z_VERSION_ERROR; + + /* initialize state */ + if (z == Z_NULL) + return Z_STREAM_ERROR; + z->msg = Z_NULL; + if (z->zalloc == Z_NULL) + { + z->zalloc = (void *(*)(void *, unsigned, unsigned))zcalloc; + z->opaque = (voidp)0; + } + if (z->zfree == Z_NULL) z->zfree = (void (*)(void *, void *))zcfree; + if ((z->state = (struct internal_state *) + ZALLOC(z,1,sizeof(struct internal_state))) == Z_NULL) + return Z_MEM_ERROR; + z->state->blocks = Z_NULL; + + /* handle undocumented nowrap option (no zlib header or check) */ + z->state->nowrap = 0; + if (w < 0) + { + w = - w; + z->state->nowrap = 1; + } + + /* set window size */ + if (w < 8 || w > 15) + { + inflateEnd(z); + return Z_STREAM_ERROR; + } + z->state->wbits = (uInt)w; + + /* create inflate_blocks state */ + if ((z->state->blocks = + inflate_blocks_new(z, z->state->nowrap ? Z_NULL : adler32, (uInt)1 << w)) + == Z_NULL) + { + inflateEnd(z); + return Z_MEM_ERROR; + } + Tracev(("inflate: allocated\n")); + + /* reset state */ + inflateReset(z); + return Z_OK; +} + + +int inflateInit_(z_streamp z, const char *version, int stream_size) +{ + return inflateInit2_(z, DEF_WBITS, version, stream_size); +} + + +#define iNEEDBYTE {if(z->avail_in==0)return r;r=f;} +#define iNEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) + +int inflate(z_streamp z, int f) +{ + int r; + uInt b; + + if (z == Z_NULL || z->state == Z_NULL || z->next_in == Z_NULL) + return Z_STREAM_ERROR; + f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; + r = Z_BUF_ERROR; + while (1) switch (z->state->mode) + { + case imMETHOD: + iNEEDBYTE + if (((z->state->sub.method = iNEXTBYTE) & 0xf) != Z_DEFLATED) + { + z->state->mode = imBAD; + z->msg = (char*)"unknown compression method"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + if ((z->state->sub.method >> 4) + 8 > z->state->wbits) + { + z->state->mode = imBAD; + z->msg = (char*)"invalid window size"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + z->state->mode = imFLAG; + case imFLAG: + iNEEDBYTE + b = iNEXTBYTE; + if (((z->state->sub.method << 8) + b) % 31) + { + z->state->mode = imBAD; + z->msg = (char*)"incorrect header check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev(("inflate: zlib header ok\n")); + if (!(b & PRESET_DICT)) + { + z->state->mode = imBLOCKS; + break; + } + z->state->mode = imDICT4; + case imDICT4: + iNEEDBYTE + z->state->sub.check.need = (uLong)iNEXTBYTE << 24; + z->state->mode = imDICT3; + case imDICT3: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 16; + z->state->mode = imDICT2; + case imDICT2: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 8; + z->state->mode = imDICT1; + case imDICT1: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE; + z->adler = z->state->sub.check.need; + z->state->mode = imDICT0; + return Z_NEED_DICT; + case imDICT0: + z->state->mode = imBAD; + z->msg = (char*)"need dictionary"; + z->state->sub.marker = 0; /* can try inflateSync */ + return Z_STREAM_ERROR; + case imBLOCKS: + r = inflate_blocks(z->state->blocks, z, r); + if (r == Z_DATA_ERROR) + { + z->state->mode = imBAD; + z->state->sub.marker = 0; /* can try inflateSync */ + break; + } + if (r == Z_OK) + r = f; + if (r != Z_STREAM_END) + return r; + r = f; + inflate_blocks_reset(z->state->blocks, z, &z->state->sub.check.was); + if (z->state->nowrap) + { + z->state->mode = imDONE; + break; + } + z->state->mode = imCHECK4; + case imCHECK4: + iNEEDBYTE + z->state->sub.check.need = (uLong)iNEXTBYTE << 24; + z->state->mode = imCHECK3; + case imCHECK3: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 16; + z->state->mode = imCHECK2; + case imCHECK2: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE << 8; + z->state->mode = imCHECK1; + case imCHECK1: + iNEEDBYTE + z->state->sub.check.need += (uLong)iNEXTBYTE; + + if (z->state->sub.check.was != z->state->sub.check.need) + { + z->state->mode = imBAD; + z->msg = (char*)"incorrect data check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev(("inflate: zlib check ok\n")); + z->state->mode = imDONE; + case imDONE: + return Z_STREAM_END; + case imBAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} + + +int inflateSetDictionary(z_streamp z, const Byte *dictionary, uInt dictLength) +{ + uInt length = dictLength; + + if (z == Z_NULL || z->state == Z_NULL || z->state->mode != imDICT0) + return Z_STREAM_ERROR; + + if (adler32(1L, dictionary, dictLength) != z->adler) return Z_DATA_ERROR; + z->adler = 1L; + + if (length >= ((uInt)1<state->wbits)) + { + length = (1<state->wbits)-1; + dictionary += dictLength - length; + } + inflate_set_dictionary(z->state->blocks, dictionary, length); + z->state->mode = imBLOCKS; + return Z_OK; +} + + +int inflateSync(z_streamp z) +{ + uInt n; /* number of bytes to look at */ + Byte *p; /* pointer to bytes */ + uInt m; /* number of marker bytes found in a row */ + uLong r, w; /* temporaries to save total_in and total_out */ + + /* set up */ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->mode != imBAD) + { + z->state->mode = imBAD; + z->state->sub.marker = 0; + } + if ((n = z->avail_in) == 0) + return Z_BUF_ERROR; + p = z->next_in; + m = z->state->sub.marker; + + /* search */ + while (n && m < 4) + { + static const Byte mark[4] = {0, 0, 0xff, 0xff}; + if (*p == mark[m]) + m++; + else if (*p) + m = 0; + else + m = 4 - m; + p++, n--; + } + + /* restore */ + z->total_in += p - z->next_in; + z->next_in = p; + z->avail_in = n; + z->state->sub.marker = m; + + /* return no joy or set up to restart on a new block */ + if (m != 4) + return Z_DATA_ERROR; + r = z->total_in; w = z->total_out; + inflateReset(z); + z->total_in = r; z->total_out = w; + z->state->mode = imBLOCKS; + return Z_OK; +} + + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + * implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + * but removes the length bytes of the resulting empty stored block. When + * decompressing, PPP checks that at the end of input packet, inflate is + * waiting for these length bytes. + */ +int inflateSyncPoint(z_streamp z) +{ + if (z == Z_NULL || z->state == Z_NULL || z->state->blocks == Z_NULL) + return Z_STREAM_ERROR; + return inflate_blocks_sync_point(z->state->blocks); +} + +voidp zcalloc (voidp opaque, unsigned items, unsigned size) +{ + if (opaque) items += size - size; /* make compiler happy */ + return (voidp)Mem_ClearedAlloc(items*size); +} + +void zcfree (voidp opaque, voidp ptr) +{ + Mem_Free(ptr); + if (opaque) return; /* make compiler happy */ +} diff --git a/src/framework/usercmdgen.cpp b/src/framework/usercmdgen.cpp new file mode 100644 index 0000000..5c59969 --- /dev/null +++ b/src/framework/usercmdgen.cpp @@ -0,0 +1,1235 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +================ +usercmd_t::ByteSwap +================ +*/ +void usercmd_t::ByteSwap( void ) { + angles[0] = LittleShort( angles[0] ); + angles[1] = LittleShort( angles[1] ); + angles[2] = LittleShort( angles[2] ); + sequence = LittleLong( sequence ); +} + +/* +================ +usercmd_t::operator== +================ +*/ +bool usercmd_t::operator==( const usercmd_t &rhs ) const { + return ( buttons == rhs.buttons && + forwardmove == rhs.forwardmove && + rightmove == rhs.rightmove && + upmove == rhs.upmove && + angles[0] == rhs.angles[0] && + angles[1] == rhs.angles[1] && + angles[2] == rhs.angles[2] && + impulse == rhs.impulse && + flags == rhs.flags && + mx == rhs.mx && + my == rhs.my ); +} + + +typedef enum { + UB_NONE, + + UB_UP, + UB_DOWN, + UB_LEFT, + UB_RIGHT, + UB_FORWARD, + UB_BACK, + UB_LOOKUP, + UB_LOOKDOWN, + UB_STRAFE, + UB_MOVELEFT, + UB_MOVERIGHT, + + UB_BUTTON0, + UB_BUTTON1, + UB_BUTTON2, + UB_BUTTON3, + UB_BUTTON4, + UB_BUTTON5, + UB_BUTTON6, + UB_BUTTON7, + + UB_ATTACK, + UB_SPEED, + UB_ZOOM, + UB_SHOWSCORES, + UB_MLOOK, + UB_INGAMESTATS, + UB_VOICECHAT, + UB_TOURNEY, + + UB_IMPULSE0, + UB_IMPULSE1, + UB_IMPULSE2, + UB_IMPULSE3, + UB_IMPULSE4, + UB_IMPULSE5, + UB_IMPULSE6, + UB_IMPULSE7, + UB_IMPULSE8, + UB_IMPULSE9, + UB_IMPULSE10, + UB_IMPULSE11, + UB_IMPULSE12, + UB_IMPULSE13, + UB_IMPULSE14, + UB_IMPULSE15, + UB_IMPULSE16, + UB_IMPULSE17, + UB_IMPULSE18, + UB_IMPULSE19, + UB_IMPULSE20, + UB_IMPULSE21, + UB_IMPULSE22, + UB_IMPULSE23, + UB_IMPULSE24, + UB_IMPULSE25, + UB_IMPULSE26, + UB_IMPULSE27, + UB_IMPULSE28, + UB_IMPULSE29, + UB_IMPULSE30, + UB_IMPULSE31, + UB_IMPULSE32, + UB_IMPULSE33, + UB_IMPULSE34, + UB_IMPULSE35, + UB_IMPULSE36, + UB_IMPULSE37, + UB_IMPULSE38, + UB_IMPULSE39, + UB_IMPULSE40, + UB_IMPULSE41, + UB_IMPULSE42, + UB_IMPULSE43, + UB_IMPULSE44, + UB_IMPULSE45, + UB_IMPULSE46, + UB_IMPULSE47, + UB_IMPULSE48, + UB_IMPULSE49, + UB_IMPULSE50, + UB_IMPULSE51, + UB_IMPULSE52, + UB_IMPULSE53, + UB_IMPULSE54, + UB_IMPULSE55, + UB_IMPULSE56, + UB_IMPULSE57, + UB_IMPULSE58, + UB_IMPULSE59, + UB_IMPULSE60, + UB_IMPULSE61, + UB_IMPULSE62, + UB_IMPULSE63, + UB_IMPULSE100, + UB_IMPULSE101, + UB_IMPULSE102, + UB_IMPULSE103, + UB_IMPULSE104, + UB_IMPULSE105, + UB_IMPULSE106, + UB_IMPULSE107, + UB_IMPULSE108, + UB_IMPULSE109, + UB_IMPULSE110, + UB_IMPULSE111, + UB_IMPULSE112, + UB_IMPULSE113, + UB_IMPULSE114, + UB_IMPULSE115, + UB_IMPULSE116, + UB_IMPULSE117, + UB_IMPULSE118, + UB_IMPULSE119, + UB_IMPULSE120, + UB_IMPULSE121, + UB_IMPULSE122, + UB_IMPULSE123, + UB_IMPULSE124, + UB_IMPULSE125, + UB_IMPULSE126, + UB_IMPULSE127, + + UB_MAX_BUTTONS +} usercmdButton_t; + +typedef struct { + const char *string; + usercmdButton_t button; +} userCmdString_t; + +userCmdString_t userCmdStrings[] = { + { "_moveUp", UB_UP }, + { "_moveDown", UB_DOWN }, + { "_left", UB_LEFT }, + { "_right", UB_RIGHT }, + { "_forward", UB_FORWARD }, + { "_back", UB_BACK }, + { "_lookUp", UB_LOOKUP }, + { "_lookDown", UB_LOOKDOWN }, + { "_strafe", UB_STRAFE }, + { "_moveLeft", UB_MOVELEFT }, + { "_moveRight", UB_MOVERIGHT }, + + { "_attack", UB_ATTACK }, + { "_speed", UB_SPEED }, + { "_zoom", UB_ZOOM }, + { "_showScores", UB_SHOWSCORES }, + { "_mlook", UB_MLOOK }, + { "_ingameStats", UB_INGAMESTATS }, + { "_voicechat", UB_VOICECHAT }, + { "_tourney", UB_TOURNEY }, + + { "_button0", UB_BUTTON0 }, + { "_button1", UB_BUTTON1 }, + { "_button2", UB_BUTTON2 }, + { "_button3", UB_BUTTON3 }, + { "_button4", UB_BUTTON4 }, + { "_button5", UB_BUTTON5 }, + { "_button6", UB_BUTTON6 }, + { "_button7", UB_BUTTON7 }, + + { "_impulse0", UB_IMPULSE0 }, + { "_impulse1", UB_IMPULSE1 }, + { "_impulse2", UB_IMPULSE2 }, + { "_impulse3", UB_IMPULSE3 }, + { "_impulse4", UB_IMPULSE4 }, + { "_impulse5", UB_IMPULSE5 }, + { "_impulse6", UB_IMPULSE6 }, + { "_impulse7", UB_IMPULSE7 }, + { "_impulse8", UB_IMPULSE8 }, + { "_impulse9", UB_IMPULSE9 }, + { "_impulse10", UB_IMPULSE10 }, + { "_impulse11", UB_IMPULSE11 }, + { "_impulse12", UB_IMPULSE12 }, + { "_impulse13", UB_IMPULSE13 }, + { "_impulse14", UB_IMPULSE14 }, + { "_impulse15", UB_IMPULSE15 }, + { "_impulse16", UB_IMPULSE16 }, + { "_impulse17", UB_IMPULSE17 }, + { "_impulse18", UB_IMPULSE18 }, + { "_impulse19", UB_IMPULSE19 }, + { "_impulse20", UB_IMPULSE20 }, + { "_impulse21", UB_IMPULSE21 }, + { "_impulse22", UB_IMPULSE22 }, + { "_impulse23", UB_IMPULSE23 }, + { "_impulse24", UB_IMPULSE24 }, + { "_impulse25", UB_IMPULSE25 }, + { "_impulse26", UB_IMPULSE26 }, + { "_impulse27", UB_IMPULSE27 }, + { "_impulse28", UB_IMPULSE28 }, + { "_impulse29", UB_IMPULSE29 }, + { "_impulse30", UB_IMPULSE30 }, + { "_impulse31", UB_IMPULSE31 }, + { "_impulse32", UB_IMPULSE32 }, + { "_impulse33", UB_IMPULSE33 }, + { "_impulse34", UB_IMPULSE34 }, + { "_impulse35", UB_IMPULSE35 }, + { "_impulse36", UB_IMPULSE36 }, + { "_impulse37", UB_IMPULSE37 }, + { "_impulse38", UB_IMPULSE38 }, + { "_impulse39", UB_IMPULSE39 }, + { "_impulse40", UB_IMPULSE40 }, + { "_impulse41", UB_IMPULSE41 }, + { "_impulse42", UB_IMPULSE42 }, + { "_impulse43", UB_IMPULSE43 }, + { "_impulse44", UB_IMPULSE44 }, + { "_impulse45", UB_IMPULSE45 }, + { "_impulse46", UB_IMPULSE46 }, + { "_impulse47", UB_IMPULSE47 }, + { "_impulse48", UB_IMPULSE48 }, + { "_impulse49", UB_IMPULSE49 }, + { "_impulse50", UB_IMPULSE50 }, + { "_impulse51", UB_IMPULSE51 }, + { "_impulse52", UB_IMPULSE52 }, + { "_impulse53", UB_IMPULSE53 }, + { "_impulse54", UB_IMPULSE54 }, + { "_impulse55", UB_IMPULSE55 }, + { "_impulse56", UB_IMPULSE56 }, + { "_impulse57", UB_IMPULSE57 }, + { "_impulse58", UB_IMPULSE58 }, + { "_impulse59", UB_IMPULSE59 }, + { "_impulse60", UB_IMPULSE60 }, + { "_impulse61", UB_IMPULSE61 }, + { "_impulse62", UB_IMPULSE62 }, + { "_impulse63", UB_IMPULSE63 }, + { "_impulse100", UB_IMPULSE100 }, + { "_impulse101", UB_IMPULSE101 }, + { "_impulse102", UB_IMPULSE102 }, + { "_impulse103", UB_IMPULSE103 }, + { "_impulse104", UB_IMPULSE104 }, + { "_impulse105", UB_IMPULSE105 }, + { "_impulse106", UB_IMPULSE106 }, + { "_impulse107", UB_IMPULSE107 }, + { "_impulse108", UB_IMPULSE108 }, + { "_impulse109", UB_IMPULSE109 }, + { "_impulse110", UB_IMPULSE110 }, + { "_impulse111", UB_IMPULSE111 }, + { "_impulse112", UB_IMPULSE112 }, + { "_impulse113", UB_IMPULSE113 }, + { "_impulse114", UB_IMPULSE114 }, + { "_impulse115", UB_IMPULSE115 }, + { "_impulse116", UB_IMPULSE116 }, + { "_impulse117", UB_IMPULSE117 }, + { "_impulse118", UB_IMPULSE118 }, + { "_impulse119", UB_IMPULSE119 }, + { "_impulse120", UB_IMPULSE120 }, + { "_impulse121", UB_IMPULSE121 }, + { "_impulse122", UB_IMPULSE122 }, + { "_impulse123", UB_IMPULSE123 }, + { "_impulse124", UB_IMPULSE124 }, + { "_impulse125", UB_IMPULSE125 }, + { "_impulse126", UB_IMPULSE126 }, + { "_impulse127", UB_IMPULSE127 }, + + { NULL, UB_NONE }, +}; + + class buttonState_t { + public: + int on; + bool held; + + buttonState_t() { Clear(); }; + void Clear( void ); + void SetKeyState( int keystate, bool toggle ); +}; + +/* +================ +buttonState_t::Clear +================ +*/ +void buttonState_t::Clear( void ) { + held = false; + on = 0; +} + +/* +================ +buttonState_t::SetKeyState +================ +*/ +void buttonState_t::SetKeyState( int keystate, bool toggle ) { + if ( !toggle ) { + held = false; + on = keystate; + } else if ( !keystate ) { + held = false; + } else if ( !held ) { + held = true; + on ^= 1; + } +} + + +const int NUM_USER_COMMANDS = sizeof(userCmdStrings) / sizeof(userCmdString_t); + +const int MAX_CHAT_BUFFER = 127; + +class idUsercmdGenLocal : public idUsercmdGen { +public: + idUsercmdGenLocal( void ); + + void Init( void ); + + void InitForNewMap( void ); + + void Shutdown( void ); + + void Clear( void ); + + void ClearAngles( void ); + + usercmd_t TicCmd( int ticNumber ); + + void InhibitUsercmd( inhibit_t subsystem, bool inhibit ); + + void UsercmdInterrupt( void ); + + int CommandStringUsercmdData( const char *cmdString ); + + int GetNumUserCommands( void ); + + const char * GetUserCommandName( int index ); + + void MouseState( int *x, int *y, int *button, bool *down ); + + int ButtonState( int key ); + int KeyState( int key ); + + usercmd_t GetDirectUsercmd( void ); + void SetSlowJoystick( int slow ); + void StuffImpulse( int impulse ); + +private: + void MakeCurrent( void ); + void InitCurrent( void ); + + bool Inhibited( void ); + void AdjustAngles( void ); + void KeyMove( void ); + void JoystickMove( void ); + void MouseMove( void ); + void CmdButtons( void ); + + void Mouse( void ); + void Keyboard( void ); + void Joystick( void ); + + void Key( int keyNum, bool down ); + + idVec3 viewangles; + int flags; + int impulse; + + buttonState_t toggled_crouch; + buttonState_t toggled_run; + buttonState_t toggled_zoom; + + int buttonState[UB_MAX_BUTTONS]; + bool keyState[K_LAST_KEY]; + + int inhibitCommands; // true when in console or menu locally + int lastCommandTime; + + bool initialized; + + usercmd_t cmd; // the current cmd being built + usercmd_t buffered[MAX_BUFFERED_USERCMD]; + + int continuousMouseX, continuousMouseY; // for gui event generatioin, never zerod + int mouseButton; // for gui event generatioin + bool mouseDown; + + int mouseDx, mouseDy; // added to by mouse events + int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + int slowJoystick; + int stuffedImpulse; + + static idCVar in_yawSpeed; + static idCVar in_pitchSpeed; + static idCVar in_angleSpeedKey; + static idCVar in_freeLook; + static idCVar in_alwaysRun; + static idCVar in_toggleRun; + static idCVar in_toggleCrouch; + static idCVar in_toggleZoom; + static idCVar in_joystickLeftStickMove; + static idCVar sensitivity; + static idCVar m_pitch; + static idCVar m_yaw; + static idCVar m_strafeScale; + static idCVar m_smooth; + static idCVar m_strafeSmooth; + static idCVar m_showMouseRate; + static idCVar m_maxInput; +}; + +#if defined( _WIN32 ) && defined( Q4_RECON_ENGINE_PRIVATE ) +static_assert( sizeof( usercmd_t ) == 0x28, "usercmd_t ABI drift" ); +static_assert( sizeof( idUsercmdGenLocal ) == 0xD74, "idUsercmdGenLocal ABI drift" ); +#endif + +idCVar idUsercmdGenLocal::in_yawSpeed( "in_yawspeed", "140", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "yaw change speed when holding down _left or _right button" ); +idCVar idUsercmdGenLocal::in_pitchSpeed( "in_pitchspeed", "140", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "pitch change speed when holding down look _lookUp or _lookDown button" ); +idCVar idUsercmdGenLocal::in_angleSpeedKey( "in_anglespeedkey", "1.5", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "angle change scale when holding down _speed button" ); +idCVar idUsercmdGenLocal::in_freeLook( "in_freeLook", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "look around with mouse (reverse _mlook button)" ); +idCVar idUsercmdGenLocal::in_alwaysRun( "in_alwaysRun", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "always run (reverse _speed button) - only in MP" ); +idCVar idUsercmdGenLocal::in_toggleRun( "in_toggleRun", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _speed button toggles run on/off - only in MP" ); +idCVar idUsercmdGenLocal::in_toggleCrouch( "in_toggleCrouch", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _movedown button toggles player crouching/standing" ); +idCVar idUsercmdGenLocal::in_toggleZoom( "in_toggleZoom", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "pressing _zoom button toggles zoom on/off" ); +idCVar idUsercmdGenLocal::in_joystickLeftStickMove( "in_joystickLeftStickMove", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "left or right joystick controls move" ); +idCVar idUsercmdGenLocal::sensitivity( "sensitivity", "5", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse view sensitivity" ); +idCVar idUsercmdGenLocal::m_pitch( "m_pitch", "0.022", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse pitch scale" ); +idCVar idUsercmdGenLocal::m_yaw( "m_yaw", "0.022", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse yaw scale" ); +idCVar idUsercmdGenLocal::m_strafeScale( "m_strafeScale", "6.25", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_FLOAT, "mouse strafe movement scale" ); +idCVar idUsercmdGenLocal::m_smooth( "m_smooth", "1", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse viewing", 1, 8, idCmdSystem::ArgCompletion_Integer<1,8> ); +idCVar idUsercmdGenLocal::m_strafeSmooth( "m_strafeSmooth", "4", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "number of samples blended for mouse moving", 1, 8, idCmdSystem::ArgCompletion_Integer<1,8> ); +idCVar idUsercmdGenLocal::m_showMouseRate( "m_showMouseRate", "0", CVAR_SYSTEM | CVAR_BOOL, "shows mouse movement" ); +idCVar idUsercmdGenLocal::m_maxInput( "m_maxInput", "0", CVAR_SYSTEM, "caps the amount of mouse movement possible" ); + +static idUsercmdGenLocal localUsercmdGen; +idUsercmdGen *usercmdGen = &localUsercmdGen; + +/* +================ +idUsercmdGenLocal::idUsercmdGenLocal +================ +*/ +idUsercmdGenLocal::idUsercmdGenLocal( void ) { + lastCommandTime = 0; + initialized = false; + + flags = 0; + impulse = 0; + + toggled_crouch.Clear(); + toggled_run.Clear(); + toggled_zoom.Clear(); + toggled_run.on = in_alwaysRun.GetBool(); + + ClearAngles(); + Clear(); + slowJoystick = 100; +} + +/* +================ +idUsercmdGenLocal::InhibitUsercmd +================ +*/ +void idUsercmdGenLocal::InhibitUsercmd( inhibit_t subsystem, bool inhibit ) { + if ( inhibit ) { + inhibitCommands |= 1 << subsystem; + } else { + inhibitCommands &= ( 0xffffffff ^ ( 1 << subsystem ) ); + } +} + +/* +=============== +idUsercmdGenLocal::ButtonState + +Returns (the fraction of the frame) that the key was down +=============== +*/ +int idUsercmdGenLocal::ButtonState( int key ) { + if ( key<0 || key>=UB_MAX_BUTTONS ) { + return -1; + } + return ( buttonState[key] > 0 ) ? 1 : 0; +} + +/* +=============== +idUsercmdGenLocal::KeyState + +Returns (the fraction of the frame) that the key was down +bk20060111 +=============== +*/ +int idUsercmdGenLocal::KeyState( int key ) { + if ( key<0 || key>=K_LAST_KEY ) { + return -1; + } + return ( keyState[key] ) ? 1 : 0; +} + + +//===================================================================== + + +/* +================ +idUsercmdGenLocal::GetNumUserCommands +================ +*/ +int idUsercmdGenLocal::GetNumUserCommands( void ) { + return NUM_USER_COMMANDS; +} + +/* +================ +idUsercmdGenLocal::GetNumUserCommands +================ +*/ +const char *idUsercmdGenLocal::GetUserCommandName( int index ) { + if (index >= 0 && index < NUM_USER_COMMANDS) { + return userCmdStrings[index].string; + } + return ""; +} + +/* +================ +idUsercmdGenLocal::Inhibited + +is user cmd generation inhibited +================ +*/ +bool idUsercmdGenLocal::Inhibited( void ) { + return ( inhibitCommands != 0); +} + +/* +================ +idUsercmdGenLocal::AdjustAngles + +Moves the local angle positions +================ +*/ +void idUsercmdGenLocal::AdjustAngles( void ) { + float speed; + + if ( toggled_run.on ^ in_alwaysRun.GetBool() ) { + speed = idMath::M_MS2SEC * common->GetUserCmdMSec() * in_angleSpeedKey.GetFloat(); + } else { + speed = idMath::M_MS2SEC * common->GetUserCmdMSec(); + } + + if ( !ButtonState( UB_STRAFE ) ) { + viewangles[YAW] -= speed * in_yawSpeed.GetFloat() * ButtonState( UB_RIGHT ); + viewangles[YAW] += speed * in_yawSpeed.GetFloat() * ButtonState( UB_LEFT ); + } + + viewangles[PITCH] -= speed * in_pitchSpeed.GetFloat() * ButtonState( UB_LOOKUP ); + viewangles[PITCH] += speed * in_pitchSpeed.GetFloat() * ButtonState( UB_LOOKDOWN ); +} + +/* +================ +idUsercmdGenLocal::KeyMove + +Sets the usercmd_t based on key states +================ +*/ +void idUsercmdGenLocal::KeyMove( void ) { + int forward, side, up; + + forward = 0; + side = 0; + up = 0; + if ( ButtonState( UB_STRAFE ) ) { + side += KEY_MOVESPEED * ButtonState( UB_RIGHT ); + side -= KEY_MOVESPEED * ButtonState( UB_LEFT ); + } + + side += KEY_MOVESPEED * ButtonState( UB_MOVERIGHT ); + side -= KEY_MOVESPEED * ButtonState( UB_MOVELEFT ); + + up -= KEY_MOVESPEED * toggled_crouch.on; + up += KEY_MOVESPEED * ButtonState( UB_UP ); + + forward += KEY_MOVESPEED * ButtonState( UB_FORWARD ); + forward -= KEY_MOVESPEED * ButtonState( UB_BACK ); + + cmd.forwardmove = idMath::ClampChar( forward ); + cmd.rightmove = idMath::ClampChar( side ); + cmd.upmove = idMath::ClampChar( up ); +} + +/* +================= +idUsercmdGenLocal::MouseMove +================= +*/ +void idUsercmdGenLocal::MouseMove( void ) { + float mx, my, strafeMx, strafeMy; + static int history[8][2]; + static int historyCounter; + int i; + + history[historyCounter&7][0] = mouseDx; + history[historyCounter&7][1] = mouseDy; + + // allow mouse movement to be smoothed together + int smooth = m_smooth.GetInteger(); + if ( smooth < 1 ) { + smooth = 1; + } + if ( smooth > 8 ) { + smooth = 8; + } + mx = 0; + my = 0; + for ( i = 0 ; i < smooth ; i++ ) { + mx += history[ ( historyCounter - i + 8 ) & 7 ][0]; + my += history[ ( historyCounter - i + 8 ) & 7 ][1]; + } + mx /= smooth; + my /= smooth; + + // use a larger smoothing for strafing + smooth = m_strafeSmooth.GetInteger(); + if ( smooth < 1 ) { + smooth = 1; + } + if ( smooth > 8 ) { + smooth = 8; + } + strafeMx = 0; + strafeMy = 0; + for ( i = 0 ; i < smooth ; i++ ) { + strafeMx += history[ ( historyCounter - i + 8 ) & 7 ][0]; + strafeMy += history[ ( historyCounter - i + 8 ) & 7 ][1]; + } + strafeMx /= smooth; + strafeMy /= smooth; + + historyCounter++; + + if ( idMath::Fabs( mx ) > 1000 || idMath::Fabs( my ) > 1000 ) { + Sys_DebugPrintf( "idUsercmdGenLocal::MouseMove: Ignoring ridiculous mouse delta.\n" ); + mx = my = 0; + } + + if ( m_maxInput.GetFloat() != 0.0f ) { + if ( idMath::Fabs( mx ) > m_maxInput.GetFloat() ) { + mx = mx / idMath::Fabs( mx ) * m_maxInput.GetFloat(); + } + if ( idMath::Fabs( my ) > m_maxInput.GetFloat() ) { + my = my / idMath::Fabs( my ) * m_maxInput.GetFloat(); + } + } + + mx *= sensitivity.GetFloat(); + my *= sensitivity.GetFloat(); + + if ( m_showMouseRate.GetBool() ) { + Sys_DebugPrintf( "[%3i %3i = %5.1f %5.1f = %5.1f %5.1f] ", mouseDx, mouseDy, mx, my, strafeMx, strafeMy ); + } + + mouseDx = 0; + mouseDy = 0; + + if ( !strafeMx && !strafeMy ) { + return; + } + + if ( ButtonState( UB_STRAFE ) || !( cmd.buttons & BUTTON_MLOOK ) ) { + // add mouse X/Y movement to cmd + strafeMx *= m_strafeScale.GetFloat(); + strafeMy *= m_strafeScale.GetFloat(); + // clamp as a vector, instead of separate floats + float len = sqrt( strafeMx * strafeMx + strafeMy * strafeMy ); + if ( len > 127 ) { + strafeMx = strafeMx * 127 / len; + strafeMy = strafeMy * 127 / len; + } + } + + if ( !ButtonState( UB_STRAFE ) ) { + viewangles[YAW] -= m_yaw.GetFloat() * mx; + } else { + cmd.rightmove = idMath::ClampChar( (int)(cmd.rightmove + strafeMx) ); + } + + if ( !ButtonState( UB_STRAFE ) && ( cmd.buttons & BUTTON_MLOOK ) ) { + viewangles[PITCH] += m_pitch.GetFloat() * my; + } else { + cmd.forwardmove = idMath::ClampChar( (int)(cmd.forwardmove - strafeMy) ); + } +} + +/* +================= +idUsercmdGenLocal::JoystickMove +================= +*/ +void idUsercmdGenLocal::JoystickMove( void ) { + const float lookScale = slowJoystick * 0.01f; + viewangles[YAW] -= joystickAxis[AXIS_LEFT_HORIZONTAL] * m_yaw.GetFloat() * lookScale; + viewangles[PITCH] += joystickAxis[AXIS_LEFT_VERTICAL] * m_pitch.GetFloat() * lookScale; + cmd.rightmove += idMath::Ftoi( joystickAxis[AXIS_RIGHT_HORIZONTAL] * ( 1.0f / MAX_AXIS_RANGE ) * KEY_MOVESPEED ); + cmd.forwardmove -= idMath::Ftoi( joystickAxis[AXIS_RIGHT_VERTICAL] * ( 1.0f / MAX_AXIS_RANGE ) * KEY_MOVESPEED ); +} + +/* +============== +idUsercmdGenLocal::CmdButtons +============== +*/ +void idUsercmdGenLocal::CmdButtons( void ) { + int i; + + cmd.buttons = 0; + + // figure button bits + for (i = 0 ; i <= 7 ; i++) { + if ( ButtonState( (usercmdButton_t)( UB_BUTTON0 + i ) ) ) { + cmd.buttons |= 1 << ( i + 8 ); + } + } + + // check the attack button + if ( ButtonState( UB_ATTACK ) ) { + cmd.buttons |= BUTTON_ATTACK; + } + + // check the run button + if ( toggled_run.on ^ in_alwaysRun.GetBool() ) { + cmd.buttons |= BUTTON_RUN; + } + + // check the zoom button + if ( toggled_zoom.on ) { + cmd.buttons |= BUTTON_ZOOM; + } + + // check the scoreboard button + if ( ButtonState( UB_SHOWSCORES ) || ButtonState( UB_IMPULSE19 ) ) { + // the button is toggled in SP mode as well but without effect + cmd.buttons |= BUTTON_SCORES; + } + + // check the mouse look button + if ( ButtonState( UB_MLOOK ) ^ in_freeLook.GetInteger() ) { + cmd.buttons |= BUTTON_MLOOK; + } + + if ( ButtonState( UB_INGAMESTATS ) ) { + cmd.buttons |= BUTTON_INGAMESTATS; + } + if ( ButtonState( UB_VOICECHAT ) ) { + cmd.buttons |= BUTTON_VOICECHAT; + } + if ( ButtonState( UB_TOURNEY ) ) { + cmd.buttons |= BUTTON_TOURNEY; + } + if ( ButtonState( UB_STRAFE ) ) { + cmd.buttons |= BUTTON_STRAFE; + } +} + +/* +================ +idUsercmdGenLocal::InitCurrent + +inits the current command for this frame +================ +*/ +void idUsercmdGenLocal::InitCurrent( void ) { + memset( &cmd, 0, sizeof( cmd ) ); + cmd.flags = flags; + cmd.impulse = impulse; + // The matching retail executable initializes the run bit unconditionally. + // CmdButtons recalculates it for non-inhibited commands. + cmd.buttons |= BUTTON_RUN; + cmd.buttons |= in_freeLook.GetBool() ? BUTTON_MLOOK : 0; +} + +/* +================ +idUsercmdGenLocal::MakeCurrent + +creates the current command for this frame +================ +*/ +void idUsercmdGenLocal::MakeCurrent( void ) { + idVec3 oldAngles; + int i; + + oldAngles = viewangles; + + if ( !Inhibited() ) { + // update toggled key states + toggled_crouch.SetKeyState( ButtonState( UB_DOWN ), in_toggleCrouch.GetBool() ); + toggled_run.SetKeyState( ButtonState( UB_SPEED ), in_toggleRun.GetBool() && idAsyncNetwork::IsActive() ); + toggled_zoom.SetKeyState( ButtonState( UB_ZOOM ), in_toggleZoom.GetBool() ); + + // keyboard angle adjustment + AdjustAngles(); + + // set button bits + CmdButtons(); + + // get basic movement from keyboard + KeyMove(); + + // get basic movement from mouse + MouseMove(); + + // get basic movement from joystick + JoystickMove(); + + // check to make sure the angles haven't wrapped + if ( viewangles[PITCH] - oldAngles[PITCH] > 90 ) { + viewangles[PITCH] = oldAngles[PITCH] + 90; + } else if ( oldAngles[PITCH] - viewangles[PITCH] > 90 ) { + viewangles[PITCH] = oldAngles[PITCH] - 90; + } + } else { + mouseDx = 0; + mouseDy = 0; + } + + for ( i = 0; i < 3; i++ ) { + cmd.angles[i] = ANGLE2SHORT( viewangles[i] ); + } + + cmd.mx = continuousMouseX; + cmd.my = continuousMouseY; + if ( stuffedImpulse != -1 ) { + cmd.flags ^= UCF_IMPULSE_SEQUENCE; + cmd.impulse = stuffedImpulse; + stuffedImpulse = -1; + } + + flags = cmd.flags; + impulse = cmd.impulse; + +} + +//===================================================================== + + +/* +================ +idUsercmdGenLocal::CommandStringUsercmdData + +Returns the button if the command string is used by the async usercmd generator. +================ +*/ +int idUsercmdGenLocal::CommandStringUsercmdData( const char *cmdString ) { + for ( userCmdString_t *ucs = userCmdStrings ; ucs->string ; ucs++ ) { + if ( idStr::Icmp( cmdString, ucs->string ) == 0 ) { + return ucs->button; + } + } + return UB_NONE; +} + +/* +================ +idUsercmdGenLocal::Init +================ +*/ +void idUsercmdGenLocal::Init( void ) { + initialized = true; +} + +/* +================ +idUsercmdGenLocal::InitForNewMap +================ +*/ +void idUsercmdGenLocal::InitForNewMap( void ) { + flags = 0; + impulse = 0; + + toggled_crouch.Clear(); + toggled_run.Clear(); + toggled_zoom.Clear(); + toggled_run.on = in_alwaysRun.GetBool(); + + Clear(); + ClearAngles(); +} + +/* +================ +idUsercmdGenLocal::Shutdown +================ +*/ +void idUsercmdGenLocal::Shutdown( void ) { + initialized = false; +} + +/* +================ +idUsercmdGenLocal::Clear +================ +*/ +void idUsercmdGenLocal::Clear( void ) { + // clears all key states + memset( buttonState, 0, sizeof( buttonState ) ); + memset( keyState, false, sizeof( keyState ) ); + + inhibitCommands = false; + + mouseDx = mouseDy = 0; + mouseButton = 0; + mouseDown = false; +} + +/* +================ +idUsercmdGenLocal::ClearAngles +================ +*/ +void idUsercmdGenLocal::ClearAngles( void ) { + viewangles.Zero(); +} + +/* +================ +idUsercmdGenLocal::TicCmd + +Returns a buffered usercmd +================ +*/ +usercmd_t idUsercmdGenLocal::TicCmd( int ticNumber ) { + + // the packetClient code can legally ask for com_ticNumber+1, because + // it is in the async code and com_ticNumber hasn't been updated yet, + // but all other code should never ask for anything > com_ticNumber + if ( ticNumber > com_ticNumber+1 ) { + common->Error( "idUsercmdGenLocal::TicCmd ticNumber > com_ticNumber" ); + } + + if ( ticNumber <= com_ticNumber - MAX_BUFFERED_USERCMD ) { + // this can happen when something in the game code hitches badly, allowing the + // async code to overflow the buffers + //common->Printf( "warning: idUsercmdGenLocal::TicCmd ticNumber <= com_ticNumber - MAX_BUFFERED_USERCMD\n" ); + } + + return buffered[ ticNumber & (MAX_BUFFERED_USERCMD-1) ]; +} + +//====================================================================== + + +/* +=================== +idUsercmdGenLocal::Key + +Handles async mouse/keyboard button actions +=================== +*/ +void idUsercmdGenLocal::Key( int keyNum, bool down ) { + + // Sanity check, sometimes we get double message :( + if ( keyState[ keyNum ] == down ) { + return; + } + keyState[ keyNum ] = down; + + int action = idKeyInput::GetUsercmdAction( keyNum ); + + if ( down ) { + + buttonState[ action ]++; + + if ( !Inhibited() ) { + if ( action >= UB_IMPULSE0 && action <= UB_MAX_BUTTONS ) { + cmd.impulse = action - UB_IMPULSE0; + cmd.flags ^= UCF_IMPULSE_SEQUENCE; + } + } + } else { + buttonState[ action ]--; + // we might have one held down across an app active transition + if ( buttonState[ action ] < 0 ) { + buttonState[ action ] = 0; + } + } +} + +/* +=================== +idUsercmdGenLocal::Mouse +=================== +*/ +void idUsercmdGenLocal::Mouse( void ) { + int i, numEvents; + + numEvents = Sys_PollMouseInputEvents(); + + if ( numEvents ) { + // + // Study each of the buffer elements and process them. + // + for( i = 0; i < numEvents; i++ ) { + int action, value; + if ( Sys_ReturnMouseInputEvent( i, action, value ) ) { + if ( action >= M_ACTION1 && action <= M_ACTION8 ) { + mouseButton = K_MOUSE1 + ( action - M_ACTION1 ); + mouseDown = ( value != 0 ); + Key( mouseButton, mouseDown ); + } else { + switch ( action ) { + case M_DELTAX: + mouseDx += value; + continuousMouseX += value; + break; + case M_DELTAY: + mouseDy += value; + continuousMouseY += value; + break; + case M_DELTAZ: + int key = value < 0 ? K_MWHEELDOWN : K_MWHEELUP; + value = abs( value ); + while( value-- > 0 ) { + Key( key, true ); + Key( key, false ); + mouseButton = key; + mouseDown = true; + } + break; + } + } + } + } + } + + Sys_EndMouseInputEvents(); +} + +/* +=============== +idUsercmdGenLocal::Keyboard +=============== +*/ +void idUsercmdGenLocal::Keyboard( void ) { + + int numEvents = Sys_PollKeyboardInputEvents(); + + if ( numEvents ) { + // + // Study each of the buffer elements and process them. + // + int key; + bool state; + for( int i = 0; i < numEvents; i++ ) { + if (Sys_ReturnKeyboardInputEvent( i, key, state )) { + Key ( key, state ); + } + } + } + + Sys_EndKeyboardInputEvents(); +} + +/* +=============== +idUsercmdGenLocal::Joystick +=============== +*/ +void idUsercmdGenLocal::Joystick( void ) { + const int numEvents = Sys_PollJoystickInputEvents(); + for ( int i = 0; i < numEvents; i++ ) { + int action; + int value; + if ( !Sys_ReturnJoystickInputEvent( i, action, value ) ) { + continue; + } + + if ( action >= J_ACTION_BUTTON_LEFT_SHOULDER && action <= J_ACTION_BUTTON_RIGHT_TRIGGER ) { + Key( K_JOY1 + action, value != 0 ); + continue; + } + + switch ( action ) { + case J_DELTA_LEFT_HORIZONTAL: + joystickAxis[in_joystickLeftStickMove.GetBool() ? AXIS_RIGHT_HORIZONTAL : AXIS_LEFT_HORIZONTAL] = value; + break; + case J_DELTA_LEFT_VERTICAL: + joystickAxis[in_joystickLeftStickMove.GetBool() ? AXIS_RIGHT_VERTICAL : AXIS_LEFT_VERTICAL] = value; + break; + case J_DELTA_RIGHT_HORIZONTAL: + joystickAxis[in_joystickLeftStickMove.GetBool() ? AXIS_LEFT_HORIZONTAL : AXIS_RIGHT_HORIZONTAL] = value; + break; + case J_DELTA_RIGHT_VERTICAL: + joystickAxis[in_joystickLeftStickMove.GetBool() ? AXIS_LEFT_VERTICAL : AXIS_RIGHT_VERTICAL] = value; + break; + default: + break; + } + } + Sys_EndJoystickInputEvents(); +} + +/* +================ +idUsercmdGenLocal::UsercmdInterrupt + +Called asyncronously +================ +*/ +void idUsercmdGenLocal::UsercmdInterrupt( void ) { + // dedicated servers won't create usercmds + if ( !initialized ) { + return; + } + + // init the usercmd for com_ticNumber+1 + InitCurrent(); + + // process the system mouse events + Mouse(); + + // process the system keyboard events + Keyboard(); + + // process the system joystick events + Joystick(); + + // create the usercmd for com_ticNumber+1 + MakeCurrent(); + + // save a number for debugging cmdDemos and networking + cmd.sequence = com_ticNumber+1; + + buffered[(com_ticNumber+1) & (MAX_BUFFERED_USERCMD-1)] = cmd; +} + +/* +================ +idUsercmdGenLocal::MouseState +================ +*/ +void idUsercmdGenLocal::MouseState( int *x, int *y, int *button, bool *down ) { + *x = continuousMouseX; + *y = continuousMouseY; + *button = mouseButton; + *down = mouseDown; +} + +/* +================ +idUsercmdGenLocal::GetDirectUsercmd +================ +*/ +usercmd_t idUsercmdGenLocal::GetDirectUsercmd( void ) { + + // initialize current usercmd + InitCurrent(); + + // process the system mouse events + Mouse(); + + // process the system keyboard events + Keyboard(); + + // process the system joystick events + Joystick(); + + // create the usercmd + MakeCurrent(); + + cmd.duplicateCount = 0; + + return cmd; +} + +void idUsercmdGenLocal::SetSlowJoystick( int slow ) { + slowJoystick = slow; +} + +void idUsercmdGenLocal::StuffImpulse( int impulseValue ) { + stuffedImpulse = impulseValue; +} diff --git a/src/game/AF.cpp b/src/game/AF.cpp new file mode 100644 index 0000000..804ecab --- /dev/null +++ b/src/game/AF.cpp @@ -0,0 +1,1359 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ +#define ARTICULATED_FIGURE_ANIM "af_pose" +#define POSE_BOUNDS_EXPANSION 5.0f + +/* +================ +idAF::idAF +================ +*/ +idAF::idAF( void ) { + self = NULL; + animator = NULL; + modifiedAnim = 0; + baseOrigin.Zero(); + baseAxis.Identity(); + poseTime = -1; + restStartTime = -1; + isLoaded = false; + isActive = false; + hasBindConstraints = false; +} + +/* +================ +idAF::~idAF +================ +*/ +idAF::~idAF( void ) { +} + +/* +================ +idAF::Save +================ +*/ +void idAF::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + savefile->WriteString( GetName() ); + savefile->WriteBool( hasBindConstraints ); + savefile->WriteVec3( baseOrigin ); + savefile->WriteMat3( baseAxis ); + savefile->WriteInt( poseTime ); + savefile->WriteInt( restStartTime ); + savefile->WriteBool( isLoaded ); + savefile->WriteBool( isActive ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idAF::Restore +================ +*/ +void idAF::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( name ); + savefile->ReadBool( hasBindConstraints ); + savefile->ReadVec3( baseOrigin ); + savefile->ReadMat3( baseAxis ); + savefile->ReadInt( poseTime ); + savefile->ReadInt( restStartTime ); + savefile->ReadBool( isLoaded ); + savefile->ReadBool( isActive ); + + animator = NULL; + modifiedAnim = 0; + + if ( self ) { + SetAnimator( self->GetAnimator() ); + Load( self, name ); + if ( hasBindConstraints ) { + AddBindConstraints(); + } + } + + savefile->ReadStaticObject( physicsObj ); + + if ( self ) { + if ( isActive ) { + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + + // switch to articulated figure physics + self->RestorePhysics( &physicsObj ); + physicsObj.EnableClip(); + } + UpdateAnimation(); + } +} + +/* +================ +idAF::UpdateAnimation +================ +*/ +bool idAF::UpdateAnimation( void ) { + int i; + idVec3 origin, renderOrigin, bodyOrigin; + idMat3 axis, renderAxis, bodyAxis; + renderEntity_t *renderEntity; + + if ( !IsLoaded() ) { + return false; + } + + if ( !IsActive() ) { + return false; + } + + renderEntity = self->GetRenderEntity(); + if ( !renderEntity ) { + return false; + } + + if ( physicsObj.IsAtRest() ) { + if ( restStartTime == physicsObj.GetRestStartTime() ) { + return false; + } + restStartTime = physicsObj.GetRestStartTime(); + } + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // create an animation frame which reflects the current pose of the articulated figure + animator->InitAFPose(); + for ( i = 0; i < jointMods.Num(); i++ ) { + // check for the origin joint + if ( jointMods[i].jointHandle == 0 ) { + continue; + } + bodyOrigin = physicsObj.GetOrigin( jointMods[i].bodyId ); + bodyAxis = physicsObj.GetAxis( jointMods[i].bodyId ); + axis = jointMods[i].jointBodyAxis.Transpose() * ( bodyAxis * renderAxis.Transpose() ); + origin = ( bodyOrigin - jointMods[i].jointBodyOrigin * axis - renderOrigin ) * renderAxis.Transpose(); + animator->SetAFPoseJointMod( jointMods[i].jointHandle, jointMods[i].jointMod, axis, origin ); + } + animator->FinishAFPose( modifiedAnim, GetBounds().Expand( POSE_BOUNDS_EXPANSION ), gameLocal.time ); + animator->SetAFPoseBlendWeight( 1.0f ); + + return true; +} + +/* +================ +idAF::GetBounds + + returns bounds for the current pose +================ +*/ +idBounds idAF::GetBounds( void ) const { + int i; + idAFBody *body; + idVec3 origin, entityOrigin; + idMat3 axis, entityAxis; + idBounds bounds, b; + + bounds.Clear(); + + // get model base transform + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + + entityAxis = baseAxis.Transpose() * axis; + entityOrigin = origin - baseOrigin * entityAxis; + + // get bounds relative to base + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + origin = ( body->GetWorldOrigin() - entityOrigin ) * entityAxis.Transpose(); + axis = body->GetWorldAxis() * entityAxis.Transpose(); + b.FromTransformedBounds( body->GetClipModel()->GetBounds(), origin, axis ); + + bounds += b; + } + + return bounds; +} + +/* +================ +idAF::SetupPose + + Transforms the articulated figure to match the current animation pose of the given entity. +================ +*/ +void idAF::SetupPose( idEntity *ent, int time ) { + int i; + idAFBody *body; + idVec3 origin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + } + + if ( isActive ) { + physicsObj.UpdateClipModels(); + } +} + +/* +================ +idAF::ChangePose + + Change the articulated figure to match the current animation pose of the given entity + and set the velocity relative to the previous pose. +================ +*/ +void idAF::ChangePose( idEntity *ent, int time ) { + int i; + float invDelta; + idAFBody *body; + idVec3 origin, lastOrigin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + invDelta = 1.0f / MS2SEC( time - poseTime ); + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + lastOrigin = body->GetWorldOrigin(); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + body->SetLinearVelocity( ( body->GetWorldOrigin() - lastOrigin ) * invDelta ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::EntitiesTouchingAF +================ +*/ +int idAF::EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const { + int i, j, numClipModels; + idAFBody *body; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + int numTouching; + + if ( !IsLoaded() ) { + return 0; + } + + numTouching = 0; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( self, physicsObj.GetAbsBounds(), -1, clipModels, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + for ( j = 0; j < numClipModels; j++ ) { + cm = clipModels[j]; + + if ( !cm || cm->GetEntity() == self ) { + continue; + } + + if ( !cm->IsTraceModel() ) { + continue; + } + + if ( !body->GetClipModel()->GetAbsBounds().IntersectsBounds( cm->GetAbsBounds() ) ) { + continue; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.ContentsModel( self, body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), -1, cm->GetCollisionModel(), cm->GetOrigin(), cm->GetAxis() ) ) { +// RAVEN END + touchList[ numTouching ].touchedByBody = body; + touchList[ numTouching ].touchedClipModel = cm; + touchList[ numTouching ].touchedEnt = cm->GetEntity(); + numTouching++; + clipModels[j] = NULL; + } + } + } + + return numTouching; +} + +/* +================ +idAF::BodyForClipModelId +================ +*/ +int idAF::BodyForClipModelId( int id ) const { + if ( id >= 0 ) { + return id; + } else { + id = CLIPMODEL_ID_TO_JOINT_HANDLE( id ); + if ( id < jointBody.Num() ) { + return jointBody[id]; + } else { + return 0; + } + } +} + +/* +================ +idAF::GetPhysicsToVisualTransform +================ +*/ +void idAF::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const { + origin = - baseOrigin; + axis = baseAxis.Transpose(); +} + +/* +================ +idAF::GetImpactInfo +================ +*/ +void idAF::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + SetupPose( self, gameLocal.time ); + physicsObj.GetImpactInfo( BodyForClipModelId( id ), point, info ); +} + +/* +================ +idAF::ApplyImpulse +================ +*/ +void idAF::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + SetupPose( self, gameLocal.time ); + physicsObj.ApplyImpulse( BodyForClipModelId( id ), point, impulse ); +} + +/* +================ +idAF::AddForce +================ +*/ +void idAF::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + SetupPose( self, gameLocal.time ); + physicsObj.AddForce( BodyForClipModelId( id ), point, force ); +} + +/* +================ +idAF::AddBody + + Adds a body. +================ +*/ +void idAF::AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ) { + int index; + jointHandle_t handle; + idVec3 origin; + idMat3 axis; + + handle = animator->GetJointHandle( jointName ); + if ( handle == INVALID_JOINT ) { + gameLocal.Error( "idAF for entity '%s' at (%s) modifies unknown joint '%s'", self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), jointName ); + } + + assert( handle < animator->NumJoints() ); + origin = joints[ handle ].ToVec3(); + axis = joints[ handle ].ToMat3(); + + index = jointMods.Num(); + jointMods.SetNum( index + 1, false ); + jointMods[index].bodyId = physicsObj.GetBodyId( body ); + jointMods[index].jointHandle = handle; + jointMods[index].jointMod = mod; + jointMods[index].jointBodyOrigin = ( body->GetWorldOrigin() - origin ) * axis.Transpose(); + jointMods[index].jointBodyAxis = body->GetWorldAxis() * axis.Transpose(); +} + +/* +================ +idAF::SetBase + + Sets the base body. +================ +*/ +void idAF::SetBase( idAFBody *body, const idJointMat *joints ) { + physicsObj.ForceBodyId( body, 0 ); + baseOrigin = body->GetWorldOrigin(); + baseAxis = body->GetWorldAxis(); + AddBody( body, joints, animator->GetJointName( animator->GetFirstChild( "origin" ) ), AF_JOINTMOD_AXIS ); +} + +/* +================ +idAF::LoadBody +================ +*/ +bool idAF::LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ) { + int id, i; + float length, mass; + idTraceModel trm; + idClipModel *clip; + idAFBody *body; + idMat3 axis, inertiaTensor; + idVec3 centerOfMass, origin; + idBounds bounds; + idList jointList; + + origin = fb->origin.ToVec3(); + axis = fb->angles.ToMat3(); + bounds[0] = fb->v1.ToVec3(); + bounds[1] = fb->v2.ToVec3(); + + switch( fb->modelType ) { + case TRM_BOX: { + trm.SetupBox( bounds ); + break; + } + case TRM_OCTAHEDRON: { + trm.SetupOctahedron( bounds ); + break; + } + case TRM_DODECAHEDRON: { + trm.SetupDodecahedron( bounds ); + break; + } + case TRM_CYLINDER: { + trm.SetupCylinder( bounds, fb->numSides ); + break; + } + case TRM_CONE: { + // place the apex at the origin + bounds[0].z -= bounds[1].z; + bounds[1].z = 0.0f; + trm.SetupCone( bounds, fb->numSides ); + break; + } + case TRM_BONE: { + // direction of bone + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + length = axis[2].Normalize(); + // axis of bone trace model + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + // create bone trace model + trm.SetupBone( length, fb->width ); + break; + } + default: + assert( 0 ); + break; + } + + trm.GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + trm.Translate( -centerOfMass ); + origin += centerOfMass * axis; + + body = physicsObj.GetBody( fb->name ); + if ( body ) { + clip = body->GetClipModel(); + if ( !clip->IsEqual( trm ) ) { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body->SetClipModel( clip ); + } + clip->SetContents( fb->contents ); + body->SetDensity( fb->density, fb->inertiaScale ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( axis ); + id = physicsObj.GetBodyId( body ); + } + else { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body = new idAFBody( fb->name, clip, fb->density ); + if ( fb->inertiaScale != mat3_identity ) { + body->SetDensity( fb->density, fb->inertiaScale ); + } + id = physicsObj.AddBody( body ); + } + if ( fb->linearFriction != -1.0f ) { + body->SetFriction( fb->linearFriction, fb->angularFriction, fb->contactFriction ); + } + body->SetClipMask( fb->clipMask ); + body->SetSelfCollision( fb->selfCollision ); + + if ( fb->jointName == "origin" ) { + SetBase( body, joints ); + } else { + AFJointModType_t mod; + if ( fb->jointMod == DECLAF_JOINTMOD_AXIS ) { + mod = AF_JOINTMOD_AXIS; + } else if ( fb->jointMod == DECLAF_JOINTMOD_ORIGIN ) { + mod = AF_JOINTMOD_ORIGIN; + } else if ( fb->jointMod == DECLAF_JOINTMOD_BOTH ) { + mod = AF_JOINTMOD_BOTH; + } else { + mod = AF_JOINTMOD_AXIS; + } + AddBody( body, joints, fb->jointName, mod ); + } + + if ( fb->frictionDirection.ToVec3() != vec3_origin ) { + body->SetFrictionDirection( fb->frictionDirection.ToVec3() ); + } + if ( fb->contactMotorDirection.ToVec3() != vec3_origin ) { + body->SetContactMotorDirection( fb->contactMotorDirection.ToVec3() ); + } + + // update table to find the nearest articulated figure body for a joint of the skeletal model + animator->GetJointList( fb->containedJoints, jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + if ( jointBody[ jointList[ i ] ] != -1 ) { + +// RAVEN BEGIN +// kfuller: better load time warning for joints contained by multiple bodies + gameLocal.Warning( "%s: body '%s': joint '%s' is already contained by body '%s'", + name.c_str(), fb->name.c_str(), + animator->GetJointName( (jointHandle_t)jointList[i] ), + physicsObj.GetBody( jointBody[ jointList[ i ] ] )->GetName().c_str() ); +// RAVEN END + } + jointBody[ jointList[ i ] ] = id; + } + + return true; +} + +/* +================ +idAF::LoadConstraint +================ +*/ +bool idAF::LoadConstraint( const idDeclAF_Constraint *fc ) { + idAFBody *body1, *body2; + + body1 = physicsObj.GetBody( fc->body1 ); + body2 = physicsObj.GetBody( fc->body2 ); + + switch( fc->type ) { + case DECLAF_CONSTRAINT_FIXED: { + idAFConstraint_Fixed *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Fixed( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + break; + } + case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_BallAndSocketJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0], fc->shaft[0].ToVec3() ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1], fc->shaft[0].ToVec3() ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_UniversalJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetShafts( fc->shaft[0].ToVec3(), fc->shaft[1].ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0] ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1] ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_HINGE: { + idAFConstraint_Hinge *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Hinge( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetAxis( fc->axis.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + idVec3 left, up, axis, shaft; + fc->axis.ToVec3().OrthogonalBasis( left, up ); + axis = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[0] ); + shaft = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[2] ); + c->SetLimit( axis, fc->limitAngles[1], shaft ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_SLIDER: { + idAFConstraint_Slider *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Slider( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAxis( fc->axis.ToVec3() ); + break; + } + case DECLAF_CONSTRAINT_SPRING: { + idAFConstraint_Spring *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Spring( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3(), fc->anchor2.ToVec3() ); + c->SetSpring( fc->stretch, fc->compress, fc->damping, fc->restLength ); + c->SetLimit( fc->minLength, fc->maxLength ); + break; + } + } + return true; +} + +/* +================ +GetJointTransform +================ +*/ +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + jointHandle_t joint; + + joint = reinterpret_cast(model)->GetJointHandle( jointName ); + if ( ( joint >= 0 ) && ( joint < reinterpret_cast(model)->NumJoints() ) ) { + origin = frame[ joint ].ToVec3(); + axis = frame[ joint ].ToMat3(); + return true; + } else { + return false; + } +} + +/* +================ +idAF::Load +================ +*/ +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one +bool idAF::Load( idEntity *ent, const char *fileName, bool purgeAF /* = false */ ) { +// RAVEN END + int i, j; + const idDeclAF *file; + const idDeclModelDef *modelDef; + idRenderModel *model; + int numJoints; + idJointMat *joints; + + assert( ent ); + + self = ent; + physicsObj.SetSelf( self ); + + if ( animator == NULL ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s): NULL animator\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + name = fileName; + name.StripFileExtension(); + + file = static_cast( declManager->FindType( DECL_AF, name ) ); + if ( !file ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s)\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + if ( file->bodies.Num() == 0 || file->bodies[0]->jointName != "origin" ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no body which modifies the origin joint.", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + modelDef = animator->ModelDef(); + if ( modelDef == NULL || modelDef->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted modelDef '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), modelDef ? modelDef->GetName() : "" ); + return false; + } + + model = animator->ModelHandle(); + if ( model == NULL || model->IsDefaultModel() ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted model '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), model ? model->Name() : "" ); + return false; + } + + // get the modified animation + modifiedAnim = animator->GetAnim( ARTICULATED_FIGURE_ANIM ); + if ( !modifiedAnim ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no modified animation '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), ARTICULATED_FIGURE_ANIM ); + return false; + } + + // create the animation frame used to setup the articulated figure + numJoints = animator->NumJoints(); + joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + gameEdit->ANIM_CreateAnimFrame( model, animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset(), animator->RemoveOrigin() ); + + // set all vector positions from model joints + file->Finish( GetJointTransform, joints, animator ); + + // initialize articulated figure physics + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( file->clipMask ); + physicsObj.SetDefaultFriction( file->defaultLinearFriction, file->defaultAngularFriction, file->defaultContactFriction ); + physicsObj.SetSuspendSpeed( file->suspendVelocity, file->suspendAcceleration ); + physicsObj.SetSuspendTolerance( file->noMoveTime, file->noMoveTranslation, file->noMoveRotation ); + physicsObj.SetSuspendTime( file->minMoveTime, file->maxMoveTime ); + physicsObj.SetSelfCollision( file->selfCollision ); +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + physicsObj.SetFastEval( file->fastEval ); +// RAVEN END + + // clear the list with transforms from joints to bodies + jointMods.SetNum( 0, false ); + + // clear the joint to body conversion list + jointBody.AssureSize( animator->NumJoints() ); + for ( i = 0; i < jointBody.Num(); i++ ) { + jointBody[i] = -1; + } + +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + // delete any bodies in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + physicsObj.DeleteBody( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + idAFBody *body = physicsObj.GetBody( i ); + for ( j = 0; j < file->bodies.Num(); j++ ) { + if ( file->bodies[j]->name.Icmp( body->GetName() ) == 0 ) { + break; + } + } + if ( j >= file->bodies.Num() ) { + physicsObj.DeleteBody( i ); + i--; + } + } + } + + // delete any constraints in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + idAFConstraint *constraint = physicsObj.GetConstraint( i ); + for ( j = 0; j < file->constraints.Num(); j++ ) { + if ( file->constraints[j]->name.Icmp( constraint->GetName() ) == 0 && + file->constraints[j]->type == constraint->GetType() ) { + break; + } + } + if ( j >= file->constraints.Num() ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } + } +// RAVEN END + // load bodies from the file + for ( i = 0; i < file->bodies.Num(); i++ ) { + LoadBody( file->bodies[i], joints ); + } + + // load constraints from the file + for ( i = 0; i < file->constraints.Num(); i++ ) { + LoadConstraint( file->constraints[i] ); + } + + physicsObj.UpdateClipModels(); + + // check if each joint is contained by a body + for( i = 0; i < animator->NumJoints(); i++ ) { + if ( jointBody[i] == -1 ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) joint '%s' is not contained by a body", + name.c_str(), self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), animator->GetJointName( (jointHandle_t)i ) ); + } + } + + physicsObj.SetMass( file->totalMass ); + physicsObj.SetChanged(); + + // disable the articulated figure for collision detection until activated + physicsObj.DisableClip(); + + isLoaded = true; + + poseTime = -1; + + return true; +} + +/* +================ +idAF::Start +================ +*/ +void idAF::Start( void ) { + if ( !IsLoaded() ) { + return; + } + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + // switch to articulated figure physics + self->SetPhysics( &physicsObj ); + // start the articulated figure physics simulation + physicsObj.EnableClip(); + + physicsObj.Activate(); + isActive = true; +} + +/* +================ +idAF::TestSolid +================ +*/ +bool idAF::TestSolid( void ) const { + int i; + idAFBody *body; + trace_t trace; + idStr str; + bool solid; + + if ( !IsLoaded() ) { + return false; + } + + if ( !af_testSolid.GetBool() ) { + return false; + } + + solid = false; + + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + body = physicsObj.GetBody( i ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Translation( self, trace, body->GetWorldOrigin(), body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), body->GetClipMask(), self ) ) { +// RAVEN END + float depth = idMath::Fabs( trace.c.point * trace.c.normal - trace.c.dist ); + + body->SetWorldOrigin( body->GetWorldOrigin() + trace.c.normal * ( depth + 8.0f ) ); + + gameLocal.DWarning( "%s: body '%s' stuck in %d (normal = %.2f %.2f %.2f, depth = %.2f)", self->name.c_str(), + body->GetName().c_str(), trace.c.contents, trace.c.normal.x, trace.c.normal.y, trace.c.normal.z, depth ); + solid = true; + + } + } + return solid; +} + +/* +================ +idAF::StartFromCurrentPose +================ +*/ +void idAF::StartFromCurrentPose( int inheritVelocityTime ) { + if ( !IsLoaded() ) { + return; + } + + // if the ragdoll should inherit velocity from the animation + if ( inheritVelocityTime > 0 ) { + + // make sure the ragdoll is at rest + physicsObj.PutToRest(); + + // set the pose for some time back + SetupPose( self, gameLocal.time - inheritVelocityTime ); + + // change the pose for the current time and set velocities + ChangePose( self, gameLocal.time ); + } + else { + // transform the articulated figure to reflect the current animation pose + SetupPose( self, gameLocal.time ); + } + + physicsObj.UpdateClipModels(); + + TestSolid(); + + Start(); + + UpdateAnimation(); + + // update the render entity origin and axis + self->UpdateModel(); + + // make sure the renderer gets the updated origin and axis + self->Present(); +} + +/* +================ +idAF::Stop +================ +*/ +void idAF::Stop( void ) { + // disable the articulated figure for collision detection + physicsObj.UnlinkClip(); + isActive = false; +} + +/* +================ +idAF::Rest +================ +*/ +void idAF::Rest( void ) { + physicsObj.PutToRest(); +} + +/* +================ +idAF::SetConstraintPosition + + Only moves constraints that bind the entity to another entity. +================ +*/ +void idAF::SetConstraintPosition( const char *name, const idVec3 &pos ) { + idAFConstraint *constraint; + + constraint = GetPhysics()->GetConstraint( name ); + + if ( !constraint ) { + gameLocal.Warning( "can't find a constraint with the name '%s'", name ); + return; + } + + if ( constraint->GetBody2() != NULL ) { + gameLocal.Warning( "constraint '%s' does not bind to another entity", name ); + return; + } + + switch( constraint->GetType() ) { + case CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *bs = static_cast(constraint); + bs->Translate( pos - bs->GetAnchor() ); + break; + } + case CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *uj = static_cast(constraint); + uj->Translate( pos - uj->GetAnchor() ); + break; + } + case CONSTRAINT_HINGE: { + idAFConstraint_Hinge *hinge = static_cast(constraint); + hinge->Translate( pos - hinge->GetAnchor() ); + break; + } + default: { + gameLocal.Warning( "cannot set the constraint position for '%s'", name ); + break; + } + } +} + +/* +================ +idAF::SaveState +================ +*/ +void idAF::SaveState( idDict &args ) const { + int i; + idAFBody *body; + idStr key, value; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + key = "body " + body->GetName(); + value = body->GetWorldOrigin().ToString( 8 ); + value += " "; + value += body->GetWorldAxis().ToAngles().ToString( 8 ); + args.Set( key, value ); + } +} + +/* +================ +idAF::LoadState +================ +*/ +void idAF::LoadState( const idDict &args ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idVec3 origin; + idAngles angles; + + kv = args.MatchPrefix( "body ", NULL ); + while ( kv ) { + + name = kv->GetKey(); + name.Strip( "body " ); + body = physicsObj.GetBody( name ); + if ( body ) { + sscanf( kv->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( angles.ToMat3() ); + } else { + gameLocal.Warning("Unknown body part %s in articulated figure %s", name.c_str(), this->name.c_str()); + } + + kv = args.MatchPrefix( "body ", kv ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::AddBindConstraints +================ +*/ +void idAF::AddBindConstraints( void ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idLexer lexer; + idToken type, bodyName, jointName; + idVec3 origin, renderOrigin; + idMat3 axis, renderAxis; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + idToken jointFriction; +// RAVEN END + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // parse all the bind constraints + for ( kv = args.MatchPrefix( "bindConstraint ", NULL ); kv; kv = args.MatchPrefix( "bindConstraint ", kv ) ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + + lexer.ReadToken( &bodyName ); + body = physicsObj.GetBody( bodyName ); + if ( !body ) { + gameLocal.Warning( "idAF::AddBindConstraints: body '%s' not found on entity '%s'", bodyName.c_str(), self->name.c_str() ); + lexer.FreeSource(); + continue; + } + + if ( type.Icmp( "fixed" ) == 0 ) { + idAFConstraint_Fixed *c; + + c = new idAFConstraint_Fixed( name, body, NULL ); + physicsObj.AddConstraint( c ); + } + else if ( type.Icmp( "ballAndSocket" ) == 0 ) { + idAFConstraint_BallAndSocketJoint *c; + + c = new idAFConstraint_BallAndSocketJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if ( type.Icmp( "universal" ) == 0 ) { + idAFConstraint_UniversalJoint *c; + + c = new idAFConstraint_UniversalJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if (type.Icmp( "hinge" ) == 0 ) + { + idAFConstraint_Hinge *c; + c = new idAFConstraint_Hinge( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) + { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found\n", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetAxis(renderAxis[1]); + c->SetNoLimit(); + if (lexer.ReadToken(&jointFriction)) + { + float frictionValue = 0; + + sscanf(jointFriction.c_str(), "%f", &frictionValue); + c->SetFriction(frictionValue); + } + idToken hingeAxis; + if (lexer.ReadToken(&hingeAxis)) + { + int hingeAxisValue = 1; + + sscanf(hingeAxis.c_str(), "%d", &hingeAxisValue); + if (hingeAxisValue >= 0 && hingeAxisValue <= 2) + { + c->SetAxis(renderAxis[hingeAxisValue]); + } + } + } +// RAVEN END + else { + gameLocal.Warning( "idAF::AddBindConstraints: unknown constraint type '%s' on entity '%s'", type.c_str(), self->name.c_str() ); + } + + lexer.FreeSource(); + } + + hasBindConstraints = true; +} + +/* +================ +idAF::RemoveBindConstraints +================ +*/ +void idAF::RemoveBindConstraints( void ) { + const idKeyValue *kv; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + idStr name; + + kv = args.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + if ( physicsObj.GetConstraint( name ) ) { + physicsObj.DeleteConstraint( name ); + } + + kv = args.MatchPrefix( "bindConstraint ", kv ); + } + + hasBindConstraints = false; +} diff --git a/src/game/AF.h b/src/game/AF.h new file mode 100644 index 0000000..eb31abf --- /dev/null +++ b/src/game/AF.h @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_AF_H__ +#define __GAME_AF_H__ + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ + +typedef struct jointConversion_s { + int bodyId; // id of the body + jointHandle_t jointHandle; // handle of joint this body modifies + AFJointModType_t jointMod; // modify joint axis, origin or both + idVec3 jointBodyOrigin; // origin of body relative to joint + idMat3 jointBodyAxis; // axis of body relative to joint +} jointConversion_t; + +typedef struct afTouch_s { + idEntity * touchedEnt; + idClipModel * touchedClipModel; + idAFBody * touchedByBody; +} afTouch_t; + +class idAF { +public: + idAF( void ); + ~idAF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetAnimator( idAnimator *a ) { animator = a; } +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + bool Load( idEntity *ent, const char *fileName, bool purgeAF = false ); +// RAVEN END + bool IsLoaded( void ) const { return isLoaded && self != NULL; } + const char * GetName( void ) const { return name.c_str(); } + void SetupPose( idEntity *ent, int time ); + void ChangePose( idEntity *ent, int time ); + int EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const; + void Start( void ); + void StartFromCurrentPose( int inheritVelocityTime ); + void Stop( void ); + void Rest( void ); + bool IsActive( void ) const { return isActive; } + void SetConstraintPosition( const char *name, const idVec3 &pos ); + + idPhysics_AF * GetPhysics( void ) { return &physicsObj; } + const idPhysics_AF * GetPhysics( void ) const { return &physicsObj; } + idBounds GetBounds( void ) const; + bool UpdateAnimation( void ); + + void GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const; + void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + idPhysics_AF physicsObj; // articulated figure physics + bool TestSolid( void ) const; + +protected: + idStr name; // name of the loaded .af file + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // anim to modify + idVec3 baseOrigin; // offset of base body relative to skeletal model origin + idMat3 baseAxis; // axis of base body relative to skeletal model origin + idListjointMods; // list with transforms from skeletal model joints to articulated figure bodies + idList jointBody; // table to find the nearest articulated figure body for a joint of the skeletal model + int poseTime; // last time the articulated figure was transformed to reflect the current animation pose + int restStartTime; // time the articulated figure came to rest + bool isLoaded; // true when the articulated figure is properly loaded + bool isActive; // true if the articulated figure physics is active + bool hasBindConstraints; // true if the bind constraints have been added + +protected: + void SetBase( idAFBody *body, const idJointMat *joints ); + void AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ); + + bool LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ); + bool LoadConstraint( const idDeclAF_Constraint *fc ); + +}; + +#endif /* !__GAME_AF_H__ */ diff --git a/src/game/AFEntity.cpp b/src/game/AFEntity.cpp new file mode 100644 index 0000000..78cb50f --- /dev/null +++ b/src/game/AFEntity.cpp @@ -0,0 +1,3226 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + + idMultiModelAF + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMultiModelAF ) +END_CLASS + +/* +================ +idMultiModelAF::Spawn +================ +*/ +void idMultiModelAF::Spawn( void ) { + physicsObj.SetSelf( this ); +} + +/* +================ +idMultiModelAF::~idMultiModelAF +================ +*/ +idMultiModelAF::~idMultiModelAF( void ) { + int i; + + for ( i = 0; i < modelDefHandles.Num(); i++ ) { + if ( modelDefHandles[i] != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandles[i] ); + modelDefHandles[i] = -1; + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMultiModelAF::SetModelForId +================ +*/ +void idMultiModelAF::SetModelForId( int id, const idStr &modelName ) { + modelHandles.AssureSize( id+1, NULL ); + modelDefHandles.AssureSize( id+1, -1 ); + modelHandles[id] = renderModelManager->FindModel( modelName ); +} + +/* +================ +idMultiModelAF::Present +================ +*/ +void idMultiModelAF::Present( void ) { + int i; + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + for ( i = 0; i < modelHandles.Num(); i++ ) { + + if ( !modelHandles[i] ) { + continue; + } + + renderEntity.origin = physicsObj.GetOrigin( i ); + renderEntity.axis = physicsObj.GetAxis( i ); + renderEntity.hModel = modelHandles[i]; + renderEntity.bodyId = i; + + // add to refresh list + if ( modelDefHandles[i] == -1 ) { + modelDefHandles[i] = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandles[i], &renderEntity ); + } + } +} + +/* +================ +idMultiModelAF::Think +================ +*/ +void idMultiModelAF::Think( void ) { + RunPhysics(); + Present(); +} + + +/* +=============================================================================== + + idChain + +=============================================================================== +*/ + +CLASS_DECLARATION( idMultiModelAF, idChain ) +END_CLASS + +/* +================ +idChain::BuildChain + + builds a chain hanging down from the ceiling + the highest link is a child of the link below it etc. + this allows an object to be attached to multiple chains while keeping a single tree structure +================ +*/ +void idChain::BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld ) { + int i; + float halfLinkLength = linkLength * 0.5f; + idTraceModel trm; + idClipModel *clip; + idAFBody *body, *lastBody; + idAFConstraint_BallAndSocketJoint *bsj; + idAFConstraint_UniversalJoint *uj; + idVec3 org; + + // create a trace model + trm = idTraceModel( linkLength, linkWidth ); + trm.Translate( -trm.offset ); + + org = origin - idVec3( 0, 0, halfLinkLength ); + + lastBody = NULL; + for ( i = 0; i < numLinks; i++ ) { + + // add body +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clip = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip->SetContents( CONTENTS_SOLID ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, org, mat3_identity ); +// RAVEN END +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + body = new idAFBody( name + idStr(i), clip, density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.AddBody( body ); + + // visual model for body + SetModelForId( physicsObj.GetBodyId( body ), spawnArgs.GetString( "model" ) ); + + // add constraint + if ( bindToWorld ) { + if ( !lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), body, lastBody ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, -1 ), idVec3( 0, 0, 1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, -1 ), 30.0f ); + //uj->SetPyramidLimit( idVec3( 0, 0, -1 ), idVec3( 1, 0, 0 ), 90.0f, 30.0f ); + } + else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, 1 ), 30.0f ); + } + uj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + uj->SetFriction( 0.9f ); + physicsObj.AddConstraint( uj ); + } + else { + if ( lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + bsj = new idAFConstraint_BallAndSocketJoint( "joint" + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + bsj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + bsj->SetConeLimit( idVec3( 0, 0, 1 ), 60.0f, idVec3( 0, 0, 1 ) ); + physicsObj.AddConstraint( bsj ); + } + } + + org[2] -= linkLength; + + lastBody = body; + } +} + +/* +================ +idChain::Spawn +================ +*/ +void idChain::Spawn( void ) { + int numLinks; + float length, linkLength, linkWidth, density; + bool drop; + idVec3 origin; + + spawnArgs.GetBool( "drop", "0", drop ); + spawnArgs.GetInt( "links", "3", numLinks ); + spawnArgs.GetFloat( "length", idStr( numLinks * 32.0f ), length ); + spawnArgs.GetFloat( "width", "8", linkWidth ); + spawnArgs.GetFloat( "density", "0.2", density ); + linkLength = length / numLinks; + origin = GetPhysics()->GetOrigin(); + + // initialize physics + physicsObj.SetSelf( this ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY ); + SetPhysics( &physicsObj ); + + BuildChain( "link", origin, linkLength, linkWidth, density, numLinks, !drop ); +} + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +const idEventDef EV_ClearAnims( "clearAnims" ); + +CLASS_DECLARATION( idAnimatedEntity, idAFAttachment ) +// RAVEN BEGIN +// jshepard: we'd like these to animate. + EVENT( AI_PlayAnim, idAFAttachment::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAFAttachment::Event_PlayCycle ) + EVENT( EV_ClearAnims, idAFAttachment::Event_ClearAnims ) +// RAVEN END +END_CLASS + +/* +===================== +idAFAttachment::idAFAttachment +===================== +*/ +idAFAttachment::idAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; + +// RAVEN BEGIN +// jscott: Lip Syncing variables + lipSyncAnim = 0; + lipSyncData = NULL; + soundJoint = INVALID_JOINT; +// MCG + noPlayerImpactFX = false; +// RAVEN END +} + +/* +===================== +idAFAttachment::~idAFAttachment +===================== +*/ +idAFAttachment::~idAFAttachment( void ) { + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN + EndLipSyncing(); +// RAVEN END +} + +/* +===================== +idAFAttachment::Spawn +===================== +*/ +void idAFAttachment::Spawn( void ) { + idStr jointName; + + idleAnim = animator.GetAnim( "idle" ); + +// RAVEN BEGIN + // Init the lip sync data + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } + + // Grab the joint to play sounds off + soundJoint = INVALID_JOINT; + spawnArgs.GetString( "sound_bone", "", jointName ); + + // Do we want a bone to play sounds on? + if( jointName.Length() ) { + + // Try to find the specified bone + soundJoint = animator.GetJointHandle( jointName ); + + if ( soundJoint == INVALID_JOINT ) { + // The def specified a bone for this and we can't find it - warn them. + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +// RAVEN END +} + +/* +===================== +idAFAttachment::InitCopyJoints +===================== +*/ +void idAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on entity %s", jointName, name.c_str() ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %s", kv->GetValue().c_str(), name.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +idAFAttachment::CopyJointsFromBody +===================== +*/ +void idAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +idAFAttachment::SetBody +===================== +*/ +void idAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + fl.takedamage = true; + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +idAFAttachment::ClearBody +===================== +*/ +void idAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +idAFAttachment::GetBody +===================== +*/ +idEntity *idAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Save + +archive object for savegame file +================ +*/ +void idAFAttachment::Save( idSaveGame *savefile ) const { + int i; + + body.Save ( savefile ); + savefile->WriteInt( idleAnim ); + savefile->WriteJoint( damageJoint ); + savefile->WriteJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->WriteInt( lipSyncAnim ); +// RAVEN END + + savefile->WriteInt( copyJoints.Num() ); + for( i = 0; i < copyJoints.Num(); i++ ) { + savefile->WriteInt( copyJoints[i].mod ); + savefile->WriteJoint( copyJoints[i].from ); + savefile->WriteJoint( copyJoints[i].to ); + } + + bool hadCombatModel = false; + if ( combatModel ) { + hadCombatModel = true; + } + savefile->WriteBool( hadCombatModel ); + + savefile->WriteBool( noPlayerImpactFX ); +} + +/* +================ +idAFAttachment::Restore + +unarchives object from save game file +================ +*/ +void idAFAttachment::Restore( idRestoreGame *savefile ) { + body.Restore ( savefile ); + savefile->ReadInt( idleAnim ); + savefile->ReadJoint( damageJoint ); + savefile->ReadJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->ReadInt( lipSyncAnim ); +// jscott: difficult to start a sound mid sentence and impossible to sync up the timing with the animation + lipSyncData = NULL; +// RAVEN END + + int i; + int num; + savefile->ReadInt( num ); + copyJoints.SetNum( num ); + for( i = 0; i < num; i++ ) { + int val; + savefile->ReadInt( val ); + copyJoints[i].mod = static_cast( val ); + savefile->ReadJoint( copyJoints[i].from ); + savefile->ReadJoint( copyJoints[i].to ); + } + + bool hadCombatModel; + savefile->ReadBool( hadCombatModel ); + if ( hadCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + savefile->ReadBool( noPlayerImpactFX ); + + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void idAFAttachment::Hide( void ) { + idEntity::Hide(); + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void idAFAttachment::Show( void ) { + idEntity::Show(); + LinkCombat(); +} + +/* +============ +idAFAttachment::Damage + +Pass damage to body at the bindjoint +============ +*/ +void idAFAttachment::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + if ( body ) { + body->Damage( inflictor, attacker, dir, damageDefName, damageScale, damageJoint ); + } +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void idAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +idAFAttachment::GetImpactInfo +================ +*/ +void idAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFAttachment::CanPlayImpactEffect +================ +*/ +bool idAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFAttachment::GetNoPlayerImpactFX +================ +*/ +bool idAFAttachment::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFAttachment::ApplyImpulse +================ +*/ +void idAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFAttachment::AddForce +================ +*/ +void idAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + idEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +idAFAttachment::PlayIdleAnim +================ +*/ +// RAVEN BEGIN +// bdube: added channel +void idAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +// RAVEN END +} + +/* +================ +idAfAttachment::Think +================ +*/ +void idAFAttachment::Think( void ) { +// RAVEN BEGIN +// jscott: Lip sync main code + HandleLipSync(); +// RAVEN END + + idAnimatedEntity::Think(); +} + +// RAVEN BEGIN +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAFAttachment::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = GetPhysics()->GetAxis(); + } else { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); + } + + return true; +} +// RAVEN END + +/* +================ +idAFAttachment::UpdateAnimationControllers +================ +*/ +bool idAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return idAnimatedEntity::UpdateAnimationControllers( ); +} + +/* +================ +idAFAttachment::SetCombatModel +================ +*/ +void idAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + combatModel->SetOwner( body ); +} + +/* +================ +idAFAttachment::GetCombatModel +================ +*/ +idClipModel *idAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFAttachment::LinkCombat +================ +*/ +void idAFAttachment::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFAttachment::UnlinkCombat +================ +*/ +void idAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +// RAVEN BEGIN +// bdube: return the body entity for damage +/* +================ +idAFAttachment::GetDamageEntity +================ +*/ +idEntity* idAFAttachment::GetDamageEntity ( void ) { + return body ? body : this; +} + +// jshepard: we need to animate these +/* +================ +idAFAttachment::Event_PlayAnim +================ +*/ +void idAFAttachment::Event_PlayAnim ( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animTime - gameLocal.time ) ); + } + blendFrames = 0; +} + +// jdischler: and we want cycling, too. +/* +=============== +idAFAttachment::Event_PlayCycle +=============== +*/ +void idAFAttachment::Event_PlayCycle( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +================ +idAFAttachment::Event_ClearAnims + +Clears any animation running on the idAFAttachment +================ +*/ +void idAFAttachment::Event_ClearAnims( void ) { + //animator.ClearAllAnims( gameLocal.time, 100 ); + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, 0 ); +} + +// RAVEN END + +/* +=============================================================================== + + idAFEntity_Base + +=============================================================================== +*/ + +const idEventDef EV_SetConstraintPosition( "SetConstraintPosition", "sv" ); +// RAVEN BEGIN +// kfuller: added +const idEventDef EV_TPose( "tpose", NULL ); +// RAVEN END + +CLASS_DECLARATION( idAnimatedEntity, idAFEntity_Base ) + EVENT( EV_SetConstraintPosition, idAFEntity_Base::Event_SetConstraintPosition ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +idAFEntity_Base::idAFEntity_Base +================ +*/ +idAFEntity_Base::idAFEntity_Base( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); + noPlayerImpactFX = false; +} + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +idAFEntity_Base::~idAFEntity_Base( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +idAFEntity_Base::Save +================ +*/ +void idAFEntity_Base::Save( idSaveGame *savefile ) const { + savefile->WriteBool( noPlayerImpactFX ); + savefile->WriteInt( combatModelContents ); + savefile->WriteClipModel( combatModel ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( nextSoundTime ); + af.Save( savefile ); +} + +/* +================ +idAFEntity_Base::Restore +================ +*/ +void idAFEntity_Base::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( noPlayerImpactFX ); + savefile->ReadInt( combatModelContents ); + savefile->ReadClipModel( combatModel ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( nextSoundTime ); + LinkCombat(); + + af.Restore( savefile ); +} + +/* +================ +idAFEntity_Base::Spawn +================ +*/ +void idAFEntity_Base::Spawn( void ) { + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + nextSoundTime = 0; + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +} + +/* +================ +idAFEntity_Base::LoadAF +================ +*/ +bool idAFEntity_Base::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + if ( !af.Load( this, fileName ) ) { + gameLocal.Error( "idAFEntity_Base::LoadAF: Couldn't load af file '%s' on entity '%s'", fileName.c_str(), name.c_str() ); + } + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +idAFEntity_Base::Think +================ +*/ +void idAFEntity_Base::Think( void ) { + RunPhysics(); + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + +// RAVEN BEGIN +// kfuller: added + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } + +// rjohnson: added check to see if we no longer need to think! + if (GetPhysics()->IsAtRest() && !animator.IsAnimating( gameLocal.time, true ) ) { + BecomeInactive( TH_ANIMATE ); + } +// RAVEN END +} + +/* +================ +idAFEntity_Base::BodyForClipModelId +================ +*/ +int idAFEntity_Base::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +idAFEntity_Base::SaveState +================ +*/ +void idAFEntity_Base::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +idAFEntity_Base::LoadState +================ +*/ +void idAFEntity_Base::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +idAFEntity_Base::AddBindConstraints +================ +*/ +void idAFEntity_Base::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +idAFEntity_Base::RemoveBindConstraints +================ +*/ +void idAFEntity_Base::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +idAFEntity_Base::GetImpactInfo +================ +*/ +void idAFEntity_Base::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFEntity_Base::ApplyImpulse +================ +*/ +void idAFEntity_Base::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( !splash && ( noPlayerImpactFX || GetNoPlayerImpactFX( ) ) ) { + if ( ent->IsType( idPlayer::GetClassType() ) ) + {//player + return; + } + if ( ent->IsType( idProjectile::GetClassType() ) ) + {//projectile + if ( ((idProjectile*)ent)->GetOwner() && ((idProjectile*)ent)->GetOwner()->IsType( idPlayer::GetClassType() ) ) + {//owned by player + return; + } + } + } + + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFEntity_Base::AddForce +================ +*/ +void idAFEntity_Base::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + idEntity::AddForce( ent, id, point, force ); + } +} + +bool idAFEntity_Base::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFEntity_Base::Collide +================ +*/ +bool idAFEntity_Base::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +idAFEntity_Base::GetPhysicsToVisualTransform +================ +*/ +bool idAFEntity_Base::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return idEntity::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +idAFEntity_Base::UpdateAnimationControllers +================ +*/ +bool idAFEntity_Base::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +idAFEntity_Base::SetCombatModel +================ +*/ +void idAFEntity_Base::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::GetCombatModel +================ +*/ +idClipModel *idAFEntity_Base::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFEntity_Base::SetCombatContents +================ +*/ +void idAFEntity_Base::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +idAFEntity_Base::LinkCombat +================ +*/ +void idAFEntity_Base::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::UnlinkCombat +================ +*/ +void idAFEntity_Base::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +idAFEntity_Base::FreeModelDef +================ +*/ +void idAFEntity_Base::FreeModelDef( void ) { + UnlinkCombat(); + idEntity::FreeModelDef(); +} + +/* +=============== +idAFEntity_Base::ShowEditingDialog +=============== +*/ +void idAFEntity_Base::ShowEditingDialog( void ) { + common->InitTool( EDITOR_AF, &spawnArgs ); +} + +/* +================ +idAFEntity_Base::DropAFs + + The entity should have the following key/value pairs set: + "def_dropAF" "af def" + "dropSkin" "skin name" + To drop multiple articulated figures the following key/value pairs can be used: + "def_dropAF*" "af def" + where * is an aribtrary string. +================ +*/ +void idAFEntity_Base::DropAFs( idEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName; + idEntity *newEnt; + idAFEntity_Base *af; + idDict args; + const idDeclSkin *skin; + + // drop the articulated figures + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), NULL ); + while ( kv ) { + + args.Set( "classname", kv->GetValue() ); + gameLocal.SpawnEntityDef( args, &newEnt ); + + if ( newEnt && newEnt->IsType( idAFEntity_Base::Type ) ) { + af = static_cast(newEnt); + af->GetPhysics()->SetOrigin( ent->GetPhysics()->GetOrigin() ); + af->GetPhysics()->SetAxis( ent->GetPhysics()->GetAxis() ); + af->af.SetupPose( ent, gameLocal.time ); + if ( list ) { + list->Append( af ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), kv ); + } + + // change the skin to hide all the dropped articulated figures + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +================ +idAFEntity_Base::GetNoPlayerImpactFX +================ +*/ +bool idAFEntity_Base::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFEntity_Base::Event_SetConstraintPosition +================ +*/ +void idAFEntity_Base::Event_SetConstraintPosition( const char *name, const idVec3 &pos ) { + af.SetConstraintPosition( name, pos ); +} + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +const idEventDef EV_Gib( "gib", "s" ); +const idEventDef EV_Gibbed( "" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Gibbable ) + EVENT( EV_Gib, idAFEntity_Gibbable::Event_Gib ) + EVENT( EV_Gibbed, idAFEntity_Base::Event_Remove ) +END_CLASS + + +/* +================ +idAFEntity_Gibbable::idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::idAFEntity_Gibbable( void ) { + skeletonModel = NULL; + skeletonModelDefHandle = -1; + gibbed = false; +} + +/* +================ +idAFEntity_Gibbable::~idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::~idAFEntity_Gibbable() { + if ( skeletonModelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( skeletonModelDefHandle ); + skeletonModelDefHandle = -1; + } +} + +/* +================ +idAFEntity_Gibbable::Save +================ +*/ +void idAFEntity_Gibbable::Save( idSaveGame *savefile ) const { + savefile->WriteBool( gibbed ); + savefile->WriteBool( combatModel != NULL ); +} + +/* +================ +idAFEntity_Gibbable::Restore +================ +*/ +void idAFEntity_Gibbable::Restore( idRestoreGame *savefile ) { + bool hasCombatModel; + + savefile->ReadBool( gibbed ); + savefile->ReadBool( hasCombatModel ); + + InitSkeletonModel(); + + if ( hasCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::Spawn +================ +*/ +void idAFEntity_Gibbable::Spawn( void ) { + InitSkeletonModel(); + + gibbed = false; + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::InitSkeletonModel +================ +*/ +void idAFEntity_Gibbable::InitSkeletonModel( void ) { + const char *modelName; + const idDeclModelDef *modelDef; + + skeletonModel = NULL; + skeletonModelDefHandle = -1; + + modelName = spawnArgs.GetString( "model_gib" ); + + modelDef = NULL; + if ( modelName[0] != '\0' ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + skeletonModel = modelDef->ModelHandle(); + } else { + skeletonModel = renderModelManager->FindModel( modelName ); + } + if ( skeletonModel != NULL && renderEntity.hModel != NULL ) { + if ( skeletonModel->NumJoints() != renderEntity.hModel->NumJoints() ) { + gameLocal.Error( "gib model '%s' has different number of joints than model '%s'", + skeletonModel->Name(), renderEntity.hModel->Name() ); + } + } + } +} + +/* +================ +idAFEntity_Gibbable::Present +================ +*/ +void idAFEntity_Gibbable::Present( void ) { + renderEntity_t skeleton; + + if ( !gameLocal.isNewFrame ) { + return; + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // update skeleton model + if ( gibbed && !IsHidden() && skeletonModel != NULL ) { + skeleton = renderEntity; + skeleton.hModel = skeletonModel; + // add to refresh list + if ( skeletonModelDefHandle == -1 ) { + skeletonModelDefHandle = gameRenderWorld->AddEntityDef( &skeleton ); + } else { + gameRenderWorld->UpdateEntityDef( skeletonModelDefHandle, &skeleton ); + } + } + + idEntity::Present(); +} + +/* +================ +idAFEntity_Gibbable::Damage +================ +*/ +void idAFEntity_Gibbable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + idAFEntity_Base::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( health < -20 && spawnArgs.GetBool( "gib" ) ) { + Gib( dir, damageDefName ); + } +} + +/* +===================== +idAFEntity_Gibbable::SpawnGibs +===================== +*/ +void idAFEntity_Gibbable::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + int i; + bool gibNonSolid; + idVec3 entityCenter, velocity; + idList list; + + assert( !gameLocal.isClient ); + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + // spawn gib articulated figures + idAFEntity_Base::DropAFs( this, "gib", &list ); + + // spawn gib items + idMoveableItem::DropItems( this, "gib", &list ); + + // blow out the gibs in the given direction away from the center of the entity + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + gibNonSolid = damageDef->GetBool( "gibNonSolid" ); + for ( i = 0; i < list.Num(); i++ ) { + if ( gibNonSolid ) { + list[i]->GetPhysics()->SetContents( 0 ); + list[i]->GetPhysics()->SetClipMask( 0 ); + list[i]->GetPhysics()->UnlinkClip(); + list[i]->GetPhysics()->PutToRest(); + } else { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + list[i]->GetPhysics()->SetLinearVelocity( velocity * 225.0f ); + } + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; +// RAVEN BEGIN +// dluetscher: reduced the gib lifetime from 4. to 1.5 + list[i]->PostEventSec( &EV_Remove, 1.5f ); +// RAVEN END + } +} + +/* +============ +idAFEntity_Gibbable::Gib +============ +*/ +void idAFEntity_Gibbable::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + if ( damageDef->GetBool( "gibNonSolid" ) ) { + GetAFPhysics()->SetContents( 0 ); + GetAFPhysics()->SetClipMask( 0 ); + GetAFPhysics()->UnlinkClip(); + GetAFPhysics()->PutToRest(); + } else { + GetAFPhysics()->SetContents( CONTENTS_CORPSE ); + GetAFPhysics()->SetClipMask( CONTENTS_SOLID ); + } + + UnlinkCombat(); +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( g_decals.GetBool() ) { +// RAVEN END + if ( gameLocal.time > gameLocal.GetGibTime() ) { + gameLocal.SetGibTime( gameLocal.time + GIB_DELAY ); + SpawnGibs( dir, damageDefName ); + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + StartSound( "snd_gibbed", SND_CHANNEL_ANY, 0, false, NULL ); + gibbed = true; + } + } else { + gibbed = true; + } + +// RAVEN BEGIN +// bdube: default is to remove the character immediately + // however, this is bad in the case of the player. What was happening is that the + // player was being removed right away, but the respawn logic runs off of + // idPlayer::EvaluateControls, which no longer gets run when the player is gibbed and removed. + // So...the game effectively sits locked on a black screen and you never get a menu.. + if ( !gameLocal.isMultiplayer && this->IsType( idPlayer::GetClassType() )) { + return; + } + PostEventSec( &EV_Gibbed, spawnArgs.GetFloat ( "gibRemoveDelay", "0" ) ); +// RAVEN END +} + +/* +============ +idAFEntity_Gibbable::Event_Gib +============ +*/ +void idAFEntity_Gibbable::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_Generic ) + EVENT( EV_Activate, idAFEntity_Generic::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_Generic::idAFEntity_Generic +================ +*/ +idAFEntity_Generic::idAFEntity_Generic( void ) { + keepRunningPhysics = false; +} + +/* +================ +idAFEntity_Generic::~idAFEntity_Generic +================ +*/ +idAFEntity_Generic::~idAFEntity_Generic( void ) { +} + +/* +================ +idAFEntity_Generic::Save +================ +*/ +void idAFEntity_Generic::Save( idSaveGame *savefile ) const { + savefile->WriteBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Restore +================ +*/ +void idAFEntity_Generic::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Think +================ +*/ +void idAFEntity_Generic::Think( void ) { + idAFEntity_Base::Think(); + + if ( keepRunningPhysics ) { + BecomeActive( TH_PHYSICS ); + } +} + +/* +================ +idAFEntity_Generic::Spawn +================ +*/ +void idAFEntity_Generic::Spawn( void ) { + if ( !LoadAF() ) { + gameLocal.Error( "Couldn't load af file on entity '%s'", name.c_str() ); + } + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + +// RAVEN BEGIN +// kfuller: we just put the guy to rest so don't be affected by an attractor +/* + const char *attractor = spawnArgs.GetString("attractor"); + if (attractor && attractor[0]) { + rvRagdollAttractor *attractorEnt = dynamic_cast(gameLocal.FindEntity(attractor)); + if (attractorEnt) { + attractorEnt->RemoveAttractee(entityNumber); + } else { + gameLocal.Warning("idAFEntity::LoadAF -- failed to find attractor '%s'", attractor); + } + } +*/ +// RAVEN END + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; +} + +/* +================ +idAFEntity_Generic::Event_Activate +================ +*/ +void idAFEntity_Generic::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_WithAttachedHead ) + EVENT( EV_Gib, idAFEntity_WithAttachedHead::Event_Gib ) + EVENT( EV_Activate, idAFEntity_WithAttachedHead::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead() { + head = NULL; +} + +/* +================ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead() { + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Spawn +================ +*/ +void idAFEntity_WithAttachedHead::Spawn( void ) { + SetupHead(); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; + + if ( head.GetEntity() ) { + int anim = head.GetEntity()->GetAnimator()->GetAnim( "dead" ); + + if ( anim ) { +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + head.GetEntity()->GetAnimator()->SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + } + } + + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Save +================ +*/ +void idAFEntity_WithAttachedHead::Save( idSaveGame *savefile ) const { + head.Save( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::Restore +================ +*/ +void idAFEntity_WithAttachedHead::Restore( idRestoreGame *savefile ) { + head.Restore( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::SetupHead +================ +*/ +void idAFEntity_WithAttachedHead::SetupHead( const char* headDefName ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); + } + + if ( headDefName[ 0 ] ) { + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headDefName, joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + + head->InitCopyJoints ( ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Think +================ +*/ +void idAFEntity_WithAttachedHead::Think( void ) { + idAFEntity_Base::Think(); +} + +/* +================ +idAFEntity_WithAttachedHead::LinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::UnlinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Hide +================ +*/ +void idAFEntity_WithAttachedHead::Hide( void ) { + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + UnlinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::Show +================ +*/ +void idAFEntity_WithAttachedHead::Show( void ) { + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + LinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::ProjectOverlay +================ +*/ +void idAFEntity_WithAttachedHead::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + + idEntity::ProjectOverlay( origin, dir, size, material ); + + if ( head.GetEntity() ) { + head.GetEntity()->ProjectOverlay( origin, dir, size, material ); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Gib +============ +*/ +void idAFEntity_WithAttachedHead::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Event_Gib +============ +*/ +void idAFEntity_WithAttachedHead::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +================ +idAFEntity_WithAttachedHead::Event_Activate +================ +*/ +void idAFEntity_WithAttachedHead::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_Vehicle + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Vehicle ) +END_CLASS + +/* +================ +idAFEntity_Vehicle::idAFEntity_Vehicle +================ +*/ +idAFEntity_Vehicle::idAFEntity_Vehicle( void ) { + player = NULL; + eyesJoint = INVALID_JOINT; + steeringWheelJoint = INVALID_JOINT; + wheelRadius = 0.0f; + steerAngle = 0.0f; + steerSpeed = 0.0f; +// dustSmoke = NULL; +} + +/* +================ +idAFEntity_Vehicle::Spawn +================ +*/ +void idAFEntity_Vehicle::Spawn( void ) { + const char *eyesJointName = spawnArgs.GetString( "eyesJoint", "eyes" ); + const char *steeringWheelJointName = spawnArgs.GetString( "steeringWheelJoint", "steeringWheel" ); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + if ( !eyesJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no eyes joint specified", name.c_str() ); + } + eyesJoint = animator.GetJointHandle( eyesJointName ); + if ( !steeringWheelJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no steering wheel joint specified", name.c_str() ); + } + steeringWheelJoint = animator.GetJointHandle( steeringWheelJointName ); + + spawnArgs.GetFloat( "wheelRadius", "20", wheelRadius ); + spawnArgs.GetFloat( "steerSpeed", "5", steerSpeed ); + + player = NULL; + steerAngle = 0.0f; + +/* + const char *smokeName = spawnArgs.GetString( "smoke_vehicle_dust", "muzzlesmoke" ); + if ( *smokeName != '\0' ) { + dustSmoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } +*/ +} + +/* +================ +idAFEntity_Vehicle::Use +================ +*/ +void idAFEntity_Vehicle::Use( idPlayer *other ) { + idVec3 origin; + idMat3 axis; + + if ( player ) { + if ( player == other ) { + other->Unbind(); + player = NULL; + + af.GetPhysics()->SetComeToRest( true ); + } + } + else { + player = other; + animator.GetJointTransform( eyesJoint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + player->GetPhysics()->SetOrigin( origin ); + player->BindToBody( this, 0, true ); + + af.GetPhysics()->SetComeToRest( false ); + af.GetPhysics()->Activate(); + } +} + +/* +================ +idAFEntity_Vehicle::GetSteerAngle +================ +*/ +float idAFEntity_Vehicle::GetSteerAngle( void ) { + float idealSteerAngle, angleDelta; + + idealSteerAngle = player->usercmd.rightmove * ( 30.0f / 128.0f ); + angleDelta = idealSteerAngle - steerAngle; + + if ( angleDelta > steerSpeed ) { + steerAngle += steerSpeed; + } else if ( angleDelta < -steerSpeed ) { + steerAngle -= steerSpeed; + } else { + steerAngle = idealSteerAngle; + } + + return steerAngle; +} + + +/* +=============================================================================== + + idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleFourWheels ) +END_CLASS + + +/* +================ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels +================ +*/ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; +} + +/* +================ +idAFEntity_VehicleFourWheels::Spawn +================ +*/ +void idAFEntity_VehicleFourWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 4; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 2; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleFourWheels::Think +================ +*/ +void idAFEntity_VehicleFourWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 2; i++ ) { + wheels[2+i]->SetContactMotorVelocity( velocity ); + wheels[2+i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + wheels[2]->SetContactMotorVelocity( velocity * 0.5f ); + } + else if ( steerAngle > 0.0f ) { + wheels[3]->SetContactMotorVelocity( velocity * 0.5f ); + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + for ( i = 0; i < 2; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 4; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleSixWheels ) +END_CLASS + + /* +================ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels +================ +*/ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels( void ) { + int i; + + for ( i = 0; i < 6; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; + steering[2] = NULL; + steering[3] = NULL; +} + +/* +================ +idAFEntity_VehicleSixWheels::Spawn +================ +*/ +void idAFEntity_VehicleSixWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyMiddleLeft", + "wheelBodyMiddleRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointMiddleLeft", + "wheelJointMiddleRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + "steeringHingeRearLeft", + "steeringHingeRearRight" + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 6; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 4; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleSixWheels::Think +================ +*/ +void idAFEntity_VehicleSixWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 6; i++ ) { + wheels[i]->SetContactMotorVelocity( velocity ); + wheels[i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + else if ( steerAngle > 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[1+(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + steering[2]->SetSteerAngle( -steerAngle ); + steering[3]->SetSteerAngle( -steerAngle ); + for ( i = 0; i < 4; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 6; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_SteamPipe + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_SteamPipe ) +END_CLASS + + +/* +================ +idAFEntity_SteamPipe::idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::idAFEntity_SteamPipe( void ) { + steamBody = 0; + steamForce = 0.0f; + steamUpForce = 0.0f; + steamModelDefHandle = -1; + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); +} + +/* +================ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe( void ) { + if ( steamModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( steamModelDefHandle ); + } +} + +/* +================ +idAFEntity_SteamPipe::Save +================ +*/ +void idAFEntity_SteamPipe::Save( idSaveGame *savefile ) const { +} + +/* +================ +idAFEntity_SteamPipe::Restore +================ +*/ +void idAFEntity_SteamPipe::Restore( idRestoreGame *savefile ) { + Spawn(); +} + +/* +================ +idAFEntity_SteamPipe::Spawn +================ +*/ +void idAFEntity_SteamPipe::Spawn( void ) { + idVec3 steamDir; + const char *steamBodyName; + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + steamBodyName = spawnArgs.GetString( "steamBody", "" ); + steamForce = spawnArgs.GetFloat( "steamForce", "2000" ); + steamUpForce = spawnArgs.GetFloat( "steamUpForce", "10" ); + steamDir = af.GetPhysics()->GetAxis( steamBody )[2]; + steamBody = af.GetPhysics()->GetBodyId( steamBodyName ); + force.SetPosition( af.GetPhysics(), steamBody, af.GetPhysics()->GetOrigin( steamBody ) ); + force.SetForce( steamDir * -steamForce ); + + InitSteamRenderEntity(); + + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_SteamPipe::InitSteamRenderEntity +================ +*/ +void idAFEntity_SteamPipe::InitSteamRenderEntity( void ) { + const char *temp; + const idDeclModelDef *modelDef; + + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); + steamRenderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + modelDef = NULL; + temp = spawnArgs.GetString ( "model_steam" ); + if ( *temp != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + steamRenderEntity.hModel = modelDef->ModelHandle(); + } + } + + if ( !steamRenderEntity.hModel ) { + steamRenderEntity.hModel = renderModelManager->FindModel( temp ); + } + + if ( steamRenderEntity.hModel ) { + steamRenderEntity.bounds = steamRenderEntity.hModel->Bounds( &steamRenderEntity ); + } else { + steamRenderEntity.bounds.Zero(); + } + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + steamModelDefHandle = gameRenderWorld->AddEntityDef( &steamRenderEntity ); + } +} + +/* +================ +idAFEntity_SteamPipe::Think +================ +*/ +void idAFEntity_SteamPipe::Think( void ) { + idVec3 steamDir; + + if ( thinkFlags & TH_THINK ) { + steamDir.x = gameLocal.random.CRandomFloat() * steamForce; + steamDir.y = gameLocal.random.CRandomFloat() * steamForce; + steamDir.z = steamUpForce; + force.SetForce( steamDir ); + force.Evaluate( gameLocal.time ); + //gameRenderWorld->DebugArrow( colorWhite, af.GetPhysics()->GetOrigin( steamBody ), af.GetPhysics()->GetOrigin( steamBody ) - 10.0f * steamDir, 4 ); + } + + if ( steamModelDefHandle >= 0 ){ + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + gameRenderWorld->UpdateEntityDef( steamModelDefHandle, &steamRenderEntity ); + } + + idAFEntity_Base::Think(); +} + + +/* +=============================================================================== + + idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +const idEventDef EV_SetFingerAngle( "setFingerAngle", "f" ); +const idEventDef EV_StopFingers( "stopFingers" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_ClawFourFingers ) + EVENT( EV_SetFingerAngle, idAFEntity_ClawFourFingers::Event_SetFingerAngle ) + EVENT( EV_StopFingers, idAFEntity_ClawFourFingers::Event_StopFingers ) +END_CLASS + +static const char *clawConstraintNames[] = { + "claw1", "claw2", "claw3", "claw4" +}; + +/* +================ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers +================ +*/ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers( void ) { + fingers[0] = NULL; + fingers[1] = NULL; + fingers[2] = NULL; + fingers[3] = NULL; +} + +/* +================ +idAFEntity_ClawFourFingers::Save +================ +*/ +void idAFEntity_ClawFourFingers::Save( idSaveGame *savefile ) const { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->Save( savefile ); + } +} + +/* +================ +idAFEntity_ClawFourFingers::Restore +================ +*/ +void idAFEntity_ClawFourFingers::Restore( idRestoreGame *savefile ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + fingers[i]->Restore( savefile ); + } + + SetCombatModel(); + LinkCombat(); +} + +/* +================ +idAFEntity_ClawFourFingers::Spawn +================ +*/ +void idAFEntity_ClawFourFingers::Spawn( void ) { + int i; + + LoadAF(); + + SetCombatModel(); + + af.GetPhysics()->LockWorldConstraints( true ); + af.GetPhysics()->SetForcePushable( true ); + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + if ( !fingers[i] ) { + gameLocal.Error( "idClaw_FourFingers '%s': can't find claw constraint '%s'", name.c_str(), clawConstraintNames[i] ); + } + } +} + +/* +================ +idAFEntity_ClawFourFingers::Event_SetFingerAngle +================ +*/ +void idAFEntity_ClawFourFingers::Event_SetFingerAngle( float angle ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( angle ); + fingers[i]->SetSteerSpeed( 0.5f ); + } + af.GetPhysics()->Activate(); +} + +/* +================ +idAFEntity_ClawFourFingers::Event_StopFingers +================ +*/ +void idAFEntity_ClawFourFingers::Event_StopFingers( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( fingers[i]->GetAngle() ); + } +} + + +/* +=============================================================================== + + editor support routines + +=============================================================================== +*/ + + +/* +================ +idGameEdit::AF_SpawnEntity +================ +*/ +bool idGameEdit::AF_SpawnEntity( const char *fileName ) { + idDict args; + idPlayer *player; + idAFEntity_Generic *ent; + const idDeclAF *af; + idVec3 org; + float yaw; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return false; + } + + af = static_cast( declManager->FindType( DECL_AF, fileName ) ); + if ( !af ) { + return false; + } + + yaw = player->viewAngles.yaw; + args.Set( "angle", va( "%f", yaw + 180 ) ); + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + args.Set( "origin", org.ToString() ); + args.Set( "spawnclass", "idAFEntity_Generic" ); + if ( af->model[0] ) { + args.Set( "model", af->model.c_str() ); + } else { + args.Set( "model", fileName ); + } + if ( af->skin[0] ) { + args.Set( "skin", af->skin.c_str() ); + } + args.Set( "articulatedFigure", fileName ); + args.Set( "nodrop", "1" ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + ent = static_cast(gameLocal.SpawnEntityType( idAFEntity_Generic::GetClassType(), &args)); +// RAVEN END + + // always update this entity + ent->BecomeActive( TH_THINK ); + ent->KeepRunningPhysics(); + ent->fl.forcePhysicsUpdate = true; + + player->dragEntity.SetSelected( ent ); + + return true; +} + +/* +================ +idGameEdit::AF_UpdateEntities +================ +*/ +void idGameEdit::AF_UpdateEntities( const char *fileName ) { + idEntity *ent; + idAFEntity_Base *af; + idStr name; + + name = fileName; + name.StripFileExtension(); + + // reload any idAFEntity_Generic which uses the given articulated figure file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( name.Icmp( af->GetAFName() ) == 0 ) { + af->LoadAF(); + af->GetAFPhysics()->PutToRest(); + } + } + } +} + +/* +================ +idGameEdit::AF_UndoChanges +================ +*/ +void idGameEdit::AF_UndoChanges( void ) { + int i, c; + idEntity *ent; + idAFEntity_Base *af; + idDeclAF *decl; + + c = declManager->GetNumDecls( DECL_AF ); + for ( i = 0; i < c; i++ ) { + decl = static_cast( const_cast( declManager->DeclByIndex( DECL_AF, i, false ) ) ); + if ( !decl->modified ) { + continue; + } + + decl->Invalidate(); + declManager->FindType( DECL_AF, decl->GetName() ); + + // reload all AF entities using the file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( idStr::Icmp( decl->GetName(), af->GetAFName() ) == 0 ) { + af->LoadAF(); + } + } + } + } +} + +/* +================ +GetJointTransform +================ +*/ +typedef struct { + renderEntity_t *ent; + const idMD5Joint *joints; +} jointTransformData_t; + +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + int i; + jointTransformData_t *data = reinterpret_cast(model); + + for ( i = 0; i < data->ent->numJoints; i++ ) { + if ( data->joints[i].name.Icmp( jointName ) == 0 ) { + break; + } + } + if ( i >= data->ent->numJoints ) { + return false; + } + origin = frame[i].ToVec3(); + axis = frame[i].ToMat3(); + return true; +} + +/* +================ +GetArgString +================ +*/ +static const char *GetArgString( const idDict &args, const idDict *defArgs, const char *key ) { + const char *s; + + s = args.GetString( key ); + if ( !s[0] && defArgs ) { + s = defArgs->GetString( key ); + } + return s; +} + +/* +================ +idGameEdit::AF_CreateMesh +================ +*/ +idRenderModel *idGameEdit::AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ) { + int i, jointNum; + const idDeclAF *af; + const idDeclAF_Body *fb = NULL; + renderEntity_t ent; + idVec3 origin, *bodyOrigin, *newBodyOrigin, *modifiedOrigin; + idMat3 axis, *bodyAxis, *newBodyAxis, *modifiedAxis; + declAFJointMod_t *jointMod; + idAngles angles; + const idDict *defArgs; + const idKeyValue *arg; + idStr name; + jointTransformData_t data; + const char *classname, *afName, *modelName; + idRenderModel *md5; + const idDeclModelDef *modelDef; + const idMD5Anim *MD5anim; + const idMD5Joint *MD5joint; + const idMD5Joint *MD5joints; + int numMD5joints; + idJointMat *originalJoints; + int parentNum; + + poseIsSet = false; + meshOrigin.Zero(); + meshAxis.Identity(); + + classname = args.GetString( "classname" ); + defArgs = gameLocal.FindEntityDefDict( classname ); + + // get the articulated figure + afName = GetArgString( args, defArgs, "articulatedFigure" ); + af = static_cast( declManager->FindType( DECL_AF, afName ) ); + if ( !af ) { + return NULL; + } + + // get the md5 model + modelName = GetArgString( args, defArgs, "model" ); + modelDef = static_cast< const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( !modelDef ) { + return NULL; + } + + // make sure model hasn't been purged + if ( modelDef->ModelHandle() && !modelDef->ModelHandle()->IsLoaded() ) { + modelDef->ModelHandle()->LoadModel(); + } + + // get the md5 + md5 = modelDef->ModelHandle(); + if ( !md5 || md5->IsDefaultModel() ) { + return NULL; + } + + // get the articulated figure pose anim + int animNum = modelDef->GetAnim( "af_pose" ); + if ( !animNum ) { + return NULL; + } + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + MD5anim = anim->MD5Anim( 0 ); + MD5joints = md5->GetJoints(); + numMD5joints = md5->NumJoints(); + + // setup a render entity + memset( &ent, 0, sizeof( ent ) ); + ent.customSkin = modelDef->GetSkin(); + ent.bounds.Clear(); + ent.numJoints = numMD5joints; + ent.joints = ( idJointMat * )_alloca16( ent.numJoints * sizeof( *ent.joints ) ); + + // create animation from of the af_pose + ANIM_CreateAnimFrame( md5, MD5anim, ent.numJoints, ent.joints, 1, modelDef->GetVisualOffset(), false ); + + // buffers to store the initial origin and axis for each body + bodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + bodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + newBodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + newBodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + + // finish the AF positions + data.ent = &ent; + data.joints = MD5joints; + af->Finish( GetJointTransform, ent.joints, &data ); + + // get the initial origin and axis for each AF body + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->modelType == TRM_BONE ) { + // axis of bone trace model + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + axis[2].Normalize(); + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + } else { + axis = fb->angles.ToMat3(); + } + + newBodyOrigin[i] = bodyOrigin[i] = fb->origin.ToVec3(); + newBodyAxis[i] = bodyAxis[i] = axis; + } + + // get any new body transforms stored in the key/value pairs + for ( arg = args.MatchPrefix( "body ", NULL ); arg; arg = args.MatchPrefix( "body ", arg ) ) { + name = arg->GetKey(); + name.Strip( "body " ); + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + if ( fb->name.Icmp( name ) == 0 ) { + break; + } + } + if ( i >= af->bodies.Num() ) { + continue; + } + sscanf( arg->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + meshAxis = bodyAxis[i].Transpose() * angles.ToMat3(); + meshOrigin = origin - bodyOrigin[i] * meshAxis; + poseIsSet = true; + } else { + newBodyOrigin[i] = origin; + newBodyAxis[i] = angles.ToMat3(); + } + } + + // save the original joints + originalJoints = ( idJointMat * )_alloca16( numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( originalJoints, ent.joints, numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN END + + // buffer to store the joint mods + jointMod = (declAFJointMod_t *) _alloca16( numMD5joints * sizeof( declAFJointMod_t ) ); + memset( jointMod, -1, numMD5joints * sizeof( declAFJointMod_t ) ); + modifiedOrigin = (idVec3 *) _alloca16( numMD5joints * sizeof( idVec3 ) ); + memset( modifiedOrigin, 0, numMD5joints * sizeof( idVec3 ) ); + modifiedAxis = (idMat3 *) _alloca16( numMD5joints * sizeof( idMat3 ) ); + memset( modifiedAxis, 0, numMD5joints * sizeof( idMat3 ) ); + + // get all the joint modifications + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + continue; + } + + for ( jointNum = 0; jointNum < numMD5joints; jointNum++ ) { + if ( MD5joints[jointNum].name.Icmp( fb->jointName ) == 0 ) { + break; + } + } + + if ( jointNum >= 0 && jointNum < ent.numJoints ) { + jointMod[ jointNum ] = fb->jointMod; + modifiedAxis[ jointNum ] = ( bodyAxis[i] * originalJoints[jointNum].ToMat3().Transpose() ).Transpose() * ( newBodyAxis[i] * meshAxis.Transpose() ); + // FIXME: calculate correct modifiedOrigin + modifiedOrigin[ jointNum ] = originalJoints[ jointNum ].ToVec3(); + } + } + + // apply joint modifications to the skeleton + MD5joint = MD5joints + 1; + for( i = 1; i < numMD5joints; i++, MD5joint++ ) { + + parentNum = MD5joint->parent - MD5joints; + idMat3 parentAxis = originalJoints[ parentNum ].ToMat3(); + idMat3 localm = originalJoints[i].ToMat3() * parentAxis.Transpose(); + idVec3 localt = ( originalJoints[i].ToVec3() - originalJoints[ parentNum ].ToVec3() ) * parentAxis.Transpose(); + + switch( jointMod[i] ) { + case DECLAF_JOINTMOD_ORIGIN: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + case DECLAF_JOINTMOD_AXIS: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + case DECLAF_JOINTMOD_BOTH: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + default: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + } + } + + // instantiate a mesh using the joint information from the render entity + return md5->InstantiateDynamicModel( &ent, NULL, NULL ); +} + +// RAVEN BEGIN +// bdube: af attractors + +/* +=============================================================================== + + rvAFAttractor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAFAttractor ) +END_CLASS + +// RAVEN END diff --git a/src/game/AFEntity.h b/src/game/AFEntity.h new file mode 100644 index 0000000..642a411 --- /dev/null +++ b/src/game/AFEntity.h @@ -0,0 +1,531 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_AFENTITY_H__ +#define __GAME_AFENTITY_H__ + + +/* +=============================================================================== + +idMultiModelAF + +Entity using multiple separate visual models animated with a single +articulated figure. Only used for debugging! + +=============================================================================== +*/ +const int GIB_DELAY = 200; // only gib this often to keep performace hits when blowing up several mobs + +class idMultiModelAF : public idEntity { +public: + CLASS_PROTOTYPE( idMultiModelAF ); + + void Spawn( void ); + ~idMultiModelAF( void ); + + virtual void Think( void ); + virtual void Present( void ); + +protected: + idPhysics_AF physicsObj; + + void SetModelForId( int id, const idStr &modelName ); + +private: + idList modelHandles; + idList modelDefHandles; +}; + + +/* +=============================================================================== + +idChain + +Chain hanging down from the ceiling. Only used for debugging! + +=============================================================================== +*/ + +class idChain : public idMultiModelAF { +public: + CLASS_PROTOTYPE( idChain ); + + void Spawn( void ); + +protected: + void BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld = true ); +}; + + +/* +=============================================================================== + +idAFAttachment + +=============================================================================== +*/ + +typedef struct { + jointModTransform_t mod; + jointHandle_t from; + jointHandle_t to; +} copyJoints_t; + +class idAFAttachment : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFAttachment ); + + idAFAttachment( void ); + virtual ~idAFAttachment( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + +// RAVEN BEGIN +// bdube: added channel + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + // for getting th speaker position + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// jshepard: animations for heads + void Event_PlayAnim ( int channel, const char *animname ); +// jdischler: animations for heads + void Event_PlayCycle ( int channel, const char *animname ); + void Event_ClearAnims ( void ); + + +// RAVEN END + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + int StartLipSyncing( const char *speechDecl ); + void HandleLipSync( void ); + void EndLipSyncing( void ); + bool IsLipSyncing( void ) const; + + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + + + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + jointHandle_t soundJoint; + + int lipSyncAnim; // Anim that contains the visemes + class rvLipSyncData* lipSyncData; // The current instance of lip syncing data + + idList copyJoints; // copied from the body animation to the head model + + bool noPlayerImpactFX; +}; + +// RAVEN BEGIN +// bdube: inlines +ID_INLINE bool idAFAttachment::IsLipSyncing( void ) const { + return !!lipSyncData; +} + +ID_INLINE void idAFAttachment::SetDamageJoint ( jointHandle_t _damageJoint ) { + damageJoint = _damageJoint; +} +// RAVEN END + +/* +=============================================================================== + +idAFEntity_Base + +=============================================================================== +*/ + +class idAFEntity_Base : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFEntity_Base ); + + idAFEntity_Base( void ); + virtual ~idAFEntity_Base( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeModelDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + virtual void ShowEditingDialog( void ); + + static void DropAFs( idEntity *ent, const char *type, idList *list ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound + + bool noPlayerImpactFX; + + void Event_SetConstraintPosition( const char *name, const idVec3 &pos ); +}; + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +extern const idEventDef EV_Gib; +extern const idEventDef EV_Gibbed; + +class idAFEntity_Gibbable : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Gibbable ); + + idAFEntity_Gibbable( void ); + ~idAFEntity_Gibbable( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Present( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +protected: + idRenderModel * skeletonModel; + int skeletonModelDefHandle; + bool gibbed; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void InitSkeletonModel( void ); + + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +class idAFEntity_Generic : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_Generic ); + + idAFEntity_Generic( void ); + ~idAFEntity_Generic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void KeepRunningPhysics( void ) { keepRunningPhysics = true; } + +private: + void Event_Activate( idEntity *activator ); + + bool keepRunningPhysics; +}; + + +/* +=============================================================================== + +idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +class idAFEntity_WithAttachedHead : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_WithAttachedHead ); + + idAFEntity_WithAttachedHead(); + ~idAFEntity_WithAttachedHead(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void SetupHead( const char* headDefName = "" ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + +protected: + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + + idEntityPtr head; // safe pointer to attached head + +private: + + void Event_Gib( const char *damageDefName ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idAFEntity_Vehicle + +=============================================================================== +*/ + +class idAFEntity_Vehicle : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Vehicle ); + + idAFEntity_Vehicle( void ); + + void Spawn( void ); + void Use( idPlayer *player ); + +protected: + idPlayer * player; + jointHandle_t eyesJoint; + jointHandle_t steeringWheelJoint; + float wheelRadius; + float steerAngle; + float steerSpeed; +// const idDeclParticle * dustSmoke; + + float GetSteerAngle( void ); +}; + + +/* +=============================================================================== + +idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleFourWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleFourWheels ); + + idAFEntity_VehicleFourWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +protected: + idAFBody * wheels[4]; + idAFConstraint_Hinge * steering[2]; + jointHandle_t wheelJoints[4]; + float wheelAngles[4]; +}; + + +/* +=============================================================================== + +idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleSixWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleSixWheels ); + + idAFEntity_VehicleSixWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +private: + idAFBody * wheels[6]; + idAFConstraint_Hinge * steering[4]; + jointHandle_t wheelJoints[6]; + float wheelAngles[6]; +}; + + +/* +=============================================================================== + +idAFEntity_SteamPipe + +=============================================================================== +*/ + +class idAFEntity_SteamPipe : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_SteamPipe ); + + idAFEntity_SteamPipe( void ); + ~idAFEntity_SteamPipe( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + int steamBody; + float steamForce; + float steamUpForce; + idForce_Constant force; + renderEntity_t steamRenderEntity; + qhandle_t steamModelDefHandle; + + void InitSteamRenderEntity( void ); +}; + + +/* +=============================================================================== + +idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +class idAFEntity_ClawFourFingers : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_ClawFourFingers ); + + idAFEntity_ClawFourFingers( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idAFConstraint_Hinge * fingers[4]; + + void Event_SetFingerAngle( float angle ); + void Event_StopFingers( void ); +}; + +// RAVEN BEGIN +// bdube: AFAttractor + +/* +=============================================================================== + +idAFAttractor + +=============================================================================== +*/ + +class rvAFAttractor : public idEntity { +public: + CLASS_PROTOTYPE( rvAFAttractor ); + + rvAFAttractor( void ) { } + +private: +}; + +// RAVEN END + +#endif /* !__GAME_AFENTITY_H__ */ diff --git a/src/game/Actor.cpp b/src/game/Actor.cpp new file mode 100644 index 0000000..a7aa86a --- /dev/null +++ b/src/game/Actor.cpp @@ -0,0 +1,3879 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "Vehicle/Vehicle.h" +#endif + +#include "ai/AI.h" +#include "ai/AI_Manager.h" + +/*********************************************************************** + + idAnimState + +***********************************************************************/ + +/* +===================== +idAnimState::idAnimState +===================== +*/ +idAnimState::idAnimState() { + self = NULL; + animator = NULL; + idleAnim = true; + disabled = true; + channel = ANIMCHANNEL_ALL; + animBlendFrames = 0; + lastAnimBlendFrames = 0; +} + +/* +===================== +idAnimState::~idAnimState +===================== +*/ +idAnimState::~idAnimState() { +} + +/* +===================== +idAnimState::Save +===================== +*/ +void idAnimState::Save( idSaveGame *savefile ) const { + + savefile->WriteBool( idleAnim ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( lastAnimBlendFrames ); + + savefile->WriteObject( self ); + + // Save the entity owner of the animator + savefile->WriteObject( animator->GetEntity() ); + + savefile->WriteInt( channel ); + savefile->WriteBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Save( savefile ); +// RAVEN END +} + +/* +===================== +idAnimState::Restore +===================== +*/ +void idAnimState::Restore( idRestoreGame *savefile ) { + + savefile->ReadBool( idleAnim ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( lastAnimBlendFrames ); + + savefile->ReadObject( reinterpret_cast( self ) ); + + idEntity *animowner; + savefile->ReadObject( reinterpret_cast( animowner ) ); + if ( animowner ) { + animator = animowner->GetAnimator(); + } + + savefile->ReadInt( channel ); + savefile->ReadBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Restore( savefile, self ); +// RAVEN END +} + +/* +===================== +idAnimState::Init +===================== +*/ +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it +void idAnimState::Init( idEntity *owner, idAnimator *_animator, int animchannel ) { +// RAVEN BEGIN + assert( owner ); + assert( _animator ); + self = owner; + animator = _animator; + channel = animchannel; + + stateThread.SetName ( va("%s_anim_%d", owner->GetName(), animchannel ) ); + stateThread.SetOwner ( owner ); +} + +/* +===================== +idAnimState::Shutdown +===================== +*/ +void idAnimState::Shutdown( void ) { + stateThread.Clear ( true ); +} + +/* +===================== +idAnimState::PostState +===================== +*/ +void idAnimState::PostState ( const char* statename, int blendFrames, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, blendFrames, delay, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + disabled = false; +} + +/* +===================== +idAnimState::SetState +===================== +*/ +void idAnimState::SetState( const char *statename, int blendFrames, int flags ) { + if ( SRESULT_OK != stateThread.SetState ( statename, blendFrames, 0, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + disabled = false; + idleAnim = false; +} + +/* +===================== +idAnimState::StopAnim +===================== +*/ +void idAnimState::StopAnim( int frames ) { + animBlendFrames = 0; + animator->Clear( channel, gameLocal.time, FRAME2MS( frames ) ); +} + +/* +===================== +idAnimState::PlayAnim +===================== +*/ +void idAnimState::PlayAnim( int anim ) { + if ( anim ) { + animator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::CycleAnim +===================== +*/ +void idAnimState::CycleAnim( int anim ) { + if ( anim ) { + animator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::BecomeIdle +===================== +*/ +void idAnimState::BecomeIdle( void ) { + idleAnim = true; +} + +/* +===================== +idAnimState::Disabled +===================== +*/ +bool idAnimState::Disabled( void ) const { + return disabled; +} + +/* +===================== +idAnimState::AnimDone +===================== +*/ +bool idAnimState::AnimDone( int blendFrames ) const { + int animDoneTime; + + animDoneTime = animator->CurrentAnim( channel )->GetEndTime(); + if ( animDoneTime < 0 ) { + // playing a cycle + return false; + } else if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } else { + return false; + } +} + +/* +===================== +idAnimState::IsIdle +===================== +*/ +bool idAnimState::IsIdle( void ) const { + return disabled || idleAnim; +} + +/* +===================== +idAnimState::GetAnimFlags +===================== +*/ +animFlags_t idAnimState::GetAnimFlags( void ) const { + animFlags_t flags; + + memset( &flags, 0, sizeof( flags ) ); + if ( !disabled && !AnimDone( 0 ) ) { + flags = animator->GetAnimFlags( animator->CurrentAnim( channel )->AnimNum() ); + } + + return flags; +} + +/* +===================== +idAnimState::Enable +===================== +*/ +void idAnimState::Enable( int blendFrames ) { + if ( disabled ) { + disabled = false; + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + } +} + +/* +===================== +idAnimState::Disable +===================== +*/ +void idAnimState::Disable( void ) { + disabled = true; + idleAnim = false; +} + +/* +===================== +idAnimState::UpdateState +===================== +*/ +bool idAnimState::UpdateState( void ) { + if ( disabled ) { + return false; + } + + stateThread.Execute ( ); + + return true; +} + +/*********************************************************************** + + idActor + +***********************************************************************/ + +const idEventDef AI_EnableEyeFocus( "enableEyeFocus" ); +const idEventDef AI_DisableEyeFocus( "disableEyeFocus" ); +const idEventDef AI_EnableBlink( "enableBlinking" ); +const idEventDef AI_DisableBlink( "disableBlinking" ); +const idEventDef EV_Footstep( "footstep" ); +const idEventDef EV_FootstepLeft( "leftFoot" ); +const idEventDef EV_FootstepRight( "rightFoot" ); +const idEventDef EV_EnableWalkIK( "EnableWalkIK" ); +const idEventDef EV_DisableWalkIK( "DisableWalkIK" ); +const idEventDef EV_EnableLegIK( "EnableLegIK", "d" ); +const idEventDef EV_DisableLegIK( "DisableLegIK", "d" ); +const idEventDef AI_StopAnim( "stopAnim", "dd" ); +const idEventDef AI_PlayAnim( "playAnim", "ds", 'd' ); +const idEventDef AI_PlayCycle( "playCycle", "ds", 'd' ); +const idEventDef AI_IdleAnim( "idleAnim", "ds", 'd' ); +const idEventDef AI_SetSyncedAnimWeight( "setSyncedAnimWeight", "ddf" ); +const idEventDef AI_SetBlendFrames( "setBlendFrames", "dd" ); +const idEventDef AI_GetBlendFrames( "getBlendFrames", "d", 'd' ); +const idEventDef AI_AnimDone( "animDone", "dd", 'd' ); +const idEventDef AI_OverrideAnim( "overrideAnim", "d" ); +const idEventDef AI_EnableAnim( "enableAnim", "dd" ); +const idEventDef AI_PreventPain( "preventPain", "f" ); +const idEventDef AI_DisablePain( "disablePain" ); +const idEventDef AI_EnablePain( "enablePain" ); +const idEventDef AI_SetAnimPrefix( "setAnimPrefix", "s" ); +const idEventDef AI_HasEnemies( "hasEnemies", NULL, 'd' ); +const idEventDef AI_NextEnemy( "nextEnemy", "E", 'e' ); +const idEventDef AI_ClosestEnemyToPoint( "closestEnemyToPoint", "v", 'e' ); +const idEventDef AI_GetHead( "getHead", NULL, 'e' ); + + +// RAVEN BEGIN +// bdube: added +const idEventDef AI_Flashlight("flashlight", "d" ); +const idEventDef AI_Teleport("teleport", "vv"); +const idEventDef AI_EnterVehicle ( "enterVehicle", "e" ); +const idEventDef AI_ExitVehicle ( "exitVehicle", "d" ); +const idEventDef AI_PostExitVehicle ( "", "d" ); + +//jshepard: change animation rate +const idEventDef AI_SetAnimRate ( "setAnimRate","f"); +//MCG: damage over time +const idEventDef EV_DamageOverTime ( "damageOverTime","ddEEvsfd" ); +const idEventDef EV_DamageOverTimeEffect ( "damageOverTimeEffect","dds" ); +// MCG: script-callable joint crawl effect +const idEventDef EV_JointCrawlEffect ( "jointCrawlEffect","sf" ); + +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idActor ) + EVENT( AI_EnableEyeFocus, idActor::Event_EnableEyeFocus ) + EVENT( AI_DisableEyeFocus, idActor::Event_DisableEyeFocus ) + EVENT( AI_EnableBlink, idActor::Event_EnableBlink ) + EVENT( AI_DisableBlink, idActor::Event_DisableBlink ) + EVENT( EV_Footstep, idActor::Event_Footstep ) + EVENT( EV_FootstepLeft, idActor::Event_Footstep ) + EVENT( EV_FootstepRight, idActor::Event_Footstep ) + EVENT( EV_EnableWalkIK, idActor::Event_EnableWalkIK ) + EVENT( EV_DisableWalkIK, idActor::Event_DisableWalkIK ) + EVENT( EV_EnableLegIK, idActor::Event_EnableLegIK ) + EVENT( EV_DisableLegIK, idActor::Event_DisableLegIK ) + EVENT( AI_PreventPain, idActor::Event_PreventPain ) + EVENT( AI_DisablePain, idActor::Event_DisablePain ) + EVENT( AI_EnablePain, idActor::Event_EnablePain ) + EVENT( AI_SetAnimPrefix, idActor::Event_SetAnimPrefix ) + EVENT( AI_SetSyncedAnimWeight, idActor::Event_SetSyncedAnimWeight ) + EVENT( AI_SetBlendFrames, idActor::Event_SetBlendFrames ) + EVENT( AI_GetBlendFrames, idActor::Event_GetBlendFrames ) + EVENT( AI_OverrideAnim, idActor::Event_OverrideAnim ) + EVENT( AI_EnableAnim, idActor::Event_EnableAnim ) + EVENT( AI_HasEnemies, idActor::Event_HasEnemies ) + EVENT( AI_NextEnemy, idActor::Event_NextEnemy ) + EVENT( AI_ClosestEnemyToPoint, idActor::Event_ClosestEnemyToPoint ) + EVENT( EV_StopSound, idActor::Event_StopSound ) + EVENT( AI_GetHead, idActor::Event_GetHead ) + +// RAVEN BEGIN +// bdube: added + EVENT( AI_Flashlight, idActor::Event_Flashlight ) + EVENT( AI_Teleport, idActor::Event_Teleport ) + EVENT( AI_EnterVehicle, idActor::Event_EnterVehicle ) + + // twhitaker: Yeah... this just got confusing. + // basically, I need a delay in between the time the space bar is hit and the time the person actually get's ejected. + // this is mostly for things such as screen fades when exiting a vehicle. This was the least obtrusive way I could think of. + EVENT( AI_ExitVehicle, idActor::Event_PreExitVehicle ) + EVENT( AI_PostExitVehicle, idActor::Event_ExitVehicle ) + +// jshepard: added + EVENT( AI_SetAnimRate, idActor::Event_SetAnimRate ) + +// twhitaker: added animation support (mostly for vehicle purposes) + EVENT( AI_PlayAnim, idActor::Event_PlayAnim ) + +// MCG: added recurring damage + EVENT( EV_DamageOverTime, idActor::Event_DamageOverTime ) + EVENT( EV_DamageOverTimeEffect, idActor::Event_DamageOverTimeEffect ) +// MCG: script-callable joint crawl effect + EVENT( EV_JointCrawlEffect, idActor::Event_JointCrawlEffect ) +// RAVEN END + +END_CLASS + +CLASS_STATES_DECLARATION ( idActor ) + STATE ( "Wait_Frame", idActor::State_Wait_Frame ) + STATE ( "Wait_LegsAnim", idActor::State_Wait_LegsAnim ) + STATE ( "Wait_TorsoAnim", idActor::State_Wait_TorsoAnim ) +END_CLASS_STATES + +// RAVEN END + +/* +===================== +idActor::idActor +===================== +*/ +idActor::idActor( void ) +{ + viewAxis.Identity(); + + use_combat_bbox = false; + head = NULL; + + eyeOffset.Zero(); + chestOffset.Zero(); + modelOffset.Zero(); + + team = 0; + rank = 0; + fovDot = 0.0f; + pain_debounce_time = 0; + pain_delay = 0; + + leftEyeJoint = INVALID_JOINT; + rightEyeJoint = INVALID_JOINT; + soundJoint = INVALID_JOINT; + + deltaViewAngles.Zero(); + + painTime = 0; + inDamageEvent = false; +// RAVEN BEGIN +// bdube: reversed var + disablePain = true; +// RAVEN END + allowEyeFocus = false; + + blink_anim = NULL; + blink_time = 0; + blink_min = 0; + blink_max = 0; + + finalBoss = false; + + attachments.SetGranularity( 1 ); + + enemyNode.SetOwner( this ); + enemyList.SetOwner( this ); + + teamNode.SetOwner ( this ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlightHandle = -1; + + deathPushTime = 0; + + eyeOffsetJoint = INVALID_JOINT; + chestOffsetJoint = INVALID_JOINT; + + lightningEffects = 0; + lightningNextTime = 0; +} + +/* +===================== +idActor::~idActor +===================== +*/ +idActor::~idActor( void ) { +// RAVEN BEGIN +// bdube: flashlights + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef( flashlightHandle ); + flashlightHandle = -1; + } +// RAVEN END + + int i; + idEntity *ent; + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + ShutdownThreads(); +} + +/* +===================== +idActor::Spawn +===================== +*/ +void idActor::Spawn( void ) { + idEntity *ent; + idStr jointName; + float fovDegrees; + float fovDegreesClose; + + animPrefix = ""; + + spawnArgs.GetInt( "rank", "0", rank ); + spawnArgs.GetInt( "team", "0", team ); + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + spawnArgs.GetBool( "use_combat_bbox", "0", use_combat_bbox ); + + viewAxis = GetPhysics()->GetAxis(); + + spawnArgs.GetFloat( "fov", "90", fovDegrees ); + spawnArgs.GetFloat( "fovClose", "200", fovDegreesClose ); + spawnArgs.GetFloat( "fovCloseRange", "180", fovCloseRange ); + SetFOV( fovDegrees, fovDegreesClose ); + + pain_debounce_time = 0; + + pain_delay = SEC2MS( spawnArgs.GetFloat( "pain_delay" ) ); + + LoadAF ( ); + + walkIK.Init( this, IK_ANIM, modelOffset ); + + // the animation used to be set to the IK_ANIM at this point, but that was fixed, resulting in + // attachments not binding correctly, so we're stuck setting the IK_ANIM before attaching things. + animator.ClearAllAnims( gameLocal.time, 0 ); +// RAVEN BEGIN +// jscott: new setframe stuff + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, animator.GetAnim( IK_ANIM ), frameBlend ); +// RAVEN END + + // spawn any attachments we might have + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_attach", NULL ); + while ( kv ) { + idDict args; + + args.Set( "classname", kv->GetValue().c_str() ); + + // make items non-touchable so the player can't take them out of the character's hands + args.Set( "no_touch", "1" ); + + // don't let them drop to the floor + args.Set( "dropToFloor", "0" ); + + gameLocal.SpawnEntityDef( args, &ent ); + if ( !ent ) { + gameLocal.Error( "Couldn't spawn '%s' to attach to entity '%s'", kv->GetValue().c_str(), name.c_str() ); + } else { + Attach( ent ); + } + kv = spawnArgs.MatchPrefix( "def_attach", kv ); + } + + SetupDamageGroups(); + + // MP sets up heads on players from UpdateModelSetup() + if( !gameLocal.isMultiplayer || !IsType( idPlayer::GetClassType() ) ) { + SetupHead ( ); + } + + // clear the bind anim + animator.ClearAllAnims( gameLocal.time, 0 ); + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } + + // set up blinking + blink_anim = headAnimator->GetAnim( "blink" ); + blink_time = 0; // it's ok to blink right away + blink_min = SEC2MS( spawnArgs.GetFloat( "blink_min", "0.5" ) ); + blink_max = SEC2MS( spawnArgs.GetFloat( "blink_max", "8" ) ); + fl.allowAutoBlink = spawnArgs.GetBool( "allowAutoBlink", "1" ); + + if ( spawnArgs.GetString( "sound_bone", "", jointName ) ) { + soundJoint = animator.GetJointHandle( jointName ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + finalBoss = spawnArgs.GetBool( "finalBoss" ); + +// RAVEN BEGIN +// bdube: flashlight + flashlightJoint = animator.GetJointHandle( spawnArgs.GetString ( "joint_flashlight", "flashlight" ) ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlight.suppressLightInViewID = entityNumber + 1; + flashlight.allowLightInViewID = 0; + flashlight.lightId = 1 + entityNumber; + + flashlight.allowLightInViewID = 1; + + idVec3 color; + spawnArgs.GetVector ( "flashlightColor", "1 1 1", color ); + + flashlight.pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + flashlight.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_flashlight", "muzzleflash" ), false ); + flashlight.shaderParms[ SHADERPARM_RED ] = color[0]; + flashlight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + flashlight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + flashlight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + flashlight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + flashlight.lightRadius[0] = flashlight.lightRadius[1] = + flashlight.lightRadius[2] = spawnArgs.GetFloat ( "flashlightRadius" ); + + if ( !flashlight.pointLight ) { + flashlight.target = spawnArgs.GetVector( "flashlightTarget" ); + flashlight.up = spawnArgs.GetVector( "flashlightUp" ); + flashlight.right = spawnArgs.GetVector( "flashlightRight" ); + flashlight.end = spawnArgs.GetVector( "flashlightTarget" ); + } + + spawnArgs.GetVector ( "flashlightOffset", "0 0 0", flashlightOffset ); + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + +// RAVEN END + + stateThread.SetName ( GetName() ); + stateThread.SetOwner ( this ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = true; +// RAVEN END + + FinishSetup(); +} + +/* +================ +idActor::FinishSetup +================ +*/ +void idActor::FinishSetup( void ) { + if ( spawnArgs.GetBool ( "flashlight", "0" ) ) { + FlashlightUpdate ( true ); + } + + SetupBody(); +} + +/* +================ +idActor::SetupHead +================ +*/ +void idActor::SetupHead( const char* headDefName, idVec3 headOffset ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); +// jshepard: allow for heads to override persona defs + headDefName = spawnArgs.GetString( "override_head", headDefName ); + } + + if ( headDefName[ 0 ] ) { + // free the old head if we want a new one + if( gameLocal.isServer ) { + if( head && idStr::Icmp( head->spawnArgs.GetString( "classname" ), headDefName ) ) { + head->SetName( va( "%s_oldhead", name.c_str() ) ); + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } else if( head ) { + // the current head is OK + return; + } + } + + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin + headOffset ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } + + if ( head ) { + int i; + // set the damage joint to be part of the head damage group + for( i = 0; i < damageGroups.Num(); i++ ) { + if ( damageGroups[ i ] == "head" ) { + head->SetDamageJoint ( static_cast( i ) ); + break; + } + } + + head->InitCopyJoints ( ); + head->SetInstance( instance ); + } +} + +/* +================ +idActor::Restart +================ +*/ +void idActor::Restart( void ) { + assert( !head.GetEntity() ); + // MP sets up heads from UpdateModelSetup() + if( !gameLocal.isMultiplayer ) { + SetupHead(); + } + FinishSetup(); +} + +/* +================ +idActor::Save + +archive object for savegame file +================ +*/ +void idActor::Save( idSaveGame *savefile ) const { + idActor *ent; + int i; + + savefile->WriteInt( team ); + + // cnicholson: This line was already commented out, so we aint changing it. + // No need to write/read teamNode + // idLinkList teamNode; + + savefile->WriteInt( rank ); + savefile->WriteMat3( viewAxis ); + +// twhitaker: this confuses me... should we be writing out enemyList.Num() or enemyList.Next()->enemyNode->Num(). I'm not sure what these variables represent. +// cnicholson: bdube said to do it how id does it, so here goes: + savefile->WriteInt( enemyList.Num() ); + for ( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + savefile->WriteObject( ent ); + } + + savefile->WriteInt( lightningNextTime );// cnicholson: Added unwritten var + savefile->WriteInt( lightningEffects ); // cnicholson: Added unwritten var + + savefile->WriteFloat( fovDot ); + savefile->WriteFloat( fovCloseDot ); + savefile->WriteFloat( fovCloseRange ); + savefile->WriteVec3( eyeOffset ); + savefile->WriteVec3( chestOffset ); + savefile->WriteVec3( modelOffset ); + savefile->WriteAngles( deltaViewAngles ); + + savefile->WriteInt( pain_debounce_time ); + savefile->WriteInt( pain_delay ); + + savefile->WriteInt( damageGroups.Num() ); + for( i = 0; i < damageGroups.Num(); i++ ) { + savefile->WriteString( damageGroups[ i ] ); + } + + savefile->WriteInt( damageScale.Num() ); + for( i = 0; i < damageScale.Num(); i++ ) { + savefile->WriteFloat( damageScale[ i ] ); + } +//MCG + savefile->WriteBool( inDamageEvent ); + + savefile->WriteBool( use_combat_bbox ); + + savefile->WriteJoint( leftEyeJoint ); + savefile->WriteJoint( rightEyeJoint ); + savefile->WriteJoint( soundJoint ); + savefile->WriteJoint( eyeOffsetJoint ); + savefile->WriteJoint( chestOffsetJoint ); + savefile->WriteJoint( neckJoint ); + savefile->WriteJoint( headJoint ); + + walkIK.Save( savefile ); + + savefile->WriteString( animPrefix ); + savefile->WriteString( painType ); + savefile->WriteString( painAnim ); + + savefile->WriteInt( blink_anim ); + savefile->WriteInt( blink_time ); + savefile->WriteInt( blink_min ); + savefile->WriteInt( blink_max ); + + headAnim.Save( savefile ); + torsoAnim.Save( savefile ); + legsAnim.Save( savefile ); + + stateThread.Save( savefile ); + + // idEntityPtr head; + head.Save( savefile ); // cnicholson: Added unwritten var + + savefile->WriteBool( disablePain ); + savefile->WriteBool( allowEyeFocus ); + savefile->WriteBool( finalBoss ); + + savefile->WriteInt( painTime ); + + savefile->WriteInt( attachments.Num() ); + for ( i = 0; i < attachments.Num(); i++ ) { + attachments[i].ent.Save( savefile ); + savefile->WriteInt( attachments[i].channel ); + } + + vehicleController.Save ( savefile ); + + // These aren't saved in the same order as they're declared, due to the dependency (I didn't see the need to change the order of declaration) + savefile->WriteInt ( flashlightHandle ); + savefile->WriteJoint ( flashlightJoint ); + savefile->WriteVec3 ( flashlightOffset ); + savefile->WriteRenderLight ( flashlight ); + + savefile->WriteInt( deathPushTime ); + savefile->WriteVec3( deathPushForce ); + savefile->WriteJoint( deathPushJoint ); +} + +/* +================ +idActor::Restore + +unarchives object from save game file +================ +*/ +void idActor::Restore( idRestoreGame *savefile ) { + int i, num; + idActor *ent; + + savefile->ReadInt( team ); + +// cnicholson: This line was already commented out, so we aint changing it. +// No need to write/read teamNode + // idLinkList teamNode; + + savefile->ReadInt( rank ); + savefile->ReadMat3( viewAxis ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->enemyNode.AddToEnd( enemyList ); + } + } + + savefile->ReadInt( lightningEffects ); + savefile->ReadInt( lightningNextTime ); + + savefile->ReadFloat( fovDot ); + savefile->ReadFloat( fovCloseDot ); + savefile->ReadFloat( fovCloseRange ); + savefile->ReadVec3( eyeOffset ); + savefile->ReadVec3( chestOffset ); + savefile->ReadVec3( modelOffset ); + savefile->ReadAngles( deltaViewAngles ); + + savefile->ReadInt( pain_debounce_time ); + savefile->ReadInt( pain_delay ); + + savefile->ReadInt( num ); + damageGroups.SetGranularity( 1 ); + damageGroups.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadString( damageGroups[ i ] ); + } + + savefile->ReadInt( num ); + damageScale.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadFloat( damageScale[ i ] ); + } +//MCG + savefile->ReadBool( inDamageEvent ); + + savefile->ReadBool( use_combat_bbox ); + + savefile->ReadJoint( leftEyeJoint ); + savefile->ReadJoint( rightEyeJoint ); + savefile->ReadJoint( soundJoint ); + savefile->ReadJoint( eyeOffsetJoint ); + savefile->ReadJoint( chestOffsetJoint ); + savefile->ReadJoint( neckJoint ); + savefile->ReadJoint( headJoint ); + + walkIK.Restore( savefile ); + + savefile->ReadString( animPrefix ); + savefile->ReadString( painType ); + savefile->ReadString( painAnim ); + + savefile->ReadInt( blink_anim ); + savefile->ReadInt( blink_time ); + savefile->ReadInt( blink_min ); + savefile->ReadInt( blink_max ); + + headAnim.Restore( savefile ); + torsoAnim.Restore( savefile ); + legsAnim.Restore( savefile ); + + stateThread.Restore( savefile, this ); + +// cnicholson: Restore unread var + // idEntityPtr head; + head.Restore(savefile); + + savefile->ReadBool( disablePain ); + savefile->ReadBool( allowEyeFocus ); + savefile->ReadBool( finalBoss ); + + savefile->ReadInt( painTime ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idAttachInfo &attach = attachments.Alloc(); + attach.ent.Restore( savefile ); + savefile->ReadInt( attach.channel ); + } + +// RAVEN BEGIN +// bdube: added + vehicleController.Restore ( savefile ); + + savefile->ReadInt ( flashlightHandle ); + savefile->ReadJoint ( flashlightJoint ); + savefile->ReadVec3 ( flashlightOffset ); + savefile->ReadRenderLight ( flashlight ); + if ( flashlightHandle != -1 ) { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + + savefile->ReadInt( deathPushTime ); + savefile->ReadVec3( deathPushForce ); + savefile->ReadJoint( deathPushJoint ); + +// mekberg: update this + FlashlightUpdate( ); +// RAVEN END +} + +/* +================ +idActor::Hide +================ +*/ +void idActor::Hide( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Hide(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Off(); + } + } + } + UnlinkCombat(); +} + +/* +================ +idActor::Show +================ +*/ +void idActor::Show( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Show(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->On(); + } + } + } + LinkCombat(); +} + +/* +============== +idActor::GetDefaultSurfaceType +============== +*/ +int idActor::GetDefaultSurfaceType( void ) const { + return SURFTYPE_FLESH; +} + +/* +================ +idActor::ProjectOverlay +================ +*/ +void idActor::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + idEntity *ent; + idEntity *next; + + idEntity::ProjectOverlay( origin, dir, size, material ); + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + if ( ent->fl.takedamage && ent->spawnArgs.GetBool( "bleed" ) ) { + ent->ProjectOverlay( origin, dir, size, material ); + } + } + } +} + +/* +================ +idActor::LoadAF +================ +*/ +bool idActor::LoadAF( const char* keyname, bool purgeAF /* = false */ ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) || !fileName.Length() ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName, purgeAF ); +} + +/* +===================== +idActor::SetupBody +===================== +*/ +void idActor::SetupBody( void ) { + const char* jointname; + idAnimator* headAnimator; + float height; + + animator.ClearAllAnims( gameLocal.time, 0 ); + animator.ClearAllJoints(); + + // Cache the head entity pointer and determine which animator to use + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator ( ); + } else { + headAnimator = GetAnimator( ); + } + + // Get left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_leftEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_leftEye" ); + } + leftEyeJoint = headAnimator->GetJointHandle( jointname ); + + // Get right eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_rightEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_rightEye" ); + } + rightEyeJoint = headAnimator->GetJointHandle( jointname ); + + // If head height is specified, just use that + if ( spawnArgs.GetFloat ( "eye_height", "0", height ) ) { + SetEyeHeight ( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_eyeOffset", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_eyeOffset" ); + } + // Get the eye offset joint + eyeOffsetJoint = headAnimator->GetJointHandle ( jointname ); + } + + // If eye height is specified, just use that + if ( spawnArgs.GetFloat ( "chest_height", "0", height ) ) { + SetChestHeight ( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + spawnArgs.GetString ( "joint_chestOffset", "", &jointname ); + // Get the chest offset joint + chestOffsetJoint = animator.GetJointHandle ( jointname ); + } + + // Get the neck joint + spawnArgs.GetString ( "joint_look_neck", "", &jointname ); + neckJoint = animator.GetJointHandle ( jointname ); + + // Get the head joint + spawnArgs.GetString ( "joint_look_head", "", &jointname ); + headJoint = animator.GetJointHandle ( jointname ); + + headAnim.Init ( this, &animator, ANIMCHANNEL_HEAD ); + torsoAnim.Init( this, &animator, ANIMCHANNEL_TORSO ); + legsAnim.Init( this, &animator, ANIMCHANNEL_LEGS ); +} + +/* +===================== +idActor::CheckBlink +===================== +*/ +void idActor::CheckBlink( void ) { + // check if it's time to blink + if ( !blink_anim || ( health <= 0 ) || ( blink_time > gameLocal.time ) || !fl.allowAutoBlink ) { + return; + } + + idAnimator *animator = head.GetEntity() ? head->GetAnimator() : &this->animator; + animator->PlayAnim( ANIMCHANNEL_EYELIDS, blink_anim, gameLocal.time, 1 ); + + // set the next blink time + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +================ +idActor::GetPhysicsToVisualTransform +================ +*/ +bool idActor::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } +// RAVEN BEGIN +// bdube: position player in seat (nmckenzie: copy and paste from the player version of this call) + if ( vehicleController.IsDriving ( ) ) { + vehicleController.GetDriverPosition ( origin, axis ); + return true; + } +// RAVEN END + + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +idActor::GetPhysicsToSoundTransform +================ +*/ +bool idActor::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + origin += modelOffset; + axis = viewAxis; + } else { + origin = GetPhysics()->GetGravityNormal() * -eyeOffset.z; + axis.Identity(); + } + return true; +} + +/*********************************************************************** + + script state management + +***********************************************************************/ + +/* +================ +idActor::ShutdownThreads +================ +*/ +void idActor::ShutdownThreads( void ) { + headAnim.Shutdown(); + torsoAnim.Shutdown(); + legsAnim.Shutdown(); +} + +/* +===================== +idActor::OnStateThreadClear +===================== +*/ +void idActor::OnStateThreadClear( const char *statename, int flags ) { +} + +/* +===================== +idActor::SetState +===================== +*/ +void idActor::SetState( const char *statename, int flags ) { + OnStateThreadClear( statename, flags ); + stateThread.SetState ( statename, 0, 0, flags ); +} + +/* +===================== +idActor::PostState +===================== +*/ +void idActor::PostState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::InterruptState +===================== +*/ +void idActor::InterruptState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.InterruptState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::UpdateState +===================== +*/ +void idActor::UpdateState ( void ) { + stateThread.Execute ( ); +} + +/*********************************************************************** + + vision + +***********************************************************************/ + +/* +===================== +idActor::setFov +===================== +*/ +void idActor::SetFOV( float fov, float fovClose ) { + fovDot = idMath::Cos( DEG2RAD( fov * 0.5f ) ); + fovCloseDot = idMath::Cos( DEG2RAD( fovClose * 0.5f ) ); +} + +/* +===================== +idActor::SetEyeHeight +===================== +*/ +void idActor::SetEyeHeight( float height ) { + eyeOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::EyeHeight +===================== +*/ +float idActor::EyeHeight( void ) const { + return eyeOffset.z; +} + +/* +===================== +idActor::SetChestHeight +===================== +*/ +void idActor::SetChestHeight( float height ) { + chestOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::GetEyePosition +===================== +*/ +idVec3 idActor::GetEyePosition( void ) const { + return GetPhysics()->GetOrigin() + eyeOffset * viewAxis; +} + +/* +===================== +idActor::GetChestPosition +===================== +*/ +idVec3 idActor::GetChestPosition ( void ) const { + return GetPhysics()->GetOrigin() + chestOffset * viewAxis; +} + +// RAVEN BEGIN +// bdube: flashlights + +/* +===================== +idActor::GetGroundEntity +===================== +*/ +idEntity* idActor::GetGroundEntity( void ) const { + return static_cast(GetPhysics())->GetGroundEntity(); +} + +/* +===================== +idActor::Present +===================== +*/ +void idActor::Present( void ) { + idAFEntity_Gibbable::Present(); + + FlashlightUpdate(); +} + +/* +===================== +idActor::Event_Teleport +===================== +*/ +void idActor::Event_Teleport( idVec3 &newPos, idVec3 &newAngles ) { + Teleport ( newPos, newAngles.ToAngles(), NULL ); +} + +/* +===================== +idActor::Event_EnterVehicle +===================== +*/ +void idActor::Event_EnterVehicle ( idEntity* vehicle ) { + if ( IsInVehicle ( ) ) { + return; + } + EnterVehicle ( vehicle ); +} + +/* +===================== +idActor::Event_ExitVehicle +===================== +*/ +void idActor::Event_ExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + ExitVehicle ( force ); +} + +/* +===================== +idActor::Event_PreExitVehicle +===================== +*/ +void idActor::Event_PreExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + + // call the script func regardless of the fact that we may not be getting out just yet. + // this allows things in the script to happen before ejection actually occurs (such as screen fades). + vehicleController.GetVehicle()->OnExit(); + + // this is done because having an exit delay when the player is dead was causing bustedness if you died in the walker + // specifically the restart menu would not appear if you were still in the walker + if ( health > 0 ) { + PostEventMS( &AI_PostExitVehicle, vehicleController.GetVehicle()->spawnArgs.GetInt( "exit_vehicle_delay" ), force ); + } + else { + ExitVehicle(true); + } +} + +/* +===================== +idActor::Event_Flashlight +===================== +*/ +void idActor::Event_Flashlight( bool on ) { + if ( on ) { + FlashlightUpdate(true); + } else { + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( flashlightHandle ); + flashlightHandle = -1; + } + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + StopEffect( "fx_flashlight", true ); + } + } +} + +/* +===================== +idActor::FlashlightUpdate +===================== +*/ +void idActor::FlashlightUpdate ( bool forceOn ) { + + // Dont do anything if flashlight is off and its not being forced on + if ( !forceOn && flashlightHandle == -1 ) { + return; + } + + if ( !flashlight.lightRadius[0] || flashlightJoint == INVALID_JOINT ) { + return; + } + + if ( forceOn && flashlightHandle == -1 ) { + //first time turning it on + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + ShowSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + PlayEffect( "fx_flashlight", flashlightJoint, true ); + } + } + + // the flash has an explicit joint for locating it + GetJointWorldTransform ( flashlightJoint, gameLocal.time, flashlight.origin, flashlight.axis ); + flashlight.origin += flashlightOffset * flashlight.axis; + + if ( flashlightHandle != -1 ) { + gameRenderWorld->UpdateLightDef( flashlightHandle, &flashlight ); + } else { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + +} + +// RAVEN END + +/* +===================== +idActor::GetViewPos +===================== +*/ +void idActor::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + origin = GetEyePosition(); + axis = viewAxis; +} + +/* +===================== +idActor::CheckFOV +===================== +*/ +bool idActor::CheckFOV( const idVec3 &pos, float ang ) const { + float testAng = ( ang != -1.0f ) ? idMath::Cos( DEG2RAD( ang * 0.5f ) ) : fovDot; + if ( testAng == 1.0f ) { + return true; + } + + if(!GetPhysics()) { + return false; + } + + float dot; + float dist; + idVec3 delta; + + delta = pos - GetEyePosition(); + dist = delta.LengthFast(); + + //NOTE!!! + //This logic is BACKWARDS, but it's too late in the project + //for me to feel comfortable fixing this. It SHOULD be: + //if ( ang == -1.0f ) + // - MCG + if ( ang != -1.0f ) + {//not overriding dot test value + if (distGetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + dot = viewAxis[ 0 ] * delta; + + return ( dot >= testAng ); +} + +/* +===================== +idActor::HasFOV +===================== +*/ + +bool idActor::HasFOV( idEntity *ent ) +{ + // Fixme: Make this do something, anything. + return true; +} +// RAVEN END + +/* +===================== +idActor::CanSee +===================== +*/ +bool idActor::CanSee( const idEntity *ent, bool useFov ) const { + return CanSeeFrom ( GetEyePosition ( ), ent, useFov ); +} + +/* +===================== +idActor::CanSeeFrom +===================== +*/ +bool idActor::CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFov ) const { + idVec3 toPos; + + if ( !ent || ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ((idActor*)ent)->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetAbsBounds().GetCenter ( ); + } + + return CanSeeFrom ( from, toPos, useFov ); +} + +bool idActor::CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFov ) const { + trace_t tr; + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + return true; + } + gameLocal.TracePoint( this, tr, from, toPos, MASK_OPAQUE, this ); + if ( tr.fraction >= 1.0f ) { // || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + + return false; +} + +/* +===================== +idActor::GetRenderView +===================== +*/ +renderView_t *idActor::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->viewaxis = viewAxis; + rv->vieworg = GetEyePosition(); + return rv; +} + +/*********************************************************************** + + Model/Ragdoll + +***********************************************************************/ + +/* +================ +idActor::SetCombatModel +================ +*/ +void idActor::SetCombatModel( void ) { + idAFAttachment *headEnt; + +// RAVEN BEGIN +// bdube: set the combat model reguardless + if ( 1 ) { // !use_combat_bbox ) { +// RAVEN END + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->SetCombatModel(); + } + } +} + +/* +================ +idActor::GetCombatModel +================ +*/ +idClipModel *idActor::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idActor::LinkCombat +================ +*/ +void idActor::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden || use_combat_bbox ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idActor::UnlinkCombat +================ +*/ +void idActor::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idActor::StartRagdoll +================ +*/ +bool idActor::StartRagdoll( void ) { + float slomoStart, slomoEnd; + float jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd; + float contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd; + + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // Raise the origin up 5 units to help ensure the ragdoll doesnt start in the ground + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -5.0f ); + UpdateModelTransform(); + + // disable the monster bounding box + GetPhysics()->DisableClip(); + + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + slomoStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoStart", "-1.6" ); + slomoEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoEnd", "0.8" ); + + // do the first part of the death in slow motion + af.GetPhysics()->SetTimeScaleRamp( slomoStart, slomoEnd ); + + jointFrictionDent = spawnArgs.GetFloat( "ragdoll_jointFrictionDent", "0.1" ); + jointFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionStart", "0.2" ); + jointFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionEnd", "1.2" ); + + // set joint friction dent + af.GetPhysics()->SetJointFrictionDent( jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd ); + + contactFrictionDent = spawnArgs.GetFloat( "ragdoll_contactFrictionDent", "0.1" ); + contactFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionStart", "1.0" ); + contactFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionEnd", "2.0" ); + + // set contact friction dent + af.GetPhysics()->SetContactFrictionDent( contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd ); + + // drop any items the actor is holding + idList list; + idMoveableItem::DropItems( this, "death", &list ); + for ( int i = 0; i < list.Num(); i++ ) { + if ( list[i] && list[i]->GetPhysics() ) + { + idVec3 velocity; + float pitchDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float yawDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float rollDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + velocity.Set( pitchDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + yawDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + (gameLocal.random.RandomFloat() * 300.0f) + 100.0f ); + list[i]->GetPhysics()->SetAngularVelocity( idVec3( pitchDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + yawDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + rollDir*((gameLocal.random.RandomFloat() * 10.0f) + 3.0f))); + if ( gibbed ) { + //only throw them if we end up gibbed? + list[i]->GetPhysics()->SetLinearVelocity( velocity ); + } + } + } + + // drop any articulated figures the actor is holding + idAFEntity_Base::DropAFs( this, "death", NULL ); + + RemoveAttachments(); + +// RAVEN BEGIN +// bdube: evaluate one ragdoll frame + RunPhysics(); +// RAVEN END + + return true; +} + +/* +================ +idActor::StopRagdoll +================ +*/ +void idActor::StopRagdoll( void ) { + if ( af.IsActive() ) { + af.Stop(); + } +} + +/* +================ +idActor::UpdateAnimationControllers +================ +*/ +bool idActor::UpdateAnimationControllers( void ) { + + if ( af.IsActive() ) { + return idAFEntity_Gibbable::UpdateAnimationControllers(); + } else { + animator.ClearAFPose(); + } + + if ( walkIK.IsInitialized() ) { + walkIK.Evaluate(); + return true; + } + + idMat3 axis; + idAnimatedEntity* headEnt = head.GetEntity( ); + if ( !headEnt ) { + headEnt = this; + } + + // Dynamically update the eye offset if a joint was specified + if ( eyeOffsetJoint != INVALID_JOINT ) { + headEnt->GetJointWorldTransform( eyeOffsetJoint, gameLocal.time, eyeOffset, axis ); + eyeOffset = (eyeOffset - GetPhysics()->GetOrigin()) * viewAxis.Transpose( ); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorRed, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + // Dynamically update the chest offset if a joint was specified + if ( chestOffsetJoint != INVALID_JOINT ) { + GetJointWorldTransform( chestOffsetJoint, gameLocal.time, chestOffset, axis ); + chestOffset = ( chestOffset - GetPhysics()->GetOrigin() ) * viewAxis.Transpose(); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorPink, GetChestPosition(), GetChestPosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + return false; +} + +/* +================ +idActor::RemoveAttachments +================ +*/ +void idActor::RemoveAttachments( void ) { + int i; + idEntity *ent; + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && ent->spawnArgs.GetBool( "remove" ) ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } +} + +/* +================ +idActor::Attach +================ +*/ +void idActor::Attach( idEntity *ent ) { + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + idStr jointName; + idAttachInfo &attach = attachments.Alloc(); + idAngles angleOffset; + idVec3 originOffset; + + jointName = ent->spawnArgs.GetString( "joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for attaching '%s' on '%s'", jointName.c_str(), ent->GetClassname(), name.c_str() ); + } + + angleOffset = ent->spawnArgs.GetAngles( "angles" ); + originOffset = ent->spawnArgs.GetVector( "origin" ); + + attach.channel = animator.GetChannelForJoint( joint ); + GetJointWorldTransform( joint, gameLocal.time, origin, axis ); + attach.ent = ent; + + ent->SetOrigin( origin + originOffset * renderEntity.axis ); + idMat3 rotate = angleOffset.ToMat3(); + idMat3 newAxis = rotate * axis; + ent->SetAxis( newAxis ); + ent->BindToJoint( this, joint, true ); + ent->cinematic = cinematic; +} + +idEntity* idActor::FindAttachment( const char* attachmentName ) +{ + idEntity *ent = NULL; + const char* fullName = va("idAFAttachment_%s",attachmentName); + // find the specified attachment + for( int i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && !ent->name.CmpPrefix(fullName) ) { + return ent; + } + } + return NULL; +} + +void idActor::HideAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Hide(); + } +} + +void idActor::ShowAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Show(); + } +} + +/* +================ +idActor::Teleport +================ +*/ +void idActor::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + viewAxis = angles.ToMat3(); + + UpdateVisuals(); + + if ( !IsHidden() ) { + // kill anything at the new position + gameLocal.KillBox( this ); + } +} + +/* +================ +idActor::GetDeltaViewAngles +================ +*/ +const idAngles &idActor::GetDeltaViewAngles( void ) const { + return deltaViewAngles; +} + +/* +================ +idActor::SetDeltaViewAngles +================ +*/ +void idActor::SetDeltaViewAngles( const idAngles &delta ) { + deltaViewAngles = delta; +} + +/* +================ +idActor::HasEnemies +================ +*/ +bool idActor::HasEnemies( void ) const { + idActor *ent; + + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden ) { + return true; + } + } + + return false; +} + +/* +================ +idActor::ClosestEnemyToPoint +================ +*/ +idActor *idActor::ClosestEnemyToPoint( const idVec3 &pos, float maxRange, bool returnFirst, bool checkPVS ) { + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + pvsHandle_t pvs; + + //just to supress the compiler warning + pvs.i = 0; + + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + } + + bestDistSquared = maxRange?(maxRange*maxRange):idMath::INFINITY; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - pos; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + if ( checkPVS ) { + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + } + bestEnt = ent; + bestDistSquared = distSquared; + if ( returnFirst ) { + break; + } + } + } + + if ( checkPVS ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + return bestEnt; +} + +/* +================ +idActor::EnemyWithMostHealth +================ +*/ +idActor *idActor::EnemyWithMostHealth() { + idActor *ent; + idActor *bestEnt; + + int most = -9999; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden && ( ent->health > most ) ) { + bestEnt = ent; + most = ent->health; + } + } + return bestEnt; +} + +/* +================ +idActor::OnLadder +================ +*/ +bool idActor::OnLadder( void ) const { + return false; +} + +/* +============== +idActor::GetAASLocation +============== +*/ +void idActor::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + idVec3 size; + idBounds bounds; + + GetFloorPos( 64.0f, pos ); + if ( !aas ) { + areaNum = 0; + return; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aas->PushPointIntoAreaNum( areaNum, pos ); + } +} + +/*********************************************************************** + + animation state + +***********************************************************************/ + +/* +===================== +idActor::StopAnimState +===================== +*/ +void idActor::StopAnimState ( int channel ) { + GetAnimState ( channel ).Shutdown ( ); +} + +/* +===================== +idActor::PostAnimState +===================== +*/ +void idActor::PostAnimState ( int channel, const char* statename, int blendFrames, int delay, int flags ) { + GetAnimState ( channel ).PostState ( statename, blendFrames, delay, flags ); +} + +/* +===================== +idActor::SetAnimState +===================== +*/ +void idActor::SetAnimState( int channel, const char *statename, int blendFrames, int flags ) { + switch ( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.GetStateThread().Clear ( ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.GetStateThread().Clear ( ); + legsAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.GetStateThread().Clear ( ); + torsoAnim.Enable( blendFrames ); + break; + } + + OnStateChange ( channel ); + + PostAnimState ( channel, statename, blendFrames, flags ); +} + +/* +===================== +idActor::OnStateChange +===================== +*/ +void idActor::OnStateChange ( int channel ) { + allowEyeFocus = true; + + // Only clear eye focus on head channel change + if ( channel == ANIMCHANNEL_HEAD ) { + return; + } + + disablePain = false; +} + +/* +===================== +idActor::OnFriendlyFire +===================== +*/ +void idActor::OnFriendlyFire ( idActor* attacker ) { +} + +/* +===================== +idActor::UpdateAnimState +===================== +*/ +void idActor::UpdateAnimState( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ANIM); +// RAVEN END + headAnim.UpdateState(); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); +} + +/* +===================== +idActor::GetAnim +===================== +*/ +int idActor::GetAnim( int channel, const char *animname, bool forcePrefix ) { + int anim; + const char *temp; + idAnimator *animatorPtr; + + if ( channel == ANIMCHANNEL_HEAD ) { + if ( !head.GetEntity() ) { + return 0; + } + animatorPtr = head.GetEntity()->GetAnimator(); + } else { + animatorPtr = &animator; + } + + // Allow for anim substitution + animname = spawnArgs.GetString ( va("anim %s", animname ), animname ); + + if ( animPrefix.Length() ) { + temp = va( "%s_%s", animPrefix.c_str(), animname ); + anim = animatorPtr->GetAnim( temp ); + if ( anim ) { + return anim; + } else if ( forcePrefix ) { + return NULL; + } + } + + anim = animatorPtr->GetAnim( animname ); + + return anim; +} + +/* +=============== +idActor::SyncAnimChannels +=============== +*/ +void idActor::SyncAnimChannels( int channel, int syncToChannel, int blendFrames ) { + idAnimator *headAnimator; + idAFAttachment *headEnt; + int anim; + idAnimBlend *syncAnim; + int starttime; + int blendTime; + int cycle; + + blendTime = FRAME2MS( blendFrames ); + if ( channel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = animator.CurrentAnim( syncToChannel ); + if ( syncAnim ) { + anim = headAnimator->GetAnim( syncAnim->AnimFullName() ); + if ( !anim ) { + anim = headAnimator->GetAnim( syncAnim->AnimName() ); + } + if ( anim ) { + cycle = animator.CurrentAnim( syncToChannel )->GetCycleCount(); + starttime = animator.CurrentAnim( syncToChannel )->GetStartTime(); + headAnimator->PlayAnim( ANIMCHANNEL_LEGS, anim, gameLocal.time, blendTime ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetCycleCount( cycle ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetStartTime( starttime ); + } else { + headEnt->PlayIdleAnim( ANIMCHANNEL_LEGS, blendTime ); + } + } + } + } else if ( syncToChannel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS ); + if ( syncAnim ) { + anim = GetAnim( channel, syncAnim->AnimFullName() ); + if ( !anim ) { + anim = GetAnim( channel, syncAnim->AnimName() ); + } + if ( anim ) { + cycle = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetCycleCount(); + starttime = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetStartTime(); + animator.PlayAnim( channel, anim, gameLocal.time, blendTime ); + animator.CurrentAnim( channel )->SetCycleCount( cycle ); + animator.CurrentAnim( channel )->SetStartTime( starttime ); + } + } + } + } else { + animator.SyncAnimChannels( channel, syncToChannel, gameLocal.time, blendTime ); + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idActor::Gib +============ +*/ +void idActor::Gib( const idVec3 &dir, const char *damageDefName ) { + // for multiplayer we use client-side models + if ( gameLocal.isMultiplayer ) { + return; + } + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + StopSound( SND_CHANNEL_VOICE, false ); + + gameLocal.PlayEffect ( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +} + +void idActor::CheckDeathObjectives( void ) +{ + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player || !player->GetObjectiveHud() ) { + return; + } + + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_failed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_failed" ) ) ); + + player->FailObjective( spawnArgs.GetString( "objectivetitle_failed" ) ); + } + + if ( spawnArgs.GetString( "objectivetitle_completed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_completed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_completed" ) ) ); + + player->CompleteObjective( spawnArgs.GetString( "objectivetitle_completed" ) ); + } +} +/* +============ +idActor::Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +Bleeding wounds and surface overlays are applied in the collision code that +calls Damage() +============ +*/ +void idActor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ) * damageScale; + damage = GetDamageForLocation( damage, location ); + + // friendly fire damage + bool noDmgFeedback = false; + if ( attacker->IsType ( idActor::Type ) && static_cast(attacker)->team == team ) { + + OnFriendlyFire ( static_cast(attacker) ); + + // jshepard: + // if the player deals friendly fire damage it is reduced to 0. If the damage is splash damage, + // then the victim should use a pain anim. + if( static_cast( attacker ) == gameLocal.GetLocalPlayer() ) { + + //play pain (maybe one day a special anim?) for damages that have the cower keyword + if ( damageDef->GetBool( "cower" )) { + Pain( inflictor, attacker, damage, dir, location ); + } + + //reduce the damage + damage = 0; + noDmgFeedback = true; + } + + // reduce friendly fire damage by the teamscale + damage = floor( damage * damageDef->GetFloat ( "teamScale", "0.5" ) ); + + + } + + if ( !IsType( idPlayer::GetClassType() ) && attacker->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)attacker)->aifl.killerGuard ) { + //Hard-coded to do lots of damage + damage = 100; + } + } + + if ( !noDmgFeedback ) { + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + } + +// RAVEN BEGIN +// jnewquist: FIXME - Was this removed from Xenon intentionally? +#ifdef _XENON + // singleplayer stat reporting. + if(!gameLocal.isMultiplayer) { + int methodOfDeath = -1; + // jnewquist: Use accessor for static class type + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + // RAVEN END + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::GetClassType() ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + if( methodOfDeath != -1 && attacker && attacker->IsType( idActor::Type ) ) { +// jnewquist: Fix Xenon compile warning + statManager->WeaponHit( static_cast (attacker) , this, methodOfDeath, !!damage ); + } + } +#endif +// RAVEN END + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + if ( damage > 0 ) { + int oldHealth = health; + AdjustHealthByDamage ( damage ); + if ( health <= 0 ) { + + //allow for quick burning + if (damageDef->GetFloat( "quickburn", "0" ) && !spawnArgs.GetFloat("no_quickburn", "0")) { + fl.quickBurn = true; + } + + if ( health < -999 ) { + health = -999; + } + //annoying hack for StartRagdoll + bool saveGibbed = gibbed; + bool canDMG_Gib = (spawnArgs.GetBool( "gib" ) | spawnArgs.GetBool( "DMG_gib" )); + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + gibbed = true; + } + } + Killed( inflictor, attacker, damage, dir, location ); + gibbed = saveGibbed; + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + Gib( dir, damageDefName ); + } + } + + if ( oldHealth > 0 && !gibbed && !fl.quickBurn) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::GetClassType() ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, damageDef, pushScale ); + } + } else { + painType = damageDef->GetString ( "pain" ); + Pain( inflictor, attacker, damage, dir, location ); + } + } else { + // don't accumulate knockback + /* + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calli/ng af.Rest() + BecomeActive( TH_PHYSICS ); + } + */ + } +} + +/* +===================== +idActor::InitDeathPush +===================== +*/ +void idActor::InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale ) { + idVec2 forceMin; + idVec2 forceMax; + + if( !af.IsActive() ) { + return; + } + + if ( deathPushTime > gameLocal.time ) { + return; + } + + if ( !damageDict->GetInt ( "deathPush", "0", deathPushTime ) || deathPushTime <= 0 ) { + return; + } + + damageDict->GetVec2( "deathPushMin", "", forceMin ); + damageDict->GetVec2( "deathPushMax", "", forceMax ); + +/* + forceMin *= (pushScale * GetPhysics()->GetMass()); + forceMax *= (pushScale * GetPhysics()->GetMass()); +*/ + forceMin *= (pushScale); + forceMax *= (pushScale); + + deathPushForce = dir; + deathPushForce.Normalize ( ); + deathPushForce = rvRandom::flrand ( forceMin.x, forceMax.x ) * deathPushForce + + -rvRandom::flrand ( forceMin.y, forceMax.y ) * GetPhysics()->GetGravityNormal ( ); + + deathPushTime += gameLocal.time; + deathPushJoint = (jointHandle_t) location; +} + +/* +===================== +idActor::DeathPush +===================== +*/ +void idActor::DeathPush ( void ) { + if ( deathPushTime <= gameLocal.time ) { + return; + } + + idVec3 center; + center = GetPhysics()->GetAbsBounds ( ).GetCenter(); + + GetPhysics()->ApplyImpulse ( 0, center, -0.5f * GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()) * GetPhysics()->GetGravity ( ) ); + + if ( deathPushJoint != INVALID_JOINT ) { + idVec3 origin; + idMat3 axis; + GetJointWorldTransform ( deathPushJoint, gameLocal.time, origin, axis ); + GetPhysics()->ApplyImpulse ( 0, origin, deathPushForce ); + } else { + GetPhysics()->ApplyImpulse ( 0, center, deathPushForce ); + } +} + +/* +===================== +idActor::SkipImpulse +===================== +*/ +bool idActor::SkipImpulse( idEntity* ent, int id ) { + return idAFEntity_Gibbable::SkipImpulse( ent, id ) || health <= 0 || gibbed || ent->IsType( idActor::GetClassType() ) || ent->IsType( idProjectile::GetClassType() ); +} + +/* +===================== +idActor::AddDamageEffect +===================== +*/ +void idActor::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !gameLocal.isMultiplayer && inflictor && inflictor->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast(inflictor)->team == team ) { + return; + } + } + idAFEntity_Gibbable::AddDamageEffect( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +idActor::ClearPain +===================== +*/ +void idActor::ClearPain( void ) { + pain_debounce_time = 0; +} + +/* +===================== +idActor::Pain +===================== +*/ +bool idActor::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + if ( gameLocal.time < pain_debounce_time ) { + return false; + } + + // No pain if being hit by a friendly target + // jshepard: friendly targets can now cause pain +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return false; + } + } +*/ + + // don't play pain sounds more than necessary + pain_debounce_time = gameLocal.time + pain_delay; + + float f; +// RAVEN BEGIN +// mekberg: fixed divide by zero + float spawnHealth = spawnArgs.GetFloat ( "health", "1" ); + if (spawnHealth<1.0f) { + spawnHealth = 1.0f; // more devide by zero nonsense + } + f = (float)damage / spawnHealth; +// RAVEN END + if( gameLocal.isMultiplayer && IsType( idPlayer::GetClassType() ) && (health < 0.25f * ((idPlayer*)this)->inventory.maxHealth) ) { + StartSound( "snd_pain_low_health", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + if ( f > 0.75f ) { + StartSound( "snd_pain_huge", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.5f ) { + StartSound( "snd_pain_large", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.25f ) { + StartSound( "snd_pain_medium", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + StartSound( "snd_pain_small", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( disablePain || ( gameLocal.time < painTime ) ) { + // don't play a pain anim + return false; + } + + // set the pain anim + idStr damageGroup = GetDamageGroup( location ); + + painAnim.Clear ( ); + + // If we have both a damage group and a pain type then check that combination first + if ( damageGroup.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s_%s", painType.c_str(), damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the pain type? + if ( !painAnim.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s", painType.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the damage group? + if ( !painAnim.Length ( ) && damageGroup.Length ( ) ) { + painAnim = va ( "pain_%s", damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + if ( !painAnim.Length() ) { + painAnim = "pain"; + } + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s', anim '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + damageGroup.c_str(), painAnim.c_str() ); + } + + return true; +} + +/* +===================== +idActor::SpawnGibs +===================== +*/ +void idActor::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + idAFEntity_Gibbable::SpawnGibs( dir, damageDefName ); + RemoveAttachments(); +} + +/* +===================== +idActor::SetupDamageGroups + +FIXME: only store group names once and store an index for each joint +===================== +*/ +void idActor::SetupDamageGroups( void ) { + int i; + const idKeyValue *arg; + idStr groupname; + idList jointList; + int jointnum; + float scale; + + // create damage zones + damageGroups.SetNum( animator.NumJoints() ); + arg = spawnArgs.MatchPrefix( "damage_zone ", NULL ); + while ( arg ) { + groupname = arg->GetKey(); + groupname.Strip( "damage_zone " ); + animator.GetJointList( arg->GetValue(), jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + damageGroups[ jointnum ] = groupname; + } + jointList.Clear(); + arg = spawnArgs.MatchPrefix( "damage_zone ", arg ); + } + + // initilize the damage zones to normal damage + damageScale.SetNum( animator.NumJoints() ); + for( i = 0; i < damageScale.Num(); i++ ) { + damageScale[ i ] = 1.0f; + } + + // set the percentage on damage zones + arg = spawnArgs.MatchPrefix( "damage_scale ", NULL ); + while ( arg ) { + scale = atof( arg->GetValue() ); + groupname = arg->GetKey(); + groupname.Strip( "damage_scale " ); + for( i = 0; i < damageScale.Num(); i++ ) { + if ( damageGroups[ i ] == groupname ) { + damageScale[ i ] = scale; + } + } + arg = spawnArgs.MatchPrefix( "damage_scale ", arg ); + } +} + +/* +===================== +idActor::GetDamageForLocation +===================== +*/ +int idActor::GetDamageForLocation( int damage, int location ) { + if ( ( location < 0 ) || ( location >= damageScale.Num() ) ) { + return damage; + } + + return (int)ceil( damage * damageScale[ location ] ); +} + +/* +===================== +idActor::GetDamageGroup +===================== +*/ +const char *idActor::GetDamageGroup( int location ) { + if ( ( location < 0 ) || ( location >= damageGroups.Num() ) ) { + return ""; + } + + return damageGroups[ location ]; +} + + +// RAVEN BEGIN +// bdube: added for vehicle +/* +============== +idActor::ExitVehicle +============== +*/ +bool idActor::ExitVehicle ( bool force ) { + idMat3 axis; + idVec3 origin; + + if ( !IsInVehicle ( ) ) { + return false; + } + + if ( vehicleController.GetVehicle()->IsLocked() ) { + if ( force ) { + vehicleController.GetVehicle()->Unlock(); + } else { + return false; + } + } + + if( !vehicleController.FindClearExitPoint(origin, axis) ) { + if ( force ) { + origin = GetPhysics()->GetOrigin() + idVec3( spawnArgs.GetVector( "forced_exit_offset", "-100 0 0" ) ); + axis = GetPhysics()->GetAxis(); + } else { + return false; + } + } + + vehicleController.Eject ( force ); + + GetPhysics()->SetOrigin( origin ); + viewAxis = axis[0].ToMat3(); + GetPhysics()->SetAxis( mat3_identity ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + return true; +} + +/* +===================== +idActor::EnterVehicle +===================== +*/ +bool idActor::EnterVehicle ( idEntity* ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsInVehicle ( ) || !ent->IsType ( rvVehicle::GetClassType() ) ) { +// RAVEN END + return false ; + } + + // Get in the vehicle + if ( !vehicleController.Drive ( static_cast(ent), this ) ) { + return false; + } + + return true; +} + +// RAVEN END + + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +===================== +idActor::FootStep +===================== +*/ +void idActor::FootStep ( void ) { + const char* sound; + const rvDeclMatType* materialType; + + if ( !GetPhysics()->HasGroundContacts() ) { + return; + } + + // start footstep sound based on material type + materialType = GetPhysics()->GetContact( 0 ).materialType; + sound = NULL; + + // Sound based on material type? + if ( materialType ) { + sound = spawnArgs.GetString( va( "snd_footstep_%s", materialType->GetName() ) ); + } + if ( !sound || !*sound ) { + sound = spawnArgs.GetString( "snd_footstep" ); + } + + // If we have a sound then play it + if ( sound && *sound ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +===================== +idActor::Event_EnableEyeFocus +===================== +*/ +void idActor::Event_EnableEyeFocus( void ) { + allowEyeFocus = true; + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableEyeFocus +===================== +*/ +void idActor::Event_DisableEyeFocus( void ) { + allowEyeFocus = false; + + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetAnimator()->Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } else { + animator.Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } +} + +/* +===================== +idActor::Event_EnableBlink +===================== +*/ +void idActor::Event_EnableBlink( void ) { + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableBlink +===================== +*/ +void idActor::Event_DisableBlink( void ) { + blink_time = 0x7FFFFFFF; +} + +/* +=============== +idActor::Event_Footstep +=============== +*/ +void idActor::Event_Footstep( void ) { + FootStep ( ); +} + +/* +===================== +idActor::Event_EnableWalkIK +===================== +*/ +void idActor::Event_EnableWalkIK( void ) { + walkIK.EnableAll(); +} + +/* +===================== +idActor::Event_DisableWalkIK +===================== +*/ +void idActor::Event_DisableWalkIK( void ) { + walkIK.DisableAll(); +} + +/* +===================== +idActor::Event_EnableLegIK +===================== +*/ +void idActor::Event_EnableLegIK( int num ) { + walkIK.EnableLeg( num ); +} + +/* +===================== +idActor::Event_DisableLegIK +===================== +*/ +void idActor::Event_DisableLegIK( int num ) { + walkIK.DisableLeg( num ); +} + +/* +===================== +idActor::Event_PreventPain +===================== +*/ +void idActor::Event_PreventPain( float duration ) { + painTime = gameLocal.time + SEC2MS( duration ); +} + +/* +=============== +idActor::Event_DisablePain +=============== +*/ +void idActor::Event_DisablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = true; +// RAVEN END +} + +/* +=============== +idActor::Event_EnablePain +=============== +*/ +void idActor::Event_EnablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = false; +// RAVEN END +} + +/* +===================== +idActor::Event_SetAnimPrefix +===================== +*/ +void idActor::Event_SetAnimPrefix( const char *prefix ) { + animPrefix = prefix; +} + +/* +=============== +idActor::Event_StopAnim +=============== +*/ +void idActor::Event_StopAnim( int channel, int frames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.StopAnim( frames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_PlayAnim +=============== +*/ +void idActor::Event_PlayAnim( int channel, const char *animname ) { + idThread::ReturnFloat( MS2SEC(PlayAnim(channel, animname, -1)) ); +} + +/* +=============== +idActor::Event_PlayCycle +=============== +*/ +void idActor::Event_PlayCycle( int channel, const char *animname ) { + PlayCycle ( channel, animname, -1 ); + idThread::ReturnInt( true ); +} + +/* +===================== +idAI::DebugFilter +===================== +*/ +bool idActor::DebugFilter ( const idCVar& test ) const { + return ( health>0 && (test.GetBool() || test.GetInteger()>0) && (!ai_debugFilterString.GetString()[0] || !stricmp( name.c_str(), ai_debugFilterString.GetString() ))); +} + +/* +=============== +idActor::Event_IdleAnim +=============== +*/ +void idActor::Event_IdleAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( false ); + return; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso body if it doesn't override idle anims + headAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + // everything is idle, so play the anim on the head and copy it to the torso and legs + headAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } else if ( torsoAnim.IsIdle() ) { + // sync the head and torso to the legs + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, headAnim.animBlendFrames ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } else { + // sync the head to the torso + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, headAnim.animBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + if ( legsAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to legs if legs anim doesn't override idle anims + torsoAnim.CycleAnim( anim ); + } else if ( legsAnim.IsIdle() ) { + // play the anim in both legs and torso + torsoAnim.CycleAnim( anim ); + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + // sync the anim to the legs + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } + + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso if torso anim doesn't override idle anims + legsAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() ) { + // play the anim in both legs and torso + legsAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } else { + // sync the anim to the torso + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, legsAnim.animBlendFrames ); + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( true ); +} + +/* +================ +idActor::Event_SetSyncedAnimWeight +================ +*/ +void idActor::Event_SetSyncedAnimWeight( int channel, int anim, float weight ) { + idEntity *headEnt; + + headEnt = head.GetEntity(); + switch( channel ) { + case ANIMCHANNEL_HEAD : + if ( headEnt ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } else { + animator.CurrentAnim( ANIMCHANNEL_HEAD )->SetSyncedAnimWeight( anim, weight ); + } + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + case ANIMCHANNEL_TORSO : + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + break; + + case ANIMCHANNEL_LEGS : + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } +} + +/* +=============== +idActor::Event_OverrideAnim +=============== +*/ +void idActor::Event_OverrideAnim( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Disable(); + if ( !torsoAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_EnableAnim +=============== +*/ +void idActor::Event_EnableAnim( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Enable( blendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_SetBlendFrames +=============== +*/ +void idActor::Event_SetBlendFrames( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.animBlendFrames = blendFrames; + headAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.animBlendFrames = blendFrames; + torsoAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_LEGS : + legsAnim.animBlendFrames = blendFrames; + legsAnim.lastAnimBlendFrames = blendFrames; + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_GetBlendFrames +=============== +*/ +void idActor::Event_GetBlendFrames( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + idThread::ReturnInt( headAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_TORSO : + idThread::ReturnInt( torsoAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_LEGS : + idThread::ReturnInt( legsAnim.animBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +================ +idActor::Event_HasEnemies +================ +*/ +void idActor::Event_HasEnemies( void ) { + bool hasEnemy; + + hasEnemy = HasEnemies(); + idThread::ReturnInt( hasEnemy ); +} + +/* +================ +idActor::Event_NextEnemy +================ +*/ +void idActor::Event_NextEnemy( idEntity *ent ) { + idActor *actor; + + if ( !ent || ( ent == this ) ) { + actor = enemyList.Next(); + } else { + if ( !ent->IsType( idActor::Type ) ) { + gameLocal.Error( "'%s' cannot be an enemy", ent->name.c_str() ); + } + + actor = static_cast( ent ); + if ( actor->enemyNode.ListHead() != &enemyList ) { + gameLocal.Error( "'%s' is not in '%s' enemy list", actor->name.c_str(), name.c_str() ); + } + } + + for( ; actor != NULL; actor = actor->enemyNode.Next() ) { + if ( !actor->fl.hidden ) { + idThread::ReturnEntity( actor ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +================ +idActor::Event_ClosestEnemyToPoint +================ +*/ +void idActor::Event_ClosestEnemyToPoint( const idVec3 &pos ) { + idActor *bestEnt = ClosestEnemyToPoint( pos ); + idThread::ReturnEntity( bestEnt ); +} + +/* +================ +idActor::Event_StopSound +================ +*/ +void idActor::Event_StopSound( int channel, int netSync ) { + if ( channel == SND_CHANNEL_VOICE ) { + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->StopSound( channel, ( netSync != 0 ) ); + } + } + StopSound( channel, ( netSync != 0 ) ); +} + +/* +===================== +idActor::Event_GetHead +===================== +*/ +void idActor::Event_GetHead( void ) { + idThread::ReturnEntity( head.GetEntity() ); +} + +// RAVEN BEGIN +// jshepard: added +/* +===================== +idActor::Event_SetAnimRate +===================== +*/ +void idActor::Event_SetAnimRate( float multiplier ) { + animator.SetPlaybackRate(multiplier); +} + + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idActor::State_Wait_Frame + +Stop a state thread for a single frame +================ +*/ +stateResult_t idActor::State_Wait_Frame ( const stateParms_t& parms ) { + return SRESULT_DONE_WAIT; +} + +/* +================ +idActor::State_Wait_LegsAnim + +Stop a state thread until the animation running on the legs channel is finished +================ +*/ +stateResult_t idActor::State_Wait_LegsAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::State_Wait_TorsoAnim + +Stop a state thread until the animation running on the torso channel is finished +================ +*/ +stateResult_t idActor::State_Wait_TorsoAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::PlayAnim +================ +*/ +int idActor::PlayAnim ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + idEntity *headEnt; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + + if( ai_animShow.GetBool() ){ + gameLocal.DPrintf( "Playing animation '%s' on '%s' (%s)\n", animname, name.c_str(), spawnArgs.GetString( "head", "" ) ); + } + + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return 0; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnim.idleAnim = false; + headAnim.PlayAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.PlayAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.PlayAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default : + gameLocal.Error( "Unknown anim group" ); + break; + } + + return animator.CurrentAnim( channel )->Length(); +} + +/* +================ +idActor::PlayCycle +================ +*/ +bool idActor::PlayCycle ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return false; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.idleAnim = false; + headAnim.CycleAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.CycleAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.CycleAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + return true; +} + +void idActor::IdleAnim ( int channel, const char *name, int blendFrames ) { + Event_SetBlendFrames ( channel, blendFrames ); + Event_IdleAnim ( channel, name ); +} + +void idActor::OverrideAnim ( int channel ) { + Event_OverrideAnim ( channel ); +} + +idAnimState& idActor::GetAnimState ( int channel ) { + switch ( channel ) { + case ANIMCHANNEL_LEGS: return legsAnim; + case ANIMCHANNEL_TORSO: return torsoAnim; + case ANIMCHANNEL_HEAD: return headAnim; + default: + gameLocal.Error( "idActor::GetAnimState: Unknown anim channel" ); + return torsoAnim; + } +} + +void idActor::DisableAnimState ( int channel ) { + Event_OverrideAnim ( channel ); +// GetAnimState ( channel ).Disable ( ); +} + +void idActor::EnableAnimState ( int channel ) { + GetAnimState ( channel ).Enable ( 4 ); +} + +bool idActor::HasAnim ( int channel, const char* animname, bool forcePrefix ) { + return GetAnim( channel, animname, forcePrefix ) != NULL; +} + +bool idActor::AnimDone ( int channel, int blendFrames ) { + return GetAnimState( channel ).AnimDone ( blendFrames ); +} + +/* +===================== +idActor::GetDebugInfo +===================== +*/ +void idActor::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAFEntity_Gibbable::GetDebugInfo ( proc, userData ); + + proc ( "idActor", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); + + proc ( "idActor", "legs_state", legsAnim.GetStateThread().GetState()?legsAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "legs_disable", legsAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "legs_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) : "", userData ); + + proc ( "idActor", "torso_state", torsoAnim.GetStateThread().GetState()?torsoAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "torso_disabled", torsoAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "torso_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) : "", userData ); + + proc ( "idActor", "head_state", headAnim.GetStateThread().GetState()?headAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "head_disabled", headAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "head_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD )->AnimName ( ) : "", userData ); + + proc ( "idActor", "painAnim", painAnim.c_str(), userData ); + proc ( "idActor", "animPrefix", animPrefix.c_str(), userData ); +} + +//MCG: damage over time +void idActor::Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + inDamageEvent = true; + Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + inDamageEvent = false; + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + } +} + +void idActor::Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), this, interval ); + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +// MCG: script-callable joint crawl effect +void idActor::Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ) { + if ( effectKeyName ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( spawnArgs, effectKeyName ), this, 100 ); + effect->Play ( gameLocal.GetTime(), false ); + crawlSecs -= 0.1f; + if ( crawlSecs >= 0.1f ) { + PostEventMS( &EV_JointCrawlEffect, 100, effectKeyName, crawlSecs ); + } + } +} + +idEntity* idActor::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + + if ( testElevator && groundEnt != testElevator ) { + return groundEnt; + } + + idEntity* traceEnt; + idVec3 testPoint = GetPhysics()->GetOrigin(); + idVec3 testBottom; + testPoint.z += 1.0f; + + for ( int x = 0; x < 2; x++ ) { + testPoint.x = GetPhysics()->GetAbsBounds()[x].x; + for ( int y = 0; y < 2; y++ ) { + testPoint.y = GetPhysics()->GetAbsBounds()[y].y; + testBottom = testPoint; + testBottom.z -= 65.0f; + + trace_t tr; + gameLocal.TracePoint( this, tr, testPoint, testBottom, GetPhysics()->GetClipMask(), this ); + traceEnt = gameLocal.FindEntity( tr.c.entityNum ); + if ( !traceEnt ) { + return NULL; + } + while ( traceEnt->GetBindMaster() ) { + traceEnt = traceEnt->GetBindMaster(); + } + if ( traceEnt != groundEnt ) { + return traceEnt; + } + if ( testElevator && traceEnt != testElevator ) { + return traceEnt; + } + } + } + + return groundEnt; +} + +void idActor::GuidedProjectileIncoming( idGuidedProjectile *projectile ) +{ + if ( IsInVehicle() ) + { + if ( vehicleController.GetVehicle() ) + { + vehicleController.GetVehicle()->GuidedProjectileIncoming( projectile ); + } + } +} +// RAVEN END diff --git a/src/game/Actor.h b/src/game/Actor.h new file mode 100644 index 0000000..8ed03dd --- /dev/null +++ b/src/game/Actor.h @@ -0,0 +1,450 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ACTOR_H__ +#define __GAME_ACTOR_H__ + +/* +=============================================================================== + + idActor + +=============================================================================== +*/ + +extern const idEventDef AI_EnableEyeFocus; +extern const idEventDef AI_DisableEyeFocus; +extern const idEventDef EV_Footstep; +extern const idEventDef EV_FootstepLeft; +extern const idEventDef EV_FootstepRight; +extern const idEventDef EV_EnableWalkIK; +extern const idEventDef EV_DisableWalkIK; +extern const idEventDef EV_EnableLegIK; +extern const idEventDef EV_DisableLegIK; +extern const idEventDef AI_SetAnimPrefix; +extern const idEventDef AI_PlayAnim; +extern const idEventDef AI_PlayCycle; +extern const idEventDef AI_AnimDone; +extern const idEventDef AI_SetBlendFrames; +extern const idEventDef AI_GetBlendFrames; +extern const idEventDef AI_ScriptedMove; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; + +// RAVEN BEGIN +// bdube: added flashlight +extern const idEventDef AI_Flashlight; +extern const idEventDef AI_EnterVehicle; +extern const idEventDef AI_ExitVehicle; +// nmckenzie: +extern const idEventDef AI_OverrideAnim; +extern const idEventDef AI_IdleAnim; +extern const idEventDef AI_SetState; +// jshepard: adjust animation speed +extern const idEventDef AI_SetAnimRate; +//MCG: damage over time +extern const idEventDef EV_DamageOverTime; +extern const idEventDef EV_DamageOverTimeEffect; +//MCG: script-callable joint crawl effect +extern const idEventDef EV_JointCrawlEffect; + +// abahr: +extern const idEventDef AI_LookAt; +extern const idEventDef AI_FaceEnemy; +extern const idEventDef AI_FaceEntity; +extern const idEventDef AI_JumpDown; +extern const idEventDef AI_SetLeader; +// RAVEN END + +class idAnimState { +public: + + bool idleAnim; + int animBlendFrames; + int lastAnimBlendFrames; // allows override anims to blend based on the last transition time + +public: + idAnimState(); + ~idAnimState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Init( idEntity *owner, idAnimator *_animator, int animchannel ); + void Shutdown( void ); + void SetState ( const char *name, int blendFrames, int flags = 0 ); + void PostState ( const char* name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnim( int frames ); + void PlayAnim( int anim ); + void CycleAnim( int anim ); + void BecomeIdle( void ); + bool UpdateState( void ); + bool Disabled( void ) const; + void Enable( int blendFrames ); + void Disable( void ); + bool AnimDone( int blendFrames ) const; + bool IsIdle( void ) const; + animFlags_t GetAnimFlags( void ) const; + + rvStateThread& GetStateThread ( void ); + + idAnimator * GetAnimator( void ) const {return animator;}; +private: +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it + idEntity * self; +// RAVEN END + idAnimator * animator; + int channel; + bool disabled; + + rvStateThread stateThread; +}; + +ID_INLINE rvStateThread& idAnimState::GetStateThread ( void ) { + return stateThread; +} + +class idAttachInfo { +public: + idEntityPtr ent; + int channel; +}; + +class idActor : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idActor ); + + int team; + idLinkList teamNode; + int rank; // monsters don't fight back if the attacker's rank is higher + idMat3 viewAxis; // view axis of the actor + + idLinkList enemyNode; // node linked into an entity's enemy list for quick lookups of who is attacking him + idLinkList enemyList; // list of characters that have targeted the player as their enemy + +public: + idActor( void ); + virtual ~idActor( void ); + + void Spawn( void ); + virtual void Restart( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual int GetDefaultSurfaceType( void ) const; + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual bool LoadAF( const char* keyname = NULL, bool purgeAF = false ); + void SetupBody( void ); + + virtual void CheckBlink( void ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + // script state management + void ShutdownThreads ( void ); + void UpdateState ( void ); + + virtual void OnStateThreadClear ( const char *statename, int flags = 0 ); + void SetState ( const char *statename, int flags = 0 ); + void PostState ( const char* statename, int delay = 0, int flags = 0 ); + void InterruptState ( const char* statename, int delay = 0, int flags = 0 ); + + // vision testing + void SetEyeHeight( float height ); + void SetChestHeight ( float height ); + float EyeHeight( void ) const; + + virtual idVec3 GetEyePosition( void ) const; + virtual idVec3 GetChestPosition ( void ) const; + idEntity* GetGroundEntity ( void ) const; + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + void Present( void ); + + virtual void GetViewPos ( idVec3 &origin, idMat3 &axis ) const; + void SetFOV ( float fov, float fovClose ); + bool CheckFOV ( const idVec3 &pos, float ang = -1.0f ) const; + virtual bool HasFOV ( idEntity *ent ); + virtual bool CanSee ( const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFOV ) const; + + // damage + void SetupDamageGroups( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); +// RAVEN BEGIN +// nmckenzie: a final hook in the middle of the damage function + virtual void AdjustHealthByDamage ( int inDamage ){health -= inDamage;} +// RAVEN END + + virtual int GetDamageForLocation( int damage, int location ); + const char * GetDamageGroup( int location ); + void ClearPain( void ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + // model/combat model/ragdoll + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + bool StartRagdoll( void ); + void StopRagdoll( void ); + virtual bool UpdateAnimationControllers( void ); + + // delta view angles to allow movers to rotate the view of the actor + const idAngles & GetDeltaViewAngles( void ) const; + void SetDeltaViewAngles( const idAngles &delta ); + + bool HasEnemies( void ) const; + idActor * ClosestEnemyToPoint( const idVec3 &pos, float maxRange=0.0f, bool returnFirst=false, bool checkPVS=false ); + idActor * EnemyWithMostHealth(); + + virtual bool OnLadder ( void ) const; + virtual void OnStateChange ( int channel ); + virtual void OnFriendlyFire ( idActor* attacker ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + + void Attach( idEntity *ent ); + idEntity* FindAttachment( const char* attachmentName ); + void HideAttachment( const char* attachmentName ); + void ShowAttachment( const char* attachmentName ); + idEntity* GetHead() { return head; } + + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual renderView_t * GetRenderView(); + + // Animation + int PlayAnim ( int channel, const char *name, int blendFrames ); + bool PlayCycle ( int channel, const char *name, int blendFrames ); + void IdleAnim ( int channel, const char *name, int blendFrames ); + void OverrideAnim ( int channel ); + bool HasAnim ( int channel, const char *name, bool forcePrefix = false ); + int GetAnim ( int channel, const char *name, bool forcePrefix = false ); + bool AnimDone ( int channel, int blendFrames ); + + // animation state control + void UpdateAnimState ( void ); + void SetAnimState ( int channel, const char *name, int blendFrames = 0, int flags = 0 ); + void PostAnimState ( int channel, const char *name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnimState ( int channel ); + bool InAnimState ( int channel, const char *name ); + + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +// RAVEN BEGIN +// bdube: added for vehicle + bool IsInVehicle ( void ) const; + rvVehicleController& GetVehicleController ( void ); + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); + + bool DebugFilter (const idCVar& test) const; +// RAVEN END + virtual bool IsCrouching ( void ) const {return false;}; + + virtual bool SkipImpulse( idEntity* ent, int id ); + + int lightningNextTime; + int lightningEffects; + +protected: + friend class idAnimState; + + float fovDot; // cos( fovDegrees ) + float fovCloseDot; // cos( fovDegreesClose ) + float fovCloseRange; // range within to use fovCloseDot + idVec3 eyeOffset; // offset of eye relative to physics origin + idVec3 chestOffset; // offset of chest relative to physics origin + idVec3 modelOffset; // offset of visual model relative to the physics origin + + idAngles deltaViewAngles; // delta angles relative to view input angles + + int pain_debounce_time; // next time the actor can show pain + int pain_delay; // time between playing pain sound + + idStrList damageGroups; // body damage groups + idList damageScale; // damage scale per damage gruop + bool inDamageEvent; // hacky-ass bool to prevent us from starting a new EV_DamageOverTime in our ::Damage + + bool use_combat_bbox; // whether to use the bounding box for combat collision + + // joint handles + jointHandle_t leftEyeJoint; + jointHandle_t rightEyeJoint; + jointHandle_t soundJoint; + jointHandle_t eyeOffsetJoint; + jointHandle_t chestOffsetJoint; + jointHandle_t neckJoint; + jointHandle_t headJoint; + + idIK_Walk walkIK; + + idStr animPrefix; + idStr painType; + idStr painAnim; + + // blinking + int blink_anim; + int blink_time; + int blink_min; + int blink_max; + + idAnimState headAnim; + idAnimState torsoAnim; + idAnimState legsAnim; + + rvStateThread stateThread; + + idEntityPtr head; // safe pointer to attached head + + bool disablePain; + bool allowEyeFocus; + bool finalBoss; + + int painTime; + + idList attachments; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void CheckDeathObjectives( void ); + +// RAVEN BEGIN +// bdube: vehicles + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); +// RAVEN END + + // removes attachments with "remove" set for when character dies + void RemoveAttachments( void ); + +// RAVEN BEGIN +// bdube: vehicles + rvVehicleController vehicleController; +// bdube: flashlights + renderLight_t flashlight; + int flashlightHandle; + jointHandle_t flashlightJoint; + idVec3 flashlightOffset; + +// bdube: death force + int deathPushTime; + idVec3 deathPushForce; + jointHandle_t deathPushJoint; + + void FlashlightUpdate ( bool forceOn = false ); + void InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale = 1.0f ); + void DeathPush ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +protected: + + virtual void FootStep ( void ); + virtual void SetupHead( const char* headDefName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + void SyncAnimChannels( int channel, int syncToChannel, int blendFrames ); + void FinishSetup( void ); + + void Event_EnableEyeFocus( void ); + void Event_DisableEyeFocus( void ); + void Event_EnableBlink( void ); + void Event_DisableBlink( void ); + void Event_Footstep( void ); + void Event_EnableWalkIK( void ); + void Event_DisableWalkIK( void ); + void Event_EnableLegIK( int num ); + void Event_DisableLegIK( int num ); + void Event_SetAnimPrefix( const char *name ); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_PreventPain( float duration ); + void Event_DisablePain( void ); + void Event_EnablePain( void ); + void Event_StopAnim( int channel, int frames ); + void Event_PlayAnim( int channel, const char *name ); + void Event_PlayCycle( int channel, const char *name ); + void Event_IdleAnim( int channel, const char *name ); + void Event_SetSyncedAnimWeight( int channel, int anim, float weight ); + void Event_OverrideAnim( int channel ); + void Event_EnableAnim( int channel, int blendFrames ); + void Event_SetBlendFrames( int channel, int blendFrames ); + void Event_GetBlendFrames( int channel ); + void Event_HasEnemies( void ); + void Event_NextEnemy( idEntity *ent ); + void Event_ClosestEnemyToPoint( const idVec3 &pos ); + void Event_StopSound( int channel, int netsync ); + void Event_GetHead( void ); + + void Event_Teleport ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Flashlight ( bool enable ); + void Event_EnterVehicle ( idEntity* vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_PreExitVehicle( bool force ); + + void Event_SetAnimRate ( float multiplier ); + void Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, const char *damageDefName, const float damageScale, int location ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + void Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ); + + CLASS_STATES_PROTOTYPE ( idActor ); + +protected: + + // Wait states + stateResult_t State_Wait_LegsAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_TorsoAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_Frame ( const stateParms_t& parms ); + + void DisableAnimState ( int channel ); + void EnableAnimState ( int channel ); + idAnimState& GetAnimState ( int channel ); +}; + +ID_INLINE bool idActor::IsInVehicle( void ) const { + return vehicleController.IsDriving(); +} + +ID_INLINE rvVehicleController& idActor::GetVehicleController( void ) { + return vehicleController; +} + +#endif /* !__GAME_ACTOR_H__ */ + +// RAVEN END diff --git a/src/game/BrittleFracture.cpp b/src/game/BrittleFracture.cpp new file mode 100644 index 0000000..ae45d6c --- /dev/null +++ b/src/game/BrittleFracture.cpp @@ -0,0 +1,1378 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes +#ifdef _MD5R_SUPPORT +#include "../renderer/tr_local.h" +#include "../renderer/Model_local.h" +#endif +// RAVEN END + +CLASS_DECLARATION( idEntity, idBrittleFracture ) + EVENT( EV_Activate, idBrittleFracture::Event_Activate ) + EVENT( EV_Touch, idBrittleFracture::Event_Touch ) +END_CLASS + +const int SHARD_ALIVE_TIME = 5000; +const int SHARD_FADE_START = 2000; + +static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_"; + +/* +================ +idBrittleFracture::idBrittleFracture +================ +*/ +idBrittleFracture::idBrittleFracture( void ) { + material = NULL; + decalMaterial = NULL; + decalSize = 0.0f; + maxShardArea = 0.0f; + maxShatterRadius = 0.0f; + minShatterRadius = 0.0f; + linearVelocityScale = 0.0f; + angularVelocityScale = 0.0f; + shardMass = 0.0f; + density = 0.0f; + friction = 0.0f; + bouncyness = 0.0f; + fxFracture.Clear(); + + bounds.Clear(); + disableFracture = false; + + lastRenderEntityUpdate = -1; + changed = false; + + fl.networkSync = true; +} + +/* +================ +idBrittleFracture::~idBrittleFracture +================ +*/ +idBrittleFracture::~idBrittleFracture( void ) { + int i; + + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->decals.DeleteContents( true ); + delete shards[i]; + } + + // make sure the render entity is freed before the model is freed + FreeModelDef(); + renderModelManager->FreeModel( renderEntity.hModel ); +} + +/* +================ +idBrittleFracture::Save +================ +*/ +void idBrittleFracture::Save( idSaveGame *savefile ) const { + int i, j; + + savefile->WriteInt( health ); + savefile->Write( &fl, sizeof( fl ) ); + + // setttings + savefile->WriteMaterial( material ); + savefile->WriteMaterial( decalMaterial ); + savefile->WriteFloat( decalSize ); + savefile->WriteFloat( maxShardArea ); + savefile->WriteFloat( maxShatterRadius ); + savefile->WriteFloat( minShatterRadius ); + savefile->WriteFloat( linearVelocityScale ); + savefile->WriteFloat( angularVelocityScale ); + savefile->WriteFloat( shardMass ); + savefile->WriteFloat( density ); + savefile->WriteFloat( friction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteString( fxFracture ); + + // state + savefile->WriteBounds( bounds ); + savefile->WriteBool( disableFracture ); + + savefile->WriteInt( lastRenderEntityUpdate ); + savefile->WriteBool( changed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( shards.Num() ); + for ( i = 0; i < shards.Num(); i++ ) { + savefile->WriteWinding( shards[i]->winding ); + + savefile->WriteInt( shards[i]->decals.Num() ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { + savefile->WriteWinding( *shards[i]->decals[j] ); + } + + savefile->WriteInt( shards[i]->neighbours.Num() ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index = shards.FindIndex(shards[i]->neighbours[j]); + assert(index != -1); + savefile->WriteInt( index ); + } + + savefile->WriteInt( shards[i]->edgeHasNeighbour.Num() ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->WriteBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->WriteInt( shards[i]->droppedTime ); + savefile->WriteInt( shards[i]->islandNum ); + savefile->WriteBool( shards[i]->atEdge ); + savefile->WriteStaticObject( shards[i]->physicsObj ); + } +} + +/* +================ +idBrittleFracture::Restore +================ +*/ +void idBrittleFracture::Restore( idRestoreGame *savefile ) { + int i, j , num; + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; + + savefile->ReadInt( health ); + savefile->Read( &fl, sizeof( fl ) ); + + // setttings + savefile->ReadMaterial( material ); + savefile->ReadMaterial( decalMaterial ); + savefile->ReadFloat( decalSize ); + savefile->ReadFloat( maxShardArea ); + savefile->ReadFloat( maxShatterRadius ); + savefile->ReadFloat( minShatterRadius ); + savefile->ReadFloat( linearVelocityScale ); + savefile->ReadFloat( angularVelocityScale ); + savefile->ReadFloat( shardMass ); + savefile->ReadFloat( density ); + savefile->ReadFloat( friction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadString( fxFracture ); + + // state + savefile->ReadBounds(bounds); + savefile->ReadBool( disableFracture ); + + savefile->ReadInt( lastRenderEntityUpdate ); + savefile->ReadBool( changed ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + shards.SetNum( num ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + for ( i = 0; i < num; i++ ) { + shards[i] = new shard_t; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + savefile->ReadWinding( shards[i]->winding ); + + savefile->ReadInt( j ); + shards[i]->decals.SetNum( j ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shards[i]->decals[j] = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + savefile->ReadWinding( *shards[i]->decals[j] ); + } + + savefile->ReadInt( j ); + shards[i]->neighbours.SetNum( j ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index; + savefile->ReadInt( index ); + assert(index != -1); + shards[i]->neighbours[j] = shards[index]; + } + + savefile->ReadInt( j ); + shards[i]->edgeHasNeighbour.SetNum( j ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->ReadBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->ReadInt( shards[i]->droppedTime ); + savefile->ReadInt( shards[i]->islandNum ); + savefile->ReadBool( shards[i]->atEdge ); + savefile->ReadStaticObject( shards[i]->physicsObj ); + if ( shards[i]->droppedTime < 0 ) { + shards[i]->clipModel = physicsObj.GetClipModel( i ); + } else { + shards[i]->clipModel = shards[i]->physicsObj.GetClipModel(); + } + } +} + +/* +================ +idBrittleFracture::Spawn +================ +*/ +void idBrittleFracture::Spawn( void ) { + + // get shard properties + decalMaterial = declManager->FindMaterial( spawnArgs.GetString( "mtr_decal" ) ); + decalSize = spawnArgs.GetFloat( "decalSize", "40" ); + maxShardArea = spawnArgs.GetFloat( "maxShardArea", "200" ); + maxShardArea = idMath::ClampFloat( 100, 10000, maxShardArea ); + maxShatterRadius = spawnArgs.GetFloat( "maxShatterRadius", "40" ); + minShatterRadius = spawnArgs.GetFloat( "minShatterRadius", "10" ); + linearVelocityScale = spawnArgs.GetFloat( "linearVelocityScale", "0.1" ); + angularVelocityScale = spawnArgs.GetFloat( "angularVelocityScale", "40" ); + fxFracture = spawnArgs.GetString( "fx" ); + + // get rigid body properties + shardMass = spawnArgs.GetFloat( "shardMass", "20" ); + shardMass = idMath::ClampFloat( 0.001f, 1000.0f, shardMass ); + spawnArgs.GetFloat( "density", "0.1", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.4", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.01", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + disableFracture = spawnArgs.GetBool( "disableFracture", "0" ); + health = spawnArgs.GetInt( "health", "40" ); + fl.takedamage = true; + + // FIXME: set "bleed" so idProjectile calls AddDamageEffect + spawnArgs.SetBool( "bleed", 1 ); + + CreateFractures( renderEntity.hModel ); + + FindNeighbours(); + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; +} + +/* +================ +idBrittleFracture::AddShard +================ +*/ +void idBrittleFracture::AddShard( idClipModel *clipModel, idFixedWinding &w ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shard_t *shard = new shard_t; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shard->clipModel = clipModel; + shard->droppedTime = -1; + shard->winding = w; + shard->decals.Clear(); + shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false ); + shard->neighbours.Clear(); + shard->atEdge = false; + shards.Append( shard ); +} + +/* +================ +idBrittleFracture::RemoveShard +================ +*/ +void idBrittleFracture::RemoveShard( int index ) { + int i; + + delete shards[index]; + shards.RemoveIndex( index ); + physicsObj.RemoveIndex( index ); + + for ( i = index; i < shards.Num(); i++ ) { + shards[i]->clipModel->SetId( i ); + } +} + +/* +================ +idBrittleFracture::UpdateRenderEntity +================ +*/ +bool idBrittleFracture::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + int i, j, k, n, msec, numTris, numDecalTris; + float fade; + dword packedColor; + srfTriangles_t *tris, *decalTris; + modelSurface_t surface; + idDrawVert *v; + idPlane plane; + idMat3 tangents; + + // this may be triggered by a model trace or other non-view related source, + // to which we should look like an empty model + if ( !renderView ) { + return false; + } + + // don't regenerate it if it is current + if ( lastRenderEntityUpdate == gameLocal.time || !changed ) { + return false; + } + + lastRenderEntityUpdate = gameLocal.time; + changed = false; + + numTris = 0; + numDecalTris = 0; + for ( i = 0; i < shards.Num(); i++ ) { + n = shards[i]->winding.GetNumPoints(); + if ( n > 2 ) { + numTris += n - 2; + } + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + n = shards[i]->decals[k]->GetNumPoints(); + if ( n > 2 ) { + numDecalTris += n - 2; + } + } + } + + // FIXME: re-use model surfaces + renderEntity->hModel->InitEmpty( brittleFracture_SnapshotName ); + + // allocate triangle surfaces for the fractures and decals + { + ConditionalAutoCrit crit; + + tris = renderEntity->hModel->AllocSurfaceTriangles( numTris * 3, material->ShouldCreateBackSides() ? numTris * 6 : numTris * 3 ); + decalTris = renderEntity->hModel->AllocSurfaceTriangles( numDecalTris * 3, decalMaterial->ShouldCreateBackSides() ? numDecalTris * 6 : numDecalTris * 3 ); + } + + for ( i = 0; i < shards.Num(); i++ ) { + const idVec3 &origin = shards[i]->clipModel->GetOrigin(); + const idMat3 &axis = shards[i]->clipModel->GetAxis(); + + fade = 1.0f; + if ( shards[i]->droppedTime >= 0 ) { + msec = gameLocal.time - shards[i]->droppedTime - SHARD_FADE_START; + if ( msec > 0 ) { + fade = 1.0f - (float) msec / ( SHARD_ALIVE_TIME - SHARD_FADE_START ); + } + } + packedColor = PackColor( idVec4( renderEntity->shaderParms[ SHADERPARM_RED ] * fade, + renderEntity->shaderParms[ SHADERPARM_GREEN ] * fade, + renderEntity->shaderParms[ SHADERPARM_BLUE ] * fade, + fade ) ); + + const idWinding &winding = shards[i]->winding; + + winding.GetPlane( plane ); + tangents = ( plane.Normal() * axis ).ToMat3(); + + for ( j = 2; j < winding.GetNumPoints(); j++ ) { + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[0].ToVec3() * axis; + v->st[0] = winding[0].s; + v->st[1] = winding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j-1].ToVec3() * axis; + v->st[0] = winding[j-1].s; + v->st[1] = winding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j].ToVec3() * axis; + v->st[0] = winding[j].s; + v->st[1] = winding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + + if ( material->ShouldCreateBackSides() ) { + + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + } + } + + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + const idWinding &decalWinding = *shards[i]->decals[k]; + + for ( j = 2; j < decalWinding.GetNumPoints(); j++ ) { + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[0].ToVec3() * axis; + v->st[0] = decalWinding[0].s; + v->st[1] = decalWinding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j-1].ToVec3() * axis; + v->st[0] = decalWinding[j-1].s; + v->st[1] = decalWinding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j].ToVec3() * axis; + v->st[0] = decalWinding[j].s; + v->st[1] = decalWinding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + + if ( decalMaterial->ShouldCreateBackSides() ) { + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + } + } + } + } + + tris->tangentsCalculated = true; + decalTris->tangentsCalculated = true; + + SIMDProcessor->MinMax( tris->bounds[0], tris->bounds[1], tris->verts, tris->numVerts ); + SIMDProcessor->MinMax( decalTris->bounds[0], decalTris->bounds[1], decalTris->verts, decalTris->numVerts ); + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = material; + surface.id = 0; + surface.geometry = tris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = decalMaterial; + surface.id = 1; + surface.geometry = decalTris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + return true; +} + +/* +================ +idBrittleFracture::ModelCallback +================ +*/ +bool idBrittleFracture::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + const idBrittleFracture *ent; + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idBrittleFracture::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idBrittleFracture::Present +================ +*/ +void idBrittleFracture::Present() { + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + renderEntity.bounds = bounds; + renderEntity.origin.Zero(); + renderEntity.axis.Identity(); + + // force an update because the bounds/origin/axis may stay the same while the model changes + renderEntity.forceUpdate = true; + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + + changed = true; +} + +/* +================ +idBrittleFracture::Think +================ +*/ +void idBrittleFracture::Think( void ) { + int i, startTime, endTime, droppedTime; + shard_t *shard; + bool atRest = true, fading = false; + + // remove overdue shards + for ( i = 0; i < shards.Num(); i++ ) { + droppedTime = shards[i]->droppedTime; + if ( droppedTime != -1 ) { + if ( gameLocal.time - droppedTime > SHARD_ALIVE_TIME ) { + RemoveShard( i ); + i--; + } + fading = true; + } + } + + // remove the entity when nothing is visible + if ( !shards.Num() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + if ( thinkFlags & TH_PHYSICS ) { + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + // run physics on shards + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime == -1 ) { + continue; + } + + shard->physicsObj.Evaluate( endTime - startTime, endTime ); + + if ( !shard->physicsObj.IsAtRest() ) { + atRest = false; + } + } + + if ( atRest ) { + BecomeInactive( TH_PHYSICS ); + } else { + BecomeActive( TH_PHYSICS ); + } + } + + if ( !atRest || bounds.IsCleared() ) { + bounds.Clear(); + for ( i = 0; i < shards.Num(); i++ ) { + bounds.AddBounds( shards[i]->clipModel->GetAbsBounds() ); + } + } + + if ( fading ) { + BecomeActive( TH_UPDATEVISUALS | TH_THINK ); + } else { + BecomeInactive( TH_THINK ); + } + + RunPhysics(); + Present(); +} + +/* +================ +idBrittleFracture::ApplyImpulse +================ +*/ +void idBrittleFracture::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.ApplyImpulse( 0, point, impulse ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, impulse, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::AddForce +================ +*/ +void idBrittleFracture::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.AddForce( 0, point, force ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, force, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::ProjectDecal +================ +*/ +void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) { + int i, j, bits, clipBits; + float a, c, s; + idVec2 st[MAX_POINTS_ON_WINDING]; + idVec3 origin; + idMat3 axis, axistemp; + idPlane textureAxis[2]; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( dir[0] ); + msg.WriteFloat( dir[1] ); + msg.WriteFloat( dir[2] ); + ServerSendInstanceEvent( EVENT_PROJECT_DECAL, &msg, true, -1 ); + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; + } + } + + if ( time >= gameLocal.time ) { + // try to get the sound from the damage def + const idDeclEntityDef *damageDef = NULL; + const idSoundShader *sndShader = NULL; + if ( damageDefName ) { + damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + sndShader = declManager->FindSound( damageDef->dict.GetString( "snd_shatter", "" ) ); + } + } + + if ( sndShader ) { + StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL ); + } else { + StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + + axis[2] = -dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + textureAxis[0] = axis[0] * ( 1.0f / decalSize ); + textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f; + + textureAxis[1] = axis[1] * ( 1.0f / decalSize ); + textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f; + + for ( i = 0; i < shards.Num(); i++ ) { + idFixedWinding &winding = shards[i]->winding; + origin = shards[i]->clipModel->GetOrigin(); + axis = shards[i]->clipModel->GetAxis(); + float d0, d1; + + clipBits = -1; + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + idVec3 p = origin + winding[j].ToVec3() * axis; + + st[j].x = d0 = textureAxis[0].Distance( p ); + st[j].y = d1 = textureAxis[1].Distance( p ); + + bits = FLOATSIGNBITSET( d0 ); + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 2; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 1; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + clipBits &= bits; + } + + if ( clipBits ) { + continue; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idFixedWinding *decal = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shards[i]->decals.Append( decal ); + + decal->SetNumPoints( winding.GetNumPoints() ); + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + (*decal)[j].ToVec3() = winding[j].ToVec3(); + (*decal)[j].s = st[j].x; + (*decal)[j].t = st[j].y; + } + } + + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idBrittleFracture::DropShard +================ +*/ +void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) { + int i, j, clipModelId; + float dist, f; + idVec3 dir2, origin; + idMat3 axis; + shard_t *neighbour; + + // don't display decals on dropped shards + shard->decals.DeleteContents( true ); + + // remove neighbour pointers of neighbours pointing to this shard + for ( i = 0; i < shard->neighbours.Num(); i++ ) { + neighbour = shard->neighbours[i]; + for ( j = 0; j < neighbour->neighbours.Num(); j++ ) { + if ( neighbour->neighbours[j] == shard ) { + neighbour->neighbours.RemoveIndex( j ); + break; + } + } + } + + // remove neighbour pointers + shard->neighbours.Clear(); + + // remove the clip model from the static physics object + clipModelId = shard->clipModel->GetId(); + physicsObj.SetClipModel( NULL, 1.0f, clipModelId, false ); + + origin = shard->clipModel->GetOrigin(); + axis = shard->clipModel->GetAxis(); + + // set the dropped time for fading + shard->droppedTime = time; + + dir2 = origin - point; + dist = dir2.Normalize(); +// RAVEN BEGIN +// jscott: changed to avoid negative sqrt call which propagated into badness + if( dist > maxShatterRadius ) { + f = 1.0f; + } else if( dist < minShatterRadius ) { + f = 0.0f; + } else { + f = idMath::Sqrt( dist - minShatterRadius ) * idMath::InvSqrt( maxShatterRadius - minShatterRadius ); + } +// RAVEN END + + // setup the physics + shard->physicsObj.SetSelf( this ); + shard->physicsObj.SetClipModel( shard->clipModel, density ); + shard->physicsObj.SetMass( shardMass ); + shard->physicsObj.SetOrigin( origin ); + shard->physicsObj.SetAxis( axis ); + shard->physicsObj.SetBouncyness( bouncyness ); + shard->physicsObj.SetFriction( 0.6f, 0.6f, friction ); + shard->physicsObj.SetGravity( gameLocal.GetGravity() ); + shard->physicsObj.SetContents( CONTENTS_RENDERMODEL ); + shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir ); + shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) ); + + shard->clipModel->SetId( clipModelId ); + + BecomeActive( TH_PHYSICS ); +} + +/* +================ +idBrittleFracture::Shatter +================ +*/ +void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i; + idVec3 dir; + shard_t *shard; + float m; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( impulse[0] ); + msg.WriteFloat( impulse[1] ); + msg.WriteFloat( impulse[2] ); + ServerSendInstanceEvent( EVENT_SHATTER, &msg, true, -1 ); + } + + if ( time > ( gameLocal.time - SHARD_ALIVE_TIME ) ) { + StartSound( "snd_shatter", SND_CHANNEL_ANY, 0, false, NULL ); + } + + if ( !IsBroken() ) { + Break(); + } + +// RAVEN BEGIN +// bdube: raven effect system + PlayEffect ( "fx_shatter", point, GetPhysics()->GetAxis() ); +// RAVEN END + + dir = impulse; + m = dir.Normalize(); + + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime != -1 ) { + continue; + } + + if ( ( shard->clipModel->GetOrigin() - point ).LengthSqr() > Square( maxShatterRadius ) ) { + continue; + } + + DropShard( shard, point, dir, m, time ); + } + + DropFloatingIslands( point, impulse, time ); +} + +/* +================ +idBrittleFracture::DropFloatingIslands +================ +*/ +void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i, j, numIslands; + int queueStart, queueEnd; + shard_t *curShard, *nextShard, **queue; + bool touchesEdge; + idVec3 dir; + + dir = impulse; + dir.Normalize(); + + numIslands = 0; + queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) ); + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->islandNum = 0; + } + + for ( i = 0; i < shards.Num(); i++ ) { + + if ( shards[i]->droppedTime != -1 ) { + continue; + } + + if ( shards[i]->islandNum ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = shards[i]; + shards[i]->islandNum = numIslands+1; + touchesEdge = false; + + if ( shards[i]->atEdge ) { + touchesEdge = true; + } + + for ( curShard = queue[queueStart]; queueStart < queueEnd; curShard = queue[++queueStart] ) { + + for ( j = 0; j < curShard->neighbours.Num(); j++ ) { + + nextShard = curShard->neighbours[j]; + + if ( nextShard->droppedTime != -1 ) { + continue; + } + + if ( nextShard->islandNum ) { + continue; + } + + queue[queueEnd++] = nextShard; + nextShard->islandNum = numIslands+1; + + if ( nextShard->atEdge ) { + touchesEdge = true; + } + } + } + numIslands++; + + // if the island is not connected to the world at any edges + if ( !touchesEdge ) { + for ( j = 0; j < queueEnd; j++ ) { + DropShard( queue[j], point, dir, 0.0f, time ); + } + } + } +} + +/* +================ +idBrittleFracture::Break +================ +*/ +void idBrittleFracture::Break( void ) { + fl.takedamage = false; + physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER ); +} + +/* +================ +idBrittleFracture::IsBroken +================ +*/ +bool idBrittleFracture::IsBroken( void ) const { + return ( fl.takedamage == false ); +} + +/* +================ +idBrittleFracture::Killed +================ +*/ +void idBrittleFracture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !disableFracture ) { + ActivateTargets( this ); + Break(); + } +} + +/* +================ +idBrittleFracture::AddDamageEffect +================ +*/ +void idBrittleFracture::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !disableFracture ) { + ProjectDecal( collision.c.point, collision.c.normal, gameLocal.time, damageDefName ); + } +} + +/* +================ +idBrittleFracture::Fracture_r +================ +*/ +void idBrittleFracture::Fracture_r( idFixedWinding &w ) { + int i, j, bestPlane; + float a, c, s, dist, bestDist; + idVec3 origin; + idPlane windingPlane, splitPlanes[2]; + idMat3 axis, axistemp; + idFixedWinding back; + idTraceModel trm; + idClipModel *clipModel; + + while( 1 ) { + origin = w.GetCenter(); + w.GetPlane( windingPlane ); + + if ( w.GetArea() < maxShardArea ) { + break; + } + + // randomly create a split plane + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + axis[2] = windingPlane.Normal(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + // get the best split plane + bestDist = 0.0f; + bestPlane = 0; + for ( i = 0; i < 2; i++ ) { + splitPlanes[i].SetNormal( axis[i] ); + splitPlanes[i].FitThroughPoint( origin ); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + dist = splitPlanes[i].Distance( w[j].ToVec3() ); + if ( dist > bestDist ) { + bestDist = dist; + bestPlane = i; + } + } + } + + // split the winding + if ( !w.Split( &back, splitPlanes[bestPlane] ) ) { + break; + } + + // recursively create shards for the back winding + Fracture_r( back ); + } + + // translate the winding to it's center + origin = w.GetCenter(); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + w[j].ToVec3() -= origin; + } + w.RemoveEqualPoints(); + + trm.SetupPolygon( w ); + trm.Shrink( CM_CLIP_EPSILON ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetClipModel( clipModel, 1.0f, shards.Num() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + origin, shards.Num() ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), shards.Num() ); + + AddShard( clipModel, w ); +} + +/* +================ +idBrittleFracture::CreateFractures +================ +*/ +void idBrittleFracture::CreateFractures( const idRenderModel *renderModel ) { + int i, j, k; + const modelSurface_t *surf; + const idDrawVert *v; + idFixedWinding w; + + if ( !renderModel ) { + return; + } + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin(), 0 ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), 0 ); + + for ( i = 0; i < 1 /*renderModel->NumSurfaces()*/; i++ ) { + surf = renderModel->Surface( i ); + material = surf->shader; + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes + srfTriangles_t * tri = surf->geometry; +#ifdef _MD5R_SUPPORT + if ( tri->primBatchMesh != NULL ) { + + srfTriangles_t *tempTri = (srfTriangles_t *) _alloca16( sizeof(srfTriangles_t) ); + memset( tempTri, 0, sizeof( srfTriangles_t ) ); + + assert( tri->silTraceVerts != NULL ); + + tempTri->numVerts = tri->primBatchMesh->GetNumDrawVertices(); + tempTri->numIndexes = tri->primBatchMesh->GetNumDrawIndices(); + + tempTri->indexes = (glIndex_t *) _alloca16( tempTri->numIndexes * sizeof( tempTri->indexes[0] ) ); + tempTri->verts = (idDrawVert *) _alloca16( tempTri->numVerts * sizeof( idDrawVert ) ); + + renderSystem->CopyPrimBatchTriangles(tempTri->verts, tempTri->indexes, tri->primBatchMesh, tri->silTraceVerts ); + + tri = tempTri; + } +#endif +// RAVEN END + + for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) { + w.Clear(); + for ( k = 0; k < 3; k++ ) { +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes (referred to surf->geometry as tri) + v = &tri->verts[ tri->indexes[ j + 2 - k ] ]; +// RAVEN END + w.AddPoint( v->xyz ); + w[k].s = v->st[0]; + w[k].t = v->st[1]; + } + Fracture_r( w ); + } + } + + physicsObj.SetContents( material->GetContentFlags() ); + SetPhysics( &physicsObj ); +} + +/* +================ +idBrittleFracture::FindNeighbours +================ +*/ +void idBrittleFracture::FindNeighbours( void ) { + int i, j, k, l; + idVec3 p1, p2, dir; + idMat3 axis; + idPlane plane[4]; + + for ( i = 0; i < shards.Num(); i++ ) { + + shard_t *shard1 = shards[i]; + const idWinding &w1 = shard1->winding; + const idVec3 &origin1 = shard1->clipModel->GetOrigin(); + const idMat3 &axis1 = shard1->clipModel->GetAxis(); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + + p1 = origin1 + w1[k].ToVec3() * axis1; + p2 = origin1 + w1[(k+1)%w1.GetNumPoints()].ToVec3() * axis1; + dir = p2 - p1; + dir.Normalize(); + axis = dir.ToMat3(); + + plane[0].SetNormal( dir ); + plane[0].FitThroughPoint( p1 ); + plane[1].SetNormal( -dir ); + plane[1].FitThroughPoint( p2 ); + plane[2].SetNormal( axis[1] ); + plane[2].FitThroughPoint( p1 ); + plane[3].SetNormal( axis[2] ); + plane[3].FitThroughPoint( p1 ); + + for ( j = 0; j < shards.Num(); j++ ) { + + if ( i == j ) { + continue; + } + + shard_t *shard2 = shards[j]; + + for ( l = 0; l < shard1->neighbours.Num(); l++ ) { + if ( shard1->neighbours[l] == shard2 ) { + break; + } + } + if ( l < shard1->neighbours.Num() ) { + continue; + } + + const idWinding &w2 = shard2->winding; + const idVec3 &origin2 = shard2->clipModel->GetOrigin(); + const idMat3 &axis2 = shard2->clipModel->GetAxis(); + + for ( l = w2.GetNumPoints()-1; l >= 0; l-- ) { + p1 = origin2 + w2[l].ToVec3() * axis2; + p2 = origin2 + w2[(l-1+w2.GetNumPoints())%w2.GetNumPoints()].ToVec3() * axis2; + if ( plane[0].Side( p2, 0.1f ) == SIDE_FRONT && plane[1].Side( p1, 0.1f ) == SIDE_FRONT ) { + if ( plane[2].Side( p1, 0.1f ) == SIDE_ON && plane[3].Side( p1, 0.1f ) == SIDE_ON ) { + if ( plane[2].Side( p2, 0.1f ) == SIDE_ON && plane[3].Side( p2, 0.1f ) == SIDE_ON ) { + shard1->neighbours.Append( shard2 ); + shard1->edgeHasNeighbour[k] = true; + shard2->neighbours.Append( shard1 ); + shard2->edgeHasNeighbour[(l-1+w2.GetNumPoints())%w2.GetNumPoints()] = true; + break; + } + } + } + } + } + } + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + if ( !shard1->edgeHasNeighbour[k] ) { + break; + } + } + if ( k < w1.GetNumPoints() ) { + shard1->atEdge = true; + } else { + shard1->atEdge = false; + } + } +} + +/* +================ +idBrittleFracture::Event_Activate +================ +*/ +void idBrittleFracture::Event_Activate( idEntity *activator ) { + disableFracture = false; + if ( health <= 0 ) { + Break(); + } +} + +/* +================ +idBrittleFracture::Event_Touch +================ +*/ +void idBrittleFracture::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 point, impulse; + + if ( !IsBroken() ) { + return; + } + + if ( trace->c.id < 0 || trace->c.id >= shards.Num() ) { + return; + } + + point = shards[trace->c.id]->clipModel->GetOrigin(); + impulse = other->GetPhysics()->GetLinearVelocity() * other->GetPhysics()->GetMass(); + + Shatter( point, impulse, gameLocal.time ); +} + +/* +================ +idBrittleFracture::ClientPredictionThink +================ +*/ +void idBrittleFracture::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + + Think(); +} + +/* +================ +idBrittleFracture::ClientReceiveEvent +================ +*/ +bool idBrittleFracture::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 point, dir; + + switch( event ) { + case EVENT_PROJECT_DECAL: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + ProjectDecal( point, dir, time, NULL ); + return true; + } + case EVENT_SHATTER: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + Shatter( point, dir, time ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} diff --git a/src/game/BrittleFracture.h b/src/game/BrittleFracture.h new file mode 100644 index 0000000..479128b --- /dev/null +++ b/src/game/BrittleFracture.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_BRITTLEFRACTURE_H__ +#define __GAME_BRITTLEFRACTURE_H__ + + +/* +=============================================================================== + +B-rep Brittle Fracture - Static entity using the boundary representation +of the render model which can fracture. + +=============================================================================== +*/ + +typedef struct shard_s { + idClipModel * clipModel; + idFixedWinding winding; + idList decals; + idList edgeHasNeighbour; + idList neighbours; + idPhysics_RigidBody physicsObj; + int droppedTime; + bool atEdge; + int islandNum; +} shard_t; + + +class idBrittleFracture : public idEntity { + +public: + CLASS_PROTOTYPE( idBrittleFracture ); + + idBrittleFracture( void ); + virtual ~idBrittleFracture( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Present( void ); + virtual void Think( void ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ); + bool IsBroken( void ) const; + + enum { + EVENT_PROJECT_DECAL = idEntity::EVENT_MAXEVENTS, + EVENT_SHATTER, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + // setttings + const idMaterial * material; + const idMaterial * decalMaterial; + float decalSize; + float maxShardArea; + float maxShatterRadius; + float minShatterRadius; + float linearVelocityScale; + float angularVelocityScale; + float shardMass; + float density; + float friction; + float bouncyness; + idStr fxFracture; + + // state + idPhysics_StaticMulti physicsObj; + idList shards; + idBounds bounds; + bool disableFracture; + + // for rendering + mutable int lastRenderEntityUpdate; + mutable bool changed; + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + void AddShard( idClipModel *clipModel, idFixedWinding &w ); + void RemoveShard( int index ); + void DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ); + void Shatter( const idVec3 &point, const idVec3 &impulse, const int time ); + void DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ); + void Break( void ); + void Fracture_r( idFixedWinding &w ); + void CreateFractures( const idRenderModel *renderModel ); + void FindNeighbours( void ); + + void Event_Activate( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif /* !__GAME_BRITTLEFRACTURE_H__ */ diff --git a/src/game/Camera.cpp b/src/game/Camera.cpp new file mode 100644 index 0000000..71abd1b --- /dev/null +++ b/src/game/Camera.cpp @@ -0,0 +1,2232 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idCamera + + Base class for cameras + +=============================================================================== +*/ + +ABSTRACT_DECLARATION( idEntity, idCamera ) +END_CLASS + +/* +===================== +idCamera::Spawn +===================== +*/ +void idCamera::Spawn( void ) { +} + +/* +===================== +idCamera::GetRenderView +===================== +*/ +renderView_t *idCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + GetViewParms( rv ); + return rv; +} + +/*********************************************************************** + + idCameraView + +***********************************************************************/ +const idEventDef EV_Camera_SetAttachments( "", NULL ); + +// RAVEN BEGIN +// bdube: added events +const idEventDef EV_SetFOV ( "setFOV", "f" ); +const idEventDef EV_GetFOV ( "getFOV", NULL, 'f' ); +const idEventDef EV_BlendFOV ( "blendFOV", "fff"); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraView ) + EVENT( EV_Activate, idCameraView::Event_Activate ) + EVENT( EV_Camera_SetAttachments, idCameraView::Event_SetAttachments ) + +// RAVEN BEGIN +// bdube: added events + EVENT( EV_SetFOV, idCameraView::Event_SetFOV ) + EVENT( EV_BlendFOV, idCameraView::Event_BlendFOV ) + EVENT( EV_GetFOV, idCameraView::Event_GetFOV ) +// RAVEN END +END_CLASS + +/* +=============== +idCameraView::idCameraView +================ +*/ +idCameraView::idCameraView() { +// RAVEN BEGIN +// bdube: interpolate fov +// scork: get it from the cvar, don't assume 90 + fov.Init ( gameLocal.time, 0, g_fov.GetFloat(), g_fov.GetFloat() ); +// RAVEN END + attachedTo = NULL; + attachedView = NULL; +} + +/* +=============== +idCameraView::Save +================ +*/ +void idCameraView::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// bdube: fov interpolated now + savefile->WriteInt( fov.GetDuration() ); + savefile->WriteInt( fov.GetStartTime() ); + savefile->WriteFloat( fov.GetStartValue() ); + savefile->WriteFloat( fov.GetEndValue() ); +// RAVEN END + savefile->WriteObject( attachedTo ); + savefile->WriteObject( attachedView ); +} + +/* +=============== +idCameraView::Restore +================ +*/ +void idCameraView::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// bdube: fov interpolated now + int set; + float setf; + savefile->ReadInt( set ); + fov.SetDuration( set ); + savefile->ReadInt( set ); + fov.SetStartTime( set ); + savefile->ReadFloat( setf ); + fov.SetStartValue( setf ); + savefile->ReadFloat( setf ); + fov.SetEndValue( setf ); +// RAVEN END + savefile->ReadObject( reinterpret_cast( attachedTo ) ); + savefile->ReadObject( reinterpret_cast( attachedView ) ); +} + +/* +=============== +idCameraView::Event_SetAttachments +================ +*/ +void idCameraView::Event_SetAttachments( ) { + SetAttachment( &attachedTo, "attachedTo" ); + SetAttachment( &attachedView, "attachedView" ); +} + +/* +=============== +idCameraView::Event_Activate +================ +*/ +void idCameraView::Event_Activate( idEntity *activator ) { + if (spawnArgs.GetBool("trigger")) { + if (gameLocal.GetCamera() != this) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + gameLocal.SetCamera(this); + } else { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + } + } +} + +/* +===================== +idCameraView::Stop +===================== +*/ +void idCameraView::Stop( void ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + ActivateTargets( gameLocal.GetLocalPlayer() ); +} + +// RAVEN BEGIN +// bdube: added events +/* +===================== +idCameraView::Event_SetFOV +===================== +*/ +void idCameraView::Event_SetFOV ( float newfov ) +{ + fov.Init ( gameLocal.time, 0, newfov, newfov ); +} + +/* +===================== +idCameraView::Event_BlendFOV +===================== +*/ +void idCameraView::Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ) +{ + fov.Init ( gameLocal.time, SEC2MS(blendTime), beginFOV, endFOV ); +} + +/* +===================== +idCameraView::Event_GetFOV +===================== +*/ +void idCameraView::Event_GetFOV() +{ + idThread::ReturnFloat(fov.GetCurrentValue(gameLocal.time)); +} +// RAVEN END + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::SetAttachment( idEntity **e, const char *p ) { + const char *cam = spawnArgs.GetString( p ); + if ( strlen ( cam ) ) { + *e = gameLocal.FindEntity( cam ); + } +} + + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::Spawn( void ) { + // if no target specified use ourself + const char *cam = spawnArgs.GetString("cameraTarget"); + if ( strlen ( cam ) == 0) { + spawnArgs.Set("cameraTarget", spawnArgs.GetString("name")); + } +// RAVEN BEGIN +// bdube: interpolate fov +// scork: ... but default from the cvar, not hardwired 90 + fov.Init ( gameLocal.time, 0, spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())), spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())) ); +// RAVEN END + + PostEventMS( &EV_Camera_SetAttachments, 0 ); + + UpdateChangeableSpawnArgs(NULL); +} + +/* +===================== +idCamera::RenderView +===================== +*/ +void idCameraView::GetViewParms( renderView_t *view ) { + assert( view ); + + if (view == NULL) { + return; + } + + idVec3 dir; + idEntity *ent; + + if ( attachedTo ) { + ent = attachedTo; + } else { + ent = this; + } + + view->vieworg = ent->GetPhysics()->GetOrigin(); + if ( attachedView ) { + dir = attachedView->GetPhysics()->GetOrigin() - view->vieworg; + dir.Normalize(); + view->viewaxis = dir.ToMat3(); + } else { + view->viewaxis = ent->GetPhysics()->GetAxis(); + } + +// RAVEN BEGIN +// bdube: interpolate fov + gameLocal.CalcFov( fov.GetCurrentValue( gameLocal.time ), view->fov_x, view->fov_y ); +// RAVEN END +} + + + + + + + + + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/*********************************************************************** + + rvCameraAnimation + +***********************************************************************/ +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( void ) { + frameRate = 0; +} + +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ) { + cameraCuts = anim->cameraCuts; + camera = anim->camera; + frameLookup = anim->frameLookup; + frameCommands = anim->frameCommands; + frameRate = anim->frameRate; + name = anim->name; + realname = anim->realname; +} + +/* +===================== +rvCameraAnimation::~rvCameraAnimation +===================== +*/ +rvCameraAnimation::~rvCameraAnimation( void ) { +} + +/* +===================== +rvCameraAnimation::Name +===================== +*/ +const char *rvCameraAnimation::Name( void ) const { + return name; +} + +/* +===================== +rvCameraAnimation::FullName +===================== +*/ +const char *rvCameraAnimation::FullName( void ) const { + return realname; +} + +/* +===================== +rvCameraAnimation::NumFrames +===================== +*/ +int rvCameraAnimation::NumFrames( void ) const { + return camera.Num(); +} + +/* +===================== +rvCameraAnimation::NumCuts +===================== +*/ +int rvCameraAnimation::NumCuts( void ) const { + return cameraCuts.Num(); +} + +void rvCameraAnimation::SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ) { + int version; + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + int numFrames; + int numCuts; + int i; + + filename.SetFileExtension( MD5_CAMERA_EXT ); + if ( !parser.LoadFile( filename ) ) { + gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); + } + + cameraCuts.Clear(); + cameraCuts.SetGranularity( 1 ); + camera.Clear(); + camera.SetGranularity( 1 ); + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse framerate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate <= 0 ) { + parser.Error( "Invalid framerate: %d", frameRate ); + } + + // parse num cuts + parser.ExpectTokenString( "numCuts" ); + numCuts = parser.ParseInt(); + if ( ( numCuts < 0 ) || ( numCuts > numFrames ) ) { + parser.Error( "Invalid number of camera cuts: %d", numCuts ); + } + + // parse the camera cuts + parser.ExpectTokenString( "cuts" ); + parser.ExpectTokenString( "{" ); + cameraCuts.SetNum( numCuts ); + for( i = 0; i < numCuts; i++ ) { + cameraCuts[ i ] = parser.ParseInt(); + if ( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) { + parser.Error( "Invalid camera cut" ); + } + } + parser.ExpectTokenString( "}" ); + + // parse the camera frames + parser.ExpectTokenString( "camera" ); + parser.ExpectTokenString( "{" ); + camera.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() ); + camera[ i ].fov = parser.ParseFloat(); + } + parser.ExpectTokenString( "}" ); + +#if 0 + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + idDebugGraph gGraph; + idDebugGraph tGraph; + idDebugGraph qGraph; + idDebugGraph dtGraph; + idDebugGraph dqGraph; + gGraph.SetNumSamples( numFrames ); + tGraph.SetNumSamples( numFrames ); + qGraph.SetNumSamples( numFrames ); + dtGraph.SetNumSamples( numFrames ); + dqGraph.SetNumSamples( numFrames ); + + gameLocal.Printf( "\n\ndelta vec:\n" ); + float diff_t, last_t, t; + float diff_q, last_q, q; + diff_t = last_t = 0.0f; + diff_q = last_q = 0.0f; + for( i = 1; i < numFrames; i++ ) { + t = ( camera[ i ].t - camera[ i - 1 ].t ).Length(); + q = ( camera[ i ].q.ToQuat() - camera[ i - 1 ].q.ToQuat() ).Length(); + diff_t = t - last_t; + diff_q = q - last_q; + gGraph.AddValue( ( i % 10 ) == 0 ); + tGraph.AddValue( t ); + qGraph.AddValue( q ); + dtGraph.AddValue( diff_t ); + dqGraph.AddValue( diff_q ); + + gameLocal.Printf( "%d: %.8f : %.8f, %.8f : %.8f\n", i, t, diff_t, q, diff_q ); + last_t = t; + last_q = q; + } + + gGraph.Draw( colorBlue, 300.0f ); + tGraph.Draw( colorOrange, 60.0f ); + dtGraph.Draw( colorYellow, 6000.0f ); + qGraph.Draw( colorGreen, 60.0f ); + dqGraph.Draw( colorCyan, 6000.0f ); +#endif +} + +/* +===================== +rvCameraAnimation::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *rvCameraAnimation::AddFrameCommand( const idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0].c_str() ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { +/* if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token );*/ + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( camera.Num() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > camera.Num() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, name.c_str(), camera.Num() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +/* +===================== +rvCameraAnimation::CallFrameCommandSound +===================== +*/ +void rvCameraAnimation::CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + ent->StartSound ( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} + +/* +===================== +rvCameraAnimation::CallFrameCommands +===================== +*/ +void rvCameraAnimation::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + if ( !frameLookup.Num() ) { + return; + } + + numframes = NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "Cameraframecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect ( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect ( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect ( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_origin, mat3_identity ); + } else { + cent = gameLocal.PlayEffect ( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent && command.joint2 && ent->IsType ( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + cent->SetEndOrigin ( ent->GetAnimator()->GetJointHandle ( *command.joint2 ) ); + } +// RAVEN END + break; + } + case FC_SKIN: +// ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { + break; + } + case FC_FOOTSTEP : { +// ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { +// ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { +// ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { +// ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { +// ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { +// ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { +// ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: +// ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: +// ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: +// ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: +// ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: +// ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: +// ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: +// ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_ACT_ATTACH_SHOW: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_AI_ATTACK_MELEE: +// ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + + + + + + + + + +/*********************************************************************** + + idDeclCameraDef + +***********************************************************************/ + +/* +===================== +idDeclCameraDef::idDeclCameraDef +===================== +*/ +idDeclCameraDef::idDeclCameraDef() { +} + +/* +===================== +idDeclCameraDef::~idDeclCameraDef +===================== +*/ +idDeclCameraDef::~idDeclCameraDef() { + FreeData(); +} + +// RAVEN BEGIN +// jsinger: Added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +/* +===================== +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +===================== +*/ +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::Write( SerialOutputStream &stream ) const +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::AddReferences() const +{ + // not supported yet + assert(false); +} +#endif +// RAVEN END +/* +================= +idDeclCameraDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclCameraDef::Size( void ) const { + + size_t size; + + size = sizeof( idDeclCameraDef ); + size += anims.Allocated(); + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclCameraDef::CopyDecl +===================== +*/ +void idDeclCameraDef::CopyDecl( const idDeclCameraDef *decl ) { + int i; + + FreeData(); + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new rvCameraAnimation( this, decl->anims[ i ] ); + } +} + +/* +===================== +idDeclCameraDef::FreeData +===================== +*/ +void idDeclCameraDef::FreeData( void ) { + anims.DeleteContents( true ); +} + +/* +================ +idDeclCameraDef::DefaultDefinition +================ +*/ +const char *idDeclCameraDef::DefaultDefinition( void ) const { + return "{ }"; +} + + + +/* +===================== +idDeclCameraDef::Touch +===================== +*/ +void idDeclCameraDef::Touch( void ) const { +} + +/* +===================== +idDeclCameraDef::ParseAnim +===================== +*/ +bool idDeclCameraDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + rvCameraAnimation *anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + + numAnims = 0; + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new rvCameraAnimation(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, token ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + return true; +} + +/* +================ +idDeclCameraDef::Parse +================ +*/ +bool idDeclCameraDef::Parse( const char *text, const int textLength, bool noCaching ) { + idStr filename; + idStr extension; + idLexer src; + idToken token; + idToken token2; + int numDefaultAnims; + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + else if ( token == "anim" ) { + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclCameraDef::Validate +===================== +*/ +bool idDeclCameraDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclCameraDef *pSelf = (idDeclCameraDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclCameraDef::HasAnim +===================== +*/ +bool idDeclCameraDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclCameraDef::NumAnims +===================== +*/ +int idDeclCameraDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclCameraDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclCameraDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclCameraDef::GetAnim +===================== +*/ +int idDeclCameraDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + + + + + + + + + + +/* +=============================================================================== + + idCameraAnim + +=============================================================================== +*/ +const idEventDef EV_Camera_Start( "start", NULL ); +const idEventDef EV_Camera_Stop( "stop", NULL ); + +// RAVEN BEGIN +// mekberg: wait support +const idEventDef EV_Camera_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraAnim ) + EVENT( EV_Thread_SetCallback, idCameraAnim::Event_SetCallback ) + EVENT( EV_Camera_Stop, idCameraAnim::Event_Stop ) + EVENT( EV_Camera_Start, idCameraAnim::Event_Start ) + EVENT( EV_Activate, idCameraAnim::Event_Activate ) + + // RAVEN BEGIN + // mekberg: wait support + EVENT( EV_IsActive, idCameraAnim::Event_IsActive ) + // RAVEN END +END_CLASS + + +/* +===================== +idCameraAnim::idCameraAnim +===================== +*/ +idCameraAnim::idCameraAnim() { + threadNum = 0; + offset.Zero(); + cycle = 1; + starttime = 0; + activator = NULL; + lastFrame = -1; +} + +/* +===================== +idCameraAnim::~idCameraAnim +===================== +*/ +idCameraAnim::~idCameraAnim() { + if ( gameLocal.GetCamera() == this ) { + gameLocal.SetCamera( NULL ); + } +} + +/* +=============== +idCameraAnim::Save +================ +*/ +void idCameraAnim::Save( idSaveGame *savefile ) const { + savefile->WriteInt( threadNum ); + savefile->WriteVec3( offset ); + savefile->WriteInt( starttime ); + savefile->WriteInt( cycle ); + savefile->WriteInt( lastFrame ); // cnicholson: Added unsaved var + + activator.Save( savefile ); +} + +/* +=============== +idCameraAnim::Restore +================ +*/ +void idCameraAnim::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( threadNum ); + savefile->ReadVec3( offset ); + savefile->ReadInt( starttime ); + savefile->ReadInt( cycle ); + savefile->ReadInt( lastFrame ); // cnicholson: Added unread var + + activator.Restore( savefile ); + + LoadAnim(); +} + +/* +===================== +idCameraAnim::Spawn +===================== +*/ +void idCameraAnim::Spawn( void ) { + if ( spawnArgs.GetVector( "old_origin", "0 0 0", offset ) ) { + offset = GetPhysics()->GetOrigin() - offset; + } else { + offset.Zero(); + } + + // always think during cinematics + cinematic = true; + + LoadAnim(); + +// RAVEN BEGIN +#ifndef _CONSOLE + // touch the cinematic streaming command file during build + if ( cvarSystem->GetCVarBool("com_makingBuild") && cvarSystem->GetCVarBool("com_Bundler") ) + { + idFile *file; + idStr filename = "cinematics/"; + filename += gameLocal.mapFileNameStripped; + filename += "_"; + filename += name; + filename += ".cincmd"; + file = fileSystem->OpenFileRead( filename ); + fileSystem->CloseFile( file ); + } +#endif +// RAVEN END +} + +/* +================ +idCameraAnim::Load +================ +*/ +void idCameraAnim::LoadAnim( void ) { + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + idStr filename; + const char *key; + + key = spawnArgs.GetString( "camera" ); + const idDecl *mapDecl = declManager->FindType( DECL_CAMERADEF, key, false ); + cameraDef = static_cast( mapDecl ); + + key = spawnArgs.GetString( "anim" ); + if ( !key ) { + gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() ); + } + +} + +/* +=============== +idCameraAnim::Start +================ +*/ +void idCameraAnim::Start( void ) { + cycle = spawnArgs.GetInt( "cycle" ); + if ( !cycle ) { + cycle = 1; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + lastFrame = -1; + starttime = gameLocal.time; + gameLocal.SetCamera( this ); + BecomeActive( TH_THINK ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_BEGIN, cameraDef->GetAnim(1)->GetFrameRate(), GetName() ); +#endif +// RAVEN END + + // if the player has already created the renderview for this frame, have him update it again so that the camera starts this frame +// RAVEN BEGIN +// mekberg: make sure render view is valid. + if ( gameLocal.GetLocalPlayer( )->GetRenderView( ) && gameLocal.GetLocalPlayer()->GetRenderView()->time == gameLocal.time ) { +// RAVEN END + gameLocal.GetLocalPlayer()->CalculateRenderView(); + } +} + +/* +===================== +idCameraAnim::Stop +===================== +*/ +void idCameraAnim::Stop( void ) { + if ( gameLocal.GetCamera() == this ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + + BecomeInactive( TH_THINK ); + gameLocal.SetCamera( NULL ); + if ( threadNum ) { + idThread::ObjectMoveDone( threadNum, this ); + threadNum = 0; + } + ActivateTargets( activator.GetEntity() ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_END, cameraDef->GetAnim(1)->GetFrameRate() ); +#endif +// RAVEN END + } +} + +/* +===================== +idCameraAnim::Think +===================== +*/ +void idCameraAnim::Think( void ) { + int frame; + int frameTime; + + if ( thinkFlags & TH_THINK ) { + // check if we're done in the Think function when the cinematic is being skipped (idCameraAnim::GetViewParms isn't called when skipping cinematics). +// RAVEN BEGIN +// abahr: removed '!' + if ( gameLocal.skipCinematic ) { +// RAVEN END + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() < 2 ) { + // 1 frame anims never end + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + } + + if ( frame > cameraDef->GetAnim(1)->NumFrames() + cameraDef->GetAnim(1)->NumCuts() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + lastFrame = -1; + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + } else { + Stop(); + } + } + } +} + +/* +===================== +idCameraAnim::GetViewParms +===================== +*/ +void idCameraAnim::GetViewParms( renderView_t *view ) { + int realFrame; + int frame; + int frameTime; + float lerp; + float invlerp; + const cameraFrame_t *camFrame; + int i; + int cut; + idQuat q1, q2, q3; + + assert( view ); + if ( !view ) { + return; + } +//RAVEN BEGIN +//jshepard: safety first + if( !cameraDef ) { + gameLocal.Warning("Invalid cameraDef in GetViewParms"); + return; + } +//RAVEN END + if ( !cameraDef->GetAnim(1) ) { + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() == 0 ) { + // we most likely are in the middle of a restore + // FIXME: it would be better to fix it so this doesn't get called during a restore + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + lerp = 0.0f; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + lerp = ( frameTime % 1000 ) * 0.001f; + } + + // skip any frames where camera cuts occur + realFrame = frame; + cut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( frame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + frame++; + cut++; + } + if ( lastFrame != frame ) { + cameraDef->GetAnim(1)->CallFrameCommands( this, lastFrame, frame ); + lastFrame = frame; + } + + if ( g_debugCinematic.GetBool() ) { + int prevFrameTime = ( gameLocal.time - starttime - gameLocal.msec ) * cameraDef->GetAnim(1)->GetFrameRate(); + int prevFrame = prevFrameTime / 1000; + int prevCut; + + prevCut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( prevFrame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + prevFrame++; + prevCut++; + } + + if ( prevCut != cut ) { + gameLocal.Printf( "%d: '%s' cut %d\n", gameLocal.framenum, GetName(), cut ); + } + } + + // clamp to the first frame. also check if this is a one frame anim. one frame anims would end immediately, + // but since they're mainly used for static cams anyway, just stay on it infinitely. + if ( ( frame < 0 ) || ( cameraDef->GetAnim(1)->NumFrames() < 2 ) ) { + view->viewaxis = cameraDef->GetAnim(1)->GetAnim( 0 )->q.ToQuat().ToMat3(); + view->vieworg = cameraDef->GetAnim(1)->GetAnim( 0 )->t + offset; + view->fov_x = cameraDef->GetAnim(1)->GetAnim( 0 )->fov; + } else if ( frame > cameraDef->GetAnim(1)->NumFrames() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + GetViewParms( view ); + return; + } + + Stop(); + if ( gameLocal.GetCamera() != NULL ) { + // we activated another camera when we stopped, so get it's viewparms instead + gameLocal.GetCamera()->GetViewParms( view ); + return; + } else { + // just use our last frame + camFrame = cameraDef->GetAnim(1)->GetAnim( cameraDef->GetAnim(1)->NumFrames() - 1 ); + view->viewaxis = camFrame->q.ToQuat().ToMat3(); + view->vieworg = camFrame->t + offset; + view->fov_x = camFrame->fov; + } + } else if ( lerp == 0.0f ) { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + view->viewaxis = camFrame[ 0 ].q.ToMat3(); + view->vieworg = camFrame[ 0 ].t + offset; + view->fov_x = camFrame[ 0 ].fov; + } else { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + invlerp = 1.0f - lerp; + q1 = camFrame[ 0 ].q.ToQuat(); + q2 = camFrame[ 1 ].q.ToQuat(); + q3.Slerp( q1, q2, lerp ); + view->viewaxis = q3.ToMat3(); + view->vieworg = camFrame[ 0 ].t * invlerp + camFrame[ 1 ].t * lerp + offset; + view->fov_x = camFrame[ 0 ].fov * invlerp + camFrame[ 1 ].fov * lerp; + } + + gameLocal.CalcFov( view->fov_x, view->fov_x, view->fov_y ); + + // setup the pvs for this frame + UpdatePVSAreas( view->vieworg ); + +#if 0 + static int lastFrame = 0; + static idVec3 lastFrameVec( 0.0f, 0.0f, 0.0f ); + if ( gameLocal.time != lastFrame ) { + gameRenderWorld->DebugBounds( colorCyan, idBounds( view->vieworg ).Expand( 16.0f ), vec3_origin, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, view->vieworg, view->vieworg + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + gameRenderWorld->DebugLine( colorCyan, lastFrameVec, view->vieworg, 10000, false ); + gameRenderWorld->DebugLine( colorYellow, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 66.0f, 10000, false ); + gameRenderWorld->DebugLine( colorOrange, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 64.0f + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + lastFrameVec = view->vieworg; + lastFrame = gameLocal.time; + } +#endif + + if ( g_showcamerainfo.GetBool() ) { + gameLocal.Printf( "^5Frame: ^7%d/%d\n\n\n", realFrame + 1, cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ); + } +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_UPDATE, realFrame ); +#endif +} +// RAVEN END + +/* +=============== +idCameraAnim::Event_Activate +================ +*/ +void idCameraAnim::Event_Activate( idEntity *_activator ) { + activator = _activator; + if ( thinkFlags & TH_THINK ) { + Stop(); + } else { + Start(); + } +} + +/* +=============== +idCameraAnim::Event_Start +================ +*/ +void idCameraAnim::Event_Start( void ) { + Start(); +} + +/* +=============== +idCameraAnim::Event_Stop +================ +*/ +void idCameraAnim::Event_Stop( void ) { + Stop(); +} + +/* +================ +idCameraAnim::Event_SetCallback +================ +*/ +void idCameraAnim::Event_SetCallback( void ) { + if ( ( gameLocal.GetCamera() == this ) && !threadNum ) { + threadNum = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// mekberg: wait support +/* +================ +idCameraAnim::Event_IsActive +================ +*/ +void idCameraAnim::Event_IsActive( void ) { + idThread::ReturnFloat( gameLocal.GetCamera( ) ? 1.0f : 0.0f ); +} + +// RAVEN END + +// RAVEN BEGIN +// jscott: portal sky support + +/*********************************************************************** + + rvCameraPortalSky + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPortalSky ) +END_CLASS + +/* +=============== +rvCameraPortalSky::Save +================ +*/ +void rvCameraPortalSky::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPortalSky::Restore +================ +*/ +void rvCameraPortalSky::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPortalSky::Spawn +===================== +*/ +void rvCameraPortalSky::Spawn( void ) +{ + if( gameLocal.GetPortalSky() ) + { + gameLocal.Error( "Only one portal sky camera allowed" ); + } + gameLocal.SetPortalSky( this ); +} + +/* +===================== +rvCameraPortalSky::GetViewParms +===================== +*/ +void rvCameraPortalSky::GetViewParms( renderView_t *view ) +{ + assert( view ); + if( view ) + { + view->vieworg = GetPhysics()->GetOrigin(); + view->viewID = -1; + } +} + + +/*********************************************************************** + + rvCameraPlayback + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPlayback ) +END_CLASS + +/* +=============== +rvCameraPlayback::Save +================ +*/ +void rvCameraPlayback::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPlayback::Restore +================ +*/ +void rvCameraPlayback::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPlayback::Spawn +===================== +*/ +void rvCameraPlayback::Spawn( void ) +{ + startTime = gameLocal.time; + playback = declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); +} + +/* +===================== +rvCameraPlayback::GetViewParms +===================== +*/ +void rvCameraPlayback::GetViewParms( renderView_t *view ) +{ + rvDeclPlaybackData pbd; + + assert( view ); + if( view ) + { + pbd.Init(); + if( declManager->GetPlaybackData( playback, PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, gameLocal.time - startTime, gameLocal.time - startTime, &pbd ) ) + { + startTime = gameLocal.time; + } + + view->vieworg = pbd.GetPosition(); + view->viewaxis = pbd.GetAngles().ToMat3(); + + // field of view +// RAVEN BEGIN +// jshepard: fov as a float for smoove transitions + gameLocal.CalcFov ( g_fov.GetFloat(), view->fov_x, view->fov_y ); + +// RAVEN END + } +} + +// RAVEN END diff --git a/src/game/Camera.h b/src/game/Camera.h new file mode 100644 index 0000000..e9cbb7e --- /dev/null +++ b/src/game/Camera.h @@ -0,0 +1,319 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_CAMERA_H__ +#define __GAME_CAMERA_H__ + +/* +=============================================================================== + +Camera providing an alternative view of the level. + +=============================================================================== +*/ + +class idCamera : public idEntity { +public: + ABSTRACT_PROTOTYPE( idCamera ); + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ) = 0; + virtual renderView_t * GetRenderView(); + virtual void Stop( void ){} ; +}; + +/* +=============================================================================== + +idCameraView + +=============================================================================== +*/ + +extern const idEventDef EV_SetFOV; +extern const idEventDef EV_Camera_Start; +extern const idEventDef EV_Camera_Stop; + +class idCameraView : public idCamera { +public: + CLASS_PROTOTYPE( idCameraView ); + idCameraView(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( ); + virtual void GetViewParms( renderView_t *view ); + virtual void Stop( void ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_SetAttachments(); + void SetAttachment( idEntity **e, const char *p ); +// RAVEN BEGIN +// bdube: changed fov to interpolated value + idInterpolate fov; +// RAVEN END + idEntity *attachedTo; + idEntity *attachedView; + +// RAVEN BEGIN +// bdube: added setfov event + void Event_SetFOV ( float fov ); + void Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ); + void Event_GetFOV ( void ); +// RAVEN END +}; + + + +/* +=============================================================================== + +A camera which follows a path defined by an animation. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/* +============================================================================================== + + rvCameraAnimation + +============================================================================================== +*/ +class idDeclCameraDef; + +typedef struct { + idCQuat q; + idVec3 t; + float fov; +} cameraFrame_t; + +class rvCameraAnimation { +private: + idList cameraCuts; + idList camera; + idList frameLookup; + idList frameCommands; + int frameRate; + idStr name; + idStr realname; + +public: + rvCameraAnimation(); + rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ); + ~rvCameraAnimation(); + + void SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ); + const char *Name( void ) const; + const char *FullName( void ) const; + int NumFrames( void ) const; + const cameraFrame_t * GetAnim( int index ) const; + int NumCuts( void ) const; + const int GetCut( int index ) const; + const int GetFrameRate( void ) const; + + const char *AddFrameCommand( const class idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ); + void CallFrameCommands( idEntity *ent, int from, int to ) const; + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; +}; + +ID_INLINE const cameraFrame_t *rvCameraAnimation::GetAnim( int index ) const { + return &camera[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetCut( int index ) const { + return cameraCuts[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetFrameRate( void ) const { + return frameRate; +} + +/* +============================================================================================== + + idDeclCameraDef + +============================================================================================== +*/ +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclCameraDef : public idDecl, public Serializable<'IDCD'> { +public: + idDeclCameraDef( SerialInputStream &stream ); + + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; +#else +class idDeclCameraDef : public idDecl { +#endif +// RAVEN END +public: + idDeclCameraDef(); + ~idDeclCameraDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + int NumAnims( void ) const; + const rvCameraAnimation * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + +private: + void CopyDecl( const idDeclCameraDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idList anims; +}; + +ID_INLINE const rvCameraAnimation *idDeclCameraDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idCameraAnim + +============================================================================================== +*/ +class idCameraAnim : public idCamera { +public: + CLASS_PROTOTYPE( idCameraAnim ); + + idCameraAnim(); + ~idCameraAnim(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int threadNum; + idVec3 offset; + int starttime; + int cycle; + const idDeclCameraDef *cameraDef; + int lastFrame; + idEntityPtr activator; + + void Start( void ); + void Stop( void ); + void Think( void ); + + void LoadAnim( void ); + void Event_Start( void ); + void Event_Stop( void ); + void Event_SetCallback( void ); + void Event_Activate( idEntity *activator ); + +// RAVEN BEGIN +// mekberg: wait support + void Event_IsActive( ); + + idList imageTable; + idList imageCmds; +// RAVEN END +}; +// RAVEN END + +// RAVEN BEGIN +/* +=============================================================================== + +rvCameraPortalSky + +=============================================================================== +*/ +// jscott: for portal skies +class rvCameraPortalSky : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPortalSky ); + + rvCameraPortalSky( void ) {} + ~rvCameraPortalSky( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); +}; + +/* +=============================================================================== + +rvCameraPlayback + +=============================================================================== +*/ +class rvCameraPlayback : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPlayback ); + + rvCameraPlayback( void ) {} + ~rvCameraPlayback( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int startTime; + const rvDeclPlayback *playback; +}; +// RAVEN END + +#endif /* !__GAME_CAMERA_H__ */ diff --git a/src/game/Effect.cpp b/src/game/Effect.cpp new file mode 100644 index 0000000..71e06c0 --- /dev/null +++ b/src/game/Effect.cpp @@ -0,0 +1,487 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Effect.h" +#include "client/ClientEffect.h" + +const idEventDef EV_LookAtTarget( "lookAtTarget", NULL ); +const idEventDef EV_Attenuate( "attenuate", "f" ); + +CLASS_DECLARATION( idEntity, rvEffect ) + EVENT( EV_Activate, rvEffect::Event_Activate ) + EVENT( EV_LookAtTarget, rvEffect::Event_LookAtTarget ) + EVENT( EV_Earthquake, rvEffect::Event_EarthQuake ) + EVENT( EV_Camera_Start, rvEffect::Event_Start ) + EVENT( EV_Camera_Stop, rvEffect::Event_Stop ) + EVENT( EV_Attenuate, rvEffect::Event_Attenuate ) + EVENT( EV_IsActive, rvEffect::Event_IsActive ) +END_CLASS + +/* +================ +rvEffect::rvEffect +================ +*/ +rvEffect::rvEffect ( void ) { + fl.networkSync = true; + loop = false; + lookAtTarget = false; + effect = NULL; + endOrigin.Zero(); +} + +/* +================ +rvEffect::Spawn +================ +*/ +void rvEffect::Spawn( void ) { + const char* fx; + if ( !spawnArgs.GetString ( "fx", "", &fx ) || !*fx ) { + if ( !( gameLocal.editors & EDITOR_FX ) ) { + gameLocal.Warning ( "no effect file specified on effect entity '%s'", name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + } else { + effect = ( const idDecl * )declManager->FindEffect( spawnArgs.GetString ( "fx" ) ); + if( effect->IsImplicit() ) { + common->Warning( "Unknown effect \'%s\' on entity \'%s\'", spawnArgs.GetString ( "fx" ), GetName() ); + } + } + + spawnArgs.GetVector ( "endOrigin", "0 0 0", endOrigin ); + + spawnArgs.GetBool ( "loop", "0", loop ); + + // If look at target is set the effect will continually update itself to look at its target + spawnArgs.GetBool( "lookAtTarget", "0", lookAtTarget ); + + renderEntity.shaderParms[SHADERPARM_ALPHA] = spawnArgs.GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[SHADERPARM_BRIGHTNESS] = spawnArgs.GetFloat ( "_brightness", "1" ); + + if( spawnArgs.GetBool( "start_on", loop ? "1" : "0" ) ) { + ProcessEvent( &EV_Activate, this ); + } +#if 0 + // If anyone ever gets around to a flood fill from the origin rather than the over generous PushVolumeIntoTree bounds, + // this warning will become useful. Until then, it's a bogus warning. + if( gameRenderWorld->PointInArea( GetPhysics()->GetOrigin() ) < 0 ) { + common->Warning( "Effect \'%s\' out of world", name.c_str() ); + } +#endif +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Think( void ) { + + if( clientEntities.IsListEmpty ( ) ) { + BecomeInactive( TH_THINK ); + + // Should the func_fx be removed now? + if( !(gameLocal.editors & EDITOR_FX) && spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + return; + } + else if( lookAtTarget ) { + // If activated and looking at its target then update the target information + ProcessEvent( &EV_LookAtTarget ); + } + + UpdateVisuals(); + Present ( ); +} + +/* +================ +rvEffect::Save +================ +*/ +void rvEffect::Save( idSaveGame *savefile ) const { + savefile->WriteBool( loop ); + savefile->WriteBool( lookAtTarget ); + savefile->WriteString( effect->GetName() ); + savefile->WriteVec3( endOrigin ); + clientEffect.Save( savefile ); +} + +/* +================ +rvEffect::Restore +================ +*/ +void rvEffect::Restore( idRestoreGame *savefile ) { + idStr name; + + savefile->ReadBool( loop ); + savefile->ReadBool( lookAtTarget ); + savefile->ReadString( name ); + effect = declManager->FindType( DECL_EFFECT, name ); + savefile->ReadVec3( endOrigin ); + clientEffect.Restore( savefile ); +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Stop( bool destroyParticles ) { + StopEffect ( effect, destroyParticles ); +} + +/* +================ +rvEffect::Play +================ +*/ +bool rvEffect::Play( void ) { + clientEffect = PlayEffect ( effect, renderEntity.origin, renderEntity.axis, loop, endOrigin ); + if ( clientEffect ) { + + idVec4 color; + color[0] = renderEntity.shaderParms[SHADERPARM_RED]; + color[1] = renderEntity.shaderParms[SHADERPARM_GREEN]; + color[2] = renderEntity.shaderParms[SHADERPARM_BLUE]; + color[3] = renderEntity.shaderParms[SHADERPARM_ALPHA]; + clientEffect->SetColor ( color ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + clientEffect->SetAmbient( true ); + + BecomeActive ( TH_THINK ); + return true; + } + + return false; +} + +/* +================ +rvEffect::Attenuate +================ +*/ +void rvEffect::Attenuate ( float attenuation ) { + rvClientEntity* cent; + for( cent = clientEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { +// RAVEN END + static_cast(cent)->Attenuate ( attenuation ); + } + } +} + +/* +================ +rvEffect::Restart +================ +*/ +void rvEffect::Restart( void ) { + Stop( false ); + + if( loop ) { + Play(); + } +} + +/* +================ +rvEffect::UpdateChangeableSpawnArgs +================ +*/ +void rvEffect::UpdateChangeableSpawnArgs( const idDict *source ) { + const char* fx; + const idDecl *newEffect; + bool newLoop; + + idEntity::UpdateChangeableSpawnArgs(source); + if ( !source ) { + return; + } + + if ( source->GetString ( "fx", "", &fx ) && *fx ) { + newEffect = ( const idDecl * )declManager->FindEffect( fx ); + } else { + newEffect = NULL; + } + + idVec3 color; + source->GetVector( "_color", "1 1 1", color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = source->GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] = source->GetFloat ( "_brightness", "1" ); + if ( clientEffect ) { + clientEffect->SetColor ( idVec4(color[0],color[1],color[2],renderEntity.shaderParms[ SHADERPARM_ALPHA ]) ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + } + + source->GetBool ( "loop", "0", newLoop ); + + spawnArgs.Copy( *source ); + + // IF the effect handle has changed or the loop status has changed then restart the effect + if ( newEffect != effect || loop != newLoop ) { + Stop ( false ); + + loop = newLoop; + effect = newEffect; + + if ( effect ) { + Play ( ); + BecomeActive( TH_THINK ); + UpdateVisuals(); + } else { + BecomeInactive ( TH_THINK ); + UpdateVisuals(); + } + } +} + +/* +=============== +rvEffect::ShowEditingDialog +=============== +*/ +void rvEffect::ShowEditingDialog( void ) { + common->InitTool( EDITOR_FX, &spawnArgs ); +} + +/* +================= +rvEffect::WriteToSnapshot +================= +*/ +void rvEffect::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteBits( loop, 1 ); +} + +/* +================= +rvEffect::ReadFromSnapshot +================= +*/ +void rvEffect::ReadFromSnapshot( const idBitMsgDelta &msg ) { + const idDecl *old = effect; + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + loop = ( msg.ReadBits( 1 ) != 0 ); + + if ( effect && !old ) { + // TODO: need to account for when the effect really started + Play(); + } +} + +/* +================= +rvEffect::ClientPredictionThink +================= +*/ +void rvEffect::ClientPredictionThink( void ) { + if ( gameLocal.isNewFrame ) { + Think ( ); + } + RunPhysics(); + Present(); +} + +/* +================ +rvEffect::Event_Start +================ +*/ +void rvEffect::Event_Start ( void ) { + if( !effect || !clientEntities.IsListEmpty ( ) ) { + return; + } + + if( !Play() ) { + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + } else { + gameLocal.Warning( "Unable to play effect '%s'", effect->GetName() ); + } + BecomeInactive ( TH_THINK ); + } + + ProcessEvent( &EV_LookAtTarget ); +} + +/* +================ +rvEffect::Event_Stop +================ +*/ +void rvEffect::Event_Stop ( void ) { + if( !effect ) { + return; + } + + Stop( false ); +} + +/* +================= +rvEffect::Event_Activate +================= +*/ +void rvEffect::Event_Activate( idEntity *activator ) { + // Stop the effect if its already playing + if( !clientEntities.IsListEmpty ( ) ) { + Event_Stop ( ); + } else { + Event_Start ( ); + } + + ActivateTargets( activator ); +} + +/* +================ +rvEffect::Event_LookAtTarget + +Reorients the effect entity towards its target and sets the end origin as well +================ +*/ +void rvEffect::Event_LookAtTarget ( void ) { + const idKeyValue *kv; + idVec3 dir; + + if ( !effect || !clientEffect ) { + return; + } + + kv = spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if( ent ) { + if( !idStr::Icmp( ent->GetEntityDefName(), "target_null" ) ) { + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + + clientEffect->SetEndOrigin ( ent->GetPhysics()->GetOrigin() ); + clientEffect->SetAxis ( dir.ToMat3( ) ); + return; + } + } + kv = spawnArgs.MatchPrefix( "target", kv ); + } +} + +/* +================ +rvEffect::Event_EarthQuake +================ +*/ +void rvEffect::Event_EarthQuake ( float requiresLOS ) { + float quakeChance; + + if ( !spawnArgs.GetFloat("quakeChance", "0", quakeChance) ) { + return; + } + + if ( rvRandom::flrand(0, 1.0f) > quakeChance ) { + // failed its activation roll + return; + } + + if ( requiresLOS ) { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + + // activate this effect now + ProcessEvent ( &EV_Activate, gameLocal.entities[ENTITYNUM_WORLD] ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_Attenuate( float attenuation ) { + Attenuate( attenuation ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_IsActive( void ) { + idThread::ReturnFloat( ( !effect || !clientEntities.IsListEmpty() ) ? 0.0f : 1.0f ); +} + +/* +================ +rvEffect::InstanceLeave +================ +*/ +void rvEffect::InstanceLeave( void ) { + idEntity::InstanceLeave(); + Stop( true ); +} + +/* +================ +rvEffect::InstanceJoin +================ +*/ +void rvEffect::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + Restart(); +} \ No newline at end of file diff --git a/src/game/Effect.h b/src/game/Effect.h new file mode 100644 index 0000000..ea74eb5 --- /dev/null +++ b/src/game/Effect.h @@ -0,0 +1,90 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Effect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_EFFECT_H__ +#define __GAME_EFFECT_H__ + +class rvEffect : public idEntity +{ +public: + + CLASS_PROTOTYPE( rvEffect ); + + rvEffect ( void ); + + const bool GetEndOrigin ( idVec3 &result ) const; + void SetEndOrigin ( const idVec3 &origin ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Play ( void ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + void Attenuate ( float attenuation ); + + float GetBrightness ( void ) const; + + bool IsLooping ( void ) { return( loop ); } + + virtual void UpdateChangeableSpawnArgs ( const idDict *source ); + virtual void ShowEditingDialog ( void ); + + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + void ClientPredictionThink ( void ); + virtual void InstanceLeave ( void ); + virtual void InstanceJoin ( void ); + +protected: + + bool loop; + bool lookAtTarget; + const idDecl *effect; + idVec3 endOrigin; + rvClientEntityPtr clientEffect; + +private: + + void Event_Activate ( idEntity *activator ); + void Event_LookAtTarget ( void ); + void Event_EarthQuake ( float requiresLOS ); + + void Event_Start ( void ); + void Event_Stop ( void ); + + void Event_Attenuate ( float attenuation ); + void Event_IsActive ( void ); +}; + +#endif // __GAME_EFFECT_H__ diff --git a/src/game/Entity.cpp b/src/game/Entity.cpp new file mode 100644 index 0000000..ac75a7f --- /dev/null +++ b/src/game/Entity.cpp @@ -0,0 +1,6813 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// bdube: client effects +#include "client/ClientEffect.h" +//mcg: need to know team for AddDamageEffects +#include "ai/AI_Manager.h" +// RAVEN END + +/* +=============================================================================== + + idEntity + +=============================================================================== +*/ + +// overridable events +const idEventDef EV_PostSpawn( "", NULL ); +const idEventDef EV_FindTargets( "", NULL ); +const idEventDef EV_Touch( "", "et" ); +const idEventDef EV_GetName( "getName", NULL, 's' ); +const idEventDef EV_SetName( "setName", "s" ); +const idEventDef EV_Activate( "activate", "e" ); +const idEventDef EV_ActivateTargets( "activateTargets", "e" ); +const idEventDef EV_NumTargets( "numTargets", NULL, 'f' ); +const idEventDef EV_GetTarget( "getTarget", "f", 'e' ); +const idEventDef EV_RandomTarget( "randomTarget", "s", 'e' ); +const idEventDef EV_Bind( "bind", "e" ); +const idEventDef EV_BindPosition( "bindPosition", "e" ); +const idEventDef EV_BindToJoint( "bindToJoint", "esf" ); +const idEventDef EV_Unbind( "unbind", NULL ); +const idEventDef EV_RemoveBinds( "removeBinds" ); +const idEventDef EV_SpawnBind( "", NULL ); +const idEventDef EV_SetOwner( "setOwner", "e" ); +const idEventDef EV_SetModel( "setModel", "s" ); +const idEventDef EV_SetSkin( "setSkin", "s" ); +const idEventDef EV_GetWorldOrigin( "getWorldOrigin", NULL, 'v' ); +const idEventDef EV_SetWorldOrigin( "setWorldOrigin", "v" ); +const idEventDef EV_GetOrigin( "getOrigin", NULL, 'v' ); +const idEventDef EV_SetOrigin( "setOrigin", "v" ); +const idEventDef EV_GetAngles( "getAngles", NULL, 'v' ); +const idEventDef EV_SetAngles( "setAngles", "v" ); +const idEventDef EV_GetLinearVelocity( "getLinearVelocity", NULL, 'v' ); +const idEventDef EV_SetLinearVelocity( "setLinearVelocity", "v" ); +const idEventDef EV_GetAngularVelocity( "getAngularVelocity", NULL, 'v' ); +const idEventDef EV_SetAngularVelocity( "setAngularVelocity", "v" ); +const idEventDef EV_GetSize( "getSize", NULL, 'v' ); +const idEventDef EV_SetSize( "setSize", "vv" ); +const idEventDef EV_GetMins( "getMins", NULL, 'v' ); +const idEventDef EV_GetMaxs( "getMaxs", NULL, 'v' ); +const idEventDef EV_IsHidden( "isHidden", NULL, 'd' ); +const idEventDef EV_Hide( "hide", NULL ); +const idEventDef EV_Show( "show", NULL ); +const idEventDef EV_Touches( "touches", "E", 'd' ); +const idEventDef EV_ClearSignal( "clearSignal", "d" ); +const idEventDef EV_GetShaderParm( "getShaderParm", "d", 'f' ); +const idEventDef EV_SetShaderParm( "setShaderParm", "df" ); +const idEventDef EV_SetShaderParms( "setShaderParms", "ffff" ); +const idEventDef EV_SetColor( "setColor", "fff" ); +const idEventDef EV_GetColor( "getColor", NULL, 'v' ); +const idEventDef EV_CacheSoundShader( "cacheSoundShader", "s" ); +const idEventDef EV_StartSoundShader( "startSoundShader", "sd", 'f' ); +const idEventDef EV_StartSound( "startSound", "sdd", 'f' ); +const idEventDef EV_StopSound( "stopSound", "dd" ); +const idEventDef EV_FadeSound( "fadeSound", "dff" ); +const idEventDef EV_SetGuiParm( "setGuiParm", "ss" ); +const idEventDef EV_SetGuiFloat( "setGuiFloat", "sf" ); +const idEventDef EV_GetNextKey( "getNextKey", "ss", 's' ); +const idEventDef EV_SetKey( "setKey", "ss" ); +const idEventDef EV_GetKey( "getKey", "s", 's' ); +const idEventDef EV_GetIntKey( "getIntKey", "s", 'f' ); +const idEventDef EV_GetFloatKey( "getFloatKey", "s", 'f' ); +const idEventDef EV_GetVectorKey( "getVectorKey", "s", 'v' ); +const idEventDef EV_GetEntityKey( "getEntityKey", "s", 'e' ); +const idEventDef EV_RestorePosition( "restorePosition" ); +const idEventDef EV_UpdateCameraTarget( "", NULL ); +const idEventDef EV_DistanceTo( "distanceTo", "E", 'f' ); +const idEventDef EV_DistanceToPoint( "distanceToPoint", "v", 'f' ); +const idEventDef EV_StartFx( "startFx", "s" ); +const idEventDef EV_HasFunction( "hasFunction", "s", 'd' ); +const idEventDef EV_CallFunction( "callFunction", "s" ); +const idEventDef EV_SetNeverDormant( "setNeverDormant", "d" ); + +// RAVEN BEGIN +// bgeisler: go back to default skin +const idEventDef EV_ClearSkin( "clearSkin"); +// kfuller: added events +const idEventDef EV_SetContents( "setContents", "d" ); +const idEventDef EV_GetLastBlocker( "getLastBlocker", NULL, 'e' ); +const idEventDef EV_Earthquake( "earthquake", "f" ); +// we should probably try to integrate this with AI_PlayAnim +const idEventDef EV_PlayAnim("playAnimNoChannel", "s"); +const idEventDef EV_PlayAnimXTimes("playAnimXTimes", "sf"); +// bdube: effect events +const idEventDef EV_PlayEffect( "playEffect", "ssd" ); +const idEventDef EV_StopEffect( "stopEffect", "s" ); +const idEventDef EV_StopAllEffects( "stopAllEffects" ); +const idEventDef EV_GetHealth ( "getHealth", NULL, 'f' ); +// bdube: surface related events +const idEventDef EV_HideSurface( "hideSurface", "s" ); +const idEventDef EV_ShowSurface( "showSurface", "s" ); +// bdube: added gui events +const idEventDef EV_GuiEvent ( "guiEvent", "s" ); +// jscott: for playback button handling +const idEventDef EV_PlaybackCallback( "playbackCallback", "ddd" ); +// nmckenzie: +const idEventDef EV_GetBindMaster( "getBindMaster", NULL, 'e' ); +const idEventDef EV_ApplyImpulse( "applyImpulse", "evv" ); +// abahr: +const idEventDef EV_RemoveNullTargets( "removeNullTargets" ); +const idEventDef EV_IsA( "isA", "s", 'f' ); +const idEventDef EV_IsSameTypeAs( "isSameTypeAs", "e", 'f' ); +const idEventDef EV_MatchPrefix( "matchPrefix", "ss", 's' ); +const idEventDef EV_ClearTargetList( "clearTargetList", "f" ); +// twhitaker: +const idEventDef EV_AppendTarget( "appendTarget", "E", 'f' ); +const idEventDef EV_RemoveTarget( "removeTarget", "e" ); +// mekberg: +const idEventDef EV_SetHealth( "setHealth", "f" ); +// RAVEN END + +ABSTRACT_DECLARATION( idClass, idEntity ) + EVENT( EV_GetName, idEntity::Event_GetName ) + EVENT( EV_SetName, idEntity::Event_SetName ) + EVENT( EV_FindTargets, idEntity::Event_FindTargets ) + EVENT( EV_ActivateTargets, idEntity::Event_ActivateTargets ) + EVENT( EV_NumTargets, idEntity::Event_NumTargets ) + EVENT( EV_GetTarget, idEntity::Event_GetTarget ) + EVENT( EV_RandomTarget, idEntity::Event_RandomTarget ) + EVENT( EV_BindToJoint, idEntity::Event_BindToJoint ) + EVENT( EV_RemoveBinds, idEntity::Event_RemoveBinds ) + EVENT( EV_Bind, idEntity::Event_Bind ) + EVENT( EV_BindPosition, idEntity::Event_BindPosition ) + EVENT( EV_Unbind, idEntity::Event_Unbind ) + EVENT( EV_SpawnBind, idEntity::Event_SpawnBind ) + EVENT( EV_SetOwner, idEntity::Event_SetOwner ) + EVENT( EV_SetModel, idEntity::Event_SetModel ) + EVENT( EV_SetSkin, idEntity::Event_SetSkin ) + EVENT( EV_GetShaderParm, idEntity::Event_GetShaderParm ) + EVENT( EV_SetShaderParm, idEntity::Event_SetShaderParm ) + EVENT( EV_SetShaderParms, idEntity::Event_SetShaderParms ) + EVENT( EV_SetColor, idEntity::Event_SetColor ) + EVENT( EV_GetColor, idEntity::Event_GetColor ) + EVENT( EV_IsHidden, idEntity::Event_IsHidden ) + EVENT( EV_Hide, idEntity::Event_Hide ) + EVENT( EV_Show, idEntity::Event_Show ) + EVENT( EV_CacheSoundShader, idEntity::Event_CacheSoundShader ) + EVENT( EV_StartSoundShader, idEntity::Event_StartSoundShader ) + EVENT( EV_StartSound, idEntity::Event_StartSound ) + EVENT( EV_StopSound, idEntity::Event_StopSound ) + EVENT( EV_FadeSound, idEntity::Event_FadeSound ) + EVENT( EV_GetWorldOrigin, idEntity::Event_GetWorldOrigin ) + EVENT( EV_SetWorldOrigin, idEntity::Event_SetWorldOrigin ) + EVENT( EV_GetOrigin, idEntity::Event_GetOrigin ) + EVENT( EV_SetOrigin, idEntity::Event_SetOrigin ) + EVENT( EV_GetAngles, idEntity::Event_GetAngles ) + EVENT( EV_SetAngles, idEntity::Event_SetAngles ) + EVENT( EV_GetLinearVelocity, idEntity::Event_GetLinearVelocity ) + EVENT( EV_SetLinearVelocity, idEntity::Event_SetLinearVelocity ) + EVENT( EV_GetAngularVelocity, idEntity::Event_GetAngularVelocity ) + EVENT( EV_SetAngularVelocity, idEntity::Event_SetAngularVelocity ) + EVENT( EV_GetSize, idEntity::Event_GetSize ) + EVENT( EV_SetSize, idEntity::Event_SetSize ) + EVENT( EV_GetMins, idEntity::Event_GetMins) + EVENT( EV_GetMaxs, idEntity::Event_GetMaxs ) + EVENT( EV_Touches, idEntity::Event_Touches ) + EVENT( EV_SetGuiParm, idEntity::Event_SetGuiParm ) + EVENT( EV_SetGuiFloat, idEntity::Event_SetGuiFloat ) + EVENT( EV_GetNextKey, idEntity::Event_GetNextKey ) + EVENT( EV_SetKey, idEntity::Event_SetKey ) + EVENT( EV_GetKey, idEntity::Event_GetKey ) + EVENT( EV_GetIntKey, idEntity::Event_GetIntKey ) + EVENT( EV_GetFloatKey, idEntity::Event_GetFloatKey ) + EVENT( EV_GetVectorKey, idEntity::Event_GetVectorKey ) + EVENT( EV_GetEntityKey, idEntity::Event_GetEntityKey ) + EVENT( EV_RestorePosition, idEntity::Event_RestorePosition ) + EVENT( EV_UpdateCameraTarget, idEntity::Event_UpdateCameraTarget ) + EVENT( EV_DistanceTo, idEntity::Event_DistanceTo ) + EVENT( EV_DistanceToPoint, idEntity::Event_DistanceToPoint ) + EVENT( EV_StartFx, idEntity::Event_StartFx ) + EVENT( EV_Thread_WaitFrame, idEntity::Event_WaitFrame ) + EVENT( EV_Thread_Wait, idEntity::Event_Wait ) + EVENT( EV_HasFunction, idEntity::Event_HasFunction ) + EVENT( EV_CallFunction, idEntity::Event_CallFunction ) + EVENT( EV_SetNeverDormant, idEntity::Event_SetNeverDormant ) + +// RAVEN BEGIN +// bgeisler: go back to default skin + EVENT( EV_ClearSkin, idEntity::Event_ClearSkin ) +// kfuller: added events + EVENT( EV_SetContents, idEntity::Event_SetContents ) + EVENT( EV_GetLastBlocker, idEntity::Event_GetLastBlocker) +// bdube: effect events + EVENT( EV_PlayEffect, idEntity::Event_PlayEffect ) + EVENT( EV_StopEffect, idEntity::Event_StopEffect ) + EVENT( EV_StopAllEffects, idEntity::Event_StopAllEffects ) + EVENT( EV_GetHealth, idEntity::Event_GetHealth ) +// bdube: mesh events + EVENT( EV_HideSurface, idEntity::Event_HideSurface ) + EVENT( EV_ShowSurface, idEntity::Event_ShowSurface ) +// bdube: gui events + EVENT( EV_GuiEvent, idEntity::Event_GuiEvent ) +// jscott: playback callback + EVENT( EV_PlaybackCallback, idEntity::Event_PlaybackCallback ) +// nmckenzie: Check who we're bound to. + EVENT( EV_GetBindMaster, idEntity::Event_GetBindMaster ) + EVENT( EV_ApplyImpulse, idEntity::Event_ApplyImpulse ) +// abahr: so we can call this from script + EVENT( EV_RemoveNullTargets, idEntity::Event_RemoveNullTargets ) + EVENT( EV_IsA, idEntity::Event_IsA ) + EVENT( EV_IsSameTypeAs, idEntity::Event_IsSameTypeAs ) + EVENT( EV_MatchPrefix, idEntity::Event_MatchPrefix ) + EVENT( EV_ClearTargetList, idEntity::Event_ClearTargetList ) +// twhitaker: to dynamically add/remove targets in script + EVENT( EV_AppendTarget, idEntity::Event_AppendTarget ) + EVENT( EV_RemoveTarget, idEntity::Event_RemoveTarget ) +// mekberg: added + EVENT( EV_SetHealth, idEntity::Event_SetHealth ) +// RAVEN END +END_CLASS + +/* +================ +UpdateGuiParms +================ +*/ +void UpdateGuiParms( idUserInterface *gui, const idDict *args ) { + if ( gui == NULL || args == NULL ) { + return; + } + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + while( kv ) { + gui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = args->MatchPrefix( "gui_parm", kv ); + } + gui->SetStateBool( "noninteractive", args->GetBool( "gui_noninteractive" ) ) ; + gui->StateChanged( gameLocal.time ); +} + +/* +================ +AddRenderGui +================ +*/ +void AddRenderGui( const char *name, idUserInterface **gui, const idDict *args ) { + + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + *gui = uiManager->FindGui( name, true, ( kv != NULL ) || args->GetBool( "gui_noninteractive" ) ); + UpdateGuiParms( *gui, args ); +} + +/* +================ +idGameEdit::ParseSpawnArgsToRenderEntity + +parse the static model parameters +this is the canonical renderEntity parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ) { + int i; + const char *temp; + idVec3 color; + float angle; + const idDeclModelDef *modelDef; + + memset( renderEntity, 0, sizeof( *renderEntity ) ); + + temp = args->GetString( "model" ); + + modelDef = NULL; + if ( temp[0] != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + renderEntity->hModel = modelDef->ModelHandle(); + if ( renderEntity->hModel && !renderEntity->hModel->IsLoaded() ) { + renderEntity->hModel->LoadModel(); + } + } + } + + if ( !renderEntity->hModel ) { + renderEntity->hModel = renderModelManager->FindModel( temp ); + } + } + if ( renderEntity->hModel ) { + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + } else { + renderEntity->bounds.Zero(); + } + + temp = args->GetString( "skin" ); + if ( temp[0] != '\0' ) { + renderEntity->customSkin = declManager->FindSkin( temp ); + } else if ( modelDef ) { + renderEntity->customSkin = modelDef->GetDefaultSkin(); + } + + temp = args->GetString( "shader" ); + if ( temp[0] != '\0' ) { + renderEntity->customShader = declManager->FindMaterial( temp ); + } + + args->GetVector( "origin", "0 0 0", renderEntity->origin ); + + // get the rotation matrix in either full form, or single angle form + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", renderEntity->axis ) ) { + angle = args->GetFloat( "angle" ); +// RAVEN BEGIN +// abahr: allowing up and down buttons to affect orientation + if( angle == -1.0f ) { + renderEntity->axis = idAngles( -90.0f, 0.0f, 0.0f ).ToMat3(); + } else if( angle == -2.0f ) { + renderEntity->axis = idAngles( 90.0f, 0.0f, 0.0f ).ToMat3(); + } else +// RAVEN END + if ( angle != 0.0f ) { + renderEntity->axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } else { + renderEntity->axis.Identity(); + } + } + +// RAVEN BEGIN + renderEntity->referenceSoundHandle = -1; +// RAVEN END + + // get shader parms + args->GetVector( "_color", "1 1 1", color ); + renderEntity->shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity->shaderParms[ 3 ] = args->GetFloat( "shaderParm3", "1" ); + renderEntity->shaderParms[ 4 ] = args->GetFloat( "shaderParm4", "0" ); + renderEntity->shaderParms[ 5 ] = args->GetFloat( "shaderParm5", "0" ); + renderEntity->shaderParms[ 6 ] = args->GetFloat( "shaderParm6", "0" ); + renderEntity->shaderParms[ 7 ] = args->GetFloat( "shaderParm7", "0" ); + renderEntity->shaderParms[ 8 ] = args->GetFloat( "shaderParm8", "0" ); + renderEntity->shaderParms[ 9 ] = args->GetFloat( "shaderParm9", "0" ); + renderEntity->shaderParms[ 10 ] = args->GetFloat( "shaderParm10", "0" ); + renderEntity->shaderParms[ 11 ] = args->GetFloat( "shaderParm11", "0" ); + + // check noDynamicInteractions flag + renderEntity->noDynamicInteractions = args->GetBool( "noDynamicInteractions" ); + + // check noshadows flag + renderEntity->noShadow = args->GetBool( "noshadows" ); + + // check noselfshadows flag + renderEntity->noSelfShadow = args->GetBool( "noselfshadows" ); + + // init any guis, including entity-specific states + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + temp = args->GetString( i == 0 ? "gui" : va( "gui%d", i + 1 ) ); + if ( temp[ 0 ] != '\0' ) { + AddRenderGui( temp, &renderEntity->gui[ i ], args ); + } + } +} + +/* +================ +idGameEdit::ParseSpawnArgsToRefSound + +parse the sound parameters +this is the canonical refSound parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ) { + const char *temp; + + memset( refSound, 0, sizeof( *refSound ) ); + refSound->referenceSoundHandle = -1; + +// RAVEN BEGIN + refSound->parms.minDistance = args->GetFloat( "s_mindistance" ); + refSound->parms.maxDistance = args->GetFloat( "s_maxdistance" ); + // WARNING: This overrides the volume; it does not modify it + if( args->GetFloat( "s_volume" ) != 0.0f ) { + refSound->parms.volume = idMath::dBToScale( args->GetFloat( "s_volume" ) ); + } + + if( refSound->parms.volume < 0.0f || refSound->parms.volume > 5.0f ) { + common->Warning( "Unreasonable volume (%g) on entity \'%s\'", refSound->parms.volume, args->GetString( "name" ) ); + refSound->parms.volume = 5.0f; + } +// RAVEN END + refSound->parms.shakes = args->GetFloat( "s_shakes" ); + + args->GetVector( "origin", "0 0 0", refSound->origin ); + + // if a diversity is not specified, every sound start will make + // a random one. Specifying diversity is usefull to make multiple + // lights all share the same buzz sound offset, for instance. + refSound->diversity = args->GetFloat( "s_diversity", "-1" ); + refSound->waitfortrigger = args->GetBool( "s_waitfortrigger" ); + + if ( args->GetBool( "s_omni" ) ) { + refSound->parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; + } + if ( args->GetBool( "s_looping" ) ) { + refSound->parms.soundShaderFlags |= SSF_LOOPING; + } + if ( args->GetBool( "s_occlusion" ) ) { + refSound->parms.soundShaderFlags |= SSF_NO_OCCLUSION; + } + if ( args->GetBool( "s_global" ) ) { + refSound->parms.soundShaderFlags |= SSF_GLOBAL; + } + if ( args->GetBool( "s_unclamped" ) ) { + refSound->parms.soundShaderFlags |= SSF_UNCLAMPED; + } + if ( args->GetBool( "s_center" ) ) { + refSound->parms.soundShaderFlags |= SSF_CENTER; + } + + refSound->parms.soundClass = args->GetInt( "s_soundClass" ); + + temp = args->GetString( "s_shader" ); + if ( temp[0] != '\0' ) { + refSound->shader = declManager->FindSound( temp ); + } + +// RAVEN BEGIN + if( refSound->parms.maxDistance < refSound->parms.minDistance ) { + common->Warning( "ParseSpawnArgsToRefSound: Max distance less than min distance for entity \'%s\'", args->GetString( "name", "*unknown*" ) ); + } +// RAVEN END +} + +/* +=============== +idEntity::UpdateChangeableSpawnArgs + +Any key val pair that might change during the course of the game ( via a gui or whatever ) +should be initialize here so a gui or other trigger can change something and have it updated +properly. An optional source may be provided if the values reside in an outside dictionary and +first need copied over to spawnArgs +=============== +*/ +void idEntity::UpdateChangeableSpawnArgs( const idDict *source ) { + int i; + const char *target; + + if ( !source ) { + source = &spawnArgs; + } + cameraTarget = NULL; + target = source->GetString( "cameraTarget" ); + if ( target && target[0] ) { +// RAVEN BEGIN +// bdube: EV_UpdateCameraTarget pulls from spawnargs so we need to move the target over + spawnArgs.Set ( "cameraTarget", target ); +// RAVEN END + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], source ); + } +} + +/* +================ +idEntity::idEntity +================ +*/ +idEntity::idEntity() { + + entityNumber = ENTITYNUM_NONE; + entityDefNumber = -1; + + spawnNode.SetOwner( this ); + activeNode.SetOwner( this ); + + snapshotNode.SetOwner( this ); + snapshotSequence = -1; + snapshotBits = 0; + + thinkFlags = 0; + dormantStart = 0; + cinematic = false; + renderView = NULL; + cameraTarget = NULL; + health = 0; + + physics = NULL; + bindMaster = NULL; + bindJoint = INVALID_JOINT; + bindBody = -1; + teamMaster = NULL; + teamChain = NULL; + signals = NULL; + + memset( PVSAreas, 0, sizeof( PVSAreas ) ); + numPVSAreas = -1; + + memset( &fl, 0, sizeof( fl ) ); + fl.neverDormant = true; // most entities never go dormant + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + modelDefHandle = -1; + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + + mpGUIState = -1; + +// RAVEN BEGIN +// rjohnson: added this to persist long thinking entities + mLastLongThinkTime = 0; + mLastLongThinkColor.Zero(); +// ddynerman: instance, clipworld + SetInstance( 0 ); + SetClipWorld( 0 ); + fl.persistAcrossInstances = false; +// twhitaker + forwardDamageEnt = NULL; +// ddynerman: optional preprediction + predictTime = 0; +// RAVEN END +} + +/* +================ +idEntity::Spawn +================ +*/ +void idEntity::Spawn( void ) { + int i; + const char *temp; + idVec3 origin; + idMat3 axis; + const idKeyValue *networkSync; + const char *classname; + const char *scriptObjectName; + + gameLocal.RegisterEntity( this ); + +// bdube: make sure there is a classname before trying to use it + if ( spawnArgs.GetString( "classname", NULL, &classname ) ) { + const idDeclEntityDef *def = gameLocal.FindEntityDef( classname, false ); + if ( def ) { + entityDefNumber = def->Index(); + } + } + + // Persona is a set of keys that augment an entity giving it its own custom persona + const idDict* dict; + dict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_persona", "" ), false ); + if ( dict ) { + spawnArgs.Copy ( *dict ); + } +// RAVEN END + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + +// RAVEN BEGIN +// bdube: added hidesurface + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } +// RAVEN END + + renderEntity.entityNum = entityNumber; + +// RAVEN BEGIN +// ddynerman: LOD code + renderEntity.shadowLODDistance = spawnArgs.GetFloat( "shadow_lod_distance", "768.0" ); + renderEntity.shadowLODDistance *= renderEntity.shadowLODDistance; +// ddynerman: multiple clip worlds + int spawnInstance = spawnArgs.GetInt( "instance" ); + SetInstance( spawnInstance ); +// RAVEN END + + // go dormant within 5 frames so that when the map starts most monsters are dormant + dormantStart = gameLocal.time - DELAY_DORMANT_TIME + gameLocal.msec * 5; + + origin = renderEntity.origin; + axis = renderEntity.axis; + + // do the audio parsing the same way dmap and the editor do + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + // only play SCHANNEL_PRIVATE when sndworld->PlaceListener() is called with this listenerId + // don't spatialize sounds from the same entity + refSound.listenerId = entityNumber + 1; + + cameraTarget = NULL; + temp = spawnArgs.GetString( "cameraTarget" ); + if ( temp && temp[0] ) { + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], &spawnArgs ); + } + + fl.solidForTeam = spawnArgs.GetBool( "solidForTeam", "0" ); +// RAVEN BEGIN +// bdube: usable + fl.usable = spawnArgs.GetBool ( "usable", "0" ); +// RAVEN END + + fl.neverDormant = spawnArgs.GetBool( "neverDormant", "0" ); + fl.hidden = spawnArgs.GetBool( "hide", "0" ); + if ( fl.hidden ) { + // make sure we're hidden, since a spawn function might not set it up right + PostEventMS( &EV_Hide, 0 ); + } + cinematic = spawnArgs.GetBool( "cinematic", "0" ); + + networkSync = spawnArgs.FindKey( "networkSync" ); + if ( networkSync ) { + fl.networkSync = ( atoi( networkSync->GetValue() ) != 0 ); + } + + // every object will have a unique name + temp = spawnArgs.GetString( "name", va( "%s_%s_%d", GetClassname(), spawnArgs.GetString( "classname" ), entityNumber ) ); + SetName( temp ); + + // if we have targets, wait until all entities are spawned to get them + if ( spawnArgs.MatchPrefix( "target" ) || spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); + + temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + + if ( spawnArgs.GetString( "bind", "", &temp ) ) { + PostEventMS( &EV_SpawnBind, 0 ); + } + + // auto-start a sound on the entity + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + // setup script object + if ( ShouldConstructScriptObjectAtSpawn() && spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + ConstructScriptObject(); + } + +// RAVEN BEGIN + fl.persistAcrossInstances = false; +// bgeisler: added + fl.triggerAnim = spawnArgs.GetBool( "trigger_anim" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +// RAVEN END +} + +/* +================ +idEntity::~idEntity +================ +*/ +idEntity::~idEntity( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + if ( thinkFlags ) { + BecomeInactive( thinkFlags ); + } + activeNode.Remove(); + + Signal( SIG_REMOVED ); + + // we have to set back the default physics object before unbinding because the entity + // specific physics object might be an entity variable and as such could already be destroyed. + SetPhysics( NULL ); + + // remove any entities that are bound to me + RemoveBinds(); + + // unbind from master + Unbind(); + QuitTeam(); + + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + + delete renderView; + renderView = NULL; + + delete signals; + signals = NULL; + +// RAVEN BEGIN +// bdube: make sure all sounds and attached effects are stopped + StopSound( SCHANNEL_ANY, false ); + + RemoveClientEntities(); +// RAVEN END + + FreeModelDef(); + FreeSoundEmitter( false ); + + gameLocal.UnregisterEntity( this ); +} + +/* +================ +idEntity::Save +================ +*/ +void idEntity::Save( idSaveGame *savefile ) const { + int i, j; + rvClientEntity* cent; + + savefile->WriteInt( entityNumber ); + savefile->WriteInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->WriteInt( snapshotSequence ); + savefile->WriteInt( snapshotBits ); + + savefile->WriteString( name ); + savefile->WriteDict( &spawnArgs ); + scriptObject.Save( savefile ); + + savefile->WriteInt( thinkFlags ); + savefile->WriteInt( dormantStart ); + savefile->WriteBool( cinematic ); + + // renderView_t * renderView; + + savefile->WriteObject( cameraTarget ); + + savefile->WriteInt( targets.Num() ); + for( i = 0; i < targets.Num(); i++ ) { + targets[ i ].Save( savefile ); + } + + savefile->WriteInt( health ); + + savefile->WriteInt( clientEntities.Num() ); + for( cent = clientEntities.Next(); cent; cent = cent->bindNode.Next() ) { + savefile->WriteObject( cent ); + } + +// savefile->WriteInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->WriteVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( modelDefHandle ); + savefile->WriteRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper save + forwardDamageEnt.Save ( savefile ); +// RAVEN END + + savefile->WriteStaticObject( defaultPhysicsObj ); + + savefile->WriteObject( bindMaster.GetEntity() ); + savefile->WriteJoint( bindJoint ); + savefile->WriteInt( bindBody ); + savefile->WriteObject( teamMaster ); + savefile->WriteObject( teamChain ); + + savefile->WriteInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->WriteInt( PVSAreas[ i ] ); + } + + if ( !signals ) { + savefile->WriteBool( false ); + } else { + savefile->WriteBool( true ); + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->WriteInt( signals->signal[ i ].Num() ); + for( j = 0; j < signals->signal[ i ].Num(); j++ ) { + savefile->WriteInt( signals->signal[ i ][ j ].threadnum ); + savefile->WriteString( signals->signal[ i ][ j ].function->Name() ); + } + } + } + + savefile->WriteInt( mpGUIState ); + + savefile->WriteInt( instance ); + savefile->WriteInt( clipWorld ); +} + +/* +================ +idEntity::Restore +================ +*/ +void idEntity::Restore( idRestoreGame *savefile ) { + int i, j; + int num; + rvClientEntity *temp; + idStr funcname; + + savefile->ReadInt( entityNumber ); + savefile->ReadInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->ReadInt( snapshotSequence ); + savefile->ReadInt( snapshotBits ); + + savefile->ReadString( name ); + SetName( name ); + savefile->ReadDict( &spawnArgs ); + + scriptObject.Restore( savefile ); + + savefile->ReadInt( thinkFlags ); + savefile->ReadInt( dormantStart ); + savefile->ReadBool( cinematic ); + + // renderView_t * renderView; + + savefile->ReadObject( reinterpret_cast( cameraTarget ) ); + + targets.Clear(); + savefile->ReadInt( num ); + targets.SetNum( num ); + for( i = 0; i < num; i++ ) { + targets[ i ].Restore( savefile ); + } + + savefile->ReadInt( health ); + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( temp ) ); + if( temp ) { + temp->bindNode.AddToEnd( clientEntities ); + } + } + +// savefile->ReadInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->ReadVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Read( &fl, sizeof( fl ) ); + +// RAVEN BEGIN + savefile->ReadRenderEntity( renderEntity, &spawnArgs ); +// RAVEN END + savefile->ReadInt( modelDefHandle ); + savefile->ReadRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper restore + forwardDamageEnt.Restore ( savefile ); +// RAVEN END + + savefile->ReadStaticObject( defaultPhysicsObj ); + RestorePhysics( &defaultPhysicsObj ); + + idEntity *templol = 0; + savefile->ReadObject( reinterpret_cast( templol ) ); + bindMaster = templol; + + savefile->ReadJoint( bindJoint ); + savefile->ReadInt( bindBody ); + savefile->ReadObject( reinterpret_cast( teamMaster ) ); + savefile->ReadObject( reinterpret_cast( teamChain ) ); + + savefile->ReadInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->ReadInt( PVSAreas[ i ] ); + } + + bool readsignals; + savefile->ReadBool( readsignals ); + if ( readsignals ) { + signals = new signalList_t; + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->ReadInt( num ); + signals->signal[ i ].SetNum( num ); + for( j = 0; j < num; j++ ) { + savefile->ReadInt( signals->signal[ i ][ j ].threadnum ); + savefile->ReadString( funcname ); + signals->signal[ i ][ j ].function = gameLocal.program.FindFunction( funcname ); + if ( !signals->signal[ i ][ j ].function ) { + savefile->Error( "Function '%s' not found", funcname.c_str() ); + } + } + } + } + + savefile->ReadInt( mpGUIState ); + + // restore must retrieve modelDefHandle from the renderer + if ( modelDefHandle != -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + savefile->ReadInt( instance ); + savefile->ReadInt( clipWorld ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +} + +/* +================ +idEntity::GetEntityDefName +================ +*/ +const char * idEntity::GetEntityDefName( void ) const { + if ( entityDefNumber < 0 ) { + return "*unknown*"; + } + return declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); +} + +/* +================ +idEntity::SetName +================ +*/ +void idEntity::SetName( const char *newname ) { + if ( name.Length() ) { + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, NULL ); + } + + name = newname; + if ( name.Length() ) { + if ( ( name == "NULL" ) || ( name == "null_entity" ) ) { + gameLocal.Error( "Cannot name entity '%s'. '%s' is reserved for script.", name.c_str(), name.c_str() ); + } + gameLocal.AddEntityToHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, this ); + } +} + +/* +================ +idEntity::GetName +================ +*/ +const char * idEntity::GetName( void ) const { + return name.c_str(); +} + + +/*********************************************************************** + + Thinking + +***********************************************************************/ + +/* +================ +idEntity::Think +================ +*/ +void idEntity::Think( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::DoDormantTests + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::DoDormantTests( void ) { + // Never go dormant? + if ( fl.neverDormant || (gameLocal.inCinematic && cinematic) ) { + return false; + } + + // if the monster area is not topologically connected to a player + if ( !gameLocal.InPlayerConnectedArea( this ) ) { + return true; + } else { + // the monster area is topologically connected to a player, but if + // the monster hasn't been woken up before, do the more precise PVS check + if ( !fl.hasAwakened ) { + if ( !gameLocal.InPlayerPVS( this ) ) { + return true; + } + } + } + + return false; +} + +/* +================ +idEntity::CheckDormant + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::CheckDormant( void ) { + bool dormant; + + dormant = DoDormantTests(); + if ( dormant ) { + if ( dormantStart == 0 ) { + dormantStart = gameLocal.time; + } + if ( gameLocal.time - dormantStart < DELAY_DORMANT_TIME ) { + dormant = false; + } + } else { + dormantStart = 0; + fl.hasAwakened = true; + } + + if ( dormant && !fl.isDormant ) { + fl.isDormant = true; + DormantBegin(); + } else if ( !dormant && fl.isDormant ) { + fl.isDormant = false; + DormantEnd(); + } + + return dormant; +} + +/* +================ +idEntity::DormantBegin + +called when entity becomes dormant +================ +*/ +void idEntity::DormantBegin( void ) { +} + +/* +================ +idEntity::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idEntity::DormantEnd( void ) { +} + +/* +================ +idEntity::IsActive +================ +*/ +bool idEntity::IsActive( void ) const { + return activeNode.InList(); +} + +/* +================ +idEntity::BecomeActive +================ +*/ +void idEntity::BecomeActive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // enable the team master if this entity is part of a physics team + if ( teamMaster && teamMaster != this ) { + teamMaster->BecomeActive( TH_PHYSICS ); + } else if ( !( thinkFlags & TH_PHYSICS ) ) { + // if this is a pusher +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Parametric::GetClassType() ) || physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + gameLocal.sortPushers = true; + } +// RAVEN BEGIN +// abahr: +// jnewquist: Use accessor for static class type + if( physics->IsType( rvPhysics_Spline::GetClassType() ) ) { + gameLocal.sortPushers = true; + } +// RAVEN END + } + } + + int oldFlags = thinkFlags; + thinkFlags |= flags; + if ( thinkFlags ) { + if ( !IsActive() ) { + activeNode.AddToEnd( gameLocal.activeEntities ); + } else if ( !oldFlags ) { + // we became inactive this frame, so we have to decrease the count of entities to deactivate + gameLocal.numEntitiesToDeactivate--; + } + } +} + +/* +================ +idEntity::BecomeInactive +================ +*/ +void idEntity::BecomeInactive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // may only disable physics on a team master if no team members are running physics or bound to a joints + if ( teamMaster == this ) { + for ( idEntity *ent = teamMaster->teamChain; ent; ent = ent->teamChain ) { + if ( ( ent->thinkFlags & TH_PHYSICS ) || ( ( ent->bindMaster == this ) && ( ent->bindJoint != INVALID_JOINT ) ) ) { + flags &= ~TH_PHYSICS; + break; + } + } + } + } + + if ( thinkFlags ) { + thinkFlags &= ~flags; + if ( !thinkFlags && IsActive() ) { + gameLocal.numEntitiesToDeactivate++; + } + } + + if ( ( flags & TH_PHYSICS ) ) { + // if this entity has a team master + if ( teamMaster && teamMaster != this ) { + // if the team master is at rest + if ( teamMaster->IsAtRest() ) { + teamMaster->BecomeInactive( TH_PHYSICS ); + } + } + } +} + +/*********************************************************************** + + Visuals + +***********************************************************************/ + +/* +================ +idEntity::SetShaderParm +================ +*/ +void idEntity::SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Warning( "shader parm index (%d) out of range", parmnum ); + return; + } + + renderEntity.shaderParms[ parmnum ] = value; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( float red, float green, float blue ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = red; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = green; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = blue; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec3 &color ) { + SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec3 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec4 &color ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec4 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idEntity::UpdateAnimationControllers +================ +*/ +bool idEntity::UpdateAnimationControllers( void ) { + // any ragdoll and IK animation controllers should be updated here + return false; +} + +/* +================ +idEntity::SetModel +================ +*/ +void idEntity::SetModel( const char *modelname ) { + assert( modelname ); + + FreeModelDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetSkin +================ +*/ +void idEntity::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} +// RAVEN BEGIN +// bgeisler: go back to default skin +/* +================ +idEntity::ClearSkin +================ +*/ +void idEntity::ClearSkin( void ) +{ + if ( GetAnimator() && GetAnimator()->ModelDef() ) { + renderEntity.customSkin = GetAnimator()->ModelDef()->GetDefaultSkin(); + } else { + renderEntity.customSkin = NULL; + } + + UpdateVisuals(); +} +// RAVEN END + +/* +================ +idEntity::GetSkin +================ +*/ +const idDeclSkin *idEntity::GetSkin( void ) const { + return renderEntity.customSkin; +} + +/* +================ +idEntity::FreeModelDef +================ +*/ +void idEntity::FreeModelDef( void ) { + if ( modelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandle ); + modelDefHandle = -1; + + rvClientEntity* cent; + + for( cent = clientEntities.Next(); cent != NULL; cent = cent->bindNode.Next() ) { + cent->FreeEntityDef(); + } + } +} + +/* +================ +idEntity::FreeLightDef +================ +*/ +void idEntity::FreeLightDef( void ) { +} + +/* +================ +idEntity::IsHidden +================ +*/ +bool idEntity::IsHidden( void ) const { + return fl.hidden; +} + +/* +================ +idEntity::Hide +================ +*/ +void idEntity::Hide( void ) { + if ( !IsHidden() ) { + fl.hidden = true; + FreeModelDef(); + UpdateVisuals(); + } +} + +/* +================ +idEntity::Show +================ +*/ +void idEntity::Show( void ) { + if ( IsHidden() ) { + fl.hidden = false; + UpdateVisuals(); + } +} + +/* +================ +idEntity::UpdateModelTransform +================ +*/ +void idEntity::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +idEntity::UpdateModel +================ +*/ +void idEntity::UpdateModel( void ) { + UpdateModelTransform(); + +// RAVEN BEGIN +// abahr: moved GetAnimator call because its invalid when called from a destructor + UpdateRenderEntityCallback(); +// RAVEN END + + // set to invalid number to force an update the next time the PVS areas are retrieved + ClearPVSAreas(); + + // ensure that we call Present this frame + BecomeActive( TH_UPDATEVISUALS ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEntity::UpdateRenderEntityCallback +================ +*/ +void idEntity::UpdateRenderEntityCallback() { +} +// RAVEN END + +/* +================ +idEntity::UpdateVisuals +================ +*/ +void idEntity::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( void ) { + int localNumPVSAreas, localPVSAreas[32]; + idBounds modelAbsBounds; + int i; + + modelAbsBounds.FromTransformedBounds( renderEntity.bounds, renderEntity.origin, renderEntity.axis ); + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( modelAbsBounds, localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + + // FIXME: some particle systems may have huge bounds and end up in many PVS areas + // the first MAX_PVS_AREAS may not be visible to a network client and as a result the particle system may not show up when it should + if ( localNumPVSAreas > MAX_PVS_AREAS ) { + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( renderEntity.origin ).Expand( 64.0f ), localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + } + + for ( numPVSAreas = 0; numPVSAreas < MAX_PVS_AREAS && numPVSAreas < localNumPVSAreas; numPVSAreas++ ) { + PVSAreas[numPVSAreas] = localPVSAreas[numPVSAreas]; + } + + for( i = numPVSAreas; i < MAX_PVS_AREAS; i++ ) { + PVSAreas[ i ] = 0; + } +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( const idVec3 &pos ) { + int i; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( pos ), PVSAreas, MAX_PVS_AREAS ); + i = numPVSAreas; + while ( i < MAX_PVS_AREAS ) { + PVSAreas[ i++ ] = 0; + } +} + +/* +================ +idEntity::GetNumPVSAreas +================ +*/ +int idEntity::GetNumPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return numPVSAreas; +} + +/* +================ +idEntity::GetPVSAreas +================ +*/ +const int *idEntity::GetPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return PVSAreas; +} + +/* +================ +idEntity::ClearPVSAreas +================ +*/ +void idEntity::ClearPVSAreas( void ) { + numPVSAreas = -1; +} + +/* +================ +idEntity::PhysicsTeamInPVS + + FIXME: for networking also return true if any of the entity shadows is in the PVS +================ +*/ +bool idEntity::PhysicsTeamInPVS( pvsHandle_t pvsHandle ) { + idEntity *part; + + if ( teamMaster ) { + for ( part = teamMaster; part; part = part->teamChain ) { + if ( gameLocal.pvs.InCurrentPVS( pvsHandle, part->GetPVSAreas(), part->GetNumPVSAreas() ) ) { + return true; + } + } + } else { + return gameLocal.pvs.InCurrentPVS( pvsHandle, GetPVSAreas(), GetNumPVSAreas() ); + } + return false; +} + +/* +============== +idEntity::ProjectOverlay +============== +*/ +void idEntity::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( modelDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( modelDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +idEntity::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idEntity::Present( void ) { + + if ( !gameLocal.isNewFrame ) { + return; + } + + // if there is no handle yet, go ahead and add it, ignoring the last predict frame early out + // if not, that causes next render frame to have a bunch of spurious primitive draws ( r_showPrimitives ) + // ( we suspect this is because TH_UPDATEVISUALS doesn't get cleared? ) + if ( !gameLocal.isLastPredictFrame && modelDefHandle != -1 ) { + return; + } + +// RAVEN BEGIN +// ddynerman: don't render objects not in our instance (only on server) + if ( gameLocal.isServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != GetInstance() ) { + FreeModelDef(); + return; + } +// RAVEN END + + // don't render server demo stuff that's not in our instance + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ) { + if ( instance != 0 ) { + FreeModelDef(); + return; + } + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views +// RAVEN BEGIN +// rjohnson: removed PVS check for when func_static's are not starting in your PVS + if ( cameraTarget ) { // && gameLocal.InPlayerPVS( this ) ) { +// RAVEN END + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idEntity::UpdateRenderEntity +================ +*/ +bool idEntity::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +idEntity::ModelCallback + + NOTE: may not change the game state whatsoever! +================ +*/ +bool idEntity::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + idEntity *ent; + + ent = gameLocal.entities[ renderEntity->entityNum ]; + if ( !ent ) { + gameLocal.Error( "idEntity::ModelCallback: callback with NULL game entity '%d'", renderEntity->entityNum ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idEntity::GetAnimator + +Subclasses will be responsible for allocating animator. +================ +*/ +idAnimator *idEntity::GetAnimator( void ) { + return NULL; +} + +/* +============= +idEntity::GetRenderView + +This is used by remote camera views to look from an entity +============= +*/ +renderView_t *idEntity::GetRenderView( void ) { + if ( !renderView ) { + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + renderView->vieworg = GetPhysics()->GetOrigin(); + renderView->fov_x = 120; + renderView->fov_y = 120; + renderView->viewaxis = GetPhysics()->GetAxis(); + + // copy global shader parms + for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + + renderView->time = gameLocal.time; + + return renderView; +} + +// RAVEN BEGIN +// bdube: added convienince functions for effects + +/*********************************************************************** + + effects + +***********************************************************************/ + +/* +================ +idEntity::PlayEffect +================ +*/ +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + if ( joint == INVALID_JOINT ) { + return NULL; + } + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteLong( joint ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT_JOINT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( originOffset ); + clientEffect->SetAxis( axisOffset ); + clientEffect->Bind( this, joint ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + idVec3 localOrigin; + idMat3 localAxis; + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( entityNumber == ENTITYNUM_WORLD ) { + return gameLocal.PlayEffect( effect, origin, axis, loop, endOrigin, broadcast, false, category, effectTint ); + } + + // Calculate the local origin and axis from the given globals + localOrigin = ( origin - renderEntity.origin ) * renderEntity.axis.Transpose(); + localAxis = axis * renderEntity.axis.Transpose(); + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + idCQuat quat; + + quat = localAxis.ToCQuat(); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( localOrigin.x ); + msg.WriteFloat( localOrigin.y ); + msg.WriteFloat( localOrigin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return( NULL ); + } +// ddynerman: a listen server might get this far re: playing effects, don't actually play out of instance effects + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); +// RAVEN END + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( localOrigin ); + clientEffect->SetAxis( localAxis ); + clientEffect->Bind( this ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +/* +================ +idEntity::StopAllEffects +================ +*/ +void idEntity::StopAllEffects( bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + static_cast( cent )->Stop( destroyParticles ); + } + } +} + +/* +================ +idEntity::StopEffect +================ +*/ +void idEntity::StopEffect( const idDecl *effect, bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + if( !effect ) { + return; + } + + // Build a list of all the effects to stop + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + + // Is this client entity an effect? + if ( !cent->IsType( rvClientEffect::GetClassType() ) ) { + continue; + } + + // Now check to make sure its the specific effect we want to stop + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex() == effect->Index() ) { + clientEffect->Stop( destroyParticles ); + } + } +} + +void idEntity::StopEffect( const char* effectName, bool destroyParticles ) { + StopEffect( gameLocal.GetEffect( spawnArgs, effectName ), destroyParticles ); +} + +// RAVEN END + +/*********************************************************************** + + Sound + +***********************************************************************/ + +/* +================ +idEntity::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idEntity::CanPlayChatterSounds( void ) const { + return true; +} + +/* +================ +idEntity::StartSound +================ +*/ +bool idEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + idStr soundNameStr = soundName; + if( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); +} + +/* +================ +idEntity::StartSoundShader +================ +*/ +bool idEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + float diversity; + int len; + + if ( length ) { + *length = 0; + } + + if ( !shader ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + return true; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, shader ); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STARTSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return false; + } + + // rjohnson: don't play sounds on a dedicated server! + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return false; + } + + // set a random value for diversity unless one was parsed from the entity + if ( refSound.diversity < 0.0f ) { + diversity = gameLocal.random.RandomFloat(); + } else { + diversity = refSound.diversity; + } + +// RAVEN BEGIN + // if we don't have a soundEmitter allocated yet, get one now + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + len = emitter->StartSound( shader, channel, diversity, soundShaderFlags ); + if ( length ) { + *length = len; + } + } +// RAVEN END + + // set reference to the sound for shader synced effects + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + + return true; +} + +/* +================ +idEntity::StopSound +================ +*/ +void idEntity::StopSound( const s_channelType channel, bool broadcast ) { + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STOPSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if ( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return; + } + +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->StopSound( channel ); + } +// RAVEN END +} + +/* +================ +idEntity::SetSoundVolume + + Must be called before starting a new sound. +================ +*/ +void idEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + +/* +================ +idEntity::UpdateSound +================ +*/ +void idEntity::UpdateSound( void ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { +// RAVEN END + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + +// RAVEN BEGIN + refSound.velocity = GetPhysics()->GetLinearVelocity(); + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); +// RAVEN END + } +} + +/* +================ +idEntity::GetListenerId +================ +*/ +int idEntity::GetListenerId( void ) const { + return refSound.listenerId; +} + +/* +================ +idEntity::GetSoundEmitter +================ +*/ +// RAVEN BEGIN +int idEntity::GetSoundEmitter( void ) const { + return( refSound.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idEntity::FreeSoundEmitter +================ +*/ +void idEntity::FreeSoundEmitter( bool immediate ) { +// RAVEN BEGIN + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, immediate ); + refSound.referenceSoundHandle = -1; +// RAVEN END +} + +// RAVEN BEGIN +// bdube: client entities + +/*********************************************************************** + + client entities + +***********************************************************************/ + +/* +================ +idEntity::RemoveClientEntities +================ +*/ +void idEntity::RemoveClientEntities( void ) { + rvClientEntity* cent; + // Unbinding should remove the node from the list so keep using the head until + // there are no more entities + for( cent = clientEntities.Next(); cent != NULL; cent = clientEntities.Next() ) { + cent->Unbind( ); + delete cent; + } + clientEntities.Clear( ); +} +// RAVEN END + +/*********************************************************************** + + entity binding + +***********************************************************************/ + +/* +================ +idEntity::PreBind +================ +*/ +void idEntity::PreBind( void ) { +} + +/* +================ +idEntity::PostBind +================ +*/ +void idEntity::PostBind( void ) { +} + +/* +================ +idEntity::PreUnbind +================ +*/ +void idEntity::PreUnbind( void ) { +} + +/* +================ +idEntity::PostUnbind +================ +*/ +void idEntity::PostUnbind( void ) { +} + +/* +================ +idEntity::InitBind +================ +*/ +bool idEntity::InitBind( idEntity *master ) { + + if ( master == this ) { + gameLocal.Error( "Tried to bind an object to itself." ); + return false; + } + + if ( this == gameLocal.world ) { + gameLocal.Error( "Tried to bind world to another entity" ); + return false; + } + + // unbind myself from my master + Unbind(); + + // add any bind constraints to an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master && IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->AddBindConstraints(); + } + + if ( !master || master == gameLocal.world ) { + // this can happen in scripts, so safely exit out. + return false; + } + + return true; +} + +/* +================ +idEntity::FinishBind +================ +*/ +void idEntity::FinishBind( void ) { + + // set the master on the physics object + physics->SetMaster( bindMaster, fl.bindOrientated ); + + // We are now separated from our previous team and are either + // an individual, or have a team of our own. Now we can join + // the new bindMaster's team. Bindmaster must be set before + // joining the team, or we will be placed in the wrong position + // on the team. + JoinTeam( bindMaster ); + + // if our bindMaster is enabled during a cinematic, we must be, too +// RAVEN BEGIN +// rjohnson: players should always have cinematic turned on, no matter what + if ( !IsType ( idPlayer::GetClassType() ) ) { + cinematic = bindMaster->cinematic; + } +// RAVEN END + + // make sure the team master is active so that physics get run + teamMaster->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idEntity::Bind + + bind relative to the visual position of the master +================ +*/ +void idEntity::Bind( idEntity *master, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind( ); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, const char *jointname, bool orientated ) { + jointHandle_t jointnum; + idAnimator *masterAnimator; + + if ( !InitBind( master ) ) { + return; + } + + masterAnimator = master->GetAnimator(); + if ( !masterAnimator ) { + gameLocal.Warning( "idEntity::BindToJoint: entity '%s' cannot support skeletal models.", master->GetName() ); + return; + } + + jointnum = masterAnimator->GetJointHandle( jointname ); + if ( jointnum == INVALID_JOINT ) { + gameLocal.Warning( "idEntity::BindToJoint: joint '%s' not found on entity '%s'.", jointname, master->GetName() ); + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToBody + + bind relative to a collision model used by the physics of the master +================ +*/ +void idEntity::BindToBody( idEntity *master, int bodyId, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + if ( bodyId < 0 ) { + gameLocal.Warning( "idEntity::BindToBody: body '%d' not found.", bodyId ); + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = bodyId; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::Unbind +================ +*/ +void idEntity::Unbind( void ) { + idEntity * prev; + idEntity * next; + idEntity * last; + idEntity * ent; + + // remove any bind constraints from an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->RemoveBindConstraints(); + } + + if ( !bindMaster ) { + return; + } + + if ( !teamMaster ) { + // Teammaster already has been freed + bindMaster = NULL; + return; + } + + PreUnbind(); + + if ( physics ) { + physics->SetMaster( NULL, fl.bindOrientated ); + } + + // We're still part of a team, so that means I have to extricate myself + // and any entities that are bound to me from the old team. + // Find the node previous to me in the team + prev = teamMaster; + for( ent = teamMaster->teamChain; ent && ( ent != this ); ent = ent->teamChain ) { + prev = ent; + } + + assert( ent == this ); // If ent is not pointing to this, then something is very wrong. + + // Find the last node in my team that is bound to me. + // Also find the first node not bound to me, if one exists. + last = this; + for( next = teamChain; next != NULL; next = next->teamChain ) { + if ( !next->IsBoundTo( this ) ) { + break; + } + + // Tell them I'm now the teamMaster + next->teamMaster = this; + last = next; + } + + // disconnect the last member of our team from the old team + last->teamChain = NULL; + + // connect up the previous member of the old team to the node that + // follow the last node bound to me (if one exists). + if ( teamMaster != this ) { + prev->teamChain = next; + if ( !next && ( teamMaster == prev ) ) { + prev->teamMaster = NULL; + } + } else if ( next ) { + // If we were the teamMaster, then the nodes that were not bound to me are now + // a disconnected chain. Make them into their own team. + for( ent = next; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamMaster = next; + } + next->teamMaster = next; + } + + // If we don't have anyone on our team, then clear the team variables. + if ( teamChain ) { + // make myself my own team + teamMaster = this; + } else { + // no longer a team + teamMaster = NULL; + } + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = NULL; + + PostUnbind(); +} + +/* +================ +idEntity::RemoveBinds +================ +*/ +void idEntity::RemoveBinds( void ) { + idEntity *ent; + idEntity *next; + + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->teamChain; + if ( ent->bindMaster == this ) { + ent->Unbind(); + ent->PostEventMS( &EV_Remove, 0 ); + next = teamChain; + } + } +} + +/* +================ +idEntity::IsBound +================ +*/ +bool idEntity::IsBound( void ) const { + if ( bindMaster ) { + return true; + } + return false; +} + +/* +================ +idEntity::IsBoundTo +================ +*/ +// RAVEN BEGIN +// abahr: added const so it can be called from const functions +bool idEntity::IsBoundTo( const idEntity *master ) const { +// RAVEN END + idEntity *ent; + + if ( !bindMaster ) { + return false; + } + + for ( ent = bindMaster; ent != NULL; ent = ent->bindMaster ) { + if ( ent == master ) { + return true; + } + } + + return false; +} + +/* +================ +idEntity::GetBindMaster +================ +*/ +idEntity *idEntity::GetBindMaster( void ) const { + return bindMaster; +} + +/* +================ +idEntity::GetBindJoint +================ +*/ +jointHandle_t idEntity::GetBindJoint( void ) const { + return bindJoint; +} + +/* +================ +idEntity::GetBindBody +================ +*/ +int idEntity::GetBindBody( void ) const { + return bindBody; +} + +/* +================ +idEntity::GetTeamMaster +================ +*/ +idEntity *idEntity::GetTeamMaster( void ) const { + return teamMaster; +} + +/* +================ +idEntity::GetNextTeamEntity +================ +*/ +idEntity *idEntity::GetNextTeamEntity( void ) const { + return teamChain; +} + +/* +===================== +idEntity::ConvertLocalToWorldTransform +===================== +*/ +void idEntity::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + UpdateModelTransform(); + + offset = renderEntity.origin + offset * renderEntity.axis; + axis *= renderEntity.axis; +} + +/* +================ +idEntity::GetLocalVector + +Takes a vector in worldspace and transforms it into the parent +object's localspace. + +Note: Does not take origin into acount. Use getLocalCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetLocalVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetLocalCoordinates + +Takes a vector in world coordinates and transforms it into the parent +object's local coordinates. +================ +*/ +idVec3 idEntity::GetLocalCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec - masterOrigin, pos ); + + return pos; +} + +// RAVEN BEGIN +// kfuller: added method + +/* +================ +idEntity::DistanceTo2d +================ +*/ +float idEntity::DistanceTo2d ( const idVec3& pos ) const { + idVec3 pos1; + idVec3 pos2; + pos1 = pos - (pos * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + pos2 = GetPhysics()->GetOrigin ( ); + pos2 = pos2 - (pos2 * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + return (pos2 - pos1).LengthFast ( ); +} + +/* +================ +idEntity::GetLocalAngles +================ +*/ +void idEntity::GetLocalAngles(idAngles &localAng) +{ + idVec3 localVec = GetPhysics()->GetAxis()[0]; + + GetLocalVector(localVec); + localAng = localVec.ToAngles(); +} +// RAVEN END + +/* +================ +idEntity::GetWorldVector + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. + +Note: Does not take origin into acount. Use getWorldCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetWorldVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetWorldCoordinates + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. +================ +*/ +idVec3 idEntity::GetWorldCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + pos += masterOrigin; + + return pos; +} + +/* +================ +idEntity::GetMasterPosition +================ +*/ +bool idEntity::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idVec3 localOrigin; + idMat3 localAxis; + idAnimator *masterAnimator; + + if ( bindMaster ) { + // if bound to a joint of an animated model + if ( bindJoint != INVALID_JOINT ) { + masterAnimator = bindMaster->GetAnimator(); + if ( !masterAnimator ) { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } else { + masterAnimator->GetJointTransform( bindJoint, gameLocal.time, masterOrigin, masterAxis ); + masterAxis *= bindMaster->renderEntity.axis; + masterOrigin = bindMaster->renderEntity.origin + masterOrigin * bindMaster->renderEntity.axis; + } + } else if ( bindBody >= 0 && bindMaster->GetPhysics() ) { + masterOrigin = bindMaster->GetPhysics()->GetOrigin( bindBody ); + masterAxis = bindMaster->GetPhysics()->GetAxis( bindBody ); + } else { + masterOrigin = bindMaster->renderEntity.origin; + masterAxis = bindMaster->renderEntity.axis; + } + return true; + } else { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } +} + +// RAVEN BEGIN +// abahr: needed so client get the correct position +/* +================ +idEntity::GetPosition +================ +*/ +void idEntity::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = renderEntity.origin; + axis = renderEntity.axis; +} +// RAVEN END + +/* +================ +idEntity::GetWorldVelocities +================ +*/ +void idEntity::GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const { + + linearVelocity = physics->GetLinearVelocity(); + angularVelocity = physics->GetAngularVelocity(); + + if ( bindMaster ) { + idVec3 masterOrigin, masterLinearVelocity, masterAngularVelocity; + idMat3 masterAxis; + + // get position of master + GetMasterPosition( masterOrigin, masterAxis ); + + // get master velocities + bindMaster->GetWorldVelocities( masterLinearVelocity, masterAngularVelocity ); + + // linear velocity relative to master plus master linear and angular velocity + linearVelocity = linearVelocity * masterAxis + masterLinearVelocity + + masterAngularVelocity.Cross( GetPhysics()->GetOrigin() - masterOrigin ); + } +} + +/* +================ +idEntity::JoinTeam +================ +*/ +void idEntity::JoinTeam( idEntity *teammember ) { + idEntity *ent; + idEntity *master; + idEntity *prev; + idEntity *next; + + // if we're already on a team, quit it so we can join this one + if ( teamMaster && ( teamMaster != this ) ) { + QuitTeam(); + } + + assert( teammember ); + + if ( teammember == this ) { + teamMaster = this; + return; + } + + // check if our new team mate is already on a team + master = teammember->teamMaster; + if ( !master ) { + // he's not on a team, so he's the new teamMaster + master = teammember; + teammember->teamMaster = teammember; + teammember->teamChain = this; + + // make anyone who's bound to me part of the new team + for( ent = teamChain; ent != NULL; ent = ent->teamChain ) { + ent->teamMaster = master; + } + } else { + // skip past the chain members bound to the entity we're teaming up with + prev = teammember; + next = teammember->teamChain; + if ( bindMaster ) { + // if we have a bindMaster, join after any entities bound to the entity + // we're joining + while( next && next->IsBoundTo( teammember ) ) { + prev = next; + next = next->teamChain; + } + } else { + // if we're not bound to someone, then put us at the end of the team + while( next ) { + prev = next; + next = next->teamChain; + } + } + + // make anyone who's bound to me part of the new team and + // also find the last member of my team + for( ent = this; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamChain->teamMaster = master; + } + + prev->teamChain = this; + ent->teamChain = next; + } + + teamMaster = master; + + // reorder the active entity list + gameLocal.sortTeamMasters = true; +} + +/* +================ +idEntity::QuitTeam +================ +*/ +void idEntity::QuitTeam( void ) { + idEntity *ent; + + if ( !teamMaster ) { + return; + } + + // check if I'm the teamMaster + if ( teamMaster == this ) { + // do we have more than one teammate? + if ( !teamChain->teamChain ) { + // no, break up the team + teamChain->teamMaster = NULL; + } else { + // yes, so make the first teammate the teamMaster + for( ent = teamChain; ent; ent = ent->teamChain ) { + ent->teamMaster = teamChain; + } + } + } else { + assert( teamMaster ); + assert( teamMaster->teamChain ); + + // find the previous member of the teamChain + ent = teamMaster; + while( ent->teamChain != this ) { + assert( ent->teamChain ); // this should never happen + ent = ent->teamChain; + } + + // remove this from the teamChain + ent->teamChain = teamChain; + + // if no one is left on the team, break it up + if ( !teamMaster->teamChain ) { + teamMaster->teamMaster = NULL; + } + } + + teamMaster = NULL; + teamChain = NULL; +} + +/*********************************************************************** + + Physics. + +***********************************************************************/ + +/* +================ +idEntity::InitDefaultPhysics +================ +*/ +void idEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( temp ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on entity '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on entity '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); +// RAVEN BEGIN +// bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); +// RAVEN END + } else { + trm.SetupBox( bounds ); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { +// RAVEN BEGIN +// jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( this ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; +} + +/* +================ +idEntity::SetPhysics +================ +*/ +void idEntity::SetPhysics( idPhysics *phys ) { + // clear any contacts the current physics object has + if ( physics ) { + physics->ClearContacts(); + } + // set new physics object or set the default physics if NULL + if ( phys != NULL ) { + defaultPhysicsObj.SetClipModel( NULL, 1.0f ); + physics = phys; + physics->Activate(); + } else { + physics = &defaultPhysicsObj; + } + physics->UpdateTime( gameLocal.time ); + physics->SetMaster( bindMaster, fl.bindOrientated ); +} + +/* +================ +idEntity::RestorePhysics +================ +*/ +void idEntity::RestorePhysics( idPhysics *phys ) { + assert( phys != NULL ); + // restore physics pointer + physics = phys; +} + +/* +================ +idEntity::RunPhysics +================ +*/ +bool idEntity::RunPhysics( void ) { + int i, reachedTime, startTime, endTime; + idEntity * part, *blockedPart, *blockingEntity = NULL; + trace_t results; + bool moved; + + moved = false; + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PHYSICS); +// RAVEN END + + // don't run physics if not enabled + if ( !( thinkFlags & TH_PHYSICS ) ) { + // however do update any animation controllers + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } +// RAVEN BEGIN +// kfuller: we want to be able to debug draw the bbox regardless + physics->DebugDraw(); +// RAVEN END + return false; + } + + // if this entity is a team slave don't do anything because the team master will handle everything + if ( teamMaster && teamMaster != this ) { + return false; + } + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + gameLocal.push.InitSavingPushedEntityPositions(); + blockedPart = NULL; + + // save the physics state of the whole team and disable the team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->DisableClip(); + } + part->physics->SaveState(); + } + } + + // move the whole team + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + // run physics +// RAVEN BEGIN +// ddynerman: optional pre-prediction + moved = part->physics->Evaluate( endTime - startTime + part->predictTime, endTime ); + part->predictTime = 0; +// RAVEN END + + // check if the object is blocked + blockingEntity = part->physics->GetBlockingEntity(); + if ( blockingEntity ) { + blockedPart = part; + break; + } + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateFromPhysics( false ); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + + // enable the whole team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->EnableClip(); + } + } + } + + // cdr: Obstacle Avoidance + if (ai_useRVMasterMove.GetBool() && moved && fl.isAIObstacle) { + AI_EntityMoved(this); + } + + // if one of the team entities is a pusher and blocked + if ( blockedPart ) { + // move the parts back to the previous position + for ( part = this; part != blockedPart; part = part->teamChain ) { + + if ( part->physics ) { + + // restore the physics state + part->physics->RestoreState(); + + // move back the visual position and orientation + part->UpdateFromPhysics( true ); + } + } + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + // update the physics time without moving + part->physics->UpdateTime( endTime ); + } + } + + // restore the positions of any pushed entities + gameLocal.push.RestorePushedEntityPositions(); + + if ( gameLocal.isClient ) { + return false; + } + + // if the master pusher has a "blocked" function, call it + Signal( SIG_BLOCKED ); + ProcessEvent( &EV_TeamBlocked, blockedPart, blockingEntity ); + // call the blocked function on the blocked part + blockedPart->ProcessEvent( &EV_PartBlocked, blockingEntity ); + return false; + } + + // set pushed + for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) { + idEntity *ent = gameLocal.push.GetPushedEntity( i ); + ent->physics->SetPushed( endTime - startTime ); + } + + if ( gameLocal.isClient ) { + return true; + } + + // post reached event if the current time is at or past the end point of the motion + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + reachedTime = part->physics->GetLinearEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedPos ); + } + reachedTime = part->physics->GetAngularEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedAng ); + } + } + } + + return true; +} + +/* +================ +idEntity::UpdateFromPhysics +================ +*/ +void idEntity::UpdateFromPhysics( bool moveBack ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idActor::GetClassType() ) ) { +// RAVEN END + idActor *actor = static_cast( this ); + + // set master delta angles for actors + if ( GetBindMaster() ) { + idAngles delta = actor->GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= static_cast(physics)->GetMasterDeltaYaw(); + } else { + delta.yaw += static_cast(physics)->GetMasterDeltaYaw(); + } + actor->SetDeltaViewAngles( delta ); + } + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetOrigin +================ +*/ +void idEntity::SetOrigin( const idVec3 &org ) { + + GetPhysics()->SetOrigin( org ); + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAxis +================ +*/ +void idEntity::SetAxis( const idMat3 &axis ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + static_cast(this)->viewAxis = axis; + } else { + GetPhysics()->SetAxis( axis ); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAngles +================ +*/ +void idEntity::SetAngles( const idAngles &ang ) { + SetAxis( ang.ToMat3() ); +} + +/* +================ +idEntity::GetFloorPos +================ +*/ +bool idEntity::GetFloorPos( float max_dist, idVec3 &floorpos ) const { + trace_t result; + + if ( !GetPhysics()->HasGroundContacts() ) { + GetPhysics()->ClipTranslation( result, GetPhysics()->GetGravityNormal() * max_dist, NULL ); + if ( result.fraction < 1.0f ) { + floorpos = result.endpos; + return true; + } else { + floorpos = GetPhysics()->GetOrigin(); + return false; + } + } else { + floorpos = GetPhysics()->GetOrigin(); + return true; + } +} + +/* +================ +idEntity::GetPhysicsToVisualTransform +================ +*/ +bool idEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +idEntity::GetPhysicsToSoundTransform +================ +*/ +bool idEntity::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + // by default play the sound at the center of the bounding box of the first clip model + if ( GetPhysics()->GetNumClipModels() > 0 ) { + origin = GetPhysics()->GetBounds().GetCenter(); + axis.Identity(); + return true; + } + return false; +} + +/* +================ +idEntity::Collide +================ +*/ +bool idEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + // this entity collides with collision.c.entityNum + return false; +} + +/* +================ +idEntity::GetImpactInfo +================ +*/ +void idEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +idEntity::ApplyImpulse +================ +*/ +void idEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( SkipImpulse(ent, id) ) { + return; + } + + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +idEntity::AddForce +================ +*/ +void idEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +idEntity::ActivatePhysics +================ +*/ +void idEntity::ActivatePhysics( idEntity *ent ) { + GetPhysics()->Activate(); +} + +/* +================ +idEntity::IsAtRest +================ +*/ +bool idEntity::IsAtRest( void ) const { + return GetPhysics()->IsAtRest(); +} + +/* +================ +idEntity::GetRestStartTime +================ +*/ +int idEntity::GetRestStartTime( void ) const { + return GetPhysics()->GetRestStartTime(); +} + +/* +================ +idEntity::AddContactEntity +================ +*/ +void idEntity::AddContactEntity( idEntity *ent ) { + GetPhysics()->AddContactEntity( ent ); +} + +/* +================ +idEntity::RemoveContactEntity +================ +*/ +void idEntity::RemoveContactEntity( idEntity *ent ) { +// RAVEN BEGIN + if( GetPhysics() ) { + + GetPhysics()->RemoveContactEntity( ent ); + } +// RAVEN END +} + + + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idEntity::CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +// RAVEN BEGIN +// bdube: added ignore entity +bool idEntity::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt ) const { +// RAVEN END + idVec3 dest; + trace_t tr; + idVec3 midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin at 0,0,0 + midpoint = ( GetPhysics()->GetAbsBounds()[0] + GetPhysics()->GetAbsBounds()[1] ) * 0.5; + + dest = midpoint; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + // this should probably check in the plane of projection, rather than in world coordinate + dest = midpoint; + dest[0] += 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN ENE + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] += 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN EN + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] += 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] -= 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + return false; +} + +/* +================ +idEntity::DamageFeedback + +callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. +================ +*/ +void idEntity::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + // implemented in subclasses +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +============ +*/ +void idEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + return; + } + + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + if ( damage ) { + // do the damage + //jshepard: this is kinda important, no? + health -= damage; + + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } +} + +/* +============ +idEntity::SkipImpulse +============ +*/ +// RAVEN BEGIN +// abahr: push stuff +bool idEntity::SkipImpulse( idEntity *ent, int id ) { + return false;//ent == this; +} + +/* +============ +idEntity::ApplyImpulse +============ +*/ +void idEntity::ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ) { + ApplyImpulse( ent, id, point, dir * damageDef->GetFloat("push", "5000") ); +} +// RAVEN END + +/* +================ +idEntity::AddDamageEffect +================ +*/ +void idEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + const char *sound, *decal, *key; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); +// RAVEN BEGIN +// bdube: impact_blood is now in the damage def + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { +// RAVEN END + return; + } + + const char *materialType = gameLocal.sufaceTypeNames[ collision.c.material->GetSurfaceType() ]; + + // start impact sound based on material type + key = va( "snd_%s", materialType ); + sound = spawnArgs.GetString( key ); + if ( *sound == '\0' ) { + sound = def->dict.GetString( key ); + } + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } + + if ( g_decals.GetBool() ) { + // place a wound overlay on the model + key = va( "mtr_wound_%s", materialType ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + if ( *decal == '\0' ) { + decal = def->dict.RandomPrefix( key, gameLocal.random ); + } + if ( *decal != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + } + } +} + +/* +================ +idEntity::CanPlayImpactEffect +================ +*/ +bool idEntity::CanPlayImpactEffect ( idEntity* owner, idEntity* ent ) { + if( gameLocal.isMultiplayer ) { + if( gameLocal.IsTeamGame() && !cvarSystem->GetCVarBool("si_teamDamage") && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && ((idPlayer*)owner)->team == ((idPlayer*)ent)->team ) { + return false; + } + + // default to blood + return true; + } else { + idActor* actorOwner; + idAI* aiEnt; + actorOwner = dynamic_cast( owner ); + + if ( ent->IsType ( idAFAttachment::GetClassType ( ) ) ) { + aiEnt = dynamic_cast( static_cast( ent )->GetBody ( ) ); + } else { + aiEnt = dynamic_cast( ent ); + } + + if ( !actorOwner || !aiEnt ) { + return true; + } + + return (actorOwner->team != aiEnt->team); + } +} + +/* +============ +idEntity::Pain + +Called whenever an entity recieves damage. Returns whether the entity responds to the pain. +This is a virtual function that subclasses are expected to implement. +============ +*/ +bool idEntity::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + return false; +} + +/* +============ +idEntity::Killed + +Called whenever an entity's health is reduced to 0 or less. +This is a virtual function that subclasses are expected to implement. +============ +*/ +void idEntity::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +} + +/*********************************************************************** + + Script functions + +***********************************************************************/ + +/* +================ +idEntity::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idEntity::ShouldConstructScriptObjectAtSpawn( void ) const { + return true; +} + +/* +================ +idEntity::ConstructScriptObject + +Called during idEntity::Spawn. Calls the constructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +================ +*/ +idThread *idEntity::ConstructScriptObject( void ) { + idThread *thread; + const function_t *constructor; + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, constructor, true ); + thread->DelayedStart( 0 ); + } else { + thread = NULL; + } + + // clear out the object's memory + scriptObject.ClearObject(); + + return thread; +} + +/* +================ +idEntity::DeconstructScriptObject + +Called during idEntity::~idEntity. Calls the destructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +Not called during idGameLocal::MapShutdown. +================ +*/ +void idEntity::DeconstructScriptObject( void ) { + idThread *thread; + const function_t *destructor; + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and be destroyed +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_TEMPORARY); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, destructor, true ); + thread->Execute(); + delete thread; + } +} + +/* +================ +idEntity::HasSignal +================ +*/ +bool idEntity::HasSignal( signalNum_t signalnum ) const { + if ( !signals ) { + return false; + } + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + return ( signals->signal[ signalnum ].Num() > 0 ); +} + +/* +================ +idEntity::SetSignal +================ +*/ +void idEntity::SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ) { + int i; + int num; + signal_t sig; + int threadnum; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + signals = new signalList_t; + } + + assert( thread ); + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ][ i ].function = function; + return; + } + } + + if ( num >= MAX_SIGNAL_THREADS ) { + thread->Error( "Exceeded maximum number of signals per object" ); + } + + sig.threadnum = threadnum; + sig.function = function; + signals->signal[ signalnum ].Append( sig ); +} + +/* +================ +idEntity::ClearSignal +================ +*/ +void idEntity::ClearSignal( idThread *thread, signalNum_t signalnum ) { + assert( thread ); + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + signals->signal[ signalnum ].Clear(); +} + +/* +================ +idEntity::ClearSignalThread +================ +*/ +void idEntity::ClearSignalThread( signalNum_t signalnum, idThread *thread ) { + int i; + int num; + int threadnum; + + assert( thread ); + + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ].RemoveIndex( i ); + return; + } + } +} + +/* +================ +idEntity::Signal +================ +*/ +void idEntity::Signal( signalNum_t signalnum ) { + int i; + int num; + signal_t sigs[ MAX_SIGNAL_THREADS ]; + idThread *thread; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + return; + } + + // we copy the signal list since each thread has the potential + // to end any of the threads in the list. By copying the list + // we don't have to worry about the list changing as we're + // processing it. + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + sigs[ i ] = signals->signal[ signalnum ][ i ]; + } + + // clear out the signal list so that we don't get into an infinite loop + signals->signal[ signalnum ].Clear(); + + for( i = 0; i < num; i++ ) { + thread = idThread::GetThread( sigs[ i ].threadnum ); + if ( thread ) { + thread->CallFunction( this, sigs[ i ].function, true ); + thread->Execute(); + } + } +} + +/* +================ +idEntity::SignalEvent +================ +*/ +void idEntity::SignalEvent( idThread *thread, signalNum_t signalnum ) { + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + Signal( signalnum ); +} + +/*********************************************************************** + + Guis. + +***********************************************************************/ + + +/* +================ +idEntity::TriggerGuis +================ +*/ +void idEntity::TriggerGuis( void ) { + int i; + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->Trigger( gameLocal.time ); + } + } +} + +/* +================ +idEntity::HandleGuiCommands +================ +*/ +bool idEntity::HandleGuiCommands( idEntity *entityGui, const char *cmds ) { + idEntity *targetEnt; + bool ret = false; + if ( entityGui && cmds && *cmds ) { + idLexer src; + idToken token, token2, token3, token4; + src.LoadMemory( cmds, strlen( cmds ), "guiCommands" ); + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + return ret; + } + + if ( token == ";" ) { + continue; + } + + if ( token.Icmp( "activate" ) == 0 ) { + bool targets = true; + if ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + } else { + targets = false; + } + } + + if ( targets ) { + entityGui->ActivateTargets( this ); + } else { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + } + } + + entityGui->renderEntity.shaderParms[ SHADERPARM_MODE ] = 1.0f; + continue; + } + + + if ( token.Icmp( "runScript" ) == 0 ) { + if ( src.ReadToken( &token2 ) ) { + while( src.CheckTokenString( "::" ) ) { + idToken token3; + if ( !src.ReadToken( &token3 ) ) { + gameLocal.Error( "Expecting function name following '::' in gui for entity '%s'", entityGui->name.c_str() ); + } + token2 += "::" + token3; + } + } +// RAVEN BEGIN +// abahr: allow parms to be passed in +// For some reason the semi colon is used as a delimeter so we need the above code + rvScriptFuncUtility utility; + if( utility.Init(token2) > SFU_ERROR ) { + utility.InsertEntity( entityGui, 0 ); + utility.CallFunc( &entityGui->spawnArgs ); + } +// RAVEN END + continue; + } + + if ( token.Icmp("play") == 0 ) { + if ( src.ReadToken( &token2 ) ) { + const idSoundShader *shader = declManager->FindSound(token2); + entityGui->StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + continue; + } + + if ( token.Icmp( "setkeyval" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) && src.ReadToken( &token4 ) ) { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->spawnArgs.Set( token3, token4 ); + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + } + } + continue; + } + + if ( token.Icmp( "setshaderparm" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) ) { + entityGui->SetShaderParm( atoi( token2 ), atof( token3 ) ); + entityGui->UpdateVisuals(); + } + continue; + } + + if ( token.Icmp("close") == 0 ) { + ret = true; + continue; + } + + // handy for debugging GUI stuff + if ( !token.Icmp( "print" ) ) { + idStr msg; + while ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + break; + } + msg += token2.c_str(); + } + common->Printf( "ent gui 0x%x '%s': %s\n", entityNumber, name.c_str(), msg.c_str() ); + continue; + } + + // if we get to this point we don't know how to handle it + src.UnreadToken(&token); + if ( !HandleSingleGuiCommand( entityGui, &src ) ) { + // not handled there see if entity or any of its targets can handle it + // this will only work for one target atm + if ( entityGui->HandleSingleGuiCommand( entityGui, &src ) ) { + continue; + } + + int c = entityGui->targets.Num(); + int i; + for ( i = 0; i < c; i++) { + targetEnt = entityGui->targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->HandleSingleGuiCommand( entityGui, &src ) ) { + break; + } + } + + if ( i == c ) { + // not handled + common->DPrintf( "idEntity::HandleGuiCommands: '%s' not handled\n", token.c_str() ); + src.ReadToken( &token ); + } + } + + } + } + return ret; +} + +/* +================ +idEntity::HandleSingleGuiCommand +================ +*/ +bool idEntity::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + return false; +} + +/*********************************************************************** + + Targets + +***********************************************************************/ + +/* +=============== +idEntity::FindTargets + +We have to wait until all entities are spawned +Used to build lists of targets after the entity is spawned. Since not all entities +have been spawned when the entity is created at map load time, we have to wait +=============== +*/ +void idEntity::FindTargets( void ) { + int i; + + // targets can be a list of multiple names + gameLocal.GetTargets( spawnArgs, targets, "target" ); + + // ensure that we don't target ourselves since that could cause an infinite loop when activating entities + for( i = 0; i < targets.Num(); i++ ) { + if ( targets[ i ].GetEntity() == this ) { + gameLocal.Error( "Entity '%s' is targeting itself", name.c_str() ); + } + } +} + +/* +================ +idEntity::RemoveNullTargets +================ +*/ +void idEntity::RemoveNullTargets( void ) { + int i; + + for( i = targets.Num() - 1; i >= 0; i-- ) { + if ( !targets[ i ].GetEntity() ) { + targets.RemoveIndex( i ); + } + } +} + +/* +============================== +idEntity::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idEntity::ActivateTargets( idEntity *activator ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->renderEntity.gui[ j ] ) { + ent->renderEntity.gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +// RAVEN BEGIN +// twhitaker: added (meant to be used from script) +/* +================ +idEntity::AppendTarget +================ +*/ +int idEntity::AppendTarget( idEntity *appendMe ) { + + int index = -1; + // silently fail if they pass in null + if ( appendMe ) { + index = targets.Append( appendMe ); + RemoveNullTargets(); + } + return index; +} +/* +================ +idEntity::RemoveTarget +================ +*/ +void idEntity::RemoveTarget( idEntity *removeMe ) { + + targets.Remove( removeMe ); + RemoveNullTargets(); +} +/* +================ +idEntity::RemoveTargets +================ +*/ +void idEntity::RemoveTargets( bool destroyContents ) { + if( destroyContents ) { + targets.RemoveContents( true ); + } else { + targets.Clear(); + } +} + +// jshepard: added +/* +================ +idEntity::UnbindTargets +================ +*/ +void idEntity::UnbindTargets( idEntity *activator ) const { + idEntity *ent; + int i; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + ent->Unbind(); + + } +} + +// bdube: added +/* +================ +idEntity::Event_SetContents +================ +*/ +void idEntity::Event_SetContents( int contents ) { + GetPhysics()->SetContents( contents ); +} + +/* +================ +idEntity::Event_GetLastBlocker +================ +*/ +void idEntity::Event_GetLastBlocker(idThread *thread) { + int whichEntNum = GetLastBlocker(); + + if (whichEntNum < 0 || whichEntNum == ENTITYNUM_WORLD) { + thread->ReturnEntity(this); + return; + } + thread->ReturnEntity(gameLocal.entities[whichEntNum]); +} + +/* +================ +idEntity::ShowSurface +================ +*/ +void idEntity::ShowSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask &= (~renderEntity.hModel->GetSurfaceMask ( surface )); +} + +/* +================ +idEntity::Event_ShowSurface +================ +*/ +void idEntity::Event_ShowSurface ( const char* surface ) { + ShowSurface ( surface ); +} + +/* +================ +idEntity::HideSurface +================ +*/ +void idEntity::HideSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask |= renderEntity.hModel->GetSurfaceMask ( surface ) ; +} + +/* +================ +idEntity::Event_HideSurface +================ +*/ +void idEntity::Event_HideSurface ( const char* surface ) { + HideSurface ( surface ); +} + +/* +================ +idEntity::Event_GuiEvent +================ +*/ +void idEntity::Event_GuiEvent ( const char* eventName ) { + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( eventName ); + } +} + +/* +================ +idEntity::Event_clearSkin +================ +*/ +void idEntity::Event_ClearSkin( void ) { + ClearSkin(); +} + +/* +================ +idEntity::Event_StopAllEffects +================ +*/ +void idEntity::Event_StopAllEffects ( void ) { + StopAllEffects ( ); +} + +/* +================ +idEntity::Event_GetHealth +================ +*/ +void idEntity::Event_GetHealth ( void ) { + idThread::ReturnFloat( health ); +} + +// jscott: +/* +================ +idEntity::Event_PlaybackCallback +================ +*/ +void idEntity::Event_PlaybackCallback ( int type, int changed, int impulse ) { + common->Printf( "Playback callback type %d - %d/%d\n", type, changed, impulse ); +} + +// nmckenzie: Check who we're bound to. +/* +================ +idEntity::Event_GetBindMaster +================ +*/ + +void idEntity::Event_GetBindMaster ( void ) { + idThread::ReturnEntity( GetBindMaster() ); +} + +/* +================ +idEntity::Event_ApplyImpulse +================ +*/ + +void idEntity::Event_ApplyImpulse( idEntity *source, const idVec3 &point, const idVec3 &impulse ){ + ApplyImpulse( source, 0, point, impulse ); +} + +/* +================ +idEntity::Event_PlayEffect +================ +*/ +void idEntity::Event_PlayEffect( const char *effectName, const char* jointName, bool loop ) { + jointHandle_t joint; + joint = GetAnimator ( ) ? GetAnimator()->GetJointHandle ( jointName ) : INVALID_JOINT; + if ( joint != INVALID_JOINT ) { + PlayEffect ( effectName, joint, loop ); + } else { + PlayEffect ( effectName, renderEntity.origin, renderEntity.axis, loop ); + } +} + +/* +================ +idEntity::Event_StopEffect +================ +*/ +void idEntity::Event_StopEffect( const char *effectName ) { + StopEffect ( effectName ); +} + +// END RAVEN + +/*********************************************************************** + + Misc. + +***********************************************************************/ + +/* +================ +idEntity::Teleport +================ +*/ +void idEntity::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( angles.ToMat3() ); + + UpdateVisuals(); +} + +/* +============ +idEntity::TouchTriggers + + Activate all trigger entities touched at the current position. + + Optionally only activate triggers of ownerType +============ +*/ +bool idEntity::TouchTriggers( const idTypeInfo* ownerType ) const { + int i, numClipModels, numEntities; + idClipModel * cm; + idClipModel * clipModels[ MAX_GENTITIES ]; + idEntity * ent; + trace_t trace; + + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = GetPhysics()->GetOrigin(); + trace.endAxis = GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, clipModels, MAX_GENTITIES ); +// RAVEN END + numEntities = 0; + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModels[ i ]; + + // don't touch it if we're the owner + if ( cm->GetOwner() == this ) { + continue; + } + + ent = cm->GetEntity(); + + if ( !ent->RespondsTo( EV_Touch ) && !ent->HasSignal( SIG_TOUCH ) ) { + continue; + } + + if( ownerType && !ent->IsType( *ownerType ) ) { + continue; + } + +// RAVEN BEGIN +// abahr: needed so tram car can has collision model and touch triggers + bool useSimpleClip = spawnArgs.GetBool("useSimpleTriggerClip"); + if ( !useSimpleClip && !GetPhysics()->ClipContents( cm ) ) { +// RAVEN END + continue; + } + + numEntities++; + + trace.c.contents = cm->GetContents(); + trace.c.entityNum = cm->GetEntity()->entityNumber; + trace.c.id = cm->GetId(); + + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, this, &trace ); + + if ( !gameLocal.entities[ entityNumber ] ) { + gameLocal.Printf( "entity was removed while touching triggers\n" ); + return true; + } + } + + return ( numEntities != 0 ); +} + +/* +================ +idEntity::GetSpline +================ +*/ +idCurve_Spline *idEntity::GetSpline( void ) const { + int i, numPoints, t; + const idKeyValue *kv; + idLexer lex; + idVec3 v; + idCurve_Spline *spline; + const char *curveTag = "curve_"; + + kv = spawnArgs.MatchPrefix( curveTag ); + if ( !kv ) { + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idStr str = kv->GetKey().Right( kv->GetKey().Length() - strlen( curveTag ) ); + if ( str.Icmp( "CatmullRomSpline" ) == 0 ) { + spline = new idCurve_CatmullRomSpline(); + } else if ( str.Icmp( "nubs" ) == 0 ) { + spline = new idCurve_NonUniformBSpline(); + } else if ( str.Icmp( "nurbs" ) == 0 ) { + spline = new idCurve_NURBS(); + } else { + spline = new idCurve_BSpline(); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + spline->SetBoundaryType( idCurve_Spline::BT_CLAMPED ); + + lex.LoadMemory( kv->GetValue(), kv->GetValue().Length(), curveTag ); + numPoints = lex.ParseInt(); + lex.ExpectTokenString( "(" ); + for ( t = i = 0; i < numPoints; i++, t += 100 ) { + v.x = lex.ParseFloat(); + v.y = lex.ParseFloat(); + v.z = lex.ParseFloat(); + spline->AddValue( t, v ); + } + lex.ExpectTokenString( ")" ); + + return spline; +} + +/* +=============== +idEntity::ShowEditingDialog +=============== +*/ +void idEntity::ShowEditingDialog( void ) { +} + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +================ +idEntity::Event_GetName +================ +*/ +void idEntity::Event_GetName( void ) { + idThread::ReturnString( name.c_str() ); +} + +/* +================ +idEntity::Event_SetName +================ +*/ +void idEntity::Event_SetName( const char *newname ) { + SetName( newname ); +} + +/* +=============== +idEntity::Event_FindTargets +=============== +*/ +void idEntity::Event_FindTargets( void ) { + FindTargets(); +} + +/* +============ +idEntity::Event_ActivateTargets + +Activates any entities targeted by this entity. Mainly used as an +event to delay activating targets. +============ +*/ +void idEntity::Event_ActivateTargets( idEntity *activator ) { + ActivateTargets( activator ); +} + +// RAVEN BEGIN +// jshepard: added +/* +============ +idEntity::Event_UnbindTargets + +Unbinds all targets of this entity. Useful to make held or clamped items +drop when shot, and for breakable walls. +============ +*/ +void idEntity::Event_UnbindTargets( idEntity *activator ) { + UnbindTargets( activator ); +} + +// RAVEN END + + +/* +================ +idEntity::Event_NumTargets +================ +*/ +void idEntity::Event_NumTargets( void ) { + idThread::ReturnFloat( targets.Num() ); +} + +/* +================ +idEntity::Event_GetTarget +================ +*/ +void idEntity::Event_GetTarget( float index ) { + int i; + + i = ( int )index; + if ( ( i < 0 ) || i >= targets.Num() ) { + idThread::ReturnEntity( NULL ); + } else { + idThread::ReturnEntity( targets[ i ].GetEntity() ); + } +} + +/* +================ +idEntity::Event_RandomTarget +================ +*/ +void idEntity::Event_RandomTarget( const char *ignore ) { + int num; + idEntity *ent; + int i; + int ignoreNum; + + RemoveNullTargets(); + if ( !targets.Num() ) { + idThread::ReturnEntity( NULL ); + return; + } + + ignoreNum = -1; + if ( ignore && ( ignore[ 0 ] != 0 ) && ( targets.Num() > 1 ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ( ent->name == ignore ) ) { + ignoreNum = i; + break; + } + } + } + + if ( ignoreNum >= 0 ) { + num = gameLocal.random.RandomInt( targets.Num() - 1 ); + if ( num >= ignoreNum ) { + num++; + } + } else { + num = gameLocal.random.RandomInt( targets.Num() ); + } + + ent = targets[ num ].GetEntity(); + idThread::ReturnEntity( ent ); +} + +// RAVEN BEGIN +// abahr: so we can call this from script +/* +================ +idEntity::Event_RemoveNullTargets +================ +*/ +void idEntity::Event_RemoveNullTargets() { + RemoveNullTargets(); +} + +// twhitaker: So targets can be added from script +/* +================ +idEntity::Event_AppendTarget +================ +*/ +void idEntity::Event_AppendTarget( idEntity *appendMe ) { + idThread::ReturnFloat( AppendTarget( appendMe ) ); +} + +/* +================ +idEntity::Event_RemoveTarget +================ +*/ +void idEntity::Event_RemoveTarget( idEntity *removeMe ) { + RemoveTarget( removeMe ); +} + +/* +================ +idEntity::Event_ClearTargetList +================ +*/ +void idEntity::Event_ClearTargetList( float destroyContents ) { + RemoveTargets( destroyContents != 0.0f ); +} + +/* +================ +idEntity::Event_MatchPrefix +================ +*/ +void idEntity::Event_MatchPrefix( const char *prefix, const char* previousKey ) { + const idKeyValue* kv = (previousKey[0]) ? spawnArgs.FindKey(previousKey) : NULL; + + kv = spawnArgs.MatchPrefix( prefix, kv ); + if( !kv || !kv->GetValue() ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( kv->GetKey() ); +} + +/* +================ +idEntity::Event_IsA +================ +*/ +void idEntity::Event_IsA( const char* entityDefName ) { + const idDict* dict = gameLocal.FindEntityDefDict( entityDefName ); + if( !dict ) { + idThread::ReturnFloat( false ); + return; + } + + idTypeInfo* info = idClass::GetClass( dict->GetString("spawnclass") ); + if( !info ) { + idThread::ReturnFloat( false ); + return; + } + + idThread::ReturnFloat( IsType(*info) ); +} + +/* +================ +idEntity::Event_IsSameTypeAs +================ +*/ +void idEntity::Event_IsSameTypeAs( const idEntity* ent ) { + assert( ent ); + + idThread::ReturnFloat( IsType(ent->Type) ); +} + +// mekberg: allow sethealth on all entities. +// jshepard: removed clamping +/* +================ +idEntity::Event_SetHealth +================ +*/ +void idEntity::Event_SetHealth( float newHealth ) { + health = newHealth; +} +// RAVEN END + +/* +================ +idEntity::Event_BindToJoint +================ +*/ +void idEntity::Event_BindToJoint( idEntity *master, const char *jointname, float orientated ) { + BindToJoint( master, jointname, ( orientated != 0.0f ) ); +} + +/* +================ +idEntity::Event_RemoveBinds +================ +*/ +void idEntity::Event_RemoveBinds( void ) { + RemoveBinds(); +} + +/* +================ +idEntity::Event_Bind +================ +*/ +void idEntity::Event_Bind( idEntity *master ) { + Bind( master, true ); +} + +/* +================ +idEntity::Event_BindPosition +================ +*/ +void idEntity::Event_BindPosition( idEntity *master ) { + Bind( master, false ); +} + +/* +================ +idEntity::Event_Unbind +================ +*/ +void idEntity::Event_Unbind( void ) { + Unbind(); +} + +/* +================ +idEntity::Event_SpawnBind +================ +*/ +void idEntity::Event_SpawnBind( void ) { + idEntity *parent; + const char *bind, *joint, *bindanim; + jointHandle_t bindJoint; + bool bindOrientated; + int id; + const idAnim *anim; + int animNum; + idAnimator *parentAnimator; + + if ( spawnArgs.GetString( "bind", "", &bind ) ) { + if ( idStr::Icmp( bind, "worldspawn" ) == 0 ) { + //FIXME: Completely unneccessary since the worldspawn is called "world" + parent = gameLocal.world; + } else { + parent = gameLocal.FindEntity( bind ); + } + bindOrientated = spawnArgs.GetBool( "bindOrientated", "1" ); + if ( parent ) { + // bind to a joint of the skeletal model of the parent + if ( spawnArgs.GetString( "bindToJoint", "", &joint ) && *joint ) { + parentAnimator = parent->GetAnimator(); + if ( !parentAnimator ) { + gameLocal.Error( "Cannot bind to joint '%s' on '%s'. Entity does not support skeletal models.", joint, name.c_str() ); + } + bindJoint = parentAnimator->GetJointHandle( joint ); + if ( bindJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for bind on '%s'", joint, name.c_str() ); + } + + // bind it relative to a specific anim + if ( ( parent->spawnArgs.GetString( "bindanim", "", &bindanim ) || parent->spawnArgs.GetString( "anim", "", &bindanim ) ) && *bindanim ) { + animNum = parentAnimator->GetAnim( bindanim ); + if ( !animNum ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + anim = parentAnimator->GetAnim( animNum ); + if ( !anim ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + + // make sure parent's render origin has been set + parent->UpdateModelTransform(); + + //FIXME: need a BindToJoint that accepts a joint position + parentAnimator->CreateFrame( gameLocal.time, true ); + idJointMat *frame = parent->renderEntity.joints; + gameEdit->ANIM_CreateAnimFrame( parentAnimator->ModelHandle(), anim->MD5Anim( 0 ), parent->renderEntity.numJoints, frame, 0, parentAnimator->ModelDef()->GetVisualOffset(), parentAnimator->RemoveOrigin() ); + BindToJoint( parent, joint, bindOrientated ); + parentAnimator->ForceUpdate(); + } else { + BindToJoint( parent, joint, bindOrientated ); + } + } + // bind to a body of the physics object of the parent + else if ( spawnArgs.GetInt( "bindToBody", "0", id ) ) { + BindToBody( parent, id, bindOrientated ); + } + // bind to the parent + else { + Bind( parent, bindOrientated ); + } + } + } +} + +/* +================ +idEntity::Event_SetOwner +================ +*/ +void idEntity::Event_SetOwner( idEntity *owner ) { + int i; + + for ( i = 0; i < GetPhysics()->GetNumClipModels(); i++ ) { + GetPhysics()->GetClipModel( i )->SetOwner( owner ); + } +} + +/* +================ +idEntity::Event_SetModel +================ +*/ +void idEntity::Event_SetModel( const char *modelname ) { + SetModel( modelname ); +} + +/* +================ +idEntity::Event_SetSkin +================ +*/ +void idEntity::Event_SetSkin( const char *skinname ) { + renderEntity.customSkin = declManager->FindSkin( skinname ); + UpdateVisuals(); +} + +/* +================ +idEntity::Event_GetShaderParm +================ +*/ +void idEntity::Event_GetShaderParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderEntity.shaderParms[ parmnum ] ); +} + +/* +================ +idEntity::Event_SetShaderParm +================ +*/ +void idEntity::Event_SetShaderParm( int parmnum, float value ) { + SetShaderParm( parmnum, value ); +} + +/* +================ +idEntity::Event_SetShaderParms +================ +*/ +void idEntity::Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + UpdateVisuals(); +} + + +/* +================ +idEntity::Event_SetColor +================ +*/ +void idEntity::Event_SetColor( float red, float green, float blue ) { + SetColor( red, green, blue ); +} + +/* +================ +idEntity::Event_GetColor +================ +*/ +void idEntity::Event_GetColor( void ) { + idVec3 out; + + GetColor( out ); + idThread::ReturnVector( out ); +} + +/* +================ +idEntity::Event_IsHidden +================ +*/ +void idEntity::Event_IsHidden( void ) { + idThread::ReturnInt( fl.hidden ); +} + +/* +================ +idEntity::Event_Hide +================ +*/ +void idEntity::Event_Hide( void ) { + Hide(); +} + +/* +================ +idEntity::Event_Show +================ +*/ +void idEntity::Event_Show( void ) { + Show(); +} + +/* +================ +idEntity::Event_CacheSoundShader +================ +*/ +void idEntity::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idEntity::Event_StartSoundShader +================ +*/ +void idEntity::Event_StartSoundShader( const char *soundName, int channel ) { + int length; + + StartSoundShader( declManager->FindSound( soundName ), (s_channelType)channel, 0, false, &length ); + idThread::ReturnFloat( MS2SEC( length ) ); +} + +/* +================ +idEntity::Event_StopSound +================ +*/ +void idEntity::Event_StopSound( int channel, int netSync ) { + StopSound( channel, ( netSync != 0 ) ); +} + +/* +================ +idEntity::Event_StartSound +================ +*/ +void idEntity::Event_StartSound( const char *soundName, int channel, int netSync ) { + int time; + + StartSound( soundName, ( s_channelType )channel, 0, ( netSync != 0 ), &time ); + idThread::ReturnFloat( MS2SEC( time ) ); +} + +/* +================ +idEntity::Event_FadeSound +================ +*/ +void idEntity::Event_FadeSound( int channel, float to, float over ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->FadeSound( channel, to, over ); + } +// RAVEN END +} + +/* +================ +idEntity::Event_GetWorldOrigin +================ +*/ +void idEntity::Event_GetWorldOrigin( void ) { + idThread::ReturnVector( GetPhysics()->GetOrigin() ); +} + +/* +================ +idEntity::Event_SetWorldOrigin +================ +*/ +void idEntity::Event_SetWorldOrigin( idVec3 const &org ) { + idVec3 neworg = GetLocalCoordinates( org ); + SetOrigin( neworg ); +} + +/* +================ +idEntity::Event_SetOrigin +================ +*/ +void idEntity::Event_SetOrigin( idVec3 const &org ) { + SetOrigin( org ); +} + +/* +================ +idEntity::Event_GetOrigin +================ +*/ +void idEntity::Event_GetOrigin( void ) { + idThread::ReturnVector( GetLocalCoordinates( GetPhysics()->GetOrigin() ) ); +} + +/* +================ +idEntity::Event_SetAngles +================ +*/ +void idEntity::Event_SetAngles( idAngles const &ang ) { + SetAngles( ang ); +} + +/* +================ +idEntity::Event_GetAngles +================ +*/ +void idEntity::Event_GetAngles( void ) { + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + idThread::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +/* +================ +idEntity::Event_SetLinearVelocity +================ +*/ +void idEntity::Event_SetLinearVelocity( const idVec3 &velocity ) { + GetPhysics()->SetLinearVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetLinearVelocity +================ +*/ +void idEntity::Event_GetLinearVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetLinearVelocity() ); +} + +/* +================ +idEntity::Event_SetAngularVelocity +================ +*/ +void idEntity::Event_SetAngularVelocity( const idVec3 &velocity ) { + GetPhysics()->SetAngularVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetAngularVelocity +================ +*/ +void idEntity::Event_GetAngularVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetAngularVelocity() ); +} + +/* +================ +idEntity::Event_SetSize +================ +*/ +void idEntity::Event_SetSize( idVec3 const &mins, idVec3 const &maxs ) { + GetPhysics()->SetClipBox( idBounds( mins, maxs ), 1.0f ); +} + +/* +================ +idEntity::Event_GetSize +================ +*/ +void idEntity::Event_GetSize( void ) { + idBounds bounds; + + bounds = GetPhysics()->GetBounds(); + idThread::ReturnVector( bounds[1] - bounds[0] ); +} + +/* +================ +idEntity::Event_GetMins +================ +*/ +void idEntity::Event_GetMins( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[0] ); +} + +/* +================ +idEntity::Event_GetMaxs +================ +*/ +void idEntity::Event_GetMaxs( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[1] ); +} + +/* +================ +idEntity::Event_Touches +================ +*/ +void idEntity::Event_Touches( idEntity *ent ) { + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + const idBounds &myBounds = GetPhysics()->GetAbsBounds(); + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + idThread::ReturnInt( myBounds.IntersectsBounds( entBounds ) ); +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiParm( const char *key, const char *val ) { +// RAVEN BEGIN +// mekberg: added + idStr temp = key; + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) ) { + temp.Insert( "gui_", 0 ); + } + spawnArgs.Set( temp.c_str(), val ); +// RAVEN END + + renderEntity.gui[ i ]->SetStateString( key, val ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiFloat( const char *key, float f ) { + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( key, va( "%f", f ) ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_GetNextKey +================ +*/ +void idEntity::Event_GetNextKey( const char *prefix, const char *lastMatch ) { + const idKeyValue *kv; + const idKeyValue *previous; + + if ( *lastMatch ) { + previous = spawnArgs.FindKey( lastMatch ); + } else { + previous = NULL; + } + + kv = spawnArgs.MatchPrefix( prefix, previous ); + if ( !kv ) { + idThread::ReturnString( "" ); + } else { + idThread::ReturnString( kv->GetKey() ); + } +} + +/* +================ +idEntity::Event_SetKey +================ +*/ +void idEntity::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +/* +================ +idEntity::Event_GetKey +================ +*/ +void idEntity::Event_GetKey( const char *key ) { + const char *value; + + spawnArgs.GetString( key, "", &value ); + idThread::ReturnString( value ); +} + +/* +================ +idEntity::Event_GetIntKey +================ +*/ +void idEntity::Event_GetIntKey( const char *key ) { + int value; + + spawnArgs.GetInt( key, "0", value ); + + // scripts only support floats + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetFloatKey +================ +*/ +void idEntity::Event_GetFloatKey( const char *key ) { + float value; + + spawnArgs.GetFloat( key, "0", value ); + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetVectorKey +================ +*/ +void idEntity::Event_GetVectorKey( const char *key ) { + idVec3 value; + + spawnArgs.GetVector( key, "0 0 0", value ); + idThread::ReturnVector( value ); +} + +/* +================ +idEntity::Event_GetEntityKey +================ +*/ +void idEntity::Event_GetEntityKey( const char *key ) { + idEntity *ent; + const char *entname; + + if ( !spawnArgs.GetString( key, NULL, &entname ) ) { + idThread::ReturnEntity( NULL ); + return; + } + + ent = gameLocal.FindEntity( entname ); + if ( !ent ) { + gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() ); + } + + idThread::ReturnEntity( ent ); +} + +/* +================ +idEntity::Event_RestorePosition +================ +*/ +void idEntity::Event_RestorePosition( void ) { + idVec3 org; + idAngles angles; + idMat3 axis; + idEntity * part; + + spawnArgs.GetVector( "origin", "0 0 0", org ); + + // get the rotation matrix in either full form, or single angle form + if ( spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { + angles = axis.ToAngles(); + } else { + angles[ 0 ] = 0; + angles[ 1 ] = spawnArgs.GetFloat( "angle" ); + angles[ 2 ] = 0; + } + + Teleport( org, angles, NULL ); + + for ( part = teamChain; part != NULL; part = part->teamChain ) { + if ( part->bindMaster != this ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + if ( static_cast(part->GetPhysics())->IsPusher() ) { + gameLocal.Warning( "teleported '%s' which has the pushing mover '%s' bound to it\n", GetName(), part->GetName() ); + } + } else if ( part->GetPhysics()->IsType( idPhysics_AF::GetClassType() ) ) { +// RAVEN END + gameLocal.Warning( "teleported '%s' which has the articulated figure '%s' bound to it\n", GetName(), part->GetName() ); + } + } +} + +/* +================ +idEntity::Event_UpdateCameraTarget +================ +*/ +void idEntity::Event_UpdateCameraTarget( void ) { + const char *target; + const idKeyValue *kv; + idVec3 dir; + + target = spawnArgs.GetString( "cameraTarget" ); + + cameraTarget = gameLocal.FindEntity( target ); + + if ( cameraTarget ) { + kv = cameraTarget->spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent && idStr::Icmp( ent->GetEntityDefName(), "target_null" ) == 0) { + dir = ent->GetPhysics()->GetOrigin() - cameraTarget->GetPhysics()->GetOrigin(); + dir.Normalize(); + cameraTarget->SetAxis( dir.ToMat3() ); +// RAVEN BEGIN +// rjohnson: if you have a func_cameraview pointing to an info_null via "cameratarget" and +// you have a func_static pointing to the func_cameraview via a "cameratarget" then +// the func_static evaluates 'target' to the func_cameraview and its target it the info null +// the SexAxis() is then applied to the the func_static rather than the func_cameraview +// SetAxis(dir.ToMat3()); +// RAVEN END + break; + } + kv = cameraTarget->spawnArgs.MatchPrefix( "target", kv ); + } + } + UpdateVisuals(); +} + +/* +================ +idEntity::Event_DistanceTo +================ +*/ +void idEntity::Event_DistanceTo( idEntity *ent ) { + if ( !ent ) { + // just say it's really far away + idThread::ReturnFloat( MAX_WORLD_SIZE ); + } else { + float dist = ( GetPhysics()->GetOrigin() - ent->GetPhysics()->GetOrigin() ).LengthFast(); + idThread::ReturnFloat( dist ); + } +} + +/* +================ +idEntity::Event_DistanceToPoint +================ +*/ +void idEntity::Event_DistanceToPoint( const idVec3 &point ) { + float dist = ( GetPhysics()->GetOrigin() - point ).LengthFast(); + idThread::ReturnFloat( dist ); +} + +/* +================ +idEntity::Event_StartFx +================ +*/ +void idEntity::Event_StartFx( const char *fx ) { +// RAVEN BEGIN +// bdube: not used +// idEntityFx::StartFx( fx, NULL, NULL, this, true ); +// RAVEN END +} + +/* +================ +idEntity::Event_WaitFrame +================ +*/ +void idEntity::Event_WaitFrame( void ) { + idThread *thread; + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->WaitFrame(); + } +} + +/* +===================== +idEntity::Event_Wait +===================== +*/ +void idEntity::Event_Wait( float time ) { + idThread *thread = idThread::CurrentThread(); + + if ( !thread ) { + gameLocal.Error( "Event 'wait' called from outside thread" ); + } + + thread->WaitSec( time ); +} + +/* +===================== +idEntity::Event_HasFunction +===================== +*/ +void idEntity::Event_HasFunction( const char *name ) { + const function_t *func; + + func = scriptObject.GetFunction( name ); + if ( func ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +===================== +idEntity::Event_CallFunction +===================== +*/ +void idEntity::Event_CallFunction( const char *funcname ) { +// RAVEN BEGIN +// bdube: states + stateParms_t parms = {0}; + if ( ProcessState ( funcname, parms ) != SRESULT_ERROR ) { + return; + } + gameLocal.CallObjectFrameCommand ( this, funcname ); +// RAVEN END +} + +/* +================ +idEntity::Event_SetNeverDormant +================ +*/ +void idEntity::Event_SetNeverDormant( int enable ) { + fl.neverDormant = ( enable != 0 ); + dormantStart = 0; +} + +/*********************************************************************** + + Network + +***********************************************************************/ + +/* +================ +idEntity::ClientPredictionThink +================ +*/ +void idEntity::ClientPredictionThink( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::WriteBindToSnapshot +================ +*/ +void idEntity::WriteBindToSnapshot( idBitMsgDelta &msg ) const { + int bindInfo; + + if ( bindMaster ) { + bindInfo = bindMaster->entityNumber; + bindInfo |= ( fl.bindOrientated & 1 ) << GENTITYNUM_BITS; + if ( bindJoint != INVALID_JOINT ) { + bindInfo |= 1 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindJoint << ( 3 + GENTITYNUM_BITS ); + } else if ( bindBody != -1 ) { + bindInfo |= 2 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindBody << ( 3 + GENTITYNUM_BITS ); + } + } else { + bindInfo = ENTITYNUM_NONE; + } + msg.WriteBits( bindInfo, GENTITYNUM_BITS + 3 + 9 ); +} + +/* +================ +idEntity::ReadBindFromSnapshot +================ +*/ +void idEntity::ReadBindFromSnapshot( const idBitMsgDelta &msg ) { + int bindInfo, bindEntityNum, bindPos; + bool bindOrientated; + idEntity *master; + + bindInfo = msg.ReadBits( GENTITYNUM_BITS + 3 + 9 ); + bindEntityNum = bindInfo & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + + if ( bindEntityNum != ENTITYNUM_NONE ) { + master = gameLocal.entities[ bindEntityNum ]; + + bindOrientated = ( bindInfo >> GENTITYNUM_BITS ) & 1; + bindPos = ( bindInfo >> ( GENTITYNUM_BITS + 3 ) ); + switch( ( bindInfo >> ( GENTITYNUM_BITS + 1 ) ) & 3 ) { + case 1: { + BindToJoint( master, (jointHandle_t) bindPos, bindOrientated ); + break; + } + case 2: { + BindToBody( master, bindPos, bindOrientated ); + break; + } + default: { + Bind( master, bindOrientated ); + break; + } + } + } else if ( bindMaster ) { + Unbind(); + } +} + +/* +================ +idEntity::WriteColorToSnapshot +================ +*/ +void idEntity::WriteColorToSnapshot( idBitMsgDelta &msg ) const { + idVec4 color; + + color[0] = renderEntity.shaderParms[ SHADERPARM_RED ]; + color[1] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + color[2] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + color[3] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; + msg.WriteLong( PackColor( color ) ); +} + +/* +================ +idEntity::ReadColorFromSnapshot +================ +*/ +void idEntity::ReadColorFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 color; + + UnpackColor( msg.ReadLong(), color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[3]; +} + +/* +================ +idEntity::WriteGUIToSnapshot +================ +*/ +void idEntity::WriteGUIToSnapshot( idBitMsgDelta &msg ) const { + // no need to loop over MAX_RENDERENTITY_GUI at this time + if ( renderEntity.gui[ 0 ] ) { + msg.WriteByte( renderEntity.gui[ 0 ]->State().GetInt( "networkState" ) ); + } else { + msg.WriteByte( 0 ); + } +} + +/* +================ +idEntity::ReadGUIFromSnapshot +================ +*/ +void idEntity::ReadGUIFromSnapshot( const idBitMsgDelta &msg ) { + int state; + idUserInterface *gui; + state = msg.ReadByte( ); + gui = renderEntity.gui[ 0 ]; + if ( gui && state != mpGUIState ) { + mpGUIState = state; + gui->SetStateInt( "networkState", state ); + gui->HandleNamedEvent( "networkState" ); + } +} + +/* +================ +idEntity::WriteToSnapshot +================ +*/ +void idEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idEntity::ReadFromSnapshot +================ +*/ +void idEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +idEntity::ServerSendEvent + + Saved events are also sent to any client that connects late so all clients + always receive the events nomatter what time they join the game. + ================ + */ +void idEntity::ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ServerSendReliableMessageExcluding( excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ServerSendInstanceEvent +================ +*/ +void idEntity::ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + gameLocal.ServerSendInstanceReliableMessageExcluding( this, excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ClientSendEvent +================ +*/ +void idEntity::ClientSendEvent( int eventId, const idBitMsg *msg ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isClient ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idEntity::ServerReceiveEvent +================ +*/ +bool idEntity::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case 0: { + } + default: { + return false; + } + } +} + +/* +================ +idEntity::ClientReceiveEvent +================ +*/ +bool idEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + const idSoundShader *shader; + s_channelType channel; + + switch( event ) { + case EVENT_STARTSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + if ( time < gameLocal.realClientTime - 300 ) { + // too old, skip it + common->DPrintf( "ent 0x%x: start sound shader too old (%d ms)\n", entityNumber, gameLocal.realClientTime - time ); + return true; + } + shader = static_cast< const idSoundShader* >( idGameLocal::ReadDecl( msg, DECL_SOUND ) ); + channel = (s_channelType)msg.ReadByte(); + StartSoundShader( shader, channel, 0, false, NULL ); + return true; + } + case EVENT_STOPSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + channel = (s_channelType)msg.ReadByte(); + StopSound( channel, false ); + return true; + } +// RAVEN BEGIN +// bdube: new events + case EVENT_PLAYEFFECT_JOINT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin; + rvClientEffect* clientEffect; + effectCategory_t category; + jointHandle_t jointHandle; + bool loop; + + // TMP - not quite sure this is still used for anything + common->Warning( "FIXME: idEntity::PlayEffect happens" ); + assert( false ); + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + jointHandle = ( jointHandle_t )msg.ReadLong(); + loop = ( msg.ReadBits ( 1 ) != 0 ); + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin ( vec3_origin ); + clientEffect->SetAxis ( mat3_identity ); + clientEffect->Bind( this, jointHandle ); + + clientEffect->Play( time, loop, origin ); + } + return true; + } + + case EVENT_PLAYEFFECT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* clientEffect; + effectCategory_t category; + bool loop; + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + loop = ( msg.ReadBits( 1 ) != 0 ); + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin ( origin ); + clientEffect->SetAxis ( quat.ToMat3() ); + clientEffect->Bind ( this ); + + clientEffect->Play ( time, loop, origin2 ); + } + return true; + } +// RAVEN END + default: { + return false; + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idEntity::ClientStale +================ +*/ +bool idEntity::ClientStale( void ) { + FreeModelDef(); + UpdateVisuals(); + GetPhysics()->UnlinkClip(); + return false; +} + +/* +================ +idEntity::ClientUnstale +================ +*/ +void idEntity::ClientUnstale( void ) { +} + +/* +================ +idEntity::GetDamageEntity + +Returns the entity that should take damage in place of this entity. The default is the +entity itself. +================ +*/ +idEntity* idEntity::GetDamageEntity( void ) { + return forwardDamageEnt.IsValid() ? forwardDamageEnt.GetEntity() : this; +} + +// rjohnson: moved entity info out of idGameLocal into its own function +/* +================ +idEntity::DrawDebugEntityInfo +================ +*/ +void idEntity::DrawDebugEntityInfo( idBounds *viewBounds, idBounds *viewTextBounds, idVec4 *overrideColor ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idMat3 axis = player->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + // skip if the entity is very far away + if ( viewBounds && !viewBounds->IntersectsBounds( GetPhysics()->GetAbsBounds() ) ) { + return; + } + + const idBounds &entBounds = GetPhysics()->GetAbsBounds(); + + if (overrideColor) { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( *overrideColor, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( *overrideColor, entBounds, vec3_origin ); + } + } else { + int contents = GetPhysics()->GetContents(); + if ( contents & CONTENTS_BODY ) { + gameRenderWorld->DebugBounds ( colorCyan, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_TRIGGER ) { + gameRenderWorld->DebugBounds( colorOrange, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_SOLID ) { + gameRenderWorld->DebugBounds( colorGreen, entBounds, vec3_origin ); + } else { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds, vec3_origin ); + } + } + } + + if ( !viewTextBounds || viewTextBounds->IntersectsBounds( entBounds ) ) { + gameRenderWorld->DrawText( name.c_str(), entBounds.GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), entBounds.GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + + if ( gameLocal.GetLocalPlayer() && this != gameLocal.GetLocalPlayer() && teamMaster != gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + } +} + +/* +===================== +idEntity::SetInstance +===================== +*/ +void idEntity::SetInstance( int newInstance ) { + instance = newInstance; + + if( gameLocal.isServer ) { + SetClipWorld( newInstance ); + } +} + +/* +===================== +idEntity::InstanceJoin +Gets called when the local player joins the same instance as this entity +===================== +*/ +void idEntity::InstanceJoin( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ); + + BecomeActive( TH_UPDATEVISUALS ); + Present(); +} + +/* +===================== +idEntity::InstanceLeave +Gets called when the local player leaves the same instance as this entity +===================== +*/ +void idEntity::InstanceLeave( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ); + + FreeLightDef(); + FreeModelDef(); + //RemoveClientEntities(); + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +===================== +idEntity::GetDebugInfo +===================== +*/ +void idEntity::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idClass::GetDebugInfo ( proc, userData ); + + proc ( "idEntity", "health", va("%d",health), userData ); + proc ( "idEntity", "name", name, userData ); + proc ( "idEntity", "entityNumber", va("%d",entityNumber), userData ); + proc ( "idEntity", "origin", renderEntity.origin.ToString ( ), userData ); + + proc ( "idEntity", "notarget", fl.notarget?"true":"false", userData ); + proc ( "idEntity", "takedamage", fl.takedamage?"true":"false", userData ); + proc ( "idEntity", "hidden", fl.hidden?"true":"false", userData ); + proc ( "idEntity", "bindOrientated",fl.bindOrientated?"true":"false", userData ); + proc ( "idEntity", "isDormant", fl.isDormant?"true":"false", userData ); + proc ( "idEntity", "neverDormant", fl.neverDormant?"true":"false", userData ); + proc ( "idEntity", "isAIObstacle", fl.isAIObstacle?"true":"false", userData ); + + proc ( "idEntity", "forwardDamageEnt",forwardDamageEnt.GetEntity() ? forwardDamageEnt.GetEntity()->GetName() : "", userData ); + + proc ( "idEntity", "bindMaster", bindMaster ? bindMaster->GetName() : "", userData ); + proc ( "idEntity", "bindJoint", va("%d",((int)bindJoint)), userData ); + proc ( "idEntity", "bindBody", va("%d",bindBody), userData ); + + proc ( "idEntity", "teamMaster", teamMaster ? teamMaster->GetName() : "", userData ); + proc ( "idEntity", "teamChain", teamChain ? teamChain->GetName() : "", userData ); +} + +// mwhitlock: memory profiling +/* +===================== +idEntity::Size() + +Returns memory size of an idEntity instance +===================== +*/ + +size_t idEntity::Size( void ) const +{ + // TODO: more crap needs to go here! + return sizeof (idEntity); +} +// RAVEN END + + + +/* +=============================================================================== + + idAnimatedEntity + +=============================================================================== +*/ + +const idEventDef EV_GetJointHandle( "getJointHandle", "s", 'd' ); +const idEventDef EV_ClearAllJoints( "clearAllJoints" ); +const idEventDef EV_ClearJoint( "clearJoint", "d" ); +const idEventDef EV_SetJointPos( "setJointPos", "ddv" ); +const idEventDef EV_SetJointAngle( "setJointAngle", "ddv" ); +const idEventDef EV_GetJointPos( "getJointPos", "d", 'v' ); +const idEventDef EV_GetJointAngle( "getJointAngle", "d", 'v' ); + + +// RAVEN BEGIN +// bdube: programmer controlled joint events +const idEventDef EV_SetJointAngularVelocity ( "setJointAngularVelocity", "sfffd" ); +const idEventDef EV_CollapseJoints ( "collapseJoints", "ss" ); +// jshepard: clear out all animations still running on the model +const idEventDef EV_ClearAnims( "clearAnims" ); + +// RAVEN END + +CLASS_DECLARATION( idEntity, idAnimatedEntity ) + EVENT( EV_GetJointHandle, idAnimatedEntity::Event_GetJointHandle ) + EVENT( EV_ClearAllJoints, idAnimatedEntity::Event_ClearAllJoints ) + EVENT( EV_ClearJoint, idAnimatedEntity::Event_ClearJoint ) + EVENT( EV_SetJointPos, idAnimatedEntity::Event_SetJointPos ) + EVENT( EV_SetJointAngle, idAnimatedEntity::Event_SetJointAngle ) + EVENT( EV_GetJointPos, idAnimatedEntity::Event_GetJointPos ) + EVENT( EV_GetJointAngle, idAnimatedEntity::Event_GetJointAngle ) + +// RAVEEN BEGIN +// bdube: programmer controlled joint events + EVENT( EV_SetJointAngularVelocity, idAnimatedEntity::Event_SetJointAngularVelocity ) + EVENT( EV_CollapseJoints, idAnimatedEntity::Event_CollapseJoints ) +// RAVEN END +END_CLASS + +/* +================ +idAnimatedEntity::idAnimatedEntity +================ +*/ +idAnimatedEntity::idAnimatedEntity() { + animator.SetEntity( this ); + damageEffects = NULL; +} + +/* +================ +idAnimatedEntity::~idAnimatedEntity +================ +*/ +idAnimatedEntity::~idAnimatedEntity() { + damageEffect_t *de; + + for ( de = damageEffects; de; de = damageEffects ) { + damageEffects = de->next; + delete de; + } +} + +/* +================ +idAnimatedEntity::Save + +archives object for save game file +================ +*/ +void idAnimatedEntity::Save( idSaveGame *savefile ) const { + animator.Save( savefile ); + + // Wounds are very temporary, ignored at this time + //damageEffect_t *damageEffects; +} + +/* +================ +idAnimatedEntity::Restore + +unarchives object from save game file +================ +*/ +void idAnimatedEntity::Restore( idRestoreGame *savefile ) { + animator.Restore( savefile ); + + // check if the entity has an MD5 model + if ( animator.ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( modelDefHandle != -1 ) { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + } +} + +/* +================ +idAnimatedEntity::ClientPredictionThink +================ +*/ +void idAnimatedEntity::ClientPredictionThink( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::Think +================ +*/ +void idAnimatedEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::UpdateAnimation +================ +*/ +void idAnimatedEntity::UpdateAnimation( void ) { + // don't do animations if they're not enabled + if ( !( thinkFlags & TH_ANIMATE ) ) { + return; + } + + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + +// RAVEN BEGIN +// bgeisler: for triggered anims + // call any frame commands that have happened in the past frame + if ( !fl.hidden || fl.triggerAnim ) { + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } +// RAVEN END + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() && !fl.hidden ) { + gameLocal.DPrintf( "idAnimatedEntity %s %d: inside out bounds - %d\n", GetName(), entityNumber, gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +idAnimatedEntity::GetAnimator +================ +*/ +idAnimator *idAnimatedEntity::GetAnimator( void ) { + return &animator; +} + +/* +================ +idAnimatedEntity::SetModel +================ +*/ +void idAnimatedEntity::SetModel( const char *modelname ) { + FreeModelDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + idEntity::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + UpdateVisuals(); +} + +/* +===================== +idAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool idAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass(); + axis = renderEntity.axis; + return true; + } + + if ( !animator.GetJointTransform( jointHandle, currentTime, offset, axis ) ) { + return false; + } + + ConvertLocalToWorldTransform( offset, axis ); + return true; +} + +/* +============== +idAnimatedEntity::GetJointTransformForAnim +============== +*/ +bool idAnimatedEntity::GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int frameTime, idVec3 &offset, idMat3 &axis ) const { + const idAnim *anim; + int numJoints; + idJointMat *frame; + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass() - GetPhysics()->GetOrigin(); + axis = renderEntity.axis; + return true; + } + + anim = animator.GetAnim( animNum ); + if ( !anim ) { + assert( 0 ); + return false; + } + + numJoints = animator.NumJoints(); + if ( ( jointHandle < 0 ) || ( jointHandle >= numJoints ) ) { + assert( 0 ); + return false; + } + + frame = ( idJointMat * )_alloca16( numJoints * sizeof( idJointMat ) ); + gameEdit->ANIM_CreateAnimFrame( animator.ModelHandle(), anim->MD5Anim( 0 ), renderEntity.numJoints, frame, frameTime, animator.ModelDef()->GetVisualOffset(), animator.RemoveOrigin() ); + + offset = frame[ jointHandle ].ToVec3(); + axis = frame[ jointHandle ].ToMat3(); + + return true; +} + +// RAVEN BEGIN +// ddynerman: removed/merged AddLocalDamageEffect() (redundant math) +/* +============== +idAnimatedEntity::AddDamageEffect + + Dammage effects track the animating impact position, spitting out particles. +============== +*/ +void idAnimatedEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // ddynerman: note, on client the collision struct is incomplete. Only contains impact point and material + const char *splat, *decal, *key; + idVec3 dir; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { + return; + } + + if ( !spawnArgs.GetBool( "bleed" ) ) { + return; + } + + dir = velocity; + dir.Normalize(); + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( collision.c.point[0] ); + msg.WriteFloat( collision.c.point[1] ); + msg.WriteFloat( collision.c.point[2] ); + msg.WriteDir( dir, 24 ); + idGameLocal::WriteDecl( msg, def ); + idGameLocal::WriteDecl( msg, collision.c.material ); + ServerSendInstanceEvent( EVENT_ADD_DAMAGE_EFFECT, &msg, false, -1 ); + } + + if ( !g_decals.GetBool() ) { + return; + } + + if ( gameLocal.GetLocalPlayer() && GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; // no blood from other instances + } + + // blood splats are thrown onto nearby surfaces + splat = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_splat_%s", collision.c.material->GetMaterialType()->GetName() ); + splat = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !splat || !*splat ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + } + if ( splat && *splat ) { + //jshepard original 64.0f + // dluetscher: changed from 64. to 48. for performance reasons + gameLocal.BloodSplat( this, collision.c.point, dir, 48.0f, splat ); + } + + // can't see wounds on the player model in single player mode + if ( !( IsType( idPlayer::GetClassType() ) && !gameLocal.isMultiplayer ) ) { + //If this is a buddy marine, no wound decals until they're actually dead unless it's mp. + if ( gameLocal.isMultiplayer + || !IsType( idAI::GetClassType() ) + || this->health <= 0 + || ((idAI*)this)->team != AITEAM_MARINE ) { + // place a wound overlay on the model + decal = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_wound_%s", collision.c.material->GetMaterialType()->GetName() ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !decal || !*decal ) { + decal = spawnArgs.RandomPrefix( "mtr_wound", gameLocal.random ); + } + if ( decal && *decal ) { + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + if( IsType( idPlayer::GetClassType() ) ) { + ProjectHeadOverlay( collision.c.point, dir, 20.0f, decal ); + } + } + } + } +} +// RAVEN END + +/* +============== +idAnimatedEntity::GetDefaultSurfaceType +============== +*/ +int idAnimatedEntity::GetDefaultSurfaceType( void ) const { + return SURFTYPE_METAL; +} + +/* +================ +idAnimatedEntity::ClientReceiveEvent +================ +*/ +bool idAnimatedEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 origin, dir; + + switch( event ) { + case EVENT_ADD_DAMAGE_EFFECT: { + origin[0] = msg.ReadFloat(); + origin[1] = msg.ReadFloat(); + origin[2] = msg.ReadFloat(); + dir = msg.ReadDir( 24 ); + const idDeclEntityDef *damageDef = static_cast< const idDeclEntityDef* >( idGameLocal::ReadDecl( msg, DECL_ENTITYDEF ) ); + const idMaterial *collisionMaterial = static_cast< const idMaterial* >( idGameLocal::ReadDecl( msg, DECL_MATERIAL ) ); +// RAVEN BEGIN +// ddynerman: removed redundant AddLocalDamageEffect() + trace_t collision; + collision.c.point = origin; + collision.c.material = collisionMaterial; + AddDamageEffect( collision, dir, damageDef->GetName(), NULL ); +// RAVEN END + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// abahr: so we don't crash if UpdateModel is called from a destructor +/* +================ +idAnimatedEntity::UpdateRenderEntityCallback +================ +*/ +void idAnimatedEntity::UpdateRenderEntityCallback() { + // check if the entity has an MD5 model + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + } +} +// RAVEN END + +/* +================ +idAnimatedEntity::Event_GetJointHandle + +looks up the number of the specified joint. returns INVALID_JOINT if the joint is not found. +================ +*/ +void idAnimatedEntity::Event_GetJointHandle( const char *jointname ) { + jointHandle_t joint; + + joint = animator.GetJointHandle( jointname ); + idThread::ReturnInt( joint ); +} + +/* +================ +idAnimatedEntity::Event_ClearAllJoints + +removes any custom transforms on all joints +================ +*/ +void idAnimatedEntity::Event_ClearAllJoints( void ) { + animator.ClearAllJoints(); +} + +/* +================ +idAnimatedEntity::Event_ClearJoint + +removes any custom transforms on the specified joint +================ +*/ +void idAnimatedEntity::Event_ClearJoint( jointHandle_t jointnum ) { + animator.ClearJoint( jointnum ); +} + +/* +================ +idAnimatedEntity::Event_ClearAnims + +Clears any animation running on the animated entity +================ +*/ +void idAnimatedEntity::Event_ClearAnims( void ) { + animator.Clear( ANIMCHANNEL_ALL, gameLocal.GetTime(), gameLocal.GetTime() ); +} + +/* +================ +idAnimatedEntity::Event_SetJointPos + +modifies the position of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { + animator.SetJointPos( jointnum, transform_type, pos ); +} + +/* +================ +idAnimatedEntity::Event_SetJointAngle + +modifies the orientation of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ) { + idMat3 mat; + + mat = angles.ToMat3(); + animator.SetJointAxis( jointnum, transform_type, mat ); +} + +/* +================ +idAnimatedEntity::Event_GetJointPos + +returns the position of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idThread::ReturnVector( offset ); +} + +/* +================ +idAnimatedEntity::Event_GetJointAngle + +returns the orientation of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointAngle( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idAngles ang = axis.ToAngles(); + idVec3 vec( ang[ 0 ], ang[ 1 ], ang[ 2 ] ); + idThread::ReturnVector( vec ); +} + +// RAVEN BEGIN +// bdube: moved to idAnimatedEntity +/* +================ +idAnimatedEntity::Event_SetJointAngularVelocity +================ +*/ +void idAnimatedEntity::Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ) { + jointHandle_t joint = animator.GetJointHandle ( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + animator.SetJointAngularVelocity ( joint, idAngles(pitch,yaw,roll), gameLocal.time, blendTime ); +} + +/* +================ +idAnimatedEntity::Event_CollapseJoints +================ +*/ +void idAnimatedEntity::Event_CollapseJoints ( const char* jointnames, const char* collapseTo ) { + jointHandle_t collapseToJoint = animator.GetJointHandle ( collapseTo ); + if ( collapseToJoint == INVALID_JOINT ) { + return; + } + + animator.CollapseJoints ( jointnames, collapseToJoint ); +} +// RAVEN END + diff --git a/src/game/Entity.h b/src/game/Entity.h new file mode 100644 index 0000000..5190c8f --- /dev/null +++ b/src/game/Entity.h @@ -0,0 +1,872 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_ENTITY_H__ +#define __GAME_ENTITY_H__ + +/* +=============================================================================== + + Game entity base class. + +=============================================================================== +*/ + +static const int DELAY_DORMANT_TIME = 3000; + +extern const idEventDef EV_PostSpawn; +extern const idEventDef EV_FindTargets; +extern const idEventDef EV_Touch; +extern const idEventDef EV_Use; +extern const idEventDef EV_Activate; +extern const idEventDef EV_ActivateTargets; +extern const idEventDef EV_Hide; +extern const idEventDef EV_Show; +extern const idEventDef EV_GetShaderParm; +extern const idEventDef EV_SetShaderParm; +extern const idEventDef EV_SetOwner; +extern const idEventDef EV_GetAngles; +extern const idEventDef EV_SetAngles; +extern const idEventDef EV_SetLinearVelocity; +extern const idEventDef EV_SetAngularVelocity; +extern const idEventDef EV_SetSkin; +extern const idEventDef EV_StartSoundShader; +extern const idEventDef EV_StopSound; +extern const idEventDef EV_CacheSoundShader; + +// RAVEN BEGIN +extern const idEventDef EV_CallFunction; +extern const idEventDef EV_SetGuiParm; +extern const idEventDef EV_SetGuiFloat; +extern const idEventDef EV_ClearSkin; +// bdube: more global events for idEntity +extern const idEventDef EV_GetFloatKey; +extern const idEventDef EV_HideSurface; +extern const idEventDef EV_ShowSurface; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_StopAllEffects; +extern const idEventDef EV_PlayEffect; +extern const idEventDef EV_Earthquake; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_SetKey; +// jscott: +extern const idEventDef EV_PlaybackCallback; +// jshepard: +extern const idEventDef EV_UnbindTargets; +// twhitaker: +extern const idEventDef EV_ApplyImpulse; +// RAVEN END + +class idGuidedProjectile; + +// Think flags +enum { + TH_ALL = -1, + TH_THINK = 1, // run think function each frame + TH_PHYSICS = 2, // run physics each frame + TH_ANIMATE = 4, // update animation each frame + TH_UPDATEVISUALS = 8, // update renderEntity +}; + +// +// Signals +// make sure to change script/doom_defs.script if you add any, or change their order +// +typedef enum { + SIG_TOUCH, // object was touched + SIG_USE, // object was used + SIG_TRIGGER, // object was activated + SIG_REMOVED, // object was removed from the game + SIG_DAMAGE, // object was damaged + SIG_BLOCKED, // object was blocked + + SIG_MOVER_POS1, // mover at position 1 (door closed) + SIG_MOVER_POS2, // mover at position 2 (door open) + SIG_MOVER_1TO2, // mover changing from position 1 to 2 + SIG_MOVER_2TO1, // mover changing from position 2 to 1 + +// RAVEN BEGIN +// kfuller: added signals + // WARNING: these entries are mirrored in scripts/defs.script so make sure if they move + // here that they move there as well + SIG_REACHED, // object reached it's rotation/motion destination +// RAVEN END + + NUM_SIGNALS +} signalNum_t; + +// FIXME: At some point we may want to just limit it to one thread per signal, but +// for now, I'm allowing multiple threads. We should reevaluate this later in the project +#define MAX_SIGNAL_THREADS 16 // probably overkill, but idList uses a granularity of 16 + +struct signal_t { + int threadnum; + const function_t *function; +}; + +class signalList_t { +public: + idList signal[ NUM_SIGNALS ]; +}; + +class idEntity : public idClass { +public: + static const int MAX_PVS_AREAS = 4; + + int entityNumber; // index into the entity list + int entityDefNumber; // index into the entity def list + + idLinkList spawnNode; // for being linked into spawnedEntities list + idLinkList activeNode; // for being linked into activeEntities list + + idLinkList snapshotNode; // for being linked into snapshotEntities list + int snapshotSequence; // last snapshot this entity was in + int snapshotBits; // number of bits this entity occupied in the last snapshot + + idStr name; // name of entity + idDict spawnArgs; // key/value pairs used to spawn and initialize entity + idScriptObject scriptObject; // contains all script defined data for this entity + + int thinkFlags; // TH_? flags + int dormantStart; // time that the entity was first closed off from player + bool cinematic; // during cinematics, entity will only think if cinematic is set + + renderView_t * renderView; // for camera views from this entity + idEntity * cameraTarget; // any remoteRenderMap shaders will use this + + idList< idEntityPtr > targets; // when this entity is activated these entities entity are activated + + int health; // FIXME: do all objects really need health? + +// RAVEN BEGIN +// ddynerman: optional pre-prediction + int predictTime; +// bdube: client entities + idLinkList clientEntities; + +// rjohnson: will now draw entity info for long thinkers + int mLastLongThinkTime; + idVec4 mLastLongThinkColor; +// RAVEN END + + struct entityFlags_s { + bool notarget :1; // if true never attack or target this entity + bool noknockback :1; // if true no knockback from hits + bool takedamage :1; // if true this entity can be damaged + bool hidden :1; // if true this entity is not visible + bool bindOrientated :1; // if true both the master orientation is used for binding + bool solidForTeam :1; // if true this entity is considered solid when a physics team mate pushes entities + bool forcePhysicsUpdate :1; // if true always update from the physics whether the object moved or not + bool selected :1; // if true the entity is selected for editing + bool neverDormant :1; // if true the entity never goes dormant + bool isDormant :1; // if true the entity is dormant + bool hasAwakened :1; // before a monster has been awakened the first time, use full PVS for dormant instead of area-connected + bool networkSync :1; // if true the entity is synchronized over the network + +// RAVEN BEGIN +// bdube: added + bool networkStale :1; // was in the snapshot but isnt anymore +// bgeisler: added block + bool triggerAnim :1; + bool usable :1; // if true the entity is usable by the player +// cdr: Obstacle Avoidance + bool isAIObstacle :1; // if true, this entity will add itself to the obstacles list in each aas area it touches +// RAVEN END + +// RAVEN BEGIN +// ddynerman: exclude this entity from instance-purging + // twhitaker: moved variable to be within the bit flags + bool persistAcrossInstances :1; +// twhitaker: exited vehicle already? + bool exitedVehicle :1; +// twhitaker: blinking + bool allowAutoBlink :1; +// jshepard: instant burnout when destroyed + bool quickBurn :1; + +// RAVEN END + } fl; + +public: + ABSTRACT_PROTOTYPE( idEntity ); + + idEntity(); + ~idEntity(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + const char * GetEntityDefName( void ) const; + void SetName( const char *name ); + const char * GetName( void ) const; + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + // clients generate views based on all the player specific options, + // cameras have custom code, and everything else just uses the axis orientation + virtual renderView_t * GetRenderView(); + + // thinking + virtual void Think( void ); + bool CheckDormant( void ); // dormant == on the active list, but out of PVS + virtual void DormantBegin( void ); // called when entity becomes dormant + virtual void DormantEnd( void ); // called when entity wakes from being dormant + bool IsActive( void ) const; + void BecomeActive( int flags ); + void BecomeInactive( int flags ); + void UpdatePVSAreas( const idVec3 &pos ); + +// RAVEN BEGIN +// abahr: + bool IsActive( int flags ) const { return (flags & thinkFlags) > 0; } + const char* GetEntityDefClassName() const { return spawnArgs.GetString("classname"); } + bool IsEntityDefClass( const char* className ) const { return !idStr::Icmp(className, GetEntityDefClassName()); } + virtual void GetPosition( idVec3& origin, idMat3& axis ) const; +// kfuller: added methods + virtual void GetLocalAngles(idAngles &localAng); +// RAVEN END + + // visuals + virtual void Present( void ); + // instance visuals + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); +// RAVEN BEGIN +// bdube: removed virtual so it could be inlined + renderEntity_t * GetRenderEntity( void ); + int GetModelDefHandle( void ); +// RAVEN END + virtual void SetModel( const char *modelname ); + void SetSkin( const idDeclSkin *skin ); + const idDeclSkin * GetSkin( void ) const; + +// RAVEN BEGIN +// bdube: surfaces + void HideSurface ( const char* surface ); + void ShowSurface ( const char* surface ); + void ClearSkin( void ); +// RAVEN END + + void SetShaderParm( int parmnum, float value ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec3 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec4 &out ) const; + virtual void FreeModelDef( void ); + virtual void FreeLightDef( void ); + virtual void Hide( void ); + virtual void Show( void ); + bool IsHidden( void ) const; + void UpdateVisuals( void ); + void UpdateModel( void ); +// RAVEN BEGIN +// abahr: added virtual to UpdateModelTransform + virtual + void UpdateModelTransform( void ); + virtual void UpdateRenderEntityCallback(); + virtual const idAnimator * GetAnimator( void ) const { return NULL; } // returns animator object used by this entity +// RAVEN END + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + int GetNumPVSAreas( void ); + const int * GetPVSAreas( void ); + void ClearPVSAreas( void ); + bool PhysicsTeamInPVS( pvsHandle_t pvsHandle ); + + // animation + virtual bool UpdateAnimationControllers( void ); + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + virtual idAnimator * GetAnimator( void ); // returns animator object used by this entity + + // sound + virtual bool CanPlayChatterSounds( void ) const; + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound( const s_channelType channel, bool broadcast ); // pass SND_CHANNEL_ANY to stop all sounds + void SetSoundVolume( float volume ); + void UpdateSound( void ); + int GetListenerId( void ) const; +// RAVEN BEGIN + int GetSoundEmitter( void ) const; +// RAVEN END + void FreeSoundEmitter( bool immediate ); + +// RAVEN BEGIN +// bdube: added effect functions + // effects + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + + rvClientEffect* PlayEffect ( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + void StopEffect ( const char* effectName, bool destroyParticles = false ); + void StopEffect ( const idDecl *effect, bool destroyParticles = false ); + void StopAllEffects ( bool destroyParticles = false ); + void UpdateEffects ( void ); + + float DistanceTo ( idEntity* ent ); + float DistanceTo ( const idVec3& pos ) const; + float DistanceTo2d ( idEntity* ent ); + float DistanceTo2d ( const idVec3& pos ) const; + + virtual bool CanTakeDamage ( void ) const; +// RAVEN END + + // entity binding + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void PreUnbind( void ); + virtual void PostUnbind( void ); + void JoinTeam( idEntity *teammember ); + void Bind( idEntity *master, bool orientated ); + void BindToJoint( idEntity *master, const char *jointname, bool orientated ); + void BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ); + void BindToBody( idEntity *master, int bodyId, bool orientated ); + void Unbind( void ); + bool IsBound( void ) const; +// RAVEN BEGIN +// abahr: added const so it can be called from const functions + bool IsBoundTo( const idEntity *master ) const; +// RAVEN END + idEntity * GetBindMaster( void ) const; + jointHandle_t GetBindJoint( void ) const; + int GetBindBody( void ) const; + idEntity * GetTeamMaster( void ) const; + idEntity * GetNextTeamEntity( void ) const; +// RAVEN BEGIN +// abahr: added virtual + virtual + void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); +// RAVEN END + idVec3 GetLocalVector( const idVec3 &vec ) const; + idVec3 GetLocalCoordinates( const idVec3 &vec ) const; + idVec3 GetWorldVector( const idVec3 &vec ) const; + idVec3 GetWorldCoordinates( const idVec3 &vec ) const; + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + void GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const; + + // physics + // set a new physics object to be used by this entity + void SetPhysics( idPhysics *phys ); + // get the physics object used by this entity + idPhysics * GetPhysics( void ) const; + // restore physics pointer for save games + void RestorePhysics( idPhysics *phys ); + // run the physics for this entity + bool RunPhysics( void ); + // set the origin of the physics object (relative to bindMaster if not NULL) + void SetOrigin( const idVec3 &org ); + // set the axis of the physics object (relative to bindMaster if not NULL) + void SetAxis( const idMat3 &axis ); + // use angles to set the axis of the physics object (relative to bindMaster if not NULL) + void SetAngles( const idAngles &ang ); + // get the floor position underneath the physics object + bool GetFloorPos( float max_dist, idVec3 &floorpos ) const; + // retrieves the transformation going from the physics origin/axis to the visual origin/axis + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + // retrieves the transformation going from the physics origin/axis to the sound origin/axis + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + // called from the physics object when colliding, should return true if the physics simulation should stop + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { hitTeleporter = false; return Collide(collision, velocity); } + // retrieves impact information, 'ent' is the entity retrieving the info + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + // apply an impulse to the physics object, 'ent' is the entity applying the impulse + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + // add a force to the physics object, 'ent' is the entity adding the force + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + // activate the physics object, 'ent' is the entity activating this entity + virtual void ActivatePhysics( idEntity *ent ); + // returns true if the physics object is at rest + virtual bool IsAtRest( void ) const; + // returns the time the physics object came to rest + virtual int GetRestStartTime( void ) const; + // add a contact entity + virtual void AddContactEntity( idEntity *ent ); + // remove a touching entity + virtual void RemoveContactEntity( idEntity *ent ); + +// RAVEN BEGIN +// kfuller: added blocked methods + virtual void LastBlockedBy(int blockedEntNum) {} + virtual int GetLastBlocker(void) { return -1; } +// rjohnson: moved entity info out of idGameLocal into its own function + virtual void DrawDebugEntityInfo( idBounds *viewBounds = 0, idBounds *viewTextBounds = 0, idVec4 *overrideColor = 0 ); +// nmckenzie: Adding ability for non-Actors to be enemies for AI characters. Rename this function at some point, most likely. + virtual idVec3 GetEyePosition( void ) const { return GetPhysics()->GetOrigin(); } +// abahr: + virtual bool SkipImpulse( idEntity *ent, int id ); + virtual void ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ); +// RAVEN END + + // damage +// RAVEN BEGIN + // twhitaker: // Sets the damage enitty + void SetDamageEntity ( idEntity * forward ) { forwardDamageEnt = forward; } + + // bdube: added ignore entity + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + + // returns true if this entity can be damaged from the given origin + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt = NULL ) const; +// RAVEN END + // applies damage to this entity + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + // adds a damage effect like overlays, blood, sparks, debris etc. + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + // callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + // notifies this entity that it is in pain + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + // notifies this entity that is has been killed + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // scripting + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + virtual idThread * ConstructScriptObject( void ); + virtual void DeconstructScriptObject( void ); + void SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ); + void ClearSignal( idThread *thread, signalNum_t signalnum ); + void ClearSignalThread( signalNum_t signalnum, idThread *thread ); + bool HasSignal( signalNum_t signalnum ) const; + void Signal( signalNum_t signalnum ); + void SignalEvent( idThread *thread, signalNum_t signalnum ); + + // gui + void TriggerGuis( void ); + bool HandleGuiCommands( idEntity *entityGui, const char *cmds ); + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + + // targets +// RAVEN BEGIN +// abahr: made virtual + virtual + void FindTargets( void ); + virtual + void RemoveNullTargets( void ); + virtual + void ActivateTargets( idEntity *activator ) const; +// jshepard: unbind targets + void UnbindTargets( idEntity *activator ) const; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: Add to the list of targets (usually from script) + int AppendTarget( idEntity *appendMe ); + void RemoveTarget( idEntity *removeMe ); + void RemoveTargets( bool destroyContents ); +// RAVEN END + + // misc + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + bool TouchTriggers( const idTypeInfo* ownerType = NULL ) const; + idCurve_Spline *GetSpline( void ) const; + virtual void ShowEditingDialog( void ); + + enum { + EVENT_STARTSOUNDSHADER, + EVENT_STOPSOUNDSHADER, +// RAVEN BEGIN +// bdube: new events + EVENT_PLAYEFFECT, + EVENT_PLAYEFFECT_JOINT, + EVENT_STOPEFFECT, +// RAVEN END + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); +// RAVEN BEGIN + // the entity was not in the snapshot sent by server. means it either went out of PVS, or was deleted server side + // return true if the entity wishes to be deleted locally + // depending on the entity, understanding stale as just another state, or a removal is best + // ( by default, idEntity keeps the entity around after a little cleanup ) + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); +// RAVEN END + + void WriteBindToSnapshot( idBitMsgDelta &msg ) const; + void ReadBindFromSnapshot( const idBitMsgDelta &msg ); + void WriteColorToSnapshot( idBitMsgDelta &msg ) const; + void ReadColorFromSnapshot( const idBitMsgDelta &msg ); + void WriteGUIToSnapshot( idBitMsgDelta &msg ) const; + void ReadGUIFromSnapshot( const idBitMsgDelta &msg ); + + void ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ClientSendEvent( int eventId, const idBitMsg *msg ) const; + +// RAVEN BEGIN +// bdube: debugging + virtual void GetDebugInfo( debugInfoProc_t proc, void* userData ); +// mwhitlock: memory profiling + virtual size_t Size( void ) const; +// ddynerman: multiple arenas (for MP) + virtual void SetInstance( int newInstance ); + virtual int GetInstance( void ) const; +// ddynerman: multiple clip world support + virtual int GetClipWorld( void ) const; + virtual void SetClipWorld( int newCW ); +// scork: accessors so sound editor can indicate current-highlit ent + virtual int GetRefSoundShaderFlags( void ) const; + virtual void SetRefSoundShaderFlags( int iFlags ); +// twhitaker: guided projectiles + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ) { } +// RAVEN END + +protected: + renderEntity_t renderEntity; // used to present a model to the renderer + int modelDefHandle; // handle to static renderer model + refSound_t refSound; // used to present sound to the audio engine + idEntityPtr< idEntity > forwardDamageEnt; // damage applied to the invoking object will be forwarded to this entity + idEntityPtr< idEntity > bindMaster; // entity bound to if unequal NULL + jointHandle_t bindJoint; // joint bound to if unequal INVALID_JOINT +private: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics * physics; // physics used for this entity + int bindBody; // body bound to if unequal -1 + idEntity * teamMaster; // master of the physics team + idEntity * teamChain; // next entity in physics team + + int numPVSAreas; // number of renderer areas the entity covers + int PVSAreas[MAX_PVS_AREAS]; // numbers of the renderer areas the entity covers + + signalList_t * signals; + + int mpGUIState; // local cache to avoid systematic SetStateInt +// RAVEN BEGIN +// abahr: changed to protected for access in children classes +protected: +// ddynerman: multiple game instances + int instance; +// ddynerman: multiple collision worlds + int clipWorld; +// RAVEN END + +// RAVEN BEGIN +// bdube: made virtual + virtual bool DoDormantTests( void ); // dormant == on the active list, but out of PVS +// RAVEN END + + // physics + // initialize the default physics + void InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ); + // update visual position from the physics + void UpdateFromPhysics( bool moveBack ); + + // entity binding + bool InitBind( idEntity *master ); // initialize an entity binding + void FinishBind( void ); // finish an entity binding + void RemoveBinds( void ); // deletes any entities bound to this object + void QuitTeam( void ); // leave the current team + + void UpdatePVSAreas( void ); + +// RAVEN BEGIN +// bdube: client entities + void RemoveClientEntities ( void ); // deletes any client entities bound to this object +// RAVEN END + + // events + void Event_GetName( void ); + void Event_SetName( const char *name ); + void Event_FindTargets( void ); + void Event_ActivateTargets( idEntity *activator ); + void Event_NumTargets( void ); + void Event_GetTarget( float index ); + void Event_RandomTarget( const char *ignore ); + void Event_Bind( idEntity *master ); + void Event_BindPosition( idEntity *master ); + void Event_BindToJoint( idEntity *master, const char *jointname, float orientated ); + void Event_Unbind( void ); + void Event_RemoveBinds( void ); + void Event_SpawnBind( void ); + void Event_SetOwner( idEntity *owner ); + void Event_SetModel( const char *modelname ); + void Event_SetSkin( const char *skinname ); + void Event_GetShaderParm( int parmnum ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetColor( float red, float green, float blue ); + void Event_GetColor( void ); + void Event_IsHidden( void ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_StartSoundShader( const char *soundName, int channel ); + void Event_StopSound( int channel, int netSync ); + void Event_StartSound( const char *soundName, int channel, int netSync ); + void Event_FadeSound( int channel, float to, float over ); + void Event_GetWorldOrigin( void ); + void Event_SetWorldOrigin( idVec3 const &org ); + void Event_GetOrigin( void ); + void Event_SetOrigin( const idVec3 &org ); + void Event_GetAngles( void ); + void Event_SetAngles( const idAngles &ang ); + void Event_SetLinearVelocity( const idVec3 &velocity ); + void Event_GetLinearVelocity( void ); + void Event_SetAngularVelocity( const idVec3 &velocity ); + void Event_GetAngularVelocity( void ); + void Event_SetSize( const idVec3 &mins, const idVec3 &maxs ); + void Event_GetSize( void ); + void Event_GetMins( void ); + void Event_GetMaxs( void ); + void Event_Touches( idEntity *ent ); + void Event_SetGuiParm( const char *key, const char *val ); + void Event_SetGuiFloat( const char *key, float f ); + void Event_GetNextKey( const char *prefix, const char *lastMatch ); + void Event_SetKey( const char *key, const char *value ); + void Event_GetKey( const char *key ); + void Event_GetIntKey( const char *key ); + void Event_GetFloatKey( const char *key ); + void Event_GetVectorKey( const char *key ); + void Event_GetEntityKey( const char *key ); + void Event_RestorePosition( void ); + void Event_UpdateCameraTarget( void ); + void Event_DistanceTo( idEntity *ent ); + void Event_DistanceToPoint( const idVec3 &point ); + void Event_StartFx( const char *fx ); + void Event_WaitFrame( void ); + void Event_Wait( float time ); + void Event_HasFunction( const char *name ); + void Event_CallFunction( const char *name ); + void Event_SetNeverDormant( int enable ); + +// RAVEN BEGIN +// kfuller: added events + void Event_SetContents ( int contents ); + void Event_GetLastBlocker ( idThread *thread ); +// begisler: added + void Event_ClearSkin ( void ); +// bdube: effect events + void Event_PlayEffect ( const char* effectName, const char* boneName, bool loop ); + void Event_StopEffect ( const char* effectName ); + void Event_StopAllEffects ( void ); + void Event_GetHealth ( void ); +// bdube: mesh events + void Event_ShowSurface ( const char* surface ); + void Event_HideSurface ( const char* surface ); +// bdube: gui events + void Event_GuiEvent ( const char* eventName ); +// jscott: + void Event_PlaybackCallback ( int type, int changed, int impulse ); +// nmckenzie: get bind master + void Event_GetBindMaster ( void ); + void Event_ApplyImpulse ( idEntity *source, const idVec3 &point, const idVec3 &impulse ); +// jshepard: unbind all targets + void Event_UnbindTargets ( idEntity *activator); +// abahr: + void Event_RemoveNullTargets (); + void Event_IsA ( const char* entityDefName ); + void Event_IsSameTypeAs ( const idEntity* ent ); + void Event_MatchPrefix ( const char *prefix, const char* previousKey ); + void Event_ClearTargetList ( float destroyContents ); +// twhitaker: added - to add targets from script + void Event_AppendTarget ( idEntity *appendMe ); + void Event_RemoveTarget ( idEntity *removeMe ); +// mekberg: added + void Event_SetHealth ( float newHealth ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added inlines +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, + bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), origin, axis, loop, endOrigin, broadcast, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, bool loop, const idVec3& endOrigin, + bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, vec3_origin, mat3_identity, loop, endOrigin, broadcast, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, + bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, originOffset, axisOffset, loop, endOrigin, broadcast, category, effectTint ); +} + + +ID_INLINE idPhysics *idEntity::GetPhysics( void ) const { + return physics; +} + +ID_INLINE renderEntity_t *idEntity::GetRenderEntity( void ) { + return &renderEntity; +} + +ID_INLINE int idEntity::GetModelDefHandle( void ) { + return modelDefHandle; +} + +// scork: accessors so sound editor can indicate current-highlit ent +ID_INLINE int idEntity::GetRefSoundShaderFlags( void ) const +{ + return refSound.parms.soundShaderFlags; +} + +ID_INLINE void idEntity::SetRefSoundShaderFlags( int iFlags ) +{ + refSound.parms.soundShaderFlags = iFlags; +} + + +// RAVEN END + +/* +=============================================================================== + + Animated entity base class. + +=============================================================================== +*/ + +typedef struct damageEffect_s { + jointHandle_t jointNum; + idVec3 localOrigin; + idVec3 localNormal; + int time; +// RAVEN BEGIN +// jscott: not using +// const idDeclParticle* type; +// RAVEN END + struct damageEffect_s * next; +} damageEffect_t; + +class idAnimatedEntity : public idEntity { +public: + CLASS_PROTOTYPE( idAnimatedEntity ); + + idAnimatedEntity(); + ~idAnimatedEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientPredictionThink( void ); + virtual void Think( void ); + + void UpdateAnimation( void ); + + virtual idAnimator * GetAnimator( void ); + virtual void SetModel( const char *modelname ); + + bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + bool GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int currentTime, idVec3 &offset, idMat3 &axis ) const; + + virtual int GetDefaultSurfaceType( void ) const; + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) {} + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +// RAVEN BEGIN +// abahr: + virtual const idAnimator * GetAnimator( void ) const { return &animator; } + virtual void UpdateRenderEntityCallback(); +// RAVEN BEGIN + + enum { + EVENT_ADD_DAMAGE_EFFECT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + +protected: + idAnimator animator; + damageEffect_t * damageEffects; + +// RAVEN BEGIN +// jshepard: + void Event_ClearAnims ( void ); +// RAVEN END + +private: + void Event_GetJointHandle( const char *jointname ); + void Event_ClearAllJoints( void ); + void Event_ClearJoint( jointHandle_t jointnum ); + void Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ); + void Event_GetJointPos( jointHandle_t jointnum ); + void Event_GetJointAngle( jointHandle_t jointnum ); + +// RAVEN BEGIN +// bdube: programmer controlled joint events + void Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ); + void Event_CollapseJoints ( const char* jointnames, const char* collapseTo ); + + + +// RAVEN END +}; + +// RAVEN BEGIN +void UpdateGuiParms( idUserInterface *gui, const idDict *args ); +// RAVEN END + +ID_INLINE float idEntity::DistanceTo ( idEntity* ent ) { + return DistanceTo ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE float idEntity::DistanceTo ( const idVec3& pos ) const { + return (pos - GetPhysics()->GetOrigin()).LengthFast ( ); +} + +ID_INLINE float idEntity::DistanceTo2d ( idEntity* ent ) { + return DistanceTo2d ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE bool idEntity::CanTakeDamage ( void ) const { + return fl.takedamage; +} + +// RAVEN BEGIN +// ddynerman: MP arena stuff +ID_INLINE int idEntity::GetInstance( void ) const { + return instance; +} + +// ddynerman: multiple collision worlds +ID_INLINE int idEntity::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idEntity::SetClipWorld( int newCW ) { + clipWorld = newCW; + if( GetPhysics() ) { + GetPhysics()->UnlinkClip(); + GetPhysics()->LinkClip(); + } +} +// RAVEN END +#endif /* !__GAME_ENTITY_H__ */ diff --git a/src/game/FreeView.cpp b/src/game/FreeView.cpp new file mode 100644 index 0000000..73a4ed7 --- /dev/null +++ b/src/game/FreeView.cpp @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +=============== +idFreeView::~idFreeView +=============== +*/ +idFreeView::~idFreeView() { + if ( physics ) { + delete physics; + physics = NULL; + } +} + +/* +=============== +idFreeView::SetFreeView +=============== +*/ +void idFreeView::SetFreeView( int clientNum ) { + trace_t trace; + idPlayer *p; + idVec3 start, end; + idClip *clipWorld; + + if ( !physics ) { + Setup(); + } + p = static_cast( gameLocal.entities[ clientNum ] ); + if ( !p ) { + PickRandomSpawn(); + return; + } + start = p->GetEyePosition(); + end = start; end[2] += 20.0f; + clipWorld = gameLocal.GetEntityClipWorld( p ); + if ( clipWorld ) { + clipWorld->Translation( trace, start, end, physics->GetClipModel(), mat3_identity, MASK_PLAYERSOLID, NULL, NULL ); + physics->SetOrigin( trace.endpos ); + } else { + assert( false ); + physics->SetOrigin( start ); + } + viewAngles = p->viewAngles; + viewAngles[2] = 0.0f; + snapAngle = true; +} + +/* +=============== +idFreeView::PickRandomSpawn +=============== +*/ +void idFreeView::PickRandomSpawn( void ) { + if ( !physics ) { + Setup(); + } + idPlayerStart *start = gameLocal.RandomSpawn(); + physics->SetOrigin( start->GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, pm_normalheight.GetFloat() ) ); + viewAngles = start->GetPhysics()->GetAxis().ToAngles(); + snapAngle = true; +} + +/* +=============== +idFreeView::Fly +=============== +*/ +void idFreeView::Fly( const usercmd_t &ucmd ) { + idAngles src; + + assert( physics ); + + src[0] = SHORT2ANGLE( ucmd.angles[0] ); + src[1] = SHORT2ANGLE( ucmd.angles[1] ); + src[2] = 0.0f; + + if ( snapAngle ) { + viewAngleOffset = viewAngles - src; + snapAngle = false; + } + + viewAngles = src + viewAngleOffset; + + physics->SetPlayerInput( ucmd, viewAngles ); + physics->Evaluate( gameLocal.time - gameLocal.previousTime, gameLocal.time ); +} + +/* +=============== +idFreeView::Draw +=============== +*/ +void idFreeView::Draw( void ) { + + assert( physics ); + + view.vieworg = physics->PlayerGetOrigin(); + view.viewaxis = viewAngles.ToMat3(); + view.time = gameLocal.time; + gameLocal.CalcFov( g_fov.GetFloat(), view.fov_x, view.fov_y ); + + // free flying demo client rendering + gameLocal.mpGame.SetShaderParms( &view ); + // FIXME: player hud? draw scoreboard? + soundSystem->PlaceListener( view.vieworg, view.viewaxis, 0, gameLocal.time, "Undefined" ); + // from idPlayerView::SingleView + idCamera *portalSky = gameLocal.GetPortalSky(); + if ( portalSky ) { + renderView_t portalSkyView = view; + portalSky->GetViewParms( &portalSkyView ); + gameRenderWorld->RenderScene( &portalSkyView, RF_DEFER_COMMAND_SUBMIT | RF_PORTAL_SKY ); + } + gameRenderWorld->RenderScene( &view, RF_PRIMARY_VIEW ); +} + +/* +=============== +idFreeView::Setup +=============== +*/ +void idFreeView::Setup( void ) { + idClipModel *clip; + idBounds b; + + assert( !physics ); + + memset( &view, 0, sizeof( view ) ); + view.viewID = 0; + view.x = view.y = 0; + view.width = 640; + view.height = 480; + gameLocal.CalcFov( g_fov.GetFloat(), view.fov_x, view.fov_y ); + view.cramZNear = false; + + b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + clip = new idClipModel( idTraceModel( b ), declManager->FindMaterial( "textures/flesh_boundingbox" ) ); + physics = new idPhysics_Player(); + physics->SetSpeed( pm_spectatespeed.GetFloat(), pm_crouchspeed.GetFloat() ); + physics->SetClipModelNoLink( clip ); + physics->SetClipMask( MASK_PLAYERSOLID ); + physics->SetMovementType( PM_SPECTATOR ); +} + +/* +=============== +idFreeView::Shutdown +=============== +*/ +void idFreeView::Shutdown( void ) { + if ( physics ) { + delete physics; + physics = NULL; + } +} + +/* +=============== +idFreeView::GetOrigin +=============== +*/ +const idVec3 &idFreeView::GetOrigin( void ) { + return view.vieworg; +} diff --git a/src/game/FreeView.h b/src/game/FreeView.h new file mode 100644 index 0000000..dbf0f96 --- /dev/null +++ b/src/game/FreeView.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FREEVIEW_H__ +#define __FREEVIEW_H__ + +class idPhysics_Player; + +class idFreeView { +public: + + idFreeView() { physics = NULL; snapAngle = false; } + + ~idFreeView(); + + // start free flying from this client's current position + void SetFreeView( int clientNum ); + + // pick a random spawn in the map + void PickRandomSpawn( void ); + + // update view and position + void Fly( const usercmd_t &ucmd ); + + void Draw( void ); + + bool Initialized( void ) const { return physics != NULL; } + + void Shutdown( void ); + + const idVec3 & GetOrigin( void ); + +private: + + void Setup( void ); + + renderView_t view; + idPhysics_Player *physics; + idAngles viewAngles; + + bool snapAngle; + idAngles viewAngleOffset; +}; + +#endif diff --git a/src/game/Game.def b/src/game/Game.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/src/game/Game.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/src/game/Game.h b/src/game/Game.h new file mode 100644 index 0000000..87eac98 --- /dev/null +++ b/src/game/Game.h @@ -0,0 +1,742 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_H__ +#define __GAME_H__ + +/* +=============================================================================== + + Public game interface with methods to run the game. + +=============================================================================== +*/ + +// RAVEN BEGIN +// bgeisler: moved into scripts directory +// default scripts +#define SCRIPT_DEFAULTDEFS "scripts/defs.script" +#define SCRIPT_DEFAULT "scripts/main.script" +// RAVEN END +#define SCRIPT_DEFAULTFUNC "doom_main" + +struct gameReturn_t { + char sessionCommand[MAX_STRING_CHARS]; // "map", "disconnect", "victory", etc + int consistencyHash; // used to check for network game divergence + int health; + int heartRate; + int stamina; + int combat; + bool syncNextGameFrame; // used when cinematics are skipped to prevent session from simulating several game frames to + // keep the game time in sync with real time +}; + +enum allowReply_t { + ALLOW_YES = 0, + ALLOW_BADPASS, // core will prompt for password and connect again + ALLOW_NOTYET, // core will wait with transmitted message + ALLOW_NO // core will abort with transmitted message +}; + +enum escReply_t { + ESC_IGNORE = 0, // do nothing + ESC_MAIN, // start main menu GUI + ESC_GUI // set an explicit GUI +}; + +enum demoState_t { + DEMO_NONE, + DEMO_RECORDING, + DEMO_PLAYING +}; + +enum demoReliableGameMessage_t { + DEMO_RECORD_CLIENTNUM, + DEMO_RECORD_EXCLUDE, + DEMO_RECORD_COUNT +}; + +// +// these defines work for all startsounds from all entity types +// make sure to change script/doom_defs.script if you add any channels, or change their order +// +typedef enum { + SND_CHANNEL_ANY = SCHANNEL_ANY, + SND_CHANNEL_VOICE = SCHANNEL_ONE, + SND_CHANNEL_VOICE2, + SND_CHANNEL_BODY, + SND_CHANNEL_BODY2, + SND_CHANNEL_BODY3, + SND_CHANNEL_WEAPON, + SND_CHANNEL_ITEM, + SND_CHANNEL_HEART, + SND_CHANNEL_DEMONIC, + SND_CHANNEL_RADIO, + + // internal use only. not exposed to script or framecommands. + SND_CHANNEL_AMBIENT, + SND_CHANNEL_DAMAGE + +// RAVEN BEGIN +// bdube: added custom to tell us where the end of the predefined list is + , + SND_CHANNEL_POWERUP, + SND_CHANNEL_POWERUP_IDLE, + SND_CHANNEL_MP_ANNOUNCER, + SND_CHANNEL_CUSTOM +// RAVEN END +} gameSoundChannel_t; + +// RAVEN BEGIN +// bdube: forward reference +class rvClientEffect; +// RAVEN END + +struct ClientStats_t { + bool isLastPredictFrame; + bool isLagged; + bool isNewFrame; +}; + +typedef struct userOrigin_s { + idVec3 origin; + int followClient; +} userOrigin_t; + +class idGame { +public: + virtual ~idGame() {} + + // Initialize the game for the first time. +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + virtual void Init( void *(*allocator)( size_t size ), void (*deallocator)( void *ptr ), size_t (*msize)( void *ptr ) ) = 0; +#else + virtual void Init( void ) = 0; +#endif + + // Shut down the entire game. + virtual void Shutdown( void ) = 0; + + // Set the local client number. Distinguishes listen ( == 0 ) / dedicated ( == -1 ) + virtual void SetLocalClient( int clientNum ) = 0; + + // Sets the user info for a client. + // The game can modify the user info in the returned dictionary pointer, server will forward back. + virtual const idDict * SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ) = 0; + + // Retrieve the game's userInfo dict for a client. + virtual const idDict * GetUserInfo( int clientNum ) = 0; + + // Sets the user info for a viewer. + // The game can modify the user info in the returned dictionary pointer. + virtual const idDict * RepeaterSetUserInfo( int clientNum, const idDict &userInfo ) = 0; + + // Checks to see if a client is active + virtual bool IsClientActive( int clientNum ) = 0; + + // The game gets a chance to alter userinfo before they are emitted to server. + virtual void ThrottleUserInfo( void ) = 0; + + // Sets the serverinfo at map loads and when it changes. + virtual void SetServerInfo( const idDict &serverInfo ) = 0; + + // The session calls this before moving the single player game to a new level. + virtual const idDict & GetPersistentPlayerInfo( int clientNum ) = 0; + + // The session calls this right before a new level is loaded. + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) = 0; + + // Loads a map and spawns all the entities. + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randseed ) = 0; + + // Loads a map from a savegame file. + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ) = 0; + + // Saves the current game state, the session may have written some data to the file already. +// RAVEN BEGIN +// mekberg: added saveTypes + virtual void SaveGame( idFile *saveGameFile, saveType_t saveType = ST_REGULAR ) = 0; +// RAVEN END + + // Shut down the current map. + virtual void MapShutdown( void ) = 0; + + // Caches media referenced from in key/value pairs in the given dictionary. + virtual void CacheDictionaryMedia( const idDict *dict ) = 0; + + // Spawns the player entity to be used by the client. + virtual void SpawnPlayer( int clientNum ) = 0; + +// RAVEN BEGIN + // Runs a game frame, may return a session command for level changing, etc + // lastCatchupFrame is always true except if we are running several game frames in a row and this one is not the last one + // subsystems which can tolerate skipping frames will not run during those catchup frames + // several game frames in a row happen when game + renderer time goes above the tick time ( 16ms ) + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ) = 0; + + virtual void MenuFrame( void ) = 0; +// RAVEN END + + // Runs a repeater frame + virtual void RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int serverGameFrame ) = 0; + + // Makes rendering and sound system calls to display for a given clientNum. + virtual bool Draw( int clientNum ) = 0; + + // Let the game do it's own UI when ESCAPE is used + virtual escReply_t HandleESC( idUserInterface **gui ) = 0; + + // get the games menu if appropriate ( multiplayer ) + virtual idUserInterface * StartMenu() = 0; + + // When the game is running it's own UI fullscreen, GUI commands are passed through here + // return NULL once the fullscreen UI mode should stop, or "main" to go to main menu + virtual const char * HandleGuiCommands( const char *menuCommand ) = 0; + + // main menu commands not caught in the engine are passed here + virtual void HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) = 0; + + // Early check to deny connect. + virtual allowReply_t ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ) = 0; + + // Connects a client. + virtual void ServerClientConnect( int clientNum, const char *guid ) = 0; + + // Spawns the player entity to be used by the client. + virtual void ServerClientBegin( int clientNum ) = 0; + + // Disconnects a client and removes the player entity from the game. + virtual void ServerClientDisconnect( int clientNum ) = 0; + + // Writes initial reliable messages a client needs to recieve when first joining the game. + virtual void ServerWriteInitialReliableMessages( int clientNum ) = 0; + + // Early check to deny connect. + virtual allowReply_t RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ) = 0; + + // Connects a client. + virtual void RepeaterClientConnect( int clientNum ) = 0; + + // Spawns the player entity to be used by the client. + virtual void RepeaterClientBegin( int clientNum ) = 0; + + // Disconnects a client and removes the player entity from the game. + virtual void RepeaterClientDisconnect( int clientNum ) = 0; + + // Writes initial reliable messages a client needs to recieve when first joining the game. + virtual void RepeaterWriteInitialReliableMessages( int clientNum ) = 0; + + // Writes a snapshot of the server game state for the given client. + virtual void ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ) = 0; + + // Patches the network entity states at the server with a snapshot for the given client. + virtual bool ServerApplySnapshot( int clientNum, int sequence ) = 0; + + // Processes a reliable message from a client. + virtual void ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0; + + // Patches the network entity states at the server with a snapshot for the given client. + virtual bool RepeaterApplySnapshot( int clientNum, int sequence ) = 0; + + // Processes a reliable message from a client. + virtual void RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0; + + // Reads a snapshot and updates the client game state. + virtual void ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) = 0; + + // Patches the network entity states at the client with a snapshot. + virtual bool ClientApplySnapshot( int clientNum, int sequence ) = 0; + + // Processes a reliable message from the server. + virtual void ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0; + + // Runs prediction on entities at the client. + virtual gameReturn_t ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame = true, ClientStats_t *cs = NULL ) = 0; + +// RAVEN BEGIN +// ddynerman: client game frame + virtual void ClientRun( void ) = 0; + virtual void ClientEndFrame( void ) = 0; + +// jshepard: rcon password check + virtual void ProcessRconReturn( bool success ) = 0; + +// RAVEN END + + virtual bool ValidateServerSettings( const char *map, const char *gameType ) = 0; + + // Returns a summary of stats for a given client + virtual void GetClientStats( int clientNum, char *data, const int len ) = 0; + + // Switch a player to a particular team + virtual void SwitchTeam( int clientNum, int team ) = 0; + + virtual bool DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) = 0; + + // return true to allow download from the built-in http server + virtual bool HTTPRequest( const char *IP, const char *file, bool isGamePak ) = 0; + +// RAVEN BEGIN +// jscott: for the effects system + virtual void StartViewEffect( int type, float time, float scale ) = 0; + virtual rvClientEffect* PlayEffect( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ) = 0; + virtual void GetPlayerView( idVec3 &origin, idMat3 &axis ) = 0; + virtual const idVec3 GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const = 0; + virtual void Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ) = 0; + virtual void SpawnClientMoveable ( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ) = 0; +// bdube: debugging stuff + virtual void DebugSetString ( const char* name, const char* value ) = 0; + virtual void DebugSetFloat ( const char* name, float value ) = 0; + virtual void DebugSetInt ( const char* name, int value ) = 0; + virtual const char* DebugGetStatString ( const char* name ) = 0; + virtual int DebugGetStatInt ( const char* name ) = 0; + virtual float DebugGetStatFloat ( const char* name ) = 0; + virtual bool IsDebugHudActive ( void ) const = 0; +// rjohnson: for new note taking mechanism + virtual bool GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL, int reqClientNum = -1 ) = 0; + virtual void SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1 ) = 0; + virtual bool PlayerChatDisabled( int clientNum ) = 0; + virtual void SetViewComments( const char *text = 0 ) = 0; +// ddynerman: utility functions + virtual void GetPlayerName( int clientNum, char* name ) = 0; + virtual void GetPlayerClan( int clientNum, char* clan ) = 0; + virtual void SetFriend( int clientNum, bool isFriend ) = 0; + virtual const char* GetLongGametypeName( const char* gametype ) = 0; + virtual void ReceiveRemoteConsoleOutput( const char* output ) = 0; +// rjohnson: entity usage stats + virtual void ListEntityStats( const idCmdArgs &args ) = 0; +// shouchard: for ban lists + virtual void RegisterClientGuid( int clientNum, const char *guid ) = 0; + virtual bool IsMultiplayer( void ) = 0; +// mekberg: added + virtual bool InCinematic( void ) = 0; +// mekberg: so banlist can be populated outside of multiplayer game + virtual void PopulateBanList( idUserInterface* hud ) = 0; + virtual void RemoveGuidFromBanList( const char *guid ) = 0; +// mekberg: interface + virtual void AddGuidToBanList( const char *guid ) = 0; + virtual const char* GetGuidByClientNum( int clientNum ) = 0; +// jshepard: updating player post-menu + virtual void UpdatePlayerPostMainMenu( void ) = 0; + virtual void ResetRconGuiStatus( void ) = 0; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushBeforelevelLoad( void ) = 0; +#endif +// RAVEN END + + // Set the demo state. + virtual void SetDemoState( demoState_t state, bool serverDemo, bool timeDemo ) = 0; + + // Set the repeater state; engine will call this with true for isRepeater if this is a repeater, and true for serverIsRepeater if we are connected to a repeater + virtual void SetRepeaterState( bool isRepeater, bool serverIsRepeater ) = 0; + + // Writes current network info to a file (used as initial state for demo recording). + virtual void WriteNetworkInfo( idFile* file, int clientNum ) = 0; + + // Reads current network info from a file (used as initial state for demo playback). + virtual void ReadNetworkInfo( int gameTime, idFile* file, int clientNum ) = 0; + + // Let gamecode decide if it wants to accept demos from older releases of the engine. + virtual bool ValidateDemoProtocol( int minor_ref, int minor ) = 0; + + // Write a snapshot for server demo recording. + virtual void ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ) = 0; + + // Read a snapshot from a server demo stream. + virtual void ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ) = 0; + + // Write a snapshot for repeater clients. + virtual void RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ) = 0; + + // Done writing snapshots for repeater clients. + virtual void RepeaterEndSnapshots( void ) = 0; + + // Read a snapshot from a repeater stream. + virtual void ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ) = 0; + + // Get the currently followed client in demo playback + virtual int GetDemoFollowClient( void ) = 0; + + // Build a bot's userCmd + virtual void GetBotInput( int clientNum, usercmd_t &userCmd ) = 0; + + // Return the name of a gui to override the loading screen + virtual const char * GetLoadingGui( const char *mapDeclName ) = 0; + + // Set any additional gui variables needed by the loading screen + virtual void SetupLoadingGui( idUserInterface *gui ) = 0; +}; + +extern idGame * game; + + +/* +=============================================================================== + + Public game interface with methods for in-game editing. + +=============================================================================== +*/ + +struct refSound_t { +// RAVEN BEGIN + int referenceSoundHandle; // this is the interface to the sound system, created + // with idSoundWorld::AllocSoundEmitter() when needed +// RAVEN END + idVec3 origin; +// RAVEN BEGIN +// jscott: for Miles doppler + idVec3 velocity; +// RAVEN END + int listenerId; // SSF_PRIVATE_SOUND only plays if == listenerId from PlaceListener + // no spatialization will be performed if == listenerID + const idSoundShader * shader; // this really shouldn't be here, it is a holdover from single channel behavior + float diversity; // 0.0 to 1.0 value used to select which + // samples in a multi-sample list from the shader are used + bool waitfortrigger; // don't start it at spawn time + soundShaderParms_t parms; // override volume, flags, etc +}; + +enum { + TEST_PARTICLE_MODEL = 0, + TEST_PARTICLE_IMPACT, + TEST_PARTICLE_MUZZLE, + TEST_PARTICLE_FLIGHT, + TEST_PARTICLE_SELECTED +}; + +class idEntity; +class idMD5Anim; +// RAVEN BEGIN +// bdube: more forward declarations +class idProgram; +class idInterpreter; +class idThread; + +typedef void (*debugInfoProc_t) ( const char* classname, const char* name, const char* value, void *userdata ); +// RAVEN END + +// FIXME: this interface needs to be reworked but it properly separates code for the time being +class idGameEdit { +public: + virtual ~idGameEdit( void ) {} + + // These are the canonical idDict to parameter parsing routines used by both the game and tools. + virtual bool ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ); + virtual void ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ); + virtual void ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ); + + // Animation system calls for non-game based skeletal rendering. + virtual idRenderModel * ANIM_GetModelFromEntityDef( const char *classname ); + virtual const idVec3 &ANIM_GetModelOffsetFromEntityDef( const char *classname ); + virtual idRenderModel * ANIM_GetModelFromEntityDef( const idDict *args ); + virtual idRenderModel * ANIM_GetModelFromName( const char *modelName ); + virtual const idMD5Anim * ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ); +// RAVEN BEGIN +// bdube: added +// scork: added 'const' qualifiers so other stuff would compile + virtual const idMD5Anim * ANIM_GetAnimFromEntity( const idEntity* ent, int animNum ); + virtual float ANIM_GetAnimPlaybackRateFromEntity ( idEntity* ent, int animNum ); + virtual const char* ANIM_GetAnimNameFromEntity ( const idEntity* ent, int animNum ); +// RAVEN END + virtual int ANIM_GetNumAnimsFromEntityDef( const idDict *args ); + virtual const char * ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ); + virtual const idMD5Anim * ANIM_GetAnim( const char *fileName ); + virtual int ANIM_GetLength( const idMD5Anim *anim ); + virtual int ANIM_GetNumFrames( const idMD5Anim *anim ); +// RAVEN BEGIN +// bdube: added + virtual const char * ANIM_GetFilename( const idMD5Anim* anim ); + virtual int ANIM_ConvertFrameToTime ( const idMD5Anim* anim, int frame ); + virtual int ANIM_ConvertTimeToFrame ( const idMD5Anim* anim, int time ); +// RAVEN END + virtual void ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *frame, int time, const idVec3 &offset, bool remove_origin_offset ); + virtual idRenderModel * ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ); + +// RAVEN BEGIN +// mekberg: access to animationlib functions for radiant + virtual void FlushUnusedAnims( void ); +// RAVEN END + + // Articulated Figure calls for AF editor and Radiant. + virtual bool AF_SpawnEntity( const char *fileName ); + virtual void AF_UpdateEntities( const char *fileName ); + virtual void AF_UndoChanges( void ); + virtual idRenderModel * AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ); + + + // Entity selection. + virtual void ClearEntitySelection( void ); + virtual int GetSelectedEntities( idEntity *list[], int max ); + virtual void AddSelectedEntity( idEntity *ent ); + + // Selection methods + virtual void TriggerSelected(); + + // Entity defs and spawning. + virtual const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + virtual void SpawnEntityDef( const idDict &args, idEntity **ent ); + virtual idEntity * FindEntity( const char *name ) const; + virtual const char * GetUniqueEntityName( const char *classname ) const; + + // Entity methods. + virtual void EntityGetOrigin( idEntity *ent, idVec3 &org ) const; + virtual void EntityGetAxis( idEntity *ent, idMat3 &axis ) const; + virtual void EntitySetOrigin( idEntity *ent, const idVec3 &org ); + virtual void EntitySetAxis( idEntity *ent, const idMat3 &axis ); + virtual void EntityTranslate( idEntity *ent, const idVec3 &org ); +// RAVEN BEGIN +// scork: const-qualified 'ent' so other things would compile + virtual const idDict * EntityGetSpawnArgs( const idEntity *ent ) const; +// RAVEN END + virtual void EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ); + virtual void EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ); + virtual void EntityUpdateVisuals( idEntity *ent ); + virtual void EntitySetModel( idEntity *ent, const char *val ); + virtual void EntityStopSound( idEntity *ent ); + virtual void EntityDelete( idEntity *ent ); + virtual void EntitySetColor( idEntity *ent, const idVec3 color ); +// RAVEN BEGIN +// bdube: added + virtual const char* EntityGetName ( idEntity* ent ) const; + virtual int EntityToSafeId( idEntity* ent ) const; + virtual idEntity * EntityFromSafeId( int safeID) const; + virtual void EntitySetSkin ( idEntity *ent, const char* temp ) const; + virtual void EntityClearSkin ( idEntity *ent ) const; + virtual void EntityShow ( idEntity* ent ) const; + virtual void EntityHide ( idEntity* ent ) const; + virtual void EntityGetBounds ( idEntity* ent, idBounds &bounds ) const; + virtual int EntityPlayAnim ( idEntity* ent, int animNum, int time, int blendtime ); + virtual void EntitySetFrame ( idEntity* ent, int animNum, int frame, int time, int blendtime ); + virtual void EntityStopAllEffects ( idEntity* ent ); + virtual void EntityGetDelta ( idEntity* ent, int fromTime, int toTime, idVec3& delta ); + virtual void EntityRemoveOriginOffset ( idEntity* ent, bool remove ); + virtual const char* EntityGetClassname ( idEntity* ent ) const; + virtual bool EntityIsDerivedFrom ( idEntity* ent, const char* classname ) const; + virtual renderEntity_t* EntityGetRenderEntity ( idEntity* ent ); +// scork: accessor functions for various utils + virtual idEntity * EntityGetNextTeamEntity( idEntity *pEnt ) const; + virtual void GetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL ) const; + virtual void SetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum = -1 ) const; + virtual void EntitySetName( idEntity* pEnt, const char *psName ); +// RAVEN END + + // Player methods. + virtual bool PlayerIsValid() const; + virtual void PlayerGetOrigin( idVec3 &org ) const; + virtual void PlayerGetAxis( idMat3 &axis ) const; + virtual void PlayerGetViewAngles( idAngles &angles ) const; + virtual void PlayerGetEyePosition( idVec3 &org ) const; +// RAVEN BEGIN +// bdube: new game edit stuff + virtual bool PlayerTraceFromEye ( trace_t &results, float length, int contentMask ); + + // Effect methods + virtual void EffectRefreshTemplate ( const idDecl *effect ) const; + + // Light entity methods + virtual void LightSetParms ( idEntity* ent, int maxLevel, int currentLevel, float radius ); + + // Common editing functions + virtual int GetGameTime ( int *previous = NULL ) const; + virtual void SetGameTime ( int time ) const; + virtual bool TracePoint ( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask ) const; + virtual void CacheDictionaryMedia ( const idDict* dict ) const; + virtual void SetCamera ( idEntity* camera ) const; +// RAVEN BEGIN +// bdube: added + virtual int GetGameEntityRegisterTime ( void ) const; + virtual idEntity* GetFirstSpawnedEntity ( void ) const; + virtual idEntity* GetNextSpawnedEntity ( idEntity* from ) const; +// jscott: added + virtual void DrawPlaybackDebugInfo( void ); + virtual void RecordPlayback( const usercmd_t &cmd, idEntity *source ); + virtual bool PlayPlayback( void ); + virtual void ShutdownPlaybacks( void ); +// RAVEN END + + // Script methods + virtual int ScriptGetStatementLineNumber ( idProgram* program, int instructionPointer ) const; + virtual const char* ScriptGetStatementFileName ( idProgram* program, int instructionPointer ) const; + virtual int ScriptGetStatementOperator ( idProgram* program, int instructionPointer ) const; + virtual void* ScriptGetCurrentFunction ( idInterpreter* interpreter ) const; + virtual const char* ScriptGetCurrentFunctionName ( idInterpreter* interpreter ) const; + virtual int ScriptGetCallstackDepth ( idInterpreter* interpreter ) const; + virtual void* ScriptGetCallstackFunction ( idInterpreter* interpreter, int depth ) const; + virtual const char* ScriptGetCallstackFunctionName ( idInterpreter* interpreter, int depth ) const; + virtual int ScriptGetCallstackStatement ( idInterpreter* interpreter, int depth ) const; + virtual bool ScriptIsReturnOperator ( int op ) const; + virtual const char* ScriptGetRegisterValue ( idInterpreter* interpreter, const char* varname, int callstackDepth ) const; + virtual idThread* ScriptGetThread ( idInterpreter* interpreter ) const; + + // Thread methods + virtual int ThreadGetCount ( void ); + virtual idThread* ThreadGetThread ( int index ); + virtual const char* ThreadGetName ( idThread* thread ); + virtual int ThreadGetNumber ( idThread* thread ); + virtual const char* ThreadGetState ( idThread* thread ); + + // Class externals for entity viewer + virtual void GetClassDebugInfo ( const idEntity* entity, debugInfoProc_t proc, void* userdata ); + + // In game map editing support. + virtual const idDict * MapGetEntityDict( const char *name ) const; + virtual void MapSave( const char *path = NULL ) const; +// RAVEN BEGIN +// rjohnson: added entity export + virtual bool MapHasExportEntities( void ) const; +// scork: simple func for the sound editor + virtual const char* MapLoaded( void ) const; +// cdr: AASTactical + virtual idAASFile* GetAASFile( int i ); +// jscott: added entries for memory tracking + virtual void PrintMemInfo( MemInfo *mi ); + virtual size_t ScriptSummary( const idCmdArgs &args ) const; + virtual size_t ClassSummary( const idCmdArgs &args ) const; + virtual size_t EntitySummary( const idCmdArgs &args ) const; +// RAVEN END + virtual void MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const ; + virtual void MapCopyDictToEntity( const char *name, const idDict *dict ) const; + virtual int MapGetUniqueMatchingKeyVals( const char *key, const char *list[], const int max ) const; + virtual void MapAddEntity( const idDict *dict ) const; + virtual int MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const; + virtual void MapRemoveEntity( const char *name ) const; + virtual void MapEntityTranslate( const char *name, const idVec3 &v ) const; +}; + +extern idGameEdit * gameEdit; + +// RAVEN BEGIN +// bdube: game logging +/* +=============================================================================== + + Game Log. + +=============================================================================== +*/ +class rvGameLog { +public: + virtual ~rvGameLog( void ) {} + + virtual void Init ( void ) = 0; + virtual void Shutdown ( void ) = 0; + + virtual void BeginFrame ( int time ) = 0; + virtual void EndFrame ( void ) = 0; + + virtual void Set ( const char* keyword, int value ) = 0; + virtual void Set ( const char* keyword, float value ) = 0; + virtual void Set ( const char* keyword, const char* value ) = 0; + virtual void Set ( const char* keyword, bool value ) = 0; + + virtual void Add ( const char* keyword, int value ) = 0; + virtual void Add ( const char* keyword, float value ) = 0; +}; + +extern rvGameLog * gameLog; + +#define GAMELOG_SET(x,y) {if(g_gamelog.GetBool())gameLog->Set ( x, y );} +#define GAMELOG_ADD(x,y) {if(g_gamelog.GetBool())gameLog->Add ( x, y );} + +#define GAMELOG_SET_IF(x,y,z) {if(g_gamelog.GetBool()&&(z))gameLog->Set ( x, y );} +#define GAMELOG_ADD_IF(x,y,z) {if(g_gamelog.GetBool()&&(z))gameLog->Add ( x, y );} + +// RAVEN END + +/* +=============================================================================== + + Game API. + +=============================================================================== +*/ + +// 4: network demos +// 5: fix idNetworkSystem ( memory / DLL boundary related ) +// 6: more network demo APIs +// 7: cleanups +// 8: added some demo functions to the FS class +// 9: bump up for 1.1 patch +// 9: Q4 Gold +// 10: Patch 2 changes +// 14: 1.3 +// 26: 1.4 beta +// 30: 1.4 +// 37: 1.4.2 +const int GAME_API_VERSION = 37; + +struct gameImport_t { + + int version; // API version + idSys * sys; // non-portable system services + idCommon * common; // common + idCmdSystem * cmdSystem; // console command system + idCVarSystem * cvarSystem; // console variable system + idFileSystem * fileSystem; // file system + idNetworkSystem * networkSystem; // network system + idRenderSystem * renderSystem; // render system + idSoundSystem * soundSystem; // sound system + idRenderModelManager * renderModelManager; // render model manager + idUserInterfaceManager * uiManager; // user interface manager + idDeclManager * declManager; // declaration manager + idAASFileManager * AASFileManager; // AAS file manager + idCollisionModelManager * collisionModelManager; // collision model manager + +// RAVEN BEGIN +// jscott: + rvBSEManager * bse; // Raven effects system +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added the following members to exchange memory system data +#ifdef _RV_MEM_SYS_SUPPORT + rvHeapArena * heapArena; // main heap arena that all other heaps use + rvHeap * systemHeapArray[MAX_SYSTEM_HEAPS]; // array of pointers to rvHeaps that are common to idLib, Game, and executable +#endif +// RAVEN END +}; + +struct gameExport_t { + + int version; // API version + idGame * game; // interface to run the game + idGameEdit * gameEdit; // interface for in-game editing +// RAVEN BEGIN +// bdube: added + rvGameLog * gameLog; // interface for game logging +// RAVEN END +}; + +extern "C" { +typedef gameExport_t * (*GetGameAPI_t)( gameImport_t *import ); +} + +#endif /* !__GAME_H__ */ diff --git a/src/game/GameEdit.cpp b/src/game/GameEdit.cpp new file mode 100644 index 0000000..5672145 --- /dev/null +++ b/src/game/GameEdit.cpp @@ -0,0 +1,1984 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: added +#include "Effect.h" +// nmckenzie: +//#include "rvAI/AI.h" +#include "ai/AI.h" +#include "client/ClientEffect.h" +// RAVEN END + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idCursor3D ) +END_CLASS + +/* +=============== +idCursor3D::idCursor3D +=============== +*/ +idCursor3D::idCursor3D( void ) { + draggedPosition.Zero(); +} + +/* +=============== +idCursor3D::~idCursor3D +=============== +*/ +idCursor3D::~idCursor3D( void ) { +} + +/* +=============== +idCursor3D::Spawn +=============== +*/ +void idCursor3D::Spawn( void ) { +} + +/* +=============== +idCursor3D::Present +=============== +*/ +void idCursor3D::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + const idVec3 &origin = GetPhysics()->GetOrigin(); + const idMat3 &axis = GetPhysics()->GetAxis(); + gameRenderWorld->DebugArrow( colorYellow, origin + axis[1] * -5.0f + axis[2] * 5.0f, origin, 2 ); + gameRenderWorld->DebugArrow( colorRed, origin, draggedPosition, 2 ); +} + +/* +=============== +idCursor3D::Think +=============== +*/ +void idCursor3D::Think( void ) { + if ( thinkFlags & TH_THINK ) { + drag.Evaluate( gameLocal.time ); + } + Present(); +} + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +#define MAX_DRAG_TRACE_DISTANCE 2048.0f + +/* +============== +idDragEntity::idDragEntity +============== +*/ +idDragEntity::idDragEntity( void ) { + cursor = NULL; + Clear(); +} + +/* +============== +idDragEntity::~idDragEntity +============== +*/ +idDragEntity::~idDragEntity( void ) { + StopDrag(); + selected = NULL; + delete cursor; + cursor = NULL; +} + + +/* +============== +idDragEntity::Clear +============== +*/ +void idDragEntity::Clear() { + dragEnt = NULL; + joint = INVALID_JOINT; + id = 0; + localEntityPoint.Zero(); + localPlayerPoint.Zero(); + bodyName.Clear(); + selected = NULL; +} + +/* +============== +idDragEntity::StopDrag +============== +*/ +void idDragEntity::StopDrag( void ) { + dragEnt = NULL; + if ( cursor ) { + cursor->BecomeInactive( TH_THINK ); + } +} + +/* +============== +idDragEntity::Update +============== +*/ +void idDragEntity::Update( idPlayer *player ) { + idVec3 viewPoint, origin; + idMat3 viewAxis, axis; + trace_t trace; + idEntity *newEnt; + idAngles angles; + jointHandle_t newJoint = INVALID_JOINT; + idStr newBodyName; + + player->GetViewPos( viewPoint, viewAxis ); + + // if no entity selected for dragging + if ( !dragEnt.GetEntity() ) { + + if ( player->usercmd.buttons & BUTTON_ATTACK ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( player, trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + + newEnt = gameLocal.entities[ trace.c.entityNum ]; + if ( newEnt ) { + + if ( newEnt->GetBindMaster() ) { + if ( newEnt->GetBindJoint() ) { + trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() ); + } else { + trace.c.id = newEnt->GetBindBody(); + } + newEnt = newEnt->GetBindMaster(); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( newEnt->IsType( idAFEntity_Base::GetClassType() ) && static_cast(newEnt)->IsActiveAF() ) { +// RAVEN END + idAFEntity_Base *af = static_cast(newEnt); + + // joint being dragged + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + // get the body id from the trace model id which might be a joint handle + trace.c.id = af->BodyForClipModelId( trace.c.id ); + // get the name of the body being dragged + newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( !newEnt->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + + if ( trace.c.id < 0 ) { + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + } else { + newJoint = INVALID_JOINT; + } + newBodyName = ""; + + } else { + + newJoint = INVALID_JOINT; + newEnt = NULL; + } + } + if ( newEnt ) { + dragEnt = newEnt; + selected = newEnt; + joint = newJoint; + id = trace.c.id; + bodyName = newBodyName; + + if ( !cursor ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::GetClassType() ); +// RAVEN END + } + + idPhysics *phys = dragEnt.GetEntity()->GetPhysics(); + localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose(); + origin = phys->GetOrigin( id ); + axis = phys->GetAxis( id ); + localEntityPoint = ( trace.c.point - origin ) * axis.Transpose(); + + cursor->drag.Init( g_dragDamping.GetFloat() ); + cursor->drag.SetPhysics( phys, id, localEntityPoint ); + cursor->Show(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( phys->IsType( idPhysics_AF::GetClassType() ) || + phys->IsType( idPhysics_RigidBody::GetClassType() ) || + phys->IsType( idPhysics_Monster::GetClassType() ) ) { +// RAVEN END + cursor->BecomeActive( TH_THINK ); + } + } + } + } + } + + // if there is an entity selected for dragging + idEntity *drag = dragEnt.GetEntity(); + if ( drag ) { + + if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { + StopDrag(); + return; + } + + cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis ); + cursor->SetAxis( viewAxis ); + + cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() ); + + renderEntity_t *renderEntity = drag->GetRenderEntity(); + idAnimator *dragAnimator = drag->GetAnimator(); + + if ( joint != INVALID_JOINT && renderEntity && dragAnimator ) { + dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis ); + cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis; + gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } else { + cursor->draggedPosition = cursor->GetPhysics()->GetOrigin(); + gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } + } + + // if there is a selected entity + if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) { + // draw the bbox of the selected entity + renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity(); + if ( renderEntity ) { + gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) ); + } + } +} + +/* +============== +idDragEntity::SetSelected +============== +*/ +void idDragEntity::SetSelected( idEntity *ent ) { + selected = ent; + StopDrag(); +} + +/* +============== +idDragEntity::DeleteSelected +============== +*/ +void idDragEntity::DeleteSelected( void ) { + delete selected.GetEntity(); + selected = NULL; + StopDrag(); +} + +/* +============== +idDragEntity::BindSelected +============== +*/ +void idDragEntity::BindSelected( void ) { + int num, largestNum; + idLexer lexer; + idToken type, bodyName; + idStr key, value, bindBodyName; + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(dragEnt.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + bindBodyName = af->GetAFPhysics()->GetBody( id )->GetName(); + largestNum = 1; + + // parse all the bind constraints + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + key = kv->GetKey(); + key.Strip( "bindConstraint " ); + if ( sscanf( key, "bind%d", &num ) ) { + if ( num >= largestNum ) { + largestNum = num + 1; + } + } + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + lexer.ReadToken( &bodyName ); + lexer.FreeSource(); + + // if there already exists a bind constraint for this body + if ( bodyName.Icmp( bindBodyName ) == 0 ) { + // delete the bind constraint + af->spawnArgs.Delete( kv->GetKey() ); + kv = NULL; + } + + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + sprintf( key, "bindConstraint bind%d", largestNum ); + sprintf( value, "ballAndSocket %s %s", bindBodyName.c_str(), af->GetAnimator()->GetJointName( joint ) ); + + af->spawnArgs.Set( key, value ); + af->spawnArgs.Set( "bind", "worldspawn" ); + af->Bind( gameLocal.world, true ); +} + +/* +============== +idDragEntity::UnbindSelected +============== +*/ +void idDragEntity::UnbindSelected( void ) { + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(selected.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + // unbind the selected entity + af->Unbind(); + + // delete all the bind constraints + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + selected.GetEntity()->spawnArgs.Delete( kv->GetKey() ); + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + } + + // delete any bind information + af->spawnArgs.Delete( "bind" ); + af->spawnArgs.Delete( "bindToJoint" ); + af->spawnArgs.Delete( "bindToBody" ); +} + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ + +/* +============== +idEditEntities::idEditEntities +============== +*/ +idEditEntities::idEditEntities( void ) { + selectableEntityClasses.Clear(); + nextSelectTime = 0; +} + +// RAVEN BEGIN +// bdube: made this special to edit entities +/* +============= +idEditEntities::FindTraceEntity +============= +*/ +idEntity* idEditEntities::FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ) { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent != skip && EntityIsSelectable ( ent ) ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} +// RAVEN END + +/* +============= +idEditEntities::SelectEntity +============= +*/ +bool idEditEntities::SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ) { + idVec3 end; + idEntity *ent; + + if ( !g_editEntityMode.GetInteger() || selectableEntityClasses.Num() == 0 ) { + return false; + } + + if ( gameLocal.time < nextSelectTime ) { + return true; + } + nextSelectTime = gameLocal.time + 300; + + end = origin + dir * 4096.0f; + +// RAVEN BEGIN +// bdube: more generic + ent = NULL; + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + ent = FindTraceEntity( origin, end, skip ); + if ( ent ) { + break; + } + } + if ( !ent ) { + return false; + } + + ClearSelectedEntities(); + + AddSelectedEntity( ent ); + gameLocal.Printf( "entity #%d: %s '%s'\n", ent->entityNumber, ent->GetClassname(), ent->name.c_str() ); + + if ( gameLocal.editors & EDITOR_ENTVIEW ) { + common->InitTool ( EDITOR_ENTVIEW, &ent->spawnArgs ); + } else { + ent->ShowEditingDialog(); + } + + return true; +// RAVEN END +} + +/* +============= +idEditEntities::AddSelectedEntity +============= +*/ +void idEditEntities::AddSelectedEntity(idEntity *ent) { + ent->fl.selected = true; + selectedEntities.AddUnique(ent); +} + +/* +============== +idEditEntities::RemoveSelectedEntity +============== +*/ +void idEditEntities::RemoveSelectedEntity( idEntity *ent ) { + if ( selectedEntities.Find( ent ) ) { + selectedEntities.Remove( ent ); + } +} + +/* +============= +idEditEntities::ClearSelectedEntities +============= +*/ +void idEditEntities::ClearSelectedEntities() { + int i, count; + + count = selectedEntities.Num(); + for ( i = 0; i < count; i++ ) { + selectedEntities[i]->fl.selected = false; + } + selectedEntities.Clear(); +} + + +/* +============= +idEditEntities::EntityIsSelectable +============= +*/ +bool idEditEntities::EntityIsSelectable( idEntity *ent, idVec4 *color, idStr *text ) { +// RAVEN BEGIN +// bdube: no matter what dont let the player or its entities be selectable + idPlayer* player; + if ( 0 != (player = gameLocal.GetLocalPlayer() )) { + if ( ent == player || ent == player->GetWeaponViewModel ( ) || ent == player->GetWeaponWorldModel ( ) ) { + return false; + } + } + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + if ( ent->IsType ( *selectableEntityClasses[i].typeInfo ) ) { +// RAVEN END + if ( text ) { + *text = selectableEntityClasses[i].textKey; + } + if ( color ) { + if ( ent->fl.selected ) { + *color = colorRed; + } else { + switch( i ) { + case 1 : + *color = colorYellow; + break; + case 2 : + *color = colorBlue; + break; + default: + *color = colorGreen; + } + } + } + return true; + } + } + return false; +} + +/* +============= +idEditEntities::DisplayEntities +============= +*/ +void idEditEntities::DisplayEntities( void ) { + idEntity *ent; + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + selectableEntityClasses.Clear(); + selectedTypeInfo_t sit; + + switch( g_editEntityMode.GetInteger() ) { + case 1: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idLight::GetClassType(); +// RAVEN END + sit.textKey = "texture"; + selectableEntityClasses.Append( sit ); + break; + case 2: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idSound::GetClassType(); +// scork: added secondary "name" field as well (Zack request) + sit.textKey = "s_shader|name"; + selectableEntityClasses.Append( sit ); +// scork: Zack (reasonably enough) doesn't want the lights displayed when editing sounds +// sit.typeInfo = &idLight::GetClassType(); +// sit.textKey = "texture"; +// selectableEntityClasses.Append( sit ); +// RAVEN END + break; + case 3: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAFEntity_Base::GetClassType(); +// RAVEN END + sit.textKey = "articulatedFigure"; + selectableEntityClasses.Append( sit ); + break; + case 4: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idFuncEmitter::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; + case 5: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAI::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 6: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 7: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; +// RAVEN BEGIN +// bdube: added fx entities + case 8: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &rvEffect::GetClassType(); +// RAVEN END + sit.textKey = "fx"; + selectableEntityClasses.Append ( sit ); + break; +// RAVEN END + default: + return; + } + + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + + viewBounds.ExpandSelf( g_editEntityDistance.GetFloat() ); +// RAVEN BEGIN +// scork: changed from 128 to 256 so we can see speaker ent descriptions before getting right up to them +// rhummer: Added cvar to adjust the distance for the text too. + viewTextBounds.ExpandSelf( g_editEntityTextDistance.GetFloat() ); +// RAVEN END + + idStr textKey; +// RAVEN BEGIN +// scork: secondary field + idStr textKey2; + idStr strOutput; +// RAVEN END + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + idVec4 color; + + textKey = ""; + if ( !EntityIsSelectable( ent, &color, &textKey ) ) { + continue; + } + +// RAVEN BEGIN +// scork: handle optional secondary field + textKey2 = ""; + int iIndex = textKey.Find('|'); + if (iIndex >= 0) + { + textKey2 = textKey.Mid ( iIndex+1, textKey.Length()-(iIndex+1) ); // hmmm, they emulate 99% of MS CString but don't have a single-param Mid() func? + textKey = textKey.Left( iIndex ); + } +// RAVEN END + + + bool drawArrows = false; +// RAVEN BEGIN +// bdube: added + bool drawDirection = false; +// RAVEN END +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetType() == &idAFEntity_Base::GetClassType() ) { +// RAVEN END + if ( !static_cast(ent)->IsActiveAF() ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &idSound::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) + { + drawArrows = true; + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag |= SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + else + { + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag &= ~SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + ent->UpdateSound(); + const idSoundShader * ss = declManager->FindSound( ent->spawnArgs.GetString( textKey ) ); + if ( ss->HasDefaultSound() || ss->base->GetState() == DS_DEFAULTED ) { + color.Set( 1.0f, 0.0f, 1.0f, 1.0f ); + } +// RAVEN BEGIN +// bdube: added +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &rvEffect::GetClassType() ) { + drawDirection = true; + if ( ent->fl.selected ) { + drawArrows = true; + } + } else if ( ent->GetType() == &idFuncEmitter::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) { + drawArrows = true; + } + } + + if ( !viewBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + continue; + } + + gameRenderWorld->DebugBounds( color, idBounds( ent->GetPhysics()->GetOrigin() ).Expand( 8 ) ); + if ( drawArrows ) { + idVec3 start = ent->GetPhysics()->GetOrigin(); + idVec3 end = start + idVec3( 1, 0, 0 ) * 20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x+", end + idVec3( 4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 1, 0, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x-", end + idVec3( -4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * +20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y+", end + idVec3( 0, 4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y-", end + idVec3( 0, -4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * +20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z+", end + idVec3( 0, 0, 4 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * -20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z-", end + idVec3( 0, 0, -4 ), 0.15f, colorWhite, axis ); + } + +// RAVEN BEGIN +// bdube: added + if ( drawDirection ) { + idVec3 start = ent->GetPhysics()->GetOrigin ( ); + idVec3 end = start + ent->GetPhysics()->GetAxis()[0] * 35.0f; + gameRenderWorld->DebugArrow ( colorYellow, start, end, 6 ); + } +// RAVEN END + + if ( textKey.Length() ) { +// RAVEN BEGIN +// scork: handle optional secondary field, plus only call GetString when bounds are within view + if ( viewTextBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + strOutput = ent->spawnArgs.GetString( textKey ); + if (!textKey2.IsEmpty()) + { + strOutput += " ( "; + strOutput += ent->spawnArgs.GetString( textKey2 ); + strOutput += " )"; + } + gameRenderWorld->DrawText( strOutput.c_str(), ent->GetPhysics()->GetOrigin() + idVec3(0, 0, 12), 0.25, colorWhite, axis, 1 ); + } +// RAVEN END + } + } +} + + +/* +=============================================================================== + + idGameEdit + +=============================================================================== +*/ + +idGameEdit gameEditLocal; +idGameEdit * gameEdit = &gameEditLocal; + + +/* +============= +idGameEdit::GetSelectedEntities +============= +*/ +int idGameEdit::GetSelectedEntities( idEntity *list[], int max ) { + int num = 0; + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + list[num++] = ent; + if ( num >= max ) { + break; + } + } + } + return num; +} + +/* +============= +idGameEdit::TriggerSelected +============= +*/ +void idGameEdit::TriggerSelected() { + idEntity *ent; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + } + } +} + +/* +================ +idGameEdit::ClearEntitySelection +================ +*/ +void idGameEdit::ClearEntitySelection() { + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + ent->fl.selected = false; + } +// RAVEN BEGIN +// bdube: fixed potential crash + if ( gameLocal.editEntities ) { + gameLocal.editEntities->ClearSelectedEntities(); + } +// RAVEN END +} + +/* +================ +idGameEdit::AddSelectedEntity +================ +*/ +void idGameEdit::AddSelectedEntity( idEntity *ent ) { +// RAVEN BEGIN +// mekberg: fixed crash + if ( ent && gameLocal.editEntities ) { + gameLocal.editEntities->AddSelectedEntity( ent ); + } +// RAVEN END +} + +/* +================ +idGameEdit::FindEntityDefDict +================ +*/ +const idDict *idGameEdit::FindEntityDefDict( const char *name, bool makeDefault ) const { + return gameLocal.FindEntityDefDict( name, makeDefault ); +} + +/* +================ +idGameEdit::SpawnEntityDef +================ +*/ +void idGameEdit::SpawnEntityDef( const idDict &args, idEntity **ent ) { + gameLocal.SpawnEntityDef( args, ent ); +} + +/* +================ +idGameEdit::FindEntity +================ +*/ +idEntity *idGameEdit::FindEntity( const char *name ) const { + return gameLocal.FindEntity( name ); +} + +/* +============= +idGameEdit::GetUniqueEntityName + +generates a unique name for a given classname +============= +*/ +const char *idGameEdit::GetUniqueEntityName( const char *classname ) const { + int id; + static char name[1024]; + + // can only have MAX_GENTITIES, so if we have a spot available, we're guaranteed to find one + for( id = 0; id < MAX_GENTITIES; id++ ) { + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + if ( !gameLocal.FindEntity( name ) ) { + return name; + } + } + + // id == MAX_GENTITIES + 1, which can't be in use if we get here + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + return name; +} + +/* +================ +idGameEdit::EntityGetOrigin +================ +*/ +void idGameEdit::EntityGetOrigin( idEntity *ent, idVec3 &org ) const { + if ( ent ) { + org = ent->GetPhysics()->GetOrigin(); + } +} + +/* +================ +idGameEdit::EntityGetAxis +================ +*/ +void idGameEdit::EntityGetAxis( idEntity *ent, idMat3 &axis ) const { + if ( ent ) { + axis = ent->GetPhysics()->GetAxis(); + } +} + +/* +================ +idGameEdit::EntitySetOrigin +================ +*/ +void idGameEdit::EntitySetOrigin( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->SetOrigin( org ); + } +} + +/* +================ +idGameEdit::EntitySetAxis +================ +*/ +void idGameEdit::EntitySetAxis( idEntity *ent, const idMat3 &axis ) { + if ( ent ) { + ent->SetAxis( axis ); + } +} + +/* +================ +idGameEdit::EntitySetColor +================ +*/ +void idGameEdit::EntitySetColor( idEntity *ent, const idVec3 color ) { + if ( ent ) { + ent->SetColor( color ); + } +} + +/* +================ +idGameEdit::EntityTranslate +================ +*/ +void idGameEdit::EntityTranslate( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->GetPhysics()->Translate( org ); + } +} + +/* +================ +idGameEdit::EntityGetSpawnArgs +================ +*/ +// RAVEN BEGIN +// scork: const-qualified 'ent' so other things would compile +const idDict *idGameEdit::EntityGetSpawnArgs( const idEntity *ent ) const { +// RAVEN END + if ( ent ) { + return &ent->spawnArgs; + } + return NULL; +} + +/* +================ +idGameEdit::EntityUpdateChangeableSpawnArgs +================ +*/ +void idGameEdit::EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ) { + if ( ent ) { + ent->UpdateChangeableSpawnArgs( dict ); + } +} + +/* +================ +idGameEdit::EntityChangeSpawnArgs +================ +*/ +void idGameEdit::EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ) { + if ( ent ) { + for ( int i = 0 ; i < newArgs->GetNumKeyVals () ; i ++ ) { + const idKeyValue *kv = newArgs->GetKeyVal( i ); + + if ( kv->GetValue().Length() > 0 ) { + ent->spawnArgs.Set ( kv->GetKey() ,kv->GetValue() ); + } else { + ent->spawnArgs.Delete ( kv->GetKey() ); + } + } + } +} + +/* +================ +idGameEdit::EntityUpdateVisuals +================ +*/ +void idGameEdit::EntityUpdateVisuals( idEntity *ent ) { + if ( ent ) { + ent->UpdateVisuals(); + } +} + +/* +================ +idGameEdit::EntitySetModel +================ +*/ +void idGameEdit::EntitySetModel( idEntity *ent, const char *val ) { + if ( ent ) { + ent->spawnArgs.Set( "model", val ); + ent->SetModel( val ); + } +} + +/* +================ +idGameEdit::EntityStopSound +================ +*/ +void idGameEdit::EntityStopSound( idEntity *ent ) { + if ( ent ) { + ent->StopSound( SND_CHANNEL_ANY, false ); + } +} + +/* +================ +idGameEdit::EntityDelete +================ +*/ +void idGameEdit::EntityDelete( idEntity *ent ) { + delete ent; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idGameEdit::EntityGetRenderEntity +================ +*/ +renderEntity_t* idGameEdit::EntityGetRenderEntity ( idEntity* ent ) { + return ent->GetRenderEntity(); +} + +/* +================ +idGameEdit::EntityGetName +================ +*/ +const char* idGameEdit::EntityGetName ( idEntity* ent ) const { + if ( !ent ) { + return ""; + } + return ent->GetName(); +} + +/* +================ +idGameEdit::EntityGetClassname +================ +*/ +const char* idGameEdit::EntityGetClassname ( idEntity* ent ) const { + return ent->GetType()->classname; +} + +/* +================ +idGameEdit::EntityIsDerivedFrom +================ +*/ +bool idGameEdit::EntityIsDerivedFrom ( idEntity* ent, const char* classname ) const { + idTypeInfo* type; + type = idClass::GetClass ( classname ); + if ( !type ) { + return false; + } + + return ent->IsType ( *type ); +} + +/* +================ +idGameEdit::EntityIsValid +================ +*/ +int idGameEdit::EntityToSafeId ( idEntity* ent ) const { + if ( !ent ) { + return 0; + } + return ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameEdit::EntityFromSafeId +================ +*/ +idEntity *idGameEdit::EntityFromSafeId( int safeID ) const { + int entityNum = safeID & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( safeID >> GENTITYNUM_BITS ) ) ) { + return gameLocal.entities[ entityNum ]; + } + return NULL; +} + +/* +================ +idGameEdit::EntitySetSkin +================ +*/ +void idGameEdit::EntitySetSkin ( idEntity* ent, const char* temp ) const { + ent->SetSkin ( declManager->FindSkin ( temp ) ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityClearSkin ( idEntity* ent ) const { + ent->ClearSkin ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityShow ( idEntity* ent ) const { + ent->Show ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityHide ( idEntity* ent ) const { + ent->Hide ( ); +} + +/* +================ +idGameEdit::EntityGetBounds +================ +*/ +void idGameEdit::EntityGetBounds ( idEntity* ent, idBounds &bounds ) const { + bounds = ent->GetRenderEntity()->bounds; +} + +/* +================ +idGameEdit::EntityPlayAnim +================ +*/ +int idGameEdit::EntityPlayAnim ( idEntity* ent, int animNum, int time, int blendtime ) { + if ( !ent->GetAnimator ( ) ) { + return 0; + } + ent->GetAnimator()->PlayAnim ( ANIMCHANNEL_ALL, animNum, time, blendtime ); + ent->GetAnimator()->ServiceAnims ( time, time ); + return ent->GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->GetEndTime ( ); +} + +/* +================ +idGameEdit::EntitySetFrame +================ +*/ +void idGameEdit::EntitySetFrame ( idEntity* ent, int animNum, int frame, int time, int blendtime ) { + idAnimator* animator; + + animator = ent->GetAnimator ( ); + if ( !animator ) { + return; + } + + animator->ClearAllAnims ( time, time ); + + // Move to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, frame, frame, 1.0f, 0 }; + animator->SetFrame ( ANIMCHANNEL_ALL, animNum, frameBlend ); +// RAVEN END + animator->ForceUpdate ( ); +} + +/* +================ +idGameEdit::EntityGetDelta +================ +*/ +void idGameEdit::EntityGetDelta ( idEntity* ent, int fromTime, int toTime, idVec3& delta ) { + ent->GetAnimator()->GetDelta ( fromTime, toTime, delta ); +} + +/* +================ +idGameEdit::EntityRemoveOriginOffset +================ +*/ +void idGameEdit::EntityRemoveOriginOffset ( idEntity* ent, bool remove ) { + ent->GetAnimator()->RemoveOriginOffset ( remove ); +} + +/* +================ +idGameEdit::EntityStopAllEffects +================ +*/ +void idGameEdit::EntityStopAllEffects ( idEntity* ent ) { + ent->StopAllEffects ( ); + ent->StopSound ( SND_CHANNEL_ANY, false ); +} + +// RAVEN BEGIN +// scork: some accessor functions for various utils +idEntity *idGameEdit::EntityGetNextTeamEntity( idEntity *pEnt ) const { + return pEnt->GetNextTeamEntity(); +} +void idGameEdit::GetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum, idAngles *deltaViewAngles ) const +{ + game->GetPlayerInfo( v3Origin, mat3Axis, PlayerNum, deltaViewAngles ); +} + +void idGameEdit::SetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum ) const +{ + game->SetPlayerInfo( v3Origin, mat3Axis, PlayerNum ); +} +void idGameEdit::EntitySetName( idEntity* pEnt, const char *psName ) +{ + pEnt->SetName( psName ); +} +// RAVEN END + +/* +================ +idGameEdit::LightSetParms +================ +*/ +void idGameEdit::LightSetParms ( idEntity* ent, int maxLevel, int currentLevel, float radius ) { + int data; + idLight* light; + + // Switch to a light entity + light = dynamic_cast(ent); + if ( !light ) + { + return; + } + + light->ProcessEvent ( &EV_Light_SetMaxLightLevel, maxLevel ); + light->ProcessEvent ( &EV_Light_SetCurrentLightLevel, (int)currentLevel ); + + (*(float*)&data) = radius; + light->ProcessEventArgPtr ( &EV_Light_SetRadius, &data ); + + light->SetLightLevel(); +} +// RAVEN END + +/* +================ +idGameEdit::PlayerIsValid +================ +*/ +bool idGameEdit::PlayerIsValid() const { + return ( gameLocal.GetLocalPlayer() != NULL ); +} + +/* +================ +idGameEdit::PlayerGetOrigin +================ +*/ +void idGameEdit::PlayerGetOrigin( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); +} + +/* +================ +idGameEdit::PlayerGetAxis +================ +*/ +void idGameEdit::PlayerGetAxis( idMat3 &axis ) const { + axis = gameLocal.GetLocalPlayer()->GetPhysics()->GetAxis(); +} + +/* +================ +idGameEdit::PlayerGetViewAngles +================ +*/ +void idGameEdit::PlayerGetViewAngles( idAngles &angles ) const { + angles = gameLocal.GetLocalPlayer()->viewAngles; +} + +/* +================ +idGameEdit::PlayerGetEyePosition +================ +*/ +void idGameEdit::PlayerGetEyePosition( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetEyePosition(); +} + + +/* +================ +idGameEdit::MapGetEntityDict +================ +*/ +const idDict *idGameEdit::MapGetEntityDict( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + return &mapent->epairs; + } + } + return NULL; +} + +/* +================ +idGameEdit::MapSave +================ +*/ +void idGameEdit::MapSave( const char *path ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { +// RAVEN BEGIN +// rjohnson: added entity export + if (mapFile->HasExportEntities()) { + mapFile->WriteExport( (path) ? path : mapFile->GetName() ); + } else { + if ( path ) { + mapFile->Write( path, ".map"); + } else { + idStr osPath; + osPath = mapFile->GetName ( ); + osPath.DefaultFileExtension ( ".map" ); + idFile* file = fileSystem->OpenFileRead ( osPath ); + if ( file ) { + osPath = file->GetFullPath ( ); + fileSystem->CloseFile ( file ); + mapFile->Write ( osPath, ".map", false ); + } else { + mapFile->Write ( file->GetName(), ".map" ); + } + } + } +// RAVEN END + } +} + +// RAVEN BEGIN +// rjohnson: added entity export +bool idGameEdit::MapHasExportEntities( void ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { + return mapFile->HasExportEntities(); + } + return false; +} +// scork: simple query function for the sound editor +// cdr: changed to also return the full string name of the map file (still compatable as a bool test) +const char* idGameEdit::MapLoaded( void ) const { + + const char *psMapName = gameLocal.GetMapName(); + if (psMapName && psMapName[0]) { + return psMapName; + } + return 0; +} + +// cdr: AASTactical +idAASFile* idGameEdit::GetAASFile( int i ) { + if (gameLocal.GetAAS( i )) { + return gameLocal.GetAAS( i )->GetFile(); + } + return 0; +} +// RAVEN END + +/* +================ +idGameEdit::MapSetEntityKeyVal +================ +*/ +void idGameEdit::MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + mapent->epairs.Set( key, val ); + } + } +} + +/* +================ +idGameEdit::MapCopyDictToEntity +================ +*/ +void idGameEdit::MapCopyDictToEntity( const char *name, const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + for ( int i = 0; i < dict->GetNumKeyVals(); i++ ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + const char *key = kv->GetKey(); + const char *val = kv->GetValue(); + mapent->epairs.Set( key, val ); + } + } + } +} + + + +/* +================ +idGameEdit::MapGetUniqueMatchingKeyVals +================ +*/ +int idGameEdit::MapGetUniqueMatchingKeyVals( const char *key, const char *list[], int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + for ( int i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *ent = mapFile->GetEntity( i ); + if ( ent ) { + const char *k = ent->epairs.GetString( key ); + if ( k && *k && count < max ) { + list[count++] = k; + } + } + } + } + return count; +} + +/* +================ +idGameEdit::MapAddEntity +================ +*/ +void idGameEdit::MapAddEntity( const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = new idMapEntity(); + ent->epairs = *dict; + mapFile->AddEntity( ent ); + } +} + +/* +================ +idGameEdit::MapRemoveEntity +================ +*/ +void idGameEdit::MapRemoveEntity( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = mapFile->FindEntity( name ); + if ( ent ) { + mapFile->RemoveEntity( ent ); + } + } +} + + +/* +================ +idGameEdit::MapGetEntitiesMatchignClassWithString +================ +*/ +int idGameEdit::MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + int entCount = mapFile->GetNumEntities(); + for ( int i = 0 ; i < entCount; i++ ) { + idMapEntity *ent = mapFile->GetEntity(i); + if (ent) { + idStr work = ent->epairs.GetString("classname"); + if ( work.Icmp( classname ) == 0 ) { + if ( match && *match ) { + work = ent->epairs.GetString( "soundgroup" ); + if ( count < max && work.Icmp( match ) == 0 ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } else if ( count < max ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } + } + } + } + return count; +} + + +/* +================ +idGameEdit::MapEntityTranslate +================ +*/ +void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + idVec3 origin; + mapent->epairs.GetVector( "origin", "", origin ); + origin += v; + mapent->epairs.SetVector( "origin", origin ); + } + } +} + +// RAVEN BEGIN +// bdube: new game edit stuff +/* +================ +idGameEdit::PlayerTraceFromEye +================ +*/ +bool idGameEdit::PlayerTraceFromEye ( trace_t &results, float length, int contentMask ) { + idVec3 start; + idVec3 end; + idAngles angles; + + PlayerGetEyePosition( start ); + PlayerGetEyePosition( end ); + PlayerGetViewAngles ( angles ); + + end += angles.ToForward() * length; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint ( gameLocal.GetLocalPlayer(), results, start, end, contentMask, gameLocal.GetLocalPlayer() ); +// RAVEN END +} + +/* +================ +idGameEdit::EffectRefreshTemplate +================ +*/ +void idGameEdit::EffectRefreshTemplate ( const idDecl *effect ) const { + rvClientEntity* cent; + + // Restart all effects + for ( cent = gameLocal.clientSpawnedEntities.Next(); cent; cent = cent->spawnNode.Next() ) { + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex ( ) == effect->Index() ) { + clientEffect->Restart ( ); + } + } + } +} + +/* +================ +idGameEdit::GetGameTime +================ +*/ +int idGameEdit::GetGameTime ( int *previous ) const { + if ( previous ) { + *previous = gameLocal.previousTime; + } + + return gameLocal.time; +} + +/* +================ +idGameEdit::SetGameTime +================ +*/ +void idGameEdit::SetGameTime ( int time ) const { + gameLocal.time = time; + gameLocal.previousTime = time; +} + +/* +================ +idGameEdit::TracePoint +================ +*/ +bool idGameEdit::TracePoint ( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint( gameLocal.GetLocalPlayer(), results, start, end, contentMask, NULL ); +// RAVEN END +} + +/* +================ +idGameEdit::CacheDictionaryMedia +================ +*/ +void idGameEdit::CacheDictionaryMedia ( const idDict* dict ) const { + gameLocal.CacheDictionaryMedia ( dict ); +} + +/* +================ +idGameEdit::SetCamera +================ +*/ +void idGameEdit::SetCamera ( idEntity* camera ) const { + gameLocal.SetCamera ( dynamic_cast(camera) ); +} + +/* +================ +idGameEdit::ScriptGetStatementLineNumber +================ +*/ +int idGameEdit::ScriptGetStatementLineNumber ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).linenumber; +} + +/* +================ +idGameEdit::ScriptGetStatementFileName +================ +*/ +const char* idGameEdit::ScriptGetStatementFileName ( idProgram* program, int instructionPointer ) const { + return program->GetFilename ( program->GetStatement ( instructionPointer ).file ); +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetStatementOperator ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).op; +} + +/* +================ +idGameEdit::ScriptGetCurrentFunction +================ +*/ +void* idGameEdit::ScriptGetCurrentFunction ( idInterpreter* interpreter ) const { + return (void*)interpreter->GetCurrentFunction ( ); +} + +/* +================ +idGameEdit::ScriptGetCurrentFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCurrentFunctionName ( idInterpreter* interpreter ) const { + if ( interpreter->GetCurrentFunction ( ) ) { + return interpreter->GetCurrentFunction ( )->Name(); + } + return ""; +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetCallstackDepth ( idInterpreter* interpreter ) const { + return interpreter->GetCallstackDepth ( ); +} + +/* +================ +idGameEdit::ScriptGetCallstackFunction +================ +*/ +void* idGameEdit::ScriptGetCallstackFunction ( idInterpreter* interpreter, int depth ) const { + return (void*)interpreter->GetCallstack ( )[depth].f; +} + +/* +================ +idGameEdit::ScriptGetCallstackFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCallstackFunctionName ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].f->Name(); +} + +/* +================ +idGameEdit::ScriptGetCallstackStatement +================ +*/ +int idGameEdit::ScriptGetCallstackStatement ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].s; +} + +/* +================ +idGameEdit::ScriptIsReturnOperator +================ +*/ +bool idGameEdit::ScriptIsReturnOperator ( int op ) const { + return op == OP_RETURN; +} + +/* +================ +idGameEdit::ScriptGetRegisterValue +================ +*/ +const char* idGameEdit::ScriptGetRegisterValue ( idInterpreter* interpreter, const char* varname, int callstackDepth ) const { + static char value[4096]; + idStr out; + + value[0] = '\0'; + if ( interpreter->GetRegisterValue ( varname, out, callstackDepth ) ) { + idStr::snPrintf ( value, 4095, out.c_str() ); + } + + return value; +} + +/* +================ +idGameEdit::ScriptGetThread +================ +*/ +idThread* idGameEdit::ScriptGetThread ( idInterpreter* interpreter ) const { + return interpreter->GetThread(); +} + +/* +================ +idGameEdit::ThreadGetCount +================ +*/ +int idGameEdit::ThreadGetCount ( void ) { + return idThread::GetThreads().Num(); +} + +/* +================ +idGameEdit::ThreadGetThread +================ +*/ +idThread* idGameEdit::ThreadGetThread ( int index ) { + return idThread::GetThreads()[index]; +} + +/* +================ +idGameEdit::ThreadGetName +================ +*/ +const char* idGameEdit::ThreadGetName ( idThread* thread ) { + return thread->GetThreadName ( ); +} + +/* +================ +idGameEdit::ThreadGetNumber +================ +*/ +int idGameEdit::ThreadGetNumber ( idThread* thread ) { + return thread->GetThreadNum ( ); +} + +/* +================ +idGameEdit::ThreadGetState +================ +*/ +const char* idGameEdit::ThreadGetState ( idThread* thread ) { + if ( thread->IsDying() ) { + return "Dying"; + } else if ( thread->IsWaiting() ) { + return "Waiting"; + } else if ( thread->IsDoneProcessing() ) { + return "Stopped"; + } + + return "Running"; +} + +/* +================ +idGameEdit::GetClassDebugInfo +================ +*/ +void idGameEdit::GetClassDebugInfo ( const idEntity* entity, debugInfoProc_t proc, void* userdata ) { + const_cast( entity )->GetDebugInfo ( proc, userdata ); +} + +/* +================ +idGameEdit::GetGameEntityRegisterTime +================ +*/ +int idGameEdit::GetGameEntityRegisterTime ( void ) const { + return gameLocal.entityRegisterTime; +} + +/* +================ +idGameEdit::GetFirstSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetFirstSpawnedEntity ( void ) const { + return gameLocal.spawnedEntities.Next(); +} + +/* +================ +idGameEdit::GetNextSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetNextSpawnedEntity ( idEntity* from ) const { + if ( !from ) { + return NULL; + } + return from->spawnNode.Next(); +} + + +// RAVEN END + +// RAVEN BEGIN +// mekberg: access to animationlib functions for radiant +void idGameEdit::FlushUnusedAnims ( void ) { + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END +} + +/* +=============================================================================== + + rvModviewModel + + Actor model for modview + +=============================================================================== +*/ + +class rvModviewModel : public idActor { +public: + CLASS_PROTOTYPE( rvModviewModel ); + + rvModviewModel ( void ); + +private: + + void Event_Speak ( const char* lipsync ); +}; + +CLASS_DECLARATION( idActor, rvModviewModel ) + EVENT( AI_Speak, rvModviewModel::Event_Speak ) +END_CLASS + +/* +===================== +rvModviewModel::rvModviewModel +===================== +*/ +rvModviewModel::rvModviewModel ( void ) { +} + +/* +===================== +rvModviewModel::Event_Speak +===================== +*/ +void rvModviewModel::Event_Speak ( const char* lipsync ) { + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + lipsync = spawnArgs.GetString ( lipsync ); + if ( !lipsync || !*lipsync ) { + return; + } + + if ( head ) { + head->StartLipSyncing( lipsync ); + } else { + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +// RAVEN END + diff --git a/src/game/GameEdit.h b/src/game/GameEdit.h new file mode 100644 index 0000000..53e7a3f --- /dev/null +++ b/src/game/GameEdit.h @@ -0,0 +1,119 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_EDIT_H__ +#define __GAME_EDIT_H__ + + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +class idCursor3D : public idEntity { +public: + CLASS_PROTOTYPE( idCursor3D ); + + idCursor3D( void ); + ~idCursor3D( void ); + + void Spawn( void ); + void Present( void ); + void Think( void ); + + idForce_Drag drag; + idVec3 draggedPosition; +}; + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +class idDragEntity { +public: + idDragEntity( void ); + ~idDragEntity( void ); + + void Clear(); + void Update( idPlayer *player ); + void SetSelected( idEntity *ent ); + idEntity * GetSelected( void ) const { return selected.GetEntity(); } + void DeleteSelected( void ); + void BindSelected( void ); + void UnbindSelected( void ); + +private: + idEntityPtr dragEnt; // entity being dragged + jointHandle_t joint; // joint being dragged + int id; // id of body being dragged + idVec3 localEntityPoint; // dragged point in entity space + idVec3 localPlayerPoint; // dragged point in player space + idStr bodyName; // name of the body being dragged + idCursor3D * cursor; // cursor entity + idEntityPtr selected; // last dragged entity + + void StopDrag( void ); +}; + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ +typedef struct selectedTypeInfo_s { + idTypeInfo *typeInfo; + idStr textKey; +} selectedTypeInfo_t; + +class idEditEntities { +public: + idEditEntities( void ); + bool SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ); + void AddSelectedEntity( idEntity *ent ); + void RemoveSelectedEntity( idEntity *ent ); + void ClearSelectedEntities( void ); + void DisplayEntities( void ); + bool EntityIsSelectable( idEntity *ent, idVec4 *color = NULL, idStr *text = NULL ); +// RAVEN BEGIN +// bdube: added + idEntity* FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ); +// RAVEN END +private: + int nextSelectTime; + idList selectableEntityClasses; + idList selectedEntities; +}; + +#endif /* !__GAME_EDIT_H__ */ diff --git a/src/game/Game_Debug.cpp b/src/game/Game_Debug.cpp new file mode 100644 index 0000000..8e71983 --- /dev/null +++ b/src/game/Game_Debug.cpp @@ -0,0 +1,396 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Game_Debug.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +rvGameDebug gameDebug; + +/* +=============================================================================== + + rvGameDebug + +=============================================================================== +*/ + +/* +================ +rvGameDebug::rvGameDebug +================ +*/ +rvGameDebug::rvGameDebug ( void ) { +} + +/* +================ +rvGameDebug::Init +================ +*/ +void rvGameDebug::Init ( void ) { + focusEntity = NULL; + overrideEntity = NULL; + currentHud = NULL; + memset ( &hud, 0, sizeof(hud) ); + + jumpIndex = -1; + jumpPoints.Clear ( ); +} + +/* +================ +rvGameDebug::Shutdown +================ +*/ +void rvGameDebug::Shutdown ( void ) { + nonGameState.Clear ( ); + gameStats.Clear ( ); + + currentHud = NULL; + focusEntity = NULL; + overrideEntity = NULL; + + memset ( &hud, 0, sizeof(hud) ); +} + +/* +================ +rvGameDebug::Think +================ +*/ +void rvGameDebug::BeginFrame ( void ) { + int hudIndex; + + inFrame = true; + + hudIndex = g_showDebugHud.GetInteger(); + if ( hudIndex <= 0 ) { + focusEntity = NULL; + currentHud = NULL; + return; + } + + // Update the current debug hud if the cvar has changed + if ( g_showDebugHud.IsModified() || !currentHud ) { + if ( hudIndex > DBGHUD_MAX ) { + g_showDebugHud.SetInteger( 0 ); + focusEntity = NULL; + currentHud = NULL; + return; + } + + g_showDebugHud.ClearModified( ); + + // If the debug hud hasnt been loaded yet then load it now + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->FindGui( va("guis/debug/hud%d.gui",hudIndex), true, true, true ); + + // If the hud wasnt found auto-generate one + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->Alloc(); + } + } + + // Cache the debug hud state. + currentHud = hud[hudIndex]; + } + + currentHud->ClearState ( ); + + // IF there is an override entity just use that, otherwise find one that + // is in front of the players crosshair + if ( overrideEntity ) { + focusEntity = overrideEntity; + overrideEntity = NULL; + } else { + idPlayer* player; + idVec3 start; + idVec3 end; + trace_t tr; + + player = gameLocal.GetLocalPlayer ( ); + start = player->GetEyePosition(); + end = start + player->viewAngles.ToForward() * 4096.0f; + + gameLocal.TracePoint( player, tr, start, end, MASK_SHOT_BOUNDINGBOX, player ); + if ( tr.fraction < 1.0 && tr.c.entityNum != ENTITYNUM_WORLD ) { + focusEntity = static_cast(gameLocal.entities[ tr.c.entityNum ]); + } else { + focusEntity = NULL; + } + } + + // Automatically add some basic entity information + if ( focusEntity ) { + SetInt ( "entityNumber", focusEntity->entityNumber ); + SetInt ( "entityHealth", focusEntity->health ); + SetString ( "entityName", focusEntity->name ); + SetString ( "entityClass", focusEntity->GetClassname ( ) ); + } + + // General map information + SetString ( "mapname", gameLocal.GetMapName ( ) ); + SetString ( "version", cvarSystem->GetCVarString ( "si_version" ) ); + if ( gameLocal.GetLocalPlayer() ) { + SetString ( "viewpos", gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin().ToString() ); + } +} + +/* +================ +rvGameDebug::EndFrame +================ +*/ +void rvGameDebug::EndFrame ( void ) { + inFrame = false; +} + +/* +================ +rvGameDebug::DrawHud +================ +*/ +void rvGameDebug::DrawHud ( void ) { + if ( !currentHud ) { + return; + } + + // The scratch hud displays key value pairs in a list so + // we need to push the keys into the list + if ( IsHudActive ( DBGHUD_SCRATCH ) ) { + int index; + idDict tempState; + + tempState.Copy ( currentHud->State() ); + currentHud->ClearState ( ); + + for ( index = 0; index < tempState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + + kv = tempState.GetKeyVal ( index ); + SetString ( va("scratchKey_item_%d", index ), kv->GetKey() ); + SetString ( va("scratchValue_item_%d", index ), kv->GetValue() ); + } + } + else { + int index; + for ( index = 0; index < nonGameState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + kv = nonGameState.GetKeyVal ( index ); + currentHud->SetStateString ( kv->GetKey(), kv->GetValue() ); + } + } + + // Activate the hud to ensure lists get updated and redraw it + currentHud->StateChanged ( gameLocal.time ); + currentHud->Redraw( gameLocal.time ); +} + +/* +================ +rvGameDebug::AppendList +================ +*/ +void rvGameDebug::AppendList ( const char* listname, const char* value ) { + if ( !currentHud ) { + return; + } + + int count; + char countName[1024]; + char itemName[1024]; + + idStr::snPrintf ( countName, 1023, "%sCount", listname ); + count = GetInt ( countName ); + + idStr::snPrintf ( itemName, 1023, "%s_item_%d", listname, count ); + SetString ( va("%s_item_%d", listname, count ), value ); + + SetInt ( countName, count + 1 ); +} + +/* +================ +rvGameDebug::Set +================ +*/ +void rvGameDebug::SetInt ( const char* key, int value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateInt( key, value ); + } + } else { + nonGameState.SetInt ( key, value ); + } +} + +void rvGameDebug::SetFloat ( const char* key, float value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateFloat( key, value ); + } + } else { + nonGameState.SetFloat ( key, value ); + } +} + +void rvGameDebug::SetString ( const char* key, const char* value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateString( key, value ); + } + } else { + nonGameState.Set ( key, value ); + } +} + +/* +================ +rvGameDebug::Get +================ +*/ +int rvGameDebug::GetInt ( const char* key ) { + return currentHud ? currentHud->State().GetInt ( key ) : 0; +} + +float rvGameDebug::GetFloat ( const char* key ) { + return currentHud ? currentHud->State().GetFloat ( key ) : 0.0f; +} + +const char* rvGameDebug::GetString ( const char* key ) { + return currentHud ? currentHud->State().GetString ( key ) : ""; +} + +/* +================ +rvGameDebug::SetStat +================ +*/ +void rvGameDebug::SetStatInt ( const char* key, int value ) { + gameStats.SetInt ( key, value ); +} + +void rvGameDebug::SetStatFloat ( const char* key, float value ) { + gameStats.SetFloat ( key, value ); +} + +void rvGameDebug::SetStatString ( const char* key, const char* value ) { + gameStats.Set ( key, value ); +} + +/* +================ +rvGameDebug::GetStat +================ +*/ +int rvGameDebug::GetStatInt ( const char* key ) { + return gameStats.GetInt ( key ); +} + +float rvGameDebug::GetStatFloat ( const char* key ) { + return gameStats.GetFloat ( key ); +} + +const char* rvGameDebug::GetStatString ( const char* key ) { + return gameStats.GetString ( key ); +} + + +/* +================ +rvGameDebug::JumpAdd +================ +*/ +void rvGameDebug::JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ) { + debugJumpPoint_t jump; + jump.name = name; + jump.origin = origin; + jump.angles = angles; + jumpPoints.Append ( jump ); +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( const char* name ) { + int index; + for ( index = 0; index < jumpPoints.Num(); index ++ ) { + if ( !jumpPoints[index].name.Icmp ( name ) ) { + JumpTo ( index ); + return; + } + } +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( int jumpIndex ) { + if ( jumpIndex >= jumpPoints.Num() ) { + return; + } + + jumpIndex = jumpIndex; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player ) { + player->Teleport( jumpPoints[jumpIndex].origin, jumpPoints[jumpIndex].angles, NULL ); + } +} + +/* +================ +rvGameDebug::JumpNext +================ +*/ +void rvGameDebug::JumpNext ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + 1 ) % jumpPoints.Num() ); +} + +/* +================ +rvGameDebug::JumpPrev +================ +*/ +void rvGameDebug::JumpPrev ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + jumpPoints.Num() - 1 ) % jumpPoints.Num() ); +} diff --git a/src/game/Game_Debug.h b/src/game/Game_Debug.h new file mode 100644 index 0000000..0e3cb51 --- /dev/null +++ b/src/game/Game_Debug.h @@ -0,0 +1,119 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Game_Debug.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_DEBUG_H__ +#define __GAME_DEBUG_H__ + +#define DBGHUD_NONE (0) +#define DBGHUD_PLAYER (1<<0) +#define DBGHUD_PHYSICS (1<<1) +#define DBGHUD_AI (1<<2) +#define DBGHUD_VEHICLE (1<<3) +#define DBGHUD_PERFORMANCE (1<<4) +#define DBGHUD_FX (1<<5) +#define DBGHUD_MAPINFO (1<<6) +#define DBGHUD_AI_PERFORM (1<<7) +#define DBGHUD_SCRATCH (1<<31) +#define DBGHUD_ANY (0xFFFFFFFF) + +#define DBGHUD_MAX 32 + +typedef struct debugJumpPoint_s +{ + idStr name; + idVec3 origin; + idAngles angles; + +} debugJumpPoint_t; + +class rvGameDebug { +public: + + rvGameDebug( ); + + void Init ( void ); + void Shutdown ( void ); + void BeginFrame ( void ); + void EndFrame ( void ); + + void SetFocusEntity ( idEntity* focusEnt ); + + bool IsHudActive ( int hudMask, const idEntity* focusEnt = NULL ); + + void DrawHud ( void ); + + void AppendList ( const char* listname, const char* value ); + + void SetInt ( const char* key, int value ); + void SetFloat ( const char* key, float value ); + void SetString ( const char* key, const char* value ); + + int GetInt ( const char* key ); + float GetFloat ( const char* key ); + const char* GetString ( const char* key ); + + void SetStatInt ( const char* key, int value ); + void SetStatFloat ( const char* key, float value ); + void SetStatString ( const char* key, const char* value ); + + int GetStatInt ( const char* key ); + float GetStatFloat ( const char* key ); + const char* GetStatString ( const char* key ); + + void JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ); + void JumpTo ( const char* name ); + void JumpTo ( int jumpIndex ); + void JumpNext ( void ); + void JumpPrev ( void ); + +private: + + idList jumpPoints; + int jumpIndex; + + idEntityPtr focusEntity; + idEntityPtr overrideEntity; + idUserInterface * hud[DBGHUD_MAX+1]; + idUserInterface * currentHud; + idDict nonGameState, gameStats; + bool inFrame; +}; + +ID_INLINE bool rvGameDebug::IsHudActive ( int hudMask, const idEntity* ent ) { + return (g_showDebugHud.GetInteger() && (hudMask & (1 << (g_showDebugHud.GetInteger()-1))) && (!ent || focusEntity == ent ) ); +} + +ID_INLINE void rvGameDebug::SetFocusEntity ( idEntity* ent ) { + overrideEntity = ent; +} + +extern rvGameDebug gameDebug; + +#endif /* !__GAME_DEBUG_H__ */ diff --git a/src/game/Game_Log.cpp b/src/game/Game_Log.cpp new file mode 100644 index 0000000..0c6cf5b --- /dev/null +++ b/src/game/Game_Log.cpp @@ -0,0 +1,251 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Game_Log.h" + +/* +=============================================================================== + + rvGameLog + +=============================================================================== +*/ + +rvGameLogLocal gameLogLocal; +rvGameLog* gameLog = &gameLogLocal; + +/* +================ +rvGameLogLocal::rvGameLogLocal +================ +*/ +rvGameLogLocal::rvGameLogLocal ( ) { + initialized = false; +} + +/* +================ +rvGameLogLocal::Init +================ +*/ +void rvGameLogLocal::Init ( void ) { + file = NULL; + indexCount = 0; + initialized = true; + + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); +} + +/* +================ +rvGameLogLocal::Shutdown +================ +*/ +void rvGameLogLocal::Shutdown ( void ) { + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); + + if ( initialized && file ) { + const char* out; + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + file->Flush ( ); + fileSystem->CloseFile ( file ); + file = NULL; + } + initialized = false; +} + +/* +================ +rvGameLogLocal::BeginFrame +================ +*/ +void rvGameLogLocal::BeginFrame ( int time ) { + // See if logging has been turned on or not + if ( g_gamelog.GetBool ( ) != initialized ) { + if ( initialized ) { + Shutdown ( ); + return; + } else { + Init ( ); + } + } else if ( !g_gamelog.GetBool ( ) ) { + return; + } +} + +/* +================ +rvGameLogLocal::EndFrame +================ +*/ +void rvGameLogLocal::EndFrame ( void ) { + int i; + const char* out; + bool wroteTime; + + // Dont do anything if not logging + if ( !g_gamelog.GetBool ( ) ) { + return; + } + + // When not in multiplayer, log the players approx origin and viewangles + if ( !gameLocal.isMultiplayer ) { + idPlayer* player; + player = gameLocal.GetLocalPlayer ( ); + if ( player ) { + Set ( "player0_origin", va("%d %d %d", (int)player->GetPhysics()->GetOrigin()[0], (int)player->GetPhysics()->GetOrigin()[1], (int)player->GetPhysics()->GetOrigin()[2] ) ); + Set ( "player0_angles_yaw", va("%g", (float)player->viewAngles[YAW] ) ); + Set ( "player0_angles_pitch", va("%g", (float)player->viewAngles[PITCH] ) ); + Set ( "player0_buttons", player->usercmd.buttons ); + Set ( "player0_health", player->health ); + Set ( "player0_armor", player->inventory.armor ); + } + } + + if ( !file ) { + idStr mapName; + idStr filename; + mapName = gameLocal.serverInfo.GetString( "si_map" ); + mapName.StripFileExtension ( ); + filename = "logs/" + mapName + "/" + cvarSystem->GetCVarString("win_username") + "_"; + + // Find a unique filename + for ( i = 0; fileSystem->ReadFile( filename + va("%06d.log", i ), NULL, NULL ) > 0; i ++ ); + + // Actually open the file now + file = fileSystem->OpenFileWrite ( filename + va("%06d.log", i ), "fs_cdpath" ); + if ( !file ) { + return; + } + + timer_fps.Stop( ); + timer_fps.Clear ( ); + timer_fps.Start ( ); + } else { + static int fpsIndex; + static float fpsValue[4]; + + timer_fps.Stop ( ); + fpsValue[(fpsIndex++)%4] = 1000.0f / (timer_fps.Milliseconds ( ) + 1); + if ( fpsIndex >= 4 ) { + GAMELOG_SET ( "fps", Min(60,(int)((int)(fpsValue[0] + fpsValue[1] + fpsValue[2] + fpsValue[3]) / 40.0f) * 10) ); + } + timer_fps.Clear ( ); + timer_fps.Start ( ); + } + + // Write out any new indexes that were added this frame + for ( ; indexCount < index.Num(); indexCount ++ ) { + const char* out; + out = va("#%d ", indexCount ); + file->Write ( out, strlen ( out ) ); + file->Write ( index[indexCount].c_str(), index[indexCount].Length() ); + file->Write ( "\r\n", 2 ); + } + + // Write out any data that was added this frame + wroteTime = false; + for ( i = frame.Num() - 1; i >= 0; i -- ) { + // TODO: filter + if ( oldframe[i] != frame[i] ) { + if ( !wroteTime ) { + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + wroteTime = true; + } + out = va(" %d \"", i ); + file->Write ( out, strlen(out) ); + file->Write ( frame[i].c_str(), frame[i].Length ( ) ); + file->Write ( "\"", 1 ); + oldframe[i] = frame[i]; + } + } + + if ( wroteTime ) { + file->Write ( "\r\n", 2 ); + file->Flush ( ); + } + + // Clear the frame for next time + for ( i = index.Num() - 1; i >= 0; i -- ) { + frame[i] = ""; + } +} + +/* +================ +rvGameLogLocal::Set +================ +*/ +void rvGameLogLocal::Set ( const char* keyword, const char* value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = value; +} + +void rvGameLogLocal::Set ( const char* keyword, int value ) { + Set ( keyword, va("%d", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, float value ) { + Set ( keyword, va("%g", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, bool value ) { + Set ( keyword, va("%d", (int)value ) ); +} + +/* +================ +rvGameLogLocal::Add +================ +*/ +void rvGameLogLocal::Add ( const char* keyword, int value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%d",atoi(frame[i].c_str()) + value ); +} + +void rvGameLogLocal::Add ( const char* keyword, float value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%g",atof(frame[i].c_str()) + value ); +} + + diff --git a/src/game/Game_Log.h b/src/game/Game_Log.h new file mode 100644 index 0000000..8cce723 --- /dev/null +++ b/src/game/Game_Log.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_LOG_H__ +#define __GAME_LOG_H__ + +//============================================================================ + +class rvGameLogLocal : public rvGameLog { +public: + + rvGameLogLocal ( void ); + + virtual void Init ( void ); + virtual void Shutdown ( void ); + + virtual void BeginFrame ( int time ); + virtual void EndFrame ( void ); + + virtual void Set ( const char* keyword, int value ); + virtual void Set ( const char* keyword, float value ); + virtual void Set ( const char* keyword, const char* value ); + virtual void Set ( const char* keyword, bool value ); + + virtual void Add ( const char* keyword, int value ); + virtual void Add ( const char* keyword, float value ); + +protected: + + int lastTime; + int indexCount; + idStrList index; + idStrList frame; + idStrList oldframe; + idFile* file; + bool initialized; + idTimer timer_fps; +}; + +extern rvGameLogLocal gameLogLocal; + +#endif // __GAME_LOG_H__ diff --git a/src/game/Game_local.cpp b/src/game/Game_local.cpp new file mode 100644 index 0000000..ef1b70e --- /dev/null +++ b/src/game/Game_local.cpp @@ -0,0 +1,8532 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +#include "../bse/BSEInterface.h" +#include "Projectile.h" +#include "client/ClientEffect.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AAS_tactical.h" +#include "Game_Log.h" +// RAVEN END + +//#define UI_DEBUG 1 + +#ifdef GAME_DLL + +idSys * sys = NULL; +idCommon * common = NULL; +idCmdSystem * cmdSystem = NULL; +idCVarSystem * cvarSystem = NULL; +idFileSystem * fileSystem = NULL; +idNetworkSystem * networkSystem = NULL; +idRenderSystem * renderSystem = NULL; +idSoundSystem * soundSystem = NULL; +idRenderModelManager * renderModelManager = NULL; +idUserInterfaceManager * uiManager = NULL; +idDeclManager * declManager = NULL; +idAASFileManager * AASFileManager = NULL; +idCollisionModelManager * collisionModelManager = NULL; + +// RAVEN BEGIN +// jscott: game interface to the fx system +rvBSEManager * bse = NULL; +// RAVEN END + +idCVar * idCVar::staticVars = NULL; + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui +idCVarHelp * idCVarHelp::staticCVarHelps = NULL; +idCVarHelp * idCVarHelp::staticCVarHelpsTail = NULL; +// RAVEN END + +idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL|CVAR_SYSTEM, "force generic platform independent SIMD" ); + +#endif + +idRenderWorld * gameRenderWorld = NULL; // all drawing is done to this world + +static gameExport_t gameExport; + +// global animation lib +// RAVEN BEGIN +// jsinger: changed to a pointer to prevent its constructor from allocating +// memory before the unified allocator could be initialized +idAnimManager *animationLib = NULL; +// RAVEN END + +// the rest of the engine will only reference the "game" variable, while all local aspects stay hidden +idGameLocal gameLocal; +idGame * game = &gameLocal; // statically pointed at an idGameLocal + +const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = { + "none", "metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic", + "ricochet", "surftype10", "surftype11", "surftype12", "surftype13", "surftype14", "surftype15" +}; + +/* +=========== +GetGameAPI +============ +*/ + +#if __GNUC__ >= 4 +#pragma GCC visibility push(default) +#endif +extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) { + + if ( import->version == GAME_API_VERSION ) { + + // set interface pointers used by the game + sys = import->sys; + common = import->common; + cmdSystem = import->cmdSystem; + cvarSystem = import->cvarSystem; + fileSystem = import->fileSystem; + networkSystem = import->networkSystem; + renderSystem = import->renderSystem; + soundSystem = import->soundSystem; + renderModelManager = import->renderModelManager; + uiManager = import->uiManager; + declManager = import->declManager; + AASFileManager = import->AASFileManager; + collisionModelManager = import->collisionModelManager; +// RAVEN BEGIN +// jscott: import the fx system + bse = import->bse; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: import the memory system variables +#ifdef _RV_MEM_SYS_SUPPORT + ::currentHeapArena = import->heapArena; + rvSetAllSysHeaps( import->systemHeapArray ); +#endif +// RAVEN END + } + + // set interface pointers used by idLib + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = cvarSystem; + idLib::fileSystem = fileSystem; + + // setup export interface + gameExport.version = GAME_API_VERSION; + gameExport.game = game; + gameExport.gameEdit = gameEdit; +// RAVEN BEGIN +// bdube: game log + gameExport.gameLog = gameLog; +// RAVEN END + + return &gameExport; +} +#if __GNUC__ >= 4 +#pragma GCC visibility pop +#endif + +/* +=========== +TestGameAPI +============ +*/ +void TestGameAPI( void ) { + gameImport_t testImport; + gameExport_t testExport; + + testImport.sys = ::sys; + testImport.common = ::common; + testImport.cmdSystem = ::cmdSystem; + testImport.cvarSystem = ::cvarSystem; + testImport.fileSystem = ::fileSystem; + testImport.networkSystem = ::networkSystem; + testImport.renderSystem = ::renderSystem; + testImport.soundSystem = ::soundSystem; + testImport.renderModelManager = ::renderModelManager; + testImport.uiManager = ::uiManager; + testImport.declManager = ::declManager; + testImport.AASFileManager = ::AASFileManager; + testImport.collisionModelManager = ::collisionModelManager; + +// RAVEN BEGIN +// jscott: import the fx system + testImport.bse = ::bse; +// RAVEN END + + testExport = *GetGameAPI( &testImport ); +} + +/* +================ +idGameLocal::BuildModList +================ +*/ +void idGameLocal::BuildModList( ) { + int i; + idStr currentMod; + + int numServers = networkSystem->GetNumScannedServers(); + + if ( filterMod >= 0 && filterMod < modList.Num() ) { + currentMod = modList[ filterMod ]; + } else { + currentMod = ""; + } + + modList.Clear(); + for (i = 0; i < numServers; i++) { + const scannedServer_t *server; + idStr modname; + + server = networkSystem->GetScannedServerInfo( i ); + + server->serverInfo.GetString( "fs_game", "", modname ); + modname.ToLower(); + modList.AddUnique( modname ); + } + + modList.Sort(); + + if ( modList.Num() > 0 && (modList[ 0 ].Cmp( "" ) == 0) ) { + modList.RemoveIndex( 0 ); + } + + filterMod = modList.Num(); + for (i = 0; i < modList.Num(); i++) { + if ( modList[ i ].Icmp( currentMod ) == 0 ) { + filterMod = i; + } + } +} + +/* +================ +FilterByMod +================ +*/ +static int FilterByMod( const int* serverIndex ) { + const scannedServer_t *server; + + if ( gameLocal.filterMod < 0 || gameLocal.filterMod >= gameLocal.modList.Num() ) { + return (int)false; + } + + server = networkSystem->GetScannedServerInfo( *serverIndex ); + + return (int)(gameLocal.modList[ gameLocal.filterMod ].Icmp( server->serverInfo.GetString( "fs_game" ) ) != 0); +} + +static sortInfo_t filterByMod = { + SC_ALL, + NULL, + FilterByMod, + "#str_123006" +}; + +/* +=========== +idGameLocal::idGameLocal +============ +*/ +idGameLocal::idGameLocal() { + Clear(); +} + +/* +=========== +idGameLocal::Clear +============ +*/ +void idGameLocal::Clear( void ) { + int i; + + serverInfo.Clear(); + numClients = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + usercmds = NULL; + memset( entities, 0, sizeof( entities ) ); + memset( spawnIds, -1, sizeof( spawnIds ) ); + firstFreeIndex = 0; + num_entities = 0; + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortPushers = false; + sortTeamMasters = false; + persistentLevelInfo.Clear(); + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + random.SetSeed( 0 ); + world = NULL; + frameCommandThread = NULL; + testmodel = NULL; +// RAVEN BEGIN +// bdube: not using id effects +// testFx = NULL; +// ddynerman: multiple clip worlds + ShutdownInstances(); +// mwhitlock: Dynamic memory consolidation + clip.Clear(); +// RAVEN END + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + clientsPVS[ i ].i = -1; + clientsPVS[ i ].h = -1; + } + freePlayerPVS = false; + pvs.Shutdown(); + sessionCommand.Clear(); + locationEntities = NULL; + editEntities = NULL; + entityHash.Clear( 1024, MAX_GENTITIES ); + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + framenum = 0; + previousTime = 0; + time = 0; + vacuumAreaNum = 0; + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + mapFileName.Clear(); +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped.Clear(); +// RAVEN END + mapFile = NULL; + spawnCount = INITIAL_SPAWN_COUNT; + memset( isMapEntity, 0, sizeof( bool ) * MAX_GENTITIES ); + + camera = NULL; + +// RAVEN BEGIN +// jscott: for portal skies + portalSky = NULL; +// RAVEN END + + aasList.Clear(); + aasNames.Clear(); +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + spawnArgs.Clear(); + gravity.Set( 0, 0, -1 ); + playerPVS.i = -1; + playerPVS.h = -1; + playerConnectedAreas.i = -1; + playerConnectedAreas.h = -1; + gamestate = GAMESTATE_UNINITIALIZED; + skipCinematic = false; + influenceActive = false; + + localClientNum = 0; + isMultiplayer = false; + isServer = false; + isClient = false; + realClientTime = 0; + isNewFrame = true; + entityDefBits = 0; + + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = false; +// RITUAL END + globalMaterial = NULL; + newInfo.Clear(); + lastGUIEnt = NULL; + lastGUI = 0; + + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + eventQueue.Init(); + + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear(); + memset( clientEntities, 0, sizeof( clientEntities ) ); + memset( clientSpawnIds, -1, sizeof( clientSpawnIds ) ); + + gameDebug.Shutdown(); + gameLogLocal.Shutdown(); + currentThinkingEntity = NULL; + + memset( lagometer, 0, sizeof( lagometer ) ); + + demoState = DEMO_NONE; + serverDemo = false; + timeDemo = false; + + memset( &usercmd, 0, sizeof( usercmd ) ); + memset( &oldUsercmd, 0, sizeof( oldUsercmd ) ); + followPlayer = -1; // start free flying + demo_hud = NULL; + demo_mphud = NULL; + demo_cursor = NULL; + + demo_protocol = 0; + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + minSpawnIndex = MAX_CLIENTS; + + modList.Clear(); + filterMod = -1; + if ( networkSystem ) { + networkSystem->UseSortFunction( filterByMod, false ); + } +} + +/* +=========== +idGameLocal::Init + + initialize the game object, only happens once at startup, not each level load +============ +*/ +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +extern idHashTable *visemeTable100; +extern idHashTable *visemeTable66; +extern idHashTable *visemeTable33; +#ifdef RV_UNIFIED_ALLOCATOR +void idGameLocal::Init( void *(*allocator)(size_t size), void (*deallocator)( void *ptr ), size_t (*msize)(void *ptr) ) { +#else +void idGameLocal::Init( void ) { +#endif +// RAVEN END + const idDict *dict; + idAAS *aas; + +#ifndef GAME_DLL + + TestGameAPI(); + +#else + + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + Memory::InitAllocator(allocator, deallocator, msize); +#endif +// RAVEN END + // initialize idLib + idLib::Init(); + + // register static cvars declared in the game + idCVar::RegisterStaticVars(); + + // initialize processor specific SIMD + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + +#endif +// RAVEN BEGIN +// jsinger: these need to be initialized after the InitAllocator call above in order +// to avoid crashes when the allocator is used. + animationLib = new idAnimManager(); + visemeTable100 = new idHashTable; + visemeTable66 = new idHashTable; + visemeTable33 = new idHashTable; +// RAVEN END + + Printf( "------------- Initializing Game -------------\n" ); + Printf( "gamename: %s\n", GAME_VERSION ); + Printf( "gamedate: %s\n", __DATE__ ); + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + idCVarHelp::RegisterStatics(); + +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + idStr prefix=""; + if(cvarSystem->GetCVarBool("com_binaryDeclRead")) + { + prefix = "binary/"; + } + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator, idDeclStreamAllocator ); + //declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclFolderWrapper( prefix + "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( prefix + "af", ".af", DECL_AF ); +#else +// RAVEN END + // register game specific decl types + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator ); + declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator ); + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator ); +// RAVEN END + // register game specific decl folders +// RAVEN BEGIN +#ifndef RV_SINGLE_DECL_FILE + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); +// bdube: not used in quake 4 +// declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); +// declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); +// declManager->RegisterDeclFolderWrapper( "newpdas", ".pda", DECL_PDA ); +#else + if(!cvarSystem->GetCVarBool("com_SingleDeclFile")) + { + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); + } + else + { + // loads the second set of decls from the file which will contain + // modles, cameras + declManager->LoadDeclsFromFile(); + declManager->FinishLoadingDecls(); + } +#endif +#endif // RV_BINARYDECLS +// RAVEN END + + cmdSystem->AddCommand( "listModelDefs", idListDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" ); + cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); + + Clear(); + + idEvent::Init(); +// RAVEN BEGIN +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + idClass::RegisterClasses(); +// RAVEN END + idClass::Init(); + + InitConsoleCommands(); + // load default scripts + program.Startup( SCRIPT_DEFAULT ); + + // set up the aas +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + dict = FindEntityDefDict( "aas_types", false ); +// RAVEN END + if ( !dict ) { + Error( "Unable to find entityDef for 'aas_types'" ); + } + + // allocate space for the aas + const idKeyValue *kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + aas = idAAS::Alloc(); + aasList.Append( aas ); + aasNames.Append( kv->GetValue() ); + kv = dict->MatchPrefix( "type", kv ); + } + + gamestate = GAMESTATE_NOMAP; + + Printf( "...%d aas types\n", aasList.Num() ); + Printf( "game initialized.\n" ); + Printf( "---------------------------------------------\n" ); + +// RAVEN BEGIN +// bdube: debug stuff + gameDebug.Init(); + gameLogLocal.Init(); + +// jscott: facial animation init + if( !FAS_Init( "annosoft" ) ) { + common->Warning( "Failed to load viseme file" ); + } + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_RENDER); +// shouchard: make sure ban list starts out in a known state + banListLoaded = false; + banListChanged = false; + memset( clientGuids, 0, sizeof( clientGuids ) ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + for(int i=0;iAddSortFunction( filterByMod ); +} + +/* +=========== +idGameLocal::Shutdown + + shut down the entire game +============ +*/ +void idGameLocal::Shutdown( void ) { + + int i; + + if ( !common ) { + return; + } + +// RAVEN BEGIN +// jscott: FAS + FAS_Shutdown(); +// shouchard: clean up ban list stuff + SaveBanList(); + FlushBanList(); +// RAVEN END + + Printf( "--------------- Game Shutdown ---------------\n" ); + + networkSystem->RemoveSortFunction( filterByMod ); + + mpGame.Shutdown(); + + MapShutdown(); + + aasList.DeleteContents( true ); + aasNames.Clear(); + + idAI::FreeObstacleAvoidanceNodes(); + + // shutdown the model exporter + idModelExport::Shutdown(); + + idEvent::Shutdown(); + + program.Shutdown(); + + idClass::Shutdown(); + + // clear list with forces + idForce::ClearForceList(); + + // free the program data + program.FreeData(); + + // delete the .map file + delete mapFile; + mapFile = NULL; + + // free the collision map + collisionModelManager->FreeMap( GetMapName() ); + +// RAVEN BEGIN +// jscott: free up static objects + for( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[i].Clear(); + } + + serverInfo.Clear(); + persistentLevelInfo.Clear(); + sessionCommand.Clear(); + mapFileName.Clear(); + mapFileNameStripped.Clear(); + entityUsageList.Clear(); + + newInfo.Clear(); + spawnArgs.Clear(); + shakeSounds.Clear(); + aiManager.Clear(); +// RAVEN END + + ShutdownConsoleCommands(); + + // free memory allocated by class objects + Clear(); + + // shut down the animation manager +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->Shutdown(); +// RAVEN END + +// RAVEN BEGIN +// rjohnson: entity usage stats + entityUsageList.Clear(); +// RAVEN END + + freeView.Shutdown(); + + Printf( "---------------------------------------------\n" ); + + instances.DeleteContents( true ); + +#ifdef GAME_DLL + + // remove auto-completion function pointers pointing into this DLL + cvarSystem->RemoveFlaggedAutoCompletion( CVAR_GAME ); + + // enable leak test + Mem_EnableLeakTest( "game" ); + + // shutdown idLib + idLib::ShutDown(); + +#endif +} + +/* +=========== +idGameLocal::SaveGame + +save the current player state, level name, and level state +the session may have written some data to the file already +============ +*/ +// RAVEN BEGIN +// mekberg: added saveTypes +void idGameLocal::SaveGame( idFile *f, saveType_t saveType ) { +// RAVEN END + int i; + idEntity *ent; + idEntity *link; + + //remove weapon effect entites from the world + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + + GetLocalPlayer()->weapon->PreSave(); + } + + idSaveGame savegame( f ); + + if (g_flushSave.GetBool( ) == true ) { + // force flushing with each write... for tracking down + // save game bugs. + f->ForceFlush(); + } + + savegame.WriteBuildNumber( BUILD_NUMBER ); + + // go through all entities and threads and add them to the object list + for( i = 0; i < MAX_GENTITIES; i++ ) { + ent = entities[i]; + + if ( ent ) { + if ( ent->GetTeamMaster() && ent->GetTeamMaster() != ent ) { + continue; + } + for ( link = ent; link != NULL; link = link->GetNextTeamEntity() ) { + savegame.AddObject( link ); + } + } + } + + idList threads; + threads = idThread::GetThreads(); + + for( i = 0; i < threads.Num(); i++ ) { + savegame.AddObject( threads[i] ); + } + +// RAVEN BEGIN +// abahr: saving clientEntities + rvClientEntity* clientEnt = NULL; + for( i = 0; i < MAX_CENTITIES; ++i ) { + clientEnt = clientEntities[ i ]; + if( !clientEnt ) { + continue; + } +// if( clientEnt->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.AddObject( clientEnt ); + } +// RAVEN END + + // write out complete object list + savegame.WriteObjectList(); + + program.Save( &savegame ); + + savegame.WriteInt( g_skill.GetInteger() ); + + savegame.WriteDict( &serverInfo ); + + savegame.WriteInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't write out userinfo. Grab from cvars +// savegame.WriteDict( &userInfo[ i ] ); +// RAVEN END +// savegame.WriteUsercmd( usercmds[ i ] ); + savegame.WriteDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.WriteObject( entities[ i ] ); + savegame.WriteInt( spawnIds[ i ] ); + } + +// RAVEN BEGIN +// abahr: more clientEntities saving + for( i = 0; i < MAX_CENTITIES; i++ ) { +// if( clientEntities[ i ] && clientEntities[ i ]->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.WriteObject( clientEntities[ i ] ); + savegame.WriteInt( clientSpawnIds[ i ] ); + } +// RAVEN END + + savegame.WriteInt( firstFreeIndex ); + savegame.WriteInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.WriteObject( world ); + + savegame.WriteInt( spawnedEntities.Num() ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( scriptObjectProxies.Num() ); + for( i = 0; i < scriptObjectProxies.Num(); ++i ) { + scriptObjectProxies[i].Save( &savegame ); + } + // used to write clientSpawnedEntities.Num() then iterate through the spawn nodes + // this is fragile however, as some elsewhere get those out of sync (mantis #70) + int countClientSpawnedEntities = 0; + for ( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { + countClientSpawnedEntities++; + } + if ( countClientSpawnedEntities != clientSpawnedEntities.Num() ) { + common->Warning( "countClientSpawnedEntities %d != clientSpawnedEntities.Num() %d\n", countClientSpawnedEntities, clientSpawnedEntities.Num() ); + } + savegame.WriteInt( countClientSpawnedEntities ); + for ( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { + savegame.WriteObject( clientEnt ); + } + // same as above. haven't seen a problem with that one but I can't trust it either + int countActiveEntities = 0; + for ( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + countActiveEntities++; + } + if ( countActiveEntities != activeEntities.Num() ) { + common->Warning( "countActiveEntities %d != activeEntities.Num() %d\n", countActiveEntities, activeEntities.Num() ); + } + savegame.WriteInt( countActiveEntities ); + for ( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( numEntitiesToDeactivate ); + savegame.WriteBool( sortPushers ); + savegame.WriteBool( sortTeamMasters ); + savegame.WriteDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.WriteFloat( globalShaderParms[ i ] ); + } + + savegame.WriteInt( random.GetSeed() ); + savegame.WriteObject( frameCommandThread ); + + // clip + // push + // pvs + + testmodel = NULL; +// RAVEN BEGIN +// bdube: no test fx +// testFx = NULL; +// RAVEN END + + savegame.WriteString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system save itself too. + aiManager.Save( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.WriteInt( cinematicSkipTime ); + savegame.WriteInt( cinematicStopTime ); + savegame.WriteInt( cinematicMaxSkipTime ); + savegame.WriteBool( inCinematic ); + savegame.WriteBool( skipCinematic ); + + savegame.WriteBool( isMultiplayer ); + savegame.WriteInt( gameType ); + + savegame.WriteInt( framenum ); + savegame.WriteInt( previousTime ); + savegame.WriteInt( time ); + + savegame.WriteInt( vacuumAreaNum ); + + savegame.WriteInt( entityDefBits ); + savegame.WriteBool( isServer ); + savegame.WriteBool( isClient ); +// RAVEN BEGIN + savegame.WriteBool( isListenServer ); +// RAVEN END + + savegame.WriteInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.WriteInt( realClientTime ); + savegame.WriteBool( isNewFrame ); + + savegame.WriteBool( mapCycleLoaded ); + savegame.WriteInt( spawnCount ); + + if ( !locationEntities ) { + savegame.WriteInt( 0 ); + } else { + savegame.WriteInt( gameRenderWorld->NumAreas() ); + for( i = 0; i < gameRenderWorld->NumAreas(); i++ ) { + savegame.WriteObject( locationEntities[ i ] ); + } + } + + savegame.WriteObject( camera ); + + savegame.WriteMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertActor.Save( &savegame ); + lastAIAlertEntity.Save( &savegame ); + savegame.WriteInt( lastAIAlertEntityTime ); + savegame.WriteInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.WriteDict( &spawnArgs ); + + savegame.WriteInt( playerPVS.i ); + savegame.WriteInt( playerPVS.h ); + savegame.WriteInt( playerConnectedAreas.i ); + savegame.WriteInt( playerConnectedAreas.h ); + + savegame.WriteVec3( gravity ); + + // gamestate + + savegame.WriteBool( influenceActive ); + savegame.WriteInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // write out pending events + idEvent::Save( &savegame ); + + savegame.Close(); + +// RAVEN BEGIN +// mekberg: added saveTypes and wrapped saveMessage + if ( saveType != ST_AUTO && GetLocalPlayer() && GetLocalPlayer()->GetHud() ) { + GetLocalPlayer()->SaveMessage(); + } + +// jshepard: resume weapon operation + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + GetLocalPlayer()->weapon->PostSave(); + } +// RAVEN END +} + +/* +=========== +idGameLocal::GetPersistentPlayerInfo +============ +*/ +const idDict &idGameLocal::GetPersistentPlayerInfo( int clientNum ) { + idEntity *ent; + + persistentPlayerInfo[ clientNum ].Clear(); + ent = entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SavePersistantInfo(); + } + + return persistentPlayerInfo[ clientNum ]; +} + +/* +=========== +idGameLocal::SetPersistentPlayerInfo +============ +*/ +void idGameLocal::SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + persistentPlayerInfo[ clientNum ] = playerInfo; +} + +/* +============ +idGameLocal::Printf +============ +*/ +void idGameLocal::Printf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::DPrintf +============ +*/ +void idGameLocal::DPrintf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::Warning +============ +*/ +void idGameLocal::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); +// MERGE:FIXME - the following now gets into a recursive stack overflow when enemies are killed. Not sure why. +// nmckenzie: +/* if ( thread ) { + thread->Warning( "%s", text ); + } else {*/ + common->Warning( "%s", text ); +// } +} + +/* +============ +idGameLocal::DWarning +============ +*/ +void idGameLocal::DWarning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Warning( "%s", text ); + } else { + common->DWarning( "%s", text ); + } +} + +/* +============ +idGameLocal::Error +============ +*/ +void idGameLocal::Error( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN +// scork: some model errors arrive here during validation which kills the whole process, so let's just warn about them instead... + if ( common->DoingDeclValidation() ) { + this->Warning( "%s", text ); + return; + } +// RAVEN END + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Error( "%s", text ); + } else { + common->Error( "%s", text ); + } +} + +/* +=============== +gameError +=============== +*/ +void gameError( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + gameLocal.Error( "%s", text ); +} + +/* +=========== +idGameLocal::SetLocalClient +============ +*/ +void idGameLocal::SetLocalClient( int clientNum ) { + localClientNum = clientNum; +} + +/* +=========== +idGameLocal::SetUserInfo +============ +*/ +const idDict* idGameLocal::SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + bool modifiedInfo = false; + + this->isClient = isClient; + + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + idGameLocal::userInfo[ clientNum ] = userInfo; + + // server sanity + if ( !isClient ) { + + // don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server + if ( idStr::IsNumeric( this->userInfo[ clientNum ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + } + + // don't allow dupe nicknames + for ( i = 0; i < numClients; i++ ) { + if ( i == clientNum ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp( idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ i ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_ because of client %d\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), i ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + i = -1; // rescan + continue; + } + } + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + modifiedInfo |= static_cast( entities[ clientNum ] )->UserInfoChanged(); + } + + if ( !isClient ) { + // now mark this client in game + mpGame.EnterGame( clientNum ); + } + } + + if ( modifiedInfo ) { + newInfo = idGameLocal::userInfo[ clientNum ]; + return &newInfo; + } + return NULL; +} + +/* +=========== +idGameLocal::GetUserInfo +============ +*/ +const idDict* idGameLocal::GetUserInfo( int clientNum ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return &userInfo[ clientNum ]; + } + return NULL; +} + +/* +=========== +idGameLocal::IsClientActive +============ +*/ +bool idGameLocal::IsClientActive( int clientNum ) { + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + return true; + } + + return false; +} + +/* +=========== +idGameLocal::SetServerInfo +============ +*/ +void idGameLocal::SetServerInfo( const idDict &_serverInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + bool timeLimitChanged = false; + +// RAVEN BEGIN +// mekberg: clear announcer and reschedule time announcements + if ( ( isClient || isListenServer ) && mpGame.GetGameState( ) && mpGame.GetGameState( )->GetMPGameState( ) == GAMEON && + serverInfo.GetInt( "si_timelimit" ) != _serverInfo.GetInt( "si_timelimit" ) ) { + timeLimitChanged = true; + } + + serverInfo = _serverInfo; + + if ( timeLimitChanged ) { + mpGame.ClearAnnouncerSounds( ); + mpGame.ScheduleTimeAnnouncements( ); + } +// RAVEN END + + if ( isServer ) { + + // Let our clients know the server info changed + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } +} + +/* +=================== +idGameLocal::LoadMap + +Initializes all map variables common to both save games and spawned games. +=================== +*/ +void idGameLocal::LoadMap( const char *mapName, int randseed ) { + int i; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PARSER); +// RAVEN END + bool sameMap = (mapFile && idStr::Icmp(mapFile->GetName(), mapName) == 0); + + networkSystem->SetLoadingText( mapName ); + + InitAsyncNetwork(); + + // these can changed based upon sp / mp + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + + if ( !sameMap || ( mapFile && mapFile->NeedsReload() ) ) { + // load the .map file + if ( mapFile ) { + delete mapFile; + } + mapFile = new idMapFile; + if ( !mapFile->Parse( idStr( mapName ) + ".map" ) ) { + delete mapFile; + mapFile = NULL; + Error( "Couldn't load %s", mapName ); + } +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + mapFile->Resolve(); +// RAVEN END + } + mapFileName = mapFile->GetName(); + + assert(!idStr::Cmp(mapFileName, mapFile->GetName())); + +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped = mapFileName; + mapFileNameStripped.StripFileExtension(); + mapFileNameStripped.StripPath(); + + const char* entityFilter; + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + mapFileNameStripped += " "; + mapFileNameStripped += entityFilter; + } +// RAVEN END + + // load the collision map + networkSystem->SetLoadingText( common->GetLocalizedString( "#str_107668" ) ); + collisionModelManager->LoadMap( mapFile, false ); + + numClients = 0; + + // initialize all entities for this game + memset( entities, 0, sizeof( entities ) ); + usercmds = NULL; + memset( spawnIds, -1, sizeof( spawnIds ) ); + spawnCount = INITIAL_SPAWN_COUNT; + + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortTeamMasters = false; + sortPushers = false; + lastGUIEnt = NULL; + lastGUI = 0; + +// RAVEN BEGIN +// bdube: client entities + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear(); + memset ( clientSpawnIds, -1, sizeof(clientSpawnIds ) ); + memset ( clientEntities, 0, sizeof(clientEntities) ); + firstFreeClientIndex = 0; +// RAVEN END + + globalMaterial = NULL; + + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + num_entities = MAX_CLIENTS; + firstFreeIndex = MAX_CLIENTS; + + // reset the random number generator. + random.SetSeed( isMultiplayer ? randseed : 0 ); + + camera = NULL; + world = NULL; + testmodel = NULL; +// RAVEN BEGIN +// bdube: not using id effects +// testFx = NULL; +// RAVEN END + +// RAVEN BEGIN +// bdube: merged + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + + previousTime = 0; + time = 0; + framenum = 0; + sessionCommand = ""; + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = 0; +// RITUAL END + + vacuumAreaNum = -1; // if an info_vacuum is spawned, it will set this + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + if ( !editEntities ) { + editEntities = new idEditEntities; + } + + if ( gameLocal.isMultiplayer ) { + gravity.Set( 0, 0, -g_mp_gravity.GetFloat() ); + } else { + gravity.Set( 0, 0, -g_gravity.GetFloat() ); + } + + spawnArgs.Clear(); + +// RAVEN BEGIN +// nmckenzie: + //make sure we clear all reachabilities we marked as blocked! + aiManager.UnMarkAllReachBlocked(); + aiManager.Clear(); +// RAVEN END + + skipCinematic = false; + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + +// RAVEN BEGIN +// ddynerman: main world instance + PACIFIER_UPDATE; + AddInstance( 0, true ); + assert( instances.Num() == 1 && instances[ 0 ]->GetInstanceID() == 0 ); +// RAVEN END + pvs.Init(); +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + // Experimental use of game's PVS for reducing amount of stuff streamed. Will + // do this a bit more cleanly if I decide to keep this. + extern idPVS* pvsForStreaming; + pvsForStreaming=&pvs; +#endif +// RAVEN END + + playerPVS.i = -1; + playerConnectedAreas.i = -1; + + // load navigation system for all the different monster sizes + for( i = 0; i < aasNames.Num(); i++ ) { + aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() ); + } + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + AI_MoveInitialize(); +// RAVEN END + + FindEntityDef( "preCacheExtras", false ); + + if ( !sameMap ) { + mapFile->RemovePrimitiveData(); + } + +// RAVEN BEGIN +// ddynerman: ambient light list + ambientLights.Clear(); +// RAVEN END +} + +/* +=================== +idGameLocal::LocalMapRestart +=================== +*/ +void idGameLocal::LocalMapRestart( int instance ) { + int i, latchSpawnCount; + + Printf( "----------- Game Map Restart (%s) ------------\n", instance == -1 ? "all instances" : va( "instance %d", instance ) ); + + // client always respawns everything, so make sure it picks up the right map entities + if( instance == -1 || isClient ) { + memset( isMapEntity, 0, sizeof(bool) * MAX_GENTITIES ); + } else { + assert( instance >= 0 && instance < instances.Num() ); + + for( int i = 0; i < instances[ instance ]->GetNumMapEntities(); i++ ) { + if ( instances[ instance ]->GetMapEntityNumber( i ) >= 0 && instances[ instance ]->GetMapEntityNumber( i ) < MAX_GENTITIES ) { + isMapEntity[ instances[ instance ]->GetMapEntityNumber( i ) ] = false; + } + } + } + + gamestate = GAMESTATE_SHUTDOWN; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->PrepareForRestart(); + } + } + + eventQueue.Shutdown(); + + MapClear( false, instance ); + + // clear the sound system + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + + // clear icons + iconManager->Shutdown(); + + // the spawnCount is reset to zero temporarily to spawn the map entities with the same spawnId + // if we don't do that, network clients are confused and don't show any map entities + latchSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + + gamestate = GAMESTATE_STARTUP; + + program.Restart(); + + InitScriptForMap(); + + MapPopulate( instance ); + + // once the map is populated, set the spawnCount back to where it was so we don't risk any collision + // (note that if there are no players in the game, we could just leave it at it's current value) + spawnCount = latchSpawnCount; + + // setup the client entities again + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->Restart(); + } + } + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); +} + +/* +=================== +idGameLocal::MapRestart +=================== +*/ +void idGameLocal::MapRestart( int instance ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idDict newInfo; + int i; + const idKeyValue *keyval, *keyval2; + + if ( isClient ) { +// RAVEN BEGIN +// ddynerman: check if gametype changed + SetGameType(); +// RAVEN END + LocalMapRestart( instance ); + } else { + + if ( mpGame.PickMap( "", true ) ) { + common->Warning( "map %s and gametype %s are incompatible, aborting map change", si_map.GetString(), si_gameType.GetString() ); + return; + } + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + // this has to be after the cvars are moved to the dict + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + break; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && + ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { + break; + } +//RAVEN BEGIN + //asalmon: need to restart if the game type has changed but the map has not cause someone could be connecting + if( keyval->GetValue().Icmp( keyval2->GetValue() ) && (!keyval->GetKey().Icmp("si_gametype"))) + { + break; + } +//RAVEN END + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + + SetGameType(); + + mpGame.isBuyingAllowedRightNow = false; + + if ( i != newInfo.GetNumKeyVals() ) { + gameLocal.sessionCommand = "nextMap"; + } else { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 1, 1 ); + outMsg.WriteDeltaDict( serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + LocalMapRestart( instance ); + mpGame.MapRestart(); + } + } +} + +/* +=================== +idGameLocal::VerifyServerSettings_f +=================== +*/ +void idGameLocal::VerifyServerSettings_f( const idCmdArgs &args ) { + gameLocal.mpGame.PickMap( si_gameType.GetString() ); +} + +/* +=================== +idGameLocal::MapRestart_f +=================== +*/ +void idGameLocal::MapRestart_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running - use spawnServer\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + return; + } + + int instance = -1; + if( args.Argc() > 1 ) { + instance = atoi( args.Args( 1 ) ); + if( instance < 0 || instance >= gameLocal.GetNumInstances() ) { + common->Warning( "idGameLocal::MapRestart_f() - Invalid instance '%d' specified\n", instance ); + return; + } + gameLocal.LocalMapRestart( instance ); + return; + } + + gameLocal.MapRestart( instance ); +} + +/* +=================== +idGameLocal::NextMap +=================== +*/ +bool idGameLocal::NextMap( void ) { + const function_t *func; + idThread *thread; + idDict newInfo; + const idKeyValue *keyval, *keyval2; + int i; + const char *mapCycleList, *currentMap; + +// RAVEN BEGIN +// rjohnson: traditional map cycle +// si_mapCycle "mp/q4dm4;mp/q4dm5;mp/q4dm6" + mapCycleList = si_mapCycle.GetString(); + if ( mapCycleList && strlen( mapCycleList ) ) { + idLexer src; + idToken token, firstFound; + int numMaps = 0; + bool foundMap = false; + + src.FreeSource(); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( mapCycleList, strlen( mapCycleList ), "idGameLocal::NextMap" ); + if ( src.IsLoaded() ) { + + currentMap = si_map.GetString(); + while( src.ReadToken( &token ) ) { + if ( token == ";" ) { + continue; + } + numMaps++; + + if ( numMaps == 1 ) { + // guarantee that we use a map no matter what ( or when we wrap ) + firstFound = token; + } + if ( foundMap ) { + firstFound = token; + break; + } + + if ( idStr::Icmp( token, currentMap ) == 0 ) { + foundMap = true; + } + } + + if ( firstFound != "" ) { + si_map.SetString( firstFound ); + return true; + } + } + + return false; + } +// RAVEN END + + if ( !g_mapCycle.GetString()[0] ) { + Printf( common->GetLocalizedString( "#str_104294" ) ); + return false; + } + if ( fileSystem->ReadFile( g_mapCycle.GetString(), NULL, NULL ) < 0 ) { + if ( fileSystem->ReadFile( va( "%s.scriptcfg", g_mapCycle.GetString() ), NULL, NULL ) < 0 ) { + Printf( "map cycle script '%s': not found\n", g_mapCycle.GetString() ); + return false; + } else { + g_mapCycle.SetString( va( "%s.scriptcfg", g_mapCycle.GetString() ) ); + } + } + + Printf( "map cycle script: '%s'\n", g_mapCycle.GetString() ); + func = program.FindFunction( "mapcycle::cycle" ); + if ( !func ) { + program.CompileFile( g_mapCycle.GetString() ); + func = program.FindFunction( "mapcycle::cycle" ); + } + if ( !func ) { + Printf( "Couldn't find mapcycle::cycle\n" ); + return false; + } + thread = new idThread( func ); + thread->Start(); + delete thread; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 || keyval->GetValue().Icmp( keyval2->GetValue() ) ) { + break; + } + } + return ( i != newInfo.GetNumKeyVals() ); +} + +/* +=================== +idGameLocal::NextMap_f +=================== +*/ +void idGameLocal::NextMap_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running\n" ); + return; + } + + gameLocal.NextMap( ); + // next map was either voted for or triggered by a server command - always restart + gameLocal.MapRestart( ); +} + +/* +=============== +idGameLocal::GetStartingIndexForInstance +=============== +*/ +int idGameLocal::GetStartingIndexForInstance( int instanceID ) { + if ( isServer ) { + assert( instancesEntityIndexWatermarks.Num() >= instanceID ); + if ( instanceID == 0 ) { + return MAX_CLIENTS; + } else { + // the high watermark of the previous instance is the starting index of the next one + return instancesEntityIndexWatermarks[ instanceID - 1 ]; + } + } else { + assert( instanceID == 0 ); + return clientInstanceFirstFreeIndex; + } +} + +/* +=============== +idGameLocal::ServerSetEntityIndexWatermark +keep track of the entity layout at the server - specially when there are multiple instances ( tourney ) +=============== +*/ +void idGameLocal::ServerSetEntityIndexWatermark( int instanceID ) { + if ( isClient ) { + return; + } + instancesEntityIndexWatermarks.AssureSize( instanceID + 1, MAX_CLIENTS ); + // make sure there is no drift. if a value was already set it has to match + // otherwise that means the server is repopulating with different indexes, and that would likely lead to net corruption + assert( instancesEntityIndexWatermarks[ instanceID ] == MAX_CLIENTS || instancesEntityIndexWatermarks[ instanceID ] == firstFreeIndex ); + instancesEntityIndexWatermarks[ instanceID ] = firstFreeIndex; +} + +/* +=============== +idGameLocal::ServerSetMinSpawnIndex +=============== +*/ +void idGameLocal::ServerSetMinSpawnIndex( void ) { + if ( isClient ) { + return; + } + // setup minSpawnIndex with enough headroom so gameplay entities don't cause bad offsets + // only needed on server, clients are completely slaved up to server entity layout + if ( !idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Tourney" ) ) { + minSpawnIndex = MAX_CLIENTS + GetNumMapEntities() * MAX_INSTANCES; + } +} + +/* +=================== +idGameLocal::MapPopulate +=================== +*/ +void idGameLocal::MapPopulate( int instance ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + if ( isMultiplayer ) { + cvarSystem->SetCVarBool( "r_skipSpecular", false ); + } + + minSpawnIndex = MAX_CLIENTS; + + // parse the key/value pairs and spawn entities +// RAVEN BEGIN +// ddynerman: instance code + // reload the instances + if ( instance == -1 ) { + int i; + firstFreeIndex = MAX_CLIENTS; + for ( i = 0; i < instances.Num(); i++ ) { + if ( instances[ i ] ) { + instances[ i ]->Restart(); + + ServerSetEntityIndexWatermark( i ); + } + } + } else { + assert( instance >= 0 && instance < instances.Num() ); + instances[ instance ]->Restart(); + } +// RAVEN END + + ServerSetMinSpawnIndex(); + + // mark location entities in all connected areas + SpreadLocations(); + + // RAVEN BEGIN + // ddynerman: prepare the list of spawn spots + InitializeSpawns(); + // RAVEN END + + // execute pending events before the very first game frame + // this makes sure the map script main() function is called + // before the physics are run so entities can bind correctly + Printf( "------------ Processing events --------------\n" ); + idEvent::ServiceEvents(); +} + +/* +=================== +idGameLocal::InitFromNewMap +=================== +*/ +void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randseed ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + this->isServer = isServer; + this->isClient = isClient; +// RAVEN BEGIN +// ddynerman: listen server + this->isListenServer = isServer && !cvarSystem->GetCVarBool( "net_serverDedicated" ); +// RAVEN END + this->isMultiplayer = isServer || isClient; + + if ( this->isMultiplayer ) + gameLocal.Error( "This mod is for singleplayer only" ); + + PACIFIER_UPDATE; + +//RAVEN BEGIN +//asalmon: stats for single player + if (!this->isMultiplayer) { +#ifdef _MPBETA + return; +#else + statManager->EndGame(); +#ifdef _XENON + Live()->DeleteSPSession(true); +#endif + statManager->Shutdown(); + statManager->Init(); + statManager->BeginGame(); + statManager->ClientConnect(0); +#ifdef _XENON + Live()->CreateSPSession(); +#endif +#endif // _MPBETA + } +//RAVEN END + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "-------------- Game Map Init ----------------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->BeginLevelLoad(); +#endif + +// ddynerman: set gametype + SetGameType(); +// RAVEN END + + LoadMap( mapName, randseed ); + + InitScriptForMap(); + + MapPopulate(); + + mpGame.Reset(); + + mpGame.Precache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->EndLevelLoad(); +#endif +// RAVEN END + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "---------------------------------------------\n" ); +} + +/* +================= +idGameLocal::InitFromSaveGame +================= +*/ +bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + int num; + idEntity *ent; + idDict si; + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "---------- Game Map Init SaveGame -----------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + + idRestoreGame savegame( saveGameFile ); + + savegame.ReadBuildNumber(); + + // Create the list of all objects in the game + savegame.CreateObjects(); + + // Load the idProgram, also checking to make sure scripting hasn't changed since the savegame + if ( program.Restore( &savegame ) == false ) { + + // Abort the load process, and let the session know so that it can restart the level + // with the player persistent data. + savegame.DeleteObjects(); + program.Restart(); + + return false; + } + + // load the map needed for this savegame + LoadMap( mapName, 0 ); + + savegame.ReadInt( i ); + g_skill.SetInteger( i ); + + // precache any media specified in the map + for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *mapEnt = mapFile->GetEntity( i ); + + if ( !InhibitEntitySpawn( mapEnt->epairs ) ) { + CacheDictionaryMedia( &mapEnt->epairs ); + const char *classname = mapEnt->epairs.GetString( "classname" ); + if ( classname != '\0' ) { + FindEntityDef( classname, false ); + } + } + } + + savegame.ReadDict( &si ); + SetServerInfo( si ); + + savegame.ReadInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't read in userinfo. Grab from cvars +// savegame.ReadDict( &userInfo[ i ] ); +// RAVEN END +// savegame.ReadUsercmd( usercmds[ i ] ); + savegame.ReadDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( entities[ i ] ) ); + savegame.ReadInt( spawnIds[ i ] ); + + // restore the entityNumber + if ( entities[ i ] != NULL ) { + entities[ i ]->entityNumber = i; + } + } + + // Precache the player +// RAVEN BEGIN +// bdube: changed so we actually cache stuff + FindEntityDef( idPlayer::GetSpawnClassname() ); + +// abahr: saving clientEntities + for( i = 0; i < MAX_CENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( clientEntities[ i ] ) ); + savegame.ReadInt( clientSpawnIds[ i ] ); + + // restore the entityNumber + if ( clientEntities[ i ] != NULL ) { + clientEntities[ i ]->entityNumber = i; + } + } +// RAVEN END + + savegame.ReadInt( firstFreeIndex ); + savegame.ReadInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.ReadObject( reinterpret_cast( world ) ); + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->spawnNode.AddToEnd( spawnedEntities ); + } + } + +// RAVEN BEGIN +// abahr: save scriptObject proxies + savegame.ReadInt( num ); + scriptObjectProxies.SetNum( num ); + for( i = 0; i < num; ++i ) { + scriptObjectProxies[i].Restore( &savegame ); + } +// abahr: save client entity stuff + rvClientEntity* clientEnt = NULL; + savegame.ReadInt( num ); + for( i = 0; i < num; ++i ) { + savegame.ReadObject( reinterpret_cast( clientEnt ) ); + assert( clientEnt ); + if ( clientEnt ) { + clientEnt->spawnNode.AddToEnd( clientSpawnedEntities ); + } + } +// RAVEN END + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->activeNode.AddToEnd( activeEntities ); + } + } + + savegame.ReadInt( numEntitiesToDeactivate ); + savegame.ReadBool( sortPushers ); + savegame.ReadBool( sortTeamMasters ); + savegame.ReadDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.ReadFloat( globalShaderParms[ i ] ); + } + + savegame.ReadInt( i ); + random.SetSeed( i ); + + savegame.ReadObject( reinterpret_cast( frameCommandThread ) ); + + // clip + // push + // pvs + + // testmodel = "" + // testFx = "" + + savegame.ReadString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system load itself too. + aiManager.Restore( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.ReadInt( cinematicSkipTime ); + savegame.ReadInt( cinematicStopTime ); + savegame.ReadInt( cinematicMaxSkipTime ); + savegame.ReadBool( inCinematic ); + savegame.ReadBool( skipCinematic ); + + savegame.ReadBool( isMultiplayer ); + savegame.ReadInt( (int &)gameType ); + + savegame.ReadInt( framenum ); + savegame.ReadInt( previousTime ); + savegame.ReadInt( time ); + + savegame.ReadInt( vacuumAreaNum ); + + savegame.ReadInt( entityDefBits ); + savegame.ReadBool( isServer ); + savegame.ReadBool( isClient ); +// RAVEN BEGIN + savegame.ReadBool( isListenServer ); +// RAVEN END + + savegame.ReadInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.ReadInt( realClientTime ); + savegame.ReadBool( isNewFrame ); + + savegame.ReadBool( mapCycleLoaded ); + savegame.ReadInt( spawnCount ); + + savegame.ReadInt( num ); + if ( num ) { + if ( num != gameRenderWorld->NumAreas() ) { + savegame.Error( "idGameLocal::InitFromSaveGame: number of areas in map differs from save game." ); + } + + locationEntities = new idLocationEntity *[ num ]; + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( locationEntities[ i ] ) ); + } + } + + savegame.ReadObject( reinterpret_cast( camera ) ); + + savegame.ReadMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity.Restore( &savegame ); + savegame.ReadInt( lastAIAlertEntityTime ); + lastAIAlertActor.Restore( &savegame ); + savegame.ReadInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.ReadDict( &spawnArgs ); + + savegame.ReadInt( playerPVS.i ); + savegame.ReadInt( (int &)playerPVS.h ); + savegame.ReadInt( playerConnectedAreas.i ); + savegame.ReadInt( (int &)playerConnectedAreas.h ); + + savegame.ReadVec3( gravity ); + + // gamestate is restored after restoring everything else + + savegame.ReadBool( influenceActive ); + savegame.ReadInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // Read out pending events + idEvent::Restore( &savegame ); + + // call the restore functions to read out all the data from the entities + savegame.RestoreObjects(); + + mpGame.Reset(); + + mpGame.Precache(); + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); + + return true; +} + +/* +=========== +idGameLocal::MapClear +=========== +*/ +// RAVEN BEGIN +// ddynerman: multiple instances +void idGameLocal::MapClear( bool clearClients, int instance ) { +// RAVEN END + int i; + +// RAVEN BEGIN +// bdube: delete client entities first since they reference real entities + for( i = 0; i < MAX_CENTITIES; i++ ) { + // on the server we need to keep around client entities bound to entities in our instance2 + if( gameLocal.isServer && gameLocal.GetLocalPlayer() && instance != -1 && + instance != gameLocal.GetLocalPlayer()->GetInstance() && + clientEntities[ i ] && clientEntities[ i ]->GetBindMaster() && + clientEntities[ i ]->GetBindMaster()->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + delete clientEntities[ i ]; + clientEntities[ i ] = NULL; + clientSpawnIds[ i ] = -1; + } +// RAVEN END + + for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) { + if( instance >= 0 && entities[ i ] && entities[ i ]->GetInstance() != instance ) { + continue; + } + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); +// RAVEN BEGIN +// see FIXME in idRestoreGame::Error + entities[ i ] = NULL; +// RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + spawnedEntities.Clear(); + } +// RAVEN END + + if ( !clearClients ) { + // add back the hashes of the clients/stuff in other instances + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + entities[ i ]->spawnNode.AddToEnd( spawnedEntities ); + } +// RAVEN END + } + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; +// abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + +// RAVEN BEGIN +// ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); +// RAVEN END + + // set the free index back at MAX_CLIENTS for registering entities again + // the deletion of map entities causes the index to go down, but it may not have been left exactly at 32 + // under such conditions, the map populate that will follow may be offset + firstFreeIndex = MAX_CLIENTS; +} + +// RAVEN BEGIN +// ddynerman: instance-specific clear +void idGameLocal::InstanceClear( void ) { + // note: clears all ents EXCEPT those in the instance + int i; + + for( i = 0; i < MAX_CENTITIES; i++ ) { + delete clientEntities[ i ]; + assert( !clientEntities[ i ] ); + clientSpawnIds[ i ] = -1; + } + + for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + if( i == ENTITYNUM_CLIENT || i == ENTITYNUM_WORLD ) { + continue; + } + + if( entities[ i ] && entities[ i ]->fl.persistAcrossInstances ) { + //common->DPrintf( "Instance %d: persistant: excluding ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + continue; + } + + //if( entities[ i ] ) { + //Printf( "Instance %d: CLEARING ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + //} + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); + // RAVEN BEGIN + // see FIXME in idRestoreGame::Error + entities[ i ] = NULL; + // RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; + // abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + + // RAVEN BEGIN + // ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); + + +} +// RAVEN END + +/* +=========== +idGameLocal::MapShutdown +============ +*/ +void idGameLocal::MapShutdown( void ) { + Printf( "------------ Game Map Shutdown --------------\n" ); + + gamestate = GAMESTATE_SHUTDOWN; + + if ( soundSystem ) { + soundSystem->ResetListener(); + } + +// RAVEN BEGIN +// rjohnson: new blur special effect + renderSystem->ShutdownSpecialEffects(); +// RAVEN END + + // clear out camera if we're in a cinematic + if ( inCinematic ) { + camera = NULL; + inCinematic = false; + } + +// RAVEN BEGIN +// jscott: cleanup playbacks + gameEdit->ShutdownPlaybacks(); +// RAVEN END + + MapClear( true ); + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + +// RAVEN BEGIN +// jscott: make sure any spurious events are killed + idEvent::ClearEventList(); + + // reset the script to the state it was before the map was started + program.Restart(); + +// bdube: game debug + gameDebug.Shutdown( ); + gameLogLocal.Shutdown( ); +// RAVEN END + + iconManager->Shutdown(); + + pvs.Shutdown(); + +// RAVEN BEGIN +// ddynerman: MP multiple instances + ShutdownInstances(); +// mwhitlock: Dynamic memory consolidation + clip.Clear(); +// RAVEN END + idClipModel::ClearTraceModelCache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + idForce::ClearForceList(); +#endif +// RAVEN END + + ShutdownAsyncNetwork(); + + mapFileName.Clear(); + + gameRenderWorld = NULL; + + gamestate = GAMESTATE_NOMAP; + + Printf( "---------------------------------------------\n" ); +} + +/* +=================== +idGameLocal::DumpOggSounds +=================== +*/ +void idGameLocal::DumpOggSounds( void ) { + int i, j, k, size, totalSize; + idFile *file; + idStrList oggSounds, weaponSounds; + const idSoundShader *soundShader; + const soundShaderParms_t *parms; + idStr soundName; + + for ( i = 0; i < declManager->GetNumDecls( DECL_SOUND ); i++ ) { + soundShader = static_cast(declManager->DeclByIndex( DECL_SOUND, i, false )); + parms = soundShader->GetParms(); + + if ( soundShader->EverReferenced() && soundShader->GetState() != DS_DEFAULTED ) { + + const_cast(soundShader)->EnsureNotPurged(); + + for ( j = 0; j < soundShader->GetNumSounds(); j++ ) { + soundName = soundShader->GetSound( j ); + soundName.BackSlashesToSlashes(); + + // don't OGG sounds that cause a shake because that would + // cause continuous seeking on the OGG file which is expensive + if ( parms->shakes != 0.0f ) { + shakeSounds.AddUnique( soundName ); + continue; + } + + // if not voice over or combat chatter + if ( soundName.Find( "/vo/", false ) == -1 && + soundName.Find( "/combat_chatter/", false ) == -1 && + soundName.Find( "/bfgcarnage/", false ) == -1 && + soundName.Find( "/enpro/", false ) == - 1 && + soundName.Find( "/soulcube/energize_01.wav", false ) == -1 ) { + // don't OGG weapon sounds + if ( soundName.Find( "weapon", false ) != -1 || + soundName.Find( "gun", false ) != -1 || + soundName.Find( "bullet", false ) != -1 || + soundName.Find( "bfg", false ) != -1 || + soundName.Find( "plasma", false ) != -1 ) { + weaponSounds.AddUnique( soundName ); + continue; + } + } + + for ( k = 0; k < shakeSounds.Num(); k++ ) { + if ( shakeSounds[k].IcmpPath( soundName ) == 0 ) { + break; + } + } + if ( k < shakeSounds.Num() ) { + continue; + } + + oggSounds.AddUnique( soundName ); + } + } + } + + file = fileSystem->OpenFileWrite( "makeogg.bat", "fs_savepath" ); + if ( file == NULL ) { + common->Warning( "Couldn't open makeogg.bat" ); + return; + } + + // list all the shake sounds + totalSize = 0; + for ( i = 0; i < shakeSounds.Num(); i++ ) { + size = fileSystem->ReadFile( shakeSounds[i], NULL, NULL ); + totalSize += size; + shakeSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", shakeSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in shake sounds\n\n\n", totalSize >> 10 ); + + // list all the weapon sounds + totalSize = 0; + for ( i = 0; i < weaponSounds.Num(); i++ ) { + size = fileSystem->ReadFile( weaponSounds[i], NULL, NULL ); + totalSize += size; + weaponSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", weaponSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in weapon sounds\n\n\n", totalSize >> 10 ); + + // list commands to convert all other sounds to ogg + totalSize = 0; + for ( i = 0; i < oggSounds.Num(); i++ ) { +// RAVEN BEGIN +// rjohnson: changed path to raven's directories +// jnewquist: Use filesystem search path to get absolute file path + idStr tempFile; + idFile *f = fileSystem->OpenFileRead( oggSounds[i] ); + if ( !f ) { + continue; + } + size = f->Length(); + totalSize += size; + tempFile = f->GetFullPath(); + fileSystem->CloseFile(f); + f = NULL; + tempFile.Replace( "/", "\\" ); + +// jnewquist: prevent alterations to files relative to cdpath + const char *cdPath = cvarSystem->GetCVarString("fs_cdpath"); + const int cdPathLen = idStr::Length(cdPath); + if ( cdPathLen > 0 && idStr::Icmpn(cdPath, tempFile, cdPathLen) == 0 ) { + file->Printf( "rem Ignored file from CD path: %s\n", tempFile.c_str() ); + continue; + } + + file->Printf( "echo %d / %d\n", i, oggSounds.Num() ); + file->Printf( "k:\\utility\\oggenc -q 0 \"%s\"\n", tempFile.c_str() ); + file->Printf( "del \"%s\"\n", tempFile.c_str() ); +// RAVEN END + } + file->Printf( "\n\necho %d kB in OGG sounds\n\n\n", totalSize >> 10 ); + + fileSystem->CloseFile( file ); + + shakeSounds.Clear(); +} + +/* +=================== +idGameLocal::GetShakeSounds +=================== +*/ +void idGameLocal::GetShakeSounds( const idDict *dict ) { + const idSoundShader *soundShader; + const char *soundShaderName; + idStr soundName; + + soundShaderName = dict->GetString( "s_shader" ); + if ( soundShaderName != '\0' && dict->GetFloat( "s_shakes" ) != 0.0f ) { + soundShader = declManager->FindSound( soundShaderName ); + + for ( int i = 0; i < soundShader->GetNumSounds(); i++ ) { + soundName = soundShader->GetSound( i ); + soundName.BackSlashesToSlashes(); + + shakeSounds.AddUnique( soundName ); + } + } +} + +/* +=================== +idGameLocal::CacheDictionaryMedia + +This is called after parsing an EntityDef and for each entity spawnArgs before +merging the entitydef. It could be done post-merge, but that would +avoid the fast pre-cache check associated with each entityDef +=================== +*/ +void idGameLocal::CacheDictionaryMedia( const idDict *dict ) { + idDict spawnerArgs; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( dict == NULL ) { +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild") ) { + DumpOggSounds(); + } +#endif + return; + } + +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild" ) ) { + GetShakeSounds( dict ); + } +#endif + + int numVals = dict->GetNumKeyVals(); + + for ( int i = 0; i < numVals; ++i ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + + #define MATCH(s) \ + (!kv->GetKey().Icmpn( s, strlen(s) )) + /**/ + + if ( !kv || !kv->GetValue().Length() ) { + continue; + } + + if ( MATCH( "model" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MODEL); + declManager->MediaPrint( "Precaching model %s\n", kv->GetValue().c_str() ); + // precache model/animations + if ( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( kv->GetValue() ); + // precache .cm files only + collisionModelManager->PreCacheModel( GetMapName(), kv->GetValue() ); + } + } else if ( MATCH( "s_shader" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "snd_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "gui_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_GUI); + if ( !idStr::Icmp( kv->GetKey(), "gui_noninteractive" ) + || !idStr::Icmpn( kv->GetKey(), "gui_parm", 8 ) + || !idStr::Icmp( kv->GetKey(), "gui_inventory" ) ) { + // unfortunate flag names, they aren't actually a gui + } else { + declManager->MediaPrint( "Precaching gui %s\n", kv->GetValue().c_str() ); + uiManager->FindGui( kv->GetValue().c_str(), true ); + } + } else if ( MATCH( "texture" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "mtr_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "screenShot" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "inv_icon" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "teleport" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + int teleportType = atoi( kv->GetValue() ); + const char *p = ( teleportType ) ? va( "effects/teleporter%i.fx", teleportType ) : "effects/teleporter.fx"; + declManager->FindType( DECL_EFFECT, p ); + } else if ( MATCH( "fx_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + declManager->MediaPrint( "Precaching fx %s\n", kv->GetValue().c_str() ); + declManager->FindEffect( kv->GetValue() ); + } else if ( MATCH( "skin" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->MediaPrint( "Precaching skin %s\n", kv->GetValue().c_str() ); + declManager->FindType( DECL_SKIN, kv->GetValue() ); + } else if ( MATCH( "def_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + FindEntityDef( kv->GetValue().c_str(), false ); + } else if ( MATCH( "playback_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching playback %s\n", kv->GetValue().c_str() ); + declManager->FindPlayback( kv->GetValue() ); + } else if ( MATCH( "lipsync_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching lipsync %s\n", kv->GetValue().c_str() ); + declManager->FindLipSync( kv->GetValue() ); + declManager->FindSound ( kv->GetValue() ); + } else if ( MATCH( "icon " ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + idLexer src ( LEXFL_ALLOWPATHNAMES ); + idToken token; + idToken token2; + src.LoadMemory( kv->GetValue(), kv->GetValue().Length(), "icon" ); + + src.ReadToken ( &token ); + if ( src.CheckTokenString ( "," ) ) { + int x, y, w, h; + src.ReadToken ( &token2 ) ; + x = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + y = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + w = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + h = token2.GetIntValue ( ); + + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token, x, y, w, h ); + } else { + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token ); + } + } else if ( MATCH( "spawn_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + spawnerArgs.Set ( kv->GetKey ( ).c_str() + 6, kv->GetValue ( ) ); + } + + #undef MATCH + } + + if ( spawnerArgs.GetNumKeyVals() ) { + CacheDictionaryMedia ( &spawnerArgs ); + } +// RAVEN END +} + +/* +=========== +idGameLocal::InitScriptForMap +============ +*/ +void idGameLocal::InitScriptForMap( void ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + // create a thread to run frame commands on + frameCommandThread = new idThread(); + frameCommandThread->ManualDelete(); + frameCommandThread->SetThreadName( "frameCommands" ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_SCRIPT); + + // run the main game script function (not the level specific main) + const function_t *func = program.FindFunction( SCRIPT_DEFAULTFUNC ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_DEFAULT); + + if ( func != NULL ) { + idThread *thread = new idThread( func ); + if ( thread->Start() ) { + // thread has finished executing, so delete it + delete thread; + } + } + +// RAVEN END + +} + +/* +=========== +idGameLocal::SpawnPlayer +============ +*/ +void idGameLocal::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + idEntity *ent; + idDict args; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + // they can connect + common->DPrintf( "SpawnPlayer: %i\n", clientNum ); + + args.SetInt( "spawn_entnum", clientNum ); + args.Set( "name", va( "player%d", clientNum + 1 ) ); +// RAVEN BEGIN +// bdube: changed marine class + args.Set( "classname", idPlayer::GetSpawnClassname() ); +// RAVEN END + + // This takes a really long time. + PACIFIER_UPDATE; + if ( !SpawnEntityDef( args, &ent ) || !entities[ clientNum ] ) { + Error( "Failed to spawn player as '%s'", args.GetString( "classname" ) ); + } + + // make sure it's a compatible class +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Error( "'%s' spawned the player as a '%s'. Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() ); + } + + if ( clientNum >= numClients ) { + numClients = clientNum + 1; + } + + PACIFIER_UPDATE; + mpGame.SpawnPlayer( clientNum ); +} + +/* +================ +idGameLocal::GetClientByNum +================ +*/ +idPlayer *idGameLocal::GetClientByNum( int current ) const { + if ( current == MAX_CLIENTS ) { + return NULL; + } + if ( current < 0 || current >= numClients ) { + // that's a bit nasty but I suppose it has it's use + current = 0; + } + if ( entities[current] ) { + return static_cast( entities[ current ] ); + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByName +================ +*/ +idPlayer *idGameLocal::GetClientByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END +// RAVEN BEGIN +// bdube: escape codes + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { +// RAVEN END + return static_cast( ent ); + } + } + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByCmdArgs +================ +*/ +idPlayer *idGameLocal::GetClientByCmdArgs( const idCmdArgs &args ) const { + idPlayer *player; + idStr client = args.Argv( 1 ); + if ( !client.Length() ) { + return NULL; + } + // we don't allow numeric ui_name so this can't go wrong + if ( client.IsNumeric() ) { + player = GetClientByNum( atoi( client.c_str() ) ); + } else { + player = GetClientByName( client.c_str() ); + } + if ( !player ) { + common->Printf( "Player '%s' not found\n", client.c_str() ); + } + return player; +} + +/* +================ +idGameLocal::GetClientNumByName +================ +*/ +int idGameLocal::GetClientNumByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { + return i; + } + } + } + return -1; +} + +/* +================ +idGameLocal::GetNextClientNum +================ +*/ +int idGameLocal::GetNextClientNum( int _current ) const { + int i, current; + + current = 0; + for ( i = 0; i < numClients; i++) { + current = ( _current + i + 1 ) % numClients; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ current ] && entities[ current ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return current; + } + } + + return current; +} + +/* +================ +idGameLocal::GetLocalPlayer + +Nothing in the game tic should EVER make a decision based on what the +local client number is, it shouldn't even be aware that there is a +draw phase even happening. + +localClientNum == MAX_CLIENTS when playing server demos +don't send back the wrong entity obviously +================ +*/ +idPlayer *idGameLocal::GetLocalPlayer() const { + if ( localClientNum < 0 || localClientNum == MAX_CLIENTS ) { + return NULL; + } + + if ( !entities[ localClientNum ] || !entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ) { + // not fully in game yet + return NULL; + } + + // through idGameLocal::MapShutdown, deleting player ents, calls idEntity::StopSound + // calls in here and triggers the assert because proper type info is already gone + //assert( gamestate == GAMESTATE_SHUTDOWN || entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ); + + return static_cast( entities[ localClientNum ] ); +} + +/* +================ +idGameLocal::SetupClientPVS +for client spectating others, get the pvs of spectated +================ +*/ +pvsHandle_t idGameLocal::GetClientPVS( idPlayer *player, pvsType_t type ) { + if ( player->GetPrivateCameraView() ) { + return pvs.SetupCurrentPVS( player->GetPrivateCameraView()->GetPVSAreas(), player->GetPrivateCameraView()->GetNumPVSAreas() ); + } else if ( camera ) { + return pvs.SetupCurrentPVS( camera->GetPVSAreas(), camera->GetNumPVSAreas() ); + } else { + if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { + player = static_cast( entities[ player->spectator ] ); + } + return pvs.SetupCurrentPVS( player->GetPVSAreas(), player->GetNumPVSAreas() ); + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +================ +idGameLocal::GetSpawnCount +================ +*/ +int idGameLocal::GetSpawnCount ( void ) const { + return spawnCount; +} + +/* +================ +idGameLocal::SetupPortalSkyPVS +================ +*/ +bool idGameLocal::SetupPortalSkyPVS( idPlayer *player ) { + + int i, count, numAreas; + const int *areaNums; + bool *visibleAreas; + + if( !portalSky ) { + + return( false ); + } + + // Allocate room for the area flags + numAreas = gameRenderWorld->NumAreas(); + visibleAreas = ( bool * )_alloca( numAreas ); + memset( visibleAreas, 0, numAreas ); + + // Grab the areas the player can see.... + count = player->GetNumPVSAreas(); + areaNums = player->GetPVSAreas(); + for( i = 0; i < count; i++ ) { + + // Work out the referenced areas + gameRenderWorld->FindVisibleAreas( player->GetPhysics()->GetOrigin(), areaNums[i], visibleAreas ); + } + + // Do any of the visible areas have a skybox? + for( i = 0; i < numAreas; i++ ) { + + if( !visibleAreas[i] ) { + + continue; + } + + if( gameRenderWorld->HasSkybox( i ) ) { + + break; + } + } + + // .. if any one has a skybox component, then merge in the portal sky + return ( i != numAreas ); +} +// RAVEN END + +/* +=============== +idGameLocal::UpdateClientsPVS +=============== +*/ +void idGameLocal::UpdateClientsPVS( void ) { + int i; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[ i ] ) { + continue; + } + assert( clientsPVS[ i ].i == -1 ); + clientsPVS[ i ] = GetClientPVS( static_cast< idPlayer * >( entities[ i ] ), PVS_NORMAL ); + } +} + +/* +================ +idGameLocal::SetupPlayerPVS +================ +*/ +void idGameLocal::SetupPlayerPVS( void ) { + int i = 0; + idPlayer * player = NULL; + pvsHandle_t otherPVS, newPVS; + + UpdateClientsPVS( ); + + playerPVS.i = -1; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[i] ) { + return; + } + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + + player = static_cast( entities[ i ] ); + + if ( playerPVS.i == -1 ) { + playerPVS = clientsPVS[ i ]; + freePlayerPVS = false; // don't try to free it as long as we stick to the client PVS + } else { + otherPVS = clientsPVS[ i ]; + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS = newPVS; + freePlayerPVS = true; // that merged one will need to be freed + } + +// RAVEN BEGIN +// jscott: for portal skies + portalSkyVisible = SetupPortalSkyPVS( player ); + if ( portalSkyVisible ) { + + otherPVS = pvs.SetupCurrentPVS( portalSky->GetPVSAreas(), portalSky->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + pvs.FreeCurrentPVS( otherPVS ); + playerPVS = newPVS; + freePlayerPVS = true; + } +// RAVEN END + + if ( playerConnectedAreas.i == -1 ) { + playerConnectedAreas = GetClientPVS( player, PVS_CONNECTED_AREAS ); + } else { + otherPVS = GetClientPVS( player, PVS_CONNECTED_AREAS ); + newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); + pvs.FreeCurrentPVS( playerConnectedAreas ); + pvs.FreeCurrentPVS( otherPVS ); + playerConnectedAreas = newPVS; + } + } +} + +/* +================ +idGameLocal::FreePlayerPVS +================ +*/ +void idGameLocal::FreePlayerPVS( void ) { + int i; + + // only clear playerPVS if it's a different handle than the one in clientsPVS + if ( freePlayerPVS && playerPVS.i != -1 ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS.i = -1; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( clientsPVS[ i ].i >= 0 ) { + pvs.FreeCurrentPVS( clientsPVS[ i ] ); + clientsPVS[i].i = -1; + } + } + if ( playerConnectedAreas.i != -1 ) { + pvs.FreeCurrentPVS( playerConnectedAreas ); + playerConnectedAreas.i = -1; + } +} + +/* +================ +idGameLocal::InPlayerPVS + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerPVS( idEntity *ent ) const { + if ( playerPVS.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerPVS, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + +/* +================ +idGameLocal::InPlayerConnectedArea + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerConnectedArea( idEntity *ent ) const { + if ( playerConnectedAreas.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerConnectedAreas, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + + +/* +================ +idGameLocal::UpdateGravity +================ +*/ +void idGameLocal::UpdateGravity( void ) { + idEntity *ent; + + idCVar* gravityCVar = NULL; + + if( gameLocal.isMultiplayer ) { + gravityCVar = &g_mp_gravity; + } else { + gravityCVar = &g_gravity; + } + + if ( gravityCVar->IsModified() ) { + if ( gravityCVar->GetFloat() == 0.0f ) { + gravityCVar->SetFloat( 1.0f ); + } + gravity.Set( 0, 0, -gravityCVar->GetFloat() ); + + // update all physics objects + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + idPhysics *phys = ent->GetPhysics(); + if ( phys ) { + phys->SetGravity( gravity ); + } +// RAVEN BEGIN +// ddynerman: jump pads + } else if ( ent->IsType( rvJumpPad::GetClassType() ) ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + } +// RAVEN END + } + gravityCVar->ClearModified(); + } +} + +/* +================ +idGameLocal::GetGravity +================ +*/ +const idVec3 &idGameLocal::GetGravity( void ) const { + return gravity; +} + +/* +================ +idGameLocal::SortActiveEntityList + + Sorts the active entity list such that pushing entities come first, + actors come next and physics team slaves appear after their master. +================ +*/ +void idGameLocal::SortActiveEntityList( void ) { + idEntity *ent, *next_ent, *master, *part; + + // if the active entity list needs to be reordered to place physics team masters at the front + if ( sortTeamMasters ) { + // Sort bind masters first + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + for ( part = ent->GetBindMaster ( ); part; part = part->GetBindMaster ( ) ) { + // Ensure we dont rerun the whole active entity list if our cached next_ent is one + // of the entities we are moving + if ( next_ent == part ) { + next_ent = next_ent->activeNode.Next(); + part = ent->GetBindMaster ( ); + continue; + } + part->activeNode.Remove(); + part->activeNode.AddToFront( activeEntities ); + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( master && master == ent ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + // if the active entity list needs to be reordered to place pushers at the front + if ( sortPushers ) { + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an actor on the team + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + break; + } + } + // if there is an actor on the team + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an entity on the team using parametric physics + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + break; + } + if ( part->GetPhysics()->IsType( rvPhysics_Spline::GetClassType() ) ) { + break; + } + } + // if there is an entity on the team using parametric physics + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + } + + sortTeamMasters = false; + sortPushers = false; +} + +/* +================ +idGameLocal::MenuFrame +Called each session frame when a map is not running (e.g. usually in the main menu) +================ +*/ +void idGameLocal::MenuFrame( void ) { } + +/* +================ +idGameLocal::RunFrame +================ +*/ +// RAVEN BEGIN + gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ) { + idEntity * ent; + int num; + float ms; + idTimer timer_think, timer_events, timer_singlethink; + idTimer timer_misc, timer_misc2; + gameReturn_t ret; + idPlayer *player; + const renderView_t *view; + + editors = activeEditors; + isLastPredictFrame = lastCatchupFrame; + + assert( !isClient ); + + player = GetLocalPlayer(); + + if ( !isMultiplayer && g_stopTime.GetBool() ) { + + // set the user commands for this frame + usercmds = clientCmds; + + if ( player ) { + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = player; + player->Think(); + currentThinkingEntity = NULL; + } + } else do { + // update the game time + framenum++; + previousTime = time; + // bdube: use GetMSec access rather than USERCMD_TIME + time += GetMSec(); + + realClientTime = time; + { +TIME_THIS_SCOPE("idGameLocal::RunFrame - gameDebug.BeginFrame()"); + // bdube: added advanced debug support + gameDebug.BeginFrame( ); + gameLogLocal.BeginFrame( time ); + } + +#ifdef GAME_DLL + // allow changing SIMD usage on the fly + if ( com_forceGenericSIMD.IsModified() ) { + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + } +#endif + + // make sure the random number counter is used each frame so random events + // are influenced by the player's actions + random.RandomInt(); + + if ( player ) { + // update the renderview so that any gui videos play from the right frame + view = player->GetRenderView(); + if ( view ) { + gameRenderWorld->SetRenderView( view ); + } + } + + // If modview is running then let it think + common->ModViewThink( ); + + // rjohnson: added option for guis to always think + common->RunAlwaysThinkGUIs( time ); + + // nmckenzie: Let AI System stuff update itself. + if ( !isMultiplayer ) { +#ifndef _MPBETA + aiManager.RunFrame(); +#endif // !_MPBETA + } + + timer_misc.Start(); + + // set the user commands for this frame + usercmds = clientCmds; + + // create a merged pvs for all players + // do this before we process events, which may rely on PVS info + SetupPlayerPVS(); + + // process events on the server + ServerProcessEntityNetworkEventQueue(); + + // update our gravity vector if needed. + UpdateGravity(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->StartFrame(); + } + + // sort the active entity list + SortActiveEntityList(); + + timer_think.Clear(); + timer_think.Start(); + + // jscott: for timing and effect handling + timer_misc2.Start(); + timer_misc.Stop(); + + // let entities think + if ( g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + idPlayer *player; + idVec3 origin; + + player = GetLocalPlayer(); + if ( player ) { + origin = player->GetPhysics()->GetOrigin(); + } + + idBounds viewTextBounds( origin ); + viewTextBounds.ExpandSelf( 128.0f ); + + num = 0; + + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + timer_singlethink.Clear(); + timer_singlethink.Start(); + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + timer_singlethink.Stop(); + ms = timer_singlethink.Milliseconds(); + if ( ms >= g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + Printf( "%d: entity '%s' [%s]: %.1f ms\n", time, ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(), ms ); + + if ( ms >= g_timeentities.GetFloat() * 3.0f ) + { + ent->mLastLongThinkColor = colorRed; + } + else + { + ent->mLastLongThinkColor[0] = 1.0f; + ent->mLastLongThinkColor[1] = 2.0f - (( ms - g_timeentities.GetFloat()) / g_timeentities.GetFloat() ); + ent->mLastLongThinkColor[2] = 0.0f; + ent->mLastLongThinkColor[3] = 1.0f; + } + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + ent->mLastLongThinkTime = time + 2000; + } + else if ( ent->mLastLongThinkTime > time ) + { + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + } + num++; + } + } else { + if ( inCinematic ) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } else { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } + } + + // remove any entities that have stopped thinking + if ( numEntitiesToDeactivate ) { + idEntity *next_ent; + int c = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + if ( !ent->thinkFlags ) { + ent->activeNode.Remove(); + c++; + } + } + //assert( numEntitiesToDeactivate == c ); + numEntitiesToDeactivate = 0; + } + + timer_think.Stop(); + timer_events.Clear(); + timer_events.Start(); + + if ( isLastPredictFrame ) { + // bdube: client entities + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } + + // service any pending events + idEvent::ServiceEvents(); + + // nrausch: player could have been deleted in an event + player = GetLocalPlayer(); + + timer_events.Stop(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->EndFrame(); + } + + // do multiplayer related stuff + if ( isMultiplayer ) { + mpGame.Run(); + } + + // free the player pvs + FreePlayerPVS(); + + // display how long it took to calculate the current game frame + if ( g_frametime.GetBool() ) { + Printf( "game %d: all:%.1f th:%.1f ev:%.1f %d ents \n", + time, timer_think.Milliseconds() + timer_events.Milliseconds(), + timer_think.Milliseconds(), timer_events.Milliseconds(), num ); + } + + // build the return value + ret.consistencyHash = 0; + ret.sessionCommand[0] = 0; + + if ( !isMultiplayer && player ) { + ret.health = player->health; + ret.heartRate = 0.0f; + ret.stamina = 0.0f; + // combat is a 0-100 value based on lastHitTime and lastDmgTime + // each make up 50% of the time spread over 10 seconds + ret.combat = 0; + if ( player->lastDmgTime > 0 && time < player->lastDmgTime + 10000 ) { + ret.combat += 50.0f * (float) ( time - player->lastDmgTime ) / 10000; + } + if ( player->lastHitTime > 0 && time < player->lastHitTime + 10000 ) { + ret.combat += 50.0f * (float) ( time - player->lastHitTime ) / 10000; + } + } + + // see if a target_sessionCommand has forced a changelevel + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + break; + } + + // make sure we don't loop forever when skipping a cinematic + if ( skipCinematic && ( time > cinematicMaxSkipTime ) ) { + Warning( "Exceeded maximum cinematic skip length. Cinematic may be looping infinitely." ); + skipCinematic = false; + break; + } + + // jscott: additional timings + timer_misc2.Stop(); + + if ( g_frametime.GetInteger() > 1 ) { + gameLocal.Printf( "misc:%.1f misc2:%.1f\n", timer_misc.Milliseconds(), timer_misc2.Milliseconds() ); + } + // bdube: let gameDebug know that its not in a game frame anymore + gameDebug.EndFrame( ); + gameLogLocal.EndFrame( ); + + } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); + + ret.syncNextGameFrame = skipCinematic; + if ( skipCinematic ) { + soundSystem->EndCinematic(); + soundSystem->SetMute( false ); + skipCinematic = false; + } + + // show any debug info for this frame + RunDebugInfo(); + D_DrawDebugLines(); + + g_simpleItems.ClearModified(); + return ret; +} +// RAVEN END + + +/* +====================================================================== + + Game view drawing + +====================================================================== +*/ + +/* +==================== +idGameLocal::CalcFov + +Calculates the horizontal and vertical field of view based on a horizontal field of view and custom aspect ratio +==================== +*/ +void idGameLocal::CalcFov( float base_fov, float &fov_x, float &fov_y ) const { + float x; + float y; + float ratio_x; + float ratio_y; + + if ( !sys->FPU_StackIsEmpty() ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: FPU stack not empty" ); + } + +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( g_fixedHorizFOV.GetBool() ) { + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + ratio_x = 4.0f; + ratio_y = 3.0f; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + x = ratio_x / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( ratio_y, x ); + fov_y = y * 360.0f / idMath::PI; + fov_x = base_fov; + return; + } +// RAVEN END + + // first, calculate the vertical fov based on a 640x480 view + x = 640.0f / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( 480.0f, x ); + fov_y = y * 360.0f / idMath::PI; + + // FIXME: somehow, this is happening occasionally + assert( fov_y > 0 ); + if ( fov_y <= 0 ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } + + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + fov_x = base_fov; + return; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + + y = ratio_y / idMath::Tan( fov_y / 360.0f * idMath::PI ); + fov_x = idMath::ATan( ratio_x, y ) * 360.0f / idMath::PI; + + if ( fov_x < base_fov ) { + fov_x = base_fov; + x = ratio_x / idMath::Tan( fov_x / 360.0f * idMath::PI ); + fov_y = idMath::ATan( ratio_y, x ) * 360.0f / idMath::PI; + } + + // FIXME: somehow, this is happening occasionally + assert( ( fov_x > 0 ) && ( fov_y > 0 ) ); + if ( ( fov_y <= 0 ) || ( fov_x <= 0 ) ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } +} + +void ClearClipProfile( void ); +void DisplayClipProfile( void ); + +/* +================ +idGameLocal::Draw + +makes rendering and sound system calls +================ +*/ +bool idGameLocal::Draw( int clientNum ) { +// DisplayClipProfile( ); +// ClearClipProfile( ); + + if ( isMultiplayer ) { + return mpGame.Draw( clientNum ); + } + + idPlayer *player = static_cast(entities[ clientNum ]); + + if ( !player ) { + return false; + } + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming. +#if defined(_XENON) + renderView_t *view = player->GetRenderView(); + // nrausch: view doesn't necessarily exist yet + if ( !view ) { + player->CalculateRenderView(); + view = player->GetRenderView(); + } +#endif +// RAVEN END + // render the scene + player->playerView.RenderPlayerView( player->hud ); + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud( ); +// RAVEN END + + return true; +} + +/* +================ +idGameLocal::HandleESC +================ +*/ +escReply_t idGameLocal::HandleESC( idUserInterface **gui ) { + +//RAVEN BEGIN +//asalmon: xbox dedicated server needs to bring up a special menu +#ifdef _XBOX + if( cvarSystem->GetCVarBool( "net_serverDedicated" )) + { + return ESC_MAIN; + } +#endif +//RAVEN END + + if ( isMultiplayer ) { + *gui = StartMenu(); + // we may set the gui back to NULL to hide it + return ESC_GUI; + } + idPlayer *player = GetLocalPlayer(); + if ( player ) { + if ( player->HandleESC() ) { + return ESC_IGNORE; + } else { + return ESC_MAIN; + } + } + return ESC_MAIN; +} + +/* +================ +idGameLocal::UpdatePlayerPostMainMenu +================ +*/ +void idGameLocal::UpdatePlayerPostMainMenu() { + idPlayer* player = GetLocalPlayer(); + + //dedicated server? + if( !player) { + return; + } + + //crosshair may have changed + player->UpdateHudWeapon(); +} + +/* +================ +idGameLocal::StartMenu +================ +*/ +idUserInterface* idGameLocal::StartMenu( void ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.StartMenu(); +} + +/* +================ +idGameLocal::GetClientStats +================ +*/ +void idGameLocal::GetClientStats( int clientNum, char *data, const int len ) { + mpGame.PlayerStats( clientNum, data, len ); +} + +/* +================ +idGameLocal::SwitchTeam +================ +*/ +void idGameLocal::SwitchTeam( int clientNum, int team ) { + + idPlayer * player; + player = clientNum >= 0 ? static_cast( gameLocal.entities[ clientNum ] ) : NULL; + + if ( !player ) { + return; + } + + int oldTeam = player->team; + + // Put in spectator mode + if ( team == -1 ) { + static_cast< idPlayer * >( entities[ clientNum ] )->Spectate( true ); + } + // Switch to a team + else { + mpGame.SwitchToTeam ( clientNum, oldTeam, team ); + } +} + +/* +================ +idGameLocal::HandleGuiCommands +================ +*/ +const char* idGameLocal::HandleGuiCommands( const char *menuCommand ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.HandleGuiCommands( menuCommand ); +} + +/* +================ +idGameLocal::HandleMainMenuCommands +================ +*/ +void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { + if ( !idStr::Icmp( menuCommand, "initCreateServerSettings" ) ) { + int guiValue = 0; + + switch ( mpGame.GameTypeToVote( si_gameType.GetString() ) ) { + case idMultiplayerGame::VOTE_GAMETYPE_DM: + guiValue = 0; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TOURNEY: + guiValue = 1; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TDM: + guiValue = 2; + break; + case idMultiplayerGame::VOTE_GAMETYPE_CTF: + guiValue = 3; + break; + case idMultiplayerGame::VOTE_GAMETYPE_ARENA_CTF: + guiValue = 4; + break; + case idMultiplayerGame::VOTE_GAMETYPE_DEADZONE: + guiValue = 5; + break; + } + + gui->SetStateInt( "currentGametype", guiValue ); + } else if ( !idStr::Icmp( menuCommand, "filterByNextMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = 0; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + ++filterMod; + if ( filterMod < modList.Num() ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "filterByPrevMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = modList.Num() - 1; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + --filterMod; + if ( filterMod >= 0 ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "updateFilterByMod" ) ) { + if ( filterMod < 0 || filterMod >= modList.Num() ) { + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } else { + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } + } else if ( !idStr::Icmp( menuCommand, "server_clearSort" ) ) { + filterMod = -1; + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + + return; +} + +/* +================ +idGameLocal::GetLevelMap + + should only be used for in-game level editing +================ +*/ +idMapFile *idGameLocal::GetLevelMap( void ) { +// RAVEN BEGIN +// rhummer: Added the HasBeenResolved check, if resolve has been run then it doesn't have func_groups. +// Which we probably don't want, so force the map to be reloaded. + if ( mapFile && mapFile->HasPrimitiveData() && !mapFile->HasBeenResloved() ) { +// RAVEN END + return mapFile; + } + if ( !mapFileName.Length() ) { + return NULL; + } + + if ( mapFile ) { + delete mapFile; + } + + mapFile = new idMapFile; + if ( !mapFile->Parse( mapFileName ) ) { + delete mapFile; + mapFile = NULL; + } + + return mapFile; +} + +/* +================ +idGameLocal::GetMapName +================ +*/ +const char *idGameLocal::GetMapName( void ) const { + return mapFileName.c_str(); +} + +/* +================ +idGameLocal::CallFrameCommand +================ +*/ +void idGameLocal::CallFrameCommand( idEntity* ent, const char* frameCommand ) { + const function_t *func = program.FindFunction( frameCommand ); + if ( !func ) { + Warning( "Script function '%s' not found.", frameCommand ); + return; + } + CallFrameCommand ( ent, func ); +} + +void idGameLocal::CallFrameCommand( idEntity *ent, const function_t *frameCommand ) { + frameCommandThread->CallFunction( ent, frameCommand, true ); + frameCommandThread->Execute(); +} + +/* +================ +idGameLocal::CallObjectFrameCommand +================ +*/ +void idGameLocal::CallObjectFrameCommand( idEntity *ent, const char *frameCommand ) { + const function_t *func; + + func = ent->scriptObject.GetFunction( frameCommand ); + if ( !func ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idTestModel::GetClassType() ) ) { +// RAVEN END + Error( "Unknown function '%s' called for frame command on entity '%s'", frameCommand, ent->name.c_str() ); + } + } else { + frameCommandThread->CallFunction( ent, func, true ); + frameCommandThread->Execute(); + } +} + +/* +================ +idGameLocal::ShowTargets +================ +*/ +void idGameLocal::ShowTargets( void ) { + idMat3 axis = GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + const idVec3 &viewPos = GetLocalPlayer()->GetPhysics()->GetOrigin(); + idBounds viewTextBounds( viewPos ); + idBounds viewBounds( viewPos ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + idBounds totalBounds; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + totalBounds = ent->GetPhysics()->GetAbsBounds(); + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + totalBounds.AddBounds( target->GetPhysics()->GetAbsBounds() ); + } + } + + if ( !viewBounds.IntersectsBounds( totalBounds ) ) { + continue; + } + + float dist; + idVec3 dir = totalBounds.GetCenter() - viewPos; + dir.NormalizeFast(); + totalBounds.RayIntersection( viewPos, dir, dist ); + float frac = ( 512.0f - dist ) / 512.0f; + if ( frac < 0.0f ) { + continue; + } + + gameRenderWorld->DebugBounds( ( ent->IsHidden() ? colorLtGrey : colorOrange ) * frac, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + idVec3 center = ent->GetPhysics()->GetAbsBounds().GetCenter(); + gameRenderWorld->DrawText( ent->name.c_str(), center - up, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), center, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), center + up, 0.1f, colorWhite * frac, axis, 1 ); + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow * frac, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen * frac, box, target->GetPhysics()->GetOrigin() ); + } + } + } +} + +/* +================ +idGameLocal::RunDebugInfo +================ +*/ +void idGameLocal::RunDebugInfo( void ) { + idEntity *ent; + idPlayer *player; + + player = GetLocalPlayer(); + if ( !player ) { + return; + } + + const idVec3 &origin = player->GetPhysics()->GetOrigin(); + + if ( g_showEntityInfo.GetBool() ) { + idBounds viewTextBounds( origin ); + idBounds viewBounds( origin ); + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // don't draw the worldspawn + if ( ent == world ) { + continue; + } + +// RAVEN BEGIN +// rjohnson: moved entity info out of idGameLocal into its own function + ent->DrawDebugEntityInfo(&viewBounds, &viewTextBounds); +// RAVEN END + } + } + + // debug tool to draw bounding boxes around active entities + if ( g_showActiveEntities.GetBool() ) { + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + idBounds b = ent->GetPhysics()->GetBounds(); + if ( b.GetVolume() <= 0 ) { + b[0][0] = b[0][1] = b[0][2] = -8; + b[1][0] = b[1][1] = b[1][2] = 8; + } + if ( ent->fl.isDormant ) { + gameRenderWorld->DebugBounds( colorYellow, b, ent->GetPhysics()->GetOrigin() ); + } else { + gameRenderWorld->DebugBounds( colorGreen, b, ent->GetPhysics()->GetOrigin() ); + } + } + } + +// RAVEN BEGIN +// bdube: client entities + if ( cl_showEntityInfo.GetBool ( ) ) { + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->DrawDebugInfo ( ); + } + } +// RAVEN END + + if ( g_showTargets.GetBool() ) { + ShowTargets(); + } + + if ( g_showTriggers.GetBool() ) { + idTrigger::DrawDebugInfo(); + } + + if ( ai_showCombatNodes.GetBool() ) { +// idCombatNode::DrawDebugInfo(); + } + + if ( ai_showPaths.GetBool() ) { + idPathCorner::DrawDebugInfo(); + } + + if ( g_editEntityMode.GetBool() ) { + if ( gameLocal.isMultiplayer ) { + g_editEntityMode.SetInteger(0); + Printf( "Not allowed in multiplayer.\n" ); + } else { + editEntities->DisplayEntities(); + } + } + + if ( g_showCollisionWorld.GetBool() ) { + // use g_maxShowDistance value instead of 128.0f + collisionModelManager->DrawModel( clip[0]->GetWorldCollisionModel(), vec3_origin, mat3_identity, origin, mat3_identity, g_maxShowDistance.GetFloat() ); + } + + if ( g_showCollisionModels.GetBool() ) { + if( g_showCollisionModels.GetInteger() == 2 ) { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player, &idPlayer::GetClassType() ); + } else { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player ); + } + + } + + if ( g_showCollisionTraces.GetBool() ) { + clip[ 0 ]->PrintStatistics(); + } + + if ( g_showPVS.GetInteger() ) { + pvs.DrawPVS( origin, ( g_showPVS.GetInteger() == 2 ) ? PVS_ALL_PORTALS_OPEN : PVS_NORMAL ); + } + +// RAVEN BEGIN +// rjohnson: added debug hud + if ( gameDebug.IsHudActive ( DBGHUD_PHYSICS ) ) { + clip[ 0 ]->DebugHudStatistics(); + } + + clip[ 0 ]->ClearStatistics(); +// RAVEN END + + if ( aas_test.GetInteger() >= 0 ) { + idAAS *aas = GetAAS( aas_test.GetInteger() ); + if ( aas ) { + aas->Test( origin ); + if ( ai_testPredictPath.GetBool() ) { + idVec3 velocity; + predictedPath_t path; + + velocity.x = idMath::Cos( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.y = idMath::Sin( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.z = 0.0f; + idAI::PredictPath( player, aas, origin, velocity, 1000, 100, SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA, path ); + } + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin ); + } + } + } + if ( aas_showProblemAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin, true ); + } + } + } +// RAVEN END + } + + if ( ai_showObstacleAvoidance.GetInteger() == 2 ) { + idAAS *aas = GetAAS( 0 ); + if ( aas ) { + idVec3 seekPos; + obstaclePath_t path; + + seekPos = player->GetPhysics()->GetOrigin() + player->viewAxis[0] * 200.0f; + idAI::FindPathAroundObstacles( player->GetPhysics(), aas, NULL, player->GetPhysics()->GetOrigin(), seekPos, path ); + } + } + + // collision map debug output + collisionModelManager->DebugOutput( player->GetEyePosition(), mat3_identity ); + +// RAVEN BEGIN +// jscott: for debugging playbacks + if( g_showPlayback.GetInteger() ) { + gameEdit->DrawPlaybackDebugInfo(); + } +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if ( g_showClipSectors.GetBool() ) { + clip[ 0 ]->DrawClipSectors(); + } + + if ( g_showAreaClipSectors.GetFloat() ) { + clip[ 0 ]->DrawAreaClipSectors( g_showAreaClipSectors.GetFloat() ); + } +// RAVEN END +} + +/* +================== +idGameLocal::NumAAS +================== +*/ +int idGameLocal::NumAAS( void ) const { + return aasList.Num(); +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( int num ) const { + if ( ( num >= 0 ) && ( num < aasList.Num() ) ) { + if ( aasList[ num ] && aasList[ num ]->GetSettings() ) { + return aasList[ num ]; + } + } + return NULL; +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( const char *name ) const { + int i; + + for ( i = 0; i < aasNames.Num(); i++ ) { + if ( aasNames[ i ] == name ) { + if ( !aasList[ i ]->GetSettings() ) { + return NULL; + } else { + return aasList[ i ]; + } + } + } + return NULL; +} + +/* +================== +idGameLocal::SetAASAreaState +================== +*/ +void idGameLocal::SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->SetAreaState( bounds, areaContents, closed ); + } +} + +/* +================== +idGameLocal::AddAASObstacle +================== +*/ +aasHandle_t idGameLocal::AddAASObstacle( const idBounds &bounds ) { + int i; + aasHandle_t obstacle; + aasHandle_t check; + + if ( !aasList.Num() ) { + return -1; + } + + obstacle = aasList[ 0 ]->AddObstacle( bounds ); + for( i = 1; i < aasList.Num(); i++ ) { + check = aasList[ i ]->AddObstacle( bounds ); + assert( check == obstacle ); + } + + return obstacle; +} + +/* +================== +idGameLocal::RemoveAASObstacle +================== +*/ +void idGameLocal::RemoveAASObstacle( const aasHandle_t handle ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveObstacle( handle ); + } +} + +/* +================== +idGameLocal::RemoveAllAASObstacles +================== +*/ +void idGameLocal::RemoveAllAASObstacles( void ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveAllObstacles(); + } +} + +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. +/* +================== +idGameLocal::GetEntityMemoryUsage + +Compute combined total memory footprint of server and client entity storage. +================== +*/ + +size_t idGameLocal::GetEntityMemoryUsage ( void ) const { + + // Server ents. + size_t serverEntitiesSize = 0; + for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + serverEntitiesSize += sizeof( idEntity ); + } + + // Client ents. + size_t clientEntitiesSize = 0; + for( rvClientEntity *ent = gameLocal.clientSpawnedEntities.Next() ; ent != NULL; ent = ent->spawnNode.Next() ) { + clientEntitiesSize += ent->Size(); + } + + return serverEntitiesSize + clientEntitiesSize; +} +// RAVEN END + +/* +================== +idGameLocal::CheatsOk +================== +*/ +bool idGameLocal::CheatsOk( bool requirePlayer ) { + idPlayer *player; + + if ( isMultiplayer && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { + Printf( "Not allowed in multiplayer.\n" ); + return false; + } + + if ( developer.GetBool() ) { + return true; + } + + player = GetLocalPlayer(); + if ( !requirePlayer || ( player && ( player->health > 0 ) ) ) { + return true; + } + + Printf( "You must be alive to use this command.\n" ); + + return false; +} + +/* +=================== +idGameLocal::RegisterEntity +=================== +*/ +void idGameLocal::RegisterEntity( idEntity *ent ) { + int spawn_entnum; + + if ( spawnCount >= ( 1 << ( 32 - GENTITYNUM_BITS ) ) ) { + Error( "idGameLocal::RegisterEntity: spawn count overflow" ); + } + + firstFreeIndex = Max( minSpawnIndex, firstFreeIndex ); + + if ( !spawnArgs.GetInt( "spawn_entnum", "0", spawn_entnum ) ) { + while ( entities[firstFreeIndex] && firstFreeIndex < ENTITYNUM_MAX_NORMAL ) { + firstFreeIndex++; + } + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } + spawn_entnum = firstFreeIndex++; + } else { + assert( spawn_entnum < MAX_CLIENTS || spawn_entnum >= minSpawnIndex ); + } + + entities[ spawn_entnum ] = ent; + spawnIds[ spawn_entnum ] = spawnCount++; + ent->entityNumber = spawn_entnum; + ent->spawnNode.AddToEnd( spawnedEntities ); + ent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( spawn_entnum >= num_entities ) { + num_entities++; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=================== +idGameLocal::UnregisterEntity +=================== +*/ +void idGameLocal::UnregisterEntity( idEntity *ent ) { + assert( ent ); + + if ( editEntities ) { + editEntities->RemoveSelectedEntity( ent ); + } + + if ( ( ent->entityNumber != ENTITYNUM_NONE ) && ( entities[ ent->entityNumber ] == ent ) ) { + ent->spawnNode.Remove(); + entities[ ent->entityNumber ] = NULL; + spawnIds[ ent->entityNumber ] = -1; + if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < firstFreeIndex ) { + firstFreeIndex = ent->entityNumber; + } + ent->entityNumber = ENTITYNUM_NONE; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=============== +idGameLocal::SkipEntityIndex +=============== +*/ +void idGameLocal::SkipEntityIndex( void ) { + assert( entities[ firstFreeIndex ] == NULL ); + firstFreeIndex++; + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } +} + +/* +================ +idGameLocal::SpawnEntityType +================ +*/ +idEntity *idGameLocal::SpawnEntityType( const idTypeInfo &classdef, const idDict *args, bool bIsClientReadSnapshot ) { + idClass *obj; + +#ifdef _DEBUG + if ( isClient ) { + assert( bIsClientReadSnapshot ); + } +#endif + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !classdef.IsType( idEntity::GetClassType() ) ) { +// RAVEN END + Error( "Attempted to spawn non-entity class '%s'", classdef.classname ); + } + + try { + if ( args ) { + spawnArgs = *args; + } else { + spawnArgs.Clear(); + } + obj = classdef.CreateInstance(); + obj->CallSpawn(); + } + + catch( idAllocError & ) { + obj = NULL; + } + spawnArgs.Clear(); + + return static_cast(obj); +} + +/* +=================== +idGameLocal::SpawnClientEntityDef + +Finds the spawn function for the client entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnClientEntityDef( const idDict &args, rvClientEntity **cent, bool setDefaults, const char* spawn ) { + const char *classname; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + if ( cent ) { + *cent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ){ + //not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { + error = va( " on '%s'", name ); + } + + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { + // RAVEN BEGIN + // jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } + // RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + + // check if we should spawn a class object + if( spawn == NULL ) { + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + } + + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + + if ( cent && obj->IsType( rvClientEntity::GetClassType() ) ) { + *cent = static_cast(obj); + } + + return true; + } + + Warning( "%s doesn't include a spawnfunc%s.", classname, error.c_str() ); + return false; +} + +/* +=================== +idGameLocal::SpawnEntityDef + +Finds the spawn function for the entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDefaults ) { + const char *classname; + const char *spawn; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( ent ) { + *ent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ) + {//not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + error = va( " on '%s'", name ); +// RAVEN END + } + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { +// RAVEN BEGIN +// jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } +// RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + if ( g_keepEntityStats.GetBool() ) { + if ( idStr::Icmp( classname, "func_spawner" ) == 0 || + idStr::Icmp( classname, "func_spawner_enemy" ) == 0 ) { + // special case for spawners + for( int i = 1; ; i++ ) { + char tempSpawn[128]; + const char *tempClassname; + + sprintf( tempSpawn, "def_spawn_type%d", i ); + tempClassname = spawnArgs.GetString( tempSpawn, NULL ); + if ( tempClassname ) { + const idDeclEntityDef *tempDef = FindEntityDef( tempClassname, false ); + + if ( tempDef ) { + idDict tempArgs = tempDef->dict; + + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } else { + break; + } + } + } + else if ( def->dict.GetBool( "report_stats" ) ) { + idDict tempArgs = spawnArgs; + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } +// RAVEN END + + // check if we should spawn a class object + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && obj->IsType( idEntity::GetClassType() ) ) { +// RAVEN END + *ent = static_cast(obj); + } + + return true; + } + + // check if we should call a script function to spawn + spawnArgs.GetString( "spawnfunc", NULL, &spawn ); + if ( spawn ) { + const function_t *func = program.FindFunction( spawn ); + if ( !func ) { + Warning( "Could not spawn '%s'. Script function '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + idThread *thread = new idThread( func ); + thread->DelayedStart( 0 ); + + return true; + } + + Warning( "%s doesn't include a spawnfunc or spawnclass%s.", classname, error.c_str() ); + return false; +} + +// abahr: +idEntity* idGameLocal::SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idDict finalArgs; + const idDict* entityDict = NULL; + idEntity* entity = NULL; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if( !entityDefName ) { + return NULL; + } + + entityDict = FindEntityDefDict( entityDefName, false ); + if( !entityDict ) { + return NULL; + } + + if( !additionalArgs ) { + SpawnEntityDef( *entityDict, &entity ); + } else { + finalArgs.Copy( *entityDict ); + finalArgs.Copy( *additionalArgs ); + SpawnEntityDef( finalArgs, &entity ); + } + + return entity; +} +// RAVEN END + +/* +================ +idGameLocal::FindEntityDef +================ +*/ +const idDeclEntityDef *idGameLocal::FindEntityDef( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDecl *decl = NULL; + if ( isMultiplayer ) { + decl = declManager->FindType( DECL_ENTITYDEF, va( "%s_mp", name ), false ); + } + if ( !decl ) { + decl = declManager->FindType( DECL_ENTITYDEF, name, makeDefault ); + } + return static_cast( decl ); +} + +/* +================ +idGameLocal::FindEntityDefDict +================ +*/ +const idDict *idGameLocal::FindEntityDefDict( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDeclEntityDef *decl = FindEntityDef( name, makeDefault ); + return decl ? &decl->dict : NULL; +} + +/* +================ +idGameLocal::InhibitEntitySpawn +================ +*/ +bool idGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { + + bool result = false; + + if ( isMultiplayer ) { + spawnArgs.GetBool( "not_multiplayer", "0", result ); + } else if ( g_skill.GetInteger() == 0 ) { + spawnArgs.GetBool( "not_easy", "0", result ); + } else if ( g_skill.GetInteger() == 1 ) { + spawnArgs.GetBool( "not_medium", "0", result ); + } else { + spawnArgs.GetBool( "not_hard", "0", result ); + } + + const char *name; +#ifndef ID_DEMO_BUILD + if ( g_skill.GetInteger() == 3 ) { + name = spawnArgs.GetString( "classname" ); + if ( idStr::Icmp( name, "item_medkit" ) == 0 || idStr::Icmp( name, "item_medkit_small" ) == 0 ) { + result = true; + } + } +#endif + +// RAVEN BEGIN +// bdube: suppress ents that don't match the entity filter + const char* entityFilter; + if ( serverInfo.GetString( "si_entityFilter", "", &entityFilter ) && *entityFilter ) { + if ( spawnArgs.MatchPrefix ( "filter_" ) && !spawnArgs.GetBool ( va("filter_%s", entityFilter) ) ) { + return true; + } + } +// RAVEN END + +// RITUAL BEGIN +// squirrel: suppress ents that aren't supported in Buying modes (if that's the mode we're in) + if ( mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( spawnArgs.GetBool( "disableSpawnInBuying", "0" ) ) { + return true; + } + + /// Don't spawn weapons or armor vests or ammo in Buying modes + idStr classname = spawnArgs.GetString( "classname" ); + if( idStr::FindText( classname, "weapon_" ) == 0 || + idStr::FindText( classname, "item_armor_small" ) == 0 || + idStr::FindText( classname, "ammo_" ) == 0 || + idStr::FindText( classname, "item_armor_large" ) == 0 ) + { + return true; + } + } +// RITUAL END + + // suppress deadzone triggers if we're not running DZ + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "DeadZone" ) != 0 ) { + if ( idStr::Icmp( spawnArgs.GetString( "classname" ), "trigger_controlzone" ) == 0 ) { + return true; + } + } + + return result; +} + +/* +================ +idGameLocal::SetSkill +================ +*/ +void idGameLocal::SetSkill( int value ) { + int skill_level; + + if ( value < 0 ) { + skill_level = 0; + } else if ( value > 3 ) { + skill_level = 3; + } else { + skill_level = value; + } + + g_skill.SetInteger( skill_level ); +} + +/* +============== +idGameLocal::GameState + +Used to allow entities to know if they're being spawned during the initial spawn. +============== +*/ +gameState_t idGameLocal::GameState( void ) const { + return gamestate; +} + +/* +============== +idGameLocal::SpawnMapEntities + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple game instances +void idGameLocal::SpawnMapEntities( int instance, unsigned short* entityNumIn, unsigned short* entityNumOut, int* startSpawnCount ) { +// RAVEN END + int i; + int num; + int inhibit; + int latchedSpawnCount = -1; + idMapEntity *mapEnt; + int numEntities; + idDict args; + idDict items; + + bool proto69 = ( GetCurrentDemoProtocol() == 69 ); + + Printf( "Spawning entities\n" ); + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( mapFile == NULL ) { + Printf("No mapfile present\n"); + return; + } + + SetSkill( g_skill.GetInteger() ); + + numEntities = mapFile->GetNumEntities(); + if ( numEntities == 0 ) { + Error( "...no entities" ); + } + +// RAVEN BEGIN +// ddynerman: on the server, perform world-initialization when spawning instance 0 only and while starting up +// on the client, perform world-init when spawning while starting up, as client main instance may not be ID 0 +// always spawn world-init in single-player + num = inhibit = 0; + + // the worldspawn is a special that performs any global setup + // needed by a level + if ( ( gameLocal.isServer && instance == 0 && gamestate == GAMESTATE_STARTUP ) || ( gameLocal.isClient && gamestate == GAMESTATE_STARTUP ) || !gameLocal.isMultiplayer ) { + mapEnt = mapFile->GetEntity( 0 ); + args = mapEnt->epairs; + args.SetInt( "spawn_entnum", ENTITYNUM_WORLD ); + + // on the client, spawnCount won't always start at INITIAL_SPAWN_COUNT (see rvInstance::Populate()) + // make sure the world and physics ent get the right spawn id + if( gameLocal.isClient && spawnCount != INITIAL_SPAWN_COUNT ) { + latchedSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + } + +// abahr: precache stuff on worldSpawn like player def and other misc things + CacheDictionaryMedia( &args ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityDef( args ) || !entities[ ENTITYNUM_WORLD ] || !entities[ ENTITYNUM_WORLD ]->IsType( idWorldspawn::GetClassType() ) ) { + Error( "Problem spawning world entity" ); + } + num++; + +// bdube: dummy entity for client entities with physics + args.Clear(); + args.SetInt( "spawn_entnum", ENTITYNUM_CLIENT ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityType( rvClientPhysics::GetClassType(), &args, true ) || !entities[ ENTITYNUM_CLIENT ] ) { + Error( "Problem spawning client physics entity" ); + } + + if( gameLocal.isClient && latchedSpawnCount != -1 ) { + spawnCount = latchedSpawnCount; + } + + isMapEntity[ ENTITYNUM_CLIENT ] = true; + isMapEntity[ ENTITYNUM_WORLD ] = true; +// RAVEN END + } + + // capture spawn count of start of map entities (after we've spawned in the physics ents) + if ( startSpawnCount ) { + (*startSpawnCount) = spawnCount; + } + + for ( i = 1 ; i < numEntities ; i++ ) { + + mapEnt = mapFile->GetEntity( i ); + args = mapEnt->epairs; + +// RAVEN BEGIN +// ddynerman: merge the dicts ahead of SpawnEntityDef() so we can inhibit using merged info + const idDeclEntityDef* entityDef = FindEntityDef( args.GetString( "classname" ), false ); + + if( entityDef == NULL ) { + gameLocal.Error( "idGameLocal::SpawnMapEntities() - Unknown entity classname '%s'\n", args.GetString( "classname" ) ); + return; + } + args.SetDefaults( &(entityDef->dict) ); +// RAVEN END + + if ( !InhibitEntitySpawn( args ) ) { + + if( args.GetBool( "inv_item" ) ) { + if( !items.GetBool( args.GetString( "inv_icon" ) ) ) { + networkSystem->AddLoadingIcon( args.GetString( "inv_icon" ) ); + networkSystem->SetLoadingText( common->GetLocalizedString( args.GetString( "inv_name" ) ) ); + items.SetBool( args.GetString( "inv_icon" ), true ); + } + } + + // precache any media specified in the map entity + CacheDictionaryMedia( &args ); +// RAVEN BEGIN + if ( instance != 0 ) { +// ddynerman: allow this function to be called multiple-times to respawn map entities in other instances + args.SetInt( "instance", instance ); + args.Set( "name", va( "%s_instance%d", args.GetString( "name" ), instance ) ); + //gameLocal.Printf( "Instance %d: Spawning %s (class: %s)\n", instance, args.GetString( "name" ), args.GetString( "classname" ) ); + + // need a better way to do this - map entities that target other map entities need + // to target the ones in the correct instance. + idStr target; + if( args.GetString( "target", "", target ) ) { + args.Set( "target", va( "%s_instance%d", target.c_str(), instance ) ); + } + + // and also udpate binds on a per-instance bases + idStr bind; + if( args.GetString( "bind", "", bind ) ) { + args.Set( "bind", va( "%s_instance%d", bind.c_str(), instance ) ); + } + + // and also door triggers + idStr triggerOnOpen; + if( args.GetString( "triggerOnOpen", "", triggerOnOpen ) ) { + args.Set( "triggerOnOpen", va( "%s_instance%d", triggerOnOpen.c_str(), instance ) ); + } + + idStr triggerOpened; + if( args.GetString( "triggerOpened", "", triggerOpened ) ) { + args.Set( "triggerOpened", va( "%s_instance%d", triggerOpened.c_str(), instance ) ); + } + } + + // backward compatible 1.2 playback: proto69 reads the entity mapping list from the server + // only 1.2 backward replays should get passed a != NULL entityNumIn + assert( entityNumIn == NULL || proto69 ); + if ( proto69 && entityNumIn ) { + assert( gameLocal.isClient ); + if ( entityNumIn[ i ] < 0 || entityNumIn[ i ] >= MAX_GENTITIES ) { + // this entity was not spawned in on the server, ignore it here + // this is one of the symptoms of net corruption <= 1.2 - fix here acted as a band aid + // if you replay a 1.2 netdemo and hit this, it's likely other things will go wrong in the replay + common->Warning( "entity inhibited on server not properly inhibited on client - map ent %d ( %s )", i, args.GetString( "name" ) ); + inhibit++; + continue; + } + args.SetInt( "spawn_entnum", entityNumIn[ i ] ); + } + + idEntity* ent = NULL; + SpawnEntityDef( args, &ent ); + //common->Printf( "pop: spawn map ent %d at %d ( %s )\n", i, ent->entityNumber, args.GetString( "name" ) ); + + if ( ent && entityNumOut ) { + entityNumOut[ i ] = ent->entityNumber; + } + + if ( gameLocal.GetLocalPlayer() && ent && gameLocal.isServer && instance != gameLocal.GetLocalPlayer()->GetInstance() ) { + // if we're spawning entities not in our instance, tell them not to draw + ent->BecomeInactive( TH_UPDATEVISUALS ); + } + + if ( ent ) { + isMapEntity[ ent->entityNumber ] = true; + } +// RAVEN END + num++; + } else { + if ( !proto69 ) { + // keep counting and leave an empty slot in the entity list for inhibited entities + // this so we maintain the same layout as the server and don't change it across restarts with different inhibit schemes + //common->Printf( "pop: skip map ent %d at index %d ( %s )\n", i, firstFreeIndex, args.GetString( "name" ) ); + SkipEntityIndex(); + } else { + // backward 1.2 netdemo replay compatibility - no skipping + // they might net corrupt but there's no fix client side + assert( isClient ); + } + inhibit++; + } + } + + Printf( "...%i entities spawned, %i inhibited\n\n", num, inhibit ); +} + +/* +================ +idGameLocal::AddEntityToHash +================ +*/ +void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) { + if ( FindEntity( name ) ) { + Error( "Multiple entities named '%s'", name ); + } + entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber ); +} + +/* +================ +idGameLocal::RemoveEntityFromHash +================ +*/ +bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) { + entityHash.Remove( hash, i ); + return true; + } + } + return false; +} + +/* +================ +idGameLocal::GetTargets +================ +*/ +int idGameLocal::GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const { + int i, num, refLength; + const idKeyValue *arg; + idEntity *ent; + + list.Clear(); + + refLength = strlen( ref ); + num = args.GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + + arg = args.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( ref, refLength ) == 0 ) { + + ent = FindEntity( arg->GetValue() ); + if ( ent ) { + idEntityPtr &entityPtr = list.Alloc(); + entityPtr = ent; + } + } + } + + return list.Num(); +} + +/* +============= +idGameLocal::GetTraceEntity + +returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. +============= +*/ +idEntity *idGameLocal::GetTraceEntity( const trace_t &trace ) const { + idEntity *master; + + if ( !entities[ trace.c.entityNum ] ) { + return NULL; + } + master = entities[ trace.c.entityNum ]->GetBindMaster(); + if ( master ) { + return master; + } + return entities[ trace.c.entityNum ]; +} + +/* +============= +idGameLocal::ArgCompletion_EntityName + +Argument completion for entity names +============= +*/ +void idGameLocal::ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::ArgCompletion_AIName + +Argument completion for idAI entity names +============= +*/ +void idGameLocal::ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idAI::GetClassType() ) ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::FindEntity + +Returns the entity whose name matches the specified string. +============= +*/ +idEntity *idGameLocal::FindEntity( const char *name ) const { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i]->name.Icmp( name ) == 0 ) { + return entities[i]; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindEntityUsingDef + +Searches all active entities for the next one using the specified entityDef. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. +============= +*/ +idEntity *idGameLocal::FindEntityUsingDef( idEntity *from, const char *match ) const { + idEntity *ent; + + if ( !from ) { + ent = spawnedEntities.Next(); + } else { + ent = from->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + assert( ent ); + if ( idStr::Icmp( ent->GetEntityDefName(), match ) == 0 ) { + return ent; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindTraceEntity + +Searches all active entities for the closest ( to start ) match that intersects +the line start,end +============= +*/ +idEntity *idGameLocal::FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( c ) && ent != skip ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} + +/* +================ +idGameLocal::EntitiesWithinRadius +================ +*/ +int idGameLocal::EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const { + idEntity *ent; + idBounds bo( org ); + int entCount = 0; + + bo.ExpandSelf( radius ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { + entityList[entCount++] = ent; + } + } + + return entCount; +} + +/* +================= +idGameLocal::KillBox + +Kills all entities that would touch the proposed new positioning of ent. The ent itself will not being killed. +Checks if player entities are in the teleporter, and marks them to die at teleport exit instead of immediately. +If catch_teleport, this only marks teleport players for death on exit +================= +*/ +void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idPhysics *phys; + + phys = ent->GetPhysics(); + if ( !phys->GetNumClipModels() ) { + return; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = ClipModelsTouchingBounds( ent, phys->GetAbsBounds(), phys->GetClipMask(), clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == ent ) || !hit->fl.takedamage ) { + continue; + } + + if ( !phys->ClipContents( cm ) ) { + continue; + } + + // nail it +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( hit->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( hit )->IsInTeleport() ) { +// RAVEN END + static_cast< idPlayer * >( hit )->TeleportDeath( ent->entityNumber ); + } else if ( !catch_teleport ) { + hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + } + + if ( !gameLocal.isMultiplayer ) { + // let the mapper know about it + Warning( "'%s' telefragged '%s'", ent->name.c_str(), hit->name.c_str() ); + } + } +} + +/* +================ +idGameLocal::RequirementMet +================ +*/ +bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) { + if ( requires.Length() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + idPlayer *player = static_cast(activator); + idDict *item = player->FindInventoryItem( requires ); + if ( item ) { + if ( removeItem ) { + player->RemoveInventoryItem( item ); + } + return true; + } else { + return false; + } + } + } + + return true; +} + + +/* +================ +idGameLocal::IsWeaponsStayOn +================ +*/ +bool idGameLocal::IsWeaponsStayOn( void ) { + /// Override weapons stay when buying is active + if( isMultiplayer && mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + return false; + } + + return serverInfo.GetBool( "si_weaponStay" ); +} + + +/* +============ +idGameLocal::AlertAI +============ +*/ +void idGameLocal::AlertAI( idEntity *ent ) { +// RAVEN BEGIN +// bdube: merged + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // alert them for the next frame + lastAIAlertActorTime = time + GetMSec(); + lastAIAlertActor = static_cast( ent ); + } else { + lastAIAlertEntityTime = time + GetMSec(); + lastAIAlertEntity = ent; + } + } else { + lastAIAlertEntityTime = 0; + lastAIAlertActorTime = 0; + lastAIAlertEntity = NULL; + lastAIAlertActor = NULL; + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: alert entity returns an entity, alert actor a an actor +/* +============ +idGameLocal::GetAlertEntity +============ +*/ +idEntity *idGameLocal::GetAlertEntity( void ) { + if ( lastAIAlertEntityTime >= time ) { + return lastAIAlertEntity.GetEntity(); + } + + return NULL; +} + +/* +============ +idGameLocal::GetAlertActor +============ +*/ +idActor *idGameLocal::GetAlertActor( void ) { + if ( lastAIAlertActorTime >= time ) { + return lastAIAlertActor.GetEntity(); + } + + return NULL; +} +// RAVEN END + +/* +============ +SortClipModelsByEntity +============ +*/ +static int SortClipModelsByEntity( const void* left, const void* right ) { + idEntity* leftEntity = left ? (*((const idClipModel**)left))->GetEntity() : NULL; + idEntity* rightEntity = right ? (*((const idClipModel**)right))->GetEntity() : NULL; + + int entityNumLeft = (leftEntity) ? leftEntity->entityNumber : 0; + int entityNumRight = (rightEntity) ? rightEntity->entityNumber : 0; + + return entityNumLeft - entityNumRight; +} + +// RAVEN BEGIN +/* +============ +idGameLocal::RadiusDamage +Returns the number of actors damaged +============ +*/ +// abahr: changed to work with deathPush +void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower, int* hitCount ) { + float dist, damageScale, attackerDamageScale, attackerPushScale; + idEntity* ent = NULL; + idEntity* lastEnt = NULL; + idClipModel* clipModel = NULL; + idClipModel* clipModelList[ MAX_GENTITIES ]; + int numListedClipModels; + modelTrace_t result; + idVec3 v, damagePoint, dir; + int i, damage, radius, push; + + const idDict *damageDef = FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damageDef->GetInt( "radius", "50", radius ); + damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); + damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); + if( gameLocal.isMultiplayer ) { + damageDef->GetFloat( "attackerPushScale", "2", attackerPushScale ); + } else { + damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + } + + if ( radius < 1 ) { + radius = 1; + } + + +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, idBounds(origin).Expand(radius), MASK_ALL, clipModelList, MAX_GENTITIES ); + if( numListedClipModels > 0 ) { + //Sort list by unique entities for easier searching + qsort( clipModelList, numListedClipModels, sizeof(clipModelList[0]), SortClipModelsByEntity ); + } + + if ( inflictor ) { + inflictor = inflictor->GetDamageEntity ( ); + } + if ( attacker ) { + attacker = attacker->GetDamageEntity ( ); + } + if ( ignoreDamage ) { + ignoreDamage = ignoreDamage->GetDamageEntity ( ); + } + + for( int c = 0; c < numListedClipModels; ++c ) { + clipModel = clipModelList[ c ]; + assert( clipModel ); + + ent = clipModel->GetEntity(); + + // Skip all entitys that arent primary damage entities + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } + + // Dont damage inflictor or the ignore entity + if( ent == inflictor || ent == ignoreDamage ) { + continue; + } + + idBounds absBounds = clipModel->GetAbsBounds(); + + // find the distance from the edge of the bounding box + for ( i = 0; i < 3; i++ ) { + if ( origin[ i ] < absBounds[0][ i ] ) { + v[ i ] = absBounds[0][ i ] - origin[ i ]; + } else if ( origin[ i ] > absBounds[1][ i ] ) { + v[ i ] = origin[ i ] - absBounds[1][ i ]; + } else { + v[ i ] = 0; + } + } + + dist = v.Length(); + if ( dist >= radius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, absBounds.GetCenter()) ) { + continue; + } + + RadiusPushClipModel ( inflictor, origin, push, clipModel ); + + // Only damage unique entities. This works because we have a sorted list + if( lastEnt == ent ) { + continue; + } + + lastEnt = ent; + + if ( ent->CanDamage( origin, damagePoint, ignoreDamage ) ) { + // push the center of mass higher than the origin so players + // get knocked into the air more + if( gameLocal.isMultiplayer ) { + // fudge the direction in MP to account for player height difference and origin shift + // 31.875 = origin is 23.875 units lower in Q4 than Q3 + player is 8 units taller in Q4 + dir = ( ent->GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 31.875f ) ) - origin; + } else { + dir = ent->GetPhysics()->GetOrigin() - origin; + } + + dir[2] += 24; + + // get the damage scale + damageScale = dmgPower * ( 1.0f - dist / radius ); + + if ( ent == attacker ) { + damageScale *= attackerDamageScale; + } + + dir.Normalize(); + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(ent->GetPhysics()->GetClipModel()->GetId()) ); + + // for stats, count the first + if( attacker && attacker->IsType( idPlayer::GetClassType() ) && inflictor && inflictor->IsType( idProjectile::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && hitCount ) { + // with splash damage projectiles, one projectile fire can damage multiple people. If anyone is hit, + // the shot counts as a hit but only report accuracy on the first one to avoid accuracies > 100% + statManager->WeaponHit( (const idActor*)attacker, ent, ((idProjectile*)inflictor)->methodOfDeath, (*hitCount) == 0 ); + (*hitCount)++; + } + } + } +} +// RAVEN END + +/* +============== +idGameLocal::RadiusPush +============== +*/ +void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) { + int i, numListedClipModels; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 dir; + idBounds bounds; + modelTrace_t result; + idEntity *ent; + float scale; + +// RAVEN BEGIN +// abahr: need to use gravity instead of assuming z is up + dir = -GetCurrentGravity( const_cast(inflictor) ).ToNormal(); +// RAVEN END + + bounds = idBounds( origin ).Expand( radius ); + + // get all clip models touching the bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, bounds, -1, clipModelList, MAX_GENTITIES ); + +// bdube: getdamageentity + if ( inflictor ) { + inflictor = ((idEntity*)inflictor)->GetDamageEntity ( ); + } + if ( ignore ) { + ignore = ((idEntity*)ignore)->GetDamageEntity ( ); + } +// RAVEN END + + // apply impact to all the clip models through their associated physics objects + for ( i = 0; i < numListedClipModels; i++ ) { + + clipModel = clipModelList[i]; + + // never push render models + if ( clipModel->IsRenderModel() ) { + continue; + } + + ent = clipModel->GetEntity(); +// RAVEN BEGIN +// bdube: damage entity + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } +// RAVEN END + + // never push projectiles +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + continue; + } + + // players use "knockback" in idPlayer::Damage +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idPlayer::GetClassType() ) && !quake ) { +// RAVEN END + continue; + } + + // don't push the ignore entity + if ( ent == ignore ) { + continue; + } + + if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetOrigin() ) ) { + continue; + } + + // scale the push for the inflictor + if ( ent == inflictor ) { + scale = inflictorScale; + } else { + scale = 1.0f; + } + + if ( quake ) { + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); + } else { +// RAVEN BEGIN +// bdube: inflictor + RadiusPushClipModel( (idEntity*)inflictor, origin, scale * push, clipModel ); +// RAVEN END + } + } +} + +/* +============== +idGameLocal::RadiusPushClipModel +============== +*/ +// RAVEN BEGIN +// bdube: inflictor +void idGameLocal::RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ) { +// RAVEN END + int i, j; + float dot, dist, area; + const idTraceModel *trm; + const traceModelPoly_t *poly; + idFixedWinding w; + idVec3 v, localOrigin, center, impulse; + + trm = clipModel->GetTraceModel(); + if ( !trm || 1 ) { + impulse = clipModel->GetAbsBounds().GetCenter() - origin; + impulse.Normalize(); +// RAVEN BEGIN +// abahr: removed because z isn't always up + //impulse.z += 1.0f; + //impulse = idVec3( 0.0, 0.0, 1.0 ); + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), clipModel->GetOrigin(), push * impulse, true ); +// RAVEN END + return; + } + + localOrigin = ( origin - clipModel->GetOrigin() ) * clipModel->GetAxis().Transpose(); + for ( i = 0; i < trm->numPolys; i++ ) { + poly = &trm->polys[i]; + + center.Zero(); + for ( j = 0; j < poly->numEdges; j++ ) { + v = trm->verts[ trm->edges[ abs(poly->edges[j]) ].v[ INTSIGNBITSET( poly->edges[j] ) ] ]; + center += v; + v -= localOrigin; + v.NormalizeFast(); // project point on a unit sphere + w.AddPoint( v ); + } + center /= poly->numEdges; + v = center - localOrigin; + dist = v.NormalizeFast(); + dot = v * poly->normal; + if ( dot > 0.0f ) { + continue; + } + area = w.GetArea(); + // impulse in polygon normal direction + impulse = poly->normal * clipModel->GetAxis(); + // always push up for nicer effect + impulse.z -= 1.0f; + // scale impulse based on visible surface area and polygon angle + impulse *= push * ( dot * area * ( 1.0f / ( 4.0f * idMath::PI ) ) ); + // scale away distance for nicer effect + impulse *= ( dist * 2.0f ); + // impulse is applied to the center of the polygon + center = clipModel->GetOrigin() + center * clipModel->GetAxis(); + +// RAVEN BEGIN +// bdube: inflictor + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), center, impulse ); +// RAVEN END + } +} + +/* +=============== +idGameLocal::ProjectDecal +=============== +*/ +void idGameLocal::ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle ) { + float s, c; + idMat3 axis, axistemp; + idFixedWinding winding; + idVec3 windingOrigin, projectionOrigin; + + static idVec3 decalWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !g_decals.GetBool() ) { + return; + } + +// RAVEN BEGIN +// rjohnson: no decals on dedicated server + if ( isMultiplayer && !isClient && !isListenServer ) { + // no decals on dedicated server + return; + } +// RAVEN END + + // randomly rotate the decal winding + idMath::SinCos16( ( angle ) ? angle : random.RandomFloat() * idMath::TWO_PI, s, c ); + + // winding orientation + axis[2] = dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + windingOrigin = origin + depth * axis[2]; + if ( parallel ) { + projectionOrigin = origin - depth * axis[2]; + } else { + projectionOrigin = origin; + } + + size *= 0.5f; + + winding.Clear(); + winding += idVec5( windingOrigin + ( axis * decalWinding[0] ) * size, idVec2( 1, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[1] ) * size, idVec2( 0, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[2] ) * size, idVec2( 0, 0 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[3] ) * size, idVec2( 1, 0 ) ); + gameRenderWorld->ProjectDecalOntoWorld( winding, projectionOrigin, parallel, depth * 0.5f, declManager->FindMaterial( material ), time ); +} + +/* +============== +idGameLocal::BloodSplat +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple collision models +void idGameLocal::BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dirArg, float size, const char *material ) { + + float halfSize = size * 0.5f; + idVec3 verts[] = { idVec3( 0.0f, +halfSize, +halfSize ), + idVec3( 0.0f, +halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, +halfSize ) }; + idVec3 dir = dirArg; + idTraceModel trm; + idClipModel mdl; + trace_t results; + +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( !g_decals.GetBool() ) { + return; + } +// RAVEN END + + size = halfSize + random.RandomFloat() * halfSize; + trm.SetupPolygon( verts, 4 ); + mdl.LoadModel( trm, NULL ); + + // I don't want dir to be axis aligned, as it is more likely to streak them (because most architecture is axis aligned + dir.Set( dirArg.x*.1f, dirArg.y*.1f, -1 ); + dir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Translation( ent, results, origin, origin + dir * 72.0f, &mdl, mat3_identity, CONTENTS_SOLID, NULL ); +// RAVEN END + ProjectDecal( results.endpos, dir, 2.0f * size, true, size, material ); +} + +/* +============= +idGameLocal::SetCamera +============= +*/ +void idGameLocal::SetCamera( idCamera *cam ) { + int i; + idEntity *ent; + idAI *ai; + + // this should fix going into a cinematic when dead.. rare but happens + idPlayer *client = GetLocalPlayer(); + if ( client->health <= 0 || client->pfl.dead ) { + return; + } + + camera = cam; + if ( camera ) { +// RAVEN BEGIN +// bdube: tool support + inCinematic = false; + if( !( gameLocal.editors & ( EDITOR_MODVIEW | EDITOR_PLAYBACKS ) ) ) { + inCinematic = true; + } +// RAVEN END + + if ( skipCinematic && camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return; + } + + if ( time > cinematicStopTime ) { + cinematicSkipTime = time + CINEMATIC_SKIP_DELAY; + } + + // set r_znear so that transitioning into/out of the player's head doesn't clip through the view + cvarSystem->SetCVarFloat( "r_znear", 1.0f ); + + // hide all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + client = static_cast< idPlayer* >( entities[ i ] ); + client->EnterCinematic(); + } + } + + if ( !cam->spawnArgs.GetBool( "ignore_enemies" ) ) { + // kill any active monsters that are enemies of the player + for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->cinematic || ent->fl.isDormant ) { + // only kill entities that aren't needed for cinematics and aren't dormant + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAI::GetClassType() ) ) { +// RAVEN END + ai = static_cast( ent ); + if ( !ai->GetEnemy() || !ai->IsActive() ) { + // no enemy, or inactive, so probably safe to ignore + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + } else if ( ent->spawnArgs.GetBool( "cinematic_remove" ) ) { + // remove anything marked to be removed during cinematics + } else { + // ignore everything else + continue; + } + + // remove it + DPrintf( "removing '%s' for cinematic\n", ent->GetName() ); + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + } else { + inCinematic = false; + cinematicStopTime = time + msec; + + // restore r_znear + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + + // show all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + idPlayer *client = static_cast< idPlayer* >( entities[ i ] ); + client->ExitCinematic(); + } + } + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +============= +idGameLocal::GetPortalSky +============= +*/ +idCamera *idGameLocal::GetPortalSky( void ) const +{ + if( !portalSkyVisible ) { + + return( NULL ); + } + return( portalSky ); +} +/* +============= +idGameLocal::SetPortalSky +============= +*/ +void idGameLocal::SetPortalSky( idCamera *cam ) +{ + portalSky = cam; +} +// RAVEN END + +/* +============= +idGameLocal::GetCamera +============= +*/ +idCamera *idGameLocal::GetCamera( void ) const { + return camera; +} + +/* +============= +idGameLocal::SkipCinematic +============= +*/ +bool idGameLocal::SkipCinematic( void ) { + if ( camera ) { + if ( camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return false; + } + + if ( camera->spawnArgs.GetBool( "instantSkip" ) ) { + camera->Stop(); + return false; + } + } + + soundSystem->SetMute( true ); + if ( !skipCinematic ) { + skipCinematic = true; + cinematicMaxSkipTime = gameLocal.time + SEC2MS( g_cinematicMaxSkipTime.GetFloat() ); + } + + return true; +} + +// RAVEN BEGIN +/* +====================== +idGameLocal::StartViewEffect + +For passing in an effect triggered view effect +====================== +*/ +void idGameLocal::StartViewEffect( int type, float time, float scale ) +{ + idPlayer *player; + idPlayerView *view; + + player = GetLocalPlayer(); + if( player ) + { + view = &player->playerView; + + switch( type ) + { + case VIEWEFFECT_DOUBLEVISION: + view->SetDoubleVisionParms( time, scale ); + break; + + case VIEWEFFECT_SHAKE: + if( !gameLocal.isMultiplayer ) { + view->SetShakeParms( time, scale ); + } + break; + + case VIEWEFFECT_TUNNEL: + view->SetTunnelParms( time, scale ); + break; + + default: + gameLocal.Warning( "Invalid view effect" ); + break; + } + } +} + +/* +====================== +idGameLocal::GetPlayerView +====================== +*/ +void idGameLocal::GetPlayerView( idVec3 &origin, idMat3 &axis ) +{ + idPlayer *player; + renderView_t *view; + + player = GetLocalPlayer(); + if( player ) + { + view = player->GetRenderView(); + origin = view->vieworg; + axis = view->viewaxis; + } + else + { + origin = vec3_origin; + axis = mat3_identity; + } +} + +/* +====================== +idGameLocal::Translation + +small portion of physics required for the effects system +====================== +*/ +void idGameLocal::Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ) { + + if( !trm ) { + // HACK + clip[0]->Translation( trace, source, dest, NULL, mat3_identity, clipMask, NULL, NULL ); + } + else { + idClipModel cm; + + cm.LoadModel( *trm, NULL ); + // HACK + clip[0]->Translation( trace, source, dest, &cm, mat3_identity, clipMask, NULL, NULL ); + } +} + +/* +====================== +idGameLocal::SpawnClientMoveable +====================== +*/ +void idGameLocal::SpawnClientMoveable( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ) { + // find the debris def + const idDict* args = gameLocal.FindEntityDefDict( name, false ); + if ( !args ) { + return; + } + + // Ensure client moveables never last forever + if ( lifetime <= 0 ) { + lifetime = SEC2MS(args->GetFloat( "duration", "5" )); + } + int burn_time = idMath::ClampInt( 0, lifetime, SEC2MS(args->GetFloat( "burn_time", "2.5" )) ); + + // Spawn the debris + + rvClientMoveable* cent = NULL; + // force the args to spawn a rvClientMoveable + SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + return; + } + + cent->SetOrigin( origin ); + cent->SetAxis( axis ); + + cent->GetPhysics()->SetLinearVelocity( velocity ); + cent->GetPhysics()->SetAngularVelocity( angular_velocity ); + + if ( !burn_time ) { + //just disappear + cent->PostEventMS( &EV_Remove, lifetime ); + } else { + cent->PostEventMS( &CL_FadeOut, lifetime-burn_time, burn_time ); + } +} + +/* +====================== +idGameLocal::DebugSet +====================== +*/ +void idGameLocal::DebugSetString( const char* name, const char* value ) { + gameDebug.SetString( name, value ); +} +void idGameLocal::DebugSetFloat( const char* name, float value ) { + gameDebug.SetFloat( name, value ); +} +void idGameLocal::DebugSetInt( const char* name, int value ) { + gameDebug.SetInt( name, value ); +} + +/* +====================== +idGameLocal::DebugGetStat +====================== +*/ +const char* idGameLocal::DebugGetStatString ( const char* name ) { + return gameDebug.GetStatString( name ); +} + +int idGameLocal::DebugGetStatInt ( const char* name ) { + return gameDebug.GetStatInt( name ); +} + +float idGameLocal::DebugGetStatFloat ( const char* name ) { + return gameDebug.GetStatFloat( name ); +} + +/* +====================== +idGameLocal::IsDebugHudActive +====================== +*/ +bool idGameLocal::IsDebugHudActive ( void ) const { + return gameDebug.IsHudActive( DBGHUD_ANY ); +} + + +// rjohnson: added player info support for note taking system +/* +====================== +idGameLocal::GetPlayerInfo +====================== +*/ +bool idGameLocal::GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum, idAngles *deltaViewAngles, int reqClientNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if( reqClientNum != -1 ) { + idPlayer* reqClient = GetClientByNum( reqClientNum ); + if( reqClient && player ) { + if( reqClient->GetInstance() != player->GetInstance() ) { + return false; + } + } + } + + if ( !player ) { + return false; + } + + player->GetViewPos( origin, axis ); + origin = player->GetPhysics()->GetOrigin(); + + if ( deltaViewAngles ) { + *deltaViewAngles = player->GetDeltaViewAngles(); + } + + return true; +}; + +/* +====================== +idGameLocal::SetCurrentPlayerInfo +====================== +*/ +void idGameLocal::SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if ( !player ) { + return; + } + + player->Teleport( origin, axis.ToAngles(), NULL ); +// RAVEN BEGIN +// ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = player; + player->CalculateFirstPersonView(); + player->CalculateRenderView(); + currentThinkingEntity = NULL; +// RAVEN END + + return; +}; + +bool idGameLocal::PlayerChatDisabled( int clientNum ) { + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !entities[ clientNum ] ) { + return false; + } + + return !( ((idPlayer*)entities[ clientNum ])->isChatting || ((idPlayer*)entities[ clientNum ])->pfl.dead ); +} + +void idGameLocal::SetViewComments( const char *text ) { + idPlayer *player; + + player = GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( text ) { + player->hud->SetStateString( "viewcomments", text ); + player->hud->HandleNamedEvent( "showViewComments" ); + } + else { + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); + } +} + +/* +=================== +idGameLocal::GetNumGravityAreas +=================== +*/ +int idGameLocal::GetNumGravityAreas() const { + return gravityInfo.Num(); +} + +/* +=================== +idGameLocal::GetGravityInfo +=================== +*/ +const rvGravityArea* idGameLocal::GetGravityInfo( int index ) const { + return gravityInfo[ index ]; +} + +/* +=================== +idGameLocal::SetGravityArea +=================== +*/ +void idGameLocal::SetGravityInfo( int index, rvGravityArea* info ) { + gravityInfo[ index ] = info; +} + +/* +=================== +idGameLocal::AddUniqueGravityInfo +=================== +*/ +void idGameLocal::AddUniqueGravityInfo( rvGravityArea* info ) { + gravityInfo.AddUnique( info ); +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( const idVec3& origin ) const { + int numGravityAreas = GetNumGravityAreas(); + if( !numGravityAreas ) { + return -1; + } + + int areaNum = gameRenderWorld->PointInArea( origin ); + + for( int ix = 0; ix < numGravityAreas; ++ix ) { + if( !gameRenderWorld->AreasAreConnected(GetGravityInfo(ix)->GetArea(), areaNum, PS_BLOCK_GRAVITY) ) { + continue; + } + + return ix; + } + + return -1; +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( idEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const { + int index = GetCurrentGravityInfoIndex( origin ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(origin, axis, MASK_SOLID, NULL) : GetGravity(); +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( rvClientEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::ReferenceScriptObjectProxy +=================== +*/ +idEntity* idGameLocal::ReferenceScriptObjectProxy( const char* scriptObjectName ) { + idEntity* proxy = NULL; + idEntityPtr safeProxy; + idDict args; + idScriptObject* object = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + proxy = scriptObjectProxies[ ix ].GetEntity(); + assert( proxy ); + + object = &proxy->scriptObject; + if( !object->data ) { + object->SetType( scriptObjectName ); + proxy->ConstructScriptObject(); + return proxy; + } + } + + args.Set( "classname", "func_static" ); + args.Set( "scriptobject", scriptObjectName ); + args.SetBool( "noclipmodel", true ); + bool spawned = SpawnEntityDef(args, &proxy); + if ( !spawned ) { + assert( 0 ); + } + safeProxy = proxy; + scriptObjectProxies.AddUnique( safeProxy ); + return proxy; +} + +/* +=================== +idGameLocal::ReleaseScriptObjectProxy +=================== +*/ +void idGameLocal::ReleaseScriptObjectProxy( const char* proxyName ) { + idScriptObject* object = NULL; + idEntity* entity = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + entity = scriptObjectProxies[ ix ].GetEntity(); + if( entity && !idStr::Icmp(entity->GetName(), proxyName) ) { + object = &entity->scriptObject; + if( !object ) { + continue; + } + + entity->DeconstructScriptObject(); + object->Free(); + } + } +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +void idGameLocal::ListEntityStats( const idCmdArgs &args ) { + int i, j; + idStr currentMap; + idList uniqueMapNames; + + + for( i = 1; i < args.Argc(); i++ ) { + if ( idStr::Icmp( args.Argv( i ), "clear" ) == 0 ) { + entityUsageList.Clear(); + common->Printf("Entity stats cleared.\n"); + return; + } + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[ i ].SetInt( "reported_stat", false ); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) ) { + continue; + } + + entityUsageList[ i ].GetString( "mapFileName", "none", mapFileName ); + if ( currentMap != mapFileName ) + { + if ( i ) { + common->Printf( "\n" ); + } + common->Printf( "================ %s ================\n", mapFileName.c_str() ); + currentMap = mapFileName; + uniqueMapNames.Insert( mapFileName ); + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + if ( checkMapFileName != mapFileName ) { + break; + } + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 1 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + common->Printf( "\n" ); + common->Printf( "\n" ); + common->Printf( "================ OVERALL ================\n" ); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 2 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 2 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + idFile *FH = fileSystem->OpenFileWrite( "EntityStats.csv" ); + if ( FH ) { + int size = sizeof( int ) * uniqueMapNames.Num(); + int *count = ( int * )_alloca( size ); + + FH->Printf("\"Definition\""); + for( i = 0; i < uniqueMapNames.Num(); i++ ) { + FH->Printf( ",\"%s\"", uniqueMapNames[ i ].c_str() ); + } + FH->Printf(",Total\n"); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr className; + int total; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 3 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + + memset( count, 0, size ); + for( j = i; j < entityUsageList.Num(); j++ ) + { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 3 ); + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + + int loc = uniqueMapNames.FindIndex( checkMapFileName ); + if ( loc >= 0 ) { + count[ loc ]++; + } + } + } + + total = 0; + FH->Printf( "\"%s\"", className.c_str() ); + for( j = 0; j < uniqueMapNames.Num(); j++ ) { + FH->Printf( ",%d", count[ j ] ); + total += count[ j ]; + } + FH->Printf( ",%d\n", total ); + } + + fileSystem->CloseFile( FH ); + } +} +// RAVEN END + +/* +====================== +idGameLocal::SpreadLocations + +Now that everything has been spawned, associate areas with location entities +====================== +*/ +void idGameLocal::SpreadLocations() { + idEntity *ent; + +// RAVEN BEGIN + if( !gameRenderWorld ) { + common->Error( "GameRenderWorld is NULL!" ); + } +// RAVEN END + + // allocate the area table + int numAreas = gameRenderWorld->NumAreas(); + locationEntities = new idLocationEntity *[ numAreas ]; + memset( locationEntities, 0, numAreas * sizeof( *locationEntities ) ); + + // for each location entity, make pointers from every area it touches + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLocationEntity::GetClassType() ) ) { +// RAVEN END + continue; + } + idVec3 point = ent->spawnArgs.GetVector( "origin" ); + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Printf( "SpreadLocations: location '%s' is not in a valid area\n", ent->spawnArgs.GetString( "name" ) ); + continue; + } + if ( areaNum >= numAreas ) { + Error( "idGameLocal::SpreadLocations: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning( "location entity '%s' overlaps '%s'", ent->spawnArgs.GetString( "name" ), + locationEntities[areaNum]->spawnArgs.GetString( "name" ) ); + continue; + } + locationEntities[areaNum] = static_cast(ent); + + // spread to all other connected areas + for ( int i = 0 ; i < numAreas ; i++ ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(ent); + } + } + } +} + +/* +=================== +idGameLocal::AddLocation +=================== +*/ +idLocationEntity* idGameLocal::AddLocation( const idVec3& point, const char* name ) { + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Warning ( "idGameLocal::AddLocation: cannot add location entity '%s' at '%g %g %g'\n", name, point.x, point.y, point.z ); + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::AddLocation: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning ( "idGameLocal::AddLocation: location '%s' already exists at '%g %g %g'\n", locationEntities[areaNum]->GetName(), point.x, point.y, point.z ); + return NULL; + } + + // Spawn the new location entity + idDict args; + args.Set ( "location", name ); + locationEntities[areaNum] = static_cast(SpawnEntityType ( idLocationEntity::GetClassType(), &args )); + + // spread to all other connected areas + for ( int i = gameRenderWorld->NumAreas() - 1 ; i >= 0 ; i-- ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(locationEntities[areaNum]); + } + } + + return locationEntities[areaNum]; +} + +/* +=================== +idGameLocal::LocationForPoint + +The player checks the location each frame to update the HUD text display +May return NULL +=================== +*/ +idLocationEntity *idGameLocal::LocationForPoint( const idVec3 &point ) { + if ( !locationEntities ) { + // before SpreadLocations() has been called + return NULL; + } + + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::LocationForPoint: areaNum >= gameRenderWorld->NumAreas()" ); + } + + return locationEntities[ areaNum ]; +} + +/* +============ +idGameLocal::SetPortalState +============ +*/ +void idGameLocal::SetPortalState( qhandle_t portal, int blockingBits ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTAL ); + outMsg.WriteLong( portal ); + outMsg.WriteBits( blockingBits, NUM_RENDER_PORTAL_BITS ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + gameRenderWorld->SetPortalState( portal, blockingBits ); +} + +/* +============ +idGameLocal::sortSpawnPoints +============ +*/ +int idGameLocal::sortSpawnPoints( const void *ptr1, const void *ptr2 ) { + const spawnSpot_t *spot1 = static_cast( ptr1 ); + const spawnSpot_t *spot2 = static_cast( ptr2 ); + float diff; + + diff = spot1->dist - spot2->dist; + if ( diff < 0.0f ) { + return 1; + } else if ( diff > 0.0f ) { + return -1; + } else { + return 0; + } +} + +// RAVEN BEGIN +// ddynerman: new gametype specific spawn code +// TODO this should be moved to idMultiplayerGame +/* +=========== +idGameLocal::InitializeSpawns +randomize the order of the initial spawns +prepare for a sequence of initial player spawns +============ +*/ +void idGameLocal::InitializeSpawns( void ) { + idEntity* spot = NULL; + + // initialize the spawns for clients as well, need them for free fly demo replays + if ( !isMultiplayer ) { + return; + } + + spawnSpots.Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[i].Clear(); + } + + spot = FindEntityUsingDef( NULL, "info_player_team" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + spot = FindEntityUsingDef( NULL, "info_player_deathmatch" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + spawnSpots.Append ( static_cast(spot) ); + } + spot = FindEntityUsingDef( spot, "info_player_deathmatch" ); + } + + while( spot ) { + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { + // RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + if( IsFlagGameType() && ( teamSpawnSpots[ TEAM_STROGG ].Num() == 0 || teamSpawnSpots[ TEAM_MARINE ].Num() == 0 ) ) { + Error( "InitializeSpawns() - Map must have at least one Marine and one Strogg spawn for CTF gametype."); + } + + if( spawnSpots.Num() == 0 ) { + Error( "InitializeSpawns() - Map must have a spawn spot." ); + } + + common->Printf( "%d general spawns\n", spawnSpots.Num() ); + common->Printf( "%d team spawns (%d strogg/%d marine)\n", teamSpawnSpots[TEAM_STROGG].Num() + teamSpawnSpots[TEAM_MARINE].Num(), + teamSpawnSpots[TEAM_STROGG].Num(), teamSpawnSpots[TEAM_MARINE].Num()); +} + +/* +=========== +idGameLocal::UpdateForwardSpawn +ddynerman: Updates forward spawn lists +=========== +*/ +void idGameLocal::UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ) { + teamForwardSpawnSpots[ team ].Append( point ); +} + +/* +=========== +idGameLocal::ClearForwardSpawn +ddynerman: Clears forward spawn lists +=========== +*/ +void idGameLocal::ClearForwardSpawns( void ) { + for( int i = 0; i < TEAM_MAX; i++ ) { + teamForwardSpawnSpots[ i ].Clear(); + } +} + +/* +=========== +idGameLocal::SpotWouldTelefrag +=========== +*/ +bool idGameLocal::SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ) { + idPlayer* playerList[ MAX_CLIENTS ]; + idBounds bound = player->GetPhysics()->GetBounds(); + + bound.TranslateSelf( spawn->GetPhysics()->GetOrigin() ); + int numEntities = PlayersTouchingBounds( player, bound, CONTENTS_BODY, playerList, MAX_CLIENTS ); + + return !( numEntities == 0 ); +} + +/* +=========== +idGameLocal::SelectSpawnSpot +ddynerman: Selects a spawn spot randomly from spots furthest from the player + This is taken from q3 +=========== +*/ +idEntity* idGameLocal::SelectSpawnPoint( idPlayer* player ) { + if( !isMultiplayer ) { + idEntity* ent = FindEntityUsingDef( NULL, "info_player_start" ); + if ( !ent ) { + Error( "No info_player_start on map.\n" ); + } + return ent; + } + + if ( player == NULL ) { + return NULL; + } + + // Give spectators any old random spot + if ( player->team < 0 || player->team >= TEAM_MAX || player->spectating ) { + common->DPrintf("Returning a random spot\n"); + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; + } + + idList weightedSpawns; + idList* spawnArray = NULL; + + // Pick which spawns to use based on gametype +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_TOURNEY ) { + spawnArray = &spawnSpots; + } + else if( IsFlagGameType() || gameLocal.gameType == GAME_DEADZONE ) { + if( teamForwardSpawnSpots[ player->team ].Num() ) { + spawnArray = &teamForwardSpawnSpots[ player->team ]; + } else { + spawnArray = &teamSpawnSpots[ player->team ]; + } + } +// RITUAL END + + if ( spawnArray == NULL ) { + Error( "SelectSpawnPoint() - invalid spawn list." ); + return NULL; + } + + idVec3 refPos; + if ( player->lastKiller != NULL && !player->lastKiller->spectating && player->lastKiller->GetInstance() == player->GetInstance() ) { + refPos = player->lastKiller->GetPhysics()->GetOrigin(); + } else { + refPos = player->GetPhysics()->GetOrigin(); + } + + for ( int i = 0; i < spawnArray->Num(); i++ ) { + idPlayerStart* spot = (*spawnArray)[i]; + + if ( spot->GetInstance() != player->GetInstance() || SpotWouldTelefrag( player, spot ) ) { + continue; + } + + idVec3 pos = spot->GetPhysics()->GetOrigin(); + float dist = ( pos - refPos ).LengthSqr(); + + spawnSpot_t newSpot; + + newSpot.dist = dist; + newSpot.ent = (*spawnArray)[ i ]; + weightedSpawns.Append( newSpot ); + } + + if ( weightedSpawns.Num() == 0 ) { + // no spawns avaialable, spawn randomly + common->DPrintf("no spawns avaialable, spawn randomly\n"); + return (*spawnArray)[ random.RandomInt( spawnArray->Num() ) ]; + } + + qsort( ( void * )weightedSpawns.Ptr(), weightedSpawns.Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints ); + + int rnd = rvRandom::flrand( 0.0, 1.0 ) * (weightedSpawns.Num() / 2); + return weightedSpawns[ rnd ].ent; +} +/* +================ +idGameLocal::UpdateServerInfoFlags +================ +*/ +// RAVEN BEGIN +// ddynerman: new gametype strings +void idGameLocal::SetGameType( void ) { + gameType = GAME_SP; + + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "singleplayer" ) ) { + mpGame.SetGameType(); + } +} +// RAVEN END + +/* +================ +idGameLocal::SetGlobalMaterial +================ +*/ +void idGameLocal::SetGlobalMaterial( const idMaterial *mat ) { + globalMaterial = mat; +} + +/* +================ +idGameLocal::GetGlobalMaterial +================ +*/ +const idMaterial *idGameLocal::GetGlobalMaterial() { + return globalMaterial; +} + +/* +================ +idGameLocal::GetSpawnId +================ +*/ +int idGameLocal::GetSpawnId( const idEntity* ent ) const { + return ( gameLocal.spawnIds[ ent->entityNumber ] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameLocal::ThrottleUserInfo +================ +*/ +void idGameLocal::ThrottleUserInfo( void ) { + mpGame.ThrottleUserInfo(); +} + +/* +=========== +idGameLocal::ValidateServerSettings +============ +*/ +bool idGameLocal::ValidateServerSettings( const char* map, const char* gametype ) { + // PickMap uses si_map directly + // PickMap returns wether we would have to change the maps, which means settings are invalid + assert( !idStr::Icmp( si_map.GetString(), map ) ); + if ( mpGame.PickMap( gametype, true ) ) { + common->Printf( "map '%s' and gametype '%s' are not compatible\n", map, gametype ); + return false; + } + return true; +} + +/* +=========== +idGameLocal::NeedRestart +============ +*/ +bool idGameLocal::NeedRestart() { + + idDict newInfo; + const idKeyValue *keyval, *keyval2; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + + for ( int i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + return true; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { + return true; + } + } + return false; +} + +// RAVEN BEGIN +// jshepard: update player hud to alert to end of level +/* +=================== +idGameLocal::UpdateEndLevel +=================== +*/ +void idGameLocal::UpdateEndLevel() { + idPlayer * player = GetLocalPlayer(); + + if( player && player->GetHud() ) { + player->GetHud()->HandleNamedEvent( "showExit" ); + } +} + + +// bdube: added +/* +================ +idGameLocal::GetEffect + +Get the handle of the effect with the given name +================ +*/ +const idDecl *idGameLocal::GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType ) { + const char *effectFile = NULL; + + float chance = args.GetFloat ( idStr("effectchance ") + effectName, "1" ); + if ( random.RandomFloat ( ) > chance ) { + return NULL; + } + + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + assert( !idStr::Icmpn( effectName, "fx_", 3 ) ); + + if ( materialType ) { + idStr temp; + const char* result = NULL; + + temp = effectName; + temp += "_"; + temp += materialType->GetName(); + + // See if the given material effect is specified + if ( isMultiplayer ) { + idStr testMP = temp; + testMP += "_mp"; + + result = args.GetString( testMP ); + } + if ( !result || !*result ) { + result = args.GetString( temp ); + } + if ( result && *result) { + return( ( const idDecl * )declManager->FindEffect( result ) ); + } + } + + // grab the non material effect name + if ( isMultiplayer ) { + idStr testMP = effectName; + testMP += "_mp"; + + effectFile = args.GetString( testMP ); + } + + if ( !effectFile || !*effectFile ) { + effectFile = args.GetString( effectName ); + } + + if ( !effectFile || !*effectFile ) { + return NULL; + } + + return( ( const idDecl * )declManager->FindEffect( effectFile ) ); +} + +/* +================ +idGameLocal::PlayEffect + +Plays an effect at the given origin using the given direction +================ +*/ +rvClientEffect* idGameLocal::PlayEffect( + const idDecl *effect, + const idVec3& origin, + const idMat3& axis, + bool loop, + const idVec3& endOrigin, + bool broadcast, + bool predictBit, + effectCategory_t category, + const idVec4& effectTint ) { + + if ( !effect ) { + return NULL; + } + + if ( !gameLocal.isNewFrame ) { + return NULL; + } + + if ( isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idCQuat quat; + + quat = axis.ToCQuat(); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EFFECT ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( origin.x ); + msg.WriteFloat( origin.y ); + msg.WriteFloat( origin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + + // send to everyone who has start or end in it's PVS + SendUnreliableMessagePVS( msg, currentThinkingEntity, pvs.GetPVSArea( origin ), pvs.GetPVSArea( endOrigin ) ); + } + + if ( isServer && localClientNum < 0 ) { + // no effects on dedicated server + return NULL; + } + + if ( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } +// RAVEN END + + if ( gameLocal.isListenServer && currentThinkingEntity && gameLocal.GetLocalPlayer() ) { + if ( currentThinkingEntity->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( origin ); + clientEffect->SetAxis( axis ); + clientEffect->SetGravity( GetCurrentGravity( origin, axis ) ); + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +void idGameLocal::CheckPlayerWhizzBy( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ) +{ + //FIXME: make this client-side? Work in MP? + if ( gameLocal.isMultiplayer ) { + return; + } + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + if ( player->IsHidden() ) { + return; + } + if ( player == attacker ) { + return; + } + if ( player == hitEnt ) { + return; + } + idVec3 center = player->firstPersonViewOrigin; + idVec3 diff = end-center; + if ( diff.Length() < 64.0f ) { + //hit too close - didn't actually pass by, will hear impact sound instead + return; + } + idVec3 closestPoint = player->firstPersonViewOrigin; + if ( closestPoint.ProjectToLineSeg( start, end ) ) { + //on line seg + diff = closestPoint-center; + if ( diff.Length() < 48.0f ) { + //close enough to hear whizz-by + idVec3 dir = end-start; + dir.Normalize(); + idVec3 fxStart = closestPoint+dir*-32.0f; + idVec3 fxEnd = closestPoint+dir*32.0f; + player->PlayEffect( "fx_whizby", fxStart, player->firstPersonViewAxis, false, fxEnd ); + } + } +} + +/* +================ +idGameLocal::HitScan + +Run a hitscan trace from the given origin and direction +================ +*/ +idEntity* idGameLocal::HitScan( + const idDict& hitscanDict, + const idVec3& origOrigin, + const idVec3& origDir, + const idVec3& origFxOrigin, + idEntity* owner, + bool noFX, + float damageScale, +// twhitaker: added additionalIgnore parameter + idEntity* additionalIgnore, + int areas[ 2 ] + ) { + + idVec3 dir; + idVec3 origin; + idVec3 fxOrigin; + idVec3 fxDir; + idVec3 impulse; + idVec4 hitscanTint( 1.0f, 1.0f, 1.0f, 1.0f ); + int reflect; + float tracerChance; + idEntity* ignore; + float penetrate; + + if ( areas ) { + areas[ 0 ] = pvs.GetPVSArea( origFxOrigin ); + areas[ 1 ] = -1; + } + + ignore = owner; + penetrate = hitscanDict.GetFloat( "penetrate" ); + + if( hitscanDict.GetBool( "hitscanTint" ) && owner->IsType( idPlayer::GetClassType() ) ) { + hitscanTint = ((idPlayer*)owner)->GetHitscanTint(); + } + + // twhitaker: additionalIgnore parameter + if ( !additionalIgnore ) { + additionalIgnore = ignore; + } + + origin = origOrigin; + fxOrigin = origFxOrigin; + dir = origDir; + tracerChance = ((g_perfTest_weaponNoFX.GetBool())?0:hitscanDict.GetFloat( "tracerchance", "0" )); + + // Apply player powerups + if ( owner && owner->IsType( idPlayer::GetClassType() ) ) { + damageScale *= static_cast(owner)->PowerUpModifier(PMOD_PROJECTILE_DAMAGE); + } + + // Run reflections + for ( reflect = hitscanDict.GetFloat( "reflect", "0" ); reflect >= 0; reflect-- ) { + idVec3 start; + idVec3 end; + idEntity* ent; + idEntity* actualHitEnt; + trace_t tr; + int contents; + int collisionArea; + idVec3 collisionPoint; + bool tracer; + + // Calculate the end point of the trace + start = origin; + if ( g_perfTest_hitscanShort.GetBool() ) { + end = start + (dir.ToMat3() * idVec3(idMath::ClampFloat(0,2048,hitscanDict.GetFloat ( "range", "2048" )),0,0)); + } else { + end = start + (dir.ToMat3() * idVec3(hitscanDict.GetFloat ( "range", "40000" ),0,0)); + } + if ( g_perfTest_hitscanBBox.GetBool() ) { + contents = MASK_SHOT_BOUNDINGBOX|CONTENTS_PROJECTILE; + } else { + contents = MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE; + } + + // Loop the traces to handle cases where something can be shot through + while ( 1 ) { + // Trace to see if we hit any entities +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( hitscanDict.GetFloat( "trace_size", "0" ) > 0.0f ) + { + float range = hitscanDict.GetFloat ( "range", "1024" ); + if ( range > 4096.0f ) + { + assert( !(range > 4096.0f) ); + Warning( "idGameLocal::HitScan: hitscan def (%s) with trace_size must have max range of 4096!", hitscanDict.GetString( "classname" ) ); + range = idMath::ClampFloat( 0.0f, 4096.0f, range ); + } + end = start + (dir * range); + + idBounds traceBounds; + traceBounds.Zero(); + traceBounds.ExpandSelf( hitscanDict.GetFloat( "trace_size", "0" ) ); + // twhitaker: additionalIgnore parameter + TraceBounds( owner, tr, start, end, traceBounds, contents, additionalIgnore ); + } + else + { + // twhitaker: additionalIgnore parameter + TracePoint( owner, tr, start, end, contents, additionalIgnore ); + } + //gameRenderWorld->DebugArrow( colorRed, start, end, 10, 5000 ); +// RAVEN END + + // If the hitscan hit a no impact surface we can just return out + //assert( tr.c.material ); + if ( tr.fraction >= 1.0f || (tr.c.material && tr.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + PlayEffect( hitscanDict, "fx_path", fxOrigin, dir.ToMat3(), false, tr.endpos, false, EC_IGNORE, hitscanTint ); + if ( random.RandomFloat( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, dir.ToMat3(), false, tr.endpos ); + tracer = true; + } else { + tracer = false; + } + + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.endpos ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + + return NULL; + } + + // computing the collisionArea from the collisionPoint fails sometimes + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.c.point ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + collisionPoint = tr.c.point - ( tr.c.normal * tr.c.point - tr.c.dist ) * tr.c.normal; + ent = entities[ tr.c.entityNum ]; + actualHitEnt = NULL; + start = collisionPoint; + + // Keep tracing if we hit water + if ( (ent->GetPhysics()->GetContents() & CONTENTS_WATER) || (tr.c.material && (tr.c.material->GetContentFlags() & CONTENTS_WATER)) ) { + // Apply force to the water entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -(hitscanDict.GetFloat( "push", "5000" )) * tr.c.normal ); + // Continue on excluding water + contents &= (~CONTENTS_WATER); + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + continue; + // Reflect off a bounce target? + } else if ( (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) && !hitscanDict.GetBool ( "noBounce" ) ) { + reflect++; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->fl.takedamage && ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster( ); + } + + if ( !gameLocal.isClient ) { + + // Apply force to the entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -tr.c.normal, &hitscanDict ); + + // Handle damage to the entity + if ( ent->fl.takedamage && !(( tr.c.material != NULL ) && ( tr.c.material->GetSurfaceFlags() & SURF_NODAMAGE )) ) { + const char* damage; + + damage = NULL; + + // RAVEN BEGIN + // jdischler: code from the other project..to ensure that if an attached head is hit, the body will use the head joint + // otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(tr.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == tr.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } + // RAVEN END + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + + if ( damage && damage[0] ) { + // RAVEN BEGIN + // ddynerman: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, ((idPlayer*)owner)->GetCurrentWeapon() ); + } + // RAVEN END + ent->Damage( owner, owner, dir, damage, damageScale, hitJoint ); + } + + // Let the entity add its own damage effect + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect ( tr, dir, damage, owner ); + } + } else { + if ( actualHitEnt + && actualHitEnt != ent + && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + const char* damage = NULL; + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + if ( damage && damage[0] ) { + actualHitEnt->Damage( owner, owner, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( tr.c.id ) ); + } + } + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect( tr, dir, hitscanDict.GetString ( "def_damage" ), owner ); + } + } + } + + + // Pass through actors + if ( ent->IsType ( idActor::GetClassType() ) && penetrate > 0.0f ) { + start = collisionPoint; + additionalIgnore = ent; + damageScale *= penetrate; + continue; + } + break; + } + + // Path effect + fxDir = collisionPoint - fxOrigin; + fxDir.Normalize( ); + PlayEffect( hitscanDict, "fx_path", fxOrigin, fxDir.ToMat3(), false, collisionPoint, false, EC_IGNORE, hitscanTint ); + if ( !ent->fl.takedamage && random.RandomFloat ( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, fxDir.ToMat3(), false, collisionPoint ); + tracer = true; + } else { + tracer = false; + } + + if ( !reflect ) { + //on initial trace only + if ( hitscanDict.GetBool( "doWhizz" ) ) { + //play whizz-by sound if trace is close to player's head + CheckPlayerWhizzBy( origin, collisionPoint, ent, owner ); + } + } + + // Play a different effect when reflecting + if ( !reflect || ent->fl.takedamage ) { + idMat3 axis; + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType ( idActor::GetClassType() ) ) { + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + } else { + axis = tr.c.normal.ToMat3(); + } + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + // End of reflection + return ent; + } else { + PlayEffect( GetEffect( hitscanDict, "fx_reflect", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3() ); + } + + // Calc new diretion based on bounce + origin = start; + fxOrigin = start; + dir = ( dir - ( 2.0f * DotProduct( dir, tr.c.normal ) * tr.c.normal ) ); + dir.Normalize( ); + + // Increase damage scale on reflect + damageScale += hitscanDict.GetFloat( "reflect_powerup", "0" ); + } + + assert( false ); + + return NULL; +} + +/* +=================== +idGameLocal::RegisterClientEntity +=================== +*/ +void idGameLocal::RegisterClientEntity( rvClientEntity *cent ) { + int entityNumber; + + assert ( cent ); + + if ( clientSpawnCount >= ( 1 << ( 32 - CENTITYNUM_BITS ) ) ) { +// Error( "idGameLocal::RegisterClientEntity: spawn count overflow" ); + clientSpawnCount = INITIAL_SPAWN_COUNT; + } + + // Find a free entity index to use + while( clientEntities[firstFreeClientIndex] && firstFreeClientIndex < MAX_CENTITIES ) { + firstFreeClientIndex++; + } + + if ( firstFreeClientIndex >= MAX_CENTITIES ) { + cent->PostEventMS ( &EV_Remove, 0 ); + Warning( "idGameLocal::RegisterClientEntity: no free client entities" ); + return; + } + + entityNumber = firstFreeClientIndex++; + + // Add the client entity to the lists + clientEntities[ entityNumber ] = cent; + clientSpawnIds[ entityNumber ] = clientSpawnCount++; + cent->entityNumber = entityNumber; + cent->spawnNode.AddToEnd( clientSpawnedEntities ); + cent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( entityNumber >= num_clientEntities ) { + num_clientEntities++; + } +} + +/* +=================== +idGameLocal::UnregisterClientEntity +=================== +*/ +void idGameLocal::UnregisterClientEntity( rvClientEntity* cent ) { + assert( cent ); + + // No entity number then it failed to register + if ( cent->entityNumber == -1 ) { + return; + } + + cent->spawnNode.Remove ( ); + cent->bindNode.Remove ( ); + + if ( clientEntities [ cent->entityNumber ] == cent ) { + clientEntities [ cent->entityNumber ] = NULL; + clientSpawnIds[ cent->entityNumber ] = -1; + if ( cent->entityNumber < firstFreeClientIndex ) { + firstFreeClientIndex = cent->entityNumber; + } + cent->entityNumber = -1; + } +} + +// RAVEN BEGIN +// ddynerman: idClip wrapper functions + +/* +=================== +idGameLocal::Translation +=================== +*/ +bool idGameLocal::Translation( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Translation( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } + + return false; +} + +/* +=================== +idGameLocal::Rotation +=================== +*/ +bool idGameLocal::Rotation( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::Motion +=================== +*/ +bool idGameLocal::Motion( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Motion( results, start, end, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + + +/* +=================== +idGameLocal::Contacts +=================== +*/ +int idGameLocal::Contacts( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contacts( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, passEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::Contents +=================== +*/ +int idGameLocal::Contents( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contents( start, mdl, trmAxis, contentMask, passEntity, touchedEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::TracePoint +=================== +*/ +bool idGameLocal::TracePoint( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TracePoint( results, start, end, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TraceBounds +=================== +*/ +bool idGameLocal::TraceBounds( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TraceBounds( results, start, end, bounds, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TranslationModel +=================== +*/ +void idGameLocal::TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationModel( results, start, end, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::RotationModel +=================== +*/ +void idGameLocal::RotationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->RotationModel( results, start, rotation, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::ContactsModel +=================== +*/ +int idGameLocal::ContactsModel( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContactsModel( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::ContentsModel +=================== +*/ +int idGameLocal::ContentsModel( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContentsModel( start, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::TranslationEntities +=================== +*/ +void idGameLocal::TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationEntities( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } +} + +/* +=================== +idGameLocal::GetModelContactFeature +=================== +*/ +bool idGameLocal::GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->GetModelContactFeature( contact, clipModel, winding ); + } + + return false; +} + +/* +=================== +idGameLocal::EntitiesTouchingBounds +=================== +*/ +int idGameLocal::EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->EntitiesTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::ClipModelsTouchingBounds +=================== +*/ +int idGameLocal::ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ClipModelsTouchingBounds( bounds, contentMask, clipModelList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::PlayersTouchingBounds +=================== +*/ +int idGameLocal::PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->PlayersTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::GetWorldBounds +=================== +*/ +const idBounds& idGameLocal::GetWorldBounds( const idEntity* ent ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if ( clipWorld ) { + return clipWorld->GetWorldBounds(); + } + + return clip[ 0 ]->GetWorldBounds(); +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +const idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) const { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::AddClipWorld +=================== +*/ +int idGameLocal::AddClipWorld( int id ) { + if( id >= clip.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = clip.Num(); i <= id; i++ ) { + clip.Append( NULL ); + } + } + + if( clip[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); +// RAVEN END + clip[ id ] = new idClip(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip[ id ]->Init(); + } + return id; +} + +/* +=================== +idGameLocal::RemoveClipWorld +=================== +*/ +void idGameLocal::RemoveClipWorld( int id ) { + assert( id >= 0 && id < clip.Num() ); + + clip[ id ]->Shutdown(); + delete clip[ id ]; + clip[ id ] = NULL; +} + +/* +=================== +idGameLocal::ShutdownInstances +=================== +*/ +void idGameLocal::ShutdownInstances( void ) { + if( gamestate == GAMESTATE_UNINITIALIZED ) { + return; + } + + instances.DeleteContents( true ); + + // free the trace model used for the defaultClipModel + idClip::FreeDefaultClipModel(); +} + +/* +=================== +idGameLocal::AddInstance +=================== +*/ +int idGameLocal::AddInstance( int id, bool deferPopulate ) { + if ( id == -1 ) { + id = instances.Num(); + } + + if ( id >= instances.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = instances.Num(); i <= id; i++ ) { + instances.Append( NULL ); + } + } + + if ( instances[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + instances[ id ] = new rvInstance( id, deferPopulate ); + RV_POP_HEAP(); +// RAVEN END + + if ( !deferPopulate ) { + // keep track of the high watermark + ServerSetEntityIndexWatermark( id ); + } + + common->DPrintf( "idGameLocal::AddInstance(): Adding instance %d\n", instances[ id ]->GetInstanceID() ); + } else { + common->DPrintf( "idGameLocal::AddInstance(): Instance %d already exists\n", instances[ id ]->GetInstanceID() ); + } + + // keep the min spawn index correctly set + ServerSetMinSpawnIndex(); + + return instances[ id ]->GetInstanceID(); +} + +/* +=================== +idGameLocal::RemoveInstance +=================== +*/ +void idGameLocal::RemoveInstance( int id ) { + delete instances[ id ]; + instances[ id ] = NULL; +} + +/* +=================== +idGameLocal::GetPlayerName +Returns the specified player name, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerName( int clientNum, char* name ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( name, gameLocal.GetUserInfo( clientNum )->GetString( "ui_name" ), 64 ); + name[ 63 ] = 0; +} + +/* +=================== +idGameLocal::GetPlayerClan +Returns the specified player clan, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerClan( int clientNum, char* clan ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( clan, gameLocal.GetUserInfo( clientNum )->GetString( "ui_clan" ), 64 ); + clan[ 63 ] = 0; +} + +/* +=================== +idGameLocal::SetFriend +=================== +*/ +void idGameLocal::SetFriend( int clientNum, bool isFriend ) { + if( !gameLocal.GetLocalPlayer() ) { + Warning( "idGameLocal::SetFriend() - SetFriend() called with NULL local player\n" ); + return; + } + + gameLocal.GetLocalPlayer()->SetFriend( clientNum, isFriend ); +} + +/* +=================== +idGameLocal::GetLongGametypeName +=================== +*/ +const char* idGameLocal::GetLongGametypeName( const char* gametype ) { + return mpGame.GetLongGametypeName( gametype ); +} + +void idGameLocal::Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ) { + int instance = 0; + + if ( args.Argc() > 1 ) { + instance = atoi( args.Argv( 1 ) ); + } + + if( gameLocal.instances[ instance ] ) { + gameLocal.instances[ instance ]->PrintMapNumbers(); + } +} + +void idGameLocal::Cmd_PrintSpawnIds_f( const idCmdArgs& args ) { + for( int i = 0; i < MAX_GENTITIES; i++ ) { + if( gameLocal.entities[ i ] ) { + gameLocal.Printf( "Spawn id %d: %d\n", i, gameLocal.spawnIds[ i ] ); + } + } +} + +/* +=============== +idGameLocal::GetDemoHud +=============== +*/ +idUserInterface *idGameLocal::GetDemoHud( void ) { + if ( !demo_hud ) { + demo_hud = uiManager->FindGui( "guis/hud.gui", true, false, true ); + assert( demo_hud ); + } + return demo_hud; +} + +/* +=============== +idGameLocal::GetDemoMphud +=============== +*/ +idUserInterface *idGameLocal::GetDemoMphud( void ) { + if ( !demo_mphud ) { + demo_mphud = uiManager->FindGui( "guis/mphud.gui", true, false, true ); + assert( demo_mphud ); + } + return demo_mphud; +} + +/* +=============== +idGameLocal::GetDemoCursor +=============== +*/ +idUserInterface *idGameLocal::GetDemoCursor( void ) { + if ( !demo_cursor ) { + demo_cursor = uiManager->FindGui( "guis/cursor.gui", true, false, true ); + assert( demo_cursor ); + } + return demo_cursor; +} + +/* +=============== +idGameLocal::IsTeamPowerups +=============== +*/ +bool idGameLocal::IsTeamPowerups( void ) { + if ( !serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + return false; + } + if ( !IsTeamGameType() ) { + return false; + } + return ( gameType != GAME_ARENA_CTF ); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +=================== +idGameLocal::FlushBeforelevelLoad +=================== +*/ +void idGameLocal::FlushBeforelevelLoad( void ) +{ + TIME_THIS_SCOPE( __FUNCLINE__); + +#ifndef _XENON + MapShutdown(); +#else + mpGame.Clear(); +#endif + for(int i = 0; i < aasNames.Num(); i++) + { + aasList[i]->Shutdown(); + } +} +#endif +// RAVEN END + +// dluetscher: moved the overloaded new/delete to sys_local.cpp and Game_local.cpp (from Heap.h) +// so that the tools.dll will link. +#if !defined(_XBOX) && (defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT)) + +#undef new +#undef delete +#undef Mem_Alloc +#undef Mem_Free + +#ifdef ID_DEBUG_MEMORY +void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p, "", 0 ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p, "", 0 ); +} + +#else // #ifdef ID_DEBUG_MEMORY + +void *operator new( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p ); +} +#endif // #else #ifdef ID_DEBUG_MEMORY +#endif // #if defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) +// RAVEN END diff --git a/src/game/Game_local.h b/src/game/Game_local.h new file mode 100644 index 0000000..15c7bf1 --- /dev/null +++ b/src/game/Game_local.h @@ -0,0 +1,1436 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_LOCAL_H__ +#define __GAME_LOCAL_H__ + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +inline void *operator new( size_t s ) { return Memory::Allocate(s); } +inline void operator delete( void *p ) { Memory::Free(p); } +inline void *operator new[]( size_t s ) { return Memory::Allocate(s); } +inline void operator delete[]( void *p ) { Memory::Free(p); } +#endif +// RAVEN END + +/* +=============================================================================== + + Local implementation of the public game interface. + +=============================================================================== +*/ + +#define LAGO_IMG_WIDTH 64 +#define LAGO_IMG_HEIGHT 64 +#define LAGO_WIDTH 64 +#define LAGO_HEIGHT 44 +#define LAGO_MATERIAL "textures/mptextures/lagometer" +#define LAGO_IMAGE "textures/mptextures/lagometer.tga" + +// if set to 1 the server sends the client PVS with snapshots and the client compares against what it sees +#ifndef ASYNC_WRITE_PVS + #define ASYNC_WRITE_PVS 0 +#endif + +extern idRenderWorld * gameRenderWorld; + +#include "../sys/AutoVersion.h" +// the "gameversion" client command will print this plus compile date +#define GAME_VERSION "baseQUAKE4-1" + +// classes used by idGameLocal +class idEntity; +class idActor; +class idPlayer; +class idCamera; +class idWorldspawn; +class idTestModel; +class idAAS; +class idAI; +// RAVEN BEGIN +// bdube: not using id effects +//class idSmokeParticles; +//class idEntityFx; +// bdube: client side entities +class rvInstance; +class rvClientEntity; +class rvClientModel; +class rvCTFAssaultPlayerStart; +class idPlayerStart; +// RAVEN END +class idTypeInfo; +class idProgram; +class idThread; +class idEditEntities; +class idLocationEntity; + +// RAVEN BEGIN +// dluetscher: reduced max clients for memory usage +#ifdef _XENON +#define MAX_CLIENTS 16 +#else +// RAVEN END +#define MAX_CLIENTS 32 +#endif + +#define GENTITYNUM_BITS 12 +#define MAX_GENTITIES (1<PostEventMS(&EV_Remove, 0); (p) = NULL; } +// RAVEN END + +//============================================================================ + +void gameError( const char *fmt, ... ); + +#include "gamesys/Event.h" +// RAVEN BEGIN +// bdube: added +#include "gamesys/State.h" +// RAVEN END +#include "gamesys/Class.h" +#include "gamesys/SysCvar.h" +#include "gamesys/SysCmds.h" +#include "gamesys/SaveGame.h" +#include "gamesys/DebugGraph.h" + +#include "script/Script_Program.h" + +#include "anim/Anim.h" + +#include "ai/AAS.h" + +#include "physics/Clip.h" +#include "physics/Push.h" + +#include "Pvs.h" + +#include "FreeView.h" + +//============================================================================ + +const int MAX_GAME_MESSAGE_SIZE = 8192; +const int MAX_ENTITY_STATE_SIZE = 512; +const int ENTITY_PVS_SIZE = ((MAX_GENTITIES+31)>>5); +// RAVEN BEGIN +// abahr: changed to NUM_PORTAL_ATTRIBUTES to take into account gravity +const int NUM_RENDER_PORTAL_BITS = NUM_PORTAL_ATTRIBUTES; +// RAVEN END + +typedef struct entityState_s { + int entityNumber; + idBitMsg state; + byte stateBuf[MAX_ENTITY_STATE_SIZE]; + struct entityState_s * next; +} entityState_t; + +typedef struct snapshot_s { + int sequence; + entityState_t * firstEntityState; + int pvs[ENTITY_PVS_SIZE]; + struct snapshot_s * next; +} snapshot_t; + +const int MAX_EVENT_PARAM_SIZE = 128; + +typedef struct entityNetEvent_s { + int spawnId; + int event; + int time; + int paramsSize; + byte paramsBuf[MAX_EVENT_PARAM_SIZE]; + struct entityNetEvent_s *next; + struct entityNetEvent_s *prev; +} entityNetEvent_t; + +enum { + GAME_RELIABLE_MESSAGE_SPAWN_PLAYER, + GAME_RELIABLE_MESSAGE_DELETE_ENT, + GAME_RELIABLE_MESSAGE_CHAT, + GAME_RELIABLE_MESSAGE_TCHAT, + GAME_RELIABLE_MESSAGE_DB, + GAME_RELIABLE_MESSAGE_KILL, + GAME_RELIABLE_MESSAGE_DROPWEAPON, + GAME_RELIABLE_MESSAGE_RESTART, + GAME_RELIABLE_MESSAGE_SERVERINFO, + GAME_RELIABLE_MESSAGE_CALLVOTE, + GAME_RELIABLE_MESSAGE_CASTVOTE, + GAME_RELIABLE_MESSAGE_STARTVOTE, + GAME_RELIABLE_MESSAGE_UPDATEVOTE, + GAME_RELIABLE_MESSAGE_PORTALSTATES, + GAME_RELIABLE_MESSAGE_PORTAL, + GAME_RELIABLE_MESSAGE_VCHAT, + GAME_RELIABLE_MESSAGE_STARTSTATE, + GAME_RELIABLE_MESSAGE_MENU, + GAME_RELIABLE_MESSAGE_EVENT, +// RAVEN BEGIN +// bdube: effect + GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND, +// ddynerman: death messages + GAME_RELIABLE_MESSAGE_DEATH, +// ddynerman: game state + GAME_RELIABLE_MESSAGE_GAMESTATE, +// ddynerman: game stat + GAME_RELIABLE_MESSAGE_STAT, +// asalmon: game stats for xenon + GAME_RELIABLE_MESSAGE_ALL_STATS, +// ddynerman: ingame awards + GAME_RELIABLE_MESSAGE_INGAMEAWARD, +// ddynerman: instances + GAME_RELIABLE_MESSAGE_SET_INSTANCE, +// shouchard: for voicechat + GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING, +// shouchard: for server admin + GAME_RELIABLE_MESSAGE_SERVER_ADMIN, +// shouchard: for voting + GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE, + GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE, +// mekberg: get ban list for server + GAME_RELIABLE_MESSAGE_GETADMINBANLIST, + GAME_RELIABLE_MESSAGE_PRINT +// jscott: for voice comms +// TTimo: implemented or not by the OS, the network protocol should not be affected + , + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST +// RAVEN END +}; + +enum { + GAME_UNRELIABLE_MESSAGE_EVENT, + GAME_UNRELIABLE_MESSAGE_EFFECT, + GAME_UNRELIABLE_MESSAGE_HITSCAN, + GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER +}; + +enum { + GAME_UNRELIABLE_RECORD_CLIENTNUM, + GAME_UNRELIABLE_RECORD_AREAS, + + GAME_UNRELIABLE_RECORD_COUNT +}; + +typedef enum { + GAMESTATE_UNINITIALIZED, // prior to Init being called + GAMESTATE_NOMAP, // no map loaded + GAMESTATE_STARTUP, // inside InitFromNewMap(). spawning map entities. + GAMESTATE_RESTART, // spawning map entities from an instance restart, but not fully restarting + GAMESTATE_ACTIVE, // normal gameplay + GAMESTATE_SHUTDOWN // inside MapShutdown(). clearing memory. +} gameState_t; + +typedef struct { + idPlayerStart *ent; + int dist; +} spawnSpot_t; + +//============================================================================ +class idEventQueue { +public: + typedef enum { + OUTOFORDER_IGNORE, + OUTOFORDER_DROP, + OUTOFORDER_SORT + } outOfOrderBehaviour_t; + + idEventQueue() : start( NULL ), end( NULL ) {} + + entityNetEvent_t * Alloc(); + void Free( entityNetEvent_t *event ); + void Shutdown(); + + void Init(); + void Enqueue( entityNetEvent_t* event, outOfOrderBehaviour_t oooBehaviour ); + entityNetEvent_t * Dequeue( void ); + entityNetEvent_t * RemoveLast( void ); + + entityNetEvent_t * Start( void ) { return start; } + +private: + entityNetEvent_t * start; + entityNetEvent_t * end; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc eventAllocator; +// RAVEN END +}; + +//============================================================================ + +template< class type > +class idEntityPtr { +public: + idEntityPtr(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + idEntityPtr & operator=( type *ent ); + + // synchronize entity pointers over the network + int GetSpawnId( void ) const { return spawnId; } + bool SetSpawnId( int id ); + bool UpdateSpawnId( void ); + + bool IsValid( void ) const; + type * GetEntity( void ) const; + int GetEntityNum( void ) const; + +// RAVEN BEGIN +// bdube: overloaded operators + idEntityPtr( type* ent ) { *this = ent; } + idEntityPtr& operator=( idEntityPtr& ent ) { *this = ent.GetEntity(); return *this; } + type * operator->( void ) const; + operator type *( void ) const; +// RAVEN END + +private: + int spawnId; +}; + +// RAVEN BEGIN +// abahr: forward declaration +class rvGravityArea; +// RAVEN END + +//============================================================================ +// ddynerman: moved MultiplayerGame.h down here, so it can use more stuff in Game_local (idEntityPtr) +#include "MultiplayerGame.h" + +//============================================================================ + +class idGameLocal : public idGame { +public: + idDict serverInfo; // all the tunable parameters, like numclients, etc + int numClients; // pulled from serverInfo and verified + idDict userInfo[MAX_CLIENTS]; // client specific settings + const usercmd_t *usercmds; // client input commands + idDict persistentPlayerInfo[MAX_CLIENTS]; + idEntity * entities[MAX_GENTITIES];// index to entities + int spawnIds[MAX_GENTITIES];// for use in idEntityPtr + int firstFreeIndex; // first free index in the entities array + int minSpawnIndex; // when spawning multiple instances, so nothing pollutes in between the instances + int num_entities; // current number <= MAX_GENTITIES + idHashIndex entityHash; // hash table to quickly find entities by name + idWorldspawn * world; // world entity + idLinkList spawnedEntities; // all spawned entities + idLinkList activeEntities; // all thinking entities (idEntity::thinkFlags != 0) + int numEntitiesToDeactivate;// number of entities that became inactive in current frame + bool sortPushers; // true if active lists needs to be reordered to place pushers at the front + bool sortTeamMasters; // true if active lists needs to be reordered to place physics team masters before their slaves + idDict persistentLevelInfo; // contains args that are kept around between levels + +// RAVEN BEGIN +// bdube: client entities + rvClientEntity * clientEntities[MAX_CENTITIES]; // index to client entities + int clientSpawnIds[MAX_CENTITIES]; // for use in idClientEntityPtr + idLinkList clientSpawnedEntities; // all client side entities + int num_clientEntities; // current number of client entities + int firstFreeClientIndex; // first free index in the client entities array + + int entityRegisterTime; +// RAVEN END + + // can be used to automatically effect every material in the world that references globalParms + float globalShaderParms[ MAX_GLOBAL_SHADER_PARMS ]; + + idRandom random; // random number generator used throughout the game + + idProgram program; // currently loaded script and data space + idThread * frameCommandThread; + + idPush push; // geometric pushing + idPVS pvs; // potential visible set + pvsHandle_t clientsPVS[MAX_CLIENTS];// PVS of multiplayer clients updated every frame + + idTestModel * testmodel; // for development testing of models +// RAVEN BEGIN +// bdube: not using id effects +// idEntityFx * testFx; // for development testing of fx +// RAVEN END + + // only set when an end level is activated, which will take over camera positioning + // and draw end-level guis, then + + idStr sessionCommand; // a target_sessionCommand can set this to return something to the session + + idMultiplayerGame mpGame; // handles rules for standard dm + + idEditEntities * editEntities; // in game editing + + int cinematicSkipTime; // don't allow skipping cinemetics until this time has passed so player doesn't skip out accidently from a firefight + int cinematicStopTime; // cinematics have several camera changes, so keep track of when we stop them so that we don't reset cinematicSkipTime unnecessarily + int cinematicMaxSkipTime; // time to end cinematic when skipping. there's a possibility of an infinite loop if the map isn't set up right. + bool inCinematic; // game is playing cinematic (player controls frozen) + bool skipCinematic; + + // are kept up to date with changes to serverInfo + int framenum; + int previousTime; // time in msec of last frame + int time; // in msec + int msec; // time since last update in milliseconds + int mHz; // hertz + + int vacuumAreaNum; // -1 if level doesn't have any outside areas + +// RAVEN BEGIN +// abahr: + idList gravityInfo; // area num for each gravity zone + idList< idEntityPtr > scriptObjectProxies; +// RAVEN END + + gameType_t gameType; + bool isMultiplayer; // set if the game is run in multiplayer mode + bool isServer; // set if the game is run for a dedicated or listen server + bool isClient; // set if the game is run for a client + // discriminates between the RunFrame path and the ClientPrediction path + // NOTE: on a listen server, isClient is false +// RAVEN BEGIN +// ddynerman: set if we're a server and not dedicated + bool isListenServer; +// RAVEN END + int localClientNum; // number of the local client. MP: -1 on a dedicated, MAX_CLIENTS when playing a server demo + idLinkList snapshotEntities; // entities from the last snapshot + int realClientTime; // real client time + bool isNewFrame; // true if this is a new game frame, not a rerun due to prediction + int entityDefBits; // bits required to store an entity def number + + static const char * sufaceTypeNames[ MAX_SURFACE_TYPES ]; // text names for surface types + + idEntityPtr lastGUIEnt; // last entity with a GUI, used by Cmd_NextGUI_f + int lastGUI; // last GUI on the lastGUIEnt + +// RAVEN BEGIN +// bdube: added + int editors; // Mirrored editors flags from common determine which editors are running + bool isLastPredictFrame; // on an MP server or in SP game this means 'last catchup frame' rather than predict +// RAVEN END + +// RAVEN BEGIN +// rjohnson: entity usage stats + idStr mapFileNameStripped; // name of the map, empty string if no map loaded, with path and extension removed. If entity filter, that is appended + idList entityUsageList; +// ddynerman: the entity currently thinking, used to play effects/etc only in the appropriate instance + idEntity* currentThinkingEntity; + + const static int INITIAL_SPAWN_COUNT = 1; + + idFreeView freeView; + +// RAVEN END + + int filterMod; + idList modList; + + // ---------------------- Public idGame Interface ------------------- + + idGameLocal(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + virtual void Init( void *(*allocator)( size_t size ), void (*deallocator)( void *ptr ), size_t (*msize)( void *ptr ) ); +#else + virtual void Init( void ); +#endif +// RAVEN END + virtual void Shutdown( void ); + virtual void SetLocalClient( int clientNum ); + virtual void ThrottleUserInfo( void ); + virtual const idDict * SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ); + virtual const idDict * GetUserInfo( int clientNum ); + virtual bool IsClientActive( int clientNum ); + virtual void SetServerInfo( const idDict &serverInfo ); + + virtual const idDict * RepeaterSetUserInfo( int clientNum, const idDict &userInfo ) { assert(false); return NULL; } + + virtual const idDict & GetPersistentPlayerInfo( int clientNum ); + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ); + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randSeed ); + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ); +// RAVEN BEGIN +// mekberg: added saveTypes + virtual void SaveGame( idFile *saveGameFile, saveType_t saveType = ST_REGULAR ); +// RAVEN END + virtual void MapShutdown( void ); + virtual void CacheDictionaryMedia( const idDict *dict ); + virtual void SpawnPlayer( int clientNum ); +// RAVEN BEGIN + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ); + virtual void MenuFrame( void ); +// RAVEN END + virtual void RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int spoolTime = 0 ) {}; + virtual bool Draw( int clientNum ); + virtual escReply_t HandleESC( idUserInterface **gui ); + virtual idUserInterface *StartMenu( void ); + virtual const char * HandleGuiCommands( const char *menuCommand ); + virtual void HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ); + virtual allowReply_t ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ); + virtual void ServerClientConnect( int clientNum, const char *guid ); + virtual void ServerClientBegin( int clientNum ); + virtual void ServerClientDisconnect( int clientNum ); + virtual void ServerWriteInitialReliableMessages( int clientNum ); + virtual allowReply_t RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ) { idStr::Copynz( reason, "#str_107239" /* zinx - FIXME - not banned... */, sizeof(reason) ); return ALLOW_NO; }; + virtual void RepeaterClientConnect( int clientNum ) {assert(false);}; + virtual void RepeaterClientBegin( int clientNum ) {assert(false);}; + virtual void RepeaterClientDisconnect( int clientNum ) {assert(false);}; + virtual void RepeaterWriteInitialReliableMessages( int clientNum ) {assert(false);}; +// RAVEN BEGIN +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + virtual void ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ); +// RAVEN END + virtual bool ServerApplySnapshot( int clientNum, int sequence ); + virtual void ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual bool RepeaterApplySnapshot( int clientNum, int sequence ) { assert(false); return false; } + virtual void RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ) { assert(false); } + virtual void ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ); + virtual bool ClientApplySnapshot( int clientNum, int sequence ); + virtual void ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual gameReturn_t ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame = true, ClientStats_t *cs = NULL ); +// RAVEN BEGIN +// ddynerman: client game frame + virtual void ClientRun( void ); + virtual void ClientEndFrame( void ); + +// jshepard: rcon password check + virtual void ProcessRconReturn( bool success ); + virtual void ResetRconGuiStatus( void ); +// RAVEN END + + virtual void GetClientStats( int clientNum, char *data, const int len ); + virtual void SwitchTeam( int clientNum, int team ); + + virtual bool DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ); + + virtual bool HTTPRequest( const char *IP, const char *file, bool isGamePak ); + +// RAVEN BEGIN +// bdube: client hitscan + virtual void ClientHitScan( const idBitMsg &msg ); +// jscott: for effects system + virtual void StartViewEffect( int type, float time, float scale ); + virtual void GetPlayerView( idVec3 &origin, idMat3 &axis ); + virtual void Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ); + virtual void SpawnClientMoveable ( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ); +// bdube: added debug methods + virtual void DebugSetString ( const char* name, const char* value ); + virtual void DebugSetFloat ( const char* name, float value ); + virtual void DebugSetInt ( const char* name, int value ); + virtual const char* DebugGetStatString ( const char* name ); + virtual int DebugGetStatInt ( const char* name ); + virtual float DebugGetStatFloat ( const char* name ); + virtual bool IsDebugHudActive ( void ) const; +// rjohnson: for new note taking mechanism + virtual bool GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL, int reqClientNum = -1 ); + virtual void SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1 ); + virtual bool PlayerChatDisabled( int clientNum ); + virtual void SetViewComments( const char *text = 0 ); +// ddynerman: utility functions + virtual void GetPlayerName( int clientNum, char* name ); + virtual void GetPlayerClan( int clientNum, char* clan ); + virtual void SetFriend( int clientNum, bool isFriend ); + static void Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ); + static void Cmd_PrintSpawnIds_f( const idCmdArgs& args ); +// abahr: + virtual int GetNumGravityAreas() const; + virtual const rvGravityArea* GetGravityInfo( int index ) const; + virtual void SetGravityInfo( int index, rvGravityArea* info ); + virtual void AddUniqueGravityInfo( rvGravityArea* info ); + + virtual int GetCurrentGravityInfoIndex( const idVec3& origin ) const; + virtual bool InGravityArea( idEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( idEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( idEntity* entity ) const; + + virtual const idVec3 GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const; + + virtual bool InGravityArea( rvClientEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( rvClientEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( rvClientEntity* entity ) const; + virtual idEntity* ReferenceScriptObjectProxy( const char* scriptObjectName ); + virtual void ReleaseScriptObjectProxy( const char* proxyName ); + +// rjohnson: entity usage stats + virtual void ListEntityStats( const idCmdArgs &args ); +// RAVEN END + + virtual void SetDemoState( demoState_t state, bool serverDemo, bool timeDemo ); + virtual void SetRepeaterState( bool isRepeater, bool serverIsRepeater ) {if (isRepeater || serverIsRepeater) Warning("Repeater does not work for single player.");}; + virtual void WriteNetworkInfo( idFile* file, int clientNum ); + virtual void ReadNetworkInfo( int gameTime, idFile* file, int clientNum ); + virtual bool ValidateDemoProtocol( int minor_ref, int minor ); + + virtual void ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ); + virtual void ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ); + + virtual void RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ) {assert(false);}; + virtual void RepeaterEndSnapshots( void ) {}; + virtual void ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ) {assert(false);}; + + virtual int GetDemoFollowClient( void ) { return serverDemo ? followPlayer : -1; } + + virtual void GetBotInput( int clientNum, usercmd_t &userCmd ) { Error( "Bot input requested\n" ); }; + + virtual const char * GetLoadingGui( const char *mapDeclName ) { return NULL; } + virtual void SetupLoadingGui( idUserInterface *gui ) {} + + // ---------------------- Public idGameLocal Interface ------------------- + + void Printf( const char *fmt, ... ) const; + void DPrintf( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + void DWarning( const char *fmt, ... ) const; + void Error( const char *fmt, ... ) const; + + // Initializes all map variables common to both save games and spawned games + void LoadMap( const char *mapName, int randseed ); + + void LocalMapRestart( int instance = -1 ); + void MapRestart( int instance = -1 ); + static void VerifyServerSettings_f( const idCmdArgs &args ); + static void MapRestart_f( const idCmdArgs &args ); + bool NextMap( void ); // returns wether serverinfo settings have been modified + static void NextMap_f( const idCmdArgs &args ); + + idMapFile * GetLevelMap( void ); + const char * GetMapName( void ) const; + + int NumAAS( void ) const; + idAAS * GetAAS( int num ) const; + idAAS * GetAAS( const char *name ) const; +// RAVEN BEGIN +// jscott: added accessor for memory tracking + int GetNumAAS( void ) const { return( aasList.Num() ); } +// RAVEN END + void SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ); + aasHandle_t AddAASObstacle( const idBounds &bounds ); + void RemoveAASObstacle( const aasHandle_t handle ); + void RemoveAllAASObstacles( void ); +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. + size_t GetEntityMemoryUsage ( void ) const; +// RAVEN END + bool CheatsOk( bool requirePlayer = true ); + void SetSkill( int value ); + gameState_t GameState( void ) const; + void SetGameState( gameState_t newState ) { gamestate = newState; } + idEntity * SpawnEntityType( const idTypeInfo &classdef, const idDict *args = NULL, bool bIsClientReadSnapshot = false ); + bool SpawnEntityDef( const idDict &args, idEntity **ent = NULL, bool setDefaults = true ); + bool SpawnClientEntityDef( const idDict &args, rvClientEntity **ent = NULL, bool setDefaults = true, const char* spawn = NULL ); +// abahr: + idEntity* SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + template< class type > + type* SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + int GetPreviousTime() const { return previousTime; } +// RAVEN END + int GetSpawnId( const idEntity *ent ) const; + + const idDeclEntityDef * FindEntityDef( const char *name, bool makeDefault = true ) const; + const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + + void RegisterEntity( idEntity *ent ); + void UnregisterEntity( idEntity *ent ); + // used to skip one when registering entities, leaving an empty entity in the array + void SkipEntityIndex( void ); + + bool RequirementMet( idEntity *activator, const idStr &requires, int removeItem ); + +// RITUAL BEGIN +// squirrel: accessor for si_weaponStay checks + bool IsWeaponsStayOn( void ); +// RITUAL END + +// RAVEN BEGIN +// bdube: client entities + void RegisterClientEntity( rvClientEntity *cent ); + void UnregisterClientEntity( rvClientEntity *cent ); +// RAVEN END + + void AlertAI( idEntity *ent ); +// RAVEN BEGIN +// bdube: added get alert actor + idActor * GetAlertActor( void ); + idEntity * GetAlertEntity( void ); +// RAVEN END + + + bool InPlayerPVS( idEntity *ent ) const; + bool InPlayerConnectedArea( idEntity *ent ) const; + + void SetCamera( idCamera *cam ); + idCamera * GetCamera( void ) const; + bool SkipCinematic( void ); + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *GetPortalSky( void ) const; + void SetPortalSky( idCamera *cam ); +// RAVEN END + + void CalcFov( float base_fov, float &fov_x, float &fov_y ) const; + + void AddEntityToHash( const char *name, idEntity *ent ); + bool RemoveEntityFromHash( const char *name, idEntity *ent ); + int GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const; + + // returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. + idEntity * GetTraceEntity( const trace_t &trace ) const; + + static void ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ); + idEntity * FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const; + static void ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ); + +//RAVEN BEGIN +// bgeisler: added, I don't want to have to do this work myself every single time I have an entityNumber + idEntity * FindEntity( int entityNumber ) { return ((entityNumber >= 0 && entityNumber < MAX_GENTITIES) ? entities[entityNumber] : NULL); } +//RAVEN BEGIN + + idEntity * FindEntity( const char *name ) const; + idEntity * FindEntityUsingDef( idEntity *from, const char *match ) const; + int EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const; + + void KillBox( idEntity *ent, bool catch_teleport = false ); + void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); +// RAVEN BEGIN +// ddynerman: return number of people damaged + void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f, int* hitCount = NULL ); +// bdube: inflictor + void RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ); +// RAVEN END + + void ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle = 0 ); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + void BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dir, float size, const char *material ); +// RAVEN END + + void CallFrameCommand( idEntity *ent, const function_t *frameCommand ); +// RAVEN BEGIN +// bdube: added script object frame commands + void CallFrameCommand( idScriptObject* obj, const function_t* frameCommand ); + void CallFrameCommand( idEntity* ent, const char* frameCommand ); +// RAVEN END + + void CallObjectFrameCommand( idEntity *ent, const char *frameCommand ); + + const idVec3 & GetGravity( void ) const; + + // added the following to assist licensees with merge issues + int GetFrameNum() const { return framenum; } + int GetTime() const { return time; } + int GetMSec() const { return msec; } + int GetMHz() const { return mHz; } + + int GetNextClientNum( int current ) const; + idPlayer * GetClientByNum( int current ) const; + idPlayer * GetClientByName( const char *name ) const; + idPlayer * GetClientByCmdArgs( const idCmdArgs &args ) const; + int GetClientNumByName( const char *name ) const; + + idPlayer * GetLocalPlayer() const; + +// RAVEN BEGIN +// jshepard: update player data after main menu close + void UpdatePlayerPostMainMenu(); + +// bdube: added + int GetSpawnCount ( void ) const; + void SetSpawnCount ( int newSpawnCount ) { spawnCount = newSpawnCount; } +// ddynerman: team type + bool IsTeamGame ( void ) const; +// RAVEN END + + void SpreadLocations(); + idLocationEntity * LocationForPoint( const idVec3 &point ); // May return NULL +// RAVEN BEGIN +// bdube: added + idLocationEntity* AddLocation ( const idVec3& point, const char* name ); +// ddynerman: new gametype specific spawn code + bool SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ); + idEntity* SelectSpawnPoint( idPlayer* player ); + void UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ); + void ClearForwardSpawns( void ); +// RAVEN END + + void SetPortalState( qhandle_t portal, int blockingBits ); + void ServerSendChatMessage( int to, const char *name, const char *text, const char *parm = "" ); + + void SetGlobalMaterial( const idMaterial *mat ); + const idMaterial * GetGlobalMaterial(); + + void SetGibTime( int _time ) { nextGibTime = _time; } + int GetGibTime() { return nextGibTime; } +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + void SetUnFreezeTime( int _time ) { unFreezeTime = _time; }; + int GetUnFreezeTime() { return unFreezeTime; }; + void SetIsFrozen( bool _isFrozen ) { isFrozen = _isFrozen; }; + bool GetIsFrozen() { return isFrozen; }; +// RITUAL END + bool NeedRestart(); + +// RAVEN BEGIN +// jshepard: update end of level on player hud + void UpdateEndLevel(); +// MCG: added whizz-by sound + void CheckPlayerWhizzBy ( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ); +// bdube: added hitscan +// twhitaker: added additionalIgnore parameter + idEntity* HitScan ( const idDict& hitscanDef, const idVec3& origin, const idVec3& dir, const idVec3& fxOrigin, idEntity* owner = NULL, bool noFX = false, float damageScale = 1.0f, idEntity * additionalIgnore = NULL, int *areas = NULL ); +// bdube: added effect calls + virtual rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + const idDecl *GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType = NULL ); + + idList ambientLights; // lights that cast ambient + +// ddynerman: multiple collision world - game collision wrapper functions to +// use the correct idClip +// --------------------------------------------------------------- +// These are wrapper functions around idClip collision detection +// functions. They expose the collision detection engine to the +// game code, but do collision world determination in one spot. +// 'ent' refers to the entity we want collision information about + bool Translation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + bool Rotation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contents ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); + // special case translations versus the rest of the world + bool TracePoint ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ); + bool TraceBounds ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ); + // clip versus a specific model + void TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // clip versus all entities but not the world + void TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + // get a contact feature + bool GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + int PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; + const idBounds & GetWorldBounds( const idEntity* ent ) const; + + void Link( idClipModel* clip, idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); + + idClip* GetEntityClipWorld( const idEntity* ent ); + const idClip* GetEntityClipWorld( const idEntity* ent ) const; + int GetNumMapEntities( void ) const; + + int AddClipWorld( int id ); + void RemoveClipWorld( int id ); + int AddInstance( int id = -1, bool deferPopulate = false ); + void RemoveInstance( int id ); + rvInstance* GetInstance( int id ); + int GetNumInstances( void ); +// ddynerman: multiple game instances + void SpawnMapEntities( int instance = 0, unsigned short* entityNumIn = NULL, unsigned short* entityNumOut = NULL, int* startSpawnCount = NULL ); + void InstanceClear( void ); +// ddynerman: utility function + virtual const char* GetLongGametypeName( const char* gametype ); + virtual void ReceiveRemoteConsoleOutput( const char* output ); + + bool IsFlagGameType( void ) { return ( gameType == GAME_CTF || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_ARENA_1F_CTF ); } + bool IsTeamGameType( void ) { return ( gameType == GAME_TDM || gameType == GAME_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ); } + bool IsTeamPowerups( void ); + + // twhitaker: needed this for difficulty settings + float GetDifficultyModifier( void ) { const static float difficulty[] = { -0.3f, 0.0f, 0.4f, 0.8f }; return difficulty[ idMath::ClampInt( 0, 3, g_skill.GetInteger() ) ]; } + + bool IsMultiplayer( void ) { return isMultiplayer; } + +// mekberg: added + bool InCinematic( void ) { return inCinematic; } + + // mekberg: so ban list can be populated outside of multiplayer game + void PopulateBanList( idUserInterface* hud ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushBeforelevelLoad( void ); +#endif +// RAVEN END + + void ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ); + void ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ); + + void SendUnreliableMessage( const idBitMsg &msg, const int clientNum ); + // note: local client on dedicated server is always excluded + void SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1 = -1, int area2 = -1 ); + + demoState_t GetDemoState( void ) const { return demoState; } + bool IsServerDemo( void ) const { return serverDemo; } + bool IsTimeDemo( void ) const { return timeDemo; } + int GetDemoFollowClient( void ) const { return serverDemo ? followPlayer : -1; } + idUserInterface *GetDemoHud( void ); + idUserInterface *GetDemoMphud( void ); + idUserInterface *GetDemoCursor( void ); + + /* + do not synchronize implicit decls over the network + implicit decls are created when loading sounds and materials that don't have an explicit entry in the def files + clients and server may load those in different orders in a same map, or even join in with different orders because of different map histories before this game + in D3 we maintain remap tables, but it's much better to have tighter multiplayer assets so we have no need at all + still, you want to catch when bad things happen, so indexes should ALL be read and written through these functions + */ + static void WriteDecl( idBitMsg &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsg &msg, declType_t type ); + static void WriteDecl( idBitMsgDelta &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsgDelta &msg, declType_t type ); + + idPlayerStart *RandomSpawn( void ); + + int GetStartingIndexForInstance( int instanceID ); + void ClientSetStartingIndex( int i ) { clientInstanceFirstFreeIndex = i; } + void ServerSetMinSpawnIndex( void ); + void ServerSetEntityIndexWatermark( int instanceID ); + +private: +// RAVEN BEGIN +// ddynerman: multiple instance for MP + idList clip; // collision detection + idList instances; +// RAVEN END + + // keep watermarks on the high entity index + // server transmits this to clients so they use the right entity layout + idList instancesEntityIndexWatermarks; + int clientInstanceFirstFreeIndex; + + idStr mapFileName; // name of the map, empty string if no map loaded + idMapFile * mapFile; // will be NULL during the game unless in-game editing is used + bool mapCycleLoaded; + + int spawnCount; + bool isMapEntity[ MAX_GENTITIES ]; // it's handy to know which entities are part of the map +// RAVEN BEGIN +// bdube: client entities + int clientSpawnCount; +// RAVEN END + + idLocationEntity ** locationEntities; // for location names, etc + + idCamera * camera; + const idMaterial * globalMaterial; // for overriding everything + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *portalSky; + bool portalSkyVisible; +// RAVEN END + + idList aasList; // area system + idStrList aasNames; + +// RAVEN BEGIN +// bdube: GetAlertActor + idEntityPtr lastAIAlertActor; + int lastAIAlertActorTime; + idEntityPtr lastAIAlertEntity; + int lastAIAlertEntityTime; +// RAVEN END + + idDict spawnArgs; // spawn args used during entity spawning FIXME: shouldn't be necessary anymore +// RAVEN BEGIN +// nmckenzie: + const idDeclEntityDef * spawnOverrides; +// RAVEN END + + pvsHandle_t playerPVS; // merged pvs of all players + bool freePlayerPVS; // tracks if playerPVS needs to be released + pvsHandle_t playerConnectedAreas; // all areas connected to any player area + + idVec3 gravity; // global gravity vector + gameState_t gamestate; // keeps track of whether we're spawning, shutting down, or normal gameplay + bool influenceActive; // true when a phantasm is happening + int nextGibTime; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int unFreezeTime; // time at which players unfreeze and the match begins + bool isFrozen; // true if the match is frozen (for buying, etc.) +// RITUAL END + + entityState_t * clientEntityStates[MAX_CLIENTS+1][MAX_GENTITIES]; // MAX_CLIENTS slot is for server demo recordings + int clientPVS[MAX_CLIENTS+1][ENTITY_PVS_SIZE]; + snapshot_t * clientSnapshots[MAX_CLIENTS+1]; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc entityStateAllocator; + idBlockAlloc snapshotAllocator; +// RAVEN END + + idEventQueue eventQueue; + + idList spawnSpots; +// RAVEN BEGIN +// ddynerman: two lists to hold team spawn points for team based games + idList teamSpawnSpots[TEAM_MAX]; + idList teamForwardSpawnSpots[TEAM_MAX]; // forward spawn positions, used in CTF +// RAVEN END + + idDict newInfo; + + idStrList shakeSounds; + + byte lagometer[ LAGO_IMG_HEIGHT ][ LAGO_IMG_WIDTH ][ 4 ]; + + idMsgQueue unreliableMessages[ MAX_CLIENTS+1 ]; // MAX_CLIENTS slot for server demo recording + + demoState_t demoState; + bool serverDemo; + bool timeDemo; + + // demo interaction usercmds + usercmd_t usercmd, oldUsercmd; + int followPlayer; // free fly or spectate follow through local interaction during server demo replays + idUserInterface *demo_hud; + idUserInterface *demo_mphud; + idUserInterface *demo_cursor; + + int demo_protocol; // keep track of the protocol of the demo we're replaying + +private: + + void Clear( void ); + // returns true if the entity shouldn't be spawned at all in this game type or difficulty level + bool InhibitEntitySpawn( idDict &spawnArgs ); + // spawn entities from the map file + // commons used by init, shutdown, and restart + void MapPopulate( int instance = -1 ); + void MapClear( bool clearClients, int instance = -1 ); + + bool SetupPortalSkyPVS( idPlayer *player ); +// RAVEN END + void SetupPlayerPVS( void ); + void FreePlayerPVS( void ); + void UpdateGravity( void ); + void SortActiveEntityList( void ); + void ShowTargets( void ); + void RunDebugInfo( void ); + + void InitScriptForMap( void ); + void InitConsoleCommands( void ); + void ShutdownConsoleCommands( void ); + + void InitAsyncNetwork( void ); + void ShutdownAsyncNetwork( void ); + void InitLocalClient( int clientNum ); + void FreeSnapshotsOlderThanSequence( int clientNum, int sequence ); + bool ApplySnapshot( int clientNum, int sequence ); + void WriteGameStateToSnapshot( idBitMsgDelta &msg ) const; + void ReadGameStateFromSnapshot( const idBitMsgDelta &msg ); + void NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) id_attribute((format(printf,3,4))); + void ServerProcessEntityNetworkEventQueue( void ); + void ClientProcessEntityNetworkEventQueue( void ); + void ClientShowSnapshot( int clientNum ) const; + void SetGameType( void ); +// RAVEN BEGIN +// ddynerman: gametype specific spawn code + void InitializeSpawns( void ); + idList WeightSpawnSpots( idPlayer* player ); +// RAVEN END + static int sortSpawnPoints( const void *ptr1, const void *ptr2 ); + + void DumpOggSounds( void ); + void GetShakeSounds( const idDict *dict ); + bool ValidateServerSettings( const char *map, const char *gametype ); + + void Tokenize( idStrList &out, const char *in ); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void ShutdownInstances( void ); +// shouchard: ban list support (lives in game_network.cpp) + + void UpdateLagometer( int aheadOfServer, int dupeUsercmds ); + + void ClientReadUnreliableMessages( const idBitMsg &msg ); + void ProcessUnreliableMessage( const idBitMsg &msg ); + + void UpdateClientsPVS( void ); + + bool IsDemoReplayInAreas( int area1, int area2 ); + + void BuildModList( void ); + +public: + void LoadBanList(); + void SaveBanList(); + void FlushBanList(); + bool IsPlayerBanned( const char *name ); + bool IsGuidBanned( const char *guid ); + void AddGuidToBanList( const char *guid ); + void RemoveGuidFromBanList( const char *guid ); + virtual void RegisterClientGuid( int clientNum, const char *guid ); + + int GetBanListCount(); + const mpBanInfo_t* GetBanListEntry( int entry ); // returns client name + const char* GetGuidByClientNum( int clientNum ); // returns GUID + int GetClientNumByGuid( const char* ); // returns clientNum + +// mekberg: get and send ban list + void ServerSendBanList( int clientNum ); + +// jscott: made public + pvsHandle_t GetClientPVS( idPlayer *player, pvsType_t type ); + + int GetCurrentDemoProtocol( void ) { return demo_protocol; } + +private: + char clientGuids[ MAX_CLIENTS ][ CLIENT_GUID_LENGTH ]; + idList banList; + bool banListLoaded; + bool banListChanged; +// RAVEN END +}; + +//============================================================================ + +extern idGameLocal gameLocal; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer to prevent it from allocating memory +// before the unified allocator is initialized +extern idAnimManager *animationLib; +// RAVEN END + +//============================================================================ + +ID_INLINE void idGameLocal::WriteDecl( idBitMsg &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsg &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +ID_INLINE void idGameLocal::WriteDecl( idBitMsgDelta &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsgDelta &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +//============================================================================ + +class idGameError : public idException { +public: + idGameError( const char *text ) : idException( text ) {} +}; + +//============================================================================ + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER) +#define MASK_OPAQUE (CONTENTS_OPAQUE|CONTENTS_SIGHTCLIP) +#define MASK_SHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL) +#define MASK_SHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY) +#define MASK_LARGESHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_LARGESHOTCLIP) +#define MASK_LARGESHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_LARGESHOTCLIP) +#define MASK_DMGSOLID (CONTENTS_SOLID|CONTENTS_LARGESHOTCLIP) + +// RAVEN BEGIN +// creed: added monster clip +#define MASK_MONSTERCLIP (CONTENTS_SOLID|CONTENTS_MONSTERCLIP) +// RAVEN END + +const float DEFAULT_GRAVITY = 1066.0f; +const float DEFAULT_GRAVITY_MP = 800.0f; + +#define DEFAULT_GRAVITY_STRING "1066" +#define DEFAULT_MP_GRAVITY_STRING "800" +const idVec3 DEFAULT_GRAVITY_VEC3( 0, 0, -DEFAULT_GRAVITY ); + +const int CINEMATIC_SKIP_DELAY = SEC2MS( 2.0f ); + +//============================================================================ + +#include "physics/Force.h" +#include "physics/Force_Constant.h" +#include "physics/Force_Drag.h" +#include "physics/Force_Field.h" +#include "physics/Force_Spring.h" +#include "physics/Physics.h" +#include "physics/Physics_Static.h" +#include "physics/Physics_StaticMulti.h" +#include "physics/Physics_Base.h" +#include "physics/Physics_Actor.h" +#include "physics/Physics_Monster.h" +#include "physics/Physics_Player.h" +#include "physics/Physics_Parametric.h" +#include "physics/Physics_RigidBody.h" +#include "physics/Physics_AF.h" +#include "physics/Physics_Particle.h" +#include "physics/Physics_VehicleMonster.h" + +#include "vehicle/VehicleController.h" + +#include "Entity.h" +#include "Game_Debug.h" +#include "IconManager.h" +#include "GameEdit.h" +#include "AF.h" +#include "IK.h" +#include "AFEntity.h" +#include "Misc.h" +#include "Actor.h" + +// client entities +#include "client/ClientEntity.h" +#include "client/ClientEffect.h" +#include "client/ClientMoveable.h" +#include "client/ClientModel.h" +#include "client/ClientAFEntity.h" + +#include "Weapon.h" + +#include "script/ScriptFuncUtility.h" + +#include "Light.h" +#include "WorldSpawn.h" +#include "Item.h" +#include "PlayerView.h" +// TTimo: moved AI.h up, can't do template instanciation on forward declared-classes +#include "ai/AI.h" +#include "Player.h" +#include "Mover.h" +// RAVEN BEGIN +// abahr: +#include "vehicle/VehicleParts.h" +#include "vehicle/Vehicle.h" +#include "SplineMover.h" +#include "TramGate.h" +#include "vehicle/VehicleDriver.h" +// RAVEN END +#include "Camera.h" +#include "Moveable.h" +#include "Target.h" +#include "Trigger.h" +#include "Sound.h" +#include "SecurityCamera.h" +#include "BrittleFracture.h" + +// RAVEN BEGIN +// nmckenzie: Reduce dependencies. +#include "mp/CTF.h" +#include "mp/stats/StatManager.h" +#include "mp/Tourney.h" +#include "Instance.h" +// RAVEN END +#include "anim/Anim_Testmodel.h" + +// RAVEN BEGIN +// jscott: for lip syncing +#include "LipSync.h" +// RAVEN END + +#include "script/Script_Compiler.h" +#include "script/Script_Interpreter.h" +#include "script/Script_Thread.h" + +#ifdef _XENON +#define PACIFIER_UPDATE session->PacifierUpdate() +#else +#define PACIFIER_UPDATE +#endif + +// RAVEN BEGIN +// bdube: inlines +ID_INLINE rvClientEffect* idGameLocal::PlayEffect( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect ( GetEffect ( args, effectName ), origin, axis, loop, endOrigin, broadcast, false, category, effectTint ); +} + +ID_INLINE bool idGameLocal::IsTeamGame( void ) const { + return ( isMultiplayer && ( gameType == GAME_CTF || gameType == GAME_TDM || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ) ); +} + +ID_INLINE int idGameLocal::GetNumMapEntities( void ) const { + if ( mapFile == NULL ) { + return -1; + } else { + return mapFile->GetNumEntities(); + } +} + +ID_INLINE rvInstance* idGameLocal::GetInstance( int id ) { + return instances[ id ]; +} + +ID_INLINE int idGameLocal::GetNumInstances( void ) { + return instances.Num(); +} + +ID_INLINE void idGameLocal::ReceiveRemoteConsoleOutput( const char* output ) { + if( isMultiplayer ) { + mpGame.ReceiveRemoteConsoleOutput( output ); + } +} + +// abahr: +template< class type > +type* idGameLocal::SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idEntity* entity = SpawnEntityDef( entityDefName, additionalArgs ); + if( !entity ) { + return NULL; + } + + if( !entity->IsType(type::GetClassType()) ) { + entity->PostEventMS( &EV_Remove, 0 ); + return NULL; + } + + return static_cast( entity ); +} +// RAVEN END + +template< class type > +ID_INLINE idEntityPtr::idEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE void idEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void idEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +template< class type > +ID_INLINE idEntityPtr &idEntityPtr::operator=( type *ent ) { + if ( ent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool idEntityPtr::SetSpawnId( int id ) { + if ( id == spawnId ) { + return false; + } + if ( ( id >> GENTITYNUM_BITS ) == gameLocal.spawnIds[ id & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool idEntityPtr::IsValid( void ) const { + return ( gameLocal.spawnIds[ spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] == ( spawnId >> GENTITYNUM_BITS ) ); +} + +template< class type > +ID_INLINE type *idEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( spawnId >> GENTITYNUM_BITS ) ) ) { + return static_cast( gameLocal.entities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int idEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ); +} + +// RAVEN BEGIN +// bdube: overloaded operator +template< class type > +ID_INLINE type * idEntityPtr::operator->( void ) const { + return GetEntity ( ); +} + +template< class type > +ID_INLINE idEntityPtr::operator type * ( void ) const { + return GetEntity(); +} +// RAVEN END + +#include "../idlib/containers/ListGame.h" + +#endif /* !__GAME_LOCAL_H__ */ diff --git a/src/game/Game_network.cpp b/src/game/Game_network.cpp new file mode 100644 index 0000000..287f181 --- /dev/null +++ b/src/game/Game_network.cpp @@ -0,0 +1,3539 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: client entities +#include "client/ClientEffect.h" +// shouchard: ban list support +#define BANLIST_FILENAME "banlist.txt" +// RAVEN END + +/* +=============================================================================== + + Client running game code: + - entity events don't work and should not be issued + - entities should never be spawned outside idGameLocal::ClientReadSnapshot + +=============================================================================== +*/ + +// adds tags to the network protocol to detect when things go bad ( internal consistency ) +// NOTE: this changes the network protocol +#ifndef ASYNC_WRITE_TAGS + #define ASYNC_WRITE_TAGS 0 +#endif + +idCVar net_clientShowSnapshot( "net_clientShowSnapshot", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar net_clientShowSnapshotRadius( "net_clientShowSnapshotRadius", "128", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar net_clientMaxPrediction( "net_clientMaxPrediction", "1000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." ); +idCVar net_clientLagOMeter( "net_clientLagOMeter", "0", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT | PC_CVAR_ARCHIVE, "draw prediction graph" ); + +// RAVEN BEGIN +// ddynerman: performance profiling +int net_entsInSnapshot; +int net_snapshotSize; + +extern const int ASYNC_PLAYER_FRAG_BITS; +// RAVEN END + +/* +================ +idGameLocal::InitAsyncNetwork +================ +*/ +void idGameLocal::InitAsyncNetwork( void ) { + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + eventQueue.Init(); + + // NOTE: now that we removed spawning by typeNum, we could stick to a >0 entityDefBits + // not making the change at this point because of proto69 back compat stuff + entityDefBits = -( idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1 ); + localClientNum = 0; // on a listen server SetLocalUser will set this right + realClientTime = 0; + isNewFrame = true; +} + +/* +================ +idGameLocal::ShutdownAsyncNetwork +================ +*/ +void idGameLocal::ShutdownAsyncNetwork( void ) { + entityStateAllocator.Shutdown(); + snapshotAllocator.Shutdown(); + eventQueue.Shutdown(); + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); +} + +/* +================ +idGameLocal::InitLocalClient +================ +*/ +void idGameLocal::InitLocalClient( int clientNum ) { + isServer = false; + isClient = true; + localClientNum = clientNum; +} + +/* +================ +idGameLocal::ServerAllowClient +clientId is the ID of the connecting client - can later be mapped to a clientNum by calling networkSystem->ServerGetClientNum( clientId ) +================ +*/ +allowReply_t idGameLocal::ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[ MAX_STRING_CHARS ] ) { + reason[0] = '\0'; + +// RAVEN BEGIN +// shouchard: ban support + if ( IsGuidBanned( guid ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107239" ); + return ALLOW_NO; + } +// RAVEN END + + if ( serverInfo.GetInt( "si_pure" ) && !mpGame.IsPureReady() ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107139" ); + return ALLOW_NOTYET; + } + + if ( !serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107140" ); + return ALLOW_NOTYET; + } + + // completely full + if ( numClients >= serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + + // check private clients + if( serverInfo.GetInt( "si_privatePlayers" ) > 0 ) { + // just in case somehow we have a stale private clientId that matches a new client + mpGame.RemovePrivatePlayer( clientId ); + + const char *privatePass = cvarSystem->GetCVarString( "g_privatePassword" ); + if( privatePass[ 0 ] == '\0' ) { + common->Warning( "idGameLocal::ServerAllowClient() - si_privatePlayers > 0 with no g_privatePassword" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_privatePlayers is set but g_privatePassword is empty" ); + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + + int numPrivateClients = cvarSystem->GetCVarInteger( "si_numPrivatePlayers" ); + + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, serverInfo.GetInt( "si_privatePlayers" ), numPrivateClients ); + + if ( !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + mpGame.AddPrivatePlayer( clientId ); + } else if( (numClients - numPrivateClients) >= (serverInfo.GetInt( "si_maxPlayers" ) - serverInfo.GetInt( "si_privatePlayers" )) ) { + // if the number of public clients is greater than or equal to the number of public slots, require a private slot + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + } + + + if ( !cvarSystem->GetCVarBool( "si_usePass" ) ) { + return ALLOW_YES; + } + + const char *pass = cvarSystem->GetCVarString( "g_password" ); + if ( pass[ 0 ] == '\0' ) { + common->Warning( "si_usePass is set but g_password is empty" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_usePass is set but g_password is empty" ); + // avoids silent misconfigured state + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + if ( !idStr::Cmp( pass, password ) ) { + return ALLOW_YES; + } + + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting client %s from IP %s: invalid password\n", guid, IP ); + return ALLOW_BADPASS; +} + +/* +================ +idGameLocal::ServerClientConnect +================ +*/ +void idGameLocal::ServerClientConnect( int clientNum, const char *guid ) { + // make sure no parasite entity is left + if ( entities[ clientNum ] ) { + common->DPrintf( "ServerClientConnect: remove old player entity\n" ); + delete entities[ clientNum ]; + } + unreliableMessages[ clientNum ].Init( 0 ); + userInfo[ clientNum ].Clear(); + mpGame.ServerClientConnect( clientNum ); + Printf( "client %d connected.\n", clientNum ); +} + +/* +================ +idGameLocal::ServerClientBegin +================ +*/ +void idGameLocal::ServerClientBegin( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn the player + SpawnPlayer( clientNum ); + if ( clientNum == localClientNum ) { + mpGame.EnterGame( clientNum ); + } + + // ddynerman: connect time + ((idPlayer*)entities[ clientNum ])->SetConnectTime( time ); + + // send message to spawn the player at the clients + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteLong( spawnIds[ clientNum ] ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + if( gameType != GAME_TOURNEY ) { + ((idPlayer*)entities[ clientNum ])->JoinInstance( 0 ); + } else { + // instance 0 might be empty in Tourney + ((idPlayer*)entities[ clientNum ])->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } +//RAVEN BEGIN +//asalmon: This client has finish loading and will be spawned mark them as ready. +#ifdef _XENON + Live()->ClientReady(clientNum); +#endif +//RAVEN END +} + +/* +================ +idGameLocal::ServerClientDisconnect +clientNum == MAX_CLIENTS for cleanup of server demo recording data +================ +*/ +void idGameLocal::ServerClientDisconnect( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( clientNum < MAX_CLIENTS ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DELETE_ENT ); + outMsg.WriteBits( ( spawnIds[ clientNum ] << GENTITYNUM_BITS ) | clientNum, 32 ); // see GetSpawnId + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + // free snapshots stored for this client + FreeSnapshotsOlderThanSequence( clientNum, 0x7FFFFFFF ); + + // free entity states stored for this client + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( clientEntityStates[ clientNum ][ i ] ) { + entityStateAllocator.Free( clientEntityStates[ clientNum ][ i ] ); + clientEntityStates[ clientNum ][ i ] = NULL; + } + } + + // clear the client PVS + memset( clientPVS[ clientNum ], 0, sizeof( clientPVS[ clientNum ] ) ); + + if ( clientNum == MAX_CLIENTS ) { + return; + } + + // only drop MP clients if we're in multiplayer and the server isn't going down + if ( gameLocal.isMultiplayer && !(gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) ) { + // idMultiplayerGame::DisconnectClient will do the delete in MP + mpGame.DisconnectClient( clientNum ); + } else { + // delete the player entity + delete entities[ clientNum ]; + } + + +} + +/* +================ +idGameLocal::ServerWriteInitialReliableMessages + + Send reliable messages to initialize the client game up to a certain initial state. +================ +*/ +void idGameLocal::ServerWriteInitialReliableMessages( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn players + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[i] == NULL || i == clientNum ) { + continue; + } + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( i ); + outMsg.WriteLong( spawnIds[ i ] ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + } + + // update portals for opened doors + int numPortals = gameRenderWorld->NumPortals(); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTALSTATES ); + outMsg.WriteLong( numPortals ); + for ( i = 0; i < numPortals; i++ ) { + outMsg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); + } + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + mpGame.ServerWriteInitialReliableMessages( clientNum ); +} + +/* +================ +idGameLocal::FreeSnapshotsOlderThanSequence +================ +*/ +void idGameLocal::FreeSnapshotsOlderThanSequence( int clientNum, int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + for ( lastSnapshot = NULL, snapshot = clientSnapshots[clientNum]; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence < sequence ) { + for ( state = snapshot->firstEntityState; state; state = snapshot->firstEntityState ) { + snapshot->firstEntityState = snapshot->firstEntityState->next; + entityStateAllocator.Free( state ); + } + if ( lastSnapshot ) { + lastSnapshot->next = snapshot->next; + } else { + clientSnapshots[clientNum] = snapshot->next; + } + snapshotAllocator.Free( snapshot ); + } else { + lastSnapshot = snapshot; + } + } +} + +/* +================ +idGameLocal::ApplySnapshot +================ +*/ +bool idGameLocal::ApplySnapshot( int clientNum, int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + FreeSnapshotsOlderThanSequence( clientNum, sequence ); + + for ( lastSnapshot = NULL, snapshot = clientSnapshots[clientNum]; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence == sequence ) { + for ( state = snapshot->firstEntityState; state; state = state->next ) { + if ( clientEntityStates[clientNum][state->entityNumber] ) { + entityStateAllocator.Free( clientEntityStates[clientNum][state->entityNumber] ); + } + clientEntityStates[clientNum][state->entityNumber] = state; + } + // ~512 bytes + memcpy( clientPVS[clientNum], snapshot->pvs, sizeof( snapshot->pvs ) ); + if ( lastSnapshot ) { + lastSnapshot->next = nextSnapshot; + } else { + clientSnapshots[clientNum] = nextSnapshot; + } + snapshotAllocator.Free( snapshot ); + return true; + } else { + lastSnapshot = snapshot; + } + } + + return false; +} + +/* +================ +idGameLocal::WriteGameStateToSnapshot +================ +*/ +void idGameLocal::WriteGameStateToSnapshot( idBitMsgDelta &msg ) const { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + msg.WriteFloat( globalShaderParms[i] ); + } + + mpGame.WriteToSnapshot( msg ); +} + +/* +================ +idGameLocal::ReadGameStateFromSnapshot +================ +*/ +void idGameLocal::ReadGameStateFromSnapshot( const idBitMsgDelta &msg ) { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + globalShaderParms[i] = msg.ReadFloat(); + } + + mpGame.ReadFromSnapshot( msg ); +} + +/* +================ +idGameLocal::ServerWriteSnapshot +Write a snapshot of the current game state for the given client. +================ +*/ +// RAVEN BEGIN +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. +void idGameLocal::ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ) { +// RAVEN END + int i, msgSize, msgWriteBit; + idPlayer *player, *spectated = NULL; + idEntity *ent; + pvsHandle_t pvsHandle; + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + player = static_cast( entities[ clientNum ] ); + if ( !player ) { + return; + } + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + // free too old snapshots + // ( that's a security, normal acking from server keeps a smaller backlog of snaps ) + FreeSnapshotsOlderThanSequence( clientNum, sequence - 64 ); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[clientNum]; + clientSnapshots[clientNum] = snapshot; + memset( snapshot->pvs, 0, sizeof( snapshot->pvs ) ); + + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( random.RandomInt() ); + msg.WriteLong( tagRandom.GetSeed() ); +#endif + + // write unreliable messages + unreliableMessages[ clientNum ].FlushTo( msg ); + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + + // create the snapshot + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // if the entity is not in the player PVS + if ( !ent->PhysicsTeamInPVS( pvsHandle ) && ent->entityNumber != clientNum ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: don't transmit entities not in your clip world + if ( ent->GetInstance() != player->GetInstance() ) { + continue; + } +// RAVEN END + + // if the entity is a map entity, mark it in PVS + if ( isMapEntity[ ent->entityNumber ] ) { + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + } + + // if that entity is not marked for network synchronization + if ( !ent->fl.networkSync ) { + continue; + } + + // add the entity to the snapshot PVS + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + + // save the write state to which we can revert when the entity didn't change at all + msg.SaveWriteState( msgSize, msgWriteBit ); + + // write the entity to the snapshot + msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ent->entityNumber; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); + assert( ent->entityDefNumber > 0 ); + deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); + + // write the class specific data to the snapshot + ent->WriteToSnapshot( deltaMsg ); + + if ( !deltaMsg.HasChanged() ) { + msg.RestoreWriteState( msgSize, msgWriteBit ); + entityStateAllocator.Free( newBase ); + } else { + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + } + } + + msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); + + // write the PVS to the snapshot +#if ASYNC_WRITE_PVS + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + if ( i < numSourceAreas ) { + msg.WriteLong( sourceAreas[ i ] ); + } else { + msg.WriteLong( 0 ); + } + } + gameLocal.pvs.WritePVS( pvsHandle, msg ); +#endif + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + msg.WriteDeltaLong( clientPVS[clientNum][i], snapshot->pvs[i] ); + } + + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + + // write the game and player state to the snapshot + base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { + assert( entities[ player->spectator ]->IsType( idPlayer::GetClassType() ) ); + deltaMsg.WriteBits( player->spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + static_cast< idPlayer * >( gameLocal.entities[ player->spectator ] )->WritePlayerStateToSnapshot( deltaMsg ); + } else { + deltaMsg.WriteBits( player->entityNumber, idMath::BitsForInteger( MAX_CLIENTS ) ); + player->WritePlayerStateToSnapshot( deltaMsg ); + } + WriteGameStateToSnapshot( deltaMsg ); + + // copy the client PVS string +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + const int numDwords = ( numPVSClients + 31 ) >> 5; + for ( i = 0; i < numDwords; i++ ) { + clientInPVS[i] = snapshot->pvs[i]; + } +// RAVEN END +} + +/* +=============== +idGameLocal::ServerWriteServerDemoSnapshot +=============== +*/ +void idGameLocal::ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ) { + snapshot_t *snapshot; + int i, msgSize, msgWriteBit; + idEntity *ent; + entityState_t *base, *newBase; + idBitMsgDelta deltaMsg; + + bool ret = ServerApplySnapshot( MAX_CLIENTS, sequence - 1 ); + ret = ret; // silence warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[ MAX_CLIENTS ]; + clientSnapshots[ MAX_CLIENTS ] = snapshot; + + unreliableMessages[ MAX_CLIENTS ].FlushTo( msg ); + + for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + if ( !ent->fl.networkSync ) { + continue; + } + + // only record instance 0 ( tourney games ) + if ( ent->GetInstance() != 0 ) { + continue; + } + + msg.SaveWriteState( msgSize, msgWriteBit ); + + msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); + + base = clientEntityStates[ MAX_CLIENTS ][ ent->entityNumber ]; + if ( base ) { + base->state.BeginReading(); + } + + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ent->entityNumber; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); + assert( ent->entityDefNumber > 0 ); + deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); + + ent->WriteToSnapshot( deltaMsg ); + + if ( !deltaMsg.HasChanged() ) { + msg.RestoreWriteState( msgSize, msgWriteBit ); + entityStateAllocator.Free( newBase ); + } else { + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + } + } + msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); + + // write player states and game states + base = clientEntityStates[MAX_CLIENTS][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + // all the players + for ( i = 0; i < numClients; i++ ) { + if ( entities[i] ) { + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + idPlayer *p = static_cast< idPlayer * >( entities[i] ); + p->WritePlayerStateToSnapshot( deltaMsg ); + } + } + // and the game state + WriteGameStateToSnapshot( deltaMsg ); + +} + +/* +================ +idGameLocal::ServerApplySnapshot +================ +*/ +bool idGameLocal::ServerApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::NetworkEventWarning +================ +*/ +void idGameLocal::NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) { + char buf[1024]; + int length = 0; + va_list argptr; + + int entityNum = event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + int id = event->spawnId >> GENTITYNUM_BITS; + + length += idStr::snPrintf( buf+length, sizeof(buf)-1-length, "event %d for entity %d %d: ", event->event, entityNum, id ); + va_start( argptr, fmt ); + length = idStr::vsnPrintf( buf+length, sizeof(buf)-1-length, fmt, argptr ); + va_end( argptr ); + idStr::Append( buf, sizeof(buf), "\n" ); + + common->DWarning( buf ); +} + +/* +================ +idGameLocal::ServerProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ServerProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while ( eventQueue.Start() ) { + event = eventQueue.Start(); + + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if( !entPtr.SetSpawnId( event->spawnId ) ) { + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ServerReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +/* +================ +idGameLocal::ServerSendChatMessage +================ +*/ +void idGameLocal::ServerSendChatMessage( int to, const char *name, const char *text, const char *parm ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( parm ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + + if ( to == -1 || to == localClientNum ) { + idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); + mpGame.AddChatLine( "%s^0: %s", name, temp.c_str() ); + } +} + +/* +================ +idGameLocal::ServerProcessReliableMessage +================ +*/ +void idGameLocal::ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + + id = msg.ReadByte(); + switch( id ) { + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + char name[128]; + char text[128]; + char parm[128]; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + // This parameter is ignored - it is only used when going to client from server + msg.ReadString( parm, sizeof( parm ) ); + + mpGame.ProcessChatMessage( clientNum, id == GAME_RELIABLE_MESSAGE_TCHAT, name, text, NULL ); + break; + } + case GAME_RELIABLE_MESSAGE_VCHAT: { + int index = msg.ReadLong(); + bool team = msg.ReadBits( 1 ) != 0; + mpGame.ProcessVoiceChat( clientNum, team, index ); + break; + } + case GAME_RELIABLE_MESSAGE_KILL: { + mpGame.WantKilled( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_DROPWEAPON: { + mpGame.DropWeapon( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_CALLVOTE: { + mpGame.ServerCallVote( clientNum, msg ); + break; + } + case GAME_RELIABLE_MESSAGE_CASTVOTE: { + bool vote = ( msg.ReadByte() != 0 ); + mpGame.CastVote( clientNum, vote ); + break; + } +// RAVEN BEGIN +// shouchard: multivalue votes + case GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE: { + mpGame.ServerCallPackedVote( clientNum, msg ); + break; + } +// RAVEN END +#if 0 + // uncomment this if you want to track when players are in a menu + case GAME_RELIABLE_MESSAGE_MENU: { + bool menuUp = ( msg.ReadBits( 1 ) != 0 ); + break; + } +#endif + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_DROP ); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + +// RAVEN BEGIN +// jscott: voice comms + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST: { + mpGame.ReceiveAndForwardVoiceData( clientNum, msg, id - GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT ); + break; + } +// ddynerman: stats + case GAME_RELIABLE_MESSAGE_STAT: { + int client = msg.ReadByte(); + statManager->SendStat( clientNum, client ); + break; + } +// shouchard: voice chat + case GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING: { + int clientDest = msg.ReadByte(); + bool mute = ( 0 != msg.ReadByte() ); + mpGame.ServerHandleVoiceMuting( clientNum, clientDest, mute ); + break; + } +// shouchard: server admin + case GAME_RELIABLE_MESSAGE_SERVER_ADMIN: { + int commandType = msg.ReadByte(); + int clientNum = msg.ReadByte(); + if ( SERVER_ADMIN_REMOVE_BAN == commandType ) { + mpGame.HandleServerAdminRemoveBan( "" ); + } else if ( SERVER_ADMIN_KICK == commandType ) { + mpGame.HandleServerAdminKickPlayer( clientNum ); + } else if ( SERVER_ADMIN_FORCE_SWITCH == commandType ) { + mpGame.HandleServerAdminForceTeamSwitch( clientNum ); + } else { + Warning( "Server admin packet with bad type %d", commandType ); + } + break; + } +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + ServerSendBanList( clientNum ); + break; + } +// RAVEN END + + default: { + Warning( "Unknown client->server reliable message: %d", id ); + break; + } + } +} + +/* +================ +idGameLocal::ClientShowSnapshot +================ +*/ +void idGameLocal::ClientShowSnapshot( int clientNum ) const { + int baseBits; + idEntity *ent; + idPlayer *player; + idMat3 viewAxis; + idBounds viewBounds; + entityState_t *base; + + if ( !net_clientShowSnapshot.GetInteger() ) { + return; + } + + player = static_cast( entities[clientNum] ); + if ( !player ) { + return; + } + + viewAxis = player->viewAngles.ToMat3(); + viewBounds = player->GetPhysics()->GetAbsBounds().Expand( net_clientShowSnapshotRadius.GetFloat() ); + + for( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { + + if ( net_clientShowSnapshot.GetInteger() == 1 && ent->snapshotBits == 0 ) { + continue; + } + + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + if ( !entBounds.IntersectsBounds( viewBounds ) ) { + continue; + } + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( base ) { + baseBits = base->state.GetNumBitsWritten(); + } else { + baseBits = 0; + } + + if ( net_clientShowSnapshot.GetInteger() == 2 && baseBits == 0 ) { + continue; + } + + gameRenderWorld->DebugBounds( colorGreen, entBounds ); + gameRenderWorld->DrawText( va( "%d: %s (%d,%d bytes of %d,%d)\n", ent->entityNumber, + ent->name.c_str(), ent->snapshotBits >> 3, ent->snapshotBits & 7, baseBits >> 3, baseBits & 7 ), + entBounds.GetCenter(), 0.1f, colorWhite, viewAxis, 1 ); + } +} + +/* +================ +idGameLocal::UpdateLagometer +================ +*/ +void idGameLocal::UpdateLagometer( int aheadOfServer, int dupeUsercmds ) { + int i, j, ahead; + for ( i = 0; i < LAGO_HEIGHT; i++ ) { + memmove( (byte *)lagometer + LAGO_WIDTH * 4 * i, (byte *)lagometer + LAGO_WIDTH * 4 * i + 4, ( LAGO_WIDTH - 1 ) * 4 ); + } + j = LAGO_WIDTH - 1; + for ( i = 0; i < LAGO_HEIGHT; i++ ) { + lagometer[i][j][0] = lagometer[i][j][1] = lagometer[i][j][2] = lagometer[i][j][3] = 0; + } + ahead = idMath::Rint( (float)aheadOfServer / 16.0f ); + if ( ahead >= 0 ) { + for ( i = 2 * Max( 0, 5 - ahead ); i < 2 * 5; i++ ) { + lagometer[i][j][1] = 255; + lagometer[i][j][3] = 255; + } + } else { + for ( i = 2 * 5; i < 2 * ( 5 + Min( 10, -ahead ) ); i++ ) { + lagometer[i][j][0] = 255; + lagometer[i][j][1] = 255; + lagometer[i][j][3] = 255; + } + } + for ( i = LAGO_HEIGHT - 2 * Min( 6, dupeUsercmds ); i < LAGO_HEIGHT; i++ ) { + lagometer[i][j][0] = 255; + lagometer[i][j][1] = 255; + lagometer[i][j][3] = 255; + } +} + +/* +================ +idGameLocal::ClientReadSnapshot +================ +*/ +void idGameLocal::ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) { + int i, entityDefNumber, numBitsRead; + idEntity *ent; + idPlayer *player, *spectated; + pvsHandle_t pvsHandle; + idDict args; + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int spawnId; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + int proto69TypeNum = 0; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + const idDeclEntityDef *decl; + + if ( net_clientLagOMeter.GetBool() && renderSystem && !IsServerDemo() ) { + UpdateLagometer( aheadOfServer, dupeUsercmds ); + if ( !renderSystem->UploadImage( LAGO_IMAGE, (byte *)lagometer, LAGO_IMG_WIDTH, LAGO_IMG_HEIGHT ) ) { + common->Printf( "lagometer: UploadImage failed. turning off net_clientLagOMeter\n" ); + net_clientLagOMeter.SetBool( false ); + } + } + + InitLocalClient( clientNum ); + + gameRenderWorld->DebugClear( time ); + + // update the game time + framenum = gameFrame; + time = gameTime; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + previousTime = time - GetMSec(); +// RAVEN END + + // so that StartSound/StopSound doesn't risk skipping + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = snapshotSequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[clientNum]; + clientSnapshots[clientNum] = snapshot; + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( msg.ReadLong() ); +#endif + + ClientReadUnreliableMessages( msg ); + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + Error( "error after read unreliable" ); + } +#endif + + // read all entities from the snapshot + for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { + base = clientEntityStates[clientNum][i]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = i; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + numBitsRead = msg.GetNumBitsRead(); + + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + ent = entities[i]; + + // if there is no entity or an entity of the wrong type + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + if ( i < MAX_CLIENTS && ent ) { + // SPAWN_PLAYER should be taking care of spawning the entity with the right spawnId + common->Warning( "ClientReadSnapshot: recycling client entity %d\n", i ); + } + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if ( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + if ( entityDefNumber >= 0 ) { + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); + } + } else { + // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility + assert( proto69 ); + switch ( proto69TypeNum ) { + case 183: + ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); + break; + case 182: + ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); + ent->fl.networkSync = true; + break; + default: + Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); + } + if ( !entities[i] ) { + Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); + } + } + if ( i < MAX_CLIENTS && i >= numClients ) { + numClients = i + 1; + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + +// RAVEN BEGIN +// bdube: stale network entities + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // once we read new snapshot data, unstale the ent + if( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "writeGameState" ); + assert( entityDefNumber >= 0 ); + assert( entityDefNumber < declManager->GetNumDecls( DECL_ENTITYDEF ) ); + const char * classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); + Error( "write to and read from snapshot out of sync for classname '%s'\n", classname ); + } +#endif + } + + player = static_cast( entities[clientNum] ); + if ( !player ) { + return; + } + + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + + // read the PVS from the snapshot +#if ASYNC_WRITE_PVS + int serverPVS[idEntity::MAX_PVS_AREAS]; + i = numSourceAreas; + while ( i < idEntity::MAX_PVS_AREAS ) { + sourceAreas[ i++ ] = 0; + } + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + serverPVS[ i ] = msg.ReadLong(); + } + if ( memcmp( sourceAreas, serverPVS, idEntity::MAX_PVS_AREAS * sizeof( int ) ) ) { + common->Warning( "client PVS areas != server PVS areas, sequence 0x%x", snapshotSequence ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", sourceAreas[ i ] ); + } + common->DPrintf( "\n" ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", serverPVS[ i ] ); + } + common->DPrintf( "\n" ); + } + gameLocal.pvs.ReadPVS( pvsHandle, msg ); +#endif + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + snapshot->pvs[i] = msg.ReadDeltaLong( clientPVS[clientNum][i] ); + } +// RAVEN BEGIN +// ddynerman: performance profiling + net_entsInSnapshot += snapshotEntities.Num(); + net_snapshotSize += msg.GetSize(); +// RAVEN END + // add entities in the PVS that haven't changed since the last applied snapshot + idEntity *nextSpawnedEnt; + for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { + nextSpawnedEnt = ent->spawnNode.Next(); + + // if the entity is already in the snapshot + if ( ent->snapshotSequence == snapshotSequence ) { + continue; + } + + // if the entity is not in the snapshot PVS + if ( !( snapshot->pvs[ent->entityNumber >> 5] & ( 1 << ( ent->entityNumber & 31 ) ) ) ) { + + if ( !ent->fl.networkSync ) { + // don't do stale / unstale on entities that are not marked network sync + continue; + } + + if ( ent->PhysicsTeamInPVS( pvsHandle ) ) { + if ( ent->entityNumber >= MAX_CLIENTS && isMapEntity[ ent->entityNumber ] ) { + // server says it's not in PVS, client says it's in PVS + // if that happens on map entities, most likely something is wrong + // I can see that moving pieces along several PVS could be a legit situation though + // this is a band aid, which means something is not done right elsewhere + if ( net_warnStale.GetInteger() > 1 || ( net_warnStale.GetInteger() == 1 && !ent->fl.networkStale ) ) { + common->Warning( "client thinks map entity 0x%x (%s) is stale, sequence 0x%x", ent->entityNumber, ent->name.c_str(), snapshotSequence ); + } + } +// RAVEN BEGIN +// bdube: hide while not in snapshot + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + + } else { + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + } +// RAVEN END + + continue; + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + ent->snapshotBits = 0; + +// RAVEN BEGIN +// bdube: hide while not in snapshot + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( !base ) { + // entity has probably fl.networkSync set to false + // non netsynced map entities go in and out of PVS, and may need stale/unstale calls + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + continue; + } + + if ( !ent->fl.networkSync ) { + // this is not supposed to happen + // it did however, when restarting a map with a different inhibit of entities caused entity numbers to be laid differently + // an idLight would occupy the entity number of an idItem for instance, and although it's not network-synced ( static level light ), + // the presence of a base would cause the system to think that it is and corrupt things + // we changed the map population so the entity numbers are kept the same no matter how things are inhibited + // this code is left as a fall-through fixup / sanity type of thing + // if this still happens, it's likely "client thinks map entity is stale" is happening as well, and we're still at risk of corruption + Warning( "ClientReadSnapshot: entity %d of type %s is not networkSync and has a snapshot base", ent->entityNumber, ent->GetType()->classname ); + entityStateAllocator.Free( clientEntityStates[clientNum][ent->entityNumber] ); + clientEntityStates[clientNum][ent->entityNumber] = NULL; + continue; + } + + base->state.BeginReading(); + + deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + // read the class specific data from the base state + ent->ReadFromSnapshot( deltaMsg ); + + // after snapshot read, notify client of unstale + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + } + +// RAVEN BEGIN +// ddynerman: add the ambient lights to the snapshot entities + for( int i = 0; i < ambientLights.Num(); i++ ) { + ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); + ambientLights[ i ]->fl.networkStale = false; + } +// RAVEN END + + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + + // read the game and player state from the snapshot + base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + int targetPlayer = deltaMsg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + if ( entities[ targetPlayer ] ) { + static_cast< idPlayer* >( entities[ targetPlayer ] )->ReadPlayerStateFromSnapshot( deltaMsg ); + } else { + player->ReadPlayerStateFromSnapshot( deltaMsg ); + } + + ReadGameStateFromSnapshot( deltaMsg ); + + // visualize the snapshot + ClientShowSnapshot( clientNum ); + + // process entity events + ClientProcessEntityNetworkEventQueue(); +} + +/* +=============== +idGameLocal::ClientReadServerDemoSnapshot + +server demos use a slightly different snapshot format +mostly, we don't need to transmit any PVS visibility information, as we transmit the whole entity activity +plus, we read that data to the virtual 'seeing it all' MAX_CLIENTS client + +=============== +*/ +void idGameLocal::ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ) { + int i; + snapshot_t *snapshot; + entityState_t *base, *newBase; + idBitMsgDelta deltaMsg; + int numBitsRead, spawnId, entityDefNumber; + idEntity *ent; + idDict args; + const idDeclEntityDef *decl; + + int proto69TypeNum = 0; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + bool ret = ClientApplySnapshot( MAX_CLIENTS, sequence - 1 ); + ret = ret; // silence warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + gameRenderWorld->DebugClear( time ); + + framenum = gameFrame; + time = gameTime; + previousTime = time - GetMSec(); + + isNewFrame = true; + + snapshotEntities.Clear(); + + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[ MAX_CLIENTS ]; + clientSnapshots[ MAX_CLIENTS ] = snapshot; + + ClientReadUnreliableMessages( msg ); + + for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { + + base = clientEntityStates[ MAX_CLIENTS ][i]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = i; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + numBitsRead = msg.GetNumBitsRead(); + + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + ent = entities[i]; + + // if there is no entity or an entity of the wrong type + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + if ( i < MAX_CLIENTS && ent ) { + // SpawnPlayer should be taking care of spawning the entity with the right spawnId + common->Warning( "ClientReadServerDemoSnapshot: recycling client entity %d\n", i ); + } + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + // FIXME: hu ho gonna have to rework that for server demos + if ( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + if ( entityDefNumber >= 0 ) { + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); + } + } else { + // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility + assert( proto69 ); + switch ( proto69TypeNum ) { + case 183: + ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); + break; + case 182: + ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); + ent->fl.networkSync = true; + break; + default: + Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); + } + if ( !entities[i] ) { + Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); + } + } + if ( i < MAX_CLIENTS && i >= numClients ) { + numClients = i + 1; + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = sequence; + +// RAVEN BEGIN +// bdube: stale network entities + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // once we read new snapshot data, unstale the ent + if( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; + + } + + // add entities that haven't changed since the last applied snapshot + idEntity *nextSpawnedEnt; + for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { + nextSpawnedEnt = ent->spawnNode.Next(); + + // if the entity is already in the snapshot + if ( ent->snapshotSequence == sequence ) { + continue; + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = sequence; + ent->snapshotBits = 0; + + // Ensure the clipmodel is relinked when transitioning from stale + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } + + base = clientEntityStates[ MAX_CLIENTS ][ ent->entityNumber ]; + if ( !base ) { + // entity has probably fl.networkSync set to false + // non netsynced map entities go in and out of PVS, and may need stale/unstale calls + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + continue; + } + + base->state.BeginReading(); + + deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + // if the entity is not the right type + if ( ent->entityDefNumber != entityDefNumber ) { + // should never happen + common->DWarning( "entity '%s' is not the right type ( 0x%x, expected 0x%x )", ent->GetName(), ent->entityDefNumber, entityDefNumber ); + continue; + } + + // read the class specific data from the base state + ent->ReadFromSnapshot( deltaMsg ); + + // after snapshot read, notify client of unstale + if( ent->fl.networkStale ) { + // FIXME: does this happen ( in a server demo replay? ) + assert( false ); + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + } + +// RAVEN BEGIN +// ddynerman: add the ambient lights to the snapshot entities + for( i = 0; i < ambientLights.Num(); i++ ) { + ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); + ambientLights[ i ]->fl.networkStale = false; + } +// RAVEN END + + // visualize the snapshot + // FIXME + // ClientShowSnapshot( MAX_CLIENTS ); + + // read the game and player states + base = clientEntityStates[MAX_CLIENTS][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game and player state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + // all the players + for ( i = 0; i < numClients; i++ ) { + if ( entities[i] ) { + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + idPlayer *p = static_cast< idPlayer * >( entities[i] ); + p->ReadPlayerStateFromSnapshot( deltaMsg ); + } + } + // the game state + ReadGameStateFromSnapshot( deltaMsg ); + + // process entity events + ClientProcessEntityNetworkEventQueue(); + +} + +/* +================ +idGameLocal::ClientApplySnapshot +================ +*/ +bool idGameLocal::ClientApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::ClientProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ClientProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while( eventQueue.Start() ) { + event = eventQueue.Start(); + + // only process forward, in order + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if( !entPtr.SetSpawnId( event->spawnId ) ) { + if( !gameLocal.entities[ event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + // if new entity exists in this position, silently ignore + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ClientReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +// RAVEN BEGIN +// bdube: client side hitscan + +/* +================ +idGameLocal::ClientHitScan +================ +*/ +void idGameLocal::ClientHitScan( const idBitMsg &msg ) { + int hitscanDefIndex; + idVec3 muzzleOrigin; + idVec3 dir; + idVec3 fxOrigin; + const idDeclEntityDef *decl; + int num_hitscans; + int i; + idEntity *owner; + + assert( isClient ); + + hitscanDefIndex = msg.ReadLong(); + decl = static_cast< const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, hitscanDefIndex ) ); + if ( !decl ) { + common->Warning( "idGameLocal::ClientHitScan: entity def index %d not found\n", hitscanDefIndex ); + return; + } + num_hitscans = decl->dict.GetInt( "hitscans", "1" ); + + owner = entities[ msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ) ]; + + muzzleOrigin[0] = msg.ReadFloat(); + muzzleOrigin[1] = msg.ReadFloat(); + muzzleOrigin[2] = msg.ReadFloat(); + fxOrigin[0] = msg.ReadFloat(); + fxOrigin[1] = msg.ReadFloat(); + fxOrigin[2] = msg.ReadFloat(); + + // one direction sent per hitscan + for( i = 0; i < num_hitscans; i++ ) { + dir = msg.ReadDir( 24 ); + gameLocal.HitScan( decl->dict, muzzleOrigin, dir, fxOrigin, owner ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientProcessReliableMessage +================ +*/ +void idGameLocal::ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + idDict backupSI; + + InitLocalClient( clientNum ); + + if ( serverDemo ) { + assert( demoState == DEMO_PLAYING ); + int record_type = msg.ReadByte(); + assert( record_type < DEMO_RECORD_COUNT ); + // if you need to do some special filtering: + switch ( record_type ) { + case DEMO_RECORD_CLIENTNUM: { + msg.ReadByte(); + /* + int client = msg.ReadByte(); + if ( client != -1 ) { + // reliable was targetted + if ( followPlayer != client ) { + // we're free flying or following someone else + return; + } + } + */ + break; + } + case DEMO_RECORD_EXCLUDE: { + int exclude = msg.ReadByte(); + exclude = exclude; // silence warning + assert( exclude != -1 ); + /* + if ( exclude == followPlayer ) { + return; + } + */ + break; + } + } + } + + id = msg.ReadByte(); + + switch( id ) { + case GAME_RELIABLE_MESSAGE_SPAWN_PLAYER: { + int client = msg.ReadByte(); + int spawnId = msg.ReadLong(); + if ( !entities[ client ] ) { + SpawnPlayer( client ); + entities[ client ]->FreeModelDef(); + } + // fix up the spawnId to match what the server says + // otherwise there is going to be a bogus delete/new of the client entity in the first ClientReadFromSnapshot + spawnIds[ client ] = spawnId; + break; + } + case GAME_RELIABLE_MESSAGE_DELETE_ENT: { + int spawnId = msg.ReadBits( 32 ); + idEntityPtr< idEntity > entPtr; + if( !entPtr.SetSpawnId( spawnId ) ) { + break; + } + if( entPtr.GetEntity() && entPtr.GetEntity()->entityNumber < MAX_CLIENTS ) { + delete entPtr.GetEntity(); + gameLocal.mpGame.UpdatePlayerRanks(); + } else { + delete entPtr.GetEntity(); + } + + break; + } + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { // (client should never get a TCHAT though) + char name[128]; + char text[128]; + char parm[128]; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + msg.ReadString( parm, sizeof( parm ) ); + idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); + mpGame.AddChatLine( "%s^0: %s\n", name, temp.c_str() ); + break; + } + case GAME_RELIABLE_MESSAGE_DB: { + msg_evt_t msg_evt = (msg_evt_t)msg.ReadByte(); + int parm1, parm2; + parm1 = msg.ReadByte( ); + parm2 = msg.ReadByte( ); + mpGame.PrintMessageEvent( -1, msg_evt, parm1, parm2 ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE ); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + case GAME_RELIABLE_MESSAGE_SERVERINFO: { + idDict info; + msg.ReadDeltaDict( info, NULL ); + SetServerInfo( info ); + break; + } + case GAME_RELIABLE_MESSAGE_RESTART: { + MapRestart(); + break; + } + case GAME_RELIABLE_MESSAGE_STARTVOTE: { + char voteString[ MAX_STRING_CHARS ]; + int clientNum = msg.ReadByte( ); + msg.ReadString( voteString, sizeof( voteString ) ); + mpGame.ClientStartVote( clientNum, voteString ); + break; + } + case GAME_RELIABLE_MESSAGE_PRINT: { + char str[ MAX_PRINT_LEN ] = { '\0' }; + msg.ReadString( str, MAX_PRINT_LEN ); + mpGame.PrintMessage( -1, str ); + break; + } +// RAVEN BEGIN +// shouchard: multifield vote stuff + case GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE: { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + int clientNum = msg.ReadByte(); + voteData.m_fieldFlags = msg.ReadShort(); + char mapName[256]; + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + msg.ReadString( mapName, sizeof( mapName ) ); + voteData.m_map = mapName; + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + } + mpGame.ClientStartPackedVote( clientNum, voteData ); + break; + } +// RAVEN END + case GAME_RELIABLE_MESSAGE_UPDATEVOTE: { + int result = msg.ReadByte( ); + int yesCount = msg.ReadByte( ); + int noCount = msg.ReadByte( ); +// RAVEN BEGIN +// shouchard: multifield vote stuff + int multiVote = msg.ReadByte( ); + voteStruct_t voteData; + char mapNameBuffer[256]; + memset( &voteData, 0, sizeof( voteData ) ); + if ( multiVote ) { + voteData.m_fieldFlags = msg.ReadShort(); + voteData.m_kick = msg.ReadByte(); + msg.ReadString( mapNameBuffer, sizeof( mapNameBuffer ) ); + voteData.m_map = mapNameBuffer; + voteData.m_gameType = msg.ReadByte(); + voteData.m_timeLimit = msg.ReadByte(); + voteData.m_fragLimit = msg.ReadShort(); + voteData.m_tourneyLimit = msg.ReadShort(); + voteData.m_captureLimit = msg.ReadShort(); + voteData.m_buying = msg.ReadByte(); + voteData.m_teamBalance = msg.ReadByte(); + if ( gameLocal.GetCurrentDemoProtocol() == 69 ) { + voteData.m_controlTime = 0; + } else { + voteData.m_controlTime = msg.ReadShort(); + } + } + mpGame.ClientUpdateVote( (idMultiplayerGame::vote_result_t)result, yesCount, noCount, voteData ); +// RAVEN END + break; + } + case GAME_RELIABLE_MESSAGE_PORTALSTATES: { + int numPortals = msg.ReadLong(); + assert( numPortals == gameRenderWorld->NumPortals() ); + for ( int i = 0; i < numPortals; i++ ) { + gameRenderWorld->SetPortalState( (qhandle_t) (i+1), msg.ReadBits( NUM_RENDER_PORTAL_BITS ) ); + } + break; + } + case GAME_RELIABLE_MESSAGE_PORTAL: { + qhandle_t portal = msg.ReadLong(); + int blockingBits = msg.ReadBits( NUM_RENDER_PORTAL_BITS ); + assert( portal > 0 && portal <= gameRenderWorld->NumPortals() ); + gameRenderWorld->SetPortalState( portal, blockingBits ); + break; + } + case GAME_RELIABLE_MESSAGE_STARTSTATE: { + mpGame.ClientReadStartState( msg ); + break; + } +// RAVEN BEGIN +// bdube: + case GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND: + mpGame.PlayGlobalItemAcquireSound( msg.ReadBits ( gameLocal.entityDefBits ) ); + break; + +// ddynerman: death messagse + case GAME_RELIABLE_MESSAGE_DEATH: { + int attackerEntityNumber = msg.ReadByte( ); + int attackerScore = -1; + if( attackerEntityNumber >= 0 && attackerEntityNumber < MAX_CLIENTS ) { + attackerScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + int victimEntityNumber = msg.ReadByte( ); + int victimScore = -1; + if( victimEntityNumber >= 0 && victimEntityNumber < MAX_CLIENTS ) { + victimScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + + idPlayer* attacker = (attackerEntityNumber != 255 ? static_cast(gameLocal.entities[ attackerEntityNumber ]) : NULL); + idPlayer* victim = (victimEntityNumber != 255 ? static_cast(gameLocal.entities[ victimEntityNumber ]) : NULL); + int methodOfDeath = msg.ReadByte( ); + + mpGame.ReceiveDeathMessage( attacker, attackerScore, victim, victimScore, methodOfDeath ); + break; + } +// ddynerman: game state + case GAME_RELIABLE_MESSAGE_GAMESTATE: { + mpGame.GetGameState()->ReceiveState( msg ); + break; + } +// ddynerman: game stats + case GAME_RELIABLE_MESSAGE_STAT: { + statManager->ReceiveStat( msg ); + break; + } +// asalmon: game stats for Xenon receive all client stats + case GAME_RELIABLE_MESSAGE_ALL_STATS: { + statManager->ReceiveAllStats( msg ); + break; + } +// ddynerman: multiple instances + case GAME_RELIABLE_MESSAGE_SET_INSTANCE: { + mpGame.ClientSetInstance( msg ); + break; + } +// ddynerman: awards + case GAME_RELIABLE_MESSAGE_INGAMEAWARD: { + statManager->ReceiveInGameAward( msg ); + break; + } + +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + mpBanInfo_t banInfo; + char name[MAX_STRING_CHARS]; + char guid[MAX_STRING_CHARS]; + + FlushBanList( ); + while ( msg.ReadString( name, MAX_STRING_CHARS ) && msg.ReadString( guid, MAX_STRING_CHARS ) ) { + banInfo.name = name; + strncpy( banInfo.guid, guid, CLIENT_GUID_LENGTH ); + banList.Append( banInfo ); + } + + break; + } +// RAVEN END +// RAVEN END + default: { + Error( "Unknown server->client reliable message: %d", id ); + break; + } + } +} + +// RAVEN BEGIN +/* +================ +idGameLocal::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientRun( void ) { + if( isMultiplayer ) { + mpGame.ClientRun(); + } +} + +/* +================ +idGameLocal::ClientEndFrame +Called once each client render frame (after all ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientEndFrame( void ) { + if( isMultiplayer ) { + mpGame.ClientEndFrame(); + } +} + +/* +================ +idGameLocal::ProcessRconReturn +================ +*/ +void idGameLocal::ProcessRconReturn( bool success ) { + if( isMultiplayer ) { + mpGame.ProcessRconReturn( success ); + } +} + +/* +================ +idGameLocal::ResetGuiRconStatus +================ +*/ +void idGameLocal::ResetRconGuiStatus( void ) { + if( isMultiplayer ) { + mpGame.ResetRconGuiStatus( ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientPrediction +server demos: clientNum == MAX_CLIENTS +================ +*/ +gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame, ClientStats_t *cs ) { + idEntity *ent; + idPlayer *player; // may be NULL when predicting for a server demo + gameReturn_t ret; + + ret.sessionCommand[ 0 ] = '\0'; + + if ( clientNum == MAX_CLIENTS ) { + // clientCmds[ MAX_CLIENTS ] has the local interaction + // firing -> cycle follow players, jump -> free fly and cycle map spawns + + int btn_mask; + + player = NULL; + oldUsercmd = usercmd; + usercmd = clientCmds[ MAX_CLIENTS ]; + btn_mask = usercmd.buttons ^ oldUsercmd.buttons; + + if ( usercmd.buttons & btn_mask & BUTTON_ATTACK ) { + // find the next suitable player to follow + int delta = 0; + while ( true ) { + int i_next = GetNextClientNum( followPlayer ); + if ( followPlayer < i_next ) { + delta += i_next - followPlayer; + } else { + delta += numClients - followPlayer + i_next; + } + if ( delta > numClients ) { + // tried them all, no fit + followPlayer = -1; + break; + } + followPlayer = i_next; + if ( !entities[ followPlayer ] ) { + continue; + } + idPlayer *p = static_cast< idPlayer * >( entities[ followPlayer ] ); + if ( p->spectating ) { + continue; + } + // Tourney games, we only record instance 0, only cycle on instance 0 players + if ( p->GetInstance() != 0 ) { + continue; + } + break; + } + } + if ( usercmd.upmove & !oldUsercmd.upmove ) { + if ( followPlayer != -1 ) { + // set yourself up a bit above whoever you were following + freeView.SetFreeView( followPlayer ); + } else { + // pick a random spawn spot to start flying from + freeView.PickRandomSpawn(); + } + followPlayer = -1; + } + + player = NULL; + if ( followPlayer >= 0 ) { + player = static_cast< idPlayer* >( entities[ followPlayer ] ); + if ( !player ) { + // that player we were following was removed from the game + freeView.PickRandomSpawn(); + } else if ( player->spectating ) { + // our followed player went spectator, go free fly + freeView.SetFreeView( followPlayer ); + player = NULL; + followPlayer = -1; + } + } + + if ( !player && !freeView.Initialized() ) { + freeView.PickRandomSpawn(); + } + + } else { + player = static_cast( entities[clientNum] ); + } + +// RAVEN BEGIN +// bdube: added advanced debug support + if ( g_showDebugHud.GetInteger() && net_entsInSnapshot && net_snapshotSize) { + gameDebug.SetInt( "snap_ents", net_entsInSnapshot ); + gameDebug.SetInt( "snap_size", net_snapshotSize ); + + net_entsInSnapshot = 0; + net_snapshotSize = 0; + } + + if ( clientNum == localClientNum ) { + gameDebug.BeginFrame( ); + gameLog->BeginFrame( time ); + } + + isLastPredictFrame = lastPredictFrame; +// RAVEN END + + // check for local client lag + if ( player ) { + if ( networkSystem->ClientGetTimeSinceLastPacket() >= net_clientMaxPrediction.GetInteger() ) { + player->isLagged = true; + } else { + player->isLagged = false; + } + } + + InitLocalClient( clientNum ); + + // update the game time + framenum++; + previousTime = time; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + time += GetMSec(); +// RAVEN END + + // update the real client time and the new frame flag + if ( time > realClientTime ) { + realClientTime = time; + isNewFrame = true; + } else { + isNewFrame = false; + } + + if ( cs ) { + cs->isLastPredictFrame = isLastPredictFrame; + cs->isLagged = player ? player->isLagged : false; + cs->isNewFrame = isNewFrame; + } + + // set the user commands for this frame +// RAVEN BEGIN + usercmds = clientCmds; +// RAVEN END + + if ( clientNum == MAX_CLIENTS && !player ) { + freeView.Fly( usercmd ); + } + + // TMP + bool verbose = cvarSystem->GetCVarBool( "verbose_predict" ); + + // run prediction on all entities from the last snapshot + for ( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { +#if 0 + ent->thinkFlags |= TH_PHYSICS; + ent->ClientPredictionThink(); +#else + // don't force TH_PHYSICS on, only call ClientPredictionThink if thinkFlags != 0 + // it's better to synchronize TH_PHYSICS on specific entities when needed ( movers may be trouble ) + // thinkMask is a temp thing see if there are problems with only checking for TH_PHYSICS + if ( ent->thinkFlags != 0 ) { + if ( verbose ) { + common->Printf( "%d: %s %d\n", ent->entityNumber, ent->GetType()->classname, ent->thinkFlags ); + } + ent->ClientPredictionThink(); + } else { + if ( verbose ) { + common->Printf( "skip %d: %s %d\n", ent->entityNumber, ent->GetType()->classname, ent->thinkFlags ); + } + } +#endif + } + +// RAVEN BEGIN +// bdube: client entities + // run client entities + if ( isNewFrame ) { + // rjohnson: only run the entire logic when it is a new frame + rvClientEntity* cent; + for ( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } +// RAVEN END + + // service any pending events + idEvent::ServiceEvents(); + + // show any debug info for this frame + if ( isNewFrame ) { + RunDebugInfo(); + D_DrawDebugLines(); + } + + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + } + +// RAVEN BEGIN +// ddynerman: client logging/debugging + if ( clientNum == localClientNum ) { + gameDebug.EndFrame(); + gameLog->EndFrame(); + } +// RAVEN END + + g_simpleItems.ClearModified(); + return ret; +} + +/* +=============== +idGameLocal::Tokenize +=============== +*/ +void idGameLocal::Tokenize( idStrList &out, const char *in ) { + char buf[ MAX_STRING_CHARS ]; + char *token, *next; + + idStr::Copynz( buf, in, MAX_STRING_CHARS ); + token = buf; + next = strchr( token, ';' ); + while ( token ) { + if ( next ) { + *next = '\0'; + } + idStr::ToLower( token ); + out.Append( token ); + if ( next ) { + token = next + 1; + next = strchr( token, ';' ); + } else { + token = NULL; + } + } +} + +/* +=============== +idGameLocal::DownloadRequest +=============== +*/ +bool idGameLocal::DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) { + if ( !cvarSystem->GetCVarInteger( "net_serverDownload" ) ) { + return false; + } + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 1 ) { + // 1: single URL redirect + if ( !strlen( cvarSystem->GetCVarString( "si_serverURL" ) ) ) { + common->Warning( "si_serverURL not set" ); + return false; + } + idStr::snPrintf( urls, MAX_STRING_CHARS, "1;%s", cvarSystem->GetCVarString( "si_serverURL" ) ); + return true; + } else { + // 2: table of pak URLs + // first token is the game pak if requested, empty if not requested by the client + // there may be empty tokens for paks the server couldn't pinpoint - the order matters + idStr reply = "2;"; + idStrList dlTable, pakList; + bool matchAll = false; + int i, j; + + if ( !idStr::Icmp( cvarSystem->GetCVarString( "net_serverDlTable" ), "*" ) ) { + matchAll = true; + } else { + Tokenize( dlTable, cvarSystem->GetCVarString( "net_serverDlTable" ) ); + } + Tokenize( pakList, paks ); + + for ( i = 0; i < pakList.Num(); i++ ) { + if ( i > 0 ) { + reply += ";"; + } + if ( pakList[ i ][ 0 ] == '\0' ) { + if ( i == 0 ) { + // pak 0 will always miss when client doesn't ask for game bin + common->DPrintf( "no game pak request\n" ); + } else { + common->DPrintf( "no pak %d\n", i ); + } + continue; + } + + if ( matchAll ) { + idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); + url.AppendPath( pakList[i] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } else { + for ( j = 0; j < dlTable.Num(); j++ ) { + if ( !pakList[ i ].Icmp( dlTable[ j ] ) ) { + break; + } + } + if ( j == dlTable.Num() ) { + common->Printf( "download for %s: pak not matched: %s\n", IP, pakList[ i ].c_str() ); + } else { + idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); + url.AppendPath( dlTable[ j ] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } + } + } + + idStr::Copynz( urls, reply, MAX_STRING_CHARS ); + return true; + } +} + +/* +=============== +idGameLocal::HTTPRequest +=============== +*/ +bool idGameLocal::HTTPRequest( const char *IP, const char *file, bool isGamePak ) { + return false; +} + +/* +=============== +idEventQueue::Alloc +=============== +*/ +entityNetEvent_t* idEventQueue::Alloc() { + entityNetEvent_t* event = eventAllocator.Alloc(); + event->prev = NULL; + event->next = NULL; + return event; +} + +/* +=============== +idEventQueue::Free +=============== +*/ +void idEventQueue::Free( entityNetEvent_t *event ) { + // should only be called on an unlinked event! + assert( !event->next && !event->prev ); + eventAllocator.Free( event ); +} + +/* +=============== +idEventQueue::Shutdown +=============== +*/ +void idEventQueue::Shutdown() { + eventAllocator.Shutdown(); + this->Init(); +} + +/* +=============== +idEventQueue::Init +=============== +*/ +void idEventQueue::Init( void ) { + start = NULL; + end = NULL; +} + +/* +=============== +idEventQueue::Dequeue +=============== +*/ +entityNetEvent_t* idEventQueue::Dequeue( void ) { + entityNetEvent_t* event = start; + if ( !event ) { + return NULL; + } + + start = start->next; + + if ( !start ) { + end = NULL; + } else { + start->prev = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::RemoveLast +=============== +*/ +entityNetEvent_t* idEventQueue::RemoveLast( void ) { + entityNetEvent_t *event = end; + if ( !event ) { + return NULL; + } + + end = event->prev; + + if ( !end ) { + start = NULL; + } else { + end->next = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::Enqueue +=============== +*/ +void idEventQueue::Enqueue( entityNetEvent_t *event, outOfOrderBehaviour_t behaviour ) { + if ( behaviour == OUTOFORDER_DROP ) { + // go backwards through the queue and determine if there are + // any out-of-order events + while ( end && end->time > event->time ) { + entityNetEvent_t *outOfOrder = RemoveLast(); + common->DPrintf( "WARNING: new event with id %d ( time %d ) caused removal of event with id %d ( time %d ), game time = %d.\n", event->event, event->time, outOfOrder->event, outOfOrder->time, gameLocal.time ); + Free( outOfOrder ); + } + } else if ( behaviour == OUTOFORDER_SORT && end ) { + // NOT TESTED -- sorting out of order packets hasn't been + // tested yet... wasn't strictly necessary for + // the patch fix. + entityNetEvent_t *cur = end; + // iterate until we find a time < the new event's + while ( cur && cur->time > event->time ) { + cur = cur->prev; + } + if ( !cur ) { + // add to start + event->next = start; + event->prev = NULL; + start = event; + } else { + // insert + event->prev = cur; + event->next = cur->next; + cur->next = event; + } + return; + } + + // add the new event + event->next = NULL; + event->prev = NULL; + + if ( end ) { + end->next = event; + event->prev = end; + } else { + start = event; + } + end = event; +} + +// RAVEN BEGIN +// shouchard: ban list stuff here + +/* +================ +idGameLocal::LoadBanList +================ +*/ +void idGameLocal::LoadBanList() { + + // open file + idStr token; + idFile *banFile = fileSystem->OpenFileRead( BANLIST_FILENAME ); + mpBanInfo_t banInfo; + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::LoadBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + // parse file (read three consecutive strings per banInfo (real complex ;) ) ) + while ( banFile->ReadString( token ) > 0 ) { + // name + banInfo.name = token; + + // guid + if ( banFile->ReadString( token ) > 0 && token.Length() >= 11 ) { + idStr::Copynz( banInfo.guid, token.c_str(), CLIENT_GUID_LENGTH ); + + banList.Append( banInfo ); + continue; + } + + gameLocal.Warning( "idGameLocal::LoadBanList: Potential curruption of banlist file (%s).", BANLIST_FILENAME ); + } + fileSystem->CloseFile( banFile ); + + banListLoaded = true; + banListChanged = false; +} + +/* +================ +idGameLocal::SaveBanList +================ +*/ +void idGameLocal::SaveBanList() { + if ( !banListChanged ) { + return; + } + + // open file + idFile *banFile = fileSystem->OpenFileWrite( BANLIST_FILENAME ); + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::SaveBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + for ( int i = 0; i < banList.Num(); i++ ) { + const mpBanInfo_t& banInfo = banList[ i ]; + char temp[ 16 ] = { 0, }; + banFile->WriteString( va( "%s", banInfo.name.c_str() ) ); + idStr::Copynz( temp, banInfo.guid, CLIENT_GUID_LENGTH ); + banFile->WriteString( temp ); +// idStr::Copynz( temp, (const char*)banInfo.ip, 15 ); +// banFile->WriteString( "255.255.255.255" ); + } + fileSystem->CloseFile( banFile ); + banListChanged = false; +} + +/* +================ +idGameLocal::FlushBanList +================ +*/ +void idGameLocal::FlushBanList() { + banList.Clear(); + banListLoaded = false; + banListChanged = false; +} + +/* +================ +idGameLocal::IsPlayerBanned +================ +*/ +bool idGameLocal::IsPlayerBanned( const char *name ) { + assert( name ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( name, banList[ i ].name ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::IsGuidBanned +================ +*/ +bool idGameLocal::IsGuidBanned( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::AddGuidToBanList +================ +*/ +void idGameLocal::AddGuidToBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + mpBanInfo_t banInfo; + char name[ 512 ]; // TODO: clean this up + gameLocal.GetPlayerName( gameLocal.GetClientNumByGuid( guid ), name ); + banInfo.name = name; + idStr::Copynz( banInfo.guid, guid, CLIENT_GUID_LENGTH ); +// SIMDProcessor->Memset( banInfo.ip, 0xFF, 15 ); + banList.Append( banInfo ); + banListChanged = true; +} + +/* +================ +idGameLocal::RemoveGuidFromBanList +================ +*/ +void idGameLocal::RemoveGuidFromBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we find a match remove it. + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + banList.RemoveIndex( i ); + banListChanged = true; + return; + } + } +} + +/* +================ +idGameLocal::RegisterClientGuid +================ +*/ +void idGameLocal::RegisterClientGuid( int clientNum, const char *guid ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + assert( guid ); + memset( clientGuids[ clientNum ], 0, CLIENT_GUID_LENGTH ); // just in case + idStr::Copynz( clientGuids[ clientNum ], guid, CLIENT_GUID_LENGTH ); +} + +/* +================ +idGameLocal::GetBanListCount +================ +*/ +int idGameLocal::GetBanListCount() { + if ( !banListLoaded ) { + LoadBanList(); + } + + return banList.Num(); +} + +/* +================ +idGameLocal::GetBanListEntry +================ +*/ +const mpBanInfo_t* idGameLocal::GetBanListEntry( int entry ) { + if ( !banListLoaded ) { + LoadBanList(); + } + + if ( entry < 0 || entry >= banList.Num() ) { + return NULL; + } + + return &banList[ entry ]; +} + +/* +================ +idGameLocal::GetGuidByClientNum +================ +*/ +const char *idGameLocal::GetGuidByClientNum( int clientNum ) { + assert( clientNum >= 0 && clientNum < numClients ); + + return clientGuids[ clientNum ]; +} + +/* +================ +idGameLocal::GetClientNumByGuid +================ +*/ +int idGameLocal::GetClientNumByGuid( const char * guid ) { + assert( guid ); + + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( !idStr::Icmp( networkSystem->GetClientGUID( i ), guid ) ) { + return i; + } + } + return -1; +} + +// mekberg: send ban list to client +/* +================ +idGameLocal::ServerSendBanList +================ +*/ +void idGameLocal::ServerSendBanList( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + + if ( !banListLoaded ) { + LoadBanList(); + } + + int i; + int c = banList.Num(); + for ( i = 0; i < c; i++ ) { + outMsg.WriteString( banList[ i ].name.c_str() ); + outMsg.WriteString( banList[ i ].guid, CLIENT_GUID_LENGTH ); + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); +} + +// mekberg: so we can populate ban list outside of multiplayer game +/* +=================== +idGameLocal::PopulateBanList +=================== +*/ +void idGameLocal::PopulateBanList( idUserInterface* hud ) { + if ( !hud ) { + return; + } + + int bans = GetBanListCount(); + for ( int i = 0; i < bans; i++ ) { + const mpBanInfo_t * banInfo = GetBanListEntry( i ); + hud->SetStateString( va( "sa_banList_item_%d", i ), va( "%d: %s\t%s", i+1, banInfo->name.c_str(), banInfo->guid ) ); + } + hud->DeleteStateVar( va( "sa_banList_item_%d", bans ) ); + hud->SetStateString( "sa_banList_sel_0", "-1" ); + // used to trigger a redraw, was slow, and doesn't seem to do anything so took it out. fixes #13675 + hud->StateChanged( gameLocal.time, false ); +} +// RAVEN END + +/* +================ +idGameLocal::ServerSendInstanceReliableMessageExcluding +Works like networkSystem->ServerSendReliableMessageExcluding, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if ( owner == NULL ) { + networkSystem->ServerSendReliableMessageExcluding( excludeClient, msg ); + return; + } + + for( i = 0; i < numClients; i++ ) { + if ( i == excludeClient ) { + continue; + } + + if( entities[ i ] == NULL ) { + continue; + } + + if( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg ); + } +} + +/* +================ +idGameLocal::ServerSendInstanceReliableMessage +Works like networkSystem->ServerSendReliableMessage, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if( owner == NULL ) { + networkSystem->ServerSendReliableMessage( clientNum, msg ); + return; + } + + if( clientNum == -1 ) { + for( i = 0; i < numClients; i++ ) { + if( entities[ i ] == NULL ) { + continue; + } + + if( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg ); + } + } else { + if( entities[ clientNum ] && entities[ clientNum ]->GetInstance() == owner->GetInstance() ) { + networkSystem->ServerSendReliableMessage( clientNum, msg ); + } + } +} + +/* +=============== +idGameLocal::SendUnreliableMessage +for spectating support, we have to loop through the clients and emit to the spectator client too +note that a clientNum == -1 means send to everyone +=============== +*/ +void idGameLocal::SendUnreliableMessage( const idBitMsg &msg, const int clientNum ) { + int icl; + idPlayer *player; + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // not to local client + // note that if local is spectated he will still get it + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( icl != clientNum ) { + player = static_cast< idPlayer * >( entities[ icl ] ); + // drop all clients except the ones that follow the client we emit to + if ( !player->spectating || player->spectator != clientNum ) { + continue; + } + } + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING ) { + // record the type and destination for remap on readback + idBitMsg dest; + byte msgBuf[ 16 ]; + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_CLIENTNUM ); + dest.WriteByte( clientNum ); + + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } +} + +/* +=============== +idGameLocal::SendUnreliableMessagePVS +instanceEnt to NULL for no instance checks +excludeClient to -1 for no exclusions +=============== +*/ +void idGameLocal::SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1, int area2 ) { + int icl; + int matchInstance = instanceEnt ? instanceEnt->GetInstance() : -1; + idPlayer *player; + int areas[ 2 ]; + int numEvAreas; + + numEvAreas = 0; + if ( area1 != -1 ) { + areas[ 0 ] = area1; + numEvAreas++; + } + if ( area2 != -1 ) { + areas[ numEvAreas ] = area2; + numEvAreas++; + } + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // local client is always excluded + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( matchInstance >= 0 && entities[ icl ]->GetInstance() != matchInstance ) { + continue; + } + if ( clientsPVS[ icl ].i < 0 ) { + // clients for which we don't have PVS info won't get anything + continue; + } + player = static_cast< idPlayer * >( entities[ icl ] ); + + // if no areas are given, this is a global emit + if ( numEvAreas ) { + // ony send if pvs says this client can see it + if ( !pvs.InCurrentPVS( clientsPVS[ icl ], areas, numEvAreas ) ) { + continue; + } + } + + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING ) { + // record the target areas to the message + idBitMsg dest; + byte msgBuf[ 16 ]; + + // Tourney games: only record from instance 0 + if ( !instanceEnt || instanceEnt->GetInstance() == 0 ) { + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_AREAS ); + dest.WriteLong( area1 ); + dest.WriteLong( area2 ); + + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } + } +} + +/* +=============== +idGameLocal::ClientReadUnreliableMessages +=============== +*/ +void idGameLocal::ClientReadUnreliableMessages( const idBitMsg &_msg ) { + idMsgQueue localQueue; + int size; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idBitMsg msg; + + localQueue.ReadFrom( _msg ); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + while ( localQueue.Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + ProcessUnreliableMessage( msg ); + msg.BeginWriting(); + } +} + +/* +=============== +idGameLocal::DemoReplayInAreas +checks if our current demo replay view ( server demo ) matches the areas given +=============== +*/ +bool idGameLocal::IsDemoReplayInAreas( int area1, int area2 ) { + int areas[2]; + int numAreas; + + idVec3 view; + pvsHandle_t handle; + + bool ret; + + numAreas = 0; + if ( area1 != -1 ) { + areas[ 0 ] = area1; + numAreas++; + } + if ( area2 != -1 ) { + areas[ numAreas ] = area2; + numAreas++; + } + + assert( serverDemo ); + assert( demoState == DEMO_PLAYING ); + + if ( followPlayer == -1 ) { + view = freeView.GetOrigin(); + } else { + view = entities[ followPlayer ]->GetPhysics()->GetOrigin(); + } + + // could probably factorize this, at least for processing all unreliable messages, maybe at a higher level of the loop? + handle = pvs.SetupCurrentPVS( view ); + ret = pvs.InCurrentPVS( handle, areas, numAreas ); + pvs.FreeCurrentPVS( handle ); + return ret; +} + +/* +=============== +idGameLocal::ProcessUnreliableMessage +=============== +*/ +void idGameLocal::ProcessUnreliableMessage( const idBitMsg &msg ) { + if ( serverDemo ) { + assert( demoState == DEMO_PLAYING ); + int record_type = msg.ReadByte(); + assert( record_type < GAME_UNRELIABLE_RECORD_COUNT ); + switch ( record_type ) { + case GAME_UNRELIABLE_RECORD_CLIENTNUM: { + int client = msg.ReadByte(); + if ( client != -1 ) { + // unreliable was targetted + if ( followPlayer != client ) { + // either free flying, or following someone else + return; + } + } + break; + } + case GAME_UNRELIABLE_RECORD_AREAS: { + int area1 = msg.ReadLong(); + int area2 = msg.ReadLong(); + if ( !IsDemoReplayInAreas( area1, area2 ) ) { + return; + } + break; + } + } + } + + int type = msg.ReadByte(); + switch ( type ) { + case GAME_UNRELIABLE_MESSAGE_EVENT: { + idEntityPtr p; + int spawnId = msg.ReadBits( 32 ); + p.SetSpawnId( spawnId ); + + if ( p.GetEntity() ) { + p.GetEntity()->ClientReceiveEvent( msg.ReadByte(), time, msg ); + } else { + Warning( "ProcessUnreliableMessage: no local entity 0x%x for event %d", spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ), msg.ReadByte() ); + } + break; + } + case GAME_UNRELIABLE_MESSAGE_EFFECT: { + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* effect; + effectCategory_t category; + const idDecl *decl; + + decl = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + bool loop = msg.ReadBits( 1 ) != 0; + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + effect = new rvClientEffect( decl ); + effect->SetOrigin( origin ); + effect->SetAxis( quat.ToMat3() ); + effect->Play( time, loop, origin2 ); + } + + break; + } + case GAME_UNRELIABLE_MESSAGE_HITSCAN: { + ClientHitScan( msg ); + break; + } +#ifdef _USE_VOICECHAT + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + mpGame.ReceiveAndPlayVoiceData( msg ); + break; + } +#else + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + break; + } +#endif + default: { + Error( "idGameLocal::ProcessUnreliableMessage() - Unknown unreliable message '%d'\n", type ); + } + } +} + +/* +=============== +idGameLocal::WriteNetworkInfo +=============== +*/ +void idGameLocal::WriteNetworkInfo( idFile* file, int clientNum ) { + int i, j; + snapshot_t *snapshot; + entityState_t *entityState; + + if ( !IsServerDemo() ) { + + // save the current states + for ( i = 0; i < MAX_GENTITIES; i++ ) { + entityState = clientEntityStates[clientNum][i]; + file->WriteBool( !!entityState ); + if ( entityState ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + } + + // save the PVS states + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->WriteInt( clientPVS[i][j] ); + } + } + + } + + // players ( including local client ) + j = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + j++; + } + file->WriteInt( j ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + file->WriteInt( i ); + file->WriteInt( spawnIds[ i ] ); + } + + if ( !IsServerDemo() ) { + + // write number of snapshots so on readback we know how many to allocate + i = 0; + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + i++; + } + file->WriteInt( i ); + + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + file->WriteInt( snapshot->sequence ); + + // write number of entity states in the snapshot + i = 0; + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + i++; + } + file->WriteInt( i ); + + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + + file->Write( snapshot->pvs, sizeof( snapshot->pvs ) ); + } + + } + + // write the 'initial reliables' data + mpGame.WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idGameLocal::ReadNetworkInfo +=============== +*/ +void idGameLocal::ReadNetworkInfo( int gameTime, idFile* file, int clientNum ) { + int i, j, num, numStates, stateSize; + snapshot_t *snapshot, **lastSnap; + entityState_t *entityState, **lastState; + int proto69TypeNum = 0; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + assert( clientNum == MAX_CLIENTS || !IsServerDemo() ); + InitLocalClient( clientNum ); + + time = gameTime; + previousTime = gameTime; + + // force new frame + realClientTime = 0; + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + if ( !IsServerDemo() ) { + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + bool isValid; + + file->ReadBool( isValid ); + if ( isValid ) { + clientEntityStates[clientNum][i] = entityStateAllocator.Alloc(); + entityState = clientEntityStates[clientNum][i]; + entityState->next = NULL; + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + } else { + clientEntityStates[clientNum][i] = NULL; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->ReadInt( clientPVS[i][j] ); + } + } + + } + + // spawn player entities. ( numClients is not a count but the watermark of client indexes ) + file->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + int icl, spawnId; + file->ReadInt( icl ); + file->ReadInt( spawnId ); + SpawnPlayer( icl ); + spawnIds[ icl ] = spawnId; + numClients = icl + 1; + } + + if ( !IsServerDemo() ) { + + file->ReadInt( num ); + lastSnap = &clientSnapshots[ localClientNum ]; + for ( i = 0; i < num; i++ ) { + snapshot = snapshotAllocator.Alloc(); + snapshot->firstEntityState = NULL; + snapshot->next = NULL; + file->ReadInt( snapshot->sequence ); + + file->ReadInt( numStates ); + lastState = &snapshot->firstEntityState; + for ( j = 0; j < numStates; j++ ) { + entityState = entityStateAllocator.Alloc(); + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + entityState->next = NULL; + assert( !(*lastState ) ); + *lastState = entityState; + lastState = &entityState->next; + } + + file->Read( snapshot->pvs, sizeof( snapshot->pvs ) ); + + assert( !(*lastSnap) ); + *lastSnap = snapshot; + lastSnap = &snapshot->next; + } + + // spawn entities + for ( i = 0; i < ENTITYNUM_NONE; i++ ) { + int spawnId, entityDefNumber; + idBitMsgDelta deltaMsg; + idDict args; + entityState_t *base = clientEntityStates[clientNum][i]; + idEntity *ent = entities[i]; + const idDeclEntityDef *decl; + + if ( !base ) { + continue; + } + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + if ( proto69 ) { + proto69TypeNum = deltaMsg.ReadBits( idClass::GetTypeNumBits() ); + } + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + if ( entityDefNumber >= 0 ) { + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString("spawnclass") ); + } + } else { + // we no longer support spawning entities by type num only. we would only hit this when playing 1.2 demos for backward compatibility + assert( proto69 ); + switch ( proto69TypeNum ) { + case 183: + ent = SpawnEntityType( rvViewWeapon::GetClassType(), &args, true ); + break; + case 182: + ent = SpawnEntityType( idAnimatedEntity::GetClassType(), &args, true ); + ent->fl.networkSync = true; + break; + default: + Error( "Unexpected protocol 69 typenum (%d) for spawning entity by type", proto69TypeNum ); + } + if ( !entities[i] ) { + Error( "Failed to spawn entity by typenum %d ( protocol 69 backwards compat )", proto69TypeNum ); + } + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // this is useful. for instance on idPlayer, resets stuff so powerups actually appear + ent->ClientUnstale(); + } + + { + // specific state read for game and player state + idBitMsgDelta deltaMsg; + entityState_t *base = clientEntityStates[clientNum][ENTITYNUM_NONE]; + idPlayer *player; + int targetPlayer; + + // it's possible to have a recording start right at CS_INGAME and not have a base for reading this yet + if ( base ) { + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + + targetPlayer = deltaMsg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + player = static_cast< idPlayer* >( entities[ targetPlayer ] ); + if ( !player ) { + Error( "ReadNetworkInfo: no local player entity" ); + return; + } + player->ReadPlayerStateFromSnapshot( deltaMsg ); + ReadGameStateFromSnapshot( deltaMsg ); + } + } + + // set self spectating state according to userinfo settings + GetLocalPlayer()->Spectate( idStr::Icmp( userInfo[ clientNum ].GetString( "ui_spectate" ), "Spectate" ) == 0 ); + + } + + // read the 'initial reliables' data + mpGame.ReadNetworkInfo( file, clientNum ); +} + +/* +============ +idGameLocal::SetDemoState +============ +*/ +void idGameLocal::SetDemoState( demoState_t state, bool _serverDemo, bool _timeDemo ) { + if ( demoState == DEMO_RECORDING && state == DEMO_NONE ) { + ServerClientDisconnect( MAX_CLIENTS ); + } + demoState = state; + serverDemo = _serverDemo; + timeDemo = _timeDemo; + if ( demoState == DEMO_NONE ) { + demo_hud = NULL; + demo_mphud = NULL; + demo_cursor = NULL; + } +} + +/* +=============== +idGameLocal::ValidateDemoProtocol +=============== +*/ +bool idGameLocal::ValidateDemoProtocol( int minor_ref, int minor ) { + // 1.1 beta : 67 + // 1.1 final: 68 + // 1.2 : 69 + // 1.3 : 71 + + // let 1.3 play 1.2 demos - keep a careful eye on snapshotting changes + demo_protocol = minor; + return ( minor_ref == minor || ( minor_ref == 71 && minor == 69 ) ); +} + +/* +=============== +idGameLocal::RandomSpawn +=============== +*/ +idPlayerStart *idGameLocal::RandomSpawn( void ) { + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; +} diff --git a/src/game/Healing_Station.cpp b/src/game/Healing_Station.cpp new file mode 100644 index 0000000..427dd1f --- /dev/null +++ b/src/game/Healing_Station.cpp @@ -0,0 +1,233 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Healing_Station.h" + +CLASS_DECLARATION( idAnimatedEntity, rvHealingStation ) +END_CLASS + +/* +================ +rvHealingStation::Think +================ +*/ +void rvHealingStation::Think ( void ) { + // TODO: I'm guessing this is bad, but I wanted to get this in so that people could start + // placing it. The entity decided to stop thinking and I didn't have time to debug it. + BecomeActive( TH_ALL ); + + stateThread.Execute(); + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + if ( healthDispensed > 0 ) { + CreateFrame( float( healthDispensed ) / maxHealth ); + } + Present(); + } +} + +/* +================ +rvHealingStation::Spawn +================ +*/ +void rvHealingStation::Spawn ( void ) { + entityToHeal = 0; + nextHealTime = 0; + healFrequency = spawnArgs.GetInt( "heal_frequency", "24" ); + healAmount = spawnArgs.GetInt( "heal_amount", "1" ); + + healthDispensed = 0; + soundStartTime = 0; + soundLength = 0; + maxHealth = spawnArgs.GetInt( "max_health", "100" ); + + dispenseAnim = GetAnimator()->GetAnim( spawnArgs.GetString( "dispense_anim", "dispense" ) ); + + CreateFrame( 0 ); + + stateThread.SetOwner( this ); + stateThread.SetName( GetName() ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 4 ); +} + +/* +================ +rvHealingStation::Save +================ +*/ +void rvHealingStation::Save ( idSaveGame *savefile ) const { + stateThread.Save( savefile ); + entityToHeal.Save ( savefile ); + savefile->WriteInt( nextHealTime ); + savefile->WriteInt( healFrequency ); + savefile->WriteInt( healAmount ); + savefile->WriteInt( healthDispensed ); + savefile->WriteInt( maxHealth ); + savefile->WriteInt( dispenseAnim ); + savefile->WriteInt( soundStartTime ); + savefile->WriteInt( soundLength ); +} + +/* +================ +rvHealingStation::Restore +================ +*/ +void rvHealingStation::Restore ( idRestoreGame *savefile ) { + stateThread.Restore( savefile, this ); + entityToHeal.Restore ( savefile ); + savefile->ReadInt( nextHealTime ); + savefile->ReadInt( healFrequency ); + savefile->ReadInt( healAmount ); + savefile->ReadInt( healthDispensed ); + savefile->ReadInt( maxHealth ); + savefile->ReadInt( dispenseAnim ); + savefile->ReadInt( soundStartTime ); + savefile->ReadInt( soundLength ); +} + +/* +================ +rvHealingStation::BeginHealing +================ +*/ +void rvHealingStation::BeginHealing ( idEntity *toHeal ) { + entityToHeal = toHeal; + stateThread.SetState( "Healing" ); +} + +/* +================ +rvHealingStation::EndHealing +================ +*/ +void rvHealingStation::EndHealing ( void ) { + entityToHeal = NULL; +} + +/* +================ +rvHealingStation::CreateFrame +================ +*/ +void rvHealingStation::CreateFrame ( float station_health ) { + // Update the GUI + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->SetStateFloat( "station_health", 1.0f - station_health ); + renderEntity.gui[ 0 ]->StateChanged( gameLocal.time, true ); + } + + // Update the Animation + int numFrames = GetAnimator()->GetAnim( dispenseAnim )->NumFrames(); + float lerp = numFrames * station_health; + int frame = lerp; + lerp = lerp - frame; + frameBlend_t frameBlend = { 0, frame, frame + 1, 1.0f - lerp, lerp }; + GetAnimator()->SetFrame( ANIMCHANNEL_ALL, dispenseAnim, frameBlend ); +} + +/* +================ +rvHealingStation::IsPlaying +================ +*/ +bool rvHealingStation::IsPlaying ( void ) { + idSoundEmitter* emitter = soundSystem->EmitterForIndex ( SOUNDWORLD_GAME, GetSoundEmitter ( ) ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvHealingStation ) + STATE ( "Healing", rvHealingStation::State_Healing ) +END_CLASS_STATES + +/* +================ +rvHealingStation::State_Healing +================ +*/ +stateResult_t rvHealingStation::State_Healing ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_DISPENSE, + }; + + if ( entityToHeal.IsValid() ) { + idPlayer* player = static_cast( entityToHeal.GetEntity( ) ); + const int entityMaxHealth = player->inventory.maxHealth; + + if ( healthDispensed < maxHealth && // and we have health to dispense... + entityToHeal->health < entityMaxHealth && // and the entity needs health. + entityToHeal->health > 0 ) // and he's still alive. + { + switch ( parms.stage ) { + case STAGE_INIT: + soundStartTime = gameLocal.time; + StartSound( "snd_start", SND_CHANNEL_ANY, 0, false, &soundLength ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time > soundStartTime + soundLength ) { + soundStartTime = 0; + soundLength = 0; + return SRESULT_STAGE ( STAGE_DISPENSE ); + } + return SRESULT_WAIT; + + case STAGE_DISPENSE: + if ( gameLocal.time > nextHealTime ) { // If it's time to heal... + int healthGiven = Min( maxHealth - healthDispensed, Min( healAmount, entityMaxHealth - entityToHeal->health ) ); + entityToHeal->health += healthGiven; + healthDispensed += healthGiven; + nextHealTime = gameLocal.time + healFrequency; + } + if ( !IsPlaying ( ) ) { + StartSound( "snd_loop", SND_CHANNEL_ANY, 0, false, NULL ); + } + return SRESULT_WAIT; + } + } + } + + StopSound ( SND_CHANNEL_ANY, 0 ); + StartSound ( "snd_stop", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_DONE; +} diff --git a/src/game/Healing_Station.h b/src/game/Healing_Station.h new file mode 100644 index 0000000..4acab43 --- /dev/null +++ b/src/game/Healing_Station.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + rvHealingStation + +=============================================================================== +*/ +class rvHealingStation : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvHealingStation ); + + virtual void Think ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BeginHealing ( idEntity *toHeal ); + void EndHealing ( void ); + +protected: + + void CreateFrame ( float station_health ); + + stateResult_t State_Healing ( const stateParms_t& parms ); + + rvStateThread stateThread; + idEntityPtr entityToHeal; + int nextHealTime; + int healFrequency; + int healAmount; + int healthDispensed; + int maxHealth; + int dispenseAnim; + int soundStartTime; + int soundLength; + +private: + + bool IsPlaying ( void ); + + CLASS_STATES_PROTOTYPE ( rvHealingStation ); +}; + diff --git a/src/game/IK.cpp b/src/game/IK.cpp new file mode 100644 index 0000000..34d8431 --- /dev/null +++ b/src/game/IK.cpp @@ -0,0 +1,1085 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idIK + +=============================================================================== +*/ + +/* +================ +idIK::idIK +================ +*/ +idIK::idIK( void ) { + ik_activate = false; + initialized = false; + self = NULL; + animator = NULL; + modifiedAnim = 0; + modelOffset.Zero(); +} + +/* +================ +idIK::~idIK +================ +*/ +idIK::~idIK( void ) { +} + +/* +================ +idIK::Save +================ +*/ +void idIK::Save( idSaveGame *savefile ) const { + savefile->WriteBool( initialized ); + savefile->WriteBool( ik_activate ); + savefile->WriteObject( self ); + savefile->WriteString( animator != NULL && animator->GetAnim( modifiedAnim ) ? animator->GetAnim( modifiedAnim )->Name() : "" ); + //savefile->WriteInt( modifiedAnim ); // Recomputed during restore, do not save + savefile->WriteVec3( modelOffset ); +} + +/* +================ +idIK::Restore +================ +*/ +void idIK::Restore( idRestoreGame *savefile ) { + idStr anim; + + savefile->ReadBool( initialized ); + savefile->ReadBool( ik_activate ); + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( anim ); + //savefile->ReadInt( modifiedAnim ); // This is defined below + savefile->ReadVec3( modelOffset ); + + if ( self ) { + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + } else { + animator = NULL; + modifiedAnim = 0; + } +} + +/* +================ +idIK::IsInitialized +================ +*/ +bool idIK::IsInitialized( void ) const { + return initialized && ik_enable.GetBool(); +} + +/* +================ +idIK::Init +================ +*/ +bool idIK::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + idRenderModel *model; + + if ( self == NULL ) { + return false; + } + + this->self = self; + + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + if ( animator->ModelDef()->ModelHandle() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) uses default model.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + model = animator->ModelHandle(); + if ( model == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + this->modelOffset = modelOffset; + + return true; +} + +/* +================ +idIK::Evaluate +================ +*/ +void idIK::Evaluate( void ) { +} + +/* +================ +idIK::ClearJointMods +================ +*/ +void idIK::ClearJointMods( void ) { + ik_activate = false; +} + +/* +================ +idIK::SolveTwoBones +================ +*/ +bool idIK::SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ) { + float length, lengthSqr, lengthInv, x, y; + idVec3 vec0, vec1; + + vec0 = endPos - startPos; + lengthSqr = vec0.LengthSqr(); + lengthInv = idMath::InvSqrt( lengthSqr ); + length = lengthInv * lengthSqr; + + // if the start and end position are too far out or too close to each other + if ( length > len0 + len1 || length < idMath::Fabs( len0 - len1 ) ) { + jointPos = startPos + 0.5f * vec0; + return false; + } + + vec0 *= lengthInv; + vec1 = dir - vec0 * dir * vec0; + vec1.Normalize(); + + x = ( length * length + len0 * len0 - len1 * len1 ) * ( 0.5f * lengthInv ); + y = idMath::Sqrt( len0 * len0 - x * x ); + + jointPos = startPos + x * vec0 + y * vec1; + + return true; +} + +/* +================ +idIK::GetBoneAxis +================ +*/ +float idIK::GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ) { + float length; + axis[0] = endPos - startPos; + length = axis[0].Normalize(); + axis[1] = dir - axis[0] * dir * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[1], axis[0] ); + return length; +} + + +/* +=============================================================================== + + idIK_Walk + +=============================================================================== +*/ + +/* +================ +idIK_Walk::idIK_Walk +================ +*/ +idIK_Walk::idIK_Walk() { + int i; + + initialized = false; + footModel = NULL; + numLegs = 0; + enabledLegs = 0; + for ( i = 0; i < MAX_LEGS; i++ ) { + footJoints[i] = INVALID_JOINT; + ankleJoints[i] = INVALID_JOINT; + kneeJoints[i] = INVALID_JOINT; + hipJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + hipForward[i].Zero(); + kneeForward[i].Zero(); + upperLegLength[i] = 0.0f; + lowerLegLength[i] = 0.0f; + upperLegToHipJoint[i].Identity(); + lowerLegToKneeJoint[i].Identity(); + oldAnkleHeights[i] = 0.0f; + } + waistJoint = INVALID_JOINT; + + smoothing = 0.75f; + waistSmoothing = 0.5f; + footShift = 0.0f; + waistShift = 0.0f; + minWaistFloorDist = 0.0f; + minWaistAnkleDist = 0.0f; + footUpTrace = 32.0f; + footDownTrace = 32.0f; + tiltWaist = false; + usePivot = false; + + pivotFoot = -1; + pivotYaw = 0.0f; + pivotPos.Zero(); + + oldHeightsValid = false; + oldWaistHeight = 0.0f; + waistOffset.Zero(); +} + +/* +================ +idIK_Walk::~idIK_Walk +================ +*/ +idIK_Walk::~idIK_Walk() { + if ( footModel ) { + delete footModel; + } +} + +/* +================ +idIK_Walk::Save +================ +*/ +void idIK_Walk::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteClipModel( footModel ); + + savefile->WriteInt( numLegs ); + savefile->WriteInt( enabledLegs ); + savefile->Write( footJoints, sizeof( footJoints ) ); + savefile->Write( ankleJoints, sizeof( ankleJoints ) ); + savefile->Write( kneeJoints, sizeof( kneeJoints ) ); + savefile->Write( hipJoints, sizeof( hipJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + savefile->Write( &waistJoint, sizeof( waistJoint ) ); + + savefile->Write( hipForward, sizeof( hipForward ) ); + savefile->Write( kneeForward, sizeof( kneeForward ) ); + + savefile->Write( upperLegLength, sizeof( upperLegLength ) ); + savefile->Write( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Write( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Write( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->WriteFloat( smoothing ); + savefile->WriteFloat( waistSmoothing ); + savefile->WriteFloat( footShift ); + savefile->WriteFloat( waistShift ); + savefile->WriteFloat( minWaistFloorDist ); + savefile->WriteFloat( minWaistAnkleDist ); + savefile->WriteFloat( footUpTrace ); + savefile->WriteFloat( footDownTrace ); + savefile->WriteBool( tiltWaist ); + savefile->WriteBool( usePivot ); + + savefile->WriteInt( pivotFoot ); + savefile->WriteFloat( pivotYaw ); + savefile->WriteVec3( pivotPos ); + savefile->WriteBool( oldHeightsValid ); + savefile->WriteFloat( oldWaistHeight ); + savefile->Write( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->WriteVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Restore +================ +*/ +void idIK_Walk::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadClipModel( footModel ); + + savefile->ReadInt( numLegs ); + savefile->ReadInt( enabledLegs ); + savefile->Read( footJoints, sizeof( footJoints ) ); + savefile->Read( ankleJoints, sizeof( ankleJoints ) ); + savefile->Read( kneeJoints, sizeof( kneeJoints ) ); + savefile->Read( hipJoints, sizeof( hipJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + savefile->Read( &waistJoint, sizeof( waistJoint ) ); + + savefile->Read( hipForward, sizeof( hipForward ) ); + savefile->Read( kneeForward, sizeof( kneeForward ) ); + + savefile->Read( upperLegLength, sizeof( upperLegLength ) ); + savefile->Read( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Read( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Read( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->ReadFloat( smoothing ); + savefile->ReadFloat( waistSmoothing ); + savefile->ReadFloat( footShift ); + savefile->ReadFloat( waistShift ); + savefile->ReadFloat( minWaistFloorDist ); + savefile->ReadFloat( minWaistAnkleDist ); + savefile->ReadFloat( footUpTrace ); + savefile->ReadFloat( footDownTrace ); + savefile->ReadBool( tiltWaist ); + savefile->ReadBool( usePivot ); + + savefile->ReadInt( pivotFoot ); + savefile->ReadFloat( pivotYaw ); + savefile->ReadVec3( pivotPos ); + savefile->ReadBool( oldHeightsValid ); + savefile->ReadFloat( oldWaistHeight ); + savefile->Read( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->ReadVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Init +================ +*/ +bool idIK_Walk::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + float footSize; + idVec3 verts[4]; + idTraceModel trm; + const char *jointName; + idVec3 dir, ankleOrigin, kneeOrigin, hipOrigin, dirOrigin; + idMat3 axis, ankleAxis, kneeAxis, hipAxis; + + static idVec3 footWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !self ) { + return false; + } + + numLegs = Min( self->spawnArgs.GetInt( "ik_numLegs", "0" ), MAX_LEGS ); + if ( numLegs == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledLegs = 0; + + // get all the joints + for ( i = 0; i < numLegs; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_foot%d", i+1 ) ); + footJoints[i] = animator->GetJointHandle( jointName ); + if ( footJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid foot joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_ankle%d", i+1 ) ); + ankleJoints[i] = animator->GetJointHandle( jointName ); + if ( ankleJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid ankle joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_knee%d", i+1 ) ); + kneeJoints[i] = animator->GetJointHandle( jointName ); + if ( kneeJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid knee joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_hip%d", i+1 ) ); + hipJoints[i] = animator->GetJointHandle( jointName ); + if ( hipJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid hip joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_dir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledLegs |= 1 << i; + } + + jointName = self->spawnArgs.GetString( "ik_waist" ); + waistJoint = animator->GetJointHandle( jointName ); + if ( waistJoint == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid waist joint '%s'\n", jointName ); + } + + // get the leg bone lengths and rotation matrices + for ( i = 0; i < numLegs; i++ ) { + oldAnkleHeights[i] = 0.0f; + + ankleAxis = joints[ ankleJoints[ i ] ].ToMat3(); + ankleOrigin = joints[ ankleJoints[ i ] ].ToVec3(); + + kneeAxis = joints[ kneeJoints[ i ] ].ToMat3(); + kneeOrigin = joints[ kneeJoints[ i ] ].ToVec3(); + + hipAxis = joints[ hipJoints[ i ] ].ToMat3(); + hipOrigin = joints[ hipJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - kneeOrigin; + } else { + dir.Set( 1.0f, 0.0f, 0.0f ); + } + + hipForward[i] = dir * hipAxis.Transpose(); + kneeForward[i] = dir * kneeAxis.Transpose(); + + // conversion from upper leg bone axis to hip joint axis + upperLegLength[i] = GetBoneAxis( hipOrigin, kneeOrigin, dir, axis ); + upperLegToHipJoint[i] = hipAxis * axis.Transpose(); + + // conversion from lower leg bone axis to knee joint axis + lowerLegLength[i] = GetBoneAxis( kneeOrigin, ankleOrigin, dir, axis ); + lowerLegToKneeJoint[i] = kneeAxis * axis.Transpose(); + } + + smoothing = self->spawnArgs.GetFloat( "ik_smoothing", "0.75" ); + waistSmoothing = self->spawnArgs.GetFloat( "ik_waistSmoothing", "0.75" ); + footShift = self->spawnArgs.GetFloat( "ik_footShift", "0" ); + waistShift = self->spawnArgs.GetFloat( "ik_waistShift", "0" ); + minWaistFloorDist = self->spawnArgs.GetFloat( "ik_minWaistFloorDist", "0" ); + minWaistAnkleDist = self->spawnArgs.GetFloat( "ik_minWaistAnkleDist", "0" ); + footUpTrace = self->spawnArgs.GetFloat( "ik_footUpTrace", "32" ); + footDownTrace = self->spawnArgs.GetFloat( "ik_footDownTrace", "32" ); + tiltWaist = self->spawnArgs.GetBool( "ik_tiltWaist", "0" ); + usePivot = self->spawnArgs.GetBool( "ik_usePivot", "0" ); + + // setup a clip model for the feet + footSize = self->spawnArgs.GetFloat( "ik_footSize", "4" ) * 0.5f; + if ( footSize > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + verts[i] = footWinding[i] * footSize; + } + trm.SetupPolygon( verts, 4 ); + footModel = new idClipModel( trm ); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Walk::Evaluate +================ +*/ +void idIK_Walk::Evaluate( void ) { + int i, newPivotFoot = 0; + float modelHeight, jointHeight, lowestHeight, floorHeights[MAX_LEGS]; + float shift, smallestShift, newHeight, step, newPivotYaw, height, largestAnkleHeight; + idVec3 modelOrigin, normal, hipDir, kneeDir, start, end, jointOrigins[MAX_LEGS]; + idVec3 footOrigin, ankleOrigin, kneeOrigin, hipOrigin, waistOrigin; + idMat3 modelAxis, waistAxis, axis; + idMat3 hipAxis[MAX_LEGS], kneeAxis[MAX_LEGS], ankleAxis[MAX_LEGS]; + trace_t results; + + if ( !self || !gameLocal.isNewFrame ) { + return; + } + + // if no IK enabled on any legs + if ( !enabledLegs ) { + return; + } + + normal = - self->GetPhysics()->GetGravityNormal(); + modelOrigin = self->GetPhysics()->GetOrigin(); + modelAxis = self->GetRenderEntity()->axis; + modelHeight = modelOrigin * normal; + + modelOrigin += modelOffset * modelAxis; + + // create frame without joint mods + animator->CreateFrame( gameLocal.time, false ); + + // get the joint positions for the feet + lowestHeight = idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + animator->GetJointTransform( footJoints[i], gameLocal.time, footOrigin, axis ); + jointOrigins[i] = modelOrigin + footOrigin * modelAxis; + jointHeight = jointOrigins[i] * normal; + if ( jointHeight < lowestHeight ) { + lowestHeight = jointHeight; + newPivotFoot = i; + } + } + + if ( usePivot ) { + + newPivotYaw = modelAxis[0].ToYaw(); + + // change pivot foot + if ( newPivotFoot != pivotFoot || idMath::Fabs( idMath::AngleNormalize180( newPivotYaw - pivotYaw ) ) > 30.0f ) { + pivotFoot = newPivotFoot; + pivotYaw = newPivotYaw; + animator->GetJointTransform( footJoints[pivotFoot], gameLocal.time, footOrigin, axis ); + pivotPos = modelOrigin + footOrigin * modelAxis; + } + + // keep pivot foot in place + jointOrigins[pivotFoot] = pivotPos; + } + + // get the floor heights for the feet + for ( i = 0; i < numLegs; i++ ) { + + if ( !( enabledLegs & ( 1 << i ) ) ) { + continue; + } + + start = jointOrigins[i] + normal * footUpTrace; + end = jointOrigins[i] - normal * footDownTrace; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, mat3_identity, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + floorHeights[i] = results.endpos * normal; + + if ( ik_debug.GetBool() && footModel ) { + idFixedWinding w; + for ( int j = 0; j < footModel->GetTraceModel()->numVerts; j++ ) { + w += footModel->GetTraceModel()->verts[j]; + } + gameRenderWorld->DebugWinding( colorRed, w, results.endpos, results.endAxis ); + } + } + + const idPhysics *phys = self->GetPhysics(); + + // test whether or not the character standing on the ground + bool onGround = phys->HasGroundContacts(); + + // test whether or not the character is standing on a plat + bool onPlat = false; + for ( i = 0; i < phys->GetNumContacts(); i++ ) { + idEntity *ent = gameLocal.entities[ phys->GetContact( i ).entityNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != NULL && ent->IsType( idPlat::GetClassType() ) ) { +// RAVEN END + onPlat = true; + break; + } + } + + // adjust heights of the ankles + smallestShift = idMath::INFINITY; + largestAnkleHeight = -idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + + if ( onGround && ( enabledLegs & ( 1 << i ) ) ) { + shift = floorHeights[i] - modelHeight + footShift; + } else { + shift = 0.0f; + } + + if ( shift < smallestShift ) { + smallestShift = shift; + } + + animator->GetJointTransform( ankleJoints[i], gameLocal.time, ankleOrigin, ankleAxis[i] ); + jointOrigins[i] = modelOrigin + ankleOrigin * modelAxis; + + height = jointOrigins[i] * normal; + + if ( oldHeightsValid && !onPlat ) { + step = height + shift - oldAnkleHeights[i]; + shift -= smoothing * step; + } + + newHeight = height + shift; + if ( newHeight > largestAnkleHeight ) { + largestAnkleHeight = newHeight; + } + + oldAnkleHeights[i] = newHeight; + + jointOrigins[i] += shift * normal; + } + + animator->GetJointTransform( waistJoint, gameLocal.time, waistOrigin, waistAxis ); + waistOrigin = modelOrigin + waistOrigin * modelAxis; + + // adjust position of the waist + waistOffset = ( smallestShift + waistShift ) * normal; + + // if the waist should be at least a certain distance above the floor + if ( minWaistFloorDist > 0.0f && waistOffset * normal < 0.0f ) { + start = waistOrigin; + end = waistOrigin + waistOffset - normal * minWaistFloorDist; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, modelAxis, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + height = ( waistOrigin + waistOffset - results.endpos ) * normal; + if ( height < minWaistFloorDist ) { + waistOffset += ( minWaistFloorDist - height ) * normal; + } + } + + // if the waist should be at least a certain distance above the ankles + if ( minWaistAnkleDist > 0.0f ) { + height = ( waistOrigin + waistOffset ) * normal; + if ( height - largestAnkleHeight < minWaistAnkleDist ) { + waistOffset += ( minWaistAnkleDist - ( height - largestAnkleHeight ) ) * normal; + } + } + + if ( oldHeightsValid ) { + // smoothly adjust height of waist + newHeight = ( waistOrigin + waistOffset ) * normal; + step = newHeight - oldWaistHeight; + waistOffset -= waistSmoothing * step * normal; + } + + // save height of waist for smoothing + oldWaistHeight = ( waistOrigin + waistOffset ) * normal; + + if ( !oldHeightsValid ) { + oldHeightsValid = true; + return; + } + + // solve IK + for ( i = 0; i < numLegs; i++ ) { + + // get the position of the hip in world space + animator->GetJointTransform( hipJoints[i], gameLocal.time, hipOrigin, axis ); + hipOrigin = modelOrigin + waistOffset + hipOrigin * modelAxis; + hipDir = hipForward[i] * axis * modelAxis; + + // get the IK bend direction + animator->GetJointTransform( kneeJoints[i], gameLocal.time, kneeOrigin, axis ); + kneeDir = kneeForward[i] * axis * modelAxis; + + // solve IK and calculate knee position + SolveTwoBones( hipOrigin, jointOrigins[i], kneeDir, upperLegLength[i], lowerLegLength[i], kneeOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, hipOrigin, kneeOrigin ); + gameRenderWorld->DebugLine( colorRed, kneeOrigin, jointOrigins[i] ); + gameRenderWorld->DebugLine( colorYellow, kneeOrigin, kneeOrigin + hipDir ); + gameRenderWorld->DebugLine( colorGreen, kneeOrigin, kneeOrigin + kneeDir ); + } + + // get the axis for the hip joint + GetBoneAxis( hipOrigin, kneeOrigin, hipDir, axis ); + hipAxis[i] = upperLegToHipJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the knee joint + GetBoneAxis( kneeOrigin, jointOrigins[i], kneeDir, axis ); + kneeAxis[i] = lowerLegToKneeJoint[i] * ( axis * modelAxis.Transpose() ); + } + + // set the joint mods + animator->SetJointAxis( waistJoint, JOINTMOD_WORLD_OVERRIDE, waistAxis ); + animator->SetJointPos( waistJoint, JOINTMOD_WORLD_OVERRIDE, ( waistOrigin + waistOffset - modelOrigin ) * modelAxis.Transpose() ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_WORLD_OVERRIDE, hipAxis[i] ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_WORLD_OVERRIDE, kneeAxis[i] ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_WORLD_OVERRIDE, ankleAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Walk::ClearJointMods +================ +*/ +void idIK_Walk::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + animator->SetJointAxis( waistJoint, JOINTMOD_NONE, mat3_identity ); + animator->SetJointPos( waistJoint, JOINTMOD_NONE, vec3_origin ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} + +/* +================ +idIK_Walk::EnableAll +================ +*/ +void idIK_Walk::EnableAll( void ) { + enabledLegs = ( 1 << numLegs ) - 1; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::DisableAll +================ +*/ +void idIK_Walk::DisableAll( void ) { + enabledLegs = 0; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::EnableLeg +================ +*/ +void idIK_Walk::EnableLeg( int num ) { + enabledLegs |= 1 << num; +} + +/* +================ +idIK_Walk::DisableLeg +================ +*/ +void idIK_Walk::DisableLeg( int num ) { + enabledLegs &= ~( 1 << num ); +} + + +/* +=============================================================================== + + idIK_Reach + +=============================================================================== +*/ + +/* +================ +idIK_Reach::idIK_Reach +================ +*/ +idIK_Reach::idIK_Reach() { + int i; + + initialized = false; + numArms = 0; + enabledArms = 0; + for ( i = 0; i < MAX_ARMS; i++ ) { + handJoints[i] = INVALID_JOINT; + elbowJoints[i] = INVALID_JOINT; + shoulderJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + shoulderForward[i].Zero(); + elbowForward[i].Zero(); + upperArmLength[i] = 0.0f; + lowerArmLength[i] = 0.0f; + upperArmToShoulderJoint[i].Identity(); + lowerArmToElbowJoint[i].Identity(); + } +} + +/* +================ +idIK_Reach::~idIK_Reach +================ +*/ +idIK_Reach::~idIK_Reach() { +} + +/* +================ +idIK_Reach::Save +================ +*/ +void idIK_Reach::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteInt( numArms ); + savefile->WriteInt( enabledArms ); + savefile->Write( handJoints, sizeof( handJoints ) ); + savefile->Write( elbowJoints, sizeof( elbowJoints ) ); + savefile->Write( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + + savefile->Write( shoulderForward, sizeof( shoulderForward ) ); + savefile->Write( elbowForward, sizeof( elbowForward ) ); + + savefile->Write( upperArmLength, sizeof( upperArmLength ) ); + savefile->Write( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Write( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Write( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Restore +================ +*/ +void idIK_Reach::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadInt( numArms ); + savefile->ReadInt( enabledArms ); + savefile->Read( handJoints, sizeof( handJoints ) ); + savefile->Read( elbowJoints, sizeof( elbowJoints ) ); + savefile->Read( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + + savefile->Read( shoulderForward, sizeof( shoulderForward ) ); + savefile->Read( elbowForward, sizeof( elbowForward ) ); + + savefile->Read( upperArmLength, sizeof( upperArmLength ) ); + savefile->Read( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Read( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Read( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Init +================ +*/ +bool idIK_Reach::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + const char *jointName; + idTraceModel trm; + idVec3 dir, handOrigin, elbowOrigin, shoulderOrigin, dirOrigin; + idMat3 axis, handAxis, elbowAxis, shoulderAxis; + + if ( !self ) { + return false; + } + + numArms = Min( self->spawnArgs.GetInt( "ik_numArms", "0" ), MAX_ARMS ); + if ( numArms == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledArms = 0; + + // get all the joints + for ( i = 0; i < numArms; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_hand%d", i+1 ) ); + handJoints[i] = animator->GetJointHandle( jointName ); + if ( handJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid hand joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbow%d", i+1 ) ); + elbowJoints[i] = animator->GetJointHandle( jointName ); + if ( elbowJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid elbow joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_shoulder%d", i+1 ) ); + shoulderJoints[i] = animator->GetJointHandle( jointName ); + if ( shoulderJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid shoulder joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbowDir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledArms |= 1 << i; + } + + // get the arm bone lengths and rotation matrices + for ( i = 0; i < numArms; i++ ) { + + handAxis = joints[ handJoints[ i ] ].ToMat3(); + handOrigin = joints[ handJoints[ i ] ].ToVec3(); + + elbowAxis = joints[ elbowJoints[ i ] ].ToMat3(); + elbowOrigin = joints[ elbowJoints[ i ] ].ToVec3(); + + shoulderAxis = joints[ shoulderJoints[ i ] ].ToMat3(); + shoulderOrigin = joints[ shoulderJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - elbowOrigin; + } else { + dir.Set( -1.0f, 0.0f, 0.0f ); + } + + shoulderForward[i] = dir * shoulderAxis.Transpose(); + elbowForward[i] = dir * elbowAxis.Transpose(); + + // conversion from upper arm bone axis to should joint axis + upperArmLength[i] = GetBoneAxis( shoulderOrigin, elbowOrigin, dir, axis ); + upperArmToShoulderJoint[i] = shoulderAxis * axis.Transpose(); + + // conversion from lower arm bone axis to elbow joint axis + lowerArmLength[i] = GetBoneAxis( elbowOrigin, handOrigin, dir, axis ); + lowerArmToElbowJoint[i] = elbowAxis * axis.Transpose(); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Reach::Evaluate +================ +*/ +void idIK_Reach::Evaluate( void ) { + int i; + idVec3 modelOrigin, shoulderOrigin, elbowOrigin, handOrigin, shoulderDir, elbowDir; + idMat3 modelAxis, axis; + idMat3 shoulderAxis[MAX_ARMS], elbowAxis[MAX_ARMS]; + trace_t trace; + + modelOrigin = self->GetRenderEntity()->origin; + modelAxis = self->GetRenderEntity()->axis; + + // solve IK + for ( i = 0; i < numArms; i++ ) { + + // get the position of the shoulder in world space + animator->GetJointTransform( shoulderJoints[i], gameLocal.time, shoulderOrigin, axis ); + shoulderOrigin = modelOrigin + shoulderOrigin * modelAxis; + shoulderDir = shoulderForward[i] * axis * modelAxis; + + // get the position of the hand in world space + animator->GetJointTransform( handJoints[i], gameLocal.time, handOrigin, axis ); + handOrigin = modelOrigin + handOrigin * modelAxis; + + // get first collision going from shoulder to hand +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( self, trace, shoulderOrigin, handOrigin, CONTENTS_SOLID, self ); +// RAVEN END + handOrigin = trace.endpos; + + // get the IK bend direction + animator->GetJointTransform( elbowJoints[i], gameLocal.time, elbowOrigin, axis ); + elbowDir = elbowForward[i] * axis * modelAxis; + + // solve IK and calculate elbow position + SolveTwoBones( shoulderOrigin, handOrigin, elbowDir, upperArmLength[i], lowerArmLength[i], elbowOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, shoulderOrigin, elbowOrigin ); + gameRenderWorld->DebugLine( colorRed, elbowOrigin, handOrigin ); + gameRenderWorld->DebugLine( colorYellow, elbowOrigin, elbowOrigin + elbowDir ); + gameRenderWorld->DebugLine( colorGreen, elbowOrigin, elbowOrigin + shoulderDir ); + } + + // get the axis for the shoulder joint + GetBoneAxis( shoulderOrigin, elbowOrigin, shoulderDir, axis ); + shoulderAxis[i] = upperArmToShoulderJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the elbow joint + GetBoneAxis( elbowOrigin, handOrigin, elbowDir, axis ); + elbowAxis[i] = lowerArmToElbowJoint[i] * ( axis * modelAxis.Transpose() ); + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_WORLD_OVERRIDE, shoulderAxis[i] ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_WORLD_OVERRIDE, elbowAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Reach::ClearJointMods +================ +*/ +void idIK_Reach::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( handJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} diff --git a/src/game/IK.h b/src/game/IK.h new file mode 100644 index 0000000..a117f7c --- /dev/null +++ b/src/game/IK.h @@ -0,0 +1,178 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_IK_H__ +#define __GAME_IK_H__ + +/* +=============================================================================== + + IK base class with a simple fast two bone solver. + +=============================================================================== +*/ + +#define IK_ANIM "ik_pose" + +class idIK { +public: + idIK( void ); + virtual ~idIK( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsInitialized( void ) const; + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + bool SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ); + float GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ); + +protected: + bool initialized; + bool ik_activate; + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // animation modified by the IK + idVec3 modelOffset; +}; + + +/* +=============================================================================== + + IK controller for a walking character with an arbitrary number of legs. + +=============================================================================== +*/ + +class idIK_Walk : public idIK { +public: + + idIK_Walk( void ); + virtual ~idIK_Walk( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + void EnableAll( void ); + void DisableAll( void ); + void EnableLeg( int num ); + void DisableLeg( int num ); + +private: + static const int MAX_LEGS = 8; + + idClipModel * footModel; + + int numLegs; + int enabledLegs; + jointHandle_t footJoints[MAX_LEGS]; + jointHandle_t ankleJoints[MAX_LEGS]; + jointHandle_t kneeJoints[MAX_LEGS]; + jointHandle_t hipJoints[MAX_LEGS]; + jointHandle_t dirJoints[MAX_LEGS]; + jointHandle_t waistJoint; + + idVec3 hipForward[MAX_LEGS]; + idVec3 kneeForward[MAX_LEGS]; + + float upperLegLength[MAX_LEGS]; + float lowerLegLength[MAX_LEGS]; + + idMat3 upperLegToHipJoint[MAX_LEGS]; + idMat3 lowerLegToKneeJoint[MAX_LEGS]; + + float smoothing; + float waistSmoothing; + float footShift; + float waistShift; + float minWaistFloorDist; + float minWaistAnkleDist; + float footUpTrace; + float footDownTrace; + bool tiltWaist; + bool usePivot; + + // state + int pivotFoot; + float pivotYaw; + idVec3 pivotPos; + bool oldHeightsValid; + float oldWaistHeight; + float oldAnkleHeights[MAX_LEGS]; + idVec3 waistOffset; +}; + + +/* +=============================================================================== + + IK controller for reaching a position with an arm or leg. + +=============================================================================== +*/ + +class idIK_Reach : public idIK { +public: + + idIK_Reach( void ); + virtual ~idIK_Reach( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + +private: + + static const int MAX_ARMS = 2; + + int numArms; + int enabledArms; + jointHandle_t handJoints[MAX_ARMS]; + jointHandle_t elbowJoints[MAX_ARMS]; + jointHandle_t shoulderJoints[MAX_ARMS]; + jointHandle_t dirJoints[MAX_ARMS]; + + idVec3 shoulderForward[MAX_ARMS]; + idVec3 elbowForward[MAX_ARMS]; + + float upperArmLength[MAX_ARMS]; + float lowerArmLength[MAX_ARMS]; + + idMat3 upperArmToShoulderJoint[MAX_ARMS]; + idMat3 lowerArmToElbowJoint[MAX_ARMS]; +}; + +#endif /* !__GAME_IK_H__ */ diff --git a/src/game/Icon.cpp b/src/game/Icon.cpp new file mode 100644 index 0000000..d4562f3 --- /dev/null +++ b/src/game/Icon.cpp @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Icon.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Icon.h" + +/* +=============== +rvIcon::rvIcon +=============== +*/ +rvIcon::rvIcon() { + iconHandle = -1; +} + +/* +=============== +rvIcon::~rvIcon +=============== +*/ +rvIcon::~rvIcon() { + FreeIcon(); +} + +/* +=============== +rvIcon::FreeIcon +=============== +*/ +void rvIcon::FreeIcon( void ) { + if ( iconHandle != - 1 ) { + gameRenderWorld->FreeEntityDef( iconHandle ); + iconHandle = -1; + } +} + +/* +=============== +rvIcon::CreateIcon +=============== +*/ +qhandle_t rvIcon::CreateIcon( const char *mtr, int suppressViewID ) { + FreeIcon(); + + memset( &renderEnt, 0, sizeof( renderEnt ) ); + renderEnt.origin = vec3_origin; + renderEnt.axis = mat3_identity; + renderEnt.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 16.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 16.0f; + renderEnt.hModel = renderModelManager->FindModel( "_sprite" ); + renderEnt.callback = NULL; + renderEnt.numJoints = 0; + renderEnt.joints = NULL; + renderEnt.customSkin = 0; + renderEnt.noShadow = true; + renderEnt.noSelfShadow = true; + renderEnt.customShader = declManager->FindMaterial( mtr ); + renderEnt.referenceShader = 0; + renderEnt.bounds = renderEnt.hModel->Bounds( &renderEnt ); + renderEnt.suppressSurfaceInViewID = suppressViewID; + + iconHandle = gameRenderWorld->AddEntityDef( &renderEnt ); + + return iconHandle; +} + +/* +=============== +rvIcon::UpdateIcon +=============== +*/ +void rvIcon::UpdateIcon( const idVec3 &origin, const idMat3 &axis ) { + assert( iconHandle >= 0 ); + + renderEnt.origin = origin; + renderEnt.axis = axis; + gameRenderWorld->UpdateEntityDef( iconHandle, &renderEnt ); +} + +int rvIcon::GetWidth( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ]; +} + +int rvIcon::GetHeight( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ]; +} diff --git a/src/game/Icon.h b/src/game/Icon.h new file mode 100644 index 0000000..ef4b260 --- /dev/null +++ b/src/game/Icon.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Icon.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICON_H__ +#define __ICON_H__ + +class rvIcon { +public: + rvIcon(); + ~rvIcon(); + void UpdateIcon( const idVec3 &origin, const idMat3 &axis ); + qhandle_t CreateIcon( const char *mtr, int suppressViewID = 0 ); + void FreeIcon( void ); + qhandle_t GetHandle( void ) const; + + int GetHeight( void ) const; + int GetWidth( void ) const; +private: + void Draw( jointHandle_t joint ); + void Draw( const idVec3 &origin ); + + renderEntity_t renderEnt; + qhandle_t iconHandle; +}; + +ID_INLINE qhandle_t rvIcon::GetHandle( void ) const { + return iconHandle; +} + +#endif /* !_ICON_H_ */ + diff --git a/src/game/IconManager.cpp b/src/game/IconManager.cpp new file mode 100644 index 0000000..ce0671e --- /dev/null +++ b/src/game/IconManager.cpp @@ -0,0 +1,229 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// IconManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "IconManager.h" + +rvIconManager iconManagerLocal; +rvIconManager* iconManager = &iconManagerLocal; + +/* +=============================================================================== + + rvIconManager + +=============================================================================== +*/ + +void rvIconManager::AddIcon( int clientNum, const char* iconName ) { + assert( gameLocal.GetLocalPlayer() ); + + idPlayer* player = gameLocal.GetLocalPlayer(); + + icons[ clientNum ].Append( rvPair(new rvIcon(), gameLocal.time + ICON_STAY_TIME) ); + icons[ clientNum ][ icons[ clientNum ].Num() - 1 ].First()->CreateIcon( player->spawnArgs.GetString( iconName ), (clientNum == gameLocal.localClientNum ? gameLocal.localClientNum + 1 : 0) ); +} + +void rvIconManager::UpdateIcons( void ) { + if( gameLocal.GetLocalPlayer() == NULL || !gameLocal.GetLocalPlayer()->GetRenderView() ) { + return; + } + + // draw team icons + if( gameLocal.IsTeamGame() ) { + UpdateTeamIcons(); + } + + // draw chat icons + UpdateChatIcons(); + + // remove old icons and icons not in our snapshot + // ** if you want to have permanent icons, you'll have to add support + // ** for the icons to re-appear when the player comes back in your snapshot (like team icons and chat icons) + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( gameLocal.time > icons[ i ][ j ].Second() || (gameLocal.entities[ i ] && gameLocal.entities[ i ]->fl.networkStale) ) { + rvIcon* oldIcon = icons[ i ][ j ].First(); + oldIcon->FreeIcon(); + icons[ i ].RemoveIndex( j-- ); + delete oldIcon; + } + } + } + + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + // draw extra icons + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + if( player->IsHidden() || !icons[ i ].Num() ) { + continue; + } + + // distribute the icons appropriately + int maxHeight = 0; + int totalWidth = 0; + + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + totalWidth += icons[ i ][ j ].First()->GetWidth(); + } + + idVec3 centerIconPosition = player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + + + if( teamIcons[ player->entityNumber ].GetHandle() >= 0 ) { + centerIconPosition[ 2 ] += teamIcons[ player->entityNumber ].GetHeight(); + } + + int incrementalWidth = 0; + for( int j = 0; j < icons[ i ].Num(); j++ ) { + idVec3 iconPosition = centerIconPosition; + iconPosition += ( (-totalWidth / 2) + incrementalWidth + (icons[ i ][ j ].First()->GetWidth() / 2) ) * localPlayer->GetRenderView()->viewaxis[ 1 ]; + incrementalWidth += icons[ i ][ j ].First()->GetWidth(); + icons[ i ][ j ].First()->UpdateIcon( player->GetPhysics()->GetOrigin() + iconPosition, localPlayer->GetRenderView()->viewaxis ); + } + } + } +} + +void rvIconManager::UpdateTeamIcons( void ) { + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + return; + } + int localTeam = localPlayer->team; + bool spectating = localPlayer->spectating; + + if( localPlayer->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ localPlayer->spectator ]; + if( spec ) { + localTeam = spec->team; + localPlayer = spec; + } else { + localTeam = -1; + } + } + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + //if entity i is a player, manage his icon. + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale && !spectating ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + //if the player is alive and not hidden, show his icon. + if( player->team == localTeam && !player->IsHidden() && !player->pfl.dead && gameLocal.mpGame.IsInGame( i ) ) { + if( teamIcons[ i ].GetHandle() < 0 ) { + teamIcons[ i ].CreateIcon( player->spawnArgs.GetString( player->team ? "mtr_team_icon_strogg" : "mtr_team_icon_marine" ), (player == localPlayer ? localPlayer->entityNumber + 1 : 0) ); + } + teamIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ), localPlayer->GetRenderView()->viewaxis ); + //else, the player is hidden, dead, or otherwise not needing an icon-- free it. + } else { + if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } + //if entity i is not a player, free icon i from the map. + } else if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } +} + +void rvIconManager::UpdateChatIcons( void ) { + + int localInst = gameLocal.GetLocalPlayer()->GetInstance(); + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( gameLocal.localClientNum == i ) { + continue; + } + + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[ i ] ); + + if ( player->isChatting && + !player->IsHidden() && + !( ( idPhysics_Player* )player->GetPhysics() )->IsDead() && + gameLocal.mpGame.IsInGame( i ) + && (localInst == player->GetInstance())) { + if ( chatIcons[ i ].GetHandle() < 0 ) { + chatIcons[ i ].CreateIcon( player->spawnArgs.GetString( "mtr_icon_chatting" ), ( player == gameLocal.GetLocalPlayer() ? player->entityNumber + 1 : 0) ); + } + int maxHeight = 0; + for ( int j = 0; j < icons[ i ].Num(); j++ ) { + if ( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + } + if ( teamIcons[ i ].GetHandle() >= 0 && teamIcons[ i ].GetHeight() > maxHeight ) { + maxHeight = teamIcons[ i ].GetHeight(); + } + idVec3 centerIconPosition = player->spawnArgs.GetVector( ( player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + centerIconPosition[ 2 ] += maxHeight; + chatIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + centerIconPosition, gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis ); + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } +} + +/* +=============== +rvIconManager::Shutdown +=============== +*/ +void rvIconManager::Shutdown( void ) { + int i, j; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < icons[ i ].Num(); j++ ) { + icons[ i ][ j ].First()->FreeIcon(); + } + icons[ i ].Clear(); + teamIcons[ i ].FreeIcon(); + chatIcons[ i ].FreeIcon(); + } +} diff --git a/src/game/IconManager.h b/src/game/IconManager.h new file mode 100644 index 0000000..61ebd9b --- /dev/null +++ b/src/game/IconManager.h @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// IconManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICONMANAGER_H__ +#define __ICONMANAGER_H__ + +#include "Icon.h" + +const int ICON_STAY_TIME = 2000; + +class rvIconManager { +public: + void AddIcon( int clientNum, const char* iconName ); + void UpdateIcons( void ); + void UpdateTeamIcons( void ); + void UpdateChatIcons( void ); + void Shutdown( void ); + +private: + idList > icons[ MAX_CLIENTS ]; + rvIcon teamIcons[ MAX_CLIENTS ]; + rvIcon chatIcons[ MAX_CLIENTS ]; +}; + +extern rvIconManager* iconManager; + +#endif diff --git a/src/game/Instance.cpp b/src/game/Instance.cpp new file mode 100644 index 0000000..6b8651d --- /dev/null +++ b/src/game/Instance.cpp @@ -0,0 +1,257 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Instance.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Instance.h" + +rvInstance::rvInstance( int id, bool deferPopulate ) { + instanceID = id; + spawnInstanceID = id; + + gameLocal.AddClipWorld( id ); + + mapEntityNumbers = NULL; + numMapEntities = 0; + + initialSpawnCount = idGameLocal::INITIAL_SPAWN_COUNT; + + if ( !deferPopulate ) { + Populate(); + } +} + +rvInstance::~rvInstance() { + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + } + gameLocal.RemoveClipWorld( instanceID ); +} + +void rvInstance::Populate( int serverChecksum ) { + gameState_t currentState = gameLocal.GameState(); + + // disable the minSpawnIndex lock out + int latchMinSpawnIndex = gameLocal.minSpawnIndex; + gameLocal.minSpawnIndex = MAX_CLIENTS; + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( GAMESTATE_RESTART ); + } + + if ( gameLocal.isServer ) { + // When populating on a server, record the entity numbers + numMapEntities = gameLocal.GetNumMapEntities(); + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + } + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + // read the index we should start populating at as transmitted by the server + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + //common->Printf( "pos: get starting index for instance %d sets firstFreeIndex to %d\n", instanceID, gameLocal.firstFreeIndex ); + + // remember the spawnCount ahead of time, so that the client can accurately reconstruct its spawnIds + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers, &initialSpawnCount ); + + // only build the message in MP + if ( gameLocal.isMultiplayer ) { + BuildInstanceMessage(); + + // force joins of anyone in our instance so they get potentially new map entitynumbers + for( int i = 0; i < MAX_CLIENTS; i++ ) { + PACIFIER_UPDATE; + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player && player->GetInstance() == instanceID ) { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg ); + } + } + } + } else { + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + // When populating on a client, spawn the map using existing numbers + // check for good state + // OK to spawn w/o specific entity numbers if we're in the startup process. Otherwise, + // we need entity numbers from the server. + // TTimo: only valid for backward 1.2 playback now + assert( !proto69 || ( mapEntityNumbers || ( instanceID == 0 && gameLocal.GameState() == GAMESTATE_STARTUP ) ) ); + + if ( !proto69 ) { + // have the client produce a log of the entity layout so we can match it with the server's + // this is also going to be used to issue the EV_FindTargets below + if ( mapEntityNumbers ) { + delete []mapEntityNumbers; + } + numMapEntities = gameLocal.GetNumMapEntities(); + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + } + + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + + gameLocal.SetSpawnCount( initialSpawnCount ); // that was transmitted through the instance msg + if ( proto69 ) { + gameLocal.SpawnMapEntities( spawnInstanceID, mapEntityNumbers, NULL ); + } else { + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers ); + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + if ( serverChecksum != 0 && checksum != serverChecksum ) { + common->Error( "client side map populate checksum ( 0x%x ) doesn't match server's ( 0x%x )", checksum, serverChecksum ); + } + } + } + + + for ( int i = 0; i < numMapEntities; i++ ) { + if ( mapEntityNumbers[ i ] < 0 || mapEntityNumbers[ i ] >= MAX_GENTITIES ) { + continue; + } + + if ( (i % 100) == 0 ) { + PACIFIER_UPDATE; + } + + idEntity* ent = gameLocal.entities[ mapEntityNumbers[ i ] ]; + + if ( ent ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + ent->PostEventMS( &EV_PostSpawn, 0 ); + } + } + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( currentState ); + } + + // re-enable the min spawn index + assert( latchMinSpawnIndex == MAX_CLIENTS || gameLocal.firstFreeIndex <= latchMinSpawnIndex ); + gameLocal.minSpawnIndex = latchMinSpawnIndex; +} + +void rvInstance::PopulateFromMessage( const idBitMsg& msg ) { + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + if ( proto69 ) { + numMapEntities = msg.ReadShort(); + } + initialSpawnCount = msg.ReadShort(); + + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + + if ( proto69 ) { + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + for ( int i = 0; i < numMapEntities; i++ ) { + mapEntityNumbers[ i ] = msg.ReadShort(); + } + Populate(); + } else { + int populateIndex = msg.ReadLong(); + gameLocal.ClientSetStartingIndex( populateIndex ); + //common->Printf( "pos: set firstFreeIndex to %d\n", populateIndex ); + int checksum = msg.ReadLong(); + Populate( checksum ); + } +} + +void rvInstance::Restart( void ) { + if ( gameLocal.isMultiplayer ) { + Populate(); + } else { + gameLocal.SpawnMapEntities(); + } +} + +void rvInstance::BuildInstanceMessage( void ) { + // Build the client join instance msg + mapEntityMsg.BeginWriting(); + mapEntityMsg.Init( mapEntityMsgBuf, sizeof( byte ) * MAX_GAME_MESSAGE_SIZE ); + mapEntityMsg.WriteByte( GAME_RELIABLE_MESSAGE_SET_INSTANCE ); + + mapEntityMsg.WriteByte( instanceID ); + mapEntityMsg.WriteShort( initialSpawnCount ); + // we need to send that down for tourney so the index will match + mapEntityMsg.WriteLong( gameLocal.GetStartingIndexForInstance( instanceID ) ); + + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + //common->Printf( "pop: server checksum: 0x%x\n", checksum ); + mapEntityMsg.WriteLong( checksum ); +} + +void rvInstance::JoinInstance( idPlayer* player ) { + assert( player && gameLocal.isServer ); + + // Transmit the instance information to the new client + if( gameLocal.isListenServer && player == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ServerSetInstance( instanceID ); + } else { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg ); + } +} + +void rvInstance::PrintMapNumbers( void ) { + gameLocal.Printf( "Instance: %d\n", instanceID ); + gameLocal.Printf( "Num Map Entities: %d\n", numMapEntities ); + + for( int i = 0; i < numMapEntities; i++ ) { + gameLocal.Printf( "%d\n", mapEntityNumbers[ i ] ); + } +} + +/* +================ +rvInstance::SetSpawnInstanceID +Sets the spawn instance ID for this instance. On the client, only instance 0 is ever +used, but it spawns in map entities for other instances. spawnInstanceID is used to +spawn map entities with the correct instance number on the client. +================ +*/ +void rvInstance::SetSpawnInstanceID( int newInstance ) { + assert( gameLocal.isClient ); + + spawnInstanceID = newInstance; +} diff --git a/src/game/Instance.h b/src/game/Instance.h new file mode 100644 index 0000000..2a115ee --- /dev/null +++ b/src/game/Instance.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Instance.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __INSTANCE_H__ +#define __INSTANCE_H__ + +#include "Game_local.h" + +class rvInstance { +public: + rvInstance( int id, bool deferPopulate = false ); + ~rvInstance(); + + void Populate( int serverChecksum = 0 ); + void PopulateFromMessage( const idBitMsg& msg ); + void Restart( void ); + + void JoinInstance( idPlayer* player ); + int GetInstanceID( void ); + + void SetSpawnInstanceID( int newInstance ); + + void PrintMapNumbers( void ); + int GetNumMapEntities( void ) { return numMapEntities; } + unsigned short GetMapEntityNumber( int i ) { return mapEntityNumbers[ i ]; } + +private: + void BuildInstanceMessage( void ); + + int instanceID; + int spawnInstanceID; + unsigned short* mapEntityNumbers; + int numMapEntities; + int initialSpawnCount; + + idBitMsg mapEntityMsg; + byte mapEntityMsgBuf[ MAX_GAME_MESSAGE_SIZE ]; +}; + +ID_INLINE int rvInstance::GetInstanceID( void ) { + return instanceID; +} + +#endif diff --git a/src/game/Item.cpp b/src/game/Item.cpp new file mode 100644 index 0000000..35e20a7 --- /dev/null +++ b/src/game/Item.cpp @@ -0,0 +1,2399 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + idItem + +=============================================================================== +*/ + +const idEventDef EV_DropToFloor( "" ); +const idEventDef EV_RespawnItem( "respawn" ); +const idEventDef EV_RespawnFx( "" ); +const idEventDef EV_GetPlayerPos( "" ); +const idEventDef EV_HideObjective( "", "e" ); +const idEventDef EV_CamShot( "" ); + +// RAVEN BEGIN +// abahr: +const idEventDef EV_SetGravity( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idItem ) + EVENT( EV_DropToFloor, idItem::Event_DropToFloor ) + EVENT( EV_Touch, idItem::Event_Touch ) + EVENT( EV_Activate, idItem::Event_Trigger ) + EVENT( EV_RespawnItem, idItem::Event_Respawn ) + EVENT( EV_RespawnFx, idItem::Event_RespawnFx ) +// RAVEN BEGIN +// abahr + EVENT( EV_SetGravity, idItem::Event_SetGravity ) +// RAVEN END + +END_CLASS + + +/* +================ +idItem::idItem +================ +*/ +idItem::idItem() { + spin = false; + inView = false; + skin = NULL; + pickupSkin = NULL; + inViewTime = 0; + lastCycle = 0; + lastRenderViewTime = -1; + itemShellHandle = -1; + shellMaterial = NULL; + orgOrigin.Zero(); + canPickUp = true; + fl.networkSync = true; + trigger = NULL; + syncPhysics = false; + srvReady = -1; + clReady = -1; + effectIdle = NULL; + itemPVSArea = 0; + effectIdle = NULL; + simpleItem = false; + pickedUp = false; +} + +/* +================ +idItem::~idItem +================ +*/ +idItem::~idItem() { + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + } + if ( trigger ) { + delete trigger; + } + + SetPhysics( NULL ); +} + +/* +================ +idItem::Save +================ +*/ +void idItem::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteBool( spin ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( pickupSkin ); + + savefile->WriteVec3( orgOrigin ); + + savefile->WriteBool( pulse ); + savefile->WriteBool( canPickUp ); + + savefile->WriteStaticObject( physicsObj ); + +// savefile->WriteInt(itemShellHandle); // cnicholson: Set at end of Restore, do not save + savefile->WriteMaterial( shellMaterial ); + + savefile->WriteBool( inView ); + savefile->WriteInt( inViewTime ); + savefile->WriteInt( lastCycle ); + savefile->WriteInt( lastRenderViewTime ); +} + +/* +================ +idItem::Restore +================ +*/ +void idItem::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadBool( spin ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( pickupSkin ); + + savefile->ReadVec3( orgOrigin ); + + savefile->ReadBool( pulse ); + savefile->ReadBool( canPickUp ); + + savefile->ReadStaticObject ( physicsObj ); + +// savefile->ReadInt(itemShellHandle); // cnicholson: Set at end of function, do not restore + savefile->ReadMaterial( shellMaterial ); + + savefile->ReadBool( inView ); + savefile->ReadInt( inViewTime ); + savefile->ReadInt( lastCycle ); + savefile->ReadInt( lastRenderViewTime ); + + RestorePhysics( &physicsObj ); + + physicsObj.SetSelf( this ); + + itemShellHandle = -1; +} + +/* +================ +idItem::UpdateRenderEntity +================ +*/ +bool idItem::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + if( simpleItem ) { + return false; + } + + if ( lastRenderViewTime == renderView->time ) { + return false; + } + + lastRenderViewTime = renderView->time; + + // check for glow highlighting if near the center of the view + idVec3 dir = renderEntity->origin - renderView->vieworg; + dir.Normalize(); + float d = dir * renderView->viewaxis[0]; + + // two second pulse cycle + float cycle = ( renderView->time - inViewTime ) / 2000.0f; + + if ( d > 0.94f ) { + if ( !inView ) { + inView = true; + if ( cycle > lastCycle ) { + // restart at the beginning + inViewTime = renderView->time; + cycle = 0.0f; + } + } + } else { + if ( inView ) { + inView = false; + lastCycle = ceil( cycle ); + } + } + + // fade down after the last pulse finishes + if ( !inView && cycle > lastCycle ) { + renderEntity->shaderParms[4] = 0.0f; + } else { + // pulse up in 1/4 second + cycle -= (int)cycle; + if ( cycle < 0.1f ) { + renderEntity->shaderParms[4] = cycle * 10.0f; + } else if ( cycle < 0.2f ) { + renderEntity->shaderParms[4] = 1.0f; + } else if ( cycle < 0.3f ) { + renderEntity->shaderParms[4] = 1.0f - ( cycle - 0.2f ) * 10.0f; + } else { + // stay off between pulses + renderEntity->shaderParms[4] = 0.0f; + } + } + + // update every single time this is in view + return true; +} + +/* +================ +idItem::UpdateTrigger +================ +*/ +void idItem::UpdateTrigger ( void ) { + // update trigger position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END +} + +/* +================ +idItem::ModelCallback +================ +*/ +bool idItem::ModelCallback( renderEntity_t *renderEntity, const renderView_t *renderView ) { + const idItem *ent; + + // this may be triggered by a model trace or other non-view related source + if ( !renderView ) { + return false; + } + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idItem::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + + +/* +================ +idItem::GetPhysicsToVisualTransform +================ +*/ +bool idItem::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if( simpleItem ) { + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetRenderView() ) { + if( gameLocal.GetLocalPlayer()->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ gameLocal.GetLocalPlayer()->spectator ]; + if( spec && spec->GetRenderView() ) { + axis = spec->GetRenderView()->viewaxis; + } + } else { + axis = gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis; + } + } else { + // dedicated server for instance + axis = mat3_identity; + } + origin = idVec3( 0.0f, 0.0f, 32.0f ); + return true; + } + + if( !spin || (gameLocal.isServer && !gameLocal.isListenServer) ) { + return false; + } + + idAngles ang; + ang.pitch = ang.roll = 0.0f; + ang.yaw = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; + axis = ang.ToMat3() * GetPhysics()->GetAxis(); + + float scale = 0.005f; + float offset = entityNumber * 0.685145f; // rjohnson: just a random number here to shift the cos curve + + origin.Zero(); + + origin += GetPhysics()->GetAxis()[2] * (4.0f + idMath::Cos( ( ( gameLocal.time + 1000 ) * scale ) + offset ) * 4.0f); + + return true; +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idItem::Collide +================ +*/ +bool idItem::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_Remove, 0 ); + } + return false; +} +// RAVEN END + +/* +================ +idItem::Think +================ +*/ +void idItem::Think( void ) { + if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + UpdateTrigger(); + } + + if ( gameLocal.IsMultiplayer() && g_skipItemShadowsMP.GetBool() ) { + renderEntity.suppressShadowInViewID = gameLocal.localClientNum + 1; + } else { + renderEntity.suppressShadowInViewID = 0; + } + + if( !(simpleItem && pickedUp) ) { + UpdateVisuals(); + Present(); + } +} + +/* +================ +idItem::Present +================ +*/ +void idItem::Present( void ) { + idEntity::Present(); + + if ( !fl.hidden && pulse ) { + // also add a highlight shell model + renderEntity_t shell; + + shell = renderEntity; + + // we will mess with shader parms when the item is in view + // to give the "item pulse" effect + shell.callback = idItem::ModelCallback; + shell.entityNum = entityNumber; + shell.customShader = shellMaterial; + if ( itemShellHandle == -1 ) { + itemShellHandle = gameRenderWorld->AddEntityDef( &shell ); + } else { + gameRenderWorld->UpdateEntityDef( itemShellHandle, &shell ); + } + } +} + +/* +================ +idItem::InstanceJoin +================ +*/ +void idItem::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::InstanceLeave +================ +*/ +void idItem::InstanceLeave( void ) { + idEntity::InstanceLeave(); + + StopEffect( "fx_idle", true ); +} + +/* +================ +idItem::Spawn +================ +*/ +void idItem::Spawn( void ) { + idStr giveTo; + idEntity * ent; + idVec3 vSize; + idBounds bounds(vec3_origin); + + // check for triggerbounds, which allows for non-square triggers (useful for, say, a CTF flag) + if ( spawnArgs.GetVector( "triggerbounds", "16 16 16", vSize )) { + bounds.AddPoint(idVec3( vSize.x*0.5f, vSize.y*0.5f, 0.0f)); + bounds.AddPoint(idVec3(-vSize.x*0.5f, -vSize.y*0.5f, vSize.z)); + } + else { + // create a square trigger for item pickup + float tsize; + spawnArgs.GetFloat( "triggersize", "16.0", tsize ); + bounds.ExpandSelf( tsize ); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds )); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + + physicsObj.SetSelf ( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_SOLID ); + physicsObj.SetFriction( 0.0f, 0.0f, 6.0f ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_off" ) ) { + trigger->SetContents( 0 ); + Hide(); + } else { + trigger->SetContents( CONTENTS_TRIGGER ); + } + + giveTo = spawnArgs.GetString( "owner" ); + if ( giveTo.Length() ) { + ent = gameLocal.FindEntity( giveTo ); + if ( !ent ) { + gameLocal.Error( "Item couldn't find owner '%s'", giveTo.c_str() ); + } + PostEventMS( &EV_Touch, 0, ent, NULL ); + } + + if ( spawnArgs.GetBool( "spin" ) || gameLocal.isMultiplayer ) { + spin = true; + BecomeActive( TH_THINK ); + } + + // pulse ( and therefore itemShellHandle ) was taken out and shot. do not sync + //pulse = !spawnArgs.GetBool( "nopulse" ); + pulse = false; + orgOrigin = GetPhysics()->GetOrigin(); + + canPickUp = !( spawnArgs.GetBool( "triggerFirst" ) || spawnArgs.GetBool( "no_touch" ) ); + + inViewTime = -1000; + lastCycle = -1; + itemShellHandle = -1; +// RAVEN BEGIN +// abahr: move texture to def file for precaching + shellMaterial = declManager->FindMaterial( spawnArgs.GetString("mtr_highlight", "_default") ); + PostEventMS( &EV_SetGravity, 0 ); +// RAVEN END + if ( spawnArgs.GetString( "skin", NULL ) ) { + skin = declManager->FindSkin( spawnArgs.GetString( "skin" ), false ); + if( skin ) { + SetSkin( skin ); + srvReady = 1; + } + } else { + skin = NULL; + } + + if ( spawnArgs.GetString( "skin_pickup", NULL ) ) { + pickupSkin = declManager->FindSkin( spawnArgs.GetString( "skin_pickup" ), false ); + } else { + pickupSkin = NULL; + } + + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( srvReady == -1 ) { + srvReady = IsHidden() ? 0 : 1; + } + +// RAVEN BEGIN +// mekberg: added for removing pickups in mp in pits + if ( gameLocal.isMultiplayer ) { + trigger->SetContents( trigger->GetContents() | CONTENTS_ITEMCLIP ); + } +// RAVEN END + + if( gameLocal.isMultiplayer ) { + itemPVSArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + } else { + itemPVSArea = 0; + } + + simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !IsType( rvItemCTFFlag::GetClassType() ); + if( simpleItem ) { + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.axis = mat3_identity; + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 32.0f; + renderEntity.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 32.0f; + renderEntity.hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + renderEntity.customSkin = 0; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customShader = declManager->FindMaterial( spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity.referenceShader = 0; + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + SetAxis( mat3_identity ); + } else { + if ( spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + pickedUp = false; +} + +/* +================ +idItem::Event_SetGravity +================ +*/ +void idItem::Event_SetGravity() { + // If the item isnt a dropped item then see if it should settle itself + // to the floor or not + if ( !spawnArgs.GetBool( "dropped" ) ) { + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + PostEventMS( &EV_DropToFloor, 0 ); + } + } +} +// RAVEN END + +/* +================ +idItem::GetAttributes +================ +*/ +void idItem::GetAttributes( idDict &attributes ) { + int i; + const idKeyValue *arg; + + for( i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) { + arg = spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Left( 4 ) == "inv_" ) { + attributes.Set( arg->GetKey().Right( arg->GetKey().Length() - 4 ), arg->GetValue() ); + } + } +} + +/* +================ +idItem::GiveToPlayer +================ +*/ +bool idItem::GiveToPlayer( idPlayer *player ) { + if ( player == NULL ) { + return false; + } + + if ( spawnArgs.GetBool( "inv_carry" ) ) { + return player->GiveInventoryItem( &spawnArgs ); + } + + // Handle the special ammo pickup that gives ammo for the weapon the player currently has + if ( spawnArgs.GetBool( "item_currentWeaponAmmo" ) ) { + const char *ammoName = player->weapon->GetAmmoNameForIndex(player->weapon->GetAmmoType()); + if ( player->weapon->TotalAmmoCount() != player->weapon->maxAmmo && player->weapon->AmmoRequired() ) { + player->GiveItem(ammoName); + player->GiveInventoryItem( &spawnArgs ); + return true; + } + return false; + } + + return player->GiveItem( this ); +} + +/* +=============== +idItem::SendPickupMsg +=============== +*/ +void idItem::SendPickupMsg( int clientNum ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_PICKUP ); + msg.WriteByte( clientNum ); + + // send as unreliable to client picking up the item, so it can play HUD things + gameLocal.SendUnreliableMessagePVS( msg, this, itemPVSArea ); +} + +/* +================ +idItem::Pickup +================ +*/ +bool idItem::Pickup( idPlayer *player ) { + //dropped weapon? + bool dropped = spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && !dropped && spawnArgs.FindKey( "weaponclass" ) + && gameLocal.IsWeaponsStayOn() && gameLocal.time > player->lastPickupTime + 1000 ) { + + idDict attr; + GetAttributes( attr ); + const idKeyValue* arg = attr.FindKey( "weapon" ); + + if ( arg ) { + if ( !player->inventory.Give( player, player->spawnArgs, arg->GetKey(), arg->GetValue(), NULL, false, dropped, true ) ) { + StartSound( "snd_noacquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + } + + // only predict noacquire on client + if ( gameLocal.isClient ) { + return false; + } + + int givenToPlayer = spawnArgs.GetInt( "givenToPlayer", "-1" ); + if ( player == NULL || ( givenToPlayer != -1 && givenToPlayer != player->entityNumber ) ) { + // idPlayer::GiveItem spawns an idItem for pickup, which appears at the origin before being picked + // and could sometimes be picked by someone else, particularly in buy mode when there is a play spawn sitting on the origin ;-) + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + player->lastPickupTime = gameLocal.time; + + //if a placed item and si_weaponStay is on and we're a weapon, don't remove and respawn + if ( gameLocal.IsMultiplayer() ) { + if ( !dropped ) { + if ( spawnArgs.FindKey( "weaponclass" ) ) { + if ( gameLocal.IsWeaponsStayOn() ) { + return true; + } + } + } + } + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model, or switch to the pickup skin + if ( pickupSkin ) { + SetSkin( pickupSkin ); + srvReady = 0; + } else { + Hide(); + BecomeInactive( TH_THINK ); + } + + pickedUp = true; + + // allow SetSkin or Hide() to get called regardless of simpleitem mode + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + itemShellHandle = -1; + } + + // asalmon: Added option for a differnt respawn rate based on gametype. + float respawn = spawnArgs.GetFloat(va("respawn_%s",gameLocal.serverInfo.GetString( "si_gameType" )), "-1.0"); + if( respawn == -1.0f ) { + respawn = spawnArgs.GetFloat( "respawn", "5.0" ); + } + + bool no_respawn = spawnArgs.GetBool( "no_respawn" ); + + if ( !gameLocal.isMultiplayer ) { + respawn = 0.0f; + } else if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( givenToPlayer != -1 ) { + respawn = 0.0f; + } + } + + if ( respawn && !dropped && !no_respawn ) { + const char *sfx = spawnArgs.GetString( "fx_Respawn" ); + if ( sfx && *sfx ) { + PostEventSec( &EV_RespawnFx, respawn - 0.5f ); + } + PostEventSec( &EV_RespawnItem, respawn ); + } else if ( !spawnArgs.GetBool( "inv_objective" ) && !no_respawn ) { + // give some time for the pickup sound to play + // FIXME: Play on the owner + if ( !spawnArgs.GetBool( "inv_carry" ) ) { + PostEventMS( &EV_Remove, 5000 ); + } + } + + trigger->SetContents( 0 ); + + StopEffect( "fx_idle" ); + + return true; +} + +/* +================ +idItem::Hide +================ +*/ +void idItem::Hide( void ) { + srvReady = 0; + idEntity::Hide( ); + trigger->SetContents( 0 ); +} + +/* +================ +idItem::Show +================ +*/ +void idItem::Show( void ) { + srvReady = 1; + idEntity::Show( ); + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +================ +idItem::ClientStale +================ +*/ +bool idItem::ClientStale( void ) { + idEntity::ClientStale(); + + StopEffect( "fx_idle" ); + return false; +} + +/* +================ +idItem::ClientUnstale +================ +*/ +void idItem::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::ClientPredictionThink +================ +*/ +void idItem::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + Think(); +} + +/* +================ +idItem::WriteFromSnapshot +================ +*/ +void idItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + assert( srvReady != -1 ); + msg.WriteBits( ( srvReady == 1 ), 1 ); +} + +/* +================ +idItem::ReadFromSnapshot +================ +*/ +void idItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + int newReady = ( msg.ReadBits( 1 ) != 0 ); + idVec3 resetOrigin( 0, 0, 0 ); + // client spawns the ent with ready == -1 so the state set happens at least once + if ( newReady != clReady ) { + if ( newReady ) { + // g_simpleItems might force a hide even with a pickup skin + if ( pickupSkin ) { + SetSkin( skin ); + } else { + SetSkin( skin ); + Show(); + } + + if( simpleItem ) { + UpdateVisuals(); + } + pickedUp = false; + + if ( effectIdle.GetEntity( ) ) { + UpdateModelTransform(); + effectIdle->SetOrigin( resetOrigin ); + } else if ( spawnArgs.GetString( "fx_idle" ) && !simpleItem ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } else { + if ( pickupSkin ) { + SetSkin( pickupSkin ); + } else { + Hide(); + } + + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + pickedUp = true; + + StopEffect( "fx_idle" ); + effectIdle = NULL; + } + } + clReady = newReady; +} + +/* +================ +idItem::Event_Pickup +================ +*/ +void idItem::Event_Pickup( int clientNum ) { + idPlayer *player; + + assert( gameLocal.isClient ); + + // play pickup sound + if ( !spawnArgs.GetBool( "globalAcquireSound" ) ) { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + if( clientNum == gameLocal.localClientNum ) { + player = (idPlayer*)gameLocal.entities[ clientNum ]; + player->lastPickupTime = gameLocal.time; + if ( player ) { + player->GiveItem( this ); + } + } +} + +/* +================ +idItem::ClientReceiveEvent +================ +*/ +bool idItem::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_PICKUP: { + int clientNum = msg.ReadByte(); + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + Event_Pickup( clientNum ); + } + return true; + } + case EVENT_RESPAWNFX: { + Event_RespawnFx(); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +================ +idItem::Event_DropToFloor +================ +*/ +void idItem::Event_DropToFloor( void ) { + // don't drop the floor if bound to another entity + if ( GetBindMaster() != NULL && GetBindMaster() != this ) { + return; + } + + physicsObj.DropToFloor( ); +} + +/* +================ +idItem::Event_Touch +================ +*/ +void idItem::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( !canPickUp ) { + return; + } + + Pickup( static_cast(other) ); +} + +/* +================ +idItem::Event_Trigger +================ +*/ +void idItem::Event_Trigger( idEntity *activator ) { + if ( !canPickUp && spawnArgs.GetBool( "triggerFirst" ) ) { + canPickUp = true; + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Pickup( static_cast( activator ) ); + } +} + +/* +================ +idItem::Event_Respawn +================ +*/ +void idItem::Event_Respawn( void ) { + + // with simple items, re-show the item, but still set srvReady to true to let clients + // know how to deal + if ( pickupSkin ) { + srvReady = true; + if( !simpleItem ) { + SetSkin( skin ); + } + } + + if( !pickupSkin ) { + BecomeActive( TH_THINK ); + Show(); + } + + if( simpleItem ) { + Show(); + } + + pickedUp = false; + + inViewTime = -1000; + lastCycle = -1; + trigger->SetContents ( CONTENTS_TRIGGER ); + SetOrigin( orgOrigin ); + StartSound( "snd_respawn", SND_CHANNEL_ITEM, 0, false, NULL ); + PostEventMS( &EV_SetGravity, 0 ); + CancelEvents( &EV_RespawnItem ); // don't double respawn + + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::Event_RespawnFx +================ +*/ +void idItem::Event_RespawnFx( void ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_RESPAWNFX ); + // send unreliable PVS-sensitive + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ) ); + } + + if( !simpleItem ) { + gameLocal.PlayEffect( spawnArgs, "fx_respawn", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } +} + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +/* +=============== +idItemPowerup +=============== +*/ + +CLASS_DECLARATION( idItem, idItemPowerup ) +END_CLASS + +/* +================ +idItemPowerup::idItemPowerup +================ +*/ +idItemPowerup::idItemPowerup() { + time = 0; + type = 0; + droppedTime = 0; + unique = false; +} + +/* +================ +idItemPowerup::Save +================ +*/ +void idItemPowerup::Save( idSaveGame *savefile ) const { + savefile->WriteInt( time ); + savefile->WriteInt( type ); + savefile->WriteInt( droppedTime ); // cnicholson: Added unsaved var +} + +/* +================ +idItemPowerup::Restore +================ +*/ +void idItemPowerup::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( time ); + savefile->ReadInt( type ); + savefile->ReadInt( droppedTime ); // cnicholson: Added unrestored var +} + +/* +================ +idItemPowerup::Spawn +================ +*/ +void idItemPowerup::Spawn( void ) { + time = SEC2MS( spawnArgs.GetInt( "time", "30" ) ); + // SEC2MS screws up when we want -1 time (no expiration) + if( spawnArgs.GetInt( "time" ) == -1 ) { + time = -1; + } + + type = spawnArgs.GetInt( "type", "0" ); + + // If the powerup was dropped then make it dissapear using its remaining time. + if ( spawnArgs.GetBool( "dropped" ) && time != -1 ) { + droppedTime = gameLocal.time + time; + PostEventMS( &EV_Remove, time ); + } + + // unique powerpus won't respawn while a player has them + unique = spawnArgs.GetBool( "unique", "0" ); + if( unique ) { + spawnArgs.SetBool( "no_respawn", true ); + } + + if ( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else { + team = -1; + } +} + +/* +================ +idItemPowerup::GiveToPlayer +================ +*/ +bool idItemPowerup::GiveToPlayer( idPlayer *player ) { + if ( player == NULL || player->spectating ) { + return false; + } + + // only one arena CTF powerup at a time + if ( type >= POWERUP_AMMOREGEN && type <= POWERUP_SCOUT ) { + if ( ( player->inventory.powerups & ARENA_POWERUP_MASK ) != 0 ) { + return false; + } + } + + // in flavours of arena CTF (or are idItemPowerups only used in Arena? or even, are idItemPowerups MP only?), + // ensure that items with a team can only be picked up by members of that team + if ( gameLocal.IsMultiplayer() && gameLocal.IsTeamGame() && team >= 0 ) { + if( team != player->team ) { + return false; + } + } + + if ( droppedTime > 0 ) { + player->GivePowerUp( type, droppedTime - gameLocal.time ); + } else { + player->GivePowerUp( type, time ); + } + + // also call idItem::GiveToPlayer so any inv_* keywords get applied + idItem::GiveToPlayer( player ); + + return true; +} + +/* +================ +idItemPowerup::Think +================ +*/ +void idItemPowerup::Think( void ) { + int i; + + // idItem::Think() only needs to be called if we're spawned in + if( !IsHidden() || (pickupSkin && GetSkin() != pickupSkin ) ) { + // only get here if spawned in + idItem::Think(); + return; + } + + if( !unique ) { + // non-unique despawned powerups don't need to think + return; + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL ) { + continue; + } + + // only spawn back in if noone on your team has the powerup + if( p->PowerUpActive( type ) && p->team == team ) { + break; + } + } + + if( i >= gameLocal.numClients ) { + PostEventMS( &EV_RespawnItem, 0 ); + } +} + +/* +================ +idItemPowerup::Pickup +================ +*/ +bool idItemPowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + + if( gameLocal.isClient ) { + // no client-side powerup prediction + return false; + } + + pickup = idItem::Pickup( player ); + if( unique ) { + BecomeActive( TH_THINK ); + } + + return pickup; +} + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idObjective ) + EVENT( EV_Activate, idObjective::Event_Trigger ) + EVENT( EV_HideObjective, idObjective::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjective::Event_GetPlayerPos ) + EVENT( EV_CamShot, idObjective::Event_CamShot ) +END_CLASS + +/* +================ +idObjective::idObjective +================ +*/ +idObjective::idObjective() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjective::Save +================ +*/ +void idObjective::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjective::Restore +================ +*/ +void idObjective::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +// RAVEN BEGIN +// jnewquist: Don't do this on Xenon, we want prebuilt textures +#ifndef _XENON + PostEventMS( &EV_CamShot, 250 ); +#endif +// RAVEN END +} + +/* +================ +idObjective::Spawn +================ +*/ +void idObjective::Spawn( void ) { + Hide(); +// RAVEN BEGIN +// jnewquist: Only post a camshot event if the spawn args request it +#ifndef _XENON + const char *camName; + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + common->Warning( "SpawnArg camShot on %s is not recommended.", spawnArgs.GetString( "name" ) ); + PostEventMS( &EV_CamShot, 250 ); + } +#endif +// RAVEN END +} + +/* +================ +idObjective::Event_Screenshot +================ +*/ +void idObjective::Event_CamShot( ) { + const char *camName; +// RAVEN BEGIN +// bdube: changed screenshot location + idStr shotName = "gfx/objectives/"; + shotName += spawnArgs.GetString( "screenshot" ); + shotName.SetFileExtension( ".tga" ); + // RAVEN END + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + idEntity *ent = gameLocal.FindEntity( camName ); + if ( ent && ent->cameraTarget ) { + const renderView_t *view = ent->cameraTarget->GetRenderView(); + renderView_t fullView = *view; + fullView.width = SCREEN_WIDTH; + fullView.height = SCREEN_HEIGHT; + + // draw a view to a texture + renderSystem->CropRenderSize( 256, 256, true ); + gameRenderWorld->RenderScene( &fullView ); + // TTimo: I don't think this jives with SMP code, but I grepped through the final maps and didn't see any using it + renderSystem->CaptureRenderToFile( shotName ); + renderSystem->UnCrop(); + } + } +} + +/* +================ +idObjective::Event_Trigger +================ +*/ +void idObjective::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so when in a vehicle we update the vehicle hud +// twhitaker: changed player->hud to player->GetObjectiveHud(), to resolve all issues + if ( player && player->GetObjectiveHud() ) { +// bdube: changed screenshot location + idStr shotName = spawnArgs.GetString( "screenshot" ); + + player->GetObjectiveHud()->SetStateString( "screenshot", shotName ); + player->GetObjectiveHud()->SetStateString( "objective", "1" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->GiveObjective( spawnArgs.GetString( "objectivetitle" ), spawnArgs.GetString( "objectivetext" ), shotName ); + + // a tad slow but keeps from having to update all objectives in all maps with a name ptr + for( int i = 0; i < gameLocal.num_entities; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idObjectiveComplete::GetClassType() ) ) { +// RAVEN END + if ( idStr::Icmp( spawnArgs.GetString( "objectivetitle" ), gameLocal.entities[ i ]->spawnArgs.GetString( "objectivetitle" ) ) == 0 ){ + gameLocal.entities[ i ]->spawnArgs.SetBool( "objEnabled", true ); + break; + } + } + } + + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjective::Event_GetPlayerPos +================ +*/ +void idObjective::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjective::Event_HideObjective +================ +*/ +void idObjective::Event_HideObjective(idEntity *e) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idMoveableItem ) + EVENT( EV_Gib, idMoveableItem::Event_Gib ) +END_CLASS + +/* +================ +idMoveableItem::idMoveableItem +================ +*/ +idMoveableItem::idMoveableItem() { +} + +/* +================ +idMoveableItem::~idMoveableItem +================ +*/ +idMoveableItem::~idMoveableItem() { + // If this entity has been allocated, but not spawned, the physics object will + // not have a self pointer, and will not unregister itself from the entity. + SetPhysics( NULL ); +} + +/* +================ +idMoveableItem::Save +================ +*/ +void idMoveableItem::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idMoveableItem::Restore +================ +*/ +void idMoveableItem::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); +} + +/* +================ +idMoveableItem::Spawn +================ +*/ +void idMoveableItem::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + idStr clipModelName; + idBounds bounds; + + // if the model should be shrinked + if ( spawnArgs.GetBool( "clipshrink" ) ) { + trm.Shrink( CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + // setup the physics + physicsObj.SetSelf( this ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // check if a clip model is set + spawnArgs.GetString( "itemclipmodel", "", clipModelName ); + if ( clipModelName[0] ) { + if ( collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + physicsObj.SetClipModel( new idClipModel( trm ), density ); + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + +// RAVEN BEGIN +// mekberg: added + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } +// RAVEN END +} + +/* +================ +idMoveableItem::Think +================ +*/ +void idMoveableItem::Think( void ) { + RunPhysics(); + UpdateTrigger( ); + Present(); +} + +/* +================ +idMoveableItem::DropItem +================ +*/ +idEntity *idMoveableItem::DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ) { + idDict args; + idEntity *item; + + args.Set( "classname", classname ); + args.Set( "dropped", "1" ); + + // we sometimes drop idMoveables here, so set 'nodrop' to 1 so that it doesn't get put on the floor + args.Set( "nodrop", "1" ); + + if ( activateDelay ) { + args.SetBool( "triggerFirst", true ); + } + + gameLocal.SpawnEntityDef( args, &item ); + if ( item ) { + // set item position + item->GetPhysics()->SetOrigin( origin ); + item->GetPhysics()->SetAxis( axis ); + item->GetPhysics()->SetLinearVelocity( velocity ); + item->UpdateVisuals(); + if ( activateDelay ) { + item->PostEventMS( &EV_Activate, activateDelay, item ); + } + if ( !removeDelay ) { + removeDelay = 5 * 60 * 1000; + } + // always remove a dropped item after 5 minutes in case it dropped to an unreachable location + item->PostEventMS( &EV_Remove, removeDelay ); + } + + return item; +} + +/* +================ +idMoveableItem::DropItems + + The entity should have the following key/value pairs set: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + "skin_drop" "skin name" + To drop multiple items the following key/value pairs can be used: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + where is an aribtrary string. +================ +*/ +void idMoveableItem::DropItems( idAnimatedEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName, *c, *jointName; + idStr key, key2; + idVec3 origin; + idMat3 axis; + idAngles angles; + const idDeclSkin *skin; + jointHandle_t joint; + idEntity *item; + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), NULL ); + while ( kv ) { + + c = kv->GetKey().c_str() + kv->GetKey().Length(); + if ( idStr::Icmp( c - 5, "Joint" ) != 0 && idStr::Icmp( c - 8, "Rotation" ) != 0 ) { + + key = kv->GetKey().c_str() + 4; + key2 = key; + key += "Joint"; + key2 += "Offset"; + jointName = ent->spawnArgs.GetString( key ); + joint = ent->GetAnimator()->GetJointHandle( jointName ); + if ( !ent->GetJointWorldTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", key.c_str(), jointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + if ( g_dropItemRotation.GetString()[0] ) { + angles.Zero(); + sscanf( g_dropItemRotation.GetString(), "%f %f %f", &angles.pitch, &angles.yaw, &angles.roll ); + } else { + key = kv->GetKey().c_str() + 4; + key += "Rotation"; + ent->spawnArgs.GetAngles( key, "0 0 0", angles ); + } + axis *= angles.ToMat3() * axis; + + origin += ent->spawnArgs.GetVector( key2, "0 0 0" ); + + item = DropItem( kv->GetValue(), origin, axis, vec3_origin, 0, 0 ); + if ( list && item ) { + list->Append( item ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), kv ); + } + + // change the skin to hide all items + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +====================== +idMoveableItem::WriteToSnapshot +====================== +*/ +void idMoveableItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +====================== +idMoveableItem::ReadFromSnapshot +====================== +*/ +void idMoveableItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +============ +idMoveableItem::Gib +============ +*/ +void idMoveableItem::Gib( const idVec3 &dir, const char *damageDefName ) { + // remove the entity + PostEventMS( &EV_Remove, 0 ); +} + +/* +============ +idMoveableItem::Event_Gib +============ +*/ +void idMoveableItem::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idItemRemover + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idItemRemover ) + EVENT( EV_Activate, idItemRemover::Event_Trigger ) +END_CLASS + +/* +================ +idItemRemover::Spawn +================ +*/ +void idItemRemover::Spawn( void ) { +} + +/* +================ +idItemRemover::RemoveItem +================ +*/ +void idItemRemover::RemoveItem( idPlayer *player ) { + const char *remove; + + remove = spawnArgs.GetString( "remove" ); + player->RemoveInventoryItem( remove ); +} + +/* +================ +idItemRemover::Event_Trigger +================ +*/ +void idItemRemover::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + RemoveItem( static_cast(activator) ); + } +} + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +CLASS_DECLARATION( idItemRemover, idObjectiveComplete ) + EVENT( EV_Activate, idObjectiveComplete::Event_Trigger ) + EVENT( EV_HideObjective, idObjectiveComplete::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjectiveComplete::Event_GetPlayerPos ) +END_CLASS + +/* +================ +idObjectiveComplete::idObjectiveComplete +================ +*/ +idObjectiveComplete::idObjectiveComplete() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjectiveComplete::Save +================ +*/ +void idObjectiveComplete::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Restore +================ +*/ +void idObjectiveComplete::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Spawn +================ +*/ +void idObjectiveComplete::Spawn( void ) { + spawnArgs.SetBool( "objEnabled", false ); + Hide(); +} + +/* +================ +idObjectiveComplete::Event_Trigger +================ +*/ +void idObjectiveComplete::Event_Trigger( idEntity *activator ) { + if ( !spawnArgs.GetBool( "objEnabled" ) ) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + RemoveItem( player ); + + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so if in a vehicle we update that hud +// twhitaker: moved objective system to it's own hud, to solve vehicle hud issues. + if ( player->GetObjectiveHud() ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->CompleteObjective( spawnArgs.GetString( "objectivetitle" ) ); + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjectiveComplete::Event_GetPlayerPos +================ +*/ +void idObjectiveComplete::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjectiveComplete::Event_HideObjective +================ +*/ +void idObjectiveComplete::Event_HideObjective( idEntity *e ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->GetObjectiveHud() ) { + idVec3 v = player->GetPhysics()->GetOrigin(); + v -= playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ +CLASS_DECLARATION( idItemRemover, rvObjectiveFailed ) + EVENT( EV_Activate, rvObjectiveFailed::Event_Trigger ) +END_CLASS + +rvObjectiveFailed::rvObjectiveFailed ( void ) { +} + +/* +================ +rvObjectiveFailed::Event_Trigger +================ +*/ +void rvObjectiveFailed::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->GetObjectiveHud() ) { + return; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + return; + } + + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); + player->FailObjective( spawnArgs.GetString( "objectivetitle" ) ); +} + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +const idEventDef EV_ResetFlag ( "" ); +const idEventDef EV_LinkTrigger( "" ); +CLASS_DECLARATION( idItem, rvItemCTFFlag ) + EVENT( EV_ResetFlag, rvItemCTFFlag::Event_ResetFlag ) + EVENT( EV_LinkTrigger, rvItemCTFFlag::Event_LinkTrigger ) +END_CLASS + +/* +================ +rvItemCTFFlag::rvItemCTFFlag +================ +*/ +rvItemCTFFlag::rvItemCTFFlag() { +} + + +/* +================ +rvItemCTFFlag::Spawn +================ +*/ +void rvItemCTFFlag::Spawn () { +/* rjohnson: I fixed the crash so we don't need to remove them... + //don't spawn outside of CTF games + if( (gameLocal.gameType != GAME_CTF) && (gameLocal.gameType != GAME_1F_CTF) && + (gameLocal.gameType != GAME_ARENA_CTF) && (gameLocal.gameType != GAME_ARENA_1F_CTF) ){ + //don't spawn! + gameLocal.Error("Flag spawn on in non-CTF gametype."); + PostEventMS ( &EV_Remove, 0 ); + return; + } +*/ + spawnArgs.GetBool ( "dropped", "0", dropped ); + spawnArgs.GetInt ( "team", "0", team ); + + bool reset = false; + spawnArgs.GetBool ( "reset", "0", reset ); + + switch ( team ) { + case TEAM_MARINE: { + powerup = POWERUP_CTF_MARINEFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_MARINE ); + break; + } + case TEAM_STROGG: { + powerup = POWERUP_CTF_STROGGFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_STROGG ); + break; + } + case TEAM_MAX: { + powerup = POWERUP_CTF_ONEFLAG; + break; + } + default: + gameLocal.Warning ( "Unknown ctf flag team '%d' on entity '%s'", team, name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + if ( dropped ) { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_DROPPED ); + } + if ( reset ) { + //PostEventSec( &EV_ResetFlag, 1 ); + } else { + PostEventSec( &EV_ResetFlag, 30 ); + } + + // Let powerups settle for a frame before allowing them to be picked up - we need to + // make sure we hit the dropped state for VO, etc to work properly + trigger->SetContents( 0 ); + PostEventSec( &EV_LinkTrigger, 0 ); + } else { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + + spin = false; +} + +/* +================ +rvItemCTFFlag::Event_LinkTrigger +================ +*/ +void rvItemCTFFlag::Event_LinkTrigger( void ) { + trigger->SetContents( CONTENTS_TRIGGER | CONTENTS_ITEMCLIP ); +} + +/* +================ +rvItemCTFFlag::GiveToPlayer +================ +*/ +bool rvItemCTFFlag::GiveToPlayer( idPlayer* player ) { + if ( !gameLocal.IsMultiplayer() ) { + return false; + } + + if ( player->spectating || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && (gameLocal.mpGame.GetGameState())->GetMPGameState() != SUDDENDEATH && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return false; + } + + int teamPowerup; + int enemyPowerup; + + bool canPickup = ( team == player->team ); + + switch ( player->team ) { + default: + case TEAM_MARINE: + teamPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + break; + + case TEAM_STROGG: + teamPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + break; + } + + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + // in one flag CTF, we score touching the enemy's flag + canPickup = ( team == gameLocal.mpGame.OpposingTeam( player->team ) ); + enemyPowerup = POWERUP_CTF_ONEFLAG; + } + + // If the player runs over their own flag the only thing they + // can do is score or return it. + + // when the player touches the one flag, he always gets it + if ( canPickup ) { + // If the flag was dropped, return it + if ( dropped ) { + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + statManager->FlagReturned( player ); + ResetFlag ( teamPowerup ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagReturned", 0 ) ); +// RITUAL END + } else if ( player->PowerUpActive ( enemyPowerup ) ) { + // If they have the enemy flag then they score + if ( !gameLocal.mpGame.CanCapture ( player->team ) ) { + return false; + } + + ResetFlag( enemyPowerup ); + + gameLocal.mpGame.FlagCaptured( player ); + } + return false; + } + + // only pickup one flag in arena CTF + if( ( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) && team != TEAM_MAX ) { + return false; + } + + player->GivePowerUp( enemyPowerup, -1 ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagStolen", 0 ) ); +// RITUAL END + return true; +} + +/* +================ +rvItemCTFFlag::Pickup +================ +*/ +bool rvItemCTFFlag::Pickup( idPlayer *player ) { + if( gameLocal.isClient ) { + // no client-side CTF flag prediction + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + // ServerSendEvent( EVENT_PICKUP, NULL, false, -1 ); + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model + Hide(); + + gameLocal.mpGame.SetFlagEntity( NULL, team ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 1 ); + + if( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_ARENA_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_TAKEN ); + } else if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, (player->team == TEAM_MARINE ? FS_TAKEN_MARINE : FS_TAKEN_STROGG) ); + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagCarrier( team, player->entityNumber ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + BecomeInactive( TH_THINK ); + + return true; +} + +/* +================ +rvItemCTFFlag::ResetFlag +================ +*/ +void rvItemCTFFlag::ResetFlag( int powerup ) { + idEntity* ent; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // Make sure no players have the flag anymore +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType ( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->ClearPowerup ( powerup ); + continue; + } + + // If its not a CTF flag item then skip it + if ( !ent->IsType( rvItemCTFFlag::Type ) ) { + continue; + } + + // Make sure its the right type first + rvItemCTFFlag* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->dropped ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + flag->PostEventMS( &EV_RespawnItem, 0 ); + gameLocal.mpGame.SetFlagEntity( flag, flag->team ); + } + } + + if ( !gameLocal.isClient ) { + int team = -1; + if ( powerup == POWERUP_CTF_MARINEFLAG ) { + team = TEAM_MARINE; + } else if ( powerup == POWERUP_CTF_STROGGFLAG ) { + team = TEAM_STROGG; + } else if ( powerup == POWERUP_CTF_ONEFLAG ) { + team = TEAM_MAX; + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + +} + +/* +================ +rvItemCTFFlag::Event_ResetFlag +================ +*/ +void rvItemCTFFlag::Event_ResetFlag( void ) { + ResetFlag( powerup ); +} + +void rvItemCTFFlag::Think( void ) { + idItem::Think(); +} + +/* +================ +rvItemCTFFlag::Collide +================ +*/ +bool rvItemCTFFlag::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + + if ( collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + ResetFlag( powerup ); + } + return false; +} +// RAVEN END + + + + +const idEventDef EV_ResetSpawn ( "" ); +CLASS_DECLARATION( idItemPowerup, riDeadZonePowerup ) + EVENT( EV_ResetSpawn, riDeadZonePowerup::Event_ResetSpawn ) +END_CLASS + +/* +================ +riDeadZonePowerup::idItemPowerup +================ +*/ +riDeadZonePowerup::riDeadZonePowerup() { +} + +/* +================ +riDeadZonePowerup::Save +================ +*/ +void riDeadZonePowerup::Save( idSaveGame *savefile ) const { +} + +/* +================ +riDeadZonePowerup::Restore +================ +*/ +void riDeadZonePowerup::Restore( idRestoreGame *savefile ) { +} + + +/* +================ +riDeadZonePowerup::Show +================ +*/ +void riDeadZonePowerup::Show() +{ + idItem::Show(); +} + + +/* +================ +riDeadZonePowerup::Spawn +================ +*/ +void riDeadZonePowerup::Spawn( void ) { + powerup = POWERUP_DEADZONE; + + time = SEC2MS( gameLocal.serverInfo.GetInt("si_deadZonePowerupTime") ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + time = SEC2MS( spawnArgs.GetInt( "time", "10" ) ); + if ( time > SEC2MS(10) ) + time = SEC2MS(10); + + Show(); + CancelEvents(&EV_Remove); + PostEventSec( &EV_ResetSpawn, MS2SEC(time) ); + } + else + Hide(); +} + + +/* +================ +riDeadZonePowerup::Pickup +================ +*/ +bool riDeadZonePowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + if ( player->PowerUpActive(POWERUP_DEADZONE) ) + return false; + + pickup = idItemPowerup::Pickup( player ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + // Cancel the respawn so the powerup doesn't get removed + // from the player that just picked it up. + PostEventMS( &EV_Remove, 0 ); + CancelEvents( &EV_ResetSpawn ); + } + + return pickup; +} + +/* +================ +riDeadZonePowerup::ResetSpawn +================ +*/ +void riDeadZonePowerup::ResetSpawn( int powerup ) { + int count = 1; + idEntity* ent; + riDeadZonePowerup* spawnSpot = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::Type ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && flag == this ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + if ( !flag->IsVisible() ) { + if ( !(rand()%count) ) { + spawnSpot = flag; + if ( flag->spawnArgs.GetBool("dropped", "0") ) + gameLocal.DPrintf("WARNING: Trying to spawn a powerup at a DROPPED location!"); + } + count++; + } + } + } + + if ( spawnSpot ) { + spawnSpot->Show(); + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + } + else { + gameLocal.DPrintf("WARNING: Failed to find a valid spawn spot!"); + } +} + +/* +================ +riDeadZonePowerup::Collide +================ +*/ +bool riDeadZonePowerup::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_ResetSpawn, 0 ); // Just respawn it. + } + return false; +} + +/* +================ +riDeadZonePowerup::Event_ResetFlag +================ +*/ +void riDeadZonePowerup::Event_ResetSpawn( void ) { + ResetSpawn( POWERUP_DEADZONE ); +} diff --git a/src/game/Item.h b/src/game/Item.h new file mode 100644 index 0000000..248b3bb --- /dev/null +++ b/src/game/Item.h @@ -0,0 +1,386 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ITEM_H__ +#define __GAME_ITEM_H__ + +extern const int ARENA_POWERUP_MASK; +extern const idEventDef EV_ResetFlag; +extern const idEventDef EV_RespawnItem; +extern const idEventDef EV_SetGravity; + +/* +=============================================================================== + + Items the player can pick up or use. + +=============================================================================== +*/ + +class idItem : public idEntity { +public: + CLASS_PROTOTYPE( idItem ); + + idItem(); + virtual ~idItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void GetAttributes( idDict &attributes ); + virtual bool GiveToPlayer( idPlayer *player ); + virtual bool Pickup( idPlayer *player ); + virtual void Think( void ); + virtual void Present(); + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// mekberg: added + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); +// RAVEN END + + enum { + EVENT_PICKUP = idEntity::EVENT_MAXEVENTS, + EVENT_RESPAWNFX, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + int IsVisible() { return srvReady; } + + rvClientEntityPtr effectIdle; + bool simpleItem; + bool pickedUp; + const idDeclSkin* pickupSkin; + void Event_DropToFloor ( void ); +protected: + + void UpdateTrigger( void ); + void SendPickupMsg( int clientNum ); + + idClipModel * trigger; + bool spin; + // only a small subset of idItem need their physics object synced + bool syncPhysics; + + bool pulse; + bool canPickUp; + const idDeclSkin* skin; + +private: + idVec3 orgOrigin; + + rvPhysics_Particle physicsObj; + + // for item pulse effect + int itemShellHandle; + const idMaterial * shellMaterial; + + // used to update the item pulse effect + mutable bool inView; + mutable int inViewTime; + mutable int lastCycle; + mutable int lastRenderViewTime; + + // synced through snapshots to indicate show/hide or pickupSkin state + // -1 on a client means undef, 0 not ready, 1 ready +public: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int srvReady; +private: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int clReady; + + int itemPVSArea; + + bool UpdateRenderEntity ( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback ( renderEntity_s *renderEntity, const renderView_t *renderView ); + + + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_Trigger ( idEntity *activator ); + void Event_Respawn ( void ); + void Event_RespawnFx ( void ); + void Event_Pickup ( int clientNum ); + +// RAVEN BEGIN +// abahr + void Event_SetGravity(); +// RAVEN END +}; + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +class idItemPowerup : public idItem { +public: + CLASS_PROTOTYPE( idItemPowerup ); + + idItemPowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual bool GiveToPlayer( idPlayer *player ); + virtual void Think( void ); + virtual bool Pickup( idPlayer *player ); + +protected: + + int time; + int type; + int droppedTime; + int team; + bool unique; +}; + +/* +=============================================================================== + + riDeadZonePowerup + +=============================================================================== +*/ + +class riDeadZonePowerup : public idItemPowerup { +public: + CLASS_PROTOTYPE( riDeadZonePowerup ); + + riDeadZonePowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Pickup( idPlayer *player ); + + void Spawn(); + void Show(); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + int powerup; + +protected: + void ResetSpawn( int powerup ); + +private: + void Event_ResetSpawn( void ); +}; + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +class rvItemCTFFlag : public idItem { +public: + CLASS_PROTOTYPE( rvItemCTFFlag ); + + rvItemCTFFlag(); + + void Spawn(); + virtual bool GiveToPlayer ( idPlayer* player ); + virtual bool Pickup( idPlayer *player ); + + static void ResetFlag( int type ); + virtual void Think( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + +private: + int team; + int powerup; + bool dropped; + + void Event_ResetFlag( void ); + void Event_LinkTrigger( void ); +}; + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +class idObjective : public idItem { +public: + CLASS_PROTOTYPE( idObjective ); + + idObjective(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); + void Event_CamShot(); +}; + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +class idMoveableItem : public idItem { +public: + CLASS_PROTOTYPE( idMoveableItem ); + + idMoveableItem(); + virtual ~idMoveableItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual void Think( void ); + + static void DropItems( idAnimatedEntity *ent, const char *type, idList *list ); + static idEntity* DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + idPhysics_RigidBody physicsObj; + + void Gib( const idVec3 &dir, const char *damageDefName ); + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + Item removers. + +=============================================================================== +*/ + +class idItemRemover : public idEntity { +public: + CLASS_PROTOTYPE( idItemRemover ); + + void Spawn(); + void RemoveItem( idPlayer *player ); + +private: + void Event_Trigger( idEntity *activator ); +}; + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +class idObjectiveComplete : public idItemRemover { +public: + CLASS_PROTOTYPE( idObjectiveComplete ); + + idObjectiveComplete(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); +}; + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ + +class rvObjectiveFailed : public idItemRemover { +public: + CLASS_PROTOTYPE( rvObjectiveFailed ); + + rvObjectiveFailed (); +private: + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_ITEM_H__ */ + + +// RAVEN END diff --git a/src/game/Light.cpp b/src/game/Light.cpp new file mode 100644 index 0000000..d0754b7 --- /dev/null +++ b/src/game/Light.cpp @@ -0,0 +1,1509 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idLight + +=============================================================================== +*/ + +const idEventDef EV_Light_SetShader( "setShader", "s" ); +const idEventDef EV_Light_GetLightParm( "getLightParm", "d", 'f' ); +const idEventDef EV_Light_SetLightParm( "setLightParm", "df" ); +const idEventDef EV_Light_SetLightParms( "setLightParms", "ffff" ); +const idEventDef EV_Light_SetRadiusXYZ( "setRadiusXYZ", "fff" ); +const idEventDef EV_Light_SetRadius( "setRadius", "f" ); +const idEventDef EV_Light_On( "On", NULL ); +const idEventDef EV_Light_Off( "Off", NULL ); +const idEventDef EV_Light_FadeOut( "fadeOutLight", "f" ); +const idEventDef EV_Light_FadeIn( "fadeInLight", "f" ); + +// RAVEN BEGIN +// bdube: added +const idEventDef EV_Light_SetLightGUI ( "setLightGUI", "s" ); +// jscott: added for modview +const idEventDef EV_Light_SetCurrentLightLevel( "setCurrentLightLevel", "d" ); +const idEventDef EV_Light_SetMaxLightLevel( "setMaxLightLevel", "d" ); +// kfuller: 8/11/03 +const idEventDef EV_Light_IsOn( "isOn", NULL, 'f' ); +const idEventDef EV_Light_Break( "break", "ef" ); +// kfuller: lights that blink to life +const idEventDef EV_Light_DoneBlinking( "doneBlinking", NULL ); +// kfuller: lights that blink off +const idEventDef EV_Light_DoneBlinkingOff( "doneBlinkingOff", NULL ); +// abahr: +const idEventDef EV_Light_Timer( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idLight ) + EVENT( EV_Light_SetShader, idLight::Event_SetShader ) + EVENT( EV_Light_GetLightParm, idLight::Event_GetLightParm ) + EVENT( EV_Light_SetLightParm, idLight::Event_SetLightParm ) + EVENT( EV_Light_SetLightParms, idLight::Event_SetLightParms ) + EVENT( EV_Light_SetRadiusXYZ, idLight::Event_SetRadiusXYZ ) + EVENT( EV_Light_SetRadius, idLight::Event_SetRadius ) + EVENT( EV_Hide, idLight::Event_Hide ) + EVENT( EV_Show, idLight::Event_Show ) + EVENT( EV_Light_On, idLight::Event_On ) + EVENT( EV_Light_Off, idLight::Event_Off ) + EVENT( EV_Activate, idLight::Event_ToggleOnOff ) + EVENT( EV_PostSpawn, idLight::Event_SetSoundHandles ) + EVENT( EV_Light_FadeOut, idLight::Event_FadeOut ) + EVENT( EV_Light_FadeIn, idLight::Event_FadeIn ) + +// RAVEN BEGIN +// bdube: added + EVENT( EV_Light_SetLightGUI, idLight::Event_SetLightGUI ) + EVENT( EV_Light_SetCurrentLightLevel, idLight::Event_SetCurrentLightLevel ) + EVENT( EV_Light_SetMaxLightLevel, idLight::Event_SetMaxLightLevel ) +// kfuller: 8/11/03 + EVENT( EV_Light_IsOn, idLight::Event_IsOn ) + EVENT( EV_Light_Break, idLight::Event_Break ) +// kfuller: lights that blink to life + EVENT( EV_Light_DoneBlinking, idLight::Event_DoneBlinking ) +// kfuller: lights that blink off + EVENT( EV_Light_DoneBlinkingOff, idLight::Event_DoneBlinkingOff ) + EVENT( EV_Earthquake, idLight::Event_EarthQuake ) +// abahr: + EVENT( EV_Light_Timer, idLight::Event_Timer ) +// RAVEN END +END_CLASS + + +/* +================ +idGameEdit::ParseSpawnArgsToRenderLight + +parse the light parameters +this is the canonical renderLight parm parsing, +which should be used by dmap and the editor +================ +*/ +bool idGameEdit::ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ) { + bool gotTarget, gotUp, gotRight; + const char *texture; + idVec3 color; + bool rv = true; + + memset( renderLight, 0, sizeof( *renderLight ) ); + + if (!args->GetVector("light_origin", "", renderLight->origin)) { + args->GetVector( "origin", "", renderLight->origin ); + } + + gotTarget = args->GetVector( "light_target", "", renderLight->target ); + gotUp = args->GetVector( "light_up", "", renderLight->up ); + gotRight = args->GetVector( "light_right", "", renderLight->right ); + args->GetVector( "light_start", "0 0 0", renderLight->start ); + if ( !args->GetVector( "light_end", "", renderLight->end ) ) { + renderLight->end = renderLight->target; + } + + // we should have all of the target/right/up or none of them + if ( ( gotTarget || gotUp || gotRight ) != ( gotTarget && gotUp && gotRight ) ) { + gameLocal.Warning( "Light at (%f,%f,%f) has bad target info\n", + renderLight->origin[0], renderLight->origin[1], renderLight->origin[2] ); + + return false; + } + + if ( !gotTarget ) { + renderLight->pointLight = true; + + // allow an optional relative center of light and shadow offset + args->GetVector( "light_center", "0 0 0", renderLight->lightCenter ); + +// RAVEN BEGIN +// bdube: default light radius changed to 320 + // create a point light + if (!args->GetVector( "light_radius", "320 320 320", renderLight->lightRadius ) ) { + float radius; + + args->GetFloat( "light", "320", radius ); +// RAVEN END + renderLight->lightRadius[0] = renderLight->lightRadius[1] = renderLight->lightRadius[2] = radius; + } + + if ( renderLight->lightRadius[0] == 0 || + renderLight->lightRadius[1] == 0 || + renderLight->lightRadius[2] == 0 ) { + gameLocal.Warning( "PointLight at ( %d, %d, %d ) has at least one radius component of 0!", + ( int )renderLight->origin[0], ( int )renderLight->origin[1], ( int )renderLight->origin[2] ); + rv = false; + } + } + + // get the rotation matrix in either full form, or single angle form + idAngles angles; + idMat3 mat; + if ( !args->GetMatrix( "light_rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + args->GetFloat( "angle", "0", angles[ 1 ] ); + angles[ 0 ] = 0; + angles[ 1 ] = idMath::AngleNormalize360( angles[ 1 ] ); + angles[ 2 ] = 0; + mat = angles.ToMat3(); + } + } + + // fix degenerate identity matrices + mat[0].FixDegenerateNormal(); + mat[1].FixDegenerateNormal(); + mat[2].FixDegenerateNormal(); + + renderLight->axis = mat; + + // check for other attributes + args->GetVector( "_color", "1 1 1", color ); + renderLight->shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight->shaderParms[ SHADERPARM_BLUE ] = color[2]; + args->GetFloat( "shaderParm3", "1", renderLight->shaderParms[ SHADERPARM_TIMESCALE ] ); + if ( !args->GetFloat( "shaderParm4", "0", renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] ) ) { + // offset the start time of the shader to sync it to the game time + renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + + args->GetFloat( "shaderParm5", "0", renderLight->shaderParms[5] ); + args->GetFloat( "shaderParm6", "0", renderLight->shaderParms[6] ); + args->GetFloat( "shaderParm7", "0", renderLight->shaderParms[ SHADERPARM_MODE ] ); + args->GetBool( "noshadows", "0", renderLight->noShadows ); + +// RAVEN BEGIN +// dluetscher: added a min light detail level setting that describes when this light is visible + args->GetFloat( "detailLevel", "10", renderLight->detailLevel ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: dynamic shadows + args->GetBool( "noDynamicShadows", "0", renderLight->noDynamicShadows ); +// RAVEN END + args->GetBool( "nospecular", "0", renderLight->noSpecular ); + args->GetBool( "parallel", "0", renderLight->parallel ); + + args->GetString( "texture", "lights/squarelight1", &texture ); + // allow this to be NULL + renderLight->shader = declManager->FindMaterial( texture, false ); + + return rv; +} + +/* +================ +idLight::UpdateChangeableSpawnArgs +================ +*/ +void idLight::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + source->Print(); + } + FreeSoundEmitter( true ); + gameEdit->ParseSpawnArgsToRefSound( source ? source : &spawnArgs, &refSound ); + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + gameEdit->ParseSpawnArgsToRenderLight( source ? source : &spawnArgs, &renderLight ); + + UpdateVisuals(); +} + +/* +================ +idLight::idLight +================ +*/ +idLight::idLight() { + memset( &renderLight, 0, sizeof( renderLight ) ); +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + localLightOrigin = vec3_zero; + localLightAxis = mat3_identity; + lightDefHandle = -1; + levels = 0; + currentLevel = 0; + baseColor = vec3_zero; + breakOnTrigger = false; + count = 0; + triggercount = 0; + lightParent = NULL; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + soundWasPlaying = false; + +// RAVEN BEGIN +// bdube: light gui + lightGUI = NULL; + random = 0.0f; + wait = 0.0f; +// RAVEN END +} + +/* +================ +idLight::~idLight +================ +*/ +idLight::~idLight() { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + } +} + +/* +================ +idLight::Save + +archives object for save game file +================ +*/ +void idLight::Save( idSaveGame *savefile ) const { + savefile->WriteRenderLight( renderLight ); + + savefile->WriteBool( renderLight.prelightModel != NULL ); + + savefile->WriteVec3( localLightOrigin ); + savefile->WriteMat3( localLightAxis ); + //qhandle_t lightDefHandle; // cnicholson: This wasn't here from id, so I didnt add it either. + + savefile->WriteString( brokenModel ); + savefile->WriteInt( levels ); + savefile->WriteInt( currentLevel ); + + savefile->WriteVec3( baseColor ); + savefile->WriteBool( breakOnTrigger ); + savefile->WriteInt( count ); + savefile->WriteInt( triggercount ); + savefile->WriteObject( lightParent ); + + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + + lightGUI.Save( savefile ); // cnicholson: added unsaved var + + savefile->WriteBool( soundWasPlaying ); +} + +/* +================ +idLight::Restore + +unarchives object from save game file +================ +*/ +void idLight::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// jscott: constants can be read from the spawnargs + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// RAVEN END + + bool hadPrelightModel; + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadBool( hadPrelightModel ); + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + if ( ( renderLight.prelightModel == NULL ) && hadPrelightModel ) { + assert( 0 ); + if ( developer.GetBool() ) { + // we really want to know if this happens + gameLocal.Error( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } else { + // but let it slide after release + gameLocal.Warning( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } + } + + savefile->ReadVec3( localLightOrigin ); + savefile->ReadMat3( localLightAxis ); + + savefile->ReadString( brokenModel ); + savefile->ReadInt( levels ); + savefile->ReadInt( currentLevel ); + + savefile->ReadVec3( baseColor ); + savefile->ReadBool( breakOnTrigger ); + savefile->ReadInt( count ); + savefile->ReadInt( triggercount ); + savefile->ReadObject( reinterpret_cast( lightParent ) ); + + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +// RAVEN BEGIN +// bdube: light gui + lightGUI.Restore ( savefile ); +// RAVEN END + + savefile->ReadBool( soundWasPlaying ); + + lightDefHandle = -1; + + SetLightLevel(); +} + +/* +================ +idLight::Spawn +================ +*/ +void idLight::Spawn( void ) { + bool start_off; + bool needBroken; + const char *demonic_shader; + + // do the parsing the same way dmap and the editor do + if ( !gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &renderLight ) ) { + gameLocal.Warning( "Removing invalid light named: %s", GetName() ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + // we need the origin and axis relative to the physics origin/axis + localLightOrigin = ( renderLight.origin - GetPhysics()->GetOrigin() ) * GetPhysics()->GetAxis().Transpose(); + localLightAxis = renderLight.axis * GetPhysics()->GetAxis().Transpose(); + + // set the base color from the shader parms + baseColor.Set( renderLight.shaderParms[ SHADERPARM_RED ], renderLight.shaderParms[ SHADERPARM_GREEN ], renderLight.shaderParms[ SHADERPARM_BLUE ] ); + + // set the number of light levels + spawnArgs.GetInt( "levels", "1", levels ); + currentLevel = levels; + if ( levels <= 0 ) { + gameLocal.Error( "Invalid light level set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + // make sure the demonic shader is cached + if ( spawnArgs.GetString( "mat_demonic", NULL, &demonic_shader ) ) { + declManager->FindType( DECL_MATERIAL, demonic_shader ); + } + + // game specific functionality, not mirrored in + // editor or dmap light parsing + + // also put the light texture on the model, so light flares + // can get the current intensity of the light + renderEntity.referenceShader = renderLight.shader; + + lightDefHandle = -1; // no static version yet + + // see if an optimized shadow volume exists + // the renderer will ignore this value after a light has been moved, + // but there may still be a chance to get it wrong if the game moves + // a light before the first present, and doesn't clear the prelight + renderLight.prelightModel = 0; + if ( name[ 0 ] ) { + // this will return 0 if not found + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + } + + spawnArgs.GetBool( "start_off", "0", start_off ); + if ( start_off ) { + Off(); + } + + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + spawnArgs.GetBool( "break", "0", breakOnTrigger ); + spawnArgs.GetInt( "count", "1", count ); + + triggercount = 0; + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // if we have a health make light breakable + if ( health ) { + idStr model = spawnArgs.GetString( "model" ); // get the visual model + if ( !model.Length() ) { + gameLocal.Error( "Breakable light without a model set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + fl.takedamage = true; + + // see if we need to create a broken model name + needBroken = true; + if ( model.Length() && !brokenModel.Length() ) { + int pos; + + needBroken = false; + + pos = model.Find( "." ); + if ( pos < 0 ) { + pos = model.Length(); + } + if ( pos > 0 ) { + model.Left( pos, brokenModel ); + } + brokenModel += "_broken"; + if ( pos > 0 ) { + brokenModel += &model[ pos ]; + } + } + + // make sure the model gets cached + if ( !renderModelManager->CheckModel( brokenModel ) ) { + if ( needBroken ) { + gameLocal.Error( "Model '%s' not found for entity %d(%s)", brokenModel.c_str(), entityNumber, name.c_str() ); + } else { + brokenModel = ""; + } + } + + GetPhysics()->SetContents( spawnArgs.GetBool( "nonsolid" ) ? 0 : CONTENTS_SOLID ); + } + + PostEventMS( &EV_PostSpawn, 0 ); + +// RAVEN BEGIN +// bdube: light guis + const char* lightGUI; + if ( spawnArgs.GetString ( "light_gui", "", &lightGUI ) ) { + PostEventMS( &EV_Light_SetLightGUI, 0, lightGUI ); + } + +// abahr: + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// AReis: Minor light optimization stuff. + spawnArgs.GetBool( "globalLight", "0", renderLight.globalLight ); +// RAVEN END + + UpdateVisuals(); + +// RAVEN BEGIN +// ddynerman: ambient lights added clientside + if( renderLight.shader && renderLight.shader->IsAmbientLight() ) { + if ( !gameLocal.ambientLights.Find( static_cast(this) ) ) { + gameLocal.ambientLights.Append( static_cast(this) ); + } + fl.networkSync = false; // don't transmit ambient lights + } +// RAVEN END +} + +/* +================ +idLight::SetLightLevel +================ +*/ +void idLight::SetLightLevel( void ) { + idVec3 color; + float intensity; + + intensity = ( float )currentLevel / ( float )levels; + color = baseColor * intensity; + renderLight.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec3 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec4 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderLight.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( float red, float green, float blue ) { + baseColor.Set( red, green, blue ); + SetLightLevel(); +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( const idVec4 &color ) { + baseColor = color.ToVec3(); + renderLight.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + SetLightLevel(); +} + +/* +================ +idLight::SetShader +================ +*/ +void idLight::SetShader( const char *shadername ) { + // allow this to be NULL + renderLight.shader = declManager->FindMaterial( shadername, false ); + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParm +================ +*/ +void idLight::SetLightParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + renderLight.shaderParms[ parmnum ] = value; + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParms +================ +*/ +void idLight::SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + renderLight.shaderParms[ SHADERPARM_RED ] = parm0; + renderLight.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderLight.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderLight.shaderParms[ SHADERPARM_ALPHA ] = parm3; + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + baseColor.Set( parm0, parm1, parm2 ); + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::SetRadiusXYZ +================ +*/ +void idLight::SetRadiusXYZ( float x, float y, float z ) { + renderLight.lightRadius[0] = x; + renderLight.lightRadius[1] = y; + renderLight.lightRadius[2] = z; + PresentLightDefChange(); +} + +/* +================ +idLight::SetRadius +================ +*/ +void idLight::SetRadius( float radius ) { + renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = radius; + PresentLightDefChange(); +} + +/* +================ +idLight::On +================ +*/ +void idLight::On( void ) { + currentLevel = levels; + // offset the start time of the shader to sync it to the game time + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + +// RAVEN BEGIN + idStr blinkOnSound; + if (spawnArgs.GetString("snd_blinkOn", "", blinkOnSound)) + { + refSound.shader = declManager->FindSound(blinkOnSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + PostEventMS(&EV_Light_DoneBlinking, howLongInMS); + soundWasPlaying = false; + idStr blinkOnTexture; + if (spawnArgs.GetString( "mtr_blinkOn", "", blinkOnTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOnTexture, false ); + UpdateVisuals(); + Present(); + } + } + else +// RAVEN END + + if ( ( soundWasPlaying || refSound.waitfortrigger ) && refSound.shader ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Off +================ +*/ +void idLight::Off( void ) { +// RAVEN BEGIN +// kfuller: lights can flicker off + idStr blinkOffSound; + if (spawnArgs.GetString("snd_blinkOff", "", blinkOffSound)) + { + refSound.shader = declManager->FindSound(blinkOffSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL );//*1000; + PostEventMS(&EV_Light_DoneBlinkingOff, howLongInMS); + soundWasPlaying = false; + idStr blinkOffTexture; + if (spawnArgs.GetString( "mtr_blinkOff", "", blinkOffTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOffTexture, false ); + UpdateVisuals(); + Present(); + } + } + else + { + currentLevel = 0; + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { + StopSound( SND_CHANNEL_ANY, false ); + soundWasPlaying = true; + } + } +// RAVEN END + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Fade +================ +*/ +void idLight::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idLight::FadeOut +================ +*/ +void idLight::FadeOut( float time ) { + Fade( colorBlack, time ); +} + +/* +================ +idLight::FadeIn +================ +*/ +void idLight::FadeIn( float time ) { + idVec3 color; + idVec4 color4; + + currentLevel = levels; + spawnArgs.GetVector( "_color", "1 1 1", color ); + color4.Set( color.x, color.y, color.z, 1.0f ); + Fade( color4, time ); +} + +/* +================ +idLight::Killed +================ +*/ +void idLight::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + BecomeBroken( attacker ); +} + +/* +================ +idLight::BecomeBroken +================ +*/ +void idLight::BecomeBroken( idEntity *activator ) { + const char *damageDefName; + + fl.takedamage = false; + + if ( brokenModel.Length() ) { + SetModel( brokenModel ); + + if ( !spawnArgs.GetBool( "nonsolid" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel( brokenModel.c_str() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + } else if ( spawnArgs.GetBool( "hideModelOnBreak" ) ) { + SetModel( "" ); + GetPhysics()->SetContents( 0 ); + } + + if ( gameLocal.isServer ) { + + ServerSendInstanceEvent( EVENT_BECOMEBROKEN, NULL, true, -1 ); + + if ( spawnArgs.GetString( "def_damage", "", &damageDefName ) ) { + idVec3 origin = renderEntity.origin + renderEntity.bounds.GetCenter() * renderEntity.axis; + gameLocal.RadiusDamage( origin, activator, activator, this, this, damageDefName ); + } + + } + + ActivateTargets( activator ); + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + // set the state parm + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + renderLight.shaderParms[ SHADERPARM_MODE ] = 1; + + // if the light has a sound, either start the alternate (broken) sound, or stop the sound + const char *parm = spawnArgs.GetString( "snd_broken" ); + if ( refSound.shader || ( parm && *parm ) ) { + StopSound( SND_CHANNEL_ANY, false ); + const idSoundShader *alternate = refSound.shader ? refSound.shader->GetAltSound() : declManager->FindSound( parm ); + if ( alternate ) { + // start it with no diversity, so the leadin break sound plays + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound( alternate, SND_CHANNEL_ANY, 0.0, 0 ); + } + } + } + + parm = spawnArgs.GetString( "mtr_broken" ); + if ( parm && *parm ) { + SetShader( parm ); + } + + UpdateVisuals(); +} + +/* +================ +idLight::PresentLightDefChange +================ +*/ +void idLight::PresentLightDefChange( void ) { + // let the renderer apply it to the world + if ( ( lightDefHandle != -1 ) ) { + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } +} + +/* +================ +idLight::PresentModelDefChange +================ +*/ +void idLight::PresentModelDefChange( void ) { + + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idLight::Present +================ +*/ +void idLight::Present( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_RENDER ); +// RAVEN END + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // add the model + idEntity::Present(); + + // current transformation + renderLight.axis = localLightAxis * GetPhysics()->GetAxis(); + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * localLightOrigin; + + // reference the sound for shader synced effects + if ( lightParent ) { +// RAVEN BEGIN + renderLight.referenceSoundHandle = lightParent->GetSoundEmitter(); + renderEntity.referenceSoundHandle = lightParent->GetSoundEmitter(); + } + else { + renderLight.referenceSoundHandle = refSound.referenceSoundHandle; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; +// RAVEN END + } + + // update the renderLight and renderEntity to render the light and flare + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::Think +================ +*/ +void idLight::Think( void ) { + idVec4 color; + + if ( thinkFlags & TH_THINK ) { + if ( fadeEnd > 0 ) { + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + +// RAVEN BEGIN +// bdube: gui controlled lights + if ( lightGUI ) { + SetColor ( lightGUI->GetRenderEntity()->gui[0]->GetLightColor ( ) ); + } +// RAVEN END + } + + RunPhysics(); + Present(); +} + +/* +================ +idLight::GetPhysicsToSoundTransform +================ +*/ +bool idLight::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = localLightOrigin + renderLight.lightCenter; + axis = localLightAxis * GetPhysics()->GetAxis(); + return true; +} + +/* +================ +idLight::FreeLightDef +================ +*/ +void idLight::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================ +idLight::SaveState +================ +*/ +void idLight::SaveState( idDict *args ) { + int i, c = spawnArgs.GetNumKeyVals(); + for ( i = 0; i < c; i++ ) { + const idKeyValue *pv = spawnArgs.GetKeyVal(i); + if ( pv->GetKey().Find( "editor_", false ) >= 0 || pv->GetKey().Find( "parse_", false ) >= 0 ) { + continue; + } + args->Set( pv->GetKey(), pv->GetValue() ); + } +} + +/* +=============== +idLight::ShowEditingDialog +=============== +*/ +void idLight::ShowEditingDialog( void ) { + if ( g_editEntityMode.GetInteger() == 1 ) { + common->InitTool( EDITOR_LIGHT, &spawnArgs ); + } else { + common->InitTool( EDITOR_SOUND, &spawnArgs ); + } +} + +/* +================ +idLight::Event_SetShader +================ +*/ +void idLight::Event_SetShader( const char *shadername ) { + SetShader( shadername ); +} + +/* +================ +idLight::Event_GetLightParm +================ +*/ +void idLight::Event_GetLightParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderLight.shaderParms[ parmnum ] ); +} + +/* +================ +idLight::Event_SetLightParm +================ +*/ +void idLight::Event_SetLightParm( int parmnum, float value ) { + SetLightParm( parmnum, value ); +} + +/* +================ +idLight::Event_SetLightParms +================ +*/ +void idLight::Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + SetLightParms( parm0, parm1, parm2, parm3 ); +} + +/* +================ +idLight::Event_SetRadiusXYZ +================ +*/ +void idLight::Event_SetRadiusXYZ( float x, float y, float z ) { + SetRadiusXYZ( x, y, z ); +} + +/* +================ +idLight::Event_SetRadius +================ +*/ +void idLight::Event_SetRadius( float radius ) { + SetRadius( radius ); +} + +/* +================ +idLight::Event_Hide +================ +*/ +void idLight::Event_Hide( void ) { + Hide(); + PresentModelDefChange(); + Off(); +} + +/* +================ +idLight::Event_Show +================ +*/ +void idLight::Event_Show( void ) { + Show(); + PresentModelDefChange(); + On(); +} + +/* +================ +idLight::Event_On +================ +*/ +void idLight::Event_On( void ) { + On(); +} + +/* +================ +idLight::Event_Off +================ +*/ +void idLight::Event_Off( void ) { + Off(); +} + +/* +================ +idLight::Event_ToggleOnOff +================ +*/ +void idLight::Event_ToggleOnOff( idEntity *activator ) { +// RAVEN BEGIN +// abahr: + if( wait > 0 ) { + if( EventIsPosted(&EV_Light_Timer) ) { + CancelEvents( &EV_Light_Timer ); + } else { + ProcessEvent( &EV_Light_Timer ); + } + } else { +// RAVEN END + triggercount++; + if ( triggercount < count ) { + return; + } + + // reset trigger count + triggercount = 0; + + if ( breakOnTrigger ) { + BecomeBroken( activator ); + breakOnTrigger = false; + return; + } + + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } +// RAVEN BEGIN +// abahr: + } +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSound::Event_Timer +================ +*/ +void idLight::Event_Timer( void ) { +// FIXME: think about putting this logic in helper function so we don't have cut and pasted code + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } + + PostEventSec( &EV_Light_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idLight::Event_SetSoundHandles + + set the same sound def handle on all targeted lights +================ +*/ +void idLight::Event_SetSoundHandles( void ) { + int i; + idEntity *targetEnt; + + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idLight::Type ) ) { + idLight *light = static_cast(targetEnt); + light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->renderEntity.referenceSoundHandle = renderEntity.referenceSoundHandle; + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } +// RAVEN BEGIN +// rjohnson: func_static's can now have their color parms affected by lights + else if ( targetEnt && targetEnt->IsType( idStaticEntity::GetClassType() ) ) { + targetEnt->GetRenderEntity()->referenceShader = renderLight.shader; + targetEnt->GetRenderEntity()->referenceSoundHandle = renderEntity.referenceSoundHandle; + } +// RAVEN END + } +} + +/* +================ +idLight::Event_FadeOut +================ +*/ +void idLight::Event_FadeOut( float time ) { + FadeOut( time ); +} + +/* +================ +idLight::Event_FadeIn +================ +*/ +void idLight::Event_FadeIn( float time ) { + FadeIn( time ); +} + +/* +================ +idLight::ClientPredictionThink +================ +*/ +void idLight::ClientPredictionThink( void ) { + Think(); +} + +/* +================ +idLight::WriteToSnapshot +================ +*/ +void idLight::WriteToSnapshot( idBitMsgDelta &msg ) const { + + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + + msg.WriteByte( currentLevel ); + msg.WriteLong( PackColor( baseColor ) ); + // msg.WriteBits( lightParent.GetEntityNum(), GENTITYNUM_BITS ); + +/* // only helps prediction + msg.WriteLong( PackColor( fadeFrom ) ); + msg.WriteLong( PackColor( fadeTo ) ); + msg.WriteLong( fadeStart ); + msg.WriteLong( fadeEnd ); +*/ + + // FIXME: send renderLight.shader + msg.WriteFloat( renderLight.lightRadius[0], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[1], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[2], 5, 10 ); + + msg.WriteLong( PackColor( idVec4( renderLight.shaderParms[SHADERPARM_RED], + renderLight.shaderParms[SHADERPARM_GREEN], + renderLight.shaderParms[SHADERPARM_BLUE], + renderLight.shaderParms[SHADERPARM_ALPHA] ) ) ); + + msg.WriteFloat( renderLight.shaderParms[SHADERPARM_TIMESCALE], 5, 10 ); + msg.WriteLong( renderLight.shaderParms[SHADERPARM_TIMEOFFSET] ); + //msg.WriteByte( renderLight.shaderParms[SHADERPARM_DIVERSITY] ); + msg.WriteShort( renderLight.shaderParms[SHADERPARM_MODE] ); + + WriteColorToSnapshot( msg ); +} + +/* +================ +idLight::ReadFromSnapshot +================ +*/ +void idLight::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 shaderColor; + int oldCurrentLevel = currentLevel; + idVec3 oldBaseColor = baseColor; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + currentLevel = msg.ReadByte(); + if ( currentLevel != oldCurrentLevel ) { + // need to call On/Off for flickering lights to start/stop the sound + // while doing it this way rather than through events, the flickering is out of sync between clients + // but at least there is no question about saving the event and having them happening globally in the world + if ( currentLevel ) { + On(); + } else { + Off(); + } + } + UnpackColor( msg.ReadLong(), baseColor ); + // lightParentEntityNum = msg.ReadBits( GENTITYNUM_BITS ); + +/* // only helps prediction + UnpackColor( msg.ReadLong(), fadeFrom ); + UnpackColor( msg.ReadLong(), fadeTo ); + fadeStart = msg.ReadLong(); + fadeEnd = msg.ReadLong(); +*/ + + // FIXME: read renderLight.shader + renderLight.lightRadius[0] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[1] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[2] = msg.ReadFloat( 5, 10 ); + + UnpackColor( msg.ReadLong(), shaderColor ); + renderLight.shaderParms[SHADERPARM_RED] = shaderColor[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = shaderColor[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = shaderColor[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = shaderColor[3]; + + renderLight.shaderParms[SHADERPARM_TIMESCALE] = msg.ReadFloat( 5, 10 ); + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadLong(); + //renderLight.shaderParms[SHADERPARM_DIVERSITY] = msg.ReadFloat(); + renderLight.shaderParms[SHADERPARM_MODE] = msg.ReadShort(); + + ReadColorFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( ( currentLevel != oldCurrentLevel ) || ( baseColor != oldBaseColor ) ) { + SetLightLevel(); + } else { + PresentLightDefChange(); + PresentModelDefChange(); + } + } +} + +/* +================ +idLight::ClientReceiveEvent +================ +*/ +bool idLight::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_BECOMEBROKEN: { + BecomeBroken( NULL ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// kfuller: 8/11/03 +void idLight::Event_IsOn() +{ + // not entirely sure this is the best way to check for offness + if (currentLevel == 0) + { + idThread::ReturnFloat( false ); + } + else + { + idThread::ReturnFloat( true ); + } +} + +void idLight::Event_Break(idEntity *activator, float turnOff) +{ + BecomeBroken(activator); + if (turnOff) + { + Off(); + } +} + +void idLight::Event_DoneBlinking() +{ + // switch to a new (possibly non-blinking) shader for the light as well as a new looping sound + idStr blinkedOn; + if (spawnArgs.GetString( "mtr_doneBlinking", "", blinkedOn )) + { + renderLight.shader = declManager->FindMaterial( blinkedOn, false ); + UpdateVisuals(); + Present(); + } + idStr doneBlinkingSound; + if (spawnArgs.GetBool("doneBlinkingNoSound")) + { + StopSound( SCHANNEL_ANY, false ); + } + else if (spawnArgs.GetString("snd_doneBlinking", "", doneBlinkingSound)) + { + StopSound( SCHANNEL_ANY, false ); + if (doneBlinkingSound.Icmp("none")) + { + refSound.shader = declManager->FindSound(doneBlinkingSound); + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + } +} + +void idLight::Event_DoneBlinkingOff() +{ + // switch light and sound off + currentLevel = 0; + SetLightLevel(); +// RAVEN BEGIN + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying ( ) ) { +// RAVEN END + StopSound( SCHANNEL_ANY, false ); + soundWasPlaying = true; + } +} + +// kfuller: want fx entities to be able to respond to earthquakes +void idLight::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + + if (requiresLOS) + { + bool inPVS = gameLocal.InPlayerPVS( this ); + + // for lights, a line-of-sight check doesn't make as much sense, so if the quake requires an LOS check + //we'll actually perform a PVS check + if (!inPVS) + { + return; + } + } + // do something with this light + if (spawnArgs.GetBool("quakeBreak")) + { + spawnArgs.SetBool("quakeBreak", false); + BecomeBroken(gameLocal.entities[ENTITYNUM_WORLD]); + return; + } + + float offTime = spawnArgs.GetFloat("quakeOffTime", "1.0"); + + Off(); + PostEventMS(&EV_Light_On, offTime*1000.0f); +} + +/* +================ +idLight::Event_SetLightGUI +================ +*/ +void idLight::Event_SetLightGUI ( const char* gui ) { + lightGUI = gameLocal.FindEntity( gui ); + if ( lightGUI && lightGUI->GetRenderEntity() && lightGUI->GetRenderEntity()->gui[0] ) { + BecomeActive( TH_THINK ); + } else { + lightGUI = NULL; + } +} + +/* +================ +idLight::Event_SetCurrentLightLevel +================ +*/ +void idLight::Event_SetCurrentLightLevel( int in ) { + currentLevel = in; + SetLightLevel(); +} + +/* +================ +idLight::Event_SetMaxLightLevel +================ +*/ +void idLight::Event_SetMaxLightLevel( int in ) { + levels = in; + SetLightLevel(); +} + +// RAVEN END diff --git a/src/game/Light.h b/src/game/Light.h new file mode 100644 index 0000000..6a3f974 --- /dev/null +++ b/src/game/Light.h @@ -0,0 +1,170 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_LIGHT_H__ +#define __GAME_LIGHT_H__ + +/* +=============================================================================== + + Generic light. + +=============================================================================== +*/ + +extern const idEventDef EV_Light_GetLightParm; +extern const idEventDef EV_Light_SetLightParm; +extern const idEventDef EV_Light_SetLightParms; + +class idLight : public idEntity { +public: + CLASS_PROTOTYPE( idLight ); + + idLight(); + ~idLight(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + virtual void Think( void ); + virtual void FreeLightDef( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + void Present( void ); + + void SaveState( idDict *args ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void GetColor( idVec4 &out ) const; + const idVec3 & GetBaseColor( void ) const { return baseColor; } + void SetShader( const char *shadername ); + void SetLightParm( int parmnum, float value ); + void SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void SetRadiusXYZ( float x, float y, float z ); + void SetRadius( float radius ); + void On( void ); + void Off( void ); + void Fade( const idVec4 &to, float fadeTime ); + void FadeOut( float time ); + void FadeIn( float time ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void BecomeBroken( idEntity *activator ); + qhandle_t GetLightDefHandle( void ) const { return lightDefHandle; } + void SetLightParent( idEntity *lparent ) { lightParent = lparent; } + void SetLightLevel( void ); + +// RAVEN BEGIN +// jshepard: other entities (speakers) need access to the refSound of a light object + void SetRefSound( int rSound ) { refSound.referenceSoundHandle = rSound;} +// ddynerman: sometimes the game needs to know if this light is ambient + bool IsAmbient( void ) { return renderLight.shader->IsAmbientLight(); } +// RAVEN END + virtual void ShowEditingDialog( void ); + + enum { + EVENT_BECOMEBROKEN = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + renderLight_t renderLight; // light presented to the renderer + idVec3 localLightOrigin; // light origin relative to the physics origin + idMat3 localLightAxis; // light axis relative to physics axis + qhandle_t lightDefHandle; // handle to renderer light def + idStr brokenModel; + int levels; + int currentLevel; + idVec3 baseColor; + bool breakOnTrigger; + int count; + int triggercount; + idEntity * lightParent; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; +// RAVEN BEGIN +// bdube: light gui + idEntityPtr lightGUI; +// abahr: + float wait; + float random; +// RAVEN END + +private: + bool soundWasPlaying; + + void PresentLightDefChange( void ); + void PresentModelDefChange( void ); + +// RAVEN BEGIN +// jscott: added events for light level +private: + void Event_SetCurrentLightLevel ( int in ); + void Event_SetMaxLightLevel ( int in ); + void Event_IsOn( void ); + void Event_Break( idEntity *activator, float turnOff ); + void Event_DoneBlinking( void ); + void Event_DoneBlinkingOff( void ); + void Event_EarthQuake( float requiresLOS ); + void Event_Timer( void ); +// RAVEN END + +private: + void Event_SetShader( const char *shadername ); + void Event_GetLightParm( int parmnum ); + void Event_SetLightParm( int parmnum, float value ); + void Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetRadiusXYZ( float x, float y, float z ); + void Event_SetRadius( float radius ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_On( void ); + void Event_Off( void ); + void Event_ToggleOnOff( idEntity *activator ); + void Event_SetSoundHandles( void ); + void Event_FadeOut( float time ); + void Event_FadeIn( float time ); +// RAVEN BEGIN +// bdube: set light gui + void Event_SetLightGUI( const char* gui ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: externed events +extern const idEventDef EV_Light_SetCurrentLightLevel; +extern const idEventDef EV_Light_SetMaxLightLevel; +extern const idEventDef EV_Light_SetRadius; +// RAVEN END + +#endif /* !__GAME_LIGHT_H__ */ diff --git a/src/game/LipSync.cpp b/src/game/LipSync.cpp new file mode 100644 index 0000000..f9eb065 --- /dev/null +++ b/src/game/LipSync.cpp @@ -0,0 +1,491 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// 1 normal +// 2 scared +// 3 surprised +// 4 panicked +// 5 angry +// 6 suspicious with rt eyelid raised +// 7 suspicious with lft eyelid raised +// 8 curious +// 9 tired +// 10 happy + +idCVar fas_debug( "fas_debug", "0", CVAR_INTEGER, "debug info for facial animation system" ); +idCVar fas_threshhold0( "fas_threshhold0", "60", CVAR_INTEGER, "intensity required to use frame set 0" ); +idCVar fas_threshhold1( "fas_threshhold1", "30", CVAR_INTEGER, "intensity required to use frame set 1" ); +idCVar fas_blendBias( "fas_blendBias", "1.5", 0, "multiplier to the per phoneme blend time" ); +idCVar fas_intensityBias( "fas_intensityBias", "0", CVAR_INTEGER, "bias applied to the intensity of the phoneme when trying to extract the viseme" ); +idCVar fas_timeOffset( "fas_timeOffset", "50", CVAR_INTEGER, "ms offset to the viseme frame" ); + +idStr phonemeFile; +idHashTable *visemeTable100; +idHashTable *visemeTable66; +idHashTable *visemeTable33; + +/* +================ +rvViseme::Init +================ +*/ +void rvViseme::Init( idStr &phon, int f, int bt ) +{ + phoneme = phon; + frame = f; + blendTime = bt; +} + +/* +================ +FAS_LoadPhonemes + +Load in the the file that cross references phonemes with visemes. +================ +*/ +bool FAS_LoadPhonemes( const char *visemes ) +{ + idStr visemeFile; + rvViseme viseme; + idLexer lexer; + idToken token; + idStr phoneme; + int frame, blendTime, intensity; + + phonemeFile = visemes; + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + + common->Printf( "Loading viseme file: %s\n", visemes ); + + visemeFile = "lipsync/"; + visemeFile += visemes; + visemeFile += ".viseme"; + + lexer.SetFlags( DECL_LEXER_FLAGS ); + lexer.LoadFile( visemeFile ); + + if( !lexer.ExpectTokenString( "visemes" ) ) + { + return( false ); + } + + if( !lexer.ExpectTokenString( "{" ) ) + { + return( false ); + } + + while( true ) + { + if( !lexer.ReadToken( &token ) ) + { + return( false ); + } + + if( token == "}" ) + { + break; + } + + phoneme = token; + lexer.ExpectTokenString( "," ); + frame = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + blendTime = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + intensity = lexer.ParseInt(); + + viseme.Init( phoneme, frame, blendTime ); + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Set( phoneme, viseme ); + } + else if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Set( phoneme, viseme ); + } + else + { + visemeTable33->Set( phoneme, viseme ); + } + } + + return( true ); +} + +/* +================ +rvLipSyncData +================ +*/ +rvLipSyncData::rvLipSyncData( const rvDeclLipSync *ls, int time ) +{ + const char *lsd = ls->GetLipSyncData(); + + mLexer.SetFlags( LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWNUMBERNAMES ); + mLexer.LoadMemory( lsd, idStr::Length( lsd ), ls->GetName() ); + mFlags = 0; + mFrame = 0; + mBlendTime = 0; + mEmotion = "idle"; + mNextTokenTime = time; +} + +void rvLipSyncData::SetFrame( int frame ) +{ + mLastFrame = mFrame; + mFrame = frame; + mVisemeStartTime = mNextTokenTime; +} + +float rvLipSyncData::GetFrontLerp( void ) +{ + float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( float )( gameLocal.GetTime() + fas_timeOffset.GetInteger() - mVisemeStartTime ) / ( float )mBlendTime ); + return( lerp ); +} + +/* +================ +FAS_StartVisemeExtraction + +Use a lexer to extract the phoneme data. This is a pretty slow but safe way. +There shouldn't be much in the way of multiple lip syncs going on, and if there are, it should be in a non performance critical cinematic. +================ +*/ +rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ) +{ + rvLipSyncData *lsd; + + lsd = new rvLipSyncData( ls, time ); + + return( lsd ); +} + +/* +================ +FAS_EndVisemeExtraction + +Delete the workspace. FAS could use a void pointer if it wanted +================ +*/ +void FAS_EndVisemeExtraction( rvLipSyncData *lsd ) +{ + delete lsd; +} + +/* +================ +FAS_ExtractViseme + +Extract the correct viseme frame from the lexer +================ +*/ +void FAS_ExtractViseme( rvLipSyncData *lsd, int time ) +{ + idToken token; + rvViseme *viseme; + idStr phoneme, duration; + int index, intensity; + + // Make sure not to return any garbage + lsd->ClearFlags(); + + // Grab all the visemes, phrases and emotions until we are current + while( lsd->Ready( time ) ) + { + if( !lsd->ReadToken( &token ) ) + { + lsd->SetFlags( FAS_ENDED ); + return; + } + + if( token == "<" ) + { + // Extract phrase + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse phrase from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetLastPhrase( token ); + lsd->ExpectTokenString( ">" ); + + lsd->SetFlags( FAS_NEW_PHRASE ); + } + else if( token == "{" ) + { + // Extract emotion + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse emotion from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetEmotion( token ); + lsd->ExpectTokenString( "}" ); + + lsd->SetFlags( FAS_NEW_EMOTION ); + } + else + { + // Extract phoneme data + index = 0; + phoneme = idStr( token[index] ); + if( isupper( phoneme[0] ) ) + { + index++; + phoneme += token[index]; + } + + // Extract duration + index++; + + duration = idStr( token[index] ); + index++; + if( isdigit( token[index] ) ) + { + duration += token[index]; + index++; + } + + // Extract intensity + intensity = ( token[index] - 'a' ) * 4; + intensity += fas_intensityBias.GetInteger(); + + // Extract the viseme data for the selected viseme + viseme = NULL; + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Get( phoneme, &viseme ); + } + if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Get( phoneme, &viseme ); + } + else + { + visemeTable33->Get( phoneme, &viseme ); + } + + if( !viseme ) + { + common->Printf( "FAS: Failed to find phoneme %s intensity %d", phoneme.c_str(), intensity ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetFrame( viseme->GetFrame() ); + lsd->SetNextTokenTime( atol( duration ) * 10 ); + lsd->SetBlendTime( int( viseme->GetBlendTime() * fas_blendBias.GetFloat() ) ); + lsd->SetFlags( FAS_NEW_VISEME ); + } + } +} + +/* +================ +FAS_Reload_f +================ +*/ +void FAS_Reload_f( const idCmdArgs &args ) +{ + // mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + + FAS_LoadPhonemes( phonemeFile.c_str() ); + + fileSystem->SetIsFileLoadingAllowed( false ); +} + +/* +================ +FAS_Init +================ +*/ +bool FAS_Init( const char *visemes ) +{ + // jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_ANIM ); + + cmdSystem->AddCommand( "reloadFAS", FAS_Reload_f, CMD_FL_SYSTEM, "reloads the viseme data" ); + return( FAS_LoadPhonemes( visemes ) ); +} + +/* +================ +FAS_Shutdown +================ +*/ +void FAS_Shutdown( void ) +{ + phonemeFile.Clear(); + if ( visemeTable100 ) { + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + } + + cmdSystem->RemoveCommand( "reloadFAS" ); +} + +/* +================ +idAFAttachment::EndLipSyncing +================ +*/ +void idAFAttachment::EndLipSyncing( void ) +{ + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0.0f }; + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + animator.CycleAnim( ANIMCHANNEL_HEAD, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + + FAS_EndVisemeExtraction( lipSyncData ); + lipSyncData = NULL; +} + +/* +================ +idAFAttachment::StartLipSyncing +================ +*/ +int idAFAttachment::StartLipSyncing( const char *speechDecl ) +{ + int length; + + length = 0; + + // Clean up any spurious data + EndLipSyncing(); + + // Start a new lipsync if there is one + if( speechDecl[0] ) + { + const rvDeclLipSync *lipSync; + int emotion; + idStr anim; + + lipSync = declManager->FindLipSync( speechDecl ); + lipSyncData = FAS_StartVisemeExtraction( lipSync, gameLocal.GetTime() ); + + // Output debug info + if( lipSync->GetDescription() && fas_debug.GetInteger() ) + { + gameLocal.Printf( "Name: %s\n", speechDecl ); + gameLocal.Printf( "Sub: %s\n", lipSync->GetDescription().c_str() ); + gameLocal.Printf( "Lip: %s\n", lipSync->GetLipSyncData() ); + } + + // Start the associated sound + refSound.diversity = 0.0f; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + StartSoundShader( declManager->FindSound( lipSync->GetName() ), SND_CHANNEL_VOICE, refSound.parms.soundShaderFlags | SSF_IS_VO, false, &length ); + + // Start the default emotion + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + } + + return( length ); +} + +/* +================ +idAFAttachment::HandleLipSync +================ +*/ +void idAFAttachment::HandleLipSync( void ) +{ + idStr anim; + int emotion; + + if( !lipSyncData ) + { + return; + } + + FAS_ExtractViseme( lipSyncData, gameLocal.GetTime() + fas_timeOffset.GetInteger() ); + if( lipSyncData->HasEnded() ) + { + EndLipSyncing(); + return; + } + + // If frame non zero - blend to it as a new viseme + if( lipSyncData->GetFrame() || lipSyncData->GetLastFrame() ) + { + frameBlend_t frameBlend; + + frameBlend.cycleCount = 0; + frameBlend.frame1 = idMath::ClampInt( 0, 120, lipSyncData->GetLastFrame() - 1 ); + frameBlend.frame2 = idMath::ClampInt( 0, 120, lipSyncData->GetFrame() - 1 ); + frameBlend.frontlerp = lipSyncData->GetFrontLerp(); + frameBlend.backlerp = 1.0f - frameBlend.frontlerp; + + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + if( fas_debug.GetInteger() > 1 ) + { + common->Printf( "Blending: %d (%2f) -> %d (%2f)\n", frameBlend.frame1, frameBlend.frontlerp, frameBlend.frame2, frameBlend.backlerp ); + } + } + + // If an embedded emotion command, play it. + if( lipSyncData->HasNewEmotion() ) + { + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + + if( fas_debug.GetInteger() ) + { + common->Printf( "Emotion: %s\n", lipSyncData->GetEmotion().c_str() ); + } + } + + // If a new phrase, display in debug mode + if( fas_debug.GetInteger() && lipSyncData->HasNewPhrase() ) + { + common->Printf( "Phrase: %s(%i)\n", lipSyncData->GetLastPhrase().c_str(), lipSyncData->GetFrame() ); + } +} + +// end diff --git a/src/game/LipSync.h b/src/game/LipSync.h new file mode 100644 index 0000000..0ac903b --- /dev/null +++ b/src/game/LipSync.h @@ -0,0 +1,111 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _LIPSYNC_H_INC_ +#define _LIPSYNC_H_INC_ + +// Possible TBDs +// Auto head swapping from simple to lip sync head +// Return time of sound from the speak call for the script to wait +// Set emotion directly without encoding it into the string + +class rvViseme +{ +public: + rvViseme( void ) {} + ~rvViseme( void ) { phoneme.Clear(); } + + void Init( idStr &phon, int f, int bt ); + + int GetFrame( void ) const { return( frame ); } + int GetBlendTime( void ) const { return( blendTime ); } + +private: + idStr phoneme; + int frame; + int blendTime; +}; + +#define FAS_NEW_VISEME BIT( 0 ) +#define FAS_NEW_PHRASE BIT( 1 ) +#define FAS_NEW_EMOTION BIT( 2 ) +#define FAS_ENDED BIT( 3 ) + +class rvLipSyncData +{ +public: + rvLipSyncData( const rvDeclLipSync *ls, int time ); + ~rvLipSyncData( void ) {} + + bool Ready( int time ) { return( time >= mNextTokenTime ); } + + int ReadToken( idToken *token ) { return( mLexer.ReadToken( token ) ); } + int ExpectTokenString( const char *str ) { return( mLexer.ExpectTokenString( str ) ); } + void SetNextTokenTime( int time ) { mNextTokenTime += time; } + + void ClearFlags( void ) { mFlags = 0; } + void SetFlags( int flags ) { mFlags |= flags; } + bool HasNewPhoneme( void ) const { return( !!( mFlags & FAS_NEW_VISEME ) ); } + bool HasNewPhrase( void ) const { return( !!( mFlags & FAS_NEW_PHRASE ) ); } + bool HasNewEmotion( void ) const { return( !!( mFlags & FAS_NEW_EMOTION ) ); } + bool HasEnded( void ) const { return( !!( mFlags & FAS_ENDED ) ); } + + void SetFrame( int frame ); + int GetFrame( void ) const { return( mFrame ); } + int GetLastFrame( void ) { return( mLastFrame ); } + + void SetBlendTime( int bt ) { mBlendTime = bt; } + int GetBlendTime( void ) const { return( mBlendTime ); } + + float GetFrontLerp( void ); + + void SetEmotion( idStr &str ) { mEmotion = str; } + const idStr &GetEmotion( void ) const { return( mEmotion ); } + + void SetLastPhrase( idStr &str ) { mLastPhrase = str; } + const idStr &GetLastPhrase( void ) const { return( mLastPhrase ); } + +private: + int mNextTokenTime; + int mFlags; + int mFrame; + int mLastFrame; + int mVisemeStartTime; + int mBlendTime; + idStr mEmotion; + idStr mLastPhrase; + idLexer mLexer; +}; + +bool FAS_Init( const char *visemes ); +void FAS_Shutdown( void ); + +void FAS_ExtractViseme( class rvLipSyncData *lsd, int time ); +class rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ); +void FAS_EndVisemeExtraction( class rvLipSyncData *lsd ); + +extern idCVar fas_debug; +extern idCVar fas_timeOffset; + +#endif // _LIPSYNC_H_INC_ diff --git a/src/game/Misc.cpp b/src/game/Misc.cpp new file mode 100644 index 0000000..435620d --- /dev/null +++ b/src/game/Misc.cpp @@ -0,0 +1,4410 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Various utility objects and functions. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// nmckenzie: added ai +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#include "ai/AI.h" +// RAVEN END + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpawnableEntity ) +END_CLASS + +/* +====================== +idSpawnableEntity::Spawn +====================== +*/ +void idSpawnableEntity::Spawn() { + // this just holds dict information +} + + +/* +=============================================================================== + + idPlayerStart + +=============================================================================== +*/ + +const idEventDef EV_TeleportStage( "", "e" ); + +CLASS_DECLARATION( idEntity, idPlayerStart ) + EVENT( EV_Activate, idPlayerStart::Event_Teleport ) + EVENT( EV_TeleportStage, idPlayerStart::Event_TeleportStage ) +END_CLASS + +/* +=============== +idPlayerStart::idPlayerStart +================ +*/ +idPlayerStart::idPlayerStart( void ) { + teleportStage = 0; +} + +/* +=============== +idPlayerStart::Spawn +================ +*/ +void idPlayerStart::Spawn( void ) { + teleportStage = 0; +} + +/* +================ +idPlayerStart::Save +================ +*/ +void idPlayerStart::Save( idSaveGame *savefile ) const { + savefile->WriteInt( teleportStage ); +} + +/* +================ +idPlayerStart::Restore +================ +*/ +void idPlayerStart::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( teleportStage ); +} + +/* +================ +idPlayerStart::ClientReceiveEvent +================ +*/ +bool idPlayerStart::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int entityNumber; + + switch( event ) { + case EVENT_TELEPORTPLAYER: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idPlayer *player = static_cast( gameLocal.entities[entityNumber] ); + float x = msg.ReadFloat(); + float y = msg.ReadFloat(); + float z = msg.ReadFloat(); + + idVec3 prevOrigin( x, y, z ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( player != NULL && player->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Event_TeleportEntity( player, false, prevOrigin ); + } + return true; + } + case EVENT_TELEPORTITEM: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idEntity* activator = gameLocal.entities[ entityNumber ]; + + if ( activator != NULL ) { + Event_TeleportEntity( activator, false ); + } + return true; + } + + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +=============== +idPlayerStart::Event_TeleportStage + +FIXME: add functionality to fx system ( could be done with player scripting too ) +================ +*/ +void idPlayerStart::Event_TeleportStage( idPlayer *player ) { + float teleportDelay = spawnArgs.GetFloat( "teleportDelay" ); + switch ( teleportStage ) { + case 0: + player->playerView.Flash( colorWhite, 125 ); + player->SetInfluenceLevel( INFLUENCE_LEVEL3 ); + player->SetInfluenceView( spawnArgs.GetString( "mtr_teleportFx" ), NULL, 0.0f, NULL ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -20.0f, teleportDelay ); + teleportStage++; + PostEventSec( &EV_TeleportStage, teleportDelay, player ); + break; + case 1: + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0.25f ); + teleportStage++; + PostEventSec( &EV_TeleportStage, 0.25f, player ); + break; + case 2: + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + TeleportPlayer( player ); + player->StopSound( SND_CHANNEL_BODY2, false ); + player->SetInfluenceLevel( INFLUENCE_NONE ); + teleportStage = 0; + break; + default: + break; + } +} + +/* +=============== +idPlayerStart::TeleportPlayer +================ +*/ +void idPlayerStart::TeleportPlayer( idPlayer *player ) { + float pushVel = spawnArgs.GetFloat( "push", "50000" ); + float f = spawnArgs.GetFloat( "visualEffect", "0" ); + const char *viewName = spawnArgs.GetString( "visualView", "" ); + idEntity *ent = viewName ? gameLocal.FindEntity( viewName ) : NULL; + + if ( f && ent ) { + // place in private camera view for some time + // the entity needs to teleport to where the camera view is to have the PVS right + player->Teleport( ent->GetPhysics()->GetOrigin(), ang_zero, this ); + + player->SetPrivateCameraView( static_cast(ent) ); + // the player entity knows where to spawn from the previous Teleport call + if ( !gameLocal.isClient ) { + player->PostEventSec( &EV_Player_ExitTeleporter, f ); + } + } else { + // direct to exit, Teleport will take care of the killbox + player->Teleport( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles(), NULL ); + + // multiplayer hijacked this entity, so only push the player in multiplayer + if ( gameLocal.isMultiplayer ) { + //push + idVec3 impulse( pushVel, 0, 0 ); + impulse *= GetPhysics( )->GetAxis ( ); + player->ApplyImpulse( gameLocal.world, 0, player->GetPhysics( )->GetOrigin( ), impulse ); + } + } +} + +/* +=============== +idPlayerStart::Teleport +For non-players +================ +*/ +void idPlayerStart::Teleport( idEntity* other ) { + other->SetOrigin( GetPhysics()->GetOrigin() ); + idVec3 vel = other->GetPhysics()->GetLinearVelocity(); + vel *= GetPhysics()->GetAxis(); + other->GetPhysics()->SetLinearVelocity( vel ); +} + +void idPlayerStart::Event_TeleportEntity( idEntity* activator, bool predicted, idVec3& prevOrigin ) { + idPlayer *player; + + if ( activator->IsType( idPlayer::GetClassType() ) ) { + player = static_cast( activator ); + } else { + player = NULL; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( activator->entityNumber, GENTITYNUM_BITS ); + ServerSendInstanceEvent( EVENT_TELEPORTITEM, &msg, false, -1 ); + } + + idVec3 oldOrigin = activator->GetPhysics()->GetOrigin(); + Teleport( activator ); + + if( gameLocal.isMultiplayer ) { + if( gameLocal.isServer ) { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", activator->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else if( predicted ) { + // only predict teleport enter + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + + if ( player ) { + if ( spawnArgs.GetBool( "visualFx" ) ) { + + teleportStage = 0; + Event_TeleportStage( player ); + + } else { + idVec3 oldOrigin; + + if( gameLocal.isServer || prevOrigin == vec3_zero ) { + oldOrigin = activator->GetPhysics()->GetOrigin(); + } else { + oldOrigin = prevOrigin; + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( player->entityNumber, GENTITYNUM_BITS ); + msg.WriteFloat( oldOrigin.x ); + msg.WriteFloat( oldOrigin.y ); + msg.WriteFloat( oldOrigin.z ); + ServerSendInstanceEvent( EVENT_TELEPORTPLAYER, &msg, false, -1 ); + } + + TeleportPlayer( player ); + + if( gameLocal.isMultiplayer ) { + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport", player->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + } + + +/* +=============== +idPlayerStart::Event_TeleportPlayer +================ +*/ +void idPlayerStart::Event_Teleport( idEntity *activator ) { + Event_TeleportEntity( activator, true ); +} + +/* +=============================================================================== + + idActivator + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idActivator ) + EVENT( EV_Activate, idActivator::Event_Activate ) +END_CLASS + +/* +=============== +idActivator::Save +================ +*/ +void idActivator::Save( idSaveGame *savefile ) const { + savefile->WriteBool( stay_on ); +} + +/* +=============== +idActivator::Restore +================ +*/ +void idActivator::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( stay_on ); + + if ( stay_on ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Spawn +================ +*/ +void idActivator::Spawn( void ) { + bool start_off; + + spawnArgs.GetBool( "stay_on", "0", stay_on ); + spawnArgs.GetBool( "start_off", "0", start_off ); + +// RAVEN BEGIN +// bdube: optional clip model on activators + const char *temp; + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel(temp), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } else { + GetPhysics()->SetClipBox( idBounds( vec3_origin ).Expand( 4 ), 1.0f ); + } + + GetPhysics()->SetContents( 0 ); +// RAVEN END + + if ( !start_off ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Think +================ +*/ +void idActivator::Think( void ) { + RunPhysics(); + if ( thinkFlags & TH_THINK ) { + if ( TouchTriggers() ) { + if ( !stay_on ) { + BecomeInactive( TH_THINK ); + } + } + } + Present(); +} + +/* +=============== +idActivator::Activate +================ +*/ +void idActivator::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + + +/* +=============================================================================== + +idPathCorner + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPathCorner ) +// EVENT( AI_RandomPath, idPathCorner::Event_RandomPath ) +END_CLASS + +/* +===================== +idPathCorner::Spawn +===================== +*/ +void idPathCorner::Spawn( void ) { +} + +/* +===================== +idPathCorner::DrawDebugInfo +===================== +*/ +void idPathCorner::DrawDebugInfo( void ) { + idEntity *ent; + idBounds bnds( idVec3( -4.0, -4.0f, -8.0f ), idVec3( 4.0, 4.0f, 64.0f ) ); + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idPathCorner::Type ) ) { + continue; + } + + idVec3 org = ent->GetPhysics()->GetOrigin(); + gameRenderWorld->DebugBounds( colorRed, bnds, org, 0 ); + } +} + +/* +============ +idPathCorner::RandomPath +============ +*/ +idPathCorner *idPathCorner::RandomPath( const idEntity *source, const idEntity *ignore ) { + int i; + int num; + int which; + idEntity *ent; + idPathCorner *path[ MAX_GENTITIES ]; + + num = 0; + for( i = 0; i < source->targets.Num(); i++ ) { + ent = source->targets[ i ].GetEntity(); + if ( ent && ( ent != ignore ) && ent->IsType( idPathCorner::Type ) ) { + path[ num++ ] = static_cast( ent ); + if ( num >= MAX_GENTITIES ) { + break; + } + } + } + + if ( !num ) { + return NULL; + } + + which = gameLocal.random.RandomInt( num ); + return path[ which ]; +} + +/* +===================== +idPathCorner::Event_RandomPath +===================== +*/ +void idPathCorner::Event_RandomPath( void ) { + idPathCorner *path; + + path = RandomPath( this, NULL ); + idThread::ReturnEntity( path ); +} + +/* +=============================================================================== + + idDamagable + +=============================================================================== +*/ + +const idEventDef EV_RestoreDamagable( "" ); +// RAVEN BEGIN +// kfuller: spawn other things +const idEventDef EV_SpawnForcefield( "" ); +const idEventDef EV_SpawnTriggerHurt( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idDamagable ) + EVENT( EV_Activate, idDamagable::Event_BecomeBroken ) + EVENT( EV_RestoreDamagable, idDamagable::Event_RestoreDamagable ) +END_CLASS + +/* +================ +idDamagable::idDamagable +================ +*/ +idDamagable::idDamagable( void ) { + count = 0; + nextTriggerTime = 0; + invincibleTime = 0; +} + +/* +================ +idDamagable::Save +================ +*/ +void idDamagable::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( invincibleTime ); + + savefile->WriteInt( stage ); + savefile->WriteInt( stageNext ); + + // cnicholson: Don't save the stageDict, its setup in the restore + + savefile->WriteInt( stageEndTime ); + savefile->WriteInt( stageEndHealth ); + savefile->WriteInt( stageEndSpeed ); + savefile->WriteBool( stageEndOnGround ); + savefile->WriteBool( activateStageOnTrigger ); + + savefile->WriteInt( count ); + savefile->WriteInt( nextTriggerTime ); +} + +/* +================ +idDamagable::Restore +================ +*/ +void idDamagable::Restore( idRestoreGame *savefile ) { + + const char* stageName; + + savefile->ReadInt( invincibleTime ); + + savefile->ReadInt( stage ); + savefile->ReadInt( stageNext ); + savefile->ReadInt( stageEndTime ); + savefile->ReadInt( stageEndHealth ); + savefile->ReadInt( stageEndSpeed ); + savefile->ReadBool( stageEndOnGround ); + savefile->ReadBool( activateStageOnTrigger ); + + savefile->ReadInt( count ); + savefile->ReadInt( nextTriggerTime ); + + + // Get the stage name, if there is none then there are no more stages + stageDict = NULL; + if ( spawnArgs.GetString ( va( "def_stage%d", stage ), "", &stageName ) && stageName ) { + + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + } +} + +/* +================ +idDamagable::Spawn +================ +*/ +void idDamagable::Spawn( void ) { + idStr broken; + bool keepContents; + +// RAVEN BEGIN +// abahr: stage stuff + stage = 0; + stageNext = 1; + stageDict = NULL; + stageEndTime = 0; + stageEndHealth = 9999; + stageEndSpeed = 0; +//jshepard: + stageEndOnGround = false; +// RAVEN END + + health = spawnArgs.GetInt( "health", "5" ); + +// RAVEN BEGIN +// abahr: stage stuff + stageEndHealth = health - 1; + activateStageOnTrigger = spawnArgs.GetBool("activateOnTrigger"); +// RAVEN END + + spawnArgs.GetInt( "count", "1", count ); + invincibleTime = gameLocal.GetTime() + SEC2MS( spawnArgs.GetFloat( "invincibleTime", "0" ) ); + nextTriggerTime = 0; + + // make sure the model gets cached + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() && !renderModelManager->CheckModel( broken ) ) { + gameLocal.Error( "idDamagable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), broken.c_str() ); + } + +// RAVEN BEGIN +// bdube: turn take damage off if it has no health + fl.takedamage = health > 0 ? true : false; +// RAVEN END + + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idDamagable::BecomeBroken +================ +*/ +void idDamagable::BecomeBroken( idEntity *activator ) { + float forceState; + int numStates; + int cycle; + float wait; + + if ( gameLocal.time < nextTriggerTime ) { + return; + } + + spawnArgs.GetFloat( "wait", "0.1", wait ); + nextTriggerTime = gameLocal.time + SEC2MS( wait ); + if ( count > 0 ) { + count--; + if ( !count ) { + fl.takedamage = false; + } else { + health = spawnArgs.GetInt( "health", "5" ); + } + } + + idStr broken; + + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() ) { + SetModel( broken ); + } + + // offset the start time of the shader to sync it to the gameLocal time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + spawnArgs.GetInt( "numstates", "1", numStates ); + spawnArgs.GetInt( "cycle", "0", cycle ); + spawnArgs.GetFloat( "forcestate", "0", forceState ); + + // set the state parm + if ( cycle ) { + renderEntity.shaderParms[ SHADERPARM_MODE ]++; + if ( renderEntity.shaderParms[ SHADERPARM_MODE ] > numStates ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = 0; + } + } else if ( forceState ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = forceState; + } else { + renderEntity.shaderParms[ SHADERPARM_MODE ] = gameLocal.random.RandomInt( numStates ) + 1; + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + ActivateTargets( activator ); + + if ( spawnArgs.GetBool( "hideWhenBroken" ) ) { + Hide(); + PostEventMS( &EV_RestoreDamagable, nextTriggerTime - gameLocal.time ); + BecomeActive( TH_THINK ); + } +} + +/* +============ +idMoveable::Damage +============ +*/ +// RAVEN BEGIN +// abahr +void idDamagable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + + if ( invincibleTime > gameLocal.GetTime() ) { + return; + } + + // If there is a damage filter we need to check to see if the damage def meets the requirement + const char* damageFilter; + if ( spawnArgs.GetString ( "damage_filter", "", &damageFilter ) && *damageFilter ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + // If the filter isnt matched then ignore it + if ( !damageDef->GetBool ( va("filter_%s", damageFilter ) ) ) { + return; + } + } + if ( spawnArgs.GetBool( "nosplash", "0" ) ) { + //ignore splash damage + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + //if it has radius, then it's splash damage and ignore it + if ( damageDef->GetFloat ( "radius", "0" ) ) { + return; + } + } + + idEntity::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Force a stage update so impact effects will be correct + UpdateStage(); +} + +/* +================ +idDamagable::Killed +================ +*/ +void idDamagable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( gameLocal.time < nextTriggerTime ) { + health += damage; + return; + } + + BecomeBroken( attacker ); +} + +/* +================ +idDamagable::UpdateStage +================ +*/ +void idDamagable::UpdateStage ( void ) { + + // If there is a stage to go to then see if its time to do that + if ( stage != stageNext ) { + int oldstage; + + // Check to see if the stage is complete + oldstage = stage; + while ( health < stageEndHealth || (stageEndTime != 0 && gameLocal.time > stageEndTime ) || (stageEndOnGround && GetPhysics()->HasGroundContacts()) || activateStageOnTrigger ) { + stage = stageNext; + + //this only needs to happen once + activateStageOnTrigger = false; + + // Get the stage name, if there is none then there are no more stages + const char* stageName; + if ( !spawnArgs.GetString ( va("def_stage%d", stage ), "", &stageName ) || !stageName ) { + stage = 0; + break; + } + + // Get the stage dictionary + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + if ( !stageDict ) { + gameLocal.Warning ( "could not find stage '%s' for moveable '%s'", stageName, name.c_str() ); + stage = 0; + break; + } + + // Get the end conditions of the stage + stageEndHealth = stageDict->GetInt ( "end_health", "-9999" ); + + stageEndOnGround = stageDict->GetBool( "end_onGround" ); + + // Timed? + stageEndTime = SEC2MS ( GetStageFloat ( "end_time" ) ); + if ( stageEndTime > 0 ) { + stageEndTime += gameLocal.time; + } + + // Set the next stage to move to when end conditions met + stageNext = stage + 1; + + // Execute the new stage + ExecuteStage ( ); + } + } +} + +/* +================ +idDamagable::ExecuteStage +================ +*/ +void idDamagable::ExecuteStage ( void ) { + const idKeyValue* kv; + bool remove; + + // Remove the entity this frame? + remove = stageDict->GetBool ( "remove" ); + + // Targets? + if ( stageDict->GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } + + // Unbind targets + if ( stageDict->GetBool( "unbindTargets" ) ) { + UnbindTargets( this ); + } + + // Stop current looping effects + StopAllEffects ( ); + + // Play all effects (if they start with "fx_loop" then also loop them + for ( kv = stageDict->MatchPrefix ( "fx_" ); kv; kv = stageDict->MatchPrefix("fx_",kv) ) { + if ( !kv->GetKey().Icmpn ( "fx_loop", 7 ) ) { + if ( !remove ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, kv->GetKey() ), + renderEntity.origin + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() * renderEntity.axis, + true ); + } + } else { + gameLocal.PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_explode" ), + GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ) * GetPhysics()->GetAxis(), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() ); + } + } + + // Kick off debris + for ( kv = stageDict->MatchPrefix ( "def_debris" ); kv; kv = stageDict->MatchPrefix("def_debris",kv) ) { + const idDict* args = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !args ) { + continue; + } + + rvClientMoveable* cent = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + continue; + } + cent->SetOrigin ( GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis() ); + cent->SetAxis ( GetPhysics()->GetAxis ( ) ); + + idVec3 vel; + vel = GetStageVector ( va("vel_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis(); + vel += GetPhysics()->GetLinearVelocity ( ); + cent->GetPhysics()->SetLinearVelocity ( vel ); + cent->PostEventMS ( &CL_FadeOut, 2500, 2500 ); + } + + // Apply force to all objects nearby + float fPush; + stageDict->GetFloat("radiusPush","0",fPush); + if (fPush > 0) { + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 128, fPush, this, this, 1.0f, true ); + } + + // Remove the entity now? + if ( remove ) { + Hide ( ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Switch model? + const char* model; + if ( stageDict->GetString ( "model", "", &model ) && *model ) { + SetModel( model ); + } + + // Skin? + const char* skin; + if ( stageDict->GetString ( "skin", "", &skin ) && *skin ) { + renderEntity.customSkin = declManager->FindSkin ( skin, false ); + } + + // Velocities + idVec3 vel; + vel = GetStageVector ( "vel_world" ); + vel += (GetStageVector ( "vel_local" ) * GetPhysics()->GetAxis() ); + vel += GetPhysics()->GetLinearVelocity ( ); + GetPhysics()->SetLinearVelocity ( vel ); + + // Enable thinking to ensure stages are run + BecomeActive ( TH_THINK ); +} + +/* +================ +idDamagable::GetStageVector +================ +*/ +idVec3 idDamagable::GetStageVector ( const char* key, const char* defaultString ) const { + idVec3 mins; + if ( stageDict->GetVector ( va("%s_min", key ), "", mins ) ) { + idVec3 maxs; + stageDict->GetVector ( va("%s_max", key), mins.ToString(), maxs ); + return mins + (maxs - mins) * gameLocal.random.RandomFloat ( ); + } + + return stageDict->GetVector ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageFloat +================ +*/ +float idDamagable::GetStageFloat ( const char* key, const char* defaultString ) const { + float minValue; + if ( stageDict->GetFloat ( va("%s_min", key ), "", minValue ) ) { + float maxValue; + stageDict->GetFloat ( va("%s_max", key), va("%g",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetFloat ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageInt +================ +*/ +int idDamagable::GetStageInt ( const char* key, const char* defaultString ) const { + int minValue; + if ( stageDict->GetInt ( va("%s_min", key ), "", minValue ) ) { + int maxValue; + stageDict->GetInt ( va("%s_max", key), va("%d",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetInt ( key, defaultString ); +} + +/* +================ +idDamagable::Event_BecomeBroken +================ +*/ +void idDamagable::Event_BecomeBroken( idEntity *activator ) { + BecomeBroken( activator ); + +// RAVEN BEGIN +// bdube: start stages + UpdateStage ( ); +// RAVEN END +} + +/* +================ +idDamagable::Event_RestoreDamagable +================ +*/ +void idDamagable::Event_RestoreDamagable( void ) { + health = spawnArgs.GetInt( "health", "5" ); + Show(); +} + + +/* +=============================================================================== + + idExplodable + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idExplodable ) + EVENT( EV_Activate, idExplodable::Event_Explode ) +END_CLASS + +/* +================ +idExplodable::Spawn +================ +*/ +void idExplodable::Spawn( void ) { + Hide(); +} + +/* +================ +idExplodable::Event_Explode +================ +*/ +void idExplodable::Event_Explode( idEntity *activator ) { + const char *temp; + + if ( spawnArgs.GetString( "def_damage", "damage_explosion", &temp ) ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), activator, activator, this, this, temp ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + // Show() calls UpdateVisuals, so we don't need to call it ourselves after setting the shaderParms + renderEntity.shaderParms[SHADERPARM_RED] = 1.0f; + renderEntity.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEntity.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = 0.0f; + Show(); + + PostEventMS( &EV_Remove, 2000 ); + + ActivateTargets( activator ); +} + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpring ) + EVENT( EV_PostSpawn, idSpring::Event_LinkSpring ) +END_CLASS + +/* +================ +idSpring::Think +================ +*/ +void idSpring::Think( void ) { + idVec3 start, end, origin; + idMat3 axis; + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + // evaluate force + spring.Evaluate( gameLocal.time ); + + start = p1; + if ( ent1 && ent1->GetPhysics() ) { + axis = ent1->GetPhysics()->GetAxis(); + origin = ent1->GetPhysics()->GetOrigin(); + start = origin + start * axis; + } + + end = p2; + if ( ent2 && ent2->GetPhysics() ) { + axis = ent2->GetPhysics()->GetAxis(); + origin = ent2->GetPhysics()->GetOrigin(); + end = origin + p2 * axis; + } + + gameRenderWorld->DebugLine( idVec4(1, 1, 0, 1), start, end, 0, true ); + } + + Present(); +} + +/* +================ +idSpring::Event_LinkSpring +================ +*/ +void idSpring::Event_LinkSpring( void ) { + idStr name1, name2; + + spawnArgs.GetString( "ent1", "", name1 ); + spawnArgs.GetString( "ent2", "", name2 ); + + if ( name1.Length() ) { + ent1 = gameLocal.FindEntity( name1 ); + if ( !ent1 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find first entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name1.c_str() ); + } + } + else { + ent1 = gameLocal.entities[ENTITYNUM_WORLD]; + } + + if ( name2.Length() ) { + ent2 = gameLocal.FindEntity( name2 ); + if ( !ent2 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find second entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name2.c_str() ); + } + } + else { + ent2 = gameLocal.entities[ENTITYNUM_WORLD]; + } + spring.SetPosition( ent1->GetPhysics(), id1, p1, ent2->GetPhysics(), id2, p2 ); + BecomeActive( TH_THINK ); +} + +/* +================ +idSpring::Spawn +================ +*/ +void idSpring::Spawn( void ) { + float Kstretch, damping, restLength; + + spawnArgs.GetInt( "id1", "0", id1 ); + spawnArgs.GetInt( "id2", "0", id2 ); + spawnArgs.GetVector( "point1", "0 0 0", p1 ); + spawnArgs.GetVector( "point2", "0 0 0", p2 ); + spawnArgs.GetFloat( "constant", "100.0f", Kstretch ); + spawnArgs.GetFloat( "damping", "10.0f", damping ); + spawnArgs.GetFloat( "restlength", "0.0f", restLength ); + + spring.InitSpring( Kstretch, 0.0f, damping, restLength ); + + ent1 = ent2 = NULL; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idSpring::Save +================ +*/ +void idSpring::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( id1 ); + savefile->WriteInt ( id2 ); + savefile->WriteVec3 ( p1 ); + savefile->WriteVec3 ( p2 ); + spring.Save ( savefile ); +} + +/* +================ +idSpring::Restore +================ +*/ +void idSpring::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( id1 ); + savefile->ReadInt ( id2 ); + savefile->ReadVec3 ( p1 ); + savefile->ReadVec3 ( p2 ); + spring.Restore ( savefile ); + Event_LinkSpring ( ); +} + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const idEventDef EV_Toggle( "Toggle", NULL ); + +CLASS_DECLARATION( idEntity, idForceField ) + EVENT( EV_Activate, idForceField::Event_Activate ) + EVENT( EV_Toggle, idForceField::Event_Toggle ) + EVENT( EV_FindTargets, idForceField::Event_FindTargets ) +END_CLASS + +/* +=============== +idForceField::Toggle +================ +*/ +void idForceField::Toggle( void ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idForceField::Think +================ +*/ +void idForceField::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + } + Present(); +} + +/* +================ +idForceField::Save +================ +*/ +void idForceField::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( forceField ); +} + +/* +================ +idForceField::Restore +================ +*/ +void idForceField::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( forceField ); +} + +/* +================ +idForceField::Spawn +================ +*/ +void idForceField::Spawn( void ) { + idVec3 uniform; + float explosion, implosion, randomTorque; + + if ( spawnArgs.GetVector( "uniform", "0 0 0", uniform ) ) { + forceField.Uniform( uniform ); + } else if ( spawnArgs.GetFloat( "explosion", "0", explosion ) ) { + forceField.Explosion( explosion ); + } else if ( spawnArgs.GetFloat( "implosion", "0", implosion ) ) { + forceField.Implosion( implosion ); + } + + if ( spawnArgs.GetFloat( "randomTorque", "0", randomTorque ) ) { + forceField.RandomTorque( randomTorque ); + } + + if ( spawnArgs.GetBool( "applyForce", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_FORCE ); + } else if ( spawnArgs.GetBool( "applyImpulse", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_IMPULSE ); + } else { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + } + + forceField.SetPlayerOnly( spawnArgs.GetBool( "playerOnly", "0" ) ); + forceField.SetMonsterOnly( spawnArgs.GetBool( "monsterOnly", "0" ) ); + + // set the collision model on the force field +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + forceField.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + forceField.SetOwner( this ); + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idForceField::Event_Toggle +================ +*/ +void idForceField::Event_Toggle( void ) { + Toggle(); +} + +/* +================ +idForceField::Event_Activate +================ +*/ +void idForceField::Event_Activate( idEntity *activator ) { + float wait; + + Toggle(); + if ( spawnArgs.GetFloat( "wait", "0.01", wait ) ) { + PostEventSec( &EV_Toggle, wait ); + } +} + +/* +================ +idForceField::Event_FindTargets +================ +*/ +void idForceField::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + forceField.Uniform( targets[0].GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ); + } +} + +// RAVEN BEGIN +// bdube: jump pads + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const int JUMPPAD_EFFECT_DELAY = 100; + +CLASS_DECLARATION( idForceField, rvJumpPad ) + EVENT( EV_FindTargets, rvJumpPad::Event_FindTargets ) +END_CLASS + +/* +================ +rvJumpPad::rvJumpPad +================ +*/ +rvJumpPad::rvJumpPad ( void ) { + lastEffectTime = -1; +} + +/* +================ +rvJumpPad::Think +================ +*/ +void rvJumpPad::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + + // If force has been applied to an entity and jump pad effect hasnt been played for a bit + // then play it now. + if ( forceField.GetLastApplyTime ( ) - lastEffectTime > JUMPPAD_EFFECT_DELAY ) { + // start locally + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + + // send through unreliable + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_JUMPFX ); + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( renderEntity.origin ) ); + } + + lastEffectTime = forceField.GetLastApplyTime( ); + } + } + Present(); +} + +/* +================ +rvJumpPad::Spawn +================ +*/ +void rvJumpPad::Spawn( void ) { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + forceField.SetOwner( this ); +} + +/* +================ +rvJumpPad::Event_FindTargets +================ +*/ +void rvJumpPad::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + idEntity* ent; + ent = targets[0].GetEntity(); + assert( ent ); + + idVec3 vert; + idVec3 diff; + idVec3 vel; + idVec3 localGravity( gameLocal.GetCurrentGravity(this) ); + idVec3 localGravityNormal( localGravity.ToNormal() ); + float time; + float dist; + + // Find the distance along the gravity vector between the jump pad and its target + diff = (ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()); + vert = (diff * localGravityNormal) * localGravityNormal; + + // Determine how long it would take to cover the distance along the gravity vector + time = idMath::Sqrt( vert.Length() / (0.5f * localGravity.Length()) ); + if ( !time ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // The final velocity is the direction between the jump pad and its target using + // the travel time to determine the forward vector and adding in an inverse gravity vector. + vel = diff - vert; + dist = vel.Normalize(); + + vel = vel * (dist / time); + vel += (localGravity * -time); + + forceField.Uniform( vel ); + + // calculate a coordinate axis where the vector to the jumppad target is forward + // use this to play the fx_jump fx + diff.Normalize(); + effectAxis = diff.ToMat3(); + } +} + +/* +=============== +rvJumpPad::ClientReceiveEvent +=============== +*/ +bool rvJumpPad::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch ( event ) { + case EVENT_JUMPFX: { + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + return true; + } + default: + return idEntity::ClientReceiveEvent( event, time, msg ); + } +} + +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +const idEventDef EV_Animated_Start( "" ); +const idEventDef EV_LaunchMissiles( "launchMissiles", "ssssdf" ); +const idEventDef EV_LaunchMissilesUpdate( "", "dddd" ); +const idEventDef EV_AnimDone( "", "d" ); +const idEventDef EV_StartRagdoll( "startRagdoll" ); + +// RAVEN BEGIN +// bdube: script object +const idEventDef EV_SetAnimState ( "setAnimState", "sd" ); +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idAnimated ) + EVENT( EV_Activate, idAnimated::Event_Activate ) + EVENT( EV_Animated_Start, idAnimated::Event_Start ) + EVENT( EV_StartRagdoll, idAnimated::Event_StartRagdoll ) + EVENT( EV_AnimDone, idAnimated::Event_AnimDone ) + EVENT( EV_Footstep, idAnimated::Event_Footstep ) + EVENT( EV_FootstepLeft, idAnimated::Event_Footstep ) + EVENT( EV_FootstepRight, idAnimated::Event_Footstep ) + EVENT( EV_LaunchMissiles, idAnimated::Event_LaunchMissiles ) + EVENT( EV_LaunchMissilesUpdate, idAnimated::Event_LaunchMissilesUpdate ) + +// RAVEN BEGIN +// bdube: script object + EVENT( EV_SetAnimState, idAnimated::Event_SetAnimState ) + EVENT( AI_PlayAnim, idAnimated::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAnimated::Event_PlayCycle ) + EVENT( AI_AnimDone, idAnimated::Event_AnimDone2 ) +// RAVEN END + +END_CLASS + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::idAnimated() { + int i; + + anim = 0; + blendFrames = 0; + soundJoint = INVALID_JOINT; + activated = false; + combatModel = NULL; + activator = NULL; + current_anim_index = 0; + num_anims = 0; +// RAVEN BEGIN +// bdube: script control + scriptThread = NULL; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + animDoneTime[i] = 0; + } +// RAVEN END +} + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::~idAnimated() { + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN +// bdube: kill script object + delete scriptThread; +// RAVEN END +} + +/* +=============== +idAnimated::Save +================ +*/ +void idAnimated::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( num_anims ); + savefile->WriteInt( current_anim_index ); + savefile->WriteInt( anim ); + savefile->WriteInt( blendFrames ); + savefile->WriteJoint( soundJoint ); + activator.Save( savefile ); + savefile->WriteBool( activated ); + +// RAVEN BEGIN + savefile->WriteObject( scriptThread ); + savefile->WriteString( state ); + savefile->WriteString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Restore +================ +*/ +void idAnimated::Restore( idRestoreGame *savefile ) { + int i; + + savefile->ReadInt( num_anims ); + savefile->ReadInt( current_anim_index ); + savefile->ReadInt( anim ); + savefile->ReadInt( blendFrames ); + savefile->ReadJoint( soundJoint ); + activator.Restore( savefile ); + savefile->ReadBool( activated ); + +// RAVEN BEGIN + savefile->ReadObject( reinterpret_cast( scriptThread ) ); + savefile->ReadString( state ); + savefile->ReadString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Spawn +================ +*/ +void idAnimated::Spawn( void ) { + idStr animname; + int anim2; + float wait; + const char *joint; + + joint = spawnArgs.GetString( "sound_bone", "origin" ); + soundJoint = animator.GetJointHandle( joint ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), joint ); + } + + LoadAF( NULL ); + + // allow bullets to collide with a combat model + if ( spawnArgs.GetBool( "combatModel", "0" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + // allow the entity to take damage + if ( spawnArgs.GetBool( "takeDamage", "0" ) ) { + fl.takedamage = true; + } + + blendFrames = 0; + +// RAVEN BEGIN +// bdube: script control + // setup script object + const char* scriptObjectName; + if ( spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + const function_t *constructor; + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + scriptThread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + scriptThread->ManualDelete(); + scriptThread->ManualControl(); + scriptThread->SetThreadName( name.c_str() ); + scriptThread->CallFunction( this, constructor, true ); + scriptThread->Execute ( ); + } + + fl.takedamage = true; + + return; + } +// RAVEN END + + current_anim_index = 0; + spawnArgs.GetInt( "num_anims", "0", num_anims ); + + blendFrames = spawnArgs.GetInt( "blend_in" ); + + animname = spawnArgs.GetString( num_anims ? "anim1" : "anim" ); + if ( !animname.Length() ) { + anim = 0; + } else { + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + } + + if ( spawnArgs.GetBool( "hide" ) ) { + Hide(); + + if ( !num_anims ) { + blendFrames = 0; + } + } else if ( spawnArgs.GetString( "start_anim", "", animname ) ) { + anim2 = animator.GetAnim( animname ); + if ( !anim2 ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, 0 ); + } else if ( anim ) { + // init joints to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + + if ( !num_anims ) { + blendFrames = 0; + } + } + + spawnArgs.GetFloat( "wait", "-1", wait ); + + if ( wait >= 0 ) { + PostEventSec( &EV_Activate, wait, this ); + } +} + +/* +=============== +idAnimated::LoadAF +=============== +*/ +bool idAnimated::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName ); +} + +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAnimated::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = renderEntity.axis; + return true; +} + +/* +================ +idAnimated::StartRagdoll +================ +*/ +bool idAnimated::StartRagdoll( void ) { + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // disable any collision model used + GetPhysics()->DisableClip(); + + // start using the AF + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + return true; +} + +/* +===================== +idAnimated::PlayNextAnim +===================== +*/ +void idAnimated::PlayNextAnim( void ) { + const char *animname; + int len; + int cycle; + + if ( current_anim_index >= num_anims ) { + Hide(); + if ( spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } else { + current_anim_index = 0; + } + return; + } + + Show(); + current_anim_index++; + + spawnArgs.GetString( va( "anim%d", current_anim_index ), NULL, &animname ); + if ( !animname ) { + anim = 0; + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, FRAME2MS( blendFrames ) ); + return; + } + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing anim '%s' on %s", animname, name.c_str() ); + return; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animname ); + } + + spawnArgs.GetInt( "cycle", "1", cycle ); + if ( ( current_anim_index == num_anims ) && spawnArgs.GetBool( "loop_last_anim" ) ) { + cycle = -1; + } + + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, current_anim_index ); + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_StartRagdoll +================ +*/ +void idAnimated::Event_StartRagdoll( void ) { + StartRagdoll(); +} + +/* +=============== +idAnimated::Event_AnimDone +================ +*/ +void idAnimated::Event_AnimDone( int animindex ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' end anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + + if ( ( animindex >= num_anims ) && spawnArgs.GetBool( "remove" ) ) { + Hide(); + PostEventMS( &EV_Remove, 0 ); + } else if ( spawnArgs.GetBool( "auto_advance" ) ) { + PlayNextAnim(); + } else { + activated = false; + } + + ActivateTargets( activator.GetEntity() ); +} + +/* +=============== +idAnimated::Event_Activate +================ +*/ +void idAnimated::Event_Activate( idEntity *_activator ) { +// RAVEN BEGIN +// bdube: script object support + if ( scriptThread ) { + CallHandler ( "onActivate" ); + return; + } +// RAVEN END + + if ( num_anims ) { + PlayNextAnim(); + activator = _activator; + return; + } + + if ( activated ) { + // already activated + return; + } + + activated = true; + activator = _activator; + ProcessEvent( &EV_Animated_Start ); +} + +/* +=============== +idAnimated::Event_Start +================ +*/ +void idAnimated::Event_Start( void ) { + int cycle; + int len; + + Show(); + + if ( num_anims ) { + PlayNextAnim(); + return; + } + + if ( anim ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + spawnArgs.GetInt( "cycle", "1", cycle ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, 1 ); + } + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_Footstep +=============== +*/ +void idAnimated::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +===================== +idAnimated::Event_LaunchMissilesUpdate +===================== +*/ +void idAnimated::Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ) { + idVec3 launchPos; + idVec3 targetPos; + idMat3 axis; + idVec3 dir; + idEntity * ent; + idProjectile * projectile; + const idDict * projectileDef; + const char * projectilename; + + projectilename = spawnArgs.GetString( "projectilename" ); + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): 'launchMissiles' called with unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + StartSound( "snd_missile", SND_CHANNEL_WEAPON, 0, false, NULL ); + + animator.GetJointTransform( ( jointHandle_t )launchjoint, gameLocal.time, launchPos, axis ); + launchPos = renderEntity.origin + launchPos * renderEntity.axis; + +// RAVEN BEGIN +// bdube: with no target bone it will just shoot out the direction of the bones orientation + if ( targetjoint != INVALID_JOINT ) { + animator.GetJointTransform( ( jointHandle_t )targetjoint, gameLocal.time, targetPos, axis ); + targetPos = renderEntity.origin + targetPos * renderEntity.axis; + dir = targetPos - launchPos; + } else { + axis *= renderEntity.axis; + dir = axis[0]; + } +// RAVEN END + + dir.Normalize(); + + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "idAnimated '%s' at (%s): in 'launchMissiles' call '%s' is not an idProjectile", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + } + projectile = ( idProjectile * )ent; + projectile->Create( this, launchPos, dir ); + projectile->Launch( launchPos, dir, vec3_origin ); + + if ( numshots > 0 ) { + PostEventMS( &EV_LaunchMissilesUpdate, FRAME2MS( framedelay ), launchjoint, targetjoint, numshots - 1, framedelay ); + } +} + +/* +===================== +idAnimated::Event_LaunchMissiles +===================== +*/ +void idAnimated::Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ) { + const idDict * projectileDef; + jointHandle_t launch; + jointHandle_t target; + + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + launch = animator.GetJointHandle( launchjoint ); + if ( launch == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown launch joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), launchjoint ); + gameLocal.Error( "Unknown joint '%s'", launchjoint ); + } + + target = animator.GetJointHandle( targetjoint ); +// RAVEN BEGIN +// bdube: invalid is ok now, it means shoot out the direction of the bone +// if ( target == INVALID_JOINT ) { +// gameLocal.Warning( "idAnimated '%s' at (%s): unknown target joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), targetjoint ); +// } +// RAVEN END + + spawnArgs.Set( "projectilename", projectilename ); + spawnArgs.Set( "missilesound", sound ); + + CancelEvents( &EV_LaunchMissilesUpdate ); +// RAVEN BEGIN +// nmckenzie: Do this now. No delays. No event loops or updates. Just a straightforward DO THIS. Tacky. + if ( numshots == 1 && framedelay == 0 ){ + Event_LaunchMissilesUpdate ( launch, target, 1, 0 ); + } + else{ + ProcessEvent( &EV_LaunchMissilesUpdate, launch, target, numshots - 1, framedelay ); + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idAnimated::ShouldConstructScriptObjectAtSpawn +===================== +*/ +bool idAnimated::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Think ( void ) { + UpdateScript ( ); + idAFEntity_Gibbable::Think ( ); +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Damage ( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) { + if ( !scriptThread ) { + return; + } + + CallHandler ( "onDamage" ); +} + +/* +===================== +idAnimated::Event_PlayAnim +===================== +*/ +void idAnimated::Event_PlayAnim( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animDoneTime[channel] - gameLocal.time ) ); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_PlayCycle +=============== +*/ +void idAnimated::Event_PlayCycle( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_AnimDone2 +=============== +*/ +void idAnimated::Event_AnimDone2( int channel, int blend ) { + if ( animDoneTime[channel] - FRAME2MS( blend ) <= gameLocal.time ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idAnimated::Event_SetAnimState +=============== +*/ +void idAnimated::Event_SetAnimState ( const char* statename, int blend ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + idealState = statename; + blendFrames = blend; + scriptThread->DoneProcessing(); +} + +/* +================ +idAnimated::UpdateScript +================ +*/ +void idAnimated::UpdateScript( void ) { + int count; + + if ( !scriptThread || !gameLocal.isNewFrame ) { + return; + } + + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + + // If no state has been set then dont execute nothing + if ( !state.Length ( ) || scriptThread->IsWaiting() ) { + return; + } + + // update script state + count = 10; + while( ( scriptThread->Execute() || idealState.Length() ) && count-- ) { + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + } +} + +/* +===================== +idAnimated::CallHandler +===================== +*/ +void idAnimated::CallHandler ( const char* handler ) { + const function_t *func; + func = scriptObject.GetFunction( handler ); + if ( !func ) { + return; + } + + scriptThread->CallFunction( this, func, false ); + scriptThread->Execute ( ); +} + +/* +===================== +idAnimated::SetState +===================== +*/ +void idAnimated::SetState( const char *statename, int frames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + scriptThread->CallFunction( this, func, true ); + state = statename; + blendFrames = frames; + idealState = ""; +} + +// RAVEN END + +/* +=============================================================================== + + idStaticEntity + + Some static entities may be optimized into inline geometry by dmap + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idStaticEntity ) + EVENT( EV_Activate, idStaticEntity::Event_Activate ) +END_CLASS + +/* +=============== +idStaticEntity::idStaticEntity +=============== +*/ +idStaticEntity::idStaticEntity( void ) { + spawnTime = 0; + active = false; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + runGui = false; +} + +/* +=============== +idStaticEntity::Save +=============== +*/ +void idStaticEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnTime ); + savefile->WriteBool( active ); + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + savefile->WriteBool( runGui ); +} + +/* +=============== +idStaticEntity::Restore +=============== +*/ +void idStaticEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnTime ); + savefile->ReadBool( active ); + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); + savefile->ReadBool( runGui ); +} + +/* +=============== +idStaticEntity::Spawn +=============== +*/ +void idStaticEntity::Spawn( void ) { + bool solid; + bool hidden; + bool keepContents; + +// RAVEN BEGIN +// rjohnson: inline models can be solid, since the entities hang around anyway (don't know why it was done that way) + solid = spawnArgs.GetBool( "solid" ); + + // an inline static model will not do anything at all + if ( spawnArgs.GetBool( "inline" ) || gameLocal.world->spawnArgs.GetBool( "inlineAllStatics" ) ) { + Hide(); + if ( solid ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + return; + } +// RAVEN END + + hidden = spawnArgs.GetBool( "hide" ); + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else if ( solid && !hidden ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + GetPhysics()->SetContents( 0 ); + } + + spawnTime = gameLocal.time; + active = false; + + idStr model = spawnArgs.GetString( "model" ); + // FIXME: temp also catch obsolete .ips extension + if ( model.Find( ".ips" ) >= 0 || model.Find( ".prt" ) >= 0 ) { + // we want the parametric particles out of sync with each other + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = gameLocal.random.RandomInt( 32767 ); + } + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // NOTE: this should be used very rarely because it is expensive + runGui = spawnArgs.GetBool( "runGui" ); + if ( runGui ) { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idStaticEntity::ShowEditingDialog +================ +*/ +void idStaticEntity::ShowEditingDialog( void ) { +// RAVEN BEGIN +// bdube: not using +// common->InitTool( EDITOR_PARTICLE, &spawnArgs ); +// RAVEN END +} +/* +================ +idStaticEntity::Think +================ +*/ +void idStaticEntity::Think( void ) { + idEntity::Think(); + if ( thinkFlags & TH_THINK ) { + if ( runGui && renderEntity.gui[0] ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->objectiveSystemOpen ) { + for( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } + } + } + } + if ( fadeEnd > 0 ) { + idVec4 color; + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + } +} + +/* +================ +idStaticEntity::Fade +================ +*/ +void idStaticEntity::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idStaticEntity::Hide +================ +*/ +void idStaticEntity::Hide( void ) { + idEntity::Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idStaticEntity::Show +================ +*/ +void idStaticEntity::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid" ) ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idStaticEntity::Event_Activate +================ +*/ +void idStaticEntity::Event_Activate( idEntity *activator ) { + idStr activateGui; + + spawnTime = gameLocal.time; + active = !active; + + const idKeyValue *kv = spawnArgs.FindKey( "hide" ); + if ( kv ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( spawnTime ); + renderEntity.shaderParms[5] = active; + // this change should be a good thing, it will automatically turn on + // lights etc.. when triggered so that does not have to be specifically done + // with trigger parms.. it MIGHT break things so need to keep an eye on it + renderEntity.shaderParms[ SHADERPARM_MODE ] = ( renderEntity.shaderParms[ SHADERPARM_MODE ] ) ? 0.0f : 1.0f; + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idStaticEntity::WriteToSnapshot +================ +*/ +void idStaticEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + WriteGUIToSnapshot( msg ); + msg.WriteBits( IsHidden()?1:0, 1 ); +} + +/* +================ +idStaticEntity::ReadFromSnapshot +================ +*/ +void idStaticEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bool hidden; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + hidden = msg.ReadBits( 1 ) == 1; + if ( hidden != IsHidden() ) { + if ( hidden ) { + Hide(); + } else { + Show(); + } + } + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + + +CLASS_DECLARATION( idStaticEntity, idFuncEmitter ) +EVENT( EV_Activate, idFuncEmitter::Event_Activate ) +END_CLASS + +/* +=============== +idFuncEmitter::idFuncEmitter +=============== +*/ +idFuncEmitter::idFuncEmitter( void ) { + hidden = false; +} + +/* +=============== +idFuncEmitter::Spawn +=============== +*/ +void idFuncEmitter::Spawn( void ) { + if ( spawnArgs.GetBool( "start_off" ) ) { + hidden = true; + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( 1 ); + UpdateVisuals(); + } else { + hidden = false; + } +} + +/* +=============== +idFuncEmitter::Save +=============== +*/ +void idFuncEmitter::Save( idSaveGame *savefile ) const { + savefile->WriteBool( hidden ); +} + +/* +=============== +idFuncEmitter::Restore +=============== +*/ +void idFuncEmitter::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( hidden ); +} + +/* +================ +idFuncEmitter::Event_Activate +================ +*/ +void idFuncEmitter::Event_Activate( idEntity *activator ) { + if ( hidden || spawnArgs.GetBool( "cycleTrigger" ) ) { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + hidden = false; + } else { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + hidden = true; + } + UpdateVisuals(); +} + +/* +================ +idFuncEmitter::WriteToSnapshot +================ +*/ +void idFuncEmitter::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( hidden ? 1 : 0, 1 ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +/* +================ +idFuncEmitter::ReadFromSnapshot +================ +*/ +void idFuncEmitter::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hidden = msg.ReadBits( 1 ) != 0; + renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + + +const idEventDef EV_Splat( "" ); +CLASS_DECLARATION( idFuncEmitter, idFuncSplat ) +EVENT( EV_Activate, idFuncSplat::Event_Activate ) +EVENT( EV_Splat, idFuncSplat::Event_Splat ) +END_CLASS + +/* +=============== +idFuncSplat::idFuncSplat +=============== +*/ +idFuncSplat::idFuncSplat( void ) { +} + +/* +=============== +idFuncSplat::Spawn +=============== +*/ +void idFuncSplat::Spawn( void ) { +} + +/* +================ +idFuncSplat::Event_Splat +================ +*/ +void idFuncSplat::Event_Splat( void ) { + const char *splat = NULL; + int count = spawnArgs.GetInt( "splatCount", "1" ); + for ( int i = 0; i < count; i++ ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + if ( splat && *splat ) { + float size = spawnArgs.GetFloat( "splatSize", "128" ); + float dist = spawnArgs.GetFloat( "splatDistance", "128" ); + float angle = spawnArgs.GetFloat( "splatAngle", "0" ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[2], dist, true, size, splat, angle ); + } + } + StartSound( "snd_splat", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +idFuncSplat::Event_Activate +================ +*/ +void idFuncSplat::Event_Activate( idEntity *activator ) { + idFuncEmitter::Event_Activate( activator ); + PostEventSec( &EV_Splat, spawnArgs.GetFloat( "splatDelay", "0.25" ) ); + StartSound( "snd_spurt", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +=============================================================================== + + idTextEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTextEntity ) +END_CLASS + +/* +================ +idTextEntity::Spawn +================ +*/ +void idTextEntity::Spawn( void ) { + // these are cached as the are used each frame + text = spawnArgs.GetString( "text" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + bool force = spawnArgs.GetBool( "force" ); + if ( developer.GetBool() || force ) { + BecomeActive(TH_THINK); + } +} + +/* +================ +idTextEntity::Save +================ +*/ +void idTextEntity::Save( idSaveGame *savefile ) const { + savefile->WriteString( text ); + savefile->WriteBool( playerOriented ); +} + +/* +================ +idTextEntity::Restore +================ +*/ +void idTextEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadString( text ); + savefile->ReadBool( playerOriented ); +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::Think( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::ClientPredictionThink( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + + + +/* +=============================================================================== + + idVacuumSeperatorEntity + + Can be triggered to let vacuum through a portal (blown out window) + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumSeparatorEntity ) + EVENT( EV_Activate, idVacuumSeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +idVacuumSeparatorEntity::idVacuumSeparatorEntity +================ +*/ +idVacuumSeparatorEntity::idVacuumSeparatorEntity( void ) { + portal = 0; +} + +/* +================ +idVacuumSeparatorEntity::Save +================ +*/ +void idVacuumSeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +idVacuumSeparatorEntity::Restore +================ +*/ +void idVacuumSeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +idVacuumSeparatorEntity::Spawn +================ +*/ +void idVacuumSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "VacuumSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_AIR | PS_BLOCK_LOCATION ); +} + +/* +================ +idVacuumSeparatorEntity::Event_Activate +================ +*/ +void idVacuumSeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_NONE ); +} + + +/* +=============================================================================== + +idLocationSeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationSeparatorEntity ) +END_CLASS + +/* +================ +idLocationSeparatorEntity::Spawn +================ +*/ +void idLocationSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + qhandle_t portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "LocationSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + } + gameLocal.SetPortalState( portal, PS_BLOCK_LOCATION ); +} + + +/* +=============================================================================== + + idVacuumEntity + + Levels should only have a single vacuum entity. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumEntity ) +END_CLASS + +/* +================ +idVacuumEntity::Spawn +================ +*/ +void idVacuumEntity::Spawn() { + if ( gameLocal.vacuumAreaNum != -1 ) { + gameLocal.Warning( "idVacuumEntity::Spawn: multiple idVacuumEntity in level" ); + return; + } + + idVec3 org = spawnArgs.GetVector( "origin" ); + + gameLocal.vacuumAreaNum = gameRenderWorld->PointInArea( org ); +} + +// RAVEN BEGIN +// abahr: +/* +=============================================================================== + + rvGravitySeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvGravitySeparatorEntity ) + EVENT( EV_Activate, rvGravitySeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +rvGravitySeparatorEntity::idVacuumSeparatorEntity +================ +*/ +rvGravitySeparatorEntity::rvGravitySeparatorEntity( void ) { + portal = 0; +} + +/* +================ +rvGravitySeparatorEntity::Save +================ +*/ +void rvGravitySeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +rvGravitySeparatorEntity::Restore +================ +*/ +void rvGravitySeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +rvGravitySeparatorEntity::Spawn +================ +*/ +void rvGravitySeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "GravitySeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, gameRenderWorld->GetPortalState(portal) | PS_BLOCK_GRAVITY ); +} + +/* +================ +rvGravitySeparatorEntity::Event_Activate +================ +*/ +void rvGravitySeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + + int portalState = gameRenderWorld->GetPortalState( portal ); + gameLocal.SetPortalState( portal, (portalState & PS_BLOCK_GRAVITY) > 0 ? (portalState & ~PS_BLOCK_GRAVITY) : portalState | PS_BLOCK_GRAVITY ); +} + +/* +=============================================================================== + + rvGravityEntity + +=============================================================================== +*/ +ABSTRACT_DECLARATION( idEntity, rvGravityArea ) +END_CLASS + +/* +================ +rvGravityArea::Spawn +================ +*/ +void rvGravityArea::Spawn() { + area = gameRenderWorld->PointInArea( spawnArgs.GetVector("origin") ); + gameLocal.AddUniqueGravityInfo( this ); +} + +/* +================ +rvGravityArea::Save +================ +*/ +void rvGravityArea::Save( idSaveGame *savefile ) const { + savefile->WriteInt( area ); +} + +/* +================ +rvGravityArea::Restore +================ +*/ +void rvGravityArea::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( area ); +} + +/* +================ +rvGravityArea::IsEqualTo +================ +*/ +bool rvGravityArea::IsEqualTo( const rvGravityArea* area ) const { + assert( area ); + return !idStr::Icmp( GetName(), area->GetName() ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea* area ) const { + return IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea& area ) const { + return IsEqualTo( &area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea* area ) const { + return !IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea& area ) const { + return !IsEqualTo( &area ); +} + +/* +=============================================================================== + + rvGravityEntity_Static + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_Static ) +END_CLASS + +/* +================ +rvGravityArea_Static::Spawn +================ +*/ +void rvGravityArea_Static::Spawn() { + gravity = spawnArgs.GetVector( "gravity" );// * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_Static::Save +================ +*/ +void rvGravityArea_Static::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( gravity ); +} + +/* +================ +rvGravityArea_Static::Restore +================ +*/ +void rvGravityArea_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( gravity ); +} + +/* +=============================================================================== + + rvGravityArea_SurfaceNormal + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_SurfaceNormal ) +END_CLASS + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { + trace_t results; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if( !gameLocal.TracePoint( this, results, origin, origin + -axis[2] * 50.0f, clipMask, passEntity ) ) { +// RAVEN END + return gameLocal.GetGravity(); + } + + return -results.c.normal * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const rvClientEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idPhysics* physics ) const { + return GetGravity( physics->GetOrigin(), physics->GetAxis(), physics->GetClipMask(), physics->GetSelf() ); +} +// RAVEN END + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationEntity ) +END_CLASS + +/* +====================== +idLocationEntity::Spawn +====================== +*/ +void idLocationEntity::Spawn() { + idStr realName; + + // this just holds dict information + + // if "location" not already set, use the entity name. + if ( !spawnArgs.GetString( "location", "", realName ) ) { + spawnArgs.Set( "location", name ); + } +} + +/* +====================== +idLocationEntity::GetLocation +====================== +*/ +const char *idLocationEntity::GetLocation( void ) const { + return spawnArgs.GetString( "location" ); +} + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idBeam ) + EVENT( EV_PostSpawn, idBeam::Event_MatchTarget ) + EVENT( EV_Activate, idBeam::Event_Activate ) +END_CLASS + +/* +=============== +idBeam::idBeam +=============== +*/ +idBeam::idBeam() { + target = NULL; + master = NULL; +} + +/* +=============== +idBeam::Save +=============== +*/ +void idBeam::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + master.Save( savefile ); +} + +/* +=============== +idBeam::Restore +=============== +*/ +void idBeam::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + master.Restore( savefile ); +} + +/* +=============== +idBeam::Spawn +=============== +*/ +void idBeam::Spawn( void ) { + float width; + + if ( spawnArgs.GetFloat( "width", "0", width ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = width; + } + + SetModel( "_BEAM" ); + Hide(); + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idBeam::Think +================ +*/ +void idBeam::Think( void ) { + idBeam *masterEnt; + + if ( !IsHidden() && !target.GetEntity() ) { + // hide if our target is removed + Hide(); + } + + RunPhysics(); + + masterEnt = master.GetEntity(); + if ( masterEnt ) { + const idVec3 &origin = GetPhysics()->GetOrigin(); + masterEnt->SetBeamTarget( origin ); + } + Present(); +} + +/* +================ +idBeam::SetMaster +================ +*/ +void idBeam::SetMaster( idBeam *masterbeam ) { + master = masterbeam; +} +/* +================ +idBeam::SetBeamTarget +================ +*/ +void idBeam::SetBeamTarget( const idVec3 &origin ) { + if ( ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] != origin.x ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] != origin.y ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] != origin.z ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = origin.x; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = origin.y; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = origin.z; + UpdateVisuals(); + } +} + +/* +================ +idBeam::Show +================ +*/ +void idBeam::Show( void ) { + idBeam *targetEnt; + + idEntity::Show(); + + targetEnt = target.GetEntity(); + if ( targetEnt ) { + const idVec3 &origin = targetEnt->GetPhysics()->GetOrigin(); + SetBeamTarget( origin ); + } +} + +/* +================ +idBeam::Event_MatchTarget +================ +*/ +void idBeam::Event_MatchTarget( void ) { + int i; + idEntity *targetEnt; + idBeam *targetBeam; + + if ( !targets.Num() ) { + return; + } + + targetBeam = NULL; + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idBeam::Type ) ) { + targetBeam = static_cast( targetEnt ); + break; + } + } + + if ( !targetBeam ) { + gameLocal.Error( "Could not find valid beam target for '%s'", name.c_str() ); + } + + target = targetBeam; + targetBeam->SetMaster( this ); + if ( !spawnArgs.GetBool( "start_off" ) ) { + Show(); + } +} + +/* +================ +idBeam::Event_Activate +================ +*/ +void idBeam::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } +} + +/* +================ +idBeam::WriteToSnapshot +================ +*/ +void idBeam::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_X] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] ); +} + +/* +================ +idBeam::ReadFromSnapshot +================ +*/ +void idBeam::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + renderEntity.shaderParms[SHADERPARM_BEAM_END_X] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLiquid ) + EVENT( EV_Touch, idLiquid::Event_Touch ) +END_CLASS + +/* +================ +idLiquid::Save +================ +*/ +void idLiquid::Save( idSaveGame *savefile ) const { + // Nothing to save + // cnicholson: It says nothing to save here, no mention of *model +} + +/* +================ +idLiquid::Restore +================ +*/ +void idLiquid::Restore( idRestoreGame *savefile ) { + //FIXME: NO! + Spawn(); +} + +/* +================ +idLiquid::Spawn +================ +*/ +void idLiquid::Spawn() { +/* + model = dynamic_cast( renderEntity.hModel ); + if ( !model ) { + gameLocal.Error( "Entity '%s' must have liquid model", name.c_str() ); + } + model->Reset(); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +*/ +} + +/* +================ +idLiquid::Event_Touch +================ +*/ +void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) { + // FIXME: for QuakeCon +/* + idVec3 pos; + + pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f ); +*/ +} + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idShaking ) + EVENT( EV_Activate, idShaking::Event_Activate ) +END_CLASS + +/* +=============== +idShaking::idShaking +=============== +*/ +idShaking::idShaking() { + active = false; +} + + +idShaking::~idShaking() { + SetPhysics( NULL ); +} + +/* +=============== +idShaking::Save +=============== +*/ +void idShaking::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +=============== +idShaking::Restore +=============== +*/ +void idShaking::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +=============== +idShaking::Spawn +=============== +*/ +void idShaking::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + SetPhysics( &physicsObj ); + + active = false; + if ( !spawnArgs.GetBool( "start_off" ) ) { + BeginShaking(); + } +} + +/* +================ +idShaking::BeginShaking +================ +*/ +void idShaking::BeginShaking( void ) { + int phase; + idAngles shake; + int period; + + active = true; + phase = gameLocal.random.RandomInt( 1000 ); + shake = spawnArgs.GetAngles( "shake", "0.5 0.5 0.5" ); + period = spawnArgs.GetFloat( "period", "0.05" ) * 1000; + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase, period * 0.25f, GetPhysics()->GetAxis().ToAngles(), shake, ang_zero ); +} + +/* +================ +idShaking::Event_Activate +================ +*/ +void idShaking::Event_Activate( idEntity *activator ) { + if ( !active ) { + BeginShaking(); + } else { + active = false; + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, physicsObj.GetAxis().ToAngles(), ang_zero, ang_zero ); + } +} + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ +idCVar g_earthquake( "g_earthquake", "1", 0, "controls earthquake effect" ); + +CLASS_DECLARATION( idEntity, idEarthQuake ) + EVENT( EV_Activate, idEarthQuake::Event_Activate ) +END_CLASS + +/* +=============== +idEarthQuake::idEarthQuake +=============== +*/ +idEarthQuake::idEarthQuake() { + wait = 0.0f; + random = 0.0f; + nextTriggerTime = 0; + shakeStopTime = 0; + triggered = false; + playerOriented = false; + disabled = false; + shakeTime = 0.0f; +} + +/* +=============== +idEarthQuake::Save +=============== +*/ +void idEarthQuake::Save( idSaveGame *savefile ) const { + savefile->WriteInt( nextTriggerTime ); + savefile->WriteInt( shakeStopTime ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteBool( triggered ); + savefile->WriteBool( playerOriented ); + savefile->WriteBool( disabled ); + savefile->WriteFloat( shakeTime ); +} + +/* +=============== +idEarthQuake::Restore +=============== +*/ +void idEarthQuake::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( nextTriggerTime ); + savefile->ReadInt( shakeStopTime ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadBool( triggered ); + savefile->ReadBool( playerOriented ); + savefile->ReadBool( disabled ); + savefile->ReadFloat( shakeTime ); + + if ( shakeStopTime > gameLocal.time ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idEarthQuake::Spawn +=============== +*/ +void idEarthQuake::Spawn( void ) { + nextTriggerTime = 0; + shakeStopTime = 0; + wait = spawnArgs.GetFloat( "wait", "15" ); + random = spawnArgs.GetFloat( "random", "5" ); + triggered = spawnArgs.GetBool( "triggered" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + disabled = false; + shakeTime = spawnArgs.GetFloat( "shakeTime", "0" ); + + if ( !triggered ){ + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "wait" ), this ); + } + BecomeInactive( TH_THINK ); +} + +/* +================ +idEarthQuake::Event_Activate +================ +*/ +void idEarthQuake::Event_Activate( idEntity *activator ) { + + if ( nextTriggerTime > gameLocal.time ) { + return; + } + + if ( disabled && activator == this ) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + nextTriggerTime = 0; + + if ( !triggered && activator != this ){ + // if we are not triggered ( i.e. random ), disable or enable + disabled ^= 1; + if (disabled) { + return; + } else { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } + } + + ActivateTargets( activator ); + + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_quake" ) ); + if ( playerOriented ) { + player->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } else { + StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off + AffectNearbyEntities(spawnArgs.GetFloat("affectEntities", "0")); +// RAVEN END + + if ( shakeTime > 0.0f ) { + shakeStopTime = gameLocal.time + SEC2MS( shakeTime ); + BecomeActive( TH_THINK ); + } + + if ( wait > 0.0f ) { + if ( !triggered ) { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } else { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } + } else if ( shakeTime == 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round, earthquake, etc goes off +void idEarthQuake::AffectNearbyEntities(float affectRadius) +{ + if( !g_earthquake.GetBool() ) + { + return; + } + + if (affectRadius == 0) + { + return; + } + idVec3 affectOrigin(GetPhysics()->GetOrigin()); + + if (playerOriented) + { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) + { + return; + } + affectOrigin = player->GetPhysics()->GetOrigin(); + } + + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities = 0; + idEntity *curEnt = NULL; + bool requiresLOS = spawnArgs.GetBool("requiresLOS", "false"); + + // get all entities touching the bounds + numListedEntities = gameLocal.EntitiesWithinRadius(affectOrigin, affectRadius, entityList, MAX_GENTITIES ); + for (int i = 0; i < numListedEntities; i++) + { + // only affect certain entities. first check if they have the "quakeChance" key/value, then send them an earthquake event + curEnt = entityList[i]; + if (curEnt && curEnt->spawnArgs.GetFloat("quakeChance")) + { + curEnt->PostEventMS(&EV_Earthquake, 0, (float)requiresLOS); + } + } +} +// RAVEN END + +/* +=============== +idEarthQuake::Think +================ +*/ +void idEarthQuake::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( gameLocal.time > shakeStopTime ) { + BecomeInactive( TH_THINK ); + if ( wait <= 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } + return; + } + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, gameLocal.GetLocalPlayer()->firstPersonViewOrigin ); + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 256, 1500 * shakeVolume, this, this, 1.0f, true ); + } + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncPortal ) + EVENT( EV_Activate, idFuncPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncPortal::idFuncPortal +=============== +*/ +idFuncPortal::idFuncPortal() { + portal = 0; + state = false; +} + +/* +=============== +idFuncPortal::Save +=============== +*/ +void idFuncPortal::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteBool( state ); +} + +/* +=============== +idFuncPortal::Restore +=============== +*/ +void idFuncPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)portal ); + savefile->ReadBool( state ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); +} + +/* +=============== +idFuncPortal::Spawn +=============== +*/ +void idFuncPortal::Spawn( void ) { + portal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds().Expand( 32.0f ) ); + if ( portal > 0 ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +================ +idFuncPortal::Event_Activate +================ +*/ +void idFuncPortal::Event_Activate( idEntity *activator ) { + if ( portal > 0 ) { + state = !state; + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASPortal ) + EVENT( EV_Activate, idFuncAASPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASPortal::idFuncAASPortal +=============== +*/ +idFuncAASPortal::idFuncAASPortal() { + state = false; +} + +/* +=============== +idFuncAASPortal::Save +=============== +*/ +void idFuncAASPortal::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASPortal::Restore +=============== +*/ +void idFuncAASPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============== +idFuncAASPortal::Spawn +=============== +*/ +void idFuncAASPortal::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +================ +idFuncAASPortal::Event_Activate +================ +*/ +void idFuncAASPortal::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASObstacle ) + EVENT( EV_Activate, idFuncAASObstacle::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASObstacle::idFuncAASObstacle +=============== +*/ +idFuncAASObstacle::idFuncAASObstacle() { + state = false; +} + +/* +=============== +idFuncAASObstacle::Save +=============== +*/ +void idFuncAASObstacle::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASObstacle::Restore +=============== +*/ +void idFuncAASObstacle::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============== +idFuncAASObstacle::Spawn +=============== +*/ +void idFuncAASObstacle::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::Event_Activate +================ +*/ +void idFuncAASObstacle::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::SetState +================ +*/ +void idFuncAASObstacle::SetState ( bool _state ) { + state = _state; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +const idEventDef EV_ResetRadioHud( "", "e" ); +idEntityPtr idFuncRadioChatter::lastRadioChatter = 0; + +CLASS_DECLARATION( idEntity, idFuncRadioChatter ) +EVENT( EV_Activate, idFuncRadioChatter::Event_Activate ) +EVENT( EV_ResetRadioHud, idFuncRadioChatter::Event_ResetRadioHud ) +EVENT( EV_IsActive, idFuncRadioChatter::Event_IsActive ) +END_CLASS + +/* +=============== +idFuncRadioChatter::idFuncRadioChatter +=============== +*/ +idFuncRadioChatter::idFuncRadioChatter() { + time = 0.0f; + isActive = false; +} + +/* +=============== +idFuncRadioChatter::Save +=============== +*/ +void idFuncRadioChatter::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( time ); + savefile->WriteBool( isActive ); // cnicholson: Added unsaved var + lastRadioChatter.Save ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Restore +=============== +*/ +void idFuncRadioChatter::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( time ); + savefile->ReadBool( isActive ); // cnicholson: Added unrestored var + lastRadioChatter.Restore ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Spawn +=============== +*/ +void idFuncRadioChatter::Spawn( void ) { + time = spawnArgs.GetFloat( "time", "5.0" ); +} + +/* +================ +idFuncRadioChatter::Event_Activate +================ +*/ +void idFuncRadioChatter::Event_Activate( idEntity *activator ) { + idPlayer *player; + const char *sound; + const idSoundShader *shader; + int length; + bool isDirectedAtPlayer = false; + + lastRadioChatter = this; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + player = static_cast( activator ); + } else { + player = gameLocal.GetLocalPlayer(); + } + +// RAVEN BEGIN +// bdube: replaced named event with method + player->StartRadioChatter(); +// twhitaker: for isActive() script event + isActive = true; +// RAVEN END + + sound = spawnArgs.GetString( "snd_radiochatter", "" ); + if ( sound && *sound ) { + shader = declManager->FindSound( sound ); + if ( shader && shader->IsVO_ForPlayer()){ + isDirectedAtPlayer = true; + } + player->StartSoundShader( shader, SND_CHANNEL_RADIO, SSF_GLOBAL, false, &length ); + time = MS2SEC( length + 150 ); + } + + if ( player && player->GetHud() ) { + player->GetHud()->SetStateBool( "rhinochatter", isDirectedAtPlayer ); + } + + // we still put the hud up because this is used with no sound on + // certain frame commands when the chatter is triggered + PostEventSec( &EV_ResetRadioHud, time, player ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_ResetRadioHud( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + idPlayer *player = ( activator->IsType( idPlayer::GetClassType() ) ) ? static_cast( activator ) : gameLocal.GetLocalPlayer(); +// RAVEN END +// RAVEN BEGIN +// bdube: replaced named event with method + player->StopRadioChatter(); +// twhitaker: for isActive() script event + isActive = false; +// RAVEN END + ActivateTargets( activator ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_IsActive( void ) { + idThread::ReturnFloat( static_cast< float >( isActive ) ); +} + +/* +================ +idFuncRadioChatter::RepeatLast +================ +*/ +void idFuncRadioChatter::RepeatLast( void ) { + if ( lastRadioChatter ) { + lastRadioChatter->Event_Activate( gameLocal.GetLocalPlayer() ); + } +} + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPhantomObjects ) + EVENT( EV_Activate, idPhantomObjects::Event_Activate ) +END_CLASS + +/* +=============== +idPhantomObjects::idPhantomObjects +=============== +*/ +idPhantomObjects::idPhantomObjects() { + target = NULL; + end_time = 0; + throw_time = 0.0f; + shake_time = 0.0f; + shake_ang.Zero(); + speed = 0.0f; + min_wait = 0; + max_wait = 0; + fl.neverDormant = false; +} + +/* +=============== +idPhantomObjects::Save +=============== +*/ +void idPhantomObjects::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( end_time ); + savefile->WriteFloat( throw_time ); + savefile->WriteFloat( shake_time ); + savefile->WriteVec3( shake_ang ); + savefile->WriteFloat( speed ); + savefile->WriteInt( min_wait ); + savefile->WriteInt( max_wait ); + target.Save( savefile ); + savefile->WriteInt( targetTime.Num() ); + for( i = 0; i < targetTime.Num(); i++ ) { + savefile->WriteInt( targetTime[ i ] ); + } + + for( i = 0; i < lastTargetPos.Num(); i++ ) { + savefile->WriteVec3( lastTargetPos[ i ] ); + } +} + +/* +=============== +idPhantomObjects::Restore +=============== +*/ +void idPhantomObjects::Restore( idRestoreGame *savefile ) { + int num; + int i; + + savefile->ReadInt( end_time ); + savefile->ReadFloat( throw_time ); + savefile->ReadFloat( shake_time ); + savefile->ReadVec3( shake_ang ); + savefile->ReadFloat( speed ); + savefile->ReadInt( min_wait ); + savefile->ReadInt( max_wait ); + target.Restore( savefile ); + + savefile->ReadInt( num ); + targetTime.SetGranularity( 1 ); + targetTime.SetNum( num ); + lastTargetPos.SetGranularity( 1 ); + lastTargetPos.SetNum( num ); + + for( i = 0; i < num; i++ ) { + savefile->ReadInt( targetTime[ i ] ); + } + + if ( savefile->GetBuildNumber() == INITIAL_RELEASE_BUILD_NUMBER ) { + // these weren't saved out in the first release + for( i = 0; i < num; i++ ) { + lastTargetPos[ i ].Zero(); + } + } else { + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( lastTargetPos[ i ] ); + } + } +} + +/* +=============== +idPhantomObjects::Spawn +=============== +*/ +void idPhantomObjects::Spawn( void ) { + throw_time = spawnArgs.GetFloat( "time", "5" ); + speed = spawnArgs.GetFloat( "speed", "1200" ); + shake_time = spawnArgs.GetFloat( "shake_time", "1" ); + throw_time -= shake_time; + if ( throw_time < 0.0f ) { + throw_time = 0.0f; + } + min_wait = SEC2MS( spawnArgs.GetFloat( "min_wait", "1" ) ); + max_wait = SEC2MS( spawnArgs.GetFloat( "max_wait", "3" ) ); + + shake_ang = spawnArgs.GetVector( "shake_ang", "65 65 65" ); + Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idPhantomObjects::Event_Activate +================ +*/ +void idPhantomObjects::Event_Activate( idEntity *activator ) { + int i; + float time; + float frac; + float scale; + + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + return; + } + + RemoveNullTargets(); + if ( !targets.Num() ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !activator || !activator->IsType( idActor::GetClassType() ) ) { +// RAVEN END + target = gameLocal.GetLocalPlayer(); + } else { + target = static_cast( activator ); + } + + end_time = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "end_time", "0" ) ); + + targetTime.SetNum( targets.Num() ); + lastTargetPos.SetNum( targets.Num() ); + + const idVec3 &toPos = target.GetEntity()->GetEyePosition(); + + // calculate the relative times of all the objects + time = 0.0f; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = SEC2MS( time ); + lastTargetPos[ i ] = toPos; + + frac = 1.0f - ( float )i / ( float )targetTime.Num(); + time += ( gameLocal.random.RandomFloat() + 1.0f ) * 0.5f * frac + 0.1f; + } + + // scale up the times to fit within throw_time + scale = throw_time / time; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = gameLocal.time + SEC2MS( shake_time )+ targetTime[ i ] * scale; + } + + BecomeActive( TH_THINK ); +} + +/* +=============== +idPhantomObjects::Think +================ +*/ +void idPhantomObjects::Think( void ) { + int i; + int num; + float time; + idVec3 vel; + idVec3 ang; + idEntity *ent; + idActor *targetEnt; + idPhysics *entPhys; + trace_t tr; + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + if ( !( thinkFlags & TH_THINK ) ) { + BecomeInactive( thinkFlags & ~TH_THINK ); + return; + } + + targetEnt = target.GetEntity(); + if ( !targetEnt || ( targetEnt->health <= 0 ) || ( end_time && ( gameLocal.time > end_time ) ) || gameLocal.inCinematic ) { + BecomeInactive( TH_THINK ); + } + + const idVec3 &toPos = targetEnt->GetEyePosition(); + + num = 0; + for ( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + + if ( ent->fl.hidden ) { + // don't throw hidden objects + continue; + } + + if ( !targetTime[ i ] ) { + // already threw this object + continue; + } + + num++; + + time = MS2SEC( targetTime[ i ] - gameLocal.time ); + if ( time > shake_time ) { + continue; + } + + entPhys = ent->GetPhysics(); + const idVec3 &entOrg = entPhys->GetOrigin(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, entOrg, toPos, MASK_OPAQUE, ent ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == targetEnt ) ) { + lastTargetPos[ i ] = toPos; + } + + if ( time < 0.0f ) { + idAI::PredictTrajectory( entPhys->GetOrigin(), lastTargetPos[ i ], speed, entPhys->GetGravity(), + entPhys->GetClipModel(), entPhys->GetClipMask(), 256.0f, ent, targetEnt, ai_debugTrajectory.GetBool() ? 1 : 0, vel ); + vel *= speed; + entPhys->SetLinearVelocity( vel ); + if ( !end_time ) { + targetTime[ i ] = 0; + } else { + targetTime[ i ] = gameLocal.time + gameLocal.random.RandomInt( max_wait - min_wait ) + min_wait; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + idMoveable *ment = static_cast( ent ); + ment->EnableDamage( true, 2.5f ); + } + } else { + // this is not the right way to set the angular velocity, but the effect is nice, so I'm keeping it. :) + ang.Set( gameLocal.random.CRandomFloat() * shake_ang.x, gameLocal.random.CRandomFloat() * shake_ang.y, gameLocal.random.CRandomFloat() * shake_ang.z ); + ang *= ( 1.0f - time / shake_time ); + entPhys->SetAngularVelocity( ang ); + } + } + + if ( !num ) { + BecomeInactive( TH_THINK ); + } +} + +// RAVEN BEGIN +// bdube: jump points + +/* +=============================================================================== + +rvDebugJumpPoint + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvDebugJumpPoint ) +END_CLASS + +/* +===================== +rvDebugJumpPoint::Spawn +===================== +*/ +void rvDebugJumpPoint::Spawn() { + // Add the jump point + gameDebug.JumpAdd ( name, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles ( ) ); + + // Dont need the entity any more + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvFuncSaveGame ) + EVENT( EV_Activate, rvFuncSaveGame::Event_Activate ) +END_CLASS + +void rvFuncSaveGame::Spawn( void ) { + +} + +void rvFuncSaveGame::Event_Activate( idEntity *activator ) { + // TODO: Xenon - request the player to save the game. + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "saveGame checkPoint" ); +} diff --git a/src/game/Misc.h b/src/game/Misc.h new file mode 100644 index 0000000..b754ee5 --- /dev/null +++ b/src/game/Misc.h @@ -0,0 +1,997 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_MISC_H__ +#define __GAME_MISC_H__ + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +class idSpawnableEntity : public idEntity { +public: + CLASS_PROTOTYPE( idSpawnableEntity ); + + void Spawn( void ); + +private: +}; + +/* +=============================================================================== + + Potential spawning position for players. + The first time a player enters the game, they will be at an 'initial' spot. + Targets will be fired when someone spawns in on them. + + When triggered, will cause player to be teleported to spawn spot. + +=============================================================================== +*/ + +class idPlayerStart : public idEntity { +public: + CLASS_PROTOTYPE( idPlayerStart ); + + enum { + EVENT_TELEPORTPLAYER = idEntity::EVENT_MAXEVENTS, + EVENT_TELEPORTITEM, + EVENT_MAXEVENTS + }; + + idPlayerStart( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + int teleportStage; + + void Event_TeleportEntity( idEntity *activator, bool predicted, idVec3& prevOrigin = vec3_origin ); + void Event_Teleport( idEntity *activator ); + void Teleport( idEntity* other ); + void Event_TeleportStage( idPlayer *player ); + void Event_ResetCamera( idPlayer *player ); + void TeleportPlayer( idPlayer *player ); +}; + + +/* +=============================================================================== + + Non-displayed entity used to activate triggers when it touches them. + Bind to a mover to have the mover activate a trigger as it moves. + When target by triggers, activating the trigger will toggle the + activator on and off. Check "start_off" to have it spawn disabled. + +=============================================================================== +*/ + +class idActivator : public idEntity { +public: + CLASS_PROTOTYPE( idActivator ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + bool stay_on; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + Path entities for monsters to follow. + +=============================================================================== +*/ +class idPathCorner : public idEntity { +public: + CLASS_PROTOTYPE( idPathCorner ); + + void Spawn( void ); + + static void DrawDebugInfo( void ); + + static idPathCorner *RandomPath( const idEntity *source, const idEntity *ignore ); + +private: + void Event_RandomPath( void ); +}; + +// RAVEN BEGIN +// bdube: jump points +/* +=============================================================================== + + Debug Jump Point + +=============================================================================== +*/ + +class rvDebugJumpPoint : public idEntity { +public: + + CLASS_PROTOTYPE( rvDebugJumpPoint ); + + void Spawn(); +}; + +/* +=============================================================================== + + Object that fires targets and changes shader parms when damaged. + +=============================================================================== +*/ + +class idDamagable : public idEntity { +public: + CLASS_PROTOTYPE( idDamagable ); + + idDamagable( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +// RAVEN BEGIN +// abahr: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + int invincibleTime; +// RAVEN BEGIN +// abahr: changed to protected +protected: + int stage; + int stageNext; + const idDict* stageDict; + int stageEndTime; + int stageEndHealth; + int stageEndSpeed; +//jshepard: used to end a stage if a moveable is on the ground (for falling objects) + bool stageEndOnGround; +//jshepard: we want to activate certain objects when triggered-- falling blocks yes, barrels no. + bool activateStageOnTrigger; + + virtual void ExecuteStage ( void ); + void UpdateStage ( void ); + idVec3 GetStageVector ( const char* key, const char* defaultString = "" ) const; + float GetStageFloat ( const char* key, const char* defaultString = "" ) const; + int GetStageInt ( const char* key, const char* defaultString = "" ) const; +// RAVEN END + + int count; + int nextTriggerTime; + + void BecomeBroken( idEntity *activator ); + void Event_BecomeBroken( idEntity *activator ); + void Event_RestoreDamagable( void ); +}; + +/* +=============================================================================== + + Hidden object that explodes when activated + +=============================================================================== +*/ + +class idExplodable : public idEntity { +public: + CLASS_PROTOTYPE( idExplodable ); + + void Spawn( void ); + +private: + void Event_Explode( idEntity *activator ); +}; + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +class idSpring : public idEntity { +public: + CLASS_PROTOTYPE( idSpring ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + idEntityPtr ent1; + idEntityPtr ent2; + int id1; + int id2; + idVec3 p1; + idVec3 p2; + idForce_Spring spring; + + void Event_LinkSpring( void ); +}; + + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +class idForceField : public idEntity { +public: + CLASS_PROTOTYPE( idForceField ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: idDamagable may want to change some things on the fly + void SetExplosion(float force) { forceField.Explosion(force); } +// RAVEN END + + +// RAVEN BEGIN +// bdube: made force field protected +protected: + + idForce_Field forceField; + +private: +// RAVEN END + void Toggle( void ); + + void Event_Activate( idEntity *activator ); + void Event_Toggle( void ); + void Event_FindTargets( void ); +}; + +// RAVEN BEGIN +// bdube: jump pads +/* +=============================================================================== + + rvJumpPad + +=============================================================================== +*/ + +class rvJumpPad : public idForceField { +public: + CLASS_PROTOTYPE( rvJumpPad ); + + rvJumpPad ( void ); + + void Spawn( void ); + void Think( void ); + +private: + + int lastEffectTime; + + void Event_FindTargets( void ); + + enum { + EVENT_JUMPFX = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + idMat3 effectAxis; +}; +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +class idAnimated : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAnimated ); + + idAnimated(); + ~idAnimated(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual bool LoadAF( const char* keyname ); + bool StartRagdoll( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// bdube: script + void Think ( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; +// RAVEN END + +private: + int num_anims; + int current_anim_index; + int anim; + int blendFrames; + jointHandle_t soundJoint; + idEntityPtr activator; + bool activated; + +// RAVEN BEGIN +// bdube: script variables + // script control + idThread * scriptThread; + idStr state; + idStr idealState; + int animDoneTime[ANIM_NumAnimChannels]; + + // Script state management + void UpdateScript( void ); + void SetState( const char *statename, int blend ); + void CallHandler ( const char* handler ); +// RAVEN END + + void PlayNextAnim( void ); + + void Event_Activate( idEntity *activator ); + void Event_Start( void ); + void Event_StartRagdoll( void ); + void Event_AnimDone( int animIndex ); + void Event_Footstep( void ); + void Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ); + void Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetAnimState( const char* state, int blendframes ); + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + void Event_AnimDone2( int channel, int blendFrames ); +// RAVEN END +}; + +/* +=============================================================================== + + idStaticEntity + +=============================================================================== +*/ + +class idStaticEntity : public idEntity { +public: + CLASS_PROTOTYPE( idStaticEntity ); + + idStaticEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void ShowEditingDialog( void ); + virtual void Hide( void ); + virtual void Show( void ); + void Fade( const idVec4 &to, float fadeTime ); + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_Activate( idEntity *activator ); + + int spawnTime; + bool active; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; + bool runGui; +}; + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + +class idFuncEmitter : public idStaticEntity { +public: + CLASS_PROTOTYPE( idFuncEmitter ); + + idFuncEmitter( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Event_Activate( idEntity *activator ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + bool hidden; + +}; + + +// RAVEN BEGIN +// bdube: not using +#if 0 +// RAVEN END + +/* +=============================================================================== + +idFuncSmoke + +=============================================================================== +*/ + +class idFuncSmoke : public idEntity { +public: + CLASS_PROTOTYPE( idFuncSmoke ); + + idFuncSmoke(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void Event_Activate( idEntity *activator ); + +private: + int smokeTime; + const idDeclParticle * smoke; + bool restart; +}; + +// RAVEN BEGIN +// bdube: not using +#endif +// RAVEN END + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + +class idFuncSplat : public idFuncEmitter { +public: + CLASS_PROTOTYPE( idFuncSplat ); + + idFuncSplat( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Splat(); +}; + + +/* +=============================================================================== + +idTextEntity + +=============================================================================== +*/ + +class idTextEntity : public idEntity { +public: + CLASS_PROTOTYPE( idTextEntity ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + +private: + idStr text; + bool playerOriented; +}; + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +class idLocationEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationEntity ); + + void Spawn( void ); + + const char * GetLocation( void ) const; + +private: +}; + +class idLocationSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationSeparatorEntity ); + + void Spawn( void ); + +private: +}; + +class idVacuumSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumSeparatorEntity ); + + idVacuumSeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class idVacuumEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumEntity ); + + void Spawn( void ); + +private: +}; + +// RAVEN BEGIN +// abahr +class rvGravitySeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( rvGravitySeparatorEntity ); + + rvGravitySeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class rvGravityArea : public idEntity { +public: + ABSTRACT_PROTOTYPE( rvGravityArea ); + + void Spawn( void ); + + virtual int GetArea() const { return area; } + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const = 0; + virtual const idVec3 GetGravity( const idEntity* ent ) const = 0; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const = 0; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsEqualTo( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea& area ) const; + bool operator!=( const rvGravityArea* area ) const; + bool operator!=( const rvGravityArea& area ) const; + +protected: + int area; +}; + +class rvGravityArea_Static : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_Static ); + + void Spawn( void ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { return gravity; } + virtual const idVec3 GetGravity( const idEntity* ent ) const { return gravity; } + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const { return gravity; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idVec3 gravity; +}; + +class rvGravityArea_SurfaceNormal : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_SurfaceNormal ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const; + virtual const idVec3 GetGravity( const idEntity* ent ) const; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const; + +protected: + virtual const idVec3 GetGravity( const idPhysics* physics ) const; +}; +// RAVEN END + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +class idBeam : public idEntity { +public: + CLASS_PROTOTYPE( idBeam ); + + idBeam(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + void SetMaster( idBeam *masterbeam ); + void SetBeamTarget( const idVec3 &origin ); + + virtual void Show( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_MatchTarget( void ); + void Event_Activate( idEntity *activator ); + + idEntityPtr target; + idEntityPtr master; +}; + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +class idRenderModelLiquid; + +class idLiquid : public idEntity { +public: + CLASS_PROTOTYPE( idLiquid ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + void Event_Touch( idEntity *other, trace_t *trace ); + + + idRenderModelLiquid *model; +}; + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +class idShaking : public idEntity { +public: + CLASS_PROTOTYPE( idShaking ); + + idShaking(); + ~idShaking(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idPhysics_Parametric physicsObj; + bool active; + + void BeginShaking( void ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ + +class idEarthQuake : public idEntity { +public: + CLASS_PROTOTYPE( idEarthQuake ); + + idEarthQuake(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off +protected: + void AffectNearbyEntities(float affectRadius); +// RAVEN END + +private: + int nextTriggerTime; + int shakeStopTime; + float wait; + float random; + bool triggered; + bool playerOriented; + bool disabled; + float shakeTime; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +class idFuncPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncPortal ); + + idFuncPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + qhandle_t portal; + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +class idFuncAASPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASPortal ); + + idFuncAASPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +class idFuncAASObstacle : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASObstacle ); + + idFuncAASObstacle(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetState ( bool _state ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +class idFuncRadioChatter : public idEntity { +public: + CLASS_PROTOTYPE( idFuncRadioChatter ); + + idFuncRadioChatter(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + static void RepeatLast ( void ); + +private: + static idEntityPtr lastRadioChatter; + float time; + bool isActive; + void Event_Activate( idEntity *activator ); + void Event_ResetRadioHud( idEntity *activator ); + void Event_IsActive( void ); +}; + + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +class idPhantomObjects : public idEntity { +public: + CLASS_PROTOTYPE( idPhantomObjects ); + + idPhantomObjects(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Throw( void ); + void Event_ShakeObject( idEntity *object, int starttime ); + + int end_time; + float throw_time; + float shake_time; + idVec3 shake_ang; + float speed; + int min_wait; + int max_wait; + idEntityPtrtarget; + idList targetTime; + idList lastTargetPos; +}; + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +class rvFuncSaveGame : public idEntity { +public: + CLASS_PROTOTYPE( rvFuncSaveGame ); + + void Spawn( void ); + + void Event_Activate ( idEntity *activator ); + +private: +}; + + +#endif /* !__GAME_MISC_H__ */ diff --git a/src/game/Moveable.cpp b/src/game/Moveable.cpp new file mode 100644 index 0000000..954e799 --- /dev/null +++ b/src/game/Moveable.cpp @@ -0,0 +1,1371 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif + +/* +=============================================================================== + + idMoveable + +=============================================================================== +*/ + +const idEventDef EV_BecomeNonSolid( "becomeNonSolid" ); +const idEventDef EV_SetOwnerFromSpawnArgs( "" ); +const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' ); +const idEventDef EV_CanDamage( "canDamage", "f" ); +const idEventDef EV_SetHealth( "setHealth", "f" ); +const idEventDef EV_RadiusDamage( "", "es" ); + +CLASS_DECLARATION( idDamagable, idMoveable ) + EVENT( EV_Activate, idMoveable::Event_Activate ) + EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid ) + EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs ) + EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest ) + EVENT( EV_CanDamage, idMoveable::Event_CanDamage ) + EVENT( EV_SetHealth, idMoveable::Event_SetHealth ) + EVENT( EV_RadiusDamage, idMoveable::Event_RadiusDamage ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; +static const int BOUNCE_SOUND_DELAY_MIN = 500.0f; +static const int BOUNCE_SOUND_DELAY_MAX = 1200.0f; + +/* +================ +idMoveable::idMoveable +================ +*/ +idMoveable::idMoveable( void ) { + minDamageVelocity = 100.0f; + maxDamageVelocity = 200.0f; + nextCollideFxTime = 0; + initialSpline = NULL; + initialSplineDir = vec3_zero; + unbindOnDeath = false; + allowStep = false; + canDamage = false; + + lastAttacker = NULL; +} + +/* +================ +idMoveable::~idMoveable +================ +*/ +idMoveable::~idMoveable( void ) { + delete initialSpline; + initialSpline = NULL; + SetPhysics( NULL ); +} + +/* +================ +idMoveable::Spawn +================ +*/ +void idMoveable::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + int clipShrink; + idStr clipModelName; + bool setClipModel = false; + idBounds bounds; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName[0] ) { + idVec3 size; + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on moveable '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on moveable '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + } + + if ( setClipModel ) { + trm.SetupBox( bounds ); + } else { + if ( !clipModelName[0] ) { + clipModelName = spawnArgs.GetString ( "model" ); // use the visual model + } + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + } + + // if the model should be shrinked + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + unbindOnDeath = spawnArgs.GetBool( "unbindondeath" ); + + nextCollideFxTime = 0; + + damage = spawnArgs.GetString( "def_damage", "" ); + canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true; + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + + if ( health ) { + if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) { + gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), brokenModel.c_str() ); + } + } + + fl.takedamage = (health > 0 ); + + // setup the physics + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM( this ); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( trm, GetRenderModelMaterial() ), density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + physicsObj.DropToFloor(); + } + + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + BecomeNonSolid(); + } + + allowStep = spawnArgs.GetBool( "allowStep", "1" ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = !physicsObj.IsPushable(); +// RAVEN END + + PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 ); +} + +/* +================ +idMoveable::Save +================ +*/ +void idMoveable::Save( idSaveGame *savefile ) const { + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteString( brokenModel ); + savefile->WriteString( damage ); + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteFloat( minDamageVelocity ); + savefile->WriteFloat( maxDamageVelocity ); + + savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 ); + savefile->WriteVec3( initialSplineDir ); + + savefile->WriteBool( unbindOnDeath ); + savefile->WriteBool( allowStep ); + savefile->WriteBool( canDamage ); + + lastAttacker.Save(savefile); // cnicholson: Added unsaved var +} + +/* +================ +idMoveable::Restore +================ +*/ +void idMoveable::Restore( idRestoreGame *savefile ) { + int initialSplineTime; + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadString( brokenModel ); + savefile->ReadString( damage ); + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadFloat( minDamageVelocity ); + savefile->ReadFloat( maxDamageVelocity ); + + savefile->ReadInt( initialSplineTime ); + if ( initialSplineTime != -1 ) { + InitInitialSpline( initialSplineTime ); + } else { + initialSpline = NULL; + } + + savefile->ReadVec3( initialSplineDir ); + savefile->ReadBool( unbindOnDeath ); + savefile->ReadBool( allowStep ); + savefile->ReadBool( canDamage ); // cnicholson: Added unrestored var + + lastAttacker.Restore(savefile); // cnicholson: Added unrestored var + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMoveable::Hide +================ +*/ +void idMoveable::Hide( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMoveable::Show +================ +*/ +void idMoveable::Show( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Show(); + if ( !spawnArgs.GetBool( "nonsolid" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } +} + +/* +================= +idMoveable::Collide +================= +*/ +bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { + float len, f; + idVec3 dir; + idEntity *ent; + + dir = velocity; + len = dir.NormalizeFast(); + + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( gameLocal.time > nextCollideFxTime ) { + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_collide",collision.c.materialType), collision.c.point, collision.c.normal.ToMat3() ); +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( len <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + SetSoundVolume( f ); + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, false, NULL ); + + nextCollideFxTime = gameLocal.time + BOUNCE_SOUND_DELAY_MIN + gameLocal.random.RandomInt(BOUNCE_SOUND_DELAY_MAX - BOUNCE_SOUND_DELAY_MIN); + } + } + + if ( canDamage && damage.Length() ) { + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent && len > minDamageVelocity ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > maxDamageVelocity ) { + f = 1.0f; + } else if( len <= minDamageVelocity ) { + f = 0.0f; + } else { + f = idMath::Sqrt( len - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) ); + } +// RAVEN END + ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT ); + } + } + + return false; +} + +/* +============ +idMoveable::Damage +============ +*/ +void idMoveable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + idDamagable::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Cache the attacker to ensure credit for a kill is given to the entity that caused the damage + lastAttacker = inflictor; +// jshepard: +// gameLocal.Warning("idMoveable Damaged! Health is %d", health); + +} + +/* +============ +idMoveable::Killed +============ +*/ +void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + +// jshepard: I am dead! +// gameLocal.Warning("idMoveable Killed! Health is %d", health); + +/* + // No more taking damage + fl.takedamage = false; + + // Should the moveable launch around when killed? + int launchTime; + launchTime = SEC2MS ( spawnArgs.GetFloat ( "launch_time", "0" ); + if ( launchTime > 0 ) { + launchOffset = spawnArgs.GetVector ( "launch_offset" ); + launchDir = spawnArgs.GetVector ( " + } + + spawnArgs.GetFloat ( "explode_lapse", + PostEventSec ( &EV_Remove, explodeLapse ); + + // Two part explosion? + explode_impulse + explode_lapse + + if ( unbindOnDeath ) { + Unbind(); + } + + if ( brokenModel != "" ) { + SetModel( brokenModel ); + } + + if ( explode ) { + if ( brokenModel == "" ) { + Hide(); + physicsObj.PutToRest(); + GetPhysics()->SetContents( 0 ); + PostEventMS( &EV_Remove, 0 ); + } + + const char *splash = spawnArgs.GetString( "def_splash_damage", "" ); + if ( splash && *splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), inflictor, inflictor, this, this, splash ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + StopAllEffects ( ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets( this ); + + fl.takedamage = false; +*/ +} + + +/* +================ +idMoveable::ExecuteStage +================ +*/ +void idMoveable::ExecuteStage ( void ) { + // Splash damage? + const char *splash; + if ( stageDict->GetString( "def_splash_damage", "", &splash ) && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, splash ); + } + + idDamagable::ExecuteStage(); +} + +/* +================ +idMoveable::AddDamageEffect +================ +*/ +void idMoveable::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + + // Play an impact effect during this stage? + if ( stageDict ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_impact" ), + collision.c.point, collision.c.normal.ToMat3(), + true, vec3_origin, true ); + } +} + +/* +================ +idMoveable::AllowStep +================ +*/ +bool idMoveable::AllowStep( void ) const { + return allowStep; +} + +/* +================ +idMoveable::BecomeNonSolid +================ +*/ +void idMoveable::BecomeNonSolid( void ) { + // set CONTENTS_RENDERMODEL so bullets still collide with the moveable + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); +} + +/* +================ +idMoveable::EnableDamage +================ +*/ +void idMoveable::EnableDamage( bool enable, float duration ) { + canDamage = enable; + if ( duration ) { + PostEventSec( &EV_CanDamage, duration, ( !enable ) ? 0.0f : 1.0f ); + } +} + +/* +================ +idMoveable::InitInitialSpline +================ +*/ +void idMoveable::InitInitialSpline( int startTime ) { + int initialSplineTime; + + initialSpline = GetSpline(); + initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" ); + + if ( initialSpline != NULL ) { + initialSpline->MakeUniform( initialSplineTime ); + initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) ); + initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime ); + initialSplineDir *= physicsObj.GetAxis().Transpose(); + initialSplineDir.Normalize(); + BecomeActive( TH_THINK ); + } +} + +/* +================ +idMoveable::FollowInitialSplinePath +================ +*/ +bool idMoveable::FollowInitialSplinePath( void ) { + if ( initialSpline != NULL ) { + if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) { + idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time ); + idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * gameLocal.GetMHz(); + physicsObj.SetLinearVelocity( linearVelocity ); + + idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time ); + idVec3 dir = initialSplineDir * physicsObj.GetAxis(); + idVec3 angularVelocity = dir.Cross( splineDir ); + angularVelocity.Normalize(); + angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * gameLocal.GetMHz(); + physicsObj.SetAngularVelocity( angularVelocity ); + return true; + } else { + delete initialSpline; + initialSpline = NULL; + } + } + return false; +} + +/* +================ +idMoveable::Think +================ +*/ +void idMoveable::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // Move to the next stage? + UpdateStage ( ); + + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Think(); +} + +/* +================ +idMoveable::GetRenderModelMaterial +================ +*/ +const idMaterial *idMoveable::GetRenderModelMaterial( void ) const { + if ( renderEntity.customShader ) { + return renderEntity.customShader; + } + if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) { + return renderEntity.hModel->Surface( 0 )->shader; + } + return NULL; +} + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idMoveable::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idMoveable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idMoveable::Event_BecomeNonSolid +================ +*/ +void idMoveable::Event_BecomeNonSolid( void ) { + BecomeNonSolid(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idMoveable::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + if ( !spawnArgs.GetInt( "notPushable" ) ) { + physicsObj.EnableImpact(); + } + + physicsObj.Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } + + InitInitialSpline( gameLocal.time ); + +// RAVEN BEGIN +// jshepard: we should update it's stage on activation, specifically for falling blocks. + UpdateStage(); +// RAVEN END + +} + +/* +================ +idMoveable::Event_SetOwnerFromSpawnArgs +================ +*/ +void idMoveable::Event_SetOwnerFromSpawnArgs( void ) { + idStr owner; + + if ( spawnArgs.GetString( "owner", "", owner ) ) { + ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) ); + } +} + +/* +================ +idMoveable::Event_IsAtRest +================ +*/ +void idMoveable::Event_IsAtRest( void ) { + idThread::ReturnInt( physicsObj.IsAtRest() ); +} + +/* +================ +idMoveable::Event_CanDamage +================ +*/ +void idMoveable::Event_CanDamage( float enable ) { + canDamage = ( enable != 0.0f ); +} + +/* +================ +idMoveable::Event_SetHealth +================ +*/ +void idMoveable::Event_SetHealth( float newHealth ) { + health = newHealth; +} + +/* +================ +idMoveable::Event_RadiusDamage +================ +*/ +void idMoveable::Event_RadiusDamage( idEntity *attacker, const char* splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash ); +} + +/* +=============================================================================== + + idBarrel + +=============================================================================== +*/ + +CLASS_DECLARATION( idMoveable, idBarrel ) +END_CLASS + +/* +================ +idBarrel::idBarrel +================ +*/ +idBarrel::idBarrel() { + radius = 1.0f; + barrelAxis = 0; + lastOrigin.Zero(); + lastAxis.Identity(); + additionalRotation = 0.0f; + additionalAxis.Identity(); + fl.networkSync = true; +} + +/* +================ +idBarrel::Save +================ +*/ +void idBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteInt( barrelAxis ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( lastAxis ); + savefile->WriteFloat( additionalRotation ); + savefile->WriteMat3( additionalAxis ); +} + +/* +================ +idBarrel::Restore +================ +*/ +void idBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadInt( barrelAxis ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( lastAxis ); + savefile->ReadFloat( additionalRotation ); + savefile->ReadMat3( additionalAxis ); +} + +/* +================ +idBarrel::BarrelThink +================ +*/ +void idBarrel::BarrelThink( void ) { + bool wasAtRest, onGround; + float movedDistance, rotatedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + idMat3 curAxis, axis; + + wasAtRest = IsAtRest(); + + // Progress to the next stage? + UpdateStage ( ); + + // run physics + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + onGround = GetPhysics()->HasGroundContacts(); + curOrigin = GetPhysics()->GetOrigin(); + curAxis = GetPhysics()->GetAxis(); + + // if the barrel is on the ground + if ( onGround ) { + gravityNormal = GetPhysics()->GetGravityNormal(); + + dir = curOrigin - lastOrigin; + dir -= gravityNormal * dir * gravityNormal; + movedDistance = dir.LengthSqr(); + + // if the barrel moved and the barrel is not aligned with the gravity direction + if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) { + + // barrel movement since last think frame orthogonal to the barrel axis + movedDistance = idMath::Sqrt( movedDistance ); + dir *= 1.0f / movedDistance; + movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance; + + // get rotation about barrel axis since last think frame + angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3]; + angle = idMath::ACos( angle ); + // distance along cylinder hull + rotatedDistance = angle * radius; + + // if the barrel moved further than it rotated about it's axis + if ( movedDistance > rotatedDistance ) { + + // additional rotation of the visual model to make it look + // like the barrel rolls instead of slides + angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI); + if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) { + additionalRotation += angle; + } else { + additionalRotation -= angle; + } + dir = vec3_origin; + dir[barrelAxis] = 1.0f; + additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3(); + } + } + } + + // save state for next think + lastOrigin = curOrigin; + lastAxis = curAxis; + } + + Present(); +} + +/* +================ +idBarrel::Think +================ +*/ +void idBarrel::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } + + BarrelThink(); +} + +/* +================ +idBarrel::GetPhysicsToVisualTransform +================ +*/ +bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis; + return true; +} + +/* +================ +idBarrel::Spawn +================ +*/ +void idBarrel::Spawn( void ) { + const idBounds &bounds = GetPhysics()->GetBounds(); + + // radius of the barrel cylinder + radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f; + + // always a vertical barrel with cylinder axis parallel to the z-axis + barrelAxis = 2; + + lastOrigin = GetPhysics()->GetOrigin(); + lastAxis = GetPhysics()->GetAxis(); + + additionalRotation = 0.0f; + additionalAxis.Identity(); +} + +/* +================ +idBarrel::ClientPredictionThink +================ +*/ +void idBarrel::ClientPredictionThink( void ) { + Think(); +} + + +/* +=============================================================================== + +idExplodingBarrel + +=============================================================================== +*/ +const idEventDef EV_Respawn( "" ); +const idEventDef EV_TriggerTargets( "" ); + +CLASS_DECLARATION( idBarrel, idExplodingBarrel ) + EVENT( EV_Activate, idExplodingBarrel::Event_Activate ) + EVENT( EV_Respawn, idExplodingBarrel::Event_Respawn ) + EVENT( EV_Explode, idExplodingBarrel::Event_Explode ) + EVENT( EV_TriggerTargets, idExplodingBarrel::Event_TriggerTargets ) +END_CLASS + +/* +================ +idExplodingBarrel::idExplodingBarrel +================ +*/ +idExplodingBarrel::idExplodingBarrel() { + spawnOrigin.Zero(); + spawnAxis.Zero(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + ipsTime = 0; + lightTime = 0; + time = 0.0f; + explodeFinishTime = 0; +} + +/* +================ +idExplodingBarrel::~idExplodingBarrel +================ +*/ +idExplodingBarrel::~idExplodingBarrel() { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + if ( lightHandle >= 0 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + } +} + +/* +================ +idExplodingBarrel::Save +================ +*/ +void idExplodingBarrel::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( state ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( ipsHandle ); + savefile->WriteInt( lightHandle ); + savefile->WriteRenderEntity( ipsEntity ); + savefile->WriteRenderLight( light ); + savefile->WriteInt( ipsTime ); + savefile->WriteInt( lightTime ); + savefile->WriteFloat( time ); + savefile->WriteInt( explodeFinishTime ); +} + +/* +================ +idExplodingBarrel::Restore +================ +*/ +void idExplodingBarrel::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( (int &)state ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( (int &)ipsHandle ); + savefile->ReadInt( (int &)lightHandle ); +// RAVEN BEGIN + savefile->ReadRenderEntity( ipsEntity, &spawnArgs ); +// RAVEN END + savefile->ReadRenderLight( light ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &light ); + } + savefile->ReadInt( ipsTime ); + savefile->ReadInt( lightTime ); + savefile->ReadFloat( time ); + savefile->ReadInt( explodeFinishTime ); + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } + + if ( ipsHandle != -1 ) { + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::Spawn +================ +*/ +void idExplodingBarrel::Spawn( void ) { + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = true; + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + lightTime = 0; + ipsTime = 0; + time = spawnArgs.GetFloat( "time" ); + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + explodeFinishTime = 0; + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Think( void ) { + idBarrel::BarrelThink(); + + // MP: EXPLODED means no effect on client when updating the state + if ( !gameLocal.isClient && state == EXPLODING ) { + if ( gameLocal.time > explodeFinishTime ) { + state = EXPLODED; + } + } + + if ( lightHandle >= 0 ){ + if ( state == BURNING ) { + // ramp the color up over 250 ms + float pct = (gameLocal.time - lightTime) / 250.f; + if ( pct > 1.0f ) { + pct = 1.0f; + } + light.origin = physicsObj.GetAbsBounds().GetCenter(); + light.axis = mat3_identity; + light.shaderParms[ SHADERPARM_RED ] = pct; + light.shaderParms[ SHADERPARM_GREEN ] = pct; + light.shaderParms[ SHADERPARM_BLUE ] = pct; + light.shaderParms[ SHADERPARM_ALPHA ] = pct; + gameRenderWorld->UpdateLightDef( lightHandle, &light ); + } else { + if ( gameLocal.time - lightTime > 250 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } + return; + } + } + + if ( !gameLocal.isClient && state != BURNING && state != EXPLODING ) { + BecomeInactive( TH_THINK ); + return; + } + + if ( ipsHandle >= 0 ){ + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + gameRenderWorld->UpdateEntityDef( ipsHandle, &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::AddIPS +================ +*/ +void idExplodingBarrel::AddIPS( const char *name, bool burn ) { + if ( name && *name ) { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + memset( &ipsEntity, 0, sizeof ( ipsEntity ) ); + const idDeclModelDef *modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name ) ); + if ( modelDef ) { + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + ipsEntity.hModel = modelDef->ModelHandle(); + float rgb = ( burn ) ? 0.0f : 1.0f; + ipsEntity.shaderParms[ SHADERPARM_RED ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_GREEN ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_BLUE ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + ipsEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 ); + if ( !ipsEntity.hModel ) { + ipsEntity.hModel = renderModelManager->FindModel( name ); + } + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + if ( burn ) { + BecomeActive( TH_THINK ); + } + ipsTime = gameLocal.time; + } + } +} + +/* +================ +idExplodingBarrel::AddLight +================ +*/ +void idExplodingBarrel::AddLight( const char *name, bool burn ) { + if ( lightHandle >= 0 ){ + gameRenderWorld->FreeLightDef( lightHandle ); + } + memset( &light, 0, sizeof ( light ) ); + light.axis = mat3_identity; + light.lightRadius.x = spawnArgs.GetFloat( "light_radius" ); + light.lightRadius.y = light.lightRadius.z = light.lightRadius.x; + light.origin = physicsObj.GetOrigin(); + light.origin.z += 128; + light.pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: made sure that barrels are set to no shadows + light.noShadows = true; +// RAVEN END + light.shader = declManager->FindMaterial( name ); + light.shaderParms[ SHADERPARM_RED ] = 2.0f; + light.shaderParms[ SHADERPARM_GREEN ] = 2.0f; + light.shaderParms[ SHADERPARM_BLUE ] = 2.0f; + light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f; + lightHandle = gameRenderWorld->AddLightDef( &light ); + lightTime = gameLocal.time; + BecomeActive( TH_THINK ); +} + +/* +================ +idExplodingBarrel::ExplodingEffects +================ +*/ +void idExplodingBarrel::ExplodingEffects( void ) { + const char *temp; + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + temp = spawnArgs.GetString( "model_damage", "" ); + if ( temp && *temp ) { + SetModel( temp ); + Show(); + } + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + temp = spawnArgs.GetString( "mtr_lightexplode", "" ); + if ( temp && *temp ) { + AddLight( temp, false ); + } +*/ + StopEffect ( "fx_burn" ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); +} + +/* +================ +idExplodingBarrel::Killed +================ +*/ +void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + if ( IsHidden() || state == EXPLODED || state == EXPLODING || state == BURNING ) { + return; + } + + float f = spawnArgs.GetFloat( "burn" ); + if ( f > 0.0f && state == NORMAL ) { + state = BURNING; + PostEventSec( &EV_Explode, f ); + StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_burn"), + vec3_origin, (-GetPhysics()->GetGravityNormal()).ToMat3(), true, vec3_origin, true ); + return; + } else { + state = EXPLODING; + spawnArgs.GetInt( "explode_lapse", "1000", explodeFinishTime ); + explodeFinishTime += gameLocal.time; + } + + // do this before applying radius damage so the ent can trace to any damagable ents nearby + Hide(); + physicsObj.SetContents( 0 ); + + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, inflictor, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, this, splash); + } + + ExplodingEffects( ); + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + //FIXME: need to precache all the debris stuff here and in the projectiles + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" ); + // bool first = true; + while ( kv ) { + const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( debris_args ) { + idEntity *ent; + idVec3 dir; + idDebris *debris; + //if ( first ) { + dir = physicsObj.GetAxis()[1]; + // first = false; + //} else { + dir.x += gameLocal.random.CRandomFloat() * 4.0f; + dir.y += gameLocal.random.CRandomFloat() * 4.0f; + //dir.z = gameLocal.random.RandomFloat() * 8.0f; + //} + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris_args, &ent, false ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f; + debris->UpdateVisuals(); + + } + kv = spawnArgs.MatchPrefix( "def_debris", kv ); + } +*/ +// RAVEN END + + physicsObj.PutToRest(); + CancelEvents( &EV_Explode ); + CancelEvents( &EV_Activate ); + + f = spawnArgs.GetFloat( "respawn" ); + if ( f > 0.0f ) { + PostEventSec( &EV_Respawn, f ); + } else { + PostEventMS( &EV_Remove, 5000 ); + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) { + PostEventMS( &EV_Explode, 400 ); + } else { + idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + + +/* +================ +idExplodingBarrel::Event_TriggerTargets +================ +*/ +void idExplodingBarrel::Event_TriggerTargets() { + ActivateTargets( this ); +} + +/* +================ +idExplodingBarrel::Event_Explode +================ +*/ +void idExplodingBarrel::Event_Explode() { + if ( state == NORMAL || state == BURNING ) { + state = BURNEXPIRED; + Killed( NULL, NULL, 0, vec3_zero, 0 ); + } +} + +/* +================ +idExplodingBarrel::Event_Respawn +================ +*/ +void idExplodingBarrel::Event_Respawn() { + const char *temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = (health > 0); + physicsObj.SetOrigin( spawnOrigin ); + physicsObj.SetAxis( spawnAxis ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.DropToFloor(); + state = NORMAL; + Show(); + UpdateVisuals(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idExplodingBarrel::Event_Activate( idEntity *activator ) { + Killed( activator, activator, 0, vec3_origin, 0 ); +} + + + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idExplodingBarrel::WriteToSnapshot( idBitMsgDelta &msg ) const { + idMoveable::WriteToSnapshot( msg ); + msg.WriteBits( IsHidden(), 1 ); + msg.WriteBits( state, 3 ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idExplodingBarrel::ReadFromSnapshot( const idBitMsgDelta &msg ) { + explode_state_t newState; + + idMoveable::ReadFromSnapshot( msg ); + if ( msg.ReadBits( 1 ) ) { + Hide(); + } else { + Show(); + } + newState = (explode_state_t)msg.ReadBits( 3 ); + if ( newState != state ) { + state = newState; + if ( state == EXPLODING ) { + ExplodingEffects( ); + } + } +} + +// RAVEN END diff --git a/src/game/Moveable.h b/src/game/Moveable.h new file mode 100644 index 0000000..5c6e744 --- /dev/null +++ b/src/game/Moveable.h @@ -0,0 +1,203 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 06/02/2004 + +#ifndef __GAME_MOVEABLE_H__ +#define __GAME_MOVEABLE_H__ + +/* +=============================================================================== + + Entity using rigid body physics. + +=============================================================================== +*/ + +extern const idEventDef EV_BecomeNonSolid; +extern const idEventDef EV_IsAtRest; + +class idMoveable : public idDamagable { +public: + CLASS_PROTOTYPE( idMoveable ); + + idMoveable( void ); + ~idMoveable( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + bool AllowStep( void ) const; + void EnableDamage( bool enable, float duration ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + idPhysics_RigidBody physicsObj; // physics object + idStr brokenModel; // model set when health drops down to or below zero + idStr damage; // if > 0 apply damage to hit entities + int nextCollideFxTime; // next time it is ok to spawn collision fx + float minDamageVelocity; // minimum velocity before moveable applies damage + float maxDamageVelocity; // velocity at which the maximum damage is applied + idCurve_Spline *initialSpline; // initial spline path the moveable follows + idVec3 initialSplineDir; // initial relative direction along the spline path + bool unbindOnDeath; // unbind from master when health drops down to or below zero + bool allowStep; // allow monsters to step on the object + bool canDamage; // only apply damage when this is set + + idEntityPtr lastAttacker; + + virtual void ExecuteStage ( void ); + + const idMaterial * GetRenderModelMaterial( void ) const; + void BecomeNonSolid( void ); + void InitInitialSpline( int startTime ); + bool FollowInitialSplinePath( void ); + + void Event_Activate( idEntity *activator ); + void Event_BecomeNonSolid( void ); + void Event_SetOwnerFromSpawnArgs( void ); + void Event_IsAtRest( void ); + void Event_CanDamage ( float enable ); + void Event_SetHealth ( float newHealth ); + void Event_RadiusDamage( idEntity *attacker, const char* splash ); +}; + + +/* +=============================================================================== + + A barrel using rigid body physics. The barrel has special handling of + the view model orientation to make it look like it rolls instead of slides. + +=============================================================================== +*/ + +class idBarrel : public idMoveable { + +public: + CLASS_PROTOTYPE( idBarrel ); + idBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void BarrelThink( void ); + virtual void Think( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void ClientPredictionThink( void ); + +private: + float radius; // radius of barrel + int barrelAxis; // one of the coordinate axes the barrel cylinder is parallel to + idVec3 lastOrigin; // origin of the barrel the last think frame + idMat3 lastAxis; // axis of the barrel the last think frame + float additionalRotation; // additional rotation of the barrel about it's axis + idMat3 additionalAxis; // additional rotation axis +}; + + +/* +=============================================================================== + + A barrel using rigid body physics and special handling of the view model + orientation to make it look like it rolls instead of slides. The barrel + can burn and explode when damaged. + +=============================================================================== +*/ + +class idExplodingBarrel : public idBarrel { +public: + CLASS_PROTOTYPE( idExplodingBarrel ); + + idExplodingBarrel(); + ~idExplodingBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + typedef enum { + NORMAL = 0, + BURNING, + BURNEXPIRED, + EXPLODING, + EXPLODED + } explode_state_t; + explode_state_t state; + + idVec3 spawnOrigin; + idMat3 spawnAxis; + qhandle_t ipsHandle; + qhandle_t lightHandle; + renderEntity_t ipsEntity; + renderLight_t light; + int ipsTime; + int lightTime; + float time; + + int explodeFinishTime; + + void AddIPS( const char *name, bool burn ); + void AddLight( const char *name , bool burn ); + void ExplodingEffects( void ); + + void Event_Activate( idEntity *activator ); + void Event_Respawn(); + void Event_Explode(); + void Event_TriggerTargets(); +}; + +#endif /* !__GAME_MOVEABLE_H__ */ + +// RAVEN END diff --git a/src/game/Mover.cpp b/src/game/Mover.cpp new file mode 100644 index 0000000..b226f30 --- /dev/null +++ b/src/game/Mover.cpp @@ -0,0 +1,6000 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +// a mover will update any gui entities in it's target list with +// a key/val pair of "mover" "state" from below.. guis can represent +// realtime info like this +// binary only +static const char *guiBinaryMoverStates[] = { + "1", // pos 1 + "2", // pos 2 + "3", // moving 1 to 2 + "4" // moving 2 to 1 +}; + + +/* +=============================================================================== + +idMover + +=============================================================================== +*/ + +const idEventDef EV_FindGuiTargets( "", NULL ); +const idEventDef EV_TeamBlocked( "", "ee" ); +const idEventDef EV_PartBlocked( "", "e" ); +const idEventDef EV_ReachedPos( "", NULL ); +const idEventDef EV_ReachedAng( "", NULL ); +// RAVEN BEGIN +const idEventDef EV_PostRestoreExt( "", "ddddd" ); +// RAVEN END +const idEventDef EV_StopMoving( "stopMoving", NULL ); +const idEventDef EV_StopRotating( "stopRotating", NULL ); +const idEventDef EV_Speed( "speed", "f" ); +const idEventDef EV_Time( "time", "f" ); +const idEventDef EV_AccelTime( "accelTime", "f" ); +const idEventDef EV_DecelTime( "decelTime", "f" ); +const idEventDef EV_MoveTo( "moveTo", "e" ); +const idEventDef EV_MoveToPos( "moveToPos", "v" ); +const idEventDef EV_Move( "move", "ff" ); +const idEventDef EV_MoveAccelerateTo( "accelTo", "ff" ); +const idEventDef EV_MoveDecelerateTo( "decelTo", "ff" ); +const idEventDef EV_RotateDownTo( "rotateDownTo", "df" ); +const idEventDef EV_RotateUpTo( "rotateUpTo", "df" ); +const idEventDef EV_RotateTo( "rotateTo", "v" ); +const idEventDef EV_Rotate( "rotate", "v" ); +const idEventDef EV_RotateOnce( "rotateOnce", "v" ); +const idEventDef EV_Bob( "bob", "ffv" ); +const idEventDef EV_Sway( "sway", "ffv" ); +const idEventDef EV_Mover_OpenPortal( "openPortal" ); +const idEventDef EV_Mover_ClosePortal( "closePortal" ); +const idEventDef EV_AccelSound( "accelSound", "s" ); +const idEventDef EV_DecelSound( "decelSound", "s" ); +// RAVEN BEGIN +// cnicholson: added stop sound support +const idEventDef EV_StoppedSound( "stoppedSound", "s" ); +// RAVEN END +const idEventDef EV_MoveSound( "moveSound", "s" ); +const idEventDef EV_Mover_InitGuiTargets( "", NULL ); +const idEventDef EV_EnableSplineAngles( "enableSplineAngles", NULL ); +const idEventDef EV_DisableSplineAngles( "disableSplineAngles", NULL ); +const idEventDef EV_RemoveInitialSplineAngles( "removeInitialSplineAngles", NULL ); +const idEventDef EV_StartSpline( "startSpline", "e" ); +const idEventDef EV_StopSpline( "stopSpline", NULL ); +const idEventDef EV_IsMoving( "isMoving", NULL, 'd' ); +const idEventDef EV_IsRotating( "isRotating", NULL, 'd' ); +// RAVEN BEGIN +// abahr: +const idEventDef EV_GetSplineEntity( "getSplineEntity", NULL, 'E' ); +const idEventDef EV_MoveAlongVector( "moveAlongVector", "v" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idMover ) + EVENT( EV_FindGuiTargets, idMover::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover::Event_SetCallback ) + EVENT( EV_TeamBlocked, idMover::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover::Event_PartBlocked ) + EVENT( EV_ReachedPos, idMover::Event_UpdateMove ) + EVENT( EV_ReachedAng, idMover::Event_UpdateRotation ) +// RAVEN BEGIN + EVENT( EV_PostRestoreExt, idMover::Event_PostRestoreExt ) +// RAVEN END + EVENT( EV_StopMoving, idMover::Event_StopMoving ) + EVENT( EV_StopRotating, idMover::Event_StopRotating ) + EVENT( EV_Speed, idMover::Event_SetMoveSpeed ) + EVENT( EV_Time, idMover::Event_SetMoveTime ) + EVENT( EV_AccelTime, idMover::Event_SetAccellerationTime ) + EVENT( EV_DecelTime, idMover::Event_SetDecelerationTime ) + EVENT( EV_MoveTo, idMover::Event_MoveTo ) + EVENT( EV_MoveToPos, idMover::Event_MoveToPos ) + EVENT( EV_Move, idMover::Event_MoveDir ) + EVENT( EV_MoveAccelerateTo, idMover::Event_MoveAccelerateTo ) + EVENT( EV_MoveDecelerateTo, idMover::Event_MoveDecelerateTo ) + EVENT( EV_RotateDownTo, idMover::Event_RotateDownTo ) + EVENT( EV_RotateUpTo, idMover::Event_RotateUpTo ) + EVENT( EV_RotateTo, idMover::Event_RotateTo ) + EVENT( EV_Rotate, idMover::Event_Rotate ) + EVENT( EV_RotateOnce, idMover::Event_RotateOnce ) + EVENT( EV_Bob, idMover::Event_Bob ) + EVENT( EV_Sway, idMover::Event_Sway ) + EVENT( EV_Mover_OpenPortal, idMover::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover::Event_ClosePortal ) + EVENT( EV_AccelSound, idMover::Event_SetAccelSound ) + EVENT( EV_DecelSound, idMover::Event_SetDecelSound ) +// RAVEN BEGIN +// cnicholson: added stop sound support + EVENT( EV_StoppedSound, idMover::Event_SetStoppedSound ) +// RAVEN END + EVENT( EV_MoveSound, idMover::Event_SetMoveSound ) + EVENT( EV_Mover_InitGuiTargets, idMover::Event_InitGuiTargets ) + EVENT( EV_EnableSplineAngles, idMover::Event_EnableSplineAngles ) + EVENT( EV_DisableSplineAngles, idMover::Event_DisableSplineAngles ) + EVENT( EV_RemoveInitialSplineAngles, idMover::Event_RemoveInitialSplineAngles ) + EVENT( EV_StartSpline, idMover::Event_StartSpline ) + EVENT( EV_StopSpline, idMover::Event_StopSpline ) + EVENT( EV_Activate, idMover::Event_Activate ) + EVENT( EV_IsMoving, idMover::Event_IsMoving ) + EVENT( EV_IsRotating, idMover::Event_IsRotating ) +// RAVEN BEGIN +// abahr: + EVENT( EV_GetSplineEntity, idMover::Event_GetSplineEntity ) + EVENT( EV_MoveAlongVector, idMover::Event_MoveAlongVector ) +// RAVEN END +END_CLASS + +/* +================ +idMover::idMover +================ +*/ +idMover::idMover( void ) { + memset( &move, 0, sizeof( move ) ); + memset( &rot, 0, sizeof( rot ) ); + move_thread = 0; + rotate_thread = 0; + dest_angles.Zero(); + angle_delta.Zero(); + dest_position.Zero(); + move_delta.Zero(); + move_speed = 0.0f; + move_time = 0; + deceltime = 0; + acceltime = 0; + stopRotation = false; + useSplineAngles = true; + attenuate = false; + useIdleSound = false; + lastCommand = MOVER_NONE; + damage = 0.0f; + areaPortal = 0; + maxAttenuation = 0.0f; + attenuationScale = 0.0f; + lastOrigin.Zero( ); + lastTime = 0; + splineStartTime = 0; + + fl.networkSync = true; +} + + +idMover::~idMover( void ) { + SetPhysics( NULL ); +} + + + +/* +================ +idMover::Save +================ +*/ +void idMover::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteStaticObject( physicsObj ); + + savefile->Write( &move, sizeof( move ) ); + savefile->Write( &rot, sizeof( rot ) ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( rotate_thread ); + + savefile->WriteAngles( dest_angles ); + savefile->WriteAngles( angle_delta ); + savefile->WriteVec3( dest_position ); + savefile->WriteVec3( move_delta ); + + savefile->WriteFloat( move_speed ); + savefile->WriteInt( move_time ); + savefile->WriteInt( deceltime ); + savefile->WriteInt( acceltime ); + savefile->WriteBool( stopRotation ); + savefile->WriteBool( useSplineAngles ); + savefile->WriteInt( lastCommand ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal > 0 ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } + + if ( splineEnt.GetEntity() ) { + idCurve_Spline *spline = physicsObj.GetSpline(); + if (spline) { + + savefile->WriteBool( true ); + splineEnt.Save( savefile ); + savefile->WriteInt( spline->GetTime( 0 ) ); + savefile->WriteInt( spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) ); + savefile->WriteInt( physicsObj.GetSplineAcceleration() ); + savefile->WriteInt( physicsObj.GetSplineDeceleration() ); + savefile->WriteInt( (int)physicsObj.UsingSplineAngles() ); + } else { + savefile->WriteBool( false ); + } + + } else { + savefile->WriteBool( false ); + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->WriteBool( attenuate ); + savefile->WriteFloat( maxAttenuation ); + savefile->WriteFloat( attenuationScale ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteInt( lastTime ); + savefile->WriteBool( useIdleSound ); + splineStateThread.Save( savefile ); + savefile->WriteInt( splineStartTime ); +// RAVEN END +} + +/* +================ +idMover::Restore +================ +*/ +void idMover::Restore( idRestoreGame *savefile ) { + int i, num; + bool hasSpline = false; + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->Read( &move, sizeof( move ) ); + savefile->Read( &rot, sizeof( rot ) ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( rotate_thread ); + + savefile->ReadAngles( dest_angles ); + savefile->ReadAngles( angle_delta ); + savefile->ReadVec3( dest_position ); + savefile->ReadVec3( move_delta ); + + savefile->ReadFloat( move_speed ); + savefile->ReadInt( move_time ); + savefile->ReadInt( deceltime ); + savefile->ReadInt( acceltime ); + savefile->ReadBool( stopRotation ); + savefile->ReadBool( useSplineAngles ); + savefile->ReadInt( (int &)lastCommand ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal > 0 ) { + int portalState = 0; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } + + savefile->ReadBool( hasSpline ); + if ( hasSpline ) { + int starttime; + int totaltime; + int accel; + int decel; + int useAngles; + + splineEnt.Restore( savefile ); + savefile->ReadInt( starttime ); + savefile->ReadInt( totaltime ); + savefile->ReadInt( accel ); + savefile->ReadInt( decel ); + savefile->ReadInt( useAngles ); + +// RAVEN BEGIN + PostEventMS( &EV_PostRestoreExt, 0, starttime, totaltime, accel, decel, useAngles ); +// RAVEN END + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->ReadBool( attenuate ); + savefile->ReadFloat( maxAttenuation ); + savefile->ReadFloat( attenuationScale ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadInt( lastTime ); + savefile->ReadBool( useIdleSound ); + splineStateThread.Restore( savefile, this ); + savefile->ReadInt( splineStartTime ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +/* +================ +idMover::Event_PostRestoreExt +================ +*/ +// RAVEN BEGIN +void idMover::Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ) { +// RAVEN END + idCurve_Spline *spline; + + idEntity *splineEntity = splineEnt.GetEntity(); + if ( !splineEntity ) { + // We should never get this event if splineEnt is invalid + common->Warning( "Invalid spline entity during restore\n" ); + return; + } + + spline = splineEntity->GetSpline(); + + spline->MakeUniform( total ); + spline->ShiftTime( start - spline->GetTime( 0 ) ); + + physicsObj.SetSpline( spline, accel, decel, useSplineAng ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); +} + +/* +================ +idMover::Spawn +================ +*/ +void idMover::Spawn( void ) { + move_thread = 0; + rotate_thread = 0; + stopRotation = false; + lastCommand = MOVER_NONE; + + acceltime = 1000.0f * spawnArgs.GetFloat( "accel_time", "0" ); + deceltime = 1000.0f * spawnArgs.GetFloat( "decel_time", "0" ); + move_time = 1000.0f * spawnArgs.GetFloat( "move_time", "1" ); // safe default value + move_speed = spawnArgs.GetFloat( "move_speed", "0" ); + + spawnArgs.GetFloat( "damage" , "0", damage ); + + dest_position = GetPhysics()->GetOrigin(); + dest_angles = GetPhysics()->GetAxis().ToAngles(); + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !renderEntity.hModel || !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + if( spawnArgs.GetBool("removeGimbleLock") ) { + physicsObj.SetAxisOffset( spawnArgs.GetMatrix("rotation", mat3_identity.ToString()) ); + physicsObj.SetAxis( mat3_identity ); + UpdateVisuals(); + } + +// mekberg: attenuation + attenuate = spawnArgs.GetBool( "attenuate" ); + if( attenuate ) { + maxAttenuation = spawnArgs.GetFloat( "maxAttenuation", "3" ); + attenuationScale = spawnArgs.GetFloat( "attenuationScale", "100" ); + + // Check for bad value/prevent divide by zero + if ( attenuationScale == 0.0f || attenuationScale < 0.0f ) { + attenuationScale = 100; + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } else { + maxAttenuation = 0.0f; + attenuationScale = 1.0f; + } + + if ( !idStr::Icmp( spawnArgs.GetString( "snd_idle", "" ), "" ) ) { + useIdleSound = false; + } else { + StartSound( "snd_idle", SND_CHANNEL_BODY, 0, false, NULL ); + useIdleSound = true; + } + + splineStateThread.SetName( "SplineStateThread" ); + splineStateThread.SetOwner( this ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idMover::Think +================ +*/ +void idMover::Think( void ) { + idVec3 deltaPosition; + float deltaTime; + float speed; + float attenuation; + + if ( physicsObj.GetSpline( ) ) { + splineStateThread.Execute( ); + } + + if ( attenuate ) { + deltaPosition = physicsObj.GetOrigin( ) - lastOrigin; + deltaTime = gameLocal.time - lastTime; + + if ( !deltaTime ) { + deltaTime = 1; + } + + speed = deltaPosition.Length( ) * ( 1000.0f / float( deltaTime ) ); + + if ( speed >= VECTOR_EPSILON ) { + soundShaderParms_t parms = refSound.parms; + + attenuation = 0.8f + 0.2f * ( speed / attenuationScale ); + parms.frequencyShift = idMath::ClampFloat( 0.0f, maxAttenuation, attenuation ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( SND_CHANNEL_BODY, &parms ); + } + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } + + idEntity::Think( ); +} + +// abahr +/* +================ +idMover::GetPhysicsToVisualTransform +================ +*/ +bool idMover::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin.Zero(); + axis = physicsObj.GetAxisOffset(); + return physicsObj.UseAxisOffset(); +} + +/* +================ +idMover::MoveAlongVector +================ +*/ +void idMover::MoveAlongVector( const idVec3& vec ) { + idAngles ang; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.GetLocalAngles( ang ); + dest_position = org + vec * ang.ToMat3(); + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAlongVector +================ +*/ +void idMover::Event_MoveAlongVector( const idVec3& vec ) { + MoveAlongVector( vec ); +} +// RAVEN END + +/* +================ +idMover::Hide +================ +*/ +void idMover::Hide( void ) { + idEntity::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMover::Show +================ +*/ +void idMover::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } + SetPhysics( &physicsObj ); +} + +/* +============ +idMover::Killed +============ +*/ +void idMover::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; + ActivateTargets( this ); +} + + +/* +================ +idMover::Event_SetCallback +================ +*/ +void idMover::Event_SetCallback( void ) { + if ( ( lastCommand == MOVER_ROTATING ) && !rotate_thread ) { + lastCommand = MOVER_NONE; + rotate_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else if ( ( lastCommand == MOVER_MOVING || lastCommand == MOVER_SPLINE ) && !move_thread ) { + lastCommand = MOVER_NONE; + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idMover::VectorForDir +================ +*/ +void idMover::VectorForDir( float angle, idVec3 &vec ) { + idAngles ang; + + switch( ( int )angle ) { + case DIR_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 90; + vec = ang.ToForward(); + break; + + case DIR_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw -= 90; + vec = ang.ToForward(); + break; + + case DIR_FORWARD : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + vec = ang.ToForward(); + break; + + case DIR_BACK : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 180; + vec = ang.ToForward(); + break; + + case DIR_REL_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_REL_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_REL_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + vec *= -1; + break; + + case DIR_REL_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + break; + + case DIR_REL_FORWARD : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward(); + break; + + case DIR_REL_BACK : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward() * -1; + break; + + default: + ang.Set( 0, angle, 0 ); + vec = GetWorldVector( ang.ToForward() ); + break; + } +} + +/* +================ +idMover::FindGuiTargets +================ +*/ +void idMover::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover::SetGuiState( const char *key, const char *val ) const { + gameLocal.Printf( "Setting %s to %s\n", key, val ); + for( int i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover::SetGuiStates +================ +*/ +void idMover::SetGuiStates( const char *state ) { + int i; + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( "movestate", state ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_InitGuiTargets( void ) { + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); +} + +/*********************************************************************** + + Translation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopMoving +================ +*/ +void idMover::Event_StopMoving( void ) { + physicsObj.GetLocalOrigin( dest_position ); + DoneMoving(); +} + +/* +================ +idMover::DoneMoving +================ +*/ +void idMover::DoneMoving( void ) { + + if ( lastCommand != MOVER_SPLINE ) { + // set our final position so that we get rid of any numerical inaccuracy + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + } + + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added sig reached + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + +// RAVEN BEGIN +// mekberg: for idle sound + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } +} + +/* +================ +idMover::UpdateMoveSound +================ +*/ +void idMover::UpdateMoveSound( moveStage_t stage ) { +// RAVEN BEGIN +// mekberg: Idle sound plays instead of snd_move. Don't stop idle sounds. + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case LINEAR_STAGE: { + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case DECELERATION_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + // cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + } +// RAVEN END +} + +/* +================ +idMover::Event_UpdateMove +================ +*/ +void idMover::Event_UpdateMove( void ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + + UpdateMoveSound( move.stage ); + + switch( move.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, move.dir, vec3_origin ); + if ( move.movetime > 0 ) { + move.stage = LINEAR_STAGE; + } else if ( move.deceleration > 0 ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, move.movetime, org, move.dir, vec3_origin ); + if ( move.deceleration ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, move.dir, vec3_origin ); + move.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' move done\n", gameLocal.time, name.c_str() ); + } + DoneMoving(); + break; + } + } +} + +/* +================ +idMover::BeginMove +================ +*/ +void idMover::BeginMove( idThread *thread ) { + moveStage_t stage; + idVec3 org; + float dist; + float acceldist; + int totalacceltime; + int at; + int dt; + + lastCommand = MOVER_MOVING; + move_thread = 0; + + physicsObj.GetLocalOrigin( org ); + + move_delta = dest_position - org; + if ( move_delta.Compare( vec3_zero ) ) { + DoneMoving(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + + // if we're moving at a specific speed, we need to calculate the move time + if ( move_speed ) { + dist = move_delta.Length(); + + totalacceltime = acceltime + deceltime; + + // calculate the distance we'll move during acceleration and deceleration + acceldist = totalacceltime * 0.5f * 0.001f * move_speed; + if ( acceldist >= dist ) { + // going too slow for this distance to move at a constant speed + move_time = totalacceltime; + } else { + // calculate move time taking acceleration into account + move_time = totalacceltime + 1000.0f * ( dist - acceldist ) / move_speed; + } + } + + // scale time up to a whole physics frames + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + move_delta = move_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + move.stage = stage; + move.acceleration = at; + move.movetime = move_time - at - dt; + move.deceleration = dt; + move.dir = move_delta; + + ProcessEvent( &EV_ReachedPos ); +} + +/*********************************************************************** + + Rotation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopRotating +================ +*/ +void idMover::Event_StopRotating( void ) { + physicsObj.GetLocalAngles( dest_angles ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + DoneRotating(); +} + +/* +================ +idMover::DoneRotating +================ +*/ +void idMover::DoneRotating( void ) { + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added reached signal + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( rotate_thread, this ); + rotate_thread = 0; + + StopSound( SND_CHANNEL_BODY, false ); +} + +/* +================ +idMover::UpdateRotationSound +================ +*/ +void idMover::UpdateRotationSound( moveStage_t stage ) { + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case LINEAR_STAGE: { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case DECELERATION_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); +// RAVEN BEGIN +// cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY, 0, false, NULL ); +// RAVEN END + break; + } + } +} + +/* +================ +idMover::Event_UpdateRotation +================ +*/ +void idMover::Event_UpdateRotation( void ) { + idAngles ang; + + physicsObj.GetLocalAngles( ang ); + + UpdateRotationSound( rot.stage ); + + switch( rot.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, rot.acceleration, ang, rot.rot, ang_zero ); + if ( rot.movetime > 0 ) { + rot.stage = LINEAR_STAGE; + } else if ( rot.deceleration > 0 ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + if ( !stopRotation && !rot.deceleration ) { + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } else { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } + + if ( rot.deceleration ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, rot.deceleration, ang, rot.rot, ang_zero ); + rot.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + lastCommand = MOVER_NONE; + if ( stopRotation ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + } else if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_ACCELLINEAR ) { + // keep our angular velocity constant + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, rot.rot, ang_zero ); + } + + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' rotation done\n", gameLocal.time, name.c_str() ); + } + + DoneRotating(); + break; + } + } +} + +/* +================ +idMover::BeginRotation +================ +*/ +void idMover::BeginRotation( idThread *thread, bool stopwhendone ) { + moveStage_t stage; + idAngles ang; + int at; + int dt; + + lastCommand = MOVER_ROTATING; + rotate_thread = 0; + + // rotation always uses move_time so that if a move was started before the rotation, + // the rotation will take the same amount of time as the move. If no move has been + // started and no time is set, the rotation takes 1 second. + if ( !move_time ) { + move_time = 1; + } + + physicsObj.GetLocalAngles( ang ); + angle_delta = dest_angles - ang; + if ( angle_delta == ang_zero ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + DoneRotating(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + angle_delta = angle_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + stopRotation = stopwhendone || ( dt != 0 ); + + rot.stage = stage; + rot.acceleration = at; + rot.movetime = move_time - at - dt; + rot.deceleration = dt; + rot.rot = angle_delta; + + ProcessEvent( &EV_ReachedAng ); +} + + +/*********************************************************************** + + Script callable routines + +***********************************************************************/ + +/* +=============== +idMover::Event_TeamBlocked +=============== +*/ +void idMover::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' stopped due to team member '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockedEntity->name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +=============== +idMover::Event_PartBlocked +=============== +*/ +void idMover::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +================ +idMover::Event_SetMoveSpeed +================ +*/ +void idMover::Event_SetMoveSpeed( float speed ) { + if ( speed <= 0 ) { + gameLocal.Error( "Cannot set speed less than or equal to 0." ); + } + + move_speed = speed; + move_time = 0; // move_time is calculated for each move when move_speed is non-0 +} + +/* +================ +idMover::Event_SetMoveTime +================ +*/ +void idMover::Event_SetMoveTime( float time ) { + if ( time <= 0 ) { + gameLocal.Error( "Cannot set time less than or equal to 0." ); + } + + move_speed = 0; + move_time = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetAccellerationTime +================ +*/ +void idMover::Event_SetAccellerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set acceleration time less than 0." ); + } + + acceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetDecelerationTime +================ +*/ +void idMover::Event_SetDecelerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set deceleration time less than 0." ); + } + + deceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_MoveTo +================ +*/ +void idMover::Event_MoveTo( idEntity *ent ) { + if ( !ent ) { + gameLocal.Warning( "Entity not found" ); +// RAVEN BEGIN +// abahr: added return so the NULL ptr doesn't get used + return; +// RAVEN END + } + + dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::MoveToPos +================ +*/ +void idMover::MoveToPos( const idVec3 &pos ) { + dest_position = GetLocalCoordinates( pos ); + BeginMove( NULL ); +} + +/* +================ +idMover::Event_MoveToPos +================ +*/ +void idMover::Event_MoveToPos( idVec3 &pos ) { + MoveToPos( pos ); +} + +/* +================ +idMover::Event_MoveDir +================ +*/ +void idMover::Event_MoveDir( float angle, float distance ) { + idVec3 dir; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + VectorForDir( angle, dir ); + dest_position = org + dir * distance; + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAccelerateTo +================ +*/ +void idMover::Event_MoveAccelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int at; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: cannot set acceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: not moving." ); + } + + // if already moving faster than the desired speed + if ( v >= speed ) { + return; + } + + at = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = ACCELERATION_STAGE; + move.acceleration = at; + move.movetime = 0; + move.deceleration = 0; + + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, dir * ( speed - v ), dir * v ); +} + +/* +================ +idMover::Event_MoveDecelerateTo +================ +*/ +void idMover::Event_MoveDecelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int dt; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: cannot set deceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: not moving." ); + } + + // if already moving slower than the desired speed + if ( v <= speed ) { + return; + } + + dt = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = DECELERATION_STAGE; + move.acceleration = 0; + move.movetime = 0; + move.deceleration = dt; + + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, dir * ( v - speed ), dir * speed ); +} + +/* +================ +idMover::Event_RotateDownTo +================ +*/ +void idMover::Event_RotateDownTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] > ang[ axis ] ) { + dest_angles[ axis ] -= 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateUpTo +================ +*/ +void idMover::Event_RotateUpTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] < ang[ axis ] ) { + dest_angles[ axis ] += 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateTo +================ +*/ +void idMover::Event_RotateTo( idAngles &angles ) { + dest_angles = angles; + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Rotate +================ +*/ +void idMover::Event_Rotate( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles * ( move_time - ( acceltime + deceltime ) / 2 ) * 0.001f; + + BeginRotation( idThread::CurrentThread(), false ); +} + +/* +================ +idMover::Event_RotateOnce +================ +*/ +void idMover::Event_RotateOnce( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles; + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Bob +================ +*/ +void idMover::Event_Bob( float speed, float phase, idVec3 &depth ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), speed * 1000 * phase, speed * 500, org, depth * 2.0f, vec3_origin ); +} + +/* +================ +idMover::Event_Sway +================ +*/ +void idMover::Event_Sway( float speed, float phase, idAngles &depth ) { + idAngles ang, angSpeed; + float duration; + + physicsObj.GetLocalAngles( ang ); + assert ( speed > 0.0f ); + duration = idMath::Sqrt( depth[0] * depth[0] + depth[1] * depth[1] + depth[2] * depth[2] ) / speed; + angSpeed = depth / ( duration * idMath::SQRT_1OVER2 ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), duration * 1000.0f * phase, duration * 1000.0f, ang, angSpeed, ang_zero ); +} + +/* +================ +idMover::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover::Event_OpenPortal( void ) { + if ( areaPortal ) { + SetPortalState( true ); + } +} + +/* +================ +idMover::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover::Event_ClosePortal( void ) { + if ( areaPortal ) { + SetPortalState( false ); + } +} + +/* +================ +idMover::Event_SetAccelSound +================ +*/ +void idMover::Event_SetAccelSound( const char *sound ) { +// refSound.SetSound( "accel", sound ); +} + +/* +================ +idMover::Event_SetDecelSound +================ +*/ +void idMover::Event_SetDecelSound( const char *sound ) { +// refSound.SetSound( "decel", sound ); +} + +// RAVEN BEGIN +// cnicholson: added stop sound support +/* +================ +idMover::Event_SetStoppedSound +================ +*/ +void idMover::Event_SetStoppedSound( const char *sound ) { +// refSound.SetSound( "stopped", sound ); +} +// RAVEN END + +/* +================ +idMover::Event_SetMoveSound +================ +*/ +void idMover::Event_SetMoveSound( const char *sound ) { +// refSound.SetSound( "move", sound ); +} + +/* +================ +idMover::Event_EnableSplineAngles +================ +*/ +void idMover::Event_EnableSplineAngles( void ) { + useSplineAngles = true; +} + +/* +================ +idMover::Event_DisableSplineAngles +================ +*/ +void idMover::Event_DisableSplineAngles( void ) { + useSplineAngles = false; +} + +/* +================ +idMover::Event_RemoveInitialSplineAngles +================ +*/ +void idMover::Event_RemoveInitialSplineAngles( void ) { + idCurve_Spline *spline; + idAngles ang; + + spline = physicsObj.GetSpline(); + if ( !spline ) { + return; + } + ang = spline->GetCurrentFirstDerivative( 0 ).ToAngles(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, -ang, ang_zero, ang_zero ); +} + +/* +================ +idMover::Event_StartSpline +================ +*/ +void idMover::Event_StartSpline( idEntity *splineEntity ) { + idCurve_Spline *spline; + + if ( !splineEntity ) { + return; + } + + // Needed for savegames + splineEnt = splineEntity; + + spline = splineEntity->GetSpline(); + if ( !spline ) { + return; + } + + lastCommand = MOVER_SPLINE; + move_thread = 0; + +// RAVEN BEGIN +// bdube: movement speed + // Use movement speed? + if ( idMath::Fabs(move_speed) >= VECTOR_EPSILON ) { + // Set a fixed time to determine the length from + spline->MakeUniform( 1000 ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + + // Calculate the move time from the speed + move.movetime = SEC2MS( spline->GetLengthForTime(spline->GetTime(spline->GetNumValues() - 1)) / move_speed ); + move_time = move.movetime; + + spline->SetConstantSpeed( move.movetime ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } else { + spline->MakeUniform( move_time ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } +// RAVEN END + + if ( acceltime + deceltime > move_time ) { + acceltime = move_time / 2; + deceltime = move_time - acceltime; + } + + move.stage = FINISHED_STAGE; + move.acceleration = acceltime; + move.movetime = move_time; + move.deceleration = deceltime; + + physicsObj.SetSpline( spline, move.acceleration, move.deceleration, useSplineAngles ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + +// RAVEN BEGIN +// mekberg: let the splines use a state thread instead of linear extrapolation + if ( acceltime ) { + splineStateThread.SetState( "Accel" ); + } else { + splineStateThread.SetState( "Linear" ); + } + + splineStartTime = gameLocal.time; +// RAVEN END +} + +/* +================ +idMover::Event_StopSpline +================ +*/ +void idMover::Event_StopSpline( void ) { + physicsObj.SetSpline( NULL, 0, 0, useSplineAngles ); + splineEnt = NULL; +} + +/* +================ +idMover::Event_Activate +================ +*/ +void idMover::Event_Activate( idEntity *activator ) { + Show(); + Event_StartSpline( this ); +} + +/* +================ +idMover::Event_IsMoving +================ +*/ +void idMover::Event_IsMoving( void ) { + if ( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::Event_IsRotating +================ +*/ +void idMover::Event_IsRotating( void ) { + if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::WriteToSnapshot +================ +*/ +void idMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( ( ( thinkFlags & TH_PHYSICS ) != 0 ), 1 ); + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( move.stage, 3 ); + msg.WriteBits( rot.stage, 3 ); + WriteBindToSnapshot( msg ); + WriteGUIToSnapshot( msg ); +} + +/* +================ +idMover::ReadFromSnapshot +================ +*/ +void idMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moveStage_t oldMoveStage = move.stage; + moveStage_t oldRotStage = rot.stage; + + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + if ( !proto69 ) { + // sync down the TH_PHYSICS flag for movers, now that we skip ClientPredictionThink when thinkFlags == 0 and no longer force TH_PHYSICS on + // movers still have prediction issues though, they predict a stop and clear TH_PHYSICS too early + bool physics_on = ( msg.ReadBits( 1 ) != 0 ); + if ( physics_on ) { + thinkFlags |= TH_PHYSICS; + } else { + thinkFlags &= ~TH_PHYSICS; + } + } + + physicsObj.ReadFromSnapshot( msg ); + move.stage = (moveStage_t) msg.ReadBits( 3 ); + rot.stage = (moveStage_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( move.stage != oldMoveStage ) { + UpdateMoveSound( oldMoveStage ); + } + if ( rot.stage != oldRotStage ) { + UpdateRotationSound( oldRotStage ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover::SetPortalState +================ +*/ +void idMover::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +// RAVEN BEGIN +// abahr: +void idMover::Event_GetSplineEntity() { + idThread::ReturnEntity( splineEnt.GetEntity() ); +} + +CLASS_STATES_DECLARATION( idMover ) + STATE( "Accel", idMover::State_Accel ) + STATE( "Linear", idMover::State_Linear ) + STATE( "Decel", idMover::State_Decel ) +END_CLASS_STATES + +// mekberg: spline states +/* +================ +idMover::State_Accel +================ +*/ +stateResult_t idMover::State_Accel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + acceltime ) { + splineStateThread.SetState( "Linear" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Linear +================ +*/ +stateResult_t idMover::State_Linear( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time - deceltime ) { + if ( deceltime ) { + splineStateThread.SetState( "Decel" ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Decel +================ +*/ +stateResult_t idMover::State_Decel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +// RAVEN END + +/* +=============================================================================== + + idSplinePath, holds a spline path to be used by an idMover + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger +const idEventDef EV_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idSplinePath ) +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger + EVENT( EV_Activate, idSplinePath::Event_Toggle ) + EVENT( EV_IsActive, idSplinePath::Event_IsActive ) +// RAVEN END +END_CLASS + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::idSplinePath() { + sampledTimes = NULL; + numSamples = 0; +} + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::~idSplinePath() { + if ( sampledTimes ) { + delete [] sampledTimes; + sampledTimes = NULL; + } +} + +/* +================ +idSplinePath::Spawn +================ +*/ +void idSplinePath::Spawn( void ) { +// RAVEN BEGIN +// abahr: + SetActive( spawnArgs.GetBool("start_active", "1") ); + SampleSpline ( ); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSplinePath::FindTargets +================ +*/ +void idSplinePath::FindTargets() { + idEntity::FindTargets(); + + gameLocal.GetTargets( spawnArgs, backwardPathTargets, "target_reverse" ); + + // This alows us to seperate forward targets from backward targets + for( int ix = backwardPathTargets.Num() - 1; ix >= 0; --ix ) { + targets.Remove( backwardPathTargets[ix] ); + } +} + +/* +================ +idSplinePath::SortTargets +================ +*/ +int rvSortByActiveState( const void* a, const void* b ) { + idEntityPtr splineA; + idEntityPtr splineB; + + splineA = *(idEntityPtr*)a; + splineB = *(idEntityPtr*)b; + + return splineB->IsActive() - splineA->IsActive(); +} +int idSplinePath::SortTargets( idList< idEntityPtr >& list ) { + int numActive = 0; + idSplinePath* target = NULL; + + RemoveNullTargets(); + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByActiveState ); + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + target = static_cast( list[ix].GetEntity() ); + if( target->IsActive() ) { + numActive++; + } + } + + return numActive; +} + +int idSplinePath::SortTargets() { + return SortTargets( targets ); +} + +int idSplinePath::SortBackwardsTargets() { + return SortTargets( backwardPathTargets ); +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( idList< idEntityPtr >& list ) { + int i; + + for( i = list.Num() - 1; i >= 0; i-- ) { + if ( !list[ i ].GetEntity() ) { + list.RemoveIndex( i ); + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < list.Num(); i++ ) { + ent = list[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( void ) { + RemoveNullTargets( targets ); + RemoveNullTargets( backwardPathTargets ); +} + +/* +============================== +idSplinePath::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idSplinePath::ActivateTargets( idEntity *activator ) const { + ActivateTargets( activator, targets ); + ActivateTargets( activator, backwardPathTargets ); +} + +/* +================ +idSplinePath::Save +================ +*/ +void idSplinePath::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); +} + +/* +================ +idSplinePath::Restore +================ +*/ +void idSplinePath::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); +} + +/* +================ +idSplinePath::Event_IsActive +================ +*/ +void idSplinePath::Event_IsActive() { + idThread::ReturnInt( IsActive() ); +} + +/* +================ +idSplinePath::SampleSpline +================ +*/ +void idSplinePath::SampleSpline ( void ) { + int i; + float splineLength; + idCurve_Spline* tempSpline = GetSpline ( ); + splineLength = tempSpline->GetLengthForTime( tempSpline->GetTime(tempSpline->GetNumValues() - 1) ); + + if ( splineLength > SPLINE_SAMPLE_RATE ) { + numSamples = int( splineLength / SPLINE_SAMPLE_RATE ); + sampledTimes = new float[ numSamples ]; + float stepSize = splineLength / ( numSamples - 1 ); + + for ( i = 0; i < numSamples; i++ ) { + float time = float( i * stepSize ); + if ( time >= splineLength ) { + time = splineLength; + } + sampledTimes[ i ] = tempSpline->GetTimeForLength ( time, 0.01f ); + } + } + SAFE_DELETE_PTR( tempSpline ); +} + +/* +================ +idSplinePath::GetSampledTime +================ +*/ +float idSplinePath::GetSampledTime ( float distance ) const { + if ( sampledTimes && distance >= 0.0f ) { + int lowIndex, highIndex; + float lerp = distance / SPLINE_SAMPLE_RATE; + int actualLowIndex = int( lerp ); + lowIndex = idMath::ClampInt ( 0, numSamples - 2, actualLowIndex ); + highIndex = lowIndex + 1; + lerp = lerp - idMath::Floor ( lerp ); + return ( actualLowIndex != lowIndex ) ? sampledTimes[ highIndex ] : idMath::Lerp( sampledTimes[ lowIndex ], sampledTimes[ highIndex ], lerp ); + } + return -1.0f; +} + +// RAVEN END + + +/* +=============================================================================== + +idElevator + +=============================================================================== +*/ +const idEventDef EV_PostArrival( "postArrival", NULL ); +const idEventDef EV_GotoFloor( "gotoFloor", "d" ); +const idEventDef EV_UpdateFloorInfo ( "updateFloorInfo", NULL ); + +CLASS_DECLARATION( idMover, idElevator ) + EVENT( EV_Activate, idElevator::Event_Activate ) + EVENT( EV_TeamBlocked, idElevator::Event_TeamBlocked ) + EVENT( EV_PostArrival, idElevator::Event_PostFloorArrival ) + EVENT( EV_GotoFloor, idElevator::Event_GotoFloor ) + EVENT( EV_Touch, idElevator::Event_Touch ) + EVENT( EV_UpdateFloorInfo, idElevator::Event_UpdateFloorInfo ) +END_CLASS + +/* +================ +idElevator::idElevator +================ +*/ +idElevator::idElevator( void ) { + state = INIT; + floorInfo.Clear(); + currentFloor = 0; + pendingFloor = 0; + lastFloor = 0; + controlsDisabled = false; + lastTouchTime = 0; + returnFloor = 0; + returnTime = 0; +} + +/* +================ +idElevator::Save +================ +*/ +void idElevator::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( (int)state ); + + savefile->WriteInt( floorInfo.Num() ); + for ( i = 0; i < floorInfo.Num(); i++ ) { + savefile->WriteVec3( floorInfo[ i ].pos ); + savefile->WriteString( floorInfo[ i ].door ); + savefile->WriteInt( floorInfo[ i ].floor ); + } + + savefile->WriteInt( currentFloor ); + savefile->WriteInt( pendingFloor ); + savefile->WriteInt( lastFloor ); + savefile->WriteBool( controlsDisabled ); +// savefile->WriteBool( waitingForPlayerFollowers ); + savefile->WriteFloat( returnTime ); + savefile->WriteInt( returnFloor ); + savefile->WriteInt( lastTouchTime ); +} + +/* +================ +idElevator::Restore +================ +*/ +void idElevator::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( (int &)state ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + floorInfo_s floor; + + savefile->ReadVec3( floor.pos ); + savefile->ReadString( floor.door ); + savefile->ReadInt( floor.floor ); + + floorInfo.Append( floor ); + } + + savefile->ReadInt( currentFloor ); + savefile->ReadInt( pendingFloor ); + savefile->ReadInt( lastFloor ); + savefile->ReadBool( controlsDisabled ); +// savefile->ReadBool( waitingForPlayerFollowers ); + savefile->ReadFloat( returnTime ); + savefile->ReadInt( returnFloor ); + savefile->ReadInt( lastTouchTime ); +} + +/* +================ +idElevator::Spawn +================ +*/ +void idElevator::Spawn( void ) { + lastFloor = 0; + currentFloor = 0; + pendingFloor = spawnArgs.GetInt( "floor", "1" ); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1]); + + returnTime = spawnArgs.GetFloat( "returnTime" ); + returnFloor = spawnArgs.GetInt( "returnFloor" ); + + UpdateFloorInfo ( ); + + lastTouchTime = 0; + state = INIT; + BecomeActive( TH_THINK | TH_PHYSICS ); + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); + controlsDisabled = false; +// waitingForPlayerFollowers = false; +} + +/* +============== +idElevator::UpdateFloorInfo +=============== +*/ +void idElevator::UpdateFloorInfo ( void ) { + int len1; + idStr str; + + floorInfo.Clear ( ); + + len1 = strlen( "floorPos_" ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "floorPos_", NULL ); + while( kv ) { + str = kv->GetKey().Right( kv->GetKey().Length() - len1 ); + floorInfo_s fi; + fi.floor = atoi( str ); + fi.door = spawnArgs.GetString( va( "floorDoor_%i", fi.floor ) ); + fi.pos = spawnArgs.GetVector( kv->GetKey() ); + floorInfo.Append( fi ); + kv = spawnArgs.MatchPrefix( "floorPos_", kv ); + } +} + +/* +============== +idElevator::Event_UpdateFloorInfo +=============== +*/ +void idElevator::Event_UpdateFloorInfo ( void ) { + UpdateFloorInfo ( ); +} + +/* +============== +idElevator::Event_Touch +=============== +*/ +void idElevator::Event_Touch( idEntity *other, trace_t *trace ) { + + if ( gameLocal.time < lastTouchTime + 2000 ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + lastTouchTime = gameLocal.time; + + if ( thinkFlags & TH_PHYSICS ) { + return; + } + + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, 0.25f, triggerFloor ); + } +} + +/* +================ +idElevator::Think +================ +*/ +void idElevator::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idDoor *doorent = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( state == INIT ) { + state = IDLE; + if ( doorent ) { + doorent->BindTeam( this ); + doorent->spawnArgs.Set( "snd_open", "" ); + doorent->spawnArgs.Set( "snd_close", "" ); + doorent->spawnArgs.Set( "snd_opened", "" ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + idDoor *door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->SetCompanion( doorent ); + } + } + + Event_GotoFloor( pendingFloor ); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + +// RAVEN BEGIN +// bdube: provide floor information to status guis + if ( floorInfo.Num ( ) > 0 ) { + int j; + // Guis on the elevator are considered status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI && renderEntity.gui[j]; j++ ) { + InitStatusGui ( renderEntity.gui[j] ); + } + + // Initialize all the status guis of the elevator + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix( "statusGui" ); kv; kv = spawnArgs.MatchPrefix( "statusGui", kv ) ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( !ent || !ent->GetRenderEntity() ) { + continue; + } + for ( j = 0; j < MAX_RENDERENTITY_GUI && ent->GetRenderEntity()->gui[j]; j++ ) { + InitStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + } +// RAVEN END + + } else if ( state == WAITING_ON_DOORS ) { + if ( doorent ) { + state = doorent->IsOpen() ? WAITING_ON_DOORS : IDLE; + } else { + state = IDLE; + } + if ( state == IDLE ) { + lastFloor = currentFloor; + currentFloor = pendingFloor; + floorInfo_s *fi = GetFloorInfo( currentFloor ); + if ( fi ) { + MoveToPos( fi->pos ); + } + } + } + RunPhysics(); + Present(); +} + +/* +================ +idElevator::Event_Activate +================ +*/ +void idElevator::Event_Activate( idEntity *activator ) { + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + Event_GotoFloor( triggerFloor ); + } +} + +/* +================ +idElevator::Event_TeamBlocked +================ +*/ +void idElevator::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( blockedEntity == this ) { + Event_GotoFloor( lastFloor ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( blockedEntity && blockedEntity->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + // open the inner doors if one is blocked + idDoor *blocked = static_cast( blockedEntity ); + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && blocked->GetMoveMaster() == door->GetMoveMaster() ) { + door->SetBlocked(true); + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } + } +} + + +/* +=============== +idElevator::HandleSingleGuiCommand +=============== +*/ +bool idElevator::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( controlsDisabled ) { + return false; + } + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "changefloor" ) == 0 ) { + if ( src->ReadToken( &token ) ) { +// RAVEN BEGIN +// bdube: up and down floor commands + int newFloor; + if (!token.Cmp("up")) { + newFloor = currentFloor + 1; + } else if (!token.Cmp("down")) { + newFloor = currentFloor - 1; + } else { + newFloor = atoi( token ); + } +// RAVEN END + + if ( newFloor == currentFloor ) { + // open currentFloor and interior doors + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } else { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, newFloor ); + } else { + ProcessEvent( &EV_GotoFloor, newFloor ); + } + } + return true; + } + } + + src->UnreadToken( &token ); + return false; +} + +/* +================ +idElevator::OpenFloorDoor +================ +*/ +void idElevator::OpenFloorDoor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( fi->door ); + if ( door ) { + door->Open(); + } + } +} + +/* +================ +idElevator::OpenInnerDoor +================ +*/ +void idElevator::OpenInnerDoor( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Open(); + } +} + +/* +================ +idElevator::GetFloorInfo +================ +*/ +floorInfo_s *idElevator::GetFloorInfo( int floor ) { + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == floor ) { + return &floorInfo[i]; + } + } + return NULL; +} + +/* +================ +idElevator::Event_GotoFloor +================ +*/ +void idElevator::Event_GotoFloor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + if ( door->IsBlocked() || door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + /* + if ( !gameLocal.isMultiplayer ) { + //FIXME: make sure player is my activator? + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->GetGroundEntity() ) { + //player in air + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + if ( player->GetGroundElevator() == this ) { + idActor* actor = NULL; + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + if ( !actor->IsHidden() && actor->health > 0 && actor->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)(actor))->leader == player && !((idAI*)(actor))->move.fl.disabled && !((idAI*)(actor))->aifl.scripted ) { + if ( actor->GetGroundElevator( this ) != this ) { + waitingForPlayerFollowers = true; + //follower of player is not standing on me, don't move! + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + } + } + } else if ( waitingForPlayerFollowers ) { + //player got off, cancel + waitingForPlayerFollowers = false; + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + UpdateStatusGuis ( ); + return; + } + } + } + waitingForPlayerFollowers = false; + */ + DisableAllDoors(); + CloseAllDoors(); + state = WAITING_ON_DOORS; + pendingFloor = floor; + } +} + +/* +================ +idElevator::BeginMove +================ +*/ +void idElevator::BeginMove( idThread *thread ) { + controlsDisabled = true; + CloseAllDoors(); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[3] : guiBinaryMoverStates[2] ); + +// RAVEN BEGIN +// bdube: replaced with function + SetAASAreaState ( true ); + + idMover::BeginMove( thread ); + + UpdateStatusGuis ( ); +// RAVEN END +} + +/* +================ +idElevator::GetDoor +================ +*/ +idDoor *idElevator::GetDoor( const char *name ) { + idEntity *ent; + idEntity *master; + idDoor *doorEnt; + + doorEnt = NULL; + if ( name && *name ) { + ent = gameLocal.FindEntity( name ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( ent ); + master = doorEnt->GetMoveMaster(); + if ( master != doorEnt ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( master ); + } else { + doorEnt = NULL; + } + } + } + } + + return doorEnt; +} + +/* +================ +idElevator::Event_PostFloorArrival +================ +*/ +void idElevator::Event_PostFloorArrival() { + OpenFloorDoor( currentFloor ); + OpenInnerDoor(); + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + controlsDisabled = false; + if ( returnTime > 0.0f && returnFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, returnTime, returnFloor ); + } +} + +/* +================ +idElevator::DoneMoving +================ +*/ +void idElevator::DoneMoving( void ) { + idMover::DoneMoving(); + EnableProperDoors(); +// RAVEN BEGIN +// bdube: factored into a function + UpdateStatusGuis ( ); +// RAVEN END + if ( spawnArgs.GetInt( "pauseOnFloor", "-1" ) == currentFloor ) { + PostEventSec( &EV_PostArrival, spawnArgs.GetFloat( "pauseTime" ) ); + } else { + Event_PostFloorArrival(); + } + +// RAVEN BEGIN +// kfuller: we want an elevator to fire its targets when it reaches a floor + SetAASAreaState( false ); + + // Floor targets + if ( lastFloor != 0 ) { + const char* floorTarget = spawnArgs.GetString ( va("floorTarget_%d", currentFloor ) ); + if ( floorTarget && *floorTarget ) { + idEntity* ent = gameLocal.FindEntity ( floorTarget ); + if ( ent ) { + ent->ProcessEvent ( &EV_Activate, this ); + } + } + } + + if (spawnArgs.GetInt("fireTargetsAtFloor") == currentFloor) { + ActivateTargets(gameLocal.entities[ENTITYNUM_WORLD]); + spawnArgs.SetInt("fireTargetsAtFloor", -1); + } +// RAVEN END +} + +/* +================ +idElevator::CloseAllDoors +================ +*/ +void idElevator::CloseAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Close(); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Close(); + } + } +} + +/* +================ +idElevator::DisableAllDoors +================ +*/ +void idElevator::DisableAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( false ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( false ); + } + } +} + +/* +================ +idElevator::EnableProperDoors +================ +*/ +void idElevator::EnableProperDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( true ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == currentFloor ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( true ); + break; + } + } + } +} + +// RAVEN BEGIN +// bdube: more advanced status gui control + +/* +================ +idElevator::SetAASAreaState +================ +*/ +void idElevator::SetAASAreaState ( bool enable ) { + idEntity* ents[16]; + int numEnts; + numEnts = gameLocal.EntitiesTouchingBounds ( this, physicsObj.GetAbsBounds(), CONTENTS_AAS_OBSTACLE, ents, 16 ); + for ( numEnts--; numEnts >= 0; numEnts -- ) { + idFuncAASObstacle* obstacle = dynamic_cast(ents[numEnts]); + if ( obstacle ) { + obstacle->SetState ( enable ); + } + } +} + +/* +================ +idElevator::InitStatusGui +================ +*/ +void idElevator::InitStatusGui ( idUserInterface* gui ) { + int floor; + int topFloor; + int bottomFloor; + + topFloor = -1; + bottomFloor = 9999; + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + topFloor = Max( floorInfo[floor].floor, topFloor ); + bottomFloor = Min( floorInfo[floor].floor, bottomFloor ); + } + + gui->SetStateInt ( "topFloor", topFloor ); + gui->SetStateInt ( "bottomFloor", bottomFloor ); + + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + idStr keySrc; + idStr keyDest; + gui->SetStateInt ( va("floorNumber_%d", floor ), floorInfo[floor].floor ); + keySrc = va("floorName_%d", floorInfo[floor].floor ); + keyDest = va("floorName_%d", floor ); + gui->SetStateString ( keyDest, spawnArgs.GetString ( keySrc, va("%d", floorInfo[floor].floor ) ) ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGui +================ +*/ +void idElevator::UpdateStatusGui ( idUserInterface* gui ) { + idStr floorName; + floorName = va("floorName_%d", currentFloor ); + gui->SetStateInt ( "floor", (physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE) ? currentFloor : lastFloor ); + gui->SetStateInt ( "floorNext", currentFloor ); + gui->SetStateString( "floorName", spawnArgs.GetString ( floorName, va("%d", currentFloor ) ) ); + gui->StateChanged( gameLocal.time, true ); + + // mekberg: trigger all status guis if we moved the elevator from another gui. + if ( lastFloor && !( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) ) { + gui->HandleNamedEvent( "triggerGui" ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGuis +================ +*/ +void idElevator::UpdateStatusGuis ( void ) { + int j; + + // Treat the guis on the elevator as status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( renderEntity.gui[ j ] ) { + UpdateStatusGui ( renderEntity.gui[ j ] ); + } + } + + // All entities linked as status guis should get updated + const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + UpdateStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + ent->UpdateVisuals(); + } + kv = spawnArgs.MatchPrefix( "statusGui", kv ); + } +} +// RAVEN END + +/* +=============================================================================== + +idMover_Binary + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" + +=============================================================================== +*/ + +const idEventDef EV_Mover_ReturnToPos1( "", NULL ); +const idEventDef EV_Mover_MatchTeam( "", "dd" ); +const idEventDef EV_Mover_Enable( "enable", NULL ); +const idEventDef EV_Mover_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idMover_Binary ) + EVENT( EV_FindGuiTargets, idMover_Binary::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover_Binary::Event_SetCallback ) + EVENT( EV_Mover_ReturnToPos1, idMover_Binary::Event_ReturnToPos1 ) + EVENT( EV_Activate, idMover_Binary::Event_Use_BinaryMover ) + EVENT( EV_ReachedPos, idMover_Binary::Event_Reached_BinaryMover ) + EVENT( EV_Mover_MatchTeam, idMover_Binary::Event_MatchActivateTeam ) + EVENT( EV_Mover_Enable, idMover_Binary::Event_Enable ) + EVENT( EV_Mover_Disable, idMover_Binary::Event_Disable ) + EVENT( EV_Mover_OpenPortal, idMover_Binary::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover_Binary::Event_ClosePortal ) + EVENT( EV_Mover_InitGuiTargets, idMover_Binary::Event_InitGuiTargets ) +END_CLASS + +/* +================ +idMover_Binary::idMover_Binary() +================ +*/ +idMover_Binary::idMover_Binary() { + pos1.Zero(); + pos2.Zero(); + moverState = MOVER_POS1; + moveMaster = NULL; + activateChain = NULL; + soundPos1 = 0; + sound1to2 = 0; + sound2to1 = 0; + soundPos2 = 0; + soundLoop = 0; + wait = 0.0f; + damage = 0.0f; + duration = 0; + accelTime = 0; + decelTime = 0; + activatedBy = this; + stateStartTime = 0; + team.Clear(); + enabled = false; + deferedOpen = false; + move_thread = 0; + updateStatus = 0; + areaPortal = 0; + blocked = false; + fl.networkSync = true; +} + +/* +================ +idMover_Binary::~idMover_Binary +================ +*/ +idMover_Binary::~idMover_Binary() { + idMover_Binary *mover; + + // if this is the mover master + if ( this == moveMaster ) { + // make the next mover in the chain the move master + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + mover->moveMaster = this->activateChain; + } + } + else { + // remove mover from the activate chain + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + if ( mover->activateChain == this ) { + mover->activateChain = this->activateChain; + break; + } + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMover_Binary::Save +================ +*/ +void idMover_Binary::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteVec3( pos1 ); + savefile->WriteVec3( pos2 ); + savefile->WriteInt( (moverState_t)moverState ); + + savefile->WriteObject( moveMaster ); + savefile->WriteObject( activateChain ); + + savefile->WriteInt( soundPos1 ); + savefile->WriteInt( sound1to2 ); + savefile->WriteInt( sound2to1 ); + savefile->WriteInt( soundPos2 ); + savefile->WriteInt( soundLoop ); + + savefile->WriteFloat( wait ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( duration ); + savefile->WriteInt( accelTime ); + savefile->WriteInt( decelTime ); + + activatedBy.Save( savefile ); + + savefile->WriteInt( stateStartTime ); + savefile->WriteString( team ); + savefile->WriteBool( enabled ); + savefile->WriteBool( deferedOpen ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( updateStatus ); + + savefile->WriteInt( buddies.Num() ); + for ( i = 0; i < buddies.Num(); i++ ) { + savefile->WriteString( buddies[ i ] ); + } + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteBool( blocked ); + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } +} + +/* +================ +idMover_Binary::Restore +================ +*/ +void idMover_Binary::Restore( idRestoreGame *savefile ) { + int i, num, portalState; + idStr temp; + + savefile->ReadVec3( pos1 ); + savefile->ReadVec3( pos2 ); + savefile->ReadInt( (int &)moverState ); + + savefile->ReadObject( reinterpret_cast( moveMaster ) ); + savefile->ReadObject( reinterpret_cast( activateChain ) ); + + savefile->ReadInt( soundPos1 ); + savefile->ReadInt( sound1to2 ); + savefile->ReadInt( sound2to1 ); + savefile->ReadInt( soundPos2 ); + savefile->ReadInt( soundLoop ); + + savefile->ReadFloat( wait ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( duration ); + savefile->ReadInt( accelTime ); + savefile->ReadInt( decelTime ); + + activatedBy.Restore( savefile ); + + savefile->ReadInt( stateStartTime ); + + savefile->ReadString( team ); + savefile->ReadBool( enabled ); + savefile->ReadBool( deferedOpen ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( updateStatus ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( temp ); + buddies.Append( temp ); + } + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadBool( blocked ); + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } +} + +/* +================ +idMover_Binary::Spawn + +Base class for all movers. + +"wait" wait before returning (3 default, -1 = never return) +"speed" movement speed +================ +*/ +void idMover_Binary::Spawn( void ) { + idEntity *ent; + const char *temp; + + move_thread = 0; + enabled = true; + areaPortal = 0; + + activateChain = NULL; + + spawnArgs.GetFloat( "wait", "0", wait ); + + spawnArgs.GetInt( "updateStatus", "0", updateStatus ); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "buddy", NULL ); + while( kv ) { + buddies.Append( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "buddy", kv ); + } + + spawnArgs.GetString( "team", "", &temp ); + team = temp; + + if ( !team.Length() ) { + ent = this; + } else { + // find the first entity spawned on this team (which could be us) + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idMover_Binary::Type ) && !idStr::Icmp( static_cast(ent)->team.c_str(), temp ) ) { + break; + } + } + if ( !ent ) { + ent = this; + } + } + moveMaster = static_cast(ent); + + // create a physics team for the binary mover parts + if ( ent != this ) { + JoinTeam( ent ); + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( moveMaster != this ) { + JoinActivateTeam( moveMaster ); + } + + idBounds soundOrigin; + idMover_Binary *slave; + + soundOrigin.Clear(); + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + soundOrigin += slave->GetPhysics()->GetAbsBounds(); + } + moveMaster->refSound.origin = soundOrigin.GetCenter(); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } +} + +/* +=============== +idMover_Binary::GetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void idMover_Binary::GetMovedir( float angle, idVec3 &movedir ) { + if ( angle == -1 ) { + movedir.Set( 0, 0, 1 ); + } else if ( angle == -2 ) { + movedir.Set( 0, 0, -1 ); + } else { + movedir = idAngles( 0, angle, 0 ).ToForward(); + } +} + +/* +================ +idMover_Binary::Event_SetCallback +================ +*/ +void idMover_Binary::Event_SetCallback( void ) { + if ( ( moverState == MOVER_1TO2 ) || ( moverState == MOVER_2TO1 ) ) { + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idMover_Binary::UpdateMoverSound +=============== +*/ +void idMover_Binary::UpdateMoverSound( moverState_t state ) { + if ( moveMaster == this ) { + switch( state ) { + case MOVER_POS1: + break; + case MOVER_POS2: + break; + case MOVER_1TO2: + StartSound( "snd_open", SND_CHANNEL_ANY, 0, false, NULL ); + break; + case MOVER_2TO1: + StartSound( "snd_close", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } + } +} + +/* +=============== +idMover_Binary::SetMoverState +=============== +*/ +void idMover_Binary::SetMoverState( moverState_t newstate, int time ) { + idVec3 delta; + + moverState = newstate; + move_thread = 0; + + UpdateMoverSound( newstate ); + + stateStartTime = time; + switch( moverState ) { + case MOVER_POS1: { + Signal( SIG_MOVER_POS1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos1, vec3_origin, vec3_origin ); + break; + } + case MOVER_POS2: { + Signal( SIG_MOVER_POS2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos2, vec3_origin, vec3_origin ); + break; + } + case MOVER_1TO2: { + Signal( SIG_MOVER_1TO2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos1, ( pos2 - pos1 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos1, pos2 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + case MOVER_2TO1: { + Signal( SIG_MOVER_2TO1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos2, ( pos1 - pos2 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos2, pos1 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + } +} + +/* +================ +idMover_Binary::MatchActivateTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void idMover_Binary::MatchActivateTeam( moverState_t newstate, int time ) { + idMover_Binary *slave; + deferedOpen = false; + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->SetMoverState( newstate, time ); + } +} + +/* +================ +idMover_Binary::Enable +================ +*/ +void idMover_Binary::Enable( bool b ) { + enabled = b; +} + +/* +================ +idMover_Binary::Event_MatchActivateTeam +================ +*/ +void idMover_Binary::Event_MatchActivateTeam( moverState_t newstate, int time ) { + MatchActivateTeam( newstate, time ); +} + +/* +================ +idMover_Binary::BindTeam + +All entities in a mover team will be bound +================ +*/ +void idMover_Binary::BindTeam( idEntity *bindTo ) { + idMover_Binary *slave; + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->Bind( bindTo, true ); + } +} + +/* +================ +idMover_Binary::JoinActivateTeam + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::JoinActivateTeam( idMover_Binary *master ) { + this->activateChain = master->activateChain; + master->activateChain = this; +} + +/* +================ +idMover_Binary::Event_Enable + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::Event_Enable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_Disable + +Set all entities in a mover team to be disabled +================ +*/ +void idMover_Binary::Event_Disable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover_Binary::Event_OpenPortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( slave->areaPortal ) { + slave->SetPortalState( true ); + } + } +} + +/* +================ +idMover_Binary::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover_Binary::Event_ClosePortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( !slave->IsHidden() ) { + if ( slave->areaPortal ) { + slave->SetPortalState( false ); + } + } + } +} + +/* +================ +idMover_Binary::Event_ReturnToPos1 +================ +*/ +void idMover_Binary::Event_ReturnToPos1( void ) { + MatchActivateTeam( MOVER_2TO1, gameLocal.time ); +} + +/* +================ +idMover_Binary::Event_Reached_BinaryMover +================ +*/ +void idMover_Binary::Event_Reached_BinaryMover( void ) { + + if ( moverState == MOVER_1TO2 ) { + // reached pos2 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + if ( moveMaster == this ) { + StartSound( "snd_opened", SND_CHANNEL_ANY, 0, false, NULL ); + } + + SetMoverState( MOVER_POS2, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 1 ); +// RAVEN END + + if ( enabled && wait >= 0 && !spawnArgs.GetBool( "toggle" ) ) { + // return to pos1 after a delay + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + } + + // fire targets + ActivateTargets( moveMaster->GetActivator() ); + + SetBlocked ( false ); + } else if ( moverState == MOVER_2TO1 ) { + // reached pos1 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + SetMoverState( MOVER_POS1, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 0 ); +// RAVEN END + + // close areaportals + if ( moveMaster == this ) { +// RAVEN BEGIN +// kfuller: added "snd_closed" + StartSound( "snd_closed", SND_CHANNEL_ANY, 0, false, NULL ); +// RAVEN END + ProcessEvent( &EV_Mover_ClosePortal ); + } + + if ( enabled && wait >= 0 && spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, wait, this ); + } + + SetBlocked ( false ); + } else { + gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); + } +} + +/* +================ +idMover_Binary::GotoPosition1 +================ +*/ +void idMover_Binary::GotoPosition1( void ) { + idMover_Binary *slave; + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition1(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + if ( ( moverState == MOVER_POS1 ) || ( moverState == MOVER_2TO1 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS2 ) { + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + } + if ( !spawnArgs.GetBool( "toggle" ) ) { + ProcessEvent( &EV_Mover_ReturnToPos1 ); + } + + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); + // if already at at position 1 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::GotoPosition2 +================ +*/ +void idMover_Binary::GotoPosition2( void ) { + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition2(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + + if ( ( moverState == MOVER_POS2 ) || ( moverState == MOVER_1TO2 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS1 ) { + MatchActivateTeam( MOVER_1TO2, gameLocal.time ); + + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + return; + } + + + // only partway up before reversing + if ( moverState == MOVER_2TO1 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); + // if already at at position 2 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::UpdateBuddies +================ +*/ +void idMover_Binary::UpdateBuddies( int val ) +{ +// RAVEN BEGIN +// jdischler: was using update status but that was never getting set anyway. +// Additionally, shaderparm_mode was never getting set on the mover itself, which creates +// extra work for the designers. + int c = buddies.Num(); + for ( int i = 0; i < c; i++ ) { + idEntity *buddy = gameLocal.FindEntity( buddies[i] ); + if ( buddy ) { + buddy->SetShaderParm( SHADERPARM_MODE, val ); + buddy->UpdateVisuals(); + } + } + // Update the mover itself, too. + SetShaderParm( SHADERPARM_MODE, val ); + UpdateVisuals(); +// RAVEN END +} + +/* +================ +idMover_Binary::SetGuiStates +================ +*/ +void idMover_Binary::SetGuiStates( const char *state ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + + idMover_Binary *mb = activateChain; + while( mb ) { + if ( mb->guiTargets.Num() ) { + mb->SetGuiState( "movestate", state ); + } + mb = mb->activateChain; + } +} + +/* +================ +idMover_Binary::Use_BinaryMover +================ +*/ +void idMover_Binary::Use_BinaryMover( idEntity *activator ) { + // only the master should be used + if ( moveMaster != this ) { + moveMaster->Use_BinaryMover( activator ); + return; + } + + if ( !enabled ) { + return; + } + + activatedBy = activator; + + if ( moverState == MOVER_POS1 && !deferedOpen) { + + float openWait = spawnArgs.GetFloat( "openWait", "0" ); + deferedOpen = true; + PostEventMS( &EV_Mover_MatchTeam, SEC2MS(openWait), MOVER_1TO2, SEC2MS(openWait) + gameLocal.time ); + + // TODO: might want to delay these as well if openWait is present? + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + + // any things we should trigger when the door is first asked to open? + // NOTE: with openWait, it's possible (and desirable as per aweldon's request) + // that this will be called before the door actually opens. This is specifically + // used by the rotate/lock doors...the triggering below starts another entity rotating + // and onWait is used to offset the actual open so the rotating piece has time to work... + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOnOpen" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOnOpen", kv ); + } + + return; + } + + // if all the way up, just delay before coming down + if ( moverState == MOVER_POS2 ) { + idMover_Binary *slave; + + if ( wait == -1 ) { + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + slave->PostEventSec( &EV_Mover_ReturnToPos1, spawnArgs.GetBool( "toggle" ) ? 0 : wait ); + } + return; + } + + // only partway down before reversing + if ( moverState == MOVER_2TO1 ) { + GotoPosition2(); + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + GotoPosition1(); + return; + } +} + +/* +================ +idMover_Binary::Event_Use_BinaryMover +================ +*/ +void idMover_Binary::Event_Use_BinaryMover( idEntity *activator ) { + Use_BinaryMover( activator ); +} + +/* +================ +idMover_Binary::PreBind +================ +*/ +void idMover_Binary::PreBind( void ) { + pos1 = GetWorldCoordinates( pos1 ); + pos2 = GetWorldCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::PostBind +================ +*/ +void idMover_Binary::PostBind( void ) { + pos1 = GetLocalCoordinates( pos1 ); + pos2 = GetLocalCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::FindGuiTargets +================ +*/ +void idMover_Binary::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover_Binary::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover_Binary::SetGuiState( const char *key, const char *val ) const { + int i; + + for( i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_InitGuiTargets( void ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", guiBinaryMoverStates[MOVER_POS1] ); + } +} + +/* +================ +idMover_Binary::InitSpeed + +pos1, pos2, and speed are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ) { + idVec3 move; + float distance; + float speed; + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + speed = mspeed ? mspeed : 100; + + // calculate time to reach second position from speed + move = pos2 - pos1; + distance = move.Length(); + duration = idPhysics::SnapTimeToPhysicsFrame( distance * 1000 / speed ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::InitTime + +pos1, pos2, and time are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ) { + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mtime ) ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::SetBlocked +================ +*/ +void idMover_Binary::SetBlocked( bool b ) { + for ( idMover_Binary *slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->blocked = b; + if ( b ) { + const idKeyValue *kv = slave->spawnArgs.MatchPrefix( "triggerBlocked" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = slave->spawnArgs.MatchPrefix( "triggerBlocked", kv ); + } + } + } +} + +/* +================ +idMover_Binary::IsBlocked +================ +*/ +bool idMover_Binary::IsBlocked( void ) { + return blocked; +} + +/* +================ +idMover_Binary::GetActivator +================ +*/ +idEntity *idMover_Binary::GetActivator( void ) const { + return activatedBy.GetEntity(); +} + +/* +================ +idMover_Binary::WriteToSnapshot +================ +*/ +void idMover_Binary::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( moverState, 3 ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Binary::ReadFromSnapshot +================ +*/ +void idMover_Binary::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moverState_t oldMoverState = moverState; + + physicsObj.ReadFromSnapshot( msg ); + moverState = (moverState_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( moverState != oldMoverState ) { + UpdateMoverSound( moverState ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover_Binary::SetPortalState +================ +*/ +void idMover_Binary::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +/* +=============================================================================== + +idDoor + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +const idEventDef EV_Door_StartOpen( "", NULL ); +const idEventDef EV_Door_SpawnDoorTrigger( "", NULL ); +const idEventDef EV_Door_SpawnSoundTrigger( "", NULL ); +const idEventDef EV_Door_Open( "open", NULL ); +const idEventDef EV_Door_Close( "close", NULL ); +const idEventDef EV_Door_Lock( "lock", "d" ); +const idEventDef EV_Door_IsOpen( "isOpen", NULL, 'f' ); +const idEventDef EV_Door_IsLocked( "isLocked", NULL, 'f' ); + +CLASS_DECLARATION( idMover_Binary, idDoor ) + EVENT( EV_TeamBlocked, idDoor::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idDoor::Event_PartBlocked ) + EVENT( EV_Touch, idDoor::Event_Touch ) + EVENT( EV_Activate, idDoor::Event_Activate ) + EVENT( EV_Door_StartOpen, idDoor::Event_StartOpen ) + EVENT( EV_Door_SpawnDoorTrigger, idDoor::Event_SpawnDoorTrigger ) + EVENT( EV_Door_SpawnSoundTrigger, idDoor::Event_SpawnSoundTrigger ) + EVENT( EV_Door_Open, idDoor::Event_Open ) + EVENT( EV_Door_Close, idDoor::Event_Close ) + EVENT( EV_Door_Lock, idDoor::Event_Lock ) + EVENT( EV_Door_IsOpen, idDoor::Event_IsOpen ) + EVENT( EV_Door_IsLocked, idDoor::Event_Locked ) + EVENT( EV_ReachedPos, idDoor::Event_Reached_BinaryMover ) + EVENT( EV_SpectatorTouch, idDoor::Event_SpectatorTouch ) + EVENT( EV_Mover_OpenPortal, idDoor::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idDoor::Event_ClosePortal ) +// RAVEN BEGIN +// abahr: + EVENT( EV_Mover_ReturnToPos1, idDoor::Event_ReturnToPos1 ) +// RAVEN END +END_CLASS + +/* +================ +idDoor::idDoor +================ +*/ +idDoor::idDoor( void ) { + triggersize = 1.0f; + crusher = false; + noTouch = false; + aas_area_closed = false; + buddyStr.Clear(); + trigger = NULL; + sndTrigger = NULL; + nextSndTriggerTime = 0; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); + requires.Clear(); + removeItem = 0; + syncLock.Clear(); + companionDoor = NULL; + normalAxisIndex = 0; +} + +/* +================ +idDoor::~idDoor +================ +*/ +idDoor::~idDoor( void ) { + if ( trigger ) { + delete trigger; + } + if ( sndTrigger ) { + delete sndTrigger; + } +} + +/* +================ +idDoor::Save +================ +*/ +void idDoor::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( triggersize ); + savefile->WriteBool( crusher ); + savefile->WriteBool( noTouch ); + savefile->WriteBool( aas_area_closed ); + savefile->WriteString( buddyStr ); + + savefile->WriteClipModel( trigger ); + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); + + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); + + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteString( syncLock ); + savefile->WriteInt( normalAxisIndex ); + + savefile->WriteObject( companionDoor ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Save( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Restore +================ +*/ +void idDoor::Restore( idRestoreGame *savefile ) { + + savefile->ReadFloat( triggersize ); + savefile->ReadBool( crusher ); + savefile->ReadBool( noTouch ); + savefile->ReadBool( aas_area_closed ); + SetAASAreaState( aas_area_closed ); + savefile->ReadString( buddyStr ); + + savefile->ReadClipModel( trigger ); + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); + + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); + + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadString( syncLock ); + savefile->ReadInt( normalAxisIndex ); + + savefile->ReadObject( reinterpret_cast( companionDoor ) ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Restore( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Spawn +================ +*/ +void idDoor::Spawn( void ) { + idVec3 abs_movedir; + float distance; + idVec3 size; + idVec3 movedir; + float dir; + float lip; + bool start_open; + float time; + float speed; + + // get the direction to move + if ( !spawnArgs.GetFloat( "movedir", "0", dir ) ) { + // no movedir, so angle defines movement direction and not orientation, + // a la oldschool Quake + SetAngles( ang_zero ); + spawnArgs.GetFloat( "angle", "0", dir ); + } + GetMovedir( dir, movedir ); + + // default speed of 400 + spawnArgs.GetFloat( "speed", "400", speed ); + + // default wait of 2 seconds + spawnArgs.GetFloat( "wait", "3", wait ); + + // default lip of 8 units + spawnArgs.GetFloat( "lip", "8", lip ); + + // by default no damage + spawnArgs.GetFloat( "damage", "0", damage ); + + // trigger size + spawnArgs.GetFloat( "triggersize", "120", triggersize ); + + spawnArgs.GetBool( "crusher", "0", crusher ); + spawnArgs.GetBool( "start_open", "0", start_open ); + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // expects syncLock to be a door that must be closed before this door will open + spawnArgs.GetString( "syncLock", "", syncLock ); + + spawnArgs.GetString( "buddy", "", buddyStr ); + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + + // ever separate piece of a door is considered solid when other team mates push entities + fl.solidForTeam = true; + + // first position at start + pos1 = GetPhysics()->GetOrigin(); + + // calculate second position + abs_movedir[0] = idMath::Fabs( movedir[ 0 ] ); + abs_movedir[1] = idMath::Fabs( movedir[ 1 ] ); + abs_movedir[2] = idMath::Fabs( movedir[ 2 ] ); + size = GetPhysics()->GetAbsBounds()[1] - GetPhysics()->GetAbsBounds()[0]; + distance = ( abs_movedir * size ) - lip; + pos2 = pos1 + distance * movedir; + + // if "start_open", reverse position 1 and 2 + if ( start_open ) { + // post it after EV_SpawnBind + PostEventMS( &EV_Door_StartOpen, 1 ); + } + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } + + if ( moveMaster == this ) { + if ( health ) { + fl.takedamage = true; + } + if ( noTouch || health ) { + // non touch/shoot doors + PostEventMS( &EV_Mover_MatchTeam, 0, moverState, gameLocal.time ); + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } else { + // spawn trigger + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + } + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + if ( !start_open ) { + // start closed + ProcessEvent( &EV_Mover_ClosePortal ); + } + + int locked = spawnArgs.GetInt( "locked" ); + if ( locked ) { + // make sure all members of the team get locked + PostEventMS( &EV_Door_Lock, 0, locked ); + } + + if ( spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "delay" ), this ); + } + + // sounds have a habit of stuttering when portals close, so make them unoccluded + refSound.parms.soundShaderFlags |= SSF_NO_OCCLUSION; + + companionDoor = NULL; + + enabled = true; + blocked = false; + +// RAVEN BEGIN +// bdube: added + // Instruct ai to avoid standing in doors + if ( !spawnArgs.GetBool ( "noavoid" ) ) { + aiManager.AddAvoid ( GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetBounds().GetRadius(), -1 ); + } +// RAVEN END +} + +/* +================ +idDoor::Think +================ +*/ +void idDoor::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + if ( sndTrigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idDoor::PreBind +================ +*/ +void idDoor::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idDoor::PostBind +================ +*/ +void idDoor::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ? trigger : sndTrigger ); +} + +/* +================ +idDoor::SetAASAreaState +================ +*/ +void idDoor::SetAASAreaState( bool closed ) { + aas_area_closed = closed; + gameLocal.SetAASAreaState( physicsObj.GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL|AREACONTENTS_OBSTACLE, closed ); +} + +/* +================ +idDoor::Hide +================ +*/ +void idDoor::Hide( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Hide(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Hide(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Disable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Disable(); + } + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + slave->GetPhysics()->GetClipModel()->Disable(); + slave->idMover_Binary::Hide(); + } + } +} + +/* +================ +idDoor::Show +================ +*/ +void idDoor::Show( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Show(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Show(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Enable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Enable(); + } + if ( slaveDoor->areaPortal && ( slaveDoor->moverState == MOVER_POS1 ) ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + slave->GetPhysics()->GetClipModel()->Enable(); + slave->idMover_Binary::Show(); + } + } +} + +/* +================ +idDoor::GetLocalTriggerPosition +================ +*/ +void idDoor::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +================ +idDoor::Use +================ +*/ +void idDoor::Use( idEntity *other, idEntity *activator ) { + if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) { + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + ActivateTargets( activator ); + Use_BinaryMover( activator ); + } +} + +/* +================ +idDoor::Open +================ +*/ +void idDoor::Open( void ) { + GotoPosition2(); +} + +/* +================ +idDoor::Close +================ +*/ +void idDoor::Close( void ) { + GotoPosition1(); +} + +/* +================ +idDoor::Lock +================ +*/ +void idDoor::Lock( int f ) { + idMover_Binary *other; + + // lock all the doors on the team + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + idDoor *door = static_cast( other ); + if ( other == moveMaster ) { + if ( door->sndTrigger == NULL ) { + // in this case the sound trigger never got spawned + const char *sndtemp = door->spawnArgs.GetString( "snd_locked" ); + if ( sndtemp && *sndtemp ) { + door->PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } + if ( !f && ( door->spawnArgs.GetInt( "locked" ) != 0 ) ) { + door->StartSound( "snd_unlocked", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + door->spawnArgs.SetInt( "locked", f ); +// RAVEN BEGIN +// jdischler + // locking/unlocking doors should update the shaderparm7 of any buddies + door->UpdateBuddies( !f ); +// RAVEN END + + if ( ( f == 0 ) || ( !IsHidden() && ( door->moverState == MOVER_POS1 ) ) ) { + door->SetAASAreaState( f != 0 ); + } + } + } + + if ( f ) { + Close(); + } +} + +/* +================ +idDoor::IsLocked +================ +*/ +int idDoor::IsLocked( void ) { + return spawnArgs.GetInt( "locked" ); +} + +/* +================ +idDoor::IsOpen +================ +*/ +bool idDoor::IsOpen( void ) { + return ( moverState != MOVER_POS1 ); +} + +/* +================ +idDoor::IsNoTouch +================ +*/ +bool idDoor::IsNoTouch( void ) { + return noTouch; +} + +/* +====================== +idDoor::CalcTriggerBounds + +Calcs bounds for a trigger. +====================== +*/ +void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) { + idMover_Binary *other; + int i; + int best; + + // find the bounds of everything on the team + bounds = GetPhysics()->GetAbsBounds(); + + if ( health > 0 ) { + fl.takedamage = true; + } + for( other = activateChain; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + // find the bounds of everything on the team + bounds.AddBounds( other->GetPhysics()->GetAbsBounds() ); + + // set all of the slaves as shootable + other->fl.takedamage = true; + } + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + normalAxisIndex = best; + bounds[0][ best ] -= size; + bounds[1][ best ] += size; + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); +} + +// RAVEN BEGIN +// abahr: +/* +============================== +idDoor::SetDoorFrameController +============================== +*/ +void idDoor::SetDoorFrameController( idEntity* controller ) { + doorFrameController = controller; +} + +/* +============================== +idDoor::ActivateTargets + +If we have a frame controller we activate its targets +============================== +*/ +void idDoor::ActivateTargets( idEntity *activator ) const { + if ( doorFrameController.IsValid() && static_cast< const idMover_Binary *>( GetMoveMaster() ) == this && moverState == MOVER_POS1 ) { + doorFrameController->ActivateTargets( activator ); + } + + idMover_Binary::ActivateTargets( activator ); +} +// RAVEN END + +/* +====================== +idDoor::Event_StartOpen + +if "start_open", reverse position 1 and 2 +====================== +*/ +void idDoor::Event_StartOpen( void ) { + float time; + float speed; + + // if "start_open", reverse position 1 and 2 + pos1 = pos2; + pos2 = GetPhysics()->GetOrigin(); + + spawnArgs.GetFloat( "speed", "400", speed ); + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } +} + +/* +====================== +idDoor::Event_SpawnDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them. +====================== +*/ +void idDoor::Event_SpawnDoorTrigger( void ) { + idBounds bounds; + idMover_Binary *other; + bool toggle; + + if ( trigger ) { + // already have a trigger, so don't spawn a new one. + return; + } + + // check if any of the doors are marked as toggled + toggle = false; + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) && other->spawnArgs.GetBool( "toggle" ) ) { + toggle = true; + break; + } + } + + if ( toggle ) { + // mark them all as toggled + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + other->spawnArgs.Set( "toggle", "1" ); + } + } + // don't spawn trigger + return; + } + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + + CalcTriggerBounds( triggersize, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( trigger ); + + MatchActivateTeam( moverState, gameLocal.time ); +} + +/* +====================== +idDoor::Event_SpawnSoundTrigger + +Spawn a sound trigger to activate locked sound if it exists. +====================== +*/ +void idDoor::Event_SpawnSoundTrigger( void ) { + idBounds bounds; + + if ( sndTrigger ) { + return; + } + + CalcTriggerBounds( triggersize * 0.5f, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + sndTrigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 254, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + sndTrigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( sndTrigger ); +} + +/* +================ +idDoor::Event_Reached_BinaryMover +================ +*/ +void idDoor::Event_Reached_BinaryMover( void ) { + if ( moverState == MOVER_2TO1 ) { + SetBlocked( false ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerClosed" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerClosed", kv ); + } + } else if ( moverState == MOVER_1TO2 ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOpened" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOpened", kv ); + } + } + idMover_Binary::Event_Reached_BinaryMover(); +} + +/* +================ +idDoor::Blocked_Door +================ +*/ +void idDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + SetBlocked( true ); + + if ( crusher ) { + return; // crushers don't reverse + } + + // reverse direction + Use_BinaryMover( moveMaster->GetActivator() ); + + if ( companionDoor ) { + companionDoor->ProcessEvent( &EV_TeamBlocked, blockedEntity, blockingEntity ); + } +} + +/* +=============== +idDoor::SetCompanion +=============== +*/ +void idDoor::SetCompanion( idDoor *door ) { + companionDoor = door; +} + +/* +=============== +idDoor::Event_PartBlocked +=============== +*/ +void idDoor::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idDoor::Event_ReturnToPos1 +================ +*/ +void idDoor::Event_ReturnToPos1( void ) { + idMover_Binary::Event_ReturnToPos1(); + + if( doorFrameController.IsValid() ) { + doorFrameController->ProcessEvent( &EV_CloseGate ); + } +} +// RAVEN END + +/* +================ +idDoor::Event_Touch +================ +*/ +void idDoor::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate; + idVec3 planeaxis1, planeaxis2, normal; + idBounds bounds; + + if ( !enabled ) { + return; + } + + if ( trigger && trace->c.id == trigger->GetId() ) { + if ( !IsNoTouch() && !IsLocked() && GetMoverState() != MOVER_1TO2 ) { +// RAVEN BEGIN +// abahr: allowing animated door frames + if( doorFrameController.IsValid() && doorFrameController != other ) { + doorFrameController->ProcessEvent( &EV_Touch, other, trace ); + } else { + Use( this, other ); + } +// RAVEN END + } + } else if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( other && other->IsType( idPlayer::GetClassType() ) && IsLocked() && gameLocal.time > nextSndTriggerTime ) { +// RAVEN END + StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + } +} + +/* +================ +idDoor::Event_SpectatorTouch +================ +*/ +void idDoor::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate, normal; + idBounds bounds; + idPlayer *p; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( other && other->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( other )->spectating ); +// RAVEN END + + p = static_cast< idPlayer * >( other ); + // avoid flicker when stopping right at clip box boundaries + if ( p->lastSpectateTeleport > gameLocal.time - 1000 ) { + return; + } + if ( trigger && !IsOpen() ) { + // teleport to the other side, center to the middle of the trigger brush + bounds = trigger->GetAbsBounds(); + contact = trace->endpos - bounds.GetCenter(); + translate = bounds.GetCenter(); + normal.Zero(); + normal[ normalAxisIndex ] = 1.0f; + if ( normal * contact > 0 ) { + translate[ normalAxisIndex ] += ( bounds[ 0 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } else { + translate[ normalAxisIndex ] += ( bounds[ 1 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } + p->SetOrigin( translate ); + p->lastSpectateTeleport = gameLocal.time; + } +} + +/* +================ +idDoor::Event_Activate +================ +*/ +void idDoor::Event_Activate( idEntity *activator ) { + int old_lock; + + if ( spawnArgs.GetInt( "locked" ) ) { + if ( !trigger ) { + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + UpdateBuddies( 1 ); + + old_lock = spawnArgs.GetInt( "locked" ); + Lock( 0 ); + if ( old_lock == 2 ) { + return; + } + } + + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + +// RAVEN BEGIN +// abahr: + if( doorFrameController.IsValid() && doorFrameController != activator ) { + doorFrameController->ProcessEvent( &EV_Activate, activator ); + } else { + ActivateTargets( activator ); + + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + UpdateVisuals(); + + Use_BinaryMover( activator ); + } +//RAVEN END +} + +/* +================ +idDoor::Event_Open +================ +*/ +void idDoor::Event_Open( void ) { + Open(); +} + +/* +================ +idDoor::Event_Close +================ +*/ +void idDoor::Event_Close( void ) { + Close(); +} + +/* +================ +idDoor::Event_Lock +================ +*/ +void idDoor::Event_Lock( int f ) { + Lock( f ); +} + +/* +================ +idDoor::Event_IsOpen +================ +*/ +void idDoor::Event_IsOpen( void ) { + bool state; + + state = IsOpen(); + idThread::ReturnFloat( state ); +} + +/* +================ +idDoor::Event_Locked +================ +*/ +void idDoor::Event_Locked( void ) { + idThread::ReturnFloat( spawnArgs.GetInt("locked") ); +} + +/* +================ +idDoor::Event_OpenPortal + +Sets the portal associtated with this door to be open +================ +*/ +void idDoor::Event_OpenPortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + } +} + +/* +================ +idDoor::Event_ClosePortal + +Sets the portal associtated with this door to be closed +================ +*/ +void idDoor::Event_ClosePortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( !slave->IsHidden() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + } + } +} + + +/* +=============================================================================== + +idPlat + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Binary, idPlat ) + EVENT( EV_Touch, idPlat::Event_Touch ) + EVENT( EV_TeamBlocked, idPlat::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idPlat::Event_PartBlocked ) +END_CLASS + +/* +=============== +idPlat::idPlat +=============== +*/ +idPlat::idPlat( void ) { + trigger = NULL; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); +} + +/* +=============== +idPlat::~idPlat +=============== +*/ +idPlat::~idPlat( void ) { + if ( trigger ) { + delete trigger; + } +} + +/* +=============== +idPlat::Save +=============== +*/ +void idPlat::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Restore +=============== +*/ +void idPlat::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Spawn +=============== +*/ +void idPlat::Spawn( void ) { + float lip; + float height; + float time; + float speed; + float accel; + float decel; + bool noTouch; + + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetFloat( "damage", "0", damage ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetFloat( "lip", "8", lip ); + spawnArgs.GetFloat( "accel_time", "0.25", accel ); + spawnArgs.GetFloat( "decel_time", "0.25", decel ); + + // create second position + if ( !spawnArgs.GetFloat( "height", "0", height ) ) { + height = ( GetPhysics()->GetBounds()[1][2] - GetPhysics()->GetBounds()[0][2] ) - lip; + } + + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // pos1 is the rest (bottom) position, pos2 is the top + pos2 = GetPhysics()->GetOrigin(); + pos1 = pos2; + pos1[2] -= height; + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, accel, decel ); + } else { + InitSpeed( pos1, pos2, speed, accel, decel ); + } + + SetMoverState( MOVER_POS1, gameLocal.time ); + UpdateVisuals(); + + // spawn the trigger if one hasn't been custom made + if ( !noTouch ) { + // spawn trigger + SpawnPlatTrigger( pos1 ); + } +} + +/* +================ +idPlat::Think +================ +*/ +void idPlat::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idPlat::PreBind +================ +*/ +void idPlat::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idPlat::PostBind +================ +*/ +void idPlat::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ); +} + +/* +================ +idPlat::GetLocalTriggerPosition +================ +*/ +void idPlat::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +============== +idPlat::SpawnPlatTrigger +=============== +*/ +void idPlat::SpawnPlatTrigger( idVec3 &pos ) { + idBounds bounds; + idVec3 tmin; + idVec3 tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + + bounds = GetPhysics()->GetBounds(); + + tmin[0] = bounds[0][0] + 33; + tmin[1] = bounds[0][1] + 33; + tmin[2] = bounds[0][2]; + + tmax[0] = bounds[1][0] - 33; + tmax[1] = bounds[1][1] - 33; + tmax[2] = bounds[1][2] + 8; + + if ( tmax[0] <= tmin[0] ) { + tmin[0] = ( bounds[0][0] + bounds[1][0] ) * 0.5f; + tmax[0] = tmin[0] + 1; + } + if ( tmax[1] <= tmin[1] ) { + tmin[1] = ( bounds[0][1] + bounds[1][1] ) * 0.5f; + tmax[1] = tmin[1] + 1; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( idBounds( tmin, tmax ) ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +============== +idPlat::Event_Touch +=============== +*/ +void idPlat::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( ( GetMoverState() == MOVER_POS1 ) && trigger && ( trace->c.id == trigger->GetId() ) && ( other->health > 0 ) ) { + Use_BinaryMover( other ); + } +} + +/* +================ +idPlat::Event_TeamBlocked +================ +*/ +void idPlat::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + // reverse direction + Use_BinaryMover( activatedBy.GetEntity() ); + + Use_BinaryMover( this ); + +} + +/* +=============== +idPlat::Event_PartBlocked +=============== +*/ +void idPlat::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + + +/* +=============================================================================== + +idMover_Periodic + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMover_Periodic ) + EVENT( EV_TeamBlocked, idMover_Periodic::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover_Periodic::Event_PartBlocked ) +END_CLASS + +/* +=============== +idMover_Periodic::idMover_Periodic +=============== +*/ +idMover_Periodic::idMover_Periodic( void ) { + damage = 0.0f; + fl.neverDormant = false; +} + +idMover_Periodic::~idMover_Periodic( void ) { + SetPhysics( NULL ); +} + +/* +=============== +idMover_Periodic::Spawn +=============== +*/ +void idMover_Periodic::Spawn( void ) { + spawnArgs.GetFloat( "damage", "0", damage ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + GetPhysics()->SetContents( 0 ); + } +} + +/* +=============== +idMover_Periodic::Save +=============== +*/ +void idMover_Periodic::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( damage ); +} + +/* +=============== +idMover_Periodic::Restore +=============== +*/ +void idMover_Periodic::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + savefile->ReadFloat( damage ); + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMover_Periodic::Think +================ +*/ +void idMover_Periodic::Think( void ) { + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + RunPhysics(); + Present(); +} + +/* +=============== +idMover_Periodic::Event_TeamBlocked +=============== +*/ +void idMover_Periodic::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } +} + +/* +=============== +idMover_Periodic::Event_PartBlocked +=============== +*/ +void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +/* +================ +idMover_Periodic::WriteToSnapshot +================ +*/ +void idMover_Periodic::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Periodic::ReadFromSnapshot +================ +*/ +void idMover_Periodic::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idRotater + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRotater ) + EVENT( EV_Activate, idRotater::Event_Activate ) +END_CLASS + +/* +=============== +idRotater::idRotater +=============== +*/ +idRotater::idRotater( void ) { + activatedBy = this; +} + +/* +=============== +idRotater::Spawn +=============== +*/ +void idRotater::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, gameLocal.time, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + ProcessEvent( &EV_Activate, this ); + } +} + +/* +=============== +idRotater::Save +=============== +*/ +void idRotater::Save( idSaveGame *savefile ) const { + activatedBy.Save( savefile ); +} + +/* +=============== +idRotater::Restore +=============== +*/ +void idRotater::Restore( idRestoreGame *savefile ) { + activatedBy.Restore( savefile ); +} + +/* +=============== +idRotater::Event_Activate +=============== +*/ +void idRotater::Event_Activate( idEntity *activator ) { + float speed; + bool x_axis; + bool y_axis; + idAngles delta; + + activatedBy = activator; + + delta.Zero(); + + if ( !spawnArgs.GetBool( "rotate" ) ) { + spawnArgs.Set( "rotate", "1" ); + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of rotation + if ( x_axis ) { + delta[2] = speed; + } else if ( y_axis ) { + delta[0] = speed; + } else { + delta[1] = speed; + } + } else { + spawnArgs.Set( "rotate", "0" ); + } + + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idBobber ) +END_CLASS + +/* +=============== +idBobber::idBobber +=============== +*/ +idBobber::idBobber( void ) { +} + +/* +=============== +idBobber::Spawn +=============== +*/ +void idBobber::Spawn( void ) { + float speed; + float height; + float phase; + bool x_axis; + bool y_axis; + idVec3 delta; + + spawnArgs.GetFloat( "speed", "4", speed ); + spawnArgs.GetFloat( "height", "32", height ); + spawnArgs.GetFloat( "phase", "0", phase ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of bobbing + delta = vec3_origin; + if ( x_axis ) { + delta[ 0 ] = height; + } else if ( y_axis ) { + delta[ 1 ] = height; + } else { + delta[ 2 ] = height; + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, speed * 500, GetPhysics()->GetOrigin(), delta * 2.0f, vec3_origin ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idPendulum + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idPendulum ) +END_CLASS + +/* +=============== +idPendulum::idPendulum +=============== +*/ +idPendulum::idPendulum( void ) { +} + +/* +=============== +idPendulum::Spawn +=============== +*/ +void idPendulum::Spawn( void ) { + float speed; + float freq; + float length; + float phase; + + spawnArgs.GetFloat( "speed", "30", speed ); + spawnArgs.GetFloat( "phase", "0", phase ); + + if ( spawnArgs.GetFloat( "freq", "", freq ) ) { + if ( freq <= 0.0f ) { + gameLocal.Error( "Invalid frequency on entity '%s'", GetName() ); + } + } else { + // find pendulum length + length = idMath::Fabs( GetPhysics()->GetBounds()[0][2] ); + if ( length < 8 ) { + length = 8; + } + + if( gameLocal.isMultiplayer ) { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_mp_gravity.GetFloat() / ( 3 * length ) ); + } else { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_gravity.GetFloat() / ( 3 * length ) ); + } + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, 500/freq, GetPhysics()->GetAxis().ToAngles(), idAngles( 0, 0, speed * 2.0f ), ang_zero ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRiser ) +EVENT( EV_Activate, idRiser::Event_Activate ) +END_CLASS + +/* +=============== +idRiser::idRiser +=============== +*/ +idRiser::idRiser( void ) { +} + +/* +=============== +idRiser::Spawn +=============== +*/ +void idRiser::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + SetPhysics( &physicsObj ); +} + +/* +================ +idRiser::Event_Activate +================ +*/ +void idRiser::Event_Activate( idEntity *activator ) { + + if ( !IsHidden() && spawnArgs.GetBool("hide") ) { + Hide(); + } else { + Show(); + float time; + float height; + idVec3 delta; + + spawnArgs.GetFloat( "time", "4", time ); + spawnArgs.GetFloat( "height", "32", height ); + + delta = vec3_origin; + delta[ 2 ] = height; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, time * 1000, physicsObj.GetOrigin(), delta, vec3_origin ); + } +} + + +/* +=============================================================================== + +rvConveyor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvConveyor ) + EVENT( EV_FindTargets, rvConveyor::Event_FindTargets ) +END_CLASS + +/* +================ +rvConveyor::rvConveyor +================ +*/ +rvConveyor::rvConveyor ( void ) { +} + +/* +================ +rvConveyor::Spawn +================ +*/ +void rvConveyor::Spawn ( void ) { + spawnArgs.GetFloat ( "speed", "100", moveSpeed ); + + float angle; + if ( spawnArgs.GetFloat ( "moveAngle", "0", angle ) ) { + moveDir = idAngles ( 0, angle, 0 ).ToMat3()[0]; + } else { + moveDir = GetPhysics()->GetAxis()[0]; + } + + GetPhysics()->SetContents ( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID ); + GetPhysics()->EnableClip ( ); + + BecomeActive ( TH_THINK|TH_PHYSICS ); +} + +/* +================ +rvConveyor::Think +================ +*/ +void rvConveyor::Think ( void ) { + trace_t pushResults; + idVec3 newOrigin; + idMat3 newAxis; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin ( ); + oldAxis = GetPhysics()->GetAxis ( ); + + newOrigin = GetPhysics()->GetOrigin() + moveDir * moveSpeed * MS2SEC ( gameLocal.GetMSec() ); + newAxis = oldAxis; + gameLocal.push.ClipPush( pushResults, this, 0, + GetPhysics()->GetOrigin(), oldAxis, newOrigin, newAxis ); + + GetPhysics()->SetOrigin ( oldOrigin ); + GetPhysics()->SetAxis ( oldAxis ); + + idEntity::Think(); +} + +/* +================ +rvConveyor::Save +================ +*/ +void rvConveyor::Save( idSaveGame *savefile ) const +{ + savefile->WriteVec3( moveDir ); + savefile->WriteFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Restore +================ +*/ +void rvConveyor::Restore( idRestoreGame *savefile ) +{ + savefile->ReadVec3( moveDir ); + savefile->ReadFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Event_FindTargets +================ +*/ +void rvConveyor::Event_FindTargets ( void ) { + FindTargets ( ); + + moveDir = GetPhysics()->GetAxis ( )[0]; + if ( !targets.Num ( ) || !targets[0] ) { + return; + } + + idEntity* path; + idEntity* next; + path = targets[0]; + path->FindTargets ( ); + next = path->targets.Num() ? path->targets[0].GetEntity() : NULL; + if ( !next ) { + return; + } + + moveDir = next->GetPhysics()->GetOrigin() - path->GetPhysics()->GetOrigin(); + moveDir.Normalize ( ); + + path->PostEventMS ( &EV_Remove, 0 ); +} + + +CLASS_DECLARATION( idMover, rvPusher ) +END_CLASS + +/* +================ +rvPusher::rvPusher +================ +*/ +rvPusher::rvPusher( void ) { + parent = 0; +} + +/* +================ +rvPusher::~rvPusher +================ +*/ +rvPusher::~rvPusher( void ) { +} + +/* +================ +rvPusher::Spawn +================ +*/ +void rvPusher::Spawn( void ) { + parent = 0; +} + +/* +================ +rvPusher::Think +================ +*/ +void rvPusher::Think( void ) { + + // Total hack, but it gets the job done + BecomeActive( TH_ALL ); + + if ( parent ) { + idAnimator *parentAnimator = parent->GetAnimator(); + if ( parentAnimator ) { + + idStr jointName; + if ( spawnArgs.GetString( "attachedBone", "", jointName ) ) { + bindJointHandle = parentAnimator->GetJointHandle( jointName ); + + trace_t pushResults; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin(); + oldAxis = GetPhysics()->GetAxis(); + + parentAnimator->CreateFrame( gameLocal.time, true ); + parentAnimator->ServiceAnims( gameLocal.previousTime, gameLocal.time ); + parentAnimator->GetJointTransform( bindJointHandle, gameLocal.time, pusherOrigin, pusherAxis ); + pusherAxis *= parent->GetRenderEntity()->axis; + pusherOrigin = parent->GetRenderEntity()->origin + pusherOrigin * parent->GetRenderEntity()->axis; + MoveToPos( pusherOrigin ); + gameLocal.push.ClipTranslationalPush(pushResults, this, 0, pusherOrigin, pusherOrigin - oldOrigin ); + GetPhysics()->SetOrigin ( pusherOrigin ); + } + + } + } else { + idStr bindEntName; + parent = 0; + if ( spawnArgs.GetString( "attachedEntity", "", bindEntName ) ) { + parent = gameLocal.FindEntity( bindEntName ); + } + } + + idMover::Think(); +} + + + +// RAVEN END diff --git a/src/game/Mover.h b/src/game/Mover.h new file mode 100644 index 0000000..09a28ec --- /dev/null +++ b/src/game/Mover.h @@ -0,0 +1,730 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_MOVER_H__ +#define __GAME_MOVER_H__ + +extern const idEventDef EV_TeamBlocked; +extern const idEventDef EV_PartBlocked; +extern const idEventDef EV_ReachedPos; +extern const idEventDef EV_ReachedAng; + +// RAVEN BEGIN +// bdube: more externs +extern const idEventDef EV_Door_Lock; +extern const idEventDef EV_Door_IsLocked; +// abahr: +extern const idEventDef EV_GetSplineEntity; +extern const idEventDef EV_MoveAlongVector; +extern const idEventDef EV_Door_Open; +extern const idEventDef EV_Door_Close; +extern const idEventDef EV_Door_IsOpen; +extern const idEventDef EV_Move; +extern const idEventDef EV_RotateOnce; +// twhitaker: +extern const idEventDef EV_Speed; +extern const idEventDef EV_IsActive; +extern const idEventDef EV_IsMoving; + +// mekberg: spline sampling +#define SPLINE_SAMPLE_RATE 60.0f +// RAVEN END + +/* +=============================================================================== + + General movers. + +=============================================================================== +*/ + +class idMover : public idEntity { +public: + CLASS_PROTOTYPE( idMover ); + + idMover( void ); + ~idMover( void ); + + void Spawn( void ); + +// RAVEN BEGIN +// mekberg: added + void Think( void ); +// RAVEN END + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + void SetPortalState( bool open ); + +// RAVEN BEGIN +// mekberg: sounds for splines + void UpdateSplineStage ( void ); +// RAVEN END + +protected: + typedef enum { + ACCELERATION_STAGE, + LINEAR_STAGE, + DECELERATION_STAGE, + FINISHED_STAGE + } moveStage_t; + + typedef enum { + MOVER_NONE, + MOVER_ROTATING, + MOVER_MOVING, + MOVER_SPLINE + } moverCommand_t; + + // + // mover directions. make sure to change script/doom_defs.script if you add any, or change their order + // + typedef enum { + DIR_UP = -1, + DIR_DOWN = -2, + DIR_LEFT = -3, + DIR_RIGHT = -4, + DIR_FORWARD = -5, + DIR_BACK = -6, + DIR_REL_UP = -7, + DIR_REL_DOWN = -8, + DIR_REL_LEFT = -9, + DIR_REL_RIGHT = -10, + DIR_REL_FORWARD = -11, + DIR_REL_BACK = -12 + } moverDir_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idVec3 dir; + } moveState_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idAngles rot; + } rotationState_t; + + idPhysics_Parametric physicsObj; + + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_PartBlocked( idEntity *blockingEntity ); + + void MoveToPos( const idVec3 &pos); + void UpdateMoveSound( moveStage_t stage ); + void UpdateRotationSound( moveStage_t stage ); + void SetGuiStates( const char *state ); + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + virtual void DoneMoving( void ); + virtual void DoneRotating( void ); + virtual void BeginMove( idThread *thread = NULL ); + virtual void BeginRotation( idThread *thread, bool stopwhendone ); + moveState_t move; + + rvStateThread splineStateThread; + float damage; + +private: + rotationState_t rot; + + int move_thread; + int rotate_thread; + idAngles dest_angles; + idAngles angle_delta; + idVec3 dest_position; + idVec3 move_delta; + float move_speed; + int move_time; + int deceltime; + int acceltime; + bool stopRotation; + bool useSplineAngles; + bool attenuate; + bool useIdleSound; + idEntityPtr splineEnt; + moverCommand_t lastCommand; + + qhandle_t areaPortal; // 0 = no portal + +// mekberg: attenution and sound additions + float maxAttenuation; + float attenuationScale; + idVec3 lastOrigin; + int lastTime; + int splineStartTime; +// RAVEN END + + idList< idEntityPtr > guiTargets; + +// RAVEN BEGIN +// abahr: + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void MoveAlongVector( const idVec3& vec ); +// RAVEN END + + void VectorForDir( float dir, idVec3 &vec ); + idCurve_Spline *GetSpline( idEntity *splineEntity ) const; + + void Event_SetCallback( void ); + void Event_TeamBlocked( idEntity *blockedPart, idEntity *blockingEntity ); + void Event_StopMoving( void ); + void Event_StopRotating( void ); + void Event_UpdateMove( void ); + void Event_UpdateRotation( void ); + void Event_SetMoveSpeed( float speed ); + void Event_SetMoveTime( float time ); + void Event_SetDecelerationTime( float time ); + void Event_SetAccellerationTime( float time ); + void Event_MoveTo( idEntity *ent ); + void Event_MoveToPos( idVec3 &pos ); + void Event_MoveDir( float angle, float distance ); + void Event_MoveAccelerateTo( float speed, float time ); + void Event_MoveDecelerateTo( float speed, float time ); + void Event_RotateDownTo( int axis, float angle ); + void Event_RotateUpTo( int axis, float angle ); + void Event_RotateTo( idAngles &angles ); + void Event_Rotate( idAngles &angles ); + void Event_RotateOnce( idAngles &angles ); + void Event_Bob( float speed, float phase, idVec3 &depth ); + void Event_Sway( float speed, float phase, idAngles &depth ); + void Event_SetAccelSound( const char *sound ); + void Event_SetDecelSound( const char *sound ); +// RAVEN BEGIN +// cnicholson: added stop sound support + void Event_SetStoppedSound( const char *sound ); +// RAVEN END + void Event_SetMoveSound( const char *sound ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + void Event_EnableSplineAngles( void ); + void Event_DisableSplineAngles( void ); + void Event_RemoveInitialSplineAngles( void ); + void Event_StartSpline( idEntity *splineEntity ); + void Event_StopSpline( void ); + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN + void Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ); +// RAVEN END + void Event_IsMoving( void ); + void Event_IsRotating( void ); +// RAVEN BEGIN +// abahr: + void Event_GetSplineEntity(); + void Event_MoveAlongVector( const idVec3& vec ); + +// mekberg: spline states + CLASS_STATES_PROTOTYPE ( idMover ); + + stateResult_t State_Accel( const stateParms_t& parms ); + stateResult_t State_Linear( const stateParms_t& parms ); + stateResult_t State_Decel( const stateParms_t& parms ); +// RAVEN END +}; + +class idSplinePath : public idEntity { +public: + CLASS_PROTOTYPE( idSplinePath ); + + idSplinePath(); + ~idSplinePath(); + + void Spawn( void ); + +// RAVEN BEGIN +// abahr: so we can ignore these if needed + void Toggle() { SetActive(!active); } + void Activate() { SetActive(true); } + void Deactivate() { SetActive(false); } + + bool IsActive() const { return active; } + + int SortTargets(); + int SortBackwardsTargets(); + int SortTargets( idList< idEntityPtr >& list ); + void RemoveNullTargets( void ); + void ActivateTargets( idEntity *activator ) const; + + void RemoveNullTargets( idList< idEntityPtr >& list ); + void ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idList< idEntityPtr > backwardPathTargets; + +// mekberg: spline sampling + float GetSampledTime ( float distance ) const; + +protected: + void SetActive( bool activate ) { active = activate; } + virtual void FindTargets(); + + void Event_Toggle( idEntity* activator ) { Toggle(); } + void Event_IsActive(); + +// mekberg: spline sampling + void SampleSpline ( void ); + +protected: + bool active; + +// mekberg: sample splines. + float* sampledTimes; + float sampledSpeed; + int numSamples; +// RAVEN END +}; + + +struct floorInfo_s { + idVec3 pos; + idStr door; + int floor; +}; + +class idElevator : public idMover { +public: + CLASS_PROTOTYPE( idElevator ); + + idElevator( void ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool HandleSingleGuiCommand ( idEntity *entityGui, idLexer *src ); + floorInfo_s * GetFloorInfo ( int floor ); + +protected: + virtual void DoneMoving( void ); + virtual void BeginMove( idThread *thread = NULL ); + void SpawnTrigger( const idVec3 &pos ); + void GetLocalTriggerPosition(); + void Event_Touch( idEntity *other, trace_t *trace ); + +// RAVEN BEGIN +// bdube: added + void SetAASAreaState ( bool enable ); + void InitStatusGui ( idUserInterface* gui ); + void UpdateStatusGui ( idUserInterface* gui ); + void UpdateStatusGuis ( void ); + void UpdateFloorInfo ( void ); +// RAVEN END + +private: + typedef enum { + INIT, + IDLE, + WAITING_ON_DOORS + } elevatorState_t; + + elevatorState_t state; + idList floorInfo; + int currentFloor; + int pendingFloor; + int lastFloor; + bool controlsDisabled; +// bool waitingForPlayerFollowers; + float returnTime; + int returnFloor; + int lastTouchTime; + + class idDoor * GetDoor( const char *name ); + void Think( void ); + void OpenInnerDoor( void ); + void OpenFloorDoor( int floor ); + void CloseAllDoors( void ); + void DisableAllDoors( void ); + void EnableProperDoors( void ); + + void Event_TeamBlocked ( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_Activate ( idEntity *activator ); + void Event_PostFloorArrival ( void ); + void Event_GotoFloor ( int floor ); + void Event_UpdateFloorInfo ( void ); +}; + + +/* +=============================================================================== + + Binary movers. + +=============================================================================== +*/ + +typedef enum { + MOVER_POS1, + MOVER_POS2, + MOVER_1TO2, + MOVER_2TO1 +} moverState_t; + +class idMover_Binary : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Binary ); + + idMover_Binary(); + ~idMover_Binary(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void PreBind( void ); + virtual void PostBind( void ); + + void Enable( bool b ); + void InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ); + void InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ); + void GotoPosition1( void ); + void GotoPosition2( void ); + void Use_BinaryMover( idEntity *activator ); + void SetGuiStates( const char *state ); + void UpdateBuddies( int val ); + idMover_Binary * GetActivateChain( void ) const { return activateChain; } + idMover_Binary * GetMoveMaster( void ) const { return moveMaster; } + void BindTeam( idEntity *bindTo ); + void SetBlocked( bool b ); + bool IsBlocked( void ); + idEntity * GetActivator( void ) const; + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void SetPortalState( bool open ); + +protected: + idVec3 pos1; + idVec3 pos2; + moverState_t moverState; + idMover_Binary * moveMaster; + idMover_Binary * activateChain; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + float wait; + float damage; + int duration; + int accelTime; + int decelTime; + idEntityPtr activatedBy; + int stateStartTime; + idStr team; + bool enabled; + bool deferedOpen; + int move_thread; + int updateStatus; // 1 = lock behaviour, 2 = open close status + idStrList buddies; + idPhysics_Parametric physicsObj; + qhandle_t areaPortal; // 0 = no portal + bool blocked; + idList< idEntityPtr > guiTargets; + + void MatchActivateTeam( moverState_t newstate, int time ); + void JoinActivateTeam( idMover_Binary *master ); + + void UpdateMoverSound( moverState_t state ); + void SetMoverState( moverState_t newstate, int time ); + moverState_t GetMoverState( void ) const { return moverState; } + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + void Event_SetCallback( void ); + void Event_ReturnToPos1( void ); + void Event_Use_BinaryMover( idEntity *activator ); + void Event_Reached_BinaryMover( void ); + void Event_MatchActivateTeam( moverState_t newstate, int time ); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + + static void GetMovedir( float dir, idVec3 &movedir ); +}; + +class idDoor : public idMover_Binary { +public: + CLASS_PROTOTYPE( idDoor ); + + idDoor( void ); + ~idDoor( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void Hide( void ); + virtual void Show( void ); + + bool IsOpen( void ); + bool IsNoTouch( void ); + int IsLocked( void ); + void Lock( int f ); + void Use( idEntity *other, idEntity *activator ); + void Close( void ); + void Open( void ); + void SetCompanion( idDoor *door ); + +// RAVEN BEGIN +// abahr: + bool IsClosed( void ) const { return moverState == MOVER_POS1; } + void SetDoorFrameController( idEntity* controller ); + virtual void ActivateTargets( idEntity *activator ) const; +// RAVEN END + +private: + float triggersize; + bool crusher; + bool noTouch; + bool aas_area_closed; + idStr buddyStr; + idClipModel * trigger; + idClipModel * sndTrigger; + int nextSndTriggerTime; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + idStr requires; + int removeItem; + idStr syncLock; + int normalAxisIndex; // door faces X or Y for spectator teleports + idDoor * companionDoor; + +// RAVEN BEGIN +// abahr: so we can route calls through a tramGate + idEntityPtr doorFrameController; +// RAVEN END + + void SetAASAreaState( bool closed ); + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void CalcTriggerBounds( float size, idBounds &bounds ); + + void Event_Reached_BinaryMover( void ); + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Activate( idEntity *activator ); + void Event_StartOpen( void ); + void Event_SpawnDoorTrigger( void ); + void Event_SpawnSoundTrigger( void ); + void Event_Close( void ); + void Event_Open( void ); + void Event_Lock( int f ); + void Event_IsOpen( void ); + void Event_Locked( void ); + void Event_SpectatorTouch( idEntity *other, trace_t *trace ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + +// RAVEN BEGIN +// abahr: + void Event_ReturnToPos1( void ); +// RAVEN END +}; + +class idPlat : public idMover_Binary { +public: + CLASS_PROTOTYPE( idPlat ); + + idPlat( void ); + ~idPlat( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + +private: + idClipModel * trigger; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void SpawnPlatTrigger( idVec3 &pos ); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Special periodic movers. + +=============================================================================== +*/ + +class idMover_Periodic : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Periodic ); + + idMover_Periodic( void ); + ~idMover_Periodic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idPhysics_Parametric physicsObj; + float damage; + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); +}; + +class idRotater : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRotater ); + + idRotater( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idEntityPtr activatedBy; + + void Event_Activate( idEntity *activator ); +}; + +class idBobber : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idBobber ); + + idBobber( void ); + + void Spawn( void ); + +private: +}; + +class idPendulum : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idPendulum ); + + idPendulum( void ); + + void Spawn( void ); + +private: + +}; + +class idRiser : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRiser ); + + idRiser( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: conveyor belts +class rvConveyor : public idEntity { +public: + CLASS_PROTOTYPE( rvConveyor ); + + rvConveyor ( void ); + + void Spawn( void ); + void Think ( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +private: + + idVec3 moveDir; + float moveSpeed; + + void Event_FindTargets ( void ); +}; + +// nrausch: +class rvPusher : public idMover { +public: + CLASS_PROTOTYPE( rvPusher ); + + rvPusher( void ); + ~rvPusher( void ); + + virtual void Spawn( void ); + virtual void Think ( void ); + +private: + jointHandle_t bindJointHandle; + idEntity *parent; + idVec3 pusherOrigin; + idMat3 pusherAxis; +}; + +// RAVEN END + +#endif /* !__GAME_MOVER_H__ */ diff --git a/src/game/MultiplayerGame.cpp b/src/game/MultiplayerGame.cpp new file mode 100644 index 0000000..639046e --- /dev/null +++ b/src/game/MultiplayerGame.cpp @@ -0,0 +1,9234 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// ddynerman: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" ); + +const char *idMultiplayerGame::MPGuis[] = { +// RAVEN BEGIN +// bdube: use regular hud for now + "guis/hud.gui", +// RAVEN END + "guis/mpmain.gui", + "guis/mpmsgmode.gui", + "guis/netmenu.gui", + "guis/mphud.gui", + NULL +}; + +const char *idMultiplayerGame::ThrottleVars[] = { + "ui_spectate", + "ui_ready", + "ui_team", + NULL +}; + +const char *idMultiplayerGame::ThrottleVarsInEnglish[] = { + "#str_106738", + "#str_106737", + "#str_101991", + NULL +}; + +const int idMultiplayerGame::ThrottleDelay[] = { + 8, + 5, + 5 +}; + +const char* idMultiplayerGame::teamNames[ TEAM_MAX ] = { + "Marine", + "Strogg" +}; + +idCVar gui_ui_name( "gui_ui_name", "", CVAR_GAME | CVAR_NOCHEAT, "copy-over cvar for ui_name" ); + +/* +================ +ComparePlayerByScore +================ +*/ +int ComparePlayersByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +CompareTeamByScore +================ +*/ +int CompareTeamsByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +idMultiplayerGame::idMultiplayerGame +================ +*/ +idMultiplayerGame::idMultiplayerGame() { +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = NULL; +// RITUAL END + scoreBoard = NULL; + statSummary = NULL; + mainGui = NULL; + mapList = NULL; + msgmodeGui = NULL; + defaultWinner = -1; + deadZonePowerupCount = -1; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + + memset( lights, 0, sizeof( lights ) ); + memset( lightHandles, -1, sizeof( lightHandles ) ); + + Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + flagEntities[ i ] = NULL; + teamDeadZoneScore[i] = 0; + } + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + announcerSoundQueue.Clear(); + announcerPlayTime = 0; + + gameState = NULL; + currentSoundOverride = false; + + rankTextPlayer = NULL; + + privatePlayers = 0; + + lastAnnouncerSound = AS_NUM_SOUNDS; +} + +/* +================ +idMultiplayerGame::Shutdown +================ +*/ +void idMultiplayerGame::Shutdown( void ) { + + Clear(); + statManager->Shutdown(); + + if( gameState ) { + delete gameState; + } + gameState = NULL; +} + +/* +================ +idMultiplayerGame::Reset +================ +*/ +void idMultiplayerGame::Reset() { + Clear(); + assert( !scoreBoard && !mainGui && !mapList ); + + mpBuyingManager.Reset(); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = uiManager->FindGui( "guis/buymenu.gui", true, false, true ); + buyMenu->SetStateString( "field_credits", "$0.00"); + buyMenu->SetStateBool( "gameDraw", true ); +// RITUAL END + PACIFIER_UPDATE; + scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true ); + +#ifdef _XENON + statSummary = scoreBoard; +#else + statSummary = uiManager->FindGui( "guis/summary.gui", true, false, true ); + statSummary->SetStateBool( "gameDraw", true ); +#endif + + PACIFIER_UPDATE; + + mainGui = uiManager->FindGui( "guis/mpmain.gui", true, false, true ); + mapList = uiManager->AllocListGUI( ); + mapList->Config( mainGui, "mapList" ); + + // set this GUI so that our Draw function is still called when it becomes the active/fullscreen GUI + mainGui->SetStateBool( "gameDraw", true ); + mainGui->SetKeyBindingNames(); + mainGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + +// SetMenuSkin(); + + PACIFIER_UPDATE; + msgmodeGui = uiManager->FindGui( "guis/mpmsgmode.gui", true, false, true ); + msgmodeGui->SetStateBool( "gameDraw", true ); + + memset ( lights, 0, sizeof( lights ) ); + memset ( lightHandles, -1, sizeof( lightHandles ) ); + + renderLight_t *light; + const char *shader; + + light = &lights[ MPLIGHT_CTF_MARINE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 142.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 190.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 84.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_MARINE; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_CTF_STROGG ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 153.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_QUAD ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 0.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 128.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_HASTE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 225.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_REGEN ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 0.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + PACIFIER_UPDATE; + ClearGuis(); + +//asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = 0; + + iconManager->Shutdown(); + + // update serverinfo + UpdatePrivatePlayerCount(); + + lastReadyToggleTime = -1; + + cvarSystem->SetCVarBool( "s_voiceChatTest", false ); +} + +/* +================ +idMultiplayerGame::ServerClientConnect +================ +*/ +void idMultiplayerGame::ServerClientConnect( int clientNum ) { + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + statManager->ClientConnect( clientNum ); +} + +/* +================ +idMultiplayerGame::SpawnPlayer +================ +*/ +void idMultiplayerGame::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + bool ingame = playerState[ clientNum ].ingame; + // keep ingame to true if needed, that should only happen for local player + + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + if ( !gameLocal.isClient ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); + p->spawnedTime = gameLocal.time; + //if ( gameLocal.IsTeamGame() ) { + // SwitchToTeam( clientNum, -1, p->team ); + //} + playerState[ clientNum ].ingame = ingame; + } + + if ( clientNum == gameLocal.localClientNum ) { + tourneyGUI.SetupTourneyGUI( gameLocal.GetLocalPlayer()->mphud, scoreBoard ); + } + + lastVOAnnounce = 0; +} + +/* +================ +idMultiplayerGame::Clear +================ +*/ +void idMultiplayerGame::Clear() { + + int i; + + pingUpdateTime = 0; + vote = VOTE_NONE; + voteTimeOut = 0; + voteExecTime = 0; + matchStartedTime = 0; + memset( &playerState, 0 , sizeof( playerState ) ); + currentMenu = 0; + bCurrentMenuMsg = false; + nextMenu = 0; + pureReady = false; + scoreBoard = NULL; + buyMenu = NULL; + isBuyingAllowedRightNow = false; + statSummary = NULL; + mainGui = NULL; + msgmodeGui = NULL; + if ( mapList ) { + uiManager->FreeListGUI( mapList ); + mapList = NULL; + } + memset( &switchThrottle, 0, sizeof( switchThrottle ) ); + voiceChatThrottle = 0; + + voteValue.Clear(); + voteString.Clear(); + + prevAnnouncerSnd = -1; + + localisedGametype.Clear(); + + for( i = 0; i < MAX_CLIENTS; i++ ) { + kickVoteMapNames[ i ].Clear(); + } + + for ( i = 0; i < MPLIGHT_MAX; i ++ ) { + FreeLight( i ); + } + + chatHistory.Clear(); + rconHistory.Clear(); + + memset( rankedTeams, 0, sizeof( rvPair ) * TEAM_MAX ); + + if( gameState ) { + gameState->Clear(); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + unrankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + assaultPoints.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); +#endif +// RAVEN END + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + assaultPoints.Clear(); + + ClearAnnouncerSounds(); + + rankTextPlayer = NULL; + + for ( i = 0; i < TEAM_MAX; i++ ) { + flagEntities[ i ] = NULL; + } +} + +/* +================ +idMultiplayerGame::ClearMap +================ +*/ +void idMultiplayerGame::ClearMap( void ) { + assaultPoints.Clear(); + ClearAnnouncerSounds(); + announcerPlayTime = 0; + powerupCount = 0; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + prevAnnouncerSnd = -1; + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + // Dead Zone uses teamFragCount as the "player score" + // so we need to clear it at the beginning of every round. + if ( gameLocal.gameType == GAME_DEADZONE ) { + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + playerState[i].teamFragCount = 0; + playerState[i].deadZoneScore = 0; + } + } +} + +/* +================ +idMultiplayerGame::ClearGuis +================ +*/ +void idMultiplayerGame::ClearGuis() { + int i; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "player%i",i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_tscore", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_wins", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_status", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + scoreBoard->SetStateInt( "rank_self", 0 ); + + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->hud ) { + continue; + } + player->hud->SetStateString( va( "player%i",i+1 ), "" ); + player->hud->SetStateString( va( "player%i_score", i+1 ), "" ); + player->hud->SetStateString( va( "player%i_ready", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + player->hud->SetStateInt( "rank_self", 0 ); + + player->hud->SetStateInt( "team", TEAM_MARINE ); + player->hud->HandleNamedEvent( "flagReturn" ); + player->hud->SetStateInt( "team", TEAM_STROGG ); + player->hud->HandleNamedEvent( "flagReturn" ); + } + + ClearVote(); +} + +/* +================ +idMultiplayerGame::GetPlayerRank +Returns the player rank (0 best), returning the best rank in the case of a tie +================ +*/ +int idMultiplayerGame::GetPlayerRank( idPlayer* player, bool& isTied ) { + int initialRank = -1; + int rank = -1; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rank = i; + initialRank = rank; + } + } + + if( rank == -1 ) { + return rank; + } + + if( rank > 0 ) { + if( rankedPlayers[ rank - 1 ].Second() == rankedPlayers[ rank ].Second() ) { + rank = rankedPlayers[ rank - 1 ].First()->GetRank(); + } else { + rank = rankedPlayers[ rank - 1 ].First()->GetRank() + 1; + } + } + + // check for tie + isTied = false; + + for( int i = rank - 1; i <= rank + 1; i++ ) { + if( i < 0 || i >= rankedPlayers.Num() || rankedPlayers[ i ].First() == player ) { + continue; + } + + if( rankedPlayers[ i ].Second() == rankedPlayers[ initialRank ].Second() ) { + isTied = true; + break; + } + } + + return rank; +} + +/* +================ +idMultiplayerGame::UpdatePlayerRanks +================ +*/ +void idMultiplayerGame::UpdatePlayerRanks( playerRankMode_t rankMode ) { + idEntity* ent = NULL; + + if( rankMode == PRM_AUTO ) { + if( gameLocal.IsTeamGame() ) { + rankMode = PRM_TEAM_SCORE_PLUS_SCORE; + } else if ( gameLocal.gameType == GAME_TOURNEY ) { + rankMode = PRM_WINS; + } else { + rankMode = PRM_SCORE; + } + } + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer* player = (idPlayer*)ent; + + if ( !CanPlay( player ) ) { + unrankedPlayers.Append( player ); + } else { + int rankingValue = 0; + switch( rankMode ) { + case PRM_SCORE: { + rankingValue = GetScore( player ); + break; + } + case PRM_TEAM_SCORE: { + rankingValue = GetTeamScore( player ); + break; + } + case PRM_TEAM_SCORE_PLUS_SCORE: { + rankingValue = GetScore( player ) + GetTeamScore( player ); + break; + } + case PRM_WINS: { + rankingValue = GetWins( player ); + break; + } + default: { + gameLocal.Error( "idMultiplayerGame::UpdatePlayerRanks() - Bad ranking mode '%d'\n", rankMode ); + } + } + rankedPlayers.Append( rvPair(player, rankingValue ) ); + } + } + + qsort( rankedPlayers.Ptr(), rankedPlayers.Num(), rankedPlayers.TypeSize(), ComparePlayersByScore ); + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + bool tied; + rankedPlayers[ i ].First()->SetRank( GetPlayerRank( rankedPlayers[ i ].First(), tied ) ); + } + + for( int i = 0; i < unrankedPlayers.Num(); i++ ) { + unrankedPlayers[ i ]->SetRank( -1 ); + } +} + +/* +================ +idMultiplayerGame::UpdateTeamRanks +================ +*/ +void idMultiplayerGame::UpdateTeamRanks( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + rankedTeams[ i ] = rvPair( i, teamScore[ i ] ); + } + + qsort( rankedTeams, TEAM_MAX, sizeof( rvPair ), CompareTeamsByScore ); +} + +/* +================ +idMultiplayerGame::UpdateRankColor +================ +*/ +void idMultiplayerGame::UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ) { + for ( int j = 1; j < 4; j++ ) { + gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] ); + } +} + +/* +================ +idMultiplayerGame::CanCapture + +Determines if the given flag can be captured in the given gamestate +================ +*/ +bool idMultiplayerGame::CanCapture( int team ) { + // no AP's in one flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + return true; + } else if( gameLocal.gameType != GAME_CTF && gameLocal.gameType != GAME_ARENA_CTF ) { + return false; // no flag caps in none-CTF games + } + + if ( !assaultPoints.Num() ) { + return true; + } + + // since other logic ensures AP's are captured in order, we just need to check the last AP before the enemy flag + if ( team == TEAM_STROGG ) { + // AP 0 is always next to the marine flag + return ((rvCTFGameState*)gameState)->GetAPOwner( 0 ) == TEAM_STROGG; + } + if ( team == TEAM_MARINE ) { + // the last AP is always the one next to the strogg flag + return ((rvCTFGameState*)gameState)->GetAPOwner( assaultPoints.Num() - 1 ) == TEAM_MARINE; + } + + return false; +} + +void idMultiplayerGame::FlagCaptured( idPlayer *player ) { + if( !gameLocal.isClient ) { + AddTeamScore( player->team, 1 ); + AddPlayerTeamScore( player, 5 ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + float teamCashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_flagCapture", 0 ); + GiveCashToTeam( player->team, teamCashAward ); + + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagCapture", 0 ); + player->GiveCash( cashAward ); + } +// RITUAL END + + gameLocal.ClearForwardSpawns(); + + for( int i = 0; i < assaultPoints.Num(); i++ ) { + assaultPoints[ i ]->Reset(); + ((rvCTFGameState*)gameState)->SetAPOwner( i, AS_NEUTRAL ); + } + + statManager->FlagCaptured( player, OpposingTeam( player->team ) ); + player->SetEmote( PE_CHEER ); + } +} + +/* +================ +idMultiplayerGame::SendDeathMessage +================ +*/ +void idMultiplayerGame::SendDeathMessage( idPlayer* attacker, idPlayer* victim, int methodOfDeath ) { + if( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DEATH ); + if( attacker ) { + outMsg.WriteByte( attacker->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ attacker->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + if( victim ) { + outMsg.WriteByte( victim->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ victim->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + outMsg.WriteByte( methodOfDeath ); + + gameLocal.ServerSendInstanceReliableMessage( victim, -1, outMsg ); + + if( gameLocal.isListenServer && gameLocal.GetLocalPlayer() && victim && gameLocal.GetLocalPlayer()->GetInstance() == victim->GetInstance() ) + { + // This is for listen servers, which won't get to ClientProcessReliableMessage + ReceiveDeathMessage( attacker, attacker ? playerState[ attacker->entityNumber ].fragCount : -1, victim, victim ? playerState[ victim->entityNumber ].fragCount : -1, methodOfDeath ); + } + } +} + +/* +================ +idMultiplayerGame::ReceiveDeathMessage +================ +*/ +void idMultiplayerGame::ReceiveDeathMessage( idPlayer *attacker, int attackerScore, idPlayer *victim, int victimScore, int methodOfDeath ) { + if( gameLocal.GetLocalPlayer() == NULL ) { + return; + } + +// RITUAL BEGIN +// squirrel: force buy menu open when you die + //if( gameLocal.IsMultiplayer() && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && victim == gameLocal.GetLocalPlayer() ) + //{ + // OpenLocalBuyMenu(); + //} +// RITUAL END + + const char* icon = ""; + + // if methodOfDeath is in range [0, MAX_WEAPONS - 1] it refers to a specific weapon. MAX_WEAPONS refers to + // a generic or unknown death (i.e. "Killer killed victim") and values above MAX_WEAPONS + 1 refer + // to other non-weapon deaths (i.e. telefrags) + + // setup to either use weapon icons for a weapon death, or generic death icons + if ( methodOfDeath < MAX_WEAPONS ) { + icon = va( "w%02d", methodOfDeath ); + } else { + icon = va( "dm%d", methodOfDeath - MAX_WEAPONS ); + } + + char* message = NULL; + + if ( gameLocal.IsTeamGame() ) { + idStr attackerStr( ( attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "" ) ); + idStr victimStr( ( victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "" ) ); + + attackerStr.RemoveEscapes(); + victimStr.RemoveEscapes(); + + message = va ( "%s%s ^r^i%s %s%s", (attacker ? (attacker->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + attackerStr.c_str(), + icon, + (victim ? (victim->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + victimStr.c_str() ); + } else { + message = va ( "%s ^r^i%s %s", (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : ""), + icon, + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "") ); + } + + if( gameLocal.GetLocalPlayer()->hud ) { + gameLocal.GetLocalPlayer()->hud->SetStateString ( "deathinfo", message ); + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent ( "addDeathLine" ); + } + + // echo to console + gameLocal.Printf( gameLocal.GetLocalPlayer()->spawnArgs.GetString( va( "%s_text", icon ), "%s killed %s" ), + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "world"), + (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "world") ); + gameLocal.Printf( "\n" ); + + // display message on hud + if( attacker && victim && (gameLocal.GetLocalPlayer() == attacker || gameLocal.GetLocalPlayer() == victim) && attacker != victim && methodOfDeath < MAX_WEAPONS ) { + if( gameLocal.GetLocalPlayer() == attacker ) { +// RAVEN BEGIN +// rhummer: Added lang entries for "You fragged %s" and "You were fragged by %s" + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107295" ), gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) ) ); + } else { + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107296" ), gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + + if( gameLocal.gameType == GAME_DM ) { + // print rank text next time after we update scores + + // stash the scores on the client so we can print accurate rank info + if( gameLocal.isClient ) { + if( victim ) { + playerState[ victim->entityNumber ].fragCount = victimScore; + } + + if( attacker ) { + playerState[ attacker->entityNumber ].fragCount = attackerScore; + } + } + + if( victim && (gameLocal.GetLocalPlayer() == victim || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == victim->entityNumber)) ) { + rankTextPlayer = victim; + } + + if( attacker && (gameLocal.GetLocalPlayer() == attacker || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == attacker->entityNumber)) ) { + rankTextPlayer = attacker; + } + } + } +} + + +// ddynerman: Gametype specific scoreboard +/* +================ +idMultiplayerGame::UpdateScoreboard +================ +*/ +void idMultiplayerGame::UpdateScoreboard( idUserInterface *scoreBoard ) { + scoreBoard->SetStateInt( "gametype", gameLocal.gameType ); + + //statManager->UpdateInGameHud( scoreBoard, true ); + + if( gameLocal.IsTeamGame() ) { + UpdateTeamScoreboard( scoreBoard ); + } else { + UpdateDMScoreboard( scoreBoard ); + } + + return; +} + +/* +================ +idMultiplayerGame::UpdateDMScoreboard +================ +*/ +void idMultiplayerGame::UpdateDMScoreboard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + int i; + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if( !player ) { + return; + } + + scoreBoard->SetStateString( "scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + if( gameLocal.gameType == GAME_DM ) { + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + } else if( gameLocal.gameType == GAME_TOURNEY ) { + // loop through twice listing players who are playing, then players who have been eliminated + int listIndex = 0; + + + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), false ); + listIndex++; + } + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() != PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), true ); + listIndex++; + } + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%s\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + playerState[ unrankedPlayers[ i ]->entityNumber ].ping, // ping + "" ) ); + } else { + scoreBoard->SetStateString( va( "spectator_scores_item_%i", i ), "" ); + } + } + + for( i = listIndex; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "scores_item_%i", i ), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() + unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); + + scoreBoard->SetStateString( "position_text", GetPlayerRankText( gameLocal.GetLocalPlayer() ) ); + // shouchard: added map name + // mekberg: localized string + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) == 1 ) { + // stupid english plurals + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107712" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } else { + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107715" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "tourney_count", va( common->GetLocalizedString( "#str_107713" ), ((rvTourneyGameState*)gameState)->GetTourneyCount(), gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) ); + if( gameLocal.GetLocalPlayer() ) { + inNonTimedState |= ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetState() == AS_SUDDEN_DEATH; + } + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || inNonTimedState ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTeamScoreboard +================ +*/ + +// only output 16 clients onto the scoreboard +#define SCOREBOARD_MAX_CLIENTS 16 + +void idMultiplayerGame::UpdateTeamScoreboard( idUserInterface *scoreBoard ) { + idStr gameinfo; + int numTeamEntries[ TEAM_MAX ]; + idPlayer* player = gameLocal.GetLocalPlayer(); + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if( !player ) { + return; + } + + SIMDProcessor->Memset( numTeamEntries, 0, sizeof( int ) * TEAM_MAX ); + + scoreBoard->SetStateString( "team_0_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "team_1_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + + for ( int i = 0; i < SCOREBOARD_MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( va("team_%i_scores_sel_0", rankedPlayer->team ), numTeamEntries[ rankedPlayer->team ] ); + } + +// RAVEN BEGIN +// mekberg: redid this + if ( gameLocal.gameType == GAME_TDM ) + { + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } + //else if ( gameLocal.gameType == GAME_DEADZONE ) + //{ + // // mekberg: made this check slightly more sane. + // const char* flagString = ""; + // if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + // flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + // } else if ( gameLocal.gameType == GAME_ARENA_CTF && gameLocal.GetLocalPlayer() && rankedPlayer->team == gameLocal.GetLocalPlayer()->team ) { + // flagString = rankedPlayer->GetArenaPowerupString( ); + // } + // scoreBoard->SetStateString ( + // va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + // va("%s\t%s\t%s\t%s\t%s\t%s\t%.01f\t%i\t%i\t%i\t", + // ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + // ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + // ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + // flagString, // shouchard: twhitaker: updated steve's original flag system + // rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + // rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + // rankedScore * 0.1f, // score + // playerState[ rankedPlayer->entityNumber ].fragCount, // kills + // GetPlayerTime( rankedPlayer ), // time + // playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + // numTeamEntries[ rankedPlayer->team ]++; + //} + else + { + // mekberg: made this check slightly more sane. + const char* flagString = ""; + if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + } else if ( gameLocal.gameType == GAME_ARENA_CTF && gameLocal.GetLocalPlayer() && rankedPlayer->team == gameLocal.GetLocalPlayer()->team ) { + flagString = rankedPlayer->GetArenaPowerupString( ); + } + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + flagString, // shouchard: twhitaker: updated steve's original flag system + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].fragCount, // kills + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } +// RAVEN END + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == gameLocal.GetLocalPlayer() ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + +// RAVEN BEGIN +// mekberg: redid this + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectating && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping // ping +// RAVEN END + + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + } + } + + // clear unused space + for( int k = 0; k < TEAM_MAX; k++ ) { + for( int i = numTeamEntries[ k ]; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString ( va("team_%i_scores_item_%i", k, i), "" ); + } + } + + scoreBoard->SetStateInt ( "playerteam", gameLocal.GetLocalPlayer()->team ); + + scoreBoard->SetStateInt ( "strogg_score", teamScore[ TEAM_STROGG ] ); + scoreBoard->SetStateInt ( "marine_score", teamScore[ TEAM_MARINE ] ); + scoreBoard->SetStateInt ( "num_strogg_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] ) ); + scoreBoard->SetStateInt ( "num_marine_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] + unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); +// RAVEN BEGIN +// shouchard: added map name +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); +// RAVEN END + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + if ( gameLocal.IsFlagGameType() ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ); + } else if ( gameLocal.gameType == GAME_DEADZONE ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_122008" ), gameLocal.serverInfo.GetInt( "si_controlTime" ) ) ); + } else { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || state == WARMUP || state == GAMEREVIEW || state == SUDDENDEATH ); + + scoreBoard->StateChanged( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::BuildSummaryListString +Returns a summary string for the specified player +================ +*/ +const char* idMultiplayerGame::BuildSummaryListString( idPlayer* player, int rankedScore ) { + // track top 3 accuracies + rvPlayerStat* stat = statManager->GetPlayerStat( player->entityNumber ); + idList > bestAccuracies; + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + // only consider weapons we fired more than a few shots + if( stat->weaponShots[ j ] <= 10 ) { + continue; + } + + float accuracy = (float)stat->weaponHits[ j ] / (float)stat->weaponShots[ j ]; + bestAccuracies.Append( rvPair( j, accuracy ) ); + } + + bestAccuracies.Sort( rvPair::rvPairSecondCompareDirect ); + + // hold upto 3 top weapons at 5 chars each + idStr weaponString; + for( int j = 0; j < 3; j++ ) { + if( j >= bestAccuracies.Num() ) { + continue; + } + + weaponString += va( "^iw%02d", bestAccuracies[ j ].First() ); + } + + return va("%d. %s\t%s\t%d\t%s\t", + player->GetRank() + 1, + player->GetUserInfo()->GetString( "ui_name" ), // name + player->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + weaponString.c_str() ); +} + +/* +================ +idMultiplayerGame::UpdateSummaryBoard +Shows top 10 players if local player is in top 10, otherwise shows top 9 and localplayer +================ +*/ +void idMultiplayerGame::UpdateSummaryBoard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !player ) { + return; + } + + int playerIndex = -1; + + // update our ranks in case we call this the same frame it happens + UpdatePlayerRanks(); + + // highlight top 3 players + idVec4 blueHighlight = idStr::ColorForIndex( C_COLOR_BLUE ); + idVec4 redHighlight = idStr::ColorForIndex( C_COLOR_RED ); + idVec4 yellowHighlight = idStr::ColorForIndex( C_COLOR_YELLOW ); + blueHighlight[ 3 ] = 0.15f; + redHighlight[ 3 ] = 0.15f; + yellowHighlight[ 3 ] = 0.15f; + + if( gameLocal.IsTeamGame() ) { + scoreBoard->HandleNamedEvent( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? "marine_wins" : "strogg_wins" ); + // summary is top 5 players on each team + int lastHighIndices[ TEAM_MAX ]; + memset( lastHighIndices, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < 5; i++ ) { + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_marine_names", i ), "" ); + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_strogg_names", i ), "" ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + for( int j = 0; j < 5; j++ ) { + idPlayer* rankedPlayer = NULL; + int rankedScore = 0; + int k; + for( k = lastHighIndices[ i ]; k < rankedPlayers.Num(); k++ ) { + if( rankedPlayers[ k ].First()->team == i ) { + rankedPlayer = rankedPlayers[ k ].First(); + rankedScore = rankedPlayers[ k ].Second(); + break; + } + } + + // no more teammates + if( k >= rankedPlayers.Num() ) { + break; + } + + if( j == 4 && playerIndex == -1 && player->team == i ) { + int z; + for( z = 0; z < rankedPlayers.Num(); z++ ) { + if( rankedPlayers[ z ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ z ].Second(); + break; + } + } + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + playerIndex = j; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", i == TEAM_MARINE ? "summary_marine_names" : "summary_strogg_names", j ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + lastHighIndices[ i ] = k + 1; + } + } + + if( playerIndex > 0 ) { + if( player->team == TEAM_MARINE ) { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + } + } else { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } + } else { + for ( int i = 0; i < 10; i++ ) { + + // mekberg: delete old highlights + scoreBoard->DeleteStateVar( va( "summary_names_item_%d_highlight", i ) ); + + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( i == 9 && playerIndex == -1 ) { + // if the player is ranked, substitute them in + int i; + for( i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ i ].Second(); + break; + } + } + } + + if ( rankedPlayer == gameLocal.GetLocalPlayer() ) { + // highlight who we are + playerIndex = i; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_names", i ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + if( rankedPlayer->GetRank() == 0 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), blueHighlight ); + } else if( rankedPlayer->GetRank() == 1 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), redHighlight ); + } else if( rankedPlayer->GetRank() == 2 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), yellowHighlight ); + } + } else { + scoreBoard->SetStateString ( va("summary_names_item_%i", i), "" ); + } + } + + // highlight who we are (only if not ranked in the top 3) + if( player->GetRank() >= 0 && player->GetRank() < 3 ) { + scoreBoard->SetStateInt( "summary_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_names_sel_0", playerIndex ); + } + } + + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTestScoreboard +================ +*/ +void idMultiplayerGame::UpdateTestScoreboard ( idUserInterface *scoreBoard ) { + int i; + + gameLocal.random.SetSeed ( g_testScoreboard.GetInteger ( ) ); + + if( gameLocal.IsTeamGame() ) { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), "" ); + i++; + } + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), "" ); + i++; + } + + scoreBoard->SetStateInt ( "strogg_score", gameLocal.random.RandomInt ( 10 ) ); + scoreBoard->SetStateInt ( "marine_score", gameLocal.random.RandomInt ( 10 ) ); + } else { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( gameLocal.random.RandomInt() % 2 ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( gameLocal.random.RandomInt() % 2 ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + "", // shouchard: flag + name.c_str(), // name + "Clan", // clan + "", // team score (unused in DM) + gameLocal.random.RandomInt ( 50 ), // score + gameLocal.random.RandomInt ( 10 ), // time + gameLocal.random.RandomInt ( 300 ) + 20 ) ); + + + } + // clear remaining lines (empty slots) + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + i++; + } + } + + scoreBoard->SetStateInt ( "num_marine_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_strogg_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_players", g_testScoreboard.GetInteger() ); + + scoreBoard->SetStateInt( "rank_self", 2 ); + scoreBoard->SetStateInt ( "playercount", g_testScoreboard.GetInteger ( ) ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->SetStateString( "gameinfo", va( "Game Type:%s Frag Limit:%i Time Limit:%i", gameLocal.serverInfo.GetString( "si_gameType" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->Redraw( gameLocal.time ); +} +// RAVEN END + +/* +================ +idMultiplayerGame::GameTime +================ +*/ +const char *idMultiplayerGame::GameTime( void ) { + static char buff[32]; + int m, s, t, ms; + + bool inCountdown = false; + + ms = 0; + if( gameState->GetMPGameState() == COUNTDOWN ) { + inCountdown = true; + ms = gameState->GetNextMPGameStateTime() - gameLocal.realClientTime; + } else if( gameLocal.GetLocalPlayer() && gameLocal.gameType == GAME_TOURNEY && ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetState() == AS_WARMUP ) { + inCountdown = true; + ms = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetNextStateTime() - gameLocal.realClientTime; + } + if ( inCountdown ) { + s = ms / 1000 + 1; + if ( ms <= 0 ) { + // in tourney mode use a different string since warmups happen before each round + // (not really before the overall game) + idStr::snPrintf( buff, sizeof( buff ), "%s --", ( gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY ) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ) ); + } else { + idStr::snPrintf( buff, sizeof( buff ), "%s %i", (gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ), s ); + } + } else { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int startTime = matchStartedTime; + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.GetLocalPlayer() ) { + startTime = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetMatchStartTime(); + } + } + if ( timeLimit ) { + ms = ( timeLimit * 60000 ) - ( gameLocal.time - startTime ); + } else { + ms = gameLocal.time - startTime; + } + if ( ms < 0 ) { + ms = 0; + } + + s = ms / 1000; + m = s / 60; + s -= m * 60; + t = s / 10; + s -= t * 10; + + sprintf( buff, "%i:%i%i", m, t, s ); + } + return &buff[0]; +} + +/* +================ +idMultiplayerGame::NumActualClients +================ +*/ +int idMultiplayerGame::NumActualClients( bool countSpectators, int *teamcounts ) { + idPlayer *p; + int c = 0; + + if ( teamcounts ) { + teamcounts[ 0 ] = teamcounts[ 1 ] = 0; + } + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + p = static_cast< idPlayer * >( ent ); + if ( countSpectators || CanPlay( p ) ) { + c++; + } + if ( teamcounts && CanPlay( p ) ) { + teamcounts[ p->team ]++; + } + } + return c; +} + +/* +================ +idMultiplayerGame::EnoughClientsToPlay +================ +*/ +bool idMultiplayerGame::EnoughClientsToPlay() { + int team[ 2 ]; + int clients = NumActualClients( false, &team[ 0 ] ); + if ( gameLocal.IsTeamGame() ) { + return clients >= 2 && team[ 0 ] && team[ 1 ]; + } else { + return clients >= 2; + } +} + +/* +================ +idMultiplayerGame::AllPlayersReady +================ +*/ +bool idMultiplayerGame::AllPlayersReady( idStr* reason ) { + int i, minClients, numClients; + idEntity *ent; + idPlayer *p; + int team[ 2 ]; + bool notReady; + + notReady = false; + + minClients = Max( 2, gameLocal.serverInfo.GetInt( "si_minPlayers" ) ); + numClients = NumActualClients( false, &team[ 0 ] ); + if ( numClients < minClients ) { + if( reason ) { + // stupid english plurals + if( minClients == 2 ) { + *reason = common->GetLocalizedString( "#str_107674" ); + } else { + *reason = va( common->GetLocalizedString( "#str_107732" ), minClients - numClients ); + } + + } + + return false; + } + + if ( gameLocal.IsTeamGame() ) { + if ( !team[ 0 ] || !team[ 1 ] ) { + if( reason ) { + *reason = common->GetLocalizedString( "#str_107675" ); + } + + return false; + } + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + p = static_cast< idPlayer * >( ent ); + + if ( CanPlay( p ) && !p->IsReady() ) { + notReady = true; + } + team[ p->team ]++; + } + + if( notReady ) { + if( reason ) { + if( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() ) { + // Tourney has a different hud layout, so needs a different "you are (not)ready" string + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110018" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107711" ), common->KeysFromBinding( "_impulse17" ) ); + } + } else if( gameLocal.GetLocalPlayer() ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110017" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107710" ), common->KeysFromBinding( "_impulse17" ) ); + } + } + } + return false; + } + + return true; +} + +/* +================ +idMultiplayerGame::FragLimitHit +return the winning player (team player) +if there is no FragLeader(), the game is tied and we return NULL +================ +*/ +idPlayer *idMultiplayerGame::FragLimitHit() { + int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + idPlayer *leader = NULL; + + if ( fragLimit <= 0 ) { + return NULL; // fraglimit disabled + } + + leader = FragLeader(); + if ( !leader ) { + return NULL; + } + + if ( playerState[ leader->entityNumber ].fragCount >= fragLimit ) { + return leader; + } + + return NULL; +} + +/* +================ +idMultiplayerGame::TimeLimitHit +================ +*/ +bool idMultiplayerGame::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartedTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +================ +idMultiplayerGame::FragLeader +return the current winner +NULL if even +relies on UpdatePlayerRanks() being called earlier in frame to sort players +================ +*/ +idPlayer* idMultiplayerGame::FragLeader( void ) { + if( rankedPlayers.Num() < 2 ) { + return NULL; + } + + // mark leaders + int i; + int high = GetScore( rankedPlayers[ 0 ].First() ); + idPlayer* p; + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + p = rankedPlayers[ i ].First(); + if ( !p ) { + continue; + } + p->SetLeader( false ); + + if ( !CanPlay( p ) ) { + continue; + } + if ( gameLocal.gameType == GAME_TOURNEY ) { + continue; + } + if ( p->spectating ) { + continue; + } + + if ( GetScore( p ) >= high ) { + p->SetLeader( true ); + } + } + + if( gameLocal.IsTeamGame() ) { + // in a team game, find the first player not on the leader's team, and make sure they aren't tied + int i = 0; + while( i < rankedPlayers.Num() && rankedPlayers[ i ].First()->team == rankedPlayers[ 0 ].First()->team ) { + i++; + } + if( i < rankedPlayers.Num() ) { + if( GetScore( rankedPlayers[ i ].First()->entityNumber ) == GetScore( rankedPlayers[ 0 ].First()->entityNumber ) ) { + return NULL; + } + } + } else if( GetScore( rankedPlayers[ 0 ].First()->entityNumber ) == GetScore( rankedPlayers[ 1 ].First()->entityNumber ) ) { + return NULL; + } + + return rankedPlayers[ 0 ].First(); +} + +/* +================ +idMultiplayerGame::PlayerDeath +================ +*/ +void idMultiplayerGame::PlayerDeath( idPlayer *dead, idPlayer *killer, int methodOfDeath ) { + // don't do PrintMessageEvent + assert( !gameLocal.isClient ); + + if ( killer ) { + if ( gameLocal.IsTeamGame() ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + + // in flag games, we subtract suicides from team-score rather than player score, which is the true + // kill count + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( killer == dead ? dead : killer, -1 ); + } else { + AddPlayerScore( killer == dead ? dead : killer, -1 ); + } + + } else { + // mark a kill + AddPlayerScore( killer, 1 ); + } + + // additional CTF points + if( gameLocal.IsFlagGameType() ) { + if( dead->PowerUpActive( killer->team ? POWERUP_CTF_STROGGFLAG : POWERUP_CTF_MARINEFLAG ) ) { + AddPlayerTeamScore( killer, 2 ); + } + } + if( gameLocal.gameType == GAME_TDM ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + AddTeamScore( killer->team, -1 ); + } else { + AddTeamScore( killer->team, 1 ); + } + } + } else { + // in tourney mode, we don't award points while in the waiting arena + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameState)->GetArena( killer->GetArena() ).GetState() != AS_WARMUP ) { + AddPlayerScore( killer, ( killer == dead ) ? -1 : 1 ); + } + + // in tourney mode, frags track performance over the entire level load, team score keeps track of + // individual rounds + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( killer, ( killer == dead ) ? -1 : 1 ); + } + } + } else { + // e.g. an environmental death + + // flag gametypes subtract points from teamscore, not playerscore + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( dead, -1 ); + } else { + AddPlayerScore( dead, -1 ); + } + + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( dead, -1 ); + } + if( gameLocal.gameType == GAME_TDM ) { + AddTeamScore( dead->team, -1 ); + } + } + + SendDeathMessage( killer, dead, methodOfDeath ); + + statManager->Kill( dead, killer, methodOfDeath ); + +// RAVEN BEGIN +// shouchard: hack for CTF drop messages for listen servers + if ( dead == gameLocal.GetLocalPlayer() && + dead->PowerUpActive( dead->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + if ( dead->mphud ) { + dead->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + dead->mphud->HandleNamedEvent( "main_notice" ); + } + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::PlayerStats +================ +*/ +void idMultiplayerGame::PlayerStats( int clientNum, char *data, const int len ) { + + idEntity *ent; + int team; + + *data = 0; + + // make sure we don't exceed the client list + if ( clientNum < 0 || clientNum > gameLocal.numClients ) { + return; + } + + // find which team this player is on + ent = gameLocal.entities[ clientNum ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + team = static_cast< idPlayer * >(ent)->team; + } else { + return; + } + + idStr::snPrintf( data, len, "team=%d score=%ld tks=%ld", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount ); +} + +/* +================ +idMultiplayerGame::PlayerVote +================ +*/ +void idMultiplayerGame::PlayerVote( int clientNum, playerVote_t vote ) { + playerState[ clientNum ].vote = vote; +} + +/* +================ +idMultiplayerGame::ExecuteVote +the votes are checked for validity/relevance before they are started +we assume that they are still legit when reaching here +================ +*/ +void idMultiplayerGame::ExecuteVote( void ) { + bool needRestart; + ClearVote(); + switch ( vote ) { + case VOTE_RESTART: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_TIMELIMIT: + si_timeLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_FRAGLIMIT: + si_fragLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_GAMETYPE: + cvarSystem->SetCVarString( "si_gametype", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_KICK: + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %s", voteValue.c_str() ) ); + break; + case VOTE_MAP: + cvarSystem->SetCVarString( "si_map", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_BUYING: + if ( gameLocal.GetCurrentDemoProtocol() == 69 ) + gameLocal.Error("MIN_PLAYERS vote in a Quake 4 1.2 recorded demo ( protocol 69 ) is not supported."); + cvarSystem->SetCVarString( "si_isBuyingEnabled", voteValue ); + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; +// RAVEN BEGIN +// shouchard: added capture limit + case VOTE_CAPTURELIMIT: + si_captureLimit.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + // todo: round limit here (if we add it) + case VOTE_AUTOBALANCE: + si_autobalance.SetInteger( atoi( voteValue ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + break; + case VOTE_MULTIFIELD: + ExecutePackedVote(); + break; +// RAVEN END + case VOTE_CONTROLTIME: + si_controlTime.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + case VOTE_NEXTMAP: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverNextMap\n" ); + break; + } +} + +/* +================ +idMultiplayerGame::CheckVote +================ +*/ +void idMultiplayerGame::CheckVote( void ) { + int numVoters, i; + + if ( vote == VOTE_NONE ) { + return; + } + + if ( voteExecTime ) { + if ( gameLocal.time > voteExecTime ) { + voteExecTime = 0; + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + ExecuteVote(); + vote = VOTE_NONE; + } + return; + } + + // count voting players + numVoters = 0; + + for ( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + if ( playerState[ i ].vote != PLAYER_VOTE_NONE ) { + numVoters++; + } + } + if ( !numVoters ) { + // abort + vote = VOTE_NONE; + ClientUpdateVote( VOTE_ABORTED, yesVotes, noVotes, currentVoteData ); + return; + } + if ( float(yesVotes) / numVoters > 0.5f ) { + ClientUpdateVote( VOTE_PASSED, yesVotes, noVotes, currentVoteData ); + voteExecTime = gameLocal.time + 2000; + return; + } + if ( gameLocal.time > voteTimeOut || float(noVotes) / numVoters >= 0.5f ) { + ClientUpdateVote( VOTE_FAILED, yesVotes, noVotes, currentVoteData ); + vote = VOTE_NONE; + return; + } +} + +// RAVEN BEGIN +// shouchard: multifield voting here + +/* +================ +idMultiplayerGame::ClientCallPackedVote + +The assumption is that the zero changes case has been handled above. +================ +*/ +void idMultiplayerGame::ClientCallPackedVote( const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( 0 != voteData.m_fieldFlags ); + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ServerCallPackedVote +================ +*/ +void idMultiplayerGame::ServerCallPackedVote( int clientNum, const idBitMsg &msg ) { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + assert( -1 != clientNum ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + // this is set to false if an invalid parameter is asked for-- time limit of -1, or frag limit of "jeff" or whatever. + // if it's a multivote, it may still be valid, but this value is only checked if there are no vote parameters changed. + bool validVote = true; + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + + // flags (short) + voteData.m_fieldFlags = msg.ReadShort(); + + // clear any unallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if ( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // kick + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + if ( voteData.m_kick == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_KICK ); + } + } + + // map (string) + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + char buffer[128]; + msg.ReadString( buffer, sizeof( buffer ) ); + voteData.m_map = buffer; + if ( 0 == idStr::Icmp( buffer, si_map.GetString() ) ) { + //gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map is the same as current map." ); + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + } + + // because of addon pk4's clients may submit votes for maps the server doesn't have - audit here + const idDeclEntityDef *mapDef = (const idDeclEntityDef*)declManager->FindType( DECL_MAPDEF, voteData.m_map.c_str(), false ); + if( !mapDef ) { + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map does not exist on the server" ); + } + } + + // gametype + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + const char *voteString = "DM"; + switch ( voteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + voteString = "Tourney"; + break; + case VOTE_GAMETYPE_CTF: + voteString = "CTF"; + break; + case VOTE_GAMETYPE_TDM: + voteString = "Team DM"; + break; + case VOTE_GAMETYPE_ARENA_CTF: + voteString = "Arena CTF"; + break; + case VOTE_GAMETYPE_DEADZONE: + voteString = "DeadZone"; + break; + case VOTE_GAMETYPE_DM: + default: + voteString = "DM"; + break; + } + if ( !idStr::Icmp( voteString, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_GAMETYPE ); + } + } + + // timelimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + if ( voteData.m_timeLimit < si_timeLimit.GetMinValue() || voteData.m_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %d\n", clientNum, voteData.m_timeLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + if ( voteData.m_timeLimit == si_timeLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + } + + // tourneylimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + if ( voteData.m_tourneyLimit < si_tourneyLimit.GetMinValue() || voteData.m_tourneyLimit > si_tourneyLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104261" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + if ( voteData.m_tourneyLimit == si_tourneyLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104260" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + } + + // capture limit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + if ( voteData.m_captureLimit < si_captureLimit.GetMinValue() || voteData.m_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: caplimit value out of range for vote: %d\n", clientNum, voteData.m_captureLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + if ( voteData.m_captureLimit == si_captureLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + } + + // fraglimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + if ( voteData.m_fragLimit < si_fragLimit.GetMinValue() || voteData.m_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %d\n", clientNum, voteData.m_fragLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + if ( voteData.m_fragLimit == si_fragLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + } + + // spectators +/* if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_SPECTATORS ) ) { + voteData.m_spectators = msg.ReadByte(); + if ( voteData.m_spectators == si_spectators.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104421" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_SPECTATORS ); + } + } */ + + // buying + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadShort(); + if ( voteData.m_buying == si_isBuyingEnabled.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + validVote = false; + voteData.m_buying &= ( ~VOTEFLAG_BUYING ); + } + } + + // autobalance teams + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + if ( voteData.m_teamBalance == si_autobalance.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TEAMBALANCE ); + } + } + + // control time + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + if ( voteData.m_controlTime == si_controlTime.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CONTROLTIME ); + } + } + + // check for no changes at all + if ( 0 == voteData.m_fieldFlags ) { + // If the vote was called empty, announce there were no valid changes. Otherwise, say nothing, there's already been a warning message. + if( validVote ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104400" ); + } + return; + } + + ServerStartPackedVote( clientNum, voteData ); + ClientStartPackedVote( clientNum, voteData ); +} + +/* +================ +idMultiplayerGame::ClientStartPackedVote +================ +*/ +void idMultiplayerGame::ClientStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + assert( 0 != voteData.m_fieldFlags ); + + if ( !gameLocal.isListenServer && !gameLocal.isClient ) { + return; + } + + // "%s has called a vote!" + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + + // display the vote called text on the hud and play an announcer sound + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->mphud ) { + gameLocal.GetLocalPlayer()->mphud->SetStateInt( "voteNotice", 1 ); + } + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + currentVoteData = voteData; + idUserInterface * mpHud = gameLocal.GetLocalPlayer()->mphud; + + // push data to the interface + if ( mpHud && mainGui ) { + int voteLineCount = 1; + int menuVoteLineCount = 0; + bool kickActive = false; + bool maxWindows = false; + idStr yesKey = common->KeysFromBinding("_impulse28"); + + mainGui->SetStateInt( "vote_going", 1 ); + + //dynamic vote yes/no box + mpHud->SetStateString( "voteNoticeText", va( common->GetLocalizedString( "#str_107242" ), yesKey.c_str(), common->KeysFromBinding("_impulse29") )); + + // kick should always be the highest one + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + // mpGui here, not mpHud + //mpHud->SetStateString( "vote_data0", va( common->GetLocalizedString( "#str_104422" ), player->GetName() ); + kickActive = true; + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + + const char *mapName = currentVoteData.m_map.c_str(); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = common->GetLocalizedString( "#str_110011" ); + switch( currentVoteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + gameTypeString = common->GetLocalizedString( "#str_110012" ); + break; + case VOTE_GAMETYPE_TDM: + gameTypeString = common->GetLocalizedString( "#str_110013" ); + break; + case VOTE_GAMETYPE_CTF: + gameTypeString = common->GetLocalizedString( "#str_110014" ); + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameTypeString = common->GetLocalizedString( "#str_110015" ); + break; + case VOTE_GAMETYPE_DEADZONE: + gameTypeString = "DeadZone"; + break; + case VOTE_GAMETYPE_DM: + default: + gameTypeString = common->GetLocalizedString( "#str_110011" ); + break; + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + + //jshep: max of 7 windows and the 7th is always "..." + if( maxWindows ) { + mpHud->SetStateString( "voteInfo_7", "..." ); + } + + mainGui->DeleteStateVar( va( "voteData_item_%d", menuVoteLineCount ) ); + mainGui->SetStateInt( "vote_going", 1 ); + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), yesVotes, noVotes ) ); + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerStartPackedVote +================ +*/ +void idMultiplayerGame::ServerStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( vote == VOTE_NONE ); + + if ( !gameLocal.isServer ) { + return; + } + + // #13705: clients passing a vote during server restart could abuse the voting system into passing the vote right away after the new map loads + if ( !playerState[ clientNum ].ingame ) { + common->Printf( "ignore vote called by client %d: not in game\n", clientNum ); + return; + } + + // setup + yesVotes = 1; + noVotes = 0; + vote = VOTE_MULTIFIELD; + currentVoteData = voteData; + voteTimeOut = gameLocal.time + 30000; // 30 seconds? might need to be longer because it requires fiddling with the GUI + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); +} + +/* +================ +idMultiplayerGame::ExecutePackedVote +================ +*/ +void idMultiplayerGame::ExecutePackedVote( void ) { + assert( VOTE_MULTIFIELD == vote ); + + if ( 0 == currentVoteData.m_fieldFlags ) { + return; + } + + bool needRestart = false; + bool needNextMap = false; + bool needRescanSI = false; + + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + si_isBuyingEnabled.SetInteger( currentVoteData.m_buying ); + needRescanSI = true; + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + si_autobalance.SetInteger( currentVoteData.m_teamBalance ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + si_controlTime.SetInteger( currentVoteData.m_controlTime ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + ShuffleTeams(); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d", currentVoteData.m_kick ) ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + si_map.SetString( currentVoteData.m_map.c_str() ); + needNextMap = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = NULL; + //jshepard: Currently the DM gametypes can be played on any map. The other gametypes require specially configured maps. + //if further gametypes are added that can be played on any map, don't set the "runPickMap" flag. + bool runPickMap = false; + switch ( currentVoteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + gameTypeString = "Tourney"; + runPickMap = true; + break; + case VOTE_GAMETYPE_TDM: + gameTypeString = "Team DM"; + runPickMap = true; + break; + case VOTE_GAMETYPE_CTF: + gameTypeString = "CTF"; + runPickMap = true; + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameTypeString = "Arena CTF"; + runPickMap = true; + break; + case VOTE_GAMETYPE_DEADZONE: + gameTypeString = "DeadZone"; + runPickMap = true; + break; + case VOTE_GAMETYPE_DM: + default: + gameTypeString = "DM"; + break; + } + assert( gameTypeString ); + si_gameType.SetString( gameTypeString ); + //jshepard: run a pick map here in case the packed vote is trying to pick the wrong map type. + //PickMap returns true if the map has changed (requiring a nextMap call) + if( runPickMap ) { + if( PickMap( gameTypeString ) ) { + needNextMap = true; + } else { + needRestart = true; + } + } else { + needRestart = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + si_timeLimit.SetInteger( currentVoteData.m_timeLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + si_tourneyLimit.SetInteger( currentVoteData.m_tourneyLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + si_captureLimit.SetInteger( currentVoteData.m_captureLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + si_fragLimit.SetInteger( currentVoteData.m_fragLimit ); + needRescanSI = true; + } + + if ( needRescanSI ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + if ( needNextMap ) { + gameLocal.sessionCommand = "nextMap"; + } + else if ( needRestart || gameLocal.NeedRestart() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } +} + +// RAVEN END + +/* +================ +idMultiplayerGame::ClientEndFrame +Called once each render frame (client) after all idGameLocal::ClientPredictionThink() calls +================ +*/ +void idMultiplayerGame::ClientEndFrame( void ) { + iconManager->UpdateIcons(); +} + +/* +================ +idMultiplayerGame::CommonRun +Called once each render frame (client)/once each game frame (server) +================ +*/ +void idMultiplayerGame::CommonRun( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + // twhitaker r282 + // TTimo: sure is a nasty way to do it + if ( gameLocal.isServer && ( gameLocal.serverInfo.GetInt( "net_serverDedicated" ) != cvarSystem->GetCVarInteger( "net_serverDedicated" ) ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + } + + if ( player && player->mphud ) { + // update icons + if ( gameLocal.isServer ) { + iconManager->UpdateIcons(); + } + +#ifdef _USE_VOICECHAT + float micLevel; + bool sending, testing; + + // jscott: enable the voice recording + testing = cvarSystem->GetCVarBool( "s_voiceChatTest" ); + sending = soundSystem->EnableRecording( !!( player->usercmd.buttons & BUTTON_VOICECHAT ), testing, micLevel ); + + if( mainGui ) { + mainGui->SetStateFloat( "s_micLevel", micLevel ); + mainGui->SetStateFloat( "s_micInputLevel", cvarSystem->GetCVarFloat( "s_micInputLevel" ) ); + } + +// RAVEN BEGIN +// shouchard: let the UI know about voicechat states + if ( !testing && sending ) { + player->mphud->HandleNamedEvent( "show_transmit_self" ); + } else { + player->mphud->HandleNamedEvent( "hide_transmit_self" ); + } + + if( player->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) { + int maxChannels = soundSystem->GetNumVoiceChannels(); + int clientNum = -1; + for (int channels = 0; channels < maxChannels; channels++ ) { + clientNum = soundSystem->GetCommClientNum( channels ); + if ( -1 != clientNum ) { + break; + } + } + + // Sanity check for network errors + assert( clientNum > -2 && clientNum < MAX_CLIENTS ); + + if ( clientNum > -1 && clientNum < MAX_CLIENTS ) { + idPlayer *from = ( idPlayer * )gameLocal.entities[clientNum]; + if( from ) { + player->mphud->SetStateString( "audio_name", from->GetUserInfo()->GetString( "ui_name" ) ); + player->mphud->HandleNamedEvent( "show_transmit" ); + } + } else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } + } + else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } +#endif // _USE_VOICECHAT +// RAVEN END + } +#ifdef _USE_VOICECHAT + // jscott: Send any new voice data + XmitVoiceData(); +#endif + + int oldRank = -1; + int oldLeadingTeam = -1; + bool wasTied = false; + int oldHighScore = idMath::INT_MIN; + + if( player && rankedPlayers.Num() ) { + if( gameLocal.gameType == GAME_DM ) { + oldRank = GetPlayerRank( player, wasTied ); + oldHighScore = rankedPlayers[ 0 ].Second(); + } else if( gameLocal.IsTeamGame() ) { + oldLeadingTeam = rankedTeams[ 0 ].First(); + wasTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + oldHighScore = rankedTeams[ 0 ].Second(); + } + } + + UpdatePlayerRanks(); + if ( gameLocal.IsTeamGame() ) { + UpdateTeamRanks(); + } + + if ( player && rankedPlayers.Num() && gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType == GAME_DM ) { + // leader message + bool isTied = false; + int newRank = GetPlayerRank( player, isTied ); + + if ( newRank == 0 ) { + if( ( oldRank != 0 || wasTied ) && !isTied ) { + // we've gained first place or the person we were tied with dropped out of first place + ScheduleAnnouncerSound( AS_DM_YOU_HAVE_TAKEN_LEAD, gameLocal.time ); + } else if( oldRank != 0 || (!wasTied && isTied) ) { + // we tied first place or we were in first and someone else tied + ScheduleAnnouncerSound( AS_DM_YOU_TIED_LEAD, gameLocal.time ); + } + } else if ( oldRank == 0 ) { + // we lost first place + ScheduleAnnouncerSound( AS_DM_YOU_LOST_LEAD, gameLocal.time ); + } + } else if ( gameLocal.IsTeamGame() ) { + int leadingTeam = rankedTeams[ 0 ].First(); + bool isTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + + if ( !wasTied && isTied ) { + if ( gameLocal.gameType != GAME_DEADZONE ) + ScheduleAnnouncerSound( AS_TEAM_TEAMS_TIED, gameLocal.time ); + } else if ( (leadingTeam != oldLeadingTeam && !isTied) || ( wasTied && !isTied ) ) { + ScheduleAnnouncerSound( leadingTeam ? AS_TEAM_STROGG_LEAD : AS_TEAM_MARINES_LEAD, gameLocal.time ); + } + + if ( gameLocal.gameType == GAME_TDM && oldHighScore != teamScore[ rankedTeams[ 0 ].First() ] && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + } + + if( ( gameLocal.gameType == GAME_DM ) && rankedPlayers[ 0 ].Second() != oldHighScore && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + // fraglimit warning + if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + + } + + if ( rankTextPlayer ) { + bool tied = false; + int rank = GetPlayerRank( rankTextPlayer, tied ); + (gameLocal.GetLocalPlayer())->GUIMainNotice( GetPlayerRankText( rank, tied, playerState[ rankTextPlayer->entityNumber ].fragCount ) ); + rankTextPlayer = NULL; + } + + PlayAnnouncerSounds(); + + + // asalmon: Need to refresh stats periodically if the player is looking at stats + if ( currentStatClient != -1 ) { + rvPlayerStat* clientStat = statManager->GetPlayerStat( currentStatClient ); + if ( ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) { + statManager->SelectStatWindow(currentStatClient, currentStatTeam); + } + } + + bool updateModels = false; + if( g_forceModel.IsModified() && !gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceModel.ClearModified(); + } + + if( g_forceMarineModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceMarineModel.ClearModified(); + } + + if( g_forceStroggModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceStroggModel.ClearModified(); + } + + if( updateModels ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + player->UpdateModelSetup(); + } + } + } + + // do this here rather than in idItem::Think() because clients don't run Think on ents outside their snap + if( g_simpleItems.IsModified() ) { + + for( int i = 0; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( !ent || !ent->IsType( idItem::GetClassType() ) || ent->IsType( rvItemCTFFlag::GetClassType() ) ) { + continue; + } + + idItem* item = (idItem*)ent; + + item->FreeModelDef(); + + renderEntity_t* renderEntity = item->GetRenderEntity(); + memset( renderEntity, 0, sizeof( renderEntity ) ); + + item->simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !item->IsType( rvItemCTFFlag::GetClassType() ); + + if( item->simpleItem ) { + renderEntity->shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 32.0f; + renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 32.0f; + renderEntity->hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity->callback = NULL; + renderEntity->numJoints = 0; + renderEntity->joints = NULL; + renderEntity->customSkin = 0; + renderEntity->noShadow = true; + renderEntity->noSelfShadow = true; + renderEntity->customShader = declManager->FindMaterial( item->spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity->referenceShader = 0; + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + renderEntity->axis = mat3_identity; + + item->StopEffect( "fx_idle", true ); + item->effectIdle = NULL; + item->SetAxis( mat3_identity ); + if( item->pickedUp ) { + item->FreeModelDef(); + item->UpdateVisuals(); + } + } else { + gameEdit->ParseSpawnArgsToRenderEntity( &item->spawnArgs, renderEntity ); + item->SetAxis( renderEntity->axis ); + + if ( item->spawnArgs.GetString( "fx_idle" ) ) { + item->UpdateModelTransform(); + item->effectIdle = item->PlayEffect( "fx_idle", renderEntity->origin, renderEntity->axis, true ); + } + + if( item->pickedUp && item->pickupSkin ) { + item->SetSkin( item->pickupSkin ); + } + } + if ( !item->spawnArgs.GetBool( "dropped" ) ) { + if ( item->spawnArgs.GetBool( "nodrop" ) ) { + item->GetPhysics()->PutToRest(); + } else { + item->Event_DropToFloor(); + } + } + } + + g_simpleItems.ClearModified(); + } +} + +/* +================ +idMultiplayerGame::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idMultiplayerGame::ClientRun( void ) { + CommonRun(); +} + + +/* +================ +idMultiplayerGame::ReportZoneControllingPlayer +================ +*/ +void idMultiplayerGame::ReportZoneControllingPlayer( idPlayer* player ) +{ + if ( !player ) + return; + + playerState[player->entityNumber].deadZoneScore += gameLocal.GetMSec(); + playerState[player->entityNumber].teamFragCount = playerState[player->entityNumber].deadZoneScore / 1000; + + float cashPerSecondForDeadZoneControl = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_deadZoneControlPerSecond", 0 ); +// player->GiveCash( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); + player->buyMenuCash += ( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); +} + + +/* +================ +idMultiplayerGame::ReportZoneController +================ +*/ +void idMultiplayerGame::ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger) +{ + powerupCount = pCount; + + idTrigger_Multi* zTrigger = 0; + if ( zoneTrigger && zoneTrigger->IsType( idTrigger_Multi::GetClassType() ) ) { + zTrigger = static_cast( zoneTrigger ); + } + + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() != GAMEON && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ) + { + // We're not playing right now. However, make sure all the clients are updated to know + // that the zone is neutral. + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_NONE); + ((riDZGameState*)gameState)->SetDZState(TEAM_STROGG, DZ_NONE); + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + return; + } + + if ( IsValidTeam(team) ) { + const int t = gameLocal.serverInfo.GetInt( "si_controlTime" ); + teamDeadZoneScore[team] += gameLocal.GetMSec() * powerupCount; + teamScore[team] = (int)((float)teamDeadZoneScore[team] / 1000.0f); + + // We have a winner! + if ( teamDeadZoneScore[team] > t*1000 ) { + // Set the shaders and lights back to neutral. + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(idVec3(0.75f, 0.75f, 0.75f)); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + + OnDeadZoneTeamVictory( team ); + + return; + } + } + + // Someone took control of a zone, report this to the + if ( situation == DZ_MARINES_TAKEN || situation == DZ_STROGG_TAKEN || situation == DZ_MARINE_TO_STROGG || + situation == DZ_STROGG_TO_MARINE || situation == DZ_MARINE_REGAIN || situation == DZ_STROGG_REGAIN ) { + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_NONE); // Clear hacked deadlock + ((riDZGameState*)gameState)->SetDZState(team, DZ_TAKEN); + } + + const int NOCHANGE = -2; + const int DEADLOCK = 3; + int controlSit = NOCHANGE; + switch ( situation ) { + case DZ_NONE : + controlSit = NOCHANGE; + break; + case DZ_MARINES_TAKEN : + controlSit = TEAM_MARINE; + break; + case DZ_MARINES_LOST : + controlSit = TEAM_NONE; + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_LOST); + break; + case DZ_STROGG_TAKEN : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_LOST : + controlSit = TEAM_NONE; + ((riDZGameState*)gameState)->SetDZState(TEAM_STROGG, DZ_LOST); + break; + case DZ_MARINE_TO_STROGG : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_TO_MARINE : + controlSit = TEAM_MARINE; + break; + case DZ_MARINE_DEADLOCK : + controlSit = DEADLOCK; + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_STROGG_DEADLOCK : + controlSit = DEADLOCK; + ((riDZGameState*)gameState)->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_MARINE_REGAIN : + controlSit = TEAM_MARINE; + break; + case DZ_STROGG_REGAIN : + controlSit = TEAM_STROGG; + break; + } + + if ( zTrigger && controlSit == NOCHANGE && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + // There's been no change in status, but keep these variables updated on the client + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = (int)targetEnt->GetRenderEntity()->shaderParms[7]; + } + } + + if ( controlSit == NOCHANGE || !zTrigger ) + return; // We're done. + + idVec3 colorVec; + int parmNum = 2; + if ( controlSit == TEAM_NONE ) { + colorVec = idVec3(0.75f, 0.75f, 0.75f); + parmNum = 2; + } + else if ( controlSit == TEAM_MARINE ) { + colorVec = idVec3(0.0f, 1.0f, 0.0f); + parmNum = 0; + } + else if ( controlSit == TEAM_STROGG ) { + colorVec = idVec3(1.0f, 0.5f, 0.0f); + parmNum = 1; + } + else if ( controlSit == DEADLOCK ) { + colorVec = idVec3(1.0f, 0.0f, 0.0f); + parmNum = 3; + } + + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(colorVec); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + ((riDZGameState*)gameState)->dzTriggerEnt = targetEnt->entityNumber; + ((riDZGameState*)gameState)->dzShaderParm = parmNum; + targetEnt->SetShaderParm(7, (float)parmNum); + } + } +} + + + +bool idMultiplayerGame::IsValidTeam(int team) +{ + if ( team == TEAM_MARINE || team == TEAM_STROGG ) + return true; + + return false; +} + + +void idMultiplayerGame::OnDeadZoneTeamVictory( int winningTeam ) +{ + OnBuyModeTeamVictory( winningTeam ); + + gameState->NewState( GAMEREVIEW ); +} + +void idMultiplayerGame::OnBuyModeTeamVictory( int winningTeam ) + { + if( !IsBuyingAllowedInTheCurrentGameMode() ) + return; + + float teamCashForWin = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeWin", 0 ); + float teamCashForTie = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeTie", 0 ); + float teamCashForLoss = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeLoss", 0 ); + + if( winningTeam == TEAM_NONE ) + { + GiveCashToTeam( TEAM_MARINE, teamCashForTie ); + GiveCashToTeam( TEAM_STROGG, teamCashForTie ); + } + else + { + int losingTeam = 1 - winningTeam; + GiveCashToTeam( winningTeam, teamCashForWin ); + GiveCashToTeam( losingTeam, teamCashForLoss ); + } + } + +/* +================ +idMultiplayerGame::Run +================ +*/ +void idMultiplayerGame::Run( void ) { + pureReady = true; + + assert( gameLocal.isMultiplayer && gameLocal.isServer && gameState ); + + CommonRun(); + + CheckVote(); + + CheckRespawns(); + + CheckSpecialLights( ); + +//RITUAL BEGIN + UpdateTeamPowerups(); +//RITUAL END + gameState->Run(); + + gameState->SendState(); + + // don't update the ping every frame to save bandwidth + if ( gameLocal.time > pingUpdateTime ) { + for ( int i = 0; i < gameLocal.numClients; i++ ) { + playerState[i].ping = networkSystem->ServerGetClientPing( i ); + } + pingUpdateTime = gameLocal.time + 1000; + } + + +} + +/* +================ +idMultiplayerGame::UpdateMainGui +================ +*/ +void idMultiplayerGame::UpdateMainGui( void ) { + int i; + mainGui->SetStateInt( "readyon", gameState->GetMPGameState() == WARMUP ? 1 : 0 ); + mainGui->SetStateInt( "readyoff", gameState->GetMPGameState() != WARMUP ? 1 : 0 ); + idStr strReady = cvarSystem->GetCVarString( "ui_ready" ); + if ( strReady.Icmp( "ready") == 0 ){ + strReady = common->GetLocalizedString( "#str_104248" ); + } else { + strReady = common->GetLocalizedString( "#str_104247" ); + } + mainGui->SetStateString( "ui_ready", strReady ); + mainGui->SetStateInt( "num_spec_players", unrankedPlayers.Num() ); + + mainGui->SetStateInt( "gametype", gameLocal.gameType ); + mainGui->SetStateBool( "s_useOpenAL", cvarSystem->GetCVarBool( "s_useOpenAL" ) ); + mainGui->SetStateBool( "s_loadOpenALFailed", cvarSystem->GetCVarBool( "s_loadOpenALFailed" ) ); + + idVec4 hitscanTint; + idStr hitScanValue = cvarSystem->GetCVarString( "ui_hitscanTint" ); + sscanf( hitScanValue.c_str(), "%f %f %f %f", &hitscanTint.x, &hitscanTint.y, &hitscanTint.z, &hitscanTint.w ); + mainGui->SetStateFloat( "ui_hitscanTint", hitscanTint.x ); + + // RAVEN BEGIN +// bdube: capture the flag + if ( gameLocal.IsTeamGame() ) { + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + mainGui->SetStateInt( "teamon", 1 ); + mainGui->SetStateInt( "teamoff", 0 ); + } else { + mainGui->SetStateInt( "teamon", 0 ); + mainGui->SetStateInt( "teamoff", 1 ); + } +// RAVEN END +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + mainGui->SetStateInt( "teamon", (gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + mainGui->SetStateInt( "teamoff", !(gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { +// RITUAL END + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + } + // setup vote + mainGui->SetStateInt( "voteon", ( vote != VOTE_NONE && !voted ) ? 1 : 0 ); + mainGui->SetStateInt( "voteoff", ( vote != VOTE_NONE && !voted ) ? 0 : 1 ); + // send the current serverinfo values + for ( i = 0; i < gameLocal.serverInfo.GetNumKeyVals(); i++ ) { + const idKeyValue *keyval = gameLocal.serverInfo.GetKeyVal( i ); + mainGui->SetStateString( keyval->GetKey(), keyval->GetValue() ); + } + mainGui->StateChanged( gameLocal.time ); +#if defined( __linux__ ) + // replacing the oh-so-useful s_reverse with sound backend prompt + mainGui->SetStateString( "driver_prompt", "1" ); +#else + mainGui->SetStateString( "driver_prompt", "0" ); +#endif + +//RAVEN BEGIN +// cnicholson: Add Custom Crosshair update + mainGui->SetStateString( "g_crosshairCustom", cvarSystem->GetCVarBool( "g_crosshairCustom" ) ? "1" : "0" ); +//RAVEN END + +// RAVEN BEGIN +// cnicholson: We need to setup the custom crosshair so it shows up the first time the player enters the MP settings menu. +// This block checks the current crosshair, and compares it against the list of crosshairs in player.def (mtr_crosshair*) under the +// player_marine_mp section. If it finds a match, it assigns the crosshair, otherwise, the first found crosshair is used. +#ifndef _XENON + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + +#ifndef _XENON + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { // Loop through all crosshairs listed in the def + if ( kv->GetValue() == currentCrosshair.c_str() ) { // Until a match is found + break; + } + kv = defCH->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL ); // If no natches are found, use the first one. + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + + +//asalmon: Set up a state var for match type of Xbox 360 +#ifdef _XENON + mainGui->SetStateBool("CustomHost", Live()->IsCustomHost()); + mainGui->SetStateInt("MatchType", Live()->GetMatchtype()); + mainGui->SetStateString("si_gametype", gameLocal.serverInfo.GetString("si_gametype")); + const char *damage; + if (gameLocal.serverInfo.GetBool("si_teamdamage")){ + damage = "Yes" ; + } + else { + damage = "No"; + } + mainGui->SetStateString("si_teamdamage", damage); + const char *shuffle; + if (gameLocal.serverInfo.GetBool("si_shuffleMaps")){ + shuffle = "Yes" ; + } + else { + shuffle = "No"; + } + mainGui->SetStateString("si_shuffleMaps", shuffle); + mainGui->SetStateString("si_fraglimit", gameLocal.serverInfo.GetString("si_fraglimit")); + mainGui->SetStateString("si_capturelimit", gameLocal.serverInfo.GetString("si_capturelimit")); + mainGui->SetStateString("si_timelimit", gameLocal.serverInfo.GetString("si_timelimit")); + +// mekberg: send spectating to the mainGui. + if ( gameLocal.GetLocalPlayer( ) ) { + mainGui->SetStateBool( "spectating", gameLocal.GetLocalPlayer( )->spectating ); + if( gameLocal.gameType == GAME_TOURNEY ) { + // additionally in tourney, indicate whether the player is voluntarily spectating + mainGui->SetStateBool( "tourneyspectating", !idStr::Icmp( gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_spectate" ), "Spectate" ) ); + } + } else { + mainGui->SetStateBool( "spectating", false ); + } +#endif +// RAVEN END + +} + +/* +================ +idMultiplayerGame::SetupBuyMenuItems +================ +*/ +void idMultiplayerGame::SetupBuyMenuItems() +{ + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) + return; + + buyMenu->SetStateInt( "buyStatus_shotgun", player->ItemBuyStatus( "weapon_shotgun" ) ); + buyMenu->SetStateInt( "buyStatus_hyperblaster", player->ItemBuyStatus( "weapon_hyperblaster" ) ); + buyMenu->SetStateInt( "buyStatus_grenadelauncher", player->ItemBuyStatus( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "buyStatus_nailgun", player->ItemBuyStatus( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "buyStatus_rocketlauncher", player->ItemBuyStatus( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "buyStatus_railgun", player->ItemBuyStatus( "weapon_railgun" ) ); + buyMenu->SetStateInt( "buyStatus_lightninggun", player->ItemBuyStatus( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "buyStatus_dmg", player->ItemBuyStatus( "weapon_dmg" ) ); + buyMenu->SetStateInt( "buyStatus_napalmgun", player->ItemBuyStatus( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "buyStatus_lightarmor", player->ItemBuyStatus( "item_armor_small" ) ); + buyMenu->SetStateInt( "buyStatus_heavyarmor", player->ItemBuyStatus( "item_armor_large" ) ); + buyMenu->SetStateInt( "buyStatus_ammorefill", player->ItemBuyStatus( "ammorefill" ) ); + + buyMenu->SetStateInt( "buyStatus_special0", player->ItemBuyStatus( "ammo_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special1", player->ItemBuyStatus( "health_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special2", player->ItemBuyStatus( "damage_boost" ) ); + + buyMenu->SetStateInt( "playerTeam", player->team ); + + if ( player->weapon ) + buyMenu->SetStateString( "ammoIcon", player->weapon->spawnArgs.GetString ( "inv_icon" ) ); + + buyMenu->SetStateInt( "player_weapon", player->GetCurrentWeapon() ); +} + +/* +================ +idMultiplayerGame::StartMenu +================ +*/ +idUserInterface* idMultiplayerGame::StartMenu( void ) { + if ( mainGui == NULL ) { + return NULL; + } + //if we're the server, allow access to the admin tab right away. Otherwise, make sure we don't have it. + if( gameLocal.isServer ) { + mainGui->SetStateInt( "password_valid", 1 ); + } else { + mainGui->SetStateInt( "password_valid", 0 ); + } + + int i, j; + + if ( currentMenu ) { + currentMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + } else { + if ( nextMenu >= 2 ) { + currentMenu = nextMenu; + } else { + // for default and explicit + currentMenu = 1; + } + cvarSystem->SetCVarBool( "ui_chat", true ); + } + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = true; + } + + nextMenu = 0; + if ( currentMenu == 1 ) { + UpdateMainGui(); + + // UpdateMainGui sets most things, but it doesn't set these because + // it'd be pointless and/or harmful to set them every frame (for various reasons) + // Currenty the gui doesn't update properly if they change anyway, so we'll leave it like this. + + // player kick data + for ( i = 0; i < 16; i++ ) { + kickVoteMapNames[ i ].Clear(); + kickVoteMap[ i ] = -1; + } + + idStr kickList; + j = 0; + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( kickList.Length() ) { + kickList += ";"; + } + kickList += va( "\"%d - %s\"", i, gameLocal.userInfo[ i ].GetString( "ui_name" ) ); + kickVoteMap[ j ] = i; +// RAVEN BEGIN +// shouchard: names for kick vote map + kickVoteMapNames[ j ] = gameLocal.userInfo[ i ].GetString( "ui_name" ); +// RAVEN END + j++; + } + } + mainGui->SetStateString( "kickChoices", kickList ); + + mainGui->SetStateString( "chattext", "" ); + mainGui->Activate( true, gameLocal.time ); + + idPlayer *localP = gameLocal.GetLocalPlayer(); +#ifndef _XENON + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp", false ); +#else + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp_ui", false ); +#endif + idStr buildValues, buildNames; + + int numModels = declManager->GetNumDecls( DECL_PLAYER_MODEL ); + for( int i = 0; i < numModels; i++ ) { + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->DeclByIndex( DECL_PLAYER_MODEL, i, false ); + + if( !playerModel ) { + continue; + } + + const char *resultValue = playerModel->GetName(); + + if ( !resultValue || !resultValue[0] ) { + continue; + } + + const char *team = playerModel->team.c_str(); + + if ( gameLocal.IsTeamGame() ) { + if ( team && localP && localP->team >= 0 && localP->team < TEAM_MAX && idStr::Icmp( teamNames[ localP->team ], team ) == 0 ) { + } else { + // doesn't match, so skip + continue; + } + } + + if ( i ) { + buildValues += ";"; + buildNames += ";"; + } + buildValues += resultValue; + + const char *resultName = common->GetLocalizedString( playerModel->description.c_str() ); + + if ( !resultName || !resultName[0] ) { + buildNames += resultValue; + } else { + buildNames += resultName; + } + } + + mainGui->SetStateString( "model_values", buildValues.c_str() ); + mainGui->SetStateString( "model_names", buildNames.c_str() ); + mainGui->SetStateBool( "player_model_updated", true ); + + const char *model; + if ( localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if( *model == '\0' ) { + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + if( *model == '\0' ) { + model = def->dict.GetString( "def_default_model" ); + } + } + + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + mainGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + mainGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + mainGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + mainGui->SetStateBool( "need_update", true ); + } + + if( gameLocal.GetLocalPlayer() ) { + cvarSystem->SetCVarString( "gui_ui_name", gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_name" ) ); + } + + return mainGui; + } else if ( currentMenu == 2 ) { + // the setup is done in MessageMode + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; + } + msgmodeGui->Activate( true, gameLocal.time ); + cvarSystem->SetCVarBool( "ui_chat", true ); + return msgmodeGui; + } else if ( currentMenu == 3 ) { + statSummary->Activate( true, gameLocal.time ); + statManager->SetupStatWindow( statSummary ); + UpdateScoreboard( statSummary ); + UpdateSummaryBoard( statSummary ); + statSummary->SetStateFloat( "ready", 0 ); + statSummary->StateChanged( gameLocal.time ); + + // Moved the announcer sound here. This way we can be sure the client has updated team score information by this point. + // #13576 #13544 causing double sounds because it's getting triggered twice at endgame ( from GameStateChanged and from ReceiveAllStats ) + // the move to here was for fixing some problem when running at the previous location ( GameStateChanged ) + // there are too many codepaths leading to various orders of ReceiveAllStats and GameStateChanged + // various attempts to flag the right call that should trigger the sound failed, so just using a timeout now + if ( gameLocal.time - lastVOAnnounce > 1000 ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() ) { + int winningTeam = GetScoreForTeam( TEAM_MARINE ) > GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + if( player->team == winningTeam ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } else if ( gameLocal.gameType != GAME_TOURNEY ) { + if( player->GetRank() == 0 ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } + lastVOAnnounce = gameLocal.time; + } + + return statSummary; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + //if( mpClientGameState.gameState.currentState == COUNTDOWN ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->SetStateInt( "price_shotgun", player->GetItemCost("weapon_shotgun") ); + buyMenu->SetStateInt( "price_hyperblaster", player->GetItemCost("weapon_hyperblaster") ); + buyMenu->SetStateInt( "price_grenadelauncher", player->GetItemCost( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "price_nailgun", player->GetItemCost( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "price_rocketlauncher", player->GetItemCost( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "price_railgun", player->GetItemCost( "weapon_railgun" ) ); + buyMenu->SetStateInt( "price_lightninggun", player->GetItemCost( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "price_dmg", player->GetItemCost( "weapon_dmg" ) ); + buyMenu->SetStateInt( "price_napalmgun", player->GetItemCost( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "price_lightarmor", player->GetItemCost( "item_armor_small" ) ); + buyMenu->SetStateInt( "price_heavyarmor", player->GetItemCost( "item_armor_large" ) ); + buyMenu->SetStateInt( "price_ammorefill", player->GetItemCost( "ammorefill" ) ); + + buyMenu->SetStateInt( "price_special0", player->GetItemCost( "ammo_regen" ) ); + buyMenu->SetStateInt( "price_special1", player->GetItemCost( "health_regen" ) ); + buyMenu->SetStateInt( "price_special2", player->GetItemCost( "damage_boost" ) ); + SetupBuyMenuItems(); + buyMenu->Activate(true, gameLocal.time); + return buyMenu; + //} +// RITUAL END + } + + return NULL; +} + +/* +================ +idMultiplayerGame::DisableMenu +================ +*/ +void idMultiplayerGame::DisableMenu( void ) { + if ( currentMenu == 1 ) { + mainGui->Activate( false, gameLocal.time ); + } else if ( currentMenu == 2 ) { + msgmodeGui->Activate( false, gameLocal.time ); + } else if( currentMenu == 3 ) { + statSummary->Activate( false, gameLocal.time ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + buyMenu->Activate( false, gameLocal.time ); +// RITUAL END + } + + // copy over name from temp cvar + if( currentMenu == 1 && idStr::Cmp( cvarSystem->GetCVarString( "gui_ui_name" ), cvarSystem->GetCVarString( "ui_name" ) ) ) { + cvarSystem->SetCVarString( "ui_name", cvarSystem->GetCVarString( "gui_ui_name" ) ); + } + + currentMenu = 0; + nextMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; +//RAVEN BEGIN +//asalmon: make the scoreboard on Xenon close +#ifdef _XENON + gameLocal.GetLocalPlayer()->scoreBoardOpen = false; +#endif +//RAVEN END + if( gameLocal.GetLocalPlayer()->mphud) { + gameLocal.GetLocalPlayer()->mphud->Activate( true, gameLocal.time ); + } + } + + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = -1; +} + +/* +================ +idMultiplayerGame::SetMapShot +================ +*/ +void idMultiplayerGame::SetMapShot( void ) { +#ifdef _XENON + // Should not be used + assert( 0 ); +#else + char screenshot[ MAX_STRING_CHARS ]; + int mapNum = mapList->GetSelection( NULL, 0 ); + const idDict *dict = NULL; + if ( mapNum >= 0 ) { + dict = fileSystem->GetMapDecl( mapNum ); + } + fileSystem->FindMapScreenshot( dict ? dict->GetString( "path" ) : "", screenshot, MAX_STRING_CHARS ); + mainGui->SetStateString( "current_levelshot", screenshot ); +// RAVEN BEGIN +// cnicholson: Need to sort the material screenshot so it doesn't overlap other things + const idMaterial *mat = declManager->FindMaterial( screenshot ); + mat->SetSort( SS_GUI ); +// RAVEN END +#endif +} + +/* +================ +LocalServerRedirect +Dummy local redirect for gui rcon functionality on a local server +================ +*/ +void LocalServerRedirect( const char* string ) { + gameLocal.mpGame.ReceiveRemoteConsoleOutput( string ); +} + +/* +================ +idMultiplayerGame::HandleGuiCommands +================ +*/ +const char* idMultiplayerGame::HandleGuiCommands( const char *_menuCommand ) { + idUserInterface *currentGui; +// RAVEN BEGIN +// shouchard: removed the code that deals with these variables + //const char *voteValue; + //int vote_clientNum; +// RAVEN END + int icmd; + idCmdArgs args; + + + + if ( !_menuCommand[ 0 ] ) { + common->Printf( "idMultiplayerGame::HandleGuiCommands: empty command\n" ); + return "continue"; + } + +#ifdef _XENON + if ( currentMenu == 0 && (session->GetActiveGUI() != scoreBoard) ) { +#else + if ( currentMenu == 0 ) { +#endif + return NULL; // this will tell session to not send us events/commands anymore + } + + if ( currentMenu == 1 ) { + currentGui = mainGui; + } +#ifdef _XENON + else if (session->GetActiveGUI() != scoreBoard) { + currentGui = msgmodeGui; + } else { + currentGui = scoreBoard; + } +#else + else if( currentMenu == 2 ) { + currentGui = msgmodeGui; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + currentGui = buyMenu; +// jmartel: make sure var is initialized (compiler complained) + } else if( currentMenu == 3 ) { + currentGui = statSummary; + } else { + gameLocal.Warning( "idMultiplayerGame::HandleGuiCommands() - Unknown current menu '%d'\n", currentMenu ); + currentGui = mainGui; + } +// RITUAL END +#endif + + + args.TokenizeString( _menuCommand, false ); + + for( icmd = 0; icmd < args.Argc(); ) { + const char *cmd = args.Argv( icmd++ ); + + if ( !idStr::Icmp( cmd, ";" ) ) { + continue; + } else if ( !idStr::Icmp( cmd, "inGameMenu" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + currentGui->SetStateString( "chat", chatHistory.c_str() ); + + if( gameLocal.GetLocalPlayer() ) { + currentGui->SetStateInt( "player_team", gameLocal.GetLocalPlayer()->team ); + } + + // mekberg: added + UpdateMPSettingsModel ( currentGui ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) ) { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107699" ) ); + } else { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107700" ) ); + } + } + + currentGui->SetStateBool( "useReady", gameLocal.serverInfo.GetBool( "si_useReady", "0" ) && gameState->GetMPGameState() == WARMUP ); + if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104247" ) ); + } else if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104248" ) ); + } else { + currentGui->SetStateString( "readyStatus", "" ); + } + + currentGui->SetStateBool( "si_allowVoting", gameLocal.serverInfo.GetBool( "si_allowVoting" ) ); + currentGui->SetStateBool( "si_allowVoice", gameLocal.serverInfo.GetBool( "si_voiceChat" ) ); + + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), false ); + } else { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), true ); + } + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "video" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + if ( idStr::Icmp( vcmd, "low" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 0 ); + } else if ( idStr::Icmp( vcmd, "medium" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 1 ); + } else if ( idStr::Icmp( vcmd, "high" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 2 ); + } else if ( idStr::Icmp( vcmd, "ultra" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 3 ); + } else if ( idStr::Icmp( vcmd, "recommended" ) == 0 ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" ); + } + +// RAVEN BEGIN +// mekberg: set the r_mode. + cvarSystem->SetCVarInteger( "r_aspectRatio", 0 ); + currentGui->SetStateInt( "r_aspectRatio", 0 ); + currentGui->HandleNamedEvent( "forceAspect0" ); + currentGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + currentGui->StateChanged( gameLocal.realClientTime ); + cvarSystem->SetCVarInteger( "r_mode", common->GetRModeForMachineSpec ( cvarSystem->GetCVarInteger( "com_machineSpec" ) ) ); + common->SetDesiredMachineSpec( cvarSystem->GetCVarInteger( "com_machineSpec" ) ); +// RAVEN END + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "execMachineSpec" ); + if ( idStr::Icmp( vcmd, "restart" ) == 0) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart\n" ); + } + + continue; + } else if ( !idStr::Icmp( cmd, "join" ) ) { + if ( args.Argc() - icmd >= 1 ) { + JoinTeam( args.Argv( icmd++ ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "quit" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "disconnect" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "close" ) ) { + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "spectate" ) ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "admin" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + idStr input( currentGui->State().GetString( "admin_console_input" ) ); + input.StripTrailing( "\n" ); + //jshepard: check to see if this is a server before using rcon! + if( gameLocal.isServer ) { + char redirectBuffer[ RCON_HISTORY_SIZE ]; + common->BeginRedirect( (char *)redirectBuffer, sizeof( redirectBuffer ), LocalServerRedirect ); + + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + ReceiveRemoteConsoleOutput( input.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "%s\n", input.c_str() ) ); + } + + common->EndRedirect(); + } else { + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon \"%s\"\n", input.c_str() ) ); + ReceiveRemoteConsoleOutput( input.c_str() ); + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "chatmessage" ) ) { + int mode = currentGui->State().GetInt( "messagemode" ); +// RAVEN BEGIN +// bdube: dont send chat message if there was no text specified + const char* text; + text = currentGui->State().GetString( "chattext" ); + if ( *text ) { + if ( mode ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "sayTeam \"%s\"", text ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text ) ); + } + } +// RAVEN BEGIN + currentGui->SetStateString( "chattext", "" ); + if ( currentMenu == 1 || currentMenu == 3 ) { + return "continue"; + } else { + DisableMenu(); + return NULL; + } + } else if ( !idStr::Icmp( cmd, "toggleReady" ) ) { + ToggleReady( ); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "play" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr snd = args.Argv( icmd++ ); + int channel = 1; + if ( snd.Length() == 1 ) { + channel = atoi( snd ); + snd = args.Argv( icmd++ ); + } + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, snd, channel ); + } + continue; + } else if ( !idStr::Icmp( cmd, "callVote" ) ) { +// RAVEN BEGIN +// shouchard: new functionality to match the new interface + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + // kick + int uiKickSelection = mainGui->State().GetInt( "playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + voteData.m_kick = kickVoteMap[ uiKickSelection ]; + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + // restart + if ( 0 != mainGui->State().GetInt( "vote_val2_sel" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } + + if ( 0 != mainGui->State().GetBool( "si_shuffleteams" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_SHUFFLE; + } + // map + int uiMapSelection = mainGui->State().GetInt( "mapList_sel_0" ); + if ( -1 != uiMapSelection ) { +// rjohnson: code commented out below would get the text friendly name of the map and not the file name + int mapNum = mainGui->State().GetInt( va( "mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + voteData.m_map = dict->GetString( "path" ); + voteData.m_fieldFlags |= VOTEFLAG_MAP; + } +// const char *mapName = mainGui->State().GetString( va( "mapList_item_%d", uiMapSelection ) ); +// if ( NULL != mapName && '\0' != mapName[0] ) { +// if ( mapFileName[ 0 ] ) { +// voteData.m_map = va( "mp/%s", mapName ); +// voteData.m_fieldFlags |= VOTEFLAG_MAP; +// } + } + // gametype + // todo: need a function for switching between gametype strings and values + int uiGameTypeInt = mainGui->GetStateInt( "currentGametype" ); + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gametype" ); + int serverGameTypeInt = GameTypeToVote( currentGameTypeString ); + + if ( uiGameTypeInt != serverGameTypeInt ) { + voteData.m_gameType = uiGameTypeInt; + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + } + // time limit + int uiTimeLimit = mainGui->GetStateInt( "timeLimit" ); + if ( uiTimeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + voteData.m_timeLimit = uiTimeLimit; + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + } + // autobalance + int uiBalanceTeams = mainGui->GetStateInt( "vote_val6_sel" ); + if ( uiBalanceTeams != gameLocal.serverInfo.GetInt( "si_autobalance" ) ) { + voteData.m_teamBalance = uiBalanceTeams; + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + } + // allow spectators + /* int uiAllowSpectators = mainGui->GetStateInt( "vote_val7_sel" ); + if ( uiAllowSpectators != gameLocal.serverInfo.GetInt( "si_spectators" ) ) { + voteData.m_spectators = uiAllowSpectators; + voteData.m_fieldFlags |= VOTEFLAG_SPECTATORS; + } */ + // minimum players + int uiBuying = mainGui->GetStateInt( "buying" ); + if ( uiBuying != gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + voteData.m_buying = uiBuying; + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + } + // roundlimit (tourney only) + int uiTourneyLimit = mainGui->GetStateInt( "tourneylimit" ); + if ( uiTourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + voteData.m_tourneyLimit = uiTourneyLimit; + voteData.m_fieldFlags |= VOTEFLAG_TOURNEYLIMIT; + } + // capturelimit (ctf only) + int uiCaptureLimit = mainGui->GetStateInt( "capturelimit" ); + if ( uiCaptureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + voteData.m_captureLimit = uiCaptureLimit; + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + } + // controltime (deadzone only) + int uiControlTime = mainGui->GetStateInt( "controlTime" ); + if ( uiControlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + voteData.m_controlTime = uiControlTime; + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + } + // fraglimit (DM & TDM only) + int uiFragLimit = mainGui->GetStateInt( "fraglimit" ); + if ( uiFragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + voteData.m_fragLimit = uiFragLimit; + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + } + DisableMenu(); + + // clear any disallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // this means we haven't changed anything + if ( 0 == voteData.m_fieldFlags ) { + //AddChatLine( common->GetLocalizedString( "#str_104400" ) ); + } else { + ClientCallPackedVote( voteData ); + } + /* + // sjh: original doom code here + vote_flags_t voteIndex = (vote_flags_t)mainGui->State().GetInt( "voteIndex" ); + if ( voteIndex == VOTE_MAP ) { + int mapNum = mapList->GetSelection( NULL, 0 ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + if ( dict ) { + ClientCallVote( VOTE_MAP, dict->GetString( "path" ) ); + } + } + } else { + voteValue = mainGui->State().GetString( "str_voteValue" ); + if ( voteIndex == VOTE_KICK ) { + vote_clientNum = kickVoteMap[ atoi( voteValue ) ]; + ClientCallVote( voteIndex, va( "%d", vote_clientNum ) ); + } else { + ClientCallVote( voteIndex, voteValue ); + } + } + */ + return NULL; + } else if ( !idStr::Icmp( cmd, "voteYes" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteNo" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "click_playerList" ) ) { + // push data into the name field + int sel = mainGui->GetStateInt( "playerList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "playerKick", "" ); + } else { + mainGui->SetStateString( "playerKick", kickVoteMapNames[ sel ] ); + } + continue; + } else if ( !idStr::Icmp( cmd, "click_voteMapList" ) ) { + int sel = mainGui->GetStateInt( "mapList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "mapName", "" ); + } else { + mainGui->SetStateString( "mapName", mainGui->GetStateString( va( "mapList_item_%d", sel ) ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "setVoteMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int numMapsAdded = 0; + int i; + int gameTypeInt = mainGui->GetStateInt( "currentGametype" ); + const char *gameType = NULL; + switch ( gameTypeInt ) { + case VOTE_GAMETYPE_DM: + gameType = "DM"; + break; + case VOTE_GAMETYPE_TOURNEY: + gameType = "Tourney"; + break; + case VOTE_GAMETYPE_TDM: + gameType = "Team DM"; + break; + case VOTE_GAMETYPE_CTF: + gameType = "CTF"; + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameType = "Arena CTF"; + break; + case VOTE_GAMETYPE_DEADZONE: + gameType = "DeadZone"; + break; + } + if ( NULL == gameType || 0 == *gameType || 0 == idStr::Icmp( gameType, "singleplayer" ) ) { + gameType = "DM"; + } + + idStr originalMapName = gameLocal.serverInfo.GetString( "si_map" ); + originalMapName.StripFileExtension(); + + bool foundOriginalMap = false; + int originalMapIndex = -1; + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + + if ( idStr::Icmp(dict->GetString( "path" ), originalMapName) == 0 ) { + foundOriginalMap = true; + originalMapIndex = numMapsAdded; + } + + mainGui->SetStateString( va( "mapList_item_%d", numMapsAdded), mapName ); + mainGui->SetStateInt( va( "mapList_item_%d_id", numMapsAdded), i ); + + numMapsAdded++; + } + } + mainGui->DeleteStateVar( va( "mapList_item_%d", numMapsAdded ) ); + + if ( !foundOriginalMap ) { + mainGui->SetStateInt( "mapList_sel_0", 0 ); + mainGui->SetStateString( "mapName", mainGui->GetStateString( "mapList_item_0" ) ); + } else { + mainGui->SetStateInt( "mapList_sel_0", originalMapIndex ); + mainGui->SetStateString( "mapName", mainGui->GetStateString( va( "mapList_item_%d", originalMapIndex ) ) ); + } + +#endif + continue; + } else if ( !idStr::Icmp( cmd, "setVoteData" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + // push data into the vote_ cvars so the UI can start at where we currently are + int players; + for ( players=0; playersSetStateString( va( "playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "playerList_item_%d", players ) ); + } + mainGui->SetStateString( "playerList_sel_0", "-1" ); + mainGui->SetStateString( "playerKick", "" ); + mainGui->SetStateInt( "vote_val2_sel", 0 ); + +// RAVEN BEGIN +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + mainGui->SetStateString( "mapName", mapName ); +// RAVEN END + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int numMapsAdded = 0; + int i; + const char *gameType = gameLocal.serverInfo.GetString( "si_gametype" ); // this will need to change for multi-votes + if ( NULL == gameType || 0 == *gameType || 0 == idStr::Icmp( gameType, "singleplayer" ) ) { + gameType = "DM"; + } +// RAVEN BEGIN +// jshepard: if gametype is DM, then we can play on DM, TeamDM, Tourney or CTF maps. So, all of them, basically. + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mainGui->SetStateString( va( "mapList_item_%d", numMapsAdded), mapName ); + numMapsAdded++; + } + } + mainGui->DeleteStateVar( va( "mapList_item_%d", numMapsAdded ) ); + mainGui->SetStateString( "mapList_sel_0", "-1" ); + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "currentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "vote_val6_sel", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "si_shuffleteams", 0 ); + mainGui->StateChanged( gameLocal.time ); + mainGui->HandleNamedEvent( "gametypeChange" ); +#endif + continue; + } else if ( !idStr::Icmp( cmd, "populateServerInfo" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + mainGui->SetStateString( "serverInfoList_item_0", va( "%s:\t%s", common->GetLocalizedString( "#str_107725" ), gameLocal.serverInfo.GetString( "si_name" ) ) ); + idStr serverAddress = networkSystem->GetServerAddress( ); + mainGui->SetStateString( "serverInfoList_item_1", va( "%s:\t%s", common->GetLocalizedString( "#str_107726" ), serverAddress.c_str() ) ); + mainGui->SetStateString( "serverInfoList_item_2", va( "%s:\t%s", common->GetLocalizedString( "#str_107727" ), LocalizeGametype() ) ); + + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } +// rhummer localized "map name" + mainGui->SetStateString( "serverInfoList_item_3", va( "%s\t%s", common->GetLocalizedString( "#str_107730" ), mapName ) ); + const char *gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetString( "si_captureLimit" ) ) ); + } + else if ( 0 == idStr::Icmp( gameType, "DM" ) || 0 == idStr::Icmp( gameType, "Team DM" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetString( "si_fragLimit" ) ) ); + } + mainGui->SetStateString( "serverInfoList_item_5", va( "%s:\t%s", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetString( "si_timeLimit" ) ) ); + mainGui->SetStateString( "serverInfoList_item_6", va( "%s:\t%s", common->GetLocalizedString( "#str_107662" ), gameLocal.serverInfo.GetString( "si_pure" ) ) ); + mainGui->SetStateString( "serverInfoList_item_7", va( "%s:\t%s", common->GetLocalizedString( "#str_107663" ), gameLocal.serverInfo.GetString( "si_maxPlayers" ) ) ); + mainGui->SetStateString( "serverInfoList_item_8", va( "%s:\t%s", common->GetLocalizedString( "#str_107664" ), gameLocal.serverInfo.GetString( "si_teamDamage" ) ) ); + mainGui->SetStateString( "serverInfoList_item_9", va( "%s:\t%s", common->GetLocalizedString( "#str_104254" ), gameLocal.serverInfo.GetString( "si_spectators" ) ) ); +#endif + continue; + // handler for the server admin tab (normal stuff) + } else if ( !idStr::Icmp( cmd, "checkAdminPass" )) { + //password has been added, so call the rcon verifypassword command + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon verifyRconPass" ); + continue; + + } else if ( !idStr::Icmp( cmd, "initServerAdmin" ) ) { + mainGui->SetStateInt( "admin_server_val1_sel", 0 ); // restart defaults to off + // maplist handled in initServerAdminMaplist; this needs to be called first + // to properly set the gametype since we read it back to show an appropriate list + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "admincurrentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "sa_timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "sa_tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "sa_capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "sa_controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "sa_autobalance", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "sa_buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "sa_fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "sa_shuffleteams", 0 ); +// mekberg: get the ban list if not server + if ( !gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + networkSystem->ClientSendReliableMessage( outMsg ); + } + + mainGui->StateChanged( gameLocal.time ); + + continue; + // handler for populating the map list; called both on open and on change gametype so it'll show the right maps + } else if ( !idStr::Icmp( cmd, "initServerAdminMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + // RAVEN BEGIN +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + mainGui->SetStateString( "sa_mapName", mapName ); +// RAVEN END + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int numMapsAdded = 0; + int i; + const char *gameType = "DM"; + + int gameTypeInt = mainGui->GetStateInt( "adminCurrentGametype" ); + + if ( VOTE_GAMETYPE_TOURNEY == gameTypeInt ) { + gameType = "Tourney"; + } else if ( VOTE_GAMETYPE_TDM == gameTypeInt ) { + gameType = "Team DM"; + } else if ( VOTE_GAMETYPE_CTF == gameTypeInt ) { + gameType = "CTF"; + } else if ( VOTE_GAMETYPE_ARENA_CTF == gameTypeInt ) { + gameType = "Arena CTF"; + } else if ( VOTE_GAMETYPE_DEADZONE == gameTypeInt ) { + gameType = "DeadZone"; + } else { + gameType = "DM"; + } + // sanity check + if ( NULL == gameType || 0 == *gameType || 0 == idStr::Icmp( gameType, "singleplayer" ) ) { + gameType = "DM"; + } + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if ( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mainGui->SetStateString( va( "sa_mapList_item_%d", numMapsAdded), mapName ); + mainGui->SetStateInt( va( "sa_mapList_item_%d_id", numMapsAdded), i ); + numMapsAdded++; + } + } + mainGui->DeleteStateVar( va( "sa_mapList_item_%d", numMapsAdded ) ); + mainGui->SetStateString( "sa_mapList_sel_0", "-1" ); +#endif + continue; + // handler for updating the current map in the name + } else if ( !idStr::Icmp( cmd, "serverAdminUpdateMap" ) ) { + int mapSelection = mainGui->GetStateInt( "sa_mapList_sel_0" ); + if ( -1 == mapSelection ) { + + idDecl *mapDecl = const_cast(declManager->FindType( DECL_MAPDEF, gameLocal.serverInfo.GetString( "si_map" ), false )); + if ( mapDecl ) { + idDeclEntityDef *mapDef = static_cast( mapDecl ); + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( mapDef->dict.GetString( "name" )) ); + } else { + mainGui->SetStateString( "sa_mapName", gameLocal.serverInfo.GetString( "si_map" ) ); + } + + } else { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", mapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( dict->GetString( "name" )) ); + } + } + continue; + // handler for initializing the player list on the admin player tab + } else if ( !idStr::Icmp( cmd, "initServerAdminPlayer" ) ) { + int players; + for ( players=0; playersSetStateString( va( "sa_playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", players ) ); + //common->Printf( "DELETING at slot %d\n", players ); + } + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + continue; + // handler for actually changing something on the server admin tab + } else if ( !idStr::Icmp( cmd, "handleServerAdmin" ) ) { + // read in a bunch of data, pack it into the appropriate structure + serverAdminData_t data; + memset( &data, 0, sizeof( data ) ); + data.restartMap = 0 != mainGui->GetStateInt( "admin_server_val1_sel" ); + // map list here + int uiMapSelection = mainGui->State().GetInt( "sa_mapList_sel_0" ); + if (-1 != uiMapSelection ) { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + data.mapName = common->GetLocalizedString( dict->GetString( "path" )); + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + + switch ( mainGui->GetStateInt( "admincurrentGametype" ) ) { + case VOTE_GAMETYPE_DM: + data.gameType = GAME_DM; + break; + case VOTE_GAMETYPE_TOURNEY: + data.gameType = GAME_TOURNEY; + break; + case VOTE_GAMETYPE_TDM: + data.gameType = GAME_TDM; + break; + case VOTE_GAMETYPE_CTF: + data.gameType = GAME_CTF; + break; + case VOTE_GAMETYPE_ARENA_CTF: + data.gameType = GAME_ARENA_CTF; + break; + case VOTE_GAMETYPE_DEADZONE: + data.gameType = GAME_DEADZONE; + } + data.captureLimit = mainGui->GetStateInt( "sa_captureLimit" ); + data.fragLimit = mainGui->GetStateInt( "sa_fragLimit" ); + data.tourneyLimit = mainGui->GetStateInt( "sa_tourneylimit" ); + data.timeLimit = mainGui->GetStateInt( "sa_timeLimit" ); + data.buying = mainGui->GetStateInt( "sa_buying" ); + data.autoBalance = 0 != mainGui->GetStateInt( "sa_autobalance" ); + data.buying = 0 != mainGui->GetStateInt( "sa_buying" ); + data.controlTime = mainGui->GetStateInt( "sa_controlTime" ); + data.shuffleTeams = 0 != mainGui->GetStateInt( "sa_shuffleteams" ); + + // make the call to change the server data + if ( gameLocal.mpGame.HandleServerAdminCommands( data ) ) { + DisableMenu(); + return NULL; + } + continue; + // handler for the kick button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminKick" ) ) { + int uiKickSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + HandleServerAdminKickPlayer( kickVoteMap[ uiKickSelection ] ); + DisableMenu(); + return NULL; + } + //common->Printf( "HANDLE SERVER ADMIN KICK!\n" ); + continue; + // handler for the ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiBanSelection ) { + HandleServerAdminBanPlayer( kickVoteMap[ uiBanSelection ] ); + DisableMenu(); + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + return NULL; + } + continue; + // handler for the remove ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminRemoveBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN REMOVE BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_banList_sel_0" ); + if ( -1 != uiBanSelection ) { + idStr guid = &mainGui->GetStateString( va( "sa_banList_item_%d", uiBanSelection ) )[ 4 ]; + guid = guid.ReplaceChar( '\t', '\0' ); + guid = &guid.c_str()[ strlen( guid.c_str() ) + 1 ]; + HandleServerAdminRemoveBan( guid.c_str() ); + DisableMenu(); + return NULL; + } + continue; + // handler for the switch teams button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminSwitchTeams" ) ) { + if ( gameLocal.IsTeamGame() ) { + int uiSwitchSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiSwitchSelection ) { + HandleServerAdminForceTeamSwitch( kickVoteMap[ uiSwitchSelection ] ); + DisableMenu(); + return NULL; + } + } + continue; + // handler for the show ban list button of the server admin gui + } else if ( !idStr::Icmp( cmd, "populateBanList" ) ) { + gameLocal.PopulateBanList( mainGui ); + continue; +// RAVEN END + } else if ( !idStr::Icmp( cmd, "voteyes" ) ) { + CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteno" ) ) { + CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "bind" ) ) { + if ( args.Argc() - icmd >= 2 ) { + idStr key = args.Argv( icmd++ ); + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "bindunbindtwo \"%s\" \"%s\"", key.c_str(), bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "clearbind" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "unbind \"%s\"", bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "MAPScan" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" ); + if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) { + gametype = "DM"; + } + + int i, num; + idStr si_map = gameLocal.serverInfo.GetString("si_map"); + const idDict *dict; + + mapList->Clear(); + mapList->SetSelection( -1 ); + num = fileSystem->GetNumMaps(); + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if ( dict ) { + // any MP gametype supported + bool isMP = false; + int igt = GAME_SP + 1; + while ( si_gameTypeArgs[ igt ] ) { + if ( dict->GetBool( si_gameTypeArgs[ igt ] ) ) { + isMP = true; + break; + } + igt++; + } + if ( isMP ) { + const char *mapName = dict->GetString( "name" ); + if ( mapName[0] == '\0' ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mapList->Add( i, mapName ); + if ( !si_map.Icmp( dict->GetString( "path" ) ) ) { + mapList->SetSelection( mapList->Num() - 1 ); + } + } + } + } + // set the current level shot + SetMapShot( ); +#endif + return "continue"; + } else if ( !idStr::Icmp( cmd, "click_maplist" ) ) { + SetMapShot( ); + return "continue"; + } else if ( !idStr::Icmp( cmd, "sm_select_player" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + int index = atoi( vcmd.c_str() ); + if( index > 0 && index < MAX_CLIENTS && statSummary && currentMenu == 3 ) { + statManager->UpdateEndGameHud( statSummary, index - 1 ); + } + return "continue"; + } else if ( !idStr::Icmp( cmd, "update_model" ) ) { + UpdateMPSettingsModel( currentGui ); + continue; + } else if( !idStr::Icmp( cmd, "ingameStats" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + // setup the player list + statManager->SetupStatWindow( currentGui ); + } else if( !igArg.Icmp( "spectator" ) ) { + int currentSel = currentGui->State().GetInt( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MAX ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MAX; + } else if( !igArg.Icmp( "dm" ) ) { + int currentSel = currentGui->State().GetInt( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, 0 ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = 0; + } else if( !igArg.Icmp( "strogg" ) ) { + int currentSel = currentGui->State().GetInt( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_STROGG ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_STROGG; + } else if( !igArg.Icmp( "marine" ) ) { + int currentSel = currentGui->State().GetInt( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MARINE ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MARINE; + } + } + continue; + } else if( !idStr::Icmp( cmd, "mainMenu" ) ) { + DisableMenu(); + static idStr menuCmd; + menuCmd.Clear(); // cnicholson: In order to avoid repeated eventnames from screwing up the menu system, clear it. + menuCmd.Append( "main" ); + const char* eventName = ""; + if( args.Argc() - icmd >= 1 ) { + eventName = args.Argv( icmd++ ); + menuCmd.Append( " " ); + menuCmd.Append( eventName ); + } + return menuCmd.c_str(); + } +// RAVEN BEGIN +// cnicholson: The menu calls this prior to entering multiplayer settings. What it does is to check the current crosshair, and compare it +// agasint the list of crosshairs in player.def under the player_marine_mp section. If it finds a match, it assigns the +// crosshair to the next one in the list. If there isn't one, or if its the end of the list, the first found crosshair is used. + else if ( !idStr::Icmp( cmd, "chooseCrosshair" ) ) { +#ifndef _XENON + +#ifndef _XENON + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = def->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { + if ( kv->GetValue() == currentCrosshair.c_str() ) { + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + break; + } + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = def->dict.MatchPrefix("mtr_crosshair", NULL ); + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + } +// RAVEN END + else if( !idStr::Icmp( cmd, "friend" ) ) { + // we friend/unfriend from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + if( ( client < 0 || client >= MAX_CLIENTS ) || !gameLocal.GetLocalPlayer() ) { + continue; + } + + // un-mark this client as a friend + if( gameLocal.GetLocalPlayer()->IsFriend( client ) ) { + networkSystem->RemoveFriend( client ); + } else { + networkSystem->AddFriend( client ); + } + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + continue; + } else if( !idStr::Icmp( cmd, "mute" ) ) { + // we mute/unmute from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + + ClientVoiceMute( client, !gameLocal.GetLocalPlayer()->IsPlayerMuted( client ) ); + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + + continue; + } +//RAVEN BEGIN +//asalmon: pass through some commands that need to be handled in the main menu handle function + else if(strstr( cmd, "LiveInviteAccept" ) == cmd){ +#ifdef _XENON + Live()->SetInvite(); +#endif + } + else if ((strstr( cmd, "FilterMPMapList" ) == cmd) + || (strstr( cmd, "AddMapLive" ) == cmd) + || (strstr( cmd, "RemoveMapLive" ) == cmd) + ) { + static idStr menuCmd; + menuCmd.Clear(); + menuCmd.Append( cmd ); + return menuCmd.c_str(); + } else if( !idStr::Icmp( cmd, "toggleTourney" ) ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } + continue; + } +//RAVEN END + common->Printf( "idMultiplayerGame::HandleGuiCommands: '%s' unknown\n", cmd ); + + } + return "continue"; +} + +/* +=============== +idMultiplayerGame::SetShaderParms +=============== +*/ +void idMultiplayerGame::SetShaderParms( renderView_t *view ) { + if ( gameLocal.IsFlagGameType() ) { + view->shaderParms[ 1 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_MARINE ) != FS_AT_BASE ); + view->shaderParms[ 2 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_STROGG ) != FS_AT_BASE ); + } +} + +/* +================ +idMultiplayerGame::Draw +server demo: clientNum == MAX_CLIENTS +================ +*/ +bool idMultiplayerGame::Draw( int clientNum ) { + idPlayer *player, *viewPlayer; + idUserInterface *hud = NULL; + + if ( clientNum == MAX_CLIENTS ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING ); + clientNum = gameLocal.GetDemoFollowClient(); + hud = gameLocal.GetDemoHud(); + if ( clientNum == -1 ) { + gameLocal.freeView.Draw(); + return true; + } + } + + player = viewPlayer = static_cast( gameLocal.entities[ clientNum ] ); + + if ( player == NULL ) { + return false; + } + + if ( player->spectating ) { + viewPlayer = static_cast( gameLocal.entities[ player->spectator ] ); + if ( viewPlayer == NULL ) { + return false; + } + } + + if ( !viewPlayer->GetRenderView() ) { + return false; + } + + SetShaderParms( viewPlayer->GetRenderView() ); + + // use the hud of the local player + if ( !hud ) { + hud = player->hud; + } + viewPlayer->playerView.RenderPlayerView( hud ); + + // allow force scoreboard to overwrite a fullscreen menu + if ( currentMenu ) { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( !bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 1, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = true; + } +#endif + if ( player->wantSpectate ) { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104249" ) ); + } else { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104250" ) ); + } + // if we died, isChatting is cleared, so re-set our chatting cvar + if ( gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->isChatting && !gameLocal.GetLocalPlayer()->pfl.dead ) { + cvarSystem->SetCVarBool( "ui_chat", true ); + cvarSystem->SetModifiedFlags( CVAR_USERINFO ); // force update + } + if ( currentMenu == 1 ) { + UpdateMainGui(); + mainGui->Redraw( gameLocal.time ); + } else if( currentMenu == 2 ) { + msgmodeGui->Redraw( gameLocal.time ); + } else if( currentMenu == 3 ) { + DrawStatSummary(); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + SetupBuyMenuItems(); + player->UpdateHudStats( buyMenu ); + buyMenu->HandleNamedEvent( "update_buymenu" ); + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->Redraw(gameLocal.time); +// RITUAL END + } + } else { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = false; + } +#endif + DrawScoreBoard( player ); + } + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud(); +// RAVEN END + return true; +} + +/* +================ +idMultiplayerGame::UpdateHud +================ +*/ +void idMultiplayerGame::UpdateHud( idUserInterface* _mphud ) { + idPlayer *localPlayer; + + if ( !_mphud ) { + return; + } + + // server demos don't have a true local player, but need one for hud updates + localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + +//RAVEN BEGIN +//asalmon: Turn on/off the lag icon so that clients know that they are losing connection + if ( networkSystem->ClientGetTimeSinceLastPacket() > 0 && ( networkSystem->ClientGetTimeSinceLastPacket() > cvarSystem->GetCVarInteger("net_clientServerTimeout")*500 ) ) { + _mphud->SetStateBool("IsLagged", true); + } + else{ + _mphud->SetStateBool("IsLagged", false); + } +//RAVEN END + + _mphud->SetStateInt( "marine_score", teamScore[ TEAM_MARINE ] ); + _mphud->SetStateInt( "strogg_score", teamScore[ TEAM_STROGG ] ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + + // Always show GameTime() for WARMUP and COUNTDOWN. + mpGameState_t state = gameState->GetMPGameState(); + _mphud->SetStateString( "timeleft", GameTime() ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + /// Set "credits" gui element + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + int cash = 0; + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + + idPlayer* specPlayer = NULL; + if ( localPlayer->spectating ) + specPlayer = gameLocal.GetClientByNum( localPlayer->spectator ); + + if ( specPlayer ) + cash = (int)specPlayer->buyMenuCash; + else + cash = (int)localPlayer->buyMenuCash; + + if( localPlayer->CanBuy() ) { + _mphud->SetStateString("credits", va("%s %d %s", common->GetLocalizedString( "#str_122015" ), + cash, common->GetLocalizedString( "#str_122016" ))); + } + else { + _mphud->SetStateString("credits", va("%s %d", common->GetLocalizedString( "#str_122015" ), cash)); + } + } + else + { + _mphud->SetStateString("credits", ""); + } +// RITUAL END + + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + bool inCountdownState = (state == COUNTDOWN); + if( gameLocal.gameType == GAME_TOURNEY ) { + inNonTimedState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_SUDDEN_DEATH); + inCountdownState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_WARMUP); + } + _mphud->SetStateBool( "infinity", ( !timeLimit && !inCountdownState ) || inNonTimedState ); + + if( gameLocal.gameType == GAME_DM ) { + if( rankedPlayers.Num() ) { + _mphud->SetStateString( "player1_name", rankedPlayers[ 0 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player1_score", va( "%d", GetScore( rankedPlayers[ 0 ].First() ) ) ); + _mphud->SetStateString( "player1_rank", "1." ); + + // if we're in the lead or spectating, show the person in 2nd + if( ( (rankedPlayers[ 0 ].First() == localPlayer) || (localPlayer->spectating) ) && rankedPlayers.Num() > 1 ) { + _mphud->SetStateString( "player2_name", rankedPlayers[ 1 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( rankedPlayers[ 1 ].First() ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", rankedPlayers[ 1 ].First()->GetRank() + 1 ) ); + } else if( rankedPlayers[ 0 ].First() != localPlayer && !localPlayer->spectating ) { + // otherwise, show our score + _mphud->SetStateString( "player2_name", localPlayer->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( localPlayer ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", localPlayer->GetRank() + 1 ) ); + } else { + // no person to place in 2nd + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } else { + _mphud->SetStateString( "player1_name", "" ); + _mphud->SetStateString( "player1_score", "" ); + _mphud->SetStateString( "player1_rank", "" ); + + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } + + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DEADZONE ) { + + static int lastMarineScore = teamScore[ TEAM_MARINE ]; + static int lastStroggScore = teamScore[ TEAM_STROGG ]; + int marineScore = teamScore[ TEAM_MARINE ]; + int stroggScore = teamScore[ TEAM_STROGG ]; + const float asymptoticAverageWeight = 0.95f; + + /// Check if Marines have scored since last frame + if( marineScore != lastMarineScore ) + { + /// Pulse the bar's color + marineScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_MARINE ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + marineScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Check if Strogg have scored since last frame + if( stroggScore != lastStroggScore ) + { + /// Pulse the bar's color + stroggScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_STROGG ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + stroggScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Set "gameStatus" gui element + _mphud->SetStateString("gameStatus", "" ); + + _mphud->SetStateFloat( "marine_pulse_amount", marineScoreBarPulseAmount ); + _mphud->SetStateFloat( "strogg_pulse_amount", stroggScoreBarPulseAmount ); + + lastMarineScore = teamScore[ TEAM_MARINE ]; + lastStroggScore = teamScore[ TEAM_STROGG ]; + } + // RITUAL END + + if( gameLocal.gameType == GAME_TOURNEY && localPlayer->GetArena() == MAX_ARENAS ) { + int numWaitingArenaPlayers = 0; + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() && rankedPlayers[ i ].First()->GetArena() == MAX_ARENAS ) { + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers++ ), rankedPlayers[ i ].First()->GetUserInfo()->GetString( "ui_name" ) ); + } + } + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers ), "" ); + _mphud->SetStateBool( "waitroom", true ); + _mphud->SetStateInt( "num_waitroom_players", numWaitingArenaPlayers ); + } else { + _mphud->SetStateBool( "waitroom", false ); + } + + idStr spectateText0; + idStr spectateText1; + idStr spectateText2; + + if( gameLocal.gameType == GAME_TOURNEY ) { + // line 1 - why we aren't playing + if( localPlayer->wantSpectate ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + } else { + rvTourneyArena& currentArena = ((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ); + if( gameState->GetMPGameState() == WARMUP ) { + // grab the reason we aren't playing yet + AllPlayersReady( &spectateText0 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText0 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + spectateText0 = common->GetLocalizedString( "#str_107687" ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ADVANCED ) { + spectateText0 = common->GetLocalizedString( "#str_107688" ); + } else if( ((rvTourneyGameState*)gameState)->HasBye( localPlayer ) ) { + spectateText0 = common->GetLocalizedString( "#str_107709" ); + } else if( currentArena.IsPlaying( localPlayer ) ) { + spectateText0 = va( "%s %d; %s", common->GetLocalizedString( "#str_107716" ), localPlayer->GetArena() + 1, ((rvTourneyGameState*)gameState)->GetRoundDescription() ); + } else if( localPlayer->spectating ) { + // this should only happen if the player was spectating at start of round, but then decides + // to join the tourney + spectateText0 = common->GetLocalizedString( "#str_107684" ); + } + } + + // line 2 - will or wont be seeded, how to cycle + // line 3 - how to enter waiting room + if( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN ) { + if( localPlayer->wantSpectate ) { + spectateText1 = common->GetLocalizedString( "#str_107685" ); + spectateText2 = common->GetLocalizedString( "#str_107695" ); + } else { + spectateText1 = common->GetLocalizedString( "#str_107684" ); + spectateText2 = common->GetLocalizedString( "#str_107694" ); + } + } else if( localPlayer->spectating ) { + if( localPlayer->GetArena() == MAX_ARENAS ) { + spectateText1 = common->GetLocalizedString( "#str_107686" ); + } else { + spectateText1 = va( common->GetLocalizedString( "#str_107670" ), common->KeysFromBinding( "_impulse14" ), common->KeysFromBinding( "_impulse15" ) ); + } + } + } else { + // non-tourney spectate text + if( localPlayer->spectating ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + + // spectating instructions + if( localPlayer->spectator != localPlayer->entityNumber ) { + //cycle & exit follow + spectateText1 = va( common->GetLocalizedString( "#str_107698" ), common->KeysFromBinding( "_attack" ), common->KeysFromBinding( "_moveup" ) ); + } else { + //start follow + spectateText1 = va( common->GetLocalizedString( "#str_108024" ), common->KeysFromBinding( "_attack" ) ); + } + + } + + if( gameState->GetMPGameState() == WARMUP ) { + AllPlayersReady( &spectateText1 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText1 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } + } + + _mphud->SetStateString( "spectatetext0", spectateText0 ); + _mphud->SetStateString( "spectatetext1", spectateText1 ); + _mphud->SetStateString( "spectatetext2", spectateText2 ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + _mphud->StateChanged( gameLocal.time ); + + statManager->UpdateInGameHud( _mphud, ( localPlayer->usercmd.buttons & BUTTON_INGAMESTATS ) != 0 ); + + //update awards + if ( gameLocal.isClient || gameLocal.isListenServer) { + statManager->CheckAwardQueue(); + } +} + +/* +================ +idMultiplayerGame::DrawScoreBoard +================ +*/ +void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) { + if ( player->scoreBoardOpen ) { + if ( !playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( true, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = true; + player->disableHud = true; + } + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameState)->UpdateTourneyBrackets(); + } + UpdateScoreboard( scoreBoard ); + } else { + if ( playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( false, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = false; + player->disableHud = false; + } + } +} + +/* +=============== +idMultiplayerGame::AddChatLine +=============== +*/ +void idMultiplayerGame::AddChatLine( const char *fmt, ... ) { + idStr temp; + va_list argptr; + +// mekberg: chat changes. + wrapInfo_t wrapInfo; + idStr wrap1; + idStr wrap2; + + va_start( argptr, fmt ); + vsprintf( temp, fmt, argptr ); + va_end( argptr ); + + temp.StripTrailingOnce("\n"); + + // this isn't a good way to color informational lines.... + if( temp.Find( ":" ) > 0 && temp.Find( ":" ) < temp.Length() - 1 ) { + gameLocal.Printf( "%s^0^2%s\n", temp.Left( temp.Find( ":" ) + 1 ).c_str(), temp.Right( temp.Length() - temp.Find( ":" ) - 1).c_str() ); + } else { + gameLocal.Printf( "%s\n", temp.c_str() ); + } + + // bdube: new chat interraction with hud + if ( gameLocal.GetLocalPlayer() != NULL && gameLocal.GetLocalPlayer()->mphud ) { + wrap1 = temp; + wrap2 = temp; + do { + memset( &wrapInfo, -1, sizeof ( wrapInfo_t ) ); + gameLocal.GetLocalPlayer( )->mphud->GetMaxTextIndex( "history1", wrap1.c_str( ), wrapInfo ); + + // If we have a whitespace near the end. Otherwise the user could enter a giant word. + if ( wrapInfo.lastWhitespace != -1 && float( wrapInfo.lastWhitespace ) / float( wrapInfo.maxIndex ) > .75 ) { + wrap2 = wrap1.Left( wrapInfo.lastWhitespace++ ); + + // Just text wrap, no word wrap. + } else if ( wrapInfo.maxIndex != -1 ) { + wrap2 = wrap1.Left( wrapInfo.maxIndex ); + + // We fit in less than a line. + } else { + wrap2 = wrap1; + } + + // Recalc the base string. + wrap1 = wrap1.Right( wrap1.Length( ) - wrap2.Length( ) ); + + // Push to gui. + gameLocal.GetLocalPlayer( )->mphud->SetStateString( "chattext", wrap2.c_str( ) ); + gameLocal.GetLocalPlayer( )->mphud->HandleNamedEvent( "addchatline" ); + } while ( wrapInfo.maxIndex != -1 ); + } + + if( chatHistory.Length() + temp.Length() > CHAT_HISTORY_SIZE ) { + int removeLength = chatHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + chatHistory.Empty(); + } else { + while( (chatHistory.Length() - removeLength) + temp.Length() > CHAT_HISTORY_SIZE ) { + removeLength = chatHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + chatHistory.Empty(); + break; + } + } + } + chatHistory = chatHistory.Right( chatHistory.Length() - removeLength ); + } + + chatHistory.Append( temp ); + chatHistory.Append( '\n' ); + + if( mainGui ) { + mainGui->SetStateString( "chat", chatHistory.c_str() ); + } + if( statSummary ) { + statSummary->SetStateString( "chat", chatHistory.c_str() ); + } + + // play chat sound + char* chatSound = "snd_chat"; + if( gameLocal.GetLocalPlayer() ) { + // not a great way to detect teams + if( gameLocal.IsTeamGame() ) { + int i = temp.Find( gameLocal.GetLocalPlayer()->team ? "Strogg" : "Marine", false ); + int firstColon = temp.Find( ":" ); + if( firstColon >= 0 && i < firstColon && i >= 1 && temp[ i - 6 ] == '(' ) { + chatSound = "snd_teamchat"; + } + } + + gameLocal.GetLocalPlayer()->StartSound( chatSound, SND_CHANNEL_ANY, 0, false, NULL ); + } + +} + +void idMultiplayerGame::DrawStatSummary( void ) { + if ( !statSummary->GetStateFloat( "ready" ) ) { + statSummary->SetStateFloat( "ready", 1 ); + statSummary->HandleNamedEvent( "chatFocus" ); + statSummary->StateChanged( gameLocal.time ); + } + statSummary->Redraw( gameLocal.time ); +} + +void idMultiplayerGame::ShowStatSummary( void ) { + if ( !gameLocal.GetLocalPlayer() ) { + assert( false ); + return; + } + DisableMenu( ); + nextMenu = 3; + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.GetLocalPlayer()->GUIMainNotice( "" ); + gameLocal.GetLocalPlayer()->GUIFragNotice( "" ); +} + +/* +================ +idMultiplayerGame::WriteToSnapshot +================ +*/ +void idMultiplayerGame::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + int value; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + + assert( MAX_CLIENTS % 8 == 0 ); +// RITUAL BEGIN - DeadZone Messages + msg.WriteBits(isBuyingAllowedRightNow, 1); + msg.WriteShort(powerupCount); + msg.WriteFloat( marineScoreBarPulseAmount ); + msg.WriteFloat( stroggScoreBarPulseAmount ); +// RITUAL END + + +// RAVEN BEGIN +// ddynerman: CTF scoring +// FIXME - not in the snapshot + for( i = 0; i < TEAM_MAX; i++ ) { + msg.WriteShort( teamScore[i] ); + msg.WriteLong( teamDeadZoneScore[i] ); + } +// RAVEN END + + // write ingame bits first, then we only sync down for ingame clients + // do a single write, this doesn't change often it's best to deltify in a single shot + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ingame[ i / 8 ] |= 1 << ( i % 8 ); + } else { + ingame[ i / 8 ] &= ~( 1 << ( i % 8 ) ); + } + } + msg.WriteData( ingame, MAX_CLIENTS / 8 ); + + // those rarely change as well and will deltify away nicely + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + // clamp all values to min/max possible value that we can send over + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + msg.WriteLong( playerState[i].deadZoneScore ); + value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins ); + msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS ); + // only transmit instance info in tourney + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !ent ) { + msg.WriteBits( 0, 1 ); + } else { + msg.WriteBits( 1, 1 ); + value = idMath::ClampInt( 0, MAX_INSTANCES, ent->GetInstance() ); + msg.WriteBits( value, ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( ((idPlayer*)ent)->GetTourneyStatus(), ASYNC_PLAYER_TOURNEY_STATUS_BITS ); + } + } + } + } + + // those change all the time, keep them in a single pack + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping ); + msg.WriteBits( value, ASYNC_PLAYER_PING_BITS ); + } + } +} + +/* +================ +idMultiplayerGame::ReadFromSnapshot +================ +*/ +void idMultiplayerGame::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, newInstance; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + if ( proto69 ) { + isBuyingAllowedRightNow = false; + powerupCount = 0; + marineScoreBarPulseAmount = 0; + stroggScoreBarPulseAmount = 0; + } else { +// RITUAL BEGIN - DeadZone & buying Messages + isBuyingAllowedRightNow = msg.ReadBits(1); + powerupCount = msg.ReadShort(); + // TTimo: NOTE: sounds excessive to be transmitting floats for that + marineScoreBarPulseAmount = msg.ReadFloat(); + stroggScoreBarPulseAmount = msg.ReadFloat(); +// RITUAL END + } + + // CTF/TDM scoring + for( i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = msg.ReadShort( ); + if ( proto69 ) { + teamDeadZoneScore[ i ] = 0; + } else { + teamDeadZoneScore[ i ] = msg.ReadLong( ); + } + } + + msg.ReadData( ingame, MAX_CLIENTS / 8 ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( ingame[ i / 8 ] & ( 1 << ( i % 8 ) ) ) { + playerState[i].ingame = true; + } else { + playerState[i].ingame = false; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + playerState[ i ].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[ i ].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + if ( proto69 ) { + playerState[ i ].deadZoneScore = 0; + } else { + playerState[ i ].deadZoneScore = msg.ReadLong(); + } + playerState[ i ].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS ); + if( gameLocal.gameType == GAME_TOURNEY ) { + if( msg.ReadBits( 1 ) ) { + newInstance = msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ); + if( newInstance != ent->GetInstance() ) { + ent->SetInstance( newInstance ); + if( gameLocal.GetLocalPlayer() && i != gameLocal.localClientNum ) { + if( ent->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + ((idPlayer*)ent)->ClientInstanceJoin(); + } else { + ((idPlayer*)ent)->ClientInstanceLeave(); + } + } + } + ((idPlayer*)ent)->SetTourneyStatus( (playerTourneyStatus_t)msg.ReadBits( ASYNC_PLAYER_TOURNEY_STATUS_BITS ) ); + } + } + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + playerState[ i ].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS ); + } + } +} + +// RAVEN BEGIN +// bdube: global item sounds +/* +================ +idMultiplayerGame::PlayGlobalItemAcquireSound +================ +*/ +void idMultiplayerGame::PlayGlobalItemAcquireSound( int defIndex ) { + const idDeclEntityDef* def; + def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, defIndex, false ) ); + if ( !def ) { + gameLocal.Warning ( "NET: invalid entity def index (%d) for global item acquire sound", defIndex ); + return; + } + + if( !gameLocal.GetLocalPlayer() || !gameLocal.currentThinkingEntity || gameLocal.GetLocalPlayer()->GetInstance() == gameLocal.currentThinkingEntity->GetInstance() ) { + soundSystem->PlayShaderDirectly ( SOUNDWORLD_GAME, def->dict.GetString ( "snd_acquire" ) ); + } + + if ( gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND ); + outMsg.WriteBits( defIndex, gameLocal.entityDefBits ); + gameLocal.ServerSendInstanceReliableMessage( gameLocal.currentThinkingEntity, -1, outMsg ); + } +} +// RAVEN END + +/* +================ +idMultiplayerGame::PrintMessageEvent +================ +*/ +void idMultiplayerGame::PrintMessageEvent( int to, msg_evt_t evt, int parm1, int parm2 ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( to == -1 || ( p && to == p->entityNumber ) ) { + switch ( evt ) { + case MSG_SUICIDE: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104293" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_KILLED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104292" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_KILLEDTEAM: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104291" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_TELEFRAGGED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104290" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_DIED: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104289" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_VOTE: + AddChatLine( common->GetLocalizedString( "#str_104288" ) ); + break; + case MSG_SUDDENDEATH: + AddChatLine( common->GetLocalizedString( "#str_104287" ) ); + break; + case MSG_FORCEREADY: + AddChatLine( common->GetLocalizedString( "#str_104286" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ parm1 ] && gameLocal.entities[ parm1 ]->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + static_cast< idPlayer * >( gameLocal.entities[ parm1 ] )->forcedReady = true; + } + break; + case MSG_JOINEDSPEC: + AddChatLine( common->GetLocalizedString( "#str_104285" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_TIMELIMIT: + AddChatLine( common->GetLocalizedString( "#str_104284" ) ); + break; + case MSG_FRAGLIMIT: + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { + // RITUAL END + // RAVEN BEGIN + // rhummer: localized "Strogg" and "Marine" + AddChatLine( common->GetLocalizedString( "#str_107665" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + } else { + AddChatLine( common->GetLocalizedString( "#str_104281" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + } + break; + case MSG_CAPTURELIMIT: + // RAVEN BEGIN + // rhummer: localized "%s team hit the capture limit." and "Strogg and "Marine" + AddChatLine( common->GetLocalizedString( "#str_108027" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + break; + case MSG_HOLYSHIT: + AddChatLine( common->GetLocalizedString( "#str_106732" ) ); + break; + default: + gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt ); + return; + } + } + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DB ); + outMsg.WriteByte( evt ); + outMsg.WriteByte( parm1 ); + outMsg.WriteByte( parm2 ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::PrintMessage +================ +*/ +void idMultiplayerGame::PrintMessage( int to, const char* msg ) { + if( idStr::Length( msg ) >= MAX_PRINT_LEN ) { + common->Warning( "idMultiplayerGame::PrintMessage() - Not transmitting message of length %d", idStr::Length( msg ) ); + return; + } + + AddChatLine( msg ); + + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PRINT ); + outMsg.WriteString( msg ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::CheckSpawns +================ +*/ +void idMultiplayerGame::CheckRespawns( idPlayer *spectator ) { + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast(ent); + + // once we hit sudden death, nobody respawns till game has ended + // no respawns in tourney mode, the tourney manager manually handles spawns + if ( (WantRespawn( p ) || p == spectator) ) { + if ( gameState->GetMPGameState() == SUDDENDEATH && gameLocal.gameType != GAME_TOURNEY ) { + // respawn rules while sudden death are different + // sudden death may trigger while a player is dead, so there are still cases where we need to respawn + // don't do any respawns while we are in end game delay though + if ( gameLocal.IsTeamGame() || p->IsLeader() ) { + //everyone respawns in team games, only fragleaders respawn in DM + p->ServerSpectate( false ); + } else {//if ( !p->IsLeader() ) { + // sudden death is rolling, this player is not a leader, have him spectate + p->ServerSpectate( true ); + CheckAbortGame(); + } + } else { + if ( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType != GAME_TOURNEY ) { + // wait for team to be set before spawning in + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( false ); + } + + } else { + if( p->GetArena() >= 0 && p->GetArena() < MAX_ARENAS ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameState)->GetArena( p->GetArena() ); + if( ( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) && ( p == arena.GetPlayers()[ 0 ] || p == arena.GetPlayers()[ 1 ] ) ) { + // only allow respawn if the arena we're in is active + // and we're one of the assigned players (we're not just spectating it) + p->ServerSpectate( false ); + } + } else { + // always allow respawn in the waiting room + assert( p->GetArena() == MAX_ARENAS ); + p->ServerSpectate( false ); + } + } + } + } + } else if ( p->wantSpectate && !p->spectating ) { + playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets + p->ServerSpectate( true ); + CheckAbortGame(); + } + } +} + +void idMultiplayerGame::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 && gameRenderWorld ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + +void idMultiplayerGame::UpdateLight ( int lightID, idPlayer *player ) { + lights[ lightID ].origin = player->GetPhysics()->GetOrigin() + idVec3( 0, 0, 20 ); + + if ( lightHandles[ lightID ] == -1 ) { + lightHandles[ lightID ] = gameRenderWorld->AddLightDef ( &lights[ lightID ] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[ lightID ], &lights[ lightID ] ); + } +} + +void idMultiplayerGame::CheckSpecialLights( void ) { + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idPlayer *marineFlagCarrier = NULL; + idPlayer *stroggFlagCarrier = NULL; + idPlayer *quadDamageCarrier = NULL; + idPlayer *regenerationCarrier = NULL; + idPlayer *hasteCarrier = NULL; + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast( ent ); + + if( gameLocal.GetLocalPlayer() && p->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + + if ( p->PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + marineFlagCarrier = p; + } + else if ( p->PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + stroggFlagCarrier = p; + } + else if( p->PowerUpActive( POWERUP_QUADDAMAGE ) || p->PowerUpActive( POWERUP_TEAM_DAMAGE_MOD )) { + quadDamageCarrier = p; + } + else if( p->PowerUpActive( POWERUP_REGENERATION ) ) { + regenerationCarrier = p; + } + else if( p->PowerUpActive( POWERUP_HASTE ) ) { + hasteCarrier = p; + } + } + + if ( marineFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_MARINE, marineFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_MARINE ); + } + + if ( stroggFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_STROGG, stroggFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_STROGG ); + } + + if ( quadDamageCarrier ) { + UpdateLight( MPLIGHT_QUAD, quadDamageCarrier ); + } else { + FreeLight( MPLIGHT_QUAD ); + } + + if ( regenerationCarrier ) { + UpdateLight( MPLIGHT_REGEN, regenerationCarrier ); + } else { + FreeLight( MPLIGHT_REGEN ); + } + + if ( hasteCarrier ) { + UpdateLight( MPLIGHT_HASTE, hasteCarrier ); + } else { + FreeLight( MPLIGHT_HASTE ); + } +} + +/* +================ +idMultiplayerGame::ForceReady +================ +*/ +void idMultiplayerGame::ForceReady( ) { + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + idPlayer *p = static_cast( ent ); + if ( !p->IsReady() ) { + PrintMessageEvent( -1, MSG_FORCEREADY, i ); + p->forcedReady = true; + } + } +} + +/* +================ +idMultiplayerGame::ForceReady_f +================ +*/ +void idMultiplayerGame::ForceReady_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + gameLocal.Printf( "forceReady: multiplayer server only\n" ); + return; + } + gameLocal.mpGame.ForceReady(); +} + +/* +================ +idMultiplayerGame::DropWeapon +================ +*/ +void idMultiplayerGame::DropWeapon( int clientNum ) { + assert( !gameLocal.isClient ); + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } +// RAVEN BEGIN +// bdube: removed parameter + static_cast< idPlayer* >( ent )->DropWeapon( ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::DropWeapon_f +================ +*/ +void idMultiplayerGame::DropWeapon_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "clientDropWeapon: only valid in multiplayer\n" ); + return; + } + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DROPWEAPON ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::MessageMode_f +================ +*/ +void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) { + gameLocal.mpGame.MessageMode( args ); +} + +/* +================ +idMultiplayerGame::MessageMode +================ +*/ +void idMultiplayerGame::MessageMode( const idCmdArgs &args ) { + const char *mode; + int imode; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientMessageMode: only valid in multiplayer\n" ); + return; + } + if ( !mainGui ) { + common->Printf( "no local client\n" ); + return; + } + mode = args.Argv( 1 ); + if ( !mode[ 0 ] || !gameLocal.IsTeamGame() ) { + imode = 0; + } else { + imode = atoi( mode ); + } + msgmodeGui->SetStateString( "messagemode", imode ? "1" : "0" ); + msgmodeGui->SetStateString( "chattext", "" ); + nextMenu = 2; + // let the session know that we want our ingame main menu opened + gameLocal.sessionCommand = "game_startmenu"; +} + +/* +================ +idMultiplayerGame::Vote_f +================ +*/ +void idMultiplayerGame::Vote_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// shouchard: implemented for testing + if ( args.Argc() < 2 ) { + common->Printf( common->GetLocalizedString( "#str_104418" ) ); + return; + } + + const char *szArg1 = args.Argv(1); + bool voteValue = false; + if ( 0 == idStr::Icmp( szArg1, "yes" ) ) { + voteValue = true; + } + + gameLocal.mpGame.CastVote( gameLocal.localClientNum, voteValue ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::CallVote_f +moved this over the use the packed voting +still only does one vote though, can easily be extended to do more +================ +*/ +void idMultiplayerGame::CallVote_f( const idCmdArgs &args ) { + const char *szArg1 = args.Argv(1); + const char *szArg2 = args.Argv(2); + if ( '\0' == *szArg1 ) { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + if ( 0 == idStr::Icmp( szArg1, "restart" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } else if ( 0 == idStr::Icmp( szArg1, "timelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104406" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + voteData.m_timeLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "fraglimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104407" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + voteData.m_fragLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "gametype" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104408" ) ); + common->Printf( common->GetLocalizedString( "#str_104409" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + voteData.m_gameType = gameLocal.mpGame.GameTypeToVote( szArg2 ); + } + else if ( 0 == idStr::Icmp( szArg1, "kick" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104412" ) ); + return; + } + voteData.m_kick = gameLocal.mpGame.GetClientNumFromPlayerName( szArg2 ); + if ( voteData.m_kick >= 0 ) { + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + } else if ( 0 == idStr::Icmp( szArg1, "map" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104413" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_MAP; + voteData.m_map = szArg2; + } else if ( 0 == idStr::Icmp( szArg1, "buying" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122012" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + voteData.m_buying = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "capturelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104415" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + voteData.m_captureLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "autobalance" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104416" ) ); + } + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + voteData.m_teamBalance = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "controlTime" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122002" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + } + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + voteData.m_controlTime = atoi(szArg2 ); + } else { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + if ( voteData.m_fieldFlags != 0 ) { + gameLocal.mpGame.ClientCallPackedVote( voteData ); + } +} + +// RAVEN BEGIN +// shouchard: added voice mute and unmute console commands; sans XBOX to not step on their live voice stuff +#ifndef _XBOX +/* +================ +idMultiplayerGame::VoiceMute_f +================ +*/ +void idMultiplayerGame::VoiceMute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoicemute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), true ); +} + +/* +================ +idMultiplayerGame::VoiceUnmute_f +================ +*/ +void idMultiplayerGame::VoiceUnmute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoiceunmute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), false ); +} + +// RAVEN END +#endif // _XBOX + +// RAVEN BEGIN +/* +================ +idMultiplayerGame::ForceTeamChange_f +================ +*/ +void idMultiplayerGame::ForceTeamChange_f( const idCmdArgs &args) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Forces player to change teams. Usage: ForceTeamChange \n" ); + return; + } + + idStr clientId; + int clientNum; + + clientId = args.Argv( 1 ); + if ( !clientId.IsNumeric() ) { + common->Printf( "usage: ForceTeamChange \n" ); + return; + } + + clientNum = atoi( clientId ); + + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + } + +} + + +/* +================ +idMultiplayerGame::RemoveClientFromBanList_f +================ +*/ +void idMultiplayerGame::RemoveClientFromBanList_f( const idCmdArgs& args ) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Remove player from banlist. Usage: RemoveClientFromBanList \n" ); + return; + } + + idStr clientId; + clientId = args.Argv( 1 ); + int clientNum; + + if ( !clientId.IsNumeric() ) { + common->Printf( "Usage: RemoveClientFromBanList \n" ); + return; + } + + clientNum = atoi( clientId ); + + const char *clientGuid = networkSystem->GetClientGUID( clientNum ); // gameLocal.GetGuidByClientNum( clientNum ); + + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + // remove from the ban list + gameLocal.RemoveGuidFromBanList( clientGuid ); + } + +} + +/* +================ +idMultiplayerGame::ProcessRconReturn +================ +*/ +void idMultiplayerGame::ProcessRconReturn( bool success ) { + + if( success ) { + mainGui->HandleNamedEvent("adminPasswordSuccess"); + } else { + mainGui->HandleNamedEvent("adminPasswordFail"); + } + + +} + + +// RAVEN END + +/* +================ +idMultiplayerGame::ServerStartVote +================ +*/ +void idMultiplayerGame::ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *value ) { + int i; + + assert( vote == VOTE_NONE ); + + // setup + yesVotes = 1; + noVotes = 0; + vote = voteIndex; + voteValue = value; + voteTimeOut = gameLocal.time + 20000; + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } +} + +/* +================ +idMultiplayerGame::ClientStartVote +================ +*/ +void idMultiplayerGame::ClientStartVote( int clientNum, const char *_voteString ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteString( _voteString ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + voteString = _voteString; + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); +// RAVEN BEGIN +// shouchard: better info when a vote called in the chat buffer + AddChatLine( voteString ); // TODO: will push this into a UI field later +// shouchard: display the vote called text on the hud + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->mphud ) { + gameLocal.GetLocalPlayer()->mphud->SetStateInt( "voteNotice", 1 ); + } +// RAVEN END + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ClientUpdateVote +================ +*/ +void idMultiplayerGame::ClientUpdateVote( vote_result_t status, int yesCount, int noCount, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + const char * localizedString = 0; + idPlayer* player = gameLocal.GetLocalPlayer( ); + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_UPDATEVOTE ); + outMsg.WriteByte( status ); + outMsg.WriteByte( yesCount ); + outMsg.WriteByte( noCount ); +// RAVEN BEGIN +// shouchard: multifield vote support + if ( VOTE_MULTIFIELD != vote ) { + outMsg.WriteByte( 0 ); + } else { + outMsg.WriteByte( 1 ); + outMsg.WriteShort( voteData.m_fieldFlags ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + outMsg.WriteString( voteData.m_map.c_str() ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } else { + currentVoteData = voteData; + } +// RAVEN END + + if ( vote == VOTE_NONE ) { + // clients coming in late don't get the vote start and are not allowed to vote + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + } + return; + } + + switch ( status ) { + case VOTE_FAILED: + localizedString = common->GetLocalizedString( "#str_104278" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_FAILED, gameLocal.time ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_PASSED: + localizedString = common->GetLocalizedString( "#str_104277" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_PASSED, gameLocal.time ); + break; + case VOTE_RESET: + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_ABORTED: + localizedString = common->GetLocalizedString( "#str_104276" ); + AddChatLine( localizedString ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_UPDATE: + if ( player && player->mphud && voted ) { + player->mphud->SetStateString( "voteNoticeText", va("^:%s\n%s: %d %s: %d", + common->GetLocalizedString( "#str_107724" ), + common->GetLocalizedString( "#str_107703" ), + yesCount, + common->GetLocalizedString( "#str_107704" ), + noCount ) ); + } + + if ( mainGui ) { + mainGui->SetStateInt( "playerVoted", voted ); + } + break; + default: + break; + } + + if ( gameLocal.isClient ) { + yesVotes = yesCount; + noVotes = noCount; + } + +// RAVEN BEGIN +// shouchard: remove vote notification + if ( VOTE_FAILED == status || VOTE_PASSED == status || VOTE_RESET == status ) { + ClearVote(); + } + + if ( mainGui ) { + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), (int)yesVotes, (int)noVotes ) ); + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::ClientCallVote +================ +*/ +void idMultiplayerGame::ClientCallVote( vote_flags_t voteIndex, const char *voteValue ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLVOTE ); + outMsg.WriteByte( voteIndex ); + outMsg.WriteString( voteValue ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::CastVote +================ +*/ +void idMultiplayerGame::CastVote( int clientNum, bool castVote ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } + + if ( gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CASTVOTE ); + outMsg.WriteByte( castVote ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // sanity + if ( vote == VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104275" ); + common->DPrintf( "client %d: cast vote while no vote in progress\n", clientNum ); + return; + } + if ( playerState[ clientNum ].vote != PLAYER_VOTE_WAIT ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104274" ); + common->DPrintf( "client %d: cast vote - vote %d != PLAYER_VOTE_WAIT\n", clientNum, playerState[ clientNum ].vote ); + return; + } + + if ( castVote ) { + playerState[ clientNum ].vote = PLAYER_VOTE_YES; + yesVotes++; + } else { + playerState[ clientNum ].vote = PLAYER_VOTE_NO; + noVotes++; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerCallVote +================ +*/ +void idMultiplayerGame::ServerCallVote( int clientNum, const idBitMsg &msg ) { + vote_flags_t voteIndex; + int vote_timeLimit, vote_fragLimit, vote_clientNum, vote_gameTypeIndex, vote_buying; //, vote_kickIndex; +// RAVEN BEGIN +// shouchard: added capture limit and autobalance + int vote_captureLimit; + bool vote_autobalance; +// RAVEN END + int vote_controlTime; + char value[ MAX_STRING_CHARS ]; + + assert( clientNum != -1 ); + assert( !gameLocal.isClient ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + voteIndex = (vote_flags_t)msg.ReadByte( ); + msg.ReadString( value, sizeof( value ) ); + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + switch ( voteIndex ) { + case VOTE_RESTART: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104271" ) ); + break; + } + case VOTE_NEXTMAP: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104272" ) ); + break; + } + case VOTE_TIMELIMIT: { + vote_timeLimit = strtol( value, NULL, 10 ); + if ( vote_timeLimit == gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + common->DPrintf( "client %d: already at the voted Time Limit\n", clientNum ); + return; + } + if ( vote_timeLimit < si_timeLimit.GetMinValue() || vote_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104268" ), vote_timeLimit ) ); + break; + } + case VOTE_FRAGLIMIT: { + vote_fragLimit = strtol( value, NULL, 10 ); + if ( vote_fragLimit == gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + common->DPrintf( "client %d: already at the voted Frag Limit\n", clientNum ); + return; + } + if ( vote_fragLimit < si_fragLimit.GetMinValue() || vote_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104303" ), common->GetLocalizedString( "#str_104265" ), vote_fragLimit ) ); + break; + } + case VOTE_GAMETYPE: { +// RAVEN BEGIN +// shouchard: removed magic numbers & added CTF type + vote_gameTypeIndex = strtol( value, NULL, 10 ); + assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex < VOTE_GAMETYPE_COUNT ); + switch ( vote_gameTypeIndex ) { + case VOTE_GAMETYPE_DM: + strcpy( value, "DM" ); + break; + case VOTE_GAMETYPE_TOURNEY: + strcpy( value, "Tourney" ); + break; + case VOTE_GAMETYPE_TDM: + strcpy( value, "Team DM" ); + break; + case VOTE_GAMETYPE_CTF: + strcpy( value, "CTF" ); + break; + case VOTE_GAMETYPE_ARENA_CTF: + strcpy( value, "Arena CTF" ); + break; +// RAVEN END +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + case VOTE_GAMETYPE_DEADZONE: + strcpy( value, "DeadZone" ); + break; +// RITUAL END + + } + if ( !idStr::Icmp( value, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104258" ), value ) ); + break; + } + case VOTE_KICK: { + vote_clientNum = strtol( value, NULL, 10 ); + if ( vote_clientNum == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, va( "%d", vote_clientNum ) ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104302" ), vote_clientNum, gameLocal.userInfo[ vote_clientNum ].GetString( "ui_name" ) ) ); + break; + } + case VOTE_MAP: { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + if ( idStr::FindText( gameLocal.serverInfo.GetString( "si_map" ), value ) != -1 ) { + + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) { + mapName = common->GetLocalizedString( mapDef->dict.GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + common->DPrintf( "client %d: already running the voted map: %s\n", clientNum, value ); + return; + } + int num = fileSystem->GetNumMaps(); + int i; + const idDict *dict = NULL; + bool haveMap = false; + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if( !dict ) { + gameLocal.Warning( "idMultiplayerGame::ServerCallVote() - bad map decl index on vote\n" ); + break; + } + if ( dict && !idStr::Icmp( dict->GetString( "path" ), value ) ) { + haveMap = true; + break; + } + } + if ( !haveMap ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104296", value ); + common->Printf( "client %d: map not found: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104256" ), dict ? dict->GetString( "name" ) : value ) ); +#endif + break; + } + case VOTE_BUYING: { + if ( gameLocal.GetCurrentDemoProtocol() == 69 ) + gameLocal.Error("MIN_PLAYERS vote in a pre-1.2 recorded demo is not supported."); + vote_buying = strtol( value, NULL, 10 ); + if ( vote_buying == gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + common->DPrintf( "client %d: already at the voted buying mode\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_122014" ), vote_buying ) ); + break; + } +// RAVEN BEGIN +// shouchard: added capture limit, round limit, and autobalance + case VOTE_CAPTURELIMIT: { + vote_captureLimit = strtol( value, NULL, 10 ); + if ( vote_captureLimit == gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + common->DPrintf( "client %d: already at the voted Capture Limit\n", clientNum ); + return; + } + if ( vote_captureLimit < si_captureLimit.GetMinValue() || vote_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_captureLimit" ); + break; + } + // round limit is for tourneys + case VOTE_ROUNDLIMIT: { + // need a CVar or something to change here + break; + } + case VOTE_AUTOBALANCE: { + vote_autobalance = (0 != strtol( value, NULL, 10 ) ); + if ( vote_autobalance == gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + common->DPrintf( "client %d: already at the voted balance teams\n", clientNum ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_autobalance" ); + break; + } +// RAVEN END + case VOTE_CONTROLTIME: { + vote_controlTime = strtol( value, NULL, 10 ); + if ( vote_controlTime == gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + common->DPrintf( "client %d: already at the voted Control Time\n", clientNum ); + return; + } + if ( vote_controlTime < si_controlTime.GetMinValue() || vote_controlTime > si_controlTime.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122018" ); + common->DPrintf( "client %d: controlTime value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_controlTime" ); + break; + } + default: { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104297", va( "%d", ( int )voteIndex ) ); + common->DPrintf( "client %d: unknown vote index %d\n", clientNum, voteIndex ); + } + } +} + + +/* +================ +idMultiplayerGame::DisconnectClient +================ +*/ +void idMultiplayerGame::DisconnectClient( int clientNum ) { + // gameLocal.entities[ clientNum ] could be null if server is shutting down + if( gameLocal.entities[ clientNum ] ) { + // only kill non-spectators + if( !((idPlayer*)gameLocal.entities[ clientNum ])->spectating ) { + static_cast( gameLocal.entities[ clientNum ] )->Kill( true, true ); + } + statManager->ClientDisconnect( clientNum ); + } + + delete gameLocal.entities[ clientNum ]; + + UpdatePlayerRanks(); + CheckAbortGame(); + + privatePlayers &= ~( 1 << clientNum ); + + // update serverinfo + UpdatePrivatePlayerCount(); +} + +/* +================ +idMultiplayerGame::CheckAbortGame +================ +*/ +void idMultiplayerGame::CheckAbortGame( void ) { + // only checks for aborts -> game review below + if ( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != GAMEON && gameState->GetMPGameState() != SUDDENDEATH ) { + return; + } + + // in tourney, if we don't have enough clients to play we need to cycle back to + // warmup to re-seed + if( gameLocal.gameType == GAME_TOURNEY ) { + if ( !EnoughClientsToPlay() ) { + gameState->NewState( WARMUP ); + } + } else { + if ( !EnoughClientsToPlay() && TimeLimitHit() ) { + gameState->NewState( GAMEREVIEW ); + } + } +} + +/* +================ +idMultiplayerGame::WantKilled +================ +*/ +void idMultiplayerGame::WantKilled( int clientNum ) { + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Kill( false, false ); + } +} + +/* +================ +idMultiplayerGame::ClearVote +================ +*/ +void idMultiplayerGame::ClearVote( int clientNum ) { + int start = 0; + int end = MAX_CLIENTS; + + if( clientNum != -1 ) { + start = clientNum; + end = clientNum + 1; + } + + for ( int i = start; i < end; i++ ) { + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->mphud ) { + continue; + } + + player->mphud->SetStateInt( "voteNotice", 0 ); + player->mphud->SetStateString( "voteInfo_1", "" ); + player->mphud->SetStateString( "voteInfo_2", "" ); + player->mphud->SetStateString( "voteInfo_3", "" ); + player->mphud->SetStateString( "voteInfo_4", "" ); + player->mphud->SetStateString( "voteInfo_5", "" ); + player->mphud->SetStateString( "voteInfo_6", "" ); + player->mphud->SetStateString( "voteInfo_7", "" ); + player->mphud->StateChanged( gameLocal.time ); + } + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + mainGui->StateChanged( gameLocal.time ); + } +} +/* +================ +idMultiplayerGame::MapRestart +================ +*/ +void idMultiplayerGame::MapRestart( void ) { + int clientNum; + // jshepard: clean up votes + ClearVote(); + + ClearAnnouncerSounds(); + + assert( !gameLocal.isClient ); + if ( gameLocal.GameState() != GAMESTATE_SHUTDOWN && gameState->GetMPGameState() != WARMUP ) { + gameState->NewState( WARMUP ); + // force an immediate state detection/update, otherwise if we update our state this + // same frame we'll miss transitions + gameState->SendState(); + + gameState->SetNextMPGameState( INACTIVE ); + gameState->SetNextMPGameStateTime( 0 ); + + } + + // mekberg: moved this before the updateUI just in case these values weren't reset. + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } + + // mekberg: Re-wrote this loop to always updateUI. Previously the player would be + // on a team but the UI wouldn't know about it + // shouchard: balance teams extended to CTF + for ( clientNum = 0; clientNum < gameLocal.numClients; clientNum++ ) { + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + // mekberg: clear wins only on map restart + idPlayer *player = static_cast( gameLocal.entities[ clientNum ] ); + SetPlayerWin( player, 0 ); + + /*if( clientNum == gameLocal.localClientNum ) { + if ( player->alreadyDidTeamAnnouncerSound ) { + player->alreadyDidTeamAnnouncerSound = false; + } else { + if ( gameLocal.IsTeamGame() ) { + player->alreadyDidTeamAnnouncerSound = true; + if( player->team == TEAM_STROGG ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( player->team == TEAM_MARINE ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + }*/ + // let the player rejoin the team through normal channels + //player->ServerSpectate( true ); + //player->team = -1; + //player->latchedTeam = -1; + + // shouchard: BalanceTDM->BalanceTeam + //if ( gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // player->BalanceTeam(); + //} + + // core is in charge of syncing down userinfo changes + // it will also call back game through SetUserInfo with the current info for update + /*cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) );*/ + } + } +} + +/* +================ +idMultiplayerGame::SwitchToTeam +================ +*/ +void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) { + assert( gameLocal.IsTeamGame() ); + + assert( oldteam != newteam ); + assert( !gameLocal.isClient ); + + if ( !gameLocal.isClient && newteam >= 0 ) { + // clients might not have userinfo of joining client at this point, so + // send down the player's name + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( !p->wantSpectate ) { + PrintMessage( -1, va( common->GetLocalizedString( "#str_104280" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ), newteam ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ) ); + } + } + + if ( oldteam != -1 ) { + // kill and respawn + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( p->IsInTeleport() ) { + p->ServerSendInstanceEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false, -1 ); + p->SetPrivateCameraView( NULL ); + } +//RITUAL BEGIN + p->inventory.carryOverWeapons = 0; + p->ResetCash(); +//RITUAL END + p->Kill( true, true ); + CheckAbortGame(); + } +} + +/* +================ +idMultiplayerGame::JoinTeam +================ +*/ +void idMultiplayerGame::JoinTeam( const char* team ) { + if( !idStr::Icmp( team, "auto" ) ) { + int teamCount[ TEAM_MAX ]; + idEntity *ent; + + memset( teamCount, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ ((idPlayer*)ent)->team ]++; + } + } + } + + int minCount = idMath::INT_MAX; + int minCountTeam = -1; + for( int i = 0; i < TEAM_MAX; i++ ) { + if( teamCount[ i ] < minCount ) { + minCount = teamCount[ i ]; + minCountTeam = i; + } + } + + if( minCountTeam >= 0 && minCountTeam < TEAM_MAX ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ minCountTeam ] ); + } else { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ gameLocal.random.RandomInt( TEAM_MAX - 1 ) ] ); + } + } else if( !idStr::Icmp( team, "spectator" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + if( !idStr::Icmp( team, teamNames[ i ] ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ i ] ); + break; + } + } + if( i >= TEAM_MAX ) { + gameLocal.Warning( "idMultiplayerGame::JoinTeam() - unknown team '%s'\n", team ); + } + } +} + +/* +================ +idMultiplayerGame::ProcessChatMessage +================ +*/ +void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + const char *suffix = NULL; + int send_to; // 0 - all, 1 - specs, 2 - team + int i; + idEntity *ent; + idPlayer *p; + idStr suffixed_name; + idStr prefixed_text; + + assert( !gameLocal.isClient ); + + if ( clientNum >= 0 ) { + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating && ( p->wantSpectate || gameLocal.gameType == GAME_TOURNEY ) ) { + suffix = "spectating"; + if ( team || ( !g_spectatorChat.GetBool() && ( gameState->GetMPGameState() == GAMEON || gameState->GetMPGameState() == SUDDENDEATH ) ) ) { + // to specs + send_to = 1; + } else { + // to all + send_to = 0; + } + } else if ( team ) { + suffix = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, p->team ? "Strogg^0" : "Marine^0" ); + // to team + send_to = 2; + } else { + if( gameLocal.gameType == GAME_TOURNEY ) { + suffix = va( "Arena %d", (p->GetArena() + 1) ); + } + // to all + send_to = 0; + } + } else { + p = NULL; + send_to = 0; + } + // put the message together + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + + if ( suffix ) { + suffixed_name = va( "^0%s^0 (%s)", name, suffix ); + } else { + suffixed_name = va( "^0%s^0", name ); + } + if( p && send_to == 2 ) { + prefixed_text = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, common->GetLocalizedString( text ) ); + } else { + prefixed_text = common->GetLocalizedString( text ); + } + + if( suffixed_name.Length() + prefixed_text.Length() >= 240 ) { + gameLocal.Warning( "idMultiplayerGame::ProcessChatMessage() - Chat line too long\n" ); + return; + } + + outMsg.WriteString( suffixed_name ); + outMsg.WriteString( prefixed_text ); + outMsg.WriteString( "" ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *to = static_cast< idPlayer * >( ent ); + switch( send_to ) { + case 0: + if ( !p || !to->IsPlayerMuted( p ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 1: + if ( !p || ( to->spectating && !to->IsPlayerMuted( p ) ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 2: + if ( !p || ( to->team == p->team && !to->IsPlayerMuted( p ) ) ) { + if ( !to->spectating ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + } + break; + } + } +} + +/* +================ +idMultiplayerGame::Precache +================ +*/ +void idMultiplayerGame::Precache( void ) { + int i; + + if ( !gameLocal.isMultiplayer ) { + return; + } + gameLocal.FindEntityDef( "player_marine", false ); + + // MP game sounds + for ( i = 0; i < AS_NUM_SOUNDS; i++ ) { + declManager->FindSound( announcerSoundDefs[ i ], false ); + } + + // MP guis. just make sure we hit all of them + i = 0; + while ( MPGuis[ i ] ) { + uiManager->FindGui( MPGuis[ i ], true ); + i++; + } +} + +/* +================ +idMultiplayerGame::ToggleSpectate +================ +*/ +void idMultiplayerGame::ToggleSpectate( void ) { + bool spectating; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + spectating = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( spectating ) { + // always allow toggling to play + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + } else { + // only allow toggling to spectate if spectators are enabled. + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_106747" ) ); + } + } +} + +/* +================ +idMultiplayerGame::ToggleReady +================ +*/ +void idMultiplayerGame::ToggleReady( void ) { + bool ready; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + if ( lastReadyToggleTime == -1 ) { + lastReadyToggleTime = gameLocal.time; + } else { + int currentTime = gameLocal.time; + if ( currentTime - lastReadyToggleTime < 500 ) { + return; + } else { + lastReadyToggleTime = currentTime; + } + } + + ready = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready ) { + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + } else { + cvarSystem->SetCVarString( "ui_ready", "Ready" ); + } +} + +/* +================ +idMultiplayerGame::ToggleTeam +================ +*/ +void idMultiplayerGame::ToggleTeam( void ) { + bool team; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + // RAVEN BEGIN + // ddynerman: new multiplayer teams + team = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_team" ), "Marine" ) == 0 ); + if ( team ) { + cvarSystem->SetCVarString( "ui_team", "Strogg" ); + } else { + cvarSystem->SetCVarString( "ui_team", "Marine" ); + } + // RAVEN END +} + +/* +================ +idMultiplayerGame::ToggleUserInfo +================ +*/ +void idMultiplayerGame::ThrottleUserInfo( void ) { + int i; + + assert( gameLocal.localClientNum >= 0 ); + + i = 0; + while ( ThrottleVars[ i ] ) { + if ( idStr::Icmp( gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ), + cvarSystem->GetCVarString( ThrottleVars[ i ] ) ) ) { + if ( gameLocal.realClientTime < switchThrottle[ i ] ) { + AddChatLine( common->GetLocalizedString( "#str_104299" ), common->GetLocalizedString( ThrottleVarsInEnglish[ i ] ), ( switchThrottle[ i ] - gameLocal.time ) / 1000 + 1 ); + cvarSystem->SetCVarString( ThrottleVars[ i ], gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ) ); + } else { + switchThrottle[ i ] = gameLocal.time + ThrottleDelay[ i ] * 1000; + } + } + i++; + } +} + +/* +================ +idMultiplayerGame::CanPlay +================ +*/ +bool idMultiplayerGame::CanPlay( idPlayer *p ) { + return !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::EnterGame +================ +*/ +void idMultiplayerGame::EnterGame( int clientNum ) { + assert( !gameLocal.isClient ); + + if ( !playerState[ clientNum ].ingame ) { + playerState[ clientNum ].ingame = true; + if ( gameLocal.isMultiplayer ) { + // can't use PrintMessageEvent as clients don't know the nickname yet + //gameLocal.ServerSendChatMessage( -1, common->GetLocalizedString( "#str_102047" ), va( common->GetLocalizedString( "#str_107177" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + } + + // mark them as private and update si_numPrivatePlayers + for( int i = 0; i < privateClientIds.Num(); i++ ) { + int num = networkSystem->ServerGetClientNum( privateClientIds[ i ] ); + + // check for timed out clientids + if( num < 0 ) { + privateClientIds.RemoveIndex( i ); + i--; + continue; + } + + if( num == clientNum ) { + privatePlayers |= (1 << clientNum); + } + } + + // update serverinfo + UpdatePrivatePlayerCount(); + } +} + +/* +================ +idMultiplayerGame::WantRespawn +================ +*/ +bool idMultiplayerGame::WantRespawn( idPlayer *p ) { + return p->forceRespawn && !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, false ); +} + +/* +================ +idMultiplayerGame::UpdateMPSettingsModel +================ +*/ +void idMultiplayerGame::UpdateMPSettingsModel( idUserInterface* currentGui ) { + if ( !currentGui ) { + return; + } + + const char *model; + idPlayer *localP = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() && localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + cvarSystem->SetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ), model ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( "def_default_model" ); + cvarSystem->SetCVarString( "ui_model", model ); + } + } + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + currentGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + currentGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + currentGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + currentGui->SetStateBool( "need_update", true ); + } +} + +/* +================ +idMultiplayerGame::VoiceChatTeam +================ +*/ +void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, true ); +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat( const idCmdArgs &args, bool team ) { + idBitMsg outMsg; + byte msgBuf[128]; + const char *voc; + const idDict *spawnArgs; + const idKeyValue *keyval; + int index; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientVoiceChat: only valid in multiplayer\n" ); + return; + } + if ( args.Argc() != 2 ) { + common->Printf( "clientVoiceChat: bad args\n" ); + return; + } + // throttle + if ( gameLocal.realClientTime < voiceChatThrottle ) { + return; + } + + voc = args.Argv( 1 ); + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + index = 0; + while ( keyval ) { + if ( !keyval->GetValue().Icmp( voc ) ) { + break; + } + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index++; + } + if ( !keyval ) { + common->Printf( "Voice command not found: %s\n", voc ); + return; + } + voiceChatThrottle = gameLocal.realClientTime + 1000; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VCHAT ); + outMsg.WriteLong( index ); + outMsg.WriteBits( team ? 1 : 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ProcessVoiceChat +================ +*/ +void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index ) { + const idDict *spawnArgs; + const idKeyValue *keyval; + idStr name; + idStr snd_key; + idStr text_key; + idPlayer *p; + + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating ) { + return; + } + + // lookup the sound def + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + while ( index > 0 && keyval ) { + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index--; + } + if ( !keyval ) { + common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index ); + return; + } + snd_key = keyval->GetKey(); + name = gameLocal.userInfo[ clientNum ].GetString( "ui_name" ); + sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() ); + if ( team || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEREVIEW ) { + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) ); + } else { + p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL ); + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL ); + } +} + +// RAVEN BEGIN +// shouchard: added commands to mute/unmute voice chat +/* +================ +idMultiplayerGame::ClientVoiceMute +================ +*/ +void idMultiplayerGame::ClientVoiceMute( int muteClient, bool mute ) { + // clients/listen server only + assert( gameLocal.isListenServer || gameLocal.isClient ); + + if ( NULL == gameLocal.GetLocalPlayer() ) { + return; + } + + if ( muteClient == -1 || !gameLocal.mpGame.IsInGame( muteClient ) ) { + gameLocal.Warning( "idMultiplayerGame::ClientVoiceMute() - Invalid client '%d' specified", muteClient ); + return; + } + + // do the mute/unmute + gameLocal.GetLocalPlayer()->MutePlayer( muteClient, mute ); + + // tell the server + if( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING ); + outMsg.WriteByte( muteClient ); + outMsg.WriteByte( mute ? 1 : 0 ); // 1 for mute, 0 for unmute + networkSystem->ClientSendReliableMessage( outMsg ); + } + + // display some niceties + common->Printf( "Player %s's has been %s.\n", gameLocal.GetUserInfo( muteClient )->GetString( "ui_name" ), mute ? "muted" : "unmuted" ); +} + +/* +================ +idMultiplayerGame::GetClientNumFromPlayerName +================ +*/ +int idMultiplayerGame::GetClientNumFromPlayerName( const char *playerName ) { + if ( NULL == playerName || '\0' == *playerName ) { + return -1; + } + + int clientNum = -1; + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + if ( 0 == idStr::Icmp( gameLocal.userInfo[ i ].GetString( "ui_name" ), playerName ) ) { + clientNum = i; + break; + } + } + } + + if ( -1 == clientNum ) { + common->Warning( "idMultiplayerGame::GetClientNumFromPlayerName(): unknown player '%s'", playerName ); + } + + return clientNum; +} + +/* +================ +idMultiplayerGame::ServerHandleVoiceMuting +================ +*/ +void idMultiplayerGame::ServerHandleVoiceMuting( int clientSrc, int clientDest, bool mute ) { + assert( !gameLocal.isClient ); + + idPlayer *playerSrc = gameLocal.GetClientByNum( clientSrc ); + idPlayer *playerDest = gameLocal.GetClientByNum( clientDest ); + + if ( NULL == playerSrc ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientSrc ); + return; + } + + if ( NULL == playerDest ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientDest ); + return; + } + + if ( mute ) { + playerSrc->MutePlayer( playerDest, true ); + common->DPrintf( "DEBUG: client %s muted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } else { + playerSrc->MutePlayer( playerDest, false ); + common->DPrintf( "DEBUG: client %s unmuted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } +} + + +/* +================ +idMultiplayerGame::ClearAnnouncerSounds + +This method deletes unplayed announcer sounds at the end of a game round. +This fixes a bug where the round time warnings were being played from +previous rounds. +================ +*/ +void idMultiplayerGame::ClearAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + snd->announcerSoundNode.Remove ( ); + delete snd; + } + + announcerPlayTime = 0; +} + +/* +================ +idMultiplayerGame::HandleServerAdminBanPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminBanPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + if ( gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban the host!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i ban", clientNum ) ); + } else { + if ( clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban yourserlf!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i ban", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminRemoveBan +================ +*/ +void idMultiplayerGame::HandleServerAdminRemoveBan( const char * clientGuid ) { + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + gameLocal.RemoveGuidFromBanList( clientGuid ); + } else { + int clientNum = gameLocal.GetClientNumByGuid( clientGuid ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon removeClientFromBanList %d", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminKickPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminKickPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminKickPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i", clientNum ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminForceTeamSwitch +================ +*/ +void idMultiplayerGame::HandleServerAdminForceTeamSwitch( int clientNum ) { + if ( !gameLocal.IsTeamGame() ) { + return; + } + + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminForceTeamSwitch: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "forceTeamChange %d\n", clientNum)); + +/* if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + }*/ + } else { +/* idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVER_ADMIN ); + outMsg.WriteByte( SERVER_ADMIN_FORCE_SWITCH ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); */ + + //jshepard: need to be able to do this via rcon + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon forceTeamChange %d\n", clientNum)); + + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminCommands +================ +*/ +bool idMultiplayerGame::HandleServerAdminCommands( serverAdminData_t &data ) { + bool restartNeeded = false; + bool nextMapNeeded = false; + bool anyChanges = false; + bool runPickMap = false; + int nGameType = 0; + idStr currentMap = si_map.GetString( ); + + const char *szGameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( szGameType, "DM" ) ) { + nGameType = GAME_DM; + } else if ( 0 == idStr::Icmp( szGameType, "Team DM" ) ) { + nGameType = GAME_TDM; + } else if ( 0 == idStr::Icmp( szGameType, "CTF" ) ) { + nGameType = GAME_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "Tourney" ) ) { + nGameType = GAME_TOURNEY; + } else if ( 0 == idStr::Icmp( szGameType, "Arena CTF" ) ) { + nGameType = GAME_ARENA_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "DeadZone" ) ) { + nGameType = GAME_DEADZONE; + } else { + nGameType = GAME_SP; + } + if ( nGameType != data.gameType ) { + + switch ( data.gameType ) { + case GAME_TDM: szGameType = "Team DM"; runPickMap = true; break; + case GAME_TOURNEY: szGameType = "Tourney"; runPickMap = true; break; + case GAME_CTF: szGameType = "CTF"; runPickMap = true; break; + case GAME_ARENA_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + + // mekberg: hack, if we had 1f ctf the gui index wouldn't be off =( + case GAME_1F_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + case GAME_DEADZONE: szGameType = "DeadZone"; runPickMap = true; break; + default: + case GAME_DM: szGameType = "DM"; break; + } + + //we're going to reset the map here, so make sure to kill the active vote. + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + + si_gameType.SetString( szGameType ); + if( runPickMap && gameLocal.isServer ) { + //set the selected map to the admin data value, then make sure it can run the selected gametype. + si_map.SetString( data.mapName.c_str() ); + if( PickMap( szGameType ) || idStr::Icmp( si_map.GetString( ), currentMap.c_str( ) ) ) { + nextMapNeeded = true; + restartNeeded = false; + data.mapName = idStr( si_map.GetString() ); + data.restartMap = true; + } + } + } + + if ( gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) != data.buying ) + restartNeeded = true; + + // Rcon these cvars if this isn't the server. We can trust the input from the gui that the + // gametype and map always match. + if ( !gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_autoBalance %d", data.autoBalance ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_isBuyingEnabled %d", data.buying ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_captureLimit %d", data.captureLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_controlTime %d", data.controlTime ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_fragLimit %d", data.fragLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_gameType %s", szGameType ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_map %s", data.mapName.c_str() ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_tourneyLimit %d", data.tourneyLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_minPlayers %d", data.minPlayers ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_timeLimit %d", data.timeLimit ) ); + if( runPickMap ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon verifyServerSettings" ) ); + } + + if( data.shuffleTeams ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon shuffleTeams" ); + } + + if( restartNeeded ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon serverMapRestart" ); + } else if( data.restartMap || nextMapNeeded || idStr::Icmp( gameLocal.serverInfo.GetString( "si_map" ), data.mapName.c_str() ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon serverNextMap" ); + } + else + { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon rescanSI" ); + } + + return true; + } + + if ( data.restartMap ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + } + + if ( data.shuffleTeams ) { + ShuffleTeams(); + anyChanges = true; + } + + //this section won't be encountered if the gametype was changed. But that's ok. + if ( data.mapName.c_str() && idStr::Icmp( data.mapName.c_str(), si_map.GetString() ) ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + si_map.SetString(data.mapName.c_str()); + cvarSystem->SetCVarString( "si_map", data.mapName.c_str() ); + nextMapNeeded = true; + anyChanges = true; + } + + if ( data.captureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + si_captureLimit.SetInteger( data.captureLimit ); + anyChanges = true; + } + if ( data.fragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + si_fragLimit.SetInteger( data.fragLimit ); + anyChanges = true; + } + if ( data.tourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + si_tourneyLimit.SetInteger( data.tourneyLimit ); + anyChanges = true; + } + if ( data.timeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + si_timeLimit.SetInteger( data.timeLimit ); + anyChanges = true; + } + if ( data.buying != gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + si_isBuyingEnabled.SetInteger( data.buying ); + anyChanges = true; + restartNeeded = true; + } + if ( data.autoBalance != gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + si_autobalance.SetBool( data.autoBalance ); + anyChanges = true; + } + if ( data.controlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + si_controlTime.SetInteger( data.controlTime ); + anyChanges = true; + } + + if ( nextMapNeeded ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + gameLocal.sessionCommand = "nextMap"; + return anyChanges; + } + else if ( gameLocal.NeedRestart() || restartNeeded ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + return anyChanges; +} + + +// RAVEN END + +/* +=============== +idMultiplayerGame::WriteStartState +=============== +*/ + void idMultiplayerGame::WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ) { + int i; + idEntity *ent; + + // send the start time + msg.WriteLong( matchStartedTime ); + // send the powerup states and the spectate states + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( withLocalClient || i != clientNum ) && ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + msg.WriteShort( i ); + msg.WriteShort( static_cast< idPlayer * >( ent )->inventory.powerups ); + msg.WriteBits( ent->GetInstance(), ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 ); + } + } + msg.WriteShort( MAX_CLIENTS ); +} + +/* +================ +idMultiplayerGame::ServerWriteInitialReliableMessages +================ +*/ +void idMultiplayerGame::ServerWriteInitialReliableMessages( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTSTATE ); + WriteStartState( clientNum, outMsg, false ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // we send SI in connectResponse messages, but it may have been modified already + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + gameState->SendInitialState( clientNum ); +} + +/* +================ +idMultiplayerGame::ClientReadStartState +================ +*/ +void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) { + int i, client, powerup; + + assert( gameLocal.isClient ); + + // read the state in preparation for reading snapshot updates + matchStartedTime = msg.ReadLong( ); + while ( ( client = msg.ReadShort() ) != MAX_CLIENTS ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::GetClassType() ) ); +// RAVEN END + powerup = msg.ReadShort(); + + int instance = ( msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ) ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->SetInstance( instance ); + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->Spectate( spectate ); + + // set powerups after we get instance information for this client + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( powerup & ( 1 << i ) ) { + static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0 ); + } + } + } +} + +const char* idMultiplayerGame::announcerSoundDefs[ AS_NUM_SOUNDS ] = { + // General announcements + "announce_general_one", // AS_GENERAL_ONE + "announce_general_two", // AS_GENERAL_TWO + "announce_general_three", // AS_GENERAL_THREE + "announce_general_you_win", // AS_GENERAL_YOU_WIN + "announce_general_you_lose", // AS_GENERAL_YOU_LOSE + "announce_general_fight", // AS_GENERAL_FIGHT + "announce_general_sudden_death", // AS_GENERAL_SUDDEN_DEATH + "announce_general_vote_failed", // AS_GENERAL_VOTE_FAILED + "announce_general_vote_passed", // AS_GENERAL_VOTE_PASSED + "announce_general_vote_now", // AS_GENERAL_VOTE_NOW + "announce_general_one_frag", // AS_GENERAL_ONE_FRAG + "announce_general_two_frags", // AS_GENERAL_TWO_FRAGS + "announce_general_three_frags", // AS_GENERAL_THREE_FRAGS + "announce_general_one_minute", // AS_GENERAL_ONE_MINUTE + "announce_general_five_minute", // AS_GENERAL_FIVE_MINUTE + "announce_general_prepare_to_fight", // AS_GENERAL_PREPARE_TO_FIGHT + "announce_general_quad_damage", // AS_GENERAL_QUAD_DAMAGE + "announce_general_regeneration", // AS_GENERAL_REGENERATION + "announce_general_haste", // AS_GENERAL_HASTE + "announce_general_invisibility", // AS_GENERAL_INVISIBILITY + // DM announcements + "announce_dm_you_tied_lead", // AS_DM_YOU_TIED_LEAD + "announce_dm_you_have_taken_lead", // AS_DM_YOU_HAVE_TAKEN_LEAD + "announce_dm_you_lost_lead", // AS_DM_YOU_LOST_LEAD + // Team announcements + "announce_team_enemy_score", // AS_TEAM_ENEMY_SCORES + "announce_team_you_score", // AS_TEAM_YOU_SCORE + "announce_team_teams_tied", // AS_TEAM_TEAMS_TIED + "announce_team_strogg_lead", // AS_TEAM_STROGG_LEAD + "announce_team_marines_lead", // AS_TEAM_MARINES_LEAD + "announce_team_join_marine", // AS_TEAM_JOIN_MARINE + "announce_team_join_strogg", // AS_TEAM_JOIN_STROGG + // CTF announcements + "announce_ctf_you_have_flag", // AS_CTF_YOU_HAVE_FLAG + "announce_ctf_your_team_has_flag", // AS_CTF_YOUR_TEAM_HAS_FLAG + "announce_ctf_enemy_has_flag", // AS_CTF_ENEMY_HAS_FLAG + "announce_ctf_your_team_drops_flag", // AS_CTF_YOUR_TEAM_DROPS_FLAG + "announce_ctf_enemy_drops_flag", // AS_CTF_ENEMY_DROPS_FLAG + "announce_ctf_your_flag_returned", // AS_CTF_YOUR_FLAG_RETURNED + "announce_ctf_enemy_returns_flag", // AS_CTF_ENEMY_RETURNS_FLAG + // Tourney announcements + "announce_tourney_advance", // AS_TOURNEY_ADVANCE + "announce_tourney_join_arena_one", // AS_TOURNEY_JOIN_ARENA_ONE + "announce_tourney_join_arena_two", // AS_TOURNEY_JOIN_ARENA_TWO + "announce_tourney_join_arena_three", // AS_TOURNEY_JOIN_ARENA_THREE + "announce_tourney_join_arena_four", // AS_TOURNEY_JOIN_ARENA_FOUR + "announce_tourney_join_arena_five", // AS_TOURNEY_JOIN_ARENA_FIVE + "announce_tourney_join_arena_six", // AS_TOURNEY_JOIN_ARENA_SIX + "announce_tourney_join_arena_seven", // AS_TOURNEY_JOIN_ARENA_SEVEN + "announce_tourney_join_arena_eight", // AS_TOURNEY_JOIN_ARENA_EIGHT + "announce_tourney_join_arena_waiting", // AS_TOURNEY_JOIN_ARENA_WAITING + "announce_tourney_done", // AS_TOURNEY_DONE + "announce_tourney_start", // AS_TOURNEY_START + "announce_tourney_eliminated", // AS_TOURNEY_ELIMINATED + "announce_tourney_won", // AS_TOURNEY_WON + "announce_tourney_prelims", // AS_TOURNEY_PRELIMS + "announce_tourney_quarter_finals", // AS_TOURNEY_QUARTER_FINALS + "announce_tourney_semi_finals", // AS_TOURNEY_SEMI_FINALS + "announce_tourney_final_match", // AS_TOURNEY_FINAL_MATCH + "sound/vo/mp/9_99_320_10", // AS_GENERAL_TEAM_AMMOREGEN + "sound/vo/mp/9_99_360_6" // AS_GENERAL_TEAM_DOUBLER +}; + +void idMultiplayerGame::ScheduleAnnouncerSound( announcerSound_t sound, float time, int instance, bool allowOverride ) { + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + if ( time < gameLocal.time ) { + return; + } + + if ( sound >= AS_NUM_SOUNDS ) { + return; + } + + announcerSoundNode_t* newSound = new announcerSoundNode_t; + newSound->soundShader = sound; + newSound->time = time; + newSound->announcerSoundNode.SetOwner( newSound ); + newSound->instance = instance; + newSound->allowOverride = allowOverride; + + announcerSoundNode_t* snd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = snd->announcerSoundNode.Next() ) { + if ( snd->time > newSound->time ) { + newSound->announcerSoundNode.InsertBefore( snd->announcerSoundNode ); + break; + } + } + if ( snd == NULL ) { + newSound->announcerSoundNode.AddToEnd( announcerSoundQueue ); + } +} + +void idMultiplayerGame::RemoveAnnouncerSound( int type ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + if ( snd->soundShader == type ) { + snd->announcerSoundNode.Remove( ); + delete snd; + break; + } + } + + // if a sound is currently playing, stop it + if( lastAnnouncerSound == type ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + } +} + +void idMultiplayerGame::RemoveAnnouncerSoundRange( int startType, int endType ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + for( int i = startType; i <= endType; i++ ) { + if ( snd->soundShader == i ) { + snd->announcerSoundNode.Remove( ); + delete snd; + } + } + } + + // if a sound is currently playing, stop it + for( int i = startType; i <= endType; i++ ) { + if( lastAnnouncerSound == i ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + break; + } + } +} + + +void idMultiplayerGame::ScheduleTimeAnnouncements( void ) { + if( !gameLocal.GetLocalPlayer() || !gameState ) { + // too early + return; + } + + if( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != WARMUP ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int endGameTime = 0; + + if( gameLocal.gameType == GAME_TOURNEY ) { + int arena = gameLocal.GetLocalPlayer()->GetArena(); + if( !((rvTourneyGameState*)gameState)->GetArena( arena ).IsPlaying() ) { + return; // arena is not active + } + // per-arena timelimits + endGameTime = ((rvTourneyGameState*)gameState)->GetArena( arena ).GetMatchStartTime() + ( timeLimit * 60000 ); + } else { + endGameTime = matchStartedTime + ( timeLimit * 60000 ); + } + + // clean out any preexisting announcer sounds + RemoveAnnouncerSound( AS_GENERAL_ONE_MINUTE ); + RemoveAnnouncerSound( AS_GENERAL_FIVE_MINUTE ); + + if( timeLimit > 5 ) { + ScheduleAnnouncerSound( AS_GENERAL_FIVE_MINUTE, endGameTime - (5 * 60000) ); + } + if( timeLimit > 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_MINUTE, endGameTime - (60000) ); + } + } +} + +void idMultiplayerGame::PlayAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + // if we're done playing the last sound reset override status + if( announcerPlayTime <= gameLocal.time ) { + currentSoundOverride = false; + } + + if ( announcerPlayTime > gameLocal.time && !currentSoundOverride ) { + return; + } + + // in tourney only play sounds scheduled for your current arena + if ( gameLocal.gameType == GAME_TOURNEY ) { + // go through and find the first sound to play in our arena, delete any sounds + // for other arenas we see along the way. + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + + if( snd->time > gameLocal.time ) { + return; + } + + if( snd->instance == -1 || snd->soundShader == AS_GENERAL_VOTE_NOW || snd->soundShader == AS_GENERAL_VOTE_PASSED || snd->soundShader == AS_GENERAL_VOTE_FAILED ) { + // all-instance sound + break; + } + + if( snd->instance == gameLocal.GetLocalPlayer()->GetInstance() ) { + if( snd->allowOverride && nextSnd && nextSnd->time <= gameLocal.time ) { + // this sound is OK with being over-ridden, + // and the next sound is ready to play, so go ahead and look at the next sound + snd->announcerSoundNode.Remove ( ); + delete snd; + + continue; + } else { + break; + } + } + + snd->announcerSoundNode.Remove ( ); + delete snd; + } + } else { + snd = announcerSoundQueue.Next(); + if( snd && snd->time > gameLocal.time ) { + return; + } + } + + // play the sound locally + if ( snd && snd->soundShader < AS_NUM_SOUNDS ) { + int length = 0; + + //don't play timelimit countdown announcements if game is already over + mpGameState_t state = gameState->GetMPGameState(); + if ( state == GAMEREVIEW //game is over, in scoreboard + && ( snd->soundShader == AS_GENERAL_ONE_MINUTE + || snd->soundShader == AS_GENERAL_FIVE_MINUTE ) ) { + //ignore scheduled time limit warnings that haven't executed yet + snd->announcerSoundNode.Remove(); + delete snd; + } else { + snd->announcerSoundNode.Remove(); + + gameLocal.GetLocalPlayer()->StartSoundShader( declManager->FindSound( announcerSoundDefs[ snd->soundShader ], false ), SND_CHANNEL_MP_ANNOUNCER, 0, false, &length ); + currentSoundOverride = snd->allowOverride; + lastAnnouncerSound = snd->soundShader; + + delete snd; + } + + // if sounds remain to be played, check again + announcerPlayTime = gameLocal.time + length; + } +} + +void idMultiplayerGame::ClearTeamScores ( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } +} + +void idMultiplayerGame::AddTeamScore ( int team, int amount ) { + if ( team < 0 || team >= TEAM_MAX ) { + return; + } + + teamScore[ team ] += amount; +} + +void idMultiplayerGame::AddPlayerScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount += amount; + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].fragCount ); +} + +void idMultiplayerGame::AddPlayerTeamScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount += amount; + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].teamFragCount ); +} + +void idMultiplayerGame::AddPlayerWin( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins += amount; + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ player->entityNumber ].wins ); +} + +void idMultiplayerGame::SetPlayerScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); + +} + +void idMultiplayerGame::SetPlayerTeamScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); +} + +void idMultiplayerGame::SetPlayerDeadZoneScore( idPlayer* player, float value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].deadZoneScore = value; +} + +void idMultiplayerGame::SetPlayerWin( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, value ); +} + +rvCTF_AssaultPoint* idMultiplayerGame::NextAP( int team ) { + for( int i = 0; i < assaultPoints.Num(); i++ ) { + if( assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]->GetOwner() == team ) { + continue; + } + return assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]; + } + return NULL; +} + +void idMultiplayerGame::ClientSetInstance( const idBitMsg& msg ) { + assert( gameLocal.GetLocalPlayer() ); + + idPlayer* player = gameLocal.GetLocalPlayer(); + + int instance = msg.ReadByte(); + + gameLocal.GetInstance( 0 )->SetSpawnInstanceID( instance ); + // on the client, we delete all entities, + // the server will send over new ones + gameLocal.InstanceClear(); + // set the starting offset for repopulation back to matching what the server will have + // this should be covered by setting indexes when populating the instances as well, but it doesn't hurt + gameLocal.firstFreeIndex = MAX_CLIENTS; + + player->SetArena( instance ); + player->SetInstance( instance ); + + // spawn the instance entities + gameLocal.GetInstance( 0 )->PopulateFromMessage( msg ); + + // players in other instances might have been hidden, update them + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p ) { + if( p->GetInstance() == instance ) { + p->ClientInstanceJoin(); + } else { + p->ClientInstanceLeave(); + } + } + } +} + +void idMultiplayerGame::ServerSetInstance( int instance ) { + for( int i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( ent ) { + if( ent->GetInstance() != instance ) { + ent->InstanceLeave(); + } else { + ent->InstanceJoin(); + } + } + } +} + +const char* idMultiplayerGame::GetLongGametypeName( const char* gametype ) { + if( !idStr::Icmp( gametype, "Tourney" ) ) { + return common->GetLocalizedString( "#str_107676" ); + } else if( !idStr::Icmp( gametype, "Team DM" ) ) { + return common->GetLocalizedString( "#str_107677" ); + } else if( !idStr::Icmp( gametype, "CTF" ) ) { + return common->GetLocalizedString( "#str_107678" ); + } else if( !idStr::Icmp( gametype, "DM" ) ) { + return common->GetLocalizedString( "#str_107679" ); + } else if( !idStr::Icmp( gametype, "One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107680" ); + } else if( !idStr::Icmp( gametype, "Arena CTF" ) ) { + return common->GetLocalizedString( "#str_107681" ); + } else if( !idStr::Icmp( gametype, "Arena One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107682" ); + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + } else if( !idStr::Icmp( gametype, "DeadZone" ) ) { + return common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + // RITUAL END + } + + return ""; +} + +int idMultiplayerGame::GameTypeToVote( const char *gameType ) { + if ( 0 == idStr::Icmp( gameType, "DM" ) ) { + return VOTE_GAMETYPE_DM; + } else if ( 0 == idStr::Icmp( gameType, "Tourney" ) ) { + return VOTE_GAMETYPE_TOURNEY; + } else if ( 0 == idStr::Icmp( gameType, "Team DM" ) ) { + return VOTE_GAMETYPE_TDM; + } else if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + return VOTE_GAMETYPE_CTF; + } else if ( 0 == idStr::Icmp( gameType, "Arena CTF" ) ) { + return VOTE_GAMETYPE_ARENA_CTF; + } else if ( 0 == idStr::Icmp( gameType, "DeadZone" ) ) { + return VOTE_GAMETYPE_DEADZONE; + } + + return VOTE_GAMETYPE_DM; +} + +float idMultiplayerGame::GetPlayerDeadZoneScore( idPlayer* player ) { + return playerState[ player->entityNumber ].deadZoneScore; +} + +int idMultiplayerGame::GetPlayerTime( idPlayer* player ) { + return ( gameLocal.time - player->GetConnectTime() ) / 60000; +} + +int idMultiplayerGame::GetTeamScore( idPlayer* player ) { + return GetTeamScore( player->entityNumber ); +} + +int idMultiplayerGame::GetScore( idPlayer* player ) { + return GetScore( player->entityNumber ); +} + +int idMultiplayerGame::GetWins( idPlayer* player ) { + return GetWins( player->entityNumber ); +} + +void idMultiplayerGame::EnableDamage( bool enable ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + player->fl.takedamage = enable; + } +} + +void idMultiplayerGame::ReceiveRemoteConsoleOutput( const char* output ) { + if( mainGui ) { + idStr newOutput( output ); + + if( rconHistory.Length() + newOutput.Length() > RCON_HISTORY_SIZE ) { + int removeLength = rconHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + rconHistory.Empty(); + } else { + while( (rconHistory.Length() - removeLength) + newOutput.Length() > RCON_HISTORY_SIZE ) { + removeLength = rconHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + rconHistory.Empty(); + break; + } + } + } + rconHistory = rconHistory.Right( rconHistory.Length() - removeLength ); + } + + + int consoleInputStart = newOutput.Find( "Console Input: " ); + if( consoleInputStart != -1 ) { + idStr consoleInput = newOutput.Right( newOutput.Length() - consoleInputStart - 15 ); + newOutput = newOutput.Left( consoleInputStart ); + newOutput.StripTrailing( "\n" ); + consoleInput.StripTrailing( "\n" ); + mainGui->SetStateString( "admin_console_input", consoleInput.c_str() ); + } + + if( newOutput.Length() ) { + rconHistory.Append( newOutput ); + rconHistory.Append( '\n' ); + } + + mainGui->SetStateString( "admin_console_history", rconHistory.c_str() ); + } +} + +/* +=============== +idMultiplayerGame::ShuffleTeams +=============== +*/ +void idMultiplayerGame::ShuffleTeams( void ) { + // turn off autobalance if its on + bool autoBalance = gameLocal.serverInfo.GetBool( "si_autoBalance" ); + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", false ); + } + + int loosingTeam = teamScore[ TEAM_MARINE ] < teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG; + int winningTeam = loosingTeam == TEAM_MARINE ? TEAM_STROGG : TEAM_MARINE; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( !(i % 2) ) { + // switch even players to losing team + if( rankedPlayers[ i ].First()->team != loosingTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ loosingTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } else { + if( rankedPlayers[ i ].First()->team != winningTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ winningTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } + } + + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", true ); + } +} + + +rvGameState* idMultiplayerGame::GetGameState( void ) { + return gameState; +} + +void idMultiplayerGame::SetGameType( void ) { + if( gameState ) { + delete gameState; + } + gameState = NULL; + + if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) == 0 ) ) { + gameLocal.gameType = GAME_DM; + gameState = new rvDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Tourney" ) == 0 ) ) { + gameLocal.gameType = GAME_TOURNEY; + gameState = new rvTourneyGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) == 0 ) ) { + gameLocal.gameType = GAME_TDM; + gameState = new rvTeamDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DeadZone" ) == 0 ) ) { + gameLocal.gameType = GAME_DEADZONE; + gameState = new riDZGameState; + } else { + gameLocal.Error( "idMultiplayerGame::SetGameType() - Unknown gametype '%s'\n", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } + + // force entity filter to gametype name in multiplayer + if( gameLocal.gameType != GAME_SP ) { + gameLocal.serverInfo.Set( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + // also set as a CVar for when serverinfo is rescanned + cvarSystem->SetCVarString( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } +} + +//asalmon: need to access total frags for a team and total score for a team +int idMultiplayerGame::GetTeamsTotalFrags( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + if(rankedPlayers[ j ].First()->team == i) + { + total += GetScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +int idMultiplayerGame::GetTeamsTotalScore( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + idPlayer foo; + + if(rankedPlayers[ j ].First()->team == i) + { + total += GetTeamScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +/* +=============== +idMultiplayerGame::PickMap +=============== +*/ +bool idMultiplayerGame::PickMap( idStr gameType, bool checkOnly ) { + + idStrList maps; + int miss = 0; + const idDecl *mapDecl; + const idDeclEntityDef *mapDef; + int index = 0; + int btype; + const char* mapName; + + mapName = si_map.GetString(); + + // if we didn't set up a gametype, grab the current game type. + if ( gameType.IsEmpty() ) { + gameType = si_gameType.GetString(); + } + + // if we're playing a map of this gametype, don't change. + mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); + mapDef = static_cast( mapDecl ); + if ( mapDef ) { + btype = mapDef->dict.GetInt( gameType ); + if ( btype ) { + // ( not sure what the gloubi boulga is about re-setting si_map two ways after reading it at the start of the function already ) + cvarSystem->SetCVarString( "si_map", mapName ); + si_map.SetString( mapName ); + return false; + } + } + + if ( checkOnly ) { + // always allow switching to DM mode, whatever the settings on the map ( DM should always be possible ) + if ( !idStr::Icmp( si_gameType.GetString(), "DM" ) ) { + return false; + } + // don't actually change anything, indicate we would + return true; + } + + int i; + idFileList *files; + idStrList fileList; + + int count = 0; + + files = fileSystem->ListFiles( "maps/mp", ".map" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + fileList.AddUnique( va( "mp/%s", files->GetList()[i].c_str() ) ); + } + fileSystem->FreeFileList( files ); + + files = fileSystem->ListFiles( "maps/mp", ".mapc" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + idStr fixedExtension(files->GetList()[i]); + fixedExtension.SetFileExtension("map"); + fileList.AddUnique( va( "mp/%s", fixedExtension.c_str() ) ); + } + + fileList.Sort(); + + idStr name; + idStr cycle; + + //Populate the map list + for ( i = 0; i < fileList.Num(); i++) { + //Add only MP maps. + if(!idStr::FindText(fileList[i].c_str(), "mp/")) + { + maps.AddUnique(fileList[i].c_str()); + } + } + maps.Sort(); + + if(maps.Num() > 0) + { + while(miss < 100) + { + index = gameLocal.random.RandomInt( maps.Num() ); + mapName = maps[index].c_str(); + + mapDecl = declManager->FindType( DECL_MAPDEF, mapName, false ); + mapDef = static_cast( mapDecl ); + if ( mapDef ) { + btype = mapDef->dict.GetInt( gameType ); + if(btype) + { + cvarSystem->SetCVarString("si_map",mapName); + si_map.SetString( mapName ); + return true; + + } + } + miss++; + + } + + } + + //something is wrong and there are no maps for this game type. This should never happen. + gameLocal.Error( "No maps found for game type: %s.\n", gameType.c_str() ); + return false; +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( int rank, bool tied, int score ) { + char* placeString; + + if( rank == 0 ) { + //"1st^0 place with" + placeString = va( "%s%s %d", S_COLOR_BLUE, common->GetLocalizedString( "#str_107689" ), score ); + } else if( rank == 1 ) { + //"2nd^0 place with" + placeString = va( "%s%s %d", S_COLOR_RED, common->GetLocalizedString( "#str_107690" ), score ); + } else if( rank == 2 ) { + //"3rd^0 place with" + placeString = va( "%s%s %d", S_COLOR_YELLOW, common->GetLocalizedString( "#str_107691" ), score ); + } else { + //"th^0 place with" + placeString = va( "%d%s %d", rank + 1, common->GetLocalizedString( "#str_107692" ), score ); + } + + if( tied ) { + //Tied for + return va( "%s %s", common->GetLocalizedString( "#str_107693" ), placeString ); + } else { + return placeString; + } +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( idPlayer* player ) { + if( player == NULL ) { + return ""; + } + + bool tied = false; + int rank = GetPlayerRank( player, tied ); + return GetPlayerRankText( rank, tied, GetScore( player ) ); +} + +/* +=============== +idMultiplayerGame::WriteNetworkInfo +=============== +*/ +void idMultiplayerGame::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteStartState( clientNum, msg, true ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); + + gameState->WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idMultiplayerGame::ReadNetworkInfo +=============== +*/ +void idMultiplayerGame::ReadNetworkInfo( idFile* file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ClientReadStartState( msg ); + + gameState->ReadNetworkInfo( file, clientNum ); +} + +void idMultiplayerGame::AddPrivatePlayer( int clientId ) { + privateClientIds.Append( clientId ); +} + +void idMultiplayerGame::RemovePrivatePlayer( int clientId ) { + for( int i = 0; i < privateClientIds.Num(); i++ ) { + if( clientId == privateClientIds[ i ] ) { + privateClientIds.RemoveIndex( i ); + i--; + } + } +} + +void idMultiplayerGame::UpdatePrivatePlayerCount( void ) { + if ( !gameLocal.isServer ) { + return; + } + + int numPrivatePlayers = 0; + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( privatePlayers & (1 << i) ) { + if( gameLocal.entities[ i ] ) { + numPrivatePlayers++; + } else { + privatePlayers &= ~( 1 << i ); + } + } + } + + cvarSystem->SetCVarInteger( "si_numPrivatePlayers", numPrivatePlayers ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); +} + +void idMultiplayerGame::SetFlagEntity( idEntity* ent, int team ) { + assert( ( team == TEAM_STROGG || team == TEAM_MARINE ) ); + + flagEntities[ team ] = ent; +} + +idEntity* idMultiplayerGame::GetFlagEntity( int team ) { + assert( team >= 0 && team < TEAM_MAX ); + + return flagEntities[ team ]; +} + + +// +void idMultiplayerGame::CheckTeamBalance_f( const idCmdArgs &args ) { + + if ( args.Argc() < 5 ) { + return; + } + + idPlayer *localPlayer = gameLocal.GetClientByNum( gameLocal.localClientNum ); + + const char *team = args.Argv(1); + const char *yesEvent = args.Argv(2); + const char *noEvent = args.Argv(3); + const char *sameTeamEvent = args.Argv(4); + + if ( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) || !gameLocal.IsTeamGame() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + return; + } + + int teamCount[2]; + teamCount[0] = teamCount[1] = 0; + + for ( int i = 0; i < gameLocal.numClients; ++i ) { + idEntity *ent = gameLocal.entities[i]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != localPlayer ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + if ( idStr::Icmp( team, "marine" ) == 0 ) { + if ( localPlayer->team == TEAM_MARINE && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_MARINE] > teamCount[TEAM_STROGG] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } else { + + if ( localPlayer->team == TEAM_STROGG && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_STROGG] > teamCount[TEAM_MARINE] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } +} + +/* +================ +idMultiplayerGame::LocalizeGametype + +dupe of rvServerScanGUI::LocalizeGametype +================ +*/ +const char *idMultiplayerGame::LocalizeGametype( void ) { + + const char *gameType; + + gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + localisedGametype = gameType; + + if( !idStr::Icmp( gameType, "DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110011" ); + } + if( !idStr::Icmp( gameType, "Tourney" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110012" ); + } + if( !idStr::Icmp( gameType, "Team DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110013" ); + } + if( !idStr::Icmp( gameType, "CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110014" ); + } + if( !idStr::Icmp( gameType, "Arena CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110015" ); + } + if( !idStr::Icmp( gameType, "DeadZone" ) ) { + localisedGametype = common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + } + + return( localisedGametype.c_str() ); +} + +int idMultiplayerGame::VerifyTeamSwitch( int wantTeam, idPlayer *player ) { + idEntity* ent; + int teamCount[ TEAM_MAX ]; + int balanceTeam = -1; + + if( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) ) { + return wantTeam; + } + + teamCount[ TEAM_MARINE ] = teamCount[ TEAM_STROGG ] = 0; + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != player ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ TEAM_MARINE ] > teamCount[ TEAM_STROGG ] ) { + balanceTeam = TEAM_STROGG; + } else if ( teamCount[ TEAM_STROGG ] > teamCount[ TEAM_MARINE ] ) { + balanceTeam = TEAM_MARINE; + } + + return (balanceTeam == -1) ? wantTeam : balanceTeam; +} + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================ +idMultiplayerGame::NumberOfPlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfPlayersOnTeam( int team ) +{ + int teamPlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + teamPlayerCount ++; + } + } + } + + return teamPlayerCount; +} + + +/* +================ +idMultiplayerGame::NumberOfAlivePlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfAlivePlayersOnTeam( int team ) +{ + int teamAlivePlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team && entPlayer->allowedToRespawn ) + { + teamAlivePlayerCount ++; + } + } + } + + return teamAlivePlayerCount; +} + + +// RITUAL END + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +/* +================ +idMultiplayerGame::OpenLocalBuyMenu +================ +*/ +void idMultiplayerGame::OpenLocalBuyMenu( void ) +{ + // Buy menu work in progress + //if ( gameLocal.mpGame.GetCurrentMenu() == 4 ) + //{ + // return; + //} + + if ( currentMenu == 4 ) + return; // Already open + + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.mpGame.nextMenu = 4; +} + +/* +================ +idMultiplayerGame::RedrawLocalBuyMenu +================ +*/ +void idMultiplayerGame::RedrawLocalBuyMenu( void ) +{ + if ( !buyMenu ) + return; + + SetupBuyMenuItems(); + buyMenu->HandleNamedEvent( "update_buymenu" ); +} + + +/* +================ +idMultiplayerGame::GiveCashToTeam +================ +*/ +void idMultiplayerGame::GiveCashToTeam( int team, float cashAmount ) +{ + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + entPlayer->GiveCash( cashAmount ); + } + } + } + +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode( void ) { + if ( !gameLocal.isMultiplayer ) { + return false; + } + + if ( gameLocal.gameType != GAME_TOURNEY ) { + return gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ); + } + + return false; +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedRightNow +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedRightNow( void ) +{ + return ( IsBuyingAllowedInTheCurrentGameMode() && isBuyingAllowedRightNow ); +} + + +void idMultiplayerGame::AddTeamPowerup(int powerup, int time, int team) +{ + int i; + for ( i=0; i. + +=========================================================================== +*/ + +// RAVEN BEGIN +// ddynerman: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __MULTIPLAYERGAME_H__ +#define __MULTIPLAYERGAME_H__ + +/* +=============================================================================== + + Quake IV multiplayer + +=============================================================================== +*/ + +#include "mp/Buying.h" +class idPlayer; +class rvCTF_AssaultPoint; +class rvItemCTFFlag; +typedef enum { + GAME_SP, + GAME_DM, + GAME_TOURNEY, + GAME_TDM, + // bdube: added ctf + GAME_CTF, + // ddynerman: new gametypes + GAME_1F_CTF, + GAME_ARENA_CTF, + GAME_ARENA_1F_CTF, // is not used, but leaving it in the list so I don't offset GAME_DEADZONE + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + GAME_DEADZONE, + NUM_GAME_TYPES, +// RITUAL END +} gameType_t; + + +// ddynerman: teams +typedef enum { + TEAM_NONE = -1, + TEAM_MARINE, + TEAM_STROGG, + TEAM_MAX, +} team_t; + +// shouchard: server admin command types +typedef enum { + SERVER_ADMIN_KICK, + SERVER_ADMIN_BAN, + SERVER_ADMIN_REMOVE_BAN, + SERVER_ADMIN_FORCE_SWITCH, +} serverAdmin_t; + +// shouchard: vote struct for packing up interface values to be handled later +// note that we have two mechanisms for dealing with vote data that +// should be consolidated: this one that handles the interface and +// multi-field votes and the one that handles console commands and +// single line votes. +struct voteStruct_t { + int m_fieldFlags; // flags for which fields are valid + int m_kick; // id of the player + idStr m_map; // name of the map + int m_gameType; // game type enum + int m_timeLimit; + int m_fragLimit; + int m_tourneyLimit; + int m_captureLimit; + int m_buying; + int m_teamBalance; + int m_controlTime; + // restart is a flag only + // nextmap is a flag only but we don't technically support it (but doom had it so it's here) +}; + +typedef enum { + VOTEFLAG_RESTART = 0x0001, + VOTEFLAG_BUYING = 0x0002, + VOTEFLAG_TEAMBALANCE = 0x0004, + VOTEFLAG_SHUFFLE = 0x0008, + VOTEFLAG_KICK = 0x0010, + VOTEFLAG_MAP = 0x0020, + VOTEFLAG_GAMETYPE = 0x0040, + VOTEFLAG_TIMELIMIT = 0x0080, + VOTEFLAG_TOURNEYLIMIT = 0x0100, + VOTEFLAG_CAPTURELIMIT = 0x0200, + VOTEFLAG_FRAGLIMIT = 0x0400, + VOTEFLAG_CONTROLTIME = 0x0800, +} voteFlag_t; + +#define NUM_VOTES 11 +#define MAX_PRINT_LEN 128 + +// more compact than a chat line +typedef enum { + MSG_SUICIDE = 0, + MSG_KILLED, + MSG_KILLEDTEAM, + MSG_DIED, + MSG_VOTE, + MSG_VOTEPASSED, + MSG_VOTEFAILED, + MSG_SUDDENDEATH, + MSG_FORCEREADY, + MSG_JOINEDSPEC, + MSG_TIMELIMIT, + MSG_FRAGLIMIT, + MSG_CAPTURELIMIT, + MSG_TELEFRAGGED, + MSG_JOINTEAM, + MSG_HOLYSHIT, + MSG_COUNT +} msg_evt_t; + +typedef enum { + PLAYER_VOTE_NONE, + PLAYER_VOTE_NO, + PLAYER_VOTE_YES, + PLAYER_VOTE_WAIT // mark a player allowed to vote +} playerVote_t; + +typedef enum { + PRM_AUTO, + PRM_SCORE, + PRM_TEAM_SCORE, + PRM_TEAM_SCORE_PLUS_SCORE, + PRM_WINS +} playerRankMode_t; + +enum announcerSound_t { + // General announcements + AS_GENERAL_ONE, + AS_GENERAL_TWO, + AS_GENERAL_THREE, + AS_GENERAL_YOU_WIN, + AS_GENERAL_YOU_LOSE, + AS_GENERAL_FIGHT, + AS_GENERAL_SUDDEN_DEATH, + AS_GENERAL_VOTE_FAILED, + AS_GENERAL_VOTE_PASSED, + AS_GENERAL_VOTE_NOW, + AS_GENERAL_ONE_FRAG, + AS_GENERAL_TWO_FRAGS, + AS_GENERAL_THREE_FRAGS, + AS_GENERAL_ONE_MINUTE, + AS_GENERAL_FIVE_MINUTE, + AS_GENERAL_PREPARE_TO_FIGHT, + AS_GENERAL_QUAD_DAMAGE, + AS_GENERAL_REGENERATION, + AS_GENERAL_HASTE, + AS_GENERAL_INVISIBILITY, + // DM announcements + AS_DM_YOU_TIED_LEAD, + AS_DM_YOU_HAVE_TAKEN_LEAD, + AS_DM_YOU_LOST_LEAD, + // Team announcements + AS_TEAM_ENEMY_SCORES, + AS_TEAM_YOU_SCORE, + AS_TEAM_TEAMS_TIED, + AS_TEAM_STROGG_LEAD, + AS_TEAM_MARINES_LEAD, + AS_TEAM_JOIN_MARINE, + AS_TEAM_JOIN_STROGG, + // CTF announcements + AS_CTF_YOU_HAVE_FLAG, + AS_CTF_YOUR_TEAM_HAS_FLAG, + AS_CTF_ENEMY_HAS_FLAG, + AS_CTF_YOUR_TEAM_DROPS_FLAG, + AS_CTF_ENEMY_DROPS_FLAG, + AS_CTF_YOUR_FLAG_RETURNED, + AS_CTF_ENEMY_RETURNS_FLAG, + // Tourney announcements + AS_TOURNEY_ADVANCE, + AS_TOURNEY_JOIN_ARENA_ONE, + AS_TOURNEY_JOIN_ARENA_TWO, + AS_TOURNEY_JOIN_ARENA_THREE, + AS_TOURNEY_JOIN_ARENA_FOUR, + AS_TOURNEY_JOIN_ARENA_FIVE, + AS_TOURNEY_JOIN_ARENA_SIX, + AS_TOURNEY_JOIN_ARENA_SEVEN, + AS_TOURNEY_JOIN_ARENA_EIGHT, + AS_TOURNEY_JOIN_ARENA_WAITING, + AS_TOURNEY_DONE, + AS_TOURNEY_START, + AS_TOURNEY_ELIMINATED, + AS_TOURNEY_WON, + AS_TOURNEY_PRELIMS, + AS_TOURNEY_QUARTER_FINALS, + AS_TOURNEY_SEMI_FINALS, + AS_TOURNEY_FINAL_MATCH, + AS_GENERAL_TEAM_AMMOREGEN, + AS_GENERAL_TEAM_DOUBLER, + AS_NUM_SOUNDS +}; + +typedef struct mpPlayerState_s { + int ping; // player ping + int fragCount; // kills + int teamFragCount; // teamplay awards + int deadZoneScore; // Score in dead zone + int wins; + playerVote_t vote; // player's vote + bool scoreBoardUp; // toggle based on player scoreboard button, used to activate de-activate the scoreboard gui + bool ingame; +} mpPlayerState_t; + +const int MAX_INSTANCES = 8; + +const int NUM_CHAT_NOTIFY = 5; +const int CHAT_FADE_TIME = 400; +const int FRAGLIMIT_DELAY = 2000; +const int CAPTURELIMIT_DELAY = 750; + +const int MP_PLAYER_MINFRAGS = -100; +const int MP_PLAYER_MAXFRAGS = 999; +const int MP_PLAYER_MAXWINS = 100; +const int MP_PLAYER_MAXPING = 999; + +const int MP_PLAYER_MAXKILLS = 999; +const int MP_PLAYER_MAXDEATHS = 999; + +const int MAX_AP = 5; + +const int CHAT_HISTORY_SIZE = 2048; +const int RCON_HISTORY_SIZE = 4096; + +const int KILL_NOTIFICATION_LEN = 256; +//RAVEN BEGIN +//asalmon: update stats for Xenon +#ifdef _XENON +const int XENON_STAT_UPDATE_INTERVAL = 1000; +#endif + +const int ASYNC_PLAYER_FRAG_BITS = -idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS ); // player can have negative frags +const int ASYNC_PLAYER_WINS_BITS = idMath::BitsForInteger( MP_PLAYER_MAXWINS ); +const int ASYNC_PLAYER_PING_BITS = idMath::BitsForInteger( MP_PLAYER_MAXPING ); +const int ASYNC_PLAYER_INSTANCE_BITS = idMath::BitsForInteger( MAX_INSTANCES ); +const int ASYNC_PLAYER_DEATH_BITS = idMath::BitsForInteger( MP_PLAYER_MAXDEATHS ); +const int ASYNC_PLAYER_KILL_BITS = idMath::BitsForInteger( MP_PLAYER_MAXKILLS ); +//RAVEN END +//RITUAL BEGIN +const int MAX_TEAM_POWERUPS = 5; +//RITUAL END +// ddynerman: game state +#include "mp/GameState.h" + +typedef struct mpChatLine_s { + idStr line; + short fade; // starts high and decreases, line is removed once reached 0 +} mpChatLine_t; + +typedef struct mpBanInfo_s { + idStr name; + char guid[ CLIENT_GUID_LENGTH ]; +// unsigned char ip[ 15 ]; +} mpBanInfo_t; + +class idPhysics_Player; + +class idMultiplayerGame { + + // rvGameState manages our state + friend class rvGameState; + +public: + + idMultiplayerGame(); + + void Shutdown( void ); + + // resets everything and prepares for a match + void Reset( void ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + // Made this public so that level heap can be emptied. + void Clear( void ); +// RAVEN END + + // setup local data for a new player + void SpawnPlayer( int clientNum ); + + // Run the MP Game + void Run( void ); + + // Run the local client + void ClientRun( void ); + void ClientEndFrame( void ); + + // Run common code (client & server) + void CommonRun( void ); + + // draws mp hud, scoredboard, etc.. + bool Draw( int clientNum ); + + + // updates a player vote + void PlayerVote( int clientNum, playerVote_t vote ); + + // updates frag counts and potentially ends the match in sudden death + void PlayerDeath( idPlayer *dead, idPlayer *killer, int methodOfDeath ); + + void AddChatLine( const char *fmt, ... ) id_attribute((format(printf,2,3))); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + void OnBuyModeTeamVictory( int winningTeam ); +// squirrel: added DeadZone multiplayer mode + void OnDeadZoneTeamVictory( int winningTeam ); +// RITUAL END + + void UpdateMainGui( void ); +// RAVEN BEGIN +// bdube: global pickup sounds (powerups, etc) + // Global item acquire sounds + void PlayGlobalItemAcquireSound ( int entityDefIndex ); + + bool CanTalk( idPlayer *from, idPlayer *to, bool echo ); + void ReceiveAndForwardVoiceData( int clientNum, const idBitMsg &inMsg, int messageType ); + +#ifdef _USE_VOICECHAT +// jscott: game side voice comms + void XmitVoiceData( void ); + void ReceiveAndPlayVoiceData( const idBitMsg &inMsg ); +#endif + +// jshepard: selects a map at random that will run with the current game type + bool PickMap( idStr gameType, bool checkOnly = false ); + void ClearVote( int clientNum = -1 ); + void ResetRconGuiStatus( void ); + +// RAVEN END + + idUserInterface* StartMenu( void ); + + const char* HandleGuiCommands( const char *menuCommand ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void ShuffleTeams( void ); + void SetGameType( void ); + void SetMatchStartedTime( int time ) { matchStartedTime = time; } + + rvGameState* GetGameState( void ); + + + void PrintMessageEvent( int to, msg_evt_t evt, int parm1 = -1, int parm2 = -1 ); + void PrintMessage( int to, const char* message ); + + void DisconnectClient( int clientNum ); + static void ForceReady_f( const idCmdArgs &args ); + static void DropWeapon_f( const idCmdArgs &args ); + static void MessageMode_f( const idCmdArgs &args ); + static void VoiceChat_f( const idCmdArgs &args ); + static void VoiceChatTeam_f( const idCmdArgs &args ); + + +// RAVEN BEGIN +// shouchard: added console commands to mute/unmute voice chat + static void VoiceMute_f( const idCmdArgs &args ); + static void VoiceUnmute_f( const idCmdArgs &args ); + +// jshepard: command wrappers + static void ForceTeamChange_f( const idCmdArgs& args ); + static void RemoveClientFromBanList_f( const idCmdArgs& args ); + +// autobalance helper for the guis + static void CheckTeamBalance_f( const idCmdArgs &args ); + +// activates the admin console when a rcon password challenge returns. + void ProcessRconReturn( bool success ); +// RAVEN END + + typedef enum { + VOTE_RESTART = 0, + VOTE_TIMELIMIT, + VOTE_FRAGLIMIT, + VOTE_GAMETYPE, + VOTE_KICK, + VOTE_MAP, + VOTE_BUYING, + VOTE_NEXTMAP, +// RAVEN BEGIN +// shouchard: added capturelimit, round limit, and autobalance to vote flags + VOTE_CAPTURELIMIT, + VOTE_ROUNDLIMIT, + VOTE_AUTOBALANCE, + VOTE_MULTIFIELD, // all the "packed" vote functions +// RAVEN END + VOTE_CONTROLTIME, + VOTE_COUNT, + VOTE_NONE + } vote_flags_t; + + typedef enum { + VOTE_UPDATE, + VOTE_FAILED, + VOTE_PASSED, // passed, but no reset yet + VOTE_ABORTED, + VOTE_RESET // tell clients to reset vote state + } vote_result_t; + +// RAVEN BEGIN +// shouchard: added enum to remove magic numbers + typedef enum { + VOTE_GAMETYPE_DM = 0, + VOTE_GAMETYPE_TOURNEY, + VOTE_GAMETYPE_TDM, + VOTE_GAMETYPE_CTF, + VOTE_GAMETYPE_ARENA_CTF, +//RITUAL BEGIN +// + VOTE_GAMETYPE_DEADZONE, +//RITUAL END + VOTE_GAMETYPE_COUNT + } vote_gametype_t; +// RAVEN END + + static void Vote_f( const idCmdArgs &args ); + static void CallVote_f( const idCmdArgs &args ); + void ClientCallVote( vote_flags_t voteIndex, const char *voteValue ); + void ServerCallVote( int clientNum, const idBitMsg &msg ); + void ClientStartVote( int clientNum, const char *voteString ); + void ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *voteValue ); +// RAVEN BEGIN +// shouchard: multiline vote support + void ClientUpdateVote( vote_result_t result, int yesCount, int noCount, const voteStruct_t &voteData ); +// RAVEN END + void CastVote( int clientNum, bool vote ); + void ExecuteVote( void ); +// RAVEN BEGIN +// shouchard: multiline vote handlers + void ClientCallPackedVote( const voteStruct_t &voteData ); + void ServerCallPackedVote( int clientNum, const idBitMsg &msg ); + void ClientStartPackedVote( int clientNum, const voteStruct_t &voteData ); + void ServerStartPackedVote( int clientNum, const voteStruct_t &voteData ); + void ExecutePackedVote( void ); + const char * LocalizeGametype( void ); +// RAVEN END + + void WantKilled( int clientNum ); + int NumActualClients( bool countSpectators, int *teamcount = NULL ); + void DropWeapon( int clientNum ); + void MapRestart( void ); + void JoinTeam( const char* team ); + // called by idPlayer whenever it detects a team change (init or switch) + void SwitchToTeam( int clientNum, int oldteam, int newteam ); + + bool IsPureReady( void ) const; + void ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ); + void ProcessVoiceChat( int clientNum, bool team, int index ); +// RAVEN BEGIN +// shouchard: added commands to mute/unmute voice chat + void ClientVoiceMute( int clientNum, bool mute ); + int GetClientNumFromPlayerName( const char *playerName ); + void ServerHandleVoiceMuting( int clientSrc, int clientDest, bool mute ); +// shouchard: fixing a bug in multiplayer where round timer sounds (5 minute +// warning, etc.) don't go away at the end of the round. + void ClearAnnouncerSounds( void ); +// shouchard: server admin stuff + typedef struct + { + bool restartMap; + idStr mapName; + int gameType; + int captureLimit; + int fragLimit; + int tourneyLimit; + int timeLimit; + int minPlayers; + int controlTime; + bool buying; + bool autoBalance; + bool shuffleTeams; + } serverAdminData_t; + + void HandleServerAdminBanPlayer( int clientNum ); + void HandleServerAdminRemoveBan( const char * info ); + void HandleServerAdminKickPlayer( int clientNum ); + void HandleServerAdminForceTeamSwitch( int clientNum ); + bool HandleServerAdminCommands( serverAdminData_t &data ); +// RAVEN END + +// RITUAL BEGIN + typedef struct mpTeamPowerups_s { + int powerup; + int time; + bool update; + int endTime; + } mpTeamPowerups_t; + + mpTeamPowerups_t teamPowerups[TEAM_MAX][MAX_TEAM_POWERUPS]; + + void AddTeamPowerup(int powerup, int time, int team); + void UpdateTeamPowerups(); + void SetUpdateForTeamPowerups(int team); +// RITUAL END + + void Precache( void ); + + // throttle UI switch rates + void ThrottleUserInfo( void ); + void ToggleSpectate( void ); + void ToggleReady( void ); + void ToggleTeam( void ); + + void ClearFrags( int clientNum ); + + void EnterGame( int clientNum ); + bool CanPlay( idPlayer *p ); + bool IsInGame( int clientNum ); + bool WantRespawn( idPlayer *p ); + + void ServerWriteInitialReliableMessages( int clientNum ); + void ClientReadStartState( const idBitMsg &msg ); + + void ServerClientConnect( int clientNum ); + + void PlayerStats( int clientNum, char *data, const int len ); + + void AddTeamScore ( int team, int amount ); + void AddPlayerScore( idPlayer* player, int amount ); + void AddPlayerTeamScore( idPlayer* player, int amount ); + void AddPlayerWin( idPlayer* player, int amount ); + void SetPlayerTeamScore( idPlayer* player, int value ); + void SetPlayerDeadZoneScore( idPlayer* player, float value ); + void SetPlayerScore( idPlayer* player, int value ); + void SetPlayerWin( idPlayer* player, int value ); + void SetHudOverlay( idUserInterface* overlay, int duration ); + + void ClearMap ( void ); + + void EnableDamage( bool enable = true ); + + idPlayer* GetRankedPlayer( int i ); + int GetRankedPlayerScore( int i ); + int GetNumRankedPlayers( void ); + + idPlayer* GetUnrankedPlayer( int i ); + int GetNumUnrankedPlayers( void ); + + int GetScore( int i ); + int GetScore( idPlayer* player ); + + int GetTeamScore( int i ); + int GetTeamScore( idPlayer* player ); + + int GetWins( int i ); + int GetWins( idPlayer* player ); + + // asalmon: Get the score for a team. + int GetScoreForTeam( int i ); + int GetTeamsTotalFrags( int i ); + int GetTeamsTotalScore( int i ); + idUserInterface *GetMainGUI() {return mainGui;} + + float GetPlayerDeadZoneScore(idPlayer* player); + + int TeamLeader( void ); + + int GetPlayerTime( idPlayer* player ); + + const char* GetLongGametypeName( const char* gametype ); + int GameTypeToVote( const char *gameType ); + + void ReceiveRemoteConsoleOutput( const char* output ); + + void ClientSetInstance( const idBitMsg& msg ); + void ServerSetInstance( int instance ); + + void AddPrivatePlayer( int clientId ); + void RemovePrivatePlayer( int clientId ); + +//RAVEN BEGIN +//asalmon: Xenon scoreboard update +#ifdef _XENON + void UpdateXenonScoreboard( idUserInterface *scoreBoard ); + int lastScoreUpdate; +// mekberg: for selecting local player + void SelectLocalPlayer( idUserInterface *scoreBoard ); +#endif +//RAVEN END + int VerifyTeamSwitch( int wantTeam, idPlayer *player ); + + void RemoveAnnouncerSound( int type ); + void RemoveAnnouncerSoundRange( int startType, int endType ); + void ScheduleAnnouncerSound ( announcerSound_t sound, float time, int instance = -1, bool allowOverride = false ); + void ScheduleTimeAnnouncements( void ); +// RAVEN END + + void SendDeathMessage( idPlayer *attacker, idPlayer *victim, int methodOfDeath ); + void ReceiveDeathMessage( idPlayer *attacker, int attackerScore, idPlayer *victim, int victimScore, int methodOfDeath ); + + rvCTF_AssaultPoint* NextAP( int team ); + int OpposingTeam( int team ); + + idList > assaultPoints; + + // Buying Manager - authority for buying system game balance constants (awards, + // costs, etc.) + riBuyingManager mpBuyingManager; + + idUserInterface* statSummary; // stat summary + rvTourneyGUI tourneyGUI; + + void ShowStatSummary( void ); + bool CanCapture( int team ); + void FlagCaptured( idPlayer *player ); + + void UpdatePlayerRanks( playerRankMode_t rankMode = PRM_AUTO ); + void UpdateTeamRanks( void ); + void UpdateHud( idUserInterface* _mphud ); + idPlayer * FragLimitHit( void ); + idPlayer * FragLeader( void ); + bool TimeLimitHit( void ); + int GetCurrentMenu( void ) { return currentMenu; } + + void SetFlagEntity( idEntity* ent, int team ); + idEntity* GetFlagEntity( int team ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile* file, int clientNum ); + + void SetShaderParms( renderView_t *view ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int NumberOfPlayersOnTeam( int team ); + int NumberOfAlivePlayersOnTeam( int team ); + void ReportZoneControllingPlayer( idPlayer* player ); + void ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger = 0); + bool IsValidTeam(int team); + void ControlZoneStateChanged( int team ); + + int powerupCount; + int prevAnnouncerSnd; + int defaultWinner; + int deadZonePowerupCount; + dzState_t dzState[ TEAM_MAX ]; + float marineScoreBarPulseAmount; + float stroggScoreBarPulseAmount; +// RITUAL END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + bool isBuyingAllowedRightNow; + + void OpenLocalBuyMenu( void ); + void RedrawLocalBuyMenu( void ); + void GiveCashToTeam( int team, float cashAmount ); + bool IsBuyingAllowedInTheCurrentGameMode( void ); + bool IsBuyingAllowedRightNow( void ); +// RITUAL END + static const char* teamNames[ TEAM_MAX ]; + +private: + static const char *MPGuis[]; + static const char *ThrottleVars[]; + static const char *ThrottleVarsInEnglish[]; + static const int ThrottleDelay[]; + + char killNotificationMsg[ KILL_NOTIFICATION_LEN ]; + + int pingUpdateTime; // time to update ping + + mpPlayerState_t playerState[ MAX_CLIENTS ]; + + // game state + rvGameState* gameState; + + // vote vars + vote_flags_t vote; // active vote or VOTE_NONE + int voteTimeOut; // when the current vote expires + int voteExecTime; // delay between vote passed msg and execute + int yesVotes; // counter for yes votes + int noVotes; // and for no votes + idStr voteValue; // the data voted upon ( server ) + idStr voteString; // the vote string ( client ) + bool voted; // hide vote box ( client ) + int kickVoteMap[ MAX_CLIENTS ]; +// RAVEN BEGIN +// shouchard: names for kickVoteMap + idStr kickVoteMapNames[ MAX_CLIENTS ]; + voteStruct_t currentVoteData; // used for multi-field votes +// RAVEN END + + idStr localisedGametype; + + // time related + int matchStartedTime; // time current match started + + // guis +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + //int sqRoundNumber; // round number in DeadZone; match expires when this equals "sq_numRoundsPerMatch" (cvar) +// squirrel: Mode-agnostic buymenus + idUserInterface *buyMenu; // buy menu +// RITUAL END + idUserInterface *scoreBoard; // scoreboard + idUserInterface *mainGui; // ready / nick / votes etc. + idListGUI *mapList; + idUserInterface *msgmodeGui; // message mode + int currentMenu; // 0 - none, 1 - mainGui, 2 - msgmodeGui + int nextMenu; // if 0, will do mainGui + bool bCurrentMenuMsg; // send menu state updates to server + + + enum { + MPLIGHT_CTF_MARINE, + MPLIGHT_CTF_STROGG, + MPLIGHT_QUAD, + MPLIGHT_HASTE, + MPLIGHT_REGEN, + MPLIGHT_MAX + }; + + int lightHandles[ MPLIGHT_MAX ]; + renderLight_t lights[ MPLIGHT_MAX ]; + + // chat buffer + idStr chatHistory; + + // rcon buffer + idStr rconHistory; + +//RAVEN BEGIN +//asalmon: Need to refresh stats periodically if the player is looking at stats + int currentStatClient; + int currentStatTeam; +//RAVEN END + +public: + // current player rankings + idList > rankedPlayers; + idList unrankedPlayers; + + rvPair rankedTeams[ TEAM_MAX ]; + +private: + + int lastVOAnnounce; + + int lastReadyToggleTime; + bool pureReady; // defaults to false, set to true once server game is running with pure checksums + bool currentSoundOverride; + int switchThrottle[ 3 ]; + int voiceChatThrottle; + + void SetupBuyMenuItems(); + + idList privateClientIds; + int privatePlayers; + + // player who's rank info we're displaying + idEntityPtr rankTextPlayer; + + idEntityPtr flagEntities[ TEAM_MAX ]; + idEntityPtr flagCarriers[ TEAM_MAX ]; + + // updates the passed gui with current score information + void UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ); + + // bdube: test scoreboard + void UpdateTestScoreboard( idUserInterface *scoreBoard ); + + // ddynerman: gametype specific scoreboard + void UpdateScoreboard( idUserInterface *scoreBoard ); + + void UpdateDMScoreboard( idUserInterface *scoreBoard ); + void UpdateTeamScoreboard( idUserInterface *scoreBoard ); + void UpdateSummaryBoard( idUserInterface *scoreBoard ); + + int GetPlayerRank( idPlayer* player, bool& isTied ); + char* GetPlayerRankText( idPlayer* player ); + char* GetPlayerRankText( int rank, bool tied, int score ); + + const char* BuildSummaryListString( idPlayer* player, int rankedScore ); + + void UpdatePrivatePlayerCount( void ); + + typedef struct announcerSoundNode_s { + announcerSound_t soundShader; + float time; + idLinkList announcerSoundNode; + int instance; + bool allowOverride; + } announcerSoundNode_t; + + idLinkList announcerSoundQueue; + announcerSound_t lastAnnouncerSound; + + static const char* announcerSoundDefs[ AS_NUM_SOUNDS ]; + + float announcerPlayTime; + + void PlayAnnouncerSounds ( void ); + + int teamScore[ TEAM_MAX ]; + int teamDeadZoneScore[ TEAM_MAX]; + void ClearTeamScores ( void ); + + void UpdateLeader( idPlayer* oldLeader ); + + void ClearGuis( void ); + void DrawScoreBoard( idPlayer *player ); + void CheckVote( void ); + bool AllPlayersReady( idStr* reason = NULL ); + + const char * GameTime( void ); + + bool EnoughClientsToPlay( void ); + void DrawStatSummary( void ); + // go through the clients, and see if they want to be respawned, and if the game allows it + // called during normal gameplay for death -> respawn cycles + // and for a spectator who want back in the game (see param) + void CheckRespawns( idPlayer *spectator = NULL ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID, idPlayer *player ); + void CheckSpecialLights( void ); + void ForceReady(); + // when clients disconnect or join spectate during game, check if we need to end the game + void CheckAbortGame( void ); + void MessageMode( const idCmdArgs &args ); + void DisableMenu( void ); + void SetMapShot( void ); + // scores in TDM + void VoiceChat( const idCmdArgs &args, bool team ); + +// RAVEN BEGIN +// mekberg: added + void UpdateMPSettingsModel( idUserInterface* currentGui ); +// RAVEN END + + void WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ); +}; + +ID_INLINE bool idMultiplayerGame::IsPureReady( void ) const { + return pureReady; +} + +ID_INLINE void idMultiplayerGame::ClearFrags( int clientNum ) { + playerState[ clientNum ].fragCount = 0; +} + +ID_INLINE bool idMultiplayerGame::IsInGame( int clientNum ) { + return playerState[ clientNum ].ingame; +} + +ID_INLINE int idMultiplayerGame::OpposingTeam( int team ) { + return (team == TEAM_STROGG ? TEAM_MARINE : TEAM_STROGG); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetRankedPlayer( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].First(); + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetRankedPlayerScore( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].Second(); + } else { + return 0; + } +} + +ID_INLINE int idMultiplayerGame::GetNumUnrankedPlayers( void ) { + return unrankedPlayers.Num(); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetUnrankedPlayer( int i ) { + if( i >= 0 && i < unrankedPlayers.Num() ) { + return unrankedPlayers[ i ]; + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetNumRankedPlayers( void ) { + return rankedPlayers.Num(); +} + +ID_INLINE int idMultiplayerGame::GetTeamScore( int i ) { + return playerState[ i ].teamFragCount; +} + +ID_INLINE int idMultiplayerGame::GetScore( int i ) { + return playerState[ i ].fragCount; +} + +ID_INLINE int idMultiplayerGame::GetWins( int i ) { + return playerState[ i ].wins; +} + +ID_INLINE void idMultiplayerGame::ResetRconGuiStatus( void ) { + if( mainGui) { + mainGui->SetStateInt( "password_valid", 0 ); + } +} + +// asalmon: needed access team scores for rich presence +ID_INLINE int idMultiplayerGame::GetScoreForTeam( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + return teamScore[ i ]; +} + +ID_INLINE int idMultiplayerGame::TeamLeader( void ) { + if( teamScore[ TEAM_MARINE ] == teamScore[ TEAM_STROGG ] ) { + return -1; + } else { + return ( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG ); + } +} + +int ComparePlayersByScore( const void* left, const void* right ); +int CompareTeamsByScore( const void* left, const void* right ); + +#endif /* !__MULTIPLAYERGAME_H__ */ + +// RAVEN END diff --git a/src/game/Playback.cpp b/src/game/Playback.cpp new file mode 100644 index 0000000..920649b --- /dev/null +++ b/src/game/Playback.cpp @@ -0,0 +1,441 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI.h" + +#define RECORD_STATE_TRACE_LEN 2048.0f + +class rvGamePlayback +{ +public: + rvGamePlayback( void ); + ~rvGamePlayback( void ); + + void RecordData( const usercmd_t &cmd, idEntity *source ); +private: + int mStartTime; + byte mOldFlags; + idStr mName; + idClipModel *mClipModel; + rvDeclPlayback *mPlayback; +}; + +idCVar g_recordPlayback( "g_recordPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "record the player movement in a playback" ); +idCVar g_playPlayback( "g_playPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "plays the current playback in a camera path" ); + +rvGamePlayback *gamePlayback = NULL; +rvCameraPlayback *playbackCamera = NULL; + +rvGamePlayback::rvGamePlayback( void ) +{ + idStr newName; + const idVec3 trace_mins( -1.0f, -1.0f, -1.0f ); + const idVec3 trace_maxs( 1.0f, 1.0f, 1.0f ); + const idBounds trace_bounds( trace_mins, trace_maxs ); + idTraceModel traceModel( trace_bounds ); + + mStartTime = gameLocal.time; + mOldFlags = 0; + mClipModel = new idClipModel( traceModel ); + + if( !g_currentPlayback.GetInteger() ) + { + newName = declManager->GetNewName( DECL_PLAYBACK, "playbacks/untitled" ); + mPlayback = ( rvDeclPlayback * )declManager->CreateNewDecl( DECL_PLAYBACK, newName, newName + ".playback" ); + mPlayback->ReplaceSourceFileText(); + mPlayback->Invalidate(); + + g_currentPlayback.SetInteger( mPlayback->Index() ); + } + else + { + mPlayback = ( rvDeclPlayback * )declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); + } + + declManager->StartPlaybackRecord( mPlayback ); + common->Printf( "Starting playback record to %s type %d\n", mPlayback->GetName(), g_recordPlayback.GetInteger() ); +} + +rvGamePlayback::~rvGamePlayback( void ) +{ + declManager->FinishPlayback( mPlayback ); + delete mClipModel; + + common->Printf( "Stopping playback play/record\n" ); +} + +void rvGamePlayback::RecordData( const usercmd_t &cmd, idEntity *source ) +{ + idPlayer *player; + trace_t trace; + idMat3 axis; + idVec3 start, end; + rvDeclPlaybackData info; + + info.Init(); + + switch( g_recordPlayback.GetInteger() ) + { + case 1: + info.SetPosition( source->GetPhysics()->GetOrigin() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + break; + + case 2: + gameLocal.GetPlayerView( start, axis ); + + end = start + axis[0] * RECORD_STATE_TRACE_LEN; + + gameLocal.Translation( gameLocal.GetLocalPlayer(), trace, start, end, mClipModel, mat3_identity, CONTENTS_SOLID | CONTENTS_RENDERMODEL, source ); + + if( trace.fraction != 1.0f ) + { + info.SetPosition( trace.endpos ); + info.SetAngles( trace.c.normal.ToAngles() ); + } + break; + + case 3: + assert( source->IsType( idPlayer::GetClassType() ) ); + if( source->IsType( idPlayer::GetClassType() ) ) + { + player = static_cast( source ); + info.SetPosition( player->GetEyePosition() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + } + break; + } + + // Record buttons + info.SetButtons( cmd.buttons ); + + // Record impulses + if( ( cmd.flags & UCF_IMPULSE_SEQUENCE ) != ( mOldFlags & UCF_IMPULSE_SEQUENCE ) ) + { + info.SetImpulse( cmd.impulse ); + } + else + { + info.SetImpulse( 0 ); + } + mOldFlags = cmd.flags; + + declManager->SetPlaybackData( mPlayback, gameLocal.time - mStartTime, -1, &info ); +} + +// ================================================================================================ + +void idGameEdit::DrawPlaybackDebugInfo( void ) +{ + int duration, time; + rvDeclPlaybackData pbd, pbdOld; + const rvDeclPlayback *pb; + + pb = declManager->PlaybackByIndex( g_currentPlayback.GetInteger(), true ); + if( pb ) + { + duration = SEC2MS( pb->GetDuration() ); + pbd.Init(); + pbdOld.Init(); + + declManager->GetPlaybackData( pb, -1, 0, 0, &pbdOld ); + for( time = gameLocal.GetMSec(); time < duration; time += gameLocal.GetMSec() * g_showPlayback.GetInteger() ) + { + declManager->GetPlaybackData( pb, -1, time, time, &pbd ); + gameRenderWorld->DebugArrow( colorGreen, pbdOld.GetPosition(), pbd.GetPosition(), 2 ); + pbdOld = pbd; + } + + gameRenderWorld->DebugBounds( colorRed, pb->GetBounds(), pb->GetOrigin() ); + } +} + +void idGameEdit::RecordPlayback( const usercmd_t &cmd, idEntity *source ) +{ + // Not recording - so instantly exit + if( !g_recordPlayback.GetInteger() && !gamePlayback ) + { + return; + } + + if( !gamePlayback ) + { + gamePlayback = new rvGamePlayback(); + } + + if( g_recordPlayback.GetInteger() ) + { + gamePlayback->RecordData( cmd, source ); + } + else + { + delete gamePlayback; + gamePlayback = NULL; + } +} + +// ================================================================================================ + +bool idGameEdit::PlayPlayback( void ) +{ + // Not playing - so instantly exit + if( !g_playPlayback.GetInteger() && !playbackCamera ) + { + return( false ); + } + + if( !playbackCamera ) + { + playbackCamera = static_cast( gameLocal.SpawnEntityType( rvCameraPlayback::GetClassType() ) ); + SetCamera( playbackCamera ); + + common->Printf( "Starting playback play\n" ); + } + + if( g_currentPlayback.IsModified() ) + { + // Spawn is a misnomer - it should be init with new data + playbackCamera->Spawn(); + g_currentPlayback.ClearModified(); + } + + if( !g_playPlayback.GetInteger() ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } + + return( true ); +} + +// ================================================================================================ + +void idGameEdit::ShutdownPlaybacks( void ) +{ + g_recordPlayback.SetInteger( 0 ); + g_playPlayback.SetInteger( 0 ); + + if( gamePlayback ) + { + delete gamePlayback; + gamePlayback = NULL; + } + + if( playbackCamera ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } +} + +// ================================================================================================ + +/* +============ +rvPlaybackDriver::Start + +Start a new playback automatically blending with the old playback (if any) over numFrames +============ +*/ +bool rvPlaybackDriver::Start( const char *playback, idEntity *owner, int flags, int numFrames ) +{ + idVec3 startPos; + + if( !idStr::Length( playback ) ) + { + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + return( true ); + } + + const rvDeclPlayback *pb = declManager->FindPlayback( playback ); + + if( g_showPlayback.GetInteger() ) + { + common->Printf( "Starting playback: %s\n", pb->GetName() ); + } + + mOldPlaybackDecl = mPlaybackDecl; + mOldFlags = mFlags; + mOldStartTime = mStartTime; + mOldOffset = mOffset; + + mPlaybackDecl = pb; + mFlags = flags; + mStartTime = gameLocal.time; + mOffset.Zero(); + + if( flags & PBFL_RELATIVE_POSITION ) + { + mOffset = owner->GetPhysics()->GetOrigin() - pb->GetOrigin(); + } + + mTransitionTime = numFrames * gameLocal.GetMSec(); + return( true ); +} + +/* +============ +PlaybackCallback + +Called whenever a button up or down, or an impulse event is found while getting the playback data +============ +*/ +void PlaybackCallback( int type, float time, const void *data ) +{ + const rvDeclPlaybackData *pbd = ( const rvDeclPlaybackData * )data; + idEntity *ent = pbd->GetEntity(); + + ent->PostEventSec( &EV_PlaybackCallback, time, type, pbd->GetChanged(), pbd->GetImpulse() ); +} + +/* +============ +rvPlaybackDriver::UpdateFrame + +Blend two playbacks together +============ +*/ +bool rvPlaybackDriver::UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ) +{ + rvDeclPlaybackData pbd, oldPbd; + float blend, invBlend; + idStr ret; + bool expired, oldExpired; + + // Get the current playback position + pbd.Init(); + pbd.SetCallback( ent, PlaybackCallback ); + expired = declManager->GetPlaybackData( mPlaybackDecl, mFlags, gameLocal.time - mStartTime, mLastTime - mStartTime, &pbd ); + pbd.SetPosition( pbd.GetPosition() + mOffset ); + + // Get the playback data we are merging from + oldPbd.Init(); + oldExpired = declManager->GetPlaybackData( mOldPlaybackDecl, mOldFlags, gameLocal.time - mOldStartTime, gameLocal.time - mOldStartTime, &oldPbd ); + oldPbd.SetPosition( oldPbd.GetPosition() + mOldOffset ); + + mLastTime = gameLocal.time; + + if( g_showPlayback.GetInteger() && mPlaybackDecl ) + { + common->Printf( "Running playback: %s at %.1f\n", mPlaybackDecl->GetName(), MS2SEC( gameLocal.time - mStartTime ) ); + } + + // Fully merged - so delete the old one + if( gameLocal.time > mStartTime + mTransitionTime ) + { + oldExpired = true; + } + + // Interpolate the result + if( expired && oldExpired ) + { + out.Init(); + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + } + else if( !expired && oldExpired ) + { + out = pbd; + mOldPlaybackDecl = NULL; + } + else if( expired && !oldExpired ) + { + out = oldPbd; + mPlaybackDecl = NULL; + } + else + { + // Linear zero to one + blend = idMath::ClampFloat( 0.0f, 1.0f, ( gameLocal.time - mStartTime ) / ( float )mTransitionTime ); + + // Sinusoidal + blend = idMath::Sin( blend * idMath::HALF_PI ); + invBlend = 1.0f - blend; + + out.SetPosition( blend * pbd.GetPosition() + invBlend * oldPbd.GetPosition() ); + out.SetAngles( blend * pbd.GetAngles() + invBlend * oldPbd.GetAngles() ); + out.SetButtons( pbd.GetButtons() ); + out.SetImpulse( pbd.GetImpulse() ); + } + + return( expired ); +} + +// cnicholson: Begin Added save/restore functionality +/* +============ +rvPlaybackDriver::Save + +Save all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( mLastTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mTransitionTime ); // cnicholson: Added unsaved var + + savefile->WriteInt( mStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->WriteVec3( mOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt( mOldStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mOldFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->WriteVec3( mOldOffset ); // cnicholson: Added unsaved var +} + +/* +============ +rvPlaybackDriver::Restore + +Restore all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( mLastTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mTransitionTime ); // cnicholson: Added unrestored var + + savefile->ReadInt( mStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->ReadVec3( mOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt( mOldStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mOldFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->ReadVec3( mOldOffset ); // cnicholson: Added unrestored var +} +// cnicholson: End Added save/restore functionality + +// end + diff --git a/src/game/Player.cpp b/src/game/Player.cpp new file mode 100644 index 0000000..d654dc5 --- /dev/null +++ b/src/game/Player.cpp @@ -0,0 +1,14101 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "Weapon.h" +#include "Projectile.h" +#include "vehicle/Vehicle.h" +#include "client/ClientModel.h" +#include "ai/AAS_tactical.h" +#include "Healing_Station.h" +#include "ai/AI_Medic.h" + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off +#ifdef _XENON +#include "../ui/Window.h" + +// nrausch: support for direct button input +#include "../sys/xenon/xen_input.h" +#endif +// RAVEN END + +idCVar net_predictionErrorDecay( "net_predictionErrorDecay", "112", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "time in milliseconds it takes to fade away prediction errors", 0.0f, 200.0f ); +idCVar net_showPredictionError( "net_showPredictionError", "-1", CVAR_INTEGER | CVAR_GAME | CVAR_NOCHEAT, "show prediction errors for the given client", -1, MAX_CLIENTS ); + + +/* +=============================================================================== + + Player control. + This object handles all player movement and world interaction. + +=============================================================================== +*/ + +#ifdef _XENON +bool g_ObjectiveSystemOpen = false; +#endif + +// distance between ladder rungs (actually is half that distance, but this sounds better) +const int LADDER_RUNG_DISTANCE = 32; + +// amount of health per dose from the health station +const int HEALTH_PER_DOSE = 10; + +// time before a weapon dropped to the floor disappears +const int WEAPON_DROP_TIME = 20 * 1000; + +// time before a next or prev weapon switch happens +const int WEAPON_SWITCH_DELAY = 150; + +const float PLAYER_ITEM_DROP_SPEED = 100.0f; + +// how many units to raise spectator above default view height so it's in the head of someone +const int SPECTATE_RAISE = 25; + +const int HEALTH_PULSE = 1000; // Regen rate and heal leak rate (for health > 100) +const int ARMOR_PULSE = 1000; // armor ticking down due to being higher than maxarmor +const int AMMO_REGEN_PULSE = 1000; // ammo regen in Arena CTF +const int POWERUP_BLINKS = 5; // Number of times the powerup wear off sound plays +const int POWERUP_BLINK_TIME = 1000; // Time between powerup wear off sounds +const float MIN_BOB_SPEED = 5.0f; // minimum speed to bob and play run/walk animations at +const int MAX_RESPAWN_TIME = 10000; +const int RAGDOLL_DEATH_TIME = 3000; +#ifdef _XENON + const int RAGDOLL_DEATH_TIME_XEN_SP = 1000; + const int MAX_RESPAWN_TIME_XEN_SP = 3000; +#endif +const int STEPUP_TIME = 200; +const int MAX_INVENTORY_ITEMS = 20; + +const int ARENA_POWERUP_MASK = ( 1 << POWERUP_AMMOREGEN ) | ( 1 << POWERUP_GUARD ) | ( 1 << POWERUP_DOUBLER ) | ( 1 << POWERUP_SCOUT ); + +//const idEventDef EV_Player_HideDatabaseEntry ( "", NULL ); +const idEventDef EV_Player_ZoomIn ( "" ); +const idEventDef EV_Player_ZoomOut ( "" ); + +const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' ); +const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' ); +const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' ); +const idEventDef EV_Player_SetViewAngles( "setViewAngles", "v" ); +const idEventDef EV_Player_StopFxFov( "stopFxFov" ); +const idEventDef EV_Player_EnableWeapon( "enableWeapon" ); +const idEventDef EV_Player_DisableWeapon( "disableWeapon" ); +const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' ); +const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' ); +const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" ); +const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' ); +const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" ); +const idEventDef EV_Player_HideTip( "hideTip" ); +const idEventDef EV_Player_LevelTrigger( "levelTrigger" ); +const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" ); +const idEventDef EV_Player_GetViewPos("getViewPos", NULL, 'v'); +const idEventDef EV_Player_FinishHearingLoss ( "", "f" ); +const idEventDef EV_Player_GetAmmoData( "getAmmoData", "s", 'v'); +const idEventDef EV_Player_RefillAmmo( "refillAmmo" ); +const idEventDef EV_Player_SetExtraProjPassEntity( "setExtraProjPassEntity", "E" ); +const idEventDef EV_Player_SetArmor( "setArmor", "f" ); +const idEventDef EV_Player_DamageEffect( "damageEffect", "sE" ); +const idEventDef EV_Player_AllowFallDamage( "allowFallDamage", "d" ); + +// mekberg: allow enabling/disabling of objectives +const idEventDef EV_Player_EnableObjectives( "enableObjectives" ); +const idEventDef EV_Player_DisableObjectives( "disableObjectives" ); + +// mekberg: don't suppress showing of new objectives anymore +const idEventDef EV_Player_AllowNewObjectives( "" ); + +// RAVEN END + +CLASS_DECLARATION( idActor, idPlayer ) +// EVENT( EV_Player_HideDatabaseEntry, idPlayer::Event_HideDatabaseEntry ) + EVENT( EV_Player_ZoomIn, idPlayer::Event_ZoomIn ) + EVENT( EV_Player_ZoomOut, idPlayer::Event_ZoomOut ) + EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons ) + EVENT( EV_Player_GetMove, idPlayer::Event_GetMove ) + EVENT( EV_Player_GetViewAngles, idPlayer::Event_GetViewAngles ) + EVENT( EV_Player_SetViewAngles, idPlayer::Event_SetViewAngles ) + EVENT( EV_Player_StopFxFov, idPlayer::Event_StopFxFov ) + EVENT( EV_Player_EnableWeapon, idPlayer::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, idPlayer::Event_DisableWeapon ) + EVENT( EV_Player_GetCurrentWeapon, idPlayer::Event_GetCurrentWeapon ) + EVENT( EV_Player_GetPreviousWeapon, idPlayer::Event_GetPreviousWeapon ) + EVENT( EV_Player_SelectWeapon, idPlayer::Event_SelectWeapon ) + EVENT( EV_Player_GetWeaponEntity, idPlayer::Event_GetWeaponEntity ) + EVENT( EV_Player_ExitTeleporter, idPlayer::Event_ExitTeleporter ) + EVENT( EV_Player_HideTip, idPlayer::Event_HideTip ) + EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger ) + EVENT( EV_Player_GetViewPos, idPlayer::Event_GetViewPos ) + EVENT( EV_Player_FinishHearingLoss, idPlayer::Event_FinishHearingLoss ) + EVENT( EV_Player_GetAmmoData, idPlayer::Event_GetAmmoData ) + EVENT( EV_Player_RefillAmmo, idPlayer::Event_RefillAmmo ) + EVENT( EV_Player_AllowFallDamage, idPlayer::Event_AllowFallDamage ) + + +// mekberg: allow enabling/disabling of objectives + EVENT ( EV_Player_EnableObjectives, idPlayer::Event_EnableObjectives ) + EVENT ( EV_Player_DisableObjectives, idPlayer::Event_DisableObjectives ) + +// mekberg: don't suppress showing of new objectives anymore + EVENT ( EV_Player_AllowNewObjectives, idPlayer::Event_AllowNewObjectives ) +// RAVEN END + + EVENT( AI_EnableTarget, idPlayer::Event_EnableTarget ) + EVENT( AI_DisableTarget, idPlayer::Event_DisableTarget ) + + EVENT( EV_ApplyImpulse, idPlayer::Event_ApplyImpulse ) + +// RAVEN BEGIN +// mekberg: sethealth on player. + EVENT( AI_SetHealth, idPlayer::Event_SetHealth ) +//MCG: setArmor + EVENT( EV_Player_SetArmor, idPlayer::Event_SetArmor ) +// RAVEN END; + EVENT( EV_Player_SetExtraProjPassEntity,idPlayer::Event_SetExtraProjPassEntity ) +//MCG: direct damage + EVENT( EV_Player_DamageEffect, idPlayer::Event_DamageEffect ) +END_CLASS + +// RAVEN BEGIN +// asalmon: Xenon weapon combo system +#ifdef _XENON +nextWeaponCombo_t weaponComboChart[12] = { + // up, down, left, right + {1,3,2,4}, // 0: empty slot (select none) + {10,0,1,1}, + {2,2,5,0}, + {0,7,3,3}, + {4,4,0,9}, + {5,5,6,2}, + {6,6,6,5}, + {3,7,7,7}, + {8,8,9,8}, + {9,9,4,8}, + {10,1,10,10}, + {0,0,0,0} +}; +#endif +// RAVEN END + +const idVec4 marineHitscanTint( 0.69f, 1.0f, 0.4f, 1.0f ); +const idVec4 stroggHitscanTint( 1.0f, 0.5f, 0.0f, 1.0f ); +const idVec4 defaultHitscanTint( 0.4f, 1.0f, 0.4f, 1.0f ); + +/* +============== +idInventory::Clear +============== +*/ +void idInventory::Clear( void ) { + maxHealth = 0; + weapons = 0; + carryOverWeapons = 0; + powerups = 0; + armor = 0; + maxarmor = 0; + secretAreasDiscovered = 0; + + memset( ammo, 0, sizeof( ammo ) ); + + ClearPowerUps(); + + memset( weaponMods, 0, sizeof(weaponMods) ); + + // set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level + memset( clip, -1, sizeof( clip ) ); + + items.DeleteContents( true ); + pdas.Clear(); + videos.Clear(); + + levelTriggers.Clear(); + + nextItemPickup = 0; + nextItemNum = 1; + onePickupTime = 0; + objectiveNames.Clear(); + + ammoPredictTime = 0; + lastGiveTime = 0; + + memset( ammoRegenStep, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoIndices, -1, sizeof( int ) * MAX_WEAPONS ); + memset( startingAmmo, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoRegenTime, -1, sizeof( int ) * MAX_WEAPONS ); +} + +/* +============== +idInventory::GivePowerUp +============== +*/ +void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) { + powerups |= 1 << powerup; + powerupEndTime[ powerup ] = msec == -1 ? -1 : (gameLocal.time + msec); +} + +/* +============== +idInventory::ClearPowerUps +============== +*/ +void idInventory::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + powerupEndTime[ i ] = 0; + } + powerups = 0; +} + +/* +============== +idInventory::GetPersistantData +============== +*/ +void idInventory::GetPersistantData( idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + const idKeyValue *kv; + const char *name; + + // armor + dict.SetInt( "armor", armor ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex( i ); + if ( name ) { + dict.SetInt( name, ammo[ i ] ); + } + } + + // items + num = 0; + for( i = 0; i < items.Num(); i++ ) { + item = items[ i ]; + + // copy all keys with "inv_" + kv = item->MatchPrefix( "inv_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "inv_", kv ); + } + num++; + } + } + dict.SetInt( "items", num ); + + // weapons + dict.SetInt( "weapon_bits", weapons ); + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + dict.SetInt( va( "weapon_mods_%i", i ), weaponMods[ i ] ); + } + + dict.SetInt( "levelTriggers", levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + sprintf( key, "levelTrigger_Level_%i", i ); + dict.Set( key, levelTriggers[i].levelName ); + sprintf( key, "levelTrigger_Trigger_%i", i ); + dict.Set( key, levelTriggers[i].triggerName ); + } +} + +/* +============== +idInventory::RestoreInventory +============== +*/ +void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + idStr itemname; + const idKeyValue *kv; + const char *name; + + //We might not need to clear it out. + //Clear(); + + // health/armor + maxHealth = dict.GetInt( "maxhealth", "100" ); + armor = dict.GetInt( "armor", "50" ); + maxarmor = dict.GetInt( "maxarmor", "100" ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex ( i ); + if ( name ) { + ammo[ i ] = dict.GetInt( name ); + } + } + + // items + num = dict.GetInt( "items" ); + items.SetNum( num ); + for( i = 0; i < num; i++ ) { + item = new idDict(); + items[ i ] = item; + sprintf( itemname, "item_%i ", i ); + kv = dict.MatchPrefix( itemname ); + while( kv ) { + key = kv->GetKey(); + key.Strip( itemname ); + item->Set( key, kv->GetValue() ); + kv = dict.MatchPrefix( itemname, kv ); + } + } + + // weapons are stored as a number for persistant data, but as strings in the entityDef + weapons = dict.GetInt( "weapon_bits", "0" ); + +// RAVEN BEGIN +// mekberg: removed nightmare weapon check. + Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false ); +// RAVEN END + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + weaponMods[ i ] = dict.GetInt( va( "weapon_mods_%i", i ) ); + } + // forcefully invalidate the weapon + owner->GiveWeaponMods( 0 ); + + num = dict.GetInt( "levelTriggers" ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "levelTrigger_Level_%i", i ); + idLevelTriggerInfo lti; + lti.levelName = dict.GetString( itemname ); + sprintf( itemname, "levelTrigger_Trigger_%i", i ); + lti.triggerName = dict.GetString( itemname ); + levelTriggers.Append( lti ); + } + +} + +/* +============== +idInventory::Save +============== +*/ +void idInventory::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( maxHealth ); + savefile->WriteInt( weapons ); + savefile->WriteInt( powerups ); + savefile->WriteInt( armor ); + savefile->WriteInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->WriteInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->WriteInt( clip[ i ] ); + savefile->WriteInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->WriteInt( powerupEndTime[ i ] ); + } + + savefile->WriteInt( ammoPredictTime ); + savefile->WriteInt( lastGiveTime ); + + // Save Items + savefile->WriteInt( items.Num() ); + for( i = 0; i < items.Num(); i++ ) { + savefile->WriteDict( items[ i ] ); + } + + // TOSAVE: idStrList pdas; + // TOSAVE: idStrList pdaSecurity; + // TOSAVE: idStrList videos; + + // Save level triggers + savefile->WriteInt( levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + savefile->WriteString( levelTriggers[i].levelName ); + savefile->WriteString( levelTriggers[i].triggerName ); + } + + savefile->WriteInt( nextItemPickup ); + savefile->WriteInt( nextItemNum ); + savefile->WriteInt( onePickupTime ); + + // Save pick up item names + savefile->WriteInt( pickupItemNames.Num() ); + for( i = 0; i < pickupItemNames.Num(); i++ ) { + savefile->WriteString( pickupItemNames[ i ].name ); + savefile->WriteString( pickupItemNames[ i ].icon ); + } + + // Save objectives + savefile->WriteInt( objectiveNames.Num() ); + for( i = 0; i < objectiveNames.Num(); i++ ) { + savefile->WriteString( objectiveNames[i].screenshot ); + savefile->WriteString( objectiveNames[i].text ); + savefile->WriteString( objectiveNames[i].title ); + } +/* + // Save database + savefile->WriteInt ( database.Num() ); + for ( i = 0; i < database.Num(); i ++ ) { + savefile->WriteString ( database[i].title ); + savefile->WriteString ( database[i].text ); + savefile->WriteString ( database[i].image ); + savefile->WriteString ( database[i].filter ); + } +*/ + savefile->WriteInt( secretAreasDiscovered ); + + savefile->WriteSyncId(); +} + +/* +============== +idInventory::Restore +============== +*/ +void idInventory::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( maxHealth ); + savefile->ReadInt( weapons ); + savefile->ReadInt( powerups ); + savefile->ReadInt( armor ); + savefile->ReadInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->ReadInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->ReadInt( clip[ i ] ); + savefile->ReadInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->ReadInt( powerupEndTime[ i ] ); + } + + savefile->ReadInt( ammoPredictTime ); + savefile->ReadInt( lastGiveTime ); + + // Load Items + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idDict *itemdict = new idDict; + + savefile->ReadDict( itemdict ); + items.Append( itemdict ); + } + + // TORESTORE: idStrList pdas; + // TORESTORE: idStrList pdaSecurity; + // TORESTORE: idStrList videos; + + // Load level triggers + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idLevelTriggerInfo lti; + savefile->ReadString( lti.levelName ); + savefile->ReadString( lti.triggerName ); + levelTriggers.Append( lti ); + } + + savefile->ReadInt( nextItemPickup ); + savefile->ReadInt( nextItemNum ); + savefile->ReadInt( onePickupTime ); + + // Load pickup items + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idItemInfo itemInfo; + savefile->ReadString( itemInfo.name ); + savefile->ReadString( itemInfo.icon ); + pickupItemNames.Append( itemInfo ); + } + + // Load objectives + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idObjectiveInfo obj; + savefile->ReadString( obj.screenshot ); + savefile->ReadString( obj.text ); + savefile->ReadString( obj.title ); + objectiveNames.Append( obj ); + } +/* + // Load database + savefile->ReadInt ( num ); + for ( i = 0; i < num; i++ ) { + rvDatabaseEntry entry; + savefile->ReadString ( entry.title ); + savefile->ReadString ( entry.text ); + savefile->ReadString ( entry.image ); + savefile->ReadString ( entry.filter ); + database.Append ( entry ); + } +*/ + savefile->ReadInt( secretAreasDiscovered ); + + savefile->ReadSyncId( "idInventory::Restore" ); +} + +/* +============== +idInventory::AmmoIndexForAmmoClass +============== +*/ +int idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const { + return rvWeapon::GetAmmoIndexForName( ammo_classname ); +} + +/* +============== +idInventory::MaxAmmoForAmmoClass +============== +*/ +int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const { + return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" ); +} + +/* +============== +idInventory::AmmoIndexForWeaponClass +============== +*/ +int idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + if ( ammoRequired ) { + *ammoRequired = decl->dict.GetInt( "ammoRequired" ); + } + return AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); +} + +/* +============== +idInventory::AmmoClassForWeaponClass +============== +*/ +const char * idInventory::AmmoClassForWeaponClass( const char *weapon_classname ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + + return decl->dict.GetString( "ammoType" ); +} + +// RAVEN BEGIN +// mekberg: if the player can pick up ammo at this time +/* +============== +idInventory::DetermineAmmoAvailability +============== +*/ +bool idInventory::DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ) { + const idDeclEntityDef *weaponDecl = NULL; + const idDict* weaponDict = NULL; + const char* mod = NULL; + const idDict* modDict = NULL; + int weaponIndex = -1; + int clipSize = 0; + int modClipSize = 0; + int difference = 0; + idStr realAmmoName( ammoName ); + + // Early out + if ( ammo[ ammoIndex ] == ammoMax ) { + return false; + } + + // Make sure the clip info is updated. + if ( owner->weapon ) { + clip[ owner->GetCurrentWeapon( ) ] = owner->weapon->AmmoInClip( ); + } + + if ( !idStr::Icmpn( ammoName, "start_ammo_", 11 ) ) { + realAmmoName.StripLeading( "start_" ); + } + + // Find the entityDef for the weapon that uses this ammo. + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + + weaponDecl = owner->GetWeaponDef( i ); + + if ( !weaponDecl ) { + continue; + } + + if ( !idStr::Icmp ( weaponDecl->dict.GetString( "ammoType" ), realAmmoName.c_str() ) ) { + weaponDict = &( weaponDecl->dict ); + weaponIndex = i; + break; + } + } + + // If we didn't find one. + if ( weaponIndex == -1 ) { + // If we are picking up ammo and we aren't currently full. + if ( ammoAmount && ammo[ ammoIndex ] != ammoMax ) { + ammo[ ammoIndex ] += ammoAmount; + if ( ammo[ ammoIndex ] > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } + return true; + } + return false; + } + + clipSize = weaponDict->GetInt( "clipSize", "0" ); + + // Find the weaponmods for this weapon and see if we have any clipsize mods. + for ( int m = 0; m < MAX_WEAPONMODS; m ++ ) { + if ( ! ( weaponMods[ weaponIndex ] & ( 1 << m ) ) ) { + continue; + } + + mod = weaponDict->GetString ( va ( "def_mod%d" , m + 1 ) ); + if ( !mod || !*mod ) { + break; + } + + modDict = gameLocal.FindEntityDefDict ( mod, false ); + modClipSize = modDict->GetInt( "clipSize", "0" ); + + if ( modClipSize > clipSize ) { + clipSize = modClipSize; + } + } + + // Don't bother with these checks if we don't have a clipsize + if ( clipSize ) { + difference = ( ammoMax - clipSize ) - ( ammo[ ammoIndex ] - clip[ weaponIndex ] ); + + if ( difference ) { + if ( ammoAmount > difference ) { + ammo[ ammoIndex ] += difference; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; + } else { + return false; + } + } else if ( ( ammo[ ammoIndex ] + ammoAmount ) > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; +} +// RAVEN END + + +/* +============== +idInventory::AmmoIndexForWeaponIndex +============== +*/ +int idInventory::AmmoIndexForWeaponIndex( int weaponIndex ) { + if( ammoIndices[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoIndexForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + ammoIndices[ weaponIndex ] = AmmoIndexForWeaponClass( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ) ); + } + + return ammoIndices[ weaponIndex ]; +} + +/* +============== +idInventory::StartingAmmoForWeaponIndex +============== +*/ +int idInventory::StartingAmmoForWeaponIndex( int weaponIndex ) { + if( startingAmmo[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::StartingAmmoForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + const idKeyValue* kv = weaponDict->MatchPrefix( "inv_start_ammo" ); + if( kv == NULL ) { + startingAmmo[ weaponIndex ] = 1; + } else { + startingAmmo[ weaponIndex ] = atoi( kv->GetValue() ); + kv = weaponDict->MatchPrefix( "inv_start_ammo", kv ); + if( kv != NULL ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Weapon dict for player's def_weapon%d has multiple inv_start_ammo entries\n", weaponIndex ); + return -1; + } + } + } + + return startingAmmo[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenStepForWeaponIndex +============== +*/ +int idInventory::AmmoRegenStepForWeaponIndex( int weaponIndex ) { + if( ammoRegenStep[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenStepForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenStepForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenStep[ weaponIndex ] = weaponDict->GetInt( "ammoRegenStep", "1" ); + } + + return ammoRegenStep[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenTimeForAmmoIndex +============== +*/ +int idInventory::AmmoRegenTimeForWeaponIndex( int weaponIndex ) { + if ( ammoRegenTime[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenTimeForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenTimeForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenTime[ weaponIndex ] = weaponDict->GetInt( "ammoRegenTime", "1" ); + } + + return ammoRegenTime[ weaponIndex ]; +} + + +/* +============== +idInventory::Give +If checkOnly is true, check only for possibility of adding to inventory, don't actually add +============== +*/ +bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped, bool checkOnly ) { + int i; + const char *pos; + const char *end; + int len; + idStr weaponString; + int max; + int amount; + + if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( ammo[ i ] >= max ) { + return false; + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmpn( statname, "start_ammo_", 11 ) ) { + // starting ammo gives only if current ammo is below it + idStr ammoname( statname ); + ammoname.StripLeading( "start_" ); + i = AmmoIndexForAmmoClass( ammoname.c_str() ); + max = MaxAmmoForAmmoClass( owner, ammoname.c_str() ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( amount ) { + if ( ammo[ i ] >= amount ) { + amount = 1; + } else { + amount = amount - ammo[ i ]; + } + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( armor >= maxarmor * 2 ) { + return false; + } + } else if ( !idStr::Icmp( statname, "health" ) ) { + if ( owner->health >= maxHealth ) { + return false; + } + } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) { + i = owner->SlotForWeapon ( statname + 7 ); + if ( i != -1 && !checkOnly ) { + // set, don't add. not going over the clip size limit. + clip[ i ] = atoi( value ); + } + } else if ( !idStr::Icmp( statname, "quad" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_QUADDAMAGE, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "regen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_REGENERATION, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "haste" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_HASTE, SEC2MS( atof( value ) ) ); + } else if( !idStr::Icmp( statname, "ammoregen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_AMMOREGEN, -1 ); + } else if ( !idStr::Icmp( statname, "weapon" ) ) { + bool tookWeapon = false; + for( pos = value; pos != NULL; pos = end ) { + end = strchr( pos, ',' ); + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponName( pos, 0, len ); + + // find the number of the matching weapon names + i = owner->SlotForWeapon ( weaponName ); +// RAVEN BEGIN +// mekberg: check for not found weapons + if ( i == -1 ) { + gameLocal.Warning( "Unknown weapon '%s'", weaponName.c_str() ); + return false; + } +// RAVEN END + + if ( gameLocal.isMultiplayer + && ( weapons & ( 1 << i ) ) ) { + //already have this weapon + if ( !dropped ) { + //a placed weapon item + if ( gameLocal.IsWeaponsStayOn() ) { + //don't pick up weapons at all if you already have them... + continue; + } + } + // don't pickup "no ammo" weapon types twice + // not for singleplayer.. there is only one case in the game where you can get a no ammo + // weapon when you might already have it, in that case it is more consistent to pick it up + // cache the media for this weapon + const idDict* dict; + dict = &owner->GetWeaponDef ( i )->dict; + if ( dict && !dict->GetInt( "ammoRequired" ) ) { + continue; + } + } + + if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) ) { + if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) { + if ( ( owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) + || ( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) ) + && idealWeapon && !checkOnly ) + { + // client prediction should not get here + assert( !gameLocal.isClient ); + *idealWeapon = i; + } + if ( owner->hud && updateHud && lastGiveTime + 1000 < gameLocal.time && !checkOnly ) { + owner->hud->SetStateInt( "newWeapon", i ); + owner->hud->HandleNamedEvent( "newWeapon" ); + lastGiveTime = gameLocal.time; + } + if( !checkOnly ) { + weapons |= ( 1 << i ); + } + tookWeapon = true; + } + } + } + return tookWeapon; + } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) { + // ignore these as they're handled elsewhere + return false; + } else { + // unknown item + gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname ); + return false; + } + + return true; +} + +/* +=============== +idInventoy::Drop +=============== +*/ +void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) { + // remove the weapon bit + // also remove the ammo associated with the weapon as we pushed it in the item + assert( weapon_index != -1 || weapon_classname ); + if ( weapon_index == -1 ) { + for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) { + if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) { + break; + } + } + + if ( weapon_index >= MAX_WEAPONS ) { + gameLocal.Error( "Unknown weapon '%s'", weapon_classname ); + } + } else if ( !weapon_classname ) { + weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ); + } + weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) ); + int ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL ); + if ( ammo_i ) { + clip[ weapon_index ] = -1; + ammo[ ammo_i ] = 0; + } + + weaponMods[weapon_index] = 0; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( int index, int amount ) { + if ( ( index == 0 ) || !amount ) { + // always allow weapons that don't use ammo to fire + return -1; + } + + // check if we have infinite ammo + if ( ammo[ index ] < 0 ) { + return -1; + } + + // return how many shots we can fire + return ammo[ index ] / amount; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( const char *weapon_classname ) { + int ammoRequired; + int index; + index = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( index, ammoRequired ); +} + +/* +=============== +idInventory::UseAmmo +=============== +*/ +bool idInventory::UseAmmo( int index, int amount ) { + if ( !HasAmmo( index, amount ) ) { + return false; + } + + // take an ammo away if not infinite + if ( ammo[ index ] >= 0 ) { + ammo[ index ] -= amount; + ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + } + + return true; +} + +/* +============== +idPlayer::idPlayer +============== +*/ +idPlayer::idPlayer() { + memset( &usercmd, 0, sizeof( usercmd ) ); + + alreadyDidTeamAnnouncerSound = false; + + doInitWeapon = false; + noclip = false; + godmode = false; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + spawnAnglesSet = false; + spawnAngles = ang_zero; + viewAngles = ang_zero; + deltaViewAngles = ang_zero; + cmdAngles = ang_zero; + + demoViewAngleTime = 0; + demoViewAngles = ang_zero; + + oldButtons = 0; + buttonMask = 0; + oldFlags = 0; + + lastHitTime = 0; + lastSavingThrowTime = 0; + + weapon = NULL; + + hud = NULL; + mphud = NULL; + objectiveSystem = NULL; + objectiveSystemOpen = false; + showNewObjectives = false; +#ifdef _XENON + g_ObjectiveSystemOpen = false; +#endif + objectiveButtonReleased = false; + cinematicHud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + lastDmgTime = 0; + deathClearContentsTime = 0; + nextHealthPulse = 0; + + scoreBoardOpen = false; + forceScoreBoard = false; + forceScoreBoardTime = 0; + forceRespawn = false; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// squirrel: Mode-agnostic buymenus + inBuyZone = false; + inBuyZonePrev = false; +// RITUAL END + spectating = false; + spectator = 0; + forcedReady = false; + wantSpectate = false; + + lastHitToggle = false; + lastArmorHit = false; + + minRespawnTime = 0; + maxRespawnTime = 0; + + firstPersonViewOrigin = vec3_zero; + firstPersonViewAxis = mat3_identity; + + hipJoint = INVALID_JOINT; + chestJoint = INVALID_JOINT; + headJoint = INVALID_JOINT; + + bobFoot = 0; + bobFrac = 0.0f; + bobfracsin = 0.0f; + bobCycle = 0; + xyspeed = 0.0f; + stepUpTime = 0; + stepUpDelta = 0.0f; + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + viewBobAngles = ang_zero; + viewBob = vec3_zero; + landChange = 0; + landTime = 0; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + carryOverCurrentWeapon = -1; +// RITUAL END + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = true; + oldInventoryWeapons = 0; + +// RAVEN BEGIN +// mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; +// RAVEN END + + skin = NULL; + weaponViewSkin = NULL; + headSkin = NULL; + powerUpSkin = NULL; + + numProjectilesFired = 0; + numProjectileHits = 0; + + airless = false; + airTics = 0; + lastAirDamage = 0; + + gibDeath = false; + gibsLaunched = false; + gibDir = vec3_zero; + + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + privateCameraView = NULL; + + memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) ); + memset( loggedAccel, 0, sizeof( loggedAccel ) ); + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + + cursor = NULL; + talkCursor = 0; + + oldMouseX = 0; + oldMouseY = 0; + + lastDamageDef = 0; + lastDamageDir = vec3_zero; + lastDamageLocation = 0; + + predictedFrame = 0; + predictedOrigin = vec3_zero; + predictedAngles = ang_zero; + predictedUpdated = false; + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + predictionErrorTime = 0; + + fl.networkSync = true; + + latchedTeam = -1; + hudTeam = -1; + doingDeathSkin = false; + weaponGone = false; + useInitialSpawns = false; + lastSpectateTeleport = 0; + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + teleportKiller = -1; + lastKiller = NULL; + + respawning = false; + ready = false; + leader = false; + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + weaponCatchup = false; + lastSnapshotSequence = 0; + + aimClientNum = -1; + + spawnedTime = 0; + + isTelefragged = false; + isLagged = false; + isChatting = false; + + intentDir.Zero(); + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + ResetCash(); +// RITUAL END + + zoomFov.Init ( 0, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + + memset ( cachedWeaponDefs, 0, sizeof(cachedWeaponDefs) ); + memset ( cachedPowerupDefs, 0, sizeof(cachedPowerupDefs) ); + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time; + + weaponChangeIconsUp = false; + + reloadModel = false; + modelDecl = NULL; + + disableHud = false; + + mutedPlayers = 0; + friendPlayers = 0; + connectTime = 0; + rank = -1; + arena = 0; + + memset( nextAmmoRegenPulse, 0, sizeof( int ) * MAX_AMMO ); + + spectator = 0; + + quadOverlay = NULL; + hasteOverlay = NULL; + regenerationOverlay = NULL; + invisibilityOverlay = NULL; + powerUpOverlay = NULL; + + tourneyStatus = PTS_UNKNOWN; + + vsMsgState = false; + + deathSkinTime = 0; + + jumpDuringHitch = false; + + lastPickupTime = 0; + + int i; + + for( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + + voiceDest[i] = -1; + voiceDestTimes[i] = 0; + } + + itemCosts = NULL; + + teamHealthRegen = NULL; + teamHealthRegenPending = false; + teamAmmoRegen = NULL; + teamAmmoRegenPending = false; + teamDoubler = NULL; + teamDoublerPending = false; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +void idPlayer::SetShowHud( bool showHud ) { + disableHud = !showHud; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +bool idPlayer::GetShowHud( void ) { + return !disableHud; +} + +/* +============== +idPlayer::SetWeapon +============== +*/ +void idPlayer::SetWeapon( int weaponIndex ) { + if ( weapon && weaponIndex == currentWeapon ) { + return; + } + + // Clear the weapon entity + delete weapon; + weapon = NULL; + + previousWeapon = currentWeapon; + currentWeapon = weaponIndex; + weaponGone = false; + + if ( weaponIndex < 0 ) { + weaponGone = true; + return; + } + + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + + idTypeInfo* typeInfo; + weaponDef = GetWeaponDef( currentWeapon ); + if ( !weaponDef ) { + gameLocal.Error( "Weapon definition not found for weapon %d", currentWeapon ) ; + } + typeInfo = idClass::GetClass( weaponDef->dict.GetString( "weaponclass", "rvWeapon" ) ); + if ( !typeInfo || !typeInfo->IsType( rvWeapon::GetClassType() ) ) { + gameLocal.Error( "Invalid weapon class '%s' specified for weapon '%s'", animPrefix.c_str(), weaponDef->dict.GetString ( "weaponclass", "rvWeapon" ) ); + } + weapon = static_cast( typeInfo->CreateInstance() ); + weapon->Init( this, weaponDef, currentWeapon, isStrogg ); + weapon->CallSpawn( ); + + // Reset the zoom fov on weapon change + if ( zoomed ) { + zoomFov.Init ( gameLocal.time, 100, CalcFov(true), DefaultFov() ); + zoomed = false; + } + + UpdateHudWeapon(); + + // Remove the "weapon_" from the anim prefect for the player world anims + animPrefix.Strip( "weapon_" ); + + // Make sure weapon is hidden + if ( !weaponEnabled ) { + Event_DisableWeapon( ); + } +} + +/* +============== +idPlayer::SetupWeaponEntity +============== +*/ +void idPlayer::SetupWeaponEntity( void ) { + int w; + const char *weap; + const idDeclEntityDef *decl; + idEntity *spawn; + + // don't setup weapons for spectators + if ( gameLocal.isClient || (weaponViewModel && weaponWorldModel) || spectating ) { + return; + } + + idDict args; + + if ( !weaponViewModel ) { + // setup the view model +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_viewweapon", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_viewweapon" ); + } + args.Set( "name", va( "%s_weapon", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponViewModel" ); + } + weaponViewModel = static_cast(spawn); + weaponViewModel->SetName( va("%s_weapon", name.c_str() ) ); + weaponViewModel->SetInstance( instance ); + } + + + if ( !weaponWorldModel ) { + // setup the world model + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_animatedentity", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_animatedentity" ); + } + args.Set( "name", va( "%s_weapon_world", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponWorldModel" ); + } + weaponWorldModel = static_cast(spawn); + weaponWorldModel->fl.networkSync = true; + weaponWorldModel->SetName ( va("%s_weapon_world", name.c_str() ) ); + weaponWorldModel->SetInstance( instance ); + } + + currentWeapon = -1; + + weaponWorldModel->fl.persistAcrossInstances = true; + weaponViewModel->fl.persistAcrossInstances = true; + + for( w = 0; w < MAX_WEAPONS; w++ ) { + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( weap && *weap ) { + rvWeapon::CacheWeapon( weap ); + } + } +} + +/* +============== +idPlayer::Init +============== +*/ +void idPlayer::Init( void ) { + const char *value; + + noclip = false; + godmode = false; + godmodeDamage = 0; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + oldButtons = 0; + oldFlags = 0; + + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" ); + oldInventoryWeapons = 0; + + lastDmgTime = 0; + + bobCycle = 0; + bobFrac = 0.0f; + landChange = 0; + landTime = 0; + zoomFov.Init( 0, 0, 0, 0 ); + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + talkCursor = 0; + + lightningEffects = 0; + lightningNextTime = 0; + + modelName = idStr(); + + // Remove any hearing loss that may be set up from the last map + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0 ); + + // remove any damage effects + playerView.ClearEffects(); + + // damage values + fl.takedamage = true; + ClearPain(); + + // restore persistent data + RestorePersistantInfo(); + + bobCycle = 0; + + SetupWeaponEntity( ); + currentWeapon = -1; + previousWeapon = -1; + + flashlightOn = false; + + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + +// RAVEN BEGIN +// abahr: need to init this + vehicleCameraDist = 0.0f; + +// mekberg: moved into function. + SetPMCVars( ); +// RAVEN END + + // air always initialized to maximum too + airTics = pm_airTics.GetFloat(); + airless = false; + + gibDeath = false; + gibsLaunched = false; + gibDir.Zero(); + +// RAVEN BEGIN +// abahr: changed to GetCurrentGravity + // set the gravity + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); +// RAVEN END + + // start out standing + SetEyeHeight( pm_normalviewheight.GetFloat() ); + + stepUpTime = 0; + stepUpDelta = 0.0f; + viewBobAngles.Zero(); + viewBob.Zero(); + + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + value = spawnArgs.GetString( va( "model_%s", team ? "strogg" : "marine" ), NULL ); + } + else { + value = spawnArgs.GetString( "model" ); + } + + if( gameLocal.isMultiplayer ) { + UpdateModelSetup( true ); + } else { + if ( value && ( *value != 0 ) ) { + SetModel( value ); + } + // check head + if( idStr::Icmp( head ? head->spawnArgs.GetString( "classname", "" ) : "", spawnArgs.GetString( "def_head", "" ) ) ) { + SetupHead(); + } + } + + if ( cursor ) { + cursor->SetStateInt( "talkcursor", 0 ); + cursor->HandleNamedEvent( "showCrossCombat" ); + } + + if ( !gameLocal.isMultiplayer ) { + if ( g_testDeath.GetBool() && skin ) { + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) { + skin = declManager->FindSkin( value ); + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + if( clientHead->GetModelDefHandle() > 0) { + gameRenderWorld->RemoveDecals( clientHead->GetModelDefHandle() ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + value = spawnArgs.GetString( "joint_hips", "" ); + hipJoint = animator.GetJointHandle( value ); + if ( hipJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_hips' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_chest", "" ); + chestJoint = animator.GetJointHandle( value ); + if ( chestJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_chest' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_head", "" ); + headJoint = animator.GetJointHandle( value ); + if ( headJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", value, name.c_str() ); + } + + // initialize the script variables + memset ( &pfl, 0, sizeof( pfl ) ); + pfl.onGround = true; + pfl.noFallingDamage = false; + + // Start in idle + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + forceScoreBoard = false; + forceScoreBoardTime = 0; + forcedReady = false; + + privateCameraView = NULL; + + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + lastKiller = NULL; + teleportKiller = -1; + leader = false; + + SetPrivateCameraView( NULL ); + + lastSnapshotSequence = 0; + + if ( !gameLocal.isMultiplayer ) { + // in MP we set isStrogg in UpdateModelSetup() + isStrogg = spawnArgs.GetBool ( "strogg", "0" ); + } + + + aimClientNum = -1; + if ( mphud ) { + mphud->HandleNamedEvent( "aim_fade" ); + } + + isChatting = false; + + SetInitialHud(); + + emote = PE_NONE; + + powerupEffectTime = 0; + powerupEffect = NULL; + powerupEffectType = 0; + hasteEffect = NULL; + flagEffect = NULL; + arenaEffect = NULL; + + quadOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_quaddamage_overlay" ), false ); + hasteOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_haste_overlay" ), false ); + regenerationOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_regeneration_overlay" ), false ); + invisibilityOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_invisibility_overlay" ), false ); + powerUpOverlay = NULL; + + if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) { + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP) && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ){ + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_WARMUP && ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_SUDDEN_DEATH ) { + // don't clear notices while in warmup modes or sudden death + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP) && vsMsgState ) { + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + deathSkinTime = 0; + deathStateHitch = false; + jumpDuringHitch = false; + + lastPickupTime = 0; + + if ( teamHealthRegenPending ) { + assert( teamHealthRegen == NULL ); + teamHealthRegenPending = false; + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamAmmoRegenPending ) { + assert( teamAmmoRegen == NULL ); + teamAmmoRegenPending = false; + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamDoublerPending ) { + assert( teamDoubler == NULL ); + teamDoublerPending = false; + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +=============== +idPlayer::ProjectHeadOverlay +=============== +*/ +void idPlayer::ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) { + + if( clientHead ) { + clientHead.GetEntity()->ProjectOverlay( point, dir, size, decal ); + } +} + +/* +=============== +idPlayer::GetCursorGUI +=============== +*/ +idUserInterface* idPlayer::GetCursorGUI( void ) { + idStr temp; + + assert( !gameLocal.isMultiplayer || gameLocal.localClientNum == entityNumber ); + if ( cursor ) { + return cursor; + } + if ( spawnArgs.GetString( "cursor", "", temp ) ) { + cursor = uiManager->FindGui( temp, true, gameLocal.isMultiplayer, gameLocal.isMultiplayer ); + } + return cursor; +} + +/* +============== +idPlayer::Spawn + +Prepare any resources used by the player. +============== +*/ +void idPlayer::Spawn( void ) { + idStr temp; + idBounds bounds; + + if ( entityNumber >= MAX_CLIENTS ) { + gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." ); + } + + // allow thinking during cinematics + cinematic = true; + + if ( gameLocal.isMultiplayer ) { + // always start in spectating state waiting to be spawned in + // do this before SetClipModel to get the right bounding box + spectating = true; + } + + // set our collision model + physicsObj.SetSelf( this ); + SetClipModel( ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + SetPhysics( &physicsObj ); + InitAASLocation(); + + skin = renderEntity.customSkin; + + // only the local player needs guis + // for server netdemos that have no local player, we use demo_* guis in idGameLocal + if ( !gameLocal.isMultiplayer || entityNumber == gameLocal.localClientNum ) { + + // load HUD + hud = NULL; + mphud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + objectiveSystem = NULL; + + if ( spawnArgs.GetString( "hud", "", temp ) ) { + hud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No hud for player." ); + } + + if ( gameLocal.isMultiplayer ) { + if ( spawnArgs.GetString( "mphud", "", temp ) ) { + mphud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No MP hud overlay while in MP."); + } + } + + if ( hud ) { + hud->Activate( true, gameLocal.time ); + } + + if ( mphud ) { + mphud->Activate( true, gameLocal.time ); + } + + // load cursor + GetCursorGUI(); + if ( cursor ) { + cursor->Activate( true, gameLocal.time ); + } + + // Load + + if ( spawnArgs.GetString ( "cinematicHud", "", temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + + if ( !gameLocal.isMultiplayer ) { + objectiveSystem = uiManager->FindGui( spawnArgs.GetString( "wristcomm", "guis/wristcomm.gui" ), true, false, true ); + objectiveSystemOpen = false; +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + + // clear votes + // if we want to display current votes that were started before a player was connected + // but are still being voted on, this should check the current vote and update the gui appropriately + gameLocal.mpGame.ClearVote( entityNumber ); + } + + SetLastHitTime( 0, false ); + + // load the armor sound feedback + declManager->FindSound( "player_sounds_hitArmor" ); + + animator.RemoveOriginOffset( true ); + + // initialize user info related settings + // on server, we wait for the userinfo broadcast, as this controls when the player is initially spawned in game + // ocassionally, a race condition may mark a client in-game before he is spawned, if this is the case, parse the userinfo here + if ( (gameLocal.isClient || entityNumber == gameLocal.localClientNum) || (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber ) ) ) { + UserInfoChanged(); + } + + // create combat collision hull for exact collision detection + SetCombatModel(); + + // init the damage effects + playerView.SetPlayerEntity( this ); + + // supress model in non-player views, but allow it in mirrors and remote views + renderEntity.suppressSurfaceInViewID = entityNumber+1; + + // don't project shadow on self or weapon + renderEntity.noSelfShadow = true; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + clientHead->GetRenderEntity()->noSelfShadow = true; + } + } else { + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1; + headEnt->GetRenderEntity()->noSelfShadow = true; + } + } + + if ( gameLocal.isMultiplayer ) { + Init(); + Hide(); // properly hidden if starting as a spectator + + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + if( inOtherInstance ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } + + if ( !gameLocal.isClient ) { + // set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + spectator = entityNumber; + forceRespawn = true; + assert( spectating ); + } + } else { + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + } + + // trigger playtesting item gives, if we didn't get here from a previous level + // the devmap key will be set on the first devmap, but cleared on any level + // transitions + if ( !gameLocal.isMultiplayer && gameLocal.serverInfo.FindKey( "devmap" ) ) { + // fire a trigger with the name "devmap" + idEntity *ent = gameLocal.FindEntity( "devmap" ); + if ( ent ) { + ent->ActivateTargets( this ); + } + } + + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + hiddenWeapon = true; + if ( weapon ) { + weapon->LowerWeapon(); + } +// RAVEN BEGIN +// mekberg: set to blaster now and disable the weapon. + idealWeapon = SlotForWeapon ( "weapon_blaster" ); + Event_DisableWeapon( ); +// RAVEN END + } else { + hiddenWeapon = false; + } + + if ( hud ) { + UpdateHudWeapon( ); + hud->StateChanged( gameLocal.time ); + } + + tipUp = false; + objectiveUp = false; + + aiManager.AddTeammate ( this ); + + if ( inventory.levelTriggers.Num() ) { + PostEventMS( &EV_Player_LevelTrigger, 0 ); + } + + // ddynerman: defaults for these values are the single player fall deltas + fatalFallDelta = spawnArgs.GetFloat("fatal_fall_delta", "65"); + hardFallDelta = spawnArgs.GetFloat("hard_fall_delta", "45"); + softFallDelta = spawnArgs.GetFloat("soft_fall_delta", "30"); + noFallDelta = spawnArgs.GetFloat("no_fall_delta", "7"); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); + + gibSkin = declManager->FindSkin( spawnArgs.GetString( "skin_gibskin" ) ); + + // Skil levels + dynamicProtectionScale = 1.0f; + if ( !gameLocal.isMultiplayer ) { + if ( g_skill.GetInteger() < 2 ) { + if ( health < 25 ) { + health = 25; + } + } else { + //g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f ); + } + } + + // Powerup joints? + if ( spawnArgs.GetString ( "powerup_effect_joints", "", temp ) ) { + animator.GetJointList ( temp, powerupEffectJoints ); + } + + // RAVEN BEGIN + // mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; + + // mekberg: new objectives are suppressed until this event is processed + PostEventMS( &EV_Player_AllowNewObjectives, 5000 ); + tourneyStatus = PTS_UNKNOWN; + + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + + // zero out view angles when we spawn ourselves in MP - the server will send down + // the correct ones (only zero if our input is still zero'd) + if( gameLocal.isClient && gameLocal.localClientNum == entityNumber && usercmd.angles[ 0 ] == 0 && usercmd.angles[ 1 ] == 0 && usercmd.angles[ 2 ] == 0 ) { + deltaViewAngles = ang_zero; + } +//RITUAL BEGIN + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = 0; +//RITUAL END + + itemCosts = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "ItemCostConstants", false ) ); +} + +/* +============== +idPlayer::~idPlayer() + +Release any resources used by the player. +============== +*/ +idPlayer::~idPlayer() { + if( gameLocal.mpGame.GetGameState() ) { + gameLocal.mpGame.GetGameState()->ClientDisconnect( this ); + } + + delete weaponViewModel; + delete weaponWorldModel; + delete weapon; + delete aasSensor; + + SetPhysics( NULL ); +} + +/* +=========== +idPlayer::Save +=========== +*/ +void idPlayer::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteUsercmd( usercmd ); + + playerView.Save( savefile ); + + savefile->WriteBool( noclip ); + savefile->WriteBool( godmode ); + savefile->WriteInt ( godmodeDamage ); + savefile->WriteBool( undying ); + + // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame + savefile->WriteAngles( spawnAngles ); + savefile->WriteAngles( viewAngles ); + savefile->WriteAngles( cmdAngles ); + + savefile->WriteInt( buttonMask ); + savefile->WriteInt( oldButtons ); + savefile->WriteInt( oldFlags ); + + savefile->WriteInt( lastHitTime ); + savefile->WriteInt( 0 ); + savefile->WriteInt( lastSavingThrowTime ); + + // idBoolFields don't need to be saved, just re-linked in Restore + savefile->Write( &pfl, sizeof( pfl ) ); + + inventory.Save( savefile ); + + //weapon->Save( savefile ); // Don't save this + + weaponViewModel.Save( savefile ); + weaponWorldModel.Save ( savefile ); + // weaponDef restore = weaponDef = GetWeaponDef ( currentWeapon ); + + savefile->WriteUserInterface( hud, false ); +// savefile->WriteUserInterface( mphud, false ); // Don't save MP stuff + savefile->WriteUserInterface( objectiveSystem, false ); + savefile->WriteUserInterface( cinematicHud, false ); + savefile->WriteBool( objectiveSystemOpen ); + savefile->WriteBool( disableHud ); + + savefile->WriteInt( lastDmgTime ); + savefile->WriteInt( deathClearContentsTime ); + savefile->WriteBool( doingDeathSkin ); + savefile->WriteInt( nextHealthPulse ); + savefile->WriteInt( nextArmorPulse ); + savefile->WriteBool( hiddenWeapon ); + +// savefile->WriteInt( spectator ); // Don't save MP stuff + +// savefile->WriteBool( scoreBoardOpen ); // Don't save MP stuff +// savefile->WriteBool( tourneyBracketsOpen ); // Don't save MP stuff +// savefile->WriteBool( forceScoreBoard ); // Don't save MP stuff +// savefile->WriteBool( forceRespawn ); // Don't save MP stuff + +// savefile->WriteBool( spectating ); // Don't save MP stuff +// savefile->WriteBool( lastHitToggle ); // Don't save MP stuff +// savefile->WriteBool( forcedReady ); // Don't save MP stuff +// savefile->WriteBool( wantSpectate ); // Don't save MP stuff + +// savefile->WriteBool( weaponGone ); // Don't save MP stuff +// savefile->WriteBool( useInitialSpawns ); // Don't save MP stuff +// savefile->WriteBool( isLagged ); // Don't save MP stuff +// savefile->WriteBool( isChatting ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateTeleport ); // Don't save MP stuff +// savefile->WriteInt( latchedTeam ); // Don't save MP stuff +// savefile->WriteInt( spawnedTime ); // Don't save MP stuff + +// teleportEntity.Save( savefile ); // Don't save MP stuff +// savefile->WriteInt( teleportKiller ); // Don't save MP stuff + + savefile->WriteInt( minRespawnTime ); + savefile->WriteInt( maxRespawnTime ); + + savefile->WriteVec3( firstPersonViewOrigin ); + savefile->WriteMat3( firstPersonViewAxis ); + + // don't bother saving dragEntity or aasSensor since it's a dev tool + savefile->WriteVec3( intentDir ); + + savefile->WriteFloat ( vehicleCameraDist ); + + savefile->WriteJoint( hipJoint ); + savefile->WriteJoint( chestJoint ); + savefile->WriteJoint( headJoint ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( aasLocation.Num() ); + for( i = 0; i < aasLocation.Num(); i++ ) { + savefile->WriteInt( aasLocation[ i ].areaNum ); + savefile->WriteVec3( aasLocation[ i ].pos ); + } + + savefile->WriteString( modelName ); // cnicholson: Added unsaved var + // TOSAVE: const idDict* modelDict + + savefile->WriteInt( bobFoot ); + savefile->WriteFloat( bobFrac ); + savefile->WriteFloat( bobfracsin ); + savefile->WriteInt( bobCycle ); + savefile->WriteFloat( xyspeed ); + savefile->WriteInt( stepUpTime ); + savefile->WriteFloat( stepUpDelta ); + savefile->WriteFloat( idealLegsYaw ); + savefile->WriteFloat( legsYaw ); + savefile->WriteBool( legsForward ); + savefile->WriteFloat( oldViewYaw ); + savefile->WriteAngles( viewBobAngles ); + savefile->WriteVec3( viewBob ); + savefile->WriteInt( landChange ); + savefile->WriteInt( landTime ); + + savefile->WriteFloat( fatalFallDelta ); + savefile->WriteFloat( hardFallDelta ); + savefile->WriteFloat( softFallDelta ); + savefile->WriteFloat( noFallDelta ); + + savefile->WriteInt( currentWeapon ); + savefile->WriteInt( idealWeapon ); + savefile->WriteInt( previousWeapon ); + savefile->WriteInt( weaponSwitchTime ); + savefile->WriteBool( weaponEnabled ); + + savefile->WriteBool ( flashlightOn); + savefile->WriteBool ( zoomed ); + + savefile->WriteBool( reloadModel ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( powerUpSkin ); + savefile->WriteSkin( gibSkin ); + + savefile->WriteInt( numProjectilesFired ); + savefile->WriteInt( numProjectileHits ); + + savefile->WriteBool( airless ); + savefile->WriteInt( airTics ); + savefile->WriteInt( lastAirDamage ); + + savefile->WriteBool( gibDeath ); + savefile->WriteBool( gibsLaunched ); + savefile->WriteVec3( gibDir ); + + savefile->WriteBool( isStrogg ); + + savefile->WriteInterpolate( zoomFov ); + savefile->WriteInterpolate( centerView ); + savefile->WriteBool( fxFov ); + + savefile->WriteFloat( influenceFov ); + savefile->WriteInt( influenceActive ); + savefile->WriteObject( influenceEntity ); + savefile->WriteMaterial( influenceMaterial ); + savefile->WriteFloat( influenceRadius ); + savefile->WriteSkin( influenceSkin ); + + savefile->WriteObject( privateCameraView ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->WriteAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->WriteInt( loggedAccel[ i ].time ); + savefile->WriteVec3( loggedAccel[ i ].dir ); + } + savefile->WriteInt( currentLoggedAccel ); + + savefile->WriteUserInterface( focusUI, false ); + savefile->WriteInt( focusTime ); + savefile->WriteInt ( focusType ); + focusEnt.Save ( savefile ); + savefile->WriteUserInterface( focusBrackets, false ); + savefile->WriteInt( focusBracketsTime ); + + talkingNPC.Save( savefile ); + + extraProjPassEntity.Save( savefile ); + + savefile->WriteInt( talkCursor ); + savefile->WriteUserInterface( cursor, false ); + + savefile->WriteUserInterface( overlayHud, false ); + savefile->WriteInt ( overlayHudTime ); + + savefile->WriteBool( targetFriendly ); + + savefile->WriteInt( oldMouseX ); + savefile->WriteInt( oldMouseY ); + + savefile->WriteBool( tipUp ); + savefile->WriteBool( objectiveUp ); + + savefile->WriteFloat( dynamicProtectionScale ); + savefile->WriteInt( lastDamageDef ); + savefile->WriteVec3( lastDamageDir ); + savefile->WriteInt( lastDamageLocation ); + + savefile->WriteInt( predictedFrame ); + savefile->WriteVec3( predictedOrigin ); + savefile->WriteAngles( predictedAngles ); + savefile->WriteBool( predictedUpdated ); + savefile->WriteVec3( predictionOriginError ); + savefile->WriteAngles( predictionAnglesError ); + savefile->WriteInt( predictionErrorTime ); + +// savefile->WriteBool( ready ); // Don't save MP stuff +// savefile->WriteBool( respawning ); // Don't save MP stuff +// savefile->WriteBool( leader ); // Don't save MP stuff +// savefile->WriteBool( weaponCatchup ); // Don't save MP stuff +// savefile->WriteBool( isTelefragged ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateChange ); // Don't save MP stuff +// savefile->WriteInt( lastTeleFX ); // Don't save MP stuff +// savefile->WriteInt( lastSnapshotSequence ); // Don't save MP stuff + +// savefile->WriteInt( aimClientNum ); // Don't save MP stuff + +// lastImpulsePlayer->Save( savefile ); // Don't save MP stuff + +// savefile->WriteInt( arena ); // Don't save MP stuff + +// savefile->WriteInt( connectTime ); // Don't save MP stuff +// savefile->WriteInt( mutedPlayers ); // Don't save MP stuff +// savefile->WriteInt( friendPlayers ); // Don't save MP stuff + +// savefile->WriteInt( rank ); // Don't save MP stuff + + savefile->WriteInt( lastImpulseTime ); + bossEnemy.Save( savefile ); // cnicholson: Added unsaved var + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->WriteBool( weaponChangeIconsUp ); // cnicholson: Added unsaved var + + // mekberg: added + savefile->WriteBool( showNewObjectives ); + savefile->WriteBool( objectivesEnabled ); + + savefile->WriteBool( flagCanFire ); + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + +#ifndef _XENON + if ( hud ) { + hud->SetStateString( "message", common->GetLocalizedString( "#str_102916" ) ); + hud->HandleNamedEvent( "Message" ); + } +#endif +} + +/* +=========== +idPlayer::Restore +=========== +*/ +void idPlayer::Restore( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadUsercmd( usercmd ); + + playerView.Restore( savefile ); + + savefile->ReadBool( noclip ); + savefile->ReadBool( godmode ); + savefile->ReadInt ( godmodeDamage ); + savefile->ReadBool( undying ); + + savefile->ReadAngles( spawnAngles ); + savefile->ReadAngles( viewAngles ); + savefile->ReadAngles( cmdAngles ); + + memset( usercmd.angles, 0, sizeof( usercmd.angles ) ); + SetViewAngles( viewAngles ); + spawnAnglesSet = true; + + savefile->ReadInt( buttonMask ); + savefile->ReadInt( oldButtons ); + savefile->ReadInt( oldFlags ); + + usercmd.flags = 0; + oldFlags = 0; + + savefile->ReadInt( lastHitTime ); + int foo; + savefile->ReadInt( foo ); + savefile->ReadInt( lastSavingThrowTime ); + + savefile->Read( &pfl, sizeof( pfl ) ); + + inventory.Restore( savefile ); + + assert( !weapon ); + + weaponViewModel.Restore( savefile ); + weaponWorldModel.Restore( savefile ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + assert( !mphud ); // Don't save MP stuff + savefile->ReadUserInterface( objectiveSystem, &spawnArgs ); + savefile->ReadUserInterface( cinematicHud, &spawnArgs ); + savefile->ReadBool( objectiveSystemOpen ); + +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + + objectiveButtonReleased = false; + savefile->ReadBool( disableHud ); // cnicholson: Added unrestored var + + savefile->ReadInt( lastDmgTime ); + savefile->ReadInt( deathClearContentsTime ); + savefile->ReadBool( doingDeathSkin ); + savefile->ReadInt( nextHealthPulse ); + savefile->ReadInt( nextArmorPulse ); + savefile->ReadBool( hiddenWeapon ); + + assert( !spectator ); // Don't save MP stuff + + assert( !scoreBoardOpen ); // Don't save MP stuff + assert( !forceScoreBoard ); // Don't save MP stuff + assert( !forceRespawn ); // Don't save MP stuff + + assert( !spectating ); // Don't save MP stuff + assert( !lastHitToggle ); // Don't save MP stuff + assert( !forcedReady ); // Don't save MP stuff + assert( !wantSpectate ); // Don't save MP stuff + + assert( !weaponGone ); // Don't save MP stuff + assert( !useInitialSpawns ); // Don't save MP stuff + assert( !isLagged ); // Don't save MP stuff + assert( !isChatting ); // Don't save MP stuff + + assert( !lastSpectateTeleport ); // Don't save MP stuff + assert( latchedTeam == -1 ); // Don't save MP stuff + assert( !spawnedTime ); // Don't save MP stuff + + assert( !teleportEntity ); // Don't save MP stuff + assert( teleportKiller == -1 ); // Don't save MP stuff + + savefile->ReadInt( minRespawnTime ); + savefile->ReadInt( maxRespawnTime ); + + savefile->ReadVec3( firstPersonViewOrigin ); + savefile->ReadMat3( firstPersonViewAxis ); + + // don't bother restoring dragEntity since it's a dev tool + dragEntity.Clear(); + savefile->ReadVec3( intentDir ); + + savefile->ReadFloat ( vehicleCameraDist ); + + savefile->ReadJoint( hipJoint ); + savefile->ReadJoint( chestJoint ); + savefile->ReadJoint( headJoint ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( aasLocation[ i ].areaNum ); + savefile->ReadVec3( aasLocation[ i ].pos ); + } + + savefile->ReadString( modelName ); // cnicholson: Added unrestored var + // TORESTORE: const idDict* modelDict + + savefile->ReadInt( bobFoot ); + savefile->ReadFloat( bobFrac ); + savefile->ReadFloat( bobfracsin ); + savefile->ReadInt( bobCycle ); + savefile->ReadFloat( xyspeed ); + savefile->ReadInt( stepUpTime ); + savefile->ReadFloat( stepUpDelta ); + savefile->ReadFloat( idealLegsYaw ); + savefile->ReadFloat( legsYaw ); + savefile->ReadBool( legsForward ); + savefile->ReadFloat( oldViewYaw ); + savefile->ReadAngles( viewBobAngles ); + savefile->ReadVec3( viewBob ); + savefile->ReadInt( landChange ); + savefile->ReadInt( landTime ); + + savefile->ReadFloat( fatalFallDelta ); + savefile->ReadFloat( hardFallDelta ); + savefile->ReadFloat( softFallDelta ); + savefile->ReadFloat( noFallDelta ); + + savefile->ReadInt( currentWeapon ); + savefile->ReadInt( idealWeapon ); + savefile->ReadInt( previousWeapon ); + savefile->ReadInt( weaponSwitchTime ); + savefile->ReadBool( weaponEnabled ); + + savefile->ReadBool ( flashlightOn ); + savefile->ReadBool ( zoomed ); + + savefile->ReadBool ( reloadModel ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( powerUpSkin ); + savefile->ReadSkin( gibSkin ); + + savefile->ReadInt( numProjectilesFired ); + savefile->ReadInt( numProjectileHits ); + + savefile->ReadBool( airless ); + savefile->ReadInt( airTics ); + savefile->ReadInt( lastAirDamage ); + + savefile->ReadBool( gibDeath ); + savefile->ReadBool( gibsLaunched ); + savefile->ReadVec3( gibDir ); + + savefile->ReadBool( isStrogg ); + + savefile->ReadInterpolate( zoomFov ); + savefile->ReadInterpolate( centerView ); + savefile->ReadBool( fxFov ); + + savefile->ReadFloat( influenceFov ); + savefile->ReadInt( influenceActive ); + savefile->ReadObject( reinterpret_cast( influenceEntity ) ); + savefile->ReadMaterial( influenceMaterial ); + savefile->ReadFloat( influenceRadius ); + savefile->ReadSkin( influenceSkin ); + + savefile->ReadObject( reinterpret_cast( privateCameraView ) ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->ReadAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->ReadInt( loggedAccel[ i ].time ); + savefile->ReadVec3( loggedAccel[ i ].dir ); + } + savefile->ReadInt( currentLoggedAccel ); + + savefile->ReadUserInterface( focusUI, &spawnArgs ), + savefile->ReadInt( focusTime ); + savefile->ReadInt( (int&)focusType ); + focusEnt.Restore ( savefile ); + savefile->ReadUserInterface( focusBrackets, &spawnArgs ); + savefile->ReadInt( focusBracketsTime ); + + talkingNPC.Restore( savefile ); + + extraProjPassEntity.Restore( savefile ); + + savefile->ReadInt( talkCursor ); + savefile->ReadUserInterface( cursor, &spawnArgs ); + + savefile->ReadUserInterface( overlayHud, &spawnArgs ); // cnicholson: Added unrestored var + savefile->ReadInt ( overlayHudTime ); // cnicholson: Added unrestored var + + savefile->ReadBool( targetFriendly ); + + savefile->ReadInt( oldMouseX ); + savefile->ReadInt( oldMouseY ); + + savefile->ReadBool( tipUp ); + savefile->ReadBool( objectiveUp ); + + savefile->ReadFloat( dynamicProtectionScale ); // cnicholson: Added unrestored var + savefile->ReadInt( lastDamageDef ); + savefile->ReadVec3( lastDamageDir ); + savefile->ReadInt( lastDamageLocation ); + + savefile->ReadInt( predictedFrame ); + savefile->ReadVec3( predictedOrigin ); + savefile->ReadAngles( predictedAngles ); + savefile->ReadBool( predictedUpdated ); + savefile->ReadVec3( predictionOriginError ); + savefile->ReadAngles( predictionAnglesError ); + savefile->ReadInt( predictionErrorTime ); + + assert( !ready ); // Don't save MP stuff + assert( !respawning ); // Don't save MP stuff + assert( !leader ); // Don't save MP stuff + assert( !weaponCatchup ); // Don't save MP stuff + assert( !isTelefragged ); // Don't save MP stuff + + assert( !lastSpectateChange ); // Don't save MP stuff + assert( lastTeleFX == -9999 ); // Don't save MP stuff + assert( !lastSnapshotSequence ); // Don't save MP stuff + + assert( aimClientNum == -1 ); // Don't save MP stuff + + assert( !lastImpulsePlayer ); // Don't save MP stuff + + assert( !arena ); // Don't save MP stuff + + assert( !connectTime ); // Don't save MP stuff + assert( !mutedPlayers ); // Don't save MP stuff + assert( !friendPlayers ); // Don't save MP stuff + + assert( rank == -1 ); // Don't save MP stuff + + savefile->ReadInt( lastImpulseTime ); + bossEnemy.Restore( savefile ); // cnicholson: Added unrestored var + + // TORESTORE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TORESTORE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->ReadBool( weaponChangeIconsUp ); // cnicholson: Added unrestored var + +// mekberg: added + savefile->ReadBool( showNewObjectives ); + savefile->ReadBool( objectivesEnabled ); + + savefile->ReadBool( flagCanFire ); + + // set the pm_ cvars + const idKeyValue *kv; + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + + // Loading a game on easy mode ensures you alwasy have 20% health when you load + i = spawnArgs.GetInt ( va("minRestoreHealth%d", g_skill.GetInteger ( ) ) ); + if ( health < i ) { + health = i; + } + + //if there's hearing loss, make sure we post a finishing event + if( pfl.hearingLoss ) { + Event_FinishHearingLoss( 3.0f ); + } else { + Event_FinishHearingLoss( 0.05f ); + } + // create combat collision hull for exact collision detection + SetCombatModel(); + +// RAVEN BEGIN +// mekberg: Grab from user info. + showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); +// RAVEN END +} + +/* +=============== +idPlayer::PrepareForRestart +================ +*/ +void idPlayer::PrepareForRestart( void ) { + ClearPowerUps(); + Spectate( true ); + + forceRespawn = true; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// RITUAL END + + // we will be restarting program, clear the client entities from program-related things first + ShutdownThreads(); + + // the sound world is going to be cleared, don't keep references to emitters + FreeSoundEmitter( false ); +} + +/* +=============== +idPlayer::Restart +================ +*/ +void idPlayer::Restart( void ) { + idActor::Restart(); + + // client needs to setup the animation script object again + if ( gameLocal.isClient ) { + // clear the existing model to force a reload + Init(); + } else { + // choose a random spot and prepare the point of view in case player is left spectating + assert( spectating ); + SpawnFromSpawnSpot(); + } + + lastKiller = NULL; + useInitialSpawns = true; +} + +/* +=============== +idPlayer::ServerSpectate +================ +*/ +void idPlayer::ServerSpectate( bool spectate ) { + assert( !gameLocal.isClient ); + + if ( spectating != spectate ) { + // if we select spectating on the client + // mekberg: drop and clear powerups from the player. + DropPowerups(); + ClearPowerUps(); + Spectate( spectate ); + gameLocal.mpGame.GetGameState()->Spectate( this ); + if ( spectate ) { + SetSpectateOrigin( ); + } + } + if ( !spectate ) { + SpawnFromSpawnSpot( ); + } +} + +/* +=========== +idPlayer::SelectSpawnPoint + +Find a spawn point marked, otherwise use normal spawn selection. +============ +*/ +bool idPlayer::SelectSpawnPoint( idVec3 &origin, idAngles &angles ) { + idEntity *spot; + idStr skin; + + spot = gameLocal.SelectSpawnPoint( this ); + + // no spot, try again next frame + if( !spot ) { + forceRespawn = true; + return false; + } + + // set the player skin from the spawn location + if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) { + spawnArgs.Set( "spawn_skin", skin ); + } + + if ( spot->spawnArgs.GetString( "spawn_model", NULL ) ) { + spawnArgs.Set( "model", spot->spawnArgs.GetString( "spawn_model", NULL ) ); + } + + if ( spot->spawnArgs.GetString( "def_head", NULL ) ) { + spawnArgs.Set( "def_head", spot->spawnArgs.GetString( "def_head", NULL ) ); + } + + // activate the spawn locations targets + spot->PostEventMS( &EV_ActivateTargets, 0, this ); + + origin = spot->GetPhysics()->GetOrigin(); + origin[2] += 4.0f + CM_BOX_EPSILON; // move up to make sure the player is at least an epsilon above the floor + angles = spot->GetPhysics()->GetAxis().ToAngles(); + + return true; +} + +/* +=========== +idPlayer::SpawnFromSpawnSpot + +Chooses a spawn location and spawns the player +============ +*/ +void idPlayer::SpawnFromSpawnSpot( void ) { + idVec3 spawn_origin; + idAngles spawn_angles; + + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + forceRespawn = true; + return; + } + SpawnToPoint( spawn_origin, spawn_angles ); +} + +/* +=========== +idPlayer::SpawnToPoint + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState + +when called here with spectating set to true, just place yourself and init +============ +*/ +void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) { + idVec3 spec_origin; + + assert( !gameLocal.isClient ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // Record previous weapons for later restoration + inventory.carryOverWeapons &= ~CARRYOVER_WEAPONS_MASK; + inventory.carryOverWeapons |= inventory.weapons; + } +// RITUAL END + + respawning = true; + + Init(); + + // Force players to use bounding boxes when in multiplayer + if ( gameLocal.isMultiplayer ) { + use_combat_bbox = true; + + // Make sure the combat model is unlinked + if ( combatModel ) { + combatModel->Unlink( ); + } + } + + // Any health over max health will tick down + if ( health > inventory.maxHealth ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } + + fl.noknockback = false; + // stop any ragdolls being used + StopRagdoll(); + // set back the player physics + SetPhysics( &physicsObj ); + physicsObj.SetClipModelAxis(); + physicsObj.EnableClip(); + if ( !spectating ) { + SetCombatContents( true ); + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + // setup our initial view + if ( !spectating ) { + SetOrigin( spawn_origin ); +// RAVEN BEGIN +// abahr: taking into account gravity + SetAxis( spawn_angles.ToMat3() ); +// RAVEN END + } else { + spec_origin = spawn_origin; + spec_origin[ 2 ] += pm_normalheight.GetFloat(); + spec_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spec_origin ); + } + + // if this is the first spawn of the map, we don't have a usercmd yet, + // so the delta angles won't be correct. This will be fixed on the first think. + viewAngles = ang_zero; + SetDeltaViewAngles( ang_zero ); + SetViewAngles( spawn_angles ); + spawnAngles = spawn_angles; + spawnAnglesSet = false; + + legsForward = true; + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + + if ( spectating ) { + Hide(); + } else { + Show(); + } + + if ( gameLocal.isMultiplayer ) { + if ( !spectating ) { + // we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof' + if ( lastTeleFX < gameLocal.time - 1000 ) { + // currentThinkingEntity not set here (called out of Run()) + idEntity* thinker = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + gameLocal.PlayEffect( spawnArgs, "fx_spawn", renderEntity.origin, idVec3(0,0,1).ToMat3(), false, vec3_origin, true ); + lastTeleFX = gameLocal.time; + gameLocal.currentThinkingEntity = thinker; + } + } +//RAVEN BEGIN +//asalmon: Clear the respwan message + if ( mphud ) { + mphud->SetStateInt( "respawnNotice", 0 ); + } +//RAVEN END + pfl.teleport = true; + } else { + pfl.teleport = false; + } + + // kill anything at the new position + if ( !spectating ) { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it +// RAVEN BEGIN +// abahr: this is killing the tram car when spawning in. Ooooops! + if( gameLocal.isMultiplayer ) { + gameLocal.KillBox( this ); + } +// RAVEN END + } + + // don't allow full run speed for a bit + physicsObj.SetKnockBack( 100 ); + + // set our respawn time and buttons so that if we're killed we don't respawn immediately + minRespawnTime = gameLocal.time; + maxRespawnTime = gameLocal.time; + if ( !spectating ) { + forceRespawn = false; + } + + privateCameraView = NULL; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + // restore previous weapons + inventory.weapons |= inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + for( int weaponIndex = 0; weaponIndex < MAX_WEAPONS; weaponIndex++ ) + { + if( inventory.weapons & ( 1 << weaponIndex ) ) + { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( weaponIndex ); + inventory.ammo[ ammoIndex ] = inventory.StartingAmmoForWeaponIndex( weaponIndex ); + } + } + + /// Restore armor purchased while dead + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_LIGHT; + GiveItem( "item_armor_small" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_HEAVY; + GiveItem( "item_armor_large" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_AMMO; + GiveItem( "ammorefill" ); + } + + // Reactivate team powerups + gameLocal.mpGame.SetUpdateForTeamPowerups(team); + UpdateTeamPowerups(); + } +// RITUAL END + + BecomeActive( TH_THINK ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + Think(); + + respawning = false; + isTelefragged = false; + isLagged = false; + isChatting = false; + + lastImpulsePlayer = NULL; + lastImpulseTime = 0; +} + +/* +=============== +idPlayer::SavePersistantInfo + +Saves any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::SavePersistantInfo( void ) { + idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber]; + + playerInfo.Clear(); + inventory.GetPersistantData( playerInfo ); + playerInfo.SetInt( "health", health ); + playerInfo.SetInt( "current_weapon", currentWeapon ); +} + +/* +=============== +idPlayer::RestorePersistantInfo + +Restores any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::RestorePersistantInfo( void ) { + if ( gameLocal.isMultiplayer ) { + gameLocal.persistentPlayerInfo[entityNumber].Clear(); + } + + spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] ); + + inventory.RestoreInventory( this, spawnArgs ); + health = spawnArgs.GetInt( "health", "100" ); + if ( !gameLocal.isClient ) { + idealWeapon = spawnArgs.GetInt( "current_weapon", "0" ); + } +} + +/* +================ +idPlayer::GetUserInfo +================ +*/ +idDict *idPlayer::GetUserInfo( void ) { + return &gameLocal.userInfo[ entityNumber ]; +} + +/* +============== +idPlayer::UpdateModelSetup +Updates the player's model setup. Model setups are read from the player def, and contain +information about the player's model, the player's head model, a skin, and a team for the +composite model. +============== +*/ +void idPlayer::UpdateModelSetup( bool forceReload ) { + const idDict* dict; + const char* uiKeyName = NULL; + const char* defaultModel = NULL; + const char* newModelName = NULL; + + if( !gameLocal.isMultiplayer || spectating ) { + return; + } + + if( gameLocal.IsTeamGame() ) { + defaultModel = spawnArgs.GetString( va( "def_default_model_%s", idMultiplayerGame::teamNames[ team ] ), NULL ); + + if( g_forceMarineModel.GetString()[ 0 ] && team == TEAM_MARINE ) { + newModelName = g_forceMarineModel.GetString(); + } else if( g_forceStroggModel.GetString()[ 0 ] && team == TEAM_STROGG ) { + newModelName = g_forceStroggModel.GetString(); + } else { + uiKeyName = va( "ui_model_%s", idMultiplayerGame::teamNames[ team ] ); + newModelName = GetUserInfo()->GetString( uiKeyName ); + } + } else { + defaultModel = spawnArgs.GetString( "def_default_model" ); + + if( g_forceModel.GetString()[ 0 ] ) { + newModelName = g_forceModel.GetString(); + } else { + uiKeyName = "ui_model"; + newModelName = GetUserInfo()->GetString( uiKeyName ); + } + } + + if( !idStr::Icmp( newModelName, "" ) ) { + newModelName = defaultModel; + } + + // model hasn't changed + if( !modelName.Icmp( newModelName ) && !forceReload ) { + return; + } + + rvDeclPlayerModel* model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + + // validate that the model they've selected is OK for this team game + if( gameLocal.IsTeamGame() && model ) { + if( idStr::Icmp( model->team, idMultiplayerGame::teamNames[ team ] ) ) { + gameLocal.Warning( "idPlayer::UpdateModelSetup() - Player %d (%s) set to model %s which is restricted to team %s (Player team: %s)\n", entityNumber, GetUserInfo()->GetString( "ui_name" ), newModelName, model->team.c_str(), idMultiplayerGame::teamNames[ team ] ); + if( uiKeyName ) { + cvarSystem->SetCVarBool( uiKeyName, "" ); + } + + dict = NULL; + } + } + + // check to see if the user-specified ui_model/ui_model_strogg/ui_model_marine is valid + if( !model ) { + newModelName = defaultModel; + + model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + if( !model ) { + gameLocal.Error( "idPlayer::UpdateModelSetup() - Can't find default model (%s)\n", defaultModel ); + } else { + // If it's not valid, set the cvar to the default model + if( uiKeyName ) { + GetUserInfo()->Set( uiKeyName, defaultModel ); + + if( entityNumber == gameLocal.localClientNum ) { + cvarSystem->SetCVarString( uiKeyName, defaultModel ); + } + + if( gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "updateUI %d\n", entityNumber ) ); + return; + } + } + + } + } + + modelName = newModelName; + modelDecl = model; + + reloadModel = true; + + // if we have a strogg model, set the strogg flag here + isStrogg = false; + if( modelDecl && !modelDecl->team.Icmp( "strogg" ) ) { + isStrogg = true; + } +} + +/* +============== +idPlayer::BalanceTeam +============== +*/ +bool idPlayer::BalanceTeam( void ) { + int i, balanceTeam, teamCount[2]; + idEntity *ent; + + teamCount[ 0 ] = teamCount[ 1 ] = 0; + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::Type ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ 0 ] < teamCount[ 1 ] ) { + balanceTeam = 0; + } else if ( teamCount[ 0 ] > teamCount[ 1 ] ) { + balanceTeam = 1; + } + + if ( balanceTeam != -1 && team != balanceTeam && teamCount[ balanceTeam ]+1 != teamCount[ !balanceTeam ] ) { + common->DPrintf( "team balance: forcing player %d to %s team\n", entityNumber, balanceTeam ? "strogg" : "marine" ); + team = balanceTeam; + GetUserInfo()->Set( "ui_team", team ? "Strogg" : "Marine" ); + return true; + } + return false; +} + +void HSVtoRGB( float &r, float &g, float &b, float h, float s, float v ) { + int i; + float f, p, q, t; + + h = idMath::ClampFloat( 0.0f, 360.0f, h ); + s = idMath::ClampFloat( 0.0f, 1.0f, s ); + v = idMath::ClampFloat( 0.75f, 1.0f, v ); + + if( s == 0 ) { + // achromatic (grey) + r = g = b = v; + return; + } + + h /= 60; // sector 0 to 5 + i = floor( h ); + f = h - i; // factorial part of h + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch( i ) { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: // case 5: + r = v; + g = p; + b = q; + break; + } +} + +/* +============== +idPlayer::UserInfoChanged +============== +*/ +bool idPlayer::UserInfoChanged( void ) { + idDict *userInfo; + bool modifiedInfo; + bool spec; + bool newready; + + userInfo = GetUserInfo(); + showWeaponViewModel = userInfo->GetBool( "ui_showGun" ); + + if ( !gameLocal.isMultiplayer ) { + return false; + } + + modifiedInfo = false; + + spec = ( idStr::Icmp( userInfo->GetString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + // never let spectators go back to game while sudden death is on + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == SUDDENDEATH && !spec && wantSpectate == true ) { + userInfo->Set( "ui_spectate", "Spectate" ); + modifiedInfo |= true; + } else { + if ( spec != wantSpectate && !spec ) { + // returning from spectate, set forceRespawn so we don't get stuck in spectate forever + forceRespawn = true; + } + wantSpectate = spec; + } + } else { + if ( spec ) { + userInfo->Set( "ui_spectate", "Play" ); + modifiedInfo |= true; + } else if ( spectating ) { + // allow player to leaving spectator mode if they were in it when it was disallowed + forceRespawn = true; + } + wantSpectate = false; + } + + if ( gameLocal.serverInfo.GetBool( "si_useReady" ) ) { + newready = ( idStr::Icmp( userInfo->GetString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready != newready && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP && !wantSpectate ) { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_107180" ), userInfo->GetString( "ui_name" ), newready ? common->GetLocalizedString( "#str_104300" ) : common->GetLocalizedString( "#str_104301" ) ); + } + ready = newready; + } + + int newTeam = ( idStr::Icmp( userInfo->GetString( "ui_team" ), "Strogg" ) == 0 ); + + if( hud && gameLocal.IsTeamGame() ) { + hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + } else if( hud ) { + hud->HandleNamedEvent( "setTeam_marine" ); + } + + if ( gameLocal.IsTeamGame() && newTeam != latchedTeam ) { + team = newTeam; + + if ( gameLocal.isServer ) { + int verifyTeam = gameLocal.mpGame.VerifyTeamSwitch( newTeam, this ); + if( verifyTeam != newTeam ) { + if( verifyTeam == TEAM_MARINE || verifyTeam == TEAM_STROGG ) { + gameLocal.userInfo[ entityNumber ].Set( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + if( gameLocal.localClientNum == entityNumber ) { + cvarSystem->SetCVarString( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + } + modifiedInfo = true; + team = verifyTeam; + } + } + } + + // if still OK to change + if( team != latchedTeam ) { + if( gameLocal.isServer ) { + gameLocal.mpGame.SwitchToTeam( entityNumber, latchedTeam, team ); + } + + SetInitialHud(); + if( mphud ) { + mphud->SetStateInt( "playerteam", team ); + mphud->HandleNamedEvent( "TeamChange" ); + } + + if( entityNumber == gameLocal.localClientNum ) { + alreadyDidTeamAnnouncerSound = true; + // the client might set its team to a value before the server corrects for team balance + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_MARINE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_STROGG ); + + if ( !wantSpectate ) { + if( team == TEAM_STROGG ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( team == TEAM_MARINE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + + // ATVI DevTrack #13224 - update on each team change + iconManager->UpdateTeamIcons(); + + latchedTeam = team; + } + } + + //if ( !gameLocal.isClient && gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // bool teamsBalanced = BalanceTeam(); + // modifiedInfo |= teamsBalanced; + //} + + UpdateModelSetup(); + + if( (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber )) || (gameLocal.isClient && gameLocal.localClientNum == entityNumber) ) { + isChatting = userInfo->GetBool( "ui_chat", "0" ); + if ( isChatting && pfl.dead ) { + // if dead, always force chat icon off. + isChatting = false; + userInfo->SetBool( "ui_chat", false ); + modifiedInfo |= true; + } + } + + // grab hitscan tint + hitscanTint = userInfo->GetVec4( "ui_hitscanTint", "81 1 1 1" ); + HSVtoRGB( hitscanTint.x, hitscanTint.y, hitscanTint.z, hitscanTint.x, hitscanTint.y, hitscanTint.z ); + // force alpha to 1 + hitscanTint.w = 1.0f; + + return modifiedInfo; +} + +/* +=============== +idPlayer::UpdateHudAmmo +=============== +*/ +void idPlayer::UpdateHudAmmo( idUserInterface *_hud ) { + int inclip; + int ammoamount; + + assert( weapon ); + assert( _hud ); + + inclip = weapon->AmmoInClip(); + ammoamount = weapon->AmmoAvailable(); + + if ( ammoamount < 0 ) { + // show infinite ammo + _hud->SetStateString( "player_ammo", "-1" ); + _hud->SetStateString( "player_totalammo", "-1" ); + _hud->SetStateFloat ( "player_ammopct", 1.0f ); + } else if ( weapon->ClipSize ( ) && !gameLocal.isMultiplayer ) { + _hud->SetStateInt ( "player_clip_size", weapon->ClipSize() ); + _hud->SetStateFloat ( "player_ammopct", (float)inclip / (float)weapon->ClipSize ( ) ); + if ( weapon->ClipSize ( )==1) { + _hud->SetStateInt ( "player_totalammo", ammoamount ); + } + else { + _hud->SetStateInt ( "player_totalammo", ammoamount - inclip ); + } + _hud->SetStateInt ( "player_ammo", inclip ); + } else { + _hud->SetStateFloat ( "player_ammopct", (float)ammoamount / (float)weapon->maxAmmo ); + _hud->SetStateInt ( "player_totalammo", ammoamount ); + _hud->SetStateInt ( "player_ammo", -1 ); + } + + _hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) ); +} + +/* +=============== +idPlayer::UpdateHudStats +=============== +*/ +void idPlayer::UpdateHudStats( idUserInterface *_hud ) { + int temp; + + assert ( _hud ); + + temp = _hud->State().GetInt ( "player_health", "-1" ); + if ( temp != health ) { + _hud->SetStateInt ( "player_healthDelta", temp == -1 ? 0 : (temp - health) ); + _hud->SetStateInt ( "player_health", health < -100 ? -100 : health ); + _hud->SetStateFloat ( "player_healthpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)health / (float)inventory.maxHealth ) ); + _hud->HandleNamedEvent ( "updateHealth" ); + } + + temp = _hud->State().GetInt ( "player_armor", "-1" ); + if ( temp != inventory.armor ) { + _hud->SetStateInt ( "player_armorDelta", temp == -1 ? 0 : (temp - inventory.armor) ); + _hud->SetStateInt ( "player_armor", inventory.armor ); + _hud->SetStateFloat ( "player_armorpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)inventory.armor / (float)inventory.maxarmor ) ); + _hud->HandleNamedEvent ( "updateArmor" ); + } + + // Boss bar + if ( _hud->State().GetInt ( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt ( "boss_health", -1 ); + _hud->HandleNamedEvent ( "hideBossBar" ); + _hud->HandleNamedEvent ( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt ( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent ( "updateBossBar" ); + } + } + + // god mode information + _hud->SetStateString( "player_god", va( "%i", (godmode && g_showGodDamage.GetBool()) ) ); + _hud->SetStateString( "player_god_damage", va( "%i", godmodeDamage ) ); + + // Update the hit direction + idVec3 localDir; + viewAxis.ProjectVector( lastDamageDir, localDir ); + _hud->SetStateFloat( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + + //_hud->HandleNamedEvent( "updateArmorHealthAir" ); + + if ( weapon ) { + UpdateHudAmmo( _hud ); + } + + _hud->StateChanged( gameLocal.time ); +} + +/* +=============== +idPlayer::UpdateHudWeapon +=============== +*/ +void idPlayer::UpdateHudWeapon( int displayWeapon ) { + + if ( (displayWeapon < -1) || (displayWeapon >= MAX_WEAPONS) ) { + common->DPrintf( "displayweapon was out of range" ); + return; + } + + int index = 0; + int idealIndex = 0; + idUserInterface * hud = idPlayer::hud; + idUserInterface * mphud = idPlayer::mphud; + idUserInterface * cursor = idPlayer::cursor; + + if ( !gameLocal.GetLocalPlayer() ) { + // server netdemo + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() == entityNumber ) { + hud = gameLocal.GetDemoHud(); + mphud = gameLocal.GetDemoMphud(); + cursor = gameLocal.GetDemoCursor(); + } + } else { + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud && p->mphud ); + hud = p->hud; + mphud = p->mphud; + cursor = p->GetCursorGUI(); + } + } + + if ( !hud || !weapon ) { + return; + } + + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + const char *weapnum = va( "weapon%d", i ); + int weapstate = 0; + if ( inventory.weapons & ( 1 << i ) && spawnArgs.GetBool( va( "weapon%d_cycle", i ) ) ) { + hud->SetStateBool( weapnum, true ); + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + weapstate++; + + if ( idealWeapon == i ) { + idealIndex = index; + } + + const char *weaponIcon = GetWeaponDef ( i )->dict.GetString ( "inv_icon" ); + //const idMaterial *material = declManager->FindMaterial( weaponIcon ); + //if ( material ) { + // material->SetSort( SS_GUI ); + //} + + hud->SetStateInt ( va("weapon%d_index", i ), index++ ); + hud->SetStateString ( va("weapon%d_icon", i ), weaponIcon ); + hud->SetStateInt ( va("weapon%d_ammo", i ), inventory.ammo[inventory.AmmoIndexForWeaponClass ( weap ) ] ); + } + } else { + hud->SetStateBool( weapnum, false ); + } + } + + hud->SetStateInt( "weaponcount", index ); + + const idMaterial *material = declManager->FindMaterial( weapon->GetIcon() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + material = declManager->FindMaterial( weapon->spawnArgs.GetString ( "inv_icon" ) ); + //if ( material ) { + // material->SetSort( SS_GUI ); + //} + + hud->SetStateString( "weapicon", weapon->GetIcon() ); + hud->SetStateString( "ammoIcon", weapon->spawnArgs.GetString ( "inv_icon" ) ); + hud->SetStateInt( "player_weapon", currentWeapon ); + hud->SetStateInt( "player_lastweapon", previousWeapon ); + hud->SetStateInt( "player_idealweapon", ((displayWeapon!=-1)?displayWeapon:idealWeapon) ); + hud->SetStateInt( "player_idealIndex", idealIndex ); + + // Weapon name for weapon selection + const idDeclEntityDef* w = GetWeaponDef( ( displayWeapon != -1 ) ? displayWeapon:idealWeapon ); + if ( w ) { + idStr langToken = w->dict.GetString( "inv_name" ); + hud->SetStateString( "weaponname", common->GetLocalizedString( langToken ) ); + } + + UpdateHudAmmo( hud ); + + if ( cursor ) { + weapon->UpdateCrosshairGUI( cursor ); + + // mekberg: force a redraw so ON_INIT gets called and doesn't stomp all over + // the color values we set in weaponChange. + cursor->Redraw( gameLocal.time ); + cursor->HandleNamedEvent( "weaponChange" ); + } + + hud->HandleNamedEvent( "weaponChange" ); + hud->StateChanged( gameLocal.time ); + weaponChangeIconsUp = true; + + // if previousWeapon is -1, the weaponChange state won't update it, so manually reset it +#ifdef _XENON + if( previousWeapon == -1 ) { + ResetHUDWeaponSwitch(); + } +#endif +} + +/* +=============== +idPlayer::StartRadioChatter +=============== +*/ +void idPlayer::StartRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterUp" ); + } + if ( vehicleController.IsDriving ( ) ) { + vehicleController.StartRadioChatter ( ); + } +} + +/* +=============== +idPlayer::StopRadioChatter +=============== +*/ +void idPlayer::StopRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + if ( vehicleController.IsDriving( ) ) { + vehicleController.StopRadioChatter( ); + } +} + +/* +=============== +idPlayer::DrawShadow +=============== +*/ +void idPlayer::DrawShadow( renderEntity_t *headRenderEnt ) { + if ( gameLocal.isMultiplayer && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all player shadows for the local client + renderEntity.suppressShadowInViewID = gameLocal.localClientNum+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = gameLocal.localClientNum+1; + } + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all player shadows + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + // Only show player shadows for other clients + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } +} + +/* +=============== +idPlayer::DrawHUD +=============== +*/ +void idPlayer::DrawHUD( idUserInterface *_hud ) { + idUserInterface * cursor = idPlayer::cursor; + + if ( !gameLocal.GetLocalPlayer() ) { + // server netdemo + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() == entityNumber ) { + cursor = gameLocal.GetDemoCursor(); + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + } + } else { + + if ( team != gameLocal.GetLocalPlayer()->hudTeam && _hud ) { + _hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + gameLocal.GetLocalPlayer()->hudTeam = team; + } + + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + } + } + + if ( disableHud || influenceActive != INFLUENCE_NONE || privateCameraView || !_hud || !g_showHud.GetBool() ) { + return; + } + + if ( objectiveSystemOpen ) { + if ( !GuiActive() ) { + // showing weapon zoom gui when objectives are open because that's the way I'z told to make it werkz + if ( weapon && weapon->GetZoomGui( ) && zoomed ) { + weapon->GetZoomGui( )->Redraw( gameLocal.time ); + } + } + return; + } + + if ( gameLocal.isMultiplayer ) { + _hud->SetStateBool( "mp", true ); + } else { + _hud->SetStateBool( "mp", false ); + } + + // Draw the cinematic hud when in a cinematic + if ( gameLocal.GetCamera( ) ) { + if ( cinematicHud && !(gameLocal.editors & EDITOR_MODVIEW) ) { + cinematicHud->Redraw( gameLocal.time ); + } + return; + } + + // Let the vehicle draw the hud instead + if ( vehicleController.IsDriving( ) ) { + if ( !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + vehicleController.DrawHUD( ); + if ( cursor && health > 0 ) { + // mekberg: adjustable crosshair size. + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); + cursor->SetStateBool( "vehiclecursor", true ); + + vehicleController.UpdateCursorGUI( cursor ); + cursor->Redraw( gameLocal.time ); + } + } + _hud = GetHud(); + // Boss bar + if ( _hud && _hud->State().GetInt( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt( "boss_health", -1 ); + _hud->HandleNamedEvent( "hideBossBar" ); + _hud->HandleNamedEvent( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent( "updateBossBar" ); + } + } + return; + } + + if ( cursor ) { + cursor->SetStateBool( "vehiclecursor", false ); + } + + // FIXME: this is temp to allow the sound meter to show up in the hud + // it should be commented out before shipping but the code can remain + // for mod developers to enable for the same functionality + _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) ); + + // don't draw main hud in spectator (only mphud) + if ( !spectating && !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + // weapon targeting crosshair + if ( !GuiActive() ) { + if ( weapon && weapon->GetZoomGui( ) && zoomed ) { + weapon->GetZoomGui( )->Redraw( gameLocal.time ); + } +// RAVEN BEGIN +// mekberg: removed check for weapon->ShowCrosshair. +// we want to show the crosshair regardless for NPC tags + if ( cursor && health > 0 ) { + // Pass the current weapon to the cursor gui for custom crosshairs +// mekberg: adjustable crosshair size. + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); +// RAVEN END + cursor->Redraw( gameLocal.time ); + } + } + + UpdateHudStats( _hud ); + + if ( focusBrackets ) { + // If 2d_calc is still true then the gui didnt render so we can abandon it + if ( focusBrackets->State().GetBool( "2d_calc" ) ) { + focusBrackets->SetStateBool( "2d_calc", false ); + focusBrackets = NULL; + focusBracketsTime = 0; + _hud->HandleNamedEvent( "hideBrackets" ); + } else { + _hud->SetStateString( "bracket_left", focusBrackets->State().GetString( "2d_min_x" ) ); + _hud->SetStateString( "bracket_top", focusBrackets->State().GetString( "2d_min_y" ) ); + _hud->SetStateFloat( "bracket_width", focusBrackets->State().GetFloat( "2d_max_x" ) - focusBrackets->State().GetFloat( "2d_min_x" ) ); + _hud->SetStateFloat( "bracket_height", focusBrackets->State().GetFloat( "2d_max_y" ) - focusBrackets->State().GetFloat( "2d_min_y" ) ); + // TODO: Find a way to get bracket text from gui to hud + } + } + _hud->Redraw( gameLocal.realClientTime ); + } + + if ( gameLocal.isMultiplayer ) { + idUserInterface* _mphud = mphud; + // server netdemos don't have a local player, grab the right mphud + if ( !gameLocal.GetLocalPlayer() ) { + assert( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + _mphud = gameLocal.GetDemoMphud(); + } else if ( gameLocal.GetLocalPlayer() != this ) { + // if we're spectating someone else, use our local hud + _mphud = gameLocal.GetLocalPlayer()->mphud; + } + if ( _mphud ) { + gameLocal.mpGame.UpdateHud( _mphud ); + _mphud->Redraw( gameLocal.time ); + } + + if ( overlayHud && overlayHudTime > gameLocal.time && overlayHudTime != 0 ) { + overlayHud->Redraw( gameLocal.time ); + } else { + overlayHud = NULL; + overlayHudTime = 0; + } + } +} + +/* +=============== +idPlayer::EnterCinematic +=============== +*/ +void idPlayer::EnterCinematic( void ) { + Hide(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(true); +// RAVEN END + + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + + cinematicHud = NULL; + + // See if camera has custom cinematic gui + if ( gameLocal.GetCamera ( ) ) { + const char* guiCinematic; + guiCinematic = gameLocal.GetCamera()->spawnArgs.GetString ( "guiCinematic", "" ); + if ( *guiCinematic ) { + cinematicHud = uiManager->FindGui( guiCinematic, true, false, true ); + } + } + + // Load default cinematic gui? + if ( !cinematicHud ) { + const char* temp; + if ( spawnArgs.GetString ( "cinematicHud", "", &temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + } + + // Have the cinematic hud start + if ( cinematicHud ) { + cinematicHud->Activate ( true, gameLocal.time ); + cinematicHud->HandleNamedEvent ( "cinematicStart" ); +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( cvarSystem->GetCVarInteger( "r_aspectRatio" ) != 0 ) { + cinematicHud->HandleNamedEvent ( "hideLetterbox" ); + } +// RAVEN END + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + if ( weaponEnabled && weapon ) { + //this preSave kills all effects and sounds that we don't need lingering around. + weapon->PreSave(); + weapon->EnterCinematic(); + } + + // Reset state flags + memset ( &pfl, 0, sizeof(pfl) ); + pfl.onGround = true; + pfl.dead = (health <= 0); +} + +/* +=============== +idPlayer::ExitCinematic +=============== +*/ +void idPlayer::ExitCinematic( void ) { + Show(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(false); +// RAVEN END + + if ( weaponEnabled && weapon ) { + //and this will restore them! + weapon->PostSave(); + weapon->ExitCinematic(); + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + UpdateState(); +} + +/* +=============== +idPlayer::SkipCinematic +=============== +*/ +bool idPlayer::SkipCinematic( void ) { + StartSound( "snd_skipcinematic", SND_CHANNEL_ANY, 0, false, NULL ); + return gameLocal.SkipCinematic(); +} + +/* +===================== +idPlayer::UpdateConditions +===================== +*/ +void idPlayer::UpdateConditions( void ) { + idVec3 velocity; + float fallspeed; + float forwardspeed; + float sidespeed; + + // minus the push velocity to avoid playing the walking animation and sounds when riding a mover + velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity(); + fallspeed = velocity * physicsObj.GetGravityNormal(); + + if ( influenceActive ) { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } else if ( gameLocal.time - lastDmgTime < 500 ) { + forwardspeed = velocity * viewAxis[ 0 ]; + sidespeed = velocity * viewAxis[ 1 ]; + pfl.forward = pfl.onGround && ( forwardspeed > 20.01f ); + pfl.backward = pfl.onGround && ( forwardspeed < -20.01f ); + pfl.strafeLeft = pfl.onGround && ( sidespeed > 20.01f ); + pfl.strafeRight = pfl.onGround && ( sidespeed < -20.01f ); + } else if ( xyspeed > MIN_BOB_SPEED ) { + pfl.forward = pfl.onGround && ( usercmd.forwardmove > 0 ); + pfl.backward = pfl.onGround && ( usercmd.forwardmove < 0 ); + pfl.strafeLeft = pfl.onGround && ( usercmd.rightmove < 0 ); + pfl.strafeRight = pfl.onGround && ( usercmd.rightmove > 0 ); + } else { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } + + pfl.run = 1; + pfl.dead = ( health <= 0 ); +} + +/* +================== +idPlayer::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) { + // force a blink + blink_time = 0; + + // play the fire animation + pfl.weaponFired = true; + + // Bias the intent direction more heavily due to firing + BiasIntentDir( viewAxis[0]*100.0f, 1.0f ); + + // update view feedback + playerView.WeaponFireFeedback( weaponDef ); +} + +/* +=============== +idPlayer::StopFiring +=============== +*/ +void idPlayer::StopFiring( void ) { + pfl.attackHeld = false; + pfl.weaponFired = false; + pfl.reload = false; + if ( weapon ) { + weapon->EndAttack(); + } +} + +/* +=============== +idPlayer::FireWeapon +=============== +*/ +void idPlayer::FireWeapon( void ) { + idMat3 axis; + idVec3 muzzle; + +//RITUAL BEGIN + if( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) + { + return; + } +//RITUAL END + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ); + return; + } + + if ( !hiddenWeapon && weapon->IsReady() ) { + // cheap hack so in MP the LG isn't allowed to fire in the short lapse while it goes from Fire -> Idle before changing to another weapon + // this gimps the weapon a lil bit but is consistent with the visual feedback clients are getting since 1.0 + bool noFireWhileSwitching = false; + noFireWhileSwitching = ( gameLocal.isMultiplayer && idealWeapon != currentWeapon && weapon->NoFireWhileSwitching() ); + if ( !noFireWhileSwitching ) { + if ( weapon->AmmoInClip() || weapon->AmmoAvailable() ) { + pfl.attackHeld = true; + weapon->BeginAttack(); + } else { + pfl.attackHeld = false; + pfl.weaponFired = false; + StopFiring(); + NextBestWeapon(); + } + } else { + StopFiring(); + } + } + // If reloading when fire is hit cancel the reload + else if ( weapon->IsReloading() ) { + weapon->CancelReload(); + } +/* twhitaker: removed this at the request of Matt Vainio. + if ( !gameLocal.isMultiplayer ) { + if ( hud && tipUp ) { + HideTip(); + } + // may want to track with with a bool as well + // keep from looking up named events so often + if ( objectiveSystem && objectiveUp ) { + HideObjective(); + } + } +*/ + if( hud && weaponChangeIconsUp ) { + hud->HandleNamedEvent( "weaponFire" ); + // nrausch: objectiveSystem does not necessarily exist (in mp it doesn't) + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "weaponFire" ); + } + weaponChangeIconsUp = false; + } +} + +/* +=============== +idPlayer::CacheWeapons +=============== +*/ +void idPlayer::CacheWeapons( void ) { + idStr weap; + int w; + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + for( w = 0; w < MAX_WEAPONS; w++ ) { + if ( inventory.weapons & ( 1 << w ) ) { + if ( !GetWeaponDef ( w ) ) { + inventory.weapons &= ~( 1 << w ); + } else { + rvWeapon::CacheWeapon( spawnArgs.GetString( va( "def_weapon%d", w ) ) ); + } + } + } +} + +/* +=============== +idPlayer::Give +=============== +*/ +bool idPlayer::Give( const char *statname, const char *value, bool dropped ) { + int amount; + + if ( pfl.dead ) { + return false; + } + + if ( IsInVehicle ( ) ) { + vehicleController.Give ( statname, value ); + } + + int boundaryHealth = inventory.maxHealth; + int boundaryArmor = inventory.maxarmor; + if( PowerUpActive( POWERUP_GUARD ) ) { + boundaryHealth = inventory.maxHealth / 2; + boundaryArmor = inventory.maxarmor / 2; + } + if( PowerUpActive( POWERUP_SCOUT ) ) { + boundaryArmor = 0; + } + if ( gameLocal.isMultiplayer ) { + //In MP, you can get twice your max from pickups + boundaryArmor *= 2; + } + + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= boundaryHealth ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth ) { + health = boundaryHealth; + } + } + } else if ( !idStr::Icmp( statname, "bonushealth" ) ) { + // allow health over max health + if ( health >= boundaryHealth * 2 ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth * 2 ) { + health = boundaryHealth * 2; + } + } + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( inventory.armor >= boundaryArmor ) { + return false; + } + amount = atoi( value ); + + inventory.armor += amount; + if ( inventory.armor > boundaryArmor ) { + inventory.armor = boundaryArmor; + } + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } else if ( !idStr::Icmp( statname, "air" ) ) { + if ( airTics >= pm_airTics.GetInteger() ) { + return false; + } + airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger(); + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } else if ( !idStr::Icmp ( statname, "weaponmod" ) ) { + if( !idStr::Icmp( value, "all" ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + GiveWeaponMods( i, 0xFFFFFFFF ); + } + } + } else { + const char* pos = value; + + while( pos != NULL ) { + const char* end = strchr( pos, ',' ); + int len; + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponMod ( pos, 0, len ); + GiveWeaponMod ( weaponMod ); + + pos = end; + } + } + } else { + return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true, dropped ); + } + return true; +} + +/* +=============== +idPlayer::GiveItem + +Returns false if the item shouldn't be picked up +=============== +*/ +bool idPlayer::GiveItem( idItem *item ) { + int i; + const idKeyValue *arg; + idDict attr; + bool gave; + + bool dropped = item->spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && spectating ) { + return false; + } + + item->GetAttributes( attr ); + + if( gameLocal.isServer || !gameLocal.isMultiplayer ) { + gave = false; + bool skipWeaponKey = false; + bool skipRestOfKeys = false; + if ( gameLocal.IsMultiplayer() ) { + dropped = item->spawnArgs.GetBool( "dropped" ); + if ( item->spawnArgs.FindKey( "weaponclass" ) ) { + //this is really fucking lame, but + //this is the only way we know we're trying + //to pick up a weapon before we blindly start + //processesing the attribute arguments in + //whatever order they're in below. We need + //to not process any at all if we're not allowed + //to pick up the weapon in the first place! + arg = attr.FindKey( "weapon" ); + if ( arg ) { + skipWeaponKey = true; + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } else if ( !dropped//not a dropped weapon + && gameLocal.IsWeaponsStayOn() ) { + //if failed to give weapon, don't give anything else with the weapon + skipRestOfKeys = true; + } + } + } + } + if ( !skipRestOfKeys ) { + for( i = 0; i < attr.GetNumKeyVals(); i++ ) { + arg = attr.GetKeyVal( i ); + if ( skipWeaponKey && arg->GetKey() == "weapon" ) { + //already processed this above + continue; + } + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } + } + } + + // hack - powerups call into this code to let them give stuff based on inv_ keywords + // for powerups that don't have any ammo/etc to give to the player, we still want to + // display the inv_name on the hud + // since idItemPowerup::GiveToPlayer() handles whether or not a player gets a powerup, + // we can override gave here for powerups + if ( !gave && !item->IsType( idItemPowerup::GetClassType() ) ) { + return false; + } + } else { + gave = true; + } + + arg = item->spawnArgs.MatchPrefix( "inv_ammo_", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "ammoPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_health", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL ); + if ( arg && hud ) { + // We need to update the weapon hud manually, but not + // the armor/ammo/health because they are updated every + // frame no matter what + if ( gameLocal.isMultiplayer ) { + UpdateHudWeapon( ); + } else { + //so weapon mods highlight the correct weapon when received + int weapon = SlotForWeapon ( arg->GetValue() ); + UpdateHudWeapon( weapon ); + } + hud->HandleNamedEvent( "weaponPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_armor", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "armorPulse" ); + } + +// GiveDatabaseEntry ( &item->spawnArgs ); + + // Show the item pickup on the hud + if ( hud ) { + idStr langToken = item->spawnArgs.GetString( "inv_name" ); + hud->SetStateString ( "itemtext", common->GetLocalizedString( langToken ) ); + hud->SetStateString ( "itemicon", item->spawnArgs.GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } +//RITUAL BEGIN + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + gameLocal.mpGame.RedrawLocalBuyMenu(); +//RITUAL END + + return gave; +} + +/* +=============== +idPlayer::PowerUpModifier +=============== +*/ +float idPlayer::PowerUpModifier( int type ) { + float mod = 1.0f; + + if ( PowerUpActive( POWERUP_QUADDAMAGE ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_PROJECTILE_DEATHPUSH: { + mod *= 2.0f; + break; + } + } + } + + if ( PowerUpActive( POWERUP_HASTE ) ) { + switch ( type ) { + case PMOD_SPEED: + mod *= 1.3f; + break; + + case PMOD_FIRERATE: + mod *= 0.7f; + break; + } + } + + // Arena CTF powerups + if( PowerUpActive( POWERUP_AMMOREGEN ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= 0.7f; + break; + } + } + } + + if( PowerUpActive( POWERUP_DOUBLER ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 2.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 2.0f; + break; + } + } + } + +//RITUAL BEGIN + if( PowerUpActive( POWERUP_TEAM_DAMAGE_MOD ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_FIRERATE: { + mod *= 0.80f; + break; + } + } + } +//RITUAL END + if( PowerUpActive( POWERUP_SCOUT ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= (2.0f / 3.0f); + break; + } + case PMOD_SPEED: { + mod *= 1.5f; + break; + } + } + } + + return mod; +} + +/* +=============== +idPlayer::PowerUpActive +=============== +*/ +bool idPlayer::PowerUpActive( int powerup ) const { + return ( inventory.powerups & ( 1 << powerup ) ) != 0; +} + +/* +=============== +idPlayer::StartPowerUpEffect +=============== +*/ +void idPlayer::StartPowerUpEffect( int powerup ) { + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_marine_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_marine_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_strogg_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_strogg_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_ONEFLAG: { + AddClientModel( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + PlayEffect( "fx_deadzone", animator.GetJointHandle( "origin" ), true ); + break; + } + case POWERUP_QUADDAMAGE: { + powerUpOverlay = quadOverlay; + + StopEffect( "fx_regeneration" ); + PlayEffect( "fx_quaddamage", animator.GetJointHandle( "chest" ), true ); + StartSound( "snd_quaddamage_idle", SND_CHANNEL_POWERUP_IDLE, 0, false, NULL ); + + // Spawn quad effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_quaddamage_crawl" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_QUADDAMAGE; + + break; + } + + case POWERUP_REGENERATION: { + + // when buy mode is enabled, we use the guard effect for team powerup regen ( more readable than everyone going red ) + if ( gameLocal.IsTeamPowerups() ) { + // don't setup the powerup on dead bodies, it will float up where the body is invisible and the orientation will be messed up + if ( teamHealthRegen == NULL ) { + if ( health <= 0 ) { + // we can't start it now, it will be floating where the hidden dead body is + teamHealthRegenPending = true; + } else { + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + powerUpOverlay = regenerationOverlay; + + StopEffect( "fx_quaddamage" ); + PlayEffect( "fx_regeneration", animator.GetJointHandle( "chest" ), true ); + + // Spawn regen effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_regeneration" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_REGENERATION; + } + + break; + } + + case POWERUP_HASTE: { + powerUpOverlay = hasteOverlay; + + hasteEffect = PlayEffect( "fx_haste", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), true ); + break; + } + + case POWERUP_INVISIBILITY: { + powerUpOverlay = invisibilityOverlay; + + powerUpSkin = declManager->FindSkin( spawnArgs.GetString( "skin_invisibility" ), false ); + break; + } + + case POWERUP_GUARD: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_guard", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_SCOUT: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_scout", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_AMMOREGEN: { + if ( gameLocal.IsTeamPowerups() ) { + if ( teamAmmoRegen == NULL ) { + if ( health <= 0 ) { + teamAmmoRegenPending = true; + } else { + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + break; + } + + case POWERUP_TEAM_DAMAGE_MOD: { + assert( gameLocal.IsTeamPowerups() ); + if ( teamDoubler == NULL ) { + if ( health <= 0 ) { + teamDoublerPending = true; + } else { + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } + } + break; + } + + case POWERUP_DOUBLER: { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + break; + } + } +} + +/* +=============== +idPlayer::StopPowerUpEffect +=============== +*/ +void idPlayer::StopPowerUpEffect( int powerup ) { + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if( !( + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) + ) ) { + + powerUpOverlay = NULL; + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + switch( powerup ) { + case POWERUP_QUADDAMAGE: { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_quaddamage" ); + break; + } + case POWERUP_REGENERATION: { + if ( gameLocal.IsTeamPowerups() ) { + teamHealthRegenPending = false; + StopEffect( "fx_guard" ); + } else { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_regeneration" ); + } + break; + } + case POWERUP_HASTE: { + StopEffect( "fx_haste" ); + break; + } + case POWERUP_INVISIBILITY: { + powerUpSkin = NULL; + break; + } + case POWERUP_CTF_STROGGFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + break; + } + case POWERUP_CTF_MARINEFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + break; + } + case POWERUP_CTF_ONEFLAG: { + RemoveClientModel ( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + StopEffect( "fx_deadzone" ); + break; + } + case POWERUP_SCOUT: { + StopEffect( "fx_scout" ); + break; + } + case POWERUP_GUARD: { + StopEffect( "fx_guard" ); + break; + } + case POWERUP_TEAM_DAMAGE_MOD: + teamDoublerPending = false; + // fallthrough + case POWERUP_DOUBLER: { + StopEffect( "fx_doubler" ); + break; + } + case POWERUP_AMMOREGEN: { + teamAmmoRegenPending = false; + StopEffect( "fx_ammoregen" ); + break; + } + } +} + +/* +=============== +idPlayer::GivePowerUp +passiveEffectsOnly - GivePowerup() is used to restore effects on stale players coming +back into snapshot. We don't want to announce powerups in this case +(just re-start effects) +=============== +*/ +bool idPlayer::GivePowerUp( int powerup, int time, bool team ) { + if ( powerup < 0 || powerup >= POWERUP_MAX ) { + gameLocal.Warning( "Player given power up %i\n which is out of range", powerup ); + return false; + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( powerup ); + msg.WriteBits( 1, 1 ); + // team flag only needed for POWERUP_AMMOREGEN + msg.WriteBits( team, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.GivePowerUp( this, powerup, time ); + + // only start client effects in the same instance + // play all stuff in instance 0 for server netdemo - atm other instances are not recorded + bool playClientEffects = ( ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && instance == 0 ) || + ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) ); + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_ONEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_QUADDAMAGE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_QUAD_DAMAGE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + + case POWERUP_REGENERATION: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + + // Have to test for this because buying the team regeneration powerup will cause + // this to get hit multiple times as the server distributes the powerups to the clients. + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_REGENERATION, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_HASTE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_HASTE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_INVISIBILITY: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_INVISIBILITY, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_GUARD: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + inventory.maxHealth = 200; + inventory.maxarmor = 200; + + break; + } + case POWERUP_SCOUT: { + inventory.armor = 0; + + break; + } + case POWERUP_AMMOREGEN: { + if ( team && gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_AMMOREGEN, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_TEAM_DAMAGE_MOD: { + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_DOUBLER, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } +//RITUAL BEGIN + case POWERUP_DEADZONE: { + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_122000" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + mphud->HandleNamedEvent( "main_notice" ); + } + } + break; + } +//RITUAL END + } + + // only start effects if in our instances and snapshot + if ( playClientEffects && !fl.networkStale ) { + StartPowerUpEffect( powerup ); + } + + return true; +} + +/* +============== +idPlayer::ClearPowerup +============== +*/ +void idPlayer::ClearPowerup( int i ) { + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( i ); + msg.WriteBits( 0, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.powerups &= ~( 1 << i ); + inventory.powerupEndTime[ i ] = 0; + + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if( !( + (inventory.powerups & ( 1 << POWERUP_TEAM_DAMAGE_MOD ) ) || + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) || + (inventory.powerups & ( 1 << POWERUP_DEADZONE ) ) + ) ) { + + powerUpOverlay = NULL; + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + StopPowerUpEffect( i ); +} + +/* +============== +idPlayer::GetArenaPowerupString +============== +*/ +const char* idPlayer::GetArenaPowerupString ( void ) { + if ( PowerUpActive( POWERUP_SCOUT ) ) { + return "^isct"; + } else if ( PowerUpActive( POWERUP_GUARD ) ) { + return "^igrd"; + } else if ( PowerUpActive( POWERUP_DOUBLER ) ) { + return "^idbl"; + } else if ( PowerUpActive( POWERUP_AMMOREGEN ) ) { + return "^irgn"; + } else { + return "^ixxx"; + } +} + +/* +============== +idPlayer::UpdatePowerUps +============== +*/ +void idPlayer::UpdatePowerUps( void ) { + int i; + int index; + int wearoff; + + idUserInterface *hud = idPlayer::hud; + if ( !gameLocal.GetLocalPlayer() ) { + // server netdemo + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() == entityNumber ) { + hud = gameLocal.GetDemoHud(); + } + } else { + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + assert( p->hud && p->mphud ); + hud = p->hud; + } + } + + wearoff = -1; + if ( hud ) { + hud->HandleNamedEvent( "clearPowerups" ); + } + + for ( i = 0, index = 0; i < POWERUP_MAX; i++ ) { + // Do we have this powerup? + if ( !(inventory.powerups & ( 1 << i ) ) ) { + continue; + } + + if ( inventory.powerupEndTime[i] > gameLocal.time || inventory.powerupEndTime[i] == -1 ) { + // If there is still time remaining on the powerup then update the hud + if ( hud ) { + // Play the wearoff sound for the powerup that is closest to wearing off + if ( ( wearoff == -1 || inventory.powerupEndTime[i] < inventory.powerupEndTime[wearoff] ) && inventory.powerupEndTime[i] != -1 ) { + wearoff = i; + } + + // for flags, set the powerup_flag_* variables, which give us a special pulsing flag display + if( i == POWERUP_CTF_MARINEFLAG || i == POWERUP_CTF_STROGGFLAG || i == POWERUP_CTF_ONEFLAG ) { + hud->SetStateInt( "powerup_flag_visible", 1 ); + } else { + hud->SetStateString ( va("powerup%d_icon", index ), GetPowerupDef(i)->dict.GetString ( "inv_icon" ) ); + hud->SetStateString ( va("powerup%d_time", index ), inventory.powerupEndTime[i] == -1 ? "" : va( "%d" , (int)MS2SEC(inventory.powerupEndTime[i] - gameLocal.time) + 1 ) ); + hud->SetStateInt ( va( "powerup%d_visible", index ), 1 ); + index++; + } + } + + continue; + } else if ( inventory.powerupEndTime[ i ] != -1 && gameLocal.isServer ) { + // This particular powerup needs to respawn in a special way. + if ( i == POWERUP_DEADZONE ) { + gameLocal.mpGame.GetGameState()->SpawnDeadZonePowerup(); + } + // Powerup time has run out so take it away from the player + ClearPowerup( i ); + } + } + + // PLay wear off sound? + if ( gameLocal.isNewFrame && wearoff != -1 ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) < POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) / POWERUP_BLINK_TIME != ( inventory.powerupEndTime[wearoff] - gameLocal.previousTime ) / POWERUP_BLINK_TIME ) { + StartSound ( "snd_powerup_wearoff", SND_CHANNEL_POWERUP, 0, false, NULL ); + } + } + } + + // Reneration regnerates faster when less than maxHealth and can regenerate up to maxHealth * 2 + if ( gameLocal.time > nextHealthPulse ) { +// RITUAL BEGIN +// squirrel: health regen only applies if you have positive health + if( health > 0 ) { + if ( PowerUpActive ( POWERUP_REGENERATION ) || PowerUpActive ( POWERUP_GUARD ) ) { + int healthBoundary = inventory.maxHealth; // health will regen faster under this value, slower above + int healthTic = 15; + + if( PowerUpActive ( POWERUP_GUARD ) ) { + // guard max health == 200, so set the boundary back to 100 + healthBoundary = inventory.maxHealth / 2; + if( PowerUpActive (POWERUP_REGENERATION) ) { + healthTic = 30; + } + } + + if ( health < healthBoundary ) { + // only actually give health on the server + if( gameLocal.isServer ) { + health += healthTic; + if ( health > (healthBoundary * 1.1f) ) { + health = healthBoundary * 1.1f; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( health < (healthBoundary * 2) ) { + if( gameLocal.isServer ) { + health += healthTic / 3; + if ( health > (healthBoundary * 2) ) { + health = healthBoundary * 2; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + // Health above max technically isnt a powerup but functions as one so handle it here + } else if ( health > inventory.maxHealth && gameLocal.isServer ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + health--; + } + } +// RITUAL END + } + + // Regenerate ammo + if( gameLocal.isServer && PowerUpActive( POWERUP_AMMOREGEN ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( inventory.weapons & ( 1 << i ) ) { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( i ); + int max = inventory.StartingAmmoForWeaponIndex( i ); + + // only regen ammo if lower than starting + if( gameLocal.time > nextAmmoRegenPulse[ ammoIndex ] && inventory.ammo[ ammoIndex ] < max ) { + int step = inventory.AmmoRegenStepForWeaponIndex( i ); + int time = inventory.AmmoRegenTimeForWeaponIndex( i ); + + if( inventory.ammo[ ammoIndex ] < max ) { + inventory.ammo[ ammoIndex ] += step; + } + if( inventory.ammo[ ammoIndex ] >= max ) { + inventory.ammo[ ammoIndex ] = max; + } + + nextAmmoRegenPulse[ ammoIndex ] = gameLocal.time + time; + } + } + } + } + + // Tick armor down if greater than max armor + if ( !gameLocal.isClient && gameLocal.time > nextArmorPulse ) { + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse += ARMOR_PULSE; + inventory.armor--; + } + } + + // Assign the powerup skin as long as we are alive + if ( health > 0 ) { + if ( powerUpSkin ) { + renderEntity.customSkin = powerUpSkin; + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + + if( weaponWorldModel ) { + weaponWorldModel->SetSkin( powerUpSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( powerUpSkin ); + } + } else { + renderEntity.customSkin = skin; + + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetOverlayShader( powerUpOverlay ); + } + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + if( weaponWorldModel ) { + weaponWorldModel->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + renderEntity.overlayShader = powerUpOverlay; + } else { + renderEntity.overlayShader = NULL; + powerUpOverlay = NULL; + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = NULL; + } + + if ( renderEntity.customSkin != gibSkin ) { + if ( influenceSkin ) { + renderEntity.customSkin = influenceSkin; + } else { + renderEntity.customSkin = skin; + } + } + } + + // Spawn quad effect + if( PowerUpActive( powerupEffectType ) && powerupEffect && gameLocal.time >= powerupEffectTime ) { + rvClientCrawlEffect* effect = new rvClientCrawlEffect( powerupEffect, this, 100, &powerupEffectJoints ); + effect->Play ( gameLocal.time, false ); + effect->GetRenderEffect()->suppressSurfaceInViewID = entityNumber+1; + powerupEffectTime = gameLocal.time + 400; + } + + // Attenuate haste effect + if ( hasteEffect ) { + hasteEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if ( flagEffect ) { + flagEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if( arenaEffect ) { + arenaEffect->SetOrigin( vec3_zero ); + } +} + +/* +=============== +idPlayer::ClearPowerUps +=============== +*/ +void idPlayer::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( PowerUpActive( i ) ) { + ClearPowerup( i ); + } + } + + inventory.ClearPowerUps(); +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int mods ) { + inventory.weaponMods[currentWeapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int weapon, int mods ) { + inventory.weaponMods[weapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +============== +idPlayer::GiveWeaponMod +============== +*/ +void idPlayer::GiveWeaponMod ( const char* weaponmod ) { + const idDict* modDict; + const idDict* weaponDict; + const char* weaponClass; + int m; + int weaponIndex; + + // Grab the weapon mod dictionary + modDict = gameLocal.FindEntityDefDict ( weaponmod, false ); + if ( !modDict ) { + gameLocal.Warning ( "Invalid weapon modification def specified '%s'", weaponmod ); + return; + } + + // Get the weapon it modifies + weaponClass = modDict->GetString ( "weapon" ); + weaponDict = gameLocal.FindEntityDefDict ( weaponClass, false ); + if ( !weaponDict ) { + gameLocal.Warning ( "Invalid weapon classname '%s' specified on weapon modification '%s'", weaponClass, weaponmod ); + return; + } + + weaponIndex = SlotForWeapon ( weaponClass ); + + // Find the index of the weapon mod + for ( m = 0; m < MAX_WEAPONMODS; m ++ ) { + const char* mod; + mod = weaponDict->GetString ( va("def_mod%d",m+1) ); + if ( !mod || !*mod ) { + break; + } + + if ( !idStr::Icmp ( weaponmod, mod ) ) { + if ( !(inventory.weaponMods[weaponIndex] & (1<GetLocalizedString( item->GetString( "inv_name" ) ); + hud->SetStateString ( "itemtext", itemName ); + hud->SetStateString ( "itemicon", item->GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } + + return true; +} + +/* +============== +idPlayer::UpdateObjectiveInfo +============== + */ +void idPlayer::UpdateObjectiveInfo( void ) { + if ( objectiveSystem == NULL ) { + return; + } + objectiveSystem->SetStateString( "objective1", "" ); + objectiveSystem->SetStateString( "objective2", "" ); + objectiveSystem->SetStateString( "objective3", "" ); + +// RAVEN BEGIN +// mekberg: swap objective positions to allow for stack-like appearance. + int objectiveCount = inventory.objectiveNames.Num(); + for ( int i = 0; i < inventory.objectiveNames.Num(); i++, objectiveCount-- ) { + objectiveSystem->SetStateString( va( "objective%i", objectiveCount ), "1" ); + objectiveSystem->SetStateString( va( "objectivetitle%i", objectiveCount ), inventory.objectiveNames[i].title.c_str() ); + objectiveSystem->SetStateString( va( "objectivetext%i", objectiveCount), inventory.objectiveNames[i].text.c_str() ); + objectiveSystem->SetStateInt( va( "objectiveLength%i", objectiveCount), inventory.objectiveNames[i].text.Length() ); + objectiveSystem->SetStateString( va( "objectiveshot%i", objectiveCount), inventory.objectiveNames[i].screenshot.c_str() ); + } + objectiveSystem->SetStateBool( "noObjective", !objectiveCount ); +// RAVEN END + + objectiveSystem->StateChanged( gameLocal.time ); +} + +/* +=============== +idPlayer::GiveObjective +=============== +*/ +void idPlayer::GiveObjective( const char *title, const char *text, const char *screenshot ) { + idObjectiveInfo info; +// RAVEN BEGIN + info.title = common->GetLocalizedString( title ); + info.text = common->GetLocalizedString( text ); +// RAVEN END + info.screenshot = screenshot; + inventory.objectiveNames.Append( info ); + if ( showNewObjectives ) { + ShowObjective( "newObjective" ); + } + if ( objectiveSystem ) { + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + } +} + +/* +=============== +idPlayer::CompleteObjective +=============== +*/ +void idPlayer::CompleteObjective( const char *title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); +// RAVEN END + int c = inventory.objectiveNames.Num(); + for ( int i = 0; i < c; i++ ) { + if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) { + inventory.objectiveNames.RemoveIndex( i ); + break; + } + } + ShowObjective( "newObjectiveComplete" ); + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "newObjectiveComplete" ); + } + + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } +} + +/* +=============== +idPlayer::FailObjective +=============== +*/ +void idPlayer::FailObjective ( const char* title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); + +// mekberg: prevent save games if objective failed. + gameLocal.sessionCommand = "objectiveFailed "; + gameLocal.sessionCommand += title; +// RAVEN END + HideObjective ( ); + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "objectiveFailed" ); + } + if( IsInVehicle() ) { + vehicleController.GetVehicle()->EjectAllDrivers(); + } + fl.takedamage = true; + pfl.objectiveFailed = true; +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME_XEN_SP; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME_XEN_SP; +#else + playerView.Fade( colorBlack, 12000 ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; +#endif +} + +/* +=============== +idPlayer::FindInventoryItem +=============== +*/ +idDict *idPlayer::FindInventoryItem( const char *name ) { + for ( int i = 0; i < inventory.items.Num(); i++ ) { + const char *iname = inventory.items[i]->GetString( "inv_name" ); + if ( iname && *iname ) { + if ( idStr::Icmp( name, iname ) == 0 ) { + return inventory.items[i]; + } + } + } + return NULL; +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( const char *name ) { + idDict *item = FindInventoryItem(name); + if ( item ) { + RemoveInventoryItem( item ); + } +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( idDict *item ) { + inventory.items.Remove( item ); + delete item; +} + +/* +=============== +idPlayer::GiveItem +=============== +*/ +void idPlayer::GiveItem( const char *itemname ) { + idDict args; + + args.Set( "classname", itemname ); + args.Set( "owner", name.c_str() ); + args.Set( "givenToPlayer", va( "%d", entityNumber ) ); + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // check if this is a weapon + if( !idStr::Icmpn( itemname, "weapon_", 7 ) ) { + int weaponIndex = SlotForWeapon( itemname ); + if( weaponIndex >= 0 && weaponIndex < MAX_WEAPONS ) + { + int weaponIndexBit = ( 1 << weaponIndex ); + inventory.weapons |= weaponIndexBit; + inventory.carryOverWeapons |= weaponIndexBit; + carryOverCurrentWeapon = weaponIndex; + } + } + + // if the player is dead, credit him with this armor or ammo purchase + if ( health <= 0 ) { + if( !idStr::Icmp( itemname, "item_armor_small" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_LIGHT; + } else if( !idStr::Icmp( itemname, "item_armor_large" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_HEAVY; + } else if( !idStr::Icmp( itemname, "ammorefill" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_AMMO; + } + } else { + if ( !idStr::Icmp( itemname, "ammorefill" ) ) { + int i; + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + int a = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( rvWeapon::GetAmmoNameForIndex( i ), 0 ); + inventory.ammo[i] += a; + if ( inventory.ammo[i] > inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) { + inventory.ammo[i] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ); + } + } + } + } + } + + // spawn the item if the player is alive + if ( health > 0 && idStr::Icmp( itemname, "ammorefill" ) ) { + gameLocal.SpawnEntityDef( args ); + } + +} + +/* +================== +idPlayer::SlotForWeapon +================== +*/ +int idPlayer::SlotForWeapon( const char *weaponName ) { + int i; + + for( i = 0; i < MAX_WEAPONS; i++ ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + return i; + } + } + + // not found + return -1; +} + +/* +=============== +idPlayer::Reload +=============== +*/ +void idPlayer::Reload( void ) { + if ( gameLocal.isClient || spectating || gameLocal.inCinematic || influenceActive || !weapon ) { + return; + } + + weapon->Reload(); +} + +#ifdef _XENON +/* +=============== +idPlayer::ScheduleWeaponSwitch +=============== +*/ +void idPlayer::ScheduleWeaponSwitch(int weapon) +{ + CancelEvents(&EV_Player_SelectWeapon); + hud->SetStateInt("player_selectedWeapon", weapon-1); + hud->HandleNamedEvent( "weaponSelect" ); + + // nrausch: support for turning the weapon change ui on and off + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( false ); + } + + if ( weapon > 0 ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", weapon-1 ) ); + PostEventSec(&EV_Player_SelectWeapon, 0.25f, weap); + } +} +#endif + +/* +=============== +idPlayer::ShowCrosshair +=============== +*/ +void idPlayer::ShowCrosshair( void ) { + if ( !weaponEnabled ) { + return; + } + + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::HideCrosshair +=============== +*/ +void idPlayer::HideCrosshair( void ) { + if ( cursor ) { + cursor->HandleNamedEvent( "crossHide" ); + } +} + +/* +=============== +idPlayer::LastWeapon +=============== +*/ +void idPlayer::LastWeapon( void ) { + // Dont bother if previousWeapon is invalid or the player is spectating + if ( spectating || previousWeapon < 0 ) { + return; + } + + // Do we have the weapon still? + if ( !(inventory.weapons & ( 1 << previousWeapon ) ) ) { + return; + } + + idealWeapon = previousWeapon; +} + +/* +=============== +idPlayer::NextBestWeapon +=============== +*/ +void idPlayer::NextBestWeapon( void ) { + const char *weap; + int w = MAX_WEAPONS; + + if ( gameLocal.isClient || !weaponEnabled ) { + return; + } + + while ( w > 0 ) { + w--; + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap ) ) ) { + continue; + } + if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) { + continue; + } + break; + } + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::NextWeapon +=============== +*/ +void idPlayer::NextWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + +#ifdef _XENON + + w = idealWeapon; + while( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + w = 0; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + if ( entityNumber == gameLocal.localClientNum ) { + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( true ); + } + } + + if ( gameLocal.isClient ) { + return; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } + +#else + + if ( gameLocal.isClient ) { + return; + } + + w = idealWeapon; + while( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + w = 0; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } + +#endif +// RAVEN END +} + +/* +=============== +idPlayer::PrevWeapon +=============== +*/ +void idPlayer::PrevWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + +#ifdef _XENON + + w = idealWeapon; + while( 1 ) { + w--; + if ( w < 0 ) { + w = MAX_WEAPONS - 1; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + if ( entityNumber == gameLocal.localClientNum ) { + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( true ); + } + } + + if ( gameLocal.isClient ) { + return; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } + +#else + + if ( gameLocal.isClient ) { + return; + } + + w = idealWeapon; + while( 1 ) { + w--; + if ( w < 0 ) { + w = MAX_WEAPONS - 1; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) { + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +#endif +// RAVEN END +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( const char *weapon_name ) { + Event_SelectWeapon( weapon_name ); +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( int num, bool force ) { + const char *weap; + + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + weap = spawnArgs.GetString( va( "def_weapon%d", num ) ); + if ( !weap[ 0 ] ) { + gameLocal.Warning( "Invalid weapon def_weapon%d\n", num ); + return; + } + + // cycle in-between weapons + // if a weapon_def has a "def_weapon_swap" keyvalue pointing to another + // weapon, hitting that impulse twice will cycle to the target swap. + if( num == currentWeapon ) { + const idDict* weapDict = gameLocal.FindEntityDefDict( weap, false ); + + if( weapDict == NULL ) { + gameLocal.Warning( "Invalid weapon entity %s\n", weap ); + return; + } + +// RAVEN BEGIN +// nrausch: we have no need for weapon swapping on the xenon, and it gets in the way of the quick weapon select ui +#ifndef _XENON + const char* destWeapon = weapDict->GetString( "def_weapon_swap", NULL ); + + if( destWeapon != NULL ) { + int swapNum = SlotForWeapon( destWeapon ); + if( swapNum == -1 ) { + gameLocal.Warning( "Swap weapon for %s (%s) is invalid", weap, destWeapon ); + } else { + num = swapNum; + } + } +#endif +// RAVEN END + } + + if ( force || ( inventory.weapons & ( 1 << num ) ) ) { + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) { + return; + } + if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) { + weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) { + return; + } + idealWeapon = previousWeapon; +/* NO PDA yet + } else if ( ( weapon_pda >= 0 ) && ( num == weapon_pda ) && ( inventory.pdas.Num() == 0 ) ) { + ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true ); + return; +*/ + } else { + idealWeapon = num; + } + UpdateHudWeapon(); + } +} + +/* +================= +idPlayer::DropItem +================= +*/ +idEntity* idPlayer::DropItem ( const char* itemClass, const idDict& customArgs, const idVec3& velocity ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.SetFloat ( "angle", 360.0f * gameLocal.random.RandomFloat ( ) ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + // If a velocity was given then just use that, otherwise randomly throw it around + if ( velocity != vec3_origin ) { + ent->GetPhysics()->SetLinearVelocity ( velocity ); + } else { + idVec3 vel; + float ang; + ang = idMath::TWO_PI * gameLocal.random.RandomFloat(); + vel[0] = PLAYER_ITEM_DROP_SPEED * idMath::Cos ( ang ); + vel[1] = PLAYER_ITEM_DROP_SPEED * idMath::Sin ( ang ); + vel[2] = PLAYER_ITEM_DROP_SPEED * 2; + ent->GetPhysics()->SetLinearVelocity ( vel ); + } + return ent; +} + +/* +================= +idPlayer::DropPowerups +================= +*/ +void idPlayer::DropPowerups( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + // These powerups aren't dropped + if ( i >= POWERUP_TEAM_AMMO_REGEN && i <= POWERUP_TEAM_DAMAGE_MOD ) + continue; + + // Don't drop this either with buying enabled. + if ( i == POWERUP_REGENERATION && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + continue; + + /// Don't drop arena rune powerups in non-Arena modes + if( gameLocal.gameType != GAME_ARENA_CTF ) + { + if( i == POWERUP_AMMOREGEN || + i == POWERUP_GUARD || + i == POWERUP_DOUBLER || + i == POWERUP_SCOUT ) + { + continue; + } + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + if( def->dict.GetBool( "nodrop" ) ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + args.SetInt( "instance", GetInstance() ); + item = DropItem ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +idPlayer::ResetFlag +================= +*/ +idEntity* idPlayer::ResetFlag ( const char* itemClass, const idDict& customArgs ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.Set( "reset", "1" ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + return ent; +} + +/* +================= +idPlayer::RespawnFlags +================= +*/ +void idPlayer::RespawnFlags ( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = POWERUP_CTF_MARINEFLAG; i < POWERUP_CTF_ONEFLAG; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + item = ResetFlag ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +DropWeapon +================= +*/ +void idPlayer::DropWeapon( void ) { + idEntity* item; + idDict args; + const char* itemClass; + + assert( !gameLocal.isClient ); + + if( !gameLocal.isMultiplayer ) { + return; + } + +// RITUAL BEGIN +// squirrel: don't drop weapons in Buying modes unless "always drop" is on + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && !gameLocal.serverInfo.GetBool( "si_dropWeaponsInBuyingModes" ) ) { + return; + } +// RITUAL END + + if ( spectating || weaponGone || !weapon ) { + return; + } + + // Make sure the weapon is droppable + itemClass = weapon->spawnArgs.GetString ( "def_dropItem" ); + if ( !itemClass || !*itemClass ) { + return; + } + + // If still alive then the weapon is being thrown so start it a bit in front of the player + + // copy over the instance + args.SetInt( "instance", GetInstance() ); + + if ( health > 0 ) { + idVec3 forward; + idVec3 up; + viewAngles.ToVectors( &forward, NULL, &up ); + args.SetBool( "triggerFirst", true ); + item = DropItem ( itemClass, args, 250.0f*forward + 150.0f*up ); + } else { + item = DropItem ( itemClass, args ); + } + + // Drop the weapon + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop weapon '%s'", entityNumber, weapon->spawnArgs.GetString ( "def_dropItem" ) ); + return; + } + + // Since this weapon was dropped, replace any starting ammo values with real ammo values + const idKeyValue* keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_" ); + idDict newArgs; + while( keyval ) { + newArgs.Set( va( "inv_ammo_%s", keyval->GetKey().Right( keyval->GetKey().Length() - 15 ).c_str() ), keyval->GetValue().c_str() ); + item->spawnArgs.Set( keyval->GetKey(), "" ); + keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_", keyval ); + } + + item->spawnArgs.SetDefaults( &newArgs ); + + // Set the appropriate mods on the dropped item + int i; + int mods; + idStr out; + mods = weapon->GetMods ( ); + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + if ( mods & (1<spawnArgs.GetString ( va("def_mod%d", i+1) ); + } + } + if ( out.Length() ) { + item->spawnArgs.Set ( "inv_weaponmod", out ); + } + + // Make sure the weapon removes itself over time. + item->PostEventMS ( &EV_Remove, WEAPON_DROP_TIME ); + + // Delay aquire since the weapon is being thrown + if ( health > 0 ) { + item->PostEventMS ( &EV_Activate, 500, item ); + inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 ); + NextWeapon(); + } +} + +/* +=============== +idPlayer::ActiveGui +=============== +*/ +idUserInterface *idPlayer::ActiveGui( void ) { +#ifdef _XENON + if ( objectiveSystemOpen ) { + return 0; + } +#endif + return focusUI; +} + +/* +=============== +idPlayer::Weapon_Combat +=============== +*/ +void idPlayer::Weapon_Combat( void ) { + + if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { + return; + } + + if ( weapon ) { + weapon->RaiseWeapon(); + + if ( weapon->IsReloading() ) { + if ( !pfl.reload ) { + pfl.reload = true; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Reload", 4 ); + UpdateState(); + } + } else { + pfl.reload = false; + } + } + + if ( idealWeapon != currentWeapon ) { + if ( !weapon || weaponCatchup ) { + assert( gameLocal.isClient ); + weaponGone = false; + SetWeapon( idealWeapon ); + + weapon->NetCatchup(); + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + } else { + if ( weapon->IsReady() || weapon->IsReloading() ) { + weapon->PutAway(); + } + + if ( weapon->IsHolstered() && weaponViewModel ) { + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + SetWeapon( idealWeapon ); + + weapon->Raise(); + } + } + } else { + weaponGone = false; + if ( weapon->IsHolstered() ) { + if ( !weapon->AmmoAvailable() ) { + // weapons can switch automatically if they have no more ammo + NextBestWeapon(); + } else { + weapon->Raise(); + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 3 ); + } + } + } + + weaponCatchup = false; + + // check for attack + pfl.weaponFired = false; + if ( !influenceActive ) { + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { + FireWeapon(); + } else if ( oldButtons & BUTTON_ATTACK ) { + pfl.attackHeld = false; + weapon->EndAttack(); + } + } + + if ( gameLocal.isMultiplayer && spectating ) { + UpdateHudWeapon(); + } + + // update our ammo clip in our inventory + if ( gameLocal.GetLocalPlayer() == this && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.clip[ currentWeapon ] = weapon->AmmoInClip(); + if ( hud && ( currentWeapon == idealWeapon ) ) { + UpdateHudAmmo( hud ); + } + } +} + +/* +=============== +idPlayer::Weapon_Vehicle +=============== +*/ +void idPlayer::Weapon_Vehicle( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + ProcessEvent ( &AI_EnterVehicle, focusEnt.GetEntity() ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_Usable +=============== +*/ +void idPlayer::Weapon_Usable( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + focusEnt->ProcessEvent ( &EV_Activate, this ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_NPC +=============== +*/ +void idPlayer::Weapon_NPC( void ) { + + flagCanFire = false; + + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + + if ( currentWeapon ) { + StopFiring(); + } + + if ( !focusEnt || focusEnt->health <= 0 ) { + ClearFocus ( ); + return; + } + + if ( talkCursor && ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + buttonMask |= BUTTON_ATTACK; + if ( !talkingNPC ) { + idAI *focusAI = static_cast(focusEnt.GetEntity()); + if ( focusAI ) { + focusAI->TalkTo( this ); + talkingNPC = focusAI; + } + } + } else if ( currentWeapon == SlotForWeapon ( "weapon_blaster" ) ) { + Weapon_Combat(); + } +} + + +/* +=============== +idPlayer::LowerWeapon +=============== +*/ +void idPlayer::LowerWeapon( void ) { + if ( weapon && !weapon->IsHidden() ) { + weapon->LowerWeapon(); + } +} + +/* +=============== +idPlayer::RaiseWeapon +=============== +*/ +void idPlayer::RaiseWeapon( void ) { + if ( weapon && weapon->IsHidden() ) { + weapon->RaiseWeapon(); + } +} + +/* +=============== +idPlayer::WeaponLoweringCallback +=============== +*/ +void idPlayer::WeaponLoweringCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LowerWeapon", 3 ); + UpdateState(); +} + +/* +=============== +idPlayer::WeaponRisingCallback +=============== +*/ +void idPlayer::WeaponRisingCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 2 ); + UpdateState(); +} + +/* +=============== +idPlayer::Weapon_GUI +=============== +*/ +void idPlayer::Weapon_GUI( void ) { + + flagCanFire = false; + + if ( !objectiveSystemOpen ) { + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + StopFiring(); + weapon->LowerWeapon(); + } + + // disable click prediction for the GUIs. handy to check the state sync does the right thing + if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) { + return; + } + + if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { + sysEvent_t ev; + const char *command = NULL; + bool updateVisuals = false; + + idUserInterface *ui = ActiveGui(); + if ( ui ) { + ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 ); + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + } + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +} + +/* +=============== +idPlayer::UpdateWeapon +=============== +*/ +void idPlayer::UpdateWeapon( void ) { + if ( health <= 0 ) { + return; + } + + assert( !spectating ); + + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( gameLocal.isClient && (!weaponViewModel || !weaponWorldModel) ) { + return; + } + + // always make sure the weapon is correctly setup before accessing it + if ( !weapon ) { + if ( idealWeapon != -1 ) { + SetWeapon( idealWeapon ); + weaponCatchup = false; + assert( weapon ); + } else { + return; + } + } + + + if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) { + HideTip(); + } + + // Make sure the weapon is in a settled state before preventing thinking due + // to drag entity. This way things like hitting reload, zoom, etc, wont crash + if ( g_dragEntity.GetInteger() ) { + StopFiring(); + flagCanFire = false; + if ( weapon ) { + weapon->LowerWeapon(); + } + dragEntity.Update( this ); + return; + } else if ( focusType == FOCUS_CHARACTER) { + flagCanFire = false; + Weapon_NPC(); + } else if ( focusType == FOCUS_VEHICLE ) { + flagCanFire = false; + Weapon_Vehicle(); + } else if ( focusType == FOCUS_USABLE || focusType == FOCUS_USABLE_VEHICLE ) { + flagCanFire = false; + Weapon_Usable(); + } else if ( ActiveGui() ) { + flagCanFire = false; + Weapon_GUI(); + } else if ( !hiddenWeapon ) { /* no pda yet || ( ( weapon_pda >= 0 ) && ( idealWeapon == weapon_pda ) ) ) { */ + flagCanFire = true; + Weapon_Combat(); + } + + // Range finder for debugging + if ( g_showRange.GetBool ( ) ) { + idVec3 start; + idVec3 end; + trace_t tr; + + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 50000.0f; + gameLocal.TracePoint( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); + + idVec3 forward; + idVec3 right; + idVec3 up; + viewAngles.ToVectors ( &forward, &right, &up ); + gameRenderWorld->DrawText( va( "%d qu", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d m", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f - up * 6.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d 2d", ( int )DistanceTo2d( tr.endpos ) ), start + forward * 100.0f + right * 25.0f - up * 12.0f, .2f, colorCyan, viewAxis ); + } + + if ( hiddenWeapon ) { + weapon->LowerWeapon(); + } + + // update weapon state, particles, dlights, etc + weaponViewModel->PresentWeapon( showWeaponViewModel ); +} + +/* +=============== +idPlayer::SpectateFreeFly +=============== +*/ +void idPlayer::SpectateFreeFly( bool force ) { + idPlayer *player; + idVec3 newOrig; + idVec3 spawn_origin; + idAngles spawn_angles; + + player = gameLocal.GetClientByNum( spectator ); + if ( force || gameLocal.time > lastSpectateChange ) { + spectator = entityNumber; + if ( player && player != this && !player->spectating && !player->IsInTeleport() ) { + newOrig = player->GetPhysics()->GetOrigin(); + if ( player->physicsObj.IsCrouching() ) { + newOrig[ 2 ] += pm_crouchviewheight.GetFloat(); + } else { + newOrig[ 2 ] += pm_normalviewheight.GetFloat(); + } + newOrig[ 2 ] += SPECTATE_RAISE; + idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + idVec3 start = player->GetPhysics()->GetOrigin(); + start[2] += pm_spectatebbox.GetFloat() * 0.5f; + trace_t t; + // assuming spectate bbox is inside stand or crouch box +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( player, t, start, newOrig, b, MASK_PLAYERSOLID, player ); +// RAVEN END + newOrig.Lerp( start, newOrig, t.fraction ); + SetOrigin( newOrig ); + idAngles angle = player->viewAngles; + angle[ 2 ] = 0; + SetViewAngles( angle ); + } else { + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + return; + } + spawn_origin[ 2 ] += pm_normalviewheight.GetFloat(); + spawn_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spawn_origin ); + SetViewAngles( spawn_angles ); + } + lastSpectateChange = gameLocal.time + 500; + } +} + +/* +=============== +idPlayer::SpectateCycle +=============== +*/ +void idPlayer::SpectateCycle( void ) { + idPlayer *player; + + if ( gameLocal.time > lastSpectateChange ) { + int latchedSpectator = spectator; + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + assert( player ); // never call here when the current spectator is wrong + // ignore other spectators + while ( latchedSpectator != spectator && player->spectating ) { + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + } + lastSpectateChange = gameLocal.time + 500; + + if ( player ) { + UpdateHudWeapon( player->currentWeapon ); + } + } +} + +/* +=============== +idPlayer::UpdateSpectating +=============== +*/ +void idPlayer::UpdateSpectating( void ) { + assert( spectating ); + assert( !gameLocal.isClient ); + assert( IsHidden() ); + idPlayer *player; + if ( !gameLocal.isMultiplayer ) { + return; + } + player = gameLocal.GetClientByNum( spectator ); + if ( !player || ( player->spectating && player != this ) ) { + SpectateFreeFly( true ); + } else if ( usercmd.upmove > 0 && player && player != this ) { + // following someone and hit jump? release. + SpectateFreeFly( false ); + } else if ( usercmd.buttons & BUTTON_ATTACK && gameLocal.gameType != GAME_TOURNEY ) { + // tourney mode uses seperate cycling + SpectateCycle(); + } +} + +/* +=============== +idPlayer::HandleSingleGuiCommand +=============== +*/ +bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "addhealth" ) == 0 ) { + if ( entityGui && health < 100 ) { + int _health = entityGui->spawnArgs.GetInt( "gui_parm1" ); + int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health; + _health -= amt; + entityGui->spawnArgs.SetInt( "gui_parm1", _health ); + if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) { + entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health ); + } + health += amt; + if ( health > 100 ) { + health = 100; + } + } + return true; + } + + if ( token.Icmp( "ready" ) == 0 ) { + PerformImpulse( IMPULSE_17 ); + return true; + } + +// RAVEN BEGIN +// twhitaker: no more database system +/* if ( token.Icmp( "updateDB" ) == 0 ) { + UpdateDatabaseInfo(); + return true; + } + + if ( token.Icmp ( "filterDB" ) == 0 ) { + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( objectiveSystem ) { + objectiveSystem->SetStateString ( "dbFilter", token ); + UpdateDatabaseInfo ( ); + } + } +*/ +// RAVEN END + + if ( token.Icmp( "heal" ) == 0 && + entityGui->IsType( rvHealingStation::GetClassType() ) && + src->ReadToken( &token ) ) + { + rvHealingStation * station = static_cast< rvHealingStation * >( entityGui ); + + if ( token.Icmp( "begin" ) == 0 ) { + station->BeginHealing( this ); + } else if ( token.Icmp( "end" ) == 0 ) { + station->EndHealing( ); + } else { + return false; + } + return true; + } + + src->UnreadToken( &token ); + return false; +} + +/* +============== +idPlayer::Collide +============== +*/ +bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity *other; + other = gameLocal.entities[ collision.c.entityNum ]; + + // allow client-side prediction of item collisions for simple client effects + if ( gameLocal.isClient && !other->IsType( idItem::GetClassType() ) ) { + return false; + } + + + if ( other ) { + other->Signal( SIG_TOUCH ); + if ( !spectating ) { + if ( other->RespondsTo( EV_Touch ) ) { + other->ProcessEvent( &EV_Touch, this, &collision ); + } + } else { + if ( other->RespondsTo( EV_SpectatorTouch ) ) { + other->ProcessEvent( &EV_SpectatorTouch, this, &collision ); + } + } + } + return false; +} + +/* +================ +idPlayer::UpdateLocation + +Searches nearby locations +================ +*/ + void idPlayer::UpdateLocation( void ) { + if ( hud ) { + idLocationEntity *locationEntity = gameLocal.LocationForPoint( GetEyePosition() ); + if ( locationEntity ) { + hud->SetStateString( "location", locationEntity->GetLocation() ); + } else { +// RAVEN BEGIN +// rjohnson: temp fix until id corrects slow downs created from constant string lookup +// hud->SetStateString( "location", common->GetLocalizedString( "#str_102911" ) ); + hud->SetStateString( "location", "Unidentified" ); +// RAVEN END + } + } +} + +/* +================ +idPlayer::UpdateFocus + +Searches nearby entities for interactive guis, possibly making one of them +the focus and sending it a mouse move event +================ +*/ +void idPlayer::UpdateFocus( void ) { + + // These only need to be updated at the last tic + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idClipModel* clipModelList[ MAX_GENTITIES ]; + idClipModel* clip; + int listedClipModels; + idEntity* ent; + idUserInterface* oldBrackets; + int oldTalkCursor; + int i; + int j; + idVec3 start; + idVec3 end; + trace_t renderTrace, bboxTrace, allTrace; + guiPoint_t pt; + +// RAVEN BEGIN + // mekberg: removed check to see if attack was held. +// RAVEN END + + // No focus during cinimatics + if ( gameLocal.inCinematic ) { + return; + } + + // Focus has a limited time, make sure it hasnt expired + if ( focusTime && gameLocal.time > focusTime ) { + ClearFocus ( ); + } + + if ( spectating ) { + return; + } + + if ( g_perfTest_noPlayerFocus.GetBool() ) { + return; + } + +#ifndef _XENON + cvarSystem->SetCVarInteger( "pm_isZoomed", zoomed ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + +#ifdef _XENON + if ( cursor ) { + if ( weapon ) { + cursor->SetStateInt( "autoaim", weapon->AllowAutoAim() ? 1 : 0 ); + } else { + cursor->SetStateInt( "autoaim", 0 ); + } + } +#endif + + // Kill the focus brackets when their time has elapsed + oldBrackets = focusBrackets; + if ( focusBracketsTime && gameLocal.time > focusBracketsTime ) { + focusBrackets = NULL; + } + + oldTalkCursor = talkCursor; + talkCursor = 0; + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 768.0f; + + // player identification -> names to the hud + if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) { + trace_t trace; + idVec3 end = start + viewAngles.ToForward() * 768.0f; + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + // no aim text if player is invisible + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) && ( !((idPlayer*)gameLocal.entities[ trace.c.entityNum ])->PowerUpActive( POWERUP_INVISIBILITY ) ) ) { + char* teammateHealth = ""; + idPlayer* p = static_cast(gameLocal.entities[ trace.c.entityNum ]); + + if( trace.c.entityNum != aimClientNum ) { + if( mphud ) { + mphud->SetStateString( "aim_text", va( "%s\n%s", gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_name" ), gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_clan" ) ) ); + if( gameLocal.IsTeamGame() ) { + mphud->SetStateInt( "aim_player_team", p->team ); + + if( p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + // when looking at a friendly, color the crosshair + if( cursor ) { + if( p->team == team ) { + cursor->HandleNamedEvent( "targetFriendly" ); + } else { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + + + mphud->HandleNamedEvent( "aim_text" ); + aimClientNum = trace.c.entityNum; + } + } else { + // update health + if( gameLocal.IsTeamGame() && p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + } + } else { + if( mphud && aimClientNum != -1 ) { + mphud->HandleNamedEvent( "aim_fade" ); + aimClientNum = -1; + } + if( cursor ) { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + +#ifdef _XENON + + bboxTrace.fraction = -1; + + bestEnemy = NULL; + if ( gameLocal.isMultiplayer ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + return; + } + + idEntity *ent = NULL; + idActor *actor = NULL; + bool inside; + bool showDebug = cvarSystem->GetCVarBool("pm_showAimAssist"); + float bDist = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + float dist; + idFrustum aimArea; + float dNear, dFar, size; + renderView_t *rv = GetRenderView(); + float fovY, fovX; + // field of view + gameLocal.CalcFov( CalcFov( true ), fovX, fovY ); + + + dNear = cvarSystem->GetCVarFloat( "r_znear" ); + dFar = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + +#ifndef _FINAL + if ( cvarSystem->GetCVarInteger("pm_AimAssistTest") != 0 ) { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * pm_AimAssistFOV.GetFloat()/100.0; + } else +#endif + { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * weapon->GetAutoAimFOV()/100.0; + } + + aimArea.SetOrigin( GetEyePosition() ); + aimArea.SetAxis( viewAngles.ToMat3() ); + aimArea.SetSize( dNear, dFar, size, size ); + + + if ( showDebug ) { + gameRenderWorld->DebugFrustum(colorRed, aimArea, false, 20); + } + + idLinkList *entities = &gameLocal.snapshotEntities; + if ( gameLocal.isServer ) { + entities = &gameLocal.spawnedEntities; + } + ent = entities->Next(); + while ( ent != NULL ) { + + bool isOk = true; + + if ( !ent->IsType(idPlayer::GetClassType()) ) { + isOk = false; + } else if ( gameLocal.IsTeamGame() && ((idPlayer*)ent)->team == team ) { + isOk = false; + } else if ( ((idPlayer*)ent)->instance != instance ) { + isOk = false; + } else if ( ((idPlayer*)ent)->spectating ) { + isOk = false; + } else if ( !idStr::Icmp(name.c_str(),ent->name.c_str()) ) { + isOk = false; + } else if ( ent->health <= 0 ) { + isOk = false; + } + + if ( !isOk ) { + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + continue; + } + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + if ( showDebug ) { + gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + } + inside = aimArea.IntersectsBounds(bounds); + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + } + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum) ) { + if ( bestEnemy ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + trace_t trace; + + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + return; + } + +#endif + + idBounds bounds( start ); + bounds.AddPoint( end ); + + listedClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); + + // Do autoaim + + +//RAVEN BEGIN +//asalmon: auto-aim for Xenon, finds a best enemy and changes the crosshairs. +#ifdef _XBOX + + bool doAimAssist = cvarSystem->GetCVarBool("pm_AimAssist") || gameLocal.isMultiplayer; + + bestEnemy = NULL; + bool showDebug = cvarSystem->GetCVarBool("pm_showAimAssist"); + float bDist = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + float dist; + idFrustum aimArea; + float dNear, dFar, size; + renderView_t *rv = GetRenderView(); + float fovY, fovX; + + if ( weapon && doAimAssist ) { + + // field of view + gameLocal.CalcFov( CalcFov( true ), fovX, fovY ); + + dNear = cvarSystem->GetCVarFloat( "r_znear" ); + dFar = bDist; +#ifndef _FINAL + if ( cvarSystem->GetCVarInteger("pm_AimAssistTest") != 0 ) { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * pm_AimAssistFOV.GetFloat()/100.0; + } else +#endif + { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * weapon->GetAutoAimFOV()/100.0; + } + aimArea.SetOrigin( GetEyePosition() ); + aimArea.SetAxis( viewAngles.ToMat3() ); + aimArea.SetSize( dNear, dFar, size, size ); + + if ( showDebug ) { + gameRenderWorld->DebugFrustum( colorRed, aimArea, false, 20 ); + } + } + +#endif +//RAVEN END + + // dluetscher: added optimization to eliminate redundant traces + renderTrace.fraction = -1; + bboxTrace.fraction = -1; + allTrace.fraction = -1; + + // no pretense at sorting here, just assume that there will only be one active + // gui within range along the trace + bool wasTargetFriendly = targetFriendly; + targetFriendly = false; + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); +//RITUAL BEGIN +//singlis: if ent is null, continue; + if(ent == NULL || ent->IsHidden()) { + continue; + } +//RITUAL END + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + // SP only + // basically what was happening was that heads, which are an idAFAttachment, were being used for the focusLength + // calculations, but that generates a different focus length than the body would (when you scan the crosshair back + // and forth between the head and body). This ends up looking like a bug when you are right at the threshold where + // the body will display the name and rank, but doesn't when you pitch up to aim at the head. Hence, using the body + // for this special case. + if ( !gameLocal.isMultiplayer && ent->IsType( idAFAttachment::GetClassType() )) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType()) ) { + focusLength = (body->GetPhysics()->GetOrigin() - start).LengthFast() - body->GetPhysics()->GetBounds().GetRadius(); + } + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + isFriendly = (static_cast( ent )->team == team); + } + + //change crosshair color if over a friendly + if ( !gameLocal.isMultiplayer + && focusType == FOCUS_NONE + && !g_crosshairCharInfoFar.GetBool() ) { + if ( focusLength < 512 ) { + bool newTargetFriendly = false; + if ( isAI && isFriendly ) { + newTargetFriendly = true; + } else if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->team == team ) ) { + newTargetFriendly = true; + } + } + if ( newTargetFriendly ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + targetFriendly = true; + if( cursor && !wasTargetFriendly ) { + cursor->HandleNamedEvent( "showCrossBuddy" ); + } + } + } + } + } + +// RAVEN BEGIN + +#ifdef _XENON + if ( doAimAssist ) { + if ( isAI && !isFriendly && (ent->health > 0) ) { + + if ( idStr::Icmp( name.c_str(),ent->name.c_str() ) != 0 ) { + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + //if ( showDebug ) { + //gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + //} + bool inside = aimArea.IntersectsBounds(bounds); + + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } + } +#endif + +// mekberg: allowFocus removed + if ( focusLength < (g_crosshairCharInfoFar.GetBool()?256.0f:80.0f) ) { +// RAVEN END + if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->GetTalkState() >= TALK_OK ) ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, body, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + + if ( isAI ) { + if ( static_cast( ent )->GetTalkState() >= TALK_OK ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, ent, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + } + + if ( focusLength < 80.0f ) { + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + rvVehicle* vehicle = static_cast(ent); + // dluetscher: added optimization to eliminate redundant traces + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum == ent->entityNumber ) && ((end - start).Length() * bboxTrace.fraction < vehicle->FocusLength()) ) { + //jshepard: locked or unusable vehicles + if ( !vehicle->IsLocked() && vehicle->HasOpenPositions() ) { + SetFocus ( FOCUS_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + + //jshepard: unusable vehicle + if( ent->spawnArgs.GetBool( "unusableVehicle", "0" ) ) { + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + break; + } + } + // Usable entities are last + if ( ent->fl.usable ) { + //jshepard: fake vehicles + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + if( ent->spawnArgs.GetBool("crosshair_vehicle")) { + SetFocus ( FOCUS_USABLE_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_USABLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + } + + if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) { + continue; + } + + if ( ent->spawnArgs.GetBool( "inv_item" ) ) { + // don't allow guis on pickup items focus + continue; + } + + pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end ); + if ( pt.x != -1 ) { + idUserInterface* ui = NULL; + renderEntity_t* focusGUIrenderEntity; + + focusGUIrenderEntity = ent->GetRenderEntity(); + if ( !focusGUIrenderEntity ) { + continue; + } + + if ( pt.guiId >= 1 && pt.guiId <= MAX_RENDERENTITY_GUI ) { + ui = focusGUIrenderEntity->gui[ pt.guiId-1 ]; + } + + if ( ui == NULL || !ui->IsInteractive ( ) ) { + continue; + } + + // All focused guis get brackets + focusBrackets = ui; + + // Any GUI that is too far away will just get bracket focus so the player can still shoot + // but still see which guis are interractive + if ( focusLength > 300.0f ) { + ClearFocus ( ); + break; + } else if ( focusLength > 80.0f ) { + ClearFocus ( ); + focusType = FOCUS_BRACKETS; +#ifdef _XENON + // Hide the "press whatever" text + hud->SetStateInt( "GUIIsNotUsingDPad", 3 ); + hud->SetStateInt( "hideInteractive", 1 ); +#endif + break; + } + + // If this is the first time this gui was activated then set up some things + if ( focusEnt != ent ) { + const idKeyValue* kv; + + // new activation + // going to see if we have anything in inventory a gui might be interested in + // need to enumerate inventory items + ui->SetStateInt( "inv_count", inventory.items.Num() ); + for ( j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + + ui->SetStateString( va( "inv_name_%i", j), iname ); + ui->SetStateString( va( "inv_icon_%i", j), iicon ); + ui->SetStateString( va( "inv_text_%i", j), itext ); + kv = item->MatchPrefix("inv_id", NULL); + if ( kv ) { + ui->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + ui->SetStateInt( iname, 1 ); + } + + for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) { + const char *p = inventory.pdaSecurity[ j ]; + + if ( p && *p ) { + ui->SetStateInt( p, 1 ); + } + } + + ui->SetStateString( "player_health", va("%i", health ) ); + ui->SetStateString( "player_armor", va( "%i%%", inventory.armor ) ); + + kv = ent->spawnArgs.MatchPrefix( "gui_", NULL ); + while ( kv ) { + ui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = ent->spawnArgs.MatchPrefix( "gui_", kv ); + } + } + + // clamp the mouse to the corner + const char* command; + sysEvent_t ev; + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + + // move to an absolute position + ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + +#ifdef _XENON + int usepad = 0; + if ( focusUI ) { + idWindow *dwin = ui->GetDesktop(); + if ( dwin ) { + idWinVar *wv = dwin->GetWinVarByName("dpadGUI"); + if ( wv ) { + usepad = atoi(wv->c_str()); + } + } + } + hud->SetStateInt( "GUIIsNotUsingDPad", (usepad) ? 0 : 1 ); + hud->SetStateInt( "hideInteractive", 0 ); +#endif + + SetFocus ( FOCUS_GUI, FOCUS_GUI_TIME, ent, ui ); + break; + } + } + +#ifdef _XENON + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum) ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } else { + + if ( bestEnemy && doAimAssist ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + } + trace_t trace; + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + } +#endif + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum) ) { + if ( wasTargetFriendly && !targetFriendly && focusType == FOCUS_NONE ) { + if ( cursor ) { + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); + } + } + } + + // Update the focus brackets within the hud + if ( focusBrackets && hud ) { + if (focusType == FOCUS_BRACKETS || focusType == FOCUS_GUI ) { + if ( !oldBrackets || focusBracketsTime ) { + hud->HandleNamedEvent ( "showBrackets" ); + } + focusBracketsTime = 0; + } else { + if ( focusBracketsTime == 0 ) { + hud->HandleNamedEvent ( "fadeBrackets" ); + focusBracketsTime = gameLocal.time + 2000; + } + } + focusBrackets->SetStateBool ( "2d_calc", true ); + } + + if ( cursor && ( oldTalkCursor != talkCursor ) ) { + cursor->SetStateInt( "talkcursor", talkCursor ); + } + +} + +/* +================ +idPlayer::ClearFocus +================ +*/ +void idPlayer::ClearFocus ( void ) { + SetFocus ( FOCUS_NONE, 0, NULL, NULL ); +} + +void idPlayer::UpdateFocusCharacter( idEntity* newEnt ) { + if ( !cursor ) { + return; + } + // Handle character interaction + cursor->SetStateString( "npc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_name", "Joe" )) ); + cursor->SetStateString( "npcdesc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_description", "" )) ); + if ( newEnt->IsType( rvAIMedic::GetClassType() ) ) { + if ( ((rvAIMedic*)newEnt)->isTech ) { + cursor->SetStateInt( "npc_medictech", 2 ); + } else { + cursor->SetStateInt( "npc_medictech", 1 ); + } + } else { + cursor->SetStateInt( "npc_medictech", 0 ); + } +} +/* +================ +idPlayer::SetFocus +================ +*/ +void idPlayer::SetFocus ( playerFocus_t newType, int _focusTime, idEntity* newEnt, idUserInterface* newUI ) { + const char* command; + + // Handle transitions from one user interface to another or to none + if ( newUI != focusUI ) { + if ( focusUI ) { + command = focusUI->Activate( false, gameLocal.time ); + HandleGuiCommands( focusEnt, command ); + StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); + } + if ( newUI ) { + command = newUI->Activate( true, gameLocal.time ); + HandleGuiCommands( newEnt, command ); + StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); + + // Hide the weapon when a gui is being used + /* + if ( weapon ) { + weapon->Hide(); + } + */ + }/* + else if ( weapon ) { + // Show the weapon since it was hidden when a gui first got focus + weapon->Show(); + } + */ + } + + //jshepard: the medic/tech crosshair won't update unless handleNamedEvent is called. I moved this outside + //of the switch below because the focus type is the same when moving directly from marine to marine, but the + //medic/tech status may change. + if ( newType == FOCUS_CHARACTER ) { + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + cursor->HandleNamedEvent ( "showCrossTalk" ); + } + } + // Show the appropriate cursor for the current focus type + if ( cursor && ( focusType != newType ) ) { + switch ( newType ) { + case FOCUS_VEHICLE: + case FOCUS_USABLE_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicle" ); + break; + case FOCUS_LOCKED_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicleLocked" ); + break; + case FOCUS_USABLE: + cursor->HandleNamedEvent ( "showCrossUsable" ); + break; + case FOCUS_GUI: + cursor->HandleNamedEvent ( "showCrossGui" ); + break; + case FOCUS_CHARACTER: + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + } + cursor->HandleNamedEvent ( "showCrossTalk" ); + break; + default: + // Make sure the weapon is shown in the default state +// RAVEN BEGIN +// abahr: don't do this if weapons are disabled + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); +// RAVEN END + break; + } + } + + + focusType = newType; + focusEnt = newEnt; + focusUI = newUI; + + if ( focusType == FOCUS_NONE ) { + focusTime = 0; + } else { + focusTime = gameLocal.time + _focusTime; + } +} + +/* +================= +idPlayer::CrashLand + +Check for hard landings that generate sound events +================= +*/ +void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + idVec3 origin, velocity; + idVec3 gravityVector, gravityNormal; + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + waterLevel_t waterLevel; + bool noDamage; + + pfl.softLanding = false; + pfl.hardLanding = false; + + // if the player is not on the ground + if ( !physicsObj.HasGroundContacts() ) { + return; + } + + gravityNormal = physicsObj.GetGravityNormal(); + + // if the player wasn't going down + if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) { + return; + } + + waterLevel = physicsObj.GetWaterLevel(); + + // never take falling damage if completely underwater + if ( waterLevel == WATERLEVEL_HEAD ) { + return; + } + + // no falling damage if touching a nodamage surface + noDamage = false; + for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) { + const contactInfo_t &contact = physicsObj.GetContact( i ); + if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) { + noDamage = true; + break; + } + } + + //jshepard: no falling damage if falling damage is disabled + if( pfl.noFallingDamage ) { + return; + } + + origin = GetPhysics()->GetOrigin(); + gravityVector = physicsObj.GetGravity(); + + // calculate the exact velocity on landing + dist = ( origin - oldOrigin ) * -gravityNormal; + vel = oldVelocity * -gravityNormal; + acc = -gravityVector.Length(); + + a = acc / 2.0f; + b = vel; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.0001; + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < 1.0f ) { + return; + } + + // Some bug in the player movement code causes double fall damage on the 2nd frame in certain situations, + // so hack around this. Basically, if the next frame's velocity is going to be great enough to cause damage, + // don't do the damage this frame. This feels a lot safer than messing with things that could break player + // movement in worse ways. + if ( gameLocal.isMultiplayer ) { + + float vel2 = GetPhysics()->GetLinearVelocity() * -gravityNormal; + + a = acc / 2.0f; + b = vel2; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + float delta2 = vel2 + t * acc; + delta2 = delta2 * delta2 * 0.0001; + + if ( delta2 > softFallDelta ) { + return; + } + } + + + // ddynerman: moved height delta selection to player def + if ( delta > fatalFallDelta && fatalFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -32; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 ); + } + } else if ( delta > hardFallDelta && hardFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -24; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 ); + } + } else if ( delta > softFallDelta && softFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -16; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 ); + } + } else if ( delta > noFallDelta && noFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -8; + landTime = gameLocal.time; + } + + // ddynerman: sometimes the actual landing animation is pre-empted by another animation (i.e. sliding, moving forward) + // so we play the landing sound here instead of relying on the anim + if( pfl.hardLanding ) { + StartSound ( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL ); + StartSound ( "snd_land_hard_pain", SND_CHANNEL_ANY, 0, false, NULL ); + } else if ( pfl.softLanding ) { + // todo - 2 different landing sounds for variety? + StartSound ( "snd_land_soft", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +/* +=============== +idPlayer::BobCycle +=============== +*/ +void idPlayer::BobCycle( const idVec3 &pushVelocity ) { + float bobmove; + int old, deltaTime; + idVec3 vel, gravityDir, velocity; + idMat3 viewaxis; + float bob; + float delta; + float speed; + float f; + + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + velocity = physicsObj.GetLinearVelocity() - pushVelocity; + + if ( noclip ) { + velocity.Zero ( ); + } + + gravityDir = physicsObj.GetGravityNormal(); + vel = velocity - ( velocity * gravityDir ) * gravityDir; + xyspeed = vel.LengthFast(); + + if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) { + // airborne + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) { + // start at beginning of cycle again + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else { + if ( physicsObj.IsCrouching() ) { + bobmove = pm_crouchbob.GetFloat(); + // ducked characters never play footsteps + } else { + // vary the bobbing based on the speed of the player + bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac; + } + + // check for footstep / splash sounds + old = bobCycle; + bobCycle = (int)( old + bobmove * gameLocal.GetMSec() ) & 255; + bobFoot = ( bobCycle & 128 ) >> 7; + bobfracsin = idMath::Fabs( idMath::Sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); + } + + // calculate angles for view bobbing + viewBobAngles.Zero(); + + // no view bob at all in MP while zoomed in + if( gameLocal.isMultiplayer && IsZoomed() ) { + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + return; + } + + viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + + // add angles based on velocity + delta = velocity * viewaxis[0]; + viewBobAngles.pitch += delta * pm_runpitch.GetFloat(); + + delta = velocity * viewaxis[1]; + viewBobAngles.roll -= delta * pm_runroll.GetFloat(); + + // add angles based on bob + // make sure the bob is visible even at low speeds + speed = xyspeed > 200 ? xyspeed : 200; + + delta = bobfracsin * pm_bobpitch.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching + } + viewBobAngles.pitch += delta; + delta = bobfracsin * pm_bobroll.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching accentuates roll + } + if ( bobFoot & 1 ) { + delta = -delta; + } + viewBobAngles.roll += delta; + + // calculate position for view bobbing + viewBob.Zero(); + + if ( physicsObj.HasSteppedUp() ) { + + // check for stepping up before a previous step is completed + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp(); + } else { + stepUpDelta = physicsObj.GetStepUp(); + } + if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) { + stepUpDelta = 2.0f * pm_stepsize.GetFloat(); + } + stepUpTime = gameLocal.time; + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + + // if the player stepped up recently + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME ); + } + + // add bob height after any movement smoothing + bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); + if ( bob > 6 ) { + bob = 6; + } +// RAVEN BEGIN +// abahr: added gravity + viewBob += bob * -gravityDir; +// RAVEN END + + // add fall height + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + viewBob -= gravity * ( landChange * f ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + viewBob -= gravity * ( landChange * f ); + } +} + +/* +================ +idPlayer::UpdateDeltaViewAngles +================ +*/ +void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { + // set the delta angle + idAngles delta; + for( int i = 0; i < 3; i++ ) { + delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); + } + SetDeltaViewAngles( delta ); +} + +/* +================ +idPlayer::SetViewAngles +================ +*/ +void idPlayer::SetViewAngles( const idAngles &angles ) { + UpdateDeltaViewAngles(angles); + viewAngles = angles; +} + +/* +================ +idPlayer::UpdateViewAngles +================ +*/ +void idPlayer::UpdateViewAngles( void ) { + int i; + idAngles delta; + + if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 ) ) { + // no view changes at all, but we still want to update the deltas or else when + // we get out of this mode, our view will snap to a kind of random angle + UpdateDeltaViewAngles( viewAngles ); + return; + } + + // if dead + if ( health <= 0 ) { + if ( pm_thirdPersonDeath.GetBool() ) { + viewAngles.roll = 0.0f; + viewAngles.pitch = 30.0f; + } else { + viewAngles.roll = 40.0f; + viewAngles.pitch = -15.0f; + } + return; + } + + // circularly clamp the angles with deltas +// if( gameLocal.localClientNum == entityNumber ) { +// gameLocal.Printf( "BEFORE VIEWANGLES: %s\n", viewAngles.ToString() ); +// gameLocal.Printf( "\tUSERCMD: <%d, %d, %d>\n", usercmd.angles[ 0 ], usercmd.angles[ 1 ], usercmd.angles[ 2 ] ); +// gameLocal.Printf( "\tDELTAVIEW: %s\n", deltaViewAngles.ToString() ); +// } + for ( i = 0; i < 3; i++ ) { + cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); + if ( influenceActive == INFLUENCE_LEVEL3 ) { + viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) ); + } else { + viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i] ) + deltaViewAngles[i] ); + } + } + if ( !centerView.IsDone( gameLocal.time ) ) { + viewAngles.pitch = centerView.GetCurrentValue( gameLocal.time ); + } +// if( gameLocal.localClientNum == entityNumber ) { +// gameLocal.Printf( "AFTER VIEWANGLES: %s\n", viewAngles.ToString() ); +// } + + // clamp the pitch + if ( noclip ) { + if ( viewAngles.pitch > 89.0f ) { + // don't let the player look down more than 89 degrees while noclipping + viewAngles.pitch = 89.0f; + } else if ( viewAngles.pitch < -89.0f ) { + // don't let the player look up more than 89 degrees while noclipping + viewAngles.pitch = -89.0f; + } + } else { + if ( viewAngles.pitch > pm_maxviewpitch.GetFloat() ) { + // don't let the player look down enough to see the shadow of his (non-existant) feet + viewAngles.pitch = pm_maxviewpitch.GetFloat(); + } else if ( viewAngles.pitch < pm_minviewpitch.GetFloat() ) { + // don't let the player look up more than 89 degrees + viewAngles.pitch = pm_minviewpitch.GetFloat(); + } + } + + UpdateDeltaViewAngles( viewAngles ); + + // orient the model towards the direction we're looking + SetAngles( idAngles( 0, viewAngles.yaw, 0 ) ); + + // save in the log for analyzing weapon angle offsets + loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles; +} + +/* +============== +idPlayer::UpdateAir +============== +*/ +void idPlayer::UpdateAir( void ) { + + if ( health <= 0 ) { + return; + } + + // see if the player is connected to the info_vacuum + bool newAirless = false; + + if ( gameLocal.vacuumAreaNum != -1 ) { + int num = GetNumPVSAreas(); + if ( num > 0 ) { + int areaNum; + + // if the player box spans multiple areas, get the area from the origin point instead, + // otherwise a rotating player box may poke into an outside area + if ( num == 1 ) { + const int *pvsAreas = GetPVSAreas(); + areaNum = pvsAreas[0]; + } else { + areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() ); + } + newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR ); + } + } + + if ( newAirless ) { + if ( !airless ) { + StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( hud ) { + hud->HandleNamedEvent( "noAir" ); + } + } + airTics--; + if ( airTics < 0 ) { + airTics = 0; + // check for damage + const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false ); + int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f ); + if ( gameLocal.time > lastAirDamage + dmgTiming ) { + Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 ); + lastAirDamage = gameLocal.time; + } + } + + } else { + if ( airless ) { + StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + if ( hud ) { + hud->HandleNamedEvent( "Air" ); + } + } + airTics+=2; // regain twice as fast as lose + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } + + airless = newAirless; + + if ( hud ) { + hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() ); + } +} + +// RAVEN BEGIN +// abahr +/* +============== +idPlayer::UpdateGravity +============== +*/ +void idPlayer::UpdateGravity( void ) { + GetPhysics()->SetGravity( gameLocal.GetCurrentGravity(this) ); +} +// RAVEN END + +/* +============== +idPlayer::ToggleObjectives +============== +*/ +void idPlayer::ToggleObjectives ( void ) { +// RAVEN BEGIN +// mekberg: allow disabling of objectives. + if ( objectiveSystem == NULL || !objectivesEnabled ) { + return; + } +// RAVEN END + + if ( !objectiveSystemOpen ) { + int j, c = inventory.items.Num(); + objectiveSystem->SetStateInt( "inv_count", c ); + for ( j = 0; j < c; j++ ) { + idDict *item = inventory.items[j]; + if ( !item->GetBool( "inv_pda" ) ) { + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + objectiveSystem->SetStateString( va( "inv_name_%i", j ), iname ); + objectiveSystem->SetStateString( va( "inv_icon_%i", j ), iicon ); + objectiveSystem->SetStateString( va( "inv_text_%i", j ), itext ); + const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL ); + if ( kv ) { + objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + } + } + + for ( j = 0; j < MAX_WEAPONS; j++ ) { + const char *weapnum = va( "def_weapon%d", j ); + int weapstate = 0; + if ( inventory.weapons & ( 1 << j ) ) { + const char *weap = spawnArgs.GetString( weapnum ); + if ( weap && *weap ) { + weapstate++; + } + } + objectiveSystem->SetStateInt( weapnum, weapstate ); + } + + UpdateObjectiveInfo(); + objectiveSystem->Activate( true, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommShow" ); + } else { + objectiveSystem->Activate( false, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommHide" ); + } + objectiveSystemOpen ^= 1; +} + +/* +============== +idPlayer::ToggleScoreboard +============== +*/ +void idPlayer::ToggleScoreboard( void ) { + scoreBoardOpen ^= 1; +} + +/* +============== +idPlayer::Spectate +============== +*/ +void idPlayer::Spectate( bool spectate, bool force ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + // track invisible player bug + // all hiding and showing should be performed through Spectate calls + // except for the private camera view, which is used for teleports + // ok for players in other instances to be hidden + assert( force || ( teleportEntity.GetEntity() != NULL ) || ( IsHidden() == spectating ) || ( IsHidden() && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ); + + if ( spectating == spectate && !force ) { + return; + } + + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + + spectating = spectate; + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteBits( spectating, 1 ); + ServerSendEvent( EVENT_SPECTATE, &msg, false, -1 ); + } + + // on the client, we'll get spectate messages about clients in other instances - always assume they are spectating + if ( spectating || inOtherInstance ) { + // join the spectators + delete clientHead; + clientHead = NULL; + + ClearPowerUps(); + spectator = this->entityNumber; + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + common->DPrintf( "idPlayer::Spectate() - Disabling clip for %d '%s' - spectate: %d force: %d\n", entityNumber, GetUserInfo()->GetString( "ui_name" ), spectate, force ); + physicsObj.DisableClip(); + Hide(); + Event_DisableWeapon(); + + // remove the weapon + delete weapon; + weapon = NULL; + if ( !gameLocal.isClient ) { + delete weaponViewModel; + weaponViewModel = NULL; + delete weaponWorldModel; + weaponWorldModel = NULL; + } + } else { + // put everything back together again + UpdateModelSetup( true ); + currentWeapon = -1; // to make sure the def will be loaded if necessary + Show(); + Event_EnableWeapon(); + } + + SetClipModel( inOtherInstance ); + + if ( inOtherInstance ) { + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } +} + +/* +============== +idPlayer::SetClipModel +============== +*/ +void idPlayer::SetClipModel( bool forceSpectatorBBox ) { + idBounds bounds; + + common->DPrintf( "idPlayer::SetClipModel() - Called on %d '%s' forceSpectatorBBox = %d spectate = %d instance = %d local instance = %d\n", entityNumber, GetUserInfo()->GetString( "ui_name" ), forceSpectatorBBox, spectating, instance, gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->GetInstance() : -1 ); + if ( spectating || forceSpectatorBBox ) { + bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + } else { + bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 ); + bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() ); + } + // the origin of the clip model needs to be set before calling SetClipModel + // otherwise our physics object's current origin value gets reset to 0 + idClipModel *newClip; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + if ( pm_usecylinder.GetBool() ) { + newClip = new idClipModel( idTraceModel( bounds, 8 ), declManager->FindMaterial( "textures/flesh_boundingbox" ) ); + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); + } else { + newClip = new idClipModel( idTraceModel( bounds ), declManager->FindMaterial( "textures/flesh_boundingbox" ) ); + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); + } +} + +/* +============== +idPlayer::EnterVehicle +============== +*/ +bool idPlayer::EnterVehicle( idEntity* vehicle ) { + if ( !idActor::EnterVehicle ( vehicle ) ) { + return false; + } + +// RAVEN BEGIN +// jshepard: safety first + if( weapon) { + weapon->Hide(); + } + +// abahr: + //HideCrosshair(); +// RAVEN END + + return true; +} + +/* +============== +idPlayer::ExitVehicle +============== +*/ +bool idPlayer::ExitVehicle ( bool force ) { + if ( !idActor::ExitVehicle ( force ) ) { + return false; + } + + SetViewAngles( viewAxis[0].ToAngles() ); + +// RAVEN BEGIN +// jshepard: had this crash on me more than once :( + if(weapon) { + weapon->Show(); + } +// abahr: Would like to call something more specific + ShowCrosshair(); +// RAVEN END + + return true; +} + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + +/* +============== +GetItemCost +============== +*/ +int idPlayer::GetItemCost( const char* itemName ) { + if ( !itemCosts ) { + assert( false ); + return 99999; + } + return itemCosts->dict.GetInt( itemName, "99999" ); +} + +/* +============== +GetItemBuyImpulse +============== +*/ +int GetItemBuyImpulse( const char* itemName ) +{ + struct ItemBuyImpulse + { + const char* itemName; + int itemBuyImpulse; + }; + + ItemBuyImpulse itemBuyImpulseTable[] = + { + { "weapon_shotgun", IMPULSE_100, }, + { "weapon_machinegun", IMPULSE_101, }, + { "weapon_hyperblaster", IMPULSE_102, }, + { "weapon_grenadelauncher", IMPULSE_103, }, + { "weapon_nailgun", IMPULSE_104, }, + { "weapon_rocketlauncher", IMPULSE_105, }, + { "weapon_railgun", IMPULSE_106, }, + { "weapon_lightninggun", IMPULSE_107, }, + // IMPULSE_108 - Unused + { "weapon_napalmgun", IMPULSE_109, }, + // { "weapon_dmg", IMPULSE_110, }, + // IMPULSE_111 - Unused + // IMPULSE_112 - Unused + // IMPULSE_113 - Unused + // IMPULSE_114 - Unused + // IMPULSE_115 - Unused + // IMPULSE_116 - Unused + // IMPULSE_117 - Unused + { "item_armor_small", IMPULSE_118, }, + { "item_armor_large", IMPULSE_119, }, + { "ammorefill", IMPULSE_120, }, + // IMPULSE_121 - Unused + // IMPULSE_122 - Unused + { "ammo_regen", IMPULSE_123, }, + { "health_regen", IMPULSE_124, }, + { "damage_boost", IMPULSE_125, }, + // IMPULSE_126 - Unused + // IMPULSE_127 - Unused + }; + const int itemBuyImpulseTableSize = sizeof(itemBuyImpulseTable) / sizeof(itemBuyImpulseTable[0]); + + for( int i = 0; i < itemBuyImpulseTableSize; ++ i ) + { + if( !stricmp( itemBuyImpulseTable[ i ].itemName, itemName ) ) + { + return itemBuyImpulseTable[ i ].itemBuyImpulse; + } + } + + return 0; +} + + +bool idPlayer::CanBuyItem( const char* itemName ) +{ + itemBuyStatus_t buyStatus = ItemBuyStatus( itemName ); + return( buyStatus == IBS_CAN_BUY ); +} + + +itemBuyStatus_t idPlayer::ItemBuyStatus( const char* itemName ) +{ + idStr itemNameStr = itemName; + if ( itemNameStr == "notimplemented" ) + { + return IBS_NOT_ALLOWED; + } + else if( !idStr::Cmpn( itemName, "wpmod_", 6 ) ) + { + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_small" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_large" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "ammorefill" ) + { + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + return IBS_ALREADY_HAVE; + + // If we are full of ammo for all weapons, you can't buy the ammo refill anymore. + bool fullAmmo = true; + for ( int i = 0 ; i < MAX_AMMOTYPES; i++ ) + { + if ( inventory.ammo[i] != inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) + fullAmmo = false; + } + if ( fullAmmo ) + return IBS_NOT_ALLOWED; + } + else if ( itemNameStr == "fc_armor_regen" ) + { + return IBS_NOT_ALLOWED; + } + + if ( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TOURNEY || gameLocal.gameType == GAME_ARENA_CTF || gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if ( itemNameStr == "ammo_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "health_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "damage_boost" ) + return IBS_NOT_ALLOWED; + } + + if ( CanSelectWeapon(itemName) != -1 ) + return IBS_ALREADY_HAVE; + + int cost = GetItemCost(itemName); + if ( cost > (int)buyMenuCash ) + { + return IBS_CANNOT_AFFORD; + } + + return IBS_CAN_BUY; +} + +/* +============== +idPlayer::UpdateTeamPowerups +============== +*/ +void idPlayer::UpdateTeamPowerups( bool isBuying ) { + for ( int i=0; iIsType( idPlayer::Type ) ) + continue; + + idPlayer* player = static_cast< idPlayer * >( ent ); + + // Not my teammate + if ( player->team != team ) + continue; + + // when a teammate respawns while a powerup is active, we don't want to go through other players which already have the powerup + // otherwise they'll get multiple VO announces + // ignore this when setting up powerups after a buying impulse, it only means someone is buying before expiration + if ( !isBuying && ( ( player->inventory.powerups & ( 1 << powerup ) ) != 0 ) ) { + continue; + } + + player->GivePowerUp( powerup, time, true ); + } + + gameLocal.mpGame.teamPowerups[team][i].update = false; + } + } +} + + +/* +============== +idPlayer::AttemptToBuyTeamPowerup +============== +*/ +bool idPlayer::AttemptToBuyTeamPowerup( const char* itemName ) +{ + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_AMMOREGEN, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "health_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_REGENERATION, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "damage_boost" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_TEAM_DAMAGE_MOD, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + + return false; +} + +/* +============== +idPlayer::AttemptToBuyItem +============== +*/ +bool idPlayer::AttemptToBuyItem( const char* itemName ) +{ + if ( gameLocal.isClient ) { + return false; + } + + if( !itemName ) { + return false; + } + + int itemCost = GetItemCost( itemName ); + + /// Check if the player is allowed to buy this item + if( !CanBuyItem( itemName ) ) + { + return false; + } + + const char* playerName = GetUserInfo()->GetString( "ui_name" ); + common->DPrintf( "Player %s about to buy item %s; player has %d (%g) credits, cost is %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + buyMenuCash -= (float)itemCost; + + common->DPrintf( "Player %s just bought item %s; player now has %d (%g) credits, cost was %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + + // Team-based effects + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" || itemNameStr == "health_regen" || itemNameStr == "damage_boost" ) { + return AttemptToBuyTeamPowerup(itemName); + } + + GiveStuffToPlayer( this, itemName, NULL ); + gameLocal.mpGame.RedrawLocalBuyMenu(); + return true; +} + +bool idPlayer::CanBuy( void ) { + bool ret = gameLocal.mpGame.IsBuyingAllowedRightNow(); + if ( !ret ) { + return false; + } + return !spectating; +} + + +void idPlayer::GenerateImpulseForBuyAttempt( const char* itemName ) { + if ( !CanBuy() ) + return; + + int itemBuyImpulse = GetItemBuyImpulse( itemName ); + PerformImpulse( itemBuyImpulse ); +} +// RITUAL END + + +/* +============== +idPlayer::PerformImpulse +============== +*/ +void idPlayer::PerformImpulse( int impulse ) { + +//RAVEN BEGIN +// nrausch: Don't send xenon dpad impulses over the network +#ifdef _XENON + + if ( objectiveSystemOpen ) { + return; + } + + if ( gameLocal.isClient && (impulse < IMPULSE_70) ) { +#else + if ( gameLocal.isClient ) { +#endif +//RAVEN END + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( impulse, IMPULSE_NUMBER_OF_BITS ); + ClientSendEvent( EVENT_IMPULSE, &msg ); + } + + if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 ) { + SelectWeapon( impulse, false ); + return; + } + +//RAVEN BEGIN +//asalmon: D-pad events for in game guis on Xenon +#ifdef _XBOX + sysEvent_t ev; + ev.evType = SE_KEY; + ev.evValue2 = 1; + ev.evPtrLength = 0; + ev.evPtr = NULL; + + const char *command = NULL; + idUserInterface *ui = ActiveGui(); + bool updateVisuals = false; +#endif +//RAVEN END + + switch( impulse ) { + case IMPULSE_13: { + Reload(); + break; + } + case IMPULSE_14: { + NextWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCycleNext( this ); + } + break; + } + case IMPULSE_15: { + PrevWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCyclePrev( this ); + } + break; + } + case IMPULSE_17: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleReady( ); + } + break; + } + case IMPULSE_18: { + centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); + break; + } + case IMPULSE_19: { +/* + // when we're not in single player, IMPULSE_19 is used for showScores + // otherwise it does IMPULSE_12 (PDA) + if ( !gameLocal.isMultiplayer ) { + if ( !objectiveSystemOpen ) { + if ( weapon ) { + weapon->Hide (); + } + } + ToggleMap(); + } +*/ + break; + } + case IMPULSE_20: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleTeam( ); + } + break; + } + case IMPULSE_21: { + if( gameLocal.isServer && gameLocal.gameType == GAME_TOURNEY ) { + // only allow a client to join the waiting arena if they are not currently assigned to an arena + + // removed waiting arena functionality for now + /*rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( this != arena.GetPlayers()[ 0 ] && this != arena.GetPlayers()[ 1 ] ) { + if( instance == MAX_ARENAS && !spectating ) { + ServerSpectate( true ); + JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } else if( spectating ) { + JoinInstance( MAX_ARENAS ); + ServerSpectate( false ); + } + }*/ + } + break; + } + case IMPULSE_22: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleSpectate( ); + } + break; + } + + case IMPULSE_28: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + } + break; + } + case IMPULSE_29: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + } + break; + } + case IMPULSE_40: { + idFuncRadioChatter::RepeatLast(); + break; + } + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + case IMPULSE_100: AttemptToBuyItem( "weapon_shotgun" ); break; + case IMPULSE_101: AttemptToBuyItem( "weapon_machinegun" ); break; + case IMPULSE_102: AttemptToBuyItem( "weapon_hyperblaster" ); break; + case IMPULSE_103: AttemptToBuyItem( "weapon_grenadelauncher" ); break; + case IMPULSE_104: AttemptToBuyItem( "weapon_nailgun" ); break; + case IMPULSE_105: AttemptToBuyItem( "weapon_rocketlauncher" ); break; + case IMPULSE_106: AttemptToBuyItem( "weapon_railgun" ); break; + case IMPULSE_107: AttemptToBuyItem( "weapon_lightninggun" ); break; + case IMPULSE_108: break; // Unused + case IMPULSE_109: AttemptToBuyItem( "weapon_napalmgun" ); break; + case IMPULSE_110: /* AttemptToBuyItem( "weapon_dmg" );*/ break; + case IMPULSE_111: break; // Unused + case IMPULSE_112: break; // Unused + case IMPULSE_113: break; // Unused + case IMPULSE_114: break; // Unused + case IMPULSE_115: break; // Unused + case IMPULSE_116: break; // Unused + case IMPULSE_117: break; // Unused + case IMPULSE_118: AttemptToBuyItem( "item_armor_small" ); break; + case IMPULSE_119: AttemptToBuyItem( "item_armor_large" ); break; + case IMPULSE_120: AttemptToBuyItem( "ammorefill" ); break; + case IMPULSE_121: break; // Unused + case IMPULSE_122: break; // Unused + case IMPULSE_123: AttemptToBuyItem( "ammo_regen" ); break; + case IMPULSE_124: AttemptToBuyItem( "health_regen" ); break; + case IMPULSE_125: AttemptToBuyItem( "damage_boost" ); break; + case IMPULSE_126: break; // Unused + case IMPULSE_127: break; // Unused +// RITUAL END + + case IMPULSE_50: { + ToggleFlashlight ( ); + break; + } + + case IMPULSE_51: { + LastWeapon(); + break; + } + } + +//RAVEN BEGIN +//asalmon: route d-pad input to the active gui. +#ifdef _XBOX + if (ui && ev.evValue != 0 && !objectiveSystemOpen ) { + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +#endif +//RAVEN END +} + +/* +============== +idPlayer::HandleESC +============== +*/ +bool idPlayer::HandleESC( void ) { + +// jdischler: Straight from the top, cinematic skipping on xenon is OFFICIALLY OUT. Too many problems with it and not enough time to properly address them. +#ifndef _XENON + if ( gameLocal.inCinematic ) { + return SkipCinematic(); + } +#endif + return false; +} + +/* +============== +idPlayer::HandleObjectiveInput +============== +*/ +void idPlayer::HandleObjectiveInput() { +#ifdef _XENON + if ( gameLocal.inCinematic ) { + return; + } + if ( !objectiveButtonReleased ) { + if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 ) { + objectiveButtonReleased = true; + } + } else { + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 ) { + ToggleObjectives ( ); + g_ObjectiveSystemOpen = objectiveSystemOpen; + objectiveButtonReleased = false; + } + } +#else + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 && !objectiveSystemOpen && !gameLocal.inCinematic ) { + ToggleObjectives ( ); + } else if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 && objectiveSystemOpen ) { + ToggleObjectives ( ); + } else if ( objectiveSystemOpen && gameLocal.inCinematic ) { + ToggleObjectives ( ); + } +#endif +} + +/* +============== +idPlayer::EvaluateControls +============== +*/ +void idPlayer::EvaluateControls( void ) { + // check for respawning + if ( pfl.dead || pfl.objectiveFailed ) { +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( allowedToRespawn ) { + if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) { + forceRespawn = true; + } else if ( gameLocal.time > maxRespawnTime ) { + forceRespawn = true; + } + } + else + { + Spectate(true); + } +// RITUAL END + } + + // in MP, idMultiplayerGame decides spawns + if ( forceRespawn && !gameLocal.isMultiplayer && !g_testDeath.GetBool() ) { + // in single player, we let the session handle restarting the level or loading a game + gameLocal.sessionCommand = "died"; + } + + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + + if( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + oldFlags = usercmd.flags; + + AdjustSpeed(); + + // update the viewangles + UpdateViewAngles(); +} + +/* +============== +idPlayer::AdjustSpeed +============== +*/ +void idPlayer::AdjustSpeed( void ) { + float speed; + + if ( spectating ) { + speed = pm_spectatespeed.GetFloat(); + bobFrac = 0.0f; + } else if ( noclip ) { + speed = pm_noclipspeed.GetFloat(); + bobFrac = 0.0f; + } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) { + bobFrac = 1.0f; + speed = pm_speed.GetFloat(); + } else { + speed = pm_walkspeed.GetFloat(); + bobFrac = 0.0f; + } + + speed *= PowerUpModifier(PMOD_SPEED); + + if ( influenceActive == INFLUENCE_LEVEL3 ) { + speed *= 0.33f; + } + + physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() ); +} + +/* +============== +idPlayer::AdjustBodyAngles +============== +*/ +void idPlayer::AdjustBodyAngles( void ) { + idMat3 lookAxis; + idMat3 legsAxis; + bool blend; + float diff; + float frac; + float upBlend; + float forwardBlend; + float downBlend; + + if ( health < 0 ) { + return; + } + + blend = true; + + if ( !physicsObj.HasGroundContacts() ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else if ( usercmd.forwardmove < 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = false; + } else if ( usercmd.forwardmove > 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = true; + } else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) { + if ( !legsForward ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() ); + } else { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() ); + } + } else if ( usercmd.rightmove != 0 ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else { + legsForward = true; + diff = idMath::Fabs( idealLegsYaw - legsYaw ); + idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw ); + if ( diff < 0.1f ) { + legsYaw = idealLegsYaw; + blend = false; + } + } + + if ( !physicsObj.IsCrouching() ) { + legsForward = true; + } + + oldViewYaw = viewAngles.yaw; + + pfl.turnLeft = false; + pfl.turnRight = false; + if ( idealLegsYaw < -45.0f ) { + idealLegsYaw = 0; + pfl.turnRight = true; + blend = true; + } else if ( idealLegsYaw > 45.0f ) { + idealLegsYaw = 0; + pfl.turnLeft = true; + blend = true; + } + + if ( blend ) { + legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f; + } + legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3(); + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis ); + + // calculate the blending between down, straight, and up + frac = viewAngles.pitch / 90.0f; + if ( frac > 0.0f ) { + downBlend = frac; + forwardBlend = 1.0f - frac; + upBlend = 0.0f; + } else { + downBlend = 0.0f; + forwardBlend = 1.0f + frac; + upBlend = -frac; + } + + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend ); + + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend ); + +} + +/* +============== +idPlayer::InitAASLocation +============== +*/ +void idPlayer::InitAASLocation( void ) { + int i; + int num; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + GetFloorPos( 64.0f, origin ); + + num = gameLocal.NumAAS(); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < aasLocation.Num(); i++ ) { + aasLocation[ i ].areaNum = 0; + aasLocation[ i ].pos = origin; + aas = gameLocal.GetAAS( i ); + if ( aas && aas->GetSettings() ) { + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + } + } +} + +/* +============== +idPlayer::SetAASLocation +============== +*/ +void idPlayer::SetAASLocation( void ) { + int i; + int areaNum; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + if ( !GetFloorPos( 64.0f, origin ) ) { + return; + } + + for( i = 0; i < aasLocation.Num(); i++ ) { + aas = gameLocal.GetAAS( i ); + if ( !aas ) { + continue; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aasLocation[ i ].pos = origin; + aasLocation[ i ].areaNum = areaNum; + } + } +} + +/* +============== +idPlayer::GetAASLocation +============== +*/ +void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + int i; + + if ( aas != NULL ) { + for( i = 0; i < aasLocation.Num(); i++ ) { + if ( aas == gameLocal.GetAAS( i ) ) { + areaNum = aasLocation[ i ].areaNum; + pos = aasLocation[ i ].pos; + return; + } + } + } + + areaNum = 0; + pos = physicsObj.GetOrigin(); +} + +/* +============== +idPlayer::Move +============== +*/ +void idPlayer::Move( void ) { + float newEyeOffset; + idVec3 oldOrigin; + idVec3 oldVelocity; + idVec3 pushVelocity; + + // save old origin and velocity for crashlanding + oldOrigin = physicsObj.GetOrigin(); + oldVelocity = physicsObj.GetLinearVelocity(); + pushVelocity = physicsObj.GetPushedLinearVelocity(); + + // set physics variables + physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() ); + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( health <= 0 ) { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || pfl.objectiveFailed || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else if ( health <= 0 ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + physicsObj.SetPlayerInput( usercmd, viewAngles ); + + // FIXME: physics gets disabled somehow + BecomeActive( TH_PHYSICS ); + + // If the player is dead then only run physics on new + // frames since articulated figures are not synchronized over the network + if ( health <= 0 ) { + if ( gameLocal.isNewFrame ) { + DeathPush(); + RunPhysics(); + } + } else { + RunPhysics(); + } + + // update our last valid AAS location for the AI + if( !gameLocal.isMultiplayer ) { + SetAASLocation(); + } + + if ( spectating ) { + newEyeOffset = 0.0f; + } else if ( health <= 0 ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( IsInVehicle ( ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + if ( spectating ) { + SetEyeHeight( newEyeOffset ); + } else { + // smooth out duck height changes + SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) ); + } + } + + if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + pfl.crouch = false; + pfl.onGround = ( influenceActive == INFLUENCE_LEVEL2 ); + pfl.onLadder = false; + pfl.jump = false; + } else { + pfl.crouch = physicsObj.IsCrouching(); + pfl.onGround = physicsObj.HasGroundContacts(); + pfl.onLadder = physicsObj.OnLadder(); + pfl.jump = physicsObj.HasJumped(); + + // check if we're standing on top of a monster and give a push if we are + idEntity *groundEnt = physicsObj.GetGroundEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( groundEnt && groundEnt->IsType( idAI::GetClassType() ) ) { +// RAVEN END + idVec3 vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() < 0.1f ) { + vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2(); + vel.ToVec2().NormalizeFast(); + vel.ToVec2() *= pm_speed.GetFloat(); + } else { + // give em a push in the direction they're going + vel *= 1.1f; + } + physicsObj.SetLinearVelocity( vel ); + } + } + + if ( pfl.jump ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[2] = 200; + acc->dir[0] = acc->dir[1] = 0; + } + + if ( pfl.onLadder ) { + int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE; + int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE; + + if ( old_rung != new_rung ) { + StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + UpdateIntentDir( ); + + BobCycle( pushVelocity ); + +// RAVEN BEGIN +// abahr: don't crashland while no clipping. + if( !noclip ) { + CrashLand( oldOrigin, oldVelocity ); + } +// RAVEN END +} + +/* +================== +idPlayer::BiasIntentDir + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::BiasIntentDir( idVec3 newIntentDir, float prevBias ) { + if ( !newIntentDir.Compare( vec3_origin ) ) { + if ( intentDir.Compare( vec3_origin ) ) { + //initialize it + intentDir = newIntentDir; + } else { + intentDir = ((intentDir*prevBias)+newIntentDir)/(prevBias+1.0f); + float iDirLen = idMath::ClampFloat(0.0f,1024.0f,intentDir.Normalize()); + intentDir *= iDirLen; + } + } +} + +/* +============== +idPlayer::UpdateIntentDir +============== +*/ +void idPlayer::UpdateIntentDir ( void ) { + idVec3 newIntentDir; + idVec3 viewDir = viewAxis[0]; + viewDir.z = 0; + viewDir.Normalize(); + float prevBias = 199.0f; + if ( intentDir.Compare( vec3_origin ) ) { + newIntentDir = viewDir*50.0f; + } else { + newIntentDir = viewDir*intentDir.Length(); + if ( flashlightOn ) { + //bias them more heavily to looking where I'm looking + prevBias = 19.0f; + } + } + if ( pfl.onGround ) { + idVec3 moveDir = physicsObj.GetLinearVelocity(); + if ( moveDir.x || moveDir.y ) { + // moving, too + moveDir.z = 0; + newIntentDir = moveDir; + prevBias = 39.0f; + } + } + BiasIntentDir( newIntentDir, prevBias ); + if ( ai_debugSquad.GetBool() ) { + idVec4 color = colorCyan; + if ( pfl.weaponFired ) { + color = colorRed; + } + gameRenderWorld->DebugArrow( color, GetPhysics()->GetOrigin(), GetPhysics()->GetOrigin() + intentDir*4.0f, 8, 0 ); + } +} + +/* +============== +idPlayer::UpdateHud +============== +*/ +void idPlayer::UpdateHud( void ) { + if ( !hud ) { + return; + } + + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + + // FIXME: this is here for level ammo balancing to see pct of hits + hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() ); + if ( numProjectilesFired ) { + hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) ); + } else { + hud->SetStateString( "projectilepct", "Hit % 0.0" ); + } + + if ( isLagged && gameLocal.isMultiplayer && gameLocal.localClientNum == entityNumber ) { + hud->SetStateString( "hudLag", "1" ); + } else { + hud->SetStateString( "hudLag", "0" ); + } +} + +/* +============== +idPlayer::UpdateDeathSkin +============== +*/ +void idPlayer::UpdateDeathSkin( bool state_hitch ) { + if ( !( gameLocal.isMultiplayer || g_testDeath.GetBool() ) ) { + return; + } + if ( health <= 0 ) { + if ( !doingDeathSkin && !deathSkinTime ) { + deathSkinTime = gameLocal.time + 1000; + deathStateHitch = state_hitch; + } + + // wait a bit before switching off the content + if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) { + SetCombatContents( false ); + deathClearContentsTime = 0; + } + } else { + renderEntity.noShadow = false; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } + UpdateVisuals(); + doingDeathSkin = false; + deathClearContentsTime = 0; + } +} + +/* +=============== +idPlayer::LoadDeferredModel +=============== +*/ +void idPlayer::LoadDeferredModel( void ) { + if( !modelDecl ) { + gameLocal.Warning( "idPlayer::LoadDeferredModel() - reloadModel without vaid modelDict\n" ); + return; + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + + if( weapon ) { + weapon->NetCatchup(); + } + + if( !modelDecl->skin.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( modelDecl->skin.c_str(), false ); + } + + SetModel( modelDecl->model ); + + if( !modelDecl->head.Length() ) { + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + } else { + SetupHead( modelDecl->head.c_str(), modelDecl->headOffset ); + } + + if( clientHead && health <= 0 ) { + // update death shader for new head + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ]; + clientHead.GetEntity()->GetRenderEntity()->noShadow = renderEntity.noShadow; + } + + if( powerUpSkin != NULL ) { + SetSkin( powerUpSkin ); + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + } +} + +/* +============== +idPlayer::Think + +Called every tic for each player +============== +*/ +void idPlayer::Think( void ) { + renderEntity_t *headRenderEnt; + + if ( talkingNPC ) { + if ( !talkingNPC.IsValid() ) { + talkingNPC = NULL; + } else { + idAI *talkingNPCAI = (idAI*)(talkingNPC.GetEntity()); + if ( !talkingNPCAI ) { + //wtf? + talkingNPC = NULL; + } else if ( talkingNPCAI->talkTarget != this || !talkingNPCAI->IsSpeaking() || DistanceTo( talkingNPCAI ) > 256.0f ) { + //forget about them, okay to talk to someone else now + talkingNPC = NULL; + } + } + } + + if ( !gameLocal.usercmds ) { + return; + } + +#ifdef _XENON + // change the crosshair if it's modified + if ( cursor && weapon && g_crosshairColor.IsModified() ) { + weapon->UpdateCrosshairGUI( cursor ); + cursor->HandleNamedEvent( "weaponChange" ); + g_crosshairColor.ClearModified(); + } +#endif + + // Dont do any thinking if we are in modview + if ( gameLocal.editors & EDITOR_MODVIEW || gameEdit->PlayPlayback() ) { + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + FreeModelDef(); + + if ( weapon ) { + weapon->GetWorldModel()->FreeModelDef(); + } + + if ( head.GetEntity() ) { + head->FreeModelDef(); + } + + if ( clientHead ) { + clientHead->FreeEntityDef(); + } + + return; + } + + if( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + gameEdit->RecordPlayback( usercmd, this ); + + // latch button actions + oldButtons = usercmd.buttons; + + // grab out usercmd + usercmd_t oldCmd = usercmd; + usercmd = gameLocal.usercmds[ entityNumber ]; + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + HandleObjectiveInput(); + if ( objectiveSystemOpen ) { + HandleCheats(); + } else { + ClearCheatState(); + } + + aasSensor->Update(); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + // we need to let the camera think inside of this routine + CalculateRenderView(); + return; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // if this is the very first frame of the map, set the delta view angles + // based on the usercmd angles + if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) { + spawnAnglesSet = true; + SetViewAngles( spawnAngles ); + oldFlags = usercmd.flags; + } + + if ( gameLocal.inCinematic || influenceActive +#ifdef _XENON + || objectiveSystemOpen +#endif + ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + if( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) + { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent ( &EV_Player_ZoomIn ); + } else { + ProcessEvent ( &EV_Player_ZoomOut ); + } + + if ( vehicleController.IsDriving( ) ) { +#ifdef _XENON + usercmdGen->SetSlowJoystick( zoom ? pm_zoomedSlow.GetInteger() : 100 ); +#else + cvarSystem->SetCVarInteger( "pm_isZoomed", zoom ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + } + + } + + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + thinkFlags |= TH_PHYSICS; + RunPhysics(); + + if ( health > 0 ) { + TouchTriggers(); + } + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + LinkCombat(); + } else { + UpdateModel(); + } + + // I don't want to have to add this but if you are in a locked vehicle and you fail your objective, you won't be + // ejected from the vehicle (and I don't think we'd want that even though we do have the option of forcing it..) + // and since you are still in the vehicle, EvaluateControls (which covers the logic below for player usercmds) + // will never get called. + if ( pfl.objectiveFailed ) + { + if ( ( gameLocal.time > minRespawnTime && (usercmd.buttons & BUTTON_ATTACK)) || + gameLocal.time > maxRespawnTime ) + { + gameLocal.sessionCommand = "died"; + } + } + + return; + } + + // log movement changes for weapon bobbing effects + if ( usercmd.forwardmove != oldCmd.forwardmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove; + acc->dir[1] = acc->dir[2] = 0; + } + + if ( usercmd.rightmove != oldCmd.rightmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[1] = usercmd.rightmove - oldCmd.rightmove; + acc->dir[0] = acc->dir[2] = 0; + } + + // freelook centering + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) { + centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 ); + } + + // if we have an active gui, we will unrotate the view angles as + // we turn the mouse movements into gui events + idUserInterface *gui = ActiveGui(); + if ( gui && gui != focusUI ) { + RouteGuiMouse( gui ); + } + + // set the push velocity on the weapon before running the physics + if ( weapon ) { + weapon->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); + } + + EvaluateControls(); + + +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END + + Move(); + + if ( !g_stopTime.GetBool() ) { + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers(); + } + + // not done on clients for various reasons. don't do it on server and save the sound channel for other things + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && dynamicProtectionScale < 1.0f && gameLocal.time - lastDmgTime > 500 ) { + if ( dynamicProtectionScale < 1.0f ) { + dynamicProtectionScale += 0.05f; + } + if ( dynamicProtectionScale > 1.0f ) { + dynamicProtectionScale = 1.0f; + } + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + UpdateLocation(); + + // update player script + UpdateState(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + } + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeroson / camera view + CalculateRenderView(); + + if ( spectating ) { + UpdateSpectating(); + } else if ( health > 0 && !gameLocal.inCinematic ) { + UpdateWeapon(); + } + + UpdateAir(); + + UpdateHud(); + + UpdatePowerUps(); + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + if( entityNumber == gameLocal.localClientNum ) { + renderEntity.suppressLOD = 1; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + } else { + renderEntity.suppressLOD = 0; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 0; + } + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: get first person flashlight into this + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + } + + if ( !g_stopTime.GetBool() ) { + UpdateAnimation(); + + Present(); + + LinkCombat(); + } + + if ( !( thinkFlags & TH_THINK ) ) { + common->DPrintf( "player %d not thinking?\n", entityNumber ); + } + + if ( g_showEnemies.GetBool() ) { + idActor *ent; + int num = 0; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + common->DPrintf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); + num++; + } + common->DPrintf( "%d: enemies\n", num ); + } + + if ( !inBuyZonePrev ) + inBuyZone = false; + + inBuyZonePrev = false; +} + +/* +================= +idPlayer::RouteGuiMouse +================= +*/ +void idPlayer::RouteGuiMouse( idUserInterface *gui ) { + sysEvent_t ev; + const char *command; + + if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) { + ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY ); + command = gui->HandleEvent( &ev, gameLocal.time ); + oldMouseX = usercmd.mx; + oldMouseY = usercmd.my; + } +} + +bool idPlayer::CanZoom( void ) +{ + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + return weapon && weapon->CanZoom(); + } + + return weapon && weapon->CanZoom() && !weapon->IsReloading ( ); +} + +/* +================== +idPlayer::LookAtKiller +================== +*/ +void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) { + idVec3 dir; + + if ( attacker && attacker != this ) { + dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else if ( inflictor && inflictor != this ) { + dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else { + dir = viewAxis[ 0 ]; + } + + idAngles ang( 0, dir.ToYaw(), 0 ); + SetViewAngles( ang ); +} + +/* +============== +idPlayer::Kill +============== +*/ +void idPlayer::Kill( bool delayRespawn, bool nodamage ) { + if ( spectating ) { + SpectateFreeFly( false ); + } else if ( health > 0 ) { + godmode = false; + if ( !g_forceUndying.GetBool() ) { + undying = false; + } + if ( nodamage ) { + ServerSpectate( true ); + forceRespawn = true; + } else { + Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + if ( delayRespawn ) { + forceRespawn = false; + int delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + } + } +} + +/* +================== +idPlayer::Killed +================== +*/ +void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + float delay; + + assert( !gameLocal.isClient ); + + // stop taking knockback once dead + fl.noknockback = true; + if ( health < -999 ) { + health = -999; + } + + if ( pfl.dead ) { + pfl.pain = true; + return; + } + +// squirrel: Mode-agnostic buymenus + if ( gameLocal.isMultiplayer ) { + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + if( gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP ) + { + /// Remove the player's armor + inventory.armor = 0; + + /// Preserve this player's weapons at the state of his death, to be restored on respawn + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = inventory.weapons; + + if( attacker ) + { + idPlayer* killer = NULL; + if ( attacker->IsType( idPlayer::Type ) ) + { + killer = static_cast(attacker); + if( killer == this ) + { + // Killed by self + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingSelf", 0 ); + killer->GiveCash( cashAward ); + } + else if( gameLocal.IsTeamGame() && killer->team == team ) + { + // Killed by teammate + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingTeammate", 0 ); + killer->GiveCash( cashAward ); + } + else + { + // Killed by enemy + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetOpponentKillCashAward(); + killer->GiveCash( cashAward ); + } + } + } + } + } + } +// RITUAL END + + bool noDrop = false; + if ( inflictor + && inflictor->IsType( idTrigger_Hurt::GetClassType() ) + && inflictor->spawnArgs.GetBool( "nodrop" ) ) { + //don't drop weapon or items here, flag auto-returns. + noDrop = true; + } + + if ( !g_testDeath.GetBool() && !gameLocal.isMultiplayer ) { +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); +#else + playerView.Fade( colorBlack, 12000 ); +#endif + } + + + pfl.dead = true; + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + + animator.ClearAllJoints(); + + if ( StartRagdoll() ) { + pm_modelView.SetInteger( 0 ); +#ifdef _XENON + if ( gameLocal.isMultiplayer ) + { + // didn't want to have any chance of affecting multiplayer... + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + else + { + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME_XEN_SP; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME_XEN_SP; + } +#else + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; +#endif + } else { + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + + physicsObj.SetMovementType( PM_DEAD ); + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + + fl.takedamage = true; // can still be gibbed + + if (weapon) { // cnicholson: Fix for crash if player dies while in vehicle + weapon->OwnerDied(); // get rid of weapon + if ( !noDrop ) { + DropWeapon( ); // drop the weapon as an item + } + delete weapon; + } + + weapon = NULL; + currentWeapon = -1; + + if ( !g_testDeath.GetBool() ) { + LookAtKiller( inflictor, attacker ); + } + + if ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) { + idPlayer *killer = NULL; + int methodOfDeath = MAX_WEAPONS + isTelefragged; + + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + + lastKiller = killer; + if ( gameLocal.IsTeamGame() && killer->team == team ) { + // don't worry about team killers + lastKiller = NULL; + } + if ( killer == this ) { + // don't worry about yourself + lastKiller = NULL; + } + + if ( health < -20 || killer->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + gibDeath = true; + gibDir = dir; + gibsLaunched = false; + if( gameLocal.isMultiplayer && gameLocal.isListenServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) { + ClientGib( dir ); + } + } + + if( !isTelefragged ) { + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + } + + if( methodOfDeath == -1 ) { + methodOfDeath = MAX_WEAPONS + isTelefragged; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( attacker->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + if( lastImpulseTime > gameLocal.time && lastImpulsePlayer ) { + killer = lastImpulsePlayer; + } + } + + gameLocal.mpGame.PlayerDeath( this, killer, methodOfDeath ); + } else { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + } + + if ( gameLocal.isMultiplayer && gameLocal.IsFlagGameType() ) { + if ( PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + RemoveClientModel( "mp_ctf_one_flag" ); + } + + if ( PowerUpActive( POWERUP_CTF_STROGGFLAG ) || PowerUpActive( POWERUP_CTF_MARINEFLAG ) || PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + idPlayer* killer = (idPlayer*)attacker; + if ( killer != NULL && killer->IsType( idPlayer::GetClassType() ) && killer != this ) { + if ( killer->team != team ) { + // killing the flag carrier gives killer double cash + killer->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingOpponentFlagCarrier", 0 ) ); + } + } + statManager->FlagDropped( this, attacker ); + } + } + + DropPowerups(); + + ClearPowerUps(); + + UpdateVisuals(); + + // AI sometimes needs to respond to having killed someone. + // Note: Would it be better to make this a virtual funciton of... something? + aiManager.RemoveTeammate( this ); + + isChatting = false; +} + +/* +================= +CalcDamagePoints + +Calculates how many health and armor points will be inflicted, but +doesn't actually do anything with them. This is used to tell when an attack +would have killed the player, possibly allowing a "saving throw" +================= +*/ +void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ) { + int damage; + int armorSave; + float pDmgScale; + + damageDef->GetInt( "damage", "20", damage ); + damage = GetDamageForLocation( damage, location ); + + // optional different damage in team games + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() && damageDef->GetInt( "damage_team" ) ) { + damage = damageDef->GetInt( "damage_team" ); + } + + idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast(attacker) : NULL; + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + switch ( g_skill.GetInteger() ) { + case 0: + damage = ceil(0.80f*(float)damage); + break; + case 2: + damage *= 1.7f; + break; + case 3: + damage *= 3.5f; + break; + default: + //damage *= 1.1f; reverted to 1.0 for default damage... as per Biessman's request. + break; + } + } + } + + damage = ceil(damageScale*(float)damage); + + pDmgScale = damageDef->GetFloat( "playerScale", "1" ); + damage = ceil(pDmgScale*(float)damage); + + // check for completely getting out of the damage + if ( !damageDef->GetBool( "noGod" ) ) { + // check for godmode + if ( godmode ) { + godmodeDamage += damage; + damage = 0; + } + } + + // save some from armor + if ( !damageDef->GetBool( "noArmor" ) ) { + float armor_protection; + + armor_protection = ( gameLocal.isMultiplayer ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat(); + armorSave = ceil( damage * armor_protection ); + if ( armorSave >= inventory.armor ) { + armorSave = inventory.armor; + } + + if ( !damage ) { + armorSave = 0; + } else if ( armorSave >= damage ) { + armorSave = damage - 1; + damage = 1; + } else { + damage -= armorSave; + } + } else { + armorSave = 0; + } + + // check for team damage + if ( gameLocal.IsTeamGame() + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && !damageDef->GetBool( "noTeam" ) + && player + && player != this // you get self damage no matter what + && player->team == team ) { + damage = 0; + } + + *health = damage; + *armor = armorSave; +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space + +damageDef an idDict with all the options for damage effects + +inflictor, attacker, dir, and point can be NULL for environmental effects +============ +*/ +void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + idVec3 kick; + int damage; + int armorSave; + int knockback; + idVec3 damage_from; + float attackerPushScale; + + // RAVEN BEGIN + // twhitaker: difficulty levels + float modifiedDamageScale = damageScale; + + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + modifiedDamageScale *= ( 1.0f + gameLocal.GetDifficultyModifier() ); + } + } + // RAVEN END + + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, modifiedDamageScale, location ); + return; + } + + // damage is only processed on server + if ( gameLocal.isClient ) { + return; + } + + if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) { + // If in vehicle let it know that something is trying to hurt the invisible player + if ( IsInVehicle ( ) ) { + const idDict *damageDict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDict ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + // If the damage def is marked as a hazard then issue a warning to the vehicle + if ( damageDict->GetBool ( "hazard", "0" ) ) { + vehicleController.GetVehicle()->IssueHazardWarning ( ); + } + } + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + // MCG: player doesn't take friendly fire damage, except from self! + if ( !gameLocal.isMultiplayer && attacker != this ) { + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == team ) { + return; + } + } + + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->dict.GetBool( "ignore_player" ) ) { + return; + } + + if ( damageDef->dict.GetBool( "lightning_damage_effect" ) ) + { + lightningEffects = 0; + lightningNextTime = gameLocal.GetTime(); + } + + // We pass in damageScale, because this function calculates a modified damageScale + // based on g_skill, and we don't want to compensate for skill level twice. + CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave ); + + // + // determine knockback + // + damageDef->dict.GetInt( "knockback", "0", knockback ); + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + damageDef->dict.GetInt( "knockback_team", va( "%d", knockback ), knockback ); + } + + knockback *= damageScale; + + if ( knockback != 0 && !fl.noknockback ) { + if ( !gameLocal.isMultiplayer && attacker == this ) { + //In SP, no knockback from your own stuff + knockback = 0; + } else { + if ( attacker != this ) { + attackerPushScale = 1.0f; + } else { + // since default attackerDamageScale is 0.5, default attackerPushScale should be 2 + damageDef->dict.GetFloat( "attackerPushScale", "2", attackerPushScale ); + } + + kick = dir; + + kick.Normalize(); + kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f; + + physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick ); + + // set the timer so that the player can't cancel out the movement immediately + physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) ); + } + } + + if ( damageDef->dict.GetBool( "burn" ) ) { + StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL ); + } else if ( damageDef->dict.GetBool( "no_air" ) ) { + if ( !armorSave && health > 0 ) { + StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + + // give feedback on the player view and audibly when armor is helping + inventory.armor -= armorSave; + + if ( g_debugDamage.GetInteger() ) { + gameLocal.Printf( "client:%i health:%i damage:%i armor:%i\n", + entityNumber, health, damage, armorSave ); + } + + // move the world direction vector to local coordinates + ClientDamageEffects ( damageDef->dict, dir, damage ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + +//RAVEN BEGIN +//asalmon: Xenon needs stats in singleplayer +#ifndef _XENON + if( gameLocal.isMultiplayer ) +#endif + { +//RAVEN END + idEntity* attacker = NULL; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + int methodOfDeath = -1; + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + methodOfDeath = static_cast(inflictor)->methodOfDeath; + attacker = static_cast(inflictor)->GetOwner(); + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + attacker = inflictor; + } + + statManager->Damage( attacker, this, methodOfDeath, damage ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->dict.GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->dict.GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->dict.GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->dict.GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->dict.GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->dict.GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + // do the damage + if ( damage > 0 ) { + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) { + if ( gameLocal.time > lastDmgTime + 500 && dynamicProtectionScale > 0.25f ) { + dynamicProtectionScale -= 0.05f; + } + } + + if ( dynamicProtectionScale > 0.0f ) { + damage *= dynamicProtectionScale; + } + } + + if ( damage < 1 ) { + damage = 1; + } + + int oldHealth = health; + health -= damage; + + GAMELOG_ADD ( va("player%d_damage_taken", entityNumber ), damage ); + GAMELOG_ADD ( va("player%d_damage_%s", entityNumber, damageDefName), damage ); + + // Check undying mode + if ( !damageDef->dict.GetBool( "noGod" ) ) { + if ( undying ) { + if ( health < 1 ) { + health = 1; + } + } + } + + if ( health <= 0 ) { + + if ( health < -999 ) { + health = -999; + } + + isTelefragged = damageDef->dict.GetBool( "telefrag" ); + + lastDmgTime = gameLocal.time; + + Killed( inflictor, attacker, damage, dir, location ); + + if ( oldHealth > 0 ) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::Type ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, &damageDef->dict, pushScale ); + } + } else { + // force a blink + blink_time = 0; + + // let the anim script know we took damage + pfl.pain = Pain( inflictor, attacker, damage, dir, location ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + } + } else { + // don't accumulate impulses + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + } + + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = location; +} + +/* +===================== +idPlayer::CanPlayImpactEffect +===================== +*/ +bool idPlayer::CanPlayImpactEffect( idEntity* attacker, idEntity* target ) +{ + if ( !gameLocal.isMultiplayer && attacker->IsType( idAI::GetClassType()) && target->IsType( idPlayer::GetClassType())) + { + // don't display impact effects when marines on our team shoot us... + idPlayer *player = static_cast( target ); + idAI *ai = static_cast( attacker ); + if ( player->team == ai->team ) + { + return false; + } + } + + return idAFEntity_Base::CanPlayImpactEffect( attacker, target ); +} + +/* +===================== +idPlayer::AddDamageEffect +===================== +*/ +void idPlayer::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if( gameLocal.isMultiplayer ) { + if( ! cvarSystem->GetCVarBool("si_teamDamage") && inflictor && inflictor->IsType( idPlayer::GetClassType() ) && gameLocal.IsTeamGame() && ((idPlayer*)inflictor)->team == team ) { + return; + } + } + + idActor::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +=========== +idPlayer::Teleport +============ +*/ +void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + idVec3 org; + + if ( weapon ) { + weapon->LowerWeapon(); + } + + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + if ( !gameLocal.isMultiplayer && GetFloorPos( 16.0f, org ) ) { + SetOrigin( org ); + } + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + GetPhysics()->SetLinearVelocity( vec3_origin ); + SetViewAngles( angles ); + + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + + if ( gameLocal.isMultiplayer ) { + playerView.Flash( colorWhite, 140 ); + } + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + UpdateVisuals(); + + teleportEntity = destination; + + if ( !gameLocal.isClient && !noclip ) { + if ( gameLocal.isMultiplayer ) { + // kill anything at the new position or mark for kill depending on immediate or delayed teleport + gameLocal.KillBox( this, destination != NULL ); + } else { + // kill anything at the new position + gameLocal.KillBox( this, true ); + } + } +} + +/* +==================== +idPlayer::SetPrivateCameraView +==================== +*/ +void idPlayer::SetPrivateCameraView( idCamera *camView ) { + privateCameraView = camView; + if ( camView ) { + StopFiring(); + Hide(); + } else { + if ( !spectating ) { + Show(); + } + } +} + +/* +==================== +idPlayer::DefaultFov + +Returns the base FOV +==================== +*/ +float idPlayer::DefaultFov( void ) const { + float fov; + + fov = g_fov.GetFloat(); + if ( gameLocal.isMultiplayer ) { + if ( fov < 90.0f ) { + return 90.0f; + } else if ( fov > 175.0f ) { + return 175.0f; + } + } + + return fov; +} + +/* +==================== +idPlayer::CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +float idPlayer::CalcFov( bool honorZoom ) { + float fov; + + if ( fxFov ) { + return DefaultFov() + 10.0f + idMath::Cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f; + } + + if ( influenceFov ) { + return influenceFov; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } else { + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } + + // bound normal viewsize + if ( fov < 1 ) { + fov = 1; + } else if ( fov > 179 ) { + fov = 179; + } + + return fov; +} + +/* +============== +idPlayer::GunTurningOffset + +generate a rotational offset for the gun based on the view angle +history in loggedViewAngles +============== +*/ +idAngles idPlayer::GunTurningOffset( void ) { + idAngles a; + + a.Zero(); + + if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) { + return a; + } + + idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles av, base; + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale, weaponAngleOffsetMax; + + weapon->GetAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax ); + + av = current; + + // calcualte this so the wrap arounds work properly + for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) { + idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles delta = a2 - current; + + if ( delta[1] > 180 ) { + delta[1] -= 360; + } else if ( delta[1] < -180 ) { + delta[1] += 360; + } + + av += delta * ( 1.0f / weaponAngleOffsetAverages ); + } + + a = ( av - current ) * weaponAngleOffsetScale; + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( a[i] < -weaponAngleOffsetMax ) { + a[i] = -weaponAngleOffsetMax; + } else if ( a[i] > weaponAngleOffsetMax ) { + a[i] = weaponAngleOffsetMax; + } + } + + return a; +} + +/* +============== +idPlayer::GunAcceleratingOffset + +generate a positional offset for the gun based on the movement +history in loggedAccelerations +============== +*/ +idVec3 idPlayer::GunAcceleratingOffset( void ) { + idVec3 ofs; + float weaponOffsetTime; + float weaponOffsetScale; + + ofs.Zero(); + + weapon->GetTimeOffsets( &weaponOffsetTime, &weaponOffsetScale ); + + int stop = currentLoggedAccel - NUM_LOGGED_ACCELS; + if ( stop < 0 ) { + stop = 0; + } + for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) { + loggedAccel_t *acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)]; + + float f; + float t = gameLocal.time - acc->time; + if ( t >= weaponOffsetTime ) { + break; // remainder are too old to care about + } + + f = t / weaponOffsetTime; + f = ( idMath::Cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f; + ofs += f * weaponOffsetScale * acc->dir; + } + + return ofs; +} + +/* +============== +idPlayer::CalculateViewWeaponPos + +Calculate the bobbing position of the view weapon +============== +*/ +void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) { + float scale; + float fracsin; + idAngles angles; + int delta; + + // CalculateRenderView must have been called first + const idVec3 &viewOrigin = firstPersonViewOrigin; + const idMat3 &viewAxis = firstPersonViewAxis; + + // these cvars are just for hand tweaking before moving a value to the weapon def + idVec3 gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() ); + gunpos += weapon->GetViewModelOffset ( ); + + // as the player changes direction, the gun will take a small lag + idVec3 gunOfs = GunAcceleratingOffset(); + origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis; + + // on odd legs, invert some angles + if ( noclip || 1 ) { + scale = 0; + } else if ( bobCycle & 128 ) { + scale = -xyspeed; + } else { + scale = xyspeed; + } + + // gun angles from bobbing + angles.roll = scale * bobfracsin * 0.005f + g_gun_roll.GetFloat(); + angles.yaw = scale * bobfracsin * 0.01f + g_gun_yaw.GetFloat(); + angles.pitch = xyspeed * bobfracsin * 0.005f + g_gun_pitch.GetFloat(); + + angles += weapon->GetViewModelAngles(); + + // gun angles from turning + if ( gameLocal.isMultiplayer ) { + idAngles offset = GunTurningOffset(); + offset *= g_mpWeaponAngleScale.GetFloat(); + angles += offset; + } else { + angles += GunTurningOffset(); + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + +// RAVEN BEGIN +// abahr: when looking down, really large deflections cause back of weapons to show + float landChangeFrac = idMath::Lerp( 0.25f, 0.05f, viewAngles.ToForward() * gravity); +// RAVEN ABAHR + + // drop the weapon when landing after a jump / fall + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { +// RAVEN BEGIN +// abahr: changed to use landChangeFrac + origin -= gravity * ( landChange*landChangeFrac * delta / LAND_DEFLECT_TIME ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin -= gravity * ( landChange*landChangeFrac * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME ); +// RAVEN END + } + + // speed sensitive idle drift + if ( !noclip ) { + scale = xyspeed * 0.5f + 40.0f; + fracsin = scale * idMath::Sin( MS2SEC( gameLocal.time ) ) * 0.01f; + angles.roll += fracsin; + angles.yaw += fracsin; + angles.pitch += fracsin; + } + + axis = angles.ToMat3() * viewAxis; +} + +/* +=============== +idPlayer::OffsetThirdPersonVehicleView +=============== +*/ +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world +void idPlayer::OffsetThirdPersonVehicleView( bool clip ) { +// RAVEN END + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + idVec3 origin; + idAngles angles, angles2; + idEntity* vehicle; + + assert ( IsInVehicle ( ) ); + + vehicle = vehicleController.GetVehicle(); + + origin = vehicle->GetRenderEntity()->origin; + angles = vehicle->GetRenderEntity()->axis.ToAngles(); + + angles.yaw += pm_thirdPersonAngle.GetFloat(); + angles.pitch += 25.0f; + +// angles.pitch += viewAngles.pitch; +// angles.yaw += viewAngles.yaw; + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * 8.0f; +// RAVEN END + + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + float speed = vehicle->GetPhysics()->GetLinearVelocity() * + vehicle->GetPhysics()->GetAxis()[0]; + + speed = idMath::Fabs( speed ); + speed *= pm_vehicleCameraSpeedScale.GetFloat(); + if( speed > pm_vehicleCameraScaleMax.GetFloat() ) + { + speed = pm_vehicleCameraScaleMax.GetFloat(); + } + + vehicleCameraDist += ( MS2SEC( gameLocal.GetMSec() ) * ( ( pm_vehicleCameraMinDist.GetFloat() + speed ) - vehicleCameraDist ) ); + + view -= vehicleCameraDist * renderView->viewaxis[ 0 ]; + +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + if ( clip ) { + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + const idVec3 clip_mins( -4.0f, -4.0f, -4.0f ); + const idVec3 clip_maxs( 4.0f, 4.0f, 4.0f ); + const idBounds clip_bounds( clip_mins, clip_maxs ); + idClipModel clipBounds( clip_bounds );// We clip when using a tram gun in the tram car +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + if ( trace.fraction != 1.0 ) + { + view = trace.endpos; +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32; + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + view = trace.endpos; + } + } +// RAVEN END + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) + { + focusDist = 1; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::OffsetThirdPersonView +=============== +*/ +void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + float forwardScale, sideScale; + idVec3 origin; + idAngles angles; + idMat3 axis; + idBounds bounds; + + angles = viewAngles; + GetViewPos( origin, axis ); + + if ( angle ) { + angles.pitch = 0.0f; + } + + if ( angles.pitch > 45.0f ) { + angles.pitch = 45.0f; // don't go too far overhead + } + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + focusPoint.z += height; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * (8.0f + height); +// RAVEN END + + angles.pitch *= 0.5f; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale ); + view -= range * forwardScale * renderView->viewaxis[ 0 ]; + view += range * sideScale * renderView->viewaxis[ 1 ]; + + if ( clip ) { + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + if ( trace.fraction != 1.0f ) { + view = trace.endpos; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32.0f; +// RAVEN END + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + view = trace.endpos; + } + } + + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1.0f ) { + focusDist = 1.0f; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + angles.yaw -= angle; + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::GetEyePosition +=============== +*/ +idVec3 idPlayer::GetEyePosition( void ) const { + idVec3 org; + + if ( WantSmoothing() ) { + org = predictedOrigin; + } else { + org = GetPhysics()->GetOrigin(); + } + + return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); +} + +/* +=============== +idPlayer::GetViewPos +=============== +*/ +void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + idAngles angles; + + // if dead, fix the angle and don't add any kick + if ( health <= 0 ) { + angles.yaw = viewAngles.yaw; + angles.roll = 40; + angles.pitch = -15; + axis = angles.ToMat3(); + origin = GetEyePosition(); + } else if ( IsInVehicle ( ) ) { + vehicleController.GetEyePosition ( origin, axis ); + + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + + origin += shakeOffset; + axis = (shakeAngleOffset + playerView.AngleOffset()).ToMat3() * axis; + } else { + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + origin = GetEyePosition() + viewBob + shakeOffset; + angles = viewAngles + viewBobAngles + shakeAngleOffset + playerView.AngleOffset(); + + axis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + // adjust the origin based on the camera nodal distance (eye distance from neck) + origin += physicsObj.GetGravityNormal() * g_viewNodalZ.GetFloat(); + origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat(); + } +} + +/* +=============== +idPlayer::CalculateFirstPersonView +=============== +*/ +void idPlayer::CalculateFirstPersonView( void ) { + if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) { + // Displays the view from the point of view of the "camera" joint in the player model + + idMat3 axis; + idVec3 origin; + idAngles ang; + + ang = viewBobAngles + playerView.AngleOffset(); + ang.yaw += viewAxis[ 0 ].ToYaw(); + + jointHandle_t joint = animator.GetJointHandle( "camera" ); + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob; + firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis(); + } else { + // offset for local bobbing and kicks + GetViewPos( firstPersonViewOrigin, firstPersonViewAxis ); + } +} + +/* +================== +idPlayer::GetRenderView + +Returns the renderView that was calculated for this tic +================== +*/ +renderView_t *idPlayer::GetRenderView( void ) { + return renderView; +} + +/* +================== +idPlayer::SmoothenRenderView + +various situations where the view angles need smoothing: + +demo replay: + On a slow client with low fps multiple game frames are run in quick succession + inbetween rendered frames. As a result the usercmds are not recorded at fixed + time intervals but in small bursts. This routine interpolates the view angles + based on the real time at which the usercmds were recorded to make a demo + recorded on a slow client play back smoothly on a fast client. + +spectate follow? +================== +*/ +void idPlayer::SmoothenRenderView( bool firstPerson ) { + int d1, d2; + idAngles angles, anglesDelta, newAngles; + + if ( gameLocal.GetDemoState() == DEMO_PLAYING ) { + + d1 = usercmd.gameTime - demoViewAngleTime; + if ( d1 < 0 ) { + return; + } + d2 = usercmd.realTime - demoViewAngleTime; + if ( d2 <= 0 ) { + return; + } + if ( d1 >= d2 ) { + return; + } + + angles = renderView->viewaxis.ToAngles(); + + anglesDelta = angles - demoViewAngles; + anglesDelta.Normalize180(); + newAngles = demoViewAngles + ( (float) d1 / d2 ) * anglesDelta; + renderView->viewaxis = newAngles.ToMat3(); + + if ( usercmd.gameTime + gameLocal.msec > usercmd.realTime ) { + demoViewAngleTime = usercmd.realTime; + demoViewAngles = angles; + } + + if ( firstPerson ) { + // make sure the view weapon moves smoothly + firstPersonViewAxis = renderView->viewaxis; + } + } +} + +/* +================== +idPlayer::CalculateRenderView + +create the renderView for the current tic +================== +*/ +void idPlayer::CalculateRenderView( void ) { + int i; + float range; + + if ( !renderView ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + // copy global shader parms + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + renderView->time = gameLocal.time; + + // calculate size of 3D view + renderView->x = 0; + renderView->y = 0; + renderView->width = SCREEN_WIDTH; + renderView->height = SCREEN_HEIGHT; + renderView->viewID = 0; + + // check if we should be drawing from a camera's POV + if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) { + // get origin, axis, and fov + if ( privateCameraView ) { + privateCameraView->GetViewParms( renderView ); + } else { + gameLocal.GetCamera()->GetViewParms( renderView ); + } + } else { + bool cameraIsSet = false; + + // First try out any camera views that can possibly fail. + if( !cameraIsSet ){ + if ( g_stopTime.GetBool() ) { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + + if ( !pm_thirdPerson.GetBool() ) { + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } else if ( pm_thirdPerson.GetBool() && IsInVehicle ( ) ) { +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + OffsetThirdPersonVehicleView( pm_thirdPersonClip.GetBool() ); +// RAVEN END + SmoothenRenderView( false ); + } else if ( pm_thirdPerson.GetBool() ) { + OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() ); + SmoothenRenderView( false ); + } else if ( pm_thirdPersonDeath.GetBool() ) { + range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f; + OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false ); + SmoothenRenderView( false ); + } else { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } + + // field of view + gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y ); + + } + + if ( renderView->fov_y == 0 ) { + common->Error( "renderView->fov_y == 0" ); + } + + if ( g_showviewpos.GetBool() ) { + gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() ); + } +} + +/* +================== +idPlayer::Event_EnableTarget +================== +*/ +void idPlayer::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +================== +idPlayer::Event_DisableTarget +================== +*/ +void idPlayer::Event_DisableTarget ( void ) { + fl.notarget = true; +} + +/* +================== +idPlayer::Event_GetViewPos +================== +*/ +void idPlayer::Event_GetViewPos( void ) { + idVec3 viewOrigin; + idMat3 viewAxis; + + GetViewPos(viewOrigin, viewAxis); + idThread::ReturnVector( viewOrigin ); +} + +/* +================== +idPlayer::Event_FinishHearingLoss +================== +*/ +void idPlayer::Event_FinishHearingLoss ( float fadeTime ) { + if ( fadeTime <= 0.0f ) { + StopSound ( SND_CHANNEL_DEMONIC, false ); + pfl.hearingLoss = false; + } else { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, 0.0f ); + } +} + +// RAVEN BEGIN +// twhitaker: added the event +/* +============= +idPlayer::Event_ApplyImpulse +============= +*/ +void idPlayer::Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ) { + GetPhysics()->ApplyImpulse( 0, point, impulse ); +} + +// mekberg: added Event_EnableObjectives +/* +============= +idPlayer::Event_EnableObjectives +============= +*/ +void idPlayer::Event_EnableObjectives ( void ) { + objectivesEnabled = true; +} + +// mekberg: added Event_DisableObjectives +/* +============= +idPlayer::Event_DisableObjectives +============= +*/ +void idPlayer::Event_DisableObjectives ( void ) { + // if it's open, it should be closed + if (objectiveSystemOpen ) { + ToggleObjectives(); + } + objectivesEnabled = false; +} + +// mekberg: added Event_AllowNewObjectives +void idPlayer::Event_AllowNewObjectives ( void ) { + showNewObjectives = true; +} + +// mekberg: added sethealth +/* +============= +idPlayer::Event_SetHealth +============= +*/ +void idPlayer::Event_SetHealth( float newHealth ) { + health = idMath::ClampInt( 1 , inventory.maxHealth, newHealth ); +} +/* +============= +idPlayer::Event_SetArmor +============= +*/ +void idPlayer::Event_SetArmor( float newArmor ) { + inventory.armor = idMath::ClampInt( 0 , inventory.maxarmor, newArmor ); +} + +/* +============= +idPlayer::Event_SetExtraProjPassEntity +============= +*/ +void idPlayer::Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ) { + extraProjPassEntity = _extraProjPassEntity; +} +// RAVEN END + +/* +============= +idPlayer::AddProjectilesFired +============= +*/ +void idPlayer::AddProjectilesFired( int count ) { + numProjectilesFired += count; +} + +/* +============= +idPlayer::AddProjectileHites +============= +*/ +void idPlayer::AddProjectileHits( int count ) { + numProjectileHits += count; +} + +/* +============= +idPlayer::SetLastHitTime +============= +*/ +void idPlayer::SetLastHitTime( int time, bool armorHit ) { + if ( !time ) { + // level start and inits + return; + } + + idUserInterface *cursor = idPlayer::cursor; + bool spectated = false; + if ( gameLocal.GetLocalPlayer() ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + spectated = true; + } + } else if ( gameLocal.IsServerDemo() ) { + // server netdemo + assert( gameLocal.GetDemoState() == DEMO_PLAYING ); + if ( gameLocal.GetDemoFollowClient() == entityNumber ) { + cursor = gameLocal.GetDemoCursor(); + spectated = true; + } + } + + if ( lastHitTime != time ) { + if ( cursor ) { + cursor->HandleNamedEvent( "weaponHit" ); + } + if ( gameLocal.isMultiplayer ) { + // spectated so we get sounds for a client we're following + // localClientNum check so listen server plays only for local player + if ( spectated || gameLocal.localClientNum == entityNumber ) { + const char* sound; + + if ( armorHit ) { + if ( spawnArgs.GetString ( "snd_armorHit", "", &sound ) ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, sound ); + } + } else { + if ( spawnArgs.GetString ( "snd_weaponHit", "", &sound ) ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, sound ); + } + } + } + + if ( aimClientNum != -1 ) { + if ( mphud ) { + mphud->HandleNamedEvent( "aim_hit" ); + } + } + + } + lastHitTime = time; + lastArmorHit = armorHit; + lastHitToggle ^= 1; + } +} + +/* +============= +idPlayer::SetInfluenceLevel +============= +*/ +void idPlayer::SetInfluenceLevel( int level ) { + if ( level != influenceActive ) { + if ( level ) { + for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + ent->PostEventMS( &EV_Remove, 0 ); + } + } + if ( weaponEnabled && weapon ) { + weapon->EnterCinematic(); + } + } else { + physicsObj.SetLinearVelocity( vec3_origin ); + if ( weaponEnabled && weapon ) { + weapon->ExitCinematic(); + } + } + influenceActive = level; + } +} + +/* +============= +idPlayer::SetInfluenceView +============= +*/ +void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) { + influenceMaterial = NULL; + influenceEntity = NULL; + influenceSkin = NULL; + if ( mtr && *mtr ) { + influenceMaterial = declManager->FindMaterial( mtr ); + } + if ( skinname && *skinname ) { + influenceSkin = declManager->FindSkin( skinname ); + if ( head.GetEntity() ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + UpdateVisuals(); + } + influenceRadius = radius; + if ( radius > 0.0f ) { + influenceEntity = ent; + } +} + +/* +============= +idPlayer::SetInfluenceFov +============= +*/ +void idPlayer::SetInfluenceFov( float fov ) { + influenceFov = fov; +} + +/* +================ +idPlayer::OnLadder +================ +*/ +bool idPlayer::OnLadder( void ) const { + return physicsObj.OnLadder(); +} + +/* +================== +idPlayer::Event_GetButtons +================== +*/ +void idPlayer::Event_GetButtons( void ) { + idThread::ReturnInt( usercmd.buttons ); +} + +/* +================== +idPlayer::Event_GetMove +================== +*/ +void idPlayer::Event_GetMove( void ) { + idVec3 move( usercmd.forwardmove, usercmd.rightmove, usercmd.upmove ); + idThread::ReturnVector( move ); +} + +/* +================ +idPlayer::Event_GetViewAngles +================ +*/ +void idPlayer::Event_GetViewAngles( void ) { + idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) ); +} + +/* +================ +idPlayer::Event_SetViewAngles +================ +*/ +void idPlayer::Event_SetViewAngles( const idVec3 & vec ) { + idAngles ang; + ang.Set( vec.z, vec.y, vec.x ); + SetViewAngles( ang ); +} + +/* +================== +idPlayer::Event_StopFxFov +================== +*/ +void idPlayer::Event_StopFxFov( void ) { + fxFov = false; +} + +/* +================== +idPlayer::StartFxFov +================== +*/ +void idPlayer::StartFxFov( float duration ) { + fxFov = true; + PostEventSec( &EV_Player_StopFxFov, duration ); +} + +/* +================== +idPlayer::Event_EnableWeapon +================== +*/ +void idPlayer::Event_EnableWeapon( void ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", 0 ); + Give( "weapon", spawnArgs.GetString( va( "def_weapon%d", 0 ) ) ); + hiddenWeapon = false; + weaponEnabled = true; + if ( weapon ) { + weapon->ExitCinematic(); + } + ShowCrosshair(); +} + +/* +================== +idPlayer::Event_DisableWeapon +================== +*/ +void idPlayer::Event_DisableWeapon( void ) { + hiddenWeapon = true; + weaponEnabled = false; + if ( weapon ) { + weapon->EnterCinematic(); + } + HideCrosshair(); +} + +/* +================== +idPlayer::Event_GetCurrentWeapon +================== +*/ +void idPlayer::Event_GetCurrentWeapon( void ) { + if ( currentWeapon >= 0 ) { + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ) ); + } else { + idThread::ReturnString( "" ); + } +} + +/* +================== +idPlayer::Event_GetPreviousWeapon +================== +*/ +void idPlayer::Event_GetPreviousWeapon( void ) { + if ( previousWeapon >= 0 ) { + int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon; + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", pw) ) ); + } else { + idThread::ReturnString( "def_weapon0" ); + } +} + +/* +================== +idPlayer::Event_SelectWeapon +================== +*/ +void idPlayer::Event_SelectWeapon( const char *weaponName ) { + int i; + int weaponNum; + + if ( gameLocal.isClient ) { + gameLocal.Warning( "Cannot switch weapons from script in multiplayer" ); + return; + } + + weaponNum = -1; + for( i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + + if ( !inventory.HasAmmo( weap ) ) { + return; + } + weaponNum = i; + break; + } + } + } + + if ( weaponNum < 0 ) { + gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName ); + return; + } + + hiddenWeapon = false; + idealWeapon = weaponNum; + + UpdateHudWeapon(); +} + +/* +================== +idPlayer::Event_GetAmmoData +================== +*/ +void idPlayer::Event_GetAmmoData( const char *ammoClass ) { + + idVec3 weaponAmmo; + + //ammo vector is this: current ammo count, max ammo count, and % + weaponAmmo.x = inventory.ammo[ inventory.AmmoIndexForAmmoClass( ammoClass) ]; + weaponAmmo.y = inventory.MaxAmmoForAmmoClass( this, ammoClass ); + + if( weaponAmmo.y == 0) + weaponAmmo.z = 0; + else + weaponAmmo.z = (float)(weaponAmmo.x / weaponAmmo.y); + + idThread::ReturnVector( weaponAmmo); +} + +/* +================== +idPlayer::Event_RefillAmmo +================== +*/ +void idPlayer::Event_RefillAmmo( void ) { + int a; + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + a = inventory.AmmoIndexForWeaponIndex( i ); + inventory.ammo[ a ] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex( a ) ); + } + } + } +} + +/* +================== +idPlayer::Event_AllowFallDamage +================== +*/ +void idPlayer::Event_AllowFallDamage( int toggle ) { + if( toggle ) { + pfl.noFallingDamage = false; + } else { + pfl.noFallingDamage = true; + } + +} +/* +================== +idPlayer::Event_GetWeaponEntity +================== +*/ +void idPlayer::Event_GetWeaponEntity( void ) { + idThread::ReturnEntity( weaponViewModel ); +} + +/* +================== +idPlayer::Event_HideDatabaseEntry +================== +*/ +void idPlayer::Event_HideDatabaseEntry ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "closeDatabaseEntry" ); + } +} + +/* +================== +idPlayer::Event_ZoomIn +================== +*/ +void idPlayer::Event_ZoomIn ( void ) { + float currentFov; + float t; + + if ( zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming in while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else if ( weapon && this->weaponEnabled ) { + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::Event_ZoomOut +================== +*/ +void idPlayer::Event_ZoomOut ( void ) { + float t; + float currentFov; + + if ( !zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound ( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else { + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound ( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::TeleportDeath +================== +*/ +void idPlayer::TeleportDeath( int killer ) { + teleportKiller = killer; +} + +/* +================== +idPlayer::Event_ExitTeleporter +================== +*/ +void idPlayer::Event_ExitTeleporter( void ) { + idEntity *exitEnt; + float pushVel; + + // verify and setup + exitEnt = teleportEntity; + if ( !exitEnt ) { + common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber ); + return; + } + + pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" ); + + if ( gameLocal.isServer ) { + ServerSendInstanceEvent( EVENT_EXIT_TELEPORTER, NULL, false, -1 ); + } + + SetPrivateCameraView( NULL ); + // setup origin and push according to the exit target + SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() ); + physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel ); + physicsObj.ClearPushedVelocity( ); + // teleport fx + playerView.Flash( colorWhite, 120 ); + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + UpdateVisuals(); + + gameLocal.PlayEffect( spawnArgs, "fx_teleport", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + + StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( teleportKiller != -1 ) { + // we got killed while being teleported + Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + teleportKiller = -1; + } else { + // kill anything that would have waited at teleport exit + gameLocal.KillBox( this ); + } + teleportEntity = NULL; +} + +/* +=============== +idPlayer::Event_DamageOverTimeEffect +=============== +*/ +void idPlayer::Event_DamageOverTimeEffect( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), GetWeaponViewModel(), interval ); + RV_POP_HEAP(); + + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +/* +=============== +idPlayer::LocalClientPredictionThink +=============== +*/ +void idPlayer::LocalClientPredictionThink( void ) { + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + + if ( IsInVehicle( ) ) { + vehicleController.SetInput( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } + + AdjustSpeed(); + + UpdateViewAngles(); + +/* +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END +*/ + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + Move(); + + // predict collisions with items + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers( &idItem::GetClassType() ); + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + UpdateHud(); + + if ( gameLocal.isNewFrame ) { + UpdatePowerUps(); + } + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +=============== +idPlayer::NonLocalClientPredictionThink +=============== +*/ +#define LIMITED_PREDICTION 1 + +void idPlayer::NonLocalClientPredictionThink( void ) { + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + //jshepard: added this to make sure clients can see other clients and the host switching weapons + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + +#if !LIMITED_PREDICTION + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } +#endif + + AdjustSpeed(); + + UpdateViewAngles(); + + if ( gameLocal.isLastPredictFrame && jumpDuringHitch ) { + // only play sound if still alive + if ( health > 0 ) { + StartSound( "snd_jump", (s_channelType)FC_SOUND, 0, false, NULL ); + } + jumpDuringHitch = false; + } + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + // NOTE: only running on new frames causes prediction errors even when the input does not change! + if ( gameLocal.isNewFrame ) { + Move(); + } else { + PredictionErrorDecay(); + } + } + +#if defined( _XENON ) || !LIMITED_PREDICTION + // update GUIs, Items, and character interactions + UpdateFocus(); +#endif + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + if ( gameLocal.isLastPredictFrame ) { + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + UpdateHud(); + UpdatePowerUps(); + } + +//#if !LIMITED_PREDICTION + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); +//#endif + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +================ +idPlayer::ClientPredictionThink +================ +*/ +void idPlayer::ClientPredictionThink( void ) { + + if ( doInitWeapon ) { + InitWeapon(); + } + + // common code for both the local & non local clients + if ( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + if ( entityNumber == gameLocal.GetDemoFollowClient() ) { + LocalClientPredictionThink(); + return; + } + + if ( entityNumber == gameLocal.localClientNum ) { + LocalClientPredictionThink(); + return; + } + + assert( gameLocal.localClientNum >= 0 ); + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p && p->spectating && p->spectator == entityNumber ) { + LocalClientPredictionThink(); + return; + } + + NonLocalClientPredictionThink(); +} + +/* +================ +idPlayer::GetMasterPosition +================ +*/ +bool idPlayer::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + if( !IsInVehicle() ) { + return idActor::GetMasterPosition( masterOrigin, masterAxis ); + } + + vehicleController.GetDriverPosition( masterOrigin, masterAxis ); + return true; +} + +/* +=============== +idPlayer::PredictionErrorDecay +=============== +*/ +void idPlayer::PredictionErrorDecay( void ) { + if ( predictedUpdated ) { + return; + } + + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + predictedOrigin = renderOrigin; + return; + } + + if ( gameLocal.framenum >= predictedFrame ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + + if ( gameLocal.framenum == predictedFrame ) { + + predictionOriginError = predictedOrigin - renderOrigin; + predictionAnglesError = predictedAngles - viewAngles; + predictionAnglesError.Normalize180(); + predictionErrorTime = gameLocal.time; + + if ( net_showPredictionError.GetInteger() == entityNumber ) { + renderSystem->DebugGraph( predictionOriginError.Length(), 0.0f, 100.0f, colorGreen ); + renderSystem->DebugGraph( predictionAnglesError.Length(), 0.0f, 180.0f, colorBlue ); + } + } + + int t = gameLocal.time - predictionErrorTime; + float f = ( net_predictionErrorDecay.GetFloat() - t ) / net_predictionErrorDecay.GetFloat(); + if ( f > 0.0f && f < 1.0f ) { + predictedOrigin = renderOrigin + f * predictionOriginError; + predictedAngles = viewAngles + f * predictionAnglesError; + predictedAngles.Normalize180(); + } else { + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + } + + predictedFrame = gameLocal.framenum; + + } + + viewAngles = predictedAngles; + // adjust them now so they are right for the bound objects ( head and weapon ) + AdjustBodyAngles(); + + predictedUpdated = true; +} + +/* +=============== +idPlayer::WantSmoothing +=============== +*/ +bool idPlayer::WantSmoothing( void ) const { + if ( !gameLocal.isClient ) { + return false; + } + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + return false; + } + return true; +} + +/* +================ +idPlayer::GetPhysicsToVisualTransform +================ +*/ +bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + + if ( vehicleController.IsDriving() ) { + vehicleController.GetDriverPosition( origin, axis ); + origin.Zero(); + return true; + } + + PredictionErrorDecay(); + + // smoothen the rendered origin and angles of other clients + if ( gameLocal.framenum >= predictedFrame && WantSmoothing() ) { + + axis = idAngles( 0.0f, predictedAngles.yaw, 0.0f ).ToMat3(); + origin = ( predictedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose(); + + } else { + + axis = viewAxis; + origin = modelOffset; + + } + + return true; +} + +/* +================ +idPlayer::GetPhysicsToSoundTransform +================ +*/ +bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + idCamera *camera; + + if ( privateCameraView ) { + camera = privateCameraView; + } else { + camera = gameLocal.GetCamera(); + } + + if ( camera ) { + renderView_t view; + + memset( &view, 0, sizeof( view ) ); + camera->GetViewParms( &view ); + origin = view.vieworg; + axis = view.viewaxis; + return true; + } else { + return idActor::GetPhysicsToSoundTransform( origin, axis ); + } +} + +/* +================ +idPlayer::WriteToSnapshot +================ +*/ +void idPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + msg.WriteShort( health ); + msg.WriteByte( inventory.armor ); + msg.WriteBits( lastDamageDef, gameLocal.entityDefBits ); + msg.WriteDir( lastDamageDir, 9 ); + msg.WriteShort( lastDamageLocation ); + msg.WriteBits( idealWeapon, idMath::BitsForInteger( MAX_WEAPONS ) ); + msg.WriteBits( inventory.weapons, MAX_WEAPONS ); + msg.WriteBits( weaponViewModel.GetSpawnId(), 32 ); + msg.WriteBits( weaponWorldModel.GetSpawnId(), 32 ); +// msg.WriteBits( head.GetSpawnId(), 32 ); + msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteBits( lastHitToggle, 1 ); + msg.WriteBits( lastArmorHit, 1 ); + msg.WriteBits( weaponGone, 1 ); + msg.WriteBits( isLagged, 1 ); + msg.WriteBits( isChatting, 1 ); + msg.WriteLong( connectTime ); + msg.WriteByte( lastKiller ? lastKiller->entityNumber : 255 ); + + // vehicleController.WriteToSnapshot( msg ); + + if ( weapon ) { + msg.WriteBits( 1, 1 ); + weapon->WriteToSnapshot( msg ); + } else { + msg.WriteBits( 0, 1 ); + } +//RITUAL BEGIN + msg.WriteBits( inBuyZone, 1 ); + msg.WriteLong( (int)buyMenuCash ); +//RITUAL END +} + +/* +================ +idPlayer::ReadFromSnapshot +================ +*/ +void idPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, oldHealth, newIdealWeapon, weaponSpawnId, weaponWorldSpawnId; + bool newHitToggle, stateHitch, newHitArmor; + int lastKillerEntity; + bool proto69 = ( gameLocal.GetCurrentDemoProtocol() == 69 ); + + if ( snapshotSequence - lastSnapshotSequence > 1 ) { + stateHitch = true; + } else { + stateHitch = false; + } + lastSnapshotSequence = snapshotSequence; + + oldHealth = health; + + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + health = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + lastDamageDef = msg.ReadBits( gameLocal.entityDefBits ); + lastDamageDir = msg.ReadDir( 9 ); + lastDamageLocation = msg.ReadShort(); + newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) ); + inventory.weapons = msg.ReadBits( MAX_WEAPONS ); + weaponSpawnId = msg.ReadBits( 32 ); + weaponWorldSpawnId = msg.ReadBits( 32 ); + int latchedSpectator = spectator; + spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + if ( spectating && latchedSpectator != spectator && this == gameLocal.GetLocalPlayer() ) { + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + // this is where the client updates their spectated player + if ( gameLocal.gameType == GAME_TOURNEY ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (spectator != arena.GetPlayers()[ 0 ]->entityNumber && spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_BRACKET ); + } else if( spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_ONE ); + } else if( spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + if ( gameLocal.entities[ spectator ] ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ spectator ] ); + p->UpdateHudWeapon( p->currentWeapon ); + if ( p->weapon ) { + p->weapon->SpectatorCycle(); + } + } + } + newHitToggle = msg.ReadBits( 1 ) != 0; + newHitArmor = msg.ReadBits( 1 ) != 0; + weaponGone = msg.ReadBits( 1 ) != 0; + isLagged = msg.ReadBits( 1 ) != 0; + isChatting = msg.ReadBits( 1 ) != 0; + connectTime = msg.ReadLong(); + lastKillerEntity = msg.ReadByte(); + if( lastKillerEntity >= 0 && lastKillerEntity < MAX_CLIENTS) { + lastKiller = static_cast(gameLocal.entities[ lastKillerEntity ]); + } else { + lastKiller = NULL; + } + + if ( idealWeapon != newIdealWeapon ) { + if ( stateHitch ) { + weaponCatchup = true; + } + idealWeapon = newIdealWeapon; + StopFiring(); + UpdateHudWeapon(); + usercmd.buttons &= (~BUTTON_ATTACK); + } + + // Attach the world and view entities + weaponWorldModel.SetSpawnId( weaponWorldSpawnId ); + if ( weaponWorldModel.IsValid() && weaponViewModel.SetSpawnId( weaponSpawnId ) ) { + currentWeapon = -1; + SetWeapon( idealWeapon ); + } + + // rjohnson: instance persistance information + if ( weaponWorldModel.IsValid() ) { + weaponWorldModel->fl.persistAcrossInstances = true; + weaponWorldModel->SetInstance( GetInstance() ); + } + if ( weaponViewModel.IsValid() ) { + weaponViewModel->fl.persistAcrossInstances = true; + weaponViewModel->SetInstance( GetInstance() ); + } + + // If we have a weapon then update it from the snapshot, otherwise + // we just skip whatever it would have read if it were there + if ( msg.ReadBits( 1 ) ) { + if ( weapon ) { + weapon->ReadFromSnapshot( msg ); + } else { + rvWeapon::SkipFromSnapshot( msg ); + } + } + if ( proto69 ) { + inBuyZone = false; + buyMenuCash = 0.0f; + } else { +//RITUAL BEGIN + inBuyZone = msg.ReadBits( 1 ) != 0; + int cash = msg.ReadLong(); + if ( cash != (int)buyMenuCash ) { + buyMenuCash = (float)cash; + gameLocal.mpGame.RedrawLocalBuyMenu(); + } +//RITUAL END + } + // no msg reading below this + + // if not a local client assume the client has all ammo types + if ( entityNumber != gameLocal.localClientNum ) { + for( i = 0; i < MAX_AMMO; i++ ) { + inventory.ammo[ i ] = 999; + } + } + + if ( oldHealth > 0 && health <= 0 ) { + if ( stateHitch ) { + // so we just hide and don't show a death skin + UpdateDeathSkin( true ); + } + // die + pfl.dead = true; + ClearPowerUps(); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + animator.ClearAllJoints(); + StartRagdoll(); + physicsObj.SetMovementType( PM_DEAD ); + + if ( !stateHitch ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + const idDeclEntityDef* def = static_cast(declManager->DeclByIndex ( DECL_ENTITYDEF, lastDamageDef )); + if ( def ) { + // TODO: get attackers push scale? + InitDeathPush ( lastDamageDir, lastDamageLocation, &def->dict, 1.0f ); + ClientDamageEffects ( def->dict, lastDamageDir, ( oldHealth - health ) * 4 ); + } + + //gib them here + if ( health < -20 || ( lastKiller && lastKiller->PowerUpActive( POWERUP_QUADDAMAGE )) ) { + ClientGib( lastDamageDir ); + } + + if ( weapon ) { + weapon->OwnerDied(); + + // Get rid of the weapon now + delete weapon; + weapon = NULL; + currentWeapon = -1; + } + } else if ( oldHealth <= 0 && health > 0 ) { + // respawn + //common->DPrintf( "idPlayer::ReadFromSnapshot() - Player respawn detected for %d '%s' - re-enabling clip\n", entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + + // this is the first time we've seen the player since we heard he died - he may have picked up + // some powerups since he actually spawned in, so restore those + int latchPowerup = inventory.powerups; + Init(); + inventory.powerups = latchPowerup; + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + } else if ( oldHealth - health > 2 && health > 0 ) { + if ( stateHitch ) { + lastDmgTime = gameLocal.time; + } else { + // damage feedback + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); + if ( def ) { + ClientDamageEffects ( def->dict, lastDamageDir, oldHealth - health ); + pfl.pain = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); + lastDmgTime = gameLocal.time; + } else { + common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef ); + } + } + } + + if ( lastHitToggle != newHitToggle ) { + SetLastHitTime( gameLocal.realClientTime, newHitArmor ); + } + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } + + /*if ( (head == NULL || headSpawnId != head.GetSpawnId()) && headSpawnId > 0 ) { + head.SetSpawnId( headSpawnId ); + SetupHead(); + }*/ +} + +/* +================ +idPlayer::WritePlayerStateToSnapshot +================ +*/ +void idPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const { + int i; + + msg.WriteDeltaByte( 0, bobCycle ); + msg.WriteDeltaLong( 0, stepUpTime ); + msg.WriteDeltaFloat( 0.0f, stepUpDelta ); + + msg.WriteShort( inventory.weapons ); + msg.WriteByte( inventory.armor ); + msg.WriteShort( inventory.powerups ); + + for( i = 0; i < MAX_AMMO; i++ ) { + msg.WriteBits( inventory.ammo[i], ASYNC_PLAYER_INV_AMMO_BITS ); + } + + for( i = 0; i < POWERUP_MAX; i ++ ) { + msg.WriteLong( inventory.powerupEndTime[ i ] ); + } +} + +/* +================ +idPlayer::ReadPlayerStateFromSnapshot +================ +*/ +void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) { + int i, ammo; + + bobCycle = msg.ReadDeltaByte( 0 ); + stepUpTime = msg.ReadDeltaLong( 0 ); + stepUpDelta = msg.ReadDeltaFloat( 0.0f ); + + inventory.weapons = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + inventory.powerups = msg.ReadShort(); + + for( i = 0; i < MAX_AMMO; i++ ) { + ammo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS ); + if ( gameLocal.time >= inventory.ammoPredictTime ) { + inventory.ammo[ i ] = ammo; + } + } + + int powerup_max = gameLocal.GetCurrentDemoProtocol() == 69 ? 11 : POWERUP_MAX; + for ( i = 0; i < powerup_max; i ++ ) { + inventory.powerupEndTime[ i ] = msg.ReadLong(); + } + while ( i < POWERUP_MAX ) { + inventory.powerupEndTime[ i ] = 0; + i++; + } + + if ( gameLocal.IsMultiplayer() ) { + if ( (inventory.weapons&~oldInventoryWeapons) ) { + //added a weapon from inventory, bring up bar + UpdateHudWeapon(); + } + oldInventoryWeapons = inventory.weapons; + } +} + +/* +================ +idPlayer::ServerReceiveEvent +================ +*/ +bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + + if ( idEntity::ServerReceiveEvent( event, time, msg ) ) { + return true; + } + + // client->server events + switch( event ) { + case EVENT_IMPULSE: { + PerformImpulse( msg.ReadBits( IMPULSE_NUMBER_OF_BITS ) ); + return true; + } + case EVENT_EMOTE: { + // forward the emote on to all clients except the one that sent it to us + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, entityNumber ); + + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + default: { + return false; + } + } +} + +/* +================ +idPlayer::ClientReceiveEvent +================ +*/ +bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int powerup; + bool start; + + switch ( event ) { + case EVENT_EXIT_TELEPORTER: + Event_ExitTeleporter(); + return true; + case EVENT_ABORT_TELEPORTER: + SetPrivateCameraView( NULL ); + return true; + case EVENT_POWERUP: { + powerup = msg.ReadShort(); + start = ( msg.ReadBits( 1 ) != 0 ); + if ( start ) { + bool team = false; + if ( gameLocal.GetCurrentDemoProtocol() != 69 ) { + team = ( msg.ReadBits( 1 ) != 0 ); + } + GivePowerUp( powerup, 0, team ); + } else { + ClearPowerup( powerup ); + } + + return true; + } + case EVENT_SPECTATE: { + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + // force to spectator if we got this event about a client in a different + // instance + Spectate( spectate ); + + // spectate might re-link clip for stale players, so re-call ClientStale if we're stale + if( fl.networkStale ) { + ClientStale(); + } + return true; + } + case EVENT_ADD_DAMAGE_EFFECT: { + if ( spectating ) { + // if we're spectating, ignore + // happens if the event and the spectate change are written on the server during the same frame (fraglimit) + return true; + } + return idActor::ClientReceiveEvent( event, time, msg ); + } + case EVENT_EMOTE: { + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + default: { + return idActor::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +================ +idPlayer::Hide +================ +*/ +void idPlayer::Hide( void ) { + idActor::Hide(); + + if ( weapon ) { + weapon->HideWorldModel( ); + } +} + +/* +================ +idPlayer::Show +================ +*/ +void idPlayer::Show( void ) { + idActor::Show(); + + if ( weapon ) { + weapon->ShowWorldModel( ); + } +} + +/* +=============== +idPlayer::ShowTip +=============== +*/ +void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) { + if ( tipUp ) { + return; + } + hud->SetStateString( "tip", tip ); + hud->SetStateString( "tiptitle", title ); + hud->HandleNamedEvent( "tipWindowUp" ); + if ( autoHide ) { + PostEventSec( &EV_Player_HideTip, 5.0f ); + } + tipUp = true; +} + +/* +=============== +idPlayer::HideTip +=============== +*/ +void idPlayer::HideTip( void ) { + hud->HandleNamedEvent( "tipWindowDown" ); + tipUp = false; +} + +/* +=============== +idPlayer::Event_HideTip +=============== +*/ +void idPlayer::Event_HideTip( void ) { + HideTip(); +} + +/* +=============== +idPlayer::ShowObjective +=============== +*/ +void idPlayer::ShowObjective( const char *obj ) { + objectiveSystem->HandleNamedEvent( obj ); + objectiveUp = true; +} + + +/* +=============== +idPlayer::HideObjective +=============== +*/ +void idPlayer::HideObjective( void ) { + objectiveSystem->HandleNamedEvent( "closeObjective" ); + objectiveUp = false; +} + +/* +=============== +idPlayer::SetSpectateOrigin +=============== +*/ +void idPlayer::SetSpectateOrigin( void ) { + idVec3 neworig; + + neworig = GetPhysics()->GetOrigin(); + neworig[ 2 ] += EyeHeight(); + neworig[ 2 ] += 25; + SetOrigin( neworig ); +} + +/* +=============== +idPlayer::RemoveWeapon +=============== +*/ +void idPlayer::RemoveWeapon( const char *weap ) { + if ( weap && *weap ) { + inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 ); + } +} + +/* +=============== +idPlayer::CanShowWeaponViewmodel +=============== +*/ +bool idPlayer::CanShowWeaponViewmodel( void ) const { + return showWeaponViewModel; +} + +/* +=============== +idPlayer::SetLevelTrigger +=============== +*/ +void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) { + if ( levelName && *levelName && triggerName && *triggerName ) { + idLevelTriggerInfo lti; + lti.levelName = levelName; + lti.triggerName = triggerName; + inventory.levelTriggers.Append( lti ); + } +} + + +/* +=============== +idPlayer::Event_LevelTrigger +=============== +*/ +void idPlayer::Event_LevelTrigger( void ) { + idStr mapName = gameLocal.GetMapName(); + mapName.StripPath(); + mapName.StripFileExtension(); + for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) { + if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){ + idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 1, this ); + } + } + } +} + +/* +================ +idPlayer::ToggleFlashlight +================ +*/ +void idPlayer::ToggleFlashlight ( void ) { + // Dead people can use flashlights +// RAVEN BEGIN +// mekberg: check to see if the weapon is enabled. + if ( health <= 0 || !weaponEnabled ) { + return; + } +// RAVEN END + + int flashlightWeapon = currentWeapon; + if ( !spawnArgs.GetBool( va( "weapon%d_flashlight", flashlightWeapon ) ) ) { + // TODO: find the first flashlight weapon that has ammo starting at the bottom + for( flashlightWeapon = MAX_WEAPONS - 1; flashlightWeapon >= 0; flashlightWeapon-- ) { + if ( inventory.weapons & ( 1 << flashlightWeapon ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", flashlightWeapon ) ); + int ammo = inventory.ammo[inventory.AmmoIndexForWeaponClass ( weap ) ]; + + if ( !ammo ) { + continue; + } + + if ( spawnArgs.GetBool ( va ( "weapon%d_flashlight", flashlightWeapon ) ) ) { + break; + } + } + } + + // Couldnt find flashlight + if ( flashlightWeapon < 0 ) { + return; + } + } + + // If the current weapon isnt the flashlight then always force the flashlight on + if ( flashlightWeapon != idealWeapon ) { + flashlightOn = true; + idealWeapon = flashlightWeapon; + // Inform the weapon to toggle the flashlight, this will eventually cause the players + // Flashlight method to be called + } else if ( weapon ) { + weapon->Flashlight ( ); + } +} + +/* +================ +idPlayer::Flashlight +================ +*/ +void idPlayer::Flashlight ( bool on ) { + flashlightOn = on; +} + +/* +================ +idPlayer::DamageFeedback +================ +*/ +void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + + //rvTramCars weren't built on the idActor inheritance hierarchy but need to be treated like one when shot. + //TODO: Maybe add a key to entity flags that will allow them to be shot as actors even if they aren't actors? + if( !victim || ( !victim->IsType( idActor::GetClassType() ) && !victim->IsType( rvTramCar::GetClassType() ) ) || victim->health <= 0 ) { + return; + } + + bool armorHit = false; + + if( gameLocal.isMultiplayer && victim->IsType( idPlayer::GetClassType() ) ) { + if( this == victim ) { + // no feedback for self hits + return; + } + if( gameLocal.IsTeamGame() && ((idPlayer*)victim)->team == team ) { + // no feedback for team hits + return; + } + if( ((idPlayer*)victim)->inventory.armor > 0 ) { + armorHit = true; + } + } + + SetLastHitTime( gameLocal.time, armorHit ); +} + +/* +============== +idPlayer::GetWeaponDef +============== +*/ +const idDeclEntityDef* idPlayer::GetWeaponDef ( int weaponIndex ) { + if ( cachedWeaponDefs[weaponIndex] ) { + return cachedWeaponDefs[weaponIndex]; + } + + idStr weapon; + weapon = spawnArgs.GetString ( va("def_weapon%d", weaponIndex ) ); + if ( !weapon.Length() ) { + return NULL; + } + + cachedWeaponDefs[weaponIndex] = gameLocal.FindEntityDef ( weapon, false ); + if ( !cachedWeaponDefs[weaponIndex] ) { + gameLocal.Error( "Could not find weapon definition '%s'", weapon.c_str() ); + } + + return cachedWeaponDefs[weaponIndex]; +} + +/* +============== +idPlayer::GetPowerupDef + +Returns the powerup dictionary for the given powerup index. The dictionary is cached to ensure a +speedy retrieval after the first call. +============== +*/ +const idDeclEntityDef* idPlayer::GetPowerupDef ( int powerupIndex ) { + const idDict* types; + int i; + int num; + + if ( cachedPowerupDefs[powerupIndex] ) { + return cachedPowerupDefs[powerupIndex]; + } + + types = gameLocal.FindEntityDefDict( "powerup_types", false ); + if ( !types ) { + gameLocal.Error( "Could not find entity definition for 'powerup_types'" ); + } + + num = types->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + const idKeyValue* kv; + kv = types->GetKeyVal( i ); + if ( atoi(kv->GetValue()) == powerupIndex ) { + cachedPowerupDefs[powerupIndex] = gameLocal.FindEntityDef ( kv->GetKey(), false ); + if ( !cachedPowerupDefs[powerupIndex] ) { + gameLocal.Error( "Could not find powerup definition '%s'", kv->GetKey().c_str() ); + } + return cachedPowerupDefs[powerupIndex]; + } + } + + gameLocal.Error( "Could not find powerup definition '%d'", powerupIndex ); + + return NULL; +} + +/* +============== +idPlayer::discoverSecretArea + +Announces a secret area and increases the secret area tally +============== +*/ +void idPlayer::DiscoverSecretArea(const char* _description) { + + //increment the secret area tally + inventory.secretAreasDiscovered++; +} + +/* +============== +idPlayer::StartBossBattle + +Starts a boss battle with the given entity. During a boss battle the health of the boss +will be displayed on the HUD +============== +*/ +void idPlayer::StartBossBattle ( idEntity* enemy ) { + bossEnemy = enemy; + idUserInterface *hud_ = GetHud(); + if ( hud_ ) { + hud_->SetStateInt ( "boss_maxhealth", enemy->health ); + hud_->HandleNamedEvent ( "showBossBar" ); + } +} + +/* +===================== +idPlayer::SetInitialHud +===================== +*/ +void idPlayer::SetInitialHud ( void ) { + if ( !mphud || !gameLocal.isMultiplayer || gameLocal.GetLocalPlayer() != this ) { + return; + } + + mphud->SetStateInt( "gametype", gameLocal.gameType ); + + if( hud ) { + hud->SetStateInt( "gametype", gameLocal.gameType ); + } + + mphud->HandleNamedEvent( "InitHud" ); + mphud->HandleNamedEvent( "TeamChange" ); + + if( gameLocal.IsFlagGameType() ) { + mphud->SetStateFloat( "ap", gameLocal.mpGame.assaultPoints.Num() ); + + for( int i = 0; i < TEAM_MAX; i++ ) { + mphud->SetStateInt( "team", i ); + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_DROPPED ) { + mphud->HandleNamedEvent( "flagDrop" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN ) { + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_MARINE ) { + mphud->SetStateInt( "team", TEAM_MARINE ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_STROGG ) { + mphud->SetStateInt( "team", TEAM_STROGG ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_AT_BASE ) { + mphud->SetStateInt( "team", i ); + mphud->HandleNamedEvent( "flagReturn" ); + } + } + + for( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + mphud->SetStateFloat( "apindex", i ); + //mphud->SetStateInt( "apteam", ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetAPOwner( i ) ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "APCaptured" ); + } + } + + mphud->StateChanged ( gameLocal.time ); +} + +void idPlayer::RemoveClientModel ( const char *entityDefName ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp ( ( static_cast ( cent ) )->GetClassname(), entityDefName ) ) { + cent->Unbind ( ); + delete cent; + } + } + } +} + +rvClientEntityPtr idPlayer::AddClientModel ( const char* entityDefName, const char* shaderName ) { + rvClientEntityPtr ptr; + ptr = NULL; + + if ( entityDefName == NULL ) { + return ptr; + } + + const idDict* entityDef = gameLocal.FindEntityDefDict ( entityDefName, false ); + + if ( entityDef == NULL ) { + return ptr; + } + + rvClientModel *newModel = NULL; + + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientModel" ); + + if( newModel == NULL ) { + return ptr; + } + idMat3 rotation; + rotation = entityDef->GetAngles( "angles" ).ToMat3(); + newModel->SetAxis( rotation ); + + newModel->SetOrigin( entityDef->GetVector( "origin" ) * rotation ); + + newModel->Bind ( this, animator.GetJointHandle( entityDef->GetString ( "joint" ) ) ); + + newModel->SetCustomShader ( shaderName ); + newModel->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + newModel->GetRenderEntity()->noSelfShadow = true; + newModel->GetRenderEntity()->noShadow = true; + + ptr = newModel; + + return ptr; +} + +void idPlayer::RemoveClientModels ( void ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + cent->Unbind ( ); + delete cent; + } + } +} + +/* +===================== +idPlayer::ClientGib() +ddynerman: Spawns client side gibs around this player +===================== +*/ +void idPlayer::ClientGib( const idVec3& dir ) { + + if( !spawnArgs.GetBool( "gib" ) ) { + return; + } + + int i; + idVec3 entityCenter, velocity; + idList list; + + + // hide the player + SetSkin( gibSkin ); + + //and the head + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->UnlinkCombat(); + delete clientHead; + } + } else { + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + } + + // blow out the gibs in the given direction away from the center of the entity + + + // spawn gib client models + rvClientMoveable::SpawnClientMoveables( this, "clientgib", &list ); + + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + for ( i = 0; i < list.Num(); i++ ) { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + // we don't want collision on gibs + //list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + + list[i]->GetPhysics()->ApplyImpulse( 0, list[i]->GetPhysics()->GetOrigin(), velocity * ( 25000.0f + ( gameLocal.random.RandomFloat() * 50000.0f)) ); +// list[i]->GetPhysics()->SetLinearVelocity( velocity * ( 25.0f + ( gameLocal.random.RandomFloat() * 300.0f))); + list[i]->GetPhysics()->SetAngularVelocity( velocity * ( -250.0f + ( gameLocal.random.RandomFloat() * 500.0f))); + + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + + list[i]->PostEventMS( &CL_FadeOut, SEC2MS( 4.0f ), SEC2MS( 2.0f ) ); + } + + + //play gib fx + gameLocal.PlayEffect( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + // gibs are PVS agnostic. If we gib a player outside of our PVS, set the oldHealth + // to below 0 so when this player re-appears in our snap we respawn him + if( gameLocal.isClient && gameLocal.GetLocalPlayer() && health > 0 ) { + health = -100; + } +} + +/* +===================== +idPlayer::CanDamage +===================== +*/ +bool idPlayer::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ) { + if( gameLocal.isMultiplayer && health <= 0 ) { + return false; + } + + return idActor::CanDamage( origin, damagePoint, ignoreEnt ); +} + +/* +===================== +idPlayer::ClientDamageEffects + +===================== +*/ +void idPlayer::ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ) { + idVec3 from; + idVec3 localDir; + float fadeDB; + + // Only necessary on clients + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + from = dir; + from.Normalize(); + viewAxis.ProjectVector( from, localDir ); + + if ( damage ) { +// RAVEN BEGIN +// jnewquist: Controller rumble + idPlayer *p = gameLocal.GetLocalPlayer(); + + if ( p && ( p == this || ( p->spectating && p->spectator == entityNumber ) ) ) { + playerView.DamageImpulse( localDir, &damageDef, damage ); + } +// RAVEN END + } + + // Visual effects + if ( health > 0 && damage ) { + // Let the hud know about the hit + if ( hud ) { + hud->SetStateFloat ( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + hud->HandleNamedEvent ( "playerHit" ); + } + } + + // Sound effects + if ( damageDef.GetFloat ( "hl_volumeDB", "-40", fadeDB ) ) { + float fadeTime; + + fadeTime = 0.0f; + if ( !pfl.hearingLoss ) { + const char* fade; + + fadeTime = damageDef.GetFloat ( "hl_fadeOutTime", ".25" ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); + + pfl.hearingLoss = true; + + // sound overlayed? + if ( damageDef.GetString ( "snd_hl", "", &fade ) && *fade ) { + StartSoundShader ( declManager->FindSound ( fade ), SND_CHANNEL_DEMONIC, 0, false, NULL ); + } + } + + fadeTime += damageDef.GetFloat ( "hl_time", "1" ); + + CancelEvents ( &EV_Player_FinishHearingLoss ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, damageDef.GetFloat ( "hl_fadeInTime", ".25" ) ); + } +} + +/* +===================== +idPlayer::GetDebugInfo +===================== +*/ +void idPlayer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idActor::GetDebugInfo ( proc, userData ); + proc ( "idPlayer", "inventory.armor", va("%d", inventory.armor ), userData ); + proc ( "idPlayer", "inventory.weapons", va("%d", inventory.weapons ), userData ); + proc ( "idPlayer", "inventory.powerups", va("%d", inventory.powerups ), userData ); +} + + + +// RAVEN END + +/* +===================== +idPlayer::ApplyImpulse +===================== +*/ +void idPlayer::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( !ent) { + gameLocal.Warning( "idPlayer::ApplyImpulse called with null entity as instigator."); + return; + } + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time + 1000; + + if( ent->IsType( idPlayer::Type ) && ent != this ) { + lastImpulsePlayer = static_cast(ent); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + idEntity* owner = static_cast(ent)->GetOwner(); + if( owner && owner->IsType( idPlayer::Type ) && owner != this ) { + lastImpulsePlayer = static_cast(owner); + } + } + + idAFEntity_Base::ApplyImpulse( ent, id, point, impulse, splash ); +} + +/* +===================== +idPlayer::SetupHead +===================== +*/ +void idPlayer::SetupHead( const char* headModel, idVec3 headOffset ) { + if( gameLocal.isMultiplayer ) { + // player's don't use idActor's real head entities - uses clientEntities instead + if( clientHead.GetEntity() ) { + delete clientHead.GetEntity(); + clientHead = NULL; + } + + + if( spectating || (gameLocal.GetLocalPlayer() && instance != gameLocal.GetLocalPlayer()->GetInstance()) ) { + return; + } + + const idDict* headDict = gameLocal.FindEntityDefDict( headModel, false ); + if ( !headDict ) { + return; + } + + rvClientAFAttachment* headEnt = clientHead.GetEntity(); + gameLocal.SpawnClientEntityDef( *headDict, (rvClientEntity**)&headEnt, false ); + if( headEnt ) { + idStr jointName = spawnArgs.GetString( "joint_head" ); + jointHandle_t joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + headEnt->SetBody ( this, headDict->GetString ( "model" ), joint ); + + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->Bind( this, joint, true ); + headEnt->InitCopyJoints(); + + // Spawn might have parsed a skin from the spawnargs, save it for future use here + headSkin = headEnt->GetRenderEntity()->customSkin; + clientHead = headEnt; + } + } else { + idActor::SetupHead( headModel, headOffset ); + + if ( head ) { + head->fl.persistAcrossInstances = true; + } + } +} + +/* +===================== +idPlayer::GUIMainNotice +===================== +*/ +void idPlayer::GUIMainNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "main_notice_text", message ); + mphud->SetStateBool( "main_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "main_notice" ); +} + +/* +===================== +idPlayer::GUIFragNotice +===================== +*/ +void idPlayer::GUIFragNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "frag_notice_text", message ); + mphud->SetStateBool( "frag_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "frag_notice" ); +} + +/* +===================== +idPlayer::SetHudOverlay +===================== +*/ +void idPlayer::SetHudOverlay( idUserInterface* overlay, int duration ) { + overlayHud = overlay; + overlayHudTime = gameLocal.time + duration; +} + +// RAVEN BEGIN +// mekberg: wrap saveMessages +/* +===================== +idPlayer::SaveMessage +===================== +*/ +void idPlayer::SaveMessage( void ) { +#ifndef _XENON + if ( GetHud( ) ) { + GetHud()->HandleNamedEvent( "saveMessage" ); + } + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "saveMessage" ); + } +#endif +} + +// mekberg: set pm_ cvars +/* +===================== +idPlayer::SetPMCVars +===================== +*/ +void idPlayer::SetPMCVars( void ) { + const idKeyValue *kv; + + if ( !gameLocal.isMultiplayer || gameLocal.isServer ) { + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + } +} +// RAVEN END + +/* +===================== +idPlayer::GetSpawnClassname +===================== +*/ +const char* idPlayer::GetSpawnClassname ( void ) { + idEntity* world; + const char* entityFilter; + + // Test player def + if ( *g_testPlayer.GetString() ) { + return g_testPlayer.GetString ( ); + } + + // Multiplayer + if ( gameLocal.isMultiplayer ) { + return "player_marine_mp"; + } + + // See if the world spawn specifies a player + world = gameLocal.entities[ENTITYNUM_WORLD]; + assert( world ); + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + return world->spawnArgs.GetString( va("player_%s", entityFilter ), world->spawnArgs.GetString( "player", "player_marine" ) ); + } + + return world->spawnArgs.GetString( "player", "player_marine" ); +} + +/* +=============== +idPlayer::SetInstance +=============== +*/ +void idPlayer::SetInstance( int newInstance ) { + common->DPrintf( "idPlayer::SetInstance() - Setting instance for '%s' to %d\n", name.c_str(), newInstance ); + idEntity::SetInstance( newInstance ); + + if( head.GetEntity() ) { + head.GetEntity()->SetInstance( newInstance ); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetInstance( newInstance ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetInstance( newInstance ); + } + } + + if( weaponWorldModel ) { + weaponWorldModel->SetInstance( newInstance ); + } + + if( weaponViewModel ) { + weaponViewModel->SetInstance( newInstance ); + } + + // reschedule time announcements if needed + if( this == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleTimeAnnouncements(); + + if( gameLocal.isServer ) { + // remove/add heads on server + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + if( player->instance != newInstance ) { + if( player->clientHead.GetEntity() ) { + delete player->clientHead; + player->clientHead = NULL; + } + } else { + player->UpdateModelSetup( true ); + } + } + } + } + } +} + +/* +=============== +idPlayer::JoinInstance +=============== +*/ +void idPlayer::JoinInstance( int newInstance ) { + assert( gameLocal.isServer ); + if( instance == newInstance ) { + return; + } + + if( newInstance < 0 || newInstance >= MAX_ARENAS ) { + gameLocal.Warning( "idPlayer::JoinInstance() - Invalid instance %d specified\n", newInstance ); + } + + if( gameLocal.GetNumInstances() <= newInstance || gameLocal.GetInstance( newInstance ) == NULL ) { + // don't populate instance until player gets linked into the right one + gameLocal.AddInstance( newInstance ); + + if( this == gameLocal.GetLocalPlayer() ) { + // ensure InstanceLeave() gets called on newly spawned entities before + // InstanceJoin() gets called. + gameLocal.mpGame.ServerSetInstance( instance ); + } + } + + SetArena( newInstance ); + SetInstance( newInstance ); + + gameLocal.GetInstance( newInstance )->JoinInstance( this ); +} + +/* +=============== +idPlayer::SetEmote +=============== +*/ +void idPlayer::SetEmote( playerEmote_t newEmote ) { + emote = newEmote; + + // if we're the ones generating the emote, pass it along + if( gameLocal.localClientNum == entityNumber ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( emote ); + if( gameLocal.isServer ) { + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, -1 ); + } else { + ClientSendEvent( EVENT_EMOTE, &msg ); + } + } +} + +/* +=============== +idPlayer::GetGroundElevator +=============== +*/ +idEntity* idPlayer::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + //NOTE: for player, don't care if all the way on, or not + return groundEnt; +} + +/* +=================== +idPlayer::IsCrouching +=================== +*/ +bool idPlayer::IsCrouching( void ) const { + return physicsObj.IsCrouching(); +} + +/* +=============== +idPlayer::SetArena +=============== +*/ +void idPlayer::SetArena( int newArena ) { + if( arena == newArena ) { + return; + } + + arena = newArena; + if( gameLocal.GetLocalPlayer() == this && gameLocal.gameType == GAME_TOURNEY ) { + if( arena >= 0 && arena <= MAX_ARENAS ) { + if( arena < MAX_ARENAS ) { +// RAVEN BEGIN +// rhummer: localized these strings. + GUIMainNotice( va( common->GetLocalizedString( "#str_107270" ), newArena + 1 ) ); + } else { + GUIMainNotice( common->GetLocalizedString( "#str_107271" ) ); + } +// RAVEN END + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( newArena, TGH_BRACKET ); + } + gameLocal.mpGame.RemoveAnnouncerSoundRange( AS_TOURNEY_JOIN_ARENA_ONE, AS_TOURNEY_JOIN_ARENA_EIGHT ); + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)(AS_TOURNEY_JOIN_ARENA_ONE + arena), gameLocal.time ); + } + } +} + +/* +=============== +idPlayer::Event_DamageEffect +=============== +*/ +void idPlayer::Event_DamageEffect( const char *damageDefName, idEntity* _damageFromEnt ) +{ + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) + { + idVec3 dir = (_damageFromEnt!=NULL)?(GetEyePosition()-_damageFromEnt->GetEyePosition()):viewAxis[2]; + dir.Normalize(); + int damage = 1; + ClientDamageEffects( damageDef->dict, dir, damage ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = 0; + } +} + +/* +=============== +idPlayer::UpdateDeathShader +=============== +*/ +void idPlayer::UpdateDeathShader ( bool state_hitch ) { + if ( !doingDeathSkin && gameLocal.time > deathSkinTime && deathSkinTime ) { + deathSkinTime = 0; + + deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" ); + doingDeathSkin = true; + if ( state_hitch ) { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } else { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } + renderEntity.noShadow = true; + UpdateVisuals(); + } +} + +/* +=============== +idPlayer::Event_InitWeapon +=============== +*/ +void idPlayer::InitWeapon( void ) { + doInitWeapon = false; + currentWeapon = -1; + SetWeapon( idealWeapon ); +} + +/* +=============== +idPlayer::GetHitscanTint +=============== +*/ +const idVec4& idPlayer::GetHitscanTint( void ) { + if( gameLocal.IsTeamGame() ) { + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 2 ) { + if( team == TEAM_MARINE ) { + return marineHitscanTint; + } else if( team == TEAM_STROGG ) { + return stroggHitscanTint; + } else { + gameLocal.Error( "idPlayer::GetHitscanTint() - Unknown team '%d' on player %d '%s'\n", team, entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + } + } else { + return defaultHitscanTint; + } + } + + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 1 ) { + return hitscanTint; + } + + return defaultHitscanTint; +} + +/* +=============== +idPlayer::IsReady +=============== +*/ +bool idPlayer::IsReady( void ) { + return !gameLocal.serverInfo.GetBool( "si_useReady" ) || ready || forcedReady; +} + +/* +=============== +idPlayer::ForceScoreboard +=============== +*/ +void idPlayer::ForceScoreboard( bool force, int time ) { + forceScoreBoard = force; + forceScoreBoardTime = time; +} + +/* +=============== +idPlayer::GetTextTourneyStatus +=============== +*/ +const char* idPlayer::GetTextTourneyStatus( void ) { + if( tourneyStatus == PTS_ADVANCED ) { + return common->GetLocalizedString( "#str_107740" ); + } else if( tourneyStatus == PTS_ELIMINATED ) { + return common->GetLocalizedString( "#str_107729" ); + } else if( tourneyStatus == PTS_PLAYING ) { + return common->GetLocalizedString( "#str_107728" ); + } else if( tourneyStatus == PTS_UNKNOWN ) { + return common->GetLocalizedString( "#str_107739" ); + } + return "UNKNOWN TOURNEY STATUS"; +} + +/* +=============== +idPlayer::ClientInstanceJoin +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceJoin( void ) { + assert( gameLocal.isClient ); + + common->DPrintf( "idPlayer::ClientInstanceJoin() - client %d ('%s') is being shown\n", entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + + // restore client + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientInstanceLeave +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceLeave( void ) { + assert( gameLocal.isClient ); + common->DPrintf( "idPlayer::ClientInstanceLeave() - client %d ('%s') is being hidden\n", entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + + // force client to spectate + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientStale +=============== +*/ +bool idPlayer::ClientStale( void ) { + idEntity::ClientStale(); + + // remove all powerup effects + for( int i = 0; i < POWERUP_MAX; i++ ) { + if( inventory.powerups & ( 1 << i ) ) { + StopPowerUpEffect( i ); + } + } + + + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + + // never delete client + return false; +} + +/* +=============== +idPlayer::ClientUnstale +=============== +*/ +void idPlayer::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + // force render ent to position + renderEntity.axis = physicsObj.GetAxis(); + renderEntity.origin = physicsObj.GetOrigin(); + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + // the powerup effects ( rvClientEntity ) will do some bindings, which in turn will call GetPosition + // which uses the predictedOrigin .. which won't be updated till we Think() so just don't leave the predictedOrigin to the old position + predictedOrigin = renderEntity.origin; + + // restart powerup effects on clients that are coming back into our snapshot + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( inventory.powerups & (1 << i) ) { + StartPowerUpEffect( i ); + } + } + + UpdateModelSetup( true ); + + if ( weapon ) { + weapon->ClientUnstale(); + } +} + +/* +=============== +idPlayer::AllowedVoiceDest +=============== +*/ +bool idPlayer::AllowedVoiceDest( int from ) { + + int i, free; + + free = -1; + for( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + + if( voiceDest[i] == from ) { + voiceDestTimes[i] = gameLocal.time; + return true; + } + + if( voiceDestTimes[i] + 200 < gameLocal.time ) { + free = i; + } + } + + if( free > -1 ) { + voiceDest[free] = from; + voiceDestTimes[i] = gameLocal.time; + return true; + } + + return false; +} + +// RITUAL BEGIN +void idPlayer::ClampCash( float minCash, float maxCash ) +{ + if( buyMenuCash < minCash ) + buyMenuCash = minCash; + + if( buyMenuCash > maxCash ) + buyMenuCash = maxCash; +} + +void idPlayer::GiveCash( float cashDeltaAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + float oldCash = buyMenuCash; + buyMenuCash += cashDeltaAmount; + ClampCash( minCash, maxCash ); + + if( (int)buyMenuCash != (int)oldCash ) + { + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if( (int)buyMenuCash > (int)oldCash ) + { + // Play the "get cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } + else if( (int)buyMenuCash < (int)oldCash ) + { + // Play the "lose cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +void idPlayer::SetCash( float newCashAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + buyMenuCash = newCashAmount; + ClampCash( minCash, maxCash ); +} + +void idPlayer::ResetCash() +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + //buyMenuCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerStartingCash", 0 ); + + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + buyMenuCash = (float) gameLocal.serverInfo.GetInt("si_buyModeStartingCredits"); + ClampCash( minCash, maxCash ); +} + +/** + * Checks to see if the player can accept this item in their inventory + * + * weaponName Name of the weapon. + */ +int idPlayer::CanSelectWeapon(const char* weaponName) +{ + int weaponNum = -1; + if(weaponName == NULL) + return weaponNum; + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + weaponNum = i; + break; + } + } + } + + return weaponNum; +} + +// RITUAL END diff --git a/src/game/Player.h b/src/game/Player.h new file mode 100644 index 0000000..586c244 --- /dev/null +++ b/src/game/Player.h @@ -0,0 +1,1356 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYER_H__ +#define __GAME_PLAYER_H__ + +/* +=============================================================================== + + Player entity. + +=============================================================================== +*/ + +extern const idEventDef EV_Player_GetButtons; +extern const idEventDef EV_Player_GetMove; +extern const idEventDef EV_Player_GetViewAngles; +extern const idEventDef EV_Player_SetViewAngles; +extern const idEventDef EV_Player_EnableWeapon; +extern const idEventDef EV_Player_DisableWeapon; +extern const idEventDef EV_Player_ExitTeleporter; +extern const idEventDef EV_Player_SelectWeapon; +extern const idEventDef EV_Player_Freeze; +extern const idEventDef EV_SpectatorTouch; +extern const idEventDef EV_Player_SetArmor; +extern const idEventDef EV_Player_SetExtraProjPassEntity; +extern const idEventDef EV_Player_DamageEffect; + +const float THIRD_PERSON_FOCUS_DISTANCE = 512.0f; +const int LAND_DEFLECT_TIME = 150; +const int LAND_RETURN_TIME = 300; +const int FOCUS_TIME = 200; +const int FOCUS_GUI_TIME = 300; +const int FOCUS_USABLE_TIME = 100; + +const int MAX_WEAPONS = 16; +const int MAX_AMMO = 16; +const int CARRYOVER_FLAG_AMMO = 0x40000000; +const int CARRYOVER_FLAG_ARMOR_LIGHT = 0x20000000; +const int CARRYOVER_FLAG_ARMOR_HEAVY = 0x10000000; +const int CARRYOVER_WEAPONS_MASK = 0x0FFFFFFF; +const int CARRYOVER_FLAGS_MASK = 0xF0000000; + +const int MAX_SKILL_LEVELS = 4; + +const int ZERO_VOLUME = -40; // volume at zero +const int DMG_VOLUME = 5; // volume when taking damage +const int DEATH_VOLUME = 15; // volume at death + +const int SAVING_THROW_TIME = 5000; // maximum one "saving throw" every five seconds + +const int ASYNC_PLAYER_INV_AMMO_BITS = idMath::BitsForInteger( 999 ); // 9 bits to cover the range [0, 999] +const int ASYNC_PLAYER_INV_CLIP_BITS = -7; // -7 bits to cover the range [-1, 60] +const int ASYNC_PLAYER_INV_WPMOD_BITS = 3; // 3 bits (max of 3 mods per gun) +// NOTE: protocol 69 used 6 bits, but that's only used for client -> server traffic, so doesn't affect backwards protocol replay compat +const int IMPULSE_NUMBER_OF_BITS = 8; // allows for 2< items; + idStrList pdas; + idStrList pdaSecurity; + idStrList videos; + + idList levelTriggers; + + idInventory() { Clear(); } + ~idInventory() { Clear(); } + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Clear( void ); + void GivePowerUp( idPlayer* player, int powerup, int msec ); + void ClearPowerUps( void ); + void GetPersistantData( idDict &dict ); + void RestoreInventory( idPlayer *owner, const idDict &dict ); + bool Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped = false, bool checkOnly = false ); + void Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ); + int AmmoIndexForAmmoClass( const char *ammo_classname ) const; + int MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const; + int AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired = NULL ); + const char * AmmoClassForWeaponClass( const char *weapon_classname); + +// RAVEN BEGIN +// mekberg: if the player can pick up the ammo at this time + bool DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ); +// RAVEN END + + int AmmoIndexForWeaponIndex( int weaponIndex ); + int StartingAmmoForWeaponIndex( int weaponIndex ); + int AmmoRegenStepForWeaponIndex( int weaponIndex ); + int AmmoRegenTimeForWeaponIndex( int weaponIndex ); + + int HasAmmo( int index, int amount ); + bool UseAmmo( int index, int amount ); + int HasAmmo( const char *weapon_classname ); // looks up the ammo information for the weapon class first + + int nextItemPickup; + int nextItemNum; + int onePickupTime; + idList pickupItemNames; + idList objectiveNames; +// idList database; + + int secretAreasDiscovered; +}; + +class idPlayer : public idActor { +public: + + enum { + EVENT_IMPULSE = idEntity::EVENT_MAXEVENTS, + EVENT_EXIT_TELEPORTER, + EVENT_ABORT_TELEPORTER, + EVENT_POWERUP, + EVENT_SPECTATE, + EVENT_EMOTE, + EVENT_MAXEVENTS + }; + + friend class idThread; + + usercmd_t usercmd; + + class idPlayerView playerView; // handles damage kicks and effects + + bool alreadyDidTeamAnnouncerSound; + bool noclip; + bool godmode; + int godmodeDamage; + bool undying; + + bool spawnAnglesSet; // on first usercmd, we must set deltaAngles + idAngles spawnAngles; + idAngles viewAngles; // player view angles + idAngles cmdAngles; // player cmd angles + + int buttonMask; + int oldButtons; + int oldFlags; + + int lastHitTime; // last time projectile fired by player hit target + int lastSavingThrowTime; // for the "free miss" effect + + struct playerFlags_s { + bool forward :1; + bool backward :1; + bool strafeLeft :1; + bool strafeRight :1; + bool attackHeld :1; + bool weaponFired :1; + bool jump :1; + bool crouch :1; + bool onGround :1; + bool onLadder :1; + bool dead :1; + bool run :1; + bool pain :1; + bool hardLanding :1; + bool softLanding :1; + bool reload :1; + bool teleport :1; + bool turnLeft :1; + bool turnRight :1; + bool hearingLoss :1; + bool objectiveFailed :1; + bool noFallingDamage :1; + } pfl; + + // inventory + idInventory inventory; + + rvWeapon* weapon; + idEntityPtr weaponViewModel; + idEntityPtr weaponWorldModel; + const idDeclEntityDef* weaponDef; + + + idUserInterface * hud; // Common hud + idUserInterface * mphud; // hud overlay containing MP elements + + idUserInterface * objectiveSystem; + idUserInterface * cinematicHud; + bool objectiveSystemOpen; + bool objectiveButtonReleased; + bool disableHud; + bool showNewObjectives; + + int lastDmgTime; + int deathClearContentsTime; + bool doingDeathSkin; + int nextHealthPulse; // time when health will tick down + int nextAmmoRegenPulse[ MAX_AMMO ]; // time when ammo will regenerate + int nextArmorPulse; // time when armor will tick down + bool hiddenWeapon; // if the weapon is hidden ( in noWeapons maps ) + + // mp stuff + int spectator; + + bool scoreBoardOpen; + bool forceScoreBoard; + bool forceRespawn; + int forceScoreBoardTime; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + bool allowedToRespawn; +// squirrel: Mode-agnostic buymenus + bool inBuyZone; + bool inBuyZonePrev; +// RITUAL END + bool spectating; + bool lastHitToggle; + bool lastArmorHit; + bool forcedReady; + int lastArenaChange; + + bool wantSpectate; // from userInfo + bool jumpDuringHitch; + + bool weaponGone; // force stop firing + bool useInitialSpawns; // toggled by a map restart to be active for the first game spawn + bool isLagged; // replicated from server, true if packets haven't been received from client. + bool isChatting; // replicated from server, true if the player is chatting. + + int lastSpectateTeleport; + int latchedTeam; // need to track when team gets changed + int spawnedTime; // when client first enters the game + int hudTeam; + + idEntityPtr teleportEntity; // while being teleported, this is set to the entity we'll use for exit + int teleportKiller; // entity number of an entity killing us at teleporter exit + + idEntityPtr lastKiller; + + // timers + int minRespawnTime; // can respawn when time > this, force after g_forcerespawn + int maxRespawnTime; // force respawn after this time + + // the first person view values are always calculated, even + // if a third person view is used + idVec3 firstPersonViewOrigin; + idMat3 firstPersonViewAxis; + + idDragEntity dragEntity; + idVec3 intentDir; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr extraProjPassEntity; + + bool vsMsgState; + + int lastPickupTime; +//RAVEN BEGIN +// asalmon: the eneny the player is most likely currently aiming at +#ifdef _XBOX + idEntityPtr bestEnemy; +#endif +//RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + float buyMenuCash; +// RITUAL END + +public: + CLASS_PROTOTYPE( idPlayer ); + + idPlayer(); + virtual ~idPlayer(); + + void Spawn( void ); + void Think( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + static const char* GetSpawnClassname ( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + void Init( void ); + void PrepareForRestart( void ); + virtual void Restart( void ); + void SetWeapon ( int weapon ); + void SetupWeaponEntity( void ); + bool SelectSpawnPoint( idVec3 &origin, idAngles &angles ); + void SpawnFromSpawnSpot( void ); + void SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ); + void SetClipModel( bool forceSpectatorBBox = false ); // spectator mode uses a different bbox size + + void SavePersistantInfo( void ); + void RestorePersistantInfo( void ); + void SetLevelTrigger( const char *levelName, const char *triggerName ); + + bool UserInfoChanged( void ); + idDict * GetUserInfo( void ); +// RAVEN BEGIN +// shouchard: BalanceTDM->BalanceTeam (now used for CTF as well as TDM) + bool BalanceTeam( void ); +// RAVEN END + void CacheWeapons( void ); + + bool HandleESC( void ); + void EnterCinematic( void ); + void ExitCinematic( void ); + bool SkipCinematic( void ); + + void UpdateConditions( void ); + void SetViewAngles( const idAngles &angles ); + + void BiasIntentDir ( idVec3 newIntentDir, float prevBias = 199.0f ); + + // delta view angles to allow movers to rotate the view of the player + void UpdateDeltaViewAngles( const idAngles &angles ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + void CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ); + // use exitEntityNum to specify a teleport with private camera view and delayed exit + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ); + + void Kill( bool delayRespawn, bool nodamage ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + renderView_t * GetRenderView( void ); + void SmoothenRenderView( bool firstPerson ); + void CalculateRenderView( void ); // called every tic by player code + void CalculateFirstPersonView( void ); + + void DrawShadow( renderEntity_t *headRenderEnt ); + void DrawHUD( idUserInterface *hud ); + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void WeaponFireFeedback( const idDict *weaponDef ); + + float DefaultFov( void ) const; + float CalcFov( bool honorZoom ); + void CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ); + void GetViewPos( idVec3 &origin, idMat3 &axis ) const; + void OffsetThirdPersonView( float angle, float range, float height, bool clip ); +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + void OffsetThirdPersonVehicleView ( bool clip ); +// RAVEN END + bool OffsetThirdPersonTargetView ( void ); + + bool Give( const char *statname, const char *value, bool dropped = false ); + bool GiveItem( idItem *item ); + void GiveItem( const char *name ); + + // Inventory + bool GiveInventoryItem( idDict *item ); + void RemoveInventoryItem( idDict *item ); + bool GiveInventoryItem( const char *name ); + void RemoveInventoryItem( const char *name ); + idDict * FindInventoryItem( const char *name ); + + // Wrist computer + void GiveObjective ( const char *title, const char *text, const char *screenshot ); + void CompleteObjective ( const char *title ); + void FailObjective ( const char *title ); + void GiveDatabaseEntry ( const idDict* dbEntry, bool hudPopup = true ); + bool IsObjectiveUp ( void ) const { return objectiveUp; } + idUserInterface * GetObjectiveHud ( void ) { return objectiveSystem; } + + // Secret Areas + void DiscoverSecretArea ( const char *description); + + void StartBossBattle ( idEntity* ent ); + + // Powerups + bool GivePowerUp ( int powerup, int time, bool team = false ); + void ClearPowerUps ( void ); + + void StartPowerUpEffect ( int powerup ); + void StopPowerUpEffect ( int powerup ); + + bool PowerUpActive ( int powerup ) const; + float PowerUpModifier ( int type ); + void ClearPowerup ( int i ); + const char* GetArenaPowerupString ( void ); + + // Helper methods to retrieving dictionaries + const idDeclEntityDef* GetWeaponDef ( int weaponIndex ); + const idDeclEntityDef* GetPowerupDef ( int powerupIndex ); + + // Weapons + bool GiveWeaponMods ( int mods ); + bool GiveWeaponMods ( int weapon, int mods ); + void GiveWeaponMod ( const char* weaponmod ); + + int SlotForWeapon ( const char *weaponName ); + + idEntity* DropItem ( const char* itemClass, const idDict& customArgs, const idVec3& velocity = vec3_origin ) const; + void DropPowerups ( void ); + idEntity* ResetFlag ( const char* itemClass, const idDict& customArgs ) const; + void RespawnFlags ( void ); + void DropWeapon ( void ); + +// RAVEN BEGIN +// abahr: + bool WeaponIsEnabled ( void ) const { return weaponEnabled; } + void ShowCrosshair ( void ); + void HideCrosshair ( void ); +// RAVEN END + +//RAVEN BEGIN +//asalmon: switch weapon based on d-pad combo +#ifdef _XBOX + void ScheduleWeaponSwitch (int weapon); +#endif +//RAVEN BEGIN + + void Reload ( void ); + void NextWeapon ( void ); + void NextBestWeapon ( void ); + void PrevWeapon ( void ); + void LastWeapon ( void ); + void SelectWeapon ( int num, bool force ); + void SelectWeapon ( const char * ); + void AddProjectilesFired ( int count ); + void AddProjectileHits ( int count ); + void SetLastHitTime ( int time, bool armorHit ); + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void WeaponLoweringCallback ( void ); + void WeaponRisingCallback ( void ); + void RemoveWeapon ( const char *weap ); + void Flashlight ( bool on ); + void ToggleFlashlight ( void ); + bool CanShowWeaponViewmodel ( void ) const; + + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + bool GuiActive( void ) { return focusType == FOCUS_GUI; } + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + void GenerateImpulseForBuyAttempt( const char* itemName ); + bool AttemptToBuyItem( const char* itemName ); + bool AttemptToBuyTeamPowerup( const char* itemName ); + void UpdateTeamPowerups( bool isBuying = false ); + bool CanBuy( void ); + int CanSelectWeapon ( const char* weaponName ); + int GetItemCost(const char* itemName); +// RITUAL END + void PerformImpulse( int impulse ); + void Spectate( bool spectate, bool force = false ); + void ToggleObjectives ( void ); + void ToggleScoreboard( void ); + void RouteGuiMouse( idUserInterface *gui ); + void UpdateHud( void ); + idUserInterface* GetHud(); + const idUserInterface* GetHud() const; + void SetInfluenceFov( float fov ); + void SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ); + void SetInfluenceLevel( int level ); + int GetInfluenceLevel( void ) { return influenceActive; }; + void SetPrivateCameraView( idCamera *camView ); + idCamera * GetPrivateCameraView( void ) const { return privateCameraView; } + void StartFxFov( float duration ); + void UpdateHudWeapon( int displayWeapon=-1 ); +#ifdef _XENON + void ResetHUDWeaponSwitch( void ); +#endif + void UpdateHudStats( idUserInterface *hud ); + void UpdateHudAmmo( idUserInterface *hud ); + void ShowTip( const char *title, const char *tip, bool autoHide ); + void HideTip( void ); + bool IsTipVisible( void ) { return tipUp; }; + void ShowObjective( const char *obj ); + void HideObjective( void ); + idVec3 GetEyePosition( void ) const; + + void LocalClientPredictionThink( void ); + void NonLocalClientPredictionThink( void ); + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + void WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const; + void ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + bool IsBeingTalkedTo ( void ); + bool IsReady ( void ); + bool IsRespawning ( void ); + bool IsInTeleport ( void ); + bool IsZoomed ( void ); + bool IsFlashlightOn ( void ); + virtual bool IsCrouching ( void ) const; + + // voice com muting + bool IsPlayerMuted ( idPlayer* player ) const; + bool IsPlayerMuted ( int clientNum ) const; + void MutePlayer ( idPlayer* player, bool mute ); + void MutePlayer ( int clientNum, bool mute ); + + + // buddy list + void SetFriend ( idPlayer* player, bool isFriend ); + void SetFriend ( int clientNum, bool isFriend ); + bool IsFriend ( idPlayer* player ) const; + bool IsFriend ( int clientNum ) const; + + // time joined server + int GetConnectTime ( void ) const; + void SetConnectTime ( int time ); + + // emotes + void SetEmote ( playerEmote_t emote ); + + // rankings + int GetRank ( void ) const; + void SetRank ( int newRank ); + + // arenas for tourney mode + int GetArena ( void ) const; + void SetArena ( int newArena ); + + idEntity *GetInfluenceEntity( void ) { return influenceEntity; }; + const idMaterial *GetInfluenceMaterial( void ) { return influenceMaterial; }; + float GetInfluenceRadius( void ) { return influenceRadius; }; + + // server side work for in/out of spectate. takes care of spawning it into the world as well + void ServerSpectate( bool spectate ); + + // for very specific usage. != GetPhysics() + idPhysics *GetPlayerPhysics( void ); + void TeleportDeath( int killer ); + void SetLeader( bool lead ); + bool IsLeader( void ); + + void UpdateModelSetup( bool forceReload = false ); + + bool OnLadder( void ) const; + + rvViewWeapon* GetWeaponViewModel ( void ) const; + idAnimatedEntity* GetWeaponWorldModel ( void ) const; + int GetCurrentWeapon ( void ) const; + + bool IsGibbed ( void ) const; + const idVec3& GetGibDir ( void ) const; + + void SetInitialHud ( void ); + + void RemoveClientModel ( const char* entityDefName ); + void RemoveClientModels ( void ); + + rvClientEntityPtr AddClientModel ( const char* entityDefName, const char* shaderName = NULL ); + + void ClientGib ( const idVec3& dir ); + void ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ); + + + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + + void GUIMainNotice( const char* message, bool persist = false ); + void GUIFragNotice( const char* message, bool persist = false ); + + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); + + virtual void SetClipWorld( int newCW ); + virtual int GetClipWorld( void ) const; + + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + int GetWeaponIndex( const char* weaponName ) const; + + virtual void SetInstance( int newInstance ); + void JoinInstance( int newInstance ); + + void ClientInstanceJoin( void ); + void ClientInstanceLeave( void ); + + void SetHudOverlay( idUserInterface* overlay, int duration ); + + void SetShowHud( bool showHud ); + bool GetShowHud( void ); + + + // mekberg: wrap saveMessages + void SaveMessage( void ); + + // mekberg: set pm_ cvars + void SetPMCVars( void ); + + void SetTourneyStatus( playerTourneyStatus_t newStatus ) { tourneyStatus = newStatus; } + playerTourneyStatus_t GetTourneyStatus( void ) { return tourneyStatus; } + const char* GetTextTourneyStatus( void ); + + const idVec4& GetHitscanTint( void ); + + void ForceScoreboard( bool force, int time ); + + // call only on the local player + idUserInterface* GetCursorGUI( void ); + + bool CanFire( void ) const; + + bool AllowedVoiceDest( int from ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer + itemBuyStatus_t ItemBuyStatus( const char* itemName ); + bool CanBuyItem( const char* itemName ); + void GiveCash( float cashDeltaAmount ); + void ClampCash( float minCash, float maxCash ); + void SetCash( float newCashAmount ); + void ResetCash(); +// RITUAL END + +protected: + void SetupHead( const char* modelKeyName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + float vehicleCameraDist; + + jointHandle_t hipJoint; + jointHandle_t chestJoint; + + idPhysics_Player physicsObj; // player physics + + idList aasLocation; // for AI tracking the player + + idStr modelName; // current model name + const rvDeclPlayerModel* modelDecl; + + int bobFoot; + float bobFrac; + float bobfracsin; + int bobCycle; // for view bobbing and footstep generation + float xyspeed; + int stepUpTime; + float stepUpDelta; + float idealLegsYaw; + float legsYaw; + bool legsForward; + float oldViewYaw; + idAngles viewBobAngles; + idVec3 viewBob; + int landChange; + int landTime; + + // ddynerman: we read fall deltas from spawnargs, cache them to save some lookups + float fatalFallDelta; + float hardFallDelta; + float softFallDelta; + float noFallDelta; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + int carryOverCurrentWeapon; +// RITUAL END + int currentWeapon; + int idealWeapon; + int previousWeapon; + int weaponSwitchTime; + bool weaponEnabled; + bool showWeaponViewModel; + + rvClientEntityPtr clientHead; +// RAVEN BEGIN +// mekberg: allow disabling of objectives during non-cinematic time periods + bool objectivesEnabled; +// jshepard: false when player is looking at an npc or gui + bool flagCanFire; +// RAVEN END + + bool flashlightOn; + bool zoomed; + + bool reloadModel; + + const idDeclSkin * skin; + const idDeclSkin * weaponViewSkin; + const idDeclSkin * headSkin; + + const idDeclSkin * powerUpSkin; // active powerup skin + const idDeclSkin * gibSkin; + + const idMaterial* quadOverlay; + const idMaterial* hasteOverlay; + const idMaterial* regenerationOverlay; + const idMaterial* invisibilityOverlay; + const idMaterial* powerUpOverlay; + + int numProjectilesFired; // number of projectiles fired + int numProjectileHits; // number of hits on mobs + + bool airless; + int airTics; // set to pm_airTics at start, drops in vacuum + int lastAirDamage; + + bool gibDeath; + bool gibsLaunched; + idVec3 gibDir; + + playerTourneyStatus_t tourneyStatus; + bool isStrogg; + + idInterpolate zoomFov; + idInterpolate centerView; + bool fxFov; + + float influenceFov; + int influenceActive; // level of influence.. 1 == no gun or hud .. 2 == 1 + no movement + idEntity * influenceEntity; + const idMaterial * influenceMaterial; + float influenceRadius; + const idDeclSkin * influenceSkin; + + idCamera * privateCameraView; + + static const int NUM_LOGGED_VIEW_ANGLES = 64; // for weapon turning angle offsets + idAngles loggedViewAngles[NUM_LOGGED_VIEW_ANGLES]; // [gameLocal.framenum&(LOGGED_VIEW_ANGLES-1)] + static const int NUM_LOGGED_ACCELS = 16; // for weapon turning angle offsets + loggedAccel_t loggedAccel[NUM_LOGGED_ACCELS]; // [currentLoggedAccel & (NUM_LOGGED_ACCELS-1)] + int currentLoggedAccel; + + int demoViewAngleTime; + idAngles demoViewAngles; + + // if there is a focusGUIent, the attack button will be changed into mouse clicks + idUserInterface * focusUI; + int focusTime; + playerFocus_t focusType; + idEntityPtr focusEnt; + idUserInterface * focusBrackets; + int focusBracketsTime; + + bool targetFriendly; + + idEntityPtr talkingNPC; // NPC who's currently talking to us + int talkCursor; // show the state of the focusCharacter (0 == can't talk/dead, 1 == ready to talk, 2 == busy talking) + idUserInterface * cursor; + + idUserInterface * overlayHud; // a temporary hud overlay + int overlayHudTime; + + // full screen guis track mouse movements directly + int oldMouseX; + int oldMouseY; + + bool tipUp; + bool objectiveUp; + + float dynamicProtectionScale; // value to scale damage by due to dynamic protection + int lastDamageDef; + idVec3 lastDamageDir; + int lastDamageLocation; + + int predictedFrame; + idVec3 predictedOrigin; + idAngles predictedAngles; + bool predictedUpdated; + idVec3 predictionOriginError; + idAngles predictionAnglesError; + int predictionErrorTime; + + // mp + bool ready; // from userInfo + bool respawning; // set to true while in SpawnToPoint for telefrag checks + bool leader; // for sudden death situations + bool weaponCatchup; // raise up the weapon silently ( state catchups ) + bool isTelefragged; // proper obituaries + + int lastSpectateChange; + int lastTeleFX; + unsigned int lastSnapshotSequence; // track state hitches on clients + + int aimClientNum; // player num in aim + + idPlayer* lastImpulsePlayer; // the last player who gave me an impulse, may be null + + int arena; // current arena for tourney gameplay + + int connectTime; + int mutedPlayers; // bitfield set to which clients this player wants muted + int friendPlayers; // bitfield set to which clients this player has marked as friends + + int voiceDest[MAX_CONCURRENT_VOICES]; + int voiceDestTimes[MAX_CONCURRENT_VOICES]; + + int rank; + + int deathSkinTime; + bool deathStateHitch; + + playerEmote_t emote; + + int powerupEffectTime; + const idDecl *powerupEffect; + int powerupEffectType; + idList powerupEffectJoints; + rvClientEffectPtr hasteEffect; + rvClientEffectPtr flagEffect; + rvClientEffectPtr arenaEffect; + + rvClientEffectPtr teamHealthRegen; + bool teamHealthRegenPending; + rvClientEffectPtr teamAmmoRegen; + bool teamAmmoRegenPending; + rvClientEffectPtr teamDoubler; + bool teamDoublerPending; + + idVec4 hitscanTint; + // end mp + + int lastImpulseTime; // time of last impulse + idEntityPtr bossEnemy; + + const idDeclEntityDef* cachedWeaponDefs[ MAX_WEAPONS ]; + const idDeclEntityDef* cachedPowerupDefs[ POWERUP_MAX ]; + + bool weaponChangeIconsUp; + + int oldInventoryWeapons; + + const idDeclEntityDef* itemCosts; + + bool WantSmoothing( void ) const; + void PredictionErrorDecay( void ); + + bool CanZoom(void); + + void LookAtKiller( idEntity *inflictor, idEntity *attacker ); + + void StopFiring( void ); + void FireWeapon( void ); + void Weapon_Combat( void ); + void Weapon_NPC( void ); + void Weapon_GUI( void ); + void Weapon_Vehicle ( void ); + void Weapon_Usable ( void ); + void SpectateFreeFly( bool force ); // ignore the timeout to force when followed spec is no longer valid + void SpectateCycle( void ); + idAngles GunTurningOffset( void ); + idVec3 GunAcceleratingOffset( void ); + + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + void BobCycle( const idVec3 &pushVelocity ); + void EvaluateControls( void ); + void AdjustSpeed( void ); + void AdjustBodyAngles( void ); + void Move( void ); + void SetSpectateOrigin( void ); + + void InitAASLocation( void ); + void SetAASLocation( void ); + + idUserInterface * ActiveGui( void ); + + void UpdateWeapon ( void ); + void UpdateSpectating ( void ); + void UpdateAir ( void ); +// RAVEN BEGIN +// abahr + void UpdateGravity ( void ); +// nrausch: common handling for objective screen toggle + void HandleObjectiveInput ( void ); + void HandleCheats ( void ); + void ClearCheatState ( void ); +// RAVEN END + void UpdateViewAngles ( void ); + void UpdatePowerUps ( void ); + void UpdateDeathSkin ( bool state_hitch ); + void UpdateFocus ( void ); + void UpdateLocation ( void ); + void UpdateObjectiveInfo ( void ); +// void UpdateDatabaseInfo ( void ); + void UpdateIntentDir ( void ); + + void LoadDeferredModel ( void ); + + void ClearFocus ( void ); + void UpdateFocusCharacter ( idEntity* newEnt ); + void SetFocus ( playerFocus_t type, int focusTime, idEntity* ent, idUserInterface* ui ); + + void Event_GetButtons ( void ); + void Event_GetMove ( void ); + void Event_GetViewAngles ( void ); + void Event_SetViewAngles ( const idVec3 &vec ); + void Event_StopFxFov ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_GetCurrentWeapon ( void ); + void Event_GetPreviousWeapon ( void ); + void Event_SelectWeapon ( const char *weaponName ); + void Event_GetWeaponEntity ( void ); + void Event_ExitTeleporter ( void ); + void Event_HideTip ( void ); + void Event_LevelTrigger ( void ); + void Event_GetViewPos ( void ); + void Event_TeleportPlayer ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Freeze ( float f ); + void Event_HideDatabaseEntry ( void ); + void Event_ZoomIn ( void ); + void Event_ZoomOut ( void ); + void Event_FinishHearingLoss ( float fadeTime ); + void Event_GetAmmoData ( const char *ammoClass ); + void Event_RefillAmmo ( void ); + void Event_AllowFallDamage ( int toggle ); + + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + + // RAVEN BEGIN + // twhitaker: added Event_ApplyImpulse + void Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ); + + // mekberg: added sethealth + void Event_SetHealth ( float newHealth ); + void Event_SetArmor ( float newArmor ); + + void Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ); + void Event_DamageEffect ( const char *damageDefName, idEntity* _damageFromEnt ); + + // mekberg: allow enabling/disabling of objectives + void Event_EnableObjectives ( void ); + void Event_DisableObjectives ( void ); + + // mekberg: don't supress showing new objectives anymore + void Event_AllowNewObjectives ( void ); + + // twhitaker: death shader + void UpdateDeathShader ( bool state_hitch ); + + bool doInitWeapon; + void InitWeapon ( void ); + // RAVEN END + + bool IsLegsIdle ( bool crouching ) const; + + stateResult_t State_Wait_Alive ( const stateParms_t& parms ); + stateResult_t State_Wait_ReloadAnim ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_IdleThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + stateResult_t State_Torso_RaiseWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_LowerWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire_Windup ( const stateParms_t& parms ); + + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_Dead ( const stateParms_t& parms ); + stateResult_t State_Torso_Emote ( const stateParms_t& parms ); + + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Uncrouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Jump ( const stateParms_t& parms ); + stateResult_t State_Legs_Fall ( const stateParms_t& parms ); + stateResult_t State_Legs_Land ( const stateParms_t& parms ); + stateResult_t State_Legs_Dead ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( idPlayer ); +}; + +ID_INLINE bool idPlayer::IsBeingTalkedTo( void ) { + return talkingNPC!=NULL; +} + +ID_INLINE bool idPlayer::IsRespawning( void ) { + return respawning; +} + +ID_INLINE idPhysics* idPlayer::GetPlayerPhysics( void ) { + return &physicsObj; +} + +ID_INLINE bool idPlayer::IsInTeleport( void ) { + return ( teleportEntity.GetEntity() != NULL ); +} + +ID_INLINE void idPlayer::SetLeader( bool lead ) { + leader = lead; +} + +ID_INLINE bool idPlayer::IsLeader( void ) { + return leader; +} + +ID_INLINE bool idPlayer::IsZoomed( void ) { + return zoomed; +} + +ID_INLINE bool idPlayer::IsFlashlightOn( void ) { + return flashlightOn; +} + +ID_INLINE rvViewWeapon* idPlayer::GetWeaponViewModel( void ) const { + return weaponViewModel; +} + +ID_INLINE idAnimatedEntity* idPlayer::GetWeaponWorldModel( void ) const { + return weaponWorldModel; +} + +ID_INLINE int idPlayer::GetCurrentWeapon( void ) const { + return currentWeapon; +} + +ID_INLINE bool idPlayer::IsGibbed( void ) const { + return gibDeath; +} + +ID_INLINE const idVec3& idPlayer::GetGibDir( void ) const { + return gibDir; +} + +ID_INLINE int idPlayer::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idPlayer::SetClipWorld( int newCW ) { + idEntity::SetClipWorld( newCW ); + + if( head.GetEntity() ) { + head.GetEntity()->SetClipWorld( newCW ); + + head.GetEntity()->UnlinkCombat(); + head.GetEntity()->LinkCombat(); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetClipWorld( newCW ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetClipWorld( newCW ); + } + } + + UnlinkCombat(); + LinkCombat(); +} + +ID_INLINE int idPlayer::GetWeaponIndex( const char* weaponName ) const { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( !idStr::Icmp( spawnArgs.GetString( va( "def_weapon%d", i ), "" ), weaponName ) ) { + return i; + } + } + + return 0; +} + +ID_INLINE idUserInterface* idPlayer::GetHud() { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE const idUserInterface* idPlayer::GetHud() const { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE bool idPlayer::IsPlayerMuted( idPlayer* player ) const { + return !!( mutedPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsPlayerMuted( int clientNum ) const { + return !!( mutedPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::MutePlayer( idPlayer* player, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << player->entityNumber ); + } else { + mutedPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::MutePlayer( int clientNum, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << clientNum ); + } else { + mutedPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE bool idPlayer::IsFriend( idPlayer* player ) const { + return !!( friendPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsFriend( int clientNum ) const { + return !!( friendPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::SetFriend( idPlayer* player, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << player->entityNumber ); + } else { + friendPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::SetFriend( int clientNum, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << clientNum ); + } else { + friendPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE int idPlayer::GetConnectTime( void ) const { + return connectTime; +} + +ID_INLINE void idPlayer::SetConnectTime( int time ) { + connectTime = time; +} + +ID_INLINE int idPlayer::GetRank( void ) const { + return rank; +} + +ID_INLINE void idPlayer::SetRank( int newRank ) { + rank = newRank; +} + +ID_INLINE int idPlayer::GetArena( void ) const { + return arena; +} + +ID_INLINE bool idPlayer::CanFire( void ) const { + return flagCanFire; +} + +#endif /* !__GAME_PLAYER_H__ */ + +// RAVEN END diff --git a/src/game/PlayerView.cpp b/src/game/PlayerView.cpp new file mode 100644 index 0000000..6626ddd --- /dev/null +++ b/src/game/PlayerView.cpp @@ -0,0 +1,808 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if defined(_XENON) && (defined(_PROFILE) || defined(_DEBUG)) +#include "../sys/xenon/ProfilingSupport.h" +#endif + +const int IMPULSE_DELAY = 150; +/* +============== +idPlayerView::idPlayerView +============== +*/ +idPlayerView::idPlayerView() { + memset( screenBlobs, 0, sizeof( screenBlobs ) ); + memset( &view, 0, sizeof( view ) ); + player = NULL; + dvMaterial = NULL; + dvMaterialBlend = NULL; + tunnelMaterial = NULL; + armorMaterial = NULL; + bloodSprayMaterial = NULL; + bfgVision = false; + dvFinishTime = 0; + tvFinishTime = 0; + tvStartTime = 0; + kickFinishTime = 0; + kickAngles.Zero(); + lastDamageTime = 0.0f; + fadeTime = 0; + fadeRate = 0.0; + fadeFromColor.Zero(); + fadeToColor.Zero(); + fadeColor.Zero(); + + ClearEffects(); + + dvScale = 1.0f; + shakeScale = 1.0f; + tvScale = 1.0f; +} + +/* +============== +idPlayerView::Save +============== +*/ +void idPlayerView::Save( idSaveGame *savefile ) const { + int i; + const screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->WriteMaterial( blob->material ); + savefile->WriteFloat( blob->x ); + savefile->WriteFloat( blob->y ); + savefile->WriteFloat( blob->w ); + savefile->WriteFloat( blob->h ); + savefile->WriteFloat( blob->s1 ); + savefile->WriteFloat( blob->t1 ); + savefile->WriteFloat( blob->s2 ); + savefile->WriteFloat( blob->t2 ); + savefile->WriteInt( blob->finishTime ); + savefile->WriteInt( blob->startFadeTime ); + savefile->WriteFloat( blob->driftAmount ); + } + + savefile->WriteInt( dvFinishTime ); + savefile->WriteMaterial( dvMaterial ); + + savefile->WriteMaterial( dvMaterialBlend ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( dvScale ); + + savefile->WriteInt( kickFinishTime ); + savefile->WriteAngles( kickAngles ); + + savefile->WriteBool( bfgVision ); + + savefile->WriteMaterial( tunnelMaterial ); + savefile->WriteMaterial( armorMaterial ); + + savefile->WriteMaterial( bloodSprayMaterial ); + + savefile->WriteFloat( lastDamageTime ); + + savefile->WriteFloat( shakeFinishTime ); + savefile->WriteFloat( shakeScale ); + savefile->WriteFloat( tvScale ); + savefile->WriteInt( tvFinishTime ); + savefile->WriteInt( tvStartTime ); + + savefile->WriteVec4( fadeColor ); + savefile->WriteVec4( fadeToColor ); + savefile->WriteVec4( fadeFromColor ); + savefile->WriteFloat( fadeRate ); + savefile->WriteInt( fadeTime ); + + savefile->WriteObject( player ); + savefile->WriteRenderView( view ); +} + +/* +============== +idPlayerView::Restore +============== +*/ +void idPlayerView::Restore( idRestoreGame *savefile ) { + int i; + screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->ReadMaterial( blob->material ); + savefile->ReadFloat( blob->x ); + savefile->ReadFloat( blob->y ); + savefile->ReadFloat( blob->w ); + savefile->ReadFloat( blob->h ); + savefile->ReadFloat( blob->s1 ); + savefile->ReadFloat( blob->t1 ); + savefile->ReadFloat( blob->s2 ); + savefile->ReadFloat( blob->t2 ); + savefile->ReadInt( blob->finishTime ); + savefile->ReadInt( blob->startFadeTime ); + savefile->ReadFloat( blob->driftAmount ); + } + + savefile->ReadInt( dvFinishTime ); + savefile->ReadMaterial( dvMaterial ); + + savefile->ReadMaterial( dvMaterialBlend ); // cnicholson: Added unrestored var + + savefile->ReadFloat( dvScale ); + + savefile->ReadInt( kickFinishTime ); + savefile->ReadAngles( kickAngles ); + + savefile->ReadBool( bfgVision ); + + savefile->ReadMaterial( tunnelMaterial ); + savefile->ReadMaterial( armorMaterial ); + + savefile->ReadMaterial( bloodSprayMaterial ); + + savefile->ReadFloat( lastDamageTime ); + + savefile->ReadFloat( shakeFinishTime ); + savefile->ReadFloat( shakeScale ); + savefile->ReadFloat( tvScale ); + savefile->ReadInt( tvFinishTime ); + savefile->ReadInt ( tvStartTime ); + + savefile->ReadVec4( fadeColor ); + savefile->ReadVec4( fadeToColor ); + savefile->ReadVec4( fadeFromColor ); + savefile->ReadFloat( fadeRate ); + savefile->ReadInt( fadeTime ); + + savefile->ReadObject( reinterpret_cast( player ) ); + savefile->ReadRenderView( view ); +} + +/* +============== +idPlayerView::SetPlayerEntity +============== +*/ +void idPlayerView::SetPlayerEntity( idPlayer *playerEnt ) { + player = playerEnt; + + const idDict* dict = NULL; + if( !playerEnt ) { + return; + } + + dict = gameLocal.FindEntityDefDict( playerEnt->spawnArgs.GetString("def_playerView"), false ); + + if( dict ) { + dvMaterial = declManager->FindMaterial( dict->GetString("mtr_doubleVision") ); + dvMaterialBlend = declManager->FindMaterial( dict->GetString("mtr_doubleVisionBlend") ); + tunnelMaterial = declManager->FindMaterial( dict->GetString("mtr_tunnel") ); + armorMaterial = declManager->FindMaterial( dict->GetString("mtr_armourEffect") ); + bloodSprayMaterial = declManager->FindMaterial( dict->GetString("mtr_bloodspray") ); + lagoMaterial = declManager->FindMaterial( LAGO_MATERIAL, false ); + } +} + +/* +============== +idPlayerView::ClearEffects +============== +*/ +void idPlayerView::ClearEffects() { + lastDamageTime = MS2SEC( gameLocal.time - 99999 ); + + dvFinishTime = ( gameLocal.time - 99999 ); + tvFinishTime = ( gameLocal.time - 99999 ); + tvStartTime = ( gameLocal.time - 99999 ); + kickFinishTime = ( gameLocal.time - 99999 ); + shakeFinishTime = gameLocal.time; + + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlobs[i].finishTime = gameLocal.time; + } + + fadeTime = 0; + bfgVision = false; +} + +/* +============== +idPlayerView::GetScreenBlob +============== +*/ +screenBlob_t *idPlayerView::GetScreenBlob() { + screenBlob_t *oldest = &screenBlobs[0]; + + for ( int i = 1 ; i < MAX_SCREEN_BLOBS ; i++ ) { + if ( screenBlobs[i].finishTime < oldest->finishTime ) { + oldest = &screenBlobs[i]; + } + } + return oldest; +} + +/* +============== +idPlayerView::DamageImpulse + +LocalKickDir is the direction of force in the player's coordinate system, +which will determine the head kick direction +============== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ) { + // + // double vision effect + // + float tvTime = damageDef->GetFloat( "tv_time" ); + if ( tvTime ) { + tvStartTime = gameLocal.time; + tvFinishTime = gameLocal.time + tvTime; + damageDef->GetFloat ( "tv_scale", "1", tvScale ); + } + + if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.time ) { + // keep shotgun from obliterating the view + return; + } + + // + // double vision effect + // + float dvTime = damageDef->GetFloat( "dv_time" ); + if ( dvTime ) { + dvFinishTime = gameLocal.time + (g_dvTime.GetFloat() * dvTime); + damageDef->GetFloat ( "dv_scale", "1", dvScale ); + } + + // + // head angle kick + // + const float modifierScale = 0.25f; + const float inverseModifier = ( 1.0f - modifierScale ); + + float modifier = idMath::ClampFloat( 0.0f, inverseModifier, damage / 100.0f * inverseModifier ) + modifierScale; + float kickTime = damageDef->GetFloat( "kick_time" ); + + if ( kickTime ) { + kickFinishTime = gameLocal.time + g_kickTime.GetFloat() * kickTime; + + // forward / back kick will pitch view + kickAngles[0] = localKickDir[0]; + + // side kick will yaw view + kickAngles[1] = localKickDir[1]*0.5f; + + // up / down kick will pitch view + kickAngles[0] += localKickDir[2]; + + // roll will come from side + kickAngles[2] = localKickDir[1]; + + float kickAmplitude = damageDef->GetFloat( "kick_amplitude" ); + if ( kickAmplitude ) { + kickAngles *= kickAmplitude; + } + + if ( modifier < kickAmplitude ) { + modifier = kickAmplitude; + } + } + else { + kickTime = 500; + } + + // + // screen blob + // + float blobTime = damageDef->GetFloat( "blob_time" ); + if ( blobTime ) { + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + blobTime * g_blobTime.GetFloat(); + + const char *materialName = damageDef->GetString( "mtr_blob" ); + blob->material = declManager->FindMaterial( materialName ); + blob->x = damageDef->GetFloat( "blob_x" ); + blob->x += ( gameLocal.random.RandomInt()&63 ) - 32; + blob->y = damageDef->GetFloat( "blob_y" ); + blob->y += ( gameLocal.random.RandomInt()&63 ) - 32; + + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = damageDef->GetFloat( "blob_width" ) * g_blobSize.GetFloat() * scale; + blob->h = damageDef->GetFloat( "blob_height" ) * g_blobSize.GetFloat() * scale; + blob->s1 = 0; + blob->t1 = 0; + blob->s2 = 1; + blob->t2 = 1; + } + + // + // save lastDamageTime for tunnel vision accentuation + // + lastDamageTime = MS2SEC( gameLocal.time ); +} +// RAVEN END + +/* +================== +idPlayerView::AddBloodSpray + +If we need a more generic way to add blobs then we can do that +but having it localized here lets the material be pre-looked up etc. +================== +*/ +void idPlayerView::AddBloodSpray( float duration ) { +/* + if ( duration <= 0 || bloodSprayMaterial == NULL || g_skipViewEffects.GetBool() ) { + return; + } + // visit this for chainsaw + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + ( duration * 1000 ); + blob->material = bloodSprayMaterial; + blob->x = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->y = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->driftAmount = 0.5f + gameLocal.random.CRandomFloat() * 0.5; + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = 600 * g_blobSize.GetFloat() * scale; + blob->h = 480 * g_blobSize.GetFloat() * scale; + float s1 = 0.0f; + float t1 = 0.0f; + float s2 = 1.0f; + float t2 = 1.0f; + if ( blob->driftAmount < 0.6 ) { + s1 = 1.0f; + s2 = 0.0f; + } else if ( blob->driftAmount < 0.75 ) { + t1 = 1.0f; + t2 = 0.0f; + } else if ( blob->driftAmount < 0.85 ) { + s1 = 1.0f; + s2 = 0.0f; + t1 = 1.0f; + t2 = 0.0f; + } + blob->s1 = s1; + blob->t1 = t1; + blob->s2 = s2; + blob->t2 = t2; +*/ +} + +/* +================== +idPlayerView::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayerView::WeaponFireFeedback( const idDict *weaponDef ) { + int recoilTime; + + recoilTime = weaponDef->GetInt( "recoilTime" ); + // don't shorten a damage kick in progress + if ( recoilTime && recoilTime > (kickFinishTime - gameLocal.time) ) { + idAngles angles; + weaponDef->GetAngles( "recoilAngles", "5 0 0", angles ); + kickAngles = angles; + int finish = gameLocal.time + g_kickTime.GetFloat() * recoilTime; + kickFinishTime = finish; + } + +} + +/* +=================== +idPlayerView::CalculateShake +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +float idPlayerView::CalculateShake( idAngles &shakeAngleOffset ) const { + idVec3 origin, matrix; + + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, player->firstPersonViewOrigin ); + // + // shakeVolume should somehow be molded into an angle here + // it should be thought of as being in the range 0.0 -> 1.0, although + // since CurrentShakeAmplitudeForPosition() returns all the shake sounds + // the player can hear, it can go over 1.0 too. + // + shakeAngleOffset[0] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[1] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[2] = gameLocal.random.CRandomFloat() * shakeVolume; + + return shakeVolume; +} +// RAVEN END + +/* +=================== +idPlayerView::AngleOffset + + kickVector, a world space direction that the attack should +=================== +*/ +idAngles idPlayerView::AngleOffset() const { + idAngles ang; + + ang.Zero(); + + if ( gameLocal.time < kickFinishTime ) { + float offset = kickFinishTime - gameLocal.time; + + ang = kickAngles * offset * offset * g_kickAmplitude.GetFloat(); + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( ang[i] > 70.0f ) { + ang[i] = 70.0f; + } else if ( ang[i] < -70.0f ) { + ang[i] = -70.0f; + } + } + } + return ang; +} + +/* +=================== +idPlayerView::ShakeOffsets +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const { + float shakeVolume = 0.0f; + shakeOffset.Zero(); + shakeAngleOffset.Zero(); + + if( gameLocal.isMultiplayer ) { + return; + } + + shakeVolume = CalculateShake( shakeAngleOffset ); + + if( gameLocal.time < shakeFinishTime ) { + float offset = ( shakeFinishTime - gameLocal.time ) * shakeScale * 0.001f; + + shakeOffset[0] = idMath::ClampFloat( bounds[0][0] - 1.0f, bounds[1][0] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[1] = idMath::ClampFloat( bounds[0][1] - 1.0f, bounds[1][1] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[2] = idMath::ClampFloat( bounds[0][2] - 1.0f, bounds[1][2] + 1.0f, rvRandom::flrand( -offset, offset ) ); + + shakeAngleOffset[0] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[1] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[2] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + } +} +// RAVEN END + +/* +================== +idPlayerView::SingleView +================== +*/ +void idPlayerView::SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags ) { + // normal rendering + if ( !view ) { + return; + } + + if ( !( RF_GUI_ONLY & renderFlags ) ) { + // jscott: portal sky rendering with KRABS + idCamera *portalSky = gameLocal.GetPortalSky(); + if( portalSky ) { + renderView_t portalSkyView = *view; + portalSky->GetViewParms( &portalSkyView ); + gameRenderWorld->RenderScene( &portalSkyView, ( renderFlags & ( ~RF_PRIMARY_VIEW ) ) | RF_DEFER_COMMAND_SUBMIT | RF_PORTAL_SKY ); + } + gameRenderWorld->RenderScene( view, renderFlags | RF_PENUMBRA_MAP ); + } + + if ( RF_NO_GUI & renderFlags ) { + return; + } + + // draw screen blobs + if ( !pm_thirdPerson.GetBool() && !g_skipViewEffects.GetBool() ) { + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlob_t *blob = &screenBlobs[i]; + if ( blob->finishTime <= gameLocal.time ) { + continue; + } + + blob->y += blob->driftAmount; + + float fade = (float)( blob->finishTime - gameLocal.time ) / ( blob->finishTime - blob->startFadeTime ); + if ( fade > 1.0f ) { + fade = 1.0f; + } + if ( fade ) { + renderSystem->SetColor4( 1,1,1,fade ); + renderSystem->DrawStretchPic( blob->x, blob->y, blob->w, blob->h,blob->s1, blob->t1, blob->s2, blob->t2, blob->material ); + } + } + + // Render tunnel vision + if ( gameLocal.time < tvFinishTime ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, tvScale * ((float)(tvFinishTime - gameLocal.time) / (float)(tvFinishTime - tvStartTime)) ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } + + player->DrawHUD( hud ); + + +/* + // tunnel vision + float health = 0.0f; + if ( g_testHealthVision.GetFloat() != 0.0f ) { + health = g_testHealthVision.GetFloat(); + } else { + health = player->health; + } + float alpha = health / 100.0f; + if ( alpha < 0.0f ) { + alpha = 0.0f; + } + if ( alpha > 1.0f ) { + alpha = 1.0f; + } + + if ( alpha < 1.0f ) { + renderSystem->SetColor4( ( player->health <= 0.0f ) ? MS2SEC( gameLocal.time ) : lastDamageTime, 1.0f, 1.0f, ( player->health <= 0.0f ) ? 0.0f : alpha ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } +*/ + + + + // Render the object system + // RAVEN BEGIN + // twhitaker: always draw objective system + if ( player->objectiveSystem ) { + player->objectiveSystem->Redraw( gameLocal.time ); + } + // RAVEN END + } + + // test a single material drawn over everything + if ( g_testPostProcess.GetString()[0] ) { + const idMaterial *mtr = declManager->FindMaterial( g_testPostProcess.GetString(), false ); + if ( !mtr ) { + common->Printf( "Material not found.\n" ); + g_testPostProcess.SetString( "" ); + } else { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, mtr ); + } + } +} + +/* +=================== +idPlayerView::DoubleVision +=================== +*/ +void idPlayerView::DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ) { + + if ( !g_doubleVision.GetBool() ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } + + float scale = offset * g_dvAmplitude.GetFloat() * dvScale; + if( scale < 0.0f ) { + return; + } + + if ( scale > 0.5f ) { + scale = 0.5f; + } + float shift = scale * idMath::Sin( idMath::Sqrt ( offset ) * g_dvFrequency.GetFloat() ); + shift = fabs( shift ); + + // if double vision, render to a texture + renderSystem->CropRenderSize( 512, 256, true ); + SingleView( hud, view, RF_NO_GUI ); + renderSystem->CaptureRenderToImage( "_scratch" ); + renderSystem->UnCrop(); + + // carry red tint if in berserk mode + idVec4 color(1, 1, 1, 1); + + renderSystem->SetColor4( color.x, color.y, color.z, 1.0f ); +// RAVEN BEGIN +// jnewquist: Call DrawStretchCopy, which will flip the texcoords for D3D + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, shift, 1, 1, 0, dvMaterial ); + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1, 1-shift, 0, dvMaterialBlend ); +// RAVEN END +} + +/* +================= +idPlayerView::Flash + +flashes the player view with the given color +================= +*/ +void idPlayerView::Flash(idVec4 color, int time ) { + Fade(idVec4(0, 0, 0, 0), time); + fadeFromColor = colorWhite; +} + +/* +================= +idPlayerView::Fade + +used for level transition fades +assumes: color.w is 0 or 1 +================= +*/ +void idPlayerView::Fade( idVec4 color, int time ) { + + if ( !fadeTime ) { + fadeFromColor.Set( 0.0f, 0.0f, 0.0f, 1.0f - color[ 3 ] ); + } else { + fadeFromColor = fadeColor; + } + fadeToColor = color; + + if ( time <= 0 ) { + fadeRate = 0; + time = 0; + fadeColor = fadeToColor; + } else { + fadeRate = 1.0f / ( float )time; + } + + if ( gameLocal.realClientTime == 0 && time == 0 ) { + fadeTime = 1; + } else { + fadeTime = gameLocal.realClientTime + time; + } +} + +/* +================= +idPlayerView::ScreenFade +================= +*/ +void idPlayerView::ScreenFade() { + int msec; + float t; + + if ( !fadeTime ) { + return; + } + + msec = fadeTime - gameLocal.realClientTime; + + if ( msec <= 0 ) { + fadeColor = fadeToColor; + if ( fadeColor[ 3 ] == 0.0f ) { + fadeTime = 0; + } + } else { + t = ( float )msec * fadeRate; + fadeColor = fadeFromColor * t + fadeToColor * ( 1.0f - t ); + } + + if ( fadeColor[ 3 ] != 0.0f ) { + renderSystem->SetColor4( fadeColor[ 0 ], fadeColor[ 1 ], fadeColor[ 2 ], fadeColor[ 3 ] ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); + } +} + +/* +=================== +idPlayerView::InfluenceVision +=================== +*/ +void idPlayerView::InfluenceVision( idUserInterface *hud, const renderView_t *view ) { + + float distance = 0.0f; + float pct = 1.0f; + if ( player->GetInfluenceEntity() ) { + distance = ( player->GetInfluenceEntity()->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).Length(); + if ( player->GetInfluenceRadius() != 0.0f && distance < player->GetInfluenceRadius() ) { + pct = distance / player->GetInfluenceRadius(); + pct = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, pct ); + } + } + if ( player->GetInfluenceMaterial() ) { + SingleView( hud, view ); + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, pct ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, player->GetInfluenceMaterial() ); + } else if ( player->GetInfluenceEntity() == NULL ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } else { + int offset = 25 + idMath::Sin ( gameLocal.time ); + DoubleVision( hud, view, pct * offset ); + } +} + +/* +=================== +idPlayerView::RenderPlayerView +=================== +*/ +void idPlayerView::RenderPlayerView( idUserInterface *hud ) { + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( !view ) { + return; + } + + bool guiRendered = false; + + // place the sound origin for the player + soundSystem->PlaceListener( view->vieworg, view->viewaxis, player->entityNumber + 1, gameLocal.time, "Undefined" ); + + if ( g_skipViewEffects.GetBool() ) { + SingleView( hud, view ); + } else { + if ( player->GetInfluenceMaterial() || player->GetInfluenceEntity() ) { + InfluenceVision( hud, view ); + guiRendered = true; + } else if ( g_doubleVision.GetBool() && gameLocal.time < dvFinishTime ) { + DoubleVision( hud, view, dvFinishTime - gameLocal.time ); + guiRendered = false; + } else { + SingleView( hud, view, RF_NO_GUI | RF_PRIMARY_VIEW ); + } + + // Now draw GUI's. + if ( !guiRendered ) { + SingleView( hud, view, RF_GUI_ONLY ); + } + + ScreenFade(); + } + + if ( net_clientLagOMeter.GetBool() && lagoMaterial && gameLocal.isClient && !( gameLocal.GetDemoState() == DEMO_PLAYING && ( gameLocal.IsServerDemo() || gameLocal.IsTimeDemo() ) ) ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 10.0f, 380.0f, 64.0f, 64.0f, 0.0f, 0.0f, 1.0f, 1.0f, lagoMaterial ); + } + +} + +// RAVEN END diff --git a/src/game/PlayerView.h b/src/game/PlayerView.h new file mode 100644 index 0000000..81a4035 --- /dev/null +++ b/src/game/PlayerView.h @@ -0,0 +1,167 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYERVIEW_H__ +#define __GAME_PLAYERVIEW_H__ + +/* +=============================================================================== + + Player view. + +=============================================================================== +*/ + +// screenBlob_t are for the on-screen damage claw marks, etc +typedef struct { + const idMaterial * material; + float x, y, w, h; + float s1, t1, s2, t2; + int finishTime; + int startFadeTime; + float driftAmount; +} screenBlob_t; + +#define MAX_SCREEN_BLOBS 8 + +class idPlayerView { +public: + idPlayerView(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPlayerEntity( class idPlayer *playerEnt ); + + void ClearEffects( void ); + +// RAVEN BEGIN +// jnewquist: Controller rumble + void DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ); +// RAVEN END + + void WeaponFireFeedback( const idDict *weaponDef ); + + idAngles AngleOffset( void ) const; // returns the current kick angle + +// RAVEN BEGIN +// jnewquist: Controller rumble + float CalculateShake( idAngles &shakeAngleOffset ) const; +// RAVEN END + +// RAVEN BEGIN +// jscott: for screen shake + void ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const; +// RAVEN END + + // adds little entities to the renderer for local blood blobs, etc + + // this may involve rendering to a texture and displaying + // that with a warp model or in double vision mode + void RenderPlayerView( idUserInterface *hud ); + + void Fade( idVec4 color, int time ); + + void Flash( idVec4 color, int time ); + + void AddBloodSpray( float duration ); + + // temp for view testing + void EnableBFGVision( bool b ) { bfgVision = b; }; + +// RAVEN BEGIN +// jscott: accessors required for the fx system + void SetDoubleVisionParms( float time, float scale ) { dvFinishTime = SEC2MS( time ); dvScale = scale; } + void SetShakeParms( float time, float scale ) { shakeFinishTime = SEC2MS( time ); shakeScale = scale; } + void SetTunnelParms( float time, float scale ) { tvStartTime = gameLocal.time; tvFinishTime = tvStartTime + time; tvScale = 1.0f / scale; } +// RAVEN END + +private: +// RAVEN BEGIN +// AReis: Modified SingleView() signature to include renderFlags variable. + void SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags = RF_NORMAL ); +// RAVEN END + void DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ); + void BerserkVision( idUserInterface *hud, const renderView_t *view ); + void InfluenceVision( idUserInterface *hud, const renderView_t *view ); + void ScreenFade(); + + screenBlob_t * GetScreenBlob(); + + screenBlob_t screenBlobs[MAX_SCREEN_BLOBS]; + + int dvFinishTime; // double vision will be stopped at this time + const idMaterial * dvMaterial; // material to take the double vision screen shot +// RAVEN BEGIN +// jscott: to make double vision work with alpha components + const idMaterial * dvMaterialBlend; +// jscott: for effects + float dvScale; +// RAVEN END + + int kickFinishTime; // view kick will be stopped at this time + idAngles kickAngles; + + bool bfgVision; // + + const idMaterial * tunnelMaterial; // health tunnel vision + const idMaterial * armorMaterial; // armor damage view effect +// RAVEN BEGIN +// bdube: not using these +// const idMaterial * berserkMaterial; // berserk effect +// const idMaterial * irGogglesMaterial; // ir effect + const idMaterial * bloodSprayMaterial; // blood spray +// const idMaterial * bfgMaterial; // when targeted with BFG +// RAVEN END + const idMaterial * lagoMaterial; // lagometer drawing + + float lastDamageTime; // accentuate the tunnel effect for a while + +// RAVEN BEGIN +// jscott: for effects + float shakeFinishTime; + float shakeScale; + float tvScale; + int tvFinishTime; + int tvStartTime; +// RAVEN END + + idVec4 fadeColor; // fade color + idVec4 fadeToColor; // color to fade to + idVec4 fadeFromColor; // color to fade from + float fadeRate; // fade rate + int fadeTime; // fade time + + idPlayer * player; + renderView_t view; +}; + +#endif /* !__GAME_PLAYERVIEW_H__ */ + +// RAVEN END diff --git a/src/game/Player_Cheats.cpp b/src/game/Player_Cheats.cpp new file mode 100644 index 0000000..9ea2163 --- /dev/null +++ b/src/game/Player_Cheats.cpp @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +============== +idPlayer::HandleCheats +============== +*/ +void idPlayer::HandleCheats() { +} + + +void idPlayer::ClearCheatState() { +} diff --git a/src/game/Player_States.cpp b/src/game/Player_States.cpp new file mode 100644 index 0000000..05dee60 --- /dev/null +++ b/src/game/Player_States.cpp @@ -0,0 +1,943 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +CLASS_STATES_DECLARATION ( idPlayer ) + + // Wait States + STATE ( "Wait_Alive", idPlayer::State_Wait_Alive ) + STATE ( "Wait_ReloadAnim", idPlayer::State_Wait_ReloadAnim ) + + // Torso States + STATE ( "Torso_Idle", idPlayer::State_Torso_Idle ) + STATE ( "Torso_IdleThink", idPlayer::State_Torso_IdleThink ) + STATE ( "Torso_Teleport", idPlayer::State_Torso_Teleport ) + STATE ( "Torso_RaiseWeapon", idPlayer::State_Torso_RaiseWeapon ) + STATE ( "Torso_LowerWeapon", idPlayer::State_Torso_LowerWeapon ) + STATE ( "Torso_Fire", idPlayer::State_Torso_Fire ) + STATE ( "Torso_Fire_Windup", idPlayer::State_Torso_Fire_Windup ) + STATE ( "Torso_Reload", idPlayer::State_Torso_Reload ) + STATE ( "Torso_Pain", idPlayer::State_Torso_Pain ) + STATE ( "Torso_Dead", idPlayer::State_Torso_Dead ) + STATE ( "Torso_Emote", idPlayer::State_Torso_Emote ) + + // Leg States + STATE ( "Legs_Idle", idPlayer::State_Legs_Idle ) + STATE ( "Legs_Crouch", idPlayer::State_Legs_Crouch ) + STATE ( "Legs_Uncrouch", idPlayer::State_Legs_Uncrouch ) + STATE ( "Legs_Run_Forward", idPlayer::State_Legs_Run_Forward ) + STATE ( "Legs_Run_Backward", idPlayer::State_Legs_Run_Backward ) + STATE ( "Legs_Run_Left", idPlayer::State_Legs_Run_Left ) + STATE ( "Legs_Run_Right", idPlayer::State_Legs_Run_Right ) + STATE ( "Legs_Walk_Forward", idPlayer::State_Legs_Walk_Forward ) + STATE ( "Legs_Walk_Backward", idPlayer::State_Legs_Walk_Backward ) + STATE ( "Legs_Walk_Left", idPlayer::State_Legs_Walk_Left ) + STATE ( "Legs_Walk_Right", idPlayer::State_Legs_Walk_Right ) + STATE ( "Legs_Crouch_Idle", idPlayer::State_Legs_Crouch_Idle ) + STATE ( "Legs_Crouch_Forward", idPlayer::State_Legs_Crouch_Forward ) + STATE ( "Legs_Crouch_Backward", idPlayer::State_Legs_Crouch_Backward ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Jump", idPlayer::State_Legs_Jump ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Land", idPlayer::State_Legs_Land ) + STATE ( "Legs_Dead", idPlayer::State_Legs_Dead ) + +END_CLASS_STATES + +/* +================ +idPlayer::State_Torso_Idle +================ +*/ +stateResult_t idPlayer::State_Torso_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT == State_Torso_IdleThink ( parms ) ) { + PlayCycle ( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_IdleThink", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_IdleThink +================ +*/ +stateResult_t idPlayer::State_Torso_IdleThink ( const stateParms_t& parms ) { + if ( pfl.teleport ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Teleport", 0 ); + return SRESULT_DONE; + } + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 0 ); + return SRESULT_DONE; + } + + if( pfl.attackHeld && weapon && weapon->wfl.hasWindupAnim) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_Windup", 0 ); + return SRESULT_DONE; + } + + if ( pfl.attackHeld && HasAnim ( ANIMCHANNEL_TORSO, "startfire" ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_StartFire", 2 ); + return SRESULT_DONE; + } + if ( pfl.pain ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Pain", 0 ); + return SRESULT_DONE; + } + if ( emote != PE_NONE ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Emote", 5 ); + return SRESULT_DONE; + } + + + return SRESULT_WAIT; +} + +/* +================ +idPlayer::State_Torso_Teleport +================ +*/ +stateResult_t idPlayer::State_Torso_Teleport ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + pfl.teleport = false; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Torso_RaiseWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_RaiseWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "raise", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_LowerWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_LowerWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "lower", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire +================ +*/ +stateResult_t idPlayer::State_Torso_Fire ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_FIRE_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_FIRE_WAIT); + + // Wait for the firing animation to be finished + case TORSO_FIRE_WAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", parms.blendFrames ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Keep the gun aimed but dont shoot + case TORSO_FIRE_AIM: + PlayAnim ( ANIMCHANNEL_TORSO, "aim", 3 ); + return SRESULT_STAGE(TORSO_FIRE_AIMWAIT); + + // Keep the gun aimed as long as the attack button is held and nothing is firing + case TORSO_FIRE_AIMWAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 3 ); + return SRESULT_DONE; + } else if ( !pfl.attackHeld ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire_Windup +================ +*/ +stateResult_t idPlayer::State_Torso_Fire_Windup ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_WINDUP_WAIT, + TORSO_FIRE_LOOP, + TORSO_FIRE_WAIT, + TORSO_WINDDOWN_START, + TORSO_WINDDOWN_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + //jshepard: HACK for now we're blending here, but we need to support charge up anims here + PlayAnim ( ANIMCHANNEL_TORSO, "fire", 4 ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_WINDUP_WAIT); + + // wait for the windup anim to end, or the attackHeld to be false. + case TORSO_WINDUP_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + if( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames )) { + return SRESULT_STAGE( TORSO_FIRE_LOOP ); + } + return SRESULT_WAIT; + + // play the firing loop + case TORSO_FIRE_LOOP: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + + // loop the fire anim + case TORSO_FIRE_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + //loop the attack anim + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + } + return SRESULT_WAIT; + + //wind down + case TORSO_WINDDOWN_START: + //jshepard: HACK just blend back into idle for now, we could support winddown anims here. + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + pfl.weaponFired = false; + return SRESULT_DONE; + + } + return SRESULT_DONE; +} +/* +================ +idPlayer::State_Torso_Reload +================ +*/ +stateResult_t idPlayer::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + TORSO_RELOAD_START, + TORSO_RELOAD_STARTWAIT, + TORSO_RELOAD_LOOP, + TORSO_RELOAD_LOOPWAIT, + TORSO_RELOAD_WAIT, + TORSO_RELOAD_END + }; + switch ( parms.stage ) { + // Start the reload by either playing the reload animation or the reload_start animation + case TORSO_RELOAD_START: + if ( HasAnim ( ANIMCHANNEL_TORSO, "reload_start" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "reload_start", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_STARTWAIT); + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for the reload_start animation to finish and transition to reload_loop + case TORSO_RELOAD_STARTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Play a single reload from the reload loop + case TORSO_RELOAD_LOOP: + if ( !pfl.reload ) { + return SRESULT_STAGE(TORSO_RELOAD_END); + } + PlayAnim ( ANIMCHANNEL_TORSO, "reload_loop", 0 ); + return SRESULT_STAGE(TORSO_RELOAD_LOOPWAIT); + + // Wait for the looping reload to finish and either start a new one or end the reload + case TORSO_RELOAD_LOOPWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // End the reload + case TORSO_RELOAD_END: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload_end", 3 ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for reload to finish (called by both reload_end and reload) + case TORSO_RELOAD_WAIT: + if ( pfl.weaponFired || AnimDone ( ANIMCHANNEL_TORSO, 3 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Pain +================ +*/ +stateResult_t idPlayer::State_Torso_Pain ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, painAnim.Length()?painAnim:"pain", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Dead +================ +*/ +stateResult_t idPlayer::State_Torso_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Emote +================ +*/ +stateResult_t idPlayer::State_Torso_Emote ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if( emote == PE_GRAB_A ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_a", parms.blendFrames ); + } else if( emote == PE_GRAB_B ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_b", parms.blendFrames ); + } else if( emote == PE_SALUTE ) { + PlayAnim ( ANIMCHANNEL_TORSO, "salute", parms.blendFrames ); + } else if( emote == PE_CHEER ) { + PlayAnim ( ANIMCHANNEL_TORSO, "cheer", parms.blendFrames ); + } else if( emote == PE_TAUNT ) { + PlayAnim ( ANIMCHANNEL_TORSO, "taunt", parms.blendFrames ); + } + + emote = PE_NONE; + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idPlayer::IsLegsIdle +================ +*/ +bool idPlayer::IsLegsIdle ( bool crouching ) const { + return ( (pfl.crouch == crouching) && pfl.onGround && (pfl.forward==pfl.backward) && (pfl.strafeLeft==pfl.strafeRight) ); +} + +/* +================ +idPlayer::State_Legs_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", parms.blendFrames ); + return SRESULT_DONE; + } + IdleAnim ( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + // If now crouching go back to idle so we can transition to crouch + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", 4 ); + return SRESULT_DONE; + } else if ( pfl.jump ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Jump", 4 ); + return SRESULT_DONE; + } else if ( !pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + return SRESULT_DONE; + }else if ( pfl.forward && !pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeLeft && !pfl.strafeRight ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeRight && !pfl.strafeLeft ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", parms.blendFrames ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "crouch", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.crouch || pfl.jump ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", 4 ); + return SRESULT_DONE; + } else if ( (pfl.forward && !pfl.backward) || (pfl.strafeLeft != pfl.strafeRight) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Forward", parms.blendFrames ); + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_down", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( true ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Uncrouch +================ +*/ +stateResult_t idPlayer::State_Legs_Uncrouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_up", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Run_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Forward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && ((!pfl.backward && pfl.forward) || (pfl.strafeLeft != pfl.strafeRight)) ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Backward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk_backward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && !pfl.forward && pfl.backward ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Jump +================ +*/ +stateResult_t idPlayer::State_Legs_Jump ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // prevent infinite recursion + pfl.jump = false; + if ( pfl.run ) { + PlayAnim ( ANIMCHANNEL_LEGS, "run_jump", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "jump", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Fall +================ +*/ +stateResult_t idPlayer::State_Legs_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "fall", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Land +================ +*/ +stateResult_t idPlayer::State_Legs_Land ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( IsLegsIdle ( false ) && ( pfl.hardLanding || pfl.softLanding ) ) { + if ( pfl.hardLanding ) { + PlayAnim ( ANIMCHANNEL_LEGS, "hard_land", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "soft_land", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Dead +================ +*/ +stateResult_t idPlayer::State_Legs_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_Alive + +Waits until the player is alive again. +================ +*/ +stateResult_t idPlayer::State_Wait_Alive ( const stateParms_t& parms ) { + if ( pfl.dead ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_ReloadAnim +================ +*/ +stateResult_t idPlayer::State_Wait_ReloadAnim ( const stateParms_t& parms ) { + // The gun firing can cancel any of the relod animations + if ( pfl.weaponFired ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // wait for the animation to finish + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} diff --git a/src/game/Projectile.cpp b/src/game/Projectile.cpp new file mode 100644 index 0000000..22a3f8d --- /dev/null +++ b/src/game/Projectile.cpp @@ -0,0 +1,2148 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" +#include "Projectile.h" +#include "spawner.h" + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; + +const idEventDef EV_Explode( "", NULL ); +const idEventDef EV_Fizzle( "", NULL ); +const idEventDef EV_RadiusDamage( "", "E" ); +const idEventDef EV_ResidualDamage ( "", "E" ); + +CLASS_DECLARATION( idEntity, idProjectile ) + EVENT( EV_Explode, idProjectile::Event_Explode ) + EVENT( EV_Fizzle, idProjectile::Event_Fizzle ) + EVENT( EV_Touch, idProjectile::Event_Touch ) + EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) + EVENT( EV_ResidualDamage, idProjectile::Event_ResidualDamage ) +END_CLASS + +/* +================ +idProjectile::idProjectile +================ +*/ +idProjectile::idProjectile( void ) { + methodOfDeath = -1; + owner = NULL; + memset( &projectileFlags, 0, sizeof( projectileFlags ) ); + damagePower = 1.0f; + + memset( &renderLight, 0, sizeof( renderLight ) ); + + lightDefHandle = -1; + lightOffset.Zero(); + lightStartTime = 0; + lightEndTime = 0; + lightColor.Zero(); + + visualAngles.Zero(); + angularVelocity.Zero(); + speed.Init( 0.0f, 0.0f, 0.0f, 0.0f ); + updateVelocity = false; + + rotation.Init( 0, 0.0f, mat3_identity.ToQuat(), mat3_identity.ToQuat() ); + + flyEffect = NULL; + flyEffectAttenuateSpeed = 0.0f; + bounceCount = 0; + hitCount = 0; + state = SPAWNED; + + fl.networkSync = true; + + prePredictTime = 0; + + syncPhysics = false; + + launchTime = 0; + launchOrig = vec3_origin; + launchDir = vec3_origin; + launchSpeed = 0.0f; +} + +/* +================ +idProjectile::Spawn +================ +*/ +void idProjectile::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( 0 ); + physicsObj.PutToRest(); + SetPhysics( &physicsObj ); + prePredictTime = spawnArgs.GetInt( "predictTime", "0" ); + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( gameLocal.isClient ) { + Hide(); + } +} + +/* +================ +idProjectile::Save +================ +*/ +void idProjectile::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( methodOfDeath ); // cnicholson: Added unsaved var + owner.Save( savefile ); + savefile->Write( &projectileFlags, sizeof( projectileFlags ) ); + savefile->WriteFloat( damagePower ); + + savefile->WriteRenderLight( renderLight ); + + savefile->WriteInt( ( int )lightDefHandle ); + savefile->WriteVec3( lightOffset ); + savefile->WriteInt( lightStartTime ); + savefile->WriteInt( lightEndTime ); + savefile->WriteVec3( lightColor ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteAngles( visualAngles ); // cnicholson: added unsaved var + savefile->WriteAngles( angularVelocity ); // cnicholson: moved var + + // Save speed + savefile->WriteBool ( updateVelocity ); + savefile->WriteFloat( speed.GetStartTime() ); + savefile->WriteFloat( speed.GetDuration() ); + savefile->WriteFloat( speed.GetStartValue() ); + savefile->WriteFloat( speed.GetEndValue() ); + + // rotation; this is a class, so it doesnt get saved here + + flyEffect.Save( savefile ); // cnicholson: added unsaved var + savefile->WriteFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->WriteInt ( bounceCount ); + savefile->WriteInt ( hitCount ); + + savefile->WriteInt( (int)state ); +} + +/* +================ +idProjectile::Restore +================ +*/ +void idProjectile::Restore( idRestoreGame *savefile ) { + float fset; + idVec3 temp; + + savefile->ReadInt( methodOfDeath ); // cnicholson: Added unrestored var + owner.Restore( savefile ); + savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); + savefile->ReadFloat( damagePower ); + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadInt( (int &)lightDefHandle ); + if ( lightDefHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + savefile->ReadVec3( lightOffset ); + savefile->ReadInt( lightStartTime ); + savefile->ReadInt( lightEndTime ); + savefile->ReadVec3( lightColor ); + + // Restore the physics + savefile->ReadStaticObject( physicsObj ); + RestorePhysics ( &physicsObj ); + + savefile->ReadAngles( visualAngles ); // cnicholson: added unrestored var + savefile->ReadAngles( angularVelocity ); // cnicholson: moved var + + // Restore speed + savefile->ReadBool ( updateVelocity ); + savefile->ReadFloat( fset ); + speed.SetStartTime( fset ); + savefile->ReadFloat( fset ); + speed.SetDuration( fset ); + savefile->ReadFloat( fset ); + speed.SetStartValue( fset ); + savefile->ReadFloat( fset ); + speed.SetEndValue( fset ); + + // rotation? + + flyEffect.Restore( savefile ); // cnicholson: added unrestored var + savefile->ReadFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->ReadInt ( bounceCount ); + savefile->ReadInt ( hitCount ); + + savefile->ReadInt( (int &)state ); +} + +/* +================ +idProjectile::GetOwner +================ +*/ +idEntity *idProjectile::GetOwner( void ) const { + return owner.GetEntity(); +} + +/* +================ +idProjectile::SetSpeed +================ +*/ +void idProjectile::SetSpeed( float s, int accelTime ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + speed.Init( gameLocal.time, accelTime, speed.GetCurrentValue(gameLocal.time), s ); + + if ( accelTime > 0 ) { + updateVelocity = true; + } else { + updateVelocity = false; + } + + // Update the velocity to match the direction we are facing and include any accelerations + physicsObj.SetLinearVelocity( speed.GetCurrentValue( gameLocal.time ) * vel ); +} + +/* +================ +idProjectile::Create +================ +*/ +void idProjectile::Create( idEntity* _owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore, idEntity* extraPassEntity ) { + idDict args; + idStr shaderName; + idVec3 light_color; + idVec3 light_offset; + idVec3 tmp; + idMat3 axis; + + Unbind(); + + axis = dir.ToMat3(); + + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + + physicsObj.GetClipModel()->SetOwner( ignore ? ignore : _owner ); + physicsObj.extraPassEntity = extraPassEntity; + + owner = _owner; + + memset( &renderLight, 0, sizeof( renderLight ) ); + shaderName = spawnArgs.GetString( "mtr_light_shader" ); + if ( *shaderName ) { + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius[0] = + renderLight.lightRadius[1] = + renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); + spawnArgs.GetVector( "light_color", "1 1 1", light_color ); + renderLight.shaderParms[0] = light_color[0]; + renderLight.shaderParms[1] = light_color[1]; + renderLight.shaderParms[2] = light_color[2]; + renderLight.shaderParms[3] = 1.0f; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: set the projectile lights to be no shadows + renderLight.noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + } + + spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); + + lightStartTime = 0; + lightEndTime = 0; + + damagePower = 1.0f; + + UpdateVisuals(); + + state = CREATED; +} + +/* +================= +idProjectile::~idProjectile +================= +*/ +idProjectile::~idProjectile() { + StopSound( SND_CHANNEL_ANY, false ); + FreeLightDef(); + SetPhysics( NULL ); +} + +/* +================= +idProjectile::FreeLightDef +================= +*/ +void idProjectile::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================= +idProjectile::Launch +================= +*/ +void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float dmgPower ) { + float fuse; + idVec3 velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + float temp, temp2; + idVec3 gravVec; + idVec3 tmp; + int contents; + int clipMask; + + // allow characters to throw projectiles during cinematics, but not the player + if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + cinematic = owner.GetEntity()->cinematic; + } else { + cinematic = false; + } + + // Set the damage + damagePower = dmgPower; + + if ( !spawnArgs.GetFloat( "speed", "0", temp ) ) { + spawnArgs.GetVector( "velocity", "0 0 0", tmp ); + temp = tmp[0]; + } else { + float speedRandom; + if ( !spawnArgs.GetFloat( "speedRandom", "0", speedRandom ) ) { + temp += gameLocal.random.CRandomFloat()*speedRandom; + } + } + if ( !spawnArgs.GetFloat( "speed_end", "0", temp2 ) ) { + temp2 = temp; + } + float speedDuration; + speedDuration = SEC2MS( spawnArgs.GetFloat( "speed_duration", "0" ) ); + speed.Init( gameLocal.time, speedDuration, temp, temp2 ); + if ( speedDuration > 0 && temp != temp2 ) { + // only support constant velocity projectiles in MP + // ( we also assume that no MP projectiles use speedRandom ) + assert( !gameLocal.isServer ); + updateVelocity = true; + } + launchSpeed = temp; + + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angularVelocity ); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + angular_friction = spawnArgs.GetFloat( "angular_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + fuse = spawnArgs.GetFloat( "fuse" ) + ( spawnArgs.GetFloat( "fuse_random", "0" ) * gameLocal.random.RandomFloat() ); + bounceCount = spawnArgs.GetInt( "bounce_count", "-1" ); + + //spawn impact entity information + impactEntity = spawnArgs.GetString("def_impactEntity",""); + numImpactEntities = spawnArgs.GetInt("numImpactEntities","0"); + ieMinPitch = spawnArgs.GetInt("ieMinPitch","0"); + ieMaxPitch = spawnArgs.GetInt("ieMaxPitch","0"); + ieSlicePercentage = spawnArgs.GetFloat("ieSlicePercentage","0.0"); + + projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); + projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); + projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); + projectileFlags.detonate_on_bounce = spawnArgs.GetBool( "detonate_on_bounce" ); + + lightStartTime = 0; + lightEndTime = 0; + + impactedEntity = 0; + + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + gravVec = ( idMath::Fabs(gravity) > VECTOR_EPSILON ) ? gameLocal.GetCurrentGravity(this) * gravity : vec3_zero; +// RAVEN END + + Unbind(); + + contents = 0; + clipMask = spawnArgs.GetBool( "clipmask_rendermodel", "1" ) ? MASK_SHOT_RENDERMODEL : MASK_SHOT_BOUNDINGBOX; + + // all projectiles are projectileclip + clipMask |= CONTENTS_PROJECTILECLIP; + + if ( spawnArgs.GetBool( "clipmask_largeshot", "1" ) ) { + clipMask |= CONTENTS_LARGESHOTCLIP; + } + + if ( spawnArgs.GetBool( "clipmask_moveable", "0" ) ) { + clipMask |= CONTENTS_MOVEABLECLIP; + } + if ( spawnArgs.GetBool( "clipmask_monsterclip", "0" ) ) { + clipMask |= CONTENTS_MONSTERCLIP; + } + + if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) { + contents |= CONTENTS_TRIGGER; + } + + if ( !spawnArgs.GetBool( "no_contents", "1" ) ) { + contents |= CONTENTS_PROJECTILE; + } + + clipMask |= CONTENTS_PROJECTILE; + + // don't do tracers on client, we don't know origin and direction + if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) { + SetModel( spawnArgs.GetString( "model_tracer" ) ); + projectileFlags.isTracer = true; + } + + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + physicsObj.SetBouncyness( bounce, !projectileFlags.detonate_on_bounce ); + physicsObj.SetGravity( gravVec ); + physicsObj.SetContents( contents ); + physicsObj.SetClipMask( clipMask | CONTENTS_WATER ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( dir.ToMat3() ); + + if ( !gameLocal.isClient ) { + if ( fuse <= 0 ) { + // run physics for 1 second + RunPhysics(); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); + } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Explode, fuse ); + } else { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Fizzle, fuse ); + } + } + + idQuat q( dir.ToMat3().ToQuat() ); + rotation.Init( gameLocal.GetTime(), 0.0f, q, q ); + + if ( projectileFlags.isTracer ) { + StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); + } else { + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); + } + + // used for the plasma bolts but may have other uses as well + if ( projectileFlags.randomShaderSpin ) { + float f = gameLocal.random.RandomFloat(); + f *= 0.5f; + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f; + } + + UpdateVisuals(); + + // Make sure these come after update visuals so the origin and axis are correct + PlayEffect( "fx_launch", renderEntity.origin, renderEntity.axis ); + + flyEffect = PlayEffect( "fx_fly", renderEntity.origin, renderEntity.axis, true ); + flyEffectAttenuateSpeed = spawnArgs.GetFloat( "flyEffectAttenuateSpeed", "0" ); + + state = LAUNCHED; + + hitCount = 0; + + predictTime = prePredictTime; + + if ( gameLocal.isServer ) { + // store launch information for networking + launchTime = gameLocal.time; + launchOrig = physicsObj.GetOrigin(); + launchDir = dir; + } + + if ( g_perfTest_noProjectiles.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idProjectile::Think +================ +*/ +void idProjectile::Think( void ) { + // run physics + if ( thinkFlags & TH_PHYSICS ) { + + // Update the velocity to match the changing speed + if ( updateVelocity ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize ( ); + physicsObj.SetLinearVelocity ( speed.GetCurrentValue ( gameLocal.time ) * vel ); + if ( speed.IsDone ( gameLocal.time ) ) { + updateVelocity = false; + } + } + + RunPhysics(); + + // If we werent at rest and are now then start the atrest fuse + if ( physicsObj.IsAtRest( ) ) { + float fuse = spawnArgs.GetFloat( "fuse_atrest" ); + if ( fuse > 0.0f ) { + if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + CancelEvents( &EV_Explode ); + PostEventSec( &EV_Explode, fuse ); + } else { + CancelEvents( &EV_Fizzle ); + PostEventSec( &EV_Fizzle, fuse ); + } + } + } + + // Stop the trail effect if the physics flag was removed + if ( flyEffect && flyEffectAttenuateSpeed > 0.0f ) { + if ( physicsObj.IsAtRest( ) ) { + flyEffect->Stop( ); + flyEffect = NULL; + } else { + float speed; + speed = idMath::ClampFloat( 0, flyEffectAttenuateSpeed, physicsObj.GetLinearVelocity ( ).LengthFast ( ) ); + flyEffect->Attenuate( speed / flyEffectAttenuateSpeed ); + } + } + + UpdateVisualAngles(); + } + + Present(); + + // add the light + if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) { + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset; + renderLight.axis = GetPhysics()->GetAxis(); + if ( ( lightDefHandle != -1 ) ) { + if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) { + idVec3 color( 0, 0, 0 ); + if ( gameLocal.time < lightEndTime ) { + float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime ); + color.Lerp( lightColor, color, frac ); + } + renderLight.shaderParms[SHADERPARM_RED] = color.x; + renderLight.shaderParms[SHADERPARM_GREEN] = color.y; + renderLight.shaderParms[SHADERPARM_BLUE] = color.z; + } + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } +} + +/* +================= +idProjectile::UpdateVisualAngles +================= +*/ +void idProjectile::UpdateVisualAngles() { + idVec3 linearVelocity( GetPhysics()->GetLinearVelocity() ); + + if( angularVelocity.Compare(ang_zero, VECTOR_EPSILON) ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), linearVelocity.ToNormal().ToMat3().ToQuat() ); + return; + } + + if( physicsObj.GetNumContacts() ) { + return; + } + + if( !rotation.IsDone(gameLocal.GetTime()) ) { + return; + } + + if( linearVelocity.Length() <= BOUNCE_SOUND_MIN_VELOCITY ) { + return; + } + + visualAngles += angularVelocity; + idQuat q = visualAngles.ToQuat() * linearVelocity.ToNormal().ToMat3().ToQuat(); + rotation.Init( gameLocal.GetTime(), gameLocal.GetMSec(), rotation.GetCurrentValue(gameLocal.GetTime()), q ); +} + +/* +================= +idProjectile::Collide +================= +*/ +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { + bool dummy = false; + return Collide( collision, velocity, dummy ); +} + +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { + idEntity* ent; + idEntity* actualHitEnt = NULL; + idEntity* ignore; + const char* damageDefName; + idVec3 dir; + bool canDamage; + + hitTeleporter = false; + + if ( state == EXPLODED || state == FIZZLED ) { + return true; + } + + // allow projectiles to hit triggers (teleports) + // predict this on a client + if( collision.c.contents & CONTENTS_TRIGGER ) { + idEntity* trigger = gameLocal.entities[ collision.c.entityNum ]; + + if( trigger ) { + if ( trigger->RespondsTo( EV_Touch ) || trigger->HasSignal( SIG_TOUCH ) ) { + + hitTeleporter = true; + + trace_t trace; + + trace.endpos = physicsObj.GetOrigin(); + trace.endAxis = physicsObj.GetAxis(); + + trace.c.contents = collision.c.contents; + trace.c.entityNum = collision.c.entityNum; + if( trigger->GetPhysics()->GetClipModel() ) { + trace.c.id = trigger->GetPhysics()->GetClipModel()->GetId(); + } else { + trace.c.id = 0; + } + + + trigger->Signal( SIG_TOUCH ); + trigger->ProcessEvent( &EV_Touch, this, &trace ); + } + } + + // when we hit a trigger, align our velocity to the trigger's coordinate plane + if( gameLocal.isServer ) { + idVec3 up( 0.0f, 0.0f, 1.0f ); + idVec3 right = collision.c.normal.Cross( up ); + idMat3 mat( collision.c.normal, right, up ); + + physicsObj.SetLinearVelocity( -1.0f * (physicsObj.GetLinearVelocity() * mat.Transpose()) ); + physicsObj.SetLinearVelocity( idVec3( physicsObj.GetLinearVelocity()[ 0 ], -1.0 *physicsObj.GetLinearVelocity()[ 1 ], -1.0 * physicsObj.GetLinearVelocity()[ 2 ] ) ); + + // update the projectile's launchdir and launch origin + // this will propagate the change to the clients for prediction + // re-launch the projectile + + idVec3 newDir = physicsObj.GetLinearVelocity(); + newDir.Normalize(); + launchTime = gameLocal.time; + launchDir = newDir; + physicsObj.SetOrigin( physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 32.0f ) ); + physicsObj.SetAxis( newDir.ToMat3() ); + launchOrig = physicsObj.GetOrigin(); + } + + if( !(collision.c.contents & CONTENTS_SOLID) || hitTeleporter ) { + return false; + } + } + + // don't predict collisions on projectiles that need their physics synced (e.g. grenades) + if ( gameLocal.isClient && (!g_clientProjectileCollision.GetBool() || syncPhysics) ) { + // optionally do not try to predict detonates, that causes projectiles disappearing + // but most of the time works well to stop projectiles clipping through geometry onc lients + return false; + } + + // remove projectile when a 'noimpact' surface is hit + if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + PostEventMS( &EV_Remove, 0 ); + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + return true; + } + + // get the entity the projectile collided with + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent == owner.GetEntity() ) { + // assert( 0 ); // twhitaker: this isn't necessary + return true; + } + + // just get rid of the projectile when it hits a player in noclip + if ( ent->IsType( idPlayer::GetClassType() ) && static_cast( ent )->noclip ) { + PostEventMS( &EV_Remove, 0 ); + common->DPrintf( "Projectile collision no impact\n" ); + return true; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster( ); + } + + // Can the projectile damage? + canDamage = ent->fl.takedamage && !(( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE )); + + // direction of projectile + dir = velocity; + dir.Normalize(); + + // projectiles can apply an additional impulse next to the rigid body physics impulse +// RAVEN BEGIN +// abahr: added call to SkipDamageImpulse changed where push comes from + damageDefName = NULL; + if ( collision.c.materialType ) { + damageDefName = spawnArgs.GetString( va("def_damage_%s", collision.c.materialType->GetName()) ); + } + if ( !damageDefName || !*damageDefName ) { + damageDefName = spawnArgs.GetString ( "def_damage" ); + } + + if( damageDefName && damageDefName[0] ) { + const idDict* dict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( dict ) { + ent->ApplyImpulse( this, collision.c.id, collision.endpos, dir, dict ); + } + } +// RAVEN END + + //Spawn any impact entities if necessary. + SpawnImpactEntities(collision, velocity); + + //Apply any impact force if the necessary + //ApplyImpactForce(ent, collision, dir); + + // MP: projectiles open doors + if ( gameLocal.isMultiplayer && ent->IsType( idDoor::GetClassType() ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { + ent->ProcessEvent( &EV_Activate , this ); + } + + // If the projectile hits water then we need to let the projectile keep going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + if ( !physicsObj.IsInWater( ) ) { + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + } + // Pass through water + return false; + } else if ( canDamage && ent->IsType( idActor::GetClassType() ) ) { + if ( !projectileFlags.detonate_on_actor ) { + return false; + } + } else { + bool bounce = false; + + // Determine if the projectile should bounce + bounce = !physicsObj.IsInWater() && !projectileFlags.detonate_on_world && !canDamage; + bounce = bounce && (bounceCount == -1 || bounceCount > 0); + //assert(collision.c.material); + if ( !bounce && collision.c.material && (collision.c.material->GetSurfaceFlags() & SURF_BOUNCE) ) { + bounce = !projectileFlags.detonate_on_bounce; + } + + if ( bounce ) { + if ( bounceCount != -1 ) { + bounceCount--; + } + + StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ); + + float len = velocity.Length(); + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( ent->IsType ( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } + } else { + // FIXME: clean up + idMat3 axis( rotation.GetCurrentValue(gameLocal.GetTime()).ToMat3() ); + axis[0].ProjectOntoPlane( collision.c.normal ); + axis[0].Normalize(); + axis[2] = collision.c.normal; + axis[1] = axis[2].Cross( axis[0] ).ToNormal(); + + rotation.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("settle_duration")), rotation.GetCurrentValue(gameLocal.GetTime()), axis.ToQuat() ); + } + if ( actualHitEnt + && actualHitEnt != ent + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + actualHitEnt->Damage( this, owner, dir, damageDefName, damagePower, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + } + } + return false; + } + } + + SetOrigin( collision.endpos ); +// SetAxis( collision.endAxis ); + + // unlink the clip model because we no longer need it + GetPhysics()->UnlinkClip(); + + ignore = NULL; + +// RAVEN BEGIN +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + bool willPlayDamageEffect = true; + +// jnewquist: Use accessor for static class type + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + // if the projectile hit an ai + if ( ent->IsType( idAI::GetClassType() ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + player->AddProjectileHits( 1 ); + +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + idAI * ai_ent = static_cast(ent); + if( ai_ent->team == player->team) { + willPlayDamageEffect = false; + } + } + } + +// RAVEN END + + // if the hit entity takes damage + if ( canDamage ) { + + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); +// RAVEN BEGIN +// jdischler: code from the 'other' project..to ensure that if an attached head is hit, the body will use the head joint +// otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(collision.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == collision.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } +// RAVEN END + ent->Damage( this, owner, dir, damageDefName, damagePower, hitJoint ); + + if( owner && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) ) { + statManager->WeaponHit( (const idActor*)(owner.GetEntity()), ent, methodOfDeath, hitCount == 0 ); + hitCount++; + } + } + } + + ignore = ent; + + // TODO: fixme + ent->AddDamageEffect ( collision, velocity, damageDefName, owner ); + +// RAVEN BEGIN +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any effects. +// this should make sure that only explosion effects play when the player shoots his comrades. + if( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) { + DefaultDamageEffect( collision, velocity, damageDefName ); + } +// RAVEN END + +/* + // if the projectile causes a damage effect + bool explodeFX = true; + if ( spawnArgs.GetBool( "impact_damage_effect" ) ) { + // if the hit entity has a special damage effect + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + ent->AddDamageEffect( collision, velocity, damageDefName ); + } else { + DefaultDamageEffect( collision, velocity, damageDefName ); + explodeFX = false; + } + } +*/ + + // don't predict explosions on clients + if( gameLocal.isClient ) { + return true; + } + + Explode( &collision, false, ignore ); + + return true; +} + +void idProjectile::SpawnImpactEntities(const trace_t& collision, const idVec3 velocity) +{ + if( impactEntity.Length() == 0 || numImpactEntities == 0 ) + return; + + const idDict* impactEntityDict = gameLocal.FindEntityDefDict(impactEntity); + if(impactEntityDict == NULL) + return; + + idVec3 tempDirection; + idVec3 direction; + direction.Zero(); + + idVec3 up = collision.c.normal; + + //Calculate the axes for that are oriented to the impact point. + idMat3 impactAxes; + + idVec3 right = velocity.Cross(up); + idVec3 forward = up.Cross(right); + + right.Normalize(); + forward.Normalize(); + impactAxes[0] = forward; + impactAxes[1] = right; + impactAxes[2] = up; + + //Calculate the reflection vector by calculating the forward component and up component of the projectile direction + //idVec3 reflectionVelocity = (forward * (velocity*0.33f * forward));// - (up * (velocity * up)); + idVec3 reflectionVelocity = up * 0.01f; + + //The algorithm below will launch entities at a random pitch and somewhat random yaw. + //The yaw is calculated by dividing 360 by the number of entities to spawn. This creates + //a distribution slice. Then using the slice percentage, this will determine how much of the + //slice to use. + //This creates a random,but somewhat even coverage of the circle. + + //Calculate the slice size and pick a random start position. + int sliceSize = 360 / numImpactEntities; + int startPosition = rvRandom::irand(0, 360); + + //Move the origin away from the collision point. This prevents the projectiles + //from colliding with the surface. + idVec3 origin = collision.endpos; + origin += 10.0f * collision.c.normal; + for(int i = 0;i < numImpactEntities; i++) + { + idProjectile* spawnProjectile = NULL; + gameLocal.SpawnEntityDef(*impactEntityDict,(idEntity**)&spawnProjectile); + if(spawnProjectile != NULL) + { + int pitch = rvRandom::irand(ieMinPitch, ieMaxPitch); + int sliceMiddle = (i * sliceSize) + startPosition; + int sliceSloppiness = (sliceSize * ieSlicePercentage) / 2; + + int yaw = rvRandom::irand(sliceMiddle - sliceSloppiness, sliceMiddle + sliceSloppiness); + yaw = yaw % 360; + + float cosPitch = idMath::Cos(DEG2RAD(pitch)); + tempDirection.x = cosPitch * idMath::Cos(DEG2RAD(yaw)); + tempDirection.y = cosPitch * idMath::Sin(DEG2RAD(yaw)); + tempDirection.z = idMath::Sin(DEG2RAD(pitch)); + + spawnProjectile->SetOwner(owner); + + //Now orient the direction to the surface world orientation. + direction = impactAxes * tempDirection; + spawnProjectile->Launch(origin, direction, reflectionVelocity); + } + } +} + +/* +================= +idProjectile::DefaultDamageEffect +================= +*/ +void idProjectile::DefaultDamageEffect( const trace_t &tr, const idVec3 &velocity, const char *damageDefName ) { + idEntity* ent; + idMat3 axis; + ent = gameLocal.entities[ tr.c.entityNum ]; + + // Make sure we want to play effects + if ( (!*spawnArgs.GetString( "def_splash_damage" ) || spawnArgs.GetBool( "bloodyImpactEffect" )) + && owner.GetEntity( ) && !ent->CanPlayImpactEffect( owner, ent ) ) { + return; + } + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType( idActor::GetClassType() ) || ent->IsType( idAFAttachment::GetClassType() ) ) { + idVec3 dir; + dir = velocity; + dir.Normalize ( ); + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + + // Play an actor specific impact effect? + const idDecl *actorImpactEffect = gameLocal.GetEffect( spawnArgs, "fx_impact_actor", tr.c.materialType ); + if ( actorImpactEffect ) { + gameLocal.PlayEffect( actorImpactEffect, tr.c.point, axis, false, vec3_origin, true ); + return; + } + } else { + axis = tr.c.normal.ToMat3(); + } + + // Play an impact effect on the entity that got hit + if ( ent->IsType( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, true ); + } +} + +/* +================ +idProjectile::Killed +================ +*/ +void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + Explode( NULL, true ); + physicsObj.ClearContacts(); + physicsObj.PutToRest(); + } else { + Fizzle(); + } +} + +/* +================ +idProjectile::Fizzle +================ +*/ +void idProjectile::Fizzle( void ) { + + if ( gameLocal.isClient || state == EXPLODED || state == FIZZLED ) { + return; + } + + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); + + gameLocal.PlayEffect( spawnArgs, "fx_fuse", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + physicsObj.PutToRest(); + + // No more fly effects + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + + Hide(); + FreeLightDef(); + + state = FIZZLED; + + CancelEvents( &EV_Fizzle ); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); +} + +/* +================ +idProjectile::Event_RadiusDamage +================ +*/ +void idProjectile::Event_RadiusDamage( idEntity *ignore ) { + const char *splash_damage = spawnArgs.GetString( "def_splash_damage" ); + if ( splash_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, splash_damage, damagePower, &hitCount ); + } +} + +/* +================ +idProjectile::Event_ResidualDamage +================ +*/ +void idProjectile::Event_ResidualDamage ( idEntity* ignore ) { + const char *residual_damage = spawnArgs.GetString( "def_residual_damage" ); + if ( residual_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, residual_damage, damagePower, &hitCount ); + } + + // Keep the loop going + PostEventSec ( &EV_ResidualDamage, spawnArgs.GetFloat ( "delay_residual" ), ignore ); +} + +/* +================ +idProjectile::Explode +================ +*/ +void idProjectile::Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore, const char *sndExplode ) { + idVec3 normal, endpos; + int removeTime; + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { + GetPhysics()->SetAxis( normal.ToMat3() ); + } else { + normal = collision ? collision->c.normal : idVec3( 0, 0, 1 ); + } + endpos = ( collision ) ? collision->endpos : GetPhysics()->GetOrigin(); + + removeTime = spawnArgs.GetInt( "remove_time", "1500" ); + + // play sound + StopSound( SND_CHANNEL_BODY, false ); + StartSound( sndExplode, SND_CHANNEL_BODY, 0, false, NULL ); + + if ( showExplodeFX ) { + idVec3 fxDir; + if ( physicsObj.GetGravityNormal( ) != vec3_zero ) { + fxDir = -physicsObj.GetGravityNormal( ); + } else { + fxDir = -physicsObj.GetLinearVelocity( ); + fxDir.Normalize( ); + } + // FIXME: This should be done in a better way + PlayDetonateEffect( endpos, fxDir.ToMat3() ); + } + + // Stop the fly effect without destroying particles to ensure the trail within can persist. + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + + // Stop the remaining particles + StopAllEffects( ); + + Hide(); + FreeLightDef(); + + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + 8.0f * normal ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + state = EXPLODED; + + if ( gameLocal.isClient ) { + return; + } + + // alert the ai + gameLocal.AlertAI( owner.GetEntity() ); + + // bind the projectile to the impact entity if necesary + if ( collision && gameLocal.entities[collision->c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { + Bind( gameLocal.entities[collision->c.entityNum], true ); + } + + // splash damage + removeTime = 0; + float delay = spawnArgs.GetFloat( "delay_splash" ); + if ( delay ) { + if ( removeTime < delay * 1000 ) { + removeTime = ( delay + 0.10 ) * 1000; + } + PostEventSec( &EV_RadiusDamage, delay, ignore ); + } else { + Event_RadiusDamage( ignore ); + } + + // Residual damage (damage over time) + delay = SEC2MS ( spawnArgs.GetFloat ( "delay_residual" ) ); + if ( delay > 0.0f ) { + PostEventMS ( &EV_ResidualDamage, delay, ignore ); + + // Keep the projectile around until the residual damage is done + delay = SEC2MS ( spawnArgs.GetFloat ( "residual_time" ) ); + if ( removeTime < delay ) { + removeTime = delay; + } + } + + CancelEvents( &EV_Explode ); + PostEventMS( &EV_Remove, removeTime ); +} + +/* +================ +idProjectile::GetVelocity +================ +*/ +idVec3 idProjectile::GetVelocity( const idDict *projectile ) { + idVec3 velocity; + + velocity.Zero ( ); + if ( projectile && !projectile->GetFloat ( "speed", "0", velocity.x ) ) { + projectile->GetVector( "velocity", "0 0 0", velocity ); + } + return velocity; +} + +/* +================ +idProjectile::GetGravity +================ +*/ +idVec3 idProjectile::GetGravity( const idDict *projectile ) { + if ( projectile ) { + return gameLocal.GetGravity ( ) * projectile->GetFloat( "gravity" ); + } + return vec3_origin; +} + +/* +================ +idProjectile::PlayPainEffect +================ +*/ +void idProjectile::PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& dir ) { + static int damageTable[] = { 100, 50, 25, 10, 0 }; + int index; + + // Normalize the damage value to the damage table + for ( index = 0; damage < damageTable[index] && damageTable[index]; index ++ ); + + // loop until we find a pain effect, trying lower damage numbers if needed + for ( ; damageTable[index]; index ++ ) { + // Try the pain effect for the current damage value and if it plays then + // we are done + if ( ent->PlayEffect( gameLocal.GetEffect( spawnArgs, va( "fx_pain%d", damageTable[index] ), materialType ), origin, dir.ToMat3() ) ) { + return; + } + } + + // Play the default pain effect + ent->PlayEffect( gameLocal.GetEffect ( spawnArgs, "fx_pain", materialType ), origin, dir.ToMat3() ); +} + +/* +================ +idProjectile::PlayDetonateEffect +================ +*/ +void idProjectile::PlayDetonateEffect( const idVec3& origin, const idMat3& axis ) { + if( physicsObj.HasGroundContacts() ) { + if ( spawnArgs.GetBool( "detonateTestGroundMaterial" ) ) { + trace_t tr; + idVec3 down; + down = GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal()*8.0f; + gameLocal.Translation( this, tr, GetPhysics()->GetOrigin(), down, GetPhysics()->GetClipModel(), GetPhysics()->GetClipModel()->GetAxis(), GetPhysics()->GetClipMask(), this ); + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), origin, axis, false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( spawnArgs, "fx_impact", origin, axis, false, vec3_origin, true ); + } + return; + } + + gameLocal.PlayEffect( spawnArgs, "fx_detonate", origin, axis, false, vec3_origin, true ); +} + +/* +================ +idProjectile::Event_Explode +================ +*/ +void idProjectile::Event_Explode( void ) { + // events are processed outside of the think loop, so set the current thinking ent appropriately + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Explode( NULL, true ); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Fizzle +================ +*/ +void idProjectile::Event_Fizzle( void ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Fizzle(); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Touch +================ +*/ +void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { + if ( IsHidden() ) { + return; + } + + if ( other != owner.GetEntity() ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + DefaultDamageEffect( collision, collision.c.normal, NULL ); + Explode( NULL, true ); + + gameLocal.currentThinkingEntity = think; + } +} + +/* +================ +idProjectile::ClientPredictionThink +================ +*/ +void idProjectile::ClientPredictionThink( void ) { + if ( !renderEntity.hModel && clientEntities.IsListEmpty() ) { + return; + } + if ( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + Think(); +} + +/* +================ +idProjectile::WriteToSnapshot +================ +*/ +void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + + msg.WriteBits( state, 3 ); + if ( state >= LAUNCHED ) { + // feed the client with start position, direction and time. let the client do everything else + // this won't change during projectile life and be completely deltified away + msg.WriteLong( launchTime ); + msg.WriteFloat( launchOrig[ 0 ] ); + msg.WriteFloat( launchOrig[ 1 ] ); + msg.WriteFloat( launchOrig[ 2 ] ); + msg.WriteDir( launchDir, 24 ); + } +} + +/* +================ +idProjectile::ReadFromSnapshot +================ +*/ +void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { + projectileState_t newState; + + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + + newState = (projectileState_t) msg.ReadBits( 3 ); + if ( newState >= LAUNCHED ) { + launchTime = msg.ReadLong(); + launchOrig[ 0 ] = msg.ReadFloat(); + launchOrig[ 1 ] = msg.ReadFloat(); + launchOrig[ 2 ] = msg.ReadFloat(); + launchDir = msg.ReadDir( 24 ); + } + // we always create and launch at the same time + // state on a client can be SPAWNED, LAUNCHED, EXPLODED + // expect never to get a CREATED projectile, they should launch right away + assert( state != CREATED && state != FIZZLED && newState != CREATED ); + if ( newState != state ) { + switch ( newState ) { + case LAUNCHED: + Create( NULL, launchOrig, launchDir ); + Launch( launchOrig, launchDir, vec3_origin ); + Show(); + break; + case FIZZLED: + case EXPLODED: + if ( state != EXPLODED ) { + StopSound( SND_CHANNEL_BODY, false ); + StopAllEffects(); + Hide(); + FreeLightDef(); + state = EXPLODED; + } + break; + } + } + + if ( msg.HasChanged() ) { + if( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + + UpdateVisuals(); + } +} + +/* +=============== +idProjectile::ClientStale +=============== +*/ +bool idProjectile::ClientStale( void ) { + // delete stale projectile ents. if they pop back in pvs, they will be re-spawned ( rare case anyway ) + StopAllEffects(); + return true; +} + +/* +================ +idProjectile::GetPhysicsToVisualTransform +================ +*/ +bool idProjectile::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + axis = rotation.GetCurrentValue( gameLocal.GetTime() ).ToMat3() * GetPhysics()->GetAxis().Transpose(); + + origin.Zero(); + return true; +} + +/* +=============================================================================== + + idGuidedProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, idGuidedProjectile ) +END_CLASS + +/* +================ +idGuidedProjectile::idGuidedProjectile( void ) +================ +*/ +idGuidedProjectile::idGuidedProjectile( void ) { + guideType = GUIDE_NONE; + turn_max.Init ( gameLocal.time, 0, 0.0f, 0.0f ); +} + +/* +================= +idGuidedProjectile::~idGuidedProjectile +================= +*/ +idGuidedProjectile::~idGuidedProjectile() { +} + +/* +================ +idGuidedProjectile::Save +================ +*/ +void idGuidedProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( guideType ); + guideEnt.Save( savefile ); + savefile->WriteVec3 ( guideDir ); + savefile->WriteVec3 ( guidePos ); + savefile->WriteJoint ( guideJoint ); + savefile->WriteFloat( guideMinDist ); // cnicholson: Added unsaved var + + savefile->WriteInt ( driftTime ); + savefile->WriteInt ( driftRate ); + savefile->WriteFloat ( driftRange ); + savefile->WriteFloat ( driftRadius ); + savefile->WriteFloat ( driftDiversity ); // cnicholson: Added unsaved var + savefile->WriteFloat ( driftAngle ); + savefile->WriteFloat ( driftAngleStep ); + savefile->WriteFloat ( driftProjectRange ); + + savefile->WriteFloat( turn_max.GetStartTime() ); + savefile->WriteFloat( turn_max.GetDuration() ); + savefile->WriteFloat( turn_max.GetStartValue() ); + savefile->WriteFloat( turn_max.GetEndValue() ); + + savefile->WriteInt ( launchTime ); + savefile->WriteInt ( guideDelay ); + savefile->WriteInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::Restore +================ +*/ +void idGuidedProjectile::Restore( idRestoreGame *savefile ) { + float set; + savefile->ReadInt ( guideType ); + guideEnt.Restore( savefile ); + savefile->ReadVec3 ( guideDir ); + savefile->ReadVec3 ( guidePos ); + savefile->ReadJoint ( guideJoint ); + savefile->ReadFloat( guideMinDist ); // cnicholson: Added unrestored var + + savefile->ReadInt ( driftTime ); + savefile->ReadInt ( driftRate ); + savefile->ReadFloat ( driftRange ); + savefile->ReadFloat ( driftRadius ); + savefile->ReadFloat ( driftDiversity ); // cnicholson: Added unrestored var + savefile->ReadFloat ( driftAngle ); + savefile->ReadFloat ( driftAngleStep ); + savefile->ReadFloat ( driftProjectRange ); + + savefile->ReadFloat( set ); + turn_max.SetStartTime( set ); + savefile->ReadFloat( set ); + turn_max.SetDuration( set ); + savefile->ReadFloat( set ); + turn_max.SetStartValue( set ); + savefile->ReadFloat( set ); + turn_max.SetEndValue( set ); + + savefile->ReadInt ( launchTime ); + savefile->ReadInt ( guideDelay ); + savefile->ReadInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::GetGuideDir +================ +*/ +bool idGuidedProjectile::GetGuideDir ( idVec3 &outDir, float& outDist ) { + // Dont start guiding immeidately? + if ( gameLocal.GetTime() - launchTime < guideDelay ) { + return false; + } + + switch ( guideType ) { + case GUIDE_ENTITY: + // If the guide entity is gone or dead then cancel the guide + if ( !guideEnt.GetEntity ( ) || (guideEnt->fl.takedamage && guideEnt->health <= 0 ) ) { + CancelGuide ( ); + return false; + } + // Use eye position for actors and center of bounds for everything else + if ( guideJoint != INVALID_JOINT ) { + idMat3 jointAxis; + guideEnt->GetAnimator()->GetJointTransform( guideJoint, gameLocal.GetTime(), outDir, jointAxis ); + outDir = guideEnt->GetRenderEntity()->origin + (outDir*guideEnt->GetRenderEntity()->axis); + if ( !guidePos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * guideEnt->GetRenderEntity()->axis; + outDir += jointAxis[0]*guidePos[0]; + outDir += jointAxis[1]*guidePos[1]; + outDir += jointAxis[2]*guidePos[2]; + } + } else { + outDir = guideEnt->GetPhysics()->GetAbsBounds().GetCenter(); + if ( guideEnt->IsType( idActor::GetClassType() ) ) { + outDir += static_cast(guideEnt.GetEntity())->GetEyePosition(); + outDir *= 0.5f; + } + } + outDir -= physicsObj.GetOrigin(); + break; + + case GUIDE_POS: + outDir = guidePos - physicsObj.GetOrigin(); + break; + + case GUIDE_DIR: + // Project our current position on to the desired direction + outDir = guidePos + guideDir * ((physicsObj.GetOrigin() - guidePos) * guideDir); + + // Seek towards a point forward along our desired direction + outDir = (outDir + guideDir * (guideMinDist * 1.10f)) - physicsObj.GetOrigin(); + break; + + default: + return false; + } + + // Add drifting + if ( driftRate && gameLocal.GetTime() - launchTime > driftDelay ) { + idMat3 axis; + + outDist = outDir.NormalizeFast(); + axis = outDir.ToMat3(); + + if ( gameLocal.time > driftTime ) { + driftRadius = driftRange + gameLocal.random.RandomFloat ( ) * driftRange * driftDiversity; + driftTime = gameLocal.time + driftRate; + } else { + driftAngle += driftAngleStep * MS2SEC ( gameLocal.msec ); + idMath::AngleNormalize360 ( driftAngle ); + } + + float angle; + angle = DEG2RAD ( driftAngle ); + outDir = physicsObj.GetOrigin ( ) + outDir * Min( outDist, driftProjectRange ); + outDir += axis[2] * (driftRadius * idMath::Sin ( angle )); + outDir += axis[1] * (driftRadius * idMath::Cos ( angle )); + + outDir -= physicsObj.GetOrigin(); + } + + outDist = outDir.Normalize ( ); + + return true; +} + +/* +================ +idGuidedProjectile::Think +================ +*/ +void idGuidedProjectile::Think( void ) { + + if ( state == LAUNCHED ) { + idVec3 dir; + idVec3 vel; + float angle; + float maxangle; + idMat3 axis; + float dist; + + // crank up to normal speed ? + if ( guideDelay && gameLocal.GetTime() - launchTime >= guideDelay ) { + float newSpeed = spawnArgs.GetFloat( "speed" ); + float newSpeed2; + if ( !spawnArgs.GetFloat ( "speed_end", "0", newSpeed2 ) ) { + newSpeed2 = newSpeed; + } + float newSpeedDuration; + newSpeedDuration = SEC2MS( spawnArgs.GetFloat ( "speed_duration", "0" ) ); + speed.Init ( gameLocal.time, newSpeedDuration, newSpeed, newSpeed2 ); + guideDelay = 0; + } + + if ( !GetGuideDir( dir, dist ) ) { + idProjectile::Think(); + return; + } + + // Direction of travel + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + + // Calculate the angle between the current projectile direction and where we want to go + angle = RAD2DEG( idMath::ACos( dir * vel ) ); + + // Make sure the angle doesnt cross our max turn radius + maxangle = turn_max.GetCurrentValue( gameLocal.time ); + if ( angle < -maxangle ) { + angle = -maxangle; + } else if ( angle > maxangle ) { + angle = maxangle; + } + + // Debug information + if ( g_debugWeapon.GetBool ( ) ) { + gameRenderWorld->DebugArrow( colorCyan, physicsObj.GetOrigin(), physicsObj.GetOrigin() + vel * 50.0f, 10.0f ); + gameRenderWorld->DebugArrow( colorMagenta, physicsObj.GetOrigin(), physicsObj.GetOrigin() + dir * 50.0f, 10.0f ); + } + + // Calculate the new axis by rotating the current forward vector around the cross of the forward + // vector and the direction vector. + vel = vel * idRotation( vec3_origin, dir.Cross ( vel ), angle ); + physicsObj.SetLinearVelocity( vel * GetSpeed ( ) ); + + // If within the minium distance to the target anything over a 45 degree change will cancel the guide + if ( guideMinDist != 0.0f && dist < guideMinDist ) { + // Stop guiding if we have passed our target + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize( ); + if ( vel * dir < 0.7f ) { + guideType = GUIDE_NONE; + } + } + + idProjectile::Think(); + } else { + idProjectile::Think(); + } +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + launchTime = gameLocal.GetTime(); + + if ( owner.GetEntity() ) { + if ( owner.GetEntity()->IsType( idAI::GetClassType() ) ) { + GuideTo ( static_cast( owner.GetEntity() )->GetEnemy() ); + } + } + + guideMinDist = spawnArgs.GetFloat ( "min_dist", "128" ); + guideDelay = SEC2MS(spawnArgs.GetFloat ( "delayGuide" ) + ( gameLocal.random.RandomFloat ( ) * spawnArgs.GetFloat ( "delayGuide_random")) ); + + if ( guideDelay ) { + float delaySpeed; + if ( spawnArgs.GetFloat( "delaySpeed", "0", delaySpeed ) ) { + float delaySpeed2; + if ( !spawnArgs.GetFloat ( "delaySpeed_end", "0", delaySpeed2 ) ) { + delaySpeed2 = delaySpeed; + } + float delaySpeedDuration; + delaySpeedDuration = SEC2MS( spawnArgs.GetFloat ( "delaySpeed_duration", "0" ) ); + speed.Init( gameLocal.time, delaySpeedDuration, delaySpeed, delaySpeed2 ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + } + } + + driftTime = 0; + driftRate = SEC2MS ( spawnArgs.GetFloat ( "driftRate", "0" ) ); + driftRange = spawnArgs.GetFloat ( "driftRange" ); + driftDiversity = spawnArgs.GetFloat ( "driftDiversity", ".5" ); + driftAngle = gameLocal.random.RandomFloat ( ) * 360.0f; + driftAngleStep = spawnArgs.GetFloat ( "driftRotate" ); + driftAngleStep += gameLocal.random.CRandomFloat ( ) * (driftAngleStep * driftDiversity) * (gameLocal.random.RandomFloat()<0.5f?-1.0f:1.0f); + driftDelay = SEC2MS(spawnArgs.GetFloat ( "driftDelay" )); + + driftProjectRange = spawnArgs.GetFloat ( "driftProjectRange", "128" ); + + // Turn rate can be ramped up over time + turn_max.Init ( gameLocal.time, + SEC2MS(spawnArgs.GetFloat ( "turn_accel", "0" )), + 0, + spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.GetMHz() ); + + UpdateVisuals(); +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + idProjectile::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + rvDriftingProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvDriftingProjectile ) +END_CLASS + +/* +================ +rvDriftingProjectile::rvDriftingProjectile( void ) +================ +*/ +rvDriftingProjectile::rvDriftingProjectile( void ) { +} + +/* +================= +rvDriftingProjectile::~rvDriftingProjectile +================= +*/ +rvDriftingProjectile::~rvDriftingProjectile ( void ) { +} + +/* +================ +rvDriftingProjectile::Save +================ +*/ +void rvDriftingProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteVec3 ( startDir ); + savefile->WriteVec3 ( startOrigin ); + savefile->WriteMat3 ( startAxis ); + savefile->WriteFloat ( startSpeed ); + savefile->WriteFloat ( driftOffsetMax ); + savefile->WriteFloat ( driftSpeedMax ); + savefile->WriteFloat ( driftTime ); + + savefile->WriteInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->WriteInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Restore +================ +*/ +void rvDriftingProjectile::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3 ( startDir ); + savefile->ReadVec3 ( startOrigin ); + savefile->ReadMat3 ( startAxis ); + savefile->ReadFloat ( startSpeed ); + savefile->ReadFloat ( driftOffsetMax ); + savefile->ReadFloat ( driftSpeedMax ); + savefile->ReadFloat ( driftTime ); + + savefile->ReadInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->ReadInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Think +================ +*/ +void rvDriftingProjectile::Think( void ) { + idVec3 diff; + idVec3 origin; + idVec3 oldOrigin; + idVec3 dir; + float dist; + + oldOrigin = GetPhysics()->GetOrigin ( ); + + diff = oldOrigin - startOrigin; + dist = diff.Length ( ); + origin = startOrigin + startDir * dist; + + if ( driftSpeed.IsDone ( gameLocal.time ) ) { + driftSpeed.Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftSpeed.GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftSpeedMax * (driftSpeed.GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[0].IsDone ( gameLocal.time ) ) { + driftOffset[0].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftOffset[0].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[0].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[1].IsDone ( gameLocal.time ) ) { + driftOffset[1].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat()*driftTime, + driftOffset[1].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[1].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + origin += startAxis[1] * driftOffset[0].GetCurrentValue ( gameLocal.time ); + origin += startAxis[2] * driftOffset[1].GetCurrentValue ( gameLocal.time ); + + GetPhysics ( )->SetOrigin ( origin ); + GetPhysics ( )->SetLinearVelocity ( startDir * (startSpeed + driftSpeed.GetCurrentValue ( gameLocal.time )) ); + + idProjectile::Think(); + + // Now orient the projectile using the old origin + dir = GetPhysics()->GetOrigin() - oldOrigin; + dir.Normalize ( ); + GetPhysics ( )->SetAxis ( dir.ToMat3 ( ) ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower) { + startDir = dir; + startOrigin = start; + startAxis = dir.ToMat3(); + + driftOffsetMax = spawnArgs.GetFloat ( "driftOffset", "50" ); + driftSpeedMax = spawnArgs.GetFloat ( "driftSpeed", "50" ); + driftTime = SEC2MS ( spawnArgs.GetFloat ( "driftTime", ".5" ) ); + + idProjectile::Launch ( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + startSpeed = GetPhysics()->GetLinearVelocity().Length ( ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::UpdateVisualAngles ( void ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), GetPhysics()->GetAxis().ToQuat() ); +} + +/* +=============================================================================== + + rvSpawnerProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvSpawnerProjectile ) + EVENT( EV_PostSpawn, rvSpawnerProjectile::Event_PostSpawn ) +END_CLASS + +/* +================ +rvSpawnerProjectile::rvSpawnerProjectile( void ) +================ +*/ +rvSpawnerProjectile::rvSpawnerProjectile( void ) { + spawnState = STATE_NONE; +} + +/* +================= +rvSpawnerProjectile::~rvSpawnerProjectile +================= +*/ +rvSpawnerProjectile::~rvSpawnerProjectile ( void ) { + if ( spawnState == STATE_ADDED && spawner ) { + spawner->RemoveSpawnPoint ( this ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::Spawn ( void ) { + if ( *spawnArgs.GetString ( "spawner" ) ) { + PostEventMS ( &EV_PostSpawn, 0 ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +================= +rvSpawnerProjectile::Think +================= +*/ +void rvSpawnerProjectile::Think ( void ) { + idProjectile::Think ( ); + + if ( physicsObj.IsAtRest ( ) ) { + if ( spawnState == STATE_NONE && spawner ) { + spawner->AddSpawnPoint ( this ); + spawnState = STATE_ADDED; + } + } +} + +/* +================= +rvSpawnerProjectile::Event_PostSpawn +================= +*/ +void rvSpawnerProjectile::Event_PostSpawn ( void ) { + const char* temp; + temp = spawnArgs.GetString ( "spawner" ); + if ( temp && *temp ) { + idEntity* ent; + ent = gameLocal.FindEntity ( temp ); + if ( !ent ) { + gameLocal.Warning ( "spawner entity ('%s') not found for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else if ( !ent->IsType ( rvSpawner::GetClassType() ) ) { + gameLocal.Warning ( "spawner entity ('%s') is not of type rvSpawner for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else { + SetSpawner ( static_cast(ent) ); + } + } +} + + +/* +=============================================================================== + + rvMIRVProjectile + +=============================================================================== +*/ +idEventDef EV_LaunchWarheads( "launchWarheads" ); + +CLASS_DECLARATION( idProjectile, rvMIRVProjectile ) + EVENT( EV_LaunchWarheads, rvMIRVProjectile::Event_LaunchWarheads ) +END_CLASS + +/* +================ +rvMIRVProjectile::rvMIRVProjectile( void ) +================ +*/ +rvMIRVProjectile::rvMIRVProjectile( void ) { + +} + +/* +================= +rvMIRVProjectile::~rvMIRVProjectile +================= +*/ +rvMIRVProjectile::~rvMIRVProjectile ( void ) { + +} + +/* +================ +void rvMIRVProjectile::Spawn( void ) +================ +*/ +void rvMIRVProjectile::Spawn( void ) { + + float launchDelay = spawnArgs.GetFloat("warhead_fuse", "0"); + //post event for warhead launch + PostEventSec( &EV_LaunchWarheads, launchDelay ); +} + +/* +================ +void rvMIRVProjectile::Event_LaunchWarheads( void ) +================ +*/ +void rvMIRVProjectile::Event_LaunchWarheads( void ) { + + const char* warhead; + int count; + + warhead = spawnArgs.GetString("def_warhead",""); + count = spawnArgs.GetFloat("warhead_count","0"); + + if( warhead && warhead[0] ) { + + //start launching! + float angle = (360.0f / count); + idMat3 normalMat = this->GetPhysics()->GetAxis( ); + idVec3 normal = normalMat[0]; + idVec3 axis = normalMat[1]; + + float t; + idMat3 axisMat; + idProjectile * warheadEntity; + + //hey how 'bout that. + normal.Normalize(); + + for( t = 0; t< count; t++) { + + //rotate axis around normal by angle degrees. + axisMat = axis.ToMat3(); + axisMat.RotateArbitrary( normal, angle * t); + warheadEntity = static_cast(gameLocal.SpawnEntityDef( warhead)); + warheadEntity->Create( this->GetOwner(), this->GetPhysics()->GetOrigin(), axisMat[0], this ); + warheadEntity->Launch( this->GetPhysics()->GetOrigin(), axisMat[0], axisMat[0] ); + + } + //foom! + gameLocal.PlayEffect( spawnArgs, "fx_impact", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + PostEventSec( &EV_Explode, 0 ); + } + + + +} +// RAVEN END diff --git a/src/game/Projectile.h b/src/game/Projectile.h new file mode 100644 index 0000000..3596d6d --- /dev/null +++ b/src/game/Projectile.h @@ -0,0 +1,382 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_PROJECTILE_H__ +#define __GAME_PROJECTILE_H__ + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_Explode; + +class idProjectile : public idEntity { +public : + CLASS_PROTOTYPE( idProjectile ); + + idProjectile(); + virtual ~idProjectile(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Create( idEntity *owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore = NULL, idEntity* extraPassEntity = NULL ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + virtual void FreeLightDef( void ); + +//RITUAL BEGIN + void SetOwner(idEntity* ent) { owner = ent; } +// RITUAL END + + idEntity * GetOwner( void ) const; + + virtual void Think( void ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ); + virtual void Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore = NULL, const char *sndExplode = "snd_explode" ); + void Fizzle( void ); + + static idVec3 GetVelocity( const idDict *projectile ); + static idVec3 GetGravity( const idDict *projectile ); + + void SetSpeed ( float s, int accelTime = 0 ); + float GetSpeed ( void ) const; + + virtual void UpdateVisualAngles(); + + // information about what kind of projectile we are, used for death messages + int methodOfDeath; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + +protected: + void SpawnImpactEntities(const trace_t& collision, const idVec3 projectileDirection); + + + idEntityPtr owner; + + struct projectileFlags_s { + bool detonate_on_world : 1; + bool detonate_on_actor : 1; + bool detonate_on_bounce : 1; // Detonate if hit a bounce surface + bool randomShaderSpin : 1; + bool isTracer : 1; + } projectileFlags; + + float damagePower; + + renderLight_t renderLight; + qhandle_t lightDefHandle; // handle to renderer light def + idVec3 lightOffset; + int lightStartTime; + int lightEndTime; + idVec3 lightColor; + + idEntity* impactedEntity; + + rvPhysics_Particle physicsObj; + idAngles visualAngles; + idAngles angularVelocity; + idInterpolate speed; + bool updateVelocity; + + rvSphericalInterpolate rotation; + + rvClientEffectPtr flyEffect; + float flyEffectAttenuateSpeed; + + int bounceCount; + bool sticky; + + idStr impactEntity; + int numImpactEntities; + int ieMinPitch; + int ieMaxPitch; + float ieSlicePercentage; + +// RAVEN BEGIN +// ddynerman: hit count for stats + int hitCount; +// ddynerman: pre-prediction ( rocket jumping ) + int prePredictTime; +// RAVEN END + typedef enum { + SPAWNED = 0, + CREATED = 1, + LAUNCHED = 2, + FIZZLED = 3, + EXPLODED = 4 + } projectileState_t; + + projectileState_t state; + + void PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& direction ); + virtual void PlayDetonateEffect ( const idVec3& origin, const idMat3& axis ); + +private: + void DefaultDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + + void Event_Explode ( void ); + void Event_Fizzle ( void ); + void Event_RadiusDamage ( idEntity *ignore ); + void Event_ResidualDamage ( idEntity *ignore ); + void Event_Touch ( idEntity *other, trace_t *trace ); + + bool syncPhysics; + + // cheap linear client side projectiles + // transmitted in snapshot + int launchTime; + idVec3 launchOrig; + idVec3 launchDir; + // set from def file in :Launch on both client and server + float launchSpeed; +}; + +ID_INLINE float idProjectile::GetSpeed ( void ) const { + return speed.GetCurrentValue( gameLocal.time ); +} + +/* +=============================================================================== + +idGuidedProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_UpdateGuideTarget; +extern const idEventDef EV_GuideToEntity; +extern const idEventDef EV_GuideToPos; + +class idGuidedProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( idGuidedProjectile ); + + idGuidedProjectile( void ); + ~idGuidedProjectile( void ); + + enum { + GUIDE_NONE, + GUIDE_ENTITY, + GUIDE_POS, + GUIDE_DIR, + GUIDE_MAX + }; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + void GuideTo ( const idVec3& post, const idVec3& dir ); + void GuideTo ( const idVec3& pos ); + void GuideTo ( idEntity* ent, jointHandle_t guideJoint=INVALID_JOINT, const idVec3 &offset=vec3_origin ); + void CancelGuide ( void ); + + int GetGuideType ( void ) const; + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int guideType; + idEntityPtr guideEnt; + idVec3 guideDir; + idVec3 guidePos; + jointHandle_t guideJoint; + float guideMinDist; + + int driftTime; + int driftRate; + float driftRange; + float driftRadius; + float driftDiversity; + float driftAngle; + float driftAngleStep; + float driftProjectRange; + + virtual bool GetGuideDir ( idVec3 &outDir, float& outDist ); + +private: + + idInterpolate turn_max; + int launchTime; + int guideDelay; + int driftDelay; +}; + +ID_INLINE int idGuidedProjectile::GetGuideType ( void ) const { + return guideType; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos, const idVec3& dir ) { + guideType = GUIDE_DIR; + guidePos = pos; + guideDir = dir; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos ) { + guideType = GUIDE_POS; + guidePos = pos; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( idEntity* ent, jointHandle_t joint, const idVec3 &offset ) { + guideType = GUIDE_ENTITY; + guideEnt = ent; + guideJoint = joint; + guidePos = offset; + + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( this ); + } +} + +ID_INLINE void idGuidedProjectile::CancelGuide ( void ) { + guideType = GUIDE_NONE; + + // twhitaker: TEMP + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + // +} + +/* +=============================================================================== + +rvDriftingProjectile + +=============================================================================== +*/ + +class rvDriftingProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDriftingProjectile ); + + rvDriftingProjectile ( void ); + ~rvDriftingProjectile ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + virtual void Launch ( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + +protected: + + virtual void UpdateVisualAngles ( void ); + + idVec3 startDir; + idVec3 startOrigin; + idMat3 startAxis; + float startSpeed; + + idInterpolateAccelDecelLinear driftOffset[2]; + idInterpolateAccelDecelLinear driftSpeed; + float driftOffsetMax; + float driftSpeedMax; + float driftTime; +}; + +/* +=============================================================================== + +rvSpawnerProjectile + +=============================================================================== +*/ + +class rvSpawner; +class rvSpawnerProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvSpawnerProjectile ); + + rvSpawnerProjectile ( void ); + ~rvSpawnerProjectile ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + void SetSpawner ( rvSpawner* spawner ); + +protected: + + idEntityPtr spawner; + + enum { + STATE_NONE, + STATE_ADDED, + } spawnState; + +private: + + void Event_PostSpawn ( void ); +}; + +/* +=============================================================================== + +rvMIRVProjectile + +=============================================================================== +*/ + +class rvMIRVProjectile : public idProjectile { + CLASS_PROTOTYPE( rvMIRVProjectile ); + + rvMIRVProjectile ( void ); + ~rvMIRVProjectile ( void ); + + + void Spawn ( void ); + +private: + + void Event_LaunchWarheads ( void ); +}; + +#endif /* !__GAME_PROJECTILE_H__ */ + +// RAVEN END diff --git a/src/game/Pvs.cpp b/src/game/Pvs.cpp new file mode 100644 index 0000000..14301e1 --- /dev/null +++ b/src/game/Pvs.cpp @@ -0,0 +1,1462 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#define MAX_BOUNDS_AREAS 16 + + +typedef struct pvsPassage_s { + byte * canSee; // bit set for all portals that can be seen through this passage +} pvsPassage_t; + + +typedef struct pvsPortal_s { + int areaNum; // area this portal leads to + idWinding * w; // winding goes counter clockwise seen from the area this portal is part of + idBounds bounds; // winding bounds + idPlane plane; // winding plane, normal points towards the area this portal leads to + pvsPassage_t * passages; // passages to portals in the area this portal leads to + bool done; // true if pvs is calculated for this portal + byte * vis; // PVS for this portal + byte * mightSee; // used during construction +} pvsPortal_t; + + +typedef struct pvsArea_s { + int numPortals; // number of portals in this area + idBounds bounds; // bounds of the whole area + pvsPortal_t ** portals; // array with pointers to the portals of this area +} pvsArea_t; + + +typedef struct pvsStack_s { + struct pvsStack_s * next; // next stack entry + byte * mightSee; // bit set for all portals that might be visible through this passage/portal stack +} pvsStack_t; + + +/* +================ +idPVS::idPVS +================ +*/ +idPVS::idPVS( void ) { + int i; + + numAreas = 0; + numPortals = 0; + + connectedAreas = NULL; + areaQueue = NULL; + areaPVS = NULL; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = NULL; + } + + pvsAreas = NULL; + pvsPortals = NULL; +} + +/* +================ +idPVS::~idPVS +================ +*/ +idPVS::~idPVS( void ) { + Shutdown(); +} + +/* +================ +idPVS::GetPortalCount +================ +*/ +int idPVS::GetPortalCount( void ) const { + int i, na, np; + + na = gameRenderWorld->NumAreas(); + np = 0; + for ( i = 0; i < na; i++ ) { + np += gameRenderWorld->NumPortalsInArea( i ); + } + return np; +} + +/* +================ +idPVS::CreatePVSData +================ +*/ +void idPVS::CreatePVSData( void ) { + int i, j, n, cp; + exitPortal_t portal; + pvsArea_t *area; + pvsPortal_t *p, **portalPtrs; + + if ( !numPortals ) { + return; + } + + pvsPortals = new pvsPortal_t[numPortals]; + pvsAreas = new pvsArea_t[numAreas]; + memset( pvsAreas, 0, numAreas * sizeof( *pvsAreas ) ); + + cp = 0; + portalPtrs = new pvsPortal_t*[numPortals]; + + for ( i = 0; i < numAreas; i++ ) { + + area = &pvsAreas[i]; + area->bounds.Clear(); + area->portals = portalPtrs + cp; + + n = gameRenderWorld->NumPortalsInArea( i ); + + for ( j = 0; j < n; j++ ) { + + portal = gameRenderWorld->GetPortal( i, j ); + + p = &pvsPortals[cp++]; + // the winding goes counter clockwise seen from this area + p->w = portal.w->Copy(); + p->areaNum = portal.areas[1]; // area[1] is always the area the portal leads to + + p->vis = new byte[portalVisBytes]; + memset( p->vis, 0, portalVisBytes ); + p->mightSee = new byte[portalVisBytes]; + memset( p->mightSee, 0, portalVisBytes ); + p->w->GetBounds( p->bounds ); + p->w->GetPlane( p->plane ); + // plane normal points to outside the area + p->plane = -p->plane; + // no PVS calculated for this portal yet + p->done = false; + + area->portals[area->numPortals] = p; + area->numPortals++; + + area->bounds += p->bounds; + } + } +} + +/* +================ +idPVS::DestroyPVSData +================ +*/ +void idPVS::DestroyPVSData( void ) { + int i; + + if ( !pvsAreas ) { + return; + } + + // delete portal pointer array + delete[] pvsAreas[0].portals; + + // delete all areas + delete[] pvsAreas; + pvsAreas = NULL; + + // delete portal data + for ( i = 0; i < numPortals; i++ ) { + delete[] pvsPortals[i].vis; + delete[] pvsPortals[i].mightSee; + delete pvsPortals[i].w; + } + + // delete portals + delete[] pvsPortals; + pvsPortals = NULL; +} + +/* +================ +idPVS::FloodFrontPortalPVS_r +================ +*/ +void idPVS::FloodFrontPortalPVS_r( pvsPortal_t *portal, int areaNum ) const { + int i, n; + pvsArea_t *area; + pvsPortal_t *p; + + area = &pvsAreas[ areaNum ]; + + for ( i = 0; i < area->numPortals; i++ ) { + p = area->portals[i]; + n = p - pvsPortals; + // don't flood through if this portal is not at the front + if ( !( portal->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + continue; + } + // don't flood through if already visited this portal + if ( portal->vis[ n>>3 ] & (1 << (n&7)) ) { + continue; + } + // this portal might be visible + portal->vis[ n>>3 ] |= (1 << (n&7)); + // flood through the portal + FloodFrontPortalPVS_r( portal, p->areaNum ); + } +} + +/* +================ +idPVS::FrontPortalPVS +================ +*/ +void idPVS::FrontPortalPVS( void ) const { + int i, j, k, n, p, side1, side2, areaSide; + pvsPortal_t *p1, *p2; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + + for ( j = 0; j < numAreas; j++ ) { + + area = &pvsAreas[j]; + + areaSide = side1 = area->bounds.PlaneSide( p1->plane ); + + // if the whole area is at the back side of the portal + if ( areaSide == PLANESIDE_BACK ) { + continue; + } + + for ( p = 0; p < area->numPortals; p++ ) { + + p2 = area->portals[p]; + + // if we the whole area is not at the front we need to check + if ( areaSide != PLANESIDE_FRONT ) { + // if the second portal is completely at the back side of the first portal + side1 = p2->bounds.PlaneSide( p1->plane ); + if ( side1 == PLANESIDE_BACK ) { + continue; + } + } + + // if the first portal is completely at the front of the second portal + side2 = p1->bounds.PlaneSide( p2->plane ); + if ( side2 == PLANESIDE_FRONT ) { + continue; + } + + // if the second portal is not completely at the front of the first portal + if ( side1 != PLANESIDE_FRONT ) { + // more accurate check + for ( k = 0; k < p2->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the front side + if ( p1->plane.Side( (*p2->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_FRONT ) { + break; + } + } + if ( k >= p2->w->GetNumPoints() ) { + continue; // second portal is at the back of the first portal + } + } + + // if the first portal is not completely at the back side of the second portal + if ( side2 != PLANESIDE_BACK ) { + // more accurate check + for ( k = 0; k < p1->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the back side + if ( p2->plane.Side( (*p1->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_BACK ) { + break; + } + } + if ( k >= p1->w->GetNumPoints() ) { + continue; // first portal is at the front of the second portal + } + } + + // the portal might be visible at the front + n = p2 - pvsPortals; + p1->mightSee[ n >> 3 ] |= 1 << (n&7); + } + } + } + + // flood the front portal pvs for all portals + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + FloodFrontPortalPVS_r( p1, p1->areaNum ); + } +} + +/* +=============== +idPVS::FloodPassagePVS_r +=============== +*/ +pvsStack_t *idPVS::FloodPassagePVS_r( pvsPortal_t *source, const pvsPortal_t *portal, pvsStack_t *prevStack ) const { + int i, j, n, m; + pvsPortal_t *p; + pvsArea_t *area; + pvsStack_t *stack; + pvsPassage_t *passage; + long *sourceVis, *passageVis, *portalVis, *mightSee, *prevMightSee, more; + + area = &pvsAreas[portal->areaNum]; + + stack = prevStack->next; + // if no next stack entry allocated + if ( !stack ) { + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + prevStack->next = stack; + } + + // check all portals for flooding into other areas + for ( i = 0; i < area->numPortals; i++ ) { + + passage = &portal->passages[i]; + + // if this passage is completely empty + if ( !passage->canSee ) { + continue; + } + + p = area->portals[i]; + n = p - pvsPortals; + + // if this portal cannot be seen through our current portal/passage stack + if ( !( prevStack->mightSee[n >> 3] & (1 << (n & 7)) ) ) { + continue; + } + + // mark the portal as visible + source->vis[n >> 3] |= (1 << (n & 7)); + + // get pointers to vis data + prevMightSee = reinterpret_cast(prevStack->mightSee); + passageVis = reinterpret_cast(passage->canSee); + sourceVis = reinterpret_cast(source->vis); + mightSee = reinterpret_cast(stack->mightSee); + + more = 0; + // use the portal PVS if it has been calculated + if ( p->done ) { + portalVis = reinterpret_cast(p->vis); + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++ & *portalVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + else { + // the p->mightSee is implicitely stored in the passageVis + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + + // if nothing more can be seen + if ( !more ) { + continue; + } + + // go through the portal + stack->next = FloodPassagePVS_r( source, p, stack ); + } + + return stack; +} + +/* +=============== +idPVS::PassagePVS +=============== +*/ +void idPVS::PassagePVS( void ) const { + int i; + pvsPortal_t *source; + pvsStack_t *stack, *s; + + // create the passages + CreatePassages(); + + // allocate first stack entry + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + + // calculate portal PVS by flooding through the passages + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + memset( source->vis, 0, portalVisBytes ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( stack->mightSee, source->mightSee, portalVisBytes ); +// RAVEN END + FloodPassagePVS_r( source, source, stack ); + source->done = true; + } + + // free the allocated stack + for ( s = stack; s; s = stack ) { + stack = stack->next; + delete[] s; + } + + // destroy the passages + DestroyPassages(); +} + +/* +=============== +idPVS::AddPassageBoundaries +=============== +*/ +void idPVS::AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const { + int i, j, k, l; + idVec3 v1, v2, normal; + float d, dist; + bool flipTest, front; + idPlane plane; + + + // check all combinations + for ( i = 0; i < source.GetNumPoints(); i++ ) { + + l = (i + 1) % source.GetNumPoints(); + v1 = source[l].ToVec3() - source[i].ToVec3(); + + // find a vertex of pass that makes a plane that puts all of the + // vertices of pass on the front side and all of the vertices of + // source on the back side + for ( j = 0; j < pass.GetNumPoints(); j++ ) { + + v2 = pass[j].ToVec3() - source[i].ToVec3(); + + normal = v1.Cross( v2 ); + if ( normal.Normalize() < 0.01f ) { + continue; + } + dist = normal * pass[j].ToVec3(); + + // + // find out which side of the generated seperating plane has the + // source portal + // + flipTest = false; + for ( k = 0; k < source.GetNumPoints(); k++ ) { + if ( k == i || k == l ) { + continue; + } + d = source[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + // source is on the negative side, so we want all + // pass and target on the positive side + flipTest = false; + break; + } + else if ( d > ON_EPSILON ) { + // source is on the positive side, so we want all + // pass and target on the negative side + flipTest = true; + break; + } + } + if ( k == source.GetNumPoints() ) { + continue; // planar with source portal + } + + // flip the normal if the source portal is backwards + if (flipTest) { + normal = -normal; + dist = -dist; + } + + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + front = false; + for ( k = 0; k < pass.GetNumPoints(); k++ ) { + if ( k == j ) { + continue; + } + d = pass[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + break; + } + else if ( d > ON_EPSILON ) { + front = true; + } + } + if ( k < pass.GetNumPoints() ) { + continue; // points on negative side, not a seperating plane + } + if ( !front ) { + continue; // planar with seperating plane + } + + // flip the normal if we want the back side + if ( flipClip ) { + plane.SetNormal( -normal ); + plane.SetDist( -dist ); + } + else { + plane.SetNormal( normal ); + plane.SetDist( dist ); + } + + // check if the plane is already a passage boundary + for ( k = 0; k < numBounds; k++ ) { + if ( plane.Compare( bounds[k], 0.001f, 0.01f ) ) { + break; + } + } + if ( k < numBounds ) { + break; + } + + if ( numBounds >= maxBounds ) { + gameLocal.Warning( "max passage boundaries." ); + break; + } + bounds[numBounds] = plane; + numBounds++; + break; + } + } +} + +/* +================ +idPVS::CreatePassages +================ +*/ +#define MAX_PASSAGE_BOUNDS 128 + +void idPVS::CreatePassages( void ) const { + int i, j, l, n, numBounds, front, passageMemory, byteNum, bitNum; + int sides[MAX_PASSAGE_BOUNDS]; + idPlane passageBounds[MAX_PASSAGE_BOUNDS]; + pvsPortal_t *source, *target, *p; + pvsArea_t *area; + pvsPassage_t *passage; + idFixedWinding winding; + byte canSee, mightSee, bit; + + passageMemory = 0; + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + area = &pvsAreas[source->areaNum]; + + source->passages = new pvsPassage_t[area->numPortals]; + + for ( j = 0; j < area->numPortals; j++ ) { + target = area->portals[j]; + n = target - pvsPortals; + + passage = &source->passages[j]; + + // if the source portal cannot see this portal + if ( !( source->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + // not all portals in the area have to be visible because areas are not necesarily convex + // also no passage has to be created for the portal which is the opposite of the source + passage->canSee = NULL; + continue; + } + + passage->canSee = new byte[portalVisBytes]; + passageMemory += portalVisBytes; + + // boundary plane normals point inwards + numBounds = 0; + AddPassageBoundaries( *(source->w), *(target->w), false, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + AddPassageBoundaries( *(target->w), *(source->w), true, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + + // get all portals visible through this passage + for ( byteNum = 0; byteNum < portalVisBytes; byteNum++) { + + canSee = 0; + mightSee = source->mightSee[byteNum] & target->mightSee[byteNum]; + + // go through eight portals at a time to speed things up + for ( bitNum = 0; bitNum < 8; bitNum++ ) { + + bit = 1 << bitNum; + + if ( !( mightSee & bit ) ) { + continue; + } + + p = &pvsPortals[(byteNum << 3) + bitNum]; + + if ( p->areaNum == source->areaNum ) { + continue; + } + + for ( front = 0, l = 0; l < numBounds; l++ ) { + sides[l] = p->bounds.PlaneSide( passageBounds[l] ); + // if completely at the back of the passage bounding plane + if ( sides[l] == PLANESIDE_BACK ) { + break; + } + // if completely at the front + if ( sides[l] == PLANESIDE_FRONT ) { + front++; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + + // if not at the front of all bounding planes and thus not completely inside the passage + if ( front != numBounds ) { + + winding = *p->w; + + for ( l = 0; l < numBounds; l++ ) { + // only clip if the winding possibly crosses this plane + if ( sides[l] != PLANESIDE_CROSS ) { + continue; + } + // clip away the part at the back of the bounding plane + winding.ClipInPlace( passageBounds[l] ); + // if completely clipped away + if ( !winding.GetNumPoints() ) { + break; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + } + + canSee |= bit; + } + + // store results of all eight portals + passage->canSee[byteNum] = canSee; + } + + // can always see the target portal + passage->canSee[n >> 3] |= (1 << (n&7)); + } + } + if ( passageMemory < 1024 ) { + gameLocal.Printf( "%5d bytes passage memory used to build PVS\n", passageMemory ); + } + else { + gameLocal.Printf( "%5d KB passage memory used to build PVS\n", passageMemory>>10 ); + } +} + +/* +================ +idPVS::DestroyPassages +================ +*/ +void idPVS::DestroyPassages( void ) const { + int i, j; + pvsPortal_t *p; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; + area = &pvsAreas[p->areaNum]; + for ( j = 0; j < area->numPortals; j++ ) { + if ( p->passages[j].canSee ) { + delete[] p->passages[j].canSee; + } + } + delete[] p->passages; + } +} + +/* +================ +idPVS::CopyPortalPVSToMightSee +================ +*/ +void idPVS::CopyPortalPVSToMightSee( void ) const { + int i; + pvsPortal_t *p; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( p->mightSee, p->vis, portalVisBytes ); +// RAVEN BEGIN + } +} + +/* +================ +idPVS::AreaPVSFromPortalPVS +================ +*/ +int idPVS::AreaPVSFromPortalPVS( void ) const { + int i, j, k, areaNum, totalVisibleAreas; + long *p1, *p2; + byte *pvs, *portalPVS; + pvsArea_t *area; + + totalVisibleAreas = 0; + + if ( !numPortals ) { + return totalVisibleAreas; + } + + memset( areaPVS, 0, numAreas * areaVisBytes ); + + for ( i = 0; i < numAreas; i++ ) { + area = &pvsAreas[i]; + pvs = areaPVS + i * areaVisBytes; + + // the area is visible to itself + pvs[ i >> 3 ] |= 1 << (i & 7); + + if ( !area->numPortals ) { + continue; + } + + // store the PVS of all portals in this area at the first portal + for ( j = 1; j < area->numPortals; j++ ) { + p1 = reinterpret_cast(area->portals[0]->vis); + p2 = reinterpret_cast(area->portals[j]->vis); + for ( k = 0; k < portalVisLongs; k++ ) { + *p1++ |= *p2++; + } + } + + // the portals of this area are always visible + for ( j = 0; j < area->numPortals; j++ ) { + k = area->portals[j] - pvsPortals; + area->portals[0]->vis[ k >> 3 ] |= 1 << (k & 7); + } + + // set all areas to visible that can be seen from the portals of this area + portalPVS = area->portals[0]->vis; + for ( j = 0; j < numPortals; j++ ) { + // if this portal is visible + if ( portalPVS[j>>3] & (1 << (j&7)) ) { + areaNum = pvsPortals[j].areaNum; + pvs[ areaNum >> 3 ] |= 1 << (areaNum & 7); + } + } + + // count the number of visible areas + for ( j = 0; j < numAreas; j++ ) { + if ( pvs[j>>3] & (1 << (j&7)) ) { + totalVisibleAreas++; + } + } + } + return totalVisibleAreas; +} + +/* +================ +idPVS::Init +================ +*/ +void idPVS::Init( void ) { + int totalVisibleAreas; + + Shutdown(); + + numAreas = gameRenderWorld->NumAreas(); + if ( numAreas <= 0 ) { + return; + } + + connectedAreas = new bool[numAreas]; + areaQueue = new int[numAreas]; + + areaVisBytes = ( ((numAreas+31)&~31) >> 3); + areaVisLongs = areaVisBytes/sizeof(long); + areaPVS = new byte[numAreas * areaVisBytes]; + memset( areaPVS, 0xFF, numAreas * areaVisBytes ); + + numPortals = GetPortalCount(); + + portalVisBytes = ( ((numPortals+31)&~31) >> 3); + portalVisLongs = portalVisBytes/sizeof(long); + + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = new byte[areaVisBytes]; + memset( currentPVS[i].pvs, 0, areaVisBytes ); + } + + idTimer timer; + timer.Start(); + + CreatePVSData(); + + FrontPortalPVS(); + + CopyPortalPVSToMightSee(); + + PassagePVS(); + + totalVisibleAreas = AreaPVSFromPortalPVS(); + + DestroyPVSData(); + + timer.Stop(); + +#if 0 + // bkreimeier20060123 - per MrE's recommendation, used this in Wolf + // if the PVS is not relexive for (i,j), we assume floating point errors + // and assume a false positive - remove either from either + for ( int i=0; i>3] & (1<<(j&7))); + const bool i_in_j = (pvs_j[i>>3] & (1<<(i&7))); + if ( i_in_j!=j_in_i ) { + pvs_i[j>>3] &= ~(1 << (j&7)); + pvs_j[i>>3] &= ~(1 << (i&7)); + } + } + } +#endif + + gameLocal.Printf( "%5.0f msec to calculate PVS\n", timer.Milliseconds() ); + gameLocal.Printf( "%5d areas\n", numAreas ); + gameLocal.Printf( "%5d portals\n", numPortals ); + +// RAVEN BEGIN +// rjohnson: fix for div by 0 + if( numAreas ) { + gameLocal.Printf( "%5d areas visible on average\n", totalVisibleAreas / numAreas ); + } +// RAVEN END + + if ( numAreas * areaVisBytes < 1024 ) { + gameLocal.Printf( "%5d bytes PVS data\n", numAreas * areaVisBytes ); + } + else { + gameLocal.Printf( "%5d KB PVS data\n", (numAreas * areaVisBytes) >> 10 ); + } +} + +/* +================ +idPVS::Shutdown +================ +*/ +void idPVS::Shutdown( void ) { + if ( connectedAreas ) { + delete connectedAreas; + connectedAreas = NULL; + } + if ( areaQueue ) { + delete areaQueue; + areaQueue = NULL; + } + if ( areaPVS ) { + delete areaPVS; + areaPVS = NULL; + } + if ( currentPVS ) { + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { +// RAVEN BEGIN +// jsinger: modified to check to make sure the pointers have a value before attempting to delete +// them. This prevents a call through the unified allocator before it has been initialized + if(currentPVS[i].pvs) + { + delete currentPVS[i].pvs; + } +// RAVEN END + currentPVS[i].pvs = NULL; + } + } +} + +/* +================ +idPVS::GetConnectedAreas + + assumes the 'areas' array is initialized to false +================ +*/ +void idPVS::GetConnectedAreas( int srcArea, bool *areas ) const { + int curArea, nextArea; + int queueStart, queueEnd; + int i, n; + exitPortal_t portal; + + queueStart = -1; + queueEnd = 0; + areas[srcArea] = true; + + for ( curArea = srcArea; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + n = gameRenderWorld->NumPortalsInArea( curArea ); + + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( curArea, i ); + + if ( portal.blockingBits & PS_BLOCK_VIEW ) { + continue; + } + + // area[1] is always the area the portal leads to + nextArea = portal.areas[1]; + + // if already visited this area + if ( areas[nextArea] ) { + continue; + } + + // add area to queue + areaQueue[queueEnd++] = nextArea; + areas[nextArea] = true; + } + } +} + +/* +================ +idPVS::GetPVSArea +================ +*/ +int idPVS::GetPVSArea( const idVec3 &point ) const { + return gameRenderWorld->PointInArea( point ); +} + +/* +================ +idPVS::GetPVSAreas +================ +*/ +int idPVS::GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const { + return gameRenderWorld->BoundsInAreas( bounds, areas, maxAreas ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idVec3 &source, const pvsType_t type ) const { + int sourceArea; + + sourceArea = gameRenderWorld->PointInArea( source ); + + return SetupCurrentPVS( sourceArea, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idBounds &source, const pvsType_t type ) const { + int numSourceAreas, sourceAreas[MAX_BOUNDS_AREAS]; + + numSourceAreas = gameRenderWorld->BoundsInAreas( source, sourceAreas, MAX_BOUNDS_AREAS ); + + return SetupCurrentPVS( sourceAreas, numSourceAreas, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int sourceArea, const pvsType_t type ) const { + int i; + pvsHandle_t handle; + + handle = AllocCurrentPVS( *reinterpret_cast(&sourceArea) ); + + if ( sourceArea < 0 || sourceArea >= numAreas ) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceArea * areaVisBytes, areaVisBytes ); +// RAVEN END + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + GetConnectedAreas( sourceArea, connectedAreas ); + + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type ) const { + int i, j; + unsigned int h; + long *vis, *pvs; + pvsHandle_t handle; + + h = 0; + for ( i = 0; i < numSourceAreas; i++ ) { + h ^= *reinterpret_cast(&sourceAreas[i]); + } + handle = AllocCurrentPVS( h ); + + if ( !numSourceAreas || sourceAreas[0] < 0 || sourceAreas[0] >= numAreas) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { + // merge PVS of all areas the source is in +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceAreas[0] * areaVisBytes, areaVisBytes ); +// RAVEN END + for ( i = 1; i < numSourceAreas; i++ ) { + + assert( sourceAreas[i] >= 0 && sourceAreas[i] < numAreas ); + + vis = reinterpret_cast(areaPVS + sourceAreas[i] * areaVisBytes); + pvs = reinterpret_cast(currentPVS[handle.i].pvs); + for ( j = 0; j < areaVisLongs; j++ ) { + *pvs++ |= *vis++; + } + } + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + // get all areas connected to any of the source areas + for ( i = 0; i < numSourceAreas; i++ ) { + if ( !connectedAreas[sourceAreas[i]] ) { + GetConnectedAreas( sourceAreas[i], connectedAreas ); + } + } + + // remove unconnected areas from the PVS + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::MergeCurrentPVS +================ +*/ +pvsHandle_t idPVS::MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const { + int i; + long *pvs1Ptr, *pvs2Ptr, *ptr; + pvsHandle_t handle; + + if ( pvs1.i < 0 || pvs1.i >= MAX_CURRENT_PVS || pvs1.h != currentPVS[pvs1.i].handle.h || + pvs2.i < 0 || pvs2.i >= MAX_CURRENT_PVS || pvs2.h != currentPVS[pvs2.i].handle.h ) { + gameLocal.Error( "idPVS::MergeCurrentPVS: invalid handle" ); + } + + handle = AllocCurrentPVS( pvs1.h ^ pvs2.h ); + + ptr = reinterpret_cast(currentPVS[handle.i].pvs); + pvs1Ptr = reinterpret_cast(currentPVS[pvs1.i].pvs); + pvs2Ptr = reinterpret_cast(currentPVS[pvs2.i].pvs); + + for ( i = 0; i < areaVisLongs; i++ ) { + *ptr++ = *pvs1Ptr++ | *pvs2Ptr++; + } + + return handle; +} + +/* +================ +idPVS::AllocCurrentPVS +================ +*/ +pvsHandle_t idPVS::AllocCurrentPVS( unsigned int h ) const { + int i; + pvsHandle_t handle; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + if ( currentPVS[i].handle.i == -1 ) { + currentPVS[i].handle.i = i; + currentPVS[i].handle.h = h; + return currentPVS[i].handle; + } + } + + gameLocal.Error( "idPVS::AllocCurrentPVS: no free PVS left" ); + + handle.i = -1; + handle.h = 0; + return handle; +} + +/* +================ +idPVS::FreeCurrentPVS +================ +*/ +void idPVS::FreeCurrentPVS( pvsHandle_t handle ) const { + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::FreeCurrentPVS: invalid handle" ); + } + currentPVS[handle.i].handle.i = -1; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const { + int targetArea; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + targetArea = gameRenderWorld->PointInArea( target ); + + if ( targetArea == -1 ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const { + int i, numTargetAreas, targetAreas[MAX_BOUNDS_AREAS]; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + numTargetAreas = gameRenderWorld->BoundsInAreas( target, targetAreas, MAX_BOUNDS_AREAS ); + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const { + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + if ( targetArea < 0 || targetArea >= numAreas ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const { + int i; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( targetAreas[i] < 0 || targetAreas[i] >= numAreas ) { + continue; + } + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idVec3 &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idBounds &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, num, areas[MAX_BOUNDS_AREAS]; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + num = gameRenderWorld->BoundsInAreas( source, areas, MAX_BOUNDS_AREAS ); + + if ( !num ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + for ( i = 0; i < num; i++ ) { + if ( j == areas[i] ) { + break; + } + } + if ( i < num ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::DrawCurrentPVS: invalid handle" ); + } + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } +} + +#if ASYNC_WRITE_PVS + +/* +=================== +idPVS::WritePVS +=================== +*/ +void idPVS::WritePVS( const pvsHandle_t handle, idBitMsg &msg ) { + msg.WriteData( currentPVS[ handle.i ].pvs, areaVisBytes ); +} + +/* +=================== +idPVS::ReadPVS +=================== +*/ +void idPVS::ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ) { + byte l_pvs[ 256 ]; + int i; + + assert( areaVisBytes <= 256 ); + msg.ReadData( l_pvs, areaVisBytes ); + if ( memcmp( l_pvs, currentPVS[ handle.i ].pvs, areaVisBytes ) ) { + common->Printf( "PVS not matching ( %d areaVisBytes ) - server then client:\n", areaVisBytes ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", l_pvs[ i ] ); + } + common->Printf( "\n" ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", currentPVS[ handle.i ].pvs[ i ] ); + } + common->Printf( "\n" ); + } +} + +#endif + diff --git a/src/game/Pvs.h b/src/game/Pvs.h new file mode 100644 index 0000000..55a4c9d --- /dev/null +++ b/src/game/Pvs.h @@ -0,0 +1,127 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_PVS_H__ +#define __GAME_PVS_H__ + +/* +=================================================================================== + + PVS + + Note: mirrors and other special view portals are not taken into account + +=================================================================================== +*/ + + +typedef struct pvsHandle_s { + int i; // index to current pvs + unsigned int h; // handle for current pvs +} pvsHandle_t; + +typedef struct pvsCurrent_s { + pvsHandle_t handle; // current pvs handle + byte * pvs; // current pvs bit string +} pvsCurrent_t; + +// must be a power of 2 +// base was 8, MP now stores client PVS from frame to frame ( MAX_CLIENTS ) +#define MAX_CURRENT_PVS 64 + +typedef enum { + PVS_NORMAL = 0, // PVS through portals taking portal states into account + PVS_ALL_PORTALS_OPEN = 1, // PVS through portals assuming all portals are open + PVS_CONNECTED_AREAS = 2 // PVS considering all topologically connected areas visible +} pvsType_t; + + +class idPVS { +public: + idPVS( void ); + ~idPVS( void ); + // setup for the current map + void Init( void ); + void Shutdown( void ); + // get the area(s) the source is in + int GetPVSArea( const idVec3 &point ) const; // returns the area number + int GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const; // returns number of areas + // setup current PVS for the source + pvsHandle_t SetupCurrentPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int sourceArea, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const; + void FreeCurrentPVS( pvsHandle_t handle ) const; + // returns true if the target is within the current PVS + bool InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const; + // draw all portals that are within the PVS of the source + void DrawPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + void DrawPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + // visualize the PVS the handle points to + void DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const; + +#if ASYNC_WRITE_PVS + void WritePVS( const pvsHandle_t handle, idBitMsg &msg ); + void ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ); +#endif + +private: + int numAreas; + int numPortals; + bool * connectedAreas; + int * areaQueue; + byte * areaPVS; + + // current PVS for a specific source possibly taking portal states (open/closed) into account + mutable pvsCurrent_t currentPVS[MAX_CURRENT_PVS]; + // used to create PVS + int portalVisBytes; + int portalVisLongs; + int areaVisBytes; + int areaVisLongs; + struct pvsPortal_s *pvsPortals; + struct pvsArea_s * pvsAreas; + +private: + int GetPortalCount( void ) const; + void CreatePVSData( void ); + void DestroyPVSData( void ); + void CopyPortalPVSToMightSee( void ) const; + void FloodFrontPortalPVS_r( struct pvsPortal_s *portal, int areaNum ) const; + void FrontPortalPVS( void ) const; + struct pvsStack_s * FloodPassagePVS_r( struct pvsPortal_s *source, const struct pvsPortal_s *portal, struct pvsStack_s *prevStack ) const; + void PassagePVS( void ) const; + void AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const; + void CreatePassages( void ) const; + void DestroyPassages( void ) const; + int AreaPVSFromPortalPVS( void ) const; + void GetConnectedAreas( int srcArea, bool *connectedAreas ) const; + pvsHandle_t AllocCurrentPVS( unsigned int h ) const; +}; + +#endif /* !__GAME_PVS_H__ */ diff --git a/src/game/SecurityCamera.cpp b/src/game/SecurityCamera.cpp new file mode 100644 index 0000000..2f30941 --- /dev/null +++ b/src/game/SecurityCamera.cpp @@ -0,0 +1,602 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + + SecurityCamera.cpp + + Security camera that triggers targets when player is in view + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/*********************************************************************** + + idSecurityCamera + +***********************************************************************/ + +const idEventDef EV_SecurityCam_ReverseSweep( "" ); +const idEventDef EV_SecurityCam_ContinueSweep( "" ); +const idEventDef EV_SecurityCam_Pause( "" ); +const idEventDef EV_SecurityCam_Alert( "" ); +const idEventDef EV_SecurityCam_AddLight( "" ); + +CLASS_DECLARATION( idEntity, idSecurityCamera ) + EVENT( EV_SecurityCam_ReverseSweep, idSecurityCamera::Event_ReverseSweep ) + EVENT( EV_SecurityCam_ContinueSweep, idSecurityCamera::Event_ContinueSweep ) + EVENT( EV_SecurityCam_Pause, idSecurityCamera::Event_Pause ) + EVENT( EV_SecurityCam_Alert, idSecurityCamera::Event_Alert ) + EVENT( EV_SecurityCam_AddLight, idSecurityCamera::Event_AddLight ) +END_CLASS + + +idSecurityCamera::~idSecurityCamera() { + SetPhysics( NULL ); +} + +/* +================ +idSecurityCamera::Save +================ +*/ +void idSecurityCamera::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( angle ); + savefile->WriteFloat( sweepAngle ); + savefile->WriteInt( modelAxis ); + savefile->WriteBool( flipAxis ); + savefile->WriteFloat( scanDist ); + savefile->WriteFloat( scanFov ); + + savefile->WriteFloat( sweepStart ); + savefile->WriteFloat( sweepEnd ); + savefile->WriteBool( negativeSweep ); + savefile->WriteBool( sweeping ); + savefile->WriteInt( alertMode ); + savefile->WriteFloat( stopSweeping ); + savefile->WriteFloat( scanFovCos ); + + savefile->WriteVec3( viewOffset ); + + savefile->WriteInt( pvsArea ); + savefile->WriteStaticObject( physicsObj ); + savefile->Write( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Restore +================ +*/ +void idSecurityCamera::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( angle ); + savefile->ReadFloat( sweepAngle ); + savefile->ReadInt( modelAxis ); + savefile->ReadBool( flipAxis ); + savefile->ReadFloat( scanDist ); + savefile->ReadFloat( scanFov ); + + savefile->ReadFloat( sweepStart ); + savefile->ReadFloat( sweepEnd ); + savefile->ReadBool( negativeSweep ); + savefile->ReadBool( sweeping ); + savefile->ReadInt( alertMode ); + savefile->ReadFloat( stopSweeping ); + savefile->ReadFloat( scanFovCos ); + + savefile->ReadVec3( viewOffset ); + + savefile->ReadInt( pvsArea ); + savefile->ReadStaticObject( physicsObj ); + savefile->Read( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Spawn +================ +*/ +void idSecurityCamera::Spawn( void ) { + idStr str; + + sweepAngle = spawnArgs.GetFloat( "sweepAngle", "90" ); + health = spawnArgs.GetInt( "health", "100" ); + scanFov = spawnArgs.GetFloat( "scanFov", "90" ); + scanDist = spawnArgs.GetFloat( "scanDist", "200" ); + flipAxis = spawnArgs.GetBool( "flipAxis" ); + + modelAxis = spawnArgs.GetInt( "modelAxis" ); + if ( modelAxis < 0 || modelAxis > 2 ) { + modelAxis = 0; + } + + spawnArgs.GetVector( "viewOffset", "0 0 0", viewOffset ); + + if ( spawnArgs.GetBool( "spotLight" ) ) { + PostEventMS( &EV_SecurityCam_AddLight, 0 ); + } + + negativeSweep = ( sweepAngle < 0 ) ? true : false; + sweepAngle = abs( sweepAngle ); + + scanFovCos = idMath::Cos( scanFov * idMath::PI / 360.0f ); + + angle = GetPhysics()->GetAxis().ToAngles().yaw; + StartSweep(); + SetAlertMode( SCANNING ); + BecomeActive( TH_THINK ); + + if ( health ) { + fl.takedamage = true; + } + + pvsArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + // if no target specified use ourself + str = spawnArgs.GetString( "cameraTarget" ); + if ( str.Length() == 0 ) { + spawnArgs.Set( "cameraTarget", spawnArgs.GetString( "name" ) ); + } + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", str ); + if ( !str[0] ) { + str = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), str, trm ) ) { + gameLocal.Error( "idSecurityCamera '%s': cannot load collision model %s", name.c_str(), str.c_str() ); + return; + } + + GetPhysics()->SetContents( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + // setup the physics + UpdateChangeableSpawnArgs( NULL ); +} + +/* +================ +idSecurityCamera::Event_AddLight +================ +*/ +void idSecurityCamera::Event_AddLight( void ) { + idDict args; + idVec3 right, up, target, temp; + idVec3 dir; + float radius; + idVec3 lightOffset; + idLight *spotLight; + + dir = GetAxis(); + dir.NormalVectors( right, up ); + target = GetPhysics()->GetOrigin() + dir * scanDist; + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + up = dir + up * radius; + up.Normalize(); + up = GetPhysics()->GetOrigin() + up * scanDist; + up -= target; + + right = dir + right * radius; + right.Normalize(); + right = GetPhysics()->GetOrigin() + right * scanDist; + right -= target; + + spawnArgs.GetVector( "lightOffset", "0 0 0", lightOffset ); + + args.Set( "origin", ( GetPhysics()->GetOrigin() + lightOffset ).ToString() ); + args.Set( "light_target", target.ToString() ); + args.Set( "light_right", right.ToString() ); + args.Set( "light_up", up.ToString() ); + args.SetFloat( "angle", GetPhysics()->GetAxis()[0].ToYaw() ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + spotLight = static_cast( gameLocal.SpawnEntityType( idLight::GetClassType(), &args ) ); +// RAVEN END + spotLight->Bind( this, true ); + spotLight->UpdateVisuals(); +} + +/* +================ +idSecurityCamera::DrawFov +================ +*/ +void idSecurityCamera::DrawFov( void ) { + int i; + float radius, a, s, c, halfRadius; + idVec3 right, up; + idVec4 color(1, 0, 0, 1), color2(0, 0, 1, 1); + idVec3 lastPoint, point, lastHalfPoint, halfPoint, center; + + idVec3 dir = GetAxis(); + dir.NormalVectors( right, up ); + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + halfRadius = radius * 0.5f; + lastPoint = dir + up * radius; + lastPoint.Normalize(); + lastPoint = GetPhysics()->GetOrigin() + lastPoint * scanDist; + lastHalfPoint = dir + up * halfRadius; + lastHalfPoint.Normalize(); + lastHalfPoint = GetPhysics()->GetOrigin() + lastHalfPoint * scanDist; + center = GetPhysics()->GetOrigin() + dir * scanDist; + for ( i = 1; i < 12; i++ ) { + a = idMath::TWO_PI * i / 12.0f; + idMath::SinCos( a, s, c ); + point = dir + right * s * radius + up * c * radius; + point.Normalize(); + point = GetPhysics()->GetOrigin() + point * scanDist; + gameRenderWorld->DebugLine( color, lastPoint, point ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), point ); + lastPoint = point; + + halfPoint = dir + right * s * halfRadius + up * c * halfRadius; + halfPoint.Normalize(); + halfPoint = GetPhysics()->GetOrigin() + halfPoint * scanDist; + gameRenderWorld->DebugLine( color2, point, halfPoint ); + gameRenderWorld->DebugLine( color2, lastHalfPoint, halfPoint ); + lastHalfPoint = halfPoint; + + gameRenderWorld->DebugLine( color2, halfPoint, center ); + } +} + +/* +================ +idSecurityCamera::GetRenderView +================ +*/ +renderView_t *idSecurityCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->fov_x = scanFov; + rv->fov_y = scanFov; + rv->viewaxis = GetAxis().ToAngles().ToMat3(); + rv->vieworg = GetPhysics()->GetOrigin() + viewOffset; + return rv; +} + +/* +================ +idSecurityCamera::CanSeePlayer +================ +*/ +bool idSecurityCamera::CanSeePlayer( void ) { + int i; + float dist; + idPlayer *ent; + trace_t tr; + idVec3 dir; + pvsHandle_t handle; + + handle = gameLocal.pvs.SetupCurrentPVS( pvsArea ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = static_cast(gameLocal.entities[ i ]); + + if ( !ent || ( ent->fl.notarget ) ) { + continue; + } + + // if there is no way we can see this player + if ( !gameLocal.pvs.InCurrentPVS( handle, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dist = dir.Normalize(); + + if ( dist > scanDist ) { + continue; + } + + if ( dir * GetAxis() < scanFovCos ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, GetPhysics()->GetOrigin(), ent->GetEyePosition(), MASK_OPAQUE, this ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + gameLocal.pvs.FreeCurrentPVS( handle ); + return true; + } + } + + gameLocal.pvs.FreeCurrentPVS( handle ); + + return false; +} + +/* +================ +idSecurityCamera::SetAlertMode +================ +*/ +void idSecurityCamera::SetAlertMode( int alert ) { + if (alert >= SCANNING && alert <= ACTIVATED) { + alertMode = alert; + } + renderEntity.shaderParms[ SHADERPARM_MODE ] = alertMode; + UpdateVisuals(); +} + +/* +================ +idSecurityCamera::Think +================ +*/ +void idSecurityCamera::Think( void ) { + float pct; + float travel; + + if ( thinkFlags & TH_THINK ) { + if ( g_showEntityInfo.GetBool() ) { + DrawFov(); + } + + if (health <= 0) { + BecomeInactive( TH_THINK ); + return; + } + } + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + if (CanSeePlayer()) { + if (alertMode == SCANNING) { + float sightTime; + + SetAlertMode(ALERT); + stopSweeping = gameLocal.time; + if (sweeping) { + CancelEvents( &EV_SecurityCam_Pause ); + } else { + CancelEvents( &EV_SecurityCam_ReverseSweep ); + } + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_sight", SND_CHANNEL_BODY, 0, false, NULL ); + + sightTime = spawnArgs.GetFloat( "sightTime", "5" ); + PostEventSec(&EV_SecurityCam_Alert, sightTime); + } + } else { + if (alertMode == ALERT) { + float sightResume; + + SetAlertMode(LOSINGINTEREST); + CancelEvents( &EV_SecurityCam_Alert ); + + sightResume = spawnArgs.GetFloat( "sightResume", "1.5" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, sightResume ); + } + + if ( sweeping ) { + idAngles a = GetPhysics()->GetAxis().ToAngles(); + + pct = ( gameLocal.time - sweepStart ) / ( sweepEnd - sweepStart ); + travel = pct * sweepAngle; + if ( negativeSweep ) { + a.yaw = angle + travel; + } else { + a.yaw = angle - travel; + } + + SetAngles( a ); + } + } + } + Present(); +} + +/* +================ +idSecurityCamera::GetAxis +================ +*/ +const idVec3 idSecurityCamera::GetAxis( void ) const { + return (flipAxis) ? -GetPhysics()->GetAxis()[modelAxis] : GetPhysics()->GetAxis()[modelAxis]; +}; + +/* +================ +idSecurityCamera::SweepSpeed +================ +*/ +float idSecurityCamera::SweepSpeed( void ) const { + return spawnArgs.GetFloat( "sweepSpeed", "5" ); +} + +/* +================ +idSecurityCamera::StartSweep +================ +*/ +void idSecurityCamera::StartSweep( void ) { + int speed; + + sweeping = true; + sweepStart = gameLocal.time; + speed = SEC2MS( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed ); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idSecurityCamera::Event_ContinueSweep +================ +*/ +void idSecurityCamera::Event_ContinueSweep( void ) { + float pct = (stopSweeping - sweepStart) / (sweepEnd - sweepStart); + float f = gameLocal.time - (sweepEnd - sweepStart) * pct; + int speed; + + sweepStart = f; + speed = MS2SEC( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed * (1.0 - pct)); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); + SetAlertMode(SCANNING); + sweeping = true; +} + +/* +================ +idSecurityCamera::Event_Alert +================ +*/ +void idSecurityCamera::Event_Alert( void ) { + float wait; + + SetAlertMode(ACTIVATED); + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_activate", SND_CHANNEL_BODY, 0, false, NULL ); + ActivateTargets(this); + CancelEvents( &EV_SecurityCam_ContinueSweep ); + + wait = spawnArgs.GetFloat( "wait", "20" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, wait ); +} + +/* +================ +idSecurityCamera::Event_ReverseSweep +================ +*/ +void idSecurityCamera::Event_ReverseSweep( void ) { + angle = GetPhysics()->GetAxis().ToAngles().yaw; + negativeSweep = !negativeSweep; + StartSweep(); +} + +/* +================ +idSecurityCamera::Event_Pause +================ +*/ +void idSecurityCamera::Event_Pause( void ) { + float sweepWait; + + sweepWait = spawnArgs.GetFloat( "sweepWait", "0.5" ); + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_stop", SND_CHANNEL_BODY, 0, false, NULL ); + PostEventSec( &EV_SecurityCam_ReverseSweep, sweepWait ); +} + +/* +============ +idSecurityCamera::Killed +============ +*/ +void idSecurityCamera::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); +// RAVEN BEGIN +// bdube: replaced fx call with raven call + gameLocal.PlayEffect ( spawnArgs, "fx_destroyed", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +/* + const char *fx = spawnArgs.GetString( "fx_destroyed" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( trm ), 0.02f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( 0.2f ); + physicsObj.SetFriction( 0.6f, 0.6f, 0.2f ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + physicsObj.DropToFloor(); +} + + +/* +============ +idSecurityCamera::Pain +============ +*/ +bool idSecurityCamera::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +// RAVEN BEGIN +// bdube: replaced fx call with raven call + PlayEffect ( "fx_damage", renderEntity.origin, renderEntity.axis ); +/* + const char *fx = spawnArgs.GetString( "fx_damage" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + return true; +} + + +/* +================ +idSecurityCamera::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idSecurityCamera::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views + if ( cameraTarget ) { + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} diff --git a/src/game/SecurityCamera.h b/src/game/SecurityCamera.h new file mode 100644 index 0000000..94136a7 --- /dev/null +++ b/src/game/SecurityCamera.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_SECURITYCAMERA_H__ +#define __GAME_SECURITYCAMERA_H__ + +/* +=================================================================================== + + Security camera + +=================================================================================== +*/ + + +class idSecurityCamera : public idEntity { +public: + CLASS_PROTOTYPE( idSecurityCamera ); + + ~idSecurityCamera( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual renderView_t * GetRenderView(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Present( void ); + +private: + + enum { SCANNING, LOSINGINTEREST, ALERT, ACTIVATED }; + + float angle; + float sweepAngle; + int modelAxis; + bool flipAxis; + float scanDist; + float scanFov; + + float sweepStart; + float sweepEnd; + bool negativeSweep; + bool sweeping; + int alertMode; + float stopSweeping; + float scanFovCos; + + idVec3 viewOffset; + + int pvsArea; + idPhysics_RigidBody physicsObj; + idTraceModel trm; + + void StartSweep( void ); + bool CanSeePlayer( void ); + void SetAlertMode( int status ); + void DrawFov( void ); + const idVec3 GetAxis( void ) const; + float SweepSpeed( void ) const; + + void Event_ReverseSweep( void ); + void Event_ContinueSweep( void ); + void Event_Pause( void ); + void Event_Alert( void ); + void Event_AddLight( void ); +}; + +#endif /* !__GAME_SECURITYCAMERA_H__ */ diff --git a/src/game/Sound.cpp b/src/game/Sound.cpp new file mode 100644 index 0000000..f0a0352 --- /dev/null +++ b/src/game/Sound.cpp @@ -0,0 +1,394 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + SOUND + +=============================================================================== +*/ + +const idEventDef EV_Speaker_On( "On", NULL ); +const idEventDef EV_Speaker_Off( "Off", NULL ); +const idEventDef EV_Speaker_Timer( "", NULL ); + +CLASS_DECLARATION( idEntity, idSound ) + EVENT( EV_Activate, idSound::Event_Trigger ) + EVENT( EV_Speaker_On, idSound::Event_On ) + EVENT( EV_Speaker_Off, idSound::Event_Off ) + EVENT( EV_Speaker_Timer, idSound::Event_Timer ) +END_CLASS + + +/* +================ +idSound::idSound +================ +*/ +idSound::idSound( void ) { + lastSoundVol = 0.0f; + soundVol = 0.0f; + shakeTranslate.Zero(); + shakeRotate.Zero(); + random = 0.0f; + wait = 0.0f; + timerOn = false; + playingUntilTime = 0; +} + +/* +================ +idSound::Save +================ +*/ +void idSound::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( lastSoundVol ); + savefile->WriteFloat( soundVol ); + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( timerOn ); + savefile->WriteVec3( shakeTranslate ); + savefile->WriteAngles( shakeRotate ); + savefile->WriteInt( playingUntilTime ); +} + +/* +================ +idSound::Restore +================ +*/ +void idSound::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( lastSoundVol ); + savefile->ReadFloat( soundVol ); + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( timerOn ); + savefile->ReadVec3( shakeTranslate ); + savefile->ReadAngles( shakeRotate ); + savefile->ReadInt( playingUntilTime ); +} + +/* +================ +idSound::Spawn +================ +*/ +void idSound::Spawn( void ) { + spawnArgs.GetVector( "move", "0 0 0", shakeTranslate ); + spawnArgs.GetAngles( "rotate", "0 0 0", shakeRotate ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + soundVol = 0.0f; + lastSoundVol = 0.0f; + + if ( ( shakeRotate != ang_zero ) || ( shakeTranslate != vec3_zero ) ) { + BecomeActive( TH_THINK ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } else { + timerOn = false; + } + +} + +/* +================ +idSound::Event_Trigger + +this will toggle the idle idSound on and off +================ +*/ +void idSound::Event_Trigger( idEntity *activator ) { + if ( wait > 0.0f ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } else { + timerOn = true; + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + } else { + if ( gameLocal.isMultiplayer ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && ( gameLocal.time < playingUntilTime ) ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } else { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } + } +} + +/* +================ +idSound::Event_Timer +================ +*/ +void idSound::Event_Timer( void ) { + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idSound::Think +================ +*/ +void idSound::Think( void ) { + idAngles ang; + + // run physics + RunPhysics(); + + // clear out our update visuals think flag since we never call Present + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============== +idSound::UpdateChangableSpawnArgs +=============== +*/ +void idSound::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + FreeSoundEmitter( true ); + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + + spawnArgs.Copy( *source ); + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + DoSound( false ); + CancelEvents( &EV_Speaker_Timer ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +// RAVEN BEGIN + } else if ( !refSound.waitfortrigger ) { + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + // start it if it isn't already playing, and we aren't waitForTrigger + DoSound( true ); + timerOn = false; + } + } + } +} + +/* +=============== +idSound::SetSound +=============== +*/ +void idSound::SetSound( const char *sound, int channel ) { + const idSoundShader *shader = declManager->FindSound( sound ); + if ( shader != refSound.shader ) { + FreeSoundEmitter( true ); + } + gameEdit->ParseSpawnArgsToRefSound(&spawnArgs, &refSound); + refSound.shader = shader; +// RAVEN BEGIN + // start it if it isn't already playing, and we aren't waitForTrigger + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !refSound.waitfortrigger && !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + DoSound( true ); + } + +} + +/* +================ +idSound::DoSound +================ +*/ +void idSound::DoSound( bool play ) { + if ( play ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, refSound.parms.soundShaderFlags, true, &playingUntilTime ); + playingUntilTime += gameLocal.time; + } else { + StopSound( SND_CHANNEL_ANY, true ); + playingUntilTime = 0; + } +} + +/* +================ +idSound::Event_On +================ +*/ +void idSound::Event_On( void ) { + if ( wait > 0.0f ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + DoSound( true ); +} + +/* +================ +idSound::Event_Off +================ +*/ +void idSound::Event_Off( void ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } + DoSound( false ); +} + +// RAVEN BEGIN +// abahr: so we only set the referenceSounds on our targets once +/* +================ +idSound::FindTargets +================ +*/ +void idSound::FindTargets() { + idEntity::FindTargets(); + + if( !targets.Num() ) {// I don't think we ever get in here unless we have targets + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + // if we have targets lets get an emitter + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + SetTargetSoundHandles(); +} +// RAVEN END + +/* +=============== +idSound::ShowEditingDialog +=============== +*/ +void idSound::ShowEditingDialog( void ) { + common->InitTool( EDITOR_SOUND, &spawnArgs ); +} + +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and have those lights use this speaker's refSound +/* +=============== +idSound::SetSoundHandles +=============== +*/ +void idSound::SetTargetSoundHandles( void ) { +//this code is mostly boosted from a similar function in light.cpp + int i; + idEntity *targetEnt; + + //is this check really necessary? We are a speaker after all... + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( targetEnt && targetEnt->IsType( idLight::GetClassType() ) ) { +// RAVEN END + idLight *light = static_cast(targetEnt); + + //no need to make this speaker a lightparent.... + //light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->SetRefSound(refSound.referenceSoundHandle); + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } + } +} + +// abahr: +/* +================ +idSound::GetPhysicsToSoundTransform +================ +*/ +bool idSound::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = shakeTranslate.Random( spawnArgs.GetVector("move_random_delta"), gameLocal.random ); + axis = shakeRotate.Random( spawnArgs.GetVector("shake_random_delta"), gameLocal.random ).ToMat3(); + return true; +} +// RAVEN END diff --git a/src/game/Sound.h b/src/game/Sound.h new file mode 100644 index 0000000..cbab583 --- /dev/null +++ b/src/game/Sound.h @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_SOUND_H__ +#define __GAME_SOUND_H__ + +/* +=============================================================================== + + Generic sound emitter. + +=============================================================================== +*/ + +class idSound : public idEntity { +public: + CLASS_PROTOTYPE( idSound ); + + idSound( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + void Spawn( void ); + + void ToggleOnOff( idEntity *other, idEntity *activator ); + void Think( void ); + void SetSound( const char *sound, int channel = SND_CHANNEL_ANY ); + + virtual void ShowEditingDialog( void ); + +private: + float lastSoundVol; + float soundVol; + float random; + float wait; + bool timerOn; + idVec3 shakeTranslate; + idAngles shakeRotate; + int playingUntilTime; + + void Event_Trigger( idEntity *activator ); + void Event_Timer( void ); + void Event_On( void ); + void Event_Off( void ); + + void DoSound( bool play ); +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and tie them to the speaker's ref sound + void SetTargetSoundHandles( void ); +// abahr + virtual void FindTargets(); + bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); +// RAVEN END + +}; + +#endif /* !__GAME_SOUND_H__ */ diff --git a/src/game/SplineMover.cpp b/src/game/SplineMover.cpp new file mode 100644 index 0000000..33b19b5 --- /dev/null +++ b/src/game/SplineMover.cpp @@ -0,0 +1,3313 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" + +const idEventDef EV_OnAcceleration( "" ); +const idEventDef EV_OnDeceleration( "" ); +const idEventDef EV_OnCruising( "" ); + +const idEventDef EV_OnStartMoving( "" ); +const idEventDef EV_OnStopMoving( "" ); + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Spline ) + EVENT( EV_PostRestore, rvPhysics_Spline::Event_PostRestore ) +END_CLASS + +void splinePState_t::ApplyAccelerationDelta( float timeStepSec ) { + speed = SignZero(idealSpeed) * Min( idMath::Fabs(idealSpeed), idMath::Fabs(speed) + acceleration * timeStepSec ); +} + +void splinePState_t::ApplyDecelerationDelta( float timeStepSec ) { + speed = SignZero(speed) * Max( idMath::Fabs(idealSpeed), idMath::Fabs(speed) - deceleration * timeStepSec ); +} + +void splinePState_t::UpdateDist( float timeStepSec ) { + dist += speed * timeStepSec; +} + +bool splinePState_t::ShouldAccelerate() const { + if( Sign(idealSpeed) == Sign(speed) ) { + return idMath::Fabs(speed) < idMath::Fabs(idealSpeed); + } else if( !Sign(speed) ) { + return true; + } + + return false; +} + +bool splinePState_t::ShouldDecelerate() const { + if( Sign(speed) == Sign(idealSpeed) ) { + return idMath::Fabs(speed) > idMath::Fabs(idealSpeed); + } else if( !Sign(idealSpeed) ) { + return true; + } + + return false; +} + +void splinePState_t::Clear() { + origin.Zero(); + localOrigin.Zero(); + axis.Identity(); + localAxis.Identity(); + speed = 0.0f; + idealSpeed = 0.0f; + dist = 0.0f; + acceleration = 0.0f; + deceleration = 0.0f; +} + +void splinePState_t::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteDeltaVec3( vec3_zero, origin ); + msg.WriteDeltaVec3( vec3_zero, localOrigin ); + msg.WriteDeltaMat3( mat3_identity, axis ); + msg.WriteDeltaMat3( mat3_identity, localAxis ); + msg.WriteDeltaFloat( 0.0f, speed ); + msg.WriteDeltaFloat( 0.0f, idealSpeed ); + msg.WriteDeltaFloat( 0.0f, dist ); + msg.WriteDeltaFloat( 0.0f, acceleration ); + msg.WriteDeltaFloat( 0.0f, deceleration ); +} + +void splinePState_t::ReadFromSnapshot( const idBitMsgDelta &msg ) { + origin = msg.ReadDeltaVec3( vec3_zero ); + localOrigin = msg.ReadDeltaVec3( vec3_zero ); + axis = msg.ReadDeltaMat3( mat3_identity ); + localAxis = msg.ReadDeltaMat3( mat3_identity ); + speed = msg.ReadDeltaFloat( 0.0f ); + idealSpeed = msg.ReadDeltaFloat( 0.0f ); + dist = msg.ReadDeltaFloat( 0.0f ); + acceleration = msg.ReadDeltaFloat( 0.0f ); + deceleration = msg.ReadDeltaFloat( 0.0f ); +} + +void splinePState_t::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( origin ); + savefile->WriteVec3( localOrigin ); + savefile->WriteMat3( axis ); + savefile->WriteMat3( localAxis ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( dist ); + savefile->WriteFloat( acceleration ); + savefile->WriteFloat( deceleration ); +} + +void splinePState_t::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( origin ); + savefile->ReadVec3( localOrigin ); + savefile->ReadMat3( axis ); + savefile->ReadMat3( localAxis ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( dist ); + savefile->ReadFloat( acceleration ); + savefile->ReadFloat( deceleration ); +} + +splinePState_t& splinePState_t::Assign( const splinePState_t* state ) { + SIMDProcessor->Memcpy( this, state, sizeof(splinePState_t) ); + return *this; +} + +splinePState_t& splinePState_t::operator=( const splinePState_t& state ) { + return Assign( &state ); +} + +splinePState_t& splinePState_t::operator=( const splinePState_t* state ) { + return Assign( state ); +} + +/* +================ +rvPhysics_Spline::rvPhysics_Spline +================ +*/ +rvPhysics_Spline::rvPhysics_Spline( void ) { + accelDecelStateThread.SetName( "AccelDecel" ); + accelDecelStateThread.SetOwner( this ); + accelDecelStateThread.SetState( "Cruising" ); + + clipModel = NULL; + + spline = NULL; + SetSplineEntity( NULL ); + + memset( &pushResults, 0, sizeof(trace_t) ); + pushResults.fraction = 1.0f; + + current.Clear(); + SaveState(); +} + +/* +================ +rvPhysics_Spline::~rvPhysics_Spline +================ +*/ +rvPhysics_Spline::~rvPhysics_Spline( void ) { + SAFE_DELETE_PTR( clipModel ); + + SAFE_DELETE_PTR( spline ); +} + +/* +================ +rvPhysics_Spline::Save +================ +*/ +void rvPhysics_Spline::Save( idSaveGame *savefile ) const { + + current.Save( savefile ); + saved.Save( savefile ); + + savefile->WriteFloat( splineLength ); + // This spline was retored as NULL, so there's no reason to save it. + //savefile->WriteInt(spline != NULL ? spline->GetTime( 0 ) : -1 ); // cnicholson: Added unsaved var + splineEntity.Save( savefile ); + + savefile->WriteTrace( pushResults ); + + savefile->WriteClipModel( clipModel ); + + accelDecelStateThread.Save( savefile ); +} + +/* +================ +rvPhysics_Spline::Restore +================ +*/ +void rvPhysics_Spline::Event_PostRestore( void ) { + + if( splineEntity.IsValid() ) { + spline = splineEntity->GetSpline(); + } +} + +void rvPhysics_Spline::Restore( idRestoreGame *savefile ) { + + current.Restore( savefile ); + saved.Restore( savefile ); + + savefile->ReadFloat( splineLength ); + SAFE_DELETE_PTR( spline ); + splineEntity.Restore( savefile ); + + savefile->ReadTrace( pushResults ); + + savefile->ReadClipModel( clipModel ); + + accelDecelStateThread.Restore( savefile, this ); +} + +/* +================ +rvPhysics_Spline::SetClipModel +================ +*/ +void rvPhysics_Spline::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; + + LinkClip(); +} + +/* +================ +rvPhysics_Spline::GetClipModel +================ +*/ +idClipModel *rvPhysics_Spline::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Spline::SetContents +================ +*/ +void rvPhysics_Spline::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Spline::GetContents +================ +*/ +int rvPhysics_Spline::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Spline::GetBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Spline::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Spline::SetSpline +================ +*/ +void rvPhysics_Spline::SetSpline( idCurve_Spline* spline ) { + SAFE_DELETE_PTR( this->spline ); + + //Keep any left over dist from last spline to minimize hitches + if( GetSpeed() >= 0.0f ) { + current.dist = Max( 0.0f, current.dist - splineLength ); + } + + if( !spline ) { + splineLength = 0.0f; + return; + } + + this->spline = spline; + + splineLength = spline->GetLengthForTime( spline->GetTime(spline->GetNumValues() - 1) ); + if( GetSpeed() < 0.0f ) { + current.dist = splineLength - current.dist; + } + + Activate(); +} + +/* +================ +rvPhysics_Spline::SetSplineEntity +================ +*/ +void rvPhysics_Spline::SetSplineEntity( idSplinePath* spline ) { + splineEntity = spline; + SetSpline( (spline) ? spline->GetSpline() : NULL ); +} + +/* +================ +rvPhysics_Spline::ComputeDecelFromSpline +================ +*/ +float rvPhysics_Spline::ComputeDecelFromSpline() const { + // FIXME: merge this in better. It seems very special case + float numerator = GetSpeed() * GetSpeed(); + float denomonator = 2.0f * ((GetSpeed() >= 0.0f) ? (splineLength - current.dist) : current.dist); + + assert( denomonator > VECTOR_EPSILON ); + + return numerator / denomonator; +} + +/* +================ +rvPhysics_Spline::SetLinearAcceleration +================ +*/ +void rvPhysics_Spline::SetLinearAcceleration( const float accel ) { + current.acceleration = accel; +} + +/* +================ +rvPhysics_Spline::SetLinearDeceleration +================ +*/ +void rvPhysics_Spline::SetLinearDeceleration( const float decel ) { + current.deceleration = decel; +} + +/* +================ +rvPhysics_Spline::SetSpeed +================ +*/ +void rvPhysics_Spline::SetSpeed( float speed ) { + if( IsAtRest() || StoppedMoving() ) { + current.dist = (speed < 0.0f) ? splineLength - current.dist : current.dist; + } + + current.idealSpeed = speed; + Activate(); +} + +/* +================ +rvPhysics_Spline::GetSpeed +================ +*/ +float rvPhysics_Spline::GetSpeed() const { + return current.speed; +} + +/* +================ +rvPhysics_Spline::Evaluate +================ +*/ +bool rvPhysics_Spline::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + splinePState_t previous = current; + + if( HasValidSpline() ) { + if( StoppedMoving() ) { + Rest(); + return false; + } + + accelDecelStateThread.Execute(); + + // FIXME: clean this up + if( IsAtBeginningOfSpline() || IsAtEndOfSpline() ) { + current = previous; + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + + if( gameLocal.program.GetReturnedBool() ) { + current.speed = 0.0f; + return false; + } else { + return true; + } + } + + float currentTime = splineEntity->GetSampledTime ( current.dist ); + if ( currentTime == -1.0f ) { + currentTime = spline->GetTimeForLength( Min(current.dist, splineLength), 0.01f ); + } + + current.axis = spline->GetCurrentFirstDerivative(currentTime).ToAngles().Normalize360().ToMat3(); + current.origin = spline->GetCurrentValue( currentTime ); + current.localOrigin = current.origin; + current.localAxis = current.axis; + } else if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAxis * masterAxis; + current.origin = masterOrigin + current.localOrigin * masterAxis; + } else { + Rest(); + return false; + } + + gameLocal.push.ClipPush( pushResults, self, 0, previous.origin, previous.axis, current.origin, current.axis ); + if( pushResults.fraction < 1.0f ) { + current = previous; + LinkClip(); + current.speed = 0.0f; + return false; + } + + LinkClip(); + + if( StoppedMoving() && !self->IsBound() ) { + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + return !gameLocal.program.GetReturnedBool(); + } + + return true; +} + +/* +================ +rvPhysics_Spline::Activate +================ +*/ +void rvPhysics_Spline::Activate( void ) { + assert( self ); + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::Rest +================ +*/ +void rvPhysics_Spline::Rest( void ) { + assert( self ); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtRest +================ +*/ +bool rvPhysics_Spline::IsAtRest( void ) const { + assert( self ); + return !self->IsActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtEndOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtEndOfSpline( void ) const { + return current.dist >= splineLength; +} + +/* +================ +rvPhysics_Spline::IsAtBeginningOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtBeginningOfSpline( void ) const { + return current.dist <= 0.0f; +} + +/* +================ +rvPhysics_Spline::IsPushable +================ +*/ +bool rvPhysics_Spline::IsPushable( void ) const { + return !HasValidSpline() && idPhysics_Base::IsPushable(); +} + +/* +================ +rvPhysics_Spline::StartingToMove +================ +*/ +bool rvPhysics_Spline::StartingToMove( void ) const { + float firstDeltaSpeed = current.acceleration * MS2SEC(gameLocal.GetMSec()); + return idMath::Fabs(current.idealSpeed) > VECTOR_EPSILON && idMath::Fabs(current.speed) <= firstDeltaSpeed; +} + +/* +================ +rvPhysics_Spline::StoppedMoving +================ +*/ +bool rvPhysics_Spline::StoppedMoving( void ) const { + return idMath::Fabs(current.idealSpeed) < VECTOR_EPSILON && idMath::Fabs(current.speed) < VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::HasValidSpline +================ +*/ +bool rvPhysics_Spline::HasValidSpline() const { + return spline && splineLength > VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::SaveState +================ +*/ +void rvPhysics_Spline::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Spline::RestoreState +================ +*/ +void rvPhysics_Spline::RestoreState( void ) { + current = saved; + + LinkClip(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Spline::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + + LinkClip(); + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Spline::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } + else { + current.axis = newAxis; + } + + LinkClip(); + Activate(); +} + +/* +================ +rvPhysics_Spline::Translate +================ +*/ +void rvPhysics_Spline::Translate( const idVec3 &translation, int id ) { + SetOrigin( GetLocalOrigin() + translation ); +} + +/* +================ +rvPhysics_Spline::Rotate +================ +*/ +void rvPhysics_Spline::Rotate( const idRotation &rotation, int id ) { + SetAxis( GetLocalAxis() * rotation.ToMat3() ); + SetOrigin( GetLocalOrigin() * rotation ); +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetOrigin( int id ) { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetAxis( int id ) { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) const { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) const { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::SetMaster +================ +*/ +void rvPhysics_Spline::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if( master ) { + if( self->IsBound() ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( GetOrigin() - masterOrigin ) * masterAxis.Transpose(); + current.localAxis = GetAxis() * masterAxis.Transpose(); + } + } +} + +/* +================ +rvPhysics_Spline::ClipTranslation +================ +*/ +void rvPhysics_Spline::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipRotation +================ +*/ +void rvPhysics_Spline::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipContents +================ +*/ +int rvPhysics_Spline::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.ContentsModel( self, GetOrigin(), clipModel, GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, GetOrigin(), clipModel, GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::DisableClip +================ +*/ +void rvPhysics_Spline::DisableClip( void ) { + if( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +rvPhysics_Spline::EnableClip +================ +*/ +void rvPhysics_Spline::EnableClip( void ) { + if( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +rvPhysics_Spline::UnlinkClip +================ +*/ +void rvPhysics_Spline::UnlinkClip( void ) { + if( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +rvPhysics_Spline::LinkClip +================ +*/ +void rvPhysics_Spline::LinkClip( void ) { + if( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), GetOrigin(), GetAxis() ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::GetBlockingInfo +================ +*/ +const trace_t* rvPhysics_Spline::GetBlockingInfo( void ) const { + return (pushResults.fraction < 1.0f) ? &pushResults : NULL; +} + +/* +================ +rvPhysics_Spline::GetBlockingEntity +================ +*/ +idEntity* rvPhysics_Spline::GetBlockingEntity( void ) const { + return (pushResults.fraction < 1.0f) ? gameLocal.entities[ pushResults.c.entityNum ] : NULL; +} + +/* +================ +rvPhysics_Spline::WriteToSnapshot +================ +*/ +void rvPhysics_Spline::WriteToSnapshot( idBitMsgDelta &msg ) const { + current.WriteToSnapshot( msg ); +} + +/* +================ +rvPhysics_Spline::ReadFromSnapshot +================ +*/ +void rvPhysics_Spline::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.ReadFromSnapshot( msg ); + + LinkClip(); +} + +CLASS_STATES_DECLARATION( rvPhysics_Spline ) + STATE( "Accelerating", rvPhysics_Spline::State_Accelerating ) + STATE( "Decelerating", rvPhysics_Spline::State_Decelerating ) + STATE( "Cruising", rvPhysics_Spline::State_Cruising ) +END_CLASS_STATES + +/* +================ +rvPhysics_Spline::State_Accelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Accelerating( const stateParms_t& parms ) { + stateResult_t returnResult = SRESULT_WAIT; + + if( !current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( current.ShouldDecelerate() ? "Decelerating" : "Cruising" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + if( StartingToMove() ) { + self->ProcessEvent( &EV_OnStartMoving ); + } + self->ProcessEvent( &EV_OnAcceleration ); + + returnResult = SRESULT_STAGE( parms.stage + 1 ); + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyAccelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + return returnResult; +} + +/* +================ +rvPhysics_Spline::State_Decelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Decelerating( const stateParms_t& parms ) { + if( !current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( current.ShouldAccelerate() ? "Accelerating" : "Cruising" ); + return SRESULT_DONE; + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyDecelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnDeceleration ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( StoppedMoving() ) { + self->ProcessEvent( &EV_OnStopMoving ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvPhysics_Spline::State_Cruising +================ +*/ +stateResult_t rvPhysics_Spline::State_Cruising( const stateParms_t& parms ) { + if( current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( "Accelerating" ); + return SRESULT_DONE; + } else if( current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( "Decelerating" ); + return SRESULT_DONE; + } + + current.UpdateDist( MS2SEC(gameLocal.GetMSec()) ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnCruising ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + return SRESULT_WAIT; +} + + + +const idEventDef EV_SetSpline( "setSpline", "E" ); + +const idEventDef EV_SetAccel( "setAccel", "f" ); +const idEventDef EV_SetDecel( "setDecel", "f" ); + +const idEventDef EV_SetSpeed( "setSpeed", "f" ); +const idEventDef EV_GetSpeed( "getSpeed", "", 'f' ); + +const idEventDef EV_TramCar_SetIdealSpeed( "setIdealSpeed", "f" ); +const idEventDef EV_TramCar_GetIdealSpeed( "getIdealSpeed", "", 'f' ); + +const idEventDef EV_TramCar_ApplySpeedScale( "applySpeedScale", "f" ); + +const idEventDef EV_GetCurrentTrackInfo( "getCurrentTrackInfo", "", 's' ); +const idEventDef EV_GetTrackInfo( "getTrackInfo", "e", 's' ); + +const idEventDef EV_DoneMoving( "", "", 'd' ); +const idEventDef EV_StartSoundPeriodic( "", "sddd" ); + +idLinkList rvSplineMover::splineMovers; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvSplineMover ) + EVENT( EV_PostSpawn, rvSplineMover::Event_PostSpawn ) + EVENT( EV_Activate, rvSplineMover::Event_Activate ) + EVENT( EV_SetSpline, rvSplineMover::Event_SetSpline ) + EVENT( EV_SetAccel, rvSplineMover::Event_SetAcceleration ) + EVENT( EV_SetDecel, rvSplineMover::Event_SetDeceleration ) + EVENT( EV_SetSpeed, rvSplineMover::Event_SetSpeed ) + EVENT( EV_GetSpeed, rvSplineMover::Event_GetSpeed ) + EVENT( EV_Thread_SetCallback, rvSplineMover::Event_SetCallBack ) + EVENT( EV_DoneMoving, rvSplineMover::Event_DoneMoving ) + EVENT( EV_GetSplineEntity, rvSplineMover::Event_GetSpline ) + EVENT( EV_GetCurrentTrackInfo, rvSplineMover::Event_GetCurrentTrackInfo ) + EVENT( EV_GetTrackInfo, rvSplineMover::Event_GetTrackInfo ) + EVENT( EV_TramCar_SetIdealSpeed, rvSplineMover::Event_SetIdealSpeed ) + EVENT( EV_TramCar_GetIdealSpeed, rvSplineMover::Event_GetIdealSpeed ) + EVENT( EV_TramCar_ApplySpeedScale, rvSplineMover::Event_ApplySpeedScale ) + EVENT( EV_OnAcceleration, rvSplineMover::Event_OnAcceleration ) + EVENT( EV_OnDeceleration, rvSplineMover::Event_OnDeceleration ) + EVENT( EV_OnCruising, rvSplineMover::Event_OnCruising ) + EVENT( EV_OnStartMoving, rvSplineMover::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvSplineMover::Event_OnStopMoving ) + EVENT( EV_StartSoundPeriodic, rvSplineMover::Event_StartSoundPeriodic ) + EVENT( EV_PartBlocked, rvSplineMover::Event_PartBlocked ) +END_CLASS + +/* +================ +rvSplineMover::Spawn +================ +*/ +void rvSplineMover::Spawn() { + waitThreadId = -1; + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( spawnArgs.GetBool("solid", "1") ? CONTENTS_SOLID : 0 ); + physicsObj.SetClipMask( spawnArgs.GetBool("solidClip") ? CONTENTS_SOLID : 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat("accel", "50") ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat("decel", "50") ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + AddSelfToGlobalList(); + + // This is needed so we get sorted correctly + BecomeInactive( TH_PHYSICS ); + BecomeActive( TH_PHYSICS ); + + PlayAnim( ANIMCHANNEL_ALL, "idle", 0 ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvSplineMover::~rvSplineMover +================ +*/ +rvSplineMover::~rvSplineMover() { + RemoveSelfFromGlobalList(); + SetPhysics( NULL ); +} + +/* +================ +rvSplineMover::SetSpeed +================ +*/ +void rvSplineMover::SetSpeed( float newSpeed ) { + physicsObj.SetSpeed( newSpeed ); +} + +/* +================ +rvSplineMover::GetSpeed +================ +*/ +float rvSplineMover::GetSpeed() const { + return physicsObj.GetSpeed(); +} + +/* +================ +rvSplineMover::SetIdealSpeed +================ +*/ +void rvSplineMover::SetIdealSpeed( float newIdealSpeed ) { + idealSpeed = newIdealSpeed; + SetSpeed( newIdealSpeed ); +} + +/* +================ +rvSplineMover::GetIdealSpeed +================ +*/ +float rvSplineMover::GetIdealSpeed() const { + return idealSpeed; +} + +/* +================ +rvSplineMover::SetSpline +================ +*/ +void rvSplineMover::SetSpline( idSplinePath* spline ) { + physicsObj.SetSplineEntity( spline ); + CheckSplineForOverrides( physicsObj.GetSpline(), &spline->spawnArgs ); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +const idSplinePath* rvSplineMover::GetSpline() const { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +idSplinePath* rvSplineMover::GetSpline() { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::SetAcceleration +================ +*/ +void rvSplineMover::SetAcceleration( float accel ) { + physicsObj.SetLinearAcceleration( accel ); +} + +/* +================ +rvSplineMover::SetDeceleration +================ +*/ +void rvSplineMover::SetDeceleration( float decel ) { + physicsObj.SetLinearDeceleration( decel ); +} + +/* +================ +rvSplineMover::CheckSplineForOverrides +================ +*/ +void rvSplineMover::CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ) { + if( !spline || !args ) { + return; + } + + int endSpline = args->GetInt( "end_spline" ); + if( endSpline && Sign(endSpline) == Sign(GetSpeed()) ) { + physicsObj.SetLinearDeceleration( physicsObj.ComputeDecelFromSpline() ); + SetIdealSpeed( 0.0f ); + } +} + +/* +================ +rvSplineMover::RestoreFromOverrides +================ +*/ +void rvSplineMover::RestoreFromOverrides( const idDict* args ) { + if( !args ) { + return; + } + + physicsObj.SetLinearDeceleration( args->GetFloat("decel") ); +} + +/* +================ +rvSplineMover::PlayAnim +================ +*/ +int rvSplineMover::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return 0; + } + + GetAnimator()->PlayAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim( channel )->Length(); +} + +/* +================ +rvSplineMover::CycleAnim +================ +*/ +void rvSplineMover::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return; + } + + GetAnimator()->CycleAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvSplineMover::ClearChannel +================ +*/ +void rvSplineMover::ClearChannel( int channel, int clearFrame ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(clearFrame) ); +} + +/* +================ +rvSplineMover::PreBind +================ +*/ +void rvSplineMover::PreBind() { + idAnimatedEntity::PreBind(); + + SetSpline( NULL ); +} + +/* +================ +rvSplineMover::Save +================ +*/ +void rvSplineMover::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteInt( waitThreadId ); +} + +/* +================ +rvSplineMover::Restore +================ +*/ +void rvSplineMover::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadFloat( idealSpeed ); + savefile->ReadInt( waitThreadId ); + + AddSelfToGlobalList(); +} + +/* +================ +rvSplineMover::WriteToSnapshot +================ +*/ +void rvSplineMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvSplineMover::ReadFromSnapshot +================ +*/ +void rvSplineMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvSplineMover::AddSelfToGlobalList +================ +*/ +void rvSplineMover::AddSelfToGlobalList() { + splineMoverNode.SetOwner( this ); + + if( !InGlobalList() ) { + splineMoverNode.AddToEnd( splineMovers ); + } +} + +/* +================ +rvSplineMover::RemoveSelfFromGlobalList +================ +*/ +void rvSplineMover::RemoveSelfFromGlobalList() { + splineMoverNode.Remove(); +} + +/* +================ +rvSplineMover::InGlobalList +================ +*/ +bool rvSplineMover::InGlobalList() const { + return splineMoverNode.InList(); +} + +/* +================ +rvSplineMover::WhosVisible +================ +*/ +bool rvSplineMover::WhosVisible( const idFrustum& frustum, idList& list ) const { + list.Clear(); + + if( !frustum.IsValid() ) { + return false; + } + + for( rvSplineMover* node = splineMovers.Next(); node; node = node->splineMoverNode.Next() ) { + if( node == this ) { + continue; + } + + if( frustum.IntersectsBounds(node->GetPhysics()->GetAbsBounds()) ) { + list.AddUnique( node ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvSplineMover::GetTrackInfo +================ +*/ +idStr rvSplineMover::GetTrackInfo( const idSplinePath* track ) const { + if( !track ) { + return idStr( "" ); + } + + idStr info( track->GetName() ); + return info.Mid( info.Last('_') - 1, 1 ); +} + +/* +================ +rvSplineMover::ConvertToMover +================ +*/ +rvSplineMover* rvSplineMover::ConvertToMover( idEntity* mover ) const { + return mover && mover->IsType(rvSplineMover::Type) ? static_cast(mover) : NULL; +} + +/* +================ +rvSplineMover::ConvertToSplinePath +================ +*/ +idSplinePath* rvSplineMover::ConvertToSplinePath( idEntity* spline ) const { + return (spline && spline->IsType(idSplinePath::GetClassType())) ? static_cast(spline) : NULL; +} + +/* +================ +rvSplineMover::PreDoneMoving +================ +*/ +void rvSplineMover::PreDoneMoving() { + if( waitThreadId >= 0 ) { + idThread::ObjectMoveDone( waitThreadId, this ); + waitThreadId = -1; + } + + RestoreFromOverrides( &spawnArgs ); +} + +/* +================ +rvSplineMover::PostDoneMoving +================ +*/ +void rvSplineMover::PostDoneMoving() { + CallScriptEvents( physicsObj.GetSplineEntity(), "call_doneMoving", this ); +} + +/* +============== +rvSplineMover::CallScriptEvents +============== +*/ +// FIXME: very similier code is in the spawner...if possible try and make one function for both to call +void rvSplineMover::CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ) { + if( !spline || !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spline->spawnArgs.MatchPrefix(prefixKey); kv; kv = spline->spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( spline, 0 ); + func.InsertEntity( parm, 1 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +================ +rvSplineMover::Event_PostSpawn +================ +*/ +void rvSplineMover::Event_PostSpawn() { + idEntityPtr target; + for( int ix = targets.Num() - 1; ix >= 0; --ix ) { + target = targets[ix]; + + if( target.IsValid() && target->IsType(idSplinePath::GetClassType()) ) { + SetSpline( static_cast(target.GetEntity()) ); + break; + } + } + + SetIdealSpeed( spawnArgs.GetBool("waitForTrigger") ? 0.0f : spawnArgs.GetFloat("speed", "50") ); +} + +/* +=============== +rvSplineMover::Event_PartBlocked +=============== +*/ +void rvSplineMover::Event_PartBlocked( idEntity *blockingEntity ) { + assert( blockingEntity ); + + float damage = spawnArgs.GetFloat( "damage" ); + if( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.GetTime(), GetName(), blockingEntity->GetName() ); + } +} + +/* +================ +rvSplineMover::Event_SetSpline +================ +*/ +void rvSplineMover::Event_SetSpline( idEntity* spline ) { + SetSpline( ConvertToSplinePath(spline) ); +} + +/* +================ +rvSplineMover::Event_GetSpline +================ +*/ +void rvSplineMover::Event_GetSpline() { + idThread::ReturnEntity( GetSpline() ); +} + +/* +================ +rvSplineMover::Event_SetAcceleration +================ +*/ +void rvSplineMover::Event_SetAcceleration( float accel ) { + SetAcceleration( accel ); +} + +/* +================ +rvSplineMover::Event_SetDeceleration +================ +*/ +void rvSplineMover::Event_SetDeceleration( float decel ) { + SetDeceleration( decel ); +} + +/* +================ +rvSplineMover::Event_SetSpeed +================ +*/ +void rvSplineMover::Event_SetSpeed( float speed ) { + SetIdealSpeed( speed ); + SetSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetSpeed +================ +*/ +void rvSplineMover::Event_GetSpeed() { + idThread::ReturnFloat( GetSpeed() ); +} + +/* +================ +rvSplineMover::Event_SetIdealSpeed +================ +*/ +void rvSplineMover::Event_SetIdealSpeed( float speed ) { + SetIdealSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetIdealSpeed +================ +*/ +void rvSplineMover::Event_GetIdealSpeed() { + idThread::ReturnFloat( GetIdealSpeed() ); +} + +/* +================ +rvSplineMover::Event_ApplySpeedScale +================ +*/ +void rvSplineMover::Event_ApplySpeedScale( float scale ) { + SetIdealSpeed( spawnArgs.GetFloat("speed", "50") * scale ); +} + +/* +================ +rvSplineMover::Event_SetCallBack +================ +*/ +void rvSplineMover::Event_SetCallBack() { + if( waitThreadId >= 0 ) { + idThread::ReturnInt( false ); + } + + waitThreadId = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); +} + +/* +================ +rvSplineMover::Event_DoneMoving +================ +*/ +void rvSplineMover::Event_DoneMoving() { + PreDoneMoving(); + PostDoneMoving(); + + idThread::ReturnInt( !physicsObj.HasValidSpline() ); +} + +/* +================ +rvSplineMover::Event_GetCurrentTrackInfo +================ +*/ +void rvSplineMover::Event_GetCurrentTrackInfo() { + Event_GetTrackInfo( physicsObj.GetSplineEntity() ); +} + +/* +================ +rvSplineMover::Event_GetTrackInfo +================ +*/ +void rvSplineMover::Event_GetTrackInfo( idEntity* track ) { + idThread::ReturnString( GetTrackInfo(ConvertToSplinePath(track)) ); +} + +/* +================ +rvSplineMover::Event_Activate +================ +*/ +void rvSplineMover::Event_Activate( idEntity* activator ) { + // This is for my special case in tram1b + + //if( physicsObj.StoppedMoving() ) { + // SetIdealSpeed( spawnArgs.GetFloat("speed", "50") ); + //} +} + +/* +================ +rvSplineMover::Event_OnAcceleration +================ +*/ +void rvSplineMover::Event_OnAcceleration() { + StartSound( "snd_accel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnDeceleration +================ +*/ +void rvSplineMover::Event_OnDeceleration() { + StartSound( "snd_decel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnCruising +================ +*/ +void rvSplineMover::Event_OnCruising() { + idVec2 range( spawnArgs.GetVec2("noisePeriodRange") * idMath::M_SEC2MS ); + if( !EventIsPosted(&EV_StartSoundPeriodic) && range.Length() > VECTOR_EPSILON ) { + ProcessEvent( &EV_StartSoundPeriodic, "snd_noise", (int)SND_CHANNEL_ANY, (int)range[0], (int)range[1] ); + } +} + +/* +================ +rvSplineMover::Event_OnStopMoving +================ +*/ +void rvSplineMover::Event_OnStopMoving() { + StopSound( SND_CHANNEL_ANY, false ); + CancelEvents( &EV_StartSoundPeriodic ); +} + +/* +================ +rvSplineMover::Event_OnStartMoving +================ +*/ +void rvSplineMover::Event_OnStartMoving() { +} + +/* +================ +rvSplineMover::Event_StartSoundPeriodic +================ +*/ +void rvSplineMover::Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ) { + CancelEvents( &EV_StartSoundPeriodic ); + + if( physicsObj.StoppedMoving() ) { + return; + } + + int length; + StartSound( sndKey, channel, 0, false, &length ); + + PostEventMS( &EV_StartSoundPeriodic, Max(rvRandom::irand(minDelay, maxDelay), length), sndKey, (int)channel, minDelay, maxDelay ); +} + + +const idEventDef EV_TramCar_RadiusDamage( "", "vs" ); +const idEventDef EV_TramCar_SetIdealTrack( "setIdealTrack", "s" ); +const idEventDef EV_TramCar_DriverSpeak( "driverSpeak", "s", 'e' ); +const idEventDef EV_TramCar_GetDriver( "getDriver", "", 'E' ); + +const idEventDef EV_TramCar_OpenDoors( "openDoors" ); +const idEventDef EV_TramCar_CloseDoors( "closeDoors" ); + +//======================================================= +// +// rvTramCar +// +//======================================================= +CLASS_DECLARATION( rvSplineMover, rvTramCar ) + EVENT( EV_TramCar_DriverSpeak, rvTramCar::Event_DriverSpeak ) + EVENT( EV_TramCar_GetDriver, rvTramCar::Event_GetDriver ) + EVENT( EV_Activate, rvTramCar::Event_Activate ) + EVENT( EV_TramCar_RadiusDamage, rvTramCar::Event_RadiusDamage ) + EVENT( EV_TramCar_SetIdealTrack, rvTramCar::Event_SetIdealTrack ) + EVENT( EV_OnStartMoving, rvTramCar::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvTramCar::Event_OnStopMoving ) + EVENT( EV_TramCar_OpenDoors, rvTramCar::Event_OpenDoors ) + EVENT( EV_TramCar_CloseDoors, rvTramCar::Event_CloseDoors ) + EVENT( AI_SetHealth, rvTramCar::Event_SetHealth ) +END_CLASS + +/* +================ +rvTramCar::Spawn +================ +*/ +void rvTramCar::Spawn() { + RegisterStateThread( idealTrackStateThread, "IdealTrack" ); + RegisterStateThread( speedSoundEffectsStateThread, "SpeedSoundEffects" ); + + numTracksOnMap = gameLocal.world->spawnArgs.GetInt( "numTramCarTracks", "1" ); + + idStr track; + if ( numTracksOnMap == 1 ) { + track += ConvertToTrackLetter( numTracksOnMap - 1 ); + } else { + track = spawnArgs.RandomPrefix( "idealTrack", gameLocal.random, "0" ); + } + Event_SetIdealTrack( track.c_str() ); + idealTrackStateThread.SetState( GetIdealTrack() < 0 ? "RandomTrack" : "AssignedTrack" ); + + SpawnDriver( "def_driver" ); + SpawnWeapons( "def_weapon" ); + SpawnOccupants( "def_occupant" ); + SpawnDoors(); + + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; + const char* frustumKey = spawnArgs.GetString( "collisionFov" ); + if( frustumKey[0] ) { + sscanf( frustumKey, "%f %f %f %f", &dNear, &dFar, &dLeft, &dUp ); + collisionFov.SetSize( dNear, dFar, dLeft, dUp ); + } + + fl.takedamage = health > 0; + + BecomeActive( TH_THINK ); + + SetSpline( NULL ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +rvTramCar::~rvTramCar() { + SAFE_REMOVE( driver ); + occupants.RemoveContents( true ); + weapons.RemoveContents( true ); + + SAFE_REMOVE( leftDoor ); + SAFE_REMOVE( rightDoor ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +void rvTramCar::SpawnDriver( const char* driverKey ) { + driver = (idAI*)SpawnPart( spawnArgs.GetString(driverKey), "def_occupant" ); +} + +/* +================ +rvTramCar::SpawnWeapons +================ +*/ +void rvTramCar::SpawnWeapons( const char* partKey ) { + idEntityPtr weapon; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + weapon = (rvVehicle*)SpawnPart( kv->GetValue().c_str(), partKey ); + weapon->IdleAnim( ANIMCHANNEL_LEGS, "idle", 0 ); + //if( weapon->GetAnimator()->GetAnim("toFire") && weapon->GetAnimator()->GetAnim("toIdle") ) { + weapons.AddUnique( weapon ); + //} + } +} + +/* +================ +rvTramCar::SpawnOccupants +================ +*/ +void rvTramCar::SpawnOccupants( const char* partKey ) { + idEntityPtr occupant; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + occupant = (idAI*)SpawnPart( kv->GetValue().c_str(), partKey ); + occupants.AddUnique( occupant ); + } +} + +/* +================ +rvTramCar::SpawnPart +================ +*/ +idEntity* rvTramCar::SpawnPart( const char* partDefName, const char* subPartDefName ) { + idEntity* part = NULL; + idDict entityDef; + const idDict* info = gameLocal.FindEntityDefDict( partDefName, false ); + if( !info ) { + return NULL; + } + + const idDict* def = gameLocal.FindEntityDefDict( info->GetString(subPartDefName), false ); + if( !def ) { + return NULL; + } + + entityDef.Copy( *def ); + + entityDef.SetBool( "trigger", spawnArgs.GetBool("waitForTrigger") ); + entityDef.SetVector( "origin", GetPhysics()->GetOrigin() + info->GetVector("spawn_offset") * GetPhysics()->GetAxis() ); + entityDef.SetFloat( "angle", info->GetInt("spawn_facing_offset") + spawnArgs.GetFloat("angle") ); + entityDef.Set( "bind", GetName() ); + gameLocal.SpawnEntityDef( entityDef, &part ); + + return part; +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +void rvTramCar::SpawnDoors() { + leftDoor = SpawnDoor( "clipModel_doorLeft" ); + rightDoor = SpawnDoor( "clipModel_doorRight" ); +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +idMover* rvTramCar::SpawnDoor( const char* key ) { + idDict args; + const idDict* dict = NULL; + idMover* outer = NULL; + idMover* inner = NULL; + + dict = gameLocal.FindEntityDefDict( spawnArgs.GetString(key), false ); + if( !dict ) { + return NULL; + } + + args.Set( "open_rotation", dict->GetString("open_rotation") ); + args.Set( "close_rotation", dict->GetString("close_rotation") ); + + args.Set( "open_dir", dict->GetString("open_dir") ); + args.Set( "close_dir", dict->GetString("close_dir") ); + + args.Set( "distExt", dict->GetString("distExt") ); + + args.SetVector( "origin", GetPhysics()->GetOrigin() + dict->GetVector("offset") * GetPhysics()->GetAxis() ); + args.SetMatrix( "rotation", GetPhysics()->GetAxis() ); + args.Set( "clipModel", dict->GetString("clipModel") ); + args.Set( "bind", GetName() ); + outer = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + assert( outer ); + + args.Set( "clipModel", dict->GetString("clipModelExt") ); + args.Set( "bind", outer->GetName() ); + inner = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + + return inner; +} + +/* +================ +rvTramCar::Think +================ +*/ +void rvTramCar::Think() { + RunPhysics(); + + MidThink(); + + Present(); +} + +/* +================ +rvTramCar::MidThink +================ +*/ +void rvTramCar::MidThink() { + if( !physicsObj.StoppedMoving() ) { + LookAround(); + TouchTriggers(); + speedSoundEffectsStateThread.Execute(); + } +} + +/* +================ +rvTramCar::RegisterStateThread +================ +*/ +void rvTramCar::RegisterStateThread( rvStateThread& stateThread, const char* name ) { + stateThread.SetName( name ); + stateThread.SetOwner( this ); +} + +/* +================ +rvTramCar::SetIdealTrack +================ +*/ +void rvTramCar::SetIdealTrack( int track ) { + idealTrack = track; +} + +/* +================ +rvTramCar::GetIdealTrack +================ +*/ +int rvTramCar::GetIdealTrack() const { + return idealTrack; +} + +/* +================ +rvTramCar::AddDamageEffect +================ +*/ +void rvTramCar::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + rvSplineMover::AddDamageEffect( collision, velocity, damageDefName, inflictor ); + + if( GetDamageScale() >= 0.5f && rvRandom::flrand() <= spawnArgs.GetFloat("damageEffectFreq", "0.25") ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + idVec3 v = (center - collision.endpos).ToNormal(); + float dot = v * collision.c.normal; + PlayEffect( (dot > 0.0f) ? "fx_damage_internal" : "fx_damage_external", collision.endpos, collision.c.normal.ToMat3(2) ); + } +} + +/* +================ +rvTramCar::Damage +================ +*/ +void rvTramCar::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( attacker && GetPhysics()->GetAbsBounds().Contains(attacker->GetPhysics()->GetAbsBounds()) ) { + return; + } + + if( attacker && g_debugVehicle.GetInteger() == 3 ) { + gameLocal.Printf( "Was damaged by %s. Current damage scale: %f\n", attacker->GetName(), GetDamageScale() ); + } + + rvSplineMover::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvTramCar::Killed +================ +*/ +void rvTramCar::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + //Calling gameLocal's version because we don't want to get bound and hidden + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_explode" ), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + PostEventMS( &EV_TramCar_RadiusDamage, 0, center, spawnArgs.GetString("def_damage_explode") ); + + //TODO: think about giving rigid body physics and have it fall from track + Hide(); + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetDamageScale() const { + return 1.0f - GetHealthScale(); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetHealthScale() const { + float spawnHealth = spawnArgs.GetFloat( "health" ); + return ( idMath::Fabs(spawnHealth) <= VECTOR_EPSILON ) ? 1.0f : (health / spawnHealth); +} + +/* +================ +rvTramCar::Save +================ +*/ +void rvTramCar::Save( idSaveGame *savefile ) const { + savefile->WriteInt( idealTrack ); +// savefile->WriteString( idealTrackTag.c_str() ); // cnicholson: FIXME: This has a comment of HACK in the .h file... Not sure how to write a idStr yet + + savefile->WriteFrustum( collisionFov ); + + idealTrackStateThread.Save( savefile ); + speedSoundEffectsStateThread.Save( savefile ); + + driver.Save( savefile ); + + savefile->WriteInt( occupants.Num() ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Save( savefile ); + } + + savefile->WriteInt( weapons.Num() ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Save( savefile ); + } + + savefile->WriteInt( numTracksOnMap ); + + leftDoor.Save ( savefile ); + rightDoor.Save ( savefile ); +} + +/* +================ +rvTramCar::Restore +================ +*/ +void rvTramCar::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idealTrack ); + + savefile->ReadFrustum( collisionFov ); + + idealTrackStateThread.Restore( savefile, this ); + speedSoundEffectsStateThread.Restore( savefile, this ); + + driver.Restore( savefile ); + + int num = 0; + savefile->ReadInt( num ); + occupants.SetNum( num ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Restore( savefile ); + } + + savefile->ReadInt( num ); + weapons.SetNum( num ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Restore( savefile ); + } + + savefile->ReadInt( numTracksOnMap ); + + leftDoor.Restore ( savefile ); + rightDoor.Restore ( savefile ); +} + +/* +================ +rvTramCar::WriteToSnapshot +================ +*/ +void rvTramCar::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar::ReadFromSnapshot +================ +*/ +void rvTramCar::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar::HeadTowardsIdealTrack +================ +*/ +void rvTramCar::HeadTowardsIdealTrack() { + idSplinePath* ideal = FindSplineToIdealTrack( GetSpline() ); + if( !ideal ) { + ideal = GetRandomSpline(GetSpline()); + } + + SetSpline( ideal ); +} + +/* +================ +rvTramCar::FindSplineToTrack +================ +*/ +enum { + LOOK_LEFT = -1, + LOOK_RIGHT = 1 +}; +idSplinePath* rvTramCar::FindSplineToTrack( idSplinePath* spline, const idStr& track ) const { + idSplinePath* target = NULL; + idEntity* ent = NULL; + idStr trackInfo; + idList list; + + if( !spline ) { + return NULL; + } + + for( int ix = SortSplineTargets(spline) - 1; ix >= 0; --ix ) { + ent = GetSplineTarget( spline, ix ); + target = static_cast( ent ); + assert( target->IsActive() ); + + trackInfo = GetTrackInfo( target ); + if( -1 >= trackInfo.Find(track) ) { + continue; + } + + // HACK: I hate switch statements + switch( ConvertToTrackNumber(trackInfo) - GetCurrentTrack() ) { + case LOOK_LEFT: { + if( !LookLeft(list) ) { + return target; + } + break; + } + + case LOOK_RIGHT: { + if( !LookRight(list) ) { + return target; + } + break; + } + + default: { + return target; + } + } + // HACK + } + + return NULL; +} + +/* +================ +rvTramCar::SortSplineTargets +================ +*/ +int rvTramCar::SortSplineTargets( idSplinePath* spline ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->SortTargets() : spline->SortBackwardsTargets(); +} + +/* +================ +rvTramCar::GetSplineTarget +================ +*/ +idEntity* rvTramCar::GetSplineTarget( idSplinePath* spline, int index ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->targets[index].GetEntity() : spline->backwardPathTargets[index].GetEntity(); +} + +/* +================ +rvTramCar::FindSplineToIdealTrack +================ +*/ +idSplinePath* rvTramCar::FindSplineToIdealTrack( idSplinePath* spline ) const { + // HACK + int trackDelta = idMath::ClampInt( -1, 1, GetIdealTrack() - GetCurrentTrack() ); + idStr trackLetter( ConvertToTrackLetter(GetCurrentTrack() + trackDelta) ); + idSplinePath* s = NULL; + + if( idealTrackTag.Length() && !trackDelta ) {// On ideal track + s = FindSplineToTrack( spline, idealTrackTag + trackLetter ); + } + if( !s ) { + s = FindSplineToTrack( spline, trackLetter ); + } + return s; +} + +/* +================ +rvTramCar::GetRandomSpline +================ +*/ +idSplinePath* rvTramCar::GetRandomSpline( idSplinePath* spline ) const { + if( !spline ) { + return NULL; + } + + int numActiveTargets = SortSplineTargets( spline ); + if( !numActiveTargets ) { + return NULL; + } + + idEntity* target = GetSplineTarget( spline, rvRandom::irand(0, numActiveTargets - 1) ); + return (target && target->IsType(idSplinePath::GetClassType())) ? static_cast(target) : NULL; +} + +/* +================ +rvTramCar::GetCurrentTrack +================ +*/ +int rvTramCar::GetCurrentTrack() const { + return ConvertToTrackNumber( GetTrackInfo(GetSpline()) ); +} + +/* +================ +rvTramCar::UpdateChannel +================ +*/ +void rvTramCar::UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( channel, &parms ); + } +} + +/* +================ +rvTramCar::AttenuateTrackChannel +================ +*/ +void rvTramCar::AttenuateTrackChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.frequencyShift = attenuation;//idMath::MidPointLerp( 0.0f, 1.0f, 1.1f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY, parms ); +} + +/* +================ +rvTramCar::AttenuateTramCarChannel +================ +*/ +void rvTramCar::AttenuateTramCarChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.volume = (attenuation + 1.0f) * 0.5f; + parms.frequencyShift = Max( 0.4f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY2, parms ); +} + +/* +================ +rvTramCar::LookForward +================ +*/ +bool rvTramCar::LookForward( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( GetPhysics()->GetAxis() ); + + Look( collisionFov, list ); + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( !OnSameTrackAs(list[ix]) ) { + list.Remove( list[ix] ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvTramCar::LookLeft +================ +*/ +bool rvTramCar::LookLeft( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRight +================ +*/ +bool rvTramCar::LookRight( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookLeftForTrackChange +================ +*/ +bool rvTramCar::LookLeftForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRightForTrackChange +================ +*/ +bool rvTramCar::LookRightForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::Look +================ +*/ +int rvSortByDist( const void* left, const void* right ) { + rvSplineMover* leftMover = *(rvSplineMover**)left; + rvSplineMover* rightMover = *(rvSplineMover**)right; + + return rightMover->spawnArgs.GetFloat("distAway") - leftMover->spawnArgs.GetFloat("distAway"); +} +bool rvTramCar::Look( const idFrustum& fov, idList& list ) const { + bool result = WhosVisible( fov, list ); + + if( g_debugVehicle.GetInteger() == 3 ) { + gameRenderWorld->DebugFrustum( colorRed, fov ); + } + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + list[ix]->spawnArgs.SetFloat( "distAway", (list[ix]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).Length() ); + } + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByDist ); + + // Do we need to get rid of these keyvalues? + + return result; +} + +/* +================ +rvTramCar::OnSameTrackAs +================ +*/ +bool rvTramCar::OnSameTrackAs( const rvSplineMover* tram ) const { + return tram && !GetTrackInfo(GetSpline()).Icmp( GetTrackInfo(tram->GetSpline()) ); +} + +/* +================ +rvTramCar::SameIdealTrackAs +================ +*/ +bool rvTramCar::SameIdealTrackAs( const rvSplineMover* tram ) const { + if( !tram ) { + return false; + } + return GetIdealTrack() == static_cast(tram)->GetIdealTrack(); +} + +/* +================ +rvTramCar::LookAround +================ +*/ +void rvTramCar::LookAround() { + idList moverList; + + if( !AdjustSpeed(moverList) ) { + SetSpeed( GetIdealSpeed() ); + } +} + +/* +================ +rvTramCar::AdjustSpeed +================ +*/ +bool rvTramCar::AdjustSpeed( idList& moverList ) { + if( LookForward(moverList) ) { + if( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugLine( colorRed, GetPhysics()->GetOrigin(), moverList[0]->GetPhysics()->GetOrigin() ); + } + + //Slow down and try to get onto other track if allowed + SetSpeed( moverList[0]->GetSpeed() * rvRandom::flrand(0.5f, 0.75f) ); + + // Is this safe if we are on a specified track + SetIdealTrack( (GetIdealTrack() + rvRandom::irand(1, 2)) % numTracksOnMap ); + return true; + } + + return false; +} + +/* +================ +rvTramCar::DriverSpeak +================ +*/ +idEntity* rvTramCar::DriverSpeak( const char* speechDecl, bool random ) { + if( !driver.IsValid() || driver->IsSpeaking() ) { + return NULL; + } + + driver->Speak( speechDecl, random ); + return driver; +} + +/* +================ +rvTramCar::OccupantSpeak +================ +*/ +idEntity* rvTramCar::OccupantSpeak( const char *speechDecl, bool random ) { + idEntityPtr occupant; + + if( !occupants.Num() ) { + return NULL; + } + + occupant = occupants[ rvRandom::irand(0, occupants.Num() - 1) ]; + if( !occupant.IsValid() || occupant->IsSpeaking() ) { + return NULL; + } + + occupant->Speak( speechDecl, random ); + return occupant; +} + +/* +================ +rvTramCar::PostDoneMoving +================ +*/ +void rvTramCar::PostDoneMoving() { + rvSplineMover::PostDoneMoving(); + + HeadTowardsIdealTrack(); +} + +/* +================ +rvTramCar::DeployRamp +================ +*/ +void rvTramCar::DeployRamp() { + OperateRamp( "open" ); +} + +/* +================ +rvTramCar::RetractRamp +================ +*/ +void rvTramCar::RetractRamp() { + OperateRamp( "close" ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation ) { + if( !operation || !operation[0] ) { + return; + } + + PlayAnim( ANIMCHANNEL_ALL, operation, 0 ); + + OperateRamp( operation, leftDoor ); + OperateRamp( operation, rightDoor ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation, idMover* door ) { + if( !operation || !operation[0] ) { + return; + } + + idAngles ang; + idVec3 vec; + if( !door ) { + return; + } + + if( !door->IsBound() ) { + return; + } + + ang = door->spawnArgs.GetAngles( va("%s%s", operation, "_rotation") ); + vec.Set( ang[0], ang[1], ang[2] ); + door->GetBindMaster()->ProcessEvent( &EV_RotateOnce, vec ); + + vec = door->spawnArgs.GetVector(va("%s%s", operation, "_dir")).ToNormal() * door->spawnArgs.GetFloat("distExt"); + door->PostEventSec( &EV_MoveAlongVector, 0.5f, vec ); +} + +/* +================ +rvTramCar::Event_OnStopMoving +================ +*/ +void rvTramCar::Event_OnStopMoving() { + rvSplineMover::Event_OnStopMoving(); + + speedSoundEffectsStateThread.SetState( "IdleSpeed" ); +} + +/* +================ +rvTramCar::Event_OnStartMoving +================ +*/ +void rvTramCar::Event_OnStartMoving() { + rvSplineMover::Event_OnStartMoving(); + + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); +} + +/* +================ +rvTramCar::Event_DriverSpeak +================ +*/ +void rvTramCar::Event_DriverSpeak( const char* voKey ) { + idThread::ReturnEntity( DriverSpeak(voKey) ); +} + +/* +================ +rvTramCar::Event_GetDriver +================ +*/ +void rvTramCar::Event_GetDriver() { + idThread::ReturnEntity( driver ); +} + +/* +================ +rvTramCar::Event_Activate +================ +*/ +void rvTramCar::Event_Activate( idEntity* activator ) { + rvSplineMover::Event_Activate( activator ); + + // If being activated by a spawner we need to attach to it + if( activator->IsType(rvSpawner::GetClassType()) ) { + static_cast(activator)->Attach( this ); + } else { + if( driver.IsValid() ) { + driver->ProcessEvent( &EV_Activate, activator ); + } + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->ProcessEvent( &EV_Activate, activator ); + } + } +} + +/* +================ +rvTramCar::Event_RadiusDamage +================ +*/ +void rvTramCar::Event_RadiusDamage( const idVec3& origin, const char* damageDefName ) { + gameLocal.RadiusDamage( origin, this, this, this, this, damageDefName ); +} + +/* +================ +rvTramCar::Event_SetIdealTrack +================ +*/ +void rvTramCar::Event_SetIdealTrack( const char* track ) { + idStr ideal = track; + + if( ideal.Length() > 1 ) { + idealTrackTag = ideal.Left( ideal.Length() - 1 ); + } + + idealTrackStateThread.SetState( "AssignedTrack" ); + SetIdealTrack( ConvertToTrackNumber(ideal.Right(1)) ); +} + +/* +================ +rvTramCar::Event_OpenDoors +================ +*/ +void rvTramCar::Event_OpenDoors() { + DeployRamp(); +} + +/* +================ +rvTramCar::Event_CloseDoors +================ +*/ +void rvTramCar::Event_CloseDoors() { + RetractRamp(); +} + +/* +================ +rvTramCar::Event_SetHealth +================ +*/ +void rvTramCar::Event_SetHealth ( float health ) { + this->health = health; +} + +CLASS_STATES_DECLARATION( rvTramCar ) + STATE( "IdleSpeed", rvTramCar::State_Idle ) + STATE( "NormalSpeed", rvTramCar::State_NormalSpeed ) + STATE( "ExcessiveSpeed", rvTramCar::State_ExcessiveSpeed ) + STATE( "RandomTrack", rvTramCar::State_RandomTrack ) + STATE( "AssignedTrack", rvTramCar::State_AssignedTrack ) +END_CLASS_STATES + +/* +================ +rvTramCar::State_Idle +================ +*/ +stateResult_t rvTramCar::State_Idle( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_idle_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_idle_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_NormalSpeed +================ +*/ +stateResult_t rvTramCar::State_NormalSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_normal_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_normal_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = 0.8f + 0.2f * ( GetSpeed() / GetNormalSpeed() ); + + if( speedScale >= 1.0f ) { + speedSoundEffectsStateThread.SetState( "ExcessiveSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_ExcessiveSpeed +================ +*/ +stateResult_t rvTramCar::State_ExcessiveSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_excessive_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_excessive_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = GetSpeed() / GetNormalSpeed(); + + if( speedScale < 1.0f ) { + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_RandomTrack +================ +*/ +stateResult_t rvTramCar::State_RandomTrack( const stateParms_t& parms ) { + SetIdealTrack( rvRandom::irand(0, numTracksOnMap - 1) ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_AssignedTrack +================ +*/ +stateResult_t rvTramCar::State_AssignedTrack( const stateParms_t& parms ) { + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +const idEventDef EV_TramCar_UseMountedGun( "useMountedGun", "e" ); +const idEventDef EV_TramCar_SetPlayerDamageEnt( "setPlayerDamageEnt", "f" ); + +CLASS_DECLARATION( rvTramCar, rvTramCar_Marine ) + EVENT( EV_TramCar_UseMountedGun, rvTramCar_Marine::Event_UseMountedGun ) + EVENT( EV_TramCar_SetPlayerDamageEnt, rvTramCar_Marine::Event_SetPlayerDamageEntity ) +END_CLASS + +/* +================ +rvTramCar_Marine::Spawn +================ +*/ +void rvTramCar_Marine::Spawn() { + RegisterStateThread( playerOccupationStateThread, "PlayerOccupation" ); + playerOccupationStateThread.SetState( "NotOccupied" ); + + RegisterStateThread( playerUsingMountedGunStateThread, "MountedGunInUse" ); + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); + lastHeal = 0; + healDelay = spawnArgs.GetInt( "heal_delay", "15" ); + healAmount = spawnArgs.GetInt( "heal_amount", "2" ); +} + +/* +================ +rvTramCar_Marine::MidThink +================ +*/ +void rvTramCar_Marine::MidThink() { + rvTramCar::MidThink(); + + if ( healDelay > 0 && lastHeal + healDelay < gameLocal.time && health < maxHealth ) { + health += healAmount; + + if ( health > maxHealth ) { + health = maxHealth; + } + } + + playerOccupationStateThread.Execute(); +} + +/* +================ +rvTramCar_Marine::ActivateTramHud +================ +*/ +void rvTramCar_Marine::ActivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "enterTram" ); +} + +/* +================ +rvTramCar_Marine::DeactivateTramHud +================ +*/ +void rvTramCar_Marine::DeactivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "leaveTram" ); +} + +/* +================ +rvTramCar_Marine::UpdateTramHud +================ +*/ +void rvTramCar_Marine::UpdateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->SetStateFloat( "tram_healthpct", GetHealthScale() ); +} + +/* +============ +rvTramCar_Marine::Damage +============ +*/ +void rvTramCar_Marine::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + rvTramCar::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if( attacker == gameLocal.GetLocalPlayer() ) { + return; + } + + if( fl.takedamage && GetDamageScale() < 0.35f ) { + return; + } + + if( rvRandom::flrand() > spawnArgs.GetFloat("damageWarningFreq", "0.2") ) { + return; + } + + DriverSpeak( "lipsync_damageWarning", true ); +} + +/* +================ +rvTramCar_Marine::Killed +================ +*/ +void rvTramCar_Marine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + playerOccupationStateThread.Execute(); + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && EntityIsInside(player) ) { + player->SetDamageEntity( NULL );// This should fix the damage from tram car propogation issue + player->ExitVehicle( true );// This should fix the issue that was causing the player to get removed + } + + rvTramCar::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvTramCar_Marine::Save +================ +*/ +void rvTramCar_Marine::Save( idSaveGame *savefile ) const { + savefile->WriteInt( visibleEnemies.Num() ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Save( savefile ); + } + + playerOccupationStateThread.Save( savefile ); + playerUsingMountedGunStateThread.Save( savefile ); + + // no need to save this since it's set in the restore + //savefile->WriteInt( maxHealth ); // cnicholson: added unsaved var + savefile->WriteInt( lastHeal ); + savefile->WriteInt( healDelay ); + savefile->WriteInt( healAmount ); +} + +/* +================ +rvTramCar_Marine::Restore +================ +*/ +void rvTramCar_Marine::Restore( idRestoreGame *savefile ) { + int num = 0; + savefile->ReadInt( num ); + visibleEnemies.SetNum( num ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Restore( savefile ); + } + + playerOccupationStateThread.Restore( savefile, this ); + playerUsingMountedGunStateThread.Restore( savefile, this ); + + savefile->ReadInt( lastHeal ); + savefile->ReadInt( healDelay ); + savefile->ReadInt( healAmount ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); +} + +/* +================ +rvTramCar_Marine::LookAround +================ +*/ +void rvTramCar_Marine::LookAround() { + idList moverList; + + rvTramCar::LookAround(); + + if( !driver.IsValid() || driver->IsSpeaking() ) { + return; + } + + if( LookOverLeftShoulder(moverList)) { + DriverSpeak( "lipsync_warningLeft", true ); + driver->ScriptedAnim( "point_left", 0, false, true ); + } else if( LookOverRightShoulder(moverList)) { + DriverSpeak( "lipsync_warningRight", true ); + driver->ScriptedAnim( "point_right", 0, false, true ); + } +} + + +/* +================ +rvTramCar_Marine::LookOverLeftShoulder +================ +*/ +bool rvTramCar_Marine::LookOverLeftShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, -120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::LookOverRightShoulder +================ +*/ +bool rvTramCar_Marine::LookOverRightShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, 120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::EntityIsInside +================ +*/ +bool rvTramCar_Marine::EntityIsInside( const idEntity* entity ) const { + assert( entity ); + return GetPhysics()->GetAbsBounds().ContainsPoint( entity->GetPhysics()->GetAbsBounds().GetCenter() ); +} + +/* +================ +rvTramCar_Marine::DeployRamp +================ +*/ +void rvTramCar_Marine::DeployRamp() { + rvTramCar::DeployRamp(); + + if( weapons.Num() ) { + weapons[0]->Unlock(); + weapons[0]->EjectAllDrivers( true ); + } +} + +/* +================ +rvTramCar_Marine::RetractRamp +================ +*/ +void rvTramCar_Marine::RetractRamp() { + rvTramCar::RetractRamp(); + + UseMountedGun( gameLocal.GetLocalPlayer() ); +} + +/* +================ +rvTramCar_Marine::UseMountedGun +================ +*/ +void rvTramCar_Marine::UseMountedGun( idPlayer* player ) { + if( playerOccupationStateThread.CurrentStateIs("Occupied") && EntityIsInside(player) && weapons.Num() ) { + player->EnterVehicle( weapons[0] ); + weapons[0]->Lock(); + } +} + +/* +================ +rvTramCar_Marine::Event_UseMountedGun +================ +*/ +void rvTramCar_Marine::Event_UseMountedGun( idEntity* ent ) { + if( !ent || !ent->IsType(idPlayer::GetClassType()) ) { + return; + } + + UseMountedGun( static_cast(ent) ); +} + +/* +================ +rvTramCar_Marine::Event_SetPlayerDamageEntity +================ +*/ +void rvTramCar_Marine::Event_SetPlayerDamageEntity(float f) { + + gameLocal.GetLocalPlayer()->SetDamageEntity( (f? this : NULL) ); +} + +CLASS_STATES_DECLARATION( rvTramCar_Marine ) + STATE( "Occupied", rvTramCar_Marine::State_Occupied ) + STATE( "NotOccupied", rvTramCar_Marine::State_NotOccupied ) + STATE( "UsingMountedGun", rvTramCar_Marine::State_UsingMountedGun ) + STATE( "NotUsingMountedGun",rvTramCar_Marine::State_NotUsingMountedGun ) +END_CLASS_STATES + + +/* +================ +rvTramCar_Marine::State_Occupied +================ +*/ +stateResult_t rvTramCar_Marine::State_Occupied( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_DisableWeapon ); + ActivateTramHud( player ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( !PlayerIsInside() ) { + playerOccupationStateThread.SetState( "NotOccupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( NULL ); + fl.takedamage = false; + return SRESULT_DONE_WAIT; + } + + playerUsingMountedGunStateThread.Execute(); + + UpdateTramHud( player ); + + return SRESULT_WAIT; +} + + + +/* +================ +rvTramCar_Marine::State_NotOccupied +================ +*/ +stateResult_t rvTramCar_Marine::State_NotOccupied( const stateParms_t& parms ) { + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_EnableWeapon ); + DeactivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( PlayerIsInside() ) { + playerOccupationStateThread.SetState( "Occupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( this ); + fl.takedamage = true; + return SRESULT_DONE_WAIT; + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_UsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_UsingMountedGun( const stateParms_t& parms ) { + if( !parms.stage ) { + ActivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && !player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_NotUsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_NotUsingMountedGun( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "UsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +CLASS_DECLARATION( rvTramCar, rvTramCar_Strogg ) + EVENT( EV_PostSpawn, rvTramCar_Strogg::Event_PostSpawn ) +END_CLASS + +/* +================ +rvTramCar_Strogg::Spawn +================ +*/ +void rvTramCar_Strogg::Spawn() { + SetTarget( NULL ); + + RegisterStateThread( targetSearchStateThread, "targetSearch" ); + targetSearchStateThread.SetState( "LookingForTarget" ); +} + +/* +================ +rvTramCar_Strogg::SetTarget +================ +*/ +void rvTramCar_Strogg::SetTarget( idEntity* newTarget ) { + target = ConvertToMover( newTarget ); +} + +/* +================ +rvTramCar_Strogg::GetTarget +================ +*/ +const rvSplineMover* rvTramCar_Strogg::GetTarget() const { + return target.GetEntity(); +} + +/* +================ +rvTramCar_Strogg::Save +================ +*/ +void rvTramCar_Strogg::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + targetSearchStateThread.Save( savefile ); +} + +/* +================ +rvTramCar_Strogg::Restore +================ +*/ +void rvTramCar_Strogg::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + targetSearchStateThread.Restore( savefile, this ); +} + +/* +================ +rvTramCar_Strogg::WriteToSnapshot +================ +*/ +void rvTramCar_Strogg::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar_Strogg::ReadFromSnapshot +================ +*/ +void rvTramCar_Strogg::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar_Strogg::Event_PostSpawn +================ +*/ +void rvTramCar_Strogg::Event_PostSpawn() { + idEntity* enemy = gameLocal.FindEntity( spawnArgs.GetString("enemy") ); + + SetTarget( enemy ); + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->SetEnemy( enemy ); + } + + rvTramCar::Event_PostSpawn(); +} + +/* +================ +rvTramCar_Strogg::TargetIsToLeft +================ +*/ +bool rvTramCar_Strogg::TargetIsToLeft() { + idList list; + + if( LookLeft(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::TargetIsToRight +================ +*/ +bool rvTramCar_Strogg::TargetIsToRight() { + idList list; + + if( LookRight(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::LookAround +================ +*/ +void rvTramCar_Strogg::LookAround() { + targetSearchStateThread.Execute(); +} + +CLASS_STATES_DECLARATION( rvTramCar_Strogg ) + STATE( "LookingForTarget", rvTramCar_Strogg::State_LookingForTarget ) + STATE( "TargetInSight", rvTramCar_Strogg::State_TargetInSight ) +END_CLASS_STATES + +/* +================ +rvTramCar_Strogg::State_LookingForTarget +================ +*/ +stateResult_t rvTramCar_Strogg::State_LookingForTarget( const stateParms_t& parms ) { + static const int MSEC_DELAY = 100; + idList list; + + if( !parms.stage ) {// Go back to random if we are supposed to be random + //idealTrackStateThread.SetState( "RandomTrack" ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + // Could optimise based on what track + if( TargetIsToLeft() || TargetIsToRight() ) { + targetSearchStateThread.SetState( "TargetInSight" ); + return SRESULT_DONE; + } + + SetSpeed( GetIdealSpeed() ); + return SRESULT_DELAY( MSEC_DELAY ); +} + +/* +================ +rvTramCar_Strogg::State_TargetInSight +================ +*/ +stateResult_t rvTramCar_Strogg::State_TargetInSight( const stateParms_t& parms ) { + static const int MSEC_DELAY = 3000; + + if( !target.IsValid() || (!TargetIsToLeft() && !TargetIsToRight()) ) { + targetSearchStateThread.SetState( "LookingForTarget" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + SetIdealTrack( GetCurrentTrack() ); + SetSpeed( target->GetSpeed() * 0.5f ); + idealTrackStateThread.SetState( "AssignedTrack" ); + StartSound( "snd_horn", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idList list; + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + float dot = GetPhysics()->GetAxis()[0] * (target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToNormal(); + float deltaSpeed = target->GetIdealSpeed() * rvRandom::flrand(0.0f, 0.1f) * SignZero(dot); + SetSpeed( target->GetIdealSpeed() + deltaSpeed ); + + return SRESULT_DELAY( MSEC_DELAY ); +} diff --git a/src/game/SplineMover.h b/src/game/SplineMover.h new file mode 100644 index 0000000..653e8a3 --- /dev/null +++ b/src/game/SplineMover.h @@ -0,0 +1,518 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RV_SPLINE_MOVER_H +#define __RV_SPLINE_MOVER_H + +extern const idEventDef EV_SetSpline; + +struct splinePState_t { + idVec3 origin; + idVec3 localOrigin; + idMat3 axis; + idMat3 localAxis; + + float speed; + float idealSpeed; + float dist; + + float acceleration; + float deceleration; + + bool ShouldAccelerate() const; + bool ShouldDecelerate() const; + + void ApplyAccelerationDelta( float timeStepSec ); + + void ApplyDecelerationDelta( float timeStepSec ); + + void UpdateDist( float timeStepSec ); + void Clear(); + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + splinePState_t& Assign( const splinePState_t* state ); + splinePState_t& operator=( const splinePState_t& state ); + splinePState_t& operator=( const splinePState_t* state ); +}; + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +class rvPhysics_Spline : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Spline ); + + rvPhysics_Spline( void ); + virtual ~rvPhysics_Spline( void ); + + void Save( idSaveGame *savefile ) const; + void Event_PostRestore( void ); + void Restore( idRestoreGame *savefile ); + + void SetSpline( idCurve_Spline* spline ); + const idCurve_Spline* GetSpline() const { return spline; } + idCurve_Spline* GetSpline() { return spline; } + + void SetSplineEntity( idSplinePath* spline ); + const idSplinePath* GetSplineEntity() const { return splineEntity; } + idSplinePath* GetSplineEntity() { return splineEntity; } + + void SetLinearAcceleration( const float accel ); + void SetLinearDeceleration( const float decel ); + + void SetSpeed( float speed ); + float GetSpeed( void ) const; + + virtual bool StartingToMove( void ) const; + virtual bool StoppedMoving( void ) const; + + float ComputeDecelFromSpline( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool EvaluateSpline( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + bool EvaluateMaster( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + + void Activate( void ); + void Rest( void ); + + bool IsAtRest( void ) const; + bool IsAtEndOfSpline( void ) const; + bool IsAtBeginningOfSpline( void ) const; + + virtual bool IsPushable( void ) const; + + bool HasValidSpline( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + idVec3 & GetOrigin( int id = 0 ); + idMat3 & GetAxis( int id = 0 ); + + void SetMaster( idEntity *master, const bool orientated ); + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual const trace_t* GetBlockingInfo( void ) const; + virtual idEntity* GetBlockingEntity( void ) const; + +public: + stateResult_t State_Accelerating( const stateParms_t& parms ); + stateResult_t State_Decelerating( const stateParms_t& parms ); + stateResult_t State_Cruising( const stateParms_t& parms ); + +protected: + const idVec3 & GetLocalOrigin( int id = 0 ) const; + const idMat3 & GetLocalAxis( int id = 0 ) const; + + idVec3 & GetLocalOrigin( int id = 0 ); + idMat3 & GetLocalAxis( int id = 0 ); + +protected: + splinePState_t current; + splinePState_t saved; + + float splineLength; + idCurve_Spline* spline; + idEntityPtr splineEntity; + + trace_t pushResults; + + idClipModel* clipModel; + + rvStateThread accelDecelStateThread; + + CLASS_STATES_PROTOTYPE( rvPhysics_Spline ); +}; + +extern const idEventDef EV_DoneMoving; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +class rvSplineMover : public idAnimatedEntity { + CLASS_PROTOTYPE( rvSplineMover ); + +public: + void Spawn(); + virtual ~rvSplineMover(); + + virtual void SetSpeed( float newSpeed ); + virtual float GetSpeed() const; + virtual void SetIdealSpeed( float newIdealSpeed ); + virtual float GetIdealSpeed() const; + + virtual void SetSpline( idSplinePath* spline ); + virtual const idSplinePath* GetSpline() const; + virtual idSplinePath* GetSpline(); + + virtual void SetAcceleration( float accel ); + virtual void SetDeceleration( float decel ); + + virtual void CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ); + virtual void RestoreFromOverrides( const idDict* args ); + + int PlayAnim( int channel, const char* animName, int blendFrames ); + void CycleAnim( int channel, const char* animName, int blendFrames ); + void ClearChannel( int channel, int clearFrame ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected:// TramCar utility functions + void AddSelfToGlobalList(); + void RemoveSelfFromGlobalList(); + bool InGlobalList() const; + bool WhosVisible( const idFrustum& frustum, idList& list ) const; + +protected: + virtual idStr GetTrackInfo( const idSplinePath* track ) const; + rvSplineMover* ConvertToMover( idEntity* mover ) const; + idSplinePath* ConvertToSplinePath( idEntity* spline ) const; + + void CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ); + virtual void PreBind(); + + virtual void PreDoneMoving(); + virtual void PostDoneMoving(); + +protected: + void Event_PostSpawn(); + + void Event_SetSpline( idEntity* spline ); + void Event_GetSpline(); + + void Event_SetAcceleration( float accel ); + void Event_SetDeceleration( float decel ); + + void Event_OnAcceleration(); + void Event_OnDeceleration(); + void Event_OnCruising(); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_SetSpeed( float speed ); + void Event_GetSpeed(); + void Event_SetIdealSpeed( float speed ); + void Event_GetIdealSpeed(); + void Event_ApplySpeedScale( float scale ); + + void Event_SetCallBack(); + void Event_DoneMoving(); + + void Event_GetCurrentTrackInfo(); + void Event_GetTrackInfo( idEntity* track ); + + void Event_Activate( idEntity* activator ); + + void Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ); + void Event_PartBlocked( idEntity *blockingEntity ); + +protected: + rvPhysics_Spline physicsObj; + + float idealSpeed; + + int waitThreadId; + +private: + idLinkList splineMoverNode; + static idLinkList splineMovers; +}; + +//======================================================= +// +// rvTramCar +// +//======================================================= +class rvTramCar : public rvSplineMover { + CLASS_PROTOTYPE( rvTramCar ); + +public: + void Spawn(); + virtual ~rvTramCar(); + + virtual void Think(); + virtual void MidThink(); + + virtual float GetNormalSpeed() const { return spawnArgs.GetFloat("normalSpeed"); } + + virtual void SetIdealTrack( int track ); + virtual int GetIdealTrack() const; + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual float GetDamageScale() const; + virtual float GetHealthScale() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_Idle( const stateParms_t& parms ); + stateResult_t State_NormalSpeed( const stateParms_t& parms ); + stateResult_t State_ExcessiveSpeed( const stateParms_t& parms ); + + stateResult_t State_RandomTrack( const stateParms_t& parms ); + stateResult_t State_AssignedTrack( const stateParms_t& parms ); + +protected: + void SpawnDriver( const char* driverKey ); + void SpawnWeapons( const char* partKey ); + void SpawnOccupants( const char* partKey ); + idEntity* SpawnPart( const char* partDefName, const char* subPartDefName ); + void SpawnDoors(); + idMover* SpawnDoor( const char* key ); + + int SortSplineTargets( idSplinePath* spline ) const; + idEntity* GetSplineTarget( idSplinePath* spline, int index ) const; + + void HeadTowardsIdealTrack(); + idSplinePath* FindSplineToTrack( idSplinePath* spline, const idStr& track ) const; + idSplinePath* FindSplineToIdealTrack( idSplinePath* spline ) const; + idSplinePath* GetRandomSpline( idSplinePath* spline ) const; + + char ConvertToTrackLetter( int trackNum ) const { return trackNum + 'A'; } + int ConvertToTrackNumber( char trackLetter ) const { return trackLetter - 'A'; } + int ConvertToTrackNumber( const idStr& trackInfo ) const { return ConvertToTrackNumber(idStr::ToUpper(trackInfo.Right(1)[0])); } + int GetCurrentTrack() const; + + virtual void UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ); + virtual void AttenuateTrackChannel( float attenuation ); + virtual void AttenuateTramCarChannel( float attenuation ); + + virtual void RegisterStateThread( rvStateThread& stateThread, const char* name ); + + bool LookForward( idList& list ) const; + bool LookLeft( idList& list ) const; + bool LookRight( idList& list ) const; + + bool LookLeftForTrackChange( idList& list ) const; + bool LookRightForTrackChange( idList& list ) const; + + bool Look( const idFrustum& fov, idList& list ) const; + + virtual void LookAround(); + virtual bool AdjustSpeed( idList& moverList ); + + virtual bool OnSameTrackAs( const rvSplineMover* tram ) const; + virtual bool SameIdealTrackAs( const rvSplineMover* tram ) const; + + virtual idEntity* DriverSpeak( const char *speechDecl, bool random = false ); + virtual idEntity* OccupantSpeak( const char *speechDecl, bool random = false ); + + virtual void PostDoneMoving(); + + virtual void DeployRamp(); + virtual void RetractRamp(); + void OperateRamp( const char* operation ); + void OperateRamp( const char* operation, idMover* door ); + +protected: + void Event_DriverSpeak( const char* voKey ); + void Event_GetDriver(); + void Event_Activate( idEntity* activator ); + void Event_RadiusDamage( const idVec3& origin, const char* damageDefName ); + void Event_SetIdealTrack( const char* track ); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_OpenDoors(); + void Event_CloseDoors(); + + void Event_SetHealth( float health ); + +protected: + int idealTrack; + idStr idealTrackTag;// HACK + + mutable idFrustum collisionFov; + + rvStateThread idealTrackStateThread; + rvStateThread speedSoundEffectsStateThread; + + idEntityPtr driver; + idList< idEntityPtr > occupants; + idList< idEntityPtr > weapons; + + int numTracksOnMap; + + idEntityPtr leftDoor; + idEntityPtr rightDoor; + + CLASS_STATES_PROTOTYPE( rvTramCar ); +}; + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +class rvTramCar_Marine : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Marine ); + +public: + void Spawn(); + + virtual void MidThink(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: + stateResult_t State_Occupied( const stateParms_t& parms ); + stateResult_t State_NotOccupied( const stateParms_t& parms ); + + stateResult_t State_UsingMountedGun( const stateParms_t& parms ); + stateResult_t State_NotUsingMountedGun( const stateParms_t& parms ); + +protected: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void LookAround(); + + bool LookOverLeftShoulder( idList& list ); + bool LookOverRightShoulder( idList& list ); + + bool EntityIsInside( const idEntity* entity ) const; + bool PlayerIsInside() const { return EntityIsInside(gameLocal.GetLocalPlayer()); } + + void ActivateTramHud( idPlayer* player ); + void DeactivateTramHud( idPlayer* player ); + void UpdateTramHud( idPlayer* player ); + + virtual void DeployRamp(); + virtual void RetractRamp(); + + void UseMountedGun( idPlayer* player ); + + void Event_UseMountedGun( idEntity* ent ); + void Event_SetPlayerDamageEntity(float f); + +protected: + idList< idEntityPtr > visibleEnemies; + + rvStateThread playerOccupationStateThread; + rvStateThread playerUsingMountedGunStateThread; + + int maxHealth; + int lastHeal; + int healDelay; + int healAmount; + + CLASS_STATES_PROTOTYPE( rvTramCar_Marine ); +}; + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +class rvTramCar_Strogg : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Strogg ); + +public: + void Spawn(); + + virtual void SetTarget( idEntity* newTarget ); + virtual const rvSplineMover* GetTarget() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_LookingForTarget( const stateParms_t& parms ); + stateResult_t State_TargetInSight( const stateParms_t& parms ); + +protected: + bool TargetIsToLeft(); + bool TargetIsToRight(); + + virtual void LookAround(); + +protected: + void Event_PostSpawn(); + +protected: + idEntityPtr target; + + rvStateThread targetSearchStateThread; + + CLASS_STATES_PROTOTYPE( rvTramCar_Strogg ); +}; + +#endif diff --git a/src/game/Target.cpp b/src/game/Target.cpp new file mode 100644 index 0000000..78cf35a --- /dev/null +++ b/src/game/Target.cpp @@ -0,0 +1,2345 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Invisible entities that affect other entities or the world when activated. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTarget ) +END_CLASS + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Remove ) + EVENT( EV_Activate, idTarget_Remove::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Remove::Event_Activate +================ +*/ +void idTarget_Remove::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Show ) + EVENT( EV_Activate, idTarget_Show::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Show::Event_Activate +================ +*/ +void idTarget_Show::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Show(); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Damage ) + EVENT( EV_Activate, idTarget_Damage::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Damage::Event_Activate +================ +*/ +void idTarget_Damage::Event_Activate( idEntity *activator ) { + int i; + const char *damage; + idEntity * ent; + + damage = spawnArgs.GetString( "def_damage", "damage_generic" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Damage( this, this, vec3_origin, damage, 1.0f, INVALID_JOINT ); + } + } +} + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SessionCommand ) + EVENT( EV_Activate, idTarget_SessionCommand::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SessionCommand::Event_Activate +================ +*/ +void idTarget_SessionCommand::Event_Activate( idEntity *activator ) { + gameLocal.sessionCommand = spawnArgs.GetString( "command" ); +} + + +/* +=============================================================================== + +idTarget_EndLevel + +Just a modified form of idTarget_SessionCommand +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EndLevel ) + EVENT( EV_Activate, idTarget_EndLevel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EndLevel::Event_Activate +================ +*/ +void idTarget_EndLevel::Event_Activate( idEntity *activator ) { + idStr nextMap; + +#ifdef ID_DEMO_BUILD + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endofDemo"; + return; + } +#else + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endOfGame"; + return; + } +#endif + if ( !spawnArgs.GetString( "nextMap", "", nextMap ) ) { + gameLocal.Printf( "idTarget_SessionCommand::Event_Activate: no nextMap key\n" ); + return; + } + + if ( spawnArgs.GetInt( "devmap", "0" ) ) { + gameLocal.sessionCommand = "devmap "; // only for special demos + } else { + gameLocal.sessionCommand = "map "; + } + + gameLocal.sessionCommand += nextMap; + +// RAVEN BEGIN +// jscott: additional info for multiple maps + const char* entityFilter; + if( spawnArgs.GetString( "entityFilter", "", &entityFilter ) && *entityFilter ) { + gameLocal.sessionCommand += " "; + gameLocal.sessionCommand += entityFilter; + } +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_WaitForButton ) + EVENT( EV_Activate, idTarget_WaitForButton::Event_Activate ) +END_CLASS + +/* +================ +idTarget_WaitForButton::Event_Activate +================ +*/ +void idTarget_WaitForButton::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTarget_WaitForButton::Think +================ +*/ +void idTarget_WaitForButton::Think( void ) { + idPlayer *player; + + if ( thinkFlags & TH_THINK ) { + player = gameLocal.GetLocalPlayer(); + if ( player && ( !player->oldButtons & BUTTON_ATTACK ) && ( player->usercmd.buttons & BUTTON_ATTACK ) ) { + player->usercmd.buttons &= ~BUTTON_ATTACK; + BecomeInactive( TH_THINK ); + ActivateTargets( player ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetGlobalShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetGlobalShaderTime ) +EVENT( EV_Activate, idTarget_SetGlobalShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetGlobalShaderTime::Event_Activate +================ +*/ +void idTarget_SetGlobalShaderTime::Event_Activate( idEntity *activator ) { + int parm = spawnArgs.GetInt( "globalParm" ); + float time = -MS2SEC( gameLocal.time ); + if ( parm >= 0 && parm < MAX_GLOBAL_SHADER_PARMS ) { + gameLocal.globalShaderParms[parm] = time; + } +} + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderParm ) + EVENT( EV_Activate, idTarget_SetShaderParm::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderParm::Event_Activate +================ +*/ +void idTarget_SetShaderParm::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float value; + idVec3 color; + int parmnum; + + // set the color on the targets + if ( spawnArgs.GetVector( "_color", "1 1 1", color ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + } + } + } + + // set any shader parms on the targets + for( parmnum = 0; parmnum < MAX_ENTITY_SHADER_PARMS; parmnum++ ) { + if ( spawnArgs.GetFloat( va( "shaderParm%d", parmnum ), "0", value ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( parmnum, value ); + } + } + if (spawnArgs.GetBool("toggle") && (value == 0 || value == 1)) { + int val = value; + val ^= 1; + value = val; + spawnArgs.SetFloat(va("shaderParm%d", parmnum), value); + } + } + } +} + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderTime ) + EVENT( EV_Activate, idTarget_SetShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderTime::Event_Activate +================ +*/ +void idTarget_SetShaderTime::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float time; + + time = -MS2SEC( gameLocal.time ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( SHADERPARM_TIMEOFFSET, time ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SetLightParm( SHADERPARM_TIMEOFFSET, time ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_FadeEntity ) + EVENT( EV_Activate, idTarget_FadeEntity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_FadeEntity::idTarget_FadeEntity +================ +*/ +idTarget_FadeEntity::idTarget_FadeEntity( void ) { + fadeFrom.Zero(); + fadeStart = 0; + fadeEnd = 0; +} + +/* +================ +idTarget_FadeEntity::Save +================ +*/ +void idTarget_FadeEntity::Save( idSaveGame *savefile ) const { + savefile->WriteVec4( fadeFrom ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Restore +================ +*/ +void idTarget_FadeEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadVec4( fadeFrom ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Event_Activate +================ +*/ +void idTarget_FadeEntity::Event_Activate( idEntity *activator ) { + idEntity *ent; + int i; + + if ( !targets.Num() ) { + return; + } + + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->GetColor( fadeFrom ); + break; + } + } + + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "fadetime" ) ); +} + +/* +================ +idTarget_FadeEntity::Think +================ +*/ +void idTarget_FadeEntity::Think( void ) { + int i; + idEntity *ent; + idVec4 color; + idVec4 fadeTo; + float frac; + + if ( thinkFlags & TH_THINK ) { + GetColor( fadeTo ); + if ( gameLocal.time >= fadeEnd ) { + color = fadeTo; + BecomeInactive( TH_THINK ); + } else { + frac = ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ); + color.Lerp( fadeFrom, fadeTo, frac ); + } + + // set the color on the targets + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeIn ) + EVENT( EV_Activate, idTarget_LightFadeIn::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeIn::Event_Activate +================ +*/ +void idTarget_LightFadeIn::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeIn( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeOut ) + EVENT( EV_Activate, idTarget_LightFadeOut::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeOut::Event_Activate +================ +*/ +void idTarget_LightFadeOut::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeOut( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Give ) + EVENT( EV_Activate, idTarget_Give::Event_Activate ) +// RAVEN BEGIN +// abahr: + EVENT( EV_PostSpawn, idTarget_Give::Event_PostSpawn ) +// RAVEN END +END_CLASS + +/* +================ +idTarget_Give::Spawn +================ +*/ +void idTarget_Give::Spawn( void ) { +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + if ( spawnArgs.GetBool( "onSpawn" ) ) { + PostEventMS( &EV_PostSpawn, 50 ); + +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +#endif + } +// RAVEN END + + // precache decls + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + declManager->FindType( DECL_ENTITYDEF, kv->GetValue(), false, false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +} + +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs +/* +================ +idTarget_Give::Event_PostSpawn +================ +*/ +void idTarget_Give::Event_PostSpawn() { + ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); +} +// RAVEN END + +/* +================ +idTarget_Give::Event_Activate +================ +*/ +void idTarget_Give::Event_Activate( idEntity *activator ) { + + if ( spawnArgs.GetBool( "development" ) && developer.GetInteger() == 0 ) { + return; + } + + static int giveNum = 0; + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( dict ) { + idDict d2; + d2.Copy( *dict ); + d2.Set( "name", va( "givenitem_%i", giveNum++ ) ); + idEntity *ent = NULL; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.SpawnEntityDef( d2, &ent ) && ent && ent->IsType( idItem::GetClassType() ) ) { +// RAVEN END + idItem *item = static_cast(ent); + item->GiveToPlayer( gameLocal.GetLocalPlayer() ); + item->PostEventMS ( &EV_Remove, 0 ); + + // rules are that if we are given a weapon by a character, we are supposed to switch to it regardless of + // whether auto-switch is on or not. + if ( !gameLocal.isMultiplayer && !player->GetUserInfo()->GetBool( "ui_autoSwitch" ) && !spawnArgs.GetBool( "onSpawn" )) { + const idKeyValue *kv = ent->spawnArgs.FindKey( "weaponclass" ); + if ( kv ) { + // does player already have this weapon selected? + if ( player->weapon && idStr::Icmp(player->weapon->GetClassname(), kv->GetValue())) { + kv = ent->spawnArgs.FindKey( "inv_weapon" ); + if ( kv ) { + // nope, so attempt to switch to this weapon + player->SelectWeapon(kv->GetValue()); + } + } + } + } + } + } + kv = spawnArgs.MatchPrefix( "item", kv ); + } + } +} + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveEmail ) +EVENT( EV_Activate, idTarget_GiveEmail::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Spawn +================ +*/ +void idTarget_GiveEmail::Spawn( void ) { +} + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveEmail::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using email +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + const idDeclPDA *pda = player->GetPDA(); + if ( pda ) { + player->GiveEmail( spawnArgs.GetString( "email" ) ); + } else { + player->ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_PDANeeded" ), true ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetModel ) + EVENT( EV_Activate, idTarget_SetModel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetModel::Spawn +================ +*/ +void idTarget_SetModel::Spawn( void ) { + const char *model; + + model = spawnArgs.GetString( "newmodel" ); + if ( declManager->FindType( DECL_MODELDEF, model, false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( model ); + // precache .cm files only + collisionModelManager->PreCacheModel( gameLocal.GetMapName(), model ); + } +} + +/* +================ +idTarget_SetModel::Event_Activate +================ +*/ +void idTarget_SetModel::Event_Activate( idEntity *activator ) { + for( int i = 0; i < targets.Num(); i++ ) { + idEntity *ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetModel( spawnArgs.GetString( "newmodel" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +const idEventDef EV_RestoreInfluence( "" ); +const idEventDef EV_GatherEntities( "" ); +const idEventDef EV_Flash( "", "fd" ); +const idEventDef EV_ClearFlash( "", "f" ); + +CLASS_DECLARATION( idTarget, idTarget_SetInfluence ) + EVENT( EV_Activate, idTarget_SetInfluence::Event_Activate ) + EVENT( EV_RestoreInfluence, idTarget_SetInfluence::Event_RestoreInfluence ) + EVENT( EV_GatherEntities, idTarget_SetInfluence::Event_GatherEntities ) + EVENT( EV_Flash, idTarget_SetInfluence::Event_Flash ) + EVENT( EV_ClearFlash, idTarget_SetInfluence::Event_ClearFlash ) +END_CLASS + +/* +================ +idTarget_SetInfluence::idTarget_SetInfluence +================ +*/ +idTarget_SetInfluence::idTarget_SetInfluence( void ) { + flashIn = 0.0f; + flashOut = 0.0f; + delay = 0.0f; + switchToCamera = NULL; + soundFaded = false; + restoreOnTrigger = false; +} + +/* +================ +idTarget_SetInfluence::Save +================ +*/ +void idTarget_SetInfluence::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( lightList.Num() ); + for( i = 0; i < lightList.Num(); i++ ) { + savefile->WriteInt( lightList[ i ] ); + } + + savefile->WriteInt( guiList.Num() ); + for( i = 0; i < guiList.Num(); i++ ) { + savefile->WriteInt( guiList[ i ] ); + } + + savefile->WriteInt( soundList.Num() ); + for( i = 0; i < soundList.Num(); i++ ) { + savefile->WriteInt( soundList[ i ] ); + } + + savefile->WriteInt( genericList.Num() ); + for( i = 0; i < genericList.Num(); i++ ) { + savefile->WriteInt( genericList[ i ] ); + } + + savefile->WriteFloat( flashIn ); + savefile->WriteFloat( flashOut ); + + savefile->WriteFloat( delay ); + + savefile->WriteString( flashInSound ); + savefile->WriteString( flashOutSound ); + + savefile->WriteObject( switchToCamera ); + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); + + savefile->WriteBool( soundFaded ); + savefile->WriteBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Restore +================ +*/ +void idTarget_SetInfluence::Restore( idRestoreGame *savefile ) { + int i, num; + int itemNum; + float set; + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + lightList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + guiList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + soundList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + genericList.Append( itemNum ); + } + + savefile->ReadFloat( flashIn ); + savefile->ReadFloat( flashOut ); + + savefile->ReadFloat( delay ); + + savefile->ReadString( flashInSound ); + savefile->ReadString( flashOutSound ); + + savefile->ReadObject( reinterpret_cast( switchToCamera ) ); + + savefile->ReadFloat( set ); + fovSetting.SetStartTime( set ); + savefile->ReadFloat( set ); + fovSetting.SetDuration( set ); + savefile->ReadFloat( set ); + fovSetting.SetStartValue( set ); + savefile->ReadFloat( set ); + fovSetting.SetEndValue( set ); + + savefile->ReadBool( soundFaded ); + savefile->ReadBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Spawn +================ +*/ +void idTarget_SetInfluence::Spawn() { + PostEventMS( &EV_GatherEntities, 0 ); + flashIn = spawnArgs.GetFloat( "flashIn", "0" ); + flashOut = spawnArgs.GetFloat( "flashOut", "0" ); + flashInSound = spawnArgs.GetString( "snd_flashin" ); + flashOutSound = spawnArgs.GetString( "snd_flashout" ); + delay = spawnArgs.GetFloat( "delay" ); + soundFaded = false; + restoreOnTrigger = false; + + // always allow during cinematics + cinematic = true; +} + +/* +================ +idTarget_SetInfluence::Event_Flash +================ +*/ +void idTarget_SetInfluence::Event_Flash( float flash, int out ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( idVec4( 1, 1, 1, 1 ), flash ); + const idSoundShader *shader = NULL; + if ( !out && flashInSound.Length() ){ + shader = declManager->FindSound( flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( out && ( flashOutSound.Length() || flashInSound.Length() ) ) { + shader = declManager->FindSound( flashOutSound.Length() ? flashOutSound : flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } + PostEventSec( &EV_ClearFlash, flash, flash ); +} + + +/* +================ +idTarget_SetInfluence::Event_ClearFlash +================ +*/ +void idTarget_SetInfluence::Event_ClearFlash( float flash ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( vec4_zero , flash ); +} +/* +================ +idTarget_SetInfluence::Event_GatherEntities +================ +*/ +void idTarget_SetInfluence::Event_GatherEntities() { + int i, listedEntities; + idEntity *entityList[ MAX_GENTITIES ]; + + bool lights = spawnArgs.GetBool( "effect_lights" ); + bool sounds = spawnArgs.GetBool( "effect_sounds" ); + bool guis = spawnArgs.GetBool( "effect_guis" ); + bool models = spawnArgs.GetBool( "effect_models" ); + bool vision = spawnArgs.GetBool( "effect_vision" ); + bool targetsOnly = spawnArgs.GetBool( "targetsOnly" ); + + lightList.Clear(); + guiList.Clear(); + soundList.Clear(); + + if ( spawnArgs.GetBool( "effect_all" ) ) { + lights = sounds = guis = models = vision = true; + } + + if ( targetsOnly ) { + listedEntities = targets.Num(); + for ( i = 0; i < listedEntities; i++ ) { + entityList[i] = targets[i].GetEntity(); + } + } else { + float radius = spawnArgs.GetFloat( "radius" ); + listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES ); + } + + for( i = 0; i < listedEntities; i++ ) { + idEntity *ent = entityList[ i ]; + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( lights && ent->IsType( idLight::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + lightList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( sounds && ent->IsType( idSound::GetClassType() ) && ent->spawnArgs.FindKey( "snd_demonic" ) ) { +// RAVEN END + soundList.Append( ent->entityNumber ); + continue; + } + if ( guis && ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ 0 ] && ent->spawnArgs.FindKey( "gui_demonic" ) ) { + guiList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idStaticEntity::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + genericList.Append( ent->entityNumber ); + continue; + } + } + } + idStr temp; + temp = spawnArgs.GetString( "switchToView" ); + switchToCamera = ( temp.Length() ) ? gameLocal.FindEntity( temp ) : NULL; + +} + +/* +================ +idTarget_SetInfluence::Event_Activate +================ +*/ +void idTarget_SetInfluence::Event_Activate( idEntity *activator ) { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + const char *parm; + const char *skin; + bool update; + idVec3 color; + idVec4 colorTo; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + + if ( spawnArgs.GetBool( "triggerActivate" ) ) { + if ( restoreOnTrigger ) { + ProcessEvent( &EV_RestoreInfluence ); + restoreOnTrigger = false; + return; + } + restoreOnTrigger = true; + } + + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + + if ( delay > 0.0f ) { + PostEventSec( &EV_Activate, delay, activator ); + delay = 0.0f; + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + return; + } else if ( fadeTime && !soundFaded ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( activator ); + } + + if ( flashIn ) { + PostEventSec( &EV_Flash, 0.0f, flashIn, 0 ); + } + + parm = spawnArgs.GetString( "snd_influence" ); + if ( parm && *parm ) { + PostEventSec( &EV_StartSoundShader, flashIn, parm, SND_CHANNEL_ANY ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventSec( &EV_Activate, flashIn + 0.05f, this ); + } + +// RAVEN BEGIN +// mekberg: allow for initial fov and both fovs. + int fov = spawnArgs.GetInt( "fov" ); + int fovInitial = spawnArgs.GetInt( "fov_initial" ); + if ( fov && fovInitial) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, fov ); + BecomeActive( TH_THINK ); + } else if ( fov ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), player->DefaultFov(), fov ); + BecomeActive( TH_THINK ); + } else if ( fovInitial ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, player->DefaultFov() ); + BecomeActive( TH_THINK ); + } +// RAVEN END + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + color = generic->spawnArgs.GetVector( "color_demonic" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + parm = light->spawnArgs.GetString( "mat_demonic" ); + if ( parm && *parm ) { + light->SetShader( parm ); + } + + color = light->spawnArgs.GetVector( "_color" ); + color = light->spawnArgs.GetVector( "color_demonic", color.ToString() ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + parm = sound->spawnArgs.GetString( "snd_demonic" ); + if ( parm && *parm ) { + if ( sound->spawnArgs.GetBool( "overlayDemonic" ) ) { + sound->StartSound( "snd_demonic", SND_CHANNEL_DEMONIC, 0, false, NULL ); + } else { + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( parm ); + } + } + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || ent->GetRenderEntity() == NULL ) { + continue; + } + update = false; + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] && ent->spawnArgs.FindKey( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ) ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ), true ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + + } + + player->SetInfluenceLevel( spawnArgs.GetInt( "influenceLevel" ) ); + + int snapAngle = spawnArgs.GetInt( "snapAngle" ); + if ( snapAngle ) { + idAngles ang( 0, snapAngle, 0 ); + player->SetViewAngles( ang ); + player->SetAngles( ang ); + } + + if ( spawnArgs.GetBool( "effect_vision" ) ) { + parm = spawnArgs.GetString( "mtrVision" ); + skin = spawnArgs.GetString( "skinVision" ); + player->SetInfluenceView( parm, skin, spawnArgs.GetInt( "visionRadius" ), this ); + } + + parm = spawnArgs.GetString( "mtrWorld" ); + if ( parm && *parm ) { + gameLocal.SetGlobalMaterial( declManager->FindMaterial( parm ) ); + } + + if ( !restoreOnTrigger ) { + PostEventMS( &EV_RestoreInfluence, SEC2MS( spawnArgs.GetFloat( "time" ) ) ); + } +} + +/* +================ +idTarget_SetInfluence::Think +================ +*/ +void idTarget_SetInfluence::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + if ( !spawnArgs.GetBool( "leaveFOV" ) ) { + player->SetInfluenceFov( 0 ); + } + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +================ +idTarget_SetInfluence::Event_RestoreInfluence +================ +*/ +void idTarget_SetInfluence::Event_RestoreInfluence() { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + bool update; + idVec3 color; + idVec4 colorTo; + + if ( flashOut ) { + PostEventSec( &EV_Flash, 0.0f, flashOut, 1 ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventMS( &EV_Activate, 0.0f, this ); + } + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + colorTo.Set( 1.0f, 1.0f, 1.0f, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + if ( !light->spawnArgs.GetBool( "leave_demonic_mat" ) ) { + const char *texture = light->spawnArgs.GetString( "texture", "lights/squarelight1" ); + light->SetShader( texture ); + } + color = light->spawnArgs.GetVector( "_color" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( sound->spawnArgs.GetString( "s_shader" ) ); + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || GetRenderEntity() == NULL ) { + continue; + } + update = false; + for( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui" : va( "gui%d", j+1 ) ) ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceLevel( 0 ); + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + player->SetInfluenceFov( 0 ); + gameLocal.SetGlobalMaterial( NULL ); + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime / 2.0f ); + } + +} + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetKeyVal ) + EVENT( EV_Activate, idTarget_SetKeyVal::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetKeyVal::Event_Activate +================ +*/ +void idTarget_SetKeyVal::Event_Activate( idEntity *activator ) { + int i; + idStr key, val; + idEntity *ent; + const idKeyValue *kv; + int n; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + kv = spawnArgs.MatchPrefix("keyval"); + while ( kv ) { + n = kv->GetValue().Find( ";" ); + if ( n > 0 ) { + key = kv->GetValue().Left( n ); + val = kv->GetValue().Right( kv->GetValue().Length() - n - 1 ); + ent->spawnArgs.Set( key, val ); + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) == 0 ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time ); + } + } + } + } + kv = spawnArgs.MatchPrefix( "keyval", kv ); + } + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetFov ) + EVENT( EV_Activate, idTarget_SetFov::Event_Activate ) +END_CLASS + + +/* +================ +idTarget_SetFov::Save +================ +*/ +void idTarget_SetFov::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); +} + +/* +================ +idTarget_SetFov::Restore +================ +*/ +void idTarget_SetFov::Restore( idRestoreGame *savefile ) { + float setting; + + savefile->ReadFloat( setting ); + fovSetting.SetStartTime( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetDuration( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetStartValue( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetEndValue( setting ); + + fovSetting.GetCurrentValue( gameLocal.time ); +} + +/* +================ +idTarget_SetFov::Event_Activate +================ +*/ +void idTarget_SetFov::Event_Activate( idEntity *activator ) { + // always allow during cinematics + cinematic = true; + + idPlayer *player = gameLocal.GetLocalPlayer(); + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "time" ) ), player ? player->DefaultFov() : g_fov.GetFloat(), spawnArgs.GetFloat( "fov" ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTarget_SetFov::Think +================ +*/ +void idTarget_SetFov::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + player->SetInfluenceFov( 0.0f ); + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetPrimaryObjective ) + EVENT( EV_Activate, idTarget_SetPrimaryObjective::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetPrimaryObjective::Event_Activate +================ +*/ +void idTarget_SetPrimaryObjective::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->objectiveSystem ) { + player->objectiveSystem->SetStateString( "missionobjective", spawnArgs.GetString( "text", common->GetLocalizedString( "#str_104253" ) ) ); + } +} + +// RAVEN BEGIN +// bdube: added database entry trigger +// twhitaker: removed database entry trigger +/* +=============================================================================== + +rvTarget_AddDatabaseEntry + +=============================================================================== +*/ +/* +CLASS_DECLARATION( idTarget, rvTarget_AddDatabaseEntry ) + EVENT( EV_Activate, rvTarget_AddDatabaseEntry::Event_Activate ) +END_CLASS +*/ +/* +================ +rvTarget_AddDatabaseEntry::Event_Activate +================ +*/ +/*void rvTarget_AddDatabaseEntry::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveDatabaseEntry ( gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_db", "" ) ) ); + } +} +*/ +// jshepard: secret area trigger +/* +=============================================================================== + +rvTarget_SecretArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_SecretArea ) + EVENT( EV_Activate, rvTarget_SecretArea::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_SecretArea::Event_Activate +================ +*/ +void rvTarget_SecretArea::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->DiscoverSecretArea( spawnArgs.GetString ( "description" )); + } +} + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +const idEventDef EV_SetShieldPercent( "setShieldPercent", "f" ); +const idEventDef EV_SetBossMaxHealth( "setMaxBossHealth", "f" ); +const idEventDef EV_AllowShieldBar( "allowShieldBar", "f" ); +const idEventDef EV_AllowShieldWarningBar( "allowShieldWarnBar", "f" ); + + +CLASS_DECLARATION( idTarget, rvTarget_BossBattle ) + EVENT( EV_Activate, rvTarget_BossBattle::Event_Activate ) + EVENT( EV_SetShieldPercent, rvTarget_BossBattle::Event_SetShieldPercent ) + EVENT( EV_SetBossMaxHealth, rvTarget_BossBattle::Event_SetBossMaxHealth ) + EVENT( EV_AllowShieldBar, rvTarget_BossBattle::Event_AllowShieldBar ) + EVENT( EV_AllowShieldWarningBar, rvTarget_BossBattle::Event_AllowShieldWarningBar ) +END_CLASS + +/* +================ +rvTarget_BossBattle::Event_Activate +================ +*/ +void rvTarget_BossBattle::Event_Activate( idEntity *activator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + idEntity* enemy = gameLocal.FindEntity ( spawnArgs.GetString ( "target" ) ); + if ( player && enemy ) { + player->StartBossBattle ( enemy ); + } + + StartSound ( "snd_activate", SND_CHANNEL_ANY, 0, false, NULL ); +} + + +/* +================ +rvTarget_BossBattle::Event_AllowShieldBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldBar" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldBar" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_AllowShieldWarningBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldWarningBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldWarn" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldWarn" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_SetShieldPercent +================ +*/ +void rvTarget_BossBattle::Event_SetShieldPercent( float percent ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } +} + +/* +================ +rvTarget_BossBattle::Event_SetMaxBossHealth +================ +*/ +void rvTarget_BossBattle::Event_SetBossMaxHealth( float f ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + + hud->SetStateInt ( "boss_maxhealth",f ); + } +} + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +const idEventDef EV_LaunchProjectile( "launchProjectile", "e" ); +CLASS_DECLARATION( idTarget, rvTarget_LaunchProjectile ) + EVENT( EV_Activate, rvTarget_LaunchProjectile::Event_Activate ) + EVENT( EV_LaunchProjectile, rvTarget_LaunchProjectile::Event_LaunchProjectile ) +END_CLASS + +/* +================ +rvTarget_LaunchProjectile::Spawn +================ +*/ +void rvTarget_LaunchProjectile::Spawn( void ) { + if ( spawnArgs.GetBool( "start_on" ) ) { + PostEventMS( &EV_Activate, 0, this ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_LaunchProjectile +================ +*/ +void rvTarget_LaunchProjectile::Event_LaunchProjectile( idEntity *activator ) { + const idDict* projectileDict; + idEntity* ent; + + projectileDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ), false ); + if ( !projectileDict ) { + gameLocal.Error ( "Could not find entityDef '%s' for launch projectile target '%s'", spawnArgs.GetString ( "def_projectile" ), GetName() ); + } + + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + gameLocal.Error( "Could not spawn entityDef '%s'", projectileDict->GetString( "classname" ) ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idProjectile", ent->GetClassname() ); + } + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + idProjectile* proj = static_cast(ent); + + proj->Create( this, GetPhysics()->GetOrigin(), dir, this ); + proj->Launch( GetPhysics()->GetOrigin(), dir, GetPhysics()->GetLinearVelocity(), 0.0f, 1.0f ); + + + if ( targets.Num() && proj->IsType ( idGuidedProjectile::GetClassType ( ) ) ) { + static_cast(proj)->GuideTo ( targets[0] ); + } + + if ( spawnArgs.GetFloat( "loop_interval" ) ) + { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "loop_interval" ), activator ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_Activate +================ +*/ +void rvTarget_LaunchProjectile::Event_Activate( idEntity *activator ) { + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + if ( spawnArgs.GetString( "fx_launch", NULL ) ) + { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_launch" ), GetPhysics()->GetOrigin(), dir.ToMat3() ); + } + + if ( spawnArgs.GetFloat( "delay" ) ) + { + PostEventSec( &EV_LaunchProjectile, spawnArgs.GetFloat( "delay" ), activator ); + return; + } + ProcessEvent( &EV_LaunchProjectile, activator ); +} + +/* +=============================================================================== + +rvTarget_ExitArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_ExitAreaAlert ) + EVENT( EV_Activate, rvTarget_ExitAreaAlert::Event_Activate ) +END_CLASS + +void rvTarget_ExitAreaAlert::Event_Activate( idEntity *activator ) { + gameLocal.UpdateEndLevel(); +} + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ + +int CompareAmmoData( const void* ammo1, const void* ammo2) { + if ((( ammodata_t *)ammo2)->percentFull > (( ammodata_t *)ammo1)->percentFull ) { + return -1; + } else { + return 1; + } +} + +CLASS_DECLARATION( idTarget, rvTarget_AmmoStash ) + EVENT( EV_Activate, rvTarget_AmmoStash::Event_Activate ) +END_CLASS + +void rvTarget_AmmoStash::Event_Activate( idEntity *activator ) { + + const idKeyValue* kv; + idPlayer* player; + int typeCount; + idEntity* entAmmo; + idItem* item; + idDict args; + + + player = gameLocal.GetLocalPlayer(); + + //set up the array + memset( AmmoArray, 0, sizeof( ammodata_t) * AMMO_ARRAY_SIZE); + for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + AmmoArray[ t ].percentFull = 2.0f; + } + + // we only check for certain types of ammo. + kv = spawnArgs.MatchPrefix ( "def_ammo", NULL ); + if ( kv ) { + kv->GetValue(); + for ( typeCount = 0; typeCount < AMMO_ARRAY_SIZE && kv; kv = spawnArgs.MatchPrefix ( "def_ammo", kv ) ) { + + AmmoArray[ typeCount ].ammoName = kv->GetValue(); + AmmoArray[ typeCount ].ammoIndex = player->inventory.AmmoIndexForAmmoClass( AmmoArray[ typeCount ].ammoName.c_str() ); + + //check and see how much ammo this weapon can hold... + AmmoArray[ typeCount ].ammoMax = player->inventory.MaxAmmoForAmmoClass( player, AmmoArray[ typeCount ].ammoName.c_str() ); + + //and the current amount + AmmoArray[ typeCount ].ammoCount = player->inventory.HasAmmo( AmmoArray[ typeCount ].ammoIndex, 1); + + if( AmmoArray[ typeCount ].ammoMax > 0) { + AmmoArray[ typeCount ].percentFull = float( AmmoArray[ typeCount ].ammoCount / AmmoArray[ typeCount ].ammoMax ); + } else { + AmmoArray[ typeCount ].percentFull = 2.0f; + } + + //increment + typeCount++; + } + + } else { + gameLocal.Warning("Bad ammo data on rvTarget_AmmoStash '%s'", this->GetName()); + return; + } + + //TEMP: print out the ammo counts + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + //sort the types of ammo by need. The most-needed ammo will be placed. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + int i; + //run through our targets until we have no more targets. + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + //drop the most needed ammo at ent's location. + args.Set ( "origin", ent->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", ent->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", AmmoArray[ 0 ].ammoName ); + + gameLocal.SpawnEntityDef ( args, &entAmmo ); + + //now assume that the item was picked up. + item = static_cast< idItem* >(entAmmo); + kv = entAmmo->spawnArgs.MatchPrefix ( "inv_ammo", NULL ); + + if( kv ) { + + //add the ammo as if the player picked it up + AmmoArray[ 0 ].ammoCount += atoi(kv->GetValue().c_str()); + AmmoArray[ 0 ].percentFull = (float)AmmoArray[ 0 ].ammoCount / (float)AmmoArray[ 0 ].ammoMax; + + //resort the ammo. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + } else { + gameLocal.Warning("Bad ammo data on target_ammostash '%s'", this->GetName()); + return; + } + } + +} + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_TetherAI ) + EVENT( EV_Activate, rvTarget_TetherAI::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_TetherAI::Event_Activate +================ +*/ +void rvTarget_TetherAI::Event_Activate( idEntity *activator ) { + int i; + if ( activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + } + } +} +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_Nailable ) +END_CLASS +void rvTarget_Nailable::Spawn() { + + //InitDefaultPhysics( origin, axis ); + +// int contents = GetPhysics()->GetContents(); +// contents &= CONTENTS_BODY; +// contents &= CONTENTS_SOLID; + GetPhysics()->SetContents( MASK_SHOT_BOUNDINGBOX ); + +} + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LockDoor ) + EVENT( EV_Activate, idTarget_LockDoor::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LockDoor::Event_Activate +================ +*/ +void idTarget_LockDoor::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + int lock; + + lock = spawnArgs.GetInt( "locked", "1" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + } +} + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_CallObjectFunction ) + EVENT( EV_Activate, idTarget_CallObjectFunction::Event_Activate ) +END_CLASS + +/* +================ +idTarget_CallObjectFunction::Event_Activate +================ +*/ +void idTarget_CallObjectFunction::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + const function_t *func; + const char *funcName; + idThread *thread; + + funcName = spawnArgs.GetString( "call" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ent->scriptObject.HasObject() ) { + func = ent->scriptObject.GetFunction( funcName ); + if ( !func ) { + gameLocal.Error( "Function '%s' not found on entity '%s' for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function '%s' on entity '%s' has the wrong number of parameters for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( !ent->scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function '%s' on entity '%s' is the wrong type for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + // create a thread and call the function +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->CallFunction( ent, func, true ); + thread->Start(); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableLevelWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableLevelWeapons ) + EVENT( EV_Activate, idTarget_EnableLevelWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableLevelWeapons::Event_Activate +================ +*/ +void idTarget_EnableLevelWeapons::Event_Activate( idEntity *activator ) { + int i; + const char *weap; + + gameLocal.world->spawnArgs.SetBool( "no_Weapons", spawnArgs.GetBool( "disable" ) ); + + if ( spawnArgs.GetBool( "disable" ) ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_DisableWeapon ); + } + } + } else { + weap = spawnArgs.GetString( "weapon" ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_EnableWeapon ); + if ( weap && weap[ 0 ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, weap ); + } + } + } + } +} + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +const idEventDef EV_TipOff( "" ); +extern const idEventDef EV_GetPlayerPos( "" ); + +CLASS_DECLARATION( idTarget, idTarget_Tip ) + EVENT( EV_Activate, idTarget_Tip::Event_Activate ) + EVENT( EV_TipOff, idTarget_Tip::Event_TipOff ) + EVENT( EV_GetPlayerPos, idTarget_Tip::Event_GetPlayerPos ) +END_CLASS + + +/* +================ +idTarget_Tip::idTarget_Tip +================ +*/ +idTarget_Tip::idTarget_Tip( void ) { + playerPos.Zero(); +} + +/* +================ +idTarget_Tip::Spawn +================ +*/ +void idTarget_Tip::Spawn( void ) { +} + +/* +================ +idTarget_Tip::Save +================ +*/ +void idTarget_Tip::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Restore +================ +*/ +void idTarget_Tip::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_GetPlayerPos( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_TipOff, 100 ); + } +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( player->IsTipVisible() ) { + PostEventSec( &EV_Activate, 5.1f, activator ); + return; + } + player->ShowTip( spawnArgs.GetString( "text_title" ), spawnArgs.GetString( "text_tip" ), false ); + PostEventMS( &EV_GetPlayerPos, 2000 ); + } +} + +/* +================ +idTarget_Tip::Event_TipOff +================ +*/ +void idTarget_Tip::Event_TipOff( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; + if ( v.Length() > 96.0f ) { + player->HideTip(); + } else { + PostEventMS( &EV_TipOff, 100 ); + } + } +} + + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveSecurity ) +EVENT( EV_Activate, idTarget_GiveSecurity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveSecurity::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using security +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveSecurity( spawnArgs.GetString( "text_security" ) ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_RemoveWeapons ) +EVENT( EV_Activate, idTarget_RemoveWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_RemoveWeapons::Event_Activate +================ +*/ +void idTarget_RemoveWeapons::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "weapon", NULL ); + while ( kv ) { + player->RemoveWeapon( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "weapon", kv ); + } +// RAVEN BEGIN +// bdube: default to initial weapon + player->SelectWeapon( 0, true ); +// RAVEN END + } + } +} + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LevelTrigger ) +EVENT( EV_Activate, idTarget_LevelTrigger::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LevelTrigger::Event_Activate +================ +*/ +void idTarget_LevelTrigger::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + player->SetLevelTrigger( spawnArgs.GetString( "levelName" ), spawnArgs.GetString( "triggerName" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableStamina ) +EVENT( EV_Activate, idTarget_EnableStamina::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableStamina::Event_Activate +================ +*/ +void idTarget_EnableStamina::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + if ( spawnArgs.GetBool( "enable" ) ) { + pm_stamina.SetFloat( player->spawnArgs.GetFloat( "pm_stamina" ) ); + } else { + pm_stamina.SetFloat( 0.0f ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ + +const idEventDef EV_RestoreVolume( "" ); +CLASS_DECLARATION( idTarget, idTarget_FadeSoundClass ) +EVENT( EV_Activate, idTarget_FadeSoundClass::Event_Activate ) +EVENT( EV_RestoreVolume, idTarget_FadeSoundClass::Event_RestoreVolume ) +END_CLASS + +/* +================ +idTarget_FadeSoundClass::Event_Activate +================ +*/ +void idTarget_FadeSoundClass::Event_Activate( idEntity *activator ) { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); + float fadeDuration = spawnArgs.GetFloat( "fadeDuration" ); + int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, fadeClass, spawnArgs.GetBool( "fadeIn" ) ? fadeDB : 0.0f - fadeDB, fadeTime ); + if ( fadeDuration ) { + PostEventSec( &EV_RestoreVolume, fadeDuration ); + } + } +} + +/* +================ +idTarget_FadeSoundClass::Event_RestoreVolume +================ +*/ +void idTarget_FadeSoundClass::Event_RestoreVolume() { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); +// int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // restore volume + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); +} + diff --git a/src/game/Target.h b/src/game/Target.h new file mode 100644 index 0000000..d2181ec --- /dev/null +++ b/src/game/Target.h @@ -0,0 +1,727 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_TARGET_H__ +#define __GAME_TARGET_H__ + +//Used to compare two ammoData structs and see who has more. +int CompareAmmoData( const void* ammo1, const void* ammo2); + + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +class idTarget : public idEntity { +public: + CLASS_PROTOTYPE( idTarget ); +}; + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +class idTarget_Remove : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Remove ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +class idTarget_Show : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Show ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +class idTarget_Damage : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Damage ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +class idTarget_SessionCommand : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SessionCommand ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_EndLevel + +=============================================================================== +*/ + +class idTarget_EndLevel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EndLevel ); + +private: + void Event_Activate( idEntity *activator ); + +}; + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +class idTarget_WaitForButton : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_WaitForButton ); + + void Think( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetGlobalShaderTime + +=============================================================================== +*/ + +class idTarget_SetGlobalShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetGlobalShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +class idTarget_SetShaderParm : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderParm ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +class idTarget_SetShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +class idTarget_FadeEntity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeEntity ); + + idTarget_FadeEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idVec4 fadeFrom; + int fadeStart; + int fadeEnd; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +class idTarget_LightFadeIn : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeIn ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +class idTarget_LightFadeOut : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeOut ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +class idTarget_Give : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Give ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + void Event_PostSpawn(); +// RAVEN END +}; + + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +class idTarget_GiveEmail : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveEmail ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +class idTarget_SetModel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetModel ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +class idTarget_SetInfluence : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetInfluence ); + + idTarget_SetInfluence( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreInfluence(); + void Event_GatherEntities(); + void Event_Flash( float flash, int out ); + void Event_ClearFlash( float flash ); + void Think( void ); + + idList lightList; + idList guiList; + idList soundList; + idList genericList; + float flashIn; + float flashOut; + float delay; + idStr flashInSound; + idStr flashOutSound; + idEntity * switchToCamera; + idInterpolatefovSetting; + bool soundFaded; + bool restoreOnTrigger; +}; + + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +class idTarget_SetKeyVal : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetKeyVal ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +class idTarget_SetFov : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetFov ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idInterpolate fovSetting; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +class idTarget_SetPrimaryObjective : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetPrimaryObjective ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: added player database +/* +=============================================================================== + +idTarget_AddDatabaseEntry + +=============================================================================== +*/ + +class rvTarget_AddDatabaseEntry : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AddDatabaseEntry ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// jshepard: secret area discovery + +/* +=============================================================================== + +idTarget_SecretArea + +=============================================================================== +*/ + +class rvTarget_SecretArea : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_SecretArea ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +class rvTarget_BossBattle : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_BossBattle ); + +private: + void Event_Activate( idEntity *activator ); + void Event_SetShieldPercent( float percent ); + void Event_SetBossMaxHealth( float f ); + void Event_AllowShieldBar( float activate ); + void Event_AllowShieldWarningBar( float activate ); + +}; + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +class rvTarget_TetherAI : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_TetherAI ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +class rvTarget_Nailable : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_Nailable ); + +private: + void Spawn( void ); +}; + + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_LockDoor: public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LockDoor ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +class idTarget_CallObjectFunction : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_CallObjectFunction ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_EnableLevelWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableLevelWeapons ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +class idTarget_Tip : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Tip ); + + idTarget_Tip( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idVec3 playerPos; + + void Event_Activate( idEntity *activator ); + void Event_TipOff( void ); + void Event_GetPlayerPos( void ); +}; + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ +class idTarget_GiveSecurity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveSecurity ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ +class idTarget_RemoveWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_RemoveWeapons ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ +class idTarget_LevelTrigger : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LevelTrigger ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ +class idTarget_EnableStamina : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableStamina ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ +class idTarget_FadeSoundClass : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeSoundClass ); +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreVolume(); +}; + + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +class rvTarget_LaunchProjectile : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_LaunchProjectile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_LaunchProjectile( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_ExitAreaAlert + +=============================================================================== +*/ + +class rvTarget_ExitAreaAlert : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_ExitAreaAlert ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ +typedef struct ammodata_s { + + int ammoIndex; + idStr ammoName; + int ammoCount; + int ammoMax; + float percentFull; + +} ammodata_t; + +#define AMMO_ARRAY_SIZE 10 + +class rvTarget_AmmoStash : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AmmoStash ); + + //used to detect which weapons need ammo. The values stored are 0 to 1, with -1 meaning don't check this out. + ammodata_t AmmoArray[ AMMO_ARRAY_SIZE]; + +private: + void Event_Activate( idEntity *activator ); +}; +#endif /* !__GAME_TARGET_H__ */ diff --git a/src/game/TramGate.cpp b/src/game/TramGate.cpp new file mode 100644 index 0000000..7a49fc6 --- /dev/null +++ b/src/game/TramGate.cpp @@ -0,0 +1,335 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +const idEventDef EV_OpenGate( "" ); +const idEventDef EV_CloseGate( "" ); + +//======================================================= +// +// rvTramGate +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvTramGate ) + EVENT( EV_Touch, rvTramGate::Event_Touch ) + EVENT( EV_Activate, rvTramGate::Event_Activate ) + EVENT( EV_OpenGate, rvTramGate::Event_OpenGate ) + EVENT( EV_CloseGate, rvTramGate::Event_CloseGate ) + EVENT( EV_Door_Lock, rvTramGate::Event_Lock ) + EVENT( EV_Door_IsOpen, rvTramGate::Event_IsOpen ) + EVENT( EV_Door_IsLocked, rvTramGate::Event_IsLocked ) +END_CLASS + +/* +================ +rvTramGate::Spawn +================ +*/ +void rvTramGate::Spawn() { + SpawnDoors(); + + AdjustFrameRate(); +} + +/* +================ +rvTramGate::~rvTramGate +================ +*/ +rvTramGate::~rvTramGate() { + doorList.RemoveContents( true ); +} + +/* +================ +rvTramGate::SpawnDoors +================ +*/ +void rvTramGate::SpawnDoors() { + idDict args; + idVec3 dir = spawnArgs.GetAngles("doorsAxisOffset").ToMat3() * GetPhysics()->GetAxis()[1]; + + args.Set( "team", GetName() ); + args.SetMatrix( "rotation", dir.ToMat3() ); + args.SetVector( "origin", GetPhysics()->GetOrigin() ); + + int len = strlen("door_"); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("door"); kv; kv = spawnArgs.MatchPrefix("door", kv) ) { + args.Set( kv->GetKey().Right(kv->GetKey().Length() - len), kv->GetValue() ); + } + + args.SetFloat( "movedir", (-dir).ToYaw() ); + idDoor* door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door1"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + args.SetFloat( "movedir", dir.ToYaw() ); + door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door2"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + //assert( GetDoorMaster() == door->GetMoveMaster() ); +} + +/* +================ +rvTramGate::AdjustFrameRate +================ +*/ +void rvTramGate::AdjustFrameRate() { + GetAnimator()->SetPlaybackRate( "open", spawnArgs.GetFloat("openFrameRateScale") ); + GetAnimator()->SetPlaybackRate( "close", spawnArgs.GetFloat("closeFrameRateScale") ); +} + +/* +================ +rvTramGate::OpenGate +================ +*/ +void rvTramGate::OpenGate() { + PlayAnim( ANIMCHANNEL_ALL, "open" ); +} + +/* +================ +rvTramGate::CloseGate +================ +*/ +void rvTramGate::CloseGate() { + PlayAnim( ANIMCHANNEL_ALL, "close" ); +} + +/* +================ +rvTramGate::Save +================ +*/ +void rvTramGate::Save( idSaveGame *savefile ) const { + savefile->WriteInt( doorList.Num() ); + for( int i = 0; i < doorList.Num(); i++ ) { + doorList[i].Save( savefile ); + } +} + +/* +================ +rvTramGate::Restore +================ +*/ +void rvTramGate::Restore( idRestoreGame *savefile ) { + int num = 0; + idEntityPtr temp; + savefile->ReadInt( num ); + for( int i = 0; i < num; i++ ) { + temp.Restore( savefile ); + doorList.Append( temp ); + } +} + +/* +================ +rvTramGate::PlayAnim +================ +*/ +int rvTramGate::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return 0; + } + + GetAnimator()->PlayAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim(channel)->PlayLength(); +} + +/* +================ +rvTramGate::CycleAnim +================ +*/ +void rvTramGate::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return; + } + + GetAnimator()->CycleAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAllAnims( int blendFrames ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAnim( int channel, int blendFrames ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +bool rvTramGate::AnimIsPlaying( int channel, int blendFrames ) { + return GetAnimator()->CurrentAnim(channel)->GetEndTime() - FRAME2MS(blendFrames) >= gameLocal.GetTime(); +} + +/* +================ +rvTramGate::IsOpen +================ +*/ +bool rvTramGate::IsOpen() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsOpen() : false; +} + +/* +================ +rvTramGate::IsClosed +================ +*/ +bool rvTramGate::IsClosed() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsClosed() : false; +} + +/* +================ +rvTramGate::GetDoorMaster +================ +*/ +idDoor* rvTramGate::GetDoorMaster() const { + return doorList.Num() ? doorList[0] : NULL; +} + +/* +================ +rvTramGate::IsDoorMasterValid +================ +*/ +bool rvTramGate::IsDoorMasterValid() const { + return GetDoorMaster() != NULL; +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::Event_Touch( idEntity* other, trace_t* trace ) { + if( !IsDoorMasterValid() ) { + return; + } + + if( IsClosed() ) { + OpenGate(); + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } else if( IsOpen() ) { + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } +} + +/* +================ +rvTramGate::Event_Activate +================ +*/ +void rvTramGate::Event_Activate( idEntity* activator ) { + if( !IsDoorMasterValid() ) { + return; + } + + // FIXME: may need some better logic than this. + const char* animName = (IsClosed()) ? "open" : (IsOpen()) ? "close" : NULL; + if( animName ) { + PlayAnim( ANIMCHANNEL_ALL, animName ); + GetDoorMaster()->ProcessEvent( &EV_Activate, this ); + } +} + +/* +================ +rvTramGate::Event_OpenGate +================ +*/ +void rvTramGate::Event_OpenGate() { + OpenGate(); +} + +/* +================ +rvTramGate::Event_CloseGate +================ +*/ +void rvTramGate::Event_CloseGate() { + CloseGate(); +} + +/* +================ +rvTramGate::Event_IsOpen +================ +*/ +void rvTramGate::Event_IsOpen( void ) { + idThread::ReturnInt( IsOpen() ); +} + +/* +================ +rvTramGate::Event_IsLocked +================ +*/ +void rvTramGate::Event_IsLocked( void ) { + idThread::ReturnInt( (IsDoorMasterValid()) ? GetDoorMaster()->IsLocked() : 0 ); +} + +/* +================ +rvTramGate::Event_Lock +================ +*/ +void rvTramGate::Event_Lock( int f ) { + if( IsDoorMasterValid() ) { + GetDoorMaster()->Lock( f ); + } +} diff --git a/src/game/TramGate.h b/src/game/TramGate.h new file mode 100644 index 0000000..b9cab2a --- /dev/null +++ b/src/game/TramGate.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RV_TRAM_GATE_H +#define __RV_TRAM_GATE_H + +extern const idEventDef EV_OpenGate; +extern const idEventDef EV_CloseGate; + +class rvTramGate : public idAnimatedEntity { + CLASS_PROTOTYPE( rvTramGate ); + +public: + void Spawn(); + virtual ~rvTramGate(); + + void OpenGate(); + void CloseGate(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SpawnDoors(); + void AdjustFrameRate(); + + int PlayAnim( int channel, const char* animName, int blendFrames = 0 ); + void CycleAnim( int channel, const char* animName, int blendFrames = 0 ); + void ClearAllAnims( int blendFrames = 0 ); + void ClearAnim( int channel, int blendFrames = 0 ); + bool AnimIsPlaying( int channel, int blendFrames = 0 ); + + bool IsOpen() const; + bool IsClosed() const; + + idDoor* GetDoorMaster() const; + bool IsDoorMasterValid() const; + +protected: + void Event_Touch( idEntity* other, trace_t* trace ); + void Event_Activate( idEntity* activator ); + + void Event_OpenGate(); + void Event_CloseGate(); + + void Event_IsOpen( void ); + void Event_IsLocked( void ); + void Event_Lock( int f ); + +protected: + idList< idEntityPtr > doorList; +}; + +#endif diff --git a/src/game/Trigger.cpp b/src/game/Trigger.cpp new file mode 100644 index 0000000..34a9a54 --- /dev/null +++ b/src/game/Trigger.cpp @@ -0,0 +1,1569 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +/* +=============================================================================== + + idTrigger + +=============================================================================== +*/ + +const idEventDef EV_Enable( "enable", NULL ); +const idEventDef EV_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idTrigger ) + EVENT( EV_Enable, idTrigger::Event_Enable ) + EVENT( EV_Disable, idTrigger::Event_Disable ) +END_CLASS + +/* +================ +idTrigger::DrawDebugInfo +================ +*/ +void idTrigger::DrawDebugInfo( void ) { + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + bool show; + const function_t *func; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetContents() & ( CONTENTS_TRIGGER | CONTENTS_FLASHLIGHT_TRIGGER ) ) { + show = viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ); + if ( !show ) { + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target && viewBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + show = true; + break; + } + } + } + + if ( !show ) { + continue; + } + + gameRenderWorld->DebugBounds( colorOrange, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( ent->name.c_str(), ent->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), ent->GetPhysics()->GetAbsBounds().GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + if ( ent->IsType( idTrigger::Type ) ) { + func = static_cast( ent )->GetScriptFunction(); + } else { + func = NULL; + } + + if ( func ) { + gameRenderWorld->DrawText( va( "call script '%s'", func->Name() ), ent->GetPhysics()->GetAbsBounds().GetCenter() - up, 0.1f, colorWhite, axis, 1 ); + } + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen, box, target->GetPhysics()->GetOrigin() ); + if ( viewTextBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( target->name.c_str(), target->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + } + } + } + } + } +} + +/* +================ +idTrigger::Enable +================ +*/ +void idTrigger::Enable( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + GetPhysics()->EnableClip(); +} + +/* +================ +idTrigger::Disable +================ +*/ +void idTrigger::Disable( void ) { + // we may be relinked if we're bound to another object, so clear the contents as well + GetPhysics()->SetContents( 0 ); + GetPhysics()->DisableClip(); +} + +/* +================ +idTrigger::CallScript +================ +*/ +void idTrigger::CallScript( idEntity* scriptEntity ) { +// RAVEN BEGIN +// abahr + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].InsertEntity( scriptEntity, 0 );//We could pass both the activator and self if wanted + scriptFunctions[ix].CallFunc( &spawnArgs ); + scriptFunctions[ix].RemoveIndex( 0 ); + } +// RAVEN END + +} + +/* +================ +idTrigger::GetScriptFunction +================ +*/ +const function_t *idTrigger::GetScriptFunction( void ) const { +// RAVEN BEGIN +// abahr: + return (scriptFunctions.Num()) ? scriptFunctions[0].GetFunc() : NULL; +// RAVEN END +} + +/* +================ +idTrigger::Save +================ +*/ +void idTrigger::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// abahr + savefile->WriteInt( scriptFunctions.Num() ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Save( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Restore +================ +*/ +void idTrigger::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// abahr + int numScripts = 0; + savefile->ReadInt( numScripts ); + scriptFunctions.SetNum( numScripts ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Restore( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Event_Enable +================ +*/ +void idTrigger::Event_Enable( void ) { + Enable(); +} + +/* +================ +idTrigger::Event_Disable +================ +*/ +void idTrigger::Event_Disable( void ) { + Disable(); +} + +/* +================ +idTrigger::idTrigger +================ +*/ +idTrigger::idTrigger() { +// RAVEN BEGIN +// abahr: scriptFunction init's itself + //scriptFunction = NULL; +// RAVEN END +} + +/* +================ +idTrigger::Spawn +================ +*/ +void idTrigger::Spawn( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + +// RAVEN BEGIN +// abahr: + scriptFunctions.SetGranularity( 1 ); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("call"); kv; kv = spawnArgs.MatchPrefix("call", kv) ) { + if( !kv->GetValue() ) { + continue; + } + + rvScriptFuncUtility& utility = scriptFunctions.Alloc(); + if( !utility.Init(kv->GetValue()) ) { + gameLocal.Warning( "Trigger '%s' at (%s) trying to call an unknown function.", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + } +// RAVEN END +} + + +/* +=============================================================================== + + idTrigger_Multi + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: changed to 'E' to allow NULL entities +const idEventDef EV_TriggerAction( "", "E" ); +// RAVEN END + +CLASS_DECLARATION( idTrigger, idTrigger_Multi ) + EVENT( EV_Touch, idTrigger_Multi::Event_Touch ) + EVENT( EV_Activate, idTrigger_Multi::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Multi::Event_TriggerAction ) + +// RAVEN BEGIN +// kfuller: respond to earthquakes + EVENT( EV_Earthquake, idTrigger_Multi::Event_EarthQuake ) +// RAVEN END +END_CLASS + + +/* +================ +idTrigger_Multi::idTrigger_Multi +================ +*/ +idTrigger_Multi::idTrigger_Multi( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + removeItem = 0; + touchClient = false; + touchOther = false; + touchVehicle = false; + triggerFirst = false; + triggerWithSelf = false; + buyZoneTrigger = 0; + controlZoneTrigger = 0; + prevZoneController = TEAM_NONE; +} + +/* +================ +idTrigger_Multi::Save +================ +*/ +void idTrigger_Multi::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteBool( touchClient ); + savefile->WriteBool( touchOther ); + savefile->WriteBool( touchVehicle ); + savefile->WriteBool( triggerFirst ); + savefile->WriteBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Restore +================ +*/ +void idTrigger_Multi::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadBool( touchClient ); + savefile->ReadBool( touchOther ); + savefile->ReadBool( touchVehicle ); + savefile->ReadBool( triggerFirst ); + savefile->ReadBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Spawn + +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"call" : Script function to call when triggered +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +================ +*/ +void idTrigger_Multi::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + spawnArgs.GetBool( "triggerWithSelf", "0", triggerWithSelf ); + spawnArgs.GetInt( "buyZone", "0", buyZoneTrigger); + spawnArgs.GetInt( "controlZone", "0", controlZoneTrigger); + + if ( buyZoneTrigger == -1 ) + gameLocal.Warning( "trigger_buyzone '%s' at (%s) has no buyZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + if ( controlZoneTrigger == -1 ) + gameLocal.Warning( "trigger_controlzone '%s' at (%s) has no controlZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + + if ( spawnArgs.GetBool( "onlyVehicle" ) ) { + touchVehicle = true; + } else if ( spawnArgs.GetBool( "anyTouch" ) ) { + touchClient = true; + touchOther = true; + } else if ( spawnArgs.GetBool( "noTouch" ) ) { + touchClient = false; + touchOther = false; + } else if ( spawnArgs.GetBool( "noClient" ) ) { + touchClient = false; + touchOther = true; + } else { + touchClient = true; + touchOther = false; + } + + nextTriggerTime = 0; + + if ( spawnArgs.GetBool( "flashlight_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER ); + } else if ( spawnArgs.GetBool( "projectile_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_PROJECTILE ); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Multi::CheckFacing +================ +*/ +bool idTrigger_Multi::CheckFacing( idEntity *activator ) { + if ( spawnArgs.GetBool( "facing" ) ) { + if ( !activator->IsType( idPlayer::GetClassType() ) ) { + return true; + } + idPlayer *player = static_cast< idPlayer* >( activator ); + + // Unfortunately, the angle key rotates the trigger entity also. So I've added + // an angleFacing key which is used instead when present, otherwise the code defaults + // to the behaviour present prior to this change + idVec3 tFacing = GetPhysics()->GetAxis()[0]; + if ( spawnArgs.FindKey( "angleFacing" )) { + idAngles angs(0,spawnArgs.GetFloat( "angleFacing", "0" ),0); + tFacing = angs.ToForward(); + } + float dot = player->viewAngles.ToForward() * tFacing; + + float angle = RAD2DEG( idMath::ACos( dot ) ); + if ( angle > spawnArgs.GetFloat( "angleLimit", "30" ) ) { + return false; + } + } + return true; +} + + +/* +================ +idTrigger_Multi::TriggerAction +================ +*/ +void idTrigger_Multi::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// jdischler: added for Aweldon. The trigger, when activated, will call the listed func with all attached targets, then return. + if ( spawnArgs.GetBool( "_callWithTargets", "0" )) + { + idEntity *ent; + for( int i = 0; i < targets.Num(); i++ ) + { + ent = targets[ i ].GetEntity(); + if ( !ent ) + { + continue; + } + CallScript( ent ); + } + return; + } +// RAVEN END + ActivateTargets( triggerWithSelf ? this : activator ); + CallScript( triggerWithSelf ? this : activator ); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_Multi::Event_TriggerAction +================ +*/ +void idTrigger_Multi::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_Multi::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_Multi::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// bdube: moved trigger first + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( activator ) ) { + return; + } +// RAVEN END + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + + +void idTrigger_Multi::HandleControlZoneTrigger() +{ + // This only does something in multiplayer + if ( !gameLocal.isMultiplayer ) + return; + + const int TEAM_DEADLOCK = 2; + + int pCount = 0; + int count = 0, controllingTeam = TEAM_NONE; + count = playersInTrigger.Num(); + + for ( int i = 0; iPowerUpActive( POWERUP_DEADZONE ) ) + continue; + + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + { + pCount++; + } + + int team = playersInTrigger[i]->team; + + if ( i == 0 ) + controllingTeam = playersInTrigger[i]->team; + + // Assign the controlling team based on the first player + // for zones that accept both. + if ( team != controllingTeam ) + { + controllingTeam = TEAM_DEADLOCK; + pCount = 0; + } + } + + if ( controllingTeam != controlZoneTrigger-1 && controlZoneTrigger != 3 ) + { + controllingTeam = TEAM_NONE; + pCount = 0; + } + + int situation = DZ_NONE; + if ( controllingTeam != prevZoneController ) + { + if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_NONE ) + situation = DZ_MARINES_TAKEN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_NONE ) + situation = DZ_STROGG_TAKEN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINES_LOST; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_LOST; + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_TO_MARINE; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_TO_STROGG; + + // DEADLOCK + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_NONE ) + situation = DZ_MARINE_DEADLOCK; // Unlikely case, just use this. + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINE_REGAIN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_STROGG_REGAIN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINES_LOST; // Unlikely case, just use this. + } + + /// Report individual credits + for( int i = 0; i < count; i++ ) + { + idPlayer* player = playersInTrigger[i]; + + // No token? Ignore em! + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") && !player->PowerUpActive( POWERUP_DEADZONE ) ) + continue; + + int team = player->team; + if( team == controllingTeam ) + { + gameLocal.mpGame.ReportZoneControllingPlayer( player ); + } + } + + /// Report zone control to multiplayer game manager + gameLocal.mpGame.ReportZoneController(controllingTeam, pCount, situation, this); + + playersInTrigger.Clear(); + prevZoneController = controllingTeam; +} + + +/* +================ +idTrigger_Multi::Think +================ +*/ +void idTrigger_Multi::Think() +{ + // Control zone handling + if ( controlZoneTrigger > 0 ) + HandleControlZoneTrigger(); +} + +/* +================ +idTrigger_Multi::Event_Touch +================ +*/ +void idTrigger_Multi::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + +// RAVEN BEGIN +// jdischler: vehicle only trigger + if ( touchVehicle ) { + if ( !other->IsType(rvVehicle::GetClassType()) ) { + return; + } + } else { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool player = other->IsType( idPlayer::GetClassType() ); +// RAVEN END + if ( player ) { + if ( !touchClient ) { + return; + } + if ( static_cast< idPlayer * >( other )->spectating ) { + return; + } + + // Buy zone handling + if ( buyZoneTrigger /*&& gameLocal.mpGame.mpGameState.gameState.currentState != 1*/ ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( buyZoneTrigger-1 == p->team || buyZoneTrigger == 3) + { + p->inBuyZone = true; + p->inBuyZonePrev = true; + } + } + + // Control zone handling + if ( controlZoneTrigger > 0 ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( p->PowerUpActive(POWERUP_DEADZONE) || !spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + playersInTrigger.Append(p); + } + + } else if ( !touchOther ) { + return; + } + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( other, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( other ) ) { + return; + } + + if ( spawnArgs.GetBool( "toggleTriggerFirst" ) ) { + triggerFirst = true; + } + +// RAVEN BEGIN +// rjohnson: added block + if ( developer.GetBool() && *spawnArgs.GetString ( "message" ) ) { + gameLocal.DPrintf ( "Trigger: %s\n", spawnArgs.GetString ( "message" ) ); + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +// RAVEN BEGIN +// kfuller: +void idTrigger_Multi::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + if (requiresLOS) + { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + // activate this effect now + TriggerAction(gameLocal.entities[ENTITYNUM_WORLD]); +} + +// RAVEN END + +/* +=============================================================================== + + idTrigger_EntityName + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_EntityName ) + EVENT( EV_Touch, idTrigger_EntityName::Event_Touch ) + EVENT( EV_Activate, idTrigger_EntityName::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_EntityName::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_EntityName::idTrigger_EntityName +================ +*/ +idTrigger_EntityName::idTrigger_EntityName( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + triggerFirst = false; +} + +/* +================ +idTrigger_EntityName::Save +================ +*/ +void idTrigger_EntityName::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteBool( triggerFirst ); + savefile->WriteString( entityName ); +} + +/* +================ +idTrigger_EntityName::Restore +================ +*/ +void idTrigger_EntityName::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadBool( triggerFirst ); + savefile->ReadString( entityName ); +} + +/* +================ +idTrigger_EntityName::Spawn +================ +*/ +void idTrigger_EntityName::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + + entityName = spawnArgs.GetString( "entityname" ); + if ( !entityName.Length() ) { + gameLocal.Error( "idTrigger_EntityName '%s' at (%s) doesn't have 'entityname' key specified", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + nextTriggerTime = 0; + + if ( !spawnArgs.GetBool( "noTouch" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } +} + +/* +================ +idTrigger_EntityName::TriggerAction +================ +*/ +void idTrigger_EntityName::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// abahr: want same functionality as trigger_multi. Need to move this code into these two function calls + idEntity* scriptEntity = spawnArgs.GetBool("triggerWithSelf") ? this : activator; + ActivateTargets( scriptEntity ); + CallScript( scriptEntity ); +// RAVEN END + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_EntityName::Event_TriggerAction +================ +*/ +void idTrigger_EntityName::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_EntityName::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_EntityName::Event_Trigger( idEntity *activator ) { + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !activator ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && activator->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && activator->name != entityName ) { + return; + } +// RAVEN END + + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + +/* +================ +idTrigger_EntityName::Event_Touch +================ +*/ +void idTrigger_EntityName::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !other ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && other->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && other->name != entityName ) { + return; + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +=============================================================================== + + idTrigger_Timer + +=============================================================================== +*/ + +const idEventDef EV_Timer( "", NULL ); + +CLASS_DECLARATION( idTrigger, idTrigger_Timer ) + EVENT( EV_Timer, idTrigger_Timer::Event_Timer ) + EVENT( EV_Activate, idTrigger_Timer::Event_Use ) +END_CLASS + +/* +================ +idTrigger_Timer::idTrigger_Timer +================ +*/ +idTrigger_Timer::idTrigger_Timer( void ) { + random = 0.0f; + wait = 0.0f; + on = false; + delay = 0.0f; +} + +/* +================ +idTrigger_Timer::Save +================ +*/ +void idTrigger_Timer::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteString( onName ); + savefile->WriteString( offName ); +} + +/* +================ +idTrigger_Timer::Restore +================ +*/ +void idTrigger_Timer::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadString( onName ); + savefile->ReadString( offName ); +} + +/* +================ +idTrigger_Timer::Spawn + +Repeatedly fires its targets. +Can be turned on or off by using. +================ +*/ +void idTrigger_Timer::Spawn( void ) { + spawnArgs.GetFloat( "random", "1", random ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetBool( "start_on", "0", on ); + spawnArgs.GetFloat( "delay", "0", delay ); + onName = spawnArgs.GetString( "onName" ); + offName = spawnArgs.GetString( "offName" ); + + if ( random >= wait && wait >= 0 ) { + random = wait - 0.001; + gameLocal.Warning( "idTrigger_Timer '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( on ) { + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Enable +================ +*/ +void idTrigger_Timer::Enable( void ) { + // if off, turn it on + if ( !on ) { + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Disable +================ +*/ +void idTrigger_Timer::Disable( void ) { + // if on, turn it off + if ( on ) { + on = false; + CancelEvents( &EV_Timer ); + } +} + +/* +================ +idTrigger_Timer::Event_Timer +================ +*/ +void idTrigger_Timer::Event_Timer( void ) { + ActivateTargets( this ); + + // set time before next firing + if ( wait >= 0.0f ) { + PostEventSec( &EV_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } +} + +/* +================ +idTrigger_Timer::Event_Use +================ +*/ +void idTrigger_Timer::Event_Use( idEntity *activator ) { + // if on, turn it off + if ( on ) { + if ( offName.Length() && offName.Icmp( activator->GetName() ) ) { + return; + } + on = false; + CancelEvents( &EV_Timer ); + } else { + // turn it on + if ( onName.Length() && onName.Icmp( activator->GetName() ) ) { + return; + } + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +=============================================================================== + + idTrigger_Count + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Count ) + EVENT( EV_Activate, idTrigger_Count::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Count::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_Count::idTrigger_Count +================ +*/ +idTrigger_Count::idTrigger_Count( void ) { + goal = 0; + count = 0; + delay = 0.0f; +} + +/* +================ +idTrigger_Count::Save +================ +*/ +void idTrigger_Count::Save( idSaveGame *savefile ) const { + savefile->WriteInt( goal ); + savefile->WriteInt( count ); + savefile->WriteFloat( delay ); +} + +/* +================ +idTrigger_Count::Restore +================ +*/ +void idTrigger_Count::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( goal ); + savefile->ReadInt( count ); + savefile->ReadFloat( delay ); +} + +/* +================ +idTrigger_Count::Spawn +================ +*/ +void idTrigger_Count::Spawn( void ) { + spawnArgs.GetInt( "count", "1", goal ); + spawnArgs.GetFloat( "delay", "0", delay ); + count = 0; +} + +/* +================ +idTrigger_Count::Event_Trigger +================ +*/ +void idTrigger_Count::Event_Trigger( idEntity *activator ) { + // goal of -1 means trigger has been exhausted + if (goal >= 0) { + count++; + if ( count >= goal ) { + if (spawnArgs.GetBool("repeat")) { + count = 0; + } else { + goal = -1; + } + PostEventSec( &EV_TriggerAction, delay, activator ); + } + } +} + +/* +================ +idTrigger_Count::Event_TriggerAction +================ +*/ +void idTrigger_Count::Event_TriggerAction( idEntity *activator ) { + ActivateTargets( activator ); + CallScript( activator ); + if ( goal == -1 ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +=============================================================================== + + idTrigger_Hurt + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Hurt ) + EVENT( EV_Touch, idTrigger_Hurt::Event_Touch ) + EVENT( EV_Activate, idTrigger_Hurt::Event_Toggle ) +END_CLASS + +/* +================ +idTrigger_Hurt::idTrigger_Hurt +================ +*/ +idTrigger_Hurt::idTrigger_Hurt( void ) { + on = false; + delay = 0.0f; + nextTime = 0; +} + +/* +================ +idTrigger_Hurt::Save +================ +*/ +void idTrigger_Hurt::Save( idSaveGame *savefile ) const { + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->WriteBool ( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Restore +================ +*/ +void idTrigger_Hurt::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->ReadBool( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Spawn + + Damages activator + Can be turned on or off by using. +================ +*/ +void idTrigger_Hurt::Spawn( void ) { + spawnArgs.GetBool( "on", "1", on ); + spawnArgs.GetFloat( "delay", "1.0", delay ); + +// RAVEN BEGIN +// kfuller: playeronly flag + spawnArgs.GetBool( "playerOnly", "0", playerOnly ); +// RAVEN END + + nextTime = gameLocal.time; + Enable(); +} + +/* +================ +idTrigger_Hurt::Event_Touch +================ +*/ +void idTrigger_Hurt::Event_Touch( idEntity *other, trace_t *trace ) { + const char *damage; + +// RAVEN BEGIN +// kfuller: playeronly flag +// jnewquist: Use accessor for static class type + if ( playerOnly && !other->IsType( idPlayer::GetClassType() ) ) { + return; + } +// RAVEN END + + if ( on && other && gameLocal.time >= nextTime ) { + damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); + other->Damage( this, NULL, vec3_origin, damage, 1.0f, INVALID_JOINT ); + + ActivateTargets( other ); + CallScript( other ); + + nextTime = gameLocal.time + SEC2MS( delay ); + } +} + +/* +================ +idTrigger_Hurt::Event_Toggle +================ +*/ +void idTrigger_Hurt::Event_Toggle( idEntity *activator ) { + on = !on; +} + + +/* +=============================================================================== + + idTrigger_Fade + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Fade ) + EVENT( EV_Activate, idTrigger_Fade::Event_Trigger ) +END_CLASS + +/* +================ +idTrigger_Fade::Event_Trigger +================ +*/ +void idTrigger_Fade::Event_Trigger( idEntity *activator ) { + idVec4 fadeColor; + int fadeTime; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor = spawnArgs.GetVec4( "fadeColor", "0, 0, 0, 1" ); + fadeTime = SEC2MS( spawnArgs.GetFloat( "fadeTime", "0.5" ) ); + player->playerView.Fade( fadeColor, fadeTime ); + PostEventMS( &EV_ActivateTargets, fadeTime, activator ); + } +} + +/* +=============================================================================== + + idTrigger_Touch + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Touch ) + EVENT( EV_Activate, idTrigger_Touch::Event_Trigger ) +END_CLASS + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::idTrigger_Touch( void ) { + clipModel = NULL; +} + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::~idTrigger_Touch( ) { + if ( clipModel ) { + delete clipModel; + clipModel = 0; + } +} + +/* +================ +idTrigger_Touch::Spawn +================ +*/ +void idTrigger_Touch::Spawn( void ) { + // get the clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( GetPhysics()->GetClipModel() ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } + filterTeam = -1; + idStr filterTeamStr = spawnArgs.GetString( "filterTeam" ); + if ( filterTeamStr.Size() ) + { + if ( !idStr::Icmp( "marine", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_MARINE; + } + else if ( !idStr::Icmp( "strogg", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_STROGG; + } + } +} + +/* +================ +idTrigger_Touch::Save +================ +*/ +void idTrigger_Touch::Save( idSaveGame *savefile ) { + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::Restore +================ +*/ +void idTrigger_Touch::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::TouchEntities +================ +*/ +void idTrigger_Touch::TouchEntities( void ) { + int numClipModels, i; + idBounds bounds; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + +// RAVEN BEGIN +// abahr: now scriptFunction list + if ( clipModel == NULL || !scriptFunctions.Num() ) { +// RAVEN END + return; + } + + bounds.FromTransformedBounds( clipModel->GetBounds(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ); +// RAVEN BEGIN +// MCG: filterTeam + if ( filterTeam != -1 ) + { + idActor* actor; + // Iterate through the filter team + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)filterTeam ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden actors and actors that can't be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + if ( !bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + continue; + } + cm = actor->GetPhysics()->GetClipModel(); + if ( !cm || !cm->IsTraceModel() ) { + continue; + } + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ) ) { + continue; + } + ActivateTargets( (idEntity*)actor ); + + CallScript( (idEntity*)actor ); + } + return; + } +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + ActivateTargets( entity ); + +// RAVEN BEGIN +// abahr: changed to be compatible with new script function utility + CallScript( entity ); +// RAVEN END + } +} + +/* +================ +idTrigger_Touch::Think +================ +*/ +void idTrigger_Touch::Think( void ) { + if ( thinkFlags & TH_THINK ) { + TouchEntities(); + } + idEntity::Think(); +} + +/* +================ +idTrigger_Touch::Event_Trigger +================ +*/ +void idTrigger_Touch::Event_Trigger( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTrigger_Touch::Enable +================ +*/ +void idTrigger_Touch::Enable( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Touch::Disable +================ +*/ +void idTrigger_Touch::Disable( void ) { + BecomeInactive( TH_THINK ); +} diff --git a/src/game/Trigger.h b/src/game/Trigger.h new file mode 100644 index 0000000..527af9a --- /dev/null +++ b/src/game/Trigger.h @@ -0,0 +1,311 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_TRIGGER_H__ +#define __GAME_TRIGGER_H__ + +extern const idEventDef EV_Enable; +extern const idEventDef EV_Disable; + +/* +=============================================================================== + + Trigger base. + +=============================================================================== +*/ + +class idTrigger : public idEntity { +public: + CLASS_PROTOTYPE( idTrigger ); + + static void DrawDebugInfo( void ); + + idTrigger(); + void Spawn( void ); + + const function_t * GetScriptFunction( void ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + +protected: +// RAVEN BEGIN +// abahr: removed const from function + void CallScript( idEntity* scriptEntity ); +// RAVEN END + + void Event_Enable( void ); + void Event_Disable( void ); + +// RAVEN BEGIN +// abahr: changed to allow parms to be passed + idList scriptFunctions; + //const function_t * scriptFunction; +// RAVEN END +}; + + +/* +=============================================================================== + + Trigger which can be activated multiple times. + +=============================================================================== +*/ + +class idTrigger_Multi : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Multi ); + + idTrigger_Multi( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + idStr requires; + int removeItem; + bool touchClient; + bool touchOther; + bool touchVehicle; + bool triggerFirst; + bool triggerWithSelf; + int buyZoneTrigger; + int controlZoneTrigger; + int prevZoneController; + + idList playersInTrigger; + + bool CheckFacing( idEntity *activator ); + void HandleControlZoneTrigger(); + +// RAVEN BEGIN +// kfuller: want trigger_relays entities to be able to respond to earthquakes + void Event_EarthQuake (float requiresLOS); +// RAVEN END + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Trigger which can only be activated by an entity with a specific name. + +=============================================================================== +*/ + +class idTrigger_EntityName : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_EntityName ); + + idTrigger_EntityName( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + bool triggerFirst; + idStr entityName; + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +/* +=============================================================================== + + Trigger which repeatedly fires targets. + +=============================================================================== +*/ + +class idTrigger_Timer : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Timer ); + + idTrigger_Timer( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Enable( void ); + virtual void Disable( void ); + +private: + float random; + float wait; + bool on; + float delay; + idStr onName; + idStr offName; + + void Event_Timer( void ); + void Event_Use( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fires targets after being activated a specific number of times. + +=============================================================================== +*/ + +class idTrigger_Count : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Count ); + + idTrigger_Count( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + int goal; + int count; + float delay; + + void Event_Trigger( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which hurts touching entities. + +=============================================================================== +*/ + +class idTrigger_Hurt : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Hurt ); + + idTrigger_Hurt( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + bool on; + float delay; + int nextTime; + +// RAVEN BEGIN +// kfuller: added playeronly + bool playerOnly; +// RAVEN END + + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Toggle( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fades the player view. + +=============================================================================== +*/ + +class idTrigger_Fade : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Fade ); + +private: + void Event_Trigger( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which continuously tests whether other entities are touching it. + +=============================================================================== +*/ + +class idTrigger_Touch : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Touch ); + + idTrigger_Touch( void ); + ~idTrigger_Touch( ); + + void Spawn( void ); + virtual void Think( void ); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + + void TouchEntities( void ); + +private: + idClipModel * clipModel; + int filterTeam; + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_TRIGGER_H__ */ diff --git a/src/game/Weapon.cpp b/src/game/Weapon.cpp new file mode 100644 index 0000000..086e2d7 --- /dev/null +++ b/src/game/Weapon.cpp @@ -0,0 +1,3358 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "Weapon.h" +#include "Projectile.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "client/ClientEffect.h" +//#include "../renderer/tr_local.h" + +/*********************************************************************** + + rvViewWeapon + +***********************************************************************/ + +// class def +CLASS_DECLARATION( idAnimatedEntity, rvViewWeapon ) + EVENT( EV_CallFunction, rvViewWeapon::Event_CallFunction ) +END_CLASS + +/*********************************************************************** + + init + +***********************************************************************/ + +/* +================ +rvViewWeapon::rvViewWeapon() +================ +*/ +rvViewWeapon::rvViewWeapon() { + modelDefHandle = -1; + weapon = NULL; + + Clear(); + + fl.networkSync = true; +} + +/* +================ +rvViewWeapon::~rvViewWeapon() +================ +*/ +rvViewWeapon::~rvViewWeapon() { + Clear(); +} + +/* +================ +rvViewWeapon::Spawn +================ +*/ +void rvViewWeapon::Spawn( void ) { + GetPhysics()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); + GetPhysics()->SetClipModel( NULL, 1.0f ); +} + +/* +================ +rvViewWeapon::Save +================ +*/ +void rvViewWeapon::Save( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( pendingGUIEvents.Num() ); + for ( i = 0; i < pendingGUIEvents.Num(); i ++ ) { + savefile->WriteString ( pendingGUIEvents[i] ); + } + + // TOSAVE: const idDeclSkin * saveSkin; + // TOSAVE: const idDeclSkin * invisSkin; + // TOSAVE: const idDeclSkin * saveWorldSkin; + // TOSAVE: const idDeclSkin * worldInvisSkin; + // TOSAVE: const idDeclSkin * saveHandsSkin; + // TOSAVE: const idDeclSkin * handsSkin; + + // TOSAVE: friend rvWeapon; + // TOSAVE: rvWeapon* weapon; +} + +/* +================ +rvViewWeapon::Restore +================ +*/ +void rvViewWeapon::Restore( idRestoreGame *savefile ) { + int i; + int num; + savefile->ReadInt ( num ); + pendingGUIEvents.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadString ( pendingGUIEvents[i] ); + } +} + +/* +=============== +rvViewWeapon::ClientPredictionThink +=============== +*/ +void rvViewWeapon::ClientPredictionThink( void ) { + UpdateAnimation(); +} + +/*********************************************************************** + + Weapon definition management + +***********************************************************************/ + +/* +================ +rvViewWeapon::Clear +================ +*/ +void rvViewWeapon::Clear( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + StopAllEffects( ); + + // TTimo - the weapon doesn't get a proper Event_DisableWeapon sometimes, so the sound sticks + // typically, client side instance join in tourney mode just wipes all ents + StopSound( SND_CHANNEL_ANY, false ); + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.entityNum = entityNumber; + + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customSkin = NULL; + + // set default shader parms + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[3] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = 0.0f; + renderEntity.shaderParms[5] = 0.0f; + renderEntity.shaderParms[6] = 0.0f; + renderEntity.shaderParms[7] = 0.0f; + + memset( &refSound, 0, sizeof( refSound_t ) ); + refSound.referenceSoundHandle = -1; + + // setting diversity to 0 results in no random sound. -1 indicates random. + refSound.diversity = -1.0f; + + if ( weapon && weapon->GetOwner ( ) ) { + // don't spatialize the weapon sounds + refSound.listenerId = weapon->GetOwner( )->GetListenerId(); + } + + animator.ClearAllAnims( gameLocal.time, 0 ); + + FreeModelDef(); +} + +/* +===================== +rvViewWeapon::GetDebugInfo +===================== +*/ +void rvViewWeapon::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAnimatedEntity::GetDebugInfo( proc, userData ); + weapon->GetDebugInfo( proc, userData ); +} + +/*********************************************************************** + + GUIs + +***********************************************************************/ + +/* +================ +rvViewWeapon::PostGUIEvent +================ +*/ +void rvViewWeapon::PostGUIEvent( const char* event ) { + pendingGUIEvents.Append ( event ); +} + +/*********************************************************************** + + Model and muzzleflash + +***********************************************************************/ + +/* +================ +rvViewWeapon::SetPowerUpSkin +================ +*/ +void rvViewWeapon::SetPowerUpSkin( const char *name ) { +/* FIXME + saveSkin = renderEntity.customSkin; + renderEntity.customSkin = invisSkin; + if ( worldModel.GetEntity() ) { + saveWorldSkin = worldModel.GetEntity()->GetSkin(); + worldModel.GetEntity()->SetSkin( worldInvisSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::UpdateSkin +================ +*/ +void rvViewWeapon::UpdateSkin( void ) { +/* FIXME + renderEntity.customSkin = saveSkin; + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( saveWorldSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::SetModel +================ +*/ +void rvViewWeapon::SetModel( const char *modelname, int mods ) { + assert( modelname ); + + if ( modelDefHandle >= 0 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + + renderEntity.hModel = animator.SetModel( modelname ); + if ( renderEntity.hModel ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + } else { + renderEntity.customSkin = NULL; + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + } + + // hide the model until an animation is played + Hide(); +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvViewWeapon::Think +================ +*/ +void rvViewWeapon::Think( void ) { + // do nothing because the present is called from the player through PresentWeapon +} + +/* +===================== +rvViewWeapon::ConvertLocalToWorldTransform +===================== +*/ +void rvViewWeapon::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + if( !weapon ) { + idAnimatedEntity::ConvertLocalToWorldTransform( offset, axis ); + return; + } + + offset = GetPhysics()->GetOrigin() + offset * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + axis *= GetPhysics()->GetAxis(); +} + +/* +================ +rvViewWeapon::UpdateModelTransform +================ +*/ +void rvViewWeapon::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if( !weapon ) { + idAnimatedEntity::UpdateModelTransform(); + return; + } + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +rvViewWeapon::PresentWeapon +================ +*/ +void rvViewWeapon::PresentWeapon( bool showViewModel ) { + // Dont do anything with the weapon while its stale + if ( fl.networkStale ) { + return; + } + +// RAVEN BEGIN +// rjohnson: cinematics should never be done from the player's perspective, so don't think the weapon ( and their sounds! ) + if ( gameLocal.inCinematic ) { + return; + } +// RAVEN END + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = weapon->GetOwner()->entityNumber + 1; + + // crunch the depth range so it never pokes into walls this breaks the machine gun gui + renderEntity.weaponDepthHackInViewID = weapon->GetOwner()->entityNumber + 1; + + weapon->Think(); + + // present the model + if ( showViewModel && !(weapon->wsfl.zoom && weapon->GetZoomGui() ) ) { + Present(); + } else { + FreeModelDef(); + } + + UpdateSound(); +} + +/* +================ +rvViewWeapon::WriteToSnapshot +================ +*/ +void rvViewWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvViewWeapon::ReadFromSnapshot +================ +*/ +void rvViewWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvViewWeapon::ClientStale +================ +*/ +bool rvViewWeapon::ClientStale ( void ) { + StopSound( SND_CHANNEL_ANY, false ); + + if ( weapon ) { + weapon->ClientStale( ); + } + + idEntity::ClientStale( ); + + return false; +} + +/* +================ +rvViewWeapon::ClientReceiveEvent +================ +*/ +bool rvViewWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + if ( idEntity::ClientReceiveEvent( event, time, msg ) ) { + return true; + } + if ( weapon ) { + return weapon->ClientReceiveEvent ( event, time, msg ); + } + return false; +} + +/*********************************************************************** + + Script events + +***********************************************************************/ + +/* +===================== +rvViewWeapon::Event_CallFunction +===================== +*/ +void rvViewWeapon::Event_CallFunction( const char *funcname ) { + if ( weapon ) { + stateParms_t parms = {0}; + if ( weapon->ProcessState ( funcname, parms ) == SRESULT_ERROR ) { + gameLocal.Error ( "Unknown function '%s' on entity '%s'", funcname, GetName() ); + } + } +} + +/* +================ +rvViewWeapon::SetSkin +================ +*/ +void rvViewWeapon::SetSkin( const char *skinname ) { + const idDeclSkin *skinDecl; + + if ( !skinname || !skinname[ 0 ] ) { + skinDecl = NULL; + } else { + skinDecl = declManager->FindSkin( skinname ); + } + + renderEntity.customSkin = skinDecl; + UpdateVisuals(); + + // Set the skin on the world model as well + if ( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skinDecl ); + } +} + +void rvViewWeapon::SetSkin( const idDeclSkin* skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); + + if( weapon && weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skin ); + } +} + +/* +================ +rvViewWeapon::GetPosition +================ +*/ +void rvViewWeapon::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); +} + +void rvViewWeapon::SetOverlayShader( const idMaterial* material ) { + renderEntity.overlayShader = material; +} + +/*********************************************************************** + + rvWeapon + +***********************************************************************/ + +CLASS_DECLARATION( idClass, rvWeapon ) +END_CLASS + +/* +================ +rvWeapon::rvWeapon +================ +*/ +rvWeapon::rvWeapon ( void ) { + viewModel = NULL; + worldModel = NULL; + weaponDef = NULL; + +#ifdef _XENON + aimAssistFOV = 10.0f; +#endif + + memset ( &animDoneTime, 0, sizeof(animDoneTime) ); + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + + hitscanAttackDef = -1; + + forceGUIReload = false; +} + +/* +================ +rvWeapon::~rvWeapon +================ +*/ +rvWeapon::~rvWeapon( void ) { + int i; + + // Free all current light defs + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + FreeLight ( i ); + } + + // Disassociate with the view model + if ( viewModel ) { + viewModel->weapon = NULL; + } +} + +/* +================ +rvWeapon::Init +================ +*/ +void rvWeapon::Init( idPlayer* _owner, const idDeclEntityDef* def, int _weaponIndex, bool _isStrogg ) { + int i; + + viewModel = _owner->GetWeaponViewModel( ); + worldModel = _owner->GetWeaponWorldModel( ); + weaponDef = def; + owner = _owner; + scriptObject = &viewModel->scriptObject; + weaponIndex = _weaponIndex; + mods = owner->inventory.weaponMods[ weaponIndex ]; + isStrogg = _isStrogg; + + spawnArgs = weaponDef->dict; + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif + + // Apply the mod dictionaries + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict; + if ( !(mods & (1<weapon = this; +} + +/* +================ +rvWeapon::FindViewModelPositionStyle +================ +*/ +void rvWeapon::FindViewModelPositionStyle( idVec3& viewOffset, idAngles& viewAngles ) const { + int viewStyle = g_gunViewStyle.GetInteger(); + const char* styleDefName = spawnArgs.GetString( va("def_viewStyle%d", viewStyle) ); + const idDict* styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + if( !styleDef ) { + styleDefName = spawnArgs.GetString( "def_viewStyle" ); + styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + } + assert( styleDef ); + + viewAngles = styleDef->GetAngles( "viewangles" ); + viewOffset = styleDef->GetVector( "viewoffset" ); +} + +/* +================ +rvWeapon::Spawn +================ +*/ +void rvWeapon::Spawn ( void ) { + + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + +// RAVEN BEGIN +// nrausch: +#if defined(_XENON) + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +// RAVEN END + + // Initialize variables + projectileEnt = NULL; + kick_endtime = 0; + hideStart = 0.0f; + hideEnd = 0.0f; + hideOffset = 0.0f; + status = WP_HOLSTERED; + lastAttack = 0; + clipPredictTime = 0; + + muzzleAxis.Identity(); + muzzleOrigin.Zero(); + pushVelocity.Zero(); + playerViewAxis.Identity(); + playerViewOrigin.Zero(); + viewModelAxis.Identity(); + viewModelOrigin.Zero(); + + // View + viewModelForeshorten = spawnArgs.GetFloat ( "foreshorten", "1" ); + + FindViewModelPositionStyle( viewModelOffset, viewModelAngles ); + + // Offsets + weaponAngleOffsetAverages = spawnArgs.GetInt( "weaponAngleOffsetAverages", "10" ); + weaponAngleOffsetScale = spawnArgs.GetFloat( "weaponAngleOffsetScale", "0.25" ); + weaponAngleOffsetMax = spawnArgs.GetFloat( "weaponAngleOffsetMax", "10" ); + weaponOffsetTime = spawnArgs.GetFloat( "weaponOffsetTime", "400" ); + weaponOffsetScale = spawnArgs.GetFloat( "weaponOffsetScale", "0.005" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate" ) ); + altFireRate = SEC2MS ( spawnArgs.GetFloat ( "altFireRate" ) ); + if( altFireRate == 0 ) { + altFireRate = fireRate; + } + spread = (gameLocal.IsMultiplayer()&&spawnArgs.FindKey("spread_mp"))?spawnArgs.GetFloat ( "spread_mp" ):spawnArgs.GetFloat ( "spread" ); + nextAttackTime = 0; + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); + wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); + + // Attack related values + muzzle_kick_time = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_time" ) ); + muzzle_kick_maxtime = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_maxtime" ) ); + muzzle_kick_angles = spawnArgs.GetAngles( "muzzle_kick_angles" ); + muzzle_kick_offset = spawnArgs.GetVector( "muzzle_kick_offset" ); + + // General weapon properties + wfl.silent_fire = spawnArgs.GetBool( "silent_fire" ); + wfl.hasWindupAnim = spawnArgs.GetBool( "has_windup", "0" ); + icon = spawnArgs.GetString( "mtr_icon" ); + hideTime = SEC2MS( weaponDef->dict.GetFloat( "hide_time", "0.3" ) ); + hideDistance = weaponDef->dict.GetFloat( "hide_distance", "-15" ); + hideStartTime = gameLocal.time - hideTime; + muzzleOffset = weaponDef->dict.GetFloat ( "muzzleOffset", "14" ); + + // Ammo + clipSize = spawnArgs.GetInt( "clipSize" ); + ammoRequired = spawnArgs.GetInt( "ammoRequired" ); + lowAmmo = spawnArgs.GetInt( "lowAmmo" ); + ammoType = GetAmmoIndexForName( spawnArgs.GetString( "ammoType" ) ); + maxAmmo = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ); + + if ( ( ammoType < 0 ) || ( ammoType >= MAX_AMMO ) ) { + gameLocal.Warning( "Unknown ammotype for class '%s'", this->GetClassname ( ) ); + } + + // If the weapon has a clip, then fill it up + ammoClip = owner->inventory.clip[weaponIndex]; + if ( ( ammoClip < 0 ) || ( ammoClip > clipSize ) ) { + // first time using this weapon so have it fully loaded to start + ammoClip = clipSize; + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + } + + // Complex initializations Initialize + InitDefs( ); + InitWorldModel( ); + InitViewModel( ); + + // Requires the view model so must be done after it + InitLights( ); + + viewModel->PostGUIEvent( "weapon_init" ); + viewModel->PostGUIEvent( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent( "weapon_noammo" ); + } + + stateThread.SetName( va("%s_%s_%s", owner->GetName(), viewModel->GetName ( ), spawnArgs.GetString("classname") ) ); + stateThread.SetOwner( this ); + + forceGUIReload = true; +} + +/* +================ +rvWeapon::InitViewModel +================ +*/ +void rvWeapon::InitViewModel( void ) { + const char* guiName; + const char* temp; + int i; + const idKeyValue* kv; + + // Reset view model to clean state + viewModel->Clear ( ); + // Make sure the sound handle is initted + viewModel->refSound.referenceSoundHandle = -1; + + // Intialize the weapon guis + if ( spawnArgs.GetString ( "gui", "", &guiName ) ) { + int g = 0; + do { + viewModel->GetRenderEntity()->gui[g++] = uiManager->FindGui ( guiName, true, false, true ); + guiName = spawnArgs.GetString ( va("gui%d", g + 1 ) ); + } while ( *guiName && viewModel->GetRenderEntity()->gui[g-1] ); + } + + // Set the view models spawn args + viewModel->spawnArgs = weaponDef->dict; + + // Set the model for the view model + if ( isStrogg ) { + temp = spawnArgs.GetString ( "model_view_strogg", spawnArgs.GetString ( "model_view" ) ); + } else { + temp = spawnArgs.GetString ( "model_view" ); + } + viewModel->SetModel( temp, mods ); + + // Hide surfaces + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + + // Show and Hide the mods + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_mod%d", i+1) ), false ); + if ( !modDict ) { + continue; + } + + // Hide any show surfaces for mods that arent on + if ( !(mods & (1<MatchPrefix ( "mod_showsurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_showsurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); // NOTE: HIDING them because we don't have this mod yet + } + } else { + for ( kv = modDict->MatchPrefix ( "mod_hidesurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + } + } + + // find some joints in the model for locating effects + viewAnimator = viewModel->GetAnimator ( ); + barrelJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_barrel", "barrel" ) ); + flashJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flash", "flash" ) ); + ejectJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + guiLightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_guiLight", "guiLight" ) ); + flashlightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flashlight", "flashlight" ) ); + + // Eject offset + spawnArgs.GetVector ( "ejectOffset", "0 0 0", ejectOffset ); + + // Setup a skin for the view model + if ( spawnArgs.GetString ( "skin", "", &temp ) ) { + viewModel->GetRenderEntity()->customSkin = declManager->FindSkin ( temp ); + } + + // make sure we have the correct skin + viewModel->UpdateSkin(); +} + +/* +================ +rvWeapon::InitLights +================ +*/ +void rvWeapon::InitLights ( void ) { + const char* shader; + idVec4 color; + renderLight_t* light; + + memset ( lights, 0, sizeof(lights) ); + memset ( lightHandles, -1, sizeof(lightHandles) ); + + // setup gui light + light = &lights[WPLIGHT_GUI]; + shader = spawnArgs.GetString( "mtr_guiLightShader", "" ); + if ( shader && *shader && viewModel->GetRenderEntity()->gui[0] ) { + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = spawnArgs.GetFloat("glightRadius", "3" ); + color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light->shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->lightId = WPLIGHT_GUI * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + spawnArgs.GetVector ( "glightOffset", "0 0 0", guiLightOffset ); + } + + // Muzzle flash + light = &lights[WPLIGHT_MUZZLEFLASH]; + shader = spawnArgs.GetString( "mtr_flashShader", "muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector ( "flashTarget" ); + light->up = spawnArgs.GetVector ( "flashUp" ); + light->right = spawnArgs.GetVector ( "flashRight" ); + light->end = light->target; + } + light->lightId = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + muzzleFlashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) ); + muzzleFlashEnd = 0; + spawnArgs.GetVector ( "flashViewOffset", "0 0 0", muzzleFlashViewOffset ); + } + + // the world muzzle flash is the same, just positioned differently + lights[WPLIGHT_MUZZLEFLASH_WORLD] = lights[WPLIGHT_MUZZLEFLASH]; + light = &lights[WPLIGHT_MUZZLEFLASH_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_MUZZLEFLASH_WORLD * 100 + owner->entityNumber; + + // flashlight + light = &lights[WPLIGHT_FLASHLIGHT]; + shader = spawnArgs.GetString( "mtr_flashlightShader", "lights/muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashlightColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = + (float)spawnArgs.GetInt( "flashlightRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector( "flashlightTarget" ); + light->up = spawnArgs.GetVector( "flashlightUp" ); + light->right = spawnArgs.GetVector( "flashlightRight" ); + light->end = light->target; + } + + light->allowLightInViewID = owner->entityNumber+1; + light->lightId = WPLIGHT_FLASHLIGHT * 100 + owner->entityNumber; + spawnArgs.GetVector ( "flashlightViewOffset", "0 0 0", flashlightViewOffset ); + } + + // the world muzzle flashlight is the same, just positioned differently + lights[WPLIGHT_FLASHLIGHT_WORLD] = lights[WPLIGHT_FLASHLIGHT]; + light = &lights[WPLIGHT_FLASHLIGHT_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_FLASHLIGHT_WORLD * 100 + owner->entityNumber; +} + +/* +================ +rvWeapon::InitDefs +================ +*/ +void rvWeapon::InitDefs( void ) { + const char* name; + const idDeclEntityDef* def; + const char* spawnclass; + idTypeInfo* cls; + + // get the projectile + attackDict.Clear(); + + // Projectile + if ( spawnArgs.GetString( "def_projectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_hitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + hitscanAttackDef = def->Index(); + } + wfl.attackHitscan = true; + } + + // Alternate projectile + attackAltDict.Clear (); + if ( spawnArgs.GetString( "def_altprojectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown alt projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for alt projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_althitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + wfl.attackAltHitscan = true; + } + + // get the melee damage def + meleeDistance = spawnArgs.GetFloat( "melee_distance" ); + if ( spawnArgs.GetString( "def_melee", "", &name ) && *name ) { + meleeDef = gameLocal.FindEntityDef( name, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s' for weapon '%s'", name, weaponDef->GetName() ); + } + } else { + meleeDef = NULL; + } + + // get the brass def + brassDict.Clear(); + if ( spawnArgs.GetString( "def_ejectBrass", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + brassDict = def->dict; + // force any brass to spawn as client moveable + brassDict.Set( "spawnclass", "rvClientMoveable" ); + } + } +} + +/* +================ +rvWeapon::Think +================ +*/ +void rvWeapon::Think ( void ) { + + // Cache the player origin and axis + playerViewOrigin = owner->firstPersonViewOrigin; + playerViewAxis = owner->firstPersonViewAxis; + + // calculate weapon position based on player movement bobbing + owner->CalculateViewWeaponPos( viewModelOrigin, viewModelAxis ); + + // hide offset is for dropping the gun when approaching a GUI or NPC + // This is simpler to manage than doing the weapon put-away animation + if ( gameLocal.time - hideStartTime < hideTime ) { + float frac = ( float )( gameLocal.time - hideStartTime ) / ( float )hideTime; + if ( hideStart < hideEnd ) { + frac = 1.0f - frac; + frac = 1.0f - frac * frac; + } else { + frac = frac * frac; + } + hideOffset = hideStart + ( hideEnd - hideStart ) * frac; + } else { + hideOffset = hideEnd; + } + viewModelOrigin += hideOffset * viewModelAxis[ 2 ]; + + // kick up based on repeat firing + MuzzleRise( viewModelOrigin, viewModelAxis ); + + if ( viewModel ) { + // set the physics position and orientation + viewModel->GetPhysics()->SetOrigin( viewModelOrigin ); + viewModel->GetPhysics()->SetAxis( viewModelAxis ); + viewModel->UpdateVisuals(); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + + // Update the zoom variable before updating the script + wsfl.zoom = owner->IsZoomed( ); + + // Only update the state loop on new frames + if ( gameLocal.isNewFrame ) { + stateThread.Execute( ); + } + + if ( viewModel ) { + viewModel->UpdateAnimation( ); + } + + // Clear reload and flashlight flags + wsfl.reload = false; + wsfl.flashlight = false; + + // deal with the third-person visible world model + // don't show shadows of the world model in first person + if ( worldModel && worldModel->GetRenderEntity() ) { + // always show your own weapon + if( owner->entityNumber == gameLocal.localClientNum ) { + worldModel->GetRenderEntity()->suppressLOD = 1; + } else { + worldModel->GetRenderEntity()->suppressLOD = 0; + } + + if ( gameLocal.IsMultiplayer() && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all weapon shadows for the local client + worldModel->GetRenderEntity()->suppressShadowInViewID = gameLocal.localClientNum+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all weapon shadows + worldModel->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + // Only show weapon shadows for other clients + worldModel->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } + + UpdateGUI(); + + // Update lights + UpdateFlashlight ( ); + UpdateMuzzleFlash ( ); + + // update the gui light + renderLight_t& light = lights[WPLIGHT_GUI]; + if ( light.lightRadius[0] && guiLightJointView != INVALID_JOINT ) { + if ( viewModel ) { + idVec4 color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light.shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light.shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light.shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + GetGlobalJointTransform( true, guiLightJointView, light.origin, light.axis, guiLightOffset ); + UpdateLight ( WPLIGHT_GUI ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + } + + // Alert Monsters if the flashlight is one or a muzzle flash is active? + if ( !gameLocal.isMultiplayer ) { + if ( !owner->fl.notarget && (lightHandles[WPLIGHT_MUZZLEFLASH] != -1 || lightHandles[WPLIGHT_FLASHLIGHT] != -1 ) ) { + AlertMonsters ( ); + } + } +} + +/* +================ +rvWeapon::InitWorldModel +================ +*/ +void rvWeapon::InitWorldModel( void ) { + idEntity *ent; + + ent = worldModel; + if ( !ent ) { + gameLocal.Warning ( "InitWorldModel failed due to missing entity" ); + return; + } + + const char *model = spawnArgs.GetString( "model_world" ); + const char *attach = spawnArgs.GetString( "joint_attach" ); + + if ( model[0] && attach[0] ) { + ent->Show(); + ent->SetModel( model ); + ent->GetPhysics()->SetContents( 0 ); + ent->GetPhysics()->SetClipModel( NULL, 1.0f ); + ent->BindToJoint( owner, attach, true ); + ent->GetPhysics()->SetOrigin( vec3_origin ); + ent->GetPhysics()->SetAxis( mat3_identity ); + + // supress model in player views, but allow it in mirrors and remote views + renderEntity_t *worldModelRenderEntity = ent->GetRenderEntity(); + if ( worldModelRenderEntity ) { + worldModelRenderEntity->suppressSurfaceInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } else { + ent->SetModel( "" ); + ent->Hide(); + } + + // the renderEntity is reused, so the relevant fields (except this one) appear to be correctly reinitialized + worldModel->GetRenderEntity()->suppressSurfaceMask = 0; + + // Cache the world joints + worldAnimator = ent->GetAnimator ( ); + flashJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flash", "flash" ) ); + flashlightJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flashlight", "flashlight" ) ); + ejectJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_eject", "eject" ) ); +} + +/* +================ +rvWeapon::SetState +================ +*/ +void rvWeapon::SetState( const char *statename, int blendFrames ) { + stateThread.SetState( statename, blendFrames ); +} + +/* +================ +rvWeapon::PostState +================ +*/ +void rvWeapon::PostState( const char* statename, int blendFrames ) { + stateThread.PostState( statename, blendFrames ); +} + +/* +===================== +rvWeapon::ExecuteState +===================== +*/ +void rvWeapon::ExecuteState ( const char* statename ) { + SetState ( statename, 0 ); + stateThread.Execute ( ); +} + +/* +================ +rvWeapon::UpdateLight +================ +*/ +void rvWeapon::UpdateLight ( int lightID ) { + if ( lightHandles[lightID] == -1 ) { + lightHandles[lightID] = gameRenderWorld->AddLightDef ( &lights[lightID] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[lightID], &lights[lightID] ); + } +} + +/* +================ +rvWeapon::FreeLight +================ +*/ +void rvWeapon::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + + +/*********************************************************************** + + Networking + +***********************************************************************/ + +/* +================ +rvWeapon::WriteToSnapshot +================ +*/ +void rvWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( ammoClip, ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ReadFromSnapshot +================ +*/ +void rvWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ammoClip = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::SkipFromSnapshot +================ +*/ +void rvWeapon::SkipFromSnapshot ( const idBitMsgDelta &msg ) { + msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ClientStale +================ +*/ +void rvWeapon::ClientStale( void ) { +} + +/* +================ +rvWeapon::ClientReceiveEvent +================ +*/ +bool rvWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_RELOAD: { + if ( gameLocal.time - time < 1000 ) { + wsfl.netReload = true; + wsfl.netEndReload = false; + } + return true; + } + case EVENT_ENDRELOAD: { + wsfl.netEndReload = true; + return true; + } + case EVENT_CHANGESKIN: { +/* + // FIXME: use idGameLocal::ReadDecl + int index = msg.ReadLong(); + renderEntity.customSkin = ( index != -1 ) ? static_cast( declManager->DeclByIndex( DECL_SKIN, index ) ) : NULL; + UpdateVisuals(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( renderEntity.customSkin ); + } + */ + return true; + } + } + return false; +} + +/*********************************************************************** + + Save / Load + +***********************************************************************/ + +/* +================ +rvWeapon::Save +================ +*/ +void rvWeapon::Save ( idSaveGame *savefile ) const { + int i; + + // Flags + savefile->Write ( &wsfl, sizeof( wsfl ) ); + savefile->Write ( &wfl, sizeof( wfl ) ); + + // Write all cached joints + savefile->WriteJoint ( barrelJointView ); + savefile->WriteJoint ( flashJointView ); + savefile->WriteJoint ( ejectJointView ); + savefile->WriteJoint ( guiLightJointView ); + savefile->WriteJoint ( flashlightJointView ); + + savefile->WriteJoint ( flashJointWorld ); + savefile->WriteJoint ( ejectJointWorld ); + savefile->WriteJoint ( flashlightJointWorld ); + + savefile->WriteInt ( status ); + savefile->WriteInt ( lastAttack ); + + // Hide / Show + savefile->WriteInt ( hideTime ); + savefile->WriteFloat ( hideDistance ); + savefile->WriteInt ( hideStartTime ); + savefile->WriteFloat ( hideStart ); + savefile->WriteFloat ( hideEnd ); + savefile->WriteFloat ( hideOffset ); + + // Write attack related values + savefile->WriteVec3 ( pushVelocity ); + savefile->WriteInt ( kick_endtime ); + savefile->WriteInt ( muzzle_kick_time ); + savefile->WriteInt ( muzzle_kick_maxtime ); + savefile->WriteAngles ( muzzle_kick_angles ); + savefile->WriteVec3 ( muzzle_kick_offset ); + savefile->WriteVec3 ( muzzleOrigin ); + savefile->WriteMat3 ( muzzleAxis ); + savefile->WriteFloat ( muzzleOffset ); + projectileEnt.Save ( savefile ); + savefile->WriteVec3 ( ejectOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt ( fireRate ); + savefile->WriteFloat ( spread ); + // savefile->WriteInt ( nextAttackTime ); // cnicholson: This is set to 0 in restore, so don't save it + + // cnicholson: These 3 idDicts are setup during restore, no need to save them. + // TOSAVE: idDict attackAltDict; + // TOSAVE: idDict attackDict; + // TOSAVE: idDict brassDict; + + // Defs + // TOSAVE: const idDeclEntityDef * meleeDef; // cnicholson: This is setup in restore, so don't save it + savefile->WriteFloat ( meleeDistance ); + + // Zoom + savefile->WriteInt ( zoomFov ); + savefile->WriteUserInterface ( zoomGui, true ); + savefile->WriteFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->WriteInt( lightHandles[i] ); + savefile->WriteRenderLight( lights[i] ); + } + savefile->WriteVec3 ( guiLightOffset ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashViewOffset ); + savefile->WriteVec3 ( flashlightViewOffset ); + savefile->WriteBool ( flashlightOn ); // cnicholson: Added unsaved var + savefile->WriteVec3 ( flashlightViewOffset ); // cnicholson: Added unsaved var + + // Write ammo values + savefile->WriteInt ( ammoType ); + savefile->WriteInt ( ammoRequired ); + savefile->WriteInt ( clipSize ); + savefile->WriteInt ( ammoClip ); + savefile->WriteInt ( lowAmmo ); + savefile->WriteInt ( maxAmmo ); + + // multiplayer + savefile->WriteInt ( clipPredictTime ); // TOSAVE: Save MP value? + + // View + savefile->WriteVec3 ( playerViewOrigin ); + savefile->WriteMat3 ( playerViewAxis ); + + savefile->WriteVec3 ( viewModelOrigin ); + savefile->WriteMat3 ( viewModelAxis ); + savefile->WriteAngles ( viewModelAngles ); + savefile->WriteVec3 ( viewModelOffset ); // cnicholson: Added unsaved var + + // Offsets + savefile->WriteInt ( weaponAngleOffsetAverages ); + savefile->WriteFloat ( weaponAngleOffsetScale ); + savefile->WriteFloat ( weaponAngleOffsetMax ); + savefile->WriteFloat ( weaponOffsetTime ); + savefile->WriteFloat ( weaponOffsetScale ); + + savefile->WriteString ( icon ); + savefile->WriteBool ( isStrogg ); + + // TOSAVE: idDict spawnArgs; + + // TOSAVE: idEntityPtr viewModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* viewAnimator; + // TOSAVE: idEntityPtr worldModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* worldAnimator; + // TOSAVE: const idDeclEntityDef* weaponDef; + // TOSAVE: idScriptObject* scriptObject; + savefile->WriteObject ( owner ); + savefile->WriteInt ( weaponIndex ); // cnicholson: Added unsaved var + savefile->WriteInt ( mods ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( viewModelForeshorten ); + + stateThread.Save( savefile ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } + + savefile->WriteInt ( methodOfDeath ); // cnicholson: Added unsaved var +} + +/* +================ +rvWeapon::Restore +================ +*/ +void rvWeapon::Restore ( idRestoreGame *savefile ) { + int i; + const idDeclEntityDef* def; + + // General + savefile->Read ( &wsfl, sizeof( wsfl ) ); + savefile->Read ( &wfl, sizeof( wfl ) ); + + // Read cached joints + savefile->ReadJoint ( barrelJointView ); + savefile->ReadJoint ( flashJointView ); + savefile->ReadJoint ( ejectJointView ); + savefile->ReadJoint ( guiLightJointView ); + savefile->ReadJoint ( flashlightJointView ); + + savefile->ReadJoint ( flashJointWorld ); + savefile->ReadJoint ( ejectJointWorld ); + savefile->ReadJoint ( flashlightJointWorld ); + + savefile->ReadInt ( (int&)status ); + savefile->ReadInt ( lastAttack ); + + // Hide / Show + savefile->ReadInt ( hideTime ); + savefile->ReadFloat ( hideDistance ); + savefile->ReadInt ( hideStartTime ); + savefile->ReadFloat ( hideStart ); + savefile->ReadFloat ( hideEnd ); + savefile->ReadFloat ( hideOffset ); + + // Read attack related values + savefile->ReadVec3 ( pushVelocity ); + savefile->ReadInt ( kick_endtime ); + savefile->ReadInt ( muzzle_kick_time ); + savefile->ReadInt ( muzzle_kick_maxtime ); + savefile->ReadAngles ( muzzle_kick_angles ); + savefile->ReadVec3 ( muzzle_kick_offset ); + savefile->ReadVec3 ( muzzleOrigin ); + savefile->ReadMat3 ( muzzleAxis ); + savefile->ReadFloat ( muzzleOffset ); + projectileEnt.Restore ( savefile ); + savefile->ReadVec3 ( ejectOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt ( fireRate ); + savefile->ReadFloat ( spread ); + nextAttackTime = 0; + + // Attack Alt Def + attackAltDict.Clear( ); + wfl.attackAltHitscan = false; + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_altprojectile" ), false ); + if ( def ) { + attackAltDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_althitscan" ), false ); + if ( def ) { + attackAltDict = def->dict; + wfl.attackAltHitscan = true; + } + } + + // Attack def + attackDict.Clear( ); + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_projectile" ), false ); + wfl.attackHitscan = false; + if ( def ) { + attackDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_hitscan" ), false ); + if ( def ) { + attackDict = def->dict; + wfl.attackHitscan = true; + } + } + + // Brass Def + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_ejectBrass" ), false ); + if ( def ) { + brassDict = def->dict; + } else { + brassDict.Clear(); + } + + // Melee Def + meleeDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_melee" ), false ); + savefile->ReadFloat( meleeDistance ); + + // Zoom + savefile->ReadInt ( zoomFov ); + savefile->ReadUserInterface ( zoomGui, &spawnArgs ); + savefile->ReadFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->ReadInt ( lightHandles[i] ); + savefile->ReadRenderLight( lights[i] ); + if ( lightHandles[i] != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandles[i] = gameRenderWorld->AddLightDef ( &lights[i] ); + } + } + savefile->ReadVec3 ( guiLightOffset ); + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashViewOffset ); + savefile->ReadVec3 ( flashlightViewOffset ); + savefile->ReadBool ( flashlightOn ); // cnicholson: Added unrestored var + savefile->ReadVec3 ( flashlightViewOffset ); // cnicholson: Added unrestored var + + // Read the ammo values + savefile->ReadInt ( (int&)ammoType ); + savefile->ReadInt ( ammoRequired ); + savefile->ReadInt ( clipSize ); + savefile->ReadInt ( ammoClip ); + savefile->ReadInt ( lowAmmo ); + savefile->ReadInt ( maxAmmo ); + + // multiplayer + savefile->ReadInt ( clipPredictTime ); // TORESTORE: Restore MP value? + + // View + savefile->ReadVec3 ( playerViewOrigin ); + savefile->ReadMat3 ( playerViewAxis ); + + savefile->ReadVec3 ( viewModelOrigin ); + savefile->ReadMat3 ( viewModelAxis ); + savefile->ReadAngles ( viewModelAngles ); + savefile->ReadVec3 ( viewModelOffset ); // cnicholson: Added unrestored var + + // Offsets + savefile->ReadInt ( weaponAngleOffsetAverages ); + savefile->ReadFloat ( weaponAngleOffsetScale ); + savefile->ReadFloat ( weaponAngleOffsetMax ); + savefile->ReadFloat ( weaponOffsetTime ); + savefile->ReadFloat ( weaponOffsetScale ); + + savefile->ReadString ( icon ); + savefile->ReadBool ( isStrogg ); + + // TORESTORE: idDict spawnArgs; + + // TORESTORE: idAnimator* viewAnimator; + // TORESTORE: idAnimator* worldAnimator; + // TORESTORE: const idDeclEntityDef* weaponDef; + // TORESTORE: idScriptObject* scriptObject; + + // Entities + savefile->ReadObject( reinterpret_cast( owner ) ); + viewModel = owner->GetWeaponViewModel ( ); + worldModel = owner->GetWeaponWorldModel ( ); + + savefile->ReadInt ( weaponIndex ); // cnicholson: Added unrestored var + savefile->ReadInt ( mods ); // cnicholson: Added unrestored var + + savefile->ReadFloat ( viewModelForeshorten ); + + stateThread.Restore( savefile, this ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } + + savefile->ReadInt ( methodOfDeath ); // cnicholson: Added unrestored var + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvWeapon::Hide +================ +*/ +void rvWeapon::Hide( void ) { + muzzleFlashEnd = 0; + + if ( viewModel ) { + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide ( ); + } + + // Stop flashlight and gui lights + FreeLight ( WPLIGHT_GUI ); + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::Show +================ +*/ +void rvWeapon::Show ( void ) { + if ( viewModel ) { + viewModel->Show(); + } + if ( worldModel ) { + worldModel->Show(); + } +} + +/* +================ +rvWeapon::IsHidden +================ +*/ +bool rvWeapon::IsHidden( void ) const { + return !viewModel || viewModel->IsHidden(); +} + +/* +================ +rvWeapon::HideWorldModel +================ +*/ +void rvWeapon::HideWorldModel ( void ) { + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::ShowWorldModel +================ +*/ +void rvWeapon::ShowWorldModel ( void ) { + if ( worldModel ) { + worldModel->Show(); + } +} + + +/* +================ +rvWeapon::LowerWeapon +================ +*/ +void rvWeapon::LowerWeapon( void ) { + if ( !wfl.hide ) { + hideStart = 0.0f; + hideEnd = hideDistance; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = true; + } +} + +/* +================ +rvWeapon::RaiseWeapon +================ +*/ +void rvWeapon::RaiseWeapon( void ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->Show(); + + if ( forceGUIReload ) { + forceGUIReload = false; + int ammo = AmmoInClip(); + for ( int g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + idUserInterface* gui = viewModel->GetRenderEntity()->gui[g]; + if ( gui ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + } + + if ( wfl.hide ) { + hideStart = hideDistance; + hideEnd = 0.0f; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = false; + } +} + +/* +================ +rvWeapon::PutAway +================ +*/ +void rvWeapon::PutAway( void ) { + wfl.hasBloodSplat = false; + wsfl.lowerWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_lower" ); +} + +/* +================ +rvWeapon::Raise +================ +*/ +void rvWeapon::Raise( void ) { + wsfl.raiseWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_raise" ); +} + +/* +================ +rvWeapon::Flashlight +================ +*/ +void rvWeapon::Flashlight ( void ) { + wsfl.flashlight = true; +} + +/* +================ +rvWeapon::SetPushVelocity +================ +*/ +void rvWeapon::SetPushVelocity( const idVec3& _pushVelocity ) { + pushVelocity = _pushVelocity; +} + +/* +================ +rvWeapon::Reload +NOTE: this is only for impulse-triggered reload, auto reload is scripted +================ +*/ +void rvWeapon::Reload( void ) { + if ( clipSize ) { + wsfl.reload = true; + } +} + +/* +================ +rvWeapon::CancelReload +================ +*/ +void rvWeapon::CancelReload( void ) { + wsfl.attack = true; +} + +/* +================ +rvWeapon::AutoReload +================ +*/ +bool rvWeapon::AutoReload ( void ) { + assert( owner ); + + // on a network client, never predict reloads of other clients. wait for the server + if ( gameLocal.isClient ) { + return false; + } + return gameLocal.userInfo[ owner->entityNumber ].GetBool( "ui_autoReload" ); +} + +/* +================ +rvWeapon::NetReload +================ +*/ +void rvWeapon::NetReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_RELOAD, NULL, false, -1 ); + } +} + +/* +=============== +rvWeapon::NetEndReload +=============== +*/ +void rvWeapon::NetEndReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_ENDRELOAD, NULL, false, -1 ); + } +} + +/* +================ +rvWeapon::SetStatus +================ +*/ +void rvWeapon::SetStatus ( weaponStatus_t _status ) { + status = _status; + switch ( status ) { + case WP_READY: + wsfl.raiseWeapon = false; + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_ready" ); + break; + case WP_OUTOFAMMO: + wsfl.raiseWeapon = false; + break; + case WP_RELOAD: + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_reload" ); + break; + case WP_HOLSTERED: + case WP_RISING: + wsfl.lowerWeapon = false; + owner->WeaponRisingCallback(); + break; + case WP_LOWERING: + wsfl.raiseWeapon = false; + owner->WeaponLoweringCallback(); + break; + } +} + +/* +================ +rvWeapon::OwnerDied +================ +*/ +void rvWeapon::OwnerDied( void ) { + + CleanupWeapon(); + + ExecuteState( "OwnerDied" ); + + if ( viewModel ) { + viewModel->StopSound( SCHANNEL_ANY, false ); + viewModel->StopAllEffects( ); + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::BeginAttack +================ +*/ +void rvWeapon::BeginAttack( void ) { + wsfl.attack = true; + + if ( status != WP_OUTOFAMMO ) { + lastAttack = gameLocal.time; + } +} + +/* +================ +rvWeapon::EndAttack +================ +*/ +void rvWeapon::EndAttack( void ) { + wsfl.attack = false; +} + +/* +================ +rvWeapon::isReady +================ +*/ +bool rvWeapon::IsReady( void ) const { + return !wfl.hide && !(gameLocal.time - hideStartTime < hideTime) && (viewModel && !viewModel->IsHidden()) && ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +rvWeapon::IsReloading +================ +*/ +bool rvWeapon::IsReloading( void ) const { + return ( status == WP_RELOAD ); +} + +/* +================ +rvWeapon::IsHolstered +================ +*/ +bool rvWeapon::IsHolstered( void ) const { + return ( status == WP_HOLSTERED ); +} + +/* +================ +rvWeapon::ShowCrosshair +================ +*/ +bool rvWeapon::ShowCrosshair( void ) const { + if ( owner->IsZoomed ( ) && zoomGui && wfl.zoomHideCrosshair ) { + return false; + } + return !( status == WP_HOLSTERED ); +} + +/* +===================== +rvWeapon::CanDrop +===================== +*/ +bool rvWeapon::CanDrop( void ) const { + const char *classname = spawnArgs.GetString( "def_dropItem" ); + if ( !classname[ 0 ] ) { + return false; + } + return true; +} + +/* +===================== +rvViewWeapon::CanZoom +===================== +*/ +bool rvWeapon::CanZoom( void ) const { +#ifdef _XENON + // apparently a xenon specific bug in medlabs. + return zoomFov != -1 && !IsHidden(); +#else + return zoomFov != -1; +#endif +} + +/*********************************************************************** + + Visual presentation + +***********************************************************************/ + +/* +================ +rvWeapon::MuzzleRise +================ +*/ +void rvWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + int time; + float amount; + idAngles ang; + idVec3 offset; + + time = kick_endtime - gameLocal.time; + if ( time <= 0 ) { + return; + } + + if ( muzzle_kick_maxtime <= 0 ) { + return; + } + + if ( time > muzzle_kick_maxtime ) { + time = muzzle_kick_maxtime; + } + + amount = ( float )time / ( float )muzzle_kick_maxtime; + ang = muzzle_kick_angles * amount; + offset = muzzle_kick_offset * amount; + + origin = origin - axis * offset; + axis = ang.ToMat3() * axis; +} + +/* +================ +rvWeapon::UpdateFlashPosition +================ +*/ +void rvWeapon::UpdateMuzzleFlash ( void ) { + // remove the muzzle flash light when it's done + if ( gameLocal.time >= muzzleFlashEnd || !gameLocal.GetLocalPlayer() || !owner || gameLocal.GetLocalPlayer()->GetInstance() != owner->GetInstance() ) { + FreeLight ( WPLIGHT_MUZZLEFLASH ); + FreeLight ( WPLIGHT_MUZZLEFLASH_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + light.origin = playerViewOrigin + (playerViewAxis * muzzleFlashViewOffset); + light.axis = playerViewAxis; + + // put the world muzzle flash on the end of the joint, no matter what + GetGlobalJointTransform( false, flashJointWorld, lightWorld.origin, lightWorld.axis ); + + UpdateLight ( WPLIGHT_MUZZLEFLASH ); + UpdateLight ( WPLIGHT_MUZZLEFLASH_WORLD ); +} + +/* +================ +rvWeapon::UpdateFlashlight +================ +*/ +void rvWeapon::UpdateFlashlight ( void ) { + // Turn flashlight off? + if (! owner->IsFlashlightOn ( ) ) { + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_FLASHLIGHT]; + renderLight_t& lightWorld = lights[WPLIGHT_FLASHLIGHT_WORLD]; + trace_t tr; + + // the flash has an explicit joint for locating it + GetGlobalJointTransform( true, flashlightJointView, light.origin, light.axis, flashlightViewOffset ); + + // if the desired point is inside or very close to a wall, back it up until it is clear + gameLocal.TracePoint( owner, tr, light.origin - playerViewAxis[0] * 8.0f, light.origin, MASK_SHOT_BOUNDINGBOX, owner ); + + // be at least 8 units away from a solid + light.origin = tr.endpos - (tr.fraction < 1.0f ? (playerViewAxis[0] * 8) : vec3_origin); + + // put the world muzzle flash on the end of the joint, no matter what + if ( flashlightJointWorld != INVALID_JOINT ) { + GetGlobalJointTransform( false, flashlightJointWorld, lightWorld.origin, lightWorld.axis ); + } else { + lightWorld.origin = playerViewOrigin + playerViewAxis[0] * 20.0f; + lightWorld.axis = playerViewAxis; + } + + UpdateLight ( WPLIGHT_FLASHLIGHT ); + UpdateLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::MuzzleFlash +================ +*/ +void rvWeapon::MuzzleFlash ( void ) { + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + if ( !g_muzzleFlash.GetBool() || flashJointView == INVALID_JOINT || !light.lightRadius[0] ) { + return; + } + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( viewModel ) { + // these will be different each fire + light.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + light.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + light.noShadows = true; + + lightWorld.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + lightWorld.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + lightWorld.noShadows = true; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + UpdateMuzzleFlash ( ); +} + + +/* +================ +rvWeapon::UpdateGUI +================ +*/ +void rvWeapon::UpdateGUI( void ) { + idUserInterface* gui; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + gui = viewModel->GetRenderEntity()->gui[0]; + if ( !gui || status == WP_HOLSTERED ) { + return; + } + int g; +for ( g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + gui = viewModel->GetRenderEntity()->gui[g]; + + if ( gameLocal.localClientNum != owner->entityNumber ) { + // if updating the hud for a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( !p ) { + return; + } + if ( !p->spectating || p->spectator != owner->entityNumber ) { + return; + } + } + + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + // show remaining ammo + if ( gui->State().GetInt ( "player_cachedammo", "-1") != ammo ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + +// viewModel->GetRenderEntity()->gui[g]->SetStateInt ( "player_clip_size", ClipSize() ); +} + for ( int i = 0; i < viewModel->pendingGUIEvents.Num(); i ++ ) { + gui->HandleNamedEvent( viewModel->pendingGUIEvents[i] ); + } + viewModel->pendingGUIEvents.Clear(); +} + +/* +================ +rvWeapon::UpdateCrosshairGUI +================ +*/ +void rvWeapon::UpdateCrosshairGUI( idUserInterface* gui ) const { +// RAVEN BEGIN +// cnicholson: Added support for universal crosshair + + // COMMENTED OUT until Custom crosshair GUI is implemented. + if ( g_crosshairCustom.GetBool() ) { // If there's a custom crosshair, use it. + gui->SetStateString( "crossImage", g_crosshairCustomFile.GetString()); + + const idMaterial *material = declManager->FindMaterial( g_crosshairCustomFile.GetString() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } else { + gui->SetStateString( "crossImage", spawnArgs.GetString( "mtr_crosshair" ) ); + + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } + +// Original Block + //gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); +// RAVEN END + gui->SetStateString( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt( "crossOffsetX", spawnArgs.GetInt( "crosshairOffsetX", "0" ) ); + gui->SetStateInt( "crossOffsetY", spawnArgs.GetInt( "crosshairOffsetY", "0" ) ); + gui->StateChanged( gameLocal.time ); +} + +/* +================ +rvWeapon::ForeshortenAxis +================ +*/ +idMat3 rvWeapon::ForeshortenAxis( const idMat3& axis ) const { + return idMat3( axis[0] * viewModelForeshorten, axis[1], axis[2] ); +} + +/* +================ +rvWeapon::GetAngleOffsets +================ +*/ +void rvWeapon::GetAngleOffsets ( int *average, float *scale, float *max ) { + *average = weaponAngleOffsetAverages; + *scale = weaponAngleOffsetScale; + *max = weaponAngleOffsetMax; +} + +/* +================ +rvWeapon::GetTimeOffsets +================ +*/ +void rvWeapon::GetTimeOffsets ( float *time, float *scale ) { + *time = weaponOffsetTime; + *scale = weaponOffsetScale; +} + +/* +================ +rvWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool rvWeapon::GetGlobalJointTransform ( bool view, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset ) { + if ( view) { + // view model + if ( viewModel && viewAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = origin * ForeshortenAxis(viewModelAxis) + viewModelOrigin; + axis = axis * viewModelAxis; + return true; + } + } else { + // world model + if ( worldModel && worldAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = worldModel->GetPhysics()->GetOrigin() + origin * worldModel->GetPhysics()->GetAxis(); + axis = axis * worldModel->GetPhysics()->GetAxis(); + return true; + } + } + origin = viewModelOrigin + offset * viewModelAxis; + axis = viewModelAxis; + return false; +} + +/*********************************************************************** + + Ammo + +***********************************************************************/ + +/* +================ +rvWeapon::GetAmmoIndexForName +================ +*/ +int rvWeapon::GetAmmoIndexForName( const char *ammoname ) { + int num; + const idDict *ammoDict; + + assert( ammoname ); + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + if ( !ammoname[ 0 ] ) { + return 0; + } + + if ( !ammoDict->GetInt( ammoname, "-1", num ) ) { + gameLocal.Error( "Unknown ammo type '%s'", ammoname ); + } + + if ( ( num < 0 ) || ( num >= MAX_AMMOTYPES ) ) { + gameLocal.Error( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, MAX_AMMOTYPES ); + } + + return num; +} + +/* +================ +rvWeapon::GetAmmoNameForNum +================ +*/ +const char* rvWeapon::GetAmmoNameForIndex( int index ) { + int i; + int num; + const idDict *ammoDict; + const idKeyValue *kv; + char text[ 32 ]; + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + sprintf( text, "%d", index ); + + num = ammoDict->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + kv = ammoDict->GetKeyVal( i ); + if ( kv->GetValue() == text ) { + return kv->GetKey(); + } + } + + return NULL; +} + +/* +================ +rvWeapon::TotalAmmoCount +================ +*/ +int rvWeapon::TotalAmmoCount ( void ) const { + return owner->inventory.HasAmmo( ammoType, 1 ); +} + +/* +================ +rvWeapon::AmmoAvailable +================ +*/ +int rvWeapon::AmmoAvailable( void ) const { + if ( owner ) { + return owner->inventory.HasAmmo( ammoType, ammoRequired ); + } else { + return 0; + } +} + +/* +================ +rvWeapon::AmmoInClip +================ +*/ +int rvWeapon::AmmoInClip( void ) const { + if ( !clipSize ) { + return AmmoAvailable(); + } + return ammoClip; +} + +/* +================ +rvWeapon::ResetAmmoClip +================ +*/ +void rvWeapon::ResetAmmoClip( void ) { + ammoClip = -1; +} + +/* +================ +rvWeapon::GetAmmoType +================ +*/ +int rvWeapon::GetAmmoType( void ) const { + return ammoType; +} + +/* +================ +rvWeapon::ClipSize +================ +*/ +int rvWeapon::ClipSize( void ) const { + return clipSize; +} + +/* +================ +rvWeapon::LowAmmo +================ +*/ +int rvWeapon::LowAmmo() const { + return lowAmmo; +} + +/* +================ +rvWeapon::AmmoRequired +================ +*/ +int rvWeapon::AmmoRequired( void ) const { + return ammoRequired; +} + +/* +================ +rvWeapon::SetClip +================ +*/ +void rvWeapon::SetClip ( int amount ) { + ammoClip = amount; + if ( amount < 0 ) { + ammoClip = 0; + } else if ( amount > clipSize ) { + ammoClip = clipSize; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/* +================ +rvWeapon::UseAmmo +================ +*/ +void rvWeapon::UseAmmo ( int amount ) { + owner->inventory.UseAmmo( ammoType, amount * ammoRequired ); + if ( clipSize && ammoRequired ) { + ammoClip -= ( amount * ammoRequired ); + if ( ammoClip < 0 ) { + ammoClip = 0; + } + } +} + +/* +================ +rvWeapon::AddToClip +================ +*/ +void rvWeapon::AddToClip ( int amount ) { + int ammoAvail; + + if ( gameLocal.isClient ) { + return; + } + + ammoClip += amount; + if ( ammoClip > clipSize ) { + ammoClip = clipSize; + } + + ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoAvail > 0 && ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/*********************************************************************** + + Attack + +***********************************************************************/ + + +/* +================ +rvWeapon::Attack +================ +*/ +void rvWeapon::Attack( bool altAttack, int num_attacks, float spread, float fuseOffset, float power ) { + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + if ( viewModel->IsHidden() ) { + return; + } + + // avoid all ammo considerations on an MP client + if ( !gameLocal.isClient ) { + // check if we're out of ammo or the clip is empty + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( !ammoAvail || ( ( clipSize != 0 ) && ( ammoClip <= 0 ) ) ) { + return; + } + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + if ( clipSize && ammoRequired ) { + clipPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + ammoClip -= 1; + } + + // wake up nearby monsters + if ( !wfl.silent_fire ) { + gameLocal.AlertAI( owner ); + } + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + viewModel->SetShaderParm ( SHADERPARM_DIVERSITY, gameLocal.random.CRandomFloat() ); + viewModel->SetShaderParm ( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.realClientTime ) ); + + if ( worldModel.GetEntity() ) { + worldModel->SetShaderParm( SHADERPARM_DIVERSITY, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ] ); + worldModel->SetShaderParm( SHADERPARM_TIMEOFFSET, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] ); + } + + // calculate the muzzle position + if ( barrelJointView != INVALID_JOINT && spawnArgs.GetBool( "launchFromBarrel" ) ) { + // there is an explicit joint for the muzzle + GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis ); + } else { + // go straight out of the view + muzzleOrigin = playerViewOrigin; + muzzleAxis = playerViewAxis; + muzzleOrigin += playerViewAxis[0] * muzzleOffset; + } + + // add some to the kick time, incrementally moving repeat firing weapons back + if ( kick_endtime < gameLocal.realClientTime ) { + kick_endtime = gameLocal.realClientTime; + } + kick_endtime += muzzle_kick_time; + if ( kick_endtime > gameLocal.realClientTime + muzzle_kick_maxtime ) { + kick_endtime = gameLocal.realClientTime + muzzle_kick_maxtime; + } + + // add the muzzleflash + MuzzleFlash(); + + // quad damage overlays a sound + if ( owner->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + viewModel->StartSound( "snd_quaddamage", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + // Muzzle flash effect + bool muzzleTint = spawnArgs.GetBool( "muzzleTint" ); + viewModel->PlayEffect( "fx_muzzleflash", flashJointView, false, vec3_origin, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict, "fx_muzzleflash_world" ), flashJointWorld, vec3_origin, mat3_identity, false, vec3_origin, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + } + + owner->WeaponFireFeedback( &weaponDef->dict ); + + // Inform the gui of the ammo change + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } + + // The attack is either a hitscan or a launched projectile, do that now. + if ( !gameLocal.isClient ) { + idDict& dict = altAttack ? attackAltDict : attackDict; + power *= owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ); + if ( altAttack ? wfl.attackAltHitscan : wfl.attackHitscan ) { + Hitscan( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, power ); + } else { + LaunchProjectiles( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, fuseOffset, power ); + } + //asalmon: changed to keep stats even in single player + statManager->WeaponFired( owner, weaponIndex, num_attacks ); + + } +} + +/* +================ +rvWeapon::LaunchProjectiles +================ +*/ +void rvWeapon::LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ) { + idProjectile* proj; + idEntity* ent; + int i; + float spreadRad; + idVec3 dir; + idBounds ownerBounds; + + if ( gameLocal.isClient ) { + return; + } + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack ( owner, muzzleOrigin, muzzleAxis[0] ); + } + + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + spreadRad = DEG2RAD( spread ); + + idVec3 dirOffset; + idVec3 startOffset; + + spawnArgs.GetVector( "dirOffset", "0 0 0", dirOffset ); + spawnArgs.GetVector( "startOffset", "0 0 0", startOffset ); + + for( i = 0; i < num_projectiles; i++ ) { + float ang; + float spin; + idVec3 dir; + idBounds projBounds; + idVec3 muzzle_pos; + + // Calculate a random launch direction based on the spread + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); +//RAVEN BEGIN +//asalmon: xbox must use muzzle Axis for aim assistance +#ifdef _XBOX + dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir += dirOffset; +#else + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir += dirOffset; +#endif +//RAVEN END + dir.Normalize(); + + // If a projectile entity has already been created then use that one, otherwise + // spawn a new one based on the given dictionary + if ( projectileEnt ) { + ent = projectileEnt; + ent->Show(); + ent->Unbind(); + projectileEnt = NULL; + } else { + dict.SetInt( "instance", owner->GetInstance() ); + gameLocal.SpawnEntityDef( dict, &ent, false ); + } + + // Make sure it spawned + if ( !ent ) { + gameLocal.Error( "failed to spawn projectile for weapon '%s'", weaponDef->GetName ( ) ); + } + + assert ( ent->IsType( idProjectile::GetClassType() ) ); + + // Create the projectile + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin + startOffset, dir, NULL, owner->extraProjPassEntity ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + idVec3 start; + float distance; + trace_t tr; +//RAVEN BEGIN +//asalmon: xbox must use muzzle Axis for aim assistance +#ifdef _XBOX + muzzle_pos = muzzleOrigin + muzzleAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, muzzleAxis[0], distance ) ) { + start = muzzle_pos + distance * muzzleAxis[0]; + } +#else + muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } +#endif +//RAVEN END + else { + start = ownerBounds.GetCenter(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( owner, tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + muzzle_pos = tr.endpos; + } + + // Launch the actual projectile + proj->Launch( muzzle_pos + startOffset, dir, pushVelocity, fuseOffset, power ); + + // Increment the projectile launch count and let the derived classes + // mess with it if they want. + OnLaunchProjectile ( proj ); + } +} + +/* +================ +rvWeapon::OnLaunchProjectile +================ +*/ +void rvWeapon::OnLaunchProjectile ( idProjectile* proj ) { + owner->AddProjectilesFired( 1 ); + if ( proj ) { + proj->methodOfDeath = owner->GetCurrentWeapon(); + } +} + +/* +================ +rvWeapon::Hitscan +================ +*/ +void rvWeapon::Hitscan( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ) { + idVec3 fxOrigin; + idMat3 fxAxis; + int i; + float ang; + float spin; + idVec3 dir; + int areas[ 2 ]; + + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack( owner, muzzleOrigin, muzzleAxis[0] ); + } + + GetGlobalJointTransform( true, flashJointView, fxOrigin, fxAxis, dict.GetVector( "fxOriginOffset" ) ); + + if ( gameLocal.isServer ) { + + assert( hitscanAttackDef >= 0 ); + assert( owner && owner->entityNumber < MAX_CLIENTS ); + int ownerId = owner ? owner->entityNumber : 0; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_HITSCAN ); + msg.WriteLong( hitscanAttackDef ); + msg.WriteBits( ownerId, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteFloat( muzzleOrigin[0] ); + msg.WriteFloat( muzzleOrigin[1] ); + msg.WriteFloat( muzzleOrigin[2] ); + msg.WriteFloat( fxOrigin[0] ); + msg.WriteFloat( fxOrigin[1] ); + msg.WriteFloat( fxOrigin[2] ); + } + + float spreadRad = DEG2RAD( spread ); + idVec3 end; + for( i = 0; i < num_hitscans; i++ ) { + if( weaponDef->dict.GetBool( "machinegunSpreadStyle" ) ) { + float r = gameLocal.random.RandomFloat() * idMath::PI * 2.0f; + float u = idMath::Sin( r ) * gameLocal.random.CRandomFloat() * spread * 16; + r = idMath::Cos( r ) * gameLocal.random.CRandomFloat() * spread * 16; +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else if( weaponDef->dict.GetBool( "shotgunSpreadStyle" ) ) { + float r = gameLocal.random.CRandomFloat() * spread * 16; + float u = gameLocal.random.CRandomFloat() * spread * 16; + +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //RAVEN BEGIN + //asalmon: xbox must use the muzzleAxis so the aim can be adjusted for aim assistance +#ifdef _XBOX + dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#else + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#endif + //RAVEN END + } + dir.Normalize(); + + gameLocal.HitScan( dict, muzzleOrigin, dir, fxOrigin, owner, false, 1.0f, NULL, areas ); + + if ( gameLocal.isServer ) { + msg.WriteDir( dir, 24 ); + if ( i == num_hitscans - 1 ) { + // NOTE: we emit to the areas of the last hitscan + // there is a remote possibility that multiple hitscans for shotgun would cover more than 2 areas, + // so in some rare case a client might miss it + gameLocal.SendUnreliableMessagePVS( msg, owner, areas[0], areas[1] ); + } + } + } +} + +/* +================ +rvWeapon::AlertMonsters +================ +*/ +void rvWeapon::AlertMonsters( void ) { + trace_t tr; + idEntity *ent; + idVec3 end; + renderLight_t& muzzleFlash = lights[WPLIGHT_MUZZLEFLASH]; + + end = muzzleFlash.origin + muzzleFlash.axis * muzzleFlash.target; + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } + + // jitter the trace to try to catch cases where a trace down the center doesn't hit the monster + end += muzzleFlash.axis * muzzleFlash.right * idMath::Sin16( MS2SEC( gameLocal.time ) * 31.34f ); + end += muzzleFlash.axis * muzzleFlash.up * idMath::Sin16( MS2SEC( gameLocal.time ) * 12.17f ); + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } +} + +/* +================ +rvWeapon::EjectBrass +================ +*/ +void rvWeapon::EjectBrass ( void ) { + if ( g_brassTime.GetFloat() <= 0.0f || !owner->CanShowWeaponViewmodel() ) { + return; + } + + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( ejectJointView == INVALID_JOINT || !brassDict.GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !GetGlobalJointTransform( true, ejectJointView, origin, axis ) ) { + return; + } + + // Spawn the client side moveable for the brass + rvClientMoveable* cent = NULL; + + gameLocal.SpawnClientEntityDef( brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOwner( GetOwner() ); + cent->SetOrigin ( origin + playerViewAxis * ejectOffset ); + cent->SetAxis ( playerViewAxis ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = GetOwner()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 2.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict.GetVector("linear_velocity").Random( brassDict.GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( GetOwner()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict.GetAngles("angular_velocity").Random( brassDict.GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/* +================ +rvWeapon::BloodSplat +================ +*/ +bool rvWeapon::BloodSplat( float size ) { + float s, c; + idMat3 localAxis, axistemp; + idVec3 localOrigin, normal; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + + if ( wfl.hasBloodSplat ) { + return true; + } + + wfl.hasBloodSplat = true; + + if ( viewModel->modelDefHandle < 0 ) { + return false; + } + + if ( !GetGlobalJointTransform( true, ejectJointView, localOrigin, localAxis ) ) { + return false; + } + + localOrigin[0] += gameLocal.random.RandomFloat() * -10.0f; + localOrigin[1] += gameLocal.random.RandomFloat() * 1.0f; + localOrigin[2] += gameLocal.random.RandomFloat() * -2.0f; + + normal = idVec3( gameLocal.random.CRandomFloat(), -gameLocal.random.RandomFloat(), -1 ); + normal.Normalize(); + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + localAxis[2] = -normal; + localAxis[2].NormalVectors( axistemp[0], axistemp[1] ); + localAxis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + localAxis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + localAxis[0] *= 1.0f / size; + localAxis[1] *= 1.0f / size; + + idPlane localPlane[2]; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -(localOrigin * localAxis[0]) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -(localOrigin * localAxis[1]) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( "textures/decals/duffysplatgun" ); + + gameRenderWorld->ProjectOverlay( viewModel->modelDefHandle, localPlane, mtr ); + + return true; +} + +/* +================ +rvWeapon::EnterCinematic +================ +*/ +void rvWeapon::EnterCinematic( void ) { + if( viewModel ) { + viewModel->StopSound( SND_CHANNEL_ANY, false ); + } + ExecuteState( "EnterCinematic" ); + + memset( &wsfl, 0, sizeof(wsfl) ); + + wfl.disabled = true; + + LowerWeapon(); +} + +/* +================ +rvWeapon::ExitCinematic +================ +*/ +void rvWeapon::ExitCinematic( void ) { + wfl.disabled = false; + ExecuteState ( "ExitCinematic" ); + RaiseWeapon(); +} + +/* +================ +rvWeapon::NetCatchup +================ +*/ +void rvWeapon::NetCatchup( void ) { + ExecuteState ( "NetCatchup" ); +} + +/* +=============== +rvWeapon::PlayAnim +=============== +*/ +void rvWeapon::PlayAnim( int channel, const char *animname, int blendFrames ) { + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + int anim; + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::PlayCycle +=============== +*/ +void rvWeapon::PlayCycle( int channel, const char *animname, int blendFrames ) { + int anim; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + anim = worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::AnimDone +=============== +*/ +bool rvWeapon::AnimDone( int channel, int blendFrames ) { + if ( animDoneTime[channel] - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } + return false; +} + +/* +=============== +rvWeapon::StartSound +=============== +*/ +bool rvWeapon::StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + return viewModel->StartSound( soundName, channel, soundShaderFlags, broadcast, length ); +} + +/* +=============== +rvWeapon::StopSound +=============== +*/ +void rvWeapon::StopSound( const s_channelType channel, bool broadcast ) { + if ( viewModel ) { + viewModel->StopSound( channel, broadcast ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } +} + +/* +=============== +rvWeapon::PlayEffect +=============== +*/ +rvClientEffect* rvWeapon::PlayEffect( const char* effectName, jointHandle_t joint, bool loop, const idVec3& endOrigin, bool broadcast ) { + if ( viewModel ) { + return viewModel->PlayEffect( effectName, joint, loop, endOrigin, broadcast ); + } + + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return 0; +} + +/* +================ +rvWeapon::CacheWeapon +================ +*/ +void rvWeapon::CacheWeapon( const char *weaponName ) { + const idDeclEntityDef *weaponDef; + const char *brassDefName; + const char *clipModelName; + idTraceModel trm; + + weaponDef = gameLocal.FindEntityDef( weaponName, false ); + if ( !weaponDef ) { + return; + } + + // precache the brass collision model + brassDefName = weaponDef->dict.GetString( "def_ejectBrass" ); + if ( brassDefName[0] ) { + const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( brassDefName, false ); + if ( brassDef ) { + brassDef->dict.GetString( "clipmodel", "", &clipModelName ); + if ( idStr::Icmp( clipModelName, SIMPLE_TRI_NAME ) == 0 ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + if ( !clipModelName[0] ) { + clipModelName = brassDef->dict.GetString( "model" ); // use the visual model + } + // load the trace model + collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ); + } + } + } + + const idKeyValue* kv; + + kv = weaponDef->dict.MatchPrefix( "gui", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + uiManager->FindGui( kv->GetValue().c_str(), true, false, true ); + } + kv = weaponDef->dict.MatchPrefix( "gui", kv ); + } +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeapon ) + STATE ( "Raise", rvWeapon::State_Raise ) + STATE ( "Lower", rvWeapon::State_Lower ) + STATE ( "ExitCinematic", rvWeapon::State_ExitCinematic ) + STATE ( "NetCatchup", rvWeapon::State_NetCatchup ) + STATE ( "EjectBrass", rvWeapon::Frame_EjectBrass ) +END_CLASS_STATES + +/* +================ +rvWeapon::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeapon::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeapon::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeapon::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_NetCatchup ( const stateParms_t& parms ) { + SetState ( "idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_ExitCinematic ( const stateParms_t& parms ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::Frame_EjectBrass +===================== +*/ +stateResult_t rvWeapon::Frame_EjectBrass( const stateParms_t& parms ) { + EjectBrass(); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::GetDebugInfo +===================== +*/ +void rvWeapon::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idClass::GetDebugInfo ( proc, userData ); + proc ( "rvWeapon", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); +} diff --git a/src/game/Weapon.h b/src/game/Weapon.h new file mode 100644 index 0000000..d8e0e46 --- /dev/null +++ b/src/game/Weapon.h @@ -0,0 +1,516 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 9/30/2004 + +#ifndef __GAME_WEAPON_H__ +#define __GAME_WEAPON_H__ + +/* +=============================================================================== + + Player Weapon + +=============================================================================== +*/ + +typedef enum { + WP_READY, + WP_OUTOFAMMO, + WP_RELOAD, + WP_HOLSTERED, + WP_RISING, + WP_LOWERING, + WP_FLASHLIGHT, +} weaponStatus_t; + +static const int MAX_WEAPONMODS = 4; +static const int MAX_AMMOTYPES = 16; + +class idPlayer; +class idItem; +class idAnimatedEntity; +class idProjectile; +class rvWeapon; + +class rvViewWeapon : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvViewWeapon ); + + rvViewWeapon( void ); + virtual ~rvViewWeapon( void ); + + // Init + void Spawn ( void ); + + // save games + void Save ( idSaveGame *savefile ) const; // archives object for save game file + void Restore ( idRestoreGame *savefile ); // unarchives object from save game file + + + // Weapon definition management + void Clear ( void ); + + // GUIs + void PostGUIEvent ( const char* event ); + + virtual void SetModel ( const char *modelname, int mods = 0 ); + void SetPowerUpSkin ( const char *name ); + void UpdateSkin ( void ); + + // State control/player interface + void Think ( void ); + + // Visual presentation + void PresentWeapon ( bool showViewModel ); + + // Networking + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientPredictionThink ( void ); + virtual bool ClientStale ( void ); + + virtual void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); + virtual void UpdateModelTransform ( void ); + + // Debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + void SetSkin ( const char *skinname ); + void SetSkin ( const idDeclSkin* skin ); + + void SetOverlayShader ( const idMaterial* material ); + + virtual void GetPosition ( idVec3& origin, idMat3& axis ) const; + +private: + + idStrList pendingGUIEvents; + + // effects + const idDeclSkin * saveSkin; + const idDeclSkin * invisSkin; + const idDeclSkin * saveWorldSkin; + const idDeclSkin * worldInvisSkin; + const idDeclSkin * saveHandsSkin; + const idDeclSkin * handsSkin; + + void Event_CallFunction ( const char* function ); + + friend class rvWeapon; + rvWeapon* weapon; +}; + +class rvWeapon : public idClass { +public: + + CLASS_PROTOTYPE( rvWeapon ); + + rvWeapon( void ); + virtual ~rvWeapon( void ); + + enum { + WPLIGHT_MUZZLEFLASH, + WPLIGHT_MUZZLEFLASH_WORLD, + WPLIGHT_FLASHLIGHT, + WPLIGHT_FLASHLIGHT_WORLD, + WPLIGHT_GUI, + WPLIGHT_MAX + }; + + enum { + EVENT_RELOAD = idEntity::EVENT_MAXEVENTS, + EVENT_ENDRELOAD, + EVENT_CHANGESKIN, + EVENT_MAXEVENTS + }; + + void Init ( idPlayer* _owner, const idDeclEntityDef* def, int weaponIndex, bool isStrogg = false ); + + // Virtual overrides + void Spawn ( void ); + virtual void Think ( void ); + virtual void CleanupWeapon ( void ) {} + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientStale ( void ); + virtual void ClientUnstale ( void ) { } + virtual void Attack ( bool altFire, int num_attacks, float spread, float fuseOffset, float power ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + virtual void SpectatorCycle ( void ) { } + virtual bool NoFireWhileSwitching ( void ) const { return false; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void PreSave ( void ); + virtual void PostSave ( void ); + + + // Visual presentation + bool BloodSplat ( float size ); + void MuzzleFlash ( void ); + void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + float GetMuzzleFlashLightParm ( int parm ); + void SetMuzzleFlashLightParm ( int parm, float value ); + void GetAngleOffsets ( int *average, float *scale, float *max ); + void GetTimeOffsets ( float *time, float *scale ); + bool GetGlobalJointTransform ( bool viewModel, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset = vec3_origin ); + + // State control/player interface + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void Raise ( void ); + void PutAway ( void ); + void Hide ( void ); + void Show ( void ); + void HideWorldModel ( void ); + void ShowWorldModel ( void ); + void SetFlashlight ( bool on = true ); + void Flashlight ( void ); + void SetPushVelocity ( const idVec3 &pushVelocity ); + void Reload ( void ); + void OwnerDied ( void ); + void BeginAttack ( void ); + void EndAttack ( void ); + bool IsReady ( void ) const; + bool IsReloading ( void ) const; + bool IsHolstered ( void ) const; + bool ShowCrosshair ( void ) const; + bool CanDrop ( void ) const; + bool CanZoom ( void ) const; + void CancelReload ( void ); + void SetStatus ( weaponStatus_t status ); + bool AutoReload ( void ); + bool IsHidden ( void ) const; + void EjectBrass ( void ); + + // Network helpers + void NetReload ( void ); + void NetEndReload ( void ); + void NetCatchup ( void ); + + // Ammo + static int GetAmmoIndexForName ( const char *ammoname ); + static const char* GetAmmoNameForIndex ( int index ); + int GetAmmoType ( void ) const; + int AmmoAvailable ( void ) const; + int AmmoInClip ( void ) const; + void ResetAmmoClip ( void ); + int ClipSize ( void ) const; + int LowAmmo ( void ) const; + int AmmoRequired ( void ) const; + void AddToClip ( int amount ); + void UseAmmo ( int amount ); + void SetClip ( int amount ); + int TotalAmmoCount ( void ) const; + + // Attack + bool PerformAttack ( idVec3& muzzleOrigin, idMat3& muzzleAxis, float dmgPower ); + void LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ); + void Hitscan ( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ); + void AlertMonsters ( void ); + + // Mods + int GetMods ( void ) const; + + // Zoom + idUserInterface* GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + int GetZoomFov ( void ) const; + + rvViewWeapon* GetViewModel ( void ) const; + idAnimatedEntity* GetWorldModel ( void ) const; + idPlayer* GetOwner ( void ) const; + const char * GetIcon ( void ) const; + renderLight_t& GetLight ( int light ); + const idAngles& GetViewModelAngles ( void ) const; + const idVec3& GetViewModelOffset ( void ) const; + + static void CacheWeapon ( const char *weaponName ); + static void SkipFromSnapshot ( const idBitMsgDelta &msg ); + + void EnterCinematic ( void ); + void ExitCinematic ( void ); + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetState ( const char *statename, int blendFrames ); + void PostState ( const char *statename, int blendFrames ); + void ExecuteState ( const char *statename ); + + void PlayAnim ( int channel, const char *animname, int blendFrames ); + void PlayCycle ( int channel, const char *animname, int blendFrames ); + bool AnimDone ( int channel, int blendFrames ); + bool StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound ( const s_channelType channel, bool broadcast ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false ); + + void FindViewModelPositionStyle ( idVec3& viewOffset, idAngles& viewAngles ) const; + +public: + + void InitLights ( void ); + void InitWorldModel ( void ); + void InitViewModel ( void ); + void InitDefs ( void ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID ); + + void UpdateMuzzleFlash ( void ); + void UpdateFlashlight ( void ); + + void UpdateGUI ( void ); + void UpdateCrosshairGUI ( idUserInterface* gui ) const; + + idMat3 ForeshortenAxis ( const idMat3& axis ) const; + + // Script state management + struct weaponStateFlags_s { + bool attack :1; + bool reload :1; + bool netReload :1; + bool netEndReload :1; + bool raiseWeapon :1; + bool lowerWeapon :1; + bool flashlight :1; + bool zoom :1; + } wsfl; + + // Generic flags + struct weaponFlags_s { + bool attackAltHitscan :1; + bool attackHitscan :1; + bool hide :1; + bool disabled :1; + bool hasBloodSplat :1; + bool silent_fire :1; + bool zoomHideCrosshair :1; + bool flashlightOn :1; + bool hasWindupAnim :1; + } wfl; + + // joints from models + jointHandle_t barrelJointView; + jointHandle_t flashJointView; + jointHandle_t ejectJointView; + jointHandle_t guiLightJointView; + jointHandle_t flashlightJointView; + + jointHandle_t flashJointWorld; + jointHandle_t ejectJointWorld; + jointHandle_t flashlightJointWorld; + + weaponStatus_t status; + int lastAttack; + + // hiding weapon + int hideTime; + float hideDistance; + int hideStartTime; + float hideStart; + float hideEnd; + float hideOffset; + + // Attack + idVec3 pushVelocity; + int kick_endtime; + int muzzle_kick_time; + int muzzle_kick_maxtime; + idAngles muzzle_kick_angles; + idVec3 muzzle_kick_offset; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + float muzzleOffset; + idEntityPtr projectileEnt; + idVec3 ejectOffset; + + int fireRate; + int altFireRate; + float spread; + int nextAttackTime; + + // we maintain local copies of the projectile and brass dictionaries so they + // do not have to be copied across the DLL boundary when entities are spawned + idDict attackAltDict; + idDict attackDict; + idDict brassDict; + + // Melee + const idDeclEntityDef * meleeDef; + float meleeDistance; + + // zoom + int zoomFov; // variable zoom fov per weapon (-1 is no zoom) + idUserInterface* zoomGui; // whether or not to overlay a zoom scope + float zoomTime; // time it takes to zoom in + + // lights + renderLight_t lights[WPLIGHT_MAX]; + int lightHandles[WPLIGHT_MAX]; + idVec3 guiLightOffset; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashViewOffset; + bool flashlightOn; + idVec3 flashlightViewOffset; + + // ammo management + int ammoType; + int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + int clipSize; // 0 means no reload + int ammoClip; + int lowAmmo; // if ammo in clip hits this threshold, snd_ + int maxAmmo; + + // multiplayer + int clipPredictTime; + + // these are the player render view parms, which include bobbing + idVec3 playerViewOrigin; + idMat3 playerViewAxis; + + + // View Model + idVec3 viewModelOrigin; + idMat3 viewModelAxis; + idAngles viewModelAngles; + idVec3 viewModelOffset; + + // weighting for viewmodel offsets + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale; + float weaponAngleOffsetMax; + float weaponOffsetTime; + float weaponOffsetScale; + + // General + idStr icon; + bool isStrogg; + + bool forceGUIReload; + +public: + + idDict spawnArgs; + +protected: + + idEntityPtr viewModel; + idAnimator* viewAnimator; + idEntityPtr worldModel; + idAnimator* worldAnimator; + const idDeclEntityDef* weaponDef; + idScriptObject* scriptObject; + idPlayer * owner; + int weaponIndex; + int mods; + + float viewModelForeshorten; + + rvStateThread stateThread; + int animDoneTime[ANIM_NumAnimChannels]; + +private: + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_ExitCinematic ( const stateParms_t& parms ); + stateResult_t State_NetCatchup ( const stateParms_t& parms ); + + stateResult_t Frame_EjectBrass ( const stateParms_t& parms ); + + // store weapon index information for death messages + int methodOfDeath; + + // multiplayer hitscans + int hitscanAttackDef; + + CLASS_STATES_PROTOTYPE ( rvWeapon ); +}; + +ID_INLINE rvViewWeapon* rvWeapon::GetViewModel ( void ) const { + return viewModel.GetEntity(); +} + +ID_INLINE idAnimatedEntity* rvWeapon::GetWorldModel ( void ) const { + return worldModel; +} + +ID_INLINE idPlayer* rvWeapon::GetOwner ( void ) const { + return owner; +} + +ID_INLINE const char* rvWeapon::GetIcon ( void ) const { + return icon; +} + +ID_INLINE renderLight_t& rvWeapon::GetLight ( int light ) { + assert ( light < WPLIGHT_MAX ); + return lights[light]; +} + +ID_INLINE const idAngles& rvWeapon::GetViewModelAngles( void ) const { + return viewModelAngles; +} + +ID_INLINE const idVec3& rvWeapon::GetViewModelOffset ( void ) const { + return viewModelOffset; +} + +ID_INLINE int rvWeapon::GetZoomFov ( void ) const { + return zoomFov; +} + +ID_INLINE idUserInterface* rvWeapon::GetZoomGui ( void ) const { + return zoomGui; +} + +ID_INLINE float rvWeapon::GetZoomTime ( void ) const { + return zoomTime; +} + +ID_INLINE int rvWeapon::GetMods ( void ) const { + return mods; +} + +ID_INLINE void rvWeapon::PreSave ( void ) { +} + +ID_INLINE void rvWeapon::PostSave ( void ) { +} + + +#endif /* !__GAME_WEAPON_H__ */ + +// RAVEN END diff --git a/src/game/WorldSpawn.cpp b/src/game/WorldSpawn.cpp new file mode 100644 index 0000000..7b877ba --- /dev/null +++ b/src/game/WorldSpawn.cpp @@ -0,0 +1,131 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +game_worldspawn.cpp + +Worldspawn class. Each map has one worldspawn which handles global spawnargs. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +================ +idWorldspawn + +Every map should have exactly one worldspawn. +================ +*/ +CLASS_DECLARATION( idEntity, idWorldspawn ) + EVENT( EV_Remove, idWorldspawn::Event_Remove ) + EVENT( EV_SafeRemove, idWorldspawn::Event_Remove ) +END_CLASS + +/* +================ +idWorldspawn::Spawn +================ +*/ +void idWorldspawn::Spawn( void ) { + idStr scriptname; + idThread *thread; + const function_t *func; + const idKeyValue *kv; + + assert( gameLocal.world == NULL ); + gameLocal.world = this; + + // load script + scriptname = gameLocal.GetMapName(); + scriptname.SetFileExtension( ".script" ); + if ( fileSystem->ReadFile( scriptname, NULL, NULL ) > 0 ) { + gameLocal.program.CompileFile( scriptname ); + + // call the main function by default + func = gameLocal.program.FindFunction( "main" ); + if ( func != NULL ) { + thread = new idThread( func ); + thread->DelayedStart( 0 ); + } + } + + // call any functions specified in worldspawn + kv = spawnArgs.MatchPrefix( "call" ); + while( kv != NULL ) { + func = gameLocal.program.FindFunction( kv->GetValue() ); + if ( func == NULL ) { + gameLocal.Error( "Function '%s' not found in script for '%s' key on worldspawn", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + + thread = new idThread( func ); + thread->DelayedStart( 0 ); + kv = spawnArgs.MatchPrefix( "call", kv ); + } +} + +/* +================= +idWorldspawn::Save +================= +*/ +void idWorldspawn::Save( idRestoreGame *savefile ) { +} + +/* +================= +idWorldspawn::Restore +================= +*/ +void idWorldspawn::Restore( idRestoreGame *savefile ) { + assert( gameLocal.world == this ); + +// RAVEN BEGIN +// bdube: gravity change + g_gravity.SetFloat( spawnArgs.GetFloat( "gravity", va( "%f", DEFAULT_GRAVITY) ) ); +// RAVEN END +} + +/* +================ +idWorldspawn::~idWorldspawn +================ +*/ +idWorldspawn::~idWorldspawn() { + if ( gameLocal.world == this ) { + gameLocal.world = NULL; + } +} + +/* +================ +idWorldspawn::Event_Remove +================ +*/ +void idWorldspawn::Event_Remove( void ) { + gameLocal.Error( "Tried to remove world" ); +} diff --git a/src/game/WorldSpawn.h b/src/game/WorldSpawn.h new file mode 100644 index 0000000..1b2bfd7 --- /dev/null +++ b/src/game/WorldSpawn.h @@ -0,0 +1,51 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_WORLDSPAWN_H__ +#define __GAME_WORLDSPAWN_H__ + +/* +=============================================================================== + + World entity. + +=============================================================================== +*/ + +class idWorldspawn : public idEntity { +public: + CLASS_PROTOTYPE( idWorldspawn ); + + ~idWorldspawn(); + + void Spawn( void ); + + void Save( idRestoreGame *savefile ); + void Restore( idRestoreGame *savefile ); + +private: + void Event_Remove( void ); +}; + +#endif /* !__GAME_WORLDSPAWN_H__ */ diff --git a/src/game/ai/AAS.cpp b/src/game/ai/AAS.cpp new file mode 100644 index 0000000..05a3531 --- /dev/null +++ b/src/game/ai/AAS.cpp @@ -0,0 +1,486 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +/* +============ +idAAS::Alloc +============ +*/ +idAAS *idAAS::Alloc( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + return new idAASLocal; +} + +/* +============ +idAAS::idAAS +============ +*/ +idAAS::~idAAS( void ) { +} + +/* +============ +idAASLocal::idAASLocal +============ +*/ +idAASLocal::idAASLocal( void ) { + file = NULL; +} + +/* +============ +idAASLocal::~idAASLocal +============ +*/ +idAASLocal::~idAASLocal( void ) { + Shutdown(); +} + +/* +============ +idAASLocal::Init +============ +*/ +bool idAASLocal::Init( const idStr &mapName, unsigned int mapFileCRC ) { + if ( file && mapName.Icmp( file->GetName() ) == 0 && mapFileCRC == file->GetCRC() ) { + gameLocal.Printf( "Keeping %s\n", file->GetName() ); + RemoveAllObstacles(); + } + else { + Shutdown(); + + file = AASFileManager->LoadAAS( mapName, mapFileCRC ); + if ( !file ) { + common->DWarning( "Couldn't load AAS file: '%s'", mapName.c_str() ); + return false; + } +// RAVEN BEGIN +// rhummer: Check if this is a dummy file, since it really has no valid data dump it. + else if ( file->IsDummyFile( mapFileCRC ) ) { + AASFileManager->FreeAAS( file ); + file = NULL; + return false; + } +// RAVEN END + SetupRouting(); + } + return true; +} + +/* +============ +idAASLocal::Shutdown +============ +*/ +void idAASLocal::Shutdown( void ) { + if ( file ) { + ShutdownRouting(); + RemoveAllObstacles(); + AASFileManager->FreeAAS( file ); + file = NULL; + } +} + +/* +============ +idAASLocal::Stats +============ +*/ +void idAASLocal::Stats( void ) const { + if ( !file ) { + return; + } + common->Printf( "[%s]\n", file->GetName() ); + file->PrintInfo(); + RoutingStats(); +} + +// RAVEN BEGIN +// jscott: added +/* +============ +idAASLocal::StatsSummary +============ +*/ +size_t idAASLocal::StatsSummary( void ) const { + + int size; + + if( !file ) { + + return( 0 ); + } + + size = ( numAreaTravelTimes * sizeof( unsigned short ) ) + + ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) + + ( portalCacheIndexSize * sizeof( idRoutingCache * ) ); + + return( file->GetMemorySize() + size ); +} +// RAVEN END + +/* +============ +idAASLocal::GetSettings +============ +*/ +const idAASSettings *idAASLocal::GetSettings( void ) const { + if ( !file ) { + return NULL; + } + return &file->GetSettings(); +} + +/* +============ +idAASLocal::PointAreaNum +============ +*/ +int idAASLocal::PointAreaNum( const idVec3 &origin ) const { + if ( !file ) { + return 0; + } + return file->PointAreaNum( origin ); +} + +/* +============ +idAASLocal::PointReachableAreaNum +============ +*/ +int idAASLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->PointReachableAreaNum( origin, searchBounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::BoundsReachableAreaNum +============ +*/ +int idAASLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->BoundsReachableAreaNum( bounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::PushPointIntoAreaNum +============ +*/ +void idAASLocal::PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const { + if ( !file ) { + return; + } + file->PushPointIntoAreaNum( areaNum, origin ); +} + +/* +============ +idAASLocal::AreaCenter +============ +*/ +idVec3 idAASLocal::AreaCenter( int areaNum ) const { + if ( !file ) { + return vec3_origin; + } + return file->GetArea( areaNum ).center; +} + +// RAVEN BEGIN +// bdube: added +/* +============ +idAASLocal::AreaRadius +============ +*/ +float idAASLocal::AreaRadius( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).bounds.GetRadius(); +} +// mcg: added +/* +============ +idAASLocal::AreaBounds +============ +*/ +idBounds & idAASLocal::AreaBounds( int areaNum ) const { + return file->GetArea( areaNum ).bounds; +} +/* +============ +idAASLocal::AreaCeiling +============ +*/ +float idAASLocal::AreaCeiling( int areaNum ) const { + return file->GetArea( areaNum ).ceiling; +} +// RAVEN END + +/* +============ +idAASLocal::AreaFlags +============ +*/ +int idAASLocal::AreaFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).flags; +} + +/* +============ +idAASLocal::AreaTravelFlags +============ +*/ +int idAASLocal::AreaTravelFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).travelFlags; +} + +/* +============ +idAASLocal::Trace +============ +*/ +bool idAASLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { + if ( !file ) { + trace.fraction = 0.0f; + trace.lastAreaNum = 0; + trace.numAreas = 0; + return true; + } + return file->Trace( trace, start, end ); +} + +/* +============ +idAASLocal::GetPlane +============ +*/ +const idPlane &idAASLocal::GetPlane( int planeNum ) const { + if ( !file ) { + static idPlane dummy; + return dummy; + } + return file->GetPlane( planeNum ); +} + +/* +============ +idAASLocal::GetEdgeVertexNumbers +============ +*/ +void idAASLocal::GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const { + if ( !file ) { + verts[0] = verts[1] = 0; + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + verts[0] = v[INTSIGNBITSET(edgeNum)]; + verts[1] = v[INTSIGNBITNOTSET(edgeNum)]; +} + +/* +============ +idAASLocal::GetEdge +============ +*/ +void idAASLocal::GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const { + if ( !file ) { + start.Zero(); + end.Zero(); + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + start = file->GetVertex( v[INTSIGNBITSET(edgeNum)] ); + end = file->GetVertex( v[INTSIGNBITNOTSET(edgeNum)] ); +} + + +/* +=============================================================================== + + idAASCallback + +=============================================================================== +*/ + +/* +============ +idAASCallback::~idAASCallback +============ +*/ +idAASCallback::~idAASCallback ( void ) { +} + +/* +============ +idAASCallback::Test +============ +*/ +idAASCallback::testResult_t idAASCallback::Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ) { + // Get AAS file + idAASFile* file = ((idAAS&)*aas).GetFile ( ); + if ( !file ) { + return TEST_BADAREA; + } + + // Get area for edges + aasArea_t& area = file->GetArea ( areaNum ); + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorYellow, area.center, area.center + idVec3(0,0,80.0f), 10000 ); + } + + // Make sure the area itself is valid + if ( !TestArea ( aas, areaNum, area ) ) { + return TEST_BADAREA; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 && point ) { + gameRenderWorld->DebugLine ( colorMagenta, *point, *point + idVec3(0,0,64.0f), 10000 ); + } + + // Test the original origin first + if ( point && TestPointDistance ( origin, *point, minDistance, maxDistance) && TestPoint ( aas, *point ) ) { + goal.areaNum = areaNum; + goal.origin = *point; + return TEST_OK; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorCyan, area.center, area.center + idVec3(0,0,64.0f), 10000 ); + } + + // Test the center of the area + if ( TestPointDistance ( origin, area.center, minDistance, maxDistance) && TestPoint ( aas, area.center, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = area.center; + return TEST_OK; + } + + // For each face test all available edges + int f; + int e; + for ( f = 0; f < area.numFaces; f ++ ) { + aasFace_t& face = file->GetFace ( abs ( file->GetFaceIndex (area.firstFace + f ) ) ); + + // for each edge test a point between the center of the edge and the center + for ( e = 0; e < face.numEdges; e ++ ) { + idVec3 edgeCenter = file->EdgeCenter ( abs( file->GetEdgeIndex( face.firstEdge + e ) ) ); + idVec3 dir = area.center - edgeCenter; + float dist; + for ( dist = dir.Normalize() - 64.0f; dist > 0.0f; dist -= 64.0f ) { + idVec3 testPoint = edgeCenter + dir * dist; + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorPurple, testPoint, testPoint + idVec3(0,0,64.0f), 10000 ); + } + + if ( TestPointDistance ( origin, testPoint, minDistance, maxDistance) && TestPoint ( aas, testPoint, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = testPoint; + return TEST_OK; + } + } + } + } + + return TEST_BADPOINT; +} + +/* +============ +idAASCallback::Init +============ +*/ +bool idAASCallback::TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ) { + float dist = (origin - point).LengthFast ( ); + if ( minDistance > 0.0f && dist < minDistance ) { + return false; + } + if ( maxDistance > 0.0f && dist > maxDistance ) { + return false; + } + return true; +} + +/* +============ +idAASCallback::Init +============ +*/ +void idAASCallback::Init ( void ) { +} + +/* +============ +idAASCallback::Finish +============ +*/ +void idAASCallback::Finish ( void ) { +} + +/* +============ +idAASCallback::TestArea +============ +*/ +bool idAASCallback::TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ) { + return true; +} + +/* +============ +idAASCallback::TestPoint +============ +*/ +bool idAASCallback::TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow ) { + return true; +} + +// RAVEN END diff --git a/src/game/ai/AAS.h b/src/game/ai/AAS.h new file mode 100644 index 0000000..de6da32 --- /dev/null +++ b/src/game/ai/AAS.h @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AAS_H__ +#define __AAS_H__ + +/* +=============================================================================== + + Area Awareness System + +=============================================================================== +*/ + +enum { + PATHTYPE_WALK, + PATHTYPE_WALKOFFLEDGE, + PATHTYPE_BARRIERJUMP, + PATHTYPE_JUMP +}; + +typedef struct aasPath_s { + int type; // path type + idVec3 moveGoal; // point the AI should move towards + int moveAreaNum; // number of the area the AI should move towards + idVec3 secondaryGoal; // secondary move goal for complex navigation + const idReachability * reachability; // reachability used for navigation +} aasPath_t; + + +typedef struct aasGoal_s { + int areaNum; // area the goal is in + idVec3 origin; // position of goal +} aasGoal_t; + + +typedef struct aasObstacle_s { + idBounds absBounds; // absolute bounds of obstacle + idBounds expAbsBounds; // expanded absolute bounds of obstacle +} aasObstacle_t; + +class idAASCallback { +public: + virtual ~idAASCallback ( void ); + + enum testResult_t { + TEST_OK, + TEST_BADAREA, + TEST_BADPOINT + }; + + virtual void Init ( void ); + virtual void Finish ( void ); + + testResult_t Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + bool TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ); +}; + +typedef int aasHandle_t; + +class idAAS { +public: + static idAAS * Alloc( void ); + virtual ~idAAS( void ) = 0; + // Initialize for the given map. + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ) = 0; +// RAVEN BEGIN +// jscott: added + // Prints out the memory used by this AAS + virtual size_t StatsSummary( void ) const = 0; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void Shutdown( void ) = 0; +#endif +// RAVEN END + // Print AAS stats. + virtual void Stats( void ) const = 0; + // Test from the given origin. + virtual void Test( const idVec3 &origin ) = 0; + // Get the AAS settings. + virtual const idAASSettings *GetSettings( void ) const = 0; + // Returns the number of the area the origin is in. + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + // Returns the number of the nearest reachable area for the given point. + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &bounds, const int areaFlags ) const = 0; + // Returns the number of the first reachable area in or touching the bounds. + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const = 0; + // Push the point into the area. + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const = 0; + // Returns a reachable point inside the given area. + virtual idVec3 AreaCenter( int areaNum ) const = 0; +// RAVEN BEGIN +// bdube: added + // Returns a reachable point inside the given area. + virtual float AreaRadius( int areaNum ) const = 0; + virtual idBounds & AreaBounds( int areaNum ) const = 0; + virtual float AreaCeiling( int areaNum ) const = 0; +// RAVEN END + // Returns the area flags. + virtual int AreaFlags( int areaNum ) const = 0; + // Returns the travel flags for traveling through the area. + virtual int AreaTravelFlags( int areaNum ) const = 0; + // Trace through the areas and report the first collision. + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + // Get a plane for a trace. + virtual const idPlane & GetPlane( int planeNum ) const = 0; + // Get wall edges. + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const = 0; + // Sort the wall edges to create continuous sequences of walls. + virtual void SortWallEdges( int *edges, int numEdges ) const = 0; + // Get the vertex numbers for an edge. + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const = 0; + // Get an edge. + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Find all areas within or touching the bounds with the given contents and disable/enable them for routing. + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) = 0; + // Add an obstacle to the routing system. + virtual aasHandle_t AddObstacle( const idBounds &bounds ) = 0; + // Remove an obstacle from the routing system. + virtual void RemoveObstacle( const aasHandle_t handle ) = 0; + // Remove all obstacles from the routing system. + virtual void RemoveAllObstacles( void ) = 0; + // Returns the travel time towards the goal area in 100th of a second. + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const = 0; + // Get the travel time and first reachability to be used towards the goal, returns true if there is a path. + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const = 0; + // Creates a walk path towards the goal. + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can walk along a straight line from the origin to the goal origin. + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Creates a fly path towards the goal. + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can fly along a straight line from the origin to the goal origin. + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Show the walk path from the origin towards the area. + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Show the fly path from the origin towards the area. + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Find the nearest goal which satisfies the callback. + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const = 0; + +// RAVEN BEGIN +// CDR : Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) = 0; +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ) = 0; + +// rjohnson: added more debug drawing + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const = 0; + virtual bool IsValid( void ) const = 0; +// RAVEN END +}; + +#endif /* !__AAS_H__ */ diff --git a/src/game/ai/AAS_Find.cpp b/src/game/ai/AAS_Find.cpp new file mode 100644 index 0000000..f586abc --- /dev/null +++ b/src/game/ai/AAS_Find.cpp @@ -0,0 +1,364 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + +AAS_Find.cpp + +This file has all aas search classes. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + rvAASFindGoalForHide + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForHide::rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::rvAASFindGoalForHide( const idVec3 &hideFromPos ) { + int numPVSAreas; + idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) ); + + // setup PVS + numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS ); + hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForHide::~rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::~rvAASFindGoalForHide() { + gameLocal.pvs.FreeCurrentPVS( hidePVS ); +} + +/* +rvAASFindGoalForHide +rvAASFindHide::TestArea +============ +*/ +bool rvAASFindGoalForHide::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ).TranslateSelf(idVec3(0,0,1)), PVSAreas, idEntity::MAX_PVS_AREAS ); + if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) { + return true; + } + + return false; +} + +/* +=============================================================================== + + rvAASFindGoalOutOfRange + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange +============ +*/ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange( idAI* _owner ) { + owner = _owner; +} + +/* +============ +rvAASFindAreaOutOfRange::TestArea +============ +*/ +bool rvAASFindGoalOutOfRange::TestPoint ( idAAS* aas, const idVec3& pos, const float zAllow ) { + return aiManager.ValidateDestination ( owner, pos ); +} + +/* +=============================================================================== + +rvAASFindGoalForAttack + +Find a position to move to that allows the ai to at their target + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForAttack::rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::rvAASFindGoalForAttack( idAI* _owner ) { + owner = _owner; + cachedIndex = 0; +} + +/* +============ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack ( void ) { +} + +/* +============ +rvAASFindGoalForAttack::Init +============ +*/ +void rvAASFindGoalForAttack::Init ( void ) { + // setup PVS + int numPVSAreas; + numPVSAreas = gameLocal.pvs.GetPVSAreas( owner->enemy.ent->GetPhysics()->GetAbsBounds(), PVSAreas, idEntity::MAX_PVS_AREAS ); + targetPVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); + + cachedGoals.SetGranularity ( 1024 ); +} + +/* +============ +rvAASFindGoalForAttack::Finish +============ +*/ +void rvAASFindGoalForAttack::Finish ( void ) { + gameLocal.pvs.FreeCurrentPVS( targetPVS ); +} + +/* +============ +rvAASFindGoalForAttack::TestArea +============ +*/ +bool rvAASFindGoalForAttack::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + cachedAreaNum = areaNum; + + // If the whole area is out of range then skip it + float range; + range = area.bounds.ShortestDistance ( owner->enemy.lastKnownPosition ); + if ( range > owner->combat.attackRange[1] ) { + return false; + } + + // Out of pvs? + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS ); + return gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForAttack::TestPoint +============ +*/ +bool rvAASFindGoalForAttack::TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow ) { + float dist; + + idVec3 localPoint = point; + float bestZ = owner->enemy.ent->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + + // Out of attack range? + dist = (localPoint - owner->enemy.ent->GetPhysics()->GetOrigin ( )).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // If tethered make sure the point is within the tether range + if ( owner->tether ) { + idVec3 localPoint = point; + float bestZ = owner->tether.GetEntity()->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !owner->tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + } + + aasGoal_t& goal = cachedGoals.Alloc ( ); + goal.areaNum = cachedAreaNum; + goal.origin = localPoint; + + return false; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedGoal +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoal ( int index ) { + const aasGoal_t& goal = cachedGoals[index]; + + // Out of attack range? + float dist = (goal.origin - owner->enemy.ent->GetPhysics()->GetOrigin ( ) ).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // Someone already there? + if ( !aiManager.ValidateDestination ( owner, goal.origin, true ) ) { + return false; + } + + // Can we see the enemy from this position? + if ( !owner->CanSeeFrom ( goal.origin - owner->GetPhysics()->GetGravityNormal ( ) * owner->combat.visStandHeight, owner->GetEnemy(), false ) ) { + return false; + } + + return true; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedPoints +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoals ( int count, aasGoal_t& goal ) { + int i; + + goal.areaNum = 0; + + // Test as many points as we are allowed to test + for ( i = 0; i < count && cachedIndex < cachedGoals.Num(); cachedIndex ++, i ++ ) { + // Retest simple checks + if ( TestCachedGoal( cachedIndex ) ) { + goal = cachedGoals[cachedIndex]; + return true; + } + } + + return !(cachedIndex >= cachedGoals.Num()); +} + +/* +=============================================================================== + +rvAASFindGoalForTether + +Find a goal to move to that is within the given tether. + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::rvAASFindGoalForTether( idAI* _owner, rvAITether* _tether ) { + owner = _owner; + tether = _tether; +} + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::~rvAASFindGoalForTether( void ) { +} + +/* +============ +rvAASFindGoalForTether::TestArea +============ +*/ +bool rvAASFindGoalForTether::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + // Test super class first + if ( !idAASCallback::TestArea ( aas, areaNum, area ) ) { + return false; + } + + // Make sure the area bounds is remotely valid for the tether + idBounds tempBounds = area.bounds; + tempBounds[1].z = area.ceiling; + if ( !tether->ValidateBounds ( tempBounds ) ) { + return false; + } + return true; +} + +/* +============ +rvAASFindGoalForTether::TestPoint +============ +*/ +bool rvAASFindGoalForTether::TestPoint ( class idAAS* aas, const idVec3& point, const float zAllow ) { + if ( !tether ) { + return false; + } + + idVec3 localPoint = point; + float bestZ = tether->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + + if ( !aiManager.ValidateDestination ( owner, localPoint ) ) { + return false; + } + + return true; +} diff --git a/src/game/ai/AAS_Find.h b/src/game/ai/AAS_Find.h new file mode 100644 index 0000000..c4dbb44 --- /dev/null +++ b/src/game/ai/AAS_Find.h @@ -0,0 +1,136 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AAS_Find.h + +================ +*/ + +#ifndef __AAS_FIND__ +#define __AAS_FIND__ + +class idAI; +class rvAIHelper; + +/* +=============================================================================== + rvAASFindHide +=============================================================================== +*/ + +class rvAASFindGoalForHide : public idAASCallback { +public: + rvAASFindGoalForHide ( const idVec3 &hideFromPos ); + ~rvAASFindGoalForHide ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + +private: + + pvsHandle_t hidePVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; +}; + +/* +=============================================================================== + rvAASFindAreaOutOfRange +=============================================================================== +*/ + +class rvAASFindGoalOutOfRange : public idAASCallback { +public: + + rvAASFindGoalOutOfRange ( idAI* _owner ); + +protected: + + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + +private: + + idAI* owner; +}; + +/* +=============================================================================== + rvAASFindAttackPosition +=============================================================================== +*/ + +class rvAASFindGoalForAttack : public idAASCallback { +public: + rvAASFindGoalForAttack ( idAI *self ); + ~rvAASFindGoalForAttack ( void ); + + + bool TestCachedGoals ( int count, aasGoal_t& goal ); + + virtual void Init ( void ); + virtual void Finish ( void ); + +private: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + + bool TestCachedGoal ( int index ); + + idAI* owner; + + pvsHandle_t targetPVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + idList cachedGoals; + int cachedIndex; + int cachedAreaNum; +}; + +/* +=============================================================================== + rvAASFindGoalForTether +=============================================================================== +*/ + +class rvAASFindGoalForTether : public idAASCallback { +public: + rvAASFindGoalForTether ( idAI* owner, rvAITether* helper ); + ~rvAASFindGoalForTether ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS* aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + idAI* owner; + rvAITether* tether; +}; + +#endif // __AAS_FIND__ diff --git a/src/game/ai/AAS_debug.cpp b/src/game/ai/AAS_debug.cpp new file mode 100644 index 0000000..cdb7880 --- /dev/null +++ b/src/game/ai/AAS_debug.cpp @@ -0,0 +1,932 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for cvars and debug drawing +#include "AI.h" +#include "AAS_Find.h" + + +/* +============ +idAASLocal::DrawCone +============ +*/ +void idAASLocal::DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const { + int i; + idMat3 axis; + idVec3 center, top, p, lastp; + + axis[2] = dir; + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + + center = origin + dir; + top = center + dir * (3.0f * radius); + lastp = center + radius * axis[1]; + + for ( i = 20; i <= 360; i += 20 ) { + p = center + idMath::Sin( DEG2RAD(i) ) * radius * axis[0] + idMath::Cos( DEG2RAD(i) ) * radius * axis[1]; + gameRenderWorld->DebugLine( color, lastp, p, 0 ); + gameRenderWorld->DebugLine( color, p, top, 0 ); + lastp = p; + } +} + +/* +============ +idAASLocal::DrawReachability +============ +*/ +void idAASLocal::DrawReachability( const idReachability *reach ) const { + gameRenderWorld->DebugArrow( colorCyan, reach->start, reach->end, 2 ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", reach->edgeNum ), ( reach->start + reach->end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis ); + } + + switch( reach->travelType ) { + case TFL_WALK: { +// const idReachability_Walk *walk = static_cast(reach); + break; + } + default: { + break; + } + } +} + +/* +============ +idAASLocal::DrawEdge +============ +*/ +void idAASLocal::DrawEdge( int edgeNum, bool arrow ) const { + const aasEdge_t *edge; + idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &colorRed; + if ( arrow ) { + gameRenderWorld->DebugArrow( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ), 1 ); + } else { + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); + } + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", edgeNum ), ( file->GetVertex( edge->vertexNum[0] ) + file->GetVertex( edge->vertexNum[1] ) ) * 0.5f + idVec3(0,0,4), 0.1f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } +} + +/* +============ +idAASLocal::DrawFace +============ +*/ +void idAASLocal::DrawFace( int faceNum, bool side ) const { + int i, j, numEdges, firstEdge; + const aasFace_t *face; + idVec3 mid, end; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + if ( !numEdges ) + {//wtf? A face with no edges?! + return; + } + + mid = vec3_origin; + for ( i = 0; i < numEdges; i++ ) { + DrawEdge( abs( file->GetEdgeIndex( firstEdge + i ) ), ( face->flags & FACE_FLOOR ) != 0 ); + j = file->GetEdgeIndex( firstEdge + i ); + mid += file->GetVertex( file->GetEdge( abs( j ) ).vertexNum[ j < 0 ] ); + } + + mid /= numEdges; + if ( side ) { + end = mid - 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } else { + end = mid + 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } + gameRenderWorld->DebugArrow( colorGreen, mid, end, 1 ); +} + +/* +============ +idAASLocal::DrawAreaBounds +============ +*/ +void idAASLocal::DrawAreaBounds( int areaNum ) const { + const aasArea_t *area; + if ( !file ) { + return; + } + area = &file->GetArea( areaNum ); + + idVec3 points[8]; + bool drawn[8][8]; + memset( drawn, false, sizeof( drawn ) ); + + area->bounds.ToPoints( points ); + for ( int p1 = 0; p1 < 8; p1++ ) { + for ( int p2 = 0; p2 < 8; p2++ ) { + if ( !drawn[p2][p1] ) { + if ( (points[p1].x == points[p2].x && (points[p1].y == points[p2].y||points[p1].z == points[p2].z)) + || (points[p1].y == points[p2].y && (points[p1].x == points[p2].x||points[p1].z == points[p2].z)) + || (points[p1].z == points[p2].z && (points[p1].x == points[p2].x||points[p1].y == points[p2].y)) ) { + //an edge + gameRenderWorld->DebugLine( colorRed, points[p1], points[p2] ); + drawn[p1][p2] = true; + } + } + } + } +} + +/* +============ +idAASLocal::DrawArea +============ +*/ +void idAASLocal::DrawArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idReachability *reach; + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawFace( abs( file->GetFaceIndex( firstFace + i ) ), file->GetFaceIndex( firstFace + i ) < 0 ); + } + + if (aas_showRevReach.GetInteger()) + { + for ( reach = area->rev_reach; reach; reach = reach->rev_next ) { + DrawReachability( reach ); + } + } + else + { + for ( reach = area->reach; reach; reach = reach->next ) { + DrawReachability( reach ); + } + } +} + +/* +============ +idAASLocal::DefaultSearchBounds +============ +*/ +const idBounds &idAASLocal::DefaultSearchBounds( void ) const { + return file->GetSettings().boundingBoxes[0]; +} + +/* +============ +idAASLocal::ShowArea +============ +*/ +void idAASLocal::ShowArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + const aasArea_t *area; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + org = origin; + PushPointIntoAreaNum( areaNum, org ); + + if ( aas_goalArea.GetInteger() ) { + int travelTime; + idReachability *reach; + + RouteToGoalArea( areaNum, org, aas_goalArea.GetInteger(), TFL_WALK|TFL_AIR, travelTime, &reach ); + gameLocal.Printf( "\rtt = %4d", travelTime ); + if ( reach ) { + gameLocal.Printf( " to area %4d", reach->toAreaNum ); + DrawArea( reach->toAreaNum ); + } + } + + if ( areaNum != lastAreaNum ) { + area = &file->GetArea( areaNum ); + gameLocal.Printf( "area %d: ", areaNum ); + if ( area->flags & AREA_LEDGE ) { + gameLocal.Printf( "AREA_LEDGE " ); + } + if ( area->flags & AREA_REACHABLE_WALK ) { + gameLocal.Printf( "AREA_REACHABLE_WALK " ); + } + if ( area->flags & AREA_REACHABLE_FLY ) { + gameLocal.Printf( "AREA_REACHABLE_FLY " ); + } + if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) { + gameLocal.Printf( "AREACONTENTS_CLUSTERPORTAL " ); + } + if ( area->contents & AREACONTENTS_OBSTACLE ) { + gameLocal.Printf( "AREACONTENTS_OBSTACLE " ); + } + gameLocal.Printf( "\n" ); + lastAreaNum = areaNum; + } + + if ( org != origin ) { + idBounds bnds = file->GetSettings().boundingBoxes[ 0 ]; + bnds[ 1 ].z = bnds[ 0 ].z; + gameRenderWorld->DebugBounds( colorYellow, bnds, org ); + } + + DrawArea( areaNum ); +} + +// RAVEN BEGIN +// rjohnson: added more debug drawing +/* +============ +idAASLocal::DrawSimpleEdge +============ +*/ +void idAASLocal::DrawSimpleEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &file->GetSettings().debugColor; + + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); +} + +/* +============ +idAASLocal::DrawSimpleFace +============ +*/ +const int MAX_AAS_WALL_EDGES = 256; + +// RAVEN BEGIN +// cdr: added visited check +void idAASLocal::DrawSimpleFace( int faceNum, bool visited ) const { +// RAVEN END + int i, numEdges, firstEdge, edgeNum; + const aasFace_t *face; + idVec3 sides[MAX_AAS_WALL_EDGES]; + const aasEdge_t *edge, *nextEdge; + idVec4 color2; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + DrawSimpleEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } + + if ( numEdges >= 2 && numEdges <= MAX_AAS_WALL_EDGES ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + edgeNum = abs( file->GetEdgeIndex( firstEdge + 1 ) ); + nextEdge = &file->GetEdge( abs( edgeNum ) ); + + // need to find the first common edge so that we go form the polygon in the right direction + if ( file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[0] ) || + file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[1] ) ) { + sides[ 0 ] = file->GetVertex( edge->vertexNum[0] ); + } else { + sides[ 0 ] = file->GetVertex( edge->vertexNum[1] ); + } + + for ( i = 1; i < numEdges; i++ ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge + i ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + + if ( sides[ i-1 ] == file->GetVertex( edge->vertexNum[0] ) ) { + sides[ i ] = file->GetVertex( edge->vertexNum[1] ); + } else { + sides[ i ] = file->GetVertex( edge->vertexNum[0] ); + } + } + + color2 = file->GetSettings().debugColor; + color2[3] = 0.20f; + + // RAVEN BEGIN + // cdr: added visited check + if (!visited) + { + color2[3] = 0.05f; + } + // RAVEN END + idWinding winding( sides, numEdges ); + gameRenderWorld->DebugPolygon( color2, winding, 0, true ); + } +} + + +/* +============ +idAASLocal::DrawSimpleArea +============ +*/ +void idAASLocal::DrawSimpleArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawSimpleFace( abs( file->GetFaceIndex( firstFace + i )), true ); + } +} + +/* +============ +idAASLocal::IsValid +============ +*/ +bool idAASLocal::IsValid( void ) const { + if ( !file ) { + return false; + } + + return true; +} + +/* +============ +idAASLocal::ShowAreas +============ +*/ +void idAASLocal::ShowAreas( const idVec3 &origin, bool ShowProblemAreas ) const { + int i, areaNum; + idPlayer *player; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + idReachability *reach; + int travelFlags = (TFL_WALK); + const idBounds bounds = idBounds( origin ).Expand( 256.0f ); + + if ( !file ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = 0; + queueEnd = 1; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + area = &file->GetArea( curArea ); + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + + if ( ShowProblemAreas ) { + for ( i = 0; i < queueEnd; i++ ) { + ShowProblemArea( areaQueue[ i ] ); + } + } else { + for ( i = 0; i < queueEnd; i++ ) { + DrawSimpleArea( areaQueue[ i ] ); + } + } +} + +/* +============ +idAASLocal::ShowProblemEdge +============ +*/ +void idAASLocal::ShowProblemEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + idVec4 color2; + idTraceModel trm; + idClipModel mdl; + idVec3 sides[4]; + trace_t results; + idVec3 forward, forwardNormal, left, down, start, end; + float hullSize, hullHeight; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + + start = (idVec3)file->GetVertex( edge->vertexNum[0] ); + end = (idVec3)file->GetVertex( edge->vertexNum[1] ); + forward = end - start ; + forward.Normalize(); + forwardNormal = forward; + forward.NormalVectors( left, down ); + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ) - 1.0f; // assumption that x and y are the same size + hullHeight = ( file->GetSettings().boundingBoxes[0][1][2] - file->GetSettings().boundingBoxes[0][0][2] ); + + left *= hullSize; + forward *= hullSize; + + sides[0] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[1] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[2] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + sides[3] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + trm.SetupPolygon( sides, 4 ); + mdl.LoadModel( trm, NULL ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( gameLocal.GetLocalPlayer(), results, start, end, &mdl, mat3_identity, MASK_MONSTERSOLID, gameLocal.GetLocalPlayer() ); +// RAVEN END + + if ( results.fraction != 1.0f ) { + color = &file->GetSettings().debugColor; + gameRenderWorld->DebugLine( *color, start - left, end - left ); + gameRenderWorld->DebugLine( *color, start + left, end + left ); + + color = &colorYellow; + gameRenderWorld->DebugLine( *color, start, end ); + + color2 = colorOrange; + color2[3] = 0.25f; + sides[0] += results.endpos; + sides[1] += results.endpos; + sides[2] += results.endpos; + sides[3] += results.endpos; + idWinding winding( sides, 4 ); + gameRenderWorld->DebugPolygon( color2, winding ); + } +} + +/* +============ +idAASLocal::ShowProblemFace +============ +*/ +void idAASLocal::ShowProblemFace( int faceNum ) const { + int i, numEdges, firstEdge; + const aasFace_t *face; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + ShowProblemEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + float hullSize; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + numFaces = area->numFaces; + firstFace = area->firstFace; + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ); // assumption that x and y are the same size + + bounds = area->bounds; + bounds.Expand( hullSize ); +// RAVEN BEGIN +// ddynerman: multiple clip world + numListedEntities = gameLocal.EntitiesTouchingBounds( gameLocal.GetLocalPlayer(), bounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entityList[ i ]->IsType( idMoveable::GetClassType() ) || + entityList[ i ]->IsType( idMover::GetClassType() ) || + entityList[ i ]->IsType( idAI::GetClassType() ) ) { +// RAVEN END + entityList[ i ]->GetPhysics()->DisableClip(); + continue; + } + + entityList[ i ] = NULL; + } + + for ( i = 0; i < numFaces; i++ ) { + ShowProblemFace( abs( file->GetFaceIndex( firstFace + i ) ) ); + } + + for( i = 0; i < numListedEntities; i++) { + if ( entityList[ i ] ) { + entityList[ i ]->GetPhysics()->EnableClip(); + } + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + ShowProblemArea( areaNum ); +} + +// RAVEN END + +/* +============ +idAASLocal::ShowWalkPath +============ +*/ +void idAASLocal::ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_WALK ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorGreen, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( WalkPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowFlyPath +============ +*/ +void idAASLocal::ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_FLY ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_FLY|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorPurple, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( FlyPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_FLY|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowWallEdges +============ +*/ +void idAASLocal::ShowWallEdges( const idVec3 &origin ) const { + int i, areaNum, numEdges, edges[1024]; + idVec3 start, end; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + numEdges = GetWallEdges( areaNum, idBounds( origin ).Expand( 256.0f ), TFL_WALK, edges, 1024 ); + for ( i = 0; i < numEdges; i++ ) { + GetEdge( edges[i], start, end ); + gameRenderWorld->DebugLine( colorRed, start, end ); + gameRenderWorld->DrawText( va( "%d", edges[i] ), ( start + end ) * 0.5f, 0.1f, colorWhite, player->viewAxis ); + } +} + +/* +============ +idAASLocal::ShowHideArea +============ +*/ +void idAASLocal::ShowHideArea( const idVec3 &origin, int targetAreaNum ) const { + int areaNum, numObstacles; + idVec3 target; + aasGoal_t goal; + aasObstacle_t obstacles[10]; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + target = AreaCenter( targetAreaNum ); + + // consider the target an obstacle + obstacles[0].absBounds = idBounds( target ).Expand( 16 ); + numObstacles = 1; + + DrawCone( target, idVec3(0,0,1), 16.0f, colorYellow ); + + rvAASFindGoalForHide findHide ( target ); + if ( FindNearestGoal( goal, areaNum, origin, target, TFL_WALK|TFL_AIR, 0.0f, 0.0f, obstacles, numObstacles, findHide ) ) { + DrawArea( goal.areaNum ); + ShowWalkPath( origin, goal.areaNum, goal.origin ); + DrawCone( goal.origin, idVec3(0,0,1), 16.0f, colorWhite ); + } +} + +/* +============ +idAASLocal::PullPlayer +============ +*/ +bool idAASLocal::PullPlayer( const idVec3 &origin, int toAreaNum ) const { + int areaNum; + idVec3 areaCenter, dir, vel; + idAngles delta; + aasPath_t path; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return true; + } + + idPhysics *physics = player->GetPhysics(); + if ( !physics ) { + return true; + } + + if ( !toAreaNum ) { + return false; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + areaCenter = AreaCenter( toAreaNum ); + if ( player->GetPhysics()->GetAbsBounds().Expand( 8 ).ContainsPoint( areaCenter ) ) { + return false; + } + if ( WalkPathToGoal( path, areaNum, origin, toAreaNum, areaCenter, TFL_WALK|TFL_AIR ) ) { + dir = path.moveGoal - origin; + dir[2] *= 0.5f; + dir.Normalize(); + delta = dir.ToAngles() - player->cmdAngles - player->GetDeltaViewAngles(); + delta.Normalize180(); + player->SetDeltaViewAngles( player->GetDeltaViewAngles() + delta * 0.1f ); + dir[2] = 0.0f; + dir.Normalize(); + dir *= 100.0f; + vel = physics->GetLinearVelocity(); + dir[2] = vel[2]; + physics->SetLinearVelocity( dir ); + return true; + } + else { + return false; + } +} + +/* +============ +idAASLocal::RandomPullPlayer +============ +*/ +void idAASLocal::RandomPullPlayer( const idVec3 &origin ) const { + int rnd, i, n; + + if ( !PullPlayer( origin, aas_pullPlayer.GetInteger() ) ) { + + rnd = gameLocal.random.RandomFloat() * file->GetNumAreas(); + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + n = (rnd + i) % file->GetNumAreas(); + if ( file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) { + aas_pullPlayer.SetInteger( n ); + } + } + } else { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + } +} + +/* +============ +idAASLocal::ShowPushIntoArea +============ +*/ +void idAASLocal::ShowPushIntoArea( const idVec3 &origin ) const { + int areaNum; + idVec3 target; + + target = origin; + areaNum = PointReachableAreaNum( target, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + PushPointIntoAreaNum( areaNum, target ); + gameRenderWorld->DebugArrow( colorGreen, origin, target, 1 ); +} + +/* +============ +idAASLocal::Test +============ +*/ +void idAASLocal::Test( const idVec3 &origin ) { + + if ( !file ) { + return; + } + + if ( aas_randomPullPlayer.GetBool() ) { + RandomPullPlayer( origin ); + } + if ( ( aas_pullPlayer.GetInteger() > 0 ) && ( aas_pullPlayer.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + PullPlayer( origin, aas_pullPlayer.GetInteger() ); + } + if ( ( aas_showPath.GetInteger() > 0 ) && ( aas_showPath.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_showPath.GetInteger(), AreaCenter( aas_showPath.GetInteger() ) ); + } + if ( ( aas_showFlyPath.GetInteger() > 0 ) && ( aas_showFlyPath.GetInteger() < file->GetNumAreas() ) ) { + ShowFlyPath( origin, aas_showFlyPath.GetInteger(), AreaCenter( aas_showFlyPath.GetInteger() ) ); + } + if ( ( aas_showHideArea.GetInteger() > 0 ) && ( aas_showHideArea.GetInteger() < file->GetNumAreas() ) ) { + ShowHideArea( origin, aas_showHideArea.GetInteger() ); + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 1 ) { + ShowArea( origin ); + } + else if ( aas_showAreas.GetInteger() == 2 ) { + ShowAreas( origin ); + } + if ( aas_showProblemAreas.GetInteger() == 1 ) { + ShowProblemArea( origin ); + } + else if ( aas_showProblemAreas.GetInteger() == 2 ) { + ShowAreas( origin, true ); + } +// RAVEN END + if ( aas_showWallEdges.GetBool() ) { + ShowWallEdges( origin ); + } + if ( aas_showPushIntoArea.GetBool() ) { + ShowPushIntoArea( origin ); + } +} diff --git a/src/game/ai/AAS_local.h b/src/game/ai/AAS_local.h new file mode 100644 index 0000000..8c7f92d --- /dev/null +++ b/src/game/ai/AAS_local.h @@ -0,0 +1,216 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AAS_LOCAL_H__ +#define __AAS_LOCAL_H__ + +#include "AAS.h" +#include "../Pvs.h" + + +class idRoutingCache { + friend class idAASLocal; + +public: + idRoutingCache( int size ); + ~idRoutingCache( void ); + + int Size( void ) const; + +private: + int type; // portal or area cache + int size; // size of cache + int cluster; // cluster of the cache + int areaNum; // area of the cache + int travelFlags; // combinations of the travel flags + idRoutingCache * next; // next in list + idRoutingCache * prev; // previous in list + idRoutingCache * time_next; // next in time based list + idRoutingCache * time_prev; // previous in time based list + unsigned short startTravelTime; // travel time to start with + unsigned char * reachabilities; // reachabilities used for routing + unsigned short * travelTimes; // travel time for every area +}; + + +class idRoutingUpdate { + friend class idAASLocal; + +private: + int cluster; // cluster number of this update + int areaNum; // area number of this update + unsigned short tmpTravelTime; // temporary travel time + unsigned short * areaTravelTimes; // travel times within the area + idVec3 start; // start point into area + idRoutingUpdate * next; // next in list + idRoutingUpdate * prev; // prev in list + bool isInList; // true if the update is in the list +}; + + +class idRoutingObstacle { + friend class idAASLocal; + idRoutingObstacle( void ) { } + +private: + idBounds bounds; // obstacle bounds + idList areas; // areas the bounds are in +}; + +class idAASLocal : public idAAS { +public: + idAASLocal( void ); + virtual ~idAASLocal( void ); + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ); + virtual void Shutdown( void ); +// RAVEN BEGIN +// jscott: added summary flag + virtual size_t StatsSummary( void ) const; +// RAVEN END + virtual void Stats( void ) const; + virtual void Test( const idVec3 &origin ); + virtual const idAASSettings *GetSettings( void ) const; + virtual int PointAreaNum( const idVec3 &origin ) const; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const; + virtual idVec3 AreaCenter( int areaNum ) const; +// RAVEN BEGIN +// bdube: added + virtual float AreaRadius( int areaNum ) const; + virtual idBounds & AreaBounds( int areaNum ) const; + virtual float AreaCeiling( int areaNum ) const; +// RAVEN END + virtual int AreaFlags( int areaNum ) const; + virtual int AreaTravelFlags( int areaNum ) const; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual const idPlane & GetPlane( int planeNum ) const; + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const; + virtual void SortWallEdges( int *edges, int numEdges ) const; + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const; + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const; + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ); + virtual aasHandle_t AddObstacle( const idBounds &bounds ); + virtual void RemoveObstacle( const aasHandle_t handle ); + virtual void RemoveAllObstacles( void ); + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const; + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const; + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const; +// RAVEN BEGIN +// creed: Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) {return file;} +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ); +// rjohnson: added more debug drawing + virtual bool IsValid( void ) const; + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const; +// RAVEN END + + +private: + idAASFile * file; + idStr name; + +private: // routing data + idRoutingCache *** areaCacheIndex; // for each area in each cluster the travel times to all other areas in the cluster + int areaCacheIndexSize; // number of area cache entries + idRoutingCache ** portalCacheIndex; // for each area in the world the travel times from each portal + int portalCacheIndexSize; // number of portal cache entries + idRoutingUpdate * areaUpdate; // memory used to update the area routing cache + idRoutingUpdate * portalUpdate; // memory used to update the portal routing cache + unsigned short * goalAreaTravelTimes; // travel times to goal areas + unsigned short * areaTravelTimes; // travel times through the areas + int numAreaTravelTimes; // number of area travel times + mutable idRoutingCache * cacheListStart; // start of list with cache sorted from oldest to newest + mutable idRoutingCache * cacheListEnd; // end of list with cache sorted from oldest to newest + mutable int totalCacheMemory; // total cache memory used + idList obstacleList; // list with obstacles + +private: // routing + bool SetupRouting( void ); + void ShutdownRouting( void ); + unsigned short AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const; + void CalculateAreaTravelTimes( void ); + void DeleteAreaTravelTimes( void ); + void SetupRoutingCache( void ); + void DeleteClusterCache( int clusterNum ); + void DeletePortalCache( void ); + void ShutdownRoutingCache( void ); + void RoutingStats( void ) const; + void LinkCache( idRoutingCache *cache ) const; + void UnlinkCache( idRoutingCache *cache ) const; + void DeleteOldestCache( void ) const; + idReachability * GetAreaReachability( int areaNum, int reachabilityNum ) const; + int ClusterAreaNum( int clusterNum, int areaNum ) const; + void UpdateAreaRoutingCache( idRoutingCache *areaCache ) const; + idRoutingCache * GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void UpdatePortalRoutingCache( idRoutingCache *portalCache ) const; + idRoutingCache * GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void RemoveRoutingCacheUsingArea( int areaNum ); + void DisableArea( int areaNum ); + void EnableArea( int areaNum ); + bool SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ); + void GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const; + void SetObstacleState( const idRoutingObstacle *obstacle, bool enable ); + +private: // pathing + bool EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const; + bool FloorEdgeSplitPoint( idVec3 &split, int areaNum, const idPlane &splitPlane, const idPlane &frontPlane, bool closest ) const; + idVec3 SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + idVec3 SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + +private: // debug + const idBounds & DefaultSearchBounds( void ) const; + void DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const; + void DrawAreaBounds( int areaNum ) const; + void DrawArea( int areaNum ) const; + void DrawFace( int faceNum, bool side ) const; + void DrawEdge( int edgeNum, bool arrow ) const; + void DrawReachability( const idReachability *reach ) const; + void ShowArea( const idVec3 &origin ) const; + void ShowWallEdges( const idVec3 &origin ) const; + void ShowHideArea( const idVec3 &origin, int targerAreaNum ) const; + bool PullPlayer( const idVec3 &origin, int toAreaNum ) const; + void RandomPullPlayer( const idVec3 &origin ) const; + void ShowPushIntoArea( const idVec3 &origin ) const; + +// RAVEN BEGIN +// rjohnson: added more debug drawing + void DrawSimpleEdge( int edgeNum ) const; + void DrawSimpleFace( int faceNum, bool visited ) const; + void DrawSimpleArea( int areaNum ) const; + void ShowProblemEdge( int edgeNum ) const; + void ShowProblemFace( int faceNum ) const; + void ShowProblemArea( int areaNum ) const; + void ShowProblemArea( const idVec3 &origin ) const; +// RAVEN END +}; + +#endif /* !__AAS_LOCAL_H__ */ diff --git a/src/game/ai/AAS_pathing.cpp b/src/game/ai/AAS_pathing.cpp new file mode 100644 index 0000000..efbe667 --- /dev/null +++ b/src/game/ai/AAS_pathing.cpp @@ -0,0 +1,757 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +#define SUBSAMPLE_WALK_PATH 1 +#define SUBSAMPLE_FLY_PATH 0 + +const int maxWalkPathIterations = 10; +const float maxWalkPathDistance = 500.0f; +const float walkPathSampleDistance = 8.0f; + +const int maxFlyPathIterations = 10; +const float maxFlyPathDistance = 500.0f; +const float flyPathSampleDistance = 8.0f; + + +/* +============ +idAASLocal::EdgeSplitPoint + + calculates split point of the edge with the plane + returns true if the split point is between the edge vertices +============ +*/ +bool idAASLocal::EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const { + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + edge = &file->GetEdge( edgeNum ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * plane.Normal() - plane.Dist(); + d2 = v2 * plane.Normal() - plane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + return false; + } + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + return true; +} + +// RAVEN BEGIN +// rjohnson: optimized function +/* +============ +idAASLocal::FloorEdgeSplitPoint + + calculates either the closest or furthest point on the floor of the area which also lies on the pathPlane + the point has to be on the front side of the frontPlane to be valid +============ +*/ +bool idAASLocal::FloorEdgeSplitPoint( idVec3 &bestSplit, int areaNum, const idPlane &pathPlane, const idPlane &frontPlane, bool closest ) const { + int i, j, faceNum, edgeNum; + const aasArea_t *area; + const aasFace_t *face; + idVec3 split; + float dist, bestDist; + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + area = &file->GetArea( areaNum ); + if ( closest ) { + bestDist = maxWalkPathDistance; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist >= -0.1f && dist < bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist < maxWalkPathDistance ); + + } else { + bestDist = -0.1f; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist > bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist > -0.1f ); + } +} +// RAVEN END + +/* +============ +idAASLocal::WalkPathValid + + returns true if one can walk in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + int curAreaNum, lastAreaNum, lastAreas[4], lastAreaIndex; + idPlane pathPlane, frontPlane, farPlane; + idReachability *reach; + const aasArea_t *area; + idVec3 p, dir; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + pathPlane.SetNormal( (goalOrigin - origin).Cross( file->GetSettings().gravityDir ) ); + pathPlane.Normalize(); + pathPlane.FitThroughPoint( origin ); + + frontPlane.SetNormal( goalOrigin - origin ); + frontPlane.Normalize(); + frontPlane.FitThroughPoint( origin ); + + farPlane.SetNormal( frontPlane.Normal() ); + farPlane.FitThroughPoint( goalOrigin ); + + curAreaNum = areaNum; + lastAreaNum = curAreaNum; + + while ( 1 ) { + + // find the furthest floor face split point on the path + if ( !FloorEdgeSplitPoint( endPos, curAreaNum, pathPlane, frontPlane, false ) ) { + endPos = origin; + } + + // if we found a point near or further than the goal we're done + if ( farPlane.Distance( endPos ) > -0.5f ) { + break; + } + + // if we reached the goal area we're done + if ( curAreaNum == goalAreaNum ) { + break; + } + + frontPlane.SetDist( frontPlane.Normal() * endPos ); + + area = &file->GetArea( curAreaNum ); + + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType != TFL_WALK ) { + continue; + } + + // if the reachability goes back to a previous area + if ( reach->toAreaNum == lastAreas[0] || reach->toAreaNum == lastAreas[1] || + reach->toAreaNum == lastAreas[2] || reach->toAreaNum == lastAreas[3] ) { + continue; + } + + // if undesired travel flags are required to travel through the area + if ( file->GetArea( reach->toAreaNum ).travelFlags & ~travelFlags ) { + continue; + } + + // don't optimize through an area near a ledge + if ( file->GetArea( reach->toAreaNum ).flags & AREA_LEDGE ) { + continue; + } + + // find the closest floor face split point on the path + if ( !FloorEdgeSplitPoint( p, reach->toAreaNum, pathPlane, frontPlane, true ) ) { + continue; + } + + // direction parallel to gravity + dir = ( file->GetSettings().gravityDir * endPos * file->GetSettings().gravityDir ) - + ( file->GetSettings().gravityDir * p * file->GetSettings().gravityDir ); + if ( dir.LengthSqr() > Square( file->GetSettings().maxStepHeight ) ) { + continue; + } + + // direction orthogonal to gravity + dir = endPos - p - dir; + if ( dir.LengthSqr() > Square( 0.2f ) ) { + continue; + } + + break; + } + + if ( !reach ) { + return false; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + } + + endAreaNum = curAreaNum; + + return true; +} + +/* +============ +idAASLocal::SubSampleWalkPath +============ +*/ +idVec3 idAASLocal::SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / walkPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxWalkPathDistance ) ) { + return point; + } + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::WalkPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxWalkPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.reachability = reach; + // RAVEN END + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + // only optimize a limited distance ahead + if ( (reach->start - origin).LengthSqr() > Square( maxWalkPathDistance ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( reach->travelType != TFL_WALK ) { + break; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::WalkPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + switch( reach->travelType ) { + case TFL_WALKOFFLEDGE: + path.type = PATHTYPE_WALKOFFLEDGE; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_BARRIERJUMP: + path.type |= PATHTYPE_BARRIERJUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_JUMP: + path.type |= PATHTYPE_JUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + default: + break; + } + + return true; +} + +/* +============ +idAASLocal::FlyPathValid + + returns true if one can fly in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + aasTrace_t trace; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + file->Trace( trace, origin, goalOrigin ); + + endPos = trace.endpos; + endAreaNum = trace.lastAreaNum; + + if ( trace.fraction >= 1.0f ) { + return true; + } + + return false; +} + +/* +============ +idAASLocal::SubSampleFlyPath +============ +*/ +idVec3 idAASLocal::SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / flyPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxFlyPathDistance ) ) { + return point; + } + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::FlyPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxFlyPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + if ( (reach->start - origin).LengthSqr() > Square( maxFlyPathDistance ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::FlyPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + return true; +} + +typedef struct wallEdge_s { + int edgeNum; + int verts[2]; + struct wallEdge_s * next; +} wallEdge_t; + +/* +============ +idAASLocal::SortWallEdges +============ +*/ +void idAASLocal::SortWallEdges( int *edges, int numEdges ) const { + int i, j, k, numSequences; + wallEdge_t **sequenceFirst, **sequenceLast, *wallEdges, *wallEdge; + + wallEdges = (wallEdge_t *) _alloca16( numEdges * sizeof( wallEdge_t ) ); + sequenceFirst = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + sequenceLast = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + + for ( i = 0; i < numEdges; i++ ) { + wallEdges[i].edgeNum = edges[i]; + GetEdgeVertexNumbers( edges[i], wallEdges[i].verts ); + wallEdges[i].next = NULL; + sequenceFirst[i] = &wallEdges[i]; + sequenceLast[i] = &wallEdges[i]; + } + numSequences = numEdges; + + for ( i = 0; i < numSequences; i++ ) { + for ( j = i+1; j < numSequences; j++ ) { + if ( sequenceFirst[i]->verts[0] == sequenceLast[j]->verts[1] ) { + sequenceLast[j]->next = sequenceFirst[i]; + sequenceFirst[i] = sequenceFirst[j]; + break; + } + if ( sequenceLast[i]->verts[1] == sequenceFirst[j]->verts[0] ) { + sequenceLast[i]->next = sequenceFirst[j]; + break; + } + } + if ( j < numSequences ) { + numSequences--; + for ( k = j; k < numSequences; k++ ) { + sequenceFirst[k] = sequenceFirst[k+1]; + sequenceLast[k] = sequenceLast[k+1]; + } + i = -1; + } + } + + k = 0; + for ( i = 0; i < numSequences; i++ ) { + for ( wallEdge = sequenceFirst[i]; wallEdge; wallEdge = wallEdge->next ) { + edges[k++] = wallEdge->edgeNum; + } + } +} + +/* +============ +idAASLocal::GetWallEdges +============ +*/ +int idAASLocal::GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const { + int i, j, k, l, face1Num, face2Num, edge1Num, edge2Num, numEdges, absEdge1Num; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + const aasFace_t *face1, *face2; + idReachability *reach; + + if ( !file ) { + return 0; + } + + numEdges = 0; + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = -1; + queueEnd = 0; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + area = &file->GetArea( curArea ); + + for ( i = 0; i < area->numFaces; i++ ) { + face1Num = file->GetFaceIndex( area->firstFace + i ); + face1 = &file->GetFace( abs(face1Num) ); + + if ( !(face1->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = 0; j < face1->numEdges; j++ ) { + edge1Num = file->GetEdgeIndex( face1->firstEdge + j ); + absEdge1Num = abs( edge1Num ); + + // test if the edge is shared by another floor face of this area + for ( k = 0; k < area->numFaces; k++ ) { + if ( k == i ) { + continue; + } + face2Num = file->GetFaceIndex( area->firstFace + k ); + face2 = &file->GetFace( abs(face2Num) ); + + if ( !(face2->flags & FACE_FLOOR ) ) { + continue; + } + + for ( l = 0; l < face2->numEdges; l++ ) { + edge2Num = abs( file->GetEdgeIndex( face2->firstEdge + l ) ); + if ( edge2Num == absEdge1Num ) { + break; + } + } + if ( l < face2->numEdges ) { + break; + } + } + if ( k < area->numFaces ) { + continue; + } + + // test if the edge is used by a reachability + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + if ( reach->edgeNum == absEdge1Num ) { + break; + } + } + } + if ( reach ) { + continue; + } + + // test if the edge is already in the list + for ( k = 0; k < numEdges; k++ ) { + if ( edge1Num == edges[k] ) { + break; + } + } + if ( k < numEdges ) { + continue; + } + + // add the edge to the list + edges[numEdges++] = edge1Num; + if ( numEdges >= maxEdges ) { + return numEdges; + } + } + } + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + return numEdges; +} diff --git a/src/game/ai/AAS_routing.cpp b/src/game/ai/AAS_routing.cpp new file mode 100644 index 0000000..50e1892 --- /dev/null +++ b/src/game/ai/AAS_routing.cpp @@ -0,0 +1,1447 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for print and error + +#define CACHETYPE_AREA 1 +#define CACHETYPE_PORTAL 2 + +#define MAX_ROUTING_CACHE_MEMORY (2*1024*1024) + +#define LEDGE_TRAVELTIME_PANALTY 250 + +/* +============ +idRoutingCache::idRoutingCache +============ +*/ +idRoutingCache::idRoutingCache( int size ) { + areaNum = 0; + cluster = 0; + next = prev = NULL; + time_next = time_prev = NULL; + travelFlags = 0; + startTravelTime = 0; + type = 0; + this->size = size; + reachabilities = new byte[size]; + memset( reachabilities, 0, size * sizeof( reachabilities[0] ) ); + travelTimes = new unsigned short[size]; + memset( travelTimes, 0, size * sizeof( travelTimes[0] ) ); +} + +/* +============ +idRoutingCache::~idRoutingCache +============ +*/ +idRoutingCache::~idRoutingCache( void ) { + delete [] reachabilities; + delete [] travelTimes; +} + +/* +============ +idRoutingCache::Size +============ +*/ +int idRoutingCache::Size( void ) const { + return sizeof( idRoutingCache ) + size * sizeof( reachabilities[0] ) + size * sizeof( travelTimes[0] ); +} + +/* +============ +idAASLocal::AreaTravelTime +============ +*/ +unsigned short idAASLocal::AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const { + float dist; + + dist = ( end - start ).Length(); + + if ( file->GetArea( areaNum ).travelFlags & TFL_CROUCH ) { + dist *= 100.0f / 100.0f; + } else if ( file->GetArea( areaNum ).travelFlags & TFL_WATER ) { + dist *= 100.0f / 150.0f; + } else { + dist *= 100.0f / 300.0f; + } + if ( dist < 1.0f ) { + return 1; + } + return (unsigned short) idMath::FtoiFast( dist ); +} + +/* +============ +idAASLocal::CalculateAreaTravelTimes +============ +*/ +void idAASLocal::CalculateAreaTravelTimes(void) { + int n, i, j, numReach, numRevReach, t, maxt; + byte *bytePtr; + idReachability *reach, *rev_reach; + + // get total memory for all area travel times + numAreaTravelTimes = 0; + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + numReach = 0; + for ( reach = file->GetArea( n ).reach; reach; reach = reach->next ) { + numReach++; + } + + numRevReach = 0; + for ( rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + numRevReach++; + } + numAreaTravelTimes += numReach * numRevReach; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaTravelTimes = (unsigned short *) Mem_Alloc( numAreaTravelTimes * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + bytePtr = (byte *) areaTravelTimes; + + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + // for each reachability that starts in this area calculate the travel time + // towards all the reachabilities that lead towards this area + for ( maxt = i = 0, reach = file->GetArea( n ).reach; reach; reach = reach->next, i++ ) { + assert( i < MAX_REACH_PER_AREA ); + if ( i >= MAX_REACH_PER_AREA ) { +// RAVEN BEGIN +// bdube: better error message + gameLocal.Error( "i >= MAX_REACH_PER_AREA on area %d at %g,%g,%g", n, file->GetArea( n ).center.x, file->GetArea( n ).center.y, file->GetArea( n ).center.z ); +// RAVEN END + } + reach->number = i; + reach->disableCount = 0; + reach->areaTravelTimes = (unsigned short *) bytePtr; + for ( j = 0, rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next, j++ ) { + t = AreaTravelTime( n, reach->start, rev_reach->end ); + reach->areaTravelTimes[j] = t; + if ( t > maxt ) { + maxt = t; + } + } + bytePtr += j * sizeof( unsigned short ); + } + + // if this area is a portal + if ( file->GetArea( n ).cluster < 0 ) { + // set the maximum travel time through this portal + file->SetPortalMaxTravelTime( -file->GetArea( n ).cluster, maxt ); + } + } + + assert( ( (unsigned int) bytePtr - (unsigned int) areaTravelTimes ) <= numAreaTravelTimes * sizeof( unsigned short ) ); +} + +/* +============ +idAASLocal::DeleteAreaTravelTimes +============ +*/ +void idAASLocal::DeleteAreaTravelTimes( void ) { + Mem_Free( areaTravelTimes ); + areaTravelTimes = NULL; + numAreaTravelTimes = 0; +} + +/* +============ +idAASLocal::SetupRoutingCache +============ +*/ +void idAASLocal::SetupRoutingCache( void ) { + int i; + byte *bytePtr; + + areaCacheIndexSize = 0; + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndexSize += file->GetCluster( i ).numReachableAreas; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaCacheIndex = (idRoutingCache ***) Mem_ClearedAlloc( file->GetNumClusters() * sizeof( idRoutingCache ** ) + + areaCacheIndexSize * sizeof( idRoutingCache *), MA_AAS ); +//RAVEN END + bytePtr = ((byte *)areaCacheIndex) + file->GetNumClusters() * sizeof( idRoutingCache ** ); + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndex[i] = ( idRoutingCache ** ) bytePtr; + bytePtr += file->GetCluster( i ).numReachableAreas * sizeof( idRoutingCache * ); + } + + portalCacheIndexSize = file->GetNumAreas(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + portalCacheIndex = (idRoutingCache **) Mem_ClearedAlloc( portalCacheIndexSize * sizeof( idRoutingCache * ), MA_AAS ); + + areaUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( idRoutingUpdate ),MA_AAS ); + portalUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( (file->GetNumPortals()+1) * sizeof( idRoutingUpdate ),MA_AAS ); + + goalAreaTravelTimes = (unsigned short *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::DeleteClusterCache +============ +*/ +void idAASLocal::DeleteClusterCache( int clusterNum ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !areaCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetCluster( clusterNum ).numReachableAreas; i++ ) { + for ( cache = areaCacheIndex[clusterNum][i]; cache; cache = areaCacheIndex[clusterNum][i] ) { + areaCacheIndex[clusterNum][i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::DeletePortalCache +============ +*/ +void idAASLocal::DeletePortalCache( void ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !portalCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + for ( cache = portalCacheIndex[i]; cache; cache = portalCacheIndex[i] ) { + portalCacheIndex[i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::ShutdownRoutingCache +============ +*/ +void idAASLocal::ShutdownRoutingCache( void ) { + int i; + + for ( i = 0; i < file->GetNumClusters(); i++ ) { + DeleteClusterCache( i ); + } + + DeletePortalCache(); + + Mem_Free( areaCacheIndex ); + areaCacheIndex = NULL; + areaCacheIndexSize = 0; + Mem_Free( portalCacheIndex ); + portalCacheIndex = NULL; + portalCacheIndexSize = 0; +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( areaUpdate ) { + Mem_Free( areaUpdate ); + areaUpdate = NULL; + } + if ( portalUpdate ) { + Mem_Free( portalUpdate ); + portalUpdate = NULL; + } + if ( goalAreaTravelTimes ) { + Mem_Free( goalAreaTravelTimes ); + goalAreaTravelTimes = NULL; + } +// RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::SetupRouting +============ +*/ +bool idAASLocal::SetupRouting( void ) { +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + areaCacheIndex = NULL; + portalCacheIndex = NULL; + areaUpdate = NULL; + portalUpdate = NULL; + goalAreaTravelTimes = NULL; +// RAVEN END + + CalculateAreaTravelTimes(); + SetupRoutingCache(); + return true; +} + +/* +============ +idAASLocal::ShutdownRouting +============ +*/ +void idAASLocal::ShutdownRouting( void ) { + DeleteAreaTravelTimes(); + ShutdownRoutingCache(); +} + +/* +============ +idAASLocal::RoutingStats +============ +*/ +void idAASLocal::RoutingStats( void ) const { + idRoutingCache *cache; + int numAreaCache, numPortalCache; + int totalAreaCacheMemory, totalPortalCacheMemory; + + numAreaCache = numPortalCache = 0; + totalAreaCacheMemory = totalPortalCacheMemory = 0; + for ( cache = cacheListStart; cache; cache = cache->time_next ) { + if ( cache->type == CACHETYPE_AREA ) { + numAreaCache++; + totalAreaCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } else { + numPortalCache++; + totalPortalCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } + } + + gameLocal.Printf( "%6d area cache (%d KB)\n", numAreaCache, totalAreaCacheMemory >> 10 ); + gameLocal.Printf( "%6d portal cache (%d KB)\n", numPortalCache, totalPortalCacheMemory >> 10 ); + gameLocal.Printf( "%6d total cache (%d KB)\n", numAreaCache + numPortalCache, totalCacheMemory >> 10 ); + gameLocal.Printf( "%6d area travel times (%d KB)\n", numAreaTravelTimes, ( numAreaTravelTimes * sizeof( unsigned short ) ) >> 10 ); + gameLocal.Printf( "%6d area cache entries (%d KB)\n", areaCacheIndexSize, ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); + gameLocal.Printf( "%6d portal cache entries (%d KB)\n", portalCacheIndexSize, ( portalCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); +} + +/* +============ +idAASLocal::RemoveRoutingCacheUsingArea +============ +*/ +void idAASLocal::RemoveRoutingCacheUsingArea( int areaNum ) { + int clusterNum; + + clusterNum = file->GetArea( areaNum ).cluster; + if ( clusterNum > 0 ) { + // remove all the cache in the cluster the area is in + DeleteClusterCache( clusterNum ); + } + else { + // if this is a portal remove all cache in both the front and back cluster + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[0] ); + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[1] ); + } + DeletePortalCache(); +} + +/* +============ +idAASLocal::DisableArea +============ +*/ +void idAASLocal::DisableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) { + return; + } + + file->SetAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::EnableArea +============ +*/ +void idAASLocal::EnableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( !( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) ) { + return; + } + + file->RemoveAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::SetAreaState_r +============ +*/ +bool idAASLocal::SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ) { + int res; + const aasNode_t *node; + bool foundClusterPortal = false; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + // if this area is a cluster portal + if ( file->GetArea( -nodeNum ).contents & areaContents ) { + if ( disabled ) { + DisableArea( -nodeNum ); + } else { + EnableArea( -nodeNum ); + } + foundClusterPortal |= true; + } + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + foundClusterPortal |= SetAreaState_r( node->children[1], bounds, areaContents, disabled ); + nodeNum = node->children[0]; + } + } + + return foundClusterPortal; +} + +/* +============ +idAASLocal::SetAreaState +============ +*/ +bool idAASLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) { + idBounds expBounds; + + if ( !file ) { + return false; + } + + expBounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + expBounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + + // find all areas within or touching the bounds with the given contents and disable/enable them for routing + return SetAreaState_r( 1, expBounds, areaContents, disabled ); +} + +/* +============ +idAASLocal::GetBoundsAreas_r +============ +*/ +void idAASLocal::GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const { + int res; + const aasNode_t *node; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + areas.Append( -nodeNum ); + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + GetBoundsAreas_r( node->children[1], bounds, areas ); + nodeNum = node->children[0]; + } + } +} + +/* +============ +idAASLocal::SetObstacleState +============ +*/ +void idAASLocal::SetObstacleState( const idRoutingObstacle *obstacle, bool enable ) { + int i; + const aasArea_t *area; + idReachability *reach, *rev_reach; + bool inside; + + for ( i = 0; i < obstacle->areas.Num(); i++ ) { + + RemoveRoutingCacheUsingArea( obstacle->areas[i] ); + + area = &file->GetArea( obstacle->areas[i] ); + + for ( rev_reach = area->rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + + if ( rev_reach->travelType & TFL_INVALID ) { + continue; + } + + inside = false; + + if ( obstacle->bounds.ContainsPoint( rev_reach->end ) ) { + inside = true; + } + else { + for ( reach = area->reach; reach; reach = reach->next ) { + if ( obstacle->bounds.LineIntersection( rev_reach->end, reach->start ) ) { + inside = true; + break; + } + } + } + + if ( inside ) { + if ( enable ) { + rev_reach->disableCount--; + if ( rev_reach->disableCount <= 0 ) { + rev_reach->travelType &= ~TFL_INVALID; + rev_reach->disableCount = 0; + } + } + else { + rev_reach->travelType |= TFL_INVALID; + rev_reach->disableCount++; + } + } + } + } +} + +// RAVEN BEGIN +// cdr: Alternate Routes Bug + +/* +============ +idAASLocal::SetReachabilityState +============ +*/ +void idAASLocal::SetReachabilityState( idReachability* reach, bool enable ) { + if ( enable && reach->travelType&TFL_INVALID) { + reach->disableCount--; + if ( reach->disableCount <= 0 ) { + reach->travelType &= ~TFL_INVALID; + reach->disableCount = 0; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } + } else if (!enable && !(reach->travelType&TFL_INVALID)) { + reach->travelType |= TFL_INVALID; + reach->disableCount++; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } +} +// RAVEN END + + +/* +============ +idAASLocal::AddObstacle +============ +*/ +aasHandle_t idAASLocal::AddObstacle( const idBounds &bounds ) { + idRoutingObstacle *obstacle; + + if ( !file ) { + return -1; + } + + obstacle = new idRoutingObstacle; + obstacle->bounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacle->bounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + GetBoundsAreas_r( 1, obstacle->bounds, obstacle->areas ); + SetObstacleState( obstacle, true ); + + obstacleList.Append( obstacle ); + return obstacleList.Num() - 1; +} + +/* +============ +idAASLocal::RemoveObstacle +============ +*/ +void idAASLocal::RemoveObstacle( const aasHandle_t handle ) { + if ( !file ) { + return; + } + if ( ( handle >= 0 ) && ( handle < obstacleList.Num() ) ) { + SetObstacleState( obstacleList[handle], false ); + + delete obstacleList[handle]; + obstacleList.RemoveIndex( handle ); + } +} + +/* +============ +idAASLocal::RemoveAllObstacles +============ +*/ +void idAASLocal::RemoveAllObstacles( void ) { + int i; + + if ( !file ) { + return; + } + + for ( i = 0; i < obstacleList.Num(); i++ ) { + SetObstacleState( obstacleList[i], false ); + delete obstacleList[i]; + } + obstacleList.Clear(); +} + +/* +============ +idAASLocal::LinkCache + + link the cache in the cache list sorted from oldest to newest cache +============ +*/ +void idAASLocal::LinkCache( idRoutingCache *cache ) const { + + // if the cache is already linked + if ( cache->time_next || cache->time_prev || cacheListStart == cache ) { + UnlinkCache( cache ); + } + + totalCacheMemory += cache->Size(); + + // add cache to the end of the list + cache->time_next = NULL; + cache->time_prev = cacheListEnd; + if ( cacheListEnd ) { + cacheListEnd->time_next = cache; + } + cacheListEnd = cache; + if ( !cacheListStart ) { + cacheListStart = cache; + } +} + +/* +============ +idAASLocal::UnlinkCache +============ +*/ +void idAASLocal::UnlinkCache( idRoutingCache *cache ) const { + + totalCacheMemory -= cache->Size(); + + // unlink the cache + if ( cache->time_next ) { + cache->time_next->time_prev = cache->time_prev; + } else { + cacheListEnd = cache->time_prev; + } + if ( cache->time_prev ) { + cache->time_prev->time_next = cache->time_next; + } else { + cacheListStart = cache->time_next; + } + cache->time_next = cache->time_prev = NULL; +} + +/* +============ +idAASLocal::DeleteOldestCache +============ +*/ +void idAASLocal::DeleteOldestCache( void ) const { + idRoutingCache *cache; + + assert( cacheListStart ); + + // unlink the oldest cache + cache = cacheListStart; + UnlinkCache( cache ); + + // unlink the oldest cache from the area or portal cache index + if ( cache->next ) { + cache->next->prev = cache->prev; + } + if ( cache->prev ) { + cache->prev->next = cache->next; + } + else if ( cache->type == CACHETYPE_AREA ) { + areaCacheIndex[cache->cluster][ClusterAreaNum( cache->cluster, cache->areaNum )] = cache->next; + } + else if ( cache->type == CACHETYPE_PORTAL ) { + portalCacheIndex[cache->areaNum] = cache->next; + } + + delete cache; +} + +/* +============ +idAASLocal::GetAreaReachability +============ +*/ +idReachability *idAASLocal::GetAreaReachability( int areaNum, int reachabilityNum ) const { + idReachability *reach; + + for ( reach = file->GetArea( areaNum ).reach; reach; reach = reach->next ) { + if ( --reachabilityNum < 0 ) { + return reach; + } + } + return NULL; +} + +/* +============ +idAASLocal::ClusterAreaNum +============ +*/ +ID_INLINE int idAASLocal::ClusterAreaNum( int clusterNum, int areaNum ) const { + int side, areaCluster; + + areaCluster = file->GetArea( areaNum ).cluster; + if ( areaCluster > 0 ) { + return file->GetArea( areaNum ).clusterAreaNum; + } + else { + side = file->GetPortal( -areaCluster ).clusters[0] != clusterNum; + return file->GetPortal( -areaCluster ).clusterAreaNum[side]; + } +} + +/* +============ +idAASLocal::UpdateAreaRoutingCache +============ +*/ +void idAASLocal::UpdateAreaRoutingCache( idRoutingCache *areaCache ) const { + int i, nextAreaNum, cluster, badTravelFlags, clusterAreaNum, numReachableAreas; + unsigned short t, startAreaTravelTimes[MAX_REACH_PER_AREA]; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + + // number of reachability areas within this cluster + numReachableAreas = file->GetCluster( areaCache->cluster ).numReachableAreas; + + // number of the start area within the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, areaCache->areaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + return; + } + + areaCache->travelTimes[clusterAreaNum] = areaCache->startTravelTime; + badTravelFlags = ~areaCache->travelFlags; + memset( startAreaTravelTimes, 0, sizeof( startAreaTravelTimes ) ); + + // initialize first update + curUpdate = &areaUpdate[clusterAreaNum]; + curUpdate->areaNum = areaCache->areaNum; + curUpdate->areaTravelTimes = startAreaTravelTimes; + curUpdate->tmpTravelTime = areaCache->startTravelTime; + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the list + while( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).rev_reach; reach; reach = reach->rev_next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->fromAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + // get the cluster number of the area + cluster = nextArea->cluster; + // don't leave the cluster, however do flood into cluster portals + if ( cluster > 0 && cluster != areaCache->cluster ) { + continue; + } + + // get the number of the area in the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, nextAreaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + continue; // should never happen + } + + assert( clusterAreaNum < areaCache->size ); + + // time already travelled plus the traveltime through the current area + // plus the travel time of the reachability towards the next area + t = curUpdate->tmpTravelTime + curUpdate->areaTravelTimes[i] + reach->travelTime; + + if ( !areaCache->travelTimes[clusterAreaNum] || t < areaCache->travelTimes[clusterAreaNum] ) { + + areaCache->travelTimes[clusterAreaNum] = t; + areaCache->reachabilities[clusterAreaNum] = reach->number; // reversed reachability used to get into this area + nextUpdate = &areaUpdate[clusterAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->areaTravelTimes = reach->areaTravelTimes; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetAreaRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + int clusterAreaNum; + idRoutingCache *cache, *clusterCache; + + // number of the area in the cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // pointer to the cache for the area in the cluster + clusterCache = areaCacheIndex[clusterNum][clusterAreaNum]; + // check if cache without undesired travel flags already exists + for ( cache = clusterCache; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetCluster( clusterNum ).numReachableAreas ); + cache->type = CACHETYPE_AREA; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = clusterCache; + if ( clusterCache ) { + clusterCache->prev = cache; + } + areaCacheIndex[clusterNum][clusterAreaNum] = cache; + UpdateAreaRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::UpdatePortalRoutingCache +============ +*/ +void idAASLocal::UpdatePortalRoutingCache( idRoutingCache *portalCache ) const { + int i, portalNum, clusterAreaNum; + unsigned short t; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *cache; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + + curUpdate = &portalUpdate[ file->GetNumPortals() ]; + curUpdate->cluster = portalCache->cluster; + curUpdate->areaNum = portalCache->areaNum; + curUpdate->tmpTravelTime = portalCache->startTravelTime; + + //put the area to start with in the current read list + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the current list + while( updateListStart ) { + + curUpdate = updateListStart; + // remove the current update from the list + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + // current update is removed from the list + curUpdate->isInList = false; + + cluster = &file->GetCluster( curUpdate->cluster ); + cache = GetAreaRoutingCache( curUpdate->cluster, curUpdate->areaNum, portalCache->travelFlags ); + + // take all portals of the cluster + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + assert( portalNum < portalCache->size ); + portal = &file->GetPortal( portalNum ); + + clusterAreaNum = ClusterAreaNum( curUpdate->cluster, portal->areaNum ); + if ( clusterAreaNum >= cluster->numReachableAreas ) { + continue; + } + + t = cache->travelTimes[clusterAreaNum]; + if ( t == 0 ) { + continue; + } + t += curUpdate->tmpTravelTime; + + if ( !portalCache->travelTimes[portalNum] || t < portalCache->travelTimes[portalNum] ) { + + portalCache->travelTimes[portalNum] = t; + portalCache->reachabilities[portalNum] = cache->reachabilities[clusterAreaNum]; + nextUpdate = &portalUpdate[portalNum]; + if ( portal->clusters[0] == curUpdate->cluster ) { + nextUpdate->cluster = portal->clusters[1]; + } + else { + nextUpdate->cluster = portal->clusters[0]; + } + nextUpdate->areaNum = portal->areaNum; + // add travel time through the actual portal area for the next update + nextUpdate->tmpTravelTime = t + portal->maxAreaTravelTime; + + if ( !nextUpdate->isInList ) { + + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetPortalRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + idRoutingCache *cache; + + // check if cache without undesired travel flags already exists + for ( cache = portalCacheIndex[areaNum]; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetNumPortals() ); + cache->type = CACHETYPE_PORTAL; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = portalCacheIndex[areaNum]; + if ( portalCacheIndex[areaNum] ) { + portalCacheIndex[areaNum]->prev = cache; + } + portalCacheIndex[areaNum] = cache; + UpdatePortalRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::RouteToGoalArea +============ +*/ +bool idAASLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const { + int clusterNum, goalClusterNum, portalNum, i, clusterAreaNum; + unsigned short int t, bestTime; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *areaCache, *portalCache, *clusterCache; + idReachability *bestReach, *r, *nextr; + + travelTime = 0; + *reach = NULL; + + if ( !file ) { + return false; + } + + if ( areaNum == goalAreaNum ) { + return true; + } + + if ( areaNum <= 0 || areaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: areaNum %d out of range\n", areaNum ); +// RAVEN END + return false; + } + if ( goalAreaNum <= 0 || goalAreaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: goalAreaNum %d out of range\n", goalAreaNum ); +// RAVEN END + return false; + } + + while( totalCacheMemory > MAX_ROUTING_CACHE_MEMORY ) { + DeleteOldestCache(); + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the source area is a cluster portal, read directly from the portal cache + if ( clusterNum < 0 ) { + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + *reach = GetAreaReachability( areaNum, portalCache->reachabilities[-clusterNum] ); + travelTime = portalCache->travelTimes[-clusterNum] + AreaTravelTime( areaNum, origin, (*reach)->start ); + return true; + } + + bestTime = 0; + bestReach = NULL; + + // check if the goal area is a portal of the source area cluster + if ( goalClusterNum < 0 ) { + portal = &file->GetPortal( -goalClusterNum ); + if ( portal->clusters[0] == clusterNum || portal->clusters[1] == clusterNum) { + goalClusterNum = clusterNum; + } + } + + // if both areas are in the same cluster + if ( clusterNum > 0 && goalClusterNum > 0 && clusterNum == goalClusterNum ) { + clusterCache = GetAreaRoutingCache( clusterNum, goalAreaNum, travelFlags ); + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + if ( clusterCache->travelTimes[clusterAreaNum] ) { + bestReach = GetAreaReachability( areaNum, clusterCache->reachabilities[clusterAreaNum] ); + bestTime = clusterCache->travelTimes[clusterAreaNum] + AreaTravelTime( areaNum, origin, bestReach->start ); + } + else { + clusterCache = NULL; + } + } + else { + clusterCache = NULL; + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + + // the cluster the area is in + cluster = &file->GetCluster( clusterNum ); + // current area inside the current cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // if the area is not a reachable area + if ( clusterAreaNum >= cluster->numReachableAreas) { + return false; + } + + // find the portal of the source area cluster leading towards the goal area + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + + // if the goal area isn't reachable from the portal + if ( !portalCache->travelTimes[portalNum] ) { + continue; + } + + portal = &file->GetPortal( portalNum ); + // get the cache of the portal area + areaCache = GetAreaRoutingCache( clusterNum, portal->areaNum, travelFlags ); + // if the portal is not reachable from this area + if ( !areaCache->travelTimes[clusterAreaNum] ) { + continue; + } + + r = GetAreaReachability( areaNum, areaCache->reachabilities[clusterAreaNum] ); + + if ( clusterCache ) { + // if the next reachability from the portal leads back into the cluster + nextr = GetAreaReachability( portal->areaNum, portalCache->reachabilities[portalNum] ); + if ( file->GetArea( nextr->toAreaNum ).cluster < 0 || file->GetArea( nextr->toAreaNum ).cluster == clusterNum ) { + continue; + } + } + + // the total travel time is the travel time from the portal area to the goal area + // plus the travel time from the source area towards the portal area + t = portalCache->travelTimes[portalNum] + areaCache->travelTimes[clusterAreaNum]; + // NOTE: Should add the exact travel time through the portal area. + // However we add the largest travel time through the portal area. + // We cannot directly calculate the exact travel time through the portal area + // because the reachability used to travel into the portal area is not known. + t += portal->maxAreaTravelTime; + + // if the time is better than the one already found + if ( !bestTime || t < bestTime ) { + bestReach = r; + bestTime = t; + } + } + + if ( !bestReach ) { + return false; + } + + *reach = bestReach; + travelTime = bestTime; + + return true; +} + +/* +============ +idAASLocal::TravelTimeToGoalArea +============ +*/ +int idAASLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const { + int travelTime; + idReachability *reach; + + if ( !file ) { + return 0; + } + + if ( !RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, travelTime, &reach ) ) { + return 0; + } + return travelTime; +} + +/* +============ +idAASLocal::FindNearestGoal +============ +*/ +bool idAASLocal::FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const { + int i, j, k, badTravelFlags, nextAreaNum; + aasGoal_t bestGoal; + unsigned short t, bestTravelTime; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + idVec3 v1, v2, p; + float targetDist, dist; + + if ( file == NULL || areaNum <= 0 ) { + goal.areaNum = areaNum; + goal.origin = origin; + return false; + } + + // setup obstacles + for ( k = 0; k < numObstacles; k++ ) { + obstacles[k].expAbsBounds[0] = obstacles[k].absBounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacles[k].expAbsBounds[1] = obstacles[k].absBounds[1] - file->GetSettings().boundingBoxes[0][0]; + } + + badTravelFlags = ~travelFlags; + SIMDProcessor->Memset( goalAreaTravelTimes, 0, file->GetNumAreas() * sizeof( unsigned short ) ); + + targetDist = (target - origin).Length(); + + // initialize first update + curUpdate = &areaUpdate[areaNum]; + curUpdate->areaNum = areaNum; + curUpdate->tmpTravelTime = 0; + curUpdate->start = origin; + curUpdate->next = NULL; + curUpdate->prev = NULL; + + callback.Init ( ); + + // if the first area is valid goal, just return the origin + curUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, areaNum, origin, minDistance, maxDistance, &origin, goal ); + if ( curUpdate->cluster == idAASCallback::TEST_OK ) { + callback.Finish ( ); + return true; + } + + updateListStart = curUpdate; + updateListEnd = curUpdate; + + bestTravelTime = 0; + bestGoal.areaNum = 0; + + // while there are updates in the list + while ( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + // if we already found a closer location + if ( bestTravelTime && curUpdate->tmpTravelTime >= bestTravelTime ) { + continue; + } + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).reach; reach; reach = reach->next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->toAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + t = curUpdate->tmpTravelTime + + AreaTravelTime( curUpdate->areaNum, curUpdate->start, reach->start ) + + reach->travelTime; + + // project target origin onto movement vector through the area + v1 = reach->end - curUpdate->start; + v1.Normalize(); + v2 = target - curUpdate->start; + p = curUpdate->start + (v2 * v1) * v1; + + // get the point on the path closest to the target + for ( j = 0; j < 3; j++ ) { + if ( (p[j] > curUpdate->start[j] + 0.1f && p[j] > reach->end[j] + 0.1f) || + (p[j] < curUpdate->start[j] - 0.1f && p[j] < reach->end[j] - 0.1f) ) { + break; + } + } + if ( j >= 3 ) { + dist = (target - p).Length(); + } else { + dist = (target - reach->end).Length(); + } + + // avoid moving closer to the target + if ( dist < targetDist ) { + t += ( targetDist - dist ) * 10; + } + + // if we already found a closer location + if ( bestTravelTime && t >= bestTravelTime ) { + continue; + } + + // if this is not the best path towards the next area + if ( goalAreaTravelTimes[nextAreaNum] && t >= goalAreaTravelTimes[nextAreaNum] ) { + continue; + } + + // path may not go through any obstacles + for ( k = 0; k < numObstacles; k++ ) { + // If the start of the movement vector is inside the expanded bounds then we are already too + // close to the obstacle, so use its unexpanded bounds instead. + if ( obstacles[k].expAbsBounds.ContainsPoint ( curUpdate->start ) ) { + if ( obstacles[k].absBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + // if the movement vector intersects the expanded obstacle bounds + } else if ( obstacles[k].expAbsBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + } + if ( k < numObstacles ) { + continue; + } + + goalAreaTravelTimes[nextAreaNum] = t; + nextUpdate = &areaUpdate[nextAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->start = reach->end; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + // If outside of max distance skip this area + idVec3 point = origin; + float areaDist; + file->PushPointIntoAreaNum ( nextAreaNum, point ); + areaDist = (origin-point).LengthFast(); + if ( maxDistance > 0.0f && areaDist > maxDistance ) { + curUpdate->cluster = idAASCallback::TEST_BADAREA; + continue; + } + + // don't put goal near a ledge + if ( !( nextArea->flags & AREA_LEDGE ) ) { + + // add travel time through the area + t += AreaTravelTime( reach->toAreaNum, reach->end, nextArea->center ); + + if ( !bestTravelTime || t < bestTravelTime ) { + // if the area is not visible to the target + nextUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, reach->toAreaNum, origin, minDistance, maxDistance, NULL, bestGoal ); + switch ( nextUpdate->cluster ) { + case idAASCallback::TEST_OK: + bestTravelTime = t; + break; + case idAASCallback::TEST_BADAREA: + if ( curUpdate->cluster != idAASCallback::TEST_BADAREA ) { + continue; + } + break; + } + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + + callback.Finish ( ); + + if ( bestGoal.areaNum ) { + goal = bestGoal; + return true; + } + + return false; +} diff --git a/src/game/ai/AAS_tactical.cpp b/src/game/ai/AAS_tactical.cpp new file mode 100644 index 0000000..e36670a --- /dev/null +++ b/src/game/ai/AAS_tactical.cpp @@ -0,0 +1,1789 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/////////////////////////////////////////////////////////////////////////////////// +// rvAAS_tactical.cpp +// +// AAST Tactical Search Parameters Documentation +// --------------------------------------------- +// There are 10 tests at your disposal when creating search functions, 3 +// sets of 3 and one special case test. Each of these 10 tests can have +// hard min and max limits, as well as a "soft" weight which will determine how +// highly the feature is ranked against other features. Let's examine a +// diagram of what these 10 tests are: +// +// [F] ######### Key: +// | ######### [F] = Focus Test Set (enemy, or forward projection) +// / ######### [O] = Owner Test Set (actor who is doing the search) +// [P] ######### [P] = Path Test Set (line between owner and focus) +// | ######### <-> = Advance Test (In front / behind owner) +// / x ##### x = Feature (feature being tested) +// <---[O]---> ##### ### = Walls +// #################### +// #################### +// #################### +// +// Each Test Set Has The Following: +// - Distance RANGE=[ 0, 1] WEIGHT=(-1=Close, 1=Far) +// Distance is computed from the origin of the subject to the origin of the +// feature being tested (x in the above diagram). Distance will be a common +// test to use on all three sets, with all mannor of clamped ranges and +// sort values. Most common though will be to sort close to the owner so +// as to minimize how long it will take for the AI to get to the spot. +// +// - FacingDot RANGE=[-1, 1] WEIGHT=(-1=Behind, 1=In Front) +// FacingDot is computed with the dotproduct of the subject's facing and +// the feature's normal. This too will be a common test to compare with +// all three subjects. With it you can prefer points in front or behind +// things. +// +// - DirectionDot RANGE=[-1, 1] WEIGHT=(-1=Toward, 1=Away) +// DirectionDot is computed by subtracing the origins of the feature and +// the subject, normalizing and taking the dotproduct of the result with +// the feature's normal. Usually, you will want a negative weight on this +// test to comare how close the feature is pointing at the subject (usually +// tested against Focus) +// +// Lastly, there is a special "additional" test for "advance": +// - Advance RANGE=[-1, 1] WEIGHT=(-1=Backward, 1=Forward) +// Advance is computed using the owner direction dot with the path facing. +// This test will tell you how much the feature is between the owner and +// the focus, reguardless of what direction either is facing at the time. +// As a result, it approximates "advancing". +// +/////////////////////////////////////////////////////////////////////////////////// +#include "../../idlib/precompiled.h" +#pragma hdrstop + + +/////////////////////////////////////////////////////////////////////////////////// +// Includes +/////////////////////////////////////////////////////////////////////////////////// +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_local.h" +#include "AAS_tactical.h" + +/////////////////////////////////////////////////////////////////////////////////// +// Global::CONTANER SIZES AND OTHER LIMITS +/////////////////////////////////////////////////////////////////////////////////// +const int MAX_FEATURE_LIST = 20; +const int MAX_AREAS_TOUCHED = 450; + +/////////////////////////////////////////////////////////////////////////////////// +// Global::FEATURE TESTING DISTANCES +/////////////////////////////////////////////////////////////////////////////////// +const float MAX_DISTANCE = 650.0f; +const float MIN_DISTANCE = 72.0f; +const float MIN_NEAR_DISTANCE = 112.0f; +const float FROM_ENEMY_PROJECT = 350.0f; +const float TEST_TEAMMATE_DIST = 150.0f; + + + + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Normal +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Normal() +{ + static idVec3 n(0,0,0); + n[0] = ((float)(normalx) / 127.0f) - 1.0f; + n[1] = ((float)(normaly) / 127.0f) - 1.0f; + return n; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Origin() +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Origin() +{ + static idVec3 o; + o.Set((float)x, (float)y, (float)z); + return o; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::GetLookPos() +/////////////////////////////////////////////////////////////////////////////// +int aasFeature_s::GetLookPos( idVec3& lookPos, const idVec3& aimAtOrigin, const float leanDistance ) +{ + static idVec3 up(0.0f,0.0f,1.0f); + static idVec3 direction; + static idVec3 right; + static float rightDot; + static float distance; + + lookPos = Origin(); + lookPos[2] += height - leanDistance; + direction = aimAtOrigin - lookPos; + distance = direction.NormalizeFast(); + right = Normal().Cross(up); + rightDot = right * direction; + + + // Check For Optimal Conditions + //------------------------------ + if (flags&FEATURE_LOOK_OVER && fabsf(rightDot)<0.2f) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT && rightDot>0.0f) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT && rightDot<0.0f) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + + // So Nothing Matches Perfectly, Let's Try Fallback Cases In This Order + //---------------------------------------------------------------------- + if (flags&FEATURE_LOOK_OVER) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + // This Is Odd, There Must Be No Look Flags On This Feature At All + //----------------------------------------------------------------- + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvSortReach +// +// This structure is used by the search heap below to sort areas by actual +// distance from the start point to do a distance based BFS instead of a +// least links based BFS. +/////////////////////////////////////////////////////////////////////////////// +struct rvSortReach +{ + int mAreaNum; + idReachability* mReach; + float mDistance; + + bool operator<(const rvSortReach& r) const + { + return (mDistance>r.mDistance); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTest +// +// A test is a mechanism to weight values and to provide min and max bounds. +/////////////////////////////////////////////////////////////////////////////// +struct rvTest +{ + float mMin; + float mMax; + float mWeight; + float mValue; + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteFloat(mMin); + savefile->WriteFloat(mMax); + savefile->WriteFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadFloat(mMin); + savefile->ReadFloat(mMax); + savefile->ReadFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Reset - Sets the values to defaults + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mMax = 1.0f; + mMin = -1.0f; + mWeight = 0.0f; + mValue = 0.0f; + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Simple compute weight + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return mValue * mWeight; + } + + /////////////////////////////////////////////////////////////////////////// + // Test - Records the value out of the range and returns true if succeeded + /////////////////////////////////////////////////////////////////////////// + bool Test(float Value, float MaxScale=1.0f) + { + if (mMin>-1.0f || mMax<1.0f || mWeight!=0.0f) + { + mValue = Value; + if (MaxScale!=1.0f) + { + mValue /= MaxScale; + } + mValue = (mValue-mMin) / (mMax-mMin); // Now Scale It [0.0, 1.0] Of Min And Max + return (mValue>=0.0f && mValue<=1.0f); // If Not In [0.0, 1.0], Test Fails + } + return true; // Test Is Not Active + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Returns the abs of the weight, and adds to negatives + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + if (mWeight<0.0f) + { + negatives += mWeight; + } + return fabsf(mWeight); + } + + void DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction); + void DrawDebugInfo(const idVec4 color, const idVec3& origin); +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTestSet +// +// Test sets are designed to be used by the rvAASTacticalSensorLocal class to compute +// various vectors against a given feature. +/////////////////////////////////////////////////////////////////////////////// +struct rvTestSet +{ + // Parameters + //------------ + bool mProjectOrigin; // If True, Origin Is Cast Out Along mFacing Vector + idVec3 mOrigin; // Position Of The Test Subject + idVec3 mFacing; // Orientation Of The Test Subject + + // Computed During Test() + //------------------------ + rvTest mDistance; // Resulting Distance To Feature + rvTest mFacingDot; // Resulting Facing Dot Product To Feature + rvTest mDirectionDot; // Resulting Direction Dot Product To Feature + idVec3 mDirection; // Resulting Direction To Feature + + /////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////// + rvTestSet( void ) + : mOrigin(0,0,0), mFacing(0,0,0), mProjectOrigin(false) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteBool(mProjectOrigin); + savefile->WriteVec3(mOrigin); + savefile->WriteVec3(mFacing); + mDistance.Save(savefile); + mFacingDot.Save(savefile); + mDirectionDot.Save(savefile); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadBool(mProjectOrigin); + savefile->ReadVec3(mOrigin); + savefile->ReadVec3(mFacing); + mDistance.Restore(savefile); + mFacingDot.Restore(savefile); + mDirectionDot.Restore(savefile); + } + + + /////////////////////////////////////////////////////////////////////////// + // Reset - Restets all sub tests + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mProjectOrigin = false; + mDirectionDot.Reset(); + mFacingDot.Reset(); + mDistance.Reset(); +// bdube: Had to comment this out becaue it would break the test function which checks for non defaults +// mDistance.mMin = 0.0f; // special case (default would be -1.0f because all others are dot products) + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Adds up the weights of the sub tests + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return (mDistance.Weight() + mFacingDot.Weight() + mDirectionDot.Weight()); + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Computes weight range of all sub tests + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + return (mDistance.WeightRange(negatives) + mFacingDot.WeightRange(negatives) + mDirectionDot.WeightRange(negatives)); + } + + /////////////////////////////////////////////////////////////////////////// + // Test - This is the actuall feature test + /////////////////////////////////////////////////////////////////////////// + bool Test(aasFeature_t* f, float distance=0.0f) + { + // First Compute The Direction + //----------------------------- + mDirection = f->Origin() - mOrigin; + + // If Project Origin Is True, Then Move Origin Along Facing Vector + //----------------------------------------------------------------- + if (mProjectOrigin) + { + mDirection.ProjectOntoVector(mFacing); + mOrigin += mDirection; + mDirection = f->Origin() - mOrigin; + } + + // If No Override On Distance, Compute It By Normalizing The Direction + //--------------------------------------------------------------------- + if (!distance) + { + distance = mDirection.Normalize(); + } + else + { + mDirection.Normalize(); + } + + + // THE DISTANCE TEST + //------------------- + if (!mDistance.Test(distance, MAX_DISTANCE)) + { + return false; + } + + // THE FACING TEST + //----------------- + if (!mFacingDot.Test(f->Normal()*mFacing)) + { + return false; + } + + // THE DIRECTION TEST + //-------------------- + if (!mDirectionDot.Test(f->Normal()*mDirection)) + { + return false; + } + return true; + } + + /////////////////////////////////////////////////////////////////////////// + // SetupOriginAndFacing - Called For Each Test Set + /////////////////////////////////////////////////////////////////////////// + void SetupOriginAndFacing(const idEntity* ent, const idVec3* originOverride=0, const idVec3* facingOverride=0) + { + if (!ent) + { + mOrigin = (originOverride)?(*originOverride):(vec3_origin); + mFacing = (facingOverride)?(*facingOverride):(vec3_origin); + } + else + { + const idActor* entActor = dynamic_cast(ent); // bleh. Base entity class should properly return origin, angles, and forward vector + + mOrigin = (originOverride)?(*originOverride):(ent->GetPhysics()->GetOrigin()); + mFacing = (facingOverride)?(*facingOverride):(entActor?entActor->viewAxis[0]:ent->GetPhysics()->GetAxis(0)[0]); + } + + mFacing[2] = 0; + mFacing.Normalize(); + } + + /////////////////////////////////////////////////////////////////////////// + // ProjectOriginForward - Used By Several Setup Options To Project Origin + // Along Facing Direction Some + /////////////////////////////////////////////////////////////////////////// + void ProjectOriginForward(float distance, float xyRange=0.0f, bool randomFacing=0.0f) + { + mOrigin += (mFacing * distance); + if (xyRange) + { + mOrigin[0] += rvRandom::flrand(-xyRange, xyRange); + mOrigin[1] += rvRandom::flrand(-xyRange, xyRange); + } + if (randomFacing) + { + mFacing[0] = rvRandom::flrand(-1.0f, 1.0f); + mFacing[1] = rvRandom::flrand(-1.0f, 1.0f); + } + mFacing[2] = 0.0f; + mFacing.Normalize(); + } + + void DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin); +}; + + + + + +/////////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +// +// This is the local implimentation of the rvAASTacticalSensor interface. It +// contains all the various local data and functions needed to execute a +// search of the tactical data in AAS. +/////////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensorLocal : rvAASTacticalSensor +{ + // Owner + /////////////////////////////////////////////////////////////////////////////// + idActor* mOwner; // Owner Of The Sensor + idAI* mOwnerAI; // Owner As Already Cast To AI Type + + // Search Parameters + /////////////////////////////////////////////////////////////////////////////// + idStr mSearchName; // Current Search Name + int mFlagsMatchAny; // Features must match AT LEAST ONE of these flags + int mFlagsMatchAll; // Features must match ALL of these flags + int mFlagsMatchNone; // Features must match NONE of these flags + int mFeaturesSearchMax; // Maximum number of features to extract from the grid + int mFeaturesFinalMax; // After sorting, prune list down to this size + rvTestSet mFromOwner; // Test Set For Owner Relation + rvTestSet mFromEnemy; // Test Set For Focus Relation + rvTestSet mFromTether; // Test Set for Tether Relation + rvTestSet mFromPath; // Test Set For Path Relation + rvTest mAdvance; // Single Test For Advance / Retreat + rvTest mAssignment; // Single Test For Assignment Direction Dot Product + rvTest mLeanNormal; // Single Test For Lean Normal Biasing + idVec3 mAssignmentDirection; // Used By Assignment Test + bool mAssignmentValid; // Turns On And Off The Assignment Test + idEntityPtr mEnemyOverride; // Overrides Enemy Pointer To Any Entity + + // Search & Update Results + /////////////////////////////////////////////////////////////////////////////// + idList mFeatures; // The list of all features found in the most recent search + idVec3 mReservedOrigin; // Origin of feature that is currently reserved + aasFeature_t* mReserved; // Which feature is currently reserved + aasFeature_t* mNear; // Which feature is closest + aasFeature_t* mLook; // Which feature to look down + int mLookStartTime; + float mLookStopDist; + + + + + + // Local API + /////////////////////////////////////////////////////////////////////////////// + rvAASTacticalSensorLocal(); + ~rvAASTacticalSensorLocal(); + void Update(); + void Save(idSaveGame *savefile); + void Restore(idRestoreGame *savefile); + void Clear(); + void DrawDebugInfo(); + + // Search + /////////////////////////////////////////////////////////////////////////////// + void Search(); + void SearchReset(idEntity* enemyOverride=0, float ownerRangeMin=0.0f, float ownerRangeMax=1.0f); + void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f); + void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f); + void SearchHide(idEntity* from=0); + void SearchFlank(); + void SearchAdvance(); + void SearchRetreat(); + void SearchAmbush(); + void SearchDebug(); + float SearchComputeWeightRange(float& rangeNegative); + float SearchComputeWeight(); + + // Feature Testing + /////////////////////////////////////////////////////////////////////////////// + void TestSetupCurrentValues(); + bool TestValid(aasFeature_t* f, float walkDistanceToFeature); + bool TestValidWithCurrentState(aasFeature_t* f=0); + + // Feature Reservation + /////////////////////////////////////////////////////////////////////////////// + void Reserve(aasFeature_t* f); + + // Access To Results + /////////////////////////////////////////////////////////////////////////////// + int FeatureCount() {return mFeatures.Num();} + aasFeature_t* Feature(int i) {return mFeatures[i];} + aasFeature_t* Near() const {return mNear;} + aasFeature_t* Look() const {return mLook;} + aasFeature_t* Reserved() const {return mReserved;} + const idVec3& ReservedOrigin() const {return mReservedOrigin;} +}; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Objects +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal* mSensor; +float mDebugRadius; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Typedefines +/////////////////////////////////////////////////////////////////////////////// +typedef idEntityPtr TEntPtr; +typedef aasFeature_t* TFeaturePtr; + + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor::CREATE_SENSOR +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensor* rvAASTacticalSensor::CREATE_SENSOR(idActor* owner) +{ + rvAASTacticalSensorLocal* nSensor = new rvAASTacticalSensorLocal(); + nSensor->mOwner = owner; + nSensor->mOwnerAI = dynamic_cast(owner); + return nSensor; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::rvAASTacticalSensorLocal() +{ + mOwner = 0; + mOwnerAI = 0; + Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Destructor +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::~rvAASTacticalSensorLocal() +{ + Reserve(0); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Clear +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Clear() +{ + mReserved = 0; + mNear = 0; + mLook = 0; + mSearchName = ""; + mFeatures.Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Save +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Save(idSaveGame *savefile) +{ + savefile->WriteString(mSearchName); + savefile->WriteInt(mFlagsMatchAny); + savefile->WriteInt(mFlagsMatchAll); + savefile->WriteInt(mFlagsMatchNone); + savefile->WriteInt(mFeaturesSearchMax); + savefile->WriteInt(mFeaturesFinalMax); + mFromOwner.Save(savefile); + mFromEnemy.Save(savefile); + mFromTether.Save(savefile); + mFromPath.Save(savefile); + mAdvance.Save(savefile); + mAssignment.Save(savefile); + mLeanNormal.Save(savefile); + savefile->WriteVec3(mAssignmentDirection); + savefile->WriteBool(mAssignmentValid); + mEnemyOverride.Save(savefile); + +// cnicholson: NOTE: The following 4 vars are set to 0 / cleared in the restore, so don't save them. + // NOSAVE: idList mFeatures; + // NOSAVE: savefile->WriteVec3(mFeatures); + // NOSAVE: aasFeature_t* mReserved; + // NOSAVE: aasFeature_t* mNear; + // NOSAVE: aasFeature_t* mLook; + savefile->WriteInt(mLookStartTime); // cnicholson: Added unsaved var + savefile->WriteFloat(mLookStopDist);// cnicholson: Added unsaved var +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Restore +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Restore(idRestoreGame *savefile) +{ + // Clear Old Data + //---------------- + mFeatures.Clear(); + mNear = 0; + mLook = 0; + mReserved = 0; + + // Read The Save File Search Parameters + //-------------------------------------- + savefile->ReadString(mSearchName); + savefile->ReadInt(mFlagsMatchAny); + savefile->ReadInt(mFlagsMatchAll); + savefile->ReadInt(mFlagsMatchNone); + savefile->ReadInt(mFeaturesSearchMax); + savefile->ReadInt(mFeaturesFinalMax); + mFromOwner.Restore(savefile); + mFromEnemy.Restore(savefile); + mFromTether.Restore(savefile); + mFromPath.Restore(savefile); + mAdvance.Restore(savefile); + mAssignment.Restore(savefile); + mLeanNormal.Restore(savefile); + savefile->ReadVec3(mAssignmentDirection); + savefile->ReadBool(mAssignmentValid); + mEnemyOverride.Restore(savefile); +// Search(); + + savefile->ReadInt(mLookStartTime); // cnicholson: Added unrestored var + savefile->ReadFloat(mLookStopDist);// cnicholson: Added unrestored var +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Update +// +// If called regularly, this function will handle drawing debug information +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Update() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden()) + { + return; + } + + idAASFile* file = aas->GetFile(); + + idVec3 velocityFwd = mOwner->GetPhysics()->GetLinearVelocity(); + const idVec3& ownerOrigin = mOwner->GetPhysics()->GetOrigin(); + int ownerAreaNum = mOwnerAI ? mOwnerAI->PointReachableAreaNum ( ownerOrigin ) : aas->PointReachableAreaNum(ownerOrigin, mOwner->GetPhysics()->GetBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aasFeature_t* feature = 0; + aasArea_t& area = file->GetArea(ownerAreaNum); + idActor* teammate = NULL; + float featureDistance = 0.0f; + float featureDotLeft = 0.0f; + float featureDotDirection = 0.0f; + float teammateDistance = 0.0f; + float closestDistance = 0.0f; + idVec3 velocityLeft; + idVec3 velocityDown; + idVec3 featureDirection; + idVec3 teammateDirection; + + + + // Update And Possibly Clear The Near Feature + //-------------------------------------------- + if (mNear) + { + closestDistance = mNear->Origin().Dist(ownerOrigin); + if (closestDistance>MIN_NEAR_DISTANCE) + { + mNear = 0; + } + } + + + // Search For Features In This Area That Are Close To Owner Origin + //----------------------------------------------------------------- + if (area.numFeatures) + { + for (int areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + if (feature!=mNear) + { + featureDirection = feature->Origin(); + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + if (featureDistance1.0f) + { + // Compute Velocity Vectors + //-------------------------- + velocityFwd.NormalizeFast(); + velocityFwd.NormalVectors(velocityLeft, velocityDown); + + + // Check If We Should Clear The Look Feature + //------------------------------------------- + if (mLook) + { + const idVec3& featureOrigin = mLook->Origin(); + const idVec3& featureNormal = mLook->Normal(); + + // Too Far? + //---------- + if (featureOrigin.Dist(ownerOrigin)>mLookStopDist) + { + mLook = 0; + } + + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + mLook = 0; + break; + } + } + teammate = NULL; + } + + // And, If We Are Moving, Check The Near Feature To See If It Qualifies As A Look Feature + //---------------------------------------------------------------------------------------- + if (mNear && mNear!=mLook && (gameLocal.GetTime() - mLookStartTime)>3000) + { + const idVec3& featureOrigin = mNear->Origin(); + const idVec3& featureNormal = mNear->Normal(); + + // Compute Feature Direction + //--------------------------- + featureDirection = featureOrigin; + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + // Must Be Behind Me (I've Alreay Walked Past It) + //------------------------------------------------ + if (featureDistance>16.0f && featureDirection*velocityFwd<0.0f) + { + // Must Be Facing Away From Me + //----------------------------- + featureDotDirection = featureNormal*featureDirection; + if (featureDotDirection>-0.8f) + { + // Must Be Roughly Perpendicular To My Velocity (Within 45 Degrees) + //------------------------------------------------------------------ + featureDotLeft = featureNormal*velocityLeft; + if (fabsf(featureDotLeft)>0.5f) + { + // If On Left Of Me, Must Have A Right Lean, And Converse + //-------------------------------------------------------- + if ((featureDotLeft>0.0f && mNear->flags&FEATURE_LOOK_RIGHT) || + (featureDotLeft<0.0f && mNear->flags&FEATURE_LOOK_LEFT)) + { + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + break; + } + } + + if (!teammate) + { + mLook = mNear; + mLookStartTime = gameLocal.GetTime(); + mLookStopDist = rvRandom::flrand(48.0f, 80.0f); + } + } + } + } + } + } + } + else if (!mOwnerAI || !mOwnerAI->move.fl.moving) + { + mLook = 0; + } + + // Draw Any Debug Info + //--------------------- + DrawDebugInfo(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Reserve +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Reserve(aasFeature_t* f) +{ + if (f!=mReserved && mOwner) + { + mReserved = f; + + if ( f ) + { + mReservedOrigin = f->Origin ( ); + } + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// FEATURE TESTING +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// TestSetupCurrentValuesFor +// +// This function sets up the test sets to have new +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::TestSetupCurrentValues() +{ + // Owner Test Set + //---------------- + mFromOwner.SetupOriginAndFacing(mOwner); + + + // Enemy Test Set + //---------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + if (mOwnerAI && mEnemyOverride) + { + mFromEnemy.SetupOriginAndFacing(mEnemyOverride, &mOwnerAI->LastKnownPosition(mEnemyOverride), 0); + } + else if (mOwnerAI && mOwnerAI->GetEnemy()) + { + mFromEnemy.SetupOriginAndFacing(mOwnerAI->GetEnemy(), &mOwnerAI->LastKnownPosition(mOwnerAI->GetEnemy()), 0); + } + + // Tether Test Set + //---------------- + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.SetupOriginAndFacing(mOwnerAI->GetTether ( )); + } + + // Path Test Set + //---------------- + mFromPath.mProjectOrigin = true; + mFromPath.mOrigin = mFromOwner.mOrigin; + mFromPath.mFacing = mFromEnemy.mOrigin - mFromOwner.mOrigin; + mFromPath.mFacing[2] = 0; + mFromPath.mFacing.Normalize(); + + // Advance Test Set + //------------------ + // NOTHING TO DO HERE FOR NOW... +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValidReserved +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValidWithCurrentState(aasFeature_t* f) +{ + // If Trying To Hide From An Enemy That No Longer Exists, Any Feature is Invalid + //------------------------------------------------------------------------------- + if (mEnemyOverride.GetSpawnId() && !mEnemyOverride.IsValid()) + { + return false; + } + + // Reset The Test Parameters With Current Origins (Cuz Things May Have Moved) + //---------------------------------------------------------------------------- + TestSetupCurrentValues(); + + // And Run The Test That The Original Search Ran + //----------------------------------------------- + if (!f) + { + return TestValid(mReserved, 0.0f); + } + return TestValid(f, 0.0f); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValid +// +// This is THE function that tests if a given feature matches the current +// search parameters. An optional walk distance may be passed into the function +// to replace the from owner straight line distance. +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValid(aasFeature_t* f, float walkDistanceToFeature) +{ + static idVec3 LeanNormal; + static idVec3 Up(0.0f,0.0f,1.0f); + static float LeanNormalDot; + + + // Is There A Feature At All + //--------------------------- + if (!f) + { + return false; + } + + // Does It Match The Flags? + //-------------------------- + if (!(f->flags&mFlagsMatchAny) || + ((f->flags&mFlagsMatchAll)!=mFlagsMatchAll) || + (f->flags&mFlagsMatchNone)) + { + return false; + } + + // Does It Pass The Tests? + //------------------------- + if (!mFromOwner.Test(f, walkDistanceToFeature) || + !mFromEnemy.Test(f) || + !mFromTether.Test(f) || + !mFromPath.Test(f) || + !mAdvance.Test(mFromOwner.mDirection*mFromPath.mFacing) || + !mAssignment.Test(mFromOwner.mDirection*mAssignmentDirection)) + { + return false; + } + + // Make sure this cover point is vaild for the current tether + //------------------------------------------------------------ + if ( mOwnerAI && mOwnerAI->IsTethered() && !mOwnerAI->GetTether()->ValidateDestination ( mOwnerAI, f->Origin() ) ) + { + return false; + } + + // Does It Pass The Lean Normal Test? + //------------------------------------ + if (mOwnerAI && (mEnemyOverride||mOwnerAI->GetEnemy())) + {//we have an enemy position to test against + mLeanNormal.mValue = 0.0f; + if (!(f->flags&FEATURE_LOOK_OVER) && ((f->flags&FEATURE_LOOK_RIGHT) || (f->flags&FEATURE_LOOK_LEFT))) + { + LeanNormal = f->Normal().Cross(Up); // Start With Left + LeanNormalDot = LeanNormal * mFromEnemy.mDirection; + + if (!(f->flags&FEATURE_LOOK_LEFT) || ((f->flags&FEATURE_LOOK_RIGHT) && LeanNormalDot<0.0f)) + { + LeanNormalDot *= -1.0f; // Use The Right Normal + } + + if (!mLeanNormal.Test(LeanNormalDot)) + { + return false; + } + } + } + + + // Is Anyone Else Going There? + //----------------------------- + if (mOwnerAI && !aiManager.ValidateDestination(mOwnerAI, f->Origin())) + { + return false; + } + + // Everything Passed, This Feature Is Good To Go + //----------------------------------------------- + return true; +} + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SEARCH PARAMETERS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchReset +// +// Initialize Default Flags, Feature Counts, And Population Points. This +// function must be called before first in any search function, because +// the search functions rely on this standard set of parameters and then +// build upon them. +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchReset(idEntity* enemyOverride, float ownerRangeMin, float ownerRangeMax) +{ + if (!mOwner) + { + return; + } + + // Setup Base Search Parameters + //------------------------------ + mFlagsMatchAll = 0; // Must Have All Of These + mFlagsMatchAny = (FEATURE_LOOK_LEFT|FEATURE_LOOK_RIGHT|FEATURE_LOOK_OVER); // Must Have At Least One Of These + mFlagsMatchNone = (FEATURE_PINCH|FEATURE_VANTAGE); // Don't Want Any Of These + mFeaturesSearchMax = 100; + mFeaturesFinalMax = 20; + mAssignmentValid = false; // TODO: Turn This Back On + mAssignmentDirection = vec3_zero; + mEnemyOverride = enemyOverride; + + + // Lean Normal Test + //------------------ + mLeanNormal.Reset(); + mLeanNormal.mMin =-0.2f; // Must Lean Toward Enemy + + + // Owner Test Set Default Values + //------------------------------- + mFromOwner.Reset(); + mFromOwner.mDistance.mMin = ownerRangeMin; + mFromOwner.mDistance.mMax = ownerRangeMax; + mFromOwner.mDistance.mWeight =-1.0f; // Prefer Close To Owner + + // Enemy Test Set Default Values + //------------------------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + mFromEnemy.Reset(); + if ( mOwnerAI && mOwnerAI->enemy.ent ) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; // must be at least 100 units from enemy + mFromEnemy.mDistance.mMax = 2.0f; // don't care how far the distance is to the enemy, let it go over max (up to 1600) + mFromEnemy.mDirectionDot.mMax =-0.7f; // Must Face Within 45 Degrees Of enemy + mFromEnemy.mDirectionDot.mWeight =-0.3f; // Prefer To Face Toward Enemy + if (mOwnerAI && mOwnerAI->enemy.ent ) + { + // Cap Min And Max Distances To Attack Range, and Aware Range + //------------------------------------------------------------ + mFromEnemy.mDistance.mMax = mOwnerAI->combat.attackRange[1] / MAX_DISTANCE; + mFromEnemy.mDistance.mMin = mOwnerAI->combat.attackRange[0] / MAX_DISTANCE; + + if (mFromEnemy.mDistance.mMin < (mOwnerAI->combat.awareRange / MAX_DISTANCE)) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; + } + + // If haven't seen enemy in a while, allow you to go right to his last known spot + //-------------------------------------------------------------------------------- + if ( mOwnerAI->enemy.lastVisibleTime && (gameLocal.GetTime() - mOwnerAI->enemy.lastVisibleTime)>mOwnerAI->combat.maxLostVisTime/2.0f) + { + mFromEnemy.mDistance.mMin = 0.0f; + } + } + } + + + // Tether test set + //------------------------------- + mFromTether.Reset(); + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.mFacingDot.mMin = 0.5f; + mFromTether.mFacingDot.mWeight = 0.3f; + + // Disable the owner distance test + mFromOwner.Reset(); + } + + // Other Test Sets + //----------------- + mFromPath.Reset(); + mAdvance.Reset(); + mAssignment.Reset(); + + + // Default Test Values + //--------------------- + TestSetupCurrentValues(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchDebug +// +// TO RUN THIS FUNCTION, TYPE "extract_tactical" ON THE CONSOLE. +// +// Feel free to modify this function to test whatever search or other +// operation you need. The owner will be the player. +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchDebug() +{ + SearchCover(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRadius +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRadius(const idVec3& origin, float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Radius"; + mFromEnemy.Reset(); // Don't Care About Enemy At All + if ( origin != vec3_origin ) + { + mFromOwner.mOrigin = origin; // Override the owner origin + } + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchCover +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchCover(float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Cover"; + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchHide +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchHide(idEntity* from) +{ + SearchReset(from); + mSearchName = "Hide"; + mFlagsMatchNone |= FEATURE_LOOK_OVER; // Want Full Height Walls Here + mFromEnemy.mDirectionDot.mMax = -0.8f; // Must Almost Exactly At The Enemy + mFromOwner.mDistance.mMax = 2.0f; // Go as far as you need to - ignore tethering for hide + mFromOwner.mDistance.mMin = 0.4f; // Get A Good Distance Away + mFromOwner.mDirectionDot.Reset(); // Ignore any direction dot with the leader + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchFlank +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchFlank() +{ + SearchReset(); + mSearchName = "Flank"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMin = -0.2f; // Must Be Behind Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAdvance +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAdvance() +{ + SearchReset(); + mSearchName = "Advance"; + mFromOwner.mDistance.mMin = 0.15f; // Make Sure To Move Some + mAdvance.mMin = 0.3f; // Forward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRetreat +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRetreat() +{ + SearchReset(); + mSearchName = "Retreat"; + mFromOwner.mDistance.mMin = 0.1f; // Make Sure To Move Some + mAdvance.mMax = -0.3f; // Backward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAmbush +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAmbush() +{ + SearchReset(); + mSearchName = "Ambush"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMax = 0.2f; // Must Be In Front Of Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// THE SEARCH +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchComputeWeightRange +// +// We compute the weight range so that we can make a better scale factor +// between 0.0 and 1.0 later on. It's not critical that all the weights +// factor exactly 0.0 to 1.0, but it is nice to see how "good" a feature +// matches the given search parameters +/////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeightRange(float& rangeNegative) +{ + return (mFromOwner.WeightRange(rangeNegative) + + mFromEnemy.WeightRange(rangeNegative) + + mFromTether.WeightRange(rangeNegative) + + mFromPath.WeightRange(rangeNegative) + + mAdvance.WeightRange(rangeNegative) + + mLeanNormal.WeightRange(rangeNegative) + + mAssignment.WeightRange(rangeNegative)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Weight +// +// Add up the computed weight of all tests. +/////////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeight() +{ + return (mFromOwner.Weight() + + mFromEnemy.Weight() + + mFromTether.Weight() + + mFromPath.Weight() + + mAdvance.Weight() + + mLeanNormal.Weight() + + mAssignment.Weight()); +} + +/////////////////////////////////////////////////////////////////////////////// +// SortFeature function (used by Search() below) +/////////////////////////////////////////////////////////////////////////////// +ID_INLINE int rvSortFeature( const TFeaturePtr *a, const TFeaturePtr *b ) +{ + if ((*a)->weight > (*b)->weight) + { + return -1; + } + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Search +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Search() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!mOwner || !aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures()) + { + return; + } + + + static idAASFile* file; + static int areaNum; + static int areaNumOwner; + static rvBits<32000> areaVisit; + static int areaVisitCount; + static int travelTime; + static int areaFeatureNum; + static int featureNum; + static aasFeature_t* featurePtr; + static idVec3 featureOrigin; + static idReachability* reach; + static idList searchHeap; + static rvSortReach sortReach; + static float walkDistanceToFeature; + static float weight; + static float weightRangeNegative; + static float weightRangeTotal; + static idVec3 from; + static idVec3 endPos; + static int endAreaNum; + + + + +//----------------------------------------------------------------------------- +// SETUP +//----------------------------------------------------------------------------- + file = aas->GetFile(); + weightRangeNegative = 0.0f; + weightRangeTotal = SearchComputeWeightRange(weightRangeNegative); + + mFeatures.Clear(); + searchHeap.Clear(); + areaVisit.clear(); + if (!mAssignmentValid) + { + mAssignment.Reset(); // Never Worry About Squad Assignments If No Leader Is Active + } + + + + +//----------------------------------------------------------------------------- +// PHASE I - POPULATE AREA QUEUE +//----------------------------------------------------------------------------- + if ( mOwnerAI ) + { + areaNumOwner = mOwnerAI->PointReachableAreaNum ( mFromOwner.mOrigin ); + } + else + { + areaNumOwner = aas->PointReachableAreaNum(mFromOwner.mOrigin, mOwner->GetPhysics()->GetBounds(), AREA_REACHABLE_WALK); + } + + from = mFromOwner.mOrigin; + areaNum = areaNumOwner; + sortReach.mAreaNum = areaNumOwner; + sortReach.mDistance = 0.0f; + sortReach.mReach = 0; + searchHeap.Append(sortReach); + + + + +//----------------------------------------------------------------------------- +// PHASE II - BREADTH FIRST SEARCH NEIGHBORING AREAS FOR FEATURES THAT TEST OK +//----------------------------------------------------------------------------- + areaVisitCount = 0; + while (searchHeap.Num() && areaVisitCountGetArea(sr.mAreaNum); + if (area.numFeatures) + { + for (areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + featureOrigin = featurePtr->Origin(); + + // If Walk Path Is Valid, Allow From Owner Test To Use Computed Straight Line Distance + //------------------------------------------------------------------------------------- + if (aas->WalkPathValid(areaNumOwner, mFromOwner.mOrigin, sr.mAreaNum, featureOrigin, TFL_WALK, endPos, endAreaNum)) + { + walkDistanceToFeature = 0.0f; // Allows Test to use straight line distance computed + } + + // If It Is Not Possible To Straight Line Walk To A Feature, Use The Enter Point And Distance Of The Area (Which Is A Rough Appx) + //-------------------------------------------------------------------------------------------------------------------------------- + else + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(featureOrigin)):(mFromOwner.mOrigin.Dist(featureOrigin))); + } + + // Test The Feature To See If It's Valid + //--------------------------------------- + if (!TestValid(featurePtr, walkDistanceToFeature)) + { + continue; + } + + // Compute The Weight + //-------------------- + if (weightRangeTotal>0.0f) + { + weight = SearchComputeWeight(); // Compute Weight Sum + weight -= weightRangeNegative; // Bring it into a positive range + weight /= weightRangeTotal; // Scale down to 0.0 - 1.0 + + assert(weight>0.0f && weight<255.0f); + featurePtr->weight = (char)(weight*255); + } + else + { + featurePtr->weight = (unsigned char)(128); // No Sorting + } + + + // Append The Feature To The List + //-------------------------------- + mFeatures.Append(featurePtr); + } + } + + + // Add Neighboring Areas To Search + //--------------------------------- + for (reach=area.reach; reach; reach=reach->next) + { + if ((reach->travelType&TFL_WALK)) + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(reach->end)):(mFromOwner.mOrigin.Dist(reach->end))); + + if (walkDistanceToFeaturetoAreaNum; + sortReach.mReach = reach; + searchHeap.HeapAdd(sortReach); + } + } + } + } + + + + +//----------------------------------------------------------------------------- +// PHASE III - SORT AND CLIP THE FEATURE LIST +//----------------------------------------------------------------------------- + mFeatures.Sort(rvSortFeature); + + // Now, Clip The Sorted List To The Max Size + //------------------------------------------- + if (mFeatures.Num()>MAX_FEATURE_LIST) + { + mFeatures.Resize(MAX_FEATURE_LIST); + } + + // Now Reset Any Parameters Which Were Only "Temporary" During The Search, and Do Not Invalidate The Point Later + //---------------------------------------------------------------------------------------------------------------- + mFromOwner.mDistance.mMin = 0.0f; // Allow Getting Close Again + + + // Print Search Results + //---------------------- + if (ai_showTacticalFeatures.GetInteger()==3) + { + common->Printf( "[%10d] Search%s Found %d Features For %s\n", gameLocal.GetTime(), mSearchName.c_str(), mFeatures.Num(), mOwner->GetName() ); + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DEBUG GRAPHICS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_t::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void aasFeature_t::DrawDebugInfo( int index ) +{ + static idVec3 Height; + static idVec3 Orig; + static idVec3 Norm; + static idVec3 Text; + static idVec4 color; + static idVec3 Left; + + int lifetime = 0; + + color = colorWhite; + + if (flags & FEATURE_COVER) + { + color = colorGreen; + } + + Orig = Origin(); + Orig[2] += 1.0f; + + Height = Orig; + Height[2] += height; + + Norm = Orig; + Norm += Normal() * mDebugRadius; + Left = Normal().Cross(idVec3(0,0,-1)) * mDebugRadius; + + gameRenderWorld->DebugLine( color, Orig, Height, lifetime ); + gameRenderWorld->DebugLine( color, Orig, Norm, lifetime ); + + if (index>=0) + { + Text = (Origin() + Height) * 0.5f; + gameRenderWorld->DrawText( va( "%d", index ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + + Text[2] += 15.0f; + gameRenderWorld->DrawText( va( "%d", (int)weight ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + } + + // Left Corner Is On The Ground + //------------------------------ + if (flags & FEATURE_CORNER_LEFT) + { + gameRenderWorld->DebugLine( color, Orig, Orig + Left, lifetime ); + } + + // Otherwise Windows Are At The Given Height + //------------------------------------------- + else if (flags & FEATURE_LOOK_LEFT) + { + gameRenderWorld->DebugLine( color, Height, Height + Left, lifetime ); + } + + if (flags & FEATURE_CORNER_RIGHT) + { + gameRenderWorld->DebugLine( color, Orig, Orig - Left, lifetime ); + } + else if (flags & FEATURE_LOOK_RIGHT) + { + gameRenderWorld->DebugLine( color, Height, Height - Left, lifetime ); + } + + if (flags & FEATURE_LOOK_OVER) + { + gameRenderWorld->DebugLine( color, Height, Height + (Normal()*mDebugRadius), lifetime ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction) +{ + gameRenderWorld->DebugFOV(color, origin, direction, mMax, 20.0f, mMin, 10.0f, 20.0f); +} + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin) +{ + static idVec3 up(0.0f,0.0f,-1.0f); + if (mMax<1.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMax * MAX_DISTANCE), 25); + } + if (mMin>0.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMin * MAX_DISTANCE), 25); + } +} +/////////////////////////////////////////////////////////////////////////// +// rvTestSet::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTestSet::DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin) +{ + static int lifetime = 0; + static idVec3 origin; + + origin = mOrigin; + if (mProjectOrigin) + { + origin = nonProjectedOrigin; + } + + gameRenderWorld->DebugArrow( color, origin, origin+mFacing * 25.0f, 8, lifetime ); + mFacingDot.DrawDebugInfo(color, origin, mFacing); + mDistance.DrawDebugInfo(color, origin); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::DrawDebugInfo() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden() || (ai_showTacticalFeatures.GetInteger()<2 && !mOwner->DebugFilter(ai_showTacticalFeatures) && !mOwner->DebugFilter(ai_debugTactical))) + { + return; + } + + + static idVec3 pos; + bool reservedDrawn = false; + bool nearDrawn = false; + bool lookDrawn = false; + + mDebugRadius = aas->GetSettings()->boundingBoxes[0][1][0]; + + + // Draw Parameters + //----------------- + if (ai_showTacticalFeatures.GetInteger()==1) + { + if (!mSearchName.IsEmpty()) + { + pos = mFromOwner.mOrigin + mFromEnemy.mOrigin; + pos *= 0.5f; + pos[2] += 25.0f; + gameRenderWorld->DrawText(mSearchName.c_str(), pos, 0.5f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, 0 ); + pos[2] -= 25.0f; + + // Draw Tests + //------------ + mFromEnemy.DrawDebugInfo(colorYellow, pos); + mFromTether.DrawDebugInfo(colorOrange, pos); + mFromOwner.DrawDebugInfo(colorMagenta, pos); + mFromPath.DrawDebugInfo(colorCyan, pos); + mAssignment.DrawDebugInfo(colorPink, pos); + mAdvance.DrawDebugInfo(colorPurple, mFromOwner.mOrigin, mFromPath.mFacing); + mLeanNormal.DrawDebugInfo(colorPurple, pos); + } + + + // Draw Features + //--------------- + for (int i=0; iDrawDebugInfo(i); + if (mFeatures[i]==mReserved) + { + reservedDrawn = true; + } + if (mFeatures[i]==mNear) + { + nearDrawn = true; + } + if (mFeatures[i]==mLook) + { + lookDrawn = true; + } + } + } + + // Draw All Neighboring Features If Player & CVar==2 + //--------------------------------------------------- + if (mOwner==gameLocal.GetLocalPlayer() && ai_showTacticalFeatures.GetInteger()>=2) + { + idAASFile* file = aas->GetFile(); + const idVec3& playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + for (int i=0; iGetNumFeatures(); i++) + { + if (file->GetFeature(i).Origin().Dist(playerOrigin)<600.0f) + { + file->GetFeature(i).DrawDebugInfo(); + } + } + } + + + // Always Draw Reserved + //---------------------- + if (mReserved) + { + if (!reservedDrawn) + { + mReserved->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorBlue, mOwner->GetPhysics()->GetOrigin(), mReserved->Origin(), 8); + } + + // If Near Is Valid, Draw It + //--------------------------- + if (mNear && (!mReserved || mNear!=mReserved)) + { + if (!nearDrawn) + { + mNear->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorOrange, mOwner->GetPhysics()->GetOrigin(), mNear->Origin(), 8); + } + + // If Look Is True, Then Draw That + //--------------------------------- + if (mLook && (!mOwnerAI || mOwnerAI->InLookAtCoverMode())) + { + idVec3 n = mLook->Normal(); + n *= 64.0f; + gameRenderWorld->DebugArrow(colorYellow, mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32), mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32) + n, 8); + } +} + + + + +// RAVENEND - CDR + + + + + diff --git a/src/game/ai/AAS_tactical.h b/src/game/ai/AAS_tactical.h new file mode 100644 index 0000000..3daf2ac --- /dev/null +++ b/src/game/ai/AAS_tactical.h @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/////////////////////////////////////////////////////////////////////////////// +// AAS_tactical +// +// This file is the interface to an AAS Tactical Extractor, which can search +// out from a given start point and report a variety of tactically important +// objectives, including corners, walls, and pinch points. +// +// By seeing the AAS graph as a qualitative and simplified spatial +// representation of the game world. This representation is amply capapble of +// rendering higher level tactical data efficiently in real time. +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __AAS_TACTICAL_H__ +#define __AAS_TACTICAL_H__ + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor +// +// The sensor structure is the public interface to the internals of AAS +// tactical features. +/////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensor +{ + // Regular Update Function + /////////////////////////////////////////////////////////////////////// + virtual void Update() = 0; + virtual void Save(idSaveGame *savefile) = 0; + virtual void Restore(idRestoreGame *savefile) = 0; + virtual void Clear() = 0; + + // Search + /////////////////////////////////////////////////////////////////////// + virtual void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchHide(idEntity* from=0) = 0; + virtual void SearchFlank() = 0; + virtual void SearchAdvance() = 0; + virtual void SearchRetreat() = 0; + virtual void SearchAmbush() = 0; + virtual void SearchDebug() = 0; + + // Feature Testing + /////////////////////////////////////////////////////////////////////// + virtual bool TestValid(aasFeature_t* f, float walkDistanceToFeature) = 0; + virtual bool TestValidWithCurrentState(aasFeature_t* f=0) = 0; + + // Feature Reservation + /////////////////////////////////////////////////////////////////////// + virtual void Reserve(aasFeature_t* f) = 0; + + // Access To Results + /////////////////////////////////////////////////////////////////////// + virtual int FeatureCount() = 0; + virtual aasFeature_t* Feature(int i) = 0; + virtual aasFeature_t* Near() const = 0; + virtual aasFeature_t* Look() const = 0; + virtual aasFeature_t* Reserved() const = 0; + virtual const idVec3& ReservedOrigin() const = 0; + + + + // STATIC SYSTEM FUNCTIONS + /////////////////////////////////////////////////////////////////////// + static rvAASTacticalSensor* CREATE_SENSOR(idActor* owner); +}; + + +#endif /* !__AAS_TACTICAL_H__ */ diff --git a/src/game/ai/AI.cpp b/src/game/ai/AI.cpp new file mode 100644 index 0000000..a53a88f --- /dev/null +++ b/src/game/ai/AI.cpp @@ -0,0 +1,5175 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "../Projectile.h" +#include "../spawner.h" +#include "AI_Tactical.h" + +const char* aiTalkMessageString [ ] = { + "None", + "primary", + "secondary", + "loop" +}; + +static const float AI_SIGHTDELAYSCALE = 5000.0f; // Full sight delay at 5 seconds or more of not seeing enemy + + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +/* +===================== +idAI::idAI +===================== +*/ +idAI::idAI ( void ) { + projectile_height_to_distance_ratio = 1.0f; + + aas = NULL; + aasSensor = NULL; + aasFind = NULL; + + lastHitCheckResult = false; + lastHitCheckTime = 0; + lastAttackTime = 0; + projectile = NULL; + projectileClipModel = NULL; + chatterTime = 0; + talkState = TALK_NEVER; + talkTarget = NULL; + talkMessage = TALKMSG_NONE; + talkBusyCount = 0; + + enemy.ent = NULL; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + + fl.neverDormant = false; // AI's can go dormant + + allowEyeFocus = true; + disablePain = false; + allowJointMod = true; + focusEntity = NULL; + focusTime = 0; + alignHeadTime = 0; + forceAlignHeadTime = 0; + + orientationJoint = INVALID_JOINT; + + eyeVerticalOffset = 0.0f; + eyeHorizontalOffset = 0.0f; + headFocusRate = 0.0f; + eyeFocusRate = 0.0f; + focusAlignTime = 0; + focusRange = 0.0f; + focusType = AIFOCUS_NONE; + + memset ( &combat.fl, 0, sizeof(combat.fl) ); + combat.max_chasing_turn = 0; + combat.shotAtTime = 0; + combat.shotAtAngle = 0.0f; + combat.meleeRange = 0.0f; + combat.tacticalPainTaken = 0; + combat.tacticalFlinches = 0; + combat.investigateTime = 0; + combat.aggressiveScale = 1.0f; + + passive.animFidgetPrefix.Clear ( ); + passive.animIdlePrefix.Clear ( ); + passive.animTalkPrefix.Clear ( ); + passive.idleAnim.Clear(); + passive.prefix.Clear(); + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + memset ( &passive.fl, 0, sizeof(passive.fl) ); + + pain.lastTakenTime = 0; + pain.takenThisFrame = 0; + pain.loopEndTime = 0; + + enemy.range = 0; + enemy.range2d = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + enemy.lastKnownPosition.Zero ( ); + enemy.lastVisibleEyePosition.Zero ( ); + enemy.lastVisibleChestPosition.Zero ( ); + enemy.lastVisibleFromEyePosition.Zero ( ); + enemy.checkTime = 0; + enemy.changeTime = 0; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + memset ( &enemy.fl, 0, sizeof(enemy.fl) ); + + currentFocusPos.Zero(); + eyeAng.Zero(); + lookAng.Zero(); + destLookAng.Zero(); + lookMin.Zero(); + lookMax.Zero(); + + eyeMin.Zero(); + eyeMax.Zero(); + + helperCurrent = NULL; + helperIdeal = NULL; + + speakTime = 0; + + actionAnimNum = 0; + actionSkipTime = 0; + actionTime = 0; +} + +/* +===================== +idAI::~idAI +===================== +*/ +idAI::~idAI() { + // Make sure we arent stuck in the simple think list + simpleThinkNode.Remove ( ); + + delete aasFind; + delete aasSensor; + delete projectileClipModel; + DeconstructScriptObject(); + scriptObject.Free(); + aiManager.RemoveTeammate ( this ); + SetPhysics( NULL ); +} + +/* +===================== +idAI::Save +===================== +*/ +void idAI::Save( idSaveGame *savefile ) const { + int i; + +// cnicholson: These 3 vars are intentionally not saved, as noted in the restore + // NOSAVE: idLinkList simpleThinkNode; + // NOSAVE: idAAS* aas; + // NOSAVE: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + // Movement + move.Save( savefile ); + savedMove.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + savefile->WriteBool( GetPhysics() == static_cast(&physicsObj) ); + + savefile->WriteBool( lastHitCheckResult ); + savefile->WriteInt( lastHitCheckTime ); + savefile->WriteInt( lastAttackTime ); + savefile->WriteFloat( projectile_height_to_distance_ratio ); + + savefile->WriteInt( attackAnimInfo.Num() ); + for( i = 0; i < attackAnimInfo.Num(); i++ ) { + savefile->WriteVec3( attackAnimInfo[ i ].attackOffset ); + savefile->WriteVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TOSAVE: mutable idClipModel* projectileClipModel; + projectile.Save ( savefile ); + + // Talking + savefile->WriteInt( chatterTime ); +// savefile->WriteInt( chatterRateCombat ); // NOSAVE: +// savefile->WriteInt( chatterRateIdle ); // NOSAVE: + savefile->WriteInt( talkState ); + talkTarget.Save( savefile ); + savefile->WriteInt( talkMessage ); + savefile->WriteInt( talkBusyCount ); + savefile->WriteInt ( speakTime ); + + // Focus + lookTarget.Save ( savefile ); + savefile->WriteInt( focusType ); + focusEntity.Save ( savefile ); + savefile->WriteFloat ( focusRange ); + savefile->WriteInt ( focusAlignTime ); + savefile->WriteInt ( focusTime ); + savefile->WriteVec3( currentFocusPos ); + + // Looking + savefile->WriteBool( allowJointMod ); + savefile->WriteInt( alignHeadTime ); + savefile->WriteInt( forceAlignHeadTime ); + savefile->WriteAngles( eyeAng ); + savefile->WriteAngles( lookAng ); + savefile->WriteAngles( destLookAng ); + savefile->WriteAngles( lookMin ); + savefile->WriteAngles( lookMax ); + + savefile->WriteInt( lookJoints.Num() ); + for( i = 0; i < lookJoints.Num(); i++ ) { + savefile->WriteJoint( lookJoints[ i ] ); + savefile->WriteAngles( lookJointAngles[ i ] ); + } + + savefile->WriteFloat( eyeVerticalOffset ); + savefile->WriteFloat( eyeHorizontalOffset ); + savefile->WriteFloat( headFocusRate ); + savefile->WriteFloat( eyeFocusRate ); + + // Joint Controllers + savefile->WriteAngles( eyeMin ); + savefile->WriteAngles( eyeMax ); + savefile->WriteJoint( orientationJoint ); + + pusher.Save( savefile ); // cnicholson: Added unsaved var + scriptedActionEnt.Save( savefile ); // cnicholson: Added unsaved var + + savefile->Write( &aifl, sizeof( aifl ) ); + + // Misc + savefile->WriteInt ( actionAnimNum ); + savefile->WriteInt ( actionTime ); + savefile->WriteInt ( actionSkipTime ); + savefile->WriteInt ( flagOverrides ); + + // Combat variables + savefile->Write( &combat.fl, sizeof( combat.fl ) ); + savefile->WriteFloat ( combat.max_chasing_turn ); + savefile->WriteFloat ( combat.shotAtTime ); + savefile->WriteFloat ( combat.shotAtAngle ); + savefile->WriteVec2 ( combat.hideRange ); + savefile->WriteVec2 ( combat.attackRange ); + savefile->WriteInt ( combat.attackSightDelay ); + savefile->WriteFloat ( combat.meleeRange ); + savefile->WriteFloat ( combat.aggressiveRange ); + savefile->WriteFloat ( combat.aggressiveScale ); + savefile->WriteInt ( combat.investigateTime ); + savefile->WriteFloat ( combat.visStandHeight ); + savefile->WriteFloat ( combat.visCrouchHeight ); + savefile->WriteFloat ( combat.visRange ); + savefile->WriteFloat ( combat.earRange ); + savefile->WriteFloat ( combat.awareRange ); + savefile->WriteInt ( combat.tacticalMaskAvailable ); + savefile->WriteInt ( (int&)combat.tacticalCurrent ); + savefile->WriteInt ( combat.tacticalUpdateTime ); + savefile->WriteInt ( combat.tacticalPainTaken ); + savefile->WriteInt ( combat.tacticalPainThreshold ); + savefile->WriteInt ( combat.tacticalFlinches ); + savefile->WriteInt ( combat.maxLostVisTime ); + savefile->WriteFloat ( combat.threatBase ); + savefile->WriteFloat ( combat.threatCurrent ); + savefile->WriteInt ( combat.coverValidTime ); + savefile->WriteInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->WriteString ( passive.prefix ); + savefile->WriteString ( passive.animFidgetPrefix ); + savefile->WriteString ( passive.animIdlePrefix); + savefile->WriteString ( passive.animTalkPrefix ); + savefile->WriteString ( passive.idleAnim ); + savefile->WriteInt ( passive.idleAnimChangeTime ); + savefile->WriteInt ( passive.fidgetTime ); + savefile->WriteInt ( passive.talkTime ); + savefile->Write ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Save ( savefile ); + savefile->Write ( &enemy.fl, sizeof(enemy.fl) ); + savefile->WriteInt ( enemy.lastVisibleChangeTime ); + savefile->WriteVec3 ( enemy.lastKnownPosition ); + savefile->WriteVec3 ( enemy.smoothedLinearVelocity ); + savefile->WriteVec3 ( enemy.smoothedPushedVelocity ); + savefile->WriteVec3 ( enemy.lastVisibleEyePosition ); + savefile->WriteVec3 ( enemy.lastVisibleChestPosition ); + savefile->WriteVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->WriteInt ( enemy.lastVisibleTime ); + savefile->WriteFloat ( enemy.range ); + savefile->WriteFloat ( enemy.range2d ); + savefile->WriteInt ( enemy.changeTime ); + savefile->WriteInt ( enemy.checkTime ); + + // Pain variables + savefile->WriteFloat ( pain.threshold ); + savefile->WriteFloat ( pain.takenThisFrame ); + savefile->WriteInt ( pain.lastTakenTime ); + savefile->WriteInt ( pain.loopEndTime ); + savefile->WriteString ( pain.loopType ); + + // Functions + funcs.first_sight.Save ( savefile ); + funcs.sight.Save ( savefile ); + funcs.pain.Save ( savefile ); + funcs.damage.Save ( savefile ); + funcs.death.Save ( savefile ); + funcs.attack.Save ( savefile ); + funcs.init.Save ( savefile ); + funcs.onclick.Save ( savefile ); + funcs.launch_projectile.Save ( savefile ); + funcs.footstep.Save ( savefile ); + + mPlayback.Save( savefile ); // cnicholson: Added save functionality + mLookPlayback.Save( savefile ); // cnicholson: Added save functionality + + // Tactical Sensor + aasSensor->Save( savefile ); + + // Helpers + tether.Save ( savefile ); + helperCurrent.Save ( savefile ); + helperIdeal.Save ( savefile ); + leader.Save ( savefile ); + spawner.Save ( savefile ); + + // Action timers + actionTimerRangedAttack.Save ( savefile ); + actionTimerEvade.Save ( savefile ); + actionTimerSpecialAttack.Save ( savefile ); + actionTimerPain.Save ( savefile ); + + // Actions + actionEvadeLeft.Save ( savefile ); + actionEvadeRight.Save ( savefile ); + actionRangedAttack.Save ( savefile ); + actionMeleeAttack.Save ( savefile ); + actionLeapAttack.Save ( savefile ); + actionJumpBack.Save ( savefile ); +} + +/* +===================== +idAI::Restore +===================== +*/ +void idAI::Restore( idRestoreGame *savefile ) { + bool restorePhysics; + int i; + int num; + idBounds bounds; + + InitNonPersistentSpawnArgs ( ); + + // INTENTIONALLY NOT SAVED: idLinkList simpleThinkNode; + // INTENTIONALLY NOT SAVED: idAAS* aas; + // INTENTIONALLY NOT SAVED: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + move.Restore( savefile ); + savedMove.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + savefile->ReadBool( restorePhysics ); + if ( restorePhysics ) { + RestorePhysics( &physicsObj ); + } + + savefile->ReadBool( lastHitCheckResult ); + savefile->ReadInt( lastHitCheckTime ); + savefile->ReadInt( lastAttackTime ); + savefile->ReadFloat( projectile_height_to_distance_ratio ); + + savefile->ReadInt( num ); + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( attackAnimInfo[ i ].attackOffset ); + savefile->ReadVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TORESTORE: mutable idClipModel* projectileClipModel; + projectile.Restore( savefile ); + + // Talking + savefile->ReadInt( chatterTime ); +// savefile->ReadInt( chatterRateCombat ); // Don't save +// savefile->ReadInt( chatterRateIdle ); // Don't save + savefile->ReadInt( i ); + talkState = static_cast( i ); + talkTarget.Restore( savefile ); + savefile->ReadInt( i ); + talkMessage = static_cast( i ); + savefile->ReadInt( talkBusyCount ); + savefile->ReadInt ( speakTime ); + + // Focus + lookTarget.Restore ( savefile ); + savefile->ReadInt( i ); + focusType = static_cast( i ); + focusEntity.Restore ( savefile ); + savefile->ReadFloat ( focusRange ); + savefile->ReadInt ( focusAlignTime ); + savefile->ReadInt ( focusTime ); + savefile->ReadVec3( currentFocusPos ); + + // Looking + savefile->ReadBool( allowJointMod ); + savefile->ReadInt( alignHeadTime ); + savefile->ReadInt( forceAlignHeadTime ); + savefile->ReadAngles( eyeAng ); + savefile->ReadAngles( lookAng ); + savefile->ReadAngles( destLookAng ); + savefile->ReadAngles( lookMin ); + savefile->ReadAngles( lookMax ); + + savefile->ReadInt( num ); + lookJoints.SetGranularity( 1 ); + lookJoints.SetNum( num ); + lookJointAngles.SetGranularity( 1 ); + lookJointAngles.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadJoint( lookJoints[ i ] ); + savefile->ReadAngles( lookJointAngles[ i ] ); + } + + savefile->ReadFloat( eyeVerticalOffset ); + savefile->ReadFloat( eyeHorizontalOffset ); + savefile->ReadFloat( headFocusRate ); + savefile->ReadFloat( eyeFocusRate ); + + // Joint Controllers + savefile->ReadAngles( eyeMin ); + savefile->ReadAngles( eyeMax ); + savefile->ReadJoint( orientationJoint ); + + pusher.Restore( savefile ); // cnicholson: Added unrestored var + scriptedActionEnt.Restore ( savefile ); // cnicholson: Added unrestored var + + savefile->Read( &aifl, sizeof( aifl ) ); + + // Misc + savefile->ReadInt ( actionAnimNum ); + savefile->ReadInt ( actionTime ); + savefile->ReadInt ( actionSkipTime ); + savefile->ReadInt ( flagOverrides ); + + // Combat variables + savefile->Read ( &combat.fl, sizeof( combat.fl ) ); + savefile->ReadFloat ( combat.max_chasing_turn ); + savefile->ReadFloat ( combat.shotAtTime ); + savefile->ReadFloat ( combat.shotAtAngle ); + savefile->ReadVec2 ( combat.hideRange ); + savefile->ReadVec2 ( combat.attackRange ); + savefile->ReadInt ( combat.attackSightDelay ); + savefile->ReadFloat ( combat.meleeRange ); + savefile->ReadFloat ( combat.aggressiveRange ); + savefile->ReadFloat ( combat.aggressiveScale ); + savefile->ReadInt ( combat.investigateTime ); + savefile->ReadFloat ( combat.visStandHeight ); + savefile->ReadFloat ( combat.visCrouchHeight ); + savefile->ReadFloat ( combat.visRange ); + savefile->ReadFloat ( combat.earRange ); + savefile->ReadFloat ( combat.awareRange ); + savefile->ReadInt ( combat.tacticalMaskAvailable ); + savefile->ReadInt ( (int&)combat.tacticalCurrent ); + savefile->ReadInt ( combat.tacticalUpdateTime ); + savefile->ReadInt ( combat.tacticalPainTaken ); + savefile->ReadInt ( combat.tacticalPainThreshold ); + savefile->ReadInt ( combat.tacticalFlinches ); + savefile->ReadInt ( combat.maxLostVisTime ); + savefile->ReadFloat ( combat.threatBase ); + savefile->ReadFloat ( combat.threatCurrent ); + savefile->ReadInt ( combat.coverValidTime ); + savefile->ReadInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->ReadString ( passive.prefix ); + savefile->ReadString ( passive.animFidgetPrefix ); + savefile->ReadString ( passive.animIdlePrefix); + savefile->ReadString ( passive.animTalkPrefix ); + savefile->ReadString ( passive.idleAnim ); + savefile->ReadInt ( passive.idleAnimChangeTime ); + savefile->ReadInt ( passive.fidgetTime ); + savefile->ReadInt ( passive.talkTime ); + savefile->Read ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Restore ( savefile ); + savefile->Read ( &enemy.fl, sizeof(enemy.fl) ); + savefile->ReadInt ( enemy.lastVisibleChangeTime ); + savefile->ReadVec3 ( enemy.lastKnownPosition ); + savefile->ReadVec3 ( enemy.smoothedLinearVelocity ); + savefile->ReadVec3 ( enemy.smoothedPushedVelocity ); + savefile->ReadVec3 ( enemy.lastVisibleEyePosition ); + savefile->ReadVec3 ( enemy.lastVisibleChestPosition ); + savefile->ReadVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->ReadInt ( enemy.lastVisibleTime ); + savefile->ReadFloat ( enemy.range ); + savefile->ReadFloat ( enemy.range2d ); + savefile->ReadInt ( enemy.changeTime ); + savefile->ReadInt ( enemy.checkTime ); + + // Pain variables + savefile->ReadFloat ( pain.threshold ); + savefile->ReadFloat ( pain.takenThisFrame ); + savefile->ReadInt ( pain.lastTakenTime ); + savefile->ReadInt ( pain.loopEndTime ); + savefile->ReadString ( pain.loopType ); + + // Functions + funcs.first_sight.Restore ( savefile ); + funcs.sight.Restore ( savefile ); + funcs.pain.Restore ( savefile ); + funcs.damage.Restore ( savefile ); + funcs.death.Restore ( savefile ); + funcs.attack.Restore ( savefile ); + funcs.init.Restore ( savefile ); + funcs.onclick.Restore ( savefile ); + funcs.launch_projectile.Restore ( savefile ); + funcs.footstep.Restore ( savefile ); + + mPlayback.Restore( savefile ); // cnicholson: Added restore functionality + mLookPlayback.Restore( savefile ); // cnicholson: Added restore functionality + + // Tactical Sensor + aasSensor->Restore( savefile ); + + // Helpers + tether.Restore ( savefile ); + helperCurrent.Restore( savefile ); + helperIdeal.Restore ( savefile ); + leader.Restore ( savefile ); + spawner.Restore ( savefile ); + + // Action timers + actionTimerRangedAttack.Restore ( savefile ); + actionTimerEvade.Restore ( savefile ); + actionTimerSpecialAttack.Restore ( savefile ); + actionTimerPain.Restore ( savefile ); + + // Actions + actionEvadeLeft.Restore ( savefile ); + actionEvadeRight.Restore ( savefile ); + actionRangedAttack.Restore ( savefile ); + actionMeleeAttack.Restore ( savefile ); + actionLeapAttack.Restore ( savefile ); + actionJumpBack.Restore ( savefile ); + + // Set the AAS if the character has the correct gravity vector + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + if ( gravity == gameLocal.GetGravity() ) { + SetAAS(); + } + + // create combat collision hull for exact collision detection, do initial set un-hidden + SetCombatModel(); + LinkCombat(); +} + +/* +===================== +idAI::InitNonPersistentSpawnArgs +===================== +*/ +void idAI::InitNonPersistentSpawnArgs ( void ) { + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + aasFind = NULL; + + simpleThinkNode.Remove ( ); + simpleThinkNode.SetOwner ( this ); + + chatterRateIdle = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_idle", "0" ) ); + chatterRateCombat = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_combat", "0" ) ); + chatterTime = 0; + + combat.tacticalMaskUpdate = 0; + + enemy.smoothVelocityRate = spawnArgs.GetFloat ( "smoothVelocityRate", "0.1" ); +} + +/* +===================== +idAI::Spawn +===================== +*/ +void idAI::Spawn( void ) { + const char* jointname; + const idKeyValue* kv; + idStr jointName; + idAngles jointScale; + jointHandle_t joint; + idVec3 local_dir; + + // Are all monsters disabled? + if ( !g_monsters.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // Initialize the non saved spawn args + InitNonPersistentSpawnArgs ( ); + + spawnArgs.GetInt( "team", "1", team ); + spawnArgs.GetInt( "rank", "0", rank ); + + animPrefix = spawnArgs.GetString ( "animPrefix", "" ); + + // Standard flags + fl.notarget = spawnArgs.GetBool ( "notarget", "0" ); + fl.quickBurn = false; + + // AI flags + flagOverrides = 0; + memset ( &aifl, 0, sizeof(aifl) ); + aifl.ignoreFlashlight = spawnArgs.GetBool ( "ignore_flashlight", "1" ); + aifl.lookAtPlayer = spawnArgs.GetBool ( "lookAtPlayer", "0" ); + aifl.disableLook = spawnArgs.GetBool ( "noLook", "0" ); + aifl.undying = spawnArgs.GetBool ( "undying", "0" ); + aifl.killerGuard = spawnArgs.GetBool ( "killer_guard", "0" ); + aifl.scriptedEndWithIdle = true; + + // Setup Move Data + move.Spawn( spawnArgs ); + + pain.threshold = spawnArgs.GetInt ( "painThreshold", "0" ); + pain.takenThisFrame = 0; + + // Initialize combat variables + combat.fl.aware = spawnArgs.GetBool ( "ambush", "0" ); + combat.fl.tetherNoBreak = spawnArgs.GetBool ( "tetherNoBreak", "0" ); + combat.fl.noChatter = spawnArgs.GetBool ( "noCombatChatter" ); + combat.hideRange = spawnArgs.GetVec2 ( "hideRange", "150 750" ); + combat.attackRange = spawnArgs.GetVec2 ( "attackRange", "0 1000" ); + combat.attackSightDelay = SEC2MS ( spawnArgs.GetFloat ( "attackSightDelay", "1" ) ); + combat.visRange = spawnArgs.GetFloat( "visRange", "2048" ); + combat.visStandHeight = spawnArgs.GetFloat( "visStandHeight", "68" ); + combat.visCrouchHeight = spawnArgs.GetFloat( "visCrouchHeight", "48" ); + combat.earRange = spawnArgs.GetFloat( "earRange", "2048" ); + combat.awareRange = spawnArgs.GetFloat( "awareRange", "150" ); + combat.aggressiveRange = spawnArgs.GetFloat( "aggressiveRange", "0" ); + combat.maxLostVisTime = SEC2MS ( spawnArgs.GetFloat ( "maxLostVisTime", "10" ) ); + combat.tacticalPainThreshold = spawnArgs.GetInt ( "tactical_painThreshold", va("%d", health / 4) ); + combat.coverValidTime = 0; + combat.maxInvalidCoverTime = SEC2MS ( spawnArgs.GetFloat ( "maxInvalidCoverTime", "1" ) ); + combat.threatBase = spawnArgs.GetFloat ( "threatBase", "1" ); + combat.threatCurrent = combat.threatBase; + + SetPassivePrefix ( spawnArgs.GetString ( "passivePrefix" ) ); + + disablePain = spawnArgs.GetBool ( "nopain", "0" ); + + spawnArgs.GetFloat( "melee_range", "64", combat.meleeRange ); + spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio ); + + //melee superhero -- take far reduced damage from melee. + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) && spawnArgs.GetBool( "meleeSuperhero", "1") ) { + aifl.meleeSuperhero = true; + } else { + aifl.meleeSuperhero = false; + } + + //announce rate + announceRate = spawnArgs.GetFloat( "announceRate" ); + if( 0.0f == announceRate ) { + announceRate = AISPEAK_CHANCE; + } + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + + animator.RemoveOriginOffset( true ); + + // create combat collision hull for exact collision detection + SetCombatModel(); + + lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" ); + lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" ); + + lookJoints.SetGranularity( 1 ); + lookJointAngles.SetGranularity( 1 ); + kv = spawnArgs.MatchPrefix( "look_joint", NULL ); + while( kv ) { + jointName = kv->GetKey(); + jointName.StripLeadingOnce( "look_joint " ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() ); + } else { + jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" ); + jointScale.roll = 0.0f; + + // if no scale on any component, then don't bother adding it. this may be done to + // zero out rotation from an inherited entitydef. + if ( jointScale != ang_zero ) { + lookJoints.Append( joint ); + lookJointAngles.Append( jointScale ); + } + } + kv = spawnArgs.MatchPrefix( "look_joint", kv ); + } + + // calculate joint positions on attack frames so we can do proper "can hit" tests + CalculateAttackOffsets(); + + eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" ); + eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" ); + eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" ); + eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" ); + headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.06" ); + eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" ); + focusRange = spawnArgs.GetFloat( "focus_range", "0" ); + focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) ); + + jointname = spawnArgs.GetString( "joint_orientation" ); + if ( *jointname ) { + orientationJoint = animator.GetJointHandle( jointname ); + if ( orientationJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + jointname = spawnArgs.GetString( "joint_flytilt" ); + if ( *jointname ) { + move.flyTiltJoint = animator.GetJointHandle( jointname ); + if ( move.flyTiltJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY ); + } else { + if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + } + + // move up to make sure the monster is at least an epsilon above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + physicsObj.SetGravity( gravity ); + + SetPhysics( &physicsObj ); + + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + lookAng.Zero ( ); + lookAng.yaw = move.current_yaw; + + + SetAAS(); + + projectile = NULL; + projectileClipModel = NULL; + + if ( spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "trigger_anim" ) ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + Hide(); + } else { + // play a looping ambient sound if we have one + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); + } + + if ( health <= 0 ) { + gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() ); + health = 1; + } + + BecomeActive( TH_THINK ); + + if ( move.fl.allowPushMovables ) { + af.SetupPose( this, gameLocal.time ); + af.GetPhysics()->EnableClip(); + } + + // init the move variables + StopMove( MOVE_STATUS_DONE ); + + // Initialize any scripts + idStr prefix = "script_"; + for ( kv = spawnArgs.MatchPrefix ( prefix.c_str(), NULL ); + kv; + kv = spawnArgs.MatchPrefix ( prefix.c_str(), kv ) ) { + SetScript ( kv->GetKey().c_str() + prefix.Length(), kv->GetValue() ); + } + + // Initialize actions + actionEvadeLeft.Init ( spawnArgs, "action_evadeLeft", NULL, 0 ); + actionEvadeRight.Init ( spawnArgs, "action_evadeRight", NULL, 0 ); + actionRangedAttack.Init ( spawnArgs, "action_rangedAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK|AIACTIONF_MELEE ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", NULL, AIACTIONF_ATTACK ); + actionJumpBack.Init ( spawnArgs, "action_jumpBack", NULL, 0 ); + + // Global action timers + actionTimerRangedAttack.Init ( spawnArgs, "actionTimer_RangedAttack" ); + actionTimerEvade.Init ( spawnArgs, "actionTimer_Evade" ); + actionTimerSpecialAttack.Init ( spawnArgs, "actionTimer_SpecialAttack" ); + actionTimerPain.Init ( spawnArgs, "actionTimer_pain" ); + + // Available tactical options + combat.tacticalUpdateTime = 0; + combat.tacticalCurrent = AITACTICAL_NONE; + combat.tacticalMaskUpdate = 0; + combat.tacticalMaskAvailable = AITACTICAL_TURRET_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_MOVE_TETHER_BIT; + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_cover", "0" ) ? AITACTICAL_COVER_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_ranged", "0" ) ? AITACTICAL_RANGED_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_hide", "0" ) ? AITACTICAL_HIDE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_rush", "0" ) ? AITACTICAL_MELEE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_passive", "0" ) ? AITACTICAL_PASSIVE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "allowPlayerPush", "0" ) ? AITACTICAL_MOVE_PLAYERPUSH_BIT : 0); + + // Talking? + const char* npc; + if ( spawnArgs.GetString( "npc_name", NULL, &npc ) != NULL && *npc ) { + if ( spawnArgs.GetBool ( "follows", "0" ) ) { + SetTalkState ( TALK_FOLLOW ); + } else { + switch ( spawnArgs.GetInt( "talks" ) ) + { + case 2: + SetTalkState ( TALK_WAIT ); + break; + case 1: + SetTalkState ( TALK_OK ); + break; + case 0: + default: + SetTalkState ( TALK_BUSY ); + break; + } + } + } else { + SetTalkState ( TALK_NEVER ); + } + + // Passive or aggressive ai? + if ( spawnArgs.GetBool ( "passive" ) ) { + Event_BecomePassive ( true ); + + if ( spawnArgs.GetInt ( "passive" ) > 1 ) { + aifl.disableLook = true; + } + } + + // Print out a warning about any AI that is spawned unhidden since they will be all thinking + if( gameLocal.GameState ( ) == GAMESTATE_STARTUP && !spawnArgs.GetInt( "hide" ) && !spawnArgs.GetInt( "trigger_anim" ) && !spawnArgs.GetInt( "trigger_cover" ) && !spawnArgs.GetInt( "trigger_move" ) ){ + gameLocal.Warning( "Unhidden AI placed in map (will be constantly active): %s (%s)", name.c_str(), GetPhysics()->GetOrigin().ToString() ); + } + + Begin ( ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + PostEventMS( &EV_PostSpawn, 0 ); + // RAVEN END +} + +/* +=================== +idAI::Begin +=================== +*/ +void idAI::Begin ( void ) { + const char* temp; + bool animWalk; + bool animRun; + + // Look for the lack of a run or walk animation and force the opposite + animWalk = HasAnim ( ANIMCHANNEL_LEGS, "walk" ); + animRun = HasAnim ( ANIMCHANNEL_LEGS, "run" ); + if ( !animWalk && animRun ) { + move.fl.noWalk = true; + } else if ( !animRun && animWalk ) { + move.fl.noRun = true; + } + + // If a trigger anim or hide is specified then wait until activated before waking up + if ( spawnArgs.GetString ( "trigger_anim", "", &temp ) || spawnArgs.GetBool ( "hide" ) ) { + Hide(); + PostState ( "Wait_Activated" ); + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + // Wake up + } else { + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + } +} + +/* +=================== +idAI::WakeUp +=================== +*/ +void idAI::WakeUp ( void ) { + const char* temp; + + // Already awake? + if ( aifl.awake ) { + return; + } + + // Find the closest helper + UpdateHelper ( ); + + aifl.awake = true; + + // If the monster is flying then start them with the flying movement type + if ( spawnArgs.GetBool ( "static" ) ) { + SetMoveType ( MOVETYPE_STATIC ); + } else if ( spawnArgs.GetBool ( "flying" ) ) { + SetMoveType ( MOVETYPE_FLY ); + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += move.fly_offset; + } else { + SetMoveType ( MOVETYPE_ANIM ); + } + + // Wake up any linked entities + WakeUpTargets ( ); + + // Default enemy? + if ( !combat.fl.ignoreEnemies ) { + if ( spawnArgs.GetString ( "enemy", "", &temp ) && *temp ) { + SetEnemy ( gameLocal.FindEntity ( temp ) ); + } else if ( spawnArgs.GetBool ( "forceEnemy", "0" ) ) { + SetEnemy ( FindEnemy ( false, true ) ); + } + } + + // Default leader? + if ( spawnArgs.GetString ( "leader", "", &temp ) && *temp ) { + SetLeader ( gameLocal.FindEntity ( temp ) ); + } + + // If hidden and face enemy is specified we should orient ourselves toward our enemy immediately + if ( enemy.ent && IsHidden ( ) && spawnArgs.GetBool ( "faceEnemy" ) ) { + TurnToward ( enemy.ent->GetPhysics()->GetOrigin() ); + move.current_yaw = move.ideal_yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + Show ( ); + + aiManager.AddTeammate ( this ); + + // External script functions + ExecScriptFunction( funcs.init, this ); + + OnWakeUp ( ); +} + +/* +=================== +idAI::List_f +=================== +*/ +void idAI::List_f( const idCmdArgs &args ) { + int e; + int i; + idEntity* check; + int count; + idDict countsFixed; + idDict countsSpawned; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" ); + gameLocal.Printf( "------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + if ( !check ) { + continue; + } + + if ( check->IsType ( idAI::Type ) ) { + idAI* checkAI = static_cast(check); + + // Skip spawned AI + if ( checkAI->spawner ) { + continue; + } + countsFixed.SetInt ( check->GetEntityDefName(), countsFixed.GetInt ( check->GetEntityDefName(), "0" ) + 1 ); + + gameLocal.Printf( "%4i: %-20s %-20s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), checkAI->move.fl.allowAnimMove ); + count++; + } else if ( check->IsType ( rvSpawner::GetClassType() ) ) { + const idKeyValue* kv; + rvSpawner* checkSpawner = static_cast(check); + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + for ( i = 0; i < checkSpawner->GetNumSpawnPoints(); i ++ ) { + check = checkSpawner->GetSpawnPoint ( i ); + if ( !check ) { + continue; + } + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + } + } + } + + // Combine the two lists + for ( e = 0; e < countsSpawned.GetNumKeyVals(); e ++ ) { + const char* keyName = countsSpawned.GetKeyVal ( e )->GetKey(); + countsFixed.Set ( keyName, va("(%s) %3d", + countsSpawned.GetKeyVal ( e )->GetValue().c_str(), + countsFixed.GetInt ( keyName, "0" ) + atoi(countsSpawned.GetKeyVal ( e )->GetValue()) ) ); + } + + // Print out total counts + if ( countsFixed.GetNumKeyVals() ) { + gameLocal.Printf( "------------------------------------------------\n" ); + for ( e = 0; e < countsFixed.GetNumKeyVals(); e ++ ) { + const idKeyValue* kv = countsFixed.GetKeyVal ( e ); + gameLocal.Printf( "%10s: %-20s\n", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + } + + gameLocal.Printf( "------------------------------------------------\n" ); + gameLocal.Printf( "...%d monsters (%d unique types)\n", count, countsFixed.GetNumKeyVals() ); +} + +/* +================ +idAI::DormantBegin + +called when entity becomes dormant +================ +*/ +void idAI::DormantBegin( void ) { + // since dormant happens on a timer, we wont get to update particles to + // hidden through the think loop, but we need to hide them though. + if ( enemyNode.InList() ) { + // remove ourselves from the enemy's enemylist + enemyNode.Remove(); + } + idActor::DormantBegin(); +} + +/* +================ +idAI::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idAI::DormantEnd( void ) { + if ( enemy.ent && !enemyNode.InList() ) { + // let our enemy know we're back on the trail + idActor *enemyEnt = dynamic_cast< idActor *>( enemy.ent.GetEntity() ); + if( enemyEnt ){ + enemyNode.AddToEnd( enemyEnt->enemyList ); + } + } + idActor::DormantEnd(); +} + +/* +===================== +idAI::ValidateCover + +Validate the reserved cover feature with current conditions. Also quickly tests if +cover does not face enemy at all, which triggers an instant update (ignoring maxCoverInvalidTime) +===================== +*/ +bool idAI::ValidateCover( ) { + if ( !InCoverMode( ) ) { + return false; + } + + if ( aasSensor->TestValidWithCurrentState() && (!enemy.ent || enemy.range > combat.awareRange ) ) { + combat.coverValidTime = gameLocal.time; + } else if ( combat.coverValidTime && !IsCoverValid ( ) ) { + combat.coverValidTime = 0; + OnCoverInvalidated(); + } + + return IsCoverValid(); +} + +/* +===================== +idAI::DoDormantTests +===================== +*/ +bool idAI::DoDormantTests ( void ) { + // If in a scripted move that we should never go dormant in + if ( aifl.scripted && aifl.scriptedNeverDormant ) { + return false; + } + // If following a player never go dormant + if ( leader && leader->IsType ( idPlayer::GetClassType ( ) ) ) { + return false; + } + // AI should no longer go dormant when outside of tether + if ( IsTethered () && !IsWithinTether ( ) ) { + return false; + } + return idActor::DoDormantTests ( ); +} + +/* +===================== +idAI::Think +===================== +*/ +void idAI::Think( void ) { + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + // Simple think this frame? + aifl.simpleThink = aiManager.IsSimpleThink ( this ); + + aiManager.thinkCount++; + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Start ( ); + } + + if ( thinkFlags & TH_THINK ) { + // clear out the enemy when he dies or is hidden + idEntity* enemyEnt = enemy.ent; + idActor* enemyAct = dynamic_cast( enemyEnt ); + + // Clear our enemy if necessary + if ( enemyEnt ) { + if (enemyAct && enemyAct->IsInVehicle()) { + SetEnemy(enemyAct->GetVehicleController().GetVehicle()); // always get angry at the enemy's vehicle first, not the enemy himself + } else { + bool enemyDead = (enemyEnt->fl.takedamage && enemyEnt->health <= 0); + if ( enemyDead || enemyEnt->fl.notarget || enemyEnt->IsHidden() || (enemyAct && enemyAct->team == team)) { + ClearEnemy ( enemyDead ); + } + } + } + + // Action time is stopped when the torso is not idle + if ( !aifl.action ) { + actionTime += gameLocal.msec; + } + + ValidateCover(); + + move.current_yaw += deltaViewAngles.yaw; + move.ideal_yaw = idMath::AngleNormalize180( move.ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + + if( move.moveType != MOVETYPE_PLAYBACK ){ + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + if ( !move.fl.allowHiddenMove && IsHidden() ) { + // hidden monsters + UpdateStates (); + } else if( !ai_freeze.GetBool() ) { + Prethink(); + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // update enemy position if not dead + if ( !aifl.dead ) { + UpdateEnemy ( ); + } + + // update state machine + UpdateStates(); + + // run all movement commands + Move(); + + // if not dead, chatter and blink + if( move.moveType != MOVETYPE_DEAD ){ + UpdateChatter(); + CheckBlink(); + } + + Postthink(); + } else { + DrawTactical ( ); + } + + // clear pain flag so that we recieve any damage between now and the next time we run the script + aifl.pain = false; + aifl.damage = false; + aifl.pushed = false; + pusher = NULL; + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + if ( move.fl.allowPushMovables ) { + PushWithAF(); + } + + if ( fl.hidden && move.fl.allowHiddenMove ) { + // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } + + aasSensor->Update(); + + UpdateAnimation(); + Present(); + LinkCombat(); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Stop ( ); + } +} + +/* +============ +idAI::UpdateFocus +============ +*/ +void idAI::UpdateFocus ( const idMat3& orientationAxis ) { + // Alwasy look at enemy + if ( !allowJointMod || !allowEyeFocus ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( IsBehindCover() && !aifl.action && !IsSpeaking() ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( !aifl.scripted && (IsEnemyRecentlyVisible(2.0f) || gameLocal.GetTime()-lastAttackTime<1000) ) { + //not scripted and: have an enemy OR we shot at an enemy recently (for when we kill an enemy in the middle of a volley) + SetFocus ( AIFOCUS_ENEMY, 1000 ); + } else if ( move.fl.moving && InCoverMode ( ) && DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_COVER, 1000 ); + } else if ( InLookAtCoverMode() ) { + SetFocus ( AIFOCUS_COVERLOOK, 1000 ); + } else if ( talkTarget && gameLocal.time < passive.talkTime ) { + SetFocus ( AIFOCUS_TALK, 100 ); + } else if ( lookTarget ) { + SetFocus ( AIFOCUS_TARGET, 100 ); + } else if ( !IsBehindCover() && tether && DistanceTo ( move.moveDest ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_TETHER, 100 ); + } else if ( IsTethered() && (move.fl.moving || IsBehindCover()) ) { + //tethered and at cover or moving - don't look at leader or player + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( helperCurrent && helperCurrent->IsCombat ( ) ) { + SetFocus ( AIFOCUS_HELPER, 100 ); + } else if ( !aifl.scripted && !move.fl.moving ) { + idEntity* lookat = NULL; + aiFocus_t newfocus = AIFOCUS_NONE; + + if ( leader ) { + idVec3 dir2Leader = leader->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin(); + if ( (viewAxis[0] * dir2Leader) > 0.0f ) { + //leader is in front of me + newfocus = AIFOCUS_LEADER; + lookat = leader; + } else if ( focusType == AIFOCUS_LEADER ) { + //stop looking at him! + SetFocus ( AIFOCUS_NONE, 0 ); + } + } else if ( aifl.lookAtPlayer && !move.fl.moving ) { + newfocus = AIFOCUS_PLAYER; + lookat = gameLocal.GetLocalPlayer(); + } + + if ( newfocus != AIFOCUS_NONE && lookat ) { + if ( DistanceTo ( lookat ) < focusRange * (move.fl.moving?2.0f:1.0f) && CheckFOV ( lookat->GetPhysics()->GetOrigin() ) ) { + SetFocus ( newfocus, 1000 ); + } + } + } + + // Make sure cover look wasnt invalidated + if ( focusType == AIFOCUS_COVERLOOK && !aasSensor->Look() ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_COVER && !aasSensor->Reserved ( ) ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_HELPER && !helperCurrent ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_TETHER && !tether ) { + focusType = AIFOCUS_NONE; + } + + // Update focus type to none when the focus time runs out + if ( focusType != AIFOCUS_NONE ) { + if ( gameLocal.time > focusTime ) { + focusType = AIFOCUS_NONE; + } + } + + // Calculate the focus position + if ( focusType == AIFOCUS_NONE ) { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } else if ( focusType == AIFOCUS_COVER ) { + currentFocusPos = GetEyePosition() + aasSensor->Reserved()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_COVERLOOK ) { + currentFocusPos = GetEyePosition() + aasSensor->Look()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_ENEMY ) { + currentFocusPos = enemy.lastVisibleEyePosition; + } else if ( focusType == AIFOCUS_HELPER ) { + currentFocusPos = helperCurrent->GetPhysics()->GetOrigin() + helperCurrent->GetDirection ( this ); + } else if ( focusType == AIFOCUS_TETHER ) { + currentFocusPos = GetEyePosition() + tether->GetPhysics()->GetAxis()[0] * 64.0f; + } else { + idEntity* focusEnt = NULL; + switch ( focusType ) { + case AIFOCUS_LEADER: focusEnt = leader; break; + case AIFOCUS_PLAYER: focusEnt = gameLocal.GetLocalPlayer(); break; + case AIFOCUS_TALK: focusEnt = talkTarget; break; + case AIFOCUS_TARGET: focusEnt = lookTarget; break; + } + if ( focusEnt ) { + if ( focusEnt->IsType ( idActor::GetClassType ( ) ) ) { + currentFocusPos = static_cast(focusEnt)->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + currentFocusPos = focusEnt->GetPhysics()->GetOrigin(); + } + } else { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } + } + + //draw it so we can see what they think they're looking at! + if ( DebugFilter(ai_debugMove) ) { // Cyan & Blue = currentFocusPos + if ( focusType != AIFOCUS_NONE ) { + gameRenderWorld->DebugArrow( colorCyan, GetEyePosition(), currentFocusPos, 0 ); + } else { + gameRenderWorld->DebugArrow( colorBlue, GetEyePosition(), currentFocusPos, 0 ); + } + } +} + +/* +===================== +idAI::UpdateStates +===================== +*/ +void idAI::UpdateStates ( void ) { + MEM_SCOPED_TAG(tag,MA_DEFAULT); + + // Continue updating tactical state if for some reason we dont have one + if ( !aifl.dead && !aifl.scripted && !aifl.action && stateThread.IsIdle ( ) && aifl.scriptedEndWithIdle ) { + UpdateTactical ( 0 ); + } else { + UpdateState(); + } + + // clear the hit enemy flag so we catch the next time we hit someone + aifl.hitEnemy = false; + + if ( move.fl.allowHiddenMove || !IsHidden() ) { + // update the animstate if we're not hidden + UpdateAnimState(); + } +} + +/* +===================== +idAI::OnStateChange +===================== +*/ +void idAI::OnStateChange ( int channel ) { +} + +/* +===================== +idAI::OverrideFlag +===================== +*/ +void idAI::OverrideFlag ( aiFlagOverride_t flag, bool value ) { + bool oldValue; + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: oldValue = disablePain; break; + case AIFLAGOVERRIDE_DAMAGE: oldValue = fl.takedamage; break; + case AIFLAGOVERRIDE_NOTURN: oldValue = move.fl.noTurn; break; + case AIFLAGOVERRIDE_NOGRAVITY: oldValue = move.fl.noGravity; break; + default: + return; + } + + if ( oldValue == value ) { + return; + } + + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + + if ( (flagOverrides & flagOverride) && !!(flagOverrides & flagOverrideValue) == value ) { + flagOverrides &= ~flagOverride; + } else { + if ( oldValue ) { + flagOverrides |= flagOverrideValue; + } else { + flagOverrides &= ~flagOverrideValue; + } + flagOverrides |= flagOverride; + } + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/* +===================== +idAI::RestoreFlag +===================== +*/ +void idAI::RestoreFlag ( aiFlagOverride_t flag ) { + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + bool value; + + if ( !(flagOverrides&flagOverride) ) { + return; + } + + value = !!(flagOverrides & flagOverrideValue); + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +===================== +idAI::ReactionTo +===================== +*/ +int idAI::ReactionTo( const idEntity *ent ) { + + if ( ent->fl.hidden ) { + // ignore hidden entities + return ATTACK_IGNORE; + } + + if ( !ent->IsType( idActor::GetClassType() ) ) { + return ATTACK_IGNORE; + } + + if( combat.fl.ignoreEnemies ){ + return ATTACK_IGNORE; + } + + const idActor *actor = static_cast( ent ); + if ( actor->IsType( idPlayer::GetClassType() ) && static_cast(actor)->noclip ) { + // ignore players in noclip mode + return ATTACK_IGNORE; + } + + // actors on different teams will always fight each other + if ( actor->team != team ) { + if ( actor->fl.notarget ) { + // don't attack on sight when attacker is notargeted + return ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE ; */ + } + return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE; */ + } + + //FIXME: temporarily disabled monsters of same rank fighting because it's happening too much. + // monsters will fight when attacked by lower or equal ranked monsters. rank 0 never fights back. + //if ( rank && ( actor->rank <= rank ) ) { + if ( rank && ( actor->rank < rank ) ) { + return ATTACK_ON_DAMAGE; + } + + // don't fight back + return ATTACK_IGNORE; +} + +/* +===================== +idAI::AdjustHealthByDamage +===================== +*/ +void idAI::AdjustHealthByDamage ( int damage ) { + if ( aifl.undying ) { + return; + } + idActor::AdjustHealthByDamage ( damage ); + + if ( g_perfTest_aiUndying.GetBool() && health <= 0 ) { + //so we still take pain! + health = 1; + } +} + +/* +===================== +idAI::Pain +===================== +*/ +bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + aifl.pain = idActor::Pain( inflictor, attacker, damage, dir, location ); + aifl.damage = true; + + // force a blink + blink_time = 0; + + // No special handling if friendly fire + // jshepard: friendly fire will cause pain. Players will only be able to pain buddy marines + // via splash damage. + +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return aifl.pain; + } + } +*/ + + // ignore damage from self + if ( attacker != this ) { + // React to taking pain + ReactToPain ( attacker, damage ); + + pain.takenThisFrame += damage; + pain.lastTakenTime = gameLocal.time; + combat.tacticalPainTaken += damage; + + // If taken too much pain where we then skip the current destination + if ( combat.tacticalPainThreshold && combat.tacticalPainTaken > combat.tacticalPainThreshold ) { + if (team==AITEAM_STROGG && + (combat.tacticalMaskAvailable&AITACTICAL_COVER_BITS) && + IsType(rvAITactical::GetClassType()) && + ai_allowTacticalRush.GetBool() && + spawnArgs.GetBool("rushOnPain", "1")) { + + // clear any tether + tether = NULL; + + // change ranged distances + combat.attackRange[0] = 0.0f; + combat.attackRange[1] = 100.0f; // make them get really close + + // remove cover behavior + combat.tacticalMaskAvailable &= ~AITACTICAL_COVER_BITS; + + // add ranged behavior + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BITS; + } + ForceTacticalUpdate ( ); + return true; + } + + // If looping pain and a new paintype comes in we can stop the loop + if ( pain.loopEndTime && pain.loopType.Icmp ( painType ) ) { + pain.loopEndTime = 0; + pain.loopType = painType; + } + + ExecScriptFunction( funcs.damage ); + } + + // If we were hit by our enemy and our enemy isnt visible then + // force the enemy visiblity to be updated as if we saw him momentarily + if ( enemy.ent == attacker && !enemy.fl.visible ) { + UpdateEnemyPosition ( true ); + } + + return aifl.pain; +} + +/* +===================== +idAI::Killed +===================== +*/ +void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idAngles ang; + const char* modelDeath; + const idKeyValue* kv; + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + GetDamageGroup( location ) ); + } + + if ( aifl.dead ) { + aifl.pain = true; + aifl.damage = true; + return; + } + + aifl.dead = true; + + // turn off my flashlight, if I had one + ProcessEvent( &AI_Flashlight, false ); + + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + // Hide surfaces on death + for ( kv = spawnArgs.MatchPrefix ( "deathhidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "deathhidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } + + // stop all voice sounds + StopSpeaking( true ); + + SetMoveType ( MOVETYPE_DEAD ); + + move.fl.noGravity = false; + move.fl.allowPushMovables = false; + aifl.scripted = false; + + physicsObj.UseFlyMove( false ); + physicsObj.ForceDeltaMove( false ); + + // end our looping ambient sound + StopSound( SND_CHANNEL_AMBIENT, false ); + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + + aiManager.AnnounceKill ( this, attacker, inflictor ); + aiManager.AnnounceDeath ( this, attacker ); + } + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + } + + // activate targets + ActivateTargets( this ); + + RemoveAttachments(); + RemoveProjectile(); + StopMove( MOVE_STATUS_DONE ); + + OnDeath(); + CheckDeathObjectives(); + + ClearEnemy(); + + // make monster nonsolid + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + + Unbind(); + if ( g_perfTest_aiNoRagdoll.GetBool() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else if( fl.quickBurn ){ + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else { + if ( StartRagdoll() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) { + // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + SetModel( modelDeath ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + } + } + + SetState ( "State_Killed" ); + + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while( kv ) { + idDict args; + idEntity *tEnt; + if( kv->GetValue() != "" ){ + args.Set( "classname", kv->GetValue() ); + args.Set( "origin", physicsObj.GetAbsBounds().GetCenter().ToString() ); + // Let items know that they are of the dropped variety + args.Set( "dropped", "1" ); + if (gameLocal.SpawnEntityDef( args, &tEnt )) { + if ( tEnt && tEnt->GetPhysics()) { //tEnt *should* be valid, but hey... + // magic/arbitrary number to give it some spin. Some constants used to ensure guns rarely fall standing up + tEnt->GetPhysics()->SetAngularVelocity( idVec3( (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f)); + } + } + } + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +/*********************************************************************** + + Targeting/Combat + +***********************************************************************/ + +/* +===================== +idAI::Activate + +Notifies the script that a monster has been activated by a trigger or flashlight +===================== +*/ +void idAI::Activate( idEntity *activator ) { + idPlayer *player; + + // Set our tether? + if ( activator && activator->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(activator) ); + } + + if ( aifl.dead ) { + // ignore it when they're dead + return; + } + + // make sure he's not dormant + dormantStart = 0; + + aifl.activated = true; + if ( !activator || !activator->IsType( idPlayer::GetClassType() ) ) { + player = gameLocal.GetLocalPlayer(); + } else { + player = static_cast( activator ); + } + + if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) { + SetEnemy( player ); + } + + // If being activated by a spawner we need to attach to it + if ( activator && activator->IsType ( rvSpawner::GetClassType() ) ) { + rvSpawner* spawn = static_cast( activator ); + spawn->Attach ( this ); + SetSpawner ( spawn ); + } +} + +/* +===================== +idAI::TalkTo +===================== +*/ +void idAI::TalkTo( idActor *actor ) { + + // jshepard: the dead do not speak. + if ( aifl.dead ) + return; + + ExecScriptFunction( funcs.onclick ); + + // Cant talk when already talking + if ( aifl.action || IsSpeaking ( ) || !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_TALK ) ) { + return; + } + + + switch ( GetTalkState ( ) ) { + case TALK_OK: + if ( talkMessage >= TALKMSG_LOOP ) { + //MCG: requested change: stop responding after one loop + return; + } + // at least one second between talking to people + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_TALK, 2000 ); + + talkTarget = actor; + + // Move to the next talk message + talkMessage = (talkMessage_t)((int)talkMessage + 1); + + // Loop until we find a valid talk message + while ( 1 ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Try the lipsync for the specific passive prefix first + if ( Speak ( va("lipsync_%stalk_%s", passive.prefix.c_str(), postfix.c_str() ) ) ) { + break; + } + // Try the generic lipsync for the current talk message + if ( Speak ( va("lipsync_talk_%s", postfix.c_str() ) ) ) { + break; + } + // If the first loop failed then there is no other options + if ( talkMessage == TALKMSG_LOOP ) { + return; + } else if ( talkMessage >= TALKMSG_LOOP ) { + talkMessage = TALKMSG_LOOP; + } else { + talkMessage = (talkMessage_t)((int)talkMessage + 1); + } + } + + // Start talking anim if we have one + if ( IsSpeaking ( ) ) { + passive.talkTime = speakTime; + } + + break; + + case TALK_FOLLOW: + if ( actor == leader ) { + leader = NULL; + Speak ( "lipsync_stopfollow", true ); + } else { + leader = actor; + Speak ( "lipsync_follow", true ); + } + break; + + case TALK_BUSY: + if ( talkBusyCount > 3 ) + {//only say a max of 4 lines + return; + } + talkBusyCount++; + //try to say this variant - if we can't, stop forever + if ( !Speak ( va("lipsync_busy_%d",talkBusyCount) ) ) + {//nevermore + talkBusyCount = 999; + } + break; + + case TALK_WAIT: + //do nothing + break; + } +} + +/* +===================== +idAI::GetTalkState +===================== +*/ +talkState_t idAI::GetTalkState( void ) const { + if ( ( talkState != TALK_NEVER ) && aifl.dead ) { + return TALK_DEAD; + } + if ( IsHidden() ) { + return TALK_NEVER; + } + if ( (talkState == TALK_OK || talkState == TALK_FOLLOW) && aifl.scripted ) { + return TALK_BUSY; + } + return talkState; +} + +/* +===================== +idAI::TouchedByFlashlight +===================== +*/ +void idAI::TouchedByFlashlight( idActor *flashlight_owner ) { + if ( RespondToFlashlight() ) { + Activate( flashlight_owner ); + } +} + +/* +===================== +idAI::ClearEnemy +===================== +*/ +void idAI::ClearEnemy( bool dead ) { + // Dont bother if we dont have an enemy + if ( !enemy.ent ) { + return; + } + + if ( move.moveCommand == MOVE_TO_ENEMY ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + } else if ( !aifl.scripted + && move.moveCommand == MOVE_TO_COVER + && !move.fl.done + && aasSensor->Reserved() + && !IsBehindCover() ) { + //clear cover and stop move + aasSensor->Reserve( NULL ); + combat.coverValidTime = 0; + OnCoverInvalidated(); + ForceTacticalUpdate(); + StopMove( MOVE_STATUS_DONE ); + } + + enemyNode.Remove(); + enemy.ent = NULL; + enemy.fl.dead = dead; + combat.fl.seenEnemyDirectly = false; + + UpdateHelper ( ); +} + +/* +===================== +idAI::UpdateEnemyPosition +===================== +*/ +void idAI::UpdateEnemyPosition ( bool forceUpdate ) { + if( !enemy.ent || (!enemy.fl.visible && !forceUpdate) ) { + return; + } + + idActor* enemyActor = dynamic_cast(enemy.ent.GetEntity()); + idEntity* enemyEnt = static_cast(enemy.ent.GetEntity()); + + enemy.lastVisibleFromEyePosition = GetEyePosition ( ); + + // Update the enemy origin if it isnt locked + if ( !enemy.fl.lockOrigin ) { + enemy.lastKnownPosition = enemyEnt->GetPhysics()->GetOrigin(); + + if ( enemyActor ) { + enemy.lastVisibleEyePosition = enemyActor->GetEyePosition(); + enemy.lastVisibleChestPosition = enemyActor->GetChestPosition(); + } else { + enemy.lastVisibleEyePosition = enemy.ent->GetPhysics()->GetOrigin ( ); + enemy.lastVisibleChestPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + } + } +} + +/* +===================== +idAI::UpdateEnemy +===================== +*/ +void idAI::UpdateEnemy ( void ) { + predictedPath_t predictedPath; + + // If we lost our enemy then clear it out to be sure + if( !enemy.ent ) { + return; + } + + // Rest to not being in fov + enemy.fl.inFov = false; + + // Bail out if we arent queued to do a complex think + if ( aifl.simpleThink ) { + // Keep the fov flag up to date + if ( IsEnemyVisible ( ) ) { + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + } + } else { + bool oldVisible; + + // Cache current enemy visiblity so we can see if it changes + oldVisible = IsEnemyVisible ( ); + + // See if enemy still visible + UpdateEnemyVisibility ( ); + + // If our enemy isnt visible but is within aware range then we know where he is + if ( !IsEnemyVisible ( ) && DistanceTo ( enemy.ent ) < combat.awareRange ) { + UpdateEnemyPosition( true ); + } else { +/* + // check if we heard any sounds in the last frame + if ( enemyEnt == gameLocal.GetAlertActor() ) { + float dist = ( enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin() ).LengthSqr(); + if ( dist < Square( combat.earRange ) ) { + SetEnemyPosition(); + } + } +*/ + } + + // Update fov + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + + // Set enemy.visibleTime to the time when the visibility flag changed + if ( oldVisible != IsEnemyVisible ( ) ) { + // Just caught sight of the enemy after loosing him, delay the attack actions a bit + if ( !oldVisible && combat.attackSightDelay ) { + float delay; + if ( enemy.lastVisibleChangeTime ) { + // The longer the enemy has not been visible, the more we should delay + delay = idMath::ClampFloat ( 0.0f, 1.0f, (gameLocal.time - enemy.lastVisibleChangeTime) / AI_SIGHTDELAYSCALE ); + } else { + // The enemy has never been visible so delay the full amount + delay = 1.0f; + } + // Add to the ranged attack timer providing its not already running a timer + if ( actionTimerRangedAttack.IsDone ( actionTime) ) { + actionTimerRangedAttack.Clear ( actionTime ); + actionTimerRangedAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + // Add to the special attack timer providing its not already running a timer + if ( actionTimerSpecialAttack.IsDone ( actionTime ) ) { + actionTimerSpecialAttack.Clear ( actionTime ); + actionTimerSpecialAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + } + + enemy.lastVisibleChangeTime = gameLocal.time; + } + + // Handler for visibility change + if ( oldVisible != IsEnemyVisible ( ) ) { + OnEnemyVisiblityChange ( oldVisible ); + } + } + + // Adjust smoothed linear and pushed velocities for enemies + if ( enemy.fl.visible ) { + enemy.smoothedLinearVelocity += ((enemy.ent->GetPhysics()->GetLinearVelocity ( ) - enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedLinearVelocity) * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity += ((enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedPushedVelocity) * enemy.smoothVelocityRate ); + } else { + enemy.smoothedLinearVelocity -= (enemy.smoothedLinearVelocity * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity -= (enemy.smoothedPushedVelocity * enemy.smoothVelocityRate ); + } + + // Update enemy range + enemy.range = DistanceTo ( enemy.lastKnownPosition ); + enemy.range2d = DistanceTo2d ( enemy.lastKnownPosition ); + + // Calulcate the aggression scale + // Skill level 0: (1.0) + // Skill level 1: (1.0 - 1.25) + // Skill level 2: (1.0 - 1.5) + // Skill level 3: (1.0 - 1.75) + if ( combat.aggressiveRange > 0.0f ) { + combat.aggressiveScale = (g_skill.GetFloat() / MAX_SKILL_LEVELS); + combat.aggressiveScale *= 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, enemy.range / combat.aggressiveRange ); + combat.aggressiveScale += 1.0f; + } +} + +/* +===================== +idAI::LastKnownPosition +===================== +*/ +const idVec3& idAI::LastKnownPosition ( const idEntity *ent ) { + return (ent==enemy.ent)?(enemy.lastKnownPosition):(ent->GetPhysics()->GetOrigin()); +} + +/* +===================== +idAI::UpdateEnemyVisibility + +Update whether or not the AI can see its enemy or not and determine how. +===================== +*/ +void idAI::UpdateEnemyVisibility ( void ) { + enemy.fl.visible = false; + + // No enemy so nothing to see + if ( !enemy.ent ) { + return; + } + + // Update enemy visibility flag + enemy.fl.visible = CanSeeFrom ( GetEyePosition ( ), enemy.ent, false ); + + // IF the enemy isnt visible and not forcing an update we can just early out + if ( !enemy.fl.visible ) { + return; + } + + // If we are seeing our enemy for the first time after changing enemies, call him out + if ( enemy.lastVisibleTime < enemy.changeTime ) { + AnnounceNewEnemy( ); + } + + combat.fl.seenEnemyDirectly = true; + enemy.lastVisibleTime = gameLocal.time; + + // Update our known locations of the enemy since we just saw him + UpdateEnemyPosition ( ); +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +void idAI::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +rvSpawner* idAI::GetSpawner ( void ) { + return spawner; +} + +/* +===================== +idAI::SetEnemy +===================== +*/ +bool idAI::SetEnemy( idEntity *newEnemy ) { + idEntity* oldEnemy; + + // Look for obvious early out + if ( enemy.ent == newEnemy ) { + return true; + } + + // Cant set enemy if dead + if ( aifl.dead ) { + ClearEnemy ( false ); + return false; + } + + // Cant set enemy if the enemy is dead or has no target set + if ( newEnemy ) { + if ( newEnemy->health <= 0 || newEnemy->fl.notarget ) { + return false; + } + } + + oldEnemy = enemy.ent; + enemy.fl.dead = false; + enemy.fl.lockOrigin = false; + + if ( !newEnemy ) { + ClearEnemy ( false ); + } else { + // Set our current enemy + enemy.ent = newEnemy; + + // If the enemy is an actor then add to the actors enemy list + if( newEnemy->IsType( idActor::GetClassType() ) ){ + idActor* enemyActor = static_cast( newEnemy ); + enemyNode.AddToEnd( enemyActor->enemyList ); + } + } + + // Allow custom handling of enemy change + if ( enemy.ent != oldEnemy ) { + OnEnemyChange ( oldEnemy ); + } + + return true; +} + + +/* +=================== +idAI::CalculateAttackOffsets + +calculate joint positions on attack frames so we can do proper "can hit" tests +=================== +*/ +void idAI::CalculateAttackOffsets ( void ) { + const idDeclModelDef* modelDef; + int num; + int i; + int frame; + const frameCommand_t* command; + idMat3 axis; + const idAnim* anim; + jointHandle_t joint; + + modelDef = animator.ModelDef(); + if ( !modelDef ) { + return; + } + num = modelDef->NumAnims(); + + // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim + animator.RemoveOriginOffset( false ); + + // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for + // launch offsets so that anim number can be used without subtracting 1. + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num + 1 ); + attackAnimInfo[ 0 ].attackOffset.Zero(); + attackAnimInfo[ 0 ].eyeOffset.Zero(); + + for( i = 1; i <= num; i++ ) { + attackAnimInfo[ i ].attackOffset.Zero(); + attackAnimInfo[ i ].eyeOffset.Zero(); + anim = modelDef->GetAnim( i ); + if ( !anim ) { + continue; + } + + frame = anim->FindFrameForFrameCommand( FC_AI_ATTACK, &command ); + if ( frame >= 0 ) { + joint = animator.GetJointHandle( command->joint->c_str() ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Invalid joint '%s' on 'ai_attack' frame command on frame %d of model '%s'", command->joint->c_str(), frame, modelDef->GetName() ); + } + GetJointTransformForAnim( joint, i, FRAME2MS( frame ), attackAnimInfo[ i ].attackOffset, axis ); + + if ( chestOffsetJoint!= INVALID_JOINT ) { + GetJointTransformForAnim( chestOffsetJoint, i, FRAME2MS( frame ), attackAnimInfo[ i ].eyeOffset, axis ); + } + } + } + + animator.RemoveOriginOffset( true ); +} + +/* +===================== +idAI::CreateProjectileClipModel +===================== +*/ +void idAI::CreateProjectileClipModel( void ) const { + if ( projectileClipModel == NULL ) { + idBounds projectileBounds( vec3_origin ); + projectileBounds.ExpandSelf( 2.0f ); // projectileRadius ); + projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) ); + } +} + +/* +===================== +idAI::GetPredictedAimDirOffset +===================== +*/ +void idAI::GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const { + float a; + float b; + float c; + float d; + float t; + idVec3 r; + + // Make sure there is something to predict + if ( targetVelocity.LengthSqr ( ) == 0.0f ) { + offset.Zero ( ); + return; + } + + // Solve for (t): magnitude(targetVelocity * t + target - source) = projectileSpeed * t + + r = (target-source); + a = (targetVelocity * targetVelocity) - Square(projectileSpeed); + b = (targetVelocity * 2.0f) * r; + c = r*r; + d = b*b - 4*a*c; + t = 0.0f; + + if ( d >= 0.0f ) { + float t1; + float t2; + float denom; + d = idMath::Sqrt(d); + denom = 1.0f / (2.0f * a); + t1 = (-b + d) * denom; + t2 = (-b - d) * denom; + if ( t1 < 0.0f && t2 < 0.0f ) { + t = 0.0f; + } else if ( t1 < 0.0f ) { + t = t2; + } else if ( t2 < 0.0f ) { + t = t1; + } else { + t = Min(t1,t2); + } + } + + offset = targetVelocity * t; +} + +/* +===================== +idAI::GetAimDir +===================== +*/ +bool idAI::GetAimDir( + const idVec3& firePos, + const idEntity* target, + const idDict* projectileDef, + idEntity* ignore, + idVec3& aimDir, + float aimOffset, + float predict + ) const +{ + idVec3 targetPos1; + idVec3 targetPos2; + idVec3 targetLinearVel; + idVec3 targetPushedVel; + idVec3 delta; + float max_height; + bool result; + idEntity* targetEnt = const_cast(target); + + // if no aimAtEnt or projectile set + if ( !targetEnt ) { + aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + return false; + } + + float projectileSpeed = idProjectile::GetVelocity ( projectileDef ).LengthFast ( ); + idVec3 projectileGravity = idProjectile::GetGravity ( projectileDef ); + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + if ( targetEnt == enemy.ent ) { + targetPos2 = enemy.lastVisibleEyePosition; + targetPos1 = enemy.lastVisibleChestPosition; + targetPushedVel = enemy.smoothedPushedVelocity; + targetLinearVel = enemy.smoothedLinearVelocity; + } else if ( targetEnt->IsType( idActor::GetClassType() ) ) { + targetPos2 = static_cast(targetEnt)->GetEyePosition ( ); + targetPos1 = static_cast(targetEnt)->GetChestPosition ( ); + targetPushedVel = target->GetPhysics()->GetPushedLinearVelocity ( ); + targetLinearVel = (target->GetPhysics()->GetLinearVelocity ( ) - targetPushedVel); + } else { + targetPos1 = targetEnt->GetPhysics()->GetAbsBounds().GetCenter(); + targetPos2 = targetPos1; + targetPushedVel.Zero ( ); + targetLinearVel.Zero ( ); + } + + // Target prediction + if ( projectileSpeed == 0.0f ) { + // Hitscan prediction actually causes the hitscan to miss unless it is predicted. + delta = (targetLinearVel * (-1.0f + predict)); + } else { + // Projectile prediction must figure out how far to lead the shot to hit the target + GetPredictedAimDirOffset ( firePos, targetPos1, projectileSpeed, (targetLinearVel*predict)+targetPushedVel, delta ); + } + targetPos1 += delta; + targetPos2 += delta; + + result = false; + + // try aiming for chest + delta = targetPos1 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos1 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + if ( result || !targetEnt->IsType( idActor::GetClassType() ) ) { + return result; + } + } + + // try aiming for head + delta = targetPos2 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos2 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + } + + return result; +} + +/* +===================== +idAI::CreateProjectile +===================== +*/ +idProjectile *idAI::CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ) { + idEntity* ent; + const char* clsname; + + if ( !projectile.GetEntity() ) { + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + clsname = projectileDict->GetString( "classname" ); + gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + clsname = ent->GetClassname(); + gameLocal.Error( "'%s' is not an idProjectile", clsname ); + } + projectile = (idProjectile*)ent; + } + + projectile.GetEntity()->Create( this, pos, dir ); + + return projectile.GetEntity(); +} + +/* +===================== +idAI::RemoveProjectile +===================== +*/ +void idAI::RemoveProjectile( void ) { + if ( projectile.GetEntity() ) { + projectile.GetEntity()->PostEventMS( &EV_Remove, 0 ); + projectile = NULL; + } +} + +/* +===================== +idAI::Attack +===================== +*/ +bool idAI::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + // Get the attack dictionary + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", attackName ) ), false ); + if ( !attackDict ) { + gameLocal.Error ( "could not find attack entityDef 'def_attack_%s (%s)' on AI entity %s", attackName, spawnArgs.GetString ( va("def_attack_%s", attackName ) ), GetName ( ) ); + } + + // Melee Attack? + if ( spawnArgs.GetBool ( va("attack_%s_melee", attackName ), "0" ) ) { + return AttackMelee ( attackName, attackDict ); + } + + // Ranged attack (hitscan or projectile)? + return ( AttackRanged ( attackName, attackDict, joint, target, pushVelocity ) != NULL ); +} + +/* +===================== +idAI::AttackRanged +===================== +*/ +idProjectile* idAI::AttackRanged ( + const char* attackName, + const idDict* attackDict, + jointHandle_t joint, + idEntity* target, + const idVec3& pushVelocity + ) +{ + float attack_accuracy; + float attack_cone; + float attack_spread; + int attack_count; + bool attack_hitscan; + float attack_predict; + float attack_pullback; + int i; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + idVec3 dir; + idAngles ang; + idMat3 axis; + idProjectile* lastProjectile; + + lastProjectile = NULL; + + // Generic attack properties + attack_accuracy = spawnArgs.GetFloat ( va("attack_%s_accuracy", attackName ), "7" ); + attack_cone = spawnArgs.GetFloat ( va("attack_%s_cone", attackName ), "75" ); + attack_spread = DEG2RAD ( spawnArgs.GetFloat ( va("attack_%s_spread", attackName ), "0" ) ); + attack_count = spawnArgs.GetInt ( va("attack_%s_count", attackName ), "1" ); + attack_hitscan = spawnArgs.GetBool ( va("attack_%s_hitscan", attackName ), "0" ); + attack_predict = spawnArgs.GetFloat ( va("attack_%s_predict", attackName ), "0" ); + attack_pullback = spawnArgs.GetFloat ( va("attack_%s_pullback", attackName ), "0" ); + + // Get the muzzle origin and axis from the given launch joint + GetMuzzle( joint, muzzleOrigin, muzzleAxis ); + if ( attack_pullback ) + { + muzzleOrigin -= muzzleAxis[0]*attack_pullback; + } + + // set aiming direction + bool calcAim = true; + if ( GetEnemy() && GetEnemy() == target && GetEnemy() == gameLocal.GetLocalPlayer() && spawnArgs.GetBool( va("attack_%s_missFirstShot",attackName) ) ) { + //purposely miss + if ( gameLocal.random.RandomFloat() < 0.5f ) { + //actually, hit anyway... + } else { + idVec3 targetPos = enemy.lastVisibleEyePosition; + idVec3 eFacing; + idVec3 left, up; + + up.Set( 0, 0, 1 ); + left = viewAxis[0].Cross(up); + + if ( GetEnemy()->IsType( idActor::GetClassType() ) ) + { + eFacing = ((idActor*)GetEnemy())->viewAxis[0]; + } + else + { + eFacing = GetEnemy()->GetPhysics()->GetAxis()[0]; + } + if ( left*eFacing > 0 ) + { + targetPos += left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + else + { + targetPos -= left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + dir = targetPos-muzzleOrigin; + dir.Normalize(); + attack_accuracy = 0; + + calcAim = false; + //don't miss next time + spawnArgs.SetBool( va("attack_%s_missFirstShot",attackName), false ); + } + } + if ( calcAim ) { + if ( target && !spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) { + GetAimDir( muzzleOrigin, target, attackDict, this, dir, + spawnArgs.GetFloat ( va("attack_%s_aimoffset", attackName )), + attack_predict ); + } else { + dir = muzzleAxis[0]; + if ( spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) + { + if ( spawnArgs.GetBool( va("attack_%s_traceToNextJoint", attackName ), "0" ) ) + { + jointHandle_t endJoint = animator.GetFirstChild( joint ); + if ( endJoint != INVALID_JOINT && endJoint != joint ) + { + idVec3 endJointPos; + idMat3 blah; + GetJointWorldTransform( endJoint, gameLocal.GetTime(), endJointPos, blah ); + dir = endJointPos-muzzleOrigin; + //ARGHH: need to be able to set range!!! + //const_cast(attackDict)->SetFloat( "range", dir.Normalize() );//NOTE: yes, I intentionally normalize here as well as use the result for length... + dir.Normalize(); + } + } + } + } + } + + // random accuracy + ang = dir.ToAngles(); + ang.pitch += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + ang.yaw += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + + // Lock attacks to a cone? + if ( attack_cone ) { + float diff; + // clamp the attack direction to be within monster's attack cone so he doesn't do + // things like throw the missile backwards if you're behind him + diff = idMath::AngleDelta( ang.yaw, move.current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = move.current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = move.current_yaw - attack_cone; + } + } + + ang.Normalize360 ( ); + axis = ang.ToMat3(); + + for( i = 0; i < attack_count; i++ ) { + float angle; + float spin; + + // spread the projectiles out + angle = idMath::Sin( attack_spread * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( attack_hitscan ) { + gameLocal.HitScan( *attackDict, muzzleOrigin, dir, muzzleOrigin, this, false, combat.aggressiveScale ); + } else { + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( attackDict, muzzleOrigin, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzleOrigin, dir, pushVelocity, 0.0f, combat.aggressiveScale ); + + // Let the script manage projectiles if need be + ExecScriptFunction ( funcs.launch_projectile, lastProjectile ); + + projectile = NULL; + } + } + + lastAttackTime = gameLocal.time; + + // If shooting at another ai entity then kick off an attack reaction + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) ) { + static_cast(enemy.ent.GetEntity())->ReactToShotAt ( this, muzzleOrigin, axis[0] ); + } + + return lastProjectile; +} + +/*s +================ +idAI::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( ( victim == this ) && inflictor->IsType( idProjectile::GetClassType() ) ) { + // monsters only get half damage from their own projectiles + damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage + } else if ( victim == enemy.ent ) { + aifl.hitEnemy = true; + } +} + +/* +===================== +idAI::DirectDamage + +Causes direct damage to an entity + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) { + const idDict *meleeDef; + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() ); + } + + if ( !ent->fl.takedamage ) { + const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" )); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + return; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + +// ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ); + ent->Damage( this, this, globalKickDir, meleeDefName, damageScale, NULL ); +} + +/* +===================== +idAI::TestMelee +===================== +*/ +bool idAI::TestMelee( void ) const { + trace_t trace; + idEntity* enemyEnt = enemy.ent; + + if ( !enemyEnt || !combat.meleeRange ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( DebugFilter(ai_debugMove) ) { //YELLOW = Test Melee Bounds + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +/* +===================== +idAI::AttackMelee + +jointname allows the endpoint to be exactly specified in the model, +as for the commando tentacle. If not specified, it will be set to +the facing direction + combat.meleeRange. + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +bool idAI::AttackMelee ( const char *attackName, const idDict* meleeDict ) { + idEntity* enemyEnt = enemy.ent; + const char* p; + const idSoundShader* shader; + + if ( !enemyEnt ) { + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // check for the "saving throw" automatic melee miss on lethal blow + // stupid place for this. + bool forceMiss = false; + if ( enemyEnt->IsType( idPlayer::GetClassType() ) && g_skill.GetInteger() < 2 ) { + int damage, armor; + idPlayer *player = static_cast( enemyEnt ); + player->CalcDamagePoints( this, this, meleeDict, 1.0f, INVALID_JOINT, &damage, &armor ); + + if ( enemyEnt->health <= damage ) { + int t = gameLocal.time - player->lastSavingThrowTime; + if ( t > SAVING_THROW_TIME ) { + player->lastSavingThrowTime = gameLocal.time; + t = 0; + } + if ( t < 1000 ) { + forceMiss = true; + } + } + } + + // make sure the trace can actually hit the enemy + if ( forceMiss || !TestMelee( ) ) { + // missed + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // do the damage + // + p = meleeDict->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDict->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + + // This allows some AI to be soft against melee-- most marines are selfMeleeDamageScale of 3, which means they take 3X from melee attacks! + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ) * enemyEnt->spawnArgs.GetFloat ( "selfMeleeDamageScale", "1" ); + + //if attacker is a melee superhero, damageScale is way increased + idAI* enemyAI = static_cast(enemyEnt); + if( aifl.meleeSuperhero) { + if( damageScale >=1 ) { + damageScale *= 6; + } else { + damageScale = 6; + } + } + + //if the defender is a melee superhero, damageScale is way decreased + if( enemyAI->aifl.meleeSuperhero) { + damageScale = 0.5f; + } + + int location = INVALID_JOINT; + if ( enemyEnt->IsType ( idAI::Type ) ) { + location = static_cast(enemyEnt)->chestOffsetJoint; + } + enemyEnt->Damage( this, this, globalKickDir, meleeDict->GetString ( "classname" ), damageScale, location ); + + if ( meleeDict->GetString( "fx_impact", NULL ) ) { + if ( enemyEnt == gameLocal.GetLocalPlayer() ) { + idPlayer *ePlayer = static_cast(enemyEnt); + if ( ePlayer ) { + idVec3 dir = ePlayer->firstPersonViewOrigin-GetEyePosition(); + dir.Normalize(); + idVec3 org = ePlayer->firstPersonViewOrigin + (dir * -((gameLocal.random.RandomFloat()*8.0f)+8.0f) ); + idAngles ang = ePlayer->firstPersonViewAxis.ToAngles() * -1; + idMat3 axis = ang.ToMat3(); + gameLocal.PlayEffect( *meleeDict, "fx_impact", org, viewAxis ); + } + } + } + + lastAttackTime = gameLocal.time; + + return true; +} + +/* +================ +idAI::PushWithAF +================ +*/ +void idAI::PushWithAF( void ) { + int i, j; + afTouch_t touchList[ MAX_GENTITIES ]; + idEntity *pushed_ents[ MAX_GENTITIES ]; + idEntity *ent; + idVec3 vel; + int num_pushed; + + num_pushed = 0; + af.ChangePose( this, gameLocal.time ); + int num = af.EntitiesTouchingAF( touchList ); + for( i = 0; i < num; i++ ) { + if ( touchList[ i ].touchedEnt->IsType( idProjectile::GetClassType() ) ) { + // skip projectiles + continue; + } + + // make sure we havent pushed this entity already. this avoids causing double damage + for( j = 0; j < num_pushed; j++ ) { + if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) { + break; + } + } + if ( j >= num_pushed ) { + ent = touchList[ i ].touchedEnt; + pushed_ents[num_pushed++] = ent; + vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin(); + vel.Normalize(); + ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() ); + } + } +} + +/*********************************************************************** + + Misc + +***********************************************************************/ + +/* +================ +idAI::GetMuzzle +================ +*/ +void idAI::GetMuzzle( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ) { + if ( joint == INVALID_JOINT ) { + muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14; + muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f; + } else { + GetJointWorldTransform( joint, gameLocal.time, muzzle, axis ); + //MCG + //pull the muzzle back inside the bounds if possible + //FIXME: this is nasty, we should just be able to check for starting in solid and register that as a hit! But... + /* + float scale = 0.0f; + if ( physicsObj.GetBounds().RayIntersection( muzzle, -axis[0], scale ) ) + { + if ( scale != 0.0f ) + {//not already inside + muzzle += scale * -axis[0]; + } + } + else + {//just pull it back a little anyway? + idVec3 xyOfs = muzzle-physicsObj.GetOrigin(); + xyOfs.z = 0; + muzzle += xyOfs.Length() * -axis[0]; + } + */ + } +} + +/* +================ +idAI::Hide +================ +*/ +void idAI::Hide( void ) { + idActor::Hide(); + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + StopSound( SND_CHANNEL_AMBIENT, false ); + + enemy.fl.inFov = false; + enemy.fl.visible = false; + + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::Show +================ +*/ +void idAI::Show( void ) { + idActor::Show(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + + physicsObj.GetClipModel()->Link(); + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); +} + +/* +================ +idAI::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idAI::CanPlayChatterSounds( void ) const { + if ( aifl.dead ) { + return false; + } + + if ( IsHidden() ) { + return false; + } + + if ( enemy.ent ) { + return true; + } + + if ( spawnArgs.GetBool( "no_idle_chatter" ) ) { + return false; + } + + return true; +} + +/* +===================== +idAI::UpdateChatter +===================== +*/ +void idAI::UpdateChatter ( void ) { + int chatterRate; + const char* chatter; + + // No chatter? + if ( !chatterRateIdle && !chatterRateCombat ) { + return; + } + + // check if it's time to play a chat sound + if ( IsHidden() || !aifl.awake || aifl.dead || ( chatterTime > gameLocal.time ) ) { + return; + } + + // Skip first chatter + if ( enemy.ent ) { + chatter = "lipsync_chatter_combat"; + chatterRate = chatterRateCombat; + } else { + chatter = "lipsync_chatter_idle"; + chatterRate = chatterRateIdle; + } + + // Can chatter ? + if ( !chatterRate ) { + return; + } + + // Start chattering, but not if he's already speaking. And he might already be speaking because he was scripted to do so. + if ( chatterTime > 0 && !IsSpeaking() ) { + Speak ( chatter, true ); + } + + // set the next chat time + chatterTime = gameLocal.time + chatterRate + (gameLocal.random.RandomFloat() * chatterRate * 0.5f) - (chatterRate * 0.25f); +} + +/* +===================== +idAI::HeardSound +===================== +*/ +idEntity *idAI::HeardSound( int ignore_team ){ + // check if we heard any sounds in the last frame + idActor *actor = gameLocal.GetAlertActor(); + if ( actor && ( !ignore_team || ( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) && gameLocal.InPlayerPVS( this ) ) { + idVec3 pos = actor->GetPhysics()->GetOrigin(); + idVec3 org = physicsObj.GetOrigin(); + float dist = ( pos - org ).LengthSqr(); + + if ( dist < Square( combat.earRange ) ) { + //really close? + if ( dist < Square( combat.earRange/4.0f ) ) { + return actor; + //possible LOS + } else if ( dist < Square( combat.visRange * 2.0f ) && CanSee( actor, false ) ) { + return actor; + } else if ( combat.fl.aware ) { + //FIXME: or, maybe find cover/hide/ambush spot and wait to ambush them? + //don't have an enemy and not tethered to a position + } else if ( !GetEnemy() && !tether ) { + //go into search mode + WanderAround(); + move.fl.noRun = false; + move.fl.idealRunning = true; + //undid this: was causing them to not be able to do fine nav. + //move.fl.noWalk = true; + } + } + } + + return NULL; +} + +/* +============ +idAI::SetLeader +============ +*/ +void idAI::SetLeader ( idEntity *newLeader ) { + idEntity* oldLeader = leader; + + if( !newLeader ){ + leader = NULL; + } else if ( !newLeader->IsType( idActor::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idActor (player or ai controlled character)", newLeader->name.c_str() ); + } else { + leader = static_cast( newLeader ); + } + + if ( oldLeader != leader ) { + OnLeaderChange ( oldLeader ); + } +} + + + +/*********************************************************************** + + Head & torso aiming + +***********************************************************************/ + +/* +================ +idAI::UpdateAnimationControllers +================ +*/ +bool idAI::UpdateAnimationControllers( void ) { + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + float currentHeadFocusRate = ( combat.fl.aware ? headFocusRate : headFocusRate * 0.5f ); + + MEM_SCOPED_TAG(tag,MA_ANIM); + + if ( aifl.dead ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = move.current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions. + // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which + // are already up to date because of getting the joints in this function) and then sets their positions, which + // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled, + // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no + // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number + // in order to see how many times an entity transforms the joints per frame. + idActor::UpdateAnimationControllers(); + + // Update the focus position + UpdateFocus( orientationJointAxis ); + + //MCG NOTE: don't know why Dube added this extra check for the torsoAnim (5/09/05), but it was causing popping, so I took it out... :/ + //bool canLook = ( !torsoAnim.AnimDone(0) || !torsoAnim.GetAnimator()->CurrentAnim(ANIMCHANNEL_TORSO)->IsDone(gameLocal.GetTime()) || torsoAnim.Disabled() ) && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook; + //bool canLook = (!torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook); + + bool canLook = (!animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look && !aifl.disableLook); + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + currentFocusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 64.0f; + } + + // Determine the new look yaw + dir = currentFocusPos - orientationJointPos; + newLookAng.yaw = dir.ToYaw( ); + + // Determine the new look pitch + dir = currentFocusPos - GetEyePosition(); + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + newLookAng.roll = 0.0f; + + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + newLookAng.yaw = orientationJointAxis[0].ToYaw(); + newLookAng.pitch = 0; + } + // Add the angle change using the head focus rate and convert the look angles to + // local angles so they can be properly clamped. + float f = lookAng.yaw; + if ( lookMin.yaw <= -360.0f && lookMax.yaw >= 360.0f ) + { + lookAng.yaw = f - orientationJointYaw; + float diff; + diff = ( idMath::AngleNormalize360(newLookAng.yaw) - idMath::AngleNormalize360(f) ); + diff = idMath::AngleNormalize180( diff ); + lookAng.yaw += diff * currentHeadFocusRate; + //lookAng.yaw += ( newLookAng.yaw - f ) * currentHeadFocusRate; + } + else + { + lookAng.yaw = idMath::AngleNormalize180 ( f - orientationJointYaw ); + lookAng.yaw += (idMath::AngleNormalize180 ( newLookAng.yaw - f ) * currentHeadFocusRate); + } + lookAng.pitch += ( idMath::AngleNormalize180( newLookAng.pitch - lookAng.pitch ) * currentHeadFocusRate ); + + // Clamp the look angles + lookAng.Clamp( lookMin, lookMax ); + + // Calcuate the eye angles + f = eyeAng.yaw; + eyeAng.yaw = idMath::AngleNormalize180( f - orientationJointYaw ); + eyeAng.yaw += (idMath::AngleNormalize180( newLookAng.yaw - f ) * eyeFocusRate); + eyeAng.pitch += (idMath::AngleNormalize180( newLookAng.pitch - eyeAng.pitch ) * eyeFocusRate); + + // Clamp eye angles relative to the look angles + jointAng = eyeAng - lookAng; + jointAng.Normalize180( ); + jointAng.Clamp( eyeMin, eyeMax ); + eyeAng = lookAng + jointAng; + eyeAng.Normalize180( ); + + if ( canLook ) { + // Apply the look angles to the look joints + if ( animator.GetAnimFlags( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimNum()).ai_look_head_only ) { + if ( neckJoint != INVALID_JOINT || headJoint != INVALID_JOINT ) { + float jScale = 1.0f; + if ( neckJoint != INVALID_JOINT && headJoint != INVALID_JOINT ) { + jScale = 0.5f; + } + jointAng.pitch = lookAng.pitch * jScale; + jointAng.yaw = lookAng.yaw * jScale; + if ( neckJoint != INVALID_JOINT ) { + animator.SetJointAxis( neckJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + if ( headJoint != INVALID_JOINT ) { + animator.SetJointAxis( headJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + //what if we have a previous joint mod on the rest of the lookJoints + //from an anim that *wasn't* ai_look_head_only...? + //just clear them? Or move them towards 0? + //FIXME: move them towards zero... + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + } + } else { + //blend back + //yes, this is framerate dependant and inefficient and wrong, but... eliminates pops + jointAng.roll = 0.0f; + jointMod_t *curJointMod; + idAngles curJointAngleMod; + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //blend back + curJointMod = animator.FindExistingJointMod( lookJoints[ i ], NULL ); + if ( curJointMod ) + { + curJointAngleMod = curJointMod->mat.ToAngles(); + curJointAngleMod *= 0.75f; + if ( fabs(curJointAngleMod.pitch) < 1.0f + && fabs(curJointAngleMod.yaw) < 1.0f + && fabs(curJointAngleMod.roll) < 1.0f ) + { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + else + { + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, curJointAngleMod.ToMat3() ); + } + } + } + } + } + } else { + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } else { + //if ( animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look || aifl.disableLook ) { + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + animator.ClearJoint ( lookJoints[i] ); + } + } else { + //PCJ back to neutral + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } + + // Convert the look angles back to world angles. This is done to prevent dramatic orietation changes + // from changing the look direction abruptly (unless of course the minimums are not met) + lookAng.yaw = idMath::AngleNormalize180( lookAng.yaw + orientationJointYaw ); + eyeAng.yaw = idMath::AngleNormalize180( eyeAng.yaw + orientationJointYaw ); + + if ( move.moveType == MOVETYPE_FLY || move.fl.flyTurning ) { + // lean into turns + AdjustFlyingAngles(); + } + + // Orient the eyes towards their target + if ( leftEyeJoint != INVALID_JOINT && rightEyeJoint != INVALID_JOINT ) { + if ( head ) { + idAnimator *headAnimator = head->GetAnimator(); + + if ( focusType != AIFOCUS_NONE && allowEyeFocus && canLook ) { + //tweak these since it's looking at the wrong spot and not calculating from each eye and I don't have time to bother rewriting all of this properly + eyeAng.yaw -= 0.5f; + eyeAng.pitch += 3.25f; + idMat3 eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + eyeAng.yaw += 3.0f; + eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + if ( ai_debugEyeFocus.GetBool() ) { + idVec3 eyeOrigin; + idMat3 axis; + + head->GetJointWorldTransform ( rightEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + + head->GetJointWorldTransform ( leftEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + } + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus && focusType != AIFOCUS_NONE && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook ) { + idMat3 eyeAxis = ( eyeAng ).ToMat3() * GetPhysics()->GetAxis().Transpose ( ); + animator.SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + animator.SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + } + + return true; +} + +void idAI::OnTouch( idEntity *other, trace_t *trace ) { + // if we dont have an enemy or had one for at least a second that is a potential enemy the set the enemy + if ( other->IsType( idActor::GetClassType() ) + && !other->fl.notarget + && ( ReactionTo( other )&ATTACK_ON_SIGHT) + && (!enemy.ent || gameLocal.time - enemy.changeTime > 1000 ) ) { + SetEnemy( other ); + } + + if ( !enemy.ent && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { + Activate( other ); + } + pusher = other; + aifl.pushed = true; + + // If pushed by the player update tactical + if ( pusher && pusher->IsType ( idPlayer::GetClassType() ) && (combat.tacticalMaskAvailable & AITACTICAL_MOVE_PLAYERPUSH_BIT) ) { + ForceTacticalUpdate ( ); + } +} + +idProjectile* idAI::AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ) { + idVec3 start; + trace_t tr; + idBounds projBounds; + const idClipModel* projClip; + idMat3 axis; + float distance; + idProjectile* result; + + if ( !projectileDict ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + axis = ang.ToMat3(); + if ( !projectile.GetEntity() ) { + CreateProjectile( projectileDict, org, axis[ 0 ] ); + } + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); + + // check if the owner bounds is bigger than the projectile bounds + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); +// RAVEN END + + // launch the projectile + projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin ); + result = projectile; + projectile = NULL; + + lastAttackTime = gameLocal.time; + + return result; +} + +void idAI::RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { + jointHandle_t joint; + idVec3 org; + idMat3 axis; + + if ( !jointname || !jointname[ 0 ] ) { + org = physicsObj.GetOrigin(); + } else { + joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + GetJointWorldTransform( joint, gameLocal.time, org, axis ); + } + + gameLocal.RadiusDamage( org, this, this, this, this, damageDefName ); +} + +/* +===================== +idAI::CanBecomeSolid + +returns true if the AI entity could become solid at its current position +===================== +*/ +bool idAI::CanBecomeSolid ( void ) { + int i; + int num; + idEntity * hit; + idClipModel* cm; + idClipModel* clipModels[ MAX_GENTITIES ]; + + // Determine what we are currently touching +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.ClipModelsTouchingBounds( this, physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + + if ( physicsObj.ClipContents( cm ) ) { + return false; + } + } + + return true; +} + +/* +===================== +idAI::BecomeSolid +===================== +*/ +void idAI::BecomeSolid( void ) { + physicsObj.EnableClip(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + physicsObj.GetClipModel()->Link(); +// RAVEN END + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); +} + +/* +===================== +idAI::BecomeNonSolid +===================== +*/ +void idAI::BecomeNonSolid( void ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); +} + +const char *idAI::ChooseAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( anim ) { + if ( channel == ANIMCHANNEL_HEAD ) { + if ( head.GetEntity() ) { + return head.GetEntity()->GetAnimator()->AnimFullName( anim ); + } + } else { + return animator.AnimFullName( anim ); + } + } + + return ""; +} + +/* +============ +idAI::ExecScriptFunction +============ +*/ +void idAI::ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm ) { + if( parm ) { + func.InsertEntity( parm, 0 ); + } else { + func.InsertEntity( this, 0 ); + } + + func.CallFunc( &spawnArgs ); + + if( parm ) { + func.RemoveIndex( 0 ); + } +} + +/* +============ +idAI::Prethink +============ +*/ +void idAI::Prethink ( void ) { + // Update our helper if we are moving + if ( move.fl.moving ) { + UpdateHelper ( ); + } + + if ( leader ) { + idEntity* groundEnt = leader->GetGroundEntity ( ); + if ( !(tether && !aifl.tetherMover) && groundEnt ) { + if ( groundEnt->IsType ( idMover::GetClassType ( ) ) ) { + idEntity* ent; + idEntity* next; + for( ent = groundEnt->GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == groundEnt && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(ent) ); + aifl.tetherMover = true; + break; + } + } + } else { + SetTether ( NULL ); + } + } + } else if ( tether && aifl.tetherMover ) { + SetTether ( NULL ); + } +} + +/* +============ +idAI::Postthink +============ +*/ +void idAI::Postthink( void ){ + if ( !aifl.simpleThink ) { + pain.takenThisFrame = 0; + } + + // Draw debug tactical information + DrawTactical ( ); + + if( vehicleController.IsDriving() ){ // Generate some sort of command? + usercmd_t usercmd; + + // Note! usercmd angles stuff is in deltas, not in absolute values. + + memset( &usercmd, 0, sizeof( usercmd ) ); + + idVec3 toEnemy; + + if( enemy.ent ){ + toEnemy = enemy.ent->GetPhysics()->GetOrigin(); + toEnemy -= GetPhysics()->GetOrigin(); + toEnemy.Normalize(); + + idAngles enemyAng; + + enemyAng = toEnemy.ToAngles(); + + usercmd.angles[PITCH] = ANGLE2SHORT( enemyAng.pitch ); + usercmd.angles[YAW] = ANGLE2SHORT( enemyAng.yaw ); + + usercmd.buttons = BUTTON_ATTACK; + + vehicleController.SetInput ( usercmd, enemyAng ); + } + } + + // Keep our threat value up to date + UpdateThreat ( ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::OnDeath( void ){ + if( vehicleController.IsDriving() ){ + usercmd_t usercmd; + + memset( &usercmd, 0, sizeof( usercmd ) ); + usercmd.buttons = BUTTON_ATTACK; + usercmd.upmove = 300.0f; // This will cause the character to eject. + + vehicleController.SetInput( usercmd, idAngles( 0, 0, 0 ) ); + + // Fixme! Is this safe to do immediately? + vehicleController.Eject(); + } + + aiManager.RemoveTeammate ( this ); + + ExecScriptFunction( funcs.death ); + +/* DONT DROP ANYTHING FOR NOW + float rVal = gameLocal.random.RandomInt( 100 ); + + if( spawnArgs.GetFloat( "no_drops" ) >= 1.0 ){ + spawnArgs.Set( "def_dropsItem1", "" ); + }else{ + // Fixme! Better guys should drop better stuffs! Make drops related to guy type? Do something cooler here? + if( rVal < 25 ){ // Half of guys drop nothing? + spawnArgs.Set( "def_dropsItem1", "" ); + }else if( rVal < 50 ){ + spawnArgs.Set( "def_dropsItem1", "item_health_small" ); + } + } +*/ +} + +/* +============ +idAI::OnWakeUp +============ +*/ +void idAI::OnWakeUp ( void ) { +} + +/* +============ +idAI::OnUpdatePlayback +============ +*/ +void idAI::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + return; +} + +/* +============ +idAI::OnLeaderChange +============ +*/ +void idAI::OnLeaderChange ( idEntity* oldLeader ) { + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnEnemyChange +============ +*/ +void idAI::OnEnemyChange ( idEntity* oldEnemy ) { + // Make sure we update our tactical state immediately + ForceTacticalUpdate ( ); + + // see if we should announce the enemy + if ( enemy.ent ) { + combat.fl.aware = true; + combat.investigateTime = 0; + + enemy.changeTime = gameLocal.time; + + enemy.lastKnownPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + + UpdateEnemyVisibility ( ); + UpdateEnemyPosition ( true ); + UpdateEnemy ( ); + } else { + enemy.range = 0; + enemy.range2d = 0; + enemy.changeTime = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + } + + enemy.fl.visible = false; +} + +/* +============ +idAI::OnTacticalChange +============ +*/ +void idAI::OnTacticalChange ( aiTactical_t oldTactical ) { + // if acutally moving to a new tactial location announce it + if ( move.fl.moving ) { + AnnounceTactical( combat.tacticalCurrent ); + } +} + +/* +============ +idAI::OnFriendlyFire +============ +*/ +void idAI::OnFriendlyFire ( idActor* attacker ) { + AnnounceFriendlyFire( static_cast(attacker) ); +} + +/* +============ +idAI::OnStartMoving +============ +*/ +void idAI::OnStartMoving ( void ) { + aifl.simpleThink = false; + combat.fl.crouchViewClear = false; +} + +/* +============ +idAI::OnStopMoving +============ +*/ +void idAI::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { +} + +/* +============ +idAI::OnStartAction +============ +*/ +void idAI::OnStartAction ( void ) { +} + +/* +============ +idAI::OnStopAction +============ +*/ +void idAI::OnStopAction ( void ) { +} + +/* +============ +idAI::OnEnemyVisiblityChange +============ +*/ +void idAI::OnEnemyVisiblityChange ( bool oldVisible ) { +} + +/* +============ +idAI::OnSetKey +============ +*/ +void idAI::OnSetKey ( const char* key, const char* value ) { + if ( !idStr::Icmp ( key, "noCombatChatter" ) ) { + combat.fl.noChatter = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "allowPlayerPush" ) ) { + combat.tacticalMaskAvailable &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + if ( spawnArgs.GetBool ( key ) ) { + combat.tacticalMaskAvailable |= AITACTICAL_MOVE_PLAYERPUSH_BIT; + } + } else if ( !idStr::Icmp ( key, "noLook" ) ) { + aifl.disableLook = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "killer_guard" ) ) { + aifl.killerGuard = spawnArgs.GetBool ( key ); + } +} + +/* +============ +idAI::OnCoverInvalidated +============ +*/ +void idAI::OnCoverInvalidated ( void ) { + // Force a tactical update now + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnCoverNotFacingEnemy +============ +*/ +void idAI::OnCoverNotFacingEnemy ( void ) { + // Clear attack timers so we can shoot right now + actionTimerRangedAttack.Clear ( actionTime ); + actionRangedAttack.timer.Clear( actionTime ); +} + +/* +============ +idAI::SkipCurrentDestination + +Is the AI's current destination ok enough to stay at? +============ +*/ +bool idAI::SkipCurrentDestination ( void ) const { + // can only skip current destination when we are stopped + if ( move.fl.moving ) { + return false; + } +/* + // If we are currently behind cover and that cover is no longer valid we should skip it + if ( IsBehindCover ( ) && !IsCoverValid ( ) ) { + return true; + } +*/ + return false; +} + +/* +============ +idAI::SkipImpulse +============ +*/ +bool idAI::SkipImpulse( idEntity *ent, int id ){ + bool skip = idActor::SkipImpulse( ent, id ); + + if( af.IsActive ( ) ) { + return false; + } + if( !fl.takedamage ){ + return true; + } + if( move.moveCommand == MOVE_RV_PLAYBACK ){ + return true; + } + + return skip; +} + +/* +============ +idAI::CanHitEnemy +============ +*/ +bool idAI::CanHitEnemy ( void ) { + trace_t tr; + idEntity *hit; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + idVec3 eye = GetEyePosition(); + idVec3 dir; + + // expand the ray out as far as possible so we can detect anything behind the enemy + dir = toPos - eye; + dir.Normalize(); + toPos = eye + dir * MAX_WORLD_SIZE; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return lastHitCheckResult; + } + + gameLocal.TracePoint( this, tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + hit = gameLocal.GetTraceEntity( tr ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( hit == enemyEnt ) ) { + lastHitCheckResult = true; + } else if ( ( tr.fraction < 1.0f ) && ( hit->IsType( idAI::Type ) ) && + ( static_cast( hit )->team != team ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI::CanHitEnemyFromJoint +============ +*/ + +bool idAI::CanHitEnemyFromJoint( const char *jointname ){ + trace_t tr; + idVec3 muzzle; + idMat3 axis; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return true; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + jointHandle_t joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + animator.GetJointTransform( joint, gameLocal.time, muzzle, axis ); + muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, muzzle, toPos, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI:: +============ + +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ); + +int idAI::TestTrajectory( const idVec3 &firePos, const idVec3 &target, const char *projectileName ){ + idVec3 projVelocity; + idVec3 projGravity; + float testTime; + float zVel, height, pitch, s, c; + idVec3 dir; + float newVel; + float delta_x; + float delta_z; + + projectileDef = gameLocal.FindEntityDefDict ( projectileName ); + projVelocity = idProjectile::GetVelocity( projectileDef ); + projGravity = idProjectile::GetGravity( projectileDef ).z * GetPhysics()->GetGravity(); + pitch = DEG2RAD( gameLocal.random.RandomFloat() * 50 + 20 ); // Random pitch range between 20 and 70. Should this be customizeable? + + idMath::SinCos( pitch, s, c ); + + delta_x = idMath::Sqrt( ( target.x - firePos.x ) * ( target.x - firePos.x ) + ( target.y - firePos.y ) * ( target.y - firePos.y ) ); + delta_z = target.z - firePos.z; + newVel = ( delta_x / idMath::Cos( pitch ) ) * idMath::Sqrt( projGravity.z / ( 2.0f * ( delta_x * idMath::Tan( pitch ) - delta_z ) ) ); + testTime = delta_x / ( newVel * c ); + zVel = newVel * s; + + float a = idMath::ASin ( delta_x * GetPhysics()->GetGravity().Length() / (projVelocity.x * projVelocity.x) ); + a = a / 2; + + float r = (projVelocity.x * projVelocity.x) * idMath::Sin ( 2 * a ) / GetPhysics()->GetGravity().Length(); + if ( r < delta_x - (delta_x * 0.1) ) { + mVar.valid_lobbed_shot = 0; + return 0; + } else { + mVar.lobDir = target - firePos; + mVar.lobDir.z = 0; + mVar.lobDir.z = idMath::Tan ( a ) * mVar.lobDir.LengthFast(); + mVar.lobDir.Normalize ( ); + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = 1.0f; // newVel / projVelocity.Length(); + return 1; + } + + projGravity[2] *= -1.0f; + + dir = target - firePos; + dir.z = 0; + dir.Normalize(); + delta_x = idMath::Sqrt( 1 - ( c * c ) ); + dir.x *= delta_x; + dir.y *= delta_x; + dir.z = c; + + height = HeightForTrajectory( firePos, zVel, projGravity[2] ) - firePos.z; + if ( height > MAX_WORLD_SIZE ) { + // goes higher than we want to allow + mVar.valid_lobbed_shot = 0; + return 0; + } + + if ( idAI::TestTrajectory ( firePos, target, zVel, projGravity[2], testTime, height * 2, NULL, + MASK_SHOT_RENDERMODEL, this, enemy.GetEntity(), ai_debugTrajectory.GetBool() ? 4000 : 0 ) ) { + + if ( ai_debugTrajectory.GetBool() ) { + float t = testTime / 100.0f; + idVec3 velocity = dir * newVel; + idVec3 lastPos, pos; + lastPos = firePos; + pos = firePos; + for ( int j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, ai_debugTrajectory.GetBool() ? 4000 : 0 ); + lastPos = pos; + } + } + + mVar.lobDir = dir; + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = newVel / projVelocity.Length(); + }else{ + mVar.valid_lobbed_shot = 0; + } + + return ( (int)mVar.valid_lobbed_shot != 0 ); + +} +*/ + +/* +============ +idAI:: +============ +*/ + +float idAI::GetTurnDelta( void ){ + float amount; + + if ( move.turnRate ) { + amount = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + return amount; + } else { + return 0.0f; + } +} + +/* +============ +idAI::GetIdleAnimName +============ +*/ +const char* idAI::GetIdleAnimName ( void ) { + const char* animName = NULL; + + // Start idle animation + if ( enemy.ent ) { + animName = "idle_alert"; + } + + if ( animName && HasAnim ( ANIMCHANNEL_ALL, animName ) ) { + return animName; + } + + return "idle"; +} + +/* +=============================================================================== + + idAI - Enemy Finding + +=============================================================================== +*/ + +/* +============ +idAI::FindEnemy +============ +*/ +idEntity *idAI::FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr ){ + idActor* actor; + idActor* bestEnemy; + idActor* bestEnemyBackup; + float bestThreat; + float bestThreatBackup; + float distSqr; + float enemyRangeSqr; + float awareRangeSqr; + idVec3 origin; + idVec3 delta; + pvsHandle_t pvs; + + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + bestThreat = 0.0f; + bestThreatBackup = 0.0f; + bestEnemy = NULL; + bestEnemyBackup = NULL; + awareRangeSqr = Square ( combat.awareRange ); + enemyRangeSqr = enemy.ent ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + origin = GetEyePosition ( ); + + // Iterate through the enemy team + for( actor = aiManager.GetEnemyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden enemies and enemies that cant be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + + // Calculate the distance between ourselves and our potential enemy + delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin(); + distSqr = delta.LengthSqr(); + + // Calculate the adjusted threat for this actor + float threat = CalculateEnemyThreat ( actor ); + + // Save the highest threat enemy as a backup in case we cannot find one we can see + if ( threat > bestThreatBackup ) { + bestThreatBackup = threat; + bestEnemyBackup = actor; + } + + // If we have already found a more threatening enemy then attack that + if ( threat < bestThreat ) { + continue; + } + + // If this enemy isnt in the same pvps then use them as a backup + if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { + continue; + } + + // this is pretty specific to Quake 4, but we don't want the player to be around an enemy too long who can't see him. We need to randomly spike the + // awareRange on creatures to simulate them looking behind them, or noticing someone standing around for too long. + // Modders take note, this will prevent most "sneaking up on bad guys" action because they will likely spike their aware ranges out + // during the sneaking. + if( gameLocal.random.RandomFloat() < 0.005f ) { + awareRangeSqr *= 15; + } + + // fov doesn't matter if they're within awareRange, we "sense" them if we're alert... (or should LOS not even matter at this point?) + if ( distSqr < awareRangeSqr || CanSeeFrom ( origin, actor, (inFov && !(combat.fl.aware&&distSqrIsType ( idAI::Type ) ) { + idAI* teammateAI = static_cast(teammate); + enemy.smoothedLinearVelocity = teammateAI->enemy.smoothedLinearVelocity; + enemy.smoothedPushedVelocity = teammateAI->enemy.smoothedPushedVelocity; + enemy.lastKnownPosition = teammateAI->enemy.lastKnownPosition; + enemy.lastVisibleEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleFromEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleChestPosition = teammateAI->enemy.lastVisibleChestPosition; + enemy.lastVisibleTime = 0; + } + + return true; +} + +/* +============ +idAI::CheckForCloserEnemy +============ +*/ +bool idAI::CheckForCloserEnemy ( void ) { + idEntity* newEnemy = NULL; + float maxDistSqr; + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + // See if we happen to have heard someone this frame that we can use + newEnemy = HeardSound( true ); + if ( newEnemy && newEnemy != enemy.ent && newEnemy->IsType( idActor::GetClassType() ) ) { + //heard someone else! + float newDist = DistanceTo ( newEnemy->GetPhysics()->GetOrigin() ); + + // Are they closer than the enemy we are fighting? + if ( newDist < enemy.range ) { + //new enemy is closer than current one, take them! + SetEnemy( newEnemy ); + return true; + } + } + + if ( GetEnemy() && enemy.range ) { + maxDistSqr = Min( Square ( enemy.range ), Square ( combat.awareRange ) ); + } else { + maxDistSqr = Square ( combat.awareRange ); + } + + newEnemy = FindEnemy( false, 0, maxDistSqr ); + + if ( !newEnemy ) { + return false; + } + + SetEnemy( newEnemy ); + return true; +} + +/* +============ +idAI::CheckForReplaceEnemy + +TODO: Call CalculateThreat ( ent ) and compare to current entity +============ +*/ +bool idAI::CheckForReplaceEnemy ( idEntity* replacement ) { + bool replace; + + // If our replacement is a driver a vehicle and they are hidden we will + // want to shoot back at their vehicle not them. + idActor* actor; + actor = dynamic_cast(replacement); + if ( actor && actor->IsInVehicle ( ) && actor->IsHidden ( ) ) { + replacement = actor->GetVehicleController ( ).GetVehicle ( ); + } + + // Invalid replacement? + if ( !replacement) { + return false; + } + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + if ( replacement == enemy.ent ) { + return false; + } + + // Dont want to set our enemy to a friendly target + if ( replacement->IsType( idActor::GetClassType() ) && (static_cast(replacement))->team == team ) { + return false; + } + + // Not having an enemy will set it immediately + if ( !enemy.ent ) { + SetEnemy ( replacement ); + return true; + } + + // Dont change enemies too often when being hit + if ( gameLocal.time - enemy.changeTime < 1000 ) { + return false; + } + + replace = false; + + // If new enemy is more threatening then replace it reguardless if we can see it + if ( CalculateEnemyThreat ( replacement ) > CalculateEnemyThreat ( enemy.ent ) ) { + replace = true; + // Replace our enemy if we havent seen ours in a bit + } else if ( !IsEnemyRecentlyVisible ( 0.25f ) ) { + replace = true; + } + + // Replace enemy? + if ( replace ) { + SetEnemy ( replacement ); + } + + return replace; +} + +/* +============ +idAI::UpdateThreat +============ +*/ +void idAI::UpdateThreat ( void ) { + // Start threat at base threat level + combat.threatCurrent = combat.threatBase; + + // Adjust threat using current tactical state + switch ( combat.tacticalCurrent ) { + case AITACTICAL_HIDE: combat.threatCurrent *= 0.5f; break; + case AITACTICAL_MELEE: combat.threatCurrent *= 2.0f; break; + } + + // Signifigantly reduced threat when in undying mode + if ( aifl.undying ) { + combat.threatCurrent *= 0.25f; + } +} + +/* +============ +idAI::CalculateEnemyThreat +============ +*/ +float idAI::CalculateEnemyThreat ( idEntity* enemyEnt ) { + // Calculate the adjusted threat for this actor + float threat = 1.0f; + if ( enemyEnt->IsType ( idAI::GetClassType ( ) ) ) { + idAI* enemyAI = static_cast(enemyEnt); + threat = enemyAI->combat.threatCurrent; + + // Increase threat for enemies that are targetting us + if ( enemyAI->enemy.ent == this && enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE ) { + threat *= 2.0f; + } + } else if ( enemyEnt->IsType ( idPlayer::GetClassType ( ) ) ) { + threat = 2.0f; + } else { + threat = 1.0f; + } + + float enemyRangeSqr; + float distSqr; + + enemyRangeSqr = (enemy.ent) ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + distSqr = (physicsObj.GetOrigin ( ) - enemyEnt->GetPhysics()->GetOrigin ( )).LengthSqr ( ); + + if ( distSqr > 0 ) { + return threat * (enemyRangeSqr / distSqr); + } + + return threat; +} + +/* +============ +idAI::CheckBlink +============ +*/ +void idAI::CheckBlink ( void ) { +// if ( IsSpeaking ( ) ) { +// return; +// } + idActor::CheckBlink ( ); +} + +/* +============ +idAI::Speak +============ +*/ +bool idAI::Speak( const char *lipsync, bool random ){ + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + if ( random ) { + // If there is no lipsync then skip it + if ( spawnArgs.MatchPrefix ( lipsync ) ) { + lipsync = spawnArgs.RandomPrefix ( lipsync, gameLocal.random ); + } else { + lipsync = NULL; + } + } else { + lipsync = spawnArgs.GetString ( lipsync ); + } + + if ( !lipsync || !*lipsync ) { + return false; + } + + if ( head ) { + speakTime = head->StartLipSyncing( lipsync ); + } else { + speakTime = 0; + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, SSF_IS_VO, false, &speakTime ); + } + + speakTime += gameLocal.time; + return true; +} + +/* +============ +idAI::StopSpeaking +============ +*/ +void idAI::StopSpeaking( bool stopAnims ){ + speakTime = 0; + StopSound( SND_CHANNEL_VOICE, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false ); + if ( stopAnims ) { + head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + } + } +} + +/* +============ +idAI::CanHitEnemyFromAnim +============ +*/ +bool idAI::CanHitEnemyFromAnim( int animNum, idVec3 offset ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + trace_t tr; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin()+offset; + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + +/* + if( DebugFilter(ai_debugTactical) ) { + gameRenderWorld->DebugLine ( colorYellow, org + attackAnimInfo[ animNum ].eyeOffset * viewAxis, from, 5000 ); + gameRenderWorld->DebugLine ( colorOrange, from, enemy.lastVisibleEyePosition, 5000 ); + } +*/ + + // If the point we are shooting from is within our bounds then we are good to go, otherwise make sure its not in a wall + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + if ( !ownerBounds.ContainsPoint ( from ) ) { + trace_t tr; + if ( !g_perfTest_aiNoVisTrace.GetBool() ) { + gameLocal.TracePoint( this, tr, org + attackAnimInfo[ animNum ].eyeOffset * axis, from, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction < 1.0f ) { + return false; + } + } + } + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +idAI::ScriptedBegin +================ +*/ +bool idAI::ScriptedBegin ( bool endWithIdle, bool allowDormant ) { + if ( aifl.dead ) { + return false; + } + + // Wakeup if not awake already + WakeUp ( ); + + aifl.scriptedEndWithIdle = endWithIdle; + aifl.scripted = true; +// combat.fl.aware = false; + combat.tacticalCurrent = AITACTICAL_NONE; + + // Make sure the entity never goes dormant during a scripted event or + // the event may never end. + aifl.scriptedNeverDormant = !allowDormant; + dormantStart = 0; + +/* + // actors will ignore enemies during scripted events + ClearEnemy ( ); +*/ + + // Cancel any current movement + StopMove ( MOVE_STATUS_DONE ); + + move.fl.allowAnimMove = true; + + return true; +} + +/* +================ +idAI::ScriptedEnd +================ +*/ +void idAI::ScriptedEnd ( void ) { + dormantStart = 0; + aifl.scripted = false; +} + +/* +================ +idAI::ScriptedStop +================ +*/ +void idAI::ScriptedStop ( void ) { + if ( !aifl.scripted ) { + return; + } + aifl.scriptedEndWithIdle = true; + aifl.scripted = false; + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::ScriptedMove +================ +*/ +void idAI::ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + //disable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.UnMarkAllReachBlocked(); + //attempt the move - NOTE: this *can* fail if there's no route or AAS obstacles are in the way! + MoveToEntity ( destEnt, minDist ); + //re-enable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.ReMarkAllReachBlocked(); + + // Move the torso to the idle state if its not already there + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Move the legs into the idle state so he will start moving + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + + // Set up state loop for moving + SetState ( "State_ScriptedMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedFace +================ +*/ +void idAI::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + // Force idle while facing + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Start facing the entity + FaceEntity ( faceEnt ); + + SetState ( "State_ScriptedFace" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedAnim + +Plays an the given animation in an un-interruptable state. If looping will continue indefinately until +another operation which will stop a scripted sequence is called. When done can optionally return the character +back to their normal processing if endWithIdle is set to true. +================ +*/ +void idAI::ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle, true ) ) { + return; + } + + TurnToward ( move.current_yaw ); + + if ( loop ) { + // Loop the given animation + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + // Play the given animation + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + + SetAnimState ( ANIMCHANNEL_LEGS, "Wait_Frame" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_ScriptedAnim" ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + + SetState ( "Wait_ScriptedDone" ); + PostState ( "State_ScriptedStop" ); +} + + +/* +============ +idAI::ScriptedPlaybackAim +============ +*/ +void idAI::ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + mLookPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ); + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackAim" ); + PostState ( "State_ScriptedStop" ); +} + +/* +============ +idAI::ScriptedAction +============ +*/ +void idAI::ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + const char* actionName; + + if ( !actionEnt ) { + return; + } + + // Get the action name + actionName = actionEnt->spawnArgs.GetString ( "action" ); + if ( !*actionName ) { + gameLocal.Error ( "missing action keyword on scripted action entity '%s' for ai '%s'", + actionEnt->GetName(), + GetName() ); + return; + } + + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + scriptedActionEnt = actionEnt; + + SetState ( "State_ScriptedStop" ); + PerformAction ( va("TorsoAction_%s", actionName ), 4, true ); +} + +/* +============ +idAI::FootStep +============ +*/ +void idAI::FootStep ( void ) { + idActor::FootStep ( ); + + ExecScriptFunction( funcs.footstep ); +} + +/* +============ +idAI::SetScript +============ +*/ +void idAI::SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "first_sight" ) ) { + funcs.first_sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "sight" ) ) { + funcs.sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "pain" ) ) { + funcs.pain.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "damage" ) ) { + funcs.damage.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "death" ) ) { + funcs.death.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "attack" ) ) { + funcs.attack.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "init" ) ) { + funcs.init.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "onclick" ) ) { + funcs.onclick.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "launch_projectile" ) ) { + funcs.launch_projectile.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "footstep" ) ) { + funcs.footstep.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "postHeal" ) ) { + // hax: this is a medic only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else if ( !idStr::Icmp ( scriptName, "postWeaponDestroyed" ) ) { + // hax: this is a Gladiator/Light Tank only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else { + gameLocal.Warning ( "unknown script '%s' specified on entity '%s'", scriptName, name.c_str() ); + } +} + +/* +=============================================================================== + + idAI - Reactions + +=============================================================================== +*/ + +/* +============ +idAI::ReactToShotAt +============ +*/ +void idAI::ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ) { + if ( g_perfTest_aiNoDodge.GetBool() ) { + return; + } + + idVec3 foo; + idVec3 diff; + diff = GetPhysics()->GetOrigin() - origOrigin; + diff = origOrigin + diff.ProjectOntoVector ( origDir ) * origDir; + diff = diff - GetPhysics()->GetOrigin(); + diff.NormalizeFast ( ); + diff.z = 0; + + idAngles angles = diff.ToAngles ( ); + float angleDelta = idMath::AngleDelta ( angles[YAW], move.current_yaw ); + + combat.shotAtTime = gameLocal.time; + combat.shotAtAngle = angleDelta; + + // Someone is attacking us so give them a chance to be our new enemy + CheckForReplaceEnemy ( attacker ); +} + +/* +============ +idAI::ReactToPain +============ +*/ +void idAI::ReactToPain ( idEntity* attacker, int damage ) { + CheckForReplaceEnemy ( attacker ); +} + +/* +=============================================================================== + + idAI - Helpers + +=============================================================================== +*/ + +/* +============ +idAI::UpdateHelper +============ +*/ +void idAI::UpdateHelper ( void ) { + rvAIHelper* oldhelper; + + // Link ourselves to the closest helper + oldhelper = helperCurrent; + helperCurrent = aiManager.FindClosestHelper ( physicsObj.GetOrigin() ); + + // Ideal stays the same as current as long as it was the same when we started + if ( oldhelper == helperIdeal ) { + helperIdeal = helperCurrent; + } +} + +/* +============ +idAI::GetActiveHelper + +When we have an enemy our current helper becomes the active helper, when we dont have an +enemy we instead use our ideal. +============ +*/ +rvAIHelper* idAI::GetActiveHelper ( void ) { + return GetEnemy ( ) ? helperCurrent : helperIdeal; +} + +/* +=============================================================================== + + idAI - Tethers + +=============================================================================== +*/ + +/* +============ +idAI::SetTether +============ +*/ +void idAI::SetTether ( rvAITether* newTether ) { + aifl.tetherMover = false; + + // Clear our current tether? + if ( !newTether ) { + if ( tether ) { + tether = NULL; + ForceTacticalUpdate ( ); + } + } else if ( newTether->IsType ( rvAITetherClear::GetClassType ( ) ) ) { + SetTether ( NULL ); + } else { + if ( newTether && !newTether->ValidateAAS ( this ) ) { + // If you have aas error out to make them fix it + if ( aas ) { + gameLocal.Error ( "tether entity '%s' does no link into the aas for ai '%s'. (try moving it closer to the floor where the aas is)", + newTether->GetName(), GetName () ); + // If we dont have aas, just warn + } else { + gameLocal.Warning ( "tether entity '%s' does no link into the aas for ai '%s'. (there is no aas available)", + newTether->GetName(), GetName () ); + } + SetTether ( NULL ); + } else if ( newTether != tether ) { + tether = newTether; + ForceTacticalUpdate ( ); + } + } +} + +/* +============ +idAI::GetTether +============ +*/ +rvAITether* idAI::GetTether ( void ) { + return tether; +} + +/* +============ +idAI::IsTethered +============ +*/ +bool idAI::IsTethered ( void ) const { + // Need a tether entity to be tethered + if ( !tether ) { + return false; + } + // If we have an enemy and that enemy is within our tether then break it if we can + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) && tether->CanBreak ( ) ) { + if ( tether->ValidateDestination ( static_cast(enemy.ent.GetEntity()), enemy.lastKnownPosition ) ) { + return false; + } + } + return true; +} + +/* +============ +idAI::IsWithinTether +============ +*/ +bool idAI::IsWithinTether ( void ) const { + if ( !IsTethered ( ) ) { + return false; + } + if ( !tether->ValidateDestination ( (idAI*)this, physicsObj.GetOrigin ( ) ) ) { + return false; + } + return true; +} + +/* +=============================================================================== + + idAI - NonCombat + +=============================================================================== +*/ + +/* +===================== +idAI::SetTalkState +===================== +*/ +void idAI::SetTalkState ( talkState_t state ) { + // Make sure state is valid + if ( ( state < 0 ) || ( state >= NUM_TALK_STATES ) ) { + gameLocal.Error( "Invalid talk state (%d)", (int)state ); + } + + // Same state we are already in? + if ( talkState == state ) { + return; + } + + // Set new talk state + talkState = state; + +} + +/* +============ +idAI::SetPassivePrefix +============ +*/ +void idAI::SetPassivePrefix ( const char* prefix ) { + passive.prefix = prefix; + if ( passive.prefix.Length() ) { + passive.prefix += "_"; + } + + // Force an idle change + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + + // Get animation prefixs + passive.fl.multipleIdles = GetPassiveAnimPrefix ( "idle", passive.animIdlePrefix ); + GetPassiveAnimPrefix ( "fidget", passive.animFidgetPrefix ); + GetPassiveAnimPrefix ( "talk", passive.animTalkPrefix ); +} + +/* +============ +idAI::GetPassiveAnimPrefix +============ +*/ +bool idAI::GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ) { + const idKeyValue* key; + + // First see if we have custom idle animations for the passive prefix + key = NULL; + if ( passive.prefix.Length ( ) ) { + animPrefix = va("anim_%s%s", passive.prefix.c_str(), animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + // If there are no custom idle animations for the prefix then see if there are any custom anims at all + if ( !key ) { + animPrefix = va("anim_%s", animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + if ( !key ) { + animPrefix = ""; + return false; + } + + return spawnArgs.MatchPrefix ( animPrefix, key ) ? true : false; +} + +/* +=================== +idAI::IsMeleeNeeded +=================== +*/ +bool idAI::IsMeleeNeeded( void ) { + + if( enemy.ent && enemy.ent->IsType ( idAI::Type )) { + + idAI* enemyAI = static_cast(enemy.ent.GetEntity()); + + //if our enemy is closing in on us and demands melee, we'll meet him. + if ( enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE && enemy.range < combat.meleeRange ) { + return true; + } + + //other checks... + } + + return false; +} + +/* +=================== +idAI::IsCrouching +=================== +*/ +bool idAI::IsCrouching( void ) const { + return move.fl.crouching; +} + +bool idAI::CheckDeathCausesMissionFailure( void ) +{ + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) + { + return true; + } + if ( targets.Num() ) + { + //go through my targets and see if any are of class rvObjectiveFailed + idEntity* targEnt; + for( int i = 0; i < targets.Num(); i++ ) { + targEnt = targets[ i ].GetEntity(); + if ( !targEnt ) + { + continue; + } + if ( !targEnt->IsType( rvObjectiveFailed::GetClassType() ) ) { + continue; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + continue; + } + //yep! + return true; + } + } + return false; +} diff --git a/src/game/ai/AI.h b/src/game/ai/AI.h new file mode 100644 index 0000000..7761f94 --- /dev/null +++ b/src/game/ai/AI.h @@ -0,0 +1,1397 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI.h + +================ +*/ + +#ifndef __AI_H__ +#define __AI_H__ + +// moved all motion related code to AI_Move.h +#ifndef __AI_MOVE_H__ + #include "AI_Move.h" +#endif +#ifndef __AAS_TACTICAL_H__ + #include "AAS_tactical.h" +#endif + +typedef enum { + AITACTICAL_NONE, + AITACTICAL_MELEE, // Rush towards the enemy and perform melee attacks when within range + AITACTICAL_MOVE_FOLLOW, // Move towards leader, stop when within range + AITACTICAL_MOVE_TETHER, // Move within tether, stop when within tether + AITACTICAL_MOVE_PLAYERPUSH, // Move away when the player pushes us (or another ai entity pushes us that was pushe by player) + AITACTICAL_COVER, // Move to cover and perform attacks from that cover position + AITACTICAL_COVER_FLANK, + AITACTICAL_COVER_ADVANCE, + AITACTICAL_COVER_RETREAT, + AITACTICAL_COVER_AMBUSH, + AITACTICAL_RANGED, // Move to position in which the enemy can be attacked from range, stop when there and attack enemy + AITACTICAL_TURRET, // Stay in current position and attack enemy + AITACTICAL_HIDE, // Move to a position where we cannot be seen by our enemy + AITACTICAL_PASSIVE, // Stay in current position with multiple idles, twitch animations, and conversations + + AITACTICAL_MAX +} aiTactical_t; + +typedef enum { + AICTRESULT_OK, + AICTRESULT_SKIP, + AICTRESULT_NOMOVE, +} aiCTResult_t; + +const int AITACTICAL_NONE_BIT = BIT(AITACTICAL_NONE); +const int AITACTICAL_MELEE_BIT = BIT(AITACTICAL_MELEE); +const int AITACTICAL_MOVE_FOLLOW_BIT = BIT(AITACTICAL_MOVE_FOLLOW); +const int AITACTICAL_MOVE_TETHER_BIT = BIT(AITACTICAL_MOVE_TETHER); +const int AITACTICAL_MOVE_PLAYERPUSH_BIT = BIT(AITACTICAL_MOVE_PLAYERPUSH); +const int AITACTICAL_COVER_BIT = BIT(AITACTICAL_COVER); +const int AITACTICAL_COVER_FLANK_BIT = BIT(AITACTICAL_COVER_FLANK); +const int AITACTICAL_COVER_ADVANCE_BIT = BIT(AITACTICAL_COVER_ADVANCE); +const int AITACTICAL_COVER_RETREAT_BIT = BIT(AITACTICAL_COVER_RETREAT); +const int AITACTICAL_COVER_AMBUSH_BIT = BIT(AITACTICAL_COVER_AMBUSH); +const int AITACTICAL_RANGED_BIT = BIT(AITACTICAL_RANGED); +const int AITACTICAL_TURRET_BIT = BIT(AITACTICAL_TURRET); +const int AITACTICAL_HIDE_BIT = BIT(AITACTICAL_HIDE); +const int AITACTICAL_PASSIVE_BIT = BIT(AITACTICAL_PASSIVE); + +const int AITACTICAL_COVER_BITS = (AITACTICAL_COVER_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); +const int AITACTICAL_RANGED_BITS = (AITACTICAL_RANGED_BIT); +const int AITACTICAL_NONMOVING_BITS = (AITACTICAL_NONE_BIT|AITACTICAL_TURRET_BIT|AITACTICAL_PASSIVE_BIT); + +typedef enum { + TALKMSG_NONE, + TALKMSG_PRIMARY, + TALKMSG_SECONDARY, + TALKMSG_LOOP, +} talkMessage_t; + +typedef enum { + AIFLAGOVERRIDE_DAMAGE, + AIFLAGOVERRIDE_DISABLEPAIN, + AIFLAGOVERRIDE_NOTURN, + AIFLAGOVERRIDE_NOGRAVITY +} aiFlagOverride_t; + +typedef enum { + TALK_NEVER, // Never talk to the player + TALK_DEAD, // Cant talk due to being dead + TALK_OK, // Can talk + TALK_FOLLOW, // Talking to will cause a follow + TALK_BUSY, // Cant talk right now, is busy + TALK_WAIT, // Wait a bit - he's probably in the middle of a conversation (this is so you can still see their names but they won't talk to you when clicked on) + NUM_TALK_STATES +} talkState_t; + +//chance that AI will make an announcement when they have the option (0 - 1.0f) +#define AISPEAK_CHANCE 0.2f + +typedef enum { + AIFOCUS_NONE, + AIFOCUS_LEADER, + AIFOCUS_TARGET, + AIFOCUS_TALK, + AIFOCUS_PLAYER, + AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_ENEMY = AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_COVER, + AIFOCUS_COVERLOOK, + AIFOCUS_HELPER, + AIFOCUS_TETHER, + AIFOCUS_MAX +} aiFocus_t; + +typedef enum { + AIMOVESPEED_DEFAULT, // Choose run/walk depending on situation + AIMOVESPEED_RUN, // Always run + AIMOVESPEED_WALK, // alwasy walk +} aiMoveSpeed_t; + +typedef struct rvAIFuncs_s { + rvScriptFuncUtility first_sight; // script to run when an enemy is first sighted + rvScriptFuncUtility sight; // script to run every time an enemy is sighted + rvScriptFuncUtility pain; // script to run when the AI takes pain + rvScriptFuncUtility damage; // script to run when the AI takes damage + rvScriptFuncUtility death; // script to run when the AI dies + rvScriptFuncUtility attack; // script to run when attacking an enemy + rvScriptFuncUtility init; // script to run on initialization + rvScriptFuncUtility onclick; // script to run when a friendly AI is clicked on + rvScriptFuncUtility launch_projectile; // script to run when a projectile is launched + rvScriptFuncUtility footstep; // script to run on a footstep +} rvAIFuncs_t; + +typedef struct rvAICombat_s{ + struct combatFlags_s { + bool ignoreEnemies :1; + bool alert :1; + bool aware :1; + bool tetherNoBreak :1; // Set to true to prevent enemies from breaking tethers + bool tetherAutoBreak :1; // Set to true to automatically break tethers when within range + bool tetherOutOfRange :1; // Set to true when we are out of range of our curren tether + bool seenEnemyDirectly :1; // Has directly seen an enemy (as opposed to having heard or been told about him) + bool noChatter :1; // No combat announcements + bool crouchViewClear :1; // Can I crouch at the position that I stopped at? + } fl; + + float max_chasing_turn; + float shotAtTime; + float shotAtAngle; + idVec2 hideRange; + idVec2 attackRange; + int attackSightDelay; + float meleeRange; + float aggressiveRange; // Range to become more aggressive + float aggressiveScale; // Scale to use when altering numbers due to aggression + int investigateTime; + + float visStandHeight; // Height to check enemy visibiliy while standing + float visCrouchHeight; // Height to check enemy visiblity while crouching + float visRange; // Maximum distance to check enemy visibility + float earRange; // Maximum distance to check hearing an enemy from + float awareRange; // Distance to become automatically aware of an enemy + + int tacticalMaskAvailable; // Currently available tactical states + int tacticalMaskUpdate; // states currently being evaluated + aiTactical_t tacticalCurrent; // Current tactical state + int tacticalUpdateTime; // Last time the tacitcal state was updated (for delaying updating) + int tacticalPainTaken; // Amount of damage taken in the current tactical state + int tacticalPainThreshold; // Threshold of pain before invalidating the current tactical state + int tacticalFlinches; // Number of flinches that have occured at the current tactical state + + float threatBase; // Base amount of threat generated by AI + float threatCurrent; // Current amount of threat generatd by AI based on current state + + int maxLostVisTime; + + int coverValidTime; + int maxInvalidCoverTime; +} rvAICombat_t; + +typedef struct rvAIPain_s { + float threshold; + float takenThisFrame; + int lastTakenTime; + int loopEndTime; + idStr loopType; +} rvAIPain_t; + +typedef struct rvAIEnemy_s { + struct flags_s { + bool lockOrigin :1; // Stop tracking enemy origin until state changes + bool dead :1; // Enemy is dead + bool inFov :1; // Enemy is currently in fov + bool sighted :1; // Enemy was sighted at least once + bool visible :1; // Enemy is visible? + } fl; + + idEntityPtr ent; + int lastVisibleChangeTime; // last time the visible state of the enemy changed + idVec3 lastVisibleFromEyePosition; // Origin used in last successfull visibility check + idVec3 lastVisibleEyePosition; // Origin of last known visible eye position + idVec3 lastVisibleChestPosition; // Origin of last known visible chest position + int lastVisibleTime; // Time we last saw and enemy + + idVec3 smoothedLinearVelocity; + idVec3 smoothedPushedVelocity; + float smoothVelocityRate; + + idVec3 lastKnownPosition; // last place the enemy was known to be (does not mean visiblity) + + float range; // range to enemy + float range2d; // 2d range to enemy + int changeTime; // Time enemy was last changed + int checkTime; // Time we last checked for a new enemy +} rvAIEnemy_t; + +typedef struct rvAIPassive_s { + struct flags_s { + bool disabled :1; // No advanced passive state + bool multipleIdles :1; // Has multiple idle animations to play + bool fidget :1; // Has fidget animations + } fl; + + idStr animIdlePrefix; + idStr animFidgetPrefix; + idStr animTalkPrefix; + //idStr talkPrefix; + + idStr prefix; + idStr idleAnim; + int idleAnimChangeTime; + int fidgetTime; + int talkTime; +} rvAIPassive_t; + +typedef struct rvAIAttackAnimInfo_s { + idVec3 attackOffset; + idVec3 eyeOffset; +} rvAIAttackAnimInfo_t; + +#define AIACTIONF_ATTACK BIT(0) +#define AIACTIONF_MELEE BIT(1) + +class rvAIActionTimer { +public: + + rvAIActionTimer ( void ); + + bool Init ( const idDict& args, const char* name ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( int currentTime ); + void Reset ( int currentTime, float diversity = 0.0f, float scale = 1.0f ); + void Add ( int _time, float diversity = 0.0f ); + + bool IsDone ( int currentTime ) const; + + int GetTime ( void ) const; + int GetRate ( void ) const; + +protected: + + int time; + int rate; +}; + +ID_INLINE bool rvAIActionTimer::IsDone ( int currentTime ) const { + return currentTime >= time; +} + +ID_INLINE int rvAIActionTimer::GetTime ( void ) const { + return time; +} + +ID_INLINE int rvAIActionTimer::GetRate ( void ) const { + return rate; +} +class rvAIAction { +public: + + rvAIAction ( void ); + + bool Init ( const idDict& args, const char* name, const char* defaultState, int flags ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum EStatus { + STATUS_UNUSED, + STATUS_OK, // action was performed + STATUS_FAIL_DISABLED, // action is current disabled + STATUS_FAIL_TIMER, // actions timer has not finished + STATUS_FAIL_EXTERNALTIMER, // external timer passed in to PerformAction has not finished + STATUS_FAIL_MINRANGE, // enemy is not within minimum range + STATUS_FAIL_MAXRANGE, // enemy is out of maximum range + STATUS_FAIL_CHANCE, // action chance check failed + STATUS_FAIL_ANIM, // bad animation + STATUS_FAIL_CONDITION, // condition given to PerformAction failed + STATUS_FAIL_NOENEMY, // enemy cant be attacked + STATUS_MAX + }; + + struct flags_s { + bool disabled :1; // action disabled? + bool noPain :1; // no pain during action + bool noTurn :1; // no turning during action + bool isAttack :1; // attack? + bool isMelee :1; // melee? + bool overrideLegs :1; // override legs on this action? + bool noSimpleThink :1; // dont use simple think logic for this action + } fl; + + + idStrList anims; + idStr state; + + rvAIActionTimer timer; + + int blendFrames; + int failRate; + + float minRange; + float maxRange; + float minRange2d; + float maxRange2d; + + float chance; + float diversity; + + EStatus status; +}; + +typedef bool (idAI::*checkAction_t)(rvAIAction*,int); + +class rvPlaybackDriver +{ +public: + rvPlaybackDriver( void ) { mPlaybackDecl = NULL; mOldPlaybackDecl = NULL; } + + bool Start( const char *playback, idEntity *owner, int flags, int numFrames ); + bool UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ); + void EndFrame( void ); + bool IsActive( void ) { return( !!mPlaybackDecl || !!mOldPlaybackDecl ); } + + const char *GetDestination( void ); + +// cnicholson: Begin Added save/restore functionality + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +// cnicholson: End Added save/restore functionality + +private: + int mLastTime; + int mTransitionTime; + + int mStartTime; + int mFlags; + const rvDeclPlayback *mPlaybackDecl; + idVec3 mOffset; + + int mOldStartTime; + int mOldFlags; + const rvDeclPlayback *mOldPlaybackDecl; + idVec3 mOldOffset; +}; + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +const float AI_TURN_SCALE = 60.0f; +const float AI_SEEK_PREDICTION = 0.3f; +const float AI_FLY_DAMPENING = 0.15f; +const float AI_HEARING_RANGE = 2048.0f; +const float AI_COVER_MINRANGE = 4.0f; +const float AI_PAIN_LOOP_DELAY = 200; +const int DEFAULT_FLY_OFFSET = 68.0f; + +#define ATTACK_IGNORE 0 +#define ATTACK_ON_DAMAGE BIT(0) +#define ATTACK_ON_ACTIVATE BIT(1) +#define ATTACK_ON_SIGHT BIT(2) + +// defined in script/ai_base.script. please keep them up to date. + +#define DI_NODIR -1 + + +// +// events +// +extern const idEventDef AI_DirectDamage; +extern const idEventDef AI_JumpFrame; +extern const idEventDef AI_EnableClip; +extern const idEventDef AI_DisableClip; +extern const idEventDef AI_EnableGravity; +extern const idEventDef AI_DisableGravity; +extern const idEventDef AI_EnablePain; +extern const idEventDef AI_DisablePain; +extern const idEventDef AI_EnableTarget; +extern const idEventDef AI_DisableTarget; +extern const idEventDef AI_EnableMovement; +extern const idEventDef AI_DisableMovement; +extern const idEventDef AI_Vagary_ChooseObjectToThrow; +extern const idEventDef AI_Speak; +extern const idEventDef AI_SpeakRandom; +extern const idEventDef AI_Attack; +extern const idEventDef AI_AttackMelee; +extern const idEventDef AI_WaitMove; +extern const idEventDef AI_EnableDamage; +extern const idEventDef AI_DisableDamage; +extern const idEventDef AI_LockEnemyOrigin; +extern const idEventDef AI_SetEnemy; +extern const idEventDef AI_ScriptedAnim; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; +extern const idEventDef AI_SetScript; +extern const idEventDef AI_BecomeSolid; +extern const idEventDef AI_BecomePassive; +extern const idEventDef AI_BecomeAggressive; +extern const idEventDef AI_SetHealth; +extern const idEventDef AI_TakeDamage; +extern const idEventDef AI_EnableBlink; +extern const idEventDef AI_DisableBlink; +extern const idEventDef AI_EnableAutoBlink; +extern const idEventDef AI_DisableAutoBlink; + +class idPathCorner; +class idProjectile; +class rvSpawner; +class rvAIHelper; +class rvAITether; + +class idAI : public idActor { +friend class rvAIManager; +friend class idAASFindAttackPosition; +public: + CLASS_PROTOTYPE( idAI ); + + idAI(); + ~idAI(); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Spawn ( void ); + virtual void TalkTo ( idActor *actor ); + + idEntity* GetEnemy ( void ) const; + idEntity* GetGoalEntity ( void ) const; + talkState_t GetTalkState ( void ) const; + const idVec2& GetAttackRange ( void ) const; + const idVec2& GetFollowRange ( void ) const; + int GetTravelFlags ( void ) const; + + void TouchedByFlashlight ( idActor *flashlight_owner ); + + idEntity * FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr = 0.0f ); + void SetSpawner ( rvSpawner* _spawner ); + rvSpawner* GetSpawner ( void ); + + idActor* GetLeader ( void ) const; + + // Outputs a list of all monsters to the console. + static void List_f( const idCmdArgs &args ); + + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + bool IsEnemyVisible ( void ) const; + bool InCoverMode ( void ) const; + bool InCrouchCoverMode ( void ) const; + bool LookAtCoverTall ( void ) const; + bool InLookAtCoverMode ( void ) const; + bool IsBehindCover ( void ) const; + bool IsLipSyncing ( void ) const; + bool IsSpeaking ( void ) const; + bool IsFacingEnt ( idEntity* targetEnt ); + bool IsCoverValid ( void ) const; + virtual bool IsCrouching ( void ) const; + + +public: + + idLinkList simpleThinkNode; + + // navigation + idAAS* aas; + idAASCallback* aasFind; + + // movement + idMoveState move; + idMoveState savedMove; + + // physics + idPhysics_Monster physicsObj; + + // weapon/attack vars + bool lastHitCheckResult; + int lastHitCheckTime; + int lastAttackTime; + float projectile_height_to_distance_ratio; // calculates the maximum height a projectile can be thrown + idList attackAnimInfo; + + mutable idClipModel* projectileClipModel; + idEntityPtr projectile; + + // chatter/talking + int chatterTime; + int chatterRateCombat; + int chatterRateIdle; + talkState_t talkState; + idEntityPtr talkTarget; + talkMessage_t talkMessage; + int talkBusyCount; + int speakTime; + + // Focus + idEntityPtr lookTarget; + aiFocus_t focusType; + idEntityPtr focusEntity; + float focusRange; + int focusAlignTime; + int focusTime; + idVec3 currentFocusPos; + + // Looking + bool allowJointMod; + int alignHeadTime; + int forceAlignHeadTime; + idAngles eyeAng; + idAngles lookAng; + idAngles destLookAng; + idAngles lookMin; + idAngles lookMax; + idList lookJoints; + idList lookJointAngles; + float eyeVerticalOffset; + float eyeHorizontalOffset; + float headFocusRate; + float eyeFocusRate; + + // joint controllers + idAngles eyeMin; + idAngles eyeMax; + jointHandle_t orientationJoint; + + idEntityPtr pusher; + idEntityPtr scriptedActionEnt; + + // script variables + struct aiFlags_s { + bool awake :1; // set to false until state_wakeup is called. + bool damage :1; + bool pain :1; + bool dead :1; + bool activated :1; + bool jump :1; + bool hitEnemy :1; + bool pushed :1; + bool disableAttacks :1; + bool scriptedEndWithIdle :1; + bool scriptedNeverDormant :1; // Prevent going dormant while in scripted sequence + bool scripted :1; + bool simpleThink :1; + bool ignoreFlashlight :1; + bool action :1; + bool lookAtPlayer :1; + bool disableLook :1; + bool undying :1; + bool tetherMover :1; // Currently using a dynamic tether to a mover + bool meleeSuperhero :1; + bool killerGuard :1; // Do 100 points of damage with each hit + } aifl; + + // + // ai/ai.cpp + // + void SetAAS ( void ); + virtual void DormantBegin ( void ); // called when entity becomes dormant + virtual void DormantEnd ( void ); // called when entity wakes from being dormant + virtual void Think ( void ); + void Activate ( idEntity *activator ); + virtual void Hide ( void ); + virtual void Show ( void ); + virtual void AdjustHealthByDamage ( int inDamage ); + void CalculateAttackOffsets ( void ); + + void InitNonPersistentSpawnArgs ( void ); + + /* + =============================================================================== + Speaking & Chatter + =============================================================================== + */ +public: + + bool Speak ( const char *speechDecl, bool random = false ); + void StopSpeaking ( bool stopAnims ); + virtual void CheckBlink ( void ); + +protected: + + virtual bool CanPlayChatterSounds ( void ) const; + void UpdateChatter ( void ); + + + /* + =============================================================================== + Movement + =============================================================================== + */ + + // static helper functions +public: + // Finds a path around dynamic obstacles. + static bool FindPathAroundObstacles ( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ); + // Frees any nodes used for the dynamic obstacle avoidance. + static void FreeObstacleAvoidanceNodes ( void ); + // Predicts movement, returns true if a stop event was triggered. + static bool PredictPath ( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore = NULL ); + // Return true if the trajectory of the clip model is collision free. + static bool TestTrajectory ( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ); + // Finds the best collision free trajectory for a clip model. + static bool PredictTrajectory ( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ); + + + // special flying code + void AdjustFlyingAngles ( void ); + void AddFlyBob ( idVec3 &vel ); + void AdjustFlyHeight ( idVec3 &vel, const idVec3 &goalPos ); + void FlySeekGoal ( idVec3 &vel, idVec3 &goalPos ); + void AdjustFlySpeed ( idVec3 &vel ); + void FlyTurn ( void ); + + // movement types + void Move ( void ); + virtual void DeadMove ( void ); + void AnimMove ( void ); + void SlideMove ( void ); + void PlaybackMove ( void ); + void FlyMove ( void ); + void StaticMove ( void ); + void RVMasterMove ( void ); + void SetMoveType ( moveType_t moveType ); + //twhitaker: added custom move type + virtual void CustomMove ( void ); + + // movement actions + void KickObstacles ( const idVec3 &dir, float force, idEntity *alwaysKick ); + + // steering + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + void GetAnimMoveDelta ( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ); + void CheckObstacleAvoidance ( const idVec3 &goalPos, idVec3 &newPos, idReachability* goalReach=0 ); + bool GetMovePos ( idVec3 &seekPos, idReachability** seekReach=0 ); + + + // navigation + float TravelDistance ( const idVec3 &end ) const; + float TravelDistance ( const idVec3 &start, const idVec3 &end ) const; + float TravelDistance ( idEntity* ent ) const; + float TravelDistance ( idEntity* start, idEntity* end ) const; + int PointReachableAreaNum ( const idVec3 &pos, const float boundsScale = 2.0f ) const; + bool PathToGoal ( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + void BlockedFailSafe ( void ); + + // turning + void Turn ( void ); + bool TurnToward ( float yaw ); + bool TurnToward ( const idVec3 &pos ); + bool TurnTowardLeader ( bool faceLeaderByDefault=false ); + bool FacingIdeal ( void ); + bool DirectionalTurnToward ( const idVec3 &pos ); + + // movement control + bool FaceEnemy ( void ); + bool FaceEntity ( idEntity *ent ); + bool SlideToPosition ( const idVec3 &pos, float time ); + bool WanderAround ( void ); + bool StepDirection ( float dir ); + bool NewWanderDir ( const idVec3 &dest ); + + + /* + =============================================================================== + Reactions + =============================================================================== + */ + void ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ); + void ReactToPain ( idEntity* attacker, int damage ); + + /* + =============================================================================== + AI helpers + =============================================================================== + */ + +public: + + void UpdateHelper ( void ); + rvAIHelper* GetActiveHelper ( void ); + + /* + =============================================================================== + Sensory Perception + =============================================================================== + */ + +public: + + const idVec3& LastKnownPosition ( const idEntity *ent ); + const idVec3& LastKnownFacing ( const idEntity *ent ); + idEntity * HeardSound ( int ignore_team ); + int ReactionTo ( const idEntity *ent ); + void SetLastVisibleEnemyTime ( int time=-1/* DEFAULT IS CURRENT TIME*/ ); + bool IsEnemyRecentlyVisible ( float maxLostVisTimeScale = 1.0f ) const; + + /* + =============================================================================== + Passive + =============================================================================== + */ + +public: + + void SetTalkState ( talkState_t state ); + void SetPassivePrefix ( const char* prefix ); + +protected: + + bool GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ); + + /* + =============================================================================== + Combat + =============================================================================== + */ + +public: + + // enemy managment + bool SetEnemy ( idEntity *newEnemy ); + void ClearEnemy ( bool dead = false ); + + void UpdateEnemy ( void ); + void UpdateEnemyPosition ( bool force = true ); + void UpdateEnemyVisibility ( void ); + + // Attack direction + bool GetAimDir ( const idVec3& source, const idEntity* aimAtEnt, const idDict* projectileDict, idEntity *ignore, idVec3 &aimDir, float aimOffset, float predict ) const; + void GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const; + + // damage + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + bool CheckDeathCausesMissionFailure ( void ); + + // attacks + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ); + virtual bool AttackMelee ( const char* attackName, const idDict* meleeDict ); + + void CreateProjectileClipModel ( void ) const; + idProjectile* CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ); + void RemoveProjectile ( void ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + void DirectDamage ( const char *meleeDefName, idEntity *ent ); + bool TestMelee ( void ) const; + void PushWithAF ( void ); + bool IsMeleeNeeded ( void ); + + // special effects + void GetMuzzle ( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ); + void LerpLookAngles ( idAngles &curAngles, idAngles newAngles, float orientationJointYaw, float focusRate ); + virtual bool UpdateAnimationControllers ( void ); + + // AI script state management + void UpdateStates ( void ); + void UpdateFocus ( const idMat3& orientationAxis ); + void SetFocus ( aiFocus_t focus, int time ); + + // event? + virtual void FootStep ( void ); + + void CreateMissile ( jointHandle_t joint ); + void RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void BecomeSolid ( void ); + void BecomeNonSolid ( void ); + const char * ChooseAnim ( int channel, const char *animname ); + + // + // ai/ai_events.cpp + // +public: + + + virtual bool CanTakeDamage ( void ) const; + virtual bool CanTakePain ( void ) const; + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual bool CanAnnounce ( float chance ) const; + + virtual bool SkipCurrentDestination ( void ) const; + +// ----------------------------- Functions ------------------------------------ + + virtual bool DoDormantTests ( void ); + + void OverrideFlag ( aiFlagOverride_t flag, bool value ); + void RestoreFlag ( aiFlagOverride_t flag ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual const char* GetIdleAnimName ( void ); + + bool RespondToFlashlight ( void ) { return !aifl.ignoreFlashlight;} + bool ForceFaceEnemy ( void ) { return ( move.moveCommand == MOVE_TO_ENEMY ); } + + bool CanBecomeSolid ( void ); + bool CanHitEnemyFromAnim ( int animNum, idVec3 offset = vec3_origin ); + bool CanHitEnemy ( void ); + bool CanHitEnemyFromJoint ( const char *jointname ); + + float GetTurnDelta ( void ); + + int TestTrajectory ( const idVec3 &firePos, const idVec3 &target, const char *projectileName ); + bool TestAnimMove ( int animNum, idEntity *ignore = NULL, idVec3 *pMoveVec = NULL ); + void ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm = NULL ); + void SetLeader ( idEntity *newLeader ); + + int CheckMelee ( bool disableAttack ); + bool CheckForEnemy ( bool useFov, bool force = false ); + bool CheckForCloserEnemy ( void ); + bool CheckForReplaceEnemy ( idEntity* replacement ); + bool CheckForTeammateEnemy ( void ); + + void DrawSuspicion ( void ); + float RateSuspiciousness ( idActor* shady, bool rateSound = false ); + void RateSuspicionLevel ( void ); + + void UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ); + + void LookAtEntity ( idEntity *ent, float duration ); + +// ----------------------------- Variables ------------------------------------ + + int actionAnimNum; // Index of animation to use for the upcoming action + int actionTime; // Time line for actions (time is stopped when an action is running) + int actionSkipTime; // Time to use if an action is skipped by another + + int flagOverrides; + + float announceRate; // How often (0 - 1.0f) the AI will make certain announcements. + + rvAICombat_t combat; // Members related to combat state + rvAIPassive_t passive; // Members related to passive state + rvAIEnemy_t enemy; // Members related to tracking enemies + rvAIPain_t pain; + rvAIFuncs_t funcs; + + rvPlaybackDriver mPlayback; + rvPlaybackDriver mLookPlayback; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr tether; + idEntityPtr helperCurrent; + idEntityPtr helperIdeal; + idEntityPtr leader; + idEntityPtr spawner; + + bool ValidateCover ( void ); + + virtual bool UpdateRunStatus ( void ); + bool UpdateTactical ( int delay = 0 ); + void ForceTacticalUpdate ( void ); + bool UpdateTactical_r ( void ); + virtual int FilterTactical ( int availableTactical ); + void WakeUpTargets ( void ); + + virtual aiCTResult_t CheckTactical ( aiTactical_t tactical ); + + void Begin ( void ); + void WakeUp ( void ); + + virtual void Prethink ( void ); + virtual void Postthink ( void ); + + /* + =============================================================================== + Threat Management + =============================================================================== + */ + +protected: + + virtual void UpdateThreat ( void ); + virtual float CalculateEnemyThreat ( idEntity* enemy ); + + /* + =============================================================================== + Tethers + =============================================================================== + */ + +public: + + virtual bool IsTethered ( void ) const; + bool IsWithinTether ( void ) const; + rvAITether* GetTether ( void ); + virtual void SetTether ( rvAITether* newTether ); + + /* + =============================================================================== + Scripting + =============================================================================== + */ + +public: + + void ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void ScriptedStop ( void ); + + void SetScript ( const char* scriptName, const char* funcName ); + +private: + + bool ScriptedBegin ( bool endWithIdle, bool allowDormant = false ); + void ScriptedEnd ( void ); + + /* + =============================================================================== + Handlers + =============================================================================== + */ + +protected: + + virtual void OnDeath ( void ); + virtual void OnStateChange ( int channel ); + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnLeaderChange ( idEntity* oldLeader ); + virtual void OnStartMoving ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnFriendlyFire ( idActor* attacker ); + virtual void OnWakeUp ( void ); + virtual void OnTouch ( idEntity *other, trace_t *trace ); + virtual void OnCoverInvalidated ( void ); + virtual void OnCoverNotFacingEnemy ( void ); + virtual void OnEnemyVisiblityChange ( bool oldVisible ); + virtual void OnStartAction ( void ); + virtual void OnStopAction ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + /* + =============================================================================== + Movement / Turning + =============================================================================== + */ + +protected: + + bool StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ); + void StopMove ( moveStatus_t status ); + + bool MoveTo ( const idVec3 &pos, float range = 0.0f ); + bool MoveToAttack ( idEntity *ent, int attack_anim ); + bool MoveToTether ( rvAITether* tether ); + virtual bool MoveToEnemy ( void ); + bool MoveToEntity ( idEntity *ent, float range = 0.0f ); + bool MoveToCover ( float minRange, float maxRange, aiTactical_t coverType ); + bool MoveToHide ( void ); + + bool MoveOutOfRange ( idEntity *entity, float range, float minRange=0.0f ); + + void AnimTurn ( float angles, bool force ); + + bool ReachedPos ( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range = 0.0f ) const; + + /* + =============================================================================== + Debug + =============================================================================== + */ + +public: + + void DrawRoute ( void ) const; + void DrawTactical ( void ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + +public: + + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + void AnnounceNewEnemy ( void ); + void AnnounceKill ( idActor *victim ); + void AnnounceTactical ( aiTactical_t newTactical ); + void AnnounceSuppressed ( idActor *suppressor ); + void AnnounceSuppressing ( void ); + void AnnounceFlinch ( idEntity *attacker ); + void AnnounceInjured ( void ); + void AnnounceFriendlyFire ( idActor* attacker ); + void AnnounceGrenade ( void ); + void AnnounceGrenadeThrow ( void ); + + /* + =============================================================================== + Actions + =============================================================================== + */ + +protected: + + rvAIActionTimer actionTimerRangedAttack; + rvAIActionTimer actionTimerEvade; + rvAIActionTimer actionTimerSpecialAttack; + rvAIActionTimer actionTimerPain; + + rvAIAction actionEvadeLeft; + rvAIAction actionEvadeRight; + rvAIAction actionRangedAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionLeapAttack; + rvAIAction actionJumpBack; + + bool UpdateAction ( void ); + virtual bool CheckActions ( void ); + virtual bool CheckPainActions ( void ); + + bool PerformAction ( rvAIAction* action, bool (idAI::*)(rvAIAction*,int), rvAIActionTimer* timer = NULL ); + void PerformAction ( const char* stateName, int blendFrames = 0, bool noPain = false ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + virtual void Event_PostSpawn ( void ); + // RAVEN END + +public: + + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + + /* + =============================================================================== + Events + =============================================================================== + */ + +private: + + // Orphaned events + void Event_ClosestReachableEnemyOfEntity ( idEntity *team_mate ); + void Event_GetReachableEntityPosition ( idEntity *ent ); + void Event_EntityInAttackCone ( idEntity *ent ); + void Event_TestAnimMoveTowardEnemy ( const char *animname ); + void Event_TestAnimMove ( const char *animname ); + void Event_TestMoveToPosition ( const idVec3 &position ); + void Event_TestMeleeAttack ( void ); + void Event_TestAnimAttack ( const char *animname ); + void Event_SaveMove ( void ); + void Event_RestoreMove ( void ); + void Event_ThrowMoveable ( void ); + void Event_ThrowAF ( void ); + void Event_PredictEnemyPos ( float time ); + void Event_FindActorsInBounds ( const idVec3 &mins, const idVec3 &maxs ); + + void Event_Activate ( idEntity *activator ); + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_LookAt ( idEntity* lookAt ); + + void Event_SetAngles ( idAngles const &ang ); + void Event_SetEnemy ( idEntity *ent ); + void Event_SetHealth ( float newHealth ); + void Event_SetTalkTarget ( idEntity *target ); + void Event_SetTalkState ( int state ); + void Event_SetLeader ( idEntity *newLeader ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetMoveSpeed ( int speed ); + void Event_SetPassivePrefix ( const char* prefix ); + + void Event_GetAngles ( void ); + void Event_GetEnemy ( void ); + void Event_GetLeader ( void ); + + void Event_Attack ( const char* attackName, const char* jointName ); + void Event_AttackMelee ( const char *meleeDefName ); + + void Event_DirectDamage ( idEntity *damageTarget, const char *damageDefName ); + void Event_RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void Event_CanBecomeSolid ( void ); + void Event_BecomeSolid ( void ); + void Event_BecomeNonSolid ( void ); + void Event_BecomeRagdoll ( void ); + void Event_StopRagdoll ( void ); + void Event_FaceEnemy ( void ); + void Event_FaceEntity ( idEntity *ent ); + void Event_WaitMove ( void ); + + void Event_BecomePassive ( int ignoreEnemies ); + void Event_BecomeAggressive ( void ); + + void Event_EnableDamage ( void ); + void Event_EnableClip ( void ); + void Event_EnableGravity ( void ); + void Event_EnableAFPush ( void ); + void Event_EnablePain ( void ); + void Event_DisableDamage ( void ); + void Event_DisableClip ( void ); + void Event_DisableGravity ( void ); + void Event_DisableAFPush ( void ); + void Event_DisablePain ( void ); + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + void Event_TakeDamage ( float takeDamage ); + void Event_SetUndying ( float setUndying ); + void Event_EnableAutoBlink ( void ); + void Event_DisableAutoBlink ( void ); + + void Event_LockEnemyOrigin ( void ); + + void Event_StopThinking ( void ); + void Event_JumpFrame ( void ); + void Event_RealKill ( void ); + void Event_Kill ( void ); + void Event_RemoveUpdateSpawner ( void ); + void Event_AllowHiddenMovement ( int enable ); + void Event_CanReachPosition ( const idVec3 &pos ); + void Event_CanReachEntity ( idEntity *ent ); + void Event_CanReachEnemy ( void ); + + void Event_IsSpeaking ( void ); + void Event_IsTethered ( void ); + void Event_IsWithinTether ( void ); + void Event_IsMoving ( void ); + + void Event_Speak ( const char *speechDecl ); + void Event_SpeakRandom ( const char *speechDecl ); + + void Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void Event_ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_ScriptedJumpDown ( float yaw ); + + void Event_FindEnemy ( float distSquare ); + void Event_SetKey ( const char *key, const char *value ); + + + /* + =============================================================================== + States + =============================================================================== + */ + +protected: + + // Wait states + stateResult_t State_Wait_Activated ( const stateParms_t& parms ); + stateResult_t State_Wait_ScriptedDone ( const stateParms_t& parms ); + stateResult_t State_Wait_Action ( const stateParms_t& parms ); + stateResult_t State_Wait_ActionNoPain ( const stateParms_t& parms ); + + // Global states + stateResult_t State_WakeUp ( const stateParms_t& parms ); + stateResult_t State_TriggerAnim ( const stateParms_t& parms ); + stateResult_t State_Wander ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + stateResult_t State_LightningDeath ( const stateParms_t& parms ); + stateResult_t State_Burn ( const stateParms_t& parms ); + stateResult_t State_Remove ( const stateParms_t& parms ); + + stateResult_t State_Passive ( const stateParms_t& parms ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_CombatCover ( const stateParms_t& parms ); + stateResult_t State_CombatMelee ( const stateParms_t& parms ); + stateResult_t State_CombatRanged ( const stateParms_t& parms ); + stateResult_t State_CombatTurret ( const stateParms_t& parms ); + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + + stateResult_t State_MovePlayerPush ( const stateParms_t& parms ); + stateResult_t State_MoveTether ( const stateParms_t& parms ); + stateResult_t State_MoveFollow ( const stateParms_t& parms ); + + stateResult_t State_ScriptedMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + stateResult_t State_ScriptedStop ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackAim ( const stateParms_t& parms ); + stateResult_t State_ScriptedJumpDown ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_Sight ( const stateParms_t& parms ); + stateResult_t State_Torso_CustomCycle ( const stateParms_t& parms ); + stateResult_t State_Torso_Action ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishAction ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ScriptedAnim ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveIdle ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveFidget ( const stateParms_t& parms ); + + // Leg States + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnLeft ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnRight ( const stateParms_t& parms ); + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_Legs_MoveThink ( const stateParms_t& parms ); + stateResult_t State_Legs_ChangeDirection ( const stateParms_t& parms ); + + // Head states + stateResult_t State_Head_Idle ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( idAI ); +}; + +/* +=============================================================================== + + idAI Inlines + +=============================================================================== +*/ + +ID_INLINE int DelayTime( int min, int range ) { + return min + gameLocal.random.RandomInt ( range + 1 ); +} + +ID_INLINE const idVec2& idAI::GetAttackRange ( void ) const { + return combat.attackRange; +} + +ID_INLINE const idVec2& idAI::GetFollowRange ( void ) const { + return move.followRange; +} + +ID_INLINE int idAI::GetTravelFlags ( void ) const { + return move.travelFlags; +} + +ID_INLINE bool idAI::IsEnemyVisible ( void ) const { + return enemy.ent && enemy.fl.visible; +} + +ID_INLINE bool idAI::IsEnemyRecentlyVisible( float maxLostVisTimeScale ) const { + return (enemy.ent + && combat.fl.seenEnemyDirectly + && (enemy.lastVisibleTime && gameLocal.time-enemy.lastVisibleTime < (combat.maxLostVisTime * maxLostVisTimeScale))); +} + +ID_INLINE bool idAI::LookAtCoverTall( void ) const { + return ( aasSensor->Look() + && (aasSensor->Look()->flags&FEATURE_LOOK_OVER) + && aasSensor->Look()->height > 40.0f ); +} + +ID_INLINE bool idAI::InLookAtCoverMode ( void ) const { + return (!IsBehindCover() + && !aifl.action + && move.fl.moving + && (combat.fl.alert || combat.fl.aware) + && aasSensor->Look() + && combat.tacticalCurrent != AITACTICAL_MELEE + && combat.tacticalCurrent != AITACTICAL_HIDE + && !IsEnemyRecentlyVisible(0.2f)); +} + +ID_INLINE bool idAI::InCoverMode ( void ) const { + return ( (1<Reserved ( ); +} + +ID_INLINE bool idAI::InCrouchCoverMode ( void ) const { + return ( InCoverMode() && (aasSensor->Reserved()->flags&FEATURE_LOOK_OVER) ); +} + +ID_INLINE bool idAI::IsBehindCover ( void ) const { + return ( InCoverMode() && move.fl.done && (aifl.action || DistanceTo2d ( aasSensor->ReservedOrigin() ) < AI_COVER_MINRANGE) ); +} + +ID_INLINE bool idAI::IsSpeaking ( void ) const { + return speakTime && gameLocal.time < speakTime; +} + +ID_INLINE bool idAI::IsFacingEnt ( idEntity* targetEnt ) { + return( move.moveCommand == MOVE_FACE_ENTITY && move.goalEntity == targetEnt && FacingIdeal() ); +} + +ID_INLINE bool idAI::IsCoverValid ( ) const { + return combat.coverValidTime && (gameLocal.time - combat.coverValidTime < combat.maxInvalidCoverTime); +} + +ID_INLINE idActor* idAI::GetLeader ( void ) const { + return leader; +} + +ID_INLINE idEntity* idAI::GetGoalEntity ( void ) const { + return move.goalEntity; +} + +ID_INLINE bool idAI::CanTakeDamage( void ) const { + return idActor::CanTakeDamage( ); +} + +ID_INLINE bool idAI::CanTakePain ( void ) const { + return !disablePain; +} + +ID_INLINE bool idAI::CanTurn ( void ) const { + return move.turnRate && !move.fl.noTurn && !move.fl.disabled; +} + +ID_INLINE bool idAI::CanMove ( void ) const { + return !move.fl.disabled && !move.fl.blocked && gameLocal.GetTime()>move.blockTime; +} + +ID_INLINE bool idAI::CanAnnounce ( float chance ) const { + return !aifl.dead && !IsSpeaking ( ) && !af.IsActive ( ) && !combat.fl.noChatter && ( gameLocal.random.RandomFloat() < chance ); +} + +ID_INLINE void idAI::SetFocus ( aiFocus_t focus, int time ) { + focusType = focus; + focusTime = gameLocal.time + time; +} + +ID_INLINE idEntity *idAI::GetEnemy( void ) const { + return enemy.ent; +} + +ID_INLINE void idAI::ForceTacticalUpdate ( void ) { + combat.tacticalUpdateTime = 0; + combat.tacticalMaskUpdate = 0; + delete aasFind; + aasFind = NULL; +} + +/* +=============================================================================== + + externs + +=============================================================================== +*/ + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; +extern const char* aiTalkMessageString [ ]; +extern const char* aiTacticalString [ AITACTICAL_MAX ]; +extern const char* aiMoveCommandString [ NUM_MOVE_COMMANDS ]; +extern const char* aiFocusString [ AIFOCUS_MAX ]; + +#endif /* !__AI_H__ */ + diff --git a/src/game/ai/AI_Actions.cpp b/src/game/ai/AI_Actions.cpp new file mode 100644 index 0000000..981f9b1 --- /dev/null +++ b/src/game/ai/AI_Actions.cpp @@ -0,0 +1,615 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + + rvAIActionTimer + +=============================================================================== +*/ + +/* +================ +rvAIActionTimer::rvAIActionTimer +================ +*/ +rvAIActionTimer::rvAIActionTimer ( void ) { + time = 0; +} + +/* +================ +rvAIActionTimer::Init +================ +*/ +bool rvAIActionTimer::Init ( const idDict& args, const char* name ) { + rate = SEC2MS ( args.GetFloat ( va("%s_rate",name), "0" ) ); + return true; +} + +/* +================ +rvAIActionTimer::Save +================ +*/ +void rvAIActionTimer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( rate ); + savefile->WriteInt ( time ); +} + +/* +================ +rvAIActionTimer::Restore +================ +*/ +void rvAIActionTimer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( rate ); + savefile->ReadInt ( time ); +} + +/* +================ +rvAIActionTimer::Reset +================ +*/ +void rvAIActionTimer::Reset ( int currentTime, float diversity, float scale ) { + float _rate = rate * scale; + time = currentTime + (-gameLocal.random.RandomInt( 2.0f * _rate * diversity ) + _rate * diversity + _rate); +} + +/* +================ +rvAIActionTimer::Clear +================ +*/ +void rvAIActionTimer::Clear ( int currentTime ) { + time = currentTime; +} + +/* +================ +rvAIActionTimer::Add +================ +*/ +void rvAIActionTimer::Add ( int _time, float diversity ) { + time += (-gameLocal.random.RandomInt( 2.0f * _time * diversity ) + _time * diversity + _time); +} + +/* +=============================================================================== + + rvAIAction + +=============================================================================== +*/ + +/* +================ +rvAIAction::rvAIAction +================ +*/ +rvAIAction::rvAIAction ( void ) { + memset ( &fl, 0, sizeof(fl) ); + status = STATUS_UNUSED; +} + +/* +================ +rvAIAction::Init +================ +*/ +bool rvAIAction::Init ( const idDict& args, const char* name, const char* defaultState, int _flags ) { + const idKeyValue* kv; + + if ( _flags & AIACTIONF_ATTACK ) { + fl.isAttack = true; + } + if ( _flags & AIACTIONF_MELEE ) { + fl.isMelee = true; + } + + // Initialize timer + timer.Init ( args, name ); + + // Is this action enabled? + fl.disabled = !args.GetBool ( va("%s",name), "0" ); + fl.noPain = args.GetBool ( va("%s_nopain",name), "0" ); + fl.noTurn = args.GetBool ( va("%s_noturn",name), "1" ); + fl.overrideLegs = args.GetBool ( va("%s_overrideLegs",name), "1" ); + fl.noSimpleThink = args.GetBool ( va("%s_nosimplethink",name), "0" ); + + blendFrames = args.GetInt ( va("%s_blendFrames",name), "4" ); + failRate = SEC2MS ( args.GetInt ( va("%s_failRate",name), ".1" ) ); + + minRange = args.GetInt ( va("%s_minRange",name), "0" ); + maxRange = args.GetInt ( va("%s_maxRange",name), (_flags & AIACTIONF_ATTACK ) ? "-1" : "0" ); + minRange2d = args.GetInt ( va("%s_minRange2d",name), "0" ); + maxRange2d = args.GetInt ( va("%s_maxRange2d",name), "0" ); + + chance = args.GetFloat ( va("%s_chance",name), "1" ); + diversity = args.GetFloat ( va("%s_diversity", name ), ".5" ); + + // action state + state = args.GetString ( va("%s_state",name), (!defaultState||!*defaultState) ? "Torso_Action" : defaultState ); + + // allow for multiple animations + const char* prefix = va("%s_anim",name); + for ( kv = args.MatchPrefix ( prefix, NULL ); kv; kv = args.MatchPrefix ( prefix, kv ) ) { + if ( kv->GetValue ( ).Length ( ) ) { + anims.Append ( kv->GetValue ( ) ); + } + } + + return true; +} + +/* +================ +rvAIAction::Save +================ +*/ +void rvAIAction::Save ( idSaveGame *savefile ) const { + int i; + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteInt ( anims.Num ( ) ); + for ( i = 0; i < anims.Num(); i ++ ) { + savefile->WriteString ( anims[i] ); + } + savefile->WriteString ( state ); + + timer.Save ( savefile ); + + savefile->WriteInt ( blendFrames ); + savefile->WriteInt ( failRate ); + + savefile->WriteFloat ( minRange ); + savefile->WriteFloat ( maxRange ); + savefile->WriteFloat ( minRange2d ); + savefile->WriteFloat ( maxRange2d ); + + savefile->WriteFloat ( chance ); + savefile->WriteFloat ( diversity ); + + savefile->WriteInt ( (int)status ); +} + +/* +================ +rvAIAction::Restore +================ +*/ +void rvAIAction::Restore ( idRestoreGame *savefile ) { + int num; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadInt ( num ); + anims.Clear ( ); + anims.SetNum ( num ); + for ( num--; num >= 0; num -- ) { + savefile->ReadString ( anims[num] ); + } + savefile->ReadString ( state ); + + timer.Restore ( savefile ); + + savefile->ReadInt ( blendFrames ); + savefile->ReadInt ( failRate ); + + savefile->ReadFloat ( minRange ); + savefile->ReadFloat ( maxRange ); + savefile->ReadFloat ( minRange2d ); + savefile->ReadFloat ( maxRange2d ); + + savefile->ReadFloat ( chance ); + savefile->ReadFloat ( diversity ); + + savefile->ReadInt ( (int&)status ); +} + +/* +=============================================================================== + + Actions + +=============================================================================== +*/ + +/* +================ +idAI::CheckAction_EvadeLeft +================ +*/ +bool idAI::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + // TODO: dont evade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_EvadeRight +================ +*/ +bool idAI::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_JumpBack +================ +*/ +bool idAI::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage ) { + return false; + } + // TODO: enemy must be in front to jump backwards + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_RangedAttack +================ +*/ +bool idAI::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_MeleeAttack +================ +*/ +bool idAI::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_LeapAttack +================ +*/ +bool idAI::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( enemy.range > 64.0f && !TestAnimMove ( animNum, enemy.ent ) ) { + return false; + } + // Must be looking right at the enemy to leap + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 4 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::UpdateAction +================ +*/ +bool idAI::UpdateAction ( void ) { + // Update action MUST be called from the main state loop + assert ( stateThread.IsExecuting ( ) ); + + // If an action is already running then dont let another start + if ( aifl.action ) { + return false; + } + + return CheckActions ( ); +} + +/* +================ +idAI::CheckPainActions +================ +*/ +bool idAI::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + return false; + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +idAI::CheckActions +================ +*/ +bool idAI::CheckActions ( void ) { + + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // Actions are limited at a cover position to shooting and leaning + if ( IsBehindCover ( ) ) { + // Test ranged attack first + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +idAI::PerformAction +================ +*/ +void idAI::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + if ( legsAnim.Disabled() ) { + //MCG: Hmmm... I hope this doesn't break anything, but if an action happens *right* + // at the end of a trigger_anim, then the legs will be enabled (by the SetAnimState + // on the torso) with no state! The actor will then be stuck in place until + // something actually sets the legsAnim state... so let's check for disabled and + // set a default state right here...? + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + } + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + + // Main state will wait until action is finished before continuing + if ( noPain ) { + InterruptState ( "Wait_ActionNoPain" ); + } else { + InterruptState ( "Wait_Action" ); + } + + OnStartAction ( ); +} + +bool idAI::PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + // If we arent ignoring simple think then dont perform this action on a simple think frame + if ( !action->fl.noSimpleThink && aifl.simpleThink ) { + return false; + } + + // Is the action disabled? + if ( action->fl.disabled ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + + // Action timers still running? + if ( !action->timer.IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_TIMER; + return false; + } + + if ( timer && !timer->IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_EXTERNALTIMER; + return false; + } + + // Special code for attacks + if ( action->fl.isAttack ) { + // Attacks disabled? + if ( ai_disableAttacks.GetBool() ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + // No attack actions if we have no enemy or our enemy cant be hurt + if ( !enemy.ent || enemy.ent->health <= 0 ) { + action->status = rvAIAction::STATUS_FAIL_NOENEMY; + return false; + } + } + + // Min Range check + if ( action->minRange ) { + if ( !enemy.ent || !enemy.range || enemy.range < action->minRange ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + if ( action->minRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d < action->minRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + + // Max Range check + if ( action->maxRange != 0 ) { + float maxrange = action->maxRange == -1 ? combat.attackRange[1] : action->maxRange; + if ( !enemy.ent || !enemy.range || enemy.range > maxrange ) { + if ( action->fl.isMelee && GetEnemy() ) { + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = GetEnemy()->GetPhysics()->GetOrigin(); + idBounds enemyBounds = GetEnemy()->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } else { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + } + if ( action->maxRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d > action->maxRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + + int animNum; + if ( action->anims.Num ( ) ) { + // Pick a random animation from the list + animNum = GetAnim ( ANIMCHANNEL_TORSO, action->anims[gameLocal.random.RandomInt(action->anims.Num())] ); + if ( !animNum ) { + action->status = rvAIAction::STATUS_FAIL_ANIM; + return false; + } + } else { + animNum = -1; + } + + // Random chance? + if ( action->chance < 1.0f && gameLocal.random.RandomFloat ( ) > action->chance ) { + action->status = rvAIAction::STATUS_FAIL_CHANCE; + action->timer.Clear ( actionTime ); + action->timer.Add ( 100 ); + return false; + } + + // Check the condition + if ( condition && !(this->*(condition)) ( action, animNum ) ) { + action->status = rvAIAction::STATUS_FAIL_CONDITION; + action->timer.Clear ( actionTime ); + action->timer.Add ( action->failRate ); + return false; + } + + // Disallow turning during action? + if ( action->fl.noTurn ) { + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + } + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + // Override legs for this state? + if ( action->fl.overrideLegs ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + + // When attacking scale the time by the aggression scale + float scale; + if ( action->fl.isAttack ) { + scale = 2.0f - combat.aggressiveScale; + } else { + scale = 1.0f; + } + + // Restart the action timer using the length of the animation being played + action->timer.Reset ( actionTime, action->diversity, scale ); + + // If the action gets interrupted it will be skipped, if it is then move the action timers forward + // by half of its normal delay to allow it to be performed again quicker than usual. This also allows + // other actions that may be still pending to be performed and thus cause less of a pause after taking pain. + actionSkipTime = (action->timer.GetTime ( ) + actionTime) / 2; + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + + action->status = rvAIAction::STATUS_OK; + + actionAnimNum = animNum; + + return true; +} diff --git a/src/game/ai/AI_Announcements.cpp b/src/game/ai/AI_Announcements.cpp new file mode 100644 index 0000000..24baca4 --- /dev/null +++ b/src/game/ai/AI_Announcements.cpp @@ -0,0 +1,578 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Announcements.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +/* +===================== +idAI::AnnounceNewEnemy + +Announce that we are going to shooting at a different enemy +===================== +*/ +void idAI::AnnounceNewEnemy( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + if ( !enemy.ent || !enemy.ent->IsType( idActor::GetClassType() ) ) { + return; + } + + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY ) ) { + return; + } + + // Check to see if we should announce the new enemy as a sniper + // jshepard: Disabled by request + /* + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER ) && enemy.ent->spawnArgs.GetBool ( "sniper" ) ) { + // Should we announce the sniper as a high up sniper? + if ( (enemy.lastKnownPosition - physicsObj.GetOrigin()) * physicsObj.GetGravityNormal() >= 250.0f ) { + if ( Speak ( "lipsync_high_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + return; + } + + // Just announce the sniper + if ( Speak ( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + */ + + idActor* newEnemyAct = static_cast(enemy.ent.GetEntity()); + bool result = false; + + //first see if the new enemy is behind our buddy closest to the enemy + idActor* teammate = aiManager.NearestTeammateToPoint( this, newEnemyAct->GetPhysics()->GetOrigin(), false, 200.0f ); + if ( teammate ) { + if ( aiManager.ActorIsBehindActor( newEnemyAct, teammate ) ) { + result = Speak ( "lipsync_enemy_back", true ); + } + } + + // If we havent spoken yet then find a teammat near us that we can tell about our enemy and + // determine the best announcement to make based on their location + if ( !result ) { + teammate = aiManager.NearestTeammateToPoint( this, GetPhysics()->GetOrigin(), false, 300 ); + if ( teammate ) { + if ( newEnemyAct->GetPhysics()->GetOrigin().z-GetPhysics()->GetOrigin().z >= 250.0f ) { + result = Speak ( "lipsync_enemy_high", true ); + } + + // IF we still havent spoken and we're talking to the player, give him directional info + if ( !result && teammate->IsType( idPlayer::GetClassType() ) ) { + idPlayer* teamPlayer = static_cast(teammate); + idVec3 dir = newEnemyAct->GetPhysics()->GetOrigin()-teamPlayer->GetPhysics()->GetOrigin(); + dir.z = 0; + dir.NormalizeFast(); + idVec3 fwd = teamPlayer->viewAxis[0]; + fwd.z = 0.0f; + fwd.NormalizeFast(); + idVec3 lt = teamPlayer->viewAxis[1]; + lt.z = 0.0f; + lt.NormalizeFast(); + if ( fabs( dir * fwd ) < 0.4f ) { + // more to the side than the front + if ( dir * lt > 0 ) { + result = Speak ( "lipsync_enemy_left", true ); + } else { + result = Speak ( "lipsync_enemy_right", true ); + } + } + } + + // If we still havent spoken, just announce the enemy + if ( !result ) { + //FIXME: check right & left? + //FIXME: check for surrounded? + result = Speak ( "lipsync_enemy_default", true ); + } + } + } + + // If we spoke then set the timer + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY, 2000 ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announce that we have killed our enemy +===================== +*/ +void idAI::AnnounceKill( idActor* victim ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // If already speaking or one the same team, dont announce the kill + if ( victim->team == team ) { + return; + } + //jshepard: "Watch It" sounds have been cut. Replaced with Victory. + + // Generic annoucement of enemy's death + Speak( "lipsync_victory", true ); + + + return; + +/* + // If the victim is targetting the player and was close to the player when he died + // then announce to the player to be careful + if ( victim->IsType( idAI::Type ) ) { + idAI* vicAI = static_cast(victim); + if ( vicAI && vicAI->GetEnemy() && vicAI->GetEnemy()->IsType ( idPlayer::GetClassType() ) ) { + idPlayer* vicEnemyPlayer = static_cast(vicAI->GetEnemy()); + if ( vicEnemyPlayer->team == team ) { + idVec3 diff = vicAI->GetPhysics()->GetOrigin() - vicEnemyPlayer->GetPhysics()->GetOrigin(); + if ( !vicEnemyPlayer->CheckFOV ( vicAI->GetPhysics()->GetOrigin() ) || diff.LengthSqr() < 300.0f * 300.0f ) { + Speak( "lipsync_watchit", true ); + return; + } + } + } + } + + // Chance that we say "watch it" to the closest teammate to him if the guy that died was facing the teammate + // when he died (ie, was a possible threat) + idActor* teammate = aiManager.NearestTeammateToPoint( this, victim->GetPhysics()->GetOrigin(), false, 300.0f ); + if ( teammate && victim->CheckFOV(teammate->GetPhysics()->GetOrigin()) ) { + if ( gameLocal.random.RandomInt(2) < 1 ) { + Speak( "lipsync_watchit", true ); + return; + } + } +*/ + +} + +/* +===================== +idAI::AnnounceTactical + +Announce the changing of tactical status +===================== +*/ +void idAI::AnnounceTactical( aiTactical_t newTactical ) { + bool result = false; + + // If already speaking dont bother + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL ) ) { + return; + } + + switch ( newTactical ) { + case AITACTICAL_MELEE: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_rush", true ); + } + break; +/* + case AITACTICAL_COVER: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_cover", true ); + } + break; + case AITACTICAL_COVER_FLANK: + result = Speak( "lipsync_flank", true ); + break; + case AITACTICAL_COVER_ADVANCE: + result = Speak( "lipsync_moveup", true ); + break; + case AITACTICAL_COVER_RETREAT: + result = Speak( "lipsync_fallback", true ); + break; + case AITACTICAL_COVER_AMBUSH: + break; + case AITACTICAL_RANGED: + break; + case AITACTICAL_TURRET: + break; + case AITACTICAL_HIDE: + result = Speak( "lipsync_cover", true ); + break; +*/ + } + + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL, 2000 ); + } +} + +/* +===================== +idAI::AnnounceSuppressed + +Announce that someone is using supressing fire on us +===================== +*/ +void idAI::AnnounceSuppressed( idActor *suppressor ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if we are already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED ) ) { + return; + } + + //FIXME: check crossfire? + if ( Speak( "lipsync_supressed", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED, 3000 ); + } +*/ + +} + +/* +===================== +idAI::AnnounceSuppressing + +Announce that we are about to use supressing fire +===================== +*/ +void idAI::AnnounceSuppressing( void ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING ) ) { + return; + } + + //Make the guy being shot at know this + if ( enemy.ent->IsType( idAI::Type ) ) { + idAI* enemyAI = dynamic_cast(enemy.ent.GetEntity()); + if ( enemyAI ) { + enemyAI->AnnounceSuppressed( this ); + } + } + + if ( Speak( "lipsync_supressing", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING, 5000 ); + } +*/ +} + +/* +===================== +idAI::AnnounceFlinch + +Announce that an attack just missed us +===================== +*/ +void idAI::AnnounceFlinch( idEntity *attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + idActor* attackActor = dynamic_cast(attacker); + + // Friendly fire? + if ( attackActor && attackActor->team == team ) { + if ( gameLocal.random.RandomFloat() < 0.2f ) { + AnnounceFriendlyFire( attackActor ); + } + } +//jshepard: sniper announcement removed by request +/* + else if ( attackActor->spawnArgs.GetBool ( "sniper" ) ) { + //TEMP: static debounce timer + static int lastPlayed2 = 0; + if ( gameLocal.time - lastPlayed2 < 10000 ) { + return; + } + lastPlayed2 = gameLocal.time; + Speak( "lipsync_sniper", true ); + } else { +*/ + else { + if ( gameLocal.random.RandomFloat() < 0.4f ) { + return; + } + //TEMP: static debounce timer + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + Speak( "lipsync_closeone", true ); + } +} + +/* +===================== +idAI::AnnounceInjured + +Announce that we have been injured +===================== +*/ +void idAI::AnnounceInjured( void ) { + if ( !CanAnnounce ( 1.0f ) ) { + return; + } + + Speak( "lipsync_needhelp", true ); +} + +/* +===================== +idAI::AnnounceFriendlyFire + +Announce that someone on our own team is shooting us +===================== +*/ +void idAI::AnnounceFriendlyFire( idActor* attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Early outs + if ( health <= 0 || attacker == this ) { + return; + } + + // Don't react to ff from other buddy AI + if ( !attacker->IsType( idPlayer::GetClassType() ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE ) ) { + return; + } + + // Must be on same team for friendly fire + if ( team != attacker->team ) { + return; + } + + // Must be close enough to hear it + if ( DistanceTo ( attacker ) > 300.0f ) { + return; + } + + //FIXME: escalate? + if ( Speak( "lipsync_checkfire", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, 1000 ); + } +} + +/* +===================== +idAI::AnnounceGrenade +===================== +*/ +void idAI::AnnounceGrenade( void ) { + if ( !CanAnnounce ( 1 ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_grenade", true ); +} + +/* +===================== +idAI::AnnounceGrenadeThrow + +Announce that we are throwing a grenade +===================== +*/ +void idAI::AnnounceGrenadeThrow( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 1000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_throw_grenade", true ); +} + + +/* +===================== +rvAIManager::AnnounceDeath + +Announce through an ally of the victem that they have died +===================== +*/ +void rvAIManager::AnnounceDeath( idAI* victim, idEntity* attacker ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire kill? + //MCG NOTE: This isn't even possible anymore... + if ( attacker->IsType ( idPlayer::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + teammate = NearestTeammateToPoint( static_cast(attacker), attacker->GetPhysics()->GetOrigin(), true, 500.0f ); + teammateAI = dynamic_cast(teammate); + + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + teammateAI->Speak( "lipsync_traitor", true ); + return; + } + } + + teammate = NearestTeammateToPoint( victim, victim->GetPhysics()->GetOrigin(), true, 1000.0f ); + teammateAI = dynamic_cast(teammate); + + //jshepard: double check to make sure we don't call out our own death! + if( teammateAI == victim ) { + //MCG: note - NearestTeammateToPoint should *never* allow this, should never happen + assert(0); + return; + } + + // Early out if we dont have a teammate or our teammate cant talk + if ( !attacker || !teammateAI || !teammateAI->CanAnnounce ( teammateAI->announceRate ) ) { + return; + } + + // Announce sniper? + // jshepard: sniper announcement removed by request +/* + if ( attacker->spawnArgs.GetBool ( "sniper" ) ) { + if ( !aiManager.CheckTeamTimer( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER ) ) { + if ( teammateAI->Speak( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + } +*/ + // Annoucne specific death or just a generic death + const char* shortName; + if ( !victim->spawnArgs.GetString ( "npc_shortname", "", &shortName ) || !*shortName || + !teammateAI->Speak ( va("lipsync_%s_killed", shortName ), true ) ) { + teammateAI->Speak( "lipsync_mandown", true ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announces an ai being killed +===================== +*/ +void rvAIManager::AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire deaths are handled elsewhere + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + return; + } + + // If it was an AI guy that did the killing then just let him announce it + if ( attacker->IsType( idAI::Type ) ) { + //announce the kill + static_cast(attacker)->AnnounceKill( victim ); + } else if ( attacker->IsType( idPlayer::GetClassType() ) ) { + idPlayer* attackerPlayer = static_cast(attacker); + + // If the guy who died is an AI guy who was targetting a buddy nearby, have him say "thanks!" + // jshepard: these are cut unless we can get some tighter "thanks" quotes +/* if ( victim->IsType( idAI::Type ) ) { + idAI* victimAI = static_cast(victim); + //if the victim's enemy is a teammate of mine, make the teammate say "thanks!" + if ( victimAI && victimAI->GetEnemy() ) { + idAI* victimEnemyAI = dynamic_cast(victimAI->GetEnemy()); + + // See if the enemy of the guy who died is a teammate and wants to say thanks + if ( victimEnemyAI && victimEnemyAI->CanAnnounce ( ) && victimEnemyAI->team == attackerPlayer->team ) { + float distSqr = (victimAI->GetPhysics()->GetOrigin() - victimEnemyAI->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr < Square ( 300.0f ) ) { + //teammate was fighting him or close to him + victimEnemyAI->Speak( "lipsync_thanks", true ); + return; + } + } + } + } */ + + // Grab a nearby teammate of the player and say "nice shot!" + teammate = NearestTeammateToPoint( attackerPlayer, attacker->GetPhysics()->GetOrigin(), true, 500.0f, true ); + teammateAI = dynamic_cast(teammate); + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + idProjectile* proj = dynamic_cast(inflictor); + //killed them with a grenade? + if ( proj && proj->spawnArgs.GetBool( "thrown" ) ) { + teammateAI->Speak( "lipsync_nicetoss", true ); + // Or just shot them? + } else { + teammateAI->Speak( "lipsync_niceshot", true ); + } + return; + } + } +} + diff --git a/src/game/ai/AI_Debug.cpp b/src/game/ai/AI_Debug.cpp new file mode 100644 index 0000000..2a89325 --- /dev/null +++ b/src/game/ai/AI_Debug.cpp @@ -0,0 +1,354 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Debug.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" + +const char *aiMoveCommandString[ NUM_MOVE_COMMANDS ] = { + "MOVE_NONE", + "MOVE_FACE_ENEMY", + "MOVE_FACE_ENTITY", + "MOVE_TO_ENEMY", + "MOVE_TO_ENTITY", + "MOVE_TO_ATTACK", + "MOVE_TO_HELPER", + "MOVE_TO_TETHER", + "MOVE_TO_COVER", + "MOVE_TO_HIDE", + "MOVE_TO_POSITION", + "MOVE_OUT_OF_RANGE", + "MOVE_SLIDE_TO_POSITION", + "MOVE_WANDER" +}; + +const char* aiMoveStatusString[ NUM_MOVE_STATUS ] = { + "MOVE_STATUS_DONE", + "MOVE_STATUS_MOVING", + "MOVE_STATUS_WAITING", + "MOVE_STATUS_DEST_NOT_FOUND", + "MOVE_STATUS_DEST_UNREACHABLE", + "MOVE_STATUS_BLOCKED_BY_WALL", + "MOVE_STATUS_BLOCKED_BY_OBJECT", + "MOVE_STATUS_BLOCKED_BY_ENEMY", + "MOVE_STATUS_BLOCKED_BY_MONSTER", + "MOVE_STATUS_BLOCKED_BY_PLAYER", + "MOVE_STATUS_DISABLED" +}; + +const char* aiMoveDirectionString [ MOVEDIR_MAX ] = { + "MOVEDIR_FORWARD", + "MOVEDIR_BACKWARD", + "MOVEDIR_LEFT", + "MOVEDIR_RIGHT" +}; + +const char* aiTacticalString [ AITACTICAL_MAX ] = { + "AITACTICAL_NONE", + "AITACTICAL_MELEE", + "AITACTICAL_MOVE_FOLLOW", + "AITACTICAL_MOVE_TETHER", + "AITACTICAL_MOVE_PLAYERPUSH", + "AITACTICAL_COVER", + "AITACTICAL_COVER_FLANK", + "AITACTICAL_COVER_ADVANCE", + "AITACTICAL_COVER_RETREAT", + "AITACTICAL_COVER_AMBUSH", + "AITACTICAL_RANGED", + "AITACTICAL_TURRET", + "AITACTICAL_HIDE", + "AITACTICAL_PASSIVE", +}; + +const char* aiFocusString [ AIFOCUS_MAX ] = { + "AIFOCUS_NONE", + "AIFOCUS_LEADER", + "AIFOCUS_TARGET", + "AIFOCUS_TALK", + "AIFOCUS_PLAYER", + "AIFOCUS_ENEMY", + "AIFOCUS_COVER", + "AIFOCUS_COVERLOOK", + "AIFOCUS_HELPER", + "AIFOCUS_TETHER", +}; + +const char* aiActionStatusString [ rvAIAction::STATUS_MAX ] = { + "Unused", + "OK", + "Disabled", + "Failed: timer", + "Failed: external timer", + "Failed: within min range", + "Failed: out of max range", + "Failed: random chance", + "Failed: bad animation", + "Failed: condition", + "Failed: no enemy", +}; + +/* +===================== +idAI::GetDebugInfo +===================== +*/ +void idAI::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idActor::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "aifl.damage", aifl.damage ? "true" : "false", userData ); + proc ( "idAI", "aifl.undying", aifl.undying ? "true" : "false", userData ); + proc ( "idAI", "aifl.pain", aifl.pain ? "true" : "false", userData ); + proc ( "idAI", "aifl.dead", aifl.dead ? "true" : "false", userData ); + proc ( "idAI", "aifl.activated", aifl.activated ? "true" : "false", userData ); + proc ( "idAI", "aifl.jump", aifl.jump ? "true" : "false", userData ); + proc ( "idAI", "aifl.hitEnemy", aifl.hitEnemy ? "true" : "false", userData ); + proc ( "idAI", "aifl.pushed", aifl.pushed ? "true" : "false", userData ); + proc ( "idAI", "aifl.lookAtPlayer", aifl.lookAtPlayer ? "true" : "false", userData ); + proc ( "idAI", "aifl.disableAttacks", aifl.disableAttacks ? "true" : "false", userData ); + proc ( "idAI", "aifl.simpleThink", aifl.simpleThink ? "true" : "false", userData ); + proc ( "idAI", "aifl.action", aifl.action ? "true" : "false", userData ); + proc ( "idAI", "aifl.scripted", aifl.scripted? "true" : "false", userData ); + + proc ( "idAI", "leader", leader.GetEntity() ? leader.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "move.followRangeMin", va("%g",move.followRange[0]), userData ); + proc ( "idAI", "move.followRangeMax", va("%g",move.followRange[1]), userData ); + proc ( "idAI", "combat.fl.aware", combat.fl.aware ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.ignoreEnemies", combat.fl.ignoreEnemies ? "true" : "false", userData ); + + proc ( "idAI", "enemy", enemy.ent ? enemy.ent->GetName() : "", userData ); + proc ( "idAI", "enemy.fl.inFov", enemy.fl.inFov ? "true" : "false", userData ); + proc ( "idAI", "enemy.fl.visible", enemy.fl.visible ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.seenEnemyDirectly",combat.fl.seenEnemyDirectly ? "true" : "false", userData ); + proc ( "idAI", "enemy.lastVisibleTime", va("%d",enemy.lastVisibleTime), userData ); + proc ( "idAI", "combat.maxLostVisTime", va("%d",combat.maxLostVisTime), userData ); + proc ( "idAI", "enemy.range", va("%g",enemy.range), userData ); + proc ( "idAI", "enemy.ranged2d", va("%g",enemy.range2d), userData ); + proc ( "idAI", "lastAttackTime", va("%d",lastAttackTime), userData ); + + proc ( "idAI", "move.fl.done", move.fl.done ? "true" : "false", userData ); + proc ( "idAI", "move.fl.disabled", move.fl.disabled ? "true" : "false", userData ); + proc ( "idAI", "move.fl.onGround", move.fl.onGround ? "true" : "false", userData ); + proc ( "idAI", "move.fl.blocked", move.fl.blocked ? "true" : "false", userData ); + proc ( "idAI", "move.fl.obstacleInPath", move.fl.obstacleInPath ? "true" : "false", userData ); + proc ( "idAI", "move.fl.goalUnreachable", move.fl.goalUnreachable ? "true" : "false", userData ); + proc ( "idAI", "move.fl.moving", move.fl.moving ? "true" : "false", userData ); + proc ( "idAI", "move.command", aiMoveCommandString[move.moveCommand], userData ); + proc ( "idAI", "move.status", aiMoveStatusString[move.moveStatus], userData ); + proc ( "idAI", "move.fl.allowDirectional", move.fl.allowDirectional ? "true" : "false", userData ); + proc ( "idAI", "move.direction_ideal", aiMoveDirectionString[move.idealDirection ], userData ); + proc ( "idAI", "move.direction_current", aiMoveDirectionString[move.currentDirection ], userData ); + proc ( "idAI", "move.yaw_ideal", va("%d",(int)move.ideal_yaw), userData ); + proc ( "idAI", "move.yaw_current", va("%d",(int)move.current_yaw), userData ); + proc ( "idAI", "move.fly_roll", va("%g", move.fly_roll ), userData ); + proc ( "idAI", "move.fly_pitch", va("%g", move.fly_pitch ), userData ); + + proc ( "idAI", "tether", tether!=NULL?tether->GetName():"", userData ); + proc ( "idAI", "IsTethered()", IsTethered()?"true":"false", userData ); + + proc ( "idAI", "lookTarget", lookTarget.GetEntity() ? lookTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "talkTarget", talkTarget.GetEntity() ? talkTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "focusType", aiFocusString[focusType], userData ); + proc ( "idAI", "look.yaw", va("%g", lookAng.yaw ), userData ); + proc ( "idAI", "look.pitch", va("%g", lookAng.pitch ), userData ); + + proc ( "idAI", "combat.attackRangeMin", va("%g",combat.attackRange[0]), userData ); + proc ( "idAI", "combat.attackRangeMax", va("%g",combat.attackRange[1]), userData ); + proc ( "idAI", "combat.tacticalCurrent", aiTacticalString[combat.tacticalCurrent], userData ); + + proc ( "idAI", "action_rangedAttack", aiActionStatusString[actionRangedAttack.status], userData ); + proc ( "idAI", "action_meleeAttack", aiActionStatusString[actionMeleeAttack.status], userData ); + proc ( "idAI", "action_leapAttack", aiActionStatusString[actionLeapAttack.status], userData ); + proc ( "idAI", "action_jumpBack", aiActionStatusString[actionJumpBack.status], userData ); + proc ( "idAI", "action_evadeLeft", aiActionStatusString[actionEvadeLeft.status], userData ); + proc ( "idAI", "action_evadeRight", aiActionStatusString[actionEvadeRight.status], userData ); + + proc ( "idAI", "npc_name", spawnArgs.FindKey("npc_name")?common->GetLocalizedString(spawnArgs.GetString( "npc_name", "")):"", userData ); +} + +/* +===================== +idAI::DrawRoute +===================== +*/ +void idAI::DrawRoute( void ) const { + if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY ) { + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } else { + aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } + } +} + +/* +===================== +idAI::DrawTactical + +Draw the debug tactical information +===================== +*/ +void idAI::DrawTactical ( void ) { + if ( !DebugFilter(ai_debugTactical) ) { + return; + } + + // Colors For Lines In This File + //------------------------------- + // Majenta = Move Dest + // Green / Red = Enemy (On Player Side, On Enemy Side) + // Pink = Tether Radius + // Grey = FOV + + // Colors From AAST Draw Debug Info + //---------------------------------- + // Blue = Reserved Feature + // Orange = Near Feature + // Yellow = Look Feature + + + // Get Origin + //------------ + idVec3 origin = GetPhysics()->GetOrigin(); + origin += (GetPhysics()->GetGravityNormal() * 5.0f); + + // Draw FOV (Must be close to player and on enemy team) + //------------------------------------------------------ + if (team && DistanceTo(gameLocal.GetLocalPlayer())<500.0f) { + if (!combat.fl.aware) { + gameRenderWorld->DebugFOV(colorLtGrey, origin, viewAxis[0], fovDot, 300.0f, fovCloseDot, fovCloseRange, 0.3f, 10); + } else if (!combat.fl.seenEnemyDirectly) { + gameRenderWorld->DebugFOV(colorMdGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, 0.3f, 10); + } else { + float alpha = (1.0f - ((float)(gameLocal.GetTime() - enemy.lastVisibleTime) / (float)combat.maxLostVisTime)) * 0.35f; + gameRenderWorld->DebugFOV(colorDkGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, Max(alpha, 0.1f), 10); + } + } + + // Move Over Head + //---------------- + origin -= GetPhysics()->GetGravityNormal() * (GetPhysics()->GetBounds()[ 1 ].z - GetPhysics()->GetBounds()[ 0 ].z); + + // Draw Enemy Related Info + //------------------------- + if ( enemy.ent ) { + // Draw Lost Time + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > combat.maxLostVisTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > ( combat.maxLostVisTime/2 ) ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorGreen, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Enemy Chest position for enemy lines + idVec3 enemyEyePosition; + idVec3 offset; + if ( enemy.ent->IsType ( idActor::GetClassType() ) ){ + enemyEyePosition = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + enemyEyePosition = enemy.ent->GetPhysics()->GetOrigin(); + } + + offset = idVec3(0,0,team*2.0f); + + gameRenderWorld->DebugLine ( aiTeamColor[team], GetEyePosition() + offset, enemy.lastVisibleFromEyePosition + offset, 4.0f ); + if ( enemyEyePosition != enemy.lastVisibleEyePosition ) { + gameRenderWorld->DebugLine ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemy.lastVisibleEyePosition + offset, 4.0f ); + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } else { + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } + } + + // Ivalid Cover Timer + //-------------------- + if ( IsBehindCover() && combat.coverValidTime && combat.coverValidTime < gameLocal.time) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("invalid cover time: %d", (gameLocal.GetTime() - combat.coverValidTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } + + + // Vis Crouch + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Vis Stand + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Aggression + if ( combat.aggressiveScale != 1.0f ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("aggressive: %g", combat.aggressiveScale), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw anim prefix + //----------------------- + if ( animPrefix.Length ( ) ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("anim: %s", animPrefix.c_str()), origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw focus type + //----------------------- + if ( focusType != AIFOCUS_NONE ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiFocusString[focusType], origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw Tactical Current + //----------------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiTacticalString[combat.tacticalCurrent], origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + + // Draw My Name + //-------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText(name, origin, 0.12f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis); + + // Draw the tethered radius + if ( IsTethered ( ) ) { + tether->DebugDraw ( ); + } + + // Draw Move Destination + if ( !move.fl.done ) { + gameRenderWorld->DebugArrow ( colorMagenta, GetPhysics()->GetOrigin(), move.moveDest, 5 ); + } +} diff --git a/src/game/ai/AI_Manager.cpp b/src/game/ai/AI_Manager.cpp new file mode 100644 index 0000000..9861139 --- /dev/null +++ b/src/game/ai/AI_Manager.cpp @@ -0,0 +1,790 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" + +idVec4 aiTeamColor[AITEAM_NUM] = { idVec4 ( 0.0f, 1.0f, 0.0f, 1.0f ), + idVec4 ( 1.0f, 0.0f, 0.0f, 1.0f ) }; + +rvAIManager aiManager; + +/* +================ +rvAIManager::rvAIManager +================ +*/ +rvAIManager::rvAIManager ( void ) { + Clear(); +} + +/* +================ +rvAIManager::IsActive +================ +*/ +bool rvAIManager::IsActive( void ){ + if( gameLocal.isMultiplayer ){ + return false; + } + + return true; +} + +/* +================ +rvAIManager::RunFrame +================ +*/ +void rvAIManager::RunFrame ( void ){ + if ( !IsActive() ) { + return; + } + + // Pop the top simple think off the list + if ( !simpleThink.IsListEmpty() ) { + simpleThink.Next()->simpleThinkNode.Remove ( ); + } + + // Display current ai speeds + if ( ai_speeds.GetBool ( ) && thinkCount > 0 ) { + gameLocal.Printf ( "ai:%6i n:%2i s:%2i all:%5.2f t:%5.2f e:%5.2f m:%5.f\n", + gameLocal.framenum, thinkCount, simpleThinkCount, + timerThink.Milliseconds(), + timerTactical.Milliseconds(), + timerFindEnemy.Milliseconds(), + timerMove.Milliseconds() ); + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + int i; + int j; + // look for reaches that are no longer blocked + for ( i=0; ib.time) { + break; + } + + idEntityPtr blockent = b.blockers[j]; + if (!blockent.IsValid() || blockent->DistanceTo(b.positions[j])>10.0f) { + break; + } + if (blockent->IsType(idAI::GetClassType())) { + idAI* blockentAI = static_cast(blockent.GetEntity()); + if (blockentAI->move.moveDest.Dist2XY(b.positions[j])>100.0f) { + break; + } + } + } + + // if any one of the blockers moved or no longer exists, re-enable this reach + if ( !b.blockers.Num() || jSetReachabilityState(b.reach, true); + blockedReaches.RemoveIndex(i); + break; + } + + // DEBUG GRAPHICS + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, b.reach->start, b.reach->start + idVec3(0,0,40.0f), gameLocal.msec ); + for ( j=0; jDebugArrow( colorRed, b.blockers[j]->GetPhysics()->GetOrigin(), b.reach->start, 8, gameLocal.msec ); + } + } + } + } + // RAVEN END + + timerThink.Clear ( ); + timerTactical.Clear ( ); + timerFindEnemy.Clear ( ); + timerMove.Clear ( ); + + gameDebug.SetStatInt( "ai_thinkCount", thinkCount ); + + thinkCount = 0; + simpleThinkCount = 0; + + // Draw any debugging information + DebugDraw ( ); +} + +/* +================ +rvAIManager::Clear +================ +*/ +void rvAIManager::Clear( void ) { + thinkCount = 0; + simpleThinkCount = 0; + timerThink.Clear ( ); + timerFindEnemy.Clear ( ); + timerTactical.Clear ( ); + timerMove.Clear ( ); + + blockedReaches.Clear ( ); + helpers.Clear ( ); + simpleThink.Clear ( ); + avoids.Clear ( ); + + memset ( &teamTimers, 0, sizeof(teamTimers) ); +} + +/* +================ +rvAIManager::UnMarkAllReachBlocked +================ +*/ +void rvAIManager::UnMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, true); + } +} + +/* +================ +rvAIManager::ReMarkAllReachBlocked +================ +*/ +void rvAIManager::ReMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, false); + } +} + +/* +================ +rvAIManager::MarkReachBlocked +================ +*/ +void rvAIManager::MarkReachBlocked(idAAS* aas, idReachability* reach, const idList& blockers) { + + // only if not already blocked + if (!(reach->travelType&TFL_INVALID)) { + aiBlocked_t blocked; + blocked.aas = aas; + blocked.reach = reach; + blocked.time = gameLocal.GetTime() + 5000.0f; + + for (int i=0; iGetPhysics() && (blockers[i]->GetPhysics()->IsAtRest() || blockers[i]->GetPhysics()->GetLinearVelocity().LengthSqr()<2500.0f)) { + blocked.blockers.Append(blockers[i]); + blocked.positions.Append(blockers[i]->GetPhysics()->GetOrigin()); + } + } + if (blocked.blockers.Num()) { + aas->SetReachabilityState(reach, false); + blockedReaches.Append(blocked); + } + } + +} + +/* +================ +rvAIManager::ReactToPlayerAttack +================ +*/ +void rvAIManager::ReactToPlayerAttack ( idPlayer* player, const idVec3 &origin, const idVec3 &dir ){ + idActor* actor; + float expandSize; + + // Check all enemies and see if they need to react + for ( actor = GetEnemyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + // Skip non ai entities + if ( !actor->IsType ( idAI::Type ) ) { + continue; + } + + idAI *curAI = static_cast(actor); + + // See if it will pass through an expanded bounding box + expandSize = curAI->spawnArgs.GetFloat( "shotAtReactionRange", "16" ); + if ( !curAI->GetPhysics()->GetAbsBounds ( ).Expand(expandSize).LineIntersection ( origin, origin + dir * curAI->combat.visRange ) ) { + continue; + } + + curAI->ReactToShotAt ( player, origin, dir ); + } +} + +/* +================ +rvAIManager::Save +================ +*/ +void rvAIManager::Save( idSaveGame *savefile ) const { + int i; + int j; + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->WriteInt( teams[i].Num() ); + for( actor = teams[i].Next(); actor != NULL; actor = actor->teamNode.Next() ) { + savefile->WriteObject( actor ); + } + } + + // Write out team timers + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->WriteInt ( teamTimers[i][j] ); + } + } + + // Write out team timers + savefile->WriteInt ( avoids.Num ( ) ); + for ( i = 0; i < avoids.Num ( ); i ++ ) { + savefile->WriteVec3 ( avoids[i].origin ); + savefile->WriteFloat ( avoids[i].radius ); + savefile->WriteInt ( avoids[i].team ); + } +} + +/* +================ +rvAIManager::Restore +================ +*/ +void rvAIManager::Restore( idRestoreGame *savefile ){ + int i; + int j; + + Clear ( ); + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->ReadInt( j ); + for ( ; j > 0; j -- ) { + savefile->ReadObject ( reinterpret_cast( actor ) ); + if ( actor ) { + actor->teamNode.AddToEnd ( teams[i] ); + } + } + } + + // Read team timers + memset ( teamTimers, 0, sizeof(teamTimers) ); + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->ReadInt ( teamTimers[i][j] ); + } + } + + // Read in team timers + savefile->ReadInt ( j ); + avoids.SetNum ( j ); + for ( i = 0; i < j; i ++ ) { + savefile->ReadVec3 ( avoids[i].origin ); + savefile->ReadFloat ( avoids[i].radius ); + savefile->ReadInt ( avoids[i].team ); + } +} + +/* +===================== +rvAIManager::AddTeammate +===================== +*/ +void rvAIManager::AddTeammate ( idActor* actor ) { + // If its already in a team least then ignore the call. + // NOTE: You have to call removeteammate before addteammate to switch the + // actor from one team to another + if ( actor->teamNode.InList ( ) ) { + return; + } + actor->teamNode.AddToEnd ( teams[actor->team] ); +} + +/* +===================== +rvAIManager::RemoveTeammate +===================== +*/ +void rvAIManager::RemoveTeammate ( idActor* actor ) { + actor->teamNode.Remove ( ); +} + +/* +===================== +rvAIManager::IsSimpleThink + +Determines whether or not the given AI entity should be simple thinking this frame +===================== +*/ +bool rvAIManager::IsSimpleThink ( idAI* ai ) { + + // no simple think if simple thinking is disabled or we are the head node + if ( ai_disableSimpleThink.GetBool() ) { + return false; + } + + // Add to simple think list + if ( !ai->simpleThinkNode.InList ( ) ) { + ai->simpleThinkNode.AddToEnd ( simpleThink ); + } + + // If head of list then its a complex think + if ( ai->simpleThinkNode.Prev() == NULL ) { + return false; + } + + return true; +} + +/* +================ +rvAIManager::GetEnemyTeam +================ +*/ +idActor* rvAIManager::GetEnemyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_STROGG].Next(); + case AITEAM_STROGG: + return teams[AITEAM_MARINE].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::GetAllyTeam +================ +*/ +idActor* rvAIManager::GetAllyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_MARINE].Next(); + case AITEAM_STROGG: + return teams[AITEAM_STROGG].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::ValidateDestination + +Validate whether or not the destinations is a destination +================ +*/ +bool rvAIManager::ValidateDestination ( idAI* ai, const idVec3& dest, bool skipCurrent, idActor* skipActor ) const { + int i; + idBounds bounds; + idAI* ignore; + + ignore = (!skipCurrent && ai && !ai->SkipCurrentDestination ( )) ? ai : NULL; + + bounds = ai->GetPhysics()->GetBounds ( ); + bounds.TranslateSelf ( dest ); + bounds.ExpandSelf ( 16.0f ); + + // All teams and all actors on those teams + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + // Ignored? + if ( actor == ignore || actor == skipActor || actor->IsHidden ( ) ) { + continue; + } + + // If the actor is AI that is moving we should check their destination rather than where they are now + if ( actor->IsType ( idAI::Type ) ) { + idAI* aiactor = static_cast(actor); + if ( aiactor->move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( bounds.IntersectsBounds ( aiactor->GetPhysics()->GetBounds().Translate ( aiactor->move.moveDest ) ) ) { + return false; + } + continue; + } + } + + // Does the destination overlap this actor? + if ( bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + return false; + } + } + } + + // Destinations inside an avoid area are invalid + for ( i = avoids.Num() - 1; i >= 0; i -- ) { + const aiAvoid_t& avoid = avoids[i]; + // Skip all avoids that arent meant for this team + if ( avoid.team != -1 && ai->team != avoid.team ) { + continue; + } + // Skip if within range of the avoid + if ( (avoid.origin - dest).LengthSqr ( ) < Square ( avoid.radius ) ) { + if ( ai_debugMove.GetBool() || ai_debugTactical.GetBool() ) { + gameRenderWorld->DebugCircle( colorRed, avoid.origin, idVec3(0,0,1), avoid.radius, 25 ); + } + return false; + } + } + + return true; +} + +/* +================ +rvAIManager::NearestTeammateToPoint + +Returns the teammate closest to the given point with the given parameters +================ +*/ +idActor* rvAIManager::NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer, float maxRange, bool checkFOV, bool checkLOS ) { + idActor* actor = NULL; + idActor* closestActor = NULL; + float distSqr; + float bestDistSqr; + + closestActor = 0x0; + bestDistSqr = Square ( maxRange ); + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Player? + if ( nonPlayer && actor->IsType( idPlayer::GetClassType() ) ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (point - actor->GetPhysics()->GetOrigin()).LengthSqr(); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV && !actor->CheckFOV( point ) ) { + continue; + } + //actor has clear LOS to point? + if ( checkLOS && !actor->CanSeeFrom ( from->GetEyePosition ( ), point, false ) ) { + continue; + } + // New best actor + bestDistSqr = distSqr; + closestActor = actor; + } + return closestActor; +} + +/* +================ +rvAIManager::NearestTeammateEnemy + +Returns the closest enemy of an ally. +================ +*/ +idEntity* rvAIManager::NearestTeammateEnemy( idActor* from, float maxRange, bool checkFOV, bool checkLOS, idActor** closestAllyWithEnemy ) { + idActor* actor; + idAI* allyAI; + idEntity* allyEnemy; + idEntity* closestAllyEnemy; + float distSqr; + float bestDistSqr; + + bestDistSqr = Square ( maxRange ); + closestAllyEnemy = NULL; + + // Optionally return the ally whos enemy it is + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = NULL; + } + + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + allyAI = dynamic_cast(actor); + if ( !allyAI ) { + //player? + allyEnemy = actor->EnemyWithMostHealth(); + if ( !allyEnemy ) { + continue; + } + if ( allyEnemy->health <= 0 ) { + continue; + } + } else { + allyEnemy = allyAI->GetEnemy(); + //has enemy? + if ( !allyEnemy ) { + continue; + } + //still alive? + if ( allyAI->enemy.fl.dead ) { + continue; + } + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (actor->GetPhysics()->GetOrigin() - from->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV ) { + if ( !from->CheckFOV( actor->GetPhysics()->GetOrigin() ) ) { + continue; + } + } + //actor has clear LOS to point? + if ( checkLOS ) { + if ( !from->CanSee( actor, false ) ) { + continue; + } + } + + bestDistSqr = distSqr; + closestAllyEnemy = allyEnemy; + + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = actor; + } + } + return closestAllyEnemy; +} + +/* +================ +rvAIManager::LocalTeamHasEnemies + +Returns true if nearby members of my team have any enemies or if any nearby enemies have enemies that are nearby members of my team +================ +*/ +bool rvAIManager::LocalTeamHasEnemies ( idAI* self, float maxBuddyRange, float maxEnemyRange, bool checkPVS ) { + idActor* actor = NULL; + pvsHandle_t pvs; + + if ( !self ) { + return false; + } + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)self->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + if ( actor->IsType( idAI::GetClassType() ) ) { + //Has an enemy? + if ( !((idAI*)actor)->GetEnemy() ) { + continue; + } + //Has an enemy + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ((idAI*)actor)->GetEnemy()->GetPVSAreas(), ((idAI*)actor)->GetEnemy()->GetNumPVSAreas() ) ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + continue; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + + } + //close enough? + if ( actor != self && self->DistanceTo( actor->GetPhysics()->GetOrigin() ) > maxBuddyRange ) { + continue; + } + //Anyone mad at him? + if ( !actor->ClosestEnemyToPoint( actor->GetPhysics()->GetOrigin(), maxEnemyRange, true, checkPVS ) ) { + continue; + } + return true; + } + return false; +} + +/* +================ +rvAIManager::ActorIsBehindActor + +Returns true if the given 'ambuser' is behind the given 'victim' +================ +*/ +bool rvAIManager::ActorIsBehindActor( idActor* ambusher, idActor* victim ) { + idVec3 dir2Ambusher = ambusher->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin(); + float dist = dir2Ambusher.Normalize(); + if ( DotProduct(dir2Ambusher, victim->viewAxis[0] ) < 0 && dist < 200.0f ) { + return true; + } + return false; +} + +/* +=============================================================================== + + rvAIManager - Helpers + +=============================================================================== +*/ + +/* +===================== +rvAIManager::RegisterHelper +===================== +*/ +void rvAIManager::RegisterHelper ( rvAIHelper* helper ) { + helper->helperNode.AddToEnd ( helpers ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::UnregisterHelper +===================== +*/ +void rvAIManager::UnregisterHelper ( rvAIHelper* helper ) { + helper->helperNode.Remove ( ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::FindClosestHelper +===================== +*/ +rvAIHelper* rvAIManager::FindClosestHelper ( const idVec3& origin ) { + rvAIHelper* helper; + rvAIHelper* bestHelper; + float bestDist; + float dist; + + bestDist = idMath::INFINITY; + bestHelper = NULL; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + dist = (origin - helper->GetPhysics()->GetOrigin()).LengthFast ( ); + if ( dist < bestDist ) { + bestDist = dist; + bestHelper = helper; + } + } + return bestHelper; +} + +/* +================ +rvAIManager::UpdateHelpers +================ +*/ +void rvAIManager::UpdateHelpers ( void ) { + int i; + + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + if ( actor->IsHidden ( ) || !actor->IsType ( idAI::Type ) ) { + continue; + } + static_cast(actor)->UpdateHelper ( ); + } + } +} + +/* +=============================================================================== + + rvAIManager - Debugging + +=============================================================================== +*/ + +/* +================ +rvAIManager::DebugDraw +================ +*/ +void rvAIManager::DebugDraw ( void ) { + // Draw helpers? + if ( ai_debugHelpers.GetBool ( ) ) { + DebugDrawHelpers ( ); + + int i; + for ( i = 0; i < avoids.Num(); i ++ ) { + const aiAvoid_t& avoid = avoids[i]; + gameRenderWorld->DebugCircle ( colorOrange, avoid.origin, idVec3(0,0,1), avoid.radius, 10, 0 ); + } + } + + +} + +/* +================ +rvAIManager::DebugDrawHelpers +================ +*/ +void rvAIManager::DebugDrawHelpers ( void ) { + rvAIHelper* helper; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + helper->DrawDebugEntityInfo ( ); + } +} diff --git a/src/game/ai/AI_Manager.h b/src/game/ai/AI_Manager.h new file mode 100644 index 0000000..28fb41b --- /dev/null +++ b/src/game/ai/AI_Manager.h @@ -0,0 +1,260 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AI_MANAGER_H__ +#define __AI_MANAGER_H__ + +typedef enum { + AITEAM_MARINE, + AITEAM_STROGG, + AITEAM_NUM +} aiTeam_t; + +typedef enum { + AITEAMTIMER_ANNOUNCE_TACTICAL, // Tactical change + AITEAMTIMER_ANNOUNCE_SUPPRESSING, + AITEAMTIMER_ANNOUNCE_SUPPRESSED, + AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, // Shot by a teammate + AITEAMTIMER_ANNOUNCE_ENEMYSTEATH, + AITEAMTIMER_ANNOUNCE_NEWENEMY, // New enemy was aquired + AITEAMTIMER_ANNOUNCE_SNIPER, // Sniper sighted + AITEAMTIMER_ANNOUNCE_CANIHELPYOU, // Player standing in front of a friendly too long + AITEAMTIMER_ANNOUNCE_SIGHT, // First time seeing an enemy + AITEAMTIMER_ACTION_RELAX, // Play relax animation + AITEAMTIMER_ACTION_PEEK, // Play peek animation + AITEAMTIMER_ACTION_TALK, // Able to talk to another person yet? + AITEAMTIMER_MAX +} aiTeamTimer_t; + +extern idVec4 aiTeamColor[AITEAM_NUM]; + +/* +===================== +blockedReach_t +===================== +*/ +// cdr: Alternate Routes Bug +typedef struct aiBlocked_s { + idAAS* aas; + idReachability* reach; + int time; + idList< idEntityPtr > blockers; + idList< idVec3 > positions; +} aiBlocked_t; + +/* +===================== +aiAvoid_t +===================== +*/ +typedef struct aiAvoid_s { + idVec3 origin; + float radius; + int team; +} aiAvoid_t; + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +class rvAIHelper : public idEntity { +public: + CLASS_PROTOTYPE( rvAIHelper ); + + rvAIHelper ( void ); + + idLinkList helperNode; + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + + idVec3 GetDirection ( const idAI* ent ) const; + +protected: + + virtual void OnActivate ( bool active ); + +private: + + void Event_Activate ( idEntity *activator ); +}; + +/* +=============================================================================== + +rvAIManager + +=============================================================================== +*/ + +class rvAIManager { +public: + + rvAIManager ( void ); + ~rvAIManager ( void ) {} + + /* + =============================================================================== + General + =============================================================================== + */ + + void RunFrame ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( void ); + + bool IsActive ( void ); + bool IsSimpleThink ( idAI* ai ); + + /* + =============================================================================== + Navigation + =============================================================================== + */ + + void UnMarkAllReachBlocked ( void ); + void ReMarkAllReachBlocked ( void ); + void MarkReachBlocked ( idAAS* aas, idReachability* reach, const idList& blockers); + bool ValidateDestination ( idAI* ignore, const idVec3& dest, bool skipCurrent = false, idActor* skipActor = NULL ) const; + void AddAvoid ( const idVec3& origin, float range, int team ); + + /* + =============================================================================== + Helpers + =============================================================================== + */ + + void RegisterHelper ( rvAIHelper* helper ); + void UnregisterHelper ( rvAIHelper* helper ); + rvAIHelper* FindClosestHelper ( const idVec3& origin ); + + /* + =============================================================================== + Team Management + =============================================================================== + */ + + void AddTeammate ( idActor* ent ); + void RemoveTeammate ( idActor* ent ); + + idActor* GetAllyTeam ( aiTeam_t team ); + idActor* GetEnemyTeam ( aiTeam_t team ); + + idActor* NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer = false, float maxRange = 1000.0f, bool checkFOV = false, bool checkLOS = false ); + idEntity* NearestTeammateEnemy ( idActor* from, float maxRange=1000.0f, bool checkFOV = false, bool checkLOS = false, idActor** ally = NULL ); + bool LocalTeamHasEnemies ( idAI* self, float maxBuddyRange=640.0f, float maxEnemyRange=1024.0f, bool checkPVS=false ); + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + + /* + =============================================================================== + Team Timers + =============================================================================== + */ + + bool CheckTeamTimer ( int team, aiTeamTimer_t timer ); + void ClearTeamTimer ( int team, aiTeamTimer_t timer ); + void SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + + void AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ); + void AnnounceDeath ( idAI* victim, idEntity* attacker ); + + /* + =============================================================================== + Reactions + =============================================================================== + */ + + void ReactToPlayerAttack ( idPlayer* player, const idVec3 &origOrigin, const idVec3 &origDir ); + + /* + =============================================================================== + Debugging + =============================================================================== + */ + + idTimer timerFindEnemy; + idTimer timerTactical; + idTimer timerMove; + idTimer timerThink; + + int thinkCount; + int simpleThinkCount; + +protected: + + void UpdateHelpers ( void ); + + void DebugDraw ( void ); + void DebugDrawHelpers ( void ); + + idList blockedReaches; + idLinkList simpleThink; + idLinkList helpers; + + idLinkList teams[AITEAM_NUM]; + int teamTimers[AITEAM_NUM][AITEAMTIMER_MAX]; + + idList avoids; +}; + +ID_INLINE bool rvAIManager::CheckTeamTimer ( int team, aiTeamTimer_t timer ) { + return gameLocal.time >= teamTimers[team][timer]; +} + +ID_INLINE void rvAIManager::ClearTeamTimer ( int team, aiTeamTimer_t timer ) { + teamTimers[team][timer] = 0; +} + +ID_INLINE void rvAIManager::SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ) { + teamTimers[team][timer] = gameLocal.time + delay; +} + +ID_INLINE void rvAIManager::AddAvoid ( const idVec3& origin, float radius, int team ) { + if ( !IsActive ( ) ) { + return; + } + aiAvoid_t& a = avoids.Alloc ( ); + a.origin = origin; + a.radius = radius; + a.team = team; +} + +extern rvAIManager aiManager; + +#endif // __AI_MANAGER_H__ diff --git a/src/game/ai/AI_Medic.cpp b/src/game/ai/AI_Medic.cpp new file mode 100644 index 0000000..5dfd861 --- /dev/null +++ b/src/game/ai/AI_Medic.cpp @@ -0,0 +1,860 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Medic.h" + + +const idEventDef AI_DisableHeal ( "disableHeal" ); +const idEventDef AI_EnableHeal ( "enableHeal" ); +const idEventDef AI_TakePatient ( "takePatient", "e" ); + +CLASS_DECLARATION( rvAITactical, rvAIMedic ) + EVENT( AI_DisableHeal, rvAIMedic::Event_DisableHeal ) + EVENT( AI_EnableHeal, rvAIMedic::Event_EnableHeal ) + EVENT( AI_TakePatient, rvAIMedic::TakePatient ) + EVENT( AI_EnableMovement, rvAIMedic::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvAIMedic::Event_DisableMovement ) +END_CLASS + +/* +================ +rvAIMedic::rvAIMedic +================ +*/ +rvAIMedic::rvAIMedic ( void ) { + patient = NULL; + isTech = false; + noAutoHeal = false; + stationary = false; + silent = false; + healObeyTether = false; + healing = false; + lastPatientCheckTime = 0; + emergencyOverride = false; + //healedAmount = 0; + healDisabled = false; + wasAware = false; + wasIgnoreEnemies = false; + healDebounceTime = 0; +} + +void rvAIMedic::InitSpawnArgsVariables( void ) +{ + //NOTE: these shouldn't change from spawn values - maybe they don't need to be variables and saved/loaded? + isTech = spawnArgs.GetBool( "tech" ); + noAutoHeal = spawnArgs.GetBool( "noAutoHeal" ); + healAmt = 200;//spawnArgs.GetInt( "healAmt", "25" ); + healObeyTether = spawnArgs.GetBool( "healObeyTether" ); + patientRange = spawnArgs.GetFloat( "patientRange", "640" ); + buddyRange = spawnArgs.GetFloat( "buddyRange", "640" ); + enemyRange = spawnArgs.GetFloat( "enemyRange", "1024" ); + healDebounceInterval = 0;//SEC2MS( spawnArgs.GetFloat( "healWait", "0" ) ); + + /* + // JTD: check for player being strogg, since the limits will be different in that case.. + idStr str = gameLocal.world->spawnArgs.GetString ( "player", "player_marine" ); + str.Strip( "player_" ); + // ...we'll end up with something like minMarineHeal...or minStroggHeal + */ + minHealValue = 35;//spawnArgs.GetInt( va( "min%sHeal", str.c_str()), "50" ); + maxHealValue = 0;//75;//spawnArgs.GetInt( va( "max%sHeal", str.c_str()), "75" ); +} +/* +================ +rvAIMedic::Spawn +================ +*/ +void rvAIMedic::Spawn ( void ) { + InitSpawnArgsVariables(); + + stationary = spawnArgs.GetBool( "stationary" ); + silent = spawnArgs.GetBool( "silent", "0"); + if ( spawnArgs.GetBool( "disableHeal" ) ) { + Event_DisableHeal(); + } + if ( spawnArgs.GetBool( "enableHeal" ) ) { + Event_EnableHeal(); + } + const char *func; + if ( spawnArgs.GetString( "script_postHeal", "", &func ) ) + { + mPostHealScript.Init( func ); + } +} + +/* +================ +rvAIMedic::Show +================ +*/ +void rvAIMedic::Show( void ) { + rvAITactical::Show(); + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +/* +================ +rvAIMedic::Save +================ +*/ +void rvAIMedic::Save( idSaveGame *savefile ) const { + patient.Save( savefile ); + savefile->WriteBool( healing ); + savefile->WriteInt( lastPatientCheckTime ); + savefile->WriteBool( emergencyOverride ); + savefile->WriteBool( healDisabled ); + savefile->WriteBool( wasAware ); + savefile->WriteBool( wasIgnoreEnemies ); + //savefile->WriteInt( healedAmount ); + savefile->WriteInt( healDebounceTime ); + savefile->WriteBool( stationary ); + savefile->WriteBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->WriteInt( curHealValue ); + savefile->WriteInt( maxPatientValue ); + mPostHealScript.Save( savefile ); +} + +/* +================ +rvAIMedic::Restore +================ +*/ +void rvAIMedic::Restore( idRestoreGame *savefile ) { + patient.Restore( savefile ); + savefile->ReadBool( healing ); + savefile->ReadInt( lastPatientCheckTime ); + savefile->ReadBool( emergencyOverride ); + savefile->ReadBool( healDisabled ); + savefile->ReadBool( wasAware ); + savefile->ReadBool( wasIgnoreEnemies ); + //savefile->ReadInt( healedAmount ); + savefile->ReadInt( healDebounceTime ); + savefile->ReadBool( stationary ); + savefile->ReadBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->ReadInt( curHealValue ); + savefile->ReadInt( maxPatientValue ); + mPostHealScript.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvAIMedic::Event_EnableHeal +===================== +*/ +void rvAIMedic::Event_EnableHeal( void ) { + healDisabled = false; +} + +/* +===================== +rvAIMedic::Event_DisableHeal +===================== +*/ +void rvAIMedic::Event_DisableHeal( void ) { + healDisabled = true; + /* + if ( patient ) { + //drop them right now - NOTE: should be done before any scripting? + DropPatient(); + } + */ +} + +/* +================== +rvAIMedic::Event_EnableMovement +================== +*/ +void rvAIMedic::Event_EnableMovement( void ) { + stationary = false; +} + +/* +================== +rvAIMedic::Event_DisableMovement +================== +*/ +void rvAIMedic::Event_DisableMovement ( void ) { + stationary = true; +} + +/* +===================== +rvAIMedic::TalkTo +===================== +*/ +void rvAIMedic::TalkTo( idActor *actor ) { + if ( actor->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* player = dynamic_cast(actor); + if ( player ) + { + emergencyOverride = true; + if ( AvailableToTakePatient() && CheckTakePatient( player ) ) + { + return; + } + emergencyOverride = false; + } + } + rvAITactical::TalkTo( actor ); + + if ( !aifl.action && !aifl.scripted && !IsSpeaking() && !IsHidden() && !silent) { + if ( GetEnemy() || patient ) { + Speak( "lipsync_heal_busy_", true ); + } else { + //Never got proper VO for this, disabling it... :/ + //Speak( "lipsync_heal_noheal_", true ); + } + } +} + +/* +================ +rvAIMedic::GetDebugInfo +================ +*/ +void rvAIMedic::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + rvAITactical::GetDebugInfo ( proc, userData ); + + proc ( "rvAIMedic", "aifl.scripted", aifl.scripted?"true":"false", userData ); + proc ( "rvAIMedic", "move.fl.disabled", move.fl.disabled?"true":"false", userData ); + proc ( "rvAIMedic", "IsSpeaking", IsSpeaking()?"true":"false", userData ); + proc ( "rvAIMedic", "healDisabled", healDisabled?"true":"false", userData ); + proc ( "rvAIMedic", "noAutoHeal ", noAutoHeal ?"true":"false", userData ); + proc ( "rvAIMedic", "stationary", stationary?"true":"false", userData ); + proc ( "rvAIMedic", "silent", silent?"true":"false", userData ); + proc ( "rvAIMedic", "healObeyTether", healObeyTether?"true":"false", userData ); + proc ( "rvAIMedic", "patient", patient==NULL?"":patient->GetName(), userData ); + proc ( "rvAIMedic", "wasAware", wasAware?"true":"false", userData ); + proc ( "rvAIMedic", "wasIgnoreEnemies", wasIgnoreEnemies?"true":"false", userData ); + proc ( "rvAIMedic", "lastPatientCheckTime",va("%d",lastPatientCheckTime), userData ); + proc ( "rvAIMedic", "healDebounceTime", va("%d",healDebounceTime), userData ); + proc ( "rvAIMedic", "healing", healing?"true":"false", userData ); + proc ( "rvAIMedic", "emergencyOverride",healing?"true":"false", userData ); + //proc ( "rvAIMedic", "healedAmount", va("%d",healedAmount), userData ); + proc ( "rvAIMedic", "minHealValue", va("%d",minHealValue), userData ); + proc ( "rvAIMedic", "maxHealValue", va("%d",maxHealValue), userData ); + proc ( "rvAIMedic", "healAmt", va("%d",healAmt), userData ); + proc ( "rvAIMedic", "patientRange", va("%f",patientRange), userData ); + proc ( "rvAIMedic", "buddyRange", va("%f",buddyRange), userData ); + proc ( "rvAIMedic", "enemyRange", va("%f",enemyRange), userData ); + proc ( "rvAIMedic", "tech", isTech?"true":"false", userData ); +} + +/* +============ +rvAIMedic::IsTethered +============ +*/ +bool rvAIMedic::IsTethered ( void ) const { + if ( rvAITactical::IsTethered() ) { + if ( !healObeyTether && patient ) { + return false; + } + return true; + } + return false; +} + +void rvAIMedic::TakePatient( idPlayer* pPatient ) +{ + patient = pPatient; + if ( emergencyOverride ) + { + ClearEnemy(); + } + + wasAware = combat.fl.aware; + wasIgnoreEnemies = combat.fl.ignoreEnemies; + ProcessEvent( &AI_BecomePassive, true ); + combat.fl.ignoreEnemies = true; + ClearEnemy(); + lookTarget = patient; + healing = false; + //healedAmount = 0; + + if ( pPatient && DistanceTo( pPatient ) > 250.0f && !silent ) + { + //have enough time to say this before we get there...? + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_call_player_tech_", true ); + } else { + Speak( "lipsync_call_player_", true ); + } + } + + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + SetState( "State_Medic" ); + PostState( "State_Combat" ); + + //just in case + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } +} + +void rvAIMedic::DropPatient( void ) +{ + /* + if ( !spawnArgs.GetBool( "ignoreEnemies" ) ) + { + combat.fl.ignoreEnemies = false; + } + */ + /* + if ( !aifl.scripted ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_DEFAULT ); + } + */ + healing = false; + if ( !aifl.scripted ) { + ProcessEvent( &AI_BecomeAggressive ); + combat.fl.aware = wasAware; + combat.fl.ignoreEnemies = wasIgnoreEnemies; + lookTarget = NULL; + } else if ( lookTarget == patient ) { + //if scripted, clear the looktarget only if it's the patient? This could be wrong, though.... + lookTarget = NULL; + } + + patient = NULL; + + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } + + ForceTacticalUpdate(); + if ( !aifl.scripted ) { + UpdateTactical ( 0 ); + } + //FIXME: what if they stay in this state? + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +void rvAIMedic::SetHealValues( idPlayer* player ) +{ + if ( !player ) + { + return; + } + if ( isTech ) + { + curHealValue = player->inventory.armor; + maxPatientValue = player->inventory.maxarmor; + } + else + { + curHealValue = player->health; + maxPatientValue = player->inventory.maxHealth; + } +} + +bool rvAIMedic::CheckTakePatient( idPlayer* player ) +{ + if ( !player ) + { + return false; + } + if ( player->IsHidden() ) + { + return false; + } + if ( player->health <= 0 ) + { + return false; + } + SetHealValues( player ); + + if ( curHealValue < maxPatientValue ) + {//they are hurt + if ( curHealValue <= minHealValue || (gameLocal.GetTime() >= healDebounceTime && emergencyOverride && (!maxHealValue || curHealValue < maxHealValue)) ) + {//patient needs healing or he requested a heal and it's been long enough and he's below the max heal level (if there is one) + if ( DistanceTo( player ) < patientRange ) + {//close enough + if ( (!move.fl.disabled && !stationary) || DistanceTo( player ) <= combat.meleeRange ) + {//either I am allowed to move or player is close enough that I don't have to + //if ( !tether || tether->ValidateDestination( this, player->GetPhysics()->GetOrigin() ) ) + {//not tethered or patient is in our tether + if ( emergencyOverride || CanSee( player, false ) ) + {//can see the patient - OR: just check PVS? + TakePatient( player ); + return true; + } + } + } + } + } + } + + return false; +} + +bool rvAIMedic::SituationAllowsPatient( void ) +{ + if ( !aifl.scripted && !IsHidden() && !aifl.dead ) + {//not scripted right now + if ( combat.fl.ignoreEnemies ) { + return true; + } + bool enemyInPVS = false; + bool enemyInRange = false; + if ( GetEnemy() ) + {//sorry, we have to bail on you! + enemyInRange = (DistanceTo( GetEnemy() ) < enemyRange); + if ( enemyInRange ) { + pvsHandle_t pvs; + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( gameLocal.pvs.InCurrentPVS( pvs, GetEnemy()->GetPVSAreas(), GetEnemy()->GetNumPVSAreas() ) ) { + enemyInPVS = true; + emergencyOverride = false; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + } + if ( emergencyOverride ) + {//don't care how crazy it is, go for it! + return true; + } + if ( (!GetEnemy() || !enemyInPVS || !enemyInRange ) && gameLocal.GetTime() - enemy.changeTime > 5000 ) + {//haven't had an enemy for 5 seconds + if ( !aiManager.LocalTeamHasEnemies( this, buddyRange, enemyRange, true ) ) + {//local buddies don't have enemies + //NOTE: which buddies and enemies are local can change as we head to our patient! + return true; + } + } + } + return false; +} + +bool rvAIMedic::AvailableToTakePatient( void ) +{ + if ( !healDisabled ) + {//not forced to disabled + if ( !aifl.action ) + {//not in the middle of an action + if ( !patient ) + {//don't already have a patient + if ( !IsSpeaking() ) + {//not talking + return SituationAllowsPatient(); + } + } + } + } + return false; +} + +/* +================ +rvAIMedic::Think +================ +*/ +void rvAIMedic::Think ( void ) { + rvAITactical::Think ( ); + +// while( entMedic.getKey("alive") == "true" && entMedic.getKey("healer") == "1") +//??? + if ( !noAutoHeal ) + { + if ( gameLocal.GetTime() - lastPatientCheckTime > 1000 ) + { + lastPatientCheckTime = gameLocal.GetTime(); + if ( !patient ) + { + emergencyOverride = false; + } + if ( AvailableToTakePatient() ) + { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if ( CheckTakePatient( player ) ) + { + return; + } + //otherwise, check team? + /* + idActor* actor; + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) + { + if ( CheckTakePatient( actor ) ) + { + return; + } + } + */ + } + } + } +} + +/* +===================== +rvAIMedic::OnStateThreadClear +===================== +*/ +void rvAIMedic::OnStateThreadClear( const char *statename, int flags ) { + if ( idStr::Icmp( statename, "State_Medic" ) ) { + if ( patient ) { + //BAH! Someone changed our state on us! + if ( lookTarget == patient ) { + lookTarget = NULL; + } + patient = NULL; + healing = false; + } + } +} + +/* +============ +rvAIMedic::OnStartMoving +============ +*/ +void rvAIMedic::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( patient ) + {//we were trying to heal! + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//being told to leave the patient + //abort the heal, for now + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + } +} + +/* +===================== +rvAIMedic::Pain +===================== +*/ +bool rvAIMedic::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool retVal = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( retVal && patient ) { + //if get hit while trying to heal, protect ourselves + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + StopMove ( MOVE_STATUS_DONE ); + } + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + return retVal; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAIMedic ) + STATE ( "State_Medic", rvAIMedic::State_Medic ) +END_CLASS_STATES + +/* +================ +idAI::State_Medic + +Rush towards the patient to melee range and heal until they're okay +================ +*/ +stateResult_t rvAIMedic::State_Medic ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the patient, speak & start anim + STAGE_PRE_HEAL_ANIM_WAIT,// Wait for pre heal anim to finish, then start the normal heal anim + STAGE_HEAL_START_WAIT,// Wait for start anim to finish + STAGE_HEAL, // Keep healing until no longer in melee range or they're fully healed + STAGE_WAIT_FINISH // Finish anim + }; + if ( !patient || patient->health <= 0 || !SituationAllowsPatient() ) + {//patient dead or situation is bad + //NOTE: if patient still alive and situation is just bad, maybe we shouldn't break out altogether, maybe pause and resume? + StopMove ( MOVE_STATUS_DONE ); + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + + if ( !move.fl.done ) + { + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//something stomped our move, give it up, for now... :/ + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + } + + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) + { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee(patient,false) ) + {//wtf, we're not there yet, try again! + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + } + else + {//we're there! + if ( !healing ) + { + SetHealValues( patient ); + if ( !IsSpeaking() && speakTime < (gameLocal.GetTime() - 2000) && !silent ) + {//didn't speak in last couple seconds + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_heal_start_tech_", true ); + } else { + Speak( "lipsync_heal_start_", true ); + } + } + } + StopMove ( MOVE_STATUS_DONE ); + healing = true; + // check for preHeal anim key and if it's present, play it first. + const char *preHealAnim; + if ( spawnArgs.GetString( "anim_preHeal", "", &preHealAnim )) + { + PlayAnim( ANIMCHANNEL_TORSO, preHealAnim, 4 ); + return SRESULT_STAGE ( STAGE_PRE_HEAL_ANIM_WAIT ); + } + else // otherwise just use the regular anim to start healing + { + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + } + } + if ( !stationary && move.range != 42.0f ) + { + //MCG: if you ever get this assert, please call me over so I can debug it! + assert(move.range==42.0f); + move.range = 42.0f;//shouldn't have to do this, but sometimes something is overriding the range to 8! VERY VERY BAD... :_( + } + /* + else if ( !CheckTacticalMove ( AITACTICAL_MEDIC ) && CanSee(patient,false) ) + {//we're here, just stop + Speak( "lipsync_medic_arrive", true ); + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + */ + + // Perform actions on the way to the patient + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + } + + return SRESULT_WAIT; + + // intermediate state which may or may not exist...depends on the presence of pre heal anim key on this entity + case STAGE_PRE_HEAL_ANIM_WAIT: + const char *preHealAnim; + spawnArgs.GetString( "anim_preHeal", "", &preHealAnim ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), preHealAnim )) + {//finished or interrupted + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + return SRESULT_WAIT; + + + case STAGE_HEAL_START_WAIT: + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_start" ) ) + {//finished or interrupted +// PlayCycle( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + //show the tool, just in case the anim was interrupted + ShowAttachment( spawnArgs.GetString("def_attach") ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + return SRESULT_WAIT; + + case STAGE_HEAL: + { + SetHealValues( patient ); + /* + if ( curHealValue >= maxPatientValue + || (curHealValue > minHealValue && healedAmount >= healAmt) ) + {//patient fully healed (or we've used up our allotment), we're done here + Speak( "lipsync_heal_end_", true ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + */ + + // If we are out of melee range or lost sight of our patient then start moving again + /* + if ( !stationary && !move.fl.disabled ) { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee( patient, false ) ) { + MoveToEntity( patient, 42 ); + Speak( "lipsync_heal_move_", true ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_STAGE ( STAGE_MOVE ); + } + } + */ + + /* + combat.fl.ignoreEnemies = true; + */ + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + + // Always face patient when in melee range + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + // Perform actions while standing still + /* + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + combat.tacticalUpdateTime = gameLocal.GetTime(); + if ( UpdateTactical( 100000 ) ) { + DropPatient(); + return SRESULT_DONE_WAIT; + } + } + */ + + //jshepard: tech/medic dependent speech + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player" ) ) { + if ( !isTech ) { + patient->health = patient->health+healAmt>maxPatientValue?maxPatientValue:patient->health+healAmt; + if( !silent) { + Speak( "lipsync_heal_end_", true ); + } + } else { + patient->inventory.armor = patient->inventory.armor+healAmt>maxPatientValue?maxPatientValue:patient->inventory.armor+healAmt;; + if( !silent) { + Speak( "lipsync_heal_end_tech_", true ); + } + } + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + if ( gameLocal.random.RandomFloat() > 0.5f ) + { + if ( !isTech ) + { + if ( patient->health < maxPatientValue ) { + patient->health++; + } + } + else + { + if ( patient->inventory.armor < maxPatientValue ) { + patient->inventory.armor++; + } + } + } + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_end" ) ) + {//finished or interrupted + //turn off the tool, just in case we were interrupted + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/AI_Medic.h b/src/game/ai/AI_Medic.h new file mode 100644 index 0000000..657bf64 --- /dev/null +++ b/src/game/ai/AI_Medic.h @@ -0,0 +1,116 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Medic.h + +================ +*/ +#include "AI_Tactical.h" + +#ifndef __AI_MEDIC__ +#define __AI_MEDIC__ + +class rvAIMedic : public rvAITactical { +public: + + CLASS_PROTOTYPE( rvAIMedic ); + + rvAIMedic ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Show ( void ); + + virtual void TalkTo ( idActor *actor ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool IsTethered ( void ) const; + + virtual void OnStateThreadClear ( const char *statename, int flags ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + bool isTech; + +protected: + virtual void OnStartMoving ( void ); + +private: + + idEntityPtr patient; + bool healing; + int lastPatientCheckTime; + bool emergencyOverride; + + bool noAutoHeal; + bool stationary; + bool silent; + bool healObeyTether; + int healAmt; + float patientRange; + float buddyRange; + float enemyRange; + + int curHealValue; + int maxHealValue; + int minHealValue; + int healedAmount; + int maxPatientValue; + + int healDebounceInterval; + int healDebounceTime; + + bool healDisabled; + bool wasAware; + bool wasIgnoreEnemies; + + void SetHealValues ( idPlayer* player ); + + void TakePatient ( idPlayer* pPatient ); + void DropPatient ( void ); + bool CheckTakePatient ( idPlayer* actor ); + bool SituationAllowsPatient ( void ); + bool AvailableToTakePatient ( void ); + + stateResult_t State_Medic ( const stateParms_t& parms ); + + void Event_EnableHeal ( void ); + void Event_DisableHeal ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + + rvScriptFuncUtility mPostHealScript; // script to run after completing a heal + + CLASS_STATES_PROTOTYPE ( rvAIMedic ); +}; + +#endif /* !__AI_MEDIC__ */ diff --git a/src/game/ai/AI_Move.cpp b/src/game/ai/AI_Move.cpp new file mode 100644 index 0000000..556adbe --- /dev/null +++ b/src/game/ai/AI_Move.cpp @@ -0,0 +1,4856 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + +AI_Move.cpp + +This file has all movement related functions. It and its sister H file were +split from AI.h and AI.cpp in order to prevent merge conflicts and to make +further changes to the system possible. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + idMoveState + +=============================================================================== +*/ + +/* +===================== +idMoveState::idMoveState +===================== +*/ +idMoveState::idMoveState() { + moveType = MOVETYPE_ANIM; + moveCommand = MOVE_NONE; + moveStatus = MOVE_STATUS_DONE; + moveDest.Zero(); + moveDir.Set( 1.0f, 0.0f, 0.0f ); + goalEntity = NULL; + goalEntityOrigin.Zero(); + toAreaNum = 0; + startTime = 0; + duration = 0; + speed = 0.0f; + range = 0.0f; + wanderYaw = 0; + nextWanderTime = 0; + blockTime = 0; + obstacle = NULL; + lastMoveOrigin = vec3_origin; + lastMoveTime = 0; + anim = 0; + travelFlags = TFL_WALK|TFL_AIR; + kickForce = 2048.0f; + fl.ignoreObstacles = false; + fl.allowAnimMove = false; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + blockedRadius = 0.0f; + blockedMoveTime = 750; + blockedAttackTime = 750; + turnRate = 360.0f; + turnVel = 0.0f; + anim_turn_yaw = 0.0f; + anim_turn_amount = 0.0f; + anim_turn_angles = 0.0f; + fly_offset = 0; + fly_seek_scale = 1.0f; + fly_roll_scale = 0.0f; + fly_roll_max = 0.0f; + fly_roll = 0.0f; + fly_pitch_scale = 0.0f; + fly_pitch_max = 0.0f; + fly_pitch = 0.0f; + fly_speed = 0.0f; + fly_bob_strength = 0.0f; + fly_bob_vert = 0.0f; + fly_bob_horz = 0.0f; + currentDirection = MOVEDIR_FORWARD; + idealDirection = MOVEDIR_FORWARD; + current_yaw = 0.0f; + ideal_yaw = 0.0f; + flyTiltJoint = INVALID_JOINT; + addVelocity.Zero(); +} + + + +/* +===================== +idMoveState::Spawn +===================== +*/ +void idMoveState::Spawn( idDict &spawnArgs ) { + memset ( &fl, 0, sizeof(fl) ); + fl.allowAnimMove = true; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + fl.noRun = spawnArgs.GetBool ( "forceWalk", "0" ); + fl.noWalk = spawnArgs.GetBool ( "forceRun", "0" ); + fl.noTurn = spawnArgs.GetBool ( "noTurn", "0" ); + fl.noGravity = spawnArgs.GetBool ( "animate_z", "0" ); + fl.noRangedInterrupt = spawnArgs.GetBool ( "noRangedInterrupt", "0" ); + + fl.flyTurning = spawnArgs.GetBool ( "flyTurning", "0" ); + fl.allowDirectional = spawnArgs.GetBool ( "directionalMovement", "0" ); + fl.allowPushMovables = spawnArgs.GetBool( "af_push_moveables", "0" ); + fl.ignoreObstacles = spawnArgs.GetBool( "ignore_obstacles", "0" ); + fl.disabled = spawnArgs.GetBool ( "noMove", "0" ); + walkRange = spawnArgs.GetFloat ( "walkRange", "100" ); + walkTurn = spawnArgs.GetFloat ( "walkTurn", "45" ); + followRange = spawnArgs.GetVec2 ( "followRange", "70 250" ); + searchRange = spawnArgs.GetVec2 ( "searchRange", "0 1024" ); + attackPositionRange = spawnArgs.GetFloat ( "attackPositionRange", "0" ); + turnDelta = spawnArgs.GetFloat ( "turnDelta", "10" ); + blockTime = 0; + + spawnArgs.GetInt( "fly_offset", "100", fly_offset ); + spawnArgs.GetFloat( "fly_speed", "100", fly_speed ); + spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength ); + spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz ); + spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert ); + spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale ); + spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale ); + spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max ); + spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale ); + spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max ); + + spawnArgs.GetFloat( "turn_rate", "360", turnRate ); + spawnArgs.GetFloat( "kick_force", "4096", kickForce ); + spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius ); + spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime ); + spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime ); +} + + +/* +===================== +idMoveState::Save +===================== +*/ +void idMoveState::Save( idSaveGame *savefile ) const { + int i; + + savefile->Write ( &fl, sizeof(fl) ); + + savefile->WriteInt( (int)moveType ); + savefile->WriteInt( (int)moveCommand ); + savefile->WriteInt( (int)moveStatus ); + savefile->WriteVec3( moveDest ); + savefile->WriteVec3( moveDir ); + + savefile->WriteInt( toAreaNum ); + savefile->WriteInt( startTime ); + savefile->WriteInt( duration ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( range ); + savefile->WriteFloat( wanderYaw ); + savefile->WriteInt( nextWanderTime ); + savefile->WriteInt( blockTime ); + obstacle.Save( savefile ); + savefile->WriteVec3( lastMoveOrigin ); + savefile->WriteInt( lastMoveTime ); + savefile->WriteInt( anim ); + + savefile->WriteInt( travelFlags ); + + savefile->WriteFloat ( kickForce ); + savefile->WriteFloat ( blockedRadius ); + savefile->WriteInt ( blockedMoveTime ); + savefile->WriteInt ( blockedAttackTime ); + + savefile->WriteFloat( ideal_yaw ); + savefile->WriteFloat( current_yaw ); + savefile->WriteFloat( turnRate ); + savefile->WriteFloat( turnVel ); + savefile->WriteFloat( anim_turn_yaw ); + savefile->WriteFloat( anim_turn_amount ); + savefile->WriteFloat( anim_turn_angles ); + + savefile->WriteJoint( flyTiltJoint ); + savefile->WriteFloat( fly_speed ); + savefile->WriteFloat( fly_bob_strength ); + savefile->WriteFloat( fly_bob_vert ); + savefile->WriteFloat( fly_bob_horz ); + savefile->WriteInt( fly_offset ); + savefile->WriteFloat( fly_seek_scale ); + savefile->WriteFloat( fly_roll_scale ); + savefile->WriteFloat( fly_roll_max ); + savefile->WriteFloat( fly_roll ); + savefile->WriteFloat( fly_pitch_scale ); + savefile->WriteFloat( fly_pitch_max ); + savefile->WriteFloat( fly_pitch ); + + savefile->WriteInt ( (int) currentDirection ); + savefile->WriteInt ( (int) idealDirection ); + savefile->WriteFloat( walkRange ); + savefile->WriteFloat( walkTurn ); + savefile->WriteVec2 ( followRange ); + savefile->WriteVec2 ( searchRange ); + savefile->WriteFloat( attackPositionRange ); + savefile->WriteFloat( turnDelta ); + + savefile->WriteVec3 ( goalPos ); + savefile->WriteInt ( goalArea ); + goalEntity.Save( savefile ); + savefile->WriteVec3( goalEntityOrigin ); + + savefile->WriteVec3( myPos ); // cnicholson: Added unsaved var + savefile->WriteInt( myArea ); // cnicholson: Added unsaved var + + savefile->WriteVec3 ( seekPos ); + + savefile->WriteInt( (int) MAX_PATH_LEN ); // cnicholson: Added unsaved vars + for (i=0; i< MAX_PATH_LEN; ++i) { + // TOSAVE: idReachability* reach; + savefile->WriteVec3( path[i].seekPos ); + } + + savefile->WriteInt( pathLen ); // cnicholson: Added unsaved var + savefile->WriteInt( pathArea ); // cnicholson: Added unsaved var + savefile->WriteInt( pathTime ); // cnicholson: Added unsaved var + + savefile->WriteVec3( addVelocity ); +} + +/* +===================== +idMoveState::Restore +===================== +*/ +void idMoveState::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->Read ( &fl, sizeof(fl) ); + + savefile->ReadInt( (int&)moveType ); + savefile->ReadInt( (int&)moveCommand ); + savefile->ReadInt( (int&)moveStatus ); + savefile->ReadVec3( moveDest ); + savefile->ReadVec3( moveDir ); + + savefile->ReadInt( toAreaNum ); + savefile->ReadInt( startTime ); + savefile->ReadInt( duration ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( range ); + savefile->ReadFloat( wanderYaw ); + savefile->ReadInt( nextWanderTime ); + savefile->ReadInt( blockTime ); + obstacle.Restore ( savefile ); + savefile->ReadVec3( lastMoveOrigin ); + savefile->ReadInt( lastMoveTime ); + savefile->ReadInt( anim ); + + savefile->ReadInt( travelFlags ); + + savefile->ReadFloat ( kickForce ); + savefile->ReadFloat ( blockedRadius ); + savefile->ReadInt ( blockedMoveTime ); + savefile->ReadInt ( blockedAttackTime ); + + savefile->ReadFloat( ideal_yaw ); + savefile->ReadFloat( current_yaw ); + savefile->ReadFloat( turnRate ); + savefile->ReadFloat( turnVel ); + savefile->ReadFloat( anim_turn_yaw ); + savefile->ReadFloat( anim_turn_amount ); + savefile->ReadFloat( anim_turn_angles ); + + savefile->ReadJoint( flyTiltJoint ); + savefile->ReadFloat( fly_speed ); + savefile->ReadFloat( fly_bob_strength ); + savefile->ReadFloat( fly_bob_vert ); + savefile->ReadFloat( fly_bob_horz ); + savefile->ReadInt( fly_offset ); + savefile->ReadFloat( fly_seek_scale ); + savefile->ReadFloat( fly_roll_scale ); + savefile->ReadFloat( fly_roll_max ); + savefile->ReadFloat( fly_roll ); + savefile->ReadFloat( fly_pitch_scale ); + savefile->ReadFloat( fly_pitch_max ); + savefile->ReadFloat( fly_pitch ); + + savefile->ReadInt ( (int&) currentDirection ); + savefile->ReadInt ( (int&) idealDirection ); + savefile->ReadFloat( walkRange ); + savefile->ReadFloat( walkTurn ); + savefile->ReadVec2 ( followRange ); + savefile->ReadVec2 ( searchRange ); + savefile->ReadFloat( attackPositionRange ); + savefile->ReadFloat( turnDelta ); + + savefile->ReadVec3 ( goalPos ); + savefile->ReadInt ( goalArea ); + goalEntity.Restore ( savefile ); + savefile->ReadVec3( goalEntityOrigin ); + + savefile->ReadVec3( myPos ); // cnicholson: Added unrestored var + savefile->ReadInt( myArea ); // cnicholson: Added unrestored var + + savefile->ReadVec3 ( seekPos ); + + savefile->ReadInt( num ); + for (i=0; i< num; ++i) { + // TOSAVE: idReachability* reach; + savefile->ReadVec3( path[i].seekPos ); + } + + savefile->ReadInt( pathLen ); // cnicholson: Added unrestored var + savefile->ReadInt( pathArea ); // cnicholson: Added unrestored var + savefile->ReadInt( pathTime ); // cnicholson: Added unrestored var + + savefile->ReadVec3( addVelocity ); +} + + +/* +============ +idAI::SetMoveType +============ +*/ +void idAI::SetMoveType ( moveType_t moveType ) { + if ( move.moveType == moveType ) { + return; + } + + StopSound ( SND_CHANNEL_HEART, false ); + + switch ( moveType ) { + case MOVETYPE_STATIC: + move.travelFlags = 0; + break; + + case MOVETYPE_FLY: + move.travelFlags = TFL_FLY|TFL_WALK|TFL_AIR; + StartSound ( "snd_fly", SND_CHANNEL_HEART, 0, false, NULL ); + break; + + case MOVETYPE_ANIM: + move.travelFlags = TFL_WALK|TFL_AIR; + break; + + case MOVETYPE_CUSTOM: + move.travelFlags = TFL_WALK|TFL_AIR|TFL_WALKOFFLEDGE; + break; + } + + move.moveType = moveType; +} + +/* +===================== +idAI::Event_SaveMove +===================== +*/ +void idAI::Event_SaveMove( void ) { + savedMove = move; +} + +/* +===================== +idAI::Event_RestoreMove +===================== +*/ +void idAI::Event_RestoreMove( void ) { + idVec3 dest; + + switch( savedMove.moveCommand ) { + case MOVE_NONE : + StopMove( savedMove.moveStatus ); + break; + + case MOVE_FACE_ENEMY : + FaceEnemy(); + break; + + case MOVE_FACE_ENTITY : + FaceEntity( savedMove.goalEntity.GetEntity() ); + break; + + case MOVE_TO_ENEMY : + MoveToEnemy(); + break; + + case MOVE_TO_ENTITY : + MoveToEntity( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_OUT_OF_RANGE : + MoveOutOfRange( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_TO_ATTACK: + MoveToAttack ( savedMove.goalEntity.GetEntity(), savedMove.anim ); + break; + + case MOVE_TO_COVER : + MoveToCover ( combat.attackRange[0], combat.attackRange[1], AITACTICAL_COVER ); + break; + + case MOVE_TO_POSITION : + MoveTo ( savedMove.moveDest, savedMove.range ); + break; + + case MOVE_SLIDE_TO_POSITION : + SlideToPosition( savedMove.moveDest, savedMove.duration ); + break; + + case MOVE_WANDER : + WanderAround(); + break; + } + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, dest ); + } +} + +/* +============ +idAI::KickObstacles +============ +*/ +void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) { + int i, numListedClipModels; + idBounds clipBounds; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + int clipmask; + idVec3 org; + idVec3 forceVec; + idVec3 delta; + idVec2 perpendicular; + + org = physicsObj.GetOrigin(); + + // find all possible obstacles + clipBounds = physicsObj.GetAbsBounds(); + clipBounds.TranslateSelf( dir * 32.0f ); + clipBounds.ExpandSelf( 8.0f ); + clipBounds.AddPoint( org ); + clipmask = physicsObj.GetClipMask(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( this, clipBounds, clipmask, clipModelList, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < numListedClipModels; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + if ( obEnt == alwaysKick ) { + // we'll kick this one outside the loop + continue; + } + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if (( obEnt->IsType( idMoveable::GetClassType() ) || obEnt->IsType( idAFAttachment::GetClassType() )) && obEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + delta = obEnt->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * obEnt->GetPhysics()->GetMass(); + obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec ); + } + } + + if ( alwaysKick ) { + delta = alwaysKick->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * alwaysKick->GetPhysics()->GetMass(); + alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec ); + } + +} + +/* +============ +ValidForBounds +============ +*/ +bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) { + return false; + } + if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) { + return false; + } + } + return true; +} + +/* +===================== +idAI::SetAAS +===================== +*/ +void idAI::SetAAS( void ) { + idStr use_aas; + + spawnArgs.GetString( "use_aas", NULL, use_aas ); + if ( !use_aas || !use_aas[0] ) { + //don't intend to use AAS at all? + //no warning - lack of AAS intentional + aas = NULL; + return; + } + aas = gameLocal.GetAAS( use_aas ); + if ( aas ) { + const idAASSettings *settings = aas->GetSettings(); + if ( settings ) { + if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) { + gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() ); + } + float height = settings->maxStepHeight; + physicsObj.SetMaxStepHeight( height ); + return; + } else { + aas = NULL; + } + } + gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() ); +} + +/* +===================== +idAI::ReachedPos +===================== +*/ +bool idAI::ReachedPos( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range ) const { + // When moving towards the enemy just see if our bounding box touches the desination + if ( moveCommand == MOVE_TO_ENEMY ) { + if ( !enemy.ent || physicsObj.GetAbsBounds().IntersectsBounds( enemy.ent->GetPhysics()->GetAbsBounds().Expand( range ) ) ) { + return true; + } + return false; + } + + // Dont add vertical bias when using fly move + if ( move.moveType == MOVETYPE_FLY ) { + float offset; + if ( moveCommand == MOVE_TO_ENTITY ) { + offset = 0.0f; + } else { + offset = move.fly_offset; + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-range), idVec3(range,range,range+offset) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + + if ( moveCommand == MOVE_TO_TETHER ) + {//if you're not actually in the tether, we don't want to stop early (for compliance with ai_trigger condition_tether) + if ( !IsWithinTether() ) + { + return false; + } + } + + // Excluded z height when determining reached + if ( move.toAreaNum > 0 ) { + if ( PointReachableAreaNum( physicsObj.GetOrigin() ) == move.toAreaNum ) { + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-4096.0f), idVec3(range,range,4096.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-16.0f), idVec3(range,range,64.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); +} + +/* +===================== +idAI::PointReachableAreaNum +===================== +*/ +int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const { + int areaNum; + idVec3 size; + idBounds bounds; + + if ( !aas ) { + return 0; + } + + size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + if ( move.moveType == MOVETYPE_FLY ) { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ); + } else { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + } + + return areaNum; +} + +/* +===================== +idAI::PathToGoal +===================== +*/ +bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + idVec3 org; + idVec3 goal; + + if ( !aas ) { + return false; + } + + org = origin; + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !areaNum ) { + return false; + } + + goal = goalOrigin; + aas->PushPointIntoAreaNum( goalAreaNum, goal ); + if ( !goalAreaNum ) { + return false; + } + //push goal back up if flying to a point way above an area + if ( move.moveType == MOVETYPE_FLY ) { + if ( areaNum == goalAreaNum ) { + if ( goalOrigin.z > goal.z ) { + float areaTop = aas->AreaCeiling( goalAreaNum ) - GetPhysics()->GetBounds()[1].z; + if ( goalOrigin.z > areaTop ) { + //crap, cap it... + goal.z = areaTop; + } else { + goal.z = goalOrigin.z; + } + } + } + } + + + if ( move.moveType == MOVETYPE_FLY ) { + return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } else { + return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } +} + +/* +===================== +idAI::TravelDistance + +Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance. + +This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you +are from the goal, so try to break the goals up into shorter distances. +===================== +*/ +float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const { + int fromArea; + int toArea; + float dist; + idVec2 delta; + aasPath_t path; + + if ( !aas ) { + // no aas, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + fromArea = PointReachableAreaNum( start ); + toArea = PointReachableAreaNum( end ); + + if ( !fromArea || !toArea ) { + // can't seem to get there + return -1; + } + + if ( fromArea == toArea ) { + // same area, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + idReachability *reach; + int travelTime; + if ( !aas->RouteToGoalArea( fromArea, start, toArea, move.travelFlags, travelTime, &reach ) ) { + return -1; + } + + if ( DebugFilter(ai_debugMove) ) { // Travel Distance, Fly Path & Walk Path + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( start, toArea, end ); + } else { + aas->ShowWalkPath( start, toArea, end ); + } + } + + return travelTime; +} + +float idAI::TravelDistance ( idEntity *ent ) const { + return TravelDistance ( physicsObj.GetOrigin(), ent->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( idEntity* start, idEntity* end ) const { + assert( start ); + assert( end ); + return TravelDistance( start->GetPhysics()->GetOrigin(), end->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( const idVec3 &pos ) const { + return TravelDistance( physicsObj.GetOrigin(), pos ); +} + +/* +============ +idAI::ScriptedPlaybackMove +============ +*/ +void idAI::ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + // Start the playback + if ( !mPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ) ) { + ScriptedEnd ( ); + return; + } + + move.goalEntity = NULL; + move.moveCommand = MOVE_RV_PLAYBACK; + move.moveType = MOVETYPE_PLAYBACK; + move.fl.done = false; + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +===================== +idAI::FaceEnemy + +Continually face the enemy's last known position. MoveDone is always true in this case. +===================== +*/ +bool idAI::FaceEnemy( void ) { + idEntity *enemyEnt = enemy.ent; + if ( !enemyEnt ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + TurnToward( enemy.lastKnownPosition ); + move.goalEntity = enemyEnt; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENEMY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::FaceEntity + +Continually face the entity position. MoveDone will never be true in this case. +===================== +*/ +bool idAI::FaceEntity( idEntity *ent ) { + if ( !ent ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + idVec3 entityOrg = ent->GetPhysics()->GetOrigin(); + TurnToward( entityOrg ); + move.goalEntity = ent; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENTITY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = false; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::StartMove + +Initialize a new movement by setting up the movement structure +===================== +*/ +bool idAI::StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ) { + // If we are already there then we are done + if ( ReachedPos( goalOrigin, command ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.lastMoveOrigin = physicsObj.GetOrigin ( ); + + move.seekPos = move.goalPos = move.moveDest = goalOrigin; + move.toAreaNum = goalArea; + move.goalEntity = goalEntity; + move.moveCommand = command; + move.moveStatus = MOVE_STATUS_MOVING; + move.speed = move.fly_speed; + move.startTime = gameLocal.time; + move.range = range; + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = true; + + aasSensor->Reserve ( feature ); + + OnStartMoving ( ); + + return true; +} + +/* +===================== +idAI::StopMove +===================== +*/ +void idAI::StopMove( moveStatus_t status ) { + aiMoveCommand_t oldCommand = move.moveCommand; + float saveZ = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + move.fl.obstacleInPath = false; + move.fl.blocked = false; + + if ( move.moveType == MOVETYPE_FLY ) { + if ( move.moveCommand == MOVE_TO_ENEMY + || move.moveCommand == MOVE_TO_ATTACK ) { + saveZ = (move.moveDest.z-physicsObj.GetOrigin().z); + } + } + move.moveCommand = MOVE_NONE; + move.moveStatus = status; + move.toAreaNum = 0; + move.goalEntity = NULL; + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += saveZ; + move.startTime = gameLocal.time; + move.duration = 0; + move.range = 0.0f; + move.speed = 0.0f; + move.anim = 0; + + move.moveDir.Zero(); + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + + // Callback for handling stopping + if ( oldCommand != MOVE_NONE ) { + OnStopMoving ( oldCommand ); + } +} + +/* +===================== +idAI::MoveToTether +===================== +*/ +bool idAI::MoveToTether ( rvAITether* tether ) { + aasGoal_t goal; + + // find a goal using the currently active tether + if ( !aas || !tether ) { + return false; + } + + if ( !tether->FindGoal ( this, goal ) ) { + //This is extremely bad - if 2 guys both try to get to the center of a tether, they get hosed. + return MoveTo ( tether->GetPhysics()->GetOrigin ( ), tether->GetOriginReachedRange() ); + } + + if ( move.moveType == MOVETYPE_FLY ) { + //Hmm... we shouldn't have to clamp as the area wouldn't have been valid unless the tether was in its bounds and height, but...? + //float areaTop = aas->AreaCeiling( goal.areaNum ); + goal.origin.z = tether->GetPhysics()->GetOrigin().z; + } + + return StartMove ( MOVE_TO_TETHER, goal.origin, goal.areaNum, NULL, NULL, AI_TETHER_MINRANGE ); +} + +/* +===================== +idAI::MoveToAttack +===================== +*/ +bool idAI::MoveToAttack ( idEntity *ent, int attack_anim ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( ent ); + + // if we havent started a find yet or the current find is something other than a attack find then start a new one + if ( !aasFind || !dynamic_cast(aasFind) ) { + // Allocate the new aas find + delete aasFind; + aasFind = new rvAASFindGoalForAttack ( this ); + + // Find a goal that gives us a viable attack position on our enemy + aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, IsTethered()?0:move.searchRange[0], move.searchRange[1], &obstacle, 1, *aasFind ); + } + + assert ( aasFind ); + + // Test some more points with the existing find + rvAASFindGoalForAttack* aasFindAttack = static_cast(aasFind); + if ( !aasFindAttack->TestCachedGoals ( aifl.simpleThink ? 1 : 4, goal ) ) { + delete aasFind; + aasFind = NULL; + return false; + } + + // Havent found a goal yet but exhaused our trace count + if ( !goal.areaNum ) { + return false; + } + + // Dont need the find anymore + delete aasFind; + aasFind = NULL; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(goal.areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( pos.z > areaTop ) { + goal.origin.z = areaTop; + } else if ( pos.z > goal.origin.z+1.0f ) { + goal.origin.z = pos.z; + } else if ( move.fly_offset > 0 ) { + if ( (goal.origin.z+move.fly_offset) > areaTop ) { + goal.origin.z = areaTop; + } else { + goal.origin.z += move.fly_offset; + } + } + } + } + + return StartMove ( MOVE_TO_ATTACK, goal.origin, goal.areaNum, ent, NULL, move.attackPositionRange?move.attackPositionRange:8.0f ); +} + +/* +===================== +idAI::MoveToEnemy +===================== +*/ +bool idAI::MoveToEnemy( void ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !enemy.ent ) { + return false; + } + +// pos = LastKnownPosition ( enemy.ent ); + pos = enemy.ent->GetPhysics()->GetOrigin ( ); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent && move.goalEntityOrigin == pos ) { + return true; + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENEMY, 8.0f ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = pos; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( move.fly_offset > 0 ) { + if ( (pos.z+move.fly_offset) > areaTop ) { + pos.z = areaTop; + move.goalEntityOrigin.z = areaTop; + } else { + pos.z += move.fly_offset; + move.goalEntityOrigin.z += move.fly_offset; + } + } + } + } + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + move.moveDest = pos; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENEMY, pos, areaNum, enemy.ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveToEntity +===================== +*/ +bool idAI::MoveToEntity( idEntity *ent, float range ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !ent ) { + return false; + } + + // Where do we want to go? + pos = ent->GetPhysics()->GetOrigin(); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent && move.goalEntityOrigin == pos ) { + return true; + } + + // If we arent flying we should move to a position on the floor + if ( move.moveType != MOVETYPE_FLY ) { + ent->GetFloorPos( 64.0f, pos ); + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENTITY, range ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = ent->GetPhysics()->GetOrigin(); + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent ) { + move.moveDest = pos; + move.range = range <= 0.0f ? 8.0f : range; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENTITY, pos, areaNum, ent, NULL, range <= 0.0f ? 8.0f : range ); +} + +/* +===================== +idAI::MoveOutOfRange +===================== +*/ +bool idAI::MoveOutOfRange( idEntity *ent, float range, float minRange ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + int obstacles; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + // consider the entity the monster is getting close to as an obstacle + if ( ent != this ) { + obstacles = 1; + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + } else { + obstacles = 0; + } + + pos = LastKnownPosition ( ent ); + + // Find a goal out of range of where we are + rvAASFindGoalOutOfRange findGoal( this ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, minRange, range, &obstacle, obstacles, findGoal ) ) { + return false; + } + + return StartMove ( MOVE_OUT_OF_RANGE, goal.origin, goal.areaNum, ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveTo +===================== +*/ +bool idAI::MoveTo ( const idVec3 &pos, float range ) { + idVec3 org; + int areaNum; + aasPath_t path; + + if ( !aas ) { + return false; + } + + org = pos; + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // Can we get to where we want to go? + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), PointReachableAreaNum( physicsObj.GetOrigin() ), org ) ) { + return false; + } + + // Start moving + return StartMove ( MOVE_TO_POSITION, org, areaNum, NULL, NULL, range ); +} + +/* +===================== +idAI::MoveToCover +===================== +*/ +bool idAI::MoveToCover( float minRange, float maxRange, aiTactical_t coverType ) { + idVec3 org; + int areaNum; + aasPath_t path; + aasFeature_t* feature = 0; + idVec3 featureOrigin; + + if ( !aas ) { + return false; + } + + // Look for nearby cover + switch ( coverType ) { + case AITACTICAL_HIDE: aasSensor->SearchHide(); break; + case AITACTICAL_COVER_FLANK: aasSensor->SearchFlank(); break; + case AITACTICAL_COVER_ADVANCE: aasSensor->SearchAdvance(); break; + case AITACTICAL_COVER_RETREAT: aasSensor->SearchRetreat(); break; + case AITACTICAL_COVER_AMBUSH: aasSensor->SearchAmbush(); break; + case AITACTICAL_COVER: + default: + aasSensor->SearchCover(); + break; + } + + if ( !aasSensor->FeatureCount ( ) ) { + return false; + } + + feature = aasSensor->Feature ( 0 ); + featureOrigin = feature->Origin ( ); + org = featureOrigin; + areaNum = 0; + + // Find the aas area our cover point is in + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // See if there is a path to our goal or not + aas->PushPointIntoAreaNum( areaNum, org ); +/* + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, org ) ) { + return false; + } +*/ + + combat.coverValidTime = gameLocal.time; + + // Start the move + return StartMove ( MOVE_TO_COVER, org, areaNum, enemy.ent, feature, AI_COVER_MINRANGE / 2.0f ); +} + +/* +===================== +idAI::MoveToHide +===================== +*/ +bool idAI::MoveToHide ( void ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + // Need an enemy to hide from + if ( !aas || !enemy.ent ) { + return false; + } + + const idVec3& org = physicsObj.GetOrigin(); + obstacle.absBounds = enemy.ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( enemy.ent ); + + // Search aas for a suitable goal + rvAASFindGoalForHide findGoal( pos ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, 0.0f, 0.0f, &obstacle, 1, findGoal ) ) { + return false; + } + + // Start the movement + return StartMove ( MOVE_TO_HIDE, goal.origin, goal.areaNum, enemy.ent, NULL, 0.0f ); +} + +/* +===================== +idAI::SlideToPosition +===================== +*/ +bool idAI::SlideToPosition( const idVec3 &pos, float time ) { + StopMove( MOVE_STATUS_DONE ); + + move.moveDest = pos; + move.goalEntity = NULL; + move.moveCommand = MOVE_SLIDE_TO_POSITION; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = false; + aifl.simpleThink = false; + + move.fl.allowAnimMove = false; + + if ( move.duration > 0 ) { + move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration ); + if ( move.moveType != MOVETYPE_FLY ) { + move.moveDir.z = 0.0f; + } + move.speed = move.moveDir.LengthFast(); + } + + return true; +} + +/* +===================== +idAI::WanderAround +===================== +*/ +bool idAI::WanderAround( void ) { + idVec3 dest; + + StopMove( MOVE_STATUS_DONE ); + + dest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + if ( !NewWanderDir( dest ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + return false; + } + + return StartMove ( MOVE_WANDER, dest, 0, NULL, NULL, 0.0f ); +} + +/* +================ +idAI::StepDirection +================ +*/ +bool idAI::StepDirection( float dir ) { + predictedPath_t path; + idVec3 org; + + move.wanderYaw = dir; + move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward(); + + org = physicsObj.GetOrigin(); + + idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) { + // don't report being blocked if we ran into our goal entity + return true; + } + + if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) { + float z; + + move.moveDir = path.endVelocity * 1.0f / 48.0f; + + // trace down to the floor and see if we can go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 floorPos = path.endPos; + idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = -1.0f; + return true; + } + + // trace up to see if we can go over something and go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 ceilingPos = path.endPos; + + for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) { + idVec3 start; + if ( z <= ceilingPos.z ) { + start.x = org.x; + start.y = org.y; + start.z = z; + } else { + start = ceilingPos; + } + idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = 1.0f; + return true; + } + } + return false; + } + + return ( path.endEvent == 0 ); +} + +/* +================ +idAI::NewWanderDir +================ +*/ +bool idAI::NewWanderDir( const idVec3 &dest ) { + float deltax, deltay; + float d[ 3 ]; + float tdir, olddir, turnaround; + + move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 ); + + olddir = idMath::AngleNormalize360( ( int )( move.current_yaw / 45 ) * 45 ); + turnaround = idMath::AngleNormalize360( olddir - 180 ); + + idVec3 org = physicsObj.GetOrigin(); + deltax = dest.x - org.x; + deltay = dest.y - org.y; + if ( deltax > 10 ) { + d[ 1 ]= 0; + } else if ( deltax < -10 ) { + d[ 1 ] = 180; + } else { + d[ 1 ] = DI_NODIR; + } + + if ( deltay < -10 ) { + d[ 2 ] = 270; + } else if ( deltay > 10 ) { + d[ 2 ] = 90; + } else { + d[ 2 ] = DI_NODIR; + } + + // try direct route + if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) { + if ( d[ 1 ] == 0 ) { + tdir = d[ 2 ] == 90 ? 45 : 315; + } else { + tdir = d[ 2 ] == 90 ? 135 : 215; + } + + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + + // try other directions + if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) { + tdir = d[ 1 ]; + d[ 1 ] = d[ 2 ]; + d[ 2 ] = tdir; + } + + if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) { + return true; + } + + if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) { + return true; + } + + // there is no direct path to the player, so pick another direction + if ( olddir != DI_NODIR && StepDirection( olddir ) ) { + return true; + } + + // randomly determine direction of search + if ( gameLocal.random.RandomInt() & 1 ) { + for( tdir = 0; tdir <= 315; tdir += 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } else { + for ( tdir = 315; tdir >= 0; tdir -= 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } + + if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) { + return true; + } + + // can't move + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; +} + +/* +===================== +idAI::GetMovePos +===================== +*/ +bool idAI::GetMovePos( idVec3 &seekPos, idReachability** seekReach ) { + int areaNum; + aasPath_t path; + bool result; + idVec3 org; + + org = physicsObj.GetOrigin(); + seekPos = org; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = 0; + } + // RAVEN END + + switch( move.moveCommand ) { + case MOVE_NONE : + seekPos = move.moveDest; + return false; + + case MOVE_FACE_ENEMY : + case MOVE_FACE_ENTITY : + seekPos = move.moveDest; + return false; + + case MOVE_TO_POSITION_DIRECT : + seekPos = move.moveDest; + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + return false; + + case MOVE_SLIDE_TO_POSITION : + seekPos = org; + return false; + + case MOVE_TO_ENTITY: + MoveToEntity( move.goalEntity.GetEntity(), move.range ); + break; + + case MOVE_TO_ENEMY: + if ( !MoveToEnemy() && combat.tacticalCurrent == AITACTICAL_MELEE ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; + } + break; + } + + if ( move.moveType == MOVETYPE_FLY && move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + //flying + if ( DistanceTo( move.moveDest ) < 1024.0f ) { + //less than huge translation dist, which is actually 4096, but, just to be safe... + trace_t moveTrace; + gameLocal.Translation( this, moveTrace, org, move.moveDest, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this, move.goalEntity.GetEntity() ); + if ( moveTrace.fraction >= 1.0f ) { + //can head straight for it, so do it. + seekPos = move.moveDest; + return false; + } + } + } + + move.moveStatus = MOVE_STATUS_MOVING; + result = false; + + if ( move.moveCommand == MOVE_WANDER ) { + move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + } else { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + seekPos = org; + return false; + } + } + + if ( aas && move.toAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) { + seekPos = path.moveGoal; + if ( aas->GetFile() ) { + //we have AAS + if ( move.fly_offset > 0 ) { + if ( (seekPos-move.moveDest).LengthSqr() > 10.0f ) { + //not heading to final dest, that already has offset in it + float areaTop = aas->AreaCeiling(path.moveAreaNum)-GetPhysics()->GetBounds()[1][2]; + if ( (seekPos.z+move.fly_offset) > areaTop ) { + seekPos.z = areaTop; + } else { + seekPos.z += move.fly_offset; + } + } + } + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = (idReachability*)(path.reachability); + } + // RAVEN END + + result = true; + move.nextWanderTime = 0; + } else { + move.fl.goalUnreachable = true; + } + } + + + if ( !result ) { + // wander around + if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) { + result = NewWanderDir( move.moveDest ); + if ( !result ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + seekPos = org; + + return false; + } + } else { + result = true; + } + + seekPos = org + move.moveDir * 2048.0f; + + } else { + move.fl.goalUnreachable = false; + } + + if ( DebugFilter(ai_debugMove) ) { // YELLOW = seekPos + gameRenderWorld->DebugLine( colorYellow, physicsObj.GetOrigin(), seekPos ); + } + + return result; +} + + + +/* +===================== +idAI::BlockedFailSafe +===================== +*/ +void idAI::BlockedFailSafe( void ) { +/* move.fl.blocked = false; + + if ( !ai_blockedFailSafe.GetBool() || move.blockedRadius < 0.0f ) { + return; + } + if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL || + ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( move.blockedRadius ) ) { + move.lastMoveOrigin = physicsObj.GetOrigin(); + move.lastMoveTime = gameLocal.time; + } + if ( move.lastMoveTime < gameLocal.time - move.blockedMoveTime ) { + if ( lastAttackTime < gameLocal.time - move.blockedAttackTime ) { + move.fl.blocked = true; + move.lastMoveTime = gameLocal.time; + } + }*/ +} + +/*********************************************************************** + + turning + +***********************************************************************/ + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( float yaw ) { + move.ideal_yaw = idMath::AngleNormalize180( yaw ); + bool result = FacingIdeal(); + return result; +} + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( const idVec3 &pos ) { + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + + dir = pos - physicsObj.GetOrigin(); + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.ent == NULL ) ) { + move.ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + } + + return FacingIdeal(); +} + +/* +===================== +idAI::TurnTowardLeader +===================== +*/ +bool idAI::TurnTowardLeader( bool faceLeaderByDefault ) { + if ( !leader.GetEntity() ) { + return false; + } + //see if there's a wall in the direction the player's looking + trace_t tr; + + idVec3 leaderLookDest = leader->GetPhysics()->GetOrigin() + (( (leader.GetEntity() && leader.GetEntity()->IsType( idPlayer::GetClassType() )) ? ((idPlayer*)leader.GetEntity())->intentDir:leader->viewAxis[0])*300.0f); + idVec3 myLookDir = leaderLookDest-GetPhysics()->GetOrigin(); + myLookDir.Normalize(); + idVec3 start = GetPhysics()->GetOrigin(); + start.z += EyeHeight(); + idVec3 end = start + (myLookDir*128.0f); + end.z = start.z; + idVec3 currentLookDir = viewAxis[0]; + currentLookDir.Normalize(); + + if ( !GetEnemy() && (!leader->IsType(idPlayer::GetClassType()) || !((idPlayer*)leader.GetEntity())->IsFlashlightOn()) ) { + //Not in combat and leader isn't looking around with flashlight + if ( myLookDir*currentLookDir > 0.666f ) { + //new dir isn't different enough from current dir for me to care + return true; + } + } + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + idEntity* traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //wall there - NOTE: okay to look at doors + if ( faceLeaderByDefault//want to face leader by default + || leader->viewAxis[0].ToYaw() == move.ideal_yaw ) {//a wall must have moved in front of us? + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + //just keep looking in the last valid dir + if ( FacingIdeal() ) { + //make sure it's still valid + idVec3 end = start + (viewAxis[0]*128.0f); + end.z = start.z; + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //a wall right in front of us - NOTE: okay to look at doors + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + } + return true; + } + //opening, face leader's dir + return TurnToward( myLookDir.ToYaw() ); +} + +/* +============ +idAI::DirectionalTurnToward + +Turn toward the given point using directional movement +============ +*/ +bool idAI::DirectionalTurnToward ( const idVec3 &pos ) { + static float moveDirOffset [ MOVEDIR_MAX ] = { + 0.0f, 180.0f, -90.0f, 90.0f + }; + + // Issue standard TurnToward if we are not currently eligible for directional movement + if ( combat.tacticalCurrent != AITACTICAL_MOVE_PLAYERPUSH ) { + //always move directionally when getting out of the player's way + if( !combat.fl.aware || focusType < AIFOCUS_USE_DIRECTIONAL_MOVE || !move.fl.moving || move.moveCommand == MOVE_TO_ENEMY || !move.fl.allowDirectional ) { + move.idealDirection = MOVEDIR_FORWARD; + return TurnToward ( pos ); + } + } + + // Turn towards our desination + float moveYaw; + TurnToward ( pos ); + moveYaw = move.ideal_yaw; + + // Turn towards the goal entity and determine the angle difference + TurnToward ( currentFocusPos ); + + // Check for a direction change only when we can no longer see + // where we need to look and where we are looking is greater than 3/4ths the maximum look + if ( (pos - GetPhysics()->GetOrigin()).LengthFast ( ) > 8.0f ) + if ( !FacingIdeal ( ) ) + if ( fabs ( idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ) >= fabs(lookMax[YAW]) || + fabs ( idMath::AngleNormalize180 ( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) - move.current_yaw ) ) >= 80.0f ) { + + float diffYaw; + diffYaw = idMath::AngleNormalize180 ( move.ideal_yaw - moveYaw ); + + if ( diffYaw > -45.0f && diffYaw < 45.0f ) { + move.idealDirection = MOVEDIR_FORWARD; + } else if ( diffYaw < -135.0f || diffYaw > 135.0f ) { + move.idealDirection = MOVEDIR_BACKWARD; + } else if ( diffYaw < 0.0f ) { + move.idealDirection = MOVEDIR_LEFT; + } else { + move.idealDirection = MOVEDIR_RIGHT; + } + } + + return TurnToward( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) ); +} + +/* +===================== +idAI::Turn +===================== +*/ +void idAI::Turn( void ) { + float diff; + float diff2; + float turnAmount; + animFlags_t animflags; + + // If cant turn or turning is disabled just bail + if ( !CanTurn ( ) ) { + return; + } + + // check if the animator has marked this anim as non-turning + if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) { + animflags = legsAnim.GetAnimFlags(); + } else { + animflags = torsoAnim.GetAnimFlags(); + } + if ( animflags.ai_no_turn ) { + return; + } + + if ( move.anim_turn_angles && animflags.anim_turn ) { + idMat3 rotateAxis; + + // set the blend between no turn and full turn + float frac = move.anim_turn_amount / move.anim_turn_angles; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac ); + + // get the total rotation from the start of the anim + animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis ); + move.current_yaw = idMath::AngleNormalize180( move.anim_turn_yaw + rotateAxis[ 0 ].ToYaw() ); + } else { + diff = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( move.currentDirection != move.idealDirection ) { + move.turnVel += (AI_TURN_SCALE * 2.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.running ) { + move.turnVel += (AI_TURN_SCALE * 3.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.moving ) { + move.turnVel += (AI_TURN_SCALE * 1.5f)* diff * MS2SEC( gameLocal.msec ); + } else { + move.turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec ); + } + + if ( move.turnVel > move.turnRate ) { + move.turnVel = move.turnRate; + } else if ( move.turnVel < -move.turnRate ) { + move.turnVel = -move.turnRate; + } + turnAmount = move.turnVel * MS2SEC( gameLocal.msec ); + if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } + move.current_yaw = idMath::AngleNormalize180( move.current_yaw + turnAmount ); + diff2 = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( idMath::Fabs( diff2 ) < 0.1f ) { + move.current_yaw = move.ideal_yaw; + } + } + + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + +// if ( DebugFilter(ai_debugMove) ) { // RED = ideal_yaw, GREEN = current_yaw, YELLOW = current+velocity +// const idVec3 &org = physicsObj.GetOrigin(); +// gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, move.ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, move.current_yaw, 0 ).ToForward() * 48, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, move.current_yaw + move.turnVel, 0 ).ToForward() * 32, gameLocal.msec ); +// if ( move.anim_turn_angles && animflags.anim_turn ) { +// gameRenderWorld->DebugLine( colorOrange, org, org + idAngles( 0, move.anim_turn_yaw, 0 ).ToForward() * 32, gameLocal.msec ); +// } +// } +} + +/* +===================== +idAI::FacingIdeal +===================== +*/ +bool idAI::FacingIdeal( void ) { + float diff; + + if ( !move.turnRate ) { + return true; + } + + diff = idMath::AngleDelta ( move.current_yaw, move.ideal_yaw ); + if ( idMath::Fabs( diff ) < 0.01f ) { + // force it to be exact + move.current_yaw = move.ideal_yaw; + return true; + } + + return false; +} + +/* +================ +idAI::AnimTurn +================ +*/ +void idAI::AnimTurn ( float angles, bool force ) { + move.turnVel = 0.0f; + move.anim_turn_angles = angles; + if ( angles ) { + move.anim_turn_yaw = move.current_yaw; + + if ( force ) { + move.anim_turn_amount = angles; + } else { + move.anim_turn_amount = idMath::Fabs( idMath::AngleNormalize180( move.current_yaw - move.ideal_yaw ) ); + if ( move.anim_turn_amount > move.anim_turn_angles ) { + move.anim_turn_amount = move.anim_turn_angles; + } + } + } else { + move.anim_turn_amount = 0.0f; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, 0.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, 0.0f ); + } +} + +/*********************************************************************** + + Movement Helper Functions + +***********************************************************************/ + +/* +================ +idAI::ApplyImpulse +================ +*/ +void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + // FIXME: Jim take a look at this and see if this is a reasonable thing to do + // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 ) + // and we don't want him taking physics impulses as it can knock him off the path + if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) { + idActor::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +===================== +idAI::GetAnimMoveDelta +===================== +*/ +void idAI::GetAnimMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) { + idVec3 oldModelOrigin; + idVec3 modelOrigin; + + animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta ); + delta = axis * delta; + + if ( modelOffset != vec3_zero ) { + // the pivot of the monster's model is around its origin, and not around the bounding + // box's origin, so we have to compensate for this when the model is offset so that + // the monster still appears to rotate around it's origin. + oldModelOrigin = modelOffset * oldaxis; + modelOrigin = modelOffset * axis; + delta += oldModelOrigin - modelOrigin; + } + + delta *= physicsObj.GetGravityAxis(); +} + + + +/* +===================== +TestTeammateCollisions +===================== +*/ + +void TestTeammateCollisions(idAI* owner) { + idActor* teammate; + idVec3 teammateDirection; + idVec3 teammateVelocity; + float teammateDirectionDot; + float teammateDistance; + float teammateSpeed; + const idVec3& myOrigin = owner->GetPhysics()->GetOrigin(); + const idBounds& myBounds = owner->GetPhysics()->GetBounds(); + idVec3 myVelocity = owner->GetPhysics()->GetLinearVelocity(); + float mySpeed = myVelocity.NormalizeFast(); + + // Don't Bother, We're Blocked Or Not Moving + //--------------------------------------------- + if (owner->move.blockTime>gameLocal.GetTime() || !owner->move.fl.moving || mySpeed<1.0f) { + return; + } + + + for (teammate = aiManager.GetAllyTeam((aiTeam_t)owner->team); teammate; teammate = teammate->teamNode.Next()) { + if (teammate->fl.hidden || teammate == owner || teammate->health <= 0) { + continue; + } + + // If On Same Floor + //------------------ + teammateDirection = teammate->GetPhysics()->GetOrigin() - myOrigin; + if (fabsf(teammateDirection[2])>myBounds.Size().z) { + continue; + } + + // If Close Enough + //----------------- + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance>128.0f) { + continue; + } + + // Completely Ignore Dudes Directly Behind Me + //-------------------------------------------- + teammateDirectionDot = teammateDirection*myVelocity; + if (teammateDirectionDot<-0.5f) { + continue; + } + + // Switch To Walk If I'm Heading For A Teammate + //---------------------------------------------- + if (teammateDirectionDot>0.85f) { + owner->move.fl.obstacleInPath = true; // make him slow to a walk + + if ( owner->DebugFilter(ai_debugMove) ) { // WHITE = Walk Teammate Near + gameRenderWorld->DebugArrow( colorWhite, myOrigin, teammate->GetPhysics()->GetOrigin(), 4, 250 ); + } + } + + if (teammateDistance<48.0f) { + + teammateVelocity = teammate->GetPhysics()->GetLinearVelocity(); + teammateSpeed = teammateVelocity.NormalizeFast(); + + if (teammateSpeed>50.0f) { + + // If I'm Following Him, And I'm RIGHT Behind Him, Stop And Let Him Go A Bit Farther Ahead + //------------------------------------------------------------------------------------------ + if (teammateDirectionDot>0.85f && teammateVelocity*myVelocity>0.4f) { + owner->move.blockTime = gameLocal.time + 1000; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1000); + } + + } + + // Stop moving if my leader or a guy with a higher entity number is headed straight for me + //----------------------------------------------------------------------------------------- + else if (teammate->entityNumberentityNumber && teammateDirection*teammateVelocity<0.5f) { + owner->move.blockTime = gameLocal.time + 1500; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1500); + } + } + } + } + } +} + + + + + + +/* +===================== +idAI::CheckObstacleAvoidance +===================== +*/ +void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &seekPos, idReachability* goalReach ) { + + move.fl.blocked = false; // Makes Character Stop Moving + move.fl.obstacleInPath = false; // Makes Character Walk + move.obstacle = NULL; + seekPos = goalPos; + + if (move.fl.ignoreObstacles) { + return; + } + if ( g_perfTest_aiNoObstacleAvoid.GetBool() ) { + return; + } + + // Test For Path Around Obstacles + //-------------------------------- + obstaclePath_t path; + move.fl.blocked = !FindPathAroundObstacles( &physicsObj, aas, move.moveCommand == MOVE_TO_ENEMY ? enemy.ent : NULL, physicsObj.GetOrigin(), goalPos, path ); + move.fl.obstacleInPath = (path.firstObstacle || path.seekPosObstacle || path.startPosObstacle); + move.obstacle = (path.firstObstacle)?(path.firstObstacle):(path.seekPosObstacle); + seekPos = path.seekPos; + + // Don't Worry About Obstacles Out Of Walk Range + //----------------------------------------------- + if (move.obstacle && DistanceTo(move.obstacle)>155.0f) { + move.fl.blocked = false; + move.fl.obstacleInPath = false; + move.obstacle = 0; + seekPos = goalPos; + } + + // cdr: Alternate Routes Bug + // If An Obstacle Remains, And The Seek Pos Is Fairly Farr Off Of The Straight Line Path, Then Mark The Reach As Blocked + //----------------------------------------------------------------------------------------------------------------------- + if (move.fl.obstacleInPath && goalReach && !(goalReach->travelType&TFL_INVALID) && seekPos.Dist2XY(goalPos)>100.0f) { + float scale; + float dist; + + dist = seekPos.DistToLineSeg(physicsObj.GetOrigin(), goalPos, scale); + if (scale<0.95f && dist>50.0f) { + aiManager.MarkReachBlocked(aas, goalReach, path.allObstacles); + } + } + + TestTeammateCollisions(this); + + + + if ( DebugFilter(ai_showObstacleAvoidance) ) { + gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec ); + gameRenderWorld->DebugLine( !move.fl.blocked ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec ); + } +} + +/* +============ +idAI::TestAnimMove +============ +*/ +bool idAI::TestAnimMove ( int animNum, idEntity *ignore, idVec3 *pMoveVec ) { + const idAnim* anim; + predictedPath_t path; + idVec3 moveVec; + + anim = GetAnimator()->GetAnim ( animNum ); + assert ( anim ); + +// moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.current_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec / MS2SEC( anim->Length() ), anim->Length(), 200, SE_BLOCKED | SE_ENTER_LEDGE_AREA, path, ignore ); + + if ( DebugFilter(ai_debugMove) ) { // TestAnimMove + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + } + if ( pMoveVec ) { + *pMoveVec = moveVec; + } + + return ( path.endEvent == 0 ); +} + +/* +===================== +Seek +===================== +*/ +idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) { + idVec3 predictedPos; + idVec3 goalDelta; + idVec3 seekVel; + + // predict our position + predictedPos = org + vel * prediction; + goalDelta = goal - predictedPos; +// goalDelta = goal - org; + seekVel = goalDelta * MS2SEC( gameLocal.msec ); + + return seekVel; +} + +/*********************************************************************** + + Movement + +***********************************************************************/ + +/* +===================== +idAI::DeadMove +===================== +*/ +void idAI::DeadMove( void ) { + idVec3 delta; + monsterMoveResult_t moveResult; + + DeathPush ( ); + + idVec3 org = physicsObj.GetOrigin(); + + GetAnimMoveDelta( viewAxis, viewAxis, delta ); + physicsObj.SetDelta( delta ); + + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + move.fl.onGround = physicsObj.OnGround(); +} + +/* +===================== +idAI::AdjustFlyingAngles +===================== +*/ +void idAI::AdjustFlyingAngles( void ) { + idVec3 vel; + float speed; + float roll; + float pitch; + + vel = physicsObj.GetLinearVelocity(); + + speed = vel.Length(); + if ( speed < 5.0f ) { + roll = 0.0f; + pitch = 0.0f; + } else { + roll = vel * viewAxis[ 1 ] * -move.fly_roll_scale / move.fly_speed; + if ( roll > move.fly_roll_max ) { + roll = move.fly_roll_max; + } else if ( roll < -move.fly_roll_max ) { + roll = -move.fly_roll_max; + } + + pitch = vel * viewAxis[ 0 ] * -move.fly_pitch_scale / move.fly_speed; + if ( pitch > move.fly_pitch_max ) { + pitch = move.fly_pitch_max; + } else if ( pitch < -move.fly_pitch_max ) { + pitch = -move.fly_pitch_max; + } + } + + move.fly_roll = move.fly_roll * 0.95f + roll * 0.05f; + move.fly_pitch = move.fly_pitch * 0.95f + pitch * 0.05f; + + if ( move.flyTiltJoint != INVALID_JOINT ) { + animator.SetJointAxis( move.flyTiltJoint, JOINTMOD_WORLD, idAngles( move.fly_pitch, 0.0f, move.fly_roll ).ToMat3() ); + } else { + viewAxis = idAngles( move.fly_pitch, move.current_yaw, move.fly_roll ).ToMat3(); + } +} + +/* +===================== +idAI::AddFlyBob +===================== +*/ +void idAI::AddFlyBob( idVec3 &vel ) { + idVec3 fly_bob_add; + float t; + + if ( move.fly_bob_strength ) { + t = MS2SEC( gameLocal.time + entityNumber * 497 ); + fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * move.fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * move.fly_bob_vert ) ) * move.fly_bob_strength; + vel += fly_bob_add * MS2SEC( gameLocal.msec ); + if ( DebugFilter(ai_debugMove) ) { // FlyBob + const idVec3 &origin = physicsObj.GetOrigin(); + gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 ); + } + } +} + +/* +===================== +idAI::AdjustFlyHeight +===================== +*/ +void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) { + const idVec3 &origin = physicsObj.GetOrigin(); + predictedPath_t path; + idVec3 end; + idVec3 dest; + trace_t trace; + bool goLower; + + // make sure we're not flying too high to get through doors + // FIXME: with move to enemy this thinks we're going to hit + // the enemy and then says we need to go lower... but that's not quite right... + // if we're about to hit our enemy, then maintaining our height should be desirable..? + goLower = false; + if ( origin.z > goalPos.z ) { + dest = goalPos; + dest.z = origin.z + 128.0f; + idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path, move.goalEntity.GetEntity() ); + if ( path.endPos.z < origin.z ) { + + //Hmm, should we make sure the path.endPos is high enough off the ground? + if ( move.fly_offset && (move.moveCommand == MOVE_TO_ENEMY || move.moveCommand == MOVE_TO_ATTACK) ) { + int pathArea = PointReachableAreaNum( path.endPos ); + if ( aas && (aas->AreaFlags( pathArea )&AREA_FLOOR) ) { + path.endPos.z = aas->AreaBounds( pathArea )[0][2]; + float areaTop = aas->AreaCeiling( pathArea ) - GetPhysics()->GetBounds()[1].z; + if ( path.endPos.z + move.fly_offset > areaTop ) { + path.endPos.z = areaTop; + } else { + path.endPos.z += move.fly_offset; + } + } + } + + idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION ); + vel.z += addVel.z; + goLower = true; + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Height + gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + if ( !goLower ) { + // make sure we don't fly too low + end = origin; + if ( move.moveCommand == MOVE_TO_ENEMY ) { + end.z = enemy.lastKnownPosition.z + move.fly_offset; + } else if ( move.moveCommand == MOVE_TO_ENTITY ) { + end.z = goalPos.z; + } else { + end.z = goalPos.z;// + move.fly_offset; + } + +// RAVEN BEGIN +// ddynerman: multiple collision world + idVec3 cappedEnd = (end-origin); + if ( cappedEnd.LengthFast() > 1024.0f ) { + //don't do translation predictions over 1024 + cappedEnd.Normalize(); + cappedEnd *= 1024.0f; + } + cappedEnd += origin; + gameLocal.Translation( this, trace, origin, cappedEnd, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this ); +// RAVEN END + vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION ); + } +} + +/* +===================== +idAI::FlySeekGoal +===================== +*/ +void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) { + idVec3 seekVel; + + // seek the goal position + seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION ); + seekVel *= move.fly_seek_scale; + //seekVel.Normalize(); + //vel = seekVel*move.speed; + vel += seekVel; +} + +/* +===================== +idAI::AdjustFlySpeed +===================== +*/ +void idAI::AdjustFlySpeed( idVec3 &vel ) { + float goalSpeed; + + // Slow down movespeed when we close to goal (this is similar to how AnimMove ai will + // switch to walking when they are close, it allows for more fine control of movement) + if ( move.walkRange > 0.0f ) { + float distSqr; + distSqr = (physicsObj.GetOrigin ( ) - move.moveDest).LengthSqr ( ); + goalSpeed = move.speed * idMath::ClampFloat ( 0.1f, 1.0f, distSqr / Square ( move.walkRange ) ); + } else { + goalSpeed = move.speed; + } + + // apply dampening as long as we arent within the walk range +// if ( goalSpeed != move.speed) { + float speed; + + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( goalSpeed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if (goalSpeed && ( speed > goalSpeed ) ) { + speed = goalSpeed; + } + + vel *= speed; +// } else { +// vel.Normalize ( ); +// vel *= goalSpeed; +// } +} + +/* +===================== +idAI::FlyTurn +===================== +*/ +void idAI::FlyTurn( void ) { + if ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( focusType != AIFOCUS_NONE && move.fl.allowDirectional ) { + DirectionalTurnToward ( currentFocusPos ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + + Turn(); +} + +/* +===================== +idAI::FlyMove +===================== +*/ +void idAI::FlyMove( void ) { + idVec3 oldorigin; + idVec3 newDest; + + move.fl.blocked = false; + if ( ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) && ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), aiMoveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, move.fly_speed ); + } + + // Dont move when movement is disabled + if ( !CanMove() || legsAnim.Disabled ( ) ) { + // Still allow turning though + FlyTurn ( ); + + if ( (aifl.action || aifl.scripted ) && legsAnim.Disabled () && move.fl.allowAnimMove ) { + idMat3 oldaxis = viewAxis; + idVec3 delta; + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + physicsObj.UseFlyMove( false ); + if ( spawnArgs.GetBool( "alwaysBob" ) ) { + AddFlyBob( delta ); + } + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( true ); + + RunPhysics(); + } else if ( spawnArgs.GetBool( "alwaysBob" ) ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + AddFlyBob( vel ); + physicsObj.SetLinearVelocity( vel ); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + } + + UpdateAnimationControllers ( ); + return; + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + move.seekPos.x = newDest.x; + move.seekPos.y = newDest.y; + } + + if ( move.speed ) { + FlySeekGoal( vel, move.seekPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( ( move.moveCommand != MOVE_TO_POSITION ) ) { + AdjustFlyHeight( vel, move.seekPos ); + } + + AdjustFlySpeed( vel ); + + vel += move.addVelocity; + move.addVelocity.Zero(); + + physicsObj.SetLinearVelocity( vel ); + } + + // turn + FlyTurn(); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + move.fl.blocked = true; + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 ); + gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorBlue, org, move.seekPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition ( ), GetEyePosition ( ) + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +============ +idAI::UpdatePlayback +============ +*/ +void idAI::UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ) { + rvDeclPlaybackData pbd; + bool atDest; + + // New playback stuff + if( !mPlayback.IsActive() ) { + return; + } + + atDest = mPlayback.UpdateFrame( this, pbd ); + + goalPos = pbd.GetPosition(); + SetOrigin( goalPos ); + viewAxis = pbd.GetAngles().ToMat3(); + + // Keep the yaw updated + idVec3 local_dir; + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + OnUpdatePlayback ( pbd ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::PlaybackMove( void ){ + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + move.obstacle = NULL; + + goalPos = oldorigin; + + UpdatePlayback( goalPos, delta, oldorigin, oldaxis ); + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( true ); + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else { + move.fl.blocked = true; + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::StaticMove +===================== +*/ +void idAI::StaticMove( void ) { + idEntity* enemyEnt = enemy.ent; + + if ( aifl.dead ) { + return; + } + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemyEnt ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.moveCommand != MOVE_NONE ) { + TurnToward( move.moveDest ); + } + Turn(); + + physicsObj.ForceDeltaMove( true ); // disable gravity + RunPhysics(); + + move.fl.onGround = false; + + if ( DebugFilter(ai_debugMove) ) { // Static Move + const idVec3 &org = physicsObj.GetOrigin(); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + } +} + +/* +===================== +idAI::SlideMove +===================== +*/ +void idAI::SlideMove( void ) { + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.seekPos = move.moveDest; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.seekPos = move.moveDest; + } else if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + TurnToward( newDest ); + move.seekPos = newDest; + } + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.seekPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + } else { + move.seekPos = move.moveDest; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + idVec3 vel = physicsObj.GetLinearVelocity(); + float z = vel.z; + idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION; + + // seek the goal position + goalDelta = move.seekPos - predictedPos; + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + vel += goalDelta * MS2SEC( gameLocal.msec ); + + // cap our speed + vel.Truncate( move.fly_speed ); + vel.z = z; + physicsObj.SetLinearVelocity( vel ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else { + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); + + if ( DebugFilter(ai_debugMove) ) { // Slide Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // SlideMove + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::AnimMove +===================== +*/ +void idAI::AnimMove( void ) { + + if ( ai_useRVMasterMove.GetBool ( ) ) { + RVMasterMove(); + return; + } + + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idReachability* goalReach; + // RAVEN END + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + //move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( move.moveCommand == MOVE_FACE_ENEMY && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } else { + move.moveStatus = MOVE_STATUS_MOVING; + + // Otherwise, Update The Seek Pos + if ( !aifl.simpleThink && GetMovePos( move.goalPos, &goalReach ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( move.goalPos, move.seekPos, goalReach ); + } else { + move.seekPos = move.goalPos; + } + DirectionalTurnToward ( move.seekPos ); + } + } + } + + Turn(); + + goalDelta = move.seekPos - oldorigin; + goalDist = goalDelta.LengthFast(); + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = move.goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( move.fl.allowAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + } else if ( move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + float speed = delta.LengthFast(); + delta = goalDelta; + delta.Normalize(); + delta *= speed; + } else { + delta.Zero(); + } + + if ( move.moveCommand > NUM_NONMOVING_COMMANDS ) { + //actually *trying* to move to a goal + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + + RunPhysics(); + + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + const idVec3& org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // AnimMove : GREEN / RED Bounds & Move Dest + gameRenderWorld->DebugLine( colorCyan, oldorigin, org, 5000 ); + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), org, gameLocal.msec ); + if (!ReachedPos( move.moveDest, move.moveCommand, move.range )) { + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugArrow( (team==0)?(colorGreen):(colorRed), org, move.moveDest, 4, gameLocal.msec ); + } + DrawRoute(); + } +} + +/* +===================== +idAI::CustomMove +===================== +*/ +void idAI::CustomMove( void ) { + // derived class must implement this +} + + + +/* +====================================================================================== + NEW MOVE CODE BEGIN +====================================================================================== +*/ + +struct rvObstacle; + +const float REACHED_RADIUS = 4.0f; +const float REACHED_RADIUS_SQUARE = REACHED_RADIUS*REACHED_RADIUS; + + + + + + +/* +===================== +LineIntersection2D +===================== +*/ +bool LineIntersection2D(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& contactPoint) { + + // Test If Parallel + //------------------ + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)<0.01f) { + return false; + } + + // Test CD Edge + //-------------- + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f) { + return false; + } + if (s>1.0f) { + return false; + } + + // Test AB Edge + //-------------- + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r<0.0f) { + return false; + } + if (r>1.0f) { + return false; + } + + contactPoint = A + r*(B-A); + return true; +} + + +/* +===================== +rvWindingBox + +Clockwise winding with a simple line intersection test + +[1]---->[2] + ^ | + | verts | + | V +[0]<----[3] + +===================== +*/ +struct rvWindingBox { + idVec3 verts[4]; + aasArea_t* areas[4]; + rvObstacle* obstacles[4]; // CDR_TODO: Should be a list of obstacles for each vertex + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + for (int i=0; i<4; i++) { + verts[i] = vec3_zero; + areas[i] = NULL; + obstacles[i] = NULL; + } + } + + /* + ===================== + FromBounds + ===================== + */ + void FromBounds(const idBounds& b) { + verts[0].x = b[0].x; + verts[0].y = b[0].y; + verts[0].z = b[0].z; + + verts[1].x = b[0].x; + verts[1].y = b[1].y; + verts[1].z = b[0].z; + + verts[2].x = b[1].x; + verts[2].y = b[1].y; + verts[2].z = b[0].z; + + verts[3].x = b[1].x; + verts[3].y = b[0].y; + verts[3].z = b[0].z; + } + + /* + ===================== + LineIntersection + ===================== + */ + bool LineIntersection(const idVec3& start, const idVec3& end, idVec3& contactPoint, int& v1, int& v2) const { + for (int i=0; i<4; i++) { + v1 = i; + v2 = (i<3)?(i+1):(0); + + if (start.IsLeftOf(verts[v1], verts[v2]) && LineIntersection2D(start, end, verts[v1], verts[v2], contactPoint)) { + return true; + } + } + return false; + } + + /* + ===================== + PointInside + ===================== + */ + bool PointInside(const idVec3& point) const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + if (point.IsLeftOf(vert1, vert2)) { + return false; + } + } + return true; + } + + /* + ===================== + DrawDebugGraphics + ===================== + */ + bool DrawDebugGraphics() const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + gameRenderWorld->DebugLine(colorYellow, vert1, vert2, gameLocal.msec); + if (!areas[i] || obstacles[i]) { + gameRenderWorld->DebugLine(colorRed, vert1, vert1+idVec3(0.0f,0.0f,16.0f), gameLocal.msec); + } else if (areas[i]) { + // gameRenderWorld->DebugLine(colorYellow, vert1, areas[i]->center, gameLocal.msec); + } + } + return true; + } +}; + + + + +/* +===================== +rvMarker + +A marker represents an obstacle within +an area. Any single obstacle can have +any number of markers in any number of +areas that it touches +===================== +*/ +struct rvMarker { + rvObstacle* obstacle; + aasArea_t* area; + rvMarker* prev; + rvMarker* next; +}; +rvPool markerPool; + +/* +===================== +rvObstacle +===================== +*/ +struct rvObstacle { + static idAASFile* searchFile; + static idBounds searchBounds; + + entityPointer_t entity; + idVec3 origin; + idVec3 originFuture; + int lastTimeMoved; + bool pendingUpdate; + + rvWindingBox windings[3]; + idList markers; + idList areas; + + /* + ============ + Initialize + ============ + */ + void Initialize(idEntity* ent) { + entity = ent; + origin = vec3_zero; + originFuture = vec3_zero; + lastTimeMoved = 0; + pendingUpdate = true; + + for (int i=0; i<3; i++) { + windings[i].Initialize(); + } + markers.Clear(); + areas.Clear(); + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + if ((gameLocal.time - lastTimeMoved) < 1000) { + gameRenderWorld->DebugArrow(colorBrown, origin, originFuture, 3, gameLocal.msec); + } + + windings[0].DrawDebugGraphics(); + } + + /* + ============ + RemoveMarkers + ============ + */ + void RemoveMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==this); + + if (marker->area->firstMarker==marker) { + marker->area->firstMarker = marker->next; + } + if (marker->prev) { + marker->prev->next = marker->next; + } + if (marker->next) { + marker->next->prev = marker->prev; + } + + marker->obstacle = NULL; + + markerPool.free(marker); + } + markers.Clear(); + } + /* + ============ + SearchAreas_r + ============ + */ + void SearchAreas_r( int nodeNum ) { + int side; + const aasNode_t *node; + + while( nodeNum != 0 ) { + + // Negative nodeNum signifies a Leaf Area + if ( nodeNum < 0 ) { + if ( searchFile->GetArea(-nodeNum).flags&AREA_REACHABLE_WALK ) { + areas.AddUnique( &searchFile->GetArea(-nodeNum) ); + } + break; + } + node = &searchFile->GetNode( nodeNum ); + side = searchBounds.PlaneSide( searchFile->GetPlane( node->planeNum ) ); + if ( side == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( side == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + SearchAreas_r( node->children[1] ); + nodeNum = node->children[0]; + } + } + } + + /* + ============ + PointInsideArea + ============ + */ + bool PointInsideArea( const idVec3& point, aasArea_t* area ) { + int i, faceNum; + + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = searchFile->GetFaceIndex(area->firstFace + i); + + const aasFace_t& face = searchFile->GetFace(abs( faceNum )); + if (!(face.flags & FACE_FLOOR)) { + const idPlane& plane = searchFile->GetPlane(face.planeNum ^ INTSIGNBITSET( faceNum )); + if (plane.Side(point) == PLANESIDE_BACK) { + return false; + } + } + } + return true; + } + + /* + ============ + AddMarkers + ============ + */ + void AddMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==NULL); + + // Setup The New Marker + //---------------------- + marker->obstacle = this; + marker->area = areas[i]; + marker->next = NULL; + marker->prev = NULL; + + + // Fixup Any Existing Linked List (First Marker) + //----------------------------------------------- + if (marker->area->firstMarker) { + marker->area->firstMarker->prev = marker; + marker->next = marker->area->firstMarker; + } + + // Add This Marker At THe Area's Head + //------------------------------------ + marker->area->firstMarker = marker; + + markers.Append(marker); + } + } + + + + /* + ============ + Update + ============ + */ + bool Update() { + static rvMarker* marker; + static rvObstacle* obstacle; + static aasArea_t* area; + static idVec3 expand; + static float speed; + static float distance; + static idVec3 direction; + static int aasFileNum, t, v, a; + static idList touched; + static rvWindingBox* myWinding; + + + pendingUpdate = false; + idEntity* ent = entity.GetEntity(); + if (!ent || ent->health<=0 || ent->fl.hidden || !ent->GetPhysics()) { + RemoveMarkers(); + return false; // Means This Obstacle Structure Should Be Retired + } + + + // Only Update If We've Moved Far Enough + //--------------------------------------- + idPhysics* physics = ent->GetPhysics(); + if (origin.Dist2XY(physics->GetOrigin())<100.0f) { // CDR_TODO: Scale This By Distance To Player, Other Obstacles Near... + return true; + } + + // Get The Obstacle's Seek Direction & Speed + //------------------------------------------- + if (ent->IsType(idAI::GetClassType())) { + speed = (((idAI*)ent)->move.fl.done)?(0.0f):(physics->GetLinearVelocity().LengthFast()) * 2.5f; + direction = (((idAI*)ent)->move.seekPos - physics->GetOrigin()); + distance = direction.NormalizeFast(); + + // Cap The Projection To The Seek Position + if (speed > distance) { + speed = distance; + } + } else { + direction = physics->GetLinearVelocity(); + speed = direction.NormalizeFast() * 2.5f; + } + + origin = physics->GetOrigin(); + originFuture = origin + (direction*speed); // 2.5 seconds into the future predicion + lastTimeMoved = gameLocal.time; + + + // Remove Old Markers + //-------------------- + RemoveMarkers(); + + + // Get The Entity's Bounds And Areas That These Bounds Cover + //----------------------------------------------------------- + for (aasFileNum=0; aasFileNumGetFile(); + searchBounds = physics->GetAbsBounds(); + expand = searchFile->GetSettings().boundingBoxes[0].Size(); + expand[0] *= 0.5f; + expand[1] *= 0.5f; + expand[2] = 0; + expand[0] += REACHED_RADIUS; + expand[1] += REACHED_RADIUS; + + searchBounds.ExpandSelf(expand); + SearchAreas_r(1); + + // Setup The Winding From The Bounds + //----------------------------------- + myWinding->FromBounds(searchBounds); + + // Setup Each Vertex On The Winding + //---------------------------------- + for (v=0; v<4; v++) { + const idVec3& myVertex = myWinding->verts[v]; + + myWinding->areas[v] = NULL; + for (a=0; aareas[v] = area; + + + // Search This Area For All Obstacles That May Contain This Vertex + //----------------------------------------------------------------- + obstacle = NULL; + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle->windings[aasFileNum].PointInside(myVertex)) { + obstacle = marker->obstacle; + break; + } + } + + // Update The Obstacle On The Winding + //------------------------------------ + if (myWinding->obstacles[v] && myWinding->obstacles[v]!=obstacle) { + touched.AddUnique(myWinding->obstacles[v]); + } + if (obstacle) { + touched.AddUnique(obstacle); + } + + myWinding->obstacles[v] = obstacle; + break; + } + } + } + + // Touched Obstacles Need To Test Their Verts Against This Winding + //----------------------------------------------------------------- + for (t=0; tPointInside(obstacle->windings[aasFileNum].verts[v])) { + obstacle->windings[aasFileNum].obstacles[v] = this; + } else if (obstacle->windings[aasFileNum].obstacles[v]==this) { + obstacle->windings[aasFileNum].obstacles[v] = NULL; + } + } + } + + // AddMarkers In Each Found Area + //------------------------------- + AddMarkers(); + } + + return true; + } + + /* + ===================== + VertexValid + ===================== + */ + static bool VertexValid(const rvWindingBox& bounds, int v, const aasArea_t* inArea, const idEntity* ignore) { + return (bounds.areas[v] && (bounds.obstacles[v]==NULL || bounds.obstacles[v]->entity.GetEntity()==ignore)); + } + + /* + ============ + MovedRecently + ============ + */ + bool MovedRecently(int time=800) { + if ((gameLocal.time - lastTimeMoved) < time) { + return true; + } + // idEntity* ent = entity.GetEntity(); + // if (ent && ent->IsType(idAI::GetClassType())) { + // return !((idAI*)ent)->move.fl.done; + // } + return false; + } +}; +rvIndexPool obstaclePool; + +idAASFile* rvObstacle::searchFile; +idBounds rvObstacle::searchBounds; + + + + + + +/* +===================== +rvObstacleFinder +===================== +*/ +struct rvObstacleFinder { + idList obstaclesPendingUpdate; + int obstaclesUpdateTime; + + + struct traceResult_t { + rvObstacle* obstacle; + idVec3 endPoint; + float distance; + int v1; + int v2; + bool v1Valid; + bool v2Valid; + }; + traceResult_t contact; + + + + + /* + ============ + Initialize + ============ + */ + void Initialize() { + markerPool.clear(); + obstaclePool.clear(); + + obstaclesPendingUpdate.Clear(); + obstaclesUpdateTime = 0; + memset(&contact, 0, sizeof(contact)); + + // Clear All The Marker Pointers In Any Areas + //-------------------------------------------- + for (int i=0; iGetFile(); + for (int a=0; aGetNumAreas(); a++) { + file->GetArea(a).firstMarker = NULL; + } + } + } + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + static int nextDrawTime=0; + static rvObstacle* obstacle; + if (nextDrawTime>=gameLocal.time) { + return; + } + nextDrawTime = gameLocal.time; + + for (int i=0; iDrawDebugGraphics(); + } + } + } + + /* + ============ + UpdateObstacles + ============ + */ + void UpdateObstacles() { + static rvObstacle* obstacle; + static float entityNum; + + if (obstaclesUpdateTime < gameLocal.time && obstaclesPendingUpdate.Num()) { + obstaclesUpdateTime = gameLocal.time + 50; + + // CDR_TODO: Priority Queue Of Updates? Track Last Update Time Perhaps? + while (obstaclesPendingUpdate.Num()) { + obstacle = obstaclesPendingUpdate.StackTop(); + obstaclesPendingUpdate.StackPop(); + + if (!obstacle->Update()) { + obstaclePool.free(obstacle->entity.GetEntityNum()); + } + } + } + } + + /* + ============ + MarkEntityForUpdate + ============ + */ + void MarkEntityForUpdate(idEntity* ent) { + + // Ignore Non Physics Entities + //----------------------------- + if (!ent || !ent->GetPhysics()) { + return; + } + + // Ignore Obstacles That Are Already Pending + //-------------------------------------------- + if (obstaclePool.valid(ent->entityNumber) && obstaclePool[ent->entityNumber]->pendingUpdate) { + return; + } + + if (!obstaclePool.valid(ent->entityNumber)) { + + // If No More Obstacles Are Available, Ignore This One + //----------------------------------------------------- + if (obstaclePool.full()) { + return; + } + + obstaclePool.alloc(ent->entityNumber)->Initialize(ent); + } + + + obstaclePool[ent->entityNumber]->pendingUpdate = true; + obstaclesPendingUpdate.Append(obstaclePool[ent->entityNumber]); + } + + + /* + ============ + RecordContact + ============ + */ + void RecordContact(float maxDistance, const idVec3& start, rvObstacle* obstacle, const idVec3& point, int vert1=-1, int vert2=-1) { + static float distance; + distance = point.DistXY(start); + if ((maxDistance==0.0f || distance distance || !contact.obstacle)) { + contact.obstacle = obstacle; + contact.endPoint = point; + contact.distance = distance; + contact.v1 = vert1; + contact.v2 = vert2; + } + } + + /* + ============ + RayTrace + ============ + */ + bool RayTrace(float maxDistance, const aasArea_t* area, const idVec3& start, const idVec3& stop, int aasNum, const idEntity* ignore1, const idEntity* ignore2=NULL, const idEntity* ignore3=NULL) { + static rvMarker* marker; + static idVec3 p; + static int v1; + static int v2; + static rvObstacle* ignoreA; + static rvObstacle* ignoreB; + static rvObstacle* ignoreC; + static idEntity* ent; + static idVec3 startBack; + static idVec3 direction; + static bool pulledBack; + + ignoreA = (ignore1 && obstaclePool.valid(ignore1->entityNumber))?(obstaclePool[ignore1->entityNumber]):(NULL); + ignoreB = (ignore2 && obstaclePool.valid(ignore2->entityNumber))?(obstaclePool[ignore2->entityNumber]):(NULL); + ignoreC = (ignore3 && obstaclePool.valid(ignore3->entityNumber))?(obstaclePool[ignore3->entityNumber]):(NULL); + + contact.obstacle = NULL; + pulledBack = false; + + + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle==NULL || marker->obstacle==ignoreA || marker->obstacle==ignoreB || marker->obstacle==ignoreC) { + continue; + } + + // Handle Moving Obstacles Differently + //------------------------------------- + if (marker->obstacle->MovedRecently()) { + + // Ignore Moving Actors With Higher Entity Numbers + //------------------------------------------------- + ent = marker->obstacle->entity.GetEntity(); + if (ent && ignore1 && ent->entityNumber>ignore1->entityNumber && ent->IsType(idActor::GetClassType())) { + continue; + } + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Moving Obstacle's Predicted Path + //---------------------------------------------------------- + if (LineIntersection2D(startBack, stop, marker->obstacle->origin, marker->obstacle->originFuture, p)) { + RecordContact(maxDistance, startBack, marker->obstacle, p); + } + + + // Stationary Obstacles + //---------------------- + } else { + + // CDR_TODO: Special Line Intersection Test For Player Forward Aim And On Same Team + //---------------------------------------------------------------------------------- + + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Stationary Obstacle's Position + //-------------------------------------------------------- + if (marker->obstacle->windings[aasNum].LineIntersection(startBack, stop, p, v1, v2)) { + RecordContact(maxDistance, startBack, marker->obstacle, p, v1, v2); + } + } + } + + if (contact.obstacle) { + if (contact.obstacle->MovedRecently()) { + contact.v1Valid = true; + contact.v2Valid = true; + } else { + contact.v1Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v1, area, ignore1); + contact.v2Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v2, area, ignore1); + } + + return true; + } + return false; + } +}; +rvObstacleFinder obstacleFinder; + + + + + +/* +===================== +rvPathFinder +===================== +*/ +class rvPathFinder { +public: + /* + ===================== + visitNode + ===================== + */ + struct visitNode { + float costToGoal; + float travelCost; + bool closed; + int vertexNum; + idReachability* reach; + visitNode* from; + + float cost() { + return travelCost + costToGoal; + } + }; + + static int visitSort( const void *a, const void *b ) { + return (int)( (*((visitNode**)b))->cost() - (*((visitNode**)a))->cost() ); + } + + + + enum { + MAX_VISITED = 512, + MAX_OPEN = 255, + MAX_PENDING = 60, + }; + + idAAS* myAAS; + float myRadius; + idMoveState* myMove; + int myTeam; + const idEntity* myIgnoreEntity; + const idEntity* myIgnoreEntity2; + bool drawVisitTree; + + visitNode* next; + + visitNode* open[MAX_OPEN]; + int openCount; + bool openListUpdate; + + visitNode* pending[MAX_PENDING]; + visitNode* pendingBest; + int pendingCount; + + visitNode visited[MAX_VISITED]; + int visitedCount; + idHashIndex visitedIndexReach; + idHashIndex visitedIndexVert; + + + + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + next = NULL; + } + + + /* + ===================== + Close + ===================== + */ + void Close(visitNode* node) { + node->closed = true; + } + + /* + ===================== + DrawVisitTree + ===================== + */ + void DrawVisitTree() { + for (int i=0; imyPos); + const idVec3& stop = (GetSeekPosition(&visited[i])); + const idVec4& color = (visited[i].closed)?(colorOrange):(colorYellow); + const int duration = (visited[i].closed)?(3000):(500); + + gameRenderWorld->DebugArrow(color, start, stop, 3, duration); + } + } + + /* + ===================== + XYLineIntersection + ===================== + */ + bool XYLineIntersection(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& P) { + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)>0.01f) { + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f || s>1.0f) { + return false; + } + + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r>1.0f) { + P = B; + return false; + } + if (r<0.0f) { + P = A; + return false; + } + + P = A + r*(B-A); + return true; + } + + // Lines Are Parallel, No Intersection + return false; + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(idReachability* reach, int vertexNum) { + return ((vertexNum)?(myAAS->GetFile()->GetVertex(vertexNum)):(reach->start)); + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(visitNode* node) { + return GetSeekPosition(node->reach, node->vertexNum); + } + + /* + ===================== + Success + ===================== + */ + bool Success(visitNode* node) { + static idVec3 edgeA; + static idVec3 edgeB; + static idVec3 intersect; + static idVec3 direction; + static idVec3 smoothedPos; + static int at; + static int count; + static rvMarker* marker; + static bool isCorner; + + // Always Add The Goal Pos At The End Of The Path + //------------------------------------------------ + myMove->path[myMove->pathLen].reach = NULL; + myMove->path[myMove->pathLen].seekPos = myMove->goalPos; + myMove->pathLen ++; + + // Build The Path + //---------------- + while (node && myMove->pathLenpath[myMove->pathLen].reach = node->reach; + myMove->path[myMove->pathLen].seekPos = GetSeekPosition(node); + + myMove->pathLen ++; + node = node->from; + } + + + // Additional Path Point Modifications + //------------------------------------- + count = myMove->pathLen-1; + for (at=count; at>0; at--) { + pathSeek_t& pathPrev = myMove->path[at+1]; + pathSeek_t& pathAt = myMove->path[at]; + pathSeek_t& pathNext = myMove->path[at-1]; + + myAAS->GetEdge(pathAt.reach->edgeNum, edgeA, edgeB); + + // Smooth The Path One Pass + //-------------------------- + if (pathAt.reach->travelType==TFL_WALK) { + const idVec3& walkA = (at==count) ? (myMove->myPos) :(pathPrev.seekPos); + const idVec3& walkB = (at==0) ? (myMove->goalPos) :(pathNext.seekPos); + + isCorner = !XYLineIntersection(edgeA, edgeB, walkA, walkB, smoothedPos); + + + // If The Smoothed Position Is Not Blocked By An Obstacle + //-------------------------------------------------------- + aasArea_t* area = &myAAS->GetFile()->GetArea(pathAt.reach->toAreaNum); + for (marker=area->firstMarker; marker; marker=marker->next) { + if (!marker->obstacle || marker->obstacle->entity.GetEntity()==myIgnoreEntity || marker->obstacle->entity.GetEntity()==myIgnoreEntity2) { + continue; + } + if (marker->obstacle->windings[0/*CDR_TODO: use aasNum*/].PointInside(smoothedPos)) { + break; + } + } + if (!marker) { + pathAt.seekPos = smoothedPos; + + // Push Away From The Corner A Bit + //--------------------------------- + if (isCorner) { + smoothedPos.ProjectToLineSeg(walkA, walkB); + direction = pathAt.seekPos - smoothedPos; + direction.NormalizeFast(); + pathAt.seekPos += direction * (REACHED_RADIUS+1.0f); + } + } + } + } + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return true; + } + + /* + ===================== + Failure + ===================== + */ + bool Failure() { + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return false; + } + + + /* + ===================== + ErrorCondition + ===================== + */ + bool ErrorCondition() { + assert(0); + // Stats? + return false; + } + + /* + ===================== + SelectNextVisitedNode + ===================== + */ + visitNode* SelectNextVisitedNode() { + if (pendingBest && (!openCount || open[openCount-1]->cost() > pendingBest->cost())) { + next = pendingBest; + pendingBest = NULL; + openListUpdate = true; + return next; + } + + if (openCount) { + openCount--; + return open[openCount]; + } + return NULL; + } + + /* + ===================== + Open + ===================== + */ + void Open(visitNode* node) { + node->closed = false; + + // If This Node Is Cheaper Than The Existing Best Open Node, Add It To The End Of The Open List + //---------------------------------------------------------------------------------------------- + if (openCountcost() < open[openCount-1]->cost())) { + open[openCount] = node; + openCount++; + return; + } + } + + // Otherwise, Add It To The Pending List + //--------------------------------------- + if (pendingCountcost() < pendingBest->cost())) { + pendingBest = node; + } + } + } + + /* + ===================== + UpdateOpenList + ===================== + */ + void UpdateOpenList() { + + // List Is Updated Now, So Remove The Flag + //----------------------------------------- + openListUpdate = false; + + // Add All Pending Nodes To The Open List + //---------------------------------------- + for (int i=0; iclosed) { + open[openCount] = pending[i]; + openCount ++; + } + } + pendingCount = 0; + pendingBest = NULL; + + + // Sort The Open List + //-------------------- + qsort( (void*)open, (size_t)openCount, (size_t)sizeof(visitNode*), visitSort ); + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(idReachability* reach) { + for (int i=visitedIndexReach.First(abs(reach->edgeNum)); i>=0; i=visitedIndexReach.Next(i)) { + if (visited[i].reach==reach) { + return &visited[i]; + } + } + return NULL; + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(int vertexNum) { + for (int i=visitedIndexVert.First(vertexNum); i>=0; i=visitedIndexVert.Next(i)) { + if (visited[i].vertexNum==vertexNum) { + return &visited[i]; + } + } + return NULL; + } + + + /* + ===================== + TravelCost + ===================== + */ + float TravelCost(idReachability* reach, int vertexNum, visitNode* from) { + static float distance; + + const idVec3& start = (from)?(GetSeekPosition(from)):(myMove->myPos); + const idVec3& stop = GetSeekPosition(reach, vertexNum); + + + // Test For Any Obstacles In The Way + //----------------------------------- + const aasArea_t* area = &myAAS->GetFile()->GetArea((from)?(from->reach->toAreaNum):(myMove->myArea)); + if (obstacleFinder.RayTrace(0.0f, area, start, stop, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + + // If It Is Not Possible To Steer Around, Then This Edge Is Completely Invalid + //----------------------------------------------------------------------------- + if (!obstacleFinder.contact.v1Valid && !obstacleFinder.contact.v2Valid) { + return 0.0f; + } + + // Completely Disable Any Points Completely Covered By An Obstacle + //----------------------------------------------------------------- + if (obstacleFinder.contact.obstacle->windings[0/*CDR_TODO: Get myAASNum*/].PointInside(stop)) { + return 0.0f; + } + distance += 128.0f; + } + + + // Compute Standard Distance + //--------------------------- + distance = start.Dist(stop); + if (from) { + distance += from->travelCost + from->reach->travelTime; + } + + return distance; + } + + + /* + ===================== + Visit + ===================== + */ + void Visit(idReachability* reach, int vertexNum, visitNode* from) { + static visitNode* visit; + static float travelCost; + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Compute Travel Cost, And Test To See If This Edge Is Blocked + //-------------------------------------------------------------- + travelCost = TravelCost(reach, vertexNum, from); + if (travelCost==0.0f) { + return; + } + + // If The Visited Version Is Already Less Costly, Then Ignore This Reach + //----------------------------------------------------------------------- + visit = (vertexNum)?(WasVisited(vertexNum)):(WasVisited(reach)); + if (visit && visit->travelCost<=travelCost) { + return; + } + + + + // Reopen Any Nodes That Were Closed + //----------------------------------- + if (visit && visit->closed) { + Open(visit); + + // Otherwise, If The Node Is Already In The Open List, Just Change The Cost And Mark The List For Resorting + //---------------------------------------------------------------------------------------------------------- + } else if (visit) { + visit->from = from; + visit->travelCost = travelCost; + openListUpdate = true; + + // Must Never Have Visited This Node Before, So Make A Whole New One + //------------------------------------------------------------------- + } else { + + const idVec3& pos = GetSeekPosition(reach, vertexNum); + + // Constant Data (Will Never Change) + //----------------------------------- + visit = &visited[visitedCount]; + visit->reach = reach; + visit->vertexNum = vertexNum; + visit->costToGoal = pos.Dist(myMove->goalPos); + + // Temporary Data + //---------------- + visit->from = from; + visit->travelCost = travelCost; + visit->closed = false; + + // Add It To The Hash Table To Be Found Later + //-------------------------------------------- + if (!vertexNum) { + visitedIndexReach.Add(abs(reach->edgeNum), visitedCount); + } else { + visitedIndexVert.Add(vertexNum, visitedCount); + } + visitedCount++; + + // Mark It As Open + //----------------- + Open(visit); + } + } + + /* + ===================== + VisitReach + + This function first visits the center of the reachability, and then if + the edge is long enough and close enough to the start or end of the + path, it visits the verts as well + ===================== + */ + void VisitReach(idReachability *reach, visitNode* from) { + static int verts[2]; + static int vertexNum; + static idVec3 start; + static idVec3 stop; + + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Ignore Any Reach To The Area We Came From + //------------------------------------------- + if (from && from->reach->fromAreaNum==reach->toAreaNum) { + return; + } + + // Ignore Any Reach That Does Not Match Our Travel Flags + //------------------------------------------------------- + if (reach->travelType&TFL_INVALID || !(reach->travelType&myMove->travelFlags)) { + return; + } + + // Visit The Center Of The Reach + //------------------------------- + Visit(reach, 0, from); + + + // If Running Low On Visit Space, Stop Adding Verts + //-------------------------------------------------- + if (visitedCount>=(int)((float)MAX_VISITED * 0.85f)) { + return; + } + + // If Edge Is Far From Start and Goal, Don't Add Verts + //----------------------------------------------------- + if (!reach->fromAreaNum!=myMove->myArea && + !reach->toAreaNum!=myMove->myArea && + !reach->fromAreaNum!=myMove->goalArea && + !reach->toAreaNum!=myMove->goalArea && + reach->start.Dist2XY(myMove->myPos)>22500.0f/*(250*250)*/ && reach->start.Dist2XY(myMove->goalPos)>22500.0f/*(250*250)*/) { + return; + } + + // If This Edge Is Small Enough, Just Skip The Verts + //--------------------------------------------------- + myAAS->GetEdge(reach->edgeNum, start, stop); + if (start.Dist2XY(stop)<6400.0f/*(80*80)*/) { + return; + } + + // Ok, So Visit The Verts Too + //---------------------------- + myAAS->GetEdgeVertexNumbers(reach->edgeNum, verts); + for (int i=0; i<2 && visitedCountGetFile()->GetArea(areaNum); + + // If Visiting From Another Node, Close That Node Now + //---------------------------------------------------- + if (from) { + + // If Already Closed + //------------------- + if (from->closed) { + if (openListUpdate) { + UpdateOpenList(); // this is kind of hacky... + } + + // Don't Bother To Look At These Reaches + //--------------------------------------- + return; + + // Otherwise, Close It + //--------------------- + } else { + Close(from); + } + } + + // Ok, Iterate All Reachabilities Within This Area + //------------------------------------------------- + for (reach=area.reach; reach && visitedCountnext) { + VisitReach(reach, from); + } + + // Finally, If Necessary, Update The Open List Now + //------------------------------------------------- + if (openListUpdate) { + UpdateOpenList(); + } + } + + + /* + ===================== + FindPath + ===================== + */ + bool FindPath(idAAS* aas, idMoveState& move, float radius, bool inDebugMode, idEntity* ignoreEntity, idEntity* ignoreEntity2) { + myAAS = aas; + myMove = &move; + myRadius = radius; + drawVisitTree = inDebugMode; + myIgnoreEntity = ignoreEntity; + myIgnoreEntity2 = ignoreEntity2; + + myMove->pathArea = myMove->goalArea; + myMove->pathTime = gameLocal.GetTime(); + myMove->pathLen = 0; + + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + + + + if (!myMove->myArea) { + return ErrorCondition(); + } + const aasArea_t* myArea = &myAAS->GetFile()->GetArea(myMove->myArea); + const aasArea_t* goalArea = &myAAS->GetFile()->GetArea(myMove->goalArea); + + + // Special Case For Starting In The Goal Area + //-------------------------------------------- + if (myMove->myArea==myMove->goalArea) { + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, myArea, myMove->myPos, myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(NULL); + } + + // If There Is An Obstacle But We Think We Can Steer Around It, Then We've Still Succeeded + //----------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(NULL); + } + } + + + + // Start With My Area + //-------------------- + VisitArea(myMove->myArea); + + + // While Reachabilities Are Still Pending + //---------------------------------------- + while (openCount || pendingCount) { + + // Select Next Visited Node + //-------------------------- + next = SelectNextVisitedNode(); + if (!next) { + return ErrorCondition(); + } + + // If This Node Reaches Our Target Destination, We've Succeeded + //-------------------------------------------------------------- + if (next->reach->toAreaNum==myMove->goalArea) { + next->closed = true; + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, goalArea, GetSeekPosition(next), myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(next); + } + + // Or If There Is An Obstacle, But One Of The Verts Is Valid, Then This Is Still A Safe Course + //--------------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(next); + } + + // Visit All Reaches In The Next Area + //------------------------------------- + } else { + VisitArea(next->reach->toAreaNum, next); + } + } + + return Failure(); + } +}; + +rvPathFinder pathFinder; + + + +void AI_EntityMoved(idEntity* ent) { + obstacleFinder.MarkEntityForUpdate(ent); +} +void AI_MoveInitialize() { + pathFinder.Initialize(); + obstacleFinder.Initialize(); +} + + + + + + + +/* +===================== +idAI::NewCombinedMove +===================== +*/ +void idAI::RVMasterMove( void ) { + static idVec3 mySeekDelta; + static idVec3 mySeekDirection; + static float mySeekDistance; + + static float moveDistance; + static idVec3 moveDelta; + static idEntity* moveBlockEnt; + static monsterMoveResult_t moveResult; + + + //==================================================================== + // UPDATE DATA + // A simple first step for movement is to get data from the command + // and goal arguments and compute changes if necessary. + //==================================================================== + bool seekMove = CanMove() && move.moveCommand>=NUM_NONMOVING_COMMANDS; + bool seekTurn = CanTurn() && move.moveCommand> MOVE_NONE; + + idMat3 myAxis = viewAxis; + const idVec3& myPos = physicsObj.GetOrigin(); + const idBounds& myBounds = physicsObj.GetBounds(); + float myRadius = myBounds.Size().x / 2.0f; + idVec3 myPosOld = move.myPos; // only used for debug graphics + bool myPosMoved = false; // only used for debug graphics + + const idEntity* goalEntity = move.goalEntity.GetEntity(); + const idVec3& goalPos = (goalEntity)?(LastKnownPosition(goalEntity)):(move.moveDest); + + + + + // Update My Position And Area + //----------------------------- + if (move.myArea==0 || move.myPos.Dist2XY(myPos)>20.0f) { + move.myPos = myPos; + move.myArea = PointReachableAreaNum(move.myPos); + myPosMoved = true; + } + aasArea_t* myArea = &aas->GetFile()->GetArea(move.myArea); + + // Update Goal Position And Area + //------------------------------- + if ((seekMove || seekTurn) && move.goalPos.Dist2XY(goalPos)>20.0f) { + move.goalPos = goalPos; + move.goalArea = PointReachableAreaNum(move.goalPos); + } + + // If Reached The Goal Position, Then Stop Moving + //------------------------------------------------ + if (seekMove && ReachedPos( move.goalPos, move.moveCommand, move.range )) { + StopMove( MOVE_STATUS_DONE ); + seekMove = false; + move.pathLen = 0; + } + + // Update The Obstacle Markers + //----------------------------- + obstacleFinder.UpdateObstacles(); + + + + //==================================================================== + // PATH FINDING + // If the goal area is not the same as myArea, then we need to run + // a pathfinding search to get to the goal area. This operation + // will alter the seek position + //==================================================================== + if (seekMove) { + + // If No Path Exists, Find One + //----------------------------- + if (move.pathArea!=move.goalArea || (!move.pathLen && move.pathTime < (gameLocal.time-10000))) { + pathFinder.FindPath(aas, move, myRadius, DebugFilter(ai_debugMove), this, move.goalEntity.GetEntity()); + } + + + // Set The SeekPos + //----------------- + if (move.pathLen) { + move.seekPos = move.path[move.pathLen-1].seekPos; + + // If We've Reached The Next Path Pos, Pop It Off The List And Continue + //---------------------------------------------------------------------- + if (move.seekPos.Dist2XY(move.myPos)entity; + const rvWindingBox& bounds = tr.obstacle->windings[0/*CDR_TODO: Get actual aasNumber*/]; + + // Is The Obstacle Standing On Seek Position + //------------------------------------------- + if (bounds.PointInside(move.seekPos)) { + if (move.myArea!=move.goalArea) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1150; + move.pathArea = 0; // force a refind path next update + } else { + + // Otherwise, Stop And Wait For It To Move + //----------------------------------------- + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1050; + // CDR_TODO: Issue MoveDestInvalid() Callback Here + } + } + + // Is The Obstacle About To Cross My Path? + //----------------------------------------- + else if (tr.v1==-1) { // CDR_TODO: -1 is an obtouse way to detect this + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1500; + } + + // Ok, So Let's Try To Steer Around The Obstacle + //----------------------------------------------- + else { + + // If Neither Vertex Is Valid, Need To Refind Path + //------------------------------------------------- + if (!tr.v1Valid && !tr.v2Valid) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 3250; + move.pathArea = 0; // force a refind path next update + + } else { + + int vertex; + + // Otherwise, Choose The Best Valid Vertex + //----------------------------------------- + if (!tr.v2Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v1; + } else if (!tr.v1Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v2; + } else { + // CDRTODO: Record clockwise / counter clockwise and only test this once. + vertex = (tr.obstacle->origin.IsLeftOf(move.myPos, move.seekPos))?(tr.v2):(tr.v1); + } + + + // Get Close The Contact Point if The Choosen Vertex Is Not In This Area, Before Going Toward The Vertex + //------------------------------------------------------------------------------------------------------- + if (bounds.areas[vertex]!=myArea && move.myPos.Dist2XY(tr.endPoint)>400.0f /*20*20*/) { + move.seekPos = tr.endPoint; + } else { + move.seekPos = bounds.verts[vertex]; + } + + + // And Recompute The Seek Vectors + //-------------------------------- + mySeekDelta = move.seekPos - move.myPos; + mySeekDirection = mySeekDelta; + mySeekDistance = mySeekDirection.NormalizeFast(); + } + } + } + } + + + + + + //==================================================================== + // TURNING + // Having finialized our seek position, it is now time to turn + // toward it. + //==================================================================== + if (seekTurn) { + DirectionalTurnToward(move.seekPos); + } + Turn(); + + + + //==================================================================== + // VELOCITY + // Now we need to get the instantanious velocity vector, which will + // usually come right from the animation + //==================================================================== + if (move.moveType == MOVETYPE_ANIM) { + if ( move.fl.allowAnimMove || move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( myAxis, viewAxis, moveDelta ); + } else { + moveDelta.Zero(); + } + } else { + // CDR_TODO: Other movetypes here + } + + // If Doing Seek Move, Cap The Delta To Avoid Overshooting The Seek Position + //--------------------------------------------------------------------------- + if (seekMove && moveDelta!=vec3_zero) { + moveDistance = moveDelta.LengthFast(); + + if (mySeekDistance<0.5f) { + moveDelta = vec3_zero; + } else if (mySeekDistanceIsType( idMoveable::GetClassType() ) && moveBlockEnt->GetPhysics()->IsPushable()) { + KickObstacles( viewAxis[ 0 ], move.kickForce, moveBlockEnt ); + } + + // Touch Triggers + //---------------- + if (moveDelta!=vec3_zero ) { + if (moveResult!=MM_BLOCKED) { + TouchTriggers(); + } + } + + + + //==================================================================== + // DEBUG GRAPHICS + //==================================================================== + idVec3 origin = physicsObj.GetOrigin(); + if (DebugFilter(ai_debugMove)) { + static const idVec3 upPole(0.0f, 0.0f, 60.0f); + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + + gameRenderWorld->DebugBounds(colorMagenta, physicsObj.GetBounds(), origin, gameLocal.msec); // Bounds: MAGENTA + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + gameRenderWorld->DebugLine(colorPurple, move.goalPos, move.goalPos + upPole, gameLocal.msec); // Goal: PURPLE + + if (myPosMoved) { + gameRenderWorld->DebugLine(colorCyan, myPosOld, move.myPos, 800); // Trail: CYAN + } + + if (move.pathLen) { + for (int i=move.pathLen-1; i>=0; i--) { + gameRenderWorld->DebugLine(colorBlue, origin, move.path[i].seekPos, gameLocal.msec); // FoundPath: BLUE + origin = move.path[i].seekPos; + } + } + // PathVisited: ORANGE + // PathOpened: YELLOW + } + + if (DebugFilter(ai_showObstacleAvoidance)) { + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + const idVec3& obstaclePos = (move.obstacle.GetEntity())?(move.obstacle->GetPhysics()->GetOrigin()):(move.seekPos); + + if (!DebugFilter(ai_debugMove)) { + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + } + + if (move.blockTime>gameLocal.time) { + gameRenderWorld->DebugArrow(colorRed, move.myPos, obstaclePos, 3, gameLocal.msec); // Blocked Obstacle: RED + } else if (move.fl.obstacleInPath) { + gameRenderWorld->DebugArrow(colorWhite, move.myPos, obstaclePos, 3, gameLocal.msec); // Walk Obstacle: WHITE + } + // ObstacleBox: YELLOW + // VertexInvalid: RED + // FuturePosition: BROWN + obstacleFinder.DrawDebugGraphics(); + } +} + +/* +===================== +idAI::Move +===================== +*/ +void idAI::Move ( void ) { + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Start ( ); + } + + switch( move.moveType ) { + case MOVETYPE_DEAD: + DeadMove(); + break; + case MOVETYPE_FLY : + FlyMove(); + break; + case MOVETYPE_STATIC : + StaticMove(); + break; + case MOVETYPE_ANIM : + AnimMove(); + break; + case MOVETYPE_SLIDE : + SlideMove(); + break; + case MOVETYPE_PLAYBACK: + PlaybackMove(); + break; + case MOVETYPE_CUSTOM: + CustomMove(); + break; + } + + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Stop ( ); + } +} + diff --git a/src/game/ai/AI_Move.h b/src/game/ai/AI_Move.h new file mode 100644 index 0000000..beb9806 --- /dev/null +++ b/src/game/ai/AI_Move.h @@ -0,0 +1,319 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + +AI_Move.h + +This file has all movement related typedefs, enums, flags, and structures. It +and its sister CPP file were split from AI.h and AI.cpp in order to prevent +merge conflicts and to make further changes to the system possible. + +=============================================================================== +*/ + +#ifndef __AI_MOVE_H__ +#define __AI_MOVE_H__ + + +typedef idEntityPtr entityPointer_t; + +#define MAX_PATH_LEN 48 + +/* +===================== +aiMoveDir_t - used by directional movement code +===================== +*/ +typedef enum { + MOVEDIR_FORWARD, + MOVEDIR_BACKWARD, + MOVEDIR_LEFT, + MOVEDIR_RIGHT, + MOVEDIR_MAX +} aiMoveDir_t; +extern const char* aiMoveDirectionString [ MOVEDIR_MAX ]; + +/* +===================== +moveType_t - determines which movement function to call +===================== +*/ +typedef enum { + MOVETYPE_DEAD, + MOVETYPE_ANIM, + MOVETYPE_SLIDE, + MOVETYPE_FLY, + MOVETYPE_STATIC, + MOVETYPE_PLAYBACK, + //twhitaker: added custom move type + MOVETYPE_CUSTOM, + NUM_MOVETYPES +} moveType_t; + +/* +===================== +aiMoveCommand_t - tells the AI how to get there +===================== +*/ +typedef enum { + MOVE_NONE, + MOVE_FACE_ENEMY, + MOVE_FACE_ENTITY, + + // commands < NUM_NONMOVING_COMMANDS don't cause a change in position + NUM_NONMOVING_COMMANDS, + + MOVE_TO_ENEMY = NUM_NONMOVING_COMMANDS, + MOVE_TO_ENTITY, + MOVE_TO_ATTACK, + MOVE_TO_HELPER, + MOVE_TO_TETHER, + MOVE_TO_COVER, + MOVE_TO_HIDE, + MOVE_TO_POSITION, + MOVE_TO_POSITION_DIRECT, + MOVE_OUT_OF_RANGE, + MOVE_SLIDE_TO_POSITION, + MOVE_WANDER, + MOVE_RV_PLAYBACK, + NUM_MOVE_COMMANDS + +} aiMoveCommand_t; + +/* +===================== +moveStatus_t - status results from move commands + +make sure to change script/doom_defs.script if you add any, or change their order +===================== +*/ +typedef enum { + MOVE_STATUS_DONE, + MOVE_STATUS_MOVING, + MOVE_STATUS_WAITING, + MOVE_STATUS_DEST_NOT_FOUND, + MOVE_STATUS_DEST_UNREACHABLE, + MOVE_STATUS_BLOCKED_BY_WALL, + MOVE_STATUS_BLOCKED_BY_OBJECT, + MOVE_STATUS_BLOCKED_BY_ENEMY, + MOVE_STATUS_BLOCKED_BY_MONSTER, + MOVE_STATUS_BLOCKED_BY_PLAYER, + MOVE_STATUS_DISABLED, + NUM_MOVE_STATUS, +} moveStatus_t; +extern const char* aiMoveStatusString[ NUM_MOVE_STATUS ]; + +/* +===================== +stopEvent_t - used by path prediction +===================== +*/ +typedef enum { + SE_BLOCKED = BIT(0), + SE_ENTER_LEDGE_AREA = BIT(1), + SE_ENTER_OBSTACLE = BIT(2), + SE_FALL = BIT(3), + SE_LAND = BIT(4) +} stopEvent_t; + +/* +===================== +obstaclePath_s +===================== +*/ +typedef struct obstaclePath_s { + idVec3 seekPos; // seek position avoiding obstacles + idEntity * firstObstacle; // if != NULL the first obstacle along the path + idVec3 startPosOutsideObstacles; // start position outside obstacles + idEntity * startPosObstacle; // if != NULL the obstacle containing the start position + idVec3 seekPosOutsideObstacles; // seek position outside obstacles + idEntity * seekPosObstacle; // if != NULL the obstacle containing the seek position + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idList allObstacles; + // RAVEN END +} obstaclePath_t; + +/* +===================== +predictedPath_s +===================== +*/ +typedef struct predictedPath_s { + idVec3 endPos; // final position + idVec3 endVelocity; // velocity at end position + idVec3 endNormal; // normal of blocking surface + int endTime; // time predicted + int endEvent; // event that stopped the prediction + const idEntity * blockingEntity; // entity that blocks the movement +} predictedPath_t; + + +/* +===================== +pathSeek_s +===================== +*/ +typedef struct pathSeek_s { + idReachability* reach; + idVec3 seekPos; +} pathSeek_t; + + + +/* +===================== +idMoveState +===================== +*/ +class idMoveState { +public: + idMoveState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Spawn( idDict &spawnArgs ); + + + struct movementFlags_s { + + // current state + bool done :1; + bool moving :1; + bool crouching :1; // is currently crouching? + bool running :1; // is currently running? + bool blocked :1; + bool obstacleInPath :1; + bool goalUnreachable :1; + bool onGround :1; + bool flyTurning :1; // lean into turns when flying + + // ideal state + bool idealRunning :1; // what we want running to be + + + // various enable / disable flags + bool disabled :1; // is movement disabled? (this includes turning) + bool ignoreObstacles :1; // don't check for obsticles in path + + bool allowDirectional :1; // allows directional movement (ie. running sideways, backwards) + bool allowAnimMove :1; // allows any animation movement + bool allowPrevAnimMove :1; // allows slide move if current animmove has no motion extraction on it (smooth transitions) + bool allowHiddenMove :1; // allows character to still move around while hidden + bool allowPushMovables :1; // allows the articulated figure to push moveable objects + bool allowSlideToGoal :1; // allows the AI to excactly slide to the goal position if close enough + + bool noRun :1; // force the actor to walk + bool noWalk :1; // force the actor to run + bool noTurn :1; // can the ai turn? + bool noGravity :1; // dont use gravity + bool noRangedInterrupt :1; // dont stop halfway to attack position just because you have a clear shot + } fl; + + + moveType_t moveType; + aiMoveCommand_t moveCommand; + moveStatus_t moveStatus; + idVec3 moveDest; + idVec3 moveDir; // used for wandering and slide moves + + int toAreaNum; + int startTime; + int duration; + float speed; // only used by flying creatures + float range; + float wanderYaw; + int nextWanderTime; + int blockTime; + idEntityPtr obstacle; + idVec3 lastMoveOrigin; + int lastMoveTime; + int anim; + + int travelFlags; + + float kickForce; + float blockedRadius; + int blockedMoveTime; + int blockedAttackTime; + + // turning + float ideal_yaw; + float current_yaw; + float turnRate; + float turnVel; + float anim_turn_yaw; + float anim_turn_amount; + float anim_turn_angles; + + // flying + jointHandle_t flyTiltJoint; + float fly_speed; + float fly_bob_strength; + float fly_bob_vert; + float fly_bob_horz; + int fly_offset; // prefered offset from player's view + float fly_seek_scale; + float fly_roll_scale; + float fly_roll_max; + float fly_roll; + float fly_pitch_scale; + float fly_pitch_max; + float fly_pitch; + + aiMoveDir_t currentDirection; // Direction currently moving in + aiMoveDir_t idealDirection; // Direction we want to be moving in + float walkRange; // Distance to target before starting to walk + float walkTurn; // Turn delta threshold for walking when turning + idVec2 followRange; // Min and max range for AI following their leader + idVec2 searchRange; // Min and max range to use when searching for a new place to move to + float attackPositionRange; // Override for how close you have to get to your attackPosition before stopping + float turnDelta; // Amount to turn when turning + + idVec3 goalPos; + int goalArea; + entityPointer_t goalEntity; + idVec3 goalEntityOrigin; // move to entity uses this to avoid checking the floor position every frame + + idVec3 myPos; + int myArea; + + idVec3 seekPos; + + pathSeek_t path[MAX_PATH_LEN]; + int pathLen; + int pathArea; + int pathTime; + + idVec3 addVelocity; +}; + +// cdr: Obstacle Avoidance +void AI_EntityMoved(idEntity* ent); +void AI_MoveInitialize(); + + +#endif /* !__AI_MOVE_H__ */ diff --git a/src/game/ai/AI_States.cpp b/src/game/ai/AI_States.cpp new file mode 100644 index 0000000..981c462 --- /dev/null +++ b/src/game/ai/AI_States.cpp @@ -0,0 +1,2236 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +static const int TACTICALUPDATE_MELEEDELAY = 500; // Rate to update tactical state when rushing +static const int TACTICALUPDATE_RANGEDDELAY = 2000; // Rate to update tactical state when in ranged combat +static const int TACTICALUPDATE_COVERDELAY = 5000; // Rate to update tactical state when in cover +static const int TACTICALUPDATE_HIDEDELAY = 5000; // Rate to update tactical state when hiding +static const int TACTICALUPDATE_FOLLOWDELAY = 500; // Rate to update tactical state when following +static const int TACTICALUPDATE_MOVETETHERDELAY = 1000; // Rate to update tactical state when moving into tether range +static const int TACTICALUPDATE_PASSIVEDELAY = 500; // Rate to update tactical state when in passive mode +static const int TACTICALUPDATE_TURRETDELAY = 250; // Rate to update tactical state when in turret mode + +static const int RANGED_ENEMYDELAY = 2000; // Time to wait to move after losing sight of an enemy +static const int COVER_ENEMYDELAY = 5000; // Stay behind cover for 5 seconds after loosing sight of an enemy + +static const float COVER_TRIGGERRADIUS = 64.0f; + +CLASS_STATES_DECLARATION ( idAI ) + // Wait States + STATE ( "Wait_Activated", idAI::State_Wait_Activated ) + STATE ( "Wait_ScriptedDone", idAI::State_Wait_ScriptedDone ) + STATE ( "Wait_Action", idAI::State_Wait_Action ) + STATE ( "Wait_ActionNoPain", idAI::State_Wait_ActionNoPain ) + + // Global States + STATE ( "State_WakeUp", idAI::State_WakeUp ) + STATE ( "State_TriggerAnim", idAI::State_TriggerAnim ) + + // Passive states + STATE ( "State_Passive", idAI::State_Passive ) + + // Combat states + STATE ( "State_Combat", idAI::State_Combat ) + STATE ( "State_CombatCover", idAI::State_CombatCover ) + STATE ( "State_CombatMelee", idAI::State_CombatMelee ) + STATE ( "State_CombatRanged", idAI::State_CombatRanged ) + STATE ( "State_CombatTurret", idAI::State_CombatTurret ) + STATE ( "State_CombatHide", idAI::State_CombatHide ) + + STATE ( "State_Wander", idAI::State_Wander ) + STATE ( "State_MoveTether", idAI::State_MoveTether ) + STATE ( "State_MoveFollow", idAI::State_MoveFollow ) + STATE ( "State_MovePlayerPush", idAI::State_MovePlayerPush ) + STATE ( "State_Killed", idAI::State_Killed ) + STATE ( "State_Dead", idAI::State_Dead ) + STATE ( "State_LightningDeath", idAI::State_LightningDeath ) + STATE ( "State_Burn", idAI::State_Burn ) + STATE ( "State_Remove", idAI::State_Remove ) + STATE ( "State_ScriptedMove", idAI::State_ScriptedMove ) + STATE ( "State_ScriptedFace", idAI::State_ScriptedFace ) + STATE ( "State_ScriptedStop", idAI::State_ScriptedStop ) + STATE ( "State_ScriptedPlaybackMove", idAI::State_ScriptedPlaybackMove ) + STATE ( "State_ScriptedPlaybackAim", idAI::State_ScriptedPlaybackAim ) + STATE ( "State_ScriptedJumpDown", idAI::State_ScriptedJumpDown ) + + // Torso States + STATE ( "Torso_Idle", idAI::State_Torso_Idle ) + STATE ( "Torso_Sight", idAI::State_Torso_Sight ) + STATE ( "Torso_CustomCycle", idAI::State_Torso_CustomCycle ) + STATE ( "Torso_Action", idAI::State_Torso_Action ) + STATE ( "Torso_FinishAction", idAI::State_Torso_FinishAction ) + STATE ( "Torso_Pain", idAI::State_Torso_Pain ) + STATE ( "Torso_ScriptedAnim", idAI::State_Torso_ScriptedAnim ) + STATE ( "Torso_PassiveIdle", idAI::State_Torso_PassiveIdle ) + STATE ( "Torso_PassiveFidget", idAI::State_Torso_PassiveFidget ) + + // Leg States + STATE ( "Legs_Idle", idAI::State_Legs_Idle ) + STATE ( "Legs_Move", idAI::State_Legs_Move ) + STATE ( "Legs_MoveThink", idAI::State_Legs_MoveThink ) + STATE ( "Legs_TurnLeft", idAI::State_Legs_TurnLeft ) + STATE ( "Legs_TurnRight", idAI::State_Legs_TurnRight ) + STATE ( "Legs_ChangeDirection", idAI::State_Legs_ChangeDirection ) + + // Head States + STATE ( "Head_Idle", idAI::State_Head_Idle ) + +END_CLASS_STATES + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +/* +================ +idAI::State_TriggerAnim +================ +*/ +stateResult_t idAI::State_TriggerAnim ( const stateParms_t& parms ) { + const char* triggerAnim; + + // If we dont have the trigger anim, just skip it + triggerAnim = spawnArgs.GetString ( "trigger_anim" ); + if ( !*triggerAnim || !HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + gameLocal.Warning ( "Missing or invalid 'trigger_anim' ('%s') specified for entity '%s'", + triggerAnim, GetName() ); + return SRESULT_DONE; + } + + Show(); + ScriptedAnim ( triggerAnim, 4, false, true ); + + // FIXME: should do this a better way + // Alwasy let trigger anims play out + fl.neverDormant = true; + + return SRESULT_DONE; +} + +/* +================ +idAI::State_WakeUp +================ +*/ +stateResult_t idAI::State_WakeUp ( const stateParms_t& parms ) { + const char* triggerAnim; + + WakeUp ( ); + + // Start immeidately into a playback? + if( spawnArgs.FindKey( "playback_intro" ) ){ + ScriptedPlaybackMove ( "playback_intro", PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, 0 ); + // Start immeidately into a scripted anim? + } else if ( spawnArgs.GetString ( "trigger_anim", "", &triggerAnim) && *triggerAnim && HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + PostState ( "State_TriggerAnim" ); + return SRESULT_DONE; + } else { + // Either wait to be triggered or wait until they have an enemy + if ( spawnArgs.GetBool ( "trigger" ) ) { + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + + PostState ( "Wait_Activated" ); + } + } + + PostState ( "State_Combat" ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Passive +================ +*/ +stateResult_t idAI::State_Passive ( const stateParms_t& parms ) { + if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + if ( UpdateTactical ( TACTICALUPDATE_PASSIVEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Combat + +The base combat state is basically the clearing house for other combat states. By calling +UpdateTactical with a timer of zero it ensures a better tactical move can be found. +================ +*/ +stateResult_t idAI::State_Combat ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + combat.tacticalCurrent = AITACTICAL_NONE; + + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Update the tactical state using all available tactical abilities and reset + // the current tactical state since this is the generic state. + combat.tacticalCurrent = AITACTICAL_NONE; + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If we are here then there isnt a single combat state available, thats not good + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatCover + +Seek out a cover point that has a vantage point on the enemy. Stay there firing at the +enemy until the cover point is invalidated. +================ +*/ +stateResult_t idAI::State_CombatCover ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_ATTACK, + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done && aasSensor->Reserved ( ) ) { + StopMove ( MOVE_STATUS_DONE ); + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we dont have a cover point anymore then just bail out + if ( !aasSensor->Reserved ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // If we have moved off our cover point, move back + if ( DistanceTo ( aasSensor->ReservedOrigin ( ) ) > 8.0f ) { + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + } + // Dont do any cover checks until they are facing towards the wall + if ( !FacingIdeal ( ) ) { + return SRESULT_WAIT; + } + // Perform cover point actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +idAI::State_CombatMelee + +Head directly towards our enemy but allow ranged actions along the way +================ +*/ +stateResult_t idAI::State_CombatMelee ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the enemy + STAGE_ATTACK, // Keep attacking until no longer in melee range + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // If we can no longer get to our enemy, give up on this and do something else! + if ( move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we are out of melee range or lost sight of our enemy then start moving again + if ( !IsEnemyVisible ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Always face enemy when in melee range + TurnToward ( enemy.lastKnownPosition ); + + // Perform actions while standing still + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatRanged + +Move towards enemy until within firing range then continue to shoot at the enemy +================ +*/ +stateResult_t idAI::State_CombatRanged ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move to an attack position + STAGE_ATTACK, // Perform actions until the enemy is out of range or not visible + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // if we lost our enemy we are done here + if ( !enemy.ent ) { + ForceTacticalUpdate ( ); + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + // If done moving or within range of a visible enemy we can stop + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Stop early because we have a shot on our enemy? + if ( !move.fl.noRangedInterrupt && + !aifl.simpleThink && enemy.fl.visible + && gameLocal.time - enemy.lastVisibleChangeTime > 500 + && enemy.range >= combat.attackRange[0] + && enemy.range <= combat.attackRange[1] + && ( DistanceTo ( move.moveDest ) < DistanceTo ( move.lastMoveOrigin ) ) + && (!tether || tether->ValidateDestination ( this, physicsObj.GetOrigin( ) ) ) + && aiManager.ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If the enemy is visible but not in fov then rotate + if ( !enemy.fl.inFov && enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatTurret + +Stay put and shoot at the enemy when nearby +================ +*/ +stateResult_t idAI::State_CombatTurret ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) && !enemy.fl.inFov ) { + TurnToward ( enemy.lastKnownPosition ); + // Turn towards tether direction if we have no enemy + } else if ( !enemy.ent && tether ) { + TurnToward ( physicsObj.GetOrigin() + tether->GetPhysics()->GetAxis()[0] * 64.0f ); + } else if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If there are other available tactical states besides turret then try to + // go to one of them + if ( combat.tacticalMaskAvailable & ~(1<GetPhysics()->GetAxis()[0]; + TurnToward ( toPos ); + if ( !IsBehindCover() ) { + //Do a trace *once* to see if I can crouch-look in the direction of the tether at this point + trace_t tr; + idVec3 crouchEye = physicsObj.GetOrigin( ); + crouchEye.z += 32.0f; + gameLocal.TracePoint( this, tr, crouchEye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + combat.fl.crouchViewClear = true; + } + } + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } + // Update tactical state occasionally to see if there is something better to do + if ( UpdateTactical ( TACTICALUPDATE_MOVETETHERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MoveFollow +================ +*/ +stateResult_t idAI::State_MoveFollow ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + // If we lost our leader we are done + if ( !leader ) { + move.fl.done = true; + // Can we just stop here? + } else if ( DistanceTo ( leader ) < (move.followRange[0]+move.followRange[1])*0.5f && aiManager.ValidateDestination ( this, physicsObj.GetOrigin( ), false, leader ) ) { + move.fl.done = true; + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + move.fl.done = false; + } + */ + } + // Are we done moving? + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + if ( leader ) { + TurnToward ( leader->GetPhysics()->GetOrigin() ); + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else { + if ( UpdateTactical ( TACTICALUPDATE_FOLLOWDELAY ) ) { + return SRESULT_DONE_WAIT; + } + } + + // Perform actions on the way to the enemy + UpdateAction ( ); + + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MovePlayerPush + +Move out of the way when the player is pushing us +================ +*/ +stateResult_t idAI::State_MovePlayerPush ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + if ( move.fl.done ) { + StopMove(MOVE_STATUS_DONE); + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else if ( gameLocal.GetTime() - move.startTime > 2000 ) { + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Killed +================ +*/ +stateResult_t idAI::State_Killed ( const stateParms_t& parms ) { + disablePain = true; + + //quickburning subjects skip all this jazz + if( fl.quickBurn ) { + PostState ( "State_Dead" ); + return SRESULT_DONE; + } + + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + // Make sure all animations stop + animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; +} + + +/* +================ +idAI::State_Dead +================ +*/ +stateResult_t idAI::State_Dead ( const stateParms_t& parms ) { + if ( !fl.hidden ) { + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + if( fl.quickBurn ) { + StopRagdoll(); + PostState ( "State_Burn", SEC2MS(0.05f) ); + } else if ( spawnArgs.GetString( "fx_burn_lightning", NULL ) ) { + lightningNextTime = 0; + lightningEffects = 0; + PostState ( "State_LightningDeath", SEC2MS(burnDelay) ); + } else { + PostState ( "State_Burn", SEC2MS(burnDelay) ); + } + } + float removeDelay = SEC2MS ( spawnArgs.GetFloat ( "removeDelay" ) ); + if ( removeDelay >= 0.0f ) { + PostState ( "State_Remove", removeDelay ); + } + } else { + PostState ( "State_Remove" ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_LightningDeath +================ +*/ +stateResult_t idAI::State_LightningDeath ( const stateParms_t& parms ) { + if ( gameLocal.time > lightningNextTime ) { + if ( !lightningEffects ) + { + StartSound ( "snd_burn_lightning", SND_CHANNEL_BODY, 0, false, NULL ); + } + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( spawnArgs, "fx_burn_lightning" ), this, 100 ); + effect->Play ( gameLocal.time, false ); + lightningNextTime = gameLocal.time + 100; + lightningEffects++; + } + if ( lightningEffects < 10 ) + { + return SRESULT_WAIT; + } + + /* + if ( spawnArgs.GetString( "fx_burn_lightning" ) ) + { + for ( int i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + if ( !gameLocal.random.RandomInt(1) ) { + PlayEffect( "fx_burn_lightning", (jointHandle_t)i ); + } + } + } + } + */ + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + PostState ( "State_Burn", 0 ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Burn +================ +*/ +stateResult_t idAI::State_Burn ( const stateParms_t& parms ) { + if( fl.hidden ){ + return SRESULT_DONE; + } + + + renderEntity.noShadow = true; + + // Dont let the articulated figure be shot once they start burning away + SetCombatContents ( false ); + fl.takedamage = false; + + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + idEntity *head = GetHead(); + if ( head ) { + head->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + } + + UpdateVisuals(); + + if ( spawnArgs.GetString( "fx_burn_particles", NULL ) ) + { + int i = GetAnimator()->GetJointHandle( spawnArgs.GetString("joint_chestOffset","chest") ); + if ( i != INVALID_JOINT ) + { + PlayEffect( "fx_burn_particles_chest", (jointHandle_t)i ); + } + + for ( i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + PlayEffect( "fx_burn_particles", (jointHandle_t)i ); + } + } + //FIXME: head, too? + //FIXME: from joint to parent joint? + //FIXME: not small joints... + } + + StartSound ( "snd_burn", SND_CHANNEL_BODY, 0, false, NULL ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Remove +================ +*/ +stateResult_t idAI::State_Remove ( const stateParms_t& parms ) { + PostEventMS( &EV_Remove, 0 ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_ScriptedAnim +================ +*/ +stateResult_t idAI::State_Torso_ScriptedAnim ( const stateParms_t& parms ) { + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + aifl.scripted = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedMove +================ +*/ +stateResult_t idAI::State_ScriptedMove ( const stateParms_t& parms ) { + if ( !aifl.scripted || move.fl.done ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedFace +================ +*/ +stateResult_t idAI::State_ScriptedFace ( const stateParms_t& parms ) { + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedPlaybackMove +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + if ( mPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedPlaybackAim +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackAim ( const stateParms_t& parms ) { + if ( mLookPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedStop +================ +*/ +stateResult_t idAI::State_ScriptedStop ( const stateParms_t& parms ) { + ScriptedEnd ( ); + + // If ending in an idle animation move the legs back to idle and + // revert back to normal combat + if ( aifl.scriptedEndWithIdle ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + } + + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Torso States + +=============================================================================== +*/ + +/* +================ +idAI::State_Torso_Idle +================ +*/ +stateResult_t idAI::State_Torso_Idle ( const stateParms_t& parms ) { + // Custom passive idle? + if ( combat.tacticalCurrent == AITACTICAL_PASSIVE && passive.animIdlePrefix.Length() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the legs were disabled then get them back into idle again + if ( legsAnim.Disabled ( ) ) { + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + } + + // Start idle animation + const char* idleAnim = GetIdleAnimName (); + if ( !torsoAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_TORSO, idleAnim, parms.blendFrames ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_PassiveIdle +================ +*/ +stateResult_t idAI::State_Torso_PassiveIdle ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT, + STAGE_TALK_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + // Talk animation? + if ( passive.talkTime > gameLocal.time ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Find their talk animation + animName = spawnArgs.GetString ( va("%s_%s", passive.animTalkPrefix.c_str(), postfix.c_str() ) ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.GetString ( passive.animTalkPrefix ); + } + + if ( animName.Length ( ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_TALK_WAIT ); + } + } + + // If we have a fidget animation then see if its time to play it + if ( passive.animFidgetPrefix.Length() ) { + if ( passive.fidgetTime == 0 ) { + passive.fidgetTime = gameLocal.time + SEC2MS ( spawnArgs.GetInt ( "fidget_rate", "20" ) ); + } else if ( gameLocal.time > passive.fidgetTime ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveFidget", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", 4 ); + return SRESULT_DONE; + } + } + + // Do we have a custom idle animation? + passive.idleAnim = spawnArgs.RandomPrefix ( passive.animIdlePrefix, gameLocal.random ); + passive.idleAnimChangeTime = passive.fl.multipleIdles ? SEC2MS ( spawnArgs.GetFloat ( "idle_change_rate", "10" ) ) : 0; + + // Is there a start animation for the idle? + animName = va("%s_start", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, 0 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + // Talk animation interrupting? + if ( passive.animTalkPrefix.Length() && passive.talkTime > gameLocal.time ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If its time to play a new idle animation then do so + if ( passive.idleAnimChangeTime && gameLocal.time > passive.idleAnimChangeTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If its time to fidget then do so + if ( passive.fidgetTime && gameLocal.time > passive.fidgetTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // Loop the idle again + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va("%s_end", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_STAGE ( STAGE_START ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + + case STAGE_TALK_WAIT: + if ( gameLocal.time > passive.talkTime ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_PassiveFidget +================ +*/ +stateResult_t idAI::State_Torso_PassiveFidget ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_INIT: + passive.fidgetTime = 0; + animName = spawnArgs.RandomPrefix ( va("anim_%sfidget", passive.prefix.c_str() ), gameLocal.random ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.RandomPrefix ( "anim_fidget", gameLocal.random ); + } + if ( !animName.Length ( ) ) { + return SRESULT_DONE; + } + if ( !PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_ScriptedJumpDown + +Face edge, walk off +================ +*/ +stateResult_t idAI::State_ScriptedJumpDown( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FACE_WAIT, + STAGE_ANIM, + STAGE_JUMP_WAIT, + STAGE_INAIR, + STAGE_WAIT_LAND, + STAGE_ANIM_WAIT, + STAGE_FINISH + }; + + switch ( parms.stage ) { + case STAGE_INIT: + { + aifl.scripted = true; + Event_SaveMove(); + StopMove(MOVE_STATUS_DONE); + //move.current_yaw = move.ideal_yaw; + move.moveCommand = MOVE_NONE; + move.fl.allowDirectional = false; + } + if ( !move.fl.onGround ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_STAGE ( STAGE_FACE_WAIT ); + break; + case STAGE_FACE_WAIT: + if ( FacingIdeal() ) + { + return SRESULT_STAGE ( STAGE_ANIM ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM: + { + DisableAnimState ( ANIMCHANNEL_LEGS ); + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + if ( HasAnim( ANIMCHANNEL_TORSO, "jumpdown_start" ) ) { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_start", 4 ); + move.fl.allowAnimMove = true; + move.fl.noGravity = true; + return SRESULT_STAGE ( STAGE_JUMP_WAIT ); + } + return SRESULT_STAGE ( STAGE_INAIR ); + } + break; + case STAGE_JUMP_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_WAIT; + break; + case STAGE_INAIR: + { + idVec3 vel = viewAxis[0] * 200.0f; + vel.z = -50.0f; + physicsObj.SetLinearVelocity( vel ); + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_loop", 4 ); + move.fl.allowAnimMove = false; + move.fl.noGravity = false; + return SRESULT_STAGE ( STAGE_WAIT_LAND ); + } + break; + case STAGE_WAIT_LAND: + if ( physicsObj.OnGround() )//GetPhysics()->HasGroundContacts() ) + { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_end", 0 ); + move.fl.allowAnimMove = true; + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 ) ) + { + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + break; + case STAGE_FINISH: + Event_RestoreMove(); + SetState( "State_Combat" ); + aifl.scripted = false; + return SRESULT_DONE; + break; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_CustomCycle +================ +*/ +stateResult_t idAI::State_Torso_CustomCycle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Sight +================ +*/ +stateResult_t idAI::State_Torso_Sight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + // Dont let everyone on the team play their sight sounds at the same time + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT ) ) { + Speak ( "lipsync_sight", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT, 1000 ); + } + + // Execute sighted scripts + if( !enemy.fl.sighted ) { + enemy.fl.sighted = true; + ExecScriptFunction ( funcs.first_sight, this ); + } + ExecScriptFunction ( funcs.sight ); + + idStr animName = spawnArgs.GetString ( "anim_sight", spawnArgs.GetString ( "sight_anim" ) ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_DONE; + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_Action +================ +*/ +stateResult_t idAI::State_Torso_Action ( const stateParms_t& parms ) { + // Invalid animation so dont bother with running the action + if ( actionAnimNum == -1 ) { + return SRESULT_DONE_WAIT; + } + + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + + // Wait till animation is finished + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", parms.blendFrames ); + + return SRESULT_DONE_WAIT; +} + +/* +================ +idAI::State_Torso_FinishAction +================ +*/ +stateResult_t idAI::State_Torso_FinishAction ( const stateParms_t& parms ) { + + RestoreFlag ( AIFLAGOVERRIDE_DISABLEPAIN ); + RestoreFlag ( AIFLAGOVERRIDE_DAMAGE ); + RestoreFlag ( AIFLAGOVERRIDE_NOTURN ); + RestoreFlag ( AIFLAGOVERRIDE_NOGRAVITY ); + + enemy.fl.lockOrigin = false; + aifl.action = false; + + OnStopAction ( ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Pain +================ +*/ +stateResult_t idAI::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + pain.loopEndTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "pain_maxLoopTime", "1" ) ); + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + animName = va( "%s_start", painAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 1 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle ( ANIMCHANNEL_TORSO, painAnim, 1 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + if ( gameLocal.time - pain.lastTakenTime > AI_PAIN_LOOP_DELAY ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( !pain.loopEndTime || gameLocal.time > pain.loopEndTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va( "%s_end", painAnim.c_str() ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, 1 ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Head States + +=============================================================================== +*/ + +/* +================ +idAI::State_Head_Idle +================ +*/ +stateResult_t idAI::State_Head_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idAI::State_Legs_Idle +================ +*/ +stateResult_t idAI::State_Legs_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + const char* idleAnim; + move.fl.allowAnimMove = false; + idleAnim = GetIdleAnimName ( ); + if ( !legsAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_LEGS, idleAnim, parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + // Dont let the legs do anything from idle until they are done blending + // the animations they have (this is to prevent pops when an action finishes + // and blends to idle then immediately the legs move to walk) + if ( animator.IsBlending ( ANIMCHANNEL_LEGS, gameLocal.time ) ) { + return SRESULT_WAIT; + } + + if ( move.fl.moving && CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Legs_TurnLeft +================ +*/ +stateResult_t idAI::State_Legs_TurnLeft ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_TurnRight +================ +*/ +stateResult_t idAI::State_Legs_TurnRight ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_Move +================ +*/ +stateResult_t idAI::State_Legs_Move ( const stateParms_t& parms ) { + idStr animName; + + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // Movement direction changed? + if ( move.idealDirection != move.currentDirection ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_ChangeDirection", parms.blendFrames ); + } + + // Make sure run status is up to date when legs start moving + UpdateRunStatus ( ); + + // Run or walk? + animName = "run"; + if ( !move.fl.idealRunning && HasAnim ( ANIMCHANNEL_TORSO, "walk" ) ) { + animName = "walk"; + } + + // Append the run direction to the animation name + if ( move.idealDirection == MOVEDIR_LEFT ) { + animName = animName + "_left"; + } else if ( move.idealDirection == MOVEDIR_RIGHT ) { + animName = animName + "_right"; + } else if ( move.idealDirection == MOVEDIR_BACKWARD ) { + animName = animName + "_backwards"; + } else { +/* + // When moving to cover the walk animation is special + if ( move.moveCommand == MOVE_TO_COVER && !move.fl.idealRunning && move.fl.running ) { + animName = "run_slowdown"; + } +*/ + } + + // Update running flag with ideal state + move.fl.running = move.fl.idealRunning; + move.currentDirection = move.idealDirection; + + PlayCycle ( ANIMCHANNEL_LEGS, animName, parms.blendFrames ); + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_MoveThink", parms.blendFrames ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_MoveThink +================ +*/ +stateResult_t idAI::State_Legs_MoveThink ( const stateParms_t& parms ) { + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving anymore then go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the run state has changed restart the leg movement + if ( UpdateRunStatus ( ) || (move.idealDirection != move.currentDirection) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 4 ); + return SRESULT_DONE; + } + + // Just wait a frame before doing anything else + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Legs_ChangeDirection +================ +*/ +stateResult_t idAI::State_Legs_ChangeDirection ( const stateParms_t& parms ) { + if ( FacingIdeal ( ) || (move.idealDirection != move.currentDirection) ) { + return SRESULT_DONE; + } + + move.fl.allowAnimMove = false; + move.fl.allowPrevAnimMove = true; + return SRESULT_WAIT; +} + +/* +=============================================================================== + + State helpers + +=============================================================================== +*/ + +/* +================ +idAI::UpdateRunStatus +================ +*/ +bool idAI::UpdateRunStatus ( void ) { + // Get new run status + if ( !move.fl.moving || !CanMove() ) { + move.fl.idealRunning = false; + } else if ( move.walkRange && !ai_useRVMasterMove.GetBool() && move.moveCommand != MOVE_TO_ENEMY && (DistanceTo ( move.moveDest ) - move.range) < move.walkRange ) { + move.fl.idealRunning = false; + } else if ( IsTethered() && tether->IsRunForced ( ) ) { + move.fl.idealRunning = true; + } else if ( IsTethered() && tether->IsWalkForced ( ) ) { + move.fl.idealRunning = false; + } else if ( move.fl.noWalk ) { + move.fl.idealRunning = true; + } else if ( move.fl.noRun ) { + move.fl.idealRunning = false; + } else if ( move.walkRange ) { + if ( move.fl.obstacleInPath ) { + move.fl.idealRunning = false; + } else if ( InLookAtCoverMode() ) { + move.fl.idealRunning = false; + } else if ( !ai_useRVMasterMove.GetBool() && move.walkTurn && !move.fl.allowDirectional && fabs(GetTurnDelta ( )) > move.walkTurn ) { + move.fl.idealRunning = false; + } else { + move.fl.idealRunning = true; + } + } else { + move.fl.idealRunning = true; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +idAI::UpdateTactical + +This method is a recursive method that will determine the best tactical state to be in +from the given available states. +================ +*/ +bool idAI::UpdateTactical ( int delay ) { + // Update tactical cannot be called while performing an action and it must be called from the main state loop + assert ( !aifl.action ); + + // No movement updating on simple think (if the update is forced do it anyway) + if ( combat.tacticalUpdateTime && aifl.simpleThink && !combat.tacticalMaskUpdate ) { + return false; + } + // Don't let tactical updates pre-empt pain actions + if ( pain.takenThisFrame ) { + return false; + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Start ( ); + } + + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Stop ( ); + } + + // We have sighted an enemy so execute the sight state + if ( IsEnemyVisible() && !enemy.fl.sighted ) { + PerformAction ( "Torso_Sight", 4, true ); + return true; + } + + // continue with the last updatetactical if we still have bits left to check in our update mask + if ( !combat.tacticalMaskUpdate ) { + // Ignore the tactical delay if we are taking too much damage here. + // TODO: use skipcurrentdestination instead + if ( !combat.tacticalPainThreshold || combat.tacticalPainTaken < combat.tacticalPainThreshold ) { + // Delay tactical updating? + if ( combat.tacticalUpdateTime && move.moveStatus != MOVE_STATUS_BLOCKED_BY_ENEMY && delay && (gameLocal.time - combat.tacticalUpdateTime) < delay ) { + return false; + } + } + + // handle Auto break tethers + if ( IsTethered ( ) && tether->IsAutoBreak ( ) && IsWithinTether ( ) ) { + tether = NULL; + } + + // Filter the tactical + combat.tacticalMaskUpdate = FilterTactical ( combat.tacticalMaskAvailable ); + } else { + // Make sure all cached tactical updates are still valid + combat.tacticalMaskUpdate &= FilterTactical ( combat.tacticalMaskAvailable ); + if ( !combat.tacticalMaskUpdate ) { + return false; + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Start ( ); + } + + // Recursively look for a better tactical state + bool result = UpdateTactical_r ( ); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Stop ( ); + } + + return result; +} + +bool idAI::UpdateTactical_r ( void ) { + // Mapping of tactical types to combat states + static const char* tacticalState [ ] = { + "State_Combat", // AITACTICAL_NONE + "State_CombatMelee", // AITACTICAL_MELEE + "State_MoveFollow", // AITACTICAL_MOVE_FOLLOW + "State_MoveTether", // AITACTICAL_MOVE_TETHER + "State_MovePlayerPush", // AITACTICAL_MOVE_PLAYERPUSH + "State_CombatCover", // AITACTICAL_COVER + "State_CombatCover", // AITACTICAL_COVER_FLANK + "State_CombatCover", // AITACTICAL_COVER_ADVANCE + "State_CombatCover", // AITACTICAL_COVER_RETREAT + "State_CombatCover", // AITACTICAL_COVER_AMBUSH + "State_CombatRanged", // AITACTICAL_RANGED + "State_CombatTurret", // AITACTICAL_TURRET + "State_CombatHide", // AITACTICAL_HIDE + "State_Passive", // AITACTICAL_PASSIVE + }; + + if ( g_perfTest_aiStationary.GetBool() ) { + return false; + } + // Determine the new tactical state + aiTactical_t newTactical = AITACTICAL_TURRET; + + if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_PLAYERPUSH_BIT ) { + newTactical = AITACTICAL_MOVE_PLAYERPUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_FOLLOW_BIT ) { + newTactical = AITACTICAL_MOVE_FOLLOW; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_ADVANCE_BIT ) { + newTactical = AITACTICAL_COVER_ADVANCE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_FLANK_BIT ) { + newTactical = AITACTICAL_COVER_FLANK; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_RETREAT_BIT ) { + newTactical = AITACTICAL_COVER_RETREAT; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_AMBUSH_BIT ) { + newTactical = AITACTICAL_COVER_AMBUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_BIT ) { + newTactical = AITACTICAL_COVER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_RANGED_BIT ) { + newTactical = AITACTICAL_RANGED; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MELEE_BIT ) { + newTactical = AITACTICAL_MELEE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_TETHER_BIT ) { + newTactical = AITACTICAL_MOVE_TETHER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_HIDE_BIT ) { + newTactical = AITACTICAL_HIDE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_PASSIVE_BIT ) { + newTactical = AITACTICAL_PASSIVE; + } + + // Remove the new tactical from the available in case we have to recursively call ourself + combat.tacticalMaskUpdate &= ~(1<IsType ( idPlayer::GetClassType ( ) ) ) { + //no current pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, just continue it + availableTactical = AITACTICAL_NONE_BIT; + return availableTactical; + } + //stop pushing away + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } else { + //have a pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, only allow it to continue playerpush, but can re-calc, if needbe + availableTactical = AITACTICAL_MOVE_PLAYERPUSH_BIT; + return availableTactical; + } + if ( ai_playerPushAlways.GetBool() ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + case AITACTICAL_MOVE_FOLLOW: + case AITACTICAL_MOVE_TETHER: + case AITACTICAL_MOVE_PLAYERPUSH: + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + case AITACTICAL_RANGED: + case AITACTICAL_HIDE: + //okay to be pushed by players + break; + default: + if ( !leader ) { + //no leader and not in a moving tactical state, don't be pushed by players + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + break; + } + } else if ( !leader || (move.fl.moving&&move.goalEntity!=leader) ) { + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + } + } + + // No tether move if not tethered + if ( !IsTethered ( ) ) { + availableTactical &= ~(AITACTICAL_MOVE_TETHER_BIT); + + // Filter out any tactical states that require a leader + if ( !leader || (enemy.ent && IsEnemyVisible() && enemy.range < move.followRange[1] * 2.0f ) ) { + availableTactical &= ~(AITACTICAL_MOVE_FOLLOW_BIT); + } else if ( leader && !enemy.ent ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + // When tethered disable actions that dont adhear to the tether. + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_HIDE_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); + } + + // If we dont have an enemy we can filter out pure combat states + if ( !enemy.ent ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_MELEE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + + // If we arent aware either then we cant take cover + if ( !combat.fl.aware ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + } else { + // Dont advance or flank our enemy if we have seen them recently + if ( IsEnemyRecentlyVisible ( 0.5f ) ) { + availableTactical &= ~(AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT); + } + + // FIXME: need conditions for these cover states + availableTactical &= ~(AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_COVER_RETREAT_BIT); + } + + // Filter out all cover states if cover is disabled + if ( ai_disableCover.GetBool ( ) ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + + //if we need to fight in melee then fight in melee! + if( IsMeleeNeeded( ) ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + } + + return availableTactical; +} + +/* +================ +idAI::CheckTactical + +Returns 'AICHECKTACTICAL_MOVE' if we should use the given tactical state and execute a movement to do so +Returns 'AICHECKTACTICAL_SKIP' if the given tactical state should be skipped +Returns 'AICHECKTACTICAL_NOMOVE' if the given tactical state should be used but no movement is required +================ +*/ +aiCTResult_t idAI::CheckTactical ( aiTactical_t tactical ) { + // Handle non movement tactical states first + if ( BIT(tactical) & AITACTICAL_NONMOVING_BITS ) { + // If we are moving + if ( move.fl.moving ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + // If we are tethered always check that first + if ( IsTethered ( ) ) { + if ( !move.fl.moving || tactical != combat.tacticalCurrent ) { + // We stopped out of tether range so try a new move to get back in + if ( !tether->ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + return AICTRESULT_OK; + } + } else { + // Move is out of tether range, so look for another one + if ( !tether->ValidateDestination ( this, move.moveDest ) ) { + return AICTRESULT_OK; + } + } + } + + // Anything wrong with our current destination, if so pick a new move + if ( SkipCurrentDestination ( ) ) { + return AICTRESULT_OK; + } + + switch ( tactical ) { + case AITACTICAL_MOVE_FOLLOW: + // If already moving to our leader dont move again + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == leader ) { + return AICTRESULT_NOMOVE; + } + // If not moving and within follow range we can skip the follow state + if ( !move.fl.moving ) { + if ( DistanceTo ( leader ) < move.followRange[1] ) { + //unless the leader is on an elevator we should be standing on... + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + return AICTRESULT_OK; + } + */ + return AICTRESULT_SKIP; + } + } + return AICTRESULT_OK; + + case AITACTICAL_MOVE_TETHER: + // If not moving we dont want to idle in the move state so skip it + if ( !move.fl.moving ) { + return AICTRESULT_SKIP; + } + // We are still heading towards our tether so dont reissue a move + return AICTRESULT_NOMOVE; + + case AITACTICAL_RANGED: + // Currently issuing a find + if ( dynamic_cast(aasFind) ) { + return AICTRESULT_OK; + } + + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + // If the enemy is visible then no movement is required for ranged combat + if ( !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + if ( !move.fl.moving ) { + // If not within the attack range we need to move + if ( enemy.range > combat.attackRange[1] || enemy.range < combat.attackRange[0] ) { + return AICTRESULT_OK; + } + + // If we havent seen our enemy for a while and arent moving, then get moving! + if ( enemy.lastVisibleTime && gameLocal.time - enemy.lastVisibleTime > RANGED_ENEMYDELAY ) { + return AICTRESULT_OK; + } + } else { + // Is our destination out of range? + if ( (move.moveDest - enemy.lastKnownPosition).LengthSqr ( ) > Square ( combat.attackRange[1] ) ) { + return AICTRESULT_OK; + } + } + // Our position is fine so just stay in ranged combat + return AICTRESULT_NOMOVE; + + case AITACTICAL_MELEE: + // IF we are already moving towards our enemy then we dont need a state change + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + return AICTRESULT_NOMOVE; + } + return AICTRESULT_OK; + + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + if ( enemy.ent && !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + // No longer behind cover? + if ( !IsBehindCover ( ) ) { + return AICTRESULT_OK; + } + // Move if there have been too many close calls + if ( combat.tacticalFlinches && combat.tacticalFlinches > gameLocal.random.RandomInt(12) + 3 ) { + return AICTRESULT_OK; + } + // Move if cover destination isnt valid anymore + if ( !IsCoverValid() ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + return AICTRESULT_OK; +} + +/* +================ +idAI::WakeUpTargets +================ +*/ +void idAI::WakeUpTargets ( void ) { + const idKeyValue* kv; + + for ( kv = spawnArgs.MatchPrefix ( "wakeup_target" ); kv; kv = spawnArgs.MatchPrefix ( "wakeup_target", kv ) ) { + idEntity* ent; + ent = gameLocal.FindEntity ( kv->GetValue ( ) ); + if ( !ent ) { + gameLocal.Warning ( "Unknown wakeup_target '%s' on entity '%s'", kv->GetValue().c_str(), GetName() ); + } else { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + ent->TriggerGuis ( ); + } + } + + // Find all the tether entities we target + const char* target; + if ( spawnArgs.GetString ( "tether_target", "", &target ) && *target ) { + idEntity* ent; + ent = gameLocal.FindEntity ( target ); + if ( ent && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + ProcessEvent ( &EV_Activate, ent ); + } + } +} + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idAI::State_Wait_Activated + +Stop the state thread until the ai is activated +================ +*/ +stateResult_t idAI::State_Wait_Activated ( const stateParms_t& parms ) { + if ( (aifl.activated || aifl.pain ) && CanBecomeSolid ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_Action + +Stop the state thread as long as a torso action is running and allow a pain action to interrupt +================ +*/ +stateResult_t idAI::State_Wait_Action ( const stateParms_t& parms ) { + if ( CheckPainActions ( ) ) { + // Our current action is being interrupted by pain so make sure the action can be performed + // immediately after coming out of the pain. + actionTime = actionSkipTime; + return SRESULT_DONE; + } + + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ActionNoPain + +Stop the state thread as long as a torso action is running +================ +*/ +stateResult_t idAI::State_Wait_ActionNoPain ( const stateParms_t& parms ) { + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ScriptedDone + +Stop the state thread as a scripted sequence is active +================ +*/ +stateResult_t idAI::State_Wait_ScriptedDone ( const stateParms_t& parms ) { + if ( aifl.scripted ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} diff --git a/src/game/ai/AI_Tactical.cpp b/src/game/ai/AI_Tactical.cpp new file mode 100644 index 0000000..1d4289e --- /dev/null +++ b/src/game/ai/AI_Tactical.cpp @@ -0,0 +1,1078 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// TODO: +// - unarmed posture +// - relaxed posture +// - turning in place too much, rely more on look angles? + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Tactical.h" + +const idEventDef AI_ForcePosture ( "forcePosture", "d" ); + +CLASS_DECLARATION( idAI, rvAITactical ) + EVENT( AI_ForcePosture, rvAITactical::Event_ForcePosture ) + EVENT( EV_PostSpawn, rvAITactical::Event_PostSpawn ) +END_CLASS + +static const char* aiPostureString[AIPOSTURE_MAX] = { + "stand", // AIPOSTURE_STAND, + "crouch", // AIPOSTURE_CROUCH, + "cover_left", // AIPOSTURE_STAND_COVER_LEFT, + "cover_right", // AIPOSTURE_STAND_COVER_RIGHT, + "crouch_cover", // AIPOSTURE_CROUCH_COVER + "crouch_cover_left", // AIPOSTURE_CROUCH_COVER_LEFT, + "crouch_cover_right", // AIPOSTURE_CROUCH_COVER_RIGHT, + "relaxed", // AIPOSTURE_RELAXED + "unarmed", // AIPOSTURE_UNARMED + "at_attention", // AIPOSTURE_AT_ATTENTION +}; + +/* +================ +rvAITactical::rvAITactical +================ +*/ +rvAITactical::rvAITactical ( void ) { + shots = 0; + nextWallTraceTime = 0; +} + +void rvAITactical::InitSpawnArgsVariables ( void ) +{ + // Initialize the posture info + InitPostureInfo ( ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate", "1" ) ); + + healthRegen = spawnArgs.GetInt( "healthRegen", "2" ); + healthRegenEnabled = spawnArgs.GetBool( "healthRegenEnabled", "0" ); +} + +/* +================ +rvAITactical::Spawn +================ +*/ +void rvAITactical::Spawn ( void ) { + InitSpawnArgsVariables(); + + // Force a posture? + const char* temp; + postureForce = AIPOSTURE_DEFAULT; + if ( spawnArgs.GetString ( "forcePosture", "", &temp ) && *temp ) { + for ( postureForce = AIPOSTURE_STAND; postureForce != AIPOSTURE_MAX; ((int&)postureForce)++ ) { + if ( !idStr::Icmp ( aiPostureString[postureForce], temp ) ) { + break; + } + } + if ( postureForce >= AIPOSTURE_MAX ) { + postureForce = AIPOSTURE_DEFAULT; + } + } + + UpdatePosture ( ); + postureCurrent = postureIdeal; + + OnPostureChange ( ); + + ammo = spawnArgs.GetInt ( "ammo", "-1" ); + + // Initialize custom actions + actionElbowAttack.Init ( spawnArgs, "action_elbowAttack", NULL, AIACTIONF_ATTACK ); + actionKillswitchAttack.Init ( spawnArgs, "action_killswitchAttack", NULL, AIACTIONF_ATTACK ); + + actionTimerPeek.Init ( spawnArgs, "actionTimer_peek" ); + +// playerFocusTime = 0; +// playerAnnoyTime = SEC2MS(spawnArgs.GetFloat ( "annoyed", "5" )); + + healthRegenNextTime = 0; + maxHealth = health; +} + +/* +================ +rvAITactical::Think +================ +*/ +void rvAITactical::Think ( void ) { + idAI::Think ( ); + + // If not simple thinking and not in an action, update the posture + if ( !(aifl.scripted&&move.moveCommand==MOVE_NONE) && aifl.awake && !aifl.simpleThink && !aifl.action && !aifl.dead ) { + if ( UpdatePosture ( ) ) { + PerformAction ( "Torso_SetPosture", 4, true ); + } + } + +// FIXME: disabled for now, its annoying people + /* + idPlayer* localPlayer; + localPlayer = gameLocal.GetLocalPlayer(); + + // If the player has been standing in front of the marine and looking at him for too long he should say something + if ( !aifl.dead && playerFocusTime && playerAnnoyTime + && !aifl.scripted && focusType == AIFOCUS_PLAYER && localPlayer && !IsSpeaking() + && !localPlayer->IsBeingTalkedTo() //nobody else is talking to him right now + && DistanceTo( localPlayer ) < 64.0f ) { + idVec3 diff; + + + diff = GetPhysics()->GetOrigin() - localPlayer->GetPhysics()->GetOrigin(); + diff.NormalizeFast(); + + // Is the player looking at the marine? + if ( diff * localPlayer->viewAxis[0] > 0.7f ) { + // Say something every 5 seconds + if ( gameLocal.time - playerFocusTime > playerAnnoyTime ) { + // Debounce it against other marines + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU ) ) { + Speak ( "lipsync_canihelpyou", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU, 5000 ); + } + } + } else { + playerFocusTime = gameLocal.time; + } + } else { + playerFocusTime = gameLocal.time; + } + */ + + if ( health > 0 ) { + //alive + if ( healthRegenEnabled && healthRegen ) { + if ( gameLocal.GetTime() >= healthRegenNextTime ) { + health = idMath::ClampInt( 0, maxHealth, health+healthRegen ); + healthRegenNextTime = gameLocal.GetTime() + 1000; + } + } + } + + //crappy place to do this, just testing + bool clearPrefix = true; + bool facingWall = false; + if ( move.fl.moving && InCoverMode() && combat.fl.aware ) { + clearPrefix = false; + if ( DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + facingWall = true; + } else if ( nextWallTraceTime < gameLocal.GetTime() ) { + //do an occasional check for solid architecture directly in front of us + nextWallTraceTime = gameLocal.GetTime() + gameLocal.random.RandomInt(750)+750; + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + if ( neckJoint != INVALID_JOINT ) { + GetJointWorldTransform ( neckJoint, gameLocal.GetTime(), start, axis ); + end = start + axis[0] * 32.0f; + } else { + start = GetEyePosition(); + start += viewAxis[0] * 8.0f;//still inside bbox + end = start + viewAxis[0] * 32.0f; + } + //trace against solid arcitecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, MASK_SOLID, this ); + if ( wallTrace.fraction < 1.0f ) { + facingWall = true; + } else { + clearPrefix = true; + } + } + } + + if ( facingWall ) { + if ( !animPrefix.Length() ) { + animPrefix = "nearcover"; + } + } else if ( clearPrefix && animPrefix == "nearcover" ) { + animPrefix = ""; + } +} + +/* +================ +rvAITactical::Save +================ +*/ +void rvAITactical::Save( idSaveGame *savefile ) const { + savefile->WriteSyncId(); + + savefile->WriteInt ( ammo ); + savefile->WriteInt ( shots ); + +// savefile->WriteInt ( playerFocusTime ); +// savefile->WriteInt ( playerAnnoyTime ); + + savefile->WriteInt ( (int&)postureIdeal ); + savefile->WriteInt ( (int&)postureCurrent ); + savefile->WriteInt ( (int&)postureForce ); + // TOSAVE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; // cnicholson: + savefile->WriteInt ( healthRegenNextTime ); + savefile->WriteInt ( maxHealth ); + savefile->WriteInt ( nextWallTraceTime ); + + + actionElbowAttack.Save ( savefile ); + actionKillswitchAttack.Save ( savefile ); + + actionTimerPeek.Save ( savefile ); +} + +/* +================ +rvAITactical::Restore +================ +*/ +void rvAITactical::Restore( idRestoreGame *savefile ) { + InitSpawnArgsVariables ( ); + savefile->ReadSyncId( "rvAITactical" ); + + savefile->ReadInt ( ammo ); + savefile->ReadInt ( shots ); + +// savefile->ReadInt ( playerFocusTime ); +// savefile->ReadInt ( playerAnnoyTime ); + + savefile->ReadInt ( (int&)postureIdeal ); + savefile->ReadInt ( (int&)postureCurrent ); + savefile->ReadInt ( (int&)postureForce ); + // TORESTORE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + savefile->ReadInt ( healthRegenNextTime ); + savefile->ReadInt ( maxHealth ); + savefile->ReadInt ( nextWallTraceTime ); + + actionElbowAttack.Restore ( savefile ); + actionKillswitchAttack.Restore ( savefile ); + + actionTimerPeek.Restore ( savefile ); + + UpdateAnimPrefix ( ); +} + +/* +================ +rvAITactical::CanTurn +================ +*/ +bool rvAITactical::CanTurn ( void ) const { + if ( !move.fl.moving && !postureInfo[postureCurrent].fl.canTurn ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvAITactical::CanMove +================ +*/ +bool rvAITactical::CanMove ( void ) const { + if ( !postureInfo[postureCurrent].fl.canMove ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvAITactical::CheckAction_Reload +================ +*/ +bool rvAITactical::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( ammo == 0 ) { + return true; + } + return false; +} + +/* +================ +rvAITactical::CheckActions +================ +*/ +bool rvAITactical::CheckActions ( void ) { + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // If we are pressed, fight-- do not break melee combat until you or the enemy is dead. + if ( IsMeleeNeeded ( )) { + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + //take no actions other than fighting + return false; + } + + // Handle any posture changes + if ( postureIdeal != postureCurrent ) { + PerformAction ( "Torso_SetPosture", 4, true ); + return true; + } + + // Reload takes precedence + if ( !move.fl.moving && postureInfo[postureCurrent].fl.canReload && ammo == 0 ) { + PerformAction ( "Torso_Reload", 4, false ); + return true; + } + + if ( IsBehindCover ( ) ) { + // If we have no enemy try peeking + if ( !IsEnemyRecentlyVisible ( ) ) { + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_PEEK ) ) { + if ( actionTimerPeek.IsDone ( actionTime ) ) { + actionTimerPeek.Reset ( actionTime, 0.5f ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_PEEK, 2000 ); + PerformAction ( "Torso_Cover_Peek", 4, false ); + return true; + } + } + } + + // Attacks from cover + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + // Kill switch attack from cover? + if ( postureInfo[postureCurrent].fl.canKillswitch && IsEnemyRecentlyVisible ( ) ) { + if ( PerformAction ( &actionKillswitchAttack, NULL, &actionTimerRangedAttack ) ) { + return true; + } + } + + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; + } + + // Standard attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + + // Ranged attack only if there is ammo + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +rvAITactical::CheckRelaxed + +Returns true if the marine should currently be in a relaxed state +================ +*/ +bool rvAITactical::CheckRelaxed ( void ) const { +// if ( forceRelaxed ) { +// return true; +// } + +/* + // If we have a leader, no enemy, and havent had an enemy for over 5 seconds go to relaxed + if ( leader && !enemy.ent && !tether && gameLocal.time - enemy.changeTime > 5000 ) { + return true; + } +*/ + + // Alwasy relaxed when ignoring enemies + if ( !combat.fl.aware ) { + return true; + } + + if ( enemy.ent || focusType != AIFOCUS_PLAYER || move.fl.moving || move.fl.crouching || talkState == TALK_OK ) { + return false; + } + + if ( gameLocal.time >= focusTime ) { + return false; + } + + return true; +} + +/* +================ +rvAITactical::GetIdleAnimName +================ +*/ +const char* rvAITactical::GetIdleAnimName ( void ) { + return "idle"; +} + +/* +================ +rvAITactical::UpdateAnimPrefix +================ +*/ +void rvAITactical::UpdateAnimPrefix ( void ) { + if ( postureCurrent == AIPOSTURE_STAND ) { + animPrefix = ""; + } else { + animPrefix = aiPostureString[postureCurrent]; + } +} + +/* +================ +rvAITactical::InitPostureInfo +================ +*/ +void rvAITactical::InitPostureInfo ( void ) { + int posture; + for ( posture = AIPOSTURE_DEFAULT + 1; posture < AIPOSTURE_MAX; posture ++ ) { + aiPostureInfo_t& info = postureInfo[(aiPosture_t)posture]; + + postureCurrent = (aiPosture_t)posture; + UpdateAnimPrefix ( ); + + info.fl.canMove = HasAnim ( ANIMCHANNEL_TORSO, "run", true ); + info.fl.canPeek = HasAnim ( ANIMCHANNEL_TORSO, "peek", true ); + info.fl.canReload = HasAnim ( ANIMCHANNEL_TORSO, "reload", true ); + info.fl.canShoot = HasAnim ( ANIMCHANNEL_TORSO, "range_attack", true ); + info.fl.canKillswitch = HasAnim ( ANIMCHANNEL_TORSO, "killswitch", true ); + info.fl.canTurn = false; + } + + // FIXME: this should be based on the availablity of turn anims + postureInfo[AIPOSTURE_STAND].fl.canTurn = true; + postureInfo[AIPOSTURE_RELAXED].fl.canTurn = true; + postureInfo[AIPOSTURE_UNARMED].fl.canTurn = true; +} + +/* +================ +rvAITactical::UpdatePosture +================ +*/ +bool rvAITactical::UpdatePosture ( void ) { + // If the posture is being forced then use that until its no longer forced + if ( postureForce != AIPOSTURE_DEFAULT ) { + postureIdeal = postureForce; + // Not forcing posture, determine it from our current state + } else { + postureIdeal = AIPOSTURE_STAND; + + // Behind cover? + if ( IsBehindCover ( ) ) { + bool left; + if ( enemy.ent ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * (enemy.lastVisibleEyePosition - physicsObj.GetOrigin())) > 0.0f; + } else if ( tether ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * tether->GetPhysics()->GetAxis()[0] ) > 0.0f; + } else { + left = false; + } + // Should be crouching behind cover? + if ( InCrouchCoverMode ( ) ) { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_RIGHT; + } else { + postureIdeal = AIPOSTURE_CROUCH_COVER; + } + } else { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } + } + } else if ( combat.fl.aware //aggressive + && (FacingIdeal ( ) || CheckFOV ( currentFocusPos )) //looking in desired direction + && ((leader && leader->IsCrouching()) || combat.fl.crouchViewClear) ) {//leader is crouching or we can crouch-look in this direction here + //we crouch only if leader is + postureIdeal = AIPOSTURE_CROUCH; + } else if ( CheckRelaxed ( ) ) { + postureIdeal = AIPOSTURE_RELAXED; + } + + //never crouch in melee! + if( IsMeleeNeeded() ) { + postureIdeal = AIPOSTURE_STAND; + } + } + + // Default the posture if trying to move with one that doesnt support it + if ( move.fl.moving && !postureInfo[postureIdeal].fl.canMove ) { + postureIdeal = AIPOSTURE_STAND; + // Default the posture if trying to turn and we cant in the posture we chose + } else if ( (move.moveCommand == MOVE_FACE_ENEMY || move.moveCommand == MOVE_FACE_ENTITY) && !postureInfo[postureIdeal].fl.canTurn ) { + postureIdeal = AIPOSTURE_STAND; + } + + return (postureIdeal != postureCurrent); +} + +/* +================ +rvAITactical::OnPostureChange +================ +*/ +void rvAITactical::OnPostureChange ( void ) { + UpdateAnimPrefix ( ); +} + +/* +============ +rvAITactical::OnSetKey +============ +*/ +void rvAITactical::OnSetKey ( const char* key, const char* value ) { + idAI::OnSetKey ( key, value ); + +/* + if ( !idStr::Icmp ( key, "annoyed" ) ) { + playerAnnoyTime = SEC2MS( atof ( value ) ); + } +*/ +} + +/* +================ +rvAITactical::OnStopMoving +================ +*/ +void rvAITactical::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + // Ensure the peek doesnt happen immedately every time we stop at a cover + if ( IsBehindCover ( ) ){ + actionTimerPeek.Clear ( actionTime ); + actionTimerPeek.Add ( 2000, 0.5f ); + + actionKillswitchAttack.timer.Reset ( actionTime, actionKillswitchAttack.diversity ); + + // We should be looking fairly close to the right direction, so just snap it + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + move.current_yaw = move.ideal_yaw; + } + + idAI::OnStopMoving ( oldMoveCommand ); +} + +/* +================ +rvAITactical::CalculateShots +================ +*/ +void rvAITactical::CalculateShots ( const char* fireAnim ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + if ( shots > ammo ) { + shots = ammo; + } + + // Update the firing animation playback rate + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } +} + +/* +================ +rvAITactical::UseAmmo +================ +*/ +void rvAITactical::UseAmmo ( int amount ) { + if ( ammo <= 0 ) { + return; + } + + shots--; + ammo-=amount; + if ( ammo < 0 ) { + ammo = 0; + shots = 0; + } +} + +/* +================ +rvAITactical::GetDebugInfo +================ +*/ +void rvAITactical::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvAITactical", "postureIdeal", aiPostureString[postureIdeal], userData ); + proc ( "rvAITactical", "postureCurrent", aiPostureString[postureCurrent], userData ); + proc ( "rvAITactical", "healthRegen", va("%d",healthRegen), userData ); + proc ( "rvAITactical", "healthRegenEnabled",healthRegenEnabled?"true":"false", userData ); + proc ( "rvAITactical", "healthRegenNextTime",va("%d",healthRegenNextTime), userData ); + proc ( "rvAITactical", "maxHealth", va("%d",maxHealth), userData ); + + proc ( "rvAITactical", "nextWallTraceTime", va("%d",nextWallTraceTime), userData ); + + + proc ( "idAI", "action_killswitchAttack", aiActionStatusString[actionKillswitchAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAITactical ) + STATE ( "Torso_RangedAttack", rvAITactical::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvAITactical::State_Torso_MovingRangedAttack ) + + STATE ( "Torso_Cover_LeanAttack", rvAITactical::State_Torso_Cover_LeanAttack ) + STATE ( "Torso_Cover_LeanLeftAttack", rvAITactical::State_Torso_Cover_LeanLeftAttack ) + STATE ( "Torso_Cover_LeanRightAttack", rvAITactical::State_Torso_Cover_LeanRightAttack ) + STATE ( "Torso_Cover_Peek", rvAITactical::State_Torso_Cover_Peek ) + + STATE ( "Torso_Reload", rvAITactical::State_Torso_Reload ) + + STATE ( "Torso_SetPosture", rvAITactical::State_Torso_SetPosture ) + + STATE ( "Frame_Peek", rvAITactical::State_Frame_Peek ) +END_CLASS_STATES + +/* +================ +rvAITactical::State_Torso_SetPosture +================ +*/ +stateResult_t rvAITactical::State_Torso_SetPosture ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_RELAXED, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: { + idStr transAnim = va("%s_to_%s", aiPostureString[postureCurrent], aiPostureString[postureIdeal] ); + if ( !HasAnim ( ANIMCHANNEL_TORSO, transAnim ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + + if ( postureCurrent < AIPOSTURE_STAND_COVER_LEFT + || postureCurrent > AIPOSTURE_CROUCH_COVER_RIGHT + || (postureIdeal != AIPOSTURE_STAND && postureIdeal != AIPOSTURE_RELAXED && postureIdeal != AIPOSTURE_CROUCH) ) + { + // FIXME: TEMPORARY UNTIL ANIM IS FIXED TO NOT HAVE ORIGIN TRANSLATION + move.fl.allowAnimMove = false; + } else { + //no need to play cover-to-stand/relaxed transition if: + //scripted... + //or we're moving already... + //or turning away from our old cover direction... + if ( aifl.scripted + /*|| (move.fl.moving&&!move.fl.blocked) + || (fabs(move.current_yaw-move.ideal_yaw) > 30.0f && (move.moveCommand == MOVE_FACE_ENEMY||move.moveCommand == MOVE_FACE_ENTITY))*/ ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, transAnim, parms.blendFrames ); + if ( postureCurrent >= AIPOSTURE_STAND_COVER_LEFT + && postureCurrent <= AIPOSTURE_CROUCH_COVER_RIGHT + && postureIdeal == AIPOSTURE_RELAXED ) { + //we need to also play stand_to_relaxed at the end... + return SRESULT_STAGE ( STAGE_WAIT_RELAXED ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT_RELAXED: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + if ( HasAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_RangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + CalculateShots ( "range_attack" ); + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_start", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 0 ); + UseAmmo ( 1 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov ) { + return SRESULT_STAGE ( STAGE_END ); + } else if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_end", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots ( "range_attack_torso" ); + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 || !enemy.fl.inFov ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Reload +================ +*/ +stateResult_t rvAITactical::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + ammo = spawnArgs.GetInt ( "ammo" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanLeftAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanRightAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanAttack ( const stateParms_t& parms ) { + enum { + STAGE_OUT, + STAGE_OUTWAIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_IN, + STAGE_INWAIT, + }; + switch ( parms.stage ) { + case STAGE_OUT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // The lean out animation cannot blend with any other animations since + // it is essential that the movement delta out match the one back in. Therefore + // we force the legs and torso to be stoped before playing any animations + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + PlayAnim ( ANIMCHANNEL_TORSO, "lean_out", 0 ); + return SRESULT_STAGE ( STAGE_OUTWAIT ); + + case STAGE_OUTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Random number of shots + CalculateShots ( "lean_attack" ); + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "lean_attack", 0 ); + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots > 0 ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_STAGE ( STAGE_IN ); + } + return SRESULT_WAIT; + + case STAGE_IN: + PlayAnim ( ANIMCHANNEL_TORSO, "lean_in", 0 ); + return SRESULT_STAGE ( STAGE_INWAIT ); + + case STAGE_INWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_Peek +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_Peek ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + if ( !PlayAnim ( ANIMCHANNEL_TORSO, "peek", parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Frame_Peek +================ +*/ +stateResult_t rvAITactical::State_Frame_Peek ( const stateParms_t& parms ) { + CheckForEnemy ( true, true ); + return SRESULT_OK; +} + +/* +================ +rvAITactical::Event_ForcePosture +================ +*/ +void rvAITactical::Event_ForcePosture ( int posture ) { + postureForce = (aiPosture_t)posture; +} + +/* +=================== +rvAITactical::IsCrouching +=================== +*/ +bool rvAITactical::IsCrouching( void ) const { + if ( postureCurrent == AIPOSTURE_CROUCH + || postureCurrent == AIPOSTURE_CROUCH_COVER + || postureCurrent == AIPOSTURE_CROUCH_COVER_LEFT + || postureCurrent == AIPOSTURE_CROUCH_COVER_RIGHT ) { + return true; + } + return idAI::IsCrouching(); +} + +/* +================ +rvAITactical::Event_PostSpawn +================ +*/ +void rvAITactical::Event_PostSpawn( void ) { + idAI::Event_PostSpawn(); + if ( team == AITEAM_MARINE && healthRegenEnabled ) + {//regen-enabled buddy marine + if ( CheckDeathCausesMissionFailure() ) + {//who is important to a mission + if ( g_skill.GetInteger() > 2 ) + {//on impossible + health *= 1.5f; + healthRegen *= 1.5f; + } + else if ( g_skill.GetInteger() > 1 ) + {//on hard + health *= 1.2f; + healthRegen *= 1.25f; + } + } + } +} \ No newline at end of file diff --git a/src/game/ai/AI_Tactical.h b/src/game/ai/AI_Tactical.h new file mode 100644 index 0000000..94248f8 --- /dev/null +++ b/src/game/ai/AI_Tactical.h @@ -0,0 +1,158 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Tactical.h + +================ +*/ + +#ifndef __AI_TACTICAL__ +#define __AI_TACTICAL__ + +typedef enum { + AIPOSTURE_DEFAULT = -1, + AIPOSTURE_STAND, + AIPOSTURE_CROUCH, + AIPOSTURE_STAND_COVER_LEFT, + AIPOSTURE_STAND_COVER_RIGHT, + AIPOSTURE_CROUCH_COVER, + AIPOSTURE_CROUCH_COVER_LEFT, + AIPOSTURE_CROUCH_COVER_RIGHT, + AIPOSTURE_RELAXED, + AIPOSTURE_UNARMED, + AIPOSTURE_AT_ATTENTION, + AIPOSTURE_MAX +} aiPosture_t; + +typedef struct { + struct { + bool canMove; + bool canShoot; + bool canPeek; + bool canReload; + bool canTurn; + bool canKillswitch; + } fl; + +} aiPostureInfo_t; + +class rvAITactical : public idAI { +public: + + CLASS_PROTOTYPE( rvAITactical ); + + rvAITactical ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool CheckActions ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + + virtual bool IsCrouching ( void ) const; + +protected: + + virtual const char* GetIdleAnimName ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnPostureChange ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + bool CheckRelaxed ( void ) const; + + void InitPostureInfo ( void ); + bool UpdatePosture ( void ); + void CalculateShots ( const char* fireAnim ); + void UseAmmo ( int amount ); + void SetPosture ( aiPosture_t newPosture ); + void UpdateAnimPrefix ( void ); + + int ammo; + int maxShots; + int minShots; + int shots; + float fireRate; + + int playerFocusTime; + int playerAnnoyTime; + + aiPosture_t postureIdeal; + aiPosture_t postureCurrent; + aiPosture_t postureForce; + aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + + rvAIAction actionElbowAttack; + rvAIAction actionKillswitchAttack; + + rvAIActionTimer actionTimerPeek; + +private: + + int healthRegen; + bool healthRegenEnabled; + int healthRegenNextTime; + int maxHealth; + + int nextWallTraceTime; + + // Custom actions + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + bool CheckAction_Relax ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_SetPosture ( const stateParms_t& parms ); + + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + + stateResult_t State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_Peek ( const stateParms_t& parms ); + + // Frame Commands + stateResult_t State_Frame_Peek ( const stateParms_t& parms ); + + // Events + void Event_ForcePosture ( int posture ); + + virtual void Event_PostSpawn ( void ); + + CLASS_STATES_PROTOTYPE ( rvAITactical ); +}; + +extern const idEventDef AI_ForcePosture; + +#endif /* !__AI_TACTICAL__ */ diff --git a/src/game/ai/AI_Util.cpp b/src/game/ai/AI_Util.cpp new file mode 100644 index 0000000..7ccd46a --- /dev/null +++ b/src/game/ai/AI_Util.cpp @@ -0,0 +1,1001 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIHelper ) + EVENT( EV_Activate, rvAIHelper::Event_Activate ) +END_CLASS + +/* +================ +rvAIHelper::rvAIHelper +================ +*/ +rvAIHelper::rvAIHelper ( void ) { + helperNode.SetOwner( this ); +} + +/* +================ +rvAIHelper::Spawn +================ +*/ +void rvAIHelper::Spawn ( void ) { + // Auto activate? + if ( spawnArgs.GetBool ( "start_on" ) ) { + PostEventMS ( &EV_Activate, 0, this ); + } +} + +/* +================ +rvAIHelper::IsCombat +================ +*/ +bool rvAIHelper::IsCombat ( void ) const { + return false; +} + + +/* +================ +rvAIHelper::OnActivate +================ +*/ +void rvAIHelper::OnActivate ( bool active ) { + if ( active ) { + ActivateTargets ( this ); + } +} + +/* +================ +rvAIHelper::Event_Activate +================ +*/ +void rvAIHelper::Event_Activate( idEntity *activator ) { + if ( !helperNode.InList ( ) ) { + aiManager.RegisterHelper ( this ); + OnActivate ( true ); + } else { + aiManager.UnregisterHelper ( this ); + OnActivate ( false ); + } +} + +/* +================ +rvAIHelper::GetDirection +================ +*/ +idVec3 rvAIHelper::GetDirection ( const idAI* ai ) const { + if ( ai->team == 0 ) { + return GetPhysics()->GetAxis()[0]; + } + return -GetPhysics()->GetAxis()[0]; +} + +/* +================ +rvAIHelper::ValidateDestination +================ +*/ +bool rvAIHelper::ValidateDestination ( const idAI* ent, const idVec3& dest ) const { + return true; +} + +/* +=============================================================================== + +rvAICombatHelper + +=============================================================================== +*/ + +class rvAICombatHelper : public rvAIHelper { +public: + CLASS_PROTOTYPE( rvAICombatHelper ); + + rvAICombatHelper ( void ); + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + +protected: + + virtual void OnActivate ( bool active ); + + idEntityPtr location; +}; + +CLASS_DECLARATION( rvAIHelper, rvAICombatHelper ) +END_CLASS + +/* +================ +rvAICombatHelper::rvAICombatHelper +================ +*/ +rvAICombatHelper::rvAICombatHelper ( void ) { +} + +/* +================ +rvAICombatHelper::Spawn +================ +*/ +void rvAICombatHelper::Spawn ( void ) { +} + +/* +================ +rvAICombatHelper::OnActivate +================ +*/ +void rvAICombatHelper::OnActivate ( bool active ) { + rvAIHelper::OnActivate ( active ); + + if ( active ) { + if ( spawnArgs.GetBool ( "tetherLocation", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() ); + } else { + location = NULL; + } + } +} + +/* +================ +rvAICombatHelper::IsCombat +================ +*/ +bool rvAICombatHelper::IsCombat ( void ) const { + return true; +} + +/* +================ +rvAICombatHelper::ValidateDestination +================ +*/ +bool rvAICombatHelper::ValidateDestination ( const idAI* ai, const idVec3& dest ) const { + // If tethering to a location then see if the location of the given points matches our tethered location + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + + // Is the destination on the wrong side of the helper? + idVec3 origin; + idVec3 dir; + + dir = GetDirection(ai); + if ( ai->enemy.ent ) { + origin = ai->enemy.lastKnownPosition; + origin -= (dir * ai->combat.attackRange[0]); + } else { + origin = GetPhysics()->GetOrigin(); + origin -= (dir * 32.0f); + } + + if ( dir * (origin-dest) < 0.0f ) { + return false; + } + + // Would this destination link us to the wrong helper? + if ( static_cast< const rvAIHelper * >( aiManager.FindClosestHelper( dest ) ) != this ) { + return false; + } + + return true; +} + +/* +=============================================================================== + + rvAIAvoid + +=============================================================================== +*/ + +class rvAIAvoid : public idEntity { +public: + CLASS_PROTOTYPE( rvAIAvoid ); + + rvAIAvoid ( void ); + + void Spawn ( void ); +}; + +CLASS_DECLARATION( idEntity, rvAIAvoid ) +END_CLASS + +/* +================ +rvAIAvoid::rvAIAvoid +================ +*/ +rvAIAvoid::rvAIAvoid ( void ) { +} + +/* +================ +rvAIAvoid::Spawn +================ +*/ +void rvAIAvoid::Spawn ( void ) { + int team = -1; + if ( !spawnArgs.GetInt ( "teamFilter", "-1", team ) ) { + //hmm, no "teamFilter" set, check "team" since many were set up like this + team = spawnArgs.GetInt ( "team", "-1" ); + } + aiManager.AddAvoid ( GetPhysics()->GetOrigin(), spawnArgs.GetFloat ( "radius", "64" ), team ); + PostEventMS ( &EV_Remove, 0 ); +} + +/* +=============================================================================== + + rvAITrigger + +=============================================================================== +*/ + +const idEventDef AI_AppendFromSpawner ( "", "ee" ); + +CLASS_DECLARATION( idEntity, rvAITrigger ) + EVENT( EV_Activate, rvAITrigger::Event_Activate ) + EVENT( AI_AppendFromSpawner, rvAITrigger::Event_AppendFromSpawner ) +END_CLASS + +/* +================ +rvAITrigger::rvAITrigger +================ +*/ +rvAITrigger::rvAITrigger( void ) { +} + +/* +================ +rvAITrigger::Spawn +================ +*/ +void rvAITrigger::Spawn ( void ) { + nextTriggerTime = 0; + wait = SEC2MS ( spawnArgs.GetFloat ( "wait", "-1" ) ); + + conditionDead = spawnArgs.GetBool ( "condition_dead", "0" ); + conditionTether = spawnArgs.GetBool ( "condition_tether", "0" ); + conditionStop = spawnArgs.GetBool ( "condition_stop", "0" ); + + percent = spawnArgs.GetFloat ( "percent", "1" ); + + // Start on by default? + nextTriggerTime = spawnArgs.GetBool ( "start_on", "0" ) ? 0 : -1; + if ( nextTriggerTime == 0 ) { + BecomeActive ( TH_THINK ); + } else { + BecomeInactive ( TH_THINK ); + } + + // If there are no conditions we are done + if ( !conditionDead && !conditionTether && !conditionStop ) { + gameLocal.Warning ( "No conditions specified on ai trigger entity '%s'", GetName ( ) ); + PostEventMS ( &EV_Remove, 0 ); + } +} + +/* +================ +rvAITrigger::Save +================ +*/ +void rvAITrigger::Save ( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( testAI.Num ( ) ); + for ( i = 0; i < testAI.Num(); i ++ ) { + testAI[i].Save ( savefile ); + } + + savefile->WriteInt ( testSpawner.Num ( ) ); + for ( i = 0; i < testSpawner.Num(); i ++ ) { + testSpawner[i].Save ( savefile ); + } + + savefile->WriteBool ( conditionDead ); + savefile->WriteBool ( conditionTether ); + savefile->WriteBool ( conditionStop ); + + savefile->WriteInt ( wait ); + savefile->WriteInt ( nextTriggerTime ); + + savefile->WriteFloat ( percent ); +} + +/* +================ +rvAITrigger::Restore +================ +*/ +void rvAITrigger::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + testAI.Clear ( ); + testAI.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testAI[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + testSpawner.Clear ( ); + testSpawner.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testSpawner[i].Restore ( savefile ); + } + + savefile->ReadBool ( conditionDead ); + savefile->ReadBool ( conditionTether ); + savefile->ReadBool ( conditionStop ); + + savefile->ReadInt ( wait ); + savefile->ReadInt ( nextTriggerTime ); + + savefile->ReadFloat ( percent ); +} + +/* +================ +rvAITrigger::Think +================ +*/ +void rvAITrigger::Think ( void ) { + int v; + + // Only trigger so often + if ( nextTriggerTime == -1 || gameLocal.time < nextTriggerTime ) { + return; + } + + // If we have any attached spawners then our condition cannot be met + if ( testSpawner.Num() ) { + for ( v = 0; v < testSpawner.Num(); v ++ ) { + rvSpawner* spawner = testSpawner[v]; + if ( !spawner ) { + testSpawner.RemoveIndex ( v ); + v--; + continue; + } + return; + } + } + + // If we have no AI we are tracking then wait until we do + if ( !testAI.Num ( ) ) { + return; + } + + int count = 0; + for ( v = 0; v < testAI.Num(); v ++ ) { + idAI* ai = testAI[v]; + if ( !ai ) { + testAI.RemoveIndex ( v ); + v--; + continue; + } + if ( !ai->aifl.dead ) { + if ( conditionDead ) { + continue; + } + if ( conditionTether && !ai->IsWithinTether ( ) ) { + continue; + } + if ( conditionStop && ai->move.fl.moving ) { + continue; + } + } + count++; + } + + // If result is true then we should fire our trigger now + if ( count >= (int) ((float)testAI.Num()*percent) ) { + ActivateTargets ( this ); + + // If only triggering once just remove ourselves now + if ( wait < 0 ) { + nextTriggerTime = -1; + } else { + nextTriggerTime = gameLocal.time + wait; + } + } +} + +/* +================ +rvAITrigger::FindTargets +================ +*/ +void rvAITrigger::FindTargets ( void ) { + int t; + + idEntity::FindTargets ( ); + + for ( t = 0; t < targets.Num(); t ++ ) { + idEntity* ent = targets[t]; + if ( !ent ) { + continue; + } + if ( ent->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + if ( ent->IsType ( rvSpawner::GetClassType ( ) ) ) { + static_cast(ent)->AddCallback ( this, &AI_AppendFromSpawner ); + testSpawner.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + } +} + +/* +================ +rvAITrigger::Event_Activate +================ +*/ +void rvAITrigger::Event_Activate( idEntity *activator ) { + + // Add spawners and ai to the list when they come in + if ( activator && activator->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(activator)) ); + return; + } + + if ( nextTriggerTime == -1 ) { + nextTriggerTime = 0; + BecomeActive ( TH_THINK ); + } else { + nextTriggerTime = -1; + BecomeInactive ( TH_THINK ); + } +} + +/* +================ +rvAITrigger::Event_AppendFromSpawner +================ +*/ +void rvAITrigger::Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ) { + // If its an ai entity being spawned then add it to our test list + if ( spawned && spawned->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(spawned)) ); + } +} + +/* +=============================================================================== + + rvAITether + +=============================================================================== +*/ +const idEventDef EV_TetherSetupLocation ( "tetherSetupLocation" ); +const idEventDef EV_TetherGetLocation ( "tetherGetLocation" ); +CLASS_DECLARATION( idEntity, rvAITether ) + EVENT( EV_Activate, rvAITether::Event_Activate ) + EVENT( EV_TetherGetLocation, rvAITether::Event_TetherGetLocation ) + EVENT( EV_TetherSetupLocation, rvAITether::Event_TetherSetupLocation ) +END_CLASS + +/* +================ +rvAITether::rvAITether +================ +*/ +rvAITether::rvAITether ( void ) { +} + +/* +================ +rvAITether::InitNonPersistentSpawnArgs +================ +*/ +void rvAITether::InitNonPersistentSpawnArgs ( void ) { + tfl.canBreak = spawnArgs.GetBool ( "allowBreak", "1" ); + tfl.autoBreak = spawnArgs.GetBool ( "autoBreak", "0" ); + tfl.forceRun = spawnArgs.GetBool ( "forceRun", "0" ); + tfl.forceWalk = spawnArgs.GetBool ( "forceWalk", "0" ); + tfl.becomeAggressive = spawnArgs.GetBool ( "becomeAggressive", "0" ); + tfl.becomePassive = spawnArgs.GetBool ( "becomePassive", "0" ); + + // Check for both being set + if ( tfl.forceRun && tfl.forceWalk ) { + gameLocal.Warning ( "both forceRun and forceWalk were specified for tether '%s', forceRun will take precedence" ); + tfl.forceWalk = false; + } + if ( tfl.becomeAggressive && tfl.becomePassive ) { + gameLocal.Warning ( "both becomePassive and becomeAggressive were specified for tether '%s', becomeAggressive will take precedence" ); + tfl.becomePassive = false; + } +} + +/* +================ +rvAITether::Spawn +================ +*/ +void rvAITether::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); + + PostEventMS( &EV_TetherSetupLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherSetupLocation +================ +*/ +void rvAITether::Event_TetherSetupLocation( void ) { + //NOTE: we now do this right after spawn so we don't stomp other tether's locations + // if we activate after them and are in the same room as them. + // Dynamically-spawned locations are very, very bad! + + // All pre-existing locations should be placed and spread by now + // Get the location entity we are attached to + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + if ( !location ) { + location = gameLocal.AddLocation ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f, "tether_location" ); + } + } else { + location = NULL; + } + PostEventMS( &EV_TetherGetLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherGetLocation +================ +*/ +void rvAITether::Event_TetherGetLocation( void ) { + //NOW: all locations should be made & spread, get our location (may not be the one + // we added, it could be be the same as another tether if it's in the same room as us) + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } else { + location = NULL; + } +} + +/* +================ +rvAITether::Save +================ +*/ +void rvAITether::Save ( idSaveGame *savefile ) const { + location.Save( savefile ); +} + +/* +================ +rvAITether::Restore +================ +*/ +void rvAITether::Restore ( idRestoreGame *savefile ) { + location.Restore( savefile ); + + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITether::ValidateAAS +================ +*/ +bool rvAITether::ValidateAAS ( idAI* ai ) { + if ( !ai->aas ) { + return false; + } + return true; +} + +/* +================ +rvAITether::ValidateDestination +================ +*/ +bool rvAITether::ValidateDestination ( idAI* ai, const idVec3& dest ) { + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + return true; +} + +/* +================ +rvAITether::ValidateBounds +================ +*/ +bool rvAITether::ValidateBounds ( const idBounds& bounds ) { + return true; +} + +/* +================ +rvAITether::FindGoal +================ +*/ +bool rvAITether::FindGoal ( idAI* ai, aasGoal_t& goal ) { + rvAASFindGoalForTether findGoal ( ai, this ); + if ( !ai->aas->FindNearestGoal( goal, + ai->PointReachableAreaNum( ai->GetPhysics()->GetOrigin() ), + ai->GetPhysics()->GetOrigin(), + GetPhysics()->GetOrigin(), + ai->move.travelFlags, + 0.0f, 0.0f, + NULL, 0, findGoal ) ) { + return false; + } + return true; +} + + +/* +================ +rvAITether::Event_Activate +================ +*/ +void rvAITether::Event_Activate( idEntity *activator ) { + int i; + + //WELL! Turns out designers are binding tethers to movers, so we have to get our location *again* on activation... + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } + + if ( activator && activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + + // Aggressive/Passive stance change? + if ( tfl.becomeAggressive ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } else if ( tfl.becomePassive ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, false ); + } + } + } +} + +/* +================ +rvAITether::DebugDraw +================ +*/ +void rvAITether::DebugDraw ( void ) { + const idBounds& bounds = GetPhysics()->GetAbsBounds(); + gameRenderWorld->DebugBounds ( colorYellow, bounds.Expand ( 8.0f ) ); + gameRenderWorld->DebugArrow ( colorWhite, bounds.GetCenter(), bounds.GetCenter() + GetPhysics()->GetAxis()[0] * 16.0f, 8.0f ); + gameRenderWorld->DrawText( name.c_str(), bounds.GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), bounds.GetCenter() - GetPhysics()->GetGravityNormal() * 5.0f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +} + +/* +=============================================================================== + + rvAITetherBehind + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherBehind ) +END_CLASS + +/* +================ +rvAITetherBehind::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherBehind::InitNonPersistentSpawnArgs ( void ) { + range = spawnArgs.GetFloat ( "range" ); +} + +/* +================ +rvAITetherBehind::Spawn +================ +*/ +void rvAITetherBehind::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::Restore +================ +*/ +void rvAITetherBehind::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::ValidateDestination +================ +*/ +bool rvAITetherBehind::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + + // Make sure we include the move range in the tether + idVec3 origin; + if ( range ) { + origin = GetPhysics()->GetOrigin ( ) + GetPhysics()->GetAxis()[0] * GetOriginReachedRange() - GetPhysics()->GetAxis()[0] * range; + if ( GetPhysics()->GetAxis()[0] * (origin-dest) > 0.0f ) { + return false; + } + } + + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis()[0] * GetOriginReachedRange(); + + // Are we on wrong side of tether? + return ( GetPhysics()->GetAxis()[0] * (origin-dest) ) >= 0.0f; +} + +/* +================ +rvAITetherBehind::ValidateBounds +================ +*/ +bool rvAITetherBehind::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + + idPlane plane; + int side; + plane.SetNormal ( GetPhysics()->GetAxis ( )[0] ); + plane.FitThroughPoint ( GetPhysics()->GetOrigin ( ) ); + side = bounds.PlaneSide ( plane ); + return ( side == PLANESIDE_CROSS || side == PLANESIDE_BACK ); +} + +/* +================ +rvAITetherBehind::DebugDraw +================ +*/ +void rvAITetherBehind::DebugDraw ( void ) { + idVec3 dir; + + rvAITether::DebugDraw ( ); + + dir = GetPhysics()->GetGravityNormal ( ).Cross ( GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorPink, + GetPhysics()->GetOrigin() - dir * 1024.0f, + GetPhysics()->GetOrigin() + dir * 1024.0f ); + + if ( range ) { + idVec3 origin; + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis ( )[0] * range; + gameRenderWorld->DebugArrow ( colorPink, GetPhysics()->GetOrigin(), origin, 5.0f ); + gameRenderWorld->DebugLine ( colorPink, + origin - dir * 1024.0f, + origin + dir * 1024.0f ); + } +} + +/* +=============================================================================== + + rvAITetherRadius + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherRadius ) +END_CLASS + +/* +================ +rvAITetherRadius::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherRadius::InitNonPersistentSpawnArgs ( void ) { + float radius; + if ( !spawnArgs.GetFloat ( "tetherRadius", "0", radius ) ) { + radius = spawnArgs.GetFloat ( "radius", "128" ); + } + radiusSqr = Square ( radius ); +} + +/* +================ +rvAITetherRadius::Spawn +================ +*/ +void rvAITetherRadius::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::Restore +================ +*/ +void rvAITetherRadius::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::ValidateDestination +================ +*/ +bool rvAITetherRadius::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + // Are we within tether radius? + return ((dest - GetPhysics()->GetOrigin()).LengthSqr ( ) < radiusSqr - Square ( ai->move.range ) ); +} + + +/* +================ +rvAITetherRadius::ValidateBounds +================ +*/ +bool rvAITetherRadius::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + return ( Square ( bounds.ShortestDistance ( GetPhysics()->GetOrigin ( ) ) ) < radiusSqr ); +} + +/* +================ +rvAITetherRadius::DebugDraw +================ +*/ +void rvAITetherRadius::DebugDraw ( void ) { + rvAITether::DebugDraw ( ); + gameRenderWorld->DebugCircle( colorPink, GetPhysics()->GetOrigin(), GetPhysics()->GetGravityNormal(), idMath::Sqrt(radiusSqr), 25 ); +} + + +/* +=============================================================================== + + rvAITetherClear + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherClear ) +END_CLASS + + +/* +=============================================================================== + + rvAIBecomePassive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomePassive ) + EVENT( EV_Activate, rvAIBecomePassive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomePassive::Event_Activate +================ +*/ +void rvAIBecomePassive::Event_Activate( idEntity *activator ) { + int i; + bool ignoreEnemies; + + ignoreEnemies = spawnArgs.GetBool ( "ignoreEnemies", "1" ); + + // All targeted AI will become passive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, ignoreEnemies ); + } + } +} + +/* +=============================================================================== + + rvAIBecomeAggressive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomeAggressive ) + EVENT( EV_Activate, rvAIBecomeAggressive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomeAggressive::Event_Activate +================ +*/ +void rvAIBecomeAggressive::Event_Activate( idEntity *activator ) { + int i; + + // All targetted AI will become aggressive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } + } +} diff --git a/src/game/ai/AI_Util.h b/src/game/ai/AI_Util.h new file mode 100644 index 0000000..1c95005 --- /dev/null +++ b/src/game/ai/AI_Util.h @@ -0,0 +1,256 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Util.h + +================ +*/ + +#ifndef __AI_UTIL__ +#define __AI_UTIL__ + +const float AI_TETHER_MINRANGE = 8.0f; + +/* +=============================================================================== + rvAITrigger +=============================================================================== +*/ + +class rvAITrigger : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITrigger ); + + rvAITrigger ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void Think ( void ); + + virtual void FindTargets ( void ); + +protected: + + idList< idEntityPtr > testAI; + idList< idEntityPtr > testSpawner; + + bool conditionDead; + bool conditionTether; + bool conditionStop; + + int wait; + int nextTriggerTime; + + float percent; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_PostRestore ( void ); + + void Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ); +}; + +/* +=============================================================================== + rvAITether +=============================================================================== +*/ + +class rvAITether : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITether ); + + rvAITether ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateAAS ( idAI* ai ); + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + + virtual bool FindGoal ( idAI* ai, aasGoal_t& goal ); + virtual float GetOriginReachedRange ( void ) {return AI_TETHER_MINRANGE;} + + virtual void DebugDraw ( void ); + + bool CanBreak ( void ) const; + bool IsAutoBreak ( void ) const; + + idList areaNum; + + bool IsWalkForced ( void ) const; + bool IsRunForced ( void ) const; + +protected: + + idEntityPtr location; + + struct tetherFlags_s { + bool canBreak :1; // Temporarily break when enemy is within tether + bool autoBreak :1; // Break when the ai gets within the tether + bool forceRun :1; // Alwasy run when heading towards tether + bool forceWalk :1; // Alwasy walk when heading towards tether + bool becomeAggressive :1; // + bool becomePassive :1; + } tfl; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_TetherSetupLocation ( void ); + void Event_TetherGetLocation ( void ); +}; + +ID_INLINE bool rvAITether::CanBreak ( void ) const { + return tfl.canBreak; +} + +ID_INLINE bool rvAITether::IsWalkForced ( void ) const { + return tfl.forceWalk; +} + +ID_INLINE bool rvAITether::IsRunForced ( void ) const { + return tfl.forceRun; +} + +ID_INLINE bool rvAITether::IsAutoBreak ( void ) const { + return tfl.autoBreak; +} + +/* +=============================================================================== + rvAITetherBehind +=============================================================================== +*/ + +class rvAITetherBehind : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherBehind ); + + rvAITetherBehind( void ) { range = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + +protected: + + float range; +}; + +/* +=============================================================================== + rvAITetherRadius +=============================================================================== +*/ + +class rvAITetherRadius : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherRadius ); + + rvAITetherRadius( void ) { radiusSqr = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + + /* + virtual float GetOriginReachedRange ( void ) + { + float rad = sqrt(radiusSqr); + float halfRad = rad/2.0f; + if ( rad < AI_TETHER_MINRANGE ) + { + return rad; + } + return (halfRad. + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "AI.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + +#include "AI_Manager.h" +#include "../vehicle/Vehicle.h" +#include "../spawner.h" + + +#define idAI_EVENT( eventname, inparms, outparm) \ + const idEventDef AI_##eventname( #eventname, inparms, outparm); \ + void idAI::Event_##eventname + + +/*********************************************************************** + + AI Events + +***********************************************************************/ + +// Get / Set +const idEventDef AI_GetLeader ( "getLeader", NULL, 'e' ); +const idEventDef AI_SetLeader ( "setLeader", "E" ); +const idEventDef AI_GetEnemy ( "getEnemy", NULL, 'e' ); +const idEventDef AI_SetEnemy ( "setEnemy", "E" ); +const idEventDef AI_SetHealth ( "setHealth", "f" ); +const idEventDef AI_SetTalkState ( "setTalkState", "d" ); +const idEventDef AI_SetScript ( "setScript", "ss" ); +const idEventDef AI_SetMoveSpeed ( "setMoveSpeed", "d" ); +const idEventDef AI_SetPassivePrefix ( "setPassivePrefix", "s" ); + +// Enable / Disable +const idEventDef AI_EnableClip ( "enableClip" ); +const idEventDef AI_DisableClip ( "disableClip" ); +const idEventDef AI_EnableGravity ( "enableGravity" ); +const idEventDef AI_DisableGravity ( "disableGravity" ); +const idEventDef AI_EnableAFPush ( "enableAFPush" ); +const idEventDef AI_DisableAFPush ( "disableAFPush" ); +const idEventDef AI_EnableDamage ( "enableDamage" ); +const idEventDef AI_DisableDamage ( "disableDamage" ); +const idEventDef AI_EnableTarget ( "enableTarget" ); +const idEventDef AI_DisableTarget ( "disableTarget" ); +const idEventDef AI_EnableMovement ( "enableMovement" ); +const idEventDef AI_DisableMovement ( "disableMovement" ); +const idEventDef AI_TakeDamage ( "takeDamage", "f" ); +const idEventDef AI_SetUndying ( "setUndying", "f" ); +const idEventDef AI_EnableAutoBlink ( "enableAutoBlink" ); +const idEventDef AI_DisableAutoBlink ( "disableAutoBlink" ); + +// Scripted Sequences +const idEventDef AI_ScriptedMove ( "scriptedMove", "efd" ); +const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" ); +const idEventDef AI_ScriptedAnim ( "scriptedAnim", "sddd" ); +const idEventDef AI_ScriptedPlaybackMove ( "scriptedPlaybackMove", "sdd" ); +const idEventDef AI_ScriptedPlaybackAim ( "scriptedPlaybackAim", "sdd" ); +const idEventDef AI_ScriptedAction ( "scriptedAction", "ed" ); +const idEventDef AI_ScriptedDone ( "scriptedDone", NULL, 'd' ); +const idEventDef AI_ScriptedStop ( "scriptedStop" ); +const idEventDef AI_ScriptedJumpDown ( "scriptedJumpDown", "f" ); + +// Misc +const idEventDef AI_LookAt ( "lookAt", "E" ); +const idEventDef AI_Attack ( "attack", "ss" ); +const idEventDef AI_LockEnemyOrigin ( "lockEnemyOrigin" ); +const idEventDef AI_StopThinking ( "stopThinking" ); +const idEventDef AI_JumpFrame ( "" ); +const idEventDef AI_RealKill ( "" ); +const idEventDef AI_Kill ( "kill" ); +const idEventDef AI_RemoveUpdateSpawner ( "removeUpdateSpawner" ); +const idEventDef AI_AllowHiddenMovement ( "allowHiddenMovement", "d" ); +const idEventDef AI_Speak ( "speak", "s" ); +const idEventDef AI_SpeakRandom ( "speakRandom", "s" ); +const idEventDef AI_IsSpeaking ( "isSpeaking", NULL, 'f' ); +const idEventDef AI_IsTethered ( "isTethered", NULL, 'f' ); +const idEventDef AI_IsWithinTether ( "isWithinTether", NULL, 'f' ); +const idEventDef AI_LaunchMissile ( "launchMissile", "vv", 'e' ); +const idEventDef AI_AttackMelee ( "attackMelee", "s", 'd' ); +const idEventDef AI_DirectDamage ( "directDamage", "es" ); +const idEventDef AI_RadiusDamageFromJoint ( "radiusDamageFromJoint", "ss" ); +const idEventDef AI_MeleeAttackToJoint ( "meleeAttackToJoint", "ss", 'd' ); +const idEventDef AI_CanBecomeSolid ( "canBecomeSolid", NULL, 'f' ); +const idEventDef AI_BecomeSolid ( "becomeSolid" ); +const idEventDef AI_BecomeRagdoll ( "becomeRagdoll", NULL, 'd' ); +const idEventDef AI_BecomePassive ( "becomePassive", "d" ); +const idEventDef AI_BecomeAggressive ( "becomeAggressive" ); +const idEventDef AI_StopRagdoll ( "stopRagdoll" ); +const idEventDef AI_FaceEnemy ( "faceEnemy" ); +const idEventDef AI_FaceEntity ( "faceEntity", "E" ); + +//jshepard +const idEventDef AI_FindEnemy ( "findEnemy", "f", 'e'); + +void idAI::Event_Activate( idEntity *activator ) { Activate( activator );} +void idAI::Event_Touch( idEntity *other, trace_t *trace ) { OnTouch( other, trace ); } +void idAI::Event_SetEnemy( idEntity *ent ) { if ( !ent ) ClearEnemy(); else SetEnemy( ent );} +void idAI::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) { DirectDamage( damageDefName, damageTarget ); } +void idAI::Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { RadiusDamageFromJoint( jointname, damageDefName ); } +void idAI::Event_CanBecomeSolid( void ) { idThread::ReturnFloat( CanBecomeSolid() ); } +void idAI::Event_BecomeSolid( void ) { BecomeSolid(); } +void idAI::Event_BecomeNonSolid( void ) { BecomeNonSolid(); } +void idAI::Event_BecomeRagdoll( void ) { idThread::ReturnInt( StartRagdoll() ); } +void idAI::Event_StopRagdoll( void ) { StopRagdoll(); SetPhysics( &physicsObj ); } +void idAI::Event_SetHealth( float newHealth ) { health = newHealth; fl.takedamage = true; if( health > 0 ) aifl.dead = false; else aifl.dead = true; } +void idAI::Event_FaceEnemy( void ) { FaceEnemy(); } +void idAI::Event_FaceEntity( idEntity *ent ) { FaceEntity( ent ); } +void idAI::Event_SetTalkState( int state ) { SetTalkState ( (talkState_t)state ); } +void idAI::Event_Speak( const char *speechDecl ) { Speak( speechDecl ); } +void idAI::Event_SpeakRandom( const char *speechDecl ) { Speak( speechDecl, true ); } +void idAI::Event_GetLeader( void ) { idThread::ReturnEntity( leader ); } +void idAI::Event_SetLeader( idEntity* ent ) { SetLeader ( ent ); } +void idAI::Event_GetEnemy( void ) { idThread::ReturnEntity( enemy.ent ); } +void idAI::Event_TakeDamage( float takeDamage ) { fl.takedamage = ( takeDamage ) ? true : false; } +void idAI::Event_SetUndying( float setUndying ) { aifl.undying = ( setUndying ) ? true : false; } + + +void idAI::Event_IsSpeaking ( void ) { idThread::ReturnFloat ( IsSpeaking ( ) ); } +void idAI::Event_IsTethered ( void ) { idThread::ReturnFloat ( IsTethered ( ) ); } +void idAI::Event_IsWithinTether ( void ) { idThread::ReturnFloat ( IsWithinTether ( ) ); } +void idAI::Event_IsMoving ( void ) { idThread::ReturnFloat ( move.fl.moving ); } + +CLASS_DECLARATION( idActor, idAI ) + EVENT( EV_Activate, idAI::Event_Activate ) + EVENT( EV_Touch, idAI::Event_Touch ) + + // Enable / Disable + EVENT( AI_EnableClip, idAI::Event_EnableClip ) + EVENT( AI_DisableClip, idAI::Event_DisableClip ) + EVENT( AI_EnableGravity, idAI::Event_EnableGravity ) + EVENT( AI_DisableGravity, idAI::Event_DisableGravity ) + EVENT( AI_EnableAFPush, idAI::Event_EnableAFPush ) + EVENT( AI_DisableAFPush, idAI::Event_DisableAFPush ) + EVENT( AI_EnableDamage, idAI::Event_EnableDamage ) + EVENT( AI_DisableDamage, idAI::Event_DisableDamage ) + EVENT( AI_EnablePain, idAI::Event_EnablePain ) + EVENT( AI_DisablePain, idAI::Event_DisablePain ) + EVENT( AI_EnableTarget, idAI::Event_EnableTarget ) + EVENT( AI_DisableTarget, idAI::Event_DisableTarget ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_SetUndying, idAI::Event_SetUndying ) + EVENT( AI_EnableAutoBlink, idAI::Event_EnableAutoBlink ) + EVENT( AI_DisableAutoBlink, idAI::Event_DisableAutoBlink ) + + // Scripted sequences + EVENT( AI_ScriptedMove, idAI::Event_ScriptedMove ) + EVENT( AI_ScriptedFace, idAI::Event_ScriptedFace ) + EVENT( AI_ScriptedAnim, idAI::Event_ScriptedAnim ) + EVENT( AI_ScriptedAction, idAI::Event_ScriptedAction ) + EVENT( AI_ScriptedPlaybackMove, idAI::Event_ScriptedPlaybackMove ) + EVENT( AI_ScriptedPlaybackAim, idAI::Event_ScriptedPlaybackAim ) + EVENT( AI_ScriptedDone, idAI::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, idAI::Event_ScriptedStop ) + EVENT( AI_ScriptedJumpDown, idAI::Event_ScriptedJumpDown ) + + // Get / Set + EVENT( AI_SetTalkState, idAI::Event_SetTalkState ) + EVENT( AI_SetLeader, idAI::Event_SetLeader ) + EVENT( AI_GetLeader, idAI::Event_GetLeader ) + EVENT( AI_SetEnemy, idAI::Event_SetEnemy ) + EVENT( AI_GetEnemy, idAI::Event_GetEnemy ) + EVENT( EV_GetAngles, idAI::Event_GetAngles ) + EVENT( EV_SetAngles, idAI::Event_SetAngles ) + EVENT( AI_SetScript, idAI::Event_SetScript ) + EVENT( AI_SetMoveSpeed, idAI::Event_SetMoveSpeed ) + EVENT( AI_SetPassivePrefix, idAI::Event_SetPassivePrefix ) + + // Misc + EVENT( AI_Attack, idAI::Event_Attack ) + EVENT( AI_AttackMelee, idAI::Event_AttackMelee ) + + EVENT( AI_LookAt, idAI::Event_LookAt ) + EVENT( AI_DirectDamage, idAI::Event_DirectDamage ) + EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint ) + EVENT( AI_CanBecomeSolid, idAI::Event_CanBecomeSolid ) + EVENT( AI_BecomeSolid, idAI::Event_BecomeSolid ) + EVENT( EV_BecomeNonSolid, idAI::Event_BecomeNonSolid ) + EVENT( AI_BecomeRagdoll, idAI::Event_BecomeRagdoll ) + EVENT( AI_BecomePassive, idAI::Event_BecomePassive ) + EVENT( AI_BecomeAggressive, idAI::Event_BecomeAggressive ) + EVENT( AI_StopRagdoll, idAI::Event_StopRagdoll ) + EVENT( AI_SetHealth, idAI::Event_SetHealth ) + EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy ) + EVENT( AI_FaceEntity, idAI::Event_FaceEntity ) + EVENT( AI_StopThinking, idAI::Event_StopThinking ) + EVENT( AI_LockEnemyOrigin, idAI::Event_LockEnemyOrigin ) + EVENT( AI_JumpFrame, idAI::Event_JumpFrame ) + EVENT( AI_RealKill, idAI::Event_RealKill ) + EVENT( AI_Kill, idAI::Event_Kill ) + EVENT( AI_RemoveUpdateSpawner, idAI::Event_RemoveUpdateSpawner ) + EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement ) + EVENT( AI_Speak, idAI::Event_Speak ) + EVENT( AI_SpeakRandom, idAI::Event_SpeakRandom ) + EVENT( AI_IsSpeaking, idAI::Event_IsSpeaking ) + EVENT( AI_IsTethered, idAI::Event_IsTethered ) + EVENT( AI_IsWithinTether, idAI::Event_IsWithinTether ) + EVENT( EV_IsMoving, idAI::Event_IsMoving ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_FindEnemy, idAI::Event_FindEnemy ) + EVENT( EV_SetKey, idAI::Event_SetKey ) + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + EVENT( EV_PostSpawn, idAI::Event_PostSpawn ) + // RAVEN END +END_CLASS + +/* +===================== +idAI::Event_PredictEnemyPos +===================== +*/ +void idAI::Event_PredictEnemyPos( float time ) { + predictedPath_t path; + idEntity* enemyEnt = enemy.ent; + + // if no enemy set + if ( !enemyEnt ) { + idThread::ReturnVector( physicsObj.GetOrigin() ); + return; + } + + // predict the enemy movement + idAI::PredictPath( enemyEnt, aas, enemy.lastKnownPosition, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnVector( path.endPos ); +} + +/* +===================== +idAI::Event_TestAnimMoveTowardEnemy +===================== +*/ +void idAI::Event_TestAnimMoveTowardEnemy( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + float yaw; + idVec3 delta; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + delta = enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(); + yaw = delta.ToYaw(); + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestAnimMove +===================== +*/ +void idAI::Event_TestAnimMove( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + int animLen; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + animLen = animator.AnimLength( anim ); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMoveToPosition +===================== +*/ +void idAI::Event_TestMoveToPosition( const idVec3 &position ) { + predictedPath_t path; + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), position - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, gameLocal.msec ); + if ( path.endEvent ) { + gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMeleeAttack +===================== +*/ +void idAI::Event_TestMeleeAttack( void ) { + bool result = TestMelee(); + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_TestAnimAttack +===================== +*/ +void idAI::Event_TestAnimAttack( const char *animname ) { + int anim; + predictedPath_t path; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), animator.TotalMovementDelta( anim ), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnInt( path.blockingEntity && ( path.blockingEntity == enemy.ent ) ); +} + +/* +===================== +idAI::Event_LockEnemyOrigin +===================== +*/ +void idAI::Event_LockEnemyOrigin ( void ) { + enemy.fl.lockOrigin = true; +} + +/* +===================== +idAI::Event_StopThinking +===================== +*/ +void idAI::Event_StopThinking( void ) { + BecomeInactive( TH_THINK ); + idThread *thread = idThread::CurrentThread(); + if ( thread ) { + thread->DoneProcessing(); + } +} + +/* +===================== +idAI::Event_JumpFrame +===================== +*/ +void idAI::Event_JumpFrame( void ) { + aifl.jump = true; +} + +/* +===================== +idAI::Event_EnableClip +===================== +*/ +void idAI::Event_EnableClip( void ) { + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + Event_EnableGravity ( ); +} + +/* +===================== +idAI::Event_DisableClip +===================== +*/ +void idAI::Event_DisableClip( void ) { + physicsObj.SetClipMask( 0 ); + Event_DisableGravity ( ); +} + +/* +===================== +idAI::Event_EnableGravity +===================== +*/ +void idAI::Event_EnableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, false ); +} + +/* +===================== +idAI::Event_DisableGravity +===================== +*/ +void idAI::Event_DisableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, true ); +} + +/* +===================== +idAI::Event_EnableAFPush +===================== +*/ +void idAI::Event_EnableAFPush( void ) { + move.fl.allowPushMovables = true; +} + +/* +===================== +idAI::Event_DisableAFPush +===================== +*/ +void idAI::Event_DisableAFPush( void ) { + move.fl.allowPushMovables = false; +} + +/* +===================== +idAI::Event_EnableDamage +===================== +*/ +void idAI::Event_EnableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, true ); +} + +/* +===================== +idAI::Event_DisableDamage +===================== +*/ +void idAI::Event_DisableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, false ); +} + +/* +=============== +idAI::Event_DisablePain +=============== +*/ +void idAI::Event_DisablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, true ); +} + +/* +=============== +idAI::Event_EnablePain +=============== +*/ +void idAI::Event_EnablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, false ); +} + +/* +=============== +idAI::Event_EnableTarget +=============== +*/ +void idAI::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +=============== +idAI::Event_DisableTarget +=============== +*/ +void idAI::Event_DisableTarget ( void ) { + fl.notarget = true; +} + + +/* +===================== +idAI::Event_EnableAutoBlink +===================== +*/ +void idAI::Event_EnableAutoBlink( void ) { + fl.allowAutoBlink = true; +} + +/* +===================== +idAI::Event_DisableAutoBlink +===================== +*/ +void idAI::Event_DisableAutoBlink( void ) { + fl.allowAutoBlink = false; +} + +/* +===================== +idAI::Event_BecomeAggressive +===================== +*/ +void idAI::Event_BecomeAggressive ( void ) { + combat.fl.ignoreEnemies = false; + combat.fl.aware = true; + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_BecomePassive +===================== +*/ +void idAI::Event_BecomePassive ( int ignoreEnemies ) { + combat.fl.ignoreEnemies = (ignoreEnemies != 0); + combat.fl.aware = false; + SetEnemy ( NULL ); + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_LookAt +===================== +*/ +void idAI::Event_LookAt ( idEntity* lookAt ) { + lookTarget = lookAt; +} + +/* +===================== +idAI::LookAtEntity +===================== +*/ +void idAI::LookAtEntity( idEntity *ent, float duration ) { + if ( ent == this ) { + ent = NULL; + } + + if ( ( ent != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = ent; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + focusTime = gameLocal.time + SEC2MS( duration ); +} + +/* +================ +idAI::Event_ThrowMoveable +================ +*/ +void idAI::Event_ThrowMoveable( void ) { + idEntity *ent; + idEntity *moveable = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + moveable = ent; + break; + } + } + if ( moveable ) { + moveable->Unbind(); + moveable->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_ThrowAF +================ +*/ +void idAI::Event_ThrowAF( void ) { + idEntity *ent; + idEntity *af = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = ent; + break; + } + } + if ( af ) { + af->Unbind(); + af->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_SetAngles +================ +*/ +void idAI::Event_SetAngles( idAngles const &ang ) { + move.current_yaw = ang.yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); +} + +/* +================ +idAI::Event_GetAngles +================ +*/ +void idAI::Event_GetAngles( void ) { + idThread::ReturnVector( idVec3( 0.0f, move.current_yaw, 0.0f ) ); +} + +/* +================ +idAI::Event_RealKill +================ +*/ +void idAI::Event_RealKill( void ) { + health = 0; + + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + Killed( this, this, 0, vec3_zero, INVALID_JOINT ); +} + +/* +================ +idAI::Event_Kill +================ +*/ +void idAI::Event_Kill( void ) { + PostEventMS( &AI_RealKill, 0 ); +} + +/* +================ +idAI::Event_RemoveUpdateSpawner +================ +*/ +void idAI::Event_RemoveUpdateSpawner( void ) { + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + PostEventMS( &EV_Remove, 0 ); +} +/* +===================== +idAI::Event_FindActorsInBounds +===================== +*/ +void idAI::Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ) { + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + int i; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedEntities = gameLocal.EntitiesTouchingBounds( this, idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++ ) { + ent = entityList[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + idThread::ReturnEntity( ent ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +===================== +idAI::Event_ClosestReachableEnemyOfEntity +===================== +*/ +void idAI::Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ) { + idActor *actor; + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + int areaNum; + int enemyAreaNum; + aasPath_t path; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !team_mate->IsType( idActor::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() ); + } + + actor = static_cast( team_mate ); + + const idVec3 &origin = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( origin ); + + bestDistSquared = idMath::INFINITY; + bestEnt = NULL; + for( ent = actor->enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - origin; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + const idVec3 &enemyPos = ent->GetPhysics()->GetOrigin(); + enemyAreaNum = PointReachableAreaNum( enemyPos ); + if ( ( areaNum != 0 ) && PathToGoal( path, areaNum, origin, enemyAreaNum, enemyPos ) ) { + bestEnt = ent; + bestDistSquared = distSquared; + } + } + } + + idThread::ReturnEntity( bestEnt ); +} + +/* +===================== +idAI::Event_EntityInAttackCone +===================== +*/ +void idAI::Event_EntityInAttackCone( idEntity *ent ) { + float attack_cone; + idVec3 delta; + float yaw; + float relYaw; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + delta = ent->GetPhysics()->GetOrigin() - GetEyePosition(); + + // get our gravity normal + const idVec3 &gravityDir = GetPhysics()->GetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + yaw = delta.ToYaw(); + + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + relYaw = idMath::AngleNormalize180( move.ideal_yaw - yaw ); + if ( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idAI::Event_CanReachPosition +================ +*/ +void idAI::Event_CanReachPosition( const idVec3 &pos ) { + aasPath_t path; + int toAreaNum; + int areaNum; + + toAreaNum = PointReachableAreaNum( pos ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEntity +================ +*/ +void idAI::Event_CanReachEntity( idEntity *ent ) { + aasPath_t path; + int toAreaNum; + int areaNum; + idVec3 pos; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + toAreaNum = PointReachableAreaNum( pos ); + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !toAreaNum || !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEnemy +================ +*/ +void idAI::Event_CanReachEnemy( void ) { + aasPath_t path; + int toAreaNum = 0; + int areaNum; + idVec3 pos; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if( enemyEnt->IsType( idActor::GetClassType() ) ){ + idActor *enemyAct = static_cast( enemyEnt ); + if ( enemyAct->OnLadder() ) { + idThread::ReturnInt( false ); + return; + } + enemyAct->GetAASLocation( aas, pos, toAreaNum ); + } + } else { + pos = enemyEnt->GetPhysics()->GetOrigin(); + toAreaNum = PointReachableAreaNum( pos ); + } + + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_GetReachableEntityPosition +================ +*/ +void idAI::Event_GetReachableEntityPosition( idEntity *ent ) { + int toAreaNum; + idVec3 pos; + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + if ( aas ) { + toAreaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( toAreaNum, pos ); + } + + idThread::ReturnVector( pos ); +} + +/* +================ +idAI::Event_ScriptedMove +================ +*/ +void idAI::Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + ScriptedMove ( destEnt, minDist, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedFace +================ +*/ +void idAI::Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + ScriptedFace ( faceEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAnim +================ +*/ +void idAI::Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + ScriptedAnim ( animname, blendFrames, loop, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAction +================ +*/ +void idAI::Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + ScriptedAction ( actionEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedPlaybackMove +================ +*/ +void idAI::Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackMove ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedPlaybackAim +================ +*/ +void idAI::Event_ScriptedPlaybackAim( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackAim ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedDone +================ +*/ +void idAI::Event_ScriptedDone ( void ) { + idThread::ReturnFloat ( !aifl.scripted ); +} + +/* +================ +idAI::Event_ScriptedStop +================ +*/ +void idAI::Event_ScriptedStop ( void ) { + ScriptedStop ( ); +} + +/* +================ +idAI::Event_AllowHiddenMovement +================ +*/ +void idAI::Event_AllowHiddenMovement( int enable ) { + move.fl.allowHiddenMove = ( enable != 0 ); +} + +/* +================ +idAI::Event_SetScript +================ +*/ +void idAI::Event_SetScript ( const char* scriptName, const char* funcName ) { + SetScript ( scriptName, funcName ); +} + +/* +================ +idAI::Event_SetMoveSpeed +================ +*/ +void idAI::Event_SetMoveSpeed ( int speed ) { + switch ( speed ) { + case AIMOVESPEED_DEFAULT: + move.fl.noRun = false; + move.fl.noWalk = false; + break; + + case AIMOVESPEED_RUN: + move.fl.noRun = false; + move.fl.noWalk = true; + break; + + case AIMOVESPEED_WALK: + move.fl.noRun = true; + move.fl.noWalk = false; + break; + } +} + +/* +================ +idAI::Event_SetPassivePrefix +================ +*/ +void idAI::Event_SetPassivePrefix ( const char* prefix ) { + SetPassivePrefix ( prefix ); +} + +/* +================ +idAI::Event_Attack +================ +*/ +void idAI::Event_Attack ( const char* attackName, const char* jointName ) { + Attack ( attackName, animator.GetJointHandle ( jointName ), enemy.ent ); // , physicsObj.GetPushedLinearVelocity ( ) ); +} + +/* +================ +idAI::Event_AttackMelee +================ +*/ +void idAI::Event_AttackMelee( const char* meleeName ) { + const idDict* meleeDict; + meleeDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", meleeName ) ), false ); + if ( !meleeDict ) { + gameLocal.Error ( "missing meleeDef '%s' for ai entity '%s'", meleeName, GetName() ); + } + AttackMelee ( meleeName, meleeDict ); +} + +/* +================ +idAI::Event_ScriptedJumpDown +================ +*/ +void idAI::Event_ScriptedJumpDown( float yaw ) { + if ( animator.HasAnim( "jumpdown_start" ) ) + { + aifl.scripted = true; + move.ideal_yaw = yaw; + SetState( "State_ScriptedJumpDown" ); + } +} + +/* +================ +idAI::Event_FindEnemy +================ +*/ +void idAI::Event_FindEnemy( float distSqr ) { + idThread::ReturnEntity ( FindEnemy( false, 1, distSqr )); +} + +/* +================ +idAI::Event_SetKey +================ +*/ +void idAI::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); + + OnSetKey ( key, value ); +} + +/* +================ +idAI::Event_PostSpawn +================ +*/ +void idAI::Event_PostSpawn( void ) { + // RAVEN BEGIN + // twhitaker: difficulty levels + if ( team == TEAM_MARINE ) { + //health /= 1.0f + gameLocal.GetDifficultyModifier( ); + + //buddies are a little more healthy on hard & nightmare since the baddies deal so much more damage + switch ( g_skill.GetInteger() ) { + case 3: + health *= 1.4f; + break; + case 2: + health *= 1.2f; + break; + case 0: + health *= 1.2f; + break; + case 1: + default: + break; + } + } else { + health *= 1.0f + gameLocal.GetDifficultyModifier( ); + } + // RAVEN END +} diff --git a/src/game/ai/AI_pathing.cpp b/src/game/ai/AI_pathing.cpp new file mode 100644 index 0000000..ed4bf4a --- /dev/null +++ b/src/game/ai/AI_pathing.cpp @@ -0,0 +1,1649 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +// nmckenzie: +#include "AI.h" +// RAVEN END + +/* +=============================================================================== + + Dynamic Obstacle Avoidance + + - assumes the AI lives inside a bounding box aligned with the gravity direction + - obstacles in proximity of the AI are gathered + - if obstacles are found the AAS walls are also considered as obstacles + - every obstacle is represented by an oriented bounding box (OBB) + - an OBB is projected onto a 2D plane orthogonal to AI's gravity direction + - the 2D windings of the projections are expanded for the AI bbox + - a path tree is build using clockwise and counter clockwise edge walks along the winding edges + - the path tree is pruned and optimized + - the shortest path is chosen for navigation + +=============================================================================== +*/ + +const float MAX_OBSTACLE_RADIUS = 256.0f; +const float PUSH_OUTSIDE_OBSTACLES = 0.5f; +const float CLIP_BOUNDS_EPSILON = 10.0f; +const int MAX_AAS_WALL_EDGES = 256; +const int MAX_OBSTACLES = 256; +const int MAX_PATH_NODES = 256; +const int MAX_OBSTACLE_PATH = 64; + +typedef struct obstacle_s { + idVec2 bounds[2]; + idWinding2D winding; + idEntity * entity; + bool fakePlayerForwardObstacle; +} obstacle_t; + +typedef struct pathNode_s { + int dir; + idVec2 pos; + idVec2 delta; + float dist; + int obstacle; + int edgeNum; + int numNodes; + struct pathNode_s * parent; + struct pathNode_s * children[2]; + struct pathNode_s * next; + void Init(); +} pathNode_t; + +void pathNode_s::Init() { + dir = 0; + pos.Zero(); + delta.Zero(); + obstacle = -1; + edgeNum = -1; + numNodes = 0; + parent = children[0] = children[1] = next = NULL; +} + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc pathNodeAllocator; +// RAVEN END + +/* +============ +LineIntersectsPath +============ +*/ +bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) { + float d0, d1, d2, d3; + idVec3 plane1, plane2; + + plane1 = idWinding2D::Plane2DFromPoints( start, end ); + d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z; + while( node->parent ) { + d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z; + if ( FLOATSIGNBITSET( d0 ) ^ FLOATSIGNBITSET( d1 ) ) { + plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos ); + d2 = plane2.x * start.x + plane2.y * start.y + plane2.z; + d3 = plane2.x * end.x + plane2.y * end.y + plane2.z; + if ( FLOATSIGNBITSET( d2 ) ^ FLOATSIGNBITSET( d3 ) ) { + return true; + } + } + d0 = d1; + node = node->parent; + } + return false; +} + +/* +============ +PointInsideObstacle +============ +*/ +int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point, bool skipFakePlayerForwardObstacles = false ) { + int i; + + for ( i = 0; i < numObstacles; i++ ) { + if ( skipFakePlayerForwardObstacles && obstacles[i].fakePlayerForwardObstacle ) { + //don't care if we're inside of these + continue; + } + + const idVec2 *bounds = obstacles[i].bounds; + if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) { + continue; + } + + if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) { + continue; + } + + return i; + } + + return -1; +} + +/* +============ +GetPointOutsideObstacles +============ +*/ +void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) { + int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2]; + float d, bestd, scale[2]; + idVec3 plane, bestPlane; + idVec2 newPoint, dir, bestPoint; + int *queue; + bool *obstacleVisited; + idWinding2D w1, w2; + + if ( obstacle ) { + *obstacle = -1; + } + if ( edgeNum ) { + *edgeNum = -1; + } + + bestObstacle = PointInsideObstacle( obstacles, numObstacles, point ); + if ( bestObstacle == -1 ) { + return; + } + + const idWinding2D &w = obstacles[bestObstacle].winding; + bestd = idMath::INFINITY; + bestEdgeNum = 0; + for ( i = 0; i < w.GetNumPoints(); i++ ) { + plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d < bestd ) { + bestd = d; + bestPlane = plane; + bestEdgeNum = i; + } + // if this is a wall always try to pop out at the first edge + if ( obstacles[bestObstacle].entity == NULL ) { + break; + } + } + + newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2(); + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + point = newPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + + queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) ); + obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) ); + + queueStart = 0; + queueEnd = 1; + queue[0] = bestObstacle; + + memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) ); + obstacleVisited[bestObstacle] = true; + + bestd = idMath::INFINITY; + for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) { + w1 = obstacles[i].winding; + w1.Expand( PUSH_OUTSIDE_OBSTACLES ); + + for ( j = 0; j < numObstacles; j++ ) { + // if the obstacle has been visited already + if ( obstacleVisited[j] ) { + continue; + } + // if the bounds do not intersect + if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y || + obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + + queue[queueEnd++] = j; + obstacleVisited[j] = true; + + w2 = obstacles[j].winding; + w2.Expand( 0.2f ); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + dir = w1[(k+1)%w1.GetNumPoints()] - w1[k]; + if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) { + continue; + } + for ( n = 0; n < 2; n++ ) { + newPoint = w1[k] + scale[n] * dir; + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + d = ( newPoint - point ).LengthSqr(); + if ( d < bestd ) { + bestd = d; + bestPoint = newPoint; + bestEdgeNum = edgeNums[n]; + bestObstacle = j; + } + } + } + } + } + + if ( bestd < idMath::INFINITY ) { + point = bestPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + } + gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" ); +} + +/* +============ +GetFirstBlockingObstacle +============ +*/ +bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) { + int i, edgeNums[2]; + float dist, scale1, scale2; + idVec2 bounds[2]; + + // get bounds for the current movement delta + bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(delta.x)].x += delta.x; + bounds[FLOATSIGNBITNOTSET(delta.y)].y += delta.y; + + // test for obstacles blocking the path + blockingScale = idMath::INFINITY; + dist = delta.Length(); + for ( i = 0; i < numObstacles; i++ ) { + if ( i == skipObstacle ) { + continue; + } + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) { + if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) { + blockingScale = scale1; + blockingObstacle = i; + blockingEdgeNum = edgeNums[0]; + } + } + } + return ( blockingScale < 1.0f ); +} + +/* +============ +GetObstacles +============ +*/ +int GetObstacles( const idActor* owner, const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) { + int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum, numIterations, extrude; + int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2]; + float stepHeight, headHeight, blockingScale, min, max; + idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd; + idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal; + idVec2 obDelta; + idPhysics *obPhys; + idBox box; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 extrudeVec; + idPlayer* player = gameLocal.GetLocalPlayer(); + bool extrudePlayer = false; + + if ( player && owner->team == player->team ) { + if ( player->HasEnemies() + || (player->weapon && player->weapon->lastAttack > gameLocal.GetTime() - 2000) + || (owner->IsType( idAI::GetClassType() ) && ((idAI*)owner)->GetEnemy()) ) { + + extrudeVec = player->firstPersonViewAxis[0]; + extrudeVec[2] = 0; + extrudeVec.Normalize(); + idVec3 myMoveDir = (seekPos-startPos); + /* + myMoveDir.z = 0; + if ( myMoveDir * extrudeVec > 200.0f ) + {//I'm already moving out away from him in the direction he's moving... (he's following me?) + } + else + {//we are heading into his line of sight, stop + */ + extrudePlayer = true; + extrudeVec *= (player->GetPhysics()->GetBounds()[1][0]-player->GetPhysics()->GetBounds()[0][0]); + //} + //FIXME: keep moving if already inside this cone? Otherwise, we'll stop in our tracks? + } + } + + numObstacles = 0; + + seekDelta = seekPos - startPos; + expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + + physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight ); + stepHeight += aas->GetSettings()->maxStepHeight; + + // clip bounds for the obstacle search space + clipBounds[0] = clipBounds[1] = startPos; + clipBounds.AddPoint( seekPos ); + clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS ); + clipMask = physics->GetClipMask(); + + // find all obstacles touching the clip bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( owner, clipBounds, clipMask, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( obEnt->IsType( idActor::GetClassType() ) ) { +// RAVEN END + obPhys = obEnt->GetPhysics(); + // ignore myself, my enemy, and dead bodies + if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) { + continue; + } + // if the actor is moving + // cdr: Alternate Routes Bug + // idVec3 v1 = obPhys->GetLinearVelocity(); + // if ( v1.LengthSqr() > Square( 10.0f ) ) { + // idVec3 v2 = physics->GetLinearVelocity(); + // if ( v2.LengthSqr() > Square( 10.0f ) ) { + // // if moving in about the same direction + // if ( v1 * v2 > 0.0f ) { + // continue; + // } + // } + // } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +// cdr: Ignore Pushable objects + } else if (obEnt->GetPhysics()->IsPushable()) { + continue; + } else if ( obEnt->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // moveables are considered obstacles + } else { + // ignore everything else + continue; + } + + // check if we can step over the object + clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max ); + if ( max < stepHeight || min > headHeight ) { + // can step over this one + continue; + } + + numIterations = 1; + if ( extrudePlayer && obEnt == player ) + {//do magic + numIterations = 3; + } + for ( extrude = 0; extrude < numIterations; extrude++ ) { + // project a box containing the obstacle onto the floor plane + box = idBox( clipModel->GetBounds(), extrude?clipModel->GetOrigin()+(extrude*extrudeVec):clipModel->GetOrigin(), clipModel->GetAxis() ); + numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts ); + + // create a 2D winding for the obstacle; + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + for ( j = 0; j < numVerts; j++ ) { + obstacle.winding.AddPoint( silVerts[j].ToVec2() ); + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( j = 0; j < numVerts; j++ ) { + silVerts[j].z = startPos.z; + } + for ( j = 0; j < numVerts; j++ ) { + gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 ); + } + } + + // expand the 2D winding for collision with a 2D box + obstacle.winding.ExpandForAxialBox( expBounds ); + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = obEnt; + if ( extrudePlayer && obEnt == player && extrude > 0 ) { + //don't care if we're inside one of these, because that would just make us stop where we are, anyway! + obstacle.fakePlayerForwardObstacle = true; + } else { + obstacle.fakePlayerForwardObstacle = false; + } + } + } + + // if there are no dynamic obstacles the path should be through valid AAS space + if ( numObstacles == 0 ) { + return 0; + } + + // if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space + if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2(), true ) == -1 ) { + if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) { + return 0; + } + } + + // create obstacles for AAS walls + if ( aas ) { + float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f; + + numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES ); + aas->SortWallEdges( wallEdges, numWallEdges ); + + lastVerts[0] = lastVerts[1] = 0; + lastEdgeNormal.Zero(); + nextVerts[0] = nextVerts[1] = 0; + for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) { + aas->GetEdge( wallEdges[i], start, end ); + aas->GetEdgeVertexNumbers( wallEdges[i], verts ); + edgeDir = end.ToVec2() - start.ToVec2(); + edgeDir.Normalize(); + edgeNormal.x = edgeDir.y; + edgeNormal.y = -edgeDir.x; + if ( i < numWallEdges-1 ) { + aas->GetEdge( wallEdges[i+1], nextStart, nextEnd ); + aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts ); + nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2(); + nextEdgeDir.Normalize(); + nextEdgeNormal.x = nextEdgeDir.y; + nextEdgeNormal.y = -nextEdgeDir.x; + } + + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + obstacle.winding.AddPoint( end.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize ); + obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize ); + if ( lastVerts[1] == verts[0] ) { + obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[1] -= edgeDir; + } + if ( verts[1] == nextVerts[0] ) { + obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[0] += edgeDir; + } + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = NULL; + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( lastVerts, verts, sizeof( lastVerts ) ); +// RAVEN END + lastEdgeNormal = edgeNormal; + } + } + + // show obstacles + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( i = 0; i < numObstacles; i++ ) { + obstacle_t &obstacle = obstacles[i]; + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + silVerts[j].ToVec2() = obstacle.winding[j]; + silVerts[j].z = startPos.z; + } + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 ); + } + } + } + + return numObstacles; +} + +/* +============ +FreePathTree_r +============ +*/ +void FreePathTree_r( pathNode_t *node ) { + if ( node->children[0] ) { + FreePathTree_r( node->children[0] ); + } + if ( node->children[1] ) { + FreePathTree_r( node->children[1] ); + } + pathNodeAllocator.Free( node ); +} + +/* +============ +DrawPathTree +============ +*/ +void DrawPathTree( const pathNode_t *root, const float height ) { + int i; + idVec3 start, end; + const pathNode_t *node; + + for ( node = root; node; node = node->next ) { + for ( i = 0; i < 2; i++ ) { + if ( node->children[i] ) { + start.ToVec2() = node->pos; + start.z = height; + end.ToVec2() = node->children[i]->pos; + end.z = height; + gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 ); + break; + } + } + } +} + +/* +============ +GetPathNodeDelta +============ +*/ +bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) { + int numPoints, edgeNum; + bool facing; + idVec2 seekDelta, dir; + pathNode_t *n; + + numPoints = obstacles[node->obstacle].winding.GetNumPoints(); + + // get delta along the current edge + while( 1 ) { + edgeNum = ( node->edgeNum + node->dir ) % numPoints; + node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos; + if ( node->delta.LengthSqr() > 0.01f ) { + break; + } + node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints; + } + + // if not blocked + if ( !blocked ) { + + // test if the current edge faces the goal + seekDelta = seekPos - node->pos; + facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f; + + // if the current edge faces goal and the line from the current + // position to the goal does not intersect the current path + if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) { + node->delta = seekPos - node->pos; + node->edgeNum = -1; + } + } + + // if the delta is along the obstacle edge + if ( node->edgeNum != -1 ) { + // if the edge is found going from this node to the root node + for ( n = node->parent; n; n = n->parent ) { + + if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) { + continue; + } + + // test whether or not the edge segments actually overlap + if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) { + continue; + } + if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) { + continue; + } + + break; + } + if ( n ) { + return false; + } + } + return true; +} + +/* +============ +BuildPathTree +============ +*/ +pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) { + int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH; + float blockingScale; + pathNode_t *root, *node, *child; + + // gcc 4.0 + idQueueTemplate pathNodeQueue, treeQueue; + + root = pathNodeAllocator.Alloc(); + root->Init(); + root->pos = startPos; + + root->delta = seekPos - root->pos; + root->numNodes = 0; + pathNodeQueue.Add( root ); + + for ( node = pathNodeQueue.Get(); node && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) { + + treeQueue.Add( node ); + + // if this path has more than twice the number of nodes than the best path so far + if ( node->numNodes > bestNumNodes * 2 ) { + continue; + } + + // don't move outside of the clip bounds + idVec2 endPos = node->pos + node->delta; + if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x || + endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) { + continue; + } + + // if an obstacle is blocking the path + if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) { + + if ( path.firstObstacle == NULL ) { + path.firstObstacle = obstacles[blockingObstacle].entity; + } + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(obstacles[blockingObstacle].entity); + // RAVEN END + + + node->delta *= blockingScale; + + if ( node->edgeNum == -1 ) { + node->children[0] = pathNodeAllocator.Alloc(); + node->children[0]->Init(); + node->children[1] = pathNodeAllocator.Alloc(); + node->children[1]->Init(); + node->children[0]->dir = 0; + node->children[1]->dir = 1; + node->children[0]->parent = node->children[1]->parent = node; + node->children[0]->pos = node->children[1]->pos = node->pos + node->delta; + node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle; + node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum; + node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[0] ); + } + if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[1] ); + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->obstacle = blockingObstacle; + child->edgeNum = blockingEdgeNum; + child->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) { + pathNodeQueue.Add( child ); + } + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->numNodes = node->numNodes + 1; + + // there is a free path towards goal + if ( node->edgeNum == -1 ) { + if ( node->numNodes < bestNumNodes ) { + bestNumNodes = node->numNodes; + } + continue; + } + + child->obstacle = node->obstacle; + obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints(); + child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints; + + if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) { + pathNodeQueue.Add( child ); + } + } + } + + return root; +} + +/* +============ +PrunePathTree +============ +*/ +void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) { + int i; + float bestDist; + pathNode_t *node, *lastNode, *n, *bestNode; + + node = root; + while( node ) { + + node->dist = ( seekPos - node->pos ).LengthSqr(); + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + + // find the node closest to the goal along this path + bestDist = idMath::INFINITY; + bestNode = node; + for ( n = node; n; n = n->parent ) { + if ( n->children[0] && n->children[1] ) { + break; + } + if ( n->dist < bestDist ) { + bestDist = n->dist; + bestNode = n; + } + } + + // free tree down from the best node + for ( i = 0; i < 2; i++ ) { + if ( bestNode->children[i] ) { + FreePathTree_r( bestNode->children[i] ); + bestNode->children[i] = NULL; + } + } + + for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && ( node->children[1] != lastNode ) ) { + node = node->children[1]; + break; + } + } + } + } +} + +/* +============ +OptimizePath +============ +*/ +int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) { + int i, numPathPoints, edgeNums[2]; + const pathNode_t *curNode, *nextNode; + idVec2 curPos, curDelta, bounds[2]; + float scale1, scale2, curLength; + + optimizedPath[0] = root->pos; +// BDUBE: FIXME - Added this line because quite a few places in the code count on there being at least 2 items in that array + optimizedPath[1] = root->pos; + + numPathPoints = 1; + + for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) { + + for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) { + + // can only take shortcuts when going from one object to another + if ( nextNode->obstacle == curNode->obstacle ) { + continue; + } + + curPos = curNode->pos; + curDelta = nextNode->pos - curPos; + curLength = curDelta.Length(); + + // get bounds for the current movement delta + bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(curDelta.x)].x += curDelta.x; + bounds[FLOATSIGNBITNOTSET(curDelta.y)].y += curDelta.y; + + // test if the shortcut intersects with any obstacles + for ( i = 0; i < numObstacles; i++ ) { + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) { + if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) { + break; + } + if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) { + break; + } + } + } + if ( i >= numObstacles ) { + break; + } + } + + // store the next position along the optimized path + optimizedPath[numPathPoints++] = nextNode->pos; + } + + return numPathPoints; +} + +/* +============ +PathLength +============ +*/ +float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) { + int i; + float pathLength; + + // calculate the path length + pathLength = 0.0f; + for ( i = 0; i < numPathPoints-1; i++ ) { + pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast(); + } + + // add penalty if this path does not go in the current direction + if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) { + pathLength += 100.0f; + } + return pathLength; +} + +/* +============ +FindOptimalPath + + Returns true if there is a path all the way to the goal. +============ +*/ +bool FindOptimalPath( const idActor* owner, const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) { + int i, numPathPoints, bestNumPathPoints; + const pathNode_t *node, *lastNode, *bestNode; + idVec2 optimizedPath[MAX_OBSTACLE_PATH]; + float pathLength, bestPathLength; + bool pathToGoalExists, optimizedPathCalculated; + + seekPos.Zero(); + seekPos.z = height; + + pathToGoalExists = false; + optimizedPathCalculated = false; + + bestNode = root; + bestNumPathPoints = 0; + bestPathLength = idMath::INFINITY; + + node = root; + while( node ) { + + pathToGoalExists |= ( node->dist < 0.1f ); + + if ( node->dist <= bestNode->dist ) { + + if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) { + + if ( !optimizedPathCalculated ) { + bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() ); + seekPos.ToVec2() = optimizedPath[1]; + } + + numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath ); + pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() ); + + if ( pathLength < bestPathLength ) { + bestNode = node; + bestNumPathPoints = numPathPoints; + bestPathLength = pathLength; + seekPos.ToVec2() = optimizedPath[1]; + } + optimizedPathCalculated = true; + + } else { + + bestNode = node; + optimizedPathCalculated = false; + } + } + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && node->children[1] != lastNode ) { + node = node->children[1]; + break; + } + } + } + } + + if ( !pathToGoalExists ) { + if ( root->children[0] ) { + seekPos.ToVec2() = root->children[0]->pos; + } else if ( root->children[1] ) { + seekPos.ToVec2() = root->children[1]->pos; + } + } else if ( !optimizedPathCalculated ) { + OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + seekPos.ToVec2() = optimizedPath[1]; + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + idVec3 start, end; + start.z = end.z = height + 4.0f; + numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + for ( i = 0; i < numPathPoints-1; i++ ) { + start.ToVec2() = optimizedPath[i]; + end.ToVec2() = optimizedPath[i+1]; + gameRenderWorld->DebugArrow( colorCyan, start, end, 1 ); + } + } + + return pathToGoalExists; +} + +/* +============ +idAI::FindPathAroundObstacles + + Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks. +============ +*/ +bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) { + int numObstacles, areaNum, insideObstacle; + static obstacle_t obstacles[MAX_OBSTACLES]; + idBounds clipBounds; + idBounds bounds; + pathNode_t *root; + bool pathToGoalExists; + + path.seekPos = seekPos; + path.firstObstacle = NULL; + path.startPosOutsideObstacles = startPos; + path.startPosObstacle = NULL; + path.seekPosOutsideObstacles = seekPos; + path.seekPosObstacle = NULL; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.Clear(); + // RAVEN END + + if ( !aas ) { + return true; + } + idActor* owner = dynamic_cast(physics->GetSelf()); + + + bounds[1] = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -bounds[1]; + bounds[1].z = 32.0f; + + // get the AAS area number and a valid point inside that area + areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles ); + + // get all the nearby obstacles + numObstacles = GetObstacles( owner, physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds ); + + // get a source position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.startPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.startPosObstacle); + // RAVEN END + } + + // get a goal position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.seekPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.seekPosObstacle); + // RAVEN END + } + + // if start and destination are pushed to the same point, we don't have a path around the obstacle + if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) { + if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) { + return false; + } + } + + // build a path tree + root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path ); + + // draw the path tree + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + DrawPathTree( root, physics->GetOrigin().z ); + } + + // prune the tree + PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() ); + + // find the optimal path + pathToGoalExists = FindOptimalPath( owner, root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos ); + + // free the tree + FreePathTree_r( root ); + + return pathToGoalExists; +} + +/* +============ +idAI::FreeObstacleAvoidanceNodes +============ +*/ +void idAI::FreeObstacleAvoidanceNodes( void ) { + pathNodeAllocator.Shutdown(); +} + + +/* +=============================================================================== + + Path Prediction + + Uses the AAS to quickly and accurately predict a path for a certain + period of time based on an initial position and velocity. + +=============================================================================== +*/ + +const float OVERCLIP = 1.001f; +const int MAX_FRAME_SLIDE = 5; + +typedef struct pathTrace_s { + float fraction; + idVec3 endPos; + idVec3 normal; + const idEntity * blockingEntity; +} pathTrace_t; + +/* +============ +PathTrace + + Returns true if a stop event was triggered. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Adding ignoreent parm +bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path, const idEntity *ignore = NULL ) { +// RAVEN BEGIN + trace_t clipTrace; + aasTrace_t aasTrace; + + memset( &trace, 0, sizeof( trace ) ); + + if ( !aas || !aas->GetSettings() ) { +// RAVEN BEGIN +// nmckenzie: Added ignore ent +// ddynerman: multiple clip worlds + gameLocal.Translation( ent, clipTrace, start, end, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + // NOTE: could do (expensive) ledge detection here for when there is no AAS file + + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } else { + aasTrace.getOutOfSolid = true; + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + aasTrace.flags |= AREA_LEDGE; + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + aasTrace.travelFlags |= TFL_INVALID; + } + + aas->Trace( aasTrace, start, end ); + +// RAVEN BEGIN +// nmckenzie: Added ignore ent. + gameLocal.TranslationEntities( ent, clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + if ( clipTrace.fraction >= 1.0f ) { + + trace.fraction = aasTrace.fraction; + trace.endPos = aasTrace.endpos; + trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal(); + trace.blockingEntity = gameLocal.world; + + if ( aasTrace.fraction < 1.0f ) { + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_LEDGE_AREA; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) { // PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_OBSTACLE; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) {// PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + } + } else { + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } + } + + if ( trace.fraction >= 1.0f ) { + trace.blockingEntity = NULL; + } + + return false; +} + +/* +============ +idAI::PredictPath + + Can also be used when there is no AAS file available however ledges are not detected. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Added ignore ent parm. +bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore ) { +// RAVEN END + int i, j, step, numFrames, curFrameTime; + idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart; + idVec3 gravity, gravityDir, invGravityDir; + float maxStepHeight, minFloorCos; + pathTrace_t trace; + + if ( aas && aas->GetSettings() ) { + gravity = aas->GetSettings()->gravity; + gravityDir = aas->GetSettings()->gravityDir; + invGravityDir = aas->GetSettings()->invGravityDir; + maxStepHeight = aas->GetSettings()->maxStepHeight; + minFloorCos = aas->GetSettings()->minFloorCos; + } else { + gravity = DEFAULT_GRAVITY_VEC3; + gravityDir = idVec3( 0, 0, -1 ); + invGravityDir = idVec3( 0, 0, 1 ); + maxStepHeight = 14.0f; + minFloorCos = 0.7f; + } + + path.endPos = start; + path.endVelocity = velocity; + path.endNormal.Zero(); + path.endEvent = 0; + path.endTime = 0; + path.blockingEntity = NULL; + + curStart = start; + curVelocity = velocity; + + numFrames = ( totalTime + frameTime - 1 ) / frameTime; + curFrameTime = frameTime; + for ( i = 0; i < numFrames; i++ ) { + + if ( i == numFrames-1 ) { + curFrameTime = totalTime - i * curFrameTime; + } + + delta = curVelocity * curFrameTime * 0.001f; + + path.endVelocity = curVelocity; + path.endTime = i * frameTime; + + // allow sliding along a few surfaces per frame + for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) { + + idVec3 lineStart = curStart; + + // allow stepping up three times per frame + for ( step = 0; step < 3; step++ ) { + + curEnd = curStart + delta; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path, ignore ) ) { +// RAVEN END + return true; + } + + if ( step ) { + + // step down at end point + tmpStart = trace.endPos; + curEnd = tmpStart - stepUp; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + + // if not moved any further than without stepping up, or if not on a floor surface + if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f || + ( trace.normal * invGravityDir ) < minFloorCos ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = lastEnd; + path.endEvent = SE_BLOCKED; + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd ); + } + + return true; + } + + curStart = lastEnd; + break; + } + } + + path.endNormal = trace.normal; + path.blockingEntity = trace.blockingEntity; + + // if the trace is not blocked or blocked by a floor surface + if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) { + curStart = trace.endPos; + break; + } + + // save last result + lastEnd = trace.endPos; + + // step up + stepUp = invGravityDir * maxStepHeight; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + stepUp *= trace.fraction; + curStart = trace.endPos; + } + + if ( ai_debugMove.GetBool() ) {//PredictPath + gameRenderWorld->DebugLine( colorRed, lineStart, curStart ); + } + + if ( trace.fraction >= 1.0f ) { + break; + } + + delta.ProjectOntoPlane( trace.normal, OVERCLIP ); + curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP ); + + if ( stopEvent & SE_BLOCKED ) { + // if going backwards + if ( (curVelocity - gravityDir * curVelocity * gravityDir ) * + (velocity - gravityDir * velocity * gravityDir) < 0.0f ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + + return true; + } + } + } + + if ( j >= MAX_FRAME_SLIDE ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + return true; + } + } + + // add gravity + curVelocity += gravity * frameTime * 0.001f; + } + + path.endTime = totalTime; + path.endVelocity = curVelocity; + path.endPos = curStart; + path.endEvent = 0; + + return false; +} + + +/* +=============================================================================== + + Trajectory Prediction + + Finds the best collision free trajectory for a clip model based on an + initial position, target position and speed. + +=============================================================================== +*/ + +/* +===================== +Ballistics + + get the ideal aim pitch angle in order to hit the target + also get the time it takes for the projectile to arrive at the target +===================== +*/ +typedef struct ballistics_s { + float angle; // angle in degrees in the range [-180, 180] + float time; // time it takes before the projectile arrives +} ballistics_t; + +static int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) { + int n, i; + float x, y, a, b, c, d, sqrtd, inva, p[2]; + + x = ( end.ToVec2() - start.ToVec2() ).Length(); + y = end[2] - start[2]; + + a = 4.0f * y * y + 4.0f * x * x; + b = -4.0f * speed * speed - 4.0f * y * gravity; + c = gravity * gravity; + + d = b * b - 4.0f * a * c; + if ( d <= 0.0f || a == 0.0f ) { + return 0; + } + sqrtd = idMath::Sqrt( d ); + inva = 0.5f / a; + p[0] = ( - b + sqrtd ) * inva; + p[1] = ( - b - sqrtd ) * inva; + n = 0; + for ( i = 0; i < 2; i++ ) { + if ( p[i] <= 0.0f ) { + continue; + } + d = idMath::Sqrt( p[i] ); + bal[n].angle = idMath::ATan( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x ); + bal[n].time = x / ( idMath::Cos( bal[n].angle ) * speed ); + bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) ); + n++; + } + + return n; +} + +/* +===================== +HeightForTrajectory + +Returns the maximum hieght of a given trajectory +===================== +*/ +// RAVEN BEGIN +// nmckenzie: Removing static for this one. +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) { +// RAVEN END + float maxHeight, t; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + + return maxHeight; +} + +/* +===================== +idAI::TestTrajectory +===================== +*/ +bool idAI::TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ) { + int i, numSegments; + float maxHeight, t, t2; + idVec3 points[5]; + trace_t trace; + bool result; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + // time it takes to fall from the top to the end height + t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) ); + + // start of parabolic + points[0] = start; + + if ( t < time ) { + numSegments = 4; + // point in the middle between top and start + t2 = ( time - t ) * 0.5f; + points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // top of parabolic + t2 = time - t; + points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // point in the middel between top and end + t2 = time - t * 0.5f; + points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } else { + numSegments = 2; + // point halfway through + t2 = time * 0.5f; + points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f; + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } + + // end of parabolic + points[numSegments] = end; + + if ( drawtime ) { + for ( i = 0; i < numSegments; i++ ) { + gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime ); + } + } + + // make sure projectile doesn't go higher than we want it to go + for ( i = 0; i < numSegments; i++ ) { + if ( points[i].z > max_height ) { + // goes higher than we want to allow + return false; + } + } + + result = true; + for ( i = 0; i < numSegments; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) { + result = true; + } else { + result = false; + } + break; + } + } + + if ( drawtime ) { + if ( clip ) { + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime ); + } else { + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + } + + return result; +} + +/* +===================== +idAI::PredictTrajectory + + returns true if there is a collision free trajectory for the clip model + aimDir is set to the ideal aim direction in order to hit the target +===================== +*/ +bool idAI::PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ) { + int n, i, j; + float zVel, a, t, pitch, s, c; + trace_t trace; + ballistics_t ballistics[2]; + idVec3 dir[2]; + idVec3 velocity; + idVec3 lastPos, pos; + + assert( targetEntity ); + + // check if the projectile starts inside the target + if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) { + aimDir = target - firePos; + aimDir.Normalize(); + return true; + } + + // if no velocity or the projectile is not affected by gravity + if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) { + + aimDir = target - firePos; + aimDir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, firePos, target, clip, mat3_identity, clipmask, ignore ); +// RAVEN END + + if ( drawtime ) { + gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime ); + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + + return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ); + } + + n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics ); + if ( n == 0 ) { + // there is no valid trajectory + aimDir = target - firePos; + aimDir.Normalize(); + return false; + } + + // make sure the first angle is the smallest + if ( n == 2 ) { + if ( ballistics[1].angle < ballistics[0].angle ) { + a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a; + t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t; + } + } + + // test if there is a collision free trajectory + for ( i = 0; i < n; i++ ) { + pitch = DEG2RAD( ballistics[i].angle ); + idMath::SinCos( pitch, s, c ); + dir[i] = target - firePos; + dir[i].z = 0.0f; + dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() ); + dir[i].z = s; + + zVel = projectileSpeed * dir[i].z; + + if ( ai_debugTrajectory.GetBool() ) { + t = ballistics[i].time / 100.0f; + velocity = dir[i] * projectileSpeed; + lastPos = firePos; + pos = firePos; + for ( j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; +// RAVEN BEGIN +// nmckenzie: Added time parm to debugline so I can actually see it. + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, drawtime ); +// RAVEN END + lastPos = pos; + } + } + + if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) { + aimDir = dir[i]; + return true; + } + } + + aimDir = dir[0]; + + // there is no collision free trajectory + return false; +} diff --git a/src/game/ai/Monster_Berserker.cpp b/src/game/ai/Monster_Berserker.cpp new file mode 100644 index 0000000..c99f9ef --- /dev/null +++ b/src/game/ai/Monster_Berserker.cpp @@ -0,0 +1,488 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterBerserker : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBerserker ); + + rvMonsterBerserker ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + int FilterTactical ( int availableTactical ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + int standingMeleeNoAttackTime; + int painConsecutive; + + // Actions + rvAIAction actionPopupAttack; + rvAIAction actionChargeAttack; + + bool Berz_CanHitEnemyFromAnim ( int animNum ); + bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ChargeAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_ChargeGroundImpact ( const stateParms_t& parms ); + stateResult_t Frame_DoBlastAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterBerserker ); +}; + +CLASS_DECLARATION( idAI, rvMonsterBerserker ) +END_CLASS + +/* +================ +rvMonsterBerserker::rvMonsterBerserker +================ +*/ +rvMonsterBerserker::rvMonsterBerserker ( ) { + painConsecutive = 0; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterBerserker::Spawn +================ +*/ +void rvMonsterBerserker::Spawn ( void ) { + actionPopupAttack.Init ( spawnArgs, "action_popupAttack", NULL, AIACTIONF_ATTACK ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", "Torso_ChargeAttack", AIACTIONF_ATTACK ); + PlayEffect( "fx_ambient_electricity", animator.GetJointHandle( "r_Lowerarm_Real" ), true ); + PlayEffect( "fx_ambient_electricity_mace", animator.GetJointHandle( "chain9" ), true ); +} + +/* +================ +rvMonsterBerserker::Save +================ +*/ +void rvMonsterBerserker::Save ( idSaveGame *savefile ) const { + actionPopupAttack.Save ( savefile ); + actionChargeAttack.Save ( savefile ); + savefile->WriteInt( painConsecutive ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::Restore +================ +*/ +void rvMonsterBerserker::Restore ( idRestoreGame *savefile ) { + actionPopupAttack.Restore ( savefile ); + actionChargeAttack.Restore ( savefile ); + savefile->ReadInt( painConsecutive ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( GetEnemy() && GetEnemy()->GetPhysics()->GetOrigin().z > GetPhysics()->GetOrigin().z + 24.0f ) + {//this is a ground attack and enemy is above me, so don't even try it, stupid! + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +============ +rvMonsterBerserker::CanHitEnemyFromAnim +============ +*/ +bool rvMonsterBerserker::Berz_CanHitEnemyFromAnim( int animNum ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin(); + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +rvMonsterBerserker::CheckAction_RangedAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !Berz_CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterBerserker::CheckActions +================ +*/ +bool rvMonsterBerserker::CheckActions ( void ) { + // Pop-up attack is a forward moving melee attack that throws the enemy up in the air + if ( PerformAction ( &actionPopupAttack, (checkAction_t)&idAI::CheckAction_LeapAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + // Charge attack is where the berserker will charge up his spike and slam it in to the ground + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterBerserker::CheckAction_ChargeAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&rvMonsterBerserker::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} +/* +================ +rvMonsterBerserker::FilterTactical +================ +*/ +int rvMonsterBerserker::FilterTactical ( int availableTactical ) { + if ( move.moveCommand == MOVE_TO_ENEMY && move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else if ( combat.tacticalCurrent != AITACTICAL_RANGED + && combat.tacticalCurrent != AITACTICAL_MELEE + && (combat.tacticalMaskAvailable&AITACTICAL_RANGED_BIT) ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else { + availableTactical &= ~AITACTICAL_RANGED_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterBerserker::OnTacticalChange + +Enable/Disable the ranged attack based on whether the berzerker needs it +================ +*/ +void rvMonsterBerserker::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + //once you've gone into melee once, it's okay to try ranged attacks later + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BIT; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterBerserker::GetDebugInfo +===================== +*/ +void rvMonsterBerserker::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_popupAttack", aiActionStatusString[actionPopupAttack.status], userData ); + proc ( "idAI", "action_chargeAttack", aiActionStatusString[actionChargeAttack.status], userData ); +} + +/* +================ +rvMonsterBerserker::Pain +================ +*/ +bool rvMonsterBerserker::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterBerserker::CheckPainActions +================ +*/ +bool rvMonsterBerserker::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBerserker ) + STATE ( "State_Killed", rvMonsterBerserker::State_Killed ) + + STATE ( "Torso_ChargeAttack", rvMonsterBerserker::State_Torso_ChargeAttack ) + STATE ( "Torso_Pain", rvMonsterBerserker::State_Torso_Pain ) + + STATE ( "Frame_ChargeGroundImpact", rvMonsterBerserker::Frame_ChargeGroundImpact ) + STATE ( "Frame_DoBlastAttack", rvMonsterBerserker::Frame_DoBlastAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterBerserker::State_Torso_ChargeAttack +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_ChargeAttack ( const stateParms_t& parms ) { + enum { + TORSO_CHARGEATTACK_INIT, + TORSO_CHARGEATTACK_WAIT, + TORSO_CHARGEATTACK_RECOVER, + TORSO_CHARGEATTACK_RECOVERWAIT + }; + + switch ( parms.stage ) { + // Start the charge attack animation + case TORSO_CHARGEATTACK_INIT: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the ground strike + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_WAIT ); + + // Wait for charge attack animation to finish + case TORSO_CHARGEATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVER ); + } + return SRESULT_WAIT; + + // Play recover animation + case TORSO_CHARGEATTACK_RECOVER: + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike_recover", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVERWAIT ); + + // Wait for recover animation to finish + case TORSO_CHARGEATTACK_RECOVERWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBerserker::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_Pain ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterBerserker::State_Killed +================ +*/ +stateResult_t rvMonsterBerserker::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + StopEffect ( "fx_ambient_electricity" ); + StopEffect ( "fx_ambient_electricity_mace" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterBerserker::Frame_ChargeGroundImpact +================ +*/ +stateResult_t rvMonsterBerserker::Frame_ChargeGroundImpact ( const stateParms_t& parms ) { + idVec3 start; + idVec3 end; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform ( animator.GetJointHandle ( "R_lowerArm_Real" ), gameLocal.time, start, axis ); + + end = start; + end.z -= 128; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + + gameLocal.PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_ground_impact" ), tr.endpos, idVec3(0,0,1).ToMat3() ); + + return SRESULT_OK; +} + +/* +================ +rvMonsterBerserker::Frame_DoBlastAttack +================ +*/ +stateResult_t rvMonsterBerserker::Frame_DoBlastAttack ( const stateParms_t& parms ) { + float i; + idVec3 start; + idMat3 axis; + idAngles angles ( 0.0f, move.current_yaw, 0.0f ); + const idDict* blastDict; + + blastDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_spike" ), false ); + if ( !blastDict ) { + gameLocal.Error ( "missing projectile on spike attack for AI entity '%s'", GetName ( ) ) ; + return SRESULT_OK; + } + + GetJointWorldTransform ( animator.GetJointHandle ( "end_spike" ), gameLocal.time, start, axis ); + + for( i = 0; i < 32; i++ ) { + angles.yaw += (360.0f / 32.0f); + AttackProjectile ( blastDict, start, angles ); + } + + return SRESULT_OK; +} diff --git a/src/game/ai/Monster_BossBuddy.cpp b/src/game/ai/Monster_BossBuddy.cpp new file mode 100644 index 0000000..90a4176 --- /dev/null +++ b/src/game/ai/Monster_BossBuddy.cpp @@ -0,0 +1,685 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +//------------------------------------------------------------ +class rvMonsterBossBuddy : public idAI +//------------------------------------------------------------ +{ +public: + + CLASS_PROTOTYPE( rvMonsterBossBuddy ); + + rvMonsterBossBuddy( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + void Think ( void ); + bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void OnWakeUp ( void ); + + // Add some dynamic externals for debugging + void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + bool CheckActions ( void ); +// void PerformAction ( const char* stateName, int blendFrames, bool noPain ); +// bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + void AdjustShieldState ( bool becomeShielded ); + void ReduceShields ( int amount ); + + int mShots; + int mShields; + int mMaxShields; + int mLastDamageTime; + int mShieldsLastFor; // read from def file, shouldn't need to be saved. + + bool mIsShielded; + bool mRequestedZoneMove; + bool mRequestedRecharge; + + //bool mCanIdle; + //bool mChaseMode; + +private: + + rvAIAction mActionRocketAttack; + rvAIAction mActionSlashMoveAttack; + rvAIAction mActionLightningAttack; + rvAIAction mActionDarkMatterAttack; + rvAIAction mActionMeleeMoveAttack; + rvAIAction mActionMeleeAttack; + + rvScriptFuncUtility mRequestRecharge; // script to run when a projectile is launched + rvScriptFuncUtility mRequestZoneMove; // script to run when he should move to the next zone + + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SlashAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + void Event_RechargeShields( float amount ); + + CLASS_STATES_PROTOTYPE( rvMonsterBossBuddy ); +}; + +#define BOSS_BUDDY_MAX_SHIELDS 8500 +//------------------------------------------------------------ +// rvMonsterBossBuddy::rvMonsterBossBuddy +//------------------------------------------------------------ +rvMonsterBossBuddy::rvMonsterBossBuddy( void ) +{ + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; + mShields = mMaxShields; + mLastDamageTime = 0; + mIsShielded = false; + mRequestedZoneMove = false; + mRequestedRecharge = false; +// mCanIdle = false; +// mChaseMode = true; +} + +void rvMonsterBossBuddy::InitSpawnArgsVariables ( void ) +{ + mShieldsLastFor = (int)(spawnArgs.GetFloat( "mShieldsLastFor", "6" ) * 1000.0f); + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Spawn +//------------------------------------------------------------ +void rvMonsterBossBuddy::Spawn( void ) +{ + mActionRocketAttack.Init( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + mActionLightningAttack.Init( spawnArgs,"action_lightningAttack", NULL, AIACTIONF_ATTACK ); + mActionDarkMatterAttack.Init( spawnArgs,"action_dmgAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeMoveAttack.Init( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionSlashMoveAttack.Init( spawnArgs, "action_slashMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + mShields = mMaxShields; + + const char *func; + if ( spawnArgs.GetString( "requestRecharge", "", &func ) ) + { + mRequestRecharge.Init( func ); + } + if ( spawnArgs.GetString( "requestZoneMove", "", &func ) ) + { + mRequestZoneMove.Init( func ); + } + + HideSurface( "models/monsters/bossbuddy/forcefield" ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Save +//------------------------------------------------------------ +void rvMonsterBossBuddy::Save( idSaveGame *savefile ) const +{ + savefile->WriteInt( mShots ); + savefile->WriteInt( mShields ); + savefile->WriteInt( mMaxShields ); + savefile->WriteInt( mLastDamageTime ); + savefile->WriteBool( mIsShielded ); + savefile->WriteBool( mRequestedZoneMove ); + savefile->WriteBool( mRequestedRecharge ); + + mActionRocketAttack.Save( savefile ); + mActionLightningAttack.Save( savefile ); + mActionDarkMatterAttack.Save( savefile ); + mActionMeleeMoveAttack.Save( savefile ); + mActionMeleeAttack.Save( savefile ); + mActionSlashMoveAttack.Save( savefile ); + + mRequestRecharge.Save( savefile ); + mRequestZoneMove.Save( savefile ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Restore +//------------------------------------------------------------ +void rvMonsterBossBuddy::Restore( idRestoreGame *savefile ) +{ + savefile->ReadInt( mShots ); + savefile->ReadInt( mShields ); + savefile->ReadInt( mMaxShields ); + savefile->ReadInt( mLastDamageTime ); + savefile->ReadBool( mIsShielded ); + savefile->ReadBool( mRequestedZoneMove ); + savefile->ReadBool( mRequestedRecharge ); + + mActionRocketAttack.Restore( savefile ); + mActionLightningAttack.Restore( savefile ); + mActionDarkMatterAttack.Restore( savefile ); + mActionMeleeMoveAttack.Restore( savefile ); + mActionMeleeAttack.Restore( savefile ); + mActionSlashMoveAttack.Restore( savefile ); + + mRequestRecharge.Restore( savefile ); + mRequestZoneMove.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +//------------------------------------------------------------ +// rvMonsterBerserker::GetDebugInfo +//------------------------------------------------------------ +void rvMonsterBossBuddy::GetDebugInfo( debugInfoProc_t proc, void* userData ) +{ + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_darkMatterAttack", aiActionStatusString[mActionDarkMatterAttack.status], userData ); + proc ( "idAI", "action_rocketAttack", aiActionStatusString[mActionRocketAttack.status], userData ); + proc ( "idAI", "action_meleeMoveAttack", aiActionStatusString[mActionMeleeMoveAttack.status], userData ); + proc ( "idAI", "action_lightningAttack", aiActionStatusString[mActionLightningAttack.status], userData ); +} + +//-------------------------------------------------------------- +// Custom Script Events +//-------------------------------------------------------------- +const idEventDef EV_RechargeShields( "rechargeShields", "f", 'f' ); + +CLASS_DECLARATION( idAI, rvMonsterBossBuddy ) + EVENT( EV_RechargeShields, rvMonsterBossBuddy::Event_RechargeShields ) +END_CLASS + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Event_RechargeShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::Event_RechargeShields( float amount ) +{ + mShields += (int)amount; + + if ( mShields >= mMaxShields ) + { + // charge is done + mShields = mMaxShields; + idThread::ReturnInt(0); + + // reset request states + mRequestedRecharge = false; + mRequestedZoneMove = false; + + // shield warning no longer neede for now + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("hideBossShieldWarn"); + } + else + { + // still charging + idThread::ReturnInt(1); + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::ReduceShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::ReduceShields( int amount ) +{ + mShields -= amount; + + // if no mShields left... or the last time we took damage was more than 8 seconds ago + if ( mShields <= 0 || (mLastDamageTime + 8000) < gameLocal.time ) + { + //....remove the shielding + AdjustShieldState( false ); + } + + if (mShields < 1000) + { + if (!mRequestedRecharge ) + { + // entering a dangerous state! Get to the recharge station, fast! + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("showBossShieldWarn"); + ExecScriptFunction( mRequestRecharge ); + mRequestedRecharge = true; + } + } + else if (mShields < 4000) + { + if ( !mRequestedZoneMove ) + { + // Getting low, so move him close to the next zone so he can be ready to recharge + ExecScriptFunction( mRequestZoneMove ); + mRequestedZoneMove = true; + } + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::AdjustShieldState +//------------------------------------------------------------ +void rvMonsterBossBuddy::AdjustShieldState( bool becomeShielded ) +{ + // only do the work for adjusting the state when it doesn't match our current state + if ( !mIsShielded && becomeShielded ) + { + // Activate Shields! + ShowSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound( "snd_enable_shields", SND_CHANNEL_ANY, 0, false, NULL ); + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "showBossShieldBar" ); + } + else if ( mIsShielded && !becomeShielded ) + { + // Deactivate Shields! + HideSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound ( "snd_disable_shields", SND_CHANNEL_ANY, 0, false, NULL ); +// gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "hideBossShieldBar" ); + } + mIsShielded = becomeShielded; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Think() +{ + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) + { + // run simple shielding logic when we have them active + if ( mIsShielded ) + { + ReduceShields( 1 ); + + // if they are on but we haven't taken damage in x seconds, turn them off to conserve on shields + if ( (mLastDamageTime + mShieldsLastFor) < gameLocal.time ) + { + AdjustShieldState( false ); + } + } + + // update shield bar + idUserInterface *hud = gameLocal.GetLocalPlayer()->hud; + if ( hud ) + { + float percent = ((float)mShields/mMaxShields); + + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } + } + + if ( move.obstacle.GetEntity() ) + { + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack ); + } + + idAI::Think(); +} +/* +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +void rvMonsterBossBuddy::PerformAction( const char* stateName, int blendFrames, bool noPain ) +{ + // Allow movement in actions + move.fl.allowAnimMove = true; + + if ( mChaseMode ) + { + return; + } + + // Start the action + SetAnimState( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( mCanIdle ) + { + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState( "Wait_ActionNoPain" ); + OnStartAction( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +bool rvMonsterBossBuddy::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) +{ + if ( mChaseMode ) + { + return false; + } + + return idAI::PerformAction( action, condition ,timer ); +} +*/ +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + // get damage amount so we can decay the shields and check for ignoreShields + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) + { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + // NOTE: there is a damage def for the electrocution that is marked 'ignoreShields'.. when present on the damage def, + // we don't run shielding logic + bool directDamage = damageDef->GetBool( "ignoreShields" ); + + int loc = location; + if ( directDamage ) + { + // Lame, I know, but hack the location + loc = INVALID_JOINT; + } + else if ( attacker == this ) + { + // can't damage self + return; + } + + float scale = 1; + + // Shields will activate for a set amount of time when damage is being taken + mLastDamageTime = gameLocal.time; + + // if shields are active, we should try to 'eat' them before directing damage to the BB + if ( mIsShielded && !directDamage ) + { + // BB is resistant to any kind of splash damage when the shields are up + if ( loc <= INVALID_JOINT ) + { + // damage must have been done by splash damage + return; + } + + int damage = damageDef->GetInt( "damage" ); + ReduceShields( damage * 8 ); + + // Shielding dramatically reduces actual damage done to BB + scale = 0.1f; + } + else if ( mShields > 0 ) // not currently shielded...does he have shields to use? + { + // Yep, so turn them on + AdjustShieldState( true ); + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale * scale, loc ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Pain +//------------------------------------------------------------ +bool rvMonsterBossBuddy::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) +{ + // immune to small damage. Is this safe to do? + if ( damage > 5 ) + { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( spawnArgs, "fx_shieldcrawl" ), this, SEC2MS(spawnArgs.GetFloat ( "shieldCrawlTime", ".2" )) ); + effect->Play( gameLocal.time, false ); + + return idAI::Pain( inflictor, attacker, damage, dir, location ); + } + return false; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CheckActions +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CheckActions( void ) +{ + // If not moving, try turning in place +/* if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnLeft90", 4, true ); + return true; + } + } +*/ + if ( PerformAction( &mActionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack )) + { + return true; + } + + if ( PerformAction( &mActionDarkMatterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionLightningAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack )) + { + return true; + } + + return idAI::CheckActions( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CanTurn +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CanTurn( void ) const +{ +/* if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +*/ + return idAI::CanTurn ( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::OnWakeUp +//------------------------------------------------------------ +void rvMonsterBossBuddy::OnWakeUp( void ) +{ + mActionDarkMatterAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + mActionRocketAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + idAI::OnWakeUp( ); +} + +//------------------------------------------------------------ +// States +//------------------------------------------------------------ + +CLASS_STATES_DECLARATION( rvMonsterBossBuddy ) + STATE( "Torso_RocketAttack", rvMonsterBossBuddy::State_Torso_RocketAttack ) + STATE( "Torso_SlashAttack", rvMonsterBossBuddy::State_Torso_SlashAttack ) + STATE( "Torso_TurnRight90", rvMonsterBossBuddy::State_Torso_TurnRight90 ) + STATE( "Torso_TurnLeft90", rvMonsterBossBuddy::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_RocketAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_RocketAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2start", parms.blendFrames ); + mShots = (gameLocal.random.RandomInt( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2loop2", 0 ); + return SRESULT_STAGE( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + if ( --mShots <= 0 || // exhausted mShots? .. or + (!IsEnemyVisible() && rvRandom::irand(0,10)>=8 ) || // ... player is no longer visible .. or + ( enemy.ent && DistanceTo(enemy.ent)<256 ) ) // ... player is so close, we prolly want to do a melee attack + { + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2end", 0 ); + return SRESULT_STAGE( STAGE_WAITEND ); + } + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_SlashAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_SlashAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT_FIRST_SWIPE, + STAGE_WAIT_FINISH + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FIRST_SWIPE ); + + case STAGE_WAIT_FIRST_SWIPE: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnRight90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnRight90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnLeft90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnLeft90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_BossMakron.cpp b/src/game/ai/Monster_BossMakron.cpp new file mode 100644 index 0000000..9b6c25d --- /dev/null +++ b/src/game/ai/Monster_BossMakron.cpp @@ -0,0 +1,2371 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterBossMakron : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBossMakron ); + + rvMonsterBossMakron ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + + bool CanTurn ( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BuildActionArray ( void ); + + //void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + +protected: + + bool CheckActions ( void ); + bool CheckTurnActions ( void ); + + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + //The Makron behaves differently, and we don't always need him sinking back into an idle after every attack + //This version is actually overridden + void PerformAction ( const char* stateName, int blendFrames, bool noPain ); + + //This just calls the parent version + bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + //This performs a patterned action + void PerformPatternedAction ( ); + + //For debug, may not need this. + void OnStopAction ( void ); + + //stops all effects + void StopAllEffects ( void ); + + //for the cannon fire + int shots; + + //determines if the Makron will enter idles between attacks + bool noIdle; + + //changes Makron from patterned, script controlled to normal AI + bool patternedMode; + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + float facingIdealYaw; + float facingTime; + float turnRate; + + //flag to indicate whether or not the Makron is in the corner of the map when he takes an action. + bool flagCornerState; + + //tallies up the number of consecutive actions the Makron has taken in the corner of the map. + //If the tally gets too high, teleport him to the map center. + int cornerCount; + + //in the state of teleporting + bool flagTeleporting; + + //for flying mode ---------------------------------------------------------------------------------------- + void BeginSeparation ( void ); + void CompleteSeparation ( void ); + + bool flagFlyingMode; + bool flagFakeDeath; + int flagUndying; + + rvClientEffectPtr effectHover; + jointHandle_t jointHoverEffect; + + //for the stomp attack ---------------------------------------------------------------------------------------- + float stompMaxRadius; + float stompWidth; + float stompSpeed; + float stompRadius; + + //for the lightning bolt sweep attacks ------------------------------------------------------------------- + void MaintainBoltSweep ( void ); + void InitBoltSweep ( idVec3 idealTarget ); + void LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ); + void StopAllBoltEffects ( void ); + + rvClientEffectPtr leftBoltEffect; + rvClientEffectPtr rightBoltEffect; + rvClientEffectPtr leftBoltImpact; + rvClientEffectPtr rightBoltImpact; + rvClientEffectPtr boltMuzzleFlash; + + idStr boltEffectName; + + idVec3 leftBoltVector; + idVec3 rightBoltVector; + idVec3 boltVectorMin; + idVec3 boltVectorMax; + idMat3 boltAimMatrix; + + bool flagSweepDone; + + //these are grabbed from the def file + float boltSweepTime; + float boltWaitTime; + + //used internally + float boltSweepStartTime; + float boltTime; + float boltNextStateTime; + + int stateBoltSweep; + + jointHandle_t jointLightningBolt; + + //end lightning bolt sweep attacks ------------------------------------------------------------------- + //changed by script event, allows for grenades to spawn baddies. + bool flagAllowSpawns; + + enum { MAKRON_ACTION_DMG, + MAKRON_ACTION_MELEE, + MAKRON_ACTION_CANNON, + MAKRON_ACTION_CANNON_SWEEP, + MAKRON_ACTION_GRENADE, + MAKRON_ACTION_LIGHTNING_1, + MAKRON_ACTION_LIGHTNING_2, + MAKRON_ACTION_STOMP, + MAKRON_ACTION_HEAL, + MAKRON_ACTION_CHARGE, + MAKRON_ACTION_KILLPLAYER, + MAKRON_ACTION_COUNT, }; + + rvAIAction actionDMGAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionCannonAttack; + rvAIAction actionCannonSweepAttack; + rvAIAction actionGrenadeAttack; + rvAIAction actionLightningPattern1Attack; + rvAIAction actionLightningPattern2Attack; + rvAIAction actionStompAttack; + rvAIAction actionHeal; + rvAIAction actionCharge; + rvAIAction actionKillPlayer; + + rvAIAction * actionArray[ MAKRON_ACTION_COUNT ]; + + int actionPatterned; + + //when the Makron is low on health, he'll use this. + rvScriptFuncUtility scriptRecharge; + rvScriptFuncUtility scriptTeleport; + + stateResult_t State_Torso_DMGAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_GrenadeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MeleeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonSweepAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning1Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning2Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_StompAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Recharge ( const stateParms_t& parms ); + stateResult_t State_Torso_Charge ( const stateParms_t& parms ); + stateResult_t State_Torso_KillPlayer ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + + //used when killed + stateResult_t State_Killed ( const stateParms_t& parms ); + + //walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + //flying turn anims + stateResult_t State_Torso_RotateToAngle ( const stateParms_t& parms ); + + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + + //like jesus, except with robot parts + stateResult_t State_Torso_FirstDeath ( const stateParms_t& parms ); + stateResult_t State_Torso_Resurrection ( const stateParms_t& parms ); + + //These two frames activate and deactivate the second lightning sweep + stateResult_t Frame_BeginLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_EndLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_StompAttack ( const stateParms_t& parms ); + stateResult_t Frame_Teleport ( const stateParms_t& parms ); + + + void Event_AllowMoreSpawns ( void ); + void Event_SetNextAction ( const char* actionString ); + void Event_EnablePatternMode ( void ); + void Event_DisablePatternMode ( void ); + void Event_StompAttack ( idVec3 &origin ); + void Event_Separate ( void ); + void Event_FlyingRotate ( idVec3 &vecOrg ); + void Event_ToggleCornerState ( float f ); + + + CLASS_STATES_PROTOTYPE ( rvMonsterBossMakron ); +}; + +const idEventDef EV_AllowMoreSpawns( "allowMoreSpawns" ); +const idEventDef EV_SetNextAction( "setNextAction", "s", 'f' ); +const idEventDef EV_EnablePatternMode( "enablePatternMode" ); +const idEventDef EV_DisablePatternMode( "disablePatternMode" ); +const idEventDef EV_StompAttack( "stompAttack", "v" ); +const idEventDef EV_Separate( "separate" ); +const idEventDef EV_FlyingRotate( "flyingRotate", "v"); +const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" ); +const idEventDef EV_ToggleCornerState( "toggleCornerState", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterBossMakron ) + EVENT( EV_AllowMoreSpawns, rvMonsterBossMakron::Event_AllowMoreSpawns ) + EVENT( EV_SetNextAction, rvMonsterBossMakron::Event_SetNextAction ) + EVENT( EV_EnablePatternMode, rvMonsterBossMakron::Event_EnablePatternMode ) + EVENT( EV_DisablePatternMode, rvMonsterBossMakron::Event_DisablePatternMode ) + EVENT( EV_StompAttack, rvMonsterBossMakron::Event_StompAttack ) + EVENT( EV_Separate, rvMonsterBossMakron::Event_Separate ) + EVENT( EV_FlyingRotate, rvMonsterBossMakron::Event_FlyingRotate ) + EVENT( AI_ScriptedFace, rvMonsterBossMakron::ScriptedFace ) + EVENT( EV_ToggleCornerState, rvMonsterBossMakron::Event_ToggleCornerState ) +END_CLASS + +/* +================ +rvMonsterBossMakron::rvMonsterBossMakron +================ +*/ +rvMonsterBossMakron::rvMonsterBossMakron ( void ) { + + //set up this action array + + + +} + +/* +================ +rvMonsterBossMakron::BuildActionArray ( void ) +================ +*/ +void rvMonsterBossMakron::BuildActionArray ( void ) { + + actionArray[ MAKRON_ACTION_DMG ] = &actionDMGAttack; + actionArray[ MAKRON_ACTION_MELEE ] = &actionMeleeAttack; + actionArray[ MAKRON_ACTION_CANNON ] = &actionCannonAttack; + actionArray[ MAKRON_ACTION_CANNON_SWEEP ] = &actionCannonSweepAttack; + actionArray[ MAKRON_ACTION_GRENADE ] = &actionGrenadeAttack; + actionArray[ MAKRON_ACTION_LIGHTNING_1 ] = &actionLightningPattern1Attack; + actionArray[ MAKRON_ACTION_LIGHTNING_2 ] = &actionLightningPattern2Attack; + actionArray[ MAKRON_ACTION_STOMP ] = &actionStompAttack; + actionArray[ MAKRON_ACTION_HEAL ] = &actionHeal; + actionArray[ MAKRON_ACTION_CHARGE ] = &actionCharge; + actionArray[ MAKRON_ACTION_KILLPLAYER ] = &actionKillPlayer; + +} +/* +================ +rvMonsterBossMakron::Save +================ +*/ +void rvMonsterBossMakron::Save ( idSaveGame *savefile ) const { + + savefile->WriteInt( shots); + + savefile->WriteFloat( facingIdealYaw); + savefile->WriteFloat( facingTime); + + savefile->WriteBool( noIdle); + + savefile->WriteBool( patternedMode); + + savefile->WriteBool( flagFlyingMode); + savefile->WriteBool( flagFakeDeath); + savefile->WriteInt( flagUndying ); + + effectHover.Save( savefile ); + savefile->WriteJoint( jointHoverEffect ); // cnicholson: added unsaved var + + savefile->WriteFloat( stompRadius); + + leftBoltEffect.Save( savefile ); + rightBoltEffect.Save( savefile ); + leftBoltImpact.Save( savefile ); + rightBoltImpact.Save( savefile ); + boltMuzzleFlash.Save( savefile ); + + savefile->WriteString( boltEffectName); + + savefile->WriteVec3( leftBoltVector); + savefile->WriteVec3( rightBoltVector); + savefile->WriteVec3( boltVectorMin); + savefile->WriteVec3( boltVectorMax); + savefile->WriteMat3( boltAimMatrix); + + savefile->WriteBool( flagSweepDone); + + savefile->WriteFloat( boltSweepTime); + + savefile->WriteFloat( boltSweepStartTime); + savefile->WriteFloat( boltTime); + savefile->WriteFloat( boltNextStateTime); + + savefile->WriteInt( stateBoltSweep); + + savefile->WriteBool( flagAllowSpawns); + + savefile->WriteBool( flagCornerState ); + savefile->WriteInt( cornerCount ); + savefile->WriteBool( flagTeleporting ); + + + actionDMGAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionCannonAttack.Save( savefile ); + actionCannonSweepAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + actionLightningPattern1Attack.Save( savefile ); + actionLightningPattern2Attack.Save( savefile ); + actionStompAttack.Save( savefile ); + actionHeal.Save( savefile ); + actionCharge.Save( savefile ); + actionKillPlayer.Save( savefile ); + + savefile->WriteInt( actionPatterned); + + scriptRecharge.Save( savefile ); + scriptTeleport.Save( savefile ); + // cnicholson: No need to save actionArry, its rebuilt during restore +} + +/* +================ +rvMonsterBossMakron::Restore +================ +*/ +void rvMonsterBossMakron::Restore ( idRestoreGame *savefile ) { + + savefile->ReadInt( shots); + + savefile->ReadFloat( facingIdealYaw); + savefile->ReadFloat( facingTime); + + savefile->ReadBool( noIdle); + + savefile->ReadBool( patternedMode); + + savefile->ReadBool( flagFlyingMode); + savefile->ReadBool( flagFakeDeath); + savefile->ReadInt( flagUndying ); + + effectHover.Restore( savefile ); + savefile->ReadJoint( jointHoverEffect ); // cnicholson: added unrestoed var + + savefile->ReadFloat( stompRadius); + + leftBoltEffect.Restore( savefile ); + rightBoltEffect.Restore( savefile ); + leftBoltImpact.Restore( savefile ); + rightBoltImpact.Restore( savefile ); + boltMuzzleFlash.Restore( savefile ); + + savefile->ReadString( boltEffectName); + + savefile->ReadVec3( leftBoltVector); + savefile->ReadVec3( rightBoltVector); + savefile->ReadVec3( boltVectorMin); + savefile->ReadVec3( boltVectorMax); + savefile->ReadMat3( boltAimMatrix); + + savefile->ReadBool( flagSweepDone); + + savefile->ReadFloat( boltSweepTime); + + savefile->ReadFloat( boltSweepStartTime); + savefile->ReadFloat( boltTime); + savefile->ReadFloat( boltNextStateTime); + + savefile->ReadInt( stateBoltSweep); + + savefile->ReadBool( flagAllowSpawns); + + savefile->ReadBool( flagCornerState ); + savefile->ReadInt( cornerCount ); + savefile->ReadBool( flagTeleporting ); + + + actionDMGAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionCannonAttack.Restore( savefile ); + actionCannonSweepAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + actionLightningPattern1Attack.Restore( savefile ); + actionLightningPattern2Attack.Restore( savefile ); + actionStompAttack.Restore( savefile ); + actionHeal.Restore( savefile ); + actionCharge.Restore( savefile ); + actionKillPlayer.Restore( savefile ); + + + savefile->ReadInt( actionPatterned); + + scriptRecharge.Restore( savefile ); + scriptTeleport.Restore( savefile ); + //reload the action array, this is done in the init section but if we don't do it here our array is bunk. + BuildActionArray(); + InitSpawnArgsVariables(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + + +/* +================ +rvMonsterBossMakron::StopAllEffects +================ +*/ +void rvMonsterBossMakron::StopAllEffects ( void ) { + + StopAllBoltEffects(); + + //stop the over effect + if( effectHover ) { + effectHover.GetEntity()->Stop(); + effectHover = 0; + } + +} + +/* +================ +rvMonsterBossMakron::StopAllBoltEffects +================ +*/ +void rvMonsterBossMakron::StopAllBoltEffects ( void ) { + + if( leftBoltEffect ) { + leftBoltEffect.GetEntity()->Stop(); + leftBoltEffect = 0; + } + + if( rightBoltEffect ) { + rightBoltEffect.GetEntity()->Stop(); + rightBoltEffect = 0; + } + + if( leftBoltImpact ) { + leftBoltImpact.GetEntity()->Stop(); + leftBoltImpact = 0; + } + + if( rightBoltImpact ) { + rightBoltImpact.GetEntity()->Stop(); + rightBoltImpact = 0; + } + + if( boltMuzzleFlash ) { + boltMuzzleFlash .GetEntity()->Stop(); + boltMuzzleFlash = 0; + } + + +} + +void rvMonsterBossMakron::InitSpawnArgsVariables ( void ) { + //slick! + jointLightningBolt = animator.GetJointHandle ( spawnArgs.GetString ( "joint_lightningBolt", "claw_muzzle" ) ); + + stompMaxRadius = spawnArgs.GetFloat( "stomp_max_range", "1600"); + stompWidth = spawnArgs.GetFloat( "stomp_width", "32"); + stompSpeed = spawnArgs.GetFloat( "stomp_speed", "32"); + + boltWaitTime = spawnArgs.GetFloat( "lightingsweep_wait_time", "1"); + + turnRate = spawnArgs.GetFloat( "fly_turnRate", "180"); +} + +/* +================ +rvMonsterBossMakron::Spawn +================ +*/ +void rvMonsterBossMakron::Spawn ( void ) { + + if( spawnArgs.GetBool("passive")) { + flagFakeDeath = true; + } else { + flagFakeDeath = false; + } + + if( spawnArgs.GetBool("furniture") ) { + fl.takedamage = false; + } + + if( spawnArgs.GetBool("junior") ) { + flagUndying = 1; + } else { + flagUndying = 0; + } + + flagAllowSpawns = false; + flagFlyingMode = false; + patternedMode = false; + noIdle = false; + + flagCornerState = false; + cornerCount = 0; + flagTeleporting = false; + + //start clean + actionPatterned = -1; + + boltTime = 0; + boltSweepTime = spawnArgs.GetFloat( "lightningsweep_sweep_time", "3"); + + InitSpawnArgsVariables(); + + actionDMGAttack.Init ( spawnArgs, "action_DMGAttack", "Torso_DMGAttack", AIACTIONF_ATTACK ); + actionCannonAttack.Init ( spawnArgs, "action_cannonAttack", "Torso_CannonAttack", AIACTIONF_ATTACK ); + actionCannonSweepAttack.Init ( spawnArgs, "action_cannonsweepAttack", "Torso_CannonSweepAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", "Torso_GrenadeAttack", AIACTIONF_ATTACK ); + actionLightningPattern1Attack.Init( spawnArgs, "action_lightningPattern1Attack", "Torso_Lightning1Attack", AIACTIONF_ATTACK ); + actionLightningPattern2Attack.Init( spawnArgs, "action_lightningPattern2Attack", "Torso_Lightning2Attack", AIACTIONF_ATTACK ); + actionStompAttack.Init ( spawnArgs, "action_stompAttack", "Torso_StompAttack", AIACTIONF_ATTACK ); + actionHeal.Init( spawnArgs, "action_heal", "Torso_Recharge", AIACTIONF_ATTACK ); + actionCharge.Init( spawnArgs, "action_charge", "Torso_Charge", AIACTIONF_ATTACK ); + actionKillPlayer.Init( spawnArgs, "action_lightningPattern3Attack", "Torso_KillPlayer", AIACTIONF_ATTACK ); + + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", "Torso_MeleeAttack", AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_recharge", "", &func ) ) + { + scriptRecharge.Init( func ); + } + if ( spawnArgs.GetString( "script_teleport", "", &func ) ) + { + scriptTeleport.Init( func ); + } + + //build the action array + BuildActionArray(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + +/* +================ +rvMonsterBossMakron::CheckTurnActions +================ +*/ +bool rvMonsterBossMakron::CheckTurnActions ( void ) { + + if ( !flagFakeDeath && !move.fl.moving && !move.anim_turn_angles ) { //&& gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + ////gameLocal.Printf(" turn yaw == %f \n", turnYaw); + } + return false; +} +/* +================ +rvMonsterBossMakron::CheckActions +================ +*/ +bool rvMonsterBossMakron::CheckActions ( void ) { + + if( spawnArgs.GetFloat("furniture", "0")) { + return true; + } + + //if in the middle of teleporting, do nothing + if( flagTeleporting ) { + return true; + } + + //gameLocal.Printf("Begin CheckActions \n"); + + if( CheckTurnActions() ) { + //gameLocal.Printf("---CheckActions: TurnActions was true \n"); + return true; + } + + + //If we need more baddies, do it right now regardless of range or player FOV + if ( flagAllowSpawns ) { + PerformAction ( actionGrenadeAttack.state,actionGrenadeAttack.blendFrames, actionGrenadeAttack.fl.noPain ); + //gameLocal.Printf("---CheckActions: Spawned in more fellas \n"); + return true; + } + + //melee attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction ( &actionStompAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + //gameLocal.Printf("---CheckActions: Melee attacks \n"); + return true; + } + + //cannon sweep is good to check if we're up close. + //if ( PerformAction ( &actionCannonSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + // return true; + //} + + //If we're in total patterned mode, then don't relay on timers or ranges to perform actions. + if ( patternedMode && !flagFakeDeath ) { + //gameLocal.Printf("**CheckActions: PerformPatternedAction called \n"); + PerformPatternedAction ( ); + return true; + } + //gameLocal.Printf("---CheckActions: FlagFakeDeath: %d \n", flagFakeDeath ); + //gameLocal.Printf("---CheckActions: PatternedMode: %d \n", patternedMode ); + + //ranged attacks + if ( PerformAction ( &actionDMGAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningPattern1Attack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionCannonAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + //gameLocal.Printf("---CheckActions: Ranged attack called \n"); + return true; + } + //gameLocal.Printf("---CheckActions: Defaulting to AI CheckActions \n"); + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterMakron::PerformPatternedAction +================ +*/ +void rvMonsterBossMakron::PerformPatternedAction( ) { + + //gameLocal.Printf("PerformPatternedAction::Begin--\n"); + + //if we don't have an action to perform, don't. + if( actionPatterned == -1) { + //gameLocal.Printf("actionPatterned = -1\n"); + //gameLocal.Printf("PerformPatternedAction::End--\n"); + return; + } + + //if we're in a corner, right now, then add to the corner tally. + if( flagCornerState ) { + cornerCount++; + } else { + cornerCount = 0; + } + + //if this is our third consecutive action in the corner, maybe we should teleport? + if( cornerCount == 3 ) { + gameLocal.Warning("Makron is stuck in a corner!"); + cornerCount = 0; + SetState( "Torso_Teleport" ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + return; + + } + + rvAIAction* action; + rvAIActionTimer* timer; + + action = actionArray[ actionPatterned]; + timer = &actionTimerRangedAttack; + + //if the attack is variable length, then do all this jive... + float scale; + scale = 1.0f; + + //gameLocal.Printf("performing the action\n"); + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + //Clear this out so the next attack can be queued up. + actionPatterned = -1; + + // Restart the action timer using the length of the animation being played + // Even though we don't use the action timer for this instance, we need to keep it updated and fresh. + action->timer.Reset ( actionTime, action->diversity, scale ); + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + +} + + +/* +================ +rvMonsterMakron::CanTurn +================ +*/ +bool rvMonsterBossMakron::CanTurn ( void ) const { + if( !flagFlyingMode ) { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; + } else { + return idAI::CanTurn ( ); + } +} + + +/* +================ +rvMonsterMakron::InitBoltSweep +================ +*/ +void rvMonsterBossMakron::InitBoltSweep ( idVec3 idealTarget ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + idVec3 fwd; + idVec3 right; + idVec3 worldup(0,0,1); + + idVec3 targetPoint; + + //init all the lightning bolt stuff + boltTime = 0; + flagSweepDone = false; + stateBoltSweep = 0; + + //get the vector from makron's gun to the target point. + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + targetPoint = idealTarget; //enemy.lastVisibleChestPosition; + + fwd = targetPoint - origin; + + fwd.Normalize(); + right = fwd.Cross( worldup); + + boltAimMatrix[0] = fwd; + boltAimMatrix[1] = right; + boltAimMatrix[2] = worldup; + + targetPoint = origin + (fwd * 2400); + + //left + targetPoint = targetPoint - (right * 1800); + boltVectorMin = targetPoint - origin; + boltVectorMin.Normalize(); + leftBoltVector = boltVectorMin; + + //right + targetPoint = targetPoint + (right * 1800 * 2); + boltVectorMax = targetPoint - origin; + boltVectorMax.Normalize(); + rightBoltVector = boltVectorMax; + +} + +/* +================ +rvMonsterMakron::MaintainBoltSweep +================ +*/ +void rvMonsterBossMakron::MaintainBoltSweep ( void ) { + enum { + STATE_START, + STATE_WAIT1, + STATE_SWEEP1, + STATE_WAIT2, + STATE_SWEEP2, + STATE_END, + }; + + //advance the state when time is up. + + switch( stateBoltSweep ) { + + //init vars + case STATE_START: + flagSweepDone = false; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + StopAllBoltEffects(); + stateBoltSweep = STATE_WAIT1; + return; + + //blast at the waiting points, vectors do not move + case STATE_WAIT1: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + + case STATE_SWEEP1: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + } + + return; + case STATE_WAIT2: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + case STATE_SWEEP2: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + //min and max are reversed here. + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = 0; + StopAllBoltEffects(); + } + return; + case STATE_END: + flagSweepDone = 1; + return; + } + +} +/* +================ +rvMonsterBossMakron::LightningSweep +================ +*/ +void rvMonsterBossMakron::LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + + //trace out from origin along attackVector + attackVector.Normalize(); + gameLocal.TracePoint( this, tr, origin, origin + (attackVector * 25600), MASK_SHOT_RENDERMODEL, this); + //gameRenderWorld->DebugLine( colorRed, origin, tr.c.point, 100, true); + + if ( !boltEffect ) { + boltEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_fly" ), origin, attackVector.ToMat3(), true, tr.c.point); + } else { + boltEffect->SetOrigin ( origin ); + boltEffect->SetAxis ( attackVector.ToMat3() ); + boltEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_impact" ), tr.c.point, tr.c.normal.ToMat3(), true, tr.c.point); + } else { + impactEffect->SetOrigin ( tr.c.point ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + impactEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !boltMuzzleFlash ) { + boltMuzzleFlash = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_muzzle" ), origin, attackVector.ToMat3(), true, origin); + } else { + boltMuzzleFlash->SetOrigin ( origin ); + boltMuzzleFlash->SetAxis ( attackVector.ToMat3() ); + boltMuzzleFlash->SetEndOrigin ( origin ); + } + + //hurt anything in the way + idEntity* ent = gameLocal.entities[tr.c.entityNum]; + + if( ent) { + ent->Damage ( this, this, attackVector, spawnArgs.GetString ( "def_makron_sweep_damage" ), 1.0f, 0 ); + } + +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( !noIdle ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState ( "Wait_ActionNoPain" ); + + OnStartAction ( ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +bool rvMonsterBossMakron::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + return idAI::PerformAction( action, condition ,timer ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::OnStopAction( void ) { + ////gameLocal.Printf("\n\nAction stopped ----------- \n"); +} + +/* +================ +rvMonsterBossMakron::Damage +================ +*/ +void rvMonsterBossMakron::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + //Deal damage here, + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + //if the Makron has 0 or less health, make sure he's killed + ///if ( health <= 0 && !flagFlyingMode && !flagFakeDeath ) { + // Killed( this, this, 1, dir, location); + //} +} +/* +================ +rvMonsterBossMakron::Killed +================ +*/ +void rvMonsterBossMakron::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + //if this is the undying Makron Jr, don't worry about death. Stop what we're doing, + //Call the script function and let it ride. + if( flagUndying == 1) { + flagUndying = 2; + ExecScriptFunction( funcs.death ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + PerformAction ( actionKillPlayer.state, actionKillPlayer.blendFrames, actionKillPlayer.fl.noPain ); + return; + } + + if( flagUndying == 2) { + return; + } + + //stop all effects. + StopAllEffects(); + + //if the makron isn't in flying mode, it is now. + if( flagFlyingMode ) { + //play the falling animation, then die. + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + //SetState( "Torso_Death_Fall" ); + idAI::Killed( inflictor, attacker, damage, dir, location); + return; + } + + //the Makron is in undying mode until he finishes getting up. + //gameLocal.Warning("First form defeated!"); + + health = 1; + aifl.undying = true; + fl.takedamage = false; + SetMoveType ( MOVETYPE_DEAD ); + StopMove( MOVE_STATUS_DONE ); + //gameLocal.Printf("************\n************\n************\n************\n---flagFakeDeath set to TRUE! ************\n************\n************\n************\n" ); + flagFakeDeath = true; + + + + //play the death anim + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + //Call this anyway-- hopefully the script is prepared to handle multiple Makron deaths... + ExecScriptFunction( funcs.death ); + + //make sure he goes through deadness, but we need to post FinishAction afterwards +// SetState( "Torso_FirstDeath" ); +// aifl.action = true; + SetState( "Torso_FirstDeath" ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FirstDeath", 30, 0, SFLAG_ONCLEAR ); + +} + + +/* +================ +rvMonsterBossMakron::BeginSeparation( void ) +================ +*/ +void rvMonsterBossMakron::BeginSeparation( void ) { + + //spawn in a new Makron leg, + idDict args; + idEntity* newLegs; + + //We may need to do this at a later point + //SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_makron_legs" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &newLegs ); + + //store the name of the entity in the Makron's keys so we can burn it out as well. + spawnArgs.Set( "legs_name", newLegs->GetName() ); + + +} + +/* +================ +rvMonsterBossMakron::CompleteSeparation( void ) +================ +*/ +void rvMonsterBossMakron::CompleteSeparation( void ) { + + //flying mode now + flagFlyingMode = true; + SetMoveType ( MOVETYPE_FLY ); + move.fl.noGravity = true; + animPrefix = "fly"; + + //is there a cooler way to do this? Heal over time? + //health = spawnArgs.GetFloat("health_flying", "5000" ); + + ExecScriptFunction( funcs.init); + + //makron is once again pwnable. + aifl.undying = false; + fl.takedamage = true; + flagFakeDeath = false; + patternedMode = true; + + //make sure he cleans up. +// PostAnimState ( ANIMCHANNEL_ALL, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_LEGS, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + //gameLocal.Printf("*****\n*****\n*****\nPast 'PostAnimState' flagFakeDeath is: %d \n*****\n*****\n*****\n", flagFakeDeath); + + +// These four lines work! + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + + +} + +/* +================ +rvMonsterBossMakron::ScriptedFace( idEntity* faceEnt, bool endWithIdle ) +================ +*/ +/* +void rvMonsterBossMakron::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + + //set the ideal yaw, the change to the facing state. + FaceEntity( faceEnt ); + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + facingIdealYaw = move.ideal_yaw; + + //become scripted + aifl.scripted = true; + + //This will get us close to facing the entity correctly. + SetState( "State_ScriptedFace", SFLAG_ONCLEAR); + +} +*/ + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterBossMakron::Event_AllowMoreSpawns +================ +*/ +// this will allow Makron to spawn more baddies. +void rvMonsterBossMakron::Event_AllowMoreSpawns( void ) { + flagAllowSpawns = true; +} + +/* +================ +rvMonsterBossMakron::Event_EnablePatternMode +================ +*/ +// When set, the Makron will now only fight via scripted patterns +void rvMonsterBossMakron::Event_EnablePatternMode( void ) { + patternedMode = true; + noIdle = true; + flagTeleporting = false; +} + +/* +================ +rvMonsterBossMakron::Event_DisablePatternMode +================ +*/ +void rvMonsterBossMakron::Event_DisablePatternMode( void ) { + patternedMode = false; + noIdle = false; +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_Separate( void ) { + + //all we need to do here is post the separation state, right? + BeginSeparation(); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Resurrection", 0, 5000, SFLAG_ONCLEAR ); + +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_FlyingRotate( idVec3& vecOrg ) { + + //set move.ideal_yaw + TurnToward( vecOrg ); + + //copy it over + facingIdealYaw = move.ideal_yaw; + aifl.scripted = true; + + //set the state + SetState( "Torso_RotateToAngle"); + +} + +/* +================ +rvMonsterBossMakron::Event_SetNextAction +================ +*/ +void rvMonsterBossMakron::Event_SetNextAction( const char * actionString) { + + //if the next action is occupied, return false + if( actionPatterned != -1) { + idThread::ReturnFloat(0); + return; + } + + //otherwise, select the action from a list + if( !idStr::Cmp( actionString, "actionCannon")) { + actionPatterned = MAKRON_ACTION_CANNON; + } + else if( !idStr::Cmp( actionString, "actionCannonSweep")) { + actionPatterned = MAKRON_ACTION_CANNON_SWEEP; + } + else if( !idStr::Cmp( actionString, "actionDMG")) { + actionPatterned = MAKRON_ACTION_DMG; + } + else if( !idStr::Cmp( actionString, "actionDMGrenades")) { + actionPatterned = MAKRON_ACTION_GRENADE; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep1")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_1; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep2")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_2; + } + else if( !idStr::Cmp( actionString, "actionStomp")) { + actionPatterned = MAKRON_ACTION_STOMP; + } + else if( !idStr::Cmp( actionString, "actionHeal")) { + actionPatterned = MAKRON_ACTION_HEAL; + } + else if( !idStr::Cmp( actionString, "actionCharge")) { + actionPatterned = MAKRON_ACTION_CHARGE; + } + else if( !idStr::Cmp( actionString, "actionKillPlayer")) { + actionPatterned = MAKRON_ACTION_KILLPLAYER; + } + else if( !idStr::Cmp( actionString, "actionEndPattern")) { + actionPatterned = -1; + } + else { + gameLocal.Error(" Bad action %s passed into MonsterMakron::SetNextAction", actionString); + idThread::ReturnFloat(0); + return; + } + + idThread::ReturnFloat(1); + return; + + + +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBossMakron ) + STATE ( "Torso_DMGAttack", rvMonsterBossMakron::State_Torso_DMGAttack ) + STATE ( "Torso_MeleeAttack", rvMonsterBossMakron::State_Torso_MeleeAttack ) + STATE ( "Torso_CannonAttack", rvMonsterBossMakron::State_Torso_CannonAttack ) + STATE ( "Torso_GrenadeAttack", rvMonsterBossMakron::State_Torso_GrenadeAttack ) + STATE ( "Torso_CannonSweepAttack", rvMonsterBossMakron::State_Torso_CannonSweepAttack ) + STATE ( "Torso_Lightning1Attack", rvMonsterBossMakron::State_Torso_Lightning1Attack ) + STATE ( "Torso_Lightning2Attack", rvMonsterBossMakron::State_Torso_Lightning2Attack ) + STATE ( "Torso_StompAttack", rvMonsterBossMakron::State_Torso_StompAttack ) + STATE ( "Torso_Recharge", rvMonsterBossMakron::State_Torso_Recharge ) + STATE ( "Torso_Charge", rvMonsterBossMakron::State_Torso_Charge ) + STATE ( "Torso_KillPlayer", rvMonsterBossMakron::State_Torso_KillPlayer ) + STATE ( "Torso_FirstDeath", rvMonsterBossMakron::State_Torso_FirstDeath ) + STATE ( "Torso_Resurrection", rvMonsterBossMakron::State_Torso_Resurrection ) + STATE ( "State_Killed", rvMonsterBossMakron::State_Killed ) + STATE ( "Torso_Teleport", rvMonsterBossMakron::State_Torso_Teleport ) + + STATE ( "Torso_TurnRight90", rvMonsterBossMakron::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterBossMakron::State_Torso_TurnLeft90 ) + + STATE ( "Torso_RotateToAngle", rvMonsterBossMakron::State_Torso_RotateToAngle ) + + STATE ( "Frame_BeginLightningSweep2", rvMonsterBossMakron::Frame_BeginLightningSweep2 ) + STATE ( "Frame_EndLightningSweep2", rvMonsterBossMakron::Frame_EndLightningSweep2 ) + STATE ( "Frame_StompAttack", rvMonsterBossMakron::Frame_StompAttack ) + STATE ( "Frame_Teleport", rvMonsterBossMakron::Frame_Teleport ) + + STATE ( "State_ScriptedFace", rvMonsterBossMakron::State_ScriptedFace ) + + +END_CLASS_STATES + + +/* +================ +rvBossMakron::State_Torso_DMGAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_DMGAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron DMG Go!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_dmg", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_Torso_Charge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Charge ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "run", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_ScriptedFace +================ +*/ + +stateResult_t rvMonsterBossMakron::State_ScriptedFace ( const stateParms_t& parms ) { + + //note this uses the Makron's version of FacingIdeal, + if( !flagFlyingMode) { + if ( !aifl.scripted || (!CheckTurnActions( ) && (!move.anim_turn_angles))) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; + + } else { + + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +} + +/* + enum { + STAGE_INIT, + STAGE_WAIT + }; + + idStr turnAnim; + float turnYaw; + + switch( parms.stage ) { + case STAGE_INIT: + + DisableAnimState ( ANIMCHANNEL_LEGS ); + //which way do we need to face? + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + turnAnim = "turn_right_90"; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + turnAnim = "turn_left_90"; + } else { + //guess we don't need to turn? We're done. + aifl.scripted = false; + return SRESULT_DONE; + } + PlayAnim ( ANIMCHANNEL_TORSO, turnAnim, 4 ); + AnimTurn ( 90.0f, true ); + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + //back to the start to make sure we're facing the right way. + return SRESULT_STAGE ( STAGE_INIT ); + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; + +} +*/ + + +/* +================ +rvBossMakron::State_Torso_FirstDeath +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_FirstDeath ( const stateParms_t& parms ) { + + + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; +} + +/* +================ +rvBossMakron::State_Torso_Resurrection +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Resurrection ( const stateParms_t& parms ) { +/* enum { + STAGE_INIT, + STAGE_WAIT_FIRST, + STAGE_RISE, + STAGE_WAIT_SECOND + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT_FIRST); + + case STAGE_WAIT_FIRST: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_RISE); + } + return SRESULT_WAIT; + + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT_SECOND); + + case STAGE_WAIT_SECOND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +*/ + enum { + STAGE_RISE, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvBossMakron::State_Lightning2Attack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Lightning2Attack ( const stateParms_t& parms ) { + + idVec3 boltVector; + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + + //prep up for the bolt + //gameLocal.Warning("Prepping sweep 2"); + //init these values + StopAllBoltEffects(); + stateBoltSweep = 0; + flagSweepDone = false; + //set up bolt targeting at a little above the players chest-- make him duck this one. + boltVector = enemy.lastVisibleEyePosition; + boltVector.z += 8; + InitBoltSweep( boltVector ); + + //play the anim + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "claw_sweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + if( stateBoltSweep == 1) { + //fire the bolt out from the claw based on where the claw is pointing, sort of. + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact ); + } + else if( stateBoltSweep == 2) { + //gameLocal.Warning("Sweep 2 done."); + StopAllBoltEffects(); + stateBoltSweep = 0; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterBossMakron::State_Torso_StompAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_StompAttack( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //can't do the stomp attack while flying-- ain't got no legs!! + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shockwave_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvMonsterBossMakron::State_Torso_Teleport +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Teleport( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //No teleporting in flying mode. + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + //can't take damage in teleport anim, bad things happen! + aifl.undying = true; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + + //restore former state, which is killable unless some other effect renders Makron unkillable. + if( !flagUndying ) { + aifl.undying = false; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvBossMakron::State_Torso_GrenadeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_GrenadeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Grenades!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + //only allow spawns if the script tells us we can + if( !flagAllowSpawns) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_dm", parms.blendFrames ); + } + else { + flagAllowSpawns = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_spawn", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvBossMakron::State_Torso_Recharge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Recharge ( const stateParms_t& parms ) { + + ExecScriptFunction( scriptRecharge ); + return SRESULT_DONE; +} + +/* +================ +rvBossMakron::State_Torso_MeleeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_MeleeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //swing! + //gameLocal.Warning("Makron Melee Attack"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "melee_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + + +/* +================ +rvMonsterBossMakron::State_Torso_RotateToAngle +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_RotateToAngle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_LOOP, + }; + + float facingTimeDelta; + float turnYaw; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTime = MS2SEC( gameLocal.time); + aifl.scripted = true; + return SRESULT_STAGE ( STAGE_WAIT_LOOP ); + } + aifl.scripted = false; + return SRESULT_DONE; + case STAGE_WAIT_LOOP: + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTimeDelta = MS2SEC( gameLocal.time) - facingTime; + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + ang.yaw += ( turnRate * facingTimeDelta ); + SetAngles( ang); + move.current_yaw = ang.yaw; + return SRESULT_STAGE( STAGE_WAIT_LOOP); + } + aifl.scripted = false; + return SRESULT_DONE; + } + + return SRESULT_ERROR; +} +/* +================ +rvMonsterBossMakron::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_CannonAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + //once an anim starts with the legs disabled, the rest of the anims should match that. + static bool noLegs; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( !flagFlyingMode || ( flagFlyingMode && !move.fl.moving )) { + noLegs = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + } else { + noLegs = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_start", parms.blendFrames ); + + } + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + //if we're flying, and moving, fire fast! + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire_fast", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_fire_fast", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_end", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_Lightning1Attack +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_Lightning1Attack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_INIT_BOLT, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + idVec3 origin; + idVec3 targetPoint; + idMat3 axis; + trace_t tr; + + switch ( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning( "Lightningbolt!"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_INIT_BOLT ); + } + return SRESULT_WAIT; + + case STAGE_INIT_BOLT: + //aim a little below the player's chest + targetPoint = enemy.lastVisibleChestPosition; + targetPoint.z -= 24; + InitBoltSweep( targetPoint ); + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //sweep the blasts back and forth + MaintainBoltSweep(); + + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + if ( flagSweepDone ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_KillPlayer +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_KillPlayer ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = 8; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + shots--; + if ( shots < 1) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + + +/* +================ +rvBossMakron::State_Torso_CannonSweepAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonSweepAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron CannonSweep Go!"); + //sweep across with the cannon + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_cannonsweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + //if the flag is up, fire. + + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterBossMakron::State_Killed +================ +*/ +stateResult_t rvMonsterBossMakron::State_Killed ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + disablePain = true; + // At this point the Makron is killed! + // Make sure all animation stops + //StopAnimState ( ANIMCHANNEL_TORSO ); + //StopAnimState ( ANIMCHANNEL_LEGS ); + //if ( head ) { + // StopAnimState ( ANIMCHANNEL_HEAD ); + //} + + // Make sure all animations stop + //animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_BeginLightningSweep2 ( const stateParms_t& parms ) { + + //begin the sweep with this flag + stateBoltSweep = 1; + boltSweepStartTime = gameLocal.time; + boltSweepTime = 1; + return SRESULT_OK; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_EndLightningSweep2 ( const stateParms_t& parms ) { + + //end the sweep with this flag + stateBoltSweep = 2; + return SRESULT_OK; + +} + +/* +================ +rvMonsterBossMakron::Frame_Teleport +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_Teleport ( const stateParms_t& parms ) { + + //hide + Hide(); + + //turn on the do-nothing teleport flag + flagTeleporting = true; + + //call some script. + ExecScriptFunction( scriptTeleport ); + return SRESULT_DONE; + +} + + +/* +================ +rvMonsterBossMakron::Frame_StompAttack +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_StompAttack ( const stateParms_t& parms ) { + + idVec3 origin; + idVec3 worldUp(0, 0, 1); + + // Eminate from Makron origin + origin = this->GetPhysics()->GetOrigin(); + + //start radius at 256; + stompRadius = spawnArgs.GetFloat("stomp_start_size", "64"); + + //stomp + gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_stomp_wave" ), origin, worldUp.ToMat3(), false, origin); + + //bamf! + PostEventMS( &EV_StompAttack, 0, origin); + + return SRESULT_OK; + +} +/* +================ +rvMonsterBossMakron::Event_ToggleCornerState +================ +*/ +void rvMonsterBossMakron::Event_ToggleCornerState ( float f ) { + if( f == 1.0f) { + flagCornerState = true; + } else { + flagCornerState = false; + } +} + +/* +================ +rvMonsterBossMakron::Event_StompAttack +================ +*/ + +void rvMonsterBossMakron::Event_StompAttack (idVec3& origin) { + + idVec3 targetOrigin; + idVec3 worldUp; + idEntity* entities[ 1024 ]; + int count; + int i; + float stompZ; + idVec3 dir; + modelTrace_t result; + + + stompZ = origin.z; + + worldUp.x = 0; + worldUp.y = 0; + worldUp.z = 1; + + //if the radius is too big, stop. + if ( stompRadius > stompMaxRadius ) { + return; + } + + //get all enemies within radius. If they are: + // within radius, + // more than (radius - stompWidth) units away, + // Z valued within 16 of the the stomp Z + //they take stomp damage. + count = gameLocal.EntitiesWithinRadius ( origin, stompRadius, entities, 1024 ); + + //gameRenderWorld->DebugCircle( colorRed,origin,worldUp,stompRadius,24,20,false); + //gameRenderWorld->DebugCircle( colorBlue,origin,worldUp,stompRadius - stompWidth,24,20,false); + + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + + //don't stomp ourself, derp... + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Are they Z equal (about?) + targetOrigin = ent->GetPhysics()->GetOrigin(); + if( idMath::Abs( targetOrigin.z - origin.z) > 16) { + continue; + } + + // are they within the stomp width? + if( targetOrigin.Dist( origin) < ( stompRadius - stompWidth) || + targetOrigin.Dist( origin) > stompRadius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, ent->GetPhysics()->GetCenterMass()) ) { + continue; + } + + //ok, damage them + dir = targetOrigin - origin; + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_makron_stomp_damage" ), 1.0f, 0 ); + //gameRenderWorld->DebugArrow( colorYellow, origin, targetOrigin, 5, 1000); + + } + + //move the radius along + stompRadius += stompSpeed; + + //run it back + PostEventSec( &EV_StompAttack, stompSpeed / stompMaxRadius , origin ); + +} + + diff --git a/src/game/ai/Monster_ConvoyGround.cpp b/src/game/ai/Monster_ConvoyGround.cpp new file mode 100644 index 0000000..490d012 --- /dev/null +++ b/src/game/ai/Monster_ConvoyGround.cpp @@ -0,0 +1,626 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +// +class rvMonsterConvoyGround : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterConvoyGround ); + + rvMonsterConvoyGround ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Postthink ( void ); + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool CheckPainActions ( void ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AdjustHealthByDamage ( int inDamage ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + int shots; + int minShots; + int maxShots; + bool isOpen; + bool vehicleCollision; + float moveCurrentAnimRate; + float moveAnimRateMin; + float moveAnimRateRange; + float moveAccelRate; + bool onGround; + idVec3 oldOrigin; + idVec3 lastPainDir; + + rvAIAction actionBlasterAttack; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual const char* GetIdleAnimName ( void ); + + virtual void OnDeath ( void ); + +private: + + // General states + stateResult_t State_Fall ( const stateParms_t& parms ); + + // Legs States + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Open ( const stateParms_t& parms ); + stateResult_t State_Torso_Close ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyGround ); +}; + +CLASS_DECLARATION( idAI, rvMonsterConvoyGround ) +END_CLASS + +/* +================ +rvMonsterConvoyGround::rvMonsterConvoyGround +================ +*/ +rvMonsterConvoyGround::rvMonsterConvoyGround ( ) { + shots = 0; + isOpen = false; + vehicleCollision = false; + moveCurrentAnimRate = 1.0f; +} + +void rvMonsterConvoyGround::InitSpawnArgsVariables ( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); + moveAccelRate = spawnArgs.GetFloat ( "moveAccelRate", ".1" ); + moveAnimRateMin = spawnArgs.GetFloat ( "moveMinAnimRate", "1" ); + moveAnimRateRange = spawnArgs.GetFloat ( "moveMaxAnimRate", "10" ) - moveAnimRateMin; +} + +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +void rvMonsterConvoyGround::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + aifl.disableLook = true; + + onGround = true; + +} + +/* +================ +rvMonsterConvoyGround::Prethink +================ +*/ +void rvMonsterConvoyGround::Postthink ( void ) { +/* FIXME + if ( onGround && !physicsObj.HasGroundContacts ( ) ) { + onGround = false; + InterruptState ( "State_Fall" ); + } +*/ + + idAI::Postthink ( ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +bool rvMonsterConvoyGround::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + lastPainDir = dir; + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +void rvMonsterConvoyGround::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + //minShots and maxShots are set in the Restore + savefile->WriteBool ( isOpen ); + savefile->WriteBool ( vehicleCollision ); + savefile->WriteBool ( onGround ); + + savefile->WriteFloat ( moveCurrentAnimRate ); + savefile->WriteVec3 ( oldOrigin ); + savefile->WriteVec3 ( lastPainDir ); + + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterConvoyGround::Restore +================ +*/ +void rvMonsterConvoyGround::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadBool ( isOpen ); + savefile->ReadBool ( vehicleCollision ); + savefile->ReadBool ( onGround ); + + savefile->ReadFloat ( moveCurrentAnimRate ); + savefile->ReadVec3 ( oldOrigin ); + savefile->ReadVec3 ( lastPainDir ); + + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterConvoyGround::CanMove +================ +*/ +bool rvMonsterConvoyGround::CanMove ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvMonsterConvoyGround::CanTurn +================ +*/ +bool rvMonsterConvoyGround::CanTurn ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvMonsterConvoyGround::OnDeath +================ +*/ +void rvMonsterConvoyGround::OnDeath ( void ) { + idVec3 fxOrg; + idVec3 up; + idMat3 fxAxis; + + //center it + fxOrg = GetPhysics()->GetCenterMass(); + + //point it up + up.Set( 0, 0, 1 ); + fxAxis = up.ToMat3(); + + //if we can play it at the joint, do that + jointHandle_t axisJoint = animator.GetJointHandle ( "axis" ); + if ( axisJoint != INVALID_JOINT ) { + idMat3 junk; + animator.GetJointLocalTransform( axisJoint, gameLocal.GetTime(), fxOrg, junk ); + fxOrg = renderEntity.origin + (fxOrg*renderEntity.axis); + } + + gameLocal.PlayEffect ( spawnArgs, "fx_death", fxOrg, fxAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterConvoyGround::AdjustHealthByDamage +================ +*/ +void rvMonsterConvoyGround::AdjustHealthByDamage ( int damage ) { + if ( isOpen || vehicleCollision ) { + idAI::AdjustHealthByDamage ( damage ); + } else { + PlayEffect ( "fx_shieldHit", animator.GetJointHandle ( "axis" ) ); + } +} + +void rvMonsterConvoyGround::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + vehicleCollision = false; + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + vehicleCollision = true; + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +bool rvMonsterConvoyGround::CheckActions ( void ) { + if ( isOpen ) { + if ( move.fl.moving ) { +/* + || !CheckAction_RangedAttack( &actionBlasterAttack, -1 ) + || enemy.range > actionBlasterAttack.maxRange + || enemy.range < actionBlasterAttack.minRange + || (!move.fl.moving && (gameLocal.GetTime()-move.startTime) > 3000 ) ) { +*/ + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Close", 4, true ); + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + // Open up if we have stopped and have an enemy + if ( !move.fl.moving && physicsObj.HasGroundContacts ( ) && enemy.ent && legsAnim.IsIdle ( ) && CheckTactical ( AITACTICAL_RANGED ) ) { + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Open", 4, true ); + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterConvoyGround::CheckPainActions +================ +*/ +bool rvMonsterConvoyGround::CheckPainActions ( void ) { + if ( isOpen ) { + return false; + } + + return idAI::CheckPainActions ( ); +} + +/* +================ +rvMonsterConvoyGround::GetIdleAnimName +================ +*/ +const char* rvMonsterConvoyGround::GetIdleAnimName ( void ) { + // Start idle animation + if ( isOpen ) { + return "idle_open"; + } + return "idle"; +} + +/* +================ +rvMonsterConvoyGround::FilterTactical +================ +*/ +int rvMonsterConvoyGround::FilterTactical ( int availableTactical ) { + return idAI::FilterTactical ( availableTactical ); +} + +/* +===================== +rvMonsterConvoyGround::GetDebugInfo +===================== +*/ +void rvMonsterConvoyGround::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + + proc ( "rvMonsterConvoyGround", "moveAnimRate", va("%g", moveCurrentAnimRate ), userData ); + proc ( "rvMonsterConvoyGround", "isOpen", isOpen ? "true" : "false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyGround ) + STATE ( "State_Fall", rvMonsterConvoyGround::State_Fall ) + STATE ( "Torso_Open", rvMonsterConvoyGround::State_Torso_Open ) + STATE ( "Torso_Close", rvMonsterConvoyGround::State_Torso_Close ) + STATE ( "Torso_BlasterAttack", rvMonsterConvoyGround::State_Torso_BlasterAttack ) + STATE ( "Torso_Pain", rvMonsterConvoyGround::State_Torso_Pain ) + + STATE ( "Legs_Move", rvMonsterConvoyGround::State_Legs_Move ) +END_CLASS_STATES + +/* +================ +rvMonsterConvoyGround::State_Fall +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact, switch to combat state + STAGE_WAITDONE, + STAGE_DONE + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StopAnimState ( ANIMCHANNEL_LEGS ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle", 0 ); + oldOrigin = physicsObj.GetOrigin ( ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( physicsObj.HasGroundContacts ( ) ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + + if ( (physicsObj.GetOrigin ( ) - oldOrigin).LengthSqr() > Square(128.0f) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "land", 0 ); + return SRESULT_STAGE ( STAGE_WAITDONE ); + } + return SRESULT_STAGE ( STAGE_DONE ); + + case STAGE_WAITDONE: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + onGround = true; + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Open +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Open ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "extend_legs", parms.blendFrames ); + isOpen = true; + aifl.disableLook = false; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Close +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Close ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "retract_legs", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + isOpen = false; + aifl.disableLook = true; + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_END + }; + + switch ( parms.stage ) { + case STAGE_START: + // Force the orientation to the direction we got hit from so the animation looks correct + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + TurnToward ( physicsObj.GetOrigin() - lastPainDir * 128.0f ); + move.current_yaw = move.ideal_yaw; + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END ); + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Legs_Move +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_MOVE + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + move.fl.running = true; + move.currentDirection = MOVEDIR_FORWARD; + // TODO: Looks like current anim rate never gets reset, so they do not correctly accelerate from a stop + // unfortunately, adding this change (with a decent acceleration factor) caused them to do lots of + // not-so-good looking short moves. +// moveCurrentAnimRate = 0; + + oldOrigin = physicsObj.GetOrigin ( ); + PlayCycle ( ANIMCHANNEL_LEGS, "run", 0 ); + StartSound( "snd_move", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_MOVE ); + + case STAGE_MOVE: + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + StopSound( SND_CHANNEL_BODY3, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + + // If on the ground update the animation rate based on the normal of the ground plane + if ( !ai_debugHelpers.GetBool ( ) && physicsObj.HasGroundContacts ( ) ) { + float rate; + idVec3 dir; + + dir = (physicsObj.GetOrigin ( ) - oldOrigin); + + if ( DistanceTo ( move.moveDest ) < move.walkRange ) { + rate = moveAnimRateMin; + } else if ( dir.Normalize ( ) > 0.0f ) { + rate = idMath::ClampFloat ( -0.7f, 0.7f, physicsObj.GetGravityNormal ( ) * dir ) / 0.7f; + rate = moveAnimRateMin + moveAnimRateRange * (1.0f + rate) / 2.0f; + } else { + rate = moveAnimRateMin + moveAnimRateRange * 0.5f; + } + moveCurrentAnimRate += ((rate - moveCurrentAnimRate) * moveAccelRate); + + animator.CurrentAnim ( ANIMCHANNEL_LEGS )->SetPlaybackRate ( gameLocal.time, moveCurrentAnimRate ); + } + + oldOrigin = physicsObj.GetOrigin ( ); + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + diff --git a/src/game/ai/Monster_ConvoyHover.cpp b/src/game/ai/Monster_ConvoyHover.cpp new file mode 100644 index 0000000..d5df4c6 --- /dev/null +++ b/src/game/ai/Monster_ConvoyHover.cpp @@ -0,0 +1,654 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// rvMonsterConvoyHover.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +class rvMonsterConvoyHover : public rvVehicleMonster { +public: + CLASS_PROTOTYPE ( rvMonsterConvoyHover ); + + rvMonsterConvoyHover ( void ); + ~rvMonsterConvoyHover ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + +private: + + void Think_Random ( void ); + void Think_Pathing ( void ); + + void AttackBlaster ( void ); + void AttackBeam ( void ); + //void AttackBomb ( void ); + + float angleYaw; // actually acos( angleYaw ) from player + float minYaw; + float maxYaw; + float desiredHeight; // actually acos( desiredHeight ) from player + float minHeight; + float maxHeight; + float distance; // distance to enemy + float minDistance; + float maxDistance; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + + int shotCount; + + idPhysics_RigidBody physicsObj; + + static const int DAMPEN_ANGLE_SAMPLES = 8; + idAngles hoverDampening[ DAMPEN_ANGLE_SAMPLES ]; + + void CalcDampening ( const idAngles & cur, idAngles & out ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_BeamAttack ( const stateParms_t& parms ); + //stateResult_t State_BombAttack ( const stateParms_t& parms ); + + struct { + bool pathing:1; + bool faceEnemy:1; + bool dead:1; + } myfl; + + virtual void OnDeath ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyHover ); +}; + +CLASS_DECLARATION ( rvVehicleMonster, rvMonsterConvoyHover ) +END_CLASS + +/* +================ +rvMonsterConvoyHover::rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::rvMonsterConvoyHover ( void ) { +} + +/* +================ +rvMonsterConvoyHover::~rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::~rvMonsterConvoyHover ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvMonsterConvoyHover::Save +================ +*/ +void rvMonsterConvoyHover::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( angleYaw ); + savefile->WriteFloat ( desiredHeight ); + savefile->WriteFloat ( distance ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->Write ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Write( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var +} + +/* +================ +rvMonsterConvoyHover::Restore +================ +*/ +void rvMonsterConvoyHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( angleYaw ); + savefile->ReadFloat ( desiredHeight ); + savefile->ReadFloat ( distance ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + savefile->ReadInt ( shotCount ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->Read ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Read( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var + + InitSpawnArgsVariables(); +} + +void rvMonsterConvoyHover::InitSpawnArgsVariables ( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + minYaw = spawnArgs.GetFloat( "minYaw", "0" ); + maxYaw = spawnArgs.GetFloat( "maxYaw", "360" ); + minHeight = spawnArgs.GetFloat( "minHeight", "10" ); + maxHeight = spawnArgs.GetFloat( "maxHeight", "70" ); + minDistance = spawnArgs.GetFloat( "minDistance", "100" ); + maxDistance = spawnArgs.GetFloat( "maxDistance", "500" ); + + minDistance *= minDistance; + maxDistance *= maxDistance; + + if ( minYaw == 0.0f && maxYaw == 0.0f ) { + maxYaw = 360.0f; + } + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} +/* +================ +rvMonsterConvoyHover::Spawn +================ +*/ +void rvMonsterConvoyHover::Spawn ( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( physicsObj ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP|CONTENTS_FLYCLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( vec3_origin ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); + + InitSpawnArgsVariables(); + + shotCount = 0; + + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = 0.5f * ( maxHeight - minHeight ) + minHeight; + distance = 0.5f * ( maxDistance - minDistance ) + minDistance; + + lastAttackTime = 0; + + SetState( "Idle" ); + + myfl.pathing = false; + myfl.faceEnemy = true; + myfl.dead = false; + + for ( int i = 0; i < DAMPEN_ANGLE_SAMPLES; i++ ) { + hoverDampening[ i ].Zero(); + } + + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvMonsterConvoyHover::Think +================ +*/ +void rvMonsterConvoyHover::Think ( void ) { + if ( !driver ) { + rvVehicleMonster::Think(); + return; + } + + if ( !driver->IsDriving() ) { + GetPhysics()->SetGravity( gameLocal.GetGravity() ); + } + + trace_t trace; + idVec3 dir = GetPhysics()->GetLinearVelocity(); + dir.Normalize(); + if ( gameLocal.TracePoint( this, trace, GetOrigin(), GetPhysics()->GetOrigin() + dir * 100.0f, MASK_SOLID|CONTENTS_FLYCLIP, 0 ) ) { + if ( trace.c.contents == CONTENTS_FLYCLIP ) { + float maxHeight = this->maxHeight * 2.0f; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), GetPhysics()->GetLinearVelocity() * 2.0f ); + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance + 5.0f ); + } + } + + if ( !driver->CanSee( driver->enemy.ent, false ) ) { + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance - 5.0f ); + } + + if ( myfl.pathing ) { + Think_Pathing( ); + } else { + Think_Random( ); + } + + rvVehicleMonster::Think(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); +// vel += idVec3( idMath::Sin( gameLocal.time + vel.x ), +// idMath::Sin( gameLocal.time + vel.y ), +// idMath::Sin( gameLocal.time + vel.z ) ); + + + idAngles hover = idAngles( idMath::ClampFloat( -45.0f, 45.0f, vel.x / 20.0f ), + 0.0f, + idMath::ClampFloat( -25.0f, 25.0f, vel.z / 20.0f ) ); + + CalcDampening( hover, hover ); + + if ( myfl.faceEnemy ) { + LookAtEntity( gameLocal.GetLocalPlayer(), 0 ); + idAngles angles = GetPhysics()->GetAxis( ).ToAngles( ); + angles.pitch *= 0.35f; + GetPhysics()->SetAxis( angles.ToMat3( ) * hover.ToMat3() ); + } else { + GetPhysics()->SetAxis( GetPhysics()->GetAxis() * hover.ToMat3() ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Random +================ +*/ +void rvMonsterConvoyHover::Think_Random ( void ) { + const idVec3 xAxis( 1.0f, 0.0f, 0.0f ); + const idVec3 yAxis( 0.0f, 1.0f, 0.0f ); + const idVec3 zAxis( 0.0f, 0.0f, 1.0f ); + const float xSpeed = 16000.0f; + const float ySpeed = 9000.0f; + const float zSpeed = 9000.0f; + + idEntity * ent = driver->enemy.ent; + if ( !ent ) { + ent = gameLocal.GetLocalPlayer(); + } + + idVec3 toEnemyYaw = ent->GetPhysics()->GetOrigin() - GetOrigin(); + toEnemyYaw.z = 0.0f; + toEnemyYaw.Normalize(); + float yaw = toEnemyYaw.ToAngles().yaw; + float height = GetOrigin().z - ent->GetPhysics()->GetOrigin().z; + float dist = GetOrigin().Dist2XY( ent->GetPhysics()->GetOrigin() ); + + // yaw + if ( idMath::Fabs( yaw - angleYaw ) < 10.0f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } else { + idVec3 impulse = yAxis * ( ( angleYaw < yaw ) ? ySpeed : -ySpeed ); + impulse *= GetPhysics()->GetAxis(); + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + + // 5% chance of choosing a new angle + if ( rvRandom::flrand() < 0.05f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } + } + + // pitch (changed to height for convenience) + if ( idMath::Fabs( height - desiredHeight ) < 5.0f ) { + desiredHeight = rvRandom::flrand( minHeight, maxHeight ); + } else if ( GetOrigin().z < ent->GetPhysics()->GetOrigin().z + desiredHeight ) { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * zSpeed ); + } else { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * -zSpeed ); + } + + // distance + if ( idMath::Fabs( dist - distance ) > 20.0f ) { + idVec3 impulse = xAxis * ( ( dist < distance ) ? -xSpeed : xSpeed ); + idMat3 axis = GetPhysics()->GetAxis(); + axis[ 1 ] = idVec3( 0.0f, 0.0f, 1.0f ); + axis[ 2 ] = axis[ 1 ].Cross( axis[ 0 ] ); + impulse *= axis; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + } else { + // just choose a random distance for now + distance = rvRandom::flrand( minDistance, maxDistance ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Pathing +================ +*/ +void rvMonsterConvoyHover::Think_Pathing ( void ) { +} + +/* +================ +rvMonsterConvoyHover::AttackBlaster +================ +*/ +void rvMonsterConvoyHover::AttackBlaster ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBeam +================ +*/ +void rvMonsterConvoyHover::AttackBeam ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBomb +================ +* +void rvMonsterConvoyHover::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + + PlayEffect ( "fx_bombflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_bomb") ); + if( proj ) { + proj->Create( this, offset, axis[0], NULL ); + proj->Launch( offset, axis[0], GetPhysics()->GetPushedLinearVelocity() ); + } +} +*/ + + + +/* +================ +rvMonsterConvoyHover::OnDeath +================ +*/ +void rvMonsterConvoyHover::OnDeath ( void ) { + myfl.dead = true; + idVec3 angular = idVec3( rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ) ); + + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetFriction( 0.0f, 0.0f, 0.0f ); + GetPhysics()->SetAngularVelocity ( angular ); + + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); +} + +/* +================ +rvMonsterConvoyHover::CalcDampening +================ +*/ +void rvMonsterConvoyHover::CalcDampening ( const idAngles & cur, idAngles & out ) { + idAngles current = cur; // just incase cur == out + out = cur; + + for ( int i = 1; i < DAMPEN_ANGLE_SAMPLES; i ++ ) { + hoverDampening[ i - 1 ] = hoverDampening[ i ]; + out += hoverDampening[ i ]; + } + hoverDampening[ DAMPEN_ANGLE_SAMPLES - 1 ] = current; + + out *= ( 1.0f / DAMPEN_ANGLE_SAMPLES ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyHover ) + STATE( "Idle", rvMonsterConvoyHover::State_Idle ) + STATE( "BlasterAttack", rvMonsterConvoyHover::State_BlasterAttack ) + STATE( "BeamAttack", rvMonsterConvoyHover::State_BeamAttack ) +// STATE( "BombAttack", rvMonsterConvoyHover::State_BombAttack ) +END_CLASS_STATES + + +/* +================ +rvMonsterConvoyHover::State_Idle +================ +*/ +stateResult_t rvMonsterConvoyHover::State_Idle ( const stateParms_t& parms ) { + if ( driver ) { + if ( gameLocal.time - lastAttackTime > rvRandom::irand( 1000, 1200 ) && CanSee( driver->enemy.ent, false ) ) { +// if ( rvRandom::irand( 0, 100 ) < 10 ) { +// distance = 0; +// PostState( "BeamAttack" ); +// } else { + PostState( "BlasterAttack" ); +// } + PostState( "Idle" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterConvoyHover::State_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BeamAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BeamAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BEAM, + STAGE_BEAMWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BEAM ); + + case STAGE_BEAM: + lastAttackTime = gameLocal.time; + AttackBeam( ); + return SRESULT_STAGE ( STAGE_BEAMWAIT ); + + case STAGE_BEAMWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BEAM ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BombAttack +================ +* +stateResult_t rvMonsterConvoyHover::State_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ diff --git a/src/game/ai/Monster_FailedTransfer.cpp b/src/game/ai/Monster_FailedTransfer.cpp new file mode 100644 index 0000000..f9a1437 --- /dev/null +++ b/src/game/ai/Monster_FailedTransfer.cpp @@ -0,0 +1,145 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" + +class rvMonsterFailedTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFailedTransfer ); + + rvMonsterFailedTransfer ( void ); + + void Spawn ( void ); + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + bool allowSplit; + + virtual void OnDeath ( void ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterFailedTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFailedTransfer ) +END_CLASS + +/* +================ +rvMonsterFailedTransfer::rvMonsterFailedTransfer +================ +*/ +rvMonsterFailedTransfer::rvMonsterFailedTransfer ( ) { + allowSplit = false; +} + +/* +================ +rvMonsterFailedTransfer::Spawn +================ +*/ +void rvMonsterFailedTransfer::Spawn ( void ) { + LoadAF ( "ragdoll_legs", true ); + LoadAF ( NULL, true ); +} + +/* +================ +rvMonsterFailedTransfer::Save +================ +*/ +void rvMonsterFailedTransfer::Save( idSaveGame *savefile ) const { + savefile->WriteBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::Restore +================ +*/ +void rvMonsterFailedTransfer::Restore( idRestoreGame *savefile ) { + savefile->ReadBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::OnDeath +================ +*/ +void rvMonsterFailedTransfer::OnDeath ( void ) { + idAI::OnDeath ( ); + + if ( allowSplit ) { + idEntity* torso; + idDict args; + + LoadAF ( "ragdoll_legs", true ); + + PlayEffect ( "fx_bloodyburst", animator.GetJointHandle ( "chest" ) ); + SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_failed_transfer_torso" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -50.0f ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &torso ); + torso->fl.takedamage = false; + PostEventMS( &AI_TakeDamage, 100, 1.0f ); + } +} + +/* +================ +rvMonsterFailedTransfer::Killed +================ +*/ +void rvMonsterFailedTransfer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !idStr::Icmp ( GetDamageGroup( location ), "legs" ) && damage < 999 ) { + allowSplit = true; + } else { + allowSplit = false; + } + + idAI::Killed ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFailedTransfer ) +END_CLASS_STATES + diff --git a/src/game/ai/Monster_Fatty.cpp b/src/game/ai/Monster_Fatty.cpp new file mode 100644 index 0000000..bd831d5 --- /dev/null +++ b/src/game/ai/Monster_Fatty.cpp @@ -0,0 +1,443 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +typedef struct monsterFattyChain_s { + jointHandle_t orientationJoint; + jointHandle_t attackJoint; + bool out; + idEntityPtr projectile; +} monsterFattyChain_t; + +class rvMonsterFatty : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFatty ); + + rvMonsterFatty ( void ) {} + ~rvMonsterFatty ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool UpdateAnimationControllers ( void ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { return false; }; + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + rvAIAction actionWhipAttack; + + enum { + CHAIN_LEFT, + CHAIN_RIGHT, + CHAIN_MAX + }; + + monsterFattyChain_t chains[CHAIN_MAX]; + + float missHeight; + + virtual bool CheckActions ( void ); + + void PlayAttackAnim ( const idVec3& target, int blendFrames ); + + void ResetAllChains ( void ); + void ChainIn ( int chain ); + void ChainOut ( int chain ); + +private: + + // Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + + // Frame Commands + stateResult_t Frame_LeftChainOut ( const stateParms_t& parms ); + stateResult_t Frame_LeftChainIn ( const stateParms_t& parms ); + + stateResult_t Frame_RightChainOut ( const stateParms_t& parms ); + stateResult_t Frame_RightChainIn ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterFatty ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFatty ) +END_CLASS + +/* +================ +rvMonsterFatty::~rvMonsterFatty +================ +*/ +rvMonsterFatty::~rvMonsterFatty ( void ) { + ResetAllChains ( ); +} + +void rvMonsterFatty::InitSpawnArgsVariables ( void ) +{ + chains[CHAIN_LEFT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChain", "chainb1" ) ); + chains[CHAIN_LEFT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChainAttack", "hookb" ) ); + + chains[CHAIN_RIGHT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChain", "chaina1" ) ); + chains[CHAIN_RIGHT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChainAttack", "hooka" ) ); + + missHeight = spawnArgs.GetFloat ( "missHeight", "72" ); +} +/* +================ +rvMonsterFatty::Spawn +================ +*/ +void rvMonsterFatty::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + + // Cache joints + chains[CHAIN_LEFT].out = false; + chains[CHAIN_LEFT].projectile = NULL; + + chains[CHAIN_RIGHT].out = false; + chains[CHAIN_RIGHT].projectile = NULL; + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::Save +================ +*/ +void rvMonsterFatty::Save ( idSaveGame *savefile ) const { + int i; + + actionWhipAttack.Save( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->WriteBool( chains[i].out ); + chains[i].projectile.Save ( savefile ); + } +} + +/* +================ +rvMonsterFatty::Restore +================ +*/ +void rvMonsterFatty::Restore ( idRestoreGame *savefile ) { + int i; + + actionWhipAttack.Restore( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->ReadBool( chains[i].out ); + chains[i].projectile.Restore ( savefile ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::UpdateAnimationControllers +================ +*/ +bool rvMonsterFatty::UpdateAnimationControllers ( void ) { + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + if ( enemy.ent && CheckFOV ( enemy.lastKnownPosition ) ) { + int i; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( enemy.ent->IsType ( idActor::GetClassType ( ) ) ) { + target = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + target = enemy.ent->GetPhysics()->GetOrigin ( ); + } + + if ( !IsEnemyVisible ( ) ) { + target -= enemy.ent->GetPhysics()->GetGravityNormal() * missHeight; + } + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + if ( !chains[i].out ) { + continue; + } + + animator.ClearJoint ( chains[i].orientationJoint ); + GetJointWorldTransform ( chains[i].orientationJoint, gameLocal.time, origin, axis ); + dir = target - origin; + dir.Normalize ( ); + axis.ProjectVector ( dir, localDir ); + animator.SetJointAxis ( chains[i].orientationJoint, JOINTMOD_LOCAL, localDir.ToMat3() ); + } + } + + return true; +} + +/* +================ +rvMonsterFatty::AddDamageEffect +================ +*/ +void rvMonsterFatty::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + /* + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + */ +} + +/* +================ +rvMonsterFatty::CheckAction_WhipAttack +================ +*/ +bool rvMonsterFatty::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterFatty::CheckActions +================ +*/ +bool rvMonsterFatty::CheckActions ( void ) { + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterFatty::CheckAction_WhipAttack, NULL ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterFatty::PlayAttackAnim +================ +*/ +void rvMonsterFatty::PlayAttackAnim ( const idVec3& target, int blendFrames ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -45.0f ) { + animName = "attack4b"; + } else if ( yaw < -20.0f ) { + animName = "attack3b"; + } else if ( yaw < -5.0f ) { + animName = "attack5r"; + } else if ( yaw < 5.0f ) { + animName = "attack5"; + } else if ( yaw < 20.0f ) { + animName = "attack5l"; + } else if ( yaw < 45.0f ) { + animName = "attack2b"; + } else{ + animName = "attack1b"; + } + + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); +} + +/* +================ +rvMonsterFatty::ChainOut +================ +*/ +void rvMonsterFatty::ChainOut ( int chain ) { + idEntity* ent; + idProjectile* proj; + + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_hook" ) ), &ent, false ); + proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + return; + } + + chains[chain].out = true; + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + chains[chain].projectile = proj; + ent->BindToJoint ( this, chains[chain].attackJoint, false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); +} + +/* +================ +rvMonsterFatty::ChainIn +================ +*/ +void rvMonsterFatty::ChainIn ( int chain ) { + chains[chain].out = false; + if ( chains[chain].projectile ) { + delete chains[chain].projectile; + chains[chain].projectile = NULL; + } +} + +/* +================ +rvMonsterFatty::ResetChains +================ +*/ +void rvMonsterFatty::ResetAllChains ( void ) { + int i; + + animator.ClearAllJoints ( ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + ChainIn ( i ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFatty ) + STATE ( "Torso_WhipAttack", rvMonsterFatty::State_Torso_WhipAttack ) + + STATE ( "Frame_LeftChainOut", rvMonsterFatty::Frame_LeftChainOut ) + STATE ( "Frame_LeftChainIn", rvMonsterFatty::Frame_LeftChainIn ) + + STATE ( "Frame_RightChainOut", rvMonsterFatty::Frame_RightChainOut ) + STATE ( "Frame_RightChainIn", rvMonsterFatty::Frame_RightChainIn ) +END_CLASS_STATES + + +/* +================ +rvMonsterFatty::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterFatty::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + PlayAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ResetAllChains ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_RIGHT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_RIGHT ); + return SRESULT_OK; +} diff --git a/src/game/ai/Monster_Gladiator.cpp b/src/game/ai/Monster_Gladiator.cpp new file mode 100644 index 0000000..8896774 --- /dev/null +++ b/src/game/ai/Monster_Gladiator.cpp @@ -0,0 +1,925 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class rvMonsterGladiator : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGladiator ); + + rvMonsterGladiator ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual int GetDamageForLocation( int damage, int location ); + +// virtual void SetTether ( rvAITether* newTether ); + +protected: + + // Actions + rvAIAction actionRailgunAttack; + + // Blaster attack + int maxShots; + int minShots; + int shots; + int lastShotTime; + + // Shield + bool usingShield; + idEntityPtr shield; + int shieldStartTime; + int shieldWaitTime; + int shieldHitDelay; + //int shieldInDelay; + //int shieldFov; + int shieldHealth; + int shieldConsecutiveHits; + int shieldLastHitTime; + + int railgunHealth; + int railgunDestroyedTime; + int nextTurnTime; + + virtual bool CheckActions ( void ); + void ShowShield ( void ); + void HideShield ( int hideTime=0 ); + void DestroyRailgun ( void ); + +private: + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso states + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldStart ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldEnd ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldFire ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after railgun is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterGladiator ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGladiator ) +END_CLASS + +/* +================ +rvMonsterGladiator::rvMonsterGladiator +================ +*/ +rvMonsterGladiator::rvMonsterGladiator ( ) { + usingShield = false; +} + +void rvMonsterGladiator::InitSpawnArgsVariables ( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + shieldHitDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldHitDelay", "1" ) ); +// shieldInDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldInDelay", "3" ) ); +// shieldFov = spawnArgs.GetInt ( "shieldfov", "90" ); +} +/* +================ +rvMonsterGladiator::Spawn +================ +*/ +void rvMonsterGladiator::Spawn ( void ) { + shieldWaitTime = 0; + shieldStartTime = 0; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + + InitSpawnArgsVariables(); + + shots = 0; + lastShotTime = 0; + + railgunHealth = spawnArgs.GetInt ( "railgunHealth", "100" ); + railgunDestroyedTime = 0; + + actionRailgunAttack.Init ( spawnArgs, "action_railgunAttack", "Torso_RailgunAttack", AIACTIONF_ATTACK ); + + // Disable range attack until using shield + //actionRangedAttack.fl.disabled = true; + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterGladiator::CheckActions + +Overriden to handle taking the shield out and putting it away. Will also ensure the gladiator +stays hidden behind his shield if getting shot at. +================ +*/ +bool rvMonsterGladiator::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f || (turnYaw > 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f || (turnYaw < 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + // Limited actions with shield out + if ( usingShield ) { + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + if ( move.moveCommand == MOVE_TO_ENEMY + && move.fl.moving ) + {//advancing on enemy with shield up + if ( gameLocal.GetTime() - lastShotTime > 1500 ) + {//been at least a second since the last time we fired while moving + if ( !gameLocal.random.RandomInt(2) ) + {//fire! + PerformAction ( "Torso_ShieldFire", 0, true ); + return true; + } + } + } + // Only ranged attack and melee attack are available when using shield + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + ( railgunHealth > 0 + && gameLocal.GetTime() - shieldStartTime > 2000 + && gameLocal.time - pain.lastTakenTime > 500 + && gameLocal.time - combat.shotAtTime > 300 + && gameLocal.GetTime() - shieldLastHitTime > 500 + && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) ) { + shieldWaitTime = 0; + return true; + } + + // see if it's safe to lower it? + if ( gameLocal.GetTime() - shieldStartTime > 2000 ) + {//shield's been up for at least 2 seconds + if ( !enemy.fl.visible || (gameLocal.time - combat.shotAtTime > 1000 && gameLocal.GetTime() - shieldLastHitTime > 1500) ) + { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) + { + PerformAction ( "Torso_ShieldEnd", 4, true ); + return true; + } + } + } + + return false; + } + else + {// Bring the shield out? + if ( combat.tacticalCurrent != AITACTICAL_MELEE || move.fl.done ) + {//not while rushing (NOTE: unless railgun was just destroyed?) + if ( enemy.fl.visible && enemy.fl.inFov ) + { + if ( combat.fl.aware && shieldWaitTime < gameLocal.GetTime() ) + { + if ( gameLocal.time - pain.lastTakenTime <= 1500 + || ( combat.shotAtAngle < 0 && gameLocal.time - combat.shotAtTime < 100 ) + || !gameLocal.random.RandomInt( 20 ) ) + { + if ( !gameLocal.random.RandomInt( 5 ) ) + { + PerformAction ( "Torso_ShieldStart", 4, true ); + return true; + } + } + } + } + } + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterGladiator::ShowShield +================ +*/ +void rvMonsterGladiator::ShowShield ( void ) { + // First time? + if ( !shield ) { + idEntity* ent; + idDict args; + const idDict *shieldDef = gameLocal.FindEntityDefDict( spawnArgs.GetString ( "def_shield" ), false ); + args.Set ( "classname", spawnArgs.GetString ( "def_shield" ) ); + if ( gameLocal.SpawnEntityDef( args, &ent ) ) { + shield = ent; + ent->GetPhysics()->SetClipMask ( 0 ); + ent->GetPhysics()->SetContents ( CONTENTS_RENDERMODEL ); + ent->GetPhysics()->GetClipModel ( )->SetOwner ( this ); + Attach ( ent ); + } + if ( !shield ) { + return; + } + if ( shieldDef && shield->IsType( idAFAttachment::GetClassType() ) ) + { + idAFAttachment* afShield = static_cast(shield.GetEntity()); + if ( afShield ) + { + jointHandle_t joint = animator.GetJointHandle( shieldDef->GetString( "joint" ) ); + afShield->SetBody ( this, shieldDef->GetString( "model" ), joint ); + } + } + } else if ( !shield || !shield->IsHidden() ) { + return; + } + + usingShield = true; + shieldWaitTime = 0; + animPrefix = "shield"; + shieldStartTime = gameLocal.time; +// actionRangedAttack.fl.disabled = false; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StartSound ( "snd_shield_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + + shield->Show ( ); + + SetShaderParm ( 6, gameLocal.time + 2000 ); +} + +/* +================ +rvMonsterGladiator::HideShield +================ +*/ +void rvMonsterGladiator::HideShield ( int hideTime ) { + if ( !shield || shield->IsHidden() ) { + return; + } + + usingShield = false; + animPrefix = ""; + shieldWaitTime = gameLocal.GetTime()+hideTime; +// actionRangedAttack.fl.disabled = true; + shieldHealth = 0; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StopSound ( SND_CHANNEL_ITEM, false ); + + shield->Hide ( ); +} + +/* +================ +rvMonsterGladiator::DestroyRailgun +================ +*/ +void rvMonsterGladiator::DestroyRailgun ( void ) { + HideSurface ( "models/monsters/gladiator/glad_railgun" ); + railgunHealth = -1; + + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_railgun_explode", "gun_main_jt" ) ); + GetJointWorldTransform ( joint, gameLocal.time, origin, axis ); + gameLocal.PlayEffect ( spawnArgs, "fx_railgun_explode", origin, axis ); + PlayEffect ( "fx_railgun_burn", joint, true ); + + GetAFPhysics()->GetBody ( "b_railgun" )->SetClipMask ( 0 ); + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "pain_big"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + railgunDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + actionRailgunAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + actionRangedAttack.chance = 0.25f; + actionRangedAttack.maxRange = 400; + minShots = 5; + maxShots = 15; + + combat.tacticalMaskAvailable &= ~AITACTICAL_HIDE_BIT; + + //temporarily disable this so we can charge and get mad + //FIXME: force MELEE + actionRangedAttack.timer.Add( 6000 ); + actionTimerRangedAttack.Add( 6000 ); + actionMeleeAttack.timer.Reset( actionTime ); + + //drop any tether since we need to advance + //SetTether(NULL); + //nevermind: let scripters handle it + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +================ +rvMonsterGladiator::UpdateRunStatus +================ +*/ +bool rvMonsterGladiator::UpdateRunStatus ( void ) { + // If rushing and moving forward, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE && move.currentDirection == MOVEDIR_FORWARD ) { + move.fl.idealRunning = true; + return move.fl.running != move.fl.idealRunning; + } + + // Alwasy walk with shield out + if ( usingShield ) { + move.fl.idealRunning = false; + return move.fl.running != move.fl.idealRunning; + } + + return idAI::UpdateRunStatus ( ); +} + +/* +============ +rvMonsterGladiator::SetTether +============ +*/ +/* +void rvMonsterGladiator::SetTether ( rvAITether* newTether ) { + if ( railgunHealth <= 0 ) { + //don't allow any tethers! + idAI::SetTether(NULL); + } else { + idAI::SetTether(newTether); + } +} +*/ + +/* +================ +rvMonsterGladiator::FilterTactical +================ +*/ +int rvMonsterGladiator::FilterTactical ( int availableTactical ) { + if ( railgunHealth > 0 ) { // Only let the gladiator rush when he is really close to his enemy + if ( !enemy.range || enemy.range > combat.awareRange ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } else { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } else if ( gameLocal.GetTime() - railgunDestroyedTime < 6000 ) { + availableTactical = AITACTICAL_MELEE_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGladiator::AddDamageEffect +================ +*/ +void rvMonsterGladiator::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( collision.c.material != NULL && (collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE ) ) { + // Delay putting shield away and shooting until the shield hasnt been hit for a while + actionRangedAttack.timer.Reset ( actionTime ); + actionRangedAttack.timer.Add ( shieldHitDelay ); + shieldStartTime = gameLocal.time; + return; + } + + return idAI::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +rvMonsterGladiator::GetDamageForLocation +===================== +*/ +int rvMonsterGladiator::GetDamageForLocation( int damage, int location ) { + // If the gun was hit only do damage to it + if ( idStr::Icmp ( GetDamageGroup ( location ), "gun" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( railgunHealth > 0 ){ + railgunHealth -= damage; + if ( railgunHealth <= 0 ) { + DestroyRailgun ( ); + } + } + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterGladiator::CanTurn +================ +*/ +bool rvMonsterGladiator::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterGladiator::Damage +================ +*/ +void rvMonsterGladiator::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef ) + { + if ( usingShield ) + {//shield up + if ( !damageDef->GetString( "filter_electricity", NULL ) ) + {//not by electricity + //If we get hit enough times with shield up, charge forward + if ( gameLocal.GetTime() - shieldLastHitTime > 1500 ) + { + shieldConsecutiveHits = 0; + } + shieldConsecutiveHits++; + shieldLastHitTime = gameLocal.GetTime(); + if ( shieldConsecutiveHits > 20 && combat.tacticalCurrent != AITACTICAL_MELEE && move.fl.done ) + {//really laying into us, move up + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } + + if ( idStr::Icmp ( GetDamageGroup ( location ), "shield" ) == 0 ) + {//Hit in shield + if ( damageDef->GetString( "filter_electricity", NULL ) ) + {//by electricity + shieldHealth -= damageDef->GetInt( "damage" ) * damageScale; + if ( shield ) + { + shield->SetShaderParm( SHADERPARM_MODE, gameLocal.GetTime() + gameLocal.random.RandomInt(1000) + 1000 ); + } + StartSound( "snd_shield_flicker", SND_CHANNEL_ANY, 0, false, NULL ); + if ( shieldHealth <= 0 ) + {//drop it + HideShield( gameLocal.random.RandomInt(3000)+2000 );//FIXME: when it does come back on, flicker back on? + painAnim = "pain_con"; + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + combat.shotAtTime = gameLocal.GetTime(); + } + return; + } + } + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( aifl.pain ) + {//hurt + if ( usingShield ) + {//shield up + //move in! + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } +} + +/* +================ +rvMonsterGladiator::Save +================ +*/ +void rvMonsterGladiator::Save( idSaveGame *savefile ) const { + actionRailgunAttack.Save ( savefile ) ; + + savefile->WriteInt ( shots ); + savefile->WriteInt ( lastShotTime ); + + savefile->WriteBool ( usingShield ); + shield.Save( savefile ); + savefile->WriteInt ( shieldStartTime ); + savefile->WriteInt ( shieldWaitTime ); + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shieldConsecutiveHits ); + savefile->WriteInt ( shieldLastHitTime ); + + savefile->WriteInt ( railgunHealth ); + savefile->WriteInt ( railgunDestroyedTime ); + savefile->WriteInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Save( savefile ); +} + +/* +================ +rvMonsterGladiator::Restore +================ +*/ +void rvMonsterGladiator::Restore( idRestoreGame *savefile ) { + actionRailgunAttack.Restore ( savefile ) ; + + savefile->ReadInt ( shots ); + savefile->ReadInt ( lastShotTime ); + savefile->ReadBool ( usingShield ); + shield.Restore( savefile ); + savefile->ReadInt ( shieldStartTime ); + savefile->ReadInt ( shieldWaitTime ); + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shieldConsecutiveHits ); + savefile->ReadInt ( shieldLastHitTime ); + + savefile->ReadInt ( railgunHealth ); + savefile->ReadInt ( railgunDestroyedTime ); + savefile->ReadInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGladiator::GetDebugInfo +================ +*/ +void rvMonsterGladiator::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_RailgunAttack", aiActionStatusString[actionRailgunAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGladiator ) + STATE ( "State_Killed", rvMonsterGladiator::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterGladiator::State_Torso_BlasterAttack ) + STATE ( "Torso_RailgunAttack", rvMonsterGladiator::State_Torso_RailgunAttack ) + STATE ( "Torso_ShieldStart", rvMonsterGladiator::State_Torso_ShieldStart ) + STATE ( "Torso_ShieldEnd", rvMonsterGladiator::State_Torso_ShieldEnd ) + + STATE ( "Torso_TurnRight90", rvMonsterGladiator::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterGladiator::State_Torso_TurnLeft90 ) + STATE ( "Torso_ShieldFire", rvMonsterGladiator::State_Torso_ShieldFire ) + +END_CLASS_STATES + +/* +================ +rvMonsterGladiator::State_Killed +================ +*/ +stateResult_t rvMonsterGladiator::State_Killed ( const stateParms_t& parms ) { + HideShield ( ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldStart +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldStart ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + ShowShield ( ); + PlayAnim ( ANIMCHANNEL_TORSO, "start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + SetShaderParm ( 6, 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldEnd +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldEnd ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) //anim done + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + HideShield ( 2000 ); + actionRailgunAttack.timer.Reset( actionTime ); + actionMeleeAttack.timer.Reset( actionTime ); + actionRangedAttack.timer.Reset( actionTime ); + actionTimerRangedAttack.Reset( actionTime ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_start", parms.blendFrames ); + //shots = 4; + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_start" ) ) {//anim changed + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + if ( --shots <= 0 || !enemy.fl.inFov || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGladiator::State_Torso_RailgunAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_RailgunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( usingShield ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ShieldEnd", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RailgunAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "railgun_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "railgun_attack" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_right" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_left" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldFire +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldFire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + PlayCycle( ANIMCHANNEL_TORSO, "walk_aim", 1 ); + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + Attack( "blaster", animator.GetJointHandle( "lft_wrist_jt"), GetEnemy() ); + PlayEffect( "fx_blaster_flash", animator.GetJointHandle("lft_wrist_jt") ); + lastShotTime = gameLocal.GetTime(); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( move.fl.done ) + { + return SRESULT_DONE; + } + if ( (gameLocal.GetTime()-lastShotTime) >= 250 ) { + shots--; + if ( GetEnemy() && shots > 0 ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } + PlayCycle( ANIMCHANNEL_TORSO, "walk", 1 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_Grunt.cpp b/src/game/ai/Monster_Grunt.cpp new file mode 100644 index 0000000..894e2c4 --- /dev/null +++ b/src/game/ai/Monster_Grunt.cpp @@ -0,0 +1,341 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGrunt : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGrunt ); + + rvMonsterGrunt ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void AdjustHealthByDamage ( int damage ); + +protected: + + rvAIAction actionMeleeMoveAttack; + rvAIAction actionChaingunAttack; + + virtual bool CheckActions ( void ); + + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnDeath ( void ); + +private: + + int standingMeleeNoAttackTime; + int rageThreshold; + + void RageStart ( void ); + void RageStop ( void ); + + // Torso States + stateResult_t State_Torso_Enrage ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_LeapAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGrunt ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGrunt ) +END_CLASS + +/* +================ +rvMonsterGrunt::rvMonsterGrunt +================ +*/ +rvMonsterGrunt::rvMonsterGrunt ( void ) { + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterGrunt::Spawn +================ +*/ +void rvMonsterGrunt::Spawn ( void ) { + rageThreshold = spawnArgs.GetInt ( "health_rageThreshold" ); + + // Custom actions + actionMeleeMoveAttack.Init ( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", NULL, AIACTIONF_ATTACK ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", "Torso_LeapAttack", AIACTIONF_ATTACK ); + + // Enraged to start? + if ( spawnArgs.GetBool ( "preinject" ) ) { + RageStart ( ); + } +} + +/* +================ +rvMonsterGrunt::Save +================ +*/ +void rvMonsterGrunt::Save ( idSaveGame *savefile ) const { + actionMeleeMoveAttack.Save( savefile ); + actionChaingunAttack.Save( savefile ); + + savefile->WriteInt( rageThreshold ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::Restore +================ +*/ +void rvMonsterGrunt::Restore ( idRestoreGame *savefile ) { + actionMeleeMoveAttack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + savefile->ReadInt( rageThreshold ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::RageStart +================ +*/ +void rvMonsterGrunt::RageStart ( void ) { + SetShaderParm ( 6, 1 ); + + // Disable non-rage actions + actionEvadeLeft.fl.disabled = true; + actionEvadeRight.fl.disabled = true; + + // Speed up animations + animator.SetPlaybackRate ( 1.25f ); + + // Disable pain + pain.threshold = 0; + + // Start over with health when enraged + health = spawnArgs.GetInt ( "health" ); + + // No more going to rage + rageThreshold = 0; +} + +/* +================ +rvMonsterGrunt::RageStop +================ +*/ +void rvMonsterGrunt::RageStop ( void ) { + SetShaderParm ( 6, 0 ); +} + +/* +================ +rvMonsterGrunt::CheckActions +================ +*/ +bool rvMonsterGrunt::CheckActions ( void ) { + // If our health is below the rage threshold then enrage + if ( health < rageThreshold ) { + PerformAction ( "Torso_Enrage", 4, true ); + return true; + } + + // Moving melee attack? + if ( PerformAction ( &actionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + + // Default actions + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + //allow ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} + +/* +================ +rvMonsterGrunt::OnDeath +================ +*/ +void rvMonsterGrunt::OnDeath ( void ) { + RageStop ( ); + return idAI::OnDeath ( ); +} + +/* +================ +rvMonsterGrunt::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGrunt::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterGrunt::AdjustHealthByDamage +===================== +*/ +void rvMonsterGrunt::AdjustHealthByDamage ( int damage ) { + // Take less damage during enrage process + if ( rageThreshold && health < rageThreshold ) { + health -= (damage * 0.25f); + return; + } + return idAI::AdjustHealthByDamage ( damage ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGrunt ) + STATE ( "Torso_Enrage", rvMonsterGrunt::State_Torso_Enrage ) + STATE ( "Torso_Pain", rvMonsterGrunt::State_Torso_Pain ) + STATE ( "Torso_LeapAttack", rvMonsterGrunt::State_Torso_LeapAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGrunt::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Pain ( const stateParms_t& parms ) { + // Stop streaming pain if its time to get angry + if ( pain.loopEndTime && health < rageThreshold ) { + pain.loopEndTime = 0; + } + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterGrunt::State_Torso_Enrage +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Enrage ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "anger", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + RageStart ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGrunt::State_Torso_LeapAttack +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_LeapAttack ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + lastAttackTime = 0; + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + // If we missed our leap attack get angry + if ( !lastAttackTime && rageThreshold ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Enrage", parms.blendFrames ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_Gunner.cpp b/src/game/ai/Monster_Gunner.cpp new file mode 100644 index 0000000..18bee6d --- /dev/null +++ b/src/game/ai/Monster_Gunner.cpp @@ -0,0 +1,471 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGunner : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGunner ); + + rvMonsterGunner ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int shots; + int shotsFired; + idStr nailgunPrefix; + int nailgunMinShots; + int nailgunMaxShots; + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + // Actions + rvAIAction actionGrenadeAttack; + rvAIAction actionNailgunAttack; + + rvAIAction actionSideStepLeft; + rvAIAction actionSideStepRight; + rvAIActionTimer actionTimerSideStep; + + bool CheckAction_SideStepLeft ( rvAIAction* action, int animNum ); + bool CheckAction_SideStepRight ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_NailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGunner ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGunner ) +END_CLASS + +/* +================ +rvMonsterGunner::rvMonsterGunner +================ +*/ +rvMonsterGunner::rvMonsterGunner ( ) { + nextShootTime = 0; +} + + +void rvMonsterGunner::InitSpawnArgsVariables( void ) +{ + nailgunMinShots = spawnArgs.GetInt ( "action_nailgunAttack_minshots", "5" ); + nailgunMaxShots = spawnArgs.GetInt ( "action_nailgunAttack_maxshots", "20" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.3" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterGunner::Spawn +================ +*/ +void rvMonsterGunner::Spawn ( void ) { + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + actionNailgunAttack.Init ( spawnArgs, "action_nailgunAttack", "Torso_NailgunAttack", AIACTIONF_ATTACK ); + actionSideStepLeft.Init ( spawnArgs, "action_sideStepLeft", NULL, 0 ); + actionSideStepRight.Init ( spawnArgs, "action_sideStepRight", NULL, 0 ); + actionTimerSideStep.Init ( spawnArgs, "actionTimer_sideStep" ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGunner::Save +================ +*/ +void rvMonsterGunner::Save ( idSaveGame *savefile ) const { + actionGrenadeAttack.Save ( savefile ); + actionNailgunAttack.Save ( savefile ); + actionSideStepLeft.Save ( savefile ); + actionSideStepRight.Save ( savefile ); + actionTimerSideStep.Save ( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + savefile->WriteString ( nailgunPrefix ); + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterGunner::Restore +================ +*/ +void rvMonsterGunner::Restore ( idRestoreGame *savefile ) { + actionGrenadeAttack.Restore ( savefile ); + actionNailgunAttack.Restore ( savefile ); + actionSideStepLeft.Restore ( savefile ); + actionSideStepRight.Restore ( savefile ); + actionTimerSideStep.Restore ( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + savefile->ReadString ( nailgunPrefix ); + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterGunner::OnStopMoving +============ +*/ +void rvMonsterGunner::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + {//hiding + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE || enemy.range <= combat.meleeRange ) + {//in melee state or in melee range + actionMeleeAttack.timer.Clear( actionTime ); + } + else if ( (!actionNailgunAttack.timer.IsDone(actionTime) || !actionTimerRangedAttack.IsDone(actionTime)) + && (!actionGrenadeAttack.timer.IsDone(actionTime) || !actionTimerSpecialAttack.IsDone(actionTime)) ) + {//no attack is ready + //Ready at least one of them + if ( gameLocal.random.RandomInt(3) ) + { + actionNailgunAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + } + else + { + actionGrenadeAttack.timer.Clear( actionTime ); + actionTimerSpecialAttack.Clear( actionTime ); + } + } + } +} + +/* +================ +rvMonsterGunner::UpdateRunStatus +================ +*/ +bool rvMonsterGunner::UpdateRunStatus ( void ) { + move.fl.idealRunning = false; + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterGunner::FilterTactical +================ +*/ +int rvMonsterGunner::FilterTactical ( int availableTactical ) { + if ( !move.fl.moving && enemy.range > combat.meleeRange ) + {//keep moving! + if ( (!actionNailgunAttack.timer.IsDone(actionTime+500) || !actionTimerRangedAttack.IsDone(actionTime+500)) + && (!actionGrenadeAttack.timer.IsDone(actionTime+500) || !actionTimerSpecialAttack.IsDone(actionTime+500)) ) + {//won't be attacking in the next 1 second + combat.tacticalUpdateTime = 0; + availableTactical |= (AITACTICAL_MELEE_BIT); + if ( !gameLocal.random.RandomInt(2) ) + { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGunner::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGunner::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + //walk for at least 2 seconds (default update time of 500 is too short) + combat.tacticalUpdateTime = gameLocal.GetTime() + 2000 + gameLocal.random.RandomInt(1000); + break; + } +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepLeft +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepLeft ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the left? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepRight +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepRight ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove ( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the right? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckActions +================ +*/ +bool rvMonsterGunner::CheckActions ( void ) { + // Fire a grenade? + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) || + PerformAction ( &actionNailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + bool action = idAI::CheckActions( ); + if ( !action ) { + //try a strafe + if ( GetEnemy() && enemy.fl.visible && gameLocal.GetTime()-lastAttackTime > actionTimerRangedAttack.GetRate()+1000 ) { + //we can see our enemy but haven't been able to shoot him in a while... + if ( PerformAction ( &actionSideStepLeft, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepLeft, &actionTimerSideStep ) + || PerformAction ( &actionSideStepRight, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepRight, &actionTimerSideStep ) ) { + return true; + } + } + } + return action; +} + +/* +===================== +rvMonsterGunner::GetDebugInfo +===================== +*/ +void rvMonsterGunner::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_grenadeAttack", aiActionStatusString[actionGrenadeAttack.status], userData ); + proc ( "idAI", "action_nailgunAttack", aiActionStatusString[actionNailgunAttack.status], userData ); + proc ( "idAI", "actionSideStepLeft", aiActionStatusString[actionSideStepLeft.status], userData ); + proc ( "idAI", "actionSideStepRight", aiActionStatusString[actionSideStepRight.status], userData ); +} + +bool rvMonsterGunner::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + actionTimerRangedAttack.Clear( actionTime ); + actionNailgunAttack.timer.Clear( actionTime ); + return (idAI::Pain( inflictor, attacker, damage, dir, location )); +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGunner ) + STATE ( "Torso_NailgunAttack", rvMonsterGunner::State_Torso_NailgunAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterGunner::State_Torso_MovingRangedAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGunner::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "nail", attackJoint, GetEnemy() ); + PlayEffect( "fx_nail_flash", attackJoint ); + } + StartSound( "snd_nailgun_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= nailgunMinShots) || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGunner::State_Torso_NailgunAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_NailgunAttack ( const stateParms_t& parms ) { + static const char* nailgunAnims [ ] = { "nailgun_short", "nailgun_long" }; + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && !actionNailgunAttack.fl.overrideLegs && FacingIdeal() && !gameLocal.random.RandomInt(1) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + DisableAnimState ( ANIMCHANNEL_LEGS ); + shotsFired = 0; + nailgunPrefix = nailgunAnims[shots%2]; + if ( !CanHitEnemyFromAnim( GetAnim( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ) ) ) ) + {//this is hacky, but we really need to test the attack anim first since they're so different + //can't hit with this one, just use the other one... + nailgunPrefix = nailgunAnims[(shots+1)%2]; + } + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_start", nailgunPrefix.c_str() ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ), 0 ); + shotsFired++; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (shotsFired >= nailgunMinShots && !enemy.fl.inFov) || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_end", nailgunPrefix.c_str() ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_Harvester.cpp b/src/game/ai/Monster_Harvester.cpp new file mode 100644 index 0000000..875454f --- /dev/null +++ b/src/game/ai/Monster_Harvester.cpp @@ -0,0 +1,1214 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +class rvMonsterHarvester : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvester ); + + rvMonsterHarvester ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + + virtual bool UpdateAnimationControllers ( void ); + bool CanTurn ( void ) const; + virtual int GetDamageForLocation ( int damage, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + enum { + WHIP_LEFT, + WHIP_CENTER, + WHIP_RIGHT, + WHIP_MAX + }; + + enum { + PART_ARM_R, + PART_ARM_L, + PART_LEG_FR, + PART_LEG_FL, + PART_LEG_BR, + PART_LEG_BL, + PART_TANK_R, + PART_TANK_L, + PARTS_MAX + }; + idStr partLocation[PARTS_MAX]; + idStr partSurf[PARTS_MAX]; + idStr partJoint[PARTS_MAX]; + int partHealth[PARTS_MAX]; + void DestroyPart ( int part ); + + rvAIAction actionWhipAttack; + rvAIAction actionSprayAttack; + rvAIAction actionRocketAttack; + rvAIAction actionGrenadeAttack; + + jointHandle_t whipJoints[WHIP_MAX]; + idEntityPtr whipProjectiles[WHIP_MAX]; + + jointHandle_t jointLeftMuzzle; + jointHandle_t jointRightMuzzle; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + const char* GetMeleeAttackAnim ( const idVec3& target ); + bool PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ); + const char* GetRangedAttackAnim ( const idVec3& target ); + bool PlayRangedAttackAnim ( const idVec3& target, int blendFrames ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + + void DropLeg ( int part ); +// Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RocketAttack( rvAIAction* action, int animNum ); + bool CheckAction_GrenadeAttack( rvAIAction* action, int animNum ); + + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ClawAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvester ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvester ) +END_CLASS + +/* +================ +rvMonsterHarvester::rvMonsterHarvester +================ +*/ +rvMonsterHarvester::rvMonsterHarvester ( void ) { +} + +void rvMonsterHarvester::InitSpawnArgsVariables( void ) +{ + whipJoints[WHIP_LEFT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_left" ) ); + whipJoints[WHIP_RIGHT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_right" ) ); + whipJoints[WHIP_CENTER] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_center" ) ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partLocation[part] = spawnArgs.GetString( va("part_%d_location",part), "" ); + partSurf[part] = spawnArgs.GetString( va("part_%d_surf",part), "" ); + partJoint[part] = spawnArgs.GetString( va("part_%d_joint",part), "" ); + } + + jointLeftMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_left_arm" ) ); + jointRightMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_right_arm" ) ); +} +/* +================ +rvMonsterHarvester::Spawn +================ +*/ +void rvMonsterHarvester::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + whipProjectiles[i] = NULL; + } + + InitSpawnArgsVariables(); + shots = 0; + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partHealth[part] = spawnArgs.GetInt( va("part_%d_health",part), "500" ); + } +} + +/* +================ +rvMonsterHarvester::Save +================ +*/ +void rvMonsterHarvester::Save ( idSaveGame *savefile ) const { + actionWhipAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->WriteObject( whipProjectiles[i] ); + } + + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->WriteInt( partHealth[part] ); + } +} + +/* +================ +rvMonsterHarvester::Restore +================ +*/ +void rvMonsterHarvester::Restore ( idRestoreGame *savefile ) { + actionWhipAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->ReadObject( reinterpret_cast( whipProjectiles[i] ) ); + } + + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->ReadInt( partHealth[part] ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterHarvester::UpdateAnimationControllers +================ +*/ +bool rvMonsterHarvester::UpdateAnimationControllers ( void ) { + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + return true; +} + +/* +===================== +rvMonsterHarvester::SkipImpulse +===================== +*/ +bool rvMonsterHarvester::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +void rvMonsterHarvester::DropLeg( int part ) +{ + jointHandle_t joint = INVALID_JOINT; + const char* legDef = NULL; + switch ( part ) + { + case PART_LEG_FR: + joint = animator.GetJointHandle( "r_toe_front" ); + legDef = "def_leg_part1"; + break; + case PART_LEG_FL: + joint = animator.GetJointHandle( "l_toe_front" ); + legDef = "def_leg_part2"; + break; + case PART_LEG_BR: + joint = animator.GetJointHandle( "r_toe_back" ); + legDef = "def_leg_part4"; + break; + case PART_LEG_BL: + joint = animator.GetJointHandle( "l_toe_back" ); + legDef = "def_leg_part3"; + break; + } + if ( joint != INVALID_JOINT ) + { + idEntity* leg = gameLocal.SpawnEntityDef( spawnArgs.GetString( legDef ) ); + if ( leg ) + { + idVec3 jointOrg; + idMat3 jointAxis; + animator.GetJointTransform( joint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + leg->GetPhysics()->SetOrigin( jointOrg ); + leg->GetPhysics()->SetAxis( jointAxis*renderEntity.axis ); + leg->PlayEffect( "fx_trail", vec3_origin, jointAxis, true, vec3_origin, true ); + if ( leg->IsType( idDamagable::GetClassType() ) ) + {//don't be destroyed for at least 5 seconds + ((idDamagable*)leg)->invincibleTime = gameLocal.GetTime() + 5000; + } + // push it + if ( jointOrg.z > GetPhysics()->GetOrigin().z+20.0f ) + {//leg was blown off while in the air... + jointHandle_t attachJoint = animator.GetJointHandle(partJoint[part].c_str()); + if ( attachJoint != INVALID_JOINT ) + { + animator.GetJointTransform( attachJoint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + + idVec3 impulse = leg->GetPhysics()->GetCenterMass() - jointOrg; + impulse.z = 0; + impulse.Normalize(); + impulse *= ((gameLocal.random.RandomFloat()*3.0f)+2.0f) * 1000;//away + impulse.z = (gameLocal.random.CRandomFloat()*500.0f)+1000.0f;//up! + leg->ApplyImpulse( this, 0, jointOrg, impulse ); + } + } + } + } +} + +void rvMonsterHarvester::DestroyPart( int part ) +{ + idStr explodeFX; + idStr trailFX; + switch ( part ) + { + case PART_ARM_R: + if ( partHealth[PART_ARM_L] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + case PART_ARM_L: + if ( partHealth[PART_ARM_R] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + //FIXME: spawn leg func_movables + case PART_LEG_FR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_frt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_FL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_flt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_brt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_blt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_TANK_R: + if ( partHealth[PART_TANK_L] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + case PART_TANK_L: + if ( partHealth[PART_TANK_R] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + } + HideSurface( partSurf[part].c_str() ); + PlayEffect( explodeFX, animator.GetJointHandle(partJoint[part].c_str()) ); + PlayEffect( trailFX, animator.GetJointHandle(partJoint[part].c_str()), true ); + //make sure it plays this pain + actionTimerPain.Reset ( actionTime ); +} + +/* +================ +rvMonsterHarvester::CanTurn +================ +*/ +bool rvMonsterHarvester::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); +} + +/* +===================== +rvMonsterHarvester::GetDamageForLocation +===================== +*/ +int rvMonsterHarvester::GetDamageForLocation( int damage, int location ) { + // If the part was hit only do damage to it + const char* dmgGroup = GetDamageGroup ( location ); + if ( dmgGroup ) + { + for ( int part = 0; part < PARTS_MAX; part++ ) + { + if ( idStr::Icmp ( dmgGroup, partLocation[part].c_str() ) == 0 ) + { + if ( partHealth[part] > 0 ) + { + partHealth[part] -= damage; + painAnim = "pain"; + if ( partHealth[part] <= 0 ) + { + if ( animPrefix.Length() + && (part == PART_LEG_FR + || part == PART_LEG_FL + || part == PART_LEG_BR + || part == PART_LEG_BL ) ) + {//just blew off a leg and already had one blown off... + DestroyPart( part ); + //we dead + health = 0; + return damage; + } + else + { + //FIXME: big pain? + DestroyPart( part ); + } + } + else + { + if ( !animPrefix.Length() ) + { + switch ( part ) + { + case PART_LEG_FR: + painAnim = "leg_pain_fr"; + break; + case PART_LEG_FL: + painAnim = "leg_pain_fl"; + break; + case PART_LEG_BR: + painAnim = "leg_pain_br"; + break; + case PART_LEG_BL: + painAnim = "leg_pain_bl"; + break; + } + } + } + + if ( pain.threshold < damage && health > 0 ) + //if ( move.anim_turn_angles == 0.0f ) + {//not in the middle of a turn + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + } + //pain.takenThisFrame = damage; + return 0; + } + } + } + + if ( health <= spawnArgs.GetInt( "death_damage_threshold" ) + && spawnArgs.GetInt( "death_damage_threshold" ) > damage ) + {//doesn't meet the minimum damage requirements to kill us + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterHarvester::Damage +================ +*/ +void rvMonsterHarvester::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + //don't take damage from ourselves + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHarvester::CheckAction_SprayAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !CheckFOV( GetEnemy()->GetEyePosition(), 20.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RocketAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RocketAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_GrenadeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_GrenadeAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_WhipAttack +================:: +*/ +bool rvMonsterHarvester::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_MeleeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 90 ) ) { + return false; + } + if ( !GetMeleeAttackAnim( enemy.ent->GetEyePosition() ) ) + { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( idAI::CheckAction_RangedAttack(action,animNum) && enemy.ent && GetRangedAttackAnim( enemy.ent->GetEyePosition() ) ); +} + +/* +================ +rvMonsterHarvester::CheckActions +================ +*/ +bool rvMonsterHarvester::CheckActions ( void ) { + + // such a dirty hack... I'm not sure what is actually wrong, but somehow nextTurnTime is getting to be a rediculously high number. + // I have some more significant bugs that really need to be solved, so for now, this will have to do. + if ( nextTurnTime > gameLocal.time + 500 ) { + nextTurnTime = gameLocal.time-1; + } + + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.6f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.6f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_WhipAttack, NULL ) ) { + return true; + } + if ( PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_SprayAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_RocketAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_GrenadeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterHarvester::FilterTactical +================ +*/ +int rvMonsterHarvester::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT|AITACTICAL_RANGED_BITS|AITACTICAL_MELEE_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvester ) + STATE ( "State_Killed", rvMonsterHarvester::State_Killed ) + STATE ( "State_Dead", rvMonsterHarvester::State_Dead ) + + STATE ( "Torso_WhipAttack", rvMonsterHarvester::State_Torso_WhipAttack ) + STATE ( "Torso_ClawAttack", rvMonsterHarvester::State_Torso_ClawAttack ) + STATE ( "Torso_RangedAttack", rvMonsterHarvester::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvester::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvester::State_Torso_TurnLeft90 ) + STATE ( "Torso_SprayAttack", rvMonsterHarvester::State_Torso_SprayAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHarvester::State_Torso_RocketAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvester::State_Killed +================ +*/ +stateResult_t rvMonsterHarvester::State_Killed ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + int numLegsLost = 0; + for ( int i = PART_LEG_FR; i <= PART_LEG_BL; i++ ) { + if ( partHealth[i] <= 0 ) { + numLegsLost++; + } + } + if ( numLegsLost > 1 ) { + //dmg_death when 2 legs are blown off + PlayAnim( ANIMCHANNEL_TORSO, "dmg_death", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "death", 0 ); + } + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterHarvester::State_Dead +================ +*/ +stateResult_t rvMonsterHarvester::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterHarvester::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + idEntity* ent; + int i; + + for ( i = 0; i < WHIP_MAX; i ++ ) { + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_whip" ) ), &ent, false ); + idProjectile* proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + continue; + } + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + whipProjectiles[i] = proj; + ent->BindToJoint ( this, whipJoints[i], false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + delete whipProjectiles[i]; + whipProjectiles[i] = NULL; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvMonsterHarvester::Attack +===================== +*/ +bool rvMonsterHarvester::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + //NOTE: this stops spawning of projectile, but not muzzle flash... oh well... + if ( joint == jointLeftMuzzle && partHealth[PART_ARM_L] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + if ( joint == jointRightMuzzle && partHealth[PART_ARM_R] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + return idAI::Attack( attackName, joint, target, pushVelocity ); +} + +/* +================ +rvMonsterHarvester::GetMeleeAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetMeleeAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -10.0f ) { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw_rt"; + } else if ( yaw > 10.0f ) { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw_lt"; + } else{ + if ( gameLocal.random.RandomFloat() < 0.5f || partHealth[PART_LEG_FR] <= 0 ) + { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw"; + } + else + { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw"; + } + } + return animName; +} +/* +================ +rvMonsterHarvester::PlayMeleeAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetMeleeAttackAnim( target ); + if ( animName ) + { + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::GetRangedAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetRangedAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName = NULL; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -20.0f ) { + if ( partHealth[PART_ARM_R] <= 0 ) + { + return NULL; + } + animName = "fire_right"; + } else if ( yaw > 20.0f ) { + if ( partHealth[PART_ARM_L] <= 0 ) + { + return NULL; + } + animName = "fire_left"; + } else{ + animName = "fire_forward"; + } + + return animName; +} + +/* +================ +rvMonsterHarvester::PlayRangedAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayRangedAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetRangedAttackAnim( target ); + if ( animName ) + { + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::State_Torso_ClawAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_ClawAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + if ( !PlayMeleeAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ) ) + { + return SRESULT_DONE; + } + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent || !PlayRangedAttackAnim ( enemy.ent->GetEyePosition(), 0 ) ) + { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) + { + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_STAGE ( STAGE_FIRE ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "missile_fire_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "missile_fire_start", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + if ( partHealth[PART_TANK_L] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("l_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("l_hopper_muzzle_flash"), enemy.ent ); + } + if ( partHealth[PART_TANK_R] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("r_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("r_hopper_muzzle_flash"), enemy.ent ); + } + + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_DONE; + } + + PlayAnim ( ANIMCHANNEL_TORSO, "attack_rocket", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "attack_rocket", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_HarvesterDispersal.cpp b/src/game/ai/Monster_HarvesterDispersal.cpp new file mode 100644 index 0000000..79f2a2f --- /dev/null +++ b/src/game/ai/Monster_HarvesterDispersal.cpp @@ -0,0 +1,418 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ +rvMonsterHarvesterDispersal.cpp + +AI for the Harvester on dispersal +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterHarvesterDispersal : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvesterDispersal ); + + rvMonsterHarvesterDispersal ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + rvAIAction actionSprayScream; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + +// Custom actions + virtual bool CheckAction_SprayScream( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_SprayScream ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvesterDispersal ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvesterDispersal ) +END_CLASS + +/* +================ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal +================ +*/ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal ( void ) { +} + +void rvMonsterHarvesterDispersal::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterHarvesterDispersal::Spawn +================ +*/ +void rvMonsterHarvesterDispersal::Spawn ( void ) { + actionSprayScream.Init ( spawnArgs, "action_sprayScream", "State_SprayScream", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterHarvesterDispersal::Save +================ +*/ +void rvMonsterHarvesterDispersal::Save ( idSaveGame *savefile ) const { + actionSprayScream.Save( savefile ); + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); +} + +/* +================ +rvMonsterHarvesterDispersal::Restore +================ +*/ +void rvMonsterHarvesterDispersal::Restore ( idRestoreGame *savefile ) { + actionSprayScream.Restore( savefile ); + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvMonsterHarvesterDispersal::SkipImpulse +===================== +*/ +bool rvMonsterHarvesterDispersal::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +/* +================ +rvMonsterHarvesterDispersal::CanTurn +================ +*/ +bool rvMonsterHarvesterDispersal::CanTurn ( void ) const { + return false; + /* + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); + */ +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( enemy.ent && idAI::CheckAction_RangedAttack( action, animNum ) && IsEnemyRecentlyVisible( ) ); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_SprayScream +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_SprayScream ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + return (!IsEnemyRecentlyVisible()); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckActions +================ +*/ +bool rvMonsterHarvesterDispersal::CheckActions ( void ) { + + // If not moving, try turning in place + /* + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.8f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.8f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + */ + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( idAI::CheckActions() ) { + return true; + } + + if ( PerformAction ( &actionSprayScream, (checkAction_t)&rvMonsterHarvesterDispersal::CheckAction_SprayScream ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterHarvesterDispersal::FilterTactical +================ +*/ +int rvMonsterHarvesterDispersal::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvesterDispersal ) + STATE ( "Torso_RangedAttack", rvMonsterHarvesterDispersal::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ) + STATE ( "State_SprayScream", rvMonsterHarvesterDispersal::State_SprayScream ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent ) + { + return SRESULT_DONE; + } + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_SprayScream ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH, + STAGE_SCREAM, + STAGE_FINISH_SCREAM + }; + if ( parms.stage < STAGE_SCREAM && IsEnemyRecentlyVisible() ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + lookTarget = GetEnemy(); + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) { + return SRESULT_STAGE ( STAGE_SWEEP ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam on + PlayAnim ( ANIMCHANNEL_TORSO, "dispersal_vent", 0 ); + return SRESULT_STAGE ( STAGE_FINISH_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_FINISH_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam off + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/game/ai/Monster_HeavyHoverTank.cpp b/src/game/ai/Monster_HeavyHoverTank.cpp new file mode 100644 index 0000000..c817635 --- /dev/null +++ b/src/game/ai/Monster_HeavyHoverTank.cpp @@ -0,0 +1,557 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +class rvMonsterHeavyHoverTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHeavyHoverTank ); + + rvMonsterHeavyHoverTank ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + +protected: + + enum weaponState_t { + WEAPONSTATE_BLASTER, + WEAPONSTATE_ROCKET, + WEAPONSTATE_MAX + }; + + int blasterAnimIndex; + int shots; + weaponState_t weaponStateCurrent; + weaponState_t weaponStateIdeal; + + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionStrafe; + + jointHandle_t jointHoverEffect; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover; + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + +// virtual const char* GetIdleAnimName ( void ); + +private: + + float strafeSpeed; + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ChangeWeaponState ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHeavyHoverTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHeavyHoverTank ) +END_CLASS + +/* +================ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank +================ +*/ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank ( ) { + weaponStateIdeal = WEAPONSTATE_BLASTER; + weaponStateCurrent = weaponStateIdeal; + + effectDust = NULL; + effectHover = NULL; + + blasterAnimIndex = 0; + shots = 0; + + strafeSpeed = 0; +} + +void rvMonsterHeavyHoverTank::InitSpawnArgsVariables( void ) +{ + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover") ); + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "300" ); +} +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +void rvMonsterHeavyHoverTank::Spawn ( void ) { + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + + InitSpawnArgsVariables(); + + if ( jointHoverEffect != INVALID_JOINT ) { + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + } +} + +/* +================ +rvMonsterHeavyHoverTank::Think +================ +*/ +void rvMonsterHeavyHoverTank::Think ( void ) { + idAI::Think ( ); + + // If thinking we should play an effect on the ground under us + if ( jointHoverEffect != INVALID_JOINT ) { + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 128 units down from the hover effect joint + GetJointWorldTransform ( jointHoverEffect, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * 128.0f, CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + } +} + +/* +================ +rvMonsterHeavyHoverTank::Save +================ +*/ +void rvMonsterHeavyHoverTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( blasterAnimIndex ); + savefile->WriteInt ( shots ); + savefile->WriteInt ( (int)weaponStateCurrent ); + savefile->WriteInt ( (int)weaponStateIdeal ); + + actionRocketAttack.Save ( savefile ); + actionBlasterAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + + effectDust.Save ( savefile ); + effectHover.Save ( savefile ); +} + +/* +================ +rvMonsterHeavyHoverTank::Restore +================ +*/ +void rvMonsterHeavyHoverTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( blasterAnimIndex ); + savefile->ReadInt ( shots ); + savefile->ReadInt ( (int&)weaponStateCurrent ); + savefile->ReadInt ( (int&)weaponStateIdeal ); + + actionRocketAttack.Restore ( savefile ); + actionBlasterAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + + effectDust.Restore ( savefile ); + effectHover.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +void rvMonsterHeavyHoverTank::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + if ( damageScale > 0.0f ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + //push me hard! + float push = idMath::ClampFloat( 250.0f, 500.0f, damageScale*1000.0f ); + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += dir * push; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHeavyHoverTank::OnDeath +================ +*/ +void rvMonsterHeavyHoverTank::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + if ( effectHover ) { + effectHover->Stop ( ); + effectHover = NULL; + } + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterHeavyHoverTank::CheckAction_Strafe +================ +*/ +bool rvMonsterHeavyHoverTank::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +bool rvMonsterHeavyHoverTank::CheckActions ( void ) { + if ( weaponStateIdeal != weaponStateCurrent ) { + PerformAction ( "Torso_ChangeWeaponState", 4 ); + return true; + } + + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterHeavyHoverTank::CheckAction_Strafe ) ) + { + return true; + } + return false; +} + +/* +================ +rvMonsterHeavyHoverTank::OnEnemyChange +================ +*/ +void rvMonsterHeavyHoverTank::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } + + if ( enemy.ent->IsType ( rvVehicle::GetClassType() ) ) { + weaponStateIdeal = WEAPONSTATE_ROCKET; + } +} + +/* +================ +rvMonsterHeavyHoverTank::GetIdleAnimName +================ +*/ +/* +const char* rvMonsterHeavyHoverTank::GetIdleAnimName ( void ) { + if ( weaponStateCurrent == WEAPONSTATE_ROCKET ) { + return "rocket_idle"; + } + + return idAI::GetIdleAnimName ( ); +} +*/ + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHeavyHoverTank ) + STATE ( "Torso_BlasterAttack", rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHeavyHoverTank::State_Torso_RocketAttack ) + STATE ( "Torso_ChangeWeaponState", rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ) + STATE ( "Torso_EvadeLeft", rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterHeavyHoverTank::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterHeavyHoverTank::State_Torso_Strafe ) +END_CLASS_STATES + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_BLASTER; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BlasterAttack", parms.blendFrames ); + return SRESULT_DONE; + } + shots = gameLocal.random.RandomInt ( 8 ) + 4; + blasterAnimIndex = (blasterAnimIndex + 1) % 2; + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_preshoot", blasterAnimIndex + 1 ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_fire", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_postshoot", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_ROCKET; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RocketAttack", parms.blendFrames ); + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ( const stateParms_t& parms ) { + static const char* stateAnims [ WEAPONSTATE_MAX ] [ WEAPONSTATE_MAX ] = { + { NULL, "blaster_to_rocket" }, // WEAPONSTATE_BLASTER + { "rocket_to_blaster", NULL }, // WEAPONSTATE_ROCKET + }; + + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // No anim for that transition? + if ( stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ] == NULL ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ], parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + weaponStateCurrent = weaponStateIdeal; + switch ( weaponStateCurrent ) { + case WEAPONSTATE_ROCKET: + animPrefix = "rocket"; + break; + + default: + animPrefix = ""; + break; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeRight", parms.blendFrames ); + } else { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeLeft", parms.blendFrames ); + } + return SRESULT_DONE; +} diff --git a/src/game/ai/Monster_IronMaiden.cpp b/src/game/ai/Monster_IronMaiden.cpp new file mode 100644 index 0000000..261b7ab --- /dev/null +++ b/src/game/ai/Monster_IronMaiden.cpp @@ -0,0 +1,554 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterIronMaiden : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterIronMaiden ); + + rvMonsterIronMaiden ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual int FilterTactical ( int availableTactical ); + +protected: + + int phaseTime; + jointHandle_t jointBansheeAttack; + int enemyStunTime; + + rvAIAction actionBansheeAttack; + + virtual bool CheckActions ( void ); + + void PhaseOut ( void ); + void PhaseIn ( void ); + + virtual void OnDeath ( void ); + +private: + + // Custom actions + bool CheckAction_BansheeAttack ( rvAIAction* action, int animNum ); + bool PerformAction_PhaseOut ( void ); + bool PerformAction_PhaseIn ( void ); + + // Global States + stateResult_t State_Phased ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_PhaseIn ( const stateParms_t& parms ); + stateResult_t State_Torso_PhaseOut ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttackDone ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_PhaseIn ( const stateParms_t& parms ); + stateResult_t Frame_PhaseOut ( const stateParms_t& parms ); + stateResult_t Frame_BansheeAttack ( const stateParms_t& parms ); + stateResult_t Frame_EndBansheeAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterIronMaiden ); +}; + +CLASS_DECLARATION( idAI, rvMonsterIronMaiden ) +END_CLASS + +/* +================ +rvMonsterIronMaiden::rvMonsterIronMaiden +================ +*/ +rvMonsterIronMaiden::rvMonsterIronMaiden ( void ) { + enemyStunTime = 0; + phaseTime = 0; +} + +void rvMonsterIronMaiden::InitSpawnArgsVariables ( void ) { + // Cache the mouth joint + jointBansheeAttack = animator.GetJointHandle ( spawnArgs.GetString ( "joint_bansheeAttack", "mouth_effect" ) ); +} +/* +================ +rvMonsterIronMaiden::Spawn +================ +*/ +void rvMonsterIronMaiden::Spawn ( void ) { + // Custom actions + actionBansheeAttack.Init ( spawnArgs, "action_bansheeAttack", "Torso_BansheeAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + PlayEffect ( "fx_dress", animator.GetJointHandle ( spawnArgs.GetString ( "joint_laser", "cog_bone" ) ), true ); +} + +/* +================ +rvMonsterIronMaiden::Save +================ +*/ +void rvMonsterIronMaiden::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( phaseTime ); + savefile->WriteInt ( enemyStunTime ); + + actionBansheeAttack.Save ( savefile ); +} + +/* +================ +rvMonsterIronMaiden::Restore +================ +*/ +void rvMonsterIronMaiden::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( phaseTime ); + savefile->ReadInt ( enemyStunTime ); + + actionBansheeAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterIronMaiden::FilterTactical +================ +*/ +int rvMonsterIronMaiden::FilterTactical ( int availableTactical ) { + // When hidden the iron maiden only uses ranged tactical + if ( fl.hidden ) { + availableTactical &= (AITACTICAL_RANGED_BIT | AITACTICAL_TURRET_BIT); + } + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterIronMaiden::CheckAction_BansheeAttack +================ +*/ +bool rvMonsterIronMaiden::CheckAction_BansheeAttack ( rvAIAction* action, int animNum ) { + return CheckAction_RangedAttack ( action, animNum ); +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseIn +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseIn ( void ) { + if ( !phaseTime ) { + return false; + } + + // Must be out for at least 3 seconds + if ( gameLocal.time - phaseTime < 3000 ) { + return false; + } + + // Phase in after 10 seconds or our movement is done + if ( gameLocal.time - phaseTime > 10000 || move.fl.done ) { + // Make sure we arent in something + if ( CanBecomeSolid ( ) ) { + PerformAction ( "Torso_PhaseIn", 4, true ); + return true; + } + } + + return false; +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseOut +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseOut ( void ) { + // Little randomization + if ( gameLocal.random.RandomFloat ( ) > 0.5f ) { + return false; + } + if ( !enemyStunTime || gameLocal.time - enemyStunTime > 1500 ) { + return false; + } + + PerformAction ( "Torso_PhaseOut", 4, true ); + return true; +} + +/* +================ +rvMonsterIronMaiden::CheckActions +================ +*/ +bool rvMonsterIronMaiden::CheckActions ( void ) { + // When phased the only available action is phase in + if ( phaseTime ) { + if ( PerformAction_PhaseIn ( ) ) { + return true; + } + return false; + } + + if ( PerformAction ( &actionBansheeAttack, (checkAction_t)&rvMonsterIronMaiden::CheckAction_BansheeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction_PhaseOut ( ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterIronMaiden::PhaseOut +================ +*/ +void rvMonsterIronMaiden::PhaseOut ( void ) { + if ( phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + Hide ( ); + + move.fl.allowHiddenMove = true; + + ProcessEvent ( &EV_BecomeNonSolid ); + + StopMove ( MOVE_STATUS_DONE ); + SetState ( "State_Phased" ); + + // Move away from here, to anywhere + MoveOutOfRange ( this, 500.0f, 150.0f ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); + + phaseTime = gameLocal.time; +} + +/* +================ +rvMonsterIronMaiden::PhaseIn +================ +*/ +void rvMonsterIronMaiden::PhaseIn ( void ) { + if ( !phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + ProcessEvent ( &AI_BecomeSolid ); + + Show ( ); + +// PlayEffect ( "fx_dress", animator.GetJointHandle ( "cog_bone" ), true ); + + phaseTime = 0; + // Wait for the action that started the phase in to finish, then go back to combat + SetState ( "Wait_Action" ); + PostState ( "State_Combat" ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); +} + +/* +================ +rvMonsterIronMaiden::OnDeath +================ +*/ +void rvMonsterIronMaiden::OnDeath ( void ) { + StopSound ( SND_CHANNEL_ITEM, false ); + + // Stop looping effects + StopEffect ( "fx_banshee" ); + StopEffect ( "fx_dress" ); + + idAI::OnDeath( ); +} + +/* +================ +rvMonsterIronMaiden::GetDebugInfo +================ +*/ +void rvMonsterIronMaiden::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_BansheeAttack", aiActionStatusString[actionBansheeAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterIronMaiden ) + STATE ( "State_Phased", rvMonsterIronMaiden::State_Phased ) + + STATE ( "Torso_PhaseOut", rvMonsterIronMaiden::State_Torso_PhaseOut ) + STATE ( "Torso_PhaseIn", rvMonsterIronMaiden::State_Torso_PhaseIn ) + STATE ( "Torso_BansheeAttack", rvMonsterIronMaiden::State_Torso_BansheeAttack ) + STATE ( "Torso_BansheeAttackDone", rvMonsterIronMaiden::State_Torso_BansheeAttackDone ) + + STATE ( "Frame_PhaseIn", rvMonsterIronMaiden::Frame_PhaseIn ) + STATE ( "Frame_PhaseOut", rvMonsterIronMaiden::Frame_PhaseOut ) + STATE ( "Frame_BansheeAttack", rvMonsterIronMaiden::Frame_BansheeAttack ) + STATE ( "Frame_EndBansheeAttack", rvMonsterIronMaiden::Frame_EndBansheeAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterIronMaiden::State_Phased +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Phased ( const stateParms_t& parms ) { + // If done moving and cant become solid here, move again + if ( move.fl.done ) { + if ( !CanBecomeSolid ( ) ) { + MoveOutOfRange ( this, 300.0f, 150.0f ); + } + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Make sure we are checking actions if we have no tactical move + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseIn ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + STAGE_PHASE, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_in", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseOut ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_out", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "banshee", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BansheeAttackDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttackDone + +To ensure the movement is enabled after the banshee attack and that the effect is stopped this state +will be posted to be run after the banshe attack finishes. +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttackDone ( const stateParms_t& parms ) { + Frame_EndBansheeAttack ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterIronMaiden::Frame_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseIn ( const stateParms_t& parms ) { + PhaseIn ( ); + return SRESULT_OK; +} + + +/* +================ +rvMonsterIronMaiden::Frame_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseOut ( const stateParms_t& parms ) { + PhaseOut ( ); + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_BansheeAttack ( const stateParms_t& parms ) { + idVec3 origin; + idMat3 axis; + idEntity* entities[ 1024 ]; + int count; + int i; + + // Get mouth origin + GetJointWorldTransform ( jointBansheeAttack, gameLocal.time, origin, axis ); + + // Find all entities within the banshee attacks radius + count = gameLocal.EntitiesWithinRadius ( origin, actionBansheeAttack.maxRange, entities, 1024 ); + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Has to be within fov + if ( !CheckFOV ( ent->GetEyePosition ( ) ) ) { + continue; + } + + // Do some damage + idVec3 dir; + dir = origin = ent->GetEyePosition ( ); + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_banshee_damage" ), 1.0f, 0 ); + + // Cache the last time we stunned our own enemy for the phase out + if ( ent == enemy.ent ) { + enemyStunTime = gameLocal.time; + } + } + + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_EndBansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_EndBansheeAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_banshee" ); + return SRESULT_OK; +} diff --git a/src/game/ai/Monster_LightTank.cpp b/src/game/ai/Monster_LightTank.cpp new file mode 100644 index 0000000..8776a62 --- /dev/null +++ b/src/game/ai/Monster_LightTank.cpp @@ -0,0 +1,633 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterLightTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterLightTank ); + + rvMonsterLightTank ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual int GetDamageForLocation ( int damage, int location ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + int flamethrowerHealth; + int chargeDebounce; + void DestroyFlamethrower ( void ); + +private: + + int standingMeleeNoAttackTime; + bool damaged; +// bool damagedMove; + int powerUpStartTime; + + rvAIAction actionFlameThrower; + rvAIAction actionPowerUp; + rvAIAction actionChargeAttack; + + bool CheckAction_PowerUp ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_FlameThrower ( const stateParms_t& parms ); + stateResult_t State_Torso_FlameThrowerThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_PowerUp ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after flamethrower is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterLightTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterLightTank ) +END_CLASS + +/* +================ +rvMonsterLightTank::rvMonsterLightTank +================ +*/ +rvMonsterLightTank::rvMonsterLightTank ( void ) { + damaged = false; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterLightTank::Spawn +================ +*/ +void rvMonsterLightTank::Spawn ( void ) { +// damagedThreshold = spawnArgs.GetInt ( "health_damagedThreshold" ); + flamethrowerHealth = spawnArgs.GetInt ( "flamethrowerHealth", "160" ); + chargeDebounce = 0; + + actionFlameThrower.Init ( spawnArgs, "action_flameThrower", "Torso_FlameThrower", AIACTIONF_ATTACK ); + actionPowerUp.Init ( spawnArgs, "action_powerup", "Torso_PowerUp", 0 ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", NULL, AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterLightTank::Save +================ +*/ +void rvMonsterLightTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( flamethrowerHealth ); + savefile->WriteInt( chargeDebounce ); + + savefile->WriteBool( damaged ); +// savefile->WriteBool( damagedMove ); + savefile->WriteInt( powerUpStartTime ); + + actionFlameThrower.Save( savefile ); + actionPowerUp.Save( savefile ); + actionChargeAttack.Save( savefile ); + mPostWeaponDestroyed.Save( savefile ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::Restore +================ +*/ +void rvMonsterLightTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( flamethrowerHealth ); + savefile->ReadInt( chargeDebounce ); + + savefile->ReadBool( damaged ); +// savefile->ReadBool( damagedMove ); + savefile->ReadInt( powerUpStartTime ); + + actionFlameThrower.Restore( savefile ); + actionPowerUp.Restore( savefile ); + actionChargeAttack.Restore( savefile ); + mPostWeaponDestroyed.Restore( savefile ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::FilterTactical +================ +*/ +int rvMonsterLightTank::FilterTactical ( int availableTactical ) { + if ( flamethrowerHealth > 0 ) { + // Only let the light tank use ranged tactical when he is really far from his enemy + if ( !enemy.range || enemy.range < combat.attackRange[1] ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + if ( chargeDebounce > gameLocal.GetTime() ) + {//don't charge again any time soon + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterLightTank::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterLightTank::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + break; + + default: + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +================ +rvMonsterLightTank::UpdateRunStatus +================ +*/ +bool rvMonsterLightTank::UpdateRunStatus ( void ) { + // If rushing, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + move.fl.idealRunning = true; + } + else + { + move.fl.idealRunning = false; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterLightTank::DestroyFlamethrower +================ +*/ +void rvMonsterLightTank::DestroyFlamethrower ( void ) { + + StopEffect ( "fx_flame_muzzle" ); + //HideSurface ( "models/monsters/light_tank/flamethrower" ); + //GetAFPhysics()->GetBody ( "b_right_forearm" )->SetClipMask ( 0 ); + animator.CollapseJoint( animator.GetJointHandle( "r_smallShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_bigShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_gun_effect" ), animator.GetJointHandle( "r_elbo" ) ); + flamethrowerHealth = -1; + + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; +// flamethrowerDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + + PlayEffect ( "fx_destroy_arm", animator.GetJointHandle("r_elbo") ); + PlayEffect ( "fx_destroy_arm_trail", animator.GetJointHandle("r_elbo"), true ); + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "damaged"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + chargeDebounce = 0; + damaged = true; + animPrefix = "damage"; + + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + combat.tacticalMaskAvailable &= ~(AITACTICAL_RANGED_BITS); + + actionMeleeAttack.timer.Reset( actionTime ); + actionChargeAttack.timer.Reset( actionTime ); + + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +===================== +rvMonsterLightTank::Pain +===================== +*/ +bool rvMonsterLightTank::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool didPain = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( move.fl.moving && move.fl.running ) + { + painAnim = "pain_charge"; + } + return didPain; +} + /* +===================== +rvMonsterLightTank::GetDamageForLocation +===================== +*/ +int rvMonsterLightTank::GetDamageForLocation( int damage, int location ) { + // If the flamethrower was hit only do damage to it + if( !damaged && !aifl.dead ) + { + if ( idStr::Icmp ( GetDamageGroup ( location ), "flamethrower" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( flamethrowerHealth > 0 ){ + flamethrowerHealth -= damage; + if ( flamethrowerHealth <= 0 ) { + DestroyFlamethrower(); + } + } + return 0; + } + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterLightTank::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void rvMonsterLightTank::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( !damaged ) + { + if ( victim == GetEnemy() && inflictor == this ) + { + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//okay, get out of melee state for now + chargeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt(3000) + 3000; + } + } + } + + idAI::DamageFeedback( victim, inflictor, damage ); +} + +/* +============ +rvMonsterLightTank::OnStopMoving +============ +*/ +void rvMonsterLightTank::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionFlameThrower.timer.Clear( actionTime ); + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//so we don't stand there and look stupid + actionMeleeAttack.timer.Clear( actionTime ); + actionChargeAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterLightTank::CheckAction_PowerUp +================ +*/ +bool rvMonsterLightTank::CheckAction_PowerUp ( rvAIAction* action, int animNum ) +{ + if ( !damaged && combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + if ( health > 20 || gameLocal.time - pain.lastTakenTime < 500 ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeLeft( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeRight +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeRight( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterLightTank::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + if ( damaged || idStr::Icmp( "run", animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimName() ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckActions +================ +*/ +bool rvMonsterLightTank::CheckActions ( void ) { + if ( PerformAction ( &actionFlameThrower, (checkAction_t)&idAI::CheckAction_RangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterLightTank::CheckAction_ChargeAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + if ( PerformAction ( &actionPowerUp, (checkAction_t)&rvMonsterLightTank::CheckAction_PowerUp ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterLightTank ) + STATE ( "State_Killed", rvMonsterLightTank::State_Killed ) + + STATE ( "Torso_FlameThrower", rvMonsterLightTank::State_Torso_FlameThrower ) + STATE ( "Torso_FlameThrowerThink", rvMonsterLightTank::State_Torso_FlameThrowerThink ) + STATE ( "Torso_Pain", rvMonsterLightTank::State_Torso_Pain ) + STATE ( "Torso_RangedAttack", rvMonsterLightTank::State_Torso_RangedAttack ) + STATE ( "Torso_PowerUp", rvMonsterLightTank::State_Torso_PowerUp ) + +END_CLASS_STATES + +/* +================ +rvMonsterLightTank::State_Killed +================ +*/ +stateResult_t rvMonsterLightTank::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_destroy_arm_trail" ); + StopEffect ( "fx_flame_muzzle" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrower +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrower ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Flame effect + PlayEffect ( "fx_flame_muzzle", animator.GetJointHandle ( "gun_effect" ), true ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "flamethrower", parms.blendFrames ); + + // Delay start the flame thrower think to ensure he flames for a minimum time + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FlameThrowerThink", 0, 500 ); + + return SRESULT_DONE; +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrowerThink +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrowerThink ( const stateParms_t& parms ) { + if ( !enemy.fl.inFov || AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + StopEffect ( "fx_flame_muzzle" ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterLightTank::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_Pain ( const stateParms_t& parms ) { + + StopEffect ( "fx_flame_muzzle" ); + + // Default pain animation + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( !move.fl.moving || !FacingIdeal() ) { + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_megaattack", parms.blendFrames ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", parms.blendFrames ); + } + + return SRESULT_STAGE ( STAGE_FINISH ); + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterLightTank::State_Torso_PowerUp +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_PowerUp ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + //fl.takedamage = false; + powerUpStartTime = gameLocal.GetTime(); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_start", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayCycle( ANIMCHANNEL_TORSO, "powerup_loop", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + health++; + if ( health >= spawnArgs.GetInt( "health" ) + || gameLocal.GetTime() - powerUpStartTime > 3875 ) + {//full health or been charging up for 3 full anim loops + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_NetworkGuardian.cpp b/src/game/ai/Monster_NetworkGuardian.cpp new file mode 100644 index 0000000..8ca4640 --- /dev/null +++ b/src/game/ai/Monster_NetworkGuardian.cpp @@ -0,0 +1,845 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterNetworkGuardian : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterNetworkGuardian ); + + rvMonsterNetworkGuardian ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + + int shots; + int landTime; + + int flagFlying; + + float strafeSpeed; + + //this flag allows the AI to control when it takes off and lands. + bool flagAutopilot; + + int battleStage; + + enum { + FLY_NONE = 0, + FLY_TRANSITION, + FLY_FLYING, + }; + +private: + + rvAIAction actionShotgunRocketAttack; + rvAIAction actionFlyingRangedAttack; + rvAIAction actionFlyingSweepAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionBlasterSweepGround; + rvAIAction actionBlasterAttack; + rvAIAction actionMIRVAttack; + + + + stateResult_t State_Wait_Flying ( const stateParms_t& parms ); + +// stateResult_t State_Dead ( const stateParms_t& parms ); + + // walking melee attacks + + // walking ranged attacks + stateResult_t State_Torso_ShotgunRocket ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterSweepGround ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MIRVAttack ( const stateParms_t& parms ); + + //flying evades + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + //flying ranged attacks + stateResult_t State_Torso_FlyingRanged ( const stateParms_t& parms ); + stateResult_t State_Torso_FlyingSweep ( const stateParms_t& parms ); + + // flying anims + stateResult_t State_Torso_LiftOff ( const stateParms_t& parms ); + stateResult_t State_Torso_Fall ( const stateParms_t& parms ); + // walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + + //force the NG into walking mode. This does not play the landing anim, and will make him fall from the sky. + void Event_ForceWalkMode( void ); + + //These commands change the NG's state, but do so through states and animations. + void Event_ForceLanding( void ); + void Event_ForceTakeoff( void ); + + //toggles the NG between AI controlled flight and script controlled flight + void Event_AllowAutopilot( float f); + + //for staged combat, sets the int value of the battle stage + void Event_SetBattleStage( float f); + + CLASS_STATES_PROTOTYPE ( rvMonsterNetworkGuardian ); +}; + +const idEventDef EV_ForceWalkMode( "forceWalkMode" ); +const idEventDef EV_ForceLanding( "forceLanding" ); +const idEventDef EV_ForceTakeoff( "forceTakeoff" ); +const idEventDef EV_AllowAutopilot( "allowAutopilot", "f" ); +const idEventDef EV_SetBattleStage( "setBattleStage", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterNetworkGuardian ) + EVENT( EV_ForceWalkMode, rvMonsterNetworkGuardian::Event_ForceWalkMode ) + EVENT( EV_ForceLanding, rvMonsterNetworkGuardian::Event_ForceLanding ) + EVENT( EV_ForceTakeoff, rvMonsterNetworkGuardian::Event_ForceTakeoff ) + EVENT( EV_AllowAutopilot, rvMonsterNetworkGuardian::Event_AllowAutopilot ) + EVENT( EV_SetBattleStage, rvMonsterNetworkGuardian::Event_SetBattleStage ) + +END_CLASS + +/* +================ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian +================ +*/ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian ( ) { + shots = 0; + landTime = 0; + flagFlying = FLY_NONE; + battleStage = 1; + strafeSpeed = 0; + +} + +/* +================ +rvMonsterNetworkGuardian::Spawn +================ +*/ +void rvMonsterNetworkGuardian::Spawn ( void ) { + + disablePain = true; + flagAutopilot = false; + + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "500" ); + + actionShotgunRocketAttack.Init ( spawnArgs, "action_ShotgunRocket", "Torso_ShotgunRocket_Attack", AIACTIONF_ATTACK ); + actionFlyingRangedAttack.Init( spawnArgs, "action_FlyingRangedAttack", "Torso_FlyingRanged_Attack", AIACTIONF_ATTACK ); + actionFlyingSweepAttack.Init( spawnArgs, "action_blasterSweepAirAttack", "Torso_FlyingSweep_Attack", AIACTIONF_ATTACK ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterSweepGround.Init( spawnArgs, "action_blasterSweepGroundAttack", "Torso_BlasterSweepGround_Attack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init( spawnArgs, "action_blasterAttack", "Torso_Blaster_Attack", AIACTIONF_ATTACK ); + actionMIRVAttack.Init( spawnArgs, "action_MIRVAttack", "Torso_MIRV_Attack", AIACTIONF_ATTACK ); + +} + + +/* +================ +rvMonsterNetworkGuardian::Save +================ +*/ +void rvMonsterNetworkGuardian::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( shots ); + savefile->WriteInt( landTime ); + savefile->WriteInt( flagFlying ); + savefile->WriteInt( battleStage ); + savefile->WriteBool( flagAutopilot ); + savefile->WriteFloat( strafeSpeed ); + + actionShotgunRocketAttack.Save( savefile ); + actionFlyingRangedAttack.Save( savefile ); + actionFlyingSweepAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionBlasterSweepGround.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionMIRVAttack.Save( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::Restore +================ +*/ +void rvMonsterNetworkGuardian::Restore ( idRestoreGame *savefile ) +{ + savefile->ReadInt( shots ); + savefile->ReadInt( landTime ); + savefile->ReadInt( flagFlying ); + savefile->ReadInt( battleStage ); + savefile->ReadBool( flagAutopilot ); + savefile->ReadFloat( strafeSpeed ); + + actionShotgunRocketAttack.Restore( savefile ); + actionFlyingRangedAttack.Restore( savefile ); + actionFlyingSweepAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionBlasterSweepGround.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionMIRVAttack.Restore( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::CheckActions +================ +*/ +bool rvMonsterNetworkGuardian::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + //if the flight is transitioning, do nothing + if( flagFlying == FLY_TRANSITION ) { + return false; + } + + //this is the autopilot section. + if( flagAutopilot ) { + // If he's been on the ground long enough, fly... + if ( move.moveType == MOVETYPE_ANIM && move.fl.onGround && !flagFlying && gameLocal.time > landTime ) + { + PostState ( "Wait_Flying" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + return true; + } + else if ( move.moveType == MOVETYPE_FLY && gameLocal.time > landTime ) + { + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + return true; + } + } + + //Normal actions here ----------- + + //if he's close enough for melee, use melee + if( flagFlying == FLY_NONE) { + if( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + //check for ranged attacks on the ground + if( flagFlying == FLY_NONE ) { + + switch (battleStage) { + + case 1: + if( PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + case 2: + if( PerformAction ( &actionMIRVAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + default: + gameLocal.Error("Bad battleStage '%d' set for Network Guardian.", battleStage); + break; + } + + } + //airborne attack actions + if( (flagFlying == FLY_FLYING) ) { + + if( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionFlyingRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionFlyingSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ){ + return true; + } + } + + //No. + //return idAI::CheckActions ( ); + return false; +} + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterNetworkGuardian::Event_ForceWalkMode +================ +*/ +// forces NG to obey gravity and immediately switch to walking mode. +void rvMonsterNetworkGuardian::Event_ForceWalkMode( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_NONE; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceLanding +================ +*/ +// forces NG play his landing animation. He will not just fall from the sky. +void rvMonsterNetworkGuardian::Event_ForceLanding( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + //PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceTakeoff +================ +*/ +// forces NG to take off and fly. +void rvMonsterNetworkGuardian::Event_ForceTakeoff( void ) { + + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + move.fl.allowDirectional = true; + SetState ( "Wait_Flying" ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_AllowAutoPilot +================ +*/ +// toggles the AI autoPilot for deciding when to fly and land. +void rvMonsterNetworkGuardian::Event_AllowAutopilot( float f ) { + + flagAutopilot = f ? true : false; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_SetBattleStage +================ +*/ +// sets the current battle stage. Each stage has different behaviors. +void rvMonsterNetworkGuardian::Event_SetBattleStage( float f ) { + + battleStage = f; + +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterNetworkGuardian ) + STATE ( "Wait_Flying", rvMonsterNetworkGuardian::State_Wait_Flying ) + STATE ( "State_Dead", rvMonsterNetworkGuardian::State_Dead ) + + STATE ( "Torso_ShotgunRocket_Attack", rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ) + STATE ( "Torso_BlasterSweepGround_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ) + STATE ( "Torso_Blaster_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterAttack ) + STATE ( "Torso_MIRV_Attack", rvMonsterNetworkGuardian::State_Torso_MIRVAttack) + + STATE ( "Torso_EvadeLeft", rvMonsterNetworkGuardian::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterNetworkGuardian::State_Torso_EvadeRight ) + + STATE ( "Torso_FlyingRanged_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingRanged ) + STATE ( "Torso_FlyingSweep_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingSweep ) + + STATE ( "Torso_LiftOff", rvMonsterNetworkGuardian::State_Torso_LiftOff ) + STATE ( "Torso_Fall", rvMonsterNetworkGuardian::State_Torso_Fall ) + + STATE ( "Torso_TurnRight90", rvMonsterNetworkGuardian::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ) + +END_CLASS_STATES + + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterNetworkGuardian::State_Torso_ShotgunRocket +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shotgunRocket_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_grd", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingSweep +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingSweep ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_air", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingRanged +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingRanged ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "flyingRanged_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_MIRVAttack +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_MIRVAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_vert", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterNetworkGuardian::State_Wait_Flying +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Wait_Flying ( const stateParms_t& parms ) { + if ( move.moveType == MOVETYPE_ANIM ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterNetworkGuardian::State_Dead +================ +*/ +/* +stateResult_t rvMonsterNetworkGuardian::State_Dead ( const stateParms_t& parms ) { + return SRESULT_DONE; +}*/ + +/* +================ +rvMonsterNetworkGuardian::State_Torso_LiftOff +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_LiftOff ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.noGravity = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "liftoff", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + SetMoveType ( MOVETYPE_FLY ); + landTime = gameLocal.time + DelayTime ( 5000, 10000 ); + animPrefix = "fly"; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + flagFlying = FLY_FLYING; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_Fall +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_Fall ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + // we've landed! determine the next fly time + landTime = gameLocal.time + DelayTime ( 10000, 15000 ); + flagFlying = FLY_NONE; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnRight90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnLeft90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_BlasterAttack +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( (flagFlying != FLY_NONE ) && move.fl.moving ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 12 ) + 8) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; + +} diff --git a/src/game/ai/Monster_RepairBot.cpp b/src/game/ai/Monster_RepairBot.cpp new file mode 100644 index 0000000..9c32c7d --- /dev/null +++ b/src/game/ai/Monster_RepairBot.cpp @@ -0,0 +1,399 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class repairBotArm_t { +public: + jointHandle_t joint; + int repairTime; + bool repairing; + rvClientEffectPtr effectRepair; + rvClientEffectPtr effectImpact; + + int periodicEndTime; + + repairBotArm_t () { + periodicEndTime = -1; + } + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvMonsterRepairBot : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterRepairBot ); + + rvMonsterRepairBot ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + int repairEndTime; + float repairEffectDist; + + repairBotArm_t armLeft; + repairBotArm_t armRight; + +private: + + void UpdateRepairs ( repairBotArm_t& arm ); + void StopRepairs ( repairBotArm_t& arm ); + + // Leg states + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_Repair ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_RepairDone ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterRepairBot ); +}; + +CLASS_DECLARATION( idAI, rvMonsterRepairBot ) +END_CLASS + +/* +================ +rvMonsterRepairBot::rvMonsterRepairBot +================ +*/ +rvMonsterRepairBot::rvMonsterRepairBot ( ) { +} + +void rvMonsterRepairBot::InitSpawnArgsVariables( void ) +{ + armLeft.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_left", "l_fx" ) ); + armRight.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_right", "r_fx" ) ); + repairEffectDist = spawnArgs.GetFloat( "repairEffectDist", "64" ); +} +/* +================ +rvMonsterRepairBot::Spawn +================ +*/ +void rvMonsterRepairBot::Spawn ( void ) { + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::CheckActions +================ +*/ +bool rvMonsterRepairBot::CheckActions ( void ) { + return false; +} + +/* +================ +rvMonsterRepairBot::OnDeath +================ +*/ +void rvMonsterRepairBot::OnDeath ( void ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), viewAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterRepairBot::Save +================ +*/ +void rvMonsterRepairBot::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( repairEndTime ); + + armLeft.Save ( savefile ); + armRight.Save ( savefile ); +} + +/* +================ +rvMonsterRepairBot::Restore +================ +*/ +void rvMonsterRepairBot::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( repairEndTime ); + + armLeft.Restore ( savefile ); + armRight.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::Think +================ +*/ +void rvMonsterRepairBot::Think ( void ) { + idAI::Think ( ); + + // Update repair effects? (dont worry about stopping them, the state ending will do so) + UpdateRepairs ( armLeft ); + UpdateRepairs ( armRight ); +} + +/* +================ +rvMonsterRepairBot::UpdateRepairs +================ +*/ +void rvMonsterRepairBot::UpdateRepairs ( repairBotArm_t& arm ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + if ( arm.joint == INVALID_JOINT ) { + return; + } + + if ( gameLocal.time > repairEndTime ) { + StopRepairs ( arm ) ; + return; + } + + // If the repair time has been crossed we need to start/stop the repairs + if ( gameLocal.time > arm.repairTime ) { + if ( arm.repairing ) { + StopRepairs ( arm ); + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 500 ); + arm.repairing = false; + } else { + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 2500 ); + arm.repairing = true; + } + } + + if ( !arm.repairing ) { + return; + } + + // Left repair effect + GetJointWorldTransform ( arm.joint, gameLocal.time, origin, axis ); + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * repairEffectDist, MASK_SHOT_RENDERMODEL, this ); + + if ( tr.fraction >= 1.0f ) { + StopRepairs ( arm ); + } else { + // Start the repair effect if not already started + if ( !arm.effectRepair ) { + arm.effectRepair = PlayEffect ( "fx_repair", arm.joint, true ); + } + // If the repair effect is running then set its end origin + if ( arm.effectRepair ) { + arm.effectRepair->SetEndOrigin ( tr.endpos ); + } + // Start the impact effect + if ( !arm.effectImpact ) { + arm.effectImpact = PlayEffect ( "fx_repair_impact", tr.endpos, tr.c.normal.ToMat3(), true ); + + } else { + // Calculate the local origin and axis from the given globals + idVec3 localOrigin = (tr.endpos - renderEntity.origin) * renderEntity.axis.Transpose ( ); + idMat3 localAxis = tr.c.normal.ToMat3 ();// * renderEntity.axis.Transpose(); + arm.effectImpact->SetOrigin ( localOrigin ); + arm.effectImpact->SetAxis ( localAxis ); + } + + if( gameLocal.GetTime() > arm.periodicEndTime ) {// FIXME: Seems dumb to keep banging on this if the fx isn't defined. + gameLocal.PlayEffect( spawnArgs, "fx_repair_impact_periodic", tr.endpos, tr.c.normal.ToMat3() ); + arm.periodicEndTime = gameLocal.GetTime() + SEC2MS( rvRandom::flrand(spawnArgs.GetVec2("impact_fx_delay_range", "1 1")) ); + } + } +} + +/* +================ +rvMonsterRepairBot::StopRepairs +================ +*/ +void rvMonsterRepairBot::StopRepairs ( repairBotArm_t& arm ) { + if ( arm.effectImpact ) { + arm.effectImpact->Stop ( ); + arm.effectImpact = NULL; + } + if ( arm.effectRepair ) { + arm.effectRepair->Stop ( ); + arm.effectRepair = NULL; + } + + arm.periodicEndTime = -1; +} + +/* +================ +rvMonsterRepairBot::GetDebugInfo +================ +*/ +void rvMonsterRepairBot::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterRepairBot ) + STATE ( "Legs_Move", rvMonsterRepairBot::State_Legs_Move ) + STATE ( "TorsoAction_Repair", rvMonsterRepairBot::State_TorsoAction_Repair ) + STATE ( "TorsoAction_RepairDone", rvMonsterRepairBot::State_TorsoAction_RepairDone ) +END_CLASS_STATES + +/* +================ +rvMonsterRepairBot::State_Legs_Move +================ +*/ +stateResult_t rvMonsterRepairBot::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_MOVE, + STAGE_MOVE_WAIT, + STAGE_STOP, + STAGE_STOP_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim ( ANIMCHANNEL_LEGS, "idle_to_run", 4 ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + return SRESULT_STAGE ( STAGE_MOVE ); + } + return SRESULT_WAIT; + + case STAGE_MOVE: + PlayCycle ( ANIMCHANNEL_LEGS, "run", 4 ); + return SRESULT_STAGE ( STAGE_MOVE_WAIT ); + + case STAGE_MOVE_WAIT: + if ( !move.fl.moving || !CanMove() ) { + return SRESULT_STAGE ( STAGE_STOP ); + } + return SRESULT_WAIT; + + case STAGE_STOP: + PlayAnim ( ANIMCHANNEL_LEGS, "run_to_idle", 4 ); + return SRESULT_STAGE ( STAGE_STOP_WAIT ); + + case STAGE_STOP_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_Repair +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_Repair ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_REPAIR, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "repair", 4 ); + repairEndTime = gameLocal.time + SEC2MS(scriptedActionEnt->spawnArgs.GetInt ( "duration", "5" ) ); + PostAnimState ( ANIMCHANNEL_TORSO, "TorsoAction_RepairDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE(STAGE_REPAIR); + + case STAGE_REPAIR: + if ( gameLocal.time > repairEndTime ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_RepairDone +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_RepairDone ( const stateParms_t& parms ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + + return SRESULT_DONE; +} + +/* +================ +repairBotArm_t::Save +================ +*/ +void repairBotArm_t::Save ( idSaveGame* savefile ) const { + savefile->WriteInt ( repairTime ); + savefile->WriteBool ( repairing ); + effectRepair.Save ( savefile ); + effectImpact.Save ( savefile ); + + savefile->WriteInt ( periodicEndTime ); +} + +/* +================ +repairBotArm_t::Restore +================ +*/ +void repairBotArm_t::Restore ( idRestoreGame* savefile ) { + savefile->ReadInt ( repairTime ); + savefile->ReadBool ( repairing ); + effectRepair.Restore ( savefile ); + effectImpact.Restore ( savefile ); + + savefile->ReadInt ( periodicEndTime ); +} diff --git a/src/game/ai/Monster_Scientist.cpp b/src/game/ai/Monster_Scientist.cpp new file mode 100644 index 0000000..b63e776 --- /dev/null +++ b/src/game/ai/Monster_Scientist.cpp @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterScientist : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterScientist ); + + rvMonsterScientist ( void ); + + void Spawn ( void ); + + virtual void OnDeath ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterScientist ); +}; + +CLASS_DECLARATION( idAI, rvMonsterScientist ) +END_CLASS + +/* +================ +rvMonsterScientist::rvMonsterScientist +================ +*/ +rvMonsterScientist::rvMonsterScientist ( void ) { +} + +/* +================ +rvMonsterScientist::Spawn +================ +*/ +void rvMonsterScientist::Spawn ( void ) { + PlayEffect ( "fx_fly", animator.GetJointHandle ( "effects_bone" ), true ); +} + +/* +================ +rvMonsterScientist::OnDeath +================ +*/ +void rvMonsterScientist::OnDeath ( void ) { + StopEffect ( "fx_fly" ); + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterScientist::GetDebugInfo +================ +*/ +void rvMonsterScientist::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterScientist ) +END_CLASS_STATES diff --git a/src/game/ai/Monster_Sentry.cpp b/src/game/ai/Monster_Sentry.cpp new file mode 100644 index 0000000..6588823 --- /dev/null +++ b/src/game/ai/Monster_Sentry.cpp @@ -0,0 +1,435 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSentry : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSentry ); + + rvMonsterSentry ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + rvAIAction actionBlasterAttack; + rvAIAction actionKamakaziAttack; + rvAIAction actionCircleStrafe; + + int shots; + int minShots; + int maxShots; + int kamakaziHealth; + int strafeTime; + bool strafeRight; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual void OnDeath ( void ); + + void Explode ( bool force = false ); + +private: + + int nextChatterTime; + + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_KamakaziAttack ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Action_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSentry ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSentry ) +END_CLASS + +/* +================ +rvMonsterSentry::rvMonsterSentry +================ +*/ +rvMonsterSentry::rvMonsterSentry ( void ) { + strafeTime = 0; + strafeRight = false; +} + +void rvMonsterSentry::InitSpawnArgsVariables( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); +} +/* +================ +rvMonsterSentry::Spawn +================ +*/ +void rvMonsterSentry::Spawn ( void ) { + // Custom actions + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionKamakaziAttack.Init ( spawnArgs, "action_kamakaziAttack", "Torso_KamakaziAttack", AIACTIONF_ATTACK ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "State_Action_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + kamakaziHealth = spawnArgs.GetInt ( "kamakazi_Health", va("%d", health / 2) ); + nextChatterTime = 0; +} + +/* +================ +rvMonsterSentry::Save +================ +*/ +void rvMonsterSentry::Save ( idSaveGame *savefile ) const { + actionBlasterAttack.Save( savefile ); + actionKamakaziAttack.Save( savefile ); + actionCircleStrafe.Save( savefile ); + + savefile->WriteInt( shots ); + savefile->WriteInt( kamakaziHealth ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteInt ( nextChatterTime ); +} + +/* +================ +rvMonsterSentry::Restore +================ +*/ +void rvMonsterSentry::Restore ( idRestoreGame *savefile ) { + actionBlasterAttack.Restore( savefile ); + actionKamakaziAttack.Restore( savefile ); + actionCircleStrafe.Restore( savefile ); + + savefile->ReadInt( shots ); + savefile->ReadInt( kamakaziHealth ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadInt ( nextChatterTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSentry::Pain +================ +*/ +bool rvMonsterSentry::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( kamakaziHealth > 0 && health < kamakaziHealth ) { + kamakaziHealth = 0; + move.speed = move.fly_speed = spawnArgs.GetFloat ( "kamakazi_fly_speed", va("%g", move.fly_speed ) ); + move.turnRate = spawnArgs.GetFloat ( "kamakazi_turn_rate", va("%g", move.turnRate ) ); + move.fly_pitch_max = spawnArgs.GetFloat ( "kamakazi_fly_pitch_max" ); + move.fly_pitch_scale = spawnArgs.GetFloat ( "kamakazi_fly_pitch_scale" ); + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterSentry::Think +================ +*/ +void rvMonsterSentry::Think ( void ) { + // Explode whenever we hit something + if ( kamakaziHealth == 0 && physicsObj.GetSlideMoveEntity () ) { + Explode ( ); + } else { + /* + if ( nextChatterTime < gameLocal.GetTime() ) { + if ( GetEnemy() ) { + Speak( "lipsync_chatter_combat" ); + nextChatterTime = speakTime + 2000 + gameLocal.random.RandomInt( 2000 ); + } else { + Speak( "lipsync_chatter_idle" ); + nextChatterTime = speakTime + 3000 + gameLocal.random.RandomInt( 3000 ); + } + } + */ + + idAI::Think ( ); + } +} + +/* +================ +rvMonsterSentry::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterSentry::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterSentry::CheckActions +================ +*/ +bool rvMonsterSentry::CheckActions ( void ) { + if ( kamakaziHealth != 0 ) { + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterSentry::CheckAction_CircleStrafe ) ) { + return true; + } + } else { + if ( PerformAction ( &actionKamakaziAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterSentry::FilterTactical +================ +*/ +int rvMonsterSentry::FilterTactical ( int availableTactical ) { + // Kamakazi Health will be set to 0 when its time to kamakazi + if ( !kamakaziHealth ) { + return availableTactical & (AITACTICAL_MELEE_BIT|AITACTICAL_TURRET_BIT); + // No rush when not in kamakazi mode + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterSentry::OnDeath +================ +*/ +void rvMonsterSentry::OnDeath ( void ) { + idAI::OnDeath ( ); + + Explode ( true ); +} + +/* +================ +rvMonsterSentry::Explode +================ +*/ +void rvMonsterSentry::Explode ( bool force ) { + if ( health > 0 || force ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString ( "def_kamakazi_damage" ), 1.0f ); + + // Kill ourselves + Damage ( this, this, viewAxis[0], "damage_gib", 1.0f, 0 ); + } +} + +/* +================ +rvMonsterSentry::GetDebugInfo +================ +*/ +void rvMonsterSentry::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_kamakaziAttack", aiActionStatusString[actionKamakaziAttack.status], userData ); + proc ( "idAI", "action_circleStrafe", aiActionStatusString[actionCircleStrafe.status], userData ); + proc ( "rvMonsterSentry", "strafeRight",strafeRight?"true":"false", userData ); + proc ( "rvMonsterSentry", "strafeTime", va("%d",strafeTime), userData ); + proc ( "rvMonsterSentry", "nextChatterTime", va("%d",nextChatterTime), userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSentry ) + STATE ( "Torso_BlasterAttack", rvMonsterSentry::State_Torso_BlasterAttack ) + STATE ( "Torso_KamakaziAttack", rvMonsterSentry::State_Torso_KamakaziAttack ) + STATE ( "State_CircleStrafe", rvMonsterSentry::State_CircleStrafe ) + STATE ( "State_Action_CircleStrafe",rvMonsterSentry::State_Action_CircleStrafe ) + STATE ( "Torso_EvadeLeft", rvMonsterSentry::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterSentry::State_Torso_EvadeRight ) +END_CLASS_STATES + +/* +================ +rvMonsterSentry::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSentry::State_Torso_KamakaziAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_KamakaziAttack ( const stateParms_t& parms ) { + Explode ( ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_CircleStrafe ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_CIRCLE + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( strafeTime ) { + //already strafing, just change dir? + strafeTime = (strafeTime0.5f); + } + return SRESULT_STAGE ( STAGE_CIRCLE ); + case STAGE_CIRCLE: + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) { + //FIXME: also stop if I bump into something + strafeTime = 0; + SetState( "State_Combat" ); + return SRESULT_DONE; + } + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (strafeRight?-200:200); + vel.Normalize(); + vel *= 200.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( enemy.lastKnownPosition ); + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterSentry::State_Action_CircleStrafe ( const stateParms_t& parms ) { + SetState( "State_CircleStrafe" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = false; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * 400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeRight ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = true; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} diff --git a/src/game/ai/Monster_SlimyTransfer.cpp b/src/game/ai/Monster_SlimyTransfer.cpp new file mode 100644 index 0000000..107e43f --- /dev/null +++ b/src/game/ai/Monster_SlimyTransfer.cpp @@ -0,0 +1,228 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSlimyTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSlimyTransfer ); + + rvMonsterSlimyTransfer ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + jointHandle_t jointVomitMuzzle; + + int vomitNextAttackTime; + int vomitAttackRate; + +private: + + rvAIAction actionVomitAttack; + + // Torso States + stateResult_t State_Torso_VomitAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishVomitAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t State_Frame_StartVomit ( const stateParms_t& parms ); + stateResult_t State_Frame_StopVomit ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSlimyTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSlimyTransfer ) +END_CLASS + +/* +================ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer +================ +*/ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer ( void ) { +} + +void rvMonsterSlimyTransfer::InitSpawnArgsVariables ( void ) { + jointVomitMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_vomitMuzzle", "puke_bone" ) ); + + vomitAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_vomit_rate", ".15" ) ); +} + +/* +================ +rvMonsterSlimyTransfer::Spawn +================ +*/ +void rvMonsterSlimyTransfer::Spawn ( void ) { + actionVomitAttack.Init ( spawnArgs, "action_vomitAttack", "Torso_VomitAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::Save +================ +*/ +void rvMonsterSlimyTransfer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( vomitNextAttackTime ); + + actionVomitAttack.Save( savefile ); +} + +/* +================ +rvMonsterSlimyTransfer::Restore +================ +*/ +void rvMonsterSlimyTransfer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( vomitNextAttackTime ); + + actionVomitAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::CheckActions +================ +*/ +bool rvMonsterSlimyTransfer::CheckActions ( void ) { + if ( PerformAction ( &actionVomitAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterSlimyTransfer::OnDeath +================ +*/ +void rvMonsterSlimyTransfer::OnDeath ( void ) { + StopEffect ( "fx_vomit_muzzle" ); + idAI::OnDeath ( ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSlimyTransfer ) + STATE ( "Torso_VomitAttack", rvMonsterSlimyTransfer::State_Torso_VomitAttack ) + STATE ( "Torso_FinishVomitAttack", rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ) + + STATE ( "Frame_StartVomit", rvMonsterSlimyTransfer::State_Frame_StartVomit ) + STATE ( "Frame_StopVomit", rvMonsterSlimyTransfer::State_Frame_StopVomit ) +END_CLASS_STATES + +/* +================ +rvMonsterSlimyTransfer::State_Torso_VomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_VomitAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + vomitNextAttackTime = 0; + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "vomit_attack", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishVomitAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + if ( vomitNextAttackTime && gameLocal.time >= vomitNextAttackTime ) { + Attack ( "vomit", jointVomitMuzzle, enemy.ent ); + vomitNextAttackTime = gameLocal.time + vomitAttackRate; + } + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ( const stateParms_t& parms ) { + State_Frame_StopVomit ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StartVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StartVomit ( const stateParms_t& parms ) { + PlayEffect ( "fx_vomit_muzzle", jointVomitMuzzle, true ); + vomitNextAttackTime = gameLocal.time; + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StopVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StopVomit ( const stateParms_t& parms ) { + StopEffect ( "fx_vomit_muzzle" ); + vomitNextAttackTime = 0; + return SRESULT_DONE; +} diff --git a/src/game/ai/Monster_StreamProtector.cpp b/src/game/ai/Monster_StreamProtector.cpp new file mode 100644 index 0000000..6ae0bd5 --- /dev/null +++ b/src/game/ai/Monster_StreamProtector.cpp @@ -0,0 +1,561 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterStreamProtector : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStreamProtector ); + + rvMonsterStreamProtector ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + virtual bool UpdateAnimationControllers ( void ); + + jointHandle_t jointPlasmaMuzzle; + + int attackEndTime; + int attackNextTime; + int plasmaAttackRate; + int shots; + +private: + + int painConsecutive; + + rvAIAction actionPlasmaAttack; + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionHeavyBlasterAttack; + rvAIAction actionLightningActtack; + rvAIAction actionChaingunAttack; + + // Torso States + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + stateResult_t State_Torso_PlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_LightningAttack ( const stateParms_t& parms ); + + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStreamProtector ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStreamProtector ) +END_CLASS + +/* +================ +rvMonsterStreamProtector::rvMonsterStreamProtector +================ +*/ +rvMonsterStreamProtector::rvMonsterStreamProtector ( void ) { + painConsecutive = 0; +} + +void rvMonsterStreamProtector::InitSpawnArgsVariables ( void ) { + jointPlasmaMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_plasmaMuzzle", "NM_muzzle" ) ); + + plasmaAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_plasma_rate", ".15" ) ); +} +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +void rvMonsterStreamProtector::Spawn ( void ) { + actionPlasmaAttack.Init ( spawnArgs, "action_plasmaAttack", "Torso_PlasmaAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", NULL, AIACTIONF_ATTACK ); + actionHeavyBlasterAttack.Init ( spawnArgs, "action_heavyBlasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionLightningActtack.Init ( spawnArgs, "action_lightningAttack", "Torso_LightningAttack", AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", "Torso_ChaingunAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Save +================ +*/ +void rvMonsterStreamProtector::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( attackEndTime ); + savefile->WriteInt( attackNextTime ); + savefile->WriteInt( shots ); + savefile->WriteInt( painConsecutive ); + + actionPlasmaAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionHeavyBlasterAttack.Save ( savefile ); + actionLightningActtack.Save( savefile ); + actionChaingunAttack.Save( savefile ); +} + +/* +================ +rvMonsterStreamProtector::Restore +================ +*/ +void rvMonsterStreamProtector::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( attackEndTime ); + savefile->ReadInt( attackNextTime ); + savefile->ReadInt( shots ); + savefile->ReadInt( painConsecutive ); + + actionPlasmaAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionHeavyBlasterAttack.Restore ( savefile ); + actionLightningActtack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +bool rvMonsterStreamProtector::UpdateAnimationControllers ( void ) { + // TODO: Target enemies behind us? (doesnt need to be the same enemy that we are targetting) + + return idAI::UpdateAnimationControllers ( ); +} + +/* +================ +rvMonsterStreamProtector::CheckPainActions +================ +*/ +bool rvMonsterStreamProtector::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +rvMonsterStreamProtector::CheckActions +================ +*/ +bool rvMonsterStreamProtector::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( PerformAction ( &actionPlasmaAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningActtack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionHeavyBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStreamProtector::CanTurn +================ +*/ +bool rvMonsterStreamProtector::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterStreamProtector::Pain +================ +*/ +bool rvMonsterStreamProtector::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterStreamProtector::Damage +================ +*/ +void rvMonsterStreamProtector::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker && attacker->IsType( rvMonsterStreamProtector::GetClassType() ) ) { + //don't take damage from ourselves or other stream protectors + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStreamProtector ) + STATE ( "State_Killed", rvMonsterStreamProtector::State_Killed ) + STATE ( "State_Dead", rvMonsterStreamProtector::State_Dead ) + + STATE ( "Torso_PlasmaAttack", rvMonsterStreamProtector::State_Torso_PlasmaAttack ) + STATE ( "Torso_FinishPlasmaAttack", rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ) + STATE ( "Torso_BlasterAttack", rvMonsterStreamProtector::State_Torso_BlasterAttack ) + STATE ( "Torso_LightningAttack", rvMonsterStreamProtector::State_Torso_LightningAttack ) + + STATE ( "Torso_TurnRight90", rvMonsterStreamProtector::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterStreamProtector::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +/* +================ +rvMonsterStreamProtector::State_Killed +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Killed ( const stateParms_t& parms ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death", parms.blendFrames ); + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Dead +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterStreamProtector::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_PlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_PlasmaAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_INITIALFIRE_WAIT, + STAGE_FIRE_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_start", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishPlasmaAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + attackEndTime = gameLocal.time + 500; + attackNextTime = gameLocal.time; + + // Flame effect + PlayEffect ( "fx_plasma_muzzle", jointPlasmaMuzzle, true ); + + PlayCycle ( ANIMCHANNEL_TORSO, "range_plasma_fire", 0 ); + + return SRESULT_STAGE ( STAGE_INITIALFIRE_WAIT ); + + case STAGE_INITIALFIRE_WAIT: + if ( gameLocal.time > attackEndTime ) { + attackEndTime = gameLocal.time + SEC2MS ( 1.0f + gameLocal.random.RandomFloat ( ) * 4.0f ); + return SRESULT_STAGE ( STAGE_FIRE_WAIT ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_FIRE_WAIT: + // If we have been using plasma too long or havent seen our enemy for at least half a second then + // stop now. + if ( gameLocal.time > attackEndTime || gameLocal.time - enemy.lastVisibleTime > 500 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_STAGE ( STAGE_END ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_END: + // End animations + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_LightningAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_LightningAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + attackEndTime = gameLocal.time + 5000; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( gameLocal.time > attackEndTime || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_StroggFlyer.cpp b/src/game/ai/Monster_StroggFlyer.cpp new file mode 100644 index 0000000..46173f9 --- /dev/null +++ b/src/game/ai/Monster_StroggFlyer.cpp @@ -0,0 +1,386 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterStroggFlyer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggFlyer ); + + rvMonsterStroggFlyer ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnWakeUp ( void ); + + rvAIAction actionBombAttack; + rvAIAction actionBlasterAttack; + + idVec3 velocity; + + int shotCount; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + +private: + + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); + + void AttackBlaster ( void ); + void AttackBomb ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggFlyer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggFlyer ) +END_CLASS + +/* +================ +rvMonsterStroggFlyer::rvMonsterStroggFlyer +================ +*/ +rvMonsterStroggFlyer::rvMonsterStroggFlyer ( ) { + shotCount = 0; + lastAttackTime = 0; + attackStartTime = 0; +} + +void rvMonsterStroggFlyer::InitSpawnArgsVariables( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} + +/* +================ +rvMonsterStroggFlyer::Spawn +================ +*/ +void rvMonsterStroggFlyer::Spawn ( void ) { + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::OnUpdatePlayback +================ +*/ +void rvMonsterStroggFlyer::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + byte buttons; + byte changed; + byte impulse; + + velocity = pbd.GetVelocity ( ); + + buttons = pbd.GetButtons(); + changed = pbd.GetChanged(); + impulse = pbd.GetImpulse(); + + // Shoot the blaster if the attack button was pressed + if ( (changed & buttons) & BUTTON_ATTACK ){ + AttackBlaster ( ); + } + + if ( (changed & buttons) & BUTTON_ZOOM ){ + AttackBomb ( ); + } + + switch ( impulse ) { + case 40: + aifl.disableAttacks = true; + break; + + case 41: + aifl.disableAttacks = false; + break; + + case 42: + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } +} + +/* +================ +rvMonsterStroggFlyer::OnWakeUp +================ +*/ +void rvMonsterStroggFlyer::OnWakeUp ( void ) { + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); + + return idAI::OnWakeUp ( ); +} + + +/* +================ +rvMonsterStroggFlyer::AttackBlaster +================ +*/ +void rvMonsterStroggFlyer::AttackBlaster ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_muzzleflash", joint ); + Attack ( "blaster", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::AttackBomb +================ +*/ +void rvMonsterStroggFlyer::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( "fx_bombflash", joint ); + Attack ( "bomb", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::CheckActions +================ +*/ +bool rvMonsterStroggFlyer::CheckActions ( void ) { + if ( PerformAction ( &actionBombAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStroggFlyer::Save +================ +*/ +void rvMonsterStroggFlyer::Save( idSaveGame *savefile ) const { + actionBombAttack.Save ( savefile ) ; + actionBlasterAttack.Save ( savefile ); + + savefile->WriteVec3 ( velocity ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); +} + +/* +================ +rvMonsterStroggFlyer::Restore +================ +*/ +void rvMonsterStroggFlyer::Restore( idRestoreGame *savefile ) { + actionBombAttack.Restore ( savefile ) ; + actionBlasterAttack.Restore ( savefile ); + + savefile->ReadVec3 ( velocity ); + + savefile->ReadInt ( shotCount ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::GetDebugInfo +================ +*/ +void rvMonsterStroggFlyer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggFlyer ) + STATE ( "State_WakeUp", rvMonsterStroggFlyer::State_WakeUp ) + STATE ( "State_ScriptedPlaybackMove", rvMonsterStroggFlyer::State_ScriptedPlaybackMove ) + STATE ( "State_Killed", rvMonsterStroggFlyer::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterStroggFlyer::State_Torso_BlasterAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggFlyer::State_Torso_BombAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggFlyer::State_ScriptedPlaybackMove +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + // When the playback finishes cancel any running states + if ( !mPlayback.IsActive() ) { + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Perform actions + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterStroggFlyer::State_Killed +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Killed ( const stateParms_t& parms ) { + PlayEffect ( "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_StroggHover.cpp b/src/game/ai/Monster_StroggHover.cpp new file mode 100644 index 0000000..1eeb889 --- /dev/null +++ b/src/game/ai/Monster_StroggHover.cpp @@ -0,0 +1,1471 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +#define MAX_MISSILE_JOINTS 4 +#define MAX_HOVER_JOINTS 4 +class rvMonsterStroggHover : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggHover ); + + rvMonsterStroggHover ( void ); + ~rvMonsterStroggHover ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + virtual void DeadMove ( void ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual int FilterTactical ( int availableTactical ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Hide( void ); + virtual void Show( void ); + + void StartHeadlight ( void ); + void StopHeadlight ( void ); + +protected: + +// rvAIAction actionRocketAttack; +// rvAIAction actionBlasterAttack; + rvAIAction actionMGunAttack; + rvAIAction actionMissileAttack; + rvAIAction actionBombAttack; + rvAIAction actionStrafe; + rvAIAction actionCircleStrafe; + + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnStartMoving ( void ); + + virtual const char* GetIdleAnimName ( void ); + +private: + + idEntityPtr marker; + idVec3 attackPosOffset; + bool inPursuit; + int holdPosTime; + + int strafeTime; + bool strafeRight; + bool circleStrafing; + float deathPitch; + float deathRoll; + float deathPitchRate; + float deathYawRate; + float deathRollRate; + float deathSpeed; + float deathGrav; + + int markerCheckTime; + + bool MarkerPosValid ( void ); + void TryStartPursuit ( void ); + void Pursue ( void ); + void CircleStrafe ( void ); + void Evade ( bool left ); + + int mGunFireRate; + int missileFireRate; + int bombFireRate; + int nextMGunFireTime; + int nextMissileFireTime; + int nextBombFireTime; + + int mGunMinShots; + int mGunMaxShots; + int missileMinShots; + int missileMaxShots; + int bombMinShots; + int bombMaxShots; + + int shots; + + int evadeDebounce; + int evadeDebounceRate; + float evadeChance; + float evadeSpeed; + float strafeSpeed; + float circleStrafeSpeed; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover[MAX_HOVER_JOINTS]; + rvClientEffectPtr effectHeadlight; + + jointHandle_t jointDust; + int numHoverJoints; + jointHandle_t jointHover[MAX_HOVER_JOINTS]; + jointHandle_t jointBomb; + jointHandle_t jointMGun; + int numMissileJoints; + jointHandle_t jointMissile[MAX_MISSILE_JOINTS]; + jointHandle_t jointHeadlight; + jointHandle_t jointHeadlightControl; + + renderLight_t renderLight; + int lightHandle; +// bool lightOn; + + void DoNamedAttack ( const char* attackName, jointHandle_t joint ); + + void UpdateLightDef ( void ); + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + bool CheckAction_BombAttack ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + +// stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MGunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MissileAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + stateResult_t State_Torso_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_DeathSpiral ( const stateParms_t& parms ); + stateResult_t State_Pursue ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggHover ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggHover ) +END_CLASS + +/* +================ +rvMonsterStroggHover::rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::rvMonsterStroggHover ( ) { + effectDust = NULL; + for ( int i = 0; i < MAX_HOVER_JOINTS; i++ ) { + effectHover[i] = NULL; + } + effectHeadlight = NULL; + + shots = 0; + strafeTime = 0; + strafeRight = false; + circleStrafing = false; + evadeDebounce = 0; + deathPitch = 0; + deathRoll = 0; + deathPitchRate = 0; + deathYawRate = 0; + deathRollRate = 0; + deathSpeed = 0; + deathGrav = 0; + + markerCheckTime = 0; + + marker = NULL; + attackPosOffset.Zero(); + inPursuit = false; + holdPosTime = 0; + + nextMGunFireTime = 0; + nextMissileFireTime = 0; + nextBombFireTime = 0; + + lightHandle = -1; +} + +/* +================ +rvMonsterStroggHover::~rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::~rvMonsterStroggHover ( ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +void rvMonsterStroggHover::InitSpawnArgsVariables( void ) +{ + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + jointHover[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_hover%d",i+1) ) ); + } + + jointDust = animator.GetJointHandle ( spawnArgs.GetString( "joint_dust" ) ); + + jointMGun = animator.GetJointHandle ( spawnArgs.GetString( "joint_mgun" ) ); + numMissileJoints = idMath::ClampInt(0,MAX_MISSILE_JOINTS,spawnArgs.GetInt( "num_missile_joints", "1" )); + for ( int i = 0; i < numMissileJoints; i++ ) { + jointMissile[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_missile%d",i+1) ) ); + } + jointBomb = animator.GetJointHandle ( spawnArgs.GetString( "joint_bomb" ) ); + + mGunFireRate = SEC2MS(spawnArgs.GetFloat( "mgun_fire_rate", "0.1" )); + missileFireRate = SEC2MS(spawnArgs.GetFloat( "missile_fire_rate", "0.25" )); + bombFireRate = SEC2MS(spawnArgs.GetFloat( "bomb_fire_rate", "0.5" )); + + mGunMinShots = spawnArgs.GetInt( "mgun_minShots", "20" ); + mGunMaxShots = spawnArgs.GetInt( "mgun_maxShots", "40" ); + missileMinShots = spawnArgs.GetInt( "missile_minShots", "4" ); + missileMaxShots = spawnArgs.GetInt( "missile_maxShots", "12" ); + bombMinShots = spawnArgs.GetInt( "bomb_minShots", "5" ); + bombMaxShots = spawnArgs.GetInt( "bomb_maxShots", "20" ); + + evadeDebounceRate = SEC2MS(spawnArgs.GetFloat( "evade_rate", "0" )); + evadeChance = spawnArgs.GetFloat( "evade_chance", "0.6" ); + evadeSpeed = spawnArgs.GetFloat( "evade_speed", "400" ); + strafeSpeed = spawnArgs.GetFloat( "strafe_speed", "500" ); + circleStrafeSpeed = spawnArgs.GetFloat( "circle_strafe_speed", "200" ); + + //LIGHT + jointHeadlight = animator.GetJointHandle ( spawnArgs.GetString( "joint_light" ) ); + jointHeadlightControl = animator.GetJointHandle ( spawnArgs.GetString( "joint_light_control" ) ); +} + +void rvMonsterStroggHover::Hide( void ) +{ + StopHeadlight(); + idAI::Hide(); +} + +void rvMonsterStroggHover::Show( void ) +{ + idAI::Show(); + StartHeadlight(); +} + +void rvMonsterStroggHover::StartHeadlight( void ) +{ + if ( jointHeadlight != INVALID_JOINT ) + { + lightHandle = -1; + if ( cvarSystem->GetCVarInteger( "com_machineSpec" ) > 1 && spawnArgs.GetString("mtr_light") ) { + idVec3 color; + //const char* temp; + + const idMaterial *headLightMaterial = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + if ( headLightMaterial ) + { + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "light_pointlight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + spawnArgs.GetVector( "light_color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "light_radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "light_target" ); + renderLight.up = spawnArgs.GetVector( "light_up" ); + renderLight.right = spawnArgs.GetVector( "light_right" ); + renderLight.end = spawnArgs.GetVector( "light_target" );; + } + + //lightOn = spawnArgs.GetBool( "start_on", "1" ); + + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } + // Hide flare surface if there is one + /* + temp = spawnArgs.GetString ( "light_flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + */ + + // Sounds shader when turning light + //spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + //spawnArgs.GetString ( "snd_off", "", soundOff); + + UpdateLightDef ( ); + } +} + +void rvMonsterStroggHover::StopHeadlight( void ) +{ + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( lightHandle ); + lightHandle = -1; + } + memset ( &renderLight, 0, sizeof(renderLight) ); +} +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +void rvMonsterStroggHover::Spawn ( void ) { +// actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); +// actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionMGunAttack.Init ( spawnArgs, "action_mGunAttack", "Torso_MGunAttack", AIACTIONF_ATTACK ); + actionMissileAttack.Init ( spawnArgs, "action_missileAttack", "Torso_MissileAttack", AIACTIONF_ATTACK ); + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "Torso_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + evadeDebounce = 0; + + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + effectHover[i] = PlayEffect ( "fx_hover", jointHover[i], true ); + } + } + + if ( !marker ) { + marker = gameLocal.SpawnEntityDef( "target_null" ); + } + + //LIGHT + StopHeadlight(); + StartHeadlight(); + + if ( jointHeadlight != INVALID_JOINT ) + { + effectHeadlight = PlayEffect( "fx_headlight", jointHeadlight, true ); + } +} + +/* +================ +rvMonsterStroggHover::GetDebugInfo +================ +*/ +void rvMonsterStroggHover::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterStroggHover", "action_mGunAttack", aiActionStatusString[actionMGunAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_missileAttack",aiActionStatusString[actionMissileAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_strafe", aiActionStatusString[actionStrafe.status], userData ); + proc ( "rvMonsterStroggHover", "action_circleStrafe",aiActionStatusString[actionCircleStrafe.status], userData ); + + proc ( "rvMonsterStroggHover", "inPursuit", inPursuit?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "marker", (!inPursuit||marker==NULL)?"0 0 0":va("%f %f %f",marker->GetPhysics()->GetOrigin().x,marker->GetPhysics()->GetOrigin().y,marker->GetPhysics()->GetOrigin().z), userData ); + proc ( "rvMonsterStroggHover", "holdPosTime", va("%d",holdPosTime), userData ); + + proc ( "rvMonsterStroggHover", "circleStrafing", circleStrafing?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeRight", strafeRight?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeTime", va("%d",strafeTime), userData ); + + proc ( "rvMonsterStroggHover", "mGunFireRate", va("%d",mGunFireRate), userData ); + proc ( "rvMonsterStroggHover", "missileFireRate", va("%d",missileFireRate), userData ); + proc ( "rvMonsterStroggHover", "bombFireRate", va("%d",bombFireRate), userData ); + + proc ( "rvMonsterStroggHover", "mGunMinShots", va("%d",mGunMinShots), userData ); + proc ( "rvMonsterStroggHover", "mGunMaxShots", va("%d",mGunMaxShots), userData ); + proc ( "rvMonsterStroggHover", "missileMinShots", va("%d",missileMinShots), userData ); + proc ( "rvMonsterStroggHover", "missileMaxShots", va("%d",missileMaxShots), userData ); + proc ( "rvMonsterStroggHover", "bombMinShots", va("%d",bombMinShots), userData ); + proc ( "rvMonsterStroggHover", "bombMaxShots", va("%d",bombMaxShots), userData ); + proc ( "rvMonsterStroggHover", "nextMGunFireTime", va("%d",nextMGunFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextMissileFireTime",va("%d",nextMissileFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextBombFireTime", va("%d",nextBombFireTime), userData ); + proc ( "rvMonsterStroggHover", "shots", va("%d",shots), userData ); + + proc ( "rvMonsterStroggHover", "evadeDebounce", va("%d",evadeDebounce), userData ); + proc ( "rvMonsterStroggHover", "evadeDebounceRate", va("%d",evadeDebounceRate), userData ); + proc ( "rvMonsterStroggHover", "evadeChance", va("%g",evadeChance), userData ); + proc ( "rvMonsterStroggHover", "evadeSpeed", va("%g",evadeSpeed), userData ); + proc ( "rvMonsterStroggHover", "strafeSpeed", va("%g",strafeSpeed), userData ); + proc ( "rvMonsterStroggHover", "circleStrafeSpeed", va("%g",circleStrafeSpeed), userData ); +} + +/* +===================== +rvMonsterStroggHover::UpdateLightDef +===================== +*/ +void rvMonsterStroggHover::UpdateLightDef ( void ) { + if ( jointHeadlight != INVALID_JOINT ) + { + idVec3 origin; + idMat3 axis; + + if ( jointHeadlightControl != INVALID_JOINT ) { + idAngles jointAng; + jointAng.Zero(); + jointAng.yaw = 10.0f * sin( ( (gameLocal.GetTime()%2000)-1000 ) / 1000.0f * idMath::PI ); + jointAng.pitch = 7.5f * sin( ( (gameLocal.GetTime()%4000)-2000 ) / 2000.0f * idMath::PI ); + + animator.SetJointAxis( jointHeadlightControl, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + + GetJointWorldTransform ( jointHeadlight, gameLocal.time, origin, axis ); + + //origin += (localOffset * axis); + + // Include this part in the total bounds + // FIXME: bounds are local + //parent->AddToBounds ( worldOrigin ); + //UpdateOrigin ( ); + + if ( lightHandle != -1 ) { + renderLight.origin = origin; + renderLight.axis = axis; + + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); + } + } +} + +/* +================ +rvMonsterStroggHover::Think +================ +*/ +void rvMonsterStroggHover::Think ( void ) { + idAI::Think ( ); + + if ( !aifl.dead ) + { + // If thinking we should play an effect on the ground under us + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 80 units down from the bottom of our bbox + GetJointWorldTransform ( jointDust, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * (GetPhysics()->GetBounds()[0][2]+80.0f), CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + /* + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + */ + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + //Try to circle strafe or pursue + if ( circleStrafing ) + { + CircleStrafe(); + } + else if ( !inPursuit ) + { + if ( !aifl.action && move.fl.done && !aifl.scripted ) + { + if ( GetEnemy() ) + { + if ( DistanceTo( GetEnemy() ) > 2000.0f + || (GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetEnemy()->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin())) > 1000.0f ) + {//enemy is far away or moving away from us at a pretty decent speed + TryStartPursuit(); + } + } + } + } + else + { + Pursue(); + } + + //Dodge + if ( !circleStrafing ) { + if( combat.shotAtTime && gameLocal.GetTime() - combat.shotAtTime < 1000.0f ) { + if ( nextBombFireTime < gameLocal.GetTime() - 3000 ) { + if ( gameLocal.random.RandomFloat() > evadeChance ) { + //40% chance of ignoring it - makes them dodge rockets less often but bullets more often? + combat.shotAtTime = 0; + } else if ( evadeDebounce < gameLocal.GetTime() ) { + //ramps down from 400 to 100 over 1 second + float speed = evadeSpeed - ((((float)(gameLocal.GetTime()-combat.shotAtTime))/1000.0f)*(evadeSpeed-(evadeSpeed*0.25f))); + idVec3 evadeVel = viewAxis[1] * ((combat.shotAtAngle >= 0)?-1:1) * speed; + evadeVel.z *= 0.5f; + move.addVelocity += evadeVel; + move.addVelocity.Normalize(); + move.addVelocity *= speed; + /* + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ) { + //just need to do it once? + combat.shotAtTime = 0; + } + */ + if ( evadeDebounceRate > 1 ) + { + evadeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt( evadeDebounceRate ) + (ceil(((float)evadeDebounceRate)/2.0f)); + } + } + } + } + } + + //If using melee rush to nav to him, stop when we're close enough to attack + if ( combat.tacticalCurrent == AITACTICAL_MELEE + && move.moveCommand == MOVE_TO_ENEMY + && !move.fl.done + && nextBombFireTime < gameLocal.GetTime() - 3000 + && enemy.fl.visible && DistanceTo( GetEnemy() ) < 2000.0f ) { + StopMove( MOVE_STATUS_DONE ); + ForceTacticalUpdate(); + } else { + //whenever we're not in the middle of something, force an update of our tactical + if ( !aifl.action ) { + if ( !aasFind ) { + if ( move.fl.done ) { + if ( !inPursuit && !circleStrafing ) { + ForceTacticalUpdate(); + } + } + } + } + } + } + + //update light +// if ( lightOn ) { + UpdateLightDef ( ); +// } +} + +/* +============ +rvMonsterStroggHover::OnStartMoving +============ +*/ +void rvMonsterStroggHover::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( move.moveCommand == MOVE_TO_ENEMY ) { + move.range = combat.meleeRange; + } +} + +/* +================ +rvMonsterStroggHover::Save +================ +*/ +void rvMonsterStroggHover::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteBool ( circleStrafing ); + savefile->WriteFloat ( deathPitch ); + savefile->WriteFloat ( deathRoll ); + savefile->WriteFloat ( deathPitchRate ); + savefile->WriteFloat ( deathYawRate ); + savefile->WriteFloat ( deathRollRate ); + savefile->WriteFloat ( deathSpeed ); + savefile->WriteFloat ( deathGrav ); + savefile->WriteInt ( markerCheckTime ); + + savefile->WriteVec3( attackPosOffset ); + +// actionRocketAttack.Save ( savefile ); +// actionBlasterAttack.Save ( savefile ); + actionMGunAttack.Save ( savefile ); + actionMissileAttack.Save ( savefile ); + actionBombAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + actionCircleStrafe.Save ( savefile ); + + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Save ( savefile ); + } + + effectDust.Save ( savefile ); + effectHeadlight.Save ( savefile ); + + marker.Save( savefile ); + savefile->WriteBool ( inPursuit ); + savefile->WriteInt ( holdPosTime ); + savefile->WriteInt ( nextMGunFireTime ); + savefile->WriteInt ( nextMissileFireTime ); + savefile->WriteInt ( nextBombFireTime ); + + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + + savefile->WriteInt( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Restore +================ +*/ +void rvMonsterStroggHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadBool ( circleStrafing ); + savefile->ReadFloat ( deathPitch ); + savefile->ReadFloat ( deathRoll ); + savefile->ReadFloat ( deathPitchRate ); + savefile->ReadFloat ( deathYawRate ); + savefile->ReadFloat ( deathRollRate ); + savefile->ReadFloat ( deathSpeed ); + savefile->ReadFloat ( deathGrav ); + savefile->ReadInt ( markerCheckTime ); + + savefile->ReadVec3( attackPosOffset ); + +// actionRocketAttack.Restore ( savefile ); +// actionBlasterAttack.Restore ( savefile ); + actionMGunAttack.Restore ( savefile ); + actionMissileAttack.Restore ( savefile ); + actionBombAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + actionCircleStrafe.Restore ( savefile ); + + InitSpawnArgsVariables(); + //NOTE: if the def file changes the the number of numHoverJoints, this will be BAD... + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Restore ( savefile ); + } + + effectDust.Restore ( savefile ); + effectHeadlight.Restore ( savefile ); + + marker.Restore( savefile ); + savefile->ReadBool ( inPursuit ); + savefile->ReadInt ( holdPosTime ); + savefile->ReadInt ( nextMGunFireTime ); + savefile->ReadInt ( nextMissileFireTime ); + savefile->ReadInt ( nextBombFireTime ); + + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadInt ( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Collide +================ +*/ +bool rvMonsterStroggHover::Collide( const trace_t &collision, const idVec3 &velocity ) { + if ( aifl.dead ) { + StopHeadlight(); + //stop headlight + if ( effectHeadlight ) { + effectHeadlight->Stop ( ); + effectHeadlight = NULL; + } + // Stop the crash & burn effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + SetState ( "State_Remove" ); + return false; + } + return idAI::Collide( collision, velocity ); +} + +/* +================ +rvMonsterStroggHover::Damage +================ +*/ +void rvMonsterStroggHover::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + return; + } + bool wasDead = aifl.dead; + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if ( !wasDead && aifl.dead ) { + SetState( "State_DeathSpiral" ); + } +} +/* +================ +rvMonsterStroggHover::OnDeath +================ +*/ +void rvMonsterStroggHover::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + + idAI::OnDeath ( ); +} + +/* +===================== +rvMonsterStroggHover::DeadMove +===================== +*/ +void rvMonsterStroggHover::DeadMove( void ) { + DeathPush ( ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); +} + +/* +===================== +rvMonsterStroggHover::SkipImpulse +===================== +*/ +bool rvMonsterStroggHover::SkipImpulse( idEntity* ent, int id ) { + return ((ent==this) || (move.moveCommand==MOVE_RV_PLAYBACK)); +} + +/* +================ +rvMonsterStroggHover::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( inPursuit && !holdPosTime ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + if ( evadeDebounce >= gameLocal.GetTime() ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( inPursuit ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_BombAttack ( rvAIAction* action, int animNum ) { + if ( !GetEnemy() || !enemy.fl.visible ) { + return false; + } + /* + if ( GetPhysics()->GetLinearVelocity().Length() < 200.0f ) { + //not moving enough + return false; + } + */ + if ( GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetPhysics()->GetOrigin()-GetEnemy()->GetPhysics()->GetOrigin()) >= 250.0f ) { + //enemy is moving toward me, drop 'em! + return true; + } + return false; +} + +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +bool rvMonsterStroggHover::CheckActions ( void ) { + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_CircleStrafe ) ) { + return true; + } + +/* + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } +*/ + if ( PerformAction ( &actionBombAttack, (checkAction_t)&rvMonsterStroggHover::CheckAction_BombAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMissileAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMGunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_Strafe ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterStroggHover::OnEnemyChange +================ +*/ +void rvMonsterStroggHover::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } +} + +/* +================ +rvMonsterStroggHover::GetIdleAnimName +================ +*/ +const char* rvMonsterStroggHover::GetIdleAnimName ( void ) { + /* + if ( move.moveType == MOVETYPE_FLY ) { + return "flying_idle"; + } + */ + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterStroggHover::DoNamedAttack +================ +*/ +void rvMonsterStroggHover::DoNamedAttack ( const char* attackName, jointHandle_t joint ) { + if ( joint != INVALID_JOINT ) { + StartSound ( va("snd_%s_fire",attackName), SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( va("fx_%s_flash",attackName), joint ); + Attack ( attackName, joint, GetEnemy() ); + } +} + +/* +================ +rvMonsterStroggHover::FilterTactical +================ +*/ +int rvMonsterStroggHover::FilterTactical ( int availableTactical ) { + availableTactical = idAI::FilterTactical( availableTactical ); + if ( circleStrafing || inPursuit ) { + return 0; + } + if ( nextBombFireTime >= gameLocal.GetTime() ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } else if ( enemy.fl.visible ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } + return availableTactical; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggHover ) +// STATE ( "Torso_BlasterAttack", rvMonsterStroggHover::State_Torso_BlasterAttack ) +// STATE ( "Torso_RocketAttack", rvMonsterStroggHover::State_Torso_RocketAttack ) + STATE ( "Torso_MGunAttack", rvMonsterStroggHover::State_Torso_MGunAttack ) + STATE ( "Torso_MissileAttack", rvMonsterStroggHover::State_Torso_MissileAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggHover::State_Torso_BombAttack ) +// STATE ( "Torso_EvadeLeft", rvMonsterStroggHover::State_Torso_EvadeLeft ) +// STATE ( "Torso_EvadeRight", rvMonsterStroggHover::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterStroggHover::State_Torso_Strafe ) + STATE ( "Torso_CircleStrafe", rvMonsterStroggHover::State_Torso_CircleStrafe ) + STATE ( "State_CircleStrafe", rvMonsterStroggHover::State_CircleStrafe ) + STATE ( "State_DeathSpiral", rvMonsterStroggHover::State_DeathSpiral ) + STATE ( "State_Pursue", rvMonsterStroggHover::State_Pursue ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggHover::State_Torso_MGunAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MGunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( mGunMaxShots-mGunMinShots ) + mGunMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "mgun", jointMGun ); + nextMGunFireTime = gameLocal.GetTime() + mGunFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMGunFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_MissileAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MissileAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( missileMaxShots-missileMinShots ) + missileMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "missile", jointMissile[gameLocal.random.RandomInt(numMissileJoints)] ); + nextMissileFireTime = gameLocal.GetTime() + missileFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMissileFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 || enemy.range < (actionMissileAttack.minRange*0.75f) ) { + //out of shots or enemy too close to safely keep launching rockets at + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + idVec3 vel = GetPhysics()->GetLinearVelocity(); + if ( vel.z < 150.0f ) { + vel.z += 20.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + switch ( parms.stage ) { + case STAGE_INIT: + move.fly_offset = 800;//go up! + shots = gameLocal.random.RandomInt ( bombMaxShots-bombMinShots ) + bombMinShots; + if ( GetEnemy() ) { + //if I'm not above him, give me a quick boost first + float zDiff = GetPhysics()->GetOrigin().z-GetEnemy()->GetPhysics()->GetOrigin().z; + if ( zDiff < 150.0f ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel.z += 200.0f; + if ( zDiff < 0.0f ) { + //even more if I'm below him! + vel.z -= zDiff*2.0f; + } + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + if ( move.moveCommand == MOVE_TO_ATTACK ) { + StopMove( MOVE_STATUS_DONE ); + } + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + ForceTacticalUpdate(); + } + if ( move.moveCommand == MOVE_NONE + && !inPursuit && !circleStrafing ) { + MoveToEnemy(); + } + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "bomb", jointBomb ); + nextBombFireTime = gameLocal.GetTime() + bombFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextBombFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + move.fly_offset = spawnArgs.GetFloat("fly_offset","250"); + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BlasterAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( 8 ) + 4; + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_preshoot", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_postshoot", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ +/* +================ +rvMonsterStroggHover::State_Torso_RocketAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "rocket_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} +*/ + +void rvMonsterStroggHover::Evade ( bool left ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (left?strafeSpeed:-strafeSpeed); + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); +} + +stateResult_t rvMonsterStroggHover::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + Evade( false ); + } else { + Evade( true ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_Torso_CircleStrafe ( const stateParms_t& parms ) { + circleStrafing = true; + return SRESULT_DONE; +} + +bool rvMonsterStroggHover::MarkerPosValid ( void ) +{ + //debouncer ftw + if( markerCheckTime > gameLocal.GetTime() ) { + return true; + } + + markerCheckTime = gameLocal.GetTime() + 500 + (gameLocal.random.RandomFloat() * 500); + + trace_t trace; + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetOrigin(), GetPhysics()->GetClipMask(), NULL ); + if ( !(trace.c.contents&GetPhysics()->GetClipMask()) ) + {//not in solid + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), GetEnemy()->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, GetEnemy() ); + idActor* enemyAct = NULL; + rvVehicle* enemyVeh = NULL; + if ( GetEnemy()->IsType( rvVehicle::GetClassType() ) ) { + enemyVeh = static_cast(GetEnemy()); + } else if ( GetEnemy()->IsType( idActor::GetClassType() ) ) { + enemyAct = static_cast(GetEnemy()); + } + idEntity* hitEnt = gameLocal.entities[trace.c.entityNum]; + idActor* hitAct = NULL; + if ( hitEnt && hitEnt->IsType( idActor::GetClassType() ) ) { + hitAct = static_cast(hitEnt); + } + if ( trace.fraction >= 1.0f + || (enemyAct && enemyAct->IsInVehicle() && enemyAct->GetVehicleController().GetVehicle() == gameLocal.entities[trace.c.entityNum]) + || (enemyVeh && hitAct && hitAct->IsInVehicle() && hitAct->GetVehicleController().GetVehicle() == enemyVeh) ) + {//have a clear LOS to enemy + if ( PointReachableAreaNum( marker.GetEntity()->GetPhysics()->GetOrigin() ) ) + {//valid AAS there... + return true; + } + } + } + return false; +} + +void rvMonsterStroggHover::TryStartPursuit ( void ) +{ + if ( GetEnemy() ) + { + inPursuit = false; + if ( !marker.GetEntity() ) { + //wtf?! + assert(0); + return; + } + attackPosOffset.Set( gameLocal.random.CRandomFloat()*500.0f, gameLocal.random.CRandomFloat()*500.0f, 0.0f ); + if ( attackPosOffset.Length() < 150.0f ) + { + attackPosOffset.Normalize(); + attackPosOffset *= 150.0f; + } + attackPosOffset.z = (gameLocal.random.CRandomFloat()*30.0f)+50.0f + move.fly_offset; + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( MarkerPosValid() ) + { + if ( MoveToEntity( marker ) ) + { + inPursuit = true; + holdPosTime = 0; + SetState( "State_Pursue" ); + } + } + } +} + +void rvMonsterStroggHover::Pursue ( void ) +{ + if ( marker.GetEntity() && GetEnemy() ) + { + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugAxis( marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetAxis() ); + } + if ( MarkerPosValid() ) + { + bool breakOff = false; + if ( move.fl.done ) + {//even once get there, hold that position for a while... + if ( holdPosTime && holdPosTime > gameLocal.GetTime() ) + {//held this position long enough + breakOff = true; + } + else + { + if ( !holdPosTime ) + {//just got there, hold position for a bit + holdPosTime = gameLocal.random.RandomInt(2000)+3000 + gameLocal.GetTime(); + } + if ( !MoveToEntity( marker ) ) + { + breakOff = true; + } + } + } + if ( !breakOff ) + { + return; + } + } + } + if ( !move.fl.done ) + { + StopMove( MOVE_STATUS_DONE ); + } + inPursuit = false; +} + +stateResult_t rvMonsterStroggHover::State_Pursue ( const stateParms_t& parms ) { + if ( inPursuit ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +void rvMonsterStroggHover::CircleStrafe ( void ) +{ + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) + { + //FIXME: also stop if I bump into something + circleStrafing = false; + strafeTime = 0; + SetState( "State_Combat" ); + return; + } + if ( !strafeTime ) + { + strafeTime = gameLocal.GetTime() + 8000; + //FIXME: try to see which side it clear? + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + } + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[1] * (strafeRight?-circleStrafeSpeed:circleStrafeSpeed); + strafeVel.z = 0.0f; + vel += strafeVel; + vel.Normalize(); + vel *= circleStrafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( GetEnemy()->GetPhysics()->GetOrigin() ); +} + +stateResult_t rvMonsterStroggHover::State_CircleStrafe ( const stateParms_t& parms ) { + if ( circleStrafing ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_DeathSpiral ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SPIRAL + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + disablePain = true; + + // Make sure all animation and attack states stop + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + + // Start the crash & burn effects + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + PlayEffect ( "fx_hurt", jointHover[i], false ); + effectHover[i] = PlayEffect ( "fx_crash", jointHover[i], true ); + } + } + deathPitch = viewAxis.ToAngles()[0]; + deathRoll = viewAxis.ToAngles()[2]; + + deathPitchRate = gameLocal.random.RandomFloat()*0.3f + 0.1f; + deathYawRate = gameLocal.random.RandomFloat()*2.0f + 1.5f; + deathRollRate = gameLocal.random.RandomFloat()*3.0f + 1.0f; + deathSpeed = gameLocal.random.RandomFloat()*300.0f + 500.0f; + deathGrav = gameLocal.random.RandomFloat()*6.0f + 6.0f; + + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + StopSound( SND_CHANNEL_HEART, false ); + StartSound ( "snd_crash", SND_CHANNEL_HEART, 0, false, NULL ); + } + return SRESULT_STAGE ( STAGE_SPIRAL ); + case STAGE_SPIRAL: + { + move.current_yaw += (strafeRight?-deathYawRate:deathYawRate); + deathPitch = idMath::ClampFloat( -90.0f, 90.0f, deathPitch+deathPitchRate ); + deathRoll += (strafeRight?deathRollRate:-deathRollRate); + viewAxis = idAngles( deathPitch, move.current_yaw, deathRoll ).ToMat3(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[0] * deathSpeed; + strafeVel.z = 0; + vel += strafeVel; + vel.Normalize(); + vel *= deathSpeed; + vel += GetPhysics()->GetGravity()/deathGrav; + + physicsObj.UseVelocityMove( true ); + + physicsObj.SetLinearVelocity( vel ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + refSound.parms.frequencyShift += 0.025f; + emitter->ModifySound ( SND_CHANNEL_HEART, &refSound.parms ); + } + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_StroggMarine.cpp b/src/game/ai/Monster_StroggMarine.cpp new file mode 100644 index 0000000..17cf66e --- /dev/null +++ b/src/game/ai/Monster_StroggMarine.cpp @@ -0,0 +1,776 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +//NOTE: actually a bit of a misnomer, as all Strogg Marine types use this class now... +class rvMonsterStroggMarine : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggMarine ); + + rvMonsterStroggMarine ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + + int maxShots; + int minShots; + int shots; + int shotsFired; + + int fireAnimNum; + bool spraySideRight; + int sweepCount; + + bool EnemyMovingToRight ( void ); + +private: + + void CalculateShots ( void ); + + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + // Actions + rvAIAction actionStrafe; + rvAIAction actionCrouchRangedAttack; + rvAIAction actionRollAttack; + rvAIAction actionSprayAttack; + rvAIAction actionAngry; + rvAIAction actionReload; + + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RollAttack ( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_Angry ( rvAIAction* action, int animNum ); + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_RollAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggMarine ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggMarine ) +END_CLASS + +/* +================ +rvMonsterStroggMarine::rvMonsterStroggMarine +================ +*/ +rvMonsterStroggMarine::rvMonsterStroggMarine ( ) { + nextShootTime = 0; +} + +void rvMonsterStroggMarine::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.2" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterStroggMarine::Spawn +================ +*/ +void rvMonsterStroggMarine::Spawn ( void ) { + actionStrafe.Init ( spawnArgs, "action_strafe", NULL, 0 ); + actionCrouchRangedAttack.Init ( spawnArgs, "action_crouchRangedAttack", NULL, AIACTIONF_ATTACK ); + actionRollAttack.Init ( spawnArgs, "action_rollAttack", NULL, AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionAngry.Init ( spawnArgs, "action_angry", NULL, 0 ); + actionReload.Init ( spawnArgs, "action_reload", NULL, 0 ); + + InitSpawnArgsVariables(); + + shots = 0; + shotsFired = 0; +} + +/* +================ +rvMonsterStroggMarine::Save +================ +*/ +void rvMonsterStroggMarine::Save ( idSaveGame *savefile ) const { + actionStrafe.Save ( savefile ); + actionCrouchRangedAttack.Save( savefile ); + actionRollAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionAngry.Save( savefile ); + actionReload.Save( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + + savefile->WriteInt ( fireAnimNum ); + savefile->WriteBool ( spraySideRight ); + savefile->WriteInt ( sweepCount ); + + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterStroggMarine::Restore +================ +*/ +void rvMonsterStroggMarine::Restore ( idRestoreGame *savefile ) { + actionStrafe.Restore ( savefile ); + actionCrouchRangedAttack.Restore( savefile ); + actionRollAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionAngry.Restore( savefile ); + actionReload.Restore( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + + savefile->ReadInt ( fireAnimNum ); + savefile->ReadBool ( spraySideRight ); + savefile->ReadInt ( sweepCount ); + + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterStroggMarine::OnStopMoving +============ +*/ +void rvMonsterStroggMarine::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + actionMeleeAttack.timer.Clear( actionTime ); + } + else + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionCrouchRangedAttack.timer.Clear( actionTime ); + actionRollAttack.timer.Clear( actionTime ); + actionSprayAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterStroggMarine::CheckAction_JumpBack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage && gameLocal.time - pain.lastTakenTime > 1500 ) { + return false; + } + + // enemy must be in front to jump backwards + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + } + // TODO: dont evade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeRight +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + return idAI::CheckAction_RangedAttack( action, animNum ); +} + +/* +================ +rvMonsterStroggMarine::CheckAction_CrouchRangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RollAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RollAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_SprayAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Angry +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Angry ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Reload +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckActions +================ +*/ +bool rvMonsterStroggMarine::CheckActions ( void ) { + + if ( idAI::CheckActions ( ) ) + { + return true; + } + if ( PerformAction ( &actionCrouchRangedAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_CrouchRangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRollAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_RollAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_SprayAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Strafe ) || + PerformAction ( &actionAngry, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Angry ) || + PerformAction ( &actionReload, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Reload ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggMarine ) + STATE ( "Torso_RollAttack", rvMonsterStroggMarine::State_Torso_RollAttack ) + STATE ( "Torso_RangedAttack", rvMonsterStroggMarine::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterStroggMarine::State_Torso_MovingRangedAttack ) + STATE ( "Torso_SprayAttack", rvMonsterStroggMarine::State_Torso_SprayAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggMarine::State_Torso_RollAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RollAttack ( const stateParms_t& parms ) { + enum { + TORSO_ROLLATTACK_ROLL, + TORSO_ROLLATTACK_FACE, + TORSO_ROLLATTACK_FIRE, + TORSO_ROLLATTACK_FINISH + }; + + TurnToward(enemy.lastKnownPosition); + + switch ( parms.stage ) { + // Start the roll attack animation + case TORSO_ROLLATTACK_ROLL: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the roll + PlayAnim ( ANIMCHANNEL_TORSO, "dive_turn", parms.blendFrames ); + move.fl.noTurn = false; + //FaceEnemy(); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FACE ); + + // Wait for roll animation to finish + case TORSO_ROLLATTACK_FACE: + if ( AnimDone ( ANIMCHANNEL_LEGS, 6 ) ) { + return SRESULT_STAGE ( TORSO_ROLLATTACK_FIRE ); + } + return SRESULT_WAIT; + + // Play fire animation + case TORSO_ROLLATTACK_FIRE: + if ( !enemy.ent || !enemy.fl.visible ) + {//whoops! rolled out of LOS + return SRESULT_DONE; + } + if ( enemy.fl.inFov ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "shotgun_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FINISH ); + } + return SRESULT_WAIT; + + // Wait for fire animation to finish + case TORSO_ROLLATTACK_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterStroggMarine::CalculateShots +================ +*/ +void rvMonsterStroggMarine::CalculateShots ( void ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + + // Update the firing animation playback rate + /* + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } + */ +} + +/* +================ +rvMonsterStroggMarine::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + //TurnToward(enemy.lastKnownPosition); + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && move.fl.running && !actionRangedAttack.fl.overrideLegs && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + fireAnimNum = gameLocal.random.RandomInt(2)+1; + CalculateShots(); + shotsFired = 0; + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_loop", fireAnimNum), 0 ); + shots--; + shotsFired++; + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov && shotsFired >= minShots ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots(); + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "base", attackJoint, GetEnemy() ); + PlayEffect( "fx_blaster_muzzleflash", attackJoint ); + } + StartSound( "snd_weapon_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= minShots) || !move.fl.running || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::EnemyMovingToRight +================ +*/ +bool rvMonsterStroggMarine::EnemyMovingToRight( void ) +{ + if ( !GetEnemy() ) + { + return false; + } + //use their movement direction + idVec3 dir = GetEnemy()->GetPhysics()->GetLinearVelocity(); + //flatten + dir.z = 0; + dir.Normalize(); + + idVec3 fwd = viewAxis[0]; + idVec3 lt = viewAxis[1]; + + float dot = 0.0f; + dot = DotProduct(dir, lt); + if ( dot > 0 ) + { + return false; + } + else + { + return true; + } +} + +/* +================ +rvMonsterStroggMarine::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + spraySideRight = EnemyMovingToRight(); + sweepCount = 0; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_start", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_start", 0 ); + } + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_sweep", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_sweep", 0 ); + } + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + bool curEnemyMovingRight = EnemyMovingToRight(); + if ( sweepCount < 3 + && (!gameLocal.random.RandomInt(2) + || (spraySideRight && !curEnemyMovingRight) + || (!spraySideRight && curEnemyMovingRight)) ) + { + spraySideRight = !spraySideRight; + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_end", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_end", 0 ); + } + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_TeleportDropper.cpp b/src/game/ai/Monster_TeleportDropper.cpp new file mode 100644 index 0000000..5fdcc56 --- /dev/null +++ b/src/game/ai/Monster_TeleportDropper.cpp @@ -0,0 +1,559 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +class rvMonsterTeleportDropper : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTeleportDropper ); + + rvMonsterTeleportDropper ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* projectileDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual const char* GetIdleAnimName ( void ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + +protected: + + idEntityPtr spawner; + + // Actions + rvAIAction actionDropSpawners; + + //bool dropAtGoalOnly; + +// virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); +// virtual bool MoveToEnemy ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + +private: + + bool leftSideBlocked; + bool rightSideBlocked; + jointHandle_t jointLeftFrontCannon; + jointHandle_t jointLeftRearCannon; + jointHandle_t jointRightFrontCannon; + jointHandle_t jointRightRearCannon; + + bool leapDidAttack; + + bool CheckAction_DropSpawners ( rvAIAction* action, int animNum ); + // Torso states + stateResult_t State_Torso_DropSpawners ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTeleportDropper ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTeleportDropper ) +END_CLASS + +/* +================ +rvMonsterTeleportDropper::rvMonsterTeleportDropper +================ +*/ +rvMonsterTeleportDropper::rvMonsterTeleportDropper ( void ) { + spawner = NULL; + leftSideBlocked = rightSideBlocked = false; + leapDidAttack = false; +// dropAtGoalOnly = false; +} + +void rvMonsterTeleportDropper::InitSpawnArgsVariables ( void ) +{ + jointLeftFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_front_cannon" ) ); + jointLeftRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_rear_cannon" ) ); + jointRightFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_front_cannon" ) ); + jointRightRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_rear_cannon" ) ); +} + +/* +================ +rvMonsterTeleportDropper::Spawn +================ +*/ +void rvMonsterTeleportDropper::Spawn ( void ) { + idEntity* ent; + idDict args; + + // Create the spawner entity + args.Clear ( ); + args.Set ( "classname", spawnArgs.GetString ( "def_spawner" ) ); + gameLocal.SpawnEntityDef ( args, &ent ); + + if ( ent ) { + spawner = static_cast(ent); + spawner->ProcessEvent ( &EV_Activate, this ); + } + + // Define actions + actionDropSpawners.Init ( spawnArgs, "action_dropSpawners", "Torso_DropSpawners", 0 ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Save +================ +*/ +void rvMonsterTeleportDropper::Save ( idSaveGame *savefile ) const { + spawner.Save( savefile ); + + actionDropSpawners.Save( savefile ); + savefile->WriteBool(leftSideBlocked); + savefile->WriteBool(rightSideBlocked); + savefile->WriteBool(leapDidAttack); +} + +/* +================ +rvMonsterTeleportDropper::Restore +================ +*/ +void rvMonsterTeleportDropper::Restore ( idRestoreGame *savefile ) { + spawner.Restore( savefile ); + + actionDropSpawners.Restore( savefile ); + savefile->ReadBool(leftSideBlocked); + savefile->ReadBool(rightSideBlocked); + savefile->ReadBool(leapDidAttack); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Think +================ +*/ +void rvMonsterTeleportDropper::Think ( void ) { + idAI::Think ( ); + if ( aifl.action && actionLeapAttack.status == rvAIAction::STATUS_OK && !leapDidAttack ) { + //in leap attack action + if ( GetEnemy() && enemy.fl.inFov && enemy.range < 64.0f ) + { + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_leap" ), false ); + AttackMelee( "leap", attackDict ); + StartSound( "snd_leap_hit", SND_CHANNEL_BODY, 0, 0, 0 ); + leapDidAttack = true; + } + } +} + +/* +================ +rvMonsterTeleportDropper::State_CombatHide +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_CombatHide ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) ) { + if ( !move.fl.moving ) { + TurnToward ( enemy.lastKnownPosition ); + } + } + + if ( !(FilterTactical( combat.tacticalMaskAvailable )&AITACTICAL_HIDE_BIT) ) + {//shouldn't hide anymore + ForceTacticalUpdate(); + } + + if ( UpdateTactical ( 5000 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; +} + +/* +void rvMonsterTeleportDropper::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + idAI::OnStopMoving(oldMoveCommand); + dropAtGoalOnly = false; +} + +bool rvMonsterTeleportDropper::MoveToEnemy ( void ) { + dropAtGoalOnly = false; + if ( !targets.Num() ) + { + return (idAI::MoveToEnemy()); + } + if ( !GetEnemy() ) + { + return false; + } + idEntity* goalEnt; + idEntity* bestGoal = NULL; + int areaNum = 0; + aasPath_t path; + idVec3 pos; + float dist; + float bestDist = actionDropSpawners.maxRange; + + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + if ( !aiManager.ValidateDestination(this,goalEnt->GetPhysics()->GetOrigin()) ) + { + continue; + } + dist = GetEnemy()->DistanceTo(pos); + if ( dist >= bestDist ) + { + continue; + } + // See if it's possible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + continue; + } + } + dist = bestDist; + bestGoal = goalEnt; + } + if ( bestGoal ) + { + if ( MoveToEntity( bestGoal, bestGoal->spawnArgs.GetFloat("range","64") ) ) + { + dropAtGoalOnly = true; + return true; + } + } + return (idAI::MoveToEnemy()); +} +*/ + +/* +================ +rvMonsterTeleportDropper::FilterTactical +================ +*/ +int rvMonsterTeleportDropper::FilterTactical ( int availableTactical ) { + //do normal filter + availableTactical = idAI::FilterTactical ( availableTactical ); + + //being tethered removes these - add them back in, we don't obey tethers! tethers are for chumps! + availableTactical |= (AITACTICAL_MELEE_BIT|AITACTICAL_HIDE_BIT); + + // Only allow hiding while the drop spawner action isnt ready + if ( !actionDropSpawners.timer.IsDone ( actionTime ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + // Hide while the spawner is still active + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + if ( leftSideBlocked && rightSideBlocked ) + {//both sides are blocked + if ( move.fl.done ) + {//not trying to move + //get out of here! + ForceTacticalUpdate(); + //try finding somewhere else to stand! + availableTactical |= AITACTICAL_RANGED_BIT; + } + } + return availableTactical; +} + +/* +================ +rvMonsterTeleportDropper::CheckAction_DropSpawners +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_DropSpawners ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + //Check to see if at least one side is open + leftSideBlocked = rightSideBlocked = false; + + //trace against solid architecture only, don't care about other entities + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + start = GetPhysics()->GetCenterMass(); + //NOTE: ASSUMPTION + int mask = (MASK_SOLID|CONTENTS_LARGESHOTCLIP|CONTENTS_MOVEABLECLIP|CONTENTS_MONSTERCLIP); + //check left + end = start + (viewAxis[1] * 64.0f); + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //left side is blocked + leftSideBlocked = true; + } + //check right + end = start - (viewAxis[1] * 64.0f); + //trace against solid architecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //right side blocked + rightSideBlocked = true; + } + if ( leftSideBlocked && rightSideBlocked ) + { + return false; + } + + /* + if ( dropAtGoalOnly && !move.fl.done && move.moveCommand == MOVE_TO_ENTITY ) + {//FIXME: check ReachedPos? + return false; + } + //fuck it, just check them all + if ( targets.Num() ) + { + idEntity* goalEnt; + idVec3 pos; + float dist; + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + dist = DistanceTo(pos); + if ( dist <= goalEnt->spawnArgs.GetFloat("range","64") ) + { + return true; + } + } + return false; + } + */ + + return true; +} + +/* +================ +rvMonsterTeleportDropper::Collide +================ +*/ + +/* +================ +rvMonsterTeleportDropper::CheckAction_LeapAttack +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + if ( !move.fl.done + && !move.fl.blocked ) + {//still running away + return false; + } + //done running, okay to attack if in range + } + else + { + if ( !leftSideBlocked || !rightSideBlocked ) + {//clear to shoot on sides + return false; + } + } + + if ( idAI::CheckAction_LeapAttack( action, animNum ) ) + { + leapDidAttack = false; + return true; + } + return false; +} + +/* +================ +rvMonsterTeleportDropper::CheckActions +================ +*/ +bool rvMonsterTeleportDropper::CheckActions ( void ) { + if ( PerformAction ( &actionDropSpawners, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_DropSpawners ) ) { + return true; + } + if ( CheckPainActions ( ) ) { + return true; + } + if ( PerformAction ( &actionLeapAttack, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_LeapAttack ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterTeleportDropper::AttackMissileExt +================ +*/ +idProjectile* rvMonsterTeleportDropper::AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + idProjectile* proj; + + // Launch the projectile + if ( leftSideBlocked || rightSideBlocked ) + { + if ( idStr::Icmp( attackName, "dropSpawner" ) == 0 ) + { + if ( leftSideBlocked ) + { + if ( joint == jointLeftFrontCannon + || joint == jointLeftRearCannon ) + { + return NULL; + } + } + else if ( rightSideBlocked ) + { + if ( joint == jointRightFrontCannon + || joint == jointRightRearCannon ) + { + return NULL; + } + } + } + } + proj = idAI::AttackRanged ( attackName, attackDict, joint, target, pushVelocity ); + + if ( !proj ) { + return NULL; + } + + // If it was a spawner projectile set the spawer + if ( proj->IsType ( rvSpawnerProjectile::GetClassType() ) ) { + static_cast(proj)->SetSpawner ( spawner ); + } + + return proj; +} + +/* +================ +rvMonsterTeleportDropper::GetIdleAnimName +================ +*/ +const char* rvMonsterTeleportDropper::GetIdleAnimName ( void ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) { + return "idle_hide"; + } + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterTeleportDropper::GetDebugInfo +================ +*/ +void rvMonsterTeleportDropper::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterTeleportDropper", "action_dropSpawners", aiActionStatusString[actionDropSpawners.status], userData ); + //proc ( "rvMonsterTeleportDropper", "dropAtGoalOnly", dropAtGoalOnly?"true":"false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTeleportDropper ) + STATE ( "State_CombatHide", rvMonsterTeleportDropper::State_CombatHide ) + STATE ( "Torso_DropSpawners", rvMonsterTeleportDropper::State_Torso_DropSpawners ) +END_CLASS_STATES + +/* +================ +rvMonsterTeleportDropper::State_Torso_DropSpawners +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_Torso_DropSpawners ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "drop_spawners", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ForceTacticalUpdate ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/src/game/ai/Monster_Turret.cpp b/src/game/ai/Monster_Turret.cpp new file mode 100644 index 0000000..2b2243f --- /dev/null +++ b/src/game/ai/Monster_Turret.cpp @@ -0,0 +1,227 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurret : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurret ); + + rvMonsterTurret ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + virtual bool CheckActions ( void ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTurret ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurret ) +END_CLASS + +/* +================ +rvMonsterTurret::rvMonsterTurret +================ +*/ +rvMonsterTurret::rvMonsterTurret ( ) { + shieldHealth = 0; +} + +void rvMonsterTurret::InitSpawnArgsVariables ( void ) { + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurret::Spawn +================ +*/ +void rvMonsterTurret::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + health += shieldHealth; + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterTurret::Save +================ +*/ +void rvMonsterTurret::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterTurret::Restore +================ +*/ +void rvMonsterTurret::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTurret::CheckActions +================ +*/ +bool rvMonsterTurret::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterTurret::Pain +================ +*/ +bool rvMonsterTurret::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurret ) + STATE ( "State_Combat", rvMonsterTurret::State_Combat ) + STATE ( "State_Killed", rvMonsterTurret::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurret::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurret::State_Combat +================ +*/ +stateResult_t rvMonsterTurret::State_Combat ( const stateParms_t& parms ) { + // Aquire a new enemy if we dont have one + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + FaceEnemy ( ); + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurret::State_Killed +================ +*/ +stateResult_t rvMonsterTurret::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_death" ), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurret::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurret::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/Monster_TurretFlying.cpp b/src/game/ai/Monster_TurretFlying.cpp new file mode 100644 index 0000000..3453387 --- /dev/null +++ b/src/game/ai/Monster_TurretFlying.cpp @@ -0,0 +1,320 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurretFlying : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurretFlying ); + + rvMonsterTurretFlying ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual bool CheckActions ( void ); + +protected: + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Fall ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + CLASS_STATES_PROTOTYPE ( rvMonsterTurretFlying ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurretFlying ) +END_CLASS + +/* +================ +rvMonsterTurretFlying::rvMonsterTurretFlying +================ +*/ +rvMonsterTurretFlying::rvMonsterTurretFlying ( ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); +} + +void rvMonsterTurretFlying::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurretFlying::Spawn +================ +*/ +void rvMonsterTurretFlying::Spawn ( void ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + + InitSpawnArgsVariables(); + shots = 0; + + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + //don't take damage until we open up + fl.takedamage = false; +} + +/* +================ +rvMonsterTurretFlying::CheckActions +================ +*/ +bool rvMonsterTurretFlying::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + + +/* +================ +rvMonsterTurretFlying::Pain +================ +*/ +bool rvMonsterTurretFlying::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterTurretFlying::Save +================ +*/ +void rvMonsterTurretFlying::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ) ; +} + +/* +================ +rvMonsterTurretFlying::Restore +================ +*/ +void rvMonsterTurretFlying::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ) ; + + InitSpawnArgsVariables(); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurretFlying ) + STATE ( "State_Combat", rvMonsterTurretFlying::State_Combat ) + STATE ( "State_Fall", rvMonsterTurretFlying::State_Fall ) + STATE ( "State_Killed", rvMonsterTurretFlying::State_Killed ) + + STATE ( "Torso_Idle", rvMonsterTurretFlying::State_Torso_Idle ) + STATE ( "Legs_Idle", rvMonsterTurretFlying::State_Legs_Idle ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurretFlying::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurretFlying::State_Combat +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Combat ( const stateParms_t& parms ) { + // Special handling for not being on the ground + if ( !move.fl.onGround ) { + PostState ( "State_Fall", 0 ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurretFlying::State_Fall +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact + STAGE_WAITOPEN, // Wait for drop turret to open up + STAGE_OPENED, // Drop turret opened and ready for combat + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle_closed", 0 ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( move.fl.onGround ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + PlayAnim ( ANIMCHANNEL_TORSO, "open", 2 ); + return SRESULT_STAGE ( STAGE_WAITOPEN ); + + case STAGE_WAITOPEN: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_OPENED ); + } + return SRESULT_WAIT; + + case STAGE_OPENED: + // Activate shield + fl.takedamage = true; + health += shieldHealth; + PlayEffect ( "fx_shieldOpen", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + PostState ( "State_Combat" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterTurretFlying::State_Killed +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurretFlying::State_Torso_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_Idle ( const stateParms_t& parms ) { + if ( move.fl.onGround ) { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_open", parms.blendFrames ); + } else { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_closed", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Legs_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Legs_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, (shots&1)?"range_attack_top":"range_attack_bottom", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/ai/VehicleAI.cpp b/src/game/ai/VehicleAI.cpp new file mode 100644 index 0000000..f74c975 --- /dev/null +++ b/src/game/ai/VehicleAI.cpp @@ -0,0 +1,266 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// rvVehicleAI.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +CLASS_DECLARATION( idAI, rvVehicleAI ) + EVENT( VD_ChoosePathTarget, rvVehicleAI::Event_ChoosePathTarget ) +END_CLASS + +/* +================ +rvVehicleAI::rvVehicleAI +================ +*/ +rvVehicleAI::rvVehicleAI ( void ) { + flags = 0; +} + +/* +================ +rvVehicleAI::~rvVehicleAI +================ +*/ +rvVehicleAI::~rvVehicleAI ( void ) { +} + +/* +================ +rvVehicleAI::Spawn +================ +*/ +void rvVehicleAI::Spawn ( void ) { + driver = static_cast( gameLocal.SpawnEntityType( rvVehicleDriver::GetClassType() ) ); +} + +/* +================ +rvVehicleAI::Save +================ +*/ +void rvVehicleAI::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); + savefile->WriteInt ( flags ); +} + +/* +================ +rvVehicleAI::Restore +================ +*/ +void rvVehicleAI::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); + savefile->ReadInt ( flags ); +} + +/* +================ +rvVehicleAI::SetVehicle +================ +*/ +void rvVehicleAI::SetVehicle ( rvVehicleMonster * vehicle ) { + const idKeyValue * val = spawnArgs.MatchPrefix( "target", 0 ); + + if ( !driver ) { + return; + } + + driver->ProcessEvent ( &AI_EnterVehicle, vehicle ); + driver->SetPathingMode( rvVehicleDriver::VDPM_Custom, this ); + vehicle->ProcessEvent ( &EV_Door_Lock, true ); + vehicle->team = team; + driver->team = team; + + if ( val ) { + driver->ProcessEvent ( &AI_ScriptedMove, gameLocal.FindEntity( val->GetValue() ), 0.0f, 0 ); + } else { + driver->ProcessEvent ( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::Event_ChoosePathTarget +================ +*/ +void rvVehicleAI::Event_ChoosePathTarget ( idEntity * current ) { + if ( !current ) { + current = const_cast( FindClosestNode() ); + } + + if ( ( flags & VAIF_Freeze ) == 0 ) { + const idVec3 & playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + switch ( flags & 0x03 ) { + case 1 : + idThread::ReturnEntity( MoveCloserTo( playerOrigin, current ) ); + break; + + case 2 : + idThread::ReturnEntity( MoveAwayFrom( playerOrigin, current ) ); + break; + + default: + idThread::ReturnEntity( NULL ); + } + } + + //PostEventMS( &VD_ChoosePathTarget, gameLocal.random.RandomInt( 100 ) + 100, driver->lastPathTargetInfo.node.GetEntity() ); +} + +/* +================ +rvVehicleAI::OnWakeUp +================ +*/ +void rvVehicleAI::OnWakeUp ( void ) { + SetMoveType ( MOVETYPE_CUSTOM ); +} + +/* +================ +rvVehicleAI::CustomMove +================ +*/ +void rvVehicleAI::CustomMove ( void ) { + if ( !driver->pathTargetInfo.node && !(flags & VAIF_Freeze) ) { + driver->ProcessEvent( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::MoveCloserTo +================ +*/ +const idEntity * rvVehicleAI::MoveCloserTo ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float shortestDistance = FLT_MAX; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( shortestDistance > distance ) { + shortestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::MoveAwayFrom +================ +*/ +const idEntity * rvVehicleAI::MoveAwayFrom ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float longestDistance = FLT_MIN; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( longestDistance < distance ) { + longestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::FindClosestNode +================ +*/ +const idEntity * rvVehicleAI::FindClosestNode ( void ) const { + idEntity * current = 0; + const idEntity * best = 0; + float bestDistance = FLT_MAX; + + for (;;) { + current = gameLocal.FindEntityUsingDef( current, "target_vehicle_path" ); + + if ( !current ) { + break; + } + + float dist = ( current->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist < bestDistance ) { + bestDistance = dist; + best = current; + } + } + + return best; +} + +/* +================ +rvVehicleAI::Think +================ +*/ +void rvVehicleAI::Think ( void ) { + idAI::Think(); + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + //HACK: always choose player as the enemy... something is broken somewhere, + // and this is a quick way to get some gameplay out of this for now. Deadlines pwn. + if ( driver && driver->IsDriving() ) { + enemy.ent = gameLocal.GetLocalPlayer(); + enemy.range = ( enemy.ent->GetPhysics()->GetOrigin() - driver->vehicleController.GetVehicle()->GetPhysics()->GetOrigin() ).Length(); + } +} + + diff --git a/src/game/ai/VehicleAI.h b/src/game/ai/VehicleAI.h new file mode 100644 index 0000000..505c0c8 --- /dev/null +++ b/src/game/ai/VehicleAI.h @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// rvVehicleAI.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEAI_H__ +#define __GAME_VEHICLEAI_H__ + +#ifndef __AI_H__ +#include "AI.h" +#endif +#ifndef __GAME_VEHICLEMONSTER_H__ +#include "../vehicle/VehicleMonster.h" +#endif + +enum VehicleAI_Flags { + VAIF_Chase = 1, + VAIF_Avoid = 2, + VAIF_Freeze = 4, +}; + +class rvVehicleAI : public idAI { + friend class rvVehicleMonster; + +public: + CLASS_PROTOTYPE( rvVehicleAI ); + + rvVehicleAI ( void ); + ~rvVehicleAI ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void SetVehicle ( rvVehicleMonster * vehicle ); + + void Random ( void ); + void StraightToEnemy ( void ); + void ChaseEnemy ( void ); + void AvoidEnemy ( void ); + void Stop ( void ); + void Start ( void ); + + int & GetFlags ( void ) { return flags; } + + bool IsDriving ( void ) { return driver && driver->IsDriving(); } + rvVehicleDriver* GetDriver ( void ) { return driver.GetEntity(); } + const rvVehicleDriver* GetDriver ( void ) const { return driver.GetEntity(); } + +private: + virtual void OnWakeUp ( void ); + virtual void CustomMove ( void ); + + const idEntity * MoveCloserTo ( const idVec3 & point, idEntity * current ); + const idEntity * MoveAwayFrom ( const idVec3 & point, idEntity * current ); + const idEntity * FindClosestNode ( void ) const; + + void Event_ChoosePathTarget ( idEntity * current ); + + idEntityPtr driver; + int flags; +}; + +ID_INLINE void rvVehicleAI::Random ( void ) { + flags = 0; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::StraightToEnemy ( void ) { + driver->ProcessEvent( &AI_ScriptedMove, enemy.ent.GetEntity(), 0.0f, 0 ); +} + +ID_INLINE void rvVehicleAI::ChaseEnemy ( void ) { + flags = VAIF_Chase; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::AvoidEnemy ( void ) { + flags = VAIF_Avoid; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::Stop ( void ) { + flags = ( flags & 0x03 ) | VAIF_Freeze; + driver->ProcessEvent( &AI_ScriptedStop ); +} + +ID_INLINE void rvVehicleAI::Start ( void ) { + flags = ( flags & 0x03 ) & ~VAIF_Freeze; + //driver->ProcessEvent( &EV_Activate, this ); + CustomMove(); +} + +#endif // __GAME_VEHICLEAI_H__ diff --git a/src/game/anim/Anim.cpp b/src/game/anim/Anim.cpp new file mode 100644 index 0000000..b42f220 --- /dev/null +++ b/src/game/anim/Anim.cpp @@ -0,0 +1,1205 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +bool idAnimManager::forceExport = false; + +/*********************************************************************** + + idMD5Anim + +***********************************************************************/ + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::idMD5Anim() { + ref_count = 0; + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + totaldelta.Zero(); +} + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::~idMD5Anim() { + Free(); +} + +/* +==================== +idMD5Anim::Free +==================== +*/ +void idMD5Anim::Free( void ) { + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + name = ""; + + totaldelta.Zero(); + + jointInfo.Clear(); + bounds.Clear(); + componentFrames.Clear(); +} + +/* +==================== +idMD5Anim::NumFrames +==================== +*/ +int idMD5Anim::NumFrames( void ) const { + return numFrames; +} + +/* +==================== +idMD5Anim::NumJoints +==================== +*/ +int idMD5Anim::NumJoints( void ) const { + return numJoints; +} + +/* +==================== +idMD5Anim::Length +==================== +*/ +int idMD5Anim::Length( void ) const { + return animLength; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const idVec3 &idMD5Anim::TotalMovementDelta( void ) const { + return totaldelta; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const char *idMD5Anim::Name( void ) const { + return name; +} + +/* +==================== +idMD5Anim::Reload +==================== +*/ +bool idMD5Anim::Reload( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idStr filename; + + filename = name; + Free(); + + return LoadAnim( filename ); +} + +/* +==================== +idMD5Anim::Allocated +==================== +*/ +size_t idMD5Anim::Allocated( void ) const { + size_t size = bounds.Allocated() + jointInfo.Allocated() + componentFrames.Allocated() + name.Allocated(); + return size; +} + +/* +==================== +idMD5Anim::LoadAnim +==================== +*/ +bool idMD5Anim::LoadAnim( const char *filename ) { + int version; +// RAVEN BEGIN +// jsinger: done this way to minimize amount of code change + idAutoPtr lexer( LexerFactory::MakeLexer(LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT) ); + Lexer &parser(*lexer); +// RAVEN END + idToken token; + int i, j; + int num; + + if ( !parser.LoadFile( filename ) ) { + return false; + } + + Free(); + + name = filename; + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse num joints + parser.ExpectTokenString( "numJoints" ); + numJoints = parser.ParseInt(); + if ( numJoints <= 0 ) { + parser.Error( "Invalid number of joints: %d", numJoints ); + } + + // parse frame rate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate < 0 ) { + parser.Error( "Invalid frame rate: %d", frameRate ); + } + + // parse number of animated components + parser.ExpectTokenString( "numAnimatedComponents" ); + numAnimatedComponents = parser.ParseInt(); + if ( ( numAnimatedComponents < 0 ) || ( numAnimatedComponents > numJoints * 6 ) ) { + parser.Error( "Invalid number of animated components: %d", numAnimatedComponents ); + } + + // parse the hierarchy + jointInfo.SetGranularity( 1 ); + jointInfo.SetNum( numJoints ); + parser.ExpectTokenString( "hierarchy" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + parser.ReadToken( &token ); +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + jointInfo[ i ].nameIndex = animationLib->JointIndex( token ); +// RAVEN END + + // parse parent num + jointInfo[ i ].parentNum = parser.ParseInt(); + if ( jointInfo[ i ].parentNum >= i ) { + parser.Error( "Invalid parent num: %d", jointInfo[ i ].parentNum ); + } + + if ( ( i != 0 ) && ( jointInfo[ i ].parentNum < 0 ) ) { + parser.Error( "Animations may have only one root joint" ); + } + + // parse anim bits + jointInfo[ i ].animBits = parser.ParseInt(); + if ( jointInfo[ i ].animBits & ~63 ) { + parser.Error( "Invalid anim bits: %d", jointInfo[ i ].animBits ); + } + + // parse first component + jointInfo[ i ].firstComponent = parser.ParseInt(); + if ( ( numAnimatedComponents > 0 ) && ( ( jointInfo[ i ].firstComponent < 0 ) || ( jointInfo[ i ].firstComponent >= numAnimatedComponents ) ) ) { + parser.Error( "Invalid first component: %d", jointInfo[ i ].firstComponent ); + } + } + + parser.ExpectTokenString( "}" ); + + // parse bounds + parser.ExpectTokenString( "bounds" ); + parser.ExpectTokenString( "{" ); + bounds.SetGranularity( 1 ); + bounds.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, bounds[ i ][ 0 ].ToFloatPtr() ); + parser.Parse1DMatrix( 3, bounds[ i ][ 1 ].ToFloatPtr() ); + } + parser.ExpectTokenString( "}" ); + + // parse base frame + baseFrame.SetGranularity( 1 ); + baseFrame.SetNum( numJoints ); + parser.ExpectTokenString( "baseframe" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + idCQuat q; + parser.Parse1DMatrix( 3, baseFrame[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, q.ToFloatPtr() );//baseFrame[ i ].q.ToFloatPtr() ); + baseFrame[ i ].q = q.ToQuat();//.w = baseFrame[ i ].q.CalcW(); + } + parser.ExpectTokenString( "}" ); + + // parse frames + componentFrames.SetGranularity( 1 ); + componentFrames.SetNum( numAnimatedComponents * numFrames ); + + float *componentPtr = componentFrames.Ptr(); + for( i = 0; i < numFrames; i++ ) { + parser.ExpectTokenString( "frame" ); + num = parser.ParseInt(); + if ( num != i ) { + parser.Error( "Expected frame number %d", i ); + } + parser.ExpectTokenString( "{" ); + + for( j = 0; j < numAnimatedComponents; j++, componentPtr++ ) { + *componentPtr = parser.ParseFloat(); + } + + parser.ExpectTokenString( "}" ); + } + + // get total move delta + if ( !numAnimatedComponents ) { + totaldelta.Zero(); + } else { + componentPtr = &componentFrames[ jointInfo[ 0 ].firstComponent ]; + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.x; + } + totaldelta.x = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.x = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.y; + } + totaldelta.y = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.y = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.z; + } + totaldelta.z = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + } else { + totaldelta.z = 0.0f; + } + } + baseFrame[ 0 ].t.Zero(); + + // we don't count last frame because it would cause a 1 frame pause at the end + animLength = ( ( numFrames - 1 ) * 1000 + frameRate - 1 ) / frameRate; + + // done + return true; +} + +/* +==================== +idMD5Anim::IncreaseRefs +==================== +*/ +void idMD5Anim::IncreaseRefs( void ) const { + ref_count++; +} + +/* +==================== +idMD5Anim::DecreaseRefs +==================== +*/ +void idMD5Anim::DecreaseRefs( void ) const { + ref_count--; +} + +/* +==================== +idMD5Anim::NumRefs +==================== +*/ +int idMD5Anim::NumRefs( void ) const { + return ref_count; +} + +/* +==================== +idMD5Anim::ConvertTimeToFrame +==================== +*/ +void idMD5Anim::ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const { + int frameTime; + int frameNum; + + if ( numFrames <= 1 ) { + frame.frame1 = 0; + frame.frame2 = 0; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + if ( time <= 0 ) { + frame.frame1 = 0; + frame.frame2 = 1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + frameTime = time * frameRate; + frameNum = frameTime / 1000; + frame.cycleCount = frameNum / ( numFrames - 1 ); + + if ( ( cyclecount > 0 ) && ( frame.cycleCount >= cyclecount ) ) { + frame.cycleCount = cyclecount - 1; + frame.frame1 = numFrames - 1; + frame.frame2 = frame.frame1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + return; + } + + frame.frame1 = frameNum % ( numFrames - 1 ); + frame.frame2 = frame.frame1 + 1; + if ( frame.frame2 >= numFrames ) { + frame.frame2 = 0; + } + + frame.backlerp = ( frameTime % 1000 ) * 0.001f; + frame.frontlerp = 1.0f - frame.backlerp; +} + +// RAVEN BEGIN +// jscott: added block +/* +==================== +idMD5Anim::ConvertFrameToTime + +MD5_FRAMERATE is always 24 +==================== +*/ +int idMD5Anim::ConvertFrameToTime( frameBlend_t &frame ) const { + int time; + + // Adjust the time so the lerping doesn't break the reverse calc + time = ( ( frame.frame1 % (numFrames - 1)) * 1000 ) / frameRate; + time += 500 / frameRate; + + return( time ); +} +// RAVEN END + +/* +==================== +idMD5Anim::GetOrigin +==================== +*/ +void idMD5Anim::GetOrigin( idVec3 &offset, int time, int cyclecount ) const { + frameBlend_t frame; + + offset = baseFrame[ 0 ].t; + if ( !( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) ) { + // just use the baseframe + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + + if ( frame.cycleCount ) { + offset += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetOriginRotation +==================== +*/ +void idMD5Anim::GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const { + frameBlend_t frame; + int animBits; + + animBits = jointInfo[ 0 ].animBits; + if ( !( animBits & ( ANIM_QX | ANIM_QY | ANIM_QZ ) ) ) { + // just use the baseframe + rotation = baseFrame[ 0 ].q; + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *jointframe1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *jointframe2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( animBits & ANIM_TX ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TY ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TZ ) { + jointframe1++; + jointframe2++; + } + + idQuat q1; + idQuat q2; + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case ANIM_QX: + q1.x = jointframe1[0]; + q2.x = jointframe2[0]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY: + q1.y = jointframe1[0]; + q2.y = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QZ: + q1.z = jointframe1[0]; + q2.z = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + q1.x = jointframe1[0]; + q1.z = jointframe1[1]; + q2.x = jointframe2[0]; + q2.z = jointframe2[1]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + q1.y = jointframe1[0]; + q1.z = jointframe1[1]; + q2.y = jointframe2[0]; + q2.z = jointframe2[1]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q1.z = jointframe1[2]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q2.z = jointframe2[2]; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + } + + rotation.Slerp( q1, q2, frame.backlerp ); +} + +/* +==================== +idMD5Anim::GetBounds +==================== +*/ +void idMD5Anim::GetBounds( idBounds &bnds, int time, int cyclecount ) const { + frameBlend_t frame; + idVec3 offset; + + ConvertTimeToFrame( time, cyclecount, frame ); + + bnds = bounds[ frame.frame1 ]; + bnds.AddBounds( bounds[ frame.frame2 ] ); + + // origin position + offset = baseFrame[ 0 ].t; + if ( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) { + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + } + + bnds[ 0 ] -= offset; + bnds[ 1 ] -= offset; +} + +/* +==================== +idMD5Anim::GetInterpolatedFrame +==================== +*/ +void idMD5Anim::GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const { + int i, numLerpJoints; + const float *frame1; + const float *frame2; + const float *jointframe1; + const float *jointframe2; + const jointAnimInfo_t *infoPtr; + int animBits; + idJointQuat *blendJoints; + idJointQuat *jointPtr; + idJointQuat *blendPtr; + int *lerpIndex; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return; + } +#endif + + if ( !numAnimatedComponents ) { + // just use the base frame + return; + } + + blendJoints = (idJointQuat *)_alloca16( baseFrame.Num() * sizeof( blendPtr[ 0 ] ) ); + lerpIndex = (int *)_alloca16( baseFrame.Num() * sizeof( lerpIndex[ 0 ] ) ); + numLerpJoints = 0; + + frame1 = &componentFrames[ frame.frame1 * numAnimatedComponents ]; + frame2 = &componentFrames[ frame.frame2 * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + blendPtr = &blendJoints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + lerpIndex[numLerpJoints++] = j; + + jointframe1 = frame1 + infoPtr->firstComponent; + jointframe2 = frame2 + infoPtr->firstComponent; + + switch( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + case 0: + blendPtr->t = jointPtr->t; + break; + case ANIM_TX: + jointPtr->t.x = jointframe1[0]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointPtr->t.y; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TY: + jointPtr->t.y = jointframe1[0]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TZ: + jointPtr->t.z = jointframe1[0]; + blendPtr->t.z = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.y = jointPtr->t.y; + jointframe1++; + jointframe2++; + break; + case ANIM_TX|ANIM_TY: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointPtr->t.z; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.y = jointPtr->t.y; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TY|ANIM_TZ: + jointPtr->t.y = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.x = jointPtr->t.x; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TY|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + jointPtr->t.z = jointframe1[2]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointframe2[2]; + jointframe1 += 3; + jointframe2 += 3; + break; + } + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case 0: + blendPtr->q = jointPtr->q; + break; + case ANIM_QX: + jointPtr->q.x = jointframe1[0]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointPtr->q.y; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY: + jointPtr->q.y = jointframe1[0]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QZ: + jointPtr->q.z = jointframe1[0]; + blendPtr->q.z = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + jointPtr->q.y = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.x = jointPtr->q.x; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + jointPtr->q.z = jointframe1[2]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointframe2[2]; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + } + } + } + + SIMDProcessor->BlendJoints( joints, blendJoints, frame.backlerp, lerpIndex, numLerpJoints ); + + if ( frame.cycleCount ) { + joints[ 0 ].t += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetSingleFrame +==================== +*/ +void idMD5Anim::GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const { + int i; + const float *frame; + const float *jointframe; + int animBits; + idJointQuat *jointPtr; + const jointAnimInfo_t *infoPtr; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + + if ( ( framenum == 0 ) || !numAnimatedComponents ) { + // just use the base frame + return; + } + + frame = &componentFrames[ framenum * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + jointframe = frame + infoPtr->firstComponent; + + if ( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + + if ( animBits & ANIM_TX ) { + jointPtr->t.x = *jointframe++; + } + + if ( animBits & ANIM_TY ) { + jointPtr->t.y = *jointframe++; + } + + if ( animBits & ANIM_TZ ) { + jointPtr->t.z = *jointframe++; + } + } + + if ( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + + if ( animBits & ANIM_QX ) { + jointPtr->q.x = *jointframe++; + } + + if ( animBits & ANIM_QY ) { + jointPtr->q.y = *jointframe++; + } + + if ( animBits & ANIM_QZ ) { + jointPtr->q.z = *jointframe; + } + + jointPtr->q.w = jointPtr->q.CalcW(); + } + } + } +} + +/* +==================== +idMD5Anim::CheckModelHierarchy +==================== +*/ +void idMD5Anim::CheckModelHierarchy( const idRenderModel *model ) const { + int i; + int jointNum; + int parent; + + if ( jointInfo.Num() != model->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Error( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), model->NumJoints(), name.c_str(), jointInfo.Num() ); +// scork: if we don't return here, we get dozens of other warnings generated by mismatching models below, one warning is sufficient... + if (common->DoingDeclValidation()) { + return; + } +// RAVEN END + } + + const idMD5Joint *modelJoints = model->GetJoints(); + for( i = 0; i < jointInfo.Num(); i++ ) { + jointNum = jointInfo[ i ].nameIndex; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + if ( modelJoints[ i ].name != animationLib->JointName( jointNum ) ) { +// RAVEN END + gameLocal.Error( "Model '%s''s joint names don't match anim '%s''s", model->Name(), name.c_str() ); + } + if ( modelJoints[ i ].parent ) { + parent = modelJoints[ i ].parent - modelJoints; + } else { + parent = -1; + } + if ( parent != jointInfo[ i ].parentNum ) { + gameLocal.Error( "Model '%s' has different joint hierarchy than anim '%s'", model->Name(), name.c_str() ); + } + } +} + +/*********************************************************************** + + idAnimManager + +***********************************************************************/ + +/* +==================== +idAnimManager::idAnimManager +==================== +*/ +idAnimManager::idAnimManager() { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + insideLevelLoad=false; +#endif +// RAVEN END +} + +/* +==================== +idAnimManager::~idAnimManager +==================== +*/ +idAnimManager::~idAnimManager() { + Shutdown(); +} + +/* +==================== +idAnimManager::Shutdown +==================== +*/ +void idAnimManager::Shutdown( void ) { + animations.DeleteContents(); + jointnames.Clear(); + jointnamesHash.Free(); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +==================== +idAnimManager::BeginLevelLoad +==================== +*/ +void idAnimManager::BeginLevelLoad( void ) +{ + insideLevelLoad = true; +} + +/* +==================== +idAnimManager::EndLevelLoad +==================== +*/ +void idAnimManager::EndLevelLoad( void ) +{ + insideLevelLoad = false; +} +#endif +// RAVEN END + +/* +==================== +idAnimManager::GetAnim +==================== +*/ +idMD5Anim *idAnimManager::GetAnim( const char *name ) { + idMD5Anim **animptrptr; + idMD5Anim *anim; + + // see if it has been asked for before + animptrptr = NULL; + if ( animations.Get( name, &animptrptr ) ) { + anim = *animptrptr; + } else { + idStr extension; + idStr filename = name; + + filename.ExtractFileExtension( extension ); + if ( extension != MD5_ANIM_EXT ) { +// RAVEN BEGIN +// nmckenzie: I'm not interested in debugging this blindly again. + gameLocal.Warning( "Animation '%s' doesn't have the correct extension for an animation file.", filename.c_str() ); +// RAVEN END + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(insideLevelLoad?RV_HEAP_ID_LEVEL:RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + anim = new idMD5Anim(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + if ( !anim->LoadAnim( filename ) ) { + gameLocal.Warning( "Couldn't load anim: '%s'", filename.c_str() ); + delete anim; + anim = NULL; + } + animations.Set( filename, anim ); + } + + return anim; +} + +/* +================ +idAnimManager::ReloadAnims +================ +*/ +void idAnimManager::ReloadAnims( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + ( *animptr )->Reload(); + } + } +} + +/* +================ +idAnimManager::JointIndex +================ +*/ +int idAnimManager::JointIndex( const char *name ) { + int i, hash; + + hash = jointnamesHash.GenerateKey( name ); + for ( i = jointnamesHash.First( hash ); i != -1; i = jointnamesHash.Next( i ) ) { + if ( jointnames[i].Cmp( name ) == 0 ) { + return i; + } + } + + i = jointnames.Append( name ); + jointnamesHash.Add( hash, i ); + return i; +} + +/* +================ +idAnimManager::JointName +================ +*/ +const char *idAnimManager::JointName( int index ) const { + return jointnames[ index ]; +} + +/* +================ +idAnimManager::ListAnims +================ +*/ +void idAnimManager::ListAnims( void ) const { + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t s; + size_t namesize; + int num; + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + s = anim->Size(); + gameLocal.Printf( "%8d bytes : %2d refs : %s\n", s, anim->NumRefs(), anim->Name() ); + size += s; + num++; + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + gameLocal.Printf( "\n%d memory used in %d anims\n", size, num ); + gameLocal.Printf( "%d memory used in %d joint names\n", namesize, jointnames.Num() ); +} + +// RAVEN BEGIN +/* +================ +idAnimManager::PrintMemInfo +================ +*/ +void idAnimManager::PrintMemInfo( MemInfo *mi ) { + + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t namesize; + int num; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_anim.txt" ); + if( !f ) { + return; + } + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + size += anim->Size(); + num++; + + f->Printf( "%8d: %s\n", anim->Size(), anim->Name() ); + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + mi->animsAssetsCount = num; + mi->animsAssetsTotal = namesize + size; + + f->Printf( "\nTotal anim bytes allocated: %s (%s items)\n", idStr::FormatNumber( mi->animsAssetsTotal ).c_str(), idStr::FormatNumber( mi->animsAssetsCount ).c_str() ); + fileSystem->CloseFile( f ); +} +// RAVEN END + +/* +================ +idAnimManager::FlushUnusedAnims +================ +*/ +void idAnimManager::FlushUnusedAnims( void ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + idList removeAnims; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + if ( ( *animptr )->NumRefs() <= 0 ) { + removeAnims.Append( *animptr ); + } + } + } + + for( i = 0; i < removeAnims.Num(); i++ ) { + animations.Remove( removeAnims[ i ]->Name() ); + delete removeAnims[ i ]; + } +} diff --git a/src/game/anim/Anim.h b/src/game/anim/Anim.h new file mode 100644 index 0000000..db0bf64 --- /dev/null +++ b/src/game/anim/Anim.h @@ -0,0 +1,825 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __ANIM_H__ +#define __ANIM_H__ + +// +// animation channels +// these can be changed by modmakers and licensees to be whatever they need. +const int ANIM_NumAnimChannels = 5; +const int ANIM_MaxAnimsPerChannel = 3; +const int ANIM_MaxSyncedAnims = 3; + +// +// animation channels. make sure to change script/doom_defs.script if you add any channels, or change their order +// +const int ANIMCHANNEL_ALL = 0; +const int ANIMCHANNEL_TORSO = 1; +const int ANIMCHANNEL_LEGS = 2; +const int ANIMCHANNEL_HEAD = 3; +const int ANIMCHANNEL_EYELIDS = 4; + +// for converting from 24 frames per second to milliseconds +ID_INLINE int FRAME2MS( int framenum ) { + return ( framenum * 1000 ) / 24; +} + +class idRenderModel; +class idAnimator; +class idAnimBlend; +class function_t; +class idEntity; +class idSaveGame; +class idRestoreGame; + +typedef struct { + int cycleCount; // how many times the anim has wrapped to the begining (0 for clamped anims) + int frame1; + int frame2; + float frontlerp; + float backlerp; +} frameBlend_t; + +typedef struct { + int nameIndex; + int parentNum; + int animBits; + int firstComponent; +} jointAnimInfo_t; + +typedef struct { + jointHandle_t num; + jointHandle_t parentNum; + int channel; +} jointInfo_t; + +// +// joint modifier modes. make sure to change script/doom_defs.script if you add any, or change their order. +// +typedef enum { + JOINTMOD_NONE, // no modification + JOINTMOD_LOCAL, // modifies the joint's position or orientation in joint local space + JOINTMOD_LOCAL_OVERRIDE, // sets the joint's position or orientation in joint local space + JOINTMOD_WORLD, // modifies joint's position or orientation in model space + JOINTMOD_WORLD_OVERRIDE, // sets the joint's position or orientation in model space + JOINTMOD_COLLAPSE +} jointModTransform_t; + +typedef struct { + jointHandle_t jointnum; + idMat3 mat; + idVec3 pos; + jointModTransform_t transform_pos; + jointModTransform_t transform_axis; + +// RAVEN BEGIN +// bdube: added more features to programmer controlled joints + idInterpolateAccelDecelLinear angularVelocity; + int lastTime; + + jointHandle_t collapseJoint; +// RAVEN END + +} jointMod_t; + +#define ANIM_TX BIT( 0 ) +#define ANIM_TY BIT( 1 ) +#define ANIM_TZ BIT( 2 ) +#define ANIM_QX BIT( 3 ) +#define ANIM_QY BIT( 4 ) +#define ANIM_QZ BIT( 5 ) + +typedef enum { + FC_SCRIPTFUNCTION, + FC_SCRIPTFUNCTIONOBJECT, + FC_EVENTFUNCTION, +// RAVEN BEGIN +// abahr: event call with parms + FC_EVENTFUNCTION_ARGS, +// RAVEN END + FC_SOUND, + FC_SOUND_VOICE, + FC_SOUND_VOICE2, + FC_SOUND_BODY, + FC_SOUND_BODY2, + FC_SOUND_BODY3, + FC_SOUND_WEAPON, + FC_SOUND_ITEM, + FC_SOUND_GLOBAL, + FC_SOUND_CHATTER, + FC_SKIN, + FC_TRIGGER, + FC_DIRECTDAMAGE, + FC_MUZZLEFLASH, + FC_FOOTSTEP, + FC_LEFTFOOT, + FC_RIGHTFOOT, + FC_ENABLE_EYE_FOCUS, + FC_DISABLE_EYE_FOCUS, + FC_FX, + FC_DISABLE_GRAVITY, + FC_ENABLE_GRAVITY, + FC_JUMP, + FC_ENABLE_CLIP, + FC_DISABLE_CLIP, + FC_ENABLE_WALK_IK, + FC_DISABLE_WALK_IK, + FC_ENABLE_LEG_IK, + FC_DISABLE_LEG_IK, + FC_RECORDDEMO, + FC_AVIGAME, + FC_GUIEVENT, + + FC_AI_ENABLE_PAIN, + FC_AI_DISABLE_PAIN, + FC_AI_ENABLE_DAMAGE, + FC_AI_DISABLE_DAMAGE, + FC_AI_LOCKENEMYORIGIN, + FC_AI_ATTACK, + FC_AI_ATTACK_MELEE, + + FC_AI_SPEAK, + FC_AI_SPEAK_RANDOM, +// MCG: for attachment managing + FC_ACT_ATTACH_HIDE, + FC_ACT_ATTACH_SHOW, + + FC_ENABLE_BLINKING, + FC_DISABLE_BLINKING, + FC_ENABLE_AUTOBLINK, + FC_DISABLE_AUTOBLINK, + + FC_COUNT + +} frameCommandType_t; + +// RAVEN BEGIN +// rjohnson: new camera frame commands +extern struct frameCommandInfo_t +{ + const char* name; + bool modview; + +} frameCommandInfo[FC_COUNT]; +// RAVEN END + +typedef struct { + int num; + int firstCommand; +} frameLookup_t; + +typedef struct { + frameCommandType_t type; + + idStr* string; + +// RAVEN BEGIN +// bdube: added joint + idStr* joint; + idStr* joint2; +// abahr: + idList* parmList; +// RAVEN END + + union { + const idSoundShader *soundShader; + const function_t *function; + const idDeclSkin *skin; + int index; +// RAVEN BEGIN +// bdube: effects + const idDecl *effect; + idStr* projectile; + idStr* melee; +// abahr: + const class idEventDef* event; +// RAVEN END + }; +} frameCommand_t; + +typedef struct { + bool prevent_idle_override : 1; + bool random_cycle_start : 1; + bool ai_no_turn : 1; + bool ai_no_look : 1; + bool ai_look_head_only : 1; + bool anim_turn : 1; + bool sync_cycle : 1; +} animFlags_t; + + +/* +============================================================================================== + + idModelExport + +============================================================================================== +*/ + +class idModelExport { +private: + void Reset( void ); + bool ParseOptions( idLexer &lex ); + int ParseExportSection( idParser &parser ); + + static bool CheckMayaInstall( void ); + static void LoadMayaDll( void ); + + bool ConvertMayaToMD5( void ); + static bool initialized; + +public: + idStr commandLine; + idStr src; + idStr dest; + bool force; + + idModelExport(); + + static void Shutdown( void ); + + int ExportDefFile( const char *filename ); + bool ExportModel( const char *model ); + bool ExportAnim( const char *anim ); + int ExportModels( const char *pathname, const char *extension ); +}; + +/* +============================================================================================== + + idMD5Anim + +============================================================================================== +*/ + +class idMD5Anim { +private: + int numFrames; + int frameRate; + int animLength; + int numJoints; + int numAnimatedComponents; + idList bounds; + idList jointInfo; + idList baseFrame; + idList componentFrames; + idStr name; + idVec3 totaldelta; + mutable int ref_count; + +public: + idMD5Anim(); + ~idMD5Anim(); + + void Free( void ); + bool Reload( void ); + size_t Allocated( void ) const; + size_t Size( void ) const { return sizeof( *this ) + Allocated(); }; + bool LoadAnim( const char *filename ); + + void IncreaseRefs( void ) const; + void DecreaseRefs( void ) const; + int NumRefs( void ) const; + + void CheckModelHierarchy( const idRenderModel *model ) const; + void GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const; + void GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumJoints( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + const char *Name( void ) const; + + void ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const; +// RAVEN BEGIN +// jscott: for modview + int ConvertFrameToTime( frameBlend_t &frame ) const; +// RAVEN END + + void GetOrigin( idVec3 &offset, int currentTime, int cyclecount ) const; + void GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const; + void GetBounds( idBounds &bounds, int currentTime, int cyclecount ) const; +}; + +/* +============================================================================================== + + idAnim + +============================================================================================== +*/ + +class idAnim { +private: + const class idDeclModelDef *modelDef; + const idMD5Anim *anims[ ANIM_MaxSyncedAnims ]; + int numAnims; + idStr name; + idStr realname; + idList frameLookup; + idList frameCommands; + animFlags_t flags; + +// RAVEN BEGIN +// bdube: added anim speed + float rate; +// RAVEN END + +public: + idAnim(); + idAnim( const idDeclModelDef *modelDef, const idAnim *anim ); + ~idAnim(); + + void SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ); + const char *Name( void ) const; + const char *FullName( void ) const; + const idMD5Anim *MD5Anim( int num ) const; + const idDeclModelDef *ModelDef( void ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumAnims( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + bool GetOrigin( idVec3 &offset, int animNum, int time, int cyclecount ) const; + bool GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const; + bool GetBounds( idBounds &bounds, int animNum, int time, int cyclecount ) const; +// RAVEN BEGIN +// bdube: frame command function that takes a list of frames + const char *AddFrameCommand( const class idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ); +// RAVEN END + void CallFrameCommands( idEntity *ent, int from, int to ) const; + bool HasFrameCommands( void ) const; + + // returns first frame (zero based) that command occurs. returns -1 if not found. + int FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const; + void SetAnimFlags( const animFlags_t &animflags ); + const animFlags_t &GetAnimFlags( void ) const; + +// RAVEN BEGIN +// bdube: added + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; + float GetPlaybackRate ( void ) const; + void SetPlaybackRate ( float rate ); +// jsinger: to support binary serialization/deserialization of idAnims +#ifdef RV_BINARYDECLS + idAnim( idDeclModelDef const *def, SerialInputStream &stream ); + void Write( SerialOutputStream &stream ) const; +#endif +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added configurable playback rate +ID_INLINE float idAnim::GetPlaybackRate ( void ) const { + return rate; +} + +ID_INLINE void idAnim::SetPlaybackRate ( float _rate ) { + rate = _rate; +} + +// RAVEN END + +/* +============================================================================================== + + idDeclModelDef + +============================================================================================== +*/ + +// RAVEN BEGIN +// jsinger; allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclModelDef : public idDecl, public Serializable<'DMD '> { +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idDeclModelDef( SerialInputStream &stream ); +private: + int mNumChannels; +#else +class idDeclModelDef : public idDecl { +#endif +// RAVEN END +public: + idDeclModelDef(); + ~idDeclModelDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + const idDeclSkin * GetDefaultSkin( void ) const; + const idJointQuat * GetDefaultPose( void ) const; + void SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const; + idRenderModel * ModelHandle( void ) const; + void GetJointList( const char *jointnames, idList &jointList ) const; + const jointInfo_t * FindJoint( const char *name ) const; + + int NumAnims( void ) const; + const idAnim * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + const idDeclSkin * GetSkin( void ) const; + const char * GetModelName( void ) const; + const idList & Joints( void ) const; + const int * JointParents( void ) const; + int NumJoints( void ) const; + const jointInfo_t * GetJoint( int jointHandle ) const; + const char * GetJointName( int jointHandle ) const; + int NumJointsOnChannel( int channel ) const; + const int * GetChannelJoints( int channel ) const; + + const idVec3 & GetVisualOffset( void ) const; + +private: + void CopyDecl( const idDeclModelDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idVec3 offset; + idList joints; + idList jointParents; + idList channelJoints[ ANIM_NumAnimChannels ]; + idRenderModel * modelHandle; + idList anims; + const idDeclSkin * skin; +}; + +ID_INLINE const idAnim *idDeclModelDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idAnimBlend + +============================================================================================== +*/ + +class idAnimBlend { +private: + const class idDeclModelDef *modelDef; + int starttime; + int endtime; + int timeOffset; + float rate; + + int blendStartTime; + int blendDuration; + float blendStartValue; + float blendEndValue; + + float animWeights[ ANIM_MaxSyncedAnims ]; + short cycle; + short animNum; + bool allowMove; + bool allowFrameCommands; + bool useFrameBlend; + + frameBlend_t frameBlend; + + friend class idAnimator; + + void Reset( const idDeclModelDef *_modelDef ); + void CallFrameCommands( idEntity *ent, int fromtime, int totime ) const; +// RAVEN BEGIN +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( const idDeclModelDef *modelDef, int animnum, const frameBlend_t & frameBlend ); +// jshepard: added rate parameter so we can speed up/slow down animations. + void CycleAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); + void PlayAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); +// RAVEN END + bool BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOrigin, bool overrideBlend, bool printInfo ) const; + void BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const; + void BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const; + void BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const; + bool AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const; + +public: + idAnimBlend(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ); + const char *AnimName( void ) const; + const char *AnimFullName( void ) const; + float GetWeight( int currenttime ) const; + float GetFinalWeight( void ) const; + void SetWeight( float newweight, int currenttime, int blendtime ); + int NumSyncedAnims( void ) const; + bool SetSyncedAnimWeight( int num, float weight ); + void Clear( int currentTime, int clearTime ); + bool IsDone( int currentTime ) const; + bool FrameHasChanged( int currentTime ) const; + int GetCycleCount( void ) const; + void SetCycleCount( int count ); + void SetPlaybackRate( int currentTime, float newRate ); + float GetPlaybackRate( void ) const; + void SetStartTime( int startTime ); + int GetStartTime( void ) const; + int GetEndTime( void ) const; + int GetFrameNumber( int currenttime ) const; + int AnimTime( int currenttime ) const; + int NumFrames( void ) const; + int Length( void ) const; + int PlayLength( void ) const; + void AllowMovement( bool allow ); + void AllowFrameCommands( bool allow ); + const idAnim *Anim( void ) const; + int AnimNum( void ) const; +}; + +ID_INLINE const idAnim *idAnimBlend::Anim( void ) const { + if ( !modelDef ) { + return NULL; + } + return modelDef->GetAnim( animNum ); +} + +/* +============================================================================================== + + idAFPoseJointMod + +============================================================================================== +*/ + +typedef enum { + AF_JOINTMOD_AXIS, + AF_JOINTMOD_ORIGIN, + AF_JOINTMOD_BOTH +} AFJointModType_t; + +class idAFPoseJointMod { +public: + idAFPoseJointMod( void ); + + AFJointModType_t mod; + idMat3 axis; + idVec3 origin; +}; + +ID_INLINE idAFPoseJointMod::idAFPoseJointMod( void ) { + mod = AF_JOINTMOD_AXIS; + axis.Identity(); + origin.Zero(); +} + +/* +============================================================================================== + + idAnimator + +============================================================================================== +*/ + +class idAnimator{ + public: + idAnimator(); + ~idAnimator(); + + size_t Allocated( void ) const; + size_t Size( void ) const; + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetEntity( idEntity *ent ); + idEntity *GetEntity( void ) const ; + void RemoveOriginOffset( bool remove ); + bool RemoveOrigin( void ) const; + + void GetJointList( const char *jointnames, idList &jointList ) const; + + int NumAnims( void ) const; + const idAnim *GetAnim( int index ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + + void ServiceAnims( int fromtime, int totime ); + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation + bool IsAnimating ( int currentTime, bool IgnoreAF = false ) const; + bool IsBlending ( int channelNum, int currentTime ) const; +// RAVEN END + + void GetJoints( int *numJoints, idJointMat **jointsPtr ); + int NumJoints( void ) const; + jointHandle_t GetFirstChild( jointHandle_t jointnum ) const; + jointHandle_t GetFirstChild( const char *name ) const; + + idRenderModel *SetModel( const char *modelname ); + idRenderModel *ModelHandle( void ) const; + const idDeclModelDef *ModelDef( void ) const; + + void ForceUpdate( void ); + void ClearForceUpdate( void ); + bool CreateFrame( int animtime, bool force ); + bool FrameHasChanged( int animtime ) const; + void GetDelta( int fromtime, int totime, idVec3 &delta ) const; + bool GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const; + void GetOrigin( int currentTime, idVec3 &pos ) const; + bool GetBounds( int currentTime, idBounds &bounds ); + + idAnimBlend *CurrentAnim( int channelNum ); + void Clear( int channelNum, int currentTime, int cleartime ); + +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( int channelNum, int animnum, const frameBlend_t & frameBlend ); + void CycleAnim( int channelNum, int animnum, int currenttime, int blendtime ); + void PlayAnim( int channelNum, int animnum, int currenttime, int blendTime ); + + // copies the current anim from fromChannelNum to channelNum. + // the copied anim will have frame commands disabled to avoid executing them twice. + void SyncAnimChannels( int channelNum, int fromChannelNum, int currenttime, int blendTime ); + + void SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ); + void GetJointAxis( jointHandle_t jointnum, idMat3 &mat ); + void CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ); + void CollapseJoints ( const char* jointnames, jointHandle_t collapseJoint ); + void ClearJoint( jointHandle_t jointnum ); + void ClearAllJoints( void ); + +// RAVEN BEGIN +// bdube: more joint control functions + void AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int blendtime ); + void SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ); + idAngles GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ); + void ClearJointAngularVelocity ( jointHandle_t jointnum ); + +// jshepard: rate of playback change + void SetPlaybackRate(float multiplier); +// abahr: + void SetPlaybackRate( const char* animName, float rate ); + void SetPlaybackRate( int animHandle, float rate ); +// RAVEN END + + void InitAFPose( void ); + void SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ); + void FinishAFPose( int animnum, const idBounds &bounds, const int time ); + void SetAFPoseBlendWeight( float blendWeight ); + bool BlendAFPose( idJointQuat *blendFrame ) const; + void ClearAFPose( void ); + + void ClearAllAnims( int currentTime, int cleartime ); + + jointHandle_t GetJointHandle( const char *name ) const; + const char * GetJointName( jointHandle_t handle ) const; + int GetChannelForJoint( jointHandle_t joint ) const; + bool GetJointTransform( jointHandle_t jointHandle, int currenttime, idVec3 &offset, idMat3 &axis ); + bool GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + + const animFlags_t GetAnimFlags( int animnum ) const; + int NumFrames( int animnum ) const; + int NumSyncedAnims( int animnum ) const; + const char *AnimName( int animnum ) const; + const char *AnimFullName( int animnum ) const; +// RAVEN BEGIN +// rjohnson: more output for animators + const char *AnimMD5Name( int animnum, int index ) const; +// RAVEN END + int AnimLength( int animnum ) const; + const idVec3 &TotalMovementDelta( int animnum ) const; + +// RAVEN BEGIN +// nrausch: get the nearest joint to a segment - ignores joints behind the origin +// you can pass it a null jointList in order to test against all joints ( use NumJoints() for the count ) + jointHandle_t GetNearestJoint( const idVec3 &start, const idVec3 &end, int time, jointHandle_t *jointList, int cnt ); +//MCG + jointMod_t * FindExistingJointMod( jointHandle_t jointnum, int *index ); +// RAVEN END + +private: +// RAVEN BEGIN +// bdube: added methods + jointMod_t * FindJointMod ( jointHandle_t jointnum ); +// RAVEN END + + void FreeData( void ); + void PushAnims( int channel, int currentTime, int blendTime ); + +private: + const idDeclModelDef * modelDef; + idEntity * entity; + + idAnimBlend channels[ ANIM_NumAnimChannels ][ ANIM_MaxAnimsPerChannel ]; + idList jointMods; + int numJoints; + idJointMat * joints; + + mutable int lastTransformTime; // mutable because the value is updated in CreateFrame + mutable bool stoppedAnimatingUpdate; + bool removeOriginOffset; + bool forceUpdate; + + idBounds frameBounds; + + float AFPoseBlendWeight; + idList AFPoseJoints; + idList AFPoseJointMods; + idJointQuat * AFPoseJointFrame; + int AFPoseJointFrameSize; + idBounds AFPoseBounds; + int AFPoseTime; + +// RAVEN BEGIN +// jshepard: multiplier for the animation rate for all anims under this animator + float rateMultiplier; +// RAVEN END +}; + +ID_INLINE void idAnimator::SetPlaybackRate ( float _rate ) { + rateMultiplier = _rate; +} + +/* +============================================================================================== + + idAnimManager + +============================================================================================== +*/ + +class idAnimManager { +public: + idAnimManager(); + ~idAnimManager(); + + static bool forceExport; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void BeginLevelLoad( void ); + void EndLevelLoad( void ); +#endif +// RAVEN END + void Shutdown( void ); + idMD5Anim * GetAnim( const char *name ); + void ReloadAnims( void ); + void ListAnims( void ) const; + void PrintMemInfo( MemInfo *mi ); + int JointIndex( const char *name ); + const char * JointName( int index ) const; + + void ClearAnimsInUse( void ); + void FlushUnusedAnims( void ); + +private: + idHashTable animations; + idStrList jointnames; + idHashIndex jointnamesHash; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + bool insideLevelLoad; +#endif +// RAVEN END +}; + +#endif /* !__ANIM_H__ */ + +// RAVEN END diff --git a/src/game/anim/Anim_Blend.cpp b/src/game/anim/Anim_Blend.cpp new file mode 100644 index 0000000..b65479f --- /dev/null +++ b/src/game/anim/Anim_Blend.cpp @@ -0,0 +1,6061 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../../game/Projectile.h" +#include "../ai/AI.h" + +static const char *channelNames[ ANIM_NumAnimChannels ] = { + "all", "torso", "legs", "head", "eyelids" +}; + +/*********************************************************************** + + idAnim + +***********************************************************************/ + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim() { + modelDef = NULL; +// RAVEN BEGIN +// bdube: added speed + rate = 1.0f; +// RAVEN END + numAnims = 0; + memset( anims, 0, sizeof( anims ) ); + memset( &flags, 0, sizeof( flags ) ); +} + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim( const idDeclModelDef *modelDef, const idAnim *anim ) { + int i; + + this->modelDef = modelDef; + numAnims = anim->numAnims; + name = anim->name; + realname = anim->realname; + flags = anim->flags; +// RAVEN BEGIN +// bdube: copy speed info + rate = anim->rate; +// RAVEN END + + memset( anims, 0, sizeof( anims ) ); + for( i = 0; i < numAnims; i++ ) { + anims[ i ] = anim->anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + frameLookup.SetNum( anim->frameLookup.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( frameLookup.Ptr(), anim->frameLookup.Ptr(), frameLookup.MemoryUsed() ); +// RAVEN END + + frameCommands.SetNum( anim->frameCommands.Num() ); + for( i = 0; i < frameCommands.Num(); i++ ) { + frameCommands[ i ] = anim->frameCommands[ i ]; + if ( anim->frameCommands[ i ].string ) { + frameCommands[ i ].string = new idStr( *anim->frameCommands[ i ].string ); + } +// RAVEN BEGIN +// bdube: copy joint information + if ( anim->frameCommands[ i ].joint ) { + frameCommands[ i ].joint = new idStr ( *anim->frameCommands[i].joint ); + } + if ( anim->frameCommands[ i ].joint2 ) { + frameCommands[ i ].joint2 = new idStr ( *anim->frameCommands[i].joint2 ); + } +// abahr: + if ( anim->frameCommands[ i ].parmList ) { + frameCommands[ i ].parmList = new idList( *anim->frameCommands[i].parmList ); + } +// RAVEN END + } +} + +/* +===================== +idAnim::~idAnim +===================== +*/ +idAnim::~idAnim() { + int i; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + } + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joint support + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } +} + +/* +===================== +idAnim::SetAnim +===================== +*/ +void idAnim::SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ) { + int i; + + this->modelDef = modelDef; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + anims[ i ] = NULL; + } + + assert( ( num > 0 ) && ( num <= ANIM_MaxSyncedAnims ) ); + numAnims = num; + realname = sourcename; + name = animname; + + for( i = 0; i < num; i++ ) { + anims[ i ] = md5anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + memset( &flags, 0, sizeof( flags ) ); + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joints as their own string + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } + + frameLookup.Clear(); + frameCommands.Clear(); +} + +/* +===================== +idAnim::Name +===================== +*/ +const char *idAnim::Name( void ) const { + return name; +} + +/* +===================== +idAnim::FullName +===================== +*/ +const char *idAnim::FullName( void ) const { + return realname; +} + +/* +===================== +idAnim::MD5Anim + +index 0 will never be NULL. Any anim >= NumAnims will return NULL. +===================== +*/ +const idMD5Anim *idAnim::MD5Anim( int num ) const { + if ( anims == NULL || anims[0] == NULL ) { + return NULL; + } + return anims[ num ]; +} + +/* +===================== +idAnim::ModelDef +===================== +*/ +const idDeclModelDef *idAnim::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnim::Length +===================== +*/ +int idAnim::Length( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->Length(); +} + +/* +===================== +idAnim::NumFrames +===================== +*/ +int idAnim::NumFrames( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->NumFrames(); +} + +/* +===================== +idAnim::NumAnims +===================== +*/ +int idAnim::NumAnims( void ) const { + return numAnims; +} + +/* +===================== +idAnim::TotalMovementDelta +===================== +*/ +const idVec3 &idAnim::TotalMovementDelta( void ) const { + if ( !anims[ 0 ] ) { + return vec3_zero; + } + + return anims[ 0 ]->TotalMovementDelta(); +} + +/* +===================== +idAnim::GetOrigin +===================== +*/ +bool idAnim::GetOrigin( idVec3 &offset, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + offset.Zero(); + return false; + } + + anims[ animNum ]->GetOrigin( offset, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetOriginRotation +===================== +*/ +bool idAnim::GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + rotation.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + return false; + } + + anims[ animNum ]->GetOriginRotation( rotation, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetBounds +===================== +*/ +ID_INLINE bool idAnim::GetBounds( idBounds &bounds, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + return false; + } + + anims[ animNum ]->GetBounds( bounds, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *idAnim::AddFrameCommand( const idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0].c_str() ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "enableBlinking" ) { + fc.type = FC_ENABLE_BLINKING; + } else if ( token == "disableBlinking" ) { + fc.type = FC_DISABLE_BLINKING; + } else if ( token == "enableAutoBlink" ) { + fc.type = FC_ENABLE_AUTOBLINK; + } else if ( token == "disableAutoBlink" ) { + fc.type = FC_DISABLE_AUTOBLINK; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "speak_random" ) { + fc.type = FC_AI_SPEAK_RANDOM; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +//MCG - added attachment frame commands + } else if ( token == "attachment_hide" ) { + fc.type = FC_ACT_ATTACH_HIDE; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "attachment_show" ) { + fc.type = FC_ACT_ATTACH_SHOW; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( anims[ 0 ]->NumFrames() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > anims[ 0 ]->NumFrames() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, anims[ 0 ]->Name(), anims[ 0 ]->NumFrames() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +// RAVEN BEGIN +// bdube: added for debugging +struct frameCommandInfo_t frameCommandInfo[FC_COUNT] = { + { "call", false }, + { "object_call", false }, + { "event", false }, + { "eventArgs", false }, + + { "sound", true }, + { "sound_voice", true }, + { "sound_voice2", true }, + { "sound_body", true }, + { "sound_body2", true }, + { "sound_body3", true }, + { "sound_weapon", true }, + { "sound_item", true }, + { "sound_global", true }, + { "sound_chatter", true }, + + { "skin", true }, + { "trigger", false }, + { "direct_damage", false }, + { "muzzle_flash", false }, + { "footstep", false }, + { "leftfoot", false }, + { "rightfoot", false }, + { "enableEyeFocus", false }, + { "disableEyeFocus", false }, + { "effect", true }, + { "disable_gravity", false }, + { "enable_gravity", false }, + { "jump", false }, + { "enableClip", false }, + { "disableClip", false }, + { "enableWalkIK", false }, + { "disableWalkIK", false }, + { "enableLegIK", false }, + { "disableLegID", false }, + { "recordDemo", false }, + { "aviGame", false }, + { "guievent", false }, + + { "ai_enablePain", false }, + { "ai_disablePain", false }, + { "ai_enableDamage", false }, + { "ai_disableDamage", false }, + { "ai_lockEnemyOrigin", false }, + { "ai_attack", false }, + { "ai_attack_melee", false }, + { "speak", true }, +}; +// RAVEN END + +// RAVEN BEGIN +// bdube: added frame command methods +/* +===================== +idAnim::CallFrameCommandSound +===================== +*/ +void idAnim::CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + ent->StartSound ( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} +// RAVEN END + +/* +===================== +idAnim::CallFrameCommands +===================== +*/ +void idAnim::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + numframes = anims[ 0 ]->NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "framecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } + + if ( ( gameLocal.editors & EDITOR_MODVIEW ) && !frameCommandInfo[command.type].modview ) { + continue; + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { + + if ( gameLocal.localClientNum == -1 ) { + // early ret on dedicated server + break; + } +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_zero, mat3_identity ); + } else { + cent = gameLocal.PlayEffect( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? + if ( cent && command.joint2 && ent->IsType( idAnimatedEntity::GetClassType() ) ) { + cent->SetEndOrigin( ent->GetAnimator()->GetJointHandle( *command.joint2 ) ); + } + + // Error print should the effect fail to play + if ( !cent ) { + idStr error = "Failed to play effect"; + + if( command.string ) { + error += va( " \'%s\'", command.string->c_str() ); + } + if ( command.effect ) { + error += va( " \'%s\'", command.effect->GetName() ); + } + if( command.joint ) { + error += va( " on bone \'%s\'", command.joint->c_str() ); + } + common->Warning( error.c_str() ); + } +// RAVEN END + break; + } + case FC_SKIN: + ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { +// RAVEN BEGIN +// nmckenzie: We're not using this. +// ent->ProcessEvent( &AI_MuzzleFlash, command.string->c_str() ); +// RAVEN END + break; + } + case FC_FOOTSTEP : { + ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { + ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { + ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_ENABLE_BLINKING: { + ent->ProcessEvent( &AI_EnableBlink ); + break; + } + case FC_DISABLE_BLINKING: { + ent->ProcessEvent( &AI_DisableBlink ); + break; + } + case FC_ENABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_EnableAutoBlink ); + break; + } + case FC_DISABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_DisableAutoBlink ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { + ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { + ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { + ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { + ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: + ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: + ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: + ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: + ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: + ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: + ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: + ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_AI_SPEAK_RANDOM: + ent->ProcessEvent( &AI_SpeakRandom, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } + break; + + case FC_ACT_ATTACH_SHOW: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } + break; + + case FC_AI_ATTACK_MELEE: + ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + +/* +===================== +idAnim::FindFrameForFrameCommand +===================== +*/ +int idAnim::FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const { + int frame; + int index; + int numframes; + int end; + + if ( !frameCommands.Num() ) { + return -1; + } + + numframes = anims[ 0 ]->NumFrames(); + for( frame = 0; frame < numframes; frame++ ) { + end = frameLookup[ frame ].firstCommand + frameLookup[ frame ].num; + for( index = frameLookup[ frame ].firstCommand; index < end; index++ ) { + if ( frameCommands[ index ].type == framecommand ) { + if ( command ) { + *command = &frameCommands[ index ]; + } + return frame; + } + } + } + + if ( command ) { + *command = NULL; + } + + return -1; +} + +/* +===================== +idAnim::HasFrameCommands +===================== +*/ +bool idAnim::HasFrameCommands( void ) const { + if ( !frameCommands.Num() ) { + return false; + } + return true; +} + +/* +===================== +idAnim::SetAnimFlags +===================== +*/ +void idAnim::SetAnimFlags( const animFlags_t &animflags ) { + flags = animflags; +} + +/* +===================== +idAnim::GetAnimFlags +===================== +*/ +const animFlags_t &idAnim::GetAnimFlags( void ) const { + return flags; +} + +/*********************************************************************** + + idAnimBlend + +***********************************************************************/ + +/* +===================== +idAnimBlend::idAnimBlend +===================== +*/ +idAnimBlend::idAnimBlend( void ) { + Reset( NULL ); +} + +/* +===================== +idAnimBlend::Save + +archives object for save game file +===================== +*/ +void idAnimBlend::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( starttime ); + savefile->WriteInt( endtime ); + savefile->WriteInt( timeOffset ); + savefile->WriteFloat( rate ); + + savefile->WriteInt( blendStartTime ); + savefile->WriteInt( blendDuration ); + savefile->WriteFloat( blendStartValue ); + savefile->WriteFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->WriteFloat( animWeights[ i ] ); + } + savefile->WriteShort( cycle ); + savefile->WriteShort( animNum ); + savefile->WriteBool( allowMove ); + savefile->WriteBool( allowFrameCommands ); + savefile->WriteBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Restore + +unarchives object from save game file +===================== +*/ +void idAnimBlend::Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ) { + int i; + + this->modelDef = modelDef; + + savefile->ReadInt( starttime ); + savefile->ReadInt( endtime ); + savefile->ReadInt( timeOffset ); + savefile->ReadFloat( rate ); + + savefile->ReadInt( blendStartTime ); + savefile->ReadInt( blendDuration ); + savefile->ReadFloat( blendStartValue ); + savefile->ReadFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->ReadFloat( animWeights[ i ] ); + } + savefile->ReadShort( cycle ); + savefile->ReadShort( animNum ); + if ( !modelDef ) { + animNum = 0; + } else if ( ( animNum < 0 ) || ( animNum > modelDef->NumAnims() ) ) { + gameLocal.Warning( "Anim number %d out of range for model '%s' during save game", animNum, modelDef->GetModelName() ); + animNum = 0; + } + savefile->ReadBool( allowMove ); + savefile->ReadBool( allowFrameCommands ); + savefile->ReadBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Reset +===================== +*/ +void idAnimBlend::Reset( const idDeclModelDef *_modelDef ) { + modelDef = _modelDef; + cycle = 1; + starttime = 0; + endtime = 0; + timeOffset = 0; + rate = 1.0f; + allowMove = true; + allowFrameCommands = true; + animNum = 0; + + memset( animWeights, 0, sizeof( animWeights ) ); + + blendStartValue = 0.0f; + blendEndValue = 0.0f; + blendStartTime = 0; + blendDuration = 0; + useFrameBlend = false; + + memset( &frameBlend, 0, sizeof( frameBlend ) ); +} + +/* +===================== +idAnimBlend::FullName +===================== +*/ +const char *idAnimBlend::AnimFullName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} + +/* +===================== +idAnimBlend::AnimName +===================== +*/ +const char *idAnimBlend::AnimName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->Name(); +} + +/* +===================== +idAnimBlend::NumFrames +===================== +*/ +int idAnimBlend::NumFrames( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumFrames(); +} + +/* +===================== +idAnimBlend::Length +===================== +*/ +int idAnimBlend::Length( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->Length(); +} + +/* +===================== +idAnimBlend::GetWeight +===================== +*/ +float idAnimBlend::GetWeight( int currentTime ) const { + int timeDelta; + float frac; + float w; + + timeDelta = currentTime - blendStartTime; + if ( timeDelta <= 0 ) { + w = blendStartValue; + } else if ( timeDelta >= blendDuration ) { + w = blendEndValue; + } else { + frac = ( float )timeDelta / ( float )blendDuration; + w = blendStartValue + ( blendEndValue - blendStartValue ) * frac; + } + + return w; +} + +/* +===================== +idAnimBlend::GetFinalWeight +===================== +*/ +float idAnimBlend::GetFinalWeight( void ) const { + return blendEndValue; +} + +/* +===================== +idAnimBlend::SetWeight +===================== +*/ +void idAnimBlend::SetWeight( float newweight, int currentTime, int blendTime ) { + blendStartValue = GetWeight( currentTime ); + blendEndValue = newweight; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + + if ( !newweight ) { + endtime = currentTime + blendTime; + } +} + +/* +===================== +idAnimBlend::NumSyncedAnims +===================== +*/ +int idAnimBlend::NumSyncedAnims( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumAnims(); +} + +/* +===================== +idAnimBlend::SetSyncedAnimWeight +===================== +*/ +bool idAnimBlend::SetSyncedAnimWeight( int num, float weight ) { + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + if ( ( num < 0 ) || ( num > anim->NumAnims() ) ) { + return false; + } + + animWeights[ num ] = weight; + return true; +} + +/* +===================== +idAnimBlend::SetFrame +===================== +*/ +void idAnimBlend::SetFrame( const idDeclModelDef *modelDef, int _animNum, const frameBlend_t & _frameBlend ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + frameBlend = _frameBlend; + animNum = _animNum; + useFrameBlend = true; + + // a frame of 0 means it's not a single frame blend, so we set it to frame + 1 + if ( frameBlend.frame1 < 0 ) { + frameBlend.frame1 = 0; + } else if ( frameBlend.frame1 >= _anim->NumFrames() ) { + frameBlend.frame1 = _anim->NumFrames() - 1; + } + + if ( frameBlend.frame2 < 0 ) { + frameBlend.frame2 = 0; + } else if ( frameBlend.frame2 >= _anim->NumFrames() ) { + frameBlend.frame2 = _anim->NumFrames() - 1; + } +} + +/* +===================== +idAnimBlend::CycleAnim +===================== +*/ +void idAnimBlend::CycleAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + animWeights[ 0 ] = 1.0f; + endtime = -1; + cycle = -1; + if ( _anim->GetAnimFlags().random_cycle_start ) { + // start the animation at a random time so that characters don't walk in sync + starttime = currentTime - gameLocal.random.RandomFloat() * _anim->Length(); + } else { + starttime = currentTime; + } + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } +// RAVEN END +} + +/* +===================== +idAnimBlend::PlayAnim +===================== +*/ +void idAnimBlend::PlayAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + starttime = currentTime; + endtime = starttime + _anim->Length(); + cycle = 1; + animWeights[ 0 ] = 1.0f; + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } + +// RAVEN END +} + +/* +===================== +idAnimBlend::Clear +===================== +*/ +void idAnimBlend::Clear( int currentTime, int clearTime ) { + if ( !clearTime ) { + Reset( modelDef ); + } else { + SetWeight( 0.0f, currentTime, clearTime ); + } +} + +/* +===================== +idAnimBlend::IsDone +===================== +*/ +bool idAnimBlend::IsDone( int currentTime ) const { + if ( !useFrameBlend && ( endtime > 0 ) && ( currentTime >= endtime ) ) { + return true; + } + + if ( ( blendEndValue <= 0.0f ) && ( currentTime >= ( blendStartTime + blendDuration ) ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimBlend::FrameHasChanged +===================== +*/ +bool idAnimBlend::FrameHasChanged( int currentTime ) const { + // if we don't have an anim, no change + if ( !animNum ) { + return false; + } + + // if anim is done playing, no change + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + // if our blend weight changes, we need to update + if ( ( currentTime < ( blendStartTime + blendDuration ) && ( blendStartValue != blendEndValue ) ) ) { + return true; + } + + // if we're a single frame anim and this isn't the frame we started on, we don't need to update + if ( ( useFrameBlend || ( NumFrames() == 1 ) ) && ( currentTime != starttime ) ) { + return false; + } + + return true; +} + +/* +===================== +idAnimBlend::GetCycleCount +===================== +*/ +int idAnimBlend::GetCycleCount( void ) const { + return cycle; +} + +/* +===================== +idAnimBlend::SetCycleCount +===================== +*/ +void idAnimBlend::SetCycleCount( int count ) { + const idAnim *anim = Anim(); + + if ( !anim ) { + cycle = -1; + endtime = 0; + } else { + cycle = count; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + if ( count < 0 ) { +// RAVEN END + cycle = -1; + endtime = -1; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + } else if ( count == 0 ) { +// RAVEN END + cycle = 1; + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length(); + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + anim->Length() / rate; + } else { + endtime = -1; + } + } else { + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length() * cycle; + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + ( anim->Length() * cycle ) / rate; + } else { + endtime = -1; + } + } + } +} + +/* +===================== +idAnimBlend::SetPlaybackRate +===================== +*/ +void idAnimBlend::SetPlaybackRate( int currentTime, float newRate ) { + int animTime; + + if ( rate == newRate ) { + return; + } + + animTime = AnimTime( currentTime ); + if ( newRate == 1.0f ) { + timeOffset = animTime - ( currentTime - starttime ); + } else { + timeOffset = animTime - ( currentTime - starttime ) * newRate; + } + + rate = newRate; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetPlaybackRate +===================== +*/ +float idAnimBlend::GetPlaybackRate( void ) const { + return rate; +} + +/* +===================== +idAnimBlend::SetStartTime +===================== +*/ +void idAnimBlend::SetStartTime( int _startTime ) { + starttime = _startTime; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetStartTime +===================== +*/ +int idAnimBlend::GetStartTime( void ) const { + if ( !animNum ) { + return 0; + } + + return starttime; +} + +/* +===================== +idAnimBlend::GetEndTime +===================== +*/ +int idAnimBlend::GetEndTime( void ) const { + if ( !animNum ) { + return 0; + } + + return endtime; +} + +/* +===================== +idAnimBlend::PlayLength +===================== +*/ +int idAnimBlend::PlayLength( void ) const { + if ( !animNum ) { + return 0; + } + + if ( endtime < 0 ) { + return -1; + } + + return endtime - starttime + timeOffset; +} + +/* +===================== +idAnimBlend::AllowMovement +===================== +*/ +void idAnimBlend::AllowMovement( bool allow ) { + allowMove = allow; +} + +/* +===================== +idAnimBlend::AllowFrameCommands +===================== +*/ +void idAnimBlend::AllowFrameCommands( bool allow ) { + allowFrameCommands = allow; +} + +/* +===================== +idAnimBlend::AnimNum +===================== +*/ +int idAnimBlend::AnimNum( void ) const { + return animNum; +} + +/* +===================== +idAnimBlend::AnimTime +===================== +*/ +int idAnimBlend::AnimTime( int currentTime ) const { + int time; + int length; + const idAnim *anim = Anim(); + + if ( anim ) { + if ( useFrameBlend ) { + return FRAME2MS( frameBlend.frame1 ); + } + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + time = currentTime - starttime + timeOffset; + } else { + time = static_cast( ( currentTime - starttime ) * rate ) + timeOffset; + } + + // given enough time, we can easily wrap time around in our frame calculations, so + // keep cycling animations' time within the length of the anim. + length = anim->Length(); + if ( ( cycle < 0 ) && ( length > 0 ) ) { + time %= length; + + // time will wrap after 24 days (oh no!), resulting in negative results for the %. + // adding the length gives us the proper result. + if ( time < 0 ) { + time += length; + } + } + return time; + } else { + return 0; + } +} + +/* +===================== +idAnimBlend::GetFrameNumber +===================== +*/ +int idAnimBlend::GetFrameNumber( int currentTime ) const { + const idMD5Anim *md5anim; + frameBlend_t frameinfo; + int animTime; + + const idAnim *anim = Anim(); + if ( !anim ) { + return 1; + } + + if ( useFrameBlend ) { + return frameBlend.frame1; + } + + md5anim = anim->MD5Anim( 0 ); + animTime = AnimTime( currentTime ); + md5anim->ConvertTimeToFrame( animTime, cycle, frameinfo ); + + return frameinfo.frame1 + 1; +} + +/* +===================== +idAnimBlend::CallFrameCommands +===================== +*/ +void idAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const { + const idMD5Anim *md5anim; + frameBlend_t frame1; + frameBlend_t frame2; + int fromFrameTime; + int toFrameTime; + + if ( !allowFrameCommands || !ent || useFrameBlend || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->HasFrameCommands() ) { + return; + } + + if ( totime <= starttime ) { + // don't play until next frame or we'll play commands twice. + // this happens on the player sometimes. + return; + } + + fromFrameTime = AnimTime( fromtime ); + toFrameTime = AnimTime( totime ); + if ( toFrameTime < fromFrameTime ) { + toFrameTime += anim->Length(); + } + + md5anim = anim->MD5Anim( 0 ); + md5anim->ConvertTimeToFrame( fromFrameTime, cycle, frame1 ); + md5anim->ConvertTimeToFrame( toFrameTime, cycle, frame2 ); + + if ( fromFrameTime <= 0 ) { + // make sure first frame is called + anim->CallFrameCommands( ent, -1, frame2.frame1 ); + } else { + anim->CallFrameCommands( ent, frame1.frame1, frame2.frame1 ); + } +} + +/* +===================== +idAnimBlend::BlendAnim +===================== +*/ +bool idAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const { + int i; + float lerp; + float mixWeight; + const idMD5Anim *md5anim; + idJointQuat *ptr; + frameBlend_t frametime = {0}; + idJointQuat *jointFrame; + idJointQuat *mixFrame; + int numAnims; + int time; + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( blendWeight > 0.0f ) { + if ( ( endtime >= 0 ) && ( currentTime >= endtime ) ) { + return false; + } + if ( !weight ) { + return false; + } + if ( overrideBlend ) { + blendWeight = 1.0f - weight; + } + } + + if ( ( channel == ANIMCHANNEL_ALL ) && !blendWeight ) { + // we don't need a temporary buffer, so just store it directly in the blend frame + jointFrame = blendFrame; + } else { + // allocate a temporary buffer to copy the joints from + jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + } + + time = AnimTime( currentTime ); + + numAnims = anim->NumAnims(); + if ( numAnims == 1 ) { + md5anim = anim->MD5Anim( 0 ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->ConvertTimeToFrame( time, cycle, frametime ); + md5anim->GetInterpolatedFrame( frametime, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + } else { + // + // need to mix the multipoint anim together first + // + // allocate a temporary buffer to copy the joints to + mixFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + + if ( !useFrameBlend ) { + anim->MD5Anim( 0 )->ConvertTimeToFrame( time, cycle, frametime ); + } + + ptr = jointFrame; + mixWeight = 0.0f; + for( i = 0; i < numAnims; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + lerp = animWeights[ i ] / mixWeight; + md5anim = anim->MD5Anim( i ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->GetInterpolatedFrame( frametime, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + // only blend after the first anim is mixed in + if ( ptr != jointFrame ) { + SIMDProcessor->BlendJoints( jointFrame, ptr, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + ptr = mixFrame; + } + } + + if ( !mixWeight ) { + return false; + } + } + + if ( removeOriginOffset ) { + if ( allowMove ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + if ( anim->GetAnimFlags().anim_turn ) { + jointFrame[ 0 ].q.Set( -0.70710677f, 0.0f, 0.0f, 0.70710677f ); + } + } + + if ( !blendWeight ) { + blendWeight = weight; + if ( channel != ANIMCHANNEL_ALL ) { + const int *index = modelDef->GetChannelJoints( channel ); + const int num = modelDef->NumJointsOnChannel( channel ); + for( i = 0; i < num; i++ ) { + int j = index[i]; + blendFrame[j].t = jointFrame[j].t; + blendFrame[j].q = jointFrame[j].q; + } + } + } else { + blendWeight += weight; + lerp = weight / blendWeight; + SIMDProcessor->BlendJoints( blendFrame, jointFrame, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + if ( printInfo ) { + if ( useFrameBlend ) { + gameLocal.Printf( " %s: '%s', %d, %.2f%%\n", channelNames[ channel ], anim->FullName(), frameBlend.frame1, weight * 100.0f ); + } else { + gameLocal.Printf( " %s: '%s', %.3f, %.2f%%\n", channelNames[ channel ], anim->FullName(), ( float )frametime.frame1 + frametime.backlerp, weight * 100.0f ); + } + } + + return true; +} + +/* +===================== +idAnimBlend::BlendOrigin +===================== +*/ +void idAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const { + float lerp; + idVec3 animpos; + idVec3 pos; + int time; + int num; + int i; + + if ( useFrameBlend || ( ( endtime > 0 ) && ( currentTime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + if ( allowMove && removeOriginOffset ) { + return; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return; + } + + time = AnimTime( currentTime ); + + pos.Zero(); + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time, cycle ); + pos += animpos * animWeights[ i ]; + } + + if ( !blendWeight ) { + blendPos = pos; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendPos += lerp * ( pos - blendPos ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDelta +===================== +*/ +void idAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const { + idVec3 pos1; + idVec3 pos2; + idVec3 animpos; + idVec3 delta; + int time1; + int time2; + float lerp; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + num = anim->NumAnims(); + + pos1.Zero(); + pos2.Zero(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time1, cycle ); + pos1 += animpos * animWeights[ i ]; + + anim->GetOrigin( animpos, i, time2, cycle ); + pos2 += animpos * animWeights[ i ]; + } + + delta = pos2 - pos1; + if ( !blendWeight ) { + blendDelta = delta; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta += lerp * ( delta - blendDelta ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDeltaRotation +===================== +*/ +void idAnimBlend::BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const { + idQuat q1; + idQuat q2; + idQuat q3; + int time1; + int time2; + float lerp; + float mixWeight; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->GetAnimFlags().anim_turn ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + q1.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + q2.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + + mixWeight = 0.0f; + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + if ( animWeights[ i ] == mixWeight ) { + anim->GetOriginRotation( q1, i, time1, cycle ); + anim->GetOriginRotation( q2, i, time2, cycle ); + } else { + lerp = animWeights[ i ] / mixWeight; + anim->GetOriginRotation( q3, i, time1, cycle ); + q1.Slerp( q1, q3, lerp ); + + anim->GetOriginRotation( q3, i, time2, cycle ); + q2.Slerp( q1, q3, lerp ); + } + } + } + + q3 = q1.Inverse() * q2; + if ( !blendWeight ) { + blendDelta = q3; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta.Slerp( blendDelta, q3, lerp ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::AddBounds +===================== +*/ +bool idAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const { + int i; + int num; + idBounds b; + int time; + idVec3 pos; + bool addorigin; + + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return false; + } + + time = AnimTime( currentTime ); + num = anim->NumAnims(); + + addorigin = !allowMove || !removeOriginOffset; + for( i = 0; i < num; i++ ) { + if ( anim->GetBounds( b, i, time, cycle ) ) { + if ( addorigin ) { + anim->GetOrigin( pos, i, time, cycle ); + b.TranslateSelf( pos ); + } + bounds.AddBounds( b ); + } + } + + return true; +} + +/*********************************************************************** + + idDeclModelDef + +***********************************************************************/ + +/* +===================== +idDeclModelDef::idDeclModelDef +===================== +*/ +idDeclModelDef::idDeclModelDef() { + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels=0; +#endif +// RAVEN END +} + +/* +===================== +idDeclModelDef::~idDeclModelDef +===================== +*/ +idDeclModelDef::~idDeclModelDef() { + FreeData(); +} + +/* +================= +idDeclModelDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclModelDef::Size( void ) const { + + int i; + size_t size; + + size = sizeof( idDeclModelDef ); + size += joints.Allocated(); + size += jointParents.Allocated(); + size += anims.Allocated(); + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + + size += channelJoints[i].Allocated(); + } + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclModelDef::CopyDecl +===================== +*/ +void idDeclModelDef::CopyDecl( const idDeclModelDef *decl ) { + int i; + + FreeData(); + + offset = decl->offset; + modelHandle = decl->modelHandle; + skin = decl->skin; + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new idAnim( this, decl->anims[ i ] ); + } + + joints.SetNum( decl->joints.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( joints.Ptr(), decl->joints.Ptr(), decl->joints.Num() * sizeof( joints[0] ) ); +// RAVEN END + jointParents.SetNum( decl->jointParents.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( jointParents.Ptr(), decl->jointParents.Ptr(), decl->jointParents.Num() * sizeof( jointParents[0] ) ); +// RAVEN END + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i] = decl->channelJoints[i]; + } +} + +/* +===================== +idDeclModelDef::FreeData +===================== +*/ +void idDeclModelDef::FreeData( void ) { + anims.DeleteContents( true ); + joints.Clear(); + jointParents.Clear(); + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +} + +/* +================ +idDeclModelDef::DefaultDefinition +================ +*/ +const char *idDeclModelDef::DefaultDefinition( void ) const { + return "{ }"; +} + +/* +==================== +idDeclModelDef::FindJoint +==================== +*/ +const jointInfo_t *idDeclModelDef::FindJoint( const char *name ) const { + int i; + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + joint = modelHandle->GetJoints(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( !joint->name.Icmp( name ) ) { + return &joints[ i ]; + } + } + + return NULL; +} + +/* +===================== +idDeclModelDef::ModelHandle +===================== +*/ +idRenderModel *idDeclModelDef::ModelHandle( void ) const { + return ( idRenderModel * )modelHandle; +} + +/* +===================== +idDeclModelDef::GetJointList +===================== +*/ +void idDeclModelDef::GetJointList( const char *jointnames, idList &jointList ) const { + const char *pos; + idStr jointname; + const jointInfo_t *joint; + const jointInfo_t *child; + int i; + int num; + bool getChildren; + bool subtract; + + if ( !modelHandle ) { + return; + } + + jointList.Clear(); + + num = modelHandle->NumJoints(); + + // scan through list of joints and add each to the joint list + pos = jointnames; + while( *pos ) { + // skip over whitespace + while( ( *pos != 0 ) && isspace( *pos ) ) { + pos++; + } + + if ( !*pos ) { + // no more names + break; + } + + // copy joint name + jointname = ""; + + if ( *pos == '-' ) { + subtract = true; + pos++; + } else { + subtract = false; + } + + if ( *pos == '*' ) { + getChildren = true; + pos++; + } else { + getChildren = false; + } + + while( ( *pos != 0 ) && !isspace( *pos ) ) { + jointname += *pos; + pos++; + } + + joint = FindJoint( jointname ); + if ( !joint ) { + gameLocal.Warning( "Unknown joint '%s' in '%s' for model '%s'", jointname.c_str(), jointnames, GetName() ); + continue; + } + + if ( !subtract ) { + jointList.AddUnique( joint->num ); + } else { + jointList.Remove( joint->num ); + } + + if ( getChildren ) { + // include all joint's children + child = joint + 1; + for( i = joint->num + 1; i < num; i++, child++ ) { + // all children of the joint should follow it in the list. + // once we reach a joint without a parent or with a parent + // who is earlier in the list than the specified joint, then + // we've gone through all it's children. + if ( child->parentNum < joint->num ) { + break; + } + + if ( !subtract ) { + jointList.AddUnique( child->num ); + } else { + jointList.Remove( child->num ); + } + } + } + } +} + +/* +===================== +idDeclModelDef::Touch +===================== +*/ +void idDeclModelDef::Touch( void ) const { + if ( modelHandle ) { + renderModelManager->FindModel( modelHandle->Name() ); + } +} + +/* +===================== +idDeclModelDef::GetDefaultSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetDefaultSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetDefaultPose +===================== +*/ +const idJointQuat *idDeclModelDef::GetDefaultPose( void ) const { + return modelHandle->GetDefaultPose(); +} + +/* +===================== +idDeclModelDef::SetupJoints +===================== +*/ +void idDeclModelDef::SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const { + int num; + const idJointQuat *pose; + idJointMat *list; + + if ( !modelHandle || modelHandle->IsDefaultModel() ) { + Mem_Free16( (*jointList) ); + (*jointList) = NULL; + frameBounds.Clear(); + return; + } + + // get the number of joints + num = modelHandle->NumJoints(); + + if ( !num ) { + gameLocal.Error( "model '%s' has no joints", modelHandle->Name() ); + } + + // set up initial pose for model (with no pose, model is just a jumbled mess) +//RAVEN BEGIN +//amccarthy: Added allocation tag + list = (idJointMat *) Mem_Alloc16( num * sizeof( list[0] ), MA_ANIM ); +//RAVEN END + pose = GetDefaultPose(); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( list, pose, joints.Num() ); + + // check if we offset the model by the origin joint + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + list[ 0 ].SetTranslation( idVec3( offset.x, offset.y + pose[0].t.y, offset.z + pose[0].t.z ) ); +#else + list[ 0 ].SetTranslation( offset ); +#endif + } else { + list[ 0 ].SetTranslation( pose[0].t + offset ); + } + + // transform the joint hierarchy + SIMDProcessor->TransformJoints( list, jointParents.Ptr(), 1, joints.Num() - 1 ); + + *numJoints = num; + *jointList = list; + + // get the bounds of the default pose + frameBounds = modelHandle->Bounds( NULL ); +} + +/* +===================== +idDeclModelDef::ParseAnim +===================== +*/ +bool idDeclModelDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + idAnim *anim; + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + const idMD5Anim *md5anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + animFlags_t flags; + + numAnims = 0; + memset( md5anims, 0, sizeof( md5anims ) ); + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new idAnim(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + do { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + // lookup the animation +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( token ); +// RAVEN END + if ( !md5anim ) { + src.Warning( "Couldn't load anim '%s'", token.c_str() ); + MakeDefault(); + return false; + } + + md5anim->CheckModelHierarchy( modelHandle ); + + if ( numAnims > 0 ) { + // make sure it's the same length as the other anims + if ( md5anim->Length() != md5anims[ 0 ]->Length() ) { + src.Warning( "Anim '%s' does not match length of anim '%s'", md5anim->Name(), md5anims[ 0 ]->Name() ); + MakeDefault(); + return false; + } + } + + if ( numAnims >= ANIM_MaxSyncedAnims ) { + src.Warning( "Exceeded max synced anims (%d)", ANIM_MaxSyncedAnims ); + MakeDefault(); + return false; + } + + // add it to our list + md5anims[ numAnims ] = md5anim; + numAnims++; + } while ( src.CheckTokenString( "," ) ); + + if ( !numAnims ) { + src.Warning( "No animation specified" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, numAnims, md5anims ); + memset( &flags, 0, sizeof( flags ) ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + }else if ( token == "prevent_idle_override" ) { + flags.prevent_idle_override = true; + } else if ( token == "random_cycle_start" ) { + flags.random_cycle_start = true; +// RAVEN BEGIN +// bdube: added speed + } else if ( token == "sync_cycle" ) { + flags.sync_cycle = true; + } else if ( token == "rate" ) { + anim->SetPlaybackRate ( src.ParseFloat ( ) ); + } else if ( token == "ai_no_look" ) { + flags.ai_no_look = true; + } else if ( token == "ai_look_head_only" ) { + flags.ai_look_head_only = true; +// RAVEN END + } else if ( token == "ai_no_turn" ) { + flags.ai_no_turn = true; + } else if ( token == "anim_turn" ) { + flags.anim_turn = true; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } + +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + // set the flags + anim->SetAnimFlags( flags ); + return true; +} + +/* +================ +idDeclModelDef::Parse +================ +*/ +bool idDeclModelDef::Parse( const char *text, const int textLength, bool noCaching ) { + int i; + int num; + idStr filename; + idStr extension; + const idMD5Joint *md5joint; + const idMD5Joint *md5joints; + idLexer src; + idToken token; + idToken token2; + idStr jointnames; + int channel; + jointHandle_t jointnum; + idList jointList; + int numDefaultAnims; +// RAVEN BEGIN +// bdube: attachments + idList attachJoints; +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + + if ( token == "inherit" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + const idDeclModelDef *copy = static_cast( declManager->FindType( DECL_MODELDEF, token2, false ) ); + if ( !copy ) { + gameLocal.Warning( "Unknown model definition '%s'", token2.c_str() ); + } else if ( copy->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "inherited model definition '%s' defaulted", token2.c_str() ); + MakeDefault(); + return false; + } else { + CopyDecl( copy ); + numDefaultAnims = anims.Num(); + } + } else if ( token == "skin" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + skin = declManager->FindSkin( token2 ); + if ( !skin ) { + src.Warning( "Skin '%s' not found", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "mesh" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + filename = token2; + filename.ExtractFileExtension( extension ); + if ( extension != MD5_MESH_EXT ) { + src.Warning( "Invalid model for MD5 mesh" ); + MakeDefault(); + return false; + } + modelHandle = renderModelManager->FindModel( filename ); + if ( !modelHandle ) { + src.Warning( "Model '%s' not found", filename.c_str() ); + MakeDefault(); + return false; + } + + if ( modelHandle->IsDefaultModel() ) { + src.Warning( "Model '%s' defaulted", filename.c_str() ); + MakeDefault(); + return false; + } + + // get the number of joints + num = modelHandle->NumJoints(); + if ( !num ) { + src.Warning( "Model '%s' has no joints", filename.c_str() ); + } + + // set up the joint hierarchy + joints.SetGranularity( 1 ); + joints.SetNum( num ); + jointParents.SetNum( num ); + channelJoints[0].SetNum( num ); + md5joints = modelHandle->GetJoints(); + md5joint = md5joints; + for( i = 0; i < num; i++, md5joint++ ) { + joints[i].channel = ANIMCHANNEL_ALL; + joints[i].num = static_cast( i ); + if ( md5joint->parent ) { + joints[i].parentNum = static_cast( md5joint->parent - md5joints ); + } else { + joints[i].parentNum = INVALID_JOINT; + } + jointParents[i] = joints[i].parentNum; + channelJoints[0][i] = i; + } + } else if ( token == "remove" ) { + // removes any anims whos name matches + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + num = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( ( token2 == anims[ i ]->Name() ) || ( token2 == anims[ i ]->FullName() ) ) { + delete anims[ i ]; + anims.RemoveIndex( i ); + if ( i >= numDefaultAnims ) { + src.Warning( "Anim '%s' was not inherited. Anim should be removed from the model def.", token2.c_str() ); + MakeDefault(); + return false; + } + i--; + numDefaultAnims--; + num++; + continue; + } + } + if ( !num ) { + src.Warning( "Couldn't find anim '%s' to remove", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "anim" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining anims" ); + MakeDefault(); + return false; + } + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else if ( token == "offset" ) { + if ( !src.Parse1DMatrix( 3, offset.ToFloatPtr() ) ) { + src.Warning( "Expected vector following 'offset'" ); + MakeDefault(); + return false; + } + } else if ( token == "channel" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining channels" ); + MakeDefault(); + return false; + } + + // set the channel for a group of joints + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( !src.CheckTokenString( "(" ) ) { + src.Warning( "Expected { after '%s'\n", token2.c_str() ); + MakeDefault(); + return false; + } + + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !stricmp( channelNames[ i ], token2 ) ) { + break; + } + } + + if ( i >= ANIM_NumAnimChannels ) { + src.Warning( "Unknown channel '%s'", token2.c_str() ); + MakeDefault(); + return false; + } + + channel = i; + jointnames = ""; + + while( !src.CheckTokenString( ")" ) ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + jointnames += token2; + if ( ( token2 != "*" ) && ( token2 != "-" ) ) { + jointnames += " "; + } + } + + GetJointList( jointnames, jointList ); + + channelJoints[ channel ].SetNum( jointList.Num() ); + for( num = i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + if ( joints[ jointnum ].channel != ANIMCHANNEL_ALL ) { + src.Warning( "Joint '%s' assigned to multiple channels", modelHandle->GetJointName( jointnum ) ); + continue; + } + joints[ jointnum ].channel = channel; + channelJoints[ channel ][ num++ ] = jointnum; + } + channelJoints[ channel ].SetNum( num ); +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels++; +#endif +// RAVEN END + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclModelDef::Validate +===================== +*/ +bool idDeclModelDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclModelDef *pSelf = (idDeclModelDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclModelDef::HasAnim +===================== +*/ +bool idDeclModelDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclModelDef::NumAnims +===================== +*/ +int idDeclModelDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclModelDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclModelDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclModelDef::GetAnim +===================== +*/ +int idDeclModelDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + +/* +===================== +idDeclModelDef::GetSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetModelName +===================== +*/ +const char *idDeclModelDef::GetModelName( void ) const { + if ( modelHandle ) { + return modelHandle->Name(); + } else { + return ""; + } +} + +/* +===================== +idDeclModelDef::Joints +===================== +*/ +const idList &idDeclModelDef::Joints( void ) const { + return joints; +} + +/* +===================== +idDeclModelDef::JointParents +===================== +*/ +const int * idDeclModelDef::JointParents( void ) const { + return jointParents.Ptr(); +} + +/* +===================== +idDeclModelDef::NumJoints +===================== +*/ +int idDeclModelDef::NumJoints( void ) const { + return joints.Num(); +} + +/* +===================== +idDeclModelDef::GetJoint +===================== +*/ +const jointInfo_t *idDeclModelDef::GetJoint( int jointHandle ) const { + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJoint : joint handle out of range" ); + } + return &joints[ jointHandle ]; +} + +/* +==================== +idDeclModelDef::GetJointName +==================== +*/ +const char *idDeclModelDef::GetJointName( int jointHandle ) const { + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJointName : joint handle out of range" ); + } + + joint = modelHandle->GetJoints(); + return joint[ jointHandle ].name.c_str(); +} + +/* +===================== +idDeclModelDef::NumJointsOnChannel +===================== +*/ +int idDeclModelDef::NumJointsOnChannel( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::NumJointsOnChannel : channel out of range" ); + } + return channelJoints[ channel ].Num(); +} + +/* +===================== +idDeclModelDef::GetChannelJoints +===================== +*/ +const int * idDeclModelDef::GetChannelJoints( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::GetChannelJoints : channel out of range" ); + } + return channelJoints[ channel ].Ptr(); +} + +/* +===================== +idDeclModelDef::GetVisualOffset +===================== +*/ +const idVec3 &idDeclModelDef::GetVisualOffset( void ) const { + return offset; +} + +// RAVEN BEGIN +// jsinger: allow exporting of this decl type in a preparsed form +#ifdef RV_BINARYDECLS +void idDeclModelDef::Write( SerialOutputStream &stream ) const +{ + WriteValue(offset[0], stream); + WriteValue(offset[1], stream); + WriteValue(offset[2], stream); + WriteValue(joints.Num(), stream); + for(int i=0; iName()), stream); + WriteValue(anims.Num(), stream); + for(int i=0; iWrite(stream); + } + + if(skin) + { + WriteValue(idStr(skin->GetName()), stream); + } + else + { + WriteValue(idStr(""), stream); + } +} + +void idDeclModelDef::AddReferences() const +{ +} + +idDeclModelDef::idDeclModelDef( SerialInputStream &stream ) +{ + offset[0] = stream.ReadFloatValue(); + offset[1] = stream.ReadFloatValue(); + offset[2] = stream.ReadFloatValue(); + int numJoints = stream.ReadIntValue(); + joints.AssureSize(numJoints); + for(int i=0; iFindModel( modelName.c_str() ); + } + int numAnims = stream.ReadIntValue(); + anims.AssureSize(numAnims); + for(int i=0; iFindSkin(skinName.c_str()); + } + else + { + skin = NULL; + } +} +#endif +// RAVEN END +/*********************************************************************** + + idAnimator + +***********************************************************************/ + +/* +===================== +idAnimator::idAnimator +===================== +*/ +idAnimator::idAnimator() { + int i, j; + + modelDef = NULL; + entity = NULL; + numJoints = 0; + joints = NULL; + lastTransformTime = -1; + stoppedAnimatingUpdate = false; + removeOriginOffset = false; + forceUpdate = false; + +// RAVEN BEGIN +// jshepard: + rateMultiplier = 1; +// RAVEN END + + frameBounds.Clear(); + + AFPoseJoints.SetGranularity( 1 ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ClearAFPose(); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } +} + +/* +===================== +idAnimator::~idAnimator +===================== +*/ +idAnimator::~idAnimator() { + FreeData(); +} + +/* +===================== +idAnimator::Allocated +===================== +*/ +size_t idAnimator::Allocated( void ) const { + size_t size; + + size = jointMods.Allocated() + numJoints * sizeof( joints[0] ) + jointMods.Num() * sizeof( jointMods[ 0 ] ) + AFPoseJointMods.Allocated() + AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) + AFPoseJoints.Allocated(); + + return size; +} + +/* +===================== +idAnimator::Save + +archives object for save game file +===================== +*/ +void idAnimator::Save( idSaveGame *savefile ) const { + int i; + int j; + + savefile->WriteModelDef( modelDef ); + savefile->WriteObject( entity ); + + savefile->WriteInt( jointMods.Num() ); + for( i = 0; i < jointMods.Num(); i++ ) { + savefile->Write( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->WriteInt( numJoints ); + savefile->Write( joints, numJoints * sizeof( joints[0] ) ); + + savefile->WriteInt( lastTransformTime ); + savefile->WriteBool( stoppedAnimatingUpdate ); + savefile->WriteBool( forceUpdate ); + savefile->WriteBounds( frameBounds ); + + savefile->WriteFloat( AFPoseBlendWeight ); + + savefile->WriteInt( AFPoseJoints.Num() ); + savefile->Write( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointMods.Num() ); + savefile->Write( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointFrameSize ); + savefile->Write( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->WriteBounds( AFPoseBounds ); + savefile->WriteInt( AFPoseTime ); + + savefile->WriteBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Save( savefile ); + } + } +} + +/* +===================== +idAnimator::Restore + +unarchives object from save game file +===================== +*/ +void idAnimator::Restore( idRestoreGame *savefile ) { + int i; + int j; + int num; + + savefile->ReadModelDef( modelDef ); + savefile->ReadObject( reinterpret_cast( entity ) ); + + savefile->ReadInt( num ); + jointMods.SetNum( num ); + for( i = 0; i < num; i++ ) { + jointMods[ i ] = new jointMod_t; + savefile->Read( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->ReadInt( numJoints ); +//RAVEN BEGIN +//amccarthy: Added allocation tag + joints = (idJointMat *) Mem_Alloc16( numJoints * sizeof( joints[0] ), MA_ANIM ); +//RAVEN END + savefile->Read( joints, numJoints * sizeof( joints[0] ) ); + + savefile->ReadInt( lastTransformTime ); + savefile->ReadBool( stoppedAnimatingUpdate ); + savefile->ReadBool( forceUpdate ); + savefile->ReadBounds( frameBounds ); + + savefile->ReadFloat( AFPoseBlendWeight ); + + savefile->ReadInt( num ); + AFPoseJoints.SetGranularity( 1 ); + AFPoseJoints.SetNum( num ); + savefile->Read( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->ReadInt( num ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointMods.SetNum( num ); + savefile->Read( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->ReadInt( AFPoseJointFrameSize ); + AFPoseJointFrame = (idJointQuat *)Mem_Alloc16( AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + savefile->Read( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->ReadBounds( AFPoseBounds ); + savefile->ReadInt( AFPoseTime ); + + savefile->ReadBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Restore( savefile, modelDef ); + } + } +} + +/* +===================== +idAnimator::FreeData +===================== +*/ +void idAnimator::FreeData( void ) { + int i, j; + + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + } + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } + + jointMods.DeleteContents( true ); + + Mem_Free16( joints ); + joints = NULL; + numJoints = 0; + + modelDef = NULL; + + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ForceUpdate(); +} + +/* +===================== +idAnimator::PushAnims +===================== +*/ +void idAnimator::PushAnims( int channelNum, int currentTime, int blendTime ) { + int i; + idAnimBlend *channel; + + channel = channels[ channelNum ]; + if ( !channel[ 0 ].GetWeight( currentTime ) || ( channel[ 0 ].starttime == currentTime ) ) { + return; + } + + for( i = ANIM_MaxAnimsPerChannel - 1; i > 0; i-- ) { + channel[ i ] = channel[ i - 1 ]; + } + + channel[ 0 ].Reset( modelDef ); + channel[ 1 ].Clear( currentTime, blendTime ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetModel +===================== +*/ +idRenderModel *idAnimator::SetModel( const char *modelname ) { + int i, j; + + FreeData(); + + // check if we're just clearing the model + if ( !modelname || !*modelname ) { + return NULL; + } + + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( !modelDef ) { + return NULL; + } + + idRenderModel *renderModel = modelDef->ModelHandle(); + if ( !renderModel ) { + modelDef = NULL; + return NULL; + } + + // make sure model hasn't been purged + modelDef->Touch(); + +// RAVEN BEGIN +// bdube: make sure models dont get purged + if ( modelDef->ModelHandle() ) { + modelDef->ModelHandle()->SetLevelLoadReferenced ( true ); + } +// RAVEN END + + modelDef->SetupJoints( &numJoints, &joints, frameBounds, removeOriginOffset ); + modelDef->ModelHandle()->Reset(); + + // set the modelDef on all channels + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( modelDef ); + } + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::Size +===================== +*/ +size_t idAnimator::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +/* +===================== +idAnimator::SetEntity +===================== +*/ +void idAnimator::SetEntity( idEntity *ent ) { + entity = ent; +} + +/* +===================== +idAnimator::GetEntity +===================== +*/ +idEntity *idAnimator::GetEntity( void ) const { + return entity; +} + +/* +===================== +idAnimator::RemoveOriginOffset +===================== +*/ +void idAnimator::RemoveOriginOffset( bool remove ) { + removeOriginOffset = remove; +} + +/* +===================== +idAnimator::RemoveOrigin +===================== +*/ +bool idAnimator::RemoveOrigin( void ) const { + return removeOriginOffset; +} + +/* +===================== +idAnimator::GetJointList +===================== +*/ +void idAnimator::GetJointList( const char *jointnames, idList &jointList ) const { + if ( modelDef ) { + modelDef->GetJointList( jointnames, jointList ); + } +} + +/* +===================== +idAnimator::NumAnims +===================== +*/ +int idAnimator::NumAnims( void ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->NumAnims(); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +const idAnim *idAnimator::GetAnim( int index ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->GetAnim( index ); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +int idAnimator::GetAnim( const char *name ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->GetAnim( name ); +} + +/* +===================== +idAnimator::HasAnim +===================== +*/ +bool idAnimator::HasAnim( const char *name ) const { + if ( !modelDef ) { + return false; + } + + return modelDef->HasAnim( name ); +} + +/* +===================== +idAnimator::NumJoints +===================== +*/ +int idAnimator::NumJoints( void ) const { + return numJoints; +} + +/* +===================== +idAnimator::ModelHandle +===================== +*/ +idRenderModel *idAnimator::ModelHandle( void ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::ModelDef +===================== +*/ +const idDeclModelDef *idAnimator::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnimator::CurrentAnim +===================== +*/ +idAnimBlend *idAnimator::CurrentAnim( int channelNum ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CurrentAnim : channel out of range" ); + } + + return &channels[ channelNum ][ 0 ]; +} + +/* +===================== +idAnimator::Clear +===================== +*/ +void idAnimator::Clear( int channelNum, int currentTime, int cleartime ) { + int i; + idAnimBlend *blend; + + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::Clear : channel out of range" ); + } + + blend = channels[ channelNum ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->Clear( currentTime, cleartime ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetFrame +===================== +*/ +void idAnimator::SetFrame( int channelNum, int animNum, const frameBlend_t & frameBlend ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SetFrame : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, gameLocal.time, 0 ); + channels[ channelNum ][ 0 ].SetFrame( modelDef, animNum, frameBlend ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::CycleAnim +===================== +*/ +void idAnimator::CycleAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CycleAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].CycleAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + + // If the old animation and new animation both sync cycles then do so now + idAnimBlend* oldChannel = &channels[channelNum][1]; + idAnimBlend* newChannel = &channels[channelNum][0]; + if ( oldChannel->AnimNum ( ) && newChannel->AnimNum ( ) ) { + if ( oldChannel->Anim ( )->GetAnimFlags ( ).sync_cycle && newChannel->Anim ( )->GetAnimFlags ( ).sync_cycle ) { + // Calculate the percentage through the animation the old channel was + float f = (float)oldChannel->GetFrameNumber ( currentTime ) / (float)oldChannel->NumFrames ( ); + // Move the new channel back in time to start it mid cycle + newChannel->SetStartTime ( gameLocal.time - f * (newChannel->Length ( ) / newChannel->GetPlaybackRate()) ); + } + } +} + +/* +===================== +idAnimator::PlayAnim +===================== +*/ +void idAnimator::PlayAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::PlayAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].PlayAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::SyncAnimChannels +===================== +*/ +void idAnimator::SyncAnimChannels( int channelNum, int fromChannelNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) || ( fromChannelNum < 0 ) || ( fromChannelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SyncToChannel : channel out of range" ); + } + + idAnimBlend &fromBlend = channels[ fromChannelNum ][ 0 ]; + idAnimBlend &toBlend = channels[ channelNum ][ 0 ]; + + float weight = fromBlend.blendEndValue; + if ( ( fromBlend.Anim() != toBlend.Anim() ) || ( fromBlend.GetStartTime() != toBlend.GetStartTime() ) || ( fromBlend.GetEndTime() != toBlend.GetEndTime() ) ) { + PushAnims( channelNum, currentTime, blendTime ); + toBlend = fromBlend; + toBlend.blendStartValue = 0.0f; + toBlend.blendEndValue = 0.0f; + } + toBlend.SetWeight( weight, currentTime - 1, blendTime ); + + // disable framecommands on the current channel so that commands aren't called twice + toBlend.AllowFrameCommands( false ); + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +// RAVEN BEGIN +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindExistingJointMod( jointHandle_t jointnum, int *index ) { + jointMod_t* jointMod; + int i; + + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( index ) + { + *index = i; + } + return jointMod; +} + +// bdube: added methods +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindJointMod ( jointHandle_t jointnum ) { + jointMod_t* jointMod; + int i; + + jointMod = FindExistingJointMod( jointnum, &i ); + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMod->transform_pos = JOINTMOD_NONE; + jointMod->angularVelocity.Init ( 0, 0, 0, 0, idAngles(0,0,0), idAngles(0,0,0) ); + jointMod->lastTime = 0; + jointMods.Insert( jointMod, i ); + } + + return jointMod; +} + +// abahr: +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( const char* animName, float rate ) { + SetPlaybackRate( GetAnim(animName), rate ); +} + +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( int animHandle, float rate ) { + idAnim* anim = const_cast( GetAnim(animHandle) ); + if( anim ) { + anim->SetPlaybackRate( rate ); + } +} +// RAVEN END + +/* +===================== +idAnimator::SetJointPos +===================== +*/ +void idAnimator::SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { +// RAVEN BEGIN +// bdube: moved old code to FindJointMod +/* + int i; +*/ +// + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + +// RAVEN BEGIN +// bdube: moved old code to FindJointMod + jointMod = FindJointMod ( jointnum ); + +/* + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } +*/ +// RAVEN END + + jointMod->pos = pos; + jointMod->transform_pos = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAxis +===================== +*/ +void idAnimator::SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ) { +// RAVEN BEGIN +// bdube: moved old code to FindJointMod +/* + int i; +*/ +// RAVEN END + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + +// RAVEN BEGIN +// bdube: moved old code to FindJointMod + jointMod = FindJointMod ( jointnum ); +/* + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->pos.Zero(); + jointMod->transform_pos = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } +*/ +// RAVEN END + + jointMod->mat = mat; + jointMod->transform_axis = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +// RAVEN BEGIN +// twhitaker: just added this for uniformity +/* +===================== +idAnimator::GetJointAxis +===================== +*/ +void idAnimator::GetJointAxis( jointHandle_t jointnum, idMat3 &mat ) { + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + mat = FindJointMod ( jointnum )->mat; +} +// RAVEN END + +/* +===================== +idAnimator::CollapseJoint + +Add a joint modification for the given joint to collapse it to another joint +===================== +*/ +void idAnimator::CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ) { + jointMod_t *jointMod; + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = FindJointMod ( jointnum ); + + jointMod->transform_pos = JOINTMOD_COLLAPSE; + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat.Zero ( ); + jointMod->collapseJoint = collapseTo; +} + +/* +===================== +idAnimator::CollapseJoints + +Add a collapse joint modification for each joint listed in "jointnames" to the given joint "coppaseJoint" +===================== +*/ +void idAnimator::CollapseJoints ( const char *jointnames, jointHandle_t collpaseJoint ) { + idList jointList; + int i; + + GetJointList( jointnames, jointList ); + + for ( i = 0; i < jointList.Num(); i ++ ) { + CollapseJoint ( jointList[i], collpaseJoint ); + } +} + +// RAVEN BEGIN +// bdube: added more programmer controlled joint methods +/* +===================== +idAnimator::AimJointAt +===================== +*/ +void idAnimator::AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int currentTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + idVec3 offset; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + + GetJointTransform ( jointnum, currentTime, offset, axis ); + + origin = entity->GetPhysics()->GetOrigin() + offset * entity->GetPhysics()->GetAxis(); + + dir = pos - origin; + dir.NormalizeFast ( ); + entity->GetPhysics()->GetAxis().ProjectVector ( dir, localDir ); + + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat = localDir.ToMat3(); + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAngularVelocity +===================== +*/ +void idAnimator::SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + if ( !jointMod->lastTime ) { + jointMod->lastTime = currentTime; + jointMod->mat = idAngles(0,0,0).ToMat3(); + } + + jointMod->transform_axis = JOINTMOD_LOCAL; + + if ( blendTime <= 0 ) { + jointMod->angularVelocity.Init ( currentTime, 0, 0, 1, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } else { + jointMod->angularVelocity.Init ( currentTime, blendTime/2, blendTime/2, blendTime, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +idAngles idAnimator::GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + return jointMod->angularVelocity.GetCurrentValue( currentTime ); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +void idAnimator::ClearJointAngularVelocity ( jointHandle_t jointnum ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + jointMod->angularVelocity.Init( 0, 0, 0, 0, jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue(), jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue() ); +} +// RAVEN END + +/* +===================== +idAnimator::ClearJoint +===================== +*/ +void idAnimator::ClearJoint( jointHandle_t jointnum ) { + int i; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + delete jointMods[ i ]; + jointMods.RemoveIndex( i ); + ForceUpdate(); + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } +} + +/* +===================== +idAnimator::ClearAllJoints +===================== +*/ +void idAnimator::ClearAllJoints( void ) { + if ( jointMods.Num() ) { + ForceUpdate(); + } + jointMods.DeleteContents( true ); +} + +/* +===================== +idAnimator::ClearAllAnims +===================== +*/ +void idAnimator::ClearAllAnims( int currentTime, int cleartime ) { + int i; + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + Clear( i, currentTime, cleartime ); + } + + ClearAFPose(); + ForceUpdate(); +} + +/* +==================== +idAnimator::GetDelta +==================== +*/ +void idAnimator::GetDelta( int fromtime, int totime, idVec3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Zero(); + return; + } + + delta.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + } +} + +/* +==================== +idAnimator::GetDeltaRotation +==================== +*/ +bool idAnimator::GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + idQuat q; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Identity(); + return false; + } + + q.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + } + + if ( blendWeight > 0.0f ) { + delta = q.ToMat3(); + return true; + } else { + delta.Identity(); + return false; + } +} + +/* +==================== +idAnimator::GetOrigin +==================== +*/ +void idAnimator::GetOrigin( int currentTime, idVec3 &pos ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() ) { + pos.Zero(); + return; + } + + pos.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + } + + pos += modelDef->GetVisualOffset(); +} + +/* +==================== +idAnimator::GetBounds +==================== +*/ +bool idAnimator::GetBounds( int currentTime, idBounds &bounds ) { + int i, j; + const idAnimBlend *blend; + int count; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + if ( AFPoseJoints.Num() ) { + bounds = AFPoseBounds; + count = 1; + } else { + bounds.Clear(); + count = 0; + } + + blend = channels[ 0 ]; + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->AddBounds( currentTime, bounds, removeOriginOffset ) ) { + count++; + } + } + } + + if ( !count ) { + if ( !frameBounds.IsCleared() ) { + bounds = frameBounds; + return true; + } else { + bounds.Zero(); + return false; + } + } + + bounds.TranslateSelf( modelDef->GetVisualOffset() ); +// RAVEN BEGIN +// jscott: HACK! The bounds only get the range of the skeleton, so add in the distance between the highest bone and the top of the head + bounds[1][2] += 4.0f; +// RAVEN END + + if ( g_debugBounds.GetBool() ) { + if ( bounds[1][0] - bounds[0][0] > 2048 || bounds[1][1] - bounds[0][1] > 2048 ) { + if ( entity ) { + gameLocal.Warning( "big frameBounds on entity '%s' with model '%s': %f,%f", entity->name.c_str(), modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } else { + gameLocal.Warning( "big frameBounds on model '%s': %f,%f", modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } + } + } + + frameBounds = bounds; + + return true; +} + +/* +===================== +idAnimator::InitAFPose +===================== +*/ +void idAnimator::InitAFPose( void ) { + + if ( !modelDef ) { + return; + } + + AFPoseJoints.SetNum( modelDef->Joints().Num(), false ); + AFPoseJoints.SetNum( 0, false ); + AFPoseJointMods.SetNum( modelDef->Joints().Num(), false ); + + if ( AFPoseJointFrame == NULL || AFPoseJointFrameSize != modelDef->Joints().Num() ) { + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = (idJointQuat *) Mem_Alloc16( modelDef->Joints().Num() * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + AFPoseJointFrameSize = modelDef->Joints().Num(); + } +} + +/* +===================== +idAnimator::SetAFPoseJointMod +===================== +*/ +void idAnimator::SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ) { + AFPoseJointMods[jointNum].mod = mod; + AFPoseJointMods[jointNum].axis = axis; + AFPoseJointMods[jointNum].origin = origin; + + int index = idBinSearch_GreaterEqual( AFPoseJoints.Ptr(), AFPoseJoints.Num(), jointNum ); + if ( index >= AFPoseJoints.Num() || jointNum != AFPoseJoints[index] ) { + AFPoseJoints.Insert( jointNum, index ); + } +} + +/* +===================== +idAnimator::FinishAFPose +===================== +*/ +void idAnimator::FinishAFPose( int animNum, const idBounds &bounds, const int time ) { + int i, j; + int numJoints; + int parentNum; + int jointMod; + int jointNum; + const int * jointParent; + + if ( !modelDef ) { + return; + } + + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return; + } + + numJoints = modelDef->Joints().Num(); + if ( !numJoints ) { + return; + } + + idRenderModel *md5 = modelDef->ModelHandle(); + const idMD5Anim *md5anim = anim->MD5Anim( 0 ); + + if ( numJoints != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", md5->Name(), numJoints, md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + md5anim->GetSingleFrame( 0, jointFrame, modelDef->GetChannelJoints( ANIMCHANNEL_ALL ), modelDef->NumJointsOnChannel( ANIMCHANNEL_ALL ) ); + + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( *joints ) ); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( AFPoseJoints.Num() && AFPoseJoints[0] == 0 ) { + switch( AFPoseJointMods[0].mod ) { + case AF_JOINTMOD_AXIS: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + } + j = 1; + } else { + j = 0; + } + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // transform the child joints + for( i = 1; j < AFPoseJoints.Num(); j++, i++ ) { + jointMod = AFPoseJoints[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod - 1 ); + i = jointMod; + + parentNum = jointParent[i]; + + switch( AFPoseJointMods[jointMod].mod ) { + case AF_JOINTMOD_AXIS: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[i].SetRotation( joints[i].ToMat3() * joints[parentNum].ToMat3() ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + } + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + // untransform hierarchy + SIMDProcessor->UntransformJoints( joints, jointParent, 1, numJoints - 1 ); + + // convert joint matrices back to joint quaternions + SIMDProcessor->ConvertJointMatsToJointQuats( AFPoseJointFrame, joints, numJoints ); + + // find all modified joints and their parents + bool *blendJoints = (bool *) _alloca16( numJoints * sizeof( bool ) ); + memset( blendJoints, 0, numJoints * sizeof( bool ) ); + + // mark all modified joints and their parents + for( i = 0; i < AFPoseJoints.Num(); i++ ) { + for( jointNum = AFPoseJoints[i]; jointNum != INVALID_JOINT; jointNum = jointParent[jointNum] ) { + blendJoints[jointNum] = true; + } + } + + // lock all parents of modified joints + AFPoseJoints.SetNum( 0, false ); + for ( i = 0; i < numJoints; i++ ) { + if ( blendJoints[i] ) { + AFPoseJoints.Append( i ); + } + } + + AFPoseBounds = bounds; + AFPoseTime = time; + + ForceUpdate(); +} + +/* +===================== +idAnimator::SetAFPoseBlendWeight +===================== +*/ +void idAnimator::SetAFPoseBlendWeight( float blendWeight ) { + AFPoseBlendWeight = blendWeight; +} + +/* +===================== +idAnimator::BlendAFPose +===================== +*/ +bool idAnimator::BlendAFPose( idJointQuat *blendFrame ) const { + + if ( !AFPoseJoints.Num() ) { + return false; + } + + SIMDProcessor->BlendJoints( blendFrame, AFPoseJointFrame, AFPoseBlendWeight, AFPoseJoints.Ptr(), AFPoseJoints.Num() ); + + return true; +} + +/* +===================== +idAnimator::ClearAFPose +===================== +*/ +void idAnimator::ClearAFPose( void ) { + if ( AFPoseJoints.Num() ) { + ForceUpdate(); + } + AFPoseBlendWeight = 1.0f; + AFPoseJoints.SetNum( 0, false ); + AFPoseBounds.Clear(); + AFPoseTime = 0; +} + +/* +===================== +idAnimator::ServiceAnims +===================== +*/ +void idAnimator::ServiceAnims( int fromtime, int totime ) { + int i, j; + idAnimBlend *blend; + + if ( !modelDef ) { + return; + } + + if ( modelDef->ModelHandle() ) { + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + blend->CallFrameCommands( entity, fromtime, totime ); + } + } + } + + if ( !IsAnimating( totime ) ) { + stoppedAnimatingUpdate = true; + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + + // present one more time with stopped animations so the renderer can properly recreate interactions + entity->BecomeActive( TH_UPDATEVISUALS ); + } + } +} + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation +/* +===================== +idAnimator::IsAnimating +===================== +*/ +bool idAnimator::IsAnimating( int currentTime, bool IgnoreAF ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( !IgnoreAF && AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( !blend->IsDone( currentTime ) ) { + return true; + } + } + } + + return false; +} +// RAVEN END + +/* +===================== +idAnimator::FrameHasChanged +===================== +*/ +bool idAnimator::FrameHasChanged( int currentTime ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->FrameHasChanged( currentTime ) ) { + return true; + } + } + } + + if ( forceUpdate && IsAnimating( currentTime ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimator::CreateFrame +===================== +*/ +bool idAnimator::CreateFrame( int currentTime, bool force ) { + int i, j; + int numJoints; + int parentNum; + bool hasAnim; + bool debugInfo; + float baseBlend; + float blendWeight; + const idAnimBlend * blend; + const int * jointParent; +// RAVEN BEGIN +// bdube: removed const + jointMod_t * jointMod; +// RAVEN END + const idJointQuat * defaultPose; + + static idCVar r_showSkel( "r_showSkel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw the skeleton when model animates, 1 = draw model with skeleton, 2 = draw skeleton only, 3 = draw joints only", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + +// RAVEN BEGIN +// rjohnson: always check the time transform except force. Even when skeleton is on + if ( !force ) { + if ( lastTransformTime == currentTime ) { + return false; + } + } + + if ( !force && !r_showSkel.GetInteger() ) { + if ( lastTransformTime != -1 && !stoppedAnimatingUpdate && !IsAnimating( currentTime ) ) { + return false; + } + } + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return false; + } +#endif +// RAVEN END + + lastTransformTime = currentTime; + stoppedAnimatingUpdate = false; + + if ( entity && ( ( g_debugAnim.GetInteger() == entity->entityNumber ) || ( g_debugAnim.GetInteger() == -2 ) ) ) { + debugInfo = true; + gameLocal.Printf( "---------------\n%d: entity '%s':\n", gameLocal.time, entity->GetName() ); + gameLocal.Printf( "model '%s':\n", modelDef->GetModelName() ); + } else { + debugInfo = false; + } + + // init the joint buffer + if ( AFPoseJoints.Num() ) { + // initialize with AF pose anim for the case where there are no other animations and no AF pose joint modifications + defaultPose = AFPoseJointFrame; + } else { + defaultPose = modelDef->GetDefaultPose(); + } + + if ( !defaultPose ) { + //gameLocal.Warning( "idAnimator::CreateFrame: no defaultPose on '%s'", modelDef->Name() ); + return false; + } + + numJoints = modelDef->Joints().Num(); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( jointFrame[0] ) ); + SIMDProcessor->Memcpy( jointFrame, defaultPose, numJoints * sizeof( jointFrame[0] ) ); + + hasAnim = false; + + // blend the all channel + baseBlend = 0.0f; + blend = channels[ ANIMCHANNEL_ALL ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_ALL, numJoints, jointFrame, baseBlend, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( baseBlend >= 1.0f ) { + break; + } + } + } + + // only blend other channels if there's enough space to blend into + if ( baseBlend < 1.0f ) { + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !modelDef->NumJointsOnChannel( i ) ) { + continue; + } + if ( i == ANIMCHANNEL_EYELIDS ) { + // eyelids blend over any previous anims, so skip it and blend it later + continue; + } + blendWeight = baseBlend; + blend = channels[ i ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, i, numJoints, jointFrame, blendWeight, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + + if ( debugInfo && !AFPoseJoints.Num() && !blendWeight ) { + gameLocal.Printf( "%d: %s using default pose in model '%s'\n", gameLocal.time, channelNames[ i ], modelDef->GetModelName() ); + } + } + } + + // blend in the eyelids + if ( modelDef->NumJointsOnChannel( ANIMCHANNEL_EYELIDS ) ) { + blend = channels[ ANIMCHANNEL_EYELIDS ]; + blendWeight = baseBlend; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_EYELIDS, numJoints, jointFrame, blendWeight, removeOriginOffset, true, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + } + + // blend the articulated figure pose + if ( BlendAFPose( jointFrame ) ) { + hasAnim = true; + } + + if ( !hasAnim && !jointMods.Num() ) { + // no animations were updated + return false; + } + + // convert the joint quaternions to rotation matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // check if we need to modify the origin + if ( jointMods.Num() && ( jointMods[0]->jointnum == 0 ) ) { + jointMod = jointMods[0]; + + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetRotation( jointMod->mat * joints[0].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[0].SetRotation( joints[0].ToMat3() * jointMod->mat ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetRotation( jointMod->mat ); + break; + } + + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetTranslation( joints[0].ToVec3() + jointMod->pos ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetTranslation( jointMod->pos ); + break; + } + j = 1; + } else { + j = 0; + } + + // add in the model offset + joints[0].SetTranslation( joints[0].ToVec3() + modelDef->GetVisualOffset() ); + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // add in any joint modifications + for( i = 1; j < jointMods.Num(); j++, i++ ) { + jointMod = jointMods[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod->jointnum - 1 ); + i = jointMod->jointnum; + + parentNum = jointParent[i]; + +// RAVEN BEGIN +// bdube: angular velocity on joints + if ( jointMod->angularVelocity.GetStartTime ( ) ) { + idAngles angles; + angles = jointMod->angularVelocity.GetCurrentValue ( currentTime ) * MS2SEC(currentTime-jointMod->lastTime); + angles.Normalize360 ( ); + jointMod->mat *= angles.ToMat3 ( ); + } +// RAVEN END + + // modify the axis + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + joints[i].SetRotation( joints[i].ToMat3() * joints[ parentNum ].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetRotation( jointMod->mat * ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetRotation( jointMod->mat * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetRotation( ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) * jointMod->mat ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetRotation( jointMod->mat ); + break; + } + + // modify the position + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetTranslation( joints[parentNum].ToVec3() + ( joints[i].ToVec3() + jointMod->pos ) * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + jointMod->pos * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() + jointMod->pos ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetTranslation( jointMod->pos ); + break; + + case JOINTMOD_COLLAPSE: + joints[i].SetTranslation( joints[jointMod->collapseJoint].ToVec3() ); + break; + } + +// RAVEN BEGIN +// bdube: more joint control + jointMod->lastTime = currentTime; +// RAVEN END + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + return true; +} + +/* +===================== +idAnimator::ForceUpdate +===================== +*/ +void idAnimator::ForceUpdate( void ) { + lastTransformTime = -1; + forceUpdate = true; +} + +/* +===================== +idAnimator::ClearForceUpdate +===================== +*/ +void idAnimator::ClearForceUpdate( void ) { + forceUpdate = false; +} + +/* +===================== +idAnimator::GetJointTransform + +===================== +*/ +bool idAnimator::GetJointTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef || ( jointHandle < 0 ) || ( jointHandle >= modelDef->NumJoints() ) ) { + return false; + } + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + CreateFrame( currentTime, false ); + + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + + return true; +} + +/* +===================== +idAnimator::GetJointLocalTransform +===================== +*/ +bool idAnimator::GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef ) { + return false; + } + + const idList &modelJoints = modelDef->Joints(); + + if ( ( jointHandle < 0 ) || ( jointHandle >= modelJoints.Num() ) ) { + return false; + } + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + // FIXME: overkill + CreateFrame( currentTime, false ); + + if ( jointHandle > 0 ) { + idJointMat m = joints[ jointHandle ]; + m /= joints[ modelJoints[ jointHandle ].parentNum ]; + offset = m.ToVec3(); + axis = m.ToMat3(); + } else { + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + } + + return true; +} + +/* +===================== +idAnimator::GetJointHandle +===================== +*/ +jointHandle_t idAnimator::GetJointHandle( const char *name ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return INVALID_JOINT; + } + + return modelDef->ModelHandle()->GetJointHandle( name ); +} + +/* +===================== +idAnimator::GetJointName +===================== +*/ +const char *idAnimator::GetJointName( jointHandle_t handle ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return ""; + } + + return modelDef->ModelHandle()->GetJointName( handle ); +} + +/* +===================== +idAnimator::GetChannelForJoint +===================== +*/ +int idAnimator::GetChannelForJoint( jointHandle_t joint ) const { + if ( !modelDef ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: NULL model" ); + } + + if ( ( joint < 0 ) || ( joint >= numJoints ) ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: invalid joint num (%d)", joint ); + } + + return modelDef->GetJoint( joint )->channel; +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( const char *name ) const { + return GetFirstChild( GetJointHandle( name ) ); +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( jointHandle_t jointnum ) const { + int i; + int num; + const jointInfo_t *joint; + + if ( !modelDef ) { + return INVALID_JOINT; + } + + num = modelDef->NumJoints(); + if ( !num ) { + return jointnum; + } + joint = modelDef->GetJoint( 0 ); + for( i = 0; i < num; i++, joint++ ) { + if ( joint->parentNum == jointnum ) { + return ( jointHandle_t )joint->num; + } + } + return jointnum; +} + +/* +===================== +idAnimator::GetJoints +===================== +*/ +void idAnimator::GetJoints( int *numJoints, idJointMat **jointsPtr ) { + *numJoints = this->numJoints; + *jointsPtr = this->joints; +} + +/* +===================== +idAnimator::GetAnimFlags +===================== +*/ +const animFlags_t idAnimator::GetAnimFlags( int animNum ) const { + animFlags_t result; + + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->GetAnimFlags(); + } + + memset( &result, 0, sizeof( result ) ); + return result; +} + +/* +===================== +idAnimator::IsBlending + +Returns true if the given channel number is currently blending more than one animation +===================== +*/ +bool idAnimator::IsBlending ( int channel, int currentTime ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::GetAnimCount : channel (%d) out of range", channel ); + } + + int i; + for( i = 1; i < ANIM_MaxAnimsPerChannel; i++ ) { + if ( !channels[ channel ][ i ].IsDone ( currentTime ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idAnimator::NumFrames +===================== +*/ +int idAnimator::NumFrames( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumFrames(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::NumSyncedAnims +===================== +*/ +int idAnimator::NumSyncedAnims( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumAnims(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::AnimName +===================== +*/ +const char *idAnimator::AnimName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Name(); + } else { + return ""; + } +} + +/* +===================== +idAnimator::AnimFullName +===================== +*/ +const char *idAnimator::AnimFullName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } else { + return ""; + } +} + +// RAVEN BEGIN +// rjohnson: more output for animators +/* +===================== +idAnimator::AnimMD5Name +===================== +*/ +const char *idAnimator::AnimMD5Name( int animNum, int index ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + const idMD5Anim *md5Anim = anim->MD5Anim( index ); + + if ( md5Anim ) { + return md5Anim->Name(); + } else { + return ""; + } + } else { + return ""; + } +} +// RAVEN END + +/* +===================== +idAnimator::AnimLength +===================== +*/ +int idAnimator::AnimLength( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Length(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::TotalMovementDelta +===================== +*/ +const idVec3 &idAnimator::TotalMovementDelta( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->TotalMovementDelta(); + } else { + return vec3_origin; + } +} + +// RAVEN BEGIN +// nrausch: added func +/* +===================== +idAnimator::GetNearestJoint +===================== +*/ +jointHandle_t idAnimator::GetNearestJoint( const idVec3 &start, const idVec3 &end, int currentTime, jointHandle_t *jointList, int cnt ) { + + int numJoints = 0; + idJointMat *joints = 0; + GetJoints( &numJoints, &joints ); + + jointHandle_t closestJoint = INVALID_JOINT; + float closest = 0.0f; + + idVec3 a = end - start; + a.Normalize(); + + for ( int i = 0; i < cnt; ++i ) { + + // If we specified a null jointlist, just run through each joint + jointHandle_t jointNum = (jointHandle_t)i; + if ( jointList ) { + jointNum = jointList[i]; + } + + if ( jointNum != INVALID_JOINT ) { + idVec3 jointPos; + idMat3 jointMat; + + GetJointTransform( jointNum, currentTime, jointPos, jointMat ); + + idVec3 b = jointPos - start; + b.Normalize(); + + float newDot = DotProduct(a, b); + + if ( newDot > closest ) { + closest = newDot; + closestJoint = jointNum; + } + } + } + + return closestJoint; +} +// RAVEN END + +/*********************************************************************** + + Util functions + +***********************************************************************/ + +/* +===================== +ANIM_GetModelDefFromEntityDef +===================== +*/ +const idDeclModelDef *ANIM_GetModelDefFromEntityDef( const idDict *args ) { + const idDeclModelDef *modelDef; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef && modelDef->ModelHandle() ) { + return modelDef; + } + + return NULL; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const idDict *args ) { + idRenderModel *model; + const idDeclModelDef *modelDef; + + model = NULL; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + + if ( !model ) { + model = renderModelManager->FindModel( name ); + } + + if ( model && model->IsDefaultModel() ) { + return NULL; + } + + return model; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const char *classname ) { + const idDict *args; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + return ANIM_GetModelFromEntityDef( args ); +} + +/* +===================== +idGameEdit::ANIM_GetModelOffsetFromEntityDef +===================== +*/ +const idVec3 &idGameEdit::ANIM_GetModelOffsetFromEntityDef( const char *classname ) { + const idDict *args; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return vec3_origin; + } + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( !modelDef ) { + return vec3_origin; + } + + return modelDef->GetVisualOffset(); +} + +/* +===================== +idGameEdit::ANIM_GetModelFromName +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromName( const char *modelName ) { + const idDeclModelDef *modelDef; + idRenderModel *model; + + model = NULL; + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + if ( !model ) { + model = renderModelManager->FindModel( modelName ); + } + return model; +} + +/* +===================== +idGameEdit::ANIM_GetAnimFromEntityDef +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ) { + const idDict *args; + const idMD5Anim *md5anim; + const idAnim *anim; + int animNum; + const char *modelname; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + md5anim = NULL; + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( animNum ) { + anim = modelDef->GetAnim( animNum ); + if ( anim ) { + md5anim = anim->MD5Anim( 0 ); + } + } + } + return md5anim; +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetAnimFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntity( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return NULL; + } + + return anim->MD5Anim(0); +} + +/* +===================== +idGameEdit::ANIM_GetAnimPlaybackRateFromEntity +===================== +*/ +float idGameEdit::ANIM_GetAnimPlaybackRateFromEntity ( idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return 1.0f; + } + + return anim->GetPlaybackRate(); +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const char* idGameEdit::ANIM_GetAnimNameFromEntity ( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} +// RAVEN END + +/* +===================== +idGameEdit::ANIM_GetNumAnimsFromEntityDef +===================== +*/ +int idGameEdit::ANIM_GetNumAnimsFromEntityDef( const idDict *args ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + return modelDef->NumAnims(); + } + return 0; +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntityDef +===================== +*/ +const char *idGameEdit::ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + const idAnim* anim = modelDef->GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } + } + return ""; +} + +/* +===================== +idGameEdit::ANIM_GetAnim +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnim( const char *fileName ) { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + return animationLib->GetAnim( fileName ); +// RAVEN END +} + +/* +===================== +idGameEdit::ANIM_GetLength +===================== +*/ +int idGameEdit::ANIM_GetLength( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->Length(); +} + +/* +===================== +idGameEdit::ANIM_GetNumFrames +===================== +*/ +int idGameEdit::ANIM_GetNumFrames( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->NumFrames(); +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetFilename +===================== +*/ +const char * idGameEdit::ANIM_GetFilename( const idMD5Anim* anim ) { + return anim->Name(); +} + +/* +===================== +idGameEdit::ANIM_ConvertFrameToTime +===================== +*/ +int idGameEdit::ANIM_ConvertFrameToTime ( const idMD5Anim* anim, int frame ) { + frameBlend_t fb; + fb.frame1 = frame; + fb.frame2 = frame; + return anim->ConvertFrameToTime ( fb ); +} + +/* +===================== +idGameEdit::ANIM_ConvertTimeToFrame +===================== +*/ +int idGameEdit::ANIM_ConvertTimeToFrame ( const idMD5Anim* anim, int time ) { + frameBlend_t fb; + anim->ConvertTimeToFrame ( time, 0, fb ); + return fb.frame1; +} + +// RAVEN END + +/* +===================== +idGameEdit::ANIM_CreateAnimFrame +===================== +*/ +void idGameEdit::ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *joints, int time, const idVec3 &offset, bool remove_origin_offset ) { + int i; + frameBlend_t frame; + const idMD5Joint *md5joints; + int *index; + + if ( !model || model->IsDefaultModel() || !anim ) { + return; + } + + if ( numJoints != model->NumJoints() ) { + gameLocal.Error( "ANIM_CreateAnimFrame: different # of joints in renderEntity_t (%d) than in model (%s)(%d)", model->NumJoints(), model->Name(), numJoints ); + } + + if ( !model->NumJoints() ) { + // FIXME: Print out a warning? + return; + } + + if ( !joints ) { + gameLocal.Error( "ANIM_CreateAnimFrame: NULL joint frame pointer on model (%s)", model->Name() ); + } + + if ( numJoints != anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), numJoints, anim->Name(), anim->NumJoints() ); +// RAVEN END + for( i = 0; i < numJoints; i++ ) { + joints[i].SetRotation( mat3_identity ); + joints[i].SetTranslation( offset ); + } + return; + } + + // create index for all joints + index = ( int * )_alloca16( numJoints * sizeof( int ) ); + for ( i = 0; i < numJoints; i++ ) { + index[i] = i; + } + + // create the frame + anim->ConvertTimeToFrame( time, 1, frame ); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + anim->GetInterpolatedFrame( frame, jointFrame, index, numJoints ); + + // convert joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( remove_origin_offset ) { + joints[0].SetTranslation( offset ); + } else { + joints[0].SetTranslation( joints[0].ToVec3() + offset ); + } + + // transform the children + md5joints = model->GetJoints(); + for( i = 1; i < numJoints; i++ ) { + joints[i] *= joints[ md5joints[i].parent - md5joints ]; + } +} + +/* +===================== +idGameEdit::ANIM_CreateMeshForAnim +===================== +*/ +idRenderModel *idGameEdit::ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ) { + renderEntity_t ent; + const idDict *args; + const char *temp; + idRenderModel *newmodel; + const idMD5Anim *md5anim; + idStr filename; + idStr extension; + const idAnim *anim; + int animNum; + idVec3 offset; + const idDeclModelDef *modelDef; + + if ( !model || model->IsDefaultModel() ) { + return NULL; + } + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + memset( &ent, 0, sizeof( ent ) ); + + ent.bounds.Clear(); + ent.suppressSurfaceInViewID = 0; + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( !animNum ) { + return NULL; + } + anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + md5anim = anim->MD5Anim( 0 ); + ent.customSkin = modelDef->GetDefaultSkin(); + offset = modelDef->GetVisualOffset(); + } else { + filename = animname; + filename.ExtractFileExtension( extension ); + if ( !extension.Length() ) { + animname = args->GetString( va( "anim %s", animname ) ); + } + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( animname ); +// RAVEN END + offset.Zero(); + } + + if ( !md5anim ) { + return NULL; + } + + temp = args->GetString( "skin", "" ); + if ( temp[ 0 ] ) { + ent.customSkin = declManager->FindSkin( temp ); + } + + ent.numJoints = model->NumJoints(); +//RAVEN BEGIN +//amccarthy: Added allocation tag + ent.joints = ( idJointMat * )Mem_Alloc16( ent.numJoints * sizeof( *ent.joints ), MA_ANIM ); +//RAVEN END + + ANIM_CreateAnimFrame( model, md5anim, ent.numJoints, ent.joints, FRAME2MS( frame ), offset, remove_origin_offset ); + + newmodel = model->InstantiateDynamicModel( &ent, NULL, NULL, ~SURF_COLLISION ); + + Mem_Free16( ent.joints ); + ent.joints = NULL; + + return newmodel; +} + +// RAVEN BEGIN +// jsinger: allow for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +idAnim::idAnim( idDeclModelDef const *def, SerialInputStream &stream ) +{ + modelDef = def; + numAnims = stream.ReadIntValue(); + for(int i=0; i(); + for(int i=0; iAppend(stream.ReadStringValue()); + } + } + else + { + com.parmList = NULL; + } + } + flags.prevent_idle_override = stream.ReadBoolValue(); + flags.random_cycle_start = stream.ReadBoolValue(); + flags.ai_no_turn = stream.ReadBoolValue(); + flags.anim_turn = stream.ReadBoolValue(); + flags.sync_cycle = stream.ReadBoolValue(); + flags.ai_no_look = stream.ReadBoolValue(); + flags.ai_look_head_only = stream.ReadBoolValue(); + rate = stream.ReadFloatValue(); +} + +void idAnim::Write( SerialOutputStream &stream ) const +{ + stream.Write(numAnims); + for(int i=0; iName()); + } + stream.Write(name); + stream.Write(realname); + stream.Write(frameLookup.Num()); + for(int i=0; iNum()); + for(int j=0; jNum(); j++) + { + stream.Write((*frameCommands[i].parmList)[j]); + } + } + } + stream.Write((bool)flags.prevent_idle_override); + stream.Write((bool)flags.random_cycle_start); + stream.Write((bool)flags.ai_no_turn); + stream.Write((bool)flags.anim_turn); + stream.Write((bool)flags.sync_cycle); + stream.Write((bool)flags.ai_no_look); + stream.Write((bool)flags.ai_look_head_only); + stream.Write(rate); +} +#endif +// RAVEN END diff --git a/src/game/anim/Anim_Import.cpp b/src/game/anim/Anim_Import.cpp new file mode 100644 index 0000000..6f367aa --- /dev/null +++ b/src/game/anim/Anim_Import.cpp @@ -0,0 +1,627 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "../../MayaImport/maya_main.h" + +/*********************************************************************** + + Maya conversion functions + +***********************************************************************/ + +static idStr Maya_Error; + +static exporterInterface_t Maya_ConvertModel = NULL; +static exporterShutdown_t Maya_Shutdown = NULL; +static int importDLL = 0; + +bool idModelExport::initialized = false; + + +// RAVEN BEGIN +// scork: now needed to cleanup after last Maya model conversion since I cache them for speed during "exportmodels" +bool g_bMayaConversionCleanerRunning = false; // so we can check all calls to Maya_ConvertModel have been updated to include this ;-) +struct MayaConversionCleaner +{ + MayaConversionCleaner() + { + g_bMayaConversionCleanerRunning = true; + } + ~MayaConversionCleaner() + { + if ( Maya_ConvertModel ) + { + Maya_ConvertModel( NULL, NULL, NULL ); + } + g_bMayaConversionCleanerRunning = false; + } +}; +// RAVEN END + + +/* +==================== +idModelExport::idModelExport +==================== +*/ +idModelExport::idModelExport() { + Reset(); +} + +/* +==================== +idModelExport::Shutdown +==================== +*/ +void idModelExport::Shutdown( void ) { + if ( Maya_Shutdown ) { + Maya_Shutdown(); + } + + if ( importDLL ) { + sys->DLL_Unload( importDLL ); + } + + importDLL = 0; + Maya_Shutdown = NULL; + Maya_ConvertModel = NULL; + Maya_Error.Clear(); + initialized = false; +} + +/* +===================== +idModelExport::CheckMayaInstall + +Determines if Maya is installed on the user's machine +===================== +*/ +bool idModelExport::CheckMayaInstall( void ) { +// RAVEN BEGIN +// jscott:revisit - this should go into the exe +//#ifdef _WIN32 +#ifndef _WINDOWS + return false; +#elif 0 + HKEY hKey; + long lres, lType; + + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya\\4.5\\Setup\\InstallPath", &hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + + lres = RegQueryValueEx( hKey, "MAYA_INSTALL_LOCATION", NULL, (unsigned long*)&lType, (unsigned char*)NULL, (unsigned long*)NULL ); + + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#else + HKEY hKey; + long lres; + + // only check the non-version specific key so that we only have to update the maya dll when new versions are released + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya", &hKey ); + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#endif +} + +/* +===================== +idModelExport::LoadMayaDll + +Checks to see if we can load the Maya export dll +===================== +*/ +void idModelExport::LoadMayaDll( void ) { + exporterDLLEntry_t dllEntry; + char dllPath[ MAX_OSPATH ]; + + fileSystem->FindDLL( "MayaImport", dllPath, false ); + if ( !dllPath[ 0 ] ) { + return; + } + importDLL = sys->DLL_Load( dllPath ); + if ( !importDLL ) { + return; + } + + // look up the dll interface functions + dllEntry = ( exporterDLLEntry_t )sys->DLL_GetProcAddress( importDLL, "dllEntry" ); + Maya_ConvertModel = ( exporterInterface_t )sys->DLL_GetProcAddress( importDLL, "Maya_ConvertModel" ); + Maya_Shutdown = ( exporterShutdown_t )sys->DLL_GetProcAddress( importDLL, "Maya_Shutdown" ); + if ( !Maya_ConvertModel || !dllEntry || !Maya_Shutdown ) { + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Invalid interface on export DLL." ); + return; + } + + // initialize the DLL +// RAVEN BEGIN +// rhummer: unify allocation strategy to try to fix some of our crashes +#ifdef RV_UNIFIED_ALLOCATOR + if ( !dllEntry( MD5_VERSION, common, sys, Memory::Allocate, Memory::Free, Memory::MSize ) ) { +#else + if ( !dllEntry( MD5_VERSION, common, sys ) ) { +#endif +// RAVEN END + // init failed + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Export DLL init failed." ); + return; + } +// RAVEN BEGIN +// jscott: maya needs the source control in the tools dll + common->LoadToolsDLL(); +// RAVEN END +} + +/* +===================== +idModelExport::ConvertMayaToMD5 + +Checks if a Maya model should be converted to an MD5, and converts if if the time/date or +version number has changed. +===================== +*/ +bool idModelExport::ConvertMayaToMD5( void ) { + unsigned sourceTime; + unsigned destTime; + int version; + idToken cmdLine; + idStr path; + + // check if our DLL got loaded + if ( initialized && !Maya_ConvertModel ) { + Maya_Error = "MayaImport dll not loaded."; + return false; + } + + // if idAnimManager::forceExport is set then we always reexport Maya models + if ( idAnimManager::forceExport ) { + force = true; + } + +// RAVEN BEGIN +// bdube: fs_import path + + // we need to make sure we have a full path, so convert the filename to an OS path + path.AppendPath( cvarSystem->GetCVarString ("fs_importpath")); + path.AppendPath(src); + + idFile* f = fileSystem->OpenExplicitFileRead ( path ); + if ( !f ) { + Maya_Error = "could not open source file"; + return false; + } + sourceTime = f->Timestamp ( ); + fileSystem->CloseFile ( f ); + +/* + // get the source file's time + if ( fileSystem->ReadFile( src, NULL, &sourceTime ) < 0 ) { + // source file doesn't exist + return true; + } +*/ + +// RAVEN END + + // get the destination file's time + if ( !force && ( fileSystem->ReadFile( dest, NULL, &destTime ) >= 0 ) ) { + idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + + parser.LoadFile( dest ); + + // read the file version + if ( parser.CheckTokenString( MD5_VERSION_STRING ) ) { + version = parser.ParseInt(); + + // check the command line + if ( parser.CheckTokenString( "commandline" ) ) { + parser.ReadToken( &cmdLine ); + + // check the file time, scale, and version + if ( ( destTime >= sourceTime ) && ( version == MD5_VERSION ) && ( cmdLine == commandLine ) ) { + // don't convert it + return true; + } + } + } + } + + // if this is the first time we've been run, check if Maya is installed and load our DLL + if ( !initialized ) { + initialized = true; + + if ( !CheckMayaInstall() ) { + Maya_Error = "Maya not installed in registry."; + return false; + } + + LoadMayaDll(); + + // check if our DLL got loaded + if ( !Maya_ConvertModel ) { + Maya_Error = "Could not load MayaImport dll."; + return false; + } + } + + // we need to make sure we have a full path, so convert the filename to an OS path + src = fileSystem->RelativePathToOSPath( src ); + dest = fileSystem->RelativePathToOSPath( dest ); + + dest.ExtractFilePath( path ); + if ( path.Length() ) { + fileSystem->CreateOSPath( path ); + } + + // get the os path in case it needs to create one + path = fileSystem->RelativePathToOSPath( "" ); + + common->SetRefreshOnPrint( true ); +// RAVEN BEGIN +// scork: if this assert ever triggers it means there's a call to ConvertMayaToMD5() that doesn't have a 'MayaConversionCleaner' object above it. Go and put one in. Now. + assert( g_bMayaConversionCleanerRunning ); +// bdube: support src and dest ospath + Maya_Error = Maya_ConvertModel( cvarSystem->GetCVarString ( "fs_importpath" ), path, commandLine ); +// RAVEN END + common->SetRefreshOnPrint( false ); + if ( Maya_Error != "Ok" ) { + return false; + } + + // conversion succeded + return true; +} + +/* +==================== +idModelExport::Reset +==================== +*/ +void idModelExport::Reset( void ) { + force = false; + commandLine = ""; + src = ""; + dest = ""; +} + +/* +==================== +idModelExport::ExportModel +==================== +*/ +bool idModelExport::ExportModel( const char *model ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + const char *game = cvarSystem->GetCVarString( "fs_game" ); + + if ( strlen(game) == 0 ) { + game = BASE_GAMEDIR; + } + + Reset(); + src = model; + dest = model; + dest.SetFileExtension( MD5_MESH_EXT ); + + sprintf( commandLine, "mesh %s -dest %s -game %s", src.c_str(), dest.c_str(), game ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ExportAnim +==================== +*/ +bool idModelExport::ExportAnim( const char *anim ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + Reset(); + src = anim; + dest = anim; + dest.SetFileExtension( MD5_ANIM_EXT ); + + sprintf( commandLine, "anim %s -dest %s -game %s", src.c_str(), dest.c_str(), CD_BASEDIR ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ParseOptions +==================== +*/ +bool idModelExport::ParseOptions( idLexer &lex ) { + idToken token; + idStr destdir; + idStr sourcedir; + + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expected filename" ); + return false; + } + + src = token; + dest = token; + + while( lex.ReadToken( &token ) ) { + if ( token == "-" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expecting option" ); + return false; + } + if ( token == "sourcedir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -sourcedir" ); + return false; + } + sourcedir = token; + } else if ( token == "destdir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -destdir" ); + return false; + } + destdir = token; + } else if ( token == "dest" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing filename after -dest" ); + return false; + } + dest = token; + } else { + commandLine += va( " -%s", token.c_str() ); + } + } else { + commandLine += va( " %s", token.c_str() ); + } + } + + if ( sourcedir.Length() ) { + src.StripPath(); + sourcedir.BackSlashesToSlashes(); + sprintf( src, "%s/%s", sourcedir.c_str(), src.c_str() ); + } + + if ( destdir.Length() ) { + dest.StripPath(); + destdir.BackSlashesToSlashes(); + sprintf( dest, "%s/%s", destdir.c_str(), dest.c_str() ); + } + + return true; +} + +/* +==================== +idModelExport::ParseExportSection +==================== +*/ +int idModelExport::ParseExportSection( idParser &parser ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + idToken command; + idToken token; + idStr defaultCommands; + idLexer lex; + idStr temp; + idStr parms; + int count; + + // only export sections that match our export mask + if ( g_exportMask.GetString()[ 0 ] ) { + if ( parser.CheckTokenString( "{" ) ) { + parser.SkipBracedSection( false ); + return 0; + } + + parser.ReadToken( &token ); + if ( token.Icmp( g_exportMask.GetString() ) ) { + parser.SkipBracedSection(); + return 0; + } + parser.ExpectTokenString( "{" ); + } else if ( !parser.CheckTokenString( "{" ) ) { + // skip the export mask + parser.ReadToken( &token ); + parser.ExpectTokenString( "{" ); + } + + count = 0; + + lex.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + while( 1 ) { + + if ( !parser.ReadToken( &command ) ) { + parser.Error( "Unexpoected end-of-file" ); + break; + } + + if ( command == "}" ) { + break; + } + + if ( command == "options" ) { + parser.ParseRestOfLine( defaultCommands ); + } else if ( command == "addoptions" ) { + parser.ParseRestOfLine( temp ); + defaultCommands += " "; + defaultCommands += temp; + } else if ( ( command == "mesh" ) || ( command == "anim" ) || ( command == "camera" ) ) { + if ( !parser.ReadToken( &token ) ) { + parser.Error( "Expected filename" ); + } + + temp = token; + parser.ParseRestOfLine( parms ); + + if ( defaultCommands.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), defaultCommands.c_str() ); + } + + if ( parms.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), parms.c_str() ); + } + + lex.LoadMemory( temp, temp.Length(), parser.GetFileName() ); + + Reset(); + if ( ParseOptions( lex ) ) { + if ( command == "mesh" ) { + dest.SetFileExtension( MD5_MESH_EXT ); + } else if ( command == "anim" ) { + dest.SetFileExtension( MD5_ANIM_EXT ); + } else if ( command == "camera" ) { + dest.SetFileExtension( MD5_CAMERA_EXT ); + } else { + dest.SetFileExtension( command ); + } + idStr back = commandLine; + sprintf( commandLine, "%s %s -dest %s -game %s%s", command.c_str(), src.c_str(), dest.c_str(), CD_BASEDIR, commandLine.c_str() ); + if ( ConvertMayaToMD5() ) { + count++; + } else { + parser.Warning( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + } + } + lex.FreeSource(); + } else { + parser.Error( "Unknown token: %s", command.c_str() ); + parser.SkipBracedSection( false ); + break; + } + } + + return count; +} + +/* +================ +idModelExport::ExportDefFile +================ +*/ +int idModelExport::ExportDefFile( const char *filename ) { + idParser parser( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + idToken token; + int count; + + count = 0; + + if ( !parser.LoadFile( filename ) ) { + gameLocal.Printf( "Could not load '%s'\n", filename ); + return 0; + } + + while( parser.ReadToken( &token ) ) { + if ( token == "export" ) { + count += ParseExportSection( parser ); + } else { + parser.ReadToken( &token ); + parser.SkipBracedSection(); + } + } + + return count; +} + +/* +================ +idModelExport::ExportModels +================ +*/ +int idModelExport::ExportModels( const char *pathname, const char *extension ) { + int count; + + count = 0; + + idFileList *files; + int i; + + if ( !CheckMayaInstall() ) { + // if Maya isn't installed, don't bother checking if we have anims to export + return 0; + } + + gameLocal.Printf( "--------- Exporting models --------\n" ); + if ( !g_exportMask.GetString()[ 0 ] ) { + gameLocal.Printf( " Export mask: '%s'\n", g_exportMask.GetString() ); + } + + count = 0; + + files = fileSystem->ListFiles( pathname, extension ); + for( i = 0; i < files->GetNumFiles(); i++ ) { + count += ExportDefFile( va( "%s/%s", pathname, files->GetFile( i ) ) ); + } + fileSystem->FreeFileList( files ); + + gameLocal.Printf( "...%d models exported.\n", count ); + gameLocal.Printf( "-----------------------------------\n" ); + + return count; +} diff --git a/src/game/anim/Anim_Testmodel.cpp b/src/game/anim/Anim_Testmodel.cpp new file mode 100644 index 0000000..e47565f --- /dev/null +++ b/src/game/anim/Anim_Testmodel.cpp @@ -0,0 +1,964 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +============================================================================= + + MODEL TESTING + +Model viewing can begin with either "testmodel " + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Extension will default to ".ase" if not specified. + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + + g_testModelRotate + g_testModelAnimate + g_testModelBlend + +============================================================================= +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idAnimatedEntity, idTestModel ) + EVENT( EV_FootstepLeft, idTestModel::Event_Footstep ) + EVENT( EV_FootstepRight, idTestModel::Event_Footstep ) +END_CLASS + +/* +================ +idTestModel::idTestModel +================ +*/ +idTestModel::idTestModel() { + head = NULL; + headAnimator = NULL; + anim = 0; + headAnim = 0; + starttime = 0; + animtime = 0; + mode = 0; + frame = 0; +} + +/* +================ +idTestModel::Save +================ +*/ +void idTestModel::Save( idSaveGame *savefile ) { +} + +/* +================ +idTestModel::Restore +================ +*/ +void idTestModel::Restore( idRestoreGame *savefile ) { + // FIXME: one day we may actually want to save/restore test models, but for now we'll just delete them + delete this; +} + +/* +================ +idTestModel::Spawn +================ +*/ +void idTestModel::Spawn( void ) { + idVec3 size; + idBounds bounds; + const char *headModel; + jointHandle_t joint; + idStr jointName; + idVec3 origin, modelOffset; + idMat3 axis; + const idKeyValue *kv; + copyJoints_t copyJoint; +// RAVEN BEGIN +// ddynerman: new heads + idAFAttachment *headEnt; +// RAVEN END + + if ( renderEntity.hModel && renderEntity.hModel->IsDefaultModel() && !animator.ModelDef() ) { + gameLocal.Warning( "Unable to create testmodel for '%s' : model defaulted", spawnArgs.GetString( "model" ) ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + mode = g_testModelAnimate.GetInteger(); + animator.RemoveOriginOffset( g_testModelAnimate.GetInteger() == 1 ); + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) ) { + spawnArgs.GetVector( "maxs", NULL, bounds[1] ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + bounds[ 0 ].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[ 1 ].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } + + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + // add the head model if it has one + headModel = spawnArgs.GetString( "def_head", "" ); + if ( headModel[ 0 ] ) { +// RAVEN BEGIN +// ddynerman: new heads + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found for 'joint_head'", jointName.c_str() ); + } else { + // copy any sounds in case we have frame commands on the head + idDict args; + const idKeyValue *sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + args.Set( "classname", headModel ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idTestModel::Spawn() - Unknown head model '%s'\n", headModel ); + return; + } + headEnt->spawnArgs.Set( "classname", headModel ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + + headEnt->BindToJoint( this, joint, true ); + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->InitCopyJoints ( ); + + head = headEnt; +// RAVEN END + + headAnimator = head.GetEntity()->GetAnimator(); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + jointName = kv->GetKey(); + + if ( jointName.StripLeadingOnce( "copy_joint_world " ) ) { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } else { + jointName.StripLeadingOnce( "copy_joint " ); + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + } + + copyJoint.from = animator.GetJointHandle( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s'", jointName.c_str() ); + continue; + } + + copyJoint.to = headAnimator->GetJointHandle( jointName ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head", jointName.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } + } + } + + // start any shader effects based off of the spawn time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + SetPhysics( &physicsObj ); + + // always keep updating so we can see the skeleton for the default pose + fl.forcePhysicsUpdate = true; + + gameLocal.Printf( "Added testmodel at origin = '%s', angles = '%s'\n", GetPhysics()->GetOrigin().ToString(), GetPhysics()->GetAxis().ToAngles().ToString() ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTestModel::~idTestModel +================ +*/ +idTestModel::~idTestModel() { + StopSound( SND_CHANNEL_ANY, false ); + if ( renderEntity.hModel ) { + gameLocal.Printf( "Removing testmodel %s\n", renderEntity.hModel->Name() ); + } else { + gameLocal.Printf( "Removing testmodel\n" ); + } + + if ( gameLocal.testmodel == this ) { + gameLocal.testmodel = NULL; + } + if ( head.GetEntity() ) { +// RAVEN BEGIN +// ddynerman: allow instant respawning of head + head.GetEntity()->SetName( va( "%s_oldhead", head.GetEntity()->name.c_str() ) ); +// RAVEN END + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + SetPhysics( NULL ); +} + +/* +=============== +idTestModel::Event_Footstep +=============== +*/ +void idTestModel::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idTestModel::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idTestModel::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +================ +idTestModel::Think +================ +*/ +void idTestModel::Think( void ) { + idVec3 pos; + idMat3 axis; + idAngles ang; + int i; + frameBlend_t frameBlend = { 0 }; + + if ( thinkFlags & TH_THINK ) { + if ( anim && ( gameLocal.testmodel == this ) && ( mode != g_testModelAnimate.GetInteger() ) ) { + StopSound( SND_CHANNEL_ANY, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + } + switch( g_testModelAnimate.GetInteger() ) { + default: + case 0: + // cycle anim with origin reset + if ( animator.NumFrames( anim ) <= 1 ) { + // single frame animations end immediately, so just cycle it since it's the same result + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + } else { + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + animator.RemoveOriginOffset( false ); + break; + + case 1: + // cycle anim with fixed origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 2: + // cycle anim with continuous origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 3: +// RAVEN BEGIN + // frame by frame with continuous origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + + case 4: + // play anim once + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 5: +// RAVEN BEGIN + // frame by frame with fixed origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + } + + mode = g_testModelAnimate.GetInteger(); + } + + if ( ( mode == 0 ) && ( gameLocal.time >= starttime + animtime ) ) { + starttime = gameLocal.time; + StopSound( SND_CHANNEL_ANY, false ); + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + + if ( headAnimator ) { + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + idMat3 mat = head.GetEntity()->GetPhysics()->GetAxis().Transpose(); + GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= head.GetEntity()->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + animator.GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } + } + + // update rotation + RunPhysics(); + + physicsObj.GetAngles( ang ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, idAngles( 0, g_testModelRotate.GetFloat() * 360.0f / 60.0f, 0 ), ang_zero ); + + idClipModel *clip = physicsObj.GetClipModel(); + if ( clip && animator.ModelDef() ) { + idVec3 neworigin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle( "origin" ); + animator.GetJointTransform( joint, gameLocal.time, neworigin, axis ); + neworigin = ( ( neworigin - animator.ModelDef()->GetVisualOffset() ) * physicsObj.GetAxis() ) + GetPhysics()->GetOrigin(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, neworigin, clip->GetAxis() ); +// RAVEN END + } + } + + UpdateAnimation(); + Present(); + + if ( ( gameLocal.testmodel == this ) && g_showTestModelFrame.GetInteger() && anim ) { + gameLocal.Printf( "^5 Anim: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n", animator.AnimFullName( anim ), animator.CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + animator.CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - animator.CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + if ( headAnim ) { + gameLocal.Printf( "^5 Head: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n\n", headAnimator->AnimFullName( headAnim ), headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + } else { + gameLocal.Printf( "\n\n" ); + } + } +} + +/* +================ +idTestModel::NextAnim +================ +*/ +void idTestModel::NextAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + anim++; + if ( anim >= animator.NumAnims() ) { + // anim 0 is no anim + anim = 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::PrevAnim +================ +*/ +void idTestModel::PrevAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + headAnim = 0; + anim--; + if ( anim < 0 ) { + anim = animator.NumAnims() - 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::NextFrame +================ +*/ +void idTestModel::NextFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame++; + if ( frame > animator.NumFrames( anim ) ) { + frame = 1; + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::PrevFrame +================ +*/ +void idTestModel::PrevFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame--; + if ( frame < 1 ) { + frame = animator.NumFrames( anim ); + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::TestAnim +================ +*/ +void idTestModel::TestAnim( const idCmdArgs &args ) { + idStr name; + int animNum; + const idAnim *newanim; + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: testanim \n" ); + return; + } + + newanim = NULL; + + name = args.Argv( 1 ); +#if 0 + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + idModelExport exporter; + exporter.ExportAnim( name ); + name.SetFileExtension( MD5_ANIM_EXT ); + md5anims[ 0 ] = animationLib.GetAnim( name ); + if ( md5anims[ 0 ] ) { + customAnim.SetAnim( animator.ModelDef(), name, name, 1, md5anims ); + newanim = &customAnim; + } + } else { + animNum = animator.GetAnim( name ); + } +#else + animNum = animator.GetAnim( name ); +#endif + + if ( !animNum ) { + gameLocal.Printf( "Animation '%s' not found.\n", name.c_str() ); + return; + } + + anim = animNum; + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + if ( !headAnim ) { + gameLocal.Printf( "Missing 'idle' anim for head.\n" ); + } + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + animname = name; + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +===================== +idTestModel::BlendAnim +===================== +*/ +void idTestModel::BlendAnim( const idCmdArgs &args ) { + int anim1; + int anim2; + + if ( args.Argc() < 4 ) { + gameLocal.Printf( "usage: testblend \n" ); + return; + } + + anim1 = gameLocal.testmodel->animator.GetAnim( args.Argv( 1 ) ); + if ( !anim1 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 1 ) ); + return; + } + + anim2 = gameLocal.testmodel->animator.GetAnim( args.Argv( 2 ) ); + if ( !anim2 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 2 ) ); + return; + } + + animname = args.Argv( 2 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim1, gameLocal.time, 0 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, FRAME2MS( atoi( args.Argv( 3 ) ) ) ); + + anim = anim2; + headAnim = 0; +} + +/*********************************************************************** + + Testmodel console commands + +***********************************************************************/ + +/* +================= +idTestModel::KeepTestModel_f + +Makes the current test model permanent, allowing you to place +multiple test models +================= +*/ +void idTestModel::KeepTestModel_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel.\n" ); + return; + } + + gameLocal.Printf( "modelDef %i kept\n", gameLocal.testmodel->renderEntity.hModel ); + + gameLocal.testmodel = NULL; +} + +/* +================= +idTestModel::TestSkin_f + +Sets a skin on an existing testModel +================= +*/ +void idTestModel::TestSkin_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "removing testSkin.\n" ); + gameLocal.testmodel->SetSkin( NULL ); + return; + } + + name = args.Argv( 1 ); + gameLocal.testmodel->SetSkin( declManager->FindSkin( name ) ); +} + +/* +================= +idTestModel::TestShaderParm_f + +Sets a shaderParm on an existing testModel +================= +*/ +void idTestModel::TestShaderParm_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() != 3 ) { + gameLocal.Printf( "USAGE: testShaderParm \n" ); + return; + } + + int parm = atoi( args.Argv( 1 ) ); + if ( parm < 0 || parm >= MAX_ENTITY_SHADER_PARMS ) { + gameLocal.Printf( "parmNum %i out of range\n", parm ); + return; + } + + float value; + if ( !stricmp( args.Argv( 2 ), "time" ) ) { + value = gameLocal.time * -0.001; + } else { + value = atof( args.Argv( 2 ) ); + } + + gameLocal.testmodel->SetShaderParm( parm, value ); +} + +/* +================= +idTestModel::TestModel_f + +Creates a static modelDef in front of the current position, which +can then be moved around +================= +*/ +void idTestModel::TestModel_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + const idDict * entityDef; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testmodel ) { + delete gameLocal.testmodel; + gameLocal.testmodel = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + entityDef = gameLocal.FindEntityDefDict( name, false ); + if ( entityDef ) { + dict = *entityDef; + } else { + if ( declManager->FindType( DECL_MODELDEF, name, false ) ) { + dict.Set( "model", name ); + } else { + // allow map models with underscore prefixes to be tested during development + // without appending an ase + if ( name[ 0 ] != '_' ) { + name.DefaultFileExtension( ".ase" ); + } + + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + idModelExport exporter; + exporter.ExportModel( name ); + name.SetFileExtension( MD5_MESH_EXT ); + } + + if ( !renderModelManager->CheckModel( name ) ) { + gameLocal.Printf( "Can't register model\n" ); + return; + } + dict.Set( "model", name ); + } + } + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "angle", va( "%f", player->viewAngles.yaw + 180.0f ) ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + gameLocal.testmodel = ( idTestModel * )gameLocal.SpawnEntityType( idTestModel::GetClassType(), &dict ); +// RAVEN END + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); +} + +/* +===================== +idTestModel::ArgCompletion_TestModel +===================== +*/ +void idTestModel::ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i, num; + + num = declManager->GetNumDecls( DECL_ENTITYDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_ENTITYDEF, i , false )->GetName() ); + } + num = declManager->GetNumDecls( DECL_MODELDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_MODELDEF, i , false )->GetName() ); + } + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", ".mb", NULL ); +} + +/* +===================== +idTestModel::TestParticleStopTime_f +===================== +*/ +void idTestModel::TestParticleStopTime_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + gameLocal.testmodel->UpdateVisuals(); +} + +/* +===================== +idTestModel::TestAnim_f +===================== +*/ +void idTestModel::TestAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->TestAnim( args ); +} + + +/* +===================== +idTestModel::ArgCompletion_TestAnim +===================== +*/ +void idTestModel::ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ) { + if ( gameLocal.testmodel ) { + idAnimator *animator = gameLocal.testmodel->GetAnimator(); + for( int i = 0; i < animator->NumAnims(); i++ ) { + callback( va( "%s %s", args.Argv( 0 ), animator->AnimFullName( i ) ) ); + } + } +} + +/* +===================== +idTestModel::TestBlend_f +===================== +*/ +void idTestModel::TestBlend_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->BlendAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextAnim_f +===================== +*/ +void idTestModel::TestModelNextAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextAnim( args ); +} + +/* +===================== +idTestModel::TestModelPrevAnim_f +===================== +*/ +void idTestModel::TestModelPrevAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextFrame_f +===================== +*/ +void idTestModel::TestModelNextFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextFrame( args ); +} + +/* +===================== +idTestModel::TestModelPrevFrame_f +===================== +*/ +void idTestModel::TestModelPrevFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevFrame( args ); +} diff --git a/src/game/anim/Anim_Testmodel.h b/src/game/anim/Anim_Testmodel.h new file mode 100644 index 0000000..408765e --- /dev/null +++ b/src/game/anim/Anim_Testmodel.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __ANIM_TESTMODEL_H__ +#define __ANIM_TESTMODEL_H__ + +/* +============================================================================================== + + idTestModel + +============================================================================================== +*/ + +class idTestModel : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idTestModel ); + + idTestModel(); + ~idTestModel(); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + + void NextAnim( const idCmdArgs &args ); + void PrevAnim( const idCmdArgs &args ); + void NextFrame( const idCmdArgs &args ); + void PrevFrame( const idCmdArgs &args ); + void TestAnim( const idCmdArgs &args ); + void BlendAnim( const idCmdArgs &args ); + + static void KeepTestModel_f( const idCmdArgs &args ); + static void TestModel_f( const idCmdArgs &args ); + static void ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestSkin_f( const idCmdArgs &args ); + static void TestShaderParm_f( const idCmdArgs &args ); + static void TestParticleStopTime_f( const idCmdArgs &args ); + static void TestAnim_f( const idCmdArgs &args ); + static void ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestBlend_f( const idCmdArgs &args ); + static void TestModelNextAnim_f( const idCmdArgs &args ); + static void TestModelPrevAnim_f( const idCmdArgs &args ); + static void TestModelNextFrame_f( const idCmdArgs &args ); + static void TestModelPrevFrame_f( const idCmdArgs &args ); + +private: +// RAVEN BEGIN +// ddynerman: new head system + idEntityPtr head; +// RAVEN END + idAnimator *headAnimator; + idAnim customAnim; + idPhysics_Parametric physicsObj; + idStr animname; + int anim; + int headAnim; + int mode; + int frame; + int starttime; + int animtime; + + idList copyJoints; + + virtual void Think( void ); + + void Event_Footstep( void ); +}; + +#endif /* !__ANIM_TESTMODEL_H__*/ diff --git a/src/game/client/ClientAFEntity.cpp b/src/game/client/ClientAFEntity.cpp new file mode 100644 index 0000000..23a3ab7 --- /dev/null +++ b/src/game/client/ClientAFEntity.cpp @@ -0,0 +1,720 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientAFEntity.cpp +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +/* +=============================================================================== + +rvClientAFEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAnimatedClientEntity, rvClientAFEntity ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +rvClientAFEntity::rvClientAFEntity +================ +*/ +rvClientAFEntity::rvClientAFEntity( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); +} + + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +rvClientAFEntity::~rvClientAFEntity( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +rvClientAFEntity::Spawn +================ +*/ +void rvClientAFEntity::Spawn( void ) { + spawnOrigin = worldOrigin; + spawnAxis = worldAxis; + nextSoundTime = 0; + + //if ( !LoadAF() ) { + // gameLocal.Error( "Couldn't load af file on entity %d", entityNumber ); + //} + + SetCombatModel(); + + //af.GetPhysics()->PutToRest(); + //if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + // af.GetPhysics()->Activate(); + //} +} + +/* +================ +rvClientAFEntity::LoadAF +================ +*/ +bool rvClientAFEntity::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + + idDict args = gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs; + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = spawnArgs; + + if ( !af.Load( gameLocal.entities[ ENTITYNUM_CLIENT ], fileName ) ) { + gameLocal.Error( "rvClientAFEntity::LoadAF: Couldn't load af file '%s' on client entity %d", fileName.c_str(), entityNumber ); + } + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = args; + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +rvClientAFEntity::Think +================ +*/ +void rvClientAFEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + + Present(); + LinkCombat(); +} + +/* +================ +rvClientAFEntity::BodyForClipModelId +================ +*/ +int rvClientAFEntity::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +rvClientAFEntity::SaveState +================ +*/ +void rvClientAFEntity::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +rvClientAFEntity::LoadState +================ +*/ +void rvClientAFEntity::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +rvClientAFEntity::AddBindConstraints +================ +*/ +void rvClientAFEntity::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +rvClientAFEntity::RemoveBindConstraints +================ +*/ +void rvClientAFEntity::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +rvClientAFEntity::GetImpactInfo +================ +*/ +void rvClientAFEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + rvClientEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFEntity::ApplyImpulse +================ +*/ +void rvClientAFEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + rvClientEntity::ApplyImpulse( ent, id, point, impulse, splash ); + } +} + +/* +================ +rvClientAFEntity::AddForce +================ +*/ +void rvClientAFEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + rvClientEntity::AddForce( ent, id, point, force ); + } +} + +bool rvClientAFEntity::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + // stubbed out + return true; +} + +/* +================ +rvClientAFEntity::Collide +================ +*/ +bool rvClientAFEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { + // RAVEN BEGIN + // jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } + // RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +rvClientAFEntity::GetPhysicsToVisualTransform +================ +*/ +bool rvClientAFEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return rvClientModel::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +rvClientAFEntity::UpdateAnimationControllers +================ +*/ +bool rvClientAFEntity::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +rvClientAFEntity::SetCombatModel +================ +*/ +void rvClientAFEntity::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } +} + +/* +================ +rvClientAFEntity::GetCombatModel +================ +*/ +idClipModel *rvClientAFEntity::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFEntity::SetCombatContents +================ +*/ +void rvClientAFEntity::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +rvClientAFEntity::LinkCombat +================ +*/ +void rvClientAFEntity::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFEntity::UnlinkCombat +================ +*/ +void rvClientAFEntity::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +rvClientAFEntity::FreeEntityDef +================ +*/ +void rvClientAFEntity::FreeEntityDef( void ) { + UnlinkCombat(); + rvClientEntity::FreeEntityDef(); +} + +/* +================ +rvClientAFEntity::GetNoPlayerImpactFX +================ +*/ +bool rvClientAFEntity::GetNoPlayerImpactFX( void ) { + // stubbed out + return false; +} + + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientAFEntity, rvClientAFAttachment ) +END_CLASS + +/* +===================== +rvClientAFAttachment::rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::rvClientAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; +} + +/* +===================== +rvClientAFAttachment::~rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::~rvClientAFAttachment( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +===================== +rvClientAFAttachment::Spawn +===================== +*/ +void rvClientAFAttachment::Spawn( void ) { + idleAnim = animator.GetAnim( "idle" ); +} + +/* +===================== +rvClientAFAttachment::InitCopyJoints +===================== +*/ +void rvClientAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on client entity %d", jointName, entityNumber ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %d", kv->GetValue().c_str(), entityNumber ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +rvClientAFAttachment::CopyJointsFromBody +===================== +*/ +void rvClientAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +rvClientAFAttachment::SetBody +===================== +*/ +void rvClientAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +rvClientAFAttachment::ClearBody +===================== +*/ +void rvClientAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +rvClientAFAttachment::GetBody +===================== +*/ +idEntity *rvClientAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void rvClientAFAttachment::Hide( void ) { + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void rvClientAFAttachment::Show( void ) { + LinkCombat(); +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void rvClientAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +rvClientAFAttachment::GetImpactInfo +================ +*/ +void rvClientAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + rvClientAFAttachment::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFAttachment::CanPlayImpactEffect +================ +*/ +bool rvClientAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + return rvClientAFEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +rvClientAFAttachment::ApplyImpulse +================ +*/ +void rvClientAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + rvClientAFEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +rvClientAFAttachment::AddForce +================ +*/ +void rvClientAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + rvClientAFEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +rvClientAFAttachment::PlayIdleAnim +================ +*/ +void rvClientAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +} + +/* +================ +rvClientAFAttachment::Think +================ +*/ +void rvClientAFAttachment::Think( void ) { + rvClientAFEntity::Think(); +} + +/* +================ +rvClientAFAttachment::UpdateAnimationControllers +================ +*/ +bool rvClientAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return rvClientAFEntity::UpdateAnimationControllers( ); +} + +/* +================ +rvClientAFAttachment::SetCombatModel +================ +*/ +void rvClientAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } + combatModel->SetOwner( body ); +} + +/* +================ +rvClientAFAttachment::GetCombatModel +================ +*/ +idClipModel *rvClientAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFAttachment::LinkCombat +================ +*/ +void rvClientAFAttachment::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFAttachment::UnlinkCombat +================ +*/ +void rvClientAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} diff --git a/src/game/client/ClientAFEntity.h b/src/game/client/ClientAFEntity.h new file mode 100644 index 0000000..4c1fcc5 --- /dev/null +++ b/src/game/client/ClientAFEntity.h @@ -0,0 +1,150 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientAFEntity.h +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_AFENTITY_H__ +#define __GAME_CLIENT_AFENTITY_H__ + +/* +=============================================================================== + +rvClientAFEntity - a regular idAFEntity_Base spawned client-side + +=============================================================================== +*/ + +class rvClientAFEntity : public rvAnimatedClientEntity { +public: + CLASS_PROTOTYPE( rvClientAFEntity ); + + rvClientAFEntity( void ); + virtual ~rvClientAFEntity( void ); + + void Spawn( void ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeEntityDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound +}; + + +/* +=============================================================================== + +rvClientAFAttachment - a regular idAFAttachment spawned client-side - links to an + idAnimatedEntity rather than rvClientAnimatedEntity + +=============================================================================== +*/ +class rvClientAFAttachment : public rvClientAFEntity { +public: + CLASS_PROTOTYPE( rvClientAFAttachment ); + + rvClientAFAttachment( void ); + virtual ~rvClientAFAttachment( void ); + + void Spawn( void ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + +protected: + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + idList copyJoints; // copied from the body animation to the head model +}; + +#endif diff --git a/src/game/client/ClientEffect.cpp b/src/game/client/ClientEffect.cpp new file mode 100644 index 0000000..8055d9e --- /dev/null +++ b/src/game/client/ClientEffect.cpp @@ -0,0 +1,521 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEffect.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientEffect.h" + +/* +=============================================================================== + +rvClientEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEntity, rvClientEffect ) +END_CLASS + +/* +================ +rvClientEffect::rvClientEffect +================ +*/ +rvClientEffect::rvClientEffect ( void ) { + Init( NULL ); + Spawn(); +} + +rvClientEffect::rvClientEffect ( const idDecl *effect ) { + Init( effect ); + Spawn(); +} + +void rvClientEffect::Init ( const idDecl *effect ) { + memset( &renderEffect, 0, sizeof( renderEffect ) ); + + renderEffect.declEffect = effect; + renderEffect.startTime = -1.0f; + renderEffect.referenceSoundHandle = -1; + effectDefHandle = -1; + endOriginJoint = INVALID_JOINT; +} + +/* +================ +rvClientEffect::~rvClientEffect +================ +*/ +rvClientEffect::~rvClientEffect( void ) { + FreeEffectDef( ); + // Prevent a double free of a SoundEmitter resulting in broken in-game sounds, when + // the second free releases a emitter that was reallocated to another sound. rvBSE caches + // this referenceSoundHandle and rvBSE::Destroy also frees the sound. rvBSE::Destroy + // is triggered by FreeEffectDef. Disable this free and let rvBSE do the releasing + + // Actually, the freeing should be done here and not in BSE. The client effect allocates and + // maintains the handle. Handling this here also allows emitters to be recycled for sparse + // looping effects. + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, renderEffect.referenceSoundHandle, true ); + renderEffect.referenceSoundHandle = -1; +} + +/* +================ +rvClientEffect::GetEffectIndex +================ +*/ +int rvClientEffect::GetEffectIndex( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->Index() ); + } + return( -1 ); +} + +/* +================ +rvClientEffect::GetEffectName +================ +*/ +const char *rvClientEffect::GetEffectName( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->GetName() ); + } + return( "unknown" ); +} + +/* +================ +rvClientEffect::FreeEffectDef +================ +*/ +void rvClientEffect::FreeEffectDef ( void ) { + if ( effectDefHandle != -1 && gameRenderWorld ) { + gameRenderWorld->FreeEffectDef( effectDefHandle ); + } + effectDefHandle = -1; +} + +/* +================ +rvClientEffect::UpdateBind +================ +*/ +void rvClientEffect::UpdateBind ( void ) { + rvClientEntity::UpdateBind ( ); + + renderEffect.origin = worldOrigin; + + if ( endOriginJoint != INVALID_JOINT && bindMaster ) { + idMat3 axis; + idVec3 endOrigin; + idVec3 dir; + + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( endOriginJoint, gameLocal.time, endOrigin, axis ); + SetEndOrigin ( endOrigin ); + + dir = (endOrigin - worldOrigin); + dir.Normalize (); + renderEffect.axis = dir.ToMat3 ( ); + } else { + renderEffect.axis = worldAxis; + } + + if ( bindMaster ) { + renderEffect.groupID = bindMaster->entityNumber + 1; + } else { + renderEffect.groupID = 0; + } +} + +/* +================ +rvClientEffect::Think +================ +*/ +void rvClientEffect::Think ( void ) { + // If we are bound to an entity that is now hidden we can just not render if looping, otherwise stop the effect + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + if ( renderEffect.loop ) { + return; + } + Stop ( ); + } + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_EFFECT); +// RAVEN END + // If there is a valid effect handle and we havent started playing + // and effect yet then see if its time + if( effectDefHandle < 0 && renderEffect.declEffect ) { + if( renderEffect.startTime >= 0.0f ) { + // Make sure our origins are all straight before starting the effect + UpdateBind(); + renderEffect.attenuation = 1.0f; + + // if the rendereffect needs sound give it an emitter. + if( renderEffect.referenceSoundHandle <= 0 ) { + if( gameRenderWorld->EffectDefHasSound( &renderEffect) ) + { + renderEffect.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } else { + renderEffect.referenceSoundHandle = -1; + } + } + // Add the render effect + effectDefHandle = gameRenderWorld->AddEffectDef( &renderEffect, gameLocal.time ); + if ( effectDefHandle < 0 ) { + PostEventMS( &EV_Remove, 0 ); + } + } + + return; + } + + // If we lost our effect def handle then just remove ourself + if( effectDefHandle < 0 ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Dont do anything else if its not a new client frame + if( !gameLocal.isNewFrame ) { + return; + } + + // Check to see if the player can possibly see the effect or not + renderEffect.inConnectedArea = true; + if( bindMaster ) { + renderEffect.inConnectedArea = gameLocal.InPlayerConnectedArea( bindMaster ); + } + + // Update the bind + UpdateBind(); + + // Update the actual render effect now + if( gameRenderWorld->UpdateEffectDef( effectDefHandle, &renderEffect, gameLocal.time ) ) { + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + return; + } +} + +/* +================ +rvClientEffect::Play +================ +*/ +bool rvClientEffect::Play ( int _startTime, bool _loop, const idVec3& endOrigin ) { + if ( !renderEffect.declEffect ) { + return false; + } + + // Initialize the render entity + if ( bindMaster ) { + renderEntity_t* renderEnt = bindMaster->GetRenderEntity ( ); + assert( renderEnt ); + + // Copy suppress values from parent entity + renderEffect.allowSurfaceInViewID = renderEnt->allowSurfaceInViewID; + renderEffect.suppressSurfaceInViewID = renderEnt->suppressSurfaceInViewID; + renderEffect.weaponDepthHackInViewID = renderEnt->weaponDepthHackInViewID; + } + + renderEffect.shaderParms[SHADERPARM_RED] = 1.0f; + renderEffect.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEffect.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f; + renderEffect.shaderParms[SHADERPARM_TIMEOFFSET] = MS2SEC( gameLocal.time ); + renderEffect.hasEndOrigin = ( endOrigin != vec3_origin ); + renderEffect.endOrigin = endOrigin; + renderEffect.loop = _loop; + + assert( effectDefHandle < 0 ); + + renderEffect.startTime = MS2SEC( _startTime ); + + return true; +} + +/* +================ +rvClientEffect::Stop +================ +*/ +void rvClientEffect::Stop ( bool destroyParticles ) { + if( effectDefHandle < 0 ) { + renderEffect.startTime = -1.0f; + renderEffect.declEffect = NULL; + return; + } + + if ( destroyParticles ) { + // Clear the effect index to make sure the effect isnt started again. This is + // an indirect way of making the effect not think + renderEffect.declEffect = NULL; + + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + gameRenderWorld->StopEffectDef( effectDefHandle ); + // this will ensure the effect doesn't re-up when loaded from a save. + renderEffect.startTime = -1.0f; + Unbind ( ); + } +} + +/* +================ +rvClientEffect::Restart +================ +*/ +void rvClientEffect::Restart ( void ) { + FreeEffectDef ( ); + + if ( renderEffect.loop ) { + Play ( gameLocal.time, true, renderEffect.endOrigin ); + } +} + +/* +================ +rvClientEffect::Attenuate +================ +*/ +void rvClientEffect::Attenuate ( float attenuation ) { + renderEffect.attenuation = attenuation; +} + +/* +================ +rvClientEffect::GetDuration +================ +*/ +float rvClientEffect::GetDuration( void ) const { + if( effectDefHandle < 0 ) { + return 0.0f; + } + + return bse->EffectDuration( gameRenderWorld->GetEffectDef( effectDefHandle ) ); +} + +/* +================ +rvClientEffect::DrawDebugInfo +================ +*/ +void rvClientEffect::DrawDebugInfo ( void ) const { + rvClientEntity::DrawDebugInfo ( ); + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + if( !renderEffect.declEffect ) { + return; + } + + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + gameRenderWorld->DrawText ( renderEffect.declEffect->GetName(), worldOrigin + up, 0.1f, colorWhite, axis, 1 ); +} + +/* +================ +rvClientEffect::Save +================ +*/ +void rvClientEffect::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEffect( renderEffect ); + savefile->WriteJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::Restore +================ +*/ +void rvClientEffect::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEffect( renderEffect ); + effectDefHandle = -1; + savefile->ReadJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::FreeEntityDef +================ +*/ +void rvClientEffect::FreeEntityDef( void ) { + FreeEffectDef(); +} + +/* +=============================================================================== + +rvClientCrawlEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEffect, rvClientCrawlEffect ) +END_CLASS + +/* +================ +rvClientCrawlEffect::rvClientCrawlEffect +================ +*/ +rvClientCrawlEffect::rvClientCrawlEffect ( void ) { +} + +rvClientCrawlEffect::rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int _crawlTime, idList* joints ) : rvClientEffect ( effect ) { + int i; + + // Crawl effects require an animated entity + crawlEnt = dynamic_cast(ent); + if ( !crawlEnt) { + return; + } + + // Specific joint list provided? + if ( joints && joints->Num () ) { + crawlJoints.Clear ( ); + for ( i = 0; i < joints->Num(); i ++ ) { + crawlJoints.Append ( (*joints)[i] ); + } + } else { + // Use only parent joints and skip joint zero which is presumed to be the origin + for ( i = ent->GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != ent->GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + crawlJoints.Append ( (jointHandle_t)i ); + } + } + } + + //no joints? abort! + if ( !crawlJoints.Num() ) { + return; + } + + jointStart = gameLocal.random.RandomInt ( crawlJoints.Num() ); + crawlDir = gameLocal.random.RandomInt ( 2 ) > 0 ? 1 : -1; + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + crawlTime = _crawlTime; + nextCrawl = gameLocal.time + crawlTime; +} + +/* +================ +rvClientCrawlEffect::Think +================ +*/ +void rvClientCrawlEffect::Think ( void ) { + + // If there is no crawl entity or no crawl joints then just free ourself + if ( !crawlEnt || !crawlJoints.Num() ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Move to the next joint if its time + if ( gameLocal.time > nextCrawl ) { + jointStart = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + nextCrawl = gameLocal.time + crawlTime; + } + + idVec3 offsetStart; + idVec3 offsetEnd; + idVec3 dir; + idMat3 axis; + + // Get the start origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointStart], gameLocal.time, offsetStart, axis ); + SetOrigin ( offsetStart ); + + // Get the end origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointEnd], gameLocal.time, offsetEnd, axis ); + SetEndOrigin ( offsetEnd ); + + // Update the axis to point at the bone + dir = offsetEnd - offsetStart; + dir.Normalize(); + SetAxis ( dir.ToMat3( ) ); + + rvClientEffect::Think ( ); +} + +/* +================ +rvClientCrawlEffect::Save +================ +*/ +void rvClientCrawlEffect::Save( idSaveGame *savefile ) const { + savefile->WriteInt( crawlJoints.Num() ); + for( int ix = 0; ix < crawlJoints.Num(); ++ix ) { + savefile->WriteJoint( crawlJoints[ix] ); + } + + savefile->WriteInt( crawlTime ); + savefile->WriteInt( nextCrawl ); + savefile->WriteInt( jointStart ); + savefile->WriteInt( jointEnd ); + savefile->WriteInt( crawlDir ); + crawlEnt.Save( savefile ); +} + +/* +================ +rvClientCrawlEffect::Restore +================ +*/ +void rvClientCrawlEffect::Restore( idRestoreGame *savefile ) { + int numJoints = 0; + savefile->ReadInt( numJoints ); + crawlJoints.SetNum( numJoints ); + for( int ix = 0; ix < numJoints; ++ix ) { + savefile->ReadJoint( crawlJoints[ix] ); + } + + savefile->ReadInt( crawlTime ); + savefile->ReadInt( nextCrawl ); + savefile->ReadInt( jointStart ); + savefile->ReadInt( jointEnd ); + savefile->ReadInt( crawlDir ); + crawlEnt.Restore( savefile ); +} diff --git a/src/game/client/ClientEffect.h b/src/game/client/ClientEffect.h new file mode 100644 index 0000000..60541b6 --- /dev/null +++ b/src/game/client/ClientEffect.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEffect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_EFFECT_H__ +#define __GAME_CLIENT_EFFECT_H__ + +class rvClientEffect : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientEffect ); + + rvClientEffect( void ); + rvClientEffect( const idDecl *effect ); + virtual ~rvClientEffect( void ); + + virtual void Think ( void ); + virtual void DrawDebugInfo ( void ) const; + virtual void FreeEntityDef ( void ); + virtual void UpdateBind ( void ); + + bool Play ( int startTime, bool loop = false, const idVec3& origin = vec3_origin ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + int GetEffectIndex ( void ); + const char * GetEffectName ( void ); + + void Attenuate ( float attenuation ); + + float GetDuration ( void ) const; + renderEffect_t* GetRenderEffect ( void ) { return &renderEffect; } + + void SetEndOrigin ( const idVec3& endOrigin ); + void SetEndOrigin ( jointHandle_t joint ) { endOriginJoint = joint; } + + void SetGravity ( const idVec3& gravity ) { renderEffect.gravity = gravity; } + + void SetColor ( const idVec4& color ); + void SetBrightness ( float brightness ) { renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = brightness; } + void SetAmbient ( bool in ) { renderEffect.ambient = in; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +protected: + + void Init ( const idDecl *effect ); + void FreeEffectDef ( void ); + + renderEffect_t renderEffect; + int effectDefHandle; + jointHandle_t endOriginJoint; +}; + +ID_INLINE void rvClientEffect::SetEndOrigin ( const idVec3& endOrigin ) { + renderEffect.endOrigin = endOrigin; + renderEffect.hasEndOrigin = !(endOrigin == vec3_origin); +} + +ID_INLINE void rvClientEffect::SetColor ( const idVec4& color ) { + renderEffect.shaderParms[SHADERPARM_RED] = color[0]; + renderEffect.shaderParms[SHADERPARM_GREEN] = color[1]; + renderEffect.shaderParms[SHADERPARM_BLUE] = color[2]; + renderEffect.shaderParms[SHADERPARM_ALPHA] = color[3]; +} + +//---------------------------------------------------------------- +// rvClientCrawlEffect +//---------------------------------------------------------------- + +class idAnimatedEntity; + +class rvClientCrawlEffect : public rvClientEffect { +public: + + CLASS_PROTOTYPE( rvClientCrawlEffect ); + + rvClientCrawlEffect ( void ); + rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int crawlTime, idList* joints = NULL ); + ~rvClientCrawlEffect ( void ) {} + + virtual void Think ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + idList crawlJoints; + int crawlTime; + int nextCrawl; + int jointStart; + int jointEnd; + int crawlDir; + idEntityPtr crawlEnt; +}; + +typedef rvClientEntityPtr rvClientEffectPtr; + +#endif // __GAME_CLIENT_EFFECT_H__ diff --git a/src/game/client/ClientEntity.cpp b/src/game/client/ClientEntity.cpp new file mode 100644 index 0000000..7e1e154 --- /dev/null +++ b/src/game/client/ClientEntity.cpp @@ -0,0 +1,675 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEntity.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, rvClientEntity ) +END_CLASS + +/* +================ +rvClientEntity::rvClientEntity +================ +*/ +rvClientEntity::rvClientEntity( void ) { + bindMaster = NULL; + entityNumber = -1; + + bindOrigin.Zero(); + bindAxis.Identity(); + bindJoint = INVALID_JOINT; + bindOrientated = false; + + memset( &refSound, 0, sizeof(refSound) ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::~rvClientEntity +================ +*/ +rvClientEntity::~rvClientEntity( void ) { + Unbind(); + gameLocal.UnregisterClientEntity( this ); + + // Free sound emitter + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::Spawn +================ +*/ +void rvClientEntity::Spawn( void ) { + idVec3 origin; + idMat3 axis; + + gameLocal.RegisterClientEntity( this ); + + spawnNode.SetOwner( this ); + bindNode.SetOwner( this ); + + origin = spawnArgs.GetVector( "origin", "0 0 0" ); + axis = spawnArgs.GetMatrix( "axis", "1 0 0 0 1 0 0 0 1" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); +} + +/* +================ +rvClientEntity::Present +================ +*/ +void rvClientEntity::Present ( void ) { +} + +/* +================ +rvClientEntity::FreeEntityDef +================ +*/ +void rvClientEntity::FreeEntityDef ( void ) { +} + +/* +================ +rvClientEntity::Think +================ +*/ +void rvClientEntity::Think ( void ) { + UpdateBind(); + UpdateSound(); + Present(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Bind ( idEntity* master, jointHandle_t joint, bool isOrientated ) { + Unbind(); + + if ( joint != INVALID_JOINT && !dynamic_cast(master) ) { + gameLocal.Warning( "rvClientEntity::Bind: entity '%s' cannot support skeletal models.", master->GetName() ); + joint = INVALID_JOINT; + } + + bindMaster = master; + bindJoint = joint; + bindOrigin = worldOrigin; + bindAxis = worldAxis; + + bindNode.AddToEnd ( bindMaster->clientEntities ); + + bindOrientated = isOrientated; + if( physics ) { + physics->SetMaster( bindMaster, bindOrientated ); + } + + UpdateBind(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Unbind ( void ) { + if ( !bindMaster ) { + return; + } + + bindMaster = NULL; + bindNode.Remove ( ); +} + +/* +================ +rvClientEntity::SetOrigin +================ +*/ +void rvClientEntity::SetOrigin( const idVec3& origin ) { + if ( bindMaster ) { + bindOrigin = origin; + } else { + worldOrigin = origin; + } +} + +/* +================ +rvClientEntity::SetAxis +================ +*/ +void rvClientEntity::SetAxis( const idMat3& axis ) { + if ( bindMaster ) { + bindAxis = axis * bindMaster->GetRenderEntity()->axis.Transpose(); + } else { + worldAxis = axis; + } +} + +/* +================ +rvClientEntity::UpdateBind +================ +*/ +void rvClientEntity::UpdateBind ( void ) { + if ( !bindMaster ) { + return; + } + + if ( bindJoint != INVALID_JOINT ) { + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( bindJoint, gameLocal.time, worldOrigin, worldAxis ); + } else { + bindMaster->GetPosition( worldOrigin, worldAxis ); + //if ( !bindMaster->GetPhysicsToVisualTransform( worldOrigin, worldAxis ) ) { + // bindMaster->GetPosition( worldOrigin, worldAxis ); + //} + } + + worldOrigin += (bindOrigin * worldAxis); + worldAxis = bindAxis * worldAxis; +} + +/* +================ +rvClientEntity::IsClient +================ +*/ +bool rvClientEntity::IsClient ( void ) const { + return true; +} + +/* +================ +rvClientEntity::DrawDebugInfo +================ +*/ +void rvClientEntity::DrawDebugInfo ( void ) const { + idBounds bounds ( idVec3(-8,-8,-8), idVec3(8,8,8) ); + + gameRenderWorld->DebugBounds ( colorGreen, bounds, worldOrigin ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText ( GetClassname ( ), worldOrigin, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } +} + +/* +================ +rvClientEntity::UpdateSound +================ +*/ +void rvClientEntity::UpdateSound( void ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } +} + +/* +================ +rvClientEntity::SetSoundVolume +================ +*/ +void rvClientEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + + +/* +================ +rvClientEntity::StartSound +================ +*/ +bool rvClientEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + + idStr soundNameStr = soundName; + if( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags ); +} + + +/* +================ +rvClientEntity::StartSoundShader +================ +*/ +int rvClientEntity::StartSoundShader ( const idSoundShader* shader, const s_channelType channel, int soundShaderFlags ) { + if ( !shader ) { + return 0; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return( 0 ); + } + + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + return emitter->StartSound( shader, channel, gameLocal.random.RandomFloat(), soundShaderFlags ); +} + +/* +================ +rvClientEntity::Size + +Returns Returns memory size of an rvClientEntity. +================ +*/ + +size_t rvClientEntity::Size ( void ) const { + return sizeof( rvClientEntity ); +} + +/* +================ +rvClientEntity::Save +================ +*/ +void rvClientEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->WriteVec3( worldOrigin ); + savefile->WriteVec3( worldVelocity ); + savefile->WriteMat3( worldAxis ); + + bindMaster.Save( savefile ); + savefile->WriteVec3( bindOrigin ); + savefile->WriteMat3( bindAxis ); + savefile->WriteJoint( bindJoint ); + + savefile->WriteRefSound( refSound ); +} + +/* +================ +rvClientEntity::Restore +================ +*/ +void rvClientEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->ReadVec3( worldOrigin ); + savefile->ReadVec3( worldVelocity ); + savefile->ReadMat3( worldAxis ); + + bindMaster.Restore( savefile ); + savefile->ReadVec3( bindOrigin ); + savefile->ReadMat3( bindAxis ); + savefile->ReadJoint( bindJoint ); + + savefile->ReadRefSound( refSound ); +} + +/* +================ +rvClientEntity::RunPhysics +================ +*/ +void rvClientEntity::RunPhysics ( void ) { + idPhysics* physics = GetPhysics( ); + if( !physics ) { + return; + } + + rvClientPhysics* clientPhysics = (rvClientPhysics*)gameLocal.entities[ENTITYNUM_CLIENT]; + static_cast( clientPhysics )->currentEntityNumber = entityNumber; + + // order important: 1) set client physics bind master to client ent's bind master + // 2) set physics to client ent's physics, which sets physics + // master to client ent's master + // 3) set client physics origin to client ent origin, depends on + // proper bind master from 1 + clientPhysics->PushBindInfo( bindMaster, bindJoint, bindOrientated ); + clientPhysics->SetPhysics( physics ); + clientPhysics->PushOriginInfo( bindMaster ? bindOrigin : worldOrigin, bindMaster ? bindAxis : worldAxis ); + + physics->Evaluate ( gameLocal.time - gameLocal.previousTime, gameLocal.time ); + + worldOrigin = physics->GetOrigin(); + worldVelocity = physics->GetLinearVelocity(); + worldAxis = physics->GetAxis(); + + // order important: 1) restore previous bind master + // 2) reset physics with previous bind master + // 3) reset origin with previous bind master + clientPhysics->PopBindInfo(); + clientPhysics->SetPhysics( NULL ); + clientPhysics->PopOriginInfo(); + + UpdateAnimationControllers(); +} + +/* +================ +rvClientEntity::GetPhysics +================ +*/ +idPhysics* rvClientEntity::GetPhysics ( void ) const { + return physics; +} + +/* +================ +rvClientEntity::Collide +================ +*/ +bool rvClientEntity::Collide ( const trace_t &collision, const idVec3 &velocity ) { + return false; +} + +/* +================ +rvClientEntity::GetImpactInfo +================ +*/ +void rvClientEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +rvClientEntity::ApplyImpulse +================ +*/ +void rvClientEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +rvClientEntity::AddForce +================ +*/ +void rvClientEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +rvClientEntity::UpdateAnimationControllers +================ +*/ +bool rvClientEntity::UpdateAnimationControllers( void ) { + return false; +} + +/* +================ +rvClientEntity::InitDefaultPhysics +================ +*/ +void rvClientEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( temp ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on client entity '%d'", bounds[0].ToString(), bounds[1].ToString(), entityNumber ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on client entity '%d'", size.ToString(), entityNumber ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); + // RAVEN BEGIN + // bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); + // RAVEN END + } else { + trm.SetupBox( bounds ); + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( trm ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { + // RAVEN BEGIN + // jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( gameLocal.entities[ENTITYNUM_CLIENT] ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; + + // by default no collision + physics->SetContents( 0 ); +} + + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvClientPhysics ) +END_CLASS + +/* +===================== +rvClientPhysics::Spawn +===================== +*/ +void rvClientPhysics::Spawn( void ) { + pushedOrientated = false; +} + +/* +===================== +rvClientPhysics::Collide +===================== +*/ +bool rvClientPhysics::Collide( const trace_t &collision, const idVec3 &velocity ) { + assert ( currentEntityNumber >= 0 && currentEntityNumber < MAX_CENTITIES ); + + rvClientEntity* cent; + cent = gameLocal.clientEntities [ currentEntityNumber ]; + if ( cent ) { + return cent->Collide ( collision, velocity ); + } + + return false; +} + +/* +===================== +rvClientPhysics::PushBindInfo +===================== +*/ +void rvClientPhysics::PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ) { + pushedBindJoint = joint; + pushedBindMaster = master; + pushedOrientated = fl.bindOrientated; + + bindMaster = master; + bindJoint = joint; + fl.bindOrientated = orientated; +} + +/* +===================== +rvClientPhysics::PopBindInfo +===================== +*/ +void rvClientPhysics::PopBindInfo( void ) { + bindMaster = pushedBindMaster; + bindJoint = pushedBindJoint; + fl.bindOrientated = pushedOrientated; +} + +/* +===================== +rvClientPhysics::PushOriginInfo +===================== +*/ +void rvClientPhysics::PushOriginInfo( const idVec3& origin, const idMat3& axis ) { + if( !GetPhysics() ) { + return; + } + + pushedOrigin = GetPhysics()->GetOrigin(); + pushedAxis = GetPhysics()->GetAxis(); + + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( axis ); +} + +/* +===================== +rvClientPhysics::PopOriginInfo +===================== +*/ +void rvClientPhysics::PopOriginInfo( void ) { + if( !GetPhysics() ) { + return; + } + + GetPhysics()->SetOrigin( pushedOrigin ); + GetPhysics()->SetAxis( pushedAxis ); +} diff --git a/src/game/client/ClientEntity.h b/src/game/client/ClientEntity.h new file mode 100644 index 0000000..5cbc536 --- /dev/null +++ b/src/game/client/ClientEntity.h @@ -0,0 +1,246 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEntity.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_ENTITY_H__ +#define __GAME_CLIENT_ENTITY_H__ + +class rvClientEntity : public idClass { +public: + + ABSTRACT_PROTOTYPE( rvClientEntity ); + + rvClientEntity ( void ); + + virtual ~rvClientEntity ( void ); + + void Spawn ( void ); + + virtual void Present ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); + const idVec3& GetOrigin ( void ); + const idMat3& GetAxis ( void ); + + void Bind ( idEntity* master, jointHandle_t joint = INVALID_JOINT, bool isOrientated = false ); + void Unbind ( void ); + + virtual bool IsClient ( void ) const; + virtual void DrawDebugInfo ( void ) const; + + void SetSoundVolume( float volume ); + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + int StartSoundShader ( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags ); + + // I'm guessing someone may decide to derive from rvClientEntity, so this virtual. + virtual size_t Size ( void ) const; + + virtual void FreeEntityDef ( void ); + + const idEntityPtr& GetBindMaster( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetImpactInfo ( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ); + virtual void AddForce ( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool UpdateAnimationControllers( void ); + + void InitDefaultPhysics ( const idVec3 &origin, const idMat3 &axis ); +protected: + + void RunPhysics ( void ); + + virtual void UpdateBind ( void ); + void UpdateSound ( void ); + +public: + + int entityNumber; + + idLinkList spawnNode; + idLinkList bindNode; + + idDict spawnArgs; + +protected: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics* physics; + + idVec3 worldOrigin; + idVec3 worldVelocity; + idMat3 worldAxis; + + idEntityPtr bindMaster; + idVec3 bindOrigin; + idMat3 bindAxis; + jointHandle_t bindJoint; + bool bindOrientated; + refSound_t refSound; +}; + +ID_INLINE const idVec3& rvClientEntity::GetOrigin ( void ) { + return worldOrigin; +} + +ID_INLINE const idMat3& rvClientEntity::GetAxis ( void ) { + return worldAxis; +} + +ID_INLINE const idEntityPtr& rvClientEntity::GetBindMaster( void ) const { + return bindMaster; +} + +//============================================================================ + +template< class type > +class rvClientEntityPtr { +public: + rvClientEntityPtr(); + + rvClientEntityPtr & operator=( type* cent ); + + int GetSpawnId ( void ) const { return spawnId; } + bool SetSpawnId ( int id ); + bool UpdateSpawnId ( void ); + + bool IsValid ( void ) const; + type * GetEntity ( void ) const; + int GetEntityNum ( void ) const; + + type * operator-> ( void ) const { return GetEntity ( ); } + operator type* ( void ) const { return GetEntity ( ); } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +private: + int spawnId; +}; + +template< class type > +ID_INLINE rvClientEntityPtr::rvClientEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE rvClientEntityPtr &rvClientEntityPtr::operator=( type *cent ) { + if ( cent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.clientSpawnIds[cent->entityNumber] << CENTITYNUM_BITS ) | cent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::SetSpawnId( int id ) { + if ( id == spawnId ) { + return false; + } + if ( ( id >> CENTITYNUM_BITS ) == gameLocal.clientSpawnIds[ id & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::IsValid( void ) const { + return ( gameLocal.clientSpawnIds[ spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ); +} + +template< class type > +ID_INLINE type *rvClientEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.clientSpawnIds[ entityNum ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ) ) { + return static_cast( gameLocal.clientEntities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int rvClientEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +class rvClientPhysics : public idEntity { +public: + + CLASS_PROTOTYPE( rvClientPhysics ); + + rvClientPhysics( void ) { currentEntityNumber = -1; } + + void Spawn( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ); + virtual void PopBindInfo( void ); + + virtual void PushOriginInfo( const idVec3& origin, const idMat3& axis ); + virtual void PopOriginInfo( void ); + + // client side entities. make sure the networking doesn't fuck around with this + virtual bool ClientStale( void ) { assert( false ); return false; } + virtual void ClientUnstale( void ) { assert( false ); } + + int currentEntityNumber; +private: + idEntity* pushedBindMaster; + jointHandle_t pushedBindJoint; + idVec3 pushedOrigin; + idMat3 pushedAxis; + bool pushedOrientated; +}; + +#endif // __GAME_CLIENT_ENTITY_H__ diff --git a/src/game/client/ClientModel.cpp b/src/game/client/ClientModel.cpp new file mode 100644 index 0000000..125ff39 --- /dev/null +++ b/src/game/client/ClientModel.cpp @@ -0,0 +1,474 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientModel.cpp +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientModel.h" + +/* +=============================================================================== + +rvClientModel + +=============================================================================== +*/ +CLASS_DECLARATION( rvClientEntity, rvClientModel ) +END_CLASS + + +/* +================ +rvClientModel::rvClientModel +================ +*/ +rvClientModel::rvClientModel ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + worldAxis = mat3_identity; + entityDefHandle = -1; +} + +/* +================ +rvClientModel::~rvClientModel +================ +*/ +rvClientModel::~rvClientModel ( void ) { + FreeEntityDef ( ); +} + +/* +================ +rvClientModel::FreeEntityDef +================ +*/ +void rvClientModel::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +/* +================ +rvClientModel::Spawn +================ +*/ +void rvClientModel::Spawn ( void ) { + const char* spawnarg; + + spawnArgs.GetString ( "classname", "", classname ); + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + renderEntity.entityNum = entityNumber; + + spawnarg = spawnArgs.GetString( "model" ); + if ( spawnarg && *spawnarg ) { + SetModel( spawnarg ); + } +} + +/* +================ +rvClientModel::Think +================ +*/ +void rvClientModel::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + UpdateBind(); + Present(); +} + +/* +================ +rvClientModel::Present +================ +*/ +void rvClientModel::Present(void) { + // Hide client entities bound to a hidden entity + if ( bindMaster && (bindMaster->IsHidden ( ) || (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) ) { + return; + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis; + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientModel::SetCustomShader +================ +*/ +bool rvClientModel::SetCustomShader ( const char* shaderName ) { + if ( shaderName == NULL ) { + return false; + } + + const idMaterial* material = declManager->FindMaterial( shaderName ); + + if ( material == NULL ) { + return false; + } + + renderEntity.customShader = material; + + return true; +} + +/* +================ +rvClientModel::Save +================ +*/ +void rvClientModel::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + savefile->WriteString ( classname ); // cnicholson: Added unsaved var + +} + +/* +================ +rvClientModel::Restore +================ +*/ +void rvClientModel::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + savefile->ReadString ( classname ); // cnicholson: Added unrestored var + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } +} + +/* +================ +rvClientModel::SetModel +================ +*/ +void rvClientModel::SetModel( const char* modelname ) { + FreeEntityDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } +} + +/* +============== +rvClientModel::ProjectOverlay +============== +*/ +void rvClientModel::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( entityDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( entityDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +rvClientModel::UpdateRenderEntity +================ +*/ +bool rvClientModel::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +rvClientModel::ModelCallback + +NOTE: may not change the game state whatsoever! +================ +*/ +bool rvClientModel::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + rvClientEntity *cent; + + cent = gameLocal.clientEntities[ renderEntity->entityNum ]; + if ( !cent ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with NULL client entity '%d'", renderEntity->entityNum ); + return false; + } + + if( !cent->IsType( rvClientModel::GetClassType() ) ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with non-client model on client entity '%d'", renderEntity->entityNum ); + return false; + } + + return ((rvClientModel*)cent)->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +rvClientModel::GetPhysicsToVisualTransform +================ +*/ +bool rvClientModel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +rvClientModel::UpdateModelTransform +================ +*/ +void rvClientModel::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * worldAxis; + renderEntity.origin = worldOrigin + origin * renderEntity.axis; + } else { + renderEntity.axis = worldAxis; + renderEntity.origin = worldOrigin; + } +} + +/* +================ +rvClientModel::UpdateModel +================ +*/ +void rvClientModel::UpdateModel( void ) { + UpdateModelTransform(); + + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + } +} + +/* +================ +rvClientModel::UpdateVisuals +================ +*/ +void rvClientModel::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +rvClientModel::SetSkin +================ +*/ +void rvClientModel::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} + +/* +=============================================================================== + +rvAnimatedClientEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientModel, rvAnimatedClientEntity ) +END_CLASS + +/* +================ +rvAnimatedClientEntity::rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::~rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::~rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::Spawn +================ +*/ +void rvAnimatedClientEntity::Spawn( void ) { + SetModel( spawnArgs.GetString( "model" ) ); +} +/* +================ +rvAnimatedClientEntity::Think +================ +*/ +void rvAnimatedClientEntity::Think ( void ) { + UpdateAnimation(); + + rvClientEntity::Think(); +} + +/* +================ +rvAnimatedClientEntity::UpdateAnimation +================ +*/ +void rvAnimatedClientEntity::UpdateAnimation( void ) { + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + + // call any frame commands that have happened in the past frame + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() ) { + gameLocal.DPrintf( "rvAnimatedClientEntity %s %d: inside out bounds - %d\n", GetClassname(), entityNumber, gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + Present(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +rvAnimatedClientEntity::SetModel +================ +*/ +void rvAnimatedClientEntity::SetModel( const char *modelname ) { + FreeEntityDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + rvClientModel::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + //UpdateVisuals(); + Present(); +} diff --git a/src/game/client/ClientModel.h b/src/game/client/ClientModel.h new file mode 100644 index 0000000..9f0121f --- /dev/null +++ b/src/game/client/ClientModel.h @@ -0,0 +1,116 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientModel.h +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MODEL_H__ +#define __GAME_CLIENT_MODEL_H__ + +class rvClientModel : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientModel ); + + rvClientModel ( void ); + virtual ~rvClientModel ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + virtual renderEntity_t* GetRenderEntity ( void ); + const char* GetClassname ( void ) const; + + virtual bool SetCustomShader ( const char* shaderName ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void FreeEntityDef ( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return NULL; } + virtual idAnimator* GetAnimator( void ) { return NULL; } + + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + void UpdateVisuals( void ); + void UpdateModel( void ); + void UpdateModelTransform( void ); + + void SetSkin( const idDeclSkin* skin ); + int GetModelDefHandle( void ); +protected: + void Present( void ); + + renderEntity_t renderEntity; + int entityDefHandle; + + idStr classname; +}; + +ID_INLINE renderEntity_t* rvClientModel::GetRenderEntity ( void ) { + return &renderEntity; +} + +ID_INLINE const char* rvClientModel::GetClassname ( void ) const { + return classname.c_str(); +} + +ID_INLINE int rvClientModel::GetModelDefHandle( void ) { + return entityDefHandle; +} + +class rvAnimatedClientEntity : public rvClientModel { +public: + CLASS_PROTOTYPE( rvAnimatedClientEntity ); + + rvAnimatedClientEntity ( void ); + virtual ~rvAnimatedClientEntity ( void ); + + void Spawn( void ); + virtual void Think( void ); + virtual void UpdateAnimation( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return &animator; } + virtual idAnimator* GetAnimator( void ) { return &animator; } + +protected: + idAnimator animator; +}; + +#endif // __GAME_CLIENT_MODEL_H__ diff --git a/src/game/client/ClientMoveable.cpp b/src/game/client/ClientMoveable.cpp new file mode 100644 index 0000000..f4c90b6 --- /dev/null +++ b/src/game/client/ClientMoveable.cpp @@ -0,0 +1,403 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientMoveable.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + +rvClientMoveable + +=============================================================================== +*/ +const idEventDef CL_FadeOut( "", "d" ); +const idEventDef CL_ClearDepthHack ( "" ); + +static const float BOUNCE_SOUND_MIN_VELOCITY = 100.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; +static const int BOUNCE_SOUND_DELAY = 200; + +CLASS_DECLARATION( rvClientEntity, rvClientMoveable ) + EVENT( CL_FadeOut, rvClientMoveable::Event_FadeOut ) + EVENT( CL_ClearDepthHack, rvClientMoveable::Event_ClearDepthHack ) +END_CLASS + + +/* +================ +rvClientMoveable::rvClientMoveable +================ +*/ +rvClientMoveable::rvClientMoveable ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + entityDefHandle = -1; + scale.Init( 0, 0, 1.0f, 1.0f ); +} + +/* +================ +rvClientMoveable::~rvClientMoveable +================ +*/ +rvClientMoveable::~rvClientMoveable ( void ) { + FreeEntityDef ( ); + + // Remove any trail effect if there is one + if ( trailEffect ) { + trailEffect->Stop ( ); + } +} + +/* +================ +rvClientMoveable::FreeEntityDef +================ +*/ +void rvClientMoveable::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +idVec3 simpleTri[3] = +{ + idVec3( -1.0, -1.0, 0.0 ), + idVec3( 0.0, 2.0, 0.0 ), + idVec3( 2.0, 0.0, 0.0 ) +}; + +/* +================ +rvClientMoveable::Spawn +================ +*/ +void rvClientMoveable::Spawn ( void ) { + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + idTraceModel trm; + int clipShrink; + idStr clipModelName; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName.Length () ) { + clipModelName = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( clipModelName == SIMPLE_TRI_NAME ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvClientMoveable '%d': cannot load collision model %s", entityNumber, clipModelName.c_str() ); + return; + } + } + + // if the model should be shrunk + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + physicsObj.SetSelf ( gameLocal.entities[ENTITYNUM_CLIENT] ); + physicsObj.SetClipModel ( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "0.5" ), entityNumber ); + + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + physicsObj.SetBouncyness( spawnArgs.GetFloat( "bouncyness", "0.6" ) ); + physicsObj.SetFriction( spawnArgs.GetFloat("linear_friction", "0.6"), spawnArgs.GetFloat( "angular_friction", "0.6"), spawnArgs.GetFloat("friction", "0.05") ); + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); + physicsObj.SetContents( 0 ); + // abahr: changed to MASK_SHOT_RENDERMODEL because brass was getting pinched between the player and the wall in some cases + // may want to try something cheaper. + physicsObj.SetClipMask( CONTENTS_OPAQUE ); // MASK_SHOT_RENDERMODEL | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + physicsObj.Activate ( ); + + trailEffect = gameLocal.PlayEffect ( spawnArgs, "fx_trail", physicsObj.GetCenterMass(), GetAxis(), true ); + trailAttenuateSpeed = spawnArgs.GetFloat ( "trailAttenuateSpeed", "200" ); + + bounceSoundShader = declManager->FindSound ( spawnArgs.GetString ( "snd_bounce" ), false ); + bounceSoundTime = 0; + mPlayBounceSoundOnce = spawnArgs.GetBool("bounce_sound_once"); + mHasBounced = false; + + scale.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("scale_reset_duration", "0.2")), Max(VECTOR_EPSILON, spawnArgs.GetFloat("scale", "1.0f")), 1.0f ); +} + +/* +================ +rvClientMoveable::SetOrigin +================ +*/ +void rvClientMoveable::SetOrigin( const idVec3& origin ) { + rvClientEntity::SetOrigin( origin ); + physicsObj.SetOrigin( origin ); +} + +/* +================ +rvClientMoveable::SetAxis +================ +*/ +void rvClientMoveable::SetAxis( const idMat3& axis ) { + rvClientEntity::SetAxis( axis ); + physicsObj.SetAxis( axis ); +} + +/* +================ +rvClientMoveable::SetOwner +================ +*/ +void rvClientMoveable::SetOwner( idEntity* owner ) { + physicsObj.GetClipModel()->SetOwner( owner ); + physicsObj.GetClipModel()->SetEntity( owner ); +} + +/* +================ +rvClientMoveable::Think +================ +*/ +void rvClientMoveable::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + + RunPhysics ( ); + + // Special case the sound update to use the center mass since the origin may be in an odd place + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = worldVelocity; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } + + // Keep the trail effect following + if ( trailEffect ) { + float speed; + speed = idMath::ClampFloat ( 0, trailAttenuateSpeed, worldVelocity.LengthFast ( ) ); + if ( physicsObj.IsAtRest ( ) ) { + trailEffect->Stop ( ); + trailEffect = NULL; + } else { + trailEffect->SetOrigin ( worldOrigin ); + trailEffect->SetAxis ( worldAxis ); + trailEffect->Attenuate ( speed / trailAttenuateSpeed ); + } + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis * scale.GetCurrentValue( gameLocal.GetTime() ); + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientMoveable::GetPhysics +================ +*/ +idPhysics* rvClientMoveable::GetPhysics ( void ) const { + return (idPhysics*)&physicsObj; +} + +/* +================ +rvClientMoveable::Collide +================ +*/ +bool rvClientMoveable::Collide ( const trace_t &collision, const idVec3 &velocity ) { + if (mPlayBounceSoundOnce && mHasBounced) + { + return false; + } + if ( bounceSoundShader && gameLocal.time > bounceSoundTime ) { + float speed; + speed = velocity.LengthFast ( ); + if ( speed > BOUNCE_SOUND_MIN_VELOCITY ) { + StartSoundShader ( bounceSoundShader, SND_CHANNEL_BODY, 0 ); + bounceSoundTime = BOUNCE_SOUND_DELAY; + mHasBounced = true; + } + } + + return false; +} + +/* +================ +rvClientMoveable::Save +================ +*/ +void rvClientMoveable::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + trailEffect.Save( savefile ); + savefile->WriteFloat( trailAttenuateSpeed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( bounceSoundTime ); + savefile->WriteSoundShader( bounceSoundShader ); + + savefile->WriteBool(mPlayBounceSoundOnce); + savefile->WriteBool(mHasBounced); + + // TOSAVE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Restore +================ +*/ +void rvClientMoveable::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + trailEffect.Restore( savefile ); + savefile->ReadFloat( trailAttenuateSpeed ); + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadInt( bounceSoundTime ); + savefile->ReadSoundShader( bounceSoundShader ); + + savefile->ReadBool(mPlayBounceSoundOnce); + savefile->ReadBool(mHasBounced); + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + // TORESTORE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Event_FadeOut +================ +*/ +void rvClientMoveable::Event_FadeOut ( int duration ) { + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + PostEventMS ( &EV_Remove, duration ); +} + +/* +================ +rvClientMoveable::Event_ClearDepthHack +================ +*/ +void rvClientMoveable::Event_ClearDepthHack ( void ) { + renderEntity.weaponDepthHackInViewID = 0; +} + +/* +================ +rvClientMoveable::SpawnClientMoveables +================ +*/ +void rvClientMoveable::SpawnClientMoveables( idEntity* ent, const char *type, idList* list ) { + const idKeyValue *kv; + idVec3 origin; + idMat3 axis; + + if( list == NULL || type == NULL ) { + return; + } + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), NULL ); + while ( kv ) { + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( ent->IsType( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + idAnimatedEntity* animEnt = static_cast(ent); + jointHandle_t clientMoveableJoint; + + const char* clientMoveableJointName = ent->spawnArgs.GetString( va( "%s_joint", kv->GetKey().c_str() + 4 ) ); + + // use a joint if specified + if ( idStr::Icmp( clientMoveableJointName, "") ) { + clientMoveableJoint = animEnt->GetAnimator()->GetJointHandle( clientMoveableJointName ); + + if ( !animEnt->GetJointWorldTransform( clientMoveableJoint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", va( "%s_joint", kv->GetKey().c_str() + 4 ), clientMoveableJointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + } + } + + // spawn the entity + const idDict* entityDef = gameLocal.FindEntityDefDict ( kv->GetValue().c_str(), false ); + + if ( entityDef == NULL ) { + gameLocal.Warning( "%s refers to invalid entity def '%s' on entity '%s'\n", kv->GetKey().c_str(), kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + + rvClientMoveable* newModel = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientMoveable" ); + + if( !newModel ) { + gameLocal.Warning( "error spawning client moveable (invalid entity def '%s' on entity '%s')\n", kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + newModel->SetOrigin ( origin ); + newModel->SetAxis( axis ); + + list->Append( newModel ); + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), kv ); + } +} + diff --git a/src/game/client/ClientMoveable.h b/src/game/client/ClientMoveable.h new file mode 100644 index 0000000..99b2841 --- /dev/null +++ b/src/game/client/ClientMoveable.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientMoveable.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MOVEABLE_H__ +#define __GAME_CLIENT_MOVEABLE_H__ + +class rvClientMoveable : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientMoveable ); + + rvClientMoveable ( void ); + virtual ~rvClientMoveable ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + renderEntity_t* GetRenderEntity ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + static void SpawnClientMoveables ( idEntity* ent, const char *type, idList* list ); + + virtual void FreeEntityDef ( void ); + + void SetOwner ( idEntity* ent ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); +protected: + renderEntity_t renderEntity; + int entityDefHandle; + + rvClientEffectPtr trailEffect; + float trailAttenuateSpeed; + + idPhysics_RigidBody physicsObj; + + int bounceSoundTime; + const idSoundShader* bounceSoundShader; + bool mPlayBounceSoundOnce; + bool mHasBounced; + + idInterpolate scale; + +private: + + void Event_FadeOut ( int duration ); + void Event_ClearDepthHack ( void ); +}; + +ID_INLINE renderEntity_t* rvClientMoveable::GetRenderEntity ( void ) { + return &renderEntity; +} + +extern const idEventDef CL_FadeOut; +extern const idEventDef CL_ClearDepthHack; + +#define SIMPLE_TRI_NAME "simpletri" + +extern idVec3 simpleTri[3]; + + +#endif // __GAME_CLIENT_MOVEABLE_H__ diff --git a/src/game/gamesys/Callbacks.cpp b/src/game/gamesys/Callbacks.cpp new file mode 100644 index 0000000..4c3c929 --- /dev/null +++ b/src/game/gamesys/Callbacks.cpp @@ -0,0 +1,2624 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// generated file - see CREATE_EVENT_CODE + + /******************************************************* + + 1 args + + *******************************************************/ + + case 512 : + typedef void ( idClass::*eventCallback_i_t )( const int ); + ( this->*( eventCallback_i_t )callback )( data[ 0 ] ); + break; + + case 513 : + typedef void ( idClass::*eventCallback_f_t )( const float ); + ( this->*( eventCallback_f_t )callback )( *( float * )&data[ 0 ] ); + break; + + /******************************************************* + + 2 args + + *******************************************************/ + + case 1024 : + typedef void ( idClass::*eventCallback_ii_t )( const int, const int ); + ( this->*( eventCallback_ii_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 1025 : + typedef void ( idClass::*eventCallback_fi_t )( const float, const int ); + ( this->*( eventCallback_fi_t )callback )( *( float * )&data[ 0 ], data[ 1 ] ); + break; + + case 1026 : + typedef void ( idClass::*eventCallback_if_t )( const int, const float ); + ( this->*( eventCallback_if_t )callback )( data[ 0 ], *( float * )&data[ 1 ] ); + break; + + case 1027 : + typedef void ( idClass::*eventCallback_ff_t )( const float, const float ); + ( this->*( eventCallback_ff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ] ); + break; + + /******************************************************* + + 3 args + + *******************************************************/ + + case 2048 : + typedef void ( idClass::*eventCallback_iii_t )( const int, const int, const int ); + ( this->*( eventCallback_iii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2049 : + typedef void ( idClass::*eventCallback_fii_t )( const float, const int, const int ); + ( this->*( eventCallback_fii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2050 : + typedef void ( idClass::*eventCallback_ifi_t )( const int, const float, const int ); + ( this->*( eventCallback_ifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2051 : + typedef void ( idClass::*eventCallback_ffi_t )( const float, const float, const int ); + ( this->*( eventCallback_ffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2052 : + typedef void ( idClass::*eventCallback_iif_t )( const int, const int, const float ); + ( this->*( eventCallback_iif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2053 : + typedef void ( idClass::*eventCallback_fif_t )( const float, const int, const float ); + ( this->*( eventCallback_fif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2054 : + typedef void ( idClass::*eventCallback_iff_t )( const int, const float, const float ); + ( this->*( eventCallback_iff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2055 : + typedef void ( idClass::*eventCallback_fff_t )( const float, const float, const float ); + ( this->*( eventCallback_fff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + /******************************************************* + + 4 args + + *******************************************************/ + + case 4096 : + typedef void ( idClass::*eventCallback_iiii_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_iiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4097 : + typedef void ( idClass::*eventCallback_fiii_t )( const float, const int, const int, const int ); + ( this->*( eventCallback_fiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4098 : + typedef void ( idClass::*eventCallback_ifii_t )( const int, const float, const int, const int ); + ( this->*( eventCallback_ifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4099 : + typedef void ( idClass::*eventCallback_ffii_t )( const float, const float, const int, const int ); + ( this->*( eventCallback_ffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4100 : + typedef void ( idClass::*eventCallback_iifi_t )( const int, const int, const float, const int ); + ( this->*( eventCallback_iifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4101 : + typedef void ( idClass::*eventCallback_fifi_t )( const float, const int, const float, const int ); + ( this->*( eventCallback_fifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4102 : + typedef void ( idClass::*eventCallback_iffi_t )( const int, const float, const float, const int ); + ( this->*( eventCallback_iffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4103 : + typedef void ( idClass::*eventCallback_fffi_t )( const float, const float, const float, const int ); + ( this->*( eventCallback_fffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4104 : + typedef void ( idClass::*eventCallback_iiif_t )( const int, const int, const int, const float ); + ( this->*( eventCallback_iiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4105 : + typedef void ( idClass::*eventCallback_fiif_t )( const float, const int, const int, const float ); + ( this->*( eventCallback_fiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4106 : + typedef void ( idClass::*eventCallback_ifif_t )( const int, const float, const int, const float ); + ( this->*( eventCallback_ifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4107 : + typedef void ( idClass::*eventCallback_ffif_t )( const float, const float, const int, const float ); + ( this->*( eventCallback_ffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4108 : + typedef void ( idClass::*eventCallback_iiff_t )( const int, const int, const float, const float ); + ( this->*( eventCallback_iiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4109 : + typedef void ( idClass::*eventCallback_fiff_t )( const float, const int, const float, const float ); + ( this->*( eventCallback_fiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4110 : + typedef void ( idClass::*eventCallback_ifff_t )( const int, const float, const float, const float ); + ( this->*( eventCallback_ifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4111 : + typedef void ( idClass::*eventCallback_ffff_t )( const float, const float, const float, const float ); + ( this->*( eventCallback_ffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + /******************************************************* + + 5 args + + *******************************************************/ + + case 8192 : + typedef void ( idClass::*eventCallback_iiiii_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8193 : + typedef void ( idClass::*eventCallback_fiiii_t )( const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8194 : + typedef void ( idClass::*eventCallback_ifiii_t )( const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8195 : + typedef void ( idClass::*eventCallback_ffiii_t )( const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8196 : + typedef void ( idClass::*eventCallback_iifii_t )( const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8197 : + typedef void ( idClass::*eventCallback_fifii_t )( const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8198 : + typedef void ( idClass::*eventCallback_iffii_t )( const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8199 : + typedef void ( idClass::*eventCallback_fffii_t )( const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8200 : + typedef void ( idClass::*eventCallback_iiifi_t )( const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8201 : + typedef void ( idClass::*eventCallback_fiifi_t )( const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8202 : + typedef void ( idClass::*eventCallback_ififi_t )( const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8203 : + typedef void ( idClass::*eventCallback_ffifi_t )( const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8204 : + typedef void ( idClass::*eventCallback_iiffi_t )( const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8205 : + typedef void ( idClass::*eventCallback_fiffi_t )( const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8206 : + typedef void ( idClass::*eventCallback_ifffi_t )( const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8207 : + typedef void ( idClass::*eventCallback_ffffi_t )( const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8208 : + typedef void ( idClass::*eventCallback_iiiif_t )( const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8209 : + typedef void ( idClass::*eventCallback_fiiif_t )( const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8210 : + typedef void ( idClass::*eventCallback_ifiif_t )( const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8211 : + typedef void ( idClass::*eventCallback_ffiif_t )( const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8212 : + typedef void ( idClass::*eventCallback_iifif_t )( const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8213 : + typedef void ( idClass::*eventCallback_fifif_t )( const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8214 : + typedef void ( idClass::*eventCallback_iffif_t )( const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8215 : + typedef void ( idClass::*eventCallback_fffif_t )( const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8216 : + typedef void ( idClass::*eventCallback_iiiff_t )( const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8217 : + typedef void ( idClass::*eventCallback_fiiff_t )( const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8218 : + typedef void ( idClass::*eventCallback_ififf_t )( const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8219 : + typedef void ( idClass::*eventCallback_ffiff_t )( const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8220 : + typedef void ( idClass::*eventCallback_iifff_t )( const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8221 : + typedef void ( idClass::*eventCallback_fifff_t )( const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8222 : + typedef void ( idClass::*eventCallback_iffff_t )( const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8223 : + typedef void ( idClass::*eventCallback_fffff_t )( const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + /******************************************************* + + 6 args + + *******************************************************/ + + case 16384 : + typedef void ( idClass::*eventCallback_iiiiii_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16385 : + typedef void ( idClass::*eventCallback_fiiiii_t )( const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16386 : + typedef void ( idClass::*eventCallback_ifiiii_t )( const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16387 : + typedef void ( idClass::*eventCallback_ffiiii_t )( const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16388 : + typedef void ( idClass::*eventCallback_iifiii_t )( const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iifiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16389 : + typedef void ( idClass::*eventCallback_fifiii_t )( const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16390 : + typedef void ( idClass::*eventCallback_iffiii_t )( const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16391 : + typedef void ( idClass::*eventCallback_fffiii_t )( const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16392 : + typedef void ( idClass::*eventCallback_iiifii_t )( const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16393 : + typedef void ( idClass::*eventCallback_fiifii_t )( const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16394 : + typedef void ( idClass::*eventCallback_ififii_t )( const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16395 : + typedef void ( idClass::*eventCallback_ffifii_t )( const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16396 : + typedef void ( idClass::*eventCallback_iiffii_t )( const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16397 : + typedef void ( idClass::*eventCallback_fiffii_t )( const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16398 : + typedef void ( idClass::*eventCallback_ifffii_t )( const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16399 : + typedef void ( idClass::*eventCallback_ffffii_t )( const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16400 : + typedef void ( idClass::*eventCallback_iiiifi_t )( const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16401 : + typedef void ( idClass::*eventCallback_fiiifi_t )( const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16402 : + typedef void ( idClass::*eventCallback_ifiifi_t )( const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16403 : + typedef void ( idClass::*eventCallback_ffiifi_t )( const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16404 : + typedef void ( idClass::*eventCallback_iififi_t )( const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16405 : + typedef void ( idClass::*eventCallback_fififi_t )( const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16406 : + typedef void ( idClass::*eventCallback_iffifi_t )( const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16407 : + typedef void ( idClass::*eventCallback_fffifi_t )( const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16408 : + typedef void ( idClass::*eventCallback_iiiffi_t )( const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16409 : + typedef void ( idClass::*eventCallback_fiiffi_t )( const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16410 : + typedef void ( idClass::*eventCallback_ififfi_t )( const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16411 : + typedef void ( idClass::*eventCallback_ffiffi_t )( const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16412 : + typedef void ( idClass::*eventCallback_iifffi_t )( const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16413 : + typedef void ( idClass::*eventCallback_fifffi_t )( const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16414 : + typedef void ( idClass::*eventCallback_iffffi_t )( const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16415 : + typedef void ( idClass::*eventCallback_fffffi_t )( const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16416 : + typedef void ( idClass::*eventCallback_iiiiif_t )( const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16417 : + typedef void ( idClass::*eventCallback_fiiiif_t )( const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16418 : + typedef void ( idClass::*eventCallback_ifiiif_t )( const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16419 : + typedef void ( idClass::*eventCallback_ffiiif_t )( const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16420 : + typedef void ( idClass::*eventCallback_iifiif_t )( const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iifiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16421 : + typedef void ( idClass::*eventCallback_fifiif_t )( const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16422 : + typedef void ( idClass::*eventCallback_iffiif_t )( const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16423 : + typedef void ( idClass::*eventCallback_fffiif_t )( const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16424 : + typedef void ( idClass::*eventCallback_iiifif_t )( const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16425 : + typedef void ( idClass::*eventCallback_fiifif_t )( const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16426 : + typedef void ( idClass::*eventCallback_ififif_t )( const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16427 : + typedef void ( idClass::*eventCallback_ffifif_t )( const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16428 : + typedef void ( idClass::*eventCallback_iiffif_t )( const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16429 : + typedef void ( idClass::*eventCallback_fiffif_t )( const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16430 : + typedef void ( idClass::*eventCallback_ifffif_t )( const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16431 : + typedef void ( idClass::*eventCallback_ffffif_t )( const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16432 : + typedef void ( idClass::*eventCallback_iiiiff_t )( const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16433 : + typedef void ( idClass::*eventCallback_fiiiff_t )( const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16434 : + typedef void ( idClass::*eventCallback_ifiiff_t )( const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16435 : + typedef void ( idClass::*eventCallback_ffiiff_t )( const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16436 : + typedef void ( idClass::*eventCallback_iififf_t )( const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16437 : + typedef void ( idClass::*eventCallback_fififf_t )( const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16438 : + typedef void ( idClass::*eventCallback_iffiff_t )( const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16439 : + typedef void ( idClass::*eventCallback_fffiff_t )( const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16440 : + typedef void ( idClass::*eventCallback_iiifff_t )( const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16441 : + typedef void ( idClass::*eventCallback_fiifff_t )( const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16442 : + typedef void ( idClass::*eventCallback_ififff_t )( const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16443 : + typedef void ( idClass::*eventCallback_ffifff_t )( const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16444 : + typedef void ( idClass::*eventCallback_iiffff_t )( const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16445 : + typedef void ( idClass::*eventCallback_fiffff_t )( const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16446 : + typedef void ( idClass::*eventCallback_ifffff_t )( const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16447 : + typedef void ( idClass::*eventCallback_ffffff_t )( const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + /******************************************************* + + 7 args + + *******************************************************/ + + case 32768 : + typedef void ( idClass::*eventCallback_iiiiiii_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32769 : + typedef void ( idClass::*eventCallback_fiiiiii_t )( const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32770 : + typedef void ( idClass::*eventCallback_ifiiiii_t )( const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32771 : + typedef void ( idClass::*eventCallback_ffiiiii_t )( const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32772 : + typedef void ( idClass::*eventCallback_iifiiii_t )( const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32773 : + typedef void ( idClass::*eventCallback_fifiiii_t )( const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32774 : + typedef void ( idClass::*eventCallback_iffiiii_t )( const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32775 : + typedef void ( idClass::*eventCallback_fffiiii_t )( const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32776 : + typedef void ( idClass::*eventCallback_iiifiii_t )( const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32777 : + typedef void ( idClass::*eventCallback_fiifiii_t )( const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32778 : + typedef void ( idClass::*eventCallback_ififiii_t )( const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ififiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32779 : + typedef void ( idClass::*eventCallback_ffifiii_t )( const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32780 : + typedef void ( idClass::*eventCallback_iiffiii_t )( const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32781 : + typedef void ( idClass::*eventCallback_fiffiii_t )( const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32782 : + typedef void ( idClass::*eventCallback_ifffiii_t )( const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ifffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32783 : + typedef void ( idClass::*eventCallback_ffffiii_t )( const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32784 : + typedef void ( idClass::*eventCallback_iiiifii_t )( const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32785 : + typedef void ( idClass::*eventCallback_fiiifii_t )( const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32786 : + typedef void ( idClass::*eventCallback_ifiifii_t )( const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32787 : + typedef void ( idClass::*eventCallback_ffiifii_t )( const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32788 : + typedef void ( idClass::*eventCallback_iififii_t )( const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iififii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32789 : + typedef void ( idClass::*eventCallback_fififii_t )( const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32790 : + typedef void ( idClass::*eventCallback_iffifii_t )( const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32791 : + typedef void ( idClass::*eventCallback_fffifii_t )( const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32792 : + typedef void ( idClass::*eventCallback_iiiffii_t )( const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32793 : + typedef void ( idClass::*eventCallback_fiiffii_t )( const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32794 : + typedef void ( idClass::*eventCallback_ififfii_t )( const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ififfii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32795 : + typedef void ( idClass::*eventCallback_ffiffii_t )( const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32796 : + typedef void ( idClass::*eventCallback_iifffii_t )( const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iifffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32797 : + typedef void ( idClass::*eventCallback_fifffii_t )( const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32798 : + typedef void ( idClass::*eventCallback_iffffii_t )( const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32799 : + typedef void ( idClass::*eventCallback_fffffii_t )( const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32800 : + typedef void ( idClass::*eventCallback_iiiiifi_t )( const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32801 : + typedef void ( idClass::*eventCallback_fiiiifi_t )( const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32802 : + typedef void ( idClass::*eventCallback_ifiiifi_t )( const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32803 : + typedef void ( idClass::*eventCallback_ffiiifi_t )( const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32804 : + typedef void ( idClass::*eventCallback_iifiifi_t )( const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32805 : + typedef void ( idClass::*eventCallback_fifiifi_t )( const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32806 : + typedef void ( idClass::*eventCallback_iffiifi_t )( const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32807 : + typedef void ( idClass::*eventCallback_fffiifi_t )( const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32808 : + typedef void ( idClass::*eventCallback_iiififi_t )( const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32809 : + typedef void ( idClass::*eventCallback_fiififi_t )( const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32810 : + typedef void ( idClass::*eventCallback_ifififi_t )( const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32811 : + typedef void ( idClass::*eventCallback_ffififi_t )( const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32812 : + typedef void ( idClass::*eventCallback_iiffifi_t )( const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32813 : + typedef void ( idClass::*eventCallback_fiffifi_t )( const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32814 : + typedef void ( idClass::*eventCallback_ifffifi_t )( const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ifffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32815 : + typedef void ( idClass::*eventCallback_ffffifi_t )( const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32816 : + typedef void ( idClass::*eventCallback_iiiiffi_t )( const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32817 : + typedef void ( idClass::*eventCallback_fiiiffi_t )( const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32818 : + typedef void ( idClass::*eventCallback_ifiiffi_t )( const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32819 : + typedef void ( idClass::*eventCallback_ffiiffi_t )( const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32820 : + typedef void ( idClass::*eventCallback_iififfi_t )( const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iififfi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32821 : + typedef void ( idClass::*eventCallback_fififfi_t )( const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32822 : + typedef void ( idClass::*eventCallback_iffiffi_t )( const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32823 : + typedef void ( idClass::*eventCallback_fffiffi_t )( const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32824 : + typedef void ( idClass::*eventCallback_iiifffi_t )( const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32825 : + typedef void ( idClass::*eventCallback_fiifffi_t )( const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32826 : + typedef void ( idClass::*eventCallback_ififffi_t )( const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ififffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32827 : + typedef void ( idClass::*eventCallback_ffifffi_t )( const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32828 : + typedef void ( idClass::*eventCallback_iiffffi_t )( const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32829 : + typedef void ( idClass::*eventCallback_fiffffi_t )( const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32830 : + typedef void ( idClass::*eventCallback_ifffffi_t )( const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32831 : + typedef void ( idClass::*eventCallback_ffffffi_t )( const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32832 : + typedef void ( idClass::*eventCallback_iiiiiif_t )( const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32833 : + typedef void ( idClass::*eventCallback_fiiiiif_t )( const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32834 : + typedef void ( idClass::*eventCallback_ifiiiif_t )( const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32835 : + typedef void ( idClass::*eventCallback_ffiiiif_t )( const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32836 : + typedef void ( idClass::*eventCallback_iifiiif_t )( const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32837 : + typedef void ( idClass::*eventCallback_fifiiif_t )( const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32838 : + typedef void ( idClass::*eventCallback_iffiiif_t )( const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32839 : + typedef void ( idClass::*eventCallback_fffiiif_t )( const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32840 : + typedef void ( idClass::*eventCallback_iiifiif_t )( const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32841 : + typedef void ( idClass::*eventCallback_fiifiif_t )( const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32842 : + typedef void ( idClass::*eventCallback_ififiif_t )( const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ififiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32843 : + typedef void ( idClass::*eventCallback_ffifiif_t )( const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32844 : + typedef void ( idClass::*eventCallback_iiffiif_t )( const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32845 : + typedef void ( idClass::*eventCallback_fiffiif_t )( const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32846 : + typedef void ( idClass::*eventCallback_ifffiif_t )( const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ifffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32847 : + typedef void ( idClass::*eventCallback_ffffiif_t )( const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32848 : + typedef void ( idClass::*eventCallback_iiiifif_t )( const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32849 : + typedef void ( idClass::*eventCallback_fiiifif_t )( const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32850 : + typedef void ( idClass::*eventCallback_ifiifif_t )( const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32851 : + typedef void ( idClass::*eventCallback_ffiifif_t )( const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32852 : + typedef void ( idClass::*eventCallback_iififif_t )( const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iififif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32853 : + typedef void ( idClass::*eventCallback_fififif_t )( const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32854 : + typedef void ( idClass::*eventCallback_iffifif_t )( const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32855 : + typedef void ( idClass::*eventCallback_fffifif_t )( const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32856 : + typedef void ( idClass::*eventCallback_iiiffif_t )( const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32857 : + typedef void ( idClass::*eventCallback_fiiffif_t )( const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32858 : + typedef void ( idClass::*eventCallback_ififfif_t )( const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ififfif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32859 : + typedef void ( idClass::*eventCallback_ffiffif_t )( const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32860 : + typedef void ( idClass::*eventCallback_iifffif_t )( const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iifffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32861 : + typedef void ( idClass::*eventCallback_fifffif_t )( const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32862 : + typedef void ( idClass::*eventCallback_iffffif_t )( const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32863 : + typedef void ( idClass::*eventCallback_fffffif_t )( const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32864 : + typedef void ( idClass::*eventCallback_iiiiiff_t )( const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32865 : + typedef void ( idClass::*eventCallback_fiiiiff_t )( const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32866 : + typedef void ( idClass::*eventCallback_ifiiiff_t )( const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32867 : + typedef void ( idClass::*eventCallback_ffiiiff_t )( const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32868 : + typedef void ( idClass::*eventCallback_iifiiff_t )( const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32869 : + typedef void ( idClass::*eventCallback_fifiiff_t )( const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32870 : + typedef void ( idClass::*eventCallback_iffiiff_t )( const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32871 : + typedef void ( idClass::*eventCallback_fffiiff_t )( const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32872 : + typedef void ( idClass::*eventCallback_iiififf_t )( const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32873 : + typedef void ( idClass::*eventCallback_fiififf_t )( const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32874 : + typedef void ( idClass::*eventCallback_ifififf_t )( const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32875 : + typedef void ( idClass::*eventCallback_ffififf_t )( const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32876 : + typedef void ( idClass::*eventCallback_iiffiff_t )( const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32877 : + typedef void ( idClass::*eventCallback_fiffiff_t )( const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32878 : + typedef void ( idClass::*eventCallback_ifffiff_t )( const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ifffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32879 : + typedef void ( idClass::*eventCallback_ffffiff_t )( const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32880 : + typedef void ( idClass::*eventCallback_iiiifff_t )( const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32881 : + typedef void ( idClass::*eventCallback_fiiifff_t )( const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32882 : + typedef void ( idClass::*eventCallback_ifiifff_t )( const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32883 : + typedef void ( idClass::*eventCallback_ffiifff_t )( const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32884 : + typedef void ( idClass::*eventCallback_iififff_t )( const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iififff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32885 : + typedef void ( idClass::*eventCallback_fififff_t )( const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32886 : + typedef void ( idClass::*eventCallback_iffifff_t )( const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32887 : + typedef void ( idClass::*eventCallback_fffifff_t )( const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32888 : + typedef void ( idClass::*eventCallback_iiiffff_t )( const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32889 : + typedef void ( idClass::*eventCallback_fiiffff_t )( const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32890 : + typedef void ( idClass::*eventCallback_ififfff_t )( const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ififfff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32891 : + typedef void ( idClass::*eventCallback_ffiffff_t )( const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32892 : + typedef void ( idClass::*eventCallback_iifffff_t )( const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iifffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32893 : + typedef void ( idClass::*eventCallback_fifffff_t )( const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32894 : + typedef void ( idClass::*eventCallback_iffffff_t )( const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32895 : + typedef void ( idClass::*eventCallback_fffffff_t )( const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + /******************************************************* + + 8 args + + *******************************************************/ + + case 65536 : + typedef void ( idClass::*eventCallback_iiiiiiii_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65537 : + typedef void ( idClass::*eventCallback_fiiiiiii_t )( const float, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65538 : + typedef void ( idClass::*eventCallback_ifiiiiii_t )( const int, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65539 : + typedef void ( idClass::*eventCallback_ffiiiiii_t )( const float, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65540 : + typedef void ( idClass::*eventCallback_iifiiiii_t )( const int, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65541 : + typedef void ( idClass::*eventCallback_fifiiiii_t )( const float, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65542 : + typedef void ( idClass::*eventCallback_iffiiiii_t )( const int, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65543 : + typedef void ( idClass::*eventCallback_fffiiiii_t )( const float, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65544 : + typedef void ( idClass::*eventCallback_iiifiiii_t )( const int, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiifiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65545 : + typedef void ( idClass::*eventCallback_fiifiiii_t )( const float, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65546 : + typedef void ( idClass::*eventCallback_ififiiii_t )( const int, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ififiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65547 : + typedef void ( idClass::*eventCallback_ffifiiii_t )( const float, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffifiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65548 : + typedef void ( idClass::*eventCallback_iiffiiii_t )( const int, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiffiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65549 : + typedef void ( idClass::*eventCallback_fiffiiii_t )( const float, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiffiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65550 : + typedef void ( idClass::*eventCallback_ifffiiii_t )( const int, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65551 : + typedef void ( idClass::*eventCallback_ffffiiii_t )( const float, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65552 : + typedef void ( idClass::*eventCallback_iiiifiii_t )( const int, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65553 : + typedef void ( idClass::*eventCallback_fiiifiii_t )( const float, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65554 : + typedef void ( idClass::*eventCallback_ifiifiii_t )( const int, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65555 : + typedef void ( idClass::*eventCallback_ffiifiii_t )( const float, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65556 : + typedef void ( idClass::*eventCallback_iififiii_t )( const int, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iififiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65557 : + typedef void ( idClass::*eventCallback_fififiii_t )( const float, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fififiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65558 : + typedef void ( idClass::*eventCallback_iffifiii_t )( const int, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iffifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65559 : + typedef void ( idClass::*eventCallback_fffifiii_t )( const float, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65560 : + typedef void ( idClass::*eventCallback_iiiffiii_t )( const int, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiffiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65561 : + typedef void ( idClass::*eventCallback_fiiffiii_t )( const float, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65562 : + typedef void ( idClass::*eventCallback_ififfiii_t )( const int, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ififfiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65563 : + typedef void ( idClass::*eventCallback_ffiffiii_t )( const float, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65564 : + typedef void ( idClass::*eventCallback_iifffiii_t )( const int, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iifffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65565 : + typedef void ( idClass::*eventCallback_fifffiii_t )( const float, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fifffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65566 : + typedef void ( idClass::*eventCallback_iffffiii_t )( const int, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65567 : + typedef void ( idClass::*eventCallback_fffffiii_t )( const float, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65568 : + typedef void ( idClass::*eventCallback_iiiiifii_t )( const int, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65569 : + typedef void ( idClass::*eventCallback_fiiiifii_t )( const float, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65570 : + typedef void ( idClass::*eventCallback_ifiiifii_t )( const int, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65571 : + typedef void ( idClass::*eventCallback_ffiiifii_t )( const float, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65572 : + typedef void ( idClass::*eventCallback_iifiifii_t )( const int, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifiifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65573 : + typedef void ( idClass::*eventCallback_fifiifii_t )( const float, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fifiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65574 : + typedef void ( idClass::*eventCallback_iffiifii_t )( const int, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iffiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65575 : + typedef void ( idClass::*eventCallback_fffiifii_t )( const float, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65576 : + typedef void ( idClass::*eventCallback_iiififii_t )( const int, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiififii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65577 : + typedef void ( idClass::*eventCallback_fiififii_t )( const float, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65578 : + typedef void ( idClass::*eventCallback_ifififii_t )( const int, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65579 : + typedef void ( idClass::*eventCallback_ffififii_t )( const float, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffififii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65580 : + typedef void ( idClass::*eventCallback_iiffifii_t )( const int, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiffifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65581 : + typedef void ( idClass::*eventCallback_fiffifii_t )( const float, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiffifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65582 : + typedef void ( idClass::*eventCallback_ifffifii_t )( const int, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65583 : + typedef void ( idClass::*eventCallback_ffffifii_t )( const float, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65584 : + typedef void ( idClass::*eventCallback_iiiiffii_t )( const int, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65585 : + typedef void ( idClass::*eventCallback_fiiiffii_t )( const float, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65586 : + typedef void ( idClass::*eventCallback_ifiiffii_t )( const int, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ifiiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65587 : + typedef void ( idClass::*eventCallback_ffiiffii_t )( const float, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65588 : + typedef void ( idClass::*eventCallback_iififfii_t )( const int, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iififfii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65589 : + typedef void ( idClass::*eventCallback_fififfii_t )( const float, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fififfii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65590 : + typedef void ( idClass::*eventCallback_iffiffii_t )( const int, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65591 : + typedef void ( idClass::*eventCallback_fffiffii_t )( const float, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65592 : + typedef void ( idClass::*eventCallback_iiifffii_t )( const int, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiifffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65593 : + typedef void ( idClass::*eventCallback_fiifffii_t )( const float, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65594 : + typedef void ( idClass::*eventCallback_ififffii_t )( const int, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ififffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65595 : + typedef void ( idClass::*eventCallback_ffifffii_t )( const float, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffifffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65596 : + typedef void ( idClass::*eventCallback_iiffffii_t )( const int, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65597 : + typedef void ( idClass::*eventCallback_fiffffii_t )( const float, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65598 : + typedef void ( idClass::*eventCallback_ifffffii_t )( const int, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65599 : + typedef void ( idClass::*eventCallback_ffffffii_t )( const float, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65600 : + typedef void ( idClass::*eventCallback_iiiiiifi_t )( const int, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65601 : + typedef void ( idClass::*eventCallback_fiiiiifi_t )( const float, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65602 : + typedef void ( idClass::*eventCallback_ifiiiifi_t )( const int, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65603 : + typedef void ( idClass::*eventCallback_ffiiiifi_t )( const float, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65604 : + typedef void ( idClass::*eventCallback_iifiiifi_t )( const int, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65605 : + typedef void ( idClass::*eventCallback_fifiiifi_t )( const float, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65606 : + typedef void ( idClass::*eventCallback_iffiiifi_t )( const int, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65607 : + typedef void ( idClass::*eventCallback_fffiiifi_t )( const float, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65608 : + typedef void ( idClass::*eventCallback_iiifiifi_t )( const int, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65609 : + typedef void ( idClass::*eventCallback_fiifiifi_t )( const float, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65610 : + typedef void ( idClass::*eventCallback_ififiifi_t )( const int, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ififiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65611 : + typedef void ( idClass::*eventCallback_ffifiifi_t )( const float, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffifiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65612 : + typedef void ( idClass::*eventCallback_iiffiifi_t )( const int, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiffiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65613 : + typedef void ( idClass::*eventCallback_fiffiifi_t )( const float, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiffiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65614 : + typedef void ( idClass::*eventCallback_ifffiifi_t )( const int, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65615 : + typedef void ( idClass::*eventCallback_ffffiifi_t )( const float, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65616 : + typedef void ( idClass::*eventCallback_iiiififi_t )( const int, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65617 : + typedef void ( idClass::*eventCallback_fiiififi_t )( const float, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65618 : + typedef void ( idClass::*eventCallback_ifiififi_t )( const int, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifiififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65619 : + typedef void ( idClass::*eventCallback_ffiififi_t )( const float, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65620 : + typedef void ( idClass::*eventCallback_iifififi_t )( const int, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iifififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65621 : + typedef void ( idClass::*eventCallback_fifififi_t )( const float, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fifififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65622 : + typedef void ( idClass::*eventCallback_iffififi_t )( const int, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iffififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65623 : + typedef void ( idClass::*eventCallback_fffififi_t )( const float, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65624 : + typedef void ( idClass::*eventCallback_iiiffifi_t )( const int, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiffifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65625 : + typedef void ( idClass::*eventCallback_fiiffifi_t )( const float, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65626 : + typedef void ( idClass::*eventCallback_ififfifi_t )( const int, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ififfifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65627 : + typedef void ( idClass::*eventCallback_ffiffifi_t )( const float, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65628 : + typedef void ( idClass::*eventCallback_iifffifi_t )( const int, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iifffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65629 : + typedef void ( idClass::*eventCallback_fifffifi_t )( const float, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fifffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65630 : + typedef void ( idClass::*eventCallback_iffffifi_t )( const int, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65631 : + typedef void ( idClass::*eventCallback_fffffifi_t )( const float, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65632 : + typedef void ( idClass::*eventCallback_iiiiiffi_t )( const int, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65633 : + typedef void ( idClass::*eventCallback_fiiiiffi_t )( const float, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65634 : + typedef void ( idClass::*eventCallback_ifiiiffi_t )( const int, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65635 : + typedef void ( idClass::*eventCallback_ffiiiffi_t )( const float, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65636 : + typedef void ( idClass::*eventCallback_iifiiffi_t )( const int, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iifiiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65637 : + typedef void ( idClass::*eventCallback_fifiiffi_t )( const float, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fifiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65638 : + typedef void ( idClass::*eventCallback_iffiiffi_t )( const int, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65639 : + typedef void ( idClass::*eventCallback_fffiiffi_t )( const float, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65640 : + typedef void ( idClass::*eventCallback_iiififfi_t )( const int, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiififfi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65641 : + typedef void ( idClass::*eventCallback_fiififfi_t )( const float, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65642 : + typedef void ( idClass::*eventCallback_ifififfi_t )( const int, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65643 : + typedef void ( idClass::*eventCallback_ffififfi_t )( const float, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffififfi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65644 : + typedef void ( idClass::*eventCallback_iiffiffi_t )( const int, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65645 : + typedef void ( idClass::*eventCallback_fiffiffi_t )( const float, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65646 : + typedef void ( idClass::*eventCallback_ifffiffi_t )( const int, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65647 : + typedef void ( idClass::*eventCallback_ffffiffi_t )( const float, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65648 : + typedef void ( idClass::*eventCallback_iiiifffi_t )( const int, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65649 : + typedef void ( idClass::*eventCallback_fiiifffi_t )( const float, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65650 : + typedef void ( idClass::*eventCallback_ifiifffi_t )( const int, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifiifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65651 : + typedef void ( idClass::*eventCallback_ffiifffi_t )( const float, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65652 : + typedef void ( idClass::*eventCallback_iififffi_t )( const int, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iififffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65653 : + typedef void ( idClass::*eventCallback_fififffi_t )( const float, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fififffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65654 : + typedef void ( idClass::*eventCallback_iffifffi_t )( const int, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iffifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65655 : + typedef void ( idClass::*eventCallback_fffifffi_t )( const float, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65656 : + typedef void ( idClass::*eventCallback_iiiffffi_t )( const int, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiffffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65657 : + typedef void ( idClass::*eventCallback_fiiffffi_t )( const float, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65658 : + typedef void ( idClass::*eventCallback_ififfffi_t )( const int, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ififfffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65659 : + typedef void ( idClass::*eventCallback_ffiffffi_t )( const float, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65660 : + typedef void ( idClass::*eventCallback_iifffffi_t )( const int, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65661 : + typedef void ( idClass::*eventCallback_fifffffi_t )( const float, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65662 : + typedef void ( idClass::*eventCallback_iffffffi_t )( const int, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65663 : + typedef void ( idClass::*eventCallback_fffffffi_t )( const float, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65664 : + typedef void ( idClass::*eventCallback_iiiiiiif_t )( const int, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65665 : + typedef void ( idClass::*eventCallback_fiiiiiif_t )( const float, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65666 : + typedef void ( idClass::*eventCallback_ifiiiiif_t )( const int, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65667 : + typedef void ( idClass::*eventCallback_ffiiiiif_t )( const float, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65668 : + typedef void ( idClass::*eventCallback_iifiiiif_t )( const int, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65669 : + typedef void ( idClass::*eventCallback_fifiiiif_t )( const float, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65670 : + typedef void ( idClass::*eventCallback_iffiiiif_t )( const int, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65671 : + typedef void ( idClass::*eventCallback_fffiiiif_t )( const float, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65672 : + typedef void ( idClass::*eventCallback_iiifiiif_t )( const int, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiifiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65673 : + typedef void ( idClass::*eventCallback_fiifiiif_t )( const float, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65674 : + typedef void ( idClass::*eventCallback_ififiiif_t )( const int, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ififiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65675 : + typedef void ( idClass::*eventCallback_ffifiiif_t )( const float, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffifiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65676 : + typedef void ( idClass::*eventCallback_iiffiiif_t )( const int, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiffiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65677 : + typedef void ( idClass::*eventCallback_fiffiiif_t )( const float, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiffiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65678 : + typedef void ( idClass::*eventCallback_ifffiiif_t )( const int, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65679 : + typedef void ( idClass::*eventCallback_ffffiiif_t )( const float, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65680 : + typedef void ( idClass::*eventCallback_iiiifiif_t )( const int, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65681 : + typedef void ( idClass::*eventCallback_fiiifiif_t )( const float, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65682 : + typedef void ( idClass::*eventCallback_ifiifiif_t )( const int, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65683 : + typedef void ( idClass::*eventCallback_ffiifiif_t )( const float, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65684 : + typedef void ( idClass::*eventCallback_iififiif_t )( const int, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iififiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65685 : + typedef void ( idClass::*eventCallback_fififiif_t )( const float, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fififiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65686 : + typedef void ( idClass::*eventCallback_iffifiif_t )( const int, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iffifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65687 : + typedef void ( idClass::*eventCallback_fffifiif_t )( const float, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65688 : + typedef void ( idClass::*eventCallback_iiiffiif_t )( const int, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiffiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65689 : + typedef void ( idClass::*eventCallback_fiiffiif_t )( const float, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65690 : + typedef void ( idClass::*eventCallback_ififfiif_t )( const int, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ififfiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65691 : + typedef void ( idClass::*eventCallback_ffiffiif_t )( const float, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65692 : + typedef void ( idClass::*eventCallback_iifffiif_t )( const int, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iifffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65693 : + typedef void ( idClass::*eventCallback_fifffiif_t )( const float, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fifffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65694 : + typedef void ( idClass::*eventCallback_iffffiif_t )( const int, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65695 : + typedef void ( idClass::*eventCallback_fffffiif_t )( const float, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65696 : + typedef void ( idClass::*eventCallback_iiiiifif_t )( const int, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65697 : + typedef void ( idClass::*eventCallback_fiiiifif_t )( const float, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65698 : + typedef void ( idClass::*eventCallback_ifiiifif_t )( const int, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65699 : + typedef void ( idClass::*eventCallback_ffiiifif_t )( const float, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65700 : + typedef void ( idClass::*eventCallback_iifiifif_t )( const int, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifiifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65701 : + typedef void ( idClass::*eventCallback_fifiifif_t )( const float, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fifiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65702 : + typedef void ( idClass::*eventCallback_iffiifif_t )( const int, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iffiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65703 : + typedef void ( idClass::*eventCallback_fffiifif_t )( const float, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65704 : + typedef void ( idClass::*eventCallback_iiififif_t )( const int, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiififif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65705 : + typedef void ( idClass::*eventCallback_fiififif_t )( const float, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65706 : + typedef void ( idClass::*eventCallback_ifififif_t )( const int, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65707 : + typedef void ( idClass::*eventCallback_ffififif_t )( const float, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffififif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65708 : + typedef void ( idClass::*eventCallback_iiffifif_t )( const int, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiffifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65709 : + typedef void ( idClass::*eventCallback_fiffifif_t )( const float, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiffifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65710 : + typedef void ( idClass::*eventCallback_ifffifif_t )( const int, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65711 : + typedef void ( idClass::*eventCallback_ffffifif_t )( const float, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65712 : + typedef void ( idClass::*eventCallback_iiiiffif_t )( const int, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65713 : + typedef void ( idClass::*eventCallback_fiiiffif_t )( const float, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65714 : + typedef void ( idClass::*eventCallback_ifiiffif_t )( const int, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ifiiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65715 : + typedef void ( idClass::*eventCallback_ffiiffif_t )( const float, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65716 : + typedef void ( idClass::*eventCallback_iififfif_t )( const int, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iififfif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65717 : + typedef void ( idClass::*eventCallback_fififfif_t )( const float, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fififfif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65718 : + typedef void ( idClass::*eventCallback_iffiffif_t )( const int, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65719 : + typedef void ( idClass::*eventCallback_fffiffif_t )( const float, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65720 : + typedef void ( idClass::*eventCallback_iiifffif_t )( const int, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiifffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65721 : + typedef void ( idClass::*eventCallback_fiifffif_t )( const float, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65722 : + typedef void ( idClass::*eventCallback_ififffif_t )( const int, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ififffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65723 : + typedef void ( idClass::*eventCallback_ffifffif_t )( const float, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffifffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65724 : + typedef void ( idClass::*eventCallback_iiffffif_t )( const int, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65725 : + typedef void ( idClass::*eventCallback_fiffffif_t )( const float, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65726 : + typedef void ( idClass::*eventCallback_ifffffif_t )( const int, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65727 : + typedef void ( idClass::*eventCallback_ffffffif_t )( const float, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65728 : + typedef void ( idClass::*eventCallback_iiiiiiff_t )( const int, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65729 : + typedef void ( idClass::*eventCallback_fiiiiiff_t )( const float, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65730 : + typedef void ( idClass::*eventCallback_ifiiiiff_t )( const int, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65731 : + typedef void ( idClass::*eventCallback_ffiiiiff_t )( const float, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65732 : + typedef void ( idClass::*eventCallback_iifiiiff_t )( const int, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65733 : + typedef void ( idClass::*eventCallback_fifiiiff_t )( const float, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65734 : + typedef void ( idClass::*eventCallback_iffiiiff_t )( const int, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65735 : + typedef void ( idClass::*eventCallback_fffiiiff_t )( const float, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65736 : + typedef void ( idClass::*eventCallback_iiifiiff_t )( const int, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiifiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65737 : + typedef void ( idClass::*eventCallback_fiifiiff_t )( const float, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65738 : + typedef void ( idClass::*eventCallback_ififiiff_t )( const int, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ififiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65739 : + typedef void ( idClass::*eventCallback_ffifiiff_t )( const float, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffifiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65740 : + typedef void ( idClass::*eventCallback_iiffiiff_t )( const int, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiffiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65741 : + typedef void ( idClass::*eventCallback_fiffiiff_t )( const float, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiffiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65742 : + typedef void ( idClass::*eventCallback_ifffiiff_t )( const int, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65743 : + typedef void ( idClass::*eventCallback_ffffiiff_t )( const float, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65744 : + typedef void ( idClass::*eventCallback_iiiififf_t )( const int, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65745 : + typedef void ( idClass::*eventCallback_fiiififf_t )( const float, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65746 : + typedef void ( idClass::*eventCallback_ifiififf_t )( const int, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifiififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65747 : + typedef void ( idClass::*eventCallback_ffiififf_t )( const float, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65748 : + typedef void ( idClass::*eventCallback_iifififf_t )( const int, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iifififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65749 : + typedef void ( idClass::*eventCallback_fifififf_t )( const float, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fifififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65750 : + typedef void ( idClass::*eventCallback_iffififf_t )( const int, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iffififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65751 : + typedef void ( idClass::*eventCallback_fffififf_t )( const float, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65752 : + typedef void ( idClass::*eventCallback_iiiffiff_t )( const int, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiffiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65753 : + typedef void ( idClass::*eventCallback_fiiffiff_t )( const float, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65754 : + typedef void ( idClass::*eventCallback_ififfiff_t )( const int, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ififfiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65755 : + typedef void ( idClass::*eventCallback_ffiffiff_t )( const float, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65756 : + typedef void ( idClass::*eventCallback_iifffiff_t )( const int, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iifffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65757 : + typedef void ( idClass::*eventCallback_fifffiff_t )( const float, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fifffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65758 : + typedef void ( idClass::*eventCallback_iffffiff_t )( const int, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65759 : + typedef void ( idClass::*eventCallback_fffffiff_t )( const float, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65760 : + typedef void ( idClass::*eventCallback_iiiiifff_t )( const int, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65761 : + typedef void ( idClass::*eventCallback_fiiiifff_t )( const float, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65762 : + typedef void ( idClass::*eventCallback_ifiiifff_t )( const int, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65763 : + typedef void ( idClass::*eventCallback_ffiiifff_t )( const float, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65764 : + typedef void ( idClass::*eventCallback_iifiifff_t )( const int, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifiifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65765 : + typedef void ( idClass::*eventCallback_fifiifff_t )( const float, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fifiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65766 : + typedef void ( idClass::*eventCallback_iffiifff_t )( const int, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iffiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65767 : + typedef void ( idClass::*eventCallback_fffiifff_t )( const float, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65768 : + typedef void ( idClass::*eventCallback_iiififff_t )( const int, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiififff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65769 : + typedef void ( idClass::*eventCallback_fiififff_t )( const float, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65770 : + typedef void ( idClass::*eventCallback_ifififff_t )( const int, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65771 : + typedef void ( idClass::*eventCallback_ffififff_t )( const float, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffififff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65772 : + typedef void ( idClass::*eventCallback_iiffifff_t )( const int, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiffifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65773 : + typedef void ( idClass::*eventCallback_fiffifff_t )( const float, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiffifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65774 : + typedef void ( idClass::*eventCallback_ifffifff_t )( const int, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65775 : + typedef void ( idClass::*eventCallback_ffffifff_t )( const float, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65776 : + typedef void ( idClass::*eventCallback_iiiiffff_t )( const int, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65777 : + typedef void ( idClass::*eventCallback_fiiiffff_t )( const float, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65778 : + typedef void ( idClass::*eventCallback_ifiiffff_t )( const int, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ifiiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65779 : + typedef void ( idClass::*eventCallback_ffiiffff_t )( const float, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65780 : + typedef void ( idClass::*eventCallback_iififfff_t )( const int, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iififfff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65781 : + typedef void ( idClass::*eventCallback_fififfff_t )( const float, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fififfff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65782 : + typedef void ( idClass::*eventCallback_iffiffff_t )( const int, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65783 : + typedef void ( idClass::*eventCallback_fffiffff_t )( const float, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65784 : + typedef void ( idClass::*eventCallback_iiifffff_t )( const int, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiifffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65785 : + typedef void ( idClass::*eventCallback_fiifffff_t )( const float, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65786 : + typedef void ( idClass::*eventCallback_ififffff_t )( const int, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ififffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65787 : + typedef void ( idClass::*eventCallback_ffifffff_t )( const float, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffifffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65788 : + typedef void ( idClass::*eventCallback_iiffffff_t )( const int, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65789 : + typedef void ( idClass::*eventCallback_fiffffff_t )( const float, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65790 : + typedef void ( idClass::*eventCallback_ifffffff_t )( const int, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65791 : + typedef void ( idClass::*eventCallback_ffffffff_t )( const float, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + diff --git a/src/game/gamesys/Class.cpp b/src/game/gamesys/Class.cpp new file mode 100644 index 0000000..997eabf --- /dev/null +++ b/src/game/gamesys/Class.cpp @@ -0,0 +1,1458 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Base class for all C++ objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +// this is the head of a singly linked list of all the idTypes +static idTypeInfo *typelist = NULL; +static idHierarchy classHierarchy; +static int eventCallbackMemory = 0; + +/* +================ +idTypeInfo::idClassType() + +Constructor for class. Should only be called from CLASS_DECLARATION macro. +Handles linking class definition into class hierarchy. This should only happen +at startup as idTypeInfos are statically defined. Since static variables can be +initialized in any order, the constructor must handle the case that subclasses +are initialized before superclasses. +================ +*/ +idTypeInfo::idTypeInfo( const char *classname, const char *superclass, idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), +// RAVEN BEGIN +// bdube: added states + void ( idClass::*Spawn )( void ), + rvStateFunc* stateCallbacks, + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ) { +// RAVEN END + + idTypeInfo *type; + idTypeInfo **insert; + + this->classname = classname; + this->superclass = superclass; + this->eventCallbacks = eventCallbacks; + this->eventMap = NULL; + this->Spawn = Spawn; + this->Save = Save; + this->Restore = Restore; + this->CreateInstance = CreateInstance; + this->super = idClass::GetClass( superclass ); + this->freeEventMap = false; + typeNum = 0; + lastChild = 0; + +// RAVEN BEGIN +// bdube: added states + this->stateCallbacks = stateCallbacks; +// RAVEN END + + // Check if any subclasses were initialized before their superclass + for( type = typelist; type != NULL; type = type->next ) { + if ( ( type->super == NULL ) && !idStr::Cmp( type->superclass, this->classname ) && + idStr::Cmp( type->classname, "idClass" ) ) { + type->super = this; + } + } + + // Insert sorted + for ( insert = &typelist; *insert; insert = &(*insert)->next ) { + assert( idStr::Cmp( classname, (*insert)->classname ) ); + if ( idStr::Cmp( classname, (*insert)->classname ) < 0 ) { + next = *insert; + *insert = this; + break; + } + } + if ( !*insert ) { + *insert = this; + next = NULL; + } +} + +/* +================ +idTypeInfo::~idTypeInfo +================ +*/ +idTypeInfo::~idTypeInfo() { + Shutdown(); +} + +/* +================ +idTypeInfo::Init + +Initializes the event callback table for the class. Creates a +table for fast lookups of event functions. Should only be called once. +================ +*/ +void idTypeInfo::Init( void ) { + idTypeInfo *c; + idEventFunc *def; + int ev; + int i; + bool *set; + int num; + + if ( eventMap ) { + // we've already been initialized by a subclass + return; + } + + // make sure our superclass is initialized first + if ( super && !super->eventMap ) { + super->Init(); + } + + // add to our node hierarchy + if ( super ) { + node.ParentTo( super->node ); + } else { + node.ParentTo( classHierarchy ); + } + node.SetOwner( this ); + + // keep track of the number of children below each class + for( c = super; c != NULL; c = c->super ) { + c->lastChild++; + } + + // if we're not adding any new event callbacks, we can just use our superclass's table + if ( ( !eventCallbacks || !eventCallbacks->event ) && super ) { + eventMap = super->eventMap; + return; + } + + // set a flag so we know to delete the eventMap table + freeEventMap = true; + + // Allocate our new table. It has to have as many entries as there + // are events. NOTE: could save some space by keeping track of the maximum + // event that the class responds to and doing range checking. + num = idEventDef::NumEventCommands(); + eventMap = new eventCallback_t[ num ]; + memset( eventMap, 0, sizeof( eventCallback_t ) * num ); + eventCallbackMemory += sizeof( eventCallback_t ) * num; + + // allocate temporary memory for flags so that the subclass's event callbacks + // override the superclass's event callback + set = new bool[ num ]; + memset( set, 0, sizeof( bool ) * num ); + + // go through the inheritence order and copies the event callback function into + // a list indexed by the event number. This allows fast lookups of + // event functions. + for( c = this; c != NULL; c = c->super ) { + def = c->eventCallbacks; + if ( !def ) { + continue; + } + + // go through each entry until we hit the NULL terminator + for( i = 0; def[ i ].event != NULL; i++ ) { + ev = def[ i ].event->GetEventNum(); + + if ( set[ ev ] ) { + continue; + } + set[ ev ] = true; + eventMap[ ev ] = def[ i ].function; + } + } + + delete[] set; +} + +/* +================ +idTypeInfo::Shutdown + +Should only be called when DLL or EXE is being shutdown. +Although it cleans up any allocated memory, it doesn't bother to remove itself +from the class list since the program is shutting down. +================ +*/ +void idTypeInfo::Shutdown() { + // free up the memory used for event lookups + if ( eventMap ) { + if ( freeEventMap ) { + delete[] eventMap; + } + eventMap = NULL; + } + typeNum = 0; + lastChild = 0; +} + + +/*********************************************************************** + + idClass + +***********************************************************************/ + +const idEventDef EV_PostRestore( "", NULL ); +const idEventDef EV_Remove( "", NULL ); +const idEventDef EV_SafeRemove( "remove", NULL ); + +ABSTRACT_DECLARATION( NULL, idClass ) +// RAVEN BEGIN + EVENT( EV_PostRestore, idClass::Event_PostRestore ) +// RAVEN END + EVENT( EV_Remove, idClass::Event_Remove ) + EVENT( EV_SafeRemove, idClass::Event_SafeRemove ) +END_CLASS + +CLASS_STATES_DECLARATION(idClass) +END_CLASS_STATES + +// alphabetical order +idList idClass::types; +// typenum order +idList idClass::typenums; + +bool idClass::initialized = false; +int idClass::typeNumBits = 0; +int idClass::memused = 0; +int idClass::numobjects = 0; + +/* +================ +idClass::CallSpawn +================ +*/ +void idClass::CallSpawn( void ) { + idTypeInfo *type; + + type = GetType(); + CallSpawnFunc( type ); +} + +/* +================ +idClass::CallSpawnFunc +================ +*/ +classSpawnFunc_t idClass::CallSpawnFunc( idTypeInfo *cls ) { + classSpawnFunc_t func; + + if ( cls->super ) { + func = CallSpawnFunc( cls->super ); + if ( func == cls->Spawn ) { + // don't call the same function twice in a row. + // this can happen when subclasses don't have their own spawn function. + return func; + } + } + + // RAVEN BEGIN + // hmmm.... stompage of memory has occured + assert(cls->Spawn != 0); + // RAVEN END + + ( this->*cls->Spawn )(); + + return cls->Spawn; +} + +/* +================ +idClass::FindUninitializedMemory +================ +*/ +void idClass::FindUninitializedMemory( void ) { +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = ( ( unsigned long * )this ) - 1; + int size = *ptr; + assert( ( size & 3 ) == 0 ); + size >>= 2; + for ( int i = 0; i < size; i++ ) { + if ( ptr[i] == 0xcdcdcdcd ) { +// RAVEN BEGIN + gameLocal.Warning( "type '%s' has uninitialized variable (offset %d)", GetClassname(), i << 2 ); +// RAVEN END + } + } +#endif +} + +/* +================ +idClass::Spawn +================ +*/ +void idClass::Spawn( void ) { +} + +/* +================ +idClass::~idClass + +Destructor for object. Cancels any events that depend on this object. +================ +*/ +idClass::~idClass() { + idEvent::CancelEvents( this ); +} + +/* +================ +idClass::DisplayInfo_f +================ +*/ +void idClass::DisplayInfo_f( const idCmdArgs &args ) { + gameLocal.Printf( "Class memory status: %i bytes allocated in %i objects\n", memused, numobjects ); +} + +/* +================ +idClass::ListClasses_f +================ +*/ +void idClass::ListClasses_f( const idCmdArgs &args ) { + int i; + idTypeInfo *type; + + gameLocal.Printf( "%-24s %-24s %-6s %-6s\n", "Classname", "Superclass", "Type", "Subclasses" ); + gameLocal.Printf( "----------------------------------------------------------------------\n" ); + + for( i = 0; i < types.Num(); i++ ) { + type = types[ i ]; + gameLocal.Printf( "%-24s %-24s %6d %6d\n", type->classname, type->superclass, type->typeNum, type->lastChild - type->typeNum ); + } + + gameLocal.Printf( "...%d classes", types.Num() ); +} + +/* +================ +idClass::CreateInstance +================ +*/ +idClass *idClass::CreateInstance( const char *name ) { + const idTypeInfo *type; + idClass *obj; + + type = idClass::GetClass( name ); + if ( !type ) { + return NULL; + } + + obj = type->CreateInstance(); + return obj; +} + +/* +================ +idClass::Init + +Should be called after all idTypeInfos are initialized, so must be called +manually upon game code initialization. Tells all the idTypeInfos to initialize +their event callback table for the associated class. This should only be called +once during the execution of the program or DLL. +================ +*/ +void idClass::Init( void ) { + idTypeInfo *c; + int num; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CLASS); +// RAVEN END + + gameLocal.Printf( "Initializing class hierarchy\n" ); + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + return; + } + + // init the event callback tables for all the classes + for( c = typelist; c != NULL; c = c->next ) { +// RAVEN BEGIN +// jnewquist: Make sure the superclass was actually registered! + if ( c->super == NULL && (c->superclass && idStr::Cmp(c->superclass, "NULL")) ) { + common->Error("Superclass %s of %s was never registered!", c->superclass, c->classname); + } +// RAVEN END + c->Init(); + } + + // number the types according to the class hierarchy so we can quickly determine if a class + // is a subclass of another + num = 0; + for( c = classHierarchy.GetNext(); c != NULL; c = c->node.GetNext(), num++ ) { + c->typeNum = num; + c->lastChild += num; + } + + // number of bits needed to send types over network + typeNumBits = idMath::BitsForInteger( num ); + + // create a list of the types so we can do quick lookups + // one list in alphabetical order, one in typenum order + types.SetGranularity( 1 ); + types.SetNum( num ); + typenums.SetGranularity( 1 ); + typenums.SetNum( num ); + num = 0; + for( c = typelist; c != NULL; c = c->next, num++ ) { + types[ num ] = c; + typenums[ c->typeNum ] = c; + } + + initialized = true; + + gameLocal.Printf( "...%i classes, %i bytes for event callbacks\n", types.Num(), eventCallbackMemory ); +} + +/* +================ +idClass::Shutdown +================ +*/ +void idClass::Shutdown( void ) { + idTypeInfo *c; + + for( c = typelist; c != NULL; c = c->next ) { + c->Shutdown(); + } + types.Clear(); + typenums.Clear(); + + initialized = false; +} + +/* +================ +idClass::new +================ +*/ +#ifdef ID_DEBUG_MEMORY +#undef new +#endif + +void * idClass::operator new( size_t s ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +void * idClass::operator new( size_t s, int, int, char *, int ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +#ifdef ID_DEBUG_MEMORY +#define new ID_DEBUG_NEW +#endif + +/* +================ +idClass::delete +================ +*/ +void idClass::operator delete( void *ptr ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +void idClass::operator delete( void *ptr, int, int, char *, int ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +/* +================ +idClass::GetClass + +Returns the idTypeInfo for the name of the class passed in. This is a static function +so it must be called as idClass::GetClass( classname ) +================ +*/ +idTypeInfo *idClass::GetClass( const char *name ) { + idTypeInfo *c; + int order; + int mid; + int min; + int max; + + if ( !initialized ) { + // idClass::Init hasn't been called yet, so do a slow lookup + for( c = typelist; c != NULL; c = c->next ) { + if ( !idStr::Cmp( c->classname, name ) ) { + return c; + } + } + } else { + // do a binary search through the list of types + min = 0; + max = types.Num() - 1; + while( min <= max ) { + mid = ( min + max ) / 2; + c = types[ mid ]; + order = idStr::Cmp( c->classname, name ); + if ( !order ) { + return c; + } else if ( order > 0 ) { + max = mid - 1; + } else { + min = mid + 1; + } + } + } + + return NULL; +} + +/* +================ +idClass::GetType +================ +*/ +idTypeInfo *idClass::GetType( const int typeNum ) { + idTypeInfo *c; + + if ( !initialized ) { + for( c = typelist; c != NULL; c = c->next ) { + if ( c->typeNum == typeNum ) { + return c; + } + } + } else if ( ( typeNum >= 0 ) && ( typeNum < types.Num() ) ) { + return typenums[ typeNum ]; + } + + return NULL; +} + +/* +================ +idClass::GetClassname + +Returns the text classname of the object. +================ +*/ +const char *idClass::GetClassname( void ) const { + idTypeInfo *type; + + type = GetType(); + return type->classname; +} + +/* +================ +idClass::GetSuperclass + +Returns the text classname of the superclass. +================ +*/ +const char *idClass::GetSuperclass( void ) const { + idTypeInfo *cls; + + cls = GetType(); + return cls->superclass; +} + +/* +================ +idClass::CancelEvents +================ +*/ +void idClass::CancelEvents( const idEventDef *ev ) { + idEvent::CancelEvents( this, ev ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idClass::EventIsPosted +================ +*/ +bool idClass::EventIsPosted( const idEventDef *ev ) const { + return idEvent::EventIsPosted( this, ev ); +} +// RAVEN END + +/* +================ +idClass::PostEventArgs +================ +*/ +bool idClass::PostEventArgs( const idEventDef *ev, int time, int numargs, ... ) { + idTypeInfo *c; + idEvent *event; + va_list args; + + assert( ev ); + + if ( !idEvent::initialized ) { + return false; + } + + c = GetType(); + if ( !c->eventMap[ ev->GetEventNum() ] ) { + // we don't respond to this event, so ignore it + return false; + } + + // we service events on the client to avoid any bad code filling up the event pool + // we don't want them processed usually, unless when the map is (re)loading. + // we allow threads to run fine, though. +// RAVEN BEGIN +// bdube: added a check to see if this is a client entity +// jnewquist: Use accessor for static class type + bool isClient = !IsClient() && gameLocal.isClient; + +#ifdef _XENON + // nrausch: We want to allow selectWeapon to pass through, but that's all + if ( idStr::Cmp( ev->GetName(), "selectWeapon" ) == 0 ) { + isClient = false; + } +#endif + + if( isClient && ( gameLocal.GameState() != GAMESTATE_STARTUP ) && ( gameLocal.GameState() != GAMESTATE_RESTART ) && !IsType( idThread::GetClassType() ) ) { +// RAVEN END + return true; + } + + va_start( args, numargs ); + event = idEvent::Alloc( ev, numargs, args ); + va_end( args ); + + event->Schedule( this, c, time ); + + return true; +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time ) { + return PostEventArgs( ev, time, 0 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ) { + return PostEventArgs( ev, time, 1, &arg1 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, time, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, time, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, time, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, time, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, time, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, time, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, time, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time ) { + return PostEventArgs( ev, SEC2MS( time ), 0 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ) { + return PostEventArgs( ev, SEC2MS( time ), 1, &arg1 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, SEC2MS( time ), 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, SEC2MS( time ), 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, SEC2MS( time ), 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, SEC2MS( time ), 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, SEC2MS( time ), 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, SEC2MS( time ), 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, SEC2MS( time ), 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgs +================ +*/ +bool idClass::ProcessEventArgs( const idEventDef *ev, int numargs, ... ) { + idTypeInfo *c; + int num; + int data[ D_EVENT_MAXARGS ]; + va_list args; + + assert( ev ); + assert( idEvent::initialized ); + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + va_start( args, numargs ); + idEvent::CopyArgs( ev, numargs, args, data ); + va_end( args ); + + ProcessEventArgPtr( ev, data ); + + return true; +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev ) { + return ProcessEventArgs( ev, 0 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1 ) { + return ProcessEventArgs( ev, 1, &arg1 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ) { + return ProcessEventArgs( ev, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return ProcessEventArgs( ev, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return ProcessEventArgs( ev, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return ProcessEventArgs( ev, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return ProcessEventArgs( ev, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return ProcessEventArgs( ev, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return ProcessEventArgs( ev, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgPtr +================ +*/ +bool idClass::ProcessEventArgPtr( const idEventDef *ev, int *data ) { + idTypeInfo *c; + int num; + eventCallback_t callback; + + assert( ev ); + assert( idEvent::initialized ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( g_debugTriggers.GetBool() && ( ev == &EV_Activate ) && IsType( idEntity::GetClassType() ) ) { +// RAVEN END + const idEntity *ent = *reinterpret_cast( data ); + gameLocal.Printf( "%d: '%s' activated by '%s'\n", gameLocal.framenum, static_cast( this )->GetName(), ent ? ent->GetName() : "NULL" ); + } + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + callback = c->eventMap[ num ]; + +#if !CPU_EASYARGS + +/* +on ppc architecture, floats are passed in a seperate set of registers +the function prototypes must have matching float declaration + +http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/2rt_powerpc_abi/chapter_9_section_5.html +*/ + + switch( ev->GetFormatspecIndex() ) { + case 1 << D_EVENT_MAXARGS : + ( this->*callback )(); + break; + +// generated file - see CREATE_EVENT_CODE +#include "Callbacks.cpp" + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#else + + assert( D_EVENT_MAXARGS == 8 ); + + switch( ev->GetNumArgs() ) { + case 0 : + ( this->*callback )(); + break; + + case 1 : + typedef void ( idClass::*eventCallback_1_t )( const int ); + ( this->*( eventCallback_1_t )callback )( data[ 0 ] ); + break; + + case 2 : + typedef void ( idClass::*eventCallback_2_t )( const int, const int ); + ( this->*( eventCallback_2_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 3 : + typedef void ( idClass::*eventCallback_3_t )( const int, const int, const int ); + ( this->*( eventCallback_3_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 4 : + typedef void ( idClass::*eventCallback_4_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_4_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 5 : + typedef void ( idClass::*eventCallback_5_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_5_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 6 : + typedef void ( idClass::*eventCallback_6_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_6_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 7 : + typedef void ( idClass::*eventCallback_7_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_7_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 8 : + typedef void ( idClass::*eventCallback_8_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_8_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#endif + + return true; +} + +/* +================ +idClass::Event_Remove +================ +*/ +void idClass::Event_Remove( void ) { + delete this; +} + +/* +================ +idClass::Event_SafeRemove +================ +*/ +void idClass::Event_SafeRemove( void ) { + // Forces the remove to be done at a safe time + PostEventMS( &EV_Remove, 0 ); +} + +// RAVEN BEGIN +// bdube: client entities +/* +================ +idClass::IsClient +================ +*/ +bool idClass::IsClient ( void ) const { + return false; +} + +/* +================ +idClass::GetDebugInfo +================ +*/ +void idClass::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { +} + +/* +================ +idClass::ProcessState +================ +*/ +stateResult_t idClass::ProcessState ( const rvStateFunc* state, const stateParms_t& parms ) { + return (this->*(state->function)) ( parms ); +} + +stateResult_t idClass::ProcessState ( const char* name, const stateParms_t& parms ) { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return (this->*(cls->stateCallbacks[i].function)) ( parms ); + } + } + } + + return SRESULT_ERROR; +} + +/* +================ +idClass::FindState +================ +*/ +const rvStateFunc* idClass::FindState ( const char* name ) const { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return &cls->stateCallbacks[i]; + } + } + } + + return NULL; +} + +/* +================ +idClass::RegisterClasses +================ +*/ +void idClass::RegisterClasses( void ) +{ +// jnewquist: Register subclasses explicitly so they aren't dead-stripped +#define REGISTER(name) void Register_##name(void); Register_##name(); + REGISTER(idAFAttachment); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Base); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_ClawFourFingers); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Generic); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Gibbable); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_SteamPipe); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Vehicle); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleFourWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleSixWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_WithAttachedHead); // ..\..\code\game\AFEntity.cpp + REGISTER(idAI); // ..\..\code\game\ai\AI_events.cpp + REGISTER(idActivator); // ..\..\code\game\Misc.cpp + REGISTER(idActor); // ..\..\code\game\Actor.cpp + REGISTER(idAnimated); // ..\..\code\game\Misc.cpp + REGISTER(idAnimatedEntity); // ..\..\code\game\Entity.cpp + REGISTER(idBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idBeam); // ..\..\code\game\Misc.cpp + REGISTER(idBobber); // ..\..\code\game\Mover.cpp + REGISTER(idBrittleFracture); // ..\..\code\game\BrittleFracture.cpp + REGISTER(idCamera); // ..\..\code\game\Camera.cpp + REGISTER(idCameraAnim); // ..\..\code\game\Camera.cpp + REGISTER(idCameraView); // ..\..\code\game\Camera.cpp + REGISTER(idChain); // ..\..\code\game\AFEntity.cpp + REGISTER(idClass); // ..\..\code\game\gamesys\Class.cpp + REGISTER(idCursor3D); // ..\..\code\game\GameEdit.cpp + REGISTER(idDamagable); // ..\..\code\game\Misc.cpp + REGISTER(idDoor); // ..\..\code\game\Mover.cpp + REGISTER(idEarthQuake); // ..\..\code\game\Misc.cpp + REGISTER(idElevator); // ..\..\code\game\Mover.cpp + REGISTER(idEntity); // ..\..\code\game\Entity.cpp + REGISTER(idExplodable); // ..\..\code\game\Misc.cpp + REGISTER(idExplodingBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idForce); // ..\..\code\game\physics\Force.cpp + REGISTER(idForceField); // ..\..\code\game\Misc.cpp + REGISTER(idForce_Constant); // ..\..\code\game\physics\Force_Constant.cpp + REGISTER(idForce_Drag); // ..\..\code\game\physics\Force_Drag.cpp + REGISTER(idForce_Field); // ..\..\code\game\physics\Force_Field.cpp + REGISTER(idForce_Spring); // ..\..\code\game\physics\Force_Spring.cpp + REGISTER(idFuncAASObstacle); // ..\..\code\game\Misc.cpp + REGISTER(idFuncAASPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncEmitter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncRadioChatter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncSplat); // ..\..\code\game\Misc.cpp + REGISTER(idGuidedProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idItem); // ..\..\code\game\Item.cpp + REGISTER(idItemPowerup); // ..\..\code\game\Item.cpp + REGISTER(idItemRemover); // ..\..\code\game\Item.cpp + REGISTER(idLight); // ..\..\code\game\Light.cpp + REGISTER(idLiquid); // ..\..\code\game\Misc.cpp + REGISTER(idLocationEntity); // ..\..\code\game\Misc.cpp + REGISTER(idLocationSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idMoveable); // ..\..\code\game\Moveable.cpp + REGISTER(idMoveableItem); // ..\..\code\game\Item.cpp + REGISTER(idMover); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Binary); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Periodic); // ..\..\code\game\Mover.cpp + REGISTER(idMultiModelAF); // ..\..\code\game\AFEntity.cpp + REGISTER(idObjective); // ..\..\code\game\Item.cpp + REGISTER(idObjectiveComplete); // ..\..\code\game\Item.cpp + REGISTER(idPathCorner); // ..\..\code\game\Misc.cpp + REGISTER(idPendulum); // ..\..\code\game\Mover.cpp + REGISTER(idPhantomObjects); // ..\..\code\game\Misc.cpp + REGISTER(idPhysics); // ..\..\code\game\physics\Physics.cpp + REGISTER(idPhysics_AF); // ..\..\code\game\physics\Physics_AF.cpp + REGISTER(idPhysics_Actor); // ..\..\code\game\physics\Physics_Actor.cpp + REGISTER(idPhysics_Base); // ..\..\code\game\physics\Physics_Base.cpp + REGISTER(idPhysics_Monster); // ..\..\code\game\physics\Physics_Monster.cpp + REGISTER(idPhysics_Parametric); // ..\..\code\game\physics\Physics_Parametric.cpp + REGISTER(idPhysics_Player); // ..\..\code\game\physics\Physics_Player.cpp + REGISTER(idPhysics_RigidBody); // ..\..\code\game\physics\Physics_RigidBody.cpp + REGISTER(idPhysics_Static); // ..\..\code\game\physics\Physics_Static.cpp + REGISTER(idPhysics_StaticMulti); // ..\..\code\game\physics\Physics_StaticMulti.cpp + REGISTER(idPlat); // ..\..\code\game\Mover.cpp + REGISTER(idPlayer); // ..\..\code\game\Player.cpp + REGISTER(idPlayerStart); // ..\..\code\game\Misc.cpp + REGISTER(idProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idRiser); // ..\..\code\game\Mover.cpp + REGISTER(idRotater); // ..\..\code\game\Mover.cpp + REGISTER(idSecurityCamera); // ..\..\code\game\SecurityCamera.cpp + REGISTER(idShaking); // ..\..\code\game\Misc.cpp + REGISTER(idSound); // ..\..\code\game\Sound.cpp + REGISTER(idSpawnableEntity); // ..\..\code\game\Misc.cpp + REGISTER(idSplinePath); // ..\..\code\game\Mover.cpp + REGISTER(idSpring); // ..\..\code\game\Misc.cpp + REGISTER(idStaticEntity); // ..\..\code\game\Misc.cpp + REGISTER(idTarget); // ..\..\code\game\Target.cpp + REGISTER(idTarget_CallObjectFunction); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Damage); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableLevelWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableStamina); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\EndLevel.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeEntity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeSoundClass); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Give); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveEmail); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveSecurity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LevelTrigger); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeIn); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeOut); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LockDoor); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Remove); // ..\..\code\game\Target.cpp + REGISTER(idTarget_RemoveWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SessionCommand); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetFov); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetGlobalShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetInfluence); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetKeyVal); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetModel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetPrimaryObjective); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderParm); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Show); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Tip); // ..\..\code\game\Target.cpp + REGISTER(idTarget_WaitForButton); // ..\..\code\game\Target.cpp + REGISTER(idTestModel); // ..\..\code\game\anim\Anim_Testmodel.cpp + REGISTER(idTextEntity); // ..\..\code\game\Misc.cpp + REGISTER(idThread); // ..\..\code\game\script\Script_Thread.cpp + REGISTER(idTrigger); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Count); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_EntityName); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Fade); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Hurt); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Multi); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Timer); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Touch); // ..\..\code\game\Trigger.cpp + REGISTER(idVacuumEntity); // ..\..\code\game\Misc.cpp + REGISTER(idVacuumSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idWorldspawn); // ..\..\code\game\WorldSpawn.cpp + REGISTER(rvAFAttractor); // ..\..\code\game\AFEntity.cpp + REGISTER(rvAIAvoid); // ..\..\code\game\ai\AI_Helper.cpp + REGISTER(rvAITactical); // ..\..\code\game\ai\AI_Tactical.cpp + REGISTER(rvAIMedic); // ..\..\code\game\ai\AI_Medic.cpp + REGISTER(rvAITether); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherRadius); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherBehind); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherClear); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomePassive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomeAggressive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITrigger); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvCTFAssaultPlayerStart); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCTF_AssaultPoint); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCameraPlayback); // ..\..\code\game\Camera.cpp + REGISTER(rvCameraPortalSky); // ..\..\code\game\Camera.cpp + REGISTER(rvClientCrawlEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEntity); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvClientModel); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvAnimatedClientEntity); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvClientAFEntity); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientAFAttachment); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientMoveable); // ..\..\code\game\client\ClientMoveable.cpp + REGISTER(rvClientPhysics); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvConveyor); // ..\..\code\game\Mover.cpp + REGISTER(rvDarkMatterProjectile); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvDebugJumpPoint); // ..\..\code\game\Misc.cpp + REGISTER(rvDriftingProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvEffect); // ..\..\code\game\Effect.cpp + REGISTER(rvFuncSaveGame); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_Static); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_SurfaceNormal); // ..\..\code\game\Misc.cpp + REGISTER(rvGravitySeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(rvHealingStation); // ..\..\code\game\Healing_Station.cpp + REGISTER(rvItemCTFFlag); // ..\..\code\game\Item.cpp + REGISTER(rvJumpPad); // ..\..\code\game\Misc.cpp + REGISTER(rvMIRVProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvModviewModel); // ..\..\code\game\GameEdit.cpp + REGISTER(rvPusher); // ..\..\code\game\Mover.cpp +#ifndef _MPBETA + REGISTER(rvMonsterBerserker); // ..\..\code\game\ai\Monster_Berserker.cpp + REGISTER(rvMonsterBossBuddy); // ..\..\code\game\ai\Monster_BossBuddy.cpp + REGISTER(rvMonsterBossMakron); // ..\..\code\game\ai\Monster_BossMakron.cpp + REGISTER(rvMonsterConvoyGround); // ..\..\code\game\ai\Monster_ConvoyGround.cpp + REGISTER(rvMonsterConvoyHover); // ..\..\code\game\ai\Monster_ConvoyHover.cpp + REGISTER(rvMonsterFailedTransfer); // ..\..\code\game\ai\Monster_FailedTransfer.cpp + REGISTER(rvMonsterFatty); // ..\..\code\game\ai\Monster_Fatty.cpp + REGISTER(rvMonsterGladiator); // ..\..\code\game\ai\Monster_Gladiator.cpp + REGISTER(rvMonsterGrunt); // ..\..\code\game\ai\Monster_Grunt.cpp + REGISTER(rvMonsterGunner); // ..\..\code\game\ai\Monster_Gunner.cpp + REGISTER(rvMonsterHarvester); // ..\..\code\game\ai\Monster_Harvester.cpp + REGISTER(rvMonsterHarvesterDispersal); // ..\..\code\game\ai\Monster_HarvesterDispersal.cpp + REGISTER(rvMonsterHeavyHoverTank); // ..\..\code\game\ai\Monster_HeavyHoverTank.cpp + REGISTER(rvMonsterIronMaiden); // ..\..\code\game\ai\Monster_IronMaiden.cpp + REGISTER(rvMonsterLightTank); // ..\..\code\game\ai\Monster_LightTank.cpp + REGISTER(rvMonsterNetworkGuardian); // ..\..\code\game\ai\Monster_NetworkGuardian.cpp + REGISTER(rvMonsterRepairBot); // ..\..\code\game\ai\Monster_RepairBot.cpp + REGISTER(rvMonsterScientist); // ..\..\code\game\ai\Monster_Scientist.cpp + REGISTER(rvMonsterSentry); // ..\..\code\game\ai\Monster_Sentry.cpp + REGISTER(rvMonsterSlimyTransfer); // ..\..\code\game\ai\Monster_SlimyTransfer.cpp + REGISTER(rvMonsterStroggMarine);// ..\..\code\game\ai\Monster_StroggMarine.cpp + REGISTER(rvMonsterStreamProtector); // ..\..\code\game\ai\Monster_StreamProtector.cpp + REGISTER(rvMonsterStroggFlyer); // ..\..\code\game\ai\Monster_StroggFlyer.cpp + REGISTER(rvMonsterStroggHover); // ..\..\code\game\ai\Monster_StroggHover.cpp + REGISTER(rvMonsterTeleportDropper); // ..\..\code\game\ai\Monster_TeleportDropper.cpp + REGISTER(rvMonsterTurret); // ..\..\code\game\ai\Monster_Turret.cpp + REGISTER(rvMonsterTurretFlying); // ..\..\code\game\ai\Monster_TurretFlying.cpp +#endif // !_MPBETA + REGISTER(rvObjectiveFailed); // ..\..\code\game\Item.cpp + REGISTER(rvPhysics_Particle); // ..\..\code\game\physics\Physics_Particle.cpp + REGISTER(rvPhysics_VehicleMonster); // ..\..\code\game\physics\Physics_VehicleMonster.cpp + REGISTER(rvPhysics_Spline); // ..\..\code\game\SplineMover.cpp + REGISTER(rvSpawner); // ..\..\code\game\spawner.cpp + REGISTER(rvSpawnerProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvSplineMover); // ..\..\code\game\SplineMover.cpp + // twhitaker: removed database support + // REGISTER(rvTarget_AddDatabaseEntry); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_AmmoStash); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_BossBattle); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_ExitAreaAlert); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_LaunchProjectile); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_Nailable); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_SecretArea); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_TetherAI); // ..\..\code\game\Target.cpp + REGISTER(rvTramCar); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Marine); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Strogg); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramGate); // ..\..\code\game\TramGate.cpp + REGISTER(rvVehicle); // ..\..\code\game\vehicle\Vehicle.cpp + REGISTER(rvVehicleAI); // ..\..\code\game\ai\VehicleAI.cpp + REGISTER(rvVehicleAnimated); // ..\..\code\game\vehicle\VehicleAnimated.cpp + REGISTER(rvVehicleDriver); // ..\..\code\game\vehicle\VehicleDriver.cpp + REGISTER(rvVehicleDropPod); // ..\..\code\game\vehicle\Vehicle_DropPod.cpp + REGISTER(rvVehicleHoverpad); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleLight); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleMonster); // ..\..\code\game\vehicle\VehicleMonster.cpp + REGISTER(rvVehiclePart); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleRigid); // ..\..\code\game\vehicle\VehicleRigid.cpp + REGISTER(rvVehicleSound); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleSpline); // ..\..\code\game\vehicle\VehicleSpline.cpp + REGISTER(rvVehicleStatic); // ..\..\code\game\vehicle\VehicleStatic.cpp + REGISTER(rvVehicleThruster); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleTurret); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleUserAnimated); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleWalker); // ..\..\code\game\vehicle\Vehicle_Walker.cpp + REGISTER(rvVehicleWeapon); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvViewWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeaponBlaster); // ..\..\code\game\weapon\WeaponBlaster.cpp + REGISTER(rvWeaponDarkMatterGun); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvWeaponGauntlet); // ..\..\code\game\weapon\WeaponGauntlet.cpp + REGISTER(rvWeaponGrenadeLauncher); // ..\..\code\game\weapon\WeaponGrenadeLauncher.cpp + REGISTER(rvWeaponHyperblaster); // ..\..\code\game\weapon\WeaponHyperblaster.cpp + REGISTER(rvWeaponLightningGun); // ..\..\code\game\weapon\WeaponLightningGun.cpp + REGISTER(rvWeaponMachinegun); // ..\..\code\game\weapon\WeaponMachinegun.cpp + REGISTER(rvWeaponNailgun); // ..\..\code\game\weapon\WeaponNailgun.cpp + REGISTER(rvWeaponRailgun); // ..\..\code\game\weapon\WeaponRailgun.cpp + REGISTER(rvWeaponRocketLauncher); // ..\..\code\game\weapon\WeaponRocketLauncher.cpp + REGISTER(rvWeaponShotgun); // ..\..\code\game\weapon\WeaponShotgun.cpp +// RITUAL BEGIN + REGISTER(riDeadZonePowerup); // ..\..\code\game\Item.cpp + REGISTER(WeaponNapalmGun); // ..\..\code\game\weapon\WeaponNapalmGun.cpp +// RITUAL END +#undef REGISTER +} + +// RAVEN END + diff --git a/src/game/gamesys/Class.h b/src/game/gamesys/Class.h new file mode 100644 index 0000000..061f7a8 --- /dev/null +++ b/src/game/gamesys/Class.h @@ -0,0 +1,550 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Base class for all game objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#ifndef __SYS_CLASS_H__ +#define __SYS_CLASS_H__ + +class idClass; +class idTypeInfo; + +// RAVEN BEGIN +extern const idEventDef EV_PostRestore; +// RAVEN END +extern const idEventDef EV_Remove; +extern const idEventDef EV_SafeRemove; + +typedef void ( idClass::*eventCallback_t )( void ); + +template< class Type > +struct idEventFunc { + const idEventDef *event; + eventCallback_t function; +}; + +// added & so gcc could compile this +#define EVENT( event, function ) { &( event ), ( void ( idClass::* )( void ) )( &function ) }, +#define END_CLASS { NULL, NULL } }; + +class idEventArg { +public: + int type; + int value; + + idEventArg() { type = D_EVENT_INTEGER; value = 0; }; + idEventArg( int data ) { type = D_EVENT_INTEGER; value = data; }; + idEventArg( float data ) { type = D_EVENT_FLOAT; value = *reinterpret_cast( &data ); }; + idEventArg( const idVec3 &data ) { type = D_EVENT_VECTOR; value = reinterpret_cast( &data ); }; + idEventArg( const idStr &data ) { type = D_EVENT_STRING; value = reinterpret_cast( data.c_str() ); }; + idEventArg( const char *data ) { type = D_EVENT_STRING; value = reinterpret_cast( data ); }; + idEventArg( const class idEntity *data ) { type = D_EVENT_ENTITY; value = reinterpret_cast( data ); }; + idEventArg( const trace_t *data ) { type = D_EVENT_TRACE; value = reinterpret_cast( data ); }; +}; + +class idAllocError : public idException { +public: + idAllocError( const char *text = "" ) : idException( text ) {} +}; + +/*********************************************************************** + + idClass + +***********************************************************************/ + +/* +================ +CLASS_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance concrete classes only. +================ +*/ +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +CLASS_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking and run-time instanciation. It also defines the list +of events that the class responds to. Take special care to ensure that the +proper superclass is indicated or the run-time type information will be +incorrect. Use this on concrete classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: Added states +// jnewquist: Use accessor for static class type +// mwhitlock: Dynamic memory consolidation +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + + +/* +================ +ABSTRACT_PROTOTYPE + +This macro must be included in the definition of any abstract subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +ABSTRACT_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking. It also defines the list of events that the class +responds to. Take special care to ensure that the proper superclass is +indicated or the run-time tyep information will be incorrect. Use this +on abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: added states +// jnewquist: Use accessor for static class type +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +typedef void ( idClass::*classSpawnFunc_t )( void ); + +class idSaveGame; +class idRestoreGame; + +class idClass { +public: + ABSTRACT_PROTOTYPE( idClass ); + +#ifdef ID_REDIRECT_NEWDELETE +#undef new +#endif + void * operator new( size_t ); + void * operator new( size_t s, int, int, char *, int ); + void operator delete( void * ); + void operator delete( void *, int, int, char *, int ); +#ifdef ID_REDIRECT_NEWDELETE +#define new ID_DEBUG_NEW +#endif + + virtual ~idClass(); + + void Spawn( void ); + void CallSpawn( void ); + bool IsType( const idTypeInfo &c ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *c ) const { return IsType(*c); } +// RAVEN END + const char * GetClassname( void ) const; + const char * GetSuperclass( void ) const; + void FindUninitializedMemory( void ); + + void Save( idSaveGame *savefile ) const {}; + void Restore( idRestoreGame *savefile ) {}; + + bool RespondsTo( const idEventDef &ev ) const; + +// RAVEN BEGIN +// bdube: states + stateResult_t ProcessState ( const rvStateFunc* state, const stateParms_t& parms ); + stateResult_t ProcessState ( const char* name, const stateParms_t& parms ); + const rvStateFunc* FindState ( const char* name ) const; + +// bdube: client entities + virtual bool IsClient ( void ) const; + +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + static void RegisterClasses( void ); +// RAVEN END + + bool PostEventMS( const idEventDef *ev, int time ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool PostEventSec( const idEventDef *ev, float time ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEvent( const idEventDef *ev ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEventArgPtr( const idEventDef *ev, int *data ); + void CancelEvents( const idEventDef *ev ); +// RAVEN BEGIN +// abahr: + bool EventIsPosted( const idEventDef *ev ) const; + + void Event_PostRestore( void ) {} +// RAVEN END + void Event_Remove( void ); + + // Static functions + static void Init( void ); + static void Shutdown( void ); + static idTypeInfo * GetClass( const char *name ); + static void DisplayInfo_f( const idCmdArgs &args ); + static void ListClasses_f( const idCmdArgs &args ); + static idClass * CreateInstance( const char *name ); + static int GetNumTypes( void ) { return types.Num(); } + static int GetTypeNumBits( void ) { return typeNumBits; } + static idTypeInfo * GetType( int num ); + +// RAVEN BEGIN +// jscott: for memory profiling + static size_t GetUsedMemory( void ) { return( memused ); } + +// bdube: debug info + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +private: + classSpawnFunc_t CallSpawnFunc( idTypeInfo *cls ); + + bool PostEventArgs( const idEventDef *ev, int time, int numargs, ... ); + bool ProcessEventArgs( const idEventDef *ev, int numargs, ... ); + + void Event_SafeRemove( void ); + + static bool initialized; + static idList types; + static idList typenums; + static int typeNumBits; + static int memused; + static int numobjects; + +// RAVEN BEGIN +// bdube: states + CLASS_STATES_PROTOTYPE(idClass); +// RAVEN END +}; + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +class idTypeInfo { +public: + const char * classname; + const char * superclass; + idClass * ( *CreateInstance )( void ); + void ( idClass::*Spawn )( void ); + void ( idClass::*Save )( idSaveGame *savefile ) const; + void ( idClass::*Restore )( idRestoreGame *savefile ); + +// RAVEN BEGIN +// bdube: added + rvStateFunc * stateCallbacks; +// RAVEN END + + idEventFunc * eventCallbacks; + eventCallback_t * eventMap; + idTypeInfo * super; + idTypeInfo * next; + bool freeEventMap; + int typeNum; + int lastChild; + + idHierarchy node; + + idTypeInfo( const char *classname, const char *superclass, + idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), void ( idClass::*Spawn )( void ), +// RAVEN BEGIN +// bdube: added + rvStateFunc *stateCallbacks, +// RAVEN END + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ); + ~idTypeInfo(); + + void Init( void ); + void Shutdown( void ); + + bool IsType( const idTypeInfo &superclass ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *superclass ) const { return IsType(*superclass); } +// RAVEN END + bool RespondsTo( const idEventDef &ev ) const; +}; + +/* +================ +idTypeInfo::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idTypeInfo::IsType( const idTypeInfo &type ) const { + return ( ( typeNum >= type.typeNum ) && ( typeNum <= type.lastChild ) ); +} + +/* +================ +idTypeInfo::RespondsTo +================ +*/ +ID_INLINE bool idTypeInfo::RespondsTo( const idEventDef &ev ) const { + assert( idEvent::initialized ); + if ( !eventMap[ ev.GetEventNum() ] ) { + // we don't respond to this event + return false; + } + + return true; +} + +/* +================ +idClass::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idClass::IsType( const idTypeInfo &superclass ) const { + idTypeInfo *subclass; + + subclass = GetType(); + return subclass->IsType( superclass ); +} + +/* +================ +idClass::RespondsTo +================ +*/ +ID_INLINE bool idClass::RespondsTo( const idEventDef &ev ) const { + const idTypeInfo *c; + + assert( idEvent::initialized ); + c = GetType(); + return c->RespondsTo( ev ); +} + +#endif /* !__SYS_CLASS_H__ */ diff --git a/src/game/gamesys/DebugGraph.cpp b/src/game/gamesys/DebugGraph.cpp new file mode 100644 index 0000000..b112e9b --- /dev/null +++ b/src/game/gamesys/DebugGraph.cpp @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idDebugGraph::idDebugGraph +================ +*/ +idDebugGraph::idDebugGraph() { + index = 0; +} + +/* +================ +idDebugGraph::SetNumSamples +================ +*/ +void idDebugGraph::SetNumSamples( int num ) { + index = 0; + samples.Clear(); + samples.SetNum( num ); + memset( samples.Ptr(), 0, samples.MemoryUsed() ); +} + +/* +================ +idDebugGraph::AddValue +================ +*/ +void idDebugGraph::AddValue( float value ) { + samples[ index ] = value; + index++; + if ( index >= samples.Num() ) { + index = 0; + } +} + +/* +================ +idDebugGraph::Draw +================ +*/ +void idDebugGraph::Draw( const idVec4 &color, float scale ) const { + int i; + float value1; + float value2; + idVec3 vec1; + idVec3 vec2; + + const idMat3 &axis = gameLocal.GetLocalPlayer()->viewAxis; + const idVec3 pos = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() + axis[ 1 ] * samples.Num() * 0.5f; + + value1 = samples[ index ] * scale; + for( i = 1; i < samples.Num(); i++ ) { + value2 = samples[ ( i + index ) % samples.Num() ] * scale; + + vec1 = pos + axis[ 2 ] * value1 - axis[ 1 ] * ( i - 1 ) + axis[ 0 ] * samples.Num(); + vec2 = pos + axis[ 2 ] * value2 - axis[ 1 ] * i + axis[ 0 ] * samples.Num(); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + gameRenderWorld->DebugLine( color, vec1, vec2, gameLocal.GetMSec ( ), false ); +// RAVEN END + value1 = value2; + } +} diff --git a/src/game/gamesys/DebugGraph.h b/src/game/gamesys/DebugGraph.h new file mode 100644 index 0000000..280ae14 --- /dev/null +++ b/src/game/gamesys/DebugGraph.h @@ -0,0 +1,37 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// DebugGraph.h + +class idDebugGraph { +public: + idDebugGraph(); + void SetNumSamples( int num ); + void AddValue( float value ); + void Draw( const idVec4 &color, float scale ) const; + +private: + idList samples; + int index; +}; diff --git a/src/game/gamesys/Event.cpp b/src/game/gamesys/Event.cpp new file mode 100644 index 0000000..2d254b0 --- /dev/null +++ b/src/game/gamesys/Event.cpp @@ -0,0 +1,837 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +sys_event.cpp + +Event are used for scheduling tasks and for linking script commands. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_EVENTSPERFRAME 8192 // Upped from 4096 +//#define CREATE_EVENT_CODE + +/*********************************************************************** + + idEventDef + +***********************************************************************/ + +idEventDef *idEventDef::eventDefList[MAX_EVENTS]; +int idEventDef::numEventDefs = 0; + +static bool eventError = false; +static char eventErrorMsg[ 128 ]; + +/* +================ +idEventDef::idEventDef +================ +*/ +idEventDef::idEventDef( const char *command, const char *formatspec, char returnType ) { + idEventDef *ev; + int i; + unsigned int bits; + + assert( command ); + assert( !idEvent::initialized ); + + // Allow NULL to indicate no args, but always store it as "" + // so we don't have to check for it. + if ( !formatspec ) { + formatspec = ""; + } + + this->name = command; + this->formatspec = formatspec; + this->returnType = returnType; + + numargs = strlen( formatspec ); + assert( numargs <= D_EVENT_MAXARGS ); + if ( numargs > D_EVENT_MAXARGS ) { + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Too many args for '%s' event.", name ); + return; + } + + // make sure the format for the args is valid, calculate the formatspecindex, and the offsets for each arg + bits = 0; + argsize = 0; + memset( argOffset, 0, sizeof( argOffset ) ); + for( i = 0; i < numargs; i++ ) { + argOffset[ i ] = argsize; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + bits |= 1 << i; + argsize += sizeof( float ); + break; + + case D_EVENT_INTEGER : + argsize += sizeof( int ); + break; + + case D_EVENT_VECTOR : + argsize += sizeof( idVec3 ); + break; + + case D_EVENT_STRING : + argsize += MAX_STRING_LEN; + break; + + case D_EVENT_ENTITY : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_ENTITY_NULL : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_TRACE : + argsize += sizeof( trace_t ) + MAX_STRING_LEN + sizeof( bool ); + break; + + default : + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Invalid arg format '%s' string for '%s' event.", formatspec, name ); + return; + break; + } + } + + // calculate the formatspecindex + formatspecIndex = ( 1 << ( numargs + D_EVENT_MAXARGS ) ) | bits; + + // go through the list of defined events and check for duplicates + // and mismatched format strings + eventnum = numEventDefs; + for( i = 0; i < eventnum; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( command, ev->name ) == 0 ) { + if ( idStr::Cmp( formatspec, ev->formatspec ) != 0 ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing format strings ('%s'!='%s').", + command, formatspec, ev->formatspec ); + return; + } + + if ( ev->returnType != returnType ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing return types ('%c'!='%c').", + command, returnType, ev->returnType ); + return; + } + // Don't bother putting the duplicate event in list. + eventnum = ev->eventnum; + return; + } + } + + ev = this; + + if ( numEventDefs >= MAX_EVENTS ) { + eventError = true; + sprintf( eventErrorMsg, "numEventDefs >= MAX_EVENTS" ); + return; + } + eventDefList[numEventDefs] = ev; + numEventDefs++; +} + +/* +================ +idEventDef::NumEventCommands +================ +*/ +int idEventDef::NumEventCommands( void ) { + return numEventDefs; +} + +/* +================ +idEventDef::GetEventCommand +================ +*/ +const idEventDef *idEventDef::GetEventCommand( int eventnum ) { + return eventDefList[ eventnum ]; +} + +/* +================ +idEventDef::FindEvent +================ +*/ +const idEventDef *idEventDef::FindEvent( const char *name ) { + idEventDef *ev; + int num; + int i; + + assert( name ); + + num = numEventDefs; + for( i = 0; i < num; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( name, ev->name ) == 0 ) { + return ev; + } + } + + return NULL; +} + +/*********************************************************************** + + idEvent + +***********************************************************************/ + +static idLinkList FreeEvents; +static idLinkList EventQueue; +static idEvent EventPool[ MAX_EVENTS ]; + +bool idEvent::initialized = false; + +idDynamicBlockAlloc idEvent::eventDataAllocator; + +/* +================ +idEvent::~idEvent() +================ +*/ +idEvent::~idEvent() { + Free(); +} + + +void idEvent::WriteDebugInfo( void ) { + idEvent *event; + int count = 0; + + idFile *FH = fileSystem->OpenFileAppend( "idEvents.txt" ); + + FH->Printf( "Num Events = %d\n", EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + count++; + + FH->Printf( "%d. %d - %s - %s - %s\n", count, event->time, event->eventdef->GetName(), event->typeinfo->classname, event->object->GetClassname() ); + + event = event->eventNode.Next(); + } + + FH->Printf( "\n\n" ); + fileSystem->CloseFile( FH ); +} + +/* +================ +idEvent::Alloc +================ +*/ +idEvent *idEvent::Alloc( const idEventDef *evdef, int numargs, va_list args ) { + idEvent *ev; + size_t size; + const char *format; + idEventArg *arg; + byte *dataPtr; + int i; + const char *materialName; + + if ( FreeEvents.IsListEmpty() ) { + WriteDebugInfo( ); + gameLocal.Error( "idEvent::Alloc : No more free events for '%s' event.", evdef->GetName() ); + } + + ev = FreeEvents.Next(); + ev->eventNode.Remove(); + + ev->eventdef = evdef; + + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::Alloc : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + size = evdef->GetArgSize(); + if ( size ) { + ev->data = eventDataAllocator.Alloc( size ); + memset( ev->data, 0, size ); + } else { + ev->data = NULL; + } + + format = evdef->GetArgFormat(); + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) || ( format[ i ] == D_EVENT_TRACE ) ) { + // when NULL is passed in for an entity or trace, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + dataPtr = &ev->data[ evdef->GetArgOffset( i ) ]; + + switch( format[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + *reinterpret_cast( dataPtr ) = arg->value; + break; + + case D_EVENT_VECTOR : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = *reinterpret_cast( arg->value ); + } + break; + + case D_EVENT_STRING : + if ( arg->value ) { + idStr::Copynz( reinterpret_cast( dataPtr ), reinterpret_cast( arg->value ), MAX_STRING_LEN ); + } + break; + +// RAVEN BEGIN +// abahr: type checking change as per Jim D. +// jshepard: TODO FIXME HACK this never ever produces desired, positive results. Events should be built to prepare for null entities, especially when dealing with +// script events. This will throw a warning, and events should be prepared to deal with null entities. + case D_EVENT_ENTITY : + if ( reinterpret_cast( arg->value ) == NULL ) { + gameLocal.Warning( "idEvent::Alloc : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, evdef->GetName() ); + } + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; +//RAVEN END + + case D_EVENT_TRACE : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = true; + *reinterpret_cast( dataPtr + sizeof( bool ) ) = *reinterpret_cast( arg->value ); + + // save off the material as a string since the pointer won't be valid in save games. + // since we save off the entire trace_t structure, if the material is NULL here, + // it will be NULL when we process it, so we don't need to save off anything in that case. + if ( reinterpret_cast( arg->value )->c.material ) { + materialName = reinterpret_cast( arg->value )->c.material->GetName(); + idStr::Copynz( reinterpret_cast( dataPtr + sizeof( bool ) + sizeof( trace_t ) ), materialName, MAX_STRING_LEN ); + } + } else { + *reinterpret_cast( dataPtr ) = false; + } + break; + + default : + gameLocal.Error( "idEvent::Alloc : Invalid arg format '%s' string for '%s' event.", format, evdef->GetName() ); + break; + } + } + + return ev; +} + +/* +================ +idEvent::CopyArgs +================ +*/ +void idEvent::CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ) { + int i; + const char *format; + idEventArg *arg; + + format = evdef->GetArgFormat(); + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::CopyArgs : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) ) { + // when NULL is passed in for an entity, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + data[ i ] = arg->value; + } +} + +/* +================ +idEvent::Free +================ +*/ +void idEvent::Free( void ) { + if ( data ) { + eventDataAllocator.Free( data ); + data = NULL; + } + + eventdef = NULL; + time = 0; + object = NULL; + typeinfo = NULL; + + eventNode.SetOwner( this ); + eventNode.AddToEnd( FreeEvents ); +} + +/* +================ +idEvent::Schedule +================ +*/ +void idEvent::Schedule( idClass *obj, const idTypeInfo *type, int time ) { + idEvent *event; + + assert( initialized ); + if ( !initialized ) { + return; + } + + object = obj; + typeinfo = type; + + // wraps after 24 days...like I care. ;) + this->time = gameLocal.time + time; + + eventNode.Remove(); + + event = EventQueue.Next(); + while( ( event != NULL ) && ( this->time >= event->time ) ) { + event = event->eventNode.Next(); + } + + if ( event ) { + eventNode.InsertBefore( event->eventNode ); + } else { + eventNode.AddToEnd( EventQueue ); + } +} + +/* +================ +idEvent::CancelEvents +================ +*/ +void idEvent::CancelEvents( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if ( event->object == obj ) { + if ( !evdef || ( evdef == event->eventdef ) ) { + event->Free(); + } + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEvent::EventIsPosted +================ +*/ +bool idEvent::EventIsPosted( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return false; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if( event->object == obj && evdef == event->eventdef ) { + return true; + } + } + + return false; +} +// RAVEN END + +/* +================ +idEvent::ClearEventList +================ +*/ +void idEvent::ClearEventList( void ) { + int i; + + // + // initialize lists + // + FreeEvents.Clear(); + EventQueue.Clear(); + + // + // add the events to the free list + // + for( i = 0; i < MAX_EVENTS; i++ ) { + EventPool[ i ].Free(); + } +} + +/* +================ +idEvent::ServiceEvents +================ +*/ +void idEvent::ServiceEvents( void ) { + idEvent *event; + int num; + int args[ D_EVENT_MAXARGS ]; + int offset; + int i; + int numargs; + const char *formatspec; + trace_t **tracePtr; + const idEventDef *ev; + byte *data; + const char *materialName; + + num = 0; + while( !EventQueue.IsListEmpty() ) { + +#ifdef _XENON + session->PacifierUpdate(); +#endif + + event = EventQueue.Next(); + assert( event ); + + if ( event->time > gameLocal.time ) { + break; + } + + // copy the data into the local args array and set up pointers + ev = event->eventdef; + formatspec = ev->GetArgFormat(); + numargs = ev->GetNumArgs(); + for( i = 0; i < numargs; i++ ) { + offset = ev->GetArgOffset( i ); + data = event->data; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + args[ i ] = *reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_VECTOR : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_STRING : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + case D_EVENT_ENTITY : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + if ( *reinterpret_cast( &args[ i ] ) == NULL ) { + gameLocal.Warning( "idEvent::ServiceEvents : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, ev->GetName() ); + } + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + break; +// RAVEN END + case D_EVENT_TRACE : + tracePtr = reinterpret_cast( &args[ i ] ); + if ( *reinterpret_cast( &data[ offset ] ) ) { + *tracePtr = reinterpret_cast( &data[ offset + sizeof( bool ) ] ); + + if ( ( *tracePtr )->c.material != NULL ) { + // look up the material name to get the material pointer + materialName = reinterpret_cast( &data[ offset + sizeof( bool ) + sizeof( trace_t ) ] ); + ( *tracePtr )->c.material = declManager->FindMaterial( materialName, true ); + } + } else { + *tracePtr = NULL; + } + break; + + default: + gameLocal.Error( "idEvent::ServiceEvents : Invalid arg format '%s' string for '%s' event.", formatspec, ev->GetName() ); + } + } + + // the event is removed from its list so that if then object + // is deleted, the event won't be freed twice + event->eventNode.Remove(); + assert( event->object ); + + // savegames can trash the object, so do this for safety + if ( event->object ) { + event->object->ProcessEventArgPtr( ev, args ); + } + +#if 0 + // event functions may never leave return values on the FPU stack + // enable this code to check if any event call left values on the FPU stack + if ( !sys->FPU_StackIsEmpty() ) { + gameLocal.Error( "idEvent::ServiceEvents %d: %s left a value on the FPU stack\n", num, ev->GetName() ); + } +#endif + + // return the event to the free list + event->Free(); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > MAX_EVENTSPERFRAME ) { + gameLocal.Error( "Event overflow. Possible infinite loop in script." ); + } + } +} + +/* +================ +idEvent::Init +================ +*/ +void idEvent::Init( void ) { + gameLocal.Printf( "Initializing event system\n" ); + + if ( eventError ) { + gameLocal.Error( "%s", eventErrorMsg ); + } + +#ifdef CREATE_EVENT_CODE + void CreateEventCallbackHandler(); + CreateEventCallbackHandler(); + gameLocal.Error( "Wrote event callback handler" ); +#endif + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + ClearEventList(); + return; + } + + ClearEventList(); + + eventDataAllocator.Init(); + + gameLocal.Printf( "...%i event definitions\n", idEventDef::NumEventCommands() ); + + // the event system has started + initialized = true; +} + +/* +================ +idEvent::Shutdown +================ +*/ +void idEvent::Shutdown( void ) { + gameLocal.Printf( "Shutdown event system\n" ); + + if ( !initialized ) { + gameLocal.Printf( "...not started\n" ); + return; + } + + ClearEventList(); + + eventDataAllocator.Shutdown(); + + // say it is now shutdown + initialized = false; +} + +/* +================ +idEvent::Save +================ +*/ +void idEvent::Save( idSaveGame *savefile ) { + idEvent *event; + + savefile->WriteInt( EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + savefile->WriteInt( event->time ); + savefile->WriteString( event->eventdef->GetName() ); + savefile->WriteString( event->typeinfo->classname ); + savefile->WriteObject( event->object ); + savefile->WriteInt( event->eventdef->GetArgSize() ); + savefile->Write( event->data, event->eventdef->GetArgSize() ); + + event = event->eventNode.Next(); + } +} + +/* +================ +idEvent::Restore +================ +*/ +void idEvent::Restore( idRestoreGame *savefile ) { + int i; + int num; + int argsize; + idStr name; + idEvent *event; + + savefile->ReadInt( num ); + + for ( i = 0; i < num; i++ ) { + if ( FreeEvents.IsListEmpty() ) { + gameLocal.Error( "idEvent::Restore : No more free events" ); + } + + event = FreeEvents.Next(); + event->eventNode.Remove(); + event->eventNode.AddToEnd( EventQueue ); + + savefile->ReadInt( event->time ); + + // read the event name + savefile->ReadString( name ); + event->eventdef = idEventDef::FindEvent( name ); + if ( !event->eventdef ) { + savefile->Error( "idEvent::Restore: unknown event '%s'", name.c_str() ); + } + + // read the classtype + savefile->ReadString( name ); + event->typeinfo = idClass::GetClass( name ); + if ( !event->typeinfo ) { + savefile->Error( "idEvent::Restore: unknown class '%s' on event '%s'", name.c_str(), event->eventdef->GetName() ); + } + + savefile->ReadObject( event->object ); + assert( event->object ); + + // read the args + savefile->ReadInt( argsize ); + if ( argsize != event->eventdef->GetArgSize() ) { + savefile->Error( "idEvent::Restore: arg size (%d) doesn't match saved arg size(%d) on event '%s'", event->eventdef->GetArgSize(), argsize, event->eventdef->GetName() ); + } + if ( argsize ) { + event->data = eventDataAllocator.Alloc( argsize ); + savefile->Read( event->data, argsize ); + } else { + event->data = NULL; + } + } +} + +#ifdef CREATE_EVENT_CODE +/* +================ +CreateEventCallbackHandler +================ +*/ +void CreateEventCallbackHandler( void ) { + int num; + int count; + int i, j, k; + char argString[ D_EVENT_MAXARGS + 1 ]; + idStr string1; + idStr string2; + idFile *file; + + file = fileSystem->OpenFileWrite( "Callbacks.cpp" ); + + file->Printf( "// generated file - see CREATE_EVENT_CODE\n\n" ); + + for( i = 1; i <= D_EVENT_MAXARGS; i++ ) { + + file->Printf( "\t/*******************************************************\n\n\t\t%d args\n\n\t*******************************************************/\n\n", i ); + + for ( j = 0; j < ( 1 << i ); j++ ) { + for ( k = 0; k < i; k++ ) { + argString[ k ] = j & ( 1 << k ) ? 'f' : 'i'; + } + argString[ i ] = '\0'; + + string1.Empty(); + string2.Empty(); + + for( k = 0; k < i; k++ ) { + if ( j & ( 1 << k ) ) { + string1 += "const float"; + string2 += va( "*( float * )&data[ %d ]", k ); + } else { + string1 += "const int"; + string2 += va( "data[ %d ]", k ); + } + + if ( k < i - 1 ) { + string1 += ", "; + string2 += ", "; + } + } + + file->Printf( "\tcase %d :\n\t\ttypedef void ( idClass::*eventCallback_%s_t )( %s );\n", ( 1 << ( i + D_EVENT_MAXARGS ) ) + j, argString, string1.c_str() ); + file->Printf( "\t\t( this->*( eventCallback_%s_t )callback )( %s );\n\t\tbreak;\n\n", argString, string2.c_str() ); + + } + } + + fileSystem->CloseFile( file ); +} + +#endif diff --git a/src/game/gamesys/Event.h b/src/game/gamesys/Event.h new file mode 100644 index 0000000..8e04d84 --- /dev/null +++ b/src/game/gamesys/Event.h @@ -0,0 +1,208 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +sys_event.h + +Event are used for scheduling tasks and for linking script commands. +*/ +#ifndef __SYS_EVENT_H__ +#define __SYS_EVENT_H__ + +#define D_EVENT_MAXARGS 8 // if changed, enable the CREATE_EVENT_CODE define in Event.cpp to generate switch statement for idClass::ProcessEventArgPtr. + // running the game will then generate c:\doom\base\events.txt, the contents of which should be copied into the switch statement. + +#define D_EVENT_VOID ( ( char )0 ) +#define D_EVENT_INTEGER 'd' +#define D_EVENT_FLOAT 'f' +#define D_EVENT_VECTOR 'v' +#define D_EVENT_STRING 's' +#define D_EVENT_ENTITY 'e' +#define D_EVENT_ENTITY_NULL 'E' // event can handle NULL entity pointers +#define D_EVENT_TRACE 't' + +#define MAX_EVENTS 8192 // Upped from 4096 + +class idClass; +class idTypeInfo; + +class idEventDef { +private: + const char *name; + const char *formatspec; + unsigned int formatspecIndex; + int returnType; + int numargs; + size_t argsize; + int argOffset[ D_EVENT_MAXARGS ]; + int eventnum; + const idEventDef * next; + + static idEventDef * eventDefList[MAX_EVENTS]; + static int numEventDefs; + +public: + idEventDef( const char *command, const char *formatspec = NULL, char returnType = 0 ); + + const char *GetName( void ) const; + const char *GetArgFormat( void ) const; + unsigned int GetFormatspecIndex( void ) const; + char GetReturnType( void ) const; + int GetEventNum( void ) const; + int GetNumArgs( void ) const; + size_t GetArgSize( void ) const; + int GetArgOffset( int arg ) const; + + static int NumEventCommands( void ); + static const idEventDef *GetEventCommand( int eventnum ); + static const idEventDef *FindEvent( const char *name ); +}; + +class idSaveGame; +class idRestoreGame; + +class idEvent { +private: + const idEventDef *eventdef; + byte *data; + int time; + idClass *object; + const idTypeInfo *typeinfo; + + idLinkList eventNode; + + static idDynamicBlockAlloc eventDataAllocator; + + +public: + static bool initialized; + + ~idEvent(); + + static void WriteDebugInfo( void ); + static idEvent *Alloc( const idEventDef *evdef, int numargs, va_list args ); + static void CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ); + + void Free( void ); + void Schedule( idClass *object, const idTypeInfo *cls, int time ); + byte *GetData( void ); + + static void CancelEvents( const idClass *obj, const idEventDef *evdef = NULL ); +// RAVEN BEGIN +// abahr: + static bool EventIsPosted( const idClass *obj, const idEventDef *evdef ); +// RAVEN END + static void ClearEventList( void ); + static void ServiceEvents( void ); + static void Init( void ); + static void Shutdown( void ); + + // save games + static void Save( idSaveGame *savefile ); // archives object for save game file + static void Restore( idRestoreGame *savefile ); // unarchives object from save game file +}; + +/* +================ +idEvent::GetData +================ +*/ +ID_INLINE byte *idEvent::GetData( void ) { + return data; +} + +/* +================ +idEventDef::GetName +================ +*/ +ID_INLINE const char *idEventDef::GetName( void ) const { + return name; +} + +/* +================ +idEventDef::GetArgFormat +================ +*/ +ID_INLINE const char *idEventDef::GetArgFormat( void ) const { + return formatspec; +} + +/* +================ +idEventDef::GetFormatspecIndex +================ +*/ +ID_INLINE unsigned int idEventDef::GetFormatspecIndex( void ) const { + return formatspecIndex; +} + +/* +================ +idEventDef::GetReturnType +================ +*/ +ID_INLINE char idEventDef::GetReturnType( void ) const { + return returnType; +} + +/* +================ +idEventDef::GetNumArgs +================ +*/ +ID_INLINE int idEventDef::GetNumArgs( void ) const { + return numargs; +} + +/* +================ +idEventDef::GetArgSize +================ +*/ +ID_INLINE size_t idEventDef::GetArgSize( void ) const { + return argsize; +} + +/* +================ +idEventDef::GetArgOffset +================ +*/ +ID_INLINE int idEventDef::GetArgOffset( int arg ) const { + assert( ( arg >= 0 ) && ( arg < D_EVENT_MAXARGS ) ); + return argOffset[ arg ]; +} + +/* +================ +idEventDef::GetEventNum +================ +*/ +ID_INLINE int idEventDef::GetEventNum( void ) const { + return eventnum; +} + +#endif /* !__SYS_EVENT_H__ */ diff --git a/src/game/gamesys/NoGameTypeInfo.h b/src/game/gamesys/NoGameTypeInfo.h new file mode 100644 index 0000000..1fb166a --- /dev/null +++ b/src/game/gamesys/NoGameTypeInfo.h @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAMETYPEINFO_H__ +#define __GAMETYPEINFO_H__ + +/* +=================================================================================== + + This file has been generated with the Type Info Generator v1.0 (c) 2004 id Software + +=================================================================================== +*/ + +typedef struct { + const char * name; + const char * type; + const char * value; +} constantInfo_t; + +typedef struct { + const char * name; + int value; +} enumValueInfo_t; + +typedef struct { + const char * typeName; + const enumValueInfo_t * values; +} enumTypeInfo_t; + +typedef struct { + const char * type; + const char * name; + int offset; + int size; +} classVariableInfo_t; + +typedef struct { + const char * typeName; + const char * superType; + int size; + const classVariableInfo_t * variables; +} classTypeInfo_t; + + +static constantInfo_t constantInfo[] = { + { NULL, NULL, NULL } +}; + +static enumTypeInfo_t enumTypeInfo[] = { + { NULL, NULL } +}; + +static classTypeInfo_t classTypeInfo[] = { + { NULL, NULL, 0, NULL } +}; + +#endif /* !__GAMETYPEINFO_H__ */ diff --git a/src/game/gamesys/SaveGame.cpp b/src/game/gamesys/SaveGame.cpp new file mode 100644 index 0000000..5ff09bf --- /dev/null +++ b/src/game/gamesys/SaveGame.cpp @@ -0,0 +1,2125 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +Save game related helper classes. + +Save games are implemented in two classes, idSaveGame and idRestoreGame, that implement write/read functions for +common types. They're passed in to each entity and object for them to archive themselves. Each class +implements save/restore functions for it's own data. When restoring, all the objects are instantiated, +then the restore function is called on each, superclass first, then subclasses. + +Pointers are restored by saving out an object index for each unique object pointer and adding them to a list of +objects that are to be saved. Restore instantiates all the objects in the list before calling the Restore function +on each object so that the pointers returned are valid. No object's restore function should rely on any other objects +being fully instantiated until after the restore process is complete. Post restore fixup should be done by posting +events with 0 delay. + +The savegame header will have the Game Name, Version, Map Name, and Player Persistent Info. + +Changes in version make savegames incompatible, and the game will start from the beginning of the level with +the player's persistent info. + +Changes to classes that don't need to break compatibilty can use the build number as the savegame version. +Later versions are responsible for restoring from previous versions by ignoring any unused data and initializing +variables that weren't in previous versions with safe information. + +At the head of the save game is enough information to restore the player to the beginning of the level should the +file be unloadable in some way (for example, due to script changes). +*/ + +// RAVEN BEGIN +// jscott: sanity length check for strings +#define MAX_PRINT_MSG 4096 +// RAVEN END + +/* +================ +idSaveGame::idSaveGame() +================ +*/ +idSaveGame::idSaveGame( idFile *savefile ) { + + file = savefile; + + // Put NULL at the start of the list so we can skip over it. + objects.Clear(); + objects.Append( NULL ); +} + +/* +================ +idSaveGame::~idSaveGame() +================ +*/ +idSaveGame::~idSaveGame( void ) { + if ( objects.Num() ) { + Close(); + } +} + +/* +================ +idSaveGame::Close +================ +*/ +void idSaveGame::Close( void ) { + int i; + + WriteSoundCommands(); + + // read trace models + idClipModel::SaveTraceModels( this ); + + for( i = 1; i < objects.Num(); i++ ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( objects[ i ]->GetType(), objects[ i ] ); + } + + objects.Clear(); + +#ifdef ID_DEBUG_MEMORY +// RAVEN BEGIN +// jscott: don't use type info +// idStr gameState = file->GetName(); +// gameState.StripFileExtension(); +// WriteGameState_f( idCmdArgs( va( "test %s_save", gameState.c_str() ), false ) ); +// RAVEN END +#endif +} + +/* +================ +idSaveGame::WriteObjectList +================ +*/ +void idSaveGame::WriteObjectList( void ) { + int i; + + WriteInt( objects.Num() - 1 ); + for( i = 1; i < objects.Num(); i++ ) { + WriteString( objects[ i ]->GetClassname() ); + } +} + +/* +================ +idSaveGame::CallSave_r +================ +*/ +void idSaveGame::CallSave_r( const idTypeInfo *cls, const idClass *obj ) { + if ( cls->super ) { + CallSave_r( cls->super, obj ); + if ( cls->super->Save == cls->Save ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + WriteSyncId(); + ( obj->*cls->Save )( this ); + WriteSyncId(); +} + +/* +================ +idSaveGame::AddObject +================ +*/ +void idSaveGame::AddObject( const idClass *obj ) { + objects.AddUnique( obj ); +} + +/* +================ +idSaveGame::WriteSyncId +================ +*/ +void idSaveGame::WriteSyncId( void ) { + file->WriteSyncId(); +} + +/* +================ +idSaveGame::Write +================ +*/ +void idSaveGame::Write( const void *buffer, int len ) { + file->Write( buffer, len ); +} + +/* +================ +idSaveGame::WriteInt +================ +*/ +void idSaveGame::WriteInt( const int value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteJoint +================ +*/ +void idSaveGame::WriteJoint( const jointHandle_t value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteShort +================ +*/ +void idSaveGame::WriteShort( const short value ) { + file->WriteShort( value ); +} + +/* +================ +idSaveGame::WriteByte +================ +*/ +void idSaveGame::WriteByte( const byte value ) { + file->WriteUnsignedChar( value ); +} + +/* +================ +idSaveGame::WriteSignedChar +================ +*/ +void idSaveGame::WriteSignedChar( const signed char value ) { + file->WriteChar( value ); +} + +/* +================ +idSaveGame::WriteFloat +================ +*/ +void idSaveGame::WriteFloat( const float value ) { + file->WriteFloat( value ); +} + +/* +================ +idSaveGame::WriteBool +================ +*/ +void idSaveGame::WriteBool( const bool value ) { + file->WriteBool( value ); +} + +/* +================ +idSaveGame::WriteString +================ +*/ +void idSaveGame::WriteString( const char *string ) { + int len; + + len = strlen( string ); + +// RAVEN BEGIN +// jscott: added safety check for silly length strings + if( len < 0 || len >= MAX_PRINT_MSG ) { + + common->Error( "idSaveGame::WriteString invalid string length (%d)", len ); + } +// RAVEN END + + file->WriteString( string ); +} + +/* +================ +idSaveGame::WriteVec2 +================ +*/ +void idSaveGame::WriteVec2( const idVec2 &vec ) { + file->WriteVec2( vec ); +} + +/* +================ +idSaveGame::WriteVec3 +================ +*/ +void idSaveGame::WriteVec3( const idVec3 &vec ) { + file->WriteVec3( vec ); +} + +/* +================ +idSaveGame::WriteVec4 +================ +*/ +void idSaveGame::WriteVec4( const idVec4 &vec ) { + file->WriteVec4( vec ); +} + +/* +================ +idSaveGame::WriteVec5 +================ +*/ +void idSaveGame::WriteVec5( const idVec5 &vec ) { + file->WriteVec5( vec ); +} + +/* +================ +idSaveGame::WriteVec6 +================ +*/ +void idSaveGame::WriteVec6( const idVec6 &vec ) { + file->WriteVec6( vec ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteBounds( const idBounds &bounds ) { + file->Write( &bounds, sizeof( bounds ) ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteWinding( const idWinding &w ) +{ + int i, num; + num = w.GetNumPoints(); + WriteInt( num ); + for ( i = 0; i < num; i++ ) { + WriteVec5( w[i] ); + } +} + + +/* +================ +idSaveGame::WriteMat3 +================ +*/ +void idSaveGame::WriteMat3( const idMat3 &mat ) { + file->WriteMat3( mat ); +} + +/* +================ +idSaveGame::WriteAngles +================ +*/ +void idSaveGame::WriteAngles( const idAngles &angles ) { + file->Write( &angles, sizeof( angles ) ); +} + +/* +================ +idSaveGame::WriteObject +================ +*/ +void idSaveGame::WriteObject( const idClass *obj ) { + int index; + + index = objects.FindIndex( obj ); + if ( index < 0 ) { + gameLocal.DPrintf( "idSaveGame::WriteObject - WriteObject FindIndex failed\n" ); + + // Use the NULL index + index = 0; + } + + WriteInt( index ); +} + +/* +================ +idSaveGame::WriteStaticObject +================ +*/ +void idSaveGame::WriteStaticObject( const idClass &obj ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( obj.GetType(), &obj ); +} + +/* +================ +idSaveGame::WriteDict +================ +*/ +void idSaveGame::WriteDict( const idDict *dict ) { + int num; + int i; + const idKeyValue *kv; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !dict ) { + WriteInt( -1 ); + } else { + num = dict->GetNumKeyVals(); + WriteInt( num ); + for( i = 0; i < num; i++ ) { + kv = dict->GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + } + } +} + +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterial( const idMaterial *material ) { + if ( !material ) { + WriteString( "" ); + } else { + WriteString( material->GetName() ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterialType ( const rvDeclMatType* materialType ) { + if ( !materialType ) { + WriteString( "" ); + } else { + WriteString( materialType->GetName() ); + } +} + +/* +================ +idSaveGame::WriteTable +================ +*/ +void idSaveGame::WriteTable ( const idDeclTable* table ) { + if ( !table ) { + WriteString( "" ); + } else { + WriteString( table->GetName() ); + } +} +// RAVEN END + +/* +================ +idSaveGame::WriteSkin +================ +*/ +void idSaveGame::WriteSkin( const idDeclSkin *skin ) { + if ( !skin ) { + WriteString( "" ); + } else { + WriteString( skin->GetName() ); + } +} + +/* +================ +idSaveGame::WriteModelDef +================ +*/ +void idSaveGame::WriteModelDef( const idDeclModelDef *modelDef ) { + if ( !modelDef ) { + WriteString( "" ); + } else { + WriteString( modelDef->GetName() ); + } +} + +/* +================ +idSaveGame::WriteSoundShader +================ +*/ +void idSaveGame::WriteSoundShader( const idSoundShader *shader ) { + const char *name; + + if ( !shader ) { + WriteString( "" ); + } else { + name = shader->GetName(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteModel +================ +*/ +void idSaveGame::WriteModel( const idRenderModel *model ) { + const char *name; + + if ( !model ) { + WriteString( "" ); + } else { + name = model->Name(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteUserInterface +================ +*/ +void idSaveGame::WriteUserInterface( const idUserInterface *ui, bool unique ) { + const char *name; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !ui ) { + WriteString( "" ); + } else { + name = ui->Name(); + WriteString( name ); + WriteBool( unique ); + if ( ui->WriteToSaveGame( file ) == false ) { + gameLocal.Error( "idSaveGame::WriteUserInterface: ui failed to write properly\n" ); + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteInt( extrap.GetStartValue() ); + WriteInt( extrap.GetBaseSpeed() ); + WriteInt( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteFloat( extrap.GetStartValue() ); + WriteFloat( extrap.GetBaseSpeed() ); + WriteFloat( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteVec3( extrap.GetStartValue() ); + WriteVec3( extrap.GetBaseSpeed() ); + WriteVec3( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteRenderEffect +================ +*/ +void idSaveGame::WriteRenderEffect( const renderEffect_t &renderEffect ) { + WriteSyncId(); + + WriteFloat( renderEffect.startTime ); + WriteInt( renderEffect.suppressSurfaceInViewID ); + WriteInt( renderEffect.allowSurfaceInViewID ); + WriteInt( renderEffect.groupID ); + + WriteVec3( renderEffect.origin ); + WriteMat3( renderEffect.axis ); + + WriteVec3( renderEffect.gravity ); + WriteVec3( renderEffect.endOrigin ); + + WriteFloat( renderEffect.attenuation ); + WriteBool( renderEffect.hasEndOrigin ); + WriteBool( renderEffect.loop ); + WriteBool( renderEffect.ambient ); + WriteBool( renderEffect.inConnectedArea ); + WriteInt( renderEffect.weaponDepthHackInViewID ); + WriteFloat( renderEffect.modelDepthHack ); + + WriteInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + WriteFloat( renderEffect.shaderParms[ ix ] ); + } + + if( renderEffect.declEffect ) { + WriteString( renderEffect.declEffect->GetName() ); + } else { + WriteString( "" ); + } +} + +/* +================ +idSaveGame::WriteFrustum +================ +*/ +void idSaveGame::WriteFrustum( const idFrustum& frustum ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteVec3( frustum.GetOrigin() ); + WriteMat3( frustum.GetAxis() ); + WriteFloat( frustum.GetNearDistance() ); + WriteFloat( frustum.GetFarDistance() ); + WriteFloat( frustum.GetLeft() ); + WriteFloat( frustum.GetUp() ); +} + +/* +================ +idSaveGame::WriteRenderEntity +================ +*/ +void idSaveGame::WriteRenderEntity( const renderEntity_t &renderEntity ) { + int i; + + WriteSyncId(); + + WriteModel( renderEntity.hModel ); + + WriteInt( renderEntity.entityNum ); + WriteInt( renderEntity.bodyId ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + WriteBounds( renderEntity.bounds ); + + // callback is set by class's Restore function + + WriteInt( renderEntity.suppressSurfaceInViewID ); + WriteInt( renderEntity.suppressShadowInViewID ); + WriteInt( renderEntity.suppressShadowInLightID ); + WriteInt( renderEntity.allowSurfaceInViewID ); + + WriteInt( renderEntity.suppressSurfaceMask ); + + WriteVec3( renderEntity.origin ); + WriteMat3( renderEntity.axis ); + + WriteMaterial( renderEntity.customShader ); + WriteMaterial( renderEntity.referenceShader ); + WriteMaterial( renderEntity.overlayShader ); + WriteSkin( renderEntity.customSkin ); + + WriteInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + WriteUserInterface( renderEntity.gui[ i ], renderEntity.gui[ i ] ? renderEntity.gui[ i ]->IsUniqued() : false ); + } + + WriteFloat( renderEntity.modelDepthHack ); + + WriteBool( renderEntity.noSelfShadow ); + WriteBool( renderEntity.noShadow ); + WriteBool( renderEntity.noDynamicInteractions ); + WriteBool( renderEntity.forceUpdate ); + + WriteInt( renderEntity.weaponDepthHackInViewID ); + WriteFloat( renderEntity.shadowLODDistance ); + WriteInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idSaveGame::WriteRenderLight +================ +*/ +void idSaveGame::WriteRenderLight( const renderLight_t &renderLight ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteMat3( renderLight.axis ); + WriteVec3( renderLight.origin ); + + WriteInt( renderLight.suppressLightInViewID ); + WriteInt( renderLight.allowLightInViewID ); + WriteBool( renderLight.noShadows ); + WriteBool( renderLight.noSpecular ); + WriteBool( renderLight.noDynamicShadows ); + WriteBool( renderLight.pointLight ); + WriteBool( renderLight.parallel ); + WriteBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + WriteFloat( renderLight.detailLevel ); +// RAVEN END + + WriteVec3( renderLight.lightRadius ); + WriteVec3( renderLight.lightCenter ); + + WriteVec3( renderLight.target ); + WriteVec3( renderLight.right ); + WriteVec3( renderLight.up ); + WriteVec3( renderLight.start ); + WriteVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // WriteModel( renderLight.prelightModel ); + + WriteInt( renderLight.lightId ); + + WriteMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + WriteInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idSaveGame::WriteRefSound +================ +*/ +void idSaveGame::WriteRefSound( const refSound_t &refSound ) { +// RAVEN BEGIN + WriteSyncId(); + + WriteInt( refSound.referenceSoundHandle ); +// RAVEN END + WriteVec3( refSound.origin ); +// RAVEN BEGIN + WriteVec3( refSound.velocity ); +// RAVEN END + WriteInt( refSound.listenerId ); + WriteSoundShader( refSound.shader ); + WriteFloat( refSound.diversity ); + WriteBool( refSound.waitfortrigger ); + + WriteFloat( refSound.parms.minDistance ); + WriteFloat( refSound.parms.maxDistance ); + WriteFloat( refSound.parms.volume ); + WriteFloat( refSound.parms.shakes ); + WriteInt( refSound.parms.soundShaderFlags ); + WriteInt( refSound.parms.soundClass ); +} + +/* +================ +idSaveGame::WriteRenderView +================ +*/ +void idSaveGame::WriteRenderView( const renderView_t &view ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteInt( view.viewID ); + WriteInt( view.x ); + WriteInt( view.y ); + WriteInt( view.width ); + WriteInt( view.height ); + + WriteFloat( view.fov_x ); + WriteFloat( view.fov_y ); + WriteVec3( view.vieworg ); + WriteMat3( view.viewaxis ); + + WriteBool( view.cramZNear ); + + WriteInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + WriteFloat( view.shaderParms[ i ] ); + } +} + +/* +=================== +idSaveGame::WriteUsercmd +=================== +*/ +void idSaveGame::WriteUsercmd( const usercmd_t &usercmd ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteInt( usercmd.gameFrame ); + WriteInt( usercmd.gameTime ); + WriteInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + WriteShort( usercmd.buttons ); +// RAVEN END + WriteSignedChar( usercmd.forwardmove ); + WriteSignedChar( usercmd.rightmove ); + WriteSignedChar( usercmd.upmove ); + WriteShort( usercmd.angles[0] ); + WriteShort( usercmd.angles[1] ); + WriteShort( usercmd.angles[2] ); + WriteShort( usercmd.mx ); + WriteShort( usercmd.my ); + WriteSignedChar( usercmd.impulse ); + WriteByte( usercmd.flags ); + WriteInt( usercmd.sequence ); +} + +/* +=================== +idSaveGame::WriteContactInfo +=================== +*/ +void idSaveGame::WriteContactInfo( const contactInfo_t &contactInfo ) { + WriteInt( (int)contactInfo.type ); + WriteVec3( contactInfo.point ); + WriteVec3( contactInfo.normal ); + WriteFloat( contactInfo.dist ); + WriteInt( contactInfo.contents ); + WriteMaterial( contactInfo.material ); + WriteInt( contactInfo.modelFeature ); + WriteInt( contactInfo.trmFeature ); + WriteInt( contactInfo.entityNum ); + WriteInt( contactInfo.id ); + WriteMaterialType( contactInfo.materialType ); +} + +/* +=================== +idSaveGame::WriteTrace +=================== +*/ +void idSaveGame::WriteTrace( const trace_t &trace ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteFloat( trace.fraction ); + WriteVec3( trace.endpos ); + WriteMat3( trace.endAxis ); + WriteContactInfo( trace.c ); +} + +/* +=================== +idSaveGame::WriteClipModel +=================== +*/ +void idSaveGame::WriteClipModel( const idClipModel *clipModel ) { + if ( clipModel != NULL ) { + WriteBool( true ); + clipModel->Save( this ); + } else { + WriteBool( false ); + } +} + +/* +=================== +idSaveGame::WriteSoundCommands +=================== +*/ +void idSaveGame::WriteSoundCommands( void ) { + soundSystem->WriteToSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +====================== +idSaveGame::WriteBuildNumber +====================== +*/ +void idSaveGame::WriteBuildNumber( const int value ) { + WriteInt( value ); +} + + + + + + +/*********************************************************************** + + idRestoreGame + +***********************************************************************/ + +/* +================ +idRestoreGame::RestoreGame +================ +*/ +idRestoreGame::idRestoreGame( idFile *savefile ) { + file = savefile; +} + +/* +================ +idRestoreGame::~idRestoreGame() +================ +*/ +idRestoreGame::~idRestoreGame() { +} + +// RAVEN BEGIN +/* +================ +void idRestoreGame::CreateObjects +================ +*/ +void idRestoreGame::CreateObjects( void ) { + int i, num; + idStr classname; + idTypeInfo *type; + + ReadInt( num ); + + // create all the objects + objects.SetNum( num + 1 ); + memset( objects.Ptr(), 0, sizeof( objects[ 0 ] ) * objects.Num() ); + + for ( i = 1; i < objects.Num(); i++ ) { + ReadString( classname ); + type = idClass::GetClass( classname ); + if ( !type ) { + Error( "idRestoreGame::CreateObjects: Unknown class '%s'", classname.c_str() ); + } + objects[ i ] = type->CreateInstance(); + assert( objects[ i ] != NULL ); + } +} + +/* +================ +void idRestoreGame::RestoreObjects +================ +*/ +void idRestoreGame::RestoreObjects( void ) { + int i; + + ReadSoundCommands(); + + // read trace models + idClipModel::RestoreTraceModels( this ); + + // restore all the objects + for( i = 1; i < objects.Num(); i++ ) { + file->ReadSyncId( "Restore objects", objects[ i ]->GetClassname() ); + CallRestore_r( objects[ i ]->GetType(), objects[ i ] ); + } + + // regenerate render entities and render lights because are not saved + for( i = 1; i < objects.Num(); i++ ) { + if ( objects[ i ]->IsType( idEntity::GetClassType() ) ) { + idEntity *ent = static_cast( objects[ i ] ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} +// RAVEN END + +/* +==================== +void idRestoreGame::DeleteObjects +==================== +*/ +void idRestoreGame::DeleteObjects( void ) { + + // Remove the NULL object before deleting + objects.RemoveIndex( 0 ); + + objects.DeleteContents( true ); +} + +/* +================ +idRestoreGame::Error +================ +*/ +void idRestoreGame::Error( const char *fmt, ... ) { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN + // FIXME: this crashes. It now leaks, but that's better than crashing. + // The problem is that some entities delete attached ents that are also in this list. When this call gets to them + // it tries to delete an already deleted object +// objects.DeleteContents( true ); +// RAVEN END + + gameLocal.Error( "%s", text ); +} + +/* +================ +idRestoreGame::CallRestore_r +================ +*/ +void idRestoreGame::CallRestore_r( const idTypeInfo *cls, idClass *obj ) { + if ( cls->super ) { + CallRestore_r( cls->super, obj ); + if ( cls->super->Restore == cls->Restore ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + file->ReadSyncId( "Callrestore_r start ", cls->classname ); + ( obj->*cls->Restore )( this ); + file->ReadSyncId( "Callrestore_r end ", cls->classname ); +} + +/* +================ +idRestoreGame::Read +================ +*/ +void idRestoreGame::Read( void *buffer, int len ) { + file->Read( buffer, len ); +} + +/* +================ +idRestoreGame::ReadInt +================ +*/ +void idRestoreGame::ReadInt( int &value ) { + file->ReadInt( value ); +} + +/* +================ +idRestoreGame::ReadJoint +================ +*/ +void idRestoreGame::ReadJoint( jointHandle_t &value ) { + file->ReadInt( ( int &)value ); +} + +/* +================ +idRestoreGame::ReadShort +================ +*/ +void idRestoreGame::ReadShort( short &value ) { + file->ReadShort( value ); +} + +/* +================ +idRestoreGame::ReadByte +================ +*/ +void idRestoreGame::ReadByte( byte &value ) { + file->ReadUnsignedChar( value ); +} + +/* +================ +idRestoreGame::ReadSignedChar +================ +*/ +void idRestoreGame::ReadSignedChar( signed char &value ) { + file->ReadChar( ( char & )value ); +} + +/* +================ +idRestoreGame::ReadFloat +================ +*/ +void idRestoreGame::ReadFloat( float &value ) { + file->ReadFloat( value ); +} + +/* +================ +idRestoreGame::ReadBool +================ +*/ +void idRestoreGame::ReadBool( bool &value ) { + file->ReadBool( value ); +} + +/* +================ +idRestoreGame::ReadString +================ +*/ +void idRestoreGame::ReadString( idStr &string ) { +/* int len; + + ReadInt( len ); +// RAVEN BEGIN +// jscott: added max check - should be big enough + if ( len < 0 || len > MAX_PRINT_MSG ) { + Error( "idRestoreGame::ReadString: invalid length (%d)", len ); +// RAVEN END + } + + string.Fill( ' ', len );*/ + file->ReadString( string ); +} + +/* +================ +idRestoreGame::ReadVec2 +================ +*/ +void idRestoreGame::ReadVec2( idVec2 &vec ) { + file->ReadVec2( vec ); +} + +/* +================ +idRestoreGame::ReadVec3 +================ +*/ +void idRestoreGame::ReadVec3( idVec3 &vec ) { + file->ReadVec3( vec ); +} + +/* +================ +idRestoreGame::ReadVec4 +================ +*/ +void idRestoreGame::ReadVec4( idVec4 &vec ) { + file->ReadVec4( vec ); +} + +/* +================ +idRestoreGame::ReadVec5 +================ +*/ +void idRestoreGame::ReadVec5( idVec5 &vec ) { + file->ReadVec5( vec ); +} + +/* +================ +idRestoreGame::ReadVec6 +================ +*/ +void idRestoreGame::ReadVec6( idVec6 &vec ) { + file->ReadVec6( vec ); +} + +/* +================ +idRestoreGame::ReadBounds +================ +*/ +void idRestoreGame::ReadBounds( idBounds &bounds ) { + file->Read( &bounds, sizeof( bounds ) ); +} + +/* +================ +idRestoreGame::ReadWinding +================ +*/ +void idRestoreGame::ReadWinding( idWinding &w ) +{ + int i, num; + file->ReadInt( num ); + w.SetNumPoints( num ); + for ( i = 0; i < num; i++ ) { + file->ReadVec5( w[i] ); + } +} + +/* +================ +idRestoreGame::ReadMat3 +================ +*/ +void idRestoreGame::ReadMat3( idMat3 &mat ) { + file->ReadMat3( mat ); +} + +/* +================ +idRestoreGame::ReadAngles +================ +*/ +void idRestoreGame::ReadAngles( idAngles &angles ) { + file->Read( &angles, sizeof( angles ) ); +} + +/* +================ +idRestoreGame::ReadObject +================ +*/ +void idRestoreGame::ReadObject( idClass *&obj ) { + int index; + + ReadInt( index ); + if ( ( index < 0 ) || ( index >= objects.Num() ) ) { + Error( "idRestoreGame::ReadObject: invalid object index" ); + } + obj = objects[ index ]; +} + +/* +================ +idRestoreGame::ReadStaticObject +================ +*/ +void idRestoreGame::ReadStaticObject( idClass &obj ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadStaticObject", obj.GetClassname() ); +// RAVEN END + + CallRestore_r( obj.GetType(), &obj ); + +// RAVEN BEGIN + obj.PostEventMS( &EV_PostRestore, 0 ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadDict +================ +*/ +void idRestoreGame::ReadDict( idDict *dict ) { + int num; + int i; + idStr key; + idStr value; + +// RAVEN BEGIN + file->ReadSyncId( "ReadDict" ); +// RAVEN END + + ReadInt( num ); + + if ( num < 0 ) { + dict = NULL; + } else { + dict->Clear(); + for( i = 0; i < num; i++ ) { + ReadString( key ); + ReadString( value ); + dict->Set( key, value ); + } + } +} + +/* +================ +idRestoreGame::ReadMaterial +================ +*/ +void idRestoreGame::ReadMaterial( const idMaterial *&material ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + material = NULL; + } else { + material = declManager->FindMaterial( name ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idRestoreGame::ReadMaterialType +================ +*/ +void idRestoreGame::ReadMaterialType ( const rvDeclMatType* &materialType ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + materialType = NULL; + } else { + materialType = declManager->FindMaterialType ( name ); + } +} + +/* +================ +idRestoreGame::ReadTable +================ +*/ +void idRestoreGame::ReadTable ( const idDeclTable* &table ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + table = NULL; + } else { + table = declManager->FindTable( name ); + } +} + +// RAVEN END + +/* +================ +idRestoreGame::ReadSkin +================ +*/ +void idRestoreGame::ReadSkin( const idDeclSkin *&skin ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( name ); + } +} + +/* +================ +idRestoreGame::ReadSoundShader +================ +*/ +void idRestoreGame::ReadSoundShader( const idSoundShader *&shader ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + shader = NULL; + } else { + shader = declManager->FindSound( name ); + } +} + +/* +================ +idRestoreGame::ReadModelDef +================ +*/ +void idRestoreGame::ReadModelDef( const idDeclModelDef *&modelDef ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + modelDef = NULL; + } else { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + } +} + +/* +================ +idRestoreGame::ReadModel +================ +*/ +void idRestoreGame::ReadModel( idRenderModel *&model ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + model = NULL; + } else { + model = renderModelManager->FindModel( name ); + } +} + +/* +================ +idRestoreGame::ReadUserInterface +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadUserInterface( idUserInterface *&ui, const idDict *args ) { +// RAVEN END + idStr name; + +// RAVEN BEGIN + file->ReadSyncId( "ReadUserInterface" ); +// RAVEN END + + ReadString( name ); + if ( !name.Length() ) { + ui = NULL; + } else { + bool unique; + ReadBool( unique ); + ui = uiManager->FindGui( name, true, unique ); + if ( ui ) { + if ( ui->ReadFromSaveGame( file ) == false ) { + Error( "idSaveGame::ReadUserInterface: ui failed to read properly\n" ); + } else { +// RAVEN BEGIN + UpdateGuiParms( ui, args ); +// RAVEN END + } + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + int startValue; + int baseSpeed; + int speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( baseSpeed ); + ReadInt( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + float startValue; + float baseSpeed; + float speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( baseSpeed ); + ReadFloat( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + idVec3 startValue; + idVec3 baseSpeed; + idVec3 speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( baseSpeed ); + ReadVec3( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} +/* +================ +idRestoreGame::ReadRenderEffect +================ +*/ +void idRestoreGame::ReadRenderEffect( renderEffect_t &renderEffect ) { + idStr name; + + file->ReadSyncId( "ReadRenderEffect" ); + + renderEffect.declEffect = NULL; + + ReadFloat( renderEffect.startTime ); + ReadInt( renderEffect.suppressSurfaceInViewID ); + ReadInt( renderEffect.allowSurfaceInViewID ); + ReadInt( renderEffect.groupID ); + + ReadVec3( renderEffect.origin ); + ReadMat3( renderEffect.axis ); + + ReadVec3( renderEffect.gravity ); + ReadVec3( renderEffect.endOrigin ); + + ReadFloat( renderEffect.attenuation ); + ReadBool( renderEffect.hasEndOrigin ); + ReadBool( renderEffect.loop ); + ReadBool( renderEffect.ambient ); + ReadBool( renderEffect.inConnectedArea ); + ReadInt( renderEffect.weaponDepthHackInViewID ); + ReadFloat( renderEffect.modelDepthHack ); + + ReadInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + ReadFloat( renderEffect.shaderParms[ ix ] ); + } + + ReadString( name ); + if( name.Length() ) { + renderEffect.declEffect = declManager->FindType( DECL_EFFECT, name ); + } +} + +/* +================ +idRestoreGame::ReadFrustum +================ +*/ +void idRestoreGame::ReadFrustum( idFrustum& frustum ) { + idVec3 origin; + idMat3 axis; + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; +// RAVEN BEGIN + file->ReadSyncId( "ReadFrustum" ); +// RAVEN END + ReadVec3( origin ); + frustum.SetOrigin( origin ); + + ReadMat3( axis ); + frustum.SetAxis( axis ); + + ReadFloat( dNear ); + ReadFloat( dFar ); + ReadFloat( dLeft ); + ReadFloat( dUp ); + frustum.SetSize( dNear, dFar, dLeft, dUp ); +} + +/* +================ +idRestoreGame::ReadRenderEntity +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ) { +// RAVEN END + int i; + + file->ReadSyncId( "ReadRenderEntity" ); + + ReadModel( renderEntity.hModel ); + + ReadInt( renderEntity.entityNum ); + ReadInt( renderEntity.bodyId ); + + ReadBounds( renderEntity.bounds ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + // callback is set by class's Restore function + renderEntity.callback = NULL; + renderEntity.callbackData = NULL; + + ReadInt( renderEntity.suppressSurfaceInViewID ); + ReadInt( renderEntity.suppressShadowInViewID ); + ReadInt( renderEntity.suppressShadowInLightID ); + ReadInt( renderEntity.allowSurfaceInViewID ); + + ReadInt( renderEntity.suppressSurfaceMask ); + + ReadVec3( renderEntity.origin ); + ReadMat3( renderEntity.axis ); + + ReadMaterial( renderEntity.customShader ); + ReadMaterial( renderEntity.referenceShader ); + ReadMaterial( renderEntity.overlayShader ); + ReadSkin( renderEntity.customSkin ); + + ReadInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { +// RAVEN BEGIN + ReadUserInterface( renderEntity.gui[ i ], args ); +// RAVEN END + } + + // idEntity will restore "cameraTarget", which will be used in idEntity::Present to restore the remoteRenderView + renderEntity.remoteRenderView = NULL; + + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + + ReadFloat( renderEntity.modelDepthHack ); + + ReadBool( renderEntity.noSelfShadow ); + ReadBool( renderEntity.noShadow ); + ReadBool( renderEntity.noDynamicInteractions ); + ReadBool( renderEntity.forceUpdate ); + + ReadInt( renderEntity.weaponDepthHackInViewID ); + ReadFloat( renderEntity.shadowLODDistance ); + ReadInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idRestoreGame::ReadRenderLight +================ +*/ +void idRestoreGame::ReadRenderLight( renderLight_t &renderLight ) { + int i; + + file->ReadSyncId( "ReadRenderLight" ); + + ReadMat3( renderLight.axis ); + ReadVec3( renderLight.origin ); + + ReadInt( renderLight.suppressLightInViewID ); + ReadInt( renderLight.allowLightInViewID ); + ReadBool( renderLight.noShadows ); + ReadBool( renderLight.noSpecular ); + ReadBool( renderLight.noDynamicShadows ); + ReadBool( renderLight.pointLight ); + ReadBool( renderLight.parallel ); + ReadBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + ReadFloat( renderLight.detailLevel ); +// RAVEN END + + ReadVec3( renderLight.lightRadius ); + ReadVec3( renderLight.lightCenter ); + + ReadVec3( renderLight.target ); + ReadVec3( renderLight.right ); + ReadVec3( renderLight.up ); + ReadVec3( renderLight.start ); + ReadVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // ReadModel( renderLight.prelightModel ); + renderLight.prelightModel = NULL; + + ReadInt( renderLight.lightId ); + + ReadMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + ReadInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadRefSound +================ +*/ +void idRestoreGame::ReadRefSound( refSound_t &refSound ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadRefSound" ); +// RAVEN END + + ReadInt( refSound.referenceSoundHandle ); + ReadVec3( refSound.origin ); +// RAVEN BEGIN + ReadVec3( refSound.velocity ); +// RAVEN END + ReadInt( refSound.listenerId ); + ReadSoundShader( refSound.shader ); + ReadFloat( refSound.diversity ); + ReadBool( refSound.waitfortrigger ); + + ReadFloat( refSound.parms.minDistance ); + ReadFloat( refSound.parms.maxDistance ); + ReadFloat( refSound.parms.volume ); + ReadFloat( refSound.parms.shakes ); + ReadInt( refSound.parms.soundShaderFlags ); + ReadInt( refSound.parms.soundClass ); +} + +/* +================ +idRestoreGame::ReadRenderView +================ +*/ +void idRestoreGame::ReadRenderView( renderView_t &view ) { + int i; + +// RAVEN BEGIN + file->ReadSyncId( "ReadRenderView" ); +// RAVEN END + + ReadInt( view.viewID ); + ReadInt( view.x ); + ReadInt( view.y ); + ReadInt( view.width ); + ReadInt( view.height ); + + ReadFloat( view.fov_x ); + ReadFloat( view.fov_y ); + ReadVec3( view.vieworg ); + ReadMat3( view.viewaxis ); + + ReadBool( view.cramZNear ); + + ReadInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + ReadFloat( view.shaderParms[ i ] ); + } +} + +/* +================= +idRestoreGame::ReadUsercmd +================= +*/ +void idRestoreGame::ReadUsercmd( usercmd_t &usercmd ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadUsercmd" ); +// RAVEN END + ReadInt( usercmd.gameFrame ); + ReadInt( usercmd.gameTime ); + ReadInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + ReadShort( usercmd.buttons ); +// RAVEN END + ReadSignedChar( usercmd.forwardmove ); + ReadSignedChar( usercmd.rightmove ); + ReadSignedChar( usercmd.upmove ); + ReadShort( usercmd.angles[0] ); + ReadShort( usercmd.angles[1] ); + ReadShort( usercmd.angles[2] ); + ReadShort( usercmd.mx ); + ReadShort( usercmd.my ); + ReadSignedChar( usercmd.impulse ); + ReadByte( usercmd.flags ); + ReadInt( usercmd.sequence ); +} + +/* +=================== +idRestoreGame::ReadContactInfo +=================== +*/ +void idRestoreGame::ReadContactInfo( contactInfo_t &contactInfo ) { + ReadInt( (int &)contactInfo.type ); + ReadVec3( contactInfo.point ); + ReadVec3( contactInfo.normal ); + ReadFloat( contactInfo.dist ); + ReadInt( contactInfo.contents ); + ReadMaterial( contactInfo.material ); + ReadInt( contactInfo.modelFeature ); + ReadInt( contactInfo.trmFeature ); + ReadInt( contactInfo.entityNum ); + ReadInt( contactInfo.id ); + ReadMaterialType( contactInfo.materialType ); +} + +/* +=================== +idRestoreGame::ReadTrace +=================== +*/ +void idRestoreGame::ReadTrace( trace_t &trace ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadTrace" ); +// RAVEN END + ReadFloat( trace.fraction ); + ReadVec3( trace.endpos ); + ReadMat3( trace.endAxis ); + ReadContactInfo( trace.c ); +} + +/* +===================== +idRestoreGame::ReadClipModel +===================== +*/ +void idRestoreGame::ReadClipModel( idClipModel *&clipModel ) { + bool restoreClipModel; + + ReadBool( restoreClipModel ); + if ( restoreClipModel ) { + clipModel = new idClipModel(); + clipModel->Restore( this ); + } else { + clipModel = NULL; + } +} + +/* +===================== +idRestoreGame::ReadSoundCommands +===================== +*/ +void idRestoreGame::ReadSoundCommands( void ) { + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + soundSystem->ReadFromSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +===================== +idRestoreGame::ReadBuildNumber +===================== +*/ +void idRestoreGame::ReadBuildNumber( void ) { + ReadInt( buildNumber ); +} + +/* +===================== +idRestoreGame::GetBuildNumber +===================== +*/ +int idRestoreGame::GetBuildNumber( void ) { + return buildNumber; +} + + + +void Cmd_CheckSave_f( const idCmdArgs &args ) +{ + idPlayer *lp = gameLocal.GetLocalPlayer(); + idFile *mp = fileSystem->GetNewFileMemory(); + idSaveGame sg( mp ); + + sg.CallSave_r( lp->GetType(), lp ); + + + mp->Rewind(); + idPlayer test; + idRestoreGame rg( mp ); + + rg.CallRestore_r( test.GetType(), &test ); +} + diff --git a/src/game/gamesys/SaveGame.h b/src/game/gamesys/SaveGame.h new file mode 100644 index 0000000..2773e68 --- /dev/null +++ b/src/game/gamesys/SaveGame.h @@ -0,0 +1,206 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SAVEGAME_H__ +#define __SAVEGAME_H__ + +/* + +Save game related helper classes. + +*/ + +const int INITIAL_RELEASE_BUILD_NUMBER = 1262; + +class idSaveGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idSaveGame( idFile *savefile ); + ~idSaveGame(); + + void Close( void ); + + void AddObject( const idClass *obj ); + void WriteObjectList( void ); + + void Write( const void *buffer, int len ); + void WriteInt( const int value ); + void WriteJoint( const jointHandle_t value ); + void WriteShort( const short value ); + void WriteByte( const byte value ); + void WriteSignedChar( const signed char value ); + void WriteFloat( const float value ); + void WriteBool( const bool value ); + void WriteString( const char *string ); + void WriteVec2( const idVec2 &vec ); + void WriteVec3( const idVec3 &vec ); + void WriteVec4( const idVec4 &vec ); + void WriteVec5( const idVec5 &vec ); + void WriteVec6( const idVec6 &vec ); + void WriteWinding( const idWinding &winding ); + void WriteBounds( const idBounds &bounds ); + void WriteMat3( const idMat3 &mat ); + void WriteAngles( const idAngles &angles ); + void WriteObject( const idClass *obj ); + void WriteStaticObject( const idClass &obj ); + void WriteDict( const idDict *dict ); + void WriteMaterial( const idMaterial *material ); + void WriteSkin( const idDeclSkin *skin ); +// RAVEN BEGIN +// jscott: not using +// void WriteParticle( const idDeclParticle *particle ); +// void WriteFX( const idDeclFX *fx ); +// RAVEN END + void WriteSoundShader( const idSoundShader *shader ); + void WriteModelDef( const class idDeclModelDef *modelDef ); + void WriteModel( const idRenderModel *model ); +// RAVEN BEGIN +// bdube: material type + void WriteMaterialType ( const rvDeclMatType* matType ); + void WriteTable ( const idDeclTable* table ); +// abahr + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteRenderEffect( const renderEffect_t &renderEffect ); + void WriteFrustum( const idFrustum& frustum ); + void WriteSyncId( void ); +// RAVEN END + void WriteUserInterface( const idUserInterface *ui, bool unique ); + void WriteRenderEntity( const renderEntity_t &renderEntity ); + void WriteRenderLight( const renderLight_t &renderLight ); + void WriteRefSound( const refSound_t &refSound ); + void WriteRenderView( const renderView_t &view ); + void WriteUsercmd( const usercmd_t &usercmd ); + void WriteContactInfo( const contactInfo_t &contactInfo ); + void WriteTrace( const trace_t &trace ); + void WriteClipModel( const class idClipModel *clipModel ); + void WriteSoundCommands( void ); + + void WriteBuildNumber( const int value ); + +protected: + idFile * file; + + idList objects; + + void CallSave_r( const idTypeInfo *cls, const idClass *obj ); +}; + +class idRestoreGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idRestoreGame( idFile *savefile ); + ~idRestoreGame(); + + void CreateObjects( void ); + void RestoreObjects( void ); + void DeleteObjects( void ); + + void Error( const char *fmt, ... ); + + void Read( void *buffer, int len ); + void ReadInt( int &value ); + void ReadJoint( jointHandle_t &value ); + void ReadShort( short &value ); + void ReadByte( byte &value ); + void ReadSignedChar( signed char &value ); + void ReadFloat( float &value ); + void ReadBool( bool &value ); + void ReadString( idStr &string ); + void ReadVec2( idVec2 &vec ); + void ReadVec3( idVec3 &vec ); + void ReadVec4( idVec4 &vec ); + void ReadVec5( idVec5 &vec ); + void ReadVec6( idVec6 &vec ); + void ReadWinding( idWinding &winding ); + void ReadBounds( idBounds &bounds ); + void ReadMat3( idMat3 &mat ); + void ReadAngles( idAngles &angles ); + void ReadObject( idClass *&obj ); + void ReadStaticObject( idClass &obj ); + void ReadDict( idDict *dict ); + void ReadMaterial( const idMaterial *&material ); + void ReadSkin( const idDeclSkin *&skin ); +// RAVEN BEGIN +// bdube: not using +// void ReadParticle( const idDeclParticle *&particle ); +// void ReadFX( const idDeclFX *&fx ); +// RAVEN END + void ReadSoundShader( const idSoundShader *&shader ); + void ReadModelDef( const idDeclModelDef *&modelDef ); + void ReadModel( idRenderModel *&model ); +// RAVEN BEGIN + void ReadUserInterface( idUserInterface *&ui, const idDict *args ); +// bdube: material type + void ReadMaterialType ( const rvDeclMatType* &matType ); + void ReadTable ( const idDeclTable* &table ); +// abahr + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadRenderEffect( renderEffect_t &renderEffect ); + void ReadFrustum( idFrustum& frustum ); + void ReadSyncId( const char *detail = "unspecified", const char *classname = NULL ) { file->ReadSyncId( detail, classname ); } + void ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ); +// RAVEN END + void ReadRenderLight( renderLight_t &renderLight ); + void ReadRefSound( refSound_t &refSound ); + void ReadRenderView( renderView_t &view ); + void ReadUsercmd( usercmd_t &usercmd ); + void ReadContactInfo( contactInfo_t &contactInfo ); + void ReadTrace( trace_t &trace ); + void ReadClipModel( idClipModel *&clipModel ); + void ReadSoundCommands( void ); + + void ReadBuildNumber( void ); + + // Used to retrieve the saved game buildNumber from within class Restore methods + int GetBuildNumber( void ); + +private: + int buildNumber; + + idFile * file; + + idList objects; + + void CallRestore_r( const idTypeInfo *cls, idClass *obj ); +}; + +#endif /* !__SAVEGAME_H__*/ diff --git a/src/game/gamesys/State.cpp b/src/game/gamesys/State.cpp new file mode 100644 index 0000000..33a3aaf --- /dev/null +++ b/src/game/gamesys/State.cpp @@ -0,0 +1,448 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const int HISTORY_COUNT = 50; + +/* +===================== +stateParms_t::Save +===================== +*/ +void stateParms_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( blendFrames ); + saveFile->WriteInt( time ); + saveFile->WriteInt( stage ); +} + +/* +===================== +stateParms_t::Restore +===================== +*/ +void stateParms_t::Restore( idRestoreGame *saveFile ) { + saveFile->ReadInt( blendFrames ); + saveFile->ReadInt( time ); + saveFile->ReadInt( stage ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( state->name ); + // TOSAVE: idLinkList node; + saveFile->WriteInt( flags ); + saveFile->WriteInt( delay ); + parms.Save( saveFile ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Restore( idRestoreGame *saveFile, const idClass* owner ) { + idStr name; + + saveFile->ReadString( name ); + state = owner->FindState( name ); + + saveFile->ReadInt( flags ); + saveFile->ReadInt( delay ); + parms.Restore( saveFile ); +} + +/* +===================== +rvStateThread::rvStateThread +===================== +*/ +rvStateThread::rvStateThread ( void ) { + owner = NULL; + insertAfter = NULL; + lastResult = SRESULT_DONE; + + states.Clear ( ); + interrupted.Clear ( ); + + memset ( &fl, 0, sizeof(fl) ); +} + +/* +===================== +rvStateThread::~rvStateThread +===================== +*/ +rvStateThread::~rvStateThread ( void ) { + Clear ( true ); +} + +/* +===================== +rvStateThread::SetOwner +===================== +*/ +void rvStateThread::SetOwner ( idClass* _owner ) { + owner = _owner; +} + +/* +===================== +rvStateThread::Post +===================== +*/ +stateResult_t rvStateThread::PostState ( const char* name, int blendFrames, int delay, int flags ) { + const rvStateFunc* func; + + // Make sure the state exists before queueing it + if ( NULL == (func = owner->FindState ( name ) ) ) { + return SRESULT_ERROR; + } + + stateCall_t* call; + call = new stateCall_t; + call->state = func; + call->delay = delay; + call->flags = flags; + call->parms.blendFrames = blendFrames; + call->parms.time = -1; + call->parms.stage = 0; + + call->node.SetOwner ( call ); + + if ( fl.executing && insertAfter ) { + call->node.InsertAfter ( insertAfter->node ); + } else { + call->node.AddToEnd ( states ); + } + + insertAfter = call; + + return SRESULT_OK; +} + +/* +===================== +rvStateThread::Set +===================== +*/ +stateResult_t rvStateThread::SetState ( const char* name, int blendFrames, int delay, int flags ) { + Clear ( ); + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::InterruptState +===================== +*/ +stateResult_t rvStateThread::InterruptState ( const char* name, int blendFrames, int delay, int flags ) { + stateCall_t* call; + + // Move all states to the front of the interrupted list in the same order + for ( call = states.Prev(); call; call = states.Prev() ) { + call->node.Remove ( ); + call->node.AddToFront ( interrupted ); + } + + // Nothing to insert after anymore + insertAfter = NULL; + fl.stateInterrupted = true; + + // Post the state now + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::CurrentStateIs +===================== +*/ +bool rvStateThread::CurrentStateIs( const char* name ) const { + return ( !IsIdle() ) ? owner->FindState(name) == GetState()->state : false; +} + +/* +===================== +rvStateThread::Clear +===================== +*/ +void rvStateThread::Clear ( bool ignoreStateCalls ) { + stateCall_t* call; + + // Clear all states from the main state list + for( call = states.Next(); call != NULL; call = states.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + // Clear all interrupted states + for( call = interrupted.Next(); call != NULL; call = interrupted.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + insertAfter = NULL; + fl.stateCleared = true; + + states.Clear ( ); + interrupted.Clear ( ); +} + +/* +===================== +rvStateThread::Execute +===================== +*/ +stateResult_t rvStateThread::Execute ( void ) { + stateCall_t* call = NULL; + int count; + const char* stateName; + int stateStage; + const char* historyState[HISTORY_COUNT]; + int historyStage[HISTORY_COUNT]; + int historyStart; + int historyEnd; + + // If our main state loop is empty copy over any states in the interrupted state + if ( !states.Next ( ) ) { + for ( call = interrupted.Next(); call; call = interrupted.Next() ) { + call->node.Remove ( ); + call->node.AddToEnd ( states ); + } + assert ( !interrupted.Next ( ) ); + } + + // State thread is idle if there are no states + if ( !states.Next() ) { + return SRESULT_IDLE; + } + + fl.executing = true; + + // Run through the states until there are no more or one of them tells us to wait + count = 0; + historyStart = 0; + historyEnd = 0; + + for( call = states.Next(); call && count < HISTORY_COUNT; call = states.Next(), ++count ) { + insertAfter = call; + fl.stateCleared = false; + fl.stateInterrupted = false; + + // If this state is only called when being cleared then just skip it + if ( call->flags & SFLAG_ONCLEARONLY ) { + call->node.Remove ( ); + delete call; + continue; + } + + // If the call has a delay on it the time will be set to negative initially and then + // converted to game time. + if ( call->parms.time <= 0 ) { + call->parms.time = gameLocal.time; + } + + // Check for delayed states + if ( call->delay && gameLocal.time < call->parms.time + call->delay ) { + fl.executing = false; + return SRESULT_WAIT; + } + + // Debugging + if ( lastResult != SRESULT_WAIT ) { + if ( *g_debugState.GetString ( ) && (*g_debugState.GetString ( ) == '*' || !idStr::Icmp ( g_debugState.GetString ( ), name ) ) ) { + if ( call->parms.stage ) { + gameLocal.Printf ( "%s: %s (%d)\n", name.c_str(), call->state->name, call->parms.stage ); + } else { + gameLocal.Printf ( "%s: %s\n", name.c_str(), call->state->name ); + } + } + + // Keep a history of the called states so we can dump them on an overflow + historyState[historyEnd] = call->state->name; + historyStage[historyEnd] = call->parms.stage; + historyEnd = (historyEnd+1) % HISTORY_COUNT; + if ( historyEnd == historyStart ) { + historyStart = (historyEnd+1) % HISTORY_COUNT; + } + } + + // Cache name and stage for error messages + stateName = call->state->name; + stateStage = call->parms.stage; + + // Actually call the state function + lastResult = owner->ProcessState ( call->state, call->parms ); + switch ( lastResult ) { + case SRESULT_WAIT: + fl.executing = false; + return SRESULT_WAIT; + + case SRESULT_ERROR: + gameLocal.Error ( "rvStateThread: error reported by state '%s (%d)'", stateName, stateStage ); + fl.executing = false; + return SRESULT_ERROR; + } + + // Dont remove the node if it was interrupted or cleared in the last process + if ( !fl.stateCleared && !fl.stateInterrupted ) { + if( lastResult >= SRESULT_SETDELAY ) { + call->delay = lastResult - SRESULT_SETDELAY; + call->parms.time = gameLocal.GetTime(); + continue; + } else if ( lastResult >= SRESULT_SETSTAGE ) { + call->parms.stage = lastResult - SRESULT_SETSTAGE; + continue; + } + + // Done with state so remove it from list + call->node.Remove ( ); + delete call; + } + + // Finished the last state but wait a frame for next one + if ( lastResult == SRESULT_DONE_WAIT ) { + fl.executing = false; + return SRESULT_WAIT; + } + } + + // Runaway state loop? + if ( count >= HISTORY_COUNT ) { + idFile *file; + + fileSystem->RemoveFile ( "statedump.txt" ); + file = fileSystem->OpenFileWrite( "statedump.txt" ); + + for ( ; historyStart != historyEnd; historyStart = (historyStart + 1) % HISTORY_COUNT ) { + if ( historyStage[historyStart] ) { + gameLocal.Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + gameLocal.Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + if ( file ) { + if ( historyStage[historyStart] ) { + file->Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + file->Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + } + } + if ( file ) { + fileSystem->CloseFile( file ); + } + + gameLocal.Error ( "rvStateThread: run away state loop '%s'", name.c_str() ); + } + + insertAfter = NULL; + fl.executing = false; + + // Move interrupted states back into the main state list when the main state list is empty + if ( !states.Next() && interrupted.Next ( ) ) { + return Execute ( ); + } + + return lastResult; +} + +/* +===================== +rvStateThread::Save +===================== +*/ +void rvStateThread::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( name.c_str() ); + + // No need to save owner, its setup in restore + + saveFile->WriteInt( lastResult ); + saveFile->Write ( &fl, sizeof(fl) ); + + saveFile->WriteInt( states.Num() ); + for( idLinkList* node = states.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + saveFile->WriteInt( interrupted.Num() ); + for( idLinkList* node = interrupted.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + // TOSAVE: stateCall_t* insertAfter; + // TOSAVE: stateResult_t lastResult; +} + +/* +===================== +rvStateThread::Restore +===================== +*/ +void rvStateThread::Restore( idRestoreGame *saveFile, idClass* owner ) { + int numStates; + stateCall_t* call = NULL; + + saveFile->ReadString( name ); + + this->owner = owner; + + saveFile->ReadInt( (int&)lastResult ); + saveFile->Read ( &fl, sizeof(fl) ); + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( states ); + } + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( interrupted ); + } +} diff --git a/src/game/gamesys/State.h b/src/game/gamesys/State.h new file mode 100644 index 0000000..2355837 --- /dev/null +++ b/src/game/gamesys/State.h @@ -0,0 +1,181 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_STATE_H__ +#define __SYS_STATE_H__ + +typedef enum { + SRESULT_OK, // Call was made successfully + SRESULT_ERROR, // An unrecoverable error occurred + SRESULT_DONE, // Done with current state, move to next + SRESULT_DONE_WAIT, // Done with current state, wait a frame then move to next + SRESULT_WAIT, // Wait a frame and re-run current state + SRESULT_IDLE, // State thread is currently idle (ie. no states) + SRESULT_SETSTAGE, // Sets the current stage of the current state and reruns the state + // NOTE: this has to be the last result becuase the stage is added to + // the result. + SRESULT_SETDELAY = SRESULT_SETSTAGE + 20 +} stateResult_t; + +#define MAX_STATE_CALLS 50 + +#define SRESULT_STAGE(x) ((stateResult_t)((int)SRESULT_SETSTAGE + (int)(x))) +#define SRESULT_DELAY(x) ((stateResult_t)((int)SRESULT_SETDELAY + (int)(x))) + +struct stateParms_t { + int blendFrames; + int time; + int stage; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); +}; + +typedef stateResult_t ( idClass::*stateCallback_t )( const stateParms_t& parms ); + +template< class Type > +struct rvStateFunc { + const char* name; + stateCallback_t function; +}; + +/* +================ +CLASS_STATES_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass that +wishes to have its own custom states. Its prototypes variables used in the process +of managing states. +================ +*/ +#define CLASS_STATES_PROTOTYPE(nameofclass) \ +protected: \ + static rvStateFunc stateCallbacks[] + +/* +================ +CLASS_STATES_DECLARATION + +This macro must be included in the code to properly initialize variables +used in state processing for a idClass dervied class +================ +*/ +#define CLASS_STATES_DECLARATION(nameofclass) \ +rvStateFunc nameofclass::stateCallbacks[] = { + +/* +================ +STATE + +This macro declares a single state. It must be surrounded by the CLASS_STATES_DECLARATION +and END_CLASS_STATES macros. +================ +*/ +#define STATE(statename,function) { statename, (stateCallback_t)( &function ) }, + +/* +================ +END_CLASS_STATES + +Terminates a state block +================ +*/ +#define END_CLASS_STATES { NULL, NULL } }; + +struct stateCall_t { + const rvStateFunc* state; + idLinkList node; + int flags; + int delay; + stateParms_t parms; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, const idClass* owner ); +}; + +class idClass; + +const int SFLAG_ONCLEAR = BIT(0); // Executes, even if the state queue is cleared +const int SFLAG_ONCLEARONLY = BIT(1); // Executes only if the state queue is cleared + +class rvStateThread { +public: + + rvStateThread ( void ); + ~rvStateThread ( void ); + + void SetName ( const char* name ); + void SetOwner ( idClass* owner ); + + bool Interrupt ( void ); + + stateResult_t InterruptState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t PostState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t SetState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateCall_t* GetState ( void ) const; + bool CurrentStateIs ( const char* name ) const; + + stateResult_t Execute ( void ); + + void Clear ( bool ignoreStateCalls = false ); + + bool IsIdle ( void ) const; + bool IsExecuting ( void ) const; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, idClass* owner ); + +protected: + + struct flags { + bool stateCleared :1; // State list was cleared + bool stateInterrupted :1; // State list was interrupted + bool executing :1; // Execute is currently processing states + } fl; + + idStr name; + idClass* owner; + idLinkList states; + idLinkList interrupted; + stateCall_t* insertAfter; + stateResult_t lastResult; +}; + +ID_INLINE void rvStateThread::SetName ( const char* _name ) { + name = _name; +} + +ID_INLINE stateCall_t* rvStateThread::GetState ( void ) const { + return states.Next(); +} + +ID_INLINE bool rvStateThread::IsIdle ( void ) const { + return !states.Next() && !interrupted.Next(); +} + +ID_INLINE bool rvStateThread::IsExecuting ( void ) const { + return fl.executing; +} + +#endif // __SYS_STATE_H__ diff --git a/src/game/gamesys/SysCmds.cpp b/src/game/gamesys/SysCmds.cpp new file mode 100644 index 0000000..19949e1 --- /dev/null +++ b/src/game/gamesys/SysCmds.cpp @@ -0,0 +1,3268 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +#include "../ai/AI.h" +#if !defined(__GAME_PROJECTILE_H__) + #include "../Projectile.h" +#endif +#if !defined(__GAME_WEAPON_H__) + #include "../Weapon.h" +#endif +#if !defined(__GAME_SPAWNER_H__) + #include "../spawner.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "../Vehicle/Vehicle.h" +#endif +#if !defined(__AI_MANAGER_H__) + #include "../ai/AI_Manager.h" +#endif +#if !defined(__INSTANCE_H__) + #include "../Instance.h" +#endif +// RAVEN END + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +================== +Cmd_GetFloatArg +================== +*/ +float Cmd_GetFloatArg( const idCmdArgs &args, int &argNum ) { + const char *value; + + value = args.Argv( argNum++ ); + return atof( value ); +} + +/* +=================== +Cmd_EntityList_f +=================== +*/ +void Cmd_EntityList_f( const idCmdArgs &args ) { + int e; + idEntity *check; + int count; + size_t size; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + size = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + + if ( !check ) { + continue; + } + + if ( !check->name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s %-20s %s\n", e, + check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + + count++; + size += check->spawnArgs.Allocated(); + } + + gameLocal.Printf( "...%d entities\n...%d bytes of spawnargs\n", count, size ); +} + +/* +=================== +Cmd_ClientEntityList_f +=================== +*/ +void Cmd_ClientEntityList_f( const idCmdArgs &args ) { + int e; + rvClientEntity *check; + int count; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + + gameLocal.Printf( "%-4s %-20s\n", " Num", "Classname" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_CENTITIES; e++ ) { + check = gameLocal.clientEntities[ e ]; + + idStr name( check->GetClassType().classname ); + + if ( !check ) { + continue; + } + + if ( !name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s\n", e, name.c_str() ); + + count++; + } + + gameLocal.Printf( "...%d entities\n", count ); +} + + +/* +=================== +Cmd_ActiveEntityList_f +=================== +*/ +void Cmd_ActiveEntityList_f( const idCmdArgs &args ) { + idEntity *check; + int count; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( check = gameLocal.activeEntities.Next(); check != NULL; check = check->activeNode.Next() ) { + char dormant = check->fl.isDormant ? '-' : ' '; + gameLocal.Printf( "%4i:%c%-20s %-20s %s\n", check->entityNumber, dormant, check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + count++; + } + + gameLocal.Printf( "...%d active entities\n", count ); +} + +/* +=================== +Cmd_ListSpawnArgs_f +=================== +*/ +void Cmd_ListSpawnArgs_f( const idCmdArgs &args ) { + int i; + idEntity *ent; + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + for ( i = 0; i < ent->spawnArgs.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = ent->spawnArgs.GetKeyVal( i ); + gameLocal.Printf( "\"%s\" "S_COLOR_WHITE"\"%s\"\n", kv->GetKey().c_str(), kv->GetValue().c_str() ); + } +} + +/* +=================== +Cmd_ReloadScript_f +=================== +*/ +void Cmd_ReloadScript_f( const idCmdArgs &args ) { + // shutdown the map because entities may point to script objects + gameLocal.MapShutdown(); + + // recompile the scripts + gameLocal.program.Startup( SCRIPT_DEFAULT ); + + // error out so that the user can rerun the scripts + gameLocal.Error( "Exiting map to reload scripts" ); +} + +/* +=================== +Cmd_Script_f +=================== +*/ +void Cmd_Script_f( const idCmdArgs &args ) { + const char * script; + idStr text; + idStr funcname; + static int funccount = 0; + idThread * thread; + const function_t *func; + idEntity *ent; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + sprintf( funcname, "ConsoleFunction_%d", funccount++ ); + + script = args.Args(); +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + text = va( "void %s() {%s;}\n", funcname.c_str(), script ); +// RAVEN END + if ( gameLocal.program.CompileText( "console", text, true ) ) { + func = gameLocal.program.FindFunction( funcname ); + if ( func ) { + // set all the entity names in case the user named one in the script that wasn't referenced in the default script + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + gameLocal.program.SetEntity( ent->name, ent ); + } + + thread = new idThread( func ); + thread->Start(); + } + } +} + +// RAVEN BEGIN +// jscott: exports for tracking memory +/* +================== +idGameEdit::ScriptSummary +================== +*/ +size_t idGameEdit::ScriptSummary( const idCmdArgs &args ) const { + + return( gameLocal.program.ScriptSummary( args ) ); +} + +/* +================== +idGameEdit::ClassSummary +================== +*/ +size_t idGameEdit::ClassSummary( const idCmdArgs &args ) const { + + common->Printf( "Classes - %dK\n", idClass::GetUsedMemory() / 1024 ); + + return( idClass::GetUsedMemory() / 1024 ); +} + +/* +================== +idGameEdit::EntitySummary +================== +*/ + +size_t idGameEdit::EntitySummary( const idCmdArgs &args ) const { + + common->Printf( "CL & SV ents - %dK\n", gameLocal.GetEntityMemoryUsage () / 1024); + + return gameLocal.GetEntityMemoryUsage() / 1024; +} +// RAVEN END + +/* +================== +KillEntities + +Kills all the entities of the given class in a level. +================== +*/ +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ) { + idEntity *ent; + idStrList ignore; + const char *name; + int i; + + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for( i = 1; i < args.Argc(); i++ ) { + name = args.Argv( i ); + ignore.Append( name ); + } + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( superClass ) ) { + for( i = 0; i < ignore.Num(); i++ ) { + if ( ignore[ i ] == ent->name ) { + break; + } + } + + if ( i >= ignore.Num() ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + } +} + +/* +================== +Cmd_KillMonsters_f + +Kills all the monsters in a level. +================== +*/ +void Cmd_KillMonsters_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAI::GetClassType() ); +// nmckenzie: rvSpawners + KillEntities( args, rvSpawner::GetClassType() ); + + // kill any projectiles as well since they have pointers to the monster that created them + KillEntities( args, idProjectile::GetClassType() ); +// RAVEN END +} + +/* +================== +Cmd_KillMovables_f + +Kills all the moveables in a level. +================== +*/ +void Cmd_KillMovables_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idMoveable::GetClassType() ); +// RAVEN END +} + +// RAVEN BEGIN +// bdube: vehicle code +/* +================== +Cmd_KillVehicles_f +================== +*/ +void Cmd_KillVehicles_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + rvVehicleController::KillVehicles ( ); +} + +void Cmd_KillMessage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + gameLocal.mpGame.SendDeathMessage( gameLocal.GetLocalPlayer(), gameLocal.GetLocalPlayer(), 2 ); +} + +void Cmd_APState_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for ( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + gameLocal.Printf ( "Assault point #%d: owner: %d\n", gameLocal.mpGame.assaultPoints[i]->GetIndex(), gameLocal.mpGame.assaultPoints[i]->GetOwner() ); + } +} +// RAVEN END + +/* +================== +Cmd_KillRagdolls_f + +Kills all the ragdolls in a level. +================== +*/ +void Cmd_KillRagdolls_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAFEntity_Generic::GetClassType() ); + KillEntities( args, idAFEntity_WithAttachedHead::GetClassType() ); +// RAVEN END +} + + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================== +GiveStuffToPlayer + +Used by the "give" and "buy" command line cmds +================== +*/ +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ) +{ + int i; + bool give_all; +// idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !player || !name ) { + return; + } + + if( !value ) { + value = ""; + } + + if ( idStr::Icmp( name, "all" ) == 0 ) { + give_all = true; + } else { + give_all = false; + } + + if ( give_all || ( idStr::Cmpn( name, "weapon", 6 ) == 0 ) ) { + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", false ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, gameLocal.entities[ i ]->spawnArgs.GetString( "def_weapon1" ) ); + } + } + } + } + + if ( ( idStr::Cmpn( name, "weapon_", 7 ) == 0 ) || ( idStr::Cmpn( name, "item_", 5 ) == 0 ) || ( idStr::Cmpn( name, "ammo_", 5 ) == 0 ) || ( idStr::Icmp( name, "ammorefill" ) == 0 ) ) { + player->GiveItem( name ); + return; + } + + if ( give_all || idStr::Icmp( name, "health" ) == 0 ) { + player->health = player->inventory.maxHealth; + if ( player->IsInVehicle() ) { + player->GetVehicleController().Give ( "health", "9999" ); + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "weapons" ) == 0 ) { + player->inventory.weapons = BIT( MAX_WEAPONS ) - 1; + player->CacheWeapons(); + + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "ammo" ) == 0 ) { +// RAVEN BEGIN +// bdube: define changed + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + player->inventory.ammo[ i ] = player->inventory.MaxAmmoForAmmoClass( player, rvWeapon::GetAmmoNameForIndex( i ) ); +// RAVEN END + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "armor" ) == 0 ) { + player->inventory.armor = player->inventory.maxarmor; + if ( !give_all ) { + return; + } + } +// RAVEN BEGIN + if (idStr::Icmp(name, "quad") == 0) { + player->GivePowerUp( POWERUP_QUADDAMAGE, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "invis" ) == 0 ) { + player->GivePowerUp( POWERUP_INVISIBILITY, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "regen" ) == 0 ) { + player->GivePowerUp( POWERUP_REGENERATION, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "haste" ) == 0 ) { + player->GivePowerUp( POWERUP_HASTE, SEC2MS( 30.0f ) ); + return; + } + + if (idStr::Icmp(name, "ammoregen") == 0) { + player->GivePowerUp( POWERUP_AMMOREGEN, -1 ); + return; + } + + if (idStr::Icmp(name, "scout") == 0) { + player->GivePowerUp( POWERUP_SCOUT, -1 ); + return; + } + + if (idStr::Icmp(name, "doubler") == 0) { + player->GivePowerUp( POWERUP_DOUBLER, -1 ); + return; + } + + if (idStr::Icmp(name, "guard") == 0) { + player->GivePowerUp( POWERUP_GUARD, -1 ); + return; + } +// RAVEN END + + if ( !idStr::Icmp ( name, "wpmod_all" ) ) { + player->GiveWeaponMods ( 0xFFFFFFFF ); + return; + } else if ( !idStr::Cmpn( name, "wpmod_", 6 ) ) { + player->GiveWeaponMod(name); + return; + } + + if ( !idStr::Cmpn( name, "stroggmod_", 10 ) ) { + player->Give ( name, "" ); + return; + } + + if ( !give_all && !player->Give( name, value ) ) { + gameLocal.Printf( "unknown item\n" ); + } +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + GiveStuffToPlayer( player, args.Argv(1), args.Argv(2) ); +} +// RITUAL END + +/* +================== +Cmd_CenterView_f + +Centers the players pitch +================== +*/ +void Cmd_CenterView_f( const idCmdArgs &args ) { + idPlayer *player; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + ang = player->viewAngles; + ang.pitch = 0.0f; + player->SetViewAngles( ang ); +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->godmode ) { + player->godmode = false; + msg = "godmode OFF\n"; + } else { + player->godmode = true; + msg = "godmode ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Undying_f + +Sets client to undying + +argv(0) undying +================== +*/ +void Cmd_Undying_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->undying ) { + player->undying = false; + msg = "undying OFF\n"; + } else { + player->undying = true; + msg = "undying ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->fl.notarget ) { + player->fl.notarget = false; + msg = "notarget OFF\n"; + } else { + player->fl.notarget = true; + msg = "notarget ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + player->noclip = !player->noclip; + + gameLocal.Printf( "%s", msg ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( gameLocal.isMultiplayer ) { + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_KILL ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "kill or kill \n" ); + return; + } + player->Kill( false, false ); +// RAVEN BEGIN +// rhummer: localized this string.. (killed client) + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say %s %d '%s^0'\n", common->GetLocalizedString( "#str_108022" ), player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + } else { + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + player->Kill( false, false ); + } +} + +// RAVEN BEGIN +// bdube: jump points +/* +================= +Cmd_DebugJump_f +================= +*/ +void Cmd_DebugJump_f( const idCmdArgs &args ) { + if (args.Argc() > 1) { + // going to a specific jump point as specified by second argument + gameDebug.JumpTo ( args.Argv( 1 ) ); + } else { + // just go to next jump point as specified + gameDebug.JumpNext ( ); + } +} + +/* +================= +Cmd_DebugNextJumpPoint_f +================= +*/ +void Cmd_DebugNextJumpPoint_f( const idCmdArgs &args ) { + // just go to next jump point as specified + gameDebug.JumpNext ( ); +} + +/* +================= +Cmd_DebugPrevJumpPoint_f +================= +*/ +void Cmd_DebugPrevJumpPoint_f( const idCmdArgs &args ) { + // just go to previous jump point as specified + gameDebug.JumpPrev ( ); +} + +/* +================= +Cmd_AASExtractTactical_f +================= +*/ +void Cmd_AASExtractTactical_f( const idCmdArgs &args ) { + if (gameLocal.GetLocalPlayer()) + { + gameLocal.GetLocalPlayer()->aasSensor->SearchDebug(); + } +} + +/* +================= +Cmd_CallScriptFunc_f +================= +*/ +void Cmd_CallScriptFunc_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: call FuncName ...\n" ); + return; + } + + idDict returnDict; + rvScriptFuncUtility util; + + if( !util.Init(args) ) { + return; + } + + util.CallFunc( &returnDict ); + + if( util.ReturnsAVal() && util.GetReturnKey() && util.GetReturnKey()[0] ) { + gameLocal.Printf( "%s: %s\n", util.GetReturnKey(), returnDict.GetString(util.GetReturnKey()) ); + } +} + +void Cmd_SetPlayerGravity_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: setPlayerGravity 'x_magnitude y_magnitude z_magnitude\n" ); + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( !player ) { + return; + } + + idVec3 gravity; + sscanf( args.Argv(1), "%f %f %f", &gravity.x, &gravity.y, &gravity.z ); + player->GetPhysics()->SetGravity( gravity ); +} +// RAVEN END + +/* +================= +Cmd_PlayerModel_f +================= +*/ +void Cmd_PlayerModel_f( const idCmdArgs &args ) { + idPlayer *player; + const char *name; + idVec3 pos; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: playerModel \n" ); + return; + } + + name = args.Argv( 1 ); + player->spawnArgs.Set( "model", name ); + + pos = player->GetPhysics()->GetOrigin(); + ang = player->viewAngles; + player->SpawnToPoint( pos, ang ); +} + +/* +================== +Cmd_Say +================== +*/ +static void Cmd_Say( bool team, const idCmdArgs &args ) { + const char *name; + idStr text; + const char *cmd = team ? "sayTeam" : "say" ; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "%s can only be used in a multiplayer game\n", cmd ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + + text = args.Args(); +// RAVEN BEGIN +// bdube: make sure text was specified + if ( text.Length() == 0 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + +// asalmon: check to see if the text passes the live decency standard +#ifdef _XENON + if(!Sys_VerifyString(text.c_str())) + { + gameLocal.Printf( "Your message did not pass Xbox decency standards\n"); + return; + } +#endif + + +// ddynerman: team speak only in team games + if ( team && !gameLocal.IsTeamGame() ) { + team = false; + } +// RAVEN END + if ( text[ text.Length() - 1 ] == '\n' ) { + text[ text.Length() - 1 ] = '\0'; + } + name = "player"; + + idPlayer * player; + + // here we need to special case a listen server to use the real client name instead of "server" + // "server" will only appear on a dedicated server + if ( gameLocal.isClient || cvarSystem->GetCVarInteger( "net_serverDedicated" ) == 0 ) { + player = gameLocal.localClientNum >= 0 ? static_cast( gameLocal.entities[ gameLocal.localClientNum ] ) : NULL; + if ( player ) { + name = player->GetUserInfo()->GetString( "ui_name", "player" ); + +// RAVEN BEGIN +// mekberg: activate the mphud gui so the time is right before receiving the chat message + if ( player->mphud ) { + player->mphud->Activate( true, gameLocal.time ); + } + } +// RAVEN END + } else { + name = "server"; + } + + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( team ? GAME_RELIABLE_MESSAGE_TCHAT : GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( "" ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + gameLocal.mpGame.ProcessChatMessage( gameLocal.localClientNum, team, name, text, NULL ); + } +} + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( const idCmdArgs &args ) { + Cmd_Say( false, args ); +} + +/* +================== +Cmd_SayTeam_f +================== +*/ +static void Cmd_SayTeam_f( const idCmdArgs &args ) { + Cmd_Say( true, args ); +} + +/* +================== +Cmd_AddChatLine_f +================== +*/ +static void Cmd_AddChatLine_f( const idCmdArgs &args ) { + gameLocal.mpGame.AddChatLine( args.Argv( 1 ) ); +} + +/* +================== +Cmd_Kick_f +================== +*/ +static void Cmd_Kick_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "kick can only be used in a multiplayer game\n" ); + return; + } + + if ( gameLocal.isClient ) { + gameLocal.Printf( "You have no such power. This is a server command\n" ); + return; + } + + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "usage: kick or kick \n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say kicking out client %d '%s^0'\n", player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d\n", player->entityNumber ) ); +} + +/* +================== +Cmd_GetViewpos_f +================== +*/ +void Cmd_GetViewpos_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( view ) { + gameLocal.Printf( "(%s) %.1f\n", view->vieworg.ToString(), view->viewaxis[0].ToYaw() ); + } else { + player->GetViewPos( origin, axis ); + gameLocal.Printf( "(%s) %.1f\n", origin.ToString(), axis[0].ToYaw() ); + } +} + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + int i; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( ( args.Argc() != 4 ) && ( args.Argc() != 5 ) ) { + gameLocal.Printf( "usage: setviewpos \n" ); + return; + } + + angles.Zero(); + if ( args.Argc() == 5 ) { + angles.yaw = atof( args.Argv( 4 ) ); + } + + for ( i = 0 ; i < 3 ; i++ ) { + origin[i] = atof( args.Argv( i + 1 ) ); + } + origin.z -= pm_normalviewheight.GetFloat() - 0.25f; + + player->Teleport( origin, angles, NULL ); +} + +/* +================= +Cmd_Teleport_f +================= +*/ +void Cmd_Teleport_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: teleport \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + angles.Zero(); + angles.yaw = ent->GetPhysics()->GetAxis()[ 0 ].ToYaw(); + origin = ent->GetPhysics()->GetOrigin(); + + player->Teleport( origin, angles, ent ); +} + +/* +================= +Cmd_Trigger_f +================= +*/ +void Cmd_Trigger_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: trigger \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); +} + +/* +=================== +Cmd_Spawn_f +=================== +*/ +void Cmd_Spawn_f( const idCmdArgs &args ) { +#ifndef _MPBETA + const char *key, *value; + int i; + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() & 1 ) { // must always have an even number of arguments + gameLocal.Printf( "usage: spawn classname [key/value pairs]\n" ); + return; + } + + yaw = player->viewAngles.yaw; + + value = args.Argv( 1 ); + dict.Set( "classname", value ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + +// RAVEN BEGIN +// kfuller: want to know the name of the entity I spawned + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } +// RAVEN END +#endif // !_MPBETA +} + +// RAVEN BEGIN +// ddynerman: MP spawning command for performance testing +/* +=================== +Cmd_EvaluateMPPerformance_f +=================== +*/ +void Cmd_EvaluateMPPerformance_f( const idCmdArgs &args ) { + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + int num = 15; + + if ( args.Argc() > 1 ) { + num = atoi( args.Argv( 1 ) ); + } + + float angleStep = 360.0f / num; + + const char* className = "char_marine"; + + yaw = player->viewAngles.yaw; + + for( int i = 0; i < num; i++ ) { + dict.Set( "classname", className ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw + (i * angleStep), 0 ).ToForward() * 120 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } + } +} +// RAVEN END + + +/* +================== +Cmd_Damage_f + +Damages the specified entity +================== +*/ +void Cmd_Damage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: damage \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Damage( gameLocal.world, gameLocal.world, idVec3( 0, 0, 1 ), "damage_moverCrush", atoi( args.Argv( 2 ) ), INVALID_JOINT ); +} + + +/* +================== +Cmd_Flashlight_f + +Toggles flashlight on specified entity +================== +*/ +void Cmd_Flashlight_f( const idCmdArgs &args ) { + if ( gameLocal.IsMultiplayer() || !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: flashight <0 = off, 1 = on>\n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent || !ent->IsType( idActor::GetClassType() ) ) { + gameLocal.Printf( "entity not found or not an actor\n" ); + return; + } + ent->ProcessEvent( &AI_Flashlight, atoi( args.Argv( 2 ) ) ); +} + +/* +================== +Cmd_Remove_f + +Removes the specified entity +================== +*/ +void Cmd_Remove_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: remove \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + delete ent; +} + +/* +================== +Cmd_AI_DebugFilter_f + +Makes the targeted entity the only one ai_debugMove & ai_debugTactical cares about +================== +*/ +void Cmd_AI_DebugFilter_f( const idCmdArgs &args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + idEntity *ent = NULL; + if ( args.Argc() != 2 ) + { + //trace ahead + trace_t trace; + idVec3 start = player->GetEyePosition(); + idVec3 end = start + player->viewAngles.ToForward() * 2048.0f; + gameLocal.TracePoint( player, trace, start, end, MASK_SHOT_RENDERMODEL, player ); + ent = gameLocal.GetTraceEntity( trace ); + } + else + { + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + } + + if ( !ent || !ent->IsType( idAI::GetClassType() ) ) { + ai_debugFilterString.SetString( "" ); + } else { + ai_debugFilterString.SetString( ent->GetName() ); + } +} + +/* +=================== +Cmd_TestLight_f +=================== +*/ +void Cmd_TestLight_f( const idCmdArgs &args ) { + int i; + idStr filename; + const char *key, *value, *name = NULL; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + renderView_t *rv = player->GetRenderView(); + + float fov = idMath::Tan( idMath::M_DEG2RAD * rv->fov_x / 2 ); + + dict.SetMatrix( "rotation", mat3_default ); + dict.SetVector( "origin", rv->vieworg ); + dict.SetVector( "light_target", rv->viewaxis[0] ); + dict.SetVector( "light_right", rv->viewaxis[1] * -fov ); + dict.SetVector( "light_up", rv->viewaxis[2] * fov ); + dict.SetVector( "light_start", rv->viewaxis[0] * 16 ); + dict.SetVector( "light_end", rv->viewaxis[0] * 1000 ); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + filename = args.Argv(1); + filename.DefaultFileExtension( ".tga" ); + dict.Set( "texture", filename ); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "spawned_light_%d", i ); // not just light_, or it might pick up a prelight shadow + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new light\n"); +} + +/* +=================== +Cmd_TestPointLight_f +=================== +*/ +void Cmd_TestPointLight_f( const idCmdArgs &args ) { + const char *key, *value, *name = NULL; + int i; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + dict.SetVector("origin", player->GetRenderView()->vieworg); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + dict.Set("light", value); + } else { + dict.Set("light", "300"); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "light_%d", i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new point light\n"); +} + +/* +================== +Cmd_PopLight_f +================== +*/ +void Cmd_PopLight_f( const idCmdArgs &args ) { + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idLight *lastLight; + int last; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + bool removeFromMap = ( args.Argc() > 1 ); + + lastLight = NULL; + last = -1; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( gameLocal.spawnIds[ ent->entityNumber ] > last ) { + last = gameLocal.spawnIds[ ent->entityNumber ]; + lastLight = static_cast( ent ); + } + } + + if ( lastLight ) { + // find map file entity + mapEnt = mapFile->FindEntity( lastLight->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + gameLocal.Printf( "Removing light %i\n", lastLight->GetLightDefHandle() ); + delete lastLight; + } else { + gameLocal.Printf( "No lights to clear.\n" ); + } + +} + +/* +==================== +Cmd_ClearLights_f +==================== +*/ +void Cmd_ClearLights_f( const idCmdArgs &args ) { + idEntity *ent; + idEntity *next; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + + bool removeFromMap = ( args.Argc() > 1 ); + + gameLocal.Printf( "Clearing all lights.\n" ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + light = static_cast( ent ); + mapEnt = mapFile->FindEntity( light->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + + delete light; + } +} + +// RAVEN BEGIN +// bdube: not using id effects +/* +================== +Cmd_TestFx_f +================== +void Cmd_TestFx_f( const idCmdArgs &args ) { + idVec3 offset; + const char *name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testFx ) { + delete gameLocal.testFx; + gameLocal.testFx = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "test", "1"); + dict.Set( "fx", name ); + gameLocal.testFx = ( idEntityFx * )gameLocal.SpawnEntityType( idEntityFx::Type, &dict ); +} +*/ +// RAVEN END + +#define MAX_DEBUGLINES 128 + +typedef struct { + bool used; + idVec3 start, end; + int color; + bool blink; + bool arrow; +} gameDebugLine_t; + +gameDebugLine_t debugLines[MAX_DEBUGLINES]; + +/* +================== +Cmd_AddDebugLine_f +================== +*/ +static void Cmd_AddDebugLine_f( const idCmdArgs &args ) { + int i, argNum; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 7 ) { + gameLocal.Printf( "usage: addline \n" ); + return; + } + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( !debugLines[i].used ) { + break; + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "no free debug lines\n" ); + return; + } + value = args.Argv( 0 ); + if ( !idStr::Icmp( value, "addarrow" ) ) { + debugLines[i].arrow = true; + } else { + debugLines[i].arrow = false; + } + debugLines[i].used = true; + debugLines[i].blink = false; + argNum = 1; + debugLines[i].start.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].color = Cmd_GetFloatArg( args, argNum ); +} + +/* +================== +Cmd_RemoveDebugLine_f +================== +*/ +static void Cmd_RemoveDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: removeline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi(value); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].used = false; +} + +/* +================== +Cmd_BlinkDebugLine_f +================== +*/ +static void Cmd_BlinkDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: blinkline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi( value ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].blink = !debugLines[i].blink; +} + +/* +================== +PrintFloat +================== +*/ +static void PrintFloat( float f ) { + char buf[128], i; + + for ( i = sprintf( buf, "%3.2f", f ); i < 7; i++ ) { + buf[i] = ' '; + } + buf[i] = '\0'; + gameLocal.Printf( buf ); +} + +/* +================== +Cmd_ListDebugLines_f +================== +*/ +static void Cmd_ListDebugLines_f( const idCmdArgs &args ) { + int i, num; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + num = 0; + gameLocal.Printf( "line num: x1 y1 z1 x2 y2 z2 c b a\n" ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + gameLocal.Printf( "line %3d: ", num ); + PrintFloat( debugLines[i].start.x ); + PrintFloat( debugLines[i].start.y ); + PrintFloat( debugLines[i].start.z ); + PrintFloat( debugLines[i].end.x ); + PrintFloat( debugLines[i].end.y ); + PrintFloat( debugLines[i].end.z ); + gameLocal.Printf( "%d %d %d\n", debugLines[i].color, debugLines[i].blink, debugLines[i].arrow ); + num++; + } + } + if ( !num ) { + gameLocal.Printf( "no debug lines\n" ); + } +} + +/* +================== +D_DrawDebugLines +================== +*/ +void D_DrawDebugLines( void ) { +// RAVEN BEGIN +// ddynerman: this eats about 5k us in release +#ifdef _DEBUG + int i; + idVec3 forward, right, up, p1, p2; + idVec4 color; + float l; + + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( !debugLines[i].blink || (gameLocal.time & (1<<9)) ) { + color = idVec4( debugLines[i].color&1, (debugLines[i].color>>1)&1, (debugLines[i].color>>2)&1, 1 ); + gameRenderWorld->DebugLine( color, debugLines[i].start, debugLines[i].end ); + // + if ( debugLines[i].arrow ) { + // draw a nice arrow + forward = debugLines[i].end - debugLines[i].start; + l = forward.Normalize() * 0.2f; + forward.NormalVectors( right, up); + + if ( l > 3.0f ) { + l = 3.0f; + } + p1 = debugLines[i].end - l * forward + (l * 0.4f) * right; + p2 = debugLines[i].end - l * forward - (l * 0.4f) * right; + gameRenderWorld->DebugLine( color, debugLines[i].end, p1 ); + gameRenderWorld->DebugLine( color, debugLines[i].end, p2 ); + gameRenderWorld->DebugLine( color, p1, p2 ); + } + } + } + } +#else + return; +#endif +// RAVEN END +} + +/* +================== +Cmd_ListCollisionModels_f +================== +*/ +static void Cmd_ListCollisionModels_f( const idCmdArgs &args ) { + if ( !gameLocal.CheatsOk() ) { + return; + } + + collisionModelManager->ListModels(); +} + +/* +================== +Cmd_CollisionModelInfo_f +================== +*/ +static void Cmd_CollisionModelInfo_f( const idCmdArgs &args ) { + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: collisionModelInfo \n" + "use 'all' instead of the model number for accumulated info\n" ); + return; + } + + value = args.Argv( 1 ); + if ( !idStr::Icmp( value, "all" ) ) { + collisionModelManager->ModelInfo( -1 ); + } else { + collisionModelManager->ModelInfo( atoi(value) ); + } +} + +/* +================== +Cmd_ExportModels_f +================== +*/ +static void Cmd_ExportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } +} + +/* +================== +Cmd_ReexportModels_f +================== +*/ +static void Cmd_ReexportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + idAnimManager::forceExport = true; + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } + idAnimManager::forceExport = false; +} + +/* +================== +Cmd_ReloadAnims_f +================== +*/ +static void Cmd_ReloadAnims_f( const idCmdArgs &args ) { + + // don't allow reloading anims when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + +// RAVEN BEGIN +// mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + +// jsinger: animationLib changed to a pointer + animationLib->ReloadAnims(); + +// mekberg: enable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( false ); +// RAVEN END +} + +/* +================== +Cmd_ListAnims_f +================== +*/ +static void Cmd_ListAnims_f( const idCmdArgs &args ) { + idEntity * ent; + int num; + size_t size; + size_t alloced; + idAnimator * animator; + const char * classname; + const idDict * dict; + int i; + + if ( args.Argc() > 1 ) { + idAnimator animator; + + classname = args.Argv( 1 ); + + dict = gameLocal.FindEntityDefDict( classname, false ); + if ( !dict ) { + gameLocal.Printf( "Entitydef '%s' not found\n", classname ); + return; + } + animator.SetModel( dict->GetString( "model" ) ); + + gameLocal.Printf( "----------------\n" ); + num = animator.NumAnims(); +// RAVEN BEGIN +// rjohnson: more output for animators + idFile *FH = fileSystem->OpenFileAppend( "animations.txt" ); + for( i = 0; i < num; i++ ) { + for( int j = 0; ; j++ ) { + const char *fileName = animator.AnimMD5Name( i, j ); + + if ( !fileName[0] ) { + break; + } + gameLocal.Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + if ( FH ) { + FH->Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + } + } + } + gameLocal.Printf( "%d anims\n", num ); + fileSystem->CloseFile( FH ); +// RAVEN END + } else { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->ListAnims(); +// RAVEN END + + size = 0; + num = 0; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + animator = ent->GetAnimator(); + if ( animator ) { + alloced = animator->Allocated(); + size += alloced; + num++; + } + } + + gameLocal.Printf( "%d memory used in %d entity animators\n", size, num ); + } +} + +// RAVEN BEGIN +// jscott: export for memory tracking +/* +================ +idGameEdit::ListAnims +================ +*/ +void idGameEdit::PrintMemInfo( MemInfo *mi ) { + + int i, count, totalSize; + idAAS *aas; + + totalSize = 0; + count = 0; + for( i = 0; i < gameLocal.GetNumAAS(); i++ ) { + + aas = gameLocal.GetAAS( i ); + if( aas ) { + + totalSize += aas->StatsSummary(); + count++; + } + } + + mi->aasAssetsTotal = totalSize; + mi->aasAssetsCount = count; + + // jsinger: animationLib changed to a pointer + animationLib->PrintMemInfo( mi ); +} +// RAVEN END + +/* +================== +Cmd_AASStats_f +================== +*/ +static void Cmd_AASStats_f( const idCmdArgs &args ) { + int aasNum; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + aasNum = aas_test.GetInteger(); + idAAS *aas = gameLocal.GetAAS( aasNum ); + if ( !aas ) { + gameLocal.Printf( "No aas #%d loaded\n", aasNum ); + } else { + aas->Stats(); + } +} + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDamage_f( const idCmdArgs &args ) { + idPlayer *player; + const char *damageDefName; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + gameLocal.Printf( "usage: testDamage [angle]\n" ); + return; + } + + damageDefName = args.Argv( 1 ); + + idVec3 dir; + if ( args.Argc() == 3 ) { + float angle = atof( args.Argv( 2 ) ); + + idMath::SinCos( DEG2RAD( angle ), dir[1], dir[0] ); + dir[2] = 0; + } else { + dir.Zero(); + } + + // give the player full health before and after + // running the damage + player->health = player->inventory.maxHealth; + player->Damage( NULL, NULL, dir, damageDefName, 1.0f, INVALID_JOINT ); + player->health = player->inventory.maxHealth; +} + +/* +================== +Cmd_TestBoneFx_f +================== +*/ +// RAVEN BEGIN +// bdube: not using +/* +static void Cmd_TestBoneFx_f( const idCmdArgs &args ) { + idPlayer *player; + const char *bone, *fx; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 3 || args.Argc() > 4 ) { + gameLocal.Printf( "usage: testBoneFx \n" ); + return; + } + + fx = args.Argv( 1 ); + bone = args.Argv( 2 ); + + player->StartFxOnBone( fx, bone ); +} +*/ +// RAVEN END + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDeath_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + idVec3 dir; + idMath::SinCos( DEG2RAD( 45.0f ), dir[1], dir[0] ); + dir[2] = 0; + + g_testDeath.SetBool( 1 ); + player->Damage( NULL, NULL, dir, "damage_triggerhurt_1000", 1.0f, INVALID_JOINT ); + if ( args.Argc() >= 2) { + player->SpawnGibs( dir, "damage_triggerhurt_1000" ); + } + +} + +/* +================== +Cmd_WeaponSplat_f +================== +*/ +static void Cmd_WeaponSplat_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->weapon->BloodSplat( 2.0f ); +} + +/* +================== +Cmd_SaveSelected_f +================== +*/ +static void Cmd_SaveSelected_f( const idCmdArgs &args ) { + int i; + idPlayer *player; + idEntity *s; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + s = player->dragEntity.GetSelected(); + if ( !s ) { + gameLocal.Printf( "no entity selected, set g_dragShowSelection 1 to show the current selection\n" ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + // find map file entity + mapEnt = mapFile->FindEntity( s->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", s->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + s->name = name; + mapEnt->epairs.Set( "classname", s->GetEntityDefName() ); + mapEnt->epairs.Set( "name", s->name ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( s->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // save the moveable state + mapEnt->epairs.Set( "origin", s->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", s->GetPhysics()->GetAxis().ToString( 8 ) ); + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + else if ( s->IsType( idAFEntity_Generic::GetClassType() ) || s->IsType( idAFEntity_WithAttachedHead::GetClassType() ) ) { +// RAVEN END + // save the articulated figure state + dict.Clear(); + static_cast(s)->SaveState( dict ); + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_DeleteSelected_f +================== +*/ +static void Cmd_DeleteSelected_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.DeleteSelected(); + } +} + +/* +================== +Cmd_SaveMoveables_f +================== +*/ +static void Cmd_SaveMoveables_f( const idCmdArgs &args ) { + int e, i; + idMoveable *m; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m || !m->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + if ( !m->IsAtRest() ) { + break; + } + } + + if ( e < MAX_GENTITIES ) { + gameLocal.Warning( "map not saved because the moveable entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m ) + { + continue; + } +// jdischler: need to check for idMoveableItem as well + if ( !m->IsType( idMoveable::GetClassType()) && !m->IsType( idMoveableItem::GetClassType()) ) + { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + // find map file entity + mapEnt = mapFile->FindEntity( m->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", m->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + m->name = name; + mapEnt->epairs.Set( "classname", m->GetEntityDefName() ); + mapEnt->epairs.Set( "name", m->name ); + } + // save the moveable state + mapEnt->epairs.Set( "origin", m->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", m->GetPhysics()->GetAxis().ToString( 8 ) ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_SaveRagdolls_f +================== +*/ +static void Cmd_SaveRagdolls_f( const idCmdArgs &args ) { + int e, i; + idAFEntity_Base *af; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + af = static_cast(gameLocal.entities[ e ]); + + if ( !af ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af->IsType( idAFEntity_WithAttachedHead::GetClassType() ) && !af->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( af->IsBound() ) { + continue; + } + + if ( !af->IsAtRest() ) { + gameLocal.Warning( "the articulated figure for entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + } + + dict.Clear(); + af->SaveState( dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( af->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", af->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + af->name = name; + mapEnt->epairs.Set( "classname", af->GetEntityDefName() ); + mapEnt->epairs.Set( "name", af->name ); + } + // save the articulated figure state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_BindRagdoll_f +================== +*/ +static void Cmd_BindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.BindSelected(); + } +} + +/* +================== +Cmd_UnbindRagdoll_f +================== +*/ +static void Cmd_UnbindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.UnbindSelected(); + } +} + +/* +================== +Cmd_GameError_f +================== +*/ +static void Cmd_GameError_f( const idCmdArgs &args ) { + gameLocal.Error( "game error" ); +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +/* +================== +Cmd_ListEntityStats_f +================== +*/ +static void Cmd_ListEntityStats_f( const idCmdArgs &args ) { + gameLocal.ListEntityStats( args ); +} +// RAVEN END + +/* +================== +Cmd_SaveLights_f +================== +*/ +static void Cmd_SaveLights_f( const idCmdArgs &args ) { + int e, i; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + light = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !light || !light->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + dict.Clear(); + light->SaveState( &dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( light->name ); + // create new map file entity if there isn't one for this light + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", light->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + light->name = name; + mapEnt->epairs.Set( "classname", light->GetEntityDefName() ); + mapEnt->epairs.Set( "name", light->name ); + } + // save the light state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_SaveParticles_f +================== +*/ +static void Cmd_SaveParticles_f( const idCmdArgs &args ) { + int e; + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName, strModel; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + + ent = static_cast ( gameLocal.entities[ e ] ); + + if ( !ent ) { + continue; + } + + strModel = ent->spawnArgs.GetString( "model" ); + if ( strModel.Length() && strModel.Find( ".prt") > 0 ) { + dict.Clear(); + dict.Set( "model", ent->spawnArgs.GetString( "model" ) ); + dict.SetVector( "origin", ent->GetPhysics()->GetOrigin() ); + + // find map file entity + mapEnt = mapFile->FindEntity( ent->name ); + // create new map file entity if there isn't one for this entity + if ( !mapEnt ) { + continue; + } + // save the particle state + mapEnt->epairs.Copy( dict ); + } + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_DisasmScript_f +================== +*/ +static void Cmd_DisasmScript_f( const idCmdArgs &args ) { + gameLocal.program.Disassemble(); +} + +/* +================== +Cmd_TestSave_f +================== +*/ +static void Cmd_TestSave_f( const idCmdArgs &args ) { + idFile *f; + + f = fileSystem->OpenFileWrite( "test.sav" ); + gameLocal.SaveGame( f ); + fileSystem->CloseFile( f ); +} + +// RAVEN BEGIN +#if 0 +/* +================== +Cmd_RecordViewNotes_f +================== +*/ +static void Cmd_RecordViewNotes_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + if ( args.Argc() <= 3 ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + player->GetViewPos( origin, axis ); + + // Argv(1) = filename for map (viewnotes/mapname/person) + // Argv(2) = note number (person0001) + // Argv(3) = comments + + idStr str = args.Argv(1); + str.SetFileExtension( ".txt" ); + idFile *file = fileSystem->OpenFileAppend( str ); + if ( file ) { + file->WriteFloatString( "\"view\"\t( %s )\t( %s )\r\n", origin.ToString(), axis.ToString() ); + file->WriteFloatString( "\"comments\"\t\"%s: %s\"\r\n\r\n", args.Argv(2), args.Argv(3) ); + fileSystem->CloseFile( file ); + } + + idStr viewComments = args.Argv(1); + viewComments.StripLeading("viewnotes/"); + viewComments += " -- Loc: "; + viewComments += origin.ToString(); + viewComments += "\n"; + viewComments += args.Argv(3); + player->hud->SetStateString( "viewcomments", viewComments ); + player->hud->HandleNamedEvent( "showViewComments" ); +} + +/* +================== +Cmd_CloseViewNotes_f +================== +*/ +static void Cmd_CloseViewNotes_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); +} + +/* +================== +Cmd_ShowViewNotes_f +================== +*/ +static void Cmd_ShowViewNotes_f( const idCmdArgs &args ) { + static idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT | LEXFL_NOFATALERRORS ); + idToken token; + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( !parser.IsLoaded() ) { + idStr str = "viewnotes/"; + str += gameLocal.GetMapName(); + str.StripFileExtension(); + str += "/"; + if ( args.Argc() > 1 ) { + str += args.Argv( 1 ); + } else { + str += "comments"; + } + str.SetFileExtension( ".txt" ); + if ( !parser.LoadFile( str ) ) { + gameLocal.Printf( "No view notes for %s\n", gameLocal.GetMapName() ); + return; + } + } + + if ( parser.ExpectTokenString( "view" ) && parser.Parse1DMatrix( 3, origin.ToFloatPtr() ) && + parser.Parse1DMatrix( 9, axis.ToFloatPtr() ) && parser.ExpectTokenString( "comments" ) && parser.ReadToken( &token ) ) { + player->hud->SetStateString( "viewcomments", token ); + player->hud->HandleNamedEvent( "showViewComments" ); + player->Teleport( origin, axis.ToAngles(), NULL ); + } else { + parser.FreeSource(); + player->hud->HandleNamedEvent( "hideViewComments" ); + return; + } +} +#endif +// RAVEN END + +/* +================= +FindEntityGUIs + +helper function for Cmd_NextGUI_f. Checks the passed entity to determine if it +has any valid gui surfaces. +================= +*/ +bool FindEntityGUIs( idEntity *ent, const modelSurface_t ** surfaces, int maxSurfs, int &guiSurfaces ) { + renderEntity_t *renderEnt; + idRenderModel *renderModel; + const modelSurface_t *surf; + const idMaterial *shader; + int i; + + assert( surfaces != NULL ); + assert( ent != NULL ); + + memset( surfaces, 0x00, sizeof( modelSurface_t *) * maxSurfs ); + guiSurfaces = 0; + + renderEnt = ent->GetRenderEntity(); + renderModel = renderEnt->hModel; + if ( renderModel == NULL ) { + return false; + } + + for( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + if ( surf == NULL ) { + continue; + } + shader = surf->shader; + if ( shader == NULL ) { + continue; + } + if ( shader->GetEntityGui() > 0 ) { + surfaces[ guiSurfaces++ ] = surf; + } + } + + return ( guiSurfaces != 0 ); +} + +/* +================= +Cmd_NextGUI_f +================= +*/ +void Cmd_NextGUI_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + int guiSurfaces; + bool newEnt; + renderEntity_t *renderEnt; + int surfIndex; + srfTriangles_t *geom; + idMat4 modelMatrix; + idVec3 normal; + idVec3 center; + const modelSurface_t *surfaces[ MAX_RENDERENTITY_GUI ]; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 1 ) { + gameLocal.Printf( "usage: nextgui\n" ); + return; + } + + // start at the last entity + ent = gameLocal.lastGUIEnt.GetEntity(); + + // see if we have any gui surfaces left to go to on the current entity. + guiSurfaces = 0; + newEnt = false; + if ( ent == NULL ) { + newEnt = true; + } else if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == true ) { + if ( gameLocal.lastGUI >= guiSurfaces ) { + newEnt = true; + } + } else { + // no actual gui surfaces on this ent, so skip it + newEnt = true; + } + + if ( newEnt == true ) { + // go ahead and skip to the next entity with a gui... + if ( ent == NULL ) { + ent = gameLocal.spawnedEntities.Next(); + } else { + ent = ent->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->spawnArgs.GetString( "gui", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui2", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui3", NULL ) != NULL ) { + break; + } + + // try the next entity + gameLocal.lastGUIEnt = ent; + } + + gameLocal.lastGUIEnt = ent; + gameLocal.lastGUI = 0; + + if ( !ent ) { + gameLocal.Printf( "No more gui entities. Starting over...\n" ); + return; + } + } + + if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == false ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces.\n", ent->name.c_str() ); + } + + if ( guiSurfaces == 0 ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces!\n", ent->name.c_str() ); + return; + } + + gameLocal.Printf( "Teleporting to gui entity \"%s\", gui #%d.\n" , ent->name.c_str (), gameLocal.lastGUI ); + + renderEnt = ent->GetRenderEntity(); + surfIndex = gameLocal.lastGUI++; + geom = surfaces[ surfIndex ]->geometry; + if ( geom == NULL ) { + gameLocal.Printf( "Entity \"%s\" has gui surface %d without geometry!\n", ent->name.c_str(), surfIndex ); + return; + } + + assert( geom->facePlanes != NULL ); + + modelMatrix = idMat4( renderEnt->axis, renderEnt->origin ); + normal = geom->facePlanes[ 0 ].Normal() * renderEnt->axis; + center = geom->bounds.GetCenter() * modelMatrix; + + origin = center + (normal * 32.0f); + origin.z -= player->EyeHeight(); + normal *= -1.0f; + angles = normal.ToAngles (); + + // make sure the player is in noclip + player->noclip = true; + player->Teleport( origin, angles, NULL ); +} + +static void ArgCompletion_DefFile( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "def/", true, ".def", NULL ); +} + +/* +=============== +Cmd_TestId_f +outputs a string from the string table for the specified id +=============== +*/ +void Cmd_TestId_f( const idCmdArgs &args ) { + idStr id; + int i; + if ( args.Argc() == 1 ) { + common->Printf( "usage: testid \n" ); + return; + } + + for ( i = 1; i < args.Argc(); i++ ) { + id += args.Argv( i ); + } + if ( idStr::Cmpn( id, STRTABLE_ID, STRTABLE_ID_LENGTH ) != 0 ) { + id = STRTABLE_ID + id; + } + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( id ), "", "", "" ); +} + +// RAVEN BEGIN +// ddynerman: instance testing commands +void Cmd_SetInstance_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_SetInstance_f(): setInstance can only be run on a server\n" ); + return; + } + + if ( args.Argc() <= 2 ) { + common->Printf( "usage: setInstance \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + int instanceID = atoi( args.Argv( 2 ) ); + + if( client < 0 || client >= MAX_CLIENTS || !gameLocal.entities[ client ] ) { + gameLocal.Warning( "Cmd_SetInstance_f(): Invalid clientnum %d\n", client ); + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ client ]; + + gameLocal.Printf( "Cmd_SetInstance_f(): Switching %d: %s to instance %d\n", client, gameLocal.userInfo[ client ].GetString( "ui_name", "unknown" ), instanceID ); + + player->JoinInstance( instanceID ); +} + +void Cmd_ListInstances_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_ListInstances_f(): listInstances can only be run on a server\n" ); + return; + } + + for( int i = 0; i < gameLocal.GetNumInstances(); i++ ) { + gameLocal.Printf("Instance %d:\n", i ); + for( int j = 0; j < MAX_CLIENTS; j++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ j ]; + if( player && player->GetInstance() == i ) { + gameLocal.Printf( "\t%d: %s\n", j, player->GetUserInfo()->GetString( "ui_name" ) ); + } + } + } +} + +void Cmd_AddIcon_f( const idCmdArgs& args ) { + if ( args.Argc() <= 1 ) { + common->Printf( "usage: addIcon \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + + iconManager->AddIcon( client, "textures/mp/awards/capture" ); +} +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +void Cmd_ToggleBuyMenu_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player && player->CanBuy() ) + { + gameLocal.mpGame.OpenLocalBuyMenu(); + } +} + +void Cmd_BuyItem_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + common->Printf( "ERROR: Cmd_BuyItem_f() failed, since GetLocalPlayer() was NULL.\n", player ); + return; + } + + player->GenerateImpulseForBuyAttempt( args.Argv(1) ); +} +// RITUAL END + +void Cmd_PlayerEmote_f( const idCmdArgs& args ) { + if( gameLocal.GetLocalPlayer() == NULL ) { + gameLocal.Warning( "Cmd_Emote_f() - local player is NULL" ); + return; + } + + if ( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: emote \n" ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab_a'\n" ); + return; + } + + if( !idStr::Icmp( args.Argv( 1 ), "taunt" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_TAUNT ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_a" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_A ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_b" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_B ); + } else if( !idStr::Icmp( args.Argv( 1), "salute" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_SALUTE ); + } else if( !idStr::Icmp( args.Argv( 1), "cheer" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_CHEER ); + } else { + gameLocal.Printf( "Invalid emote '%s'\n", args.Argv( 1 ) ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab'\n" ); + } + +} + +// mekberg: added +void Cmd_SetPMCVars_f ( const idCmdArgs &args ) { + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( gameLocal.GetLocalPlayer( ) ) { + gameLocal.GetLocalPlayer( )->SetPMCVars( ); + } +} + +void Cmd_FadeSound_f( const idCmdArgs &args ) { + + if( args.Argc() < 2) { + return; + } + + float fadeDB = 0.0f; + float fadeTime = 0.0f; + + idStr _fadeDB = args.Argv( 1); + fadeDB = atof( _fadeDB ); + + idStr _fadeTime = args.Argv( 2); + fadeTime = atof( _fadeTime ); + + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, SOUND_CLASS_MUSICAL, 0.0f - fadeDB, fadeTime ); + +} + +void Cmd_TestClientModel_f( const idCmdArgs& args ) { + rvClientEntity* face; + const idDict* dict = gameLocal.FindEntityDefDict( "player_marine_client", false ); + + gameLocal.SpawnClientEntityDef( *dict, &face, false ); + +// face = new rvClientAFEntity( *dict ); +// face->Spawn( dict ); +// face->SetOrigin( vec3_zero ); +// face->SetAxis( mat3_identity ); +// face->SetModel( "model_player_marine" ); +} + + +// RAVEN END + +void Cmd_CheckSave_f( const idCmdArgs &args ); + +void Cmd_ShuffleTeams_f( const idCmdArgs& args ) { + gameLocal.mpGame.ShuffleTeams(); +} + +#ifndef _FINAL +void Cmd_ClientOverflowReliable_f( const idCmdArgs& args ) { + idBitMsg outMsg; + byte msgBuf[ 114688 ]; + + for( int i = 0; i < 10; i++ ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( -1 ); + for( int j = 0; j < 8190; j++ ) { + outMsg.WriteByte( j ); + } + networkSystem->ClientSendReliableMessage( outMsg ); + } +} +#endif + +/* +================= +idGameLocal::InitConsoleCommands + +Let the system know about all of our commands +so it can perform tab completion +================= +*/ +void idGameLocal::InitConsoleCommands( void ) { +// RAVEN BEGIN +// jscott: typeinfo gone - didn't work, it was unfinished +// cmdSystem->AddCommand( "listTypeInfo", ListTypeInfo_f, CMD_FL_GAME, "list type info" ); +// cmdSystem->AddCommand( "writeGameState", WriteGameState_f, CMD_FL_GAME, "write game state" ); +// cmdSystem->AddCommand( "testSaveGame", TestSaveGame_f, CMD_FL_GAME|CMD_FL_CHEAT, "test a save game for a level" ); +// RAVEN END + cmdSystem->AddCommand( "game_memory", idClass::DisplayInfo_f, CMD_FL_GAME, "displays game class info" ); + cmdSystem->AddCommand( "listClasses", idClass::ListClasses_f, CMD_FL_GAME, "lists game classes" ); + cmdSystem->AddCommand( "listThreads", idThread::ListThreads_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists script threads" ); + cmdSystem->AddCommand( "listEntities", Cmd_EntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists game entities" ); + cmdSystem->AddCommand( "listClientEntities", Cmd_ClientEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists client entities" ); + cmdSystem->AddCommand( "listActiveEntities", Cmd_ActiveEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists active game entities" ); + cmdSystem->AddCommand( "listMonsters", idAI::List_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists monsters" ); + cmdSystem->AddCommand( "listSpawnArgs", Cmd_ListSpawnArgs_f, CMD_FL_GAME|CMD_FL_CHEAT, "list the spawn args of an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "say", Cmd_Say_f, CMD_FL_GAME, "text chat" ); + cmdSystem->AddCommand( "sayTeam", Cmd_SayTeam_f, CMD_FL_GAME, "team text chat" ); + cmdSystem->AddCommand( "addChatLine", Cmd_AddChatLine_f, CMD_FL_GAME, "internal use - core to game chat lines" ); + cmdSystem->AddCommand( "gameKick", Cmd_Kick_f, CMD_FL_GAME, "same as kick, but recognizes player names" ); + cmdSystem->AddCommand( "give", Cmd_Give_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives one or more items" ); + cmdSystem->AddCommand( "centerview", Cmd_CenterView_f, CMD_FL_GAME, "centers the view" ); + cmdSystem->AddCommand( "god", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "undying", Cmd_Undying_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables undying mode (take damage down to 1 health, but do not die)" ); + cmdSystem->AddCommand( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); + cmdSystem->AddCommand( "noclip", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); + cmdSystem->AddCommand( "kill", Cmd_Kill_f, CMD_FL_GAME, "kills the player" ); + cmdSystem->AddCommand( "where", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "getviewpos", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "setviewpos", Cmd_SetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the current view position" ); + cmdSystem->AddCommand( "teleport", Cmd_Teleport_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleports the player to an entity location", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "trigger", Cmd_Trigger_f, CMD_FL_GAME|CMD_FL_CHEAT, "triggers an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "spawn", Cmd_Spawn_f, CMD_FL_GAME|CMD_FL_CHEAT, "spawns a game entity", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "damage", Cmd_Damage_f, CMD_FL_GAME|CMD_FL_CHEAT, "apply damage to an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "remove", Cmd_Remove_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "killMonsters", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all monsters" ); + cmdSystem->AddCommand( "killMoveables", Cmd_KillMovables_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all moveables" ); + cmdSystem->AddCommand( "killRagdolls", Cmd_KillRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all ragdolls" ); + cmdSystem->AddCommand( "addline", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug line" ); + cmdSystem->AddCommand( "addarrow", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug arrow" ); + cmdSystem->AddCommand( "removeline", Cmd_RemoveDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes a debug line" ); + cmdSystem->AddCommand( "blinkline", Cmd_BlinkDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "blinks a debug line" ); + cmdSystem->AddCommand( "listLines", Cmd_ListDebugLines_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists all debug lines" ); + cmdSystem->AddCommand( "playerModel", Cmd_PlayerModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the given model on the player", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "flashlight", Cmd_Flashlight_f, CMD_FL_GAME|CMD_FL_CHEAT, "toggle actor's flashlight", idGameLocal::ArgCompletion_AIName ); + + cmdSystem->AddCommand( "shuffleTeams", Cmd_ShuffleTeams_f, CMD_FL_GAME, "shuffle teams" ); +// RAVEN BEGIN +// bdube: not using id effect system +// cmdSystem->AddCommand( "testFx", Cmd_TestFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system", idCmdSystem::ArgCompletion_Decl ); +// cmdSystem->AddCommand( "testBoneFx", Cmd_TestBoneFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system bound to a joint", idCmdSystem::ArgCompletion_Decl ); +// RAVEN END + cmdSystem->AddCommand( "testLight", Cmd_TestLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a light" ); + cmdSystem->AddCommand( "testPointLight", Cmd_TestPointLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a point light" ); + cmdSystem->AddCommand( "popLight", Cmd_PopLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes the last created light" ); + cmdSystem->AddCommand( "testDeath", Cmd_TestDeath_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests death" ); + cmdSystem->AddCommand( "testSave", Cmd_TestSave_f, CMD_FL_GAME|CMD_FL_CHEAT, "writes out a test savegame" ); + cmdSystem->AddCommand( "testModel", idTestModel::TestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a model", idTestModel::ArgCompletion_TestModel ); + cmdSystem->AddCommand( "testSkin", idTestModel::TestSkin_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a skin on an existing testModel", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testShaderParm", idTestModel::TestShaderParm_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets a shaderParm on an existing testModel" ); + cmdSystem->AddCommand( "keepTestModel", idTestModel::KeepTestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "keeps the last test model in the game" ); + cmdSystem->AddCommand( "testAnim", idTestModel::TestAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an animation", idTestModel::ArgCompletion_TestAnim ); + cmdSystem->AddCommand( "testParticleStopTime", idTestModel::TestParticleStopTime_f,CMD_FL_GAME|CMD_FL_CHEAT, "tests particle stop time on a test model" ); + cmdSystem->AddCommand( "nextAnim", idTestModel::TestModelNextAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation on test model" ); + cmdSystem->AddCommand( "prevAnim", idTestModel::TestModelPrevAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation on test model" ); + cmdSystem->AddCommand( "nextFrame", idTestModel::TestModelNextFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation frame on test model" ); + cmdSystem->AddCommand( "prevFrame", idTestModel::TestModelPrevFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation frame on test model" ); + cmdSystem->AddCommand( "testBlend", idTestModel::TestBlend_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests animation blending" ); + cmdSystem->AddCommand( "reloadScript", Cmd_ReloadScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads scripts" ); + cmdSystem->AddCommand( "script", Cmd_Script_f, CMD_FL_GAME|CMD_FL_CHEAT, "executes a line of script" ); + cmdSystem->AddCommand( "listCollisionModels", Cmd_ListCollisionModels_f, CMD_FL_GAME, "lists collision models" ); + cmdSystem->AddCommand( "collisionModelInfo", Cmd_CollisionModelInfo_f, CMD_FL_GAME, "shows collision model info" ); + cmdSystem->AddCommand( "reexportmodels", Cmd_ReexportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "reexports models", ArgCompletion_DefFile ); + cmdSystem->AddCommand( "reloadanims", Cmd_ReloadAnims_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads animations" ); + cmdSystem->AddCommand( "listAnims", Cmd_ListAnims_f, CMD_FL_GAME, "lists all animations" ); + cmdSystem->AddCommand( "aasStats", Cmd_AASStats_f, CMD_FL_GAME, "shows AAS stats" ); + cmdSystem->AddCommand( "testDamage", Cmd_TestDamage_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a damage def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "weaponSplat", Cmd_WeaponSplat_f, CMD_FL_GAME|CMD_FL_CHEAT, "projects a blood splat on the player weapon" ); + cmdSystem->AddCommand( "saveSelected", Cmd_SaveSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves the selected entity to the .map file" ); + cmdSystem->AddCommand( "deleteSelected", Cmd_DeleteSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "deletes selected entity" ); + cmdSystem->AddCommand( "saveMoveables", Cmd_SaveMoveables_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all moveables to the .map file" ); + cmdSystem->AddCommand( "saveRagdolls", Cmd_SaveRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all ragdoll poses to the .map file" ); + cmdSystem->AddCommand( "bindRagdoll", Cmd_BindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "binds ragdoll at the current drag position" ); + cmdSystem->AddCommand( "unbindRagdoll", Cmd_UnbindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "unbinds the selected ragdoll" ); + cmdSystem->AddCommand( "saveLights", Cmd_SaveLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "saveParticles", Cmd_SaveParticles_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "clearLights", Cmd_ClearLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "clears all lights" ); + cmdSystem->AddCommand( "gameError", Cmd_GameError_f, CMD_FL_GAME|CMD_FL_CHEAT, "causes a game error" ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + cmdSystem->AddCommand( "listEntityStats", Cmd_ListEntityStats_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists global entity stats" ); +// ddynerman: mp spawning test command + cmdSystem->AddCommand( "evaluateMPPerformance", Cmd_EvaluateMPPerformance_f,CMD_FL_GAME|CMD_FL_CHEAT, "spawns serveral player models", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "listMapEntities", idGameLocal::Cmd_PrintMapEntityNumbers_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); + cmdSystem->AddCommand( "listSpawnIds", idGameLocal::Cmd_PrintSpawnIds_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); +// RAVEN END + +#ifndef ID_DEMO_BUILD + cmdSystem->AddCommand( "disasmScript", Cmd_DisasmScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "disassembles script" ); +// RAVEN BEGIN +// rjohnson: removed old not taking system +/* + cmdSystem->AddCommand( "recordViewNotes", Cmd_RecordViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "record the current view position with notes" ); + cmdSystem->AddCommand( "showViewNotes", Cmd_ShowViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "show any view notes for the current map, successive calls will cycle to the next note" ); + cmdSystem->AddCommand( "closeViewNotes", Cmd_CloseViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "close the view showing any notes for this map" ); +*/ +// RAVEN END + cmdSystem->AddCommand( "exportmodels", Cmd_ExportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "exports models", ArgCompletion_DefFile ); + + // multiplayer client commands ( replaces old impulses stuff ) + //cmdSystem->AddCommand( "clientDropWeapon", idMultiplayerGame::DropWeapon_f, CMD_FL_GAME, "drop current weapon" ); + cmdSystem->AddCommand( "clientMessageMode", idMultiplayerGame::MessageMode_f, CMD_FL_GAME, "ingame gui message mode" ); + // FIXME: implement + cmdSystem->AddCommand( "clientVote", idMultiplayerGame::Vote_f, CMD_FL_GAME, "cast your vote: clientVote yes | no" ); + cmdSystem->AddCommand( "clientCallVote", idMultiplayerGame::CallVote_f, CMD_FL_GAME, "call a vote: clientCallVote si_.. proposed_value" ); + cmdSystem->AddCommand( "clientVoiceChat", idMultiplayerGame::VoiceChat_f, CMD_FL_GAME, "voice chats: clientVoiceChat " ); + cmdSystem->AddCommand( "clientVoiceChatTeam", idMultiplayerGame::VoiceChatTeam_f, CMD_FL_GAME, "team voice chats: clientVoiceChat " ); +// RAVEN BEGIN + // jshepard + cmdSystem->AddCommand( "forceTeamChange", idMultiplayerGame::ForceTeamChange_f, CMD_FL_GAME, "force team change: forceTeamChange " ); + cmdSystem->AddCommand( "removeClientFromBanList", idMultiplayerGame::RemoveClientFromBanList_f, CMD_FL_GAME, "removes a client id from the ban list: removeClientFromBanList " ); + +#ifndef _XBOX +// shouchard: more voice chat stuff (non-XBOX) + cmdSystem->AddCommand( "clientvoicemute", idMultiplayerGame::VoiceMute_f, CMD_FL_GAME, "mute the specified player's incoming voicechat" ); + cmdSystem->AddCommand( "clientvoiceunmute", idMultiplayerGame::VoiceUnmute_f, CMD_FL_GAME, "unmute the specified player's incoming voicechat" ); +#endif // _XBOX +// RAVEN END + + // multiplayer server commands + cmdSystem->AddCommand( "verifyServerSettings", idGameLocal::VerifyServerSettings_f, CMD_FL_GAME, "verifies the game type can be played on the map" ); + cmdSystem->AddCommand( "serverMapRestart", idGameLocal::MapRestart_f, CMD_FL_GAME, "restart the current game" ); + cmdSystem->AddCommand( "serverForceReady", idMultiplayerGame::ForceReady_f,CMD_FL_GAME, "force all players ready" ); + cmdSystem->AddCommand( "serverNextMap", idGameLocal::NextMap_f, CMD_FL_GAME, "change to the next map" ); +#endif + + cmdSystem->AddCommand( "CheckTeamBalance", idMultiplayerGame::CheckTeamBalance_f, CMD_FL_GAME, "helper for team switching in the guis - " ); + + // localization help commands + cmdSystem->AddCommand( "nextGUI", Cmd_NextGUI_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleport the player to the next func_static with a gui" ); + cmdSystem->AddCommand( "testid", Cmd_TestId_f, CMD_FL_GAME|CMD_FL_CHEAT, "output the string for the specified id." ); + +// RAVEN BEGIN +// bdube: vehicle code + cmdSystem->AddCommand( "killVehicles", Cmd_KillVehicles_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills all vehicles" ); + cmdSystem->AddCommand( "killMessage", Cmd_KillMessage_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints a fake death message" ); + cmdSystem->AddCommand( "apState", Cmd_APState_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints AP state" ); +// bdube: jump points + cmdSystem->AddCommand( "jump", Cmd_DebugJump_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to a specific debug jump point" ); + cmdSystem->AddCommand( "nextjumppoint", Cmd_DebugNextJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the next debug jump point " ); + cmdSystem->AddCommand( "prevjumppoint", Cmd_DebugPrevJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the previous debug jump point" ); +// cdr: Added Extract Tactical + cmdSystem->AddCommand( "extract_tactical", Cmd_AASExtractTactical_f, CMD_FL_GAME, "pulls tactical information for the current position." ); +// RAVEN END + +// RAVEN BEGIN +// abahr + cmdSystem->AddCommand( "call", Cmd_CallScriptFunc_f, CMD_FL_GAME|CMD_FL_CHEAT, "calls script function and prints out return val" ); + cmdSystem->AddCommand( "setPlayerGravity", Cmd_SetPlayerGravity_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets players local gravity" ); +// cdr + cmdSystem->AddCommand( "ai_debugFilter", Cmd_AI_DebugFilter_f, CMD_FL_GAME, "ai_debugMove and ai_debugTactical only work on the specified entity (if none, does one you're looking at)", idGameLocal::ArgCompletion_AIName ); +// ddynerman: multiple arena/CW stuff + cmdSystem->AddCommand( "setInstance", Cmd_SetInstance_f, CMD_FL_GAME, "sets a player's world instance" ); + cmdSystem->AddCommand( "addIcon", Cmd_AddIcon_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a test icon" ); + cmdSystem->AddCommand( "listInstances", Cmd_ListInstances_f, CMD_FL_GAME, "lists instances" ); +// ddynerman: emote anims + cmdSystem->AddCommand( "emote", Cmd_PlayerEmote_f, CMD_FL_GAME, "plays an emote" ); + + cmdSystem->AddCommand( "checkSave", Cmd_CheckSave_f, CMD_FL_GAME, "tests save system" ); + +// jshepard: fade music in / out + cmdSystem->AddCommand( "fadeSound", Cmd_FadeSound_f, CMD_FL_GAME|CMD_FL_CHEAT, "fades all sound by X decibles over Y seconds" ); + +// mekberg: added. + cmdSystem->AddCommand( "setPMCVars", Cmd_SetPMCVars_f, CMD_FL_GAME, "Resets player movement cvars" ); + + cmdSystem->AddCommand( "testClientModel", Cmd_TestClientModel_f, CMD_FL_GAME, "" ); +#ifndef _FINAL + cmdSystem->AddCommand( "clientOverflowReliable", Cmd_ClientOverflowReliable_f, CMD_FL_GAME, "" ); +#endif +// RAVEN END +// RITUAL START +// squirrel: Mode-agnostic buymenus + cmdSystem->AddCommand( "buyMenu", Cmd_ToggleBuyMenu_f, CMD_FL_GAME, "Toggle buy menu (if in a buy zone and the game type supports it)" ); + cmdSystem->AddCommand( "buy", Cmd_BuyItem_f, CMD_FL_GAME, "Buy an item (if in a buy zone and the game type supports it)" ); +// RITUAL END + +} + +/* +================= +idGameLocal::ShutdownConsoleCommands +================= +*/ +void idGameLocal::ShutdownConsoleCommands( void ) { + cmdSystem->RemoveFlaggedCommands( CMD_FL_GAME ); +} diff --git a/src/game/gamesys/SysCmds.h b/src/game/gamesys/SysCmds.h new file mode 100644 index 0000000..33fabbf --- /dev/null +++ b/src/game/gamesys/SysCmds.h @@ -0,0 +1,34 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_CMDS_H__ +#define __SYS_CMDS_H__ + +void D_DrawDebugLines( void ); + +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ); + +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ); + +#endif /* !__SYS_CMDS_H__ */ diff --git a/src/game/gamesys/SysCvar.cpp b/src/game/gamesys/SysCvar.cpp new file mode 100644 index 0000000..3d39c5c --- /dev/null +++ b/src/game/gamesys/SysCvar.cpp @@ -0,0 +1,671 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#if defined( _DEBUG ) + #define BUILD_DEBUG "-debug" +#else + #define BUILD_DEBUG "-release" +#endif + +/* + +All game cvars should be defined here. + +*/ + +// RAVEN BEGIN +// ddynerman: our gameplay modes +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +const char *si_gameTypeArgs[] = { "singleplayer", "DM", "Tourney", "Team DM", "CTF", "Arena CTF", "DeadZone", NULL }; +const int si_numGameTypeArgs = sizeof( si_gameTypeArgs ) / sizeof( si_gameTypeArgs[0] ); +// RITUAL END +// RAVEN END +const char *si_readyArgs[] = { "Not Ready", "Ready", NULL }; +const char *si_spectateArgs[] = { "Play", "Spectate", NULL }; + +// RAVEN BEGIN +// ddynerman: our teams +const char *ui_teamArgs[] = { "Marine", "Strogg", NULL }; +// RAVEN END + +struct gameVersion_s { + gameVersion_s( void ) { sprintf( string, "%s %s V%s %s %s", GAME_NAME, GAME_BUILD_TYPE, VERSION_STRING_DOTTED, BUILD_STRING, __DATE__ ); } + char string[256]; +} gameVersion; + +idCVar g_version( "g_version", gameVersion.string, CVAR_GAME | CVAR_ROM, "game version" ); + +// noset vars +idCVar gamename( "gamename", GAME_VERSION, CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "" ); +idCVar gamedate( "gamedate", __DATE__, CVAR_GAME | CVAR_ROM, "" ); + +// server info +idCVar si_name( "si_name", "Quake 4 Server", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "name of the server" ); +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +//idCVar sq_numRoundsPerMatch( "dz_numRoundsPerMatch", "5", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of rounds per match in DeadZone", 1, 999999 ); +//idCVar sq_buyFreezeSeconds( "dz_buyFreezeSeconds", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of seconds players are frozen at the start of each round in DeadZone", 0, 30 ); +//idCVar sq_buyTimeSeconds( "dz_buyTimeSeconds", "20", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of additional seconds after buy freeze that buy zones are active", 0, 999999 ); +// squirrel: Mode-agnostic buymenus +idCVar si_isBuyingEnabled( "si_isBuyingEnabled", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable buying in current mode" ); +idCVar si_dropWeaponsInBuyingModes( "si_dropWeaponsInBuyingModes", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "dead players drop weapons, even in Buying game modes" ); +// RITUAL END +// RAVEN BEGIN +// ddynerman: new gametype strings +idCVar si_gameType( "si_gameType", si_gameTypeArgs[ 0 ], CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "game type - singleplayer, DM, Tourney, Team DM, CTF, Arena CTF, or DeadZone", si_gameTypeArgs, idCmdSystem::ArgCompletion_String ); +idCVar si_map( "si_map", "mp/q4dm1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map to be played next on server", idCmdSystem::ArgCompletion_MapName ); +idCVar si_mapCycle( "si_mapCycle", "", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map cycle list semicolon delimited" ); +// bdube: raise player limit +idCVar si_maxPlayers( "si_maxPlayers", "12", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "max number of players allowed on the server", 1, 16 ); +// ddynerman: min players to start +idCVar si_minPlayers( "si_minPlayers", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "min number of players to start a game (only when warmup is enabled)", 1, 16 ); +// ddynerman: CTF +idCVar si_captureLimit( "si_captureLimit", "5", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "score limit for CTF", 1, MP_PLAYER_MAXFRAGS ); +// shouchard: for tourney +idCVar si_tourneyLimit( "si_tourneyLimit", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of times a tourney will be run before cycling maps", 1, MP_PLAYER_MAXFRAGS ); +idCVar si_useReady( "si_useReady", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "require players to ready before starting a match" ); +idCVar si_allowVoting( "si_allowVoting", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable or disable server option voting" ); +// ddynerman: disable hitscan tint +idCVar si_allowHitscanTint( "si_allowHitscanTint", "2", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "use hitscan tint (e.g. rail color) 0 - no tinting allowed, 1 - player hitscan tinting allowed in DM and NO hitscan tinting in team games, 2 - player hitscan tinting allowed in DM and use team-color hitscan tints in team games" ); +idCVar si_privatePlayers( "si_privatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "number of private player slots reserved on the server. subtracts from si_maxPlayers, so a server with si_maxPlayers 16 and 4 private player slots will only allow 12 public players to connect - see g_privatePassword, privatePassword", 0, 16 ); +idCVar g_privatePassword( "g_privatePassword", "", CVAR_GAME | PC_CVAR_ARCHIVE, "server-side password to access reserved client slots, clients set privatePassword" ); +idCVar privatePassword( "privatePassword", "", CVAR_GAME | CVAR_NOCHEAT, "client password used to access a servers private player slots" ); +idCVar si_numPrivatePlayers( "si_numPrivatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "number of private slots currently in use" ); +idCVar si_suddenDeathRestart( "si_suddenDeathRestart", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "toggles whether or not to respawn players/items when team games enter sudden death" ); +// RAVEN END +idCVar si_fragLimit( "si_fragLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "frag limit", 0, MP_PLAYER_MAXFRAGS ); +idCVar si_timeLimit( "si_timeLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "time limit in minutes", 0, 60 ); +idCVar si_teamDamage( "si_teamDamage", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable team damage" ); +idCVar si_warmup( "si_warmup", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "do pre-game warmup" ); +idCVar si_usePass( "si_usePass", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable client password checking" ); +#ifdef _MPBETA + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | CVAR_ROM, "server is pure and does not allow modified data" ); +#else + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "server is pure and does not allow modified data" ); +#endif // _MPBETA +idCVar si_spectators( "si_spectators", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "allow spectators or require all clients to play" ); +idCVar si_shuffle( "si_shuffle", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "shuffle teams after each round" ); +// shouchard: g_balanceTDM->si_autobalance so we can also use it for CTF +// asalmon: Changed to archive only on PC +idCVar si_autobalance( "si_autobalance", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | PC_CVAR_ARCHIVE, "maintain even teams" ); + +// RAVEN BEGIN +// jscott: added entity filtering +idCVar si_entityFilter( "si_entityFilter", "", CVAR_GAME | CVAR_SERVERINFO, "filter to use when spawning entities" ); +idCVar si_countDown( "si_countDown", "10", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "pregame countdown in seconds", 4, 3600 ); +// MCG: added "weapon stay" option +idCVar si_weaponStay( "si_weaponStay", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "cannot pick up weapons you already have (get no ammo from them)" ); +// RAVEN END + +// RITUAL BEGIN +// DeadZone Mode and Buying related CVARS +idCVar si_deadZonePowerupTime( "si_deadZonePowerupTime", "45", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of time the dead zone powerup lasts" ); +idCVar si_buyModeStartingCredits( "si_buyModeStartingCredits", "1000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of credits players start with in buying enable games" ); +idCVar si_buyModeMaxCredits( "si_buyModeMaxCredits", "25000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Maximum amount of credits in buying enable games" ); +idCVar si_buyModeMinCredits( "si_buyModeMinCredits", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Minimum amount of credits in buying enable games" ); +idCVar si_controlTime( "si_controlTime", "120", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "Time required to hold the dead zone", 1, 999 ); +// RITUAL END + + +// user info +idCVar ui_name( "ui_name", "Player", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player name" ); +idCVar ui_team( "ui_team", ui_teamArgs[ 0 ], CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player team", ui_teamArgs, idCmdSystem::ArgCompletion_String ); +// RAVEN BEGIN +// ddynerman: new UI cvars +idCVar ui_model( "ui_model", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model, blank uses default model" ); +idCVar ui_model_backup( "ui_model_backup", "", CVAR_GAME | CVAR_USERINFO, "player model backup" ); +idCVar ui_model_marine( "ui_model_marine", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on marine team in team games, blank uses default model" ); +idCVar ui_model_strogg( "ui_model_strogg", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on strogg team in team games, blank uses default model" ); +idCVar ui_clan( "ui_clan", "", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player clan" ); +idCVar ui_hitscanTint( "ui_hitscanTint", "120.0 0.6 1.0", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "a tint applied to select hitscan effects. Specified as a value in HSV color space. Hue [0.0-360.0] Saturation [0.0-1.0] Value [0.75-1.0]" ); +// RAVEN END +idCVar ui_autoSwitch( "ui_autoSwitch", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto switch weapon" ); +idCVar ui_autoReload( "ui_autoReload", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto reload weapon" ); +idCVar ui_showGun( "ui_showGun", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "show gun" ); +idCVar ui_ready( "ui_ready", si_readyArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "player is ready to start playing", idCmdSystem::ArgCompletion_String ); +idCVar ui_spectate( "ui_spectate", si_spectateArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "play or spectate", idCmdSystem::ArgCompletion_String ); +idCVar ui_chat( "ui_chat", "0", CVAR_GAME | CVAR_USERINFO | CVAR_BOOL | CVAR_ROM | CVAR_CHEAT, "player is chatting" ); + +// change anytime vars +idCVar developer( "developer", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_forceModel( "g_forceModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces all players to this model in non-team gameplay modes. See g_forceStroggModel, g_forceMarineModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModel ); +idCVar g_forceStroggModel( "g_forceStroggModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Strogg team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelStrogg ); +idCVar g_forceMarineModel( "g_forceMarineModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Marine team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelMarine ); + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +idCVar g_fixedHorizFOV( "r_fixedHorizFOV", "0", CVAR_RENDERER | CVAR_BOOL, "vertical stretch for letterboxed cinematics authored for 4:3 aspect" ); +idCVar g_cinematic( "g_cinematic", "1", CVAR_GAME | CVAR_BOOL, "skips updating entities that aren't marked 'cinematic' '1' during cinematics" ); +idCVar g_cinematicMaxSkipTime( "g_cinematicMaxSkipTime", "600", CVAR_GAME | CVAR_FLOAT, "# of seconds to allow game to run when skipping cinematic. prevents lock-up when cinematic doesn't end.", 0, 3600 ); + +idCVar g_muzzleFlash( "g_muzzleFlash", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show muzzle flashes" ); +idCVar g_projectileLights( "g_projectileLights", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show dynamic lights on projectiles" ); +idCVar g_doubleVision( "g_doubleVision", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show double vision when taking damage" ); +idCVar g_monsters( "g_monsters", "1", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_decals( "g_decals", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "show decals such as bullet holes" ); +idCVar g_knockback( "g_knockback", "1000", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar g_skill( "g_skill", "1", CVAR_GAME | CVAR_INTEGER, "difficulty level", 0, MAX_SKILL_LEVELS - 1 ); +idCVar g_nightmare( "g_nightmare", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "if nightmare mode is allowed" ); +idCVar g_gravity( "g_gravity", DEFAULT_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "singleplayer gravity" ); +idCVar g_mp_gravity( "g_mp_gravity", DEFAULT_MP_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "multiplayer gravity" ); +idCVar g_skipFX( "g_skipFX", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_skipParticles( "g_skipParticles", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_disasm( "g_disasm", "0", CVAR_GAME | CVAR_BOOL, "disassemble script into base/script/disasm.txt on the local drive when script is compiled" ); +idCVar g_debugBounds( "g_debugBounds", "0", CVAR_GAME | CVAR_BOOL, "checks for models with bounds > 2048" ); +idCVar g_debugAnim( "g_debugAnim", "-1", CVAR_GAME | CVAR_INTEGER, "displays information on which animations are playing on the specified entity number. set to -1 to disable." ); +idCVar g_debugMove( "g_debugMove", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugDamage( "g_debugDamage", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugWeapon( "g_debugWeapon", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugScript( "g_debugScript", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugMover( "g_debugMover", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugTriggers( "g_debugTriggers", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugCinematic( "g_debugCinematic", "0", CVAR_GAME, "set to the name of the state you want to debug or * for all" ); +// RAVEN BEGIN +// bdube: added +idCVar g_debugState( "g_debugState", "0", CVAR_GAME, "" ); +idCVar g_stopTime( "g_stopTime", "0", CVAR_GAME | CVAR_BOOL, "" ); +//idCVar g_damageScale( "g_damageScale", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale final damage on player by this factor" ); +// RAVEN END +idCVar g_armorProtection( "g_armorProtection", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage" ); +idCVar g_armorProtectionMP( "g_armorProtectionMP", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage in mp" ); +idCVar g_useDynamicProtection( "g_useDynamicProtection", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE, "scale damage and armor dynamically to keep the player alive more often" ); +idCVar g_healthTakeTime( "g_healthTakeTime", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how often to take health in nightmare mode" ); +idCVar g_healthTakeAmt( "g_healthTakeAmt", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how much health to take in nightmare mode" ); +idCVar g_healthTakeLimit( "g_healthTakeLimit", "25", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how low can health get taken in nightmare mode" ); + + + +idCVar g_showPVS( "g_showPVS", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +idCVar g_showTargets( "g_showTargets", "0", CVAR_GAME | CVAR_BOOL, "draws entities and thier targets. hidden entities are drawn grey." ); +idCVar g_showTriggers( "g_showTriggers", "0", CVAR_GAME | CVAR_BOOL, "draws trigger entities (orange) and thier targets (green). disabled triggers are drawn grey." ); +idCVar g_showCollisionWorld( "g_showCollisionWorld", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showCollisionModels( "g_showCollisionModels", "0", CVAR_GAME | CVAR_INTEGER, "0 = off, 1 = draw collision models, 2 = only draw player collision models. g_maxShowDistance controls distance." ); +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces +idCVar g_showCollisionTraces( "g_showCollisionTraces", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +// ddynerman: SD's clip sector code +idCVar g_showClipSectors( "g_showClipSectors", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showClipSectorFilter( "g_showClipSectorFilter", "0", CVAR_GAME, "" ); +idCVar g_showAreaClipSectors( "g_showAreaClipSectors", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN END +idCVar g_maxShowDistance( "g_maxShowDistance", "128", CVAR_GAME | CVAR_FLOAT, "Distance at which to draw clipmodels and clipworld - Will significantly hurt performance at values above 512" ); +idCVar g_showEntityInfo( "g_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showviewpos( "g_showviewpos", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showcamerainfo( "g_showcamerainfo", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "displays the current frame # for the camera when playing cinematics" ); +idCVar g_showTestModelFrame( "g_showTestModelFrame", "0", CVAR_GAME | CVAR_BOOL, "displays the current animation and frame # for testmodels" ); +idCVar g_showActiveEntities( "g_showActiveEntities", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around thinking entities. dormant entities (outside of pvs) are drawn yellow. non-dormant are green." ); +idCVar g_showEnemies( "g_showEnemies", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around monsters that have targeted the the player" ); + +idCVar g_frametime( "g_frametime", "0", CVAR_GAME | CVAR_BOOL, "displays timing information for each game frame" ); +idCVar g_timeentities( "g_timeEntities", "0", CVAR_GAME | CVAR_FLOAT, "when non-zero, shows entities whose think functions exceeded the # of milliseconds specified" ); + +// RAVEN BEGIN +// bdube: frame command debugging +idCVar g_showFrameCmds( "g_showFrameCmds", "0", CVAR_GAME | CVAR_BOOL, "displays frame commands as they are executed" ); +idCVar g_showGodDamage( "g_showGodDamage", "0", CVAR_GAME | CVAR_BOOL, "displays the amount of damage taken while in god mode on the hud" ); +idCVar g_debugVehicle( "g_debugVehicle", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +// RAVEN BEGIN +// twhitaker: for rvVehicleDriver +idCVar g_debugVehicleDriver( "g_debugVehicleDriver", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the func_vehicle_driver" ); +idCVar g_debugVehicleAI( "g_debugVehicleAI", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the vehicle ai system" ); +idCVar g_vehicleMode( "g_vehicleMode", "1", CVAR_GAME | CVAR_INTEGER, "enables the new vehicle control system for the GEV." ); +// RAVEN END +idCVar g_allowVehicleGunOverheat( "g_allowVehicleGunOverheat","1", CVAR_GAME | CVAR_BOOL, "allows disabling the gun overheating mechanism for vehicles that use it." ); + +idCVar ai_debugScript( "ai_debugScript", "-1", CVAR_GAME | CVAR_INTEGER, "displays script calls for the specified monster entity number" ); +idCVar ai_debugMove( "ai_debugMove", "0", CVAR_GAME | CVAR_BOOL, "draws movement information for monsters" ); +idCVar ai_debugTrajectory( "ai_debugTrajectory", "0", CVAR_GAME | CVAR_BOOL, "draws trajectory tests for monsters" ); +idCVar ai_debugTactical( "ai_debugTactical", "0", CVAR_GAME, "draws tactical information for monsters" ); +idCVar ai_debugHelpers( "ai_debugHelpers", "0", CVAR_GAME, "draws ai helpers" ); +idCVar ai_debugFilterString( "ai_debugFilterString", "", CVAR_GAME, "see ai_debugFilter" ); +idCVar ai_testPredictPath( "ai_testPredictPath", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCombatNodes( "ai_showCombatNodes", "0", CVAR_GAME | CVAR_BOOL, "draws attack cones for monsters" ); +idCVar ai_showPaths( "ai_showPaths", "0", CVAR_GAME | CVAR_BOOL, "draws path_* entities" ); +idCVar ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "0", CVAR_GAME | CVAR_INTEGER, "draws obstacle avoidance information for monsters. if 2, draws obstacles for player, as well", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar ai_blockedFailSafe( "ai_blockedFailSafe", "1", CVAR_GAME | CVAR_BOOL, "enable blocked fail safe handling" ); +idCVar ai_debugSquad( "ai_debugSquad", "0", CVAR_GAME | CVAR_BOOL, "draws squad info for allies" ); +idCVar ai_debugStealth( "ai_debugStealth", "0", CVAR_GAME | CVAR_INTEGER, "draws suspicion info for enemies" ); +idCVar ai_allowTacticalRush( "ai_allowTacticalRush", "1", CVAR_GAME | CVAR_BOOL, "allows tactical ai to rush an enemy when hurt" ); + + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +idCVar ai_speeds( "ai_speeds", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_freeze( "ai_freeze", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_animShow( "ai_animShow", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCover( "ai_showCover", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_showTacticalFeatures( "ai_showTacticalFeatures", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_disableEntTactical( "ai_disableEntTactical", "0", CVAR_GAME | CVAR_BOOL, "disables tactical points around entities" ); +idCVar ai_disableAttacks( "ai_disableAttacks", "0", CVAR_GAME | CVAR_BOOL, "disables attack decisions" ); +idCVar ai_disableSimpleThink( "ai_disableSimpleThink", "0", CVAR_GAME | CVAR_BOOL, "disables simple thinking in AI entities" ); +idCVar ai_disableCover( "ai_disableCover", "0", CVAR_GAME | CVAR_BOOL, "disables AI using cover points" ); +//cdr: use new master move functions +idCVar ai_useRVMasterMove( "ai_useRVMasterMove", "0", CVAR_GAME | CVAR_BOOL, "changes AI to use new master move function" ); +//jshepard: allow out of date AAS files to be used, for testing +idCVar ai_allowOldAAS( "ai_allowOldAAS", "0", CVAR_GAME | CVAR_BOOL, "allows AI to use most recent AAS file, even if it is not up-to-date. Enable only for testing."); +// twhitaker: debugging support for eye focus +idCVar ai_debugEyeFocus( "ai_debugEyeFocus", "0", CVAR_GAME | CVAR_BOOL, "draws eye focus info" ); +//mcg: always allow player to push buddies, unless scripted +idCVar ai_playerPushAlways( "ai_playerPushAlways", "1", CVAR_GAME | CVAR_BOOL, "always allow player to push buddies, unless scripted" ); +// RAVEN END + +idCVar g_dvTime( "g_dvTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvAmplitude( "g_dvAmplitude", "0.001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvFrequency( "g_dvFrequency", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_kickTime( "g_kickTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_kickAmplitude( "g_kickAmplitude", "0.0001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobTime( "g_blobTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobSize( "g_blobSize", "1", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_testHealthVision( "g_testHealthVision", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_editEntityMode( "g_editEntityMode", "0", CVAR_GAME | CVAR_INTEGER, "0 = off\n" + "1 = lights\n" + "2 = sounds\n" + "3 = articulated figures\n" + "4 = particle systems\n" + "5 = monsters\n" + "6 = entity names\n" +// RAVEN BEGIN +// bdube: extended + "7 = entity models\n" + "8 = effects", 0, 8, idCmdSystem::ArgCompletion_Integer<0,8> ); +// rhummer: Added archive flag. +idCVar g_editEntityDistance( "g_editEntityDistance", "512", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit" ); +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. Also added archive flag. +idCVar g_editEntityTextDistance( "g_editEntityTextDistance", "256", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit text information"); +idCVar g_testCTF( "g_testCTF", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "" ); +// rjohnson: entity usage stats +idCVar g_keepEntityStats( "g_keepEntityStats", "0", CVAR_GAME | CVAR_CHEAT |CVAR_BOOL, "keep track of entity usage stats" ); +// RAVEN END +idCVar g_dragEntity( "g_dragEntity", "0", CVAR_GAME | CVAR_BOOL, "allows dragging physics objects around by placing the crosshair over them and holding the fire button" ); +idCVar g_dragDamping( "g_dragDamping", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dragShowSelection( "g_dragShowSelection", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_dropItemRotation( "g_dropItemRotation", "", CVAR_GAME, "" ); + +idCVar g_vehicleVelocity( "g_vehicleVelocity", "1000", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleForce( "g_vehicleForce", "50000", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar ik_enable( "ik_enable", "1", CVAR_GAME | CVAR_BOOL, "enable IK" ); +idCVar ik_debug( "ik_debug", "0", CVAR_GAME | CVAR_BOOL, "show IK debug lines" ); + +idCVar af_useLinearTime( "af_useLinearTime", "1", CVAR_GAME | CVAR_BOOL, "use linear time algorithm for tree-like structures" ); +idCVar af_useImpulseFriction( "af_useImpulseFriction", "0", CVAR_GAME | CVAR_BOOL, "use impulse based contact friction" ); +idCVar af_useJointImpulseFriction( "af_useJointImpulseFriction","0", CVAR_GAME | CVAR_BOOL, "use impulse based joint friction" ); +idCVar af_useSymmetry( "af_useSymmetry", "1", CVAR_GAME | CVAR_BOOL, "use constraint matrix symmetry" ); +idCVar af_skipSelfCollision( "af_skipSelfCollision", "0", CVAR_GAME | CVAR_BOOL, "skip self collision detection" ); +idCVar af_skipLimits( "af_skipLimits", "0", CVAR_GAME | CVAR_BOOL, "skip joint limits" ); +idCVar af_skipFriction( "af_skipFriction", "0", CVAR_GAME | CVAR_BOOL, "skip friction" ); +idCVar af_forceFriction( "af_forceFriction", "-1", CVAR_GAME | CVAR_FLOAT, "force the given friction value" ); +idCVar af_maxLinearVelocity( "af_maxLinearVelocity", "128", CVAR_GAME | CVAR_FLOAT, "maximum linear velocity" ); +idCVar af_maxAngularVelocity( "af_maxAngularVelocity", "1.57", CVAR_GAME | CVAR_FLOAT, "maximum angular velocity" ); +idCVar af_timeScale( "af_timeScale", "1", CVAR_GAME | CVAR_FLOAT, "scales the time" ); +idCVar af_jointFrictionScale( "af_jointFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the joint friction" ); +idCVar af_contactFrictionScale( "af_contactFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the contact friction" ); +idCVar af_highlightBody( "af_highlightBody", "", CVAR_GAME, "name of the body to highlight" ); +idCVar af_highlightConstraint( "af_highlightConstraint", "", CVAR_GAME, "name of the constraint to highlight" ); +idCVar af_showTimings( "af_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show articulated figure cpu usage" ); +idCVar af_showConstraints( "af_showConstraints", "0", CVAR_GAME | CVAR_BOOL, "show constraints" ); +idCVar af_showConstraintNames( "af_showConstraintNames", "0", CVAR_GAME | CVAR_BOOL, "show constraint names" ); +idCVar af_showConstrainedBodies( "af_showConstrainedBodies", "0", CVAR_GAME | CVAR_BOOL, "show the two bodies contrained by the highlighted constraint" ); +idCVar af_showPrimaryOnly( "af_showPrimaryOnly", "0", CVAR_GAME | CVAR_BOOL, "show primary constraints only" ); +idCVar af_showTrees( "af_showTrees", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures" ); +idCVar af_showLimits( "af_showLimits", "0", CVAR_GAME | CVAR_BOOL, "show joint limits" ); +idCVar af_showBodies( "af_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show bodies" ); +idCVar af_showBodyNames( "af_showBodyNames", "0", CVAR_GAME | CVAR_BOOL, "show body names" ); +idCVar af_showMass( "af_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each body" ); +idCVar af_showTotalMass( "af_showTotalMass", "0", CVAR_GAME | CVAR_BOOL, "show the total mass of each articulated figure" ); +idCVar af_showInertia( "af_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each body" ); +idCVar af_showVelocity( "af_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each body" ); +idCVar af_showActive( "af_showActive", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures of articulated figures not at rest" ); +idCVar af_testSolid( "af_testSolid", "1", CVAR_GAME | CVAR_BOOL, "test for bodies initially stuck in solid" ); + +idCVar rb_showTimings( "rb_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show rigid body cpu usage" ); +idCVar rb_showBodies( "rb_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies" ); +idCVar rb_showMass( "rb_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each rigid body" ); +idCVar rb_showInertia( "rb_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each rigid body" ); +idCVar rb_showVelocity( "rb_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each rigid body" ); +idCVar rb_showActive( "rb_showActive", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies that are not at rest" ); + +// RAVEN BEGIN +// bdube: more rigid body debug +idCVar rb_showContacts( "rb_showContacts", "0", CVAR_GAME | CVAR_BOOL, "show rigid body contacts" ); +// RAVEN END + +// The default values for player movement cvars are set in def/player.def +idCVar pm_jumpheight( "pm_jumpheight", "48", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "approximate hieght the player can jump" ); +idCVar pm_stepsize( "pm_stepsize", "16", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "maximum height the player can step up without jumping" ); +idCVar pm_crouchspeed( "pm_crouchspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while crouched" ); +// RAVEN BEGIN +idCVar pm_speed( "pm_speed", "160", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while running" ); +idCVar pm_walkspeed( "pm_walkspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while walking" ); +// RAVEN END +idCVar pm_noclipspeed( "pm_noclipspeed", "270", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while in noclip" ); +idCVar pm_spectatespeed( "pm_spectatespeed", "450", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while spectating" ); +idCVar pm_spectatebbox( "pm_spectatebbox", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "size of the spectator bounding box" ); +idCVar pm_usecylinder( "pm_usecylinder", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_BOOL | CVAR_NORESET, "use a cylinder approximation instead of a bounding box for player collision detection" ); +idCVar pm_minviewpitch( "pm_minviewpitch", "-89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look up (negative values are up)" ); +idCVar pm_maxviewpitch( "pm_maxviewpitch", "89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look down" ); +idCVar pm_stamina( "pm_stamina", "24", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "length of time player can run" ); +idCVar pm_staminathreshold( "pm_staminathreshold", "45", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "when stamina drops below this value, player gradually slows to a walk" ); +idCVar pm_staminarate( "pm_staminarate", "0.75", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "rate that player regains stamina. divide pm_stamina by this value to determine how long it takes to fully recharge." ); + +// ddynerman: adjusted bboxes to actual height +idCVar pm_normalheight( "pm_normalheight", "77", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while standing" ); +idCVar pm_crouchheight( "pm_crouchheight", "49", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while crouched" ); + +idCVar pm_crouchviewheight( "pm_crouchviewheight", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while crouched" ); +idCVar pm_normalviewheight( "pm_normalviewheight", "68", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while standing" ); + +idCVar pm_deadheight( "pm_deadheight", "20", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while dead" ); +idCVar pm_deadviewheight( "pm_deadviewheight", "10", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while dead" ); +idCVar pm_crouchrate( "pm_crouchrate", "0.87", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "time it takes for player's view to change from standing to crouching" ); +idCVar pm_bboxwidth( "pm_bboxwidth", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "x/y size of player's bounding box" ); +idCVar pm_crouchbob( "pm_crouchbob", "0.5", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob much faster when crouched" ); +idCVar pm_walkbob( "pm_walkbob", "0.3", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob slowly when walking" ); +idCVar pm_runbob( "pm_runbob", "0.4", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob faster when running" ); +idCVar pm_runpitch( "pm_runpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_runroll( "pm_runroll", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobup( "pm_bobup", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobpitch( "pm_bobpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobroll( "pm_bobroll", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT, "" ); +idCVar pm_thirdPersonRange( "pm_thirdPersonRange", "80", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "camera distance from player in 3rd person" ); +idCVar pm_thirdPersonHeight( "pm_thirdPersonHeight", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "height of camera from normal view height in 3rd person" ); +idCVar pm_thirdPersonAngle( "pm_thirdPersonAngle", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "direction of camera from player in 3rd person in degrees (0 = behind player, 180 = in front)" ); +idCVar pm_thirdPersonClip( "pm_thirdPersonClip", "1", CVAR_GAME | CVAR_BOOL, "clip third person view into world space" ); +idCVar pm_thirdPerson( "pm_thirdPerson", "0", CVAR_GAME | CVAR_BOOL, "enables third person view" ); +idCVar pm_thirdPersonDeath( "pm_thirdPersonDeath", "0", CVAR_GAME | CVAR_BOOL, "enables third person view when player dies" ); +idCVar pm_modelView( "pm_modelView", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "draws camera from POV of player model (1 = always, 2 = when dead)", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar pm_airTics( "pm_air", "1800", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "how long in milliseconds the player can go without air before he starts taking damage" ); + +// RAVEN BEGIN +// asalmon: parameters for aim assistance on Xenon - or a non-final pc build so Caryn can edit the guis +#if defined( _XBOX ) || !defined( _FINAL ) +idCVar pm_AimAssist( "pm_AimAssist", "2", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE , "Enable Xbox aim assistance. 1 to use change player view method. 2 to use change muzzle aim method.\n"); +idCVar pm_AimAssistDistance( "pm_AimAssistDistance", "1000", CVAR_GAME | CVAR_INTEGER, "The max aim assist distance.\n"); +idCVar pm_AimAssistThreshold( "pm_AimAssistThreshold", "1.0", CVAR_GAME | CVAR_FLOAT, "Threshold by which the projectile is aimed at the offending target.\n"); +idCVar pm_AimAssistFOV( "pm_AimAssistFOV", "10", CVAR_GAME | CVAR_INTEGER, "The field of view for aim assistance.\n"); +idCVar pm_AimAssistBump( "pm_AimAssistBump", "10", CVAR_GAME | CVAR_INTEGER, "The percentage of correction applied either to the view or the muzzle aim.\n"); +idCVar pm_showAimAssist( "pm_showAimAssist", "0", CVAR_GAME | CVAR_BOOL, "Draw aim assist frustum and bounding boxes.\n"); +idCVar pm_AimAssistSlow( "pm_AimAssistSlow", "50", CVAR_GAME | CVAR_INTEGER, "The percentage to slow the turning motion by when targeting an enemy.\n"); + +//asalmon: xenon controller config cvars +idCVar pm_ThumbstickConfig( "pm_ThumbstickConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the thumbstick config on Xenon. 0 right handed, 1 left handed.\n"); +idCVar pm_ButtonConfig( "pm_ButtonConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the button configuration for Xenon.\n"); +idCVar pm_Inversion( "pm_Inversion", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "invert look up and down\n"); + +idCVar pm_VLookSens( "pm_VLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HLookSens( "pm_HLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_VMoveSens( "pm_VMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HMoveSens( "pm_HMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); + +idCVar pm_voiceEnabled( "pm_voiceEnabled", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL | CVAR_GUI, "Enable/disable voice.\n"); + +//asalmon: Xenon leaderboard cvars +idCVar ui_LeaderboardView( "ui_LeaderboardView", "17", CVAR_INTEGER | CVAR_NOCHEAT, "Which leaderboard to show.\n"); +idCVar ui_LeaderboardSort( "ui_LeaderboardSort", "1", CVAR_INTEGER | CVAR_NOCHEAT, "How to sort the leaderboard. 0 for rating, 1 for ranking, 2 for friends, 3 find logged in player.\n"); + +//nrausch +idCVar pm_RocketJumpAutocenter( "pm_RocketJumpAutocenter", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Automatic autocentering following a rocket jump\n"); +idCVar pm_IAmACheater( "pm_IAmACheater", "0", CVAR_GAME | CVAR_BOOL, "Whomever is playing is a dirty, rotten cheater\n"); + +idCVar g_systemLinkMatch( "g_systemLinkMatch", "0", CVAR_INTEGER, "In a system link game\n"); + +//asalmon: cvars for Live teams. Teams will now be a post launch feature but this was left here in case it is of use on future projects +//idCVar ui_LiveClanName( "ui_LiveClanName", "My Clan", CVAR_GAME | CVAR_USERINFO, "The name of the live clan being created\n"); +//idCVar ui_LiveClanDesc( "ui_LiveClanDesc", "A Quake 4 clan", CVAR_GAME | CVAR_USERINFO, "The description of the live clan being created\n"); +//idCVar ui_LiveClanMotto( "ui_LiveClanMotto", "We love Quake 4", CVAR_GAME | CVAR_USERINFO, "The motto of the live clan being created\n"); +//idCVar ui_LiveClanUrl( "ui_LiveClanUrl", "www.ravensoft.com", CVAR_GAME | CVAR_USERINFO, "The url of the live clan being created\n"); +// +//idCVar ui_LiveRecruitName( "ui_LiveRecruitName", "Recruit Name Here", CVAR_GAME | CVAR_USERINFO, "name of the gamer you are trying recruit\n"); +//idCVar ui_LiveRecruitPDelete( "ui_LiveRecruitPDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit delete permissions\n"); +//idCVar ui_LiveRecruitPData( "ui_LiveRecruitPData", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit modify data permissions\n"); +//idCVar ui_LiveRecruitPMemberPermissions("ui_LiveRecruitPMemberPermissions", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member modify permissions\n"); +//idCVar ui_LiveRecruitPMemberDelete( "ui_LiveRecruitPMemberDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member delete permissions\n"); +//idCVar ui_LiveRecruitPMemberRecruit( "ui_LiveRecruitPMemberRecruit", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member recruit permissions\n"); +#endif + +idCVar pm_zoomedSlow( "pm_zoomedSlow", "100", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "Slow look speed while zoomed 0..100% of speed"); + +#ifndef _XENON +idCVar pm_isZoomed( "pm_isZoomed", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "if nonzero, is the slow speed"); +#endif + +// nmckenzie: added ability to try alternate accelerations. +idCVar pm_acceloverride( "pm_acceloverride", "0", CVAR_GAME | CVAR_FLOAT, "Adjust the player acceleration." ); +idCVar pm_frictionoverride( "pm_frictionoverride", "-1", CVAR_GAME | CVAR_FLOAT, "Adjust the player friciton." ); +idCVar pm_forcespectatormove( "pm_forcespectatormove", "0", CVAR_GAME | CVAR_FLOAT, "Force the player to move like a spectator (fly)." ); +// bdube: added vehicle cvars +idCVar pm_vehicleCameraSnap( "pm_vehicleCameraSnap", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraMinDist( "pm_vehicleCameraMinDist", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraSpeedScale( "pm_vehicleCameraSpeedScale", "0.5", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraScaleMax( "pm_vehicleCameraScaleMax", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleSoundLerpScale( "pm_vehicleSoundLerpScale", "10", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +// RAVEN END + +idCVar g_showPlayerShadow( "g_showPlayerShadow", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables shadow of player model" ); + +idCVar g_skipPlayerShadowsMP( "g_skipPlayerShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all player shadows in multiplayer" ); +idCVar g_skipItemShadowsMP( "g_skipItemShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all item shadows in multiplayer" ); +idCVar g_simpleItems( "g_simpleItems", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "render icon representations of items instead of the actual model" ); +idCVar g_showHud( "g_showHud", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar g_showProjectilePct( "g_showProjectilePct", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables display of player hit percentage" ); +// RAVEN BEGIN +// dluetscher: changed to g_brassTime +idCVar g_brassTime( "g_brassTime", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "amount of time brass should stay in the world before dissapearing, set to 0 to disable brass" ); +// RAVEN END +idCVar g_gun_x( "g_gunX", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_y( "g_gunY", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_z( "g_gunZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_viewNodalX( "g_viewNodalX", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_viewNodalZ( "g_viewNodalZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN BEGIN +// jshepard: fov as a float for smoother transitions? +idCVar g_fov( "g_fov", "90", CVAR_GAME | CVAR_FLOAT | CVAR_NOCHEAT, "" ); +// RAVEN END +idCVar g_skipViewEffects( "g_skipViewEffects", "0", CVAR_GAME | CVAR_BOOL, "skip damage and other view effects" ); +idCVar g_mpWeaponAngleScale( "g_mpWeaponAngleScale", "0", CVAR_GAME | CVAR_FLOAT, "Control the weapon sway in MP" ); + +// RAVEN BEGIN +// bdube: crosshairs +// mekberg: custom size +idCVar g_crosshairSize( "g_crosshairSize", "32", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "crosshair size: 16,24,32,40,48", 16, 48 ); +//idCVar g_crosshairColor( "g_crosshairColor", "0.458 0.894 0.247 .75", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +idCVar g_crosshairColor( "g_crosshairColor", "1 1 1 1", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +// cnicholson: Custom crosshair +idCVar g_crosshairCustom( "g_crosshairCustom", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "sets the custom combat crosshair" ); +idCVar g_crosshairCustomFile( "g_crosshairCustomFile", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "stores the custom crosshair's filename" ); +idCVar g_crosshairCharInfoFar( "g_crosshairCharInfoFar", "1", CVAR_GAME | CVAR_BOOL, "instead of a green crosshair from far away, full character info always draws" ); +// bdube: database entries +idCVar g_showHudPopups( "g_showHudPopups", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "displays objective and database popups on the hud" ); +idCVar g_showRange( "g_showRange", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "shows the range from the player to the first collision under the players crosshair" ); +// bdube: debug hud +idCVar g_showDebugHud( "g_showDebugHud", "0", CVAR_GAME | CVAR_INTEGER, "displays the debug hud\n" + "0 = off\n" + "1 = player\n" + "2 = physics\n" + "3 = AI\n" + "4 = vehicle\n" + "5 = performance\n" + "6 = effects\n" + "7 = map information\n" + "8 = AI performance\n" + "9 = MP\n" + "10 = Sound\n" + "32 = scratch\n" ); +// bdube: cvar for messing with foreshortening and gun position +idCVar g_gun_pitch( "g_gunPitch", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_yaw( "g_gunYaw", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_roll( "g_gunRoll", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// abahr: +idCVar g_gunViewStyle( "g_gunViewStyle", "0", CVAR_GAME | CVAR_NOCHEAT | PC_CVAR_ARCHIVE | CVAR_INTEGER, "style presets\n" + "0 = Q3 style\n" + "1 = Shouldered style\n"); +// jscott: cvar for debugging playbacks +idCVar g_showPlayback( "g_showPlayback", "0", CVAR_GAME | CVAR_INTEGER, "show g_currentPlayback" ); +idCVar g_currentPlayback( "g_currentPlayback", "", CVAR_GAME, "name of playback shown by g_showPlayback" ); +// jscott: unused +//idCVar g_testParticle( "g_testParticle", "0", CVAR_GAME | CVAR_INTEGER, "test particle visualation, set by the particle editor" ); +//idCVar g_testParticleName( "g_testParticleName", "", CVAR_GAME, "name of the particle being tested by the particle editor" ); +// RAVEN END +idCVar g_testModelRotate( "g_testModelRotate", "0", CVAR_GAME, "test model rotation speed" ); +idCVar g_testPostProcess( "g_testPostProcess", "", CVAR_GAME, "name of material to draw over screen" ); +idCVar g_testModelAnimate( "g_testModelAnimate", "0", CVAR_GAME | CVAR_INTEGER, "test model animation,\n" + "0 = cycle anim with origin reset\n" + "1 = cycle anim with fixed origin\n" + "2 = cycle anim with continuous origin\n" + "3 = frame by frame with continuous origin\n" + "4 = play anim once\n" + "5 = frame by frame with fixed origin", 0, 5, idCmdSystem::ArgCompletion_Integer<0,5> ); +idCVar g_testModelBlend( "g_testModelBlend", "0", CVAR_GAME | CVAR_INTEGER, "number of frames to blend" ); +idCVar g_testDeath( "g_testDeath", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// bdube: added scoreboard testing +idCVar g_testScoreboard( "g_testScoreboard", "0", CVAR_GAME | CVAR_INTEGER, "number of clients to test in the scoreboard gui" ); +idCVar g_testPlayer( "g_testPlayer", "", CVAR_GAME, "test player classname" ); +// RAVEN END +idCVar g_exportMask( "g_exportMask", "", CVAR_GAME, "" ); +idCVar g_flushSave( "g_flushSave", "0", CVAR_GAME | CVAR_BOOL, "1 = don't buffer file writing for save games." ); + +idCVar aas_test( "aas_test", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreas( "aas_showAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreaBounds( "aas_showAreaBounds", "0", CVAR_GAME | CVAR_INTEGER, "When show areas is on, this draws the bounds of the areas, too..." ); +idCVar aas_showPath( "aas_showPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showFlyPath( "aas_showFlyPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showWallEdges( "aas_showWallEdges", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_showHideArea( "aas_showHideArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_pullPlayer( "aas_pullPlayer", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_randomPullPlayer( "aas_randomPullPlayer", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_goalArea( "aas_goalArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showPushIntoArea( "aas_showPushIntoArea", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// rjohnson: added aas help +idCVar aas_showProblemAreas( "aas_showProblemAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// cdr: added rev reach +idCVar aas_showRevReach( "aas_showRevReach", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +idCVar g_password( "g_password", "", CVAR_GAME | PC_CVAR_ARCHIVE, "game password" ); +idCVar password( "password", "", CVAR_GAME | CVAR_NOCHEAT, "client password used when connecting" ); + +// RAVEN BEGIN +idCVar g_gameReviewPause( "g_gameReviewPause", "30", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER | PC_CVAR_ARCHIVE, "scores review time in seconds (at end game)", 2, 3600 ); +// RAVEN END +idCVar net_clientPredictGUI( "net_clientPredictGUI", "1", CVAR_GAME | CVAR_BOOL, "test guis in networking without prediction" ); + +idCVar si_voteFlags( "si_voteFlags", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER | PC_CVAR_ARCHIVE, "vote flags. bit mask of votes not allowed on this server\n" + "bit 0 (+1) restart now\n" + "bit 1 (+2) min players\n" + "bit 2 (+4) auto balance teams\n" + "bit 3 (+8) shuffle teams\n" + "bit 4 (+16) kick player\n" + "bit 5 (+32) change map\n" + "bit 6 (+64) change gametype\n" + "bit 7 (+128) time limit\n" + "bit 8 (+256) tourney limit\n" + "bit 9 (+512) capture limit\n" + "bit 10 (+1024) frag limit" ); + +idCVar g_mapCycle( "g_mapCycle", "mapcycle", CVAR_GAME | CVAR_ARCHIVE, "map cycling script for multiplayer games - see mapcycle.scriptcfg" ); + +// RAVEN BEGIN +// bdube: client entitiy cvars +idCVar g_gamelog( "g_gamelog", "0", CVAR_GAME | CVAR_BOOL, "enables game logging" ); +idCVar cl_showEntityInfo( "cl_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +// ddynerman: announcer delay time +idCVar g_announcerDelay( "g_announcerDelay", "1000", CVAR_SOUND | PC_CVAR_ARCHIVE, "no more than one announcer sound will be played in this many ms" ); +// jnewquist: Option to force undying state +idCVar g_forceUndying( "g_forceUndying", "0", CVAR_GAME | CVAR_BOOL, "forces undying state" ); +// mcg: combat performance testing cvars +idCVar g_perfTest_weaponNoFX( "g_perfTest_weaponNoFX", "0", CVAR_GAME | CVAR_BOOL, "no muzzle flash, brass eject, muzzle fx, tracers, impact fx, blood decals or blood splats (whew!)" ); +idCVar g_perfTest_hitscanShort( "g_perfTest_hitscanShort", "0", CVAR_GAME | CVAR_BOOL, "all hitscans capped at 2048" ); +idCVar g_perfTest_hitscanBBox( "g_perfTest_hitscanBBox", "0", CVAR_GAME | CVAR_BOOL, "all hitscans vs bbox, not rendermodel" ); +idCVar g_perfTest_aiStationary( "g_perfTest_aiStationary", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no combat movement" ); +idCVar g_perfTest_aiNoDodge( "g_perfTest_aiNoDodge", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no dodging" ); +idCVar g_perfTest_aiNoRagdoll( "g_perfTest_aiNoRagdoll", "0", CVAR_GAME | CVAR_BOOL, "ai does not ragdoll" ); +idCVar g_perfTest_aiNoObstacleAvoid( "g_perfTest_aiNoObstacleAvoid", "0", CVAR_GAME | CVAR_BOOL, "ai does not attempt obstacle avoidance" ); +idCVar g_perfTest_aiUndying( "g_perfTest_aiUndying", "0", CVAR_GAME | CVAR_BOOL, "makes all AI undying" ); +idCVar g_perfTest_aiNoVisTrace( "g_perfTest_aiNoVisTrace", "0", CVAR_GAME | CVAR_BOOL, "ai does no vis traces" ); +idCVar g_perfTest_noJointTransform( "g_perfTest_noJointTransform", "0", CVAR_GAME | CVAR_BOOL, "all joint transforms return origin" ); +idCVar g_perfTest_noPlayerFocus( "g_perfTest_noPlayerFocus", "0", CVAR_GAME | CVAR_BOOL, "doesn't do player focus traces/logic" ); +idCVar g_perfTest_noProjectiles( "g_perfTest_noProjectiles", "0", CVAR_GAME | CVAR_BOOL, "all projectiles are removed instantly" ); + +idCVar g_clientProjectileCollision( "g_clientProjectileCollision", "1", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT, "allow the client to predict collisions" ); + +idCVar net_serverDownload( "net_serverDownload", "0", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "enable server download redirects. 0: off 1: client exits and opens si_serverURL in web browser 2: client downloads pak files from an URL and connects again. See net_serverDl* cvars for configuration" ); +idCVar net_serverDlBaseURL( "net_serverDlBaseURL", "", CVAR_GAME | CVAR_ARCHIVE, "base URL for the download redirection" ); +idCVar net_serverDlTable( "net_serverDlTable", "", CVAR_GAME | CVAR_ARCHIVE, "pak names for which download is provided, seperated by ; - use a * to mark all paks" ); + +idCVar si_serverURL( "si_serverURL", "", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "server information page" ); + + +idCVar net_warnStale( "net_warnStale", "1", CVAR_INTEGER | CVAR_GAME | CVAR_NOCHEAT, "Warn stale entity occurences on network client - == 1: only on ClientStale call, > 1 all times" ); + +// RAVEN BEGIN +// bdube: cvar helps +static idCVarHelp help_g_showHud ( "g_showHud", "Show Player HUD", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showGun ( "g_showGun", "Show Player Weapon", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTargets ( "g_showTargets", "Show Targets", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTriggers ( "g_showTriggers", "Show Triggers", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showEntityInfo ( "g_showEntityInfo", "Show Entity Information", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_timeentities ( "g_timeentities", "Show Entity Times", "Off;0.5s;1.0s", "0;0.5;1.0", CVARHELP_GAME ); +static idCVarHelp help_g_showActiveEntities ( "g_showActiveEntities", "Show Active Entities", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_frametime ( "g_frametime", "Show Game Frame Times", "Off;On", "0;1", CVARHELP_GAME ); + +static idCVarHelp help_g_showCollisionWorld ( "g_showCollisionWorld", "Show Collision World", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionModels ( "g_showCollisionModels", "Show Collision Models", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionTraces ( "g_showCollisionTraces", "Show Collision Traces", "Off;Info;Lines", "0;1;2", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showActive ( "rb_showActive", "Show Active Rigid Bodies", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showTimings ( "rb_showTimings", "Show Rigid Body Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_af_showTimings ( "af_showTimings", "Show AF Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); + +// nmckenzie: ai cvar helps +static idCVarHelp help_g_aas_showAreas( "aas_showAreas", "Show AAS areas", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showProblemAreas( "aas_showProblemAreas", "Show AAS areas with Problems", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showPath( "aas_showPath", "Show AAS Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showFlyPath( "aas_showFlyPath", "Show AAS Flying Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showWallEdges( "aas_showWallEdges", "Show AAS Wall Edges", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showHideArea( "aas_showHideArea", "Show AAS Hide Areas", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_goalArea( "aas_goalArea", "Show AAS Goal Areas", "Off;On", "0;1", CVARHELP_AI ); + +static idCVarHelp help_g_ai_debugMove( "ai_debugMove", "Show Movement for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_debugTrajectory( "ai_debugTrajectory", "Show Grenade tests for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showCombatNodes( "ai_showCombatNodes", "Show attack cones for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showPaths( "ai_showPaths", "Show all path_* entities", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "Show obstacle avoidance", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_speeds( "ai_speeds", "Show performance load of AI", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_animShow( "ai_animShow", "List animations when used.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showTacticalFeatures( "ai_showTacticalFeatures", "Show player view tactical features.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_useRVMasterMove( "ai_useRVMasterMove", "Use new master move functions.", "Off;On", "0;1", CVARHELP_AI ); +// RAVEN END diff --git a/src/game/gamesys/SysCvar.h b/src/game/gamesys/SysCvar.h new file mode 100644 index 0000000..1e6602b --- /dev/null +++ b/src/game/gamesys/SysCvar.h @@ -0,0 +1,448 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_CVAR_H__ +#define __SYS_CVAR_H__ + +extern idCVar developer; + +extern idCVar g_cinematic; +extern idCVar g_cinematicMaxSkipTime; + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +extern idCVar g_fixedHorizFOV; +// RAVEN END + +extern idCVar g_monsters; +extern idCVar g_decals; +extern idCVar g_knockback; +extern idCVar g_skill; +extern idCVar g_gravity; +extern idCVar g_mp_gravity; +extern idCVar g_skipFX; +extern idCVar g_skipParticles; +extern idCVar g_projectileLights; +extern idCVar g_doubleVision; +extern idCVar g_muzzleFlash; + +extern idCVar g_disasm; +extern idCVar g_debugBounds; +extern idCVar g_debugAnim; +extern idCVar g_debugMove; +extern idCVar g_debugDamage; +extern idCVar g_debugWeapon; +extern idCVar g_debugScript; +extern idCVar g_debugMover; +extern idCVar g_debugTriggers; +extern idCVar g_debugCinematic; +// RAVEN BEGIN +// bdube: added +extern idCVar g_debugState; +extern idCVar g_stopTime; +extern idCVar g_armorProtection; +extern idCVar g_armorProtectionMP; +//extern idCVar g_damageScale; +// jsinger: added to support binary read/write +extern idCVar com_BinaryRead; +#ifdef RV_BINARYDECLS +extern idCVar com_BinaryDeclRead; +#endif +// jsinger: added to support loading all decls from a single file +#ifdef RV_SINGLE_DECL_FILE +extern idCVar com_SingleDeclFile; +extern idCVar com_WriteSingleDeclFile; +#endif +extern idCVar com_BinaryWrite; +// RAVEN END +extern idCVar g_useDynamicProtection; +extern idCVar g_healthTakeTime; +extern idCVar g_healthTakeAmt; +extern idCVar g_healthTakeLimit; + +extern idCVar g_showPVS; +extern idCVar g_showTargets; +extern idCVar g_showTriggers; +extern idCVar g_showCollisionWorld; +extern idCVar g_showCollisionModels; +extern idCVar g_showCollisionTraces; +// RAVEN BEGIN +// ddynerman: SD's clip sector code +extern idCVar g_showClipSectors; +extern idCVar g_showClipSectorFilter; +extern idCVar g_showAreaClipSectors; +// RAVEN END +extern idCVar g_maxShowDistance; +extern idCVar g_showEntityInfo; +extern idCVar g_showviewpos; +extern idCVar g_showcamerainfo; +extern idCVar g_showTestModelFrame; +extern idCVar g_showActiveEntities; +extern idCVar g_showEnemies; +extern idCVar g_frametime; +extern idCVar g_timeentities; + +// RAVEN BEGIN +// bdube: new debug cvar +extern idCVar g_debugVehicle; +extern idCVar g_showFrameCmds; +extern idCVar g_showGodDamage; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: debug cvars for rvVehicleDriver +extern idCVar g_debugVehicleDriver; +extern idCVar g_debugVehicleAI; +extern idCVar g_vehicleMode; +// RAVEN END +extern idCVar g_allowVehicleGunOverheat; + +extern idCVar ai_debugScript; +extern idCVar ai_debugMove; +extern idCVar ai_debugTrajectory; +extern idCVar ai_debugTactical; +extern idCVar ai_debugFilterString; +extern idCVar ai_testPredictPath; +extern idCVar ai_showCombatNodes; +extern idCVar ai_showPaths; +extern idCVar ai_showObstacleAvoidance; +extern idCVar ai_blockedFailSafe; +extern idCVar ai_debugSquad; +extern idCVar ai_debugStealth; +extern idCVar ai_allowTacticalRush; + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +extern idCVar ai_speeds; +extern idCVar ai_freeze; +extern idCVar ai_animShow; +extern idCVar ai_showCover; +extern idCVar ai_showTacticalFeatures; +extern idCVar ai_disableEntTactical; +extern idCVar ai_disableAttacks; +extern idCVar ai_disableSimpleThink; +extern idCVar ai_disableCover; +extern idCVar ai_debugHelpers; +// cdr: added new master move type +extern idCVar ai_useRVMasterMove; +//jshepard: allow old AAS files +extern idCVar ai_allowOldAAS; +// twhitaker: debugging support for eye focus +extern idCVar ai_debugEyeFocus; +//mcg: always allow player to push buddies, unless scripted +extern idCVar ai_playerPushAlways; +// RAVEN END + +extern idCVar g_dvTime; +extern idCVar g_dvAmplitude; +extern idCVar g_dvFrequency; + +extern idCVar g_kickTime; +extern idCVar g_kickAmplitude; +extern idCVar g_blobTime; +extern idCVar g_blobSize; + +extern idCVar g_testHealthVision; +extern idCVar g_editEntityMode; +// RAVEN BEGIN +extern idCVar g_editEntityDistance; +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. +extern idCVar g_editEntityTextDistance; +// rjohnson: entity usage stats +extern idCVar g_keepEntityStats; +// RAVEN END +extern idCVar g_dragEntity; +extern idCVar g_dragDamping; +extern idCVar g_dragShowSelection; +extern idCVar g_dropItemRotation; + +extern idCVar g_vehicleVelocity; +extern idCVar g_vehicleForce; + +extern idCVar ik_enable; +extern idCVar ik_debug; + +extern idCVar af_useLinearTime; +extern idCVar af_useImpulseFriction; +extern idCVar af_useJointImpulseFriction; +extern idCVar af_useSymmetry; +extern idCVar af_skipSelfCollision; +extern idCVar af_skipLimits; +extern idCVar af_skipFriction; +extern idCVar af_forceFriction; +extern idCVar af_maxLinearVelocity; +extern idCVar af_maxAngularVelocity; +extern idCVar af_timeScale; +extern idCVar af_jointFrictionScale; +extern idCVar af_contactFrictionScale; +extern idCVar af_highlightBody; +extern idCVar af_highlightConstraint; +extern idCVar af_showTimings; +extern idCVar af_showConstraints; +extern idCVar af_showConstraintNames; +extern idCVar af_showConstrainedBodies; +extern idCVar af_showPrimaryOnly; +extern idCVar af_showTrees; +extern idCVar af_showLimits; +extern idCVar af_showBodies; +extern idCVar af_showBodyNames; +extern idCVar af_showMass; +extern idCVar af_showTotalMass; +extern idCVar af_showInertia; +extern idCVar af_showVelocity; +extern idCVar af_showActive; +extern idCVar af_testSolid; + +extern idCVar rb_showTimings; +extern idCVar rb_showBodies; +extern idCVar rb_showMass; +extern idCVar rb_showInertia; +extern idCVar rb_showVelocity; +extern idCVar rb_showActive; + +extern idCVar pm_jumpheight; +extern idCVar pm_stepsize; +extern idCVar pm_crouchspeed; +// RAVEN BEGIN +extern idCVar pm_speed; +extern idCVar pm_walkspeed; +extern idCVar pm_zoomedSlow; +extern idCVar pm_isZoomed; +// RAVEN END +extern idCVar pm_noclipspeed; +extern idCVar pm_spectatespeed; +extern idCVar pm_spectatebbox; +extern idCVar pm_usecylinder; +extern idCVar pm_minviewpitch; +extern idCVar pm_maxviewpitch; +extern idCVar pm_stamina; +extern idCVar pm_staminathreshold; +extern idCVar pm_staminarate; +extern idCVar pm_crouchheight; +extern idCVar pm_crouchviewheight; +extern idCVar pm_normalheight; +extern idCVar pm_normalviewheight; +extern idCVar pm_deadheight; +extern idCVar pm_deadviewheight; +extern idCVar pm_crouchrate; +extern idCVar pm_bboxwidth; +extern idCVar pm_crouchbob; +extern idCVar pm_walkbob; +extern idCVar pm_runbob; +extern idCVar pm_runpitch; +extern idCVar pm_runroll; +extern idCVar pm_bobup; +extern idCVar pm_bobpitch; +extern idCVar pm_bobroll; +extern idCVar pm_thirdPersonRange; +extern idCVar pm_thirdPersonHeight; +extern idCVar pm_thirdPersonAngle; +extern idCVar pm_thirdPersonClip; +extern idCVar pm_thirdPerson; +extern idCVar pm_thirdPersonDeath; +extern idCVar pm_modelView; +extern idCVar pm_airTics; + +// RAVEN BEGIN +// asalmon: parameters for aim assistance on Xenon +#ifdef _XBOX +extern idCVar pm_AimAssist; +extern idCVar pm_AimAssistDistance; +extern idCVar pm_AimAssistThreshold; +extern idCVar pm_AimAssistFOV; +extern idCVar pm_AimAssistBump; +extern idCVar pm_AimAssistShow; +extern idCVar pm_AimAssistSlow; + +extern idCVar pm_ThumbstickConfig; +extern idCVar pm_ButtonConfig; + +extern idCVar pm_RocketJumpAutocenter; +extern idCVar pm_IAmACheater; + + +#endif +// nmckenzie: added ability to try alternate accelerations. +extern idCVar pm_acceloverride; +extern idCVar pm_frictionoverride; +extern idCVar pm_forcespectatormove; +extern idCVar pm_thirdPersonTarget; +// bdube: vehicle +extern idCVar pm_vehicleLean; +extern idCVar pm_vehicleCameraSnap; +extern idCVar pm_vehicleCameraScaleMax; +extern idCVar pm_vehicleSoundLerpScale; +extern idCVar pm_vehicleCameraSpeedScale; +extern idCVar pm_vehicleCameraMinDist; +// RAVEN END + +extern idCVar g_showPlayerShadow; + +extern idCVar g_skipPlayerShadowsMP; +extern idCVar g_skipItemShadowsMP; + +extern idCVar g_simpleItems; +extern idCVar g_showHud; +// RAVEN BEGIN +extern idCVar g_crosshairColor; +// cnicholson: Custom Crosshair +extern idCVar g_crosshairCustom; +extern idCVar g_crosshairCustomFile; +extern idCVar g_crosshairCharInfoFar; +// bdube: hud popups +extern idCVar g_showHudPopups; +// bdube: range +extern idCVar g_showRange; +// bdube: debug hud +extern idCVar g_showDebugHud; +// RAVEN END +extern idCVar g_showProjectilePct; +// RAVEN BEGIN +// bdube: brass time +extern idCVar g_brassTime; +// RAVEN END +extern idCVar g_gun_x; +extern idCVar g_gun_y; +extern idCVar g_gun_z; +// RAVEN BEGIN +// bdube: cvar for messing with foreshortening +extern idCVar g_gun_pitch; +extern idCVar g_gun_yaw; +extern idCVar g_gun_roll; +// abahr: +extern idCVar g_gunViewStyle; +// jscott: for playbacks +extern idCVar g_showPlayback; +extern idCVar g_currentPlayback; +// RAVEN END +extern idCVar g_viewNodalX; +extern idCVar g_viewNodalZ; +extern idCVar g_fov; +extern idCVar g_testDeath; +extern idCVar g_skipViewEffects; +extern idCVar g_mpWeaponAngleScale; + +extern idCVar g_testParticle; +extern idCVar g_testParticleName; +// RAVEN BEGIN +// bdube: more rigid body debug +extern idCVar rb_showContacts; +// RAVEN END + +extern idCVar g_testPostProcess; + +extern idCVar g_testModelRotate; +extern idCVar g_testModelAnimate; +extern idCVar g_testModelBlend; + +extern idCVar g_forceModel; +extern idCVar g_forceStroggModel; +extern idCVar g_forceMarineModel; + +// RAVEN BEGIN +// bdube: test scoreboard +extern idCVar g_testScoreboard; +extern idCVar g_testPlayer; +// RAVEN END +extern idCVar g_exportMask; +extern idCVar g_flushSave; + +extern idCVar aas_test; +extern idCVar aas_showAreas; +extern idCVar aas_showAreaBounds; +extern idCVar aas_showPath; +extern idCVar aas_showFlyPath; +extern idCVar aas_showWallEdges; +extern idCVar aas_showHideArea; +extern idCVar aas_pullPlayer; +extern idCVar aas_randomPullPlayer; +extern idCVar aas_goalArea; +extern idCVar aas_showPushIntoArea; +// RAVEN BEGIN +// rjohnson: added aas help +extern idCVar aas_showProblemAreas; +// cdr: added rev reach +extern idCVar aas_showRevReach; +// RAVEN END + +extern idCVar net_clientPredictGUI; + +extern idCVar si_voteFlags; +extern idCVar g_mapCycle; +// RAVEN BEGIN +// shouchard: g_balanceTDM->g_balanceTeams so we can also use it for CTF +extern idCVar si_autobalance; +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +extern idCVar si_isBuyingEnabled; +extern idCVar si_dropWeaponsInBuyingModes; +extern idCVar si_controlTime; +// RITUAL END + +extern idCVar si_timeLimit; +extern idCVar si_fragLimit; +extern idCVar si_gameType; +extern idCVar si_map; +extern idCVar si_mapCycle; +extern idCVar si_spectators; +extern idCVar si_minPlayers; +// RAVEN BEGIN +// shouchard: CTF +extern idCVar si_captureLimit; +// shouchard: Tourney +extern idCVar si_tourneyLimit; +// RAVEN END + +extern const char *si_gameTypeArgs[]; + +// RAVEN BEGIN +// bdube: client entities +extern idCVar g_gamelog; +extern idCVar cl_showEntityInfo; +// jnewquist: Option to force undying state +extern idCVar g_forceUndying; +// mcg: combat performance testing cvars +extern idCVar g_perfTest_weaponNoFX; +extern idCVar g_perfTest_hitscanShort; +extern idCVar g_perfTest_hitscanBBox; +extern idCVar g_perfTest_aiStationary; +extern idCVar g_perfTest_aiNoDodge; +extern idCVar g_perfTest_aiNoRagdoll; +extern idCVar g_perfTest_aiNoObstacleAvoid; +extern idCVar g_perfTest_aiUndying; +extern idCVar g_perfTest_aiNoVisTrace; +extern idCVar g_perfTest_noJointTransform; +extern idCVar g_perfTest_noPlayerFocus; +extern idCVar g_perfTest_noProjectiles; +// RAVEN END + +extern idCVar g_clientProjectileCollision; + +extern idCVar net_clientLagOMeter; + +extern idCVar net_warnStale; + +#endif /* !__SYS_CVAR_H__ */ diff --git a/src/game/mp/Buying.cpp b/src/game/mp/Buying.cpp new file mode 100644 index 0000000..84ebcb5 --- /dev/null +++ b/src/game/mp/Buying.cpp @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Buying.cpp +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.cpp and Player.cpp, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Buying.h" + + +riBuyingManager::riBuyingManager() : + _buyingGameBalanceConstants( NULL ), + opponentKillCashAward( 0 ), + opponentKillFragCount( -1 ) { } + +riBuyingManager::~riBuyingManager() { } + +int riBuyingManager::GetIntValueForKey( const char* keyName, int defaultValue ) { + if( !keyName ) + { + return defaultValue; + } + + if( !_buyingGameBalanceConstants ) + { + _buyingGameBalanceConstants = static_cast( declManager->FindType( DECL_ENTITYDEF, "BuyingGameBalanceConstants", false ) ); + + if( !_buyingGameBalanceConstants ) + { + return defaultValue; + } + } + + for( int i = 0; i < _buyingGameBalanceConstants->dict.GetNumKeyVals(); i++ ) + { + const idKeyValue* keyValuePair = _buyingGameBalanceConstants->dict.GetKeyVal( i ); + if( !keyValuePair->GetKey().Icmp( keyName ) ) + { + return atoi( keyValuePair->GetValue() ); + } + } + + return defaultValue; +} + +int riBuyingManager::GetOpponentKillCashAward( void ) { + int targetFragCount = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + if ( opponentKillFragCount != targetFragCount ) { + opponentKillFragCount = targetFragCount; + if ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) && idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) ) { + // only do frag reward scaling in DM/TDM + opponentKillCashAward = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + } else { + targetFragCount = idMath::ClampInt( GetIntValueForKey( "killingOpponent_minFragAdjust", 10 ), GetIntValueForKey( "killingOpponent_maxFragAdjust",50 ), targetFragCount ); + int baseVal = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + int fragTarget = GetIntValueForKey( "killingOpponent_bestFragCount", 25 ); + opponentKillCashAward = ( baseVal * fragTarget ) / targetFragCount; + } + } + return opponentKillCashAward; +} diff --git a/src/game/mp/Buying.h b/src/game/mp/Buying.h new file mode 100644 index 0000000..9434076 --- /dev/null +++ b/src/game/mp/Buying.h @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Buying.h +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.h and Player.h, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#ifndef __BUYING_H__ +#define __BUYING_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + + +class riBuyingManager +{ +private: + const idDeclEntityDef* _buyingGameBalanceConstants; + int opponentKillCashAward; // latch + int opponentKillFragCount; + +public: + riBuyingManager(); + ~riBuyingManager(); + + int GetIntValueForKey( const char* keyName, int defaultValue ); + int GetOpponentKillCashAward( void ); + + void Reset( void ) { opponentKillFragCount = -1; } +}; + + +#endif // __BUYING_H__ diff --git a/src/game/mp/CTF.cpp b/src/game/mp/CTF.cpp new file mode 100644 index 0000000..bbdf3c6 --- /dev/null +++ b/src/game/mp/CTF.cpp @@ -0,0 +1,332 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// CTF.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "CTF.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ +CLASS_DECLARATION( idEntity, rvCTF_AssaultPoint ) + EVENT( EV_PostSpawn, rvCTF_AssaultPoint::Event_InitializeLinks ) + EVENT( EV_Touch, rvCTF_AssaultPoint::Event_Touch ) +END_CLASS + +rvCTF_AssaultPoint::rvCTF_AssaultPoint() { + trigger = NULL; + linked = false; + owner = AS_NEUTRAL; +} + +rvCTF_AssaultPoint::~rvCTF_AssaultPoint() { + delete trigger; + trigger = NULL; +} + +/* +================ +rvCTF_AssaultPoint::Spawn +================ +*/ +void rvCTF_AssaultPoint::Spawn( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvCTF_AssaultPoint::InitializeLinks +================ +*/ +void rvCTF_AssaultPoint::Event_InitializeLinks( void ) { + if( linked ) { + return; + } + + // pull in our targets + toStrogg = gameLocal.FindEntity( spawnArgs.GetString( "targetStroggAP" ) ); + toMarine = gameLocal.FindEntity( spawnArgs.GetString( "targetMarineAP" ) ); + + ResetIndices(); + + trigger = new idClipModel( idTraceModel( idBounds( vec3_origin ).Expand( 16.0f ) ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + trigger->SetContents ( CONTENTS_TRIGGER ); + + GetPhysics()->SetClipModel( NULL, 1.0f ); + + linked = true; +} + +/* +================ +rvCTF_AssaultPoint::ResetIndices +================ +*/ +void rvCTF_AssaultPoint::ResetIndices( void ) { + // find the closest AP to the marine flag, which is index 0, then work towards strogg flag + if ( !toMarine || !toMarine->IsType ( rvItemCTFFlag::GetClassType() ) ) { + return; + } + + idEntityPtr ptr; + ptr = this; + gameLocal.mpGame.assaultPoints.Append( ptr ); + int assignIndices = 0; + index = assignIndices++; + rvCTF_AssaultPoint* ap = this; + while( !ap->toStrogg->IsType ( rvItemCTFFlag::GetClassType() ) ) { + if( !ap->toStrogg->IsType ( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - non assault point targeted in assault point chain" ); + } + ap = static_cast(ap->toStrogg.GetEntity()); + ap->index = assignIndices++; + + ptr = ap; + gameLocal.mpGame.assaultPoints.Append( ptr ); + + if ( !ap->linked ) { + ap->Event_InitializeLinks(); + } + + if( !ap->toStrogg ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - break in assault point chain" ); + } + } +} + +/* +================ +rvCTF_AssaultPoint::Event_Activate +================ +*/ +void rvCTF_AssaultPoint::Event_Touch( idEntity *activator, trace_t *trace ) { + if( !activator->IsType( idPlayer::GetClassType() ) || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return; + } + + idPlayer* player = static_cast(activator); + int oldOwner = owner; + + if ( owner == player->team ) + return; + + int enemyPowerup = -1; + int friendlyPowerup = -1; + + if ( owner == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } else if ( owner == TEAM_STROGG ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + // neutral assault point + if ( player->team == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } + } + + if ( !player->PowerUpActive ( enemyPowerup ) ) { + return; + } + + + switch( player->team ) { + case TEAM_MARINE: { + if( !toMarine || owner == TEAM_MARINE ) { + break; + } + + if( toMarine->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } else if( toMarine->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toMarine.GetEntity())->owner == TEAM_MARINE ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } + } + break; + } + case TEAM_STROGG: { + if( !toStrogg || owner == TEAM_STROGG ) { + break; + } + + if( toStrogg->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } else if( toStrogg->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toStrogg.GetEntity())->owner == TEAM_STROGG ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } + } + break; + } + } + + + if( oldOwner != owner ) { + // we switched hands, reset forward spawns + gameLocal.ClearForwardSpawns(); + + if( oldOwner == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( oldOwner ); + } + } else if( oldOwner == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( oldOwner ); + } + } + + rvItemCTFFlag::ResetFlag ( enemyPowerup ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetAPOwner( index, owner ); + + ActivateTargets( this ); + + SetOwnerColor (); + } +} + +/* +================ +rvCTF_AssaultPoint::ResetSpawns +================ +*/ +void rvCTF_AssaultPoint::ResetSpawns( int team ) { + // check to see if team can spawn at me, if not pass down + if( owner == team ) { + ActivateTargets( this ); + } else { + if( team == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( team ); + } + } else if( team == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( team ); + } + } + } +} + +void rvCTF_AssaultPoint::SetOwner ( int newOwner ) { + if ( !gameLocal.isClient ) { + // server should set owner via EVENT_ACTIVATE, not here + return; + } + + owner = newOwner; + SetOwnerColor (); +} + +void rvCTF_AssaultPoint::Reset ( void ) { + owner = AS_NEUTRAL; + SetOwnerColor(); +} + +/* +================ +rvCTF_AssaultPoint::ChangeColor +================ +*/ +void rvCTF_AssaultPoint::SetOwnerColor ( void ) { + const idDeclSkin* skin = NULL; + if( owner == TEAM_MARINE ) { + skin = declManager->FindSkin( "skins/ddynerman/green_glow", false ); + } else if ( owner == TEAM_STROGG ) { + skin = declManager->FindSkin( "skins/ddynerman/orange_glow", false ); + } else { + skin = declManager->FindSkin( "skins/ddynerman/white", false ); + } + + if ( skin ) { + SetSkin( skin ); + } +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ + +CLASS_DECLARATION( idPlayerStart, rvCTFAssaultPlayerStart ) + EVENT( EV_Activate, rvCTFAssaultPlayerStart::Event_Activate ) +END_CLASS + +/* +================ +rvCTFAssaultPlayerStart::Spawn +================ +*/ +void rvCTFAssaultPlayerStart::Spawn(void) { + if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else { + gameLocal.Error("rvCTFAssaultPlayerStart::Spawn() - unknown team\n"); + team = -1; + } +} + +void rvCTFAssaultPlayerStart::Event_Activate( idEntity *activator ) { + if ( !activator->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Warning( "rvCTFAssaultPlayerStart::Event_Activate() - was activated by something other than an assault point\n" ); + return; + } + + rvCTF_AssaultPoint* ap = static_cast(activator); + + if( ap->GetOwner() == team ) { + gameLocal.UpdateForwardSpawns( this, team ); + } +} diff --git a/src/game/mp/CTF.h b/src/game/mp/CTF.h new file mode 100644 index 0000000..af82495 --- /dev/null +++ b/src/game/mp/CTF.h @@ -0,0 +1,108 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// CTF.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __CTF_H__ +#define __CTF_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ + +class rvCTF_AssaultPoint : public idEntity { +public: + CLASS_PROTOTYPE( rvCTF_AssaultPoint ); + void Spawn( void ); + + rvCTF_AssaultPoint(); + ~rvCTF_AssaultPoint(); + + int GetOwner( void ); + int GetIndex( void ); + + void SetOwnerColor ( void ); + void SetOwner ( int newOwner ); + void Reset ( void ); + +private: + void Event_Touch( idEntity *activator, trace_t *trace ); + void Event_InitializeLinks( void ); + void ResetSpawns( int team ); + + void ResetIndices( void ); + + // these could be maintained as lists to allow multiple AP paths + // the assault point one step closer to the Strogg base + idEntityPtr toStrogg; + // the assault point one step closer to the Marine base + idEntityPtr toMarine; + + // who currently owns this assault point + int owner; + int index; + bool linked; + + idClipModel* trigger; +}; + +ID_INLINE int rvCTF_AssaultPoint::GetOwner( void ) { + return owner; +} + +ID_INLINE int rvCTF_AssaultPoint::GetIndex( void ) { + return index; +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ +class rvCTFAssaultPlayerStart : public idPlayerStart { +public: + CLASS_PROTOTYPE( rvCTFAssaultPlayerStart ); + + void Spawn( void ); + + int GetTeam( void ); +private: + void Event_Activate( idEntity *activator ); + int team; +}; + +#endif diff --git a/src/game/mp/GameState.cpp b/src/game/mp/GameState.cpp new file mode 100644 index 0000000..87a80bc --- /dev/null +++ b/src/game/mp/GameState.cpp @@ -0,0 +1,3134 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// GameState.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "GameState.h" + +/* +=============================================================================== + +rvGameState + +Game state info for deathmatch, team deathmatch + +=============================================================================== +*/ + +/* +================ +rvGameState::rvGameState +================ +*/ +rvGameState::rvGameState( bool allocPrevious ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvGameState::~rvGameState +================ +*/ +rvGameState::~rvGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +rvGameState::Clear +================ +*/ +void rvGameState::Clear( void ) { + currentState = INACTIVE; + nextState = INACTIVE; + nextStateTime = 0; + fragLimitTimeout = 0; +} + +/* +================ +rvGameState::SendState +================ +*/ +void rvGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious ); + + if( clientNum == -1 && (*this) == (*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvGameState::WriteState +=============== +*/ +void rvGameState::WriteState( idBitMsg &msg ) { + PackState( msg ); +} + +/* +================ +rvGameState::SendInitialState +================ +*/ +void rvGameState::SendInitialState( int clientNum ) { + rvGameState* previousState = previousGameState; + + rvGameState invalidState; + + previousGameState = &invalidState; + + SendState( clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvGameState::ReceiveState +================ +*/ +void rvGameState::ReceiveState( const idBitMsg& msg ) { + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (*previousGameState) = (*this); +} + +/* +================ +rvGameState::PackState +================ +*/ +void rvGameState::PackState( idBitMsg& outMsg ) { + // for now, we only transmit 3 bytes. If we need to sync more data, we should + // only transmit the diff + outMsg.WriteByte( currentState ); + outMsg.WriteByte( nextState ); + outMsg.WriteLong( nextStateTime ); +} + +/* +================ +rvGameState::UnpackState +================ +*/ +void rvGameState::UnpackState( const idBitMsg& inMsg ) { + currentState = (mpGameState_t)inMsg.ReadByte(); + nextState = (mpGameState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); +} + +/* +================ +rvGameState::GameStateChanged +================ +*/ +void rvGameState::GameStateChanged( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + // Check for a currentState change + if( currentState != previousGameState->currentState ) { + if( currentState == WARMUP ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld( true ); + + // reset stats on the client-side + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == COUNTDOWN ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld(true); + if( gameLocal.gameType != GAME_TOURNEY && previousGameState->currentState != INACTIVE ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, nextStateTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, nextStateTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, nextStateTime - 1000 ); + } + } else if( currentState == GAMEON ) { + if ( !player->vsMsgState ) { + player->GUIMainNotice( "" ); + player->GUIFragNotice( "" ); + } else { + player->vsMsgState = false; + } + if( gameLocal.gameType != GAME_TOURNEY ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + } + //if ( gameLocal.gameType == GAME_DEADZONE ) { + // if ( player->team == TEAM_MARINE ) + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time ); + // else + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time ); + //} + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + soundSystem->SetActiveSoundWorld( true ); + + // tourney time announcements are scheduled as you join/leave arenas and at arena starts + if( gameLocal.gameType != GAME_TOURNEY ) { + gameLocal.mpGame.ScheduleTimeAnnouncements( ); + } + + + // clear stats on client + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == SUDDENDEATH ) { + soundSystem->SetActiveSoundWorld( true ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } else if( currentState == GAMEREVIEW ) { + // RITUAL BEGIN + gameLocal.mpGame.isBuyingAllowedRightNow = false; + // RITUAL END + gameLocal.mpGame.ShowStatSummary(); + } + } +} + +/* +================ +rvGameState::SpawnDeadZonePowerup +================ +*/ +void rvGameState::SpawnDeadZonePowerup( void ) { + idEntity *ent; + riDeadZonePowerup* spawnSpot = 0; + int count = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::GetClassType() ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != POWERUP_DEADZONE || flag->IsVisible() ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && !flag->IsVisible() ) { + flag->PostEventMS( &EV_Remove, 0 ); + + } else { + count++; + if ( !(rand()%(count)) ) { + spawnSpot = flag; + } + } + } + if ( spawnSpot ) { + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + spawnSpot->srvReady = 1; // Go ahead and set this, so the loop works properly. + } else { + gameLocal.Error("Couldn't find enough dead zone spawn spots for the number of dead zone artifacts specified in the map def!"); + } +} + +/* +================ +rvGameState::Run +================ +*/ +void rvGameState::Run( void ) { + if ( currentState == INACTIVE ) { + +#ifdef _XENON + if(Live()->RoundsPlayed() < cvarSystem->GetCVarInteger("si_matchRounds")) +#endif + { + NewState( WARMUP ); + } + } + + if ( nextState != INACTIVE && gameLocal.time > nextStateTime ) { + NewState( nextState ); + nextState = INACTIVE; + } + + switch( currentState ) { + case INACTIVE: + break; + + case GAMEREVIEW: { + if ( nextState == INACTIVE ) { + + statManager->SendAllStats(); + + nextState = NEXTGAME; + + // allow a little extra time in tourney since we have to display end brackets + if( gameLocal.gameType == GAME_TOURNEY ) { + nextStateTime = gameLocal.time + 5000 + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } else { + nextStateTime = gameLocal.time + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } + } + break; + } + case NEXTGAME: { + + // the core will force a restart at 12 hours max + // but it's nicer if we can wait for a game transition to perform the restart so the game is not interrupted + // perform a restart once we are past 8 hours + if ( networkSystem->ServerGetServerTime() > 8*60*60*1000 ) { + gameLocal.sessionCommand = "nextMap"; + return; + } + + if ( nextState == INACTIVE ) { + // game rotation, new map, gametype etc. + // only cycle in tourney if tourneylimit is higher than the specified value + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)this)->GetTourneyCount() >= gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + // whether we switch to the next map or not, reset the tourney count + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)this)->SetTourneyCount( 0 ); + } + + + if ( gameLocal.NextMap() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "serverMapRestart\n" ); + return; + } + } + + NewState( WARMUP ); + + // put everyone back in from endgame spectate + for ( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->wantSpectate ) { + gameLocal.mpGame.CheckRespawns( static_cast( ent ) ); + } + } + } + } + break; + } + case WARMUP: { + // check to see if we actually want to do a warmup, or if we fall through + +//RAVEN BEGIN +//asalmon: Live has its own rules for ending warm up +#ifdef _XENON + if(!Live()->RollCall()) + { + break; + } + +#endif +//RAVEN END + if( !gameLocal.serverInfo.GetBool( "si_warmup" ) && gameLocal.gameType != GAME_TOURNEY ) { + // tourney always needs a warmup, to ensure that at least 2 players get seeded for the tournament. + NewState( GAMEON ); + } else if ( gameLocal.mpGame.AllPlayersReady() ) { + NewState( COUNTDOWN ); + nextState = GAMEON; + nextStateTime = gameLocal.time + 1000 * gameLocal.serverInfo.GetInt( "si_countDown" ); + } + break; + } + case COUNTDOWN: { + break; + } + } +} + +/* +================ +rvGameState::NewState +================ +*/ +void rvGameState::NewState( mpGameState_t newState ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + int i; + + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + // asalmon: start the stat manager as soon as the game starts + statManager->Init(); + statManager->BeginGame(); + + // if shuffle is on, shuffle the teams around + if( gameLocal.IsTeamGame() && gameLocal.serverInfo.GetBool( "si_shuffle" ) ) { + gameLocal.mpGame.ShuffleTeams(); + } + + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( false ); + + //asalmon: clear out lingering team scores. + gameLocal.mpGame.ClearTeamScores(); + + if( gameLocal.gameType != GAME_TOURNEY ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + ((idPlayer*)ent)->JoinInstance( 0 ); + } + } + + break; + } + case GAMEON: { + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart(); +// RITUAL BEGIN +// squirrel: Buying & Deadzone + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + // If the buy menu is up during a server restart, + // make sure to refresh it. + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) + gameLocal.mpGame.deadZonePowerupCount = mapDef->dict.GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iInit(); + statManager->BeginGame(); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + gameLocal.mpGame.SetMatchStartedTime( gameLocal.time ); + + fragLimitTimeout = 0; + + // write server initial reliable messages to give everyone new base + for( i = 0; i < MAX_CLIENTS; i++ ) { + // dont send this to server - we have all the data already and this will + // just trigger extra gamestate detection + if ( gameLocal.entities[ i ] && i != gameLocal.localClientNum ) { + gameLocal.mpGame.ServerWriteInitialReliableMessages( i ); + } + } + + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + gameLocal.mpGame.SetPlayerScore( p, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( p, 0 ); + + // in normal gameplay modes, spawn the player in. For tourney, the tourney manager handles spawning + if( gameLocal.gameType != GAME_TOURNEY ) { + p->JoinInstance( 0 ); + // in team games, let UserInfoChanged() spawn people on the right teams + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( static_cast(ent)->wantSpectate ); + } + } + } + + gameLocal.mpGame.ClearTeamScores(); + + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + gameLocal.mpGame.switchThrottle[ 1 ] = 0; // passby the throttle + break; + } + case GAMEREVIEW: { + statManager->EndGame(); + + //statManager->DebugPrint(); + nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } +// RITUAL BEGIN +// squirrel: support for Buying in multiplayer + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + player->forcedReady = false; + player->ServerSpectate( true ); + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); +// RITUAL END + } + break; + } + case SUDDENDEATH: { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_SUDDENDEATH ); + //unmark all leaders, so we make sure we only let the proper people respawn + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + } + + // only restart in team games if si_suddenDeathRestart is set. + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) { + gameLocal.LocalMapRestart(); + } + + // Mark everyone tied for the lead as leaders + i = 0; + idPlayer* leader = gameLocal.mpGame.GetRankedPlayer( i ); + if( leader ) { + int highScore = gameLocal.mpGame.GetScore( leader ); + while( leader ) { + if( gameLocal.mpGame.GetScore( leader ) < highScore ) { + break; + } + leader->SetLeader( true ); + leader = gameLocal.mpGame.GetRankedPlayer( ++i ); + } + } + +// RITUAL BEGIN +// squirrel: Buying & Deadzone + /// Reset players' cash and inventory if si_suddenDeathRestart is set + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) + { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + } + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDeclEntityDef *mapDef = static_cast(declManager->FindType( DECL_MAPDEF, mapName, false )); + if ( mapDef ) + gameLocal.mpGame.deadZonePowerupCount = mapDef->dict.GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iIsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } + if ( static_cast< idPlayer *>( ent )->IsLeader() ) { + static_cast(ent)->ServerSpectate( false ); + continue; + } + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); + } + } + + break; + } + default: { + break; + } + } + + currentState = newState; +} + +/* +================ +rvGameState::ClientDisconnect +================ +*/ +void rvGameState::ClientDisconnect( idPlayer* player ) { + return; +} + +/* +================ +rvGameState::Spectate +================ +*/ +void rvGameState::Spectate( idPlayer* player ) { + if( player->spectating && player->wantSpectate ) { + gameLocal.mpGame.ClearFrags( player->entityNumber ); + } + return; +} + +/* +================ +rvGameState::operator== +================ +*/ +bool rvGameState::operator==( const rvGameState& rhs ) const { + return ( + ( currentState == rhs.currentState ) && + ( nextState == rhs.nextState ) && + ( nextStateTime == rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator!= +================ +*/ +bool rvGameState::operator!=( const rvGameState& rhs ) const { + return ( + ( currentState != rhs.currentState ) || + ( nextState != rhs.nextState ) || + ( nextStateTime != rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator= +================ +*/ +rvGameState& rvGameState::operator=( const rvGameState& rhs ) { + currentState = rhs.currentState; + nextState = rhs.nextState; + nextStateTime = rhs.nextStateTime; + return (*this); +} + +/* +=============== +rvGameState::WriteNetworkInfo +=============== +*/ +void rvGameState::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteState( msg ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); +} + +/* +=============== +rvGameState::ReadNetworkInfo +=============== +*/ +void rvGameState::ReadNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, size ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ReceiveState( msg ); +} + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +rvDMGameState::rvDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvDMGameState::Run( void ) { + idPlayer* player = NULL; + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + player = gameLocal.mpGame.FragLimitHit(); + + bool tiedForFirst = false; + idPlayer* first = gameLocal.mpGame.GetRankedPlayer( 0 ); + idPlayer* second = gameLocal.mpGame.GetRankedPlayer( 1 ); + + if( player == NULL ) { + if( first && second && gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second ) ) { + tiedForFirst = true; + } + } + + if ( player ) { + // delay between detecting frag limit and ending game. let the death anims play + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( first && second && (gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second )) ) { + //this is a tie... + if( gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it must be tied at fraglimit, so sudden death. + NewState( SUDDENDEATH ); + } + } + //otherwise, just keep playing as normal. + fragLimitTimeout = 0; + + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + // check for the rare case that two players both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + break; + } + + case SUDDENDEATH: { + player = gameLocal.mpGame.FragLeader(); + + if ( player ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + + } +} + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +rvTeamDMGameState::rvTeamDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvTeamDMGameState::Run( void ) { + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) <= 0 ) { + // no fraglimit + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( tiedForFirst ) { + //this is a tie + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it's tied at the fraglimit. + NewState( SUDDENDEATH ); + } + //not a tie, game on. + fragLimitTimeout = 0; + } + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +/* +================ +rvCTFGameState::rvCTFGameState +================ +*/ +rvCTFGameState::rvCTFGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvCTFGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvCTFGameState::Clear +================ +*/ +void rvCTFGameState::Clear( void ) { + rvGameState::Clear(); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ].state = FS_AT_BASE; + flagStatus[ i ].clientNum = -1; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = AS_NEUTRAL; + } +} + +/* +================ +rvCTFGameState::SendState +================ +*/ +void rvCTFGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious && IsType( rvCTFGameState::GetClassType() ) ); + + if( clientNum == -1 && (rvCTFGameState&)(*this) == (rvCTFGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvCTFGameState::WriteState +=============== +*/ +void rvCTFGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add CTF info + PackState( msg ); +} + +/* +================ +rvCTFGameState::ReceiveState +================ +*/ +void rvCTFGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + rvGameState::UnpackState( msg ); + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvCTFGameState&)(*previousGameState) = (rvCTFGameState&)(*this); +} + +/* +================ +rvCTFGameState::PackState +================ +*/ +void rvCTFGameState::PackState( idBitMsg& outMsg ) { + // use indexing to pack in info + int index = 0; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( flagStatus[ i ].state ); + outMsg.WriteByte( flagStatus[ i ].clientNum ); + } + index++; + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != ((rvCTFGameState*)previousGameState)->apState[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( apState[ i ] ); + } + index++; + } +} + +/* +================ +rvCTFGameState::UnpackState +================ +*/ +void rvCTFGameState::UnpackState( const idBitMsg& inMsg ) { + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + if( index >= 0 && index < TEAM_MAX ) { + flagStatus[ index ].state = (flagState_t)inMsg.ReadByte(); + flagStatus[ index ].clientNum = inMsg.ReadByte(); + } else if( index >= TEAM_MAX && index < ( TEAM_MAX + MAX_AP ) ) { + apState[ index - TEAM_MAX ] = (apState_t)inMsg.ReadByte(); + } else { + gameLocal.Error( "rvCTFGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + } +} + +/* +================ +rvCTFGameState::SendInitialState +================ +*/ +void rvCTFGameState::SendInitialState( int clientNum ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + rvCTFGameState* previousState = (rvCTFGameState*)previousGameState; + + rvCTFGameState invalidState; + + previousGameState = &invalidState; + + SendState( clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvCTFGameState::GameStateChanged +================ +*/ +void rvCTFGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // CTF specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.GetDemoState() == DEMO_PLAYING && gameLocal.IsServerDemo() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvCTFGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + bool noSounds = false; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] == ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + continue; + } + + // don't play flag messages when flag state changes as a result of the gamestate changing + if( currentState != ((rvCTFGameState*)previousGameState)->currentState && ((rvCTFGameState*)previousGameState)->currentState != INACTIVE ) { + continue; + } + + if ( ((rvCTFGameState*)previousGameState)->currentState == INACTIVE ) { + noSounds = true; + } + + // flagTeam - used to tell the HUD which flag to update + int flagTeam = i; + + // in one flag CTF flagTeam is set to whichever team has the flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if( flagStatus[ i ].state == FS_TAKEN_MARINE ) { + flagTeam = TEAM_MARINE; + } else if( flagStatus[ i ].state == FS_TAKEN_STROGG ) { + flagTeam = TEAM_STROGG; + } + } + + if( flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + if ( !player->spectating ) { + if( flagTeam == player->team ) { + // our flag was dropped, so the enemy dropped it + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_DROPS_FLAG, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_DROPS_FLAG, gameLocal.time, -1, true ); + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagDrop" ); + + if ( !player->spectating ) { + if ( flagTeam == player->team ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107723" ) ); + } else { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + } + + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else if( flagStatus[ i ].state == FS_AT_BASE ) { + if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_TAKEN && !noSounds ) { + // team scores + if ( !player->spectating ) { + if( flagTeam == player->team ) { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_ENEMY_SCORES, gameLocal.time ); + } + } else { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_YOU_SCORE, gameLocal.time ); + } + } + } + } else if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + // flag returned + if ( !player->spectating ) { + if( flagTeam == player->team ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_FLAG_RETURNED, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_RETURNS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagReturn" ); + } + } else if( flagStatus[ i ].state == FS_TAKEN || flagStatus[ i ].state == FS_TAKEN_STROGG || flagStatus[ i ].state == FS_TAKEN_MARINE ) { + // flag taken + if( flagTeam == player->team ) { + if ( !player->spectating ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_HAS_FLAG, gameLocal.time, -1, true ); + } + } + + if ( !player->spectating ) { + if ( player->mphud ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107722" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else { + if ( flagStatus[ i ].clientNum == gameLocal.localClientNum ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOU_HAVE_FLAG, gameLocal.time, -1, true ); + } + + if ( !player->spectating ) { + // shouchard: inform the GUI that you've taken the flag + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } else if ( !noSounds ) { + if ( !player->spectating ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_HAS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent ( "flagTaken" ); + } + } + } +} + +/* +================ +rvCTFGameState::Run +================ +*/ +void rvCTFGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_captureLimit" ) <= 0 ) { + // no capture limit games + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter capture limit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // OR the winner lost a point in the frag delay, and there's no tie, so no one wins, game on. + if( tiedForFirst && ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) )) { + NewState( SUDDENDEATH ); + } + fragLimitTimeout = 0; + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +================ +rvCTFGameState::SetFlagState +================ +*/ +void rvCTFGameState::SetFlagState( int flag, flagState_t newState ) { + if ( !gameLocal.isServer ) { + return; + } + + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].state = newState; +} + +/* +================ +rvCTFGameState::SetFlagCarrier +================ +*/ +void rvCTFGameState::SetFlagCarrier( int flag, int clientNum ) { + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && (clientNum >= 0 && clientNum < MAX_CLIENTS) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].clientNum = clientNum; +} + +/* +================ +rvCTFGameState::operator== +================ +*/ +bool rvCTFGameState::operator==( const rvCTFGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != rhs.flagStatus[ i ] ) { + return false; + } + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != rhs.apState[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvCTFGameState::operator= +================ +*/ +rvCTFGameState& rvCTFGameState::operator=( const rvCTFGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ] = rhs.flagStatus[ i ]; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = rhs.apState[ i ]; + } + + return (*this); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +/* +================ +rvTourneyGameState::rvTourneyGameState +================ +*/ +rvTourneyGameState::rvTourneyGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvTourneyGameState( false ); + } else { + previousGameState = NULL; + } + + packTourneyHistory = false; + + trackPrevious = allocPrevious; +} + +/* +================ +rvTourneyGameState::Clear +================ +*/ +void rvTourneyGameState::Clear( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear(); + } + + rvGameState::Clear(); + + tourneyState = TS_INVALID; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + tourneyCount = 0; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Reset +================ +*/ +void rvTourneyGameState::Reset( void ) { + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Run +================ +*/ +void rvTourneyGameState::Run( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + // check to see if we need to advance to the next round + if( nextState == GAMEREVIEW ) { + return; + } + + bool roundDone = true; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_INACTIVE ) { + continue; + } + + arenas[ i ].UpdateState(); + + if( arenas[ i ].GetState() != AS_DONE ) { + // check to see if we're done + roundDone = false; + } + } + if( roundDone && !roundTimeout ) { + roundTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + if( roundDone && gameLocal.time > roundTimeout ) { + int pastRound = round - 1; //rounds are 1 indexed + idList > advancingPlayers; + + round++; + roundTimeout = 0; + + assert( round >= 2 ); + + // copy over tourney history for the previous round + UpdateTourneyHistory( pastRound ); + + // transmit history + forceTourneyHistory = true; + + // add who will advance to the next round + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() ) { + advancingPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + gameLocal.mpGame.AddPlayerWin( arenas[ i ].GetWinner(), 1 ); + } + } + + // when only one player advances, that player has won the tournament + if( advancingPlayers.Num() == 1 ) { + idPlayer* winner = advancingPlayers[ 0 ].First(); + assert( winner ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear(); + } + + gameLocal.Printf( "Round %d: player %d (%s) has won the tourney!\n", round - 1, winner->entityNumber, gameLocal.userInfo[ winner->entityNumber ].GetString( "ui_name" ) ); + // award 5 wins for winning the entire tournament + gameLocal.mpGame.AddPlayerWin( winner, 5 ); + + nextState = GAMEREVIEW; + nextStateTime = gameLocal.time + 5000; + + return; + } + + // see if we have to swap a player from the byeArena this round + bool swapBye = false; + int thisByeArena = -1; + + if( byeArena >= 0 ) { + // look at the bye arena from the round that just ended + thisByeArena = byeArena / (round - 1); + + // if the bye arena is playing in the next round, there's no need to switch players + if( !(thisByeArena % 2) ) { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena + 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena + 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } else { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena - 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena - 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } + } + + idPlayer* oldByePlayer = NULL; + idPlayer* newByePlayer = NULL; + + if( swapBye ) { + oldByePlayer = arenas[ thisByeArena ].GetWinner(); + + // pick a new bye player only from players who will be fighting next round + // i.e., don't swap the bye player with a player who will have a disconnect bye this upcoming round + idList > nextRoundPlayers; + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() && arenas[ i + 1 ].GetState() == AS_DONE && arenas[ i + 1 ].GetWinner() ) { + nextRoundPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + nextRoundPlayers.Append( rvPair( arenas[ i + 1 ].GetWinner(), i + 1 ) ); + } + } + + if ( nextRoundPlayers.Num() ) { + newByePlayer = nextRoundPlayers[ gameLocal.random.RandomInt( nextRoundPlayers.Num() ) ].First(); + } + } + + // assign arenas for the next round + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + idPlayer* advanceOne = arenas[ i ].GetWinner(); + idPlayer* advanceTwo = arenas[ i + 1 ].GetWinner(); + + // #13266 - bug: bystander from prev round is spectator in his match arena after round change + // we call rvTourneyInstance::Clear when setting up rounds (below), which sets it's players as spectator + // if the former bystander is placed in one of the new arenas and is still referenced in the bye arena then he'll get set spectating + // so just clear him up once identified + // #13631 - we used to call RemovePlayer before entering that loop, but that was messing up the selection of the next bystander + // so have to do it inside the loop now, just before it happens + if ( ( i == thisByeArena || i + 1 == thisByeArena ) && arenas[ thisByeArena ].HasPlayer( oldByePlayer ) ) { + arenas[ thisByeArena ].RemovePlayer( oldByePlayer ); + } + + arenas[ i ].Clear(); + arenas[ i + 1 ].Clear(); + + // assign new arenas, swapping oldByePlayer with newByePlayer + if ( newByePlayer && oldByePlayer ) { + if ( advanceOne == newByePlayer ) { + advanceOne = oldByePlayer; + } else if ( advanceTwo == newByePlayer ) { + advanceTwo = oldByePlayer; + } else if ( advanceOne == oldByePlayer ) { + advanceOne = newByePlayer; + } else if ( advanceTwo == oldByePlayer ) { + advanceTwo = newByePlayer; + } + } + + if ( advanceOne || advanceTwo ) { + arenas[ (i / 2) ].AddPlayers( advanceOne, advanceTwo ); + + if ( advanceOne ) { + advanceOne->JoinInstance( (i / 2) ); + advanceOne->ServerSpectate( false ); + } + + if ( advanceTwo ) { + advanceTwo->JoinInstance( (i / 2) ); + advanceTwo->ServerSpectate( false ); + } + + gameLocal.Printf( "Round %d: Arena %d is starting play with players %d (%s) and %d (%s)\n", round, (i / 2), advanceOne ? advanceOne->entityNumber : -1, advanceOne ? advanceOne->GetUserInfo()->GetString( "ui_name" ) : "NULL", advanceTwo ? advanceTwo->entityNumber : -1, advanceTwo ? advanceTwo->GetUserInfo()->GetString( "ui_name" ) : "NULL" ); + + arenas[ (i / 2) ].Ready(); + } + } + + // once the new round is setup, go through and make sure all the spectators are in valid arena + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + + // re-select our arena since round changed + if( p && p->spectating ) { + rvTourneyArena& thisArena = arenas[ p->GetArena() ]; + if( thisArena.GetState() != AS_WARMUP ) { + p->JoinInstance( GetNextActiveArena( p->GetArena() ) ); + } + } + } + } + + break; + } + } +} + +/* +================ +rvTourneyGameState::NewState +================ +*/ +void rvTourneyGameState::NewState( mpGameState_t newState ) { + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + Reset(); + // force everyone to spectate + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] ) { + ((idPlayer*)gameLocal.entities[ i ])->ServerSpectate( true ); + } + } + break; + } + case GAMEON: { + tourneyCount++; + SetupInitialBrackets(); + break; + } + } + + rvGameState::NewState( newState ); +} + +/* +================ +rvTourneyGameState::GameStateChanged +================ +*/ +void rvTourneyGameState::GameStateChanged( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + rvGameState::GameStateChanged(); + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + int oldRound = ((rvTourneyGameState*)previousGameState)->round; + + if( round != oldRound ) { + if ( round > maxRound && gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->ForceScoreboard( true, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_DONE, gameLocal.time ); + } + + // we're starting a new round + gameLocal.mpGame.tourneyGUI.RoundStart(); + + // skip announce if the round number doesn't make sense + if ( round >= 1 && round <= MAX_ROUNDS ) { + // play the sound a bit after round restart to let spawn sounds settle + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)( AS_TOURNEY_PRELIMS + round - 1 ), gameLocal.time + 1500); + } + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& thisArena = arenas[ i ]; + rvTourneyArena& previousArena = ((rvTourneyGameState*)previousGameState)->arenas[ i ]; + + if( ((thisArena.GetPlayers()[ 0 ] != previousArena.GetPlayers()[ 0 ]) || + (thisArena.GetPlayers()[ 1 ] != previousArena.GetPlayers()[ 1 ]) || + (round != ((rvTourneyGameState*)previousGameState)->round )) /*&& + !((thisArena.GetPlayers()[ 0 ] == NULL && thisArena.GetPlayers()[ 1 ] != NULL) || + (thisArena.GetPlayers()[ 0 ] != NULL && thisArena.GetPlayers()[ 1 ] == NULL) ) */) { + + // don't re-init arenas that have changed and been done for a while + if( thisArena.GetState() != AS_DONE || previousArena.GetState() != AS_DONE ) { + gameLocal.mpGame.tourneyGUI.ArenaStart( i ); + } + + if( localPlayer && thisArena.GetPlayers()[ 0 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_ONE ); + } + if( localPlayer && thisArena.GetPlayers()[ 1 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_TWO ); + } + } + + if( localPlayer->GetArena() == thisArena.GetArenaID() && thisArena.GetState() == AS_SUDDEN_DEATH && thisArena.GetState() != previousArena.GetState() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time, thisArena.GetArenaID() ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } + + if( thisArena.GetState() == AS_DONE && thisArena.GetState() != previousArena.GetState() ) { + if( thisArena.GetWinner() != previousArena.GetWinner() && thisArena.GetWinner() == gameLocal.GetLocalPlayer() ) { + if( round >= maxRound ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_WON, gameLocal.time ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ADVANCE, gameLocal.time ); + } + } else if( thisArena.GetLoser() != previousArena.GetLoser() && thisArena.GetLoser() == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ELIMINATED, gameLocal.time ); + } + + if( previousArena.GetWinner() == NULL && thisArena.GetWinner() != NULL ) { + gameLocal.mpGame.tourneyGUI.ArenaDone( i ); + } + + // on the client, add this result to our tourneyHistory + //if( gameLocal.isClient && thisArena.GetPlayers()[ 0 ] && thisArena.GetPlayers()[ 1 ] && (round - 1 ) >= 0 && (round - 1) < MAX_ROUNDS ) { + // tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 0 ] ); + // tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 1 ] ); + //} + } + + if( localPlayer && (thisArena.GetState() == AS_WARMUP) && (thisArena.GetState() != previousArena.GetState()) && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + int warmupEndTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countdown" ) * 1000 ) + 5000; + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, warmupEndTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, warmupEndTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, warmupEndTime - 1000 ); + + localPlayer->vsMsgState = true; + localPlayer->GUIMainNotice( va( "%s^0 vs. %s^0", thisArena.GetPlayerName( 0 ), thisArena.GetPlayerName( 1 ) ), true ); + } + + if( (thisArena.GetState() == AS_ROUND) && (thisArena.GetState() != previousArena.GetState()) ) { + if( localPlayer && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleTimeAnnouncements(); + } + } + } + + if( ((rvTourneyGameState*)previousGameState)->currentState != currentState ) { + if( currentState == WARMUP ) { + gameLocal.mpGame.tourneyGUI.PreTourney(); + } else if( currentState == COUNTDOWN ) { + if( currentState == COUNTDOWN && ((rvTourneyGameState*)previousGameState)->currentState != INACTIVE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_START, gameLocal.time); + } + } else if( currentState == GAMEON ) { + gameLocal.mpGame.tourneyGUI.TourneyStart(); + } + } + + if( packTourneyHistory ) { + // apply history + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); + packTourneyHistory = false; + } + + if( localPlayer && localPlayer->spectating ) { + + rvTourneyArena& thisArena = arenas[ localPlayer->GetArena() ]; + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL || (localPlayer->spectating && localPlayer->spectator != thisArena.GetPlayers()[ 0 ]->entityNumber && localPlayer->spectator != thisArena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_BRACKET ); + } else if ( thisArena.GetPlayers()[ 0 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 0 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_ONE ); + } else if ( thisArena.GetPlayers()[ 1 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 1 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_TWO ); + } + } + +} + +/* +================ +msgTourney_t +Identifiers for transmitted state +================ +*/ +typedef enum { + MSG_TOURNEY_TOURNEYSTATE, + MSG_TOURNEY_STARTINGROUND, + MSG_TOURNEY_ROUND, + MSG_TOURNEY_MAXROUND, + MSG_TOURNEY_HISTORY, + MSG_TOURNEY_TOURNEYCOUNT, + MSG_TOURNEY_ARENAINFO +} msgTourney_t; + +/* +================ +rvTourneyGameState::PackState +================ +*/ +void rvTourneyGameState::PackState( idBitMsg& outMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( tourneyState != ((rvTourneyGameState*)previousGameState)->tourneyState ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYSTATE ); + outMsg.WriteByte( tourneyState ); + } + + if( startingRound != ((rvTourneyGameState*)previousGameState)->startingRound ) { + outMsg.WriteByte( MSG_TOURNEY_STARTINGROUND ); + outMsg.WriteByte( startingRound ); + } + + if( round != ((rvTourneyGameState*)previousGameState)->round ) { + outMsg.WriteByte( MSG_TOURNEY_ROUND ); + outMsg.WriteByte( round ); + } + + if( maxRound != ((rvTourneyGameState*)previousGameState)->maxRound ) { + outMsg.WriteByte( MSG_TOURNEY_MAXROUND ); + outMsg.WriteByte( maxRound ); + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ] != ((rvTourneyGameState*)previousGameState)->arenas[ i ] ) { + outMsg.WriteByte( MSG_TOURNEY_ARENAINFO + i ); + arenas[ i ].PackState( outMsg ); + } + } + + if( packTourneyHistory || forceTourneyHistory ) { + if( forceTourneyHistory ) { + common->Warning("forcing down tourney history\n"); + } + // don't write out uninitialized tourney history + if( startingRound > 0 && round > 1 ) { + outMsg.WriteByte( MSG_TOURNEY_HISTORY ); + + // client might not yet have startingRound or round + outMsg.WriteByte( startingRound ); + outMsg.WriteByte( round ); + outMsg.WriteByte( maxRound ); + + for( int i = startingRound - 1; i <= Min( (round - 1), (maxRound - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + outMsg.WriteString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerOneScore ) ); + outMsg.WriteString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerTwoScore ) ); + outMsg.WriteByte( tourneyHistory[ i ][ j ].playerOneNum ); + outMsg.WriteByte( tourneyHistory[ i ][ j ].playerTwoNum ); + } + } + packTourneyHistory = false; + } + } + + if( tourneyCount != ((rvTourneyGameState*)previousGameState)->tourneyCount ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYCOUNT ); + outMsg.WriteByte( tourneyCount ); + } +} + +/* +================ +rvTourneyGameState::UnpackState +================ +*/ +void rvTourneyGameState::UnpackState( const idBitMsg& inMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + switch( index ) { + case MSG_TOURNEY_TOURNEYSTATE: { + tourneyState = (tourneyState_t)inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_STARTINGROUND: { + startingRound = inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_ROUND: { + round = inMsg.ReadByte(); + // not a great way of doing this, should clamp the values + if( round == 255 ) { + round = -1; + } + break; + } + case MSG_TOURNEY_MAXROUND: { + maxRound = inMsg.ReadByte(); + if( maxRound == 255 ) { + maxRound = -1; + } + break; + } + case MSG_TOURNEY_HISTORY: { + int startRound = inMsg.ReadByte(); + int rnd = inMsg.ReadByte(); + int maxr = inMsg.ReadByte(); + + assert( rnd >= 1 ); // something is uninitialized + + for( int i = startRound - 1; i <= Min( (rnd - 1), (maxr - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + inMsg.ReadString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneScore = inMsg.ReadChar(); + inMsg.ReadString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoScore = inMsg.ReadChar(); + tourneyHistory[ i ][ j ].playerOneNum = inMsg.ReadByte(); + tourneyHistory[ i ][ j ].playerTwoNum = inMsg.ReadByte(); + + if( tourneyHistory[ i ][ j ].playerOneNum < 0 || tourneyHistory[ i ][ j ].playerOneNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerOneNum = -1; + } + + if( tourneyHistory[ i ][ j ].playerTwoNum < 0 || tourneyHistory[ i ][ j ].playerTwoNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // client side (and listen server) no reason to check for history change all the time + packTourneyHistory = true; + break; + } + case MSG_TOURNEY_TOURNEYCOUNT: { + tourneyCount = inMsg.ReadByte(); + break; + } + default: { + if( index >= MSG_TOURNEY_ARENAINFO && index < MSG_TOURNEY_ARENAINFO + MAX_ARENAS ) { + arenas[ index - MSG_TOURNEY_ARENAINFO ].UnpackState( inMsg ); + } else { + gameLocal.Error( "rvTourneyGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + break; + } + } + + } +} + +/* +================ +rvTourneyGameState::SendState +================ +*/ +void rvTourneyGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious && IsType( rvTourneyGameState::GetClassType() ) ); + + if ( clientNum == -1 && (rvTourneyGameState&)(*this) == (rvTourneyGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + if( clientNum == -1 && forceTourneyHistory ) { + forceTourneyHistory = false; + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvTourneyGameState::WriteState +=============== +*/ +void rvTourneyGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add Tourney info + PackState( msg ); +} + +/* +================ +rvTourneyGameState::ReceiveState +================ +*/ +void rvTourneyGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvGameState::UnpackState( msg ); + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvTourneyGameState&)(*previousGameState) = (rvTourneyGameState&)(*this); +} + +/* +================ +rvTourneyGameState::SendInitialState +================ +*/ +void rvTourneyGameState::SendInitialState( int clientNum ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvTourneyGameState* previousState = (rvTourneyGameState*)previousGameState; + + rvTourneyGameState invalidState; + + previousGameState = &invalidState; + + + // if the tourney has been going on, transmit the tourney history + if( round > 0 ) { + // comparing the tourney history for all state changes is wasteful when we really just want to send it to new clients + packTourneyHistory = true; + } + + SendState( clientNum ); + + previousGameState = previousState; +} + + +/* +================ +rvTourneyGameState::operator== +================ +*/ +bool rvTourneyGameState::operator==( const rvTourneyGameState& rhs ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + if( round != rhs.round || startingRound != rhs.startingRound || maxRound != rhs.maxRound || tourneyState != rhs.tourneyState ) { + return false; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ] != rhs.arenas[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvTourneyGameState::operator= +================ +*/ +rvTourneyGameState& rvTourneyGameState::operator=( const rvTourneyGameState& rhs ) { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + (rvGameState&)(*this) = (rvGameState&)rhs; + + round = rhs.round; + startingRound = rhs.startingRound; + maxRound = rhs.maxRound; + tourneyState = rhs.tourneyState; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ] = rhs.arenas[ i ]; + } + + return (*this); +} + +/* +================ +rvTourneyGameState::GetNumArenas +Returns number of active arenas +================ +*/ +int rvTourneyGameState::GetNumArenas( void ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + int num = 0; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + num++; + } + } + + return num; +} + +/* +================ +rvTourneyGameState::SetupInitialBrackets + +Sets up the brackets for a new match. fragCount in playerstate is the player's +persistant frag count over an entire level. In teamFragCount we store this +rounds score. + +Initial bracket seeds are random +================ +*/ +void rvTourneyGameState::SetupInitialBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + idList unseededPlayers; + int numRankedPlayers = gameLocal.mpGame.GetNumRankedPlayers(); + + // all this crazy math does is figure out: + // 8 arenas to 4 rounds ( round 1: 8 arenas, round 2: 4 arenas, round 3: 2 arenas, round 4: 1 arena ) + // 16 arenas to 5 rounds ( round 1: 16 arenas, round 2: 8 arenas, round 3: 4 arenas, round 4: 2 arenas, round 5: 1 arena ) + // etc + int newMaxRound = idMath::ILog2( Max( idMath::CeilPowerOfTwo( MAX_ARENAS * 2 ), 2 ) ); + + // we start at a round appropriate for our # of people + // If you have 1-2 players, start in maxRound, if you have 3-4 players start in maxRound - 1, if you have 5-8 players + // start in maxRound - 2, if you have 9 - 16 players start in maxRound - 3, etc. + int newRound = newMaxRound - idMath::ILog2( Max( idMath::CeilPowerOfTwo( gameLocal.mpGame.GetNumRankedPlayers() ), 2 ) ) + 1; + + round = newRound; + maxRound = newMaxRound; + startingRound = round; + + for( int i = 0; i < numRankedPlayers; i++ ) { + unseededPlayers.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + int numArenas = 0; + + while ( unseededPlayers.Num() > 1 ) { + idPlayer* playerOne = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerOne ); + + idPlayer* playerTwo = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerTwo ); + + rvTourneyArena& arena = arenas[ numArenas ]; + + arena.Clear(); + arena.AddPlayers( playerOne, playerTwo ); + + // place the players in the correct instance + playerOne->JoinInstance( numArenas ); + playerTwo->JoinInstance( numArenas ); + + playerOne->ServerSpectate( false ); + playerTwo->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( playerOne, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( playerTwo, 0 ); + + gameLocal.Printf( "rvTourneyGameState::SetupInitialBrackets() - %s will face %s in arena %d\n", playerOne->GetUserInfo()->GetString( "ui_name" ), playerTwo->GetUserInfo()->GetString( "ui_name" ), numArenas ); + + // this arena is ready to play + arena.Ready(); + + numArenas++; + } + + assert( unseededPlayers.Num() == 0 || unseededPlayers.Num() == 1 ); + + if( unseededPlayers.Num() ) { + assert( unseededPlayers[ 0 ] ); + // the mid player gets a bye + unseededPlayers[ 0 ]->SetTourneyStatus( PTS_UNKNOWN ); + unseededPlayers[ 0 ]->ServerSpectate( true ); + + arenas[ numArenas ].AddPlayers( unseededPlayers[ 0 ], NULL ); + arenas[ numArenas ].Ready(); + + // mark the ancestor arena of the bye player + byeArena = numArenas * startingRound; + } else { + byeArena = -1; + } +} + +/* +================ +rvTourneyGameState::ClientDisconnect +A player has disconnected from the server +================ +*/ +void rvTourneyGameState::ClientDisconnect( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + if( gameLocal.isClient ) { + return; + } + + // go through the tourney history and copy over the disconnecting player's name + if( startingRound > 0 && round <= MAX_ROUNDS ) { + for( int i = startingRound - 1; i < round - 1; i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + if( tourneyHistory[ i ][ j ].playerOneNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneNum = -1; + } else if( tourneyHistory[ i ][ j ].playerTwoNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // copy over the current rounds info if the player is playing + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetPlayers()[ 0 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( player ); + } else if( arenas[ i ].GetPlayers()[ 1 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( player ); + } + } + + // retransmit tourney history to everyone + packTourneyHistory = true; + } + + RemovePlayer( player ); + + // give RemovePlayer() a chance to update tourney history if needed + if( packTourneyHistory ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if( i == player->entityNumber ) { + continue; + } + + if( gameLocal.entities[ i ] ) { + packTourneyHistory = true; + SendState( i ); + } + } + packTourneyHistory = false; + } +} + +/* +================ +rvTourneyGameState::Spectate +================ +*/ +void rvTourneyGameState::Spectate( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer ); + + if( player->spectating && player->wantSpectate ) { + RemovePlayer( player ); + } +} + +/* +================ +rvTourneyGameState::RemovePlayer +Removes the specified player from the arena +================ +*/ +void rvTourneyGameState::RemovePlayer( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( round < 1 || round > maxRound ) { + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + idPlayer** players = GetArenaPlayers( i ); + + if( players[ 0 ] == player || players[ 1 ] == player ) { + rvTourneyArena& arena = arenas[ i ]; + + idPlayer* remainingPlayer = players[ 0 ] == player ? players[ 1 ] : players[ 0 ]; + + bool arenaInProgress = arena.GetState() == AS_ROUND || arena.GetState() == AS_WARMUP || arena.GetState() == AS_SUDDEN_DEATH; + bool remainingIsWinner = (remainingPlayer == arena.GetWinner()); + int remainingIndex = (remainingPlayer == arena.GetPlayers()[ 0 ]) ? 0 : 1; + + arena.RemovePlayer( player ); + + // both players have disconnected from this arena, or the winner has disconnected, just return + if( (!arena.GetPlayers()[ 0 ] && !arena.GetPlayers()[ 1 ]) || (!arenaInProgress && remainingPlayer && !remainingIsWinner) ) { + arena.Clear(); + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + return; + } + + assert( remainingPlayer ); + + // if this arena is in progress, try restarting + // ATVI DevTrack #13196 - do not put the bye player into a game with the abandonned player anymore + // if ( arenaInProgress && byeArena >= 0 && arenas[ byeArena / round ].GetWinner() ) { + if ( 0 ) { + idPlayer* byePlayer = arenas[ byeArena / round ].GetWinner(); + + // reset history for this new round + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + + arena.Clear(); + arenas[ byeArena / round ].Clear(); + + arena.AddPlayers( remainingPlayer, byePlayer ); + + // place the players in the correct instance + remainingPlayer->JoinInstance( i ); + byePlayer->JoinInstance( i ); + + remainingPlayer->ServerSpectate( false ); + byePlayer->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( remainingPlayer, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( byePlayer, 0 ); + + gameLocal.Printf( "rvTourneyManager::RemovePlayer() - %s will face %s in arena %d\n", remainingPlayer->GetUserInfo()->GetString( "ui_name" ), byePlayer->GetUserInfo()->GetString( "ui_name" ), i ); + + byeArena = -1; + // this arena is ready to play + arena.Ready(); + } else { + // if the arena was in progress and didn't get a bye player to step in, this becomes a bye + // arena - clear the tourney history + if( arenaInProgress ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = -1; + + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = -1; + } else { + // since the player is disconnecting, write the history for this round + if( remainingIndex == 0 ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } else { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } + } + } + + return; + } + } +} + +int rvTourneyGameState::GetNextActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena + 1; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = 0; i < arena; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +int rvTourneyGameState::GetPrevActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena - 1; i >= 0; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = MAX_ARENAS - 1; i > arena; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +void rvTourneyGameState::SpectateCycleNext( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = players[ 1 ]->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetNextActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + player->spectator = player->entityNumber; + } + } + + // this is where the listen server updates it's gui spectating elements + if ( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::SpectateCyclePrev( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetPrevActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + + rvTourneyArena& newSpectatingArena = arenas[ player->GetArena() ]; + + idPlayer** newPlayers = newSpectatingArena.GetPlayers(); + + if( !newPlayers[ 0 ] || !newPlayers[ 1 ] || newPlayers[ 0 ]->spectating || newPlayers[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + player->spectator = newPlayers[ 1 ]->entityNumber; + } + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = player->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } + + // this is where the listen server updates it gui spectating elements + if( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::UpdateTourneyBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); +} + +void rvTourneyGameState::UpdateTourneyHistory( int round ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( round >= MAX_ROUNDS ) { + assert( false ); + gameLocal.Error( "rvTourneyGameState::UpdateTourneyHistory: MAX_ROUNDS exceeded" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + // sometimes tourney history might have been updated for the current + // round, so don't clobber any data + + if( (tourneyHistory[ round ][ i ].playerOne[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerOneNum != -1) || + (tourneyHistory[ round ][ i ].playerTwo[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerTwoNum != -1) ) { + continue; + } + + tourneyHistory[ round ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneNum = -1; + tourneyHistory[ round ][ i ].playerOneScore = 0; + + tourneyHistory[ round ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoNum = -1; + tourneyHistory[ round ][ i ].playerTwoScore = 0; + + if( arenas[ i ].GetState() == AS_DONE ) { + idPlayer* playerOne = arenas[ i ].GetPlayers()[ 0 ]; + idPlayer* playerTwo = arenas[ i ].GetPlayers()[ 1 ]; + + if( playerOne ) { + tourneyHistory[ round ][ i ].playerOneNum = playerOne->entityNumber; + tourneyHistory[ round ][ i ].playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( playerOne ); + } + + if( playerTwo ) { + tourneyHistory[ round ][ i ].playerTwoNum = playerTwo->entityNumber; + tourneyHistory[ round ][ i ].playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( playerTwo ); + } + } + } +} + + +/* +================ +rvTourneyGameState::FirstAvailableArena +Returns the first non-WARMUP arena available for use in the next round +================ +*/ +int rvTourneyGameState::FirstAvailableArena( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_WARMUP ) { + return i; + } + } + + // no available arenas + assert( 0 ); + return 0; +} + +arenaOutcome_t* rvTourneyGameState::GetArenaOutcome( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( arenas[ arena ].GetState() != AS_DONE || (arenas[ arena ].GetPlayers()[ 0 ] && arenas[ arena ].GetPlayers()[ 1 ]) ) { + return NULL; + } + + return &(tourneyHistory[ round - 1 ][ arena ]); +} + +bool rvTourneyGameState::HasBye( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( player == NULL || player->GetArena() < 0 || player->GetArena() >= MAX_ARENAS ) { + return false; + } + + // if we're one of the players in the arena we're in, and the other player is NULL, we have a bye + if( (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == player || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == player) && + (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == NULL || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == NULL) ) { + return true; + } + + return false; +} + +// simple RTTI +gameStateType_t rvGameState::type = GS_BASE; +gameStateType_t rvDMGameState::type = GS_DM; +gameStateType_t rvTeamDMGameState::type = GS_TEAMDM; +gameStateType_t rvCTFGameState::type = GS_CTF; +gameStateType_t rvTourneyGameState::type = GS_TOURNEY; + +bool rvGameState::IsType( gameStateType_t type ) const { + return ( type == rvGameState::type ); +} + +bool rvDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvDMGameState::type ); +} + +bool rvTeamDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvTeamDMGameState::type ); +} + +bool rvCTFGameState::IsType( gameStateType_t type ) const { + return ( type == rvCTFGameState::type ); +} + +bool rvTourneyGameState::IsType( gameStateType_t type ) const { + return ( type == rvTourneyGameState::type ); +} + +gameStateType_t rvGameState::GetClassType( void ) { + return rvGameState::type; +} + +gameStateType_t rvDMGameState::GetClassType( void ) { + return rvDMGameState::type; +} + +gameStateType_t rvTeamDMGameState::GetClassType( void ) { + return rvTeamDMGameState::type; +} + +gameStateType_t rvCTFGameState::GetClassType( void ) { + return rvCTFGameState::type; +} + +gameStateType_t rvTourneyGameState::GetClassType( void ) { + return rvTourneyGameState::type; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +/* +================ +riDZGameState::riDZGameState +================ +*/ +riDZGameState::riDZGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new riDZGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; + + type = GS_DZ; +} + +/* +================ +riDZGameState::~riDZGameState +================ +*/ +riDZGameState::~riDZGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +riDZGameState::Clear +================ +*/ +void riDZGameState::Clear( void ) { + rvGameState::Clear(); + + if ( previousGameState ) { + riDZGameState* prevState = (riDZGameState*)previousGameState; + prevState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + + dzTriggerEnt = -1; +} + +/* +================ +riDZGameState::SendState +================ +*/ +void riDZGameState::SendState( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( gameLocal.isServer && trackPrevious && type == GS_DZ ); + + if( clientNum == -1 && (riDZGameState&)(*this) == (riDZGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +riDZGameState::WriteState +=============== +*/ +void riDZGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add DZ info + PackState( msg ); +} + +/* +================ +riDZGameState::ReceiveState +================ +*/ +void riDZGameState::ReceiveState( const idBitMsg& msg ) { + assert( type == GS_DZ ); + + rvGameState::UnpackState( msg ); + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (riDZGameState&)(*previousGameState) = (riDZGameState&)(*this); +} + +/* +================ +riDZGameState::PackState +================ +*/ +void riDZGameState::PackState( idBitMsg& outMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + outMsg.WriteByte( dzStatus[ i ].state ); + outMsg.WriteBits( dzStatus[ i ].clientNum, -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + + outMsg.WriteLong(dzTriggerEnt); + outMsg.WriteLong(dzShaderParm); +} + +/* +================ +riDZGameState::UnpackState +================ +*/ +void riDZGameState::UnpackState( const idBitMsg& inMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = (dzState_t)inMsg.ReadByte(); + dzStatus[ i ].clientNum = inMsg.ReadBits( -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + dzTriggerEnt = inMsg.ReadLong(); + dzShaderParm = inMsg.ReadLong(); +} + +/* +================ +riDZGameState::SendInitialState +================ +*/ +void riDZGameState::SendInitialState( int clientNum ) { + assert( type == GS_DZ ); + + riDZGameState* previousState = (riDZGameState*)previousGameState; + + riDZGameState invalidState; + + previousGameState = &invalidState; + + SendState( clientNum ); + + previousGameState = previousState; +} + + +/* +================ +riDZGameState::ControlZoneStateChanged +================ +*/ +void riDZGameState::ControlZoneStateChanged( int team ) { + if( !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + return; + } + + if ( dzTriggerEnt < 0 ) + return; + + idEntity* ent = gameLocal.FindEntity(dzTriggerEnt); + if ( ent ) { + ent->SetShaderParm(7, dzShaderParm); + } + + dzTriggerEnt = -1; +} + + +/* +================ +riDZGameState::GameStateChanged +================ +*/ +void riDZGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // DZ specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + //gameLocal.Error( "riDZGameState::GameStateChanged() - NULL local player\n" ); + return; + } + + if( currentState == GAMEREVIEW ) + { + // Need to clear the deadzone state at this point. + ((riDZGameState*)previousGameState)->Clear(); + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] == ((riDZGameState*)previousGameState)->dzStatus[ i ] ) { + continue; + } + + ControlZoneStateChanged(i); + } +} + +/* +================ +riDZGameState::Run +================ +*/ +void riDZGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: + { + /// Check if we're frozen (in buy mode, etc.) + if( gameLocal.GetIsFrozen() ) + { + /// Check if it's time for freeze to wear off + int unFreezeTime = gameLocal.GetUnFreezeTime(); + if( gameLocal.time >= unFreezeTime ) + { + gameLocal.SetIsFrozen( false ); + // FIXME: say "fight" + } + } + else + { + /// Check if time limit has been reached + if ( gameLocal.mpGame.TimeLimitHit() ) + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + else + { + /// Teams are tied and time limit was hit - go to sudden death! + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } + } + } + + break; + } + + case SUDDENDEATH: + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + + break; + } + } +} + +/* +================ +riDZGameState::SetDZState +================ +*/ +void riDZGameState::SetDZState( int dz, dzState_t newState ) { + assert( gameLocal.isServer && ( dz >= 0 && dz < TEAM_MAX ) && type == GS_DZ ); + + dzStatus[ dz ].state = newState; +} + +/* +================ +riDZGameState::operator== +================ +*/ +bool riDZGameState::operator==( const riDZGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] != rhs.dzStatus[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +riDZGameState::operator= +================ +*/ +riDZGameState& riDZGameState::operator=( const riDZGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ] = rhs.dzStatus[ i ]; + } + + return (*this); +} + diff --git a/src/game/mp/GameState.h b/src/game/mp/GameState.h new file mode 100644 index 0000000..7ddd326 --- /dev/null +++ b/src/game/mp/GameState.h @@ -0,0 +1,507 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// GameState.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAMESTATE_H__ +#define __GAMESTATE_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" +#include "Tourney.h" + +/* +=============================================================================== + +rvGameState + +Game state info common for all gametypes + +=============================================================================== +*/ + +typedef enum { + GS_BASE, + GS_DM, + GS_TEAMDM, + GS_CTF, + GS_TOURNEY, + GS_DZ +} gameStateType_t; + +typedef enum { + INACTIVE = 0, // not running + WARMUP, // warming up + COUNTDOWN, // post warmup pre-game + GAMEON, // game is on + SUDDENDEATH, // game is on but in sudden death, first frag wins + GAMEREVIEW, // game is over, scoreboard is up. we wait si_gameReviewPause seconds (which has a min value) + NEXTGAME, + STATE_COUNT +} mpGameState_t; + +class rvGameState { +public: + + rvGameState( bool allocPrevious = true ); + virtual ~rvGameState( void ); + + // clientNum == -1 except for SendInitialState + // when clientNum >= 0, always send the state + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + + virtual void Run( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + virtual void Clear( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + + mpGameState_t GetMPGameState( void ) { return currentState; } + + mpGameState_t GetNextMPGameState( void ) { return nextState; } + int GetNextMPGameStateTime( void ) { return nextStateTime; } + void SetNextMPGameState( mpGameState_t newState ) { nextState = newState; } + void SetNextMPGameStateTime( int time ) { nextStateTime = time; } + + bool operator==( const rvGameState& rhs ) const; + bool operator!=( const rvGameState& rhs ) const; + rvGameState& operator=( const rvGameState& rhs ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile *file, int clientNum ); + + void SpawnDeadZonePowerup(); +protected: + static gameStateType_t type; + rvGameState* previousGameState; + bool trackPrevious; + + mpGameState_t currentState; + mpGameState_t nextState; + int nextStateTime; + + int fragLimitTimeout; +}; + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +class rvDMGameState : public rvGameState { +public: + rvDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +class rvTeamDMGameState : public rvGameState { +public: + rvTeamDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +// assault point state for CTF +enum apState_t { + AS_MARINE, + AS_STROGG, + AS_NEUTRAL +}; + +// current flag state for CTF +enum flagState_t { + FS_AT_BASE = 0, + FS_DROPPED, + FS_TAKEN, + // for one flag CTF + FS_TAKEN_STROGG, // taken by strogg team + FS_TAKEN_MARINE // taken by marine team +}; + +struct flagStatus_t { + flagState_t state; + int clientNum; +}; + +class rvCTFGameState : public rvGameState { +public: + rvCTFGameState( bool allocPrevious = true ); + virtual void Clear( void ); + + + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetAPOwner( int ap, int owner ); + int GetAPOwner( int ap ); + + void SetFlagState( int flag, flagState_t newState ); + flagState_t GetFlagState( int flag ); + int GetFlagCarrier( int flag ); + + void SetFlagCarrier( int flag, int clientNum ); + + bool operator==( const rvCTFGameState& rhs ) const; + rvCTFGameState& operator=( const rvCTFGameState& rhs ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + flagStatus_t flagStatus[ TEAM_MAX ]; + apState_t apState[ MAX_AP ]; + + static gameStateType_t type; +}; + +ID_INLINE void rvCTFGameState::SetAPOwner( int ap, int owner ) { + assert( (ap >= 0 && ap < MAX_AP) && (owner >= 0 && owner < TEAM_MAX) ); + + apState[ ap ] = (apState_t)owner; +} + +ID_INLINE int rvCTFGameState::GetAPOwner( int ap ) { + return apState[ ap ]; +} + +ID_INLINE flagState_t rvCTFGameState::GetFlagState( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].state; +} + +ID_INLINE int rvCTFGameState::GetFlagCarrier( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].clientNum; +} + +ID_INLINE bool operator==( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +enum tourneyState_t { + TS_INVALID = 0, + TS_PREMATCH, + TS_MATCH, + TS_END_GAME +}; + +class rvTourneyGameState : public rvGameState { +public: + rvTourneyGameState( bool allocPrevious = true ); + + virtual void Clear( void ); + + virtual void Run( void ); + + virtual void Reset( void ); + + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + void RemovePlayer( idPlayer* player ); + + bool operator==( const rvTourneyGameState& rhs ) const; + rvTourneyGameState& operator=( const rvTourneyGameState& rhs ); + + int GetNumArenas( void ) const; + bool HasBye( idPlayer* player ); + + int GetMaxRound( void ) const; + int GetRound( void ) const; + int GetStartingRound( void ) const; + + idPlayer** GetArenaPlayers( int arena ); + + rvTourneyArena& GetArena( int arena ); + + const char* GetRoundDescription( void ); + int GetNextActiveArena( int arena ); + int GetPrevActiveArena( int arena ); + + void SpectateCycleNext( idPlayer* player ); + void SpectateCyclePrev( idPlayer* player ); + int GetTourneyCount( void ); + void SetTourneyCount( int count ) { tourneyCount = count; } + + void UpdateTourneyBrackets( void ); + + void UpdateTourneyHistory( int round ); + int FirstAvailableArena( void ); + + arenaOutcome_t* GetArenaOutcome( int arena ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + void SetupInitialBrackets( void ); + + tourneyState_t tourneyState; + + // startingRound - The starting round, with the current # of players + int startingRound; + // round - current round ( 1-indexed ) + int round; + // maxRound - the most rounds we'll run (based on MAX_ARENAS) + int maxRound; + // arenas - current brackets, an extra arena to serve as the waiting room + rvTourneyArena arenas[ MAX_ARENAS + 1 ]; + // tourneyHistory - past winners + arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ]; + // byeArena - the arena that natural (non disconnection) bye players go in + int byeArena; + // packTourneyHistory - whether or not we should transmit tourney history to one client through SendState + bool packTourneyHistory; + // forceTourneyHistory - if true will sync down tourney history for all clients + bool forceTourneyHistory; + // tourneyCount - how many tourneys to run per map + int tourneyCount; + // roundTimeout - a delay between all arenas finishing and starting the next round + int roundTimeout; + + static gameStateType_t type; +}; + +ID_INLINE int rvTourneyGameState::GetMaxRound( void ) const { + assert( type == GS_TOURNEY ); + return maxRound; +} + +ID_INLINE int rvTourneyGameState::GetRound( void ) const { + assert( type == GS_TOURNEY ); + return round; +} + +ID_INLINE int rvTourneyGameState::GetStartingRound( void ) const { + assert( type == GS_TOURNEY ); + return startingRound; +} + +ID_INLINE idPlayer** rvTourneyGameState::GetArenaPlayers( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ].GetPlayers(); +} + +ID_INLINE rvTourneyArena& rvTourneyGameState::GetArena( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ]; +} + +ID_INLINE const char* rvTourneyGameState::GetRoundDescription( void ) { + assert( type == GS_TOURNEY ); + + if( round == maxRound ) { + return common->GetLocalizedString( "#str_107720" ); + } else if( round == maxRound - 1 ) { + return common->GetLocalizedString( "#str_107719" ); + } else if( round == maxRound - 2 ) { + return common->GetLocalizedString( "#str_107718" ); + } else if( round == maxRound - 3 ) { + return common->GetLocalizedString( "#str_107717" ); + } else { + // shouldn't happen in production + return va( "ROUND %d", round ); + } +} + +ID_INLINE int rvTourneyGameState::GetTourneyCount( void ) { + assert( type == GS_TOURNEY ); + return tourneyCount; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +// current control zone state +enum dzState_t { + DZ_NONE = 0, + DZ_TAKEN, + DZ_LOST, + DZ_DEADLOCK, + DZ_MARINES_TAKEN, + DZ_MARINES_LOST, + DZ_STROGG_TAKEN, + DZ_STROGG_LOST, + DZ_MARINE_TO_STROGG, + DZ_STROGG_TO_MARINE, + DZ_MARINE_DEADLOCK, + DZ_STROGG_DEADLOCK, + DZ_MARINE_REGAIN, + DZ_STROGG_REGAIN +}; + +struct dzStatus_t { + dzState_t state; + int clientNum; +}; + +class riDZGameState : public rvGameState { +public: + riDZGameState( bool allocPrevious = true ); + ~riDZGameState( void ); + virtual void Clear( void ); + + + virtual void SendState( int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetDZState( int dz, dzState_t newState ); + dzState_t GetDZState( int dz ); + + bool operator==( const riDZGameState& rhs ) const; + riDZGameState& operator=( const riDZGameState& rhs ); + + int dzTriggerEnt; + int dzShaderParm; + +private: + dzStatus_t dzStatus[ TEAM_MAX ]; + + void ControlZoneStateChanged( int team ); +}; + + +ID_INLINE dzState_t riDZGameState::GetDZState( int dz ) { + assert( dz >= 0 && dz < TEAM_MAX ); + + return dzStatus[ dz ].state; +} + +ID_INLINE bool operator==( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} +#endif diff --git a/src/game/mp/Tourney.cpp b/src/game/mp/Tourney.cpp new file mode 100644 index 0000000..81e786d --- /dev/null +++ b/src/game/mp/Tourney.cpp @@ -0,0 +1,1045 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Tourney.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Tourney.h" + + +/* +=============================================================================== + +rvTourneyArena + +=============================================================================== +*/ + +/* +================ +rvTourneyArena::rvTourneyArena +================ +*/ +rvTourneyArena::rvTourneyArena() { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; +} + +/* +================ +rvTourneyArena::AddPlayer +================ +*/ +void rvTourneyArena::AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ) { + players[ 0 ] = playerOne; + players[ 1 ] = playerTwo; + if( playerOne ) { + playerOne->SetTourneyStatus( PTS_PLAYING ); + } + + if( playerTwo ) { + playerTwo->SetTourneyStatus( PTS_PLAYING ); + } +} + +/* +================ +rvTourneyArena::ClearPlayers +Clears player references if clearPlayer is NULL (client-side) +Clears specific player if clearPlayer is not NULL (server-side) +================ +*/ +void rvTourneyArena::ClearPlayers( idPlayer* clearPlayer /* = NULL */ ) { + if( gameLocal.isServer ) { + if( clearPlayer ) { + assert( clearPlayer == players[ 0 ] || clearPlayer == players[ 1 ] ); + if( clearPlayer == players[ 0 ] ) { + players[ 0 ] = NULL; + } else { + players[ 1 ] = NULL; + } + } + return; + } else { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + } +} + +/* +================ +rvTourneyArena::Ready +================ +*/ +void rvTourneyArena::Ready( void ) { + arenaState = AS_WARMUP; + nextStateTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countDown" ) * 1000 ) + 5000; + + matchStartTime = 0; + + if( !players[ 0 ] || !players[ 1 ] ) { + // if we don't have both players available, bye this arena + NewState( AS_DONE ); + } +} + +/* +================ +rvTourneyArena::Clear +Clears player list and state, but not round # or arena ID +================ +*/ +void rvTourneyArena::Clear( bool respawnPlayers ) { + if( gameLocal.isServer && respawnPlayers ) { + if( players[ 0 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 0 ]->ServerSpectate( true ); + } + + if( players[ 1 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 1 ]->ServerSpectate( true ); + } + + // This arena is being cleared so we must also clear out any spectators + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + } + + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; + SetState( AS_INACTIVE ); +} + +/* +================ +rvTourneyArena::GetLeader +Returns winning player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLeader( void ) { + if( players[ 0 ] == NULL || players[ 1 ] == NULL ) { + if( players[ 0 ] ) { + return players[ 0 ]; + } else if( players[ 1 ] ) { + return players[ 1 ]; + } + + return NULL; + } + + int playerOneScore = gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ); + int playerTwoScore = gameLocal.mpGame.GetTeamScore( players[ 1 ]->entityNumber ); + + if ( playerOneScore == playerTwoScore ) { + return NULL; + } + + return ( playerOneScore > playerTwoScore ? players[ 0 ] : players[ 1 ] ); +} + +/* +================ +rvTourneyArena::GetLoser +Returns losing player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLoser( void ) { + if( winner == NULL ) { + return NULL; + } else { + return ( winner == players[ 0 ] ? players[ 1 ] : players[ 0 ] ); + } +} + +/* +================ +rvTourneyArena::UpdateState +Updates this arena's state +================ +*/ +void rvTourneyArena::UpdateState( void ) { + if( (players[ 0 ] == NULL || players[ 1 ] == NULL) && arenaState != AS_DONE ) { + gameLocal.Warning( "rvTourneyArena::UpdateState() - UpdateState() called on non-full and non-done arena\n" ); + NewState( AS_DONE ); + return; + } + + switch( arenaState ) { + case AS_DONE: { + // not both players will neccesarily be valid here (if a player wins the arena then his opponent disconnects) + + break; + } + case AS_WARMUP: { + if( gameLocal.time > nextStateTime ) { + SetState( AS_ROUND ); + + // allow damage in warmup + //players[ 0 ]->fl.takedamage = true; + //players[ 1 ]->fl.takedamage = true; + // respawn the players + //players[ 0 ]->ServerSpectate( false ); + //players[ 1 ]->ServerSpectate( false ); + // respawn items + gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart( arenaID ); + + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + matchStartTime = gameLocal.time; + } + + break; + } + + case AS_ROUND: { + if( GetLeader() && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetTeamScore( GetLeader()->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + if( fragLimitTimeout && fragLimitTimeout < gameLocal.time ) { + NewState( AS_DONE ); + } else if ( !fragLimitTimeout ) { + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + } else if( fragLimitTimeout ) { + if( !GetLeader() && gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + // players tied at fraglimit + NewState( AS_SUDDEN_DEATH ); + } else { + // player hit fraglimit, but then killed himself and dropped below fraglimit, return to normal play + fragLimitTimeout = 0; + } + + //back to normal play? + } else if ( TimeLimitHit() ) { + // only send to clients in this arena, the times may be getting out of sync between the arenas during a round ( #13196 ) + int i; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if ( !player ) { + continue; + } + if ( player->GetArena() == arenaID ) { + gameLocal.mpGame.PrintMessageEvent( i, MSG_TIMELIMIT ); + } + } + if( GetLeader() == NULL ) { + // if tied at timelimit hit, goto sudden death + NewState( AS_SUDDEN_DEATH ); + } else { + // or just end the game + NewState( AS_DONE ); + } + } + break; + } + case AS_SUDDEN_DEATH: { + if ( GetLeader() ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", GetLeader()->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( AS_DONE ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, GetLeader()->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + } +} + +/* +================ +rvTourneyArena::NewState +================ +*/ +void rvTourneyArena::NewState( arenaState_t newState ) { + switch( newState ) { + case AS_DONE: { + winner = GetLeader(); + assert( winner ); + gameLocal.Printf( "rvTourneyArena::UpdateState() - %s has won this arena\n", GetLeader()->GetUserInfo()->GetString( "ui_name" ) ); + + winner->SetTourneyStatus( PTS_ADVANCED ); + + if( GetLoser() ) { + GetLoser()->SetTourneyStatus( PTS_ELIMINATED ); + } + + + if( players[ 0 ] ) { + players[ 0 ]->ServerSpectate( true ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + if( players[ 1 ] ) { + players[ 1 ]->ServerSpectate( true ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + // when we're done, put anyone who was spectating into another arena + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + + matchStartTime = 0; + break; + } + case AS_SUDDEN_DEATH: { + fragLimitTimeout = 0; + + // respawn the arena + players[ 0 ]->ServerSpectate( false ); + players[ 1 ]->ServerSpectate( false ); + gameLocal.LocalMapRestart( arenaID ); + break; + } + default: { + gameLocal.Error( "rvTourneyArena::NewState() - Unknown state '%d'\n", newState ); + return; + } + } + SetState( newState ); +} + +/* +================ +rvTourneyArena::RemovePlayer +The specified client is being removed from the server +================ +*/ +void rvTourneyArena::RemovePlayer( idPlayer* player ) { + // when we call Clear() the arena will clean up (set player instances to 0, spectate them, etc) + // we don't want it to do this for the disconnecting player, since he may not be entirely valid + // anymore. If we NULL out the disconnecting player, Clear() will properly reset the remaining player + // but will leave the volatile disconnecting player's data lone. + + bool playerInArena = false; + if( player == players[ 0 ] ) { + players[ 0 ] = NULL; + playerInArena = true; + } + + if( player == players[ 1 ] ) { + players[ 1 ] = NULL; + playerInArena = true; + } + + if( player == winner ) { + winner = NULL; + } + + if( !playerInArena ) { + // occasionally happens if a client has dropped ( the bystander maybe ) + common->Warning( "rvTourneyArena::RemovePlayer() - Called on player who is not in arena '%d'\n", arenaID ); + } +} + +/* +================ +rvTourneyArena::PackState +================ +*/ +void rvTourneyArena::PackState( idBitMsg& outMsg ) { + if( players[ 0 ] ) { + outMsg.WriteByte( players[ 0 ]->entityNumber ); + } else { + outMsg.WriteByte( -1 ); + } + + if( players[ 1 ] ) { + outMsg.WriteByte( players[ 1 ]->entityNumber ); + } else { + outMsg.WriteByte( -1 ); + } + + if( winner ) { + outMsg.WriteByte( winner->entityNumber ); + } else { + outMsg.WriteByte( -1 ); + } + + outMsg.WriteByte( arenaState ); + outMsg.WriteLong( nextStateTime ); + outMsg.WriteLong( fragLimitTimeout ); + outMsg.WriteLong( matchStartTime ); +} + +/* +================ +rvTourneyArena::UnpackState +================ +*/ +void rvTourneyArena::UnpackState( const idBitMsg& inMsg ) { + int playerOneNum = inMsg.ReadByte(); + int playerTwoNum = inMsg.ReadByte(); + int winnerNum = inMsg.ReadByte(); + + if( playerOneNum >= 0 && playerOneNum < MAX_CLIENTS ) { + players[ 0 ] = (idPlayer*)gameLocal.entities[ playerOneNum ]; + } else { + players[ 0 ] = NULL; + } + + if( playerTwoNum >= 0 && playerTwoNum < MAX_CLIENTS ) { + players[ 1 ] = (idPlayer*)gameLocal.entities[ playerTwoNum ]; + } else { + players[ 1 ] = NULL; + } + + if( winnerNum >= 0 && winnerNum < MAX_CLIENTS ) { + winner = (idPlayer*)gameLocal.entities[ winnerNum ]; + } else { + winner = NULL; + } + + arenaState = (arenaState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); + fragLimitTimeout = inMsg.ReadLong(); + matchStartTime = inMsg.ReadLong(); +} + +/* +================ +rvTourneyArena::SetState +Set's this arena's state - client side only based on UpdateState() results from server +================ +*/ +void rvTourneyArena::SetState( arenaState_t newState ) { + arenaState = newState; +} + +/* +================ +rvTourneyArena::SetNextStateTime +================ +*/ +void rvTourneyArena::SetNextStateTime( int time ) { + nextStateTime = time; +} + +/* +================ +rvTourneyArena::GetNextStateTime +================ +*/ +int rvTourneyArena::GetNextStateTime( void ) { + return nextStateTime; +} + + +/* +================ +rvTourneyArena::GetState +================ +*/ +arenaState_t rvTourneyArena::GetState( void ) const { + return arenaState; +} + +/* +================ +rvTourneyArena::GetPlayerName +================ +*/ +const char* rvTourneyArena::GetPlayerName( int player ) { + assert( player >= 0 && player < 2 ); + + if( players[ player ] ) { + return players[ player ]->GetUserInfo()->GetString( "ui_name" ); + } else { + return NULL; + } +} + +/* +================ +rvTourneyArena::GetPlayerScore +================ +*/ +int rvTourneyArena::GetPlayerScore( int player ) { + assert( player >= 0 && player < 2 ); + + + if( players[ player ] ) { + return gameLocal.mpGame.GetTeamScore( players[ player ] ); + } else { + return 0; + } +} + +/* +================ +rvTourneyArena::TimeLimitHit +================ +*/ +bool rvTourneyArena::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +=============================================================================== + +rvTourneyGUI + +=============================================================================== +*/ + +rvTourneyGUI::rvTourneyGUI() { + tourneyGUI = NULL; + tourneyScoreboard = NULL; +} + +void rvTourneyGUI::SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ) { + tourneyGUI = newTourneyGUI; + tourneyScoreboard = newTourneyScoreboard; +} + +void rvTourneyGUI::RoundStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "roundTransitionIn" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "roundTransitionIn" ); + + // set bye player for new round - the actual byePlayer may not have changed, so the gamestate won't move him over +// UpdateByePlayer(); +} + +void rvTourneyGUI::ArenaStart( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyGUI" ); + return; + } + + //arenaInit sets names to white and scores to orange. needs values "gui::round" , "gui::bracket" and "gui::empty" + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( arena ); + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyGUI->SetStateBool( "empty", true ); + } else { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +} + +void rvTourneyGUI::ArenaDone( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& tourneyArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + if( tourneyArena.GetPlayers()[ 0 ] == NULL || tourneyArena.GetPlayers()[ 1 ] == NULL ) { + // we don't transition bye arenas to done + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + idPlayer* winner = tourneyArena.GetWinner(); + idPlayer** players = tourneyArena.GetPlayers(); + + if( winner == NULL ) { + common->Error( "rvTourneyGUI::ArenaDone() - Called on arena '%d' which is not done!\n", arena ); + return; + } + + if( winner != players[ 0 ] && winner != players[ 1 ] ) { + common->Error( "rvTourneyGUI::ArenaDone() - Arena '%d' is reporting a winner that is not in the arena!\n", arena ); + return; + } + + tourneyGUI->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaDone" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyScoreboard" ); + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + tourneyScoreboard->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); +} + +void rvTourneyGUI::ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& thisArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + // don't select empty arenas + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL ) { + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + tourneyGUI->SetStateInt( "sel", (int)highlightType ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaFocus" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyScoreboard" ); + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + tourneyScoreboard->SetStateInt( "sel", (int)highlightType ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaFocus" ); +} + +void rvTourneyGUI::UpdateScores( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + if( round < 0 ) { + // not now + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyGUI->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyGUI->SetStateBool( "empty", false ); + } + } + + tourneyGUI->StateChanged( gameLocal.time ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyScoreboard->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyScoreboard->SetStateBool( "empty", false ); + } + } + + tourneyScoreboard->StateChanged( gameLocal.time ); +} + +void rvTourneyGUI::PreTourney( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::PreTourney() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->HandleNamedEvent( "warmupTransitionIn" ); +} + +void rvTourneyGUI::TourneyStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + int maxRound = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetMaxRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "warmupTransitionOut" ); + + // setup and clear the scoreboard + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 1; i <= maxRound; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyScoreboard->SetStateInt( "round", i ); + tourneyScoreboard->SetStateInt( "bracket", j + 1 ); + + if( i < round ) { + // we aren't using these brackets + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } else if ( i == round ) { + // this is our initial round + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( j ); + + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + } else { + // this is our future bracket + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + } + } + + // we might have overwritten a bye player, so update it again +// UpdateByePlayer(); +} + +/*void rvTourneyGUI::UpdateByePlayer( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyGUI" ); + return; + } + + int arena; + for( arena = 0; arena < MAX_ARENAS; arena++ ) { + if( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ).GetState() == AS_INACTIVE ) { + break; + } + } + + if( arena >= MAX_ARENAS ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Bye player with no inactive arenas!" ); + return; + } + + idPlayer* byePlayer = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetByePlayer(); + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyGUI->SetStateBool( "empty", true ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +}*/ + +void rvTourneyGUI::SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ) { + if ( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::SetupTourneyHistory() - Invalid tourneyScoreboard" ); + return; + } + + if( startHistory <= 0 ) { + return; + } + + for ( int round = startHistory; round <= endHistory; round++ ) { + tourneyScoreboard->SetStateInt( "round", round ); + + for ( int arena = 0; arena < MAX_ARENAS / round; arena++ ) { + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + // whether we want to send arenaDone to this arena + // don't send for empty brackets or bye rounds + bool arenaNotDone = tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne) && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo); + + if( arenaNotDone ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + + bool firstSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne)); + bool secondSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo)); + + assert( !(firstSlotBye && secondSlotBye) ); + + if( firstSlotBye || secondSlotBye ) { + // this was a bye round + + if( secondSlotBye ) { + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + } + + arenaNotDone = true; + } else { + // regular round + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOneScore ); + + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwoScore ); + + tourneyScoreboard->SetStateInt( "winner", tourneyHistory[ round - 1 ][ arena ].playerOneScore > tourneyHistory[ round - 1 ][ arena ].playerTwoScore ? 1 : 2 ); + } + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + if( !arenaNotDone ) { + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); + } + } + } + + UpdateScores(); +} diff --git a/src/game/mp/Tourney.h b/src/game/mp/Tourney.h new file mode 100644 index 0000000..806d6aa --- /dev/null +++ b/src/game/mp/Tourney.h @@ -0,0 +1,180 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Tourney.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __TOURNEY_H__ +#define __TOURNEY_H__ + +#include "../Game_local.h" + +enum arenaState_t { + AS_INACTIVE = 0, + AS_WARMUP, + AS_ROUND, + AS_SUDDEN_DEATH, + AS_DONE +}; + +#define MAX_TOURNEY_HISTORY_NAME_LEN 32 + +typedef struct arenaOutcome_s { + // for clients that have disconnected, copy their names for history purposes + char playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + char playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + + // for currently connected clients, use clientnum to get current name + int playerOneNum; + int playerTwoNum; + + int playerOneScore; + int playerTwoScore; +} arenaOutcome_t; + +// shouldn't exceed MAX_INSTANCES from idMultiplayerGame +const int MAX_ARENAS = 8; + +const int MAX_ROUNDS = 4; + +class rvTourneyArena { +public: + rvTourneyArena(); + + void AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ); + void ClearPlayers( idPlayer* clearPlayer = NULL ); + void Clear( bool respawnPlayers = true ); + void Ready( void ); + + idPlayer* GetLeader( void ); + idPlayer* GetLoser( void ); + idPlayer* GetWinner( void ) { return winner; } + void UpdateState( void ); + void NewState( arenaState_t newState ); + + idPlayer** GetPlayers( void ); + + void SetArenaID( int id ); + int GetArenaID( void ) const; + + arenaState_t GetState( void ) const; + void SetState( arenaState_t newState ); + void SetNextStateTime( int time ); + int GetNextStateTime( void ); + int GetMatchStartTime( void ) { return matchStartTime; } + + void PackState( idBitMsg& outMsg ); + void UnpackState( const idBitMsg& inMsg ); + + void RemovePlayer( idPlayer* player ); + bool TimeLimitHit( void ); + bool IsPlaying( idPlayer* player ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE && ( player == players[ 0 ] || player == players[ 1 ] ) ); } + bool HasPlayer( idPlayer* player ) { return ( player == players[0] || player == players[1] ); } + bool IsPlaying( void ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE ); } + + const char* GetPlayerName( int player ); + int GetPlayerScore( int player ); + int GetFraglimitTimeout( void ) { return fragLimitTimeout; } + bool operator==( const rvTourneyArena& rhs ) const; + bool operator!=( const rvTourneyArena& rhs ) const; + rvTourneyArena& operator=( const rvTourneyArena& rhs ); + +private: + // players - players in arena + idPlayer* players[ 2 ]; + // arenaID - this arena's ID + int arenaID; + // arenaState - state of the arena + arenaState_t arenaState; + // nextStateTime - transition time to next state + int nextStateTime; + // winner - the winner of the arena + idPlayer* winner; + // fragLimitTimeout - timeout to let death anims play + int fragLimitTimeout; + // matchStartTime - time arena started + int matchStartTime; +}; + +ID_INLINE idPlayer** rvTourneyArena::GetPlayers( void ) { + return players; +} + +ID_INLINE void rvTourneyArena::SetArenaID( int id ) { + arenaID = id; +} + +ID_INLINE int rvTourneyArena::GetArenaID( void ) const { + return arenaID; +} + +ID_INLINE bool rvTourneyArena::operator==( const rvTourneyArena& rhs ) const { + return ( arenaState == rhs.arenaState && players[ 0 ] == rhs.players[ 0 ] && players[ 1 ] == rhs.players[ 1 ] && nextStateTime == rhs.nextStateTime && arenaID == rhs.arenaID && fragLimitTimeout == rhs.fragLimitTimeout && matchStartTime == rhs.matchStartTime ); +} + +ID_INLINE bool rvTourneyArena::operator!=( const rvTourneyArena& rhs ) const { + return ( arenaState != rhs.arenaState || players[ 0 ] != rhs.players[ 0 ] || players[ 1 ] != rhs.players[ 1 ] || nextStateTime != rhs.nextStateTime || arenaID != rhs.arenaID || fragLimitTimeout != rhs.fragLimitTimeout || matchStartTime != rhs.matchStartTime ); +} + +ID_INLINE rvTourneyArena& rvTourneyArena::operator=( const rvTourneyArena& rhs ) { + players[ 0 ] = rhs.players[ 0 ]; + players[ 1 ] = rhs.players[ 1 ]; + arenaState = rhs.arenaState; + nextStateTime = rhs.nextStateTime; + arenaID = rhs.arenaID; + fragLimitTimeout = rhs.fragLimitTimeout; + matchStartTime = rhs.matchStartTime; + return (*this); +} + +typedef enum { + TGH_BRACKET, + TGH_PLAYER_ONE, + TGH_PLAYER_TWO +} tourneyGUIHighlight_t; + +class rvTourneyGUI { +public: + rvTourneyGUI(); + void SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ); + + void RoundStart( void ); + void ArenaStart( int arena ); + void ArenaDone( int arena ); + void ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ); + void UpdateScores( void ); + void PreTourney( void ); + void TourneyStart( void ); + //void UpdateByePlayer( void ); + void SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ); + +private: + idUserInterface* tourneyGUI; + idUserInterface* tourneyScoreboard; +}; + +#endif diff --git a/src/game/mp/VoiceComms.cpp b/src/game/mp/VoiceComms.cpp new file mode 100644 index 0000000..d239241 --- /dev/null +++ b/src/game/mp/VoiceComms.cpp @@ -0,0 +1,220 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop +#include "../Game_local.h" + +// This equates to about 1 second +#define MAX_VOICE_PACKET_SIZE 382 + +idCVar si_voiceChat( "si_voiceChat", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_INFO, "enables or disables voice chat through the server" ); +idCVar g_voiceChatDebug( "g_voiceChatDebug", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "display info on voicechat net traffic" ); + +void idMultiplayerGame::ReceiveAndForwardVoiceData( int clientNum, const idBitMsg &inMsg, int messageType ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + + idBitMsg outMsg; + int i; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 2]; + idPlayer * from; + + from = ( idPlayer * )gameLocal.entities[clientNum]; + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) || !from ) { + return; + } + + // Create a new packet with forwarded data + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteData( inMsg.GetReadData(), inMsg.GetRemainingData() ); + + if( g_voiceChatDebug.GetInteger() & 2 ) { + common->Printf( "VC: Received %d bytes, forwarding...\n", inMsg.GetRemainingData() ); + } + + // Forward to appropriate parties + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* to = ( idPlayer * )gameLocal.entities[i]; + if( to && to->GetUserInfo() && to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( i != gameLocal.localClientNum && CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to client %d\n", to->entityNumber ); + } + } + else + { + if( g_voiceChatDebug.GetInteger() ) + { + common->Printf( " ... suppressed packet to client %d\n", to->entityNumber ); + } + } + } + } + } + } + +#ifdef _USE_VOICECHAT + // Listen servers need to manually call the receive function + if ( gameLocal.isListenServer ) { + // Skip over control byte + outMsg.ReadByte(); + + idPlayer* to = gameLocal.GetLocalPlayer(); + if( to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + ReceiveAndPlayVoiceData( outMsg ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to local client %d\n", gameLocal.localClientNum ); + } + ReceiveAndPlayVoiceData( outMsg ); + } + } + } + } + } +#endif +} + +bool idMultiplayerGame::CanTalk( idPlayer *from, idPlayer *to, bool echo ) { + if ( !to ) { + return false; + } + + if ( !from ) { + return false; + } + + if ( from->spectating != to->spectating ) { + return false; + } + + if ( to->IsPlayerMuted( from ) ) { + return false; + } + + if ( to->GetArena() != from->GetArena() ) { + return false; + } + + if ( gameLocal.IsTeamGame() && from->team != to->team ) { + return false; + } + + if ( from == to ) { + return echo; + } + + return true; +} + +#ifdef _USE_VOICECHAT + +void idMultiplayerGame::XmitVoiceData( void ) +{ + idBitMsg outMsg; + int count; + byte work; + byte buffer[MAX_VOICE_PACKET_SIZE]; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 1]; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + + // Grab any new input and send up to the server + count = soundSystem->GetVoiceData( buffer, MAX_VOICE_PACKET_SIZE ); + if( count ) + { + outMsg.BeginWriting(); + outMsg.BeginReading(); + + work = GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT + cvarSystem->GetCVarBool( "s_voiceChatEcho" ) + ( cvarSystem->GetCVarInteger( "s_voiceChatTest" ) * 2 ); + outMsg.WriteByte( work ); + outMsg.WriteData( buffer, count ); + + // FIXME!!! FIXME!!! This should be unreliable - this is very bad + networkSystem->ClientSendReliableMessage( outMsg ); + + if( g_voiceChatDebug.GetInteger() & 1 ) + { + common->Printf( "VC: sent %d bytes at %d\n", count, gameLocal.time ); + } + } +} + +void idMultiplayerGame::ReceiveAndPlayVoiceData( const idBitMsg &inMsg ) +{ + int clientNum; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + clientNum = inMsg.ReadByte(); + soundSystem->PlayVoiceData( clientNum, inMsg.GetReadData(), inMsg.GetRemainingData() ); + if( g_voiceChatDebug.GetInteger() & 4 ) + { + common->Printf( "VC: Playing %d bytes\n", inMsg.GetRemainingData() ); + } +} + +#endif + +// end diff --git a/src/game/mp/stats/StatEvent.cpp b/src/game/mp/stats/StatEvent.cpp new file mode 100644 index 0000000..e1293d9 --- /dev/null +++ b/src/game/mp/stats/StatEvent.cpp @@ -0,0 +1,355 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatEvent.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + + +rvStatHit::rvStatHit( int t, int p, int v, int w, bool countForAccuracy ) : rvStat( t ) { + playerClientNum = p; + victimClientNum = v; + weapon = w; + trackAccuracy = countForAccuracy; + type = ST_HIT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatHit::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS && weapon >= 0 && weapon < MAX_WEAPONS && trackAccuracy ) { + stats[ playerClientNum ].weaponHits[ weapon ]++; + } + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_railgun" ) ) { + + // Apparently it does the rail hit event before the rail fired event, so this is backwards for a reason + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_HIT ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_RAIL_HIT; + } + + if ( rvStatManager::lastRailShot[ playerClientNum ] < stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } else { + if ( rvStatManager::lastRailShot[ playerClientNum ] >= stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + if ( (rvStatManager::lastRailShotHits[ playerClientNum ] % 2) == 0 ) { + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + ++rvStatManager::lastRailShotHits[ playerClientNum ]; + } else { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } + } + +/* + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 8000, 2, (rvStat**)lastHits ); + if( lastHits[0] ) { + if( !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[0])->weapon ) ), "weapon_railgun" ) ) { + + //Check to make sure we don't chain impressive awards. + if(lastHits[1] + && !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[1])->weapon ) ), "weapon_railgun" ) + && (lastHits[1]->GetTimeStamp() - lastHits[0]->GetTimeStamp()) < 4000) { + return; + + } + + if(gameLocal.time - lastHits[0]->GetTimeStamp() < 4000){ + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + } + } +*/ + } else if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_rocketlauncher" ) ) { + + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_FIRED ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_ROCKET_HIT; + } + + } +} + +rvStatKill::rvStatKill( int t, int p, int v, bool g, int mod ) : rvStat( t ) { + type = ST_KILL; + playerClientNum = p; + victimClientNum = v; + gibbed = g; + methodOfDeath = mod; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatKill::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum < 0 || playerClientNum >= MAX_CLIENTS || victimClientNum < 0 || victimClientNum >= MAX_CLIENTS ) { + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + idPlayer* victim = (idPlayer*)gameLocal.entities[ victimClientNum ]; + + // no award processing for suicides + if( victimClientNum == playerClientNum ) { + stats[ playerClientNum ].suicides++; + return; + } + + bool teamKill = false; + + // don't track team kills + if( !gameLocal.IsTeamGame() || player->team != victim->team ) { + stats[ playerClientNum ].kills++; + + if( methodOfDeath >= 0 && methodOfDeath < MAX_WEAPONS ) { + stats[ playerClientNum ].weaponKills[ methodOfDeath ]++; + } + } + + if( gameLocal.IsTeamGame() && player->team == victim->team ) { + teamKill = true; + } + + + // check for humiliation award + if( !teamKill && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_gauntlet" ) ) { + statManager->GiveInGameAward( IGA_HUMILIATION, playerClientNum ); + } + + rvStatKill* lastKills[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_KILL, gameLocal.time - 5000, 2, (rvStat**)lastKills ); + + // check for excellent award + if( !teamKill && lastKills[ 0 ] && lastKills[ 0 ]->GetTimeStamp() >= (gameLocal.time - 2000) && lastKills[ 0 ]->victimClientNum != playerClientNum && victimClientNum != playerClientNum ) { + statManager->GiveInGameAward( IGA_EXCELLENT, playerClientNum ); + } + + // check for rampage award + if( !teamKill && ( gibbed && victimClientNum != playerClientNum ) && + ( lastKills[ 0 ] && lastKills[ 0 ]->gibbed && lastKills[ 0 ]->victimClientNum != playerClientNum ) && + ( lastKills[ 1 ] && lastKills[ 1 ]->gibbed && lastKills[ 1 ]->victimClientNum != playerClientNum ) ) { + statManager->GiveInGameAward( IGA_RAMPAGE, playerClientNum ); + } + + + // check for combo kill award + if( victimClientNum != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_railgun" ) ) { + // the rail killing shot is the last hit, so look for the one past that + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 3000, 2, (rvStat**)lastHits ); + + if( lastHits[ 1 ] && lastHits[ 1 ]->GetVictimClientNum() != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", lastHits[ 1 ]->GetWeapon() ) ), "weapon_rocketlauncher" ) ) { + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_RAIL_HIT ) { + statManager->GiveInGameAward( IGA_COMBO_KILL, playerClientNum ); + } + } + } + + rvStatManager::comboKillState[ playerClientNum ] = CKS_NONE; + + + // check for defense award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + // defense is given for two conditions +// assert( gameLocal.mpGame.GetFlagEntity( TEAM_STROGG ) && gameLocal.mpGame.GetFlagEntity( TEAM_MARINE ) ); + assert( player->team >= 0 && player->team < TEAM_MAX ); + assert( gameLocal.mpGame.GetGameState()->IsType( rvCTFGameState::GetClassType() ) ); + + if ( gameLocal.mpGame.GetFlagEntity( player->team ) ) { + // defending your flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_AT_BASE || ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_DROPPED ) { + if( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if you're close to flag and you kill an enemy + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if ( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if enemy is close to flag and you kill them + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + + // defending your teammate carrying the enemy flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( gameLocal.mpGame.OpposingTeam( player->team ) ) == FS_TAKEN ) { + int clientNum = ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagCarrier( gameLocal.mpGame.OpposingTeam( player->team ) ); + + idPlayer* flagCarrier = NULL; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + flagCarrier = (idPlayer*)gameLocal.entities[ clientNum ]; + } + + if( flagCarrier ) { + if( (flagCarrier->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed enemy while close to the flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if( (flagCarrier->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed an enemy who was close to teh flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + } + + // check for holy shit award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + if( (player->team == TEAM_MARINE && victim->PowerUpActive( POWERUP_CTF_MARINEFLAG )) || + (player->team == TEAM_STROGG && victim->PowerUpActive( POWERUP_CTF_STROGGFLAG )) ) { + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( victim->team ) == FS_AT_BASE ) { + + // fixme: something is broken with the mpgame state + if ( gameLocal.mpGame.GetFlagEntity( victim->team ) ) { + if( (gameLocal.mpGame.GetFlagEntity( victim->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 40000 ) { + statManager->GiveInGameAward( IGA_HOLY_SHIT, playerClientNum ); + } + } + } + } + } +} + +/* +================ +rvStatDeath + +A player died +================ +*/ +rvStatDeath::rvStatDeath( int t, int p, int mod ) : rvStat( t ) { + type = ST_DEATH; + playerClientNum = p; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDeath::RegisterInGame( rvPlayerStat* stats ) { + stats[ playerClientNum ].deaths++; +} + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +rvStatDamageDealt::rvStatDamageDealt( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_DEALT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageDealt::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageGiven += damage; + } +} + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +rvStatDamageTaken::rvStatDamageTaken( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_TAKEN; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageTaken::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageTaken += damage; + } +} + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +rvStatFlagDrop::rvStatFlagDrop( int t, int p, int a, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + attacker = a; + type = ST_CTF_FLAG_DROP; +} + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +rvStatFlagReturn::rvStatFlagReturn( int t, int p, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + type = ST_CTF_FLAG_RETURN; +} + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +rvStatFlagCapture::rvStatFlagCapture( int t, int p, int f, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + flagTeam = f; + type = ST_CTF_FLAG_CAPTURE; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatFlagCapture::RegisterInGame( rvPlayerStat* stats ) { + statManager->GiveInGameAward( IGA_CAPTURE, playerClientNum ); + // figure out if we need to give an assist + + // see if someone carried the flag a bit, lost it, but it was capped + rvStatFlagDrop* flagDrop = (rvStatFlagDrop*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_DROP, gameLocal.time - 10000 ); + if( flagDrop && flagDrop->GetTeam() == team ) { + // only give the award if there was no flag return between the flag drop and the flag capture + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( flagTeam, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn == NULL ) { + statManager->GiveInGameAward( IGA_ASSIST, flagDrop->GetPlayerClientNum() ); + } + } + + // see if someone returned the flag, allowing a cap + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn ) { + statManager->GiveInGameAward( IGA_ASSIST, flagReturn->GetPlayerClientNum() ); + } +} diff --git a/src/game/mp/stats/StatEvent.h b/src/game/mp/stats/StatEvent.h new file mode 100644 index 0000000..44cfdb3 --- /dev/null +++ b/src/game/mp/stats/StatEvent.h @@ -0,0 +1,385 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatEvent.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATEVENT_H__ +#define __STATEVENT_H__ + +/* +=============================================================================== + +Multiplayer statistics events + +This file contains statistic event definitions. rvStatManager exposes +game-related functions (i.e. 'Kill', 'FlagCaptured') which create the +appropriate statistic event and add it to the statQueue in rvStatManager. + +The statQueue contains a complete picture of an MP game. It can be parsed to +calculate accuracies, award end-game awards, etc. + +Statistic events also have functionality to register in-game information for +on-the-fly statistics. + +=============================================================================== +*/ + +class rvPlayerStat; + +/* +================ +statType_t + +Type identifiers for RTTI of stat events, rvStatTeam-derived events must go +after ST_STAT_TEAM +================ +*/ +enum statType_t { + // not an actual event, undefined marker + ST_NONE = 0, + // rvStat derived + ST_BEGIN_GAME, + ST_END_GAME, + ST_CLIENT_CONNECT, + ST_HIT, + ST_KILL, + ST_DEATH, + ST_DAMAGE_DEALT, + ST_DAMAGE_TAKEN, + // not an actual event, team marker + ST_STAT_TEAM, + // rvStatTeam derived + ST_CTF_FLAG_CAPTURE, + ST_CTF_FLAG_DROP, + ST_CTF_FLAG_RETURN, + + ST_COUNT +}; + +/* +================ +rvStat + +An individual event we want to know about for stats. +================ +*/ +class rvStat { + + friend class rvStatAllocator; + +public: + + statType_t GetType( void ) const { return type; }; + int GetTimeStamp( void ) const { return timeStamp; }; + byte GetPlayerClientNum( void ) const { return playerClientNum; }; + +protected: + + // moved to protected to prevent allocating these on the normal heap. + // these should only be allocatd by rvStatAllocator. + rvStat( int t ) { timeStamp = t; playerClientNum = -1; type = ST_NONE; }; + virtual ~rvStat( void ) {}; + + virtual void RegisterInGame( rvPlayerStat* stats ) {}; + + // the entity number of the player associated with this statistic + byte playerClientNum; + + statType_t type; + int timeStamp; + +private: + // because of the way memory is handled for these we want the compiler + // on our side to find abuses. note that we aren't defining these. + // this change is propagated to all derived classes. + rvStat(); + rvStat( const rvStat &rhs ); + const rvStat &operator=( const rvStat &rhs ); +}; + +/* +================ +rvStatTeam + +A team-related rvStat +================ +*/ +class rvStatTeam : public rvStat { + + friend class rvStatAllocator; + +public: + virtual ~rvStatTeam( void ) {}; + + int GetTeam( void ) { return team; }; +protected: + + // see comment in rvStat + rvStatTeam( int t, int tm ) : rvStat( t ) { team = tm; }; + + byte team; + +private: + // see comment in rvStat + rvStatTeam(); + rvStatTeam( const rvStatTeam &rhs ); + const rvStatTeam &operator=( const rvStat &rhs ); + +}; + +/* +=============================================================================== + +rvStat/rvStatTeam-derived classes + +These are the individual events that get stored in the +statManager's stat queue + +=============================================================================== +*/ + +/* +================ +rvStatBeginGame + +A game has begun +================ +*/ +class rvStatBeginGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatBeginGame( int t ) : rvStat( t ) { type = ST_BEGIN_GAME; }; +}; + +/* +================ +rvStatEndGame + +The current game has ended +================ +*/ +class rvStatEndGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatEndGame( int t ) : rvStat( t ) { type = ST_END_GAME; }; +}; + +/* +================ +rvStatClientConnect + +A player has connected +================ +*/ +class rvStatClientConnect : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatClientConnect( int t, int p ) : rvStat( t ) { type = ST_CLIENT_CONNECT; playerClientNum = p; }; +}; + +/* +================ +rvStatHit + +A player hit another player +================ +*/ +class rvStatHit : public rvStat { + + friend class rvStatAllocator; + +public: + int GetVictimClientNum() const { return victimClientNum; } + int GetWeapon() const { return weapon; } + +protected: + rvStatHit( int t, int p, int v, int w, bool countForAccuracy ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + byte victimClientNum; + bool trackAccuracy; +}; + +/* +================ +rvStatKill + +A player killed another player +================ +*/ +class rvStatKill : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatKill( int t, int p, int v, bool g, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; + byte victimClientNum; + bool gibbed; +}; + +/* +================ +rvStatDeath + +A player died +================ +*/ +class rvStatDeath : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatDeath( int t, int p, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; +}; + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +class rvStatDamageDealt : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageDealt( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +class rvStatDamageTaken : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageTaken( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +class rvStatFlagDrop : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagDrop( int t, int p, int a, int tm ); + + byte attacker; // enemy who caused the flag drop + +private: + // see comment in rvStat + rvStatFlagDrop(); + rvStatFlagDrop( const rvStatFlagDrop &rhs ); + const rvStatFlagDrop &operator=( const rvStatFlagDrop &rhs ); +}; + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +class rvStatFlagReturn : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagReturn( int t, int p, int tm ); + +private: + // see comment in rvStat + rvStatFlagReturn(); + rvStatFlagReturn( const rvStatFlagReturn &rhs ); + const rvStatFlagReturn &operator=( const rvStatFlagReturn &rhs ); +}; + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +class rvStatFlagCapture : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagCapture( int t, int p, int f, int tm ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte flagTeam; // team of flag was captured + +private: + // see comment in rvStat + rvStatFlagCapture(); + rvStatFlagCapture( const rvStatFlagCapture &rhs ); + const rvStatFlagCapture &operator=( const rvStatFlagCapture &rhs ); +}; + +#endif diff --git a/src/game/mp/stats/StatManager.cpp b/src/game/mp/stats/StatManager.cpp new file mode 100644 index 0000000..05cf5da --- /dev/null +++ b/src/game/mp/stats/StatManager.cpp @@ -0,0 +1,1369 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + +// TTimo - *????????* +#include + +rvStatManager statManagerLocal; +rvStatManager* statManager = &statManagerLocal; + +comboKillState_t rvStatManager::comboKillState[ MAX_CLIENTS ] = { CKS_NONE }; +int rvStatManager::lastRailShot[ MAX_CLIENTS ] = { -2 }; +int rvStatManager::lastRailShotHits[ MAX_CLIENTS ] = { 0 }; + +inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ] = { + //IGA_INVALID + { + NULL + }, + //IGA_CAPTURE + { + "capture" + }, + //IGA_HUMILIATION + { + "humiliation" + }, + //IGA_IMPRESSIVE + { + "impressive" + }, + //IGA_EXCELLENT + { + "excellent" + }, + //IGA_ASSIST + { + "assist" + }, + //IGA_DEFENSE + { + "defense" + }, + //IGA_COMBO_KILL + { + "combo_kill" + }, + //IGA_RAMPAGE + { + "rampage" + }, + //IGA_HOLY_SHIT + { + "holy_shit" + } +}; + +// RAVEN BEGIN +// rhummer: localized these strings. +endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ] = { + //EGA_INVALID + { + NULL + }, + //EGA_LEMMING + { + "#str_107260" + }, + //EGA_RAIL_MASTER + { + "#str_107261" + }, + //EGA_ROCKET_SAUCE + { + "#str_107262" + }, + //EGA_BRAWLER + { + "#str_107263" + }, + //EGA_SNIPER + { + "#str_107264" + }, + //EGA_CRITICAL_FAILURE + { + "#str_107265" + }, + //EGA_TEAM_PLAYER + { + "#str_107266" + }, + //EGA_ACCURACY + { + "#str_107267" + }, + //EGA_FRAGS + { + "#str_107268" + }, + //EGA_PERFECT + { + "#str_107269" + } +}; +// RAVEN END + +void showStats_f( const idCmdArgs &args ) { + + statManager->DebugPrint(); +} + + +/* +=============================================================================== + + rvStatAllocator + + Handles allocating stat events + +=============================================================================== +*/ + +void *rvStatAllocator::GetBlock( size_t blockSize, int* blockNumOut /* = NULL */ ) { + // if not enough bytes for this allocation, get a new block + if ( GetBytesLeftInBlock() < blockSize ) { + // recycle & reuse a block + currentBlock++; + placeInBlock = 0; + if( currentBlock >= MAX_BLOCKS ) { + currentBlock = 0; + if( statManager->GetStat( 0 ) && gameLocal.isMultiplayer ) { +// int delta = gameLocal.time - statManager->GetStat( 0 )->GetTimeStamp(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Tracked %g seconds of stats before recycle\n", delta / 1000.0f ); + } + } +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Using block %d\n", currentBlock ); +// statManager->DebugPrint(); +// int numFreed = + statManager->FreeEvents( currentBlock ); +// statManager->DebugPrint(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - stat manager freed %d events which used block %d\n", numFreed, currentBlock ); + } + + // if this fires our objects are too large or our alloc size is too small + assert( GetBytesLeftInBlock() >= blockSize ); + + byte *newBlock = blocks + ( BLOCK_SIZE * currentBlock ) + placeInBlock; + placeInBlock += blockSize; + totalAllocations++; + totalBytesUsed += blockSize; + if( blockNumOut ) { + // return the current block number if requested + (*blockNumOut) = currentBlock; + } + return newBlock; +} + +void rvStatAllocator::Reset() { + currentBlock = 0; + placeInBlock = 0; + + totalBytesUsed = 0; + totalAllocations = 0; + totalBytesAllocated = 0; + for ( int i = 0; i < ST_COUNT; i++ ) { + allocationsByType[ i ] = 0; + } +} + +rvStatAllocator::rvStatAllocator() { + Reset(); +} + +void rvStatAllocator::Report() +{ + // shouchard: for debugging and tuning only + common->Printf( "rvStatAllocator: dump of usage stats\n" ); + common->Printf( "\t%d total bytes handed out in %d requests\n", GetTotalBytesUsed(), GetTotalAllocations() ); + common->Printf( "\tbegin game: %3d; end game: %3d\n", + GetAllocationsByType( ST_BEGIN_GAME ), + GetAllocationsByType( ST_END_GAME ) ); + common->Printf( "\tplayer hit: %3d; player kill: %3d\n", + GetAllocationsByType( ST_HIT ), + GetAllocationsByType( ST_KILL ) ); + common->Printf( "\tplayer death: %3d;\n", + GetAllocationsByType( ST_DEATH ) ); + common->Printf( "\tdamage dealt: %3d; damage taken: %3d\n", + GetAllocationsByType( ST_DAMAGE_DEALT ), + GetAllocationsByType( ST_DAMAGE_TAKEN ) ); + common->Printf( "\tstat team: %3d\n", + GetAllocationsByType( ST_STAT_TEAM ) ); + common->Printf( "\tflag capture: %3d;\n", + GetAllocationsByType( ST_CTF_FLAG_CAPTURE ) ), + common->Printf( "\tflag drop: %3d; flag return: %3d\n", + GetAllocationsByType( ST_CTF_FLAG_DROP ), + GetAllocationsByType( ST_CTF_FLAG_RETURN ) ); +} + +// object allocators + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new new +#endif // _INLINEDEBUGMEMORY + +rvStatBeginGame *rvStatAllocator::AllocStatBeginGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatBeginGame ), blockNumOut ); + assert( newBlock ); + rvStatBeginGame *stat = new( newBlock ) rvStatBeginGame( t ); + allocationsByType[ ST_BEGIN_GAME ]++; + return stat; +} + +rvStatEndGame *rvStatAllocator::AllocStatEndGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatEndGame ), blockNumOut ); + assert( newBlock ); + rvStatEndGame *stat = new( newBlock ) rvStatEndGame( t ); + allocationsByType[ ST_END_GAME ]++; + return stat; +} + +rvStatClientConnect *rvStatAllocator::AllocStatClientConnect( int t, int client, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatClientConnect ), blockNumOut ); + assert( newBlock ); + rvStatClientConnect *stat = new( newBlock ) rvStatClientConnect( t, client ); + allocationsByType[ ST_CLIENT_CONNECT ]++; + return stat; +} + +rvStatHit *rvStatAllocator::AllocStatHit( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatHit ), blockNumOut ); + assert( newBlock ); + rvStatHit *stat = new( newBlock ) rvStatHit( t, p, v, w, countForAccuracy ); + allocationsByType[ ST_HIT ]++; + return stat; +} + +rvStatKill *rvStatAllocator::AllocStatKill( int t, int p, int v, bool g, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatKill ), blockNumOut ); + assert( newBlock ); + rvStatKill *stat = new( newBlock ) rvStatKill( t, p, v, g, mod ); + allocationsByType[ ST_KILL ]++; + return stat; +} + +rvStatDeath *rvStatAllocator::AllocStatDeath( int t, int p, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDeath ), blockNumOut ); + assert( newBlock ); + rvStatDeath *stat = new( newBlock ) rvStatDeath( t, p, mod ); + allocationsByType[ ST_DEATH ]++; + return stat; +} + +rvStatDamageDealt *rvStatAllocator::AllocStatDamageDealt( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageDealt ), blockNumOut ); + assert( newBlock ); + rvStatDamageDealt *stat = new( newBlock ) rvStatDamageDealt( t, p, w, d ); + allocationsByType[ ST_DAMAGE_DEALT ]++; + return stat; +} + +rvStatDamageTaken *rvStatAllocator::AllocStatDamageTaken( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageTaken ), blockNumOut ); + assert( newBlock ); + rvStatDamageTaken *stat = new( newBlock ) rvStatDamageTaken( t, p, w, d ); + allocationsByType[ ST_DAMAGE_TAKEN ]++; + return stat; +} + +rvStatFlagDrop *rvStatAllocator::AllocStatFlagDrop( int t, int p, int a, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagDrop ), blockNumOut ); + assert( newBlock ); + rvStatFlagDrop *stat = new( newBlock ) rvStatFlagDrop( t, p, a, tm ); + allocationsByType[ ST_CTF_FLAG_DROP ]++; + return stat; +} + +rvStatFlagReturn *rvStatAllocator::AllocStatFlagReturn( int t, int p, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagReturn ), blockNumOut ); + assert( newBlock ); + rvStatFlagReturn *stat = new( newBlock ) rvStatFlagReturn( t, p, tm ); + allocationsByType[ ST_CTF_FLAG_CAPTURE ]++; + return stat; +} + +rvStatFlagCapture *rvStatAllocator::AllocStatFlagCapture( int t, int p, int f, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagCapture ), blockNumOut ); + assert( newBlock ); + rvStatFlagCapture *stat = new( newBlock ) rvStatFlagCapture( t, p, f, tm ); + allocationsByType[ ST_CTF_FLAG_RETURN ]++; + return stat; +} + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new ID_DEBUG_NEW +#endif // _INLINEDEBUGMEMORY + +/* +=============================================================================== + + rvStatManager + + Stores game statistic events in statQueue + +=============================================================================== +*/ + +// shouchard: stat manager start with 1 meg; we'll tune as we get better data +rvStatManager::rvStatManager() { + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Init( void ) { + Shutdown(); + statQueue.Clear(); + awardQueue.Clear(); + statQueue.SetGranularity( 1024 ); + endGameSetup = false; + cmdSystem->AddCommand( "ShowInGameStats", showStats_f, CMD_FL_SYSTEM, "show in game stats." ); + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Shutdown( void ) { + statAllocator.Report(); + statAllocator.Reset(); + statQueue.Clear(); + awardQueue.Clear(); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::BeginGame( void ) { + int blockNum; + rvStatBeginGame* stat = statAllocator.AllocStatBeginGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + endGameSetup = false; +#if ID_TRAFFICSTATS + startTime = gameLocal.time; + networkSystem->GetTrafficStats( startSent, startPacketsSent, startReceived, startPacketsReceived ); +#endif + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::EndGame( void ) { + int blockNum; + rvStatEndGame* stat = statAllocator.AllocStatEndGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + CalculateEndGameStats(); + awardQueue.Clear(); +#if ID_TRAFFICSTATS + int sent, packetsSent, received, packetsReceived, time; + networkSystem->GetTrafficStats( sent, packetsSent, received, packetsReceived ); + sent -= startSent; + packetsSent -= startPacketsSent; + received -= startReceived; + packetsReceived -= startPacketsReceived; + time = gameLocal.time - startTime; + common->Printf( "EndGame. bytes sent: %d packets sent: %d bytes received: %d packets received: %d\n", sent, packetsSent, received, packetsReceived ); + // compute averages, including packet overhead + // adjust the UDP overhead, may depend on your TCP stack implementation ( 42 comes from ethereal analysis of the traffic ) + sent += packetsSent * 42; + received += packetsReceived * 42; + float sentBps, recvBps; + sentBps = (float)( sent ) * 1000.0f / time; + recvBps = (float)( received ) * 1000.0f / time; + common->Printf( "avg sent %g B/s, received %g B/s\n", sentBps, recvBps ); +#endif +} + +void rvStatManager::ClientConnect( int clientNum ) { + // push a client connected event into the queue so we don't get confused with old + // events detailing the previous owner of this clientNum + int blockNum; + rvStatClientConnect* stat = statAllocator.AllocStatClientConnect( gameLocal.time, clientNum, &blockNum ); + statQueue.Append( rvPair( (rvStat*)stat, blockNum ) ); +} + +void rvStatManager::ClientDisconnect( int clientNum ) { + // re-init player stats + playerStats[ clientNum ] = rvPlayerStat(); +} + +void rvStatManager::Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ) { + int deathBlock, killBlock; + rvStatDeath* statDeath = statAllocator.AllocStatDeath( gameLocal.time, victim->entityNumber, methodOfDeath, &deathBlock ); + + statQueue.Append( rvPair( (rvStat*)statDeath, deathBlock ) ); + + if( killer && killer->IsType( idPlayer::GetClassType() ) ) { + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, killer->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } else if( !killer ) { + // basically a suicide + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, victim->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } +} + +void rvStatManager::FlagCaptured( const idPlayer* player, int flagTeam ) { + int blockNum; + rvStatFlagCapture* stat = statAllocator.AllocStatFlagCapture( gameLocal.time, player->entityNumber, flagTeam, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::WeaponFired( const idPlayer* player, int weapon, int num ) { + playerStats[ player->entityNumber ].weaponShots[ weapon ] += num; + lastRailShotHits[ player->entityNumber ] = 0; + + comboKillState_t cks = comboKillState[ player->entityNumber ]; + comboKillState[ player->entityNumber ] = CKS_NONE; + if ( player->GetWeaponIndex( "weapon_rocketlauncher" ) == weapon ) { + if ( cks == CKS_NONE ) { + comboKillState[ player->entityNumber ] = CKS_ROCKET_FIRED; + } + } else if ( player->GetWeaponIndex( "weapon_railgun" ) == weapon ) { + // apparently it processes hits before it does the fire.... + if ( cks == CKS_RAIL_HIT ) { + comboKillState[ player->entityNumber ] = CKS_RAIL_FIRED; + } + } + +} + +void rvStatManager::WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if( victim && victim != attacker ) { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + // if attacker was a player, track hit and damage dealt + int hitBlock; + rvStatHit* statHit = statAllocator.AllocStatHit( gameLocal.time, attacker->entityNumber, victim->entityNumber, weapon, countForAccuracy, &hitBlock ); + + statQueue.Append( rvPair( (rvStat*)(statHit), hitBlock ) ); + } + } +} + +//asalmon modified to work for single player stats on Xenon +void rvStatManager::Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if(attacker) + { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + int damageBlock; + rvStatDamageDealt* statDamage = statAllocator.AllocStatDamageDealt( gameLocal.time, attacker->entityNumber, weapon, damage, &damageBlock ); + statQueue.Append( rvPair( (rvStat*)(statDamage), damageBlock ) ); + } + } + + if(victim) + { + if( victim->IsType( idPlayer::GetClassType() ) ) { + // if victim was a player, track damage taken + int blockNum; + rvStatDamageTaken* stat = statAllocator.AllocStatDamageTaken( gameLocal.time, victim->entityNumber, weapon, damage, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + } + } +} + +void rvStatManager::FlagDropped( const idPlayer* player, const idEntity* attacker ) { + int blockNum; + rvStatFlagDrop* stat = statAllocator.AllocStatFlagDrop( gameLocal.time, player->entityNumber, attacker->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} +void rvStatManager::FlagReturned( const idPlayer* player ) { + int blockNum; + rvStatFlagReturn* stat = statAllocator.AllocStatFlagReturn( gameLocal.time, player->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::DebugPrint( void ) { + if( !gameLocal.isMultiplayer ) { + return; + } + //gameLocal.Printf( "Begin statistics debug dump:\n" ); + + //gameLocal.Printf( "Statistics queue:\n" ); + for( int i = 0; i < Min( statQueue.Num(), 50 ); i++ ) { + gameLocal.Printf( "{%d, %d} ", statQueue[i].First()->GetType(), statQueue[i].First()->GetTimeStamp() ); + } + gameLocal.Printf("\n"); + + //gameLocal.Printf( "In-game statistics\n\n" ); + //for( int i = 0; i < inGameStats.Num(); i++ ) { + // gameLocal.Printf( "\t%d - %s:\n", i, statIndex->index[ i ].GetName().c_str() ); + // gameLocal.Printf( "\t\tKills: %d\n", inGameStats[i].kills ); + // gameLocal.Printf( "\t\tDeaths: %d\n", inGameStats[i].deaths ); + // gameLocal.Printf( "\t\tFlag Caps: %d\n", inGameStats[i].flagCaptures ); + + // for( int j = 0; j < MAX_WEAPONS; j++ ) { + // gameLocal.Printf( "\t\tWeapon %d hits: %d\n\t\tWeapon %d shots: %d\n", j, inGameStats[i].weaponHits[j], j, inGameStats[i].weaponShots[j] ); + // } + //} +} + +int rvStatManager::FreeEvents( int blockNum ) { + int blockStart = -1; + int blockEnd = -1; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( blockStart == -1 && statQueue[ i ].Second() == blockNum ) { + blockStart = i; + } else if( blockStart != -1 && statQueue[ i ].Second() != blockNum ) { + blockEnd = i; + break; + } + } + + if( blockStart == -1 || blockEnd == -1 ) { +// rjohnson: commented out warning - I take it this is not a bad message? +// gameLocal.Warning( "rvStatManager::FreeEvents() - Could not find events with block num '%d'\n", blockNum ); + return 0; + } + + statQueue.RemoveRange( blockStart, blockEnd - 1 ); + + return (blockEnd - blockStart); +} + +void rvStatManager::SendInGameAward( inGameAward_t award, int clientNum ) { + assert( gameLocal.isServer ); + + idBitMsg msg; + byte msgBuf[1024]; + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_RELIABLE_MESSAGE_INGAMEAWARD ); + + msg.WriteByte( award ); + msg.WriteByte( clientNum ); + + networkSystem->ServerSendReliableMessage( -1, msg ); + + if( gameLocal.isListenServer ) { + msg.ReadByte(); + ReceiveInGameAward( msg ); + } +} + + +void rvStatManager::ReceiveInGameAward( const idBitMsg& msg ) { + assert( gameLocal.isClient || gameLocal.isListenServer ); + int numAwards = 0; + + inGameAward_t award = (inGameAward_t)msg.ReadByte(); + int client = msg.ReadByte(); + + // display award on hud + idPlayer* player = gameLocal.GetLocalPlayer(); + idPlayer* remote = gameLocal.GetClientByNum(client); + bool justSound = false; + if( client != gameLocal.localClientNum ) { + justSound = true; + } + + if ( ( gameLocal.time - inGameAwardHudTime ) < 3000 || awardQueue.Num() > 0 ) { + if ( gameLocal.GetDemoFollowClient() == client || ( player && player->GetInstance() == remote->GetInstance() ) ) { + rvPair awardPair(award, justSound); + awardQueue.StackAdd(awardPair); + return; + } + } + + if( client == gameLocal.localClientNum ) { + // don't count awards during warmup + + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + localInGameAwards[ award ]++; + numAwards = localInGameAwards[ award ]; + } else { + numAwards = 1; + } + + if( player && player->mphud ) { + player->mphud->HandleNamedEvent( "clearIGA" ); + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, numAwards ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if( numAwards < 10 ) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, numAwards ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + } else { + player->mphud->SetStateInt( "ig_award_num", numAwards ); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + //inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + + if ( player ) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + else if ( player && remote && (player->GetInstance() == remote->GetInstance()) && (award == IGA_HOLY_SHIT || award == IGA_CAPTURE)) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + inGameAwardHudTime = gameLocal.time; + + idPlayer* awardee = gameLocal.GetClientByNum( client ); + if ( awardee ) { + if ( player && player->GetInstance() == awardee->GetInstance() ) { + iconManager->AddIcon( client, va( "mtr_award_%s", inGameAwardInfo[ award ].name ) ); + } + } +} + +void rvStatManager::CheckAwardQueue() { + + if(((gameLocal.time - inGameAwardHudTime) < 3000 || awardQueue.Num() == 0)) + { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + int award = awardQueue.StackTop().First(); + bool justSound = awardQueue.StackTop().Second(); + awardQueue.StackPop(); + + if ( player ) { + if(!justSound || award == IGA_HOLY_SHIT || award == IGA_CAPTURE) + { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + if( player && player->mphud && !justSound) { + + player->mphud->HandleNamedEvent( "clearIGA" ); + localInGameAwards[ award ]++; + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, localInGameAwards[ award ] ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if(localInGameAwards[ award ] < 10) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, localInGameAwards[ award ] ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + + } + else { + player->mphud->SetStateInt( "ig_award_num", localInGameAwards[ award ]); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + +} + + +void rvStatManager::GivePlayerCashForAward( idPlayer* player, inGameAward_t award ) +{ + if( !player ) + return; + + if( !gameLocal.isMultiplayer ) + return; + + if( !gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + return; + + mpGameState_t mpGameState = gameLocal.mpGame.GetGameState()->GetMPGameState(); + if( mpGameState != GAMEON && mpGameState != SUDDENDEATH ) + return; + + const char* awardCashValueName = NULL; + switch( award ) + { + case IGA_CAPTURE: awardCashValueName = "playerCashAward_mpAward_capture"; break; + case IGA_HUMILIATION: awardCashValueName = "playerCashAward_mpAward_humiliation"; break; + case IGA_IMPRESSIVE: awardCashValueName = "playerCashAward_mpAward_impressive"; break; + case IGA_EXCELLENT: awardCashValueName = "playerCashAward_mpAward_excellent"; break; + case IGA_ASSIST: awardCashValueName = "playerCashAward_mpAward_assist"; break; + case IGA_DEFENSE: awardCashValueName = "playerCashAward_mpAward_defense"; break; + case IGA_COMBO_KILL: awardCashValueName = "playerCashAward_mpAward_combo_kill"; break; + case IGA_RAMPAGE: awardCashValueName = "playerCashAward_mpAward_rampage"; break; + case IGA_HOLY_SHIT: awardCashValueName = "playerCashAward_mpAward_holy_shit"; break; + } + + if( awardCashValueName ) + { + player->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( awardCashValueName, 0 ) ); + } +} + + +void rvStatManager::GiveInGameAward( inGameAward_t award, int clientNum ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ clientNum ]; + + if( gameLocal.isMultiplayer ) { + // show in-game awards during warmup, but don't actually let players accumulate them + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + playerStats[ clientNum ].inGameAwards[ award ]++; + GivePlayerCashForAward( player, award ); + } + SendInGameAward( award, clientNum ); + } +} + +void rvStatManager::SetupStatWindow( idUserInterface* statHud ) { + statWindow.SetupStatWindow( statHud ); +} + +void rvStatManager::SelectStatWindow( int selectionIndex, int selectionTeam ) { + statWindow.SelectPlayer( statWindow.ClientNumFromSelection( selectionIndex, selectionTeam ) ); +} + +int rvStatManager::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + return statWindow.GetSelectedClientNum( selectionIndexOut, selectionTeamOut ); +} + +void rvStatManager::UpdateInGameHud( idUserInterface* statHud, bool visible ) { + idPlayer* player = NULL; + + if( gameLocal.GetLocalPlayer() ) { + player = gameLocal.GetLocalPlayer(); + player->GetHud()->SetStateInt( "stat_visible", visible? 1 : 0); + } + + if( !visible ) { + statHud->SetStateInt( "stat_visible", 0 ); + return; + } else { + statHud->SetStateInt( "stat_visible", 1 ); + } + + if( player ) { + statWindow.SetupStatWindow( statHud, player->spectating ); + } +} + +void rvStatManager::SendStat( int toClient, int statClient ) { + if( statClient < 0 || statClient >= MAX_CLIENTS ) { + gameLocal.Warning( "rvStatManager::SendStat() - Stats requested for invalid client num '%d'\n", statClient ); + return; + } + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( statClient ); + + playerStats[ statClient ].PackStats( outMsg ); + + networkSystem->ServerSendReliableMessage( toClient, outMsg ); +} + +void rvStatManager::ReceiveStat( const idBitMsg& msg ) { + //asalmon: added because this is used to restore single player stats from saves on Xbox 360 + if(gameLocal.IsMultiplayer()) + { + assert( gameLocal.isClient ); + } + + int client = msg.ReadByte(); + + playerStats[ client ].UnpackStats( msg ); + playerStats[ client ].lastUpdateTime = gameLocal.time; + + // display the updated stat + if(gameLocal.IsMultiplayer()) + { + statWindow.SelectPlayer( client ); + } +} + +void rvStatManager::SendAllStats( int clientNum, bool full ) { + + assert( gameLocal.isServer ); + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ALL_STATS ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = 0; + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( gameLocal.entities[ i ] ) { + sentClients |= 1 << i; + } + } + + outMsg.WriteBits( sentClients, MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].PackStats( outMsg ); + } + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + //common->Printf("SENT ALL STATS %i\n", Sys_Milliseconds()); + +} + + +void rvStatManager::ReceiveAllStats( const idBitMsg& msg ) { + assert( gameLocal.isClient ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = msg.ReadBits( MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].UnpackStats( msg ); + } else { + playerStats[ i ].Clear(); + } + + playerStats[ i ].lastUpdateTime = gameLocal.time; + } + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == GAMEREVIEW ) { + gameLocal.mpGame.ShowStatSummary(); + } +} + + +void rvStatManager::ClearStats( void ) { + // clear connected stats + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +#ifdef _XENON + lastFullUpdate = -50000; +#endif +} + +void rvStatManager::CalculateEndGameStats( void ) { + int maxKills = idMath::INT_MIN; + int maxKillPlayer = -1; + + int maxSuicides = idMath::INT_MIN; + int maxSuicidesPlayer = -1; + + int maxGauntletKills = idMath::INT_MIN; + int maxGauntletKillsPlayer = -1; + + float maxDamageKillsRatio = 0.0; + int maxDamageKillsRatioPlayer = -1; + +//Dump the stats to a log file + idFile *log = NULL; + if(cvarSystem->GetCVarBool("com_logMPStats")) + { + log = fileSystem->OpenFileAppend("StatisticsLog.txt"); + } + idStr toFile; + if ( !log ) { +// common->Warning("Statistics log will not be written\n"); + } + else + { + struct tm *newtime; + time_t aclock; + time( &aclock ); + newtime = localtime( &aclock ); + toFile = va("Match on map %s played on %s\n", gameLocal.GetMapName(), asctime( newtime )); + log->Write(toFile.c_str(), toFile.Length()); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( !gameLocal.entities[ i ] ) { + continue; + } + + if(log) + { + toFile = va("Statistics for %s\n", gameLocal.userInfo[ i ].GetString("ui_name")); + log->Write(toFile.c_str(), toFile.Length()); + toFile = va("Kills: %i Deaths: %i Suicides: %i\n", playerStats[ i ].kills, playerStats[ i ].deaths, playerStats[ i ].suicides); + log->Write(toFile.c_str(), toFile.Length()); + } + + gameLocal.Printf( "Calculating stats for client %d (%s)\n", i, gameLocal.userInfo[ i ].GetString("ui_name") ); + // overall accuracy award and sniper accuracy award + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + int railgunIndex = player->GetWeaponIndex( "weapon_railgun" ); + int rocketIndex = player->GetWeaponIndex( "weapon_rocketlauncher" ); + + float accuracyAverage = 0.0f; + int numAccuracies = 0; + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats[ i ].weaponShots[ j ] == 0 ) { + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s not used\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name"))); + log->Write(toFile.c_str(), toFile.Length()); + } + } + continue; + } + + float weaponAccuracy = (float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]; + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s: %i%%\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name")), (int)(((float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]) * 100.0f)); + log->Write(toFile.c_str(), toFile.Length()); + } + } + if( j == railgunIndex ) { + // sniper award + if( weaponAccuracy >= 0.9f && playerStats[ i ].weaponShots[ railgunIndex ] >= 10 ) { + playerStats[ i ].endGameAwards.Append( EGA_SNIPER ); + } + } + + accuracyAverage += weaponAccuracy; + numAccuracies++; + } + + + if ( numAccuracies && ( accuracyAverage / (float)numAccuracies >= 0.5f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ACCURACY ); + } + + + // rail master award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ railgunIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_RAIL_MASTER ); + } + + // rocket sauce award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ rocketIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ROCKET_SAUCE ); + } + + // critical failure award + if( playerStats[ i ].kills == 0 ) { + playerStats[ i ].endGameAwards.Append( EGA_CRITICAL_FAILURE ); + } + + // frags award +//asalmon: Made the limit more reasonable for the shorter time limits and less players of Xenon +#ifdef _XENON + if( playerStats[ i ].kills >= 50 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#else + if( playerStats[ i ].kills >= 100 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#endif + + + if( playerStats[ i ].kills > maxKills ) { + maxKills = playerStats[ i ].kills; + maxKillPlayer = i; + } + + if( playerStats[ i ].suicides > maxSuicides ) { + maxSuicides = playerStats[ i ].suicides; + maxSuicidesPlayer = i; + } + + if( playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ] > maxGauntletKills ) { + maxGauntletKills = playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ]; + maxGauntletKillsPlayer = i; + } + +//asalmon: Calculate the damage ratio: + if(playerStats[i].kills > 0) + { + playerStats[i].damageRatio = playerStats[i].damageGiven / playerStats[i].kills; + } + else + { + playerStats[i].damageRatio = playerStats[i].damageGiven; + } + + if( playerStats[ i ].damageRatio > maxDamageKillsRatio ) { + maxDamageKillsRatio = playerStats[ i ].damageRatio; + maxDamageKillsRatioPlayer = i; + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + } + + +//asalmon: hack to test certain achievement awards. +#ifdef _XENON + if(cvarSystem->GetCVarInteger("si_overrideFrags")) + { + common->Printf("Overriding Frags to: %i for player %i\n", cvarSystem->GetCVarInteger("si_overrideFrags"), i); + playerStats[i].kills = cvarSystem->GetCVarInteger("si_overrideFrags"); + } +#endif + + } + + + + // Perfect award + if( maxKillPlayer >= 0 && playerStats[ maxKillPlayer ].deaths == 0 ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ maxKillPlayer ]; + if( !gameLocal.IsTeamGame() || ( gameLocal.IsTeamGame() && player && gameLocal.mpGame.TeamLeader() == player->team ) ) { + playerStats[ maxKillPlayer ].endGameAwards.Append( EGA_PERFECT ); + } + } + + // Lemming award + if( maxSuicidesPlayer >= 0 && maxSuicides >= 5 ) { + playerStats[ maxSuicidesPlayer ].endGameAwards.Append( EGA_LEMMING ); + } + + // Brawler award + if( maxGauntletKillsPlayer >= 0 && maxGauntletKills >= 3 ) { + playerStats[ maxGauntletKillsPlayer ].endGameAwards.Append( EGA_BRAWLER ); + } + + // Team player award + if( maxDamageKillsRatioPlayer >= 0 && maxDamageKillsRatio > 500 ) { + playerStats[ maxDamageKillsRatioPlayer ].endGameAwards.Append( EGA_TEAM_PLAYER ); + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + log->Flush(); + fileSystem->CloseFile(log); + } +} + +void rvStatManager::GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ) { + memset( accuracyLeaders, -1, sizeof( int ) * MAX_WEAPONS ); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] == NULL ) { + continue; + } + + rvPlayerStat* playerStats = GetPlayerStat( i ); + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats->weaponShots[ j ] == 0 ) { + continue; + } + + float playerAccuracy = (float)playerStats->weaponHits[ j ] / (float)playerStats->weaponShots[ j ]; + float leaderAccuracy = -1.0f; + if( accuracyLeaders[ j ] != -1 ) { + rvPlayerStat* leaderStats = GetPlayerStat( accuracyLeaders[ j ] ); + if( leaderStats->weaponShots[ j ] != 0 ) { + leaderAccuracy = (float)leaderStats->weaponHits[ j ] / (float)leaderStats->weaponShots[ j ]; + } + } + if( playerAccuracy > leaderAccuracy ) { + accuracyLeaders[ j ] = i; + } + } + } +} + +int rvStatManager::DamageGiven( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_DEALT ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && (statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +int rvStatManager::DamageTaken( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_TAKEN ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && ( statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound ) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +rvStat* rvStatManager::GetLastClientStat( int clientNum, statType_t type, int time ) { + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + return statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ) { + int numFound = 0; + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + results[ numFound++ ] = statQueue[ i ].First(); + if( numFound >= num ) { + return; + } + } + } + } + + return; +} + + +rvStatTeam* rvStatManager::GetLastTeamStat( int team, statType_t type, int time ) { + assert( type > ST_STAT_TEAM ); + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( ((rvStatTeam*)statQueue[ i ].First())->GetTeam() == team ) { + return (rvStatTeam*)statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::SetupEndGameHud( idUserInterface* statHud ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = gameLocal.mpGame.GetRankedPlayer( i ); + + if( p && gameLocal.mpGame.IsInGame( p->entityNumber ) ) { + statHud->SetStateString( va( "player%d_name", i + 1 ), va( "%d. %s", i + 1, gameLocal.userInfo[ p->entityNumber ].GetString( "ui_name" ) ) ); + statHud->SetStateString( va( "player%d_score", i + 1 ), va( "%d", gameLocal.mpGame.GetScore( i ) ) ); + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 1 ); + statHud->SetStateInt( va( "player%d_team", i + 1 ), gameLocal.IsTeamGame() ? p->team : 0 ); + } else { + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 0 ); + } + } + statHud->HandleNamedEvent( "Setup" ); +} + +rvPlayerStat* rvStatManager::GetPlayerStat( int clientNum ) { + return &playerStats[ clientNum ]; +} + +void rvStatManager::UpdateEndGameHud( idUserInterface* statHud, int clientNum ) { + /*if( !endGameSetup ) { + // no info yet + SetupEndGameHud( statHud ); + endGameSetup = true; + } + + rvPlayerStat* clientStat = &(playerStats[ clientNum ]); + + statHud->HandleNamedEvent( "clear" ); + statHud->SetStateString( "stat_name", gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ); + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", (int)(clientStat->weaponAccuracy[ i ] * 100) ) ); + } + + // in-game awards + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < clientStat->inGameAwards.Num(); i++ ) { + igAwardCount[ clientStat->inGameAwards[ i ] ]++; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < clientStat->endGameAwards.Num(); i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); + statHud->SetStateString( va( "eg_award%d_text", i ), endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ); + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) );*/ +} + +/* +=============================================================================== + + rvStatSummary + + Stores one player's summary information. Transmitted to clients for + intermission summary screen. + +=============================================================================== +*/ +rvPlayerStat::rvPlayerStat() { + Clear(); +} + +void rvPlayerStat::Clear( void ) { + memset( weaponShots, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponHits, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponKills, 0, sizeof(int) * MAX_WEAPONS ); + memset( inGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + + kills = deaths = suicides = lastUpdateTime = damageTaken = damageGiven = 0; + damageRatio = 0.0f; +} + +void rvPlayerStat::PackStats( idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponShots[ i ] ); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponHits[ i ] ); + } + + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + msg.WriteByte( inGameAwards[ i ] ); + } + + msg.WriteByte( endGameAwards.Num() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + msg.WriteByte( endGameAwards[ i ] ); + } + + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXDEATHS, deaths ), ASYNC_PLAYER_DEATH_BITS ); + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXKILLS, kills ), ASYNC_PLAYER_KILL_BITS ); +} + +void rvPlayerStat::UnpackStats( const idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponShots[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponHits[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + inGameAwards[ i ] = msg.ReadByte(); + } + + endGameAwards.SetNum( msg.ReadByte() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + endGameAwards[ i ] = (endGameAward_t)msg.ReadByte(); + } + + deaths = msg.ReadBits( ASYNC_PLAYER_DEATH_BITS ); + kills = msg.ReadBits( ASYNC_PLAYER_KILL_BITS ); +} diff --git a/src/game/mp/stats/StatManager.h b/src/game/mp/stats/StatManager.h new file mode 100644 index 0000000..458f734 --- /dev/null +++ b/src/game/mp/stats/StatManager.h @@ -0,0 +1,373 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATMANAGER_H__ +#define __STATMANAGER_H__ + +#include "StatEvent.h" +#include "StatWindow.h" + +#ifndef ID_TRAFFICSTATS + #define ID_TRAFFICSTATS 0 +#endif + +/* +=============================================================================== + + Multiplayer game statistics + +=============================================================================== +*/ + +/* +================ +endGameAward_t + +End-game award IDs +================ +*/ +enum endGameAward_t { + EGA_INVALID = 0, + EGA_LEMMING, + EGA_RAIL_MASTER, + EGA_ROCKET_SAUCE, + EGA_BRAWLER, + EGA_SNIPER, + EGA_CRITICAL_FAILURE, + EGA_TEAM_PLAYER, + EGA_ACCURACY, + EGA_FRAGS, + EGA_PERFECT, + EGA_NUM_AWARDS +}; + +/* +================ +endGameAwardInfo_t + +end-game award info +================ +*/ +struct endGameAwardInfo_t { + char* name; +}; + +/* +================ +inGameAward_t + +in-game award IDs +================ +*/ +enum inGameAward_t { + IGA_INVALID = 0, + IGA_CAPTURE, + IGA_HUMILIATION, + IGA_IMPRESSIVE, + IGA_EXCELLENT, + IGA_ASSIST, + IGA_DEFENSE, + IGA_COMBO_KILL, + IGA_RAMPAGE, + IGA_HOLY_SHIT, + IGA_NUM_AWARDS +}; + +enum comboKillState_t { + CKS_NONE, + CKS_ROCKET_FIRED, + CKS_ROCKET_HIT, + CKS_RAIL_FIRED, + CKS_RAIL_HIT, +}; + +/* +================ +inGameAwardInfo_t + +in-game award info +================ +*/ +struct inGameAwardInfo_t { + char* name; +}; + +/* +================ +rvStatAllocator + +A simple block allocator for different stat objects. +Note: this isn't a real heap; it has no delete or tracking of free blocks. +It matches the memory usage of the rvStatManager which dumps its entire +memory all at once at very specific times. If that ever changes, this will +need to change. +================ +*/ + +const int BLOCK_SIZE = 1024; +const int MAX_BLOCKS = 128; + +class rvStatAllocator { + protected: + byte blocks[ BLOCK_SIZE * MAX_BLOCKS ]; + short currentBlock; // current block + size_t placeInBlock; // current position, offset from start of block + size_t totalBytesUsed; // total bytes handed out from the allocator + size_t totalAllocations; // total blocks handed out from the allocator + size_t totalBytesAllocated; // total bytes in all allocated blocks + size_t allocationsByType[ST_COUNT]; // one spare at [0]; allocations per type of event + + void *GetBlock( size_t blockSize, int* blockNumOut = NULL ); + + public: + rvStatAllocator(); + void Report(); + void Reset(); + + // object allocators + rvStatBeginGame *AllocStatBeginGame ( int t, int* blockNumOut = NULL ); + rvStatEndGame *AllocStatEndGame ( int t, int* blockNumOut = NULL ); + rvStatClientConnect *AllocStatClientConnect ( int t, int client, int* blockNumOut = NULL ); + rvStatHit *AllocStatHit ( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut = NULL ); + rvStatKill *AllocStatKill ( int t, int p, int v, bool g, int mod, int* blockNumOut = NULL ); + rvStatDeath *AllocStatDeath ( int t, int p, int mod, int* blockNumOut = NULL ); + rvStatDamageDealt *AllocStatDamageDealt ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatDamageTaken *AllocStatDamageTaken ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatFlagDrop *AllocStatFlagDrop ( int t, int p, int a, int tm, int* blockNumOut = NULL ); + rvStatFlagReturn *AllocStatFlagReturn ( int t, int p, int tm, int* blockNumOut = NULL ); + rvStatFlagCapture *AllocStatFlagCapture ( int t, int p, int f, int tm, int* blockNumOut = NULL ); + + // bookkeeping readers + ID_INLINE size_t GetBytesLeftInBlock() const { + assert( placeInBlock <= BLOCK_SIZE ); + return ( size_t )( BLOCK_SIZE - placeInBlock ); + } + + ID_INLINE size_t GetPlaceInBlock() const { + return placeInBlock; + } + + ID_INLINE size_t GetTotalBytesUsed() const { + return totalBytesUsed; + } + + ID_INLINE size_t GetTotalAllocations() const { + return totalAllocations; + } + + ID_INLINE size_t GetAllocationsByType( statType_t type ) const { + return allocationsByType[ type ]; + } + + ID_INLINE size_t GetTotalBytesAllocated() const { + return totalBytesAllocated; + } +}; + + +/* +================ +rvPlayerStat + +Stores one player's stat information. +================ +*/ +class rvPlayerStat { +public: + rvPlayerStat(); + + void Clear( void ); + void CalculateStats( int p, const idList& playerStats ); + + void PackStats( idBitMsg& msg ); + void UnpackStats( const idBitMsg& msg ); + + int weaponShots[ MAX_WEAPONS ]; + int weaponHits[ MAX_WEAPONS ]; + + int weaponKills[ MAX_WEAPONS ]; + + int kills; + int deaths; + int suicides; + float damageRatio; + +// asalmon: added for Xenon +// ddynerman: also used on PC + int damageGiven; + int damageTaken; + + idList endGameAwards; +// asalmon: changed this to just an array of counts + int inGameAwards[IGA_NUM_AWARDS]; + + int lastUpdateTime; +}; + +/* +================ +rvStatManager + +The statistics management interface. This is the entrypoint into +statistics from the game. + +The game calls into rvStatManager at appropriate times (i.e. 'FlagCaptured') + +rvStatManager creates stat events (see StatEvent.h) with the appropriate +information and append them to the statQueue. + +The statQueue is parsed at the end of a game to tabulate statistics, give +end-game awards. + +================ +*/ +class rvStatManager { +public: + rvStatManager(); + + void Init( void ); + void Shutdown( void ); + + // generic events + void BeginGame( void ); + void EndGame( void ); + void ClientConnect( int clientNum ); + void ClientDisconnect( int clientNum ); + void WeaponFired( const idPlayer* player, int weapon, int num ); + void WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy = true ); + void Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ); + + + // ctf-specific events + void FlagCaptured( const idPlayer* player, int flagTeam ); + void FlagDropped( const idPlayer* player, const idEntity* attacker ); + void FlagReturned( const idPlayer* player ); + + + // shared events + void Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ); + + void DebugPrint( void ); + + rvPlayerStat* GetPlayerStats( void ) { return playerStats; }; + rvPlayerStat* GetPlayerStat( int clientNum ); + + void CalculateEndGameStats( void ); + + void GivePlayerCashForAward( idPlayer* player, inGameAward_t award ); + void GiveInGameAward( inGameAward_t award, int clientNum ); + + // network transmission + void SendStat( int toClient, int statClient ); + void ReceiveStat( const idBitMsg& msg ); + + void SendInGameAward( inGameAward_t award, int clientNum ); + void ReceiveInGameAward( const idBitMsg& msg ); + void CheckAwardQueue(); + + void ClearStats( void ); + + int DamageGiven( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + int DamageTaken( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + + void UpdateInGameHud( idUserInterface* statHud, bool visible ); + void UpdateEndGameHud( idUserInterface* statHud, int clientNum ); + void SetupEndGameHud( idUserInterface* statHud ); + + rvStat* GetLastClientStat( int clientNum, statType_t type, int time ); + void GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ); + rvStatTeam* GetLastTeamStat( int team, statType_t type, int time ); + rvStat* GetStat( int i ); + + void GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ); + + void SetupStatWindow( idUserInterface* statHud ); + void SelectStatWindow( int selectionIndex, int selectionTeam ); + int GetSelectedClientNum( int* selectionIndexOut = NULL, int* selectionTeamOut = NULL ); + + //asalmon: Sends all stats to all clients. For Xenon periodic update of stats. + void SendAllStats( int clientNum = -1, bool full = true ); + void ReceiveAllStats( const idBitMsg& msg ); + + void SetDamageGiven(int client, int damage) { playerStats[client].damageGiven = damage; } + void SetDamageTaken(int client, int damage) { playerStats[client].damageTaken = damage; } + + int FreeEvents( int blockNum ); + + static comboKillState_t comboKillState[ MAX_CLIENTS ]; + static int lastRailShot[ MAX_CLIENTS ]; + static int lastRailShotHits[ MAX_CLIENTS ]; + +private: + idList > statQueue; + idList > awardQueue; +// idList inGameStats; +// rvStatInGame previousInGameStats[ MAX_CLIENTS ]; +// rvStatInGame localClientInGameStats; + + // local hud support + int localInGameAwards[ IGA_NUM_AWARDS ]; + int inGameAwardHudTime; + + rvPlayerStat playerStats[ MAX_CLIENTS ]; + + bool endGameSetup; + + rvStatWindow statWindow; + + // shouchard: stat allocator to avoid lots of little news + rvStatAllocator statAllocator; + +#ifdef _XENON + int lastFullUpdate; +#endif + +#if ID_TRAFFICSTATS + int startSent; + int startPacketsSent; + int startReceived; + int startPacketsReceived; + int startTime; +#endif +}; + +ID_INLINE rvStat* rvStatManager::GetStat( int i ) { + if( i < 0 || i >= statQueue.Num() ) { + return NULL; + } + + return statQueue[ i ].First(); +} + +extern rvStatManager* statManager; +extern inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ]; +extern endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ]; + +#endif /* !__STATMANAGER_H__ */ diff --git a/src/game/mp/stats/StatWindow.cpp b/src/game/mp/stats/StatWindow.cpp new file mode 100644 index 0000000..5dae2f2 --- /dev/null +++ b/src/game/mp/stats/StatWindow.cpp @@ -0,0 +1,486 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatWindow.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" + +#include "StatWindow.h" +#include "StatManager.h" + +/* +================ +rvStatWindow::rvStatWindow() +================ +*/ +rvStatWindow::rvStatWindow() { + statHud = NULL; +} + +/* +================ +rvStatWindow::SetupStatWindow() + +Sets up a selectable window of current stats +================ +*/ +void rvStatWindow::SetupStatWindow( idUserInterface* hud, bool useSpectator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + assert( player ); + if ( !player ) { + return; + } + +// mekberg: added + idList marineScores; + idList stroggScores; + + int selectionIndex = -1; + int selectionTeam = -1; + + statHud = hud; + + if( gameLocal.IsTeamGame() ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + stroggPlayers.Clear(); + marinePlayers.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + idPlayer* player = gameLocal.mpGame.GetRankedPlayer( i ); + + if( player->team == TEAM_MARINE ) { + marinePlayers.Append( player ); + marineScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } else if ( player->team == TEAM_STROGG ) { + stroggPlayers.Append( player ); + stroggScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < marinePlayers.Num() ) { + statHud->SetStateString( va( "team_1_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( marinePlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( marinePlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + marinePlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + marineScores[ i ] ) ); + + if( useSpectator ) { + if( marinePlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } else { + if( marinePlayers[ i ] == player ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_1_names_item_%d", i ), "" ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < stroggPlayers.Num() ) { + statHud->SetStateString( va( "team_2_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( stroggPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( stroggPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + stroggPlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + stroggScores[ i ] ) ); + if( useSpectator ) { + if( stroggPlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } else { + if( stroggPlayers[ i ] == player ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_2_names_item_%d", i ), "" ); + } + } + + statHud->SetStateInt ( "num_strogg_players", stroggPlayers.Num() ); + statHud->SetStateInt ( "num_marine_players", marinePlayers.Num() ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + + players.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + players.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < players.Num() ) { + statHud->SetStateString( va( "dm_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( players[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( players[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + players[ i ]->GetUserInfo()->GetString( "ui_name"), + + gameLocal.mpGame.GetScore( players[ i ] ) ) ); + if( useSpectator ) { + if( players[ i ]->entityNumber == player->spectator ) { + selectionTeam = 0; + selectionIndex = i; + } + } else { + if( players[ i ] == player ) { + selectionTeam = 0; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "dm_names_item_%d", i ), "" ); + } + } + statHud->SetStateInt ( "num_players", players.Num() ); + } + + // spectators + spectators.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumUnrankedPlayers(); i++ ) { + spectators.Append( gameLocal.mpGame.GetUnrankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < spectators.Num() ) { + statHud->SetStateString( va( "spec_names_item_%d", i ), + va( "%s\t%s\t%s\t", ( player->IsFriend( spectators[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( spectators[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + spectators[ i ]->GetUserInfo()->GetString( "ui_name") ) ); + + if( useSpectator ) { + if( spectators[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } else { + if( spectators[ i ] == player ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "spec_names_item_%d", i ), "" ); + + } + } + + statHud->SetStateInt( "gametype", gameLocal.gameType ); + statHud->SetStateInt( "playerteam", gameLocal.GetLocalPlayer()->team ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); + + // we shouldn't ever draw a hud unless we're in-game + assert( selectionIndex >= 0 && selectionTeam >= 0 ); + + + statManager->SelectStatWindow( selectionIndex, selectionTeam ); +} + +/* +================ +rvStatWindow::ClearWindow() + +Clears the stat part of the stat window, but not the player lists boxes +================ +*/ +void rvStatWindow::ClearWindow( void ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), "" ); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, "" ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 0 ); + statHud->SetStateString( va( "eg_award%d_text", i ), "" ); + } + + // kills + statHud->SetStateString( "stat_frags", "" ); + + // deaths + statHud->SetStateString( "stat_deaths", "" ); + + // score + statHud->SetStateString( "stat_score", "" ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + + +/* +================ +rvStatWindow::SelectPlayer() + +Selects the specified player +================ +*/ +void rvStatWindow::SelectPlayer( int clientNum ) { + if( statHud == NULL ) { + return; + } + + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + ClearWindow(); + return; + } + + assert( gameLocal.GetLocalPlayer() ); + + rvPlayerStat* clientStat = statManager->GetPlayerStat( clientNum ); + + if( gameLocal.isClient && ( clientStat == NULL || ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) ) { + // get new stats + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + int weaponAccuracy = 0; + if( clientStat->weaponShots[ i ] != 0 ) { + weaponAccuracy = (int)(((float)clientStat->weaponHits[ i ] / (float)clientStat->weaponShots[ i ]) * 100.0f); + } + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", weaponAccuracy ) ); + } + + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + igAwardCount[i] = clientStat->inGameAwards[i]; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + if( i < clientStat->endGameAwards.Num() ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); +// RAVEN BEGIN +// rhummer: Localized the award strings.. + statHud->SetStateString( va( "eg_award%d_text", i ), common->GetLocalizedString( endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ) ); +// RAVEN END + } else { + statHud->SetStateInt( va( "eg_award%d", i), 0 ); + } + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) ); + + // score + int score = gameLocal.IsTeamGame() ? (gameLocal.mpGame.GetTeamScore( clientNum ) + gameLocal.mpGame.GetScore( clientNum )): gameLocal.mpGame.GetScore( clientNum ); + statHud->SetStateString( "stat_score", va( "%d", score ) ); + + if( gameLocal.GetLocalPlayer()->IsFriend( clientNum ) ) { + // remove friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200249" ) ); + } else { + // add friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200248" ) ); + } + + if( gameLocal.GetLocalPlayer()->IsPlayerMuted( clientNum ) ) { + // unmute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200251" ) ); + } else { + // mute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200250" ) ); + } + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + +/* +================ +rvStatWindow::ClientNumFromSelection() + +Parses a selection index and team into a clientNum +================ +*/ +int rvStatWindow::ClientNumFromSelection( int selectionIndex, int selectionTeam ) { + int clientNum = -1; + + if( gameLocal.IsTeamGame() ) { + if( selectionTeam == TEAM_MARINE ) { + if( selectionIndex < 0 || selectionIndex >= marinePlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = marinePlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_1_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else if( selectionTeam == TEAM_STROGG ) { + if( selectionIndex < 0 || selectionIndex >= stroggPlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = stroggPlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_2_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } else { + if( selectionTeam == TEAM_MAX ) { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= players.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = players[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "dm_names_sel_0", selectionIndex ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } + + return clientNum; +} + +/* +================ +rvStatWindow::GetSelectedClientNum() + +Queries the gui dict to figure out the currently selected selectionIndex/selectionTeam. +Uses the selectionIndex/selectionTeam to return the selected client num. +================ +*/ +int rvStatWindow::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + if( statHud == NULL ) { + return -1; + } + int selectionIndex = -1; + int selectionTeam = -1; + + // StatWindow update code should assure that only one selection of all these listDefs is is valid + // Find the valid one + if( statHud->State().GetInt( "spec_names_sel_0", "-1" ) >= 0 ) { + selectionIndex = statHud->State().GetInt( "spec_names_sel_0", "-1" ); + selectionTeam = TEAM_MAX; + } + + if ( statHud->State().GetInt( "dm_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "dm_names_sel_0", "-1" ); + selectionTeam = 0; + } + + if ( statHud->State().GetInt( "team_1_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_1_names_sel_0", "-1" ); + selectionTeam = TEAM_MARINE; + } + + if ( statHud->State().GetInt( "team_2_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_2_names_sel_0", "-1" ); + selectionTeam = TEAM_STROGG; + } + + // return the selection index & team + if( selectionIndexOut ) { + (*selectionIndexOut) = selectionIndex; + } + if( selectionTeamOut ) { + (*selectionTeamOut) = selectionTeam; + } + + return ClientNumFromSelection( selectionIndex, selectionTeam ); +} diff --git a/src/game/mp/stats/StatWindow.h b/src/game/mp/stats/StatWindow.h new file mode 100644 index 0000000..9c3d0f6 --- /dev/null +++ b/src/game/mp/stats/StatWindow.h @@ -0,0 +1,60 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatWindow.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATWINDOW_H__ +#define __STATWINDOW_H__ + +/* +=============================================================================== + +Stat selection window + +=============================================================================== +*/ + +class rvStatWindow { +public: + rvStatWindow(); + void SetupStatWindow( idUserInterface* statHud, bool useSpectator = false ); + void SelectPlayer( int clientNum ); + int ClientNumFromSelection( int selectionIndex, int selectionTeam ); + void ClearWindow( void ); + int GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ); +private: + idList stroggPlayers; + idList marinePlayers; + idList players; + idList spectators; + + idUserInterface* statHud; +}; + + +#endif diff --git a/src/game/physics/Clip.cpp b/src/game/physics/Clip.cpp new file mode 100644 index 0000000..16776e4 --- /dev/null +++ b/src/game/physics/Clip.cpp @@ -0,0 +1,2190 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_SECTOR_DEPTH 12 +#define MAX_SECTORS ((1<<(MAX_SECTOR_DEPTH+1))-1) + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +typedef struct clipSector_s { + int contents; + int dynamicContents; + struct clipLink_s * clipLinks; +} clipSector_t; +// RAVEN END + +typedef struct clipLink_s { + idClipModel * clipModel; + struct clipSector_s * sector; + struct clipLink_s * prevInSector; + struct clipLink_s * nextInSector; + struct clipLink_s * nextLink; +} clipLink_t; + +typedef struct trmCache_s { + idTraceModel trm; + int refCount; + float volume; + idVec3 centerOfMass; + idMat3 inertiaTensor; + const idMaterial * material; + idCollisionModel * collisionModel; + int hash; // only used to identify non-hashed trm's in a save. +} trmCache_t; + +idVec3 vec3_boxEpsilon( CM_BOX_EPSILON, CM_BOX_EPSILON, CM_BOX_EPSILON ); + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc clipLinkAllocator; +// RAVEN END + +typedef enum { + CPT_NONE = -1, + CPT_TRANSLATION, + CPT_CONTACTS, + CPT_CONTENTS, + + CPT_MAX_TYPES +} CLIP_PROFILE_TYPES; + +#if 0 + +static idTimer clipProfileTimer[ CPT_MAX_TYPES ]; +static CLIP_PROFILE_TYPES currentProfileType = CPT_NONE; +static int clipProfileDepth = 0; + +static void BeginClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth++; + + if ( clipProfileDepth == 1 ) { + clipProfileTimer[ type ].Start(); + currentProfileType = type; + } +} + +static void EndClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth--; + + if ( clipProfileDepth == 0 ) { + clipProfileTimer[ currentProfileType ].Stop(); + currentProfileType = CPT_NONE; + } +} + +void ClearClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + clipProfileTimer[ i ].Clear(); + } +} + +void DisplayClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + common->Printf( "%d:%d ", i, ( int )clipProfileTimer[ i ].Milliseconds() ); + } + common->Printf( "\n" ); +} +#else + +#define BeginClipProfile( type ) + +#define EndClipProfile( type ) + +#endif + + +/* +=============================================================== + + idClipModel trace model cache + +=============================================================== +*/ + +static idList traceModelCache; +static idHashIndex traceModelHash; + +/* +=============== +idClipModel::ClearTraceModelCache +=============== +*/ +void idClipModel::ClearTraceModelCache( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + collisionModelManager->FreeModel( traceModelCache[i]->collisionModel ); + traceModelCache[i]->collisionModel = NULL; + } + traceModelCache.DeleteContents( true ); + traceModelHash.Free(); +} + +/* +=============== +idClipModel::CacheCollisionModels +=============== +*/ +void idClipModel::CacheCollisionModels( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + if ( traceModelCache[i]->collisionModel == NULL ) { + traceModelCache[i]->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", i ), traceModelCache[i]->trm, traceModelCache[i]->material ); + } + } +} + +/* +=============== +idClipModel::TraceModelCacheSize +=============== +*/ +int idClipModel::TraceModelCacheSize( void ) { + return traceModelCache.Num() * sizeof( idTraceModel ); +} + +/* +=============== +idClipModel::AllocTraceModel +=============== +*/ +int idClipModel::AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + int i, hashKey, traceModelIndex; + trmCache_t *entry; + + if ( notHashed ) { + hashKey = 0xffffffff; + } else { + hashKey = GetTraceModelHashKey( trm ); + + for ( i = traceModelHash.First( hashKey ); i >= 0; i = traceModelHash.Next( i ) ) { + if ( traceModelCache[i]->trm == trm ) { + traceModelCache[i]->refCount++; + return i; + } + } + } + + + entry = new trmCache_t; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->material = material; + entry->hash = hashKey; + traceModelIndex = traceModelCache.Append( entry ); + + if ( !notHashed ) { + traceModelHash.Add( hashKey, traceModelIndex ); + } + + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", traceModelIndex ), trm, material ); + + return traceModelIndex; +} + +/* +=============== +idClipModel::Replace +=============== +*/ +void idClipModel::ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed ) { + common->Error( "ReplaceTraceModel was misused. Replace can only be used on non-hashed models right now.\n" ); + return; + } + + trmCache_t *entry = traceModelCache[ index ]; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->hash = 0xffffffff; + entry->material = material; + + if(entry->collisionModel) + { + collisionModelManager->FreeModel( entry->collisionModel ); + } + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", index ), trm, material ); +} + +/* +=============== +idClipModel::FreeTraceModel +=============== +*/ +void idClipModel::FreeTraceModel( int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model" ); + return; + } + traceModelCache[traceModelIndex]->refCount--; +} + +/* +=============== +idClipModel::CopyTraceModel +=============== +*/ +int idClipModel::CopyTraceModel( const int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::CopyTraceModel: tried to copy an uncached trace model" ); + return -1; + } + traceModelCache[traceModelIndex]->refCount++; + return traceModelIndex; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idTraceModel *idClipModel::GetCachedTraceModel( int traceModelIndex ) { + return &traceModelCache[traceModelIndex]->trm; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idCollisionModel *idClipModel::GetCachedCollisionModel( int traceModelIndex ) { + return traceModelCache[traceModelIndex]->collisionModel; +} + +/* +=============== +idClipModel::GetTraceModelHashKey +=============== +*/ +int idClipModel::GetTraceModelHashKey( const idTraceModel &trm ) { + const idVec3 &v = trm.bounds[0]; + return ( trm.type << 8 ) ^ ( trm.numVerts << 4 ) ^ ( trm.numEdges << 2 ) ^ ( trm.numPolys << 0 ) ^ idMath::FloatHash( v.ToFloatPtr(), v.GetDimension() ); +} + +/* +=============== +idClipModel::SaveTraceModels +=============== +*/ +void idClipModel::SaveTraceModels( idSaveGame *savefile ) { + int i; + + savefile->WriteInt( traceModelCache.Num() ); + for ( i = 0; i < traceModelCache.Num(); i++ ) { + trmCache_t *entry = traceModelCache[i]; + + savefile->Write( &entry->trm, sizeof( entry->trm ) ); + savefile->WriteFloat( entry->volume ); + savefile->WriteVec3( entry->centerOfMass ); + savefile->WriteMat3( entry->inertiaTensor ); + savefile->WriteMaterial( entry->material ); + savefile->WriteInt( entry->hash ); + } +} + +/* +=============== +idClipModel::RestoreTraceModels +=============== +*/ +void idClipModel::RestoreTraceModels( idRestoreGame *savefile ) { + int i, num; + + ClearTraceModelCache(); + + savefile->ReadInt( num ); + traceModelCache.SetNum( num ); + for ( i = 0; i < num; i++ ) { + trmCache_t *entry = new trmCache_t; + + savefile->Read( &entry->trm, sizeof( entry->trm ) ); + savefile->ReadFloat( entry->volume ); + savefile->ReadVec3( entry->centerOfMass ); + savefile->ReadMat3( entry->inertiaTensor ); + savefile->ReadMaterial( entry->material ); + savefile->ReadInt( entry->hash ); + + entry->refCount = 0; + entry->collisionModel = NULL; + + traceModelCache[i] = entry; + if ( entry->hash != 0xffffffff ) { + traceModelHash.Add( GetTraceModelHashKey( entry->trm ), i ); + } + } + + CacheCollisionModels(); +} + + +/* +=============================================================== + + idClipModel + +=============================================================== +*/ + +/* +================ +idClipModel::FreeModel +================ +*/ +void idClipModel::FreeModel( void ) { + + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + traceModelIndex = -1; + } + + if ( collisionModel != NULL ) { + collisionModelManager->FreeModel( collisionModel ); + collisionModel = NULL; + } + + renderModelHandle = -1; +} + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +/* +================ +idClipModel::UpdateDynamicContents +================ +*/ +void idClipModel::UpdateDynamicContents( void ) { + idClip::UpdateDynamicContents( this ); +} +// RAVEN END + +/* +================ +idClipModel::LoadModel +================ +*/ +bool idClipModel::LoadModel( const char *name ) { + FreeModel(); + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), name ); + if ( collisionModel != NULL ) { + collisionModel->GetBounds( bounds ); + collisionModel->GetContents( contents ); + return true; + } else { + bounds.Zero(); + return false; + } +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed || traceModelIndex == -1 ) { + FreeModel(); + traceModelIndex = AllocTraceModel( trm, material, notHashed ); + } else { + ReplaceTraceModel( traceModelIndex, trm, material, notHashed ); + } + + bounds = trm.bounds; +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const int renderModelHandle ) { + FreeModel(); + this->renderModelHandle = renderModelHandle; + if ( renderModelHandle != -1 ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + bounds = renderEntity->bounds; + } + } +} + +/* +================ +idClipModel::Init +================ +*/ +void idClipModel::Init( void ) { + enabled = true; + entity = NULL; + id = 0; + owner = NULL; + origin.Zero(); + axis.Identity(); + bounds.Zero(); + absBounds.Zero(); + contents = CONTENTS_BODY; + collisionModel = NULL; + renderModelHandle = -1; + traceModelIndex = -1; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( void ) { + Init(); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const char *name ) { + Init(); + LoadModel( name ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idTraceModel &trm, const idMaterial *material ) { + Init(); + LoadModel( trm, material ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const int renderModelHandle ) { + Init(); + contents = CONTENTS_RENDERMODEL; + LoadModel( renderModelHandle ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idClipModel *model ) { + enabled = model->enabled; + entity = model->entity; + id = model->id; + owner = model->owner; + origin = model->origin; + axis = model->axis; + bounds = model->bounds; + absBounds = model->absBounds; + contents = model->contents; + collisionModel = NULL; + if ( model->collisionModel != NULL ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), model->collisionModel->GetName() ); + } + traceModelIndex = -1; + if ( model->traceModelIndex != -1 ) { + traceModelIndex = CopyTraceModel( model->traceModelIndex ); + } + renderModelHandle = model->renderModelHandle; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::~idClipModel +================ +*/ +idClipModel::~idClipModel( void ) { + // make sure the clip model is no longer linked + Unlink(); + FreeModel(); +} + +/* +================ +idClipModel::Save +================ +*/ +void idClipModel::Save( idSaveGame *savefile ) const { + savefile->WriteBool( enabled ); + savefile->WriteObject( entity ); + savefile->WriteInt( id ); + savefile->WriteObject( owner ); + savefile->WriteVec3( origin ); + savefile->WriteMat3( axis ); + savefile->WriteBounds( bounds ); + savefile->WriteBounds( absBounds ); + savefile->WriteInt( contents ); + if ( collisionModel != NULL ) { + savefile->WriteString( collisionModel->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteInt( traceModelIndex ); + savefile->WriteInt( renderModelHandle ); + savefile->WriteBool( clipLinks != NULL ); + savefile->WriteInt( touchCount ); + + savefile->WriteBool ( checked ); // cnicholson: Added unsaved var +} + +/* +================ +idClipModel::Restore +================ +*/ +void idClipModel::Restore( idRestoreGame *savefile ) { + idStr collisionModelName; + bool linked; + + savefile->ReadBool( enabled ); + savefile->ReadObject( reinterpret_cast( entity ) ); + savefile->ReadInt( id ); + savefile->ReadObject( reinterpret_cast( owner ) ); + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); + savefile->ReadBounds( bounds ); + savefile->ReadBounds( absBounds ); + savefile->ReadInt( contents ); + savefile->ReadString( collisionModelName ); + if ( collisionModelName.Length() ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), collisionModelName ); + } else { + collisionModel = NULL; + } + savefile->ReadInt( traceModelIndex ); + if ( traceModelIndex >= 0 ) { + traceModelCache[traceModelIndex]->refCount++; + } + savefile->ReadInt( renderModelHandle ); + savefile->ReadBool( linked ); + savefile->ReadInt( touchCount ); + + savefile->ReadBool ( checked ); // cnicholson: Added unrestored var + + // the render model will be set when the clip model is linked + renderModelHandle = -1; + clipLinks = NULL; + touchCount = -1; + + if ( linked ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Link( entity, id, origin, axis, renderModelHandle ); +// RAVEN END + } +} + +/* +================ +idClipModel::SetPosition +================ +*/ +void idClipModel::SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ) { + if ( clipLinks ) { + Unlink(); // unlink from old position + } + origin = newOrigin; + axis = newAxis; +} + +/* +================ +idClipModel::GetCollisionModel +================ +*/ +idCollisionModel * idClipModel::GetCollisionModel( void ) const { + assert( renderModelHandle == -1 ); + if ( collisionModel != NULL ) { + return collisionModel; + } else if ( traceModelIndex != -1 ) { + return GetCachedCollisionModel( traceModelIndex ); + } else { + // this happens in multiplayer on the combat models + if ( entity ) { + gameLocal.Warning( "idClipModel::GetCollisionModel: clip model %d on '%s' (%x) is not a collision or trace model", id, entity->name.c_str(), entity->entityNumber ); + } + return 0; + } +} + +/* +================ +idClipModel::GetMassProperties +================ +*/ +void idClipModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + if ( traceModelIndex == -1 ) { + gameLocal.Error( "idClipModel::GetMassProperties: clip model %d on '%s' is not a trace model\n", id, entity->name.c_str() ); + } + + trmCache_t *entry = traceModelCache[traceModelIndex]; + mass = entry->volume * density; + centerOfMass = entry->centerOfMass; + inertiaTensor = density * entry->inertiaTensor; +} + +/* +=============== +idClipModel::Unlink +=============== +*/ +void idClipModel::Unlink( void ) { + clipLink_t *link; + + for ( link = clipLinks; link; link = clipLinks ) { + clipLinks = link->nextLink; + if ( link->prevInSector ) { + link->prevInSector->nextInSector = link->nextInSector; + } else { + link->sector->clipLinks = link->nextInSector; + } + if ( link->nextInSector ) { + link->nextInSector->prevInSector = link->prevInSector; + } +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idClip::UpdateDynamicContents( link->sector ); +// RAVEN END + + clipLinkAllocator.Free( link ); + } +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( void ) { + + assert( idClipModel::entity ); + if ( !idClipModel::entity ) { + return; + } + + idClip* clp = gameLocal.GetEntityClipWorld( idClipModel::entity ); + + if ( clipLinks ) { + Unlink(); // unlink from old position + } + + if ( bounds.IsCleared() ) { + return; + } + + // set the abs box + if ( axis.IsRotated() ) { + // expand for rotation + absBounds.FromTransformedBounds( bounds, origin, axis ); + } else { + // normal + absBounds[0] = bounds[0] + origin; + absBounds[1] = bounds[1] + origin; + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + absBounds[0] -= vec3_boxEpsilon; + absBounds[1] += vec3_boxEpsilon; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + clp->CoordsForBounds( coords, absBounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clp->clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + sector->dynamicContents |= GetContents(); + + clipLink_t* link = clipLinkAllocator.Alloc(); + link->clipModel = this; + link->sector = sector; + link->nextInSector = sector->clipLinks; + link->prevInSector = NULL; + if ( sector->clipLinks ) { + sector->clipLinks->prevInSector = link; + } + sector->clipLinks = link; + link->nextLink = clipLinks; + clipLinks = link; + } + } +// RAVEN END +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle ) { + this->entity = ent; + this->id = newId; + this->origin = newOrigin; + this->axis = newAxis; + if ( renderModelHandle != -1 ) { + this->renderModelHandle = renderModelHandle; + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + this->bounds = renderEntity->bounds; + } + } + this->Link(); +} +// RAVEN END + + +/* +=============================================================== + + idClip + +=============================================================== +*/ + +// RAVEN BEGIN +// ddynerman: change to static +idClipModel idClip::defaultClipModel; + +idClipModel *idClip::DefaultClipModel( void ) { + // initialize a default clip model + if( defaultClipModel.traceModelIndex == -1 ) { + defaultClipModel.LoadModel( idTraceModel( idBounds( idVec3( 0, 0, 0 ) ).Expand( 8 ) ), NULL ); + } + + return &defaultClipModel; +} + +void idClip::FreeDefaultClipModel( void ) { + if ( defaultClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( defaultClipModel.traceModelIndex ); + defaultClipModel.traceModelIndex = -1; + } +} +// RAVEN END + +/* +=============== +idClip::idClip +=============== +*/ +idClip::idClip( void ) { + clipSectors = NULL; + world = NULL; + worldBounds.Zero(); + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::CreateClipSectors_r + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +clipSector_t *idClip::CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ) { +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if( clipSectors ) { + delete[] clipSectors; + clipSectors = NULL; + } + nodeOffsetVisual = bounds[ 0 ]; + + int i; + for( i = 0; i < 3; i++ ) { +//jshepard: this crashes too often +#ifdef _DEBUG + if( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ) { + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + } else { + gameLocal.Error("zero size bounds while creating clipsectors"); + nodeScale[ i ] = depth; + } + if( nodeScale[ i ] ) { + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + } else { + gameLocal.Error("zero size nodeScale while creating clipsectors"); + nodeOffset[ i] = nodeOffset[ i] + 0.5f; + } +#else + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + +#endif + } + + clipSectors = new clipSector_t[ Square( depth ) ]; + memset( clipSectors, 0, Square( depth ) * sizeof( clipSector_t ) ); + return clipSectors; +// RAVEN END +} + +/* +=============== +idClip::Init +=============== +*/ +void idClip::Init( void ) { + idVec3 size, maxSector = vec3_origin; + + // get world map bounds + world = collisionModelManager->LoadModel( gameLocal.GetMapName(), WORLD_MODEL_NAME ); + world->GetBounds( worldBounds ); + + // create world sectors +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// ddynerman: SD's clip sector code + CreateClipSectors_r( CLIPSECTOR_WIDTH, worldBounds, maxSector ); + GetClipSectorsStaticContents(); +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + size = worldBounds[1] - worldBounds[0]; + gameLocal.Printf( "map bounds are (%1.1f, %1.1f, %1.1f)\n", size[0], size[1], size[2] ); + gameLocal.Printf( "max clip sector is (%1.1f, %1.1f, %1.1f)\n", maxSector[0], maxSector[1], maxSector[2] ); + + // set counters to zero + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::Shutdown +=============== +*/ +void idClip::Shutdown( void ) { + delete[] clipSectors; + clipSectors = NULL; + + // free the trace model used for the temporaryClipModel + if ( temporaryClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( temporaryClipModel.traceModelIndex ); + temporaryClipModel.traceModelIndex = -1; + } + + clipLinkAllocator.Shutdown(); +} + +/* +==================== +idClip::ClipModelsTouchingBounds_r +==================== +*/ +typedef struct listParms_s { + idBounds bounds; + int contentMask; + idClipModel ** list; + int count; + int maxCount; +} listParms_t; + + +/* +================ +idClip::ClipModelsTouchingBounds +================ +*/ +int idClip::ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + listParms_t parms; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int clipCount = 0; + static idClipModel* clipModels[ MAX_GENTITIES ]; + + assert( maxCount <= MAX_GENTITIES ); + if( maxCount > MAX_GENTITIES ) { + maxCount = MAX_GENTITIES; + } +// RAVEN END + + if ( bounds[0][0] > bounds[1][0] || + bounds[0][1] > bounds[1][1] || + bounds[0][2] > bounds[1][2] ) { + // we should not go through the tree for degenerate or backwards bounds + assert( false ); + return 0; + } + + parms.bounds[0] = bounds[0] - vec3_boxEpsilon; + parms.bounds[1] = bounds[1] + vec3_boxEpsilon; + parms.contentMask = contentMask; + parms.list = clipModelList; + parms.count = 0; + parms.maxCount = maxCount; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + CoordsForBounds( coords, parms.bounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + if( !( sector->dynamicContents & contentMask ) ) { + continue; + } + + for ( clipLink_t* link = sector->clipLinks; link && clipCount < MAX_GENTITIES; link = link->nextInSector ) { + idClipModel* model = link->clipModel; + + if( model->checked || !model->enabled || !( model->GetContents() & contentMask ) ) { + continue; + } + + model->checked = true; + clipModels[ clipCount++ ] = model; + } + } + } + + for( x = 0; x < clipCount; x++ ) { + clipModels[ x ]->checked = false; + } + + for( x = 0; x < clipCount; x++ ) { + idClipModel* model = clipModels[ x ]; + + // if the bounds really do overlap + if ( model->absBounds[0].x > parms.bounds[1].x || + model->absBounds[1].x < parms.bounds[0].x || + model->absBounds[0].y > parms.bounds[1].y || + model->absBounds[1].y < parms.bounds[0].y || + model->absBounds[0].z > parms.bounds[1].z || + model->absBounds[1].z < parms.bounds[0].z ) { + continue; + } + + if( parms.count >= parms.maxCount ) { +// gameLocal.Warning( "idClip::ClipModelsTouchingBounds Max Count Hit\n" ); + break; + } + + parms.list[ parms.count++ ] = model; + } +// RAVEN END + + return parms.count; +} + +/* +================ +idClip::EntitiesTouchingBounds +================ +*/ +int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, entCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + entCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < entCount; j++ ) { + if ( entityList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= entCount ) { + if ( entCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return entCount; + } + entityList[entCount] = clipModelList[i]->entity; + entCount++; + } + } + + return entCount; +} + +// RAVEN BEGIN +// ddynerman: MP helper function +/* +================ +idClip::PlayersTouchingBounds +================ +*/ +int idClip::PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **playerList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, playerCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + playerCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < playerCount; j++ ) { + if ( playerList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= playerCount ) { + if ( playerCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return playerCount; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( clipModelList[i]->entity->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerList[playerCount] = static_cast(clipModelList[i]->entity); + playerCount++; + } + } + } + + return playerCount; +} +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + +/* +==================== +idClip::DrawAreaClipSectors +==================== +*/ +void idClip::DrawAreaClipSectors( float range ) const { + idClipModel* clipModels[ MAX_GENTITIES ]; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idBounds bounds; + bounds[0] = player->GetPhysics()->GetOrigin() - idVec3( range, range, range ); + bounds[1] = player->GetPhysics()->GetOrigin() + idVec3( range, range, range ); + + int count = ClipModelsTouchingBounds( bounds, MASK_ALL, clipModels, MAX_GENTITIES ); + + int i; + for ( i = 0; i < count; i++ ) { + idEntity* owner = clipModels[ i ]->GetEntity(); + + const idVec3& org = clipModels[ i ]->GetOrigin(); + const idBounds& bounds = clipModels[ i ]->GetBounds(); + + gameRenderWorld->DebugBounds( colorCyan, bounds, org ); + gameRenderWorld->DrawText( owner->GetClassname(), org, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + } +} + +/* +==================== +idClip::DrawClipSectors +==================== +*/ +void idClip::DrawClipSectors( void ) const { + idBounds bounds; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + int i; + idVec3 inverseNodeScale; + for( i = 0; i < 3; i++ ) { + inverseNodeScale[ i ] = 1 / nodeScale[ i ]; + } + + const char* filter = g_showClipSectorFilter.GetString(); + idTypeInfo* type = idClass::GetClass( filter ); + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { +// idWinding w( 4 ); + + bounds[ 0 ].x = ( inverseNodeScale.x * x ) + nodeOffsetVisual.x + 1; + bounds[ 0 ].y = ( inverseNodeScale.y * y ) + nodeOffsetVisual.y + 1; + bounds[ 0 ].z = player->GetPhysics()->GetBounds()[0].z; + + bounds[ 1 ].x = ( inverseNodeScale.x * ( x + 1 ) ) + nodeOffsetVisual.x - 1; + bounds[ 1 ].y = ( inverseNodeScale.y * ( y + 1 ) ) + nodeOffsetVisual.y - 1; + bounds[ 1 ].z = player->GetPhysics()->GetBounds()[1].z; + + idVec3 point; + point.x = ( bounds[ 0 ].x + bounds[ 1 ].x ) * 0.5f; + point.y = ( bounds[ 0 ].y + bounds[ 1 ].y ) * 0.5f; + point.z = 0.f; + +/* point.x = bounds[ 0 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point ); + + point.x = bounds[ 0 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point );*/ + + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + clipLink_t* link = sector->clipLinks; + while ( link ) { + if ( type && !link->clipModel->GetEntity()->IsType( *type ) ) { + link = link->nextInSector; + } else { + break; + } + } + + if( link ) { + + gameRenderWorld->DrawText( link->clipModel->GetEntity()->GetClassname(), point, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DebugBounds( colorMagenta, bounds ); + gameRenderWorld->DebugBounds( colorYellow, link->clipModel->GetBounds(), link->clipModel->GetOrigin() ); + + } else { + +// gameRenderWorld->DrawText( sector->clipLinks->clipModel->GetEntity()->GetClassname(), point, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + + } + } + } +} + +/* +==================== +idClip::GetClipSectorsStaticContents +==================== +*/ +void idClip::GetClipSectorsStaticContents( void ) { + idBounds bounds; + + bounds[ 0 ].x = 0; + bounds[ 0 ].y = 0; + bounds[ 0 ].z = worldBounds[ 0 ].z; + + bounds[ 1 ].x = 1 / nodeScale.x; + bounds[ 1 ].y = 1 / nodeScale.y; + bounds[ 1 ].z = worldBounds[ 1 ].z; + + idTraceModel* trm = new idTraceModel( bounds ); + + idVec3 org; + org.z = 0; + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { + org.x = ( x / nodeScale.x ) + nodeOffset.x; + org.y = ( y / nodeScale.y ) + nodeOffset.y; + + int contents = collisionModelManager->Contents( org, trm, mat3_identity, -1, world, vec3_origin, mat3_default ); + clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ].contents = contents; + } + } + // mwhitlock: Fix leak in SD's code. + delete trm; +} + +// RAVEN END + +/* +==================== +idClip::GetTraceClipModels + + an ent will be excluded from testing if: + cm->entity == passEntity ( don't clip against the pass entity ) + cm->entity == passOwner ( missiles don't clip with owner ) + cm->owner == passEntity ( don't interact with your own missiles ) + cm->owner == passOwner ( don't interact with other missiles from same owner ) +==================== +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +int idClip::GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 ) const { +// RAVEN END + int i, num; + idClipModel *cm; + idEntity *passOwner; + + num = ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + + if ( !passEntity ) { + return num; + } + + if ( passEntity->GetPhysics()->GetNumClipModels() > 0 ) { + passOwner = passEntity->GetPhysics()->GetClipModel()->GetOwner(); + } else { + passOwner = NULL; + } + + for ( i = 0; i < num; i++ ) { + + cm = clipModelList[i]; + + // check if we should ignore this entity + if ( cm->entity == passEntity ) { + clipModelList[i] = NULL; // don't clip against the pass entity + } +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + else if ( cm->entity == passEntity2 ){ + clipModelList[i] = NULL; +// RAVEN END + } else if ( cm->entity == passOwner ) { + clipModelList[i] = NULL; // missiles don't clip with their owner + } else if ( cm->owner ) { + if ( cm->owner == passEntity ) { + clipModelList[i] = NULL; // don't clip against own missiles + } else if ( cm->owner == passOwner ) { + clipModelList[i] = NULL; // don't clip against other missiles from same owner + } + } + } + + return num; +} + +/* +============ +idClip::TraceRenderModel +============ +*/ +void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const { + trace.fraction = 1.0f; + + // if the trace is passing through the bounds + if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) { + modelTrace_t modelTrace; + + // test with exact render model and modify trace_t structure accordingly + if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) { + trace.fraction = modelTrace.fraction; + trace.endAxis = axis; + trace.endpos = modelTrace.point; + trace.c.normal = modelTrace.normal; + trace.c.dist = modelTrace.point * modelTrace.normal; + trace.c.point = modelTrace.point; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.modelFeature = 0; + trace.c.trmFeature = 0; + trace.c.contents = modelTrace.material->GetContentFlags(); + trace.c.material = modelTrace.material; + +// RAVEN BEGIN +// jscott: for material types + trace.c.materialType = modelTrace.materialType; +// RAVEN END + + // NOTE: trace.c.id will be the joint number + touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber ); + } + } +} + +/* +============ +idClip::TraceModelForClipModel +============ +*/ +const idTraceModel *idClip::TraceModelForClipModel( const idClipModel *mdl ) const { + if ( !mdl ) { + return NULL; + } else { + if ( !mdl->IsTraceModel() ) { + if ( mdl->GetEntity() ) { + gameLocal.Error( "TraceModelForClipModel: clip model %d on '%s' is not a trace model\n", mdl->GetId(), mdl->GetEntity()->name.c_str() ); + } else { + gameLocal.Error( "TraceModelForClipModel: clip model %d is not a trace model\n", mdl->GetId() ); + } + } + return idClipModel::GetCachedTraceModel( mdl->traceModelIndex ); + } +} + +/* +============ +idClip::TestHugeTranslation +============ +*/ +ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) { + if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { + assert( 0 ); + + results.fraction = 0.0f; + results.endpos = start; + results.endAxis = trmAxis; + memset( &results.c, 0, sizeof( results.c ) ); + results.c.point = start; + results.c.entityNum = ENTITYNUM_WORLD; + + if ( mdl->GetEntity() ) { + gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); + } else { + gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() ); + } + return true; + } + return false; +} + +/* +============ +idClip::TranslationEntities +============ +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +void idClip::TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return; + } + + trm = TraceModelForClipModel( mdl ); + + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + + if ( !trm ) { + traceBounds.FromPointTranslation( start, end - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, end - start ); + radius = trm->bounds.GetRadius(); + } + +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } +} + +/* +============ +idClip::Translation +============ +*/ +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation +bool idClip::Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + BeginClipProfile( CPT_TRANSLATION ); + + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces + if ( g_showCollisionTraces.GetInteger() >= 2 && !g_stopTime.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, start, end, 1000 ); + } +// RAVEN END + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + EndClipProfile( CPT_TRANSLATION ); + return true; + } + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + EndClipProfile( CPT_TRANSLATION ); + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + } + + if ( !trm ) { + traceBounds.FromPointTranslation( start, results.endpos - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, results.endpos - start ); + radius = trm->bounds.GetRadius(); + } + +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + +// RAVEN BEGIN +// jscott: for material types + results.c.materialType = trace.c.materialType; + +// mekberg: copy contents + if ( touch->IsTraceModel( ) ) { + results.c.contents = touch->GetContents( ); + } +// RAVEN END + + if ( results.fraction == 0.0f ) { + break; + } + } + } + + EndClipProfile( CPT_TRANSLATION ); + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Rotation +============ +*/ +bool idClip::Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + trace_t trace; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( !trm ) { + traceBounds.FromPointRotation( start, rotation ); + } else { + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, start, rotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Motion +============ +*/ +bool idClip::Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idVec3 dir, endPosition; + idBounds traceBounds; + float radius; + trace_t translationalTrace, rotationalTrace, trace; + idRotation endRotation; + const idTraceModel *trm; + + assert( rotation.GetOrigin() == start ); + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return true; + } + + if ( mdl != NULL && rotation.GetAngle() != 0.0f && rotation.GetVec() != vec3_origin ) { + // if no translation + if ( start == end ) { + // pure rotation + return Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + } else if ( start != end ) { + // pure translation + return Translation( results, start, end, mdl, trmAxis, contentMask, passEntity ); + } else { + // no motion + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis; + return false; + } + + trm = TraceModelForClipModel( mdl ); + + radius = trm->bounds.GetRadius(); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // translational collision with world + idClip::numTranslations++; + collisionModelManager->Translation( &translationalTrace, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + translationalTrace.c.entityNum = translationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &translationalTrace, 0, sizeof( translationalTrace ) ); + translationalTrace.fraction = 1.0f; + translationalTrace.endpos = end; + translationalTrace.endAxis = trmAxis; + } + + if ( translationalTrace.fraction != 0.0f ) { + + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + dir = translationalTrace.endpos - start; + for ( i = 0; i < 3; i++ ) { + if ( dir[i] < 0.0f ) { + traceBounds[0][i] += dir[i]; + } + else { + traceBounds[1][i] += dir[i]; + } + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < translationalTrace.fraction ) { + translationalTrace = trace; + translationalTrace.c.entityNum = touch->entity->entityNumber; + translationalTrace.c.id = touch->id; + if ( translationalTrace.fraction == 0.0f ) { + break; + } + } + } + } else { + num = -1; + } + + endPosition = translationalTrace.endpos; + endRotation = rotation; + endRotation.SetOrigin( endPosition ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // rotational collision with world + idClip::numRotations++; + collisionModelManager->Rotation( &rotationalTrace, endPosition, endRotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + rotationalTrace.c.entityNum = rotationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &rotationalTrace, 0, sizeof( rotationalTrace ) ); + rotationalTrace.fraction = 1.0f; + rotationalTrace.endpos = endPosition; + rotationalTrace.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( rotationalTrace.fraction != 0.0f ) { + + if ( num == -1 ) { + traceBounds.FromBoundsRotation( trm->bounds, endPosition, trmAxis, endRotation ); + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + } + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision detection with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, endPosition, endRotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < rotationalTrace.fraction ) { + rotationalTrace = trace; + rotationalTrace.c.entityNum = touch->entity->entityNumber; + rotationalTrace.c.id = touch->id; + if ( rotationalTrace.fraction == 0.0f ) { + break; + } + } + } + } + + if ( rotationalTrace.fraction < 1.0f ) { + results = rotationalTrace; + } else { + results = translationalTrace; + results.endAxis = rotationalTrace.endAxis; + } + + results.fraction = Max( translationalTrace.fraction, rotationalTrace.fraction ); + + return ( translationalTrace.fraction < 1.0f || rotationalTrace.fraction < 1.0f ); +} + +/* +============ +idClip::Contacts +============ +*/ +int idClip::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + BeginClipProfile( CPT_CONTACTS ); + + int i, j, num, n, numContacts; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContacts++; + numContacts = collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + numContacts = 0; + } + + for ( i = 0; i < numContacts; i++ ) { + contacts[i].entityNum = ENTITYNUM_WORLD; + contacts[i].id = 0; + } + + if ( numContacts >= maxContacts ) { + EndClipProfile( CPT_CONTACTS ); + return numContacts; + } + + if ( !trm ) { + traceBounds = idBounds( start ).Expand( depth ); + } else { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + traceBounds.ExpandSelf( depth ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contacts with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numContacts++; + n = collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts, + start, dir, depth, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + for ( j = 0; j < n; j++ ) { + contacts[numContacts].entityNum = touch->entity->entityNumber; + contacts[numContacts].id = touch->id; + numContacts++; + } + + if ( numContacts >= maxContacts ) { + break; + } + } + + EndClipProfile( CPT_CONTACTS ); + + return numContacts; +} + +/* +============ +idClip::Contents +============ +*/ +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. +int idClip::Contents( const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { +// RAVEN END + BeginClipProfile( CPT_CONTENTS ); + + int i, num, contents; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContents++; + contents = collisionModelManager->Contents( start, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + contents = 0; + } + + if ( !trm ) { + traceBounds[0] = start; + traceBounds[1] = start; + } else if ( trmAxis.IsRotated() ) { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + } else { + traceBounds[0] = trm->bounds[0] + start; + traceBounds[1] = trm->bounds[1] + start; + } + + num = GetTraceClipModels( traceBounds, -1, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contents test with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + // if the entity does not have any contents we are looking for + if ( ( touch->contents & contentMask ) == 0 ) { + continue; + } + + // if the entity has no new contents flags + if ( ( touch->contents & contents ) == touch->contents ) { + continue; + } + + idClip::numContents++; + if ( collisionModelManager->Contents( start, trm, trmAxis, contentMask, touch->GetCollisionModel(), touch->origin, touch->axis ) ) { + contents |= ( touch->contents & contentMask ); + } + +// RAVEN BEGIN + // Only sends back one entity for now. Ahh well, if this is a problem, have it send back a list... + if ( touchedEntity ) + { + *touchedEntity = touch->GetEntity(); + } +// RAVEN END + } + + EndClipProfile( CPT_CONTENTS ); + + return contents; +} + +/* +============ +idClip::TranslationModel +============ +*/ +void idClip::TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::RotationModel +============ +*/ +void idClip::RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContactsModel +============ +*/ +int idClip::ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContacts++; + return collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContentsModel +============ +*/ +int idClip::ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContents++; + return collisionModelManager->Contents( start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::GetModelContactFeature +============ +*/ +bool idClip::GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + int i; + idCollisionModel *model; + idVec3 start, end; + + model = NULL; + winding.Clear(); + + if ( clipModel == NULL ) { + model = NULL; + } else { + if ( clipModel->renderModelHandle != -1 ) { + winding += contact.point; + return true; + } else if ( clipModel->traceModelIndex != -1 ) { + model = idClipModel::GetCachedCollisionModel( clipModel->traceModelIndex ); + } else { + model = clipModel->collisionModel; + } + } + + // if contact with a collision model + if ( model != NULL ) { + switch( contact.type ) { + case CONTACT_EDGE: { + // the model contact feature is a collision model edge + model->GetEdge( contact.modelFeature, start, end ); + winding += start; + winding += end; + break; + } + case CONTACT_MODELVERTEX: { + // the model contact feature is a collision model vertex + model->GetVertex( contact.modelFeature, start ); + winding += start; + break; + } + case CONTACT_TRMVERTEX: { + // the model contact feature is a collision model polygon + model->GetPolygon( contact.modelFeature, winding ); + break; + } + } + } + + // transform the winding to world space + if ( clipModel ) { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + winding[i].ToVec3() *= clipModel->axis; + winding[i].ToVec3() += clipModel->origin; + } + } + + return true; +} + +/* +============ +idClip::PrintStatistics +============ +*/ +void idClip::PrintStatistics( void ) { +// RAVEN BEGIN +// rjohnson: added trace model cache size + gameLocal.Printf( "t=%-3d, r=%-3d, m=%-3d, render=%-3d, contents=%-3d, contacts=%-3d, cache=%d\n", + numTranslations, numRotations, numMotions, numRenderModelTraces, numContents, numContacts, traceModelCache.Num() * sizeof( idTraceModel ) ); +// RAVEN END +} + +/* +============ +idClip::DrawClipModels +============ +*/ +void idClip::DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type ) { + int i, num; + idBounds bounds; + idClipModel *clipModelList[MAX_GENTITIES]; + idClipModel *clipModel; + + bounds = idBounds( eye ).Expand( radius ); + + num = idClip::ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < num; i++ ) { + clipModel = clipModelList[i]; + if ( clipModel->GetEntity() == passEntity ) { + continue; + } + if ( type && !clipModel->GetEntity()->IsType( *type ) ) { + continue; + } + if ( clipModel->renderModelHandle != -1 ) { + gameRenderWorld->DebugBounds( colorCyan, clipModel->GetAbsBounds() ); + continue; + } + + idCollisionModel *model = clipModel->GetCollisionModel(); + if ( model != NULL ) { + collisionModelManager->DrawModel( model, clipModel->GetOrigin(), clipModel->GetAxis(), eye, mat3_identity, radius ); + } + } +} + +/* +============ +idClip::DrawModelContactFeature +============ +*/ +bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const { + int i; + idMat3 axis; + idFixedWinding winding; + + if ( !GetModelContactFeature( contact, clipModel, winding ) ) { + return false; + } + + axis = contact.normal.ToMat3(); + + if ( winding.GetNumPoints() == 1 ) { + gameRenderWorld->DebugLine( colorCyan, winding[0].ToVec3(), winding[0].ToVec3() + 2.0f * axis[0], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[1], winding[0].ToVec3() + 1.0f * axis[1], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[2], winding[0].ToVec3() + 1.0f * axis[2], lifetime ); + } else { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + gameRenderWorld->DebugLine( colorCyan, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3(), lifetime ); + } + } + + axis[0] = -axis[0]; + axis[2] = -axis[2]; + gameRenderWorld->DrawText( contact.material->GetName(), winding.GetCenter() - 4.0f * axis[2], 0.1f, colorWhite, axis, 1, 5000 ); + + return true; +} + +// RAVEN BEGIN +// rjohnson: added debug hud support + +void idClip::DebugHudStatistics( void ) +{ + gameDebug.SetInt( "physics_translations", numTranslations ); + gameDebug.SetInt( "physics_rotations", numRotations ); + gameDebug.SetInt( "physics_motions", numMotions ); + gameDebug.SetInt( "physics_render_model_traces", numRenderModelTraces ); + gameDebug.SetInt( "physics_contents", numContents ); + gameDebug.SetInt( "physics_contacts", numContacts ); +} + +void idClip::ClearStatistics( void ) +{ + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( clipSector_t* sector ) { + sector->dynamicContents = 0; + + clipLink_t* link; + for( link = sector->clipLinks; link; link = link->nextInSector ) { + sector->dynamicContents |= link->clipModel->GetContents(); + } +} + +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( idClipModel* clipModel ) { + clipLink_s* link = clipModel->clipLinks; + while ( link ) { + idClip::UpdateDynamicContents( link->sector ); + link = link->nextLink; + } +} +// RAVEN END diff --git a/src/game/physics/Clip.h b/src/game/physics/Clip.h new file mode 100644 index 0000000..a51dcf0 --- /dev/null +++ b/src/game/physics/Clip.h @@ -0,0 +1,430 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CLIP_H__ +#define __CLIP_H__ + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +const int CLIPSECTOR_DEPTH = 6; +const int CLIPSECTOR_WIDTH = 1 << CLIPSECTOR_DEPTH; +// RAVEN END + +/* +=============================================================================== + + Handles collision detection with the world and between physics objects. + +=============================================================================== +*/ + +#define CLIPMODEL_ID_TO_JOINT_HANDLE( id ) ( ( id ) >= 0 ? INVALID_JOINT : ((jointHandle_t) ( -1 - id )) ) +#define JOINT_HANDLE_TO_CLIPMODEL_ID( id ) ( -1 - id ) + +class idClip; +class idClipModel; +class idEntity; + + +//=============================================================== +// +// idClipModel +// +//=============================================================== + +class idClipModel { + + friend class idClip; + +public: + idClipModel( void ); + explicit idClipModel( const char *name ); + explicit idClipModel( const idTraceModel &trm, const idMaterial *material = NULL ); + explicit idClipModel( const int renderModelHandle ); + explicit idClipModel( const idClipModel *model ); + ~idClipModel( void ); +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void UpdateDynamicContents( void ); +// RAVEN END + + bool LoadModel( const char *name ); + void LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + void LoadModel( const int renderModelHandle ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void Link( void ); // must have been linked with an entity and id before + void Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); +// RAVEN END + void Unlink( void ); // unlink from sectors + void SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ); // unlinks the clip model + void Translate( const idVec3 &translation ); // unlinks the clip model + void Rotate( const idRotation &rotation ); // unlinks the clip model + void Enable( void ); // enable for clipping + void Disable( void ); // keep linked but disable for clipping + void SetContents( int newContents ); // override contents + int GetContents( void ) const; + void SetEntity( idEntity *newEntity ); + idEntity * GetEntity( void ) const; + void SetId( int newId ); + int GetId( void ) const; + void SetOwner( idEntity *newOwner ); + idEntity * GetOwner( void ) const; + const idBounds & GetBounds( void ) const; + const idBounds & GetAbsBounds( void ) const; + const idVec3 & GetOrigin( void ) const; + const idMat3 & GetAxis( void ) const; + bool IsTraceModel( void ) const; // returns true if this is a trace model + bool IsRenderModel( void ) const; // returns true if this is a render model + bool IsLinked( void ) const; // returns true if the clip model is linked + bool IsEnabled( void ) const; // returns true if enabled for collision detection + bool IsEqual( const idTraceModel &trm ) const; + idCollisionModel * GetCollisionModel( void ) const; // returns handle used to collide vs this model + const idTraceModel * GetTraceModel( void ) const; + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + + static void ClearTraceModelCache( void ); + static int TraceModelCacheSize( void ); + static void SaveTraceModels( idSaveGame *savefile ); + static void RestoreTraceModels( idRestoreGame *savefile ); + +private: + bool enabled; // true if this clip model is used for clipping +// RAVEN BEGIN + bool checked; // Splash's clip model code +// RAVEN END + idEntity * entity; // entity using this clip model + int id; // id for entities that use multiple clip models + idEntity * owner; // owner of the entity that owns this clip model + idVec3 origin; // origin of clip model + idMat3 axis; // orientation of clip model + idBounds bounds; // bounds + idBounds absBounds; // absolute bounds + int contents; // all contents ored together + idCollisionModel * collisionModel; // handle to collision model + int traceModelIndex; // trace model used for collision detection + int renderModelHandle; // render model def handle + + struct clipLink_s * clipLinks; // links into sectors + int touchCount; + + void Init( void ); // initialize + void FreeModel( void ); + void Link_r( struct clipSector_s *node ); + + static void CacheCollisionModels( void ); + static int AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void FreeTraceModel( int traceModelIndex ); + static int CopyTraceModel( const int traceModelIndex ); + static idTraceModel * GetCachedTraceModel( int traceModelIndex ); + static idCollisionModel*GetCachedCollisionModel( int traceModelIndex ); + static int GetTraceModelHashKey( const idTraceModel &trm ); +}; + + +ID_INLINE void idClipModel::Translate( const idVec3 &translation ) { + Unlink(); + origin += translation; +} + +ID_INLINE void idClipModel::Rotate( const idRotation &rotation ) { + Unlink(); + origin *= rotation; + axis *= rotation.ToMat3(); +} + +ID_INLINE void idClipModel::Enable( void ) { + enabled = true; +} + +ID_INLINE void idClipModel::Disable( void ) { + enabled = false; +} + +ID_INLINE void idClipModel::SetContents( int newContents ) { + contents = newContents; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + UpdateDynamicContents(); +// RAVEN END +} + +ID_INLINE int idClipModel::GetContents( void ) const { + return contents; +} + +ID_INLINE void idClipModel::SetEntity( idEntity *newEntity ) { + entity = newEntity; +} + +ID_INLINE idEntity *idClipModel::GetEntity( void ) const { + return entity; +} + +ID_INLINE void idClipModel::SetId( int newId ) { + id = newId; +} + +ID_INLINE int idClipModel::GetId( void ) const { + return id; +} + +ID_INLINE void idClipModel::SetOwner( idEntity *newOwner ) { + owner = newOwner; +} + +ID_INLINE idEntity *idClipModel::GetOwner( void ) const { + return owner; +} + +ID_INLINE const idBounds &idClipModel::GetBounds( void ) const { + return bounds; +} + +ID_INLINE const idBounds &idClipModel::GetAbsBounds( void ) const { + return absBounds; +} + +ID_INLINE const idVec3 &idClipModel::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idMat3 &idClipModel::GetAxis( void ) const { + return axis; +} + +ID_INLINE bool idClipModel::IsRenderModel( void ) const { + return ( renderModelHandle != -1 ); +} + +ID_INLINE bool idClipModel::IsTraceModel( void ) const { + return ( traceModelIndex != -1 ); +} + +ID_INLINE bool idClipModel::IsLinked( void ) const { + return ( clipLinks != NULL ); +} + +ID_INLINE bool idClipModel::IsEnabled( void ) const { + return enabled; +} + +ID_INLINE bool idClipModel::IsEqual( const idTraceModel &trm ) const { + return ( traceModelIndex != -1 && *GetCachedTraceModel( traceModelIndex ) == trm ); +} + +ID_INLINE const idTraceModel *idClipModel::GetTraceModel( void ) const { + if ( !IsTraceModel() ) { + return NULL; + } + return idClipModel::GetCachedTraceModel( traceModelIndex ); +} + + +//=============================================================== +// +// idClip +// +//=============================================================== + +class idClip { + + friend class idClipModel; + +public: + idClip( void ); + + void Init( void ); + void Shutdown( void ); + + // clip versus the rest of the world +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + bool Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + bool Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. + int Contents( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); +// RAVEN END + + // special case translations versus the rest of the world + bool TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, + int contentMask, const idEntity *passEntity ); + bool TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, + int contentMask, const idEntity *passEntity ); + + // clip versus a specific model + void TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + + // clip versus all entities but not the world +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + void TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + + // get a contact feature + bool GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + +// RAVEN BEGIN +// ddynerman: another helper function, useful in MP + int PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; +// RAVEN END + + const idBounds & GetWorldBounds( void ) const; + idCollisionModel * GetWorldCollisionModel( void ) const { return world; } + +// RAVEN BEGIN +// ddynerman: change to static + static idClipModel * DefaultClipModel( void ); + static void FreeDefaultClipModel( void ); +// RAVEN END + + // stats and debug drawing + void PrintStatistics( void ); + void DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type = NULL ); + bool DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const; + +// RAVEN BEGIN +// rjohnson: added debug hud support + void DebugHudStatistics( void ); + void ClearStatistics( void ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void CoordsForBounds( int* coords, idBounds& bounds ) const; + void DrawClipSectors( void ) const; + void DrawAreaClipSectors( float range ) const; + static void UpdateDynamicContents( struct clipSector_s* sector ); + static void UpdateDynamicContents( idClipModel* clipModel ); +// RAVEN END + +private: +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idVec3 nodeScale; + idVec3 nodeOffset; + idVec3 nodeOffsetVisual; +// ddynerman: change to static + static idClipModel defaultClipModel; +// RAVEN END + struct clipSector_s * clipSectors; + idCollisionModel * world; + idBounds worldBounds; + idClipModel temporaryClipModel; + + mutable int touchCount; + // statistics + int numTranslations; + int numRotations; + int numMotions; + int numRenderModelTraces; + int numContents; + int numContacts; + +private: + struct clipSector_s * CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ); + void ClipModelsTouchingBounds_r( const struct clipSector_s *node, struct listParms_s &parms ) const; + const idTraceModel * TraceModelForClipModel( const idClipModel *mdl ) const; +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + int GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 = 0 ) const; +// RAVEN END + void TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void GetClipSectorsStaticContents( void ); +// RAVEN END +}; + + +ID_INLINE bool idClip::TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + Translation( results, start, end, NULL, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE bool idClip::TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + temporaryClipModel.LoadModel( idTraceModel( bounds ), NULL, true ); + Translation( results, start, end, &temporaryClipModel, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE const idBounds & idClip::GetWorldBounds( void ) const { + return worldBounds; +} + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +ID_INLINE void idClip::CoordsForBounds( int* coords, idBounds& bounds ) const { + float fCoords[ 4 ]; + + fCoords[ 0 ] = ( bounds[ 0 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 1 ] = ( bounds[ 0 ].y - nodeOffset.y ) * nodeScale.y; + fCoords[ 2 ] = ( bounds[ 1 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 3 ] = ( bounds[ 1 ].y - nodeOffset.y ) * nodeScale.y; + + int i; + for( i = 0; i < 4; i++ ) { + + coords[ i ] = idMath::FtoiFast( fCoords[ i ] ); + + if( coords[ i ] < 0 ) { + coords[ i ] = 0; + } else if( coords[ i ] > CLIPSECTOR_WIDTH - 1 ) { + coords[ i ] = CLIPSECTOR_WIDTH - 1; + } + } + coords[ 2 ]++; coords[ 3 ]++; +} +// RAVEN END + +#endif /* !__CLIP_H__ */ diff --git a/src/game/physics/Force.cpp b/src/game/physics/Force.cpp new file mode 100644 index 0000000..2a66c94 --- /dev/null +++ b/src/game/physics/Force.cpp @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idClass, idForce ) +END_CLASS + +idList idForce::forceList; + +/* +================ +idForce::idForce +================ +*/ +idForce::idForce( void ) { + forceList.Append( this ); +} + +/* +================ +idForce::~idForce +================ +*/ +idForce::~idForce( void ) { + forceList.Remove( this ); +} + +/* +================ +idForce::DeletePhysics +================ +*/ +void idForce::DeletePhysics( const idPhysics *phys ) { + int i; + + for ( i = 0; i < forceList.Num(); i++ ) { + forceList[i]->RemovePhysics( phys ); + } +} + +/* +================ +idForce::ClearForceList +================ +*/ +void idForce::ClearForceList( void ) { + forceList.Clear(); +} + +/* +================ +idForce::Evaluate +================ +*/ +void idForce::Evaluate( int time ) { +} + +/* +================ +idForce::RemovePhysics +================ +*/ +void idForce::RemovePhysics( const idPhysics *phys ) { +} diff --git a/src/game/physics/Force.h b/src/game/physics/Force.h new file mode 100644 index 0000000..190d636 --- /dev/null +++ b/src/game/physics/Force.h @@ -0,0 +1,62 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_H__ +#define __FORCE_H__ + +/* +=============================================================================== + + Force base class + + A force object applies a force to a physics object. + +=============================================================================== +*/ + +class idEntity; +class idPhysics; + +class idForce : public idClass { + +public: + CLASS_PROTOTYPE( idForce ); + + idForce( void ); + virtual ~idForce( void ); + static void DeletePhysics( const idPhysics *phys ); + static void ClearForceList( void ); + +public: // common force interface + // evalulate the force up to the given time + virtual void Evaluate( int time ); + // removes any pointers to the physics object + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + static idList forceList; +}; + +#endif /* !__FORCE_H__ */ diff --git a/src/game/physics/Force_Constant.cpp b/src/game/physics/Force_Constant.cpp new file mode 100644 index 0000000..063a823 --- /dev/null +++ b/src/game/physics/Force_Constant.cpp @@ -0,0 +1,132 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Constant ) +END_CLASS + +/* +================ +idForce_Constant::idForce_Constant +================ +*/ +idForce_Constant::idForce_Constant( void ) { + force = vec3_zero; + physics = NULL; + id = 0; + point = vec3_zero; +} + +/* +================ +idForce_Constant::~idForce_Constant +================ +*/ +idForce_Constant::~idForce_Constant( void ) { +} + +/* +================ +idForce_Constant::Save +================ +*/ +void idForce_Constant::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( force ); + // TOSAVE: idPhysics * physics + savefile->WriteInt( id ); + savefile->WriteVec3( point ); +} + +/* +================ +idForce_Constant::Restore +================ +*/ +void idForce_Constant::Restore( idRestoreGame *savefile ) { + // Owner needs to call SetPhysics!! + savefile->ReadVec3( force ); + savefile->ReadInt( id ); + savefile->ReadVec3( point ); +} + +/* +================ +idForce_Constant::SetPosition +================ +*/ +void idForce_Constant::SetPosition( idPhysics *physics, int id, const idVec3 &point ) { + this->physics = physics; + this->id = id; + this->point = point; +} + +/* +================ +idForce_Constant::SetForce +================ +*/ +void idForce_Constant::SetForce( const idVec3 &force ) { + this->force = force; +} + +/* +================ +idForce_Constant::SetPhysics +================ +*/ +void idForce_Constant::SetPhysics( idPhysics *physics ) { + this->physics = physics; +} + +/* +================ +idForce_Constant::Evaluate +================ +*/ +void idForce_Constant::Evaluate( int time ) { + idVec3 p; + + if ( !physics ) { + return; + } + + p = physics->GetOrigin( id ) + point * physics->GetAxis( id ); + + physics->AddForce( id, p, force ); +} + +/* +================ +idForce_Constant::RemovePhysics +================ +*/ +void idForce_Constant::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/src/game/physics/Force_Constant.h b/src/game/physics/Force_Constant.h new file mode 100644 index 0000000..4c8de39 --- /dev/null +++ b/src/game/physics/Force_Constant.h @@ -0,0 +1,67 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_CONSTANT_H__ +#define __FORCE_CONSTANT_H__ + +/* +=============================================================================== + + Constant force + +=============================================================================== +*/ + +class idForce_Constant : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Constant ); + + idForce_Constant( void ); + virtual ~idForce_Constant( void ); + + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // constant force + void SetForce( const idVec3 &force ); + // set force position + void SetPosition( idPhysics *physics, int id, const idVec3 &point ); + + void SetPhysics( idPhysics *physics ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + // force properties + idVec3 force; + idPhysics * physics; + int id; + idVec3 point; +}; + +#endif /* !__FORCE_CONSTANT_H__ */ diff --git a/src/game/physics/Force_Drag.cpp b/src/game/physics/Force_Drag.cpp new file mode 100644 index 0000000..3a5fba2 --- /dev/null +++ b/src/game/physics/Force_Drag.cpp @@ -0,0 +1,154 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Drag ) +END_CLASS + +/* +================ +idForce_Drag::idForce_Drag +================ +*/ +idForce_Drag::idForce_Drag( void ) { + damping = 0.5f; + dragPosition = vec3_zero; + physics = NULL; + id = 0; + p = vec3_zero; + dragPosition = vec3_zero; +} + +/* +================ +idForce_Drag::~idForce_Drag +================ +*/ +idForce_Drag::~idForce_Drag( void ) { +} + +/* +================ +idForce_Drag::Init +================ +*/ +void idForce_Drag::Init( float damping ) { + if ( damping >= 0.0f && damping < 1.0f ) { + this->damping = damping; + } +} + +/* +================ +idForce_Drag::SetPhysics +================ +*/ +void idForce_Drag::SetPhysics( idPhysics *phys, int id, const idVec3 &p ) { + this->physics = phys; + this->id = id; + this->p = p; +} + +/* +================ +idForce_Drag::SetDragPosition +================ +*/ +void idForce_Drag::SetDragPosition( const idVec3 &pos ) { + this->dragPosition = pos; +} + +/* +================ +idForce_Drag::GetDragPosition +================ +*/ +const idVec3 &idForce_Drag::GetDragPosition( void ) const { + return this->dragPosition; +} + +/* +================ +idForce_Drag::GetDraggedPosition +================ +*/ +const idVec3 idForce_Drag::GetDraggedPosition( void ) const { + return ( physics->GetOrigin( id ) + p * physics->GetAxis( id ) ); +} + +/* +================ +idForce_Drag::Evaluate +================ +*/ +void idForce_Drag::Evaluate( int time ) { + float l1, l2, mass; + idVec3 dragOrigin, dir1, dir2, velocity, centerOfMass; + idMat3 inertiaTensor; + idRotation rotation; + idClipModel *clipModel; + + if ( !physics ) { + return; + } + + clipModel = physics->GetClipModel( id ); + if ( clipModel != NULL && clipModel->IsTraceModel() ) { + clipModel->GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + } else { + centerOfMass.Zero(); + } + + centerOfMass = physics->GetOrigin( id ) + centerOfMass * physics->GetAxis( id ); + dragOrigin = physics->GetOrigin( id ) + p * physics->GetAxis( id ); + + dir1 = dragPosition - centerOfMass; + dir2 = dragOrigin - centerOfMass; + l1 = dir1.Normalize(); + l2 = dir2.Normalize(); + + rotation.Set( centerOfMass, dir2.Cross( dir1 ), RAD2DEG( idMath::ACos( dir1 * dir2 ) ) ); + physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( gameLocal.GetMSec() ), id ); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( gameLocal.GetMSec ( ) ) ); +// RAVEN END + physics->SetLinearVelocity( velocity, id ); +} + +/* +================ +idForce_Drag::RemovePhysics +================ +*/ +void idForce_Drag::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/src/game/physics/Force_Drag.h b/src/game/physics/Force_Drag.h new file mode 100644 index 0000000..5d6bca2 --- /dev/null +++ b/src/game/physics/Force_Drag.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_DRAG_H__ +#define __FORCE_DRAG_H__ + +/* +=============================================================================== + + Drag force + +=============================================================================== +*/ + +class idForce_Drag : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Drag ); + + idForce_Drag( void ); + virtual ~idForce_Drag( void ); + // initialize the drag force + void Init( float damping ); + // set physics object being dragged + void SetPhysics( idPhysics *physics, int id, const idVec3 &p ); + // set position to drag towards + void SetDragPosition( const idVec3 &pos ); + // get the position dragged towards + const idVec3 & GetDragPosition( void ) const; + // get the position on the dragged physics object + const idVec3 GetDraggedPosition( void ) const; + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // properties + float damping; + + // positioning + idPhysics * physics; // physics object + int id; // clip model id of physics object + idVec3 p; // position on clip model + idVec3 dragPosition; // drag towards this position +}; + +#endif /* !__FORCE_DRAG_H__ */ diff --git a/src/game/physics/Force_Field.cpp b/src/game/physics/Force_Field.cpp new file mode 100644 index 0000000..dc8efc0 --- /dev/null +++ b/src/game/physics/Force_Field.cpp @@ -0,0 +1,295 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Field ) +END_CLASS + +/* +================ +idForce_Field::idForce_Field +================ +*/ +idForce_Field::idForce_Field( void ) { + type = FORCEFIELD_UNIFORM; + applyType = FORCEFIELD_APPLY_FORCE; + magnitude = 0.0f; + dir.Set( 0, 0, 1 ); + randomTorque = 0.0f; + playerOnly = false; + monsterOnly = false; + clipModel = NULL; +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = -1; + owner = NULL; +// RAVEN END +} + +/* +================ +idForce_Field::~idForce_Field +================ +*/ +idForce_Field::~idForce_Field( void ) { + if ( this->clipModel ) { + delete this->clipModel; + } +} + +/* +================ +idForce_Field::Save +================ +*/ +void idForce_Field::Save( idSaveGame *savefile ) const { + savefile->WriteInt( type ); + savefile->WriteInt( applyType); + savefile->WriteFloat( magnitude ); + savefile->WriteVec3( dir ); + savefile->WriteFloat( randomTorque ); + savefile->WriteBool( playerOnly ); + savefile->WriteBool( monsterOnly ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteInt ( lastApplyTime ); // cnicholson: Added unsaved var + // TOSAVE: idEntity* owner; +} + +/* +================ +idForce_Field::Restore +================ +*/ +void idForce_Field::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)type ); + savefile->ReadInt( (int &)applyType); + savefile->ReadFloat( magnitude ); + savefile->ReadVec3( dir ); + savefile->ReadFloat( randomTorque ); + savefile->ReadBool( playerOnly ); + savefile->ReadBool( monsterOnly ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadInt ( lastApplyTime ); // cnicholson: Added unrestored var +} + +/* +================ +idForce_Field::SetClipModel +================ +*/ +void idForce_Field::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && clipModel != this->clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idForce_Field::Uniform +================ +*/ +void idForce_Field::Uniform( const idVec3 &force ) { + dir = force; + magnitude = dir.Normalize(); + type = FORCEFIELD_UNIFORM; +} + +/* +================ +idForce_Field::Explosion +================ +*/ +void idForce_Field::Explosion( float force ) { + magnitude = force; + type = FORCEFIELD_EXPLOSION; +} + +/* +================ +idForce_Field::Implosion +================ +*/ +void idForce_Field::Implosion( float force ) { + magnitude = force; + type = FORCEFIELD_IMPLOSION; +} + +/* +================ +idForce_Field::RandomTorque +================ +*/ +void idForce_Field::RandomTorque( float force ) { + randomTorque = force; +} + +/* +================ +idForce_Field::Evaluate +================ +*/ +void idForce_Field::Evaluate( int time ) { + int numClipModels, i; + idBounds bounds; + idVec3 force, torque, angularVelocity; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + + assert( clipModel ); + + bounds.FromTransformedBounds( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( owner, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + + idPhysics *physics = entity->GetPhysics(); + + if ( playerOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Player::GetClassType() ) ) { +// RAVEN END + continue; + } + } else if ( monsterOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Monster::GetClassType() ) ) { + + continue; + } + } + +// nrausch: It was undesireable to have gibs and discarded weapons bouncing on jump pads + if ( gameLocal.isMultiplayer ) { + if ( entity->IsType( idItem::GetClassType() ) ) { + continue; + } + if ( entity->IsType( rvClientPhysics::GetClassType() ) ) { + continue; + } + if ( entity->IsType( idPlayer::GetClassType() ) ) { + if ( ((idPlayer*)entity)->health <= 0 ) { + continue; + } + } + + } + +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( owner, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + switch( type ) { + case FORCEFIELD_UNIFORM: { + force = dir; + break; + } + case FORCEFIELD_EXPLOSION: { + force = cm->GetOrigin() - clipModel->GetOrigin(); + force.Normalize(); + break; + } + case FORCEFIELD_IMPLOSION: { + force = clipModel->GetOrigin() - cm->GetOrigin(); + force.Normalize(); + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid type" ); + break; + } + } + + if ( randomTorque != 0.0f ) { + torque[0] = gameLocal.random.CRandomFloat(); + torque[1] = gameLocal.random.CRandomFloat(); + torque[2] = gameLocal.random.CRandomFloat(); + if ( torque.Normalize() == 0.0f ) { + torque[2] = 1.0f; + } + } + + switch( applyType ) { + case FORCEFIELD_APPLY_FORCE: { + if ( randomTorque != 0.0f ) { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + case FORCEFIELD_APPLY_VELOCITY: { + physics->SetLinearVelocity( force * magnitude, cm->GetId() ); + if ( randomTorque != 0.0f ) { + angularVelocity = physics->GetAngularVelocity( cm->GetId() ); + physics->SetAngularVelocity( 0.5f * (angularVelocity + torque * randomTorque), cm->GetId() ); + } + break; + } + case FORCEFIELD_APPLY_IMPULSE: { + if ( randomTorque != 0.0f ) { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid apply type" ); + break; + } + } + +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = time; +// RAVEN END + } +} diff --git a/src/game/physics/Force_Field.h b/src/game/physics/Force_Field.h new file mode 100644 index 0000000..2b72311 --- /dev/null +++ b/src/game/physics/Force_Field.h @@ -0,0 +1,115 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_FIELD_H__ +#define __FORCE_FIELD_H__ + +/* +=============================================================================== + + Force field + +=============================================================================== +*/ + +enum forceFieldType { + FORCEFIELD_UNIFORM, + FORCEFIELD_EXPLOSION, + FORCEFIELD_IMPLOSION +}; + +enum forceFieldApplyType { + FORCEFIELD_APPLY_FORCE, + FORCEFIELD_APPLY_VELOCITY, + FORCEFIELD_APPLY_IMPULSE +}; + +class idForce_Field : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Field ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idForce_Field( void ); + virtual ~idForce_Field( void ); + // uniform constant force + void Uniform( const idVec3 &force ); + // explosion from clip model origin + void Explosion( float force ); + // implosion towards clip model origin + void Implosion( float force ); + // add random torque + void RandomTorque( float force ); + // should the force field apply a force, velocity or impulse + void SetApplyType( const forceFieldApplyType type ) { applyType = type; } + // make the force field only push players + void SetPlayerOnly( bool set ) { playerOnly = set; } + // make the force field only push monsters + void SetMonsterOnly( bool set ) { monsterOnly = set; } + // clip model describing the extents of the force field + void SetClipModel( idClipModel *clipModel ); + +// RAVEN BEGIN +// ddynerman: owner information + void SetOwner( idEntity* ent ); +// bdube: added last apply time + int GetLastApplyTime( void ) const; +// RAVEN END + +public: // common force interface + virtual void Evaluate( int time ); + +private: + // force properties + forceFieldType type; + forceFieldApplyType applyType; + float magnitude; + idVec3 dir; + float randomTorque; + bool playerOnly; + bool monsterOnly; + idClipModel * clipModel; + +// RAVEN BEGIN +// bdube: added last apply time + int lastApplyTime; + idEntity* owner; +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added last apply time +ID_INLINE int idForce_Field::GetLastApplyTime ( void ) const { + return lastApplyTime; +} + +// ddynerman: owner information +ID_INLINE void idForce_Field::SetOwner( idEntity* ent ) { + owner = ent; +} +// RAVEN END + +#endif /* !__FORCE_FIELD_H__ */ diff --git a/src/game/physics/Force_Spring.cpp b/src/game/physics/Force_Spring.cpp new file mode 100644 index 0000000..f692339 --- /dev/null +++ b/src/game/physics/Force_Spring.cpp @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Spring ) +END_CLASS + +/* +================ +idForce_Spring::idForce_Spring +================ +*/ +idForce_Spring::idForce_Spring( void ) { + Kstretch = 100.0f; + Kcompress = 100.0f; + damping = 0.0f; + restLength = 0.0f; + physics1 = NULL; + id1 = 0; + p1 = vec3_zero; + physics2 = NULL; + id2 = 0; + p2 = vec3_zero; +} + +/* +================ +idForce_Spring::~idForce_Spring +================ +*/ +idForce_Spring::~idForce_Spring( void ) { +} + +/* +================ +idForce_Spring::InitSpring +================ +*/ +void idForce_Spring::InitSpring( float Kstretch, float Kcompress, float damping, float restLength ) { + this->Kstretch = Kstretch; + this->Kcompress = Kcompress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idForce_Spring::SetPosition +================ +*/ +void idForce_Spring::SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, idPhysics *physics2, int id2, const idVec3 &p2 ) { + this->physics1 = physics1; + this->id1 = id1; + this->p1 = p1; + this->physics2 = physics2; + this->id2 = id2; + this->p2 = p2; +} + +/* +================ +idForce_Spring::Evaluate +================ +*/ +void idForce_Spring::Evaluate( int time ) { + float length; + idMat3 axis; + idVec3 pos1, pos2, velocity1, velocity2, force, dampingForce; + impactInfo_t info; + + pos1 = p1; + pos2 = p2; + velocity1 = velocity2 = vec3_origin; + + if ( physics1 ) { + axis = physics1->GetAxis( id1 ); + pos1 = physics1->GetOrigin( id1 ); + pos1 += p1 * axis; + if ( damping > 0.0f ) { + physics1->GetImpactInfo( id1, pos1, &info ); + velocity1 = info.velocity; + } + } + + if ( physics2 ) { + axis = physics2->GetAxis( id2 ); + pos2 = physics2->GetOrigin( id2 ); + pos2 += p2 * axis; + if ( damping > 0.0f ) { + physics2->GetImpactInfo( id2, pos2, &info ); + velocity2 = info.velocity; + } + } + + force = pos2 - pos1; + dampingForce = ( damping * ( ((velocity2 - velocity1) * force) / (force * force) ) ) * force; + length = force.Normalize(); + + // if the spring is stretched + if ( length > restLength ) { + if ( Kstretch > 0.0f ) { + force = ( Square( length - restLength ) * Kstretch ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, -force ); + } + } + } + else { + if ( Kcompress > 0.0f ) { + force = ( Square( length - restLength ) * Kcompress ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, -force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, force ); + } + } + } +} + +/* +================ +idForce_Spring::RemovePhysics +================ +*/ +void idForce_Spring::RemovePhysics( const idPhysics *phys ) { + if ( physics1 == phys ) { + physics1 = NULL; + } + if ( physics2 == phys ) { + physics2 = NULL; + } +} + +/* +================ +idForce_Spring::Save +================ +*/ +void idForce_Spring::Save( idSaveGame *savefile ) const { + savefile->WriteFloat ( Kstretch ); + savefile->WriteFloat ( Kcompress ); + savefile->WriteFloat ( damping ); + savefile->WriteFloat ( restLength ); +} + +/* +================ +idForce_Spring::Restore +================ +*/ +void idForce_Spring::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat ( Kstretch ); + savefile->ReadFloat ( Kcompress ); + savefile->ReadFloat ( damping ); + savefile->ReadFloat ( restLength ); +} + diff --git a/src/game/physics/Force_Spring.h b/src/game/physics/Force_Spring.h new file mode 100644 index 0000000..c045eb6 --- /dev/null +++ b/src/game/physics/Force_Spring.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_SPRING_H__ +#define __FORCE_SPRING_H__ + +/* +=============================================================================== + + Spring force + +=============================================================================== +*/ + +class idForce_Spring : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Spring ); + + idForce_Spring( void ); + virtual ~idForce_Spring( void ); + // initialize the spring + void InitSpring( float Kstretch, float Kcompress, float damping, float restLength ); + // set the entities and positions on these entities the spring is attached to + void SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, + idPhysics *physics2, int id2, const idVec3 &p2 ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // spring properties + float Kstretch; + float Kcompress; + float damping; + float restLength; + + // positioning + idPhysics * physics1; // first physics object + int id1; // clip model id of first physics object + idVec3 p1; // position on clip model + idPhysics * physics2; // second physics object + int id2; // clip model id of second physics object + idVec3 p2; // position on clip model + +}; + +#endif /* !__FORCE_SPRING_H__ */ diff --git a/src/game/physics/Physics.cpp b/src/game/physics/Physics.cpp new file mode 100644 index 0000000..393a6f1 --- /dev/null +++ b/src/game/physics/Physics.cpp @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, idPhysics ) +END_CLASS + + +/* +================ +idPhysics::~idPhysics +================ +*/ +idPhysics::~idPhysics( void ) { +} + +/* +================ +idPhysics::Save +================ +*/ +void idPhysics::Save( idSaveGame *savefile ) const { +} + +/* +================ +idPhysics::Restore +================ +*/ +void idPhysics::Restore( idRestoreGame *savefile ) { +} + +/* +================ +idPhysics::SetClipBox +================ +*/ +void idPhysics::SetClipBox( const idBounds &bounds, float density ) { + SetClipModel( new idClipModel( idTraceModel( bounds ) ), density ); +} + +/* +================ +idPhysics::SnapTimeToPhysicsFrame +================ +*/ +int idPhysics::SnapTimeToPhysicsFrame( int t ) { + int s; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + s = t + gameLocal.GetMSec() - 1; + return ( s - s % gameLocal.GetMSec() ); +// RAVEN END +} diff --git a/src/game/physics/Physics.h b/src/game/physics/Physics.h new file mode 100644 index 0000000..e106835 --- /dev/null +++ b/src/game/physics/Physics.h @@ -0,0 +1,202 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_H__ +#define __PHYSICS_H__ + +/* +=============================================================================== + + Physics abstract class + + A physics object is a tool to manipulate the position and orientation of + an entity. The physics object is a container for idClipModels used for + collision detection. The physics deals with moving these collision models + through the world according to the laws of physics or other rules. + + The mass of a clip model is the volume of the clip model times the density. + An arbitrary mass can however be set for specific clip models or the + whole physics object. The contents of a clip model is a set of bit flags + that define the contents. The clip mask defines the contents a clip model + collides with. + + The linear velocity of a physics object is a vector that defines the + translation of the center of mass in units per second. The angular velocity + of a physics object is a vector that passes through the center of mass. The + direction of this vector defines the axis of rotation and the magnitude + defines the rate of rotation about the axis in radians per second. + The gravity is the change in velocity per second due to gravitational force. + + Entities update their visual position and orientation from the physics + using GetOrigin() and GetAxis(). Direct origin and axis changes of + entities should go through the physics. In other words the physics origin + and axis are updated first and the entity updates it's visual position + from the physics. + +=============================================================================== +*/ + +#define CONTACT_EPSILON 0.25f // maximum contact seperation distance + +class idEntity; + +typedef struct impactInfo_s { + float invMass; // inverse mass + idMat3 invInertiaTensor; // inverse inertia tensor + idVec3 position; // impact position relative to center of mass + idVec3 velocity; // velocity at the impact position +} impactInfo_t; + + +class idPhysics : public idClass { + +public: + ABSTRACT_PROTOTYPE( idPhysics ); + + virtual ~idPhysics( void ); + static int SnapTimeToPhysicsFrame( int t ); + + // Must not be virtual + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + // set pointer to entity using physics + virtual void SetSelf( idEntity *e ) = 0; + // clip models + virtual void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ) = 0; + virtual void SetClipBox( const idBounds &bounds, float density ); + virtual idClipModel * GetClipModel( int id = 0 ) const = 0; + virtual int GetNumClipModels( void ) const = 0; + // get/set the mass of a specific clip model or the whole physics object + virtual void SetMass( float mass, int id = -1 ) = 0; + virtual float GetMass( int id = -1 ) const = 0; + +// RAVEN BEGIN +// bdube: added center mass + // get the center of mass origin + virtual idVec3 GetCenterMass ( int id = -1 ) const = 0; +// RAVEN END + + // get/set the contents of a specific clip model or the whole physics object + virtual void SetContents( int contents, int id = -1 ) = 0; + virtual int GetContents( int id = -1 ) const = 0; + // get/set the contents a specific clip model or the whole physics object collides with + virtual void SetClipMask( int mask, int id = -1 ) = 0; + virtual int GetClipMask( int id = -1 ) const = 0; + // get the bounds of a specific clip model or the whole physics object + virtual const idBounds & GetBounds( int id = -1 ) const = 0; + virtual const idBounds & GetAbsBounds( int id = -1 ) const = 0; + // evaluate the physics with the given time step, returns true if the object moved + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ) = 0; + // update the time without moving + virtual void UpdateTime( int endTimeMSec ) = 0; + // get the last physics update time + virtual int GetTime( void ) const = 0; + // collision interaction between different physics objects + virtual void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const = 0; + virtual void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) = 0; + virtual void AddForce( const int id, const idVec3 &point, const idVec3 &force ) = 0; + virtual void Activate( void ) = 0; + virtual void PutToRest( void ) = 0; + virtual bool IsAtRest( void ) const = 0; + virtual int GetRestStartTime( void ) const = 0; + virtual bool IsPushable( void ) const = 0; +// RAVEN BEGIN +// bdube: water interraction + virtual bool IsInWater ( void ) const = 0; +// RAVEN END + // save and restore the physics state + virtual void SaveState( void ) = 0; + virtual void RestoreState( void ) = 0; + // set the position and orientation in master space or world space if no master set + virtual void SetOrigin( const idVec3 &newOrigin, int id = -1 ) = 0; + virtual void SetAxis( const idMat3 &newAxis, int id = -1 ) = 0; + // translate or rotate the physics object in world space + virtual void Translate( const idVec3 &translation, int id = -1 ) = 0; + virtual void Rotate( const idRotation &rotation, int id = -1 ) = 0; + // get the position and orientation in world space + virtual const idVec3 & GetOrigin( int id = 0 ) const = 0; + virtual const idMat3 & GetAxis( int id = 0 ) const = 0; + // set linear and angular velocity + virtual void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ) = 0; + virtual void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ) = 0; + // get linear and angular velocity + virtual const idVec3 & GetLinearVelocity( int id = 0 ) const = 0; + virtual const idVec3 & GetAngularVelocity( int id = 0 ) const = 0; + // gravity + virtual void SetGravity( const idVec3 &newGravity ) = 0; + virtual const idVec3 & GetGravity( void ) const = 0; + virtual const idVec3 & GetGravityNormal( void ) const = 0; + // get first collision when translating or rotating this physics object + virtual void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const = 0; + virtual void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const = 0; + virtual int ClipContents( const idClipModel *model ) const = 0; + // disable/enable the clip models contained by this physics object + virtual void DisableClip( void ) = 0; + virtual void EnableClip( void ) = 0; + // link/unlink the clip models contained by this physics object + virtual void UnlinkClip( void ) = 0; + virtual void LinkClip( void ) = 0; + // contacts + virtual bool EvaluateContacts( void ) = 0; + virtual int GetNumContacts( void ) const = 0; + virtual const contactInfo_t &GetContact( int num ) const = 0; + virtual void ClearContacts( void ) = 0; + virtual void AddContactEntity( idEntity *e ) = 0; + virtual void RemoveContactEntity( idEntity *e ) = 0; + // ground contacts + virtual bool HasGroundContacts( void ) const = 0; + virtual bool IsGroundEntity( int entityNum ) const = 0; + virtual bool IsGroundClipModel( int entityNum, int id ) const = 0; +// RAVEN BEGIN +// abahr + virtual const idVec3 GetContactNormal() const { return vec3_zero; } + virtual const idVec3 GetGroundContactNormal() const { return vec3_zero; } + virtual idEntity* GetSelf() const = 0; +// RAVEN END + // set the master entity for objects bound to a master + virtual void SetMaster( idEntity *master, const bool orientated = true ) = 0; + // set pushed state + virtual void SetPushed( int deltaTime ) = 0; + virtual const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const = 0; + virtual const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const = 0; + // get blocking info, returns NULL if the object is not blocked + virtual const trace_t * GetBlockingInfo( void ) const = 0; + virtual idEntity * GetBlockingEntity( void ) const = 0; + // movement end times in msec for reached events at the end of predefined motion + virtual int GetLinearEndTime( void ) const = 0; + virtual int GetAngularEndTime( void ) const = 0; + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const = 0; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ) = 0; + +// RAVEN BEGIN +// kfuller: we want to debug draw the bbox + virtual void DebugDraw() {} +// RAVEN END + +}; + +#endif /* !__PHYSICS_H__ */ diff --git a/src/game/physics/Physics_AF.cpp b/src/game/physics/Physics_AF.cpp new file mode 100644 index 0000000..17a93ca --- /dev/null +++ b/src/game/physics/Physics_AF.cpp @@ -0,0 +1,8102 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_AF ) +END_CLASS + +const float ERROR_REDUCTION = 0.5f; +const float ERROR_REDUCTION_MAX = 256.0f; +const float LIMIT_ERROR_REDUCTION = 0.3f; +const float LCP_EPSILON = 1e-7f; +const float LIMIT_LCP_EPSILON = 1e-4f; +const float CONTACT_LCP_EPSILON = 1e-6f; +const float CENTER_OF_MASS_EPSILON = 1e-4f; +const float NO_MOVE_TIME = 1.0f; +const float NO_MOVE_TRANSLATION_TOLERANCE = 10.0f; +const float NO_MOVE_ROTATION_TOLERANCE = 10.0f; +const float MIN_MOVE_TIME = -1.0f; +const float MAX_MOVE_TIME = -1.0f; +const float IMPULSE_THRESHOLD = 500.0f; +const float SUSPEND_LINEAR_VELOCITY = 10.0f; +const float SUSPEND_ANGULAR_VELOCITY = 15.0f; +const float SUSPEND_LINEAR_ACCELERATION = 20.0f; +const float SUSPEND_ANGULAR_ACCELERATION = 30.0f; +const idVec6 vec6_lcp_epsilon = idVec6( LCP_EPSILON, LCP_EPSILON, LCP_EPSILON, + LCP_EPSILON, LCP_EPSILON, LCP_EPSILON ); + +#define AF_TIMINGS + +#ifdef AF_TIMINGS +static int lastTimerReset = 0; +static int numArticulatedFigures = 0; +static idTimer timer_total, timer_pc, timer_ac, timer_collision, timer_lcp; +#endif + + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +/* +================ +idAFConstraint::idAFConstraint +================ +*/ +idAFConstraint::idAFConstraint( void ) { + type = CONSTRAINT_INVALID; + name = "noname"; + body1 = NULL; + body2 = NULL; + physics = NULL; + + lo.Zero( 6 ); + lo.SubVec6(0) = -vec6_infinity; + hi.Zero( 6 ); + hi.SubVec6(0) = vec6_infinity; + e.SetSize( 6 ); + e.SubVec6(0) = vec6_lcp_epsilon; + + boxConstraint = NULL; + boxIndex[0] = -1; + boxIndex[1] = -1; + boxIndex[2] = -1; + boxIndex[3] = -1; + boxIndex[4] = -1; + boxIndex[5] = -1; + + firstIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); +} + +/* +================ +idAFConstraint::~idAFConstraint +================ +*/ +idAFConstraint::~idAFConstraint( void ) { +} + +/* +================ +idAFConstraint::SetBody1 +================ +*/ +void idAFConstraint::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::SetBody2 +================ +*/ +void idAFConstraint::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::GetMultiplier +================ +*/ +const idVecX &idAFConstraint::GetMultiplier( void ) { + return lm; +} + +/* +================ +idAFConstraint::Evaluate +================ +*/ +void idAFConstraint::Evaluate( float invTimeStep ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::ApplyFriction +================ +*/ +void idAFConstraint::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint::GetForce +================ +*/ +void idAFConstraint::GetForce( idAFBody *body, idVec6 &force ) { + idVecX v; + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + if ( body == body1 ) { + J1.TransposeMultiply( v, lm ); + } + else if ( body == body2 ) { + J2.TransposeMultiply( v, lm ); + } + else { + v.Zero(); + } + force[0] = v[0]; force[1] = v[1]; force[2] = v[2]; force[3] = v[3]; force[4] = v[4]; force[5] = v[5]; +} + +/* +================ +idAFConstraint::Translate +================ +*/ +void idAFConstraint::Translate( const idVec3 &translation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::Rotate +================ +*/ +void idAFConstraint::Rotate( const idRotation &rotation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::GetCenter +================ +*/ +void idAFConstraint::GetCenter( idVec3 ¢er ) { + center.Zero(); +} + +/* +================ +idAFConstraint::DebugDraw +================ +*/ +void idAFConstraint::DebugDraw( void ) { +} + +/* +================ +idAFConstraint::InitSize +================ +*/ +void idAFConstraint::InitSize( int size ) { + J1.Zero( size, 6 ); + J2.Zero( size, 6 ); + c1.Zero( size ); + c2.Zero( size ); + s.Zero( size ); + lm.Zero( size ); +} + +/* +================ +idAFConstraint::Save +================ +*/ +void idAFConstraint::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( type ); + saveFile->WriteString ( name ); // cnicholson: Added unsaved var + // TOSAVE: idAFBody * body1; + // TOSAVE: idAFBody * body2; + // TOSAVE: idPhysics_AF * physics; + + // TOSAVE: idMatX J1, J2; + // TOSAVE: idVecX c1, c2; + // TOSAVE: idVecX lo, hi, e; + // TOSAVE: idAFConstraint * boxConstraint; + for (int i=0; i<6; ++i) { // cnicholson: Added unsaved var + saveFile->WriteInt ( boxIndex[i] ); + } + + // TOSAVE: idMatX invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX lm; + saveFile->WriteInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ));// cnicholson: Added unsaved var +} + +/* +================ +idAFConstraint::Restore +================ +*/ +void idAFConstraint::Restore( idRestoreGame *saveFile ) { + constraintType_t t; + saveFile->ReadInt( (int &)t ); + assert( t == type ); + + saveFile->ReadString ( name ); // cnicholson: Added unrestored var + + for (int i=0; i<6; ++i) { + saveFile->ReadInt ( boxIndex[i] ); + } + + saveFile->ReadInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Read( &fl, sizeof( fl )); // cnicholson: Added unsaved var +} + + +//=============================================================== +// +// idAFConstraint_Fixed +// +//=============================================================== + +/* +================ +idAFConstraint_Fixed::idAFConstraint_Fixed +================ +*/ +idAFConstraint_Fixed::idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_FIXED; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 6 ); + fl.allowPrimary = true; + fl.noCollision = true; + + InitOffset(); +} + +/* +================ +idAFConstraint_Fixed::InitOffset +================ +*/ +void idAFConstraint_Fixed::InitOffset( void ) { + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Fixed::SetBody1 +================ +*/ +void idAFConstraint_Fixed::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::SetBody2 +================ +*/ +void idAFConstraint_Fixed::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::Evaluate +================ +*/ +void idAFConstraint_Fixed::Evaluate( float invTimeStep ) { + idVec3 ofs, a2; + idMat3 ax; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + a2 = offset * master->GetWorldAxis(); + ofs = a2 + master->GetWorldOrigin(); + ax = relAxis * master->GetWorldAxis(); + } + else { + a2.Zero(); + ofs = offset; + ax = relAxis; + } + + J1.Set( mat3_identity, mat3_zero, + mat3_zero, mat3_identity ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, -mat3_identity ); + } + else { + J2.Zero( 6, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( ofs - body1->GetWorldOrigin() ); + r = ( body1->GetWorldAxis().Transpose() * ax ).ToRotation(); + c1.SubVec3(1) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * -(float) DEG2RAD( r.GetAngle() ) ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Fixed::ApplyFriction +================ +*/ +void idAFConstraint_Fixed::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Fixed::Translate +================ +*/ +void idAFConstraint_Fixed::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Fixed::Rotate +================ +*/ +void idAFConstraint_Fixed::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + relAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Fixed::GetCenter +================ +*/ +void idAFConstraint_Fixed::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin(); +} + +/* +================ +idAFConstraint_Fixed::DebugDraw +================ +*/ +void idAFConstraint_Fixed::DebugDraw( void ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), master->GetWorldOrigin() ); + } + else { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), vec3_origin ); + } +} + +/* +================ +idAFConstraint_Fixed::Save +================ +*/ +void idAFConstraint_Fixed::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Fixed::Restore +================ +*/ +void idAFConstraint_Fixed::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJoint +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_BALLANDSOCKETJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 3 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetAnchor +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_BallAndSocketJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetNoLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetConeLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetFriction +================ +*/ +float idAFConstraint_BallAndSocketJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( anchor2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + J1.Set( mat3_identity, -SkewSymmetric( a1 ) ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ) ); + } + else { + J2.Zero( 3, 6 ); + } + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_BallAndSocketJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetForce +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Translate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetCenter +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::DebugDraw +================ +*/ +void idAFConstraint_BallAndSocketJoint::DebugDraw( void ) { + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 5, 0, 0 ), a1 + idVec3( 5, 0, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 5, 0 ), a1 + idVec3( 0, 5, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 0, 5 ), a1 + idVec3( 0, 0, 5 ) ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Save +================ +*/ +void idAFConstraint_BallAndSocketJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( friction ); +// cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); // cnicholson: Bad way to save and restore + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_BallAndSocketJointFriction *fc; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Restore +================ +*/ +void idAFConstraint_BallAndSocketJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( friction ); + + // cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_BallAndSocketJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction +================ +*/ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "ballAndSocketJointFriction"; + InitSize( 3 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Setup +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Setup( idAFConstraint_BallAndSocketJoint *bsj ) { + this->joint = bsj; + body1 = bsj->GetBody1(); + body2 = bsj->GetBody2(); +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Add +================ +*/ +bool idAFConstraint_BallAndSocketJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = lo[2] = -f; + hi[0] = hi[1] = hi[2] = f; + + J1.Zero( 3, 6 ); + J1[0][3] = J1[1][4] = J1[2][5] = 1.0f; + + if ( body2 ) { + + J2.Zero( 3, 6 ); + J2[0][3] = J2[1][4] = J2[2][5] = 1.0f; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Translate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_UniversalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_UNIVERSALJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 4 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetAnchor +================ +*/ +void idAFConstraint_UniversalJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_UniversalJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_UniversalJoint::SetShafts +================ +*/ +void idAFConstraint_UniversalJoint::SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ) { + idVec3 cardanAxis; + float l; + + shaft1 = cardanShaft1; + l = shaft1.Normalize(); + assert( l != 0.0f ); + shaft2 = cardanShaft2; + l = shaft2.Normalize(); + assert( l != 0.0f ); + + // the cardan axis is a vector orthogonal to both cardan shafts + cardanAxis = shaft1.Cross( shaft2 ); + if ( cardanAxis.Normalize() == 0.0f ) { + idVec3 vecY; + shaft1.OrthogonalBasis( cardanAxis, vecY ); + cardanAxis.Normalize(); + } + + shaft1 *= body1->GetWorldAxis().Transpose(); + axis1 = cardanAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + shaft2 *= body2->GetWorldAxis().Transpose(); + axis2 = cardanAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = cardanAxis; + } + + if ( coneLimit ) { + coneLimit->SetBody1Axis( shaft1 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetBody1Axis( shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetNoLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetConeLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, shaft1 ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, shaft1 ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_UniversalJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetFriction +================ +*/ +float idAFConstraint_UniversalJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_UniversalJoint::Evaluate + + NOTE: this joint is homokinetic +================ +*/ +void idAFConstraint_UniversalJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = s1.Cross( axis1 * body1->GetWorldAxis() ); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( s1[0], s1[1], s1[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 4, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( s2[0], s2[1], s2[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 4, 6 ); + } + else { + J2.Zero( 4, 6 ); + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( d1 * d2 ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_UniversalJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetForce +================ +*/ +void idAFConstraint_UniversalJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_UniversalJoint::Translate +================ +*/ +void idAFConstraint_UniversalJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::Rotate +================ +*/ +void idAFConstraint_UniversalJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + shaft2 *= rotation.ToMat3(); + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetCenter +================ +*/ +void idAFConstraint_UniversalJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_UniversalJoint::DebugDraw +================ +*/ +void idAFConstraint_UniversalJoint::DebugDraw( void ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = axis1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + s1 * 5.0f, 1.0f ); + gameRenderWorld->DebugArrow( colorBlue, a2, a2 + s2 * 5.0f, 1.0f ); + gameRenderWorld->DebugLine( colorGreen, a1, a1 + d1 * 5.0f ); + gameRenderWorld->DebugLine( colorGreen, a2, a2 + d2 * 5.0f ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_UniversalJoint::Save +================ +*/ +void idAFConstraint_UniversalJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( shaft1 ); + saveFile->WriteVec3( shaft2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteFloat( friction ); + + // cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_UniversalJointFriction *fc; +} + +/* +================ +idAFConstraint_UniversalJoint::Restore +================ +*/ +void idAFConstraint_UniversalJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( shaft1 ); + saveFile->ReadVec3( shaft2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadFloat( friction ); + +// cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_UniversalJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_UniversalJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction +================ +*/ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "universalJointFriction"; + InitSize( 2 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Setup +================ +*/ +void idAFConstraint_UniversalJointFriction::Setup( idAFConstraint_UniversalJoint *uj ) { + this->joint = uj; + body1 = uj->GetBody1(); + body2 = uj->GetBody2(); +} + +/* +================ +idAFConstraint_UniversalJointFriction::Evaluate +================ +*/ +void idAFConstraint_UniversalJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::Add +================ +*/ +bool idAFConstraint_UniversalJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 s1, s2, dir1, dir2; + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = -f; + hi[0] = hi[1] = f; + + joint->GetShafts( s1, s2 ); + + s1 *= body1->GetWorldAxis(); + s1.NormalVectors( dir1, dir2 ); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = dir1; + J1.SubVec6(1).SubVec3(0).Zero(); + J1.SubVec6(1).SubVec3(1) = dir2; + + if ( body2 ) { + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -dir1; + J2.SubVec6(1).SubVec3(0).Zero(); + J2.SubVec6(1).SubVec3(1) = -dir2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Translate +================ +*/ +void idAFConstraint_UniversalJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_UniversalJointFriction::Rotate +================ +*/ +void idAFConstraint_UniversalJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_CylindricalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint +================ +*/ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Evaluate +================ +*/ +void idAFConstraint_CylindricalJoint::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::ApplyFriction +================ +*/ +void idAFConstraint_CylindricalJoint::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Translate +================ +*/ +void idAFConstraint_CylindricalJoint::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Rotate +================ +*/ +void idAFConstraint_CylindricalJoint::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::DebugDraw +================ +*/ +void idAFConstraint_CylindricalJoint::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Hinge +// +//=============================================================== + +/* +================ +idAFConstraint_Hinge::idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_HINGE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + coneLimit = NULL; + steering = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; + initialAxis = body1->GetWorldAxis(); + if ( body2 ) { + initialAxis *= body2->GetWorldAxis().Transpose(); + } +} + +/* +================ +idAFConstraint_Hinge::~idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::~idAFConstraint_Hinge( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( fc ) { + delete fc; + } + if ( steering ) { + delete steering; + } +} + +/* +================ +idAFConstraint_Hinge::SetAnchor +================ +*/ +void idAFConstraint_Hinge::SetAnchor( const idVec3 &worldPosition ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_Hinge::GetAnchor +================ +*/ +idVec3 idAFConstraint_Hinge::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_Hinge::SetAxis +================ +*/ +void idAFConstraint_Hinge::SetAxis( const idVec3 &axis ) { + idVec3 normAxis; + + normAxis = axis; + normAxis.Normalize(); + + // get axis relative to body1 + axis1 = normAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get axis relative to body2 + axis2 = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = normAxis; + } +} + +/* +================ +idAFConstraint_Hinge::GetAxis +================ +*/ +idVec3 idAFConstraint_Hinge::GetAxis( void ) const { + if ( body2 ) { + return axis2 * body2->GetWorldAxis(); + } + return axis2; +} + +/* +================ +idAFConstraint_Hinge::SetNoLimit +================ +*/ +void idAFConstraint_Hinge::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } +} + +/* +================ +idAFConstraint_Hinge::SetLimit +================ +*/ +void idAFConstraint_Hinge::SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, axis * body2->GetWorldAxis().Transpose(), angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, axis, angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_Hinge::SetLimitEpsilon +================ +*/ +void idAFConstraint_Hinge::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_Hinge::GetFriction +================ +*/ +float idAFConstraint_Hinge::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_Hinge::GetAngle +================ +*/ +float idAFConstraint_Hinge::GetAngle( void ) const { + idMat3 axis; + idRotation rotation; + float angle; + + axis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose() * initialAxis.Transpose(); + rotation = axis.ToRotation(); + angle = rotation.GetAngle(); + if ( rotation.GetVec() * axis1 < 0.0f ) { + return -angle; + } + return angle; +} + +/* +================ +idAFConstraint_Hinge::SetSteerAngle +================ +*/ +void idAFConstraint_Hinge::SetSteerAngle( const float degrees ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !steering ) { + steering = new idAFConstraint_HingeSteering(); + steering->Setup( this ); + } + steering->SetSteerAngle( degrees ); +} + +/* +================ +idAFConstraint_Hinge::SetSteerSpeed +================ +*/ +void idAFConstraint_Hinge::SetSteerSpeed( const float speed ) { + if ( steering ) { + steering->SetSteerSpeed( speed ); + } +} + +/* +================ +idAFConstraint_Hinge::Evaluate +================ +*/ +void idAFConstraint_Hinge::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idVec3 x1, x2, cross; + idVec3 vecX, vecY; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + x1 = axis1 * body1->GetWorldAxis(); // axis in body1 space + x1.OrthogonalBasis( vecX, vecY ); // basis for axis in body1 space + + a1 = anchor1 * body1->GetWorldAxis(); // anchor in body1 space + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); // anchor in master space + x2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + x2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( vecX[0], vecX[1], vecX[2], + vecY[0], vecY[1], vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( -vecX[0], -vecX[1], -vecX[2], + -vecY[0], -vecY[1], -vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + cross = x1.Cross( x2 ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecX ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecY ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( steering ) { + steering->Add( physics, invTimeStep ); + } + else if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::ApplyFriction +================ +*/ +void idAFConstraint_Hinge::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::GetForce +================ +*/ +void idAFConstraint_Hinge::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_Hinge::Translate +================ +*/ +void idAFConstraint_Hinge::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_Hinge::Rotate +================ +*/ +void idAFConstraint_Hinge::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_Hinge::GetCenter +================ +*/ +void idAFConstraint_Hinge::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_Hinge::DebugDraw +================ +*/ +void idAFConstraint_Hinge::DebugDraw( void ) { + idVec3 vecX, vecY; + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + idVec3 x1 = axis1 * body1->GetWorldAxis(); + x1.OrthogonalBasis( vecX, vecY ); + + gameRenderWorld->DebugArrow( colorBlue, a1 - 4.0f * x1, a1 + 4.0f * x1, 1 ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecX, a1 + 2.0f * vecX ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecY, a1 + 2.0f * vecY ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_Hinge::Save +================ +*/ +void idAFConstraint_Hinge::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteMat3( initialAxis ); + saveFile->WriteFloat( friction ); + if ( coneLimit ) { + saveFile->WriteBool( true ); + coneLimit->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( steering ) { + saveFile->WriteBool( true ); + steering->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( fc ) { + saveFile->WriteBool( true ); + fc->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } +} + +/* +================ +idAFConstraint_Hinge::Restore +================ +*/ +void idAFConstraint_Hinge::Restore( idRestoreGame *saveFile ) { + bool b; + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadMat3( initialAxis ); + saveFile->ReadFloat( friction ); + + saveFile->ReadBool( b ); + if ( b ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + } + coneLimit->SetPhysics( physics ); + coneLimit->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !steering ) { + steering = new idAFConstraint_HingeSteering; + } + steering->Setup( this ); + steering->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + } + fc->Setup( this ); + fc->Restore( saveFile ); + } +} + + +//=============================================================== +// +// idAFConstraint_HingeFriction +// +//=============================================================== + +/* +================ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction +================ +*/ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_HingeFriction::Setup +================ +*/ +void idAFConstraint_HingeFriction::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeFriction::Evaluate +================ +*/ +void idAFConstraint_HingeFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::ApplyFriction +================ +*/ +void idAFConstraint_HingeFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::Add +================ +*/ +bool idAFConstraint_HingeFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 a1, a2; + float f; + + physics = phys; + + f = hinge->GetFriction() * hinge->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = -f; + hi[0] = f; + + hinge->GetAxis( a1, a2 ); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeFriction::Translate +================ +*/ +void idAFConstraint_HingeFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeFriction::Rotate +================ +*/ +void idAFConstraint_HingeFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_HingeSteering +// +//=============================================================== + +/* +================ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering +================ +*/ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering( void ) { + type = CONSTRAINT_HINGESTEERING; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; + steerSpeed = 0.0f; + epsilon = LCP_EPSILON; +} + +/* +================ +idAFConstraint_HingeSteering::Save +================ +*/ +void idAFConstraint_HingeSteering::Save( idSaveGame *saveFile ) const { + + saveFile->WriteFloat(steerAngle); + saveFile->WriteFloat(steerSpeed); + saveFile->WriteFloat(epsilon); + + //if ( hinge ) { // cnicholson: Added unsaved var (doesnt work) + // saveFile->WriteBool( true ); + // hinge->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Restore +================ +*/ +void idAFConstraint_HingeSteering::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadFloat(steerAngle); + saveFile->ReadFloat(steerSpeed); + saveFile->ReadFloat(epsilon); + +// cnicholson: Added unrestored var (doesnt work) + //bool b; + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !hinge ) { + // hinge = new idAFConstraint_Hinge; + // } + // hinge->SetSteerAngle( steerAngle ); + // hinge->SetSteerSpeed( steerSpeed ); + // hinge->SetLimitEpsilon( epsilon ); + // hinge->Restore( saveFile ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Setup +================ +*/ +void idAFConstraint_HingeSteering::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeSteering::Evaluate +================ +*/ +void idAFConstraint_HingeSteering::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::ApplyFriction +================ +*/ +void idAFConstraint_HingeSteering::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::Add +================ +*/ +bool idAFConstraint_HingeSteering::Add( idPhysics_AF *phys, float invTimeStep ) { + float angle, speed; + idVec3 a1, a2; + + physics = phys; + + hinge->GetAxis( a1, a2 ); + angle = hinge->GetAngle(); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + speed = steerAngle - angle; + if ( steerSpeed != 0.0f ) { + if ( speed > steerSpeed ) { + speed = steerSpeed; + } + else if ( speed < -steerSpeed ) { + speed = -steerSpeed; + } + } + + c1[0] = DEG2RAD( speed ) * invTimeStep; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeSteering::Translate +================ +*/ +void idAFConstraint_HingeSteering::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeSteering::Rotate +================ +*/ +void idAFConstraint_HingeSteering::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_Slider +// +//=============================================================== + +/* +================ +idAFConstraint_Slider::idAFConstraint_Slider +================ +*/ +idAFConstraint_Slider::idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SLIDER; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + fl.allowPrimary = true; + fl.noCollision = true; + + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Slider::SetAxis +================ +*/ +void idAFConstraint_Slider::SetAxis( const idVec3 &ax ) { + idVec3 normAxis; + + // get normalized axis relative to body1 + normAxis = ax; + normAxis.Normalize(); + if ( body2 ) { + axis = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis = normAxis; + } +} + +/* +================ +idAFConstraint_Slider::Evaluate +================ +*/ +void idAFConstraint_Slider::Evaluate( float invTimeStep ) { + idVec3 vecX, vecY, ofs; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + (axis * master->GetWorldAxis()).OrthogonalBasis( vecX, vecY ); + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * (relAxis * master->GetWorldAxis()) ).ToRotation(); + } + else { + axis.OrthogonalBasis( vecX, vecY ); + ofs = offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * relAxis ).ToRotation(); + } + + J1.Set( mat3_zero, mat3_identity, + idMat3( vecX, vecY, vec3_origin ), mat3_zero ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + + J2.Set( mat3_zero, -mat3_identity, + idMat3( -vecX, -vecY, vec3_origin ), mat3_zero ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * - (float) DEG2RAD( r.GetAngle() ) ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( vecX * ofs ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( vecY * ofs ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Slider::ApplyFriction +================ +*/ +void idAFConstraint_Slider::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Slider::Translate +================ +*/ +void idAFConstraint_Slider::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Slider::Rotate +================ +*/ +void idAFConstraint_Slider::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + } +} + +/* +================ +idAFConstraint_Slider::GetCenter +================ +*/ +void idAFConstraint_Slider::GetCenter( idVec3 ¢er ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + center = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + center = offset - body1->GetWorldOrigin(); + } +} + +/* +================ +idAFConstraint_Slider::DebugDraw +================ +*/ +void idAFConstraint_Slider::DebugDraw( void ) { + idVec3 ofs; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + ofs = offset - body1->GetWorldOrigin(); + } + gameRenderWorld->DebugLine( colorGreen, ofs, ofs + axis * body1->GetWorldAxis() ); +} + +/* +================ +idAFConstraint_Slider::Save +================ +*/ +void idAFConstraint_Slider::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( axis ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Slider::Restore +================ +*/ +void idAFConstraint_Slider::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( axis ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_Line +// +//=============================================================== + +/* +================ +idAFConstraint_Line::idAFConstraint_Line +================ +*/ +idAFConstraint_Line::idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Evaluate +================ +*/ +void idAFConstraint_Line::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::ApplyFriction +================ +*/ +void idAFConstraint_Line::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Translate +================ +*/ +void idAFConstraint_Line::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Rotate +================ +*/ +void idAFConstraint_Line::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::DebugDraw +================ +*/ +void idAFConstraint_Line::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Plane +// +//=============================================================== + +/* +================ +idAFConstraint_Plane::idAFConstraint_Plane +================ +*/ +idAFConstraint_Plane::idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_PLANE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_Plane::SetPlane +================ +*/ +void idAFConstraint_Plane::SetPlane( const idVec3 &normal, const idVec3 &anchor ) { + // get anchor relative to center of mass of body1 + anchor1 = ( anchor - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( anchor - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + planeNormal = normal * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = anchor; + planeNormal = normal; + } +} + +/* +================ +idAFConstraint_Plane::Evaluate +================ +*/ +void idAFConstraint_Plane::Evaluate( float invTimeStep ) { + idVec3 a1, a2, normal, p; + idVec6 v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + normal = planeNormal * master->GetWorldAxis(); + } + else { + a2 = anchor2; + normal = planeNormal; + } + + p = a1 - body1->GetWorldOrigin(); + v.SubVec3(0) = normal; + v.SubVec3(1) = p.Cross( normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + + if ( body2 ) { + p = a1 - body2->GetWorldOrigin(); + v.SubVec3(0) = -normal; + v.SubVec3(1) = p.Cross( -normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + } + + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * (a1 * normal - a2 * normal); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Plane::ApplyFriction +================ +*/ +void idAFConstraint_Plane::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Plane::Translate +================ +*/ +void idAFConstraint_Plane::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Plane::Rotate +================ +*/ +void idAFConstraint_Plane::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + planeNormal *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Plane::DebugDraw +================ +*/ +void idAFConstraint_Plane::DebugDraw( void ) { + idVec3 a1, normal, right, up; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + normal = planeNormal * master->GetWorldAxis(); + } + else { + normal = planeNormal; + } + normal.NormalVectors( right, up ); + normal *= 4.0f; + right *= 4.0f; + up *= 4.0f; + + gameRenderWorld->DebugLine( colorCyan, a1 - right, a1 + right ); + gameRenderWorld->DebugLine( colorCyan, a1 - up, a1 + up ); + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + normal, 1 ); +} + +/* +================ +idAFConstraint_Plane::Save +================ +*/ +void idAFConstraint_Plane::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( planeNormal ); +} + +/* +================ +idAFConstraint_Plane::Restore +================ +*/ +void idAFConstraint_Plane::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( planeNormal ); +} + + +//=============================================================== +// +// idAFConstraint_Spring +// +//=============================================================== + +/* +================ +idAFConstraint_Spring::idAFConstraint_Spring +================ +*/ +idAFConstraint_Spring::idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SPRING; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = false; + kstretch = kcompress = damping = 1.0f; + minLength = maxLength = restLength = 0.0f; +} + +/* +================ +idAFConstraint_Spring::SetAnchor +================ +*/ +void idAFConstraint_Spring::SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldAnchor1 - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldAnchor2 - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldAnchor2; + } +} + +/* +================ +idAFConstraint_Spring::SetSpring +================ +*/ +void idAFConstraint_Spring::SetSpring( const float stretch, const float compress, const float damping, const float restLength ) { + assert( stretch >= 0.0f && compress >= 0.0f && restLength >= 0.0f ); + this->kstretch = stretch; + this->kcompress = compress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idAFConstraint_Spring::SetLimit +================ +*/ +void idAFConstraint_Spring::SetLimit( const float minLength, const float maxLength ) { + assert( minLength >= 0.0f && maxLength >= 0.0f && maxLength >= minLength ); + this->minLength = minLength; + this->maxLength = maxLength; +} + +/* +================ +idAFConstraint_Spring::Evaluate +================ +*/ +void idAFConstraint_Spring::Evaluate( float invTimeStep ) { + idVec3 a1, a2, velocity1, velocity2, force; + idVec6 v1, v2; + float d, dampingForce, length, error; + bool limit; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + velocity1 = body1->GetPointVelocity( a1 ); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + velocity2 = master->GetPointVelocity( a2 ); + } + else { + a2 = anchor2; + velocity2.Zero(); + } + + force = a2 - a1; + d = force * force; + if ( d != 0.0f ) { + dampingForce = damping * idMath::Fabs( (velocity2 - velocity1) * force ) / d; + } + else { + dampingForce = 0.0f; + } + length = force.Normalize(); + + if ( length > restLength ) { + if ( kstretch > 0.0f ) { + idVec3 springForce = force * ( Square( length - restLength ) * kstretch - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + else { + if ( kcompress > 0.0f ) { + idVec3 springForce = force * -( Square( restLength - length ) * kcompress - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + + // check for spring limits + if ( length < minLength ) { + force = -force; + error = minLength - length; + limit = true; + } + else if ( maxLength > 0.0f && length > maxLength ) { + error = length - maxLength; + limit = true; + } + else { + error = 0.0f; + limit = false; + } + + if ( limit ) { + a1 -= body1->GetWorldOrigin(); + v1.SubVec3(0) = force; + v1.SubVec3(1) = a1.Cross( force ); + J1.Set( 1, 6, v1.ToFloatPtr() ); + if ( body2 ) { + a2 -= body2->GetWorldOrigin(); + v2.SubVec3(0) = -force; + v2.SubVec3(1) = a2.Cross( -force ); + J2.Set( 1, 6, v2.ToFloatPtr() ); + } + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * error; + lo[0] = 0.0f; + } + else { + J1.Zero( 0, 0 ); + J2.Zero( 0, 0 ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Spring::ApplyFriction +================ +*/ +void idAFConstraint_Spring::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Spring::Translate +================ +*/ +void idAFConstraint_Spring::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Spring::Rotate +================ +*/ +void idAFConstraint_Spring::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } +} + +/* +================ +idAFConstraint_Spring::GetCenter +================ +*/ +void idAFConstraint_Spring::GetCenter( idVec3 ¢er ) { + idAFBody *master; + idVec3 a1, a2; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + center = ( a1 + a2 ) * 0.5f; +} + +/* +================ +idAFConstraint_Spring::DebugDraw +================ +*/ +void idAFConstraint_Spring::DebugDraw( void ) { + idAFBody *master; + float length; + idVec3 a1, a2, dir, mid, p; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + dir = a2 - a1; + mid = a1 + 0.5f * dir; + length = dir.Normalize(); + + // draw spring + gameRenderWorld->DebugLine( colorGreen, a1, a2 ); + + // draw rest length + p = restLength * 0.5f * dir; + gameRenderWorld->DebugCircle( colorWhite, mid + p, dir, 1.0f, 10 ); + gameRenderWorld->DebugCircle( colorWhite, mid - p, dir, 1.0f, 10 ); + if ( restLength > length ) { + gameRenderWorld->DebugLine( colorWhite, a2, mid + p ); + gameRenderWorld->DebugLine( colorWhite, a1, mid - p ); + } + + if ( minLength > 0.0f ) { + // draw min length + gameRenderWorld->DebugCircle( colorBlue, mid + minLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorBlue, mid - minLength * 0.5f * dir, dir, 2.0f, 10 ); + } + + if ( maxLength > 0.0f ) { + // draw max length + gameRenderWorld->DebugCircle( colorRed, mid + maxLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorRed, mid - maxLength * 0.5f * dir, dir, 2.0f, 10 ); + } +} + +/* +================ +idAFConstraint_Spring::Save +================ +*/ +void idAFConstraint_Spring::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( kstretch ); + saveFile->WriteFloat( kcompress ); + saveFile->WriteFloat( damping ); + saveFile->WriteFloat( restLength ); + saveFile->WriteFloat( minLength ); + saveFile->WriteFloat( maxLength ); +} + +/* +================ +idAFConstraint_Spring::Restore +================ +*/ +void idAFConstraint_Spring::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( kstretch ); + saveFile->ReadFloat( kcompress ); + saveFile->ReadFloat( damping ); + saveFile->ReadFloat( restLength ); + saveFile->ReadFloat( minLength ); + saveFile->ReadFloat( maxLength ); +} + + +//=============================================================== +// +// idAFConstraint_Contact +// +//=============================================================== + +/* +================ +idAFConstraint_Contact::idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::idAFConstraint_Contact( void ) { + name = "contact"; + type = CONSTRAINT_CONTACT; + InitSize( 1 ); + fc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_Contact::~idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::~idAFConstraint_Contact( void ) { + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_Contact::Setup +================ +*/ +void idAFConstraint_Contact::Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ) { + idVec3 p; + idVec6 v; + float vel; + + assert( b1 ); + + body1 = b1; + body2 = b2; + contact = c; + + p = c.point - body1->GetWorldOrigin(); + v.SubVec3(0) = c.normal; + v.SubVec3(1) = p.Cross( c.normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + vel = v.SubVec3(0) * body1->GetLinearVelocity() + v.SubVec3(1) * body1->GetAngularVelocity(); + + if ( body2 ) { + p = c.point - body2->GetWorldOrigin(); + v.SubVec3(0) = -c.normal; + v.SubVec3(1) = p.Cross( -c.normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + vel += v.SubVec3(0) * body2->GetLinearVelocity() + v.SubVec3(1) * body2->GetAngularVelocity(); + c2[0] = 0.0f; + } + + if ( body1->GetBouncyness() > 0.0f ) { + c1[0] = body1->GetBouncyness() * -vel; + } + else { + c1[0] = 0.0f; + } + + e[0] = CONTACT_LCP_EPSILON; + lo[0] = 0.0f; + hi[0] = idMath::INFINITY; + boxConstraint = NULL; + boxIndex[0] = -1; +} + +/* +================ +idAFConstraint_Contact::Evaluate +================ +*/ +void idAFConstraint_Contact::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_Contact::ApplyFriction +================ +*/ +void idAFConstraint_Contact::ApplyFriction( float invTimeStep ) { + idVec3 r, velocity, normal, dir1, dir2; + float friction, magnitude, forceNumerator, forceDenominator; + idVecX impulse, dv; + + friction = body1->GetContactFriction(); + if ( body2 && body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + + friction *= physics->GetContactFrictionScale(); + + if ( friction <= 0.0f ) { + return; + } + + // seperate friction per contact is silly but it's fast and often looks close enough + if ( af_useImpulseFriction.GetBool() ) { + + impulse.SetData( 6, VECX_ALLOCA( 6 ) ); + dv.SetData( 6, VECX_ALLOCA( 6 ) ); + + // calculate velocity in the contact plane + r = contact.point - body1->GetWorldOrigin(); + velocity = body1->GetLinearVelocity() + body1->GetAngularVelocity().Cross( r ); + velocity -= contact.normal * velocity * contact.normal; + + // get normalized direction of friction and magnitude of velocity + normal = -velocity; + magnitude = normal.Normalize(); + + forceNumerator = friction * magnitude; + forceDenominator = body1->GetInverseMass() + ( ( body1->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse.SubVec3(0) = (forceNumerator / forceDenominator) * normal; + impulse.SubVec3(1) = r.Cross( impulse.SubVec3(0) ); + body1->InverseWorldSpatialInertiaMultiply( dv, impulse.ToFloatPtr() ); + + // modify velocity with friction force + body1->SetLinearVelocity( body1->GetLinearVelocity() + dv.SubVec3(0) ); + body1->SetAngularVelocity( body1->GetAngularVelocity() + dv.SubVec3(1) ); + } + else { + + if ( !fc ) { + fc = new idAFConstraint_ContactFriction; + } + // call setup each frame because contact constraints are re-used for different bodies + fc->Setup( this ); + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Contact::Translate +================ +*/ +void idAFConstraint_Contact::Translate( const idVec3 &translation ) { + assert( 0 ); // contact should never be translated +} + +/* +================ +idAFConstraint_Contact::Rotate +================ +*/ +void idAFConstraint_Contact::Rotate( const idRotation &rotation ) { + assert( 0 ); // contact should never be rotated +} + +/* +================ +idAFConstraint_Contact::GetCenter +================ +*/ +void idAFConstraint_Contact::GetCenter( idVec3 ¢er ) { + center = contact.point; +} + +/* +================ +idAFConstraint_Contact::DebugDraw +================ +*/ +void idAFConstraint_Contact::DebugDraw( void ) { + idVec3 x, y; + contact.normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contact.point, contact.point + 6.0f * contact.normal ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * x, contact.point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * y, contact.point + 2.0f * y ); +} + + +//=============================================================== +// +// idAFConstraint_ContactFriction +// +//=============================================================== + +/* +================ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction +================ +*/ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "contactFriction"; + InitSize( 2 ); + cc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ContactFriction::Setup +================ +*/ +void idAFConstraint_ContactFriction::Setup( idAFConstraint_Contact *cc ) { + this->cc = cc; + body1 = cc->GetBody1(); + body2 = cc->GetBody2(); +} + +/* +================ +idAFConstraint_ContactFriction::Evaluate +================ +*/ +void idAFConstraint_ContactFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::ApplyFriction +================ +*/ +void idAFConstraint_ContactFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::Add +================ +*/ +bool idAFConstraint_ContactFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 r, dir1, dir2; + float friction; + int newRow; + + physics = phys; + + friction = body1->GetContactFriction() * physics->GetContactFrictionScale(); + + // if the body only has friction in one direction + if ( body1->GetFrictionDirection( dir1 ) ) { + // project the friction direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + c1.SetSize( 1 ); + c1[0] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + c2.SetSize( 1 ); + c2[0] = 0.0f; + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + } + else { + // get two friction directions orthogonal to contact normal + cc->GetContact().normal.NormalVectors( dir1, dir2 ); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + J1.SubVec6(1).SubVec3(0) = dir2; + J1.SubVec6(1).SubVec3(1) = r.Cross( dir2 ); + c1.SetSize( 2 ); + c1[0] = c1[1] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + J2.SubVec6(1).SubVec3(0) = -dir2; + J2.SubVec6(1).SubVec3(1) = r.Cross( -dir2 ); + c2.SetSize( 2 ); + c2[0] = c2[1] = 0.0f; + + if ( body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + lo[1] = -friction; + hi[1] = friction; + boxIndex[1] = 0; + } + + if ( body1->GetContactMotorDirection( dir1 ) && body1->GetContactMotorForce() > 0.0f ) { + // project the motor force direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + newRow = J1.GetNumRows(); + J1.ChangeSize( newRow+1, J1.GetNumColumns() ); + J1.SubVec6(newRow).SubVec3(0) = -dir1; + J1.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c1.ChangeSize( newRow+1 ); + c1[newRow] = body1->GetContactMotorVelocity(); + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.ChangeSize( newRow+1, J2.GetNumColumns() ); + J2.SubVec6(newRow).SubVec3(0) = -dir1; + J2.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c2.ChangeSize( newRow+1 ); + c2[newRow] = 0.0f; + } + + lo[newRow] = -body1->GetContactMotorForce(); + hi[newRow] = body1->GetContactMotorForce(); + boxIndex[newRow] = -1; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ContactFriction::Translate +================ +*/ +void idAFConstraint_ContactFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_ContactFriction::Rotate +================ +*/ +void idAFConstraint_ContactFriction::Rotate( const idRotation &rotation ) { +} + +/* +================ +idAFConstraint_ContactFriction::DebugDraw +================ +*/ +void idAFConstraint_ContactFriction::DebugDraw( void ) { +} + + +//=============================================================== +// +// idAFConstraint_ConeLimit +// +//=============================================================== + +/* +================ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit +================ +*/ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit( void ) { + type = CONSTRAINT_CONELIMIT; + name = "coneLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ConeLimit::Setup + + the coneAnchor is the top of the cone in body2 space + the coneAxis is the axis of the cone in body2 space + the coneAngle is the angle the cone hull makes at the top + the body1Axis is the axis in body1 space that should stay within the cone +================ +*/ +void idAFConstraint_ConeLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + this->body1 = b1; + this->body2 = b2; + this->coneAxis = coneAxis; + this->coneAxis.Normalize(); + this->coneAnchor = coneAnchor; + this->body1Axis = body1Axis; + this->body1Axis.Normalize(); + this->cosAngle = idMath::Cos( DEG2RAD( coneAngle * 0.5f ) ); + this->sinHalfAngle = idMath::Sin( DEG2RAD( coneAngle * 0.25f ) ); + this->cosHalfAngle = idMath::Cos( DEG2RAD( coneAngle * 0.25f ) ); +} + +/* +================ +idAFConstraint_ConeLimit::SetAnchor +================ +*/ +void idAFConstraint_ConeLimit::SetAnchor( const idVec3 &coneAnchor ) { + this->coneAnchor = coneAnchor; +} + +/* +================ +idAFConstraint_ConeLimit::SetBody1Axis +================ +*/ +void idAFConstraint_ConeLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_ConeLimit::Evaluate +================ +*/ +void idAFConstraint_ConeLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ConeLimit::ApplyFriction +================ +*/ +void idAFConstraint_ConeLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_ConeLimit::Add +================ +*/ +bool idAFConstraint_ConeLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + float a; + idVec6 J1row, J2row; + idVec3 ax, anchor, body1ax, normal, coneVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + a = ax * body1ax; + + // if the body1 axis is inside the cone + if ( a > cosAngle ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward cone normal for the position the body1 axis went outside the cone + normal = body1ax.Cross( ax ); + normal.Normalize(); + q.x = normal.x * sinHalfAngle; + q.y = normal.y * sinHalfAngle; + q.z = normal.z * sinHalfAngle; + q.w = cosHalfAngle; + coneVector = ax * q.ToMat3(); + normal = coneVector.Cross( ax ).Cross( coneVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * coneVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * coneVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ConeLimit::Translate +================ +*/ +void idAFConstraint_ConeLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + coneAnchor += translation; + } +} + +/* +================ +idAFConstraint_ConeLimit::Rotate +================ +*/ +void idAFConstraint_ConeLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + coneAnchor *= rotation; + coneAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_ConeLimit::DebugDraw +================ +*/ +void idAFConstraint_ConeLimit::DebugDraw( void ) { + idVec3 ax, anchor, x, y, z, start, end; + float sinAngle, a, size = 10.0f; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw cone + ax.NormalVectors( x, y ); + sinAngle = idMath::Sqrt( 1.0f - cosAngle * cosAngle ); + x *= size * sinAngle; + y *= size * sinAngle; + z = anchor + ax * size * cosAngle; + start = x + z; + for ( a = 0.0f; a < 360.0f; a += 45.0f ) { + end = x * idMath::Cos( DEG2RAD(a + 45.0f) ) + y * idMath::Sin( DEG2RAD(a + 45.0f) ) + z; + gameRenderWorld->DebugLine( colorMagenta, anchor, start ); + gameRenderWorld->DebugLine( colorMagenta, start, end ); + start = end; + } +} + +/* +================ +idAFConstraint_ConeLimit::Save +================ +*/ +void idAFConstraint_ConeLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( coneAnchor ); + saveFile->WriteVec3( coneAxis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle ); + saveFile->WriteFloat( sinHalfAngle ); + saveFile->WriteFloat( cosHalfAngle ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_ConeLimit::Restore +================ +*/ +void idAFConstraint_ConeLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( coneAnchor ); + saveFile->ReadVec3( coneAxis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle ); + saveFile->ReadFloat( sinHalfAngle ); + saveFile->ReadFloat( cosHalfAngle ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFConstraint_PyramidLimit +// +//=============================================================== + +/* +================ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit +================ +*/ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit( void ) { + type = CONSTRAINT_PYRAMIDLIMIT; + name = "pyramidLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_PyramidLimit::Setup +================ +*/ +void idAFConstraint_PyramidLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ) { + body1 = b1; + body2 = b2; + // setup the base and make sure the basis is orthonormal + pyramidBasis[2] = pyramidAxis; + pyramidBasis[2].Normalize(); + pyramidBasis[0] = baseAxis; + pyramidBasis[0] -= pyramidBasis[2] * baseAxis * pyramidBasis[2]; + pyramidBasis[0].Normalize(); + pyramidBasis[1] = pyramidBasis[0].Cross( pyramidBasis[2] ); + // pyramid top + this->pyramidAnchor = pyramidAnchor; + // angles + cosAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.5f ) ); + cosAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.5f ) ); + sinHalfAngle[0] = idMath::Sin( DEG2RAD( pyramidAngle1 * 0.25f ) ); + sinHalfAngle[1] = idMath::Sin( DEG2RAD( pyramidAngle2 * 0.25f ) ); + cosHalfAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.25f ) ); + cosHalfAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.25f ) ); + + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::SetAnchor +================ +*/ +void idAFConstraint_PyramidLimit::SetAnchor( const idVec3 &pyramidAnchor ) { + this->pyramidAnchor = pyramidAnchor; +} + +/* +================ +idAFConstraint_PyramidLimit::SetBody1Axis +================ +*/ +void idAFConstraint_PyramidLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::Evaluate +================ +*/ +void idAFConstraint_PyramidLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_PyramidLimit::ApplyFriction +================ +*/ +void idAFConstraint_PyramidLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_PyramidLimit::Add +================ +*/ +bool idAFConstraint_PyramidLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + int i; + float a[2]; + idVec6 J1row, J2row; + idMat3 worldBase; + idVec3 anchor, body1ax, ax[2], v, normal, pyramidVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + for ( i = 0; i < 2; i++ ) { + ax[i] = body1ax - worldBase[!i] * body1ax * worldBase[!i]; + ax[i].Normalize(); + a[i] = worldBase[2] * ax[i]; + } + + // if the body1 axis is inside the pyramid + if ( a[0] > cosAngle[0] && a[1] > cosAngle[1] ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward pyramid normal for the position the body1 axis went outside the pyramid + pyramidVector = worldBase[2]; + for ( i = 0; i < 2; i++ ) { + if ( a[i] <= cosAngle[i] ) { + v = ax[i].Cross( worldBase[2] ); + v.Normalize(); + q.x = v.x * sinHalfAngle[i]; + q.y = v.y * sinHalfAngle[i]; + q.z = v.z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + pyramidVector *= q.ToMat3(); + } + } + normal = pyramidVector.Cross( worldBase[2] ).Cross( pyramidVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * pyramidVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * pyramidVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_PyramidLimit::Translate +================ +*/ +void idAFConstraint_PyramidLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + pyramidAnchor += translation; + } +} + +/* +================ +idAFConstraint_PyramidLimit::Rotate +================ +*/ +void idAFConstraint_PyramidLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + pyramidAnchor *= rotation; + pyramidBasis[0] *= rotation.ToMat3(); + pyramidBasis[1] *= rotation.ToMat3(); + pyramidBasis[2] *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_PyramidLimit::DebugDraw +================ +*/ +void idAFConstraint_PyramidLimit::DebugDraw( void ) { + int i; + float size = 10.0f; + idVec3 anchor, dir, p[4]; + idMat3 worldBase, m[2]; + idQuat q; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw the pyramid + for ( i = 0; i < 2; i++ ) { + q.x = worldBase[!i].x * sinHalfAngle[i]; + q.y = worldBase[!i].y * sinHalfAngle[i]; + q.z = worldBase[!i].z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + m[i] = q.ToMat3(); + } + + dir = worldBase[2] * size; + p[0] = anchor + m[0] * (m[1] * dir); + p[1] = anchor + m[0] * (m[1].Transpose() * dir); + p[2] = anchor + m[0].Transpose() * (m[1].Transpose() * dir); + p[3] = anchor + m[0].Transpose() * (m[1] * dir); + + for ( i = 0; i < 4; i++ ) { + gameRenderWorld->DebugLine( colorMagenta, anchor, p[i] ); + gameRenderWorld->DebugLine( colorMagenta, p[i], p[(i+1)&3] ); + } +} + +/* +================ +idAFConstraint_PyramidLimit::Save +================ +*/ +void idAFConstraint_PyramidLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( pyramidAnchor ); + saveFile->WriteMat3( pyramidBasis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle[0] ); + saveFile->WriteFloat( cosAngle[1] ); + saveFile->WriteFloat( sinHalfAngle[0] ); + saveFile->WriteFloat( sinHalfAngle[1] ); + saveFile->WriteFloat( cosHalfAngle[0] ); + saveFile->WriteFloat( cosHalfAngle[1] ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_PyramidLimit::Restore +================ +*/ +void idAFConstraint_PyramidLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( pyramidAnchor ); + saveFile->ReadMat3( pyramidBasis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle[0] ); + saveFile->ReadFloat( cosAngle[1] ); + saveFile->ReadFloat( sinHalfAngle[0] ); + saveFile->ReadFloat( sinHalfAngle[1] ); + saveFile->ReadFloat( cosHalfAngle[0] ); + saveFile->ReadFloat( cosHalfAngle[1] ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( void ) { + Init(); +} + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( const idStr &name, idClipModel *clipModel, float density ) { + + assert( clipModel ); + assert( clipModel->IsTraceModel() ); + + Init(); + + this->name = name; + this->clipModel = NULL; + + SetClipModel( clipModel ); + SetDensity( density ); + + current->worldOrigin = clipModel->GetOrigin(); + current->worldAxis = clipModel->GetAxis(); + *next = *current; + +} + +/* +================ +idAFBody::~idAFBody +================ +*/ +idAFBody::~idAFBody( void ) { + delete clipModel; +} + +/* +================ +idAFBody::Init +================ +*/ +void idAFBody::Init( void ) { + name = "noname"; + parent = NULL; + clipModel = NULL; + primaryConstraint = NULL; + tree = NULL; + + linearFriction = -1.0f; + angularFriction = -1.0f; + contactFriction = -1.0f; + bouncyness = -1.0f; + clipMask = 0; + + frictionDir = vec3_zero; + contactMotorDir = vec3_zero; + contactMotorVelocity = 0.0f; + contactMotorForce = 0.0f; + + mass = 1.0f; + invMass = 1.0f; + centerOfMass = vec3_zero; + inertiaTensor = mat3_identity; + inverseInertiaTensor = mat3_identity; + + current = &state[0]; + next = &state[1]; + current->worldOrigin = vec3_zero; + current->worldAxis = mat3_identity; + current->spatialVelocity = vec6_zero; + current->externalForce = vec6_zero; + *next = *current; + saved = *current; + atRestOrigin = vec3_zero; + atRestAxis = mat3_identity; + + s.Zero( 6 ); + totalForce.Zero( 6 ); + auxForce.Zero( 6 ); + acceleration.Zero( 6 ); + + response = NULL; + responseIndex = NULL; + numResponses = 0; + maxAuxiliaryIndex = 0; + maxSubTreeAuxiliaryIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); + + fl.selfCollision = true; + fl.isZero = true; +} + +/* +================ +idAFBody::SetClipModel +================ +*/ +void idAFBody::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && this->clipModel != clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idAFBody::SetFriction +================ +*/ +void idAFBody::SetFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f ) { + gameLocal.Warning( "idAFBody::SetFriction: friction out of range, linear = %.1f, angular = %.1f, contact = %.1f", linear, angular, contact ); + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idAFBody::SetBouncyness +================ +*/ +void idAFBody::SetBouncyness( float bounce ) { + if ( bounce < 0.0f || bounce > 1.0f ) { + gameLocal.Warning( "idAFBody::SetBouncyness: bouncyness out of range, bounce = %.1f", bounce ); + return; + } + bouncyness = bounce; +} + +/* +================ +idAFBody::SetDensity +================ +*/ +void idAFBody::SetDensity( float density, const idMat3 &inertiaScale ) { + + // get the body mass properties + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // make sure we have a valid mass + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idAFBody::SetDensity: invalid mass for body '%s'", name.c_str() ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // make sure the center of mass is at the body origin + if ( !centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + gameLocal.Warning( "idAFBody::SetDentity: center of mass not at origin for body '%s'", name.c_str() ); + } + centerOfMass.Zero(); + + // calculate the inverse mass and inverse inertia tensor + invMass = 1.0f / mass; + if ( inertiaScale != mat3_identity ) { + inertiaTensor *= inertiaScale; + } + if ( inertiaTensor.IsDiagonal( 1e-3f ) ) { + inertiaTensor[0][1] = inertiaTensor[0][2] = 0.0f; + inertiaTensor[1][0] = inertiaTensor[1][2] = 0.0f; + inertiaTensor[2][0] = inertiaTensor[2][1] = 0.0f; + inverseInertiaTensor.Identity(); + inverseInertiaTensor[0][0] = 1.0f / inertiaTensor[0][0]; + inverseInertiaTensor[1][1] = 1.0f / inertiaTensor[1][1]; + inverseInertiaTensor[2][2] = 1.0f / inertiaTensor[2][2]; + } + else { + inverseInertiaTensor = inertiaTensor.Inverse(); + } +} + +/* +================ +idAFBody::SetFrictionDirection +================ +*/ +void idAFBody::SetFrictionDirection( const idVec3 &dir ) { + frictionDir = dir * current->worldAxis.Transpose(); + fl.useFrictionDir = true; +} + +/* +================ +idAFBody::GetFrictionDirection +================ +*/ +bool idAFBody::GetFrictionDirection( idVec3 &dir ) const { + if ( fl.useFrictionDir ) { + dir = frictionDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::SetContactMotorDirection +================ +*/ +void idAFBody::SetContactMotorDirection( const idVec3 &dir ) { + contactMotorDir = dir * current->worldAxis.Transpose(); + fl.useContactMotorDir = true; +} + +/* +================ +idAFBody::GetContactMotorDirection +================ +*/ +bool idAFBody::GetContactMotorDirection( idVec3 &dir ) const { + if ( fl.useContactMotorDir ) { + dir = contactMotorDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::GetPointVelocity +================ +*/ +idVec3 idAFBody::GetPointVelocity( const idVec3 &point ) const { + idVec3 r = point - current->worldOrigin; + return current->spatialVelocity.SubVec3(0) + current->spatialVelocity.SubVec3(1).Cross( r ); +} + +/* +================ +idAFBody::AddForce +================ +*/ +void idAFBody::AddForce( const idVec3 &point, const idVec3 &force ) { + current->externalForce.SubVec3(0) += force; + current->externalForce.SubVec3(1) += (point - current->worldOrigin).Cross( force ); +} + +/* +================ +idAFBody::InverseWorldSpatialInertiaMultiply + + dst = this->inverseWorldSpatialInertia * v; +================ +*/ +ID_INLINE void idAFBody::InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const { + const float *mPtr = inverseWorldSpatialInertia.ToFloatPtr(); + const float *vPtr = v; + float *dstPtr = dst.ToFloatPtr(); + + if ( fl.spatialInertiaSparse ) { + dstPtr[0] = mPtr[0*6+0] * vPtr[0]; + dstPtr[1] = mPtr[1*6+1] * vPtr[1]; + dstPtr[2] = mPtr[2*6+2] * vPtr[2]; + dstPtr[3] = mPtr[3*6+3] * vPtr[3] + mPtr[3*6+4] * vPtr[4] + mPtr[3*6+5] * vPtr[5]; + dstPtr[4] = mPtr[4*6+3] * vPtr[3] + mPtr[4*6+4] * vPtr[4] + mPtr[4*6+5] * vPtr[5]; + dstPtr[5] = mPtr[5*6+3] * vPtr[3] + mPtr[5*6+4] * vPtr[4] + mPtr[5*6+5] * vPtr[5]; + } else { + gameLocal.Warning( "spatial inertia is not sparse for body %s", name.c_str() ); + } +} + +/* +================ +idAFBody::Save +================ +*/ +void idAFBody::Save( idSaveGame *saveFile ) { + + saveFile->WriteString( name ); // cniciholson: Added Unsaved Var + //if ( parent ) { + // saveFile->WriteBool( true ); + // parent->Save( savefile ); // cniciholson: Added Unsaved Var + //} + //else { + // saveFile->WriteBool( false ); + //} + // TOSAVE: idList children + // TOSAVE: idClipModel * clipModel + // TOSAVE: idAFConstraint * primaryConstraint + // TOSAVE: idList constraints + // TOSAVE: idAFTree * tree; + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteInt( clipMask ); + saveFile->WriteVec3( frictionDir ); + saveFile->WriteVec3( contactMotorDir ); + saveFile->WriteFloat( contactMotorVelocity ); + saveFile->WriteFloat( contactMotorForce ); + + saveFile->WriteFloat( mass ); + saveFile->WriteFloat( invMass ); + saveFile->WriteVec3( centerOfMass ); + saveFile->WriteMat3( inertiaTensor ); + saveFile->WriteMat3( inverseInertiaTensor ); + + //saveFile->WriteVec3( state[0].worldOrigin ); // cnicholson: Added unsaved var state[0] + //saveFile->WriteMat3( state[0].worldAxis ); + //saveFile->WriteVec6( state[0].spatialVelocity ); + //saveFile->WriteVec6( state[0].externalForce ); + // + //saveFile->WriteVec3( state[1].worldOrigin ); // cnicholson: Added unsaved var state[1] + //saveFile->WriteMat3( state[1].worldAxis ); + //saveFile->WriteVec6( state[1].spatialVelocity ); + //saveFile->WriteVec6( state[1].externalForce ); + + saveFile->WriteVec3( current->worldOrigin ); + saveFile->WriteMat3( current->worldAxis ); + saveFile->WriteVec6( current->spatialVelocity ); + saveFile->WriteVec6( current->externalForce ); + + //saveFile->WriteVec3( next->worldOrigin ); // cnicholson: Added unsaved var next-> + //saveFile->WriteMat3( next->worldAxis ); + //saveFile->WriteVec6( next->spatialVelocity ); + //saveFile->WriteVec6( next->externalForce ); + // + //saveFile->WriteVec3( saved.worldOrigin ); // cnicholson: Added unsaved var saved + //saveFile->WriteMat3( saved.worldAxis ); + //saveFile->WriteVec6( saved.spatialVelocity ); + //saveFile->WriteVec6( saved.externalForce ); + + saveFile->WriteVec3( atRestOrigin ); + saveFile->WriteMat3( atRestAxis ); + + // TOSAVE: idMatX inverseWorldSpatialInerti + // TOSAVE: idMatX I, invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX totalForce; + // TOSAVE: idVecX auxForce; + // TOSAVE: idVecX acceleration; + // TOSAVE: float * response; + // TOSAVE: int * responseIndex; + saveFile->WriteInt( numResponses ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxAuxiliaryIndex ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ) ); // cnicholson: Added unsaved var +} + +/* +================ +idAFBody::Restore +================ +*/ +void idAFBody::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadString( name ); // cniciholson: Added Unrestored Var + // TORESTORE: idList parent + // TORESTORE: idList children + // TORESTORE: idClipModel * clipModel + // TORESTORE: idAFConstraint * primaryConstraint + // TORESTORE: idList constraints + // TORESTORE: idAFTree * tree; + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadInt( clipMask ); + saveFile->ReadVec3( frictionDir ); + saveFile->ReadVec3( contactMotorDir ); + saveFile->ReadFloat( contactMotorVelocity ); + saveFile->ReadFloat( contactMotorForce ); + + saveFile->ReadFloat( mass ); + saveFile->ReadFloat( invMass ); + saveFile->ReadVec3( centerOfMass ); + saveFile->ReadMat3( inertiaTensor ); + saveFile->ReadMat3( inverseInertiaTensor ); + + //saveFile->ReadVec3( state[0].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[0].worldAxis ); + //saveFile->ReadVec6( state[0].spatialVelocity ); + //saveFile->ReadVec6( state[0].externalForce ); + + //saveFile->ReadVec3( state[1].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[1].worldAxis ); + //saveFile->ReadVec6( state[1].spatialVelocity ); + //saveFile->ReadVec6( state[1].externalForce ); + + saveFile->ReadVec3( current->worldOrigin ); + saveFile->ReadMat3( current->worldAxis ); + saveFile->ReadVec6( current->spatialVelocity ); + saveFile->ReadVec6( current->externalForce ); + + //saveFile->ReadVec3( next->worldOrigin ); // cnicholson: Added unrestored var next-> + //saveFile->ReadMat3( next->worldAxis ); + //saveFile->ReadVec6( next->spatialVelocity ); + //saveFile->ReadVec6( next->externalForce ); + + //saveFile->ReadVec3( saved.worldOrigin ); // cnicholson: Added unrestored var saved + //saveFile->ReadMat3( saved.worldAxis ); + //saveFile->ReadVec6( saved.spatialVelocity ); + //saveFile->ReadVec6( saved.externalForce ); + + saveFile->ReadVec3( atRestOrigin ); + saveFile->ReadMat3( atRestAxis ); + + // TORESTORE: idMatX inverseWorldSpatialInerti + // TORESTORE: idMatX I, invI; + // TORESTORE: idMatX J; + // TORESTORE: idVecX s; + // TORESTORE: idVecX totalForce; + // TORESTORE: idVecX auxForce; + // TORESTORE: idVecX acceleration; + // TORESTORE: float * response; + // TORESTORE: int * responseIndex; + saveFile->ReadInt( numResponses ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxAuxiliaryIndex ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unrestored var + + saveFile->Read( &fl, sizeof( fl ) ); // cnicholson: Added unrestored var +} + + +//=============================================================== +// M +// idAFTree MrE +// E +//=============================================================== + +/* +================ +idAFTree::Factor + + factor matrix for the primary constraints in the tree +================ +*/ +void idAFTree::Factor( void ) const { + int i, j; + idAFBody *body; + idAFConstraint *child; + idMatX childI; + + childI.SetData( 6, 6, MATX_ALLOCA( 6 * 6 ) ); + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + if ( body->children.Num() ) { + +// RAVEN BEGIN +// jscott: fixed warning + child = NULL; +// RAVEN END + + for ( j = 0; j < body->children.Num(); j++ ) { + + child = body->children[j]->primaryConstraint; + + // child->I = - child->body1->J.Transpose() * child->body1->I * child->body1->J; + childI.SetSize( child->J1.GetNumRows(), child->J1.GetNumRows() ); + child->body1->J.TransposeMultiply( child->body1->I ).Multiply( childI, child->body1->J ); + childI.Negate(); + + child->invI = childI; + if ( !child->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for constraint '%s'", + child->invI.GetNumRows(), child->invI.GetNumColumns(), child->GetName().c_str() ); + } + child->J = child->invI * child->J; + + body->I -= child->J.TransposeMultiply( childI ) * child->J; + } + + body->invI = body->I; + if ( !body->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for body %s", + child->invI.GetNumRows(), child->invI.GetNumColumns(), body->GetName().c_str() ); + } + if ( body->primaryConstraint ) { + body->J = body->invI * body->J; + } + } + else if ( body->primaryConstraint ) { + body->J = body->inverseWorldSpatialInertia * body->J; + } + } +} + +/* +================ +idAFTree::Solve + + solve for primary constraints in the tree +================ +*/ +void idAFTree::Solve( int auxiliaryIndex ) const { + int i, j; + idAFBody *body, *child; + idAFConstraint *primaryConstraint; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + primaryConstraint = child->primaryConstraint; + + if ( !child->fl.isZero ) { + child->J.TransposeMultiplySub( primaryConstraint->s, child->s ); + primaryConstraint->fl.isZero = false; + } + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->J.TransposeMultiplySub( body->s, primaryConstraint->s ); + body->fl.isZero = false; + } + } + } + + bool useSymmetry = af_useSymmetry.GetBool(); + + // from the root down towards the leaves + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + + if ( primaryConstraint ) { + + if ( useSymmetry && body->parent->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->s = primaryConstraint->invI * primaryConstraint->s; + } + primaryConstraint->J.MultiplySub( primaryConstraint->s, primaryConstraint->body2->s ); + + primaryConstraint->lm = primaryConstraint->s; + + if ( useSymmetry && body->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( body->children.Num() ) { + if ( !body->fl.isZero ) { + body->s = body->invI * body->s; + } + body->J.MultiplySub( body->s, primaryConstraint->s ); + } + } else if ( body->children.Num() ) { + body->s = body->invI * body->s; + } + } +} + +/* +================ +idAFTree::Response + + calculate body forces in the tree in response to a constraint force +================ +*/ +void idAFTree::Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const { + int i, j; + idAFBody *body; + idAFConstraint *child, *primaryConstraint; + idVecX v; + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + body = constraint->body1; + if ( body->tree == this ) { + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + else { + body = constraint->body2; + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + return; + } + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + + // initialize right hand side to zero + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->s.Zero(); + primaryConstraint->fl.isZero = true; + } + body->s.Zero(); + body->fl.isZero = true; + body->GetResponseForce( body->numResponses ).Zero(); + } + + // set right hand side for first constrained body + body = constraint->body1; + if ( body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J1[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.Multiply( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.Multiply( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + } + + // set right hand side for second constrained body + body = constraint->body2; + if ( body && body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J2[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.MultiplyAdd( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.MultiplyAdd( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + } + + + // solve for primary constraints + Solve( auxiliaryIndex ); + + bool useSymmetry = af_useSymmetry.GetBool(); + + // store body forces in response to the constraint force + idVecX force; + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + if ( useSymmetry && body->maxAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + force.SetData( 6, body->response + body->numResponses * 8 ); + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( force, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( force, child->lm ); + } + + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } +} + +/* +================ +idAFTree::CalculateForces + + calculate forces on the bodies in the tree +================ +*/ +void idAFTree::CalculateForces( float timeStep ) const { + int i, j; + float invStep; + idAFBody *body; + idAFConstraint *child, *c, *primaryConstraint; + + // forces on bodies + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->totalForce.SubVec6(0) = body->current->externalForce + body->auxForce.SubVec6(0); + } + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + return; + } + + invStep = 1.0f / timeStep; + + // initialize right hand side + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + // b = ( J * acc + c ) + c = primaryConstraint; + c->s = c->J1 * c->body1->acceleration + c->J2 * c->body2->acceleration + invStep * ( c->c1 + c->c2 ); + c->fl.isZero = false; + } + body->s.Zero(); + body->fl.isZero = true; + } + + // solve for primary constraints + Solve(); + + // calculate forces on bodies after applying primary constraints + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( body->totalForce, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( body->totalForce, child->lm ); + } + } +} + +/* +================ +idAFTree::SetMaxSubTreeAuxiliaryIndex +================ +*/ +void idAFTree::SetMaxSubTreeAuxiliaryIndex( void ) { + int i, j; + idAFBody *body, *child; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + body->maxSubTreeAuxiliaryIndex = body->maxAuxiliaryIndex; + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + if ( child->maxSubTreeAuxiliaryIndex > body->maxSubTreeAuxiliaryIndex ) { + body->maxSubTreeAuxiliaryIndex = child->maxSubTreeAuxiliaryIndex; + } + } + } +} + +/* +================ +idAFTree::SortBodies_r +================ +*/ +void idAFTree::SortBodies_r( idList&sortedList, idAFBody *body ) { + int i; + + for ( i = 0; i < body->children.Num(); i++ ) { + sortedList.Append( body->children[i] ); + } + for ( i = 0; i < body->children.Num(); i++ ) { + SortBodies_r( sortedList, body->children[i] ); + } +} + +/* +================ +idAFTree::SortBodies + + sort body list to make sure parents come first +================ +*/ +void idAFTree::SortBodies( void ) { + int i; + idAFBody *body; + + // find the root + for ( i = 0; i < sortedBodies.Num(); i++ ) { + if ( !sortedBodies[i]->parent ) { + break; + } + } + + if ( i >= sortedBodies.Num() ) { + gameLocal.Error( "Articulated figure tree has no root." ); + } + + body = sortedBodies[i]; + sortedBodies.Clear(); + sortedBodies.Append( body ); + SortBodies_r( sortedBodies, body ); +} + +/* +================ +idAFTree::DebugDraw +================ +*/ +void idAFTree::DebugDraw( const idVec4 &color ) const { + int i; + idAFBody *body; + + for ( i = 1; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + gameRenderWorld->DebugArrow( color, body->parent->current->worldOrigin, body->current->worldOrigin, 1 ); + } +} + + +//=============================================================== +// M +// idPhysics_AF MrE +// E +//=============================================================== + +/* +================ +idPhysics_AF::EvaluateConstraints +================ +*/ +void idPhysics_AF::EvaluateConstraints( float timeStep ) { + int i; + float invTimeStep; + idAFBody *body; + idAFConstraint *c; + + invTimeStep = 1.0f / timeStep; + + // setup the constraint equations for the current position and orientation of the bodies + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + c = primaryConstraints[i]; + c->Evaluate( invTimeStep ); + c->J = c->J2; + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->Evaluate( invTimeStep ); + } + + // add contact constraints to the list with frame constraints + for ( i = 0; i < contactConstraints.Num(); i++ ) { + AddFrameConstraint( contactConstraints[i] ); + } + + // setup body primary constraint matrix + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->primaryConstraint ) { + body->J = body->primaryConstraint->J1.Transpose(); + } + } +} + +/* +================ +idPhysics_AF::EvaluateBodies +================ +*/ +void idPhysics_AF::EvaluateBodies( float timeStep ) { + int i; + idAFBody *body; + idMat3 axis; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // we transpose the axis before using it because idMat3 is column-major + axis = body->current->worldAxis.Transpose(); + + // if the center of mass is at the body point of reference + if ( body->centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia.Set( body->invMass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inverseInertiaTensor * axis.Transpose() ); + + body->fl.spatialInertiaSparse = true; + } + else { + idMat3 massMoment = body->mass * SkewSymmetric( body->centerOfMass ); + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, massMoment, + massMoment.Transpose(), axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia = body->I.InverseFast(); + + body->fl.spatialInertiaSparse = false; + } + + // initialize auxiliary constraint force to zero + body->auxForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddFrameConstraints +================ +*/ +void idPhysics_AF::AddFrameConstraints( void ) { + int i; + + // add frame constraints to auxiliary constraints + for ( i = 0; i < frameConstraints.Num(); i++ ) { + auxiliaryConstraints.Append( frameConstraints[i] ); + } +} + +/* +================ +idPhysics_AF::RemoveFrameConstraints +================ +*/ +void idPhysics_AF::RemoveFrameConstraints( void ) { + // remove all the frame constraints from the auxiliary constraints + auxiliaryConstraints.SetNum( auxiliaryConstraints.Num() - frameConstraints.Num(), false ); + frameConstraints.SetNum( 0, false ); +} + +/* +================ +idPhysics_AF::ApplyFriction +================ +*/ +void idPhysics_AF::ApplyFriction( float timeStep, float endTimeMSec ) { + int i; + float invTimeStep; + + if ( af_skipFriction.GetBool() ) { + return; + } + + if ( jointFrictionDentStart < MS2SEC( endTimeMSec ) && jointFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( jointFrictionDentEnd - jointFrictionDentStart ) * 0.5f; + if ( jointFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + jointFrictionDentScale = 1.0f - ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart ) / halfTime; + } else { + jointFrictionDentScale = jointFrictionDent + ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart - halfTime ) / halfTime; + } + } else { + jointFrictionDentScale = 0.0f; + } + + if ( contactFrictionDentStart < MS2SEC( endTimeMSec ) && contactFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( contactFrictionDentEnd - contactFrictionDentStart ) * 0.5f; + if ( contactFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + contactFrictionDentScale = 1.0f - ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart ) / halfTime; + } else { + contactFrictionDentScale = contactFrictionDent + ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart - halfTime ) / halfTime; + } + } else { + contactFrictionDentScale = 0.0f; + } + + invTimeStep = 1.0f / timeStep; + + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + primaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < frameConstraints.Num(); i++ ) { + frameConstraints[i]->ApplyFriction( invTimeStep ); + } +} + +/* +================ +idPhysics_AF::PrimaryFactor +================ +*/ +void idPhysics_AF::PrimaryFactor( void ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->Factor(); + } +} + +/* +================ +idPhysics_AF::PrimaryForces +================ +*/ +void idPhysics_AF::PrimaryForces( float timeStep ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->CalculateForces( timeStep ); + } +} + +/* +================ +idPhysics_AF::AuxiliaryForces +================ +*/ +void idPhysics_AF::AuxiliaryForces( float timeStep ) { + int i, j, k, l, n, m, s, numAuxConstraints, *index, *boxIndex; + float *ptr, *j1, *j2, *dstPtr, *forcePtr; + float invStep, u; + idAFBody *body; + idAFConstraint *constraint; + idVecX tmp; + idMatX jmk; + idVecX rhs, w, lm, lo, hi; + + // get the number of one dimensional auxiliary constraints + for ( numAuxConstraints = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxConstraints += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + if ( numAuxConstraints == 0 ) { + return; + } + + // allocate memory to store the body response to auxiliary constraint forces + forcePtr = (float *) _alloca16( bodies.Num() * numAuxConstraints * 8 * sizeof( float ) ); + index = (int *) _alloca16( bodies.Num() * numAuxConstraints * sizeof( int ) ); + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = forcePtr; + body->responseIndex = index; + body->numResponses = 0; + body->maxAuxiliaryIndex = 0; + forcePtr += numAuxConstraints * 8; + index += numAuxConstraints; + } + + // set on each body the largest index of an auxiliary constraint constraining the body + if ( af_useSymmetry.GetBool() ) { + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + if ( k > constraint->body1->maxAuxiliaryIndex ) { + constraint->body1->maxAuxiliaryIndex = k; + } + if ( constraint->body2 && k > constraint->body2->maxAuxiliaryIndex ) { + constraint->body2->maxAuxiliaryIndex = k; + } + } + } + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SetMaxSubTreeAuxiliaryIndex(); + } + } + + // calculate forces of primary constraints in response to the auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + // calculate body forces in the tree in response to the constraint force + constraint->body1->tree->Response( constraint, j, k ); + // if there is a second body which is part of a different tree + if ( constraint->body2 && constraint->body2->tree != constraint->body1->tree ) { + // calculate body forces in the second tree in response to the constraint force + constraint->body2->tree->Response( constraint, j, k ); + } + } + } + + // NOTE: the rows are 16 byte padded + jmk.SetData( numAuxConstraints, ((numAuxConstraints+3)&~3), MATX_ALLOCA( numAuxConstraints * ((numAuxConstraints+3)&~3) ) ); + tmp.SetData( 6, VECX_ALLOCA( 6 ) ); + + // create constraint matrix for auxiliary constraints using a mass matrix adjusted for the primary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->body1->InverseWorldSpatialInertiaMultiply( tmp, constraint->J1[j] ); + j1 = tmp.ToFloatPtr(); + ptr = constraint->body1->response; + index = constraint->body1->responseIndex; + dstPtr = jmk[k]; + s = af_useSymmetry.GetBool() ? k + 1 : numAuxConstraints; + for ( l = n = 0, m = index[n]; n < constraint->body1->numResponses && m < s; n++, m = index[n] ) { + while( l < m ) { + dstPtr[l++] = 0.0f; + } + dstPtr[l++] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + ptr += 8; + } + + while( l < s ) { + dstPtr[l++] = 0.0f; + } + + if ( constraint->body2 ) { + constraint->body2->InverseWorldSpatialInertiaMultiply( tmp, constraint->J2[j] ); + j2 = tmp.ToFloatPtr(); + ptr = constraint->body2->response; + index = constraint->body2->responseIndex; + for ( n = 0, m = index[n]; n < constraint->body2->numResponses && m < s; n++, m = index[n] ) { + dstPtr[m] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + ptr += 8; + } + } + } + } + + if ( af_useSymmetry.GetBool() ) { + n = jmk.GetNumColumns(); + for ( i = 0; i < numAuxConstraints; i++ ) { + ptr = jmk.ToFloatPtr() + ( i + 1 ) * n + i; + dstPtr = jmk.ToFloatPtr() + i * n + i + 1; + for ( j = i+1; j < numAuxConstraints; j++ ) { + *dstPtr++ = *ptr; + ptr += n; + } + } + } + + invStep = 1.0f / timeStep; + + // calculate body acceleration + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + } + + rhs.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lo.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + hi.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lm.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + boxIndex = (int *) _alloca16( numAuxConstraints * sizeof( int ) ); + + // set first index for special box constrained variables + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->firstIndex = k; + k += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + // initialize right hand side and low and high bounds for auxiliary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + n = k; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + j1 = constraint->J1[j]; + ptr = constraint->body1->acceleration.ToFloatPtr(); + rhs[k] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + rhs[k] += constraint->c1[j] * invStep; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->acceleration.ToFloatPtr(); + rhs[k] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + rhs[k] += constraint->c2[j] * invStep; + } + + rhs[k] = -rhs[k]; + lo[k] = constraint->lo[j]; + hi[k] = constraint->hi[j]; + + if ( constraint->boxIndex[j] >= 0 ) { + if ( constraint->boxConstraint->fl.isPrimary ) { + gameLocal.Error( "cannot reference primary constraints for the box index" ); + } + boxIndex[k] = constraint->boxConstraint->firstIndex + constraint->boxIndex[j]; + } + else { + boxIndex[k] = -1; + } + jmk[k][k] += constraint->e[j] * invStep; + } + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Start(); + } +#endif + + // calculate lagrange multipliers for auxiliary constraints + if ( !lcp->Solve( jmk, lm, rhs, lo, hi, boxIndex ) ) { + return; // bad monkey! + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Stop(); + } +#endif + + // calculate auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->lm[j] = u = lm[k]; + + j1 = constraint->J1[j]; + ptr = constraint->body1->auxForce.ToFloatPtr(); + ptr[0] += j1[0] * u; ptr[1] += j1[1] * u; ptr[2] += j1[2] * u; + ptr[3] += j1[3] * u; ptr[4] += j1[4] * u; ptr[5] += j1[5] * u; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->auxForce.ToFloatPtr(); + ptr[0] += j2[0] * u; ptr[1] += j2[1] * u; ptr[2] += j2[2] * u; + ptr[3] += j2[3] * u; ptr[4] += j2[4] * u; ptr[5] += j2[5] * u; + } + } + } + + // recalculate primary constraint forces in response to auxiliary constraint forces + PrimaryForces( timeStep ); + + // clear pointers pointing to stack space so tools don't get confused + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = NULL; + body->responseIndex = NULL; + } +} + +/* +================ +idPhysics_AF::VerifyContactConstraints +================ +*/ +void idPhysics_AF::VerifyContactConstraints( void ) { +#if 0 + int i; + float impulseNumerator, impulseDenominator; + idVec3 r, velocity, normalVelocity, normal, impulse; + idAFBody *body; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + + r = contact.point - body->GetCenterOfMass(); + + // calculate velocity at contact point + velocity = body->GetLinearVelocity() + body->GetAngularVelocity().Cross( r ); + + // velocity along normal vector + normalVelocity = ( velocity * contact.normal ) * contact.normal; + + // if moving towards the surface at the contact point + if ( normalVelocity * contact.normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = body->GetInverseMass() + ( ( body->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal * 1.0001f; + + // apply impulse + body->SetLinearVelocity( body->GetLinearVelocity() + impulse ); + body->SetAngularVelocity( body->GetAngularVelocity() + r.Cross( impulse ) ); + } + } +#else + int i; + idAFBody *body; + idVec3 normal; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + normal = contactConstraints[i]->GetContact().normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + body = contactConstraints[i]->body2; + if ( !body ) { + continue; + } + normal = -normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + } +#endif +} + +/* +================ +idPhysics_AF::Evolve +================ +*/ +void idPhysics_AF::Evolve( float timeStep ) { + int i; + float angle; + idVec3 vec; + idAFBody *body; + idVec6 force; + idRotation rotation; + float vSqr, maxLinearVelocity, maxAngularVelocity; + + maxLinearVelocity = af_maxLinearVelocity.GetFloat() / timeStep; + maxAngularVelocity = af_maxAngularVelocity.GetFloat() / timeStep; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // calculate the spatial velocity for the next physics state + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->next->spatialVelocity = body->current->spatialVelocity + timeStep * body->acceleration.SubVec6(0); + + if ( maxLinearVelocity > 0.0f ) { + // cap the linear velocity + vSqr = body->next->spatialVelocity.SubVec3(0).LengthSqr(); + if ( vSqr > Square( maxLinearVelocity ) ) { + body->next->spatialVelocity.SubVec3(0) *= idMath::InvSqrt( vSqr ) * maxLinearVelocity; + } + } + + if ( maxAngularVelocity > 0.0f ) { + // cap the angular velocity + vSqr = body->next->spatialVelocity.SubVec3(1).LengthSqr(); + if ( vSqr > Square( maxAngularVelocity ) ) { + body->next->spatialVelocity.SubVec3(1) *= idMath::InvSqrt( vSqr ) * maxAngularVelocity; + } + } + } + + // make absolutely sure all contact constraints are satisfied + VerifyContactConstraints(); + + // calculate the position of the bodies for the next physics state + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // translate world origin + body->next->worldOrigin = body->current->worldOrigin + timeStep * body->next->spatialVelocity.SubVec3( 0 ); + + // convert angular velocity to a rotation matrix + vec = body->next->spatialVelocity.SubVec3( 1 ); + angle = -timeStep * (float) RAD2DEG( vec.Normalize() ); + rotation = idRotation( vec3_origin, vec, angle ); + rotation.Normalize180(); + + // rotate world axis + body->next->worldAxis = body->current->worldAxis * rotation.ToMat3(); + body->next->worldAxis.OrthoNormalizeSelf(); + + // linear and angular friction + body->next->spatialVelocity.SubVec3(0) -= body->linearFriction * body->next->spatialVelocity.SubVec3(0); + body->next->spatialVelocity.SubVec3(1) -= body->angularFriction * body->next->spatialVelocity.SubVec3(1); + } +} + +/* +================ +idPhysics_AF::CollisionImpulse + + apply impulse to the colliding bodies + the current state of the body should be set to the moment of impact + this is silly as it doesn't take the AF structure into account +================ +*/ +bool idPhysics_AF::CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ) { + idVec3 r, velocity, impulse; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator; + impactInfo_t info; + idEntity *ent; + + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent == self || !ent ) { + return false; + } + + // get info from other entity involved + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + // collision point relative to the body center of mass + r = collision.c.point - (body->current->worldOrigin + body->centerOfMass * body->current->worldAxis); + // the velocity at the collision point + velocity = body->current->spatialVelocity.SubVec3(0) + body->current->spatialVelocity.SubVec3(1).Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + // never stick + if ( velocity * collision.c.normal > 0.0f ) { + velocity = collision.c.normal; + } + inverseWorldInertiaTensor = body->current->worldAxis.Transpose() * body->inverseInertiaTensor * body->current->worldAxis; + impulseNumerator = -( 1.0f + body->bouncyness ) * ( velocity * collision.c.normal ); + impulseDenominator = body->invMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + + // callback to self to let the entity know about the impact + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_AF::ApplyCollisions +================ +*/ +bool idPhysics_AF::ApplyCollisions( float timeStep ) { + int i; + + for ( i = 0; i < collisions.Num(); i++ ) { + if ( CollisionImpulse( timeStep, collisions[i].body, collisions[i].trace ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_AF::SetupCollisionForBody +================ +*/ +idEntity *idPhysics_AF::SetupCollisionForBody( idAFBody *body ) const { + int i; + idAFBody *b; + idEntity *passEntity; + + passEntity = NULL; + + if ( !selfCollision || !body->fl.selfCollision || af_skipSelfCollision.GetBool() ) { + + // disable all bodies + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } + + // don't collide with world collision model if attached to the world + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } + } + + } else { + + // enable all bodies that have self collision + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->fl.selfCollision ) { + bodies[i]->clipModel->Enable(); + } else { + bodies[i]->clipModel->Disable(); + } + } + + // don't let the body collide with itself + body->clipModel->Disable(); + + // disable any bodies attached with constraints + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } else { + if ( body->constraints[i]->body1 == body ) { + b = body->constraints[i]->body2; + } else if ( body->constraints[i]->body2 == body ) { + b = body->constraints[i]->body1; + } else { + continue; + } + // don't collide with this body + b->clipModel->Disable(); + } + } + } + + return passEntity; +} + +/* +================ +idPhysics_AF::CheckForCollisions + + check for collisions between the current and next state + if there is a collision the next state is set to the state at the moment of impact + assumes all bodies are linked for collision detection and relinks all bodies after moving them +================ +*/ +void idPhysics_AF::CheckForCollisions( float timeStep ) { +// #define TEST_COLLISION_DETECTION + int i, index; + idAFBody *body; + idMat3 axis; + idRotation rotation; + trace_t collision; + idEntity *passEntity; + + // clear list with collisions + collisions.SetNum( 0, false ); + + if ( !enableCollision ) { + return; + } + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask != 0 && !fastEval ) { +// RAVEN END + + passEntity = SetupCollisionForBody( body ); + +#ifdef TEST_COLLISION_DETECTION + bool startsolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->current->worldOrigin, body->clipModel, + body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( body->current->worldAxis, body->next->worldAxis, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( body->current->worldOrigin ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, body->current->worldOrigin, body->next->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + body->next->worldOrigin = collision.endpos; + body->next->worldAxis = collision.endAxis; + + // add collision to the list + index = collisions.Num(); + collisions.SetNum( index + 1, false ); + collisions[index].trace = collision; + collisions[index].body = body; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->next->worldOrigin, body->clipModel, + body->next->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->next->worldOrigin, body->next->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::EvaluateContacts +================ +*/ +bool idPhysics_AF::EvaluateContacts( void ) { + int i, j, k, numContacts, numBodyContacts; + idAFBody *body; + contactInfo_t contactInfo[10]; + idEntity *passEntity; + idVecX dir( 6, VECX_ALLOCA( 6 ) ); + + // evaluate bodies + EvaluateBodies( current.lastTimeStep ); + + // remove all existing contacts + ClearContacts(); + + contactBodies.SetNum( 0, false ); + + if ( !enableCollision ) { + return false; + } + + // find all the contacts + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask == 0 || fastEval ) { +// RAVEN END + continue; + } + + passEntity = SetupCollisionForBody( body ); + + body->InverseWorldSpatialInertiaMultiply( dir, body->current->externalForce.ToFloatPtr() ); + dir.SubVec6(0) = body->current->spatialVelocity + current.lastTimeStep * dir.SubVec6(0); + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numContacts = gameLocal.Contacts( self, contactInfo, 10, body->current->worldOrigin, dir.SubVec6(0), 2.0f, //CONTACT_EPSILON, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ); +// RAVEN END +#if 1 + // merge nearby contacts between the same bodies + // and assure there are at most three planar contacts between any pair of bodies + for ( j = 0; j < numContacts; j++ ) { + + numBodyContacts = 0; + for ( k = 0; k < contacts.Num(); k++ ) { + if ( contacts[k].entityNum == contactInfo[j].entityNum ) { + if ( ( contacts[k].id == i && contactInfo[j].id == contactBodies[k] ) || + ( contactBodies[k] == i && contacts[k].id == contactInfo[j].id ) ) { + + if ( ( contacts[k].point - contactInfo[j].point ).LengthSqr() < Square( 2.0f ) ) { + break; + } + if ( idMath::Fabs( contacts[k].normal * contactInfo[j].normal ) > 0.9f ) { + numBodyContacts++; + } + } + } + } + + if ( k >= contacts.Num() && numBodyContacts < 3 ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } + } + +#else + + for ( j = 0; j < numContacts; j++ ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } +#endif + + } + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_AF::SetupContactConstraints +================ +*/ +void idPhysics_AF::SetupContactConstraints( void ) { + int i; + + // make sure enough contact constraints are allocated + contactConstraints.AssureSizeAlloc( contacts.Num(), idListNewElement ); + contactConstraints.SetNum( contacts.Num(), false ); + + // setup contact constraints + for ( i = 0; i < contacts.Num(); i++ ) { + // add contact constraint + contactConstraints[i]->physics = this; + if ( contacts[i].entityNum == self->entityNumber ) { + contactConstraints[i]->Setup( bodies[contactBodies[i]], bodies[ contacts[i].id ], contacts[i] ); + } + else { + contactConstraints[i]->Setup( bodies[contactBodies[i]], NULL, contacts[i] ); + } + } +} + +/* +================ +idPhysics_AF::ApplyContactForces +================ +*/ +void idPhysics_AF::ApplyContactForces( void ) { +#if 0 + int i; + idEntity *ent; + idVec3 force; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + if ( contactConstraints[i]->body2 != NULL ) { + continue; + } + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + ent = gameLocal.entities[contact.entityNum]; + if ( !ent ) { + continue; + } + force.Zero(); + ent->AddForce( self, contact.id, contact.point, force ); + } +#endif +} + +/* +================ +idPhysics_AF::ClearExternalForce +================ +*/ +void idPhysics_AF::ClearExternalForce( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // clear external force + body->current->externalForce.Zero(); + body->next->externalForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddGravity +================ +*/ +void idPhysics_AF::AddGravity( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + // add gravitational force + body->current->externalForce.SubVec3( 0 ) += body->mass * gravityVector; + } +} + +/* +================ +idPhysics_AF::SwapStates +================ +*/ +void idPhysics_AF::SwapStates( void ) { + int i; + idAFBody *body; + AFBodyPState_t *swap; + + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + + // swap the current and next state for next simulation step + swap = body->current; + body->current = body->next; + body->next = swap; + } +} + +/* +================ +idPhysics_AF::UpdateClipModels +================ +*/ +void idPhysics_AF::UpdateClipModels( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->current->worldOrigin, body->current->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::SetSuspendSpeed +================ +*/ +void idPhysics_AF::SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ) { + this->suspendVelocity = velocity; + this->suspendAcceleration = acceleration; +} + +/* +================ +idPhysics_AF::SetSuspendTime +================ +*/ +void idPhysics_AF::SetSuspendTime( const float minTime, const float maxTime ) { + this->minMoveTime = minTime; + this->maxMoveTime = maxTime; +} + +/* +================ +idPhysics_AF::SetSuspendTolerance +================ +*/ +void idPhysics_AF::SetSuspendTolerance( const float noMoveTime, const float noMoveTranslation, const float noMoveRotation ) { + this->noMoveTime = noMoveTime; + this->noMoveTranslation = noMoveTranslation; + this->noMoveRotation = noMoveRotation; +} + +/* +================ +idPhysics_AF::SetTimeScaleRamp +================ +*/ +void idPhysics_AF::SetTimeScaleRamp( const float start, const float end ) { + timeScaleRampStart = start; + timeScaleRampEnd = end; +} + +/* +================ +idPhysics_AF::SetJointFrictionDent +================ +*/ +void idPhysics_AF::SetJointFrictionDent( const float dent, const float start, const float end ) { + jointFrictionDent = dent; + jointFrictionDentStart = start; + jointFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetJointFrictionScale +================ +*/ +float idPhysics_AF::GetJointFrictionScale( void ) const { + if ( jointFrictionDentScale > 0.0f ) { + return jointFrictionDentScale; + } else if ( jointFrictionScale > 0.0f ) { + return jointFrictionScale; + } else if ( af_jointFrictionScale.GetFloat() > 0.0f ) { + return af_jointFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::SetContactFrictionDent +================ +*/ +void idPhysics_AF::SetContactFrictionDent( const float dent, const float start, const float end ) { + contactFrictionDent = dent; + contactFrictionDentStart = start; + contactFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetContactFrictionScale +================ +*/ +float idPhysics_AF::GetContactFrictionScale( void ) const { + if ( contactFrictionDentScale > 0.0f ) { + return contactFrictionDentScale; + } else if ( contactFrictionScale > 0.0f ) { + return contactFrictionScale; + } else if ( af_contactFrictionScale.GetFloat() > 0.0f ) { + return af_contactFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::TestIfAtRest +================ +*/ +bool idPhysics_AF::TestIfAtRest( float timeStep ) { + int i; + float translationSqr, maxTranslationSqr, rotation, maxRotation; + idAFBody *body; + + if ( current.atRest >= 0 ) { + return true; + } + + current.activateTime += timeStep; + + // if the simulation should never be suspended before a certaint amount of time passed + if ( minMoveTime > 0.0f && current.activateTime < minMoveTime ) { + return false; + } + + // if the simulation should always be suspended after a certain amount time passed + if ( maxMoveTime > 0.0f && current.activateTime > maxMoveTime ) { + return true; + } + + // test if all bodies hardly moved over a period of time + if ( current.noMoveTime == 0.0f ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->atRestOrigin = body->current->worldOrigin; + body->atRestAxis = body->current->worldAxis; + } + current.noMoveTime += timeStep; + } + else if ( current.noMoveTime > noMoveTime ) { + current.noMoveTime = 0.0f; + maxTranslationSqr = 0.0f; + maxRotation = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + translationSqr = ( body->current->worldOrigin - body->atRestOrigin ).LengthSqr(); + if ( translationSqr > maxTranslationSqr ) { + maxTranslationSqr = translationSqr; + } + rotation = ( body->atRestAxis.Transpose() * body->current->worldAxis ).ToRotation().GetAngle(); + if ( rotation > maxRotation ) { + maxRotation = rotation; + } + } + + if ( maxTranslationSqr < Square( noMoveTranslation ) && maxRotation < noMoveRotation ) { + // hardly moved over a period of time so the articulated figure may come to rest + return true; + } + } else { + current.noMoveTime += timeStep; + } + + // test if the velocity or acceleration of any body is still too large to come to rest + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->current->spatialVelocity.SubVec3(0).LengthSqr() > Square( suspendVelocity[0] ) ) { + return false; + } + if ( body->current->spatialVelocity.SubVec3(1).LengthSqr() > Square( suspendVelocity[1] ) ) { + return false; + } + if ( body->acceleration.SubVec3(0).LengthSqr() > Square( suspendAcceleration[0] ) ) { + return false; + } + if ( body->acceleration.SubVec3(1).LengthSqr() > Square( suspendAcceleration[1] ) ) { + return false; + } + } + + // all bodies have a velocity and acceleration small enough to come to rest + return true; +} + +/* +================ +idPhysics_AF::Rest +================ +*/ +void idPhysics_AF::Rest( void ) { + int i; + + current.atRest = gameLocal.time; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity.Zero(); + bodies[i]->current->externalForce.Zero(); + } + + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::Activate +================ +*/ +void idPhysics_AF::Activate( void ) { + // if the articulated figure was at rest + if ( current.atRest >= 0 ) { + // normally gravity is added at the end of a simulation frame + // if the figure was at rest add gravity here so it is applied this simulation frame + AddGravity(); + // reset the active time for the max move time + current.activateTime = 0.0f; + } + current.atRest = -1; + current.noMoveTime = 0.0f; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_AF::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_AF::EnableImpact +================ +*/ +void idPhysics_AF::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_AF::DisableImpact +================ +*/ +void idPhysics_AF::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_AF::AddPushVelocity +================ +*/ +void idPhysics_AF::AddPushVelocity( const idVec6 &pushVelocity ) { + int i; + + if ( pushVelocity != vec6_origin ) { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity += pushVelocity; + } + } +} + +/* +================ +idPhysics_AF::SetClipModel +================ +*/ +void idPhysics_AF::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_AF::GetClipModel +================ +*/ +idClipModel *idPhysics_AF::GetClipModel( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel(); + } + return NULL; +} + +/* +================ +idPhysics_AF::GetNumClipModels +================ +*/ +int idPhysics_AF::GetNumClipModels( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::SetMass +================ +*/ +void idPhysics_AF::SetMass( float mass, int id ) { + if ( id >= 0 && id < bodies.Num() ) { + } + else { + forceTotalMass = mass; + } + SetChanged(); +} + +/* +================ +idPhysics_AF::GetMass +================ +*/ +float idPhysics_AF::GetMass( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->mass; + } + return totalMass; +} + +/* +================ +idPhysics_AF::SetContents +================ +*/ +void idPhysics_AF::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < bodies.Num() ) { + bodies[id]->GetClipModel()->SetContents( contents ); + } + else { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->GetClipModel()->SetContents( contents ); + } + } +} + +/* +================ +idPhysics_AF::GetContents +================ +*/ +int idPhysics_AF::GetContents( int id ) const { + int i, contents; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetContents(); + } + else { + contents = 0; + for ( i = 0; i < bodies.Num(); i++ ) { + contents |= bodies[i]->GetClipModel()->GetContents(); + } + return contents; + } +} + +/* +================ +idPhysics_AF::GetBounds +================ +*/ +const idBounds &idPhysics_AF::GetBounds( int id ) const { + int i; + static idBounds relBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetBounds(); + } + else if ( !bodies.Num() ) { + relBounds.Zero(); + return relBounds; + } + else { + relBounds = bodies[0]->GetClipModel()->GetBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + idBounds bounds; + idVec3 origin = ( bodies[i]->GetWorldOrigin() - bodies[0]->GetWorldOrigin() ) * bodies[0]->GetWorldAxis().Transpose(); + idMat3 axis = bodies[i]->GetWorldAxis() * bodies[0]->GetWorldAxis().Transpose(); + bounds.FromTransformedBounds( bodies[i]->GetClipModel()->GetBounds(), origin, axis ); + relBounds += bounds; + } + return relBounds; + } +} + +/* +================ +idPhysics_AF::GetAbsBounds +================ +*/ +const idBounds &idPhysics_AF::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetAbsBounds(); + } + else if ( !bodies.Num() ) { + absBounds.Zero(); + return absBounds; + } + else { + absBounds = bodies[0]->GetClipModel()->GetAbsBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + absBounds += bodies[i]->GetClipModel()->GetAbsBounds(); + } + return absBounds; + } +} + +/* +================ +idPhysics_AF::Evaluate +================ +*/ +bool idPhysics_AF::Evaluate( int timeStepMSec, int endTimeMSec ) { + float timeStep; + + if ( timeScaleRampStart < MS2SEC( endTimeMSec ) && timeScaleRampEnd > MS2SEC( endTimeMSec ) ) { + timeStep = MS2SEC( timeStepMSec ) * ( MS2SEC( endTimeMSec ) - timeScaleRampStart ) / ( timeScaleRampEnd - timeScaleRampStart ); + } else if ( af_timeScale.GetFloat() != 1.0f ) { + timeStep = MS2SEC( timeStepMSec ) * af_timeScale.GetFloat(); + } else { + timeStep = MS2SEC( timeStepMSec ) * timeScale; + } + current.lastTimeStep = timeStep; + + + // if the articulated figure changed + if ( changedAF || ( linearTime != af_useLinearTime.GetBool() ) ) { + BuildTrees(); + changedAF = false; + linearTime = af_useLinearTime.GetBool(); + } + + // get the new master position + if ( masterBody ) { + idVec3 masterOrigin; + idMat3 masterAxis; + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( current.atRest >= 0 && ( masterBody->current->worldOrigin != masterOrigin || masterBody->current->worldAxis != masterAxis ) ) { + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + + // if the simulation is suspended because the figure is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // move the af velocity into the frame of a pusher + AddPushVelocity( -current.pushVelocity ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Start(); + timer_collision.Start(); + } +#endif + + // evaluate contacts + EvaluateContacts(); + + // setup contact constraints + SetupContactConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // evaluate constraint equations + EvaluateConstraints( timeStep ); + + // apply friction + ApplyFriction( timeStep, endTimeMSec ); + + // add frame constraints + AddFrameConstraints(); + +#ifdef AF_TIMINGS + int numPrimary = 0, numAuxiliary = 0; + if ( af_showTimings.GetInteger() != 0 ) { + int i; + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + numPrimary += primaryConstraints[i]->J1.GetNumRows(); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxiliary += auxiliaryConstraints[i]->J1.GetNumRows(); + } + timer_pc.Start(); + } +#endif + + // factor matrices for primary constraints + PrimaryFactor(); + + // calculate forces on bodies after applying primary constraints + PrimaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_pc.Stop(); + timer_ac.Start(); + } +#endif + + // calculate and apply auxiliary constraint forces + AuxiliaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_ac.Stop(); + } +#endif + + // evolve current state to next state + Evolve( timeStep ); + + // debug graphics + DebugDraw(); + + // clear external forces on all bodies + ClearExternalForce(); + + // apply contact force to other entities + ApplyContactForces(); + + // remove all frame constraints + RemoveFrameConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions between current and next state + CheckForCollisions( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // swap the current and next state + SwapStates(); + + // make sure all clip models are disabled in case they were enabled for self collision + if ( selfCollision && !af_skipSelfCollision.GetBool() ) { + DisableClip(); + } + + // apply collision impulses + if ( ApplyCollisions( timeStep ) ) { + current.atRest = gameLocal.time; + comeToRest = true; + } + + // test if the simulation can be suspended because the whole figure is at rest + if ( comeToRest && TestIfAtRest( timeStep ) ) { + Rest(); + } else { + ActivateContactEntities(); + } + + // add gravitational force + AddGravity(); + + // move the af velocity back into the world frame + AddPushVelocity( current.pushVelocity ); + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { +// RAVEN BEGIN +// kfuller: warnings shouldn't crash the game + if ( bodies.Num() && bodies[0] && bodies[0]->current && self ) { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) ); + } else { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' -- no body", + self->name.c_str(), self->GetType()->classname ); + } +// RAVEN END + Rest(); + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Stop(); + + if ( af_showTimings.GetInteger() == 1 ) { + gameLocal.Printf( "%12s: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + else if ( af_showTimings.GetInteger() == 2 ) { + numArticulatedFigures++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "af %d: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + numArticulatedFigures, + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + } + + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numArticulatedFigures = 0; + timer_total.Clear(); + timer_pc.Clear(); + timer_ac.Clear(); + timer_collision.Clear(); + timer_lcp.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_AF::UpdateTime +================ +*/ +void idPhysics_AF::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_AF::GetTime +================ +*/ +int idPhysics_AF::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +DrawTraceModelSilhouette +================ +*/ +void DrawTraceModelSilhouette( const idVec3 &projectionOrigin, const idClipModel *clipModel ) { + int i, numSilEdges; + int silEdges[MAX_TRACEMODEL_EDGES]; + idVec3 v1, v2; + const idTraceModel *trm = clipModel->GetTraceModel(); + const idVec3 &origin = clipModel->GetOrigin(); + const idMat3 &axis = clipModel->GetAxis(); + + numSilEdges = trm->GetProjectionSilhouetteEdges( ( projectionOrigin - origin ) * axis.Transpose(), silEdges ); + for ( i = 0; i < numSilEdges; i++ ) { + v1 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITSET( silEdges[i] ) ] ]; + v2 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITNOTSET( silEdges[i] ) ] ]; + gameRenderWorld->DebugArrow( colorRed, origin + v1 * axis, origin + v2 * axis, 1 ); + } +} + +/* +================ +idPhysics_AF::DebugDraw +================ +*/ +void idPhysics_AF::DebugDraw( void ) { + int i; + idAFBody *body, *highlightBody = NULL, *constrainedBody1 = NULL, *constrainedBody2 = NULL; + idAFConstraint *constraint; + idVec3 center; + idMat3 axis; + + if ( af_highlightConstraint.GetString()[0] ) { + constraint = GetConstraint( af_highlightConstraint.GetString() ); + if ( constraint ) { + constraint->GetCenter( center ); + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DebugCone( colorYellow, center, (axis[2] - axis[1]) * 4.0f, 0.0f, 1.0f, 0 ); + + if ( af_showConstrainedBodies.GetBool() ) { + cvarSystem->SetCVarString( "cm_drawColor", colorCyan.ToString( 0 ) ); + constrainedBody1 = constraint->body1; + if ( constrainedBody1 ) { + collisionModelManager->DrawModel( constrainedBody1->clipModel->GetCollisionModel(), constrainedBody1->clipModel->GetOrigin(), + constrainedBody1->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorBlue.ToString( 0 ) ); + constrainedBody2 = constraint->body2; + if ( constrainedBody2 ) { + collisionModelManager->DrawModel( constrainedBody2->clipModel->GetCollisionModel(), constrainedBody2->clipModel->GetOrigin(), + constrainedBody2->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + } + + if ( af_highlightBody.GetString()[0] ) { + highlightBody = GetBody( af_highlightBody.GetString() ); + if ( highlightBody ) { + cvarSystem->SetCVarString( "cm_drawColor", colorYellow.ToString( 0 ) ); + collisionModelManager->DrawModel( highlightBody->clipModel->GetCollisionModel(), highlightBody->clipModel->GetOrigin(), + highlightBody->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + + if ( af_showBodies.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + if ( body == constrainedBody1 || body == constrainedBody2 ) { + continue; + } + if ( body == highlightBody ) { + continue; + } + collisionModelManager->DrawModel( body->clipModel->GetCollisionModel(), body->clipModel->GetOrigin(), + body->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + //DrawTraceModelSilhouette( gameLocal.GetLocalPlayer()->GetEyePosition(), body->clipModel ); + } + } + + if ( af_showBodyNames.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( body->GetName().c_str(), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showMass.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( va( "\n%1.2f", 1.0f / body->GetInverseMass() ), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showTotalMass.GetBool() ) { + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DrawText( va( "\n%1.2f", totalMass ), bodies[0]->GetWorldOrigin() + axis[2] * 8.0f, 0.15f, colorCyan, axis, 1 ); + } + + if ( af_showInertia.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + idMat3 &I = body->inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + body->GetWorldOrigin(), 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showVelocity.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + DrawVelocity( bodies[i]->clipModel->GetId(), 0.1f, 4.0f ); + } + } + + if ( af_showConstraints.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->DebugDraw(); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->DebugDraw(); + } + } + } + + if ( af_showConstraintNames.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + } + + if ( af_showTrees.GetBool() || ( af_showActive.GetBool() && current.atRest < 0 ) ) { + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->DebugDraw( idStr::ColorForIndex( i+3 ) ); + } + } +} + +/* +================ +idPhysics_AF::idPhysics_AF +================ +*/ +idPhysics_AF::idPhysics_AF( void ) { + trees.Clear(); + bodies.Clear(); + constraints.Clear(); + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + frameConstraints.Clear(); + contacts.Clear(); + collisions.Clear(); + changedAF = true; + masterBody = NULL; + + lcp = idLCP::AllocSymmetric(); + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + saved = current; + + linearFriction = 0.005f; + angularFriction = 0.005f; + contactFriction = 0.8f; + bouncyness = 0.4f; + totalMass = 0.0f; + forceTotalMass = -1.0f; + + suspendVelocity.Set( SUSPEND_LINEAR_VELOCITY, SUSPEND_ANGULAR_VELOCITY ); + suspendAcceleration.Set( SUSPEND_LINEAR_ACCELERATION, SUSPEND_LINEAR_ACCELERATION ); + noMoveTime = NO_MOVE_TIME; + noMoveTranslation = NO_MOVE_TRANSLATION_TOLERANCE; + noMoveRotation = NO_MOVE_ROTATION_TOLERANCE; + minMoveTime = MIN_MOVE_TIME; + maxMoveTime = MAX_MOVE_TIME; + impulseThreshold = IMPULSE_THRESHOLD; + + timeScale = 1.0f; + timeScaleRampStart = 0.0f; + timeScaleRampEnd = 0.0f; + + jointFrictionScale = 0.0f; + jointFrictionDent = 0.0f; + jointFrictionDentStart = 0.0f; + jointFrictionDentEnd = 0.0f; + jointFrictionDentScale = 0.0f; + + contactFrictionScale = 0.0f; + contactFrictionDent = 0.0f; + contactFrictionDentStart = 0.0f; + contactFrictionDentEnd = 0.0f; + contactFrictionDentScale = 0.0f; + + enableCollision = true; + selfCollision = true; + comeToRest = true; + linearTime = true; + noImpact = false; + worldConstraintsLocked = false; + forcePushable = false; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + fastEval = false; +// RAVEN END + +#ifdef AF_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_AF::~idPhysics_AF +================ +*/ +idPhysics_AF::~idPhysics_AF( void ) { + int i; + + trees.DeleteContents( true ); + + for ( i = 0; i < bodies.Num(); i++ ) { + delete bodies[i]; + } + + for ( i = 0; i < constraints.Num(); i++ ) { + delete constraints[i]; + } + + contactConstraints.SetNum( contactConstraints.NumAllocated(), false ); + for ( i = 0; i < contactConstraints.NumAllocated(); i++ ) { + delete contactConstraints[i]; + } + + delete lcp; + + if ( masterBody ) { + delete masterBody; + } +} + +/* +================ +idPhysics_AF_SavePState +================ +*/ +void idPhysics_AF_SavePState( idSaveGame *saveFile, const AFPState_t &state ) { + saveFile->WriteInt( state.atRest ); + saveFile->WriteFloat( state.noMoveTime ); + saveFile->WriteFloat( state.activateTime ); + saveFile->WriteFloat( state.lastTimeStep ); + saveFile->WriteVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF_RestorePState +================ +*/ +void idPhysics_AF_RestorePState( idRestoreGame *saveFile, AFPState_t &state ) { + saveFile->ReadInt( state.atRest ); + saveFile->ReadFloat( state.noMoveTime ); + saveFile->ReadFloat( state.activateTime ); + saveFile->ReadFloat( state.lastTimeStep ); + saveFile->ReadVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF::Save +================ +*/ +void idPhysics_AF::Save( idSaveGame *saveFile ) const { + int i; + + // the articulated figure structure is handled by the owner + + // TOSAVE: idList trees; + saveFile->WriteInt( bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Save( saveFile ); + } + if ( masterBody ) { + saveFile->WriteBool( true ); + masterBody->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + + saveFile->WriteInt( constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Save( saveFile ); + } + + // TOSAVE: idListprimaryConstraints; + // TOSAVE: idListauxiliaryConstraints; + // TOSAVE: idListframeConstraints; + // TOSAVE: idListcontactConstraints; + // TOSAVE: idList contactBodies; + // TOSAVE: idList collisions; + + saveFile->WriteBool( changedAF ); + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteFloat( totalMass ); + saveFile->WriteFloat( forceTotalMass ); + + saveFile->WriteVec2( suspendVelocity ); + saveFile->WriteVec2( suspendAcceleration ); + saveFile->WriteFloat( noMoveTime ); + saveFile->WriteFloat( noMoveTranslation ); + saveFile->WriteFloat( noMoveRotation ); + saveFile->WriteFloat( minMoveTime ); + saveFile->WriteFloat( maxMoveTime ); + saveFile->WriteFloat( impulseThreshold ); + + saveFile->WriteFloat( timeScale ); + saveFile->WriteFloat( timeScaleRampStart ); + saveFile->WriteFloat( timeScaleRampEnd ); + + saveFile->WriteFloat( jointFrictionScale ); + saveFile->WriteFloat( jointFrictionDent ); + saveFile->WriteFloat( jointFrictionDentStart ); + saveFile->WriteFloat( jointFrictionDentEnd ); + saveFile->WriteFloat( jointFrictionDentScale ); + + saveFile->WriteFloat( contactFrictionScale ); + saveFile->WriteFloat( contactFrictionDent ); + saveFile->WriteFloat( contactFrictionDentStart ); + saveFile->WriteFloat( contactFrictionDentEnd ); + saveFile->WriteFloat( contactFrictionDentScale ); + + saveFile->WriteBool( enableCollision ); + saveFile->WriteBool( selfCollision ); + saveFile->WriteBool( comeToRest ); + saveFile->WriteBool( linearTime ); + saveFile->WriteBool( noImpact ); + saveFile->WriteBool( worldConstraintsLocked ); + saveFile->WriteBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->WriteBool( fastEval ); +// RAVEN END + + idPhysics_AF_SavePState( saveFile, current ); + idPhysics_AF_SavePState( saveFile, saved ); + + // TOSAVE: idAFBody * masterBody; + // TOSAVE: idLCP * lcp; +} + +/* +================ +idPhysics_AF::Restore +================ +*/ +void idPhysics_AF::Restore( idRestoreGame *saveFile ) { + int i, num; + bool hasMaster; + + // the articulated figure structure should have already been restored + + saveFile->ReadInt( num ); + assert( num == bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Restore( saveFile ); + } + saveFile->ReadBool( hasMaster ); + if ( hasMaster ) { + masterBody = new idAFBody(); + masterBody->Restore( saveFile ); + } + + saveFile->ReadInt( num ); + assert( num == constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Restore( saveFile ); + } + + saveFile->ReadBool( changedAF ); + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadFloat( totalMass ); + saveFile->ReadFloat( forceTotalMass ); + + saveFile->ReadVec2( suspendVelocity ); + saveFile->ReadVec2( suspendAcceleration ); + saveFile->ReadFloat( noMoveTime ); + saveFile->ReadFloat( noMoveTranslation ); + saveFile->ReadFloat( noMoveRotation ); + saveFile->ReadFloat( minMoveTime ); + saveFile->ReadFloat( maxMoveTime ); + saveFile->ReadFloat( impulseThreshold ); + + saveFile->ReadFloat( timeScale ); + saveFile->ReadFloat( timeScaleRampStart ); + saveFile->ReadFloat( timeScaleRampEnd ); + + saveFile->ReadFloat( jointFrictionScale ); + saveFile->ReadFloat( jointFrictionDent ); + saveFile->ReadFloat( jointFrictionDentStart ); + saveFile->ReadFloat( jointFrictionDentEnd ); + saveFile->ReadFloat( jointFrictionDentScale ); + + saveFile->ReadFloat( contactFrictionScale ); + saveFile->ReadFloat( contactFrictionDent ); + saveFile->ReadFloat( contactFrictionDentStart ); + saveFile->ReadFloat( contactFrictionDentEnd ); + saveFile->ReadFloat( contactFrictionDentScale ); + + saveFile->ReadBool( enableCollision ); + saveFile->ReadBool( selfCollision ); + saveFile->ReadBool( comeToRest ); + saveFile->ReadBool( linearTime ); + saveFile->ReadBool( noImpact ); + saveFile->ReadBool( worldConstraintsLocked ); + saveFile->ReadBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->ReadBool( fastEval ); +// RAVEN END + + idPhysics_AF_RestorePState( saveFile, current ); + idPhysics_AF_RestorePState( saveFile, saved ); + + changedAF = true; + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::IsClosedLoop +================ +*/ +bool idPhysics_AF::IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const { + const idAFBody *b1, *b2; + + for ( b1 = body1; b1->parent; b1 = b1->parent ) { + } + for ( b2 = body2; b2->parent; b2 = b2->parent ) { + } + return ( b1 == b2 ); +} + +/* +================ +idPhysics_AF::BuildTrees +================ +*/ +void idPhysics_AF::BuildTrees( void ) { + int i; + float scale; + idAFBody *b; + idAFConstraint *c; + idAFTree *tree; + + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + trees.DeleteContents( true ); + + totalMass = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->parent = NULL; + b->primaryConstraint = NULL; + b->constraints.SetNum( 0, false ); + b->children.Clear(); + b->tree = NULL; + totalMass += b->mass; + } + + if ( forceTotalMass > 0.0f ) { + scale = forceTotalMass / totalMass; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->mass *= scale; + b->invMass = 1.0f / b->mass; + b->inertiaTensor *= scale; + b->inverseInertiaTensor = b->inertiaTensor.Inverse(); + } + totalMass = forceTotalMass; + } + + if ( af_useLinearTime.GetBool() ) { + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + // only bilateral constraints between two non-world bodies that do not + // create loops can be used as primary constraints + if ( !c->body1->primaryConstraint && c->fl.allowPrimary && c->body2 != NULL && !IsClosedLoop( c->body1, c->body2 ) ) { + c->body1->primaryConstraint = c; + c->body1->parent = c->body2; + c->body2->children.Append( c->body1 ); + c->fl.isPrimary = true; + c->firstIndex = 0; + primaryConstraints.Append( c ); + } else { + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } + + // create trees for all parent bodies + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->parent ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + } + + // add each child body to the appropriate tree + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->parent ) { + for ( b = bodies[i]->parent; !b->tree; b = b->parent ) { + } + b->tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = b->tree; + } + } + + if ( trees.Num() > 1 ) { + gameLocal.Warning( "Articulated figure has multiple seperate tree structures for entity '%s' type '%s'.", + self->name.c_str(), self->GetType()->classname ); + } + + // sort bodies in each tree to make sure parents come first + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SortBodies(); + } + + } else { + + // create a tree for each body + for ( i = 0; i < bodies.Num(); i++ ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } +} + +/* +================ +idPhysics_AF::AddBody + + bodies get an id in the order they are added starting at zero + as such the first body added will get id zero +================ +*/ +int idPhysics_AF::AddBody( idAFBody *body ) { + int id = 0; + + if ( !body->clipModel ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' has no clip model.", body->name.c_str() ); + } + + if ( bodies.Find( body ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' added twice.", body->name.c_str() ); + } + + if ( GetBody( body->name ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: a body with the name '%s' already exists.", body->name.c_str() ); + } + + id = bodies.Num(); + body->clipModel->SetId( id ); + if ( body->linearFriction < 0.0f ) { + body->linearFriction = linearFriction; + body->angularFriction = angularFriction; + body->contactFriction = contactFriction; + } + if ( body->bouncyness < 0.0f ) { + body->bouncyness = bouncyness; + } + if ( !body->fl.clipMaskSet ) { + body->clipMask = clipMask; + } + + bodies.Append( body ); + + changedAF = true; + + return id; +} + +/* +================ +idPhysics_AF::AddConstraint +================ +*/ +void idPhysics_AF::AddConstraint( idAFConstraint *constraint ) { + + if ( constraints.Find( constraint ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: constraint '%s' added twice.", constraint->name.c_str() ); + } + if ( GetConstraint( constraint->name ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: a constraint with the name '%s' already exists.", constraint->name.c_str() ); + } + if ( !constraint->body1 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 == NULL on constraint '%s'.", constraint->name.c_str() ); + } + if ( !bodies.Find( constraint->body1 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body2 && !bodies.Find( constraint->body2 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body2 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body1 == constraint->body2 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 and body2 of constraint '%s' are the same.", constraint->name.c_str() ); + } + + constraints.Append( constraint ); + constraint->physics = this; + + changedAF = true; +} + +/* +================ +idPhysics_AF::AddFrameConstraint +================ +*/ +void idPhysics_AF::AddFrameConstraint( idAFConstraint *constraint ) { + frameConstraints.Append( constraint ); + constraint->physics = this; +} + +/* +================ +idPhysics_AF::ForceBodyId +================ +*/ +void idPhysics_AF::ForceBodyId( idAFBody *body, int newId ) { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 ) { + gameLocal.Error( "ForceBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + if ( id != newId ) { + idAFBody *b = bodies[newId]; + bodies[newId] = bodies[id]; + bodies[id] = b; + changedAF = true; + } +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( idAFBody *body ) const { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 && body ) { + gameLocal.Error( "GetBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return i; + } + } + gameLocal.Error( "GetBodyId: no body with the name '%s' is not part of the articulated figure.\n", bodyName ); + return 0; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( idAFConstraint *constraint ) const { + int id; + + id = constraints.FindIndex( constraint ); + if ( id == -1 && constraint ) { + gameLocal.Error( "GetConstraintId: constraint '%s' is not part of the articulated figure.\n", constraint->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return i; + } + } + gameLocal.Error( "GetConstraintId: no constraint with the name '%s' is not part of the articulated figure.\n", constraintName ); + return 0; +} + +/* +================ +idPhysics_AF::GetNumBodies +================ +*/ +int idPhysics_AF::GetNumBodies( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::GetNumConstraints +================ +*/ +int idPhysics_AF::GetNumConstraints( void ) const { + return constraints.Num(); +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return bodies[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + gameLocal.Error( "GetBody: no body with id %d exists\n", id ); + return NULL; + } + return bodies[id]; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return constraints[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const int id ) const { + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "GetConstraint: no constraint with id %d exists\n", id ); + return NULL; + } + return constraints[id]; +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const char *bodyName ) { + int i; + + // find the body with the given name + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + break; + } + } + + if ( i >= bodies.Num() ) { + gameLocal.Warning( "DeleteBody: no body found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + bodyName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteBody( i ); +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const int id ) { + int j; + + if ( id < 0 || id > bodies.Num() ) { + gameLocal.Error( "DeleteBody: no body with id %d.", id ); + return; + } + + // remove any constraints attached to this body + for ( j = 0; j < constraints.Num(); j++ ) { + if ( constraints[j]->body1 == bodies[id] || constraints[j]->body2 == bodies[id] ) { + delete constraints[j]; + constraints.RemoveIndex( j ); + j--; + } + } + + // remove the body + delete bodies[id]; + bodies.RemoveIndex( id ); + + // set new body ids + for ( j = 0; j < bodies.Num(); j++ ) { + bodies[j]->clipModel->SetId( j ); + } + + changedAF = true; +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const char *constraintName ) { + int i; + + // find the constraint with the given name + for ( i = 0; i < constraints.Num(); i++ ) { + if ( !constraints[i]->name.Icmp( constraintName ) ) { + break; + } + } + + if ( i >= constraints.Num() ) { + gameLocal.Warning( "DeleteConstraint: no constriant found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + constraintName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteConstraint( i ); +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const int id ) { + + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "DeleteConstraint: no constraint with id %d.", id ); + return; + } + + // remove the constraint + delete constraints[id]; + constraints.RemoveIndex( id ); + + changedAF = true; +} + +/* +================ +idPhysics_AF::GetBodyContactConstraints +================ +*/ +int idPhysics_AF::GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const { + int i, numContacts; + idAFBody *body; + idAFConstraint_Contact *contact; + + if ( id < 0 || id >= bodies.Num() || maxContacts <= 0 ) { + return 0; + } + + numContacts = 0; + body = bodies[id]; + for ( i = 0; i < contactConstraints.Num(); i++ ) { + contact = contactConstraints[i]; + if ( contact->body1 == body || contact->body2 == body ) { + contacts[numContacts++] = contact; + if ( numContacts >= maxContacts ) { + return numContacts; + } + } + } + return numContacts; +} + +/* +================ +idPhysics_AF::SetDefaultFriction +================ +*/ +void idPhysics_AF::SetDefaultFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_AF::GetImpactInfo +================ +*/ +void idPhysics_AF::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + if ( id < 0 || id >= bodies.Num() ) { + memset( info, 0, sizeof( *info ) ); + return; + } + info->invMass = 1.0f / bodies[id]->mass; + info->invInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + info->position = point - bodies[id]->current->worldOrigin; + info->velocity = bodies[id]->current->spatialVelocity.SubVec3(0) + bodies[id]->current->spatialVelocity.SubVec3(1).Cross( info->position ); +} + +/* +================ +idPhysics_AF::ApplyImpulse +================ +*/ +void idPhysics_AF::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + if ( noImpact || impulse.LengthSqr() < Square( impulseThreshold ) ) { + return; + } + idMat3 invWorldInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + bodies[id]->current->spatialVelocity.SubVec3(0) += bodies[id]->invMass * impulse; + bodies[id]->current->spatialVelocity.SubVec3(1) += invWorldInertiaTensor * (point - bodies[id]->current->worldOrigin).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_AF::AddForce +================ +*/ +void idPhysics_AF::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->externalForce.SubVec3( 0 ) += force; + bodies[id]->current->externalForce.SubVec3( 1 ) += (point - bodies[id]->current->worldOrigin).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_AF::IsAtRest +================ +*/ +bool idPhysics_AF::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_AF::GetRestStartTime +================ +*/ +int idPhysics_AF::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_AF::IsPushable +================ +*/ +bool idPhysics_AF::IsPushable( void ) const { + return ( !noImpact && ( masterBody == NULL || forcePushable ) ); +} + +/* +================ +idPhysics_AF::SaveState +================ +*/ +void idPhysics_AF::SaveState( void ) { + int i; + + saved = current; + + for ( i = 0; i < bodies.Num(); i++ ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( &bodies[i]->saved, bodies[i]->current, sizeof( AFBodyPState_t ) ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::RestoreState +================ +*/ +void idPhysics_AF::RestoreState( void ) { + int i; + + current = saved; + + for ( i = 0; i < bodies.Num(); i++ ) { + *(bodies[i]->current) = bodies[i]->saved; + } + + EvaluateContacts(); +} + +/* +================ +idPhysics_AF::SetOrigin +================ +*/ +void idPhysics_AF::SetOrigin( const idVec3 &newOrigin, int id ) { + if ( masterBody ) { + Translate( masterBody->current->worldOrigin + masterBody->current->worldAxis * newOrigin - bodies[0]->current->worldOrigin ); + } else { + Translate( newOrigin - bodies[0]->current->worldOrigin ); + } +} + +/* +================ +idPhysics_AF::SetAxis +================ +*/ +void idPhysics_AF::SetAxis( const idMat3 &newAxis, int id ) { + idMat3 axis; + idRotation rotation; + + if ( masterBody ) { + axis = bodies[0]->current->worldAxis.Transpose() * ( newAxis * masterBody->current->worldAxis ); + } else { + axis = bodies[0]->current->worldAxis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( bodies[0]->current->worldOrigin ); + + Rotate( rotation ); +} + +/* +================ +idPhysics_AF::Translate +================ +*/ +void idPhysics_AF::Translate( const idVec3 &translation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // translate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Translate( translation ); + } + } + + // translate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + body->current->worldOrigin += translation; + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::Rotate +================ +*/ +void idPhysics_AF::Rotate( const idRotation &rotation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // rotate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Rotate( rotation ); + } + } + + // rotate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + body->current->worldOrigin *= rotation; + body->current->worldAxis *= rotation.ToMat3(); + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::GetOrigin +================ +*/ +const idVec3 &idPhysics_AF::GetOrigin( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->worldOrigin; + } +} + +/* +================ +idPhysics_AF::GetAxis +================ +*/ +const idMat3 &idPhysics_AF::GetAxis( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return mat3_identity; + } + else { + return bodies[id]->current->worldAxis; + } +} + +/* +================ +idPhysics_AF::SetLinearVelocity +================ +*/ +void idPhysics_AF::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 0 ) = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::SetAngularVelocity +================ +*/ +void idPhysics_AF::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 1 ) = newAngularVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetLinearVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 0 ); + } +} + +/* +================ +idPhysics_AF::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetAngularVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 1 ); + } +} + +/* +================ +idPhysics_AF::ClipTranslation +================ +*/ +void idPhysics_AF::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TranslationModel( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); +// RAVEN END + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + results.endpos = bodies[0]->current->worldOrigin + results.fraction * translation; + results.endAxis = bodies[0]->current->worldAxis; +} + +/* +================ +idPhysics_AF::ClipRotation +================ +*/ +void idPhysics_AF::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + idRotation partialRotation; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); +// RAVEN END + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + partialRotation = rotation * results.fraction; + results.endpos = bodies[0]->current->worldOrigin * partialRotation; + results.endAxis = bodies[0]->current->worldAxis * partialRotation.ToMat3(); +} + +/* +================ +idPhysics_AF::ClipContents +================ +*/ +int idPhysics_AF::ClipContents( const idClipModel *model ) const { + int i, contents; + idAFBody *body; + + contents = 0; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + contents |= gameLocal.Contents( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, NULL ); +// RAVEN END + } + } + } + + return contents; +} + +/* +================ +idPhysics_AF::DisableClip +================ +*/ +void idPhysics_AF::DisableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } +} + +/* +================ +idPhysics_AF::EnableClip +================ +*/ +void idPhysics_AF::EnableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Enable(); + } +} + +/* +================ +idPhysics_AF::UnlinkClip +================ +*/ +void idPhysics_AF::UnlinkClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Unlink(); + } +} + +/* +================ +idPhysics_AF::LinkClip +================ +*/ +void idPhysics_AF::LinkClip( void ) { + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::SetPushed +================ +*/ +void idPhysics_AF::SetPushed( int deltaTime ) { + idAFBody *body; + idRotation rotation; + + if ( bodies.Num() ) { + body = bodies[0]; + rotation = ( body->saved.worldAxis.Transpose() * body->current->worldAxis ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( body->current->worldOrigin - body->saved.worldOrigin ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); + } +} + +/* +================ +idPhysics_AF::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_AF::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_AF::SetMaster + + the binding is orientated based on the constraints being used +================ +*/ +void idPhysics_AF::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + idRotation rotation; + + if ( master ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( !masterBody ) { + masterBody = new idAFBody(); + // translate and rotate all the constraints with body2 == NULL from world space to master space + rotation = masterAxis.Transpose().ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Translate( -masterOrigin ); + constraints[i]->Rotate( rotation ); + } + } + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + else { + if ( masterBody ) { + // translate and rotate all the constraints with body2 == NULL from master space to world space + rotation = masterBody->current->worldAxis.ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Rotate( rotation ); + constraints[i]->Translate( masterBody->current->worldOrigin ); + } + } + delete masterBody; + masterBody = NULL; + Activate(); + } + } +} + + +const float AF_VELOCITY_MAX = 16000; +const int AF_VELOCITY_TOTAL_BITS = 16; +const int AF_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_VELOCITY_MAX ) ) + 1; +const int AF_VELOCITY_MANTISSA_BITS = AF_VELOCITY_TOTAL_BITS - 1 - AF_VELOCITY_EXPONENT_BITS; +const float AF_FORCE_MAX = 1e20f; +const int AF_FORCE_TOTAL_BITS = 16; +const int AF_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_FORCE_MAX ) ) + 1; +const int AF_FORCE_MANTISSA_BITS = AF_FORCE_TOTAL_BITS - 1 - AF_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_AF::WriteToSnapshot +================ +*/ +void idPhysics_AF::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat; + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.noMoveTime ); + msg.WriteFloat( current.activateTime ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + msg.WriteByte( bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + quat = state->worldAxis.ToCQuat(); + + msg.WriteFloat( state->worldOrigin[0] ); + msg.WriteFloat( state->worldOrigin[1] ); + msg.WriteFloat( state->worldOrigin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* msg.WriteDeltaFloat( 0.0f, state->externalForce[0], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[1], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[2], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[3], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[4], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[5], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + } +} + +/* +================ +idPhysics_AF::ReadFromSnapshot +================ +*/ +void idPhysics_AF::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat; + + // TODO: Check that this conditional write to delta message is OK + current.atRest = msg.ReadLong(); + current.noMoveTime = msg.ReadFloat(); + current.activateTime = msg.ReadFloat(); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + num = msg.ReadByte(); + assert( num == bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + + state->worldOrigin[0] = msg.ReadFloat(); + state->worldOrigin[1] = msg.ReadFloat(); + state->worldOrigin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + state->spatialVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* state->externalForce[0] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[1] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[2] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[3] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[4] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[5] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + state->worldAxis = quat.ToMat3(); + } + + UpdateClipModels(); +} diff --git a/src/game/physics/Physics_AF.h b/src/game/physics/Physics_AF.h new file mode 100644 index 0000000..3d39b90 --- /dev/null +++ b/src/game/physics/Physics_AF.h @@ -0,0 +1,1011 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_AF_H__ +#define __PHYSICS_AF_H__ + +/* +=================================================================================== + + Articulated Figure physics + + Employs a constraint force based dynamic simulation using a lagrangian + multiplier method to solve for the constraint forces. + +=================================================================================== +*/ + +class idAFConstraint; +class idAFConstraint_Fixed; +class idAFConstraint_BallAndSocketJoint; +class idAFConstraint_BallAndSocketJointFriction; +class idAFConstraint_UniversalJoint; +class idAFConstraint_UniversalJointFriction; +class idAFConstraint_CylindricalJoint; +class idAFConstraint_Hinge; +class idAFConstraint_HingeFriction; +class idAFConstraint_HingeSteering; +class idAFConstraint_Slider; +class idAFConstraint_Line; +class idAFConstraint_Plane; +class idAFConstraint_Spring; +class idAFConstraint_Contact; +class idAFConstraint_ContactFriction; +class idAFConstraint_ConeLimit; +class idAFConstraint_PyramidLimit; +class idAFBody; +class idAFTree; +class idPhysics_AF; + +typedef enum { + CONSTRAINT_INVALID, + CONSTRAINT_FIXED, + CONSTRAINT_BALLANDSOCKETJOINT, + CONSTRAINT_UNIVERSALJOINT, + CONSTRAINT_HINGE, + CONSTRAINT_HINGESTEERING, + CONSTRAINT_SLIDER, + CONSTRAINT_CYLINDRICALJOINT, + CONSTRAINT_LINE, + CONSTRAINT_PLANE, + CONSTRAINT_SPRING, + CONSTRAINT_CONTACT, + CONSTRAINT_FRICTION, + CONSTRAINT_CONELIMIT, + CONSTRAINT_PYRAMIDLIMIT +} constraintType_t; + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +// base class for all constraints +class idAFConstraint { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFConstraint( void ); + virtual ~idAFConstraint( void ); + constraintType_t GetType( void ) const { return type; } + const idStr & GetName( void ) const { return name; } + idAFBody * GetBody1( void ) const { return body1; } + idAFBody * GetBody2( void ) const { return body2; } + void SetPhysics( idPhysics_AF *p ) { physics = p; } + const idVecX & GetMultiplier( void ); + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + constraintType_t type; // constraint type + idStr name; // name of constraint + idAFBody * body1; // first constrained body + idAFBody * body2; // second constrained body, NULL for world + idPhysics_AF * physics; // for adding additional constraints like limits + + // simulation variables set by Evaluate + idMatX J1, J2; // matrix with left hand side of constraint equations + idVecX c1, c2; // right hand side of constraint equations + idVecX lo, hi, e; // low and high bounds and lcp epsilon + idAFConstraint * boxConstraint; // constraint the boxIndex refers to + int boxIndex[6]; // indexes for special box constrained variables + + // simulation variables used during calculations + idMatX invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX lm; // lagrange multipliers + int firstIndex; // index of the first constraint row in the lcp matrix + + struct constraintFlags_s { + bool allowPrimary : 1; // true if the constraint can be used as a primary constraint + bool frameConstraint : 1; // true if this constraint is added to the frame constraints + bool noCollision : 1; // true if body1 and body2 never collide with each other + bool isPrimary : 1; // true if this is a primary constraint + bool isZero : 1; // true if 's' is zero during calculations + } fl; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitSize( int size ); +}; + +// fixed or rigid joint which allows zero degrees of freedom +// constrains body1 to have a fixed position and orientation relative to body2 +class idAFConstraint_Fixed : public idAFConstraint { + +public: + idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetRelativeOrigin( const idVec3 &origin ) { this->offset = origin; } + void SetRelativeAxis( const idMat3 &axis ) { this->relAxis = axis; } + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 offset; // offset of body1 relative to body2 in body2 space + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitOffset( void ); +}; + +// ball and socket or spherical joint which allows 3 degrees of freedom +// constrains body1 relative to body2 with a ball and socket joint +class idAFConstraint_BallAndSocketJoint : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_BallAndSocketJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_BallAndSocketJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// ball and socket joint friction +class idAFConstraint_BallAndSocketJointFriction : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJointFriction( void ); + void Setup( idAFConstraint_BallAndSocketJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_BallAndSocketJoint *joint; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal, Cardan or Hooke joint which allows 2 degrees of freedom +// like a ball and socket joint but also constrains the rotation about the cardan shafts +class idAFConstraint_UniversalJoint : public idAFConstraint { + +public: + idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_UniversalJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ); + void GetShafts( idVec3 &cardanShaft1, idVec3 &cardanShaft2 ) { cardanShaft1 = shaft1; cardanShaft2 = shaft2; } + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 shaft1; // body1 cardan shaft in body1 space + idVec3 shaft2; // body2 cardan shaft in body2 space + idVec3 axis1; // cardan axis in body1 space + idVec3 axis2; // cardan axis in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_UniversalJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal joint friction +class idAFConstraint_UniversalJointFriction : public idAFConstraint { + +public: + idAFConstraint_UniversalJointFriction( void ); + void Setup( idAFConstraint_UniversalJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_UniversalJoint *joint; // universal joint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// cylindrical joint which allows 2 degrees of freedom +// constrains body1 to lie on a line relative to body2 and allows only translation along and rotation about the line +class idAFConstraint_CylindricalJoint : public idAFConstraint { + +public: + idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge, revolute or pin joint which allows 1 degree of freedom +// constrains all motion of body1 relative to body2 except the rotation about the hinge axis +class idAFConstraint_Hinge : public idAFConstraint { + +public: + idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_Hinge( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetAxis( const idVec3 &axis ); + void GetAxis( idVec3 &a1, idVec3 &a2 ) const { a1 = axis1; a2 = axis2; } + idVec3 GetAxis( void ) const; + void SetNoLimit( void ); + void SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + float GetAngle( void ) const; + void SetSteerAngle( const float degrees ); + void SetSteerSpeed( const float speed ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 axis1; // axis in body1 space + idVec3 axis2; // axis in body2 space + idMat3 initialAxis; // initial axis of body1 relative to body2 + float friction; // hinge friction + idAFConstraint_ConeLimit *coneLimit; // cone limit + idAFConstraint_HingeSteering *steering; // steering + idAFConstraint_HingeFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge joint friction +class idAFConstraint_HingeFriction : public idAFConstraint { + +public: + idAFConstraint_HingeFriction( void ); + void Setup( idAFConstraint_Hinge *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains two bodies attached to each other with a hinge to get a specified relative orientation +class idAFConstraint_HingeSteering : public idAFConstraint { + +public: + idAFConstraint_HingeSteering( void ); + void Setup( idAFConstraint_Hinge *cc ); + void SetSteerAngle( const float degrees ) { steerAngle = degrees; } + void SetSteerSpeed( const float speed ) { steerSpeed = speed; } + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + float steerAngle; // desired steer angle in degrees + float steerSpeed; // steer speed + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// slider, prismatic or translational constraint which allows 1 degree of freedom +// constrains body1 to lie on a line relative to body2, the orientation is also fixed relative to body2 +class idAFConstraint_Slider : public idAFConstraint { + +public: + idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAxis( const idVec3 &ax ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 axis; // axis along which body1 slides in body2 space + idVec3 offset; // offset of body1 relative to body2 + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// line constraint which allows 4 degrees of freedom +// constrains body1 to lie on a line relative to body2, does not constrain the orientation. +class idAFConstraint_Line : public idAFConstraint { + +public: + idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// plane constraint which allows 5 degrees of freedom +// constrains body1 to lie in a plane relative to body2, does not constrain the orientation. +class idAFConstraint_Plane : public idAFConstraint { + +public: + idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetPlane( const idVec3 &normal, const idVec3 &anchor ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 planeNormal; // plane normal in body2 space + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// spring constraint which allows 6 or 5 degrees of freedom based on the spring limits +// constrains body1 relative to body2 with a spring +class idAFConstraint_Spring : public idAFConstraint { + +public: + idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ); + void SetSpring( const float stretch, const float compress, const float damping, const float restLength ); + void SetLimit( const float minLength, const float maxLength ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float kstretch; // spring constant when stretched + float kcompress; // spring constant when compressed + float damping; // spring damping + float restLength; // rest length of spring + float minLength; // minimum spring length + float maxLength; // maximum spring length + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains body1 to either be in contact with or move away from body2 +class idAFConstraint_Contact : public idAFConstraint { + +public: + idAFConstraint_Contact( void ); + ~idAFConstraint_Contact( void ); + void Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ); + const contactInfo_t & GetContact( void ) const { return contact; } + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + +protected: + contactInfo_t contact; // contact information + idAFConstraint_ContactFriction *fc; // contact friction + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// contact friction +class idAFConstraint_ContactFriction : public idAFConstraint { + +public: + idAFConstraint_ContactFriction( void ); + void Setup( idAFConstraint_Contact *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Contact *cc; // contact constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a cone relative to body2 +class idAFConstraint_ConeLimit : public idAFConstraint { + +public: + idAFConstraint_ConeLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, + const float coneAngle, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &coneAnchor ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 coneAnchor; // top of the cone in body2 space + idVec3 coneAxis; // cone axis in body2 space + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle; // cos( coneAngle / 2 ) + float sinHalfAngle; // sin( coneAngle / 4 ) + float cosHalfAngle; // cos( coneAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a pyramid relative to body2 +class idAFConstraint_PyramidLimit : public idAFConstraint { + +public: + idAFConstraint_PyramidLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &pyramidAxis ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 pyramidAnchor; // top of the pyramid in body2 space + idMat3 pyramidBasis; // pyramid basis in body2 space with base[2] being the pyramid axis + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle[2]; // cos( pyramidAngle / 2 ) + float sinHalfAngle[2]; // sin( pyramidAngle / 4 ) + float cosHalfAngle[2]; // cos( pyramidAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +typedef struct AFBodyPState_s { + idVec3 worldOrigin; // position in world space + idMat3 worldAxis; // axis at worldOrigin + idVec6 spatialVelocity; // linear and rotational velocity of body + idVec6 externalForce; // external force and torque applied to body +} AFBodyPState_t; + + +class idAFBody { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFBody( void ); + idAFBody( const idStr &name, idClipModel *clipModel, float density ); + ~idAFBody( void ); + + void Init( void ); + const idStr & GetName( void ) const { return name; } + const idVec3 & GetWorldOrigin( void ) const { return current->worldOrigin; } + const idMat3 & GetWorldAxis( void ) const { return current->worldAxis; } + const idVec3 & GetLinearVelocity( void ) const { return current->spatialVelocity.SubVec3(0); } + const idVec3 & GetAngularVelocity( void ) const { return current->spatialVelocity.SubVec3(1); } + idVec3 GetPointVelocity( const idVec3 &point ) const; + const idVec3 & GetCenterOfMass( void ) const { return centerOfMass; } + void SetClipModel( idClipModel *clipModel ); + idClipModel * GetClipModel( void ) const { return clipModel; } + void SetClipMask( const int mask ) { clipMask = mask; fl.clipMaskSet = true; } + int GetClipMask( void ) const { return clipMask; } + void SetSelfCollision( const bool enable ) { fl.selfCollision = enable; } + void SetWorldOrigin( const idVec3 &origin ) { current->worldOrigin = origin; } + void SetWorldAxis( const idMat3 &axis ) { current->worldAxis = axis; } + void SetLinearVelocity( const idVec3 &linear ) const { current->spatialVelocity.SubVec3(0) = linear; } + void SetAngularVelocity( const idVec3 &angular ) const { current->spatialVelocity.SubVec3(1) = angular; } + void SetFriction( float linear, float angular, float contact ); + float GetContactFriction( void ) const { return contactFriction; } + void SetBouncyness( float bounce ); + float GetBouncyness( void ) const { return bouncyness; } + void SetDensity( float density, const idMat3 &inertiaScale = mat3_identity ); + float GetInverseMass( void ) const { return invMass; } + idMat3 GetInverseWorldInertia( void ) const { return current->worldAxis.Transpose() * inverseInertiaTensor * current->worldAxis; } + + void SetFrictionDirection( const idVec3 &dir ); + bool GetFrictionDirection( idVec3 &dir ) const; + + void SetContactMotorDirection( const idVec3 &dir ); + bool GetContactMotorDirection( idVec3 &dir ) const; + void SetContactMotorVelocity( float vel ) { contactMotorVelocity = vel; } + float GetContactMotorVelocity( void ) const { return contactMotorVelocity; } + void SetContactMotorForce( float force ) { contactMotorForce = force; } + float GetContactMotorForce( void ) const { return contactMotorForce; } + + void AddForce( const idVec3 &point, const idVec3 &force ); + void InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const; + idVec6 & GetResponseForce( int index ) { return reinterpret_cast(response[ index * 8 ]); } + + void Save( idSaveGame *saveFile ); + void Restore( idRestoreGame *saveFile ); + +private: + // properties + idStr name; // name of body + idAFBody * parent; // parent of this body + idList children; // children of this body + idClipModel * clipModel; // model used for collision detection + idAFConstraint * primaryConstraint; // primary constraint (this->constraint->body1 = this) + idListconstraints; // all constraints attached to this body + idAFTree * tree; // tree structure this body is part of + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bounce + int clipMask; // contents this body collides with + idVec3 frictionDir; // specifies a single direction of friction in body space + idVec3 contactMotorDir; // contact motor direction + float contactMotorVelocity; // contact motor velocity + float contactMotorForce; // maximum force applied to reach the motor velocity + + // derived properties + float mass; // mass of body + float invMass; // inverse mass + idVec3 centerOfMass; // center of mass of body + idMat3 inertiaTensor; // inertia tensor + idMat3 inverseInertiaTensor; // inverse inertia tensor + + // physics state + AFBodyPState_t state[2]; + AFBodyPState_t * current; // current physics state + AFBodyPState_t * next; // next physics state + AFBodyPState_t saved; // saved physics state + idVec3 atRestOrigin; // origin at rest + idMat3 atRestAxis; // axis at rest + + // simulation variables used during calculations + idMatX inverseWorldSpatialInertia; // inverse spatial inertia in world space + idMatX I, invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX totalForce; // total force acting on body + idVecX auxForce; // force from auxiliary constraints + idVecX acceleration; // acceleration + float * response; // forces on body in response to auxiliary constraint forces + int * responseIndex; // index to response forces + int numResponses; // number of response forces + int maxAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body + int maxSubTreeAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body or one of it's children + + struct bodyFlags_s { + bool clipMaskSet : 1; // true if this body has a clip mask set + bool selfCollision : 1; // true if this body can collide with other bodies of this AF + bool spatialInertiaSparse: 1; // true if the spatial inertia matrix is sparse + bool useFrictionDir : 1; // true if a single friction direction should be used + bool useContactMotorDir : 1; // true if a contact motor should be used + bool isZero : 1; // true if 's' is zero during calculations + } fl; +}; + + +//=============================================================== +// +// idAFTree +// +//=============================================================== + +class idAFTree { + friend class idPhysics_AF; + +public: + void Factor( void ) const; + void Solve( int auxiliaryIndex = 0 ) const; + void Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const; + void CalculateForces( float timeStep ) const; + void SetMaxSubTreeAuxiliaryIndex( void ); + void SortBodies( void ); + void SortBodies_r( idList&sortedList, idAFBody *body ); + void DebugDraw( const idVec4 &color ) const; + +private: + idList sortedBodies; +}; + + +//=============================================================== +// +// idPhysics_AF +// +//=============================================================== + +typedef struct AFPState_s { + int atRest; // >= 0 if articulated figure is at rest + float noMoveTime; // time the articulated figure is hardly moving + float activateTime; // time since last activation + float lastTimeStep; // last time step + idVec6 pushVelocity; // velocity with which the af is pushed +} AFPState_t; + +typedef struct AFCollision_s { + trace_t trace; + idAFBody * body; +} AFCollision_t; + +class idPhysics_AF : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_AF ); + + idPhysics_AF( void ); + ~idPhysics_AF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + int AddBody( idAFBody *body ); // returns body id + void AddConstraint( idAFConstraint *constraint ); + void AddFrameConstraint( idAFConstraint *constraint ); + // force a body to have a certain id + void ForceBodyId( idAFBody *body, int newId ); + // get body or constraint id + int GetBodyId( idAFBody *body ) const; + int GetBodyId( const char *bodyName ) const; + int GetConstraintId( idAFConstraint *constraint ) const; + int GetConstraintId( const char *constraintName ) const; + // number of bodies and constraints + int GetNumBodies( void ) const; + int GetNumConstraints( void ) const; + // retrieve body or constraint + idAFBody * GetBody( const char *bodyName ) const; + idAFBody * GetBody( const int id ) const; + idAFBody * GetMasterBody( void ) const { return masterBody; } + idAFConstraint * GetConstraint( const char *constraintName ) const; + idAFConstraint * GetConstraint( const int id ) const; + // delete body or constraint + void DeleteBody( const char *bodyName ); + void DeleteBody( const int id ); + void DeleteConstraint( const char *constraintName ); + void DeleteConstraint( const int id ); + + // get all the contact constraints acting on the body + int GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const; + // set the default friction for bodies + void SetDefaultFriction( float linear, float angular, float contact ); + // suspend settings + void SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ); + // set the time and tolerances used to determine if the simulation can be suspended when the figure hardly moves for a while + void SetSuspendTolerance( const float noMoveTime, const float translationTolerance, const float rotationTolerance ); + // set minimum and maximum simulation time in seconds + void SetSuspendTime( const float minTime, const float maxTime ); + // set the time scale value + void SetTimeScale( const float ts ) { timeScale = ts; } + // set time scale ramp + void SetTimeScaleRamp( const float start, const float end ); + // set the joint friction scale + void SetJointFrictionScale( const float scale ) { jointFrictionScale = scale; } + // set joint friction dent + void SetJointFrictionDent( const float dent, const float start, const float end ); + // get the current joint friction scale + float GetJointFrictionScale( void ) const; + // set the contact friction scale + void SetContactFrictionScale( const float scale ) { contactFrictionScale = scale; } + // set contact friction dent + void SetContactFrictionDent( const float dent, const float start, const float end ); + // get the current contact friction scale + float GetContactFrictionScale( void ) const; + // enable or disable collision detection + void SetCollision( const bool enable ) { enableCollision = enable; } + // enable or disable self collision + void SetSelfCollision( const bool enable ) { selfCollision = enable; } + // enable or disable coming to a dead stop + void SetComeToRest( bool enable ) { comeToRest = enable; } + // call when structure of articulated figure changes + void SetChanged( void ) { changedAF = true; } +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + // enable or disable fast evaluation + void SetFastEval( const bool enable ) { fastEval = enable; } +// RAVEN END + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + // lock of unlock the world constraints + void LockWorldConstraints( const bool lock ) { worldConstraintsLocked = lock; } + // set force pushable + void SetForcePushable( const bool enable ) { forcePushable = enable; } + // update the clip model positions + void UpdateClipModels( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + //MCG: added SetImpulseThreshold + void SetImpulseThreshold( float newIT ) { impulseThreshold = newIT; }; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + void Activate( void ); + void PutToRest( void ); + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // articulated figure + idList trees; // tree structures + idList bodies; // all bodies + idListconstraints; // all frame independent constraints + idListprimaryConstraints; // list with primary constraints + idListauxiliaryConstraints; // list with auxiliary constraints + idListframeConstraints; // constraints that only live one frame + idListcontactConstraints; // contact constraints + idList contactBodies; // body id for each contact + idList collisions; // collisions + bool changedAF; // true when the articulated figure just changed + + // properties + float linearFriction; // default translational friction + float angularFriction; // default rotational friction + float contactFriction; // default friction with contact surfaces + float bouncyness; // default bouncyness + float totalMass; // total mass of articulated figure + float forceTotalMass; // force this total mass + + idVec2 suspendVelocity; // simulation may not be suspended if a body has more velocity + idVec2 suspendAcceleration; // simulation may not be suspended if a body has more acceleration + float noMoveTime; // suspend simulation if hardly any movement for this many seconds + float noMoveTranslation; // maximum translation considered no movement + float noMoveRotation; // maximum rotation considered no movement + float minMoveTime; // if > 0 the simulation is never suspended before running this many seconds + float maxMoveTime; // if > 0 the simulation is always suspeded after running this many seconds + float impulseThreshold; // threshold below which impulses are ignored to avoid continuous activation + + float timeScale; // the time is scaled with this value for slow motion effects + float timeScaleRampStart; // start of time scale change + float timeScaleRampEnd; // end of time scale change + + float jointFrictionScale; // joint friction scale + float jointFrictionDent; // joint friction dives from 1 to this value and goes up again + float jointFrictionDentStart; // start time of joint friction dent + float jointFrictionDentEnd; // end time of joint friction dent + float jointFrictionDentScale; // dent scale + + float contactFrictionScale; // contact friction scale + float contactFrictionDent; // contact friction dives from 1 to this value and goes up again + float contactFrictionDentStart; // start time of contact friction dent + float contactFrictionDentEnd; // end time of contact friction dent + float contactFrictionDentScale; // dent scale + + bool enableCollision; // if true collision detection is enabled + bool selfCollision; // if true the self collision is allowed + bool comeToRest; // if true the figure can come to rest + bool linearTime; // if true use the linear time algorithm + bool noImpact; // if true do not activate when another object collides + bool worldConstraintsLocked; // if true world constraints cannot be moved + bool forcePushable; // if true can be pushed even when bound to a master +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + bool fastEval; // if true the fast eval is on +// RAVEN END + + // physics state + AFPState_t current; + AFPState_t saved; + + idAFBody * masterBody; // master body + idLCP * lcp; // linear complementarity problem solver + +private: + void BuildTrees( void ); + bool IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const; + void PrimaryFactor( void ); + void EvaluateBodies( float timeStep ); + void EvaluateConstraints( float timeStep ); + void AddFrameConstraints( void ); + void RemoveFrameConstraints( void ); + void ApplyFriction( float timeStep, float endTimeMSec ); + void PrimaryForces( float timeStep ); + void AuxiliaryForces( float timeStep ); + void VerifyContactConstraints( void ); + void SetupContactConstraints( void ); + void ApplyContactForces( void ); + void Evolve( float timeStep ); + idEntity * SetupCollisionForBody( idAFBody *body ) const; + bool CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ); + bool ApplyCollisions( float timeStep ); + void CheckForCollisions( float timeStep ); + void ClearExternalForce( void ); + void AddGravity( void ); + void SwapStates( void ); + bool TestIfAtRest( float timeStep ); + void Rest( void ); + void AddPushVelocity( const idVec6 &pushVelocity ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_AF_H__ */ diff --git a/src/game/physics/Physics_Actor.cpp b/src/game/physics/Physics_Actor.cpp new file mode 100644 index 0000000..8170ac1 --- /dev/null +++ b/src/game/physics/Physics_Actor.cpp @@ -0,0 +1,396 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Actor ) +END_CLASS + +/* +================ +idPhysics_Actor::idPhysics_Actor +================ +*/ +idPhysics_Actor::idPhysics_Actor( void ) { + clipModel = NULL; + SetClipModelAxis(); + mass = 100.0f; + invMass = 1.0f / mass; + masterEntity = NULL; + masterYaw = 0.0f; + masterDeltaYaw = 0.0f; + groundEntityPtr = NULL; +} + +/* +================ +idPhysics_Actor::~idPhysics_Actor +================ +*/ +idPhysics_Actor::~idPhysics_Actor( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +idPhysics_Actor::Save +================ +*/ +void idPhysics_Actor::Save( idSaveGame *savefile ) const { + + savefile->WriteClipModel( clipModel ); + savefile->WriteMat3( clipModelAxis ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( invMass ); + + savefile->WriteObject( masterEntity ); + savefile->WriteFloat( masterYaw ); + savefile->WriteFloat( masterDeltaYaw ); + + groundEntityPtr.Save( savefile ); +} + +/* +================ +idPhysics_Actor::Restore +================ +*/ +void idPhysics_Actor::Restore( idRestoreGame *savefile ) { + + savefile->ReadClipModel( clipModel ); + savefile->ReadMat3( clipModelAxis ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( invMass ); + + savefile->ReadObject( reinterpret_cast( masterEntity ) ); + savefile->ReadFloat( masterYaw ); + savefile->ReadFloat( masterDeltaYaw ); + + groundEntityPtr.Restore( savefile ); +} + +/* +================ +idPhysics_Actor::SetClipModelAxis +================ +*/ +void idPhysics_Actor::SetClipModelAxis( void ) { + // align clip model to gravity direction + if ( ( gravityNormal[2] == -1.0f ) || ( gravityNormal == vec3_zero ) ) { + clipModelAxis.Identity(); + } + else { + clipModelAxis[2] = -gravityNormal; + clipModelAxis[2].NormalVectors( clipModelAxis[0], clipModelAxis[1] ); + clipModelAxis[1] = -clipModelAxis[1]; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::GetGravityAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetGravityAxis( void ) const { + return clipModelAxis; +} + +/* +================ +idPhysics_Actor::GetMasterDeltaYaw +================ +*/ +float idPhysics_Actor::GetMasterDeltaYaw( void ) const { + return masterDeltaYaw; +} + +/* +================ +idPhysics_Actor::GetGroundEntity +================ +*/ +idEntity *idPhysics_Actor::GetGroundEntity( void ) const { + return groundEntityPtr.GetEntity(); +} + +/* +================ +idPhysics_Actor::SetClipModel +================ +*/ +void idPhysics_Actor::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + assert( self ); + assert( model ); // a clip model is required + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::GetClipModel +================ +*/ +idClipModel *idPhysics_Actor::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Actor::GetNumClipModels +================ +*/ +int idPhysics_Actor::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_Actor::SetMass +================ +*/ +void idPhysics_Actor::SetMass( float _mass, int id ) { + assert( _mass > 0.0f ); + mass = _mass; + invMass = 1.0f / _mass; +} + +/* +================ +idPhysics_Actor::GetMass +================ +*/ +float idPhysics_Actor::GetMass( int id ) const { + return mass; +} + +/* +================ +idPhysics_Actor::SetContents +================ +*/ +void idPhysics_Actor::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_Actor::GetContents +================ +*/ +int idPhysics_Actor::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_Actor::GetBounds +================ +*/ +const idBounds &idPhysics_Actor::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_Actor::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Actor::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_Actor::IsPushable +================ +*/ +bool idPhysics_Actor::IsPushable( void ) const { + return ( masterEntity == NULL ); +} + +/* +================ +idPhysics_Actor::GetOrigin +================ +*/ +const idVec3 &idPhysics_Actor::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +idPhysics_Player::GetAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetAxis( int id ) const { + return clipModel->GetAxis(); +} + +/* +================ +idPhysics_Actor::SetGravity +================ +*/ +void idPhysics_Actor::SetGravity( const idVec3 &newGravity ) { + if ( newGravity != gravityVector ) { + idPhysics_Base::SetGravity( newGravity ); + SetClipModelAxis(); + } +} + +/* +================ +idPhysics_Actor::ClipTranslation +================ +*/ +void idPhysics_Actor::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::ClipRotation +================ +*/ +void idPhysics_Actor::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::ClipContents +================ +*/ +int idPhysics_Actor::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip models + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::DisableClip +================ +*/ +void idPhysics_Actor::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_Actor::EnableClip +================ +*/ +void idPhysics_Actor::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_Actor::UnlinkClip +================ +*/ +void idPhysics_Actor::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_Actor::LinkClip +================ +*/ +void idPhysics_Actor::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::EvaluateContacts +================ +*/ +bool idPhysics_Actor::EvaluateContacts( void ) { + + // get all the ground contacts + ClearContacts(); + AddGroundContacts( clipModel ); + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} diff --git a/src/game/physics/Physics_Actor.h b/src/game/physics/Physics_Actor.h new file mode 100644 index 0000000..11bf12d --- /dev/null +++ b/src/game/physics/Physics_Actor.h @@ -0,0 +1,112 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_ACTOR_H__ +#define __PHYSICS_ACTOR_H__ + +/* +=================================================================================== + + Actor physics base class + + An actor typically uses one collision model which is aligned with the gravity + direction. The collision model is usually a simple box with the origin at the + bottom center. + +=================================================================================== +*/ + +class idPhysics_Actor : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Actor ); + + idPhysics_Actor( void ); + ~idPhysics_Actor( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // get delta yaw of master + float GetMasterDeltaYaw( void ) const; + // returns the ground entity + idEntity * GetGroundEntity( void ) const; + // align the clip model with the gravity direction + void SetClipModelAxis( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool IsPushable( void ) const; + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); +// RAVEN BEGIN +// abahr: made virtual + virtual const idMat3& GetGravityAxis( void ) const; +// RAVEN END + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + +protected: + idClipModel * clipModel; // clip model used for collision detection + idMat3 clipModelAxis; // axis of clip model aligned with gravity direction + + // derived properties + float mass; + float invMass; + + // master + idEntity * masterEntity; + float masterYaw; + float masterDeltaYaw; + + // results of last evaluate + idEntityPtr groundEntityPtr; +}; + +#endif /* !__PHYSICS_ACTOR_H__ */ diff --git a/src/game/physics/Physics_Base.cpp b/src/game/physics/Physics_Base.cpp new file mode 100644 index 0000000..80e1a9a --- /dev/null +++ b/src/game/physics/Physics_Base.cpp @@ -0,0 +1,895 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Base ) +END_CLASS + +/* +================ +idPhysics_Base::idPhysics_Base +================ +*/ +idPhysics_Base::idPhysics_Base( void ) { + self = NULL; + clipMask = 0; + SetGravity( gameLocal.GetGravity() ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::~idPhysics_Base +================ +*/ +idPhysics_Base::~idPhysics_Base( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::Save +================ +*/ +void idPhysics_Base::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + savefile->WriteInt( clipMask ); + savefile->WriteVec3( gravityVector ); + savefile->WriteVec3( gravityNormal ); + + savefile->WriteInt( contacts.Num() ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->WriteContactInfo( contacts[i] ); + } + + savefile->WriteInt( contactEntities.Num() ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Save( savefile ); + } +} + +/* +================ +idPhysics_Base::Restore +================ +*/ +void idPhysics_Base::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadInt( clipMask ); + savefile->ReadVec3( gravityVector ); + savefile->ReadVec3( gravityNormal ); + + savefile->ReadInt( num ); + contacts.SetNum( num ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->ReadContactInfo( contacts[i] ); + } + + savefile->ReadInt( num ); + contactEntities.SetNum( num ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Restore( savefile ); + } +} + +/* +================ +idPhysics_Base::SetSelf +================ +*/ +void idPhysics_Base::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Base::SetClipModel +================ +*/ +void idPhysics_Base::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_Base::GetClipModel +================ +*/ +idClipModel *idPhysics_Base::GetClipModel( int id ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetNumClipModels +================ +*/ +int idPhysics_Base::GetNumClipModels( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetMass +================ +*/ +void idPhysics_Base::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Base::GetMass +================ +*/ +float idPhysics_Base::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Base::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Base::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Base::SetContents +================ +*/ +void idPhysics_Base::SetContents( int contents, int id ) { +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +int idPhysics_Base::GetContents( int id ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +void idPhysics_Base::SetClipMask( int mask, int id ) { + clipMask = mask; +} + +/* +================ +idPhysics_Base::GetClipMask +================ +*/ +int idPhysics_Base::GetClipMask( int id ) const { + return clipMask; +} + +/* +================ +idPhysics_Base::GetBounds +================ +*/ +const idBounds &idPhysics_Base::GetBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Base::GetAbsBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::Evaluate +================ +*/ +bool idPhysics_Base::Evaluate( int timeStepMSec, int endTimeMSec ) { + return false; +} + +/* +================ +idPhysics_Base::UpdateTime +================ +*/ +void idPhysics_Base::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Base::GetTime +================ +*/ +int idPhysics_Base::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetImpactInfo +================ +*/ +void idPhysics_Base::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Base::ApplyImpulse +================ +*/ +void idPhysics_Base::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Base::AddForce +================ +*/ +void idPhysics_Base::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Base::Activate +================ +*/ +void idPhysics_Base::Activate( void ) { +} + +/* +================ +idPhysics_Base::PutToRest +================ +*/ +void idPhysics_Base::PutToRest( void ) { +} + +/* +================ +idPhysics_Base::IsAtRest +================ +*/ +bool idPhysics_Base::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Base::GetRestStartTime +================ +*/ +int idPhysics_Base::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::IsPushable +================ +*/ +bool idPhysics_Base::IsPushable( void ) const { + return true; +} + +// RAVEN BEGIN +// bdube: water interraction +bool idPhysics_Base::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Base::SaveState +================ +*/ +void idPhysics_Base::SaveState( void ) { +} + +/* +================ +idPhysics_Base::RestoreState +================ +*/ +void idPhysics_Base::RestoreState( void ) { +} + +/* +================ +idPhysics_Base::SetOrigin +================ +*/ +void idPhysics_Base::SetOrigin( const idVec3 &newOrigin, int id ) { +} + +/* +================ +idPhysics_Base::SetAxis +================ +*/ +void idPhysics_Base::SetAxis( const idMat3 &newAxis, int id ) { +} + +/* +================ +idPhysics_Base::Translate +================ +*/ +void idPhysics_Base::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Base::Rotate +================ +*/ +void idPhysics_Base::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Base::GetOrigin +================ +*/ +const idVec3 &idPhysics_Base::GetOrigin( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAxis +================ +*/ +const idMat3 &idPhysics_Base::GetAxis( int id ) const { + return mat3_identity; +} + +/* +================ +idPhysics_Base::SetLinearVelocity +================ +*/ +void idPhysics_Base::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Base::SetAngularVelocity +================ +*/ +void idPhysics_Base::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Base::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetGravity +================ +*/ +void idPhysics_Base::SetGravity( const idVec3 &newGravity ) { + gravityVector = newGravity; + gravityNormal = newGravity; + gravityNormal.Normalize(); +} + +/* +================ +idPhysics_Base::GetGravity +================ +*/ +const idVec3 &idPhysics_Base::GetGravity( void ) const { + return gravityVector; +} + +/* +================ +idPhysics_Base::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Base::GetGravityNormal( void ) const { + return gravityNormal; +} + +/* +================ +idPhysics_Base::ClipTranslation +================ +*/ +void idPhysics_Base::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipRotation +================ +*/ +void idPhysics_Base::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipContents +================ +*/ +int idPhysics_Base::ClipContents( const idClipModel *model ) const { + return 0; +} + +/* +================ +idPhysics_Base::DisableClip +================ +*/ +void idPhysics_Base::DisableClip( void ) { +} + +/* +================ +idPhysics_Base::EnableClip +================ +*/ +void idPhysics_Base::EnableClip( void ) { +} + +/* +================ +idPhysics_Base::UnlinkClip +================ +*/ +void idPhysics_Base::UnlinkClip( void ) { +} + +/* +================ +idPhysics_Base::LinkClip +================ +*/ +void idPhysics_Base::LinkClip( void ) { +} + +/* +================ +idPhysics_Base::EvaluateContacts +================ +*/ +bool idPhysics_Base::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Base::GetNumContacts +================ +*/ +int idPhysics_Base::GetNumContacts( void ) const { + return contacts.Num(); +} + +/* +================ +idPhysics_Base::GetContact +================ +*/ +const contactInfo_t &idPhysics_Base::GetContact( int num ) const { + return contacts[num]; +} + +/* +================ +idPhysics_Base::ClearContacts +================ +*/ +void idPhysics_Base::ClearContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent ) { + ent->RemoveContactEntity( self ); + } + } + contacts.SetNum( 0, false ); +} + +/* +================ +idPhysics_Base::AddContactEntity +================ +*/ +void idPhysics_Base::AddContactEntity( idEntity *e ) { + int i; + idEntity *ent; + bool found = false; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent == NULL ) { + contactEntities.RemoveIndex( i-- ); + } + if ( ent == e ) { + found = true; + } + } + if ( !found ) { + contactEntities.Alloc() = e; + } +} + +/* +================ +idPhysics_Base::RemoveContactEntity +================ +*/ +void idPhysics_Base::RemoveContactEntity( idEntity *e ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( !ent ) { + contactEntities.RemoveIndex( i-- ); + continue; + } + if ( ent == e ) { + contactEntities.RemoveIndex( i-- ); + return; + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + normal += GetContact( ix ).normal; + } + + return normal.ToNormal(); +} + +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetGroundContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + if ( GetContact(ix).normal * -gravityNormal > 0.0f ) { + normal += GetContact( ix ).normal; + } + } + + return normal.ToNormal(); +} +// RAVEN END + +/* +================ +idPhysics_Base::HasGroundContacts +================ +*/ +bool idPhysics_Base::HasGroundContacts( void ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].normal * -gravityNormal > 0.0f ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundEntity +================ +*/ +bool idPhysics_Base::IsGroundEntity( int entityNum ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundClipModel +================ +*/ +bool idPhysics_Base::IsGroundClipModel( int entityNum, int id ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && contacts[i].id == id && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::SetPushed +================ +*/ +void idPhysics_Base::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Base::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetMaster +================ +*/ +void idPhysics_Base::SetMaster( idEntity *master, const bool orientated ) { +} + +/* +================ +idPhysics_Base::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Base::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Base::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetLinearEndTime +================ +*/ +int idPhysics_Base::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetAngularEndTime +================ +*/ +int idPhysics_Base::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::AddGroundContacts +================ +*/ +void idPhysics_Base::AddGroundContacts( const idClipModel *clipModel ) { + idVec6 dir; + int index, num; + + index = contacts.Num(); + contacts.SetNum( index + 10, false ); + + dir.SubVec3(0) = gravityNormal; + dir.SubVec3(1) = vec3_origin; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[index], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( index + num, false ); +} + +/* +================ +idPhysics_Base::AddContactEntitiesForContacts +================ +*/ +void idPhysics_Base::AddContactEntitiesForContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent && ent != self ) { + ent->AddContactEntity( self ); + } + } +} + +/* +================ +idPhysics_Base::ActivateContactEntities +================ +*/ +void idPhysics_Base::ActivateContactEntities( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent ) { + ent->ActivatePhysics( self ); + } else { + contactEntities.RemoveIndex( i-- ); + } + } +} + +/* +================ +idPhysics_Base::IsOutsideWorld +================ +*/ +bool idPhysics_Base::IsOutsideWorld( void ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.GetWorldBounds( self ).Expand( 128.0f ).IntersectsBounds( GetAbsBounds() ) ) { +// RAVEN END + return true; + } + return false; +} + +/* +================ +idPhysics_Base::DrawVelocity +================ +*/ +void idPhysics_Base::DrawVelocity( int id, float linearScale, float angularScale ) const { + idVec3 dir, org, vec, start, end; + idMat3 axis; + float length, a; + + dir = GetLinearVelocity( id ); + dir *= linearScale; + if ( dir.LengthSqr() > Square( 0.1f ) ) { + dir.Truncate( 10.0f ); + org = GetOrigin( id ); + gameRenderWorld->DebugArrow( colorRed, org, org + dir, 1 ); + } + + dir = GetAngularVelocity( id ); + length = dir.Normalize(); + length *= angularScale; + if ( length > 0.1f ) { + if ( length < 60.0f ) { + length = 60.0f; + } + else if ( length > 360.0f ) { + length = 360.0f; + } + axis = GetAxis( id ); + vec = axis[2]; + if ( idMath::Fabs( dir * vec ) > 0.99f ) { + vec = axis[0]; + } + vec -= vec * dir * vec; + vec.Normalize(); + vec *= 4.0f; + start = org + vec; + for ( a = 20.0f; a < length; a += 20.0f ) { + end = org + idRotation( vec3_origin, dir, -a ).ToMat3() * vec; + gameRenderWorld->DebugLine( colorBlue, start, end, 1 ); + start = end; + } + end = org + idRotation( vec3_origin, dir, -length ).ToMat3() * vec; + gameRenderWorld->DebugArrow( colorBlue, start, end, 1 ); + } +} + +/* +================ +idPhysics_Base::WriteToSnapshot +================ +*/ +void idPhysics_Base::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Base::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} diff --git a/src/game/physics/Physics_Base.h b/src/game/physics/Physics_Base.h new file mode 100644 index 0000000..2c3bc32 --- /dev/null +++ b/src/game/physics/Physics_Base.h @@ -0,0 +1,183 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_BASE_H__ +#define __PHYSICS_BASE_H__ + +/* +=============================================================================== + + Physics base for a moving object using one or more collision models. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jnewquist: Changed from #define to typedef to fix compiler issues +typedef idEntityPtr contactEntity_t; +// RAVEN END + +class idPhysics_Base : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Base ); + + idPhysics_Base( void ); + ~idPhysics_Base( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: added center mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + +// RAVEN BEGIN +// abahr: helper function used in projectiles + virtual const idVec3 GetContactNormal() const; + virtual const idVec3 GetGroundContactNormal() const; +// RAVEN END + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object + int clipMask; // contents the physics object collides with + idVec3 gravityVector; // direction and magnitude of gravity + idVec3 gravityNormal; // normalized direction of gravity + idList contacts; // contacts with other physics objects + idList contactEntities; // entities touching this physics object + +protected: + // add ground contacts for the clip model + void AddGroundContacts( const idClipModel *clipModel ); + // add contact entity links to contact entities + void AddContactEntitiesForContacts( void ); + // active all contact entities + void ActivateContactEntities( void ); + // returns true if the whole physics object is outside the world bounds + bool IsOutsideWorld( void ) const; + // draw linear and angular velocity + void DrawVelocity( int id, float linearScale, float angularScale ) const; +}; + +#endif /* !__PHYSICS_BASE_H__ */ diff --git a/src/game/physics/Physics_Monster.cpp b/src/game/physics/Physics_Monster.cpp new file mode 100644 index 0000000..168d02a --- /dev/null +++ b/src/game/physics/Physics_Monster.cpp @@ -0,0 +1,861 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Monster ) +END_CLASS + +const float OVERCLIP = 1.001f; + +/* +===================== +idPhysics_Monster::CheckGround +===================== +*/ +void idPhysics_Monster::CheckGround( monsterPState_t &state ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + down = state.origin + gravityNormal * CONTACT_EPSILON; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, groundTrace, state.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + if ( groundTrace.fraction == 1.0f ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + if ( ( groundTrace.c.normal * -gravityNormal ) < minFloorCosine ) { + state.onGround = false; + return; + } + + state.onGround = true; + + // let the entity know about the collision + self->Collide( groundTrace, state.velocity ); + + // apply impact to a non world floor entity + if ( groundTrace.c.entityNum != ENTITYNUM_WORLD && groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, 0, groundTrace.c.point, state.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +===================== +idPhysics_Monster::SlideMove +===================== +*/ +monsterMoveResult_t idPhysics_Monster::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + + blockingEntity = NULL; + move = delta; + for( i = 0; i < 3; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return MM_SLIDING; + } + return MM_OK; + } + + if ( tr.c.entityNum != ENTITYNUM_NONE ) { + blockingEntity = gameLocal.entities[ tr.c.entityNum ]; + } + + // clip the movement delta and velocity + move.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + } + + return MM_BLOCKED; +} + +/* +===================== +idPhysics_Monster::StepMove + + move start into the delta direction + the velocity is clipped conform any collisions +===================== +*/ +monsterMoveResult_t idPhysics_Monster::StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + trace_t tr; + idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel; + monsterMoveResult_t result1, result2; + float stepdist; + float nostepdist; + + if ( delta == vec3_origin ) { + return MM_OK; + } + + // try to move without stepping up + noStepPos = start; + noStepVel = velocity; + result1 = SlideMove( noStepPos, noStepVel, delta ); + if ( result1 == MM_OK ) { + velocity = noStepVel; +// RAVEN BEGIN +// bdube: dont step when there is no gravity + if ( gravityNormal == vec3_zero || forceDeltaMove ) { +// RAVEN END + start = noStepPos; + return MM_OK; + } + + // try to step down so that we walk down slopes and stairs at a normal rate + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction < 1.0f ) { + start = tr.endpos; + return MM_STEPPED; + } else { + start = noStepPos; + return MM_OK; + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockingEntity && blockingEntity->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // try to step down in case walking into an actor while going down steps + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + start = tr.endpos; + velocity = noStepVel; + return MM_BLOCKED; + } + + if ( gravityNormal == vec3_zero ) { + return result1; + } + + // try to step up + up = start - gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction == 0.0f ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // try to move at the stepped up position + stepPos = tr.endpos; + stepVel = velocity; + result2 = SlideMove( stepPos, stepVel, delta ); + if ( result2 == MM_BLOCKED ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // step down again + down = stepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, stepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + stepPos = tr.endpos; + + // if the move is further without stepping up, or the slope is too steap, don't step up + nostepdist = ( noStepPos - start ).LengthSqr(); + stepdist = ( stepPos - start ).LengthSqr(); + if ( ( nostepdist >= stepdist ) || ( ( tr.c.normal * -gravityNormal ) < minFloorCosine ) ) { + start = noStepPos; + velocity = noStepVel; + return MM_SLIDING; + } + + start = stepPos; + velocity = stepVel; + + return MM_STEPPED; +} + +/* +================ +idPhysics_Monster::Activate +================ +*/ +void idPhysics_Monster::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::Rest +================ +*/ +void idPhysics_Monster::Rest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::PutToRest +================ +*/ +void idPhysics_Monster::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_Monster::idPhysics_Monster +================ +*/ +idPhysics_Monster::idPhysics_Monster( void ) { + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + saved = current; + + delta.Zero(); + maxStepHeight = 18.0f; + minFloorCosine = 0.7f; + moveResult = MM_OK; + forceDeltaMove = false; + fly = false; + useVelocityMove = false; + noImpact = false; + blockingEntity = NULL; +} + +/* +================ +idPhysics_Monster_SavePState +================ +*/ +void idPhysics_Monster_SavePState( idSaveGame *savefile, const monsterPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.onGround ); + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Monster_RestorePState +================ +*/ +void idPhysics_Monster_RestorePState( idRestoreGame *savefile, monsterPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.onGround ); + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson: Added unrestored var +} + +/* +================ +idPhysics_Monster::Save +================ +*/ +void idPhysics_Monster::Save( idSaveGame *savefile ) const { + + idPhysics_Monster_SavePState( savefile, current ); + idPhysics_Monster_SavePState( savefile, saved ); + + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( minFloorCosine ); + savefile->WriteVec3( delta ); + + savefile->WriteBool( forceDeltaMove ); + savefile->WriteBool( fly ); + savefile->WriteBool( useVelocityMove ); + savefile->WriteBool( noImpact ); + + savefile->WriteInt( (int)moveResult ); + savefile->WriteObject( blockingEntity ); +} + +/* +================ +idPhysics_Monster::Restore +================ +*/ +void idPhysics_Monster::Restore( idRestoreGame *savefile ) { + + idPhysics_Monster_RestorePState( savefile, current ); + idPhysics_Monster_RestorePState( savefile, saved ); + + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( minFloorCosine ); + savefile->ReadVec3( delta ); + + savefile->ReadBool( forceDeltaMove ); + savefile->ReadBool( fly ); + savefile->ReadBool( useVelocityMove ); + savefile->ReadBool( noImpact ); + + savefile->ReadInt( (int &)moveResult ); + savefile->ReadObject( reinterpret_cast( blockingEntity ) ); +} + +/* +================ +idPhysics_Monster::SetDelta +================ +*/ +void idPhysics_Monster::SetDelta( const idVec3 &d ) { + delta = d; + if ( delta != vec3_origin ) { + Activate(); + } +} + +/* +================ +idPhysics_Monster::SetMaxStepHeight +================ +*/ +void idPhysics_Monster::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Monster::GetMaxStepHeight +================ +*/ +float idPhysics_Monster::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Monster::OnGround +================ +*/ +bool idPhysics_Monster::OnGround( void ) const { + return current.onGround; +} + +/* +================ +idPhysics_Monster::GetSlideMoveEntity +================ +*/ +idEntity *idPhysics_Monster::GetSlideMoveEntity( void ) const { + return blockingEntity; +} + +/* +================ +idPhysics_Monster::GetMoveResult +================ +*/ +monsterMoveResult_t idPhysics_Monster::GetMoveResult( void ) const { + return moveResult; +} + +/* +================ +idPhysics_Monster::ForceDeltaMove +================ +*/ +void idPhysics_Monster::ForceDeltaMove( bool force ) { + forceDeltaMove = force; +} + +/* +================ +idPhysics_Monster::UseFlyMove +================ +*/ +void idPhysics_Monster::UseFlyMove( bool force ) { + fly = force; +} + +/* +================ +idPhysics_Monster::UseVelocityMove +================ +*/ +void idPhysics_Monster::UseVelocityMove( bool force ) { + useVelocityMove = force; +} + +/* +================ +idPhysics_Monster::EnableImpact +================ +*/ +void idPhysics_Monster::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_Monster::DisableImpact +================ +*/ +void idPhysics_Monster::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_Monster::Evaluate +================ +*/ +bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + float timeStep; + + timeStep = MS2SEC( timeStepMSec ); + + moveResult = MM_OK; + blockingEntity = NULL; + oldOrigin = current.origin; + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + current.velocity = ( current.origin - oldOrigin ) / timeStep; + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + // if the monster is at rest + if ( current.atRest >= 0 ) { + return true; + } + + ActivateContactEntities(); + + // move the monster velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + clipModel->Unlink(); + + // check if on the ground + idPhysics_Monster::CheckGround( current ); + + // if not on the ground or moving upwards + float upspeed; + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + if ( fly || ( !forceDeltaMove && ( !current.onGround || upspeed > 1.0f ) ) ) { + if ( upspeed < 0.0f ) { + moveResult = MM_FALLING; + } + else { + current.onGround = false; + moveResult = MM_OK; + } + delta = current.velocity * timeStep; + if ( delta != vec3_origin ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + + if ( !fly ) { + current.velocity += gravityVector * timeStep; + } + } else { + if ( useVelocityMove ) { + delta = current.velocity * timeStep; + } else { + current.velocity = delta / timeStep; + } + + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + + if ( delta == vec3_origin ) { + Rest(); + } else { + // try moving into the desired direction +// RAVEN BEGIN +// jshepard: flying creatures, even if not using fly move, shouldn't use step move + if( self->IsType( idAI::GetClassType() ) && ((idAI*)self)->move.moveType == MOVETYPE_FLY ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + } else { + moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta ); + } + delta.Zero(); +// RAVEN END + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + // get all the ground contacts + EvaluateContacts(); + + // move the monster velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + Rest(); + } + +// RAVEN BEGIN +// bdube: determine if we moved this frame + return true; +// RAVEN END +} + +/* +================ +idPhysics_Monster::UpdateTime +================ +*/ +void idPhysics_Monster::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Monster::GetTime +================ +*/ +int idPhysics_Monster::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Monster::GetImpactInfo +================ +*/ +void idPhysics_Monster::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Monster::ApplyImpulse +================ +*/ +void idPhysics_Monster::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.velocity += impulse * invMass; + Activate(); +} + +/* +================ +idPhysics_Monster::IsAtRest +================ +*/ +bool idPhysics_Monster::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Monster::GetRestStartTime +================ +*/ +int idPhysics_Monster::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Monster::SaveState +================ +*/ +void idPhysics_Monster::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Monster::RestoreState +================ +*/ +void idPhysics_Monster::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Monster::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Monster::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Translate +================ +*/ +void idPhysics_Monster::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Rotate +================ +*/ +void idPhysics_Monster::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::SetLinearVelocity +================ +*/ +void idPhysics_Monster::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_Monster::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Monster::SetPushed +================ +*/ +void idPhysics_Monster::SetPushed( int deltaTime ) { + // velocity with which the monster is pushed + current.pushVelocity += ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_Monster::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Monster::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Monster::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + Activate(); + } + } +} + +const float MONSTER_VELOCITY_MAX = 4000; +const int MONSTER_VELOCITY_TOTAL_BITS = 16; +const int MONSTER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( MONSTER_VELOCITY_MAX ) ) + 1; +const int MONSTER_VELOCITY_MANTISSA_BITS = MONSTER_VELOCITY_TOTAL_BITS - 1 - MONSTER_VELOCITY_EXPONENT_BITS; + +/* +================ +idPhysics_Monster::WriteToSnapshot +================ +*/ +void idPhysics_Monster::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.velocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteLong( current.atRest ); + msg.WriteBits( current.onGround, 1 ); +} + +/* +================ +idPhysics_Monster::ReadFromSnapshot +================ +*/ +void idPhysics_Monster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.velocity[0] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[1] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[2] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.atRest = msg.ReadLong(); + current.onGround = msg.ReadBits( 1 ) != 0; +} diff --git a/src/game/physics/Physics_Monster.h b/src/game/physics/Physics_Monster.h new file mode 100644 index 0000000..93ac796 --- /dev/null +++ b/src/game/physics/Physics_Monster.h @@ -0,0 +1,152 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_MONSTER_H__ +#define __PHYSICS_MONSTER_H__ + +/* +=================================================================================== + + Monster physics + + Simulates the motion of a monster through the environment. The monster motion + is typically driven by animations. + +=================================================================================== +*/ + +typedef enum { + MM_OK, + MM_SLIDING, + MM_BLOCKED, + MM_STEPPED, + MM_FALLING +} monsterMoveResult_t; + +typedef struct monsterPState_s { + int atRest; + bool onGround; + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; +// RAVEN BEGIN +// bdube: added + idVec3 lastPushVelocity; +// RAVEN END +} monsterPState_t; + +class idPhysics_Monster : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Monster ); + + idPhysics_Monster( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // maximum step up the monster can take, default 18 units + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + // minimum cosine of floor angle to be able to stand on the floor + void SetMinFloorCosine( const float newMinFloorCosine ); + // set delta for next move + void SetDelta( const idVec3 &d ); + // returns true if monster is standing on the ground + bool OnGround( void ) const; + // returns the movement result + monsterMoveResult_t GetMoveResult( void ) const; + // overrides any velocity for pure delta movement + void ForceDeltaMove( bool force ); + // whether velocity should be affected by gravity + void UseFlyMove( bool force ); + // don't use delta movement + void UseVelocityMove( bool force ); + // get entity blocking the move + idEntity * GetSlideMoveEntity( void ) const; + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // monster physics state + monsterPState_t current; + monsterPState_t saved; + + // properties + float maxStepHeight; // maximum step height + float minFloorCosine; // minimum cosine of floor angle + idVec3 delta; // delta for next move + + bool forceDeltaMove; + bool fly; + bool useVelocityMove; + bool noImpact; // if true do not activate when another object collides + + // results of last evaluate + monsterMoveResult_t moveResult; + idEntity * blockingEntity; + +private: + void CheckGround( monsterPState_t &state ); + monsterMoveResult_t SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + monsterMoveResult_t StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + void Rest( void ); +}; + +#endif /* !__PHYSICS_MONSTER_H__ */ diff --git a/src/game/physics/Physics_Parametric.cpp b/src/game/physics/Physics_Parametric.cpp new file mode 100644 index 0000000..926bdf5 --- /dev/null +++ b/src/game/physics/Physics_Parametric.cpp @@ -0,0 +1,1214 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Parametric ) +END_CLASS + + +/* +================ +idPhysics_Parametric::Activate +================ +*/ +void idPhysics_Parametric::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::TestIfAtRest +================ +*/ +bool idPhysics_Parametric::TestIfAtRest( void ) const { + + if ( ( current.linearExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + ( current.angularExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + current.linearInterpolation.GetDuration() == 0 && + current.angularInterpolation.GetDuration() == 0 && + current.spline == NULL ) { + return true; + } + + if ( !current.linearExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.linearInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( current.spline != NULL && !current.spline->IsDone( current.time ) ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_Parametric::Rest +================ +*/ +void idPhysics_Parametric::Rest( void ) { + current.atRest = gameLocal.time; + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::idPhysics_Parametric +================ +*/ +idPhysics_Parametric::idPhysics_Parametric( void ) { + + current.time = gameLocal.time; + current.atRest = -1; + current.useSplineAngles = false; + current.origin.Zero(); + current.angles.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAngles.Zero(); + current.linearExtrapolation.Init( 0, 0, vec3_zero, vec3_zero, vec3_zero, EXTRAPOLATION_NONE ); + current.angularExtrapolation.Init( 0, 0, ang_zero, ang_zero, ang_zero, EXTRAPOLATION_NONE ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + current.spline = NULL; + current.splineInterpolate.Init( 0, 1, 1, 2, 0, 0 ); + + saved = current; + + isPusher = false; + pushFlags = 0; + clipModel = NULL; + isBlocked = false; + memset( &pushResults, 0, sizeof( pushResults ) ); + + hasMaster = false; + isOrientated = false; + +// RAVEN BEGIN +// abahr: + useAxisOffset = false; + axisOffset.Identity(); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::~idPhysics_Parametric +================ +*/ +idPhysics_Parametric::~idPhysics_Parametric( void ) { + if ( clipModel != NULL ) { + delete clipModel; + clipModel = NULL; + } + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } +} + +/* +================ +idPhysics_Parametric_SavePState +================ +*/ +void idPhysics_Parametric_SavePState( idSaveGame *savefile, const parametricPState_t &state ) { + savefile->WriteInt( state.time ); + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.useSplineAngles ); + savefile->WriteVec3( state.origin ); + savefile->WriteAngles( state.angles ); + savefile->WriteMat3( state.axis ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteAngles( state.localAngles ); + + savefile->WriteInt( (int)state.linearExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.linearExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.linearExtrapolation.GetDuration() ); + savefile->WriteVec3( state.linearExtrapolation.GetStartValue() ); + savefile->WriteVec3( state.linearExtrapolation.GetBaseSpeed() ); + savefile->WriteVec3( state.linearExtrapolation.GetSpeed() ); + + savefile->WriteInt( (int)state.angularExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.angularExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.angularExtrapolation.GetDuration() ); + savefile->WriteAngles( state.angularExtrapolation.GetStartValue() ); + savefile->WriteAngles( state.angularExtrapolation.GetBaseSpeed() ); + savefile->WriteAngles( state.angularExtrapolation.GetSpeed() ); + + savefile->WriteFloat( state.linearInterpolation.GetStartTime() ); + savefile->WriteFloat( state.linearInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDuration() ); + savefile->WriteVec3( state.linearInterpolation.GetStartValue() ); + savefile->WriteVec3( state.linearInterpolation.GetEndValue() ); + + savefile->WriteFloat( state.angularInterpolation.GetStartTime() ); + savefile->WriteFloat( state.angularInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDuration() ); + savefile->WriteAngles( state.angularInterpolation.GetStartValue() ); + savefile->WriteAngles( state.angularInterpolation.GetEndValue() ); + + // spline is handled by owner + + savefile->WriteFloat( state.splineInterpolate.GetStartTime() ); + savefile->WriteFloat( state.splineInterpolate.GetAcceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetDuration() ); + savefile->WriteFloat( state.splineInterpolate.GetDeceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetStartValue() ); + savefile->WriteFloat( state.splineInterpolate.GetEndValue() ); +} + +/* +================ +idPhysics_Parametric_RestorePState +================ +*/ +void idPhysics_Parametric_RestorePState( idRestoreGame *savefile, parametricPState_t &state ) { + extrapolation_t etype; + float startTime, duration, accelTime, decelTime, startValue, endValue; + idVec3 linearStartValue, linearBaseSpeed, linearSpeed, startPos, endPos; + idAngles angularStartValue, angularBaseSpeed, angularSpeed, startAng, endAng; + + savefile->ReadInt( state.time ); + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.useSplineAngles ); + savefile->ReadVec3( state.origin ); + savefile->ReadAngles( state.angles ); + savefile->ReadMat3( state.axis ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadAngles( state.localAngles ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( linearStartValue ); + savefile->ReadVec3( linearBaseSpeed ); + savefile->ReadVec3( linearSpeed ); + + state.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, etype ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( angularStartValue ); + savefile->ReadAngles( angularBaseSpeed ); + savefile->ReadAngles( angularSpeed ); + + state.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, etype ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( startPos ); + savefile->ReadVec3( endPos ); + + state.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( startAng ); + savefile->ReadAngles( endAng ); + + state.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + // spline is handled by owner + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( startValue ); + savefile->ReadFloat( endValue ); + + state.splineInterpolate.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idPhysics_Parametric::Save +================ +*/ +void idPhysics_Parametric::Save( idSaveGame *savefile ) const { + + idPhysics_Parametric_SavePState( savefile, current ); + idPhysics_Parametric_SavePState( savefile, saved ); + + savefile->WriteBool( isPusher ); + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( pushFlags ); + + savefile->WriteTrace( pushResults ); + savefile->WriteBool( isBlocked ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); + + savefile->WriteBool ( useAxisOffset ); // cnicholson: Added unsaved var + savefile->WriteMat3 ( axisOffset ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Parametric::Restore +================ +*/ +void idPhysics_Parametric::Restore( idRestoreGame *savefile ) { + + idPhysics_Parametric_RestorePState( savefile, current ); + idPhysics_Parametric_RestorePState( savefile, saved ); + + savefile->ReadBool( isPusher ); + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( pushFlags ); + + savefile->ReadTrace( pushResults ); + savefile->ReadBool( isBlocked ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); + + savefile->ReadBool ( useAxisOffset ); // cnicholson: Added unrestored var + savefile->ReadMat3 ( axisOffset ); // cnicholson: Added unrestored var + +} + +/* +================ +idPhysics_Parametric::SetPusher +================ +*/ +void idPhysics_Parametric::SetPusher( int flags ) { + assert( clipModel ); + isPusher = true; + pushFlags = flags; +} + +/* +================ +idPhysics_Parametric::IsPusher +================ +*/ +bool idPhysics_Parametric::IsPusher( void ) const { + return isPusher; +} + +/* +================ +idPhysics_Parametric::SetLinearExtrapolation +================ +*/ +void idPhysics_Parametric::SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ) { + current.time = gameLocal.time; + current.linearExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localOrigin = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularExtrapolation +================ +*/ +void idPhysics_Parametric::SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ) { + current.time = gameLocal.time; + current.angularExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localAngles = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetLinearExtrapolationType( void ) const { + return current.linearExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::GetAngularExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetAngularExtrapolationType( void ) const { + return current.angularExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::SetLinearInterpolation +================ +*/ +void idPhysics_Parametric::SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ) { + current.time = gameLocal.time; + current.linearInterpolation.Init( time, accelTime, decelTime, duration, startPos, endPos ); + current.localOrigin = startPos; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularInterpolation +================ +*/ +void idPhysics_Parametric::SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ) { + current.time = gameLocal.time; + current.angularInterpolation.Init( time, accelTime, decelTime, duration, startAng, endAng ); + current.localAngles = startAng; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetSpline +================ +*/ +void idPhysics_Parametric::SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ) { + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } + current.spline = spline; + if ( current.spline != NULL ) { + float startTime = current.spline->GetTime( 0 ); + float endTime = current.spline->GetTime( current.spline->GetNumValues() - 1 ); + float length = current.spline->GetLengthForTime( endTime ); + current.splineInterpolate.Init( startTime, accelTime, decelTime, endTime - startTime, 0.0f, length ); + } + current.useSplineAngles = useSplineAngles; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetSpline +================ +*/ +idCurve_Spline *idPhysics_Parametric::GetSpline( void ) const { + return current.spline; +} + +/* +================ +idPhysics_Parametric::GetSplineAcceleration +================ +*/ +int idPhysics_Parametric::GetSplineAcceleration( void ) const { + return current.splineInterpolate.GetAcceleration(); +} + +/* +================ +idPhysics_Parametric::GetSplineDeceleration +================ +*/ +int idPhysics_Parametric::GetSplineDeceleration( void ) const { + return current.splineInterpolate.GetDeceleration(); +} + +/* +================ +idPhysics_Parametric::UsingSplineAngles +================ +*/ +bool idPhysics_Parametric::UsingSplineAngles( void ) const { + return current.useSplineAngles; +} + +/* +================ +idPhysics_Parametric::GetLocalOrigin +================ +*/ +void idPhysics_Parametric::GetLocalOrigin( idVec3 &curOrigin ) const { + curOrigin = current.localOrigin; +} + +/* +================ +idPhysics_Parametric::GetLocalAngles +================ +*/ +void idPhysics_Parametric::GetLocalAngles( idAngles &curAngles ) const { + curAngles = current.localAngles; +} + +/* +================ +idPhysics_Parametric::SetClipModel +================ +*/ +void idPhysics_Parametric::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::GetClipModel +================ +*/ +idClipModel *idPhysics_Parametric::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Parametric::GetNumClipModels +================ +*/ +int idPhysics_Parametric::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Parametric::SetMass +================ +*/ +void idPhysics_Parametric::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Parametric::GetMass +================ +*/ +float idPhysics_Parametric::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +void idPhysics_Parametric::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +int idPhysics_Parametric::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Parametric::GetBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return idPhysics_Base::GetBounds(); +} + +/* +================ +idPhysics_Parametric::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetAbsBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + return idPhysics_Base::GetAbsBounds(); +} + +/* +================ +idPhysics_Parametric::Evaluate +================ +*/ +bool idPhysics_Parametric::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 oldLocalOrigin, oldOrigin, masterOrigin; + idAngles oldLocalAngles, oldAngles; + idMat3 oldAxis, masterAxis; + + isBlocked = false; + oldLocalOrigin = current.localOrigin; + oldOrigin = current.origin; + oldLocalAngles = current.localAngles; + oldAngles = current.angles; + oldAxis = current.axis; + + current.localOrigin.Zero(); + current.localAngles.Zero(); + + if ( current.spline != NULL ) { + float length = current.splineInterpolate.GetCurrentValue( endTimeMSec ); + float t = current.spline->GetTimeForLength( length, 0.01f ); + current.localOrigin = current.spline->GetCurrentValue( t ); + if ( current.useSplineAngles ) { + current.localAngles = current.spline->GetCurrentFirstDerivative( t ).ToAngles(); + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + current.localOrigin += current.linearInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localOrigin += current.linearExtrapolation.GetCurrentValue( endTimeMSec ); + } + + if ( current.angularInterpolation.GetDuration() != 0 ) { + current.localAngles += current.angularInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localAngles += current.angularExtrapolation.GetCurrentValue( endTimeMSec ); + } + + current.localAngles.Normalize360(); + current.origin = current.localOrigin; + current.angles = current.localAngles; + current.axis = current.localAngles.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( masterAxis.IsRotated() ) { + current.origin = current.origin * masterAxis + masterOrigin; + if ( isOrientated ) { + current.axis *= masterAxis; + current.angles = current.axis.ToAngles(); + } + } + else { + current.origin += masterOrigin; + } + } + + if ( isPusher ) { + + gameLocal.push.ClipPush( pushResults, self, pushFlags, oldOrigin, oldAxis, current.origin, current.axis ); + if ( pushResults.fraction < 1.0f ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, oldOrigin, oldAxis ); +// RAVEN END + current.localOrigin = oldLocalOrigin; + current.origin = oldOrigin; + current.localAngles = oldLocalAngles; + current.angles = oldAngles; + current.axis = oldAxis; + isBlocked = true; + return false; + } + + current.angles = current.axis.ToAngles(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// abahr: a hack way of hiding gimble lock from movers. + clipModel->Link( self, 0, current.origin, UseAxisOffset() ? GetAxisOffset() * current.axis : current.axis ); +// RAVEN END + } + + current.time = endTimeMSec; + + if ( TestIfAtRest() ) { + Rest(); + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); +} + +/* +================ +idPhysics_Parametric::UpdateTime +================ +*/ +void idPhysics_Parametric::UpdateTime( int endTimeMSec ) { + int timeLeap = endTimeMSec - current.time; + + current.time = endTimeMSec; + // move the trajectory start times to sync the trajectory with the current endTime + current.linearExtrapolation.SetStartTime( current.linearExtrapolation.GetStartTime() + timeLeap ); + current.angularExtrapolation.SetStartTime( current.angularExtrapolation.GetStartTime() + timeLeap ); + current.linearInterpolation.SetStartTime( current.linearInterpolation.GetStartTime() + timeLeap ); + current.angularInterpolation.SetStartTime( current.angularInterpolation.GetStartTime() + timeLeap ); + if ( current.spline != NULL ) { + current.spline->ShiftTime( timeLeap ); + current.splineInterpolate.SetStartTime( current.splineInterpolate.GetStartTime() + timeLeap ); + } +} + +/* +================ +idPhysics_Parametric::GetTime +================ +*/ +int idPhysics_Parametric::GetTime( void ) const { + return current.time; +} + +/* +================ +idPhysics_Parametric::IsAtRest +================ +*/ +bool idPhysics_Parametric::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Parametric::GetRestStartTime +================ +*/ +int idPhysics_Parametric::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Parametric::IsPushable +================ +*/ +bool idPhysics_Parametric::IsPushable( void ) const { + return false; +} + +/* +================ +idPhysics_Parametric::SaveState +================ +*/ +void idPhysics_Parametric::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Parametric::RestoreState +================ +*/ +void idPhysics_Parametric::RestoreState( void ) { + + current = saved; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::SetOrigin +================ +*/ +void idPhysics_Parametric::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.linearExtrapolation.SetStartValue( newOrigin ); + current.linearInterpolation.SetStartValue( newOrigin ); + + current.localOrigin = current.linearExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAxis +================ +*/ +void idPhysics_Parametric::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAngles = newAxis.ToAngles(); + + current.angularExtrapolation.SetStartValue( current.localAngles ); + current.angularInterpolation.SetStartValue( current.localAngles ); + + current.localAngles = current.angularExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAngles.ToMat3() * masterAxis; + current.angles = current.axis.ToAngles(); + } + else { + current.axis = current.localAngles.ToMat3(); + current.angles = current.localAngles; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::Move +================ +*/ +void idPhysics_Parametric::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Parametric::Rotate +================ +*/ +void idPhysics_Parametric::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Parametric::GetOrigin +================ +*/ +const idVec3 &idPhysics_Parametric::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Parametric::GetAxis +================ +*/ +const idMat3 &idPhysics_Parametric::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Parametric::GetAngles +================ +*/ +void idPhysics_Parametric::GetAngles( idAngles &curAngles ) const { + curAngles = current.angles; +} + +/* +================ +idPhysics_Parametric::SetLinearVelocity +================ +*/ +void idPhysics_Parametric::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.origin, newLinearVelocity, vec3_origin ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularVelocity +================ +*/ +void idPhysics_Parametric::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + idRotation rotation; + idVec3 vec; + float angle; + + vec = newAngularVelocity; + angle = vec.Normalize(); + rotation.Set( vec3_origin, vec, (float) RAD2DEG( angle ) ); + + SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.angles, rotation.ToAngles(), ang_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + + curLinearVelocity = current.linearExtrapolation.GetCurrentSpeed( gameLocal.time ); + return curLinearVelocity; +} + +/* +================ +idPhysics_Parametric::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idAngles angles; + + angles = current.angularExtrapolation.GetCurrentSpeed( gameLocal.time ); + curAngularVelocity = angles.ToAngularVelocity(); + return curAngularVelocity; +} + +/* +================ +idPhysics_Parametric::DisableClip +================ +*/ +void idPhysics_Parametric::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Parametric::EnableClip +================ +*/ +void idPhysics_Parametric::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Parametric::UnlinkClip +================ +*/ +void idPhysics_Parametric::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Parametric::LinkClip +================ +*/ +void idPhysics_Parametric::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Parametric::GetBlockingInfo( void ) const { + return ( isBlocked ? &pushResults : NULL ); +} + +/* +================ +idPhysics_Parametric::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Parametric::GetBlockingEntity( void ) const { + if ( isBlocked ) { + return gameLocal.entities[ pushResults.c.entityNum ]; + } + return NULL; +} + +/* +================ +idPhysics_Parametric::SetMaster +================ +*/ +void idPhysics_Parametric::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAngles = ( current.axis * masterAxis.Transpose() ).ToAngles(); + } + else { + current.localAngles = current.axis.ToAngles(); + } + + current.linearExtrapolation.SetStartValue( current.localOrigin ); + current.angularExtrapolation.SetStartValue( current.localAngles ); + hasMaster = true; + isOrientated = orientated; + } + } + else { + if ( hasMaster ) { + // transform from master space to world space + current.localOrigin = current.origin; + current.localAngles = current.angles; + SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.origin, vec3_origin, vec3_origin ); + SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.angles, ang_zero, ang_zero ); + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Parametric::GetLinearEndTime +================ +*/ +int idPhysics_Parametric::GetLinearEndTime( void ) const { + if ( current.spline != NULL ) { + if ( current.spline->GetBoundaryType() != idCurve_Spline::BT_CLOSED ) { + return current.spline->GetTime( current.spline->GetNumValues() - 1 ); + } else { + return 0; + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + return current.linearInterpolation.GetEndTime(); + } else { + return current.linearExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::GetAngularEndTime +================ +*/ +int idPhysics_Parametric::GetAngularEndTime( void ) const { + if ( current.angularInterpolation.GetDuration() != 0 ) { + return current.angularInterpolation.GetEndTime(); + } else { + return current.angularExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::WriteToSnapshot +================ +*/ +void idPhysics_Parametric::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.time ); + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.angles[0] ); + msg.WriteFloat( current.angles[1] ); + msg.WriteFloat( current.angles[2] ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( current.angles[0], current.localAngles[0] ); + msg.WriteDeltaFloat( current.angles[1], current.localAngles[1] ); + msg.WriteDeltaFloat( current.angles[2], current.localAngles[2] ); + + msg.WriteBits( current.linearExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteBits( current.angularExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[2] ); +} + +/* +================ +idPhysics_Parametric::ReadFromSnapshot +================ +*/ +void idPhysics_Parametric::ReadFromSnapshot( const idBitMsgDelta &msg ) { + extrapolation_t linearType, angularType; + float startTime, duration, accelTime, decelTime; + idVec3 linearStartValue, linearSpeed, linearBaseSpeed, startPos, endPos; + idAngles angularStartValue, angularSpeed, angularBaseSpeed, startAng, endAng; + + current.time = msg.ReadLong(); + current.atRest = msg.ReadLong(); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.angles[0] = msg.ReadFloat(); + current.angles[1] = msg.ReadFloat(); + current.angles[2] = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAngles[0] = msg.ReadDeltaFloat( current.angles[0] ); + current.localAngles[1] = msg.ReadDeltaFloat( current.angles[1] ); + current.localAngles[2] = msg.ReadDeltaFloat( current.angles[2] ); + + linearType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, linearType ); + + angularType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, angularType ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startPos[0] = msg.ReadDeltaFloat( 0.0f ); + startPos[1] = msg.ReadDeltaFloat( 0.0f ); + startPos[2] = msg.ReadDeltaFloat( 0.0f ); + endPos[0] = msg.ReadDeltaFloat( 0.0f ); + endPos[1] = msg.ReadDeltaFloat( 0.0f ); + endPos[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startAng[0] = msg.ReadDeltaFloat( 0.0f ); + startAng[1] = msg.ReadDeltaFloat( 0.0f ); + startAng[2] = msg.ReadDeltaFloat( 0.0f ); + endAng[0] = msg.ReadDeltaFloat( 0.0f ); + endAng[1] = msg.ReadDeltaFloat( 0.0f ); + endAng[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + current.axis = current.angles.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} diff --git a/src/game/physics/Physics_Parametric.h b/src/game/physics/Physics_Parametric.h new file mode 100644 index 0000000..68fe9a7 --- /dev/null +++ b/src/game/physics/Physics_Parametric.h @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_PARAMETRIC_H__ +#define __PHYSICS_PARAMETRIC_H__ + +/* +=================================================================================== + + Parametric physics + + Used for predefined or scripted motion. The motion of an object is completely + parametrized. By adjusting the parameters an object is forced to follow a + predefined path. The parametric physics is typically used for doors, bridges, + rotating fans etc. + +=================================================================================== +*/ + +typedef struct parametricPState_s { + int time; // physics time + int atRest; // set when simulation is suspended + idVec3 origin; // world origin + idAngles angles; // world angles + idMat3 axis; // world axis + idVec3 localOrigin; // local origin + idAngles localAngles; // local angles + idExtrapolate linearExtrapolation; // extrapolation based description of the position over time + idExtrapolate angularExtrapolation; // extrapolation based description of the orientation over time + idInterpolateAccelDecelLinear linearInterpolation; // interpolation based description of the position over time + idInterpolateAccelDecelLinear angularInterpolation; // interpolation based description of the orientation over time + idCurve_Spline * spline; // spline based description of the position over time + idInterpolateAccelDecelLinear splineInterpolate; // position along the spline over time + bool useSplineAngles; // set the orientation using the spline +} parametricPState_t; + +class idPhysics_Parametric : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Parametric ); + + idPhysics_Parametric( void ); + ~idPhysics_Parametric( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPusher( int flags ); + bool IsPusher( void ) const; + + void SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ); + void SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ); + extrapolation_t GetLinearExtrapolationType( void ) const; + extrapolation_t GetAngularExtrapolationType( void ) const; + + void SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ); + void SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ); + + void SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ); + idCurve_Spline *GetSpline( void ) const; + int GetSplineAcceleration( void ) const; + int GetSplineDeceleration( void ) const; + bool UsingSplineAngles( void ) const; + + void GetLocalOrigin( idVec3 &curOrigin ) const; + void GetLocalAngles( idAngles &curAngles ) const; + + void GetAngles( idAngles &curAngles ) const; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + void SetAxisOffset( const idMat3& offset ) { axisOffset = offset; useAxisOffset = true; } + const idMat3& GetAxisOffset() const { return axisOffset; } + idMat3& GetAxisOffset() { return axisOffset; } + bool UseAxisOffset() const { return useAxisOffset; } +// RAVEN END + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // parametric physics state + parametricPState_t current; + parametricPState_t saved; + + // pusher + bool isPusher; + idClipModel * clipModel; + int pushFlags; + + // results of last evaluate + trace_t pushResults; + bool isBlocked; + + // master + bool hasMaster; + bool isOrientated; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + bool useAxisOffset; + idMat3 axisOffset; +// RAVEN END + +private: + bool TestIfAtRest( void ) const; + void Rest( void ); +}; + +#endif /* !__PHYSICS_PARAMETRIC_H__ */ diff --git a/src/game/physics/Physics_Particle.cpp b/src/game/physics/Physics_Particle.cpp new file mode 100644 index 0000000..548c1fa --- /dev/null +++ b/src/game/physics/Physics_Particle.cpp @@ -0,0 +1,1046 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Particle ) +END_CLASS + +const float PRT_OVERCLIP = 1.001f; +const float PRT_BOUNCESTOP = 10.0f; + +/* +================ +rvPhysics_Particle::DropToFloorAndRest + +Drops the object straight down to the floor +================ +*/ +void rvPhysics_Particle::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + testSolid = false; + if ( gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ) ) { + gameLocal.Warning( "entity in solid '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.origin + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + current.origin = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, clipModel->GetAxis() ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + PutToRest(); + EvaluateContacts();//Do a final contact check. Items that drop to floor never do this check otherwise + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "entity outside world bounds '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + } +} + +/* +================ +rvPhysics_Particle::DebugDraw +================ +*/ +void rvPhysics_Particle::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +} + +/* +================ +rvPhysics_Particle::rvPhysics_Particle +================ +*/ +rvPhysics_Particle::rvPhysics_Particle( void ) { + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f, true ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + current.origin.Zero(); + saved = current; + + dropToFloor = false; + testSolid = false; + hasMaster = false; + + SetFriction( 0.6f, 0.6f, 0.0f ); + SetBouncyness ( 0.5f, true ); + + gravityNormal.Zero(); +} + +/* +================ +rvPhysics_Particle::~rvPhysics_Particle +================ +*/ +rvPhysics_Particle::~rvPhysics_Particle( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +rvPhysics_Particle::Save +================ +*/ +void rvPhysics_Particle::Save( idSaveGame *savefile ) const { + savefile->WriteInt( current.atRest ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteVec3( current.pushVelocity ); + savefile->WriteVec3( current.origin ); + savefile->WriteVec3( current.velocity ); + savefile->WriteBool( current.onGround ); + savefile->WriteBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->WriteInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->WriteVec3( saved.localOrigin ); + savefile->WriteMat3( saved.localAxis ); + savefile->WriteVec3( saved.pushVelocity ); + savefile->WriteVec3( saved.origin ); + savefile->WriteVec3( saved.velocity ); + savefile->WriteBool( saved.onGround ); + savefile->WriteBool( saved.inWater ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteBool ( allowBounce ); + savefile->WriteBounds ( clipModel->GetBounds ( ) ); // cnicholson: Added unsaved var + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); // cnicholson: Added unsaved var + extraPassEntity.Save( savefile ); + + savefile->WriteClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::Restore +================ +*/ +void rvPhysics_Particle::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( current.atRest ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadVec3( current.pushVelocity ); + savefile->ReadVec3( current.origin ); + savefile->ReadVec3( current.velocity ); + savefile->ReadBool( current.onGround ); + savefile->ReadBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->ReadInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->ReadVec3( saved.localOrigin ); + savefile->ReadMat3( saved.localAxis ); + savefile->ReadVec3( saved.pushVelocity ); + savefile->ReadVec3( saved.origin ); + savefile->ReadVec3( saved.velocity ); + savefile->ReadBool( saved.onGround ); + savefile->ReadBool( saved.inWater ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadBool ( allowBounce ); + + idBounds bounds; // cnicholson: Added unrestored var + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); // cnicholson: Added unrestored var + extraPassEntity.Restore( savefile ); + + savefile->ReadClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::SetClipModel +================ +*/ +void rvPhysics_Particle::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::GetClipModel +================ +*/ +idClipModel *rvPhysics_Particle::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Particle::GetNumClipModels +================ +*/ +int rvPhysics_Particle::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +rvPhysics_Particle::SetBouncyness +================ +*/ +void rvPhysics_Particle::SetBouncyness( const float b, bool _allowBounce ) { + allowBounce = _allowBounce; + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +rvPhysics_Particle::SetFriction +================ +*/ +void rvPhysics_Particle::SetFriction( const float linear, const float angular, const float contact ) { + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + + +/* +================ +rvPhysics_Particle::PutToRest +================ +*/ +void rvPhysics_Particle::PutToRest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::DropToFloor +================ +*/ +void rvPhysics_Particle::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +rvPhysics_Particle::Activate +================ +*/ +void rvPhysics_Particle::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::EvaluateContacts +================ +*/ +bool rvPhysics_Particle::EvaluateContacts( void ) { + ClearContacts(); + AddGroundContacts( clipModel ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +rvPhysics_Particle::SetContents +================ +*/ +void rvPhysics_Particle::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Particle::GetContents +================ +*/ +int rvPhysics_Particle::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Particle::GetBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Particle::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Particle::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_Particle::Evaluate( int timeStepMSec, int endTimeMSec ) { + particlePState_t next; + float timeStep; + float upspeed; + + timeStep = MS2SEC( timeStepMSec ); + + // if bound to a master + if ( hasMaster ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idVec3 oldOrigin; + + oldOrigin = current.origin; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, current.localAxis * masterAxis ); +// RAVEN END + + trace_t tr; + gameLocal.Translation( self, tr, oldOrigin, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( tr.fraction < 1.0f ) { + self->Collide ( tr, current.origin - oldOrigin ); + } + + DebugDraw(); + + return true; + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + return true; + } + + clipModel->Unlink(); + + // Determine if currently on the ground + CheckGround ( ); + + // Determine the current upward velocity + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + + // If not on the ground, or moving upwards, or bouncing and moving toward gravity then do a straight + // forward slide move and gravity. + if ( !current.onGround || upspeed > 1.0f || (bouncyness > 0.0f && upspeed < -PRT_BOUNCESTOP && !current.inWater) ) { + // Force ground off when moving upward + if ( upspeed > 0.0f ) { + current.onGround = false; + } + SlideMove( current.origin, current.velocity, current.velocity * timeStep ); + if ( current.onGround && upspeed < PRT_BOUNCESTOP ) { + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } else { + current.velocity += (gravityVector * timeStep); + } + } else { + idVec3 delta; + + // Slow down due to friction + ApplyFriction ( timeStep ); + + delta = current.velocity * timeStep; + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + if ( delta == vec3_origin ) { + PutToRest( ); + } else { + SlideMove( current.origin, current.velocity, delta ); + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + DebugDraw(); + + // get all the ground contacts + EvaluateContacts(); + + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + PutToRest(); + } + + return true; +} + +/* +================ +rvPhysics_Particle::UpdateTime +================ +*/ +void rvPhysics_Particle::UpdateTime( int endTimeMSec ) { +} + +/* +================ +rvPhysics_Particle::GetTime +================ +*/ +int rvPhysics_Particle::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +rvPhysics_Particle::IsAtRest +================ +*/ +bool rvPhysics_Particle::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +rvPhysics_Particle::GetRestStartTime +================ +*/ +int rvPhysics_Particle::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +rvPhysics_Particle::IsPushable +================ +*/ +bool rvPhysics_Particle::IsPushable( void ) const { + return ( !hasMaster ); +} + +/* +================ +rvPhysics_Particle::SaveState +================ +*/ +void rvPhysics_Particle::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Particle::RestoreState +================ +*/ +void rvPhysics_Particle::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Particle::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Particle::SetAxis( const idMat3 &newAxis, int id ) { + current.localAxis = newAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::Translate +================ +*/ +void rvPhysics_Particle::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +rvPhysics_Particle::Rotate( +================ +*/ +void rvPhysics_Particle::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Particle::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +rvPhysics_Particle::GetAxis +================ +*/ +const idMat3 &rvPhysics_Particle::GetAxis( int id ) const { + if ( !clipModel ) { + return idPhysics_Base::GetAxis ( id ); + } + return clipModel->GetAxis(); +} + +/* +================ +rvPhysics_Particle::SetLinearVelocity +================ +*/ +void rvPhysics_Particle::SetLinearVelocity( const idVec3 &velocity, int id ) { + current.velocity = velocity; + Activate(); +} + +/* +================ +rvPhysics_Particle::GetLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +rvPhysics_Particle::ClipTranslation +================ +*/ +void rvPhysics_Particle::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipRotation +================ +*/ +void rvPhysics_Particle::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipContents +================ +*/ +int rvPhysics_Particle::ClipContents( const idClipModel *model ) const { + if ( model ) { + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); + } +} + +/* +================ +rvPhysics_Particle::DisableClip +================ +*/ +void rvPhysics_Particle::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +rvPhysics_Particle::EnableClip +================ +*/ +void rvPhysics_Particle::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +rvPhysics_Particle::UnlinkClip +================ +*/ +void rvPhysics_Particle::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +rvPhysics_Particle::LinkClip +================ +*/ +void rvPhysics_Particle::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::SetPushed +================ +*/ +void rvPhysics_Particle::SetPushed( int deltaTime ) { + // velocity with which the particle is pushed + current.pushVelocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +rvPhysics_Particle::GetPushedLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity; +} + +/* +================ +rvPhysics_Particle::SetMaster +================ +*/ +void rvPhysics_Particle::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + hasMaster = true; + } + ClearContacts(); + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +/* +===================== +rvPhysics_Particle::CheckGround +===================== +*/ +void rvPhysics_Particle::CheckGround( void ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + current.onGround = false; + return; + } + + down = current.origin + gravityNormal * CONTACT_EPSILON; + gameLocal.Translation( self, groundTrace, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( groundTrace.fraction == 1.0f ) { + current.onGround = false; + return; + } + + if ( ( groundTrace.c.normal * -gravityNormal ) < 0.7f ) { + current.onGround = false; + return; + } + + current.onGround = true; +} + +/* +================ +rvPhysics_Particle::ApplyFriction +================ +*/ +void rvPhysics_Particle::ApplyFriction( float timeStep ) { + idVec3 vel; + float speed; + float newspeed; + + // ignore slope movement, remove all velocity in gravity direction + vel = current.velocity + (current.velocity * gravityNormal) * gravityNormal; + + speed = vel.Length(); + if ( speed < 1.0f ) { + // remove all movement orthogonal to gravity, allows for sinking underwater + current.velocity = (current.velocity * gravityNormal) * gravityNormal; + return; + } + + // scale the velocity + if ( current.onGround ) { + newspeed = speed - ((speed * contactFriction) * timeStep); + } else { + newspeed = speed - ((speed * linearFriction) * timeStep); + } + + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= ( newspeed / speed ); +} + +/* +================ +rvPhysics_Particle::SlideMove +================ +*/ +bool rvPhysics_Particle::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + bool collide, rtnValue = false; + + move = delta; + for( i = 0; i < 3; i++ ) { // be sure if you change this upper value in the for() to update the exit condition below!!!!! + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self, extraPassEntity ); + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return false; + } + return true; + } + + bool hitTeleporter = false; + + // let the entity know about the collision + collide = self->Collide( tr, current.velocity, hitTeleporter ); + + idEntity* ent; + ent = gameLocal.entities[tr.c.entityNum]; + assert ( ent ); + + // If we hit water just clip the move for now and keep on going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + // Make sure we dont collide with water again + clipMask &= ~CONTENTS_WATER; + + current.inWater = true; + + // Allow the loop to go one more round to push us through the water + i--; + + velocity *= 0.4f; + + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + continue; + // bounce the projectile + } else if ( !current.inWater && allowBounce && bouncyness ) { + if ( !hitTeleporter ) { + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ) * bouncyness; + } + return true; +//RAVEN BEGIN +//jshepard: tr.c.material can (did) crash here if null + } else if ( allowBounce && tr.c.material && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) ) { +//RAVEN END + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ); + return true; + } +// RAVEN BEGIN +// dluetscher: removed redundant trace calls + else { + i = 4; + rtnValue = true; + } +// RAVEN END + + // clip the movement delta and velocity + if( collide ) { + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + } + } + + return rtnValue; +} + +const float PRT_VELOCITY_MAX = 16000; +const int PRT_VELOCITY_TOTAL_BITS = 16; +const int PRT_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PRT_VELOCITY_MAX ) ) + 1; +const int PRT_VELOCITY_MANTISSA_BITS = PRT_VELOCITY_TOTAL_BITS - 1 - PRT_VELOCITY_EXPONENT_BITS; + +/* +================ +rvPhysics_Particle::WriteToSnapshot +================ +*/ +void rvPhysics_Particle::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.atRest ); + msg.WriteBits ( current.onGround, 1 ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); +// msg.WriteFloat( current.velocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + // TODO: Check that this conditional write to delta message is OK + if ( hasMaster ) { + idCQuat localQuat; + localQuat = current.localAxis.ToCQuat(); + + msg.WriteBits ( 1, 1 ); + msg.WriteFloat( localQuat.x ); + msg.WriteFloat( localQuat.y ); + msg.WriteFloat( localQuat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + } else { + msg.WriteBits ( 0, 1 ); + } +} + +/* +================ +rvPhysics_Particle::ReadFromSnapshot +================ +*/ +void rvPhysics_Particle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.atRest = msg.ReadLong(); + current.onGround = ( msg.ReadBits( 1 ) != 0 ); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); +// current.velocity[0] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[1] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[2] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); +// current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + if ( msg.ReadBits ( 1 ) ) { + idCQuat localQuat; + localQuat.x = msg.ReadFloat( ); + localQuat.y = msg.ReadFloat( ); + localQuat.z = msg.ReadFloat( ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAxis = localQuat.ToMat3(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + } +} diff --git a/src/game/physics/Physics_Particle.h b/src/game/physics/Physics_Particle.h new file mode 100644 index 0000000..47b9788 --- /dev/null +++ b/src/game/physics/Physics_Particle.h @@ -0,0 +1,176 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_PARTICLE_H__ +#define __PHYSICS_PARTICLE_H__ + +/* +=================================================================================== + + Particle Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct particlePState_s { + int atRest; // set when simulation is suspended + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec3 pushVelocity; // push velocity + idVec3 origin; + idVec3 velocity; + bool onGround; + bool inWater; +} particlePState_t; + +class rvPhysics_Particle : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Particle ); + + rvPhysics_Particle( void ); + ~rvPhysics_Particle( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b, bool allowBounce ); + + // put to rest untill something collides with this physics object + void PutToRest( void ); + // same as above but drop to the floor first + void DropToFloor( void ); + + // returns true if touching the ground + bool IsOnGround ( void ) const; + bool IsInWater ( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + bool CanBounce ( void ) const; + + bool EvaluateContacts( void ); + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + idEntityPtr extraPassEntity; + +private: + // state of the particle + particlePState_t current; + particlePState_t saved; + + // particle properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + bool allowBounce; // Allowed to bounce at all? + idClipModel * clipModel; // clip model used for collision detection + + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for inside solid during a drop + + // master + bool hasMaster; + bool isOrientated; + +private: + + void DropToFloorAndRest ( void ); + bool SlideMove ( idVec3& start, idVec3& velocity, const idVec3& delta ); + void CheckGround ( void ); + void ApplyFriction ( float timeStep ); + void DebugDraw ( void ); +}; + +ID_INLINE bool rvPhysics_Particle::IsOnGround ( void ) const { + return current.onGround; +} + +ID_INLINE bool rvPhysics_Particle::IsInWater ( void ) const { + return current.inWater; +} + +ID_INLINE bool rvPhysics_Particle::CanBounce ( void ) const { + return allowBounce; +} + +#endif /* !__PHYSICS_PARTICLE_H__ */ diff --git a/src/game/physics/Physics_Player.cpp b/src/game/physics/Physics_Player.cpp new file mode 100644 index 0000000..0b3d577 --- /dev/null +++ b/src/game/physics/Physics_Player.cpp @@ -0,0 +1,2333 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player ) +END_CLASS + +// movement parameters +const float PM_STOPSPEED = 100.0f; +const float PM_SWIMSCALE = 0.5f; +const float PM_LADDERSPEED = 100.0f; +const float PM_STEPSCALE = 1.0f; + +const float PM_ACCELERATE_SP = 10.0f; +const float PM_AIRACCELERATE_SP = 1.0f; +const float PM_ACCELERATE_MP = 15.0f; +const float PM_AIRACCELERATE_MP = 1.18f; +const float PM_WATERACCELERATE = 4.0f; +const float PM_FLYACCELERATE = 8.0f; + +const float PM_FRICTION = 6.0f; +const float PM_AIRFRICTION = 0.0f; +const float PM_WATERFRICTION = 2.0f; +const float PM_FLYFRICTION = 3.0f; +const float PM_NOCLIPFRICTION = 12.0f; +// RAVEN BEGIN +// bdube: sliding +const float PM_SLIDEFRICTION = 0.5f; +// RAVEN END + +const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes +const float OVERCLIP = 1.001f; + +// movementFlags +const int PMF_DUCKED = 1; // set when ducking +const int PMF_JUMPED = 2; // set when the player jumped this frame +const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame +const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame +const int PMF_JUMP_HELD = 16; // set when jump button is held down +const int PMF_TIME_LAND = 32; // movementTime is time before rejump +const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time +const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump +const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK); + +int c_pmove = 0; + +float idPhysics_Player::Pm_Accelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_ACCELERATE_MP : PM_ACCELERATE_SP; +} + +float idPhysics_Player::Pm_AirAccelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_AIRACCELERATE_MP : PM_AIRACCELERATE_SP; +} + +/* +============ +idPhysics_Player::CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const { + int max; + float total; + float scale; + int forwardmove; + int rightmove; + int upmove; + + forwardmove = cmd.forwardmove; + rightmove = cmd.rightmove; + + // since the crouch key doubles as downward movement, ignore downward movement when we're on the ground + // otherwise crouch speed will be lower than specified + if ( walking ) { + upmove = 0; + } else { + upmove = cmd.upmove; + } + + max = abs( forwardmove ); + if ( abs( rightmove ) > max ) { + max = abs( rightmove ); + } + if ( abs( upmove ) > max ) { + max = abs( upmove ); + } + + if ( !max ) { + return 0.0f; + } + + total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove ); + scale = (float) playerSpeed * max / ( 127.0f * total ); + + return scale; +} + +/* +============== +idPhysics_Player::Accelerate + +Handles user intended acceleration +============== +*/ +void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) { +#if 1 + // q2 style + float addspeed, accelspeed, currentspeed; + + currentspeed = current.velocity * wishdir; + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } +// RAVEN BEGIN +// nmckenzie: added ability to try alternate accelerations. + if ( pm_acceloverride.GetFloat() > 0.0f ) { + accelspeed = pm_acceloverride.GetFloat() * frametime * wishspeed; + } else { + accelspeed = accel * frametime * wishspeed; + } +// RAVEN END + if ( accelspeed > addspeed ) { + accelspeed = addspeed; + } + + current.velocity += accelspeed * wishdir; + +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + idVec3 wishVelocity; + idVec3 pushDir; + float pushLen; + float canPush; + + wishVelocity = wishdir * wishspeed; + pushDir = wishVelocity - current.velocity; + pushLen = pushDir.Normalize(); + + canPush = accel * frametime * wishspeed; + if ( canPush > pushLen ) { + canPush = pushLen; + } + + current.velocity += canPush * pushDir; +#endif +} + +/* +================== +idPhysics_Player::SlideMove + +Returns true if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 + +bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ) { + int i, j, k, pushFlags; + int bumpcount, numbumps, numplanes; + float d, time_left, into, totalMass; + idVec3 dir, planes[MAX_CLIP_PLANES]; + idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity; + trace_t trace, stepTrace, downTrace; + bool nearGround, stepped, pushed; + + numbumps = 4; + + primal_velocity = current.velocity; + + if ( gravity ) { + endVelocity = current.velocity + gravityVector * frametime; + current.velocity = ( current.velocity + endVelocity ) * 0.5f; + primal_velocity = endVelocity; + if ( groundPlane ) { + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + } + else { + endVelocity = current.velocity; + } + + time_left = frametime; + + // never turn against the ground plane + if ( groundPlane ) { + numplanes = 1; + planes[0] = groundTrace.c.normal; + } else { + numplanes = 0; + } + + // never turn against original velocity + planes[numplanes] = current.velocity; + planes[numplanes].Normalize(); + numplanes++; + + for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) { + + // calculate position we are trying to move to + end = current.origin + time_left * current.velocity; + + // see if we can make it there +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + time_left -= time_left * trace.fraction; + current.origin = trace.endpos; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + + stepped = pushed = false; + + // if we are allowed to step up + if ( stepUp && ( trace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + nearGround = groundPlane | ladder; + + if ( !nearGround ) { + // trace down to see if the player is near the ground + // step checking when near the ground allows the player to move up stairs smoothly while jumping + stepEnd = current.origin + maxStepHeight * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + nearGround = ( downTrace.fraction < 1.0f && (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ); + } + + // may only step up if near the ground or on a ladder + if ( nearGround ) { + + // step up + stepEnd = current.origin - maxStepHeight * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + // trace along velocity + stepEnd = downTrace.endpos + time_left * current.velocity; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + // step down + stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + if ( downTrace.fraction >= 1.0f || (downTrace.c.normal * -gravityNormal) > MIN_WALK_NORMAL ) { + // if moved the entire distance + if ( stepTrace.fraction >= 1.0f ) { + time_left = 0; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + current.velocity *= PM_STEPSCALE; + break; + } + + // if the move is further when stepping up + if ( stepTrace.fraction > trace.fraction ) { + time_left -= time_left * stepTrace.fraction; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + current.velocity *= PM_STEPSCALE; + trace = stepTrace; + stepped = true; + } + } + } + } + + // if we can push other entities and not blocked by the world + if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) { + + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + // clip movement, only push idMoveables, don't push entities the player is standing on + // apply impact to pushed objects + pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE; + + // clip & push + totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin ); + + if ( totalMass > 0.0f ) { + // decrease velocity based on the total mass of the objects being pushed ? + current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f ); + pushed = true; + } + + current.origin = trace.endpos; + time_left -= time_left * trace.fraction; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + } + + if ( !stepped && self ) { + // let the entity know about the collision + self->Collide( trace, current.velocity ); + } + + if ( numplanes >= MAX_CLIP_PLANES ) { + // MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris + // where it may hit the max clip planes + current.velocity = vec3_origin; + return true; + } + + // + // if this is the same plane we hit before, nudge velocity out along it, + // which fixes some epsilon issues with non-axial planes + // + for ( i = 0; i < numplanes; i++ ) { + if ( ( trace.c.normal * planes[i] ) > 0.999f ) { + // clip into the trace normal just in case this normal is almost but not exactly the same as the groundTrace normal + current.velocity.ProjectOntoPlane( trace.c.normal, OVERCLIP ); + // also add the normal to nudge the velocity out + current.velocity += trace.c.normal; + break; + } + } + if ( i < numplanes ) { + continue; + } + planes[numplanes] = trace.c.normal; + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0; i < numplanes; i++ ) { + into = current.velocity * planes[i]; + if ( into >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // slide along the plane + clipVelocity = current.velocity; + clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // slide along the plane + endClipVelocity = endVelocity; + endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0; j < numplanes; j++ ) { + if ( j == i ) { + continue; + } + if ( ( clipVelocity * planes[j] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + + // see if it goes back into the first clip plane + if ( ( clipVelocity * planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * current.velocity; + clipVelocity = d * dir; + + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * endVelocity; + endClipVelocity = d * dir; + + // see if there is a third plane the the new move enters + for ( k = 0; k < numplanes; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( ( clipVelocity * planes[k] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a tripple plane interaction + current.velocity = vec3_origin; + return true; + } + } + + // if we have fixed all interactions, try another move + current.velocity = clipVelocity; + endVelocity = endClipVelocity; + break; + } + } + + // step down + if ( stepDown && groundPlane ) { + stepEnd = current.origin + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) { + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_DOWN; + current.velocity *= PM_STEPSCALE; + } + } + + if ( gravity ) { + current.velocity = endVelocity; + } + + // come to a dead stop when the velocity orthogonal to the gravity flipped + clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal; + endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal; + if ( clipVelocity * endClipVelocity < 0.0f ) { + current.velocity = gravityNormal * current.velocity * gravityNormal; + } + + return ( bumpcount == 0 ); +} + +/* +================== +idPhysics_Player::Friction + +Handles both ground friction and water friction +================== +*/ +void idPhysics_Player::Friction( void ) { + idVec3 vel; + float speed, newspeed, control; + float drop; + + vel = current.velocity; + if ( walking ) { + // ignore slope movement, remove all velocity in gravity direction + vel += (vel * gravityNormal) * gravityNormal; + } + + speed = vel.Length(); + if ( speed < 1.0f ) { + current.velocity.Zero(); + return; + } + + drop = 0; + + // spectator friction +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + if ( pm_frictionoverride.GetFloat() > -1 ) { + drop += speed * pm_frictionoverride.GetFloat() * frametime; + } else if ( current.movementType == PM_SPECTATOR ) { +// RAVEN END + drop += speed * PM_FLYFRICTION * frametime; + } + // apply ground friction + else if ( walking && waterLevel <= WATERLEVEL_FEET ) { + // no friction on slick surfaces + if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) { + control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed; +// RAVEN BEGIN +// bdube: crouch slide + if ( current.crouchSlideTime > 0 ) { + drop += control * PM_SLIDEFRICTION * frametime; + } else { + drop += control * PM_FRICTION * frametime; + } +// RAVEN END + } + } + } + // apply water friction even if just wading + else if ( waterLevel ) { + drop += speed * PM_WATERFRICTION * waterLevel * frametime; + } + // apply air friction + else { + drop += speed * PM_AIRFRICTION * frametime; + } + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0 ) { + newspeed = 0; + } + current.velocity *= ( newspeed / speed ); + + // TTimo - snap to avoid denormals + if ( fabs( current.velocity.x ) < 1.0e-5f ) { + current.velocity.x = 0.0f; + } + if ( fabs( current.velocity.y ) < 1.0e-5f ) { + current.velocity.y = 0.0f; + } + if ( fabs( current.velocity.z ) < 1.0e-5f ) { + current.velocity.z = 0.0f; + } +} + +/* +=================== +idPhysics_Player::WaterJumpMove + +Flying out of the water +=================== +*/ +void idPhysics_Player::WaterJumpMove( void ) { + + // waterjump has no control, but falls + idPhysics_Player::SlideMove( true, true, false, false ); + + // add gravity + current.velocity += gravityNormal * frametime; + // if falling down + if ( current.velocity * gravityNormal > 0.0f ) { + // cancel as soon as we are falling down again + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } +} + +/* +=================== +idPhysics_Player::WaterMove +=================== +*/ +void idPhysics_Player::WaterMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + float vel; + + if ( idPhysics_Player::CheckWaterJump() ) { + idPhysics_Player::WaterJumpMove(); + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // user intentions + if ( !scale ) { + wishvel = gravityNormal * 60; // sink towards bottom + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + if ( wishspeed > playerSpeed * PM_SWIMSCALE ) { + wishspeed = playerSpeed * PM_SWIMSCALE; + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE ); + + // make sure we can go up slopes easily under water + if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) { + vel = current.velocity.Length(); + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + current.velocity.Normalize(); + current.velocity *= vel; + } + + idPhysics_Player::SlideMove( false, true, false, false ); +} + +/* +=================== +idPhysics_Player::FlyMove +=================== +*/ +void idPhysics_Player::FlyMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + // normal slowdown + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +=================== +idPhysics_Player::AirMove +=================== +*/ +void idPhysics_Player::AirMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + +// RAVEN BEGIN +// bdube: crouch time + // if the player isnt pressing crouch and heading down then accumulate slide time + if ( command.upmove >= 0 && current.velocity * gravityNormal > 0 ) { + current.crouchSlideTime += framemsec * 2; + if ( current.crouchSlideTime > 2000 ) { + current.crouchSlideTime = 2000; + } + } +// RAVEN END + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishvel -= (wishvel * gravityNormal) * gravityNormal; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // not on ground, so little effect on velocity + idPhysics_Player::Accelerate( wishdir, wishspeed, Pm_AirAccelerate() ); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( groundPlane ) { + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + + // NOTE: enable stair checking while moving through the air in multiplayer to allow bunny hopping onto stairs + idPhysics_Player::SlideMove( true, gameLocal.isMultiplayer, false, false ); +} + +/* +=================== +idPhysics_Player::WalkMove +=================== +*/ +void idPhysics_Player::WalkMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + float accelerate; + idVec3 oldVelocity, vel; + float oldVel, newVel; + + if ( waterLevel > WATERLEVEL_WAIST && ( viewForward * groundTrace.c.normal ) > 0.0f ) { + // begin swimming + idPhysics_Player::WaterMove(); + return; + } + + if ( idPhysics_Player::CheckJump() ) { + // jumped away + if ( waterLevel > WATERLEVEL_FEET ) { + idPhysics_Player::WaterMove(); + } + else { + idPhysics_Player::AirMove(); + } + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + + // project the forward and right directions onto the ground plane + viewForward.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + viewRight.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + // + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // clamp the speed lower if wading or walking on the bottom + if ( waterLevel ) { + float waterScale; + + waterScale = waterLevel / 3.0f; + waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale; + if ( wishspeed > playerSpeed * waterScale ) { + wishspeed = playerSpeed * waterScale; + } + } + + // when a player gets hit, they temporarily lose full control, which allows them to be moved a bit + if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) { + accelerate = Pm_AirAccelerate(); + } + else { + accelerate = Pm_Accelerate(); + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, accelerate ); + + if ( ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) || current.movementFlags & PMF_TIME_KNOCKBACK ) { + current.velocity += gravityVector * frametime; + } + + oldVelocity = current.velocity; + + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + // if not clipped into the opposite direction + if ( oldVelocity * current.velocity > 0.0f ) { + newVel = current.velocity.LengthSqr(); + if ( newVel > 1.0f ) { + oldVel = oldVelocity.LengthSqr(); + if ( oldVel > 1.0f ) { + // don't decrease velocity when going up or down a slope + current.velocity *= idMath::Sqrt( oldVel / newVel ); + } + } + } + + // don't do anything if standing still + vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal; + if ( vel.IsZero() ) { + return; + } + + gameLocal.push.InitSavingPushedEntityPositions(); + + idPhysics_Player::SlideMove( false, true, true, !gameLocal.isMultiplayer ); +} + +/* +============== +idPhysics_Player::DeadMove +============== +*/ +void idPhysics_Player::DeadMove( void ) { + float forward; + + if ( !walking ) { + return; + } + + // extra friction + forward = current.velocity.Length(); + forward -= 20; + if ( forward <= 0 ) { + current.velocity = vec3_origin; + } + else { + current.velocity.Normalize(); + current.velocity *= forward; + } +} + +/* +=============== +idPhysics_Player::NoclipMove +=============== +*/ +void idPhysics_Player::NoclipMove( void ) { + float speed, drop, friction, newspeed, stopspeed; + float scale, wishspeed; + idVec3 wishdir; + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + if ( pm_frictionoverride.GetFloat ( ) > -1 ) { + idPhysics_Player::Friction(); + } else { +// RAVEN END + + // friction + speed = current.velocity.Length(); + if ( speed < 20.0f ) { + current.velocity = vec3_origin; + } + else { + stopspeed = playerSpeed * 0.3f; + if ( speed < stopspeed ) { + speed = stopspeed; + } + friction = PM_NOCLIPFRICTION; + drop = speed * friction * frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= newspeed / speed; + } + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + } +// RAVEN END + + // accelerate + scale = idPhysics_Player::CmdScale( command ); + + wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishdir -= scale * gravityNormal * command.upmove; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + idPhysics_Player::Accelerate( wishdir, wishspeed, Pm_Accelerate() ); + + // move + current.origin += frametime * current.velocity; +} + +/* +=============== +idPhysics_Player::SpectatorMove +=============== +*/ +void idPhysics_Player::SpectatorMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + trace_t trace; + idVec3 end; + + // fly movement + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +============ +idPhysics_Player::LadderMove +============ +*/ +void idPhysics_Player::LadderMove( void ) { + idVec3 wishdir, wishvel, right; + float wishspeed, scale; + float upscale; + + // stick to the ladder + wishvel = -100.0f * ladderNormal; + current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel; + + upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f; + if ( upscale > 1.0f ) { + upscale = 1.0f; + } + else if ( upscale < -1.0f ) { + upscale = -1.0f; + } + + scale = idPhysics_Player::CmdScale( command ); + wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove; + + // strafe + if ( command.rightmove ) { + // right vector orthogonal to gravity + right = viewRight - (gravityNormal * viewRight) * gravityNormal; + // project right vector into ladder plane + right = right - (ladderNormal * right) * ladderNormal; + right.Normalize(); + + // if we are looking away from the ladder, reverse the right vector + if ( ladderNormal * viewForward > 0.0f ) { + right = -right; + } + wishvel += 2.0f * right * scale * (float) command.rightmove; + } + + // up down movement + if ( command.upmove ) { + wishvel += -0.5f * gravityNormal * scale * (float) command.upmove; + } + + // do strafe friction + idPhysics_Player::Friction(); + + // accelerate + wishspeed = wishvel.Normalize(); + idPhysics_Player::Accelerate( wishvel, wishspeed, Pm_Accelerate() ); + + // cap the vertical velocity + upscale = current.velocity * -gravityNormal; + if ( upscale < -PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale + PM_LADDERSPEED); + } + else if ( upscale > PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale - PM_LADDERSPEED); + } + + if ( (wishvel * gravityNormal) == 0.0f ) { + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity += gravityVector * frametime; + if ( current.velocity * gravityNormal > 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + else { + current.velocity -= gravityVector * frametime; + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + } + + idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false ); +} + +/* +============= +idPhysics_Player::CorrectAllSolid +============= +*/ +void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:allsolid\n", c_pmove ); + } + + // FIXME: jitter around to find a free spot ? + + if ( trace.fraction >= 1.0f ) { + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = current.origin; + trace.endAxis = clipModelAxis; + trace.fraction = 0.0f; + trace.c.dist = current.origin.z; + trace.c.normal.Set( 0, 0, 1 ); + trace.c.point = current.origin; + trace.c.entityNum = ENTITYNUM_WORLD; + trace.c.id = 0; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.material = NULL; + trace.c.contents = contents; + } +} + +/* +============= +idPhysics_Player::CheckGround +============= +*/ +// RAVEN BEGIN +// MrE: check stuck +void idPhysics_Player::CheckGround( bool checkStuck ) { +// RAVEN END + int i, contents; + idVec3 point; + bool hadGroundContacts; + + hadGroundContacts = HasGroundContacts(); + + // set the clip model origin before getting the contacts + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + EvaluateContacts(); + + // setup a ground trace from the contacts + groundTrace.endpos = current.origin; + groundTrace.endAxis = clipModel->GetAxis(); + if ( contacts.Num() ) { + groundTrace.fraction = 0.0f; + groundTrace.c = contacts[0]; + for ( i = 1; i < contacts.Num(); i++ ) { + groundTrace.c.normal += contacts[i].normal; + } + groundTrace.c.normal.Normalize(); + } else { + groundTrace.fraction = 1.0f; + } + +// RAVEN BEGIN +// ddynerman: multiple collision worlds +// MrE: check stuck + if ( checkStuck ) { + contents = gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), -1, self ); + if ( contents & MASK_SOLID ) { + // do something corrective if stuck in solid + idPhysics_Player::CorrectAllSolid( groundTrace, contents ); + } + } +// RAVEN END + + // if the trace didn't hit anything, we are in free fall + if ( groundTrace.fraction == 1.0f ) { + groundPlane = false; + walking = false; + groundEntityPtr = NULL; + return; + } + + groundMaterial = groundTrace.c.material; + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + // check if getting thrown off the ground + if ( (current.velocity * -gravityNormal) > 0.0f && ( current.velocity * groundTrace.c.normal ) > 10.0f ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:kickoff\n", c_pmove ); + } + + groundPlane = false; + walking = false; + return; + } + + // slopes that are too steep will not be considered onground + if ( ( groundTrace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + if ( debugLevel ) { + gameLocal.Printf( "%i:steep\n", c_pmove ); + } + + // FIXME: if they can't slide down the slope, let them walk (sharp crevices) + + if( gameLocal.isMultiplayer ) { + // in multiplayer, instead of sliding push the player out from the normal for some free fall + current.origin += groundTrace.c.normal; + + groundPlane = false; + walking = false; + } else { + // make sure we don't die from sliding down a steep slope + if ( current.velocity * gravityNormal > 150.0f ) { + current.velocity -= ( current.velocity * gravityNormal - 150.0f ) * gravityNormal; + } + groundPlane = true; + walking = false; + } + + return; + } + + groundPlane = true; + walking = true; + + // hitting solid ground will end a waterjump + if ( current.movementFlags & PMF_TIME_WATERJUMP ) { + current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); + current.movementTime = 0; + } + + // if the player didn't have ground contacts the previous frame + if ( !hadGroundContacts ) { + // don't do landing time if we were just going down a slope + if ( (current.velocity * -gravityNormal) < -200.0f ) { + // don't allow another jump for a little while + current.movementFlags |= PMF_TIME_LAND; + current.movementTime = 250; + } + } + + // let the entity know about the collision + if ( self ) { + self->Collide( groundTrace, current.velocity ); + } + + if ( groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, groundTrace.c.id, groundTrace.c.point, current.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +============== +idPhysics_Player::CheckDuck + +Sets clip model size +============== +*/ +void idPhysics_Player::CheckDuck( void ) { + trace_t trace; + idVec3 end; + idBounds bounds; + float maxZ; + + if ( current.movementType == PM_DEAD ) { + maxZ = pm_deadheight.GetFloat(); + } else { + // stand up when up against a ladder + if ( command.upmove < 0 && !ladder ) { + // duck + current.movementFlags |= PMF_DUCKED; + } else { + // stand up if possible + if ( current.movementFlags & PMF_DUCKED ) { + // try to stand up + end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( trace.fraction >= 1.0f ) { + current.movementFlags &= ~PMF_DUCKED; + } + } + } + + if ( current.movementFlags & PMF_DUCKED ) { +// RAVEN BEGIN +// bdube: crouch slide + if ( !current.crouchSlideTime ) { + playerSpeed = crouchSpeed; + } +// RAVEN END + maxZ = pm_crouchheight.GetFloat(); + } else { + maxZ = pm_normalheight.GetFloat(); +// RAVEN BEGIN +// bdube: crouch slide + if ( groundPlane && current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } +// RAVEN END + } + } + // if the clipModel height should change + if ( clipModel->GetBounds()[1][2] != maxZ ) { + + bounds = clipModel->GetBounds(); + bounds[1][2] = maxZ; + if ( pm_usecylinder.GetBool() ) { + clipModel->LoadModel( idTraceModel( bounds, 8 ), NULL ); + } else { + clipModel->LoadModel( idTraceModel( bounds ), NULL ); + } + } +} + +/* +================ +idPhysics_Player::CheckLadder +================ +*/ +void idPhysics_Player::CheckLadder( void ) { + idVec3 forward, start, end; + trace_t trace; + float tracedist; + + if ( current.movementTime ) { + return; + } + + // if on the ground moving backwards + if ( walking && command.forwardmove <= 0 ) { + return; + } + + // forward vector orthogonal to gravity + forward = viewForward - (gravityNormal * viewForward) * gravityNormal; + forward.Normalize(); + + if ( walking ) { + // don't want to get sucked towards the ladder when still walking + tracedist = 1.0f; + } else { + tracedist = 48.0f; + } + + end = current.origin + tracedist * forward; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + // if near a surface + if ( trace.fraction < 1.0f ) { + + // if a ladder surface + if ( trace.c.material && ( trace.c.material->GetSurfaceFlags() & SURF_LADDER ) ) { + + // check a step height higher + end = current.origin - gravityNormal * ( maxStepHeight * 0.75f ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + start = trace.endpos; + end = start + tracedist * forward; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + // if also near a surface a step height higher + if ( trace.fraction < 1.0f ) { + + // if it also is a ladder surface + if ( trace.c.material && trace.c.material->GetSurfaceFlags() & SURF_LADDER ) { + ladder = true; + ladderNormal = trace.c.normal; + } + } + } + } +} + +/* +============= +idPhysics_Player::CheckJump +============= +*/ +bool idPhysics_Player::CheckJump( void ) { + idVec3 addVelocity; + + if ( command.upmove < 10 ) { + // not holding jump + return false; + } + + // must wait for jump to be released + if ( current.movementFlags & PMF_JUMP_HELD ) { + return false; + } + + // don't jump if we can't stand up + if ( current.movementFlags & PMF_DUCKED ) { + return false; + } + + groundPlane = false; // jumping away + walking = false; + current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED; + + addVelocity = 2.0f * maxJumpHeight * -gravityVector; + addVelocity *= idMath::Sqrt( addVelocity.Normalize() ); + current.velocity += addVelocity; + +// RAVEN BEGIN +// bdube: crouch slide, nick maggoire is awesome + current.crouchSlideTime = 0; +// RAVEN END + + return true; +} + +/* +============= +idPhysics_Player::CheckWaterJump +============= +*/ +bool idPhysics_Player::CheckWaterJump( void ) { + idVec3 spot; + int cont; + idVec3 flatforward; + + if ( current.movementTime ) { + return false; + } + + // check for water jump + if ( waterLevel != WATERLEVEL_WAIST ) { + return false; + } + + flatforward = viewForward - (viewForward * gravityNormal) * gravityNormal; + flatforward.Normalize(); + + spot = current.origin + 30.0f * flatforward; + spot -= 4.0f * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( !(cont & CONTENTS_SOLID) ) { + return false; + } + + spot -= 16.0f * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( cont ) { + return false; + } + + // jump out of water + current.velocity = 200.0f * viewForward - 350.0f * gravityNormal; + current.movementFlags |= PMF_TIME_WATERJUMP; + current.movementTime = 2000; + + return true; +} + +/* +============= +idPhysics_Player::SetWaterLevel +============= +*/ +void idPhysics_Player::SetWaterLevel( void ) { + idVec3 point; + idBounds bounds; + int contents; + + // + // get waterlevel, accounting for ducking + // + waterLevel = WATERLEVEL_NONE; + waterType = 0; + + bounds = clipModel->GetBounds(); + +// RAVEN BEGIN +// AReis: Get back the water entity (if there is one), so we can grab his density +// then apply some force to the fluid since we're moving through it. + idEntity *other = NULL; + + // check at feet level + point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self, &other ); +// RAVEN END + if ( contents & MASK_WATER ) { + + waterType = contents; + waterLevel = WATERLEVEL_FEET; + + // check at waist level + point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( contents & MASK_WATER ) { + + waterLevel = WATERLEVEL_WAIST; + + // check at head level + point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); +// RAVEN END + if ( contents & MASK_WATER ) { + waterLevel = WATERLEVEL_HEAD; + } + } + } +} + +/* +================ +idPhysics_Player::DropTimers +================ +*/ +void idPhysics_Player::DropTimers( void ) { + // drop misc timing counter + if ( current.movementTime ) { + if ( framemsec >= current.movementTime ) { + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } + else { + current.movementTime -= framemsec; + } + } + +// RAVEN BEGIN +// bdube: crouch slide + if ( groundPlane && current.crouchSlideTime ) { + if ( framemsec >= current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } else { + current.crouchSlideTime -= framemsec; + } + } +// RAVEN END +} + +/* +================ +idPhysics_Player::MovePlayer +================ +*/ +void idPhysics_Player::MovePlayer( int msec ) { + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint for the previous frame + c_pmove++; + + walking = false; + groundPlane = false; + ladder = false; + + // determine the time + framemsec = msec; + frametime = framemsec * 0.001f; + + // default speed + playerSpeed = walkSpeed; + + // remove jumped and stepped up flag + current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN); + current.stepUp = 0.0f; + + if ( command.upmove < 10 ) { + // not holding jump + current.movementFlags &= ~PMF_JUMP_HELD; + } + + // if no movement at all + if ( current.movementType == PM_FREEZE ) { + return; + } + + // move the player velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + // view vectors + viewAngles.ToVectors( &viewForward, NULL, NULL ); + viewForward *= clipModelAxis; + viewRight = gravityNormal.Cross( viewForward ); + viewRight.Normalize(); + + // fly in spectator mode +// RAVEN BEGIN +// nmckenzie: Allowing ways to force spectator movement. + if ( current.movementType == PM_SPECTATOR || pm_forcespectatormove.GetBool() ) { +// RAVEN END + SpectatorMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // special no clip mode + if ( current.movementType == PM_NOCLIP ) { + idPhysics_Player::NoclipMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // no control when dead + if ( current.movementType == PM_DEAD ) { + command.forwardmove = 0; + command.rightmove = 0; + command.upmove = 0; + } + + // set watertype and waterlevel +// RAVEN BEGIN +// ddynerman: water disabled in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } +// RAVEN END + + // check for ground + idPhysics_Player::CheckGround( true ); + + // check if up against a ladder +// RAVEN BEGIN +// MrE: no ladders in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::CheckLadder(); + } +// RAVEN END + + // set clip model size + idPhysics_Player::CheckDuck(); + + // handle timers + idPhysics_Player::DropTimers(); + + // move + if ( current.movementType == PM_DEAD ) { + // dead + idPhysics_Player::DeadMove(); + } + else if ( ladder ) { + // going up or down a ladder + idPhysics_Player::LadderMove(); + } +// RAVEN BEGIN +// ddynerman: water disabled in MP + else if ( !gameLocal.isMultiplayer && current.movementFlags & PMF_TIME_WATERJUMP ) { + + // jumping out of water + idPhysics_Player::WaterJumpMove(); + } + else if ( !gameLocal.isMultiplayer && waterLevel > 1 ) { +// RAVEN END + // swimming + idPhysics_Player::WaterMove(); + } + else if ( walking ) { + // walking on ground + idPhysics_Player::WalkMove(); + } + else { + // airborne + idPhysics_Player::AirMove(); + } + +// RAVEN BEGIN +// ddynerman: water disabled in MP + if( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } +// RAVEN END + + idPhysics_Player::CheckGround( false ); + + // move the player velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); +} + + +/* +================ +idPhysics_Player::GetWaterLevel +================ +*/ +waterLevel_t idPhysics_Player::GetWaterLevel( void ) const { + return waterLevel; +} + +/* +================ +idPhysics_Player::GetWaterType +================ +*/ +int idPhysics_Player::GetWaterType( void ) const { + return waterType; +} + +/* +================ +idPhysics_Player::HasJumped +================ +*/ +bool idPhysics_Player::HasJumped( void ) const { + return ( ( current.movementFlags & PMF_JUMPED ) != 0 ); +} + +/* +================ +idPhysics_Player::HasSteppedUp +================ +*/ +bool idPhysics_Player::HasSteppedUp( void ) const { + return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 ); +} + +/* +================ +idPhysics_Player::GetStepUp +================ +*/ +float idPhysics_Player::GetStepUp( void ) const { + return current.stepUp; +} + +/* +================ +idPhysics_Player::IsCrouching +================ +*/ +bool idPhysics_Player::IsCrouching( void ) const { + //MCG: if bound, never think we're crouched + return ( !masterEntity&&( current.movementFlags & PMF_DUCKED ) != 0 ); +} + +/* +================ +idPhysics_Player::OnLadder +================ +*/ +bool idPhysics_Player::OnLadder( void ) const { + return ladder; +} + +/* +================ +idPhysics_Player::idPhysics_Player +================ +*/ +idPhysics_Player::idPhysics_Player( void ) { + debugLevel = false; + clipModel = NULL; + clipMask = 0; + memset( ¤t, 0, sizeof( current ) ); + saved = current; + walkSpeed = 0; + crouchSpeed = 0; + maxStepHeight = 0; + maxJumpHeight = 0; + memset( &command, 0, sizeof( command ) ); + viewAngles.Zero(); + framemsec = 0; + frametime = 0; + playerSpeed = 0; + viewForward.Zero(); + viewRight.Zero(); + walking = false; + groundPlane = false; + memset( &groundTrace, 0, sizeof( groundTrace ) ); + groundMaterial = NULL; + ladder = false; + ladderNormal.Zero(); + waterLevel = WATERLEVEL_NONE; + waterType = 0; +} + +/* +================ +idPhysics_Player_SavePState +================ +*/ +void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) { + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson Added unsaved var + + savefile->WriteFloat( state.stepUp ); + savefile->WriteInt( state.movementType ); + savefile->WriteInt( state.movementFlags ); + savefile->WriteInt( state.movementTime ); + + savefile->WriteInt( state.crouchSlideTime ); // cnicholson Added unsaved var +} + +/* +================ +idPhysics_Player_RestorePState +================ +*/ +void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) { + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson Added unrestored var + + savefile->ReadFloat( state.stepUp ); + savefile->ReadInt( state.movementType ); + savefile->ReadInt( state.movementFlags ); + savefile->ReadInt( state.movementTime ); + + savefile->ReadInt( state.crouchSlideTime ); // cnicholson Added unrestored var +} + +/* +================ +idPhysics_Player::Save +================ +*/ +void idPhysics_Player::Save( idSaveGame *savefile ) const { + + idPhysics_Player_SavePState( savefile, current ); + idPhysics_Player_SavePState( savefile, saved ); + + savefile->WriteFloat( walkSpeed ); + savefile->WriteFloat( crouchSpeed ); + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( maxJumpHeight ); + savefile->WriteInt( debugLevel ); + + savefile->WriteUsercmd( command ); + savefile->WriteAngles( viewAngles ); + + savefile->WriteInt( framemsec ); + savefile->WriteFloat( frametime ); + savefile->WriteFloat( playerSpeed ); + savefile->WriteVec3( viewForward ); + savefile->WriteVec3( viewRight ); + + savefile->WriteBool( walking ); + savefile->WriteBool( groundPlane ); + savefile->WriteTrace( groundTrace ); + savefile->WriteMaterial( groundMaterial ); + + savefile->WriteBool( ladder ); + savefile->WriteVec3( ladderNormal ); + + savefile->WriteInt( (int)waterLevel ); + savefile->WriteInt( waterType ); +} + +/* +================ +idPhysics_Player::Restore +================ +*/ +void idPhysics_Player::Restore( idRestoreGame *savefile ) { + + idPhysics_Player_RestorePState( savefile, current ); + idPhysics_Player_RestorePState( savefile, saved ); + + savefile->ReadFloat( walkSpeed ); + savefile->ReadFloat( crouchSpeed ); + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( maxJumpHeight ); + savefile->ReadInt( debugLevel ); + + savefile->ReadUsercmd( command ); + savefile->ReadAngles( viewAngles ); + + savefile->ReadInt( framemsec ); + savefile->ReadFloat( frametime ); + savefile->ReadFloat( playerSpeed ); + savefile->ReadVec3( viewForward ); + savefile->ReadVec3( viewRight ); + + savefile->ReadBool( walking ); + savefile->ReadBool( groundPlane ); + savefile->ReadTrace( groundTrace ); + savefile->ReadMaterial( groundMaterial ); + + savefile->ReadBool( ladder ); + savefile->ReadVec3( ladderNormal ); + + savefile->ReadInt( (int &)waterLevel ); + savefile->ReadInt( waterType ); +} + +/* +================ +idPhysics_Player::SetPlayerInput +================ +*/ +void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ) { + command = cmd; + viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles +} + +/* +================ +idPhysics_Player::SetSpeed +================ +*/ +void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ) { + walkSpeed = newWalkSpeed; + crouchSpeed = newCrouchSpeed; +} + +/* +================ +idPhysics_Player::SetMaxStepHeight +================ +*/ +void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Player::GetMaxStepHeight +================ +*/ +float idPhysics_Player::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Player::SetMaxJumpHeight +================ +*/ +void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) { + maxJumpHeight = newMaxJumpHeight; +} + +/* +================ +idPhysics_Player::SetMovementType +================ +*/ +void idPhysics_Player::SetMovementType( const pmtype_t type ) { + current.movementType = type; +} + +/* +================ +idPhysics_Player::SetKnockBack +================ +*/ +void idPhysics_Player::SetKnockBack( const int knockBackTime ) { + if ( current.movementTime ) { + return; + } + current.movementFlags |= PMF_TIME_KNOCKBACK; + current.movementTime = knockBackTime; +} + +/* +================ +idPhysics_Player::SetDebugLevel +================ +*/ +void idPhysics_Player::SetDebugLevel( bool set ) { + debugLevel = set; +} + +/* +================ +idPhysics_Player::Evaluate +================ +*/ +bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + + waterLevel = WATERLEVEL_NONE; + waterType = 0; + oldOrigin = current.origin; + + clipModel->Unlink(); + + // if bound to a master + if ( masterEntity ) { + assert( self ); + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f ); + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + ActivateContactEntities(); + + idPhysics_Player::MovePlayer( timeStepMSec ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// TTimo: only if tied to an ent + if ( self ) { + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + + // IsOutsideWorld uses self, so it needs to be non null + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self ? "NULL" : self->name.c_str(), current.origin.ToString(0) ); + } + } +// RAVEN END + + return true; //( current.origin != oldOrigin ); +} + +/* +================ +idPhysics_Player::UpdateTime +================ +*/ +void idPhysics_Player::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Player::GetTime +================ +*/ +int idPhysics_Player::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Player::GetImpactInfo +================ +*/ +void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Player::ApplyImpulse +================ +*/ +void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( current.movementType != PM_NOCLIP ) { + current.velocity += impulse * invMass; + } +} + +/* +================ +idPhysics_Player::IsAtRest +================ +*/ +bool idPhysics_Player::IsAtRest( void ) const { + return false; +} + +/* +================ +idPhysics_Player::GetRestStartTime +================ +*/ +int idPhysics_Player::GetRestStartTime( void ) const { + return -1; +} + +/* +================ +idPhysics_Player::SaveState +================ +*/ +void idPhysics_Player::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Player::RestoreState +================ +*/ +void idPhysics_Player::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + assert( self ); + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// TTimo: only if tied to an ent + if ( self ) { + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); + } +// RAVEN END +} + +/* +================ +idPhysics_Player::GetOrigin +================ +*/ +const idVec3 & idPhysics_Player::PlayerGetOrigin( void ) const { + return current.origin; +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Translate +================ +*/ +void idPhysics_Player::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Rotate +================ +*/ +void idPhysics_Player::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::SetLinearVelocity +================ +*/ +void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; +} + +/* +================ +idPhysics_Player::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Player::SetPushed +================ +*/ +void idPhysics_Player::SetPushed( int deltaTime ) { + idVec3 velocity; + float d; + + // velocity with which the player is pushed + velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); + + // remove any downward push velocity + d = velocity * gravityNormal; + if ( d > 0.0f ) { + velocity -= d * gravityNormal; + } + + current.pushVelocity += velocity; +} + +/* +================ +idPhysics_Player::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Player::ClearPushedVelocity +================ +*/ +void idPhysics_Player::ClearPushedVelocity( void ) { + current.pushVelocity.Zero(); + current.lastPushVelocity.Zero ( ); +} + +/* +================ +idPhysics_Player::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + } + } +} + +const float PLAYER_VELOCITY_MAX = 4000; +const int PLAYER_VELOCITY_TOTAL_BITS = 16; +const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1; +const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS; +const int PLAYER_MOVEMENT_TYPE_BITS = 3; +const int PLAYER_MOVEMENT_FLAGS_BITS = 8; + +/* +================ +idPhysics_Player::WriteToSnapshot +================ +*/ +void idPhysics_Player::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); +// msg.WriteFloat( current.velocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); + + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + msg.WriteDeltaFloat( 0.0f, current.stepUp ); + msg.WriteBits( current.movementType, PLAYER_MOVEMENT_TYPE_BITS ); + msg.WriteBits( current.movementFlags, PLAYER_MOVEMENT_FLAGS_BITS ); + msg.WriteDeltaLong( 0, current.movementTime ); +// RAVEN BEGIN +// bdube: crouch slide + msg.WriteDeltaLong( 0, current.crouchSlideTime ); +// abahr: gravity + //msg.WriteQuat( GetAxis().ToQuat() ); + //msg.WriteVec3( GetGravity() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::ReadFromSnapshot +================ +*/ +void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) { + + idVec3 oldOrigin = current.origin; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + + GAMELOG_SET( "origin_delta_x", (current.origin - oldOrigin).x ); + GAMELOG_SET( "origin_delta_y", (current.origin - oldOrigin).y ); + GAMELOG_SET( "origin_delta_z", (current.origin - oldOrigin).z ); + +// current.velocity[0] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.velocity[1] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.velocity[2] = msg.ReadFloat( PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + + idVec3 oldVelocity = current.velocity; + + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); + + GAMELOG_SET( "velocity_delta_x", (current.velocity - oldVelocity).x ); + GAMELOG_SET( "velocity_delta_y", (current.velocity - oldVelocity).y ); + GAMELOG_SET( "velocity_delta_z", (current.velocity - oldVelocity).z ); + + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); +// current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PLAYER_VELOCITY_EXPONENT_BITS, PLAYER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + current.stepUp = msg.ReadDeltaFloat( 0.0f ); + current.movementType = msg.ReadBits( PLAYER_MOVEMENT_TYPE_BITS ); + + current.movementFlags = msg.ReadBits( PLAYER_MOVEMENT_FLAGS_BITS ); + if( !( saved.movementFlags & PMF_JUMP_HELD ) && current.movementFlags & PMF_JUMP_HELD ) { + assert( self && self->IsType( idPlayer::GetClassType() ) ); + ((idPlayer*)self)->jumpDuringHitch = true; + } + + current.movementTime = msg.ReadDeltaLong( 0 ); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + } +// RAVEN BEGIN +// bdube: crouch slide + current.crouchSlideTime = msg.ReadDeltaLong( 0 ); +// RAVEN END +} + +/* +=============== +idPhysics_Player::SetClipModelNoLink +=============== +*/ +void idPhysics_Player::SetClipModelNoLink( idClipModel *model ) { + assert( model ); + assert( model->IsTraceModel() ); + + if ( clipModel && clipModel != model ) { + delete clipModel; + } + clipModel = model; +} diff --git a/src/game/physics/Physics_Player.h b/src/game/physics/Physics_Player.h new file mode 100644 index 0000000..ca424bc --- /dev/null +++ b/src/game/physics/Physics_Player.h @@ -0,0 +1,221 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_PLAYER_H__ +#define __PHYSICS_PLAYER_H__ + +/* +=================================================================================== + + Player physics + + Simulates the motion of a player through the environment. Input from the + player is used to allow a certain degree of control over the motion. + +=================================================================================== +*/ + +// movementType +typedef enum { + PM_NORMAL, // normal physics + PM_DEAD, // no acceleration or turning, but free falling + PM_SPECTATOR, // flying without gravity but with collision detection + PM_FREEZE, // stuck in place without control + PM_NOCLIP // flying without collision detection nor gravity +} pmtype_t; + +typedef enum { + WATERLEVEL_NONE, + WATERLEVEL_FEET, + WATERLEVEL_WAIST, + WATERLEVEL_HEAD +} waterLevel_t; + +#define MAXTOUCH 32 + +typedef struct playerPState_s { + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; +// RAVEN BEGIN +// bdube: added + idVec3 lastPushVelocity; +// RAVEN END + float stepUp; + int movementType; + int movementFlags; + int movementTime; +// RAVEN BEGIN +// bdube: crouch slide + int crouchSlideTime; +// RAVEN END +} playerPState_t; + +class idPhysics_Player : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Player ); + + idPhysics_Player( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ); + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + void SetMaxJumpHeight( const float newMaxJumpHeight ); + void SetMovementType( const pmtype_t type ); + void SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ); + void SetKnockBack( const int knockBackTime ); + void SetDebugLevel( bool set ); + // feed back from last physics frame + waterLevel_t GetWaterLevel( void ) const; + int GetWaterType( void ) const; + bool HasJumped( void ) const; + bool HasSteppedUp( void ) const; + float GetStepUp( void ) const; + bool IsCrouching( void ) const; + bool OnLadder( void ) const; + const idVec3 & PlayerGetOrigin( void ) const; // != GetOrigin + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + void ClearPushedVelocity( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +// RAVEN BEGIN +// kfuller: Added + bool IsNoclip( void ) const; + bool IsDead( void ) const; +// RAVEN END + + void SetClipModelNoLink( idClipModel *clip ); + +private: + // player physics state + playerPState_t current; + playerPState_t saved; + + // properties + float walkSpeed; + float crouchSpeed; + float maxStepHeight; + float maxJumpHeight; + int debugLevel; // if set, diagnostic output will be printed + + // player input + usercmd_t command; + idAngles viewAngles; + + // run-time variables + int framemsec; + float frametime; + float playerSpeed; + idVec3 viewForward; + idVec3 viewRight; + + // walk movement + bool walking; + bool groundPlane; + trace_t groundTrace; + const idMaterial * groundMaterial; + + // ladder movement + bool ladder; + idVec3 ladderNormal; + + // results of last evaluate + waterLevel_t waterLevel; + int waterType; + +private: + float CmdScale( const usercmd_t &cmd ) const; + void Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ); + bool SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ); + void Friction( void ); + void WaterJumpMove( void ); + void WaterMove( void ); + void FlyMove( void ); + void AirMove( void ); + void WalkMove( void ); + void DeadMove( void ); + void NoclipMove( void ); + void SpectatorMove( void ); + void LadderMove( void ); + void CorrectAllSolid( trace_t &trace, int contents ); +// RAVEN BEGIN +// MrE: check stuck + void CheckGround( bool checkStuck ); +// RAVEN END + void CheckDuck( void ); + void CheckLadder( void ); + bool CheckJump( void ); + bool CheckWaterJump( void ); + void SetWaterLevel( void ); + void DropTimers( void ); + void MovePlayer( int msec ); + + float Pm_Accelerate( void ); + float Pm_AirAccelerate( void ); +}; + +ID_INLINE bool idPhysics_Player::IsNoclip( void ) const { + return current.movementType == PM_NOCLIP; +} + +ID_INLINE bool idPhysics_Player::IsDead( void ) const { + return current.movementType == PM_DEAD; +} + +#endif /* !__PHYSICS_PLAYER_H__ */ diff --git a/src/game/physics/Physics_RigidBody.cpp b/src/game/physics/Physics_RigidBody.cpp new file mode 100644 index 0000000..b88f7c7 --- /dev/null +++ b/src/game/physics/Physics_RigidBody.cpp @@ -0,0 +1,1646 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_RigidBody ) +END_CLASS + +const float STOP_SPEED = 10.0f; + + +#undef RB_TIMINGS + +#ifdef RB_TIMINGS +static int lastTimerReset = 0; +static int numRigidBodies = 0; +static idTimer timer_total, timer_collision; +#endif + + +/* +================ +RigidBodyDerivatives +================ +*/ +void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ) { + const idPhysics_RigidBody *p = (idPhysics_RigidBody *) clientData; + rigidBodyIState_t *s = (rigidBodyIState_t *) state; + // NOTE: this struct should be build conform rigidBodyIState_t + struct rigidBodyDerivatives_s { + idVec3 linearVelocity; + idMat3 angularMatrix; + idVec3 force; + idVec3 torque; + } *d = (struct rigidBodyDerivatives_s *) derivatives; + idVec3 angularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = s->orientation * p->inverseInertiaTensor * s->orientation.Transpose(); + angularVelocity = inverseWorldInertiaTensor * s->angularMomentum; + // derivatives + d->linearVelocity = p->inverseMass * s->linearMomentum; + d->angularMatrix = SkewSymmetric( angularVelocity ) * s->orientation; + d->force = - p->linearFriction * s->linearMomentum + p->current.externalForce; + d->torque = - p->angularFriction * s->angularMomentum + p->current.externalTorque; +} + +/* +================ +idPhysics_RigidBody::Integrate + + Calculate next state from the current state using an integrator. +================ +*/ +void idPhysics_RigidBody::Integrate( float deltaTime, rigidBodyPState_t &next ) { + idVec3 position; + + position = current.i.position; + current.i.position += centerOfMass * current.i.orientation; + + current.i.orientation.TransposeSelf(); + + integrator->Evaluate( (float *) ¤t.i, (float *) &next.i, 0, deltaTime ); + next.i.orientation.OrthoNormalizeSelf(); + + // apply gravity + next.i.linearMomentum += deltaTime * gravityVector * mass; + + current.i.orientation.TransposeSelf(); + next.i.orientation.TransposeSelf(); + + current.i.position = position; + next.i.position -= centerOfMass * next.i.orientation; + + next.atRest = current.atRest; +} + +/* +================ +idPhysics_RigidBody::CollisionImpulse + + Calculates the collision impulse using the velocity relative to the collision object. + The current state should be set to the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CollisionImpulse( const trace_t &collision, idVec3 &impulse ) { + idVec3 r, linearVelocity, angularVelocity, velocity; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator, vel; + impactInfo_t info; + idEntity *ent; + + // get info from other entity involved + ent = gameLocal.entities[collision.c.entityNum]; + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + +// RAVEN BEGIN +// bdube: once in water take out the water flag and increase friction + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + clipMask &= ~CONTENTS_WATER; + linearFriction *= 20.0f; + angularFriction *= 20.0f; + } +// RAVEN END + + // collision point relative to the body center of mass + r = collision.c.point - (current.i.position + centerOfMass * current.i.orientation); + + // the velocity at the collision point + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + + // velocity in normal direction + vel = velocity * collision.c.normal; + + if ( vel > -STOP_SPEED ) { + impulseNumerator = STOP_SPEED; + } + else { + impulseNumerator = -( 1.0f + bouncyness ) * vel; + } + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // update linear and angular momentum with impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if no movement at all don't blow up + if ( collision.fraction < 0.0001f ) { + current.i.linearMomentum *= 0.5f; + current.i.angularMomentum *= 0.5f; + } + + // callback to self to let the entity know about the collision + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_RigidBody::CheckForCollisions + + Check for collisions between the current and next state. + If there is a collision the next state is set to the state at the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ) { +//#define TEST_COLLISION_DETECTION + idMat3 axis; + idRotation rotation; + bool collided = false; + +#ifdef TEST_COLLISION_DETECTION + bool startsolid; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( current.i.orientation, next.i.orientation, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( current.i.position ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, current.i.position, next.i.position, rotation, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + next.i.position = collision.endpos; + next.i.orientation = collision.endAxis; + next.i.linearMomentum = current.i.linearMomentum; + next.i.angularMomentum = current.i.angularMomentum; + collided = true; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, next.i.position, clipModel, next.i.orientation, clipMask, self ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return collided; +} + +/* +================ +idPhysics_RigidBody::ContactFriction + + Does not solve friction for multiple simultaneous contacts but applies contact friction in isolation. + Uses absolute velocity at the contact points instead of the velocity relative to the contact object. +================ +*/ +void idPhysics_RigidBody::ContactFriction( float deltaTime ) { + int i; + float magnitude, impulseNumerator, impulseDenominator; + idMat3 inverseWorldInertiaTensor; + idVec3 linearVelocity, angularVelocity; + idVec3 massCenter, r, velocity, normal, impulse, normalVelocity; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + + massCenter = current.i.position + centerOfMass * current.i.orientation; + + for ( i = 0; i < contacts.Num(); i++ ) { + + r = contacts[i].point - massCenter; + + // calculate velocity at contact point + linearVelocity = inverseMass * current.i.linearMomentum; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + + // velocity along normal vector + normalVelocity = ( velocity * contacts[i].normal ) * contacts[i].normal; + + // calculate friction impulse + normal = -( velocity - normalVelocity ); + magnitude = normal.Normalize(); + impulseNumerator = contactFriction * magnitude; + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply friction impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if moving towards the surface at the contact point + if ( normalVelocity * contacts[i].normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross( impulse ); + } + } +} + +/* +================ +idPhysics_RigidBody::TestIfAtRest + + Returns true if the body is considered at rest. + Does not catch all cases where the body is at rest but is generally good enough. +================ +*/ +bool idPhysics_RigidBody::TestIfAtRest( void ) const { + int i; + float gv; + idVec3 v, av, normal, point; + idMat3 inverseWorldInertiaTensor; + idFixedWinding contactWinding; + + if ( current.atRest >= 0 ) { + return true; + } + + // need at least 3 contact points to come to rest + if ( contacts.Num() < 3 ) { + return false; + } + + // get average contact plane normal + normal.Zero(); + for ( i = 0; i < contacts.Num(); i++ ) { + normal += contacts[i].normal; + } + normal /= (float) contacts.Num(); + normal.Normalize(); + + // if on a too steep surface + if ( (normal * gravityNormal) > -0.7f ) { + return false; + } + + // create bounds for contact points + contactWinding.Clear(); + for ( i = 0; i < contacts.Num(); i++ ) { + // project point onto plane through origin orthogonal to the gravity + point = contacts[i].point - (contacts[i].point * gravityNormal) * gravityNormal; + contactWinding.AddToConvexHull( point, gravityNormal ); + } + + // need at least 3 contact points to come to rest + if ( contactWinding.GetNumPoints() < 3 ) { + return false; + } + + // center of mass in world space + point = current.i.position + centerOfMass * current.i.orientation; + point -= (point * gravityNormal) * gravityNormal; + + // if the point is not inside the winding + if ( !contactWinding.PointInside( gravityNormal, point, 0 ) ) { + return false; + } + + // linear velocity of body + v = inverseMass * current.i.linearMomentum; + // linear velocity in gravity direction + gv = v * gravityNormal; + // linear velocity orthogonal to gravity direction + v -= gv * gravityNormal; + + // if too much velocity orthogonal to gravity direction + if ( v.Length() > STOP_SPEED ) { + return false; + } + // if too much velocity in gravity direction + if ( gv > 2.0f * STOP_SPEED || gv < -2.0f * STOP_SPEED ) { + return false; + } + + // calculate rotational velocity + inverseWorldInertiaTensor = current.i.orientation * inverseInertiaTensor * current.i.orientation.Transpose(); + av = inverseWorldInertiaTensor * current.i.angularMomentum; + + // if too much rotational velocity + if ( av.LengthSqr() > STOP_SPEED ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_RigidBody::DropToFloorAndRest + + Drops the object straight down to the floor and verifies if the object is at rest on the floor. +================ +*/ +void idPhysics_RigidBody::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + + testSolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + gameLocal.DWarning( "rigid body in solid for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.i.position + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.i.position, down, clipModel, current.i.orientation, clipMask, self ); +// RAVEN END + current.i.position = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, current.i.orientation ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + // test if we are really at rest + EvaluateContacts(); + if ( !TestIfAtRest() ) { + gameLocal.DWarning( "rigid body not at rest for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + } + Rest(); + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + } +} + +/* +================ +idPhysics_RigidBody::DebugDraw +================ +*/ +void idPhysics_RigidBody::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showMass.GetBool() ) { +// RAVEN BEGIN +// bdube: draw center of mass at the center of mass + gameRenderWorld->DrawText( va( "\n%1.2f", mass ), current.i.position + centerOfMass * current.i.orientation, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +// RAVEN END + } + + if ( rb_showInertia.GetBool() ) { + idMat3 &I = inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + current.i.position, 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + + if ( rb_showVelocity.GetBool() ) { + DrawVelocity( clipModel->GetId(), 0.1f, 4.0f ); + } + +// RAVEN BEGIN +// bdube: added more debug info + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::idPhysics_RigidBody( void ) { + + // set default rigid body properties + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f ); + SetFriction( 0.6f, 0.6f, 0.0f ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + + current.i.position.Zero(); + current.i.orientation.Identity(); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + + saved = current; + + mass = 1.0f; + inverseMass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + inverseInertiaTensor.Identity(); + + // use the least expensive euler integrator + integrator = new idODE_Euler( sizeof(rigidBodyIState_t) / sizeof(float), RigidBodyDerivatives, this ); + + dropToFloor = false; + noImpact = false; + noContact = false; + + hasMaster = false; + isOrientated = false; + +#ifdef RB_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_RigidBody::~idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::~idPhysics_RigidBody( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } + delete integrator; +} + +/* +================ +idPhysics_RigidBody_SavePState +================ +*/ +void idPhysics_RigidBody_SavePState( idSaveGame *savefile, const rigidBodyPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteFloat( state.lastTimeStep ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteMat3( state.localAxis ); + savefile->Write( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->WriteVec3( state.externalForce ); + savefile->WriteVec3( state.externalTorque ); + + savefile->WriteVec3( state.i.position ); + savefile->WriteMat3( state.i.orientation ); + savefile->WriteVec3( state.i.linearMomentum ); + savefile->WriteVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody_RestorePState +================ +*/ +void idPhysics_RigidBody_RestorePState( idRestoreGame *savefile, rigidBodyPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadFloat( state.lastTimeStep ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadMat3( state.localAxis ); + savefile->Read( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->ReadVec3( state.externalForce ); + savefile->ReadVec3( state.externalTorque ); + + savefile->ReadVec3( state.i.position ); + savefile->ReadMat3( state.i.orientation ); + savefile->ReadVec3( state.i.linearMomentum ); + savefile->ReadVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody::Save +================ +*/ +void idPhysics_RigidBody::Save( idSaveGame *savefile ) const { + + idPhysics_RigidBody_SavePState( savefile, current ); + idPhysics_RigidBody_SavePState( savefile, saved ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( inverseMass ); + savefile->WriteVec3( centerOfMass ); + savefile->WriteMat3( inertiaTensor ); + savefile->WriteMat3( inverseInertiaTensor ); + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + savefile->WriteBool( noImpact ); + savefile->WriteBool( noContact ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::Restore +================ +*/ +void idPhysics_RigidBody::Restore( idRestoreGame *savefile ) { + + idPhysics_RigidBody_RestorePState( savefile, current ); + idPhysics_RigidBody_RestorePState( savefile, saved ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( inverseMass ); + savefile->ReadVec3( centerOfMass ); + savefile->ReadMat3( inertiaTensor ); + savefile->ReadMat3( inverseInertiaTensor ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + savefile->ReadBool( noImpact ); + savefile->ReadBool( noContact ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::SetClipModel +================ +*/ +#define MAX_INERTIA_SCALE 10.0f + +void idPhysics_RigidBody::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + int minIndex; + idMat3 inertiaScale; + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.i.position, current.i.orientation ); +// RAVEN END + + // get mass properties from the trace model + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // check whether or not the clip model has valid mass properties + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idPhysics_RigidBody::SetClipModel: invalid mass for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // check whether or not the inertia tensor is balanced + minIndex = Min3Index( inertiaTensor[0][0], inertiaTensor[1][1], inertiaTensor[2][2] ); + inertiaScale.Identity(); + inertiaScale[0][0] = inertiaTensor[0][0] / inertiaTensor[minIndex][minIndex]; + inertiaScale[1][1] = inertiaTensor[1][1] / inertiaTensor[minIndex][minIndex]; + inertiaScale[2][2] = inertiaTensor[2][2] / inertiaTensor[minIndex][minIndex]; + + if ( inertiaScale[0][0] > MAX_INERTIA_SCALE || inertiaScale[1][1] > MAX_INERTIA_SCALE || inertiaScale[2][2] > MAX_INERTIA_SCALE ) { + gameLocal.DWarning( "idPhysics_RigidBody::SetClipModel: unbalanced inertia tensor for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + float min = inertiaTensor[minIndex][minIndex] * MAX_INERTIA_SCALE; + inertiaScale[(minIndex+1)%3][(minIndex+1)%3] = min / inertiaTensor[(minIndex+1)%3][(minIndex+1)%3]; + inertiaScale[(minIndex+2)%3][(minIndex+2)%3] = min / inertiaTensor[(minIndex+2)%3][(minIndex+2)%3]; + inertiaTensor *= inertiaScale; + } + + inverseMass = 1.0f / mass; + inverseInertiaTensor = inertiaTensor.Inverse() * ( 1.0f / 6.0f ); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); +} + +/* +================ +idPhysics_RigidBody::GetClipModel +================ +*/ +idClipModel *idPhysics_RigidBody::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_RigidBody::GetNumClipModels +================ +*/ +int idPhysics_RigidBody::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_RigidBody::SetMass +================ +*/ +void idPhysics_RigidBody::SetMass( float mass, int id ) { + assert( mass > 0.0f ); + inertiaTensor *= mass / this->mass; + inverseInertiaTensor = inertiaTensor.Inverse() * (1.0f / 6.0f); + this->mass = mass; + inverseMass = 1.0f / mass; +} + +/* +================ +idPhysics_RigidBody::GetMass +================ +*/ +float idPhysics_RigidBody::GetMass( int id ) const { + return mass; +} + +// RAVEN BEGIN +// bdube: means of getting center of mass +/* +================ +idPhysics_RigidBody::GetCenterMass +================ +*/ +idVec3 idPhysics_RigidBody::GetCenterMass( int id ) const +{ + return (current.i.position + centerOfMass * current.i.orientation); +} +// RAVEN END + +/* +================ +idPhysics_RigidBody::SetFriction +================ +*/ +void idPhysics_RigidBody::SetFriction( const float linear, const float angular, const float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_RigidBody::SetBouncyness +================ +*/ +void idPhysics_RigidBody::SetBouncyness( const float b ) { + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +idPhysics_RigidBody::Rest +================ +*/ +void idPhysics_RigidBody::Rest( void ) { + current.atRest = gameLocal.time; + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::DropToFloor +================ +*/ +void idPhysics_RigidBody::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +idPhysics_RigidBody::NoContact +================ +*/ +void idPhysics_RigidBody::NoContact( void ) { + noContact = true; +} + +/* +================ +idPhysics_RigidBody::Activate +================ +*/ +void idPhysics_RigidBody::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_RigidBody::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_RigidBody::EnableImpact +================ +*/ +void idPhysics_RigidBody::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_RigidBody::DisableImpact +================ +*/ +void idPhysics_RigidBody::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_RigidBody::SetContents +================ +*/ +void idPhysics_RigidBody::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_RigidBody::GetContents +================ +*/ +int idPhysics_RigidBody::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_RigidBody::GetBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_RigidBody::GetAbsBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_RigidBody::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool idPhysics_RigidBody::Evaluate( int timeStepMSec, int endTimeMSec ) { + rigidBodyPState_t next; + idAngles angles; + trace_t collision; + idVec3 impulse; + idEntity *ent; + idVec3 oldOrigin, masterOrigin; + idMat3 oldAxis, masterAxis; + float timeStep; + bool collided, cameToRest = false; + + timeStep = MS2SEC( timeStepMSec ); + current.lastTimeStep = timeStep; + + if ( hasMaster ) { + oldOrigin = current.i.position; + oldAxis = current.i.orientation; + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.i.orientation = current.localAxis * masterAxis; + } + else { + current.i.orientation = current.localAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + current.i.linearMomentum = mass * ( ( current.i.position - oldOrigin ) / timeStep ); + current.i.angularMomentum = inertiaTensor * ( ( current.i.orientation * oldAxis.Transpose() ).ToAngularVelocity() / timeStep ); + current.externalForce.Zero(); + current.externalTorque.Zero(); + + return ( current.i.position != oldOrigin || current.i.orientation != oldAxis ); + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + current.externalForce.Zero(); + current.externalTorque.Zero(); + return true; + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Start(); + } +#endif + + // move the rigid body velocity into the frame of a pusher +// current.i.linearMomentum -= current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum -= current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + + clipModel->Unlink(); + + next = current; + + // calculate next position and orientation + Integrate( timeStep, next ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions from the current to the next state + collided = CheckForCollisions( timeStep, next, collision ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // set the new state + current = next; + +// trace_t pushResults; +// gameLocal.push.ClipPush( pushResults, self, PUSHFL_CRUSH | PUSHFL_CLIP, saved.localOrigin, saved.localAxis, current.localOrigin, current.localAxis ); + + if ( collided ) { + // apply collision impulse + if ( CollisionImpulse( collision, impulse ) ) { + current.atRest = gameLocal.time; + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + DebugDraw(); + + if ( !noContact ) { + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + // get contacts + EvaluateContacts(); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // check if the body has come to rest + if ( TestIfAtRest() ) { + // put to rest + Rest(); + cameToRest = true; + } else { + // apply contact friction + ContactFriction( timeStep ); + } + } + + if ( current.atRest < 0 ) { + ActivateContactEntities(); + } + + if ( collided ) { + // if the rigid body didn't come to rest or the other entity is not at rest + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent && ( !cameToRest || !ent->IsAtRest() ) ) { + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + } + } + + // move the rigid body velocity back into the world frame +// current.i.linearMomentum += current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum += current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + current.pushVelocity.Zero(); + + current.lastTimeStep = timeStep; + current.externalForce.Zero(); + current.externalTorque.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Stop(); + + if ( rb_showTimings->integer == 1 ) { + gameLocal.Printf( "%12s: t %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + lastTimerReset = 0; + } + else if ( rb_showTimings->integer == 2 ) { + numRigidBodies++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "rb %d: t %1.4f cd %1.4f\n", + numRigidBodies, + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + } + } + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numRigidBodies = 0; + timer_total.Clear(); + timer_collision.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_RigidBody::UpdateTime +================ +*/ +void idPhysics_RigidBody::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_RigidBody::GetTime +================ +*/ +int idPhysics_RigidBody::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_RigidBody::GetImpactInfo +================ +*/ +void idPhysics_RigidBody::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + idVec3 linearVelocity, angularVelocity; + idMat3 inverseWorldInertiaTensor; + + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + + info->invMass = inverseMass; + info->invInertiaTensor = inverseWorldInertiaTensor; + info->position = point - ( current.i.position + centerOfMass * current.i.orientation ); + info->velocity = linearVelocity + angularVelocity.Cross( info->position ); +} + +/* +================ +idPhysics_RigidBody::ApplyImpulse +================ +*/ +void idPhysics_RigidBody::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.i.linearMomentum += impulse; + current.i.angularMomentum += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::AddForce +================ +*/ +void idPhysics_RigidBody::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + current.externalForce += force; + current.externalTorque += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::IsAtRest +================ +*/ +bool idPhysics_RigidBody::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_RigidBody::GetRestStartTime +================ +*/ +int idPhysics_RigidBody::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_RigidBody::IsPushable +================ +*/ +bool idPhysics_RigidBody::IsPushable( void ) const { + return ( !noImpact && !hasMaster ); +} + +/* +================ +idPhysics_RigidBody::SaveState +================ +*/ +void idPhysics_RigidBody::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_RigidBody::RestoreState +================ +*/ +void idPhysics_RigidBody::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void idPhysics_RigidBody::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + newOrigin * masterAxis; + } + else { + current.i.position = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void idPhysics_RigidBody::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.orientation = newAxis * masterAxis; + } + else { + current.i.orientation = newAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), clipModel->GetOrigin(), current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Move +================ +*/ +void idPhysics_RigidBody::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.i.position += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Rotate +================ +*/ +void idPhysics_RigidBody::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.i.orientation *= rotation.ToMat3(); + current.i.position *= rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + current.localOrigin = current.i.position; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetOrigin +================ +*/ +const idVec3 &idPhysics_RigidBody::GetOrigin( int id ) const { + return current.i.position; +} + +/* +================ +idPhysics_RigidBody::GetAxis +================ +*/ +const idMat3 &idPhysics_RigidBody::GetAxis( int id ) const { + return current.i.orientation; +} + +/* +================ +idPhysics_RigidBody::SetLinearVelocity +================ +*/ +void idPhysics_RigidBody::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.i.linearMomentum = newLinearVelocity * mass; + Activate(); +} + +/* +================ +idPhysics_RigidBody::SetAngularVelocity +================ +*/ +void idPhysics_RigidBody::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + current.i.angularMomentum = newAngularVelocity * inertiaTensor; + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + curLinearVelocity = current.i.linearMomentum * inverseMass; + return curLinearVelocity; +} + +/* +================ +idPhysics_RigidBody::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + curAngularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + return curAngularVelocity; +} + +/* +================ +idPhysics_RigidBody::ClipTranslation +================ +*/ +void idPhysics_RigidBody::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipRotation +================ +*/ +void idPhysics_RigidBody::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipContents +================ +*/ +int idPhysics_RigidBody::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::DisableClip +================ +*/ +void idPhysics_RigidBody::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_RigidBody::EnableClip +================ +*/ +void idPhysics_RigidBody::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_RigidBody::UnlinkClip +================ +*/ +void idPhysics_RigidBody::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_RigidBody::LinkClip +================ +*/ +void idPhysics_RigidBody::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::EvaluateContacts +================ +*/ +bool idPhysics_RigidBody::EvaluateContacts( void ) { + idVec6 dir; + int num; + + ClearContacts(); + + contacts.SetNum( 10, false ); + + dir.SubVec3(0) = current.i.linearMomentum + current.lastTimeStep * gravityVector * mass; + dir.SubVec3(1) = current.i.angularMomentum; + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[0], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( num, false ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_RigidBody::SetPushed +================ +*/ +void idPhysics_RigidBody::SetPushed( int deltaTime ) { + idRotation rotation; + + rotation = ( saved.i.orientation * current.i.orientation ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( current.i.position - saved.i.position ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_RigidBody::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_RigidBody::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_RigidBody::SetMaster +================ +*/ +void idPhysics_RigidBody::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.i.orientation * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + } + hasMaster = true; + isOrientated = orientated; + ClearContacts(); + } + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +const float RB_VELOCITY_MAX = 16000; +const int RB_VELOCITY_TOTAL_BITS = 16; +const int RB_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_VELOCITY_MAX ) ) + 1; +const int RB_VELOCITY_MANTISSA_BITS = RB_VELOCITY_TOTAL_BITS - 1 - RB_VELOCITY_EXPONENT_BITS; +const float RB_MOMENTUM_MAX = 1e20f; +const int RB_MOMENTUM_TOTAL_BITS = 16; +const int RB_MOMENTUM_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_MOMENTUM_MAX ) ) + 1; +const int RB_MOMENTUM_MANTISSA_BITS = RB_MOMENTUM_TOTAL_BITS - 1 - RB_MOMENTUM_EXPONENT_BITS; +const float RB_FORCE_MAX = 1e20f; +const int RB_FORCE_TOTAL_BITS = 16; +const int RB_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_FORCE_MAX ) ) + 1; +const int RB_FORCE_MANTISSA_BITS = RB_FORCE_TOTAL_BITS - 1 - RB_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_RigidBody::WriteToSnapshot +================ +*/ +void idPhysics_RigidBody::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.i.orientation.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.i.position[0] ); + msg.WriteFloat( current.i.position[1] ); + msg.WriteFloat( current.i.position[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteFloat( current.i.linearMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.i.position[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.i.position[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.i.position[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); +} + +/* +================ +idPhysics_RigidBody::ReadFromSnapshot +================ +*/ +void idPhysics_RigidBody::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.atRest = msg.ReadLong(); + current.i.position[0] = msg.ReadFloat(); + current.i.position[1] = msg.ReadFloat(); + current.i.position[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.i.linearMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.i.position[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.i.position[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.i.position[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.externalForce[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + + current.i.orientation = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + } +} diff --git a/src/game/physics/Physics_RigidBody.h b/src/game/physics/Physics_RigidBody.h new file mode 100644 index 0000000..bcada6b --- /dev/null +++ b/src/game/physics/Physics_RigidBody.h @@ -0,0 +1,195 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_RIGIDBODY_H__ +#define __PHYSICS_RIGIDBODY_H__ + +/* +=================================================================================== + + Rigid body physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct rididBodyIState_s { + idVec3 position; // position of trace model + idMat3 orientation; // orientation of trace model + idVec3 linearMomentum; // translational momentum relative to center of mass + idVec3 angularMomentum; // rotational momentum relative to center of mass +} rigidBodyIState_t; + +typedef struct rigidBodyPState_s { + int atRest; // set when simulation is suspended + float lastTimeStep; // length of last time step + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec6 pushVelocity; // push velocity + idVec3 externalForce; // external force relative to center of mass + idVec3 externalTorque; // external torque relative to center of mass + rigidBodyIState_t i; // state used for integration +} rigidBodyPState_t; + +class idPhysics_RigidBody : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( idPhysics_RigidBody ); + + idPhysics_RigidBody( void ); + ~idPhysics_RigidBody( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b ); + // same as above but drop to the floor first + void DropToFloor( void ); + // no contact determination and contact friction + void NoContact( void ); + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + // state of the rigid body + rigidBodyPState_t current; + rigidBodyPState_t saved; + +private: + + // rigid body properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + idClipModel * clipModel; // clip model used for collision detection + + // derived properties + float mass; // mass of body + float inverseMass; // 1 / mass + idVec3 centerOfMass; // center of mass of trace model + idMat3 inertiaTensor; // mass distribution + idMat3 inverseInertiaTensor; // inverse inertia tensor + + idODE * integrator; // integrator + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for solid when dropping to the floor + bool noImpact; // if true do not activate when another object collides + bool noContact; // if true do not determine contacts and no contact friction + + // master + bool hasMaster; + bool isOrientated; + +private: + friend void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ); + void Integrate( const float deltaTime, rigidBodyPState_t &next ); + bool CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ); + bool CollisionImpulse( const trace_t &collision, idVec3 &impulse ); + void ContactFriction( float deltaTime ); + void DropToFloorAndRest( void ); + bool TestIfAtRest( void ) const; + void Rest( void ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_RIGIDBODY_H__ */ diff --git a/src/game/physics/Physics_Static.cpp b/src/game/physics/Physics_Static.cpp new file mode 100644 index 0000000..b0295c4 --- /dev/null +++ b/src/game/physics/Physics_Static.cpp @@ -0,0 +1,905 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Static ) +END_CLASS + +/* +================ +idPhysics_Static::idPhysics_Static +================ +*/ +idPhysics_Static::idPhysics_Static( void ) { + self = NULL; + clipModel = NULL; + current.origin.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAxis.Identity(); + hasMaster = false; + isOrientated = false; +} + +/* +================ +idPhysics_Static::~idPhysics_Static +================ +*/ +idPhysics_Static::~idPhysics_Static( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + if ( clipModel ) { + delete clipModel; + } +} + +/* +================ +idPhysics_Static::Save +================ +*/ +void idPhysics_Static::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + + savefile->WriteVec3( current.origin ); + savefile->WriteMat3( current.axis ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_Static::Restore +================ +*/ +void idPhysics_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadVec3( current.origin ); + savefile->ReadMat3( current.axis ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_Static::SetSelf +================ +*/ +void idPhysics_Static::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Static::SetClipModel +================ +*/ +void idPhysics_Static::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + assert( self ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetClipModel +================ +*/ +idClipModel *idPhysics_Static::GetClipModel( int id ) const { + if ( clipModel ) { + return clipModel; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_Static::GetNumClipModels +================ +*/ +int idPhysics_Static::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Static::SetMass +================ +*/ +void idPhysics_Static::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Static::GetMass +================ +*/ +float idPhysics_Static::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Static::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Static::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Static::SetContents +================ +*/ +void idPhysics_Static::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Static::GetContents +================ +*/ +int idPhysics_Static::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Static::SetClipMask +================ +*/ +void idPhysics_Static::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_Static::GetClipMask +================ +*/ +int idPhysics_Static::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetBounds +================ +*/ +const idBounds &idPhysics_Static::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return bounds_zero; +} + +/* +================ +idPhysics_Static::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Static::GetAbsBounds( int id ) const { + static idBounds absBounds; + + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + absBounds[0] = absBounds[1] = current.origin; + return absBounds; +} + +/* +================ +idPhysics_Static::Evaluate +================ +*/ +bool idPhysics_Static::Evaluate( int timeStepMSec, int endTimeMSec ) { +// RAVEN BEGIN +// bdube: draw bbox + if ( hasMaster ) { + idVec3 masterOrigin; + idVec3 oldOrigin; + idMat3 masterAxis; + idMat3 oldAxis; +// RAVEN END + + oldOrigin = current.origin; + oldAxis = current.axis; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.axis = current.localAxis * masterAxis; + } else { + current.axis = current.localAxis; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); + } + return false; +} + +/* +================ +idPhysics_Static::UpdateTime +================ +*/ +void idPhysics_Static::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Static::GetTime +================ +*/ +int idPhysics_Static::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetImpactInfo +================ +*/ +void idPhysics_Static::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Static::ApplyImpulse +================ +*/ +void idPhysics_Static::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Static::AddForce +================ +*/ +void idPhysics_Static::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Static::Activate +================ +*/ +void idPhysics_Static::Activate( void ) { +} + +/* +================ +idPhysics_Static::PutToRest +================ +*/ +void idPhysics_Static::PutToRest( void ) { +} + +/* +================ +idPhysics_Static::IsAtRest +================ +*/ +bool idPhysics_Static::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Static::GetRestStartTime +================ +*/ +int idPhysics_Static::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::IsPushable +================ +*/ +bool idPhysics_Static::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_Static::IsInWater +================ +*/ +bool idPhysics_Static::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Static::SaveState +================ +*/ +void idPhysics_Static::SaveState( void ) { +} + +/* +================ +idPhysics_Static::RestoreState +================ +*/ +void idPhysics_Static::RestoreState( void ) { +} + +/* +================ +idPhysics_Static::SetOrigin +================ +*/ +void idPhysics_Static::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } else { + current.origin = newOrigin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::SetAxis +================ +*/ +void idPhysics_Static::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } else { + current.axis = newAxis; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Translate +================ +*/ +void idPhysics_Static::Translate( const idVec3 &translation, int id ) { + current.localOrigin += translation; + current.origin += translation; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Rotate +================ +*/ +void idPhysics_Static::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + current.axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + current.localOrigin = current.origin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetOrigin +================ +*/ +const idVec3 &idPhysics_Static::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Static::GetAxis +================ +*/ +const idMat3 &idPhysics_Static::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Static::SetLinearVelocity +================ +*/ +void idPhysics_Static::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Static::SetAngularVelocity +================ +*/ +void idPhysics_Static::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Static::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetGravity +================ +*/ +void idPhysics_Static::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_Static::GetGravity +================ +*/ +const idVec3 &idPhysics_Static::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_Static::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Static::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_Static::ClipTranslation +================ +*/ +void idPhysics_Static::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Translation( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::ClipRotation +================ +*/ +void idPhysics_Static::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, current.origin, rotation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Rotation( self, results, current.origin, rotation, clipModel, current.axis, MASK_SOLID, self ); + } +// RAVEN END +} + +/* +================ +idPhysics_Static::ClipContents +================ +*/ +int idPhysics_Static::ClipContents( const idClipModel *model ) const { + if ( clipModel ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } + } + return 0; +} + +/* +================ +idPhysics_Static::DisableClip +================ +*/ +void idPhysics_Static::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Static::EnableClip +================ +*/ +void idPhysics_Static::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Static::UnlinkClip +================ +*/ +void idPhysics_Static::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Static::LinkClip +================ +*/ +void idPhysics_Static::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::EvaluateContacts +================ +*/ +bool idPhysics_Static::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Static::GetNumContacts +================ +*/ +int idPhysics_Static::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetContact +================ +*/ +const contactInfo_t &idPhysics_Static::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_Static::ClearContacts +================ +*/ +void idPhysics_Static::ClearContacts( void ) { +} + +/* +================ +idPhysics_Static::AddContactEntity +================ +*/ +void idPhysics_Static::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::RemoveContactEntity +================ +*/ +void idPhysics_Static::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::HasGroundContacts +================ +*/ +bool idPhysics_Static::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundEntity +================ +*/ +bool idPhysics_Static::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundClipModel +================ +*/ +bool idPhysics_Static::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_Static::SetPushed +================ +*/ +void idPhysics_Static::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Static::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetMaster +================ +*/ +void idPhysics_Static::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.axis * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Static::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Static::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Static::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetLinearEndTime +================ +*/ +int idPhysics_Static::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetAngularEndTime +================ +*/ +int idPhysics_Static::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::WriteToSnapshot +================ +*/ +void idPhysics_Static::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.axis.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Static::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current.axis = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); +} diff --git a/src/game/physics/Physics_Static.h b/src/game/physics/Physics_Static.h new file mode 100644 index 0000000..6e713bf --- /dev/null +++ b/src/game/physics/Physics_Static.h @@ -0,0 +1,178 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_STATIC_H__ +#define __PHYSICS_STATIC_H__ + +/* +=============================================================================== + + Physics for a non moving object using at most one collision model. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +class idStaticPState { + +public: + idVec3 origin; + idMat3 axis; + idVec3 localOrigin; + idMat3 localAxis; + + idStaticPState( void ) { } +}; +// RAVEN END + +class idPhysics_Static : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Static ); + + idPhysics_Static( void ); + ~idPhysics_Static( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idStaticPState current; // physics state +// RAVEN END + idClipModel * clipModel; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATIC_H__ */ diff --git a/src/game/physics/Physics_StaticMulti.cpp b/src/game/physics/Physics_StaticMulti.cpp new file mode 100644 index 0000000..6f7bcad --- /dev/null +++ b/src/game/physics/Physics_StaticMulti.cpp @@ -0,0 +1,1111 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_StaticMulti ) +END_CLASS + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +idStaticPState defaultState; +// RAVEN END + +/* +================ +idPhysics_StaticMulti::idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::idPhysics_StaticMulti( void ) { + self = NULL; + hasMaster = false; + isOrientated = false; + + defaultState.origin.Zero(); + defaultState.axis.Identity(); + defaultState.localOrigin.Zero(); + defaultState.localAxis.Identity(); + + current.SetNum( 1 ); + current[0] = defaultState; + clipModels.SetNum( 1 ); + clipModels[0] = NULL; +} + +/* +================ +idPhysics_StaticMulti::~idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::~idPhysics_StaticMulti( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + for ( int i = 0; i < clipModels.Num(); i++ ) { + delete clipModels[i]; + } +} + +/* +================ +idPhysics_StaticMulti::Save +================ +*/ +void idPhysics_StaticMulti::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + + savefile->WriteInt(current.Num()); + for ( i = 0; i < current.Num(); i++ ) { + savefile->WriteVec3( current[i].origin ); + savefile->WriteMat3( current[i].axis ); + savefile->WriteVec3( current[i].localOrigin ); + savefile->WriteMat3( current[i].localAxis ); + } + + savefile->WriteInt( clipModels.Num() ); + for ( i = 0; i < clipModels.Num(); i++ ) { + savefile->WriteClipModel( clipModels[i] ); + } + + savefile->WriteBool(hasMaster); + savefile->WriteBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::Restore +================ +*/ +void idPhysics_StaticMulti::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadInt(num); + current.AssureSize( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadVec3( current[i].origin ); + savefile->ReadMat3( current[i].axis ); + savefile->ReadVec3( current[i].localOrigin ); + savefile->ReadMat3( current[i].localAxis ); + } + + savefile->ReadInt(num); + clipModels.SetNum( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadClipModel( clipModels[i] ); + } + + savefile->ReadBool(hasMaster); + savefile->ReadBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::SetSelf +================ +*/ +void idPhysics_StaticMulti::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_StaticMulti::RemoveIndex +================ +*/ +void idPhysics_StaticMulti::RemoveIndex( int id, bool freeClipModel ) { + if ( id < 0 || id >= clipModels.Num() ) { + return; + } + if ( clipModels[id] && freeClipModel ) { + delete clipModels[id]; + clipModels[id] = NULL; + } + clipModels.RemoveIndex( id ); + current.RemoveIndex( id ); +} + +/* +================ +idPhysics_StaticMulti::SetClipModel +================ +*/ +void idPhysics_StaticMulti::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + int i; + + assert( self ); + + if ( id >= clipModels.Num() ) { + current.AssureSize( id+1, defaultState ); + clipModels.AssureSize( id+1, NULL ); + } + + if ( clipModels[id] && clipModels[id] != model && freeOld ) { + delete clipModels[id]; + } + clipModels[id] = model; + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + + for ( i = clipModels.Num() - 1; i >= 1; i-- ) { + if ( clipModels[i] ) { + break; + } + } + current.SetNum( i+1, false ); + clipModels.SetNum( i+1, false ); +} + +/* +================ +idPhysics_StaticMulti::GetClipModel +================ +*/ +idClipModel *idPhysics_StaticMulti::GetClipModel( int id ) const { + if ( id >= 0 && id < clipModels.Num() && clipModels[id] ) { + return clipModels[id]; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_StaticMulti::GetNumClipModels +================ +*/ +int idPhysics_StaticMulti::GetNumClipModels( void ) const { + return clipModels.Num(); +} + +/* +================ +idPhysics_StaticMulti::SetMass +================ +*/ +void idPhysics_StaticMulti::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetMass +================ +*/ +float idPhysics_StaticMulti::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_StaticMulti::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_StaticMulti::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SetContents +================ +*/ +void idPhysics_StaticMulti::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + clipModels[id]->SetContents( contents ); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->SetContents( contents ); + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetContents +================ +*/ +int idPhysics_StaticMulti::GetContents( int id ) const { + int i, contents = 0; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + contents = clipModels[id]->GetContents(); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + contents |= clipModels[i]->GetContents(); + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::SetClipMask +================ +*/ +void idPhysics_StaticMulti::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetClipMask +================ +*/ +int idPhysics_StaticMulti::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetBounds( int id ) const { + int i; + static idBounds bounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetBounds(); + } + } + if ( id == -1 ) { + bounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds[0] -= clipModels[i]->GetOrigin(); + bounds[1] -= clipModels[i]->GetOrigin(); + break; + } + } + return bounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::GetAbsBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetAbsBounds(); + } + } + if ( id == -1 ) { + absBounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + absBounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + return absBounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::Evaluate +================ +*/ +bool idPhysics_StaticMulti::Evaluate( int timeStepMSec, int endTimeMSec ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin = masterOrigin + current[i].localOrigin * masterAxis; + if ( isOrientated ) { + current[i].axis = current[i].localAxis * masterAxis; + } else { + current[i].axis = current[i].localAxis; + } + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + + // FIXME: return false if master did not move + return true; + } + return false; +} + +/* +================ +idPhysics_StaticMulti::UpdateTime +================ +*/ +void idPhysics_StaticMulti::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_StaticMulti::GetTime +================ +*/ +int idPhysics_StaticMulti::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetImpactInfo +================ +*/ +void idPhysics_StaticMulti::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_StaticMulti::ApplyImpulse +================ +*/ +void idPhysics_StaticMulti::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_StaticMulti::AddForce +================ +*/ +void idPhysics_StaticMulti::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_StaticMulti::Activate +================ +*/ +void idPhysics_StaticMulti::Activate( void ) { +} + +/* +================ +idPhysics_StaticMulti::PutToRest +================ +*/ +void idPhysics_StaticMulti::PutToRest( void ) { +} + +/* +================ +idPhysics_StaticMulti::IsAtRest +================ +*/ +bool idPhysics_StaticMulti::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_StaticMulti::GetRestStartTime +================ +*/ +int idPhysics_StaticMulti::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::IsPushable +================ +*/ +bool idPhysics_StaticMulti::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_StaticMulti::IsInWater +================ +*/ +bool idPhysics_StaticMulti::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SaveState +================ +*/ +void idPhysics_StaticMulti::SaveState( void ) { +} + +/* +================ +idPhysics_StaticMulti::RestoreState +================ +*/ +void idPhysics_StaticMulti::RestoreState( void ) { +} + +/* +================ +idPhysics_StaticMulti::SetOrigin +================ +*/ +void idPhysics_StaticMulti::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].origin = masterOrigin + newOrigin * masterAxis; + } else { + current[id].origin = newOrigin; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + Translate( masterOrigin + masterAxis * newOrigin - current[0].origin ); + } else { + Translate( newOrigin - current[0].origin ); + } + } +} + +/* +================ +idPhysics_StaticMulti::SetAxis +================ +*/ +void idPhysics_StaticMulti::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].axis = newAxis * masterAxis; + } else { + current[id].axis = newAxis; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + idMat3 axis; + idRotation rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + axis = current[0].axis.Transpose() * ( newAxis * masterAxis ); + } else { + axis = current[0].axis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( current[0].origin ); + + Rotate( rotation ); + } +} + +/* +================ +idPhysics_StaticMulti::Translate +================ +*/ +void idPhysics_StaticMulti::Translate( const idVec3 &translation, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin += translation; + current[id].origin += translation; + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin += translation; + current[i].origin += translation; + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::Rotate +================ +*/ +void idPhysics_StaticMulti::Rotate( const idRotation &rotation, int id ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].origin *= rotation; + current[id].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].localAxis *= rotation.ToMat3(); + current[id].localOrigin = ( current[id].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[id].localAxis = current[id].axis; + current[id].localOrigin = current[id].origin; + } + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin *= rotation; + current[i].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[i].localAxis *= rotation.ToMat3(); + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + current[i].localOrigin = current[i].origin; + } + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetOrigin +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetOrigin( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].origin; + } + if ( clipModels.Num() ) { + return current[0].origin; + } else { + return vec3_origin; + } +} + +/* +================ +idPhysics_StaticMulti::GetAxis +================ +*/ +const idMat3 &idPhysics_StaticMulti::GetAxis( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].axis; + } + if ( clipModels.Num() ) { + return current[0].axis; + } else { + return mat3_identity; + } +} + +/* +================ +idPhysics_StaticMulti::SetLinearVelocity +================ +*/ +void idPhysics_StaticMulti::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::SetAngularVelocity +================ +*/ +void idPhysics_StaticMulti::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetGravity +================ +*/ +void idPhysics_StaticMulti::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_StaticMulti::GetGravity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_StaticMulti::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_StaticMulti::ClipTranslation +================ +*/ +void idPhysics_StaticMulti::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipTranslation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipRotation +================ +*/ +void idPhysics_StaticMulti::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipRotation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipContents +================ +*/ +int idPhysics_StaticMulti::ClipContents( const idClipModel *model ) const { + int i, contents; + + contents = 0; + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + contents |= gameLocal.Contents( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, NULL ); +// RAVEN END + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::DisableClip +================ +*/ +void idPhysics_StaticMulti::DisableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Disable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::EnableClip +================ +*/ +void idPhysics_StaticMulti::EnableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Enable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::UnlinkClip +================ +*/ +void idPhysics_StaticMulti::UnlinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Unlink(); + } + } +} + +/* +================ +idPhysics_StaticMulti::LinkClip +================ +*/ +void idPhysics_StaticMulti::LinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } +} + +/* +================ +idPhysics_StaticMulti::EvaluateContacts +================ +*/ +bool idPhysics_StaticMulti::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_StaticMulti::GetNumContacts +================ +*/ +int idPhysics_StaticMulti::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetContact +================ +*/ +const contactInfo_t &idPhysics_StaticMulti::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_StaticMulti::ClearContacts +================ +*/ +void idPhysics_StaticMulti::ClearContacts( void ) { +} + +/* +================ +idPhysics_StaticMulti::AddContactEntity +================ +*/ +void idPhysics_StaticMulti::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::RemoveContactEntity +================ +*/ +void idPhysics_StaticMulti::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::HasGroundContacts +================ +*/ +bool idPhysics_StaticMulti::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundEntity +================ +*/ +bool idPhysics_StaticMulti::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundClipModel +================ +*/ +bool idPhysics_StaticMulti::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::SetPushed +================ +*/ +void idPhysics_StaticMulti::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_StaticMulti::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetMaster +================ +*/ +void idPhysics_StaticMulti::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current[i].localAxis = current[i].axis * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + } + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_StaticMulti::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_StaticMulti::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetBlockingEntity +================ +*/ +idEntity *idPhysics_StaticMulti::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetLinearEndTime +================ +*/ +int idPhysics_StaticMulti::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetAngularEndTime +================ +*/ +int idPhysics_StaticMulti::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::WriteToSnapshot +================ +*/ +void idPhysics_StaticMulti::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat, localQuat; + + // TODO: Check that this conditional write to delta message is OK + msg.WriteByte( current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + quat = current[i].axis.ToCQuat(); + localQuat = current[i].localAxis.ToCQuat(); + + msg.WriteFloat( current[i].origin[0] ); + msg.WriteFloat( current[i].origin[1] ); + msg.WriteFloat( current[i].origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current[i].origin[0], current[i].localOrigin[0] ); + msg.WriteDeltaFloat( current[i].origin[1], current[i].localOrigin[1] ); + msg.WriteDeltaFloat( current[i].origin[2], current[i].localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + } +} + +/* +================ +idPhysics_StaticMulti::ReadFromSnapshot +================ +*/ +void idPhysics_StaticMulti::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat, localQuat; + + num = msg.ReadByte(); + assert( num == current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + current[i].origin[0] = msg.ReadFloat(); + current[i].origin[1] = msg.ReadFloat(); + current[i].origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current[i].localOrigin[0] = msg.ReadDeltaFloat( current[i].origin[0] ); + current[i].localOrigin[1] = msg.ReadDeltaFloat( current[i].origin[1] ); + current[i].localOrigin[2] = msg.ReadDeltaFloat( current[i].origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current[i].axis = quat.ToMat3(); + current[i].localAxis = localQuat.ToMat3(); + } +} diff --git a/src/game/physics/Physics_StaticMulti.h b/src/game/physics/Physics_StaticMulti.h new file mode 100644 index 0000000..6c15a10 --- /dev/null +++ b/src/game/physics/Physics_StaticMulti.h @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_STATICMULTI_H__ +#define __PHYSICS_STATICMULTI_H__ + +/* +=============================================================================== + + Physics for a non moving object using no or multiple collision models. + +=============================================================================== +*/ + +class idPhysics_StaticMulti : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_StaticMulti ); + + idPhysics_StaticMulti( void ); + ~idPhysics_StaticMulti( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void RemoveIndex( int id = 0, bool freeClipModel = true ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idList current; // physics state +// RAVEN END + idList clipModels; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATICMULTI_H__ */ diff --git a/src/game/physics/Physics_VehicleMonster.cpp b/src/game/physics/Physics_VehicleMonster.cpp new file mode 100644 index 0000000..56793ca --- /dev/null +++ b/src/game/physics/Physics_VehicleMonster.cpp @@ -0,0 +1,60 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_RigidBody, rvPhysics_VehicleMonster ) +END_CLASS + +/* +================ +rvPhysics_VehicleMonster::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_VehicleMonster::Evaluate( int timeStepMSec, int endTimeMSec ) { + if ( idPhysics_RigidBody::Evaluate( timeStepMSec, endTimeMSec ) ) { + + idAngles euler = current.i.orientation.ToAngles(); + euler.pitch = 0.0f; + euler.roll = 0.0f; + current.i.orientation = euler.ToMat3(); + + return true; + } + + return false; +} + +void rvPhysics_VehicleMonster::SetGravity ( const idVec3 & v ) { + gravityVector = v; + gravityNormal = gameLocal.GetGravity( ); + gravityNormal.Normalize(); +} diff --git a/src/game/physics/Physics_VehicleMonster.h b/src/game/physics/Physics_VehicleMonster.h new file mode 100644 index 0000000..6764790 --- /dev/null +++ b/src/game/physics/Physics_VehicleMonster.h @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_VEHICLE_MONSTER_H__ +#define __PHYSICS_VEHICLE_MONSTER_H__ + +/* +=================================================================================== + + Vehicle Monster Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + Extents particle physics with the ability to apply impulses. + +=================================================================================== +*/ + +class rvPhysics_VehicleMonster : public idPhysics_RigidBody { +public: + CLASS_PROTOTYPE( rvPhysics_VehicleMonster ); + + bool Evaluate ( int timeStepMSec, int endTimeMSec ); + void SetGravity ( const idVec3 & v ); +}; + +#endif /* !__PHYSICS_VEHICLE_MONSTER_H__ */ diff --git a/src/game/physics/Push.cpp b/src/game/physics/Push.cpp new file mode 100644 index 0000000..62ddf25 --- /dev/null +++ b/src/game/physics/Push.cpp @@ -0,0 +1,1526 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +// RAVEN BEGIN +// bdube: projectile +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif +// RAVEN END + +/* +============ +idPush::InitSavingPushedEntityPositions +============ +*/ +void idPush::InitSavingPushedEntityPositions( void ) { + numPushed = 0; +} + +/* +============ +idPush::SaveEntityPosition +============ +*/ +void idPush::SaveEntityPosition( idEntity *ent ) { + int i; + + // if already saved the physics state for this entity + for ( i = 0; i < numPushed; i++ ) { + if ( pushed[i].ent == ent ) { + return; + } + } + + // don't overflow + if ( numPushed >= MAX_GENTITIES ) { + gameLocal.Error( "more than MAX_GENTITIES pushed entities" ); + return; + } + + pushed[numPushed].ent = ent; + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // save the delta view angles + pushed[numPushed].deltaViewAngles = static_cast(ent)->GetDeltaViewAngles(); + } + + // save the physics state + ent->GetPhysics()->SaveState(); + + numPushed++; +} + +/* +============ +idPush::RestorePushedEntityPositions +============ +*/ +void idPush::RestorePushedEntityPositions( void ) { + int i; + + for ( i = 0; i < numPushed; i++ ) { + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( pushed[i].ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // set back the delta view angles + static_cast(pushed[i].ent)->SetDeltaViewAngles( pushed[i].deltaViewAngles ); + } + + // restore the physics state + pushed[i].ent->GetPhysics()->RestoreState(); + } +} + +/* +============ +idPush::RotateEntityToAxial +============ +*/ +bool idPush::RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ) { + int i; + trace_t trace; + idRotation rotation; + idMat3 axis; + idPhysics *physics; + + physics = ent->GetPhysics(); + axis = physics->GetAxis(); + if ( !axis.IsRotated() ) { + return true; + } + +// RAVEN BEGIN +// abahr: caching current upVector to reduce visual effects of fp error + idVec3 upVector = -physics->GetGravityNormal(); +// RAVEN END + + // try to rotate the bbox back to axial with at most four rotations + for ( i = 0; i < 4; i++ ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + axis = physics->GetAxis()[2].ToMat3( 2 ); +// RAVEN END + rotation = axis.ToRotation(); + rotation.Scale( -1 ); + rotation.SetOrigin( rotationPoint ); + // tiny float numbers in the clip axis, this can get the entity stuck + if ( rotation.GetAngle() == 0.0f ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // + ent->GetPhysics()->ClipRotation( trace, rotation, NULL ); + // if the full rotation is possible + if ( trace.fraction >= 1.0f ) { + // set bbox in final axial position + physics->SetOrigin( trace.endpos ); +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // if partial rotation was possible + else if ( trace.fraction > 0.0f ) { + // partial rotation + physics->SetOrigin( trace.endpos ); + physics->SetAxis( trace.endAxis ); + } + // next rotate around collision point + rotationPoint = trace.c.point; + } + return false; +} + +#ifdef NEW_PUSH + +/* +============ +idPush::CanPushEntity +============ +*/ +bool idPush::CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ) { + + // if the physics object is not pushable + if ( !ent->GetPhysics()->IsPushable() ) { + return false; + } + + // if the entity doesn't clip with this pusher + if ( !( ent->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + return false; + } + + // don't push players in noclip mode + if ( ent->client && ent->client->noclip ) { + return false; + } + + // if we should only push idMoveable entities + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !ent->IsType( idMoveable::Type ) ) { + return false; + } + + // if we shouldn't push entities the original pusher rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( initialPusher->GetPhysics()->IsGroundEntity( ent->entityNumber ) ) { + return false; + } + } + + return true; +} + +/* +============ +idPush::AddEntityToPushedGroup +============ +*/ +void idPush::AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ) { + int i, j; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + if ( fraction > pushedGroup[i].fraction ) { + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact &= groundContact; + pushedGroup[i].test = true; + } + return; + } + else if ( fraction > pushedGroup[i].fraction ) { + for ( j = pushedGroupSize; j > i; j-- ) { + pushedGroup[j] = pushedGroup[j-1]; + } + break; + } + } + + // put the entity in the group + pushedGroupSize++; + pushedGroup[i].ent = ent; + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact = groundContact; + pushedGroup[i].test = true; + + // remove any further occurances of the same entity in the group + for ( i++; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + for ( j = i+1; j < pushedGroupSize; j++ ) { + pushedGroup[j-1] = pushedGroup[j]; + } + pushedGroupSize--; + break; + } + } +} + +/* +============ +idPush::IsFullyPushed +============ +*/ +bool idPush::IsFullyPushed( idEntity *ent ) { + int i; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( pushedGroup[i].fraction < 1.0f ) { + return false; + } + if ( ent == pushedGroup[i].ent ) { + return true; + } + } + return false; +} + +/* +============ +idPush::ClipTranslationAgainstPusher +============ +*/ +bool idPush::ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipTranslation( t, translation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForTranslation +============ +*/ +int idPush::GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsTranslation( bounds, physics->GetOrigin(), physics->GetAxis(), translation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( this, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by translating the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idVec3 realTranslation, partialTranslation; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = pusher->GetPhysics()->GetAxis(); + memset( results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForTranslation( pusher, pusher, flags, translation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip translation + pusher->GetPhysics()->ClipTranslation( results, translation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realTranslation = results.fraction * translation; + } + else { + realTranslation = translation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForTranslation( curPusher, pusher, flags, realTranslation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( !CanPushEntity( ent, curPusher, pusher, flags ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipTranslationAgainstPusher( trace, ent, curPusher, -fraction * realTranslation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 0; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipTranslation( trace, partialTranslation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipTranslation( results, realTranslation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // translate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Translate( partialTranslation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // translate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Translate( results.fraction * realTranslation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + } + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationAgainstPusher +============ +*/ +bool idPush::ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipRotation( t, rotation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForRotation +============ +*/ +int idPush::GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsRotation( bounds, physics->GetOrigin(), physics->GetAxis(), rotation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by rotating the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idRotation realRotation, partialRotation; + idMat3 oldAxis; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = newAxis; + memset( results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForRotation( pusher, pusher, flags, rotation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip rotation + pusher->GetPhysics()->ClipRotation( results, rotation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realRotation = results.fraction * rotation; + } + else { + realRotation = rotation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForRotation( curPusher, pusher, flags, realRotation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipRotationAgainstPusher( trace, ent, curPusher, -fraction * realRotation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipRotation( trace, partialRotation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipRotation( results, realRotation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // rotate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Rotate( partialRotation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // rotate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Rotate( realRotation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + pusher->GetPhysics()->GetClipModel(i)->Enable(); + } + // rotate any actors back to axial + for ( i = 1; i < pushedGroupSize; i++ ) { + // if the entity is using actor physics + if ( pushedGroup[i].ent->GetPhysics()->IsType( idPhysics_Actor::Type ) ) { + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( pushedGroup[i].ent, pushedGroup[i].ent->GetPhysics()->GetOrigin() ) ) { + // don't allow rotation if the bbox is no longer axial + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + } + } + + return totalMass; +} + +#else /* !NEW_PUSH */ + +enum { + PUSH_NO, // not pushed + PUSH_OK, // pushed ok + PUSH_BLOCKED // blocked +}; + +/* +============ +idPush::ClipEntityRotation +============ +*/ +void idPush::ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idRotation &rotation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipRotation( trace, rotation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::ClipEntityTranslation +============ +*/ +void idPush::ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idVec3 &translation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipTranslation( trace, translation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::TryRotatePushEntity +============ +*/ +#ifdef _DEBUG +// #define ROTATIONAL_PUSH_DEBUG +#endif + +int idPush::TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + trace_t trace; + idVec3 rotationPoint; + idRotation newRotation; + float checkAngle; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef ROTATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate the entity colliding with all other entities except the pusher itself + ClipEntityRotation( trace, check, NULL, clipModel, rotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle() * trace.fraction; + // test if the entity can stay at it's partly pushed position by rotating + // the entity in reverse only colliding with pusher + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), -(rotation.GetAngle() - checkAngle) ); + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle(); + } + // point to rotate entity bbox around back to axial + rotationPoint = physics->GetOrigin(); + } + else { + // rotate entity in reverse only colliding with pusher + newRotation = rotation; + newRotation.Scale( -1 ); + // + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { +#ifdef ROTATIONAL_PUSH_DEBUG + // set pusher into final position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return PUSH_NO; + } + // get point to rotate bbox around back to axial + rotationPoint = results.c.point; + // angle along which the entity will be pushed + checkAngle = rotation.GetAngle() * (1.0f - results.fraction); + // rotate the entity colliding with all other entities except the pusher itself + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + ClipEntityRotation( trace, check, NULL, clipModel, newRotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + + SaveEntityPosition( check ); + + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + // NOTE: this code prevents msvc 6.0 & 7.0 from screwing up the above code in + // release builds moving less floats than it should + static float shit = checkAngle; + + newRotation.RotatePoint( rotationPoint ); + + // rotate the entity + physics->Rotate( newRotation ); + + // set pusher into final position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity uses actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( check, rotationPoint ) ) { + // don't allow rotation if the bbox is no longer axial + return PUSH_BLOCKED; + } + } + +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity is an actor using actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idActor::GetClassType() ) && physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // if the entity is standing ontop of the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate actor view + idActor *actor = static_cast(check); + idAngles delta = actor->GetDeltaViewAngles(); + delta.yaw += newRotation.ToMat3()[0].ToYaw(); + actor->SetDeltaViewAngles( delta ); + } + } + + return PUSH_OK; +} + +/* +============ +idPush::TryTranslatePushEntity +============ +*/ +#ifdef _DEBUG +// #define TRANSLATIONAL_PUSH_DEBUG +#endif + +int idPush::TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ) { + trace_t trace; + idVec3 checkMove; + idVec3 oldOrigin; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, move ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // vector along which the entity is pushed + checkMove = move * trace.fraction; + // test if the entity can stay at it's partly pushed position by moving the entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -(move - checkMove) ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // vector along which the entity is pushed + checkMove = move; + } + } + else { + // move entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -move ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { + return PUSH_NO; + } + // vector along which the entity is pushed + checkMove = move * (1.0f - results.fraction); + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, checkMove ); + // if there is a collisions + if ( trace.fraction < 1.0f ) { + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // FIXME: try to push the blocking entity as well ? + // FIXME: handle sliding along more than one collision plane ? + // FIXME: this code has issues, player pushing box into corner in "maps/mre/aaron/test.map" + +/* + oldOrigin = physics->GetOrigin(); + + // movement still remaining + checkMove *= (1.0f - trace.fraction); + + // project the movement along the collision plane + if ( !checkMove.ProjectAlongPlane( trace.c.normal, 0.1f, 1.001f ) ) { + return PUSH_BLOCKED; + } + checkMove *= 1.001f; + + // move entity from collision point along the collision plane + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, NULL, NULL, checkMove ); + + if ( trace.fraction < 1.0f ) { + physics->SetOrigin( oldOrigin ); + return PUSH_BLOCKED; + } + + checkMove = trace.endpos - oldOrigin; + + // move entity in reverse only colliding with pusher + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, clipModel, NULL, -move ); + + physics->SetOrigin( oldOrigin ); +*/ + if ( trace.fraction < 1.0f ) { + return PUSH_BLOCKED; + } + } + } + + SaveEntityPosition( check ); + + // translate the entity + physics->Translate( checkMove ); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + // set the pusher in the translated position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + return PUSH_OK; +} + +/* +============ +idPush::DiscardEntities +============ +*/ +int idPush::DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ) { + int i, num; + idEntity *check; + + // remove all entities we cannot or should not push from the list + for ( num = i = 0; i < numEntities; i++ ) { + check = entityList[ i ]; + + // if the physics object is not pushable + if ( !check->GetPhysics()->IsPushable() ) { + continue; + } + + // if the entity doesn't clip with this pusher + if ( !( check->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + continue; + } + + // don't push players in noclip mode +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idPlayer::GetClassType() ) && static_cast(check)->noclip ) { +// RAVEN END + continue; + } + + // if we should only push idMoveable entities +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !check->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + // if we shouldn't push entities the clip model rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( pusher->GetPhysics()->IsGroundEntity( check->entityNumber ) ) { + continue; + } + } + + // keep entity in list + entityList[ num++ ] = entityList[i]; + } + + return num; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idVec3 clipMove, clipOrigin, oldOrigin, dir, impulse; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + dir = translation; + dir.Normalize(); + dir.z += 1.0f; + dir *= 10.0f; + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsTranslation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), translation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( pusher, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipMove = results.endpos - clipModel->GetOrigin(); + clipOrigin = results.endpos; + + } + else { + + clipMove = translation; + clipOrigin = newOrigin; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldOrigin = clipModel->GetOrigin(); + + // try to push the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryTranslatePushEntity( pushResults, check, clipModel, flags, clipOrigin, clipMove ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the translated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), oldOrigin, clipModel->GetAxis() ); +// RAVEN END + + // wake up this object + if ( flags & PUSHFL_APPLYIMPULSE ) { + impulse = physics->GetMass() * dir; + } else { + impulse.Zero(); + } + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), impulse ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // if the entity is a moveable item and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idMoveableItem::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idRotation clipRotation; + idMat3 clipAxis, oldAxis; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsRotation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), rotation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Rotation( pusher, results, clipModel->GetOrigin(), rotation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipRotation = rotation * results.fraction; + clipAxis = results.endAxis; + } + else { + + clipRotation = rotation; + clipAxis = newAxis; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldAxis = clipModel->GetAxis(); + + // try to push all the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryRotatePushEntity( pushResults, check, clipModel, flags, clipAxis, clipRotation ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the rotated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), oldAxis ); +// RAVEN END + + // wake up this object + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), vec3_origin ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +#endif /* !NEW_PUSH */ + + +/* +============ +idPush::ClipPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ) { + idVec3 translation; + idRotation rotation; + float mass; + + mass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // translational push + translation = newOrigin - oldOrigin; + + // if the pusher translates + if ( translation != vec3_origin ) { + + mass += ClipTranslationalPush( results, pusher, flags, newOrigin, translation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newOrigin = oldOrigin; + } + + // rotational push + rotation = ( oldAxis.Transpose() * newAxis ).ToRotation(); + rotation.SetOrigin( newOrigin ); + rotation.Normalize180(); + rotation.ReCalculateMatrix(); // recalculate the rotation matrix to avoid accumulating rounding errors + + // if the pusher rotates + if ( rotation.GetAngle() != 0.0f ) { + + // recalculate new axis to avoid floating point rounding problems + newAxis = oldAxis * rotation.ToMat3(); + newAxis.OrthoNormalizeSelf(); + newAxis.FixDenormals(); + newAxis.FixDegeneracies(); + + pusher->GetPhysics()->GetClipModel()->SetPosition( newOrigin, oldAxis ); + + mass += ClipRotationalPush( results, pusher, flags, newAxis, rotation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newAxis = oldAxis; + } + + return mass; +} diff --git a/src/game/physics/Push.h b/src/game/physics/Push.h new file mode 100644 index 0000000..b169bdb --- /dev/null +++ b/src/game/physics/Push.h @@ -0,0 +1,109 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PUSH_H__ +#define __PUSH_H__ + +/* +=============================================================================== + + Allows physics objects to be pushed geometrically. + +=============================================================================== +*/ + +#define PUSHFL_ONLYMOVEABLE 1 // only push moveable entities +#define PUSHFL_NOGROUNDENTITIES 2 // don't push entities the clip model rests upon +#define PUSHFL_CLIP 4 // also clip against all non-moveable entities +#define PUSHFL_CRUSH 8 // kill blocking entities +#define PUSHFL_APPLYIMPULSE 16 // apply impulse to pushed entities + +//#define NEW_PUSH + +class idPush { +public: + // Try to push other entities by moving the given entity. + // If results.fraction < 1.0 the move was blocked by results.c.entityNum + // Returns total mass of all pushed entities. + float ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + + float ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + + float ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ); + + // initialize saving the positions of entities being pushed + void InitSavingPushedEntityPositions( void ); + // move all pushed entities back to their previous position + void RestorePushedEntityPositions( void ); + // returns the number of pushed entities + int GetNumPushedEntities( void ) const { return numPushed; } + // get the ith pushed entity + idEntity * GetPushedEntity( int i ) const { assert( i >= 0 && i < numPushed ); return pushed[i].ent; } + +private: + struct pushed_s { + idEntity * ent; // pushed entity + idAngles deltaViewAngles; // actor delta view angles + } pushed[MAX_GENTITIES]; // pushed entities + int numPushed; // number of pushed entities + + struct pushedGroup_s { + idEntity * ent; + float fraction; + bool groundContact; + bool test; + } pushedGroup[MAX_GENTITIES]; + int pushedGroupSize; + +private: + void SaveEntityPosition( idEntity *ent ); + bool RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ); +#ifdef NEW_PUSH + bool CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ); + void AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ); + bool IsFullyPushed( idEntity *ent ); + bool ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ); + int GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ); + bool ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ); + int GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ); +#else + void ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idRotation &rotation ); + void ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idVec3 &translation ); + int TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + int TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + int DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ); +#endif +}; + +#endif /* !__PUSH_H__ */ diff --git a/src/game/script/ScriptFuncUtility.cpp b/src/game/script/ScriptFuncUtility.cpp new file mode 100644 index 0000000..e13729b --- /dev/null +++ b/src/game/script/ScriptFuncUtility.cpp @@ -0,0 +1,493 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility* sfu ) { + Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility& sfu ) { + Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const char* source ) { + Init( source ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const idCmdArgs& args ) { + Init( args ); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const char* source ) { + Clear(); + + if( !source || !source[0] ) { + return SFU_NOFUNC; + } + + idStr::Split( source, parms, ' ' ); + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const idCmdArgs& args ) { + Clear(); + + //Start at index 1 so we ignore 'call' + for( int ix = 1; ix < args.Argc(); ++ix ) { + InsertString( args.Argv(ix), ix ); + } + + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init() { + assert( parms.Num() ); + + func = FindFunction( GetParm(0) ); + if( func ) { + RemoveIndex( 0 );// remove Function name + returnKey.Clear(); + } else if( parms.Num() >= 2 ) { + returnKey = GetParm( 0 ); + RemoveIndex( 0 );// remove key name + func = FindFunction( GetParm(0) ); + RemoveIndex( 0 );// remove Function name + } else { + gameLocal.Warning( "Unable to find function %s in rvScriptFuncUtility::Init\n", parms[0].c_str() ); + } + + return func != NULL ? SFU_OK : SFU_ERROR; +} + +/* +================ +rvScriptFuncUtility::Clear +================ +*/ +void rvScriptFuncUtility::Clear() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::Save +================ +*/ +void rvScriptFuncUtility::Save( idSaveGame *savefile ) const { + bool validFunc = GetFunc() != NULL; + savefile->WriteBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->WriteString( GetReturnKey() ); + + savefile->WriteString( GetFunc()->Name() ); + + savefile->WriteInt( NumParms() ); + for( int ix = 0; ix < NumParms(); ++ix ) { + savefile->WriteString( func->Name() ); + } +} + +/* +================ +rvScriptFuncUtility::Restore +================ +*/ +void rvScriptFuncUtility::Restore( idRestoreGame *savefile ) { + idStr value; + idStr element; + int numParms = 0; + bool validFunc = false; + + savefile->ReadBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadInt( numParms ); + for( int ix = 0; ix < numParms; ++ix ) { + savefile->ReadString( element ); + value += element; + value += ' '; + } + + value.StripTrailing( ' ' ); + sfuReturnType status = Init(value.c_str()); + if ( status != SFU_OK ) { + assert( 0 ); + } +} + +/* +================ +rvScriptFuncUtility::NumParms +================ +*/ +int rvScriptFuncUtility::NumParms() const { + return (func && func->type) ? func->type->NumParameters() : 0; +} + +/* +================ +rvScriptFuncUtility::ReturnsAVal +================ +*/ +bool rvScriptFuncUtility::ReturnsAVal() const { + return GetReturnType() != &type_void; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +idTypeDef* rvScriptFuncUtility::GetParmType( int index ) const { + return (func && func->type) ? func->type->GetParmType( index ) : NULL; +} + +/* +================ +rvScriptFuncUtility::GetReturnType +================ +*/ +idTypeDef* rvScriptFuncUtility::GetReturnType() const { + return (func && func->type) ? func->type->ReturnType() : &type_void; +} + +/* +================ +rvScriptFuncUtility::SetFunction +================ +*/ +void rvScriptFuncUtility::SetFunction( const function_t* func ) { + this->func = func; +} + +/* +================ +rvScriptFuncUtility::SetParms +================ +*/ +void rvScriptFuncUtility::SetParms( const idList& parms ) { + this->parms = parms; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +const char* rvScriptFuncUtility::GetParm( int index ) const { + return parms[ index ].c_str(); +} + +/* +================ +rvScriptFuncUtility::GetFuncName +================ +*/ +const char* rvScriptFuncUtility::GetFuncName() const { + if( !func ) { + return NULL; + } + + return func->Name(); +} + +/* +================ +rvScriptFuncUtility::InsertInt +================ +*/ +void rvScriptFuncUtility::InsertInt( int parm, int index ) { + InsertString( va("%d", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertFloat +================ +*/ +void rvScriptFuncUtility::InsertFloat( float parm, int index ) { + InsertString( va("%f", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertVec3 +================ +*/ +void rvScriptFuncUtility::InsertVec3( const idVec3& parm, int index ) { + InsertString( parm.ToString(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertEntity +================ +*/ +void rvScriptFuncUtility::InsertEntity( const idEntity* parm, int index ) { + assert( parm ); + + InsertString( parm->GetName(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertString +================ +*/ +void rvScriptFuncUtility::InsertString( const char* parm, int index ) { + assert( parm ); + + parms.Insert( parm, index ); +} + +/* +================ +rvScriptFuncUtility::InsertBool +================ +*/ +void rvScriptFuncUtility::InsertBool( bool parm, int index ) { + InsertInt( (int)parm, index ); +} + +/* +================ +rvScriptFuncUtility::RemoveIndex +================ +*/ +void rvScriptFuncUtility::RemoveIndex( int index ) { + parms.RemoveIndex( index ); +} + +/* +================ +rvScriptFuncUtility::FindFunction +================ +*/ +const function_t* rvScriptFuncUtility::FindFunction( const char* name ) const { + idTypeDef* type = gameLocal.program.FindType( name ); + if( type ) {//Find based on type + return gameLocal.program.FindFunction( name, type ); + } + + //Find based on scope + return gameLocal.program.FindFunction( name ); +} + +/* +================ +rvScriptFuncUtility::CallFunc +================ +*/ +void rvScriptFuncUtility::CallFunc( idDict* returnDict ) const { + idTypeDef* type = NULL; + + if( !Valid() ) { + return; + } + + idThread* thread = new idThread(); + if( !thread ) { + return; + } + + thread->ClearStack(); + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + type->PushOntoStack( thread, GetParm(ix) ); + } + + thread->CallFunction( func, false ); + + if( thread->Execute() && returnDict && ReturnsAVal() ) { + returnDict->Set( GetReturnKey(), GetReturnType()->GetReturnedValAsString(gameLocal.program) ); + } +} + +/* +================ +rvScriptFuncUtility::Valid +================ +*/ +bool rvScriptFuncUtility::Valid() const { +//#if _DEBUG + idTypeDef* type = NULL; + + if( !GetFunc() ) { + return false; + } + + if( GetFunc()->eventdef ) { + gameLocal.Warning( "Function, %s, is an event\n", GetFunc()->Name() ); + return false; + } + + // FIXME: designers call functions with no parms even though parms are pushed on stack + //if( NumParms() != parms.Num() ) { + // gameLocal.Warning( "Number of parms doesn't equal the num required by function %s\n", func->Name() ); + // return false; + //} + + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + if( !type->IsValid( GetParm(ix) ) ) { + gameLocal.Warning( "(Func: %s) Parm '%s' doesn't match expected type '%s'\n", GetFunc()->Name(), GetParm(ix), type->Name() ); + return false; + } + } + + if( returnKey.Length() && !ReturnsAVal() ) { + gameLocal.Warning( "Expecting return val from function %s which doesn't return one\n", func->Name() ); + return false; + } +//#endif + + return true; +} + +/* +================ +rvScriptFuncUtility::Assign +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::Assign( const rvScriptFuncUtility* sfu ) { + assert( sfu ); + func = sfu->func; + parms = sfu->parms; + returnKey = sfu->returnKey; + + return *this; +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility* sfu ) { + return Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility& sfu ) { + return Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::IsEqualTo +================ +*/ +bool rvScriptFuncUtility::IsEqualTo( const rvScriptFuncUtility* sfu ) const { + assert( sfu ); + return GetFunc() == sfu->GetFunc(); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility* sfu ) const { + return IsEqualTo( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility& sfu ) const { + return IsEqualTo( &sfu ); +} diff --git a/src/game/script/ScriptFuncUtility.h b/src/game/script/ScriptFuncUtility.h new file mode 100644 index 0000000..2657c21 --- /dev/null +++ b/src/game/script/ScriptFuncUtility.h @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RV_SCRIPT_FUNC_UTILITY_H +#define __RV_SCRIPT_FUNC_UTILITY_H + +class idThread; + +enum sfuReturnType { + SFU_NOFUNC = -1, + SFU_ERROR = 0, + SFU_OK = 1 +}; + +class rvScriptFuncUtility { +public: + rvScriptFuncUtility(); + explicit rvScriptFuncUtility( const rvScriptFuncUtility* sfu ); + explicit rvScriptFuncUtility( const rvScriptFuncUtility& sfu ); + explicit rvScriptFuncUtility( const char* source ); + explicit rvScriptFuncUtility( const idCmdArgs& args ); + + sfuReturnType Init( const char* source ); + sfuReturnType Init( const idCmdArgs& args ); + void Clear(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idTypeDef* GetParmType( int index ) const; + idTypeDef* GetReturnType() const ; + int NumParms() const; + bool ReturnsAVal() const; + + void SetFunction( const function_t* func ); + void SetParms( const idList& parms ); + void SetReturnKey( const char* key ) { returnKey = key; } + + const char* GetFuncName() const; + const function_t* GetFunc() const { return func; } + const char* GetParm( int index ) const; + const char* GetReturnKey() const { return returnKey.c_str(); } + + void InsertInt( int parm, int index ); + void InsertFloat( float parm, int index ); + void InsertVec3( const idVec3& parm, int index ); + void InsertEntity( const idEntity* parm, int index ); + void InsertString( const char* parm, int index ); + void InsertBool( bool parm, int index ); + void RemoveIndex( int index ); + + const function_t* FindFunction( const char* name ) const; + void CallFunc( idDict* returnDict ) const; + + bool Valid() const; + + rvScriptFuncUtility& Assign( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility& sfu ); + + bool IsEqualTo( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility& sfu ) const; + +private: + sfuReturnType Init(); + +protected: + const function_t* func; + idList parms; + idStr returnKey; +}; + +#endif diff --git a/src/game/script/Script_Compiler.cpp b/src/game/script/Script_Compiler.cpp new file mode 100644 index 0000000..d83f087 --- /dev/null +++ b/src/game/script/Script_Compiler.cpp @@ -0,0 +1,2666 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define FUNCTION_PRIORITY 2 +#define INT_PRIORITY 2 +#define NOT_PRIORITY 5 +#define TILDE_PRIORITY 5 +#define TOP_PRIORITY 7 + +bool idCompiler::punctuationValid[ 256 ]; +char *idCompiler::punctuation[] = { + "+=", "-=", "*=", "/=", "%=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", "::", ";", ",", + "~", "!", "*", "/", "%", "(", ")", "-", "+", + "=", "[", "]", ".", "<", ">" , "&", "|", ":", NULL +}; + +opcode_t idCompiler::opcodes[] = { + { "", "RETURN", -1, false, &def_void, &def_void, &def_void }, + + { "++", "UINC_F", 1, true, &def_float, &def_void, &def_void }, + { "++", "UINCP_F", 1, true, &def_object, &def_field, &def_float }, + { "--", "UDEC_F", 1, true, &def_float, &def_void, &def_void }, + { "--", "UDECP_F", 1, true, &def_object, &def_field, &def_float }, + + { "~", "COMP_F", -1, false, &def_float, &def_void, &def_float }, + + { "*", "MUL_F", 3, false, &def_float, &def_float, &def_float }, + { "*", "MUL_V", 3, false, &def_vector, &def_vector, &def_float }, + { "*", "MUL_FV", 3, false, &def_float, &def_vector, &def_vector }, + { "*", "MUL_VF", 3, false, &def_vector, &def_float, &def_vector }, + + { "/", "DIV", 3, false, &def_float, &def_float, &def_float }, + { "%", "MOD_F", 3, false, &def_float, &def_float, &def_float }, + + { "+", "ADD_F", 4, false, &def_float, &def_float, &def_float }, + { "+", "ADD_V", 4, false, &def_vector, &def_vector, &def_vector }, + { "+", "ADD_S", 4, false, &def_string, &def_string, &def_string }, + { "+", "ADD_FS", 4, false, &def_float, &def_string, &def_string }, + { "+", "ADD_SF", 4, false, &def_string, &def_float, &def_string }, + { "+", "ADD_VS", 4, false, &def_vector, &def_string, &def_string }, + { "+", "ADD_SV", 4, false, &def_string, &def_vector, &def_string }, + + { "-", "SUB_F", 4, false, &def_float, &def_float, &def_float }, + { "-", "SUB_V", 4, false, &def_vector, &def_vector, &def_vector }, + + { "==", "EQ_F", 5, false, &def_float, &def_float, &def_float }, + { "==", "EQ_V", 5, false, &def_vector, &def_vector, &def_float }, + { "==", "EQ_S", 5, false, &def_string, &def_string, &def_float }, + { "==", "EQ_E", 5, false, &def_entity, &def_entity, &def_float }, + { "==", "EQ_EO", 5, false, &def_entity, &def_object, &def_float }, + { "==", "EQ_OE", 5, false, &def_object, &def_entity, &def_float }, + { "==", "EQ_OO", 5, false, &def_object, &def_object, &def_float }, + + { "!=", "NE_F", 5, false, &def_float, &def_float, &def_float }, + { "!=", "NE_V", 5, false, &def_vector, &def_vector, &def_float }, + { "!=", "NE_S", 5, false, &def_string, &def_string, &def_float }, + { "!=", "NE_E", 5, false, &def_entity, &def_entity, &def_float }, + { "!=", "NE_EO", 5, false, &def_entity, &def_object, &def_float }, + { "!=", "NE_OE", 5, false, &def_object, &def_entity, &def_float }, + { "!=", "NE_OO", 5, false, &def_object, &def_object, &def_float }, + + { "<=", "LE", 5, false, &def_float, &def_float, &def_float }, + { ">=", "GE", 5, false, &def_float, &def_float, &def_float }, + { "<", "LT", 5, false, &def_float, &def_float, &def_float }, + { ">", "GT", 5, false, &def_float, &def_float, &def_float }, + + { ".", "INDIRECT_F", 1, false, &def_object, &def_field, &def_float }, + { ".", "INDIRECT_V", 1, false, &def_object, &def_field, &def_vector }, + { ".", "INDIRECT_S", 1, false, &def_object, &def_field, &def_string }, + { ".", "INDIRECT_E", 1, false, &def_object, &def_field, &def_entity }, + { ".", "INDIRECT_BOOL", 1, false, &def_object, &def_field, &def_boolean }, + { ".", "INDIRECT_OBJ", 1, false, &def_object, &def_field, &def_object }, + + { ".", "ADDRESS", 1, false, &def_entity, &def_field, &def_pointer }, + + { ".", "EVENTCALL", 2, false, &def_entity, &def_function, &def_void }, + { ".", "OBJECTCALL", 2, false, &def_object, &def_function, &def_void }, + { ".", "SYSCALL", 2, false, &def_void, &def_function, &def_void }, + + { "=", "STORE_F", 6, true, &def_float, &def_float, &def_float }, + { "=", "STORE_V", 6, true, &def_vector, &def_vector, &def_vector }, + { "=", "STORE_S", 6, true, &def_string, &def_string, &def_string }, + { "=", "STORE_ENT", 6, true, &def_entity, &def_entity, &def_entity }, + { "=", "STORE_BOOL", 6, true, &def_boolean, &def_boolean, &def_boolean }, + { "=", "STORE_OBJENT", 6, true, &def_object, &def_entity, &def_object }, + { "=", "STORE_OBJ", 6, true, &def_object, &def_object, &def_object }, + { "=", "STORE_OBJENT", 6, true, &def_entity, &def_object, &def_object }, + + { "=", "STORE_FTOS", 6, true, &def_string, &def_float, &def_string }, + { "=", "STORE_BTOS", 6, true, &def_string, &def_boolean, &def_string }, + { "=", "STORE_VTOS", 6, true, &def_string, &def_vector, &def_string }, + { "=", "STORE_FTOBOOL", 6, true, &def_boolean, &def_float, &def_boolean }, + { "=", "STORE_BOOLTOF", 6, true, &def_float, &def_boolean, &def_float }, + + { "=", "STOREP_F", 6, true, &def_pointer, &def_float, &def_float }, + { "=", "STOREP_V", 6, true, &def_pointer, &def_vector, &def_vector }, + { "=", "STOREP_S", 6, true, &def_pointer, &def_string, &def_string }, + { "=", "STOREP_ENT", 6, true, &def_pointer, &def_entity, &def_entity }, + { "=", "STOREP_FLD", 6, true, &def_pointer, &def_field, &def_field }, + { "=", "STOREP_BOOL", 6, true, &def_pointer, &def_boolean, &def_boolean }, + { "=", "STOREP_OBJ", 6, true, &def_pointer, &def_object, &def_object }, + { "=", "STOREP_OBJENT", 6, true, &def_pointer, &def_object, &def_object }, + + { "<=>", "STOREP_FTOS", 6, true, &def_pointer, &def_float, &def_string }, + { "<=>", "STOREP_BTOS", 6, true, &def_pointer, &def_boolean, &def_string }, + { "<=>", "STOREP_VTOS", 6, true, &def_pointer, &def_vector, &def_string }, + { "<=>", "STOREP_FTOBOOL", 6, true, &def_pointer, &def_float, &def_boolean }, + { "<=>", "STOREP_BOOLTOF", 6, true, &def_pointer, &def_boolean, &def_float }, + + { "*=", "UMUL_F", 6, true, &def_float, &def_float, &def_void }, + { "*=", "UMUL_V", 6, true, &def_vector, &def_float, &def_void }, + { "/=", "UDIV_F", 6, true, &def_float, &def_float, &def_void }, + { "/=", "UDIV_V", 6, true, &def_vector, &def_float, &def_void }, + { "%=", "UMOD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_V", 6, true, &def_vector, &def_vector, &def_void }, + { "-=", "USUB_F", 6, true, &def_float, &def_float, &def_void }, + { "-=", "USUB_V", 6, true, &def_vector, &def_vector, &def_void }, + { "&=", "UAND_F", 6, true, &def_float, &def_float, &def_void }, + { "|=", "UOR_F", 6, true, &def_float, &def_float, &def_void }, + + { "!", "NOT_BOOL", -1, false, &def_boolean, &def_void, &def_float }, + { "!", "NOT_F", -1, false, &def_float, &def_void, &def_float }, + { "!", "NOT_V", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_S", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_ENT", -1, false, &def_entity, &def_void, &def_float }, + + { "", "NEG_F", -1, false, &def_float, &def_void, &def_float }, + { "", "NEG_V", -1, false, &def_vector, &def_void, &def_vector }, + + { "int", "INT_F", -1, false, &def_float, &def_void, &def_float }, + + { "", "IF", -1, false, &def_float, &def_jumpoffset, &def_void }, + { "", "IFNOT", -1, false, &def_float, &def_jumpoffset, &def_void }, + + // calls returns REG_RETURN + { "", "CALL", -1, false, &def_function, &def_argsize, &def_void }, + { "", "THREAD", -1, false, &def_function, &def_argsize, &def_void }, + { "", "OBJTHREAD", -1, false, &def_function, &def_argsize, &def_void }, + + { "", "PUSH_F", -1, false, &def_float, &def_float, &def_void }, + { "", "PUSH_V", -1, false, &def_vector, &def_vector, &def_void }, + { "", "PUSH_S", -1, false, &def_string, &def_string, &def_void }, + { "", "PUSH_ENT", -1, false, &def_entity, &def_entity, &def_void }, + { "", "PUSH_OBJ", -1, false, &def_object, &def_object, &def_void }, + { "", "PUSH_OBJENT", -1, false, &def_entity, &def_object, &def_void }, + { "", "PUSH_FTOS", -1, false, &def_string, &def_float, &def_void }, + { "", "PUSH_BTOF", -1, false, &def_float, &def_boolean, &def_void }, + { "", "PUSH_FTOB", -1, false, &def_boolean, &def_float, &def_void }, + { "", "PUSH_VTOS", -1, false, &def_string, &def_vector, &def_void }, + { "", "PUSH_BTOS", -1, false, &def_string, &def_boolean, &def_void }, + + { "", "GOTO", -1, false, &def_jumpoffset, &def_void, &def_void }, + + { "&&", "AND", 7, false, &def_float, &def_float, &def_float }, + { "&&", "AND_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "&&", "AND_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "&&", "AND_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + { "||", "OR", 7, false, &def_float, &def_float, &def_float }, + { "||", "OR_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "||", "OR_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "||", "OR_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + + { "&", "BITAND", 3, false, &def_float, &def_float, &def_float }, + { "|", "BITOR", 3, false, &def_float, &def_float, &def_float }, + + { "", "BREAK", -1, false, &def_float, &def_void, &def_void }, + { "", "CONTINUE", -1, false, &def_float, &def_void, &def_void }, + + { NULL } +}; + +/* +================ +idCompiler::idCompiler() +================ +*/ +idCompiler::idCompiler() { + char **ptr; + int id; + + // make sure we have the right # of opcodes in the table + assert( ( sizeof( opcodes ) / sizeof( opcodes[ 0 ] ) ) == ( NUM_OPCODES + 1 ) ); + + eof = true; + parserPtr = &parser; + + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + basetype = NULL; + currentLineNumber = 0; + currentFileNumber = 0; + errorCount = 0; + console = false; + scope = &def_namespace; + + memset( &immediate, 0, sizeof( immediate ) ); + memset( punctuationValid, 0, sizeof( punctuationValid ) ); + for( ptr = punctuation; *ptr != NULL; ptr++ ) { + id = parserPtr->GetPunctuationId( *ptr ); + if ( ( id >= 0 ) && ( id < 256 ) ) { + punctuationValid[ id ] = true; + } + } +} + +/* +============ +idCompiler::Error + +Aborts the current file load +============ +*/ +void idCompiler::Error( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + throw idCompileError( string ); +} + +/* +============ +idCompiler::Warning + +Prints a warning about the current line +============ +*/ +void idCompiler::Warning( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + parserPtr->Warning( "%s", string ); +} + +/* +============ +idCompiler::VirtualFunctionConstant + +Creates a def for an index into a virtual function table +============ +*/ +ID_INLINE idVarDef *idCompiler::VirtualFunctionConstant( idVarDef *func ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = func->scope->TypeDef()->GetFunctionNumber( func->value.functionPtr ); + if ( eval._int < 0 ) { +// RAVEN BEGIN +// bdube: added scope to print + if( !func->scope || !func->scope->initialized ) { + Error( "Function '%s' not found in uninitialized scope. Make sure function is an object method.", func->Name() ); + } else { + Error( "Function '%s' not found in scope '%s'", func->Name(), func->scope->Name() ); + } +// RAVEN END + } + + return GetImmediate( &type_virtualfunction, &eval, "" ); +} + +/* +============ +idCompiler::SizeConstant + +Creates a def for a size constant +============ +*/ +ID_INLINE idVarDef *idCompiler::SizeConstant( int size ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = size; + return GetImmediate( &type_argsize, &eval, "" ); +} + +/* +============ +idCompiler::JumpConstant + +Creates a def for a jump constant +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpConstant( int value ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = value; + return GetImmediate( &type_jumpoffset, &eval, "" ); +} + +/* +============ +idCompiler::JumpDef + +Creates a def for a relative jump from one code location to another +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpDef( int jumpfrom, int jumpto ) { + return JumpConstant( jumpto - jumpfrom ); +} + +/* +============ +idCompiler::JumpTo + +Creates a def for a relative jump from current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpTo( int jumpto ) { + return JumpDef( gameLocal.program.NumStatements(), jumpto ); +} + +/* +============ +idCompiler::JumpFrom + +Creates a def for a relative jump from code location to current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpFrom( int jumpfrom ) { + return JumpDef( jumpfrom, gameLocal.program.NumStatements() ); +} + +/* +============ +idCompiler::Divide +============ +*/ +ID_INLINE float idCompiler::Divide( float numerator, float denominator ) { + if ( denominator == 0 ) { + Error( "Divide by zero" ); + return 0; + } + + return numerator / denominator; +} + +/* +============ +idCompiler::FindImmediate + +tries to find an existing immediate with the same value +============ +*/ +idVarDef *idCompiler::FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const { + idVarDef *def; + etype_t etype; + + etype = type->Type(); + + // check for a constant with the same value + for( def = gameLocal.program.GetDefList( "" ); def != NULL; def = def->Next() ) { + if ( def->TypeDef() != type ) { + continue; + } + + switch( etype ) { + case ev_field : + if ( *def->value.intPtr == eval->_int ) { + return def; + } + break; + + case ev_argsize : + if ( def->value.argSize == eval->_int ) { + return def; + } + break; + + case ev_jumpoffset : + if ( def->value.jumpOffset == eval->_int ) { + return def; + } + break; + + case ev_entity : + if ( *def->value.intPtr == eval->entity ) { + return def; + } + break; + + case ev_string : + if ( idStr::Cmp( def->value.stringPtr, string ) == 0 ) { + return def; + } + break; + + case ev_float : + if ( *def->value.floatPtr == eval->_float ) { + return def; + } + break; + + case ev_virtualfunction : + if ( def->value.virtualFunction == eval->_int ) { + return def; + } + break; + + + case ev_vector : + if ( ( def->value.vectorPtr->x == eval->vector[ 0 ] ) && + ( def->value.vectorPtr->y == eval->vector[ 1 ] ) && + ( def->value.vectorPtr->z == eval->vector[ 2 ] ) ) { + return def; + } + break; + + default : + Error( "weird immediate type" ); + break; + } + } + + return NULL; +} + +/* +============ +idCompiler::GetImmediate + +returns an existing immediate with the same value, or allocates a new one +============ +*/ +idVarDef *idCompiler::GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ) { + idVarDef *def; + + def = FindImmediate( type, eval, string ); + if ( def ) { + def->numUsers++; + } else { + // allocate a new def + def = gameLocal.program.AllocDef( type, "", &def_namespace, true ); + if ( type->Type() == ev_string ) { + def->SetString( string, true ); + } else { + def->SetValue( *eval, true ); + } + } + + return def; +} + +/* +============ +idCompiler::OptimizeOpcode + +try to optimize when the operator works on constants only +============ +*/ +idVarDef *idCompiler::OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + eval_t c; + idTypeDef *type; + + if ( var_a && var_a->initialized != idVarDef::initializedConstant ) { + return NULL; + } + if ( var_b && var_b->initialized != idVarDef::initializedConstant ) { + return NULL; + } + + idVec3 &vec_c = *reinterpret_cast( &c.vector[ 0 ] ); + + memset( &c, 0, sizeof( c ) ); + switch( op - opcodes ) { + case OP_ADD_F: c._float = *var_a->value.floatPtr + *var_b->value.floatPtr; type = &type_float; break; + case OP_ADD_V: vec_c = *var_a->value.vectorPtr + *var_b->value.vectorPtr; type = &type_vector; break; + case OP_SUB_F: c._float = *var_a->value.floatPtr - *var_b->value.floatPtr; type = &type_float; break; + case OP_SUB_V: vec_c = *var_a->value.vectorPtr - *var_b->value.vectorPtr; type = &type_vector; break; + case OP_MUL_F: c._float = *var_a->value.floatPtr * *var_b->value.floatPtr; type = &type_float; break; + case OP_MUL_V: c._float = *var_a->value.vectorPtr * *var_b->value.vectorPtr; type = &type_float; break; + case OP_MUL_FV: vec_c = *var_b->value.vectorPtr * *var_a->value.floatPtr; type = &type_vector; break; + case OP_MUL_VF: vec_c = *var_a->value.vectorPtr * *var_b->value.floatPtr; type = &type_vector; break; + case OP_DIV_F: c._float = Divide( *var_a->value.floatPtr, *var_b->value.floatPtr ); type = &type_float; break; + case OP_MOD_F: c._float = (int)*var_a->value.floatPtr % (int)*var_b->value.floatPtr; type = &type_float; break; + case OP_BITAND: c._float = ( int )*var_a->value.floatPtr & ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_BITOR: c._float = ( int )*var_a->value.floatPtr | ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_GE: c._float = *var_a->value.floatPtr >= *var_b->value.floatPtr; type = &type_float; break; + case OP_LE: c._float = *var_a->value.floatPtr <= *var_b->value.floatPtr; type = &type_float; break; + case OP_GT: c._float = *var_a->value.floatPtr > *var_b->value.floatPtr; type = &type_float; break; + case OP_LT: c._float = *var_a->value.floatPtr < *var_b->value.floatPtr; type = &type_float; break; + case OP_AND: c._float = *var_a->value.floatPtr && *var_b->value.floatPtr; type = &type_float; break; + case OP_OR: c._float = *var_a->value.floatPtr || *var_b->value.floatPtr; type = &type_float; break; + case OP_NOT_BOOL: c._int = !*var_a->value.intPtr; type = &type_boolean; break; + case OP_NOT_F: c._float = !*var_a->value.floatPtr; type = &type_float; break; + case OP_NOT_V: c._float = !var_a->value.vectorPtr->x && !var_a->value.vectorPtr->y && !var_a->value.vectorPtr->z; type = &type_float; break; + case OP_NEG_F: c._float = -*var_a->value.floatPtr; type = &type_float; break; + case OP_NEG_V: vec_c = -*var_a->value.vectorPtr; type = &type_vector; break; + case OP_INT_F: c._float = ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_EQ_F: c._float = ( *var_a->value.floatPtr == *var_b->value.floatPtr ); type = &type_float; break; + case OP_EQ_V: c._float = var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_EQ_E: c._float = ( *var_a->value.intPtr == *var_b->value.intPtr ); type = &type_float; break; + case OP_NE_F: c._float = ( *var_a->value.floatPtr != *var_b->value.floatPtr ); type = &type_float; break; + case OP_NE_V: c._float = !var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_NE_E: c._float = ( *var_a->value.intPtr != *var_b->value.intPtr ); type = &type_float; break; + case OP_UADD_F: c._float = *var_b->value.floatPtr + *var_a->value.floatPtr; type = &type_float; break; + case OP_USUB_F: c._float = *var_b->value.floatPtr - *var_a->value.floatPtr; type = &type_float; break; + case OP_UMUL_F: c._float = *var_b->value.floatPtr * *var_a->value.floatPtr; type = &type_float; break; + case OP_UDIV_F: c._float = Divide( *var_b->value.floatPtr, *var_a->value.floatPtr ); type = &type_float; break; + case OP_UMOD_F: c._float = ( int ) *var_b->value.floatPtr % ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UOR_F: c._float = ( int )*var_b->value.floatPtr | ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UAND_F: c._float = ( int )*var_b->value.floatPtr & ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UINC_F: c._float = *var_a->value.floatPtr + 1; type = &type_float; break; + case OP_UDEC_F: c._float = *var_a->value.floatPtr - 1; type = &type_float; break; + case OP_COMP_F: c._float = ( float )~( int )*var_a->value.floatPtr; type = &type_float; break; + default: type = NULL; break; + } + + if ( !type ) { + return NULL; + } + + if ( var_a ) { + var_a->numUsers--; + if ( var_a->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_a, NULL ); + } + } + if ( var_b ) { + var_b->numUsers--; + if ( var_b->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_b, NULL ); + } + } + + return GetImmediate( type, &c, "" ); +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +idVarDef *idCompiler::EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + statement_t *statement; + idVarDef *var_c; + + var_c = OptimizeOpcode( op, var_a, var_b ); + if ( var_c ) { + return var_c; + } + + if ( var_a && !idStr::Cmp( var_a->Name(), RESULT_STRING ) ) { + var_a->numUsers++; + } + if ( var_b && !idStr::Cmp( var_b->Name(), RESULT_STRING ) ) { + var_b->numUsers++; + } + + statement = gameLocal.program.AllocStatement(); + statement->linenumber = currentLineNumber; + statement->file = currentFileNumber; + + if ( ( op->type_c == &def_void ) || op->rightAssociative ) { + // ifs, gotos, and assignments don't need vars allocated + var_c = NULL; + } else { + // allocate result space + // try to reuse result defs as much as possible + var_c = gameLocal.program.FindFreeResultDef( op->type_c->TypeDef(), RESULT_STRING, scope, var_a, var_b ); + // set user count back to 1, a result def needs to be used twice before it can be reused + var_c->numUsers = 1; + } + + statement->op = op - opcodes; + statement->a = var_a; + statement->b = var_b; + statement->c = var_c; + + if ( op->rightAssociative ) { + return var_a; + } + + return var_c; +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +ID_INLINE idVarDef *idCompiler::EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ) { + return EmitOpcode( &opcodes[ op ], var_a, var_b ); +} + +/* +============ +idCompiler::EmitPush + +Emits an opcode to push the variable onto the stack. +============ +*/ +bool idCompiler::EmitPush( idVarDef *expression, const idTypeDef *funcArg ) { + opcode_t *op; + opcode_t *out; + + out = NULL; + for( op = &opcodes[ OP_PUSH_F ]; op->name && !idStr::Cmp( op->name, "" ); op++ ) { + if ( ( funcArg->Type() == op->type_a->Type() ) && ( expression->Type() == op->type_b->Type() ) ) { + out = op; + break; + } + } + + if ( !out ) { + if ( ( expression->TypeDef() != funcArg ) && !expression->TypeDef()->Inherits( funcArg ) ) { + return false; + } + + out = &opcodes[ OP_PUSH_ENT ]; + } + + EmitOpcode( out, expression, 0 ); + + return true; +} + +/* +============== +idCompiler::NextToken + +Sets token, immediateType, and possibly immediate +============== +*/ +void idCompiler::NextToken( void ) { + int i; + + // reset our type + immediateType = NULL; + memset( &immediate, 0, sizeof( immediate ) ); + + // Save the token's line number and filename since when we emit opcodes the current + // token is always the next one to be read + currentLineNumber = token.line; + currentFileNumber = gameLocal.program.GetFilenum( parserPtr->GetFileName() ); + + if ( !parserPtr->ReadToken( &token ) ) { + eof = true; + return; + } + + if ( currentFileNumber != gameLocal.program.GetFilenum( parserPtr->GetFileName() ) ) { + if ( ( braceDepth > 0 ) && ( token != "}" ) ) { + // missing a closing brace. try to give as much info as possible. + if ( scope->Type() == ev_function ) { + Error( "Unexpected end of file inside function '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_object ) { + Error( "Unexpected end of file inside object '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_namespace ) { + Error( "Unexpected end of file inside namespace '%s'. Missing closing braces.", scope->Name() ); + } else { + Error( "Unexpected end of file inside braced section" ); + } + } + } + + switch( token.type ) { + case TT_STRING: + // handle quoted strings as a unit + immediateType = &type_string; + return; + + case TT_LITERAL: { + // handle quoted vectors as a unit + immediateType = &type_vector; + idLexer lex( token, token.Length(), parserPtr->GetFileName(), LEXFL_NOERRORS ); + idToken token2; + for( i = 0; i < 3; i++ ) { + if ( !lex.ReadToken( &token2 ) ) { + Error( "Couldn't read vector. '%s' is not in the form of 'x y z'", token.c_str() ); + } + if ( token2.type == TT_PUNCTUATION && token2 == "-" ) { + if ( !lex.CheckTokenType( TT_NUMBER, 0, &token2 ) ) { + Error( "expected a number following '-' but found '%s' in vector '%s'", token2.c_str(), token.c_str() ); + } + immediate.vector[ i ] = -token2.GetFloatValue(); + } else if ( token2.type == TT_NUMBER ) { + immediate.vector[ i ] = token2.GetFloatValue(); + } else { + Error( "vector '%s' is not in the form of 'x y z'. expected float value, found '%s'", token.c_str(), token2.c_str() ); + } + } + return; + } + + case TT_NUMBER: + immediateType = &type_float; + immediate._float = token.GetFloatValue(); + return; + + case TT_PUNCTUATION: + // entity names + if ( token == "$" ) { + immediateType = &type_entity; + parserPtr->ReadToken( &token ); + return; + } + + if ( token == "{" ) { + braceDepth++; + return; + } + + if ( token == "}" ) { + braceDepth--; + return; + } + + if ( punctuationValid[ token.subtype ] ) { + return; + } + + Error( "Unknown punctuation '%s'", token.c_str() ); + break; + + case TT_NAME: + return; + + default: + Error( "Unknown token '%s'", token.c_str() ); + } +} + +/* +============= +idCompiler::ExpectToken + +Issues an Error if the current token isn't equal to string +Gets the next token +============= +*/ +void idCompiler::ExpectToken( const char *string ) { + if ( token != string ) { + Error( "expected '%s', found '%s'", string, token.c_str() ); + } + + NextToken(); +} + +/* +============= +idCompiler::CheckToken + +Returns true and gets the next token if the current token equals string +Returns false and does nothing otherwise +============= +*/ +bool idCompiler::CheckToken( const char *string ) { + if ( token != string ) { + return false; + } + + NextToken(); + + return true; +} + +/* +============ +idCompiler::ParseName + +Checks to see if the current token is a valid name +============ +*/ +void idCompiler::ParseName( idStr &name ) { + if ( token.type != TT_NAME ) { + Error( "'%s' is not a name", token.c_str() ); + } + + name = token; + NextToken(); +} + +/* +============ +idCompiler::SkipOutOfFunction + +For error recovery, pops out of nested braces +============ +*/ +void idCompiler::SkipOutOfFunction( void ) { + while( braceDepth ) { + parserPtr->SkipBracedSection( false ); + braceDepth--; + } + NextToken(); +} + +/* +============ +idCompiler::SkipToSemicolon + +For error recovery +============ +*/ +void idCompiler::SkipToSemicolon( void ) { + do { + if ( CheckToken( ";" ) ) { + return; + } + + NextToken(); + } while( !eof ); +} + +/* +============ +idCompiler::CheckType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::CheckType( void ) { + idTypeDef *type; + + if ( token == "float" ) { + type = &type_float; + } else if ( token == "vector" ) { + type = &type_vector; + } else if ( token == "entity" ) { + type = &type_entity; + } else if ( token == "string" ) { + type = &type_string; + } else if ( token == "void" ) { + type = &type_void; + } else if ( token == "object" ) { + type = &type_object; + } else if ( token == "boolean" ) { + type = &type_boolean; + } else if ( token == "namespace" ) { + type = &type_namespace; + } else if ( token == "scriptEvent" ) { + type = &type_scriptevent; + } else { + type = gameLocal.program.FindType( token.c_str() ); + if ( type && !type->Inherits( &type_object ) ) { + type = NULL; + } + } + + return type; +} + +/* +============ +idCompiler::ParseType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::ParseType( void ) { + idTypeDef *type; + + type = CheckType(); + if ( !type ) { + Error( "\"%s\" is not a type", token.c_str() ); + } + + if ( ( type == &type_scriptevent ) && ( scope != &def_namespace ) ) { + Error( "scriptEvents can only defined in the global namespace" ); + } + + if ( ( type == &type_namespace ) && ( scope->Type() != ev_namespace ) ) { + Error( "A namespace may only be defined globally, or within another namespace" ); + } + + NextToken(); + + return type; +} + +/* +============ +idCompiler::ParseImmediate + +Looks for a preexisting constant +============ +*/ +idVarDef *idCompiler::ParseImmediate( void ) { + idVarDef *def; + + def = GetImmediate( immediateType, &immediate, token.c_str() ); + NextToken(); + + return def; +} + +/* +============ +idCompiler::EmitFunctionParms +============ +*/ +idVarDef *idCompiler::EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ) { + idVarDef *e; + const idTypeDef *type; + const idTypeDef *funcArg; + idVarDef *returnDef; + idTypeDef *returnType; + int arg; + int size; + int resultOp; + + type = func->TypeDef(); + if ( func->Type() != ev_function ) { + Error( "'%s' is not a function", func->Name() ); + } + + // copy the parameters to the global parameter variables + arg = startarg; + size = startsize; + if ( !CheckToken( ")" ) ) { + do { + if ( arg >= type->NumParameters() ) { + Error( "too many parameters" ); + } + + e = GetExpression( TOP_PRIORITY ); + + funcArg = type->GetParmType( arg ); + if ( !EmitPush( e, funcArg ) ) { + Error( "type mismatch on parm %i of call to '%s'", arg + 1, func->Name() ); + } + + if ( funcArg->Type() == ev_object ) { + size += type_object.Size(); + } else { + size += funcArg->Size(); + } + + arg++; + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + if ( arg < type->NumParameters() ) { + Error( "too few parameters for function '%s'", func->Name() ); + } + + if ( op == OP_CALL ) { + EmitOpcode( op, func, 0 ); + } else if ( ( op == OP_OBJECTCALL ) || ( op == OP_OBJTHREAD ) ) { + EmitOpcode( op, object, VirtualFunctionConstant( func ) ); + + // need arg size seperate since script object may be NULL + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = SizeConstant( func->value.functionPtr->parmTotal ); + } else { + EmitOpcode( op, func, SizeConstant( size ) ); + } + + // we need to copy off the result into a temporary result location, so figure out the opcode + returnType = type->ReturnType(); + if ( returnType->Type() == ev_string ) { + resultOp = OP_STORE_S; + returnDef = gameLocal.program.returnStringDef; + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + returnDef = gameLocal.program.returnDef; + + switch( returnType->Type() ) { + case ev_void : + resultOp = OP_STORE_F; + break; + + case ev_boolean : + resultOp = OP_STORE_BOOL; + break; + + case ev_float : + resultOp = OP_STORE_F; + break; + + case ev_vector : + resultOp = OP_STORE_V; + break; + + case ev_entity : + resultOp = OP_STORE_ENT; + break; + + case ev_object : + resultOp = OP_STORE_OBJ; + break; + + default : + Error( "Invalid return type for function '%s'", func->Name() ); + // shut up compiler + resultOp = OP_STORE_OBJ; + break; + } + } + + if ( returnType->Type() == ev_void ) { + // don't need result space since there's no result, so just return the normal result def. + return returnDef; + } + + // allocate result space + // try to reuse result defs as much as possible + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + idVarDef *resultDef = gameLocal.program.FindFreeResultDef( returnType, RESULT_STRING, scope, statement.a, statement.b ); + // set user count back to 0, a result def needs to be used twice before it can be reused + resultDef->numUsers = 0; + + EmitOpcode( resultOp, returnDef, resultDef ); + + return resultDef; +} + +/* +============ +idCompiler::ParseFunctionCall +============ +*/ +idVarDef *idCompiler::ParseFunctionCall( idVarDef *funcDef ) { + assert( funcDef ); + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( funcDef->initialized == idVarDef::uninitialized ) { + Error( "Function '%s' has not been defined yet", funcDef->GlobalName() ); + } + + assert( funcDef->value.functionPtr ); + if ( callthread ) { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + Error( "Built-in functions cannot be called as threads" ); + } + callthread = false; + return EmitFunctionParms( OP_THREAD, funcDef, 0, 0, NULL ); + } else { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + if ( !thisdef ) { + Error( "No 'self' within scope" ); + } + + return ParseEventCall( thisdef, funcDef ); + } else { + Error( "Built-in functions cannot be called without an object" ); + } + } + + return EmitFunctionParms( OP_CALL, funcDef, 0, 0, NULL ); + } +} + +/* +============ +idCompiler::ParseObjectCall +============ +*/ +idVarDef *idCompiler::ParseObjectCall( idVarDef *object, idVarDef *func ) { + EmitPush( object, object->TypeDef() ); + if ( callthread ) { + callthread = false; + return EmitFunctionParms( OP_OBJTHREAD, func, 1, type_object.Size(), object ); + } else { + return EmitFunctionParms( OP_OBJECTCALL, func, 1, 0, object ); + } +} + +/* +============ +idCompiler::ParseEventCall +============ +*/ +idVarDef *idCompiler::ParseEventCall( idVarDef *object, idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + + if ( object->Type() == ev_object ) { + EmitPush( object, &type_entity ); + } else { + EmitPush( object, object->TypeDef() ); + } + + return EmitFunctionParms( OP_EVENTCALL, funcDef, 0, type_object.Size(), NULL ); +} + +/* +============ +idCompiler::ParseSysObjectCall +============ +*/ +idVarDef *idCompiler::ParseSysObjectCall( idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !idThread::GetClassType().RespondsTo( *funcDef->value.functionPtr->eventdef ) ) { +// RAVEN END + Error( "\"%s\" is not callable as a 'sys' function", funcDef->Name() ); + } + + return EmitFunctionParms( OP_SYSCALL, funcDef, 0, 0, NULL ); +} + +/* +============ +idCompiler::LookupDef +============ +*/ +idVarDef *idCompiler::LookupDef( const char *name, const idVarDef *baseobj ) { + idVarDef *def; + idVarDef *field; + etype_t type_b; + etype_t type_c; + opcode_t *op; + + // check if we're accessing a field + if ( baseobj && ( baseobj->Type() == ev_object ) ) { + const idVarDef *tdef; + + def = NULL; + for( tdef = baseobj; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = gameLocal.program.GetDef( NULL, name, tdef ); + if ( def ) { + break; + } + } + } else { + // first look through the defs in our scope + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + // if we're in a member function, check types local to the object + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + + field = LookupDef( name, scope->scope->TypeDef()->def ); + if ( !field ) { + Error( "Unknown value \"%s\"", name ); + } + + // type check + type_b = field->Type(); + if ( field->Type() == ev_function ) { + type_c = field->TypeDef()->ReturnType()->Type(); + } else { + type_c = field->TypeDef()->FieldType()->Type(); // field access gets type from field + if ( CheckToken( "++" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for ++" ); + } + def = EmitOpcode( OP_UINCP_F, thisdef, field ); + return def; + } else if ( CheckToken( "--" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for --" ); + } + def = EmitOpcode( OP_UDECP_F, thisdef, field ); + return def; + } + } + + op = &opcodes[ OP_INDIRECT_F ]; + while( ( op->type_a->Type() != ev_object ) + || ( type_b != op->type_b->Type() ) || ( type_c != op->type_c->Type() ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && ( op->type_a->Type() == ev_object ) && ( op->type_c->Type() == ev_void ) && + ( type_c != op->type_c->Type() ) ) { + // catches object calls that return a value + break; + } + op++; + if ( !op->name || idStr::Cmp( op->name, "." ) ) { + Error( "no valid opcode to access type '%s'", field->TypeDef()->SuperClass()->Name() ); + } + } + + if ( ( op - opcodes ) == OP_OBJECTCALL ) { + ExpectToken( "(" ); + def = ParseObjectCall( thisdef, field ); + } else { + // emit the conversion opcode + def = EmitOpcode( op, thisdef, field ); + + // field access gets type from field + def->SetTypeDef( field->TypeDef()->FieldType() ); + } + } + } + } + + return def; +} + +/* +============ +idCompiler::ParseValue + +Returns the def for the current token +============ +*/ +idVarDef *idCompiler::ParseValue( void ) { + idVarDef *def; + idVarDef *namespaceDef; + idStr name; + + if ( immediateType == &type_entity ) { + // if an immediate entity ($-prefaced name) then create or lookup a def for it. + // when entities are spawned, they'll lookup the def and point it to them. + def = gameLocal.program.GetDef( &type_entity, "$" + token, &def_namespace ); + if ( !def ) { + def = gameLocal.program.AllocDef( &type_entity, "$" + token, &def_namespace, true ); + } + NextToken(); + return def; + } else if ( immediateType ) { + // if the token is an immediate, allocate a constant for it + return ParseImmediate(); + } + + ParseName( name ); + def = LookupDef( name, basetype ); + if ( !def ) { + if ( basetype ) { + Error( "%s is not a member of %s", name.c_str(), basetype->TypeDef()->Name() ); + } else { + Error( "Unknown value \"%s\"", name.c_str() ); + } + // if namespace, then look up the variable in that namespace + } else if ( def->Type() == ev_namespace ) { + while( def->Type() == ev_namespace ) { + ExpectToken( "::" ); + ParseName( name ); + namespaceDef = def; + def = gameLocal.program.GetDef( NULL, name, namespaceDef ); + if ( !def ) { + Error( "Unknown value \"%s::%s\"", namespaceDef->GlobalName(), name.c_str() ); + } + } + //def = LookupDef( name, basetype ); + } + + return def; +} + +/* +============ +idCompiler::GetTerm +============ +*/ +idVarDef *idCompiler::GetTerm( void ) { + idVarDef *e; + int op; + + if ( !immediateType && CheckToken( "~" ) ) { + e = GetExpression( TILDE_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_COMP_F; + break; + + default : + Error( "type mismatch for ~" ); + + // shut up compiler + op = OP_COMP_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + if ( !immediateType && CheckToken( "!" ) ) { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_boolean : + op = OP_NOT_BOOL; + break; + + case ev_float : + op = OP_NOT_F; + break; + + case ev_string : + op = OP_NOT_S; + break; + + case ev_vector : + op = OP_NOT_V; + break; + + case ev_entity : + op = OP_NOT_ENT; + break; + + case ev_function : + Error( "Invalid type for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + + case ev_object : + op = OP_NOT_ENT; + break; + + default : + Error( "type mismatch for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + // check for negation operator + if ( !immediateType && CheckToken( "-" ) ) { + // constants are directly negated without an instruction + if ( immediateType == &type_float ) { + immediate._float = -immediate._float; + return ParseImmediate(); + } else if ( immediateType == &type_vector ) { + immediate.vector[0] = -immediate.vector[0]; + immediate.vector[1] = -immediate.vector[1]; + immediate.vector[2] = -immediate.vector[2]; + return ParseImmediate(); + } else { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_NEG_F; + break; + + case ev_vector : + op = OP_NEG_V; + break; + default : + Error( "type mismatch for -" ); + + // shut up compiler + op = OP_NEG_F; + break; + } + return EmitOpcode( &opcodes[ op ], e, 0 ); + } + } + + if ( CheckToken( "int" ) ) { + ExpectToken( "(" ); + + e = GetExpression( INT_PRIORITY ); + if ( e->Type() != ev_float ) { + Error( "type mismatch for int()" ); + } + + ExpectToken( ")" ); + + return EmitOpcode( OP_INT_F, e, 0 ); + } + + if ( CheckToken( "thread" ) ) { + callthread = true; + e = GetExpression( FUNCTION_PRIORITY ); + + if ( callthread ) { + Error( "Invalid thread call" ); + } + + // threads return the thread number + gameLocal.program.returnDef->SetTypeDef( &type_float ); + return gameLocal.program.returnDef; + } + + if ( !immediateType && CheckToken( "(" ) ) { + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + return e; + } + + return ParseValue(); +} + +/* +============== +idCompiler::TypeMatches +============== +*/ +bool idCompiler::TypeMatches( etype_t type1, etype_t type2 ) const { + if ( type1 == type2 ) { + return true; + } + + //if ( ( type1 == ev_entity ) && ( type2 == ev_object ) ) { + // return true; + //} + + //if ( ( type2 == ev_entity ) && ( type1 == ev_object ) ) { + // return true; + //} + + return false; +} + +/* +============== +idCompiler::GetExpression +============== +*/ +idVarDef *idCompiler::GetExpression( int priority ) { + opcode_t *op; + opcode_t *oldop; + idVarDef *e; + idVarDef *e2; + const idVarDef *oldtype; + etype_t type_a; + etype_t type_b; + etype_t type_c; + + if ( priority == 0 ) { + return GetTerm(); + } + + e = GetExpression( priority - 1 ); + if ( token == ";" ) { + // save us from searching through the opcodes unneccesarily + return e; + } + + while( 1 ) { + if ( ( priority == FUNCTION_PRIORITY ) && CheckToken( "(" ) ) { + return ParseFunctionCall( e ); + } + + // has to be a punctuation + if ( immediateType ) { + break; + } + + for( op = opcodes; op->name; op++ ) { + if ( ( op->priority == priority ) && CheckToken( op->name ) ) { + break; + } + } + + if ( !op->name ) { + // next token isn't at this priority level + break; + } + + // unary operators act only on the left operand + if ( op->type_b == &def_void ) { + e = EmitOpcode( op, e, 0 ); + return e; + } + + // preserve our base type + oldtype = basetype; + + // field access needs scope from object + if ( ( op->name[ 0 ] == '.' ) && e->TypeDef()->Inherits( &type_object ) ) { + // save off what type this field is part of + basetype = e->TypeDef()->def; + } + + if ( op->rightAssociative ) { + // if last statement is an indirect, change it to an address of + if ( gameLocal.program.NumStatements() > 0 ) { + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + if ( ( statement.op >= OP_INDIRECT_F ) && ( statement.op < OP_ADDRESS ) ) { + statement.op = OP_ADDRESS; + type_pointer.SetPointerType( e->TypeDef() ); + e->SetTypeDef( &type_pointer ); + } + } + + e2 = GetExpression( priority ); + } else { + e2 = GetExpression( priority - 1 ); + } + + // restore type + basetype = oldtype; + + // type check + type_a = e->Type(); + type_b = e2->Type(); + + // field access gets type from field + if ( op->name[ 0 ] == '.' ) { + if ( ( e2->Type() == ev_function ) && e2->TypeDef()->ReturnType() ) { + type_c = e2->TypeDef()->ReturnType()->Type(); + } else if ( e2->TypeDef()->FieldType() ) { + type_c = e2->TypeDef()->FieldType()->Type(); + } else { + // not a field + type_c = ev_error; + } + } else { + type_c = ev_void; + } + + oldop = op; + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) || + ( ( type_c != ev_void ) && !TypeMatches( type_c, op->type_c->Type() ) ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && TypeMatches( type_a, op->type_a->Type() ) && TypeMatches( type_b, op->type_b->Type() ) ) { + break; + } + + op++; + if ( !op->name || idStr::Cmp( op->name, oldop->name ) ) { + Error( "type mismatch for '%s'", oldop->name ); + } + } + + switch( op - opcodes ) { + case OP_SYSCALL : + ExpectToken( "(" ); + e = ParseSysObjectCall( e2 ); + break; + + case OP_OBJECTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + case OP_EVENTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + default: + if ( callthread ) { + Error( "Expecting function call after 'thread'" ); + } + + if ( ( type_a == ev_pointer ) && ( type_b != e->TypeDef()->PointerType()->Type() ) ) { + // FIXME: need to make a general case for this + if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_boolean ) ) { + // copy from float to boolean pointer + op = &opcodes[ OP_STOREP_FTOBOOL ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_float ) ) { + // copy from boolean to float pointer + op = &opcodes[ OP_STOREP_BOOLTOF ]; + } else if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from float to string pointer + op = &opcodes[ OP_STOREP_FTOS ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from boolean to string pointer + op = &opcodes[ OP_STOREP_BTOS ]; + } else if ( ( op - opcodes == OP_STOREP_V ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from vector to string pointer + op = &opcodes[ OP_STOREP_VTOS ]; + } else if ( ( op - opcodes == OP_STOREP_ENT ) && ( e->TypeDef()->PointerType()->Type() == ev_object ) ) { + // store an entity into an object pointer + op = &opcodes[ OP_STOREP_OBJENT ]; + } else { + Error( "type mismatch for '%s'", op->name ); + } + } + + if ( op->rightAssociative ) { + e = EmitOpcode( op, e2, e ); + } else { + e = EmitOpcode( op, e, e2 ); + } + + if ( op - opcodes == OP_STOREP_OBJENT ) { + // statement.b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in statement.c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = type_pointer.PointerType()->def; + } + + // field access gets type from field + if ( type_c != ev_void ) { + e->SetTypeDef( e2->TypeDef()->FieldType() ); + } + break; + } + } + + return e; +} + +/* +================ +idCompiler::PatchLoop +================ +*/ +void idCompiler::PatchLoop( int start, int continuePos ) { + int i; + statement_t *pos; + + pos = &gameLocal.program.GetStatement( start ); + for( i = start; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_BREAK ) { + pos->op = OP_GOTO; + pos->a = JumpFrom( i ); + } else if ( pos->op == OP_CONTINUE ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, continuePos ); + } + } +} + +/* +================ +idCompiler::ParseReturnStatement +================ +*/ +void idCompiler::ParseReturnStatement( void ) { + idVarDef *e; + etype_t type_a; + etype_t type_b; + opcode_t *op; + + if ( CheckToken( ";" ) ) { + if ( scope->TypeDef()->ReturnType()->Type() != ev_void ) { + Error( "expecting return value" ); + } + + EmitOpcode( OP_RETURN, 0, 0 ); + return; + } + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + type_a = e->Type(); + type_b = scope->TypeDef()->ReturnType()->Type(); + + if ( TypeMatches( type_a, type_b ) ) { + EmitOpcode( OP_RETURN, e, 0 ); + return; + } + + for( op = opcodes; op->name; op++ ) { + if ( !idStr::Cmp( op->name, "=" ) ) { + break; + } + } + + assert( op->name ); + + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) ) { + op++; + if ( !op->name || idStr::Cmp( op->name, "=" ) ) { + Error( "type mismatch for return value" ); + } + } + + idTypeDef *returnType = scope->TypeDef()->ReturnType(); + if ( returnType->Type() == ev_string ) { + EmitOpcode( op, e, gameLocal.program.returnStringDef ); + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + EmitOpcode( op, e, gameLocal.program.returnDef ); + } + EmitOpcode( OP_RETURN, 0, 0 ); +} + +/* +================ +idCompiler::ParseWhileStatement +================ +*/ +void idCompiler::ParseWhileStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + loopDepth++; + + ExpectToken( "(" ); + + patch2 = gameLocal.program.NumStatements(); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + if ( ( e->initialized == idVarDef::initializedConstant ) && ( *e->value.intPtr != 0 ) ) { + //FIXME: we can completely skip generation of this code in the opposite case + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + } else { + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } + + // fixup breaks and continues + PatchLoop( patch2, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseForStatement + +Form of for statement with a counter: + + a = 0; +start: << patch4 + if ( !( a < 10 ) ) { + goto end; << patch1 + } else { + goto process; << patch3 + } + +increment: << patch2 + a = a + 1; + goto start; << goto patch4 + +process: + statements; + goto increment; << goto patch2 + +end: + +Form of for statement without a counter: + + a = 0; +start: << patch2 + if ( !( a < 10 ) ) { + goto end; << patch1 + } + +process: + statements; + goto start; << goto patch2 + +end: +================ +*/ +void idCompiler::ParseForStatement( void ) { + idVarDef *e; + int start; + int patch1; + int patch2; + int patch3; + int patch4; + + loopDepth++; + + start = gameLocal.program.NumStatements(); + + ExpectToken( "(" ); + + // init + if ( !CheckToken( ";" ) ) { + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ";" ); + } + + // condition + patch2 = gameLocal.program.NumStatements(); + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + // counter + if ( !CheckToken( ")" ) ) { + patch3 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IF, e, 0 ); + + patch4 = patch2; + patch2 = gameLocal.program.NumStatements(); + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + + // goto patch4 + EmitOpcode( OP_GOTO, JumpTo( patch4 ), 0 ); + + // fixup patch3 + gameLocal.program.GetStatement( patch3 ).b = JumpFrom( patch3 ); + } + + ParseStatement(); + + // goto patch2 + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + + // fixup patch1 + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + + // fixup breaks and continues + PatchLoop( start, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseDoWhileStatement +================ +*/ +void idCompiler::ParseDoWhileStatement( void ) { + idVarDef *e; + int patch1; + + loopDepth++; + + patch1 = gameLocal.program.NumStatements(); + ParseStatement(); + ExpectToken( "while" ); + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + ExpectToken( ";" ); + + EmitOpcode( OP_IF, e, JumpTo( patch1 ) ); + + // fixup breaks and continues + PatchLoop( patch1, patch1 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseIfStatement +================ +*/ +void idCompiler::ParseIfStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + ParseStatement(); + + if ( CheckToken( "else" ) ) { + patch2 = gameLocal.program.NumStatements(); + EmitOpcode( OP_GOTO, 0, 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + ParseStatement(); + gameLocal.program.GetStatement( patch2 ).a = JumpFrom( patch2 ); + } else { + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } +} + +/* +============ +idCompiler::ParseStatement +============ +*/ +void idCompiler::ParseStatement( void ) { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + if ( CheckToken( "{" ) ) { + do { + ParseStatement(); + } while( !CheckToken( "}" ) ); + + return; + } + + if ( CheckToken( "return" ) ) { + ParseReturnStatement(); + return; + } + + if ( CheckToken( "while" ) ) { + ParseWhileStatement(); + return; + } + + if ( CheckToken( "for" ) ) { + ParseForStatement(); + return; + } + + if ( CheckToken( "do" ) ) { + ParseDoWhileStatement(); + return; + } + + if ( CheckToken( "break" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot break outside of a loop" ); + } + EmitOpcode( OP_BREAK, 0, 0 ); + return; + } + + if ( CheckToken( "continue" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot contine outside of a loop" ); + } + EmitOpcode( OP_CONTINUE, 0, 0 ); + return; + } + + if ( CheckType() != NULL ) { + ParseDefs(); + return; + } + + if ( CheckToken( "if" ) ) { + ParseIfStatement(); + return; + } + + GetExpression( TOP_PRIORITY ); + ExpectToken(";"); +} + +/* +================ +idCompiler::ParseObjectDef +================ +*/ +void idCompiler::ParseObjectDef( const char *objname ) { + idTypeDef *objtype; + idTypeDef *type; + idTypeDef *parentType; + idTypeDef *fieldtype; + idStr name; + const char *fieldname; + idTypeDef newtype( ev_field, NULL, "", 0, NULL ); + idVarDef *oldscope; + int num; + int i; + + oldscope = scope; + if ( scope->Type() != ev_namespace ) { + Error( "Objects cannot be defined within functions or other objects" ); + } + + // make sure it doesn't exist before we create it + if ( gameLocal.program.FindType( objname ) != NULL ) { + Error( "'%s' : redefinition; different basic types", objname ); + } + + // base type + if ( !CheckToken( ":" ) ) { + parentType = &type_object; + } else { + parentType = ParseType(); + if ( !parentType->Inherits( &type_object ) ) { + Error( "Objects may only inherit from objects." ); + } + } + + objtype = gameLocal.program.AllocType( ev_object, NULL, objname, parentType == &type_object ? 0 : parentType->Size(), parentType ); + objtype->def = gameLocal.program.AllocDef( objtype, objname, scope, true ); + scope = objtype->def; + + // inherit all the functions + num = parentType->NumFunctions(); + for( i = 0; i < parentType->NumFunctions(); i++ ) { + const function_t *func = parentType->GetFunction( i ); + objtype->AddFunction( func ); + } + + ExpectToken( "{" ); + + do { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + continue; + } + + fieldtype = ParseType(); + newtype.SetFieldType( fieldtype ); + + fieldname = va( "%s field", fieldtype->Name() ); + newtype.SetName( fieldname ); + + ParseName( name ); + + // check for a function prototype or declaraction + if ( CheckToken( "(" ) ) { + ParseFunctionDef( newtype.FieldType(), name ); + } else { + type = gameLocal.program.GetType( newtype, true ); + assert( !type->def ); + gameLocal.program.AllocDef( type, name, scope, true ); + objtype->AddField( type, name ); + ExpectToken( ";" ); + } + } while( !CheckToken( "}" ) ); + + scope = oldscope; + + ExpectToken( ";" ); +} + +/* +============ +idCompiler::ParseFunction + +parse a function type +============ +*/ +idTypeDef *idCompiler::ParseFunction( idTypeDef *returnType, const char *name ) { + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + idTypeDef *type; + + if ( scope->Type() != ev_namespace ) { + // create self pointer + newtype.AddFunctionParm( scope->TypeDef(), "self" ); + } + + if ( !CheckToken( ")" ) ) { + idStr parmName; + do { + type = ParseType(); + ParseName( parmName ); + newtype.AddFunctionParm( type, parmName ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + return gameLocal.program.GetType( newtype, true ); +} + +/* +================ +idCompiler::ParseFunctionDef +================ +*/ +void idCompiler::ParseFunctionDef( idTypeDef *returnType, const char *name ) { + idTypeDef *type; + idVarDef *def; + const idVarDef *parm; + idVarDef *oldscope; + int i; + int numParms; + const idTypeDef *parmType; + function_t *func; + statement_t *pos; + + if ( ( scope->Type() != ev_namespace ) && !scope->TypeDef()->Inherits( &type_object ) ) { + Error( "Functions may not be defined within other functions" ); + } + + type = ParseFunction( returnType, name ); + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + type->def = def; + + func = &gameLocal.program.AllocFunction( def ); + if ( scope->TypeDef()->Inherits( &type_object ) ) { + scope->TypeDef()->AddFunction( func ); + } + } else { + func = def->value.functionPtr; + assert( func ); + if ( func->firstStatement ) { + Error( "%s redeclared", def->GlobalName() ); + } + } + +// RAVEN BEGIN +// abahr: moved to below parm size calc as per Jim D +// RAVEN END + + // calculate stack space used by parms + numParms = type->NumParameters(); +// RAVEN BEGIN +// abahr + func->parmTotal = 0; + func->parmSize.Clear(); +// RAVEN END + func->parmSize.SetNum( numParms ); + for( i = 0; i < numParms; i++ ) { + parmType = type->GetParmType( i ); + if ( parmType->Inherits( &type_object ) ) { + func->parmSize[ i ] = type_object.Size(); + } else { + func->parmSize[ i ] = parmType->Size(); + } + func->parmTotal += func->parmSize[ i ]; + } + +// RAVEN BEGIN +// abahr: moved here so prototypes calculate parm sizes + // check if this is a prototype or declaration + if ( !CheckToken( "{" ) ) { + // it's just a prototype, so get the ; and move on + ExpectToken( ";" ); + return; + } +// RAVEN END + + // define the parms + for( i = 0; i < numParms; i++ ) { + if ( gameLocal.program.GetDef( type->GetParmType( i ), type->GetParmName( i ), def ) ) { + Error( "'%s' defined more than once in function parameters", type->GetParmName( i ) ); + } + parm = gameLocal.program.AllocDef( type->GetParmType( i ), type->GetParmName( i ), def, false ); + } + + oldscope = scope; + scope = def; + + func->firstStatement = gameLocal.program.NumStatements(); + + // check if we should call the super class constructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "init" ) ) { + idTypeDef *superClass; + function_t *constructorFunc = NULL; + + // find the superclass constructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + constructorFunc = gameLocal.program.FindFunction( va( "%s::init", superClass->Name() ) ); + if ( constructorFunc ) { + break; + } + } + + // emit the call to the constructor + if ( constructorFunc ) { + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], constructorFunc->def, 0 ); + } + } + + // parse regular statements + while( !CheckToken( "}" ) ) { + ParseStatement(); + } + + // check if we should call the super class destructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "destroy" ) ) { + idTypeDef *superClass; + function_t *destructorFunc = NULL; + + // find the superclass destructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + destructorFunc = gameLocal.program.FindFunction( va( "%s::destroy", superClass->Name() ) ); + if ( destructorFunc ) { + break; + } + } + + if ( destructorFunc ) { + // change all returns to point to the call to the destructor + pos = &gameLocal.program.GetStatement( func->firstStatement ); + for( i = func->firstStatement; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_RETURN ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, gameLocal.program.NumStatements() ); + } + } + + // emit the call to the destructor + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], destructorFunc->def, 0 ); + } + } + +// Disabled code since it caused a function to fall through to the next function when last statement is in the form "if ( x ) { return; }" +#if 0 + // don't bother adding a return opcode if the "return" statement was used. + if ( ( func->firstStatement == gameLocal.program.NumStatements() ) || ( gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ).op != OP_RETURN ) ) { + // emit an end of statements opcode + EmitOpcode( OP_RETURN, 0, 0 ); + } +#else + // always emit the return opcode + EmitOpcode( OP_RETURN, 0, 0 ); +#endif + + // record the number of statements in the function + func->numStatements = gameLocal.program.NumStatements() - func->firstStatement; + + scope = oldscope; +} + +/* +================ +idCompiler::ParseVariableDef +================ +*/ +void idCompiler::ParseVariableDef( idTypeDef *type, const char *name ) { + idVarDef *def, *def2; + bool negate; + + def = gameLocal.program.GetDef( type, name, scope ); + if ( def ) { + Error( "%s redeclared", name ); + } + + def = gameLocal.program.AllocDef( type, name, scope, false ); + + // check for an initialization + if ( CheckToken( "=" ) ) { + // if a local variable in a function then write out interpreter code to initialize variable + if ( scope->Type() == ev_function ) { + def2 = GetExpression( TOP_PRIORITY ); + if ( ( type == &type_float ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_F, def2, def ); + } else if ( ( type == &type_vector ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_V, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_string ) ) { + EmitOpcode( OP_STORE_S, def2, def ); + } else if ( ( type == &type_entity ) && ( ( def2->TypeDef() == &type_entity ) || ( def2->TypeDef()->Inherits( &type_object ) ) ) ) { + EmitOpcode( OP_STORE_ENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef() == &type_entity ) ) { + EmitOpcode( OP_STORE_OBJENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef()->Inherits( type ) ) ) { + EmitOpcode( OP_STORE_OBJ, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOL, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_VTOS, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOBOOL, def2, def ); + } else if ( ( type == &type_float ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOLTOF, def2, def ); + } else { + Error( "bad initialization for '%s'", name ); + } + } else { + // global variables can only be initialized with immediate values + negate = false; + if ( token.type == TT_PUNCTUATION && token == "-" ) { + negate = true; + NextToken(); + if ( immediateType != &type_float ) { + Error( "wrong immediate type for '-' on variable '%s'", name ); + } + } + + if ( immediateType != type ) { + Error( "wrong immediate type for '%s'", name ); + } + + // global variables are initialized at start up + if ( type == &type_string ) { + def->SetString( token, false ); + } else { + if ( negate ) { + immediate._float = -immediate._float; + } + def->SetValue( immediate, false ); + } + NextToken(); + } + } else if ( type == &type_string ) { + // local strings on the stack are initialized in the interpreter + if ( scope->Type() != ev_function ) { + def->SetString( "", false ); + } + } else if ( type->Inherits( &type_object ) ) { + if ( scope->Type() != ev_function ) { + def->SetObject( NULL ); + } + } +} + +/* +================ +idCompiler::GetTypeForEventArg +================ +*/ +idTypeDef *idCompiler::GetTypeForEventArg( char argType ) { + idTypeDef *type; + + switch( argType ) { + case D_EVENT_INTEGER : + // this will get converted to int by the interpreter + type = &type_float; + break; + + case D_EVENT_FLOAT : + type = &type_float; + break; + + case D_EVENT_VECTOR : + type = &type_vector; + break; + + case D_EVENT_STRING : + type = &type_string; + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + type = &type_entity; + break; + + case D_EVENT_VOID : + type = &type_void; + break; + + case D_EVENT_TRACE : + // This data type isn't available from script + type = NULL; + break; + + default: + // probably a typo + type = NULL; + break; + } + + return type; +} + +/* +================ +idCompiler::ParseEventDef +================ +*/ +void idCompiler::ParseEventDef( idTypeDef *returnType, const char *name ) { + const idTypeDef *expectedType; + idTypeDef *argType; + idTypeDef *type; + int i; + int num; + const char *format; + const idEventDef *ev; + idStr parmName; + + ev = idEventDef::FindEvent( name ); + if ( !ev ) { + Error( "Unknown event '%s'", name ); + } + + // set the return type + expectedType = GetTypeForEventArg( ev->GetReturnType() ); + if ( !expectedType ) { + Error( "Invalid return type '%c' in definition of '%s' event.", ev->GetReturnType(), name ); + } + if ( returnType != expectedType ) { + Error( "Return type doesn't match internal return type '%s'", expectedType->Name() ); + } + + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + + ExpectToken( "(" ); + + format = ev->GetArgFormat(); + num = strlen( format ); + for( i = 0; i < num; i++ ) { + expectedType = GetTypeForEventArg( format[ i ] ); + if ( !expectedType || ( expectedType == &type_void ) ) { + Error( "Invalid parameter '%c' in definition of '%s' event.", format[ i ], name ); + } + + argType = ParseType(); + ParseName( parmName ); + if ( argType != expectedType ) { + Error( "The type of parm %d ('%s') does not match the internal type '%s' in definition of '%s' event.", + i + 1, parmName.c_str(), expectedType->Name(), name ); + } + + newtype.AddFunctionParm( argType, "" ); + + if ( i < num - 1 ) { + if ( CheckToken( ")" ) ) { + Error( "Too few parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( "," ); + } + } + if ( !CheckToken( ")" ) ) { + Error( "Too many parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( ";" ); + + type = gameLocal.program.FindType( name ); + if ( type ) { + if ( !newtype.MatchesType( *type ) || ( type->def->value.functionPtr->eventdef != ev ) ) { + Error( "Type mismatch on redefinition of '%s'", name ); + } + } else { + type = gameLocal.program.AllocType( newtype ); + type->def = gameLocal.program.AllocDef( type, name, &def_namespace, true ); + + function_t &func = gameLocal.program.AllocFunction( type->def ); + func.eventdef = ev; + func.parmSize.SetNum( num ); + for( i = 0; i < num; i++ ) { + argType = newtype.GetParmType( i ); + func.parmTotal += argType->Size(); + func.parmSize[ i ] = argType->Size(); + } + + // mark the parms as local + func.locals = func.parmTotal; + } +} + +/* +================ +idCompiler::ParseDefs + +Called at the outer layer and when a local statement is hit +================ +*/ +void idCompiler::ParseDefs( void ) { + idStr name; + idTypeDef *type; + idVarDef *def; + idVarDef *oldscope; + + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + type = ParseType(); + if ( type == &type_scriptevent ) { + type = ParseType(); + ParseName( name ); + ParseEventDef( type, name ); + return; + } + + ParseName( name ); + + if ( type == &type_namespace ) { + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + } + ParseNamespace( def ); + } else if ( CheckToken( "::" ) ) { + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + Error( "Unknown object name '%s'", name.c_str() ); + } + ParseName( name ); + oldscope = scope; + scope = def; + + ExpectToken( "(" ); + ParseFunctionDef( type, name.c_str() ); + scope = oldscope; + } else if ( type == &type_object ) { + ParseObjectDef( name.c_str() ); + } else if ( CheckToken( "(" ) ) { // check for a function prototype or declaraction + ParseFunctionDef( type, name.c_str() ); + } else { + ParseVariableDef( type, name.c_str() ); + while( CheckToken( "," ) ) { + ParseName( name ); + ParseVariableDef( type, name.c_str() ); + } + ExpectToken( ";" ); + } +} + +/* +================ +idCompiler::ParseNamespace + +Parses anything within a namespace definition +================ +*/ +void idCompiler::ParseNamespace( idVarDef *newScope ) { + idVarDef *oldscope; + + oldscope = scope; + if ( newScope != &def_namespace ) { + ExpectToken( "{" ); + } + + while( !eof ) { + scope = newScope; + callthread = false; + + if ( ( newScope != &def_namespace ) && CheckToken( "}" ) ) { + break; + } + + ParseDefs(); + } + + scope = oldscope; +} + +/* +============ +idCompiler::CompileFile + +compiles the 0 terminated text, adding definitions to the program structure +============ +*/ +void idCompiler::CompileFile( const char *text, const char *filename, bool toConsole ) { + idStr orig = filename; + idTimer compile_time; + bool error; + + compile_time.Start(); + + scope = &def_namespace; + basetype = NULL; + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + currentLineNumber = 0; + console = toConsole; + + memset( &immediate, 0, sizeof( immediate ) ); + + parser.SetFlags( LEXFL_ALLOWMULTICHARLITERALS ); + parser.LoadMemory( text, strlen( text ), filename ); + parserPtr = &parser; + + // unread tokens to include script defines + token = SCRIPT_DEFAULTDEFS; + token.type = TT_STRING; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "include"; + token.type = TT_NAME; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "#"; + token.type = TT_PUNCTUATION; + token.subtype = P_PRECOMP; + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + // init the current token line to be the first line so that currentLineNumber is set correctly in NextToken + token.line = 1; + + error = false; + try { + // read first token + NextToken(); + while( !eof && !error ) { + // parse from global namespace + ParseNamespace( &def_namespace ); + } + } + + catch( idCompileError &err ) { + idStr error; + + if ( console ) { + // don't print line number of an error if were calling script from the console using the "script" command + sprintf( error, "Error: %s\n", err.error ); + } else { + sprintf( error, "Error: file %s, line %d: %s\n", gameLocal.program.GetFilename( currentFileNumber ), currentLineNumber, err.error ); + } + + parser.FreeSource(); + + throw idCompileError( error ); + } + + parser.FreeSource(); + + compile_time.Stop(); + if ( !toConsole ) { + gameLocal.Printf( "Compiled '%s': %.1f ms\n", orig.c_str(), compile_time.Milliseconds() ); + } +} diff --git a/src/game/script/Script_Compiler.h b/src/game/script/Script_Compiler.h new file mode 100644 index 0000000..0ec1c60 --- /dev/null +++ b/src/game/script/Script_Compiler.h @@ -0,0 +1,275 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_COMPILER_H__ +#define __SCRIPT_COMPILER_H__ + +const char * const RESULT_STRING = ""; + +typedef struct opcode_s { + char *name; + char *opname; + int priority; + bool rightAssociative; + idVarDef *type_a; + idVarDef *type_b; + idVarDef *type_c; +} opcode_t; + +// These opcodes are no longer necessary: +// OP_PUSH_OBJ: +// OP_PUSH_OBJENT: + +enum { + OP_RETURN, + + OP_UINC_F, + OP_UINCP_F, + OP_UDEC_F, + OP_UDECP_F, + OP_COMP_F, + + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_MOD_F, + OP_ADD_F, + OP_ADD_V, + OP_ADD_S, + OP_ADD_FS, + OP_ADD_SF, + OP_ADD_VS, + OP_ADD_SV, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_EO, + OP_EQ_OE, + OP_EQ_OO, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_EO, + OP_NE_OE, + OP_NE_OO, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_INDIRECT_F, + OP_INDIRECT_V, + OP_INDIRECT_S, + OP_INDIRECT_ENT, + OP_INDIRECT_BOOL, + OP_INDIRECT_OBJ, + + OP_ADDRESS, + + OP_EVENTCALL, + OP_OBJECTCALL, + OP_SYSCALL, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_BOOL, + OP_STORE_OBJENT, + OP_STORE_OBJ, + OP_STORE_ENTOBJ, + + OP_STORE_FTOS, + OP_STORE_BTOS, + OP_STORE_VTOS, + OP_STORE_FTOBOOL, + OP_STORE_BOOLTOF, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_BOOL, + OP_STOREP_OBJ, + OP_STOREP_OBJENT, + + OP_STOREP_FTOS, + OP_STOREP_BTOS, + OP_STOREP_VTOS, + OP_STOREP_FTOBOOL, + OP_STOREP_BOOLTOF, + + OP_UMUL_F, + OP_UMUL_V, + OP_UDIV_F, + OP_UDIV_V, + OP_UMOD_F, + OP_UADD_F, + OP_UADD_V, + OP_USUB_F, + OP_USUB_V, + OP_UAND_F, + OP_UOR_F, + + OP_NOT_BOOL, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + + OP_NEG_F, + OP_NEG_V, + + OP_INT_F, + OP_IF, + OP_IFNOT, + + OP_CALL, + OP_THREAD, + OP_OBJTHREAD, + + OP_PUSH_F, + OP_PUSH_V, + OP_PUSH_S, + OP_PUSH_ENT, + OP_PUSH_OBJ, + OP_PUSH_OBJENT, + OP_PUSH_FTOS, + OP_PUSH_BTOF, + OP_PUSH_FTOB, + OP_PUSH_VTOS, + OP_PUSH_BTOS, + + OP_GOTO, + + OP_AND, + OP_AND_BOOLF, + OP_AND_FBOOL, + OP_AND_BOOLBOOL, + OP_OR, + OP_OR_BOOLF, + OP_OR_FBOOL, + OP_OR_BOOLBOOL, + + OP_BITAND, + OP_BITOR, + + OP_BREAK, // placeholder op. not used in final code + OP_CONTINUE, // placeholder op. not used in final code + + NUM_OPCODES +}; + +class idCompiler { +private: + static bool punctuationValid[ 256 ]; + static char *punctuation[]; + + idParser parser; + idParser *parserPtr; + idToken token; + + idTypeDef *immediateType; + eval_t immediate; + + bool eof; + bool console; + bool callthread; + int braceDepth; + int loopDepth; + int currentLineNumber; + int currentFileNumber; + int errorCount; + + idVarDef *scope; // the function being parsed, or NULL + const idVarDef *basetype; // for accessing fields + + float Divide( float numerator, float denominator ); + void Error( const char *error, ... ) const; + void Warning( const char *message, ... ) const; + idVarDef *OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ); + bool EmitPush( idVarDef *expression, const idTypeDef *funcArg ); + void NextToken( void ); + void ExpectToken( const char *string ); + bool CheckToken( const char *string ); + void ParseName( idStr &name ); + void SkipOutOfFunction( void ); + void SkipToSemicolon( void ); + idTypeDef *CheckType( void ); + idTypeDef *ParseType( void ); + idVarDef *FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const; + idVarDef *GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ); + idVarDef *VirtualFunctionConstant( idVarDef *func ); + idVarDef *SizeConstant( int size ); + idVarDef *JumpConstant( int value ); + idVarDef *JumpDef( int jumpfrom, int jumpto ); + idVarDef *JumpTo( int jumpto ); + idVarDef *JumpFrom( int jumpfrom ); + idVarDef *ParseImmediate( void ); + idVarDef *EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ); + idVarDef *ParseFunctionCall( idVarDef *func ); + idVarDef *ParseObjectCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseEventCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseSysObjectCall( idVarDef *func ); + idVarDef *LookupDef( const char *name, const idVarDef *baseobj ); + idVarDef *ParseValue( void ); + idVarDef *GetTerm( void ); + bool TypeMatches( etype_t type1, etype_t type2 ) const; + idVarDef *GetExpression( int priority ); + idTypeDef *GetTypeForEventArg( char argType ); + void PatchLoop( int start, int continuePos ); + void ParseReturnStatement( void ); + void ParseWhileStatement( void ); + void ParseForStatement( void ); + void ParseDoWhileStatement( void ); + void ParseIfStatement( void ); + void ParseStatement( void ); + void ParseObjectDef( const char *objname ); + idTypeDef *ParseFunction( idTypeDef *returnType, const char *name ); + void ParseFunctionDef( idTypeDef *returnType, const char *name ); + void ParseVariableDef( idTypeDef *type, const char *name ); + void ParseEventDef( idTypeDef *type, const char *name ); + void ParseDefs( void ); + void ParseNamespace( idVarDef *newScope ); + +public : + static opcode_t opcodes[]; + + idCompiler(); + void CompileFile( const char *text, const char *filename, bool console ); +}; + +#endif /* !__SCRIPT_COMPILER_H__ */ diff --git a/src/game/script/Script_Interpreter.cpp b/src/game/script/Script_Interpreter.cpp new file mode 100644 index 0000000..3e349c9 --- /dev/null +++ b/src/game/script/Script_Interpreter.cpp @@ -0,0 +1,1914 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idInterpreter::idInterpreter() +================ +*/ +idInterpreter::idInterpreter() { + localstackUsed = 0; + terminateOnExit = true; + debug = 0; + LastScriptVariable = 0; + memset( localstack, 0, sizeof( localstack ) ); + memset( callStack, 0, sizeof( callStack ) ); + Reset(); +} + +/* +================ +idInterpreter::Save +================ +*/ +void idInterpreter::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->WriteInt( callStack[i].s ); + if ( callStack[i].f ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( callStack[i].f ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( callStack[i].stackbase ); + } + savefile->WriteInt( maxStackDepth ); + + savefile->WriteInt( localstackUsed ); + savefile->Write( &localstack, localstackUsed ); + + savefile->WriteInt( localstackBase ); + savefile->WriteInt( maxLocalstackUsed ); + + if ( currentFunction ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( currentFunction ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( instructionPointer ); + + savefile->WriteInt( popParms ); + + // TOSAVE: idVarDef *LastScriptVariable; + if ( multiFrameEvent ) { + savefile->WriteString( multiFrameEvent->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteObject( eventEntity ); + + savefile->WriteObject( thread ); + + savefile->WriteBool( doneProcessing ); + savefile->WriteBool( threadDying ); + savefile->WriteBool( terminateOnExit ); + savefile->WriteBool( debug ); +} + +/* +================ +idInterpreter::Restore +================ +*/ +void idInterpreter::Restore( idRestoreGame *savefile ) { + int i; + idStr funcname; + int func_index; + + savefile->ReadInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->ReadInt( callStack[i].s ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + callStack[i].f = gameLocal.program.GetFunction( func_index ); + } else { + callStack[i].f = NULL; + } + + savefile->ReadInt( callStack[i].stackbase ); + } + savefile->ReadInt( maxStackDepth ); + + savefile->ReadInt( localstackUsed ); + savefile->Read( &localstack, localstackUsed ); + + savefile->ReadInt( localstackBase ); + savefile->ReadInt( maxLocalstackUsed ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + currentFunction = gameLocal.program.GetFunction( func_index ); + } else { + currentFunction = NULL; + } + savefile->ReadInt( instructionPointer ); + + savefile->ReadInt( popParms ); + + // TORESTORE: idVarDef *LastScriptVariable; + + savefile->ReadString( funcname ); + if ( funcname.Length() ) { + multiFrameEvent = idEventDef::FindEvent( funcname ); + } + + savefile->ReadObject( reinterpret_cast( eventEntity ) ); + savefile->ReadObject( reinterpret_cast( thread ) ); + + savefile->ReadBool( doneProcessing ); + savefile->ReadBool( threadDying ); + savefile->ReadBool( terminateOnExit ); + savefile->ReadBool( debug ); +} + +/* +================ +idInterpreter::Reset +================ +*/ +void idInterpreter::Reset( void ) { + callStackDepth = 0; + localstackUsed = 0; + localstackBase = 0; + + maxLocalstackUsed = 0; + maxStackDepth = 0; + + popParms = 0; + multiFrameEvent = NULL; + eventEntity = NULL; + + currentFunction = 0; + NextInstruction( 0 ); + + threadDying = false; + doneProcessing = true; +} + +/* +================ +idInterpreter::GetRegisterValue + +Returns a string representation of the value of the register. This is +used primarily for the debugger and debugging + +//FIXME: This is pretty much wrong. won't access data in most situations. +================ +*/ +// RAVEN BEGIN +// bdube: took this over again +bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDepth ) { + varEval_t reg; + idVarDef *d; + char funcObject[ 1024 ]; + char *funcName; + const idVarDef *scope = NULL; + const idVarDef *scopeObj; + const idTypeDef *field; + const function_t *func; + + out.Empty(); + + if ( scopeDepth == -1 ) { + scopeDepth = callStackDepth; + } + + if ( scopeDepth == callStackDepth ) { + func = currentFunction; + } else { + func = callStack[ scopeDepth ].f; + } + if ( !func ) { + return false; + } + + idStr::Copynz( funcObject, func->Name(), sizeof( funcObject ) ); + funcName = strstr( funcObject, "::" ); + if ( funcName ) { + *funcName = '\0'; + scopeObj = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); + funcName += 2; + if ( scopeObj ) + { + scope = gameLocal.program.GetDef( NULL, funcName, scopeObj ); + } + } else { + funcName = funcObject; + scope = gameLocal.program.GetDef( NULL, func->Name(), &def_namespace ); + scopeObj = NULL; + } + + if ( !scope ) + { + return false; + } + + d = gameLocal.program.GetDef( NULL, name, scope ); + + // Check the objects for it if it wasnt local to the function + if ( !d ) + { + for ( ; scopeObj && scopeObj->TypeDef()->SuperClass(); scopeObj = scopeObj->TypeDef()->SuperClass()->def ) + { + d = gameLocal.program.GetDef( NULL, name, scopeObj ); + if ( d ) + { + break; + } + } + } + + if ( !d ) + { + out = "???"; + return false; + } + + reg = GetVariable( d ); + switch( d->Type() ) { + case ev_float: + if ( reg.floatPtr ) { + out = va("%g", *reg.floatPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_vector: + if ( reg.vectorPtr ) { + out = va( "%g,%g,%g", reg.vectorPtr->x, reg.vectorPtr->y, reg.vectorPtr->z ); + } else { + out = "0,0,0"; + } + return true; + break; + + case ev_boolean: + if ( reg.intPtr ) { + out = va( "%d", *reg.intPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_field: + { + idEntity* entity; + idScriptObject* obj; + + if ( scope == &def_namespace ) { + // should never happen, but handle it safely anyway + return false; + } + + field = d->TypeDef()->FieldType(); + entity = GetEntity ( *((int*)&localstack[ localstackBase ]) ); + if ( !entity || !field ) + { + return false; + } + + obj = &entity->scriptObject; + if ( !obj ) { + return false; + } + + switch ( field->Type() ) { + case ev_boolean: + out = va( "%d", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_float: + out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_string: { + const char* str; + str = reinterpret_cast( &obj->data[ reg.ptrOffset ] ); + if ( !str ) { + out = "\"\""; + } else { + out = "\""; + out += str; + out += "\""; + } + return true; + } + + default: + return false; + } + + break; + } + + case ev_string: + if ( reg.stringPtr ) { + out = "\""; + out += reg.stringPtr; + out += "\""; + } else { + out = "\"\""; + } + return true; + + default: + return false; + } +} +// RAVEN END + +/* +================ +idInterpreter::GetCallstackDepth +================ +*/ +int idInterpreter::GetCallstackDepth( void ) const { + return callStackDepth; +} + +/* +================ +idInterpreter::GetCallstack +================ +*/ +const prstack_t *idInterpreter::GetCallstack( void ) const { + return &callStack[ 0 ]; +} + +/* +================ +idInterpreter::GetCurrentFunction +================ +*/ +const function_t *idInterpreter::GetCurrentFunction( void ) const { + return currentFunction; +} + +/* +================ +idInterpreter::GetThread +================ +*/ +idThread *idInterpreter::GetThread( void ) const { + return thread; +} + + +/* +================ +idInterpreter::SetThread +================ +*/ +void idInterpreter::SetThread( idThread *pThread ) { + thread = pThread; +} + +/* +================ +idInterpreter::CurrentLine +================ +*/ +int idInterpreter::CurrentLine( void ) const { + if ( instructionPointer < 0 ) { + return 0; + } + return gameLocal.program.GetLineNumberForStatement( instructionPointer ); +} + +/* +================ +idInterpreter::CurrentFile +================ +*/ +const char *idInterpreter::CurrentFile( void ) const { + if ( instructionPointer < 0 ) { + return ""; + } + return gameLocal.program.GetFilenameForStatement( instructionPointer ); +} + +/* +============ +idInterpreter::StackTrace +============ +*/ +void idInterpreter::StackTrace( void ) const { + const function_t *f; + int i; + int top; + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + return; + } + + top = callStackDepth; + if ( top >= MAX_STACK_DEPTH ) { + top = MAX_STACK_DEPTH - 1; + } + + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = top; i >= 0; i-- ) { + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } +} + +/* +============ +idInterpreter::Error + +Aborts the currently executing function +============ +*/ +void idInterpreter::Error( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + StackTrace(); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Error( "%s(%d): Thread '%s': %s\n", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Error( "Thread '%s': %s\n", thread->GetThreadName(), text ); + } +} + +/* +============ +idInterpreter::Warning + +Prints file and line number information with warning. +============ +*/ +void idInterpreter::Warning( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Warning( "%s(%d): Thread '%s': %s", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Warning( "Thread '%s' : %s", thread->GetThreadName(), text ); + } +} + +/* +================ +idInterpreter::DisplayInfo +================ +*/ +void idInterpreter::DisplayInfo( void ) const { + const function_t *f; + int i; + + gameLocal.Printf( " Stack depth: %d bytes, %d max\n", localstackUsed, maxLocalstackUsed ); + gameLocal.Printf( " Call depth: %d, %d max\n", callStackDepth, maxStackDepth ); + gameLocal.Printf( " Call Stack: " ); + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + } else { + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = callStackDepth; i > 0; i-- ) { + gameLocal.Printf( " " ); + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } + } +} + +/* +==================== +idInterpreter::ThreadCall + +Copys the args from the calling thread's stack +==================== +*/ +void idInterpreter::ThreadCall( idInterpreter *source, const function_t *func, int args ) { + Reset(); + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( localstack, &source->localstack[ source->localstackUsed - args ], args ); +// RAVEN END + + localstackUsed = args; + localstackBase = 0; + + maxLocalstackUsed = localstackUsed; + EnterFunction( func, false ); + + thread->SetThreadName( currentFunction->Name() ); +} + +/* +================ +idInterpreter::EnterObjectFunction + +Calls a function on a script object. + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +================ +*/ +void idInterpreter::EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ) { + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + Push( self->entityNumber + 1 ); + EnterFunction( func, false ); +} + +/* +==================== +idInterpreter::EnterFunction + +Returns the new program statement counter + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +==================== +*/ +void idInterpreter::EnterFunction( const function_t *func, bool clearStack ) { + int c; + prstack_t *stack; + + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + + if ( callStackDepth >= MAX_STACK_DEPTH ) { + Error( "call stack overflow" ); + } + + stack = &callStack[ callStackDepth ]; + + stack->s = instructionPointer + 1; // point to the next instruction to execute + stack->f = currentFunction; + stack->stackbase = localstackBase; + + callStackDepth++; + if ( callStackDepth > maxStackDepth ) { + maxStackDepth = callStackDepth; + } + + if ( !func ) { + Error( "NULL function" ); + } + + if ( debug ) { + if ( currentFunction ) { + gameLocal.Printf( "%d: call '%s' from '%s'(line %d)%s\n", gameLocal.time, func->Name(), currentFunction->Name(), + gameLocal.program.GetStatement( instructionPointer ).linenumber, clearStack ? " clear stack" : "" ); + } else { + gameLocal.Printf( "%d: call '%s'%s\n", gameLocal.time, func->Name(), clearStack ? " clear stack" : "" ); + } + } + + currentFunction = func; + assert( !func->eventdef ); + NextInstruction( func->firstStatement ); + + // allocate space on the stack for locals + // parms are already on stack + c = func->locals - func->parmTotal; + assert( c >= 0 ); + + if ( localstackUsed + c > LOCALSTACK_SIZE ) { + Error( "EnterFuncton: locals stack overflow\n" ); + } + + // initialize local stack variables to zero + memset( &localstack[ localstackUsed ], 0, c ); + + localstackUsed += c; + localstackBase = localstackUsed - func->locals; + + if ( localstackUsed > maxLocalstackUsed ) { + maxLocalstackUsed = localstackUsed ; + } +} + +/* +==================== +idInterpreter::LeaveFunction +==================== +*/ +void idInterpreter::LeaveFunction( idVarDef *returnDef ) { + prstack_t *stack; + varEval_t ret; + + if ( callStackDepth <= 0 ) { + Error( "prog stack underflow" ); + } + + // return value + if ( returnDef ) { + switch( returnDef->Type() ) { + case ev_string : + gameLocal.program.ReturnString( GetString( returnDef ) ); + break; + + case ev_vector : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnVector( *ret.vectorPtr ); + break; + + default : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnInteger( *ret.intPtr ); + } + } + + // remove locals from the stack + PopParms( currentFunction->locals ); + assert( localstackUsed == localstackBase ); + + if ( debug ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + gameLocal.Printf( "%d: %s(%d): exit %s", gameLocal.time, gameLocal.program.GetFilename( line.file ), line.linenumber, currentFunction->Name() ); + if ( callStackDepth > 1 ) { + gameLocal.Printf( " return to %s(line %d)\n", callStack[ callStackDepth - 1 ].f->Name(), gameLocal.program.GetStatement( callStack[ callStackDepth - 1 ].s ).linenumber ); + } else { + gameLocal.Printf( " done\n" ); + } + } + + // up stack + callStackDepth--; + stack = &callStack[ callStackDepth ]; + currentFunction = stack->f; + localstackBase = stack->stackbase; + NextInstruction( stack->s ); + + if ( !callStackDepth ) { + // all done + doneProcessing = true; + threadDying = true; + currentFunction = 0; + } +} + +/* +================ +idInterpreter::CallEvent +================ +*/ +void idInterpreter::CallEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t var; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + var.intPtr = ( int * )&localstack[ start ]; + eventEntity = GetEntity( *var.entityNumberPtr ); + + if ( !eventEntity || !eventEntity->RespondsTo( *evdef ) ) { +// RAVEN BEGIN +// jshepard: added catch for entities that don't exist + if ( eventEntity && developer.GetBool() ) { + // give a warning in developer mode + Warning( "Function '%s' not supported on entity '%s'", evdef->GetName(), eventEntity->name.c_str() ); + } else if( !eventEntity ) { + //check the last script variable. + if(!!LastScriptVariable) { + //if it's $null_entity, then we should ignore this. + if(idStr::Cmp( "$null_entity", LastScriptVariable->Name()) ) { + Warning( "Entity '%s' is a null entity", LastScriptVariable->Name() ); + } + } else { + Warning( "Null entity referenced -- check entity name spelling."); + } + } +// RAVEN END + // always return a safe value when an object doesn't exist + switch( evdef->GetReturnType() ) { + case D_EVENT_INTEGER : + gameLocal.program.ReturnInteger( 0 ); + break; + + case D_EVENT_FLOAT : + gameLocal.program.ReturnFloat( 0 ); + break; + + case D_EVENT_VECTOR : + gameLocal.program.ReturnVector( vec3_zero ); + break; + + case D_EVENT_STRING : + gameLocal.program.ReturnString( "" ); + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + gameLocal.program.ReturnEntity( ( idEntity * )NULL ); + break; + + case D_EVENT_TRACE : + default: + // unsupported data type + break; + } + + PopParms( argsize ); + eventEntity = NULL; + return; + } + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = type_object.Size(); ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + var.intPtr = ( int * )&localstack[ start + pos ]; + data[ i ] = int( *var.floatPtr ); + break; + + case D_EVENT_FLOAT : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( float * )&data[ i ] ) = *var.floatPtr; + break; + + case D_EVENT_VECTOR : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idVec3 ** )&data[ i ] ) = var.vectorPtr; + break; + + case D_EVENT_STRING : + ( *( const char ** )&data[ i ] ) = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + if ( !( *( idEntity ** )&data[ i ] ) ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + eventEntity->ProcessEventArgPtr( evdef, data ); + if ( !multiFrameEvent ) { + if ( popParms ) { + PopParms( popParms ); + } + eventEntity = NULL; + } else { + doneProcessing = true; + } + popParms = 0; +} + +/* +================ +idInterpreter::BeginMultiFrameEvent +================ +*/ +bool idInterpreter::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( eventEntity != ent ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong entity" ); + } + if ( multiFrameEvent ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong event" ); + } + return false; + } + + multiFrameEvent = event; + return true; +} + +/* +================ +idInterpreter::EndMultiFrameEvent +================ +*/ +void idInterpreter::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::EndMultiFrameEvent called with wrong event" ); + } + + multiFrameEvent = NULL; +} + +/* +================ +idInterpreter::MultiFrameEventInProgress +================ +*/ +bool idInterpreter::MultiFrameEventInProgress( void ) const { + return multiFrameEvent != NULL; +} + +/* +================ +idInterpreter::CallSysEvent +================ +*/ +void idInterpreter::CallSysEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t source; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = 0; ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( int * )&data[ i ] = int( *source.floatPtr ); + break; + + case D_EVENT_FLOAT : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( float * )&data[ i ] = *source.floatPtr; + break; + + case D_EVENT_VECTOR : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idVec3 ** )&data[ i ] = source.vectorPtr; + break; + + case D_EVENT_STRING : + *( const char ** )&data[ i ] = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + if ( !*( idEntity ** )&data[ i ] ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + thread->ProcessEventArgPtr( evdef, data ); + if ( popParms ) { + PopParms( popParms ); + } + popParms = 0; +} + +/* +==================== +idInterpreter::Execute +==================== +*/ +bool idInterpreter::Execute( void ) { + varEval_t var_a; + varEval_t var_b; + varEval_t var_c; + varEval_t var; + statement_t *st; + int runaway; + idThread *newThread; + float floatVal; + idScriptObject *obj; + const function_t *func; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_SCRIPT); +// RAVEN END + + if ( threadDying || !currentFunction ) { + return true; + } + + if ( multiFrameEvent ) { + // move to previous instruction and call it again + instructionPointer--; + } + + runaway = 5000000; + + doneProcessing = false; + while( !doneProcessing && !threadDying ) { + instructionPointer++; + + if ( !--runaway ) { + Error( "runaway loop error" ); + } + + // next statement + st = &gameLocal.program.GetStatement( instructionPointer ); + +// RAVEN BEGIN +// bdube: if the debugger is running then we need to check to see if any breakpoints have beeng hit + if ( gameLocal.editors & EDITOR_DEBUGGER ) { + common->DebuggerCheckBreakpoint ( this, &gameLocal.program, instructionPointer ); + } else if ( g_debugScript.GetBool ( ) ) { + static int lastLineNumber = -1; + if ( lastLineNumber != gameLocal.program.GetStatement ( instructionPointer ).linenumber ) { + gameLocal.Printf ( "%s (%d)\n", + gameLocal.program.GetFilename ( gameLocal.program.GetStatement ( instructionPointer ).file ), + gameLocal.program.GetStatement ( instructionPointer ).linenumber + ); + lastLineNumber = gameLocal.program.GetStatement ( instructionPointer ).linenumber; + } + } +// RAVEN END + + switch( st->op ) { + case OP_RETURN: + LeaveFunction( st->a ); + break; + + case OP_THREAD: + newThread = new idThread( this, st->a->value.functionPtr, st->b->value.argSize ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + PopParms( st->b->value.argSize ); + break; + + case OP_OBJTHREAD: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + assert( st->c->value.argSize == func->parmTotal ); + newThread = new idThread( this, GetEntity( *var_a.entityNumberPtr ), func, func->parmTotal ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + } else { + // return a null thread to the script + gameLocal.program.ReturnFloat( 0.0f ); + } + PopParms( st->c->value.argSize ); + break; + + case OP_CALL: + EnterFunction( st->a->value.functionPtr, false ); + break; + + case OP_EVENTCALL: + CallEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_OBJECTCALL: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + EnterFunction( func, false ); + } else { + // return a 'safe' value + gameLocal.program.ReturnVector( vec3_zero ); + gameLocal.program.ReturnString( "" ); + PopParms( st->c->value.argSize ); + } + break; + + case OP_SYSCALL: + CallSysEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_IFNOT: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr == 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_IF: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr != 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_GOTO: + NextInstruction( instructionPointer + st->a->value.jumpOffset ); + break; + + case OP_ADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr + *var_b.floatPtr; + break; + + case OP_ADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr + *var_b.vectorPtr; + break; + + case OP_ADD_S: + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_FS: + var_a = GetVariable( st->a ); + SetString( st->c, FloatToString( *var_a.floatPtr ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SF: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, FloatToString( *var_b.floatPtr ) ); + break; + + case OP_ADD_VS: + var_a = GetVariable( st->a ); + SetString( st->c, var_a.vectorPtr->ToString() ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SV: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, var_b.vectorPtr->ToString() ); + break; + + case OP_SUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr - *var_b.floatPtr; + break; + + case OP_SUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr - *var_b.vectorPtr; + break; + + case OP_MUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr * *var_b.floatPtr; + break; + + case OP_MUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.vectorPtr * *var_b.vectorPtr; + break; + + case OP_MUL_FV: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.floatPtr * *var_b.vectorPtr; + break; + + case OP_MUL_VF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr * *var_b.floatPtr; + break; + + case OP_DIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = idMath::INFINITY; + } else { + *var_c.floatPtr = *var_a.floatPtr / *var_b.floatPtr; + } + break; + + case OP_MOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable ( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = *var_a.floatPtr; + } else { + *var_c.floatPtr = static_cast( *var_a.floatPtr ) % static_cast( *var_b.floatPtr ); + } + break; + + case OP_BITAND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) & static_cast( *var_b.floatPtr ); + break; + + case OP_BITOR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) | static_cast( *var_b.floatPtr ); + break; + + case OP_GE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr >= *var_b.floatPtr ); + break; + + case OP_LE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr <= *var_b.floatPtr ); + break; + + case OP_GT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr > *var_b.floatPtr ); + break; + + case OP_LT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr < *var_b.floatPtr ); + break; + + case OP_AND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.intPtr != 0 ); + break; + + case OP_AND_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.intPtr != 0 ); + break; + + case OP_OR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.intPtr != 0 ); + break; + + case OP_OR_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.intPtr != 0 ); + break; + + case OP_NOT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr == 0 ); + break; + + case OP_NOT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == 0.0f ); + break; + + case OP_NOT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == vec3_zero ); + break; + + case OP_NOT_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( strlen( GetString( st->a ) ) == 0 ); + break; + + case OP_NOT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( GetEntity( *var_a.entityNumberPtr ) == NULL ); + break; + + case OP_NEG_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = -*var_a.floatPtr; + break; + + case OP_NEG_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = -*var_a.vectorPtr; + break; + + case OP_INT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ); + break; + + case OP_EQ_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == *var_b.floatPtr ); + break; + + case OP_EQ_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == *var_b.vectorPtr ); + break; + + case OP_EQ_S: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) == 0 ); + break; + + case OP_EQ_E: + case OP_EQ_EO: + case OP_EQ_OE: + case OP_EQ_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr == *var_b.entityNumberPtr ); + break; + + case OP_NE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != *var_b.floatPtr ); + break; + + case OP_NE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr != *var_b.vectorPtr ); + break; + + case OP_NE_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) != 0 ); + break; + + case OP_NE_E: + case OP_NE_EO: + case OP_NE_OE: + case OP_NE_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr != *var_b.entityNumberPtr ); + break; + + case OP_UADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr += *var_a.floatPtr; + break; + + case OP_UADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr += *var_a.vectorPtr; + break; + + case OP_USUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr -= *var_a.floatPtr; + break; + + case OP_USUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr -= *var_a.vectorPtr; + break; + + case OP_UMUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr *= *var_a.floatPtr; + break; + + case OP_UMUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr *= *var_a.floatPtr; + break; + + case OP_UDIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = idMath::INFINITY; + } else { + *var_b.floatPtr = *var_b.floatPtr / *var_a.floatPtr; + } + break; + + case OP_UDIV_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + var_b.vectorPtr->Set( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ); + } else { + *var_b.vectorPtr = *var_b.vectorPtr / *var_a.floatPtr; + } + break; + + case OP_UMOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = *var_a.floatPtr; + } else { + *var_b.floatPtr = static_cast( *var_b.floatPtr ) % static_cast( *var_a.floatPtr ); + } + break; + + case OP_UOR_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) | static_cast( *var_a.floatPtr ); + break; + + case OP_UAND_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) & static_cast( *var_a.floatPtr ); + break; + + case OP_UINC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )++; + break; + + case OP_UINCP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )++; + } + break; + + case OP_UDEC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )--; + break; + + case OP_UDECP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )--; + } + break; + + case OP_COMP_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ~static_cast( *var_a.floatPtr ); + break; + + case OP_STORE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = *var_a.floatPtr; + break; + + case OP_STORE_ENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_BOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.intPtr = *var_a.intPtr; + break; + + case OP_STORE_OBJENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.entityNumberPtr = 0; + } else if ( !obj->GetTypeDef()->Inherits( st->b->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->b->TypeDef()->Name() ); + *var_b.entityNumberPtr = 0; + } else { + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STORE_OBJ: + case OP_STORE_ENTOBJ: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_S: + SetString( st->b, GetString( st->a ) ); + break; + + case OP_STORE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr = *var_a.vectorPtr; + break; + + case OP_STORE_FTOS: + var_a = GetVariable( st->a ); + SetString( st->b, FloatToString( *var_a.floatPtr ) ); + break; + + case OP_STORE_BTOS: + var_a = GetVariable( st->a ); + SetString( st->b, *var_a.intPtr ? "true" : "false" ); + break; + + case OP_STORE_VTOS: + var_a = GetVariable( st->a ); + SetString( st->b, var_a.vectorPtr->ToString() ); + break; + + case OP_STORE_FTOBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.intPtr = 1; + } else { + *var_b.intPtr = 0; + } + break; + + case OP_STORE_BOOLTOF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_a.intPtr ); + break; + + case OP_STOREP_F: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = *var_a.floatPtr; + } + break; + + case OP_STOREP_ENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_FLD: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_BOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_S: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + idStr::Copynz( var_b.evalPtr->stringPtr, GetString( st->a ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_V: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->vectorPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->vectorPtr = *var_a.vectorPtr; + } + break; + + case OP_STOREP_FTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, FloatToString( *var_a.floatPtr ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_BTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + idStr::Copynz( var_b.evalPtr->stringPtr, "true", MAX_STRING_LEN ); + } else { + idStr::Copynz( var_b.evalPtr->stringPtr, "false", MAX_STRING_LEN ); + } + } + break; + + case OP_STOREP_VTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, var_a.vectorPtr->ToString(), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_FTOBOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.evalPtr->intPtr = 1; + } else { + *var_b.evalPtr->intPtr = 0; + } + } + break; + + case OP_STOREP_BOOLTOF: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = static_cast( *var_a.intPtr ); + } + break; + + case OP_STOREP_OBJ: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_OBJENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.evalPtr->entityNumberPtr = 0; + + // st->b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in st->c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + } else if ( !obj->GetTypeDef()->Inherits( st->c->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->c->TypeDef()->Name() ); + *var_b.evalPtr->entityNumberPtr = 0; + } else { + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + } + break; + + case OP_ADDRESS: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var_c.evalPtr->bytePtr = &obj->data[ st->b->value.ptrOffset ]; + } else { + var_c.evalPtr->bytePtr = NULL; + } + break; + + case OP_INDIRECT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.floatPtr = *var.floatPtr; + } else { + *var_c.floatPtr = 0.0f; + } + break; + + case OP_INDIRECT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } else { + *var_c.entityNumberPtr = 0; + } + break; + + case OP_INDIRECT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.intPtr = *var.intPtr; + } else { + *var_c.intPtr = 0; + } + break; + + case OP_INDIRECT_S: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + SetString( st->c, var.stringPtr ); + } else { + SetString( st->c, "" ); + } + break; + + case OP_INDIRECT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.vectorPtr = *var.vectorPtr; + } else { + var_c.vectorPtr->Zero(); + } + break; + + case OP_INDIRECT_OBJ: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_c.entityNumberPtr = 0; + } else { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } + break; + + case OP_PUSH_F: + var_a = GetVariable( st->a ); + Push( *var_a.intPtr ); + break; + + case OP_PUSH_FTOS: + var_a = GetVariable( st->a ); + PushString( FloatToString( *var_a.floatPtr ) ); + break; + + case OP_PUSH_BTOF: + var_a = GetVariable( st->a ); + floatVal = *var_a.intPtr; + Push( *reinterpret_cast( &floatVal ) ); + break; + + case OP_PUSH_FTOB: + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + Push( 1 ); + } else { + Push( 0 ); + } + break; + + case OP_PUSH_VTOS: + var_a = GetVariable( st->a ); + PushString( var_a.vectorPtr->ToString() ); + break; + + case OP_PUSH_BTOS: + var_a = GetVariable( st->a ); + PushString( *var_a.intPtr ? "true" : "false" ); + break; + + case OP_PUSH_ENT: + var_a = GetVariable( st->a ); +// RAVEN BEGIN +// jshepard: keep tabs on this guy, he's the last referenced script variable. + assert(st->a); + LastScriptVariable = st->a; + //This line leads to memory corruption, I need to come up with a better way of keeping track. + //LastScriptVariable->initialized = idVarDef::stackVariable; +// RAVEN END + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_S: + PushString( GetString( st->a ) ); + break; + + case OP_PUSH_V: + var_a = GetVariable( st->a ); + Push( *reinterpret_cast( &var_a.vectorPtr->x ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->y ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->z ) ); + break; + + case OP_PUSH_OBJ: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_OBJENT: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_BREAK: + case OP_CONTINUE: + default: + Error( "Bad opcode %i", st->op ); + break; + } + } + + return threadDying; +} diff --git a/src/game/script/Script_Interpreter.h b/src/game/script/Script_Interpreter.h new file mode 100644 index 0000000..08b063c --- /dev/null +++ b/src/game/script/Script_Interpreter.h @@ -0,0 +1,277 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_INTERPRETER_H__ +#define __SCRIPT_INTERPRETER_H__ + +#define MAX_STACK_DEPTH 64 +#define LOCALSTACK_SIZE 6144 + +typedef struct prstack_s { + int s; + const function_t *f; + int stackbase; +} prstack_t; + +class idInterpreter { +private: + prstack_t callStack[ MAX_STACK_DEPTH ]; + int callStackDepth; + int maxStackDepth; + + byte localstack[ LOCALSTACK_SIZE ]; + int localstackUsed; + int localstackBase; + int maxLocalstackUsed; + + const function_t *currentFunction; + int instructionPointer; + + int popParms; + const idEventDef *multiFrameEvent; + idEntity *eventEntity; + + idThread *thread; + + void PopParms( int numParms ); +// RAVEN BEGIN +// abahr: making Push public to allow parms to be put on stack +public: + void PushString( const char *string ); + void Push( int value ); +private: +// RAVEN END + const char *FloatToString( float value ); + void AppendString( idVarDef *def, const char *from ); + void SetString( idVarDef *def, const char *from ); + const char *GetString( idVarDef *def ); + varEval_t GetVariable( idVarDef *def ); + idEntity *GetEntity( int entnum ) const; + idScriptObject *GetScriptObject( int entnum ) const; + void NextInstruction( int position ); + + void LeaveFunction( idVarDef *returnDef ); + void CallEvent( const function_t *func, int argsize ); + void CallSysEvent( const function_t *func, int argsize ); +// RAVEN BEGIN +// jshepard: last variable referenced in the script-- keep tabs on it so we can print it for warnings. + idVarDef *LastScriptVariable; +// RAVEN END + + +public: + bool doneProcessing; + bool threadDying; + bool terminateOnExit; + bool debug; + + idInterpreter(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetThread( idThread *pThread ); + + void StackTrace( void ) const; + + int CurrentLine( void ) const; + const char *CurrentFile( void ) const; + + void Error( char *fmt, ... ) const; + void Warning( char *fmt, ... ) const; + void DisplayInfo( void ) const; + + bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + bool MultiFrameEventInProgress( void ) const; + + void ThreadCall( idInterpreter *source, const function_t *func, int args ); + void EnterFunction( const function_t *func, bool clearStack ); + void EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ); + + bool Execute( void ); + void Reset( void ); + + bool GetRegisterValue( const char *name, idStr &out, int scopeDepth ); + int GetCallstackDepth( void ) const; + const prstack_t *GetCallstack( void ) const; + const function_t *GetCurrentFunction( void ) const; + idThread *GetThread( void ) const; + +}; + +/* +==================== +idInterpreter::PopParms +==================== +*/ +ID_INLINE void idInterpreter::PopParms( int numParms ) { + // pop our parms off the stack + if ( localstackUsed < numParms ) { + Error( "locals stack underflow\n" ); + } + + localstackUsed -= numParms; +} + +/* +==================== +idInterpreter::Push +==================== +*/ +ID_INLINE void idInterpreter::Push( int value ) { + if ( localstackUsed + sizeof( int ) > LOCALSTACK_SIZE ) { + Error( "Push: locals stack overflow\n" ); + } + *( int * )&localstack[ localstackUsed ] = value; + localstackUsed += sizeof( int ); +} + +/* +==================== +idInterpreter::PushString +==================== +*/ +ID_INLINE void idInterpreter::PushString( const char *string ) { + if ( localstackUsed + MAX_STRING_LEN > LOCALSTACK_SIZE ) { + Error( "PushString: locals stack overflow\n" ); + } + idStr::Copynz( ( char * )&localstack[ localstackUsed ], string, MAX_STRING_LEN ); + localstackUsed += MAX_STRING_LEN; +} + +/* +==================== +idInterpreter::FloatToString +==================== +*/ +ID_INLINE const char *idInterpreter::FloatToString( float value ) { + static char text[ 32 ]; + + if ( value == ( float )( int )value ) { + sprintf( text, "%d", ( int )value ); + } else { + sprintf( text, "%f", value ); + } + return text; +} + +/* +==================== +idInterpreter::AppendString +==================== +*/ +ID_INLINE void idInterpreter::AppendString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Append( ( char * )&localstack[ localstackBase + def->value.stackOffset ], MAX_STRING_LEN, from ); + } else { + idStr::Append( def->value.stringPtr, MAX_STRING_LEN, from ); + } +} + +/* +==================== +idInterpreter::SetString +==================== +*/ +ID_INLINE void idInterpreter::SetString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Copynz( ( char * )&localstack[ localstackBase + def->value.stackOffset ], from, MAX_STRING_LEN ); + } else { + idStr::Copynz( def->value.stringPtr, from, MAX_STRING_LEN ); + } +} + +/* +==================== +idInterpreter::GetString +==================== +*/ +ID_INLINE const char *idInterpreter::GetString( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + return ( char * )&localstack[ localstackBase + def->value.stackOffset ]; + } else { + return def->value.stringPtr; + } +} + +/* +==================== +idInterpreter::GetVariable +==================== +*/ +ID_INLINE varEval_t idInterpreter::GetVariable( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + varEval_t val; + val.intPtr = ( int * )&localstack[ localstackBase + def->value.stackOffset ]; + return val; + } else { + return def->value; + } +} + +/* +================ +idInterpreter::GetEntity +================ +*/ +ID_INLINE idEntity *idInterpreter::GetEntity( int entnum ) const{ + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + return gameLocal.entities[ entnum - 1 ]; + } + return NULL; +} + +/* +================ +idInterpreter::GetScriptObject +================ +*/ +ID_INLINE idScriptObject *idInterpreter::GetScriptObject( int entnum ) const { + idEntity *ent; + + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + ent = gameLocal.entities[ entnum - 1 ]; + if ( ent && ent->scriptObject.data ) { + return &ent->scriptObject; + } + } + return NULL; +} + +/* +==================== +idInterpreter::NextInstruction +==================== +*/ +ID_INLINE void idInterpreter::NextInstruction( int position ) { + // Before we execute an instruction, we increment instructionPointer, + // therefore we need to compensate for that here. + instructionPointer = position - 1; +} + +#endif /* !__SCRIPT_INTERPRETER_H__ */ diff --git a/src/game/script/Script_Program.cpp b/src/game/script/Script_Program.cpp new file mode 100644 index 0000000..8a8cf11 --- /dev/null +++ b/src/game/script/Script_Program.cpp @@ -0,0 +1,2537 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +// simple types. function types are dynamically allocated +idTypeDef type_void( ev_void, &def_void, "void", 0, NULL ); +idTypeDef type_scriptevent( ev_scriptevent, &def_scriptevent, "scriptevent", sizeof( void * ), NULL ); +idTypeDef type_namespace( ev_namespace, &def_namespace, "namespace", sizeof( void * ), NULL ); +// RAVEN BEGIN +// abahr +rvTypeDefString type_string( ev_string, &def_string, "string", MAX_STRING_LEN, NULL ); +rvTypeDefFloat type_float( ev_float, &def_float, "float", sizeof( float ), NULL ); +rvTypeDefVec3 type_vector( ev_vector, &def_vector, "vector", sizeof( idVec3 ), NULL ); +rvTypeDefEntity type_entity( ev_entity, &def_entity, "entity", sizeof( int * ), NULL ); // stored as entity number pointer +// RAVEN END +idTypeDef type_field( ev_field, &def_field, "field", sizeof( void * ), NULL ); +idTypeDef type_function( ev_function, &def_function, "function", sizeof( void * ), &type_void ); +idTypeDef type_virtualfunction( ev_virtualfunction, &def_virtualfunction, "virtual function", sizeof( int ), NULL ); +idTypeDef type_pointer( ev_pointer, &def_pointer, "pointer", sizeof( void * ), NULL ); +idTypeDef type_object( ev_object, &def_object, "object", sizeof( int * ), NULL ); // stored as entity number pointer +idTypeDef type_jumpoffset( ev_jumpoffset, &def_jumpoffset, "", sizeof( int ), NULL ); // only used for jump opcodes +idTypeDef type_argsize( ev_argsize, &def_argsize, "", sizeof( int ), NULL ); // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +rvTypeDefBool type_boolean( ev_boolean, &def_boolean, "boolean", sizeof( int ), NULL ); +// RAVEN END + +idVarDef def_void( &type_void ); +idVarDef def_scriptevent( &type_scriptevent ); +idVarDef def_namespace( &type_namespace ); +idVarDef def_string( &type_string ); +idVarDef def_float( &type_float ); +idVarDef def_vector( &type_vector ); +idVarDef def_entity( &type_entity ); +idVarDef def_field( &type_field ); +idVarDef def_function( &type_function ); +idVarDef def_virtualfunction( &type_virtualfunction ); +idVarDef def_pointer( &type_pointer ); +idVarDef def_object( &type_object ); +idVarDef def_jumpoffset( &type_jumpoffset ); // only used for jump opcodes +idVarDef def_argsize( &type_argsize ); +idVarDef def_boolean( &type_boolean ); + +/*********************************************************************** + + function_t + +***********************************************************************/ + +/* +================ +function_t::function_t +================ +*/ +function_t::function_t() { + Clear(); +} + +/* +================ +function_t::Allocated +================ +*/ +size_t function_t::Allocated( void ) const { + return name.Allocated() + parmSize.Allocated(); +} + +/* +================ +function_t::SetName +================ +*/ +void function_t::SetName( const char *name ) { + this->name = name; +} + +/* +================ +function_t::Name +================ +*/ +const char *function_t::Name( void ) const { + return name; +} + +/* +================ +function_t::Clear +================ +*/ +void function_t::Clear( void ) { + eventdef = NULL; + def = NULL; + type = NULL; + firstStatement = 0; + numStatements = 0; + parmTotal = 0; + locals = 0; + filenum = 0; + name.Clear(); + parmSize.Clear(); +} + +/*********************************************************************** + + idTypeDef + +***********************************************************************/ + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + name = ename; + type = etype; + def = edef; + size = esize; + auxType = aux; + + parmTypes.SetGranularity( 1 ); + parmNames.SetGranularity( 1 ); + functions.SetGranularity( 1 ); +} + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( const idTypeDef &other ) { + *this = other; +} + +/* +================ +idTypeDef::operator= +================ +*/ +void idTypeDef::operator=( const idTypeDef& other ) { + type = other.type; + def = other.def; + name = other.name; + size = other.size; + auxType = other.auxType; + parmTypes = other.parmTypes; + parmNames = other.parmNames; + functions = other.functions; +} + +/* +================ +idTypeDef::Allocated +================ +*/ +size_t idTypeDef::Allocated( void ) const { + size_t memsize; + int i; + + memsize = name.Allocated() + parmTypes.Allocated() + parmNames.Allocated() + functions.Allocated(); + for( i = 0; i < parmTypes.Num(); i++ ) { + memsize += parmNames[ i ].Allocated(); + } + + return memsize; +} + +/* +================ +idTypeDef::Inherits + +Returns true if basetype is an ancestor of this type. +================ +*/ +bool idTypeDef::Inherits( const idTypeDef *basetype ) const { + idTypeDef *superType; + + if ( type != ev_object ) { + return false; + } + + if ( this == basetype ) { + return true; + } + for( superType = auxType; superType != NULL; superType = superType->auxType ) { + if ( superType == basetype ) { + return true; + } + } + + return false; +} + +/* +================ +idTypeDef::MatchesType + +Returns true if both types' base types and parameters match +================ +*/ +bool idTypeDef::MatchesType( const idTypeDef &matchtype ) const { + int i; + + if ( this == &matchtype ) { + return true; + } + + if ( ( type != matchtype.type ) || ( auxType != matchtype.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchtype.parmTypes.Num() ) { + return false; + } + + for( i = 0; i < matchtype.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchtype.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::MatchesVirtualFunction + +Returns true if both functions' base types and parameters match +================ +*/ +bool idTypeDef::MatchesVirtualFunction( const idTypeDef &matchfunc ) const { + int i; + + if ( this == &matchfunc ) { + return true; + } + + if ( ( type != matchfunc.type ) || ( auxType != matchfunc.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchfunc.parmTypes.Num() ) { + return false; + } + + if ( parmTypes.Num() > 0 ) { + if ( !parmTypes[ 0 ]->Inherits( matchfunc.parmTypes[ 0 ] ) ) { + return false; + } + } + + for( i = 1; i < matchfunc.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchfunc.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::AddFunctionParm + +Adds a new parameter for a function type. +================ +*/ +void idTypeDef::AddFunctionParm( idTypeDef *parmtype, const char *name ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::AddFunctionParm : tried to add parameter on non-function type" ); + } + + parmTypes.Append( parmtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; +} + +/* +================ +idTypeDef::AddField + +Adds a new field to an object type. +================ +*/ +void idTypeDef::AddField( idTypeDef *fieldtype, const char *name ) { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::AddField : tried to add field to non-object type" ); + } + + parmTypes.Append( fieldtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; + + if ( fieldtype->FieldType()->Inherits( &type_object ) ) { + size += type_object.Size(); + } else { + size += fieldtype->FieldType()->Size(); + } +} + +/* +================ +idTypeDef::SetName +================ +*/ +void idTypeDef::SetName( const char *newname ) { + name = newname; +} + +/* +================ +idTypeDef::Name +================ +*/ +const char *idTypeDef::Name( void ) const { + return name; +} + +/* +================ +idTypeDef::Type +================ +*/ +etype_t idTypeDef::Type( void ) const { + return type; +} + +/* +================ +idTypeDef::Size +================ +*/ +int idTypeDef::Size( void ) const { + return size; +} + +/* +================ +idTypeDef::SuperClass + +If type is an object, then returns the object's superclass +================ +*/ +idTypeDef *idTypeDef::SuperClass( void ) const { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::SuperClass : tried to get superclass of a non-object type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::ReturnType + +If type is a function, then returns the function's return type +================ +*/ +idTypeDef *idTypeDef::ReturnType( void ) const { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::ReturnType: tried to get return type on non-function type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetReturnType + +If type is a function, then sets the function's return type +================ +*/ +void idTypeDef::SetReturnType( idTypeDef *returntype ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::SetReturnType: tried to set return type on non-function type" ); + } + + auxType = returntype; +} + +/* +================ +idTypeDef::FieldType + +If type is a field, then returns it's type +================ +*/ +idTypeDef *idTypeDef::FieldType( void ) const { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::FieldType: tried to get field type on non-field type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetFieldType + +If type is a field, then sets the function's return type +================ +*/ +void idTypeDef::SetFieldType( idTypeDef *fieldtype ) { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::SetFieldType: tried to set return type on non-function type" ); + } + + auxType = fieldtype; +} + +/* +================ +idTypeDef::PointerType + +If type is a pointer, then returns the type it points to +================ +*/ +idTypeDef *idTypeDef::PointerType( void ) const { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::PointerType: tried to get pointer type on non-pointer" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetPointerType + +If type is a pointer, then sets the pointer's type +================ +*/ +void idTypeDef::SetPointerType( idTypeDef *pointertype ) { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::SetPointerType: tried to set type on non-pointer" ); + } + + auxType = pointertype; +} + +/* +================ +idTypeDef::NumParameters +================ +*/ +int idTypeDef::NumParameters( void ) const { + return parmTypes.Num(); +} + +/* +================ +idTypeDef::GetParmType +================ +*/ +idTypeDef *idTypeDef::GetParmType( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmTypes[ parmNumber ]; +} + +/* +================ +idTypeDef::GetParmName +================ +*/ +const char *idTypeDef::GetParmName( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmNames[ parmNumber ]; +} + +/* +================ +idTypeDef::NumFunctions +================ +*/ +int idTypeDef::NumFunctions( void ) const { + return functions.Num(); +} + +/* +================ +idTypeDef::GetFunctionNumber +================ +*/ +int idTypeDef::GetFunctionNumber( const function_t *func ) const { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( functions[ i ] == func ) { + return i; + } + } + return -1; +} + +/* +================ +idTypeDef::GetFunction +================ +*/ +const function_t *idTypeDef::GetFunction( int funcNumber ) const { + assert( funcNumber >= 0 ); + assert( funcNumber < functions.Num() ); + return functions[ funcNumber ]; +} + +/* +================ +idTypeDef::AddFunction +================ +*/ +void idTypeDef::AddFunction( const function_t *func ) { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( !idStr::Cmp( functions[ i ]->def->Name(), func->def->Name() ) ) { + if ( func->def->TypeDef()->MatchesVirtualFunction( *functions[ i ]->def->TypeDef() ) ) { + functions[ i ] = func; + return; + } + } + } + functions.Append( func ); +} + +// RAVEN BEGIN +// abahr +/* +================ +rvTypeDefInt::Parse +================ +*/ +int rvTypeDefInt::Parse( const char* source ) const { + int i; + + sscanf( source, Format(), &i ); + + return i; +} + +/* +================ +rvTypeDefInt::GetReturnedValAsString +================ +*/ +const char* rvTypeDefInt::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedInteger() ); +} + +/* +================ +rvTypeDefInt::PushOntoStack +================ +*/ +void rvTypeDefInt::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushInt( Parse(source) ); +} + +/* +================ +rvTypeDefInt::IsValid +================ +*/ +bool rvTypeDefInt::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ + rvTypeDefFloat::Parse +================ +*/ +float rvTypeDefFloat::Parse( const char* source ) const { + float f; + + sscanf( source, Format(), &f ); + + return f; +} + +/* +================ +rvTypeDefFloat::GetReturnedValAsString +================ +*/ +const char* rvTypeDefFloat::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedFloat() ); +} + +/* +================ +rvTypeDefFloat::PushOntoStack +================ +*/ +void rvTypeDefFloat::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushFloat( Parse(source) ); +} + +/* +================ +rvTypeDefFloat::IsValid +================ +*/ +bool rvTypeDefFloat::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ +rvTypeDefVec3::Parse +================ +*/ +idVec3 rvTypeDefVec3::Parse( const char* source ) const { + idVec3 v; + + sscanf( source, Format(), &v[0], &v[1], &v[2] ); + + return v; +} + +/* +================ +rvTypeDefVec3::GetReturnedValAsString +================ +*/ +const char* rvTypeDefVec3::GetReturnedValAsString( idProgram& program ) { + idVec3 v( program.GetReturnedVec3() ); + return va( Format(), v[0], v[1], v[2] ); +} + +/* +================ +rvTypeDefVec3::PushOntoStack +================ +*/ +void rvTypeDefVec3::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushVec3( Parse(source) ); +} + +/* +================ +rvTypeDefVec3::IsValid +================ +*/ +bool rvTypeDefVec3::IsValid( const char* source ) const { + //Looking for two ' ' + return idStr::FindChar(source, ' ', idStr::FindChar(source, ' ') ) != -1; +} + +/* +================ +rvTypeDefEntity::Parse +================ +*/ +idEntity* rvTypeDefEntity::Parse( const char* source ) const { + return gameLocal.FindEntity( source ); +} + +/* +================ +rvTypeDefEntity::GetReturnedValAsString +================ +*/ + +const char* rvTypeDefEntity::GetReturnedValAsString( idProgram& program ) { + idEntity* entity = program.GetReturnedEntity(); + return (entity) ? entity->GetName() : ""; +} + +/* +================ +rvTypeDefEntity::PushOntoStack +================ +*/ +void rvTypeDefEntity::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushEntity( Parse(source) ); +} + +/* +================ +rvTypeDefEntity::IsValid +================ +*/ +bool rvTypeDefEntity::IsValid( const char* source ) const { + return Parse( source ) != NULL; +} + +/* +================ +rvTypeDefString::Parse +================ +*/ +const char* rvTypeDefString::Parse( const char* source ) const { + return source; +} + +/* +================ +rvTypeDefString::GetReturnedValAsString +================ +*/ +const char* rvTypeDefString::GetReturnedValAsString( idProgram& program ) { + return program.GetReturnedString(); +} + +/* +================ +rvTypeDefString::PushOntoStack +================ +*/ +void rvTypeDefString::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushString( Parse(source) ); +} + +/* +================ +vTypeDefString::IsValid +================ +*/ +bool rvTypeDefString::IsValid( const char* source ) const { + return true; +} + +/* +================ +rvTypeDefBool::Parse +================ +*/ +bool rvTypeDefBool::Parse( const char* source ) const { + unsigned int b; + + sscanf( source, Format(), &b ); + + return !!b; +} + +/* +================ +rvTypeDefBool::GetReturnedValAsString +================ +*/ +const char* rvTypeDefBool::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedBool() ); +} + +/* +================ +rvTypeDefBool::PushOntoStack +================ +*/ +void rvTypeDefBool::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushBool( Parse(source) ); +} + +/* +================ +rvTypeDefBool::IsValid +================ +*/ +bool rvTypeDefBool::IsValid( const char* source ) const { + return !idStr::Icmp(source, "true") || !idStr::Icmp(source, "false") || !idStr::Icmp(source, "1") || !idStr::Icmp(source, "0"); +} + +/* +================ +idProgram::GetReturnedEntity +================ +*/ +idEntity* idProgram::GetReturnedEntity() { + //This is here because gameLocal isn't known about yet in the header + int entityNumber = *returnDef->value.entityNumberPtr; + if( !entityNumber ) { + return NULL; + } + + return gameLocal.entities[ entityNumber - 1 ]; +} +// RAVEN END + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +================ +idVarDef::idVarDef() +================ +*/ +idVarDef::idVarDef( idTypeDef *typeptr ) { + typeDef = typeptr; + num = 0; + scope = NULL; + numUsers = 0; + initialized = idVarDef::uninitialized; + memset( &value, 0, sizeof( value ) ); + name = NULL; + next = NULL; +} + +/* +============ +idVarDef::~idVarDef +============ +*/ +idVarDef::~idVarDef() { + if ( name ) { + name->RemoveDef( this ); + } +} + +/* +============ +idVarDef::Name +============ +*/ +const char *idVarDef::Name( void ) const { + return name->Name(); +} + +/* +============ +idVarDef::GlobalName +============ +*/ +const char *idVarDef::GlobalName( void ) const { + if ( scope != &def_namespace ) { + return va( "%s::%s", scope->GlobalName(), name->Name() ); + } else { + return name->Name(); + } +} + +/* +============ +idVarDef::DepthOfScope +============ +*/ +int idVarDef::DepthOfScope( const idVarDef *otherScope ) const { + const idVarDef *def; + int depth; + + depth = 1; + for( def = otherScope; def != NULL; def = def->scope ) { + if ( def == scope ) { + return depth; + } + depth++; + } + + return 0; +} + +/* +============ +idVarDef::SetFunction +============ +*/ +void idVarDef::SetFunction( function_t *func ) { + assert( typeDef ); + initialized = initializedConstant; + assert( typeDef->Type() == ev_function ); + value.functionPtr = func; +} + +/* +============ +idVarDef::SetObject +============ +*/ +void idVarDef::SetObject( idScriptObject *object ) { + assert( typeDef ); + initialized = initialized; + assert( typeDef->Inherits( &type_object ) ); + *value.objectPtrPtr = object; +} + +/* +============ +idVarDef::SetValue +============ +*/ +void idVarDef::SetValue( const eval_t &_value, bool constant ) { + assert( typeDef ); + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + switch( typeDef->Type() ) { + case ev_pointer : + case ev_boolean : + case ev_field : + *value.intPtr = _value._int; + break; + + case ev_jumpoffset : + value.jumpOffset = _value._int; + break; + + case ev_argsize : + value.argSize = _value._int; + break; + + case ev_entity : + *value.entityNumberPtr = _value.entity; + break; + + case ev_string : + idStr::Copynz( value.stringPtr, _value.stringPtr, MAX_STRING_LEN ); + break; + + case ev_float : + *value.floatPtr = _value._float; + break; + + case ev_vector : + value.vectorPtr->x = _value.vector[ 0 ]; + value.vectorPtr->y = _value.vector[ 1 ]; + value.vectorPtr->z = _value.vector[ 2 ]; + break; + + case ev_function : + value.functionPtr = _value.function; + break; + + case ev_virtualfunction : + value.virtualFunction = _value._int; + break; + + case ev_object : + *value.entityNumberPtr = _value.entity; + break; + + default : + throw idCompileError( va( "weird type on '%s'", Name() ) ); + break; + } +} + +/* +============ +idVarDef::SetString +============ +*/ +void idVarDef::SetString( const char *string, bool constant ) { + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + assert( typeDef && ( typeDef->Type() == ev_string ) ); + idStr::Copynz( value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +============ +idVarDef::PrintInfo +============ +*/ +void idVarDef::PrintInfo( idFile *file, int instructionPointer ) const { + statement_t *jumpst; + int jumpto; + etype_t etype; + int i; + int len; + const char *ch; + + if ( initialized == initializedConstant ) { + file->Printf( "const " ); + } + + etype = typeDef->Type(); + switch( etype ) { + case ev_jumpoffset : + jumpto = instructionPointer + value.jumpOffset; + jumpst = &gameLocal.program.GetStatement( jumpto ); + file->Printf( "address %d [%s(%d)]", jumpto, gameLocal.program.GetFilename( jumpst->file ), jumpst->linenumber ); + break; + + case ev_function : + if ( value.functionPtr->eventdef ) { + file->Printf( "event %s", GlobalName() ); + } else { + file->Printf( "function %s", GlobalName() ); + } + break; + + case ev_field : + file->Printf( "field %d", value.ptrOffset ); + break; + + case ev_argsize: + file->Printf( "args %d", value.argSize ); + break; + + default: + file->Printf( "%s ", typeDef->Name() ); + if ( initialized == initializedConstant ) { + switch( etype ) { + case ev_string : + file->Printf( "\"" ); + len = strlen( value.stringPtr ); + ch = value.stringPtr; + for( i = 0; i < len; i++, ch++ ) { + if ( idStr::CharIsPrintable( *ch ) ) { + file->Printf( "%c", *ch ); + } else if ( *ch == '\n' ) { + file->Printf( "\\n" ); + } else { + file->Printf( "\\x%.2x", static_cast( *ch ) ); + } + } + file->Printf( "\"" ); + break; + + case ev_vector : + file->Printf( "'%s'", value.vectorPtr->ToString() ); + break; + + case ev_float : + file->Printf( "%f", *value.floatPtr ); + break; + + case ev_virtualfunction : + file->Printf( "vtable[ %d ]", value.virtualFunction ); + break; + + default : + file->Printf( "%d", *value.intPtr ); + break; + } + } else if ( initialized == stackVariable ) { + file->Printf( "stack[%d]", value.stackOffset ); + } else { + file->Printf( "global[%d]", num ); + } + break; + } +} + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +============ +idVarDefName::AddDef +============ +*/ +void idVarDefName::AddDef( idVarDef *def ) { + assert( def->next == NULL ); + def->name = this; + def->next = defs; + defs = def; +} + +/* +============ +idVarDefName::RemoveDef +============ +*/ +void idVarDefName::RemoveDef( idVarDef *def ) { + if ( defs == def ) { + defs = def->next; + } else { + for ( idVarDef *d = defs; d->next != NULL; d = d->next ) { + if ( d->next == def ) { + d->next = def->next; + break; + } + } + } + def->next = NULL; + def->name = NULL; +} + +/*********************************************************************** + + idScriptObject + +***********************************************************************/ + +/* +============ +idScriptObject::idScriptObject +============ +*/ +idScriptObject::idScriptObject() { + data = NULL; + type = &type_object; +} + +/* +============ +idScriptObject::~idScriptObject +============ +*/ +idScriptObject::~idScriptObject() { + Free(); +} + +/* +============ +idScriptObject::Free +============ +*/ +void idScriptObject::Free( void ) { + if ( data ) { + Mem_Free( data ); + } + + data = NULL; + type = &type_object; +} + +/* +================ +idScriptObject::Save +================ +*/ +void idScriptObject::Save( idSaveGame *savefile ) const { + size_t size; + + if ( type == &type_object && data == NULL ) { + // Write empty string for uninitialized object + savefile->WriteString( "" ); + } else { + savefile->WriteString( type->Name() ); + size = type->Size(); + savefile->WriteInt( size ); + savefile->Write( data, size ); + } +} + +/* +================ +idScriptObject::Restore +================ +*/ +void idScriptObject::Restore( idRestoreGame *savefile ) { + idStr typeName; + size_t size; + + savefile->ReadString( typeName ); + + // Empty string signals uninitialized object + if ( typeName.Length() == 0 ) { + return; + } + + if ( !SetType( typeName ) ) { + savefile->Error( "idScriptObject::Restore: failed to restore object of type '%s'.", typeName.c_str() ); + } + + savefile->ReadInt( (int &)size ); + if ( size != type->Size() ) { + savefile->Error( "idScriptObject::Restore: size of object '%s' doesn't match size in save game.", typeName.c_str() ); + } + + savefile->Read( data, size ); +} + +/* +============ +idScriptObject::SetType + +Allocates an object and initializes memory. +============ +*/ +bool idScriptObject::SetType( const char *typeName ) { + size_t size; + idTypeDef *newtype; + + // lookup the type + newtype = gameLocal.program.FindType( typeName ); + + // only allocate memory if the object type changes + if ( newtype != type ) { + Free(); + if ( !newtype ) { + gameLocal.Warning( "idScriptObject::SetType: Unknown type '%s'", typeName ); + return false; + } + + if ( !newtype->Inherits( &type_object ) ) { + gameLocal.Warning( "idScriptObject::SetType: Can't create object of type '%s'. Must be an object type.", newtype->Name() ); + return false; + } + + // set the type + type = newtype; + + // allocate the memory + size = type->Size(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + data = ( byte * )Mem_Alloc( size, MA_SCRIPT ); +//RAVEN END + } + + // init object memory + ClearObject(); + + return true; +} + +/* +============ +idScriptObject::ClearObject + +Resets the memory for the script object without changing its type. +============ +*/ +void idScriptObject::ClearObject( void ) { + size_t size; + + if ( type != &type_object ) { + // init object memory + size = type->Size(); + memset( data, 0, size ); + } +} + +/* +============ +idScriptObject::HasObject +============ +*/ +bool idScriptObject::HasObject( void ) const { + return ( type != &type_object ); +} + +/* +============ +idScriptObject::GetTypeDef +============ +*/ +idTypeDef *idScriptObject::GetTypeDef( void ) const { + return type; +} + +/* +============ +idScriptObject::GetTypeName +============ +*/ +const char *idScriptObject::GetTypeName( void ) const { + return type->Name(); +} + +/* +============ +idScriptObject::GetConstructor +============ +*/ +const function_t *idScriptObject::GetConstructor( void ) const { + const function_t *func; + + func = GetFunction( "init" ); + return func; +} + +/* +============ +idScriptObject::GetDestructor +============ +*/ +const function_t *idScriptObject::GetDestructor( void ) const { + const function_t *func; + + func = GetFunction( "destroy" ); + return func; +} + +/* +============ +idScriptObject::GetFunction +============ +*/ +const function_t *idScriptObject::GetFunction( const char *name ) const { + const function_t *func; + + if ( type == &type_object ) { + return NULL; + } + + func = gameLocal.program.FindFunction( name, type ); + return func; +} + +/* +============ +idScriptObject::GetVariable +============ +*/ +byte *idScriptObject::GetVariable( const char *name, etype_t etype ) const { + int i; + int pos; + const idTypeDef *t; + const idTypeDef *parm; + + if ( type == &type_object ) { + return NULL; + } + + t = type; + do { + if ( t->SuperClass() != &type_object ) { + pos = t->SuperClass()->Size(); + } else { + pos = 0; + } + for( i = 0; i < t->NumParameters(); i++ ) { + parm = t->GetParmType( i ); + if ( !idStr::Cmp( t->GetParmName( i ), name ) ) { + if ( etype != parm->FieldType()->Type() ) { + return NULL; + } + return &data[ pos ]; + } + + if ( parm->FieldType()->Inherits( &type_object ) ) { + pos += type_object.Size(); + } else { + pos += parm->FieldType()->Size(); + } + } + t = t->SuperClass(); + } while( t && ( t != &type_object ) ); + + return NULL; +} + +/*********************************************************************** + + idProgram + +***********************************************************************/ + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( idTypeDef &type ) { + idTypeDef *newtype; + + newtype = new idTypeDef( type ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + idTypeDef *newtype; + + newtype = new idTypeDef( etype, edef, ename, esize, aux ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::GetType + +Returns a preexisting complex type that matches the parm, or allocates +a new one and copies it out. +============ +*/ +idTypeDef *idProgram::GetType( idTypeDef &type, bool allocate ) { + int i; + + //FIXME: linear search == slow + for( i = types.Num() - 1; i >= 0; i-- ) { + if ( types[ i ]->MatchesType( type ) && !idStr::Cmp( types[ i ]->Name(), type.Name() ) ) { + return types[ i ]; + } + } + + if ( !allocate ) { + return NULL; + } + + // allocate a new one + return AllocType( type ); +} + +/* +============ +idProgram::FindType + +Returns a preexisting complex type that matches the name, or returns NULL if not found +============ +*/ +idTypeDef *idProgram::FindType( const char *name ) { + idTypeDef *check; + int i; + + for( i = types.Num() - 1; i >= 0; i-- ) { + check = types[ i ]; + if ( !idStr::Cmp( check->Name(), name ) ) { + return check; + } + } + + return NULL; +} + +/* +============ +idProgram::GetDefList +============ +*/ +idVarDef *idProgram::GetDefList( const char *name ) const { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + return varDefNames[i]->GetDefs(); + } + } + return NULL; +} + +/* +============ +idProgram::AddDefToNameList +============ +*/ +void idProgram::AddDefToNameList( idVarDef *def, const char *name ) { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + break; + } + } + if ( i == -1 ) { + i = varDefNames.Append( new idVarDefName( name ) ); + varDefNameHash.Add( hash, i ); + } + varDefNames[i]->AddDef( def ); +} + +/* +============ +idProgram::AllocDef +============ +*/ +idVarDef *idProgram::AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ) { + idVarDef *def; + idStr element; + idVarDef *def_x; + idVarDef *def_y; + idVarDef *def_z; + + // allocate a new def + def = new idVarDef( type ); + def->scope = scope; + def->numUsers = 1; + def->num = varDefs.Append( def ); + + // add the def to the list with defs with this name and set the name pointer + AddDefToNameList( def, name ); + + if ( ( type->Type() == ev_vector ) || ( ( type->Type() == ev_field ) && ( type->FieldType()->Type() == ev_vector ) ) ) { + // + // vector + // + if ( !idStr::Cmp( name, RESULT_STRING ) ) { + // vector defs don't need the _x, _y and _z components + assert( scope->Type() == ev_function ); + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + scope->value.functionPtr->locals += type->Size(); + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + idTypeDef newtype( ev_field, NULL, "float field", 0, &type_float ); + idTypeDef *type = GetType( newtype, true ); + + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( type, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( type, element, scope, constant ); + def_y->value.ptrOffset = def_x->value.ptrOffset + type_float.Size(); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( type, element, scope, constant ); + def_z->value.ptrOffset = def_y->value.ptrOffset + type_float.Size(); + } else { + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( &type_float, element, scope, constant ); + + // point the vector def to the x coordinate + def->value = def_x->value; + def->initialized = def_x->initialized; + } + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + // + // object variable + // + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + } else if ( scope->Type() == ev_function ) { + // + // stack variable + // + // since we don't know how many local variables there are, + // we have to have them go backwards on the stack + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + + if ( type->Inherits( &type_object ) ) { + // objects only have their entity number on the stack, not the entire object + scope->value.functionPtr->locals += type_object.Size(); + } else { + scope->value.functionPtr->locals += type->Size(); + } + } else { + // + // global variable + // + def->value.bytePtr = &variables[ numVariables ]; + numVariables += def->TypeDef()->Size(); + if ( numVariables > sizeof( variables ) ) { + throw idCompileError( va( "Exceeded global memory size (%d bytes)", sizeof( variables ) ) ); + } + + memset( def->value.bytePtr, 0, def->TypeDef()->Size() ); + } + + return def; +} + +/* +============ +idProgram::GetDef + +If type is NULL, it will match any type +============ +*/ +idVarDef *idProgram::GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const { + idVarDef *def; + idVarDef *bestDef; + int bestDepth; + int depth; + + bestDepth = 0; + bestDef = NULL; + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def->scope->Type() == ev_namespace ) { + depth = def->DepthOfScope( scope ); + if ( !depth ) { + // not in the same namespace + continue; + } + } else if ( def->scope != scope ) { + // in a different function + continue; + } else { + depth = 1; + } + + if ( !bestDef || ( depth < bestDepth ) ) { + bestDepth = depth; + bestDef = def; + } + } + + // see if the name is already in use for another type + if ( bestDef && type && ( bestDef->TypeDef() != type ) ) { + throw idCompileError( va( "Type mismatch on redeclaration of %s", name ) ); + } + + return bestDef; +} + +/* +============ +idProgram::FreeDef +============ +*/ +void idProgram::FreeDef( idVarDef *def, const idVarDef *scope ) { + idVarDef *e; + int i; + + if ( def->Type() == ev_vector ) { + idStr name; + + sprintf( name, "%s_x", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_y", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_z", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + } + + varDefs.RemoveIndex( def->num ); + for( i = def->num; i < varDefs.Num(); i++ ) { + varDefs[ i ]->num = i; + } + + delete def; +} + +/* +============ +idProgram::FindFreeResultDef +============ +*/ +idVarDef *idProgram::FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ) { + idVarDef *def; + + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def == a || def == b ) { + continue; + } + if ( def->TypeDef() != type ) { + continue; + } + if ( def->scope != scope ) { + continue; + } + if ( def->numUsers <= 1 ) { + continue; + } + return def; + } + + return AllocDef( type, name, scope, false ); +} + +/* +================ +idProgram::FindFunction + +Searches for the specified function in the currently loaded script. A full namespace should be +specified if not in the global namespace. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name ) const { + int start; + int pos; + idVarDef *namespaceDef; + idVarDef *def; + + assert( name ); + + idStr fullname = name; + start = 0; + namespaceDef = &def_namespace; + do { + pos = fullname.Find( "::", true, start ); + if ( pos < 0 ) { + break; + } + + idStr namespaceName = fullname.Mid( start, pos - start ); + def = GetDef( NULL, namespaceName, namespaceDef ); + if ( !def ) { + // couldn't find namespace + return NULL; + } + namespaceDef = def; + + // skip past the :: + start = pos + 2; + } while( def->Type() == ev_namespace ); + + idStr funcName = fullname.Right( fullname.Length() - start ); + def = GetDef( NULL, funcName, namespaceDef ); + if ( !def ) { + // couldn't find function + return NULL; + } + + if ( ( def->Type() == ev_function ) && ( def->value.functionPtr->eventdef == NULL ) ) { + return def->value.functionPtr; + } + + // is not a function, or is an eventdef + return NULL; +} + +// RAVEN BEGIN +// bgeisler: list functions +/* +================ +idProgram::ListFunctions +================ +*/ +void idProgram::ListStates( void ) +{ + gameLocal.Printf( "Script States: \n"); + // function 0 is a NULL function + for( int i = 1; i < functions.Num(); i++ ) + { + gameLocal.Printf( "%s \n", functions[ i ].Name() ); + } + +} +// RAVEN END + +/* +================ +idProgram::FindFunction + +Searches for the specified object function in the currently loaded script. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name, const idTypeDef *type ) const { + const idVarDef *tdef; + const idVarDef *def; + + // look for the function + def = NULL; + for( tdef = type->def; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = GetDef( NULL, name, tdef ); + if ( def ) { + return def->value.functionPtr; + } + } + + return NULL; +} + +/* +================ +idProgram::AllocFunction +================ +*/ +function_t &idProgram::AllocFunction( idVarDef *def ) { + if ( functions.Num() >= functions.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of functions (%d)", functions.Max() ) ); + } + + // fill in the dfunction + function_t &func = *functions.Alloc(); + func.eventdef = NULL; + func.def = def; + func.type = def->TypeDef(); + func.firstStatement = 0; + func.numStatements = 0; + func.parmTotal = 0; + func.locals = 0; + func.filenum = filenum; + func.parmSize.SetGranularity( 1 ); + func.SetName( def->GlobalName() ); + + def->SetFunction( &func ); + + return func; +} + +/* +================ +idProgram::SetEntity +================ +*/ +void idProgram::SetEntity( const char *name, idEntity *ent ) { + idVarDef *def; + idStr defName( "$" ); + + defName += name; + + def = GetDef( &type_entity, defName, &def_namespace ); + if ( def && ( def->initialized != idVarDef::stackVariable ) ) { + // 0 is reserved for NULL entity + if ( !ent ) { + *def->value.entityNumberPtr = 0; + } else { + *def->value.entityNumberPtr = ent->entityNumber + 1; + } + } +} + +/* +================ +idProgram::AllocStatement +================ +*/ +statement_t *idProgram::AllocStatement( void ) { + if ( statements.Num() >= statements.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of statements (%d)", statements.Max() ) ); + } + return statements.Alloc(); +} + +/* +============== +idProgram::BeginCompilation + +called before compiling a batch of files, clears the pr struct +============== +*/ +void idProgram::BeginCompilation( void ) { + statement_t *statement; + + FreeData(); + + try { + // make the first statement a return for a "NULL" function + statement = AllocStatement(); + statement->linenumber = 0; + statement->file = 0; + statement->op = OP_RETURN; + statement->a = NULL; + statement->b = NULL; + statement->c = NULL; + + // define NULL + //AllocDef( &type_void, "", &def_namespace, true ); + + // define the return def + returnDef = AllocDef( &type_vector, "", &def_namespace, false ); + + // define the return def for strings + returnStringDef = AllocDef( &type_string, "", &def_namespace, false ); + + // define the sys object + sysDef = AllocDef( &type_void, "sys", &def_namespace, true ); + } + + catch( idCompileError &err ) { + gameLocal.Error( "%s", err.error ); + } +} + +/* +============== +idProgram::DisassembleStatement +============== +*/ +void idProgram::DisassembleStatement( idFile *file, int instructionPointer ) const { + opcode_t *op; + const statement_t *statement; + + statement = &statements[ instructionPointer ]; + op = &idCompiler::opcodes[ statement->op ]; + file->Printf( "%20s(%d):\t%6d: %15s\t", fileList[ statement->file ].c_str(), statement->linenumber, instructionPointer, op->opname ); + + if ( statement->a ) { + file->Printf( "\ta: " ); + statement->a->PrintInfo( file, instructionPointer ); + } + + if ( statement->b ) { + file->Printf( "\tb: " ); + statement->b->PrintInfo( file, instructionPointer ); + } + + if ( statement->c ) { + file->Printf( "\tc: " ); + statement->c->PrintInfo( file, instructionPointer ); + } + + file->Printf( "\n" ); +} + +/* +============== +idProgram::Disassemble +============== +*/ +void idProgram::Disassemble( void ) const { + int i; + int instructionPointer; + const function_t *func; + idFile *file; + + file = fileSystem->OpenFileByMode( "script/disasm.txt", FS_WRITE ); + + for( i = 0; i < functions.Num(); i++ ) { + func = &functions[ i ]; + if ( func->eventdef ) { + // skip eventdefs + continue; + } + + file->Printf( "\nfunction %s() %d stack used, %d parms, %d locals {\n", func->Name(), func->locals, func->parmTotal, func->locals - func->parmTotal ); + + for( instructionPointer = 0; instructionPointer < func->numStatements; instructionPointer++ ) { + DisassembleStatement( file, func->firstStatement + instructionPointer ); + } + + file->Printf( "}\n" ); + } + + fileSystem->CloseFile( file ); +} + +/* +============== +idProgram::FinishCompilation + +Called after all files are compiled to check for errors +============== +*/ +void idProgram::FinishCompilation( void ) { + int i; + + top_functions = functions.Num(); + top_statements = statements.Num(); + top_types = types.Num(); + top_defs = varDefs.Num(); + top_files = fileList.Num(); + + variableDefaults.Clear(); + variableDefaults.SetNum( numVariables ); + + for( i = 0; i < numVariables; i++ ) { + variableDefaults[ i ] = variables[ i ]; + } +} + +/* +============== +idProgram::CompileStats + +called after all files are compiled to report memory usage. +============== +*/ +void idProgram::CompileStats( void ) { + int memused; + int memallocated; + int numdefs; + int stringspace; + int funcMem; + int i; + + gameLocal.Printf( "-------------- Compile stats ----------------\n" ); + gameLocal.DPrintf( "Files loaded:\n" ); + + stringspace = 0; + for( i = 0; i < fileList.Num(); i++ ) { + gameLocal.DPrintf( " %s\n", fileList[ i ].c_str() ); + stringspace += fileList[ i ].Allocated(); + } + stringspace += fileList.Size(); + + numdefs = varDefs.Num(); + memused = varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + memused += stringspace; + + for( i = 0; i < types.Num(); i++ ) { + memused += types[ i ]->Allocated(); + } + + funcMem = functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + funcMem += functions[ i ].Allocated(); + } + + memallocated = funcMem + memused + sizeof( idProgram ); + + memused += statements.MemoryUsed(); + memused += functions.MemoryUsed(); // name and filename of functions are shared, so no need to include them + memused += sizeof( variables ); + + gameLocal.Printf( "\nMemory usage:\n" ); + gameLocal.Printf( " Strings: %d, %d bytes\n", fileList.Num(), stringspace ); + gameLocal.Printf( " Statements: %d, %d bytes\n", statements.Num(), statements.MemoryUsed() ); + gameLocal.Printf( " Functions: %d, %d bytes\n", functions.Num(), funcMem ); + gameLocal.Printf( " Variables: %d bytes\n", numVariables ); + gameLocal.Printf( " Mem used: %d bytes\n", memused ); + gameLocal.Printf( " Static data: %d bytes\n", sizeof( idProgram ) ); + gameLocal.Printf( " Allocated: %d bytes\n", memallocated ); + gameLocal.Printf( " Thread size: %d bytes\n\n", sizeof( idThread ) ); +} + +// RAVEN BEGIN +// jscott: summary of script memory usage +/* +================ +idProgram::ScriptSummary +================ +*/ +size_t idProgram::ScriptSummary( const idCmdArgs &args ) { + + int memused; + int i; + + memused = 0; + for( i = 0; i < fileList.Num(); i++ ) { + + memused += fileList[i].Allocated(); + } + memused += fileList.Size(); + + memused += varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + + for( i = 0; i < types.Num(); i++ ) { + + memused += types[i]->Allocated(); + } + + memused += functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + + memused += functions[i].Allocated(); + } + + memused += statements.MemoryUsed(); + memused += sizeof( variables ); + memused += sizeof( idProgram ); + memused += sizeof( idThread ); + + common->Printf( "Scripts - %dK\n", memused >> 10 ); + + return( memused >> 10 ); +} +// RAVEN END + +/* +================ +idProgram::CompileText +================ +*/ +bool idProgram::CompileText( const char *source, const char *text, bool console ) { + idCompiler compiler; + int i; + idVarDef *def; + idStr ospath; + +// RAVEN BEGIN +// bdube: Make sure the file hasnt already been loaded + if ( -1 != fileList.FindIndex ( idStr(source) ) ) { + return true; + } +// RAVEN END + + // use a full os path for GetFilenum since it calls OSPathToRelativePath to convert filenames from the parser + ospath = fileSystem->RelativePathToOSPath( source ); + filenum = GetFilenum( ospath ); + + try { + compiler.CompileFile( text, filename, console ); + + // check to make sure all functions prototyped have code + for( i = 0; i < varDefs.Num(); i++ ) { + def = varDefs[ i ]; + if ( ( def->Type() == ev_function ) && ( ( def->scope->Type() == ev_namespace ) || def->scope->TypeDef()->Inherits( &type_object ) ) ) { + if ( !def->value.functionPtr->eventdef && !def->value.functionPtr->firstStatement ) { + throw idCompileError( va( "function %s was not defined\n", def->GlobalName() ) ); + } + } + } + } + + catch( idCompileError &err ) { + if ( console ) { + gameLocal.Printf( "%s\n", err.error ); + return false; + } else { + gameLocal.Error( "%s\n", err.error ); + } + }; + + if ( !console ) { + CompileStats(); + } + + return true; +} + +/* +================ +idProgram::CompileFunction +================ +*/ +const function_t *idProgram::CompileFunction( const char *functionName, const char *text ) { + bool result; + + result = CompileText( functionName, text, false ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed." ); + } + + return FindFunction( functionName ); +} + +/* +================ +idProgram::CompileFile +================ +*/ +void idProgram::CompileFile( const char *filename ) { + char *src; + bool result; + + if ( fileSystem->ReadFile( filename, ( void ** )&src, NULL ) < 0 ) { + gameLocal.Error( "Couldn't load %s\n", filename ); + } + + result = CompileText( filename, src, false ); + + fileSystem->FreeFile( src ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed in file %s.", filename ); + } +} + +/* +================ +idProgram::FreeData +================ +*/ +void idProgram::FreeData( void ) { + int i; + + // free the defs + varDefs.DeleteContents( true ); + varDefNames.DeleteContents( true ); + varDefNameHash.Free(); + + returnDef = NULL; + returnStringDef = NULL; + sysDef = NULL; + + // free any special types we've created + types.DeleteContents( true ); + + filenum = 0; + + numVariables = 0; + memset( variables, 0, sizeof( variables ) ); + + // clear all the strings in the functions so that it doesn't look like we're leaking memory. + for( i = 0; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + + filename.Clear(); + fileList.Clear(); + statements.Clear(); + functions.Clear(); + + top_functions = 0; + top_statements = 0; + top_types = 0; + top_defs = 0; + top_files = 0; + + filename = ""; +} + +/* +================ +idProgram::Startup +================ +*/ +void idProgram::Startup( const char *defaultScript ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag, MA_SCRIPT); +// RAVEN END + gameLocal.Printf( "Initializing scripts\n" ); + + // make sure all data is freed up + idThread::Restart(); + + // get ready for loading scripts + BeginCompilation(); + + // load the default script + if ( defaultScript && *defaultScript ) { + CompileFile( defaultScript ); + } + + FinishCompilation(); +} + +/* +================ +idProgram::Save +================ +*/ +void idProgram::Save( idSaveGame *savefile ) const { + int i; + int currentFileNum = top_files; + + savefile->WriteInt( (fileList.Num() - currentFileNum) ); + while ( currentFileNum < fileList.Num() ) { + savefile->WriteString( fileList[ currentFileNum ] ); + currentFileNum++; + } + savefile->WriteString( filename ); // cnicholson: Added unsaved var + savefile->WriteInt ( filenum ); // cnicholson: Added unsaved var + + for ( i = 0; i < variableDefaults.Num(); i++ ) { + if ( variables[i] != variableDefaults[i] ) { + savefile->WriteInt( i ); + savefile->WriteByte( variables[i] ); + } + } + // Mark the end of the diff with default variables with -1 + savefile->WriteInt( -1 ); + + savefile->WriteInt( numVariables ); + for ( i = variableDefaults.Num(); i < numVariables; i++ ) { + savefile->WriteByte( variables[i] ); + } + + savefile->WriteInt ( top_functions ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_statements ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_types ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_defs ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_files ); // cnicholson: Added unsaved var + + int checksum = CalculateChecksum(); + savefile->WriteInt( checksum ); +} + +/* +================ +idProgram::Restore +================ +*/ +bool idProgram::Restore( idRestoreGame *savefile ) { + int i, num, index; + bool result = true; + idStr scriptname; + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( scriptname ); + CompileFile( scriptname ); + } + + savefile->ReadString( filename ); // cnicholson: Added unrestored var + savefile->ReadInt ( filenum ); // cnicholson: Added unrestored var + + savefile->ReadInt( index ); + while( index >= 0 ) { + savefile->ReadByte( variables[index] ); + savefile->ReadInt( index ); + } + + savefile->ReadInt( num ); + for ( i = variableDefaults.Num(); i < num; i++ ) { + savefile->ReadByte( variables[i] ); + } + + savefile->ReadInt ( top_functions ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_statements ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_types ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_defs ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_files ); // cnicholson: Added unrestored var + + int saved_checksum, checksum; + + savefile->ReadInt( saved_checksum ); + checksum = CalculateChecksum(); + + if ( saved_checksum != checksum ) { + result = false; + } + + return result; +} + +/* +================ +idProgram::CalculateChecksum +================ +*/ +int idProgram::CalculateChecksum( void ) const { + int i, result; + + typedef struct { + unsigned short op; + int a; + int b; + int c; + unsigned short linenumber; + unsigned short file; + } statementBlock_t; + + statementBlock_t *statementList = new statementBlock_t[ statements.Num() ]; + + memset( statementList, 0, ( sizeof(statementBlock_t) * statements.Num() ) ); + + // Copy info into new list, using the variable numbers instead of a pointer to the variable + for( i = 0; i < statements.Num(); i++ ) { + statementList[i].op = statements[i].op; + + if ( statements[i].a ) { + statementList[i].a = statements[i].a->num; + } else { + statementList[i].a = -1; + } + if ( statements[i].b ) { + statementList[i].b = statements[i].b->num; + } else { + statementList[i].b = -1; + } + if ( statements[i].c ) { + statementList[i].c = statements[i].c->num; + } else { + statementList[i].c = -1; + } + + statementList[i].linenumber = statements[i].linenumber; + statementList[i].file = statements[i].file; + } + + result = MD4_BlockChecksum( statementList, ( sizeof(statementBlock_t) * statements.Num() ) ); + + delete [] statementList; + + return result; +} + +/* +============== +idProgram::Restart + +Restores all variables to their initial value +============== +*/ +void idProgram::Restart( void ) { + int i; + + idThread::Restart(); + + // + // since there may have been a script loaded by the map or the user may + // have typed "script" from the console, free up any types and vardefs that + // have been allocated after the initial startup + // + for( i = top_types; i < types.Num(); i++ ) { + delete types[ i ]; + } + types.SetNum( top_types, false ); + + for( i = top_defs; i < varDefs.Num(); i++ ) { + delete varDefs[ i ]; + } + varDefs.SetNum( top_defs, false ); + + for( i = top_functions; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + functions.SetNum( top_functions ); + + statements.SetNum( top_statements ); + fileList.SetNum( top_files, false ); + filename.Clear(); + + // reset the variables to their default values + numVariables = variableDefaults.Num(); + for( i = 0; i < numVariables; i++ ) { + variables[ i ] = variableDefaults[ i ]; + } +} + +/* +================ +idProgram::GetFilenum +================ +*/ +int idProgram::GetFilenum( const char *name ) { + if ( filename == name ) { + return filenum; + } + + idStr strippedName; + strippedName = fileSystem->OSPathToRelativePath( name ); + if ( !strippedName.Length() ) { + // not off the base path so just use the full path + filenum = fileList.AddUnique( name ); + } else { + filenum = fileList.AddUnique( strippedName ); + } + + // save the unstripped name so that we don't have to strip the incoming name every time we call GetFilenum + filename = name; + + return filenum; +} + +/* +================ +idProgram::idProgram +================ +*/ +idProgram::idProgram() { + FreeData(); +} + +/* +================ +idProgram::~idProgram +================ +*/ +idProgram::~idProgram() { + FreeData(); +} + +// RAVEN BEGIN +// jscott: for debug with inlines and memory log +/* +================ +idProgram::Shutdown +================ +*/ +void idProgram::Shutdown( void ) +{ + FreeData(); +} +// RAVEN END + +/* +================ +idProgram::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idProgram::ReturnEntity( const idEntity *ent ) { + if ( ent ) { + *returnDef->value.entityNumberPtr = ent->entityNumber + 1; + } else { + *returnDef->value.entityNumberPtr = 0; + } +} + diff --git a/src/game/script/Script_Program.h b/src/game/script/Script_Program.h new file mode 100644 index 0000000..0931cf2 --- /dev/null +++ b/src/game/script/Script_Program.h @@ -0,0 +1,820 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_PROGRAM_H__ +#define __SCRIPT_PROGRAM_H__ + +class idScriptObject; +class idEventDef; +class idVarDef; +class idTypeDef; +class idEntity; +class idThread; +class idSaveGame; +class idRestoreGame; + +#define MAX_STRING_LEN 128 +// RAVEN BEGIN +// ddynerman: higher max limit, initially 196608 +// jshepard: ... then 393216 +#define MAX_GLOBALS 589824 // in bytes + +// jshepard: raise the limits. Formerly 1024, 3072 and 81920. +#define MAX_STRINGS 2048 +#define MAX_FUNCS 6144 +#define MAX_STATEMENTS 163840 // statement_t - 18 bytes last I checked +// RAVEN END +typedef enum { + ev_error = -1, ev_void, ev_scriptevent, ev_namespace, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_virtualfunction, ev_pointer, ev_object, ev_jumpoffset, ev_argsize, ev_boolean +} etype_t; + +class function_t { +public: + function_t(); + + size_t Allocated( void ) const; + void SetName( const char *name ); + const char *Name( void ) const; + void Clear( void ); + +private: + idStr name; +public: + const idEventDef *eventdef; + idVarDef *def; + const idTypeDef *type; + int firstStatement; + int numStatements; + int parmTotal; + int locals; // total ints of parms + locals + int filenum; // source file defined in + idList parmSize; +}; + +typedef union eval_s { + const char *stringPtr; + float _float; + float vector[ 3 ]; + function_t *function; + int _int; + int entity; +} eval_t; + +/*********************************************************************** + +idTypeDef + +Contains type information for variables and functions. + +***********************************************************************/ + +class idTypeDef { +private: + etype_t type; + idStr name; + int size; + + // function types are more complex + idTypeDef *auxType; // return type + idList parmTypes; + idStrList parmNames; + idList functions; + +public: + idVarDef *def; // a def that points to this type + + idTypeDef( const idTypeDef &other ); + idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + virtual ~idTypeDef( void ) { } + void operator=( const idTypeDef& other ); + size_t Allocated( void ) const; + + bool Inherits( const idTypeDef *basetype ) const; + bool MatchesType( const idTypeDef &matchtype ) const; + bool MatchesVirtualFunction( const idTypeDef &matchfunc ) const; + void AddFunctionParm( idTypeDef *parmtype, const char *name ); + void AddField( idTypeDef *fieldtype, const char *name ); + + void SetName( const char *newname ); + const char *Name( void ) const; + + etype_t Type( void ) const; + int Size( void ) const; + + idTypeDef *SuperClass( void ) const; + + idTypeDef *ReturnType( void ) const; + void SetReturnType( idTypeDef *type ); + + idTypeDef *FieldType( void ) const; + void SetFieldType( idTypeDef *type ); + + idTypeDef *PointerType( void ) const; + void SetPointerType( idTypeDef *type ); + + int NumParameters( void ) const; + idTypeDef *GetParmType( int parmNumber ) const; + const char *GetParmName( int parmNumber ) const; + + int NumFunctions( void ) const; + int GetFunctionNumber( const function_t *func ) const; + const function_t *GetFunction( int funcNumber ) const; + void AddFunction( const function_t *func ); + +// RAVEN BEGIN + // abahr + virtual const char* GetReturnedValAsString( idProgram& program ) { return ""; } + virtual void PushOntoStack( idThread* thread, const char* source ) { assert(0); } + virtual bool IsValid( const char* source ) const { return true; } + virtual const char* Format() const { return ""; } +// RAVEN END +}; + +// RAVEN BEGIN +// abahr: subclasses for overwritting helper functions + +/*********************************************************************** + +rvTypeDefInt + +***********************************************************************/ +class rvTypeDefInt : public idTypeDef { +public: + rvTypeDefInt( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefInt( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%d"; } + int Parse( const char* source ) const; + +}; + +/*********************************************************************** + +rvTypeDefFloat + +***********************************************************************/ +class rvTypeDefFloat : public idTypeDef { +public: + rvTypeDefFloat( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefFloat( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f"; } + float Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefVec3 + +***********************************************************************/ +class rvTypeDefVec3 : public idTypeDef { +public: + rvTypeDefVec3( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefVec3( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f %f %f"; } + idVec3 Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefEntity + +***********************************************************************/ +class rvTypeDefEntity : public idTypeDef { +public: + rvTypeDefEntity( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefEntity( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + idEntity* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefString + +***********************************************************************/ +class rvTypeDefString : public idTypeDef { +public: + rvTypeDefString( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefString( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefBool + +***********************************************************************/ +class rvTypeDefBool : public idTypeDef { +public: + rvTypeDefBool( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefBool( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%u"; } + bool Parse( const char* source ) const; +}; +// RAVEN END + +/*********************************************************************** + +idScriptObject + +In-game representation of objects in scripts. Use the idScriptVariable template +(below) to access variables. + +***********************************************************************/ + +class idScriptObject { +private: + idTypeDef *type; + +public: + byte *data; + + idScriptObject(); + ~idScriptObject(); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Free( void ); + bool SetType( const char *typeName ); + void ClearObject( void ); + bool HasObject( void ) const; + idTypeDef *GetTypeDef( void ) const; + const char *GetTypeName( void ) const; + const function_t *GetConstructor( void ) const; + const function_t *GetDestructor( void ) const; + const function_t *GetFunction( const char *name ) const; + + byte *GetVariable( const char *name, etype_t etype ) const; +}; + +/*********************************************************************** + +idScriptVariable + +Helper template that handles looking up script variables stored in objects. +If the specified variable doesn't exist, or is the wrong data type, idScriptVariable +will cause an error. + +***********************************************************************/ + +template +class idScriptVariable { +private: + type *data; + +public: + idScriptVariable(); + bool IsLinked( void ) const; + void Unlink( void ); + void LinkTo( idScriptObject &obj, const char *name ); + idScriptVariable &operator=( const returnType &value ); + operator returnType() const; +}; + +template +ID_INLINE idScriptVariable::idScriptVariable() { + data = NULL; +} + +template +ID_INLINE bool idScriptVariable::IsLinked( void ) const { + return ( data != NULL ); +} + +template +ID_INLINE void idScriptVariable::Unlink( void ) { + data = NULL; +} + +template +ID_INLINE void idScriptVariable::LinkTo( idScriptObject &obj, const char *name ) { + data = ( type * )obj.GetVariable( name, etype ); + if ( !data ) { + gameError( "Missing '%s' field in script object '%s'", name, obj.GetTypeName() ); + } +} + +template +ID_INLINE idScriptVariable &idScriptVariable::operator=( const returnType &value ) { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + *data = ( type )value; + } + return *this; +} + +template +ID_INLINE idScriptVariable::operator returnType() const { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + return ( const returnType )*data; + } else { + // reasonably safe value + return ( const returnType )0; + } +} + +/*********************************************************************** + +Script object variable access template instantiations + +These objects will automatically handle looking up of the current value +of a variable in a script object. They can be stored as part of a class +for up-to-date values of the variable, or can be used in functions to +sample the data for non-dynamic values. + +***********************************************************************/ + +typedef idScriptVariable idScriptBool; +typedef idScriptVariable idScriptFloat; +typedef idScriptVariable idScriptInt; +typedef idScriptVariable idScriptVector; +typedef idScriptVariable idScriptString; + +/*********************************************************************** + +idCompileError + +Causes the compiler to exit out of compiling the current function and +display an error message with line and file info. + +***********************************************************************/ + +class idCompileError : public idException { +public: + idCompileError( const char *text ) : idException( text ) {} +}; + +/*********************************************************************** + +idVarDef + +Define the name, type, and location of variables, functions, and objects +defined in script. + +***********************************************************************/ + +typedef union varEval_s { + idScriptObject **objectPtrPtr; + char *stringPtr; + float *floatPtr; + idVec3 *vectorPtr; + function_t *functionPtr; + int *intPtr; + byte *bytePtr; + int *entityNumberPtr; + int virtualFunction; + int jumpOffset; + int stackOffset; // offset in stack for local variables + int argSize; + varEval_s *evalPtr; + int ptrOffset; +} varEval_t; + +class idVarDefName; + +class idVarDef { + friend class idVarDefName; + +public: + int num; + varEval_t value; + idVarDef * scope; // function, namespace, or object the var was defined in + int numUsers; // number of users if this is a constant + + typedef enum { + uninitialized, initializedVariable, initializedConstant, stackVariable + } initialized_t; + + initialized_t initialized; + +public: + idVarDef( idTypeDef *typeptr = NULL ); + ~idVarDef(); + + const char * Name( void ) const; + const char * GlobalName( void ) const; + + void SetTypeDef( idTypeDef *_type ) { typeDef = _type; } + idTypeDef * TypeDef( void ) const { return typeDef; } + etype_t Type( void ) const { return ( typeDef != NULL ) ? typeDef->Type() : ev_void; } + + int DepthOfScope( const idVarDef *otherScope ) const; + + void SetFunction( function_t *func ); + void SetObject( idScriptObject *object ); + void SetValue( const eval_t &value, bool constant ); + void SetString( const char *string, bool constant ); + + idVarDef * Next( void ) const { return next; } // next var def with same name + + void PrintInfo( idFile *file, int instructionPointer ) const; + +private: + idTypeDef * typeDef; + idVarDefName * name; // name of this var + idVarDef * next; // next var with the same name +}; + +/*********************************************************************** + + idVarDefName + +***********************************************************************/ + +class idVarDefName { +public: + idVarDefName( void ) { defs = NULL; } + idVarDefName( const char *n ) { name = n; defs = NULL; } + + const char * Name( void ) const { return name; } + idVarDef * GetDefs( void ) const { return defs; } + + void AddDef( idVarDef *def ); + void RemoveDef( idVarDef *def ); + +private: + idStr name; + idVarDef * defs; +}; + +/*********************************************************************** + + Variable and type defintions + +***********************************************************************/ + +extern idTypeDef type_void; +extern idTypeDef type_scriptevent; +extern idTypeDef type_namespace; +// RAVEN BEGIN +// abahr +extern rvTypeDefString type_string; +extern rvTypeDefFloat type_float; +extern rvTypeDefVec3 type_vector; +extern rvTypeDefEntity type_entity; +// RAVEN END +extern idTypeDef type_field; +extern idTypeDef type_function; +extern idTypeDef type_virtualfunction; +extern idTypeDef type_pointer; +extern idTypeDef type_object; +extern idTypeDef type_jumpoffset; // only used for jump opcodes +extern idTypeDef type_argsize; // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +extern rvTypeDefBool type_boolean; +// RAVEN END + +extern idVarDef def_void; +extern idVarDef def_scriptevent; +extern idVarDef def_namespace; +extern idVarDef def_string; +extern idVarDef def_float; +extern idVarDef def_vector; +extern idVarDef def_entity; +extern idVarDef def_field; +extern idVarDef def_function; +extern idVarDef def_virtualfunction; +extern idVarDef def_pointer; +extern idVarDef def_object; +extern idVarDef def_jumpoffset; // only used for jump opcodes +extern idVarDef def_argsize; // only used for function call and thread opcodes +extern idVarDef def_boolean; + +typedef struct statement_s { + unsigned short op; + idVarDef *a; + idVarDef *b; + idVarDef *c; + unsigned short linenumber; + unsigned short file; +} statement_t; + +/*********************************************************************** + +idProgram + +Handles compiling and storage of script data. Multiple idProgram objects +would represent seperate programs with no knowledge of each other. Scripts +meant to access shared data and functions should all be compiled by a +single idProgram. + +***********************************************************************/ + +class idProgram { +private: + idStrList fileList; + idStr filename; + int filenum; + + int numVariables; + byte variables[ MAX_GLOBALS ]; + idStaticList variableDefaults; + idStaticList functions; + idStaticList statements; + idList types; + idList varDefNames; + idHashIndex varDefNameHash; + idList varDefs; + + idVarDef *sysDef; + + int top_functions; + int top_statements; + int top_types; + int top_defs; + int top_files; + + void CompileStats( void ); + +public: + idVarDef *returnDef; + idVarDef *returnStringDef; + + idProgram(); + ~idProgram(); + + // save games + void Save( idSaveGame *savefile ) const; + bool Restore( idRestoreGame *savefile ); + int CalculateChecksum( void ) const; // Used to insure program code has not + // changed between savegames + + void Startup( const char *defaultScript ); + void Restart( void ); + bool CompileText( const char *source, const char *text, bool console ); + const function_t *CompileFunction( const char *functionName, const char *text ); + void CompileFile( const char *filename ); + void BeginCompilation( void ); + void FinishCompilation( void ); + void DisassembleStatement( idFile *file, int instructionPointer ) const; + void Disassemble( void ) const; + void FreeData( void ); + + const char *GetFilename( int num ); + int GetFilenum( const char *name ); + int GetLineNumberForStatement( int index ); + const char *GetFilenameForStatement( int index ); + + idTypeDef *AllocType( idTypeDef &type ); + idTypeDef *AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + idTypeDef *GetType( idTypeDef &type, bool allocate ); + idTypeDef *FindType( const char *name ); + + idVarDef *AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ); + idVarDef *GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const; + void FreeDef( idVarDef *d, const idVarDef *scope ); + idVarDef *FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ); + idVarDef *GetDefList( const char *name ) const; + void AddDefToNameList( idVarDef *def, const char *name ); + + function_t *FindFunction( const char *name ) const; // returns NULL if function not found + function_t *FindFunction( const char *name, const idTypeDef *type ) const; // returns NULL if function not found + function_t &AllocFunction( idVarDef *def ); + function_t *GetFunction( int index ); + int GetFunctionIndex( const function_t *func ); + + void SetEntity( const char *name, idEntity *ent ); + + statement_t *AllocStatement( void ); + statement_t &GetStatement( int index ); + int NumStatements( void ) { return statements.Num(); } + + int GetReturnedInteger( void ); + +// RAVEN BEGIN +// abahr: adding helper functions for other types + float GetReturnedFloat(); + idVec3 GetReturnedVec3(); + idEntity* GetReturnedEntity(); + const char* GetReturnedString(); + bool GetReturnedBool(); +// RAVEN END + + void ReturnFloat( float value ); + void ReturnInteger( int value ); + void ReturnVector( idVec3 const &vec ); + void ReturnString( const char *string ); +// RAVEN BEGIN +// abahr: added const + void ReturnEntity( const idEntity *ent ); +// RAVEN END + + int NumFilenames( void ) { return fileList.Num( ); } + +// RAVEN BEGIN +// bdube: added + void ListStates( void ); +// jscott: added for debug with inlines and memory log + void Shutdown( void ); +// jscott: added for stats + size_t ScriptSummary( const idCmdArgs &args ); + size_t ClassSummary( const idCmdArgs &args ); +// RAVEN END +}; + +/* +================ +idProgram::GetStatement +================ +*/ +ID_INLINE statement_t &idProgram::GetStatement( int index ) { + return statements[ index ]; +} + +/* +================ +idProgram::GetFunction +================ +*/ +ID_INLINE function_t *idProgram::GetFunction( int index ) { + return &functions[ index ]; +} + +/* +================ +idProgram::GetFunctionIndex +================ +*/ +ID_INLINE int idProgram::GetFunctionIndex( const function_t *func ) { + return func - &functions[0]; +} + +/* +================ +idProgram::GetReturnedInteger +================ +*/ +ID_INLINE int idProgram::GetReturnedInteger( void ) { +// RAVEN BEGIN +// abahr: because we only return floats in idThread we need to only get floats + return (int)GetReturnedFloat(); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: adding helper functions for other types +/* +================ +idProgram::GetReturnedFloat +================ +*/ +ID_INLINE float idProgram::GetReturnedFloat() { + return *returnDef->value.floatPtr; +} + +/* +================ +idProgram::GetReturnedVec3 +================ +*/ +ID_INLINE idVec3 idProgram::GetReturnedVec3() { + return *returnDef->value.vectorPtr; +} + +/* +================ +idProgram::GetReturnedString +================ +*/ +ID_INLINE const char* idProgram::GetReturnedString() { + return returnDef->value.stringPtr; +} + +/* +================ +idProgram::GetReturnedBool +================ +*/ +ID_INLINE bool idProgram::GetReturnedBool() { + return GetReturnedInteger() != 0; +} +// RAVEN END + +/* +================ +idProgram::ReturnFloat +================ +*/ +ID_INLINE void idProgram::ReturnFloat( float value ) { + *returnDef->value.floatPtr = value; +} + +/* +================ +idProgram::ReturnInteger +================ +*/ +ID_INLINE void idProgram::ReturnInteger( int value ) { + *returnDef->value.intPtr = value; +} + +/* +================ +idProgram::ReturnVector +================ +*/ +ID_INLINE void idProgram::ReturnVector( idVec3 const &vec ) { + *returnDef->value.vectorPtr = vec; +} + +/* +================ +idProgram::ReturnString +================ +*/ +ID_INLINE void idProgram::ReturnString( const char *string ) { + idStr::Copynz( returnStringDef->value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +================ +idProgram::GetFilename +================ +*/ +ID_INLINE const char *idProgram::GetFilename( int num ) { + return fileList[ num ]; +} + +/* +================ +idProgram::GetLineNumberForStatement +================ +*/ +ID_INLINE int idProgram::GetLineNumberForStatement( int index ) { + return statements[ index ].linenumber; +} + +/* +================ +idProgram::GetFilenameForStatement +================ +*/ +ID_INLINE const char *idProgram::GetFilenameForStatement( int index ) { + return GetFilename( statements[ index ].file ); +} + +#endif /* !__SCRIPT_PROGRAM_H__ */ diff --git a/src/game/script/Script_Thread.cpp b/src/game/script/Script_Thread.cpp new file mode 100644 index 0000000..fa7c0a7 --- /dev/null +++ b/src/game/script/Script_Thread.cpp @@ -0,0 +1,2332 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const idEventDef EV_Thread_Execute( "", NULL ); +const idEventDef EV_Thread_SetCallback( "", NULL ); + +// script callable events +const idEventDef EV_Thread_TerminateThread( "terminate", "d" ); +const idEventDef EV_Thread_Pause( "pause", NULL ); +const idEventDef EV_Thread_Wait( "wait", "f" ); +const idEventDef EV_Thread_WaitFrame( "waitFrame" ); +const idEventDef EV_Thread_WaitFor( "waitFor", "e" ); +const idEventDef EV_Thread_WaitForThread( "waitForThread", "d" ); +const idEventDef EV_Thread_Print( "print", "s" ); +const idEventDef EV_Thread_PrintLn( "println", "s" ); +const idEventDef EV_Thread_Say( "say", "s" ); +const idEventDef EV_Thread_Assert( "assert", "f" ); +const idEventDef EV_Thread_Trigger( "trigger", "e" ); +const idEventDef EV_Thread_SetCvar( "setcvar", "ss" ); +const idEventDef EV_Thread_GetCvar( "getcvar", "s", 's' ); +const idEventDef EV_Thread_Random( "random", "f", 'f' ); +const idEventDef EV_Thread_GetTime( "getTime", NULL, 'f' ); +const idEventDef EV_Thread_KillThread( "killthread", "s" ); +const idEventDef EV_Thread_SetThreadName( "threadname", "s" ); +const idEventDef EV_Thread_GetEntity( "getEntity", "s", 'e' ); +const idEventDef EV_Thread_Spawn( "spawn", "s", 'e' ); +const idEventDef EV_Thread_CopySpawnArgs( "copySpawnArgs", "e" ); +const idEventDef EV_Thread_SetSpawnArg( "setSpawnArg", "ss" ); +const idEventDef EV_Thread_SpawnString( "SpawnString", "ss", 's' ); +const idEventDef EV_Thread_SpawnFloat( "SpawnFloat", "sf", 'f' ); +const idEventDef EV_Thread_SpawnVector( "SpawnVector", "sv", 'v' ); +const idEventDef EV_Thread_ClearPersistantArgs( "clearPersistantArgs" ); +const idEventDef EV_Thread_SetPersistantArg( "setPersistantArg", "ss" ); +const idEventDef EV_Thread_GetPersistantString( "getPersistantString", "s", 's' ); +const idEventDef EV_Thread_GetPersistantFloat( "getPersistantFloat", "s", 'f' ); +const idEventDef EV_Thread_GetPersistantVector( "getPersistantVector", "s", 'v' ); +const idEventDef EV_Thread_AngToForward( "angToForward", "v", 'v' ); +const idEventDef EV_Thread_AngToRight( "angToRight", "v", 'v' ); +const idEventDef EV_Thread_AngToUp( "angToUp", "v", 'v' ); +const idEventDef EV_Thread_Sine( "sin", "f", 'f' ); +const idEventDef EV_Thread_Cosine( "cos", "f", 'f' ); +const idEventDef EV_Thread_SquareRoot( "sqrt", "f", 'f' ); +const idEventDef EV_Thread_Normalize( "vecNormalize", "v", 'v' ); +const idEventDef EV_Thread_VecLength( "vecLength", "v", 'f' ); +const idEventDef EV_Thread_VecDotProduct( "DotProduct", "vv", 'f' ); +const idEventDef EV_Thread_VecCrossProduct( "CrossProduct", "vv", 'v' ); +const idEventDef EV_Thread_VecToAngles( "VecToAngles", "v", 'v' ); +const idEventDef EV_Thread_OnSignal( "onSignal", "des" ); +const idEventDef EV_Thread_ClearSignal( "clearSignalThread", "de" ); +const idEventDef EV_Thread_SetCamera( "setCamera", "e" ); +const idEventDef EV_Thread_FirstPerson( "firstPerson", NULL ); +const idEventDef EV_Thread_Trace( "trace", "vvvvde", 'f' ); +const idEventDef EV_Thread_TracePoint( "tracePoint", "vvde", 'f' ); +const idEventDef EV_Thread_GetTraceFraction( "getTraceFraction", NULL, 'f' ); +const idEventDef EV_Thread_GetTraceEndPos( "getTraceEndPos", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceNormal( "getTraceNormal", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceEntity( "getTraceEntity", NULL, 'e' ); +const idEventDef EV_Thread_GetTraceJoint( "getTraceJoint", NULL, 's' ); +const idEventDef EV_Thread_GetTraceBody( "getTraceBody", NULL, 's' ); +const idEventDef EV_Thread_FadeIn( "fadeIn", "vf" ); +const idEventDef EV_Thread_FadeOut( "fadeOut", "vf" ); +const idEventDef EV_Thread_FadeTo( "fadeTo", "vff" ); +const idEventDef EV_Thread_StartMusic( "music", "s" ); +const idEventDef EV_Thread_Error( "error", "s" ); +const idEventDef EV_Thread_Warning( "warning", "s" ); +const idEventDef EV_Thread_StrLen( "strLength", "s", 'd' ); +const idEventDef EV_Thread_StrLeft( "strLeft", "sd", 's' ); +const idEventDef EV_Thread_StrRight( "strRight", "sd", 's' ); +const idEventDef EV_Thread_StrSkip( "strSkip", "sd", 's' ); +const idEventDef EV_Thread_StrMid( "strMid", "sdd", 's' ); +const idEventDef EV_Thread_StrToFloat( "strToFloat", "s", 'f' ); +const idEventDef EV_Thread_RadiusDamage( "radiusDamage", "vEEEsf" ); +const idEventDef EV_Thread_IsClient( "isClient", NULL, 'f' ); +const idEventDef EV_Thread_IsMultiplayer( "isMultiplayer", NULL, 'f' ); +const idEventDef EV_Thread_GetFrameTime( "getFrameTime", NULL, 'f' ); +const idEventDef EV_Thread_GetTicsPerSecond( "getTicsPerSecond", NULL, 'f' ); +const idEventDef EV_Thread_DebugLine( "debugLine", "vvvf" ); +const idEventDef EV_Thread_DebugArrow( "debugArrow", "vvvdf" ); +const idEventDef EV_Thread_DebugCircle( "debugCircle", "vvvfdf" ); +const idEventDef EV_Thread_DebugBounds( "debugBounds", "vvvf" ); +const idEventDef EV_Thread_DrawText( "drawText", "svfvdf" ); +const idEventDef EV_Thread_InfluenceActive( "influenceActive", NULL, 'd' ); + +// RAVEN BEGIN +// kfuller: added everything below the above block +const idEventDef EV_Thread_SetSpawnVector( "setSpawnVector", "sv" ); +const idEventDef EV_Thread_ArcSine( "asin", "f", 'f' ); +const idEventDef EV_Thread_ArcCosine( "acos", "f", 'f' ); +const idEventDef EV_Thread_VecRotate( "vecRotate", "vv", 'v'); +const idEventDef EV_Thread_ClearSignalAllThreads( "clearSignalAllThreads", "de" ); +// bgeisler: added everything below the added block that was added by keith +const idEventDef EV_Thread_PlayWorldEffect( "playWorldEffect", "svv"); +// abahr: +const idEventDef EV_Thread_ReferenceScriptObjectProxy( "refProxy", "s", 'e' ); +const idEventDef EV_Thread_ReleaseScriptObjectProxy( "releaseProxy", "s" ); +const idEventDef EV_Thread_ClampFloat( "clampFloat", "fff", 'f' ); +const idEventDef EV_Thread_MinFloat( "minFloat", "ff", 'f' ); +const idEventDef EV_Thread_MaxFloat( "maxFloat", "ff", 'f' ); +const idEventDef EV_Thread_StrFind( "strFind", "ss", 'f' ); +const idEventDef EV_Thread_RandomInt( "randomInt", "f", 'f' ); +// rjohnson: new blur special effect +const idEventDef EV_Thread_SetSpecialEffect( "setSpecialEffect", "dd" ); +const idEventDef EV_Thread_SetSpecialEffectParm( "setSpecialEffectParm", "ddf" ); +// asalmon: award achievement +const idEventDef EV_Thread_AwardAchievement( "awardAchievement", "s" ); +// twhitaker: ceil, floor and intVal +const idEventDef EV_Thread_Ceil( "ceil", "f", 'f' ); +const idEventDef EV_Thread_Floor( "floor", "f", 'f' ); +const idEventDef EV_Thread_ToInt( "intVal", "f", 'f' ); +// jdischler: send named event string to specified gui +const idEventDef EV_Thread_SendNamedEvent( "sendNamedEvent", "ds" ); +// material streaming +const idEventDef EV_Thread_BeginManualStreaming( "beginManualStreaming" ); +const idEventDef EV_Thread_EndManualStreaming( "endManualStreaming" ); + +// nrausch: change material sort order on the fly +const idEventDef EV_Thread_SetMatSort( "setMatSort", "ss", 0 ); +// RAVEN END + +CLASS_DECLARATION( idClass, idThread ) + EVENT( EV_Thread_Execute, idThread::Event_Execute ) + EVENT( EV_Thread_TerminateThread, idThread::Event_TerminateThread ) + EVENT( EV_Thread_Pause, idThread::Event_Pause ) + EVENT( EV_Thread_Wait, idThread::Event_Wait ) + EVENT( EV_Thread_WaitFrame, idThread::Event_WaitFrame ) + EVENT( EV_Thread_WaitFor, idThread::Event_WaitFor ) + EVENT( EV_Thread_WaitForThread, idThread::Event_WaitForThread ) + EVENT( EV_Thread_Print, idThread::Event_Print ) + EVENT( EV_Thread_PrintLn, idThread::Event_PrintLn ) + EVENT( EV_Thread_Say, idThread::Event_Say ) + EVENT( EV_Thread_Assert, idThread::Event_Assert ) + EVENT( EV_Thread_Trigger, idThread::Event_Trigger ) + EVENT( EV_Thread_SetCvar, idThread::Event_SetCvar ) + EVENT( EV_Thread_GetCvar, idThread::Event_GetCvar ) + EVENT( EV_Thread_Random, idThread::Event_Random ) + EVENT( EV_Thread_GetTime, idThread::Event_GetTime ) + EVENT( EV_Thread_KillThread, idThread::Event_KillThread ) + EVENT( EV_Thread_SetThreadName, idThread::Event_SetThreadName ) + EVENT( EV_Thread_GetEntity, idThread::Event_GetEntity ) + EVENT( EV_Thread_Spawn, idThread::Event_Spawn ) + EVENT( EV_Thread_CopySpawnArgs, idThread::Event_CopySpawnArgs ) + EVENT( EV_Thread_SetSpawnArg, idThread::Event_SetSpawnArg ) + EVENT( EV_Thread_SpawnString, idThread::Event_SpawnString ) + EVENT( EV_Thread_SpawnFloat, idThread::Event_SpawnFloat ) + EVENT( EV_Thread_SpawnVector, idThread::Event_SpawnVector ) + EVENT( EV_Thread_ClearPersistantArgs, idThread::Event_ClearPersistantArgs ) + EVENT( EV_Thread_SetPersistantArg, idThread::Event_SetPersistantArg ) + EVENT( EV_Thread_GetPersistantString, idThread::Event_GetPersistantString ) + EVENT( EV_Thread_GetPersistantFloat, idThread::Event_GetPersistantFloat ) + EVENT( EV_Thread_GetPersistantVector, idThread::Event_GetPersistantVector ) + EVENT( EV_Thread_AngToForward, idThread::Event_AngToForward ) + EVENT( EV_Thread_AngToRight, idThread::Event_AngToRight ) + EVENT( EV_Thread_AngToUp, idThread::Event_AngToUp ) + EVENT( EV_Thread_Sine, idThread::Event_GetSine ) + EVENT( EV_Thread_Cosine, idThread::Event_GetCosine ) + EVENT( EV_Thread_SquareRoot, idThread::Event_GetSquareRoot ) + EVENT( EV_Thread_Normalize, idThread::Event_VecNormalize ) + EVENT( EV_Thread_VecLength, idThread::Event_VecLength ) + EVENT( EV_Thread_VecDotProduct, idThread::Event_VecDotProduct ) + EVENT( EV_Thread_VecCrossProduct, idThread::Event_VecCrossProduct ) + EVENT( EV_Thread_VecToAngles, idThread::Event_VecToAngles ) + EVENT( EV_Thread_OnSignal, idThread::Event_OnSignal ) + EVENT( EV_Thread_ClearSignal, idThread::Event_ClearSignalThread ) + EVENT( EV_Thread_SetCamera, idThread::Event_SetCamera ) + EVENT( EV_Thread_FirstPerson, idThread::Event_FirstPerson ) + EVENT( EV_Thread_Trace, idThread::Event_Trace ) + EVENT( EV_Thread_TracePoint, idThread::Event_TracePoint ) + EVENT( EV_Thread_GetTraceFraction, idThread::Event_GetTraceFraction ) + EVENT( EV_Thread_GetTraceEndPos, idThread::Event_GetTraceEndPos ) + EVENT( EV_Thread_GetTraceNormal, idThread::Event_GetTraceNormal ) + EVENT( EV_Thread_GetTraceEntity, idThread::Event_GetTraceEntity ) + EVENT( EV_Thread_GetTraceJoint, idThread::Event_GetTraceJoint ) + EVENT( EV_Thread_GetTraceBody, idThread::Event_GetTraceBody ) + EVENT( EV_Thread_FadeIn, idThread::Event_FadeIn ) + EVENT( EV_Thread_FadeOut, idThread::Event_FadeOut ) + EVENT( EV_Thread_FadeTo, idThread::Event_FadeTo ) + EVENT( EV_SetShaderParm, idThread::Event_SetShaderParm ) + EVENT( EV_Thread_StartMusic, idThread::Event_StartMusic ) + EVENT( EV_Thread_Warning, idThread::Event_Warning ) + EVENT( EV_Thread_Error, idThread::Event_Error ) + EVENT( EV_Thread_StrLen, idThread::Event_StrLen ) + EVENT( EV_Thread_StrLeft, idThread::Event_StrLeft ) + EVENT( EV_Thread_StrRight, idThread::Event_StrRight ) + EVENT( EV_Thread_StrSkip, idThread::Event_StrSkip ) + EVENT( EV_Thread_StrMid, idThread::Event_StrMid ) + EVENT( EV_Thread_StrToFloat, idThread::Event_StrToFloat ) + EVENT( EV_Thread_RadiusDamage, idThread::Event_RadiusDamage ) + EVENT( EV_Thread_IsClient, idThread::Event_IsClient ) + EVENT( EV_Thread_IsMultiplayer, idThread::Event_IsMultiplayer ) + EVENT( EV_Thread_GetFrameTime, idThread::Event_GetFrameTime ) + EVENT( EV_Thread_GetTicsPerSecond, idThread::Event_GetTicsPerSecond ) + EVENT( EV_CacheSoundShader, idThread::Event_CacheSoundShader ) + EVENT( EV_Thread_DebugLine, idThread::Event_DebugLine ) + EVENT( EV_Thread_DebugArrow, idThread::Event_DebugArrow ) + EVENT( EV_Thread_DebugCircle, idThread::Event_DebugCircle ) + EVENT( EV_Thread_DebugBounds, idThread::Event_DebugBounds ) + EVENT( EV_Thread_DrawText, idThread::Event_DrawText ) + EVENT( EV_Thread_InfluenceActive, idThread::Event_InfluenceActive ) + +// RAVEN BEGIN +// kfuller: added events + EVENT( EV_Thread_SetSpawnVector, idThread::Event_SetSpawnVector ) + EVENT( EV_Thread_ArcSine, idThread::Event_GetArcSine ) + EVENT( EV_Thread_ArcCosine, idThread::Event_GetArcCosine ) + EVENT( EV_Thread_VecRotate, idThread::Event_VecRotate ) + EVENT( EV_Thread_ClearSignalAllThreads, idThread::Event_ClearSignalAllThreads ) +// nmckenzie: added signal strings + EVENT( EV_Thread_PlayWorldEffect, idThread::Event_PlayWorldEffect ) +// abahr: + EVENT( EV_Thread_ReferenceScriptObjectProxy, idThread::Event_ReferenceScriptObjectProxy ) + EVENT( EV_Thread_ReleaseScriptObjectProxy, idThread::Event_ReleaseScriptObjectProxy ) + EVENT( EV_Thread_ClampFloat, idThread::Event_ClampFloat ) + EVENT( EV_Thread_MinFloat, idThread::Event_MinFloat ) + EVENT( EV_Thread_MaxFloat, idThread::Event_MaxFloat ) + EVENT( EV_Thread_StrFind, idThread::Event_StrFind ) + EVENT( EV_Thread_RandomInt, idThread::Event_RandomInt ) +// rjohnson: new blur special effect + EVENT( EV_Thread_SetSpecialEffect, idThread::Event_SetSpecialEffect ) + EVENT( EV_Thread_SetSpecialEffectParm, idThread::Event_SetSpecialEffectParm ) +// asalmon: award a Xenon achievement + EVENT( EV_Thread_AwardAchievement, idThread::Event_AwardAchievement ) +// twhitaker: ceil and floor + EVENT( EV_Thread_Ceil, idThread::Event_GetCeil ) + EVENT( EV_Thread_Floor, idThread::Event_GetFloor ) + EVENT( EV_Thread_ToInt, idThread::Event_ToInt ) +// jdischler: send named event string to specified gui + EVENT( EV_Thread_SendNamedEvent, idThread::Event_SendNamedEvent ) +// manual streaming + EVENT( EV_Thread_BeginManualStreaming, idThread::Event_BeginManualStreaming ) + EVENT( EV_Thread_EndManualStreaming, idThread::Event_EndManualStreaming ) + EVENT( EV_Thread_SetMatSort, idThread::Event_SetMatSort ) +// RAVEN END +END_CLASS + +idThread *idThread::currentThread = NULL; +int idThread::threadIndex = 0; +idList idThread::threadList; +trace_t idThread::trace; + +/* +================ +idThread::CurrentThread +================ +*/ +idThread *idThread::CurrentThread( void ) { + return currentThread; +} + +/* +================ +idThread::CurrentThreadNum +================ +*/ +int idThread::CurrentThreadNum( void ) { + if ( currentThread ) { + return currentThread->GetThreadNum(); + } else { + return 0; + } +} + +/* +================ +idThread::BeginMultiFrameEvent +================ +*/ +bool idThread::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::BeginMultiFrameEvent called without a current thread" ); + } + return currentThread->interpreter.BeginMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::EndMultiFrameEvent +================ +*/ +void idThread::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::EndMultiFrameEvent called without a current thread" ); + } + currentThread->interpreter.EndMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread() { + Init(); + SetThreadName( va( "thread_%d", threadIndex ) ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idEntity *self, const function_t *func ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.EnterObjectFunction( self, func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( const function_t *func ) { + assert( func ); + + Init(); + SetThreadName( func->Name() ); + interpreter.EnterFunction( func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, const function_t *func, int args ) { + Init(); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::~idThread +================ +*/ +idThread::~idThread() { + idThread *thread; + int i; + int n; + + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: end thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } + threadList.Remove( this ); + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->WaitingOnThread() == this ) { + thread->ThreadCallback( this ); + } + } + + if ( currentThread == this ) { + currentThread = NULL; + } +} + +/* +================ +idThread::ManualDelete +================ +*/ +void idThread::ManualDelete( void ) { + interpreter.terminateOnExit = false; +} + +/* +================ +idThread::Save +================ +*/ +void idThread::Save( idSaveGame *savefile ) const { + savefile->WriteObject( currentThread ); + + savefile->WriteObject( waitingForThread ); + savefile->WriteInt( waitingFor ); + savefile->WriteInt( waitingUntil ); + interpreter.Save( savefile ); + + savefile->WriteDict( &spawnArgs ); + + savefile->WriteInt( threadNum ); + savefile->WriteString( threadName ); + + savefile->WriteInt( lastExecuteTime ); + savefile->WriteInt( creationTime ); + + savefile->WriteBool( manualControl ); + + savefile->WriteInt( threadIndex ); +} + +/* +================ +idThread::Restore +================ +*/ +void idThread::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( currentThread ) ); + + savefile->ReadObject( reinterpret_cast( waitingForThread ) ); + savefile->ReadInt( waitingFor ); + savefile->ReadInt( waitingUntil ); + interpreter.Restore( savefile ); + + savefile->ReadDict( &spawnArgs ); + + savefile->ReadInt( threadNum ); + savefile->ReadString( threadName ); + + savefile->ReadInt( lastExecuteTime ); + savefile->ReadInt( creationTime ); + + savefile->ReadBool( manualControl ); + + savefile->ReadInt( threadIndex ); +} + +/* +================ +idThread::Init +================ +*/ +void idThread::Init( void ) { + // create a unique threadNum + do { + threadIndex++; + if ( threadIndex == 0 ) { + threadIndex = 1; + } + } while( GetThread( threadIndex ) ); + + threadNum = threadIndex; + threadList.Append( this ); + + creationTime = gameLocal.time; + lastExecuteTime = 0; + manualControl = false; + + ClearWaitFor(); + + interpreter.SetThread( this ); +} + +/* +================ +idThread::GetThread +================ +*/ +idThread *idThread::GetThread( int num ) { + int i; + int n; + idThread *thread; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->GetThreadNum() == num ) { + return thread; + } + } + + return NULL; +} + +/* +================ +idThread::DisplayInfo +================ +*/ +void idThread::DisplayInfo( void ) { + gameLocal.Printf( + "%12i: '%s'\n" + " File: %s(%d)\n" + " Created: %d (%d ms ago)\n" + " Status: ", + threadNum, threadName.c_str(), + interpreter.CurrentFile(), interpreter.CurrentLine(), + creationTime, gameLocal.time - creationTime ); + + if ( interpreter.threadDying ) { + gameLocal.Printf( "Dying\n" ); + } else if ( interpreter.doneProcessing ) { + gameLocal.Printf( + "Paused since %d (%d ms)\n" + " Reason: ", lastExecuteTime, gameLocal.time - lastExecuteTime ); + if ( waitingForThread ) { + gameLocal.Printf( "Waiting for thread #%3i '%s'\n", waitingForThread->GetThreadNum(), waitingForThread->GetThreadName() ); + } else if ( ( waitingFor != ENTITYNUM_NONE ) && ( gameLocal.entities[ waitingFor ] ) ) { + gameLocal.Printf( "Waiting for entity #%3i '%s'\n", waitingFor, gameLocal.entities[ waitingFor ]->name.c_str() ); + } else if ( waitingUntil ) { + gameLocal.Printf( "Waiting until %d (%d ms total wait time)\n", waitingUntil, waitingUntil - lastExecuteTime ); + } else { + gameLocal.Printf( "None\n" ); + } + } else { + gameLocal.Printf( "Processing\n" ); + } + + interpreter.DisplayInfo(); + + gameLocal.Printf( "\n" ); +} + +/* +================ +idThread::ListThreads_f +================ +*/ +void idThread::ListThreads_f( const idCmdArgs &args ) { + int i; + int n; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + //threadList[ i ]->DisplayInfo(); + gameLocal.Printf( "%3i: %-20s : %s(%d)\n", threadList[ i ]->threadNum, threadList[ i ]->threadName.c_str(), threadList[ i ]->interpreter.CurrentFile(), threadList[ i ]->interpreter.CurrentLine() ); + } + gameLocal.Printf( "%d active threads\n\n", n ); +} + +/* +================ +idThread::Restart +================ +*/ +void idThread::Restart( void ) { + int i; + int n; + + // reset the threadIndex + threadIndex = 0; + + currentThread = NULL; + n = threadList.Num(); + for( i = n - 1; i >= 0; i-- ) { + delete threadList[ i ]; + } + threadList.Clear(); + + memset( &trace, 0, sizeof( trace ) ); + trace.c.entityNum = ENTITYNUM_NONE; +} + +/* +================ +idThread::DelayedStart +================ +*/ +void idThread::DelayedStart( int delay ) { + CancelEvents( &EV_Thread_Execute ); + if ( gameLocal.time <= 0 ) { + delay++; + } + PostEventMS( &EV_Thread_Execute, delay ); +} + +/* +================ +idThread::Start +================ +*/ +bool idThread::Start( void ) { + bool result; + + CancelEvents( &EV_Thread_Execute ); + result = Execute(); + + return result; +} + +/* +================ +idThread::SetThreadName +================ +*/ +void idThread::SetThreadName( const char *name ) { + threadName = name; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( int threadnum, idEntity *obj ) { + idThread *thread; + + if ( !threadnum ) { + return; + } + + thread = GetThread( threadnum ); + if ( thread ) { + thread->ObjectMoveDone( obj ); + } +} + +/* +================ +idThread::End +================ +*/ +void idThread::End( void ) { + // Tell thread to die. It will exit on its own. + Pause(); + interpreter.threadDying = true; +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( const char *name ) { + int i; + int num; + int len; + const char *ptr; + idThread *thread; + + // see if the name uses a wild card + ptr = strchr( name, '*' ); + if ( ptr ) { + len = ptr - name; + } else { + len = strlen( name ); + } + + // kill only those threads whose name matches name + num = threadList.Num(); + for( i = 0; i < num; i++ ) { + thread = threadList[ i ]; + if ( !idStr::Cmpn( thread->GetThreadName(), name, len ) ) { + thread->End(); + } + } +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( thread ) { + // Tell thread to die. It will delete itself on it's own. + thread->End(); + } +} + +/* +================ +idThread::Execute +================ +*/ +bool idThread::Execute( void ) { + idThread *oldThread; + bool done; + + if ( manualControl && ( waitingUntil > gameLocal.time ) ) { + return false; + } + + oldThread = currentThread; + currentThread = this; + + lastExecuteTime = gameLocal.time; + ClearWaitFor(); + done = interpreter.Execute(); + if ( done ) { + End(); + if ( interpreter.terminateOnExit ) { + PostEventMS( &EV_Remove, 0 ); + } + } else if ( !manualControl ) { + if ( waitingUntil > lastExecuteTime ) { + PostEventMS( &EV_Thread_Execute, waitingUntil - lastExecuteTime ); + } else if ( interpreter.MultiFrameEventInProgress() ) { + PostEventMS( &EV_Thread_Execute, gameLocal.msec ); + } + } + + currentThread = oldThread; + + return done; +} + +/* +================ +idThread::IsWaiting + +Checks if thread is still waiting for some event to occur. +================ +*/ +bool idThread::IsWaiting( void ) { + if ( waitingForThread || ( waitingFor != ENTITYNUM_NONE ) ) { + return true; + } + + if ( waitingUntil && ( waitingUntil > gameLocal.time ) ) { + return true; + } + + return false; +} + +// RAVEN BEGIN +// bgeisler: +/* +================ +idThread::ListFunctions +================ +*/ +void idThread::ListStates(void) +{ + gameLocal.program.ListStates(); +} + +// abahr: added helper functions +/* +================ +idThread::ClearStack +================ +*/ +void idThread::ClearStack() { + interpreter.Reset(); +} + +/* +================ +idThread::PushInt +================ +*/ +void idThread::PushInt( int value ) { + interpreter.Push( value ); +} + +/* +================ +idThread::PushFloat +================ +*/ +void idThread::PushFloat( float value ) { + interpreter.Push( *(int*)&value ); +} + +/* +================ +idThread::PushVec3 +================ +*/ +void idThread::PushVec3( const idVec3& value ) { + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + PushFloat( value[ix] ); + } +} + +/* +================ +idThread::PushEntity +================ +*/ +void idThread::PushEntity( const idEntity* ent ) { + assert( ent ); + + PushInt( ent->entityNumber + 1 ); +} + +/* +================ +idThread::PushString +================ +*/ +void idThread::PushString( const char* string ) { + interpreter.PushString( string ); +} + +/* +================ +idThread::PushBool +================ +*/ +void idThread::PushBool( bool value ) { + PushInt( (int)value ); +} +// RAVEN END + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( const function_t *func, bool clearStack ) { + ClearWaitFor(); + interpreter.EnterFunction( func, clearStack ); +} + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( idEntity *self, const function_t *func, bool clearStack ) { + assert( self ); + ClearWaitFor(); + interpreter.EnterObjectFunction( self, func, clearStack ); +} + +/* +================ +idThread::ClearWaitFor +================ +*/ +void idThread::ClearWaitFor( void ) { + waitingFor = ENTITYNUM_NONE; + waitingForThread = NULL; + waitingUntil = 0; +} + +/* +================ +idThread::IsWaitingFor +================ +*/ +bool idThread::IsWaitingFor( idEntity *obj ) { + assert( obj ); + return waitingFor == obj->entityNumber; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( idEntity *obj ) { + assert( obj ); + + if ( IsWaitingFor( obj ) ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::ThreadCallback +================ +*/ +void idThread::ThreadCallback( idThread *thread ) { + if ( interpreter.threadDying ) { + return; + } + + if ( thread == waitingForThread ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::Event_SetThreadName +================ +*/ +void idThread::Event_SetThreadName( const char *name ) { + SetThreadName( name ); +} + +/* +================ +idThread::Error +================ +*/ +void idThread::Error( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Error( text ); +} + +/* +================ +idThread::Warning +================ +*/ +void idThread::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Warning( text ); +} + +/* +================ +idThread::ReturnString +================ +*/ +void idThread::ReturnString( const char *text ) { + gameLocal.program.ReturnString( text ); +} + +/* +================ +idThread::ReturnFloat +================ +*/ +void idThread::ReturnFloat( float value ) { + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnInt +================ +*/ +void idThread::ReturnInt( int value ) { + // true integers aren't supported in the compiler, + // so int values are stored as floats + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnVector +================ +*/ +void idThread::ReturnVector( idVec3 const &vec ) { + gameLocal.program.ReturnVector( vec ); +} + +/* +================ +idThread::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idThread::ReturnEntity( const idEntity *ent ) { + gameLocal.program.ReturnEntity( ent ); +} + +/* +================ +idThread::Event_Execute +================ +*/ +void idThread::Event_Execute( void ) { + Execute(); +} + +/* +================ +idThread::Pause +================ +*/ +void idThread::Pause( void ) { + ClearWaitFor(); + interpreter.doneProcessing = true; +} + +/* +================ +idThread::WaitMS +================ +*/ +void idThread::WaitMS( int time ) { + Pause(); +// RAVEN BEGIN +// bdube: add 1 ms to ensure a time of zero still works + waitingUntil = gameLocal.time + time + 1; +// RAVEN END +} + +/* +================ +idThread::WaitSec +================ +*/ +void idThread::WaitSec( float time ) { + WaitMS( SEC2MS( time ) ); +} + +/* +================ +idThread::WaitFrame +================ +*/ +void idThread::WaitFrame( void ) { + Pause(); + + // manual control threads don't set waitingUntil so that they can be run again + // that frame if necessary. + if ( !manualControl ) { + waitingUntil = gameLocal.time + gameLocal.msec; + } +} + +/*********************************************************************** + + Script callable events + +***********************************************************************/ + +/* +================ +idThread::Event_TerminateThread +================ +*/ +void idThread::Event_TerminateThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + KillThread( num ); +} + +/* +================ +idThread::Event_Pause +================ +*/ +void idThread::Event_Pause( void ) { + Pause(); +} + +/* +================ +idThread::Event_Wait +================ +*/ +void idThread::Event_Wait( float time ) { + WaitSec( time ); +} + +/* +================ +idThread::Event_WaitFrame +================ +*/ +void idThread::Event_WaitFrame( void ) { + WaitFrame(); +} + +/* +================ +idThread::Event_WaitFor +================ +*/ +void idThread::Event_WaitFor( idEntity *ent ) { + if ( ent && ent->RespondsTo( EV_Thread_SetCallback ) ) { + ent->ProcessEvent( &EV_Thread_SetCallback ); + if ( gameLocal.program.GetReturnedInteger() ) { + Pause(); + waitingFor = ent->entityNumber; + } + } +} + +/* +================ +idThread::Event_WaitForThread +================ +*/ +void idThread::Event_WaitForThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( !thread ) { + if ( g_debugScript.GetBool() ) { + // just print a warning and continue executing + Warning( "Thread %d not running", num ); + } + } else { + Pause(); + waitingForThread = thread; + } +} + +/* +================ +idThread::Event_Print +================ +*/ +void idThread::Event_Print( const char *text ) { + gameLocal.Printf( "%s", text ); +} + +/* +================ +idThread::Event_PrintLn +================ +*/ +void idThread::Event_PrintLn( const char *text ) { + gameLocal.Printf( "%s\n", text ); +} + +/* +================ +idThread::Event_Say +================ +*/ +void idThread::Event_Say( const char *text ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text ) ); +} + +/* +================ +idThread::Event_Assert +================ +*/ +void idThread::Event_Assert( float value ) { +// RAVEN BEGIN +// jnewquist: assert with a useful callstack, since this is script +#ifdef _DEBUG + if ( !value ) { + Error("Script assert fired"); + } +#endif +// RAVEN END +} + +/* +================ +idThread::Event_Trigger +================ +*/ +void idThread::Event_Trigger( idEntity *ent ) { + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + ent->TriggerGuis(); + } +} + +/* +================ +idThread::Event_SetCvar +================ +*/ +void idThread::Event_SetCvar( const char *name, const char *value ) const { + cvarSystem->SetCVarString( name, value ); +} + +/* +================ +idThread::Event_GetCvar +================ +*/ +void idThread::Event_GetCvar( const char *name ) const { + ReturnString( cvarSystem->GetCVarString( name ) ); +} + +/* +================ +idThread::Event_Random +================ +*/ +void idThread::Event_Random( float range ) const { + float result; + + result = gameLocal.random.RandomFloat(); + ReturnFloat( range * result ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idThread::Event_RandomInt +================ +*/ +void idThread::Event_RandomInt( float range ) const { + ReturnFloat( rvRandom::irand(0, range) ); +} + + +// rjohnson: new blur special effect +void idThread::Event_SetSpecialEffect( int Effect, int Enabled ) { + renderSystem->SetSpecialEffect( (ESpecialEffectType)Effect, !!Enabled ); +} + +void idThread::Event_SetSpecialEffectParm( int Effect, int Parm, float Value ) { + renderSystem->SetSpecialEffectParm( (ESpecialEffectType)Effect, Parm, Value ); +} + +// RAVEN END + +/* +================ +idThread::Event_GetTime +================ +*/ +void idThread::Event_GetTime( void ) { + ReturnFloat( MS2SEC( gameLocal.realClientTime ) ); +} + +/* +================ +idThread::Event_KillThread +================ +*/ +void idThread::Event_KillThread( const char *name ) { + KillThread( name ); +} + +/* +================ +idThread::Event_GetEntity +================ +*/ +void idThread::Event_GetEntity( const char *name ) { + int entnum; + idEntity *ent; + + assert( name ); + + if ( name[ 0 ] == '*' ) { + entnum = atoi( &name[ 1 ] ); + if ( ( entnum < 0 ) || ( entnum >= MAX_GENTITIES ) ) { + Error( "Entity number in string out of range." ); + } + ReturnEntity( gameLocal.entities[ entnum ] ); + } else { + ent = gameLocal.FindEntity( name ); + ReturnEntity( ent ); + } +} + +/* +================ +idThread::Event_Spawn +================ +*/ +void idThread::Event_Spawn( const char *classname ) { + idEntity *ent; + + spawnArgs.Set( "classname", classname ); + gameLocal.SpawnEntityDef( spawnArgs, &ent ); + ReturnEntity( ent ); + spawnArgs.Clear(); +} + +/* +================ +idThread::Event_CopySpawnArgs +================ +*/ +void idThread::Event_CopySpawnArgs( idEntity *ent ) { + spawnArgs.Copy( ent->spawnArgs ); +} + +/* +================ +idThread::Event_SetSpawnArg +================ +*/ +void idThread::Event_SetSpawnArg( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +// RAVEN BEGIN +// kfuller: added events + +/* +================ +idThread::Event_SetSpawnVector +================ +*/ +void idThread::Event_SetSpawnVector( const char *key, idVec3 &vec) { + spawnArgs.SetVector(key, vec); +} +// RAVEN END + +/* +================ +idThread::Event_SpawnString +================ +*/ +void idThread::Event_SpawnString( const char *key, const char *defaultvalue ) { + const char *result; + + spawnArgs.GetString( key, defaultvalue, &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_SpawnFloat +================ +*/ +void idThread::Event_SpawnFloat( const char *key, float defaultvalue ) { + float result; + + spawnArgs.GetFloat( key, va( "%f", defaultvalue ), result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_SpawnVector +================ +*/ +void idThread::Event_SpawnVector( const char *key, idVec3 &defaultvalue ) { + idVec3 result; + + spawnArgs.GetVector( key, va( "%f %f %f", defaultvalue.x, defaultvalue.y, defaultvalue.z ), result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_ClearPersistantArgs +================ +*/ +void idThread::Event_ClearPersistantArgs( void ) { + gameLocal.persistentLevelInfo.Clear(); +} + + +/* +================ +idThread::Event_SetPersistantArg +================ +*/ +void idThread::Event_SetPersistantArg( const char *key, const char *value ) { + gameLocal.persistentLevelInfo.Set( key, value ); +} + +/* +================ +idThread::Event_GetPersistantString +================ +*/ +void idThread::Event_GetPersistantString( const char *key ) { + const char *result; + + gameLocal.persistentLevelInfo.GetString( key, "", &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_GetPersistantFloat +================ +*/ +void idThread::Event_GetPersistantFloat( const char *key ) { + float result; + + gameLocal.persistentLevelInfo.GetFloat( key, "0", result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_GetPersistantVector +================ +*/ +void idThread::Event_GetPersistantVector( const char *key ) { + idVec3 result; + + gameLocal.persistentLevelInfo.GetVector( key, "0 0 0", result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_AngToForward +================ +*/ +void idThread::Event_AngToForward( idAngles &ang ) { + ReturnVector( ang.ToForward() ); +} + +/* +================ +idThread::Event_AngToRight +================ +*/ +void idThread::Event_AngToRight( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, &vec ); + ReturnVector( vec ); +} + +/* +================ +idThread::Event_AngToUp +================ +*/ +void idThread::Event_AngToUp( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, NULL, &vec ); + ReturnVector( vec ); +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_GetArcSine +================ +*/ +void idThread::Event_GetArcSine( float sinValue ) { + ReturnFloat( asinf( sinValue ) ); +} + +/* +================ +idThread::Event_GetArcCosine +================ +*/ +void idThread::Event_GetArcCosine( float cosValue ) { + ReturnFloat( acosf( cosValue ) ); +} +// RAVEN END + +/* +================ +idThread::Event_GetSine +================ +*/ +void idThread::Event_GetSine( float angle ) { + ReturnFloat( idMath::Sin( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetCosine +================ +*/ +void idThread::Event_GetCosine( float angle ) { + ReturnFloat( idMath::Cos( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetSquareRoot +================ +*/ +void idThread::Event_GetSquareRoot( float theSquare ) { + ReturnFloat( idMath::Sqrt( theSquare ) ); +} + +/* +================ +idThread::Event_VecNormalize +================ +*/ +void idThread::Event_VecNormalize( idVec3 &vec ) { + idVec3 n; + + n = vec; + n.Normalize(); + ReturnVector( n ); +} + +/* +================ +idThread::Event_VecLength +================ +*/ +void idThread::Event_VecLength( idVec3 &vec ) { + ReturnFloat( vec.Length() ); +} + +/* +================ +idThread::Event_VecDotProduct +================ +*/ +void idThread::Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnFloat( vec1 * vec2 ); +} + +/* +================ +idThread::Event_VecCrossProduct +================ +*/ +void idThread::Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnVector( vec1.Cross( vec2 ) ); +} + +/* +================ +idThread::Event_VecToAngles +================ +*/ +void idThread::Event_VecToAngles( idVec3 &vec ) { + idAngles ang = vec.ToAngles(); + ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +// RAVEN BEGIN +// kef 12/2/02 -- made these into events + +void idThread::Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch) +{ + idVec3 vecResult; + +// VectorRotate3(vecToBeRotated, rotateHowMuch, vecResult); + gameLocal.Printf("'vecRotate' doesn't work -- I guess Keith should find a replacement for VectorRotate3 now...\n"); + ReturnVector(vecResult); +} + +// RAVEN END + +/* +================ +idThread::Event_OnSignal +================ +*/ +void idThread::Event_OnSignal( int signal, idEntity *ent, const char *func ) { + const function_t *function; + + assert( func ); + + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + function = gameLocal.program.FindFunction( func ); + if ( !function ) { + Error( "Function '%s' not found", func ); + } + + ent->SetSignal( ( signalNum_t )signal, this, function ); +} + +/* +================ +idThread::Event_ClearSignalThread +================ +*/ +void idThread::Event_ClearSignalThread( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignalThread( ( signalNum_t )signal, this ); +} + +// RAVEN BEGIN +// kfuller: added +/* +================ +idThread::Event_ClearSignalAllThreads +================ +*/ +void idThread::Event_ClearSignalAllThreads( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignal(this, ( signalNum_t )signal); +} +// RAVEN END + +/* +================ +idThread::Event_SetCamera +================ +*/ +void idThread::Event_SetCamera( idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idCamera::GetClassType() ) ) { +// RAVEN END + Error( "Entity is not a camera" ); + return; + } + + gameLocal.SetCamera( ( idCamera * )ent ); +} + +/* +================ +idThread::Event_FirstPerson +================ +*/ +void idThread::Event_FirstPerson( void ) { + gameLocal.SetCamera( NULL ); +} + +/* +================ +idThread::Event_Trace +================ +*/ +void idThread::Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ) { + if ( mins == vec3_origin && maxs == vec3_origin ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); + } else { + gameLocal.TraceBounds( NULL, trace, start, end, idBounds( mins, maxs ), contents_mask, passEntity ); +// RAVEN END + } + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_TracePoint +================ +*/ +void idThread::Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); +// RAVEN END + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceFraction +================ +*/ +void idThread::Event_GetTraceFraction( void ) { + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceEndPos +================ +*/ +void idThread::Event_GetTraceEndPos( void ) { + ReturnVector( trace.endpos ); +} + +/* +================ +idThread::Event_GetTraceNormal +================ +*/ +void idThread::Event_GetTraceNormal( void ) { + if ( trace.fraction < 1.0f ) { + ReturnVector( trace.c.normal ); + } else { + ReturnVector( vec3_origin ); + } +} + +/* +================ +idThread::Event_GetTraceEntity +================ +*/ +void idThread::Event_GetTraceEntity( void ) { + if ( trace.fraction < 1.0f ) { + ReturnEntity( gameLocal.entities[ trace.c.entityNum ] ); + } else { + ReturnEntity( ( idEntity * )NULL ); + } +} + +/* +================ +idThread::Event_GetTraceJoint +================ +*/ +void idThread::Event_GetTraceJoint( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + ReturnString( af->GetAnimator()->GetJointName( CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ) ) ); + return; + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_GetTraceBody +================ +*/ +void idThread::Event_GetTraceBody( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + int bodyId = af->BodyForClipModelId( trace.c.id ); + idAFBody *body = af->GetAFPhysics()->GetBody( bodyId ); + if ( body ) { + ReturnString( body->GetName() ); + return; + } + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_FadeIn +================ +*/ +void idThread::Event_FadeIn( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 0.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeOut +================ +*/ +void idThread::Event_FadeOut( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 1.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeTo +================ +*/ +void idThread::Event_FadeTo( idVec3 &color, float alpha, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], alpha ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_SetShaderParm +================ +*/ +void idThread::Event_SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_GLOBAL_SHADER_PARMS ) ) { + Error( "shader parm index (%d) out of range", parmnum ); + } + + gameLocal.globalShaderParms[ parmnum ] = value; +} + +/* +================ +idThread::Event_StartMusic +================ +*/ +void idThread::Event_StartMusic( const char *text ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, text ); +} + +/* +================ +idThread::Event_Warning +================ +*/ +void idThread::Event_Warning( const char *text ) { + Warning( "%s", text ); +} + +/* +================ +idThread::Event_Error +================ +*/ +void idThread::Event_Error( const char *text ) { + Error( "%s", text ); +} + +/* +================ +idThread::Event_StrLen +================ +*/ +void idThread::Event_StrLen( const char *string ) { + int len; + + len = strlen( string ); + idThread::ReturnInt( len ); +} + +/* +================ +idThread::Event_StrLeft +================ +*/ +void idThread::Event_StrLeft( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idStr result( string, 0, num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrRight +================ +*/ +void idThread::Event_StrRight( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idThread::ReturnString( string + len - num ); +} + +/* +================ +idThread::Event_StrSkip +================ +*/ +void idThread::Event_StrSkip( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( string ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( string + num ); +} + +/* +================ +idThread::Event_StrMid +================ +*/ +void idThread::Event_StrMid( const char *string, int start, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + if ( start < 0 ) { + start = 0; + } + len = strlen( string ); + if ( start > len ) { + start = len; + } + + if ( start + num > len ) { + num = len - start; + } + + idStr result( string, start, start + num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrToFloat( const char *string ) +================ +*/ +void idThread::Event_StrToFloat( const char *string ) { + float result; + + result = atof( string ); + idThread::ReturnFloat( result ); +} + +/* +================ +idThread::Event_RadiusDamage +================ +*/ +void idThread::Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ) { + gameLocal.RadiusDamage( origin, inflictor, attacker, ignore, ignore, damageDefName, dmgPower ); +} + +/* +================ +idThread::Event_IsClient +================ +*/ +void idThread::Event_IsClient( void ) { + idThread::ReturnFloat( gameLocal.isClient ); +} + +/* +================ +idThread::Event_IsMultiplayer +================ +*/ +void idThread::Event_IsMultiplayer( void ) { + idThread::ReturnFloat( gameLocal.isMultiplayer ); +} + +/* +================ +idThread::Event_GetFrameTime +================ +*/ +void idThread::Event_GetFrameTime( void ) { + idThread::ReturnFloat( MS2SEC( gameLocal.msec ) ); +} + +/* +================ +idThread::Event_GetTicsPerSecond +================ +*/ +void idThread::Event_GetTicsPerSecond( void ) { + idThread::ReturnFloat( gameLocal.GetMHz() ); +} + +/* +================ +idThread::Event_CacheSoundShader +================ +*/ +void idThread::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idThread::Event_DebugLine +================ +*/ +void idThread::Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ) { + gameRenderWorld->DebugLine( idVec4( color.x, color.y, color.z, 0.0f ), start, end, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugArrow +================ +*/ +void idThread::Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ) { + gameRenderWorld->DebugArrow( idVec4( color.x, color.y, color.z, 0.0f ), start, end, size, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugCircle +================ +*/ +void idThread::Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ) { + gameRenderWorld->DebugCircle( idVec4( color.x, color.y, color.z, 0.0f ), origin, dir, radius, numSteps, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugBounds +================ +*/ +void idThread::Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ) { + gameRenderWorld->DebugBounds( idVec4( color.x, color.y, color.z, 0.0f ), idBounds( mins, maxs ), vec3_origin, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DrawText +================ +*/ +void idThread::Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ) { + gameRenderWorld->DrawText( text, origin, scale, idVec4( color.x, color.y, color.z, 0.0f ), gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), align, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_InfluenceActive +================ +*/ +void idThread::Event_InfluenceActive( void ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player && player->GetInfluenceLevel() ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_PlayWorldEffect +================ +*/ +void idThread::Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ) { + gameLocal.PlayEffect ( ( const idDecl * )declManager->FindEffect( effectName ), org, angle.ToMat3() ); +} + +// abahr: +/* +================ +idThread::Event_ReferenceScriptObjectProxy +================ +*/ +void idThread::Event_ReferenceScriptObjectProxy( const char* scriptObjectName ) { + ReturnEntity( gameLocal.ReferenceScriptObjectProxy(scriptObjectName) ); + Event_WaitFrame();// Needed because the constructor call is delayed by one frame +} + +/* +================ +idThread::Event_ReleaseScriptObjectProxy +================ +*/ +void idThread::Event_ReleaseScriptObjectProxy( const char* proxyName ) { + gameLocal.ReleaseScriptObjectProxy( proxyName ); + Event_WaitFrame();// Needed because the destructor call is delayed by one frame +} + +/* +================ +idThread::Event_ClampFloat +================ +*/ +void idThread::Event_ClampFloat( float min, float max, float val ) { + ReturnFloat( idMath::ClampFloat(min, max, val) ); +} + +/* +================ +idThread::Event_MinFloat +================ +*/ +void idThread::Event_MinFloat( float val1, float val2 ) { + ReturnFloat( Min(val1, val2) ); +} + +/* +================ +idThread::Event_MaxFloat +================ +*/ +void idThread::Event_MaxFloat( float val1, float val2 ) { + ReturnFloat( Max(val1, val2) ); +} + +/* +================ +idThread::Event_StrFind +================ +*/ +void idThread::Event_StrFind( idStr& sourceStr, idStr& subStr ) { + idThread::ReturnInt( sourceStr.Find(subStr.c_str()) ); +} + +// asalmon: award achievement +/* +================ +idThread::Event_AwardAchievement +================ +*/ +void idThread::Event_AwardAchievement( const char *name ) { +#ifdef _XENON + Live()->AwardAchievement(name); +#endif +} +// twhitaker: ceil, floor and intVal +/* +================ +idThread::Event_GetCeil +================ +*/ +void idThread::Event_GetCeil( float val ) { + ReturnFloat( idMath::Ceil( val ) ); +} + +/* +================ +idThread::Event_GetFloor +================ +*/ +void idThread::Event_GetFloor( float val ) { + ReturnFloat( idMath::Floor( val ) ); +} + +/* +================ +idThread::Event_ToInt +================ +*/ +void idThread::Event_ToInt( float val ) { + ReturnFloat( idMath::Ftoi( val ) ); +// ReturnFloat( idMath::FtoiFast( val ) ); +} + +// jdischler: send named event string to specified gui +/* +=============================== +idThread::Event_SendNamedEvent +=============================== +*/ +void idThread::Event_SendNamedEvent( int guiEnum, const char *namedEvent ) { + + typedef enum { + SNE_PLAYERHUD = 0, + SNE_CINEMATICHUD, + SNE_VEHICLECHUD, + SNE_CURSORHUD, + }; + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + switch( guiEnum ) { + case SNE_PLAYERHUD: + if ( player->hud ) { + player->hud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_CINEMATICHUD: + if ( player->cinematicHud ) { + player->cinematicHud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_VEHICLECHUD: + // twhitaker: I'd really just rather use player->GetHud() ... which returns the vehicle hud if the player is in a vehicle + // but this way the scripter will be able to strictly enforce which hud the named event gets played on. See idPlayer::GetHud() + if ( player->vehicleController.GetVehicle() ) { + idUserInterface * hud = player->vehicleController.GetVehicle()->GetHud(); + + if ( hud ) { + hud->HandleNamedEvent( namedEvent ); + } + } + break; + case SNE_CURSORHUD: + if ( player->GetCursorGUI() ) { + player->GetCursorGUI()->HandleNamedEvent( namedEvent ); + } + break; + } +} + +/* +================ +idThread::Event_SetMatSort +================ +*/ +void idThread::Event_SetMatSort( const char *name, const char *val ) const { + const idMaterial *mat = declManager->FindMaterial( name ); + if ( mat ) { + int srt = SS_DECAL; + if ( idStr::Icmp( val, "SS_MIN" ) == 0 ) { + srt = SS_MIN; + } else if ( idStr::Icmp( val, "SS_SUBVIEW" ) == 0 ) { + srt = SS_SUBVIEW; + } else if ( idStr::Icmp( val, "SS_PREGUI" ) == 0 ) { + srt = SS_PREGUI; + } else if ( idStr::Icmp( val, "SS_GUI" ) == 0 ) { + srt = SS_GUI; + } else if ( idStr::Icmp( val, "SS_BAD" ) == 0 ) { + srt = SS_BAD; + } else if ( idStr::Icmp( val, "SS_OPAQUE" ) == 0 ) { + srt = SS_OPAQUE; + } else if ( idStr::Icmp( val, "SS_PORTAL_SKY" ) == 0 ) { + srt = SS_PORTAL_SKY; + } else if ( idStr::Icmp( val, "SS_DECAL" ) == 0 ) { + srt = SS_DECAL; + } else if ( idStr::Icmp( val, "SS_FAR" ) == 0 ) { + srt = SS_FAR; + } else if ( idStr::Icmp( val, "SS_MEDIUM" ) == 0 ) { + srt = SS_MEDIUM; + } else if ( idStr::Icmp( val, "SS_CLOSE" ) == 0 ) { + srt = SS_CLOSE; + } else if ( idStr::Icmp( val, "SS_ALMOST_NEAREST" ) == 0 ) { + srt = SS_ALMOST_NEAREST; + } else if ( idStr::Icmp( val, "SS_NEAREST" ) == 0 ) { + srt = SS_NEAREST; + } else if ( idStr::Icmp( val, "SS_POST_PROCESS" ) == 0 ) { + srt = SS_POST_PROCESS; + } + + mat->SetSort( srt ); + } +} + +// jdischler: Adding ability to create texture streams from scripts +/* +================ +idThread::Event_BeginManualStreaming +================ +*/ +void idThread::Event_BeginManualStreaming() +{ +} + +/* +================ +idThread::Event_EndManualStreaming +================ +*/ +void idThread::Event_EndManualStreaming() +{ +} +// RAVEN END diff --git a/src/game/script/Script_Thread.h b/src/game/script/Script_Thread.h new file mode 100644 index 0000000..aab7f1f --- /dev/null +++ b/src/game/script/Script_Thread.h @@ -0,0 +1,388 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_THREAD_H__ +#define __SCRIPT_THREAD_H__ + +extern const idEventDef EV_Thread_Execute; +extern const idEventDef EV_Thread_SetCallback; +extern const idEventDef EV_Thread_TerminateThread; +extern const idEventDef EV_Thread_Pause; +extern const idEventDef EV_Thread_Wait; +extern const idEventDef EV_Thread_WaitFrame; +extern const idEventDef EV_Thread_WaitFor; +extern const idEventDef EV_Thread_WaitForThread; +extern const idEventDef EV_Thread_Print; +extern const idEventDef EV_Thread_PrintLn; +extern const idEventDef EV_Thread_Say; +extern const idEventDef EV_Thread_Assert; +extern const idEventDef EV_Thread_Trigger; +extern const idEventDef EV_Thread_SetCvar; +extern const idEventDef EV_Thread_GetCvar; +extern const idEventDef EV_Thread_Random; +extern const idEventDef EV_Thread_GetTime; +extern const idEventDef EV_Thread_KillThread; +extern const idEventDef EV_Thread_SetThreadName; +extern const idEventDef EV_Thread_GetEntity; +extern const idEventDef EV_Thread_Spawn; +extern const idEventDef EV_Thread_SetSpawnArg; +extern const idEventDef EV_Thread_SpawnString; +extern const idEventDef EV_Thread_SpawnFloat; +extern const idEventDef EV_Thread_SpawnVector; +extern const idEventDef EV_Thread_AngToForward; +extern const idEventDef EV_Thread_AngToRight; +extern const idEventDef EV_Thread_AngToUp; +extern const idEventDef EV_Thread_Sine; +extern const idEventDef EV_Thread_Cosine; +extern const idEventDef EV_Thread_Normalize; +extern const idEventDef EV_Thread_VecLength; +extern const idEventDef EV_Thread_VecDotProduct; +extern const idEventDef EV_Thread_VecCrossProduct; +extern const idEventDef EV_Thread_OnSignal; +extern const idEventDef EV_Thread_ClearSignal; +extern const idEventDef EV_Thread_SetCamera; +extern const idEventDef EV_Thread_FirstPerson; +extern const idEventDef EV_Thread_TraceFraction; +extern const idEventDef EV_Thread_TracePos; +extern const idEventDef EV_Thread_FadeIn; +extern const idEventDef EV_Thread_FadeOut; +extern const idEventDef EV_Thread_FadeTo; +extern const idEventDef EV_Thread_Restart; +extern const idEventDef EV_Thread_SetMatSort; + +// RAVEN BEGIN +// rjohnson: new blur special effect +extern const idEventDef EV_Thread_SetSpecialEffect; +extern const idEventDef EV_Thread_SetSpecialEffectParm; +// RAVEN END + +class idThread : public idClass { +private: + static idThread *currentThread; + + idThread *waitingForThread; + int waitingFor; + int waitingUntil; + idInterpreter interpreter; + + idDict spawnArgs; + + int threadNum; + idStr threadName; + + int lastExecuteTime; + int creationTime; + + bool manualControl; + + static int threadIndex; + static idList threadList; + + static trace_t trace; + + void Init( void ); + void Pause( void ); + + void Event_Execute( void ); + void Event_SetThreadName( const char *name ); + + // + // script callable Events + // + void Event_TerminateThread( int num ); + void Event_Pause( void ); + void Event_Wait( float time ); + void Event_WaitFrame( void ); + void Event_WaitFor( idEntity *ent ); + void Event_WaitForThread( int num ); + void Event_Print( const char *text ); + void Event_PrintLn( const char *text ); + void Event_Say( const char *text ); + void Event_Assert( float value ); + void Event_Trigger( idEntity *ent ); + void Event_SetCvar( const char *name, const char *value ) const; + void Event_GetCvar( const char *name ) const; + void Event_Random( float range ) const; + void Event_GetTime( void ); + void Event_KillThread( const char *name ); + void Event_GetEntity( const char *name ); + void Event_Spawn( const char *classname ); + void Event_CopySpawnArgs( idEntity *ent ); + void Event_SetSpawnArg( const char *key, const char *value ); + void Event_SpawnString( const char *key, const char *defaultvalue ); + void Event_SpawnFloat( const char *key, float defaultvalue ); + void Event_SpawnVector( const char *key, idVec3 &defaultvalue ); + void Event_ClearPersistantArgs( void ); + void Event_SetPersistantArg( const char *key, const char *value ); + void Event_GetPersistantString( const char *key ); + void Event_GetPersistantFloat( const char *key ); + void Event_GetPersistantVector( const char *key ); + void Event_AngToForward( idAngles &ang ); + void Event_AngToRight( idAngles &ang ); + void Event_AngToUp( idAngles &ang ); + void Event_GetSine( float angle ); + void Event_GetCosine( float angle ); + void Event_GetSquareRoot( float theSquare ); + void Event_VecNormalize( idVec3 &vec ); + void Event_VecLength( idVec3 &vec ); + void Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecToAngles( idVec3 &vec ); + void Event_OnSignal( int signal, idEntity *ent, const char *func ); + void Event_ClearSignalThread( int signal, idEntity *ent ); + void Event_SetCamera( idEntity *ent ); + void Event_FirstPerson( void ); + void Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ); + void Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ); + void Event_GetTraceFraction( void ); + void Event_GetTraceEndPos( void ); + void Event_GetTraceNormal( void ); + void Event_GetTraceEntity( void ); + void Event_GetTraceJoint( void ); + void Event_GetTraceBody( void ); + void Event_FadeIn( idVec3 &color, float time ); + void Event_FadeOut( idVec3 &color, float time ); + void Event_FadeTo( idVec3 &color, float alpha, float time ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_StartMusic( const char *name ); + void Event_Warning( const char *text ); + void Event_Error( const char *text ); + void Event_StrLen( const char *string ); + void Event_StrLeft( const char *string, int num ); + void Event_StrRight( const char *string, int num ); + void Event_StrSkip( const char *string, int num ); + void Event_StrMid( const char *string, int start, int num ); + void Event_StrToFloat( const char *string ); + void Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ); + void Event_IsClient( void ); + void Event_IsMultiplayer( void ); + void Event_GetFrameTime( void ); + void Event_GetTicsPerSecond( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ); + void Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ); + void Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ); + void Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ); + void Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ); + void Event_InfluenceActive( void ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetSpawnVector( const char *key, idVec3 &vec ); + void Event_GetArcSine( float sinValue ); + void Event_GetArcCosine( float cosValue ); + void Event_ClearSignalAllThreads( int signal, idEntity *ent ); + void Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch); + void Event_IsStringEmpty( const char *checkString ); + void Event_AnnounceToAI( const char *announcement); + void Event_ChangeCrossings(const char *originalType, const char *newType); + +// abahr: so we can fake having pure script objects + void Event_ReferenceScriptObjectProxy( const char* scriptObjectName ); + void Event_ReleaseScriptObjectProxy( const char* proxyName ); + void Event_ClampFloat( float min, float max, float val ); + void Event_MinFloat( float val1, float val2 ); + void Event_MaxFloat( float val1, float val2 ); + void Event_StrFind( idStr& sourceStr, idStr& subStr ); + void Event_RandomInt( float range ) const; + +// rjohnson: new blur special effect + void Event_SetSpecialEffect( int Effect, int Enabled ); + void Event_SetSpecialEffectParm( int Effect, int Parm, float Value ); + +// nmckenzie: string signaling + void Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ); +// asalmon: achievements for Xenon + void Event_AwardAchievement( const char *name); +// twhitaker: ceil, floor and intVal + void Event_GetCeil( float val ); + void Event_GetFloor( float val ); + void Event_ToInt( float val ); +// jdischler: send named event string to specified gui + void Event_SendNamedEvent( int guiEnum, const char *namedEvent ); + void Event_BeginManualStreaming( void ); + void Event_EndManualStreaming( void ); + void Event_SetMatSort( const char *name, const char *val ) const; +// RAVEN END + +public: + CLASS_PROTOTYPE( idThread ); + + idThread(); + idThread( idEntity *self, const function_t *func ); + idThread( const function_t *func ); + idThread( idInterpreter *source, const function_t *func, int args ); + idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ); + + virtual ~idThread(); + + // tells the thread manager not to delete this thread when it ends + void ManualDelete( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void EnableDebugInfo( void ) { interpreter.debug = true; }; + void DisableDebugInfo( void ) { interpreter.debug = false; }; + + void WaitMS( int time ); + void WaitSec( float time ); + void WaitFrame( void ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( const function_t *func, bool clearStack ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( idEntity *obj, const function_t *func, bool clearStack ); + +// RAVEN BEGIN +// bgeisler: added way to list functions + void ListStates( void ); + +// abahr: added helper functions for pushing parms onto stack + void ClearStack( void ); + void PushInt( int value ); + void PushFloat( float value ); + void PushVec3( const idVec3& value ); + void PushEntity( const idEntity* ent ); + void PushString( const char* str ); + void PushBool( bool value ); +// RAVEN END + + void DisplayInfo( void ); + static idThread *GetThread( int num ); + static void ListThreads_f( const idCmdArgs &args ); + static void Restart( void ); + static void ObjectMoveDone( int threadnum, idEntity *obj ); + + static idList& GetThreads( void ); + + bool IsDoneProcessing( void ); + bool IsDying ( void ); + + void End( void ); + static void KillThread( const char *name ); + static void KillThread( int num ); + bool Execute( void ); + void ManualControl( void ) { manualControl = true; CancelEvents( &EV_Thread_Execute ); }; + void DoneProcessing( void ) { interpreter.doneProcessing = true; }; + void ContinueProcessing( void ) { interpreter.doneProcessing = false; }; + bool ThreadDying( void ) { return interpreter.threadDying; }; + void EndThread( void ) { interpreter.threadDying = true; }; + bool IsWaiting( void ); + void ClearWaitFor( void ); + bool IsWaitingFor( idEntity *obj ); + void ObjectMoveDone( idEntity *obj ); + void ThreadCallback( idThread *thread ); + void DelayedStart( int delay ); + bool Start( void ); + idThread *WaitingOnThread( void ); + void SetThreadNum( int num ); + int GetThreadNum( void ); + void SetThreadName( const char *name ); + const char *GetThreadName( void ); + + void Error( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + + static idThread *CurrentThread( void ); + static int CurrentThreadNum( void ); + static bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + static void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + + static void ReturnString( const char *text ); + static void ReturnFloat( float value ); + static void ReturnInt( int value ); + static void ReturnVector( idVec3 const &vec ); +// RAVEN BEGIN +// abahr: added const + static void ReturnEntity( const idEntity *ent ); +// RAVEN END +}; + +/* +================ +idThread::WaitingOnThread +================ +*/ +ID_INLINE idThread *idThread::WaitingOnThread( void ) { + return waitingForThread; +} + +/* +================ +idThread::SetThreadNum +================ +*/ +ID_INLINE void idThread::SetThreadNum( int num ) { + threadNum = num; +} + +/* +================ +idThread::GetThreadNum +================ +*/ +ID_INLINE int idThread::GetThreadNum( void ) { + return threadNum; +} + +/* +================ +idThread::GetThreadName +================ +*/ +ID_INLINE const char *idThread::GetThreadName( void ) { + return threadName.c_str(); +} + +/* +================ +idThread::GetThreads +================ +*/ +ID_INLINE idList& idThread::GetThreads ( void ) { + return threadList; +} + +/* +================ +idThread::IsDoneProcessing +================ +*/ +ID_INLINE bool idThread::IsDoneProcessing ( void ) { + return interpreter.doneProcessing; +} + +/* +================ +idThread::IsDying +================ +*/ +ID_INLINE bool idThread::IsDying ( void ) { + return interpreter.threadDying; +} + +#endif /* !__SCRIPT_THREAD_H__ */ diff --git a/src/game/spawner.cpp b/src/game/spawner.cpp new file mode 100644 index 0000000..e188a08 --- /dev/null +++ b/src/game/spawner.cpp @@ -0,0 +1,624 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +Spawner.cpp + +================ +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" +#include "vehicle/Vehicle.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AI_Util.h" + +const idEventDef EV_Spawner_RemoveNullActiveEntities( "removeNullActiveEntities" ); +const idEventDef EV_Spawner_NumActiveEntities( "numActiveEntities", "", 'd' ); +const idEventDef EV_Spawner_GetActiveEntity( "getActiveEntity", "d", 'e' ); + +CLASS_DECLARATION( idEntity, rvSpawner ) + EVENT( EV_Activate, rvSpawner::Event_Activate ) + EVENT( EV_Spawner_RemoveNullActiveEntities, rvSpawner::Event_RemoveNullActiveEntities ) + EVENT( EV_Spawner_NumActiveEntities, rvSpawner::Event_NumActiveEntities ) + EVENT( EV_Spawner_GetActiveEntity, rvSpawner::Event_GetActiveEntity ) +END_CLASS + +/* +============== +rvSpawner::Spawn +============== +*/ +void rvSpawner::Spawn( void ){ + GetPhysics()->SetContents( 0 ); + + // TEMP: read max_team_test until we can get it out of all the current maps + if ( !spawnArgs.GetInt ( "max_active", "4", maxActive ) ) { + if ( spawnArgs.GetInt ( "max_team_test", "4", maxActive ) ) { + gameLocal.Warning ( "spawner '%s' using outdated 'max_team_test', please change to 'max_active'", GetName() ); + } + } + + maxToSpawn = spawnArgs.GetInt( "count", "-1" ); + skipVisible = spawnArgs.GetBool ( "skipvisible", "1" ); + spawnWaves = spawnArgs.GetInt( "waves", "1" ); + spawnDelay = SEC2MS( spawnArgs.GetFloat( "delay", "2" ) ); + numSpawned = 0; + nextSpawnTime = 0; + + // Spawn waves has to be less than max active + if ( spawnWaves > maxActive ) { + spawnWaves = maxActive; + } + + FindSpawnTypes ( ); +} + +/* +============== +rvSpawner::Save +============== +*/ +void rvSpawner::Save ( idSaveGame *savefile ) const{ + savefile->WriteInt( numSpawned ); + savefile->WriteInt( maxToSpawn ); + savefile->WriteFloat( nextSpawnTime ); + savefile->WriteInt( maxActive ); + + int i; + savefile->WriteInt( currentActive.Num() ); + for( i = 0; i < currentActive.Num(); i++ ) { + currentActive[i].Save ( savefile ); + } + + savefile->WriteInt( spawnWaves ); + savefile->WriteInt( spawnDelay ); + savefile->WriteBool( skipVisible ); + + savefile->WriteInt( spawnPoints.Num() ); + for ( i = 0; i < spawnPoints.Num(); i++ ) { + spawnPoints[ i ].Save ( savefile ); + } + + savefile->WriteInt ( callbacks.Num() ); + for ( i = 0; i < callbacks.Num(); i ++ ) { + callbacks[i].ent.Save ( savefile ); + savefile->WriteString ( callbacks[i].event ); + } +} + +/* +============== +rvSpawner::Restore +============== +*/ +void rvSpawner::Restore ( idRestoreGame *savefile ){ + int num; + int i; + + savefile->ReadInt( numSpawned ); + savefile->ReadInt( maxToSpawn ); + savefile->ReadFloat( nextSpawnTime ); + savefile->ReadInt( maxActive ); + + savefile->ReadInt( num ); + currentActive.Clear ( ); + currentActive.SetNum( num ); + for( i = 0; i < num; i++ ) { + currentActive[i].Restore ( savefile ); + } + + savefile->ReadInt( spawnWaves ); + savefile->ReadInt( spawnDelay ); + savefile->ReadBool( skipVisible ); + + savefile->ReadInt( num ); + spawnPoints.SetNum( num); + for( i = 0; i < num; i ++ ) { + spawnPoints[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + callbacks.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + callbacks[i].ent.Restore ( savefile ); + savefile->ReadString ( callbacks[i].event ); + } + + FindSpawnTypes (); +} + +/* +============== +rvSpawner::FindSpawnTypes + +Generate the list of classnames to spawn from the spawnArgs. Anything matching the +prefix "def_spawn" will be included in the list. +============== +*/ +void rvSpawner::FindSpawnTypes ( void ){ + const idKeyValue *kv; + for ( kv = spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + spawnTypes.Append ( kv->GetValue ( ) ); + + // precache decls + declManager->FindType( DECL_AF, kv->GetValue(), true, false ); + } +} + +/* +============== +rvSpawner::FindTargets +============== +*/ +void rvSpawner::FindTargets ( void ) { + int i; + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + trace_t tr; + + idEntity::FindTargets ( ); + + // Copy the relevant targets to the spawn point list (right now only target_null entities) + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = ent; + + if( !spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + gameLocal.TraceBounds( this, tr, ent->GetPhysics()->GetOrigin(), ent->GetPhysics()->GetOrigin(), bounds, MASK_MONSTERSOLID, NULL ); + if ( gameLocal.entities[tr.c.entityNum] && !gameLocal.entities[tr.c.entityNum]->IsType ( idActor::GetClassType ( ) ) ) { + //drop a console warning here + gameLocal.Warning ( "Spawner '%s' can't spawn at point '%s', the monster won't fit.", GetName(), ent->GetName() ); + } + } + } +} + +/* +============== +rvSpawner::ValidateSpawnPoint +============== +*/ +bool rvSpawner::ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ){ + trace_t tr; + if( spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + return true; + } + + gameLocal.TraceBounds( this, tr, origin, origin, bounds, MASK_MONSTERSOLID, NULL ); + return tr.fraction >= 1.0f; +} + +/* +============== +rvSpawner::AddSpawnPoint +============== +*/ +void rvSpawner::AddSpawnPoint ( idEntity* point ) { + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = point; + + // If there were no spawnPoints then start with the delay + if ( spawnPoints.Num () == 1 ) { + nextSpawnTime = gameLocal.time + spawnDelay; + } +} + +/* +============== +rvSpawner::RemoveSpawnPoint +============== +*/ +void rvSpawner::RemoveSpawnPoint ( idEntity* point ) { + int i; + for ( i = spawnPoints.Num()-1; i >= 0; i -- ) { + if ( spawnPoints[i] == point ) { + spawnPoints.RemoveIndex ( i ); + break; + } + } +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +void rvSpawner::AddCallback ( idEntity* owner, const idEventDef* ev ) { + spawnerCallback_t& callback = callbacks.Alloc ( ); + callback.event = ev->GetName ( ); + callback.ent = owner; +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +idEntity *rvSpawner::GetSpawnPoint ( void ) { + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + idList< idEntityPtr > spawns; + int spawnIndex; + idEntity* spawnEnt; + + // Run through all spawnPoints and choose a random one. Each time a spawn point is excluded + // it will be removed from the list until there are no more items in the list. + for ( spawns = spawnPoints ; spawns.Num(); spawns.RemoveIndex ( spawnIndex ) ) { + spawnIndex = gameLocal.random.RandomInt ( spawns.Num() ); + spawnEnt = spawns[spawnIndex]; + + if ( !spawnEnt || !spawnEnt->GetPhysics() ) { + continue; + } + + // Check to see if something is in the way at this spawn point + if ( !ValidateSpawnPoint ( spawnEnt->GetPhysics()->GetOrigin(), bounds ) ) { + continue; + } + + // Skip the spawn point because its currently visible? + if ( skipVisible && gameLocal.GetLocalPlayer()->CanSee ( spawnEnt, true ) ) { + continue; + } + + // Found one! + return spawnEnt; + } + + return NULL; +} + +/* +============== +rvSpawner::GetSpawnType +============== +*/ +const char* rvSpawner::GetSpawnType ( idEntity* spawnPoint ) { + const idKeyValue* kv; + + if ( spawnPoint ) { + // If the spawn point has any "def_spawn" keys then they override the normal spawn keys + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", NULL ); + if ( kv ) { + const char* types [ MAX_SPAWN_TYPES ]; + int typeCount; + + for ( typeCount = 0; + typeCount < MAX_SPAWN_TYPES && kv; + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", kv ) ) { + types [ typeCount++ ] = kv->GetValue ( ).c_str(); + } + + return types[ gameLocal.random.RandomInt( typeCount ) ]; + } + } + + // No spawn types? + if ( !spawnTypes.Num ( ) ) { + return ""; + } + + // Return from the spawners list of types + return spawnTypes[ gameLocal.random.RandomInt( spawnTypes.Num() ) ]; +} + +/* +============== +rvSpawner::CopyPrefixedSpawnArgs +============== +*/ +void rvSpawner::CopyPrefixedSpawnArgs( idEntity *src, const char *prefix, idDict &args ){ + const idKeyValue *kv = src->spawnArgs.MatchPrefix( prefix, NULL ); + while( kv ) { + args.Set( kv->GetKey().c_str() + idStr::Length( prefix ), kv->GetValue() ); + kv = src->spawnArgs.MatchPrefix( prefix, kv ); + } +} + +/* +============== +rvSpawner::SpawnEnt +============== +*/ +bool rvSpawner::SpawnEnt( void ){ + idDict args; + idEntity* spawnPoint; + idEntity* spawnedEnt; + const char* temp; + + // Find a spawn point to spawn the entity + spawnPoint = GetSpawnPoint ( ); + if( !spawnPoint ){ + return false; + } + + // No valid spawn types for this point + temp = GetSpawnType ( spawnPoint ); + if ( !temp || !*temp ) { + gameLocal.Warning ( "Spawner '%s' could not find any valid spawn types for spawn point '%s'", GetName(), spawnPoint->GetName() ); + return false; + } + + // Build the spawn parameters for the entity about to be spawned + args.Set ( "origin", spawnPoint->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", spawnPoint->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", temp ); + args.SetBool ( "forceEnemy", spawnArgs.GetBool ( "auto_target", "1" ) ); + args.SetBool ( "faceEnemy", spawnArgs.GetBool ( "faceEnemy", "0" ) ); + + // Copy all keywords prefixed with "spawn_" to the entity being spawned. + CopyPrefixedSpawnArgs( this, "spawn_", args ); + if( spawnPoint != this ) { + CopyPrefixedSpawnArgs( spawnPoint, "spawn_", args ); + } + + // Spawn the entity + if ( !gameLocal.SpawnEntityDef ( args, &spawnedEnt ) ) { + return false; + } + + // Activate the spawned entity + spawnedEnt->ProcessEvent( &EV_Activate, this ); + + // Play a spawning effect if it has one - do we possibly want some script hooks in here? + gameLocal.PlayEffect ( spawnArgs, "fx_spawning", spawnPoint->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3() ); + + // script function for spawning guys + if( spawnArgs.GetString( "call", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + + // script function for the guy being spawned + if ( spawnArgs.GetString( "call_spawned", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( spawnedEnt, temp ); + } + + // Call all of our callbacks + int c; + for ( c = callbacks.Num() - 1; c >= 0; c-- ) { + if ( callbacks[c].ent ) { + callbacks[c].ent->ProcessEvent ( idEventDef::FindEvent ( callbacks[c].event ), this, spawnedEnt ); + } + } + + // Activate the spawn point entity when an enemy is spawned there and all of its targets + if( spawnPoint != this ){ + spawnPoint->ProcessEvent( &EV_Activate, spawnPoint ); + spawnPoint->ActivateTargets( spawnedEnt ); + + // One time use on this target? + if ( spawnPoint->spawnArgs.GetBool ( "remove" ) ) { + spawnPoint->PostEventMS ( &EV_Remove, 0 ); + } + } + + // Increment the total number spawned + numSpawned++; + + return true; +} + +/* +============== +rvSpawner::Think +============== +*/ +void rvSpawner::Think( void ){ + if ( thinkFlags & TH_THINK ) { + if( ActiveListChanged() ) {// If an entity has been removed and we have not been informed via Detach + nextSpawnTime = gameLocal.GetTime() + spawnDelay; + } + + CheckSpawn ( ); + } +} + +/* +============== +rvSpawner::CheckSpawn +============== +*/ +void rvSpawner::CheckSpawn ( void ) { + int count; + + // Any spawn points? + if ( !spawnPoints.Num ( ) ) { + return; + } + + // Is it time to spawn yet? + if ( nextSpawnTime == 0 || gameLocal.time < nextSpawnTime ) { + return; + } + + // Any left to spawn? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ){ + return; + } + + // Spawn in waves? + for ( count = 0; count < spawnWaves; count ++ ) { + // Too many active? + if( currentActive.Num() >= maxActive ) { + return; + } + + // Spawn a new entity + SpawnEnt ( ); + + // Are we at the limit now? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ) { + CallScriptEvents( "script_used_up", this ); + PostEventMS ( &EV_Remove, 0 ); + break; + } + } + + // Dont spawn again until after the delay + nextSpawnTime = gameLocal.time + spawnDelay; +} + +/* +============== +rvSpawner::CallScriptEvents +============== +*/ +void rvSpawner::CallScriptEvents( const char* prefixKey, idEntity* parm ) { + if( !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spawnArgs.MatchPrefix(prefixKey); kv; kv = spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( parm, 0 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +============== +rvSpawner::ActiveListChanged +============== +*/ +bool rvSpawner::ActiveListChanged() { + int previousCount = currentActive.Num(); + + currentActive.RemoveNull(); + + return previousCount > currentActive.Num(); +} + +/* +============== +rvSpawner::Attach + +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Attach ( idEntity* ent ) { + currentActive.AddUnique( ent ); +} + +/* +============== +rvSpawner::Detach + +Detaches the given AI from the spawner which will free up an active spot for spawning. +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Detach ( idEntity* ent ){ + currentActive.Remove( ent ); + + nextSpawnTime = gameLocal.GetTime() + spawnDelay; +} + +/* +============== +rvSpawner::Event_Activate +============== +*/ +void rvSpawner::Event_Activate ( idEntity *activator ) { + + // "trigger_only" spawners will attempt to spawn when triggered + if ( spawnArgs.GetBool ( "trigger_only" ) ) { + // Update next spawn time to follo CheckSpawn into thinking its time to spawn again + nextSpawnTime = gameLocal.time; + CheckSpawn ( ); + return; + } + + // If nextSpawnTime is zero then the spawner is currently deactivated + if ( nextSpawnTime == 0 ) { + // Start thinking + BecomeActive( TH_THINK ); + + // Allow immediate spawn + nextSpawnTime = gameLocal.time; + + // Spawn any ai targets and add them to the current count + ActivateTargets ( this ); + } else { + nextSpawnTime = 0; + BecomeInactive( TH_THINK ); + + // Remove the spawner if need be + if ( spawnArgs.GetBool ( "remove", "1" ) ) { + PostEventMS ( &EV_Remove, 0 ); + } + } +} + +/* +============== +rvSpawner::Event_RemoveNullActiveEntities +============== +*/ +void rvSpawner::Event_RemoveNullActiveEntities( void ) { + for( int ix = currentActive.Num() - 1; ix >= 0; --ix ) { + if( !currentActive[ix].IsValid() ) { + currentActive.RemoveIndex( ix ); + } + } +} + +/* +============== +rvSpawner::Event_NumActiveEntities +============== +*/ +void rvSpawner::Event_NumActiveEntities( void ) { + idThread::ReturnInt( currentActive.Num() ); +} + +/* +============== +rvSpawner::Event_GetActiveEntity +============== +*/ +void rvSpawner::Event_GetActiveEntity( int index ) { + idThread::ReturnEntity( (index < 0 || index >= currentActive.Num()) ? NULL : currentActive[index] ); +} + diff --git a/src/game/spawner.h b/src/game/spawner.h new file mode 100644 index 0000000..fa0cef1 --- /dev/null +++ b/src/game/spawner.h @@ -0,0 +1,144 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +Spawner.h + +================ +*/ + +#ifndef __GAME_SPAWNER_H__ +#define __GAME_SPAWNER_H__ + +const int MAX_SPAWN_TYPES = 32; + +class rvSpawner; + +typedef void (*spawnerCallbackProc_t) ( rvSpawner* spawner, idEntity* spawned, int userdata ); + +typedef struct { + idEntityPtr ent; + idStr event; +} spawnerCallback_t; + +/* +=============================================================================== + + rvSpawner + +=============================================================================== +*/ +class rvSpawner : public idEntity { +public: + CLASS_PROTOTYPE( rvSpawner ); + + void Spawn ( void ); + void Think ( void ); + + void Attach ( idEntity* ent ); + void Detach ( idEntity* ent ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void AddSpawnPoint ( idEntity* point ); + void RemoveSpawnPoint ( idEntity* point ); + + int GetNumSpawnPoints ( void ) const; + int GetNumActive ( void ) const; + int GetMaxActive ( void ) const; + idEntity* GetSpawnPoint ( int index ); + + virtual void FindTargets ( void ); + bool ActiveListChanged ( void ); + + void CallScriptEvents ( const char* prefixKey, idEntity* parm ); + + void AddCallback ( idEntity* owner, const idEventDef* ev ); + +protected: + + int numSpawned; + int maxToSpawn; + float nextSpawnTime; + int maxActive; + idList< idEntityPtr > currentActive; + int spawnWaves; + int spawnDelay; + bool skipVisible; + idStrList spawnTypes; + + idList< idEntityPtr > spawnPoints; + + idList< spawnerCallback_t > callbacks; + + // Check to see if its time to spawn + void CheckSpawn ( void ); + + // Spawn a new entity + bool SpawnEnt ( void ); + + // Populate the spawnType list with the available spawn types + void FindSpawnTypes ( void ); + + // Get a random spawnpoint to spawn at + idEntity* GetSpawnPoint ( void ); + + // Get a random spawn type + const char* GetSpawnType ( idEntity* spawnPoint ); + + // Validate the given spawn point for spawning + bool ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ); + + // Copy key/values from the given entity to the given dictionary using the specified prefix + void CopyPrefixedSpawnArgs ( idEntity *src, const char *prefix, idDict &args ); + +private: + + void Event_Activate ( idEntity *activator ); + void Event_RemoveNullActiveEntities( void ); + void Event_NumActiveEntities ( void ); + void Event_GetActiveEntity ( int index ); +}; + + +ID_INLINE int rvSpawner::GetNumSpawnPoints( void ) const { + return spawnPoints.Num ( ); +} + +ID_INLINE idEntity* rvSpawner::GetSpawnPoint( int index ) { + return spawnPoints[index]; +} + +ID_INLINE int rvSpawner::GetNumActive( void ) const { + return currentActive.Num(); +} + +ID_INLINE int rvSpawner::GetMaxActive( void ) const { + return maxActive; +} + +#endif // __GAME_SPAWNER_H__ diff --git a/src/game/vehicle/Vehicle.cpp b/src/game/vehicle/Vehicle.cpp new file mode 100644 index 0000000..af673d9 --- /dev/null +++ b/src/game/vehicle/Vehicle.cpp @@ -0,0 +1,1706 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Effect.h" +#include "../ai/AI_Manager.h" +#include "../Projectile.h" + +#define VEHICLE_CRASH_DELAY 500 +#define VEHICLE_HAZARD_TIMEOUT 5000 +#define VEHICLE_LOCK_TIMEOUT 5000 + +const idEventDef EV_LaunchProjectiles( "launchProjectiles", "d" ); +const idEventDef EV_HUDShockWarningOff( ""); +const idEventDef EV_StalledRestart( "", "dd" ); +const idEventDef EV_GetViewAngles( "getViewAngles", NULL, 'v' ); + +CLASS_DECLARATION( idActor, rvVehicle ) + EVENT( EV_Door_Lock, rvVehicle::Event_Lock ) + EVENT( EV_Door_IsLocked, rvVehicle::Event_IsLocked ) + EVENT( AI_EnableMovement, rvVehicle::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvVehicle::Event_DisableMovement ) + EVENT( EV_Player_EnableWeapon, rvVehicle::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, rvVehicle::Event_DisableWeapon ) + EVENT( AI_EnableClip, rvVehicle::Event_EnableClip ) + EVENT( AI_DisableClip, rvVehicle::Event_DisableClip ) + EVENT( EV_Activate, rvVehicle::Event_Activate ) + EVENT( EV_LaunchProjectiles, rvVehicle::Event_LaunchProjectiles ) + EVENT( AI_SetScript, rvVehicle::Event_SetScript ) + EVENT( AI_SetHealth, rvVehicle::Event_SetHealth ) + EVENT( EV_HUDShockWarningOff, rvVehicle::Event_HUDShockWarningOff ) + EVENT( EV_StalledRestart, rvVehicle::Event_StalledRestart ) + EVENT( EV_GetViewAngles, rvVehicle::Event_GetViewAngles ) +END_CLASS + +/* +===================== +rvVehicle::rvVehicle +===================== +*/ +rvVehicle::rvVehicle ( void ) { + autoRight = false; + hud = NULL; + shieldModel = NULL; + shieldMaxHealth = 0; + hazardWarningTime = 0; + lockWarningTime = 0; + godModeDamage = 0; + drivers = 0; + + autoCorrectionBegin = 0; + + crashEffect = 0; + crashTime = 0; + crashNextSound = 0; + + fl.networkSync = true; +} + +rvVehicle::~rvVehicle ( void ) { + int i; + + // Force all the drivers out + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver ( ); + if ( !driver ) { + continue; + } + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + } + + positions.Clear ( ); +} + +/* +================ +rvVehicle::Spawn +================ +*/ +void rvVehicle::Spawn( void ) { + const char* temp; + + memset ( &vfl, 0, sizeof(vfl) ); + + SetPositions ( ); + + healthMax = health; + healthLow = spawnArgs.GetInt ( "lowhealth", va("%d", health / 10 ) ); + damageStaticChance = spawnArgs.GetFloat ( "damageStaticChance", "0" ); + crashSpeedSmall = spawnArgs.GetFloat ( "crashSpeedSmall", "50" ); + crashSpeedMedium = spawnArgs.GetFloat ( "crashSpeedMedium", "125" ); + crashSpeedLarge = spawnArgs.GetFloat ( "crashSpeedLarge", "200" ); + crashDamage = spawnArgs.GetString ( "def_crashDamage" ); + + healthRegenDelay = SEC2MS(spawnArgs.GetFloat( "healthRegenDelay", "0" )); + healthRegenRate = spawnArgs.GetInt( "healthRegenRate", "0" ); + healthRegenAmount.Init( gameLocal.time, 0, healthMax, healthMax ); + + vfl.disableMovement = spawnArgs.GetBool ( "disableMovement", "0" ); + vfl.disableWeapons = spawnArgs.GetBool ( "disableWeapon", "0" ); + vfl.scripted = 0; + vfl.flipEject = spawnArgs.GetBool( "allowFlipEject", "1" ); + + health = spawnArgs.GetInt ( "health", "100" ); + fl.takedamage = ( health > 0 ); + + // Load the HUD + if ( NULL != ( temp = spawnArgs.GetString( "gui_hud", "" ) ) ) { + hud = uiManager->FindGui( temp, true, false, false ); + if ( hud ) { + hud->SetStateInt ( "vehicle_id", spawnArgs.GetInt ( "hudid" ) ); + hud->Activate( true, gameLocal.time ); + } + } + + // Get shield parameters + shieldMaxHealth = spawnArgs.GetInt ( "shieldHealth", "0" ); + shieldRegenTime = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenTime", "0" ) ); + shieldRegenDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenDelay", "0" ) ); + shieldHitTime = 0; + shieldHealth.Init ( gameLocal.time, 0, shieldMaxHealth, shieldMaxHealth ); + + SetCombatModel(); + + cachedContents = GetPhysics()->GetContents(); + + funcs.enter.Init( spawnArgs.GetString( "enter_script" ) ); + funcs.exit.Init( spawnArgs.GetString( "exit_script" ) ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::Save +================ +*/ +void rvVehicle::Save ( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt ( positions.Num ( ) ); + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].Save ( savefile ); + } + savefile->WriteInt ( drivers ); + + savefile->WriteUserInterface( hud, false ); + + savefile->WriteFloat ( crashSpeedSmall ); + savefile->WriteFloat ( crashSpeedMedium ); + savefile->WriteFloat ( crashSpeedLarge ); + savefile->WriteString ( crashDamage ); + crashEffect.Save ( savefile ); + savefile->WriteInt ( crashNextSound ); + savefile->WriteInt ( crashTime ); + + savefile->WriteFloat ( autoRightDir ); + savefile->WriteBool ( autoRight ); + + savefile->WriteInt( autoCorrectionBegin ); + + savefile->Write( &vfl, sizeof( vfl ) ); + + savefile->WriteFloat ( damageStaticChance ); + + savefile->WriteFloat ( shieldMaxHealth ); + savefile->WriteInterpolate( shieldHealth ); + savefile->WriteInt ( shieldHitTime ); + savefile->WriteFloat ( shieldRegenTime ); + savefile->WriteInt ( shieldRegenDelay ); +// TOSAVE: idClipModel* shieldModel; + + savefile->WriteInt( healthRegenDelay ); + savefile->WriteInt( healthRegenRate ); + savefile->WriteInterpolate( healthRegenAmount ); + + savefile->WriteInt ( hazardWarningTime ); + savefile->WriteInt ( lockWarningTime ); + savefile->WriteInt ( healthMax ); + savefile->WriteInt ( healthLow ); + savefile->WriteInt ( godModeDamage ); + + savefile->WriteInt ( cachedContents ); + + funcs.enter.Save( savefile ); + funcs.exit.Save ( savefile ); + +// cnicholson: Don't save crash Velocities, they are assigned in Restore +// savefile->WriteFloat( crashVelocitySmall ); // cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityMedium );// cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityLarge ); // cnicholson: Added unsaved var + +// TOSAVE: idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; + +} + +/* +================ +rvVehicle::Restore +================ +*/ +void rvVehicle::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + positions.Clear(); + positions.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + positions[i].Restore ( savefile ); + } + savefile->ReadInt ( drivers ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + + savefile->ReadFloat ( crashSpeedSmall ); + savefile->ReadFloat ( crashSpeedMedium ); + savefile->ReadFloat ( crashSpeedLarge ); + savefile->ReadString ( crashDamage ); + crashEffect.Restore ( savefile ); + savefile->ReadInt ( crashNextSound ); + savefile->ReadInt ( crashTime ); + + savefile->ReadFloat ( autoRightDir ); + savefile->ReadBool ( autoRight ); + + savefile->ReadInt( (int&)autoCorrectionBegin ); + + savefile->Read( &vfl, sizeof( vfl ) ); + + savefile->ReadFloat ( damageStaticChance ); + + savefile->ReadFloat ( shieldMaxHealth ); + savefile->ReadInterpolate( shieldHealth ); + savefile->ReadInt ( shieldHitTime ); + savefile->ReadFloat ( shieldRegenTime ); + savefile->ReadInt ( shieldRegenDelay ); +// TORESTORE: idClipModel* shieldModel; + + savefile->ReadInt( healthRegenDelay ); + savefile->ReadInt( healthRegenRate ); + savefile->ReadInterpolate( healthRegenAmount ); + + savefile->ReadInt ( hazardWarningTime ); + savefile->ReadInt ( lockWarningTime ); + savefile->ReadInt ( healthMax ); + savefile->ReadInt ( healthLow ); + savefile->ReadInt ( godModeDamage ); + + savefile->ReadInt ( cachedContents ); + + funcs.enter.Restore ( savefile ); + funcs.exit.Restore ( savefile ); + + SetCombatModel ( ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::SetPositions +================ +*/ +void rvVehicle::SetPositions ( void ) { + int positionCount; + int index; + const idKeyValue* kv; + + // Count the positions first so we can allocate them all at once + positionCount = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + positionCount++; + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } + + // Every vehicle needs a def_position in it's def file + if ( positionCount == 0) { + //gameLocal.Error ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + gameLocal.Warning ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + positionCount = 1; + spawnArgs.Set( "def_position", "vehicle_ai_null_position" ); + } + + // Initialize the positions + positions.SetNum ( positionCount ); + + // Initialize all of the positions + index = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + const idDict* dict; + + // Get the position dictionary + dict = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Initialize the position + positions[index++].Init ( this, *dict ); + + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } +} + +/* +================ +rvVehicle::SetCombatModel +================ +*/ +void rvVehicle::SetCombatModel ( void ) { + idActor::SetCombatModel ( ); + + if ( shieldMaxHealth ) { + idBounds bounds; + bounds.Clear ( ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMins", "0 0 0" ) ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMaxs", "0 0 0" ) ); + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + shieldModel = NULL; + } + + //twhitaker: dodecahedron support + idStr shieldModelName; + if ( spawnArgs.GetString( "shieldModel", "", shieldModelName ) ) { + if ( shieldModelName.Length() && !shieldModelName.Icmp( "dodecahedron" ) ) { + idTraceModel trm; + trm.SetupDodecahedron ( GetPhysics()->GetBounds() ); + shieldModel = new idClipModel ( trm ); + } + } + //twhitaker: end + + if ( !shieldModel ) { + shieldModel = new idClipModel ( idTraceModel ( bounds, spawnArgs.GetInt ( "shieldSides", "6" ) ) ); + } + + shieldModel->SetOwner ( this ); + shieldModel->SetContents ( CONTENTS_SOLID ); + } else { + shieldModel = NULL; + } +} + +/* +================ +rvVehicle::LinkCombat +================ +*/ +void rvVehicle::LinkCombat ( void ) { + if ( fl.hidden ) { + return; + } + + if ( g_debugVehicle.GetInteger() == 1 && shieldModel ) { + collisionModelManager->DrawModel( shieldModel->GetCollisionModel(), renderEntity.origin, renderEntity.axis, vec3_origin, mat3_identity, 0.0f ); + } + + if ( shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 && HasDrivers ( ) ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + shieldModel->Link( this, 0, renderEntity.origin, renderEntity.axis ); +// RAVEN END + + if ( combatModel ) { + combatModel->Unlink ( ); + } + } else { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + + if ( shieldModel ) { + shieldModel->Unlink ( ); + } + } +} + +/* +================ +rvVehicle::ClientPredictionThink +================ +*/ +void rvVehicle::ClientPredictionThink ( void ) { + Think ( ); +} + +/* +================ +rvVehicle::WriteToSnapshot +================ +*/ +void rvVehicle::WriteToSnapshot ( idBitMsgDelta &msg ) const { + int i; + // TODO: Check that this conditional write to delta message is OK + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].WriteToSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::ReadFromSnapshot +================ +*/ +void rvVehicle::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + int i; + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].ReadFromSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::UpdateState +================ +*/ +void rvVehicle::UpdateState ( void ) { + rvVehiclePosition* pos; + pos = &positions[0]; + + vfl.forward = (pos->IsOccupied() && pos->mInputCmd.forwardmove > 0); + vfl.backward = (pos->IsOccupied() && pos->mInputCmd.forwardmove < 0); + vfl.right = (pos->IsOccupied() && pos->mInputCmd.rightmove > 0); + vfl.left = (pos->IsOccupied() && pos->mInputCmd.rightmove < 0); + vfl.driver = pos->IsOccupied(); + vfl.strafe = (pos->IsOccupied() && pos->mInputCmd.buttons & BUTTON_STRAFE ); +} + + +/* +================ +rvVehicle::Think +================ +*/ +void rvVehicle::Think ( void ) { + UpdateState(); + UpdateAnimState ( ); + UpdateIncomingProjectiles(); + + // If we are the current debug entity then output some info to the hud + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, this ) ) { + gameDebug.SetInt ( "vehicle", 1 ); + gameDebug.SetInt ( "shields", shieldHealth.GetCurrentValue(gameLocal.time) ); + gameDebug.SetInt ( "positions", positions.Num() ); + gameDebug.SetInt ( "drivers", drivers ); + } + + if ( g_debugVehicle.GetInteger() == 1 ) { + idMat3 flatAxis = idAngles(0,GetPhysics()->GetAxis().ToAngles().yaw,0).ToMat3(); + gameRenderWorld->DebugLine ( colorOrange, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[0] ); + gameRenderWorld->DebugLine ( colorYellow, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[1] ); + gameRenderWorld->DebugLine ( colorCyan, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() - 50.0f * flatAxis[2] ); + + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + +/* VEH_FIXME: where to put this? + // For engine glow + renderEntity.shaderParms[7] = mEngineStatus.GetCurrentValue ( gameLocal.time ); +*/ + + if( thinkFlags & TH_THINK ) { + int i; + + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPrePhysics( ); + } + + if( autoRight ) { + if( idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) < 30.0f ) { + GetPhysics()->SetLinearVelocity( vec3_origin ); + autoRight = false; + } else { + idVec3 upSpeed = -GetPhysics()->GetGravityNormal() * 72.0f; + idMat3 axis = GetPhysics()->GetAxis(); + + // Rotate around the forward axis + axis.RotateRelative ( 0, (renderEntity.axis.ToAngles().roll<0?180:-180) * MS2SEC(gameLocal.GetMSec()) * 1.5f ); + GetPhysics()->SetAxis ( axis ); + + // Move up to make room for the rotation + GetPhysics()->SetLinearVelocity( upSpeed ); + } + } + + // twhitaker: nothing special here + RunPrePhysics(); + + // Run the physics + RunPhysics(); + + // twhitaker: nothing special here + RunPostPhysics(); + + // Give each position a chance to think after physics has been run + vfl.godmode = false; + fl.notarget = false; + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPostPhysics( ); + + // Include the eye origin into the bounds to ensure the driver + // is inside the render bounds when the vehicle is rendered. + if ( positions[i].IsOccupied ( ) ) { + AddToBounds ( positions[i].GetEyeOrigin ( ) ); + + // Transfer godmode from driving players + if ( positions[i].mDriver && positions[i].mDriver->IsType ( idPlayer::GetClassType() ) ) { + if ( static_cast(positions[i].mDriver.GetEntity())->godmode ) { + vfl.godmode = true; + } + } + + // Inherit notarget if our driver has it on + if ( positions[i].mDriver && positions[i].mDriver->fl.notarget ) { + fl.notarget = true; + } + } + } + + // If the vehicle is flipped then kick out all drivers + if ( HasDrivers() && IsFlipped ( ) && GetPhysics()->GetLinearVelocity ( ).LengthSqr() < (100.0f * 100.0f)) { + if ( spawnArgs.GetBool( "locked_flip_death", false ) ) { + Killed( this, this, 999999999, GetPhysics()->GetLinearVelocity(), 0 ); + } else if ( vfl.flipEject ) { + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver (); + if ( driver ) { + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + } + } + } + } + + // Regenerate the shield + if ( shieldMaxHealth && shieldHealth.GetCurrentValue(gameLocal.time) < shieldMaxHealth ) { + if ( gameLocal.time > shieldHitTime + shieldRegenDelay && shieldHealth.IsDone ( gameLocal.time ) ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldRecharge", SND_CHANNEL_BODY2, 0, false, NULL ); + + float regenTime = shieldHealth.GetCurrentValue(gameLocal.time); + regenTime /= (float)shieldMaxHealth; + regenTime = 1.0f - regenTime; + regenTime *= (float)shieldRegenTime; + shieldHealth.Init ( gameLocal.time, regenTime, shieldHealth.GetCurrentValue(gameLocal.time), shieldMaxHealth ); + } + } + + // Regenerate health, if needed + // do nothing if the vehicle is at full hit points + if (health < healthMax) + { + // also do nothing if we've been damaged less than healthRegenDelay seconds ago + if ( healthRegenRate && shieldHitTime + healthRegenDelay <= gameLocal.time ) + { + // do we need to start the interpolation? + if ( healthRegenAmount.IsDone( gameLocal.time )) + { + healthRegenAmount.Init( gameLocal.time, SEC2MS((float)(healthMax - health)/healthRegenRate), + health, healthMax ); + } + else // get our interpolated health value for the current time. + { + health = healthRegenAmount.GetCurrentValue( gameLocal.time ); + } + } + } + + // Check hazards + if ( hazardWarningTime && gameLocal.time > hazardWarningTime + VEHICLE_HAZARD_TIMEOUT ) { + hazardWarningTime = 0; + StartSound ( "snd_voiceSafe", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "info_safe" ); + } + } + + // Stop the crash effect if no collide happened this frame + if ( crashEffect && crashTime != gameLocal.time ) { + crashEffect->Stop ( ); + crashEffect = NULL; + } + + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } +} + +/* +================ +rvVehicle::UpdateDrivers +================ +*/ +void rvVehicle::UpdateDrivers ( int delta ) { + int oldDrivers = drivers; + + drivers = idMath::ClampInt ( 0, positions.Num(), drivers + delta ); + + if ( drivers && !oldDrivers ) { + if ( fl.takedamage ) { + aiManager.AddTeammate ( this ); + } + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_enter", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } else if ( !drivers ) { + aiManager.RemoveTeammate ( this ); + + refSound.listenerId = entityNumber + 1; + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_exit", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } +} + +/* +================ +rvVehicle::GetAxis +================ +*/ +const idMat3& rvVehicle::GetAxis( int id ) const { + return GetPhysics()->GetAxis( id ); +} + +/* +================ +rvVehicle::GetOrigin +================ +*/ +const idVec3& rvVehicle::GetOrigin( int id ) const { + return GetPhysics()->GetOrigin(); +} + +/* +================ +rvVehicle::GetEyePosition +================ +*/ +void rvVehicle::GetEyePosition ( int pos, idVec3& origin, idMat3& axis ) { + axis = positions[pos].GetEyeAxis ( ); + origin = positions[pos].GetEyeOrigin ( ); +} + +/* +================ +rvVehicle::GetDriverPosition +================ +*/ +void rvVehicle::GetDriverPosition ( int pos, idVec3& origin, idMat3& axis ) { + const rvVehiclePosition *position = GetPosition( pos ); + + position->GetDriverPosition( origin, axis ); +} + +/* +===================== +rvVehicle::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicle::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + idMat3 identity; + identity.Identity( ); + + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + + // mekberg: vehicle bounds are resized based on rotation but not rotated with the axis. Get transformed bounds. + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, identity ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +================ +rvVehicle::AddDriver + +Add a driver to the vehicle at the given position. Its possible that the given position is +occupied, in that case the driver will be assigned the closest valid position. If no position +can be found then (-1) will be returned, otherwise the position the driver is now driving will +be returned. +================ +*/ +int rvVehicle::AddDriver ( int position, idActor* driver ) { + int wraparound; + + // Ensure the given position is valid + if ( position < 0 || position >= positions.Num() ) { + gameLocal.Warning ( "position %d is invalid for vehicle '%s'", position, name.c_str() ); + return -1; + } + + wraparound = position; + + do { + // If the position isnt occupied then set the driver + if ( !positions[position].IsOccupied ( ) ) { + positions[position].SetDriver ( driver ); + + // Vehicle is on same team as driver + team = driver->team; + + UpdateDrivers ( 1 ); + + // The local player will hear all private sounds of the vehicle + if ( driver == gameLocal.GetLocalPlayer ( ) ) { + refSound.listenerId = driver->GetListenerId ( ); + } + + return position; + } + + // Check the next position, wrap around if necessary + position = (position + 1) % positions.Num(); + + } while ( wraparound != position ); + + return -1; +} + +/* +================ +rvVehicle::RemoveDriver + +Removes the given driver from the vehicle. This includes physically taking the driver out +of the vehicle and placing them back into the world and will follow all constraints that limit +the driver from exiting the vehicle. +================ +*/ +bool rvVehicle::RemoveDriver ( int position, bool force ) { + if ( !positions[position].EjectDriver ( force ) ) { + return false; + } + + UpdateDrivers ( -1 ); + return true; +} + +/* +================ +rvVehicle::EjectAllDrivers +================ +*/ +void rvVehicle::EjectAllDrivers( bool force ) { + for( int ix = positions.Num() - 1; ix >= 0; --ix ) { + if( !GetPosition(ix)->GetDriver() ) { + continue; + } + + GetPosition(ix)->GetDriver()->ProcessEvent( &AI_ExitVehicle, force ); + } +} + +/* +============ +rvVehicle::Damage +============ +*/ +void rvVehicle::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, int location ) { + int damage; + + if ( !fl.takedamage ) { + return; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->GetBool( "ignore_vehicles", "0" ) ) { + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damage *= damageScale; + + int save = HasDrivers() ? shieldHealth.GetCurrentValue ( gameLocal.time ) : 0; + + // If one of the drivers is the player, do the player HUD effects + for ( int i = 0; i < positions.Num(); i++ ) { + rvVehiclePosition & pos = positions[ i ]; + + if ( pos.mDriver.IsValid() && pos.mDriver->IsType( idPlayer::GetClassType() ) ) { + idPlayer & driver = static_cast< idPlayer & >( *pos.mDriver.GetEntity() ); + + if ( driver.GetHud() ) { + driver.GetHud()->HandleNamedEvent( "vehicleHit" ); + } + + if ( !stricmp( damageDef->GetString( "filter" ), "electrical" ) ) { + driver.GetHud()->HandleNamedEvent( "electricWarningOn" ); + PostEventMS( &EV_HUDShockWarningOff, spawnArgs.GetInt( "hud_shock_warning_time", "2500" ) ); + + if ( damage >= spawnArgs.GetInt( "electric_stall_damage", "20" ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect( spawnArgs , "fx_electrical_stall" ), this, SEC2MS( spawnArgs.GetFloat ( "hud_shock_warning_time", "2.5" ) ) ); + effect->Play ( gameLocal.time, false ); + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall" ); + } + + vfl.stalled = true; + PostEventMS( &EV_StalledRestart, spawnArgs.GetInt( "electric_stall_delay", "3500" ), save, damage ); + } + } + } + } + + + shieldHitTime = gameLocal.time; + + // Shields off when there is no controller + if ( !damageDef->GetBool( "noShields", "0" ) ) + { + if ( save > 0 ) { + int shield; + save = Min( save, damage ); + damage -= save; + + shield = shieldHealth.GetCurrentValue ( gameLocal.time ) - save; + shieldHealth.Init ( gameLocal.time, 0, shield, shield ); + // always stop the sound because we may be playing the recharge just now. + StopSound ( SND_CHANNEL_BODY2, false ); + + // Looping warning sound for shield + if ( shield <= 0 && !vfl.stalled ) { + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + } + } + + // God Mode? + if ( vfl.godmode && !damageDef->GetBool( "noGod" ) ) { + godModeDamage += damage; + damage = 0; + } + + if ( !damage ) { + return; + } + + // Static on the gui when hit + if ( renderEntity.gui[0] && gameLocal.random.RandomFloat() < damageStaticChance ) { + renderEntity.gui[0]->HandleNamedEvent ( "shot_static" ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + health -= damage; + if ( health < 1 && damageDef->GetBool( "nonLethal" ) ) { + health = 1; + } + if ( health <= 0 ) { + // keep the driver from being killed if we're forcing undying state + if ( g_forceUndying.GetBool() && HasDrivers() ) { + idActor * driver = NULL; + for ( int i = 0; i < positions.Num(); i++ ) { + idActor * currentDriver = positions[ i ].GetDriver(); + if ( currentDriver && currentDriver->IsType( idPlayer::GetClassType() ) ) { + driver = currentDriver; + break; + } + } + + if ( driver ) { + health = 1; + Pain( inflictor, attacker, damage, dir, 0 ); + return; + } + } + + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, 0 ); + } +} + +/* +================ +rvVehicle::Killed +================ +*/ +void rvVehicle::Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + int i; + + lockWarningTime = 0; + hazardWarningTime = 0; + vfl.locked = false; + vfl.dead = true; + + // Try removing the vehicle from all lists it could be part of. + aiManager.RemoveTeammate ( this ); + + // Remove all drivers from the vehicle and kill them + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver(); + if ( !driver ) { + continue; + } + + // Dump the driver out of the vehicle and kill them + driver->health = 0; + driver->ProcessEvent ( &AI_ExitVehicle, true ); + driver->Killed( inflictor, attacker, damage, dir, location ); + } + + OnDeath(); + CheckDeathObjectives(); + + if ( spawnArgs.GetBool( "remove_on_death", "1" ) ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + + Hide ( ); + + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +rvVehicle::AddDamageEffect +================ +*/ +void rvVehicle::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + if ( HasDrivers() && shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 ) { + jointHandle_t joint = animator.GetJointHandle( spawnArgs.GetString( "fx_shield_joint", "" ) ); + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + dir.NormalizeFast ( ); + if ( INVALID_JOINT == joint ) { + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + } else { + PlayEffect ( "fx_shield", joint, false, vec3_origin, true ); + } + } +} + +/* +================ +rvVehicle::Collide +================ +*/ +bool rvVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { + idVec3 dir; + float speed; + float collisionSelfDamage = 0.0f; + + dir = velocity; + speed = dir.Normalize(); + + // No collision effect when hitting a no impact surfac +// if ( collision.c.material && collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) { +// return false; +// } + + if ( vfl.dead ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + Hide ( ); + PostEventMS( &EV_Remove, 0 ); + } + + if ( !alwaysImpactDamage ) { + if ( speed < crashSpeedSmall ) { + return false; + } + } + + // When colliding with the world play a collision effect + if ( collision.c.entityNum == ENTITYNUM_WORLD ) { + // TODO: MAterial types + if ( !crashEffect ) { + crashEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_crash" ), collision.c.point, collision.c.normal.ToMat3(), true ); + } + if ( crashEffect ) { + crashEffect->SetOrigin ( collision.c.point ); + crashEffect->SetAxis ( collision.c.normal.ToMat3() ); + crashEffect->Attenuate ( idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ) ); + } + } + + idStr collDmgDef = "damage_gev_collision_self"; + if ( gameLocal.time > crashNextSound ) { + + float dot = dir * collision.c.normal; + if( dot < -0.5f ) { + + // Crash impact sounds + if ( speed > crashSpeedLarge ) { + collisionSelfDamage += 50.0f; + StartSound ( "snd_crash_large", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedMedium ) { + collisionSelfDamage += 25.0f; + StartSound ( "snd_crash_medium", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedSmall ) { + StartSound ( "snd_crash_small", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + } + } + } + + crashTime = gameLocal.time; + float vel = GetPhysics()->GetLinearVelocity().Length(); + + if ( vel > crashVelocitySmall && crashDamage.Length ( ) ) { + idEntity* ent = gameLocal.entities[ collision.c.entityNum ]; + + if ( ent ) { + float f = vel > crashVelocityLarge ? 1.0f : idMath::Sqrt( vel - crashVelocityMedium ) * ( 1.0f / idMath::Sqrt( crashVelocityLarge - crashVelocityMedium ) ); + + // Now hurt him + if ( !( team != gameLocal.GetLocalPlayer()->team && ent->IsType( idPlayer::GetClassType() ) ) ) { + + const idDict* damageDef = gameLocal.FindEntityDefDict ( crashDamage, false ); + //MCG NOTE: This used to call vehicleController.GetDriver(), which was always NULL... + idActor* theDriver = positions[0].GetDriver(); + + if ( ent->fl.takedamage + && ent->health >= 0 + && !ent->spawnArgs.GetBool( "ignore_vehicle_damage" ) + && (vel > 100.0f || !ent->IsType(idPlayer::GetClassType())) ) { + //MCG NOTE: now that theDriver is being set correctly, this damage will get credited to the driver (as the attacker), unlike before + float dScale = f * ent->spawnArgs.GetFloat( "vehicle_damage_scale", "1" ); + ent->Damage( this, theDriver, dir, crashDamage, dScale, INVALID_JOINT ); + if ( vel > 100.0f ) { + //NOTE: we PURPOSELY override this here, so you don't take damage from slamming into damageable things, only invulnerable things... + collisionSelfDamage = ent->spawnArgs.GetFloat( "vehicle_impact_damage", "0" ); + collDmgDef = "damage_gev_collision"; + } + } + + // ApplyImpulse doesn't like it when you give it a null driver + // MCG: okay, now ApplyImpulse actually is getting called, + // but I'm only going to allow it on idMoveables since ApplyImpulse is + // rejected by idActors if it comes from an idActor and this code was + // never being called before. + if ( theDriver && damageDef && !ent->spawnArgs.GetBool("ignore_vehicle_push") && ent->IsType( idMoveable::GetClassType() ) ) { + float push = damageDef->GetFloat( "push" ) * speed / idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ); + idVec3 impulse = -push * f * collision.c.normal; + impulse[2] = push * f * 0.75f; + + // Send him flying + ent->ApplyImpulse( theDriver, collision.c.id, collision.c.point, impulse ); + + if ( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugArrow ( colorGreen, collision.c.point, collision.c.point + impulse, 3, 10000 ); + } + } + } + } + } + if ( collisionSelfDamage ) { + idVec3 dmgDir = GetPhysics()->GetLinearVelocity()*-1.0f; + Damage( this, this, dmgDir, collDmgDef.c_str(), collisionSelfDamage, 0 ); + } + return false; +} + +/* +=============== +rvVehicle::Give +=============== +*/ +bool rvVehicle::Give( const char *statname, const char *value ) { + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= healthMax ) { + return false; + } + health = Min ( atoi( value ) + health, healthMax ); + } + + return true; +} + +/* +================ +rvVehicle::AutoRight +================ +*/ +void rvVehicle::AutoRight( idEntity* activator ) { + if( autoRight ) { + return; + } + + autoRight = true; + +/* + idVec3 vec = renderEntity.origin - activator->renderEntity.origin; + vec.Normalize(); + + float dot = DotProduct( vec, renderEntity.axis[ 1 ] ); + + if( dot < 0 ) + { + autoRightDir = -1.0f; + } + else + { + autoRightDir = 1.0f; + } +*/ +} + +/* +================ +rvVehicle::GetPositionIndex +================ +*/ +int rvVehicle::GetPositionIndex ( const rvVehiclePosition* position ) const { + int i; + for ( i = positions.Num() - 1; i >= 0; i -- ) { + if ( &positions[i] == position ) { + return i; + } + } + return -1; +} + +/* +================ +rvVehicle::UpdateHUD +================ +*/ +void rvVehicle::UpdateHUD ( int position, idUserInterface* gui ) { +// VEH_FIXME: godmode ? +/* + if ( mController && mController->GetDriver() && mController->GetDriver()->IsType ( idPlayer::Type ) ) + { + idPlayer* player = static_cast(mController->GetDriver()); + gui->State()->SetInt ( "vehicle_god", player->godmode && g_showGodDamage->integer ); + gui->State()->SetInt ( "vehicle_god_damage", godModeDamage ); + } + else + { + gui->State()->SetInt ( "vehicle_god", 0 ); + } +*/ + gui->SetStateFloat( "vehicle_health", health / spawnArgs.GetFloat( "health", "1" ) ); + gui->SetStateInt ( "vehicle_armor", 0 ); + gui->SetStateInt ( "vehicle_position", position ); + gui->SetStateFloat ( "vehicle_shield", (float)shieldHealth.GetCurrentValue(gameLocal.time) / (float)shieldMaxHealth ); + gui->SetStateInt ( "vehicle_haz", hazardWarningTime != 0 ); + gui->SetStateInt ( "vehicle_locked", IsLocked ( ) ); + + // Update position specific information + positions[position].UpdateHUD ( gui ); +} + +/* +================ +rvVehicle::UpdateCursorGUI +================ +*/ +void rvVehicle::UpdateCursorGUI ( int position, idUserInterface* gui ) { + positions[position].UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehicle::DrawHUD +================ +*/ +void rvVehicle::DrawHUD ( int position ) { + if ( !hud || gameLocal.GetLocalPlayer()->IsObjectiveUp() || gameLocal.GetLocalPlayer()->objectiveSystemOpen ) { + return; + } + + UpdateHUD ( position, hud ); + + hud->Redraw ( gameLocal.time ); +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueHazardWarning ( void ) { + if ( !hazardWarningTime ) { + StartSound ( "snd_voiceHazard", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_hazard" ); + } + } + + hazardWarningTime = gameLocal.time; +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueLockedWarning ( void ) { + if ( gameLocal.time > lockWarningTime + VEHICLE_LOCK_TIMEOUT ) { + StartSound ( "snd_voiceNoExit", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_locked" ); + } + + lockWarningTime = gameLocal.time; + } +} + +/* +================== +rvVehicle::Hide +================== +*/ +void rvVehicle::Hide ( void ) { + idActor::Hide ( ); + + GetPhysics()->SetContents( 0 ); + GetPhysics()->GetClipModel()->Unlink(); +} + +/* +================== +rvVehicle::Show +================== +*/ +void rvVehicle::Show ( void ) { + idActor::Show ( ); + + GetPhysics()->SetContents ( CONTENTS_BODY ); + GetPhysics()->GetClipModel()->Link(); +} + +/* +================== +rvVehicle::Lock +================== +*/ +void rvVehicle::Lock( void ) { + Event_Lock( true ); +} + +/* +================== +rvVehicle::Unlock +================== +*/ +void rvVehicle::Unlock( void ) { + Event_Lock( false ); +} + +/* +================== +rvVehicle::GuidedProjectileLocked +================== +*/ +void rvVehicle::GuidedProjectileIncoming( idGuidedProjectile * projectile ) { + if ( projectile ) { + incomingProjectiles.Insert( projectile ); + } +} + +/* +================== +rvVehicle::UpdateIncomingProjectiles +================== +*/ +void rvVehicle::UpdateIncomingProjectiles( void ) { + idGuidedProjectile * proj = NULL; + float dist = 0.0f; + + for( int i = incomingProjectiles.Num() - 1; i >= 0; i-- ) { + if ( !incomingProjectiles[ i ].IsValid() ) { + incomingProjectiles.RemoveIndex( i ); + continue; + } + + if ( proj ) { + float d = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist > d ) { + proj = incomingProjectiles[ i ]; + dist = d; + } + } else { + proj = incomingProjectiles[ i ]; + dist = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + } + } + + if ( proj ) { + idVec3 localDir; + int length; + + GetPosition( 0 )->mEyeAxis.ProjectVector( proj->GetPhysics()->GetOrigin() - GetOrigin(), localDir ); + GetHud()->SetStateFloat ( "missiledir", localDir.ToAngles()[YAW] ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + + if ( !vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatUp" ); + } + + if ( emitter ) { + StartSound( "snd_incomingProjectile", SND_CHANNEL_BODY3, 0, false, &length ); + } + + vfl.missileWarningOn = true; + } + + if ( emitter ) { + //float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist / 100000.0f ) ); + //refSound.parms.volume = lerp * 0.6f + 0.5f; + //refSound.parms.frequencyShift = lerp * 0.4f + 0.8f; + //emitter->ModifySound ( SND_CHANNEL_ANY, &refSound.parms ); + } + } else if ( vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatDown" ); + } + + StopSound( SND_CHANNEL_BODY3, false ); + + vfl.missileWarningOn = false; + } +} + +/* +================== +rvVehicle::Event_Lock +================== +*/ +void rvVehicle::Event_Lock ( bool lock ) { + vfl.locked = lock; +} + +/* +================== +rvVehicle::Event_IsLocked +================== +*/ +void rvVehicle::Event_IsLocked ( void ) { + idThread::ReturnFloat( (float)IsLocked() ); +} + +/* +================== +rvVehicle::Event_EnableWeapon +================== +*/ +void rvVehicle::Event_EnableWeapon ( void ) { + vfl.disableWeapons = false; +} + +/* +================== +rvVehicle::Event_DisableWeapon +================== +*/ +void rvVehicle::Event_DisableWeapon ( void ) { + vfl.disableWeapons = true; +} + + +/* +================== +rvVehicle::Event_EnableMovement +================== +*/ +void rvVehicle::Event_EnableMovement( void ) { + vfl.disableMovement = false; +} + +/* +================== +rvVehicle::Event_DisableMovement +================== +*/ +void rvVehicle::Event_DisableMovement ( void ) { + vfl.disableMovement = true; +} + +/* +================== +rvVehicle::Event_EnableClip +================== +*/ +void rvVehicle::Event_EnableClip( void ) { + GetPhysics()->SetContents( cachedContents ); +} + +/* +================== +rvVehicle::Event_DisableClip +================== +*/ +void rvVehicle::Event_DisableClip( void ) { + cachedContents = GetPhysics()->GetContents(); + GetPhysics()->SetContents( 0 ); +} + +/* +================== +rvVehicle::Event_Activate +================== +*/ +void rvVehicle::Event_Activate( idEntity* activator ) { + RemoveNullTargets(); + + if( !targets.Num() && activator && activator->IsType(idPlayer::GetClassType()) ) { + static_cast( activator )->EnterVehicle( this ); + } +} + +/* +================== +rvVehicle::Event_LaunchProjectiles +================== +*/ +void rvVehicle::Event_LaunchProjectiles( const idList* parms ) { + int pos = -1; + + assert( parms && parms->Num() ); + + sscanf( (*parms)[0].c_str(), "%d", &pos ); + GetPosition( pos )->FireWeapon(); +} + +/* +================== +rvVehicle::Event_SetScript +================== +*/ +void rvVehicle::Event_SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "enter" ) ) { + funcs.enter.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "exit" ) ) { + funcs.exit.Init( funcName ); + } else { + gameLocal.Warning ( "unknown script '%s' specified on vehicle '%s'", scriptName, name.c_str() ); + } +} + +/* +================ +rvVehicle::Event_SetHealth +================ +*/ +void rvVehicle::Event_SetHealth ( float health ) { + this->health = health; +} + +/* +================ +rvVehicle::Event_HUDShockWarningOff +================ +*/ +void rvVehicle::Event_HUDShockWarningOff ( ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "electricWarningOff" ); + } +} + +/* +================ +rvVehicle::Event_StalledRestart +================ +*/ +void rvVehicle::Event_StalledRestart ( float shield, float damage ) { + vfl.stalled = false; + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + // Looping warning sound for shield + if ( shield <= 0 ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +/* +================ +rvVehicle::Event_GetViewAngles +================ +*/ +void rvVehicle::Event_GetViewAngles ( ) { + + idThread::ReturnVector( GetPosition( 0)->GetEyeAxis()[0]); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicle ) + STATE ( "Wait_Driver", rvVehicle::State_Wait_Driver ) +END_CLASS_STATES + +/* +================ +rvVehicle::State_Wait_Driver +================ +*/ +stateResult_t rvVehicle::State_Wait_Driver ( int blendFrames ) { + if ( !vfl.driver || vfl.stalled ) { + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/src/game/vehicle/Vehicle.h b/src/game/vehicle/Vehicle.h new file mode 100644 index 0000000..fafeeeb --- /dev/null +++ b/src/game/vehicle/Vehicle.h @@ -0,0 +1,440 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLE_H__ +#define __GAME_VEHICLE_H__ + +class rvVehiclePosition { +public: + + rvVehiclePosition( void ); + virtual ~rvVehiclePosition( void ); + + usercmd_t mInputCmd; + idAngles mInputAngles; + usercmd_t mOldInputCmd; + idAngles mOldInputAngles; + int mOldInputFlags; + + int mCurrentWeapon; + + idEntityPtr mDriver; + idEntityPtr mParent; + + rvVehiclePartList_t mParts; + rvVehiclePartList_t mWeapons; + + idVec3 mEyeOrigin; + idMat3 mEyeAxis; + + jointHandle_t mEyeJoint; + idVec3 mEyeOffset; + idMat3 mEyeJointTransform; + idAngles mDeltaEyeAxisScale; + int mEyeJointAxisMap[3]; + int mEyeJointDirMap[3]; + + idMat3 mAxisOffset; + + jointHandle_t mDriverJoint; + idVec3 mDriverOffset; + idMat3 mDriverJointTransform; + idAngles mDeltaDriverAxisScale; + int mDriverJointAxisMap[3]; + int mDriverJointDirMap[3]; + + idVec3 mExitPosOffset; + idMat3 mExitAxisOffset; + + idStr mDriverAnim; + + idStr mInternalSurface; + + struct positionFlags_s { + bool inputValid :1; + bool engine :1; + bool driverVisible :1; + bool depthHack :1; + bool internalView :1; + bool bindDriver :1; + bool stalled :1; + } fl; + + void Init ( rvVehicle* parent, const idDict& args ); + + int AddPart ( const idTypeInfo &classdef, const idDict& args ); + rvVehiclePart* GetPart ( int partIndex ); + + bool IsOccupied ( void ) const; + bool IsEngine ( void ) const; + + bool SetDriver ( idActor* driver ); + void SetInput ( const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( usercmd_t& cmd, idAngles& newAngles ) const; + + bool EjectDriver ( bool force = false ); + + void RunPrePhysics ( void ); + void RunPostPhysics ( void ); + + void SelectWeapon ( int weapon ); + + void UpdateHUD ( idUserInterface* gui ); + void UpdateCursorGUI ( idUserInterface* gui ); + void UpdateInternalView ( bool force = false ); + + virtual idMat3 GetAxis ( void ) const; + virtual idVec3 GetOrigin ( const idVec3& offset = vec3_zero ) const; + + const idVec3& GetEyeOrigin ( void ) const; + const idMat3& GetEyeAxis ( void ) const; + rvVehicle* GetParent ( void ) const; + idActor* GetDriver ( void ) const; + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + void GetPosition ( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const; + + void FireWeapon ( void ); + + rvVehicleWeapon * GetWeapon ( int weaponIndex ); + rvVehicleWeapon * GetActiveWeapon ( void ); + +private: + + void SetParts ( const idDict& args ); + void ActivateParts ( bool active ); + + int mSoundPart; + float mSoundMaxSpeed; +}; + +typedef struct rvVehicleFuncs_s { + rvScriptFuncUtility enter; // script to run when the vehicle becomes occupied + rvScriptFuncUtility exit; // script to run when the vehicle becomes unoccupied +} rvVehicleFuncs_t; + +class rvVehicle : public idActor { +public: + + CLASS_PROTOTYPE( rvVehicle ); + + rvVehicle ( void ); + ~rvVehicle ( void ); + + void Spawn ( void ); + void Think ( void ); + bool Give ( const char *statname, const char *value ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { return false; } + + virtual const idMat3& GetAxis ( int id = 0 ) const; + virtual const idVec3& GetOrigin ( int id = 0 ) const; + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + virtual void EjectAllDrivers ( bool force = false ); + rvVehiclePosition* GetPosition ( int index ); + const rvVehiclePosition* GetPosition ( int index ) const; + int GetPositionIndex ( const rvVehiclePosition* position ) const; + + int GetNumPositions ( void ) const; + int GetNumDrivers ( void ) const; + bool HasOpenPositions ( void ) const; + + bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + virtual void UpdateState (); + + float GetEngineOffTime ( void ) const; + float GetEngineOnTime ( void ) const; + + bool IsFlipped ( void ) const; + bool IsFrozen ( void ) const; + bool IsLocked ( void ) const; + bool HasDrivers ( void ) const; + + bool IsShootingEnabled ( void ) const; + bool IsMovementEnabled ( void ) const; + + void Lock ( void ); + void Unlock ( void ); + + void AutoRight ( idEntity* activator ); + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( int position, idVec3& origin, idMat3& axis ); + virtual void GetEyePosition ( int position, idVec3& origin, idMat3& axis ); + void DrawHUD ( int position ); + idUserInterface* GetHud ( void ); + void UpdateCursorGUI ( int position, idUserInterface* ui ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( int position, usercmd_t& cmd, idAngles& newAngles ) const; + + void IssueHazardWarning ( void ); + void IssueLockedWarning ( void ); + + void AddToBounds ( const idVec3& vec ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ) {} + + float FocusLength ( void ) const { return spawnArgs.GetFloat("focusLength_enter", "60"); } + + bool IsAutoCorrecting ( void ) const { return autoCorrectionBegin != 0; } + bool IsStalled ( void ) const { return vfl.stalled; } + + void OnEnter ( void ) { funcs.enter.CallFunc( NULL ); fl.exitedVehicle=false; }; + void OnExit ( void ) { if ( !fl.exitedVehicle ) { fl.exitedVehicle=true; funcs.exit.CallFunc( NULL );} }; + + bool IsStrafing ( void ) const { return vfl.strafe; } +// void PlayAnim ( int channel, const char *name, int blendFrames ); + + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); +protected: + + void SelectWeapon ( int weapon ); + + void UpdateDrivers ( int delta ); + virtual void UpdateHUD ( int position, idUserInterface* gui ); + + void SetPositions ( void ); + + void SetCombatModel ( void ); + void LinkCombat ( void ); + + // twhitaker: + friend class rvVehicleDriver; + virtual void RunPrePhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + + idList positions; + int drivers; + + idUserInterface * hud; + + float crashSpeedSmall; + float crashSpeedMedium; + float crashSpeedLarge; + idStr crashDamage; + rvClientEffectPtr crashEffect; + int crashNextSound; + int crashTime; + + float autoRightDir; + bool autoRight; + + // twhitaker: for rvVehicleDriver, to avoid getting stuck. + unsigned autoCorrectionBegin; // time when obstacle avoidance state began (0 if not correcting) + + struct vehicleFlags_s { + bool forward :1; + bool backward :1; + bool left :1; + bool right :1; + bool driver :1; + bool locked :1; + bool godmode :1; + bool frozen :1; + bool disableWeapons :1; + bool disableMovement :1; + bool scripted :1; + bool endWithIdle :1; + bool flipEject :1; + bool dead :1; + bool strafe :1; + bool missileWarningOn :1; + bool stalled :1; + } vfl; + + float damageStaticChance; + + // Shields + float shieldMaxHealth; + idInterpolate shieldHealth; + int shieldHitTime; + float shieldRegenTime; + int shieldRegenDelay; + idClipModel* shieldModel; + + int healthRegenDelay; + int healthRegenRate; + idInterpolate healthRegenAmount; + + int hazardWarningTime; + int lockWarningTime; + int healthMax; + int healthLow; + int godModeDamage; + + int cachedContents; + + rvVehicleFuncs_t funcs; + + float crashVelocitySmall; + float crashVelocityMedium; + float crashVelocityLarge; + + bool alwaysImpactDamage; + + void UpdateIncomingProjectiles( void ); + +public: + idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; +private: + + void Event_Lock ( bool locked ); + void Event_IsLocked ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + void Event_EnableClip ( void ); + void Event_DisableClip ( void ); + void Event_Activate ( idEntity* activator ); + void Event_LaunchProjectiles ( const idList* parms ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetHealth ( float health ); + void Event_HUDShockWarningOff( void ); + void Event_StalledRestart ( float shield, float damage ); + void Event_GetViewAngles ( void ); + + virtual void OnDeath ( void ) { } + + stateResult_t State_Wait_Driver ( int blendFrames ); + + CLASS_STATES_PROTOTYPE( rvVehicle ); +}; + +/* +=============== +rvVehiclePosition inlines +=============== +*/ + +ID_INLINE bool rvVehiclePosition::IsOccupied ( void ) const { return mDriver.IsValid();} +ID_INLINE bool rvVehiclePosition::IsEngine ( void ) const { return fl.engine; } +ID_INLINE const idVec3& rvVehiclePosition::GetEyeOrigin ( void ) const { return mEyeOrigin; } +ID_INLINE const idMat3& rvVehiclePosition::GetEyeAxis ( void ) const { return mEyeAxis; } +ID_INLINE rvVehicle* rvVehiclePosition::GetParent ( void ) const { return mParent; } +ID_INLINE idActor* rvVehiclePosition::GetDriver ( void ) const { return mDriver; } +ID_INLINE rvVehiclePart* rvVehiclePosition::GetPart ( int partIndex ) { return mParts[partIndex]; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetWeapon ( int weaponIndex ){ return ( weaponIndex >= 0 && weaponIndex < mWeapons.Num() ) ? static_cast(mWeapons[weaponIndex]) : 0; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetActiveWeapon ( void ) { return GetWeapon( mCurrentWeapon ); } + +/* +=============== +rvVehicle inlines +=============== +*/ + +ID_INLINE bool rvVehicle::IsFlipped( void ) const { + return (( !vfl.flipEject ) ? false : + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) > 60.f || + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().pitch ) > 60.0f )); +} + +ID_INLINE bool rvVehicle::IsFrozen( void ) const { + return vfl.frozen; +} + +ID_INLINE idUserInterface* rvVehicle::GetHud ( void ) { + return hud; +} + +ID_INLINE bool rvVehicle::IsLocked ( void ) const { + return vfl.locked || hazardWarningTime != 0; +} + +ID_INLINE rvVehiclePosition* rvVehicle::GetPosition ( int index ) { + return &positions[index]; +} + +ID_INLINE const rvVehiclePosition* rvVehicle::GetPosition ( int index ) const { + return &positions[index]; +} + +ID_INLINE void rvVehicle::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +ID_INLINE void rvVehicle::GetInput( int position, usercmd_t& cmd, idAngles& newAngles ) const { + GetPosition(position)->GetInput( cmd, newAngles ); +} + +ID_INLINE bool rvVehicle::HasDrivers ( void ) const { + return drivers > 0; +} + +ID_INLINE void rvVehicle::AddToBounds ( const idVec3& vec ) { + renderEntity.bounds.AddPoint ( (vec-renderEntity.origin) * GetPhysics()->GetAxis().Transpose() ); +} + +ID_INLINE bool rvVehicle::IsShootingEnabled ( void ) const { + return !vfl.disableWeapons; +} + +ID_INLINE bool rvVehicle::IsMovementEnabled ( void ) const { + return !vfl.disableMovement; +} + +ID_INLINE int rvVehicle::GetNumPositions ( void ) const { + return positions.Num(); +} + +ID_INLINE int rvVehicle::GetNumDrivers ( void ) const { + return drivers; +} + +ID_INLINE bool rvVehicle::HasOpenPositions ( void ) const { + return GetNumPositions() > GetNumDrivers(); +} + +#endif // __GAME_VEHICLE_H__ diff --git a/src/game/vehicle/VehicleAnimated.cpp b/src/game/vehicle/VehicleAnimated.cpp new file mode 100644 index 0000000..700ce4f --- /dev/null +++ b/src/game/vehicle/VehicleAnimated.cpp @@ -0,0 +1,260 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleAnimated ) +END_CLASS + +rvVehicleAnimated::rvVehicleAnimated ( void ) { +} + +rvVehicleAnimated::~rvVehicleAnimated ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleAnimated::Spawn +================ +*/ +void rvVehicleAnimated::Spawn( void ) { + + turnRate = spawnArgs.GetFloat ( "turnRate", "90" ); + viewAngles = GetPhysics()->GetAxis ( ).ToAngles ( ); + viewAxis = viewAngles.ToMat3(); + + // Initialize the physics object + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetMaxStepHeight( spawnArgs.GetFloat ( "stepheight", "14" ) ); + + // Start just a tad above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetDelta( vec3_origin ); + + // Gravity + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat ( ); + physicsObj.SetGravity( gravity ); + SetPhysics( &physicsObj ); + + animator.RemoveOriginOffset( true ); + + additionalDelta.Zero(); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleAnimated::ClientPredictionThink +================ +*/ +void rvVehicleAnimated::ClientPredictionThink ( void ) { + Think(); +} + +/* +================ +rvVehicleAnimated::Think +================ +*/ +void rvVehicleAnimated::Think ( void ) { + + rvVehicle::Think(); + + float rate = 0.0f; + usercmd_t& cmd = positions[0].mInputCmd; + idVec3 delta; + + if( positions[0].IsOccupied() && !IsFrozen() && IsMovementEnabled() ) { + + if (( g_vehicleMode.GetInteger() == 0 && !( cmd.buttons & BUTTON_STRAFE )) || // If we're in the old driving mode and we aren't strafing or... + ( g_vehicleMode.GetInteger() != 0 && ( cmd.buttons & BUTTON_STRAFE ))) // If we're in the new driving mode and we are strafing + { + rate = SignZero(cmd.forwardmove) * turnRate; + rate *= idMath::MidPointLerp( 0.0f, 0.9f, 1.0f, idMath::Fabs(cmd.rightmove) / 127.0f ); + viewAngles.yaw += Sign(cmd.rightmove) * rate * MS2SEC(gameLocal.GetMSec()); + } + } + + viewAngles.Normalize360(); + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + delta += additionalDelta; + + idStr alignmentJoint; + if ( g_vehicleMode.GetInteger() != 0 && spawnArgs.GetString( "alignment_joint", 0, alignmentJoint ) ) { + idVec3 offset; + idMat3 axis; + GetJointWorldTransform( animator.GetJointHandle( alignmentJoint ), gameLocal.time, offset, axis ); + delta *= axis; + } else { + viewAxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + delta *= viewAxis; + } + + physicsObj.SetDelta( delta ); + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetAxis +================ +*/ +const idMat3& rvVehicleAnimated::GetAxis( int id ) const { + return viewAxis; +} + +/* +================ +rvVehicleAnimated::WriteToSnapshot +================ +*/ +void rvVehicleAnimated::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot ( msg ); + + physicsObj.WriteToSnapshot( msg ); + msg.WriteFloat( viewAngles[0] ); + msg.WriteFloat( viewAngles[1] ); + msg.WriteFloat( viewAngles[2] ); +} + +/* +================ +rvVehicleAnimated::ReadFromSnapshot +================ +*/ +void rvVehicleAnimated::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot ( msg ); + + physicsObj.ReadFromSnapshot( msg ); + viewAngles[0] = msg.ReadFloat( ); + viewAngles[1] = msg.ReadFloat( ); + viewAngles[2] = msg.ReadFloat( ); +} + +/* +================ +rvVehicleAnimated::Save +================ +*/ +void rvVehicleAnimated::Save ( idSaveGame *savefile ) const { + savefile->WriteVec3( storedPosition ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->WriteAngles ( viewAngles ); + savefile->WriteFloat ( turnRate ); + + // why are we writing out the viewAxis here??? + savefile->WriteMat3 ( viewAxis ); + + // TEMP + animator.Save( savefile ); +} + +/* +================ +rvVehicleAnimated::Restore +================ +*/ +void rvVehicleAnimated::Restore ( idRestoreGame *savefile ) { + savefile->ReadVec3( storedPosition ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadAngles ( viewAngles ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadMat3 ( viewAxis ); + + // TEMP - restore animator, because it was cleared when loading the AF + animator.Restore( savefile ); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + // TEMP + + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleAnimated::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +rvVehicleAnimated::RunPrePhysics +================ +*/ +void rvVehicleAnimated::RunPrePhysics ( void ) { + storedPosition = physicsObj.GetOrigin(); +} + +/* +================ +rvVehicleAnimated::RunPostPhysics +================ +*/ +void rvVehicleAnimated::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 3000 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + idVec3 delta; + idVec3 actualDelta = physicsObj.GetOrigin() - storedPosition; + + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( !autoCorrectionBegin && delta.LengthSqr() * 0.2f > actualDelta.LengthSqr() ) { + autoCorrectionBegin = gameLocal.time; + } + } +} + + diff --git a/src/game/vehicle/VehicleAnimated.h b/src/game/vehicle/VehicleAnimated.h new file mode 100644 index 0000000..2bed08b --- /dev/null +++ b/src/game/vehicle/VehicleAnimated.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleAnimated.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEANIMATED_H__ +#define __GAME_VEHICLEANIMATED_H__ + +#include "Vehicle.h" +#include "../physics/Physics_Monster.h" + +class rvVehicleAnimated : public rvVehicle { +public: + + CLASS_PROTOTYPE( rvVehicleAnimated ); + + rvVehicleAnimated ( void ); + ~rvVehicleAnimated ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual const idMat3& GetAxis ( int id = 0 ) const; + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +protected: + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedPosition; + // end twhitaker: + + idPhysics_Monster physicsObj; + + idAngles viewAngles; + float turnRate; + idVec3 additionalDelta; +}; + +#endif // __GAME_VEHICLEANIMATED_H__ diff --git a/src/game/vehicle/VehicleController.cpp b/src/game/vehicle/VehicleController.cpp new file mode 100644 index 0000000..6b309d1 --- /dev/null +++ b/src/game/vehicle/VehicleController.cpp @@ -0,0 +1,325 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleController.h" +#include "Vehicle.h" + +/* +===================== +rvVehicleController::rvVehicleController +===================== +*/ +rvVehicleController::rvVehicleController ( void ) { + mVehicle = NULL; + //mDriver = NULL; + mPosition = 0; +} + +/* +===================== +rvVehicleController::Save +===================== +*/ +void rvVehicleController::Save ( idSaveGame *savefile ) const { + mVehicle.Save ( savefile ); + savefile->WriteInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Restore +===================== +*/ +void rvVehicleController::Restore ( idRestoreGame *savefile ) { + mVehicle.Restore ( savefile ); + savefile->ReadInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Drive +===================== +*/ +bool rvVehicleController::Drive ( rvVehicle* vehicle, idActor* driver ) { + assert ( vehicle && driver ); + + // Flip the vehicle back over? + if ( vehicle->IsFlipped ( ) ) { + vehicle->AutoRight ( vehicle ); + return false; + } + + // Add the driver to the vehicle and cache the position it was added. + if ( -1 == (mPosition = vehicle->AddDriver ( 0, driver ) ) ) { + return false; + } + + mVehicle = vehicle; + + //twhitaker: for scripted callback events + vehicle->OnEnter(); + + if ( driver->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( driver ); + + if ( player->GetHud() ) { + GetHud()->Activate( true, gameLocal.time ); + GetHud()->HandleNamedEvent( "showExitmessage" ); + } + } + + return true; +} + +/* +===================== +rvVehicleController::Eject +===================== +*/ +bool rvVehicleController::Eject ( bool force ) { + if ( !GetVehicle() ) { + return true; + } + + if ( GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( GetDriver() ); + + if ( player->GetHud() ) { + GetHud()->HandleNamedEvent( "hideExitmessage" ); + } + } + + if ( mVehicle->RemoveDriver ( GetPosition(), force ) ) { + mVehicle->OnExit(); + mVehicle = NULL; + + return true; + } + + return false; +} + +/* +===================== +rvVehicleController::FindClearExitPoint +===================== +*/ +bool rvVehicleController::FindClearExitPoint( idVec3& origin, idMat3& axis ) const { + return GetVehicle()->FindClearExitPoint( mPosition, origin, axis ); +} + +/* +===================== +rvVehicleController::KillVehicles +===================== +*/ +void rvVehicleController::KillVehicles ( void ) { + KillEntities( idCmdArgs(), rvVehicle::GetClassType() ); +} + +/* +===================== +rvVehicleController::DrawHUD +===================== +*/ +void rvVehicleController::DrawHUD ( void ) { + assert ( mVehicle ); + + if ( GetDriver() && GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast( GetDriver() ); + rvVehicleWeapon * weapon = mVehicle->GetPosition( mPosition )->GetActiveWeapon(); + if ( weapon ) { + if ( weapon->CanZoom() && player->IsZoomed() && player == gameLocal.GetLocalPlayer() ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } +// if ( mVehicle->GetHud() ) { +// mVehicle->GetHud()->SetStateFloat( "tram_heatpct", weapon->GetOverheatPercent()); +// mVehicle->GetHud()->SetStateFloat( "tram_overheat", weapon->GetOverheatState()); +// } + } + } + + mVehicle->DrawHUD ( mPosition ); +} + +/* +===================== +rvVehicleController::StartRadioChatter +===================== +*/ +void rvVehicleController::StartRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterUp" ); + } +} + +/* +===================== +rvVehicleController::StopRadioChatter +===================== +*/ +void rvVehicleController::StopRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterDown" ); + } +} + +/* +===================== +rvVehicleController::Give +===================== +*/ +void rvVehicleController::Give ( const char* statname, const char* value ) { + if( mVehicle.IsValid() ) { + mVehicle->Give ( statname, value ); + } +} + +/* +===================== +rvVehicleController::GetEyePosition +===================== +*/ +void rvVehicleController::GetEyePosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetEyePosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetDriverPosition +===================== +*/ +void rvVehicleController::GetDriverPosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetDriverPosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetVehicle +===================== +*/ +rvVehicle* rvVehicleController::GetVehicle ( void ) const { + return mVehicle; +} + +idActor* rvVehicleController::GetDriver ( void ) const { + return (GetVehicle()) ? GetVehicle()->GetPosition(GetPosition())->GetDriver() : NULL; +} + +/* +===================== +rvVehicleController::SetInput +===================== +*/ +void rvVehicleController::SetInput ( const usercmd_t& cmd, const idAngles &angles ) { + if( mVehicle.IsValid() ) { + mVehicle->SetInput ( mPosition, cmd, angles ); + } +} + +/* +===================== +rvVehicleController::GetInput +===================== +*/ +void rvVehicleController::GetInput( usercmd_t& cmd, idAngles &newAngles ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetInput( mPosition, cmd, newAngles ); + } +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +idUserInterface* rvVehicleController::GetHud( void ) { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +const idUserInterface* rvVehicleController::GetHud( void ) const { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::WriteToSnapshot +================ +*/ +void rvVehicleController::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteLong ( mPosition ); + msg.WriteLong ( mVehicle.GetSpawnId ( ) ); +} + +/* +================ +rvVehicleController::ReadFromSnapshot +================ +*/ +void rvVehicleController::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mPosition = msg.ReadLong ( ); + mVehicle.SetSpawnId ( msg.ReadLong ( ) ); +} + +/* +================ +rvVehicleController::UpdateCursorGUI +================ +*/ +void rvVehicleController::UpdateCursorGUI ( idUserInterface* ui ) { + assert ( mVehicle ); + mVehicle->UpdateCursorGUI ( mPosition, ui ); +} + +/* +================ +rvVehicleController::SelectWeapon +================ +*/ +void rvVehicleController::SelectWeapon ( int weapon ) { + if( mVehicle.IsValid() ) { + mVehicle->GetPosition( mPosition )->SelectWeapon( weapon ); + } +} diff --git a/src/game/vehicle/VehicleController.h b/src/game/vehicle/VehicleController.h new file mode 100644 index 0000000..3cd245f --- /dev/null +++ b/src/game/vehicle/VehicleController.h @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleController.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLECONTROLLER_H__ +#define __GAME_VEHICLECONTROLLER_H__ + +class rvVehicle; +class rvVehiclePosition; + +class rvVehicleController { +public: + + rvVehicleController ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Drive ( rvVehicle* vehicle, idActor* driver ); + bool Eject ( bool force = false ); + + bool FindClearExitPoint ( idVec3& origin, idMat3& axis ) const; + + bool IsDriving ( void ) const; + + rvVehicle* GetVehicle ( void ) const; + idActor* GetDriver ( void ) const; + int GetPosition ( void ) const; + + void SetInput ( const usercmd_t& cmd, const idAngles &newAngles ); + void GetInput ( usercmd_t& cmd, idAngles &newAngles ) const; + + idUserInterface* GetHud ( void ); + const idUserInterface* GetHud ( void ) const; + void DrawHUD ( void ); + void UpdateCursorGUI ( idUserInterface* ui ); + + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void Give ( const char* statname, const char* value ); + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + + static void KillVehicles ( void ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void SelectWeapon ( int weapon ); + +protected: + + idEntityPtr mVehicle; + int mPosition; +}; + +ID_INLINE bool rvVehicleController::IsDriving ( void ) const { + return mVehicle.IsValid ( ); +} + +ID_INLINE int rvVehicleController::GetPosition ( void ) const { + return mPosition; +} + +#endif // __GAME_VEHICLECONTROLLER_H__ + diff --git a/src/game/vehicle/VehicleDriver.cpp b/src/game/vehicle/VehicleDriver.cpp new file mode 100644 index 0000000..225ad6e --- /dev/null +++ b/src/game/vehicle/VehicleDriver.cpp @@ -0,0 +1,859 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// VehicleDriver.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include + +const idEventDef VD_ChoosePathTarget( "choosePathTarget", "e", 'e' ); +const idEventDef EV_FollowOffset( "followOffset", "v" ); +const idEventDef EV_FireWeapon ( "fireWeapon", "ff" ); + +CLASS_DECLARATION( idActor, rvVehicleDriver ) + EVENT( EV_PostSpawn , rvVehicleDriver::Event_PostSpawn ) + EVENT( AI_EnterVehicle , rvVehicleDriver::Event_EnterVehicle ) + EVENT( AI_ExitVehicle , rvVehicleDriver::Event_ExitVehicle ) + EVENT( AI_ScriptedMove , rvVehicleDriver::Event_ScriptedMove ) + EVENT( AI_ScriptedDone , rvVehicleDriver::Event_ScriptedDone ) + EVENT( AI_ScriptedStop , rvVehicleDriver::Event_ScriptedStop ) + EVENT( EV_Activate , rvVehicleDriver::Event_Trigger ) + EVENT( EV_Speed , rvVehicleDriver::Event_SetSpeed ) + EVENT( EV_FireWeapon , rvVehicleDriver::Event_FireWeapon ) + EVENT( AI_FaceEntity , rvVehicleDriver::Event_FaceEntity ) + EVENT( AI_LookAt , rvVehicleDriver::Event_LookAt ) + EVENT( AI_SetLeader , rvVehicleDriver::Event_SetLeader ) + +//twhitaker: remove - begin + EVENT( EV_FollowOffset , rvVehicleDriver::Event_SetFollowOffset ) +//twhitaker: remove - end +END_CLASS + +/* +================ +rvVehicleDriver::rvVehicleDriver +================ +*/ +rvVehicleDriver::rvVehicleDriver ( void ) { +} + +/* +================ +rvVehicleDriver::Spawn +================ +*/ +void rvVehicleDriver::Spawn ( void ) { + currentThrottle = 1.0f; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; + leaderFlags = 0; + decelDistance = 0.0f; + minDistance = 0.0f; + fireEndTime = 0.0f; + isMoving = false; + avoidingLeader = false; + pathingMode = VDPM_Random; + pathingOrigin = vec3_origin; + pathingEntity = NULL; + + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + SIMDProcessor->Memset( &lastPathTargetInfo, 0, sizeof( PathTargetInfo ) ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleDriver::Think +================ +*/ +void rvVehicleDriver::Think ( void ) { + if ( !IsDriving() ) { + if ( leader ) { + leader->SetLeaderHint( VDLH_Continue ); + leader = NULL; + } + return; + } + + if ( pathTargetInfo.node ) { + float targetDistance = 0.0f; + float dotForward = 0.0f; + float dotRight = 0.0f; + float desiredThrottle = 1.0f; + idEntity* currentPathTarget = pathTargetInfo.node; + rvVehicle* vehicle = vehicleController.GetVehicle(); + idMat3 vehicleAxis = vehicle->GetAxis(); + idVec3 targetOrigin; + idVec3 dirToTarget; + usercmd_t cmd; + idAngles ang; + + // We may want to hack the auto correction variable based on what the state of the driver and the driver's leader. + UpdateAutoCorrection(); + + if ( vehicle->IsAutoCorrecting( ) ) { + if ( lastPathTargetInfo.node ) { + currentPathTarget = lastPathTargetInfo.node; + } + + if ( g_debugVehicleDriver.GetInteger() != 0 ) { + gameRenderWorld->DebugBounds( colorCyan, vehicle->GetPhysics()->GetAbsBounds().Expand( 30 ) ); + } + } + + // Touch all the triggers that the vehicle touches + vehicle->TouchTriggers(); + + GetTargetInfo( currentPathTarget, &targetOrigin, &dirToTarget, &dotForward, &dotRight, &targetDistance ); + + // The primary purpose of the following portions of code is to set the desiredThrottle variable. + if ( IsMoving() ) { + + // if we're slowing down lerp throttle. + if ( pathTargetInfo.throttle < lastPathTargetInfo.throttle ) { + desiredThrottle = idMath::Lerp( lastPathTargetInfo.throttle, pathTargetInfo.throttle, targetDistance / pathTargetInfo.initialDistance ); + } else { + // otherwise accelerate rapidly or maintain throttle. + desiredThrottle = pathTargetInfo.throttle; + } + + // we could potentially be a leader, so check the leader flags + if ( leaderFlags & VDLH_SlowDown ) { + desiredThrottle = 0.1f; + } else if ( leaderFlags & VDLH_Wait ) { + desiredThrottle = 0.0f; + } + + // if we're following a someone ... + if ( leader ) { + bool canSeeLeader = vehicle->CanSee( leader->vehicleController.GetVehicle(), false ); + float distanceToLeader = ( leader->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + avoidingLeader = false; + + // update the leaders flags based on distance and visibility + if ( !canSeeLeader ) { + // if we can't see the leader, tell him to stop + leader->SetLeaderHint( VDLH_Wait ); + + } else if ( distanceToLeader > decelDistance ) { + // if we're too far from the leader, tell him to slow down + leader->SetLeaderHint( VDLH_SlowDown ); + + } else { + // we're within range of the leader so let him move along at a normal rate + leader->SetLeaderHint( VDLH_Continue ); + + // if we're too close to the leader, we need to slow ourself down + if ( distanceToLeader < minDistance ) { + desiredThrottle = -0.2f; + avoidingLeader = true; + } + } + } + + // the desiredThrottle variable should be set at this point, the only other thing that could change it + // would be in SimulateKeys, if the dotForward < 0. + } + + vehicleController.GetInput( cmd, ang ); + + // Simulate Input + SimulateKeys( cmd, dotForward, dotRight, desiredThrottle * currentThrottle, targetDistance ); + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + + // Node Transition + if( (!vehicle->IsAutoCorrecting() || !IsValidPathNode( currentPathTarget )) && isMoving ) { + idVec3 point = targetOrigin - dirToTarget * pathTargetInfo.minDistance; + + if( vehicle->GetPhysics()->GetAbsBounds().ContainsPoint( point ) ) { + int numTargets = NumValidTargets( currentPathTarget ); + lastPathTargetInfo = pathTargetInfo; + //TODO: ponder - should I be setting lastPathTargetInfo.node to pathTargetInfo.node or currentPathTarget??? + + if( numTargets ) { + Event_ScriptedMove( ChooseNextNode( currentPathTarget ), 0, 0 ); + } + + if( lastPathTargetInfo.exitVehicle ) { + Event_ExitVehicle( true ); + } else if( lastPathTargetInfo.throttle == 0 || !numTargets ) { + Event_ScriptedStop(); + + if( !numTargets ) { + pathTargetInfo.node = NULL; + } + } + + // Debug Output + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugBounds( colorRed, idBounds(idVec3(-5, -5, -5), idVec3(5, 5, 5)), point ); + } + } + } + } else { // no path + float dotForward; + float dotRight; + usercmd_t cmd; + idAngles ang; + + vehicleController.GetInput( cmd, ang ); + + if ( GetTargetInfo( faceTarget, NULL, NULL, &dotForward, &dotRight, NULL) ) { + if( ( 1.0f - dotForward ) < VECTOR_EPSILON ) { + Event_ScriptedStop(); + } else { + SimulateKeys( cmd, dotForward, dotRight, 0.0f ); + } + } + + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + } +} + +/* +================ +rvVehicleDriver::GetTargetInfo +================ +*/ +bool rvVehicleDriver::GetTargetInfo( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, float* dotForward, float* dotRight, float* distance ) const { + if( !target || !IsDriving() ) { + return false; + } + + idMat3 vehicleAxis = vehicleController.GetVehicle()->GetAxis(); + idVec3 vehicleOrigin = vehicleController.GetVehicle()->GetOrigin(); + idVec3 forward = vehicleAxis[ 0 ]; + idVec3 localTargetOrigin = target->GetPhysics()->GetOrigin(); + localTargetOrigin.z = vehicleOrigin.z;// Find way to include vehicleAxis here + idVec3 vectorToTarget = localTargetOrigin - vehicleOrigin; + idVec3 localDirToTarget = vectorToTarget; + float distToTarget = localDirToTarget.Normalize(); + + if( targetOrigin ) { + *targetOrigin = localTargetOrigin; + } + + if( dirToTarget ) { + *dirToTarget = localDirToTarget; + } + + if( distance ) { + *distance = distToTarget; + } + + if( dotForward ) { + *dotForward = localDirToTarget * forward; + } + + if( dotRight ) { + *dotRight = localDirToTarget * vehicleAxis[ 1 ]; + } + + // Debug help + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + idStr temp; + const idVec3 & origin = GetPhysics()->GetOrigin(); + gameRenderWorld->DebugLine( colorBlue, origin, target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DebugBounds( colorBlue, GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DebugBounds( colorBlue, target->GetPhysics()->GetBounds(), target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DrawText( target->GetName(), vehicleOrigin + vectorToTarget + vehicleAxis[2] * 5.0f, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + gameRenderWorld->DrawText( idStr( "State: " ) + LeaderHintsString( leaderFlags, temp ), origin, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + + if ( vehicleController.GetVehicle()->IsAutoCorrecting() ) { + gameRenderWorld->DebugBounds( colorPurple, vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DrawText( "Auto-Correcting", vehicleOrigin, 1.0f, colorPurple, gameLocal.GetLocalPlayer()->viewAxis ); + } + + if ( pathTargetInfo.node && g_debugVehicleDriver.GetInteger( ) == 2 ) { + gameRenderWorld->DebugBounds( colorOrange, pathTargetInfo.node->GetPhysics()->GetBounds() ); + + for ( int ix = 0; ix < pathTargetInfo.node->targets.Num(); ix++ ) { + gameRenderWorld->DebugLine( colorOrange, pathTargetInfo.node->GetPhysics()->GetOrigin(), pathTargetInfo.node->targets[ ix ]->GetPhysics()->GetOrigin(), 0, true ); + } + } + if ( leader ) { + idVec4 & color = colorGreen; + + if ( leader->GetLeaderHint() & VDLH_SlowDown ) { + color = colorYellow; + } else if ( leader->GetLeaderHint() & VDLH_Wait ) { + color = colorRed; + } + + idStr str = idStr( "decel_distance: " ) + idStr( decelDistance ) + idStr( "\nmin_distance: " ) + idStr( minDistance ); + + gameRenderWorld->DrawText( str, leader->GetPhysics()->GetOrigin(), 1.0f, color, gameLocal.GetLocalPlayer()->viewAxis, 1, 0, true ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), leader->GetPhysics()->GetOrigin(), 0, true ); + gameRenderWorld->DebugBounds( color, leader->vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds(), vec3_origin, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), decelDistance, 10, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), minDistance, 10, 0, true ); + } + } + + return true; +} + +/* +================ +rvVehicleDriver::ChooseNextNode +================ +*/ +idEntity* rvVehicleDriver::ChooseNextNode( idEntity* target ) { + if( !target ) { + return NULL; + } + + if( func.Init( target->spawnArgs.GetString( "call_nextNode" )) <= 0 ) { + idEntity * best = NULL; + float bestDist; + + switch ( pathingMode ) { + case VDPM_MoveTo: + bestDist = FLT_MAX; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist > distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + break; + + case VDPM_MoveAway: + bestDist = FLT_MIN; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist < distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + case VDPM_Custom: + if ( pathingEntity ) { +// best = pathingCallback( target ); + pathingEntity->ProcessEvent( &VD_ChoosePathTarget, target ); + best = gameLocal.program.GetReturnedEntity(); + } + } + + return ( best ) ? best : RandomValidTarget( target ); + } + + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + + return ( func.ReturnsAVal()) ? gameLocal.FindEntity( spawnArgs.GetString( func.GetReturnKey() )) : const_cast( target ); +} + +/* +================ +rvVehicleDriver::SimulateButtons +================ +*/ +void rvVehicleDriver::SimulateButtons( usercmd_t& cmd ) { + cmd.buttons = (fireEndTime >= gameLocal.time) ? BUTTON_ATTACK : 0; +} + +/* +================ +rvVehicleDriver::SimulateMouseMove +================ +*/ +void rvVehicleDriver::SimulateMouseMove( usercmd_t& cmd ) { + idVec3 origin; + idMat3 axis; + + idVec3 vectorToTarget; + idAngles anglesToTarget; + idAngles turretAngles; + idAngles deltaAngles; + + if( !lookTarget ) { + for( int ix = 0; ix < 3; ++ix ) { + cmd.angles[ix] = 0; + } + return; + } + + vehicleController.GetEyePosition( origin, axis ); + vectorToTarget = (lookTarget->GetPhysics()->GetOrigin() - origin).ToNormal(); + anglesToTarget = vectorToTarget.ToAngles().Normalize360(); + turretAngles = (axis[0]).ToAngles().Normalize360(); + deltaAngles = (anglesToTarget - turretAngles).Normalize180(); + + for( int ix = 0; ix < deltaAngles.GetDimension(); ++ix ) { + cmd.angles[ix] += ANGLE2SHORT( deltaAngles[ix] ); + } + + // Debug Output + if( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugLine( colorGreen, origin, origin + anglesToTarget.ToForward() * 100.0f, 17, true ); + gameRenderWorld->DebugLine( colorYellow, origin, origin + turretAngles.ToForward() * 100.0f, 17, true ); + } +} + +/* +================ +rvVehicleDriver::SimulateKeys +================ +*/ +void rvVehicleDriver::SimulateKeys( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + if( !vehicle ) { + cmd.forwardmove = 0; + cmd.rightmove = 0; + return; + } + + cmd.forwardmove = static_cast< signed char >( (!vehicle->IsAutoCorrecting() ? 127.0f : forwardThrustScale) * dotForward * speed ); + cmd.rightmove = static_cast< signed char >( ( ( dotForward < 0.0f ) ? rightThrustScale : -rightThrustScale ) * dotRight ); +} + + +/* +================ +rvVehicleDriver::SortValid +================ +*/ +int rvVehicleDriver::SortValid( const void* a, const void* b ) { + idEntityPtr A = *(idEntityPtr*)a; + idEntityPtr B = *(idEntityPtr*)b; + + return rvVehicleDriver::IsValidTarget( B ) - rvVehicleDriver::IsValidTarget( A ); +} + +/* +================ +rvVehicleDriver::SortTargetList +================ +*/ +int rvVehicleDriver::SortTargetList( idEntity* ent ) const { + if( !ent ) { + return 0; + } + + int numValidEntities = 0; + idEntity * target; + + ent->RemoveNullTargets(); + qsort( ent->targets.Ptr(), NumTargets( ent ), ent->targets.TypeSize(), rvVehicleDriver::SortValid ); + + for( int ix = NumTargets( ent ) - 1; ix >= 0; --ix ) { + target = GetTarget( ent, ix ); + + if( IsValidTarget( target ) ) { + ++numValidEntities; + } + } + + return numValidEntities; +} + +/* +================ +rvVehicleDriver::RandomValidTarget +================ +*/ +idEntity* rvVehicleDriver::RandomValidTarget( idEntity* ent ) const { + int numValid = NumValidTargets( ent ); + return (!numValid) ? NULL : ent->targets[ rvRandom::irand(0, numValid - 1) ]; +} + +/* +================ +rvVehicleDriver::NumValidTargets +================ +*/ +int rvVehicleDriver::NumValidTargets( idEntity* ent ) const { + return SortTargetList(ent); +} + +/* +================ +rvVehicleDriver::NumTargets +================ +*/ +int rvVehicleDriver::NumTargets( const idEntity* ent ) const { + return (ent) ? ent->targets.Num() : 0; +} + +/* +================ +rvVehicleDriver::GetTarget +================ +*/ +idEntity* rvVehicleDriver::GetTarget( const idEntity* ent, int index ) const { + return (ent) ? ent->targets[index] : NULL; +} + +/* +================ +rvVehicleDriver::IsValidPathNode +================ +*/ +bool rvVehicleDriver::IsValidPathNode( const idEntity* ent ) { + if( !ent ) { + return false; + } + + return ent->IsType( idTarget::GetClassType() ); +} + +/* +================ +rvVehicleDriver::IsValidTarget +================ +*/ +bool rvVehicleDriver::IsValidTarget( const idEntity* ent ) { + return IsValidPathNode( ent ) || ent->IsType( rvVehicle::GetClassType() ) || ent->IsType( idPlayer::GetClassType() ); +} + +/* +================ +rvVehicleDriver::SetLeader +================ +*/ +bool rvVehicleDriver::SetLeader( idEntity* ent ) { + if ( !ent || !ent->IsType( rvVehicleDriver::GetClassType() ) ) { + return false; + } + + leader = static_cast( ent ); + minDistance = leader->spawnArgs.GetFloat( "min_distance", "500" ); + idStr decel_distance( minDistance * 3.0f ); + decelDistance = leader->spawnArgs.GetFloat( "decel_distance", decel_distance ); + + return true; +} + +/* +================ +rvVehicleDriver::Event_PostSpawn +================ +*/ +void rvVehicleDriver::Event_PostSpawn ( void ) { + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + Event_EnterVehicle( ent ); + } + } + + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( idTarget::GetClassType() ) ) { + Event_ScriptedMove( ent, 0, 0 ); + } + if ( ent->IsType( rvVehicleDriver::GetClassType() ) ) { + SetLeader( ent ); + } + } +} + +/* +================ +rvVehicleDriver::Event_EnterVehicle +================ +*/ +void rvVehicleDriver::Event_EnterVehicle ( idEntity * vehicle ) { + if ( vehicle ) { + forwardThrustScale = vehicle->spawnArgs.GetFloat( "driver_forward_thrust", ".75" ) * 127.0f; + rightThrustScale = vehicle->spawnArgs.GetFloat( "driver_right_thrust", "1" ) * 127.0f; + + EnterVehicle( vehicle ); + } +} + +/* +================ +rvVehicleDriver::Event_ExitVehicle +================ +*/ +void rvVehicleDriver::Event_ExitVehicle( bool force ) { + Event_ScriptedStop(); + + if( vehicleController.GetVehicle() && force && !ExitVehicle(force) ) { + vehicleController.GetVehicle()->RemoveDriver( vehicleController.GetPosition(), force ); + } + + pathTargetInfo.node = NULL; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; +} + +/* +================ +rvVehicleDriver::Event_ScriptedMove +================ +*/ +void rvVehicleDriver::Event_ScriptedMove( idEntity *target, float minDist, bool exitVehicle ) { + isMoving = false; + + if( !target ) { + return; + } + + if( IsValidTarget( target ) ) { + isMoving = true; + faceTarget = NULL; + pathTargetInfo.node = target; + pathTargetInfo.initialDistance = ( target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + + if ( !target->IsType( idPlayer::GetClassType() ) ) { + pathTargetInfo.minDistance = target->spawnArgs.GetFloat( "min_distance", va("%f", Max(200.0f, minDist)) ); + pathTargetInfo.throttle = idMath::ClampFloat( 0.0f, 1.0f, target->spawnArgs.GetFloat( "throttle", "1" ) ); + pathTargetInfo.exitVehicle = target->spawnArgs.GetBool( "exit_vehicle", exitVehicle ? "1" : "0" ); + + if( pathTargetInfo.exitVehicle ) { + pathTargetInfo.throttle = 0.0f; + } + } else { + pathTargetInfo.minDistance = minDist; + pathTargetInfo.throttle = 1.0f; + pathTargetInfo.exitVehicle = false; + } + + } else { + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + } +} + +/* +================ +rvVehicleDriver::Event_ScriptedDone +================ +*/ +void rvVehicleDriver::Event_ScriptedDone( void ) { + idThread::ReturnFloat( !IsMoving() ); +} + +/* +================ +rvVehicleDriver::Event_ScriptedStop +================ +*/ +void rvVehicleDriver::Event_ScriptedStop( void ) { + if( IsDriving() ) { + usercmd_t cmd = { 0 }; + + vehicleController.SetInput( cmd, ang_zero ); + + if( pathTargetInfo.node ) { + if( func.Init( pathTargetInfo.node->spawnArgs.GetString( "call_doneMoving" )) > 0 ) { + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + } + + pathTargetInfo.node->ActivateTargets(this); + pathTargetInfo.node = NULL; + } + + isMoving = false; + } +} + +/* +================ +rvVehicleDriver::Event_Trigger +================ +*/ +void rvVehicleDriver::Event_Trigger( idEntity *activator ) { + if( IsDriving() && pathTargetInfo.node ) { + isMoving = true; + } +} + +/* +================ +rvVehicleDriver::Event_SetSpeed +================ +*/ +void rvVehicleDriver::Event_SetSpeed( float speed ) { + currentThrottle = speed; +} + +//twhitaker: remove - begin +/* +================ +rvVehicleDriver::Event_SetFollowOffset +================ +*/ +void rvVehicleDriver::Event_SetFollowOffset( const idVec3 &offset ) { + gameLocal.Warning( "Script Event \"followOffset\" is deprecated, please remove it immediately to avoid errors." ); +} +//twhitaker: remove - end + +/* +================ +rvVehicleDriver::Event_FireWeapon +================ +*/ +void rvVehicleDriver::Event_FireWeapon( float weapon_index, float time ) { + if( IsDriving() ) { + fireEndTime = gameLocal.GetTime() + SEC2MS( time ); + vehicleController.SelectWeapon( weapon_index ); + } +} + +/* +================ +rvVehicleDriver::Event_FaceEntity +================ +*/ +void rvVehicleDriver::Event_FaceEntity( const idEntity* entity ) { + if( IsMoving() ) { + return; + } + + faceTarget = entity; + isMoving = true; +} + +/* +================ +rvVehicleDriver::Event_LookAt +================ +*/ +void rvVehicleDriver::Event_LookAt( const idEntity* entity ) { + lookTarget = entity; +} + +/* +================ +rvVehicleDriver::Event_SetLeader +================ +*/ +void rvVehicleDriver::Event_SetLeader( idEntity* newLeader ) { + SetLeader( newLeader ); +} + +/* +================ +rvVehicleDriver::UpdateAutoCorrection +================ +*/ +void rvVehicleDriver::UpdateAutoCorrection ( void ) { + if ( IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + // Disregard your autocorrection state if we're slowing down to avoid collision with the leader. + if ( vehicle->IsAutoCorrecting() ) { + if ( avoidingLeader == true ) { + vehicle->autoCorrectionBegin = 0; + } + } + } +} + +/* +================ +rvVehicleDriver::Save +================ +*/ +void rvVehicleDriver::Save ( idSaveGame *savefile ) const { + savefile->Write ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Write ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->WriteFloat ( currentThrottle ); + + faceTarget.Save ( savefile ); + lookTarget.Save ( savefile ); + + leader.Save ( savefile ); + savefile->WriteInt ( leaderFlags ); + savefile->WriteFloat ( decelDistance ); + savefile->WriteFloat ( minDistance ); + savefile->WriteBool ( avoidingLeader ); + + savefile->WriteFloat ( fireEndTime ); + + func.Save ( savefile ); + + savefile->WriteBool ( isMoving ); + + savefile->WriteInt ( (int&)pathingMode ); + pathingEntity.Save ( savefile ); + savefile->WriteVec3 ( pathingOrigin ); + + savefile->WriteFloat( forwardThrustScale ); // cnicholson: Added unsaved var + savefile->WriteFloat( rightThrustScale ); // cnicholson: Added unsaved var + +} + +/* +================ +rvVehicleDriver::Restore +================ +*/ +void rvVehicleDriver::Restore ( idRestoreGame *savefile ) { + savefile->Read ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Read ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->ReadFloat ( currentThrottle ); + + faceTarget.Restore ( savefile ); + lookTarget.Restore ( savefile ); + + leader.Restore ( savefile ); + savefile->ReadInt ( leaderFlags ); + savefile->ReadFloat ( decelDistance ); + savefile->ReadFloat ( minDistance ); + savefile->ReadBool ( avoidingLeader ); + + savefile->ReadFloat ( fireEndTime ); + + func.Restore ( savefile ); + + savefile->ReadBool ( isMoving ); + + savefile->ReadInt ( (int&)pathingMode ); + pathingEntity.Restore ( savefile ); + savefile->ReadVec3 ( pathingOrigin ); + + savefile->ReadFloat( forwardThrustScale ); // cnicholson: Added unrestored var + savefile->ReadFloat( rightThrustScale ); // cnicholson: Added unrestored var +} diff --git a/src/game/vehicle/VehicleDriver.h b/src/game/vehicle/VehicleDriver.h new file mode 100644 index 0000000..aae54d4 --- /dev/null +++ b/src/game/vehicle/VehicleDriver.h @@ -0,0 +1,209 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleDriver.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +extern const idEventDef VD_ChoosePathTarget; + +class rvVehicleDriver : public idActor { + friend class rvVehicleAI; + friend class rvVehicleMonster; +public: + + CLASS_PROTOTYPE( rvVehicleDriver ); + + rvVehicleDriver ( void ); + + void Think ( void ); + void Spawn ( void ); + + static bool IsValidTarget ( const idEntity* ent ); // valid entity for looking or facing + static bool IsValidPathNode ( const idEntity* ent ); // valid entity for pathing + static bool IsValidLeader ( const idEntity* ent ); // valid entity to set as a leader + + static int SortValid ( const void* a, const void* b ); + + virtual void Present ( void ) { } + +protected: + void SimulateKeys ( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance = 0 ); + void SimulateButtons ( usercmd_t& cmd ); + void SimulateMouseMove ( usercmd_t& cmd ); + + bool GetTargetInfo ( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, + float* dotForward, float* dotRight, float* distance ) const; + idEntity* ChooseNextNode ( idEntity* target ); + + bool IsMoving ( void ) const { return isMoving && IsDriving(); } + bool IsDriving ( void ) const { return vehicleController.IsDriving(); } + + bool SetLeader ( idEntity* newLeader ); + const idEntity* GetLeader ( void ) const { return leader; } + void SetLeaderHint ( int flags ) { leaderFlags = flags; } + int GetLeaderHint ( void ) const { return leaderFlags; } + + int SortTargetList ( idEntity* ent ) const; + idEntity* RandomValidTarget ( idEntity* ent ) const; + int NumValidTargets ( idEntity* ent ) const; + int NumTargets ( const idEntity* ent ) const; + idEntity* GetTarget ( const idEntity* ent, int index ) const; + + void UpdateAutoCorrection ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum VD_PathingMode { + VDPM_Random = 0, + VDPM_MoveTo = 1, + VDPM_MoveAway = 2, + VDPM_Custom = 4, + }; + + + void SetPathingMode ( VD_PathingMode mode, const idVec3 & origin ); +// CustomPathingFunc SetPathingMode ( VD_PathingMode mode, CustomPathingFunc cb ); + idEntity * SetPathingMode ( VD_PathingMode mode, idEntity * ); + VD_PathingMode GetPathingMode ( void ) const; + const idVec3 & GetPathingOrigin ( void ) const; +// CustomPathingFunc GetPathingCustomCallback ( void ) const; + idEntity * GetPathingEntity ( void ) const; + +protected: + void Event_PostSpawn ( void ); + void Event_EnterVehicle ( idEntity * vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_ScriptedMove ( idEntity *target, float minDist, bool exitVehicle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_Trigger ( idEntity *activator ); + void Event_SetSpeed ( float speed ); + void Event_FireWeapon ( float weapon_index, float time ); + void Event_FaceEntity ( const idEntity* entity ); + void Event_LookAt ( const idEntity* entity ); + void Event_SetLeader ( idEntity* newLeader ); + +//twhitaker: remove - begin + void Event_SetFollowOffset ( const idVec3 &offset ); +//twhitaker: remove - end + + // Leader hints : a driver will send the following driving hints to it's leader + enum VD_LeaderHints { + VDLH_SlowDown = 1, // Set when distance is too great + VDLH_Wait = 2, // Set when there is no line of site + VDLH_Continue = 0, // Default following behavior + }; + + idStr & LeaderHintsString( int hints, idStr & out ) const { + switch ( hints ) { + case VDLH_SlowDown: out = "SlowDown"; break; + case VDLH_Wait: out = "Wait"; break; + default: out = "Continue"; break; + } + return out; + } + + // Stores information about a path target + struct PathTargetInfo { + idEntityPtr node; // the target entity + float initialDistance; // the distance to this entity when we first decide to move there + float minDistance; // the range that specifies when this entity has been reached + float throttle; // 0 -> 1 speed setting + bool exitVehicle; // set to true to exit a vehicle when we reach this target + }; + + struct PathNavigationData { + float dotForward; + float dotRight; + }; + + // standard movement + PathTargetInfo pathTargetInfo; + PathTargetInfo lastPathTargetInfo; + float currentThrottle; + + // facing + idEntityPtr faceTarget; + idEntityPtr lookTarget; + + // following + idEntityPtr leader; //TODO: twhitaker: see if I can make this an rvVehicle (so entities can follow player) + int leaderFlags; + float decelDistance; + float minDistance; + bool avoidingLeader; + + // firing + float fireEndTime; + + // event callbacks + rvScriptFuncUtility func; + + // state + bool isMoving; + + VD_PathingMode pathingMode; +// idEntity * (* pathingCallback)( idEntity * ); + idEntityPtr pathingEntity; + idVec3 pathingOrigin; + + float forwardThrustScale; + float rightThrustScale; +}; + + +ID_INLINE void rvVehicleDriver::SetPathingMode( VD_PathingMode mode, const idVec3 & origin ) { + if ( mode != VDPM_Custom ) { + pathingMode = mode; + pathingOrigin = origin; + } +} + +ID_INLINE idEntity * rvVehicleDriver::SetPathingMode( VD_PathingMode mode, idEntity * entity ) { + if ( mode != VDPM_Custom ) { + return NULL; + } + + pathingMode = mode; + idEntity * prev = pathingEntity; + pathingEntity = entity; + return prev; +} + +ID_INLINE rvVehicleDriver::VD_PathingMode rvVehicleDriver::GetPathingMode ( void ) const { + return pathingMode; +} + +ID_INLINE const idVec3 & rvVehicleDriver::GetPathingOrigin ( void ) const { + return pathingOrigin; +} + +ID_INLINE idEntity * rvVehicleDriver::GetPathingEntity ( void ) const { + return pathingEntity; +} + diff --git a/src/game/vehicle/VehicleMonster.cpp b/src/game/vehicle/VehicleMonster.cpp new file mode 100644 index 0000000..092551c --- /dev/null +++ b/src/game/vehicle/VehicleMonster.cpp @@ -0,0 +1,212 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleMonster.h" +#include "../ai/VehicleAI.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleMonster ) +END_CLASS + +rvVehicleMonster::rvVehicleMonster ( void ) { +} + +rvVehicleMonster::~rvVehicleMonster ( void ) { +} + +/* +================ +rvVehicleMonster::Spawn +================ +*/ +void rvVehicleMonster::Spawn( void ) { + idDict dict; + int count = 0; + const idKeyValue * val = NULL; + + for (;;) { + val = spawnArgs.MatchPrefix( "target", val ); + + if ( !val ) + break; + + dict.Set( "target" + (( count ) ? idStr( count ) : idStr( "" ) ), val->GetValue() ); + count++; + } + + dict.Set( "bind", name ); + dict.SetBool( "hide", true ); + + driver = static_cast( gameLocal.SpawnEntityDef( "ai_vehicle_driver", &dict ) ); + + if ( driver ) { + driver->SetVehicle( this ); + driver->GetPhysics()->SetOrigin( vec3_zero ); + } else { + gameLocal.Warning( "Unable to find \"entityDef ai_vehicle_driver\"." ); + } +} + +/* +================ +rvVehicleMonster::SetClipModel +================ +*/ +void rvVehicleMonster::SetClipModel ( idPhysics & physicsObj ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleMonster '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + +/* +================ +rvVehicleMonster::Save +================ +*/ +void rvVehicleMonster::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); +} + +/* +================ +rvVehicleMonster::Restore +================ +*/ +void rvVehicleMonster::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); +} + +/* +================ +rvVehicleMonster::Think +================ +*/ +void rvVehicleMonster::Think ( void ) { + if ( !driver ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + stateThread.Execute(); + rvVehicle::Think(); +} + +/* +================ +rvVehicleMonster::GetTargetOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetTargetOrigin ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToTarget +================ +*/ +idVec3 rvVehicleMonster::GetVectorToTarget ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin() - GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetEnemyOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetEnemyOrigin ( void ) { + if ( driver && driver->enemy.ent ) { + return driver->enemy.ent->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToEnemy +================ +*/ +idVec3 rvVehicleMonster::GetVectorToEnemy ( void ) { + if ( driver && driver->enemy.ent ) { + //HACK: this is hideous, I'm ashamed. + return ( driver->enemy.ent->GetPhysics()->GetOrigin() + idVec3( 0, 0, 36 ) )- GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::LookAtEntity +================ +*/ +void rvVehicleMonster::LookAtEntity ( idEntity *ent, float duration ) { + const idVec3 entOrigin = ent->GetPhysics()->GetOrigin(); + const idVec3 origin = GetOrigin(); + idVec3 toEnt = entOrigin - origin; + toEnt.Normalize(); + GetPhysics()->SetAxis( toEnt.ToMat3() ); +} + +/* +================ +rvVehicleMonster::SkipImpulse +================ +*/ +bool rvVehicleMonster::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/src/game/vehicle/VehicleMonster.h b/src/game/vehicle/VehicleMonster.h new file mode 100644 index 0000000..8849bef --- /dev/null +++ b/src/game/vehicle/VehicleMonster.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleMonster.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEMONSTER_H__ +#define __GAME_VEHICLEMONSTER_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleAI; + +class rvVehicleMonster : public rvVehicle { + friend class rvVehicleAI; +public: + + CLASS_PROTOTYPE( rvVehicleMonster ); + + rvVehicleMonster ( void ); + ~rvVehicleMonster ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( idPhysics & physicsObj ); + + const idVec3 & GetTargetOrigin ( void ); + idVec3 GetVectorToTarget ( void ); + const idVec3 & GetEnemyOrigin ( void ); + idVec3 GetVectorToEnemy ( void ); + void LookAtEntity ( idEntity *ent, float duration ); + + idEntityPtr driver; +}; + +#endif // __GAME_VEHICLEMONSTER_H__ diff --git a/src/game/vehicle/VehicleParts.cpp b/src/game/vehicle/VehicleParts.cpp new file mode 100644 index 0000000..92dee95 --- /dev/null +++ b/src/game/vehicle/VehicleParts.cpp @@ -0,0 +1,2410 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleParts.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "../Effect.h" +#include "Vehicle.h" +#include "VehicleParts.h" +#include "../ai/AI_Manager.h" + +/*********************************************************************** + + rvVehiclePart + +***********************************************************************/ + +ABSTRACT_DECLARATION( idClass, rvVehiclePart ) +END_CLASS + +/* +===================== +rvVehiclePart::Init +===================== +*/ +bool rvVehiclePart::Init ( rvVehiclePosition* _position, const idDict& args, s_channelType _soundChannel ) { + spawnArgs.Copy ( args ); + + soundChannel = _soundChannel; + position = _position; + parent = position->GetParent(); + fl.active = false; + fl.useCenterMass = false; + + return true; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehiclePart::Spawn ( void ) { + // Get joint for orienting the part + joint = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint", "" ) ); + + // More position information + fl.useCenterMass = spawnArgs.GetBool ( "useCenterMass", "0" ); + spawnArgs.GetVector ( "offset", "0 0 0", localOffset ); + + fl.active = false; + + fl.useViewAxis = spawnArgs.GetBool( "useViewAxis", "0" ); + + // Initialize the origin so we can determine which side of the vehicle we are on + UpdateOrigin ( ); + + // Determine if this part is on the left and/or front side of the vehicle + idVec3 localOrigin; + localOrigin = (worldOrigin - parent->GetPhysics()->GetCenterMass()); + fl.front = (localOrigin * parent->GetPhysics()->GetAxis()[0] < 0.0f); + fl.left = (localOrigin * parent->GetPhysics()->GetAxis()[1] < 0.0f); +} + +/* +================ +rvVehiclePart::UpdateOrigin +================ +*/ +void rvVehiclePart::UpdateOrigin ( void ) { + if ( joint != INVALID_JOINT ) { + parent->GetJointWorldTransform ( joint, gameLocal.time, worldOrigin, worldAxis ); + } else { + if ( fl.useViewAxis ) { + worldAxis = parent->viewAxis; + } else { + worldAxis = parent->GetPhysics()->GetAxis(); + } + worldOrigin = fl.useCenterMass ? parent->GetPhysics()->GetCenterMass() : parent->GetPhysics()->GetOrigin(); + } + + worldOrigin += (localOffset * worldAxis); + + // Include this part in the total bounds + // FIXME: bounds are local + parent->AddToBounds ( worldOrigin ); +} + +/* +================ +rvVehiclePart::Save +================ +*/ +void rvVehiclePart::Save ( idSaveGame* savefile ) const { + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteDict ( &spawnArgs ); + parent.Save ( savefile ); + savefile->WriteJoint ( joint ); + savefile->WriteInt ( soundChannel ); + savefile->WriteInt ( parent->GetPositionIndex ( position ) ); + + savefile->WriteVec3 ( worldOrigin ); + savefile->WriteMat3 ( worldAxis ); + savefile->WriteVec3 ( localOffset ); +} + +/* +================ +rvVehiclePart::Restore +================ +*/ +void rvVehiclePart::Restore ( idRestoreGame* savefile ) { + int temp; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadDict ( &spawnArgs ); + parent.Restore ( savefile ); + savefile->ReadJoint ( joint ); + savefile->ReadInt ( soundChannel ); + + savefile->ReadInt ( temp ); + position = parent->GetPosition ( temp ); + + savefile->ReadVec3 ( worldOrigin ); + savefile->ReadMat3 ( worldAxis ); + savefile->ReadVec3 ( localOffset ); +} + +/*********************************************************************** + + rvVehicleSound + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleSound ) +END_CLASS + +/* +===================== +rvVehicleSound::rvVehicleSound +===================== +*/ +rvVehicleSound::rvVehicleSound ( void ) { + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + fade = false; + autoActivate = true; +} + +/* +===================== +rvVehicleSound::~rvVehicleSound +===================== +*/ +rvVehicleSound::~rvVehicleSound ( void ) { + Stop(); + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehicleSound::Spawn ( void ) { + soundName = spawnArgs.GetString ( "snd_loop" ); + + spawnArgs.GetVec2 ( "freqShift", "1 1", freqShift ); + spawnArgs.GetVec2 ( "volume", "0 0", volume ); + + // Temp fix for volume + volume[0] = idMath::dBToScale( volume[0] ); + volume[1] = idMath::dBToScale( volume[1] ); + + declManager->FindSound ( soundName )->GetParms ( &refSound.parms ); +} + +/* +===================== +rvVehicleSound::RunPostPhysics +===================== +*/ +void rvVehicleSound::RunPostPhysics ( void ) { + Update ( ); +} + +/* +===================== +rvVehicleSound::Activate +===================== +*/ +void rvVehicleSound::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( autoActivate ) { + if ( active ) { + Play ( ); + } else { + Stop ( ); + } + } +} + +/* +===================== +rvVehicleSound::Play +===================== +*/ +void rvVehicleSound::Play ( void ) { + if ( !soundName.Length ( ) ) { + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + Attenuate ( 0.0f, 0.0f ); + + Update ( true ); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound ( declManager->FindSound( soundName ), soundChannel, 0, 0 ); + } +} + +/* +===================== +rvVehicleSound::Stop +===================== +*/ +void rvVehicleSound::Stop ( void ) { + if ( IsPlaying ( ) ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->StopSound( soundChannel ); + } + } +} + +/* +===================== +rvVehicleSound::Update +===================== +*/ +void rvVehicleSound::Update ( bool force ) { + if ( !force && !IsPlaying ( ) ) { + return; + } + + if ( fade && currentVolume.IsDone ( gameLocal.time ) ) { + Stop ( ); + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return; + } + + UpdateOrigin ( ); + + refSound.parms.volume = currentVolume.GetCurrentValue ( gameLocal.time ); + refSound.parms.frequencyShift = currentFreqShift.GetCurrentValue ( gameLocal.time ); + emitter->ModifySound ( soundChannel, &refSound.parms ); + + refSound.origin = worldOrigin; + // bdube: please put something sensible here if you want doppler from the sound system + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, parent->entityNumber + 1, &refSound.parms ); +} + +/* +===================== +rvVehicleSound::Attenuate +===================== +*/ +void rvVehicleSound::Attenuate ( float volumeAttenuate, float freqAttenuate ) { + float f; + + fade = false; + + f = volume[0] + (volume[1]-volume[0]) * volumeAttenuate; + currentVolume.Init ( gameLocal.time, 100, currentVolume.GetCurrentValue(gameLocal.time), f ); + + f = freqShift[0] + (freqShift[1]-freqShift[0]) * freqAttenuate; + currentFreqShift.Init ( gameLocal.time, 100, currentFreqShift.GetCurrentValue(gameLocal.time), f ); +} + +/* +===================== +rvVehicleSound::Fade +===================== +*/ +void rvVehicleSound::Fade ( int duration, float toVolume, float toFreq ) { + currentVolume.Init ( gameLocal.time, duration, currentVolume.GetCurrentValue(gameLocal.time), toVolume ); + currentFreqShift.Init ( gameLocal.time, duration, currentFreqShift.GetCurrentValue(gameLocal.time), toFreq ); +} + +/* +===================== +rvVehicleSound::Save +===================== +*/ +void rvVehicleSound::Save ( idSaveGame* savefile ) const { + savefile->WriteVec2 ( volume ); + savefile->WriteVec2 ( freqShift ); + savefile->WriteString ( soundName ); + savefile->WriteRefSound ( refSound ); + + savefile->WriteInterpolate ( currentVolume ); + savefile->WriteInterpolate ( currentFreqShift ); + + savefile->WriteBool ( fade ); + savefile->WriteBool ( autoActivate ); +} + +/* +===================== +rvVehicleSound::Restore +===================== +*/ +void rvVehicleSound::Restore ( idRestoreGame* savefile ) { + savefile->ReadVec2 ( volume ); + savefile->ReadVec2 ( freqShift ); + savefile->ReadString ( soundName ); + savefile->ReadRefSound ( refSound ); + + savefile->ReadInterpolate ( currentVolume ); + savefile->ReadInterpolate ( currentFreqShift ); + + savefile->ReadBool ( fade ); + savefile->ReadBool ( autoActivate ); +} + +//---------------------------------------------------------------- +// +// rvVehicleLight +// +//---------------------------------------------------------------- + +CLASS_DECLARATION( rvVehiclePart, rvVehicleLight ) +END_CLASS + +/* +===================== +rvVehicleLight::rvVehicleLight +===================== +*/ +rvVehicleLight::rvVehicleLight ( void ) { + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::~rvVehicleLight +===================== +*/ +rvVehicleLight::~rvVehicleLight ( void ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +/* +===================== +rvVehicleLight::Spawn +===================== +*/ +void rvVehicleLight::Spawn ( void ) { + idVec3 color; + const char* temp; + + memset ( &renderLight, 0, sizeof(renderLight) ); + + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "pointlight", "1" ); + + spawnArgs.GetVector( "color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "target" ); + renderLight.up = spawnArgs.GetVector( "up" ); + renderLight.right = spawnArgs.GetVector( "right" ); + renderLight.end = spawnArgs.GetVector( "target" );; + } + + // Hide flare surface if there is one + temp = spawnArgs.GetString ( "flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + + // Sounds shader when turning light + spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + spawnArgs.GetString ( "snd_off", "", soundOff); + + lightOn = spawnArgs.GetBool( "start_on", "1" ); + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::RunPostPhysics +===================== +*/ +void rvVehicleLight::RunPostPhysics ( void ) { + if ( lightHandle == -1 || !lightOn ) { + return; + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::UpdateLightDef +===================== +*/ +void rvVehicleLight::UpdateLightDef ( void ) { + UpdateOrigin ( ); + + renderLight.origin = worldOrigin; + renderLight.axis = worldAxis; + + if ( lightHandle == -1 ) { + return; + } + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); +} + +/* +===================== +rvVehicleLight::Activate +===================== +*/ +void rvVehicleLight::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active && lightOn ) { + TurnOn ( ); + } else { + TurnOff ( ); + } +} + +/* +===================== +rvVehicleLight::TurnOff +===================== +*/ +void rvVehicleLight::TurnOff ( void ) { + const char* surface; + + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + + // Play off sound + if ( soundOff.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOff ), soundChannel, 0, false, NULL ); + } + } + + // Hide flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_HideSurface, surface ); + } +} + +/* +===================== +rvVehicleLight::TurnOn +===================== +*/ +void rvVehicleLight::TurnOn ( void ) { + const char* surface; + + if ( lightHandle == -1 ) { + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + // Play off sound + if ( soundOn.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOn ), soundChannel, 0, false, NULL ); + } + + // Show flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_ShowSurface, surface ); + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::Impulse +===================== +*/ +void rvVehicleLight::Impulse ( int impulse ) { + switch ( impulse ) { + case IMPULSE_50: + if ( lightOn ) { + lightOn = false; + TurnOff ( ); + } else { + lightOn = true; + TurnOn ( ); + } + break; + } +} + +/* +===================== +rvVehicleLight::Save +===================== +*/ +void rvVehicleLight::Save ( idSaveGame* savefile ) const { + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + savefile->WriteBool ( lightOn ); + savefile->WriteString ( soundOn ); + savefile->WriteString ( soundOff ); +} + +/* +===================== +rvVehicleLight::Restore +===================== +*/ +void rvVehicleLight::Restore ( idRestoreGame* savefile ) { + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadBool ( lightOn ); + savefile->ReadString ( soundOn ); + savefile->ReadString ( soundOff ); +} + +/*********************************************************************** + + rvVehicleWeapon + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleWeapon ) +END_CLASS + +#define WEAPON_DELAY_FIRE 500 + +/* +===================== +rvVehicleWeapon::rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::rvVehicleWeapon ( void ) { +#ifdef _XENON + bestEnemy = 0; +#endif + nextFireTime = 0; + fireDelay = 0; + hitScanDef = NULL; + projectileDef = NULL; + animNum = 0; +} + +/* +===================== +rvVehicleWeapon::~rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::~rvVehicleWeapon ( void ) { + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + StopTargetEffect( ); +} + +/* +===================== +rvVehicleWeapon::Spawn +===================== +*/ +void rvVehicleWeapon::Spawn ( void ) { + int i; + idStr temp; + idVec3 color; + +#ifdef _XENON + bestEnemy = 0; +#endif + + launchFromJoint = spawnArgs.GetBool ( "launchFromJoint", "0" ); + lockScanning = spawnArgs.GetBool ( "lockScanning", "0" ); + lastLockTime = 0; + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + fireDelay = SEC2MS ( spawnArgs.GetFloat ( "firedelay" ) ); + spread = spawnArgs.GetFloat ( "spread" ); + jointIndex = 0; + count = spawnArgs.GetInt ( "count", "1" ); + lockRange = spawnArgs.GetFloat ( "lockrange", "0" ); + ammoPerCharge = spawnArgs.GetInt ( "ammopercharge", "-1" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargetime" ) ); + currentAmmo = ammoPerCharge; + muzzleFlashHandle = -1; + + if( spawnArgs.GetString("anim", "", temp) && *temp ) { + animNum = parent->GetAnimator()->GetAnim( temp ); + animChannel = spawnArgs.GetInt( "animChannel" ); + } + + spawnArgs.GetVector ( "force", "0 0 0", force ); + + // Vehicle guns can have multiple joints to fire from. They will be cycled through + // when firing. + joints.Append ( joint ); + for ( i = 2; ; i ++ ) { + jointHandle_t joint2; + joint2 = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint%d", i ) ) ); + if ( joint2 == INVALID_JOINT ) { + break; + } + + joints.Append ( joint2 ); + } + + // Muzzle Flash + memset ( &muzzleFlash, 0, sizeof(muzzleFlash) ); + + spawnArgs.GetVector ( "flashOffset", "0 0 0", muzzleFlashOffset ); + muzzleFlash.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_flashShader", "lights/muzzleflash" ), false ); + muzzleFlash.pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + muzzleFlash.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + spawnArgs.GetVector( "flashColor", "0 0 0", color ); + muzzleFlash.shaderParms[ SHADERPARM_RED ] = color[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = color[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = color[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + muzzleFlash.lightRadius[0] = muzzleFlash.lightRadius[1] = muzzleFlash.lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); + + if ( !muzzleFlash.pointLight ) { + muzzleFlash.target = spawnArgs.GetVector( "flashTarget" ); + muzzleFlash.up = spawnArgs.GetVector( "flashUp" ); + muzzleFlash.right = spawnArgs.GetVector( "flashRight" ); + muzzleFlash.end = spawnArgs.GetVector( "flashTarget" ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // get the brass def + idStr name; + brassDict = NULL; + if ( spawnArgs.GetString( "def_ejectBrass", "", name ) && *name ) { + brassDict = gameLocal.FindEntityDefDict( name, false ); + + if ( !brassDict ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon on vehicle '%s'", name.c_str(), position->mParent->name.c_str() ); + } + } + + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); +// wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); +} + +/* +===================== +rvVehicleWeapon::Activate +===================== +*/ +void rvVehicleWeapon::Activate ( bool activate ) { + rvVehiclePart::Activate ( activate ); + + nextFireTime = gameLocal.time + WEAPON_DELAY_FIRE; + +#ifdef _XENON + bestEnemy = 0; +#endif +} + +/* +===================== +rvVehicleWeapon::StopTargetEffect +===================== +*/ +void rvVehicleWeapon::StopTargetEffect ( void ) { + if ( targetEffect ) { + targetEffect->Stop( ); + targetEffect = NULL; + } +} + +/* +===================== +rvVehicleWeapon::UpdateLock +===================== +*/ +void rvVehicleWeapon::UpdateLock ( void ) { + +#ifdef _XENON + + bestEnemy = 0; + + // Handle auto-aim if it's enabled + if ( cvarSystem->GetCVarBool("pm_AimAssist") ) { + + const float testDist = 2000.0f; // huge because we're outdoors + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + + idVec3 start = position->GetEyeOrigin(); + idVec3 end = start + (position->GetEyeAxis().ToAngles().ToForward() * testDist); + + idBounds bounds( start ); + bounds.AddPoint( end ); + + idClipModel *clipModelList[ MAX_GENTITIES ]; + int numClipModels = gameLocal.ClipModelsTouchingBounds( player, bounds, -1, clipModelList, MAX_GENTITIES ); + + float bDist = testDist; + + float fovX, fovY; + gameLocal.CalcFov( player->CalcFov( true ), fovX, fovY ); + float dNear = cvarSystem->GetCVarFloat( "r_znear" ); + float dFar = testDist; + float size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * float(cvarSystem->GetCVarInteger("pm_AimAssistFOV")) / 100.0f; + + idFrustum aimArea; + aimArea.SetOrigin( start ); + aimArea.SetAxis( position->GetEyeAxis() ); + aimArea.SetSize( dNear, dFar, size, size ); + + for ( int i = 0; i < numClipModels; i++ ) { + + idClipModel *clip = clipModelList[ i ]; + idEntity *ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + if ( static_cast( ent )->team == player->team ) { + continue; + } + } else { + continue; + } + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + + const idBounds &b = ent->GetPhysics()->GetAbsBounds(); + bool inside = aimArea.IntersectsBounds(b); + + if ( inside ) { + float dist = b.ShortestDistance(start); + if ( bDist > dist ) { + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } +#endif + + if ( !position->GetDriver() || parent->health <= 0 || !lockScanning) { + StopTargetEffect( ); + } else if ( lockScanning && position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //always update locking info + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + // Update the guide effect + if ( targetEnt && targetEnt.IsValid() && targetEnt->health > 0 && targetEnt->IsType( idActor::GetClassType() ) ) { + idVec3 eyePos; + if ( targetJoint != INVALID_JOINT ) { + idMat3 jointAxis; + targetEnt->GetAnimator()->GetJointTransform( targetJoint, gameLocal.GetTime(), eyePos, jointAxis ); + eyePos = targetEnt->GetRenderEntity()->origin + (eyePos*targetEnt->GetRenderEntity()->axis); + if ( !targetPos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * targetEnt->GetRenderEntity()->axis; + eyePos += jointAxis[0]*targetPos[0]; + eyePos += jointAxis[1]*targetPos[1]; + eyePos += jointAxis[2]*targetPos[2]; + } + } else { + eyePos = static_cast(targetEnt.GetEntity())->GetEyePosition(); + eyePos += targetEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + eyePos *= 0.5f; + } + if ( targetEffect ) { + targetEffect->SetOrigin ( eyePos ); + targetEffect->SetAxis ( player->firstPersonViewAxis.Transpose() ); + } else { + targetEffect = gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_guide" ), eyePos, player->firstPersonViewAxis.Transpose(), true, vec3_origin, false ); + if ( targetEffect ) { + targetEffect->GetRenderEffect()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + targetEffect->GetRenderEffect()->allowSurfaceInViewID = position->GetDriver()->entityNumber + 1; + } + } + } else { + StopTargetEffect( ); + } + } + } +} + +/* +===================== +rvVehicleWeapon::RunPostPhysics +===================== +*/ +void rvVehicleWeapon::RunPostPhysics ( void ) { + if ( !IsActive() || !parent->IsShootingEnabled ( ) ) { + return; + } + + if ( currentAmmo <= 0 && currentCharge.IsDone( gameLocal.GetTime() ) ) { + currentAmmo = ammoPerCharge; + } + + UpdateLock(); + + if ( !(position->mInputCmd.buttons & BUTTON_ATTACK ) ) { + return; + } + + if ( gameLocal.time <= nextFireTime || !currentCharge.IsDone(gameLocal.GetTime()) ) { + return; + } + + // Gun animation? + if ( animNum ) { + parent->GetAnimator()->PlayAnim ( animChannel, animNum, gameLocal.time, 0 ); + } + + if( !spawnArgs.GetBool("launchOnFrameCommand") ) { + if (!Fire()) + { + return; + } + } + + nextFireTime = gameLocal.time + fireDelay; +} + +/* +================ +rvVehicleWeapon::WeaponFeedback +================ +*/ +void rvVehicleWeapon::WeaponFeedback( const idDict* dict ) { + if( !dict || !GetPosition() || !GetPosition()->IsOccupied() ) { + return; + } + + idActor* actor = GetPosition()->GetDriver(); + if( !actor || !actor->IsType( idPlayer::GetClassType() ) ) { + return; + } + + //abahr: This feels like a hack. I hate using def files for logic but it just seems the easiest way to do it + idPlayer* player = static_cast( actor ); + if( dict->GetInt("recoilTime") ) { + player->playerView.WeaponFireFeedback( dict ); + } + if( dict->GetInt("shakeTime") ) { + player->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + dict->GetInt("shakeTime")), dict->GetFloat("shakeMagnitude") ); + } + EjectBrass(); +} + +/* +================ +rvVehicleWeapon::MuzzleFlashLight +================ +*/ +void rvVehicleWeapon::MuzzleFlashLight( const idVec3& origin, const idMat3& axis ) { + if ( !muzzleFlash.lightRadius[0] ) { + return; + } + + muzzleFlash.origin = origin; + muzzleFlash.axis = axis; + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = parent->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } +} + +/* +===================== +rvVehicleWeapon::UpdateCursorGUI +===================== +*/ +void rvVehicleWeapon::UpdateCursorGUI ( idUserInterface* gui ) const { + // cnicholson: I do't see a reason why this if statement is here. Its ALWAYS false and so this block never executed, + // thus if the player was in a vehicle, the crosshair was always the player's last held weapon, not the crosshair defined in the vehicle .def. + // So I commented the if part out. + //if ( spawnArgs.GetBool( "hide_crosshair", "0" ) ) { + gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString ( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + gui->SetStateString ( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt ( "crossOffsetX", spawnArgs.GetInt ( "crosshairOffsetX", "0" ) ); + gui->SetStateInt ( "crossOffsetY", spawnArgs.GetInt ( "crosshairOffsetY", "0" ) ); + gui->StateChanged ( gameLocal.time ); + //} +} + +/* +===================== +rvVehicleWeapon::Save +===================== +*/ +void rvVehicleWeapon::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteInt ( nextFireTime ); + savefile->WriteInt ( fireDelay ); + savefile->WriteInt ( count ); + // TOSAVE: const idDict* projectileDef; + // TOSVAE: const idDict* hitScanDef; + + savefile->WriteFloat ( spread ); + savefile->WriteBool ( launchFromJoint ); + savefile->WriteBool ( lockScanning ); + savefile->WriteInt ( lastLockTime ); + savefile->WriteFloat ( lockRange ); + + savefile->WriteInt ( joints.Num() ); + for ( i = 0; i < joints.Num(); i ++ ) { + savefile->WriteJoint ( joints[i] ); + } + savefile->WriteInt ( jointIndex ); + + savefile->WriteVec3 ( force ); + + savefile->WriteInt ( ammoPerCharge ); + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( currentAmmo ); + savefile->WriteInterpolate ( currentCharge ); + + savefile->WriteRenderLight ( muzzleFlash ); + savefile->WriteInt ( muzzleFlashHandle ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashOffset ); + + savefile->WriteInt ( animNum ); + savefile->WriteInt ( animChannel ); + + targetEnt.Save( savefile ); + savefile->WriteJoint( targetJoint ); + savefile->WriteVec3( targetPos ); + targetEffect.Save( savefile ); + // Don't save shaderFire or shaderReload, they are setup in Restore + + savefile->WriteInt( zoomFov ); + savefile->WriteUserInterface( zoomGui, true ); + savefile->WriteFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +void rvVehicleWeapon::Restore ( idRestoreGame* savefile ) { + int i; + int num; + idStr temp; + +#ifdef _XENON + bestEnemy = 0; +#endif + + savefile->ReadInt ( nextFireTime ); + savefile->ReadInt ( fireDelay ); + savefile->ReadInt ( count ); + savefile->ReadFloat ( spread ); + savefile->ReadBool ( launchFromJoint ); + savefile->ReadBool ( lockScanning ); + savefile->ReadInt ( lastLockTime ); + savefile->ReadFloat ( lockRange ); + + savefile->ReadInt ( num ); + joints.Clear ( ); + joints.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadJoint ( joints[i] ); + } + savefile->ReadInt ( jointIndex ); + + savefile->ReadVec3 ( force ); + + savefile->ReadInt ( ammoPerCharge ); + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( currentAmmo ); + savefile->ReadInterpolate( currentCharge ); + + savefile->ReadRenderLight ( muzzleFlash ); + savefile->ReadInt ( muzzleFlashHandle ); + if ( muzzleFlashHandle != -1 ) { + //get the handle again as it's out of date after a restore! + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } + + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashOffset ); + + savefile->ReadInt ( animNum ); + savefile->ReadInt ( animChannel ); + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // Brass Def + brassDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_ejectBrass" ), false ); + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt.Restore( savefile ); + savefile->ReadJoint( targetJoint ); + savefile->ReadVec3( targetPos ); + targetEffect.Restore( savefile ); + + savefile->ReadInt( zoomFov ); + savefile->ReadUserInterface( zoomGui, &spawnArgs ); + savefile->ReadFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +bool rvVehicleWeapon::Fire() { + if ( muzzleFlashHandle != -1 && gameLocal.time >= muzzleFlashEnd ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + +// twhitaker: moved to rvVehicleWeapon::RunPostPhysics so that the HUD would update without having to fire the gun +// if ( currentAmmo == 0 ) { +// currentAmmo = ammoPerCharge; +// } + + LaunchProjectiles(); + + if ( currentAmmo == -1 ) { + currentCharge.Init ( gameLocal.time, fireDelay, 0.0, 1.0f ); + } else { + currentAmmo--; + if ( currentAmmo <= 0 ) { + currentAmmo = 0; + if ( chargeTime ) { + parent->StartSoundShader( shaderReload, SND_CHANNEL_ANY, 0, false, NULL ); + currentCharge.Init ( gameLocal.time, chargeTime, 0.0f, 1.0f ); + } + } + } + return true; +} + +#ifdef _XENON +/* +===================== +rvVehicleWeapon::AutoAim +===================== +*/ +void rvVehicleWeapon::AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ) { + + if ( player ) { + + // If we have a enemy selected, handle aim correction + if ( bestEnemy ) { + + idVec3 dif = bestEnemy->GetRenderEntity()->origin - origin; + idVec3 muzzleDest = origin + (dir * dif.Length() ); + + // Transform start and end into the enemy's body space + idVec3 localStart = bestEnemy->GetRenderEntity()->axis / (origin - bestEnemy->GetRenderEntity()->origin); + idVec3 localEnd = bestEnemy->GetRenderEntity()->axis / (muzzleDest - bestEnemy->GetRenderEntity()->origin); + + if ( bestEnemy->GetAnimator() ) { + int numJoints = bestEnemy->GetAnimator()->NumJoints(); + + if ( numJoints ) { + + jointHandle_t nearJoint = bestEnemy->GetAnimator()->GetNearestJoint( localStart, localEnd, gameLocal.time, 0, numJoints ); + + // If we found a valid joint to snap to... + if ( nearJoint > 0 ) { + idMat3 dummy; + bestEnemy->GetJointWorldTransform( nearJoint, gameLocal.time, muzzleDest, dummy ); + dir = muzzleDest - origin; + dir.Normalize(); + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + } + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + + } + } +} +#endif + +/* +===================== +rvVehicleWeapon::LaunchHitScan +===================== +*/ +void rvVehicleWeapon::LaunchHitScan( const idVec3& origin, const idVec3& _dir, const idVec3& jointOrigin ) { + idPlayer* player = 0; + + idVec3 dir = _dir; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + gameLocal.HitScan ( *hitScanDef, origin, dir, jointOrigin, position->GetDriver(), false, 1.0f, parent ); +} + +/* +===================== +rvVehicleWeapon::LaunchProjectile +===================== +*/ +void rvVehicleWeapon::LaunchProjectile( const idVec3& origin, const idVec3& _dir, const idVec3& pushVelocity ) { + + idPlayer *player = 0; + idEntity* ent; + idProjectile* projectile; + + idVec3 dir = _dir; + + gameLocal.SpawnEntityDef ( *projectileDef, &ent ); + if ( !ent ) { + return; + } + + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + + projectile = ( idProjectile * )ent; + projectile->Create( position->GetDriver(), origin, dir, parent ); + projectile->Launch( origin, dir, pushVelocity, 0.0f, 1.0f ); + + if ( projectile->IsType ( idGuidedProjectile::GetClassType() ) ) { + if ( spawnArgs.GetBool("guideTowardsDir") && (!targetEnt || targetJoint == INVALID_JOINT) ) { +#ifndef _XENON + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); +#else + if ( bestEnemy ) { + static_cast(projectile)->GuideTo ( origin, dir ); + } else { + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); + } +#endif + + } else if ( targetEnt ) { + static_cast(projectile)->GuideTo ( targetEnt, targetJoint, targetPos ); + } else { + static_cast(projectile)->GuideTo ( targetPos ); + } + } +} + +/* +===================== +rvVehicleWeapon::LaunchProjectiles +===================== +*/ +void rvVehicleWeapon::LaunchProjectiles() { + idVec3 jointOrigin; + idMat3 jointAxis; + idVec3 origin; + idMat3 axis; + + float spreadRad = DEG2RAD( spread ); + idVec3 dir; + float ang = 0.0f; + float spin = 0.0f; + + parent->GetJointWorldTransform ( joints[jointIndex], gameLocal.time, jointOrigin, jointAxis ); + MuzzleFlashLight ( jointOrigin + muzzleFlashOffset * jointAxis, jointAxis ); + + if( launchFromJoint ) { + origin = jointOrigin; + axis = jointAxis; + } else { + origin = position->GetEyeOrigin(); + axis = position->GetEyeAxis(); + } + + if ( !lockScanning || !targetEnt || !position->GetDriver() || !position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //don't do this continuously, so have to do it here + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + } + + parent->StartSoundShader( shaderFire, SND_CHANNEL_WEAPON, 0, false, NULL ); + + parent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_muzzleflash" ), joints[jointIndex], vec3_origin, mat3_identity ); + + gameLocal.AlertAI( parent ); + + for( int i = 0; i < count; i ++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[0] + axis[ 2 ] * ( ang * idMath::Sin( spin ) ) - axis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine ( colorBlue, origin, origin + dir * 10000.0f, 10000 ); + } + + if ( hitScanDef ) { + LaunchHitScan( origin, dir, jointOrigin ); + } else { + LaunchProjectile( origin, dir, parent->GetPhysics()->GetPushedLinearVelocity() ); + } + } + + jointIndex = (jointIndex+1) % joints.Num(); + + parent->GetPhysics()->ApplyImpulse ( 0, origin, force * axis ); + + WeaponFeedback( &spawnArgs ); +} + +/* +===================== +rvVehicleWeapon::GetLockInfo +===================== +*/ +void rvVehicleWeapon::GetLockInfo( const idVec3& eyeOrigin, const idMat3& eyeAxis ) { + if ( lastLockTime < gameLocal.GetTime() - 2000 + || (targetEnt && (!targetEnt.IsValid() || targetEnt.GetEntity()->health <= 0)) ) { + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + } + + if ( lockRange > 0.0f ) { + idVec3 end; + trace_t tr; + bool lockFound = false; + idEntity* newTargetEnt = NULL; + jointHandle_t newTargetJoint = INVALID_JOINT; + + end = eyeOrigin + eyeAxis[0] * lockRange; +// gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_BOUNDINGBOX, NULL ); + gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_RENDERMODEL, NULL ); + + if ( tr.fraction < 1.0 ) { + newTargetEnt = gameLocal.entities[ tr.c.entityNum ]; + lockFound = true; + + // Make sure the target is an actor and is alive + if ( !(newTargetEnt->IsType ( idActor::GetClassType() ) || newTargetEnt->IsType ( idProjectile::GetClassType() )) || newTargetEnt->health <= 0 ) { + newTargetEnt = NULL; + lockFound = false; + } else { + //see if there's a joint to lock onto + if ( newTargetEnt->spawnArgs.MatchPrefix ( "lockJoint" ) ) { + jointHandle_t testJointHandle; + idVec3 testJointOffset; + idStr lockJointName; + idVec3 jointOrg; + idMat3 jointAxis; + + int lockJointNum = 1; + float bestDist = 100.0f; + float dist = 0.0f; + + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",lockJointNum), NULL ); + while ( lockJointName.Length() ) { + testJointHandle = newTargetEnt->GetAnimator()->GetJointHandle( lockJointName ); + if ( testJointHandle != INVALID_JOINT ) { + newTargetEnt->GetAnimator()->GetJointTransform( testJointHandle, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = newTargetEnt->GetRenderEntity()->origin + (jointOrg*newTargetEnt->GetRenderEntity()->axis); + jointAxis = jointAxis * newTargetEnt->GetRenderEntity()->axis; + testJointOffset = newTargetEnt->spawnArgs.GetVector( va("lockJointOffset%d",lockJointNum), "0 0 0" ); + jointOrg += jointAxis*testJointOffset; + dist = (jointOrg - tr.endpos).Length(); + if ( dist < bestDist ) { + bestDist = dist; + newTargetJoint = testJointHandle; + targetPos = testJointOffset; + } + } + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",++lockJointNum), NULL ); + } + } + } + if ( spawnArgs.GetBool("guideTowardsDir") ) { + if ( newTargetJoint == INVALID_JOINT ) { + newTargetEnt = NULL; + lockFound = false; + } + } + } else if ( spawnArgs.GetBool( "guideTowardsDir" ) && !targetEnt ) { + targetPos = tr.endpos; + } + if ( lockFound ) { + targetEnt = newTargetEnt; + targetJoint = newTargetJoint; + lastLockTime = gameLocal.GetTime(); + } + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, end, 3, 10000 ); + gameRenderWorld->DebugArrow ( colorWhite, eyeOrigin, tr.endpos, 4, 10000 ); + } + } +} + +/* +===================== +rvVehicleWeapon::EjectBrass +===================== +*/ +void rvVehicleWeapon::EjectBrass ( void ) { + if ( !brassDict || gameLocal.time > brassEjectNext || g_brassTime.GetFloat() <= 0.0f || gameLocal.isMultiplayer || brassEjectJoint == INVALID_JOINT || !brassDict->GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !parent->GetJointWorldTransform( brassEjectJoint, gameLocal.time, origin, axis ) ) { + return; + } + + brassEjectNext += brassEjectDelay; + + // Spawn the client side moveable for the brass + idVec3 offset; + idMat3 playerViewAxis; + gameLocal.GetPlayerView( offset, playerViewAxis ); + rvClientMoveable* cent = NULL; + gameLocal.SpawnClientEntityDef( *brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOrigin ( origin + axis * brassEjectOffset ); + cent->SetAxis ( playerViewAxis ); + cent->SetOwner ( position->GetDriver() ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 6.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict->GetVector("linear_velocity").Random( brassDict->GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( position->GetDriver()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict->GetAngles("angular_velocity").Random( brassDict->GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/*********************************************************************** + + rvVehicleTurret + +***********************************************************************/ + +#define TURRET_MOVESOUND_FADE 200 + +CLASS_DECLARATION( rvVehiclePart, rvVehicleTurret ) +END_CLASS + +/* +===================== +rvVehicleTurret::rvVehicleTurret +===================== +*/ +rvVehicleTurret::rvVehicleTurret ( void ) { + moveTime = 0; + soundPart = -1; +} + +/* +===================== +rvVehicleTurret::Spawn +===================== +*/ +void rvVehicleTurret::Spawn ( void ) { + angles[0][PITCH] = spawnArgs.GetFloat ( "minpitch", "0" ); + angles[1][PITCH] = spawnArgs.GetFloat ( "maxpitch", "0" ); + axisMap[PITCH] = spawnArgs.GetInt ( "pitch", "-1" ); + invert[PITCH] = spawnArgs.GetBool ( "pitchinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][ROLL] = spawnArgs.GetFloat ( "minroll", "0" ); + angles[1][ROLL] = spawnArgs.GetFloat ( "maxroll", "0" ); + axisMap[ROLL] = spawnArgs.GetInt ( "roll", "-1" ); + invert[ROLL] = spawnArgs.GetBool ( "rollinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][YAW] = spawnArgs.GetFloat ( "minyaw", "0" ); + angles[1][YAW] = spawnArgs.GetFloat ( "maxyaw", "0" ); + axisMap[YAW] = spawnArgs.GetInt ( "yaw", "-1" ); + invert[YAW] = spawnArgs.GetBool ( "yawinvert", "0" ) ? -1.0f : 1.0f; + + turnRate = spawnArgs.GetFloat ( "turnrate", "360" ); + + currentAngles.Zero ( ); + + //the parent is *not* stuck on spawn. + parentStuck = false; + + + // Find the vehicle part for the turret sound + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } +} + +/* +===================== +rvVehicleTurret::Activate +===================== +*/ +void rvVehicleTurret::Activate ( bool active ) { + rvVehiclePart::Activate( active ); + + if ( !IsActive() && soundPart >= 0 ) { + static_cast(position->GetPart(soundPart))->Stop ( ); + } + +} + +/* +===================== +rvVehicleTurret::RunPostPhysics +===================== +*/ +void rvVehicleTurret::RunPostPhysics ( void ) { + int i; + idAngles inputAngles; + idAngles lastInputAngles; + idMat3 mat[3]; + idAngles oldAngles; + + if ( IsActive ( ) ) { + for ( i = 0; i < 3; i++ ) { + inputAngles[i] = SHORT2ANGLE( position->mInputCmd.angles[i] ); + lastInputAngles[i] = SHORT2ANGLE( position->mOldInputCmd.angles[i] ); + } + } else { + inputAngles.Zero ( ); + lastInputAngles.Zero ( ); + } + + oldAngles = currentAngles; + + mat[PITCH].Identity(); + mat[YAW].Identity(); + mat[ROLL].Identity(); + for ( i = 0; i < 3; i ++ ) { + if ( axisMap[i] != -1 ) { + float diff = (invert[i] * idMath::AngleDelta ( inputAngles[i], lastInputAngles[i] )); + + diff = SignZero( diff ) * idMath::ClampFloat ( 0.0f, turnRate * MS2SEC(gameLocal.GetMSec()), idMath::Fabs ( diff ) ); + if ( angles[0][i] == angles[1][i] ) { + currentAngles[axisMap[i]] = idMath::AngleNormalize360( currentAngles[axisMap[i]] + diff ); + } else { + currentAngles[axisMap[i]] = idMath::ClampFloat ( angles[0][i], angles[1][i], currentAngles[axisMap[i]] + diff ); + } + + idAngles angles; + angles.Zero(); + angles[axisMap[i]] = currentAngles[axisMap[i]]; + mat[axisMap[i]] = angles.ToMat3(); + } + } + + // Update the turret moving sound + if ( soundPart >= 0 ) { + float speed; + speed = idMath::Fabs( idMath::AngleDelta( oldAngles[YAW], currentAngles[YAW] ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[PITCH], currentAngles[PITCH] ) ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[ROLL], currentAngles[ROLL] ) ) ); + + if ( speed ) { + if ( !moveTime ) { + static_cast(position->GetPart(soundPart))->Play ( ); + } + moveTime = gameLocal.time + TURRET_MOVESOUND_FADE; + } else if ( moveTime && gameLocal.time > moveTime ) { + static_cast(position->GetPart(soundPart))->Fade ( TURRET_MOVESOUND_FADE, 0.0f, 1.0f ); + moveTime = 0; + } + + // Update the volume of the turret move sound using the current speed of the + // turret as well as how long it has been since it has last moved. + if ( moveTime ) { + float f; + f = idMath::ClampFloat ( 0.0f, 1.0f, fabs(speed) / (turnRate * MS2SEC(gameLocal.GetMSec())) ); + static_cast(position->GetPart(soundPart))->Attenuate ( f, f ); + } + } + + // Rotate the turret with the mouse + parent->GetAnimator()->SetJointAxis( joint, JOINTMOD_LOCAL, mat[YAW] * mat[PITCH] * mat[ROLL] ); + + if ( g_vehicleMode.GetInteger() != 0 && joint != INVALID_JOINT && parent->GetAnimator()->GetJointHandle( spawnArgs.GetString( "alignment_joint" ) ) == joint ) { + idMat3 turretAxis; + idVec3 temp; + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + const idMat3 & vehicleAxis = parent->GetPhysics()->GetAxis(); + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + float dotRight = vehicleAxis[1] * turretAxis[alignmentAxis]; + float dotForward = vehicleAxis[0] * turretAxis[alignmentAxis]; + float acosForward = idMath::ACos( dotForward ); + + idAngles oldAngles = currentAngles; + idMat3 oldAxis = vehicleAxis; + + if ( idMath::Fabs(1.0f - dotForward) > VECTOR_EPSILON ) { + float sinus = idMath::Cos( dotForward ); + float power = idMath::Pow( sinus, spawnArgs.GetFloat( "alignment_power", "5" ) ); + float deg = idMath::ClampFloat( 0.0f, spawnArgs.GetFloat( "alignment_delta", "2.4" ), RAD2DEG( acosForward ) ) * power; + idAngles delta( 0, deg * SignZero(dotRight), 0 ); + + //move the tank + parent->GetPhysics()->SetAxis( (delta).ToMat3() * vehicleAxis ); + currentAngles -= delta; + trace_t tr; + + //trace the tank against the world. + idBounds traceBounds; + idVec3 raiseVector( 0, 0, 80); + idVec3 reduceVector( 0, 0, -16); + idVec3 parentOrigin = parent->GetPhysics()->GetOrigin(); + idVec3 parentVecForward = parent->GetPhysics()->GetAxis()[0]; + + traceBounds.Zero(); + traceBounds = parent->GetPhysics()->GetAbsBounds(); + traceBounds.ExpandSelf( reduceVector ); + + //zero the tracebounds-- abs bounds is the right size but not the right location :) + traceBounds.TranslateSelf( traceBounds.GetCenter() * -1 ); + + //raise the bounds up so the hover tank can hover. + traceBounds.TranslateSelf( raiseVector ); + + gameLocal.TraceBounds( parent, tr, parentOrigin, parentOrigin, traceBounds, MASK_SOLID | CONTENTS_VEHICLECLIP ,parent); + + //draw the debug turning bounds if we need to. It will be redder if there's collision. + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec4 vecColor(1.0f - ( tr.fraction) , 0.25f, (0.5f * tr.fraction), 1.0f); + gameRenderWorld->DebugBounds( vecColor, traceBounds, parentOrigin, 10, true ); + } + + //roll it back if it's colliding. + if( tr.fraction < 1.0f) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + gameLocal.Warning("Turret move caused vehicle block %d distance %f", tr.c.entityNum, tr.fraction ); + } + parent->GetPhysics()->SetAxis( oldAxis ); + currentAngles = oldAngles; + + //apply a push to the parent based on the direction of the collision? + parentStuck = true; + } + } + + } +} +/* +===================== +rvVehicleTurret::RunPrePhysics +===================== +*/ +void rvVehicleTurret::RunPrePhysics ( void ) { + + //in case we're stuck, apply an impulse to the parent based on the direction the turret is facing. This will help us pull out in the right direction. + if( parentStuck ) { + + idVec3 impulseForce( 0,0,0); + idMat3 turretAxis; + idVec3 temp; + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + impulseForce += ( turretAxis[alignmentAxis] * 50 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass()); + //impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * 25 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + + // Apply the impulse to the parent entity + + parent->GetPhysics()->ApplyImpulse( 0, parent->GetPhysics()->GetOrigin(), impulseForce); + + parentStuck = false; + } + + +} + + +/* +===================== +rvVehicleTurret::Save +===================== +*/ +void rvVehicleTurret::Save ( idSaveGame* savefile ) const { + savefile->WriteBounds ( angles ); + savefile->WriteInt ( axisMap[0] ); + savefile->WriteInt ( axisMap[1] ); + savefile->WriteInt ( axisMap[2] ); + + savefile->WriteFloat ( invert[0] ); + savefile->WriteFloat ( invert[1] ); + savefile->WriteFloat ( invert[2] ); + + savefile->WriteAngles ( currentAngles ); + + savefile->WriteInt ( moveTime ); + savefile->WriteFloat ( turnRate ); + + savefile->WriteInt ( soundPart ); +} + +/* +===================== +rvVehicleTurret::Restore +===================== +*/ +void rvVehicleTurret::Restore ( idRestoreGame* savefile ) { + savefile->ReadBounds ( angles ); + savefile->ReadInt ( axisMap[0] ); + savefile->ReadInt ( axisMap[1] ); + savefile->ReadInt ( axisMap[2] ); + + savefile->ReadFloat ( invert[0] ); + savefile->ReadFloat ( invert[1] ); + savefile->ReadFloat ( invert[2] ); + + savefile->ReadAngles ( currentAngles ); + + savefile->ReadInt ( moveTime ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadInt ( soundPart ); +} + +/*********************************************************************** + + rvVehicleHoverpad + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleHoverpad ) +END_CLASS + +rvVehicleHoverpad::rvVehicleHoverpad ( void ) { + clipModel = NULL; + effectDust = NULL; + soundPart = -1; + effectDustMaterialType = NULL; +} + +rvVehicleHoverpad::~rvVehicleHoverpad ( void ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + delete clipModel; +} + +/* +================ +rvVehicleHoverpad::Spawn +================ +*/ +void rvVehicleHoverpad::Spawn ( void ) { + float size; + + spawnArgs.GetFloat ( "size", "10", size ); + spawnArgs.GetFloat ( "height", "70", height ); + spawnArgs.GetFloat ( "dampen", "10", dampen ); + spawnArgs.GetFloat ( "forceRandom", "10", forceRandom ); + spawnArgs.GetFloat ( "thrustForward", "0", thrustForward ); + spawnArgs.GetFloat ( "thrustLeft", "0", thrustLeft ); + + maxRestAngle = idMath::Cos ( DEG2RAD(spawnArgs.GetFloat ( "maxRestAngle", "20" ) ) ); + + fadeTime = SEC2MS(spawnArgs.GetFloat ( "fadetime", ".5" )); + + effectDust = NULL; + atRest = true; + + delete clipModel; + clipModel = new idClipModel ( idTraceModel ( idBounds ( spawnArgs.GetVector("mins"),spawnArgs.GetVector("maxs")) ) ); + + force = spawnArgs.GetFloat ( "force", "1066" ); + forceUpTime = SEC2MS ( spawnArgs.GetFloat ( "forceUpTime", "1" ) ); + forceDownTime = SEC2MS ( spawnArgs.GetFloat ( "forceDownTime", "1" ) ); + forceTable = declManager->FindTable( spawnArgs.GetString ( "forceTable", "linear" ), false ); + + currentForce.Init ( 0, 0, 0, 0 ); + + // Is a sound part specified? + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } + + Activate ( false ); +} + +/* +================ +rvVehicleHoverpad::Activate +================ +*/ +void rvVehicleHoverpad::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active ) { + currentForce.Init ( gameLocal.time, forceUpTime, currentForce.GetCurrentValue( gameLocal.time ), force ); + atRest = false; + } else { + currentForce.Init ( gameLocal.time, forceDownTime, currentForce.GetCurrentValue( gameLocal.time ), 0 ); + } +} + +/* +================ +rvVehicleHoverpad::RunPrePhysics + +Called before RunPhysics. Since impact velocities are altered by calls to ApplyImpulse +we need to cache them to ensure other hoverpads are not altering the data. The start +position of the hoverpad is also cached here for efficiency. +================ +*/ +void rvVehicleHoverpad::RunPrePhysics ( void ) { + impactInfo_t info; + + UpdateOrigin ( ); + + // Cache velocity at start point + parent->GetPhysics()->GetImpactInfo( 0, worldOrigin, &info ); + velocity = info.velocity; +} + +/* +================ +rvVehicleHoverpad::RunPhysics +================ +*/ +void rvVehicleHoverpad::RunPhysics ( void ) { + idVec3 end; + idMat3 axis; + trace_t tr; + idVec3 impulseForce; + idVec3 dampingForce; + float hoverForce; + float curlength; + + // Disable pad? + if ( !IsActive() && !atRest ) { + if ( parent->IsAtRest ( ) || currentForce.GetCurrentValue ( gameLocal.time ) <= 0.0f || parent->IsFlipped( ) || parent->IsStalled() ) { + if ( soundPart >= 0 ) { + float fadeVolume; + fadeVolume = idMath::dBToScale ( spawnArgs.GetFloat ( "fadevolume", "0" ) ); + static_cast(position->GetPart(soundPart))->Fade ( fadeTime, fadeVolume, spawnArgs.GetFloat ( "fadefreqshift", "0.1" ) ); + } + + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + atRest = true; + } + } + + // If the vehicle isnt activate then kill the effect on it and dont think + if ( atRest ) { + return; + } + + // Prepare for trace + axis[0] = -parent->GetPhysics()->GetAxis()[2]; + axis[1] = parent->GetPhysics()->GetAxis()[0]; + axis[2] = parent->GetPhysics()->GetAxis()[1]; + + // Always point the hover jets towards gravity + end = worldOrigin + (parent->GetPhysics()->GetGravityNormal ( ) * height); + + // Determine how far away the hoverpad is from the ground + gameLocal.Translation ( parent.GetEntity(), tr, worldOrigin, end, clipModel, axis, MASK_SOLID|CONTENTS_VEHICLECLIP, parent ); + if ( tr.fraction >= 1.0f && tr.endpos == end ) { + UpdateDustEffect ( worldOrigin, axis, 0.0f, NULL ); + return; + } + + // Determine spring properties from trace + tr.c.point = worldOrigin + (end-worldOrigin) * (tr.fraction + 0.001f); + curlength = height - (worldOrigin - tr.c.point).Length ( ); + + // Dampening + dampingForce = tr.c.point - worldOrigin; + dampingForce = ( dampen * ( (velocity * dampingForce) / (dampingForce * dampingForce) ) ) * dampingForce; + + // Random swap forces + float rnd = 0.0f; + if ( !position->mInputCmd.forwardmove && !position->mInputCmd.rightmove ) { + float angle = DEG2RAD( ( (float)( gameLocal.time % 5000 ) / 5000.0f ) * 360.f ); + if ( fl.left ) { + angle += 90; + } + if ( fl.front ) { + angle += 180; + } + rnd = forceRandom * idMath::Sin( angle ); + } + + // Determine how much force should be applied at the center of the hover spring + hoverForce = (currentForce.GetCurrentValue(gameLocal.time) + rnd) * parent->GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()); + + // If the slope under the hoverpad is < 30 degress then use the gravity vector to allow the hovertank to + // stop on slopes, otherwise use the vehicles axis which will cause it to slide down steep slopes + float f = (-parent->GetPhysics()->GetGravityNormal ( ) * tr.c.normal); + if ( f < maxRestAngle || (thrustForward && position->mInputCmd.forwardmove != 0 ) ) { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -axis[0] - dampingForce; + } else { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -parent->GetPhysics()->GetGravityNormal ( ) - dampingForce; + } + + // No thrust if movement is disabled + if ( parent->IsMovementEnabled ( ) ) { + idMat3 axis; + idVec3 offset; + jointHandle_t axisJoint = joint; + if ( axisJoint == INVALID_JOINT ) { + axisJoint = parent->GetAnimator()->GetJointHandle( "gun_pivot" ); + } + parent->GetJointWorldTransform( axisJoint, gameLocal.time, offset, axis ); + + // Add forward/backward thrust + if ( thrustForward ) { + if ( g_vehicleMode.GetInteger() == 2 ) { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * axis[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } + } + + // Add right/left thrust. The front pads will add force to the right if the right button is pressed and the rear will do the opposite. If moving backward the directions are flipped again + if ( thrustLeft ) { + if ( !parent->IsStrafing() && g_vehicleMode.GetInteger() != 0 ) { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * -axis[2] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * (position->mInputCmd.forwardmove<0?-1:1) * parent->GetPhysics()->GetAxis()[1] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass () * (fl.front ? 1 : -1); + } + } + } + + // Apply the impulse to the parent entity + parent->GetPhysics()->ApplyImpulse( 0, worldOrigin, impulseForce); + + // Update the position and intensity of the dust effect + UpdateDustEffect ( tr.c.point, tr.c.normal.ToMat3( ), (curlength / height), tr.c.materialType ); + + // Debug information + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec3 offset; + offset = worldOrigin - parent->GetPhysics()->GetOrigin(); + offset *= parent->GetPhysics()->GetAxis().Transpose(); + gameDebug.AppendList ( "hover", va("%c%c %d\t%0.1f\t%0.1f\t%d\t", (offset[0]>0?'F':'B'),(offset[1]<0?'R':'L'), (int)impulseForce.Length(), curlength, dampingForce.Length(), (int)(velocity*axis[0] ) ) ); + } + + // Draw debug information + if ( g_debugVehicle.GetInteger() == 2 ) { + collisionModelManager->DrawModel ( clipModel->GetCollisionModel(), tr.c.point, axis, vec3_origin, mat3_identity, 0.0f ); + gameRenderWorld->DebugCircle ( colorGreen, tr.c.point, axis[0], 5, 10 ); + gameRenderWorld->DebugLine ( colorMagenta, worldOrigin, worldOrigin + idVec3(0,0,-height)); + gameRenderWorld->DebugLine ( colorWhite, worldOrigin, end ); + gameRenderWorld->DebugCircle ( colorBlue, worldOrigin, axis[0], 5, 10 ); + gameRenderWorld->DebugCircle ( colorBlue, end, axis[0], 5, 10 ); + + if ( thrustForward && position->mInputCmd.forwardmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[1] * 50.0f * (fl.front ? 1 : -1) * ( position->mInputCmd.rightmove / 127.0f ), 10); + } + + if ( thrustLeft && position->mInputCmd.rightmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[0] * 50.0f * ( position->mInputCmd.forwardmove / 127.0f ), 10); + } + } +} + +/* +================ +rvVehicleHoverpad::UpdateDustEffect +================ +*/ +void rvVehicleHoverpad::UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ) { + if ( !effectDust || (mtype && effectDustMaterialType != mtype) ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust", mtype ), origin, axis, true ); + effectDustMaterialType = mtype; + } + + if ( effectDust ) { + effectDust->Attenuate ( attenuation ); + effectDust->SetOrigin ( origin ); + effectDust->SetAxis ( axis ); + } + + // If there is a sound playing update it as well + if ( soundPart >= 0 ) { + if ( static_cast(position->GetPart(soundPart))->IsPlaying ( ) ) { + static_cast(position->GetPart(soundPart))->Attenuate ( attenuation, attenuation ); + } else { + static_cast(position->GetPart(soundPart))->Play ( ); + } + } +} + +/* +===================== +rvVehicleHoverpad::Save +===================== +*/ +void rvVehicleHoverpad::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( height ); + savefile->WriteFloat ( dampen ); + savefile->WriteBool ( atRest ); + + savefile->WriteBounds ( clipModel->GetBounds ( ) ); + + savefile->WriteInterpolate ( currentForce ); + savefile->WriteFloat ( force ); + savefile->WriteTable ( forceTable ); + savefile->WriteFloat ( forceRandom ); + + savefile->WriteFloat ( fadeTime ); + savefile->WriteVec3 ( velocity ); + + savefile->WriteFloat ( thrustForward ); + savefile->WriteFloat ( thrustLeft ); + + savefile->WriteFloat ( maxRestAngle ); + + savefile->WriteInt ( soundPart ); + + savefile->WriteInt ( forceUpTime ); + savefile->WriteInt ( forceDownTime ); + + effectDust.Save ( savefile ); + savefile->WriteMaterialType ( effectDustMaterialType ); +} + +/* +===================== +rvVehicleHoverpad::Restore +===================== +*/ +void rvVehicleHoverpad::Restore ( idRestoreGame* savefile ) { + idBounds bounds; + + savefile->ReadFloat ( height ); + savefile->ReadFloat ( dampen ); + savefile->ReadBool ( atRest ); + + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadInterpolate ( currentForce ); + savefile->ReadFloat ( force ); + savefile->ReadTable ( forceTable ); + savefile->ReadFloat ( forceRandom ); + + savefile->ReadFloat ( fadeTime ); + savefile->ReadVec3 ( velocity ); + + savefile->ReadFloat ( thrustForward ); + savefile->ReadFloat ( thrustLeft ); + + savefile->ReadFloat ( maxRestAngle ); + + savefile->ReadInt ( soundPart ); + + savefile->ReadInt ( forceUpTime ); + savefile->ReadInt ( forceDownTime ); + + effectDust.Restore ( savefile ); + savefile->ReadMaterialType ( effectDustMaterialType ); +} + +/*********************************************************************** + + rvVehicleThruster + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleThruster ) +END_CLASS + +rvVehicleThruster::rvVehicleThruster ( void ) { +} + +rvVehicleThruster::~rvVehicleThruster ( void ) { +} + +/* +===================== +rvVehicleThruster::Save +===================== +*/ +void rvVehicleThruster::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( force ); + savefile->WriteInt ( forceAxis ); + savefile->WriteInt ( key ); +} + +/* +===================== +rvVehicleThruster::Restore +===================== +*/ +void rvVehicleThruster::Restore ( idRestoreGame* savefile ) { + savefile->ReadFloat ( force ); + savefile->ReadInt ( forceAxis ); + savefile->ReadInt ( (int&)key ); +} + +/* +================ +rvVehicleThruster::Spawn +================ +*/ +void rvVehicleThruster::Spawn ( void ) { + idStr keyName; + + force = spawnArgs.GetFloat ( "force", "0" ); + forceAxis = spawnArgs.GetInt ( "forceAxis", "0" ); + + // Determine which key controls the thruster + spawnArgs.GetString ( "key", "", keyName ); + if ( !keyName.Icmp ( "forward" ) ) { + key = KEY_FORWARD; + } else if ( !keyName.Icmp ( "right" ) ) { + key = KEY_RIGHT; + } else if ( !keyName.Icmp ( "up" ) ) { + key = KEY_UP; + } +} + +/* +================ +rvVehicleThruster::RunPhysics +================ +*/ +void rvVehicleThruster::RunPhysics ( void ) { + float mult; + + if ( !IsActive ( ) ) { + return; + } + + // Determine the force multiplier from the key being pressed + mult = 0.0f; + switch ( key ) { + case KEY_FORWARD: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.forwardmove ); + break; + + case KEY_RIGHT: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.rightmove ); + break; + + case KEY_UP: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.upmove ); + break; + } + + // No multiplier, no move + if ( mult == 0.0f ) { + return; + } + + UpdateOrigin ( ); + + // Apply the force + parent->GetPhysics()->ApplyImpulse ( 0, worldOrigin, worldAxis[forceAxis] * force * mult ); + + // Debug Information + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugBounds ( colorCyan, idBounds(idVec3(-4,-4,-4), idVec3(4,4,4) ), worldOrigin ); + gameRenderWorld->DebugArrow ( colorCyan, worldOrigin, worldOrigin + worldAxis[forceAxis] * 100.0f * mult * (force < 0 ? -1 : 1), 10 ); + } +} + +/*********************************************************************** + + rvVehicleUserAnimated + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleUserAnimated ) + EVENT( EV_PostSpawn, rvVehicleUserAnimated::Event_PostSpawn ) +END_CLASS + +rvVehicleUserAnimated::rvVehicleUserAnimated ( void ) { +} + +/* +===================== +rvVehicleUserAnimated::Save +===================== +*/ +void rvVehicleUserAnimated::Save ( idSaveGame* savefile ) const { +} + +/* +===================== +rvVehicleUserAnimated::Restore +===================== +*/ +void rvVehicleUserAnimated::Restore ( idRestoreGame* savefile ) { + Event_PostSpawn(); +} + +/* +================ +rvVehicleUserAnimated::Spawn +================ +*/ +void rvVehicleUserAnimated::Spawn ( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleUserAnimated::Event_PostSpawn +================ +*/ +void rvVehicleUserAnimated::Event_PostSpawn ( void ) { + InitAnim( "forward", anims[ VUAA_Forward ] ); + InitAnim( "strafe", anims[ VUAA_Strafe ] ); + InitAnim( "crouch", anims[ VUAA_Crouch ] ); + InitAnim( "attack", anims[ VUAA_Attack ] ); + + InitFunc( "forward", funcs[ VUAF_Forward ] ); + InitFunc( "strafe", funcs[ VUAF_Strafe ] ); + InitFunc( "crouch", funcs[ VUAF_Crouch ] ); + InitFunc( "attack", funcs[ VUAF_Attack ] ); +} + + +/* +================ +rvVehicleUserAnimated::RunPrePhysics +================ +*/ +void rvVehicleUserAnimated::RunPrePhysics ( void ) { + if ( !IsActive ( ) ) { + return; + } + + rvVehicle *vehicle = position->GetParent(); + const rvVehiclePosition *position = vehicle->GetPosition( 0 ); + const usercmd_t & input = position->mInputCmd; + + if ( position->IsOccupied() ) { + LateralMove( input.forwardmove, VUAA_Forward, VUAF_Forward ); + LateralMove( input.rightmove, VUAA_Strafe, VUAF_Strafe ); + LateralMove( input.upmove, VUAA_Crouch, VUAF_Crouch ); + + // cheat and use LateralMove for the attack + LateralMove( input.buttons & BUTTON_ATTACK, VUAA_Attack, VUAF_Attack ); + } +} + +/* +================ +rvVehicleUserAnimated::InitAnim +================ +*/ +void rvVehicleUserAnimated::InitAnim( const char * action, rvUserAnimatedAnim_t & anim ) { + idStr prefix( action ); + rvVehicle * vehicle = position->GetParent(); + + anim.index = vehicle->GetAnimator()->GetAnim( spawnArgs.GetString( prefix + "_anim" ) ); + anim.rate = spawnArgs.GetFloat( prefix + "_rate", "1" ); + anim.frame = spawnArgs.GetInt( prefix + "_frame" ) * anim.rate; + anim.channel = spawnArgs.GetInt( prefix + "_channel" ); + anim.loop = spawnArgs.GetBool( prefix + "_loop" ); + anim.length = vehicle->GetAnimator()->NumFrames( anim.index ); + + // NOTE: I may want to add the suffix "_play" in the InitAnim() function + // this would specify that the animation should be played rather than stepped through + // which could be useful especially for the attack command +} + +/* +================ +rvVehicleUserAnimated::InitFunc +================ +*/ +void rvVehicleUserAnimated::InitFunc( const char * action, rvScriptFuncUtility & func ) { + idStr temp = idStr( action ) + "_script"; + + if ( spawnArgs.GetString( temp, NULL, temp ) ) { + func.Init( temp ); + } +} + +/* +================ +rvVehicleUserAnimated::SetFrame +================ +*/ +void rvVehicleUserAnimated::SetFrame( const rvUserAnimatedAnim_t & anim ) { + float lerp = anim.frame - int( anim.frame ); + frameBlend_t frameBlend = { 0, anim.frame, anim.frame + 1.0f, 1.0f - lerp, lerp }; + position->GetParent()->GetAnimator()->SetFrame( anim.channel, anim.index, frameBlend ); +} + +/* +================ +rvVehicleUserAnimated::LateralMove +================ +*/ +void rvVehicleUserAnimated::LateralMove( signed char input, int anim, int func ) { + if ( !input ) { + return; + } + + rvUserAnimatedAnim_t & theAnim = anims[ anim ]; + + if ( theAnim.index ) { + + float sign = ( ( input > 0 ) ? 1.0f : -1.0f ); + theAnim.frame += theAnim.rate * sign; + + if ( theAnim.frame < 1 ) { + theAnim.frame = ( ( theAnim.loop ) ? theAnim.length : 1 ); + } else if ( theAnim.frame > theAnim.length ) { + theAnim.frame = ( ( theAnim.loop ) ? 1 : theAnim.length ); + } + + SetFrame( theAnim ); + } + + if ( funcs[ func ].Valid( ) ) + funcs[ func ].CallFunc( &spawnArgs ); +} diff --git a/src/game/vehicle/VehicleParts.h b/src/game/vehicle/VehicleParts.h new file mode 100644 index 0000000..f355838 --- /dev/null +++ b/src/game/vehicle/VehicleParts.h @@ -0,0 +1,468 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleParts.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEPARTS_H__ +#define __GAME_VEHICLEPARTS_H__ + +class idWeapon; +class rvVehicle; +class rvVehiclePosition; +class rvEffect; + +//---------------------------------------------------------------- +// Base Part +//---------------------------------------------------------------- + +class rvVehiclePart : public idClass +{ +public: + + ABSTRACT_PROTOTYPE( rvVehiclePart ); + + virtual ~rvVehiclePart( void ) { } + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual bool Init ( rvVehiclePosition* position, const idDict& args, s_channelType soundChannel ); + + virtual void RunPrePhysics ( void ) { } + virtual void RunPhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + bool IsLeft ( void ) const; + bool IsFront ( void ) const; + bool IsUsingCenterMass ( void ) const; + bool IsActive ( void ) const; + + virtual void Activate ( bool activate ) { fl.active = activate; } + + virtual void Impulse ( int impulse ) { } + + virtual rvVehiclePosition* GetPosition ( void ) const; + const idVec3& GetOrigin ( void ) const; + const idMat3& GetAxis ( void ) const; + +protected: + + void UpdateOrigin ( void ); + + typedef struct partFlags_s { + bool active : 1; // Is part active + bool left : 1; // Is part on left side of vehicle + bool front : 1; // Is part on right side of vehicle + bool useCenterMass : 1; // Use center of mass for origin + bool useViewAxis : 1; // Uses parent->viewAxis instead of the physics axis (::UpdateOrigin()) + } partFlags_t; + + partFlags_t fl; + + idDict spawnArgs; + idEntityPtr parent; + jointHandle_t joint; + s_channelType soundChannel; + rvVehiclePosition* position; + + idVec3 worldOrigin; + idMat3 worldAxis; + idVec3 localOffset; +}; + +ID_INLINE rvVehiclePosition* rvVehiclePart::GetPosition ( void ) const { return position; } +ID_INLINE const idVec3& rvVehiclePart::GetOrigin ( void ) const { return worldOrigin; } +ID_INLINE const idMat3& rvVehiclePart::GetAxis ( void ) const { return worldAxis; } + +ID_INLINE bool rvVehiclePart::IsLeft ( void ) const { return fl.left; } +ID_INLINE bool rvVehiclePart::IsFront ( void ) const { return fl.front; } +ID_INLINE bool rvVehiclePart::IsUsingCenterMass ( void ) const { return fl.useCenterMass; } +ID_INLINE bool rvVehiclePart::IsActive ( void ) const { return fl.active; } + +//---------------------------------------------------------------- +// Sound +//---------------------------------------------------------------- + +class rvVehicleSound : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleSound ); + + rvVehicleSound ( void ); + ~rvVehicleSound ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + bool IsPlaying ( void ) const; + + void Play ( void ); + void Stop ( void ); + void Update ( bool force = false ); + + void Fade ( int time, float toVolume, float toFreq ); + void Attenuate ( float volumeAttenuate, float freqAttenuate ); + + void SetAutoActivate ( bool activate ); + +protected: + + idVec2 volume; + idVec2 freqShift; + idStr soundName; + refSound_t refSound; + idInterpolate currentVolume; + idInterpolate currentFreqShift; + bool fade; + bool autoActivate; +}; + +ID_INLINE bool rvVehicleSound::IsPlaying ( void ) const { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return( false ); +} + +ID_INLINE void rvVehicleSound::SetAutoActivate ( bool activate ){ + autoActivate = activate; +} + +//---------------------------------------------------------------- +// Hoverpad +//---------------------------------------------------------------- + +class rvVehicleHoverpad : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleHoverpad ); + + rvVehicleHoverpad ( void ); + ~rvVehicleHoverpad ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + void UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ); + + float height; + float dampen; + bool atRest; + + idClipModel* clipModel; + + idInterpolate currentForce; + float force; + const idDeclTable* forceTable; + float forceRandom; + + float fadeTime; + idVec3 velocity; + + float thrustForward; + float thrustLeft; + + float maxRestAngle; + + int soundPart; + + int forceUpTime; + int forceDownTime; + + // Dust effects + rvClientEntityPtr effectDust; + const rvDeclMatType* effectDustMaterialType; +}; + +//---------------------------------------------------------------- +// Thruster +//---------------------------------------------------------------- + +class rvVehicleThruster : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleThruster ); + + rvVehicleThruster ( void ); + ~rvVehicleThruster ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPhysics ( void ); + +protected: + + enum EThrusterKey + { + KEY_FORWARD, + KEY_RIGHT, + KEY_UP + }; + + float force; + int forceAxis; + EThrusterKey key; +}; + +//---------------------------------------------------------------- +// Light +//---------------------------------------------------------------- + +class rvVehicleLight : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleLight ); + + rvVehicleLight ( void ); + ~rvVehicleLight ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + virtual void Impulse ( int impulse ); + +protected: + + void TurnOff ( void ); + void TurnOn ( void ); + + void UpdateLightDef ( void ); + + renderLight_t renderLight; + int lightHandle; + bool lightOn; + + idStr soundOn; + idStr soundOff; +}; + +//---------------------------------------------------------------- +// Weapon +//---------------------------------------------------------------- +class rvVehicleWeapon : public rvVehiclePart { +public: + CLASS_PROTOTYPE( rvVehicleWeapon ); + + rvVehicleWeapon ( void ); + ~rvVehicleWeapon ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool activate ); + + int GetCurrentAmmo ( void ) const; + float GetCurrentCharge ( void ) const; + + void UpdateCursorGUI ( idUserInterface* gui ) const; + + bool Fire (); + + int GetZoomFov ( void ) const; + idUserInterface * GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + bool CanZoom ( void ) const; + + void StopTargetEffect ( void ); + +protected: +#ifdef _XENON + idActor* bestEnemy; + void AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ); +#endif + void LaunchHitScan ( const idVec3& origin, const idVec3& dir, const idVec3& jointOrigin ); + void LaunchProjectile ( const idVec3& origin, const idVec3& dir, const idVec3& pushVelocity ); + void LaunchProjectiles (); + + void UpdateLock ( void ); + void GetLockInfo ( const idVec3& eyeOrigin, const idMat3& eyeAxis ); + + void EjectBrass ( void ); + + void MuzzleFlashLight ( const idVec3& origin, const idMat3& axis ); + void WeaponFeedback ( const idDict* dict ); + + int nextFireTime; + int fireDelay; + int count; + const idDict* projectileDef; + const idDict* hitScanDef; + float spread; + bool launchFromJoint; + bool lockScanning; + int lastLockTime; + float lockRange; + + idEntityPtr targetEnt; + jointHandle_t targetJoint; + idVec3 targetPos; + rvClientEntityPtr targetEffect; + + idList joints; + int jointIndex; + + idVec3 force; + + int ammoPerCharge; + int chargeTime; + int currentAmmo; + idInterpolate currentCharge; + + renderLight_t muzzleFlash; + int muzzleFlashHandle; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashOffset; + + int animNum; + int animChannel; + + const idSoundShader* shaderFire; + const idSoundShader* shaderReload; + + const idDict* brassDict; + jointHandle_t brassEjectJoint; + idVec3 brassEjectOffset; + int brassEjectNext; + int brassEjectDelay; + + int zoomFov; + idUserInterface * zoomGui; + float zoomTime; +}; + +ID_INLINE int rvVehicleWeapon::GetCurrentAmmo ( void ) const { return currentAmmo; } +ID_INLINE float rvVehicleWeapon::GetCurrentCharge ( void ) const { return currentCharge.IsDone(gameLocal.time) ? 1.0f : currentCharge.GetCurrentValue(gameLocal.time); } +ID_INLINE int rvVehicleWeapon::GetZoomFov ( void ) const { return zoomFov; } +ID_INLINE idUserInterface* rvVehicleWeapon::GetZoomGui ( void ) const { return zoomGui; } +ID_INLINE float rvVehicleWeapon::GetZoomTime ( void ) const { return zoomTime; } +ID_INLINE bool rvVehicleWeapon::CanZoom ( void ) const { return zoomFov != -1; } + + +//---------------------------------------------------------------- +// Turret +//---------------------------------------------------------------- + +class rvVehicleTurret : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleTurret ); + + rvVehicleTurret ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + idBounds angles; + int axisMap[3]; + + float invert[3]; + + idAngles currentAngles; + + int moveTime; + float turnRate; + + int soundPart; + + bool parentStuck; +}; + +//---------------------------------------------------------------- +// Animated Vehicle Part +//---------------------------------------------------------------- + +class rvVehicleUserAnimated : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleUserAnimated ); + + rvVehicleUserAnimated ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + + void Event_PostSpawn ( void ); + +protected: + + typedef struct rvUserAnimatedAnim_s { + int index; + float frame; + short length; + short channel; + float rate; + bool loop; + } rvUserAnimatedAnim_t; + + enum { VUAA_Forward, VUAA_Strafe, VUAA_Crouch, VUAA_Attack, VUAA_Count }; + enum { VUAF_Forward, VUAF_Strafe, VUAF_Crouch, VUAF_Attack, VUAF_Count }; + + rvUserAnimatedAnim_t anims[ VUAA_Count ]; + rvScriptFuncUtility funcs[ VUAF_Count ]; + + void InitAnim( const char * action, rvUserAnimatedAnim_t & anim ); + void InitFunc( const char * action, rvScriptFuncUtility & func ); + void SetFrame( const rvUserAnimatedAnim_t & anim ); + void LateralMove( signed char input, int anim, int func ); +}; + +typedef idList rvVehiclePartList_t; + +#endif // __GAME_VEHICLEPARTS_H__ diff --git a/src/game/vehicle/VehiclePosition.cpp b/src/game/vehicle/VehiclePosition.cpp new file mode 100644 index 0000000..451ae76 --- /dev/null +++ b/src/game/vehicle/VehiclePosition.cpp @@ -0,0 +1,869 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "Vehicle.h" + +/* +================ +rvVehiclePosition::rvVehiclePosition +================ +*/ +rvVehiclePosition::rvVehiclePosition ( void ) { + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + + memset ( &fl, 0, sizeof(fl) ); + + mCurrentWeapon = -1; + mSoundPart = -1; + + mDriver = NULL; + mParent = NULL; + + mEyeOrigin.Zero(); + mEyeAxis.Identity(); + + mEyeOffset.Zero(); + mEyeJoint = INVALID_JOINT; + + mDriverOffset.Zero ( ); + mDriverJoint = INVALID_JOINT; +} + +/* +================ +rvVehiclePosition::~rvVehiclePosition +================ +*/ +rvVehiclePosition::~rvVehiclePosition ( void ) { + mParts.DeleteContents ( true ); + mWeapons.DeleteContents ( true ); +} + +void InitIntArray( const idVec3& vec, int array[] ) { + int ix; + for( ix = 0; ix < vec.GetDimension(); ++ix ) { + array[ix] = (int)vec[ix]; + } +} + +/* +================ +rvVehiclePosition::Init +================ +*/ +void rvVehiclePosition::Init ( rvVehicle* parent, const idDict& args ) { + idVec3 garbage; + + mParent = parent; + + mEyeJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "eyeJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mEyeJoint, gameLocal.GetTime(), garbage, mEyeJointTransform) ) { + mEyeJointTransform.Identity(); + //} + //mEyeJointTransform.TransposeSelf(); + mEyeOffset = args.GetVector ( "eyeOffset", "0 0 0" ); + mDeltaEyeAxisScale = args.GetAngles( "deltaEyeAxisScale", "1 1 0" ); + mDeltaEyeAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("eyeJointAxisMap", "0 1 2"), mEyeJointAxisMap ); + InitIntArray( args.GetVector("eyeJointDirMap", "1 1 1"), mEyeJointDirMap ); + + mAxisOffset = args.GetAngles( "angles_offset" ).ToMat3(); + + mDriverJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "driverJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mDriverJoint, gameLocal.GetTime(), garbage, mDriverJointTransform) ) { + mDriverJointTransform.Identity(); + //} + //mDriverJointTransform.TransposeSelf( ); + mDriverOffset = args.GetVector ( "driverOffset", "0 0 0" ); + mDeltaDriverAxisScale = args.GetAngles( "deltaDriverAxisScale", "1 1 0" ); + mDeltaDriverAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("driverJointAxisMap", "0 1 2"), mDriverJointAxisMap ); + InitIntArray( args.GetVector("driverJointDirMap", "1 1 1"), mDriverJointDirMap ); + + mExitPosOffset = args.GetVector( "exitPosOffset" ); + mExitAxisOffset = args.GetAngles( "exitAxisOffset" ).ToMat3(); + + mDriverAnim = args.GetString ( "driverAnim", "driver" ); + + fl.driverVisible = args.GetBool ( "driverVisible", "0" ); + fl.engine = args.GetBool ( "engine", "0" ); + fl.depthHack = args.GetBool ( "depthHack", "1" ); + fl.bindDriver = args.GetBool ( "bindDriver", "1" ); + + args.GetString ( "internalSurface", "", mInternalSurface ); + + SetParts ( args ); + + mSoundMaxSpeed = args.GetFloat ( "maxsoundspeed", "0" ); + + // Looping sound when occupied? + if ( *args.GetString ( "snd_loop", "" ) ) { + mSoundPart = AddPart ( rvVehicleSound::GetClassType(), args ); + static_cast(mParts[mSoundPart])->SetAutoActivate ( false ); + } + + SelectWeapon ( 0 ); + + UpdateInternalView ( true ); +} + +/* +================ +rvVehiclePosition::SetParts +================ +*/ +void rvVehiclePosition::SetParts ( const idDict& args ) { + const idKeyValue* kv; + + // Spawn all parts + kv = args.MatchPrefix( "def_part", NULL ); + while ( kv ) { + const idDict* dict; + idTypeInfo* typeInfo; + + // Skip empty strings + if ( !kv->GetValue().Length() ) { + kv = args.MatchPrefix( "def_part", kv ); + continue; + } + + // Get the dictionary for the part + dict = gameLocal.FindEntityDefDict ( kv->GetValue() ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Determine the part type + typeInfo = idClass::GetClass ( dict->GetString ( "spawnclass" ) ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) { + gameLocal.Error ( "Class '%s' is not a vehicle part", dict->GetString ( "spawnclass" ) ); + } + + // Add the new part + AddPart ( *typeInfo, *dict ); + + kv = args.MatchPrefix( "def_part", kv ); + } +} + +/* +================ +rvVehiclePosition::AddPart +================ +*/ +int rvVehiclePosition::AddPart ( const idTypeInfo &classdef, const idDict& args ) { + rvVehiclePart* part; + int soundChannel; + + // Get a sound channel + soundChannel = SND_CHANNEL_CUSTOM; + soundChannel += mParts.Num(); + + // Allocate the new part + part = static_cast(classdef.CreateInstance ( )); + part->Init ( this, args, (s_channelType)soundChannel ); + part->CallSpawn ( ); + + // Weapons go into their own list since only one can be active + // and any given point in time. + if ( part->IsType ( rvVehicleWeapon::GetClassType() ) ) { + return mWeapons.Append ( part ); + } + + return mParts.Append ( part ); +} + +/* +================ +rvVehiclePosition::SetDriver +================ +*/ +bool rvVehiclePosition::SetDriver ( idActor* driver ) { + if ( mDriver == driver ) { + return false; + } + + fl.inputValid = false; + + if ( driver ) { + mDriver = driver; + + // Keep the driver visible if the position is exposed + if ( fl.driverVisible ) { + mDriver->Show ( ); + } else { + mDriver->Hide ( ); + + // Dont let the player take damage when inside the if not visible + mDriver->fl.takedamage = false; + } + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Play ( ); + } + + // Bind the driver to a joint or to the vehicles origin? + if( fl.bindDriver ) { + if ( INVALID_JOINT != mDriverJoint ) { + mDriver->GetPhysics()->SetAxis ( mParent->GetAxis( ) );// Not sure if this is needed + mDriver->BindToJoint ( mParent, mDriverJoint, true ); + mDriver->GetPhysics()->SetOrigin ( vec3_origin ); + } else { + mDriver->Bind ( mParent, true ); + mDriver->GetPhysics()->SetOrigin( mDriverOffset ); + } + } else {// If not bound put the vehicle first in the active list + mParent->activeNode.Remove(); + mParent->activeNode.AddToFront( gameLocal.activeEntities ); + } + + // Play a certain animation on the driver + if ( mDriverAnim.Length() ) { + mDriver->GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, mDriver->GetAnimator()->GetAnim( mDriverAnim ), gameLocal.time, 0 ); + } + } else { + if ( !fl.driverVisible ) { + mDriver->Show ( ); + + // Take damage again + mDriver->fl.takedamage = true; + } + + // Driver is no longer bound to the vehicle + mDriver->Unbind(); + + mDriver = driver; + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Stop ( ); + } + + // Clear out the input commands so the guns dont keep firing and what not when + // the player gets out + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + } + + return true; +} + +/* +================ +rvVehiclePosition::GetAxis +================ +*/ +idMat3 rvVehiclePosition::GetAxis() const { + return mAxisOffset * GetParent()->GetPhysics()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetOrigin +================ +*/ +idVec3 rvVehiclePosition::GetOrigin( const idVec3& offset ) const { + return GetParent()->GetPhysics()->GetOrigin() + (mDriverOffset + offset) * GetAxis(); +} + +/* +================ +rvVehiclePosition::ActivateParts +================ +*/ +void rvVehiclePosition::ActivateParts ( bool active ) { + int i; + + // Activate or deactive the parts based on whether there is a driver + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + mParts[i]->Activate ( active ); + } + + for ( i = mWeapons.Num() - 1; i >= 0; i -- ) { + mWeapons[i]->Activate ( active ); + } +} + +/* +================ +rvVehiclePosition::EjectDriver +================ +*/ +bool rvVehiclePosition::EjectDriver ( bool force ) { + if ( !IsOccupied ( ) ) { + return false; + } + + // Physically eject the actor from the position + if ( !force && mParent->IsLocked ( ) ) { + mParent->IssueLockedWarning ( ); + return false; + } + + // Remove the driver and if successful disable all parts for + // this position + if ( SetDriver ( NULL ) ) { + ActivateParts ( false ); + return true; + } + + return false; +} + +/* +================ +rvVehiclePosition::SetInput +================ +*/ +void rvVehiclePosition::SetInput ( const usercmd_t& cmd, const idAngles& newAngles ) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE ) ) { + if ( mDriver == gameLocal.GetLocalPlayer ( ) ) { + gameDebug.SetFocusEntity ( mParent ); + } + } + + if ( fl.inputValid || !mDriver ) { + mOldInputCmd = mInputCmd; + mOldInputAngles = mInputAngles; + } else { + mOldInputCmd = cmd; + mInputCmd = cmd; + mInputAngles = newAngles; + + // We have valid input now and there is a driver so activate all of the parts + if ( !fl.inputValid && mDriver ) { + ActivateParts ( true ); + } + } + + fl.inputValid = true; + mInputCmd = cmd; + mInputAngles = newAngles; +} + +/* +================ +rvVehiclePosition::GetInput +================ +*/ +void rvVehiclePosition::GetInput( usercmd_t& cmd, idAngles& newAngles ) const { + cmd = mInputCmd; + newAngles = mInputAngles; +} + +/* +================ +rvVehiclePosition::UpdateHUD +================ +*/ +void rvVehiclePosition::UpdateHUD ( idUserInterface* gui ) { + + // HACK: twhitaker: this is ugly but since the GEV and the Walker now have a unified HUD, it is a necessary evil + int guiWeaponID; + + if ( !stricmp( mParent->spawnArgs.GetString( "classname" ), "vehicle_walker" ) ) { + guiWeaponID = mCurrentWeapon + 2; + } else { + guiWeaponID = mCurrentWeapon; + } + + gui->SetStateInt ( "vehicle_weapon", guiWeaponID ); + // HACK: twhitaker end + + gui->SetStateInt ( "vehicle_weaponcount", mWeapons.Num() ); + if ( mCurrentWeapon >= 0 && mCurrentWeapon < mWeapons.Num() ) { + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + gui->SetStateFloat ( "vehicle_weaponcharge", weapon->GetCurrentCharge() ); + gui->SetStateInt ( "vehicle_weaponammo", weapon->GetCurrentAmmo() ); + } + + // Calculate the rotation of the view in relation to the vehicle itself + gui->SetStateFloat ( "vehicle_rotate", -idMath::AngleDelta ( mEyeAxis.ToAngles()[YAW], mParent->GetAxis ( ).ToAngles()[YAW] ) ); + + if( GetParent() ) { + GetParent()->UpdateHUD( GetDriver(), gui ); + } +} + +/* +================ +rvVehiclePosition::UpdateCursorGUI +================ +*/ +void rvVehiclePosition::UpdateCursorGUI ( idUserInterface* gui ) { + if ( mCurrentWeapon < 0 || mCurrentWeapon >= mWeapons.Num() ) { + return; + } + + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + assert ( weapon ); + weapon->UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehiclePosition::UpdateInternalView +================ +*/ +void rvVehiclePosition::UpdateInternalView ( bool force ) { + bool internal = false; + + // If the local player is driving and not in third person then use internal + internal = ( mDriver == gameLocal.GetLocalPlayer() && !pm_thirdPerson.GetInteger() ); + + // force the update? + if ( !force && internal == fl.internalView ) { + return; + } + + fl.internalView = internal; + + if ( fl.depthHack ) { + mParent->GetRenderEntity()->weaponDepthHackInViewID = (IsOccupied() && fl.internalView) ? GetDriver()->entityNumber + 1 : 0; + } + + // Show and hide the internal surface + if ( mInternalSurface.Length() ) { + mParent->ProcessEvent ( fl.internalView ? &EV_ShowSurface : &EV_HideSurface, mInternalSurface.c_str() ); + } +} + +/* +================ +rvVehiclePosition::RunPrePhysics +================ +*/ +void rvVehiclePosition::RunPrePhysics ( void ) { + int i; + + UpdateInternalView ( ); + + if ( mParent->IsStalled() ) { + if ( !fl.stalled ) { + // we just stalled + fl.stalled = true; + ActivateParts( false ); + } + return; + } else if ( IsOccupied() && !mParent->IsStalled() && fl.stalled ) { + // we just restarted + fl.stalled = false; + ActivateParts( true ); + } + + // Give the parts a chance to set up for physics + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + mParts[i]->RunPrePhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPrePhysics ( ); + } + + // Run physics for each part + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + + mParts[i]->RunPhysics ( ); + } + + // Attenuate the attached sound if speed based + if ( mSoundPart != -1 && mSoundMaxSpeed > 0.0f ) { + float f; + float speed; + idVec3 vel; + + // Only interested in forward or backward velocity + vel = mParent->GetPhysics()->GetLinearVelocity ( ) * mParent->GetPhysics()->GetAxis ( ); + speed = idMath::ClampFloat ( 0.0f, mSoundMaxSpeed, vel.Normalize ( ) ); + f = speed / mSoundMaxSpeed; + + static_cast(mParts[mSoundPart])->Attenuate ( f, f ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPhysics ( ); + } +} + +/* +================ +rvVehiclePosition::RunPostPhysics +================ +*/ +void rvVehiclePosition::RunPostPhysics ( void ) { + int i; + for ( i = 0; i < mParts.Num(); i ++ ) { + assert ( mParts[i] ); + mParts[i]->RunPostPhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPostPhysics ( ); + } + + if ( !IsOccupied ( ) ) { + return; + } + + if ( fl.inputValid ) { + if ( mInputCmd.upmove > 0 && !mParent->IsLocked() ) { + // inform the driver that its time to get out of the vehicle. + if( !mDriver->EventIsPosted(&AI_ExitVehicle) ) { + mDriver->PostEventMS( &AI_ExitVehicle, 250, false );// To remove jump when exiting + } + + // If the position isnt occupied anymore then the vehicle exit was successful and there + // is nothing else to do + if ( !IsOccupied ( ) ) { + return; + } + } else if ( (mInputCmd.flags & UCF_IMPULSE_SEQUENCE) != (mOldInputFlags & UCF_IMPULSE_SEQUENCE) ) { + int i; + + if ( mInputCmd.impulse >= IMPULSE_0 && mInputCmd.impulse <= IMPULSE_12 ) { + SelectWeapon( mInputCmd.impulse - IMPULSE_0 ); + } + + if ( mWeapons.Num () ) { + switch ( mInputCmd.impulse ) { + case IMPULSE_14: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() + 1) % mWeapons.Num() ); + break; + + case IMPULSE_15: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() - 1) % mWeapons.Num() ); + break; + } + } + + for( i = 0; i < mParts.Num(); i++ ) { + mParts[ i ]->Impulse ( mInputCmd.impulse ); + } + + mOldInputFlags = mInputCmd.flags; + } + } + + // Update the eye origin and axis + GetEyePosition( mEyeOrigin, mEyeAxis ); + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow( colorMagenta, mEyeOrigin, mEyeOrigin + mEyeAxis[0] * 30.0f, 3 ); + } +} + +/* +================ +rvVehiclePosition::GetEyePosition +================ +*/ +void rvVehiclePosition::GetEyePosition( idVec3& origin, idMat3& axis ) const { + GetPosition( mEyeJoint, mEyeOffset, mEyeJointTransform, mDeltaEyeAxisScale, mEyeJointAxisMap, mEyeJointDirMap, origin, axis ); + axis *= GetParent()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetDriverPosition +================ +*/ +void rvVehiclePosition::GetDriverPosition( idVec3& origin, idMat3& axis ) const { + if( fl.bindDriver ) { + GetPosition( mDriverJoint, mDriverOffset, mDriverJointTransform, mDeltaDriverAxisScale, mDriverJointAxisMap, mDriverJointDirMap, origin, axis ); + } else { + origin = GetDriver()->GetPhysics()->GetOrigin(); + axis = GetDriver()->viewAxis; + } +} + +/* +================ +rvVehiclePosition::GetPosition +================ +*/ +void rvVehiclePosition::GetPosition( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const { + if( GetParent()->GetAnimator()->GetJointTransform(jointHandle, gameLocal.GetTime(), origin, axis) ) { + axis *= jointTransform; + idAngles ang( axis.ToAngles().Remap(axisMap, dirMap).Scale(scale) ); + axis = ang.Normalize360().ToMat3() * mAxisOffset; + + origin = GetParent()->GetOrigin() + (origin + offset * axis) * GetParent()->GetAxis(); + } else { + origin = GetOrigin( mEyeOffset ); + axis = GetAxis(); + } +} + +/* +================ +rvVehiclePosition::FireWeapon +================ +*/ +void rvVehiclePosition::FireWeapon( void ) { + if ( mCurrentWeapon >= 0 ) { + static_cast( mWeapons[mCurrentWeapon] )->Fire(); + } +} + +/* +================ +rvVehiclePosition::SelectWeapon +================ +*/ +void rvVehiclePosition::SelectWeapon ( int weapon ) { + if ( weapon < 0 || weapon >= mWeapons.Num ( ) ) { + return; + } + +// mekberg: clear effect + if ( mCurrentWeapon != -1 ) { + static_cast ( mWeapons[ mCurrentWeapon ] )->StopTargetEffect( ); + } + + mCurrentWeapon = weapon; +} + +/* +================ +rvVehiclePosition::WriteToSnapshot +================ +*/ +void rvVehiclePosition::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteBits ( mCurrentWeapon, 3 ); +} + +/* +================ +rvVehiclePosition::ReadFromSnapshot +================ +*/ +void rvVehiclePosition::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mCurrentWeapon = msg.ReadBits ( 3 ); +} + +/* +================ +rvVehiclePosition::Save +================ +*/ +void rvVehiclePosition::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteUsercmd( mInputCmd ); + savefile->WriteAngles ( mInputAngles ); + savefile->WriteUsercmd ( mOldInputCmd ); + savefile->WriteAngles ( mOldInputAngles ); + savefile->WriteInt ( mOldInputFlags ); + + savefile->WriteInt ( mCurrentWeapon ); + + mDriver.Save ( savefile ); + mParent.Save ( savefile ); + + savefile->WriteInt ( mParts.Num ( ) ); + for ( i = 0; i < mParts.Num(); i ++ ) { + savefile->WriteString ( mParts[i]->GetClassname() ); + savefile->WriteStaticObject ( *mParts[i] ); + } + + savefile->WriteInt ( mWeapons.Num ( ) ); + for ( i = 0; i < mWeapons.Num(); i ++ ) { + savefile->WriteString ( mWeapons[i]->GetClassname() ); + savefile->WriteStaticObject ( *mWeapons[i] ); + } + + savefile->WriteVec3 ( mEyeOrigin ); + savefile->WriteMat3 ( mEyeAxis ); + + savefile->WriteJoint ( mEyeJoint ); + savefile->WriteVec3 ( mEyeOffset ); + savefile->WriteMat3( mEyeJointTransform ); + savefile->WriteAngles( mDeltaEyeAxisScale ); + savefile->Write ( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Write ( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->WriteMat3 ( mAxisOffset ); + + savefile->WriteJoint ( mDriverJoint ); + savefile->WriteVec3 ( mDriverOffset ); + savefile->WriteMat3 ( mDriverJointTransform ); + savefile->WriteAngles( mDeltaDriverAxisScale ); + savefile->Write ( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Write ( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->WriteVec3 ( mExitPosOffset ); + savefile->WriteMat3 ( mExitAxisOffset ); + + savefile->WriteString ( mDriverAnim ); + + savefile->WriteString ( mInternalSurface ); + + savefile->Write ( &fl, sizeof ( fl ) ); + + savefile->WriteInt ( mSoundPart ); + savefile->WriteFloat ( mSoundMaxSpeed ); +} + +/* +================ +rvVehiclePosition::Restore +================ +*/ +void rvVehiclePosition::Restore ( idRestoreGame* savefile ) { + int i; + int num; + + savefile->ReadUsercmd( mInputCmd ); + savefile->ReadAngles ( mInputAngles ); + savefile->ReadUsercmd ( mOldInputCmd ); + savefile->ReadAngles ( mOldInputAngles ); + savefile->ReadInt ( mOldInputFlags ); + + savefile->ReadInt ( mCurrentWeapon ); + + mDriver.Restore ( savefile ); + mParent.Restore ( savefile ); + + savefile->ReadInt ( num ); + mParts.Clear ( ); + mParts.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mParts[i] = part; + } + + savefile->ReadInt ( num ); + mWeapons.Clear ( ); + mWeapons.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mWeapons[i] = part; + } + + savefile->ReadVec3 ( mEyeOrigin ); + savefile->ReadMat3 ( mEyeAxis ); + + savefile->ReadJoint ( mEyeJoint ); + savefile->ReadVec3 ( mEyeOffset ); + savefile->ReadMat3 ( mEyeJointTransform ); + savefile->ReadAngles( mDeltaEyeAxisScale ); + savefile->Read( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Read( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->ReadMat3 ( mAxisOffset ); + + savefile->ReadJoint ( mDriverJoint ); + savefile->ReadVec3 ( mDriverOffset ); + savefile->ReadMat3 ( mDriverJointTransform ); + savefile->ReadAngles( mDeltaDriverAxisScale ); + savefile->Read( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Read( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->ReadVec3 ( mExitPosOffset ); + savefile->ReadMat3 ( mExitAxisOffset ); + + savefile->ReadString ( mDriverAnim ); + + savefile->ReadString ( mInternalSurface ); + + savefile->Read ( &fl, sizeof(fl) ); + savefile->ReadInt ( mSoundPart ); + savefile->ReadFloat ( mSoundMaxSpeed ); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/game/vehicle/VehicleRigid.cpp b/src/game/vehicle/VehicleRigid.cpp new file mode 100644 index 0000000..e5325c3 --- /dev/null +++ b/src/game/vehicle/VehicleRigid.cpp @@ -0,0 +1,187 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleRigid.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleRigid ) +END_CLASS + +rvVehicleRigid::rvVehicleRigid ( void ) { +} + +rvVehicleRigid::~rvVehicleRigid ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleRigid::Spawn +================ +*/ +void rvVehicleRigid::Spawn( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleRigid::SetClipModel +================ +*/ +void rvVehicleRigid::SetClipModel ( void ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleRigid '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + + +/* +================ +rvVehicleRigid::RunPrePhysics +================ +*/ +void rvVehicleRigid::RunPrePhysics ( void ) { + storedVelocity = physicsObj.GetLinearVelocity(); +} + +/* +================ +rvVehicleRigid::RunPostPhysics +================ +*/ +void rvVehicleRigid::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 1250 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + float lengthSq = physicsObj.GetLinearVelocity().LengthSqr(); + if ( !autoCorrectionBegin && ( ( storedVelocity * 0.4f ).LengthSqr() >= lengthSq ) || ( lengthSq < 0.01f ) ) { + autoCorrectionBegin = gameLocal.time; + } + } + + if ( g_debugVehicle.GetInteger() == 10 ) { + gameLocal.Printf( "Speed: %f\n", physicsObj.GetLinearVelocity().Length() ); + } +} + +/* +================ +rvVehicleRigid::WriteToSnapshot +================ +*/ +void rvVehicleRigid::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot( msg ); + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::ReadFromSnapshot +================ +*/ +void rvVehicleRigid::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot( msg ); + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::Save +================ +*/ +void rvVehicleRigid::Save ( idSaveGame *savefile ) const { + + savefile->WriteVec3 ( storedVelocity ); // cnicholson: Added unsaved var + savefile->WriteStaticObject ( physicsObj ); +} + +/* +================ +rvVehicleRigid::Restore +================ +*/ +void rvVehicleRigid::Restore ( idRestoreGame *savefile ) { + + savefile->ReadVec3 ( storedVelocity ); // cnicholson: Added unrestored var + + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +===================== +rvVehicleRigid::SkipImpulse +===================== +*/ +bool rvVehicleRigid::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/src/game/vehicle/VehicleRigid.h b/src/game/vehicle/VehicleRigid.h new file mode 100644 index 0000000..eb6a317 --- /dev/null +++ b/src/game/vehicle/VehicleRigid.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleRigid.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLERIGID_H__ +#define __GAME_VEHICLERIGID_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleRigid : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleRigid ); + + rvVehicleRigid ( void ); + ~rvVehicleRigid ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( void ); + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedVelocity; + // end twhitaker: + + idPhysics_RigidBody physicsObj; // physics object +}; + +#endif // __GAME_VEHICLERIGID_H__ diff --git a/src/game/vehicle/VehicleSpline.cpp b/src/game/vehicle/VehicleSpline.cpp new file mode 100644 index 0000000..0495376 --- /dev/null +++ b/src/game/vehicle/VehicleSpline.cpp @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleSplineCoupling.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleSpline.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleSpline ) + EVENT( EV_PostSpawn, rvVehicleSpline::Event_PostSpawn ) + EVENT( EV_SetSpline, rvVehicleSpline::Event_SetSpline ) + EVENT( EV_DoneMoving, rvVehicleSpline::Event_DoneMoving ) +END_CLASS + +rvVehicleSpline::rvVehicleSpline ( void ) { +} + +rvVehicleSpline::~rvVehicleSpline ( void ) { + SetPhysics( NULL ); +} + +void rvVehicleSpline::Spawn ( void ) { + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat( "accel", "200" ) ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat( "decel", "200" ) ); + + viewAxis = idAngles( 0, spawnArgs.GetFloat( "angle" ) , 0 ).ToMat3(); + + physicsObj.SetAxis( GetPhysics()->GetAxis() * viewAxis ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + BecomeActive( TH_THINK ); + + accelWithStrafe = Sign( spawnArgs.GetFloat("accel_strafe") ); + + idealSpeed = spawnArgs.GetFloat( "speed", "200" ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleSpline::Save +================ +*/ +void rvVehicleSpline::Save ( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteMat3( angleOffset ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( accelWithStrafe ); +} + +/* +================ +rvVehicleSpline::Restore +================ +*/ +void rvVehicleSpline::Restore ( idRestoreGame *savefile ) { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadMat3( angleOffset ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( accelWithStrafe ); +} + +void rvVehicleSpline::Think( void ) { + float moveAmount = 0.0f; + + if ( positions[0].IsOccupied ( ) && !IsFrozen () && IsMovementEnabled ( ) ) { + + if ( accelWithStrafe != 0.0f ) { + moveAmount = positions[0].mInputCmd.rightmove * accelWithStrafe; + } else { + moveAmount = positions[0].mInputCmd.forwardmove; + } + + moveAmount = Sign( moveAmount ); + } + + physicsObj.SetSpeed( idealSpeed * moveAmount ); + + rvVehicle::Think( ); +} + +void rvVehicleSpline::Event_PostSpawn( void ) { + idStr splinePath; + + if ( spawnArgs.GetString( "spline_path", "", splinePath ) ) { + Event_SetSpline( gameLocal.FindEntity( splinePath.c_str() ) ); + } else { + gameLocal.Warning( "\"spline_path\" key not found on %s (rvVehicleSpline)", this->GetName() ); + } +} + +void rvVehicleSpline::Event_SetSpline ( idEntity * spline ) { + if ( spline && spline->IsType( idSplinePath::GetClassType() ) ) { + physicsObj.SetSplineEntity( static_cast< idSplinePath * >( spline ) ); + //HACK: force an intitial physics update so that the object orients itself with the spline + //TEMP: this should be fixed after the build on friday (Jan 28, 2005) + physicsObj.SetSpeed( 0.1f ); + RunPhysics(); + //END HACK + } +} + +void rvVehicleSpline::Event_DoneMoving() { + idThread::ReturnInt( true ); +} diff --git a/src/game/vehicle/VehicleSpline.h b/src/game/vehicle/VehicleSpline.h new file mode 100644 index 0000000..f6cea51 --- /dev/null +++ b/src/game/vehicle/VehicleSpline.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleSpline.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESPLINECOUPLING_H__ +#define __GAME_VEHICLESPLINECOUPLING_H__ + +class rvVehicleSpline : public rvVehicle { +public: + CLASS_PROTOTYPE( rvVehicleSpline ); + + rvVehicleSpline ( void ); + ~rvVehicleSpline ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Think ( void ); + + void Event_PostSpawn ( void ); + void Event_SetSpline ( idEntity * spline ); + void Event_DoneMoving ( void ); + +protected: + rvPhysics_Spline physicsObj; + idMat3 angleOffset; + float idealSpeed; + float accelWithStrafe; +}; + +#endif // __GAME_VEHICLESPLINECOUPLING_H__ diff --git a/src/game/vehicle/VehicleStatic.cpp b/src/game/vehicle/VehicleStatic.cpp new file mode 100644 index 0000000..81b132c --- /dev/null +++ b/src/game/vehicle/VehicleStatic.cpp @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleStatic.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleStatic ) + EVENT( AI_ScriptedAnim, rvVehicleStatic::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleStatic::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleStatic::Event_ScriptedStop ) +END_CLASS + +rvVehicleStatic::rvVehicleStatic ( void ) { +} + +rvVehicleStatic::~rvVehicleStatic ( void ) { +} + +/* +================ +rvVehicleStatic::Spawn +================ +*/ +void rvVehicleStatic::Spawn( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleStatic::AddDriver +================ +*/ +int rvVehicleStatic::AddDriver ( int position, idActor* driver ) { + int pos = rvVehicle::AddDriver( position, driver ); + + if( pos < 0 ) { + return pos; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "hideGunInfo" ); + } + + return pos; +} + +/* +================ +rvVehicleStatic::RemoveDriver +================ +*/ +bool rvVehicleStatic::RemoveDriver ( int position, bool force ) { + bool result = rvVehicle::RemoveDriver( position, force ); + + if( !result ) { + return result; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "showGunInfo" ); + } + + return result; +} + +/* +================ +rvVehicleStatic::UpdateHUD +================ +*/ +void rvVehicleStatic::UpdateHUD( idActor* driver, idUserInterface* gui ) { + if( driver && driver->IsType( idPlayer::GetClassType() ) ) { + static_cast(driver)->UpdateHudStats( gui ); + } +} + +/* +================ +rvVehicleStatic::Event_ScriptedAnim +================ +*/ +void rvVehicleStatic::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + vfl.scripted = true; +} + +/* +================ +rvVehicleStatic::Event_ScriptedDone +================ +*/ +void rvVehicleStatic::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleStatic::Event_ScriptedStop +================ +*/ +void rvVehicleStatic::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + PlayCycle( ANIMCHANNEL_TORSO, spawnArgs.GetString( "idle" ), 2 ); + } +} diff --git a/src/game/vehicle/VehicleStatic.h b/src/game/vehicle/VehicleStatic.h new file mode 100644 index 0000000..278f244 --- /dev/null +++ b/src/game/vehicle/VehicleStatic.h @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleStatic.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESTATIC_H__ +#define __GAME_VEHICLESTATIC_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleStatic : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleStatic ); + + rvVehicleStatic ( void ); + ~rvVehicleStatic ( void ); + + void Spawn ( void ); + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); +}; + +#endif // __GAME_VEHICLESTATIC_H__ diff --git a/src/game/vehicle/Vehicle_DropPod.cpp b/src/game/vehicle/Vehicle_DropPod.cpp new file mode 100644 index 0000000..7d9ce04 --- /dev/null +++ b/src/game/vehicle/Vehicle_DropPod.cpp @@ -0,0 +1,201 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +class rvVehicleDropPod : public rvVehicleStatic { +public: + + CLASS_PROTOTYPE( rvVehicleDropPod ); + + rvVehicleDropPod ( void ); + + void Spawn ( void ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleDropPod ); +}; + +CLASS_DECLARATION( rvVehicleStatic, rvVehicleDropPod ) + EVENT( AI_ScriptedAnim, rvVehicleDropPod::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleDropPod::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleDropPod::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleDropPod::rvVehicleDropPod +================ +*/ +rvVehicleDropPod::rvVehicleDropPod ( void ) { +} + +/* +================ +rvVehicleDropPod::Spawn +================ +*/ +void rvVehicleDropPod::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleDropPod ) + STATE ( "State_Idle", rvVehicleDropPod::State_Idle ) + STATE ( "State_IdleThink", rvVehicleDropPod::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleDropPod::State_IdleOffline ) + + STATE ( "State_ScriptedAnim", rvVehicleDropPod::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleDropPod::State_IdleOffline +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 0 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_Idle +================ +*/ +stateResult_t rvVehicleDropPod::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_IdleThink +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", parms.blendFrames ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleDropPod::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleDropPod::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedAnim +================ +*/ +void rvVehicleDropPod::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedDone +================ +*/ +void rvVehicleDropPod::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleDropPod::Event_ScriptedStop +================ +*/ +void rvVehicleDropPod::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 1 ); + } +} + +/* +================ +rvVehicleDropPod::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleDropPod::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { +// if ( GetBindMaster() ) { +// axis = viewAxis; +// origin.Zero(); +// return true; +// } + return false; +} + diff --git a/src/game/vehicle/Vehicle_Walker.cpp b/src/game/vehicle/Vehicle_Walker.cpp new file mode 100644 index 0000000..9384ed2 --- /dev/null +++ b/src/game/vehicle/Vehicle_Walker.cpp @@ -0,0 +1,617 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +class rvVehicleWalker : public rvVehicleAnimated { +public: + + CLASS_PROTOTYPE( rvVehicleWalker ); + + rvVehicleWalker ( void ); + + void Think ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void UpdateState ( void ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + + const char* stopAnimName; + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + +private: + void HandleStrafing ( void ); + + + stateResult_t Frame_ForwardRight ( int ); + stateResult_t Frame_ForwardLeft ( int ); + stateResult_t Frame_BackwardRight ( int ); + stateResult_t Frame_BackwardLeft ( int ); + + stateResult_t State_Wait_OnlineAnim ( const stateParms_t& parms ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_Offline ( const stateParms_t& parms ); + stateResult_t State_Online ( const stateParms_t& parms ); + + stateResult_t State_ForwardStart ( const stateParms_t& parms ); + stateResult_t State_Forward ( const stateParms_t& parms ); + stateResult_t State_BackwardStart ( const stateParms_t& parms ); + stateResult_t State_Backward ( const stateParms_t& parms ); + stateResult_t State_Stop ( const stateParms_t& parms ); + stateResult_t State_Turn ( const stateParms_t& parms ); + stateResult_t State_TurnThink ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleWalker ); +}; + +CLASS_DECLARATION( rvVehicleAnimated, rvVehicleWalker ) + EVENT( AI_ScriptedAnim, rvVehicleWalker::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleWalker::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleWalker::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleWalker::rvVehicleWalker +================ +*/ +rvVehicleWalker::rvVehicleWalker ( void ) { + stopAnimName = ""; +} + +/* +================ +rvVehicleWalker::Think +================ +*/ +void rvVehicleWalker::Think ( void ) { + rvVehicleAnimated::Think(); + + if ( !HasDrivers() || IsStalled() ) { + return; + } + + idVec3 delta; + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( delta.LengthSqr() > 0.1f ) { + gameLocal.RadiusDamage( GetOrigin(), this, this, this, this, spawnArgs.GetString( "def_stompDamage", "damage_Smallexplosion" ) ); + } +} + +/* +================ +rvVehicleWalker::Spawn +================ +*/ +void rvVehicleWalker::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +================ +rvVehicleWalker::Save +================ +*/ +void rvVehicleWalker::Save ( idSaveGame *savefile ) const { + savefile->WriteString( stopAnimName ); +} + +/* +================ +rvVehicleWalker::Restore +================ +*/ +void rvVehicleWalker::Restore ( idRestoreGame *savefile ) { + //twhitaker: I just happened to see this, while going through this code which I originally wrote (at 3am or so). + //TODO: fix this. Make stopAnimName an idStr? + idStr str; + savefile->ReadString( str ); + stopAnimName = str; +} + +/* +================ +rvVehicleWalker::UpdateState +================ +*/ +void rvVehicleWalker::UpdateState ( void ) { + rvVehiclePosition& pos = positions[0]; + usercmd_t& cmd = pos.mInputCmd; + + vfl.driver = pos.IsOccupied(); + vfl.forward = (vfl.driver && cmd.forwardmove > 0); + vfl.backward = (vfl.driver && cmd.forwardmove < 0); + vfl.right = (vfl.driver && cmd.rightmove < 0); + vfl.left = (vfl.driver && cmd.rightmove > 0); + vfl.strafe = (vfl.driver && cmd.buttons & BUTTON_STRAFE ); + + if ( g_vehicleMode.GetInteger() != 0 ) { + vfl.strafe = !vfl.strafe; + } +} + +/* +================ +rvVehicleWalker::SetInput +================ +*/ +void rvVehicleWalker::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + usercmd_t* pcmd = const_cast( &cmd ); + pcmd->rightmove *= -1; + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +/* +================ +rvVehicleWalker::HandleStrafing +================ +*/ +void rvVehicleWalker::HandleStrafing ( void ) { + if ( vfl.right ) { + additionalDelta -= spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } + if ( vfl.left ) { + additionalDelta += spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } +} + +// mekberg: overloaded this because physics bounds code is significantly different +/* +===================== +rvVehicleWalker::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicleWalker::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, GetPhysics()->GetAxis() ); + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleWalker ) + STATE ( "ForwardLeft", rvVehicleWalker::Frame_ForwardLeft ) + STATE ( "ForwardRight", rvVehicleWalker::Frame_ForwardRight ) + STATE ( "BackwardLeft", rvVehicleWalker::Frame_BackwardLeft ) + STATE ( "BackwardRight", rvVehicleWalker::Frame_BackwardRight ) + + STATE ( "Wait_OnlineAnim", rvVehicleWalker::State_Wait_OnlineAnim ) + + STATE ( "State_Idle", rvVehicleWalker::State_Idle ) + STATE ( "State_IdleThink", rvVehicleWalker::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleWalker::State_IdleOffline ) + STATE ( "State_Offline", rvVehicleWalker::State_Offline ) + STATE ( "State_Online", rvVehicleWalker::State_Online ) + + STATE ( "State_ForwardStart", rvVehicleWalker::State_ForwardStart ) + STATE ( "State_Forward", rvVehicleWalker::State_Forward ) + STATE ( "State_BackwardStart", rvVehicleWalker::State_BackwardStart ) + STATE ( "State_Backward", rvVehicleWalker::State_Backward ) + STATE ( "State_Stop", rvVehicleWalker::State_Stop ) + STATE ( "State_Turn", rvVehicleWalker::State_Turn ) + STATE ( "State_TurnThink", rvVehicleWalker::State_TurnThink ) + STATE ( "State_ScriptedAnim", rvVehicleWalker::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleWalker::State_IdleOffline +================ +*/ +stateResult_t rvVehicleWalker::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle_offline", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Online", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Online +================ +*/ +stateResult_t rvVehicleWalker::State_Online ( const stateParms_t& parms ) { + vfl.frozen = false; + + PlayAnim ( ANIMCHANNEL_LEGS, "start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_OnlineAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 4 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Offline +================ +*/ +stateResult_t rvVehicleWalker::State_Offline ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "stop", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 4 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleThink", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver || vfl.stalled ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Offline", parms.blendFrames ); + return SRESULT_DONE; + } + + if ( IsMovementEnabled ( ) ) { + if ( vfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_ForwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_BackwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.right || vfl.left ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 2 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_ForwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_ForwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "forward_start", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Forward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Forward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.forward ) { + stopAnimName = "forward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 4 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "forward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_BackwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_BackwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "backward_start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Backward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Backward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.backward ) { + stopAnimName = "backward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "backward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_Stop +================ +*/ +stateResult_t rvVehicleWalker::State_Stop ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, stopAnimName, parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Turn +================ +*/ +stateResult_t rvVehicleWalker::State_Turn ( const stateParms_t& parms ) { + if ( vfl.left ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_left", parms.blendFrames ); + } else if ( vfl.right ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_right", parms.blendFrames ); + } else { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "State_TurnThink", 16 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_TurnThink +================ +*/ +stateResult_t rvVehicleWalker::State_TurnThink ( const stateParms_t& parms ) { + // If moving again bail on the turn, reguardless of whether its in mid animation + if ( vfl.forward || vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + // If the animation is done then repeat + if ( AnimDone ( ANIMCHANNEL_LEGS, 8 ) ){ + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 8 ); + return SRESULT_DONE; + } + + HandleStrafing(); + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::Frame_ForwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardLeft ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_ForwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardRight ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + + +/* +================ +rvVehicleWalker::Frame_BackwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardLeft ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_BackwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardRight ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::State_Wait_OnlineAnim +================ +*/ +stateResult_t rvVehicleWalker::State_Wait_OnlineAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) && vfl.driver ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleWalker::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::Event_ScriptedAnim +================ +*/ +void rvVehicleWalker::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleWalker::Event_ScriptedDone +================ +*/ +void rvVehicleWalker::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleWalker::Event_ScriptedStop +================ +*/ +void rvVehicleWalker::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + } +} diff --git a/src/game/weapon/WeaponBlaster.cpp b/src/game/weapon/WeaponBlaster.cpp new file mode 100644 index 0000000..00b2251 --- /dev/null +++ b/src/game/weapon/WeaponBlaster.cpp @@ -0,0 +1,511 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +#define BLASTER_SPARM_CHARGEGLOW 6 + +class rvWeaponBlaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponBlaster ); + + rvWeaponBlaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + bool UpdateAttack ( void ); + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + int chargeTime; + int chargeDelay; + idVec2 chargeGlow; + bool fireForced; + int fireHeldTime; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Charge ( const stateParms_t& parms ); + stateResult_t State_Charged ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponBlaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponBlaster ) +END_CLASS + +/* +================ +rvWeaponBlaster::rvWeaponBlaster +================ +*/ +rvWeaponBlaster::rvWeaponBlaster ( void ) { +} + +/* +================ +rvWeaponBlaster::UpdateFlashlight +================ +*/ +bool rvWeaponBlaster::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponBlaster::Flashlight +================ +*/ +void rvWeaponBlaster::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +================ +rvWeaponBlaster::UpdateAttack +================ +*/ +bool rvWeaponBlaster::UpdateAttack ( void ) { + // Clear fire forced + if ( fireForced ) { + if ( !wsfl.attack ) { + fireForced = false; + } else { + return false; + } + } + + // If the player is pressing the fire button and they have enough ammo for a shot + // then start the shooting process. + if ( wsfl.attack && gameLocal.time >= nextAttackTime ) { + // Save the time which the fire button was pressed + if ( fireHeldTime == 0 ) { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + fireHeldTime = gameLocal.time; + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + } + } + + // If they have the charge mod and they have overcome the initial charge + // delay then transition to the charge state. + if ( fireHeldTime != 0 ) { + if ( gameLocal.time - fireHeldTime > chargeDelay ) { + SetState ( "Charge", 4 ); + return true; + } + + // If the fire button was let go but was pressed at one point then + // release the shot. + if ( !wsfl.attack ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if( player ) { + + if( player->GuiActive()) { + //make sure the player isn't looking at a gui first + SetState ( "Lower", 0 ); + } else { + SetState ( "Fire", 0 ); + } + } + return true; + } + } + + return false; +} + +/* +================ +rvWeaponBlaster::Spawn +================ +*/ +void rvWeaponBlaster::Spawn ( void ) { + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + SetState ( "Raise", 0 ); + + chargeGlow = spawnArgs.GetVec2 ( "chargeGlow" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargeTime" ) ); + chargeDelay = SEC2MS ( spawnArgs.GetFloat ( "chargeDelay" ) ); + + fireHeldTime = 0; + fireForced = false; + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponBlaster::Save +================ +*/ +void rvWeaponBlaster::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( chargeDelay ); + savefile->WriteVec2 ( chargeGlow ); + savefile->WriteBool ( fireForced ); + savefile->WriteInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::Restore +================ +*/ +void rvWeaponBlaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( chargeDelay ); + savefile->ReadVec2 ( chargeGlow ); + savefile->ReadBool ( fireForced ); + savefile->ReadInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::PreSave +================ +*/ +void rvWeaponBlaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponBlaster::PostSave +================ +*/ +void rvWeaponBlaster::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponBlaster ) + STATE ( "Raise", rvWeaponBlaster::State_Raise ) + STATE ( "Lower", rvWeaponBlaster::State_Lower ) + STATE ( "Idle", rvWeaponBlaster::State_Idle) + STATE ( "Charge", rvWeaponBlaster::State_Charge ) + STATE ( "Charged", rvWeaponBlaster::State_Charged ) + STATE ( "Fire", rvWeaponBlaster::State_Fire ) + STATE ( "Flashlight", rvWeaponBlaster::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponBlaster::State_Raise +================ +*/ +stateResult_t rvWeaponBlaster::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE(RAISE_WAIT); + + case RAISE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Lower +================ +*/ +stateResult_t rvWeaponBlaster::State_Lower ( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Idle +================ +*/ +stateResult_t rvWeaponBlaster::State_Idle ( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus ( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + if ( UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charge +================ +*/ +stateResult_t rvWeaponBlaster::State_Charge ( const stateParms_t& parms ) { + enum { + CHARGE_INIT, + CHARGE_WAIT, + }; + switch ( parms.stage ) { + case CHARGE_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + StartSound ( "snd_charge", SND_CHANNEL_ITEM, 0, false, NULL ); + PlayCycle( ANIMCHANNEL_ALL, "charging", parms.blendFrames ); + return SRESULT_STAGE ( CHARGE_WAIT ); + + case CHARGE_WAIT: + if ( gameLocal.time - fireHeldTime < chargeTime ) { + float f; + f = (float)(gameLocal.time - fireHeldTime) / (float)chargeTime; + f = chargeGlow[0] + f * (chargeGlow[1] - chargeGlow[0]); + f = idMath::ClampFloat ( chargeGlow[0], chargeGlow[1], f ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, f ); + + if ( !wsfl.attack ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + SetState ( "Charged", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charged +================ +*/ +stateResult_t rvWeaponBlaster::State_Charged ( const stateParms_t& parms ) { + enum { + CHARGED_INIT, + CHARGED_WAIT, + }; + switch ( parms.stage ) { + case CHARGED_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 1.0f ); + + StopSound ( SND_CHANNEL_ITEM, false ); + StartSound ( "snd_charge_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound ( "snd_charge_click", SND_CHANNEL_BODY, 0, false, NULL ); + return SRESULT_STAGE(CHARGED_WAIT); + + case CHARGED_WAIT: + if ( !wsfl.attack ) { + fireForced = true; + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Fire +================ +*/ +stateResult_t rvWeaponBlaster::State_Fire ( const stateParms_t& parms ) { + enum { + FIRE_INIT, + FIRE_WAIT, + }; + switch ( parms.stage ) { + case FIRE_INIT: + + StopSound ( SND_CHANNEL_ITEM, false ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + //don't fire if we're targeting a gui. + idPlayer* player; + player = gameLocal.GetLocalPlayer(); + + //make sure the player isn't looking at a gui first + if( player && player->GuiActive() ) { + fireHeldTime = 0; + SetState ( "Lower", 0 ); + return SRESULT_DONE; + } + + if( player && !player->CanFire() ) { + fireHeldTime = 0; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + + + + if ( gameLocal.time - fireHeldTime > chargeTime ) { + Attack ( true, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_chargedflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "chargedfire", parms.blendFrames ); + } else { + Attack ( false, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_normalflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + fireHeldTime = 0; + + return SRESULT_STAGE(FIRE_WAIT); + + case FIRE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) || UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Flashlight +================ +*/ +stateResult_t rvWeaponBlaster::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/src/game/weapon/WeaponDarkMatterGun.cpp b/src/game/weapon/WeaponDarkMatterGun.cpp new file mode 100644 index 0000000..14543a8 --- /dev/null +++ b/src/game/weapon/WeaponDarkMatterGun.cpp @@ -0,0 +1,494 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../Projectile.h" + +class rvWeaponDarkMatterGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponDarkMatterGun ); + + rvWeaponDarkMatterGun ( void ); + ~rvWeaponDarkMatterGun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + enum darkMatterRing_t { + RING_OUTER, + RING_MIDDLE, + RING_INNER, + RING_MAX + }; + + struct rings_s { + idAngles angularVelocity; + jointHandle_t joint; + }; + rings_s rings[ RING_MAX ]; + + int nextRotateTime; + int ringStartTime; + int chargeDuration; + bool clientReload; + rvClientEffectPtr coreEffect; + rvClientEffectPtr coreStartEffect; + jointHandle_t jointCore; + + void InitRing ( darkMatterRing_t ring, const char* name ); + void StartRings ( bool chargeUp ); + void StopRings ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponDarkMatterGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponDarkMatterGun ) +END_CLASS + +/* +================ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun ( void ) { + coreStartEffect = NULL; + coreEffect = NULL; + ringStartTime = -1; + clientReload = false; +} + +/* +================ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun ( void ) { + StopRings ( ); +} + +/* +================ +rvWeaponDarkMatterGun::Spawn +================ +*/ +void rvWeaponDarkMatterGun::Spawn ( void ) { + SetState ( "Raise", 0 ); + + InitRing ( RING_OUTER, "outer" ); + InitRing ( RING_INNER, "inner" ); + InitRing ( RING_MIDDLE, "middle" ); + + nextRotateTime = 0; + + chargeDuration = SEC2MS ( spawnArgs.GetFloat ( "chargeDuration", ".5" ) ); + + jointCore = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_core" ) ); +} + +/* +================ +rvWeaponDarkMatterGun::Save +================ +*/ +void rvWeaponDarkMatterGun::Save ( idSaveGame *savefile ) const { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->WriteAngles ( rings[ i ].angularVelocity ); + savefile->WriteJoint ( rings[ i ].joint ); + } + savefile->WriteInt ( nextRotateTime ); + savefile->WriteInt ( ringStartTime ); + savefile->WriteInt ( chargeDuration ); + savefile->WriteObject( coreEffect.GetEntity() ); + savefile->WriteObject( coreStartEffect.GetEntity() ); + savefile->WriteJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::Restore +================ +*/ +void rvWeaponDarkMatterGun::Restore ( idRestoreGame *savefile ) { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->ReadAngles ( rings[ i ].angularVelocity ); + savefile->ReadJoint ( rings[ i ].joint ); + } + savefile->ReadInt ( nextRotateTime ); + savefile->ReadInt ( ringStartTime ); + savefile->ReadInt ( chargeDuration ); + savefile->ReadObject( reinterpret_cast( coreEffect ) ); + savefile->ReadObject( reinterpret_cast( coreStartEffect ) ); + savefile->ReadJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::PreSave +================ +*/ +void rvWeaponDarkMatterGun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + +} + +/* +================ +rvWeaponDarkMatterGun::PostSave +================ +*/ +void rvWeaponDarkMatterGun::PostSave ( void ) { + + //start the ring sounds + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); +} + + +/* +================ +rvWeaponDarkMatterGun::InitRing +================ +*/ +void rvWeaponDarkMatterGun::InitRing ( darkMatterRing_t ring, const char* name ) { + rings[ring].angularVelocity = spawnArgs.GetAngles ( va("ring_%s_velocity", name ) ); + rings[ring].joint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("ring_%s_joint", name ) ) ); +} + +/* +================ +rvWeaponDarkMatterGun::StartRings +================ +*/ +void rvWeaponDarkMatterGun::StartRings ( bool chargeUp ) { + int i; + + if ( ringStartTime == -1 ) { + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); + ringStartTime = gameLocal.time; + } + + if ( chargeUp ) { + coreStartEffect = viewModel->PlayEffect( "fx_core_start", jointCore ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, chargeDuration / 2 ); + } + } else if ( !coreEffect ) { + coreEffect = viewModel->PlayEffect( "fx_core", jointCore, true ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, 0 ); + } + } +} + +/* +================ +rvWeaponDarkMatterGun::StopRings +================ +*/ +void rvWeaponDarkMatterGun::StopRings ( void ) { + int i; + + if ( !viewModel ) { + return; + } + + viewModel->StopSound ( SND_CHANNEL_VOICE, false ); + + if ( coreEffect ) { + coreEffect->Stop ( ); + coreEffect = NULL; + } + + if ( coreStartEffect ) { + coreStartEffect->Stop ( ); + coreStartEffect = NULL; + } + + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->ClearJoint ( rings[i].joint ); + } + + ringStartTime = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponDarkMatterGun ) + STATE ( "Idle", rvWeaponDarkMatterGun::State_Idle) + STATE ( "Fire", rvWeaponDarkMatterGun::State_Fire ) + STATE ( "Reload", rvWeaponDarkMatterGun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponDarkMatterGun::State_Idle +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + // Auto reload? + if ( !AmmoInClip ( ) && AmmoAvailable () && !clientReload ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + clientReload = false; + + StartRings ( false ); + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload ) { + if ( owner->entityNumber != gameLocal.localClientNum ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } else { + wsfl.netReload = false; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Fire +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopRings ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 2 ) || (gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Reload +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + StartRings ( true ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + clientReload = true; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + AddToClip ( ClipSize() ); + SetStatus ( WP_READY ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + rvDarkMatterProjectile + +=============================================================================== +*/ + +class rvDarkMatterProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDarkMatterProjectile ); + + rvDarkMatterProjectile ( void ); + ~rvDarkMatterProjectile ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + +protected: + + int nextDamageTime; + const idDict* radiusDamageDef; +}; + +CLASS_DECLARATION( idProjectile, rvDarkMatterProjectile ) +END_CLASS + +/* +================ +rvDarkMatterProjectile::rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::rvDarkMatterProjectile ( void ) { + radiusDamageDef = NULL; +} + +/* +================ +rvDarkMatterProjectile::~rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::~rvDarkMatterProjectile ( void ) { +} + +/* +================ +rvDarkMatterProjectile::Spawn +================ +*/ +void rvDarkMatterProjectile::Spawn ( void ) { + nextDamageTime = 0; + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Save +================ +*/ +void rvDarkMatterProjectile::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( nextDamageTime ); +} + +/* +================ +rvDarkMatterProjectile::Restore +================ +*/ +void rvDarkMatterProjectile::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( nextDamageTime ); + + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Think +================ +*/ +void rvDarkMatterProjectile::Think ( void ) { + physicsObj.SetClipMask( MASK_DMGSOLID ); + idProjectile::Think ( ); + + if ( gameLocal.time > nextDamageTime ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, owner, owner, NULL, spawnArgs.GetString( "def_radius_damage" ), 1.0f, &hitCount ); + nextDamageTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "damageRate", ".05" ) ); + } +} + + diff --git a/src/game/weapon/WeaponGauntlet.cpp b/src/game/weapon/WeaponGauntlet.cpp new file mode 100644 index 0000000..12f1fbf --- /dev/null +++ b/src/game/weapon/WeaponGauntlet.cpp @@ -0,0 +1,538 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGauntlet : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGauntlet ); + + rvWeaponGauntlet( void ); + ~rvWeaponGauntlet( void ); + + virtual void Spawn ( void ); + virtual void CleanupWeapon ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idAngles bladeSpinFast; + idAngles bladeSpinSlow; + jointHandle_t bladeJoint; + jointHandle_t bladeJoint_world; + int bladeAccel; + + float range; + + rvClientEffectPtr impactEffect; + int impactMaterial; + + void Attack ( void ); + void StartBlade ( void ); + void StopBlade ( void ); + +private: + + void PlayLoopSound ( int sndType ); + int loopSound; + enum { + LOOP_NONE, + LOOP_WALL, + LOOP_FLESH + }; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponGauntlet ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGauntlet ) +END_CLASS + +/* +================ +rvWeaponGauntlet::rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::rvWeaponGauntlet( void ) { + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::~rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::~rvWeaponGauntlet(void) +{ + if ( viewModel ) { + StopSound( SND_CHANNEL_WEAPON, false ); + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + +} +/* +================ +rvWeaponGauntlet::Spawn +================ +*/ +void rvWeaponGauntlet::Spawn ( void ) { + SetState ( "Raise", 0 ); + + bladeJoint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + bladeJoint_world= GetWorldModel()->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + + bladeSpinFast = spawnArgs.GetAngles ( "blade_spinfast" ); + bladeSpinSlow = spawnArgs.GetAngles ( "blade_spinslow" ); + bladeAccel = SEC2MS ( spawnArgs.GetFloat ( "blade_accel", ".25" ) ); + + range = spawnArgs.GetFloat ( "range", "32" ); + + impactMaterial = -1; + impactEffect = NULL; + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::Save +================ +*/ +void rvWeaponGauntlet::Save ( idSaveGame *savefile ) const { + savefile->WriteAngles ( bladeSpinFast ); + savefile->WriteAngles ( bladeSpinSlow ); + savefile->WriteJoint ( bladeJoint ); + savefile->WriteJoint ( bladeJoint_world ); + + savefile->WriteInt ( bladeAccel ); + + savefile->WriteFloat ( range ); + + savefile->WriteObject ( impactEffect ); + savefile->WriteInt ( impactMaterial ); + savefile->WriteInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::Restore +================ +*/ +void rvWeaponGauntlet::Restore ( idRestoreGame *savefile ) { + savefile->ReadAngles ( bladeSpinFast ); + savefile->ReadAngles ( bladeSpinSlow ); + savefile->ReadJoint ( bladeJoint ); + savefile->ReadJoint ( bladeJoint_world ); + + savefile->ReadInt ( bladeAccel ); + + savefile->ReadFloat ( range ); + + savefile->ReadObject ( reinterpret_cast( impactEffect ) ); + savefile->ReadInt ( impactMaterial ); + savefile->ReadInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::PreSave +================ +*/ +void rvWeaponGauntlet::PreSave ( void ) { + +} + +/* +================ +rvWeaponGauntlet::PostSave +================ +*/ +void rvWeaponGauntlet::PostSave( void ) { +} + +void rvWeaponGauntlet::PlayLoopSound( int sndType ) { + if ( loopSound == sndType ) { + return; + } + const char *loopSoundString = NULL; + switch ( sndType ) { + case LOOP_NONE: + default: + loopSoundString = "snd_spin_loop"; + break; + case LOOP_WALL: + loopSoundString = "snd_spin_wall"; + break; + case LOOP_FLESH: + loopSoundString = "snd_spin_flesh"; + break; + } + if ( loopSoundString ) { + loopSound = sndType; + StartSound( loopSoundString, SND_CHANNEL_WEAPON, 0, false, 0 ); + } +} + +/* +================ +rvWeaponGauntlet::CleanupWeapon +================ +*/ +void rvWeaponGauntlet::CleanupWeapon( void ) { + + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); +} + +/* +================ +rvWeaponGauntlet::Attack +================ +*/ +void rvWeaponGauntlet::Attack ( void ) { + trace_t tr; + idEntity* ent; + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + owner->WeaponFireFeedback( &weaponDef->dict ); + + if ( tr.fraction >= 1.0f ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); + return; + } + + // Entity we hit? + ent = gameLocal.entities[tr.c.entityNum]; + + // If the impact material changed then stop the impact effect + if ( (tr.c.materialType && tr.c.materialType->Index ( ) != impactMaterial) || + (!tr.c.materialType && impactMaterial != -1) ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + } + + // In singleplayer-- the gauntlet never effects marine AI + if( !gameLocal.isMultiplayer ) { + idActor* actor_ent = 0; + + //ignore both the body and the head. + if (ent->IsType( idActor::GetClassType()) ) { + actor_ent = static_cast(ent); + } else if (ent->IsType ( idAFAttachment::GetClassType()) ) { + actor_ent = static_cast(ent->GetBindMaster()); + } + + if ( actor_ent && actor_ent->team == gameLocal.GetLocalPlayer()->team ) { + PlayLoopSound( LOOP_NONE ); + return; + } + } + + //multiplayer-- don't gauntlet dead stuff + if( gameLocal.isMultiplayer ) { + idPlayer * player; + if ( ent->IsType( idPlayer::GetClassType() )) { + player = static_cast< idPlayer* >(ent); + if (player->health <= 0) { + return; + } + } + + } + + if ( !impactEffect ) { + impactMaterial = tr.c.materialType ? tr.c.materialType->Index() : -1; + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_impact", tr.c.materialType ), tr.endpos, tr.c.normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( tr.endpos ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + } + + // Do damage? + if ( gameLocal.time > nextAttackTime ) { + if ( ent ) { + if ( ent->fl.takedamage ) { + float dmgScale = 1.0f; + dmgScale *= owner->PowerUpModifier( PMOD_MELEE_DAMAGE ); + ent->Damage ( owner, owner, playerViewAxis[0], spawnArgs.GetString ( "def_damage" ), dmgScale, 0 ); + StartSound( "snd_hit", SND_CHANNEL_ANY, 0, false, NULL ); + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + PlayLoopSound( LOOP_FLESH ); + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_NONE ); + } + nextAttackTime = gameLocal.time + fireRate; + } +} + +/* +================ +rvWeaponGauntlet::StartBlade +================ +*/ +void rvWeaponGauntlet::StartBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_ITEM, false ); +// StartSound ( "snd_blade_fast", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_spin_up", SND_CHANNEL_ITEM, 0, false, 0 ); +} + +/* +================ +rvWeaponGauntlet::StopBlade +================ +*/ +void rvWeaponGauntlet::StopBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_WEAPON, false ); +// StartSound ( "snd_blade_slow", SND_CHANNEL_ITEM, 0, false, NULL ); + + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGauntlet ) + STATE ( "Raise", rvWeaponGauntlet::State_Raise ) + STATE ( "Lower", rvWeaponGauntlet::State_Lower ) + STATE ( "Idle", rvWeaponGauntlet::State_Idle) + STATE ( "Fire", rvWeaponGauntlet::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponGauntlet::State_Raise +================ +*/ +stateResult_t rvWeaponGauntlet::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE(RAISE_WAIT); + + case RAISE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Lower +================ +*/ +stateResult_t rvWeaponGauntlet::State_Lower ( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "lower", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Idle +================ +*/ +stateResult_t rvWeaponGauntlet::State_Idle ( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus ( WP_READY ); + StopBlade ( ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Fire +================ +*/ +stateResult_t rvWeaponGauntlet::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim ( ANIMCHANNEL_ALL, "attack_start", parms.blendFrames ); + StartBlade ( ); + loopSound = LOOP_NONE; + return SRESULT_STAGE(STAGE_START_WAIT); + + case STAGE_START_WAIT: + if ( !wsfl.attack ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( AnimDone ( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle ( ANIMCHANNEL_ALL, "attack_loop", parms.blendFrames ); + StartSound( "snd_spin_loop", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE(STAGE_LOOP_WAIT); + + case STAGE_LOOP_WAIT: + if ( !wsfl.attack || wsfl.lowerWeapon ) { + return SRESULT_STAGE ( STAGE_END ); + } + Attack ( ); + return SRESULT_WAIT; + + case STAGE_END: + PlayAnim ( ANIMCHANNEL_ALL, "attack_end", parms.blendFrames ); + StopBlade ( ); + StartSound( "snd_spin_down", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( wsfl.attack || AnimDone ( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + PostState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/game/weapon/WeaponGrenadeLauncher.cpp b/src/game/weapon/WeaponGrenadeLauncher.cpp new file mode 100644 index 0000000..dd47d41 --- /dev/null +++ b/src/game/weapon/WeaponGrenadeLauncher.cpp @@ -0,0 +1,226 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGrenadeLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGrenadeLauncher ); + + rvWeaponGrenadeLauncher ( void ); + + virtual void Spawn ( void ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + const char* GetFireAnim() const { return (!AmmoInClip()) ? "fire_empty" : "fire"; } + const char* GetIdleAnim() const { return (!AmmoInClip()) ? "idle_empty" : "idle"; } + + CLASS_STATES_PROTOTYPE ( rvWeaponGrenadeLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGrenadeLauncher ) +END_CLASS + +/* +================ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher +================ +*/ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::Spawn +================ +*/ +void rvWeaponGrenadeLauncher::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponGrenadeLauncher::PreSave +================ +*/ +void rvWeaponGrenadeLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::PostSave +================ +*/ +void rvWeaponGrenadeLauncher::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGrenadeLauncher ) + STATE ( "Idle", rvWeaponGrenadeLauncher::State_Idle) + STATE ( "Fire", rvWeaponGrenadeLauncher::State_Fire ) + STATE ( "Reload", rvWeaponGrenadeLauncher::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponGrenadeLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, GetIdleAnim(), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, GetFireAnim(), 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/game/weapon/WeaponHyperblaster.cpp b/src/game/weapon/WeaponHyperblaster.cpp new file mode 100644 index 0000000..b91cb51 --- /dev/null +++ b/src/game/weapon/WeaponHyperblaster.cpp @@ -0,0 +1,319 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int HYPERBLASTER_SPARM_BATTERY = 6; +const int HYPERBLASTER_SPIN_SPEED = 300; + +class rvWeaponHyperblaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponHyperblaster ); + + rvWeaponHyperblaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + jointHandle_t jointBatteryView; + bool spinning; + + void SpinUp ( void ); + void SpinDown ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponHyperblaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponHyperblaster ) +END_CLASS + +/* +================ +rvWeaponHyperblaster::rvWeaponHyperblaster +================ +*/ +rvWeaponHyperblaster::rvWeaponHyperblaster ( void ) { +} + +/* +================ +rvWeaponHyperblaster::Spawn +================ +*/ +void rvWeaponHyperblaster::Spawn ( void ) { + jointBatteryView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_battery" ) ); + spinning = false; + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponHyperblaster::Save +================ +*/ +void rvWeaponHyperblaster::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint ( jointBatteryView ); + savefile->WriteBool ( spinning ); +} + +/* +================ +rvWeaponHyperblaster::Restore +================ +*/ +void rvWeaponHyperblaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint ( jointBatteryView ); + savefile->ReadBool ( spinning ); +} + +/* +================ +rvWeaponHyperBlaster::PreSave +================ +*/ +void rvWeaponHyperblaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, false ); + StopSound( SND_CHANNEL_BODY, false ); + StopSound( SND_CHANNEL_ITEM, false ); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponHyperBlaster::PostSave +================ +*/ +void rvWeaponHyperblaster::PostSave ( void ) { +} + +/* +================ +rvWeaponHyperblaster::SpinUp +================ +*/ +void rvWeaponHyperblaster::SpinUp ( void ) { + if ( spinning ) { + return; + } + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,HYPERBLASTER_SPIN_SPEED,0), gameLocal.time, 50 ); + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spin", SND_CHANNEL_BODY2, 0, false, NULL ); + spinning = true; +} + +/* +================ +rvWeaponHyperblaster::SpinDown +================ +*/ +void rvWeaponHyperblaster::SpinDown ( void ) { + if ( !spinning ) { + return; + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spindown", SND_CHANNEL_BODY2, 0, false, NULL ); + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,0,0), gameLocal.time, 500 ); + } + + spinning = false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponHyperblaster ) + STATE ( "Idle", rvWeaponHyperblaster::State_Idle) + STATE ( "Fire", rvWeaponHyperblaster::State_Fire ) + STATE ( "Reload", rvWeaponHyperblaster::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponHyperblaster::State_Idle +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + SpinDown ( ); + + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Fire +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SpinUp ( ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( (!wsfl.attack || !AmmoInClip() || wsfl.lowerWeapon) && AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Reload +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SpinDown ( ); + + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 0 ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/game/weapon/WeaponLightningGun.cpp b/src/game/weapon/WeaponLightningGun.cpp new file mode 100644 index 0000000..72f838e --- /dev/null +++ b/src/game/weapon/WeaponLightningGun.cpp @@ -0,0 +1,997 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" +#include "../ai/AI_Manager.h" + +const int LIGHTNINGGUN_NUM_TUBES = 3; +const int LIGHTNINGGUN_MAX_PATHS = 3; + +const idEventDef EV_Lightninggun_RestoreHum( "", "" ); + +class rvLightningPath { +public: + idEntityPtr target; + idVec3 origin; + idVec3 normal; + rvClientEffectPtr trailEffect; + rvClientEffectPtr impactEffect; + + void StopEffects ( void ); + void UpdateEffects ( const idVec3& from, const idDict& dict ); + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvWeaponLightningGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponLightningGun ); + + rvWeaponLightningGun( void ); + ~rvWeaponLightningGun( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + virtual void ClientStale ( void ); + + void PreSave ( void ); + void PostSave ( void ); + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + bool NoFireWhileSwitching( void ) const { return true; } + +protected: + + void UpdateTubes ( void ); + + // Tube effects + rvClientEntityPtr tubeEffects[LIGHTNINGGUN_NUM_TUBES]; + idInterpolate tubeOffsets[LIGHTNINGGUN_NUM_TUBES]; + jointHandle_t tubeJoints[LIGHTNINGGUN_NUM_TUBES]; + float tubeMaxOffset; + float tubeThreshold; + int tubeTime; + + rvClientEntityPtr trailEffectView; + + int nextCrawlTime; + + float range; + jointHandle_t spireJointView; + jointHandle_t chestJointView; + + + rvLightningPath currentPath; + + // Chain lightning mod + idList chainLightning; + idVec3 chainLightningRange; + +private: + + void Attack ( idEntity* ent, const idVec3& dir, float power = 1.0f ); + + void UpdateChainLightning ( void ); + void StopChainLightning ( void ); + void UpdateEffects ( const idVec3& origin ); + void UpdateTrailEffect ( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view = false ); + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponLightningGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponLightningGun ) +EVENT( EV_Lightninggun_RestoreHum, rvWeaponLightningGun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponLightningGun::rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::rvWeaponLightningGun( void ) { +} + +/* +================ +rvWeaponLightningGun::~rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::~rvWeaponLightningGun( void ) { + int i; + + if ( trailEffectView ) { + trailEffectView->Stop( ); + } + currentPath.StopEffects( ); + StopChainLightning( ); + + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop( ); + } + } +} + +/* +================ +rvWeaponLightningGun::Spawn +================ +*/ +void rvWeaponLightningGun::Spawn( void ) { + int i; + + trailEffectView = NULL; + nextCrawlTime = 0; + + chainLightning.Clear( ); + + // get hitscan range for our firing + range = weaponDef->dict.GetFloat( "range", "10000" ); + + // Initialize tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeJoints[i] = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint_tube_%d",i), "" ) ); + tubeOffsets[i].Init ( gameLocal.time, 0, 0, 0 ); + } + + // Cache the max ammo for the weapon and the max tube offset + tubeMaxOffset = spawnArgs.GetFloat ( "tubeoffset" ); + tubeThreshold = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ) / (float)LIGHTNINGGUN_NUM_TUBES; + tubeTime = SEC2MS ( spawnArgs.GetFloat ( "tubeTime", ".25" ) ); + + spireJointView = viewModel->GetAnimator ( )->GetJointHandle ( "spire_1" ); + + if( gameLocal.GetLocalPlayer()) { + chestJointView = gameLocal.GetLocalPlayer()->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_hideGun_flash" ) ); + } else { + chestJointView = spireJointView; + } + + chainLightningRange = spawnArgs.GetVec2( "chainLightningRange", "150 300" ); + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponLightningGun::Save +================ +*/ +void rvWeaponLightningGun::Save ( idSaveGame* savefile ) const { + int i; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Save ( savefile ); + savefile->WriteInterpolate ( tubeOffsets[i] ); + savefile->WriteJoint ( tubeJoints[i] ); + } + savefile->WriteFloat ( tubeMaxOffset ); + savefile->WriteFloat ( tubeThreshold ); + savefile->WriteInt ( tubeTime ); + + // General + trailEffectView.Save ( savefile ); + savefile->WriteInt ( nextCrawlTime ); + savefile->WriteFloat ( range ); + savefile->WriteJoint ( spireJointView ); + savefile->WriteJoint ( chestJointView ); + + currentPath.Save ( savefile ); + + // Chain Lightning mod + savefile->WriteInt ( chainLightning.Num() ); + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].Save ( savefile ); + } + savefile->WriteVec3 ( chainLightningRange ); +} + +/* +================ +rvWeaponLightningGun::Restore +================ +*/ +void rvWeaponLightningGun::Restore ( idRestoreGame* savefile ) { + int i; + int num; + float f; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Restore ( savefile ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartTime ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetDuration ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartValue ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetEndValue ( f ); + savefile->ReadJoint ( tubeJoints[i] ); + } + savefile->ReadFloat ( tubeMaxOffset ); + savefile->ReadFloat ( tubeThreshold ); + savefile->ReadInt ( tubeTime ); + + // General + trailEffectView.Restore ( savefile ); + + savefile->ReadInt ( nextCrawlTime ); + savefile->ReadFloat ( range ); + savefile->ReadJoint ( spireJointView ); + savefile->ReadJoint ( chestJointView ); + + currentPath.Restore ( savefile ); + + // Chain lightning mod + savefile->ReadInt ( num ); + chainLightning.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + chainLightning[i].Restore ( savefile ); + } + savefile->ReadVec3 ( chainLightningRange ); + + +} + +/* +================ +rvWeaponLightningGun::Think +================ +*/ +void rvWeaponLightningGun::Think ( void ) { + trace_t tr; + + rvWeapon::Think(); + + UpdateTubes(); + + // If no longer firing or out of ammo then nothing to do in the think + if ( !wsfl.attack || !IsReady() || !AmmoAvailable() ) { + if ( trailEffectView ) { + trailEffectView->Stop ( ); + trailEffectView = NULL; + } + + currentPath.StopEffects(); + StopChainLightning(); + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// jshepard: allow projectile hits + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + (MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE), owner ); +// RAVEN END + // Calculate the direction of the lightning effect using the barrel joint of the weapon + // and the end point of the trace + idVec3 origin; + idMat3 axis; + + //fire from chest if show gun models is off. + if( !cvarSystem->GetCVarBool("ui_showGun")) { + GetGlobalJointTransform( true, chestJointView, origin, axis ); + } else { + GetGlobalJointTransform( true, barrelJointView, origin, axis ); + } + + // Cache the target we are hitting + currentPath.origin = tr.endpos; + currentPath.normal = tr.c.normal; + currentPath.target = gameLocal.entities[tr.c.entityNum]; + + UpdateChainLightning(); + + UpdateEffects( origin ); + + MuzzleFlash(); + + // Inflict damage on all targets being attacked + if ( !gameLocal.isClient && gameLocal.time >= nextAttackTime ) { + int i; + float power = 1.0f; + idVec3 dir; + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + + dir = tr.endpos - origin; + dir.Normalize ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( currentPath.target, dir, power ); + for ( i = 0; i < chainLightning.Num(); i ++, power *= 0.75f ) { + Attack ( chainLightning[i].target, chainLightning[i].normal, power ); + } + + statManager->WeaponFired( owner, owner->GetCurrentWeapon(), chainLightning.Num() + 1 ); + } + + // Play the lightning crawl effect every so often when doing damage + if ( gameLocal.time > nextCrawlTime ) { + nextCrawlTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat ( "crawlDelay", ".3" )); + } +} + +/* +================ +rvWeaponLightningGun::Attack +================ +*/ +void rvWeaponLightningGun::Attack ( idEntity* ent, const idVec3& dir, float power ) { + // Double check + if ( !ent || !ent->fl.takedamage ) { + return; + } + + // Start a lightning crawl effect every so often + // we don't synchronize it, so let's not show it in multiplayer for a listen host. also fixes seeing it on the host from other instances + if ( !gameLocal.isMultiplayer && gameLocal.time > nextCrawlTime ) { + if ( ent->IsType( idActor::GetClassType() ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( weaponDef->dict, "fx_crawl" ), ent, SEC2MS( spawnArgs.GetFloat ( "crawlTime", ".2" ) ) ); + effect->Play( gameLocal.time, false ); + } + } + +// RAVEN BEGIN +// mekberg: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, owner->GetCurrentWeapon() ); + } +// RAVEN END + ent->Damage( owner, owner, dir, spawnArgs.GetString ( "def_damage" ), power * owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ), 0 ); +} + +/* +================ +rvWeaponLightningGun::UpdateChainLightning +================ +*/ +void rvWeaponLightningGun::UpdateChainLightning ( void ) { + int i; + rvLightningPath* parent; + rvLightningPath* path; + idActor* target; + + // Chain lightning not enabled + if ( !chainLightningRange[0] ) { + return; + } + + // Need to have a primary target that is not on the same team for chain lightning to work + path = ¤tPath; + target = dynamic_cast(path->target.GetEntity()); + if ( !target || !target->health || target->team == owner->team ) { + StopChainLightning ( ); + return; + } + + currentPath.target->fl.takedamage = false; + + // Look through the chain lightning list and remove any paths that are no longer valid due + // to their range or visibility + for ( i = 0; i < chainLightning.Num(); i ++ ) { + parent = path; + path = &chainLightning[i]; + target = dynamic_cast(path->target.GetEntity()); + + // If the entity isnt valid anymore or is dead then remove it from the list + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Choose a destination origin the chain lightning path target + path->origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + path->origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); + path->normal = path->origin - parent->origin; + path->normal.Normalize(); + + // Make sure the entity is still within range of its parent + if ( (path->origin - parent->origin).LengthSqr ( ) > Square ( chainLightningRange[1] ) ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Trace to make sure we can still hit them + trace_t tr; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, parent->origin, path->origin, MASK_SHOT_RENDERMODEL, parent->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + path->origin = tr.endpos; + + // Temporarily disable taking damage to flag this entity is used + target->fl.takedamage = false; + } + + // Start path at the end of the current path + if ( chainLightning.Num () ) { + path = &chainLightning[chainLightning.Num()-1]; + } else { + path = ¤tPath; + } + + // Cap the number of chain lightning jumps + while ( chainLightning.Num ( ) + 1 < LIGHTNINGGUN_MAX_PATHS ) { + for ( target = aiManager.GetEnemyTeam ( (aiTeam_t)owner->team ); target; target = target->teamNode.Next() ) { + // Must be a valid entity that takes damage to chain lightning too + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + continue; + } + // Must be within starting chain path range + if ( (target->GetPhysics()->GetOrigin() - path->target->GetPhysics()->GetOrigin()).LengthSqr() > Square ( chainLightningRange[0] ) ) { + continue; + } + + // Make sure we can trace to the target from the current path + trace_t tr; + idVec3 origin; + origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, path->origin, origin, MASK_SHOT_RENDERMODEL, path->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + continue; + } + + path = &chainLightning.Alloc ( ); + path->target = target; + path->normal = tr.endpos - path->origin; + path->normal.Normalize(); + path->origin = tr.endpos; + + // Flag this entity to ensure it is skipped + target->fl.takedamage = false; + break; + } + // Found nothing? just break out early + if ( !target ) { + break; + } + } + + // Reset the take damage flag + currentPath.target->fl.takedamage = true; + for ( i = chainLightning.Num() - 1; i >= 0; i -- ) { + chainLightning[i].target->fl.takedamage = true; + } +} + +/* +================ +rvWeaponLightningGun::UpdateEffects +================ +*/ +void rvWeaponLightningGun::UpdateEffects( const idVec3& origin ) { + int i; + rvLightningPath* parent; + idVec3 dir; + + // Main path (world effects) + currentPath.UpdateEffects ( origin, weaponDef->dict ); + if ( currentPath.trailEffect ) { + currentPath.trailEffect->GetRenderEffect()->suppressSurfaceInViewID = owner->entityNumber + 1; + } + + // In view trail effect + dir = currentPath.origin - origin; + dir.Normalize(); + if ( !trailEffectView ) { + trailEffectView = gameLocal.PlayEffect ( gameLocal.GetEffect ( weaponDef->dict, "fx_trail" ), origin, dir.ToMat3(), true, currentPath.origin ); + } else { + trailEffectView->SetOrigin( origin ); + trailEffectView->SetAxis( dir.ToMat3() ); + trailEffectView->SetEndOrigin( currentPath.origin ); + } + if ( trailEffectView ) { + trailEffectView->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1;; + } + + if ( !currentPath.target ) { + return; + } + + // Chain lightning effects + parent = ¤tPath; + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].UpdateEffects( parent->origin, weaponDef->dict ); + parent = &chainLightning[i]; + } +} + +/* +================ +rvWeaponLightningGun::UpdateTrailEffect +================ +*/ +void rvWeaponLightningGun::UpdateTrailEffect( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view ) { + idVec3 dir; + dir = end - start; + dir.Normalize(); + + if ( !effect ) { + effect = gameLocal.PlayEffect( gameLocal.GetEffect( weaponDef->dict, view ? "fx_trail" : "fx_trail_world" ), start, dir.ToMat3(), true, end ); + } else { + effect->SetOrigin( start ); + effect->SetAxis( dir.ToMat3() ); + effect->SetEndOrigin( end ); + } +} + +/* +================ +rvWeaponLightningGun::StopChainLightning +================ +*/ +void rvWeaponLightningGun::StopChainLightning( void ) { + int i; + + if ( !chainLightning.Num( ) ) { + return; + } + + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].StopEffects( ); + } + + chainLightning.Clear( ); +} + +/* +================ +rvWeaponLightningGun::ClientStale +================ +*/ +void rvWeaponLightningGun::ClientStale( void ) { + rvWeapon::ClientStale( ); + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + + currentPath.StopEffects( ); +} + +/* +================ +rvWeaponLightningGun::PreSave +================ +*/ +void rvWeaponLightningGun::PreSave( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_BODY2, 0); + StopSound( SND_CHANNEL_BODY3, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + + viewModel->StopSound( SND_CHANNEL_ANY, false ); + viewModel->StopSound( SND_CHANNEL_BODY, 0); + + worldModel->StopSound( SND_CHANNEL_ANY, false ); + worldModel->StopSound( SND_CHANNEL_BODY, 0); + + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + for ( int i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Event_Remove(); + } + } + currentPath.StopEffects( ); + +} + +/* +================ +rvWeaponLightningGun::PostSave +================ +*/ +void rvWeaponLightningGun::PostSave( void ) { + //restore the humming + PostEventMS( &EV_Lightninggun_RestoreHum, 10 ); +} + +/* +================ +rvWeaponLightningGun::UpdateTubes +================ +*/ +void rvWeaponLightningGun::UpdateTubes( void ) { + idAnimator* animator; + animator = viewModel->GetAnimator ( ); + assert ( animator ); + + int i; + float ammo; + + ammo = AmmoAvailable ( ); + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + float offset; + if ( ammo > tubeThreshold * i ) { + offset = tubeMaxOffset; + + if ( !tubeEffects[i] ) { + tubeEffects[i] = viewModel->PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_tube" ), tubeJoints[i], vec3_origin, mat3_identity, true ); + if( tubeEffects[i] ) { + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } else { + offset = 0; + + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop ( ); + tubeEffects[i] = NULL; + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + // Attenuate the tube effect to how much ammo is left in the tube + if ( tubeEffects[i] ) { + tubeEffects[i]->Attenuate ( (ammo - tubeThreshold * (float)i) / tubeThreshold ); + } + + if ( offset > tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (1.0f - (current/tubeMaxOffset)) * (float)tubeTime, current, offset ); + } else if ( offset < tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (current/tubeMaxOffset) * (float)tubeTime, current, offset ); + } + + animator->SetJointPos ( tubeJoints[i], JOINTMOD_LOCAL, idVec3(tubeOffsets[i].GetCurrentValue(gameLocal.time),0,0) ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponLightningGun ) + STATE ( "Raise", rvWeaponLightningGun::State_Raise ) + STATE ( "Lower", rvWeaponLightningGun::State_Lower ) + STATE ( "Idle", rvWeaponLightningGun::State_Idle) + STATE ( "Fire", rvWeaponLightningGun::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponLightningGun::State_Raise +================ +*/ +stateResult_t rvWeaponLightningGun::State_Raise( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus( WP_RISING ); + viewModel->SetShaderParm( 6, 1 ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT ); + + // Wait for the weapon to finish raising and allow it to be cancelled by + // lowering the weapon + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Lower +================ +*/ +stateResult_t rvWeaponLightningGun::State_Lower( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Idle +================ +*/ +stateResult_t rvWeaponLightningGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time > nextAttackTime && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Fire +================ +*/ +stateResult_t rvWeaponLightningGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACKLOOP, + STAGE_DONE, + STAGE_DONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + StartSound( "snd_fire_stereo", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_fire_loop", SND_CHANNEL_BODY2, 0, false, NULL ); + + viewModel->SetShaderParm( 6, 0 ); + + viewModel->PlayEffect( "fx_spire", spireJointView, true ); + viewModel->PlayEffect( "fx_flash", barrelJointView, true ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict,"fx_flash_world"), flashJointWorld, vec3_origin, mat3_identity, true ); + } + + PlayAnim( ANIMCHANNEL_ALL, "shoot_start", parms.blendFrames ); + return SRESULT_STAGE( STAGE_ATTACKLOOP ); + + case STAGE_ATTACKLOOP: + if ( !wsfl.attack || wsfl.lowerWeapon || !AmmoAvailable ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + PlayCycle( ANIMCHANNEL_ALL, "shoot_loop", 0 ); + if ( !gameLocal.isMultiplayer + && owner == gameLocal.GetLocalPlayer() ) { + owner->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + 500), 2.0f ); + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + StopSound( SND_CHANNEL_BODY2, false ); + + viewModel->StopEffect( "fx_spire" ); + viewModel->StopEffect( "fx_flash" ); + if ( worldModel ) { + worldModel->StopEffect( gameLocal.GetEffect( weaponDef->dict, "fx_flash_world" ) ); + } + viewModel->SetShaderParm( 6, 1 ); + + PlayAnim( ANIMCHANNEL_ALL, "shoot_end", 0 ); + return SRESULT_STAGE( STAGE_DONEWAIT ); + + case STAGE_DONEWAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( !wsfl.lowerWeapon && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponLightningGun::Event_RestoreHum +================ +*/ +void rvWeaponLightningGun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); +} + +/* +=============================================================================== + + rvLightningPath + +=============================================================================== +*/ + +/* +================ +rvLightningPath::StopEffects +================ +*/ +void rvLightningPath::StopEffects( void ) { + if ( trailEffect ) { + trailEffect->Stop( ); + trailEffect->Event_Remove( ); + trailEffect = NULL; + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } +} + +/* +================ +rvLightningPath::UpdateEffects +================ +*/ +void rvLightningPath::UpdateEffects ( const idVec3& from, const idDict& dict ) { + idVec3 dir; + dir = origin - from; + dir.Normalize(); + + // Trail effect + if ( !trailEffect ) { + trailEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_trail_world" ), from, dir.ToMat3(), true, origin ); + } else { + trailEffect->SetOrigin ( from ); + trailEffect->SetAxis ( dir.ToMat3() ); + trailEffect->SetEndOrigin ( origin ); + } + + // Impact effect + if ( !target || target.GetEntityNum ( ) == ENTITYNUM_NONE ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } + } else { + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_impact" ), origin, normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( origin ); + impactEffect->SetAxis ( normal.ToMat3 ( ) ); + } + } +} + +/* +================ +rvLightningPath::Save +================ +*/ +void rvLightningPath::Save( idSaveGame* savefile ) const { + target.Save( savefile ); + savefile->WriteVec3( origin ); + savefile->WriteVec3( normal ); + trailEffect.Save( savefile ); + impactEffect.Save( savefile ); +} + +/* +================ +rvLightningPath::Restore +================ +*/ +void rvLightningPath::Restore( idRestoreGame* savefile ) { + target.Restore( savefile ); + savefile->ReadVec3( origin ); + savefile->ReadVec3( normal ); + trailEffect.Restore( savefile ); + impactEffect.Restore( savefile ); +} diff --git a/src/game/weapon/WeaponMachinegun.cpp b/src/game/weapon/WeaponMachinegun.cpp new file mode 100644 index 0000000..5c9217e --- /dev/null +++ b/src/game/weapon/WeaponMachinegun.cpp @@ -0,0 +1,351 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponMachinegun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponMachinegun ); + + rvWeaponMachinegun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + float spreadZoom; + bool fireHeld; + + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponMachinegun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponMachinegun ) +END_CLASS + +/* +================ +rvWeaponMachinegun::rvWeaponMachinegun +================ +*/ +rvWeaponMachinegun::rvWeaponMachinegun ( void ) { +} + +/* +================ +rvWeaponMachinegun::Spawn +================ +*/ +void rvWeaponMachinegun::Spawn ( void ) { + spreadZoom = spawnArgs.GetFloat ( "spreadZoom" ); + fireHeld = false; + + SetState ( "Raise", 0 ); + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponMachinegun::Save +================ +*/ +void rvWeaponMachinegun::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( spreadZoom ); + savefile->WriteBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::Restore +================ +*/ +void rvWeaponMachinegun::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( spreadZoom ); + savefile->ReadBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::PreSave +================ +*/ +void rvWeaponMachinegun::PreSave ( void ) { +} + +/* +================ +rvWeaponMachinegun::PostSave +================ +*/ +void rvWeaponMachinegun::PostSave ( void ) { +} + + +/* +================ +rvWeaponMachinegun::Think +================ +*/ +void rvWeaponMachinegun::Think() +{ + rvWeapon::Think(); + if ( zoomGui && owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat( "playerYaw", playerViewAxis.ToAngles().yaw ); + } +} + +/* +================ +rvWeaponMachinegun::UpdateFlashlight +================ +*/ +bool rvWeaponMachinegun::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponMachinegun::Flashlight +================ +*/ +void rvWeaponMachinegun::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponMachinegun ) + STATE ( "Idle", rvWeaponMachinegun::State_Idle) + STATE ( "Fire", rvWeaponMachinegun::State_Fire ) + STATE ( "Reload", rvWeaponMachinegun::State_Reload ) + STATE ( "Flashlight", rvWeaponMachinegun::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponMachinegun::State_Idle +================ +*/ +stateResult_t rvWeaponMachinegun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + + if ( fireHeld && !wsfl.attack ) { + fireHeld = false; + } + if ( !clipSize ) { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Fire +================ +*/ +stateResult_t rvWeaponMachinegun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spreadZoom, 0, 1.0f ); + fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !fireHeld && wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Reload +================ +*/ +stateResult_t rvWeaponMachinegun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponMachinegun::State_Flashlight +================ +*/ +stateResult_t rvWeaponMachinegun::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/src/game/weapon/WeaponNailgun.cpp b/src/game/weapon/WeaponNailgun.cpp new file mode 100644 index 0000000..78cf5e7 --- /dev/null +++ b/src/game/weapon/WeaponNailgun.cpp @@ -0,0 +1,953 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" + +// Available drum speeds +const int NAILGUN_DRUMSPEED_STOPPED = 0; +const int NAILGUN_DRUMSPEED_SLOW = 1; +const int NAILGUN_DRUMSPEED_FAST = 2; + +// Spinup and spindown times +const int NAILGUN_SPINDOWN_TIME = 1000; +const int NAILGUN_SPINUP_TIME = 1000; + +// Nailgun shader parms +const int NAILGUN_SPARM_PLAYLEADIN = 7; + +// Nailgun weapon modifications +const int NAILGUN_MOD_ROF_AMMO = BIT(0); // power mod combines Rate of fire and ammoo +const int NAILGUN_MOD_SEEK = BIT(1); +//const int NAILGUN_MOD_AMMO = BIT(2); + +const int NAILGUN_SPIN_SNDCHANNEL = SND_CHANNEL_BODY2; + +class rvWeaponNailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponNailgun ); + + rvWeaponNailgun ( void ); + ~rvWeaponNailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idEntityPtr guideEnt; + int guideTime; + int guideStartTime; + rvClientEntityPtr guideEffect; + bool guideLocked; + float guideRange; + int guideHoldTime; + int guideAquireTime; + + jointHandle_t jointDrumView; + jointHandle_t jointPinsView; + jointHandle_t jointSteamRightView; + jointHandle_t jointSteamLeftView; + + jointHandle_t jointGuideEnt; + + int drumSpeed; + int drumSpeedIdeal; + float drumMultiplier; + + virtual void OnLaunchProjectile ( idProjectile* proj ); + +private: + + void UpdateGuideStatus ( float range = 0.0f ); + void CancelGuide ( void ); + bool DrumSpin ( int speed, int blendFrames ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_DrumSpinDown ( const stateParms_t& parms ); + stateResult_t State_DrumSpinUp ( const stateParms_t& parms ); + + stateResult_t Frame_ClaspOpen ( const stateParms_t& parms ); + stateResult_t Frame_ClaspClose ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponNailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponNailgun ) +END_CLASS + +/* +================ +rvWeaponNailgun::rvWeaponNailgun +================ +*/ +rvWeaponNailgun::rvWeaponNailgun ( void ) { +} + +/* +================ +rvWeaponNailgun::~rvWeaponNailgun +================ +*/ +rvWeaponNailgun::~rvWeaponNailgun ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } + if( viewModel ) { + viewModel->StopSound( NAILGUN_SPIN_SNDCHANNEL, false ); + } +} + + +/* +================ +rvWeaponNailgun::Spawn +================ +*/ +void rvWeaponNailgun::Spawn ( void ) { + spawnArgs.GetFloat ( "lockRange", "1000", guideRange ); + guideHoldTime = SEC2MS ( spawnArgs.GetFloat ( "lockHoldTime", "10" ) ); + guideAquireTime = SEC2MS ( spawnArgs.GetFloat ( "lockAquireTime", ".1" ) ); + + jointDrumView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_drum" ) ); + jointPinsView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_pins" ) ); + jointSteamRightView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamRight" ) ); + jointSteamLeftView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamLeft" ) ); + + jointGuideEnt = INVALID_JOINT; + + drumSpeed = NAILGUN_DRUMSPEED_STOPPED; + drumSpeedIdeal = drumSpeed; + drumMultiplier = spawnArgs.GetFloat ( "drumSpeed" ); + + ExecuteState ( "ClaspClose" ); + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponNailgun::Save +================ +*/ +void rvWeaponNailgun::Save ( idSaveGame *savefile ) const { + guideEnt.Save( savefile ); + savefile->WriteInt( guideTime ); + savefile->WriteInt( guideStartTime ); + guideEffect.Save( savefile ); + savefile->WriteBool ( guideLocked ); + savefile->WriteFloat ( guideRange ); + savefile->WriteInt ( guideHoldTime ); + savefile->WriteInt ( guideAquireTime ); + + savefile->WriteJoint ( jointDrumView ); + savefile->WriteJoint ( jointPinsView ); + savefile->WriteJoint ( jointSteamRightView ); + savefile->WriteJoint ( jointSteamLeftView ); + savefile->WriteJoint ( jointGuideEnt ); + + savefile->WriteInt ( drumSpeed ); + savefile->WriteInt ( drumSpeedIdeal ); + savefile->WriteFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::Restore +================ +*/ +void rvWeaponNailgun::Restore ( idRestoreGame *savefile ) { + guideEnt.Restore( savefile ); + savefile->ReadInt( guideTime ); + savefile->ReadInt( guideStartTime ); + guideEffect.Restore( savefile ); + savefile->ReadBool ( guideLocked ); + savefile->ReadFloat ( guideRange ); + savefile->ReadInt ( guideHoldTime ); + savefile->ReadInt ( guideAquireTime ); + + savefile->ReadJoint ( jointDrumView ); + savefile->ReadJoint ( jointPinsView ); + savefile->ReadJoint ( jointSteamRightView ); + savefile->ReadJoint ( jointSteamLeftView ); + savefile->ReadJoint ( jointGuideEnt ); + + + savefile->ReadInt ( drumSpeed ); + savefile->ReadInt ( drumSpeedIdeal ); + savefile->ReadFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::PreSave +================ +*/ +void rvWeaponNailgun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + + //remove the guide gui cursor if there is one. + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect->Event_Remove(); + guideEffect = NULL; + } +} + +/* +================ +rvWeaponNailgun::PostSave +================ +*/ +void rvWeaponNailgun::PostSave ( void ) { + + //restore sounds-- but which one? + if( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } else if( drumSpeed == NAILGUN_DRUMSPEED_SLOW ) { + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } + + //the guide gui effect will restore itself naturally +} + +/* +================ +rvWeaponNailgun::CancelGuide +================ +*/ +void rvWeaponNailgun::CancelGuide ( void ) { + if ( zoomGui && guideEnt ) { + zoomGui->HandleNamedEvent ( "lockStop" ); + } + + guideEnt = NULL; + guideLocked = false; + jointGuideEnt = INVALID_JOINT; + UpdateGuideStatus ( ); +} + +/* +================ +rvWeaponNailgun::UpdateGuideStatus +================ +*/ +void rvWeaponNailgun::UpdateGuideStatus ( float range ) { + // Update the zoom GUI variables + if ( zoomGui ) { + float lockStatus; + if ( guideEnt ) { + if ( guideLocked ) { + lockStatus = 1.0f; + } else { + lockStatus = Min((float)(gameLocal.time - guideStartTime)/(float)guideTime, 1.0f); + } + } else { + lockStatus = 0.0f; + } + + if ( owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat ( "lockStatus", lockStatus ); + zoomGui->SetStateFloat ( "playerYaw", playerViewAxis.ToAngles().yaw ); + } + + if ( guideEnt ) { + idVec3 diff; + diff = guideEnt->GetPhysics()->GetOrigin ( ) - playerViewOrigin; + diff.NormalizeFast ( ); + zoomGui->SetStateFloat ( "lockYaw", idMath::AngleDelta ( diff.ToAngles ( ).yaw, playerViewAxis.ToAngles().yaw ) ); + zoomGui->SetStateString ( "lockRange", va("%4.2f", range) ); + zoomGui->SetStateString ( "lockTime", va("%02d", (int)MS2SEC(guideStartTime + guideTime - gameLocal.time) + 1) ); + } + } + + // Update the guide effect + if ( guideEnt ) { + idVec3 eyePos = static_cast(guideEnt.GetEntity())->GetEyePosition(); + if( jointGuideEnt == INVALID_JOINT ) { + eyePos += guideEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + } else { + idMat3 jointAxis; + idVec3 jointLoc; + static_cast(guideEnt.GetEntity())->GetJointWorldTransform( jointGuideEnt, gameLocal.GetTime(),jointLoc,jointAxis ); + eyePos += jointLoc; + } + eyePos *= 0.5f; + if ( guideEffect ) { + guideEffect->SetOrigin ( eyePos ); + guideEffect->SetAxis ( playerViewAxis.Transpose() ); + } else { + guideEffect = gameLocal.PlayEffect ( + gameLocal.GetEffect( spawnArgs, guideLocked ? "fx_guide" : "fx_guidestart" ), + eyePos, playerViewAxis.Transpose(), true, vec3_origin, false ); + if ( guideEffect ) { + guideEffect->GetRenderEffect()->weaponDepthHackInViewID = owner->entityNumber + 1; + guideEffect->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1; + } + } + } else { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + } +} + +/* +================ +rvWeaponNailgun::Think +================ +*/ +void rvWeaponNailgun::Think ( void ) { + idEntity* ent; + trace_t tr; + + // Let the real weapon think first + rvWeapon::Think ( ); + + // If no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + // If the zoom button isnt down then dont update the lock + if ( !wsfl.zoom || IsReloading ( ) || IsHolstered ( ) ) { + CancelGuide ( ); + return; + } + + // Dont update the target if the current target is still alive, unhidden and we have already locked + if ( guideEnt && guideEnt->health > 0 && !guideEnt->IsHidden() && guideLocked ) { + float range; + range = (guideEnt->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast(); + if ( range > guideRange || gameLocal.time > guideStartTime + guideTime ) { + CancelGuide ( ); + } else { + UpdateGuideStatus ( range ); + return; + } + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_BOUNDINGBOX, owner ); +// RAVEN END + + if ( tr.fraction >= 1.0f ) { + CancelGuide( ); + return; + } + + ent = gameLocal.entities[tr.c.entityNum]; + + //if we're using a target nailable... + if( ent->IsType ( rvTarget_Nailable::GetClassType() ) ) { + const char* jointName = ent->spawnArgs.GetString("lock_joint"); + ent = ent->targets[ 0 ].GetEntity(); + if( !ent) { + CancelGuide( ); + return; + } + + if( ent->GetAnimator() ) { + jointGuideEnt = ent->GetAnimator()->GetJointHandle( jointName ); + } + } + + + if ( !ent->IsType ( idActor::GetClassType() ) ) { + CancelGuide( ); + return; + } + if ( gameLocal.GetLocalPlayer() && static_cast(ent)->team == gameLocal.GetLocalPlayer()->team ) { + CancelGuide( ); + return; + } + + if ( guideEnt != ent ) { + guideStartTime = gameLocal.time; + guideTime = guideAquireTime; + guideEnt = ent; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockStart" ); + } + } else if ( gameLocal.time > guideStartTime + guideTime ) { + // Stop the guide effect since it was just the guide_Start effect + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + guideLocked = true; + guideTime = guideHoldTime; + guideStartTime = gameLocal.time; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockAquired" ); + } + } + + UpdateGuideStatus ( (ent->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast() ); +} + +/* +================ +rvWeaponNailgun::OnLaunchProjectile +================ +*/ +void rvWeaponNailgun::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + idGuidedProjectile* guided; + guided = dynamic_cast(proj); + if ( guided ) { + guided->GuideTo ( guideEnt, jointGuideEnt ); + } +} + +/* +================ +rvWeaponNailgun::DrumSpin + +Set the drum spin speed +================ +*/ +bool rvWeaponNailgun::DrumSpin ( int speed, int blendFrames ) { + // Dont bother if the drum is already spinning at the desired speed + if ( drumSpeedIdeal == speed ) { + return false; + } + + drumSpeedIdeal = speed; + + switch ( speed ) { + case NAILGUN_DRUMSPEED_STOPPED: + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,0), gameLocal.time, 250 ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,0), gameLocal.time, 250 ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_SLOW: + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,45), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-35), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_FAST: + // Start the barrel spinning faster + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,360.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-300.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + + PostState ( "DrumSpinUp", blendFrames ); + return true; + } + + drumSpeed = drumSpeedIdeal; + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponNailgun ) + STATE ( "Raise", rvWeaponNailgun::State_Raise ) + STATE ( "Lower", rvWeaponNailgun::State_Lower ) + STATE ( "Idle", rvWeaponNailgun::State_Idle) + STATE ( "Fire", rvWeaponNailgun::State_Fire ) + STATE ( "Reload", rvWeaponNailgun::State_Reload ) + STATE ( "DrumSpinDown", rvWeaponNailgun::State_DrumSpinDown ) + STATE ( "DrumSpinUp", rvWeaponNailgun::State_DrumSpinUp ) + + STATE ( "ClaspOpen", rvWeaponNailgun::Frame_ClaspOpen ) + STATE ( "ClaspClose", rvWeaponNailgun::Frame_ClaspClose ) +END_CLASS_STATES + +/* +================ +rvWeaponNailgun::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + // Stop any looping sounds + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Idle + +Manage the idle state of the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoInClip ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + // Do we need to spin the drum down? + if ( DrumSpin ( NAILGUN_DRUMSPEED_SLOW, parms.blendFrames ) ) { + PostState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Fire + +Fire the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_DONE, + STAGE_SPINEMPTY, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !wsfl.attack ) { + SetState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + if ( DrumSpin ( NAILGUN_DRUMSPEED_FAST, 2 ) ) { + PostState ( "Fire", 2 ); + return SRESULT_DONE; + } + nextAttackTime = gameLocal.time; + + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || AmmoInClip ( ) <= 0 ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_fast", 4 ); + } else { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_slow", 4 ); + } + + if ( wsfl.zoom ) { + Attack ( true, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } else { + Attack ( false, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } + + // Play the exhaust effects + viewModel->PlayEffect ( "fx_exhaust", jointSteamRightView, false ); + viewModel->PlayEffect ( "fx_exhaust", jointSteamLeftView, false ); + + viewModel->StartSound ( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + viewModel->StartSound ( "snd_fireStereo", SND_CHANNEL_ITEM, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || AmmoInClip ( ) <= 0 ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( gameLocal.time >= nextAttackTime ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( clipSize && wsfl.attack && !wsfl.lowerWeapon && !wsfl.reload ) { + PlayCycle ( ANIMCHANNEL_LEGS, "spinempty", 4 ); + return SRESULT_STAGE ( STAGE_SPINEMPTY ); + } + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 4 ); + if ( !wsfl.attack && !AmmoInClip() && AmmoAvailable() && AutoReload ( ) && !wsfl.lowerWeapon ) { + PostState ( "Reload", 4 ); + } else { + PostState ( "Idle", 4 ); + } + return SRESULT_DONE; + + case STAGE_SPINEMPTY: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Reload +================ +*/ +stateResult_t rvWeaponNailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_RELOAD, + STAGE_RELOADWAIT, + STAGE_RELOADLEFT, + STAGE_RELOADLEFTWAIT, + STAGE_RELOADRIGHT, + STAGE_RELOADRIGHTWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( DrumSpin ( NAILGUN_DRUMSPEED_STOPPED, parms.blendFrames ) ) { + PostState ( "reload", parms.blendFrames ); + return SRESULT_DONE; + } + + SetStatus ( WP_RELOAD ); + + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + return SRESULT_STAGE( STAGE_RELOADLEFT ); + } + return SRESULT_STAGE( STAGE_RELOAD ); + + case STAGE_RELOAD: + PlayAnim( ANIMCHANNEL_LEGS, "reload", parms.blendFrames ); + return SRESULT_STAGE( STAGE_RELOADWAIT ); + + case STAGE_RELOADWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize ( ) ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLEFT: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1hold", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADLEFTWAIT ); + + case STAGE_RELOADLEFTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + AddToClip ( ClipSize() / 2 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHT ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADRIGHT: + if ( wsfl.attack || AmmoInClip() >= ClipSize() || !AmmoAvailable() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip2", 0 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHTWAIT ); + + case STAGE_RELOADRIGHTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize() / 2 ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1finish", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinUp + +Spin the drum from a slow speed to a fast speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinUp ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spinup", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL); + PlayAnim ( ANIMCHANNEL_LEGS, "spinup", 4 ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( !wsfl.attack ) { + int oldSpeed = drumSpeed; + drumSpeed = drumSpeedIdeal; + DrumSpin ( oldSpeed, 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinDown + +Spin the drum down from a faster speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinDown ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spindown", SND_CHANNEL_ANY, 0, false, 0 ); + + // Spin down animation + PlayAnim( ANIMCHANNEL_LEGS, "spindown", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoAvailable() > AmmoInClip() && AmmoInClip() < ClipSize()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Open the clasp that holds in the clips +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspOpen ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, "clasp_open", 0 ); + return SRESULT_OK; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Close the clasp that holds in the clips and make sure to use the +correct positioning depending on whether you have one or two clips +in the gun. +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspClose ( const stateParms_t& parms ) { + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_2clip", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_1clip", 0 ); + } + return SRESULT_OK; +} diff --git a/src/game/weapon/WeaponNapalmGun.cpp b/src/game/weapon/WeaponNapalmGun.cpp new file mode 100644 index 0000000..a9f9899 --- /dev/null +++ b/src/game/weapon/WeaponNapalmGun.cpp @@ -0,0 +1,467 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +const int NAPALM_GUN_NUM_CYLINDERS = 5; + +class WeaponNapalmGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( WeaponNapalmGun ); + + WeaponNapalmGun ( void ); + ~WeaponNapalmGun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + + virtual void SpectatorCycle ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + +protected: + + void UpdateCylinders(void); + + typedef enum {CYLINDER_RESET_POSITION,CYLINDER_MOVE_POSITION, CYLINDER_UPDATE_POSITION } CylinderState; + CylinderState cylinderState; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_EmptyReload ( const stateParms_t& parms ); + + stateResult_t Frame_MoveCylinder ( const stateParms_t& parms ); + stateResult_t Frame_ResetCylinder ( const stateParms_t& parms ); + + + float cylinderMaxOffsets[NAPALM_GUN_NUM_CYLINDERS]; + idInterpolate cylinderOffsets[NAPALM_GUN_NUM_CYLINDERS]; + jointHandle_t cylinderJoints[NAPALM_GUN_NUM_CYLINDERS]; + + + int cylinderMoveTime; + int previousAmmo; + bool zoomed; + + CLASS_STATES_PROTOTYPE ( WeaponNapalmGun ); +}; + +CLASS_DECLARATION( rvWeapon, WeaponNapalmGun ) +END_CLASS + +/* +================ +WeaponNapalmGun::WeaponNapalmGun +================ +*/ +WeaponNapalmGun::WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::~WeaponNapalmGun +================ +*/ +WeaponNapalmGun::~WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::Spawn +================ +*/ +void WeaponNapalmGun::Spawn( void ) { + assert(viewModel); + idAnimator* animator = viewModel->GetAnimator(); + assert(animator); + + SetState( "Raise", 0 ); + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + idStr argName = "cylinder_offset"; + argName += i; + cylinderMaxOffsets[i] = spawnArgs.GetFloat(argName, "0.0"); + + argName = "cylinder_joint"; + argName += i; + cylinderJoints[i] = animator->GetJointHandle( spawnArgs.GetString( argName, "" ) ); + + cylinderOffsets[i].Init( gameLocal.time, 0.0f, 0, 0); + } + + previousAmmo = AmmoInClip(); + cylinderMoveTime = spawnArgs.GetFloat( "cylinderMoveTime", "500" ); + cylinderState = CYLINDER_RESET_POSITION; + zoomed = false; +} + +/* +================ +WeaponNapalmGun::Think +================ +*/ +void WeaponNapalmGun::Think( void ) { + + rvWeapon::Think(); + + //Check to see if the ammo level has changed. + //This is to account for ammo pickups. + if ( previousAmmo != AmmoInClip() ) { + // don't do this in MP, the weap script doesn't sync the canisters anyway + if ( !gameLocal.isMultiplayer ) { + //change the cylinder state to reflect the new change in ammo. + cylinderState = CYLINDER_MOVE_POSITION; + } + previousAmmo = AmmoInClip(); + } + + UpdateCylinders(); +} + +/* +=============== +WeaponNapalmGun::MuzzleRise +=============== +*/ +void WeaponNapalmGun::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + if ( wsfl.zoom ) + return; + + rvWeapon::MuzzleRise( origin, axis ); +} + +/* +=============== +WeaponNapalmGun::UpdateCylinders +=============== +*/ +void WeaponNapalmGun::UpdateCylinders(void) +{ + idAnimator* animator; + animator = viewModel->GetAnimator(); + assert( animator ); + + float ammoInClip = AmmoInClip(); + float clipSize = ClipSize(); + if ( clipSize <= idMath::FLOAT_EPSILON ) { + clipSize = maxAmmo; + } + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + // move the local position of the joint along the x-axis. + float currentOffset = cylinderOffsets[i].GetCurrentValue(gameLocal.time); + + switch(cylinderState) + { + case CYLINDER_MOVE_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, cylinderMoveTime, currentOffset, endValue ); + } + break; + + case CYLINDER_RESET_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, 0, endValue, endValue ); + } + break; + } + + + animator->SetJointPos( cylinderJoints[i], JOINTMOD_LOCAL, idVec3( currentOffset, 0.0f, 0.0f ) ); + } + + cylinderState = CYLINDER_UPDATE_POSITION; +} + + +/* +===================== +WeaponNapalmGun::Save +===================== +*/ +void WeaponNapalmGun::Save( idSaveGame *saveFile ) const +{ + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->WriteFloat(cylinderMaxOffsets[i]); + saveFile->WriteInterpolate(cylinderOffsets[i]); + saveFile->WriteJoint(cylinderJoints[i]); + } + + saveFile->WriteInt(cylinderMoveTime); + saveFile->WriteInt(previousAmmo); +} + +/* +===================== +WeaponNapalmGun::Restore +===================== +*/ +void WeaponNapalmGun::Restore( idRestoreGame *saveFile ) { + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->ReadFloat(cylinderMaxOffsets[i]); + saveFile->ReadInterpolate(cylinderOffsets[i]); + saveFile->ReadJoint(cylinderJoints[i]); + } + + saveFile->ReadInt(cylinderMoveTime); + saveFile->ReadInt(previousAmmo); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( WeaponNapalmGun ) + STATE ( "Idle", WeaponNapalmGun::State_Idle) + STATE ( "Fire", WeaponNapalmGun::State_Fire ) + STATE ( "Reload", WeaponNapalmGun::State_Reload ) + STATE ( "EmptyReload", WeaponNapalmGun::State_EmptyReload ) + STATE ( "MoveCylinder", WeaponNapalmGun::Frame_MoveCylinder ) + STATE ( "ResetCylinder", WeaponNapalmGun::Frame_ResetCylinder) +END_CLASS_STATES + + + +stateResult_t WeaponNapalmGun::State_Reload( const stateParms_t& parms) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t WeaponNapalmGun::State_EmptyReload( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_empty", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip ( ClipSize() ); + + cylinderState = CYLINDER_MOVE_POSITION; + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Idle +================ +*/ +stateResult_t WeaponNapalmGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + if ( wsfl.zoom ) + PlayCycle( ANIMCHANNEL_LEGS, "altidle", parms.blendFrames ); + else + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.zoom && !zoomed ) { + SetState ( "Idle", 4 ); + zoomed = true; + return SRESULT_DONE; + } + + if ( !wsfl.zoom && zoomed ) { + SetState ( "Idle", 4 ); + zoomed = false; + return SRESULT_DONE; + } + + if(!clipSize) + { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } + else + { + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Fire +================ +*/ +stateResult_t WeaponNapalmGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + //fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + + int animNum = viewModel->GetAnimator()->GetAnim ( "fire" ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim ( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + + previousAmmo = AmmoInClip(); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + if ( !wsfl.zoom ) + SetState ( "Reload", 4 ); + else + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t WeaponNapalmGun::Frame_MoveCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_MOVE_POSITION; + return SRESULT_OK; +} + +stateResult_t WeaponNapalmGun::Frame_ResetCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_RESET_POSITION; + return SRESULT_OK; +} + +void WeaponNapalmGun::SpectatorCycle( void ) { + cylinderState = CYLINDER_RESET_POSITION; +} diff --git a/src/game/weapon/WeaponRailgun.cpp b/src/game/weapon/WeaponRailgun.cpp new file mode 100644 index 0000000..969af50 --- /dev/null +++ b/src/game/weapon/WeaponRailgun.cpp @@ -0,0 +1,294 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const idEventDef EV_Railgun_RestoreHum( "", "" ); + +class rvWeaponRailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRailgun ); + + rvWeaponRailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + void ClientUnstale ( void ); + +protected: + jointHandle_t jointBatteryView; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRailgun ) + EVENT( EV_Railgun_RestoreHum, rvWeaponRailgun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponRailgun::rvWeaponRailgun +================ +*/ +rvWeaponRailgun::rvWeaponRailgun ( void ) { +} + +/* +================ +rvWeaponRailgun::Spawn +================ +*/ +void rvWeaponRailgun::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponRailgun::Save +================ +*/ +void rvWeaponRailgun::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::Restore +================ +*/ +void rvWeaponRailgun::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::PreSave +================ +*/ +void rvWeaponRailgun::PreSave ( void ) { + + //this should shoosh the humming but not the shooting sound. + StopSound( SND_CHANNEL_BODY2, 0); +} + +/* +================ +rvWeaponRailgun::PostSave +================ +*/ +void rvWeaponRailgun::PostSave ( void ) { + + //restore the humming + PostEventMS( &EV_Railgun_RestoreHum, 10); +} + +/* +================ +rvWeaponRailgun::Think +================ +*/ +void rvWeaponRailgun::Think ( void ) { + + // Let the real weapon think first + rvWeapon::Think ( ); + + if ( zoomGui && wsfl.zoom && !gameLocal.isMultiplayer ) { + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + zoomGui->SetStateInt( "player_ammo", ammo ); + } + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRailgun ) + STATE ( "Idle", rvWeaponRailgun::State_Idle) + STATE ( "Fire", rvWeaponRailgun::State_Fire ) + STATE ( "Reload", rvWeaponRailgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponRailgun::State_Idle +================ +*/ +stateResult_t rvWeaponRailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); + SetStatus ( WP_READY ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + // Auto reload? + if ( AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRailgun::State_Fire +================ +*/ +stateResult_t rvWeaponRailgun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( ( gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime ) || + ( !gameLocal.isMultiplayer && ( AnimDone ( ANIMCHANNEL_ALL, 2 ) ) ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +stateResult_t rvWeaponRailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Event + +=============================================================================== +*/ + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +void rvWeaponRailgun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); +} + +/* +================ +rvWeaponRailgun::ClientUnStale +================ +*/ +void rvWeaponRailgun::ClientUnstale( void ) { + Event_RestoreHum(); +} + diff --git a/src/game/weapon/WeaponRocketLauncher.cpp b/src/game/weapon/WeaponRocketLauncher.cpp new file mode 100644 index 0000000..a5d7c59 --- /dev/null +++ b/src/game/weapon/WeaponRocketLauncher.cpp @@ -0,0 +1,607 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + +class rvWeaponRocketLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRocketLauncher ); + + rvWeaponRocketLauncher ( void ); + ~rvWeaponRocketLauncher ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + void PreSave ( void ); + void PostSave ( void ); + + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetRocketState ( const char* state, int blendFrames ); + + rvClientEntityPtr guideEffect; + idList< idEntityPtr > guideEnts; + float guideSpeedSlow; + float guideSpeedFast; + float guideRange; + float guideAccelTime; + + rvStateThread rocketThread; + + float reloadRate; + + bool idleEmpty; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_Rocket_Idle ( const stateParms_t& parms ); + stateResult_t State_Rocket_Reload ( const stateParms_t& parms ); + + stateResult_t Frame_AddToClip ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRocketLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRocketLauncher ) +END_CLASS + +/* +================ +rvWeaponRocketLauncher::rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::rvWeaponRocketLauncher ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } +} + +/* +================ +rvWeaponRocketLauncher::Spawn +================ +*/ +void rvWeaponRocketLauncher::Spawn ( void ) { + float f; + + idleEmpty = false; + + spawnArgs.GetFloat ( "lockRange", "0", guideRange ); + + spawnArgs.GetFloat ( "lockSlowdown", ".25", f ); + attackDict.GetFloat ( "speed", "0", guideSpeedFast ); + guideSpeedSlow = guideSpeedFast * f; + + reloadRate = SEC2MS ( spawnArgs.GetFloat ( "reloadRate", ".8" ) ); + + guideAccelTime = SEC2MS ( spawnArgs.GetFloat ( "lockAccelTime", ".25" ) ); + + // Start rocket thread + rocketThread.SetName ( viewModel->GetName ( ) ); + rocketThread.SetOwner ( this ); + + // Adjust reload animations to match the fire rate + idAnim* anim; + int animNum; + float rate; + animNum = viewModel->GetAnimator()->GetAnim ( "reload" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + animNum = viewModel->GetAnimator()->GetAnim ( "reload_empty" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + SetState ( "Raise", 0 ); + SetRocketState ( "Rocket_Idle", 0 ); +} + +/* +================ +rvWeaponRocketLauncher::Think +================ +*/ +void rvWeaponRocketLauncher::Think ( void ) { + trace_t tr; + int i; + + rocketThread.Execute ( ); + + // Let the real weapon think first + rvWeapon::Think ( ); + + // IF no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + if ( !wsfl.zoom ) { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden ( ) ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket is still guiding then stop the guide and slow it down + if ( proj->GetGuideType ( ) != idGuidedProjectile::GUIDE_NONE ) { + proj->CancelGuide ( ); + proj->SetSpeed ( guideSpeedFast, (1.0f - (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow)) * guideAccelTime ); + } + } + + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden() ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket isnt guiding yet then adjust its speed back to normal + if ( proj->GetGuideType ( ) == idGuidedProjectile::GUIDE_NONE ) { + proj->SetSpeed ( guideSpeedSlow, (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow) * guideAccelTime ); + } + proj->GuideTo ( tr.endpos ); + } + + if ( !guideEffect ) { + guideEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_guide" ), tr.endpos, tr.c.normal.ToMat3(), true, vec3_origin, true ); + } else { + guideEffect->SetOrigin ( tr.endpos ); + guideEffect->SetAxis ( tr.c.normal.ToMat3() ); + } +} + +/* +================ +rvWeaponRocketLauncher::OnLaunchProjectile +================ +*/ +void rvWeaponRocketLauncher::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + // Double check that its actually a guided projectile + if ( !proj || !proj->IsType ( idGuidedProjectile::GetClassType() ) ) { + return; + } + + // Launch the projectile + idEntityPtr ptr; + ptr = proj; + guideEnts.Append ( ptr ); +} + +/* +================ +rvWeaponRocketLauncher::SetRocketState +================ +*/ +void rvWeaponRocketLauncher::SetRocketState ( const char* state, int blendFrames ) { + rocketThread.SetState ( state, blendFrames ); +} + +/* +===================== +rvWeaponRocketLauncher::Save +===================== +*/ +void rvWeaponRocketLauncher::Save( idSaveGame *saveFile ) const { + saveFile->WriteObject( guideEffect ); + + idEntity* ent = NULL; + saveFile->WriteInt( guideEnts.Num() ); + for( int ix = 0; ix < guideEnts.Num(); ++ix ) { + ent = guideEnts[ ix ].GetEntity(); + if( ent ) { + saveFile->WriteObject( ent ); + } + } + + saveFile->WriteFloat( guideSpeedSlow ); + saveFile->WriteFloat( guideSpeedFast ); + saveFile->WriteFloat( guideRange ); + saveFile->WriteFloat( guideAccelTime ); + + saveFile->WriteFloat ( reloadRate ); + + rocketThread.Save( saveFile ); +} + +/* +===================== +rvWeaponRocketLauncher::Restore +===================== +*/ +void rvWeaponRocketLauncher::Restore( idRestoreGame *saveFile ) { + int numEnts = 0; + idEntity* ent = NULL; + rvClientEffect* clientEffect = NULL; + + saveFile->ReadObject( reinterpret_cast(clientEffect) ); + guideEffect = clientEffect; + + saveFile->ReadInt( numEnts ); + guideEnts.Clear(); + guideEnts.SetNum( numEnts ); + for( int ix = 0; ix < numEnts; ++ix ) { + saveFile->ReadObject( reinterpret_cast(ent) ); + guideEnts[ ix ] = ent; + } + + saveFile->ReadFloat( guideSpeedSlow ); + saveFile->ReadFloat( guideSpeedFast ); + saveFile->ReadFloat( guideRange ); + saveFile->ReadFloat( guideAccelTime ); + + saveFile->ReadFloat ( reloadRate ); + + rocketThread.Restore( saveFile, this ); +} + +/* +================ +rvWeaponRocketLauncher::PreSave +================ +*/ +void rvWeaponRocketLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::PostSave +================ +*/ +void rvWeaponRocketLauncher::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRocketLauncher ) + STATE ( "Idle", rvWeaponRocketLauncher::State_Idle) + STATE ( "Fire", rvWeaponRocketLauncher::State_Fire ) + STATE ( "Raise", rvWeaponRocketLauncher::State_Raise ) + STATE ( "Lower", rvWeaponRocketLauncher::State_Lower ) + + STATE ( "Rocket_Idle", rvWeaponRocketLauncher::State_Rocket_Idle ) + STATE ( "Rocket_Reload", rvWeaponRocketLauncher::State_Rocket_Reload ) + + STATE ( "AddToClip", rvWeaponRocketLauncher::Frame_AddToClip ) +END_CLASS_STATES + + +/* +================ +rvWeaponRocketLauncher::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && ( gameLocal.isClient || AmmoInClip ( ) ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_LEGS, "fire", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && ( gameLocal.isClient || AmmoInClip ( ) ) && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITEMPTY, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) <= AmmoInClip() ) { + PlayAnim( ANIMCHANNEL_TORSO, "idle_empty", parms.blendFrames ); + idleEmpty = true; + } else { + PlayAnim( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AmmoAvailable ( ) > AmmoInClip() ) { + if ( idleEmpty ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } else if ( ClipSize ( ) > 1 ) { + if ( gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) ) { + if ( !AmmoInClip() || !wsfl.attack ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } else { + if ( AmmoInClip ( ) == 0 ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Reload +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: { + const char* animName; + int animNum; + + if ( idleEmpty ) { + animName = "ammo_pickup"; + idleEmpty = false; + } else if ( AmmoAvailable ( ) == AmmoInClip( ) + 1 ) { + animName = "reload_empty"; + } else { + animName = "reload"; + } + + animNum = viewModel->GetAnimator()->GetAnim ( animName ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (reloadRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( !wsfl.attack && gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) && AmmoAvailable() > AmmoInClip() ) { + SetRocketState ( "Rocket_Reload", 0 ); + } else { + SetRocketState ( "Rocket_Idle", 0 ); + } + return SRESULT_DONE; + } + /* + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + if ( AmmoInClip ( ) == 0 ) + { + AddToClip ( ClipSize() ); + } + SetRocketState ( "Rocket_Idle", 0 ); + return SRESULT_DONE; + } + */ + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::Frame_AddToClip +================ +*/ +stateResult_t rvWeaponRocketLauncher::Frame_AddToClip ( const stateParms_t& parms ) { + AddToClip ( 1 ); + return SRESULT_OK; +} + diff --git a/src/game/weapon/WeaponShotgun.cpp b/src/game/weapon/WeaponShotgun.cpp new file mode 100644 index 0000000..7c12965 --- /dev/null +++ b/src/game/weapon/WeaponShotgun.cpp @@ -0,0 +1,313 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int SHOTGUN_MOD_AMMO = BIT(0); + +class rvWeaponShotgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponShotgun ); + + rvWeaponShotgun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + int hitscans; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( rvWeaponShotgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponShotgun ) +END_CLASS + +/* +================ +rvWeaponShotgun::rvWeaponShotgun +================ +*/ +rvWeaponShotgun::rvWeaponShotgun( void ) { +} + +/* +================ +rvWeaponShotgun::Spawn +================ +*/ +void rvWeaponShotgun::Spawn( void ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); + + SetState( "Raise", 0 ); +} + +/* +================ +rvWeaponShotgun::Save +================ +*/ +void rvWeaponShotgun::Save( idSaveGame *savefile ) const { +} + +/* +================ +rvWeaponShotgun::Restore +================ +*/ +void rvWeaponShotgun::Restore( idRestoreGame *savefile ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); +} + +/* +================ +rvWeaponShotgun::PreSave +================ +*/ +void rvWeaponShotgun::PreSave ( void ) { +} + +/* +================ +rvWeaponShotgun::PostSave +================ +*/ +void rvWeaponShotgun::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION( rvWeaponShotgun ) + STATE( "Idle", rvWeaponShotgun::State_Idle) + STATE( "Fire", rvWeaponShotgun::State_Fire ) + STATE( "Reload", rvWeaponShotgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponShotgun::State_Idle +================ +*/ +stateResult_t rvWeaponShotgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable( ) ) { + SetStatus( WP_OUTOFAMMO ); + } else { + SetStatus( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Fire +================ +*/ +stateResult_t rvWeaponShotgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack( false, hitscans, spread, 0, 1.0f ); + PlayAnim( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( (!gameLocal.isMultiplayer && (wsfl.lowerWeapon || AnimDone( ANIMCHANNEL_ALL, 0 )) ) || AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetState( "Idle", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( clipSize ) { + if ( (wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip())) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Reload +================ +*/ +stateResult_t rvWeaponShotgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_RELOADSTARTWAIT, + STAGE_RELOADLOOP, + STAGE_RELOADLOOPWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + + if ( mods & SHOTGUN_MOD_AMMO ) { + PlayAnim ( ANIMCHANNEL_ALL, "reload_clip", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_ALL, "reload_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADSTARTWAIT ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADSTARTWAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLOOP: + if ( (wsfl.attack && AmmoInClip() ) || AmmoAvailable ( ) <= AmmoInClip ( ) || AmmoInClip() == ClipSize() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_ALL, "reload_loop", 0 ); + return SRESULT_STAGE ( STAGE_RELOADLOOPWAIT ); + + case STAGE_RELOADLOOPWAIT: + if ( (wsfl.attack && AmmoInClip() ) || wsfl.netEndReload ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip( 1 ); + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + NetEndReload ( ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_end", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) && gameLocal.time > nextAttackTime ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/idlib/AutoPtr.h b/src/idlib/AutoPtr.h new file mode 100644 index 0000000..01b09df --- /dev/null +++ b/src/idlib/AutoPtr.h @@ -0,0 +1,88 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AUTOPTR_H__ +#define __AUTOPTR_H__ + + +// this class is NOT safe for array new's. It will not properly call +// the destructor for each element and you will silently leak memory. +// it does work for classes requiring no destructor however(base types) +template +class idAutoPtr +{ +public: + explicit idAutoPtr(type *ptr = 0) + : mPtr(ptr) + { + } + + ~idAutoPtr() + { + delete mPtr; + } + + type &operator*() const + { + return *mPtr; + } + + type *operator->() const + { + return &**this; + } + + type *get() const + { + return mPtr; + } + + type *release() + { + type *ptr = mPtr; + mPtr = NULL; + return ptr; + } + + void reset(type *ptr = NULL) + { + if (ptr != mPtr) + delete mPtr; + mPtr = ptr; + } + + operator type*() + { + return get(); + } + +private: + // disallow copies + idAutoPtr &operator=(idAutoPtr& ptr); + idAutoPtr(idAutoPtr& ptr); + + type *mPtr; +}; + +#endif diff --git a/src/idlib/Base64.cpp b/src/idlib/Base64.cpp new file mode 100644 index 0000000..9ff0dbb --- /dev/null +++ b/src/idlib/Base64.cpp @@ -0,0 +1,256 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +/* +Copyright (c) 1996 Lars Wirzenius. All rights reserved. + +June 14 2003: TTimo + modified + endian bug fixes + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=197039 + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +============ +idBase64::Encode +============ +*/ +static const char sixtet_to_base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void idBase64::Encode( const byte *from, int size ) { + int i, j; + unsigned long w; + byte *to; + + EnsureAlloced( 4*(size+3)/3 + 2 ); // ratio and padding + trailing \0 + to = data; + + w = 0; + i = 0; + while (size > 0) { + w |= *from << i*8; + ++from; + --size; + ++i; + if (size == 0 || i == 3) { + byte out[4]; + SixtetsForInt( out, w ); + for (j = 0; j*6 < i*8; ++j) { + *to++ = sixtet_to_base64[ out[j] ]; + } + if (size == 0) { + for (j = i; j < 3; ++j) { + *to++ = '='; + } + } + w = 0; + i = 0; + } + } + + *to++ = '\0'; + len = to - data; +} + +/* +============ +idBase64::DecodeLength +returns the minimum size in bytes of the target buffer for decoding +4 base64 digits <-> 3 bytes +============ +*/ +int idBase64::DecodeLength( void ) const { + return 3*len/4; +} + +/* +============ +idBase64::Decode +============ +*/ +int idBase64::Decode( byte *to ) const { + unsigned long w; + int i, j; + size_t n; + static char base64_to_sixtet[256]; + static int tab_init = 0; + byte *from = data; + + if (!tab_init) { + memset( base64_to_sixtet, 0, 256 ); + for (i = 0; (j = sixtet_to_base64[i]) != '\0'; ++i) { + base64_to_sixtet[j] = i; + } + tab_init = 1; + } + + w = 0; + i = 0; + n = 0; + byte in[4] = {0,0,0,0}; + while (*from != '\0' && *from != '=' ) { + if (*from == ' ' || *from == '\n') { + ++from; + continue; + } + in[i] = base64_to_sixtet[* (unsigned char *) from]; + ++i; + ++from; + if (*from == '\0' || *from == '=' || i == 4) { + w = IntForSixtets( in ); + for (j = 0; j*8 < i*6; ++j) { + *to++ = w & 0xff; + ++n; + w >>= 8; + } + i = 0; + w = 0; + } + } + return n; +} + +/* +============ +idBase64::Encode +============ +*/ +void idBase64::Encode( const idStr &src ) { + Encode( (const byte *)src.c_str(), src.Length() ); +} + +/* +============ +idBase64::Decode +============ +*/ +void idBase64::Decode( idStr &dest ) const { + byte *buf = new byte[ DecodeLength()+1 ]; // +1 for trailing \0 + int out = Decode( buf ); + buf[out] = '\0'; + dest = (const char *)buf; + delete[] buf; +} + +/* +============ +idBase64::Decode +============ +*/ +void idBase64::Decode( idFile *dest ) const { + byte *buf = new byte[ DecodeLength()+1 ]; // +1 for trailing \0 + int out = Decode( buf ); + dest->Write( buf, out ); + delete[] buf; +} + +#if 0 + +void idBase64_TestBase64() { + + idStr src; + idBase64 dest; + src = "Encode me in base64"; + dest.Encode( src ); + idLib::common->Printf( "%s -> %s\n", src.c_str(), dest.c_str() ); + dest.Decode( src ); + idLib::common->Printf( "%s -> %s\n", dest.c_str(), src.c_str() ); + + idDict src_dict; + src_dict.SetFloat("float", 0.5f); + src_dict.SetBool("bool", true); + src_dict.Set("value", "foo"); + idFile_Memory src_fmem("serialize_dict"); + src_dict.WriteToFileHandle( &src_fmem ); + dest.Encode( (const byte *)src_fmem.GetDataPtr(), src_fmem.Length() ); + idLib::common->Printf( "idDict encoded to %s\n", dest.c_str()); + + // now decode to another stream and build back + idFile_Memory dest_fmem( "build_back" ); + dest.Decode( &dest_fmem ); + dest_fmem.MakeReadOnly(); + idDict dest_dict; + dest_dict.ReadFromFileHandle( &dest_fmem ); + idLib::common->Printf( "idDict reconstructed after base64 decode\n"); + dest_dict.Print(); + + // test idDict read from file - from python generated files, see idDict.py + idFile *file = idLib::fileSystem->OpenFileRead("idDict.test"); + if (file) { + idDict test_dict; + test_dict.ReadFromFileHandle( file ); + // + idLib::common->Printf( "read idDict.test:\n"); + test_dict.Print(); + idLib::fileSystem->CloseFile(file); + file = NULL; + } else { + idLib::common->Printf( "idDict.test not found\n" ); + } + + idBase64 base64_src; + void *buffer; + if ( idLib::fileSystem->ReadFile( "idDict.base64.test", &buffer ) != -1 ) { + idFile_Memory mem_src( "dict" ); + idLib::common->Printf( "read: %d %s\n", idStr::Length( (char*)buffer ), buffer ); + base64_src = (char *)buffer; + base64_src.Decode( &mem_src ); + mem_src.MakeReadOnly(); + idDict test_dict; + test_dict.ReadFromFileHandle( &mem_src ); + idLib::common->Printf( "read idDict.base64.test:\n"); + test_dict.Print(); + idLib::fileSystem->FreeFile( buffer ); + } else { + idLib::common->Printf( "idDict.base64.test not found\n" ); + } +} + +#endif diff --git a/src/idlib/Base64.h b/src/idlib/Base64.h new file mode 100644 index 0000000..1baae55 --- /dev/null +++ b/src/idlib/Base64.h @@ -0,0 +1,107 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BASE64_H__ +#define __BASE64_H__ + +/* +=============================================================================== + + base64 + +=============================================================================== +*/ + +class idBase64 { +public: + idBase64( void ); + idBase64( const idStr &s ); + ~idBase64( void ); + + void Encode( const byte *from, int size ); + void Encode( const idStr &src ); + int DecodeLength( void ) const; // minimum size in bytes of destination buffer for decoding + int Decode( byte *to ) const; // does not append a \0 - needs a DecodeLength() bytes buffer + void Decode( idStr &dest ) const; // decodes the binary content to an idStr (a bit dodgy, \0 and other non-ascii are possible in the decoded content) + void Decode( idFile *dest ) const; + + const char *c_str() const; + + void operator=( const idStr &s ); + +private: + byte * data; + int len; + int alloced; + + void Init( void ); + void Release( void ); + void EnsureAlloced( int size ); +}; + +ID_INLINE idBase64::idBase64( void ) { + Init(); +} + +ID_INLINE idBase64::idBase64( const idStr &s ) { + Init(); + *this = s; +} + +ID_INLINE idBase64::~idBase64( void ) { + Release(); +} + +ID_INLINE const char *idBase64::c_str( void ) const { + return (const char *)data; +} + +ID_INLINE void idBase64::Init( void ) { + len = 0; + alloced = 0; + data = NULL; +} + +ID_INLINE void idBase64::Release( void ) { + if ( data ) { + delete[] data; + } + Init(); +} + +ID_INLINE void idBase64::EnsureAlloced( int size ) { + if ( size > alloced ) { + Release(); + } + data = new byte[size]; + alloced = size; +} + +ID_INLINE void idBase64::operator=( const idStr &s ) { + EnsureAlloced( s.Length()+1 ); // trailing \0 - beware, this does a Release + strcpy( (char *)data, s.c_str() ); + len = s.Length(); +} + +#endif /* !__BASE64_H__ */ diff --git a/src/idlib/BitMsg.cpp b/src/idlib/BitMsg.cpp new file mode 100644 index 0000000..02bd991 --- /dev/null +++ b/src/idlib/BitMsg.cpp @@ -0,0 +1,1484 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + + +/* +============================================================================== + + idBitMsg + +============================================================================== +*/ + +/* +================ +idBitMsg::idBitMsg +================ +*/ +idBitMsg::idBitMsg() { + writeData = NULL; + readData = NULL; + maxSize = 0; + curSize = 0; + writeBit = 0; + readCount = 0; + readBit = 0; + numValueOverflows = 0; + allowOverflow = false; + overflowed = false; +} + +/* +================ +idBitMsg::CheckOverflow +================ +*/ +bool idBitMsg::CheckOverflow( int numBits ) { + assert( numBits >= 0 ); + if ( numBits > GetRemainingWriteBits() ) { + if ( !allowOverflow ) { + idLib::common->FatalError( va( "idBitMsg: overflow without allowOverflow set: %d > %d*8", GetNumBitsWritten()+numBits, GetMaxSize() ) ); + } + if ( numBits > ( maxSize << 3 ) ) { + idLib::common->FatalError( "idBitMsg: %i bits is > full message size", numBits ); + } + idLib::common->Printf( "idBitMsg: overflow\n" ); + BeginWriting(); + overflowed = true; + return true; + } + return false; +} + +/* +================ +idBitMsg::GetByteSpace +================ +*/ +byte *idBitMsg::GetByteSpace( int length ) { + byte *ptr; + + if ( !writeData ) { + idLib::common->FatalError( "idBitMsg::GetByteSpace: cannot write to message" ); + } + + // round up to the next byte + WriteByteAlign(); + + // check for overflow + CheckOverflow( length << 3 ); + + ptr = writeData + curSize; + curSize += length; + return ptr; +} + +/* +================ +idBitMsg::WriteBits + + If the number of bits is negative a sign is included. +================ +*/ +void idBitMsg::WriteBits( int value, int numBits ) { + int put; + int fraction; + + if ( !writeData ) { + idLib::common->Error( "idBitMsg::WriteBits: cannot write to message" ); + } + + // check if the number of bits is valid + if ( numBits == 0 || numBits < -31 || numBits > 32 ) { + idLib::common->Error( "idBitMsg::WriteBits: bad numBits %i", numBits ); + } + + // check for value overflows + if ( numBits != 32 ) { + if ( numBits > 0 ) { + if ( value > ( 1 << numBits ) - 1 ) { + numValueOverflows++; + } else if ( value < 0 ) { + numValueOverflows++; + } + } else { + int r = 1 << ( - 1 - numBits ); + if ( value > r - 1 ) { + numValueOverflows++; + } else if ( value < -r ) { + numValueOverflows++; + } + } + } + + if ( numBits < 0 ) { + numBits = -numBits; + } + + // check for msg overflow + if ( CheckOverflow( numBits ) ) { + return; + } + + // write the bits + while( numBits ) { + if ( writeBit == 0 ) { + writeData[curSize] = 0; + curSize++; + } + put = 8 - writeBit; + if ( put > numBits ) { + put = numBits; + } + fraction = value & ( ( 1 << put ) - 1 ); + writeData[curSize - 1] |= fraction << writeBit; + numBits -= put; + value >>= put; + writeBit = ( writeBit + put ) & 7; + } +} + +/* +================ +idBitMsg::WriteString +================ +*/ +void idBitMsg::WriteString( const char *s, int maxLength ) { + if ( !s ) { + WriteData( "", 1 ); + } else { + int l; + byte *dataPtr; + + l = idStr::Length( s ); + if ( maxLength >= 0 && l >= maxLength ) { + l = maxLength - 1; + } + dataPtr = GetByteSpace( l + 1 ); + memcpy( dataPtr, s, l ); + dataPtr[l] = '\0'; + } +} + +/* +================ +idBitMsg::WriteData +================ +*/ +void idBitMsg::WriteData( const void *data, int length ) { + memcpy( GetByteSpace( length ), data, length ); +} + +/* +================ +idBitMsg::WriteNetadr +================ +*/ +void idBitMsg::WriteNetadr( const netadr_t adr ) { + byte *dataPtr; + dataPtr = GetByteSpace( 4 ); + memcpy( dataPtr, adr.ip, 4 ); + WriteUShort( adr.port ); +} + +/* +================ +idBitMsg::WriteDelta +================ +*/ +void idBitMsg::WriteDelta( int oldValue, int newValue, int numBits ) { + if ( oldValue == newValue ) { + WriteBits( 0, 1 ); + return; + } + WriteBits( 1, 1 ); + WriteBits( newValue, numBits ); +} + +/* +================ +idBitMsg::WriteDeltaByteCounter +================ +*/ +void idBitMsg::WriteDeltaByteCounter( int oldValue, int newValue ) { + int i, x; + + x = oldValue ^ newValue; + for ( i = 7; i > 0; i-- ) { + if ( x & ( 1 << i ) ) { + i++; + break; + } + } + WriteBits( i, 3 ); + if ( i ) { + WriteBits( ( ( 1 << i ) - 1 ) & newValue, i ); + } +} + +/* +================ +idBitMsg::WriteDeltaShortCounter +================ +*/ +void idBitMsg::WriteDeltaShortCounter( int oldValue, int newValue ) { + int i, x; + + x = oldValue ^ newValue; + for ( i = 15; i > 0; i-- ) { + if ( x & ( 1 << i ) ) { + i++; + break; + } + } + WriteBits( i, 4 ); + if ( i ) { + WriteBits( ( ( 1 << i ) - 1 ) & newValue, i ); + } +} + +/* +================ +idBitMsg::WriteDeltaLongCounter +================ +*/ +void idBitMsg::WriteDeltaLongCounter( int oldValue, int newValue ) { + int i, x; + + x = oldValue ^ newValue; + for ( i = 31; i > 0; i-- ) { + if ( x & ( 1 << i ) ) { + i++; + break; + } + } + WriteBits( i, 5 ); + if ( i ) { + WriteBits( ( ( 1 << i ) - 1 ) & newValue, i ); + } +} + +/* +================== +idBitMsg::WriteDeltaDict +================== +*/ +bool idBitMsg::WriteDeltaDict( const idDict &dict, const idDict *base ) { + int i; + const idKeyValue *kv, *basekv; + bool changed = false; + + if ( base != NULL ) { + + for ( i = 0; i < dict.GetNumKeyVals(); i++ ) { + kv = dict.GetKeyVal( i ); + basekv = base->FindKey( kv->GetKey() ); + if ( basekv == NULL || basekv->GetValue().Cmp( kv->GetValue() ) != 0 ) { + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + changed = true; + } + } + + WriteString( "" ); + + for ( i = 0; i < base->GetNumKeyVals(); i++ ) { + basekv = base->GetKeyVal( i ); + kv = dict.FindKey( basekv->GetKey() ); + if ( kv == NULL ) { + WriteString( basekv->GetKey() ); + changed = true; + } + } + + WriteString( "" ); + + } else { + + for ( i = 0; i < dict.GetNumKeyVals(); i++ ) { + kv = dict.GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + changed = true; + } + WriteString( "" ); + + WriteString( "" ); + + } + + return changed; +} + +/* +================ +idBitMsg::ReadBits + + If the number of bits is negative a sign is included. +================ +*/ +int idBitMsg::ReadBits( int numBits ) const { + int value; + int valueBits; + int get; + int fraction; + bool sgn; + + if ( !readData ) { + idLib::common->FatalError( "idBitMsg::ReadBits: cannot read from message" ); + } + + // check if the number of bits is valid + if ( numBits == 0 || numBits < -31 || numBits > 32 ) { + idLib::common->FatalError( "idBitMsg::ReadBits: bad numBits %i", numBits ); + } + + value = 0; + valueBits = 0; + + if ( numBits < 0 ) { + numBits = -numBits; + sgn = true; + } else { + sgn = false; + } + + // check for overflow + if ( numBits > GetRemainingReadBits() ) { + return -1; + } + + while ( valueBits < numBits ) { + if ( readBit == 0 ) { + readCount++; + } + get = 8 - readBit; + if ( get > (numBits - valueBits) ) { + get = numBits - valueBits; + } + fraction = readData[readCount - 1]; + fraction >>= readBit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + + valueBits += get; + readBit = ( readBit + get ) & 7; + } + + if ( sgn ) { + if ( value & ( 1 << ( numBits - 1 ) ) ) { + value |= -1 ^ ( ( 1 << numBits ) - 1 ); + } + } + + return value; +} + +/* +================ +idBitMsg::ReadString +================ +*/ +int idBitMsg::ReadString( char *buffer, int bufferSize ) const { + int l, c; + + ReadByteAlign(); + l = 0; + while( 1 ) { + c = ReadByte(); + if ( c <= 0 || c >= 255 ) { + break; + } + // translate all fmt spec to avoid crash bugs in string routines + if ( c == '%' ) { + c = '.'; + } + + // we will read past any excessively long string, so + // the following data can be read, but the string will + // be truncated + if ( l < bufferSize - 1 ) { + buffer[l] = c; + l++; + } + } + + buffer[l] = 0; + return l; +} + +/* +================ +idBitMsg::ReadData +================ +*/ +int idBitMsg::ReadData( void *data, int length ) const { + int cnt; + + ReadByteAlign(); + cnt = readCount; + + if ( readCount + length > curSize ) { + if ( data ) { + memcpy( data, readData + readCount, GetRemainingData() ); + } + readCount = curSize; + } else { + if ( data ) { + memcpy( data, readData + readCount, length ); + } + readCount += length; + } + + return ( readCount - cnt ); +} + +/* +================ +idBitMsg::ReadNetadr +================ +*/ +void idBitMsg::ReadNetadr( netadr_t *adr ) const { + int i; + + adr->type = NA_IP; + for ( i = 0; i < 4; i++ ) { + adr->ip[ i ] = ReadByte(); + } + adr->port = ReadUShort(); +} + +/* +================ +idBitMsg::ReadDelta +================ +*/ +int idBitMsg::ReadDelta( int oldValue, int numBits ) const { + if ( ReadBits( 1 ) ) { + return ReadBits( numBits ); + } + return oldValue; +} + +/* +================ +idBitMsg::ReadDeltaByteCounter +================ +*/ +int idBitMsg::ReadDeltaByteCounter( int oldValue ) const { + int i, newValue; + + i = ReadBits( 3 ); + if ( !i ) { + return oldValue; + } + newValue = ReadBits( i ); + return ( oldValue & ~( ( 1 << i ) - 1 ) | newValue ); +} + +/* +================ +idBitMsg::ReadDeltaShortCounter +================ +*/ +int idBitMsg::ReadDeltaShortCounter( int oldValue ) const { + int i, newValue; + + i = ReadBits( 4 ); + if ( !i ) { + return oldValue; + } + newValue = ReadBits( i ); + return ( oldValue & ~( ( 1 << i ) - 1 ) | newValue ); +} + +/* +================ +idBitMsg::ReadDeltaLongCounter +================ +*/ +int idBitMsg::ReadDeltaLongCounter( int oldValue ) const { + int i, newValue; + + i = ReadBits( 5 ); + if ( !i ) { + return oldValue; + } + newValue = ReadBits( i ); + return ( oldValue & ~( ( 1 << i ) - 1 ) | newValue ); +} + +/* +================== +idBitMsg::ReadDeltaDict +================== +*/ +bool idBitMsg::ReadDeltaDict( idDict &dict, const idDict *base ) const { + char key[MAX_STRING_CHARS]; + char value[MAX_STRING_CHARS]; + bool changed = false; + + if ( base != NULL ) { + dict = *base; + } else { + dict.Clear(); + } + + while( ReadString( key, sizeof( key ) ) != 0 ) { + ReadString( value, sizeof( value ) ); + dict.Set( key, value ); + changed = true; + } + + while( ReadString( key, sizeof( key ) ) != 0 ) { + dict.Delete( key ); + changed = true; + } + + return changed; +} + +/* +================ +idBitMsg::DirToBits +================ +*/ +int idBitMsg::DirToBits( const idVec3 &dir, int numBits ) { + int max, bits; + float bias; + + assert( numBits >= 6 && numBits <= 32 ); + assert( dir.LengthSqr() - 1.0f < 0.01f ); + + numBits /= 3; + max = ( 1 << ( numBits - 1 ) ) - 1; + bias = 0.5f / max; + + bits = FLOATSIGNBITSET( dir.x ) << ( numBits * 3 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.x ) + bias ) * max ) ) << ( numBits * 2 ); + bits |= FLOATSIGNBITSET( dir.y ) << ( numBits * 2 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.y ) + bias ) * max ) ) << ( numBits * 1 ); + bits |= FLOATSIGNBITSET( dir.z ) << ( numBits * 1 - 1 ); + bits |= ( idMath::Ftoi( ( idMath::Fabs( dir.z ) + bias ) * max ) ) << ( numBits * 0 ); + return bits; +} + +/* +================ +idBitMsg::BitsToDir +================ +*/ +idVec3 idBitMsg::BitsToDir( int bits, int numBits ) { + static float sign[2] = { 1.0f, -1.0f }; + int max; + float invMax; + idVec3 dir; + + assert( numBits >= 6 && numBits <= 32 ); + + numBits /= 3; + max = ( 1 << ( numBits - 1 ) ) - 1; + invMax = 1.0f / max; + + dir.x = sign[( bits >> ( numBits * 3 - 1 ) ) & 1] * ( ( bits >> ( numBits * 2 ) ) & max ) * invMax; + dir.y = sign[( bits >> ( numBits * 2 - 1 ) ) & 1] * ( ( bits >> ( numBits * 1 ) ) & max ) * invMax; + dir.z = sign[( bits >> ( numBits * 1 - 1 ) ) & 1] * ( ( bits >> ( numBits * 0 ) ) & max ) * invMax; + dir.NormalizeFast(); + return dir; +} + + +/* +============================================================================== + + idBitMsgDelta + +============================================================================== +*/ + +const int MAX_DATA_BUFFER = 1024; + +/* +================ +idBitMsgDelta::WriteBits +================ +*/ +void idBitMsgDelta::WriteBits( int value, int numBits ) { + if ( newBase ) { + newBase->WriteBits( value, numBits ); + } + + if ( !base ) { + writeDelta->WriteBits( value, numBits ); + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( baseValue == value ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteBits( value, numBits ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDelta +================ +*/ +void idBitMsgDelta::WriteDelta( int oldValue, int newValue, int numBits ) { + if ( newBase ) { + newBase->WriteBits( newValue, numBits ); + } + + if ( !base ) { + if ( oldValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteBits( newValue, numBits ); + } + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + if ( oldValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + changed = true; + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteBits( newValue, numBits ); + changed = true; + } + } + } +} + +/* +================ +idBitMsgDelta::ReadBits +================ +*/ +int idBitMsgDelta::ReadBits( int numBits ) const { + int value; + + if ( !base ) { + value = readDelta->ReadBits( numBits ); + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadBits( numBits ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, numBits ); + } + return value; +} + +/* +================ +idBitMsgDelta::ReadDelta +================ +*/ +int idBitMsgDelta::ReadDelta( int oldValue, int numBits ) const { + int value; + + if ( !base ) { + if ( readDelta->ReadBits( 1 ) == 0 ) { + value = oldValue; + } else { + value = readDelta->ReadBits( numBits ); + } + changed = true; + } else { + int baseValue = base->ReadBits( numBits ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else if ( readDelta->ReadBits( 1 ) == 0 ) { + value = oldValue; + changed = true; + } else { + value = readDelta->ReadBits( numBits ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, numBits ); + } + return value; +} + +/* +================ +idBitMsgDelta::WriteString +================ +*/ +void idBitMsgDelta::WriteString( const char *s, int maxLength ) { + if ( newBase ) { + newBase->WriteString( s, maxLength ); + } + + if ( !base ) { + writeDelta->WriteString( s, maxLength ); + changed = true; + } else { + char baseString[MAX_DATA_BUFFER]; + base->ReadString( baseString, sizeof( baseString ) ); + if ( idStr::Cmp( s, baseString ) == 0 ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteString( s, maxLength ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteData +================ +*/ +void idBitMsgDelta::WriteData( const void *data, int length ) { + if ( newBase ) { + newBase->WriteData( data, length ); + } + + if ( !base ) { + writeDelta->WriteData( data, length ); + changed = true; + } else { + byte baseData[MAX_DATA_BUFFER]; + assert( length < sizeof( baseData ) ); + base->ReadData( baseData, length ); + if ( memcmp( data, baseData, length ) == 0 ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteData( data, length ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDict +================ +*/ +void idBitMsgDelta::WriteDict( const idDict &dict ) { + if ( newBase ) { + newBase->WriteDeltaDict( dict, NULL ); + } + + if ( !base ) { + writeDelta->WriteDeltaDict( dict, NULL ); + changed = true; + } else { + idDict baseDict; + base->ReadDeltaDict( baseDict, NULL ); + changed = writeDelta->WriteDeltaDict( dict, &baseDict ); + } +} + +/* +================ +idBitMsgDelta::WriteDeltaByteCounter +================ +*/ +void idBitMsgDelta::WriteDeltaByteCounter( int oldValue, int newValue ) { + if ( newBase ) { + newBase->WriteBits( newValue, 8 ); + } + + if ( !base ) { + writeDelta->WriteDeltaByteCounter( oldValue, newValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 8 ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteDeltaByteCounter( oldValue, newValue ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDeltaShortCounter +================ +*/ +void idBitMsgDelta::WriteDeltaShortCounter( int oldValue, int newValue ) { + if ( newBase ) { + newBase->WriteBits( newValue, 16 ); + } + + if ( !base ) { + writeDelta->WriteDeltaShortCounter( oldValue, newValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 16 ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteDeltaShortCounter( oldValue, newValue ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::WriteDeltaLongCounter +================ +*/ +void idBitMsgDelta::WriteDeltaLongCounter( int oldValue, int newValue ) { + if ( newBase ) { + newBase->WriteBits( newValue, 32 ); + } + + if ( !base ) { + writeDelta->WriteDeltaLongCounter( oldValue, newValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 32 ); + if ( baseValue == newValue ) { + writeDelta->WriteBits( 0, 1 ); + } else { + writeDelta->WriteBits( 1, 1 ); + writeDelta->WriteDeltaLongCounter( oldValue, newValue ); + changed = true; + } + } +} + +/* +================ +idBitMsgDelta::ReadString +================ +*/ +void idBitMsgDelta::ReadString( char *buffer, int bufferSize ) const { + if ( !base ) { + readDelta->ReadString( buffer, bufferSize ); + changed = true; + } else { + char baseString[MAX_DATA_BUFFER]; + base->ReadString( baseString, sizeof( baseString ) ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + idStr::Copynz( buffer, baseString, bufferSize ); + } else { + readDelta->ReadString( buffer, bufferSize ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteString( buffer ); + } +} + +/* +================ +idBitMsgDelta::ReadData +================ +*/ +void idBitMsgDelta::ReadData( void *data, int length ) const { + if ( !base ) { + readDelta->ReadData( data, length ); + changed = true; + } else { + char baseData[MAX_DATA_BUFFER]; + assert( length < sizeof( baseData ) ); + base->ReadData( baseData, length ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + memcpy( data, baseData, length ); + } else { + readDelta->ReadData( data, length ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteData( data, length ); + } +} + +/* +================ +idBitMsgDelta::ReadDict +================ +*/ +void idBitMsgDelta::ReadDict( idDict &dict ) { + if ( !base ) { + readDelta->ReadDeltaDict( dict, NULL ); + changed = true; + } else { + idDict baseDict; + base->ReadDeltaDict( baseDict, NULL ); + if ( !readDelta ) { + dict = baseDict; + } else { + changed = readDelta->ReadDeltaDict( dict, &baseDict ); + } + } + + if ( newBase ) { + newBase->WriteDeltaDict( dict, NULL ); + } +} + +/* +================ +idBitMsgDelta::ReadDeltaByteCounter +================ +*/ +int idBitMsgDelta::ReadDeltaByteCounter( int oldValue ) const { + int value; + + if ( !base ) { + value = readDelta->ReadDeltaByteCounter( oldValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 8 ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadDeltaByteCounter( oldValue ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, 8 ); + } + return value; +} + +/* +================ +idBitMsgDelta::ReadDeltaShortCounter +================ +*/ +int idBitMsgDelta::ReadDeltaShortCounter( int oldValue ) const { + int value; + + if ( !base ) { + value = readDelta->ReadDeltaShortCounter( oldValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 16 ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadDeltaShortCounter( oldValue ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, 16 ); + } + return value; +} + +/* +================ +idBitMsgDelta::ReadDeltaLongCounter +================ +*/ +int idBitMsgDelta::ReadDeltaLongCounter( int oldValue ) const { + int value; + + if ( !base ) { + value = readDelta->ReadDeltaLongCounter( oldValue ); + changed = true; + } else { + int baseValue = base->ReadBits( 32 ); + if ( !readDelta || readDelta->ReadBits( 1 ) == 0 ) { + value = baseValue; + } else { + value = readDelta->ReadDeltaLongCounter( oldValue ); + changed = true; + } + } + + if ( newBase ) { + newBase->WriteBits( value, 32 ); + } + return value; +} + +/* +=========================================================================== +idMsgQueue +=========================================================================== +*/ + +/* +=============== +idMsgQueue::idMsgQueue +=============== +*/ +idMsgQueue::idMsgQueue( void ) { + Init( 0 ); +} + +/* +=============== +idMsgQueue::Init +=============== +*/ +void idMsgQueue::Init( int sequence ) { + first = last = sequence; + startIndex = endIndex = 0; +} + +/* +=============== +idMsgQueue::Add +=============== +*/ +bool idMsgQueue::Add( const byte *data, const int size, bool sequencing ) { + if ( GetSpaceLeft() < size + 8 ) { + return false; + } + + assert( size ); + + WriteUShort( size ); + if ( sequencing ) { + WriteLong( last ); + } + last++; + WriteData( data, size ); + return true; +} + +/* +=============== +idMsgQueue::AddConcat +=============== +*/ +bool idMsgQueue::AddConcat( const byte *data1, const int size1, const byte *data2, const int size2, bool sequencing ) { + if ( GetSpaceLeft() < size1 + size2 + 8 ) { + return false; + } + + assert( size1 && size2 ); + + WriteUShort( size1 + size2 ); + if ( sequencing ) { + WriteLong( last ); + } + last++; + WriteData( data1, size1 ); + WriteData( data2, size2 ); + return true; +} + +/* +=============== +idMsgQueue::Get +=============== +*/ +bool idMsgQueue::Get( byte *data, int dataSize, int &size, bool sequencing ) { + if ( sequencing ? ( first == last ) : ( startIndex == endIndex ) ) { + size = 0; + return false; + } + int sequence; + size = ReadUShort(); + if ( data && size > dataSize ) { + common->Error( "idMsgQueue::Get buffer size of %d < get size of %d", dataSize, size ); + } + + if ( sequencing ) { + sequence = ReadLong(); + assert( sequence == first ); + } + ReadData( data, size ); + first++; + return true; +} + +/* +=============== +idMsgQueue::GetTotalSize +=============== +*/ +int idMsgQueue::GetTotalSize( void ) const { + if ( startIndex <= endIndex ) { + return ( endIndex - startIndex ); + } else { + return ( sizeof( buffer ) - startIndex + endIndex ); + } +} + +/* +=============== +idMsgQueue::GetSpaceLeft +=============== +*/ +int idMsgQueue::GetSpaceLeft( void ) const { + if ( startIndex <= endIndex ) { + return sizeof( buffer ) - ( endIndex - startIndex ) - 1; + } else { + return ( startIndex - endIndex ) - 1; + } +} + +/* +=============== +idMsgQueue::CopyToBuffer +=============== +*/ +void idMsgQueue::CopyToBuffer( byte *buf ) const { + if ( startIndex <= endIndex ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( buf, buffer + startIndex, endIndex - startIndex ); +// RAVEN END + } else { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( buf, buffer + startIndex, sizeof( buffer ) - startIndex ); + SIMDProcessor->Memcpy( buf + sizeof( buffer ) - startIndex, buffer, endIndex ); +// RAVEN END + } +} + +/* +=============== +idMsgQueue::WriteByte +=============== +*/ +void idMsgQueue::WriteByte( byte b ) { + buffer[endIndex] = b; + endIndex = ( endIndex + 1 ) & ( MAX_MSG_QUEUE_SIZE - 1 ); +} + +/* +=============== +idMsgQueue::ReadByte +=============== +*/ +byte idMsgQueue::ReadByte( void ) { + byte b = buffer[startIndex]; + startIndex = ( startIndex + 1 ) & ( MAX_MSG_QUEUE_SIZE - 1 ); + return b; +} + +/* +=============== +idMsgQueue::WriteShort +=============== +*/ +void idMsgQueue::WriteShort( int s ) { + WriteByte( ( s >> 0 ) & 255 ); + WriteByte( ( s >> 8 ) & 255 ); +} + +/* +=============== +idMsgQueue::WriteUShort +=============== +*/ +void idMsgQueue::WriteUShort( int s ) { + WriteByte( ( s >> 0 ) & 255 ); + WriteByte( ( s >> 8 ) & 255 ); +} + +/* +=============== +idMsgQueue::ReadShort +=============== +*/ +int idMsgQueue::ReadShort( void ) { +// RAVEN BEGIN +// ddynerman: removed side-effecting bitwise or + byte l = ReadByte(); + byte h = ReadByte(); + return (short)(l | ( h << 8 )); // sign extend +// RAVEN LOW +} + +/* +=============== +idMsgQueue::ReadUShort +=============== +*/ +int idMsgQueue::ReadUShort( void ) { +// RAVEN BEGIN +// ddynerman: removed side-effecting bitwise or + byte l = ReadByte(); + byte h = ReadByte(); + return l | ( h << 8 ); +// RAVEN LOW +} + +/* +=============== +idMsgQueue::WriteLong +=============== +*/ +void idMsgQueue::WriteLong( int l ) { + WriteByte( ( l >> 0 ) & 255 ); + WriteByte( ( l >> 8 ) & 255 ); + WriteByte( ( l >> 16 ) & 255 ); + WriteByte( ( l >> 24 ) & 255 ); +} + +/* +=============== +idMsgQueue::ReadLong +=============== +*/ +int idMsgQueue::ReadLong( void ) { +// RAVEN BEGIN +// ddynerman: removed side-effecting bitwise or + byte ll = ReadByte(); + byte lh = ReadByte(); + byte hl = ReadByte(); + byte hh = ReadByte(); + return ll | ( lh << 8 ) | ( hl << 16 ) | ( hh << 24 ); +// RAVEN END +} + +/* +=============== +idMsgQueue::WriteData +=============== +*/ +void idMsgQueue::WriteData( const byte *data, const int size ) { + for ( int i = 0; i < size; i++ ) { + WriteByte( data[i] ); + } +} + +/* +=============== +idMsgQueue::ReadData +=============== +*/ +void idMsgQueue::ReadData( byte *data, const int size ) { + if ( data ) { + for ( int i = 0; i < size; i++ ) { + data[i] = ReadByte(); + } + } else { + for ( int i = 0; i < size; i++ ) { + ReadByte(); + } + } +} + +/* +=============== +idMsgQueue::WriteTo +=============== +*/ +void idMsgQueue::WriteTo( idBitMsg &msg ) { + msg.WriteUShort( GetTotalSize() ); + assert( startIndex == 0 ); + msg.WriteData( buffer + startIndex, endIndex - startIndex ); +} + +/* +=============== +idMsgQueue::FlushTo +=============== +*/ +void idMsgQueue::FlushTo( idBitMsg &msg ) { + WriteTo( msg ); + Init( 0 ); +} + +/* +=============== +idMsgQueue::ReadFrom +=============== +*/ +void idMsgQueue::ReadFrom( const idBitMsg &msg ) { + Init( 0 ); + endIndex = msg.ReadUShort(); + msg.ReadData( buffer, endIndex ); +} + +/* +=============== +idMsgQueue::Save +=============== +*/ +void idMsgQueue::Save( idFile *file ) const { + file->WriteInt( first ); + file->WriteInt( last ); + file->WriteInt( startIndex ); + file->WriteInt( endIndex ); + file->Write( buffer + startIndex, endIndex - startIndex ); +} +/* +=============== +idMsgQueue::Restore +=============== +*/ +void idMsgQueue::Restore( idFile *file ) { + file->ReadInt( first ); + file->ReadInt( last ); + file->ReadInt( startIndex ); + file->ReadInt( endIndex ); + file->Read( buffer + startIndex, endIndex - startIndex ); +} + +/* +=============== +idBitMsgQueue::idBitMsgQueue +=============== +*/ +idBitMsgQueue::idBitMsgQueue( void ) { + Init(); +} + +/* +=============== +idBitMsgQueue::Init +=============== +*/ +void idBitMsgQueue::Init( void ) { + readTimestamp = false; + writeList.Clear(); + readList.Clear(); +} + +/* +=============== +idBitMsgQueue::Add +=============== +*/ +void idBitMsgQueue::Add( const idBitMsg &msg, const int timestamp ) { + while ( writeList.NextNode() && writeList.Next()->GetSpaceLeft() < ( msg.GetSize() + 8+4 ) ) { + writeList.NextNode()->AddToEnd( readList ); + } + + if ( !writeList.NextNode() ) { + idLinkList< idMsgQueue > *node = new idLinkList< idMsgQueue >; + node->SetOwner( new idMsgQueue ); + node->AddToEnd( writeList ); + } + + writeList.Next()->WriteLong( timestamp ); + if ( !writeList.Next()->Add( msg.GetData(), msg.GetSize(), false ) ) { + assert( false ); + } +} + +/* +=============== +idBitMsgQueue::Get +=============== +*/ +bool idBitMsgQueue::Get( idBitMsg &msg, int ×tamp ) { + while ( readList.NextNode() && !readList.Next()->GetTotalSize() ) { + readList.Next()->Init( 0 ); + readList.NextNode()->AddToEnd( writeList ); + } + + if ( readList.NextNode() ) { + int size; + + timestamp = readTimestamp ? nextTimestamp : readList.Next()->ReadLong(); + readTimestamp = true; + if ( readList.Next()->Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + readTimestamp = false; + return true; + } + + assert( false ); + } else if ( writeList.NextNode() && writeList.Next()->GetTotalSize() ) { + int size; + + timestamp = readTimestamp ? nextTimestamp : writeList.Next()->ReadLong(); + readTimestamp = true; + if ( writeList.Next()->Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + readTimestamp = false; + return true; + } + + assert( false ); + } + + msg.SetSize( 0 ); + return false; +} + +/* +=============== +idBitMsgQueue::GetTimestamp +=============== +*/ +bool idBitMsgQueue::GetTimestamp( int ×tamp ) { + if ( readTimestamp ) { + timestamp = nextTimestamp; + return true; + } + + while ( readList.NextNode() && !readList.Next()->GetTotalSize() ) { + readList.Next()->Init( 0 ); + readList.NextNode()->AddToEnd( writeList ); + } + + if ( readList.NextNode() ) { + timestamp = nextTimestamp = readList.Next()->ReadLong(); + readTimestamp = true; + return true; + } else if ( writeList.NextNode() && writeList.Next()->GetTotalSize() ) { + timestamp = nextTimestamp = writeList.Next()->ReadLong(); + readTimestamp = true; + return true; + } + + return false; +} diff --git a/src/idlib/BitMsg.h b/src/idlib/BitMsg.h new file mode 100644 index 0000000..2e6b02f --- /dev/null +++ b/src/idlib/BitMsg.h @@ -0,0 +1,890 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BITMSG_H__ +#define __BITMSG_H__ + +/* +=============================================================================== + + idBitMsg + + Handles byte ordering and avoids alignment errors. + Allows concurrent writing and reading. + The data set with Init is never freed. + +=============================================================================== +*/ + +class idBitMsg { +public: + idBitMsg(); + ~idBitMsg() {} + + void Init( byte *data, int length ); + void Init( const byte *data, int length ); + byte * GetData( void ); // get data for writing + const byte * GetData( void ) const; // get data for reading + const byte * GetReadData( void ) const; + int GetMaxSize( void ) const; // get the maximum message size + void SetAllowOverflow( bool set ); // generate error if not set and message is overflowed + bool IsOverflowed( void ) const; // returns true if the message was overflowed + + int GetSize( void ) const; // size of the message in bytes + void SetSize( int size ); // set the message size + int GetWriteBit( void ) const; // get current write bit + void SetWriteBit( int bit ); // set current write bit + int GetNumBitsWritten( void ) const; // returns number of bits written + int GetRemainingSpace( void ) const; // space left in bytes for writing + int GetRemainingWriteBits( void ) const; // space left in bits for writing + void SaveWriteState( int &s, int &b ) const; // save the write state + void RestoreWriteState( int s, int b ); // restore the write state + + int GetReadCount( void ) const; // bytes read so far + void SetReadCount( int bytes ); // set the number of bytes and bits read + int GetReadBit( void ) const; // get current read bit + void SetReadBit( int bit ); // set current read bit + int GetNumBitsRead( void ) const; // returns number of bits read + int GetRemainingData( void ) const; // number of bytes left to read + int GetRemainingReadBits( void ) const; // number of bits left to read + void SaveReadState( int &c, int &b ) const; // save the read state + void RestoreReadState( int c, int b ) const; // restore the read state + + void BeginWriting( void ); // begin writing + void WriteByteAlign( void ); // write up to the next byte boundary + void WriteBits( int value, int numBits ); // write the specified number of bits + void WriteChar( int c ); + void WriteByte( int c ); + void WriteShort( int c ); + void WriteUShort( int c ); + void WriteLong( int c ); + void WriteFloat( float f ); + void WriteFloat( float f, int exponentBits, int mantissaBits ); + void WriteAngle8( float f ); + void WriteAngle16( float f ); + void WriteDir( const idVec3 &dir, int numBits ); + void WriteString( const char *s, int maxLength = -1 ); + void WriteData( const void *data, int length ); + void WriteNetadr( const netadr_t adr ); + + void WriteDeltaChar( int oldValue, int newValue ); + void WriteDeltaByte( int oldValue, int newValue ); + void WriteDeltaShort( int oldValue, int newValue ); + void WriteDeltaLong( int oldValue, int newValue ); + void WriteDeltaFloat( float oldValue, float newValue ); + void WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ); + void WriteDeltaByteCounter( int oldValue, int newValue ); + void WriteDeltaShortCounter( int oldValue, int newValue ); + void WriteDeltaLongCounter( int oldValue, int newValue ); + bool WriteDeltaDict( const idDict &dict, const idDict *base ); + + void BeginReading( void ) const; // begin reading. + void ReadByteAlign( void ) const; // read up to the next byte boundary + int ReadBits( int numBits ) const; // read the specified number of bits + int ReadChar( void ) const; + int ReadByte( void ) const; + int ReadShort( void ) const; + int ReadUShort( void ) const; + int ReadLong( void ) const; + float ReadFloat( void ) const; + float ReadFloat( int exponentBits, int mantissaBits ) const; + float ReadAngle8( void ) const; + float ReadAngle16( void ) const; + idVec3 ReadDir( int numBits ) const; + int ReadString( char *buffer, int bufferSize ) const; + int ReadData( void *data, int length ) const; + void ReadNetadr( netadr_t *adr ) const; + + int ReadDeltaChar( int oldValue ) const; + int ReadDeltaByte( int oldValue ) const; + int ReadDeltaShort( int oldValue ) const; + int ReadDeltaLong( int oldValue ) const; + float ReadDeltaFloat( float oldValue ) const; + float ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const; + int ReadDeltaByteCounter( int oldValue ) const; + int ReadDeltaShortCounter( int oldValue ) const; + int ReadDeltaLongCounter( int oldValue ) const; + bool ReadDeltaDict( idDict &dict, const idDict *base ) const; + + static int DirToBits( const idVec3 &dir, int numBits ); + static idVec3 BitsToDir( int bits, int numBits ); + +private: + byte * writeData; // pointer to data for writing + const byte * readData; // pointer to data for reading + int maxSize; // maximum size of message in bytes + int curSize; // current size of message in bytes + int writeBit; // number of bits written to the last written byte + mutable int readCount; // number of bytes read so far + mutable int readBit; // number of bits read from the last read byte + int numValueOverflows; // number of values that did not fit their requested bit width + bool allowOverflow; // if false, generate an error when the message is overflowed + bool overflowed; // set to true if the buffer size failed (with allowOverflow set) + +private: + bool CheckOverflow( int numBits ); + byte * GetByteSpace( int length ); + void WriteDelta( int oldValue, int newValue, int numBits ); + int ReadDelta( int oldValue, int numBits ) const; +}; + + +ID_INLINE void idBitMsg::Init( byte *data, int length ) { + writeData = data; + readData = data; + maxSize = length; +} + +ID_INLINE void idBitMsg::Init( const byte *data, int length ) { + writeData = NULL; + readData = data; + maxSize = length; +} + +ID_INLINE byte *idBitMsg::GetData( void ) { + return writeData; +} + +ID_INLINE const byte *idBitMsg::GetData( void ) const { + return readData; +} + +ID_INLINE const byte *idBitMsg::GetReadData( void ) const { + return ( readData + readCount ); +} + +ID_INLINE int idBitMsg::GetMaxSize( void ) const { + return maxSize; +} + +ID_INLINE void idBitMsg::SetAllowOverflow( bool set ) { + allowOverflow = set; +} + +ID_INLINE bool idBitMsg::IsOverflowed( void ) const { + return overflowed; +} + +ID_INLINE int idBitMsg::GetSize( void ) const { + return curSize; +} + +ID_INLINE void idBitMsg::SetSize( int size ) { + if ( size > maxSize ) { + curSize = maxSize; + } else { + curSize = size; + } +} + +ID_INLINE int idBitMsg::GetWriteBit( void ) const { + return writeBit; +} + +ID_INLINE void idBitMsg::SetWriteBit( int bit ) { + writeBit = bit & 7; + if ( writeBit ) { + writeData[curSize - 1] &= ( 1 << writeBit ) - 1; + } +} + +ID_INLINE int idBitMsg::GetNumBitsWritten( void ) const { +// return ( ( curSize << 3 ) - ( writeBit ? 8 - writeBit : 0 ) ); + return ( ( curSize << 3 ) - ( ( 8 - writeBit ) & 7 ) ); +} + +ID_INLINE int idBitMsg::GetRemainingSpace( void ) const { + return maxSize - curSize; +} + +ID_INLINE int idBitMsg::GetRemainingWriteBits( void ) const { + return ( maxSize << 3 ) - GetNumBitsWritten(); +} + +ID_INLINE void idBitMsg::SaveWriteState( int &s, int &b ) const { + s = curSize; + b = writeBit; +} + +ID_INLINE void idBitMsg::RestoreWriteState( int s, int b ) { + curSize = s; + writeBit = b & 7; + if ( writeBit ) { + writeData[curSize - 1] &= ( 1 << writeBit ) - 1; + } +} + +ID_INLINE int idBitMsg::GetReadCount( void ) const { + return readCount; +} + +ID_INLINE void idBitMsg::SetReadCount( int bytes ) { + readCount = bytes; +} + +ID_INLINE int idBitMsg::GetReadBit( void ) const { + return readBit; +} + +ID_INLINE void idBitMsg::SetReadBit( int bit ) { + readBit = bit & 7; +} + +ID_INLINE int idBitMsg::GetNumBitsRead( void ) const { +// return ( ( readCount << 3 ) - ( readBit ? 8 - readBit : 0 ) ); + return ( ( readCount << 3 ) - ( ( 8 - readBit ) & 7 ) ); +} + +ID_INLINE int idBitMsg::GetRemainingData( void ) const { + return curSize - readCount; +} + +ID_INLINE int idBitMsg::GetRemainingReadBits( void ) const { + return ( curSize << 3 ) - GetNumBitsRead(); +} + +ID_INLINE void idBitMsg::SaveReadState( int &c, int &b ) const { + c = readCount; + b = readBit; +} + +ID_INLINE void idBitMsg::RestoreReadState( int c, int b ) const { + readCount = c; + readBit = b & 7; +} + +ID_INLINE void idBitMsg::BeginWriting( void ) { + curSize = 0; + numValueOverflows = 0; + overflowed = false; + writeBit = 0; +} + +ID_INLINE void idBitMsg::WriteByteAlign( void ) { + writeBit = 0; +} + +ID_INLINE void idBitMsg::WriteChar( int c ) { + WriteBits( c, -8 ); +} + +ID_INLINE void idBitMsg::WriteByte( int c ) { + WriteBits( c, 8 ); +} + +ID_INLINE void idBitMsg::WriteShort( int c ) { + WriteBits( c, -16 ); +} + +ID_INLINE void idBitMsg::WriteUShort( int c ) { + WriteBits( c, 16 ); +} + +ID_INLINE void idBitMsg::WriteLong( int c ) { + WriteBits( c, 32 ); +} + +ID_INLINE void idBitMsg::WriteFloat( float f ) { + WriteBits( *reinterpret_cast(&f), 32 ); +} + +ID_INLINE void idBitMsg::WriteFloat( float f, int exponentBits, int mantissaBits ) { + int bits = idMath::FloatToBits( f, exponentBits, mantissaBits ); + WriteBits( bits, 1 + exponentBits + mantissaBits ); +} + +ID_INLINE void idBitMsg::WriteAngle8( float f ) { + WriteByte( ANGLE2BYTE( f ) ); +} + +ID_INLINE void idBitMsg::WriteAngle16( float f ) { + WriteShort( ANGLE2SHORT(f) ); +} + +ID_INLINE void idBitMsg::WriteDir( const idVec3 &dir, int numBits ) { + WriteBits( DirToBits( dir, numBits ), numBits ); +} + +ID_INLINE void idBitMsg::WriteDeltaChar( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -8 ); +} + +ID_INLINE void idBitMsg::WriteDeltaByte( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 8 ); +} + +ID_INLINE void idBitMsg::WriteDeltaShort( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -16 ); +} + +ID_INLINE void idBitMsg::WriteDeltaLong( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 32 ); +} + +ID_INLINE void idBitMsg::WriteDeltaFloat( float oldValue, float newValue ) { + WriteDelta( *reinterpret_cast(&oldValue), *reinterpret_cast(&newValue), 32 ); +} + +ID_INLINE void idBitMsg::WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ) { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = idMath::FloatToBits( newValue, exponentBits, mantissaBits ); + WriteDelta( oldBits, newBits, 1 + exponentBits + mantissaBits ); +} + +ID_INLINE void idBitMsg::BeginReading( void ) const { + readCount = 0; + readBit = 0; +} + +ID_INLINE void idBitMsg::ReadByteAlign( void ) const { + readBit = 0; +} + +ID_INLINE int idBitMsg::ReadChar( void ) const { + return (signed char)ReadBits( -8 ); +} + +ID_INLINE int idBitMsg::ReadByte( void ) const { + return (unsigned char)ReadBits( 8 ); +} + +ID_INLINE int idBitMsg::ReadShort( void ) const { + return (short)ReadBits( -16 ); +} + +ID_INLINE int idBitMsg::ReadUShort( void ) const { + return (unsigned short)ReadBits( 16 ); +} + +ID_INLINE int idBitMsg::ReadLong( void ) const { + return ReadBits( 32 ); +} + +ID_INLINE float idBitMsg::ReadFloat( void ) const { + float value; + *reinterpret_cast(&value) = ReadBits( 32 ); + return value; +} + +ID_INLINE float idBitMsg::ReadFloat( int exponentBits, int mantissaBits ) const { + int bits = ReadBits( 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( bits, exponentBits, mantissaBits ); +} + +ID_INLINE float idBitMsg::ReadAngle8( void ) const { + return BYTE2ANGLE( ReadByte() ); +} + +ID_INLINE float idBitMsg::ReadAngle16( void ) const { + return SHORT2ANGLE( ReadShort() ); +} + +ID_INLINE idVec3 idBitMsg::ReadDir( int numBits ) const { + return BitsToDir( ReadBits( numBits ), numBits ); +} + +ID_INLINE int idBitMsg::ReadDeltaChar( int oldValue ) const { + return (signed char)ReadDelta( oldValue, -8 ); +} + +ID_INLINE int idBitMsg::ReadDeltaByte( int oldValue ) const { + return (unsigned char)ReadDelta( oldValue, 8 ); +} + +ID_INLINE int idBitMsg::ReadDeltaShort( int oldValue ) const { + return (short)ReadDelta( oldValue, -16 ); +} + +ID_INLINE int idBitMsg::ReadDeltaLong( int oldValue ) const { + return ReadDelta( oldValue, 32 ); +} + +ID_INLINE float idBitMsg::ReadDeltaFloat( float oldValue ) const { + float value; + *reinterpret_cast(&value) = ReadDelta( *reinterpret_cast(&oldValue), 32 ); + return value; +} + +ID_INLINE float idBitMsg::ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = ReadDelta( oldBits, 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( newBits, exponentBits, mantissaBits ); +} + + +/* +=============================================================================== + + idBitMsgDelta + +=============================================================================== +*/ + +class idBitMsgDelta { +public: + idBitMsgDelta(); + ~idBitMsgDelta() {} + + void InitWriting( const idBitMsg *base, idBitMsg *newBase, idBitMsg *delta ); + void InitReading( const idBitMsg *base, idBitMsg *newBase, const idBitMsg *delta ); + bool HasChanged( void ) const; + + void WriteBits( int value, int numBits ); + void WriteChar( int c ); + void WriteByte( int c ); + void WriteShort( int c ); + void WriteUShort( int c ); + void WriteLong( int c ); + void WriteFloat( float f ); + void WriteFloat( float f, int exponentBits, int mantissaBits ); +// RAVEN BEGIN +// abahr: + void WriteVec3( const idVec3& v ); + void WriteDeltaVec3( const idVec3& oldValue, const idVec3& newValue ); + void WriteVec4( const idVec4& v ); + void WriteDeltaVec4( const idVec4& oldValue, const idVec4& newValue ); + void WriteQuat( const idQuat& q ); + void WriteDeltaQuat( const idQuat& oldValue, const idQuat& newValue ); + void WriteMat3( const idMat3& m ); + void WriteDeltaMat3( const idMat3& oldValue, const idMat3& newValue ); +// RAVEN END + void WriteAngle8( float f ); + void WriteAngle16( float f ); + void WriteDir( const idVec3 &dir, int numBits ); + void WriteString( const char *s, int maxLength = -1 ); + void WriteData( const void *data, int length ); + void WriteDict( const idDict &dict ); + + void WriteDeltaChar( int oldValue, int newValue ); + void WriteDeltaByte( int oldValue, int newValue ); + void WriteDeltaShort( int oldValue, int newValue ); + void WriteDeltaLong( int oldValue, int newValue ); + void WriteDeltaFloat( float oldValue, float newValue ); + void WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ); + void WriteDeltaByteCounter( int oldValue, int newValue ); + void WriteDeltaShortCounter( int oldValue, int newValue ); + void WriteDeltaLongCounter( int oldValue, int newValue ); + + int ReadBits( int numBits ) const; + int ReadChar( void ) const; + int ReadByte( void ) const; + int ReadShort( void ) const; + int ReadUShort( void ) const; + int ReadLong( void ) const; + float ReadFloat( void ) const; + float ReadFloat( int exponentBits, int mantissaBits ) const; +// RAVEN BEGIN +// abahr + idVec3 ReadVec3( void ) const; + idVec3 ReadDeltaVec3( const idVec3& oldValue ) const; + idVec4 ReadVec4( void ) const; + idVec4 ReadDeltaVec4( const idVec4& oldValue ) const; + idQuat ReadQuat( void ) const; + idQuat ReadDeltaQuat( const idQuat& oldValue ) const; + idMat3 ReadMat3( void ) const; + idMat3 ReadDeltaMat3( const idMat3& oldValue ) const; +// RAVEN END + float ReadAngle8( void ) const; + float ReadAngle16( void ) const; + idVec3 ReadDir( int numBits ) const; + void ReadString( char *buffer, int bufferSize ) const; + void ReadData( void *data, int length ) const; + void ReadDict( idDict &dict ); + + int ReadDeltaChar( int oldValue ) const; + int ReadDeltaByte( int oldValue ) const; + int ReadDeltaShort( int oldValue ) const; + int ReadDeltaLong( int oldValue ) const; + float ReadDeltaFloat( float oldValue ) const; + float ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const; + int ReadDeltaByteCounter( int oldValue ) const; + int ReadDeltaShortCounter( int oldValue ) const; + int ReadDeltaLongCounter( int oldValue ) const; + +private: + const idBitMsg *base; // base + idBitMsg * newBase; // new base + idBitMsg * writeDelta; // delta from base to new base for writing + const idBitMsg *readDelta; // delta from base to new base for reading + mutable bool changed; // true if the new base is different from the base + +private: + void WriteDelta( int oldValue, int newValue, int numBits ); + int ReadDelta( int oldValue, int numBits ) const; +}; + +ID_INLINE idBitMsgDelta::idBitMsgDelta() { + base = NULL; + newBase = NULL; + writeDelta = NULL; + readDelta = NULL; + changed = false; +} + +ID_INLINE void idBitMsgDelta::InitWriting( const idBitMsg *base, idBitMsg *newBase, idBitMsg *delta ) { + this->base = base; + this->newBase = newBase; + this->writeDelta = delta; + this->readDelta = delta; + this->changed = false; +} + +ID_INLINE void idBitMsgDelta::InitReading( const idBitMsg *base, idBitMsg *newBase, const idBitMsg *delta ) { + this->base = base; + this->newBase = newBase; + this->writeDelta = NULL; + this->readDelta = delta; + this->changed = false; +} + +ID_INLINE bool idBitMsgDelta::HasChanged( void ) const { + return changed; +} + +ID_INLINE void idBitMsgDelta::WriteChar( int c ) { + WriteBits( c, -8 ); +} + +ID_INLINE void idBitMsgDelta::WriteByte( int c ) { + WriteBits( c, 8 ); +} + +ID_INLINE void idBitMsgDelta::WriteShort( int c ) { + WriteBits( c, -16 ); +} + +ID_INLINE void idBitMsgDelta::WriteUShort( int c ) { + WriteBits( c, 16 ); +} + +ID_INLINE void idBitMsgDelta::WriteLong( int c ) { + WriteBits( c, 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteFloat( float f ) { + WriteBits( *reinterpret_cast(&f), 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteFloat( float f, int exponentBits, int mantissaBits ) { + int bits = idMath::FloatToBits( f, exponentBits, mantissaBits ); + WriteBits( bits, 1 + exponentBits + mantissaBits ); +} + +// RAVEN BEGIN +// abahr +ID_INLINE void idBitMsgDelta::WriteVec3( const idVec3& v ) { + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + WriteFloat( v[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaVec3( const idVec3& oldValue, const idVec3& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaFloat( oldValue[ix], newValue[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteVec4( const idVec4& v ) { + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + WriteFloat( v[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaVec4( const idVec4& oldValue, const idVec4& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaFloat( oldValue[ix], newValue[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteQuat( const idQuat& q ) { + for( int ix = 0; ix < q.GetDimension(); ++ix ) { + WriteFloat( q[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaQuat( const idQuat& oldValue, const idQuat& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaFloat( oldValue[ix], newValue[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteMat3( const idMat3& m ) { + for( int ix = 0; ix < m.GetVec3Dimension(); ++ix ) { + WriteVec3( m[ix] ); + } +} + +ID_INLINE void idBitMsgDelta::WriteDeltaMat3( const idMat3& oldValue, const idMat3& newValue ) { + for( int ix = 0; ix < oldValue.GetDimension(); ++ix ) { + WriteDeltaVec3( oldValue[ix], newValue[ix] ); + } +} +// RAVEN END + +ID_INLINE void idBitMsgDelta::WriteAngle8( float f ) { + WriteBits( ANGLE2BYTE( f ), 8 ); +} + +ID_INLINE void idBitMsgDelta::WriteAngle16( float f ) { + WriteBits( ANGLE2SHORT(f), 16 ); +} + +ID_INLINE void idBitMsgDelta::WriteDir( const idVec3 &dir, int numBits ) { + WriteBits( idBitMsg::DirToBits( dir, numBits ), numBits ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaChar( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -8 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaByte( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 8 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaShort( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, -16 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaLong( int oldValue, int newValue ) { + WriteDelta( oldValue, newValue, 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaFloat( float oldValue, float newValue ) { + WriteDelta( *reinterpret_cast(&oldValue), *reinterpret_cast(&newValue), 32 ); +} + +ID_INLINE void idBitMsgDelta::WriteDeltaFloat( float oldValue, float newValue, int exponentBits, int mantissaBits ) { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = idMath::FloatToBits( newValue, exponentBits, mantissaBits ); + WriteDelta( oldBits, newBits, 1 + exponentBits + mantissaBits ); +} + +ID_INLINE int idBitMsgDelta::ReadChar( void ) const { + return (signed char)ReadBits( -8 ); +} + +ID_INLINE int idBitMsgDelta::ReadByte( void ) const { + return (unsigned char)ReadBits( 8 ); +} + +ID_INLINE int idBitMsgDelta::ReadShort( void ) const { + return (short)ReadBits( -16 ); +} + +ID_INLINE int idBitMsgDelta::ReadUShort( void ) const { + return (unsigned short)ReadBits( 16 ); +} + +ID_INLINE int idBitMsgDelta::ReadLong( void ) const { + return ReadBits( 32 ); +} + +ID_INLINE float idBitMsgDelta::ReadFloat( void ) const { + float value; + *reinterpret_cast(&value) = ReadBits( 32 ); + return value; +} + +ID_INLINE float idBitMsgDelta::ReadFloat( int exponentBits, int mantissaBits ) const { + int bits = ReadBits( 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( bits, exponentBits, mantissaBits ); +} + +// RAVEN BEGIN +// abahr +ID_INLINE idVec3 idBitMsgDelta::ReadVec3() const { + idVec3 v; + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + v[ix] = ReadFloat(); + } + + return v; +} + +ID_INLINE idVec3 idBitMsgDelta::ReadDeltaVec3( const idVec3& oldValue ) const { + idVec3 value; + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + value[ix] = ReadDeltaFloat( oldValue[ix] ); + } + return value; +} + +ID_INLINE idVec4 idBitMsgDelta::ReadVec4( void ) const { + idVec4 v; + for( int ix = 0; ix < v.GetDimension(); ++ix ) { + v[ix] = ReadFloat(); + } + + return v; +} + +ID_INLINE idVec4 idBitMsgDelta::ReadDeltaVec4( const idVec4& oldValue ) const { + idVec4 value; + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + value[ix] = ReadDeltaFloat( oldValue[ix] ); + } + return value; +} + +ID_INLINE idQuat idBitMsgDelta::ReadQuat( void ) const { + idQuat q; + for( int ix = 0; ix < q.GetDimension(); ++ix ) { + q[ix] = ReadFloat(); + } + + return q; +} + +ID_INLINE idQuat idBitMsgDelta::ReadDeltaQuat( const idQuat& oldValue ) const { + idQuat value; + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + value[ix] = ReadDeltaFloat( oldValue[ix] ); + } + return value; +} + +ID_INLINE idMat3 idBitMsgDelta::ReadMat3() const { + idMat3 m; + + for( int ix = 0; ix < m.GetVec3Dimension(); ++ix ) { + m[ix] = ReadVec3(); + } + + return m; +} + +ID_INLINE idMat3 idBitMsgDelta::ReadDeltaMat3( const idMat3& oldValue ) const { + idMat3 value; + + for( int ix = 0; ix < value.GetVec3Dimension(); ++ix ) { + value[ix] = ReadDeltaVec3( oldValue[ix] ); + } + + return value; +} +// RAVEN END + +ID_INLINE float idBitMsgDelta::ReadAngle8( void ) const { + return BYTE2ANGLE( ReadByte() ); +} + +ID_INLINE float idBitMsgDelta::ReadAngle16( void ) const { + return SHORT2ANGLE( ReadShort() ); +} + +ID_INLINE idVec3 idBitMsgDelta::ReadDir( int numBits ) const { + return idBitMsg::BitsToDir( ReadBits( numBits ), numBits ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaChar( int oldValue ) const { + return (signed char)ReadDelta( oldValue, -8 ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaByte( int oldValue ) const { + return (unsigned char)ReadDelta( oldValue, 8 ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaShort( int oldValue ) const { + return (short)ReadDelta( oldValue, -16 ); +} + +ID_INLINE int idBitMsgDelta::ReadDeltaLong( int oldValue ) const { + return ReadDelta( oldValue, 32 ); +} + +ID_INLINE float idBitMsgDelta::ReadDeltaFloat( float oldValue ) const { + float value; + *reinterpret_cast(&value) = ReadDelta( *reinterpret_cast(&oldValue), 32 ); + return value; +} + +ID_INLINE float idBitMsgDelta::ReadDeltaFloat( float oldValue, int exponentBits, int mantissaBits ) const { + int oldBits = idMath::FloatToBits( oldValue, exponentBits, mantissaBits ); + int newBits = ReadDelta( oldBits, 1 + exponentBits + mantissaBits ); + return idMath::BitsToFloat( newBits, exponentBits, mantissaBits ); +} + +#define MAX_MSG_QUEUE_SIZE 16384 // must be a power of 2 + +class idMsgQueue { +public: + idMsgQueue(); + + void Init( int sequence ); + + bool Add( const byte *data, const int size, bool sequencing ); + // prepend to a message without the need to a memcopy and readjust + bool AddConcat( const byte *data1, const int size1, const byte *data2, const int size2, bool sequencing ); + bool Get( byte *data, int dataSize, int &size, bool sequencing ); + int GetTotalSize( void ) const; + int GetSpaceLeft( void ) const; + int GetFirst( void ) const { return first; } + int GetLast( void ) const { return last; } + void CopyToBuffer( byte *buf ) const; + + void WriteTo( idBitMsg &msg ); + void FlushTo( idBitMsg &msg ); + void ReadFrom( const idBitMsg &msg ); + + void Save( idFile *file ) const; + void Restore( idFile *file ); + +private: + byte buffer[MAX_MSG_QUEUE_SIZE]; + int first; // sequence number of first message in queue + int last; // sequence number of last message in queue + int startIndex; // index pointing to the first byte of the first message + int endIndex; // index pointing to the first byte after the last message + +public: + void WriteByte( byte b ); + byte ReadByte( void ); + void WriteShort( int s ); + void WriteUShort( int s ); + int ReadShort( void ); + int ReadUShort( void ); + void WriteLong( int l ); + int ReadLong( void ); + void WriteData( const byte *data, const int size ); + void ReadData( byte *data, const int size ); +}; + +class idBitMsgQueue { +public: + idBitMsgQueue(); + + void Init( void ); + + void Add( const idBitMsg &msg, const int timestamp ); + bool Get( idBitMsg &msg, int ×tamp ); + bool Get( idBitMsg &msg ) { int dummy; return Get( msg, dummy); } + bool GetTimestamp( int ×tamp ); + +private: + int nextTimestamp; + bool readTimestamp; + idLinkList writeList, readList; +}; + +#endif /* !__BITMSG_H__ */ diff --git a/src/idlib/CMakeLists.txt b/src/idlib/CMakeLists.txt new file mode 100644 index 0000000..6af8e79 --- /dev/null +++ b/src/idlib/CMakeLists.txt @@ -0,0 +1,78 @@ +# This is the exact first-party idlib compiland set recorded in quake4.pdb. +# Additional SDK files remain in the tree for the game DLLs but are not linked +# into the reconstructed executable until evidence assigns them to the target. +set(Q4_IDLIB_SOURCES + BitMsg.cpp + CmdArgs.cpp + Dict.cpp + Heap.cpp + LangDict.cpp + Lexer.cpp + LexerFactory.cpp + Lib.cpp + mapfile.cpp + Parser.cpp + Str.cpp + Timer.cpp + Token.cpp + bv/Bounds.cpp + bv/Box.cpp + bv/Frustum.cpp + bv/Sphere.cpp + containers/HashIndex.cpp + geometry/JointTransform.cpp + geometry/Surface.cpp + geometry/Surface_Patch.cpp + geometry/TraceModel.cpp + geometry/Winding.cpp + math/Angles.cpp + math/Math.cpp + math/Matrix.cpp + math/Plane.cpp + math/Polynomial.cpp + math/Quat.cpp + math/Radians.cpp + math/Rotation.cpp + math/Simd.cpp + math/Simd_3DNow.cpp + math/Simd_generic.cpp + math/Simd_MMX.cpp + math/Simd_SSE.cpp + math/Simd_SSE2.cpp + math/Simd_SSE3.cpp + math/Vector.cpp + hashing/CRC32.cpp + hashing/MD4.cpp + hashing/MD5.cpp +) + +add_library(q4_idlib STATIC ${Q4_IDLIB_SOURCES}) + +target_include_directories(q4_idlib + PUBLIC + ${PROJECT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE + "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)/Include" +) + +target_compile_definitions(q4_idlib + PRIVATE + WIN32 + _WINDOWS + _LOAD_DLL + _USE_32BIT_TIME_T + Q4_NO_PUNKBUSTER + Q4_RECON_SEED + Q4_RECON_ENGINE_PRIVATE + Q4_RECON_SDK_MSGQUEUE + $<$:_DEBUG> + $<$:NDEBUG;_FINAL> +) + +target_precompile_headers(q4_idlib PRIVATE precompiled.h) + +set_target_properties(q4_idlib PROPERTIES + OUTPUT_NAME idlib + FOLDER "Engine" +) diff --git a/src/idlib/CmdArgs.cpp b/src/idlib/CmdArgs.cpp new file mode 100644 index 0000000..782294a --- /dev/null +++ b/src/idlib/CmdArgs.cpp @@ -0,0 +1,193 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +/* +============ +idCmdArgs::operator= +============ +*/ +void idCmdArgs::operator=( const idCmdArgs &args ) { + int i; + + argc = args.argc; + memcpy( tokenized, args.tokenized, MAX_COMMAND_STRING ); + for ( i = 0; i < argc; i++ ) { + argv[ i ] = tokenized + ( args.argv[ i ] - args.tokenized ); + } +} + +/* +============ +idCmdArgs::Args +============ +*/ +const char *idCmdArgs::Args( int start, int end, bool escapeArgs ) const { + static char cmd_args[MAX_COMMAND_STRING]; + int i; + + if ( end < 0 ) { + end = argc - 1; + } else if ( end >= argc ) { + end = argc - 1; + } + cmd_args[0] = '\0'; + if ( escapeArgs ) { + strcat( cmd_args, "\"" ); + } + for ( i = start; i <= end; i++ ) { + if ( i > start ) { + if ( escapeArgs ) { + strcat( cmd_args, "\" \"" ); + } else { + strcat( cmd_args, " " ); + } + } + if ( escapeArgs && strchr( argv[i], '\\' ) ) { + char *p = argv[i]; + while ( *p != '\0' ) { + if ( *p == '\\' ) { + strcat( cmd_args, "\\\\" ); + } else { + int l = strlen( cmd_args ); + cmd_args[ l ] = *p; + cmd_args[ l+1 ] = '\0'; + } + p++; + } + } else { + strcat( cmd_args, argv[i] ); + } + } + if ( escapeArgs ) { + strcat( cmd_args, "\"" ); + } + + return cmd_args; +} + +/* +============ +idCmdArgs::TokenizeString + +Parses the given string into command line tokens. +The text is copied to a separate buffer and 0 characters +are inserted in the appropriate place. The argv array +will point into this temporary buffer. +============ +*/ +void idCmdArgs::TokenizeString( const char *text, bool keepAsStrings ) { + idLexer lex; + idToken token, number; + int len, totalLen; + + // clear previous args + argc = 0; + + if ( !text ) { + return; + } + + lex.LoadMemory( text, strlen( text ), "idCmdSystemLocal::TokenizeString" ); + lex.SetFlags( LEXFL_NOERRORS + | LEXFL_NOWARNINGS + | LEXFL_NOSTRINGCONCAT + | LEXFL_ALLOWPATHNAMES + | LEXFL_NOSTRINGESCAPECHARS + | LEXFL_ALLOWIPADDRESSES | ( keepAsStrings ? LEXFL_ONLYSTRINGS : 0 ) ); + + totalLen = 0; + + while ( 1 ) { + if ( argc == MAX_COMMAND_ARGS ) { + return; // this is usually something malicious + } + + if ( !lex.ReadToken( &token ) ) { + return; + } + + // check for negative numbers + if ( !keepAsStrings && ( token == "-" ) ) { + if ( lex.CheckTokenType( TT_NUMBER, 0, &number ) ) { + token = "-" + number; + } + } + + // check for cvar expansion + if ( token == "$" ) { + if ( !lex.ReadToken( &token ) ) { + return; + } + if ( idLib::cvarSystem ) { + token = idLib::cvarSystem->GetCVarString( token.c_str() ); + } else { + token = ""; + } + } + + len = token.Length(); + + if ( totalLen + len + 1 > sizeof( tokenized ) ) { + return; // this is usually something malicious + } + + // regular token + argv[argc] = tokenized + totalLen; + argc++; + + idStr::Copynz( tokenized + totalLen, token.c_str(), sizeof( tokenized ) - totalLen ); + + totalLen += len + 1; + } +} +/* +============ +idCmdArgs::AppendArg +============ +*/ +void idCmdArgs::AppendArg( const char *text ) { + if ( !argc ) { + argc = 1; + argv[ 0 ] = tokenized; + idStr::Copynz( tokenized, text, sizeof( tokenized ) ); + } else { + argv[ argc ] = argv[ argc-1 ] + strlen( argv[ argc-1 ] ) + 1; + idStr::Copynz( argv[ argc ], text, sizeof( tokenized ) - ( argv[ argc ] - tokenized ) ); + argc++; + } +} + +/* +============ +idCmdArgs::GetArgs +============ +*/ +const char **idCmdArgs::GetArgs( int *_argc ) { + *_argc = argc; + return (const char **)&argv[0]; +} + diff --git a/src/idlib/CmdArgs.h b/src/idlib/CmdArgs.h new file mode 100644 index 0000000..0e000e7 --- /dev/null +++ b/src/idlib/CmdArgs.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CMDARGS_H__ +#define __CMDARGS_H__ + +/* +=============================================================================== + + Command arguments. + +=============================================================================== +*/ + +class idCmdArgs { +public: + idCmdArgs( void ) { argc = 0; } + idCmdArgs( const char *text, bool keepAsStrings ) { TokenizeString( text, keepAsStrings ); } + + void operator=( const idCmdArgs &args ); + + // The functions that execute commands get their parameters with these functions. + int Argc( void ) const { return argc; } + // Argv() will return an empty string, not NULL if arg >= argc. + const char * Argv( int arg ) const { return ( arg >= 0 && arg < argc ) ? argv[arg] : ""; } + // Returns a single string containing argv(start) to argv(end) + // escapeArgs is a fugly way to put the string back into a state ready to tokenize again + const char * Args( int start = 1, int end = -1, bool escapeArgs = false ) const; + + // Takes a null terminated string and breaks the string up into arg tokens. + // Does not need to be /n terminated. + // Set keepAsStrings to true to only seperate tokens from whitespace and comments, ignoring punctuation + void TokenizeString( const char *text, bool keepAsStrings ); + + void AppendArg( const char *text ); + void Clear( void ) { argc = 0; } + const char ** GetArgs( int *argc ); + +private: + static const int MAX_COMMAND_ARGS = 64; + static const int MAX_COMMAND_STRING = 2 * MAX_STRING_CHARS; + + int argc; // number of arguments + char * argv[MAX_COMMAND_ARGS]; // points into tokenized + char tokenized[MAX_COMMAND_STRING]; // will have 0 bytes inserted +}; + +#endif /* !__CMDARGS_H__ */ diff --git a/src/idlib/Dict.cpp b/src/idlib/Dict.cpp new file mode 100644 index 0000000..0235cfd --- /dev/null +++ b/src/idlib/Dict.cpp @@ -0,0 +1,844 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +idStrPool idDict::globalKeys; +idStrPool idDict::globalValues; + +/* +================ +idDict::operator= + + clear existing key/value pairs and copy all key/value pairs from other +================ +*/ +idDict &idDict::operator=( const idDict &other ) { + int i; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + + // check for assignment to self + if ( this == &other ) { + return *this; + } + + Clear(); + + args = other.args; + argHash = other.argHash; + + for ( i = 0; i < args.Num(); i++ ) { + args[i].key = globalKeys.CopyString( args[i].key ); + args[i].value = globalValues.CopyString( args[i].value ); + } + + return *this; +} + +/* +================ +idDict::Copy + + copy all key value pairs without removing existing key/value pairs not present in the other dict +================ +*/ +void idDict::Copy( const idDict &other ) { + int i, n, *found; + idKeyValue kv; + + // check for assignment to self + if ( this == &other ) { + return; + } + + n = other.args.Num(); + + if ( args.Num() ) { + found = (int *) _alloca16( other.args.Num() * sizeof( int ) ); + for ( i = 0; i < n; i++ ) { + found[i] = FindKeyIndex( other.args[i].GetKey() ); + } + } else { + found = NULL; + } + + for ( i = 0; i < n; i++ ) { + if ( found && found[i] != -1 ) { + // first set the new value and then free the old value to allow proper self copying + const idPoolStr *oldValue = args[found[i]].value; + args[found[i]].value = globalValues.CopyString( other.args[i].value ); + globalValues.FreeString( oldValue ); + } else { + kv.key = globalKeys.CopyString( other.args[i].key ); + kv.value = globalValues.CopyString( other.args[i].value ); + argHash.Add( argHash.GenerateKey( kv.GetKey(), false ), args.Append( kv ) ); + } + } +} + +/* +================ +idDict::TransferKeyValues + + clear existing key/value pairs and transfer key/value pairs from other +================ +*/ +void idDict::TransferKeyValues( idDict &other ) { + int i, n; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + + if ( this == &other ) { + return; + } + + if ( other.args.Num() && other.args[0].key->GetPool() != &globalKeys ) { + common->FatalError( "idDict::TransferKeyValues: can't transfer values across a DLL boundary" ); + return; + } + + Clear(); + + n = other.args.Num(); + args.SetNum( n ); + for ( i = 0; i < n; i++ ) { + args[i].key = other.args[i].key; + args[i].value = other.args[i].value; + } + argHash = other.argHash; + + other.args.Clear(); + other.argHash.Free(); +} + +/* +================ +idDict::Parse +================ +*/ +bool idDict::Parse( idParser &parser ) { + idToken token; + idToken token2; + bool errors; + + errors = false; + + parser.ExpectTokenString( "{" ); + parser.ReadToken( &token ); + while( ( token.type != TT_PUNCTUATION ) || ( token != "}" ) ) { + if ( token.type != TT_STRING ) { + parser.Error( "Expected quoted string, but found '%s'", token.c_str() ); + } + + if ( !parser.ReadToken( &token2 ) ) { + parser.Error( "Unexpected end of file" ); + } + + if ( FindKey( token ) ) { + parser.Warning( "'%s' already defined", token.c_str() ); + errors = true; + } + Set( token, token2 ); + + if ( !parser.ReadToken( &token ) ) { + parser.Error( "Unexpected end of file" ); + } + } + + return !errors; +} + +/* +================ +idDict::SetDefaults +================ +*/ +void idDict::SetDefaults( const idDict *dict ) { + int i, n; + const idKeyValue *kv, *def; + idKeyValue newkv; + + n = dict->args.Num(); + for( i = 0; i < n; i++ ) { + def = &dict->args[i]; + kv = FindKey( def->GetKey() ); + if ( !kv ) { + newkv.key = globalKeys.CopyString( def->key ); + newkv.value = globalValues.CopyString( def->value ); + argHash.Add( argHash.GenerateKey( newkv.GetKey(), false ), args.Append( newkv ) ); + } + } +} + +/* +================ +idDict::Clear +================ +*/ +void idDict::Clear( void ) { + int i; + + for( i = 0; i < args.Num(); i++ ) { + globalKeys.FreeString( args[i].key ); + globalValues.FreeString( args[i].value ); + } + + args.Clear(); + argHash.Free(); +} + +/* +================ +idDict::Print +================ +*/ +void idDict::Print() const { + int i; + int n; + + n = args.Num(); + for( i = 0; i < n; i++ ) { + idLib::common->Printf( "%s = %s\n", args[i].GetKey().c_str(), args[i].GetValue().c_str() ); + } +} + +int KeyCompare( const idKeyValue *a, const idKeyValue *b ) { + return idStr::Cmp( a->GetKey(), b->GetKey() ); +} + +/* +================ +idDict::Checksum +================ +*/ +int idDict::Checksum( void ) const { + unsigned long ret; + int i, n; + + idList sorted = args; + sorted.Sort( KeyCompare ); + n = sorted.Num(); + CRC32_InitChecksum( ret ); + for( i = 0; i < n; i++ ) { + CRC32_UpdateChecksum( ret, sorted[i].GetKey().c_str(), sorted[i].GetKey().Length() ); + CRC32_UpdateChecksum( ret, sorted[i].GetValue().c_str(), sorted[i].GetValue().Length() ); + } + CRC32_FinishChecksum( ret ); + return ret; +} + +/* +================ +idDict::Allocated +================ +*/ +size_t idDict::Allocated( void ) const { + int i; + size_t size; + + size = args.Allocated() + argHash.Allocated(); + for( i = 0; i < args.Num(); i++ ) { + size += args[i].Size(); + } + + return size; +} + +/* +================ +idDict::Set +================ +*/ +void idDict::Set( const char *key, const char *value ) { + int i; + idKeyValue kv; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + + if ( key == NULL || key[0] == '\0' ) { + return; + } + + i = FindKeyIndex( key ); + if ( i != -1 ) { + // first set the new value and then free the old value to allow proper self copying + const idPoolStr *oldValue = args[i].value; + args[i].value = globalValues.AllocString( value ); + globalValues.FreeString( oldValue ); + } else { + kv.key = globalKeys.AllocString( key ); + kv.value = globalValues.AllocString( value ); + argHash.Add( argHash.GenerateKey( kv.GetKey(), false ), args.Append( kv ) ); + } +} + +/* +================ +idDict::GetFloat +================ +*/ +bool idDict::GetFloat( const char *key, const char *defaultString, float &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = atof( s ); + return found; +} + +/* +================ +idDict::GetInt +================ +*/ +bool idDict::GetInt( const char *key, const char *defaultString, int &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = atoi( s ); + return found; +} + +/* +================ +idDict::GetBool +================ +*/ +bool idDict::GetBool( const char *key, const char *defaultString, bool &out ) const { + const char *s; + bool found; + + found = GetString( key, defaultString, &s ); + out = ( atoi( s ) != 0 ); + return found; +} + +/* +================ +idDict::GetAngles +================ +*/ +bool idDict::GetAngles( const char *key, const char *defaultString, idAngles &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f", &out.pitch, &out.yaw, &out.roll ); + return found; +} + +/* +================ +idDict::GetVector +================ +*/ +bool idDict::GetVector( const char *key, const char *defaultString, idVec3 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f", &out.x, &out.y, &out.z ); + return found; +} + +/* +================ +idDict::GetVec2 +================ +*/ +bool idDict::GetVec2( const char *key, const char *defaultString, idVec2 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f", &out.x, &out.y ); + return found; +} + +/* +================ +idDict::GetVec4 +================ +*/ +bool idDict::GetVec4( const char *key, const char *defaultString, idVec4 &out ) const { + bool found; + const char *s; + + if ( !defaultString ) { + defaultString = "0 0 0 0"; + } + + found = GetString( key, defaultString, &s ); + out.Zero(); + sscanf( s, "%f %f %f %f", &out.x, &out.y, &out.z, &out.w ); + return found; +} + +/* +================ +idDict::GetMatrix +================ +*/ +bool idDict::GetMatrix( const char *key, const char *defaultString, idMat3 &out ) const { + const char *s; + bool found; + + if ( !defaultString ) { + defaultString = "1 0 0 0 1 0 0 0 1"; + } + + found = GetString( key, defaultString, &s ); + out.Identity(); // sccanf has a bug in it on Mac OS 9. Sigh. + sscanf( s, "%f %f %f %f %f %f %f %f %f", &out[0].x, &out[0].y, &out[0].z, &out[1].x, &out[1].y, &out[1].z, &out[2].x, &out[2].y, &out[2].z ); + return found; +} + +/* +================ +WriteString +================ +*/ +static void WriteString( const char *s, idFile *f ) { + int len = strlen( s ); + if ( len >= MAX_STRING_CHARS-1 ) { + idLib::common->Error( "idDict::WriteToFileHandle: bad string" ); + } + f->Write( s, strlen(s) + 1 ); +} + +/* +================ +idDict::FindKey +================ +*/ +const idKeyValue *idDict::FindKey( const char *key ) const { + int i, hash; + + if ( key == NULL || key[0] == '\0' ) { + idLib::common->DWarning( "idDict::FindKey: empty key" ); + return NULL; + } + + hash = argHash.GenerateKey( key, false ); + for ( i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + return &args[i]; + } + } + + return NULL; +} + +/* +================ +idDict::FindKeyIndex +================ +*/ +int idDict::FindKeyIndex( const char *key ) const { + + if ( key == NULL || key[0] == '\0' ) { + idLib::common->DWarning( "idDict::FindKeyIndex: empty key" ); + return NULL; + } + + int hash = argHash.GenerateKey( key, false ); + for ( int i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + return i; + } + } + + return -1; +} + +/* +================ +idDict::Delete +================ +*/ +void idDict::Delete( const char *key ) { + int hash, i; + + hash = argHash.GenerateKey( key, false ); + for ( i = argHash.First( hash ); i != -1; i = argHash.Next( i ) ) { + if ( args[i].GetKey().Icmp( key ) == 0 ) { + globalKeys.FreeString( args[i].key ); + globalValues.FreeString( args[i].value ); + args.RemoveIndex( i ); + argHash.RemoveIndex( hash, i ); + break; + } + } + +#if 0 + // make sure all keys can still be found in the hash index + for ( i = 0; i < args.Num(); i++ ) { + assert( FindKey( args[i].GetKey() ) != NULL ); + } +#endif +} + +/* +================ +idDict::MatchPrefix +================ +*/ +const idKeyValue *idDict::MatchPrefix( const char *prefix, const idKeyValue *lastMatch ) const { + int i; + int len; + int start; + + assert( prefix ); + len = strlen( prefix ); + + start = -1; + if ( lastMatch ) { + start = args.FindIndex( *lastMatch ); + assert( start >= 0 ); + if ( start < 1 ) { + start = 0; + } + } + + for( i = start + 1; i < args.Num(); i++ ) { + if ( !args[i].GetKey().Icmpn( prefix, len ) ) { + return &args[i]; + } + } + return NULL; +} + +/* +================ +idDict::RandomPrefix +================ +*/ +// RAVEN BEGIN +// abahr: added default value param +const char *idDict::RandomPrefix( const char *prefix, idRandom &random, const char* defaultValue ) const { + int count; + const int MAX_RANDOM_KEYS = 2048; + const char *list[MAX_RANDOM_KEYS]; + const idKeyValue *kv; + +// RAVEN BEGIN +// abahr: added defaultValue param + list[0] = defaultValue; +// RAVEN END + for ( count = 0, kv = MatchPrefix( prefix ); kv && count < MAX_RANDOM_KEYS; kv = MatchPrefix( prefix, kv ) ) { + list[count++] = kv->GetValue().c_str(); + } + return list[random.RandomInt( count )]; +} + +/* +================ +idDict::WriteToFileHandle +================ +*/ +void idDict::WriteToFileHandle( idFile *f ) const { + int c = LittleLong( args.Num() ); + f->Write( &c, sizeof( c ) ); +// RAVEN BEGIN +// jnewquist: For loop was looking at c, which got swapped on Xenon! + for ( int i = 0; i < args.Num(); i++ ) { +// RAVEN END + WriteString( args[i].GetKey().c_str(), f ); + WriteString( args[i].GetValue().c_str(), f ); + } +} + +/* +================ +ReadString +================ +*/ +static idStr ReadString( idFile *f ) { + char str[MAX_STRING_CHARS]; + int len; + + for ( len = 0; len < MAX_STRING_CHARS; len++ ) { + f->Read( (void *)&str[len], 1 ); + if ( str[len] == 0 ) { + break; + } + } + if ( len == MAX_STRING_CHARS ) { + idLib::common->Error( "idDict::ReadFromFileHandle: bad string" ); + } + + return idStr( str ); +} + +/* +================ +idDict::ReadFromFileHandle +================ +*/ +void idDict::ReadFromFileHandle( idFile *f ) { + int c; + idStr key, val; + + Clear(); + + f->Read( &c, sizeof( c ) ); + c = LittleLong( c ); + for ( int i = 0; i < c; i++ ) { + key = ReadString( f ); + val = ReadString( f ); + Set( key, val ); + } +} + +// RAVEN BEGIN +/* +================ +idDict::WriteToMemory +================ +*/ +int idDict::WriteToMemory( void *mem, int maxSize ) const { + int bytesWritten = 0; + char *out = (char*)mem; + if ( out ) { + *((int*)out) = args.Num(); + out+=sizeof(int); + } + bytesWritten+=sizeof(int); + + int l; + for ( int i = 0; i < args.Num(); i++ ) { + l = args[i].GetKey().Length(); + ++l; + + if ( bytesWritten + l > maxSize ) { + assert( 0 ); + return bytesWritten; + } + + if ( out ) { + memcpy( out, args[i].GetKey().c_str(), l ); + } + if ( out ) { + out+=l; + } + bytesWritten+=l; + + l = args[i].GetValue().Length(); + ++l; + + if ( bytesWritten + l > maxSize ) { + assert( 0 ); + return bytesWritten; + } + + if ( out ) { + memcpy( out, args[i].GetValue().c_str(), l ); + } + if ( out ) { + out+=l; + } + bytesWritten+=l; + } + + return bytesWritten; +} + +/* +================ +idDict::ReadFromMemory +================ +*/ +void idDict::ReadFromMemory( void *mem, int size ) { + int bytesRead = 0; + + char *in = (char*)mem; + int c = *((int*)in); + in+=sizeof(int); + + idStr key, val; + + Clear(); + + int readLen; + for ( int i = 0; i < c; i++ ) { + key = in; + readLen = key.Length() + 1; + in+=readLen; + bytesRead+=readLen; + if ( bytesRead >= size ) { + assert( 0 ); + return; + } + + val = in; + readLen = val.Length() + 1; + in+=readLen; + bytesRead+=readLen; + if ( bytesRead >= size ) { + assert( 0 ); + return; + } + + Set( key, val ); + } +} +// RAVEN END + +/* +================ +idDict::Init +================ +*/ +void idDict::Init( void ) { + globalKeys.SetCaseSensitive( false ); + globalValues.SetCaseSensitive( true ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + globalKeys.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_PERMANENT)); + globalValues.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_PERMANENT)); +#endif +// RAVEN END +} + +/* +================ +idDict::Shutdown +================ +*/ +void idDict::Shutdown( void ) { + globalKeys.Clear(); + globalValues.Clear(); +} + +/* +================ +idDict::ShowMemoryUsage_f +================ +*/ +void idDict::ShowMemoryUsage_f( const idCmdArgs &args ) { + idLib::common->Printf( "%5d KB in %d keys\n", globalKeys.Size() >> 10, globalKeys.Num() ); + idLib::common->Printf( "%5d KB in %d values\n", globalValues.Size() >> 10, globalValues.Num() ); +} + +/* +================ +idDictStringSortCmp +================ +*/ +// NOTE: the const wonkyness is required to make msvc happy +template<> +ID_INLINE int idListSortCompare( const idPoolStr * const *a, const idPoolStr * const *b ) { + return (*a)->Icmp( **b ); +} + +/* +================ +idDict::ListKeys_f +================ +*/ +void idDict::ListKeys_f( const idCmdArgs &args ) { + int i; + idList keyStrings; + + for ( i = 0; i < globalKeys.Num(); i++ ) { + keyStrings.Append( globalKeys[i] ); + } + keyStrings.Sort(); + for ( i = 0; i < keyStrings.Num(); i++ ) { + idLib::common->Printf( "%s\n", keyStrings[i]->c_str() ); + } + idLib::common->Printf( "%5d keys\n", keyStrings.Num() ); +} + +/* +================ +idDict::ListValues_f +================ +*/ +void idDict::ListValues_f( const idCmdArgs &args ) { + int i; + idList valueStrings; + + for ( i = 0; i < globalValues.Num(); i++ ) { + valueStrings.Append( globalValues[i] ); + } + valueStrings.Sort(); + for ( i = 0; i < valueStrings.Num(); i++ ) { + idLib::common->Printf( "%s\n", valueStrings[i]->c_str() ); + } + idLib::common->Printf( "%5d values\n", valueStrings.Num() ); +} + +// RAVEN BEGIN +// jsinger: allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +void idDict::Write( SerialOutputStream &stream ) const +{ + stream.Write(GetNumKeyVals()); + for(int i=0;iGetKey()); + stream.Write(keyVal->GetValue()); + } +} + +idDict::idDict( SerialInputStream &stream ) +{ + int numKeyVals = stream.ReadIntValue(); + for(int i=0; i. + +=========================================================================== +*/ + +#ifndef __DICT_H__ +#define __DICT_H__ + +/* +=============================================================================== + +Key/value dictionary + +This is a dictionary class that tracks an arbitrary number of key / value +pair combinations. It is used for map entity spawning, GUI state management, +and other things. + +Keys are compared case-insensitive. + +Does not allocate memory until the first key/value pair is added. + +=============================================================================== +*/ + +class idKeyValue { + friend class idDict; + +public: + const idStr & GetKey( void ) const { return *key; } + const idStr & GetValue( void ) const { return *value; } + + size_t Allocated( void ) const { return key->Allocated() + value->Allocated(); } + size_t Size( void ) const { return sizeof( *this ) + key->Size() + value->Size(); } + + bool operator==( const idKeyValue &kv ) const { return ( key == kv.key && value == kv.value ); } + +private: + const idPoolStr * key; + const idPoolStr * value; +}; + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +#include "../serialization/SerialOutputStream.h" +#include "../serialization/SerialInputStream.h" +#endif +// RAVEN END +class idDict { +public: + idDict( void ); + idDict( const idDict &other ); // allow declaration with assignment + ~idDict( void ); + + // set the granularity for the index + void SetGranularity( int granularity ); + // set hash size + void SetHashSize( int hashSize ); + // clear existing key/value pairs and copy all key/value pairs from other + idDict & operator=( const idDict &other ); + // copy from other while leaving existing key/value pairs in place + void Copy( const idDict &other ); + // clear existing key/value pairs and transfer key/value pairs from other + void TransferKeyValues( idDict &other ); + // parse dict from parser + bool Parse( idParser &parser ); + // copy key/value pairs from other dict not present in this dict + void SetDefaults( const idDict *dict ); + // clear dict freeing up memory + void Clear( void ); + // print the dict + void Print() const; + + size_t Allocated( void ) const; + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + + void Set( const char *key, const char *value ); + void SetFloat( const char *key, float val ); + void SetInt( const char *key, int val ); + void SetBool( const char *key, bool val ); + void SetVector( const char *key, const idVec3 &val ); + void SetVec2( const char *key, const idVec2 &val ); + void SetVec4( const char *key, const idVec4 &val ); + void SetAngles( const char *key, const idAngles &val ); + void SetMatrix( const char *key, const idMat3 &val ); + + // these return default values of 0.0, 0 and false + const char * GetString( const char *key, const char *defaultString = "" ) const; + float GetFloat( const char *key, const char *defaultString = "0" ) const; + int GetInt( const char *key, const char *defaultString = "0" ) const; + bool GetBool( const char *key, const char *defaultString = "0" ) const; + idVec3 GetVector( const char *key, const char *defaultString = NULL ) const; + idVec2 GetVec2( const char *key, const char *defaultString = NULL ) const; + idVec4 GetVec4( const char *key, const char *defaultString = NULL ) const; + idAngles GetAngles( const char *key, const char *defaultString = NULL ) const; + idMat3 GetMatrix( const char *key, const char *defaultString = NULL ) const; + + bool GetString( const char *key, const char *defaultString, const char **out ) const; + bool GetString( const char *key, const char *defaultString, idStr &out ) const; + bool GetFloat( const char *key, const char *defaultString, float &out ) const; + bool GetInt( const char *key, const char *defaultString, int &out ) const; + bool GetBool( const char *key, const char *defaultString, bool &out ) const; + bool GetVector( const char *key, const char *defaultString, idVec3 &out ) const; + bool GetVec2( const char *key, const char *defaultString, idVec2 &out ) const; + bool GetVec4( const char *key, const char *defaultString, idVec4 &out ) const; + bool GetAngles( const char *key, const char *defaultString, idAngles &out ) const; + bool GetMatrix( const char *key, const char *defaultString, idMat3 &out ) const; + + int GetNumKeyVals( void ) const; + const idKeyValue * GetKeyVal( int index ) const; + // returns the key/value pair with the given key + // returns NULL if the key/value pair does not exist + const idKeyValue * FindKey( const char *key ) const; + // returns the index to the key/value pair with the given key + // returns -1 if the key/value pair does not exist + int FindKeyIndex( const char *key ) const; + // delete the key/value pair with the given key + void Delete( const char *key ); + // finds the next key/value pair with the given key prefix. + // lastMatch can be used to do additional searches past the first match. + const idKeyValue * MatchPrefix( const char *prefix, const idKeyValue *lastMatch = NULL ) const; + // randomly chooses one of the key/value pairs with the given key prefix and returns it's value +// RAVEN BEGIN +// abahr: added default value param + const char * RandomPrefix( const char *prefix, idRandom &random, const char* defaultValue = "" ) const; +// RAVEN END + + void WriteToFileHandle( idFile *f ) const; + void ReadFromFileHandle( idFile *f ); + +// RAVEN BEGIN +// nrausch: write/read to memory + int WriteToMemory( void *mem, int maxSize ) const; + void ReadFromMemory( void *mem, int size ); +// RAVEN END + + // returns a unique checksum for this dictionary's content + int Checksum( void ) const; + + static void Init( void ); + static void Shutdown( void ); + + static void ShowMemoryUsage_f( const idCmdArgs &args ); + static void ListKeys_f( const idCmdArgs &args ); + static void ListValues_f( const idCmdArgs &args ); + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + void Write( SerialOutputStream &stream ) const; + idDict( SerialInputStream &stream ); +#endif +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + // Set the heap used for all allocations + void SetAllocatorHeap ( rvHeap* heap ) + { + args.SetAllocatorHeap(heap); + argHash.SetAllocatorHeap(heap); + } +#endif +// RAVEN END + +private: + idList args; + idHashIndex argHash; + static idStrPool globalKeys; + static idStrPool globalValues; +}; + + +ID_INLINE idDict::idDict( void ) { + args.SetGranularity( 16 ); + argHash.SetGranularity( 16 ); + argHash.Clear( 128, 16 ); +} + +ID_INLINE idDict::idDict( const idDict &other ) { + *this = other; +} + +ID_INLINE idDict::~idDict( void ) { + Clear(); +} + +ID_INLINE void idDict::SetGranularity( int granularity ) { + args.SetGranularity( granularity ); + argHash.SetGranularity( granularity ); +} + +ID_INLINE void idDict::SetHashSize( int hashSize ) { + if ( args.Num() == 0 ) { + argHash.Clear( hashSize, 16 ); + } +} + +ID_INLINE void idDict::SetFloat( const char *key, float val ) { + Set( key, va( "%f", val ) ); +} + +ID_INLINE void idDict::SetInt( const char *key, int val ) { + Set( key, va( "%i", val ) ); +} + +ID_INLINE void idDict::SetBool( const char *key, bool val ) { + Set( key, va( "%i", val ) ); +} + +ID_INLINE void idDict::SetVector( const char *key, const idVec3 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetVec4( const char *key, const idVec4 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetVec2( const char *key, const idVec2 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetAngles( const char *key, const idAngles &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE void idDict::SetMatrix( const char *key, const idMat3 &val ) { + Set( key, val.ToString() ); +} + +ID_INLINE bool idDict::GetString( const char *key, const char *defaultString, const char **out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + *out = kv->GetValue(); + return true; + } + *out = defaultString; + return false; +} + +ID_INLINE bool idDict::GetString( const char *key, const char *defaultString, idStr &out ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + out = kv->GetValue(); + return true; + } + out = defaultString; + return false; +} + +ID_INLINE const char *idDict::GetString( const char *key, const char *defaultString ) const { + const idKeyValue *kv = FindKey( key ); + if ( kv ) { + return kv->GetValue(); + } + return defaultString; +} + +ID_INLINE float idDict::GetFloat( const char *key, const char *defaultString ) const { + return atof( GetString( key, defaultString ) ); +} + +ID_INLINE int idDict::GetInt( const char *key, const char *defaultString ) const { + return atoi( GetString( key, defaultString ) ); +} + +ID_INLINE bool idDict::GetBool( const char *key, const char *defaultString ) const { + return ( atoi( GetString( key, defaultString ) ) != 0 ); +} + +ID_INLINE idVec3 idDict::GetVector( const char *key, const char *defaultString ) const { + idVec3 out; + GetVector( key, defaultString, out ); + return out; +} + +ID_INLINE idVec2 idDict::GetVec2( const char *key, const char *defaultString ) const { + idVec2 out; + GetVec2( key, defaultString, out ); + return out; +} + +ID_INLINE idVec4 idDict::GetVec4( const char *key, const char *defaultString ) const { + idVec4 out; + GetVec4( key, defaultString, out ); + return out; +} + +ID_INLINE idAngles idDict::GetAngles( const char *key, const char *defaultString ) const { + idAngles out; + GetAngles( key, defaultString, out ); + return out; +} + +ID_INLINE idMat3 idDict::GetMatrix( const char *key, const char *defaultString ) const { + idMat3 out; + GetMatrix( key, defaultString, out ); + return out; +} + +ID_INLINE int idDict::GetNumKeyVals( void ) const { + return args.Num(); +} + +ID_INLINE const idKeyValue *idDict::GetKeyVal( int index ) const { + if ( index >= 0 && index < args.Num() ) { + return &args[ index ]; + } + return NULL; +} + +#endif /* !__DICT_H__ */ diff --git a/src/idlib/Heap.cpp b/src/idlib/Heap.cpp new file mode 100644 index 0000000..e9dae80 --- /dev/null +++ b/src/idlib/Heap.cpp @@ -0,0 +1,2338 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +void *(*Memory::mAllocator)(size_t size); +void (*Memory::mDeallocator)(void *ptr); +size_t (*Memory::mMSize)(void *ptr); +bool Memory::sOK = true; + +void Memory::Error(const char *errStr) +{ + // If you land here it's because you allocated dynamic memory in a DLL + // before the Memory system was initialized and this will lead to instability + // later. We can't assert here since that will cause the DLL to fail loading + // so set a break point on the BreakHere=0xdeadbeef; line in order to debug + int BreakHere; + BreakHere=0xdeadbeef; + + if(common != NULL) + { + // this can't be an error or the game will fail to load the DLL and crash. + // While a crash is the desired behavior, a crash without any meaningful + // message to the user indicating what went wrong is not desired. + common->Warning(errStr); + } + else + { + // if common isn't initialized then we set a flag so that we can notify once it + // gets initialized + sOK = false; + } +} +#endif +// RAVEN END +#ifndef _RV_MEM_SYS_SUPPORT + +#ifndef USE_LIBC_MALLOC + #ifdef _XBOX + #define USE_LIBC_MALLOC 1 + #else +// RAVEN BEGIN +// scork: changed this to a 1, since 0 will crash Radiant if you BSP large maps ~3 times because of high watermark-mem that never gets freed. + #define USE_LIBC_MALLOC 1 +// RAVEN END + #endif +#endif + +#ifndef CRASH_ON_STATIC_ALLOCATION +// #define CRASH_ON_STATIC_ALLOCATION +#endif + +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon +inline +void *local_malloc(size_t size) +{ +#ifndef _XENON +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues + #ifdef RV_UNIFIED_ALLOCATOR + return Memory::Allocate(size); + #else + void *addr = malloc(size); + if( !addr && size ) { + common->FatalError( "Out of memory" ); + } + return( addr ); + #endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#else +#endif +} + +inline +void local_free(void *ptr) +{ +#ifndef _XENON +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues + #ifdef RV_UNIFIED_ALLOCATOR + Memory::Free(ptr); + #else + return free(ptr); + #endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#else +#endif +} +// RAVEN END + +//=============================================================== +// +// idHeap +// +//=============================================================== + +// RAVEN BEGIN +// amccarthy: Added space in headers in debug for allocation tag storage +#ifdef _DEBUG +#define SMALL_HEADER_SIZE ( (int) ( sizeof( byte ) + sizeof( byte )+ sizeof( byte ) ) ) +#define MEDIUM_HEADER_SIZE ( (int) ( sizeof( mediumHeapEntry_s ) + sizeof( byte )+ sizeof( byte ) ) ) +#define LARGE_HEADER_SIZE ( (int) ( sizeof( dword * ) + sizeof( byte )+ sizeof( byte ) ) ) + +#define MALLOC_HEADER_SIZE ( (int) ( 11*sizeof( byte ) + sizeof( byte ) + sizeof( dword ))) +#else +#define SMALL_HEADER_SIZE ( (int) ( sizeof( byte ) + sizeof( byte ) ) ) +#define MEDIUM_HEADER_SIZE ( (int) ( sizeof( mediumHeapEntry_s ) + sizeof( byte ) ) ) +#define LARGE_HEADER_SIZE ( (int) ( sizeof( dword * ) + sizeof( byte ) ) ) +#endif +// RAVEN END + +#define ALIGN_SIZE( bytes ) ( ( (bytes) + ALIGN - 1 ) & ~(ALIGN - 1) ) +#define SMALL_ALIGN( bytes ) ( ALIGN_SIZE( (bytes) + SMALL_HEADER_SIZE ) - SMALL_HEADER_SIZE ) +#define MEDIUM_SMALLEST_SIZE ( ALIGN_SIZE( 256 ) + ALIGN_SIZE( MEDIUM_HEADER_SIZE ) ) + +// RAVEN BEGIN +// amccarthy: Allocation tag tracking and reporting +#ifdef _DEBUG +int OutstandingXMallocSize=0; +int OutstandingXMallocTagSize[MA_MAX]; +int PeakXMallocTagSize[MA_MAX]; +int CurrNumAllocations[MA_MAX]; + + +// Descriptions that go with each tag. When updating the tag enum in Heap.h please +// update this list as well. +// (also update the list in rvHeap.cpp) +char *TagNames[] = { + "none", + "New operation", + "default", + "Lexer", + "Parser", + "AAS routing", + "Class", + "Script program", + "Collision Model", + "CVar", + "Decl System", + "File System", + "Images", + "Materials", + "Models", + "Fonts", + "Main renderer", + "Vertex data", + "Sound", + "Window", + "Event loop", + "Math - Matrices and vectors", + "Animation", + "Dynamic Blocks", + "Strings", + "GUI", + "Effects", + "Entities", + "Physics", + "AI", + "Network", + "Not Used" +}; + +// jnewquist: Detect when the tag descriptions are out of sync with the enums. +template +class TagTableCheck +{ +private: + TagTableCheck(); +}; + +template<> +class TagTableCheck<1> +{ +}; + +// An error here means you need to synchronize TagNames and Mem_Alloc_Types_t +TagTableCheck TagTableCheckedHere; +// An error here means there are too many tags. No more than 32! +TagTableCheck TagMaxCheckedHere; + +void PrintOutstandingMemAlloc() +{ + + int i; + unsigned long totalOutstanding = 0; + for (i=0;iPrintf("%-30s peak %9d curr %9d\n",TagNames[i],PeakXMallocTagSize[i],OutstandingXMallocTagSize[i]); + totalOutstanding += OutstandingXMallocTagSize[i]; + } + } + idLib::common->Printf("Mem_Alloc Outstanding: %d\n",totalOutstanding); + +} + +const char *GetMemAllocStats(int tag, int &num, int &size, int &peak) +{ + num = CurrNumAllocations[tag]; + size = OutstandingXMallocTagSize[tag]; + peak = PeakXMallocTagSize[tag]; + return TagNames[tag]; +} +#endif + + +/* +================= +Mem_ShowMemAlloc_f +================= +*/ +// amccarthy: print out outstanding mem_allocs. +void Mem_ShowMemAlloc_f( const idCmdArgs &args ) { + +#ifdef _DEBUG + PrintOutstandingMemAlloc(); +#endif +} + +// jnewquist: memory tag stack for new/delete +#if defined(_DEBUG) && !defined(ENABLE_INTEL_SMP) +MemScopedTag* MemScopedTag::mTop = NULL; +#endif + +//RAVEN END + +class idHeap { + +public: + idHeap( void ); + ~idHeap( void ); // frees all associated data + void Init( void ); // initialize +//RAVEN BEGIN +//amccarthy: Added allocation tag + void * Allocate( const dword bytes, byte tag ); // allocate memory +//RAVEN END + void Free( void *p ); // free memory +//RAVEN BEGIN +//amccarthy: Added allocation tag + void * Allocate16( const dword bytes, byte tag );// allocate 16 byte aligned memory +//RAVEN END + void Free16( void *p ); // free 16 byte aligned memory + dword Msize( void *p ); // return size of data block + void Dump( void ); + + void AllocDefragBlock( void ); // hack for huge renderbumps + +private: + + enum { + ALIGN = 8 // memory alignment in bytes + }; + + enum { + INVALID_ALLOC = 0xdd, + SMALL_ALLOC = 0xaa, // small allocation + MEDIUM_ALLOC = 0xbb, // medium allocaction + LARGE_ALLOC = 0xcc // large allocaction + }; + + struct page_s { // allocation page + void * data; // data pointer to allocated memory + dword dataSize; // number of bytes of memory 'data' points to + page_s * next; // next free page in same page manager + page_s * prev; // used only when allocated + dword largestFree; // this data used by the medium-size heap manager + void * firstFree; // pointer to first free entry + }; + + struct mediumHeapEntry_s { + page_s * page; // pointer to page + dword size; // size of block + mediumHeapEntry_s * prev; // previous block + mediumHeapEntry_s * next; // next block + mediumHeapEntry_s * prevFree; // previous free block + mediumHeapEntry_s * nextFree; // next free block + dword freeBlock; // non-zero if free block + }; + + // variables + void * smallFirstFree[256/ALIGN+1]; // small heap allocator lists (for allocs of 1-255 bytes) + page_s * smallCurPage; // current page for small allocations + dword smallCurPageOffset; // byte offset in current page + page_s * smallFirstUsedPage; // first used page of the small heap manager + + page_s * mediumFirstFreePage; // first partially free page + page_s * mediumLastFreePage; // last partially free page + page_s * mediumFirstUsedPage; // completely used page + + page_s * largeFirstUsedPage; // first page used by the large heap manager + + page_s * swapPage; + + dword pagesAllocated; // number of pages currently allocated + dword pageSize; // size of one alloc page in bytes + + dword pageRequests; // page requests + dword OSAllocs; // number of allocs made to the OS + + int c_heapAllocRunningCount; + + void *defragBlock; // a single huge block that can be allocated + // at startup, then freed when needed + + // methods + page_s * AllocatePage( dword bytes ); // allocate page from the OS + void FreePage( idHeap::page_s *p ); // free an OS allocated page + +//RAVEN BEGIN +//amccarthy: Added allocation tags + void * SmallAllocate( dword bytes, byte tag ); // allocate memory (1-255 bytes) from small heap manager + void SmallFree( void *ptr ); // free memory allocated by small heap manager + + void * MediumAllocateFromPage( idHeap::page_s *p, dword sizeNeeded, byte tag ); + void * MediumAllocate( dword bytes, byte tag ); // allocate memory (256-32768 bytes) from medium heap manager + void MediumFree( void *ptr ); // free memory allocated by medium heap manager + + void * LargeAllocate( dword bytes, byte tag ); // allocate large block from OS directly + void LargeFree( void *ptr ); // free memory allocated by large heap manager +//RAVEN END + void ReleaseSwappedPages( void ); + void FreePageReal( idHeap::page_s *p ); +}; + + +/* +================ +idHeap::Init +================ +*/ +void idHeap::Init () { + OSAllocs = 0; + pageRequests = 0; + pageSize = 65536 - sizeof( idHeap::page_s ); + pagesAllocated = 0; // reset page allocation counter + + largeFirstUsedPage = NULL; // init large heap manager + swapPage = NULL; + + memset( smallFirstFree, 0, sizeof(smallFirstFree) ); // init small heap manager + smallFirstUsedPage = NULL; + smallCurPage = AllocatePage( pageSize ); + assert( smallCurPage ); + smallCurPageOffset = SMALL_ALIGN( 0 ); + + defragBlock = NULL; + + mediumFirstFreePage = NULL; // init medium heap manager + mediumLastFreePage = NULL; + mediumFirstUsedPage = NULL; + + c_heapAllocRunningCount = 0; + +//RAVEN BEGIN +//amccarthy: initalize allocation tracking +#ifdef _DEBUG + OutstandingXMallocSize=0; + int i; + for (i=0;inext; + FreePage( p ); + p= next; + } + + p = largeFirstUsedPage; // free large-heap allocated pages + while( p ) { + idHeap::page_s *next = p->next; + FreePage( p ); + p = next; + } + + p = mediumFirstFreePage; // free medium-heap allocated pages + while( p ) { + idHeap::page_s *next = p->next; + FreePage( p ); + p = next; + } + + p = mediumFirstUsedPage; // free medium-heap allocated completely used pages + while( p ) { + idHeap::page_s *next = p->next; + FreePage( p ); + p = next; + } + + ReleaseSwappedPages(); + + if ( defragBlock ) { +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( defragBlock ); +// RAVEN END + } + + assert( pagesAllocated == 0 ); +} + +/* +================ +idHeap::AllocDefragBlock +================ +*/ +void idHeap::AllocDefragBlock( void ) { + int size = 0x40000000; + + if ( defragBlock ) { + return; + } + while( 1 ) { +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + defragBlock = local_malloc( size ); +// RAVEN END + if ( defragBlock ) { + break; + } + size >>= 1; + } + idLib::common->Printf( "Allocated a %i mb defrag block\n", size / (1024*1024) ); +} + +/* +================ +idHeap::Allocate +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::Allocate( const dword bytes, byte tag ) { +//RAVEN END + if ( !bytes ) { + return NULL; + } + c_heapAllocRunningCount++; + +#if USE_LIBC_MALLOC + +//RAVEN BEGIN +//amccarthy: Added allocation tags for malloc +#ifdef _DEBUG + byte *p = (byte *)local_malloc( bytes + MALLOC_HEADER_SIZE ); + OutstandingXMallocSize += bytes; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += bytes; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + dword *d = (dword *)p; + d[0] = bytes; + byte *ret = p+MALLOC_HEADER_SIZE; + ret[-1] = tag; + return ret; +#else + return local_malloc( bytes); +#endif + +//RAVEN END + + +#else +//RAVEN BEGIN +//amccarthy: Added allocation tag + if ( !(bytes & ~255) ) { + return SmallAllocate( bytes, tag ); + } + if ( !(bytes & ~32767) ) { + return MediumAllocate( bytes, tag ); + } + return LargeAllocate( bytes, tag ); + +//RAVEN END +#endif +} + +/* +================ +idHeap::Free +================ +*/ +void idHeap::Free( void *p ) { + if ( !p ) { + return; + } + c_heapAllocRunningCount--; + +#if USE_LIBC_MALLOC +//RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte *ptr = ((byte *)p) - MALLOC_HEADER_SIZE; + dword size = ((dword*)(ptr))[0]; + byte tag = ((byte *)(p))[-1]; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; + local_free( ptr ); +#else + local_free( p ); +#endif +//RAVEN END +#else + switch( ((byte *)(p))[-1] ) { + case SMALL_ALLOC: { + SmallFree( p ); + break; + } + case MEDIUM_ALLOC: { + MediumFree( p ); + break; + } + case LARGE_ALLOC: { + LargeFree( p ); + break; + } + default: { + idLib::common->FatalError( "idHeap::Free: invalid memory block (%s)", idLib::sys->GetCallStackCurStr( 4 ) ); + break; + } + } + +#endif +} + +/* +================ +idHeap::Allocate16 +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::Allocate16( const dword bytes, byte tag ) { +//RAVEN END + byte *ptr, *alignedPtr; + +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + ptr = (byte *) Allocate(bytes+16, tag); + alignedPtr = (byte *) ( ( (int) ptr ) + 15 & ~15 ); + int padSize = alignedPtr - ptr; + if ( padSize == 0 ) { + alignedPtr += 16; + padSize += 16; + } + *((byte *)(alignedPtr - 1)) = (byte) padSize; + + assert( ( unsigned int )alignedPtr < 0xff000000 ); + + return (void *) alignedPtr; +#else +//RAVEN END +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ptr = (byte *) local_malloc( bytes + 16 + 4 ); +// RAVEN END + if ( !ptr ) { + if ( defragBlock ) { + idLib::common->Printf( "Freeing defragBlock on alloc of %i.\n", bytes ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( defragBlock ); +// RAVEN END + defragBlock = NULL; +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ptr = (byte *) local_malloc( bytes + 16 + 4 ); +// RAVEN END + AllocDefragBlock(); + } + if ( !ptr ) { + common->FatalError( "malloc failure for %i", bytes ); + } + } + + alignedPtr = (byte *) ( ( (int) ptr ) + 15 & ~15 ); + if ( alignedPtr - ptr < 4 ) { + alignedPtr += 16; + + } + *((int *)(alignedPtr - 4)) = (int) ptr; + assert( ( unsigned int )alignedPtr < 0xff000000 ); + return (void *) alignedPtr; + +//RAVEN BEGIN +//amccarthy: allocation tracking added for debug xbox only. +#endif +//RAVEN END + +} + +/* +================ +idHeap::Free16 +================ +*/ +void idHeap::Free16( void *p ) { +//RAVEN BEGIN +//amccarthy: allocation tag +#ifdef _DEBUG + byte* ptr = (byte*)p; + int padSize = *(ptr-1); + ptr -= padSize; + Free( ptr ); +#else + local_free( (void *) *((int *) (( (byte *) p ) - 4)) ); +#endif +//RAVEN END +} + +/* +================ +idHeap::Msize + + returns size of allocated memory block + p = pointer to memory block + Notes: size may not be the same as the size in the original + allocation request (due to block alignment reasons). +================ +*/ +dword idHeap::Msize( void *p ) { + + if ( !p ) { + return 0; + } + +#if USE_LIBC_MALLOC + #ifdef _WINDOWS +//RAVEN BEGIN +//amccarthy: allocation tracking + #ifdef _DEBUG + byte *ptr = ((byte *)p) - MALLOC_HEADER_SIZE; +// jsinger: attempt to eliminate cross-DLL allocation issues + #ifdef RV_UNIFIED_ALLOCATOR + return Memory::MSize(ptr); + #else + return _msize(ptr); + #endif // RV_UNIFIED_ALLOCATOR + #else + #ifdef RV_UNIFIED_ALLOCATOR + return Memory::MSize(p); + #else + return _msize(p); + #endif // RV_UNIFIED_ALLOCATOR + #endif +//RAVEN END + #else + return 0; + #endif +#else + switch( ((byte *)(p))[-1] ) { + case SMALL_ALLOC: { + return SMALL_ALIGN( ((byte *)(p))[-SMALL_HEADER_SIZE] * ALIGN ); + } + case MEDIUM_ALLOC: { + return ((mediumHeapEntry_s *)(((byte *)(p)) - ALIGN_SIZE( MEDIUM_HEADER_SIZE )))->size - ALIGN_SIZE( MEDIUM_HEADER_SIZE ); + } + case LARGE_ALLOC: { + return ((idHeap::page_s*)(*((dword *)(((byte *)p) - ALIGN_SIZE( LARGE_HEADER_SIZE )))))->dataSize - ALIGN_SIZE( LARGE_HEADER_SIZE ); + } + default: { + idLib::common->FatalError( "idHeap::Msize: invalid memory block (%s)", idLib::sys->GetCallStackCurStr( 4 ) ); + return 0; + } + } +#endif +} + +/* +================ +idHeap::Dump + + dump contents of the heap +================ +*/ +void idHeap::Dump( void ) { + idHeap::page_s *pg; + + for ( pg = smallFirstUsedPage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (in use by small heap)\n", pg->data, pg->dataSize); + } + + if ( smallCurPage ) { + pg = smallCurPage; + idLib::common->Printf( "%p bytes %-8d (small heap active page)\n", pg->data, pg->dataSize ); + } + + for ( pg = mediumFirstUsedPage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (completely used by medium heap)\n", pg->data, pg->dataSize ); + } + + for ( pg = mediumFirstFreePage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (partially used by medium heap)\n", pg->data, pg->dataSize ); + } + + for ( pg = largeFirstUsedPage; pg; pg = pg->next ) { + idLib::common->Printf( "%p bytes %-8d (fully used by large heap)\n", pg->data, pg->dataSize ); + } + + idLib::common->Printf( "pages allocated : %d\n", pagesAllocated ); +} + +/* +================ +idHeap::FreePageReal + + frees page to be used by the OS + p = page to free +================ +*/ +void idHeap::FreePageReal( idHeap::page_s *p ) { + assert( p ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ::local_free( p ); +// RAVEN END +} + +/* +================ +idHeap::ReleaseSwappedPages + + releases the swap page to OS +================ +*/ +void idHeap::ReleaseSwappedPages () { + if ( swapPage ) { + FreePageReal( swapPage ); + } + swapPage = NULL; +} + +/* +================ +idHeap::AllocatePage + + allocates memory from the OS + bytes = page size in bytes + returns pointer to page +================ +*/ +idHeap::page_s* idHeap::AllocatePage( dword bytes ) { + idHeap::page_s* p; + + pageRequests++; + + if ( swapPage && swapPage->dataSize == bytes ) { // if we've got a swap page somewhere + p = swapPage; + swapPage = NULL; + } + else { + dword size; + + size = bytes + sizeof(idHeap::page_s); + +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + p = (idHeap::page_s *) ::local_malloc( size + ALIGN - 1 ); +// RAVEN END + if ( !p ) { + if ( defragBlock ) { + idLib::common->Printf( "Freeing defragBlock on alloc of %i.\n", size + ALIGN - 1 ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( defragBlock ); +// RAVEN END + defragBlock = NULL; +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + p = (idHeap::page_s *) ::local_malloc( size + ALIGN - 1 ); +// RAVEN END + AllocDefragBlock(); + } + if ( !p ) { + common->FatalError( "malloc failure for %i", bytes ); + } + } + + p->data = (void *) ALIGN_SIZE( (int)((byte *)(p)) + sizeof( idHeap::page_s ) ); + p->dataSize = size - sizeof(idHeap::page_s); + p->firstFree = NULL; + p->largestFree = 0; + OSAllocs++; + } + + p->prev = NULL; + p->next = NULL; + + pagesAllocated++; + + return p; +} + +/* +================ +idHeap::FreePage + + frees a page back to the operating system + p = pointer to page +================ +*/ +void idHeap::FreePage( idHeap::page_s *p ) { + assert( p ); + + if ( p->dataSize == pageSize && !swapPage ) { // add to swap list? + swapPage = p; + } + else { + FreePageReal( p ); + } + + pagesAllocated--; +} + +//=============================================================== +// +// small heap code +// +//=============================================================== + +/* +================ +idHeap::SmallAllocate + + allocate memory (1-255 bytes) from the small heap manager + bytes = number of bytes to allocate + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::SmallAllocate( dword bytes, byte tag ) { +//RAVEN END + // we need the at least sizeof( dword ) bytes for the free list + if ( bytes < sizeof( dword ) ) { + bytes = sizeof( dword ); + } + + // increase the number of bytes if necessary to make sure the next small allocation is aligned + bytes = SMALL_ALIGN( bytes ); + + byte *smallBlock = (byte *)(smallFirstFree[bytes / ALIGN]); + if ( smallBlock ) { + dword *link = (dword *)(smallBlock + SMALL_HEADER_SIZE); +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += smallBlock[0]; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += smallBlock[0]; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + smallBlock[1] = tag; + smallBlock[2] = SMALL_ALLOC; // allocation identifier + +#else + + smallBlock[1] = SMALL_ALLOC; // allocation identifier +#endif + +//RAVEN END + smallFirstFree[bytes / ALIGN] = (void *)(*link); + return (void *)(link); + } + + dword bytesLeft = (long)(pageSize) - smallCurPageOffset; + // if we need to allocate a new page + if ( bytes >= bytesLeft ) { + + smallCurPage->next = smallFirstUsedPage; + smallFirstUsedPage = smallCurPage; + smallCurPage = AllocatePage( pageSize ); + if ( !smallCurPage ) { + return NULL; + } + // make sure the first allocation is aligned + smallCurPageOffset = SMALL_ALIGN( 0 ); + } + + smallBlock = ((byte *)smallCurPage->data) + smallCurPageOffset; + smallBlock[0] = (byte)(bytes / ALIGN); // write # of bytes/ALIGN +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += smallBlock[0]; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += smallBlock[0]; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + smallBlock[1] = tag; + smallBlock[2] = SMALL_ALLOC; // allocation identifier + +#else + + smallBlock[1] = SMALL_ALLOC; // allocation identifier +#endif +//RAVEN END + smallCurPageOffset += bytes + SMALL_HEADER_SIZE; // increase the offset on the current page + return ( smallBlock + SMALL_HEADER_SIZE ); // skip the first two bytes +} + +/* +================ +idHeap::SmallFree + + frees a block of memory allocated by SmallAllocate() call + data = pointer to block of memory +================ +*/ +void idHeap::SmallFree( void *ptr ) { + ((byte *)(ptr))[-1] = INVALID_ALLOC; + +//RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte tag = ((byte *)(ptr))[-2]; + byte size = ((byte *)(ptr))[-3]; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; +#endif +//RAVEN END + + byte *d = ( (byte *)ptr ) - SMALL_HEADER_SIZE; + dword *dt = (dword *)ptr; + // index into the table with free small memory blocks + dword ix = *d; + + // check if the index is correct + if ( ix > (256 / ALIGN) ) { + idLib::common->FatalError( "SmallFree: invalid memory block" ); + } + + *dt = (dword)smallFirstFree[ix]; // write next index + smallFirstFree[ix] = (void *)d; // link +} + +//=============================================================== +// +// medium heap code +// +// Medium-heap allocated pages not returned to OS until heap destructor +// called (re-used instead on subsequent medium-size malloc requests). +// +//=============================================================== + +/* +================ +idHeap::MediumAllocateFromPage + + performs allocation using the medium heap manager from a given page + p = page + sizeNeeded = # of bytes needed + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::MediumAllocateFromPage( idHeap::page_s *p, dword sizeNeeded, byte tag ) { +//RAVEN END + + mediumHeapEntry_s *best,*nw = NULL; + byte *ret; + + best = (mediumHeapEntry_s *)(p->firstFree); // first block is largest + + assert( best ); + assert( best->size == p->largestFree ); + assert( best->size >= sizeNeeded ); + + // if we can allocate another block from this page after allocating sizeNeeded bytes + if ( best->size >= (dword)( sizeNeeded + MEDIUM_SMALLEST_SIZE ) ) { + nw = (mediumHeapEntry_s *)((byte *)best + best->size - sizeNeeded); + nw->page = p; + nw->prev = best; + nw->next = best->next; + nw->prevFree = NULL; + nw->nextFree = NULL; + nw->size = sizeNeeded; + nw->freeBlock = 0; // used block + if ( best->next ) { + best->next->prev = nw; + } + best->next = nw; + best->size -= sizeNeeded; + + p->largestFree = best->size; + } + else { + if ( best->prevFree ) { + best->prevFree->nextFree = best->nextFree; + } + else { + p->firstFree = (void *)best->nextFree; + } + if ( best->nextFree ) { + best->nextFree->prevFree = best->prevFree; + } + + best->prevFree = NULL; + best->nextFree = NULL; + best->freeBlock = 0; // used block + nw = best; + + p->largestFree = 0; + } + + ret = (byte *)(nw) + ALIGN_SIZE( MEDIUM_HEADER_SIZE ); +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += nw->size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += nw->size; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + ret[-2] = tag; +#endif +//RAVEN END + ret[-1] = MEDIUM_ALLOC; // allocation identifier + + return (void *)(ret); +} + +/* +================ +idHeap::MediumAllocate + + allocate memory (256-32768 bytes) from medium heap manager + bytes = number of bytes to allocate + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::MediumAllocate( dword bytes, byte tag ) { +//RAVEN END + idHeap::page_s *p; + void *data; + + dword sizeNeeded = ALIGN_SIZE( bytes ) + ALIGN_SIZE( MEDIUM_HEADER_SIZE ); + + // find first page with enough space + for ( p = mediumFirstFreePage; p; p = p->next ) { + if ( p->largestFree >= sizeNeeded ) { + break; + } + } + + if ( !p ) { // need to allocate new page? + p = AllocatePage( pageSize ); + if ( !p ) { + return NULL; // malloc failure! + } + p->prev = NULL; + p->next = mediumFirstFreePage; + if (p->next) { + p->next->prev = p; + } + else { + mediumLastFreePage = p; + } + + mediumFirstFreePage = p; + + p->largestFree = pageSize; + p->firstFree = (void *)p->data; + + mediumHeapEntry_s *e; + e = (mediumHeapEntry_s *)(p->firstFree); + e->page = p; + // make sure ((byte *)e + e->size) is aligned + e->size = pageSize & ~(ALIGN - 1); + e->prev = NULL; + e->next = NULL; + e->prevFree = NULL; + e->nextFree = NULL; + e->freeBlock = 1; + } + +//RAVEN BEGIN +//amccarthy: Added allocation tag + data = MediumAllocateFromPage( p, sizeNeeded, tag ); // allocate data from page +//RAVEN END + + // if the page can no longer serve memory, move it away from free list + // (so that it won't slow down the later alloc queries) + // this modification speeds up the pageWalk from O(N) to O(sqrt(N)) + // a call to free may swap this page back to the free list + + if ( p->largestFree < MEDIUM_SMALLEST_SIZE ) { + if ( p == mediumLastFreePage ) { + mediumLastFreePage = p->prev; + } + + if ( p == mediumFirstFreePage ) { + mediumFirstFreePage = p->next; + } + + if ( p->prev ) { + p->prev->next = p->next; + } + if ( p->next ) { + p->next->prev = p->prev; + } + + // link to "completely used" list + p->prev = NULL; + p->next = mediumFirstUsedPage; + if ( p->next ) { + p->next->prev = p; + } + mediumFirstUsedPage = p; + return data; + } + + // re-order linked list (so that next malloc query starts from current + // matching block) -- this speeds up both the page walks and block walks + + if ( p != mediumFirstFreePage ) { + assert( mediumLastFreePage ); + assert( mediumFirstFreePage ); + assert( p->prev); + + mediumLastFreePage->next = mediumFirstFreePage; + mediumFirstFreePage->prev = mediumLastFreePage; + mediumLastFreePage = p->prev; + p->prev->next = NULL; + p->prev = NULL; + mediumFirstFreePage = p; + } + + return data; +} + +/* +================ +idHeap::MediumFree + + frees a block allocated by the medium heap manager + ptr = pointer to data block +================ +*/ +void idHeap::MediumFree( void *ptr ) { + ((byte *)(ptr))[-1] = INVALID_ALLOC; + + mediumHeapEntry_s *e = (mediumHeapEntry_s *)((byte *)ptr - ALIGN_SIZE( MEDIUM_HEADER_SIZE )); + idHeap::page_s *p = e->page; + bool isInFreeList; + + isInFreeList = p->largestFree >= MEDIUM_SMALLEST_SIZE; + + assert( e->size ); + assert( e->freeBlock == 0 ); + +//RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte tag = ((byte *)(ptr))[-2]; + dword size = e->size; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; +#endif +//RAVEN END + + mediumHeapEntry_s *prev = e->prev; + + // if the previous block is free we can merge + if ( prev && prev->freeBlock ) { + prev->size += e->size; + prev->next = e->next; + if ( e->next ) { + e->next->prev = prev; + } + e = prev; + } + else { + e->prevFree = NULL; // link to beginning of free list + e->nextFree = (mediumHeapEntry_s *)p->firstFree; + if ( e->nextFree ) { + assert( !(e->nextFree->prevFree) ); + e->nextFree->prevFree = e; + } + + p->firstFree = e; + p->largestFree = e->size; + e->freeBlock = 1; // mark block as free + } + + mediumHeapEntry_s *next = e->next; + + // if the next block is free we can merge + if ( next && next->freeBlock ) { + e->size += next->size; + e->next = next->next; + + if ( next->next ) { + next->next->prev = e; + } + + if ( next->prevFree ) { + next->prevFree->nextFree = next->nextFree; + } + else { + assert( next == p->firstFree ); + p->firstFree = next->nextFree; + } + + if ( next->nextFree ) { + next->nextFree->prevFree = next->prevFree; + } + } + + if ( p->firstFree ) { + p->largestFree = ((mediumHeapEntry_s *)(p->firstFree))->size; + } + else { + p->largestFree = 0; + } + + // did e become the largest block of the page ? + + if ( e->size > p->largestFree ) { + assert( e != p->firstFree ); + p->largestFree = e->size; + + if ( e->prevFree ) { + e->prevFree->nextFree = e->nextFree; + } + if ( e->nextFree ) { + e->nextFree->prevFree = e->prevFree; + } + + e->nextFree = (mediumHeapEntry_s *)p->firstFree; + e->prevFree = NULL; + if ( e->nextFree ) { + e->nextFree->prevFree = e; + } + p->firstFree = e; + } + + // if page wasn't in free list (because it was near-full), move it back there + if ( !isInFreeList ) { + + // remove from "completely used" list + if ( p->prev ) { + p->prev->next = p->next; + } + if ( p->next ) { + p->next->prev = p->prev; + } + if ( p == mediumFirstUsedPage ) { + mediumFirstUsedPage = p->next; + } + + p->next = NULL; + p->prev = mediumLastFreePage; + + if ( mediumLastFreePage ) { + mediumLastFreePage->next = p; + } + mediumLastFreePage = p; + if ( !mediumFirstFreePage ) { + mediumFirstFreePage = p; + } + } +} + +//=============================================================== +// +// large heap code +// +//=============================================================== + +/* +================ +idHeap::LargeAllocate + + allocates a block of memory from the operating system + bytes = number of bytes to allocate + returns pointer to allocated memory +================ +*/ +//RAVEN BEGIN +//amccarthy: Added allocation tag +void *idHeap::LargeAllocate( dword bytes, byte tag ) { +//RAVEN END + idHeap::page_s *p = AllocatePage( bytes + ALIGN_SIZE( LARGE_HEADER_SIZE ) ); + + assert( p ); + + if ( !p ) { + return NULL; + } + + byte * d = (byte*)(p->data) + ALIGN_SIZE( LARGE_HEADER_SIZE ); + dword * dw = (dword*)(d - ALIGN_SIZE( LARGE_HEADER_SIZE )); + dw[0] = (dword)p; // write pointer back to page table +//RAVEN BEGIN +//amccarthy: Added allocation tag +#ifdef _DEBUG + OutstandingXMallocSize += p->dataSize; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] += p->dataSize; + if (OutstandingXMallocTagSize[tag] > PeakXMallocTagSize[tag]) + { + PeakXMallocTagSize[tag] = OutstandingXMallocTagSize[tag]; + } + CurrNumAllocations[tag]++; + d[-2] = tag; +#endif +//RAVEN END + d[-1] = LARGE_ALLOC; // allocation identifier + + // link to 'large used page list' + p->prev = NULL; + p->next = largeFirstUsedPage; + if ( p->next ) { + p->next->prev = p; + } + largeFirstUsedPage = p; + + return (void *)(d); +} + +/* +================ +idHeap::LargeFree + + frees a block of memory allocated by the 'large memory allocator' + p = pointer to allocated memory +================ +*/ +void idHeap::LargeFree( void *ptr) { + idHeap::page_s* pg; + + ((byte *)(ptr))[-1] = INVALID_ALLOC; + + // get page pointer + pg = (idHeap::page_s *)(*((dword *)(((byte *)ptr) - ALIGN_SIZE( LARGE_HEADER_SIZE )))); + + //RAVEN BEGIN +//amccarthy: allocation tracking +#ifdef _DEBUG + byte tag = ((byte *)(ptr))[-2]; + dword size = pg->dataSize; + OutstandingXMallocSize -= size; + assert( tag > 0 && tag < MA_MAX); + OutstandingXMallocTagSize[tag] -= size; + CurrNumAllocations[tag]--; +#endif +//RAVEN END + + // unlink from doubly linked list + if ( pg->prev ) { + pg->prev->next = pg->next; + } + if ( pg->next ) { + pg->next->prev = pg->prev; + } + if ( pg == largeFirstUsedPage ) { + largeFirstUsedPage = pg->next; + } + pg->next = pg->prev = NULL; + + FreePage(pg); +} + +//=============================================================== +// +// memory allocation all in one place +// +//=============================================================== + +#undef new + +static idHeap * mem_heap = NULL; +static memoryStats_t mem_total_allocs = { 0, 0x0fffffff, -1, 0 }; +static memoryStats_t mem_frame_allocs; +static memoryStats_t mem_frame_frees; + +/* +================== +Mem_ClearFrameStats +================== +*/ +void Mem_ClearFrameStats( void ) { + mem_frame_allocs.num = mem_frame_frees.num = 0; + mem_frame_allocs.minSize = mem_frame_frees.minSize = 0x0fffffff; + mem_frame_allocs.maxSize = mem_frame_frees.maxSize = -1; + mem_frame_allocs.totalSize = mem_frame_frees.totalSize = 0; +} + +/* +================== +Mem_GetFrameStats +================== +*/ +void Mem_GetFrameStats( memoryStats_t &allocs, memoryStats_t &frees ) { + allocs = mem_frame_allocs; + frees = mem_frame_frees; +} + +/* +================== +Mem_GetStats +================== +*/ +void Mem_GetStats( memoryStats_t &stats ) { + stats = mem_total_allocs; +} + +/* +================== +Mem_UpdateStats +================== +*/ +void Mem_UpdateStats( memoryStats_t &stats, int size ) { + stats.num++; + if ( size < stats.minSize ) { + stats.minSize = size; + } + if ( size > stats.maxSize ) { + stats.maxSize = size; + } + stats.totalSize += size; +} + +/* +================== +Mem_UpdateAllocStats +================== +*/ +void Mem_UpdateAllocStats( int size ) { + Mem_UpdateStats( mem_frame_allocs, size ); + Mem_UpdateStats( mem_total_allocs, size ); +} + +/* +================== +Mem_UpdateFreeStats +================== +*/ +void Mem_UpdateFreeStats( int size ) { + Mem_UpdateStats( mem_frame_frees, size ); + mem_total_allocs.num--; + mem_total_allocs.totalSize -= size; +} + + +#ifndef ID_DEBUG_MEMORY + +/* +================== +Mem_Alloc +================== +*/ +// RAVEN BEGIN +// amccarthy: Added allocation tag +void *Mem_Alloc( const int size, byte tag ) { + if ( !size ) { + return NULL; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// jnewquist: send all allocations through one place on the Xenon + return local_malloc( size ); + } +// amccarthy: Added allocation tag + void *mem = mem_heap->Allocate( size, tag ); + Mem_UpdateAllocStats( mem_heap->Msize( mem ) ); + return mem; +} +// RAVEN END + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr ) { + if ( !ptr ) { + return; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( ptr ); +// RAVEN END + return; + } + Mem_UpdateFreeStats( mem_heap->Msize( ptr ) ); + mem_heap->Free( ptr ); +} + +/* +================== +Mem_Alloc16 +================== +*/ +// RAVEN BEGIN +// amccarthy: Added allocation tag +void *Mem_Alloc16( const int size, byte tag ) { + if ( !size ) { + return NULL; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// jnewquist: send all allocations through one place on the Xenon + return local_malloc( size ); + } + +// amccarthy: Added allocation tag + void *mem = mem_heap->Allocate16( size, tag ); + // make sure the memory is 16 byte aligned + assert( ( ((int)mem) & 15) == 0 ); + return mem; +} +// RAVEN END + +/* +================== +Mem_Free16 +================== +*/ +void Mem_Free16( void *ptr ) { + if ( !ptr ) { + return; + } + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + local_free( ptr ); +// RAVEN END + return; + } + // make sure the memory is 16 byte aligned + assert( ( ((int)ptr) & 15) == 0 ); + mem_heap->Free16( ptr ); +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +// RAVEN BEGIN +// amccarthy: Added allocation tag +void *Mem_ClearedAlloc( const int size, byte tag ) { + void *mem = Mem_Alloc( size, tag ); +// RAVEN END + SIMDProcessor->Memset( mem, 0, size ); + return mem; +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +void Mem_AllocDefragBlock( void ) { + mem_heap->AllocDefragBlock(); +} + +/* +================== +Mem_CopyString +================== +*/ +char *Mem_CopyString( const char *in ) { + char *out; + +// RAVEN BEGIN +// amccarthy: Added allocation tag + out = (char *)Mem_Alloc( strlen(in) + 1, MA_STRING ); +// RAVEN END + strcpy( out, in ); + return out; +} + +/* +================== +Mem_Dump_f +================== +*/ +void Mem_Dump_f( const idCmdArgs &args ) { +} + +/* +================== +Mem_DumpCompressed_f +================== +*/ +void Mem_DumpCompressed_f( const idCmdArgs &args ) { +} + +/* +================== +Mem_Init +================== +*/ +void Mem_Init( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + mem_heap = new idHeap; + Mem_ClearFrameStats(); +} + +/* +================== +Mem_Shutdown +================== +*/ +void Mem_Shutdown( void ) { + idHeap *m = mem_heap; + mem_heap = NULL; + delete m; +} + +/* +================== +Mem_EnableLeakTest +================== +*/ +void Mem_EnableLeakTest( const char *name ) { +} + + +#else /* !ID_DEBUG_MEMORY */ + +#undef Mem_Alloc +#undef Mem_ClearedAlloc +#undef Com_ClearedReAlloc +#undef Mem_Free +#undef Mem_CopyString +#undef Mem_Alloc16 +#undef Mem_Free16 + +#define MAX_CALLSTACK_DEPTH 6 + +// RAVEN BEGIN +// jnewquist: Add a signature to avoid misinterpreting an early allocation +// size of this struct must be a multiple of 16 bytes +typedef struct debugMemory_s { + unsigned short signature; + unsigned short lineNumber; + const char * fileName; + int frameNumber; + int size; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct debugMemory_s * prev; + struct debugMemory_s * next; +} debugMemory_t; +// RAVEN END + +static debugMemory_t * mem_debugMemory = NULL; +static char mem_leakName[256] = ""; + +/* +================== +Mem_CleanupFileName +================== +*/ +const char *Mem_CleanupFileName( const char *fileName ) { + int i1, i2; + idStr newFileName; + static char newFileNames[4][MAX_STRING_CHARS]; + static int index; + + newFileName = fileName; + newFileName.BackSlashesToSlashes(); + i1 = newFileName.Find( "neo", false ); + if ( i1 >= 0 ) { + i1 = newFileName.Find( "/", false, i1 ); + newFileName = newFileName.Right( newFileName.Length() - ( i1 + 1 ) ); + } + while( 1 ) { + i1 = newFileName.Find( "/../" ); + if ( i1 <= 0 ) { + break; + } + i2 = i1 - 1; + while( i2 > 1 && newFileName[i2-1] != '/' ) { + i2--; + } + newFileName = newFileName.Left( i2 - 1 ) + newFileName.Right( newFileName.Length() - ( i1 + 4 ) ); + } + index = ( index + 1 ) & 3; + strncpy( newFileNames[index], newFileName.c_str(), sizeof( newFileNames[index] ) ); + return newFileNames[index]; +} + +/* +================== +Mem_Dump +================== +*/ +void Mem_Dump( const char *fileName ) { + int i, numBlocks, totalSize; + char dump[32], *ptr; + debugMemory_t *b; + idStr module, funcName; + FILE *f; + + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + totalSize = 0; + for ( numBlocks = 0, b = mem_debugMemory; b; b = b->next, numBlocks++ ) { + ptr = ((char *) b) + sizeof(debugMemory_t); + totalSize += b->size; + for ( i = 0; i < (sizeof(dump)-1) && i < b->size; i++) { + if ( ptr[i] >= 32 && ptr[i] < 127 ) { + dump[i] = ptr[i]; + } else { + dump[i] = '_'; + } + } + dump[i] = '\0'; + if ( ( b->size >> 10 ) != 0 ) { + fprintf( f, "size: %6d KB: %s, line: %d [%s], call stack: %s\r\n", ( b->size >> 10 ), Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + else { + fprintf( f, "size: %7d B: %s, line: %d [%s], call stack: %s\r\n", b->size, Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +} + + + +/* +================== +Mem_Dump_f +================== +*/ +void Mem_Dump_f( const idCmdArgs &args ) { + const char *fileName; + + if ( args.Argc() >= 2 ) { + fileName = args.Argv( 1 ); + } + else { + fileName = "memorydump.txt"; + } + Mem_Dump( fileName ); +} + +/* +================== +Mem_DumpCompressed +================== +*/ + +typedef struct allocInfo_s { + const char * fileName; + int lineNumber; + int size; + int numAllocs; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct allocInfo_s * next; +} allocInfo_t; + +typedef enum { + MEMSORT_SIZE, + MEMSORT_LOCATION, + MEMSORT_NUMALLOCS, + MEMSORT_CALLSTACK +} memorySortType_t; + +void Mem_DumpCompressed( const char *fileName, memorySortType_t memSort, int sortCallStack, int numFrames ) { + int numBlocks, totalSize, r, j; + debugMemory_t *b; + allocInfo_t *a, *nexta, *allocInfo = NULL, *sortedAllocInfo = NULL, *prevSorted, *nextSorted; + idStr module, funcName; + + // build list with memory allocations + totalSize = 0; + numBlocks = 0; +// RAVEN BEGIN + nextSorted = NULL; +// RAVEN END + + for ( b = mem_debugMemory; b; b = b->next ) { + + if ( numFrames && b->frameNumber < idLib::frameNumber - numFrames ) { + continue; + } + + numBlocks++; + totalSize += b->size; + + // search for an allocation from the same source location + for ( a = allocInfo; a; a = a->next ) { + if ( a->lineNumber != b->lineNumber ) { + continue; + } +// RAVEN BEGIN +// dluetscher: removed the call stack info for better consolidation of info and speed of dump +#ifndef _XENON + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + if ( a->callStack[j] != b->callStack[j] ) { + break; + } + } + if ( j < MAX_CALLSTACK_DEPTH ) { + continue; + } +#endif +// RAVEN END + if ( idStr::Cmp( a->fileName, b->fileName ) != 0 ) { + continue; + } + a->numAllocs++; + a->size += b->size; + break; + } + + // if this is an allocation from a new source location + if ( !a ) { +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + a = (allocInfo_t *) ::local_malloc( sizeof( allocInfo_t ) ); +// RAVEN END + a->fileName = b->fileName; + a->lineNumber = b->lineNumber; + a->size = b->size; + a->numAllocs = 1; + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + a->callStack[j] = b->callStack[j]; + } + a->next = allocInfo; + allocInfo = a; + } + } + + // sort list + for ( a = allocInfo; a; a = nexta ) { + nexta = a->next; + + prevSorted = NULL; + switch( memSort ) { + // sort on size + case MEMSORT_SIZE: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->size > nextSorted->size ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on file name and line number + case MEMSORT_LOCATION: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + r = idStr::Cmp( Mem_CleanupFileName( a->fileName ), Mem_CleanupFileName( nextSorted->fileName ) ); + if ( r < 0 || ( r == 0 && a->lineNumber < nextSorted->lineNumber ) ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on the number of allocations + case MEMSORT_NUMALLOCS: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->numAllocs > nextSorted->numAllocs ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on call stack + case MEMSORT_CALLSTACK: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->callStack[sortCallStack] < nextSorted->callStack[sortCallStack] ) { + break; + } + prevSorted = nextSorted; + } + break; + } + } + if ( !prevSorted ) { + a->next = sortedAllocInfo; + sortedAllocInfo = a; + } + else { + prevSorted->next = a; + a->next = nextSorted; + } + } + +// RAVEN BEGIN +// dluetscher: changed xenon version to output anything above 1K to the console +#ifdef _XENON + // write list to debug output and console + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + if ( (a->size >> 10) > 0 ) { + + idLib::common->Printf("size: %6d KB, allocs: %5d: %s, line: %d, call stack: %s\r\n", + (a->size >> 10), a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); + + } + ::local_free( a ); + } + + idLib::sys->ShutdownSymbols(); + + idLib::common->Printf("%8d total memory blocks allocated\r\n", numBlocks ); + idLib::common->Printf("%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); +#else +// RAVEN END + FILE *f; + + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + // write list to file + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + fprintf( f, "size: %6d KB, allocs: %5d: %s, line: %d, call stack: %s\r\n", + (a->size >> 10), a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + ::local_free( a ); +// RAVEN END + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +#endif +} + +/* +================== +Mem_DumpCompressed_f +================== +*/ +void Mem_DumpCompressed_f( const idCmdArgs &args ) { + int argNum; + const char *arg, *fileName; + memorySortType_t memSort = MEMSORT_LOCATION; + int sortCallStack = 0, numFrames = 0; + + // get cmd-line options + argNum = 1; + arg = args.Argv( argNum ); + while( arg[0] == '-' ) { + arg = args.Argv( ++argNum ); + if ( idStr::Icmp( arg, "s" ) == 0 ) { + memSort = MEMSORT_SIZE; + } else if ( idStr::Icmp( arg, "l" ) == 0 ) { + memSort = MEMSORT_LOCATION; + } else if ( idStr::Icmp( arg, "a" ) == 0 ) { + memSort = MEMSORT_NUMALLOCS; + } else if ( idStr::Icmp( arg, "cs1" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 2; + } else if ( idStr::Icmp( arg, "cs2" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 1; + } else if ( idStr::Icmp( arg, "cs3" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 0; + } else if ( arg[0] == 'f' ) { + numFrames = atoi( arg + 1 ); + } else { + idLib::common->Printf( "memoryDumpCompressed [options] [filename]\n" + "options:\n" + " -s sort on size\n" + " -l sort on location\n" + " -a sort on the number of allocations\n" + " -cs1 sort on first function on call stack\n" + " -cs2 sort on second function on call stack\n" + " -cs3 sort on third function on call stack\n" + " -f only report allocations the last X frames\n" + "By default the memory allocations are sorted on location.\n" + "By default a 'memorydump.txt' is written if no file name is specified.\n" ); + return; + } + arg = args.Argv( ++argNum ); + } + if ( argNum >= args.Argc() ) { + fileName = "memorydump.txt"; + } else { + fileName = arg; + } + Mem_DumpCompressed( fileName, memSort, sortCallStack, numFrames ); +} + +/* +================== +Mem_AllocDebugMemory +================== +*/ +// RAVEN BEGIN +void *Mem_AllocDebugMemory( const int size, const char *fileName, const int lineNumber, const bool align16, byte tag ) { +// RAVEN END + void *p; + debugMemory_t *m; + + if ( !size ) { + return NULL; + } + + if ( !mem_heap ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif + // NOTE: set a breakpoint here to find memory allocations before mem_heap is initialized +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + return local_malloc( size ); +// RAVEN END + } + + if ( align16 ) { +// RAVEN BEGIN + p = mem_heap->Allocate16( size + sizeof( debugMemory_t ), tag ); + } + else { + p = mem_heap->Allocate( size + sizeof( debugMemory_t ), tag ); +// RAVEN END + } + + Mem_UpdateAllocStats( size ); + + m = (debugMemory_t *) p; +// RAVEN BEGIN +// jnewquist: Add a signature to avoid misinterpreting an early allocation + m->signature = 0xf00d; +// RAVEN END + m->fileName = fileName; + m->lineNumber = lineNumber; + m->frameNumber = idLib::frameNumber; + m->size = size; + m->next = mem_debugMemory; + m->prev = NULL; + if ( mem_debugMemory ) { + mem_debugMemory->prev = m; + } + mem_debugMemory = m; + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + + return ( ( (byte *) p ) + sizeof( debugMemory_t ) ); +} + +/* +================== +Mem_FreeDebugMemory +================== +*/ +void Mem_FreeDebugMemory( void *p, const char *fileName, const int lineNumber, const bool align16 ) { + debugMemory_t *m; + + if ( !p ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Add a signature to avoid misinterpreting an early allocation + m = (debugMemory_t *) ( ( (byte *) p ) - sizeof( debugMemory_t ) ); + + if ( !mem_heap || m->signature != 0xf00d ) { +#ifdef CRASH_ON_STATIC_ALLOCATION + *((int*)0x0) = 1; +#endif + // NOTE: set a breakpoint here to find memory being freed before mem_heap is initialized +// jnewquist: send all allocations through one place on the Xenon + local_free( p ); + return; + } + +// RAVEN END + if ( m->size < 0 ) { + idLib::common->FatalError( "memory freed twice, first from %s, now from %s", idLib::sys->GetCallStackStr( m->callStack, MAX_CALLSTACK_DEPTH ), idLib::sys->GetCallStackCurStr( MAX_CALLSTACK_DEPTH ) ); + } + + Mem_UpdateFreeStats( m->size ); + + if ( m->next ) { + m->next->prev = m->prev; + } + if ( m->prev ) { + m->prev->next = m->next; + } + else { + mem_debugMemory = m->next; + } + + m->fileName = fileName; + m->lineNumber = lineNumber; + m->frameNumber = idLib::frameNumber; + m->size = -m->size; + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + + if ( align16 ) { + mem_heap->Free16( m ); + } + else { + mem_heap->Free( m ); + } +} + +/* +================== +Mem_Alloc +================== +*/ +void *Mem_Alloc( const int size, const char *fileName, const int lineNumber, byte tag ) { + if ( !size ) { + return NULL; + } + return Mem_AllocDebugMemory( size, fileName, lineNumber, false, tag ); +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr, const char *fileName, const int lineNumber ) { + if ( !ptr ) { + return; + } + Mem_FreeDebugMemory( ptr, fileName, lineNumber, false ); +} + +/* +================== +Mem_Alloc16 +================== +*/ +void *Mem_Alloc16( const int size, const char *fileName, const int lineNumber, byte tag ) { + if ( !size ) { + return NULL; + } + void *mem = Mem_AllocDebugMemory( size, fileName, lineNumber, true, tag ); + // make sure the memory is 16 byte aligned + assert( ( ((int)mem) & 15) == 0 ); + return mem; +} + +/* +================== +Mem_Free16 +================== +*/ +void Mem_Free16( void *ptr, const char *fileName, const int lineNumber ) { + if ( !ptr ) { + return; + } + // make sure the memory is 16 byte aligned + assert( ( ((int)ptr) & 15) == 0 ); + Mem_FreeDebugMemory( ptr, fileName, lineNumber, true ); +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +void *Mem_ClearedAlloc( const int size, const char *fileName, const int lineNumber, byte tag ) { + void *mem = Mem_Alloc( size, fileName, lineNumber, tag ); + SIMDProcessor->Memset( mem, 0, size ); + return mem; +} + +/* +================== +Mem_CopyString +================== +*/ +char *Mem_CopyString( const char *in, const char *fileName, const int lineNumber ) { + char *out; + + out = (char *)Mem_Alloc( strlen(in) + 1, fileName, lineNumber ); + strcpy( out, in ); + return out; +} + +/* +================== +Mem_Init +================== +*/ +void Mem_Init( void ) { + mem_heap = new idHeap; +} + +/* +================== +Mem_Shutdown +================== +*/ +void Mem_Shutdown( void ) { + + if ( mem_leakName[0] != '\0' ) { + Mem_DumpCompressed( va( "%s_leak_size.txt", mem_leakName ), MEMSORT_SIZE, 0, 0 ); + Mem_DumpCompressed( va( "%s_leak_location.txt", mem_leakName ), MEMSORT_LOCATION, 0, 0 ); + Mem_DumpCompressed( va( "%s_leak_cs1.txt", mem_leakName ), MEMSORT_CALLSTACK, 2, 0 ); + } + + idHeap *m = mem_heap; + mem_heap = NULL; + delete m; +} + +/* +================== +Mem_EnableLeakTest +================== +*/ +void Mem_EnableLeakTest( const char *name ) { + idStr::Copynz( mem_leakName, name, sizeof( mem_leakName ) ); +} + +// RAVEN BEGIN +// jnewquist: Add Mem_Size to query memory allocation size +/* +================== +Mem_Size +================== +*/ +int Mem_Size( void *ptr ) { + return mem_heap->Msize(ptr); +} +// RAVEN END + +#endif /* !ID_DEBUG_MEMORY */ + +#endif // #ifndef _RV_MEM_SYS_SUPPORT diff --git a/src/idlib/Heap.h b/src/idlib/Heap.h new file mode 100644 index 0000000..4215a70 --- /dev/null +++ b/src/idlib/Heap.h @@ -0,0 +1,1191 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __HEAP_H__ +#define __HEAP_H__ + +/* +=============================================================================== + + Memory Management + + This is a replacement for the compiler heap code (i.e. "C" malloc() and + free() calls). On average 2.5-3.0 times faster than MSVC malloc()/free(). + Worst case performance is 1.65 times faster and best case > 70 times. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +class Memory +{ +public: + static void *Allocate(size_t size) + { + if(mAllocator) + { + return mAllocator(size); + } + else + { +#ifndef _LOAD_DLL + // allocations from the exe are safe no matter what, but allocations from a DLL through + // this path will be unsafe, so we warn about them. Adding a breakpoint here will allow + // allow locating of the specific offending allocation + Error("Unprotected allocations are not allowed. Make sure you've initialized Memory before allocating dynamic memory"); +#endif + return malloc(size); + } + } + + static void Free(void *ptr) + { + if(mDeallocator) + { + mDeallocator(ptr); + } + else + { +#ifndef _LOAD_DLL + // allocations from the exe are safe no matter what, but allocations from a DLL through + // this path will be unsafe, so we warn about them. Adding a breakpoint here will allow + // allow locating of the specific offending allocation + Error("Unprotected allocations are not allowed. Make sure you've initialized Memory before allocating dynamic memory"); +#endif + return free(ptr); + } + } + + static size_t MSize(void *ptr) + { + if(mMSize) + { + return mMSize(ptr); + } + else + { +#ifndef _LOAD_DLL + // allocations from the exe are safe no matter what, but allocations from a DLL through + // this path will be unsafe, so we warn about them. Adding a breakpoint here will allow + // allow locating of the specific offending allocation + Error("Unprotected allocations are not allowed. Make sure you've initialized Memory before allocating dynamic memory"); +#endif + return _msize(ptr); + } + } + + static void InitAllocator(void *(*allocator)(size_t size), void (*deallocator)(void *), size_t (*msize)(void *ptr)) + { + // check for errors prior to initialization + if(!sOK) + { + Error("Unprotected allocation in DLL detected prior to initialization of memory system"); + } + + mAllocator = allocator; + mDeallocator = deallocator; + mMSize = msize; + } + + static void DeinitAllocator() + { + mAllocator = NULL; + mDeallocator = NULL; + mMSize = NULL; + } + + static void Error(const char *errStr); + +private: + static void *(*mAllocator)(size_t size); + static void (*mDeallocator)(void *ptr); + static size_t (*mMSize)(void *ptr); + static bool sOK; +}; +#endif +// RAVEN END + +typedef struct { + int num; + int minSize; + int maxSize; + int totalSize; +} memoryStats_t; + +// RAVEN BEGIN +// amccarthy: tags for memory allocation tracking. When updating this list please update the +// list of discriptions in Heap.cpp as well. +typedef enum { + MA_NONE = 0, + + MA_OPNEW, + MA_DEFAULT, + MA_LEXER, + MA_PARSER, + MA_AAS, + MA_CLASS, + MA_SCRIPT, + MA_CM, + MA_CVAR, + MA_DECL, + MA_FILESYS, + MA_IMAGES, + MA_MATERIAL, + MA_MODEL, + MA_FONT, + MA_RENDER, + MA_VERTEX, + MA_SOUND, + MA_WINDOW, + MA_EVENT, + MA_MATH, + MA_ANIM, + MA_DYNAMICBLOCK, + MA_STRING, + MA_GUI, + MA_EFFECT, + MA_ENTITY, + MA_PHYSICS, + MA_AI, + MA_NETWORK, + + MA_DO_NOT_USE, // neither of the two remaining enumerated values should be used (no use of MA_DO_NOT_USE prevents the second dword in a memory block from getting the value 0xFFFFFFFF) + MA_MAX // <- this enumerated value is a count and cannot exceed 32 (5 bits are used to encode tag within memory block with rvHeap.cpp) +} Mem_Alloc_Types_t; + +#if defined(_DEBUG) || defined(_RV_MEM_SYS_SUPPORT) +const char *GetMemAllocStats(int tag, int &num, int &size, int &peak); +#endif +// RAVEN END + +void Mem_Init( void ); +void Mem_Shutdown( void ); +void Mem_EnableLeakTest( const char *name ); +void Mem_ClearFrameStats( void ); +void Mem_GetFrameStats( memoryStats_t &allocs, memoryStats_t &frees ); +void Mem_GetStats( memoryStats_t &stats ); +void Mem_Dump_f( const class idCmdArgs &args ); +void Mem_DumpCompressed_f( const class idCmdArgs &args ); +void Mem_AllocDefragBlock( void ); + +// RAVEN BEGIN +// amccarthy command for printing tagged mem_alloc info +void Mem_ShowMemAlloc_f( const idCmdArgs &args ); + +// jnewquist: Add Mem_Size to query memory allocation size +int Mem_Size( void *ptr ); + +// jnewquist: memory tag stack for new/delete +#if (defined(_DEBUG) || defined(_RV_MEM_SYS_SUPPORT)) && !defined(ENABLE_INTEL_SMP) +class MemScopedTag { + byte mTag; + MemScopedTag *mPrev; + static MemScopedTag *mTop; +public: + MemScopedTag( byte tag ) { + mTag = tag; + mPrev = mTop; + mTop = this; + } + ~MemScopedTag() { + assert( mTop != NULL ); + mTop = mTop->mPrev; + } + void SetTag( byte tag ) { + mTag = tag; + } + static byte GetTopTag( void ) { + if ( mTop ) { + return mTop->mTag; + } else { + return MA_OPNEW; + } + } +}; +#define MemScopedTag_GetTopTag() MemScopedTag::GetTopTag() +#define MEM_SCOPED_TAG(var, tag) MemScopedTag var(tag) +#define MEM_SCOPED_TAG_SET(var, tag) var.SetTag(tag) +#else +#define MemScopedTag_GetTopTag() MA_OPNEW +#define MEM_SCOPED_TAG(var, tag) +#define MEM_SCOPED_TAG_SET(var, tag) +#endif + +// RAVEN END + +#ifndef ID_DEBUG_MEMORY + +// RAVEN BEGIN +// amccarthy: added tags from memory allocation tracking. +void * Mem_Alloc( const int size, byte tag = MA_DEFAULT ); +void * Mem_ClearedAlloc( const int size, byte tag = MA_DEFAULT ); +void Mem_Free( void *ptr ); +char * Mem_CopyString( const char *in ); +void * Mem_Alloc16( const int size, byte tag=MA_DEFAULT ); +void Mem_Free16( void *ptr ); + +// jscott: standardised stack allocation +inline void *Mem_StackAlloc( const int size ) { return( _alloca( size ) ); } +inline void *Mem_StackAlloc16( const int size ) { + byte *addr = ( byte * )_alloca( size + 15 ); + addr = ( byte * )( ( int )( addr + 15 ) & 0xfffffff0 ); + return( ( void * )addr ); +} + +// dluetscher: moved the inline new/delete operators to sys_local.cpp and Game_local.cpp so that +// Tools.dll will link. +#if defined(_XBOX) || defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) + +void *operator new( size_t s ); +void operator delete( void *p ); +void *operator new[]( size_t s ); +void operator delete[]( void *p ); + +#endif +// RAVEN END + +#else /* ID_DEBUG_MEMORY */ + +// RAVEN BEGIN +// amccarthy: added tags from memory allocation tracking. +void * Mem_Alloc( const int size, const char *fileName, const int lineNumber, byte tag = MA_DEFAULT ); +void * Mem_ClearedAlloc( const int size, const char *fileName, const int lineNumber, byte tag = MA_DEFAULT ); +void Mem_Free( void *ptr, const char *fileName, const int lineNumber ); +char * Mem_CopyString( const char *in, const char *fileName, const int lineNumber ); +void * Mem_Alloc16( const int size, const char *fileName, const int lineNumber, byte tag = MA_DEFAULT); +void Mem_Free16( void *ptr, const char *fileName, const int lineNumber ); + + +// jscott: standardised stack allocation +inline void *Mem_StackAlloc( const int size ) { return( _alloca( size ) ); } +inline void *Mem_StackAlloc16( const int size ) { + byte *addr = ( byte * )_alloca( size + 15 ); + addr = ( byte * )( ( int )( addr + 15 ) & 0xfffffff0 ); + return( ( void * )addr ); +} + +// dluetscher: moved the inline new/delete operators to sys_local.cpp and Game_local.cpp so that +// the Tools.dll will link. +#if defined(_XBOX) || defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) + +void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ); +void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ); +void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ); +void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ); + +void *operator new( size_t s ); +void operator delete( void *p ); +void *operator new[]( size_t s ); +void operator delete[]( void *p ); +// RAVEN END + +#define ID_DEBUG_NEW new( 0, 0, __FILE__, __LINE__ ) +#undef new +#define new ID_DEBUG_NEW + +#endif + +#define Mem_Alloc( size, tag ) Mem_Alloc( size, __FILE__, __LINE__ ) +#define Mem_ClearedAlloc( size, tag ) Mem_ClearedAlloc( size, __FILE__, __LINE__ ) +#define Mem_Free( ptr ) Mem_Free( ptr, __FILE__, __LINE__ ) +#define Mem_CopyString( s ) Mem_CopyString( s, __FILE__, __LINE__ ) +#define Mem_Alloc16( size, tag ) Mem_Alloc16( size, __FILE__, __LINE__ ) +#define Mem_Free16( ptr ) Mem_Free16( ptr, __FILE__, __LINE__ ) +// RAVEN END +#endif /* ID_DEBUG_MEMORY */ + + +/* +=============================================================================== + + Block based allocator for fixed size objects. + + All objects of the 'type' are properly constructed. + However, the constructor is not called for re-used objects. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +template +class idBlockAlloc { +public: + idBlockAlloc( void ); + ~idBlockAlloc( void ); + + void Shutdown( void ); + + type * Alloc( void ); + void Free( type *element ); + + int GetTotalCount( void ) const { return total; } + int GetAllocCount( void ) const { return active; } + int GetFreeCount( void ) const { return total - active; } + +// RAVEN BEGIN +// jscott: get the amount of memory used + size_t Allocated( void ) const { return( total * sizeof( type ) ); } +// RAVEN END + +private: + typedef struct element_s { + struct element_s * next; + type t; + } element_t; + typedef struct block_s { + element_t elements[blockSize]; + struct block_s * next; + } block_t; + + block_t * blocks; + element_t * free; + int total; + int active; +}; + +template +idBlockAlloc::idBlockAlloc( void ) { + blocks = NULL; + free = NULL; + total = active = 0; +} + +template +idBlockAlloc::~idBlockAlloc( void ) { + Shutdown(); +} + +template +type *idBlockAlloc::Alloc( void ) { + if ( !free ) { + MEM_SCOPED_TAG(tag, memoryTag); + block_t *block = new block_t; + block->next = blocks; + blocks = block; + for ( int i = 0; i < blockSize; i++ ) { + block->elements[i].next = free; + free = &block->elements[i]; + } + total += blockSize; + } + active++; + element_t *element = free; + free = free->next; + element->next = NULL; + return &element->t; +} + +template +void idBlockAlloc::Free( type *t ) { + element_t *element = (element_t *)( ( (unsigned char *) t ) - ( (int) &((element_t *)0)->t ) ); + element->next = free; + free = element; + active--; +} + +template +void idBlockAlloc::Shutdown( void ) { + while( blocks ) { + block_t *block = blocks; + blocks = blocks->next; + delete block; + } + blocks = NULL; + free = NULL; + total = active = 0; +} +// RAVEN END + +/* +============================================================================== + + Dynamic allocator, simple wrapper for normal allocations which can + be interchanged with idDynamicBlockAlloc. + + No constructor is called for the 'type'. + Allocated blocks are always 16 byte aligned. + +============================================================================== +*/ + +template +class idDynamicAlloc { +public: + idDynamicAlloc( void ); + ~idDynamicAlloc( void ); + + void Init( void ); + void Shutdown( void ); + void SetFixedBlocks( int numBlocks ) {} + void SetLockMemory( bool lock ) {} + void FreeEmptyBaseBlocks( void ) {} + + type * Alloc( const int num ); + type * Resize( type *ptr, const int num ); + void Free( type *ptr ); + const char * CheckMemory( const type *ptr ) const; + + int GetNumBaseBlocks( void ) const { return 0; } + int GetBaseBlockMemory( void ) const { return 0; } + int GetNumUsedBlocks( void ) const { return numUsedBlocks; } + int GetUsedBlockMemory( void ) const { return usedBlockMemory; } + int GetNumFreeBlocks( void ) const { return 0; } + int GetFreeBlockMemory( void ) const { return 0; } + int GetNumEmptyBaseBlocks( void ) const { return 0; } + +private: + int numUsedBlocks; // number of used blocks + int usedBlockMemory; // total memory in used blocks + + int numAllocs; + int numResizes; + int numFrees; + + void Clear( void ); +}; + +template +idDynamicAlloc::idDynamicAlloc( void ) { + Clear(); +} + +template +idDynamicAlloc::~idDynamicAlloc( void ) { + Shutdown(); +} + +template +void idDynamicAlloc::Init( void ) { +} + +template +void idDynamicAlloc::Shutdown( void ) { + Clear(); +} + +template +type *idDynamicAlloc::Alloc( const int num ) { + numAllocs++; + if ( num <= 0 ) { + return NULL; + } + numUsedBlocks++; + usedBlockMemory += num * sizeof( type ); +// RAVEN BEGIN +// jscott: to make it build +// mwhitlock: to make it build on Xenon + return (type *) ( (byte *) Mem_Alloc16( num * sizeof( type ), memoryTag ) ); +// RAVEN BEGIN +} + +#include "math/Simd.h" + +template +type *idDynamicAlloc::Resize( type *ptr, const int num ) { + + numResizes++; + +// RAVEN BEGIN +// jnewquist: provide a real implementation of resize + if ( num <= 0 ) { + Free( ptr ); + return NULL; + } + + type *newptr = Alloc( num ); + + if ( ptr != NULL ) { + const int oldSize = Mem_Size(ptr); + const int newSize = num*sizeof(type); + SIMDProcessor->Memcpy( newptr, ptr, (newSize +void idDynamicAlloc::Free( type *ptr ) { + numFrees++; + if ( ptr == NULL ) { + return; + } + Mem_Free16( ptr ); +} + +template +const char *idDynamicAlloc::CheckMemory( const type *ptr ) const { + return NULL; +} + +template +void idDynamicAlloc::Clear( void ) { + numUsedBlocks = 0; + usedBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; +} + + +/* +============================================================================== + + Fast dynamic block allocator. + + No constructor is called for the 'type'. + Allocated blocks are always 16 byte aligned. + +============================================================================== +*/ + +#include "containers/BTree.h" + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +//#ifdef _DEBUG +//#define DYNAMIC_BLOCK_ALLOC_CHECK +#define DYNAMIC_BLOCK_ALLOC_FASTCHECK +#define DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL +//#endif +// RAVEN END + +template +class idDynamicBlock { +public: + type * GetMemory( void ) const { return (type *)( ( (byte *) this ) + sizeof( idDynamicBlock ) ); } + int GetSize( void ) const { return abs( size ); } + void SetSize( int s, bool isBaseBlock ) { size = isBaseBlock ? -s : s; } + bool IsBaseBlock( void ) const { return ( size < 0 ); } + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + int identifier[3]; + void * allocator; +#endif + + int size; // size in bytes of the block + idDynamicBlock * prev; // previous memory block + idDynamicBlock * next; // next memory block + idBTreeNode,int> *node; // node in the B-Tree with free blocks +}; + +template +class idDynamicBlockAlloc { +public: + idDynamicBlockAlloc( void ); + ~idDynamicBlockAlloc( void ); + + void Init( void ); + void Shutdown( void ); + void SetFixedBlocks( int numBlocks ); + void SetLockMemory( bool lock ); + void FreeEmptyBaseBlocks( void ); + + type * Alloc( const int num ); + type * Resize( type *ptr, const int num ); + void Free( type *ptr ); + const char * CheckMemory( const type *ptr ) const; + + int GetNumBaseBlocks( void ) const { return numBaseBlocks; } + int GetBaseBlockMemory( void ) const { return baseBlockMemory; } + int GetNumUsedBlocks( void ) const { return numUsedBlocks; } + int GetUsedBlockMemory( void ) const { return usedBlockMemory; } + int GetNumFreeBlocks( void ) const { return numFreeBlocks; } + int GetFreeBlockMemory( void ) const { return freeBlockMemory; } + int GetNumEmptyBaseBlocks( void ) const; + +private: + idDynamicBlock * firstBlock; // first block in list in order of increasing address + idDynamicBlock * lastBlock; // last block in list in order of increasing address + idBTree,int,4>freeTree; // B-Tree with free memory blocks + bool allowAllocs; // allow base block allocations + bool lockMemory; // lock memory so it cannot get swapped out + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + int blockId[3]; +#endif + + int numBaseBlocks; // number of base blocks + int baseBlockMemory; // total memory in base blocks + int numUsedBlocks; // number of used blocks + int usedBlockMemory; // total memory in used blocks + int numFreeBlocks; // number of free blocks + int freeBlockMemory; // total memory in free blocks + + int numAllocs; + int numResizes; + int numFrees; + + void Clear( void ); + idDynamicBlock * AllocInternal( const int num ); + idDynamicBlock * ResizeInternal( idDynamicBlock *block, const int num ); + void FreeInternal( idDynamicBlock *block ); + void LinkFreeInternal( idDynamicBlock *block ); + void UnlinkFreeInternal( idDynamicBlock *block ); + void CheckMemory( void ) const; +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc + const char * CheckMemory( const idDynamicBlock *block ) const; +// RAVEN END +}; + +template +idDynamicBlockAlloc::idDynamicBlockAlloc( void ) { + Clear(); +} + +template +idDynamicBlockAlloc::~idDynamicBlockAlloc( void ) { + Shutdown(); +} + +template +void idDynamicBlockAlloc::Init( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,memoryTag); +// RAVEN END + freeTree.Init(); +} + +template +void idDynamicBlockAlloc::Shutdown( void ) { + idDynamicBlock *block; + + for ( block = firstBlock; block != NULL; block = block->next ) { + if ( block->node == NULL ) { + FreeInternal( block ); + } + } + + for ( block = firstBlock; block != NULL; block = firstBlock ) { + firstBlock = block->next; + assert( block->IsBaseBlock() ); + if ( lockMemory ) { + idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); + } + Mem_Free16( block ); + } + + freeTree.Shutdown(); + + Clear(); +} + +template +void idDynamicBlockAlloc::SetFixedBlocks( int numBlocks ) { + int i; + idDynamicBlock *block; + + for ( i = numBaseBlocks; i < numBlocks; i++ ) { +//RAVEN BEGIN +//amccarthy: Added allocation tag + block = ( idDynamicBlock * ) Mem_Alloc16( baseBlockSize, memoryTag ); +//RAVEN END + if ( lockMemory ) { + idLib::sys->LockMemory( block, baseBlockSize ); + } +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + memcpy( block->identifier, blockId, sizeof( block->identifier ) ); + block->allocator = (void*)this; +#endif + block->SetSize( baseBlockSize - (int)sizeof( idDynamicBlock ), true ); + block->next = NULL; + block->prev = lastBlock; + if ( lastBlock ) { + lastBlock->next = block; + } else { + firstBlock = block; + } + lastBlock = block; + block->node = NULL; + + FreeInternal( block ); + + numBaseBlocks++; + baseBlockMemory += baseBlockSize; + } + + allowAllocs = false; +} + +template +void idDynamicBlockAlloc::SetLockMemory( bool lock ) { + lockMemory = lock; +} + +template +void idDynamicBlockAlloc::FreeEmptyBaseBlocks( void ) { + idDynamicBlock *block, *next; + + for ( block = firstBlock; block != NULL; block = next ) { + next = block->next; + + if ( block->IsBaseBlock() && block->node != NULL && ( next == NULL || next->IsBaseBlock() ) ) { + UnlinkFreeInternal( block ); + if ( block->prev ) { + block->prev->next = block->next; + } else { + firstBlock = block->next; + } + if ( block->next ) { + block->next->prev = block->prev; + } else { + lastBlock = block->prev; + } + if ( lockMemory ) { + idLib::sys->UnlockMemory( block, block->GetSize() + (int)sizeof( idDynamicBlock ) ); + } + numBaseBlocks--; + baseBlockMemory -= block->GetSize() + (int)sizeof( idDynamicBlock ); + Mem_Free16( block ); + } + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif +} + +template +int idDynamicBlockAlloc::GetNumEmptyBaseBlocks( void ) const { + int numEmptyBaseBlocks; + idDynamicBlock *block; + + numEmptyBaseBlocks = 0; + for ( block = firstBlock; block != NULL; block = block->next ) { + if ( block->IsBaseBlock() && block->node != NULL && ( block->next == NULL || block->next->IsBaseBlock() ) ) { + numEmptyBaseBlocks++; + } + } + return numEmptyBaseBlocks; +} + +template +type *idDynamicBlockAlloc::Alloc( const int num ) { + idDynamicBlock *block; + + numAllocs++; + + if ( num <= 0 ) { + return NULL; + } + + block = AllocInternal( num ); + if ( block == NULL ) { + return NULL; + } + block = ResizeInternal( block, num ); + if ( block == NULL ) { + return NULL; + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif + + numUsedBlocks++; + usedBlockMemory += block->GetSize(); + + return block->GetMemory(); +} + +template +type *idDynamicBlockAlloc::Resize( type *ptr, const int num ) { + + numResizes++; + + if ( ptr == NULL ) { + return Alloc( num ); + } + + if ( num <= 0 ) { + Free( ptr ); + return NULL; + } + + idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + + usedBlockMemory -= block->GetSize(); + + block = ResizeInternal( block, num ); + if ( block == NULL ) { + return NULL; + } + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif + + usedBlockMemory += block->GetSize(); + + return block->GetMemory(); +} + +template +void idDynamicBlockAlloc::Free( type *ptr ) { + + numFrees++; + + if ( ptr == NULL ) { + return; + } + + idDynamicBlock *block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + + numUsedBlocks--; + usedBlockMemory -= block->GetSize(); + + FreeInternal( block ); + +#ifdef DYNAMIC_BLOCK_ALLOC_CHECK + CheckMemory(); +#endif +} + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +template +const char *idDynamicBlockAlloc::CheckMemory( const idDynamicBlock *block ) const { + if ( block->node != NULL ) { + return "memory has been freed"; + } + +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + if ( block->identifier[0] != 0x11111111 || block->identifier[1] != 0x22222222 || block->identifier[2] != 0x33333333 ) { + return "memory has invalid identifier"; + } +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifndef RV_UNIFIED_ALLOCATOR + if ( block->allocator != (void*)this ) { + return "memory was allocated with different allocator"; + } +#endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#endif + + /* base blocks can be larger than baseBlockSize which can cause this code to fail + idDynamicBlock *base; + for ( base = firstBlock; base != NULL; base = base->next ) { + if ( base->IsBaseBlock() ) { + if ( ((int)block) >= ((int)base) && ((int)block) < ((int)base) + baseBlockSize ) { + break; + } + } + } + if ( base == NULL ) { + return "no base block found for memory"; + } + */ + + return NULL; +} + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +template +const char *idDynamicBlockAlloc::CheckMemory( const type *ptr ) const { + idDynamicBlock *block; + + if ( ptr == NULL ) { + return NULL; + } + + block = ( idDynamicBlock * ) ( ( (byte *) ptr ) - (int)sizeof( idDynamicBlock ) ); + return CheckMemory( block ); +} +// RAVEN END + +template +void idDynamicBlockAlloc::Clear( void ) { + firstBlock = lastBlock = NULL; + allowAllocs = true; + lockMemory = false; + numBaseBlocks = 0; + baseBlockMemory = 0; + numUsedBlocks = 0; + usedBlockMemory = 0; + numFreeBlocks = 0; + freeBlockMemory = 0; + numAllocs = 0; + numResizes = 0; + numFrees = 0; + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + blockId[0] = 0x11111111; + blockId[1] = 0x22222222; + blockId[2] = 0x33333333; +#endif +} + +template +idDynamicBlock *idDynamicBlockAlloc::AllocInternal( const int num ) { + idDynamicBlock *block; + int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; + + block = freeTree.FindSmallestLargerEqual( alignedBytes ); + if ( block != NULL ) { + UnlinkFreeInternal( block ); + } else if ( allowAllocs ) { + int allocSize = Max( baseBlockSize, alignedBytes + (int)sizeof( idDynamicBlock ) ); +//RAVEN BEGIN +//amccarthy: Added allocation tag + block = ( idDynamicBlock * ) Mem_Alloc16( allocSize, memoryTag ); +//RAVEN END + if ( lockMemory ) { + idLib::sys->LockMemory( block, baseBlockSize ); + } +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + memcpy( block->identifier, blockId, sizeof( block->identifier ) ); + block->allocator = (void*)this; +#endif + block->SetSize( allocSize - (int)sizeof( idDynamicBlock ), true ); + block->next = NULL; + block->prev = lastBlock; + if ( lastBlock ) { + lastBlock->next = block; + } else { + firstBlock = block; + } + lastBlock = block; + block->node = NULL; + + numBaseBlocks++; + baseBlockMemory += allocSize; + } + + return block; +} + +template +idDynamicBlock *idDynamicBlockAlloc::ResizeInternal( idDynamicBlock *block, const int num ) { + int alignedBytes = ( num * sizeof( type ) + 15 ) & ~15; + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL) + const char *chkstr = CheckMemory( block ); + if ( chkstr ) { + throw idException( chkstr ); + } +#endif +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333); // && block->allocator == (void*)this ); +#else + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 && block->allocator == (void*)this ); +#endif +// RAVEN END +#endif + + // if the new size is larger + if ( alignedBytes > block->GetSize() ) { + + idDynamicBlock *nextBlock = block->next; + + // try to annexate the next block if it's free + if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL && + block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize() >= alignedBytes ) { + + UnlinkFreeInternal( nextBlock ); + block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); + block->next = nextBlock->next; + if ( nextBlock->next ) { + nextBlock->next->prev = block; + } else { + lastBlock = block; + } + } else { + // allocate a new block and copy + idDynamicBlock *oldBlock = block; + block = AllocInternal( num ); + if ( block == NULL ) { + return NULL; + } + memcpy( block->GetMemory(), oldBlock->GetMemory(), oldBlock->GetSize() ); + FreeInternal( oldBlock ); + } + } + + // if the unused space at the end of this block is large enough to hold a block with at least one element + if ( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ) < Max( minBlockSize, (int)sizeof( type ) ) ) { + return block; + } + + idDynamicBlock *newBlock; + + newBlock = ( idDynamicBlock * ) ( ( (byte *) block ) + (int)sizeof( idDynamicBlock ) + alignedBytes ); +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +// RAVEN END + memcpy( newBlock->identifier, blockId, sizeof( newBlock->identifier ) ); + newBlock->allocator = (void*)this; +#endif + newBlock->SetSize( block->GetSize() - alignedBytes - (int)sizeof( idDynamicBlock ), false ); + newBlock->next = block->next; + newBlock->prev = block; + if ( newBlock->next ) { + newBlock->next->prev = newBlock; + } else { + lastBlock = newBlock; + } + newBlock->node = NULL; + block->next = newBlock; + block->SetSize( alignedBytes, block->IsBaseBlock() ); + + FreeInternal( newBlock ); + + return block; +} + +template +void idDynamicBlockAlloc::FreeInternal( idDynamicBlock *block ) { + + assert( block->node == NULL ); + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL) + const char *chkstr = CheckMemory( block ); + if ( chkstr ) { + throw idException( chkstr ); + } +#endif +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 );//&& block->allocator == (void*)this ); +#else + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 && block->allocator == (void*)this ); +#endif // RV_UNIFIED_ALLOCATOR +// RAVEN END +#endif + + // try to merge with a next free block + idDynamicBlock *nextBlock = block->next; + if ( nextBlock && !nextBlock->IsBaseBlock() && nextBlock->node != NULL ) { + UnlinkFreeInternal( nextBlock ); + block->SetSize( block->GetSize() + (int)sizeof( idDynamicBlock ) + nextBlock->GetSize(), block->IsBaseBlock() ); + block->next = nextBlock->next; + if ( nextBlock->next ) { + nextBlock->next->prev = block; + } else { + lastBlock = block; + } + } + + // try to merge with a previous free block + idDynamicBlock *prevBlock = block->prev; + if ( prevBlock && !block->IsBaseBlock() && prevBlock->node != NULL ) { + UnlinkFreeInternal( prevBlock ); + prevBlock->SetSize( prevBlock->GetSize() + (int)sizeof( idDynamicBlock ) + block->GetSize(), prevBlock->IsBaseBlock() ); + prevBlock->next = block->next; + if ( block->next ) { + block->next->prev = prevBlock; + } else { + lastBlock = prevBlock; + } + LinkFreeInternal( prevBlock ); + } else { + LinkFreeInternal( block ); + } +} + +template +ID_INLINE void idDynamicBlockAlloc::LinkFreeInternal( idDynamicBlock *block ) { + block->node = freeTree.Add( block, block->GetSize() ); + numFreeBlocks++; + freeBlockMemory += block->GetSize(); +} + +template +ID_INLINE void idDynamicBlockAlloc::UnlinkFreeInternal( idDynamicBlock *block ) { + freeTree.Remove( block->node ); + block->node = NULL; + numFreeBlocks--; + freeBlockMemory -= block->GetSize(); +} + +template +void idDynamicBlockAlloc::CheckMemory( void ) const { + idDynamicBlock *block; + + for ( block = firstBlock; block != NULL; block = block->next ) { + +// RAVEN BEGIN +// jnewquist: Fast sanity checking of idDynamicBlockAlloc +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK) || defined(DYNAMIC_BLOCK_ALLOC_FASTCHECK) +#if defined(DYNAMIC_BLOCK_ALLOC_CHECK_IS_FATAL) + const char *chkstr = CheckMemory( block ); + if ( chkstr ) { + throw idException( chkstr ); + } +#endif +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333); // && block->allocator == (void*)this ); +#else + assert( block->identifier[0] == 0x11111111 && block->identifier[1] == 0x22222222 && block->identifier[2] == 0x33333333 && block->allocator == (void*)this ); +#endif +// RAVEN END +#endif + + // make sure the block is properly linked + if ( block->prev == NULL ) { + assert( firstBlock == block ); + } else { + assert( block->prev->next == block ); + } + if ( block->next == NULL ) { + assert( lastBlock == block ); + } else { + assert( block->next->prev == block ); + } + } +} + +#endif /* !__HEAP_H__ */ diff --git a/src/idlib/LangDict.cpp b/src/idlib/LangDict.cpp new file mode 100644 index 0000000..c584bc2 --- /dev/null +++ b/src/idlib/LangDict.cpp @@ -0,0 +1,313 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + + +/* +============ +idLangDict::idLangDict +============ +*/ +idLangDict::idLangDict( void ) { + args.SetGranularity( 256 ); + hash.SetGranularity( 256 ); + hash.Clear( 4096, 8192 ); + baseID = 0; +} + +/* +============ +idLangDict::~idLangDict +============ +*/ +idLangDict::~idLangDict( void ) { + Clear(); +} + +/* +============ +idLangDict::Clear +============ +*/ +void idLangDict::Clear( void ) { + args.Clear(); + hash.Clear(); +} + +/* +============ +idLangDict::Load +============ +*/ +bool idLangDict::Load( const char *fileName, bool clear ) { + if ( clear ) { + Clear(); + } + + const char *buffer = NULL; + idLexer src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + int len = idLib::fileSystem->ReadFile( fileName, (void**)&buffer ); + if ( len <= 0 ) { + // let whoever called us deal with the failure (so sys_lang can be reset) + return false; + } + src.LoadMemory( buffer, strlen( buffer ), fileName ); + if ( !src.IsLoaded() ) { + return false; + } + + idToken tok, tok2; + src.ExpectTokenString( "{" ); + while ( src.ReadToken( &tok ) ) { + if ( tok == "}" ) { + break; + } + + if ( src.ReadToken( &tok2 ) ) { + if ( tok2 == "}" ) { + break; + } + idLangKeyValue kv; + kv.key = tok; + kv.value = tok2; +// RAVEN BEGIN + if( kv.key.CmpPrefix( STRTABLE_ID ) ) { + common->Warning( "Invalid token id \'%s\' in \'%s\' line %d", kv.key.c_str(), fileName, src.GetLineNum() ); + } else { + hash.Add( GetHashKey( kv.key ), args.Append( kv ) ); + } +// RAVEN END + } + } + idLib::common->Printf( "%i strings read from %s\n", args.Num(), fileName ); + idLib::fileSystem->FreeFile( (void*)buffer ); + + return true; +} + +/* +============ +idLangDict::Save +============ +*/ +void idLangDict::Save( const char *fileName ) { + idFile *outFile = idLib::fileSystem->OpenFileWrite( fileName ); +// RAVEN BEGIN + if( !outFile ) { + common->Printf( "Could not open file \'%s\'for writing\n", fileName ); + return; + } +// RAVEN END + outFile->WriteFloatString( "// string table" NEWLINE "// english" NEWLINE "//" NEWLINE NEWLINE "{" NEWLINE ); + for ( int j = 0; j < args.Num(); j++ ) { + outFile->WriteFloatString( "\t\"%s\"\t\"", args[j].key.c_str() ); + int l = args[j].value.Length(); + char slash = '\\'; + char tab = 't'; + char nl = 'n'; + for ( int k = 0; k < l; k++ ) { + char ch = args[j].value[k]; + if ( ch == '\t' ) { + outFile->Write( &slash, 1 ); + outFile->Write( &tab, 1 ); + } else if ( ch == '\n' || ch == '\r' ) { + outFile->Write( &slash, 1 ); + outFile->Write( &nl, 1 ); + } else { + outFile->Write( &ch, 1 ); + } + } + outFile->WriteFloatString( "\"" NEWLINE ); + } + outFile->WriteFloatString( NEWLINE "}" NEWLINE ); + idLib::fileSystem->CloseFile( outFile ); +} + +/* +============ +idLangDict::GetString +============ +*/ +const char *idLangDict::GetString( const char *str ) const { + + if ( str == NULL || str[0] == '\0' ) { + return ""; + } + + if ( idStr::Cmpn( str, STRTABLE_ID, STRTABLE_ID_LENGTH ) != 0 ) { + return str; + } + + int hashKey = GetHashKey( str ); + for ( int i = hash.First( hashKey ); i != -1; i = hash.Next( i ) ) { + if ( args[i].key.Cmp( str ) == 0 ) { + return args[i].value; + } + } + + idLib::common->Warning( "Unknown string id %s", str ); + return str; +} + +/* +============ +idLangDict::AddString +============ +*/ +const char *idLangDict::AddString( const char *str ) { + + if ( ExcludeString( str ) ) { + return str; + } + + int c = args.Num(); + for ( int j = 0; j < c; j++ ) { + if ( idStr::Cmp( args[j].value, str ) == 0 ){ + return args[j].key; + } + } + + int id = GetNextId(); + idLangKeyValue kv; + kv.key = va( "#str_%06i", id ); + kv.value = str; + c = args.Append( kv ); + assert( kv.key.CmpPrefix( STRTABLE_ID ) == 0 ); + hash.Add( GetHashKey( kv.key ), c ); + return args[c].key; +} + +/* +============ +idLangDict::GetNumKeyVals +============ +*/ +int idLangDict::GetNumKeyVals( void ) const { + return args.Num(); +} + +/* +============ +idLangDict::GetKeyVal +============ +*/ +const idLangKeyValue * idLangDict::GetKeyVal( int i ) const { + return &args[i]; +} + +/* +============ +idLangDict::AddKeyVal +============ +*/ +void idLangDict::AddKeyVal( const char *key, const char *val ) { + idLangKeyValue kv; + kv.key = key; + kv.value = val; + assert( kv.key.CmpPrefix( STRTABLE_ID ) == 0 ); + hash.Add( GetHashKey( kv.key ), args.Append( kv ) ); +} + +/* +============ +idLangDict::ExcludeString +============ +*/ +bool idLangDict::ExcludeString( const char *str ) const { + if ( str == NULL ) { + return true; + } + + int c = strlen( str ); + if ( c <= 1 ) { + return true; + } + + if ( idStr::Cmpn( str, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { + return true; + } + + if ( idStr::Icmpn( str, "gui::", strlen( "gui::" ) ) == 0 ) { + return true; + } + + if ( str[0] == '$' ) { + return true; + } + + int i; + for ( i = 0; i < c; i++ ) { + if ( isalpha( str[i] ) ) { + break; + } + } + if ( i == c ) { + return true; + } + + return false; +} + +/* +============ +idLangDict::GetNextId +============ +*/ +int idLangDict::GetNextId( void ) const { + int c = args.Num(); + //Let and external user supply the base id for this dictionary + int id = baseID; + + if ( c == 0 ) { + return id; + } + + idStr work; + for ( int j = 0; j < c; j++ ) { + work = args[j].key; + work.StripLeading( STRTABLE_ID ); + int test = atoi( work ); + if ( test > id ) { + id = test; + } + } + return id + 1; +} + +/* +============ +idLangDict::GetHashKey +============ +*/ +int idLangDict::GetHashKey( const char *str ) const { + int hashKey = 0; + for ( str += STRTABLE_ID_LENGTH; str[0] != '\0'; str++ ) { + assert( str[0] >= '0' && str[0] <= '9' ); + hashKey = hashKey * 10 + str[0] - '0'; + } + return hashKey; +} diff --git a/src/idlib/LangDict.h b/src/idlib/LangDict.h new file mode 100644 index 0000000..15f9d4f --- /dev/null +++ b/src/idlib/LangDict.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LANGDICT_H__ +#define __LANGDICT_H__ + +/* +=============================================================================== + + Simple dictionary specifically for the localized string tables. + +=============================================================================== +*/ + +class idLangKeyValue { +public: + idStr key; + idStr value; +}; + +class idLangDict { +public: + idLangDict( void ); + ~idLangDict( void ); + + void Clear( void ); + bool Load( const char *fileName, bool clear = true ); + void Save( const char *fileName ); + + const char * AddString( const char *str ); + const char * GetString( const char *str ) const; + + // adds the value and key as passed (doesn't generate a "#str_xxxxxx" key or ensure the key/value pair is unique) + void AddKeyVal( const char *key, const char *val ); + + int GetNumKeyVals( void ) const; + const idLangKeyValue * GetKeyVal( int i ) const; + + void SetBaseID(int id) { baseID = id; }; + +private: + idList args; + idHashIndex hash; + + bool ExcludeString( const char *str ) const; + int GetNextId( void ) const; + int GetHashKey( const char *str ) const; + + int baseID; +}; + +#endif /* !__LANGDICT_H__ */ diff --git a/src/idlib/Lexer.cpp b/src/idlib/Lexer.cpp new file mode 100644 index 0000000..b775ab5 --- /dev/null +++ b/src/idlib/Lexer.cpp @@ -0,0 +1,3967 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#ifdef LEXER_READ_AHEAD + +namespace { + + #define IO_BUFFER_CHUNK_SIZE (1024 * 64) + + enum { + IOJobPending = 0, + IOJobQuit, + IOJobComplete, + IOJobCount + }; + static HANDLE mIOJobs[IOJobCount]; + + static DWORD mThreadId = 0; + static HANDLE mThreadHandle = 0; + + + struct IOJobData { + idFile *mFile; + int mAmtToRead; + byte mBuffer[IO_BUFFER_CHUNK_SIZE]; + }; + + IOJobData gIOJobData; + + void SetPendingJob(idFile *f, int size) { + gIOJobData.mFile = f; + gIOJobData.mAmtToRead = size; + + if ( mThreadHandle ) { + SetEvent( mIOJobs[IOJobPending] ); + } + } + + IOJobData *GetPendingJob() { + return &gIOJobData; + } + + void GetPendingJobResults(byte *dest, int &size) { + if ( mThreadHandle ) { + if ( WAIT_OBJECT_0 == WaitForSingleObject( mIOJobs[IOJobComplete], INFINITE ) ) { + //ResetEvent( mIOJobs[IOJobComplete] ); + size = gIOJobData.mAmtToRead; + memcpy(dest, gIOJobData.mBuffer, size ); + } else { + assert( 0 ); + } + } else { + gIOJobData.mFile->Read( dest, gIOJobData.mAmtToRead ); + size = gIOJobData.mAmtToRead; + } + } + + static LONG IOThread( void * ) { + while ( 1 ) { + DWORD res = WaitForMultipleObjects( 2, mIOJobs, FALSE, INFINITE ); + if ( res == WAIT_OBJECT_0 ) { + //ResetEvent( mIOJobs[IOJobPending] ); + IOJobData *j = GetPendingJob(); + j->mFile->Read( j->mBuffer, j->mAmtToRead ); + SetEvent( mIOJobs[IOJobComplete] ); + } else if ( res == (WAIT_OBJECT_0 + 1) ) { + ExitThread(0); + } else { + assert( 0 ); + } + } + ExitThread(0); + } + + void InitIO() { + for ( int i = 0; i < IOJobCount; ++i ) { + //mIOJobs[i] = CreateEvent( 0, TRUE, FALSE, 0 ); + mIOJobs[i] = CreateEvent( 0, FALSE, FALSE, 0 ); + } + mThreadHandle = CreateThread( NULL, 1024 * 512, ( LPTHREAD_START_ROUTINE )IOThread, 0, CREATE_SUSPENDED, &mThreadId ); + ResumeThread( mThreadHandle ); + } + + void ShutdownIO() { + + SetEvent( mIOJobs[IOJobQuit] ); + + DWORD ecode = 0; + while ( !GetExitCodeThread( mThreadHandle, &ecode ) ) { + Sleep( 1 ); + } + CloseHandle( mThreadHandle ); + + for ( int i = 0; i < IOJobCount; ++i ) { + CloseHandle( mIOJobs[i] ); + } + + mThreadHandle = 0; + } + +} + +namespace ChunkBuffer { + + idFile *mFile; + int mFileSize; + int mBytesRead; + + int mFileBytesRead; + + byte mChunkMem[IO_BUFFER_CHUNK_SIZE]; + int mCurrentChunkBytes; + int mCurrentChunkBytesRead = 0; + + /* + ================ + ChunkBuffer::Length + ================ + */ + int Length() { + return mFileSize; + } + + /* + ================ + ChunkBuffer::BytesRead + ================ + */ + int BytesRead() { + return mBytesRead; + } + + /* + ================ + ChunkBuffer::Begin + ================ + */ + void Begin( idFile *f ) { + mFile = f; + mFileSize = f->Length(); + mBytesRead = 0; + + int initialRead = (mFileSize < IO_BUFFER_CHUNK_SIZE) ? mFileSize : IO_BUFFER_CHUNK_SIZE; + mFile->Read( mChunkMem, initialRead ); + mFileBytesRead = initialRead; + mCurrentChunkBytesRead = 0; + mCurrentChunkBytes = initialRead; + + int bytesRemaining = mFileSize - mFileBytesRead; + if ( bytesRemaining > 0 ) { + SetPendingJob( mFile, (bytesRemaining > IO_BUFFER_CHUNK_SIZE) ? IO_BUFFER_CHUNK_SIZE : bytesRemaining ); + } + } + + /* + ================ + ChunkBuffer::End + ================ + */ + void End() { + } + + /* + ================ + ChunkBuffer::Advance + ================ + */ + int Advance( byte *chunkMem ) { + int readAmt = 0; + GetPendingJobResults( chunkMem, readAmt ); + mFileBytesRead+=readAmt; + + int bytesRemaining = mFileSize - mFileBytesRead; + if ( bytesRemaining > 0 ) { + SetPendingJob( mFile, (bytesRemaining > IO_BUFFER_CHUNK_SIZE) ? IO_BUFFER_CHUNK_SIZE : bytesRemaining ); + } + + return readAmt; + } + + /* + ================ + ChunkBuffer::Read + ================ + */ + int Read( byte *dest, int size ) { + int bytesRem = mFileSize - mBytesRead; + + if ( size > bytesRem ) { + size = bytesRem; + } + + if ( !size ) { + return 0; + } + + byte *writePos = dest; + int cnt = size; + + while ( cnt ) { + if ( mCurrentChunkBytesRead >= mCurrentChunkBytes ) { + mCurrentChunkBytes = Advance( mChunkMem ); + mCurrentChunkBytesRead = 0; + } + + int chunkBytesRemaining = mCurrentChunkBytes - mCurrentChunkBytesRead; + int toRead = (cnt > chunkBytesRemaining) ? chunkBytesRemaining : cnt; + + byte *readPos = &mChunkMem[mCurrentChunkBytesRead]; + + memcpy( writePos, readPos, toRead ); + + cnt-=toRead; + mCurrentChunkBytesRead+=toRead; + writePos+=toRead; + } + + mBytesRead+=size; + return size; + } +} + +/* +================ +LexerIOWrapper::LexerIOWrapper +================ +*/ +LexerIOWrapper::LexerIOWrapper() { + file = 0; + offset = 0; + size = 0; + memory = 0; +} + +/* +================ +LexerIOWrapper::InitFromMemory +================ +*/ +void LexerIOWrapper::InitFromMemory(char const * const ptr, int length) { + file = 0; + offset = 0; + size = length; + memory = (char *)ptr; +} + +/* +================ +LexerIOWrapper::OpenFile +================ +*/ +bool LexerIOWrapper::OpenFile(char const *filename, bool OSPath) { + if ( OSPath ) { + file = idLib::fileSystem->OpenExplicitFileRead( filename ); + } else { + file = idLib::fileSystem->OpenFileRead( filename ); + } + + if ( !file ) { + return false; + } + + ChunkBuffer::Begin( file ); + + return true; +} + +/* +================ +LexerIOWrapper::Length +================ +*/ +int LexerIOWrapper::Length() { + if ( file ) { + return ChunkBuffer::Length(); + } + return size; +} + +/* +================ +LexerIOWrapper::Tell +================ +*/ +int LexerIOWrapper::Tell() { + if ( file ) { + return ChunkBuffer::BytesRead(); + } + return offset; +} + +/* +================ +LexerIOWrapper::Seek +================ +*/ +void LexerIOWrapper::Seek( int loc, int mode ) { + if ( file ) { + if ( loc != 0 || mode != FS_SEEK_SET ) { + common->Error( "lol\n" ); + } + file->Seek( 0, FS_SEEK_SET ); + ChunkBuffer::Begin( file ); + return; + } + if ( mode == FS_SEEK_SET ) { + offset = loc; + } else { + common->Error( "lol" ); + } +} + +/* +================ +LexerIOWrapper::Read +================ +*/ +void LexerIOWrapper::Read( void *dest, int s ) { + if ( file ) { + ChunkBuffer::Read( (byte*)dest, s ); + return; + } + char *d = (char *)dest; + char *dEnd = d + (((s + offset) > size) ? (size - offset) : (s)); + while ( d < dEnd ) { + *d = memory[offset]; + ++d; + ++offset; + } +} + +/* +================ +LexerIOWrapper::Close +================ +*/ +void LexerIOWrapper::Close() { + if ( file ) { + ChunkBuffer::End(); + idLib::fileSystem->CloseFile(file); + return; + } +} + +/* +================ +LexerIOWrapper::IsLoaded +================ +*/ +int LexerIOWrapper::IsLoaded() { + if ( file || memory ) { + return 1; + } + return 0; +} + +/* +================ +Lexer::BeginLevelLoad +================ +*/ +void Lexer::BeginLevelLoad() { + InitIO(); +} + +/* +================ +Lexer::EndLevelLoad +================ +*/ +void Lexer::EndLevelLoad() { + ShutdownIO(); +} + +/* +================ +ReadValue +================ +*/ +template inline type ReadValue(LexerIOWrapper *in) +{ + type ret; + + in->Read(&ret, sizeof(type)); + return ret; +} + +/* +================ +ReadValue +specialization read for idStr's +================ +*/ +template <> inline idStr ReadValue(LexerIOWrapper *in) +{ + char c; + idStr str; + + in->Read(&c, 1); + while(c != '\0') + { + str.Append(c); + in->Read(&c, 1); + } + + str.Append(c); + + return str; +} + +#endif + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = { + //binary operators + {">>=",P_RSHIFT_ASSIGN}, + {"<<=",P_LSHIFT_ASSIGN}, + // + {"...",P_PARMS}, + //define merge operator + {"##",P_PRECOMPMERGE}, // pre-compiler + //logic operators + {"&&",P_LOGIC_AND}, // pre-compiler + {"||",P_LOGIC_OR}, // pre-compiler + {">=",P_LOGIC_GEQ}, // pre-compiler + {"<=",P_LOGIC_LEQ}, // pre-compiler + {"==",P_LOGIC_EQ}, // pre-compiler + {"!=",P_LOGIC_UNEQ}, // pre-compiler + //arithmatic operators + {"*=",P_MUL_ASSIGN}, + {"/=",P_DIV_ASSIGN}, + {"%=",P_MOD_ASSIGN}, + {"+=",P_ADD_ASSIGN}, + {"-=",P_SUB_ASSIGN}, + {"++",P_INC}, + {"--",P_DEC}, + //binary operators + {"&=",P_BIN_AND_ASSIGN}, + {"|=",P_BIN_OR_ASSIGN}, + {"^=",P_BIN_XOR_ASSIGN}, + {">>",P_RSHIFT}, // pre-compiler + {"<<",P_LSHIFT}, // pre-compiler + //reference operators + {"->",P_POINTERREF}, + //C++ + {"::",P_CPP1}, + {".*",P_CPP2}, + //arithmatic operators + {"*",P_MUL}, // pre-compiler + {"/",P_DIV}, // pre-compiler + {"%",P_MOD}, // pre-compiler + {"+",P_ADD}, // pre-compiler + {"-",P_SUB}, // pre-compiler + {"=",P_ASSIGN}, + //binary operators + {"&",P_BIN_AND}, // pre-compiler + {"|",P_BIN_OR}, // pre-compiler + {"^",P_BIN_XOR}, // pre-compiler + {"~",P_BIN_NOT}, // pre-compiler + //logic operators + {"!",P_LOGIC_NOT}, // pre-compiler + {">",P_LOGIC_GREATER}, // pre-compiler + {"<",P_LOGIC_LESS}, // pre-compiler + //reference operator + {".",P_REF}, + //seperators + {",",P_COMMA}, // pre-compiler + {";",P_SEMICOLON}, + //label indication + {":",P_COLON}, // pre-compiler + //if statement + {"?",P_QUESTIONMARK}, // pre-compiler + //embracements + {"(",P_PARENTHESESOPEN}, // pre-compiler + {")",P_PARENTHESESCLOSE}, // pre-compiler + {"{",P_BRACEOPEN}, // pre-compiler + {"}",P_BRACECLOSE}, // pre-compiler + {"[",P_SQBRACKETOPEN}, + {"]",P_SQBRACKETCLOSE}, + // + {"\\",P_BACKSLASH}, + //precompiler operator + {"#",P_PRECOMP}, // pre-compiler + {"$",P_DOLLAR}, +// RAVEN BEGIN + {"¡",P_INVERTED_PLING}, + {"¿",P_INVERTED_QUERY}, +// RAVEN END + {NULL, 0} +}; + +int default_punctuationtable[256]; +int default_nextpunctuation[sizeof(default_punctuations) / sizeof(punctuation_t)]; +int default_setup; + +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +char Lexer::baseFolder[ 256 ]; + +// Added this to allow easy changing of the suffix that signifies a binary file +idStr const Lexer::sCompiledFileSuffix("c"); +// RAVEN END + +/* +================ +idLexer::CreatePunctuationTable +================ +*/ +void idLexer::CreatePunctuationTable( const punctuation_t *punctuations ) { + int i, n, lastp; + const punctuation_t *p, *newp; + + //get memory for the table + if ( punctuations == default_punctuations ) { + idLexer::punctuationtable = default_punctuationtable; + idLexer::nextpunctuation = default_nextpunctuation; + if ( default_setup ) { + return; + } + default_setup = true; + i = sizeof(default_punctuations) / sizeof(punctuation_t); + } + else { + if ( !idLexer::punctuationtable || idLexer::punctuationtable == default_punctuationtable ) { +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idLexer::punctuationtable = (int *) Mem_Alloc(256 * sizeof(int),MA_LEXER); +//RAVEN END + } + if ( idLexer::nextpunctuation && idLexer::nextpunctuation != default_nextpunctuation ) { + Mem_Free( idLexer::nextpunctuation ); + } + for (i = 0; punctuations[i].p; i++) { + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idLexer::nextpunctuation = (int *) Mem_Alloc(i * sizeof(int),MA_LEXER); +//RAVEN END + } + memset(idLexer::punctuationtable, 0xFF, 256 * sizeof(int)); + memset(idLexer::nextpunctuation, 0xFF, i * sizeof(int)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) { + newp = &punctuations[i]; + lastp = -1; + //sort the punctuations in this table entry on length (longer punctuations first) + for (n = idLexer::punctuationtable[(unsigned int) newp->p[0]]; n >= 0; n = idLexer::nextpunctuation[n] ) { + p = &punctuations[n]; + if (strlen(p->p) < strlen(newp->p)) { + idLexer::nextpunctuation[i] = n; + if (lastp >= 0) { + idLexer::nextpunctuation[lastp] = i; + } + else { + idLexer::punctuationtable[(unsigned int) newp->p[0]] = i; + } + break; + } + lastp = n; + } + if (n < 0) { + idLexer::nextpunctuation[i] = -1; + if (lastp >= 0) { + idLexer::nextpunctuation[lastp] = i; + } + else { + idLexer::punctuationtable[(unsigned int) newp->p[0]] = i; + } + } + } +} + +/* +================ +idLexer::GetPunctuationFromId +================ +*/ +const char *idLexer::GetPunctuationFromId( int id ) { + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + if ( idLexer::punctuations[i].n == id ) { + return idLexer::punctuations[i].p; + } + } + return "unkown punctuation"; +} + +/* +================ +idLexer::GetPunctuationId +================ +*/ +int idLexer::GetPunctuationId( const char *p ) { + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + if ( !idStr::Cmp(idLexer::punctuations[i].p, p) ) { + return idLexer::punctuations[i].n; + } + } + return 0; +} + +/* +================ +idLexer::Error +================ +*/ + +void idLexer::Error( const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + hadError = true; + + if ( idLexer::flags & LEXFL_NOERRORS ) { + return; + } + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + + if ( idLexer::flags & LEXFL_NOFATALERRORS ) { + idLib::common->Warning( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); + } else { + idLib::common->Error( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); + } +} + +/* +================ +idLexer::Warning +================ +*/ +void idLexer::Warning( const char *str, ... ) { + char text[MAX_STRING_CHARS]; + va_list ap; + + if ( idLexer::flags & LEXFL_NOWARNINGS ) { + return; + } + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); + idLib::common->Warning( "file %s, line %d: %s", idLexer::filename.c_str(), idLexer::line, text ); +} + +/* +================ +idLexer::SetPunctuations +================ +*/ +void idLexer::SetPunctuations( const punctuation_t *p ) { +#ifdef PUNCTABLE + if (p) { + idLexer::CreatePunctuationTable( p ); + } + else { + idLexer::CreatePunctuationTable( default_punctuations ); + } +#endif //PUNCTABLE + if (p) { + idLexer::punctuations = p; + } + else { + idLexer::punctuations = default_punctuations; + } +} + +/* +================ +idLexer::ReadWhiteSpace + +Reads spaces, tabs, C-like comments etc. +When a newline character is found the scripts line counter is increased. +================ +*/ +int idLexer::ReadWhiteSpace( void ) { + while(1) { + // skip white space +// RAVEN BEGIN + while(( byte )*idLexer::script_p <= ' ') { +// RAVEN END + if (!*idLexer::script_p) { + return 0; + } + if (*idLexer::script_p == '\n') { + idLexer::line++; + } + idLexer::script_p++; + } + // skip comments + if (*idLexer::script_p == '/') { + // comments // + if (*(idLexer::script_p+1) == '/') { + idLexer::script_p++; + do { + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + } + while( *idLexer::script_p != '\n' ); + idLexer::line++; + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + continue; + } + // comments /* */ + else if (*(idLexer::script_p+1) == '*') { + idLexer::script_p++; + while( 1 ) { + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + if ( *idLexer::script_p == '\n' ) { + idLexer::line++; + } + else if ( *idLexer::script_p == '/' ) { + if ( *(idLexer::script_p-1) == '*' ) { + break; + } + if ( *(idLexer::script_p+1) == '*' ) { + idLexer::Warning( "nested comment" ); + } + } + } + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + idLexer::script_p++; + if ( !*idLexer::script_p ) { + return 0; + } + continue; + } + } + break; + } + return 1; +} + +/* +================ +idLexer::ReadEscapeCharacter +================ +*/ +int idLexer::ReadEscapeCharacter( char *ch ) { + int c, val, i; + + // step over the leading '\\' + idLexer::script_p++; + // determine the escape character + switch(*idLexer::script_p) { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + idLexer::script_p++; + for (i = 0, val = 0; ; i++, idLexer::script_p++) { + c = *idLexer::script_p; + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'Z') + c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + c = c - 'a' + 10; + else + break; + val = (val << 4) + c; + } + idLexer::script_p--; + if (val > 0xFF) { + idLexer::Warning( "too large value in escape character" ); + val = 0xFF; + } + c = val; + break; + } + default: //NOTE: decimal ASCII code, NOT octal + { + if (*idLexer::script_p < '0' || *idLexer::script_p > '9') { + idLexer::Error("unknown escape char"); + } + for (i = 0, val = 0; ; i++, idLexer::script_p++) { + c = *idLexer::script_p; + if (c >= '0' && c <= '9') + c = c - '0'; + else + break; + val = val * 10 + c; + } + idLexer::script_p--; + if (val > 0xFF) { + idLexer::Warning( "too large value in escape character" ); + val = 0xFF; + } + c = val; + break; + } + } + // step over the escape character or the last digit of the number + idLexer::script_p++; + // store the escape character + *ch = c; + // succesfully read escape character + return 1; +} + +/* +================ +idLexer::ReadString + +Escape characters are interpretted. +Reads two strings with only a white space between them as one string. +================ +*/ +int idLexer::ReadString( idToken *token, int quote ) { + int tmpline; + const char *tmpscript_p; + char ch; + + if ( quote == '\"' ) { + token->type = TT_STRING; + } else { + token->type = TT_LITERAL; + } + + // leading quote + idLexer::script_p++; + + while(1) { + // if there is an escape character and escape characters are allowed + if (*idLexer::script_p == '\\' && !(idLexer::flags & LEXFL_NOSTRINGESCAPECHARS)) { + if ( !idLexer::ReadEscapeCharacter( &ch ) ) { + return 0; + } + token->AppendDirty( ch ); + } + // if a trailing quote + else if (*idLexer::script_p == quote) { + // step over the quote + idLexer::script_p++; + // if consecutive strings should not be concatenated + if ( (idLexer::flags & LEXFL_NOSTRINGCONCAT) && + (!(idLexer::flags & LEXFL_ALLOWBACKSLASHSTRINGCONCAT) || (quote != '\"')) ) { + break; + } + + tmpscript_p = idLexer::script_p; + tmpline = idLexer::line; + // read white space between possible two consecutive strings + if ( !idLexer::ReadWhiteSpace() ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + + if ( idLexer::flags & LEXFL_NOSTRINGCONCAT ) { + if ( *idLexer::script_p != '\\' ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + // step over the '\\' + idLexer::script_p++; + if ( !idLexer::ReadWhiteSpace() || ( *idLexer::script_p != quote ) ) { + idLexer::Error( "expecting string after '\' terminated line" ); + return 0; + } + } + + // if there's no leading qoute + if ( *idLexer::script_p != quote ) { + idLexer::script_p = tmpscript_p; + idLexer::line = tmpline; + break; + } + // step over the new leading quote + idLexer::script_p++; + } + else { + if (*idLexer::script_p == '\0') { + idLexer::Error( "missing trailing quote" ); + return 0; + } + if (*idLexer::script_p == '\n') { + idLexer::Error( "newline inside string" ); + return 0; + } + token->AppendDirty( *idLexer::script_p++ ); + } + } + token->data[token->len] = '\0'; + + if ( token->type == TT_LITERAL ) { + if ( !(idLexer::flags & LEXFL_ALLOWMULTICHARLITERALS) ) { + if ( token->Length() != 1 ) { + idLexer::Warning( "literal is not one character long" ); + } + } + token->subtype = (*token)[0]; + } + else { + // the sub type is the length of the string + token->subtype = token->Length(); + } + return 1; +} + +/* +================ +idLexer::ReadName +================ +*/ +int idLexer::ReadName( idToken *token ) { + char c; + + token->type = TT_NAME; + do { + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; +// RAVEN BEGIN + } while ( idStr::CharIsAlpha( c ) || idStr::CharIsNumeric( c ) || c == '_' || +// RAVEN EBD + // if treating all tokens as strings, don't parse '-' as a seperate token + ((idLexer::flags & LEXFL_ONLYSTRINGS) && (c == '-')) || + // if special path name characters are allowed + ((idLexer::flags & LEXFL_ALLOWPATHNAMES) && (c == '/' || c == '\\' || c == ':' || c == '.')) ); + token->data[token->len] = '\0'; + //the sub type is the length of the name + token->subtype = token->Length(); + return 1; +} + +/* +================ +idLexer::CheckString +================ +*/ +ID_INLINE int idLexer::CheckString( const char *str ) const { + int i; + + for ( i = 0; str[i]; i++ ) { + if ( idLexer::script_p[i] != str[i] ) { + return false; + } + } + return true; +} + +/* +================ +idLexer::ReadNumber +================ +*/ +int idLexer::ReadNumber( idToken *token ) { + int i; + int dot; + char c, c2; + + token->type = TT_NUMBER; + token->subtype = 0; + token->intvalue = 0; + token->floatvalue = 0; + + c = *idLexer::script_p; + c2 = *(idLexer::script_p + 1); + + if ( c == '0' && c2 != '.' ) { + // check for a hexadecimal number + if ( c2 == 'x' || c2 == 'X' ) { + token->AppendDirty( *idLexer::script_p++ ); + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_HEX | TT_INTEGER; + } + // check for a binary number + else if ( c2 == 'b' || c2 == 'B' ) { + token->AppendDirty( *idLexer::script_p++ ); + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while( c == '0' || c == '1' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_BINARY | TT_INTEGER; + } + // its an octal number + else { + token->AppendDirty( *idLexer::script_p++ ); + c = *idLexer::script_p; + while( c >= '0' && c <= '7' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype = TT_OCTAL | TT_INTEGER; + } + } + else { + // decimal integer or floating point number or ip address + dot = 0; + while( 1 ) { + if ( c >= '0' && c <= '9' ) { + } + else if ( c == '.' ) { + dot++; + } + else { + break; + } + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + if( c == 'e' && dot == 0) { + //We have scientific notation without a decimal point + dot++; + } + // if a floating point number + if ( dot == 1 ) { + token->subtype = TT_DECIMAL | TT_FLOAT; + // check for floating point exponent + if ( c == 'e' ) { + //Append the e so that GetFloatValue code works + token->AppendDirty( c ); + c = *(++idLexer::script_p); + if ( c == '-' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + else if ( c == '+' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + } + // check for floating point exception infinite 1.#INF or indefinite 1.#IND or NaN + else if ( c == '#' ) { + c2 = 4; + if ( CheckString( "INF" ) ) { + token->subtype |= TT_INFINITE; + } + else if ( CheckString( "IND" ) ) { + token->subtype |= TT_INDEFINITE; + } + else if ( CheckString( "NAN" ) ) { + token->subtype |= TT_NAN; + } + else if ( CheckString( "QNAN" ) ) { + token->subtype |= TT_NAN; + c2++; + } + else if ( CheckString( "SNAN" ) ) { + token->subtype |= TT_NAN; + c2++; + } + for ( i = 0; i < c2; i++ ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + if ( !(idLexer::flags & LEXFL_ALLOWFLOATEXCEPTIONS) ) { + token->AppendDirty( 0 ); // zero terminate for c_str + idLexer::Error( "parsed %s", token->c_str() ); + } + } + } + else if ( dot > 1 ) { + if ( !( idLexer::flags & LEXFL_ALLOWIPADDRESSES ) ) { + idLexer::Error( "more than one dot in number" ); + return 0; + } + if ( dot != 3 ) { + idLexer::Error( "ip address should have three dots" ); + return 0; + } + token->subtype = TT_IPADDRESS; + } + else { + token->subtype = TT_DECIMAL | TT_INTEGER; + } + } + + if ( token->subtype & TT_FLOAT ) { + if ( c > ' ' ) { + // single-precision: float + if ( c == 'f' || c == 'F' ) { + token->subtype |= TT_SINGLE_PRECISION; + idLexer::script_p++; + } + // extended-precision: long double + else if ( c == 'l' || c == 'L' ) { + token->subtype |= TT_EXTENDED_PRECISION; + idLexer::script_p++; + } + // default is double-precision: double + else { + token->subtype |= TT_DOUBLE_PRECISION; + } + } + else { + token->subtype |= TT_DOUBLE_PRECISION; + } + } + else if ( token->subtype & TT_INTEGER ) { + if ( c > ' ' ) { + // default: signed long + for ( i = 0; i < 2; i++ ) { + // long integer + if ( c == 'l' || c == 'L' ) { + token->subtype |= TT_LONG; + } + // unsigned integer + else if ( c == 'u' || c == 'U' ) { + token->subtype |= TT_UNSIGNED; + } + else { + break; + } + c = *(++idLexer::script_p); + } + } + } + else if ( token->subtype & TT_IPADDRESS ) { + if ( c == ':' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + while( c >= '0' && c <= '9' ) { + token->AppendDirty( c ); + c = *(++idLexer::script_p); + } + token->subtype |= TT_IPPORT; + } + } + token->data[token->len] = '\0'; + return 1; +} + +/* +================ +idLexer::ReadPunctuation +================ +*/ +int idLexer::ReadPunctuation( idToken *token ) { + int l, n, i; + char *p; + const punctuation_t *punc; + +#ifdef PUNCTABLE + for (n = idLexer::punctuationtable[(unsigned int)*(idLexer::script_p)]; n >= 0; n = idLexer::nextpunctuation[n]) + { + punc = &(idLexer::punctuations[n]); +#else + int i; + + for (i = 0; idLexer::punctuations[i].p; i++) { + punc = &idLexer::punctuations[i]; +#endif + p = punc->p; + // check for this punctuation in the script + for ( l = 0; p[l] && idLexer::script_p[l]; l++ ) { + if ( idLexer::script_p[l] != p[l] ) { + break; + } + } + if ( !p[l] ) { + // + token->EnsureAlloced( l+1, false ); + for ( i = 0; i <= l; i++ ) { + token->data[i] = p[i]; + } + token->len = l; + // + idLexer::script_p += l; + token->type = TT_PUNCTUATION; + // sub type is the punctuation id + token->subtype = punc->n; + return 1; + } + } + return 0; +} + +/* +================ +idLexer::ReadToken +================ +*/ +int idLexer::ReadToken( idToken *token ) { + int c; + + if ( !loaded ) { +// RAVEN BEGIN +#ifndef _XENON +// nrausch: should not be an error on xenon since it prevents the precache stuff from working + idLib::common->Error( "idLexer::ReadToken: no file loaded" ); +#else + idLib::common->Warning( "idLexer::ReadToken: no file loaded" ); +#endif +// RAVEN END + return 0; + } + + // if there is a token available (from unreadToken) + if ( tokenavailable ) { + tokenavailable = 0; + *token = idLexer::token; + return 1; + } + // save script pointer + lastScript_p = script_p; + // save line counter + lastline = line; + // clear the token stuff + token->data[0] = '\0'; + token->len = 0; + // start of the white space + whiteSpaceStart_p = script_p; + token->whiteSpaceStart_p = script_p; + // read white space before token + if ( !ReadWhiteSpace() ) { + return 0; + } + // end of the white space + idLexer::whiteSpaceEnd_p = script_p; + token->whiteSpaceEnd_p = script_p; + // line the token is on + token->line = line; + // number of lines crossed before token + token->linesCrossed = line - lastline; + // clear token flags + token->flags = 0; + + c = *idLexer::script_p; + + // if we're keeping everything as whitespace deliminated strings + if ( idLexer::flags & LEXFL_ONLYSTRINGS ) { + // if there is a leading quote + if ( c == '\"' || c == '\'' ) { + if (!idLexer::ReadString( token, c )) { + return 0; + } + } else if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // if there is a number + else if ( (c >= '0' && c <= '9') || + (c == '.' && (*(idLexer::script_p + 1) >= '0' && *(idLexer::script_p + 1) <= '9')) ) { + if ( !idLexer::ReadNumber( token ) ) { + return 0; + } + // if names are allowed to start with a number + if ( idLexer::flags & LEXFL_ALLOWNUMBERNAMES ) { + c = *idLexer::script_p; + if ( idStr::CharIsAlpha( c ) || idStr::CharIsNumeric( c ) || c == '_' ) { + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + } + } + // if there is a leading quote + else if ( c == '\"' || c == '\'' ) { + if (!idLexer::ReadString( token, c )) { + return 0; + } + } + // if there is a name +// RAVEN BEGIN + else if ( idStr::CharIsAlpha( c ) || idStr::CharIsNumeric( c ) || c == '_' ) { +// RAVEN END + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // names may also start with a slash when pathnames are allowed + else if ( ( idLexer::flags & LEXFL_ALLOWPATHNAMES ) && ( (c == '/' || c == '\\') || c == '.' ) ) { + if ( !idLexer::ReadName( token ) ) { + return 0; + } + } + // check for punctuations + else if ( !idLexer::ReadPunctuation( token ) ) { + idLexer::Error( "unknown punctuation %c", c ); + return 0; + } +// RAVEN BEGIN +// jsinger: added to write out the binary version of this token when binary writes have been turned on + WriteBinaryToken(token); + +// RAVEN END + // succesfully read a token + return 1; +} + +/* +================ +idLexer::ExpectTokenString +================ +*/ +int idLexer::ExpectTokenString( const char *string ) { + idToken token; + + if (!idLexer::ReadToken( &token )) { + idLexer::Error( "couldn't find expected '%s'", string ); + return 0; + } + if ( token != string ) { + idLexer::Error( "expected '%s' but found '%s'", string, token.c_str() ); + return 0; + } + return 1; +} + +/* +================ +idLexer::ExpectTokenType +================ +*/ +int idLexer::ExpectTokenType( int type, int subtype, idToken *token ) { + idStr str; + + if ( !idLexer::ReadToken( token ) ) { + idLexer::Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + idLexer::Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + idLexer::Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + idLexer::Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + idLexer::Error( "expected '%s' but found '%s'", GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; +} + +/* +================ +idLexer::ExpectAnyToken +================ +*/ +int idLexer::ExpectAnyToken( idToken *token ) { + if (!idLexer::ReadToken( token )) { + idLexer::Error( "couldn't read expected token" ); + return 0; + } + else { + return 1; + } +} + +/* +================ +idLexer::CheckTokenString +================ +*/ +int idLexer::CheckTokenString( const char *string ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + return 0; + } + + // if the token is available + if ( tok == string ) { + return 1; + } + // token not available + UnreadToken( &tok ); + return 0; +} + +/* +================ +idLexer::CheckTokenType +================ +*/ +int idLexer::CheckTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + return 0; + } + // if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return 1; + } + // token is not available + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return 0; +} + +/* +================ +idLexer::SkipUntilString +================ +*/ +int idLexer::SkipUntilString( const char *string ) { + idToken token; + + while(idLexer::ReadToken( &token )) { + if ( token == string ) { + return 1; + } + } + return 0; +} + +/* +================ +idLexer::SkipRestOfLine +================ +*/ +int idLexer::SkipRestOfLine( void ) { + idToken token; + + while(idLexer::ReadToken( &token )) { + if ( token.linesCrossed ) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return 1; + } + } + return 0; +} + +/* +================= +idLexer::SkipBracedSection + +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +int idLexer::SkipBracedSection( bool parseFirstBrace ) { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if ( token.type == TT_PUNCTUATION ) { + if ( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; +} + +/* +================ +idLexer::UnreadToken +================ +*/ +void idLexer::UnreadToken( const idToken *token ) { + if ( idLexer::tokenavailable ) { + idLib::common->FatalError( "idLexer::unreadToken, unread token twice\n" ); + } + idLexer::token = *token; + idLexer::tokenavailable = 1; +} + +/* +================ +idLexer::ReadTokenOnLine +================ +*/ +int idLexer::ReadTokenOnLine( idToken *token ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + return false; + } + // if no lines were crossed before this token + if ( !tok.linesCrossed ) { + *token = tok; + return true; + } + // restore our position + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + token->Clear(); + return false; +} + +/* +================ +idLexer::ReadRestOfLine +================ +*/ +const char* idLexer::ReadRestOfLine(idStr& out) { + while(1) { + + if(*idLexer::script_p == '\n') { + idLexer::line++; + break; + } + + if(!*idLexer::script_p) { + break; + } + + if(*idLexer::script_p <= ' ') { + out += " "; + } else { + out += *idLexer::script_p; + } + idLexer::script_p++; + + } + + out.Strip(' '); + return out.c_str(); +} + +/* +================ +idLexer::ParseInt +================ +*/ +int idLexer::ParseInt( void ) { + idToken token; + + if ( !idLexer::ReadToken( &token ) ) { + idLexer::Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idLexer::ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + idLexer::Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); +} + +/* +================ +idLexer::ParseBool +================ +*/ +bool idLexer::ParseBool( void ) { + idToken token; + + if ( !idLexer::ExpectTokenType( TT_NUMBER, 0, &token ) ) { + idLexer::Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); +} + +/* +================ +idLexer::ParseFloat +================ +*/ +float idLexer::ParseFloat( bool *errorFlag ) { + idToken token; + + if ( errorFlag ) { + *errorFlag = false; + } + + if ( !idLexer::ReadToken( &token ) ) { + if ( errorFlag ) { + idLexer::Warning( "couldn't read expected floating point number" ); + *errorFlag = true; + } else { + idLexer::Error( "couldn't read expected floating point number" ); + } + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idLexer::ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + if ( errorFlag ) { + idLexer::Warning( "expected float value, found '%s'", token.c_str() ); + *errorFlag = true; + } else { + idLexer::Error( "expected float value, found '%s'", token.c_str() ); + } + } + return token.GetFloatValue(); +} + +/* +================ +idLexer::Parse1DMatrix +================ +*/ +int idLexer::Parse1DMatrix( int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = idLexer::ParseFloat(); + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +// RAVEN BEGIN +// rjohnson: added vertex color support to proc files. assume a default RGBA of 0x000000ff +/* +================ +idLexer::Parse1DMatrixOpenEnded +================ +*/ +int idLexer::Parse1DMatrixOpenEnded( int MaxCount, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return 0; + } + + for ( i = 0; i < MaxCount; i++ ) { + idToken tok; + + if (!idLexer::ReadToken( &tok )) { + return 0; + } + + if ( tok == ")" ) { + return i; + } + + idLexer::UnreadToken( &tok ); + + m[i] = idLexer::ParseFloat(); + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return 0; + } + + return i; +} +// RAVEN END + +/* +================ +idLexer::Parse2DMatrix +================ +*/ +int idLexer::Parse2DMatrix( int y, int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !idLexer::Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idLexer::Parse3DMatrix +================ +*/ +int idLexer::Parse3DMatrix( int z, int y, int x, float *m ) { + int i; + + if ( !idLexer::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !idLexer::Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !idLexer::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idLexer::ParseNumericStructArray +================ +*/ +void idLexer::ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ) +{ + int arrayOffset, curElement; + + for ( arrayOffset = 0; arrayOffset < arrayCount; arrayOffset++ ) + { + for ( curElement = 0; curElement < numStructElements; curElement++ ) + { + if ( tokenSubTypeStructElements[curElement] & TT_FLOAT ) + { + *(float*)arrayStorage = idLexer::ParseFloat(); + arrayStorage += sizeof(float); + } + else + { + *(int*)arrayStorage = idLexer::ParseInt(); + arrayStorage += sizeof(int); + } + } + } +} + +/* +================= +idParser::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Maintains exact characters between braces. + + FIXME: this should use ReadToken and replace the token white space with correct indents and newlines +================= +*/ +const char *idLexer::ParseBracedSectionExact( idStr &out, int tabs ) { + int depth; + bool doTabs; + bool skipWhite; + + out.Empty(); + + if ( !idLexer::ExpectTokenString( "{" ) ) { + return out.c_str( ); + } + + out = "{"; + depth = 1; + skipWhite = false; + doTabs = tabs >= 0; + + while( depth && *idLexer::script_p ) { + char c = *(idLexer::script_p++); + + switch ( c ) { + case '\t': + case ' ': { + if ( skipWhite ) { + continue; + } + break; + } + case '\n': { +// RAVEN BEGIN +// jscott: now gives correct line number in error reports + line++; +// RAVEN END + if ( doTabs ) { + skipWhite = true; + out += c; + continue; + } + break; + } + case '{': { + depth++; + tabs++; + break; + } + case '}': { + depth--; + tabs--; + break; + } + } + + if ( skipWhite ) { + int i = tabs; + if ( c == '{' ) { + i--; + } + skipWhite = false; + for ( ; i > 0; i-- ) { + out += '\t'; + } + } + out += c; + } + return out.c_str(); +} + +/* +================= +idLexer::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +const char *idLexer::ParseBracedSection( idStr &out ) { + idToken token; + int i, depth; + + out.Empty(); + if ( !idLexer::ExpectTokenString( "{" ) ) { + return out.c_str(); + } + out = "{"; + depth = 1; + do { + if ( !idLexer::ReadToken( &token ) ) { + Error( "missing closing brace" ); + return out.c_str(); + } + + // if the token is on a new line + for ( i = 0; i < token.linesCrossed; i++ ) { + out += "\r\n"; + } + + if ( token.type == TT_PUNCTUATION ) { + if ( token[0] == '{' ) { + depth++; + } + else if ( token[0] == '}' ) { + depth--; + } + } + + if ( token.type == TT_STRING ) { + out += "\"" + token + "\""; + } + else { + out += token; + } + out += " "; + } while( depth ); + + return out.c_str(); +} + +/* +================= +idLexer::ParseRestOfLine + + parse the rest of the line +================= +*/ +const char *idLexer::ParseRestOfLine( idStr &out ) { + idToken token; + + out.Empty(); + while(idLexer::ReadToken( &token )) { + if ( token.linesCrossed ) { + idLexer::script_p = lastScript_p; + idLexer::line = lastline; + break; + } + if ( out.Length() ) { + out += " "; + } + out += token; + } + return out.c_str(); +} + +/* +================ +idLexer::GetLastWhiteSpace +================ +*/ +int idLexer::GetLastWhiteSpace( idStr &whiteSpace ) const { + whiteSpace.Clear(); + for ( const char *p = whiteSpaceStart_p; p < whiteSpaceEnd_p; p++ ) { + whiteSpace.Append( *p ); + } + return whiteSpace.Length(); +} + +/* +================ +idLexer::GetLastWhiteSpaceStart +================ +*/ +int idLexer::GetLastWhiteSpaceStart( void ) const { + return whiteSpaceStart_p - buffer; +} + +/* +================ +idLexer::GetLastWhiteSpaceEnd +================ +*/ +int idLexer::GetLastWhiteSpaceEnd( void ) const { + return whiteSpaceEnd_p - buffer; +} + +/* +================ +idLexer::Reset +================ +*/ +void idLexer::Reset( void ) { + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // begin of white space + idLexer::whiteSpaceStart_p = NULL; + // end of white space + idLexer::whiteSpaceEnd_p = NULL; + // set if there's a token available in idLexer::token + idLexer::tokenavailable = 0; + + idLexer::line = 1; + idLexer::lastline = 1; + // clear the saved token + idLexer::token = ""; +} + +/* +================ +idLexer::EndOfFile +================ +*/ +int idLexer::EndOfFile( void ) { + return idLexer::script_p >= idLexer::end_p; +} + +/* +================ +idLexer::NumLinesCrossed +================ +*/ +int idLexer::NumLinesCrossed( void ) { + return idLexer::line - idLexer::lastline; +} + +/* +================ +idLexer::LoadFile +================ +*/ +int idLexer::LoadFile( const char *filename, bool OSPath ) { + idFile *fp; + idStr pathname; + int length; + char *buf; + + if ( idLexer::loaded ) { + idLib::common->Error("idLexer::LoadFile: another script already loaded"); + return false; + } + + if ( !OSPath && ( baseFolder[0] != '\0' ) ) { + pathname = va( "%s/%s", baseFolder, filename ); + } else { + pathname = filename; + } + if ( OSPath ) { + fp = idLib::fileSystem->OpenExplicitFileRead( pathname ); + } else { + fp = idLib::fileSystem->OpenFileRead( pathname ); + } + if ( !fp ) { + return false; + } + length = fp->Length(); +// RAVEN BEGIN +// amccarthy: Added memory allocation tag + buf = (char *) Mem_Alloc( length + 1, MA_LEXER ); + if( !buf ) { + common->FatalError( "Memory system failure : out of memory" ); + } +// RAVEN END + buf[length] = '\0'; + fp->Read( buf, length ); + idLexer::fileTime = fp->Timestamp(); + idLexer::filename = fp->GetFullPath(); + idLib::fileSystem->CloseFile( fp ); + + idLexer::buffer = buf; + idLexer::length = length; + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // pointer to end of script buffer + idLexer::end_p = &(idLexer::buffer[length]); + + idLexer::tokenavailable = 0; + idLexer::line = 1; + idLexer::lastline = 1; + idLexer::allocated = true; + idLexer::loaded = true; + +// RAVEN BEGIN +// jsinger: initialize compiled file + if(flags & LEXFL_WRITEBINARY) + { + pathname.Append(Lexer::sCompiledFileSuffix); + if ( OSPath ) { + mBinaryFile = idLib::fileSystem->OpenExplicitFileWrite( pathname ); + } else { + mBinaryFile = idLib::fileSystem->OpenFileWrite( pathname ); + } + } +// RAVEN END + + return true; +} + +/* +================ +idLexer::LoadMemory +================ +*/ +int idLexer::LoadMemory( const char *ptr, int length, const char *name, int startLine ) { + if ( idLexer::loaded ) { + idLib::common->Error("idLexer::LoadMemory: another script already loaded"); + return false; + } + idLexer::filename = name; + idLexer::buffer = ptr; + idLexer::fileTime = 0; + idLexer::length = length; + // pointer in script buffer + idLexer::script_p = idLexer::buffer; + // pointer in script buffer before reading token + idLexer::lastScript_p = idLexer::buffer; + // pointer to end of script buffer + idLexer::end_p = &(idLexer::buffer[length]); + + idLexer::tokenavailable = 0; + idLexer::line = startLine; + idLexer::lastline = startLine; + idLexer::allocated = false; + idLexer::loaded = true; + + return true; +} + +/* +================ +idLexer::FreeSource +================ +*/ +void idLexer::FreeSource( void ) { +#ifdef PUNCTABLE + if ( idLexer::punctuationtable && idLexer::punctuationtable != default_punctuationtable ) { + Mem_Free( (void *) idLexer::punctuationtable ); + idLexer::punctuationtable = NULL; + } + if ( idLexer::nextpunctuation && idLexer::nextpunctuation != default_nextpunctuation ) { + Mem_Free( (void *) idLexer::nextpunctuation ); + idLexer::nextpunctuation = NULL; + } +#endif //PUNCTABLE + if ( idLexer::allocated ) { + Mem_Free( (void *) idLexer::buffer ); + idLexer::buffer = NULL; + idLexer::allocated = false; + } + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::loaded = false; +// RAVEN BEGIN +// jsinger: close compile file if it exists + if(flags & LEXFL_WRITEBINARY) + { + if(mBinaryFile) + { + idLib::fileSystem->CloseFile(mBinaryFile); + mBinaryFile = NULL; + } + } +// RAVEN END +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( void ) { + idLexer::loaded = false; + idLexer::filename = ""; + idLexer::flags = 0; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::fileTime = 0; + idLexer::length = 0; + idLexer::line = 0; + idLexer::lastline = 0; + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( int flags ) { + idLexer::loaded = false; + idLexer::filename = ""; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::fileTime = 0; + idLexer::length = 0; + idLexer::line = 0; + idLexer::lastline = 0; + idLexer::tokenavailable = 0; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( const char *filename, int flags, bool OSPath ) { + idLexer::loaded = false; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END + idLexer::LoadFile( filename, OSPath ); +} + +/* +================ +idLexer::idLexer +================ +*/ +idLexer::idLexer( const char *ptr, int length, const char *name, int flags ) { + idLexer::loaded = false; + idLexer::flags = flags; + idLexer::SetPunctuations( NULL ); + idLexer::allocated = false; + idLexer::token = ""; + idLexer::next = NULL; + idLexer::hadError = false; +// RAVEN BEGIN +// jsinger: initialize compiled file + idLexer::mBinaryFile = NULL; +// RAVEN END + idLexer::LoadMemory( ptr, length, name ); +} + +/* +================ +idLexer::~idLexer +================ +*/ +idLexer::~idLexer( void ) { + idLexer::FreeSource(); +} + +// RAVEN BEGIN +// jsinger: SetBaseFolder was moved to the Lexer base class to unify its functionality across all +// derived classes +/* +================ +idLexer::SetBaseFolder +================ + +void idLexer::SetBaseFolder( const char *path ) { + idStr::Copynz( baseFolder, path, sizeof( baseFolder ) ); +} +*/ +// RAVEN END +/* +================ +idLexer::HadError +================ +*/ +bool idLexer::HadError( void ) const { + return hadError; +} + + +// RAVEN BEGIN +// jsinger: This method can write out a binary representation of a token in a format +// suitable to be read by the Lexer +/* +================ +idLexer::WriteBinaryToken + Writes a binary representation of the token value to the compiled file + when compiling is turned on +================ +*/ +void idLexer::WriteBinaryToken(idToken *tok) +{ + bool swapBytes = (flags & LEXFL_BYTESWAP) == LEXFL_BYTESWAP; + + if(flags & LEXFL_WRITEBINARY) + { + unsigned char prefix; + + switch(tok->type) + { + case TT_NUMBER: + if(tok->subtype & TT_INTEGER) + { + if(tok->subtype & TT_UNSIGNED) + { + unsigned long val = tok->GetUnsignedLongValue(); + unsigned char byteVal = (unsigned char)val; + unsigned short shortVal = (unsigned short)val; + + if(byteVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_UNSIGNEDINT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_UNSIGNEDINT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_UNSIGNEDINT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else + { + long val = tok->GetIntValue(); + char byteVal = (char)val; + short shortVal = (short)val; + + if(byteVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_INT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == val) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_INT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_INT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + } + else if(tok->subtype & TT_FLOAT) + { + if(tok->subtype & TT_SINGLE_PRECISION) + { + float val = tok->GetFloatValue(); + int intval = tok->GetIntValue(); + if(((float)intval) == val) // integral check + { + char byteVal = (char)intval; + short shortVal = (short)intval; + + if(byteVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else if(tok->subtype & TT_DOUBLE_PRECISION) + { + // we can write out doubles as floats because the text file doesn't have double precision anyway + float val = tok->GetDoubleValue(); + int intval = tok->GetIntValue(); + if(((float)intval) == val) // integral check + { + char byteVal = (char)intval; + short shortVal = (short)intval; + + if(byteVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_1BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(byteVal, mBinaryFile, swapBytes); + } + else if(shortVal == intval) + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_FLOAT, BTT_STORED_2BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(shortVal, mBinaryFile, swapBytes); + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_DOUBLE, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + else + { + prefix = BTT_MAKENUMBER_PREFIX(BTT_NUMBER, BTT_SUBTYPE_DOUBLE, BTT_STORED_4BYTE); + TextCompiler::WriteValue(prefix, mBinaryFile, swapBytes); + TextCompiler::WriteValue(val, mBinaryFile, swapBytes); + } + } + } + break; + case TT_STRING: + TextCompiler::WriteValue(BTT_MAKESTRING_PREFIX(BTT_STRING, tok->Length()), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + break; + case TT_LITERAL: + TextCompiler::WriteValue(BTT_MAKESTRING_PREFIX(BTT_LITERAL, tok->Length()), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + break; + case TT_NAME: + TextCompiler::WriteValue(BTT_MAKESTRING_PREFIX(BTT_NAME, tok->Length()), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + break; + case TT_PUNCTUATION: + if(*tok == "&&") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LOGICALAND), mBinaryFile, swapBytes); + } + else if (*tok == "&") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_AMPERSAND), mBinaryFile, swapBytes); + } + else if(*tok == "=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_EQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "==") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_EQUALEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "!=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_NOTEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "!") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_EXCLAMATION), mBinaryFile, swapBytes); + } + else if(*tok == "<") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LESSTHAN), mBinaryFile, swapBytes); + } + else if(*tok == "<=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LESSOREQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "<<") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_SHIFTLEFT), mBinaryFile, swapBytes); + } + else if(*tok == ">") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_GREATERTHAN), mBinaryFile, swapBytes); + } + else if(*tok == ">=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_GREATEROREQUAL), mBinaryFile, swapBytes); + } + else if(*tok == ">>") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_SHIFTRIGHT), mBinaryFile, swapBytes); + } + else if(*tok == "%") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PERCENT), mBinaryFile, swapBytes); + } + else if(*tok == "[") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LEFTBRACKET), mBinaryFile, swapBytes); + } + else if(*tok == "]") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_RIGHTBRACKET), mBinaryFile, swapBytes); + } + else if(*tok == "-") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_MINUS), mBinaryFile, swapBytes); + } + else if(*tok == "--") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_MINUSMINUS), mBinaryFile, swapBytes); + } + else if(*tok == "-=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_MINUSEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "+") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PLUS), mBinaryFile, swapBytes); + } + else if(*tok == "+=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PLUSEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "++") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PLUSPLUS), mBinaryFile, swapBytes); + } + else if(*tok == "(") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LEFTPAREN), mBinaryFile, swapBytes); + } + else if(*tok == ")") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_RIGHTPAREN), mBinaryFile, swapBytes); + } + else if(*tok == "{") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_LEFTBRACE), mBinaryFile, swapBytes); + } + else if(*tok == "}") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_RIGHTBRACE), mBinaryFile, swapBytes); + } + else if(*tok == ",") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_COMMA), mBinaryFile, swapBytes); + } + else if(*tok == "::") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOUBLECOLON), mBinaryFile, swapBytes); + } + else if(*tok == "#") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_HASH), mBinaryFile, swapBytes); + } + else if(*tok == "##") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOUBLEHASH), mBinaryFile, swapBytes); + } + else if(*tok == "/") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_FORWARDSLASH), mBinaryFile, swapBytes); + } + else if(*tok == "\\") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_BACKSLASH), mBinaryFile, swapBytes); + } + else if(*tok == ";") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_SEMICOLON), mBinaryFile, swapBytes); + } + else if(*tok == ".") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PERIOD), mBinaryFile, swapBytes); + } + else if(*tok == "$") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOLLARSIGN), mBinaryFile, swapBytes); + } + else if(*tok == "~") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_TILDE), mBinaryFile, swapBytes); + } + else if(*tok == "|") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_PIPE), mBinaryFile, swapBytes); + } + else if(*tok == "||") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_DOUBLEPIPE), mBinaryFile, swapBytes); + } + else if(*tok == "*") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_ASTERISK), mBinaryFile, swapBytes); + } + else if(*tok == "*=") + { + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_TIMESEQUAL), mBinaryFile, swapBytes); + } + else if(*tok == "¡") + { + TextCompiler::WriteValue(BTT_PUNC_INVERTEDPLING, mBinaryFile, swapBytes); + } + else if(*tok == "¿") + { + TextCompiler::WriteValue(BTT_PUNC_INVERTEDQUERY, mBinaryFile, swapBytes); + } + else + { + common->Warning("Unsupported punctuation: '%s'", tok->c_str()); + + TextCompiler::WriteValue(BTT_MAKEPUNCTUATION_PREFIX(BTT_PUNC_ASNULLTERMINATED), mBinaryFile, swapBytes); + TextCompiler::WriteValue(tok, mBinaryFile, swapBytes); + } + break; + default: + // wtf? + assert(false); + } + } +} + +// jsinger: this method takes an existing file and causes a binary tokenized +// version of the file to be generated +void idLexer::WriteBinaryFile(char const * const filename) +{ + if(!cvarSystem->GetCVarBool("com_BinaryRead")) + { + unsigned int swap=0; + + switch(cvarSystem->GetCVarInteger("com_BinaryWrite")) + { + case 1: + swap = 0; + break; + case 2: + swap = LEXFL_BYTESWAP; + break; + } + + idLexer src(filename, LEXFL_WRITEBINARY | swap); + idToken token; + if(src.IsLoaded()) + { + while(src.ReadToken(&token)) + { + // we don't need to do anything, because just reading the file + // causes it to write a tokenized version + } + } + } +} + +// jsinger: implementation for a class that can load binary tokenized files +using namespace TextCompiler; +Lexer::Lexer(int flags) +{ +#ifndef LEXER_READ_AHEAD + mFile = NULL; +#endif + offset = 0; + tokenAvailable = false; + mFlags = flags; + if(flags & LEXFL_READBINARY) + { + mDelegate = NULL; + } + else + { + mDelegate = new idLexer(flags); + } +} + +Lexer::Lexer(char const * const ptr, int length, char const * const name, int flags) +{ + if(flags & LEXFL_READBINARY) + { +#ifdef LEXER_READ_AHEAD + mLexerIOWrapper.InitFromMemory(ptr, length); +#else + assert(false); +#endif + mDelegate = NULL; + } + else + { + mDelegate = new idLexer(ptr, length, name, flags); + } +} + +Lexer::Lexer(char const * const filename, int flags, bool OSPath) +{ +#ifndef LEXER_READ_AHEAD + mFile = NULL; +#endif + offset = 0; + tokenAvailable = false; + mFlags = flags; + if(flags & LEXFL_READBINARY) + { + mDelegate = NULL; + LoadFile(filename, OSPath); + } + else + { + mDelegate = new idLexer(filename, flags, OSPath); + } +} + +Lexer::~Lexer() +{ + FreeSource(); +} + +char const * Lexer::GetFileName() +{ + if(mDelegate) + { + return mDelegate->GetFileName(); + } + else + { + assert(false); + return ""; + } +} + +bool Lexer::HadError() const +{ + if(mDelegate) + { + return mDelegate->HadError(); + } + else + { + assert(false); + return false; + } +} + +void Lexer::Warning(char const *str, ...) +{ + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); + idLib::common->Warning( "Lexer: '%s'", text ); +} + +void Lexer::Error(char const *str, ...) +{ + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start( ap, str ); + vsprintf( text, str, ap ); + va_end( ap ); + + assert(false); + + idLib::common->Error( "Lexer: offset: %d '%s'", offset, text ); +} + +int const Lexer::GetLineNum() +{ + if(mDelegate) + { + return mDelegate->GetLineNum(); + } + else + { + return 1; + } +} + +unsigned int const Lexer::GetFileTime() +{ + if(mDelegate) + { + return mDelegate->GetFileTime(); + } + else + { + return 0; + } +} + +int const Lexer::GetFileOffset() +{ + if(mDelegate) + { + return mDelegate->GetFileOffset(); + } + { + assert(false); + return 0; + } +} + +int Lexer::EndOfFile() +{ + if(mDelegate) + { + return mDelegate->EndOfFile(); + } + else + { +#ifdef LEXER_READ_AHEAD + return mLexerIOWrapper.Tell()>= mLexerIOWrapper.Length(); +#else + return mFile->Tell()>= mFile->Length(); +#endif + } +} + +void Lexer::Reset() +{ + if(mDelegate) + { + mDelegate->Reset(); + } + else + { +#ifdef LEXER_READ_AHEAD + mLexerIOWrapper.Seek(0, FS_SEEK_SET); +#else + mFile->Seek(0, FS_SEEK_SET); +#endif + offset = 0; + tokenAvailable = false; + } +} + +int Lexer::GetFlags() +{ + if(mDelegate) + { + return mDelegate->GetFlags(); + } + else + { + return mFlags; + } +} + +void Lexer::SetFlags(int flags) +{ + if(mDelegate) + { + mDelegate->SetFlags(flags); + } + else + { + mFlags = flags; + } +} + +int Lexer::GetPunctuationId(char const *str) +{ + if(mDelegate) + { + return mDelegate->GetPunctuationId(str); + } + else + { + // this method is not supported + assert(false); + return 0; + } +} + +void Lexer::SetPunctuations(struct punctuation_s const *punc) +{ + if(mDelegate) + { + mDelegate->SetPunctuations(punc); + } + else + { + // this method is not supported + assert(false); + } +} + +int Lexer::GetLastWhiteSpaceEnd() const +{ + if(mDelegate) + { + return mDelegate->GetLastWhiteSpaceEnd(); + } + else + { + return 0; + } +} + +char const *Lexer::GetPunctuationFromId(int val) +{ + if(mDelegate) + { + return mDelegate->GetPunctuationFromId(val); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +int Lexer::GetLastWhiteSpaceStart() const +{ + if(mDelegate) + { + return mDelegate->GetLastWhiteSpaceStart(); + } + else + { + return 0; + } +} + +int Lexer::GetLastWhiteSpace(idStr &str) const +{ + if(mDelegate) + { + return mDelegate->GetLastWhiteSpace(str); + } + else + { + return 0; + } +} + +char const *Lexer::ParseRestOfLine(idStr &str) +{ + if(mDelegate) + { + return mDelegate->ParseRestOfLine(str); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +char const *Lexer::ParseBracedSectionExact(idStr &str, int val) +{ + if(mDelegate) + { + return mDelegate->ParseBracedSectionExact(str, val); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +char const *Lexer::ParseBracedSection(idStr &str) +{ + if(mDelegate) + { + return mDelegate->ParseBracedSection(str); + } + else + { + // this method is not supported + assert(false); + return ""; + } +} + +int Lexer::Parse3DMatrix(int z, int y, int x, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse3DMatrix(z, y, x, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !ExpectTokenString( ")" ) ) { + return false; + } + return true; + } +} + +int Lexer::Parse2DMatrix(int y, int x, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse2DMatrix(y, x, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !ExpectTokenString( ")" ) ) { + return false; + } + return true; + } +} + +int Lexer::Parse1DMatrixOpenEnded(int MaxCount, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse1DMatrixOpenEnded(MaxCount, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return 0; + } + + for ( i = 0; i < MaxCount; i++ ) { + idToken tok; + + if (!ReadToken( &tok )) { + return 0; + } + + if ( tok == ")" ) { + return i; + } + + UnreadToken( &tok ); + + m[i] = ParseFloat(); + } + + if ( !ExpectTokenString( ")" ) ) { + return 0; + } + + return i; + } +} + +int Lexer::Parse1DMatrix(int x, float *m) +{ + if(mDelegate) + { + return mDelegate->Parse1DMatrix(x, m); + } + else + { + int i; + + if ( !ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = ParseFloat(); + } + + if ( !ExpectTokenString( ")" ) ) { + return false; + } + return true; + } +} + +float Lexer::ParseFloat(bool *errorFlag) +{ + if(mDelegate) + { + return mDelegate->ParseFloat(errorFlag); + } + else + { + idToken token; + + if(EndOfFile()) + { + return 0; + } + + if ( !ReadToken( &token ) ) { + Warning( "couldn't read expected floating point number" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + Warning( "expected float value, found '%s'", token.c_str() ); + } + return token.GetFloatValue(); + } +} + +bool Lexer::ParseBool() +{ + if(mDelegate) + { + return mDelegate->ParseBool(); + } + else + { + idToken token; + + if ( !ExpectTokenType( TT_NUMBER, 0, &token ) ) { + Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); + } +} + +int Lexer::ParseInt() +{ + if(mDelegate) + { + return mDelegate->ParseInt(); + } + else + { + idToken token; + + if ( !ReadToken( &token ) ) { + Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); + } +} + +int Lexer::ReadTokenOnLine(idToken *token) +{ + if(mDelegate) + { + return mDelegate->ReadTokenOnLine(token); + } + else + { + // lines are meaningless in the binary format + return ReadToken(token); + } +} + +void Lexer::UnreadToken(idToken const *token) +{ + if(mDelegate) + { + mDelegate->UnreadToken(token); + } + else + { + if ( tokenAvailable ) + { + idLib::common->FatalError( "Lexer::unreadToken, unread token twice\n" ); + } + unreadToken = *token; + tokenAvailable = true; + offset -= unreadSize+1; // the +1 is for the prefix + } +} + +int Lexer::SkipBracedSection(bool parseFirstBrace) +{ + if(mDelegate) + { + return mDelegate->SkipBracedSection(parseFirstBrace); + } + else + { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if ( token.type == TT_PUNCTUATION ) { + if ( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; + } +} + +int Lexer::SkipRestOfLine() +{ + if(mDelegate) + { + return mDelegate->SkipRestOfLine(); + } + else + { + // this method is not supported + assert(false); + return 0; + } +} + +int Lexer::SkipUntilString(char const *str) +{ + if(mDelegate) + { + return mDelegate->SkipUntilString(str); + } + else + { + // this method is not supported + assert(false); + return 0; + } +} + +int Lexer::CheckTokenType(int type, int subtype, idToken *token) +{ + if(mDelegate) + { + return mDelegate->CheckTokenType(type, subtype, token); + } + else + { + idToken tok; + + if (!ReadToken( &tok )) { + return 0; + } + // if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return 1; + } + return 0; + } +} + +int Lexer::CheckTokenString(char const *string) +{ + if(mDelegate) + { + return mDelegate->CheckTokenString(string); + } + else + { + idToken tok; + + if (!ReadToken( &tok )) { + return 0; + } + + // if the token is available + if ( tok == string ) { + return 1; + } + + UnreadToken( &tok ); + return 0; + } +} + +int Lexer::ExpectAnyToken(idToken *token) +{ + if(mDelegate) + { + return mDelegate->ExpectAnyToken(token); + } + else + { + if (!ReadToken( token )) { + Error( "couldn't read expected token" ); + return 0; + } + else { + return 1; + } + } +} + +int Lexer::ExpectTokenType(int type, int subtype, idToken *token) +{ + if(mDelegate) + { + return mDelegate->ExpectTokenType(type, subtype, token); + } + else + { + idStr str; + + if ( !ReadToken( token ) ) { + Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + Error( "expected '%s' but found '%s'", GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; + } +} + +int Lexer::ExpectTokenString(char const *string) +{ + if(mDelegate) + { + return mDelegate->ExpectTokenString(string); + } + else + { + idToken token; + + if(!ReadToken( &token )) + { + Error( "couldn't find expected '%s'", string ); + return 0; + } + if( token != string ) + { + Error( "expected '%s' but found '%s'", string, token.c_str() ); + return 0; + } + return 1; + } +} + +int Lexer::ReadToken(idToken *token) +{ + +#ifdef LEXER_READ_AHEAD +#define OBJ &mLexerIOWrapper +#else +#define OBJ mFile +#endif + + if(mDelegate) + { + return mDelegate->ReadToken(token); + } + else + { + // if there are any unread tokens, use them first + if(tokenAvailable) + { + *token = unreadToken; + tokenAvailable = false; + } + else + { +#ifndef LEXER_READ_AHEAD + assert(mFile); +#endif + if(EndOfFile()) + return 0; + unsigned char prefix = ReadValue(OBJ); + token->Clear(); + + // BTT types are equivalent to the TT types except they are one less, so we add one to get the token type + if(BTT_GET_TYPE(prefix) == BTT_PUNCTUATION2) + token->type = TT_PUNCTUATION; + else + token->type = BTT_GET_TYPE(prefix)+1; + + // this is used only to determine if the punctuation was encoded in the prefix or came afterwards as a null terminated string + bool encoded = true; + + switch(token->type) + { + case TT_STRING: + case TT_NAME: + case TT_LITERAL: + if(BTT_GET_STRING_LENGTH(prefix) != 0) + { + // short string + int length = BTT_GET_STRING_LENGTH(prefix); + if ( length == 0 ) { + assert( false ); + } + int i; + for( i = 0; i < length; i++ ) + { + char c = ReadValue(OBJ); + token->Append(c); + } + // short strings have no null, so we must add one + //token->Append('\0'); + + token->subtype = token->Length(); + unreadSize = token->Length(); + } + else + { + // null terminated string + *token = ReadValue(OBJ); + token->subtype = token->Length(); + unreadSize = token->Length()+1; + } + break; + case TT_PUNCTUATION: + switch(BTT_GET_PUNCTUATION(prefix)) + { + case BTT_PUNC_ASNULLTERMINATED: + // null terminated string + *token = ReadValue(OBJ); + token->subtype = token->Length(); + unreadSize = token->Length()+1; + encoded = false; + break; + case BTT_PUNC_RIGHTPAREN: + *token = ")"; + break; + case BTT_PUNC_LEFTBRACE: + *token = "{"; + break; + case BTT_PUNC_RIGHTBRACE: + *token = "}"; + break; + case BTT_PUNC_MINUS: + *token = "-"; + break; + case BTT_PUNC_PLUS: + *token = "+"; + break; + case BTT_PUNC_COMMA: + *token = ","; + break; + case BTT_PUNC_PLUSPLUS: + *token = "++"; + break; + case BTT_PUNC_LEFTBRACKET: + *token = "["; + break; + case BTT_PUNC_RIGHTBRACKET: + *token = "]"; + break; + case BTT_PUNC_EQUAL: + *token = "="; + break; + case BTT_PUNC_EQUALEQUAL: + *token = "=="; + break; + case BTT_PUNC_NOTEQUAL: + *token = "!="; + break; + case BTT_PUNC_PERCENT: + *token = "%"; + break; + case BTT_PUNC_LESSTHAN: + *token = "<"; + break; + case BTT_PUNC_GREATERTHAN: + *token = ">"; + break; + case BTT_PUNC_LOGICALAND: + *token = "&&"; + break; + case BTT_PUNC_AMPERSAND: + *token = "&"; + break; + case BTT_PUNC_MINUSMINUS: + *token = "--"; + break; + case BTT_PUNC_HASH: + *token = "#"; + break; + case BTT_PUNC_LESSOREQUAL: + *token = "<="; + break; + case BTT_PUNC_GREATEROREQUAL: + *token = ">="; + break; + case BTT_PUNC_FORWARDSLASH: + *token = "/"; + break; + case BTT_PUNC_SHIFTLEFT: + *token = "<<"; + break; + case BTT_PUNC_SHIFTRIGHT: + *token = ">>"; + break; + case BTT_PUNC_LEFTPAREN: + *token = "("; + break; + case BTT_PUNC_SEMICOLON: + *token = ";"; + break; + case BTT_PUNC_ASTERISK: + *token = "*"; + break; + case BTT_PUNC_PERIOD: + *token = "."; + break; + case BTT_PUNC_DOLLARSIGN: + *token = "$"; + break; + case BTT_PUNC_PLUSEQUAL: + *token = "+="; + break; + case BTT_PUNC_MINUSEQUAL: + *token = "-="; + break; + case BTT_PUNC_TILDE: + *token = "~"; + break; + case BTT_PUNC_EXCLAMATION: + *token = "!"; + break; + case BTT_PUNC_PIPE: + *token = "|"; + break; + case BTT_PUNC_BACKSLASH: + *token = "\\"; + break; + case BTT_PUNC_DOUBLEHASH: + *token = "##"; + break; + case BTT_PUNC_TIMESEQUAL: + *token = "*="; + break; + case BTT_PUNC_DOUBLEPIPE: + *token = "||"; + break; + case BTT_PUNC_INVERTEDPLING: + *token = "¡"; + break; + case BTT_PUNC_INVERTEDQUERY: + *token = "¿"; + break; + default: + assert(false); // unrecognized punctuation + } + if(encoded) + unreadSize = 0; // punctuation encoded in prefix + break; + case TT_NUMBER: + { + // number of bytes actually in the stream used to represent this value + unsigned int size = BTT_GET_STORED_SIZE(prefix); + const int buffersize = 100; + char buffer[buffersize]; // big enough buffer for the int to string conversion routines + + switch(BTT_GET_SUBTYPE(prefix)) + { + case BTT_SUBTYPE_INT: + switch(size) + { + case BTT_STORED_1BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(char); + break; + case BTT_STORED_2BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(short); + break; + case BTT_STORED_4BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(int); + break; + default: + // invalid stored size for an integer + assert(false); + } + + token->floatvalue = token->intvalue; + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + + // This conversion to a string assumes that long and int are the same + assert(sizeof(long) == sizeof(int)); + + //ltoa(token->intvalue, buffer, 10); + idStr::snPrintf( buffer, buffersize, "%ld", token->intvalue ); + assert(token->intvalue == atol(buffer)); + *token = buffer; + token->subtype = TT_INTEGER | TT_DECIMAL | TT_VALUESVALID; + break; + + case BTT_SUBTYPE_UNSIGNEDINT: + switch(size) + { + case BTT_STORED_1BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(unsigned char); + break; + case BTT_STORED_2BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(unsigned short); + break; + case BTT_STORED_4BYTE: + token->intvalue = ReadValue(OBJ); + unreadSize = sizeof(unsigned int); + break; + default: + // invalid stored size for an unsigned integer + assert(false); + } + token->floatvalue = token->intvalue; + + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + + // This conversion to a string assumes that long and int are the same + assert(sizeof(long) == sizeof(int)); + + //ultoa(token->intvalue, buffer, 10); + idStr::snPrintf( buffer, buffersize, "%lu", token->intvalue ); + assert(token->intvalue == ((unsigned long)atol(buffer))); + *token = buffer; + token->subtype = TT_INTEGER | TT_UNSIGNED | TT_DECIMAL | TT_VALUESVALID; + break; + + case BTT_SUBTYPE_FLOAT: + // invalid stored size for a float + assert(sizeof(float) == 4); + + switch(size) + { + case BTT_STORED_4BYTE: + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(float); + break; + case BTT_STORED_2BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(short); + break; + case BTT_STORED_1BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(char); + break; + default: + assert(false); + } + + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + idStr::snPrintf( buffer, buffersize, "%f", token->floatvalue ); + *token = buffer; + token->subtype = TT_FLOAT | TT_DECIMAL | TT_SINGLE_PRECISION | TT_VALUESVALID; + + unreadSize = sizeof(float); + break; + + case BTT_SUBTYPE_DOUBLE: + // invalid stored size for a double + assert(sizeof(double) == 8); + + // doubles are stored as floats since the original text file never uses full double precision anyway + //assert(size == BTT_STORED_4BYTE); + + switch(size) + { + case BTT_STORED_4BYTE: + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(float); + break; + case BTT_STORED_2BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(short); + break; + case BTT_STORED_1BYTE: // requested a float, but it was integral, so it was saved that way + token->floatvalue = ReadValue(OBJ); + token->intvalue = token->floatvalue; + unreadSize = sizeof(char); + break; + default: + assert(false); + } + + // I hate the fact that I have to copy into the string, but there's no way + // of easily knowing whether it is used later or not + idStr::snPrintf( buffer, buffersize, "%f", token->floatvalue ); + *token = buffer; + token->subtype = TT_FLOAT | TT_DECIMAL | TT_DOUBLE_PRECISION | TT_VALUESVALID; + break; + } + } + break; + + default: + // unsupported binary type + assert(false); + } + } + + //common->Warning("Read Token: '%s (int: %d)(float: %f)'\n", token->c_str(), token->GetIntValue(), token->GetFloatValue()); + //common->Printf("Read Token: %s\n", token->c_str()); + offset += unreadSize+1; // the +1 is for the prefix + return 1; + } +} + +int Lexer::IsLoaded() +{ + if(mDelegate) + { + return mDelegate->IsLoaded(); + } + else + { +#ifdef LEXER_READ_AHEAD + return mLexerIOWrapper.IsLoaded(); +#else + return mFile != NULL; +#endif + } +} + +void Lexer::FreeSource() +{ +#ifdef LEXER_READ_AHEAD + mLexerIOWrapper.Close(); +#else + if(mFile) + { + idLib::fileSystem->CloseFile(mFile); + mFile = NULL; + } +#endif + + if(mDelegate) + { + delete mDelegate; + mDelegate = NULL; + } +} + +int Lexer::LoadMemory(char const *ptr, int length, char const *name, int startLine) +{ + if(mDelegate) + { + return mDelegate->LoadMemory(ptr, length, name, startLine); + } + else + { + // this is essentially a no op + return true; + } +} + +int Lexer::LoadFile(char const *filename, bool OSPath) +{ + if(mDelegate) + { + return mDelegate->LoadFile(filename, OSPath); + } + else + { + idStr fullName; + FreeSource(); + + fullName = filename; + fullName.Append(sCompiledFileSuffix); + bool binaryFound = OpenFile(fullName, OSPath); + if(binaryFound) + { + return binaryFound; + } + else + { + mDelegate = new idLexer(filename, mFlags, OSPath); + int isLoaded = mDelegate->IsLoaded(); + if(isLoaded) + { + // don't do this right now until I clean up the Lexer class + if(mFlags & LEXFL_READBINARY) + { + Warning("%s not found, loading ascii version", fullName.c_str()); + } + } + else + { + // didn't find the ascii version either, make sure and clean up in case this lexer is reused + delete mDelegate; + mDelegate = NULL; + } + + return isLoaded; + } + } +} + +void Lexer::WriteBinaryToken(idToken *tok) +{ + if(mDelegate) + { + mDelegate->WriteBinaryToken(tok); + } + else + { + // can't write out an already binary file + assert(false); + } +} + +bool Lexer::OpenFile(char const *filename, bool OSPath) +{ + idStr pathname; + + if ( !OSPath && ( baseFolder[0] != '\0' ) ) { + pathname = va( "%s/%s", baseFolder, filename ); + } else { + pathname = filename; + } + +#ifdef LEXER_READ_AHEAD + if ( !mLexerIOWrapper.OpenFile( pathname, OSPath ) ) { + return false; + } +#else + if ( OSPath ) { + mFile = idLib::fileSystem->OpenExplicitFileRead( pathname ); + } else { + mFile = idLib::fileSystem->OpenFileRead( pathname ); + } + if ( !mFile ) { + return false; + } +#endif + + return true; +} + +void Lexer::SetBaseFolder( const char *path ) { + idStr::Copynz( baseFolder, path, sizeof( baseFolder ) ); +} + + +// RAVEN BEGIN +// dluetscher: added method to parse a structure array that is made up of numerics (floats, ints), and stores them in the given storage +void Lexer::ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ) +{ + if(mDelegate) + { + mDelegate->ParseNumericStructArray( numStructElements, tokenSubTypeStructElements, arrayCount, arrayStorage ); + } + else + { + int arrayOffset, curElement; + + for ( arrayOffset = 0; arrayOffset < arrayCount; arrayOffset++ ) + { + for ( curElement = 0; curElement < numStructElements; curElement++ ) + { + if ( tokenSubTypeStructElements[curElement] & TT_FLOAT ) + { + *(float*)arrayStorage = Lexer::ParseFloat(); + arrayStorage += sizeof(float); + } + else + { + *(int*)arrayStorage = Lexer::ParseInt(); + arrayStorage += sizeof(int); + } + } + } + } +} +// RAVEN END + + +char idLexer::baseFolder[256]; +// RAVEN END diff --git a/src/idlib/Lexer.h b/src/idlib/Lexer.h new file mode 100644 index 0000000..cfaf61f --- /dev/null +++ b/src/idlib/Lexer.h @@ -0,0 +1,489 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LEXER_H__ +#define __LEXER_H__ + +/* +=============================================================================== + + Lexicographical parser + + Does not use memory allocation during parsing. The lexer uses no + memory allocation if a source is loaded with LoadMemory(). + However, idToken may still allocate memory for large strings. + + A number directly following the escape character '\' in a string is + assumed to be in decimal format instead of octal. Binary numbers of + the form 0b.. or 0B.. can also be used. + +=============================================================================== +*/ + +// lexer flags +typedef enum { + LEXFL_NOERRORS = BIT(0), // don't print any errors + LEXFL_NOWARNINGS = BIT(1), // don't print any warnings + LEXFL_NOFATALERRORS = BIT(2), // errors aren't fatal + LEXFL_NOSTRINGCONCAT = BIT(3), // multiple strings seperated by whitespaces are not concatenated + LEXFL_NOSTRINGESCAPECHARS = BIT(4), // no escape characters inside strings + LEXFL_NODOLLARPRECOMPILE = BIT(5), // don't use the $ sign for precompilation + LEXFL_NOBASEINCLUDES = BIT(6), // don't include files embraced with < > + LEXFL_ALLOWPATHNAMES = BIT(7), // allow path seperators in names + LEXFL_ALLOWNUMBERNAMES = BIT(8), // allow names to start with a number + LEXFL_ALLOWIPADDRESSES = BIT(9), // allow ip addresses to be parsed as numbers + LEXFL_ALLOWFLOATEXCEPTIONS = BIT(10), // allow float exceptions like 1.#INF or 1.#IND to be parsed + LEXFL_ALLOWMULTICHARLITERALS = BIT(11), // allow multi character literals + LEXFL_ALLOWBACKSLASHSTRINGCONCAT = BIT(12), // allow multiple strings seperated by '\' to be concatenated + LEXFL_ONLYSTRINGS = BIT(13), // parse as whitespace deliminated strings (quoted strings keep quotes) +// RAVEN BEGIN +// jsinger: added to support binary writing from the lexer in either byte swapped or non-byte swapped formats + LEXFL_READBINARY = BIT(29), // read a byte code compiled version of the file + LEXFL_BYTESWAP = BIT(30), // when writing the byte code compiled file, byte swap numbers + LEXFL_WRITEBINARY = BIT(31) // write out a byte code compiled version of the file +// RAVEN END +} lexerFlags_t; + +// punctuation ids +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 + +// RAVEN BEGIN +#define P_INVERTED_PLING 53 +#define P_INVERTED_QUERY 54 +// RAVEN END + +// punctuation +typedef struct punctuation_s +{ + char *p; // punctuation character(s) + int n; // punctuation id +} punctuation_t; + +// RAVEN BEGIN + +#ifdef LEXER_READ_AHEAD +class LexerIOWrapper { + idFile *file; + + int offset; + int size; + char *memory; + +public: + LexerIOWrapper(); + void InitFromMemory(char const * const ptr, int length); + bool OpenFile(char const *filename, bool OSPath); + int Length(); + int Tell(); + void Seek( int loc, int mode ); + void Read( void *dest, int s ); + void Close(); + int IsLoaded(); +}; +#endif + +class idLexer { + friend class idParser; + +public: +// RAVEN END + // constructor + idLexer(); + idLexer( int flags ); + idLexer( const char *filename, int flags = 0, bool OSPath = false ); + idLexer( const char *ptr, int length, const char *name, int flags = 0 ); + // destructor + ~idLexer(); + // load a script from the given file at the given offset with the given length + int LoadFile( const char *filename, bool OSPath = false ); + // load a script from the given memory with the given length and a specified line offset, + // so source strings extracted from a file can still refer to proper line numbers in the file + // NOTE: the buffer is expect to be a valid C string: ptr[length] == '\0' + int LoadMemory( const char *ptr, int length, const char *name, int startLine = 1 ); + // free the script + void FreeSource( void ); + // returns true if a script is loaded + int IsLoaded( void ) { return idLexer::loaded; } + // read a token + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true when the token is available + int CheckTokenString( const char *string ); + // returns true an reads the token when a token with the given type is available + int CheckTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine( void ); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // unread the given token + void UnreadToken( const idToken *token ); + // read a token only if on the same line + int ReadTokenOnLine( idToken *token ); + //Returns the rest of the current line + const char* ReadRestOfLine(idStr& out); + // read a signed integer + int ParseInt( void ); + // read a boolean + bool ParseBool( void ); + // read a floating point number. If errorFlag is NULL, a non-numeric token will + // issue an Error(). If it isn't NULL, it will issue a Warning() and set *errorFlag = true + float ParseFloat( bool *errorFlag = NULL ); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); +// RAVEN BEGIN +// rjohnson: added vertex color support to proc files. assume a default RGBA of 0x000000ff + int Parse1DMatrixOpenEnded( int MaxCount, float *m ); +// RAVEN END + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // parse a braced section into a string + const char * ParseBracedSection( idStr &out ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact ( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // retrieves the white space characters before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // returns start index into text buffer of last white space + int GetLastWhiteSpaceStart( void ) const; + // returns end index into text buffer of last white space + int GetLastWhiteSpaceEnd( void ) const; + // set an array with punctuations, NULL restores default C/C++ set, see default_punctuations for an example + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags( void ); + // reset the lexer + void Reset( void ); + // returns true if at the end of the file + int EndOfFile( void ); + // returns the current filename + const char * GetFileName( void ); + // get offset in script + const int GetFileOffset( void ); + // get file time + const unsigned int GetFileTime( void ); + // returns the current line number + const int GetLineNum( void ); + // print an error message + void Error( const char *str, ... ) id_attribute((format(printf,2,3))); + // print a warning message + void Warning( const char *str, ... ) id_attribute((format(printf,2,3))); + // returns true if Error() was called with LEXFL_NOFATALERRORS or LEXFL_NOERRORS set + bool HadError( void ) const; + + static void SetBaseFolder(char const * const); + +// RAVEN BEGIN + // write a binary representation of a token + void WriteBinaryToken(idToken *tok); + static void WriteBinaryFile(char const * const filename); +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added method to parse a structure array that is made up of numerics (floats, ints), and stores them in the given storage + void ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ); +// RAVEN END + +private: + int loaded; // set when a script file is loaded from file or memory + idStr filename; // file name of the script + bool allocated; // true if buffer memory was allocated + const char * buffer; // buffer containing the script + const char * script_p; // current pointer in the script + const char * end_p; // pointer to the end of the script + const char * lastScript_p; // script pointer before reading token + const char * whiteSpaceStart_p; // start of last white space + const char * whiteSpaceEnd_p; // end of last white space + unsigned int fileTime; // file time + int length; // length of the script in bytes + int line; // current line in script + int lastline; // line before reading token + int tokenavailable; // set by unreadToken + int flags; // several script flags + const punctuation_t *punctuations; // the punctuations used in the script + int * punctuationtable; // ASCII table with punctuations + int * nextpunctuation; // next punctuation in chain + idToken token; // available token + idLexer* next; // next script in a chain + bool hadError; // set by idLexer::Error, even if the error is supressed + +protected: + static char baseFolder[256]; + + +private: + void CreatePunctuationTable( const punctuation_t *punctuations ); + int ReadWhiteSpace( void ); + int ReadEscapeCharacter( char *ch ); + int ReadString( idToken *token, int quote ); + int ReadName( idToken *token ); + int ReadNumber( idToken *token ); + int ReadPunctuation( idToken *token ); + int ReadPrimitive( idToken *token ); + int CheckString( const char *str ) const; + int NumLinesCrossed( void ); + +// RAVEN BEGIN +// jsinger: This is the file that WriteBinaryToken will write to when the proper flags are set + idFile *mBinaryFile; +// RAVEN END +}; + +// this class is only necessary to parse binary files +class Lexer { + friend class idParser; + +public: + +#ifdef LEXER_READ_AHEAD + static void BeginLevelLoad(); + static void EndLevelLoad(); +#endif + + // constructors + Lexer( const char *filename, int flags = 0, bool OSPath = false ); + Lexer(int flags=0); + Lexer( char const * const ptr, int length, char const * const name, int flags = 0); + + //destructor + ~Lexer(); + // load a script from the given file at the given offset with the given length + int LoadFile( const char *filename, bool OSPath = false ); + // load a script from the given memory with the given length and a specified line offset, + // so source strings extracted from a file can still refer to proper line numbers in the file + // NOTE: the buffer is expect to be a valid C string: ptr[length] == '\0' + int LoadMemory( const char *ptr, int length, const char *name, int startLine = 1 ); + // free the script + void FreeSource( void ); + // returns true if a script is loaded + int IsLoaded( void ); + // read a token + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true when the token is available + int CheckTokenString( const char *string ); + // returns true an reads the token when a token with the given type is available + int CheckTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine( void ); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // unread the given token + void UnreadToken( const idToken *token ); + // read a token only if on the same line + int ReadTokenOnLine( idToken *token ); + // read a signed integer + int ParseInt( void ); + // read a boolean + bool ParseBool( void ); + // read a floating point number. If errorFlag is NULL, a non-numeric token will + // issue an Error(). If it isn't NULL, it will issue a Warning() and set *errorFlag = true + float ParseFloat( bool *errorFlag = NULL ); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); + int Parse1DMatrixOpenEnded( int MaxCount, float *m ); + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // parse a braced section into a string + const char * ParseBracedSection( idStr &out ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact ( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // retrieves the white space characters before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // returns start index into text buffer of last white space + int GetLastWhiteSpaceStart( void ) const; + // returns end index into text buffer of last white space + int GetLastWhiteSpaceEnd( void ) const; + // set an array with punctuations, NULL restores default C/C++ set, see default_punctuations for an example + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags( void ); + // reset the lexer + void Reset( void ); + // returns true if at the end of the file + int EndOfFile( void ); + // returns the current filename + const char * GetFileName( void ); + // get offset in script + const int GetFileOffset( void ); + // get file time + const unsigned int GetFileTime( void ); + // returns the current line number + const int GetLineNum( void ); + // print an error message + void Error( const char *str, ... ); + // print a warning message + void Warning( const char *str, ... ); + // returns true if Error() was called with LEXFL_NOFATALERRORS or LEXFL_NOERRORS set + bool HadError( void ) const; + + // write a binary representation of a token + void WriteBinaryToken(idToken *tok); + + static void SetBaseFolder(char const * const); + +// RAVEN BEGIN +// dluetscher: added method to parse a structure array that is made up of numerics (floats, ints), and stores them in the given storage + void ParseNumericStructArray( int numStructElements, int tokenSubTypeStructElements[], int arrayCount, byte *arrayStorage ); +// RAVEN END + + // suffix that should be appended to the normal file extension to signify that the file is binary + static idStr const sCompiledFileSuffix; + +protected: + static char baseFolder[256]; + +private: + bool OpenFile(char const *filename, bool OSPath); + +#ifdef LEXER_READ_AHEAD + LexerIOWrapper mLexerIOWrapper; +#else + idFile *mFile; +#endif + + unsigned int offset; + idToken unreadToken; + unsigned int unreadSize; + bool tokenAvailable; + idLexer *mDelegate; + int mFlags; +}; + + + +ID_INLINE const char *idLexer::GetFileName( void ) { + return idLexer::filename; +} + +ID_INLINE const int idLexer::GetFileOffset( void ) { + return idLexer::script_p - idLexer::buffer; +} + +ID_INLINE const unsigned int idLexer::GetFileTime( void ) { + return idLexer::fileTime; +} + +ID_INLINE const int idLexer::GetLineNum( void ) { + return idLexer::line; +} + +ID_INLINE void idLexer::SetFlags( int flags ) { + idLexer::flags = flags; +} + +ID_INLINE int idLexer::GetFlags( void ) { + return idLexer::flags; +} + +#endif /* !__LEXER_H__ */ + diff --git a/src/idlib/LexerFactory.cpp b/src/idlib/LexerFactory.cpp new file mode 100644 index 0000000..57dab6a --- /dev/null +++ b/src/idlib/LexerFactory.cpp @@ -0,0 +1,77 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop +#include "LexerFactory.h" + +LexerFactory::~LexerFactory() +{ +} + +Lexer *LexerFactory::MakeLexer(char const * const filename, int flags, bool OSPath) +{ + return new Lexer(filename, flags | GetReadBinary() | GetWriteBinary(), OSPath); +} + +Lexer *LexerFactory::MakeLexer(int flags) +{ + return new Lexer(flags | GetReadBinary() | GetWriteBinary()); +} + +Lexer *LexerFactory::MakeLexer( char const * const ptr, int length, char const * const name, int flags) +{ + return new Lexer(ptr, length, name, flags | GetWriteBinary() | GetReadBinary()); +} + +int LexerFactory::GetReadBinary() +{ + if(cvarSystem->GetCVarBool("com_binaryRead")) + { + return LEXFL_READBINARY; + } + else + { + return 0; + } +} + +int LexerFactory::GetWriteBinary() +{ + int ret=0; + int writeBinary = cvarSystem->GetCVarInteger("com_binaryWrite"); + switch(writeBinary) + { + case 0: + break; + case 1: + ret = LEXFL_WRITEBINARY; + break; + case 2: + ret = LEXFL_WRITEBINARY | LEXFL_BYTESWAP; + break; + } + + return ret; +} diff --git a/src/idlib/LexerFactory.h b/src/idlib/LexerFactory.h new file mode 100644 index 0000000..458ef17 --- /dev/null +++ b/src/idlib/LexerFactory.h @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LEXERFACTORY_H__ +#define __LEXERFACTORY_H__ + +class LexerFactory +{ +public: + + static Lexer *MakeLexer( int flags ); + static Lexer *MakeLexer( char const * const filename, int flags = 0, bool OSPath = false ); + static Lexer *MakeLexer( char const * const ptr, int length, char const * const name, int flags = 0 ); + +private: + static int GetReadBinary(); + static int GetWriteBinary(); + + // disallow default constructor + LexerFactory(); + ~LexerFactory(); +}; + +#endif diff --git a/src/idlib/Lib.cpp b/src/idlib/Lib.cpp new file mode 100644 index 0000000..52ebe1b --- /dev/null +++ b/src/idlib/Lib.cpp @@ -0,0 +1,591 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#if defined( MACOS_X ) +#include +#include +#include +#endif + +/* +=============================================================================== + + idLib + +=============================================================================== +*/ + +idSys * idLib::sys = NULL; +idCommon * idLib::common = NULL; +idCVarSystem * idLib::cvarSystem = NULL; +idFileSystem * idLib::fileSystem = NULL; +int idLib::frameNumber = 0; + +/* +================ +idLib::Init +================ +*/ +void idLib::Init( void ) { + + // initialize little/big endian conversion + Swap_Init(); + + // initialize memory manager + Mem_Init(); + +// RAVEN BEGIN +// dluetscher: added the following code to initialize each of the memory heaps immediately +// following Mem_Init() +#ifdef _RV_MEM_SYS_SUPPORT + // initialize each of the memory heaps + common->InitHeaps(); +#endif +// RAVEN END + + // init string memory allocator + idStr::InitMemory(); + + // initialize generic SIMD implementation + idSIMD::Init(); + + // initialize math + idMath::Init(); + +// RAVEN BEGIN +// jsinger: There is no reason for us to be doing this on the Xenon +#ifndef _XENON + // test idMatX + //idMatX::Test(); + + // test idPolynomial + idPolynomial::Test(); +#endif +// RAVEN END + + // initialize the dictionary string pools + idDict::Init(); +} + +/* +================ +idLib::ShutDown +================ +*/ +void idLib::ShutDown( void ) { + + // shut down the dictionary string pools + idDict::Shutdown(); + + // shut down the string memory allocator + idStr::ShutdownMemory(); + + // shut down the SIMD engine + idSIMD::Shutdown(); + +// RAVEN BEGIN +// dluetscher: added the following code to shutdown each of the memory heaps immediately +// before Mem_Shutdown() +#ifdef _RV_MEM_SYS_SUPPORT + // shutdown each of the memory heaps + common->ShutdownHeaps(); +#endif +// RAVEN END + + // shut down the memory manager + Mem_Shutdown(); +} + + +/* +=============================================================================== + + Colors + +=============================================================================== +*/ + +idVec4 colorBlack = idVec4( 0.00f, 0.00f, 0.00f, 1.00f ); +idVec4 colorWhite = idVec4( 1.00f, 1.00f, 1.00f, 1.00f ); +idVec4 colorRed = idVec4( 1.00f, 0.00f, 0.00f, 1.00f ); +idVec4 colorGreen = idVec4( 0.00f, 1.00f, 0.00f, 1.00f ); +idVec4 colorBlue = idVec4( 0.00f, 0.00f, 1.00f, 1.00f ); +idVec4 colorYellow = idVec4( 1.00f, 1.00f, 0.00f, 1.00f ); +idVec4 colorMagenta= idVec4( 1.00f, 0.00f, 1.00f, 1.00f ); +idVec4 colorCyan = idVec4( 0.00f, 1.00f, 1.00f, 1.00f ); +idVec4 colorOrange = idVec4( 1.00f, 0.50f, 0.00f, 1.00f ); +idVec4 colorPurple = idVec4( 0.60f, 0.00f, 0.60f, 1.00f ); +idVec4 colorPink = idVec4( 0.73f, 0.40f, 0.48f, 1.00f ); +idVec4 colorBrown = idVec4( 0.40f, 0.35f, 0.08f, 1.00f ); +idVec4 colorLtGrey = idVec4( 0.75f, 0.75f, 0.75f, 1.00f ); +idVec4 colorMdGrey = idVec4( 0.50f, 0.50f, 0.50f, 1.00f ); +idVec4 colorDkGrey = idVec4( 0.25f, 0.25f, 0.25f, 1.00f ); + +static dword colorMask[2] = { 255, 0 }; + +/* +================ +ColorFloatToByte +================ +*/ +ID_INLINE static byte ColorFloatToByte( float c ) { + return (byte) ( ( (dword) ( c * 255.0f ) ) & colorMask[FLOATSIGNBITSET(c)] ); +} + +/* +================ +PackColor +================ +*/ +dword PackColor( const idVec4 &color ) { + dword dw, dx, dy, dz; + + dx = ColorFloatToByte( color.x ); + dy = ColorFloatToByte( color.y ); + dz = ColorFloatToByte( color.z ); + dw = ColorFloatToByte( color.w ); + +// RAVEN BEGIN +// jnewquist: Big endian support +#ifdef _LITTLE_ENDIAN + return ( dx << 0 ) | ( dy << 8 ) | ( dz << 16 ) | ( dw << 24 ); +#else + return ( dx << 24 ) | ( dy << 16 ) | ( dz << 8 ) | ( dw << 0 ); +#endif +// RAVEN END +} + +/* +================ +UnpackColor +================ +*/ +void UnpackColor( const dword color, idVec4 &unpackedColor ) { +// RAVEN BEGIN +// jnewquist: Xenon is big endian +#ifdef _LITTLE_ENDIAN + unpackedColor.Set( ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 24 ) & 255 ) * ( 1.0f / 255.0f ) ); +#else + unpackedColor.Set( ( ( color >> 24 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ) ); +#endif +// RAVEN END +} + +/* +================ +PackColor +================ +*/ +dword PackColor( const idVec3 &color ) { + dword dx, dy, dz; + + dx = ColorFloatToByte( color.x ); + dy = ColorFloatToByte( color.y ); + dz = ColorFloatToByte( color.z ); + +// RAVEN BEGIN +// jnewquist: Xenon is big endian +#ifdef _LITTLE_ENDIAN + return ( dx << 0 ) | ( dy << 8 ) | ( dz << 16 ); +#else + return ( dy << 16 ) | ( dz << 8 ) | ( dx << 0 ); +#endif +// RAVEN END +} + +/* +================ +UnpackColor +================ +*/ +void UnpackColor( const dword color, idVec3 &unpackedColor ) { +// RAVEN BEGIN +// jnewquist: Xenon is big endian +#ifdef _LITTLE_ENDIAN + unpackedColor.Set( ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ) ); +#else + unpackedColor.Set( ( ( color >> 16 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 8 ) & 255 ) * ( 1.0f / 255.0f ), + ( ( color >> 0 ) & 255 ) * ( 1.0f / 255.0f ) ); +#endif +// RAVEN END +} + +/* +=============== +idLib::Error +=============== +*/ +void idLib::Error( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Error( "%s", text ); +} + +/* +=============== +idLib::Warning +=============== +*/ +void idLib::Warning( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Warning( "%s", text ); +} + +/* +=============================================================================== + + Byte order functions + +=============================================================================== +*/ + +// can't just use function pointers, or dll linkage can mess up +static short (*_BigShort)( short l ); +static short (*_LittleShort)( short l ); +static int (*_BigLong)( int l ); +static int (*_LittleLong)( int l ); +static float (*_BigFloat)( float l ); +static float (*_LittleFloat)( float l ); +static void (*_BigRevBytes)( void *bp, int elsize, int elcount ); +static void (*_LittleRevBytes)( void *bp, int elsize, int elcount ); +static void (*_SixtetsForInt)( byte *out, int src ); +static int (*_IntForSixtets)( byte *in ); + +#ifdef _LITTLE_ENDIAN + +short LittleShort( short l ) { return l; } +int LittleLong( int l ) { return l; } +float LittleFloat( float l ) { return l; } +void LittleRevBytes( void *bp, int elsize, int elcount ) {} + +short BigShort( short l ) { return _BigShort( l ); } +int BigLong( int l ) { return _BigLong( l ); } +float BigFloat( float l ) { return _BigFloat( l ); } +void BigRevBytes( void *bp, int elsize, int elcount ) { _BigRevBytes( bp, elsize, elcount ); } + +#else + +short LittleShort( short l ) { return _LittleShort( l ); } +int LittleLong( int l ) { return _LittleLong( l ); } +float LittleFloat( float l ) { return _LittleFloat( l ); } +void LittleRevBytes( void *bp, int elsize, int elcount ) { _LittleRevBytes( bp, elsize, elcount ); } + +short BigShort( short l ) { return l; } +int BigLong( int l ) { return l; } +float BigFloat( float l ) { return l; } +void BigRevBytes( void *bp, int elsize, int elcount ) {} + +#endif + + +void SixtetsForInt( byte *out, int src) { _SixtetsForInt( out, src ); } +int IntForSixtets( byte *in ) { return _IntForSixtets( in ); } + +/* +================ +ShortSwap +================ +*/ +short ShortSwap( short l ) { + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +/* +================ +ShortNoSwap +================ +*/ +short ShortNoSwap( short l ) { + return l; +} + +/* +================ +LongSwap +================ +*/ +int LongSwap ( int l ) { + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +/* +================ +LongNoSwap +================ +*/ +int LongNoSwap( int l ) { + return l; +} + +/* +================ +FloatSwap +================ +*/ +float FloatSwap( float f ) { + union { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +/* +================ +FloatNoSwap +================ +*/ +float FloatNoSwap( float f ) { + return f; +} + +/* +===================================================================== +RevBytesSwap + +Reverses byte order in place. + +INPUTS + bp bytes to reverse + elsize size of the underlying data type + elcount number of elements to swap + +RESULTS + Reverses the byte order in each of elcount elements. +===================================================================== */ +void RevBytesSwap( void *bp, int elsize, int elcount ) { + register unsigned char *p, *q; + + p = ( unsigned char * ) bp; + + if ( elsize == 2 ) { + q = p + 1; + while ( elcount-- ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + p += 2; + q += 2; + } + return; + } + + while ( elcount-- ) { + q = p + elsize - 1; + while ( p < q ) { + *p ^= *q; + *q ^= *p; + *p ^= *q; + ++p; + --q; + } + p += elsize >> 1; + } +} + +/* +================ +RevBytesNoSwap +================ +*/ +void RevBytesNoSwap( void *bp, int elsize, int elcount ) { + return; +} + +/* +================ +SixtetsForIntLittle +================ +*/ +void SixtetsForIntLittle( byte *out, int src) { + byte *b = (byte *)&src; + out[0] = ( b[0] & 0xfc ) >> 2; + out[1] = ( ( b[0] & 0x3 ) << 4 ) + ( ( b[1] & 0xf0 ) >> 4 ); + out[2] = ( ( b[1] & 0xf ) << 2 ) + ( ( b[2] & 0xc0 ) >> 6 ); + out[3] = b[2] & 0x3f; +} + +/* +================ +SixtetsForIntBig +TTimo: untested - that's the version from initial base64 encode +================ +*/ +void SixtetsForIntBig( byte *out, int src) { + for( int i = 0 ; i < 4 ; i++ ) { + out[i] = src & 0x3f; + src >>= 6; + } +} + +/* +================ +IntForSixtetsLittle +================ +*/ +int IntForSixtetsLittle( byte *in ) { + int ret = 0; + byte *b = (byte *)&ret; + b[0] |= in[0] << 2; + b[0] |= ( in[1] & 0x30 ) >> 4; + b[1] |= ( in[1] & 0xf ) << 4; + b[1] |= ( in[2] & 0x3c ) >> 2; + b[2] |= ( in[2] & 0x3 ) << 6; + b[2] |= in[3]; + return ret; +} + +/* +================ +IntForSixtetsBig +TTimo: untested - that's the version from initial base64 decode +================ +*/ +int IntForSixtetsBig( byte *in ) { + int ret = 0; + ret |= in[0]; + ret |= in[1] << 6; + ret |= in[2] << 2*6; + ret |= in[3] << 3*6; + return ret; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init( void ) { + byte swaptest[2] = {1,0}; + + // set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) { + // little endian ex: x86 + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + _BigRevBytes = RevBytesSwap; + _LittleRevBytes = RevBytesNoSwap; + _SixtetsForInt = SixtetsForIntLittle; + _IntForSixtets = IntForSixtetsLittle; + } else { + // big endian ex: ppc + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + _BigRevBytes = RevBytesNoSwap; + _LittleRevBytes = RevBytesSwap; + _SixtetsForInt = SixtetsForIntBig; + _IntForSixtets = IntForSixtetsBig; + } +} + +/* +========== +Swap_IsBigEndian +========== +*/ +bool Swap_IsBigEndian( void ) { + byte swaptest[2] = {1,0}; + return *(short *)swaptest != 1; +} + +/* +=============================================================================== + + Assertion + +=============================================================================== +*/ + +void AssertFailed( const char *file, int line, const char *expression ) { + if ( idLib::sys ) { + idLib::sys->DebugPrintf( "\n\nASSERTION FAILED!\n%s(%d): '%s'\n", file, line, expression ); + } +#ifdef Q4_RECON_ENGINE_PRIVATE + // Preserve assertions in the normal engine log as well as the debugger + // stream so unattended reconstruction smoke tests retain the exact site. + if ( idLib::common ) { + idLib::common->Warning( "ASSERTION FAILED: %s(%d): '%s'", file, line, expression ); + } +#endif +#ifdef _WIN32 +// RAVEN BEGIN +// jnewquist: Visual Studio platform independent breakpoint + __debugbreak(); +// RAVEN END +#elif defined( __linux__ ) + __asm__ __volatile__ ("int $0x03"); +#elif defined( MACOS_X ) + kill( getpid(), SIGINT ); +#endif +} diff --git a/src/idlib/Lib.h b/src/idlib/Lib.h new file mode 100644 index 0000000..755b46d --- /dev/null +++ b/src/idlib/Lib.h @@ -0,0 +1,354 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LIB_H__ +#define __LIB_H__ + + +/* +=============================================================================== + + idLib contains stateless support classes and concrete types. Some classes + do have static variables, but such variables are initialized once and + read-only after initialization (they do not maintain a modifiable state). + + The interface pointers idSys, idCommon, idCVarSystem and idFileSystem + should be set before using idLib. The pointers stored here should not + be used by any part of the engine except for idLib. + + The frameNumber should be continuously set to the number of the current + frame if frame base memory logging is required. + +=============================================================================== +*/ + +class idLib { +public: + static class idSys * sys; + static class idCommon * common; + static class idCVarSystem * cvarSystem; + static class idFileSystem * fileSystem; + static int frameNumber; + + static void Init( void ); + static void ShutDown( void ); + + // wrapper to idCommon functions + static void Error( const char *fmt, ... ); + static void Warning( const char *fmt, ... ); +}; + + +/* +=============================================================================== + + Asserts and Exceptions + +=============================================================================== +*/ + +/* +The verify(x) macro just returns true or false in release mode, but breaks +in debug mode. That way the code can take a non-fatal path in release mode +if something that's not supposed to happen happens. + +if ( !verify(game) ) { + // This should never happen! + return; +} +*/ + +#ifdef _DEBUG + + #define ID_CONDITIONAL_ASSERT + + void AssertFailed( const char *file, int line, const char *expression ); + #undef assert + #ifdef ID_CONDITIONAL_ASSERT + // lets you disable an assertion at runtime when needed + // could extend this to count and produce an assert log - useful for 'release with asserts' builds + #define assert( x ) \ + { \ + volatile static bool assert_enabled = true; \ + if ( assert_enabled ) { \ + if ( x ) { } else AssertFailed( __FILE__, __LINE__, #x ); \ + } \ + } + #define verify( x ) \ + ( \ + ( ( x ) ? true : ( \ + ( { \ + volatile static bool assert_enabled = true; \ + if ( assert_enabled ) { AssertFailed( __FILE__, __LINE__, #x ); } \ + } ) \ + , false ) ) \ + ) + #else + #define assert( x ) if ( x ) { } else AssertFailed( __FILE__, __LINE__, #x ) + #define verify( x ) ( ( x ) ? true : ( AssertFailed( __FILE__, __LINE__, #x ), false ) ) + #endif + +#else + + #undef assert + #define assert( x ) + #define verify( x ) ( ( x ) ? true : false ) + +#endif + +#define assert_8_byte_aligned( pointer ) assert( (((UINT_PTR)(pointer))&7) == 0 ); +#define assert_16_byte_aligned( pointer ) assert( (((UINT_PTR)(pointer))&15) == 0 ); +#define assert_32_byte_aligned( pointer ) assert( (((UINT_PTR)(pointer))&31) == 0 ); + +#ifndef __TYPE_INFO_GEN__ +#define compile_time_assert( x ) { typedef int compile_time_assert_failed[(x) ? 1 : -1]; } +#define file_scoped_compile_time_assert( x ) extern int compile_time_assert_failed[(x) ? 1 : -1] +#define assert_sizeof( type, size ) file_scoped_compile_time_assert( sizeof( type ) == size ) +#define assert_offsetof( type, field, offset ) file_scoped_compile_time_assert( offsetof( type, field ) == offset ) +#define assert_sizeof_8_byte_multiple( type ) file_scoped_compile_time_assert( ( sizeof( type ) & 8 ) == 0 ) +#define assert_sizeof_16_byte_multiple( type ) file_scoped_compile_time_assert( ( sizeof( type ) & 15 ) == 0 ) +#define assert_sizeof_32_byte_multiple( type ) file_scoped_compile_time_assert( ( sizeof( type ) & 31 ) == 0 ) +#else +#define compile_time_assert( x ) +#define file_scoped_compile_time_assert( x ) +#define assert_sizeof( type, size ) +#define assert_offsetof( type, field, offset ) +#define assert_sizeof_16_byte_multiple( type ) +#endif + +class idException { +public: + char error[2048]; + + idException( const char *text = "" ) { strcpy( error, text ); } +}; + + +/* +=============================================================================== + + Types and defines used throughout the engine. + +=============================================================================== +*/ + +typedef unsigned char byte; // 8 bits +typedef unsigned short word; // 16 bits +typedef unsigned int dword; // 32 bits +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef int qhandle_t; + +class idFile; +class idVec3; +#ifdef _XENON +#define ID_VEC4_ALIGN __declspec(align(16)) +#else +#define ID_VEC4_ALIGN +#endif +class ID_VEC4_ALIGN idVec4; + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef BIT +#define BIT( num ) ( 1 << ( num ) ) +#endif + +#define MAX_STRING_CHARS 1024 // max length of a string + +// maximum world size +#define MAX_WORLD_COORD ( 128 * 1024 ) +#define MIN_WORLD_COORD ( -128 * 1024 ) +#define MAX_WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +// basic colors +extern ID_VEC4_ALIGN idVec4 colorBlack; +extern ID_VEC4_ALIGN idVec4 colorWhite; +extern ID_VEC4_ALIGN idVec4 colorRed; +extern ID_VEC4_ALIGN idVec4 colorGreen; +extern ID_VEC4_ALIGN idVec4 colorBlue; +extern ID_VEC4_ALIGN idVec4 colorYellow; +extern ID_VEC4_ALIGN idVec4 colorMagenta; +extern ID_VEC4_ALIGN idVec4 colorCyan; +extern ID_VEC4_ALIGN idVec4 colorOrange; +extern ID_VEC4_ALIGN idVec4 colorPurple; +extern ID_VEC4_ALIGN idVec4 colorPink; +extern ID_VEC4_ALIGN idVec4 colorBrown; +extern ID_VEC4_ALIGN idVec4 colorLtGrey; +extern ID_VEC4_ALIGN idVec4 colorMdGrey; +extern ID_VEC4_ALIGN idVec4 colorDkGrey; + +// packs color floats in the range [0,1] into an integer +dword PackColor( const idVec3 &color ); +void UnpackColor( const dword color, idVec3 &unpackedColor ); +dword PackColor( const idVec4 &color ); +void UnpackColor( const dword color, idVec4 &unpackedColor ); + +// little/big endian conversion +short BigShort( short l ); +short LittleShort( short l ); +int BigLong( int l ); +int LittleLong( int l ); +float BigFloat( float l ); +float LittleFloat( float l ); +void BigRevBytes( void *bp, int elsize, int elcount ); +void LittleRevBytes( void *bp, int elsize, int elcount ); +void Swap_Init( void ); + +bool Swap_IsBigEndian( void ); + +// for base64 +void SixtetsForInt( byte *out, int src); +int IntForSixtets( byte *in ); + + +/* +=============================================================================== + + idLib headers. + +=============================================================================== +*/ + +#include "math/Math.h" + +// memory management and arrays +#include "Heap.h" + +// RAVEN BEGIN +// dluetscher: added includes for new heap/memory management system +#ifdef _RV_MEM_SYS_SUPPORT +#include "rvHeapArena.h" +#include "rvHeap.h" +#include "rvMemSys.h" +#endif +// RAVEN END + +#include "containers/List.h" + +// math +#include "math/Simd.h" +#include "math/Random.h" +#include "math/Complex.h" +#include "math/Vector.h" +#include "math/Matrix.h" +#include "math/Mat3x4.h" +#include "math/Angles.h" +#include "math/Quat.h" +#include "math/Rotation.h" +#include "math/Plane.h" +#include "math/Pluecker.h" +#include "math/Polynomial.h" +#include "math/Extrapolate.h" +#include "math/Interpolate.h" +#include "math/Curve.h" +#include "math/Ode.h" +#include "math/Lcp.h" +// RAVEN BEGIN +#include "math/Radians.h" +#include "math/FFT.h" +// RAVEN END + +// bounding volumes +#include "bv/Sphere.h" +#include "bv/Bounds.h" +#include "bv/Box.h" +#include "bv/Frustum.h" + +// geometry +#include "geometry/DrawVert.h" +#include "geometry/JointTransform.h" +#include "geometry/Winding.h" +#include "geometry/Winding2D.h" +#include "geometry/Surface.h" +#include "geometry/Surface_Patch.h" +#include "geometry/Surface_Polytope.h" +#include "geometry/Surface_SweptSpline.h" +#include "geometry/TraceModel.h" + +// RAVEN BEGIN +// dluetscher: added some headers for new vertex formats +#ifdef _MD5R_SUPPORT +#include "geometry/rvVertex.h" +#endif +// RAVEN END + +// text manipulation +#include "Str.h" +#include "Token.h" +#include "Lexer.h" +#include "Parser.h" +#include "Base64.h" +#include "CmdArgs.h" + +// containers +#include "containers/BTree.h" +#include "containers/BinSearch.h" +#include "containers/HashIndex.h" +#include "containers/HashTable.h" +#include "containers/StaticList.h" +#include "containers/LinkList.h" +#include "containers/Hierarchy.h" +#include "containers/Queue.h" +#include "containers/Stack.h" +#include "containers/StrList.h" +#include "containers/StrPool.h" +#include "containers/VectorSet.h" +#include "containers/PlaneSet.h" +#include "containers/rvBlockPool.h" +// RAVEN BEGIN +// ddynerman: a pair +#include "containers/Pair.h" +// ddynerman: algorithms +#include "algorithms/MultifieldSort.h" +// RAVEN END + +// hashing +#include "hashing/CRC8.h" +#include "hashing/CRC16.h" +#include "hashing/CRC32.h" +#include "hashing/Honeyman.h" +#include "hashing/MD4.h" +#include "hashing/MD5.h" + +// misc +#include "Dict.h" +#include "LangDict.h" +#include "BitMsg.h" +#include "MapFile.h" +#include "Timer.h" +// RAVEN BEGIN +// jsinger: xenon needs these because the simd class is no longer virtual +#ifdef _XENON +#include "math/Simd_generic.h" +#include "math/Simd_Xenon.h" +#include "Threads/ThreadEventManager.h" +#include "Threads/WorkerThreadManager.h" +#endif +// RAVEN END + +#endif /* !__LIB_H__ */ diff --git a/src/idlib/MapFile.h b/src/idlib/MapFile.h new file mode 100644 index 0000000..4e2f24c --- /dev/null +++ b/src/idlib/MapFile.h @@ -0,0 +1,290 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MAPFILE_H__ +#define __MAPFILE_H__ + +/* +=============================================================================== + + Reads or writes the contents of .map files into a standard internal + format, which can then be moved into private formats for collision + detection, map processing, or editor use. + + No validation (duplicate planes, null area brushes, etc) is performed. + There are no limits to the number of any of the elements in maps. + The order of entities, brushes, and sides is maintained. + +=============================================================================== +*/ + +const int OLD_MAP_VERSION = 1; +const int CURRENT_MAP_VERSION = 3; +const int DEFAULT_CURVE_SUBDIVISION = 4; +const float DEFAULT_CURVE_MAX_ERROR = 4.0f; +const float DEFAULT_CURVE_MAX_ERROR_CD = 24.0f; +const float DEFAULT_CURVE_MAX_LENGTH = -1.0f; +const float DEFAULT_CURVE_MAX_LENGTH_CD = -1.0f; + + +class idMapPrimitive { +public: + enum { TYPE_INVALID = -1, TYPE_BRUSH, TYPE_PATCH }; + + idDict epairs; + + idMapPrimitive( void ) { type = TYPE_INVALID; } + virtual ~idMapPrimitive( void ) { } + int GetType( void ) const { return type; } + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + virtual void AdjustOrigin( idVec3 &delta ) { } +// RAVEN END + +protected: + int type; +}; + + +class idMapBrushSide { + friend class idMapBrush; + +public: + idMapBrushSide( void ); + ~idMapBrushSide( void ) { } + const char * GetMaterial( void ) const { return material; } + void SetMaterial( const char *p ) { material = p; } + const idPlane & GetPlane( void ) const { return plane; } + void SetPlane( const idPlane &p ) { plane = p; } + void SetTextureMatrix( const idVec3 mat[2] ) { texMat[0] = mat[0]; texMat[1] = mat[1]; } + void GetTextureMatrix( idVec3 &mat1, idVec3 &mat2 ) { mat1 = texMat[0]; mat2 = texMat[1]; } + void GetTextureVectors( idVec4 v[2] ) const; + +protected: + idStr material; + idPlane plane; + idVec3 texMat[2]; + idVec3 origin; +}; + +ID_INLINE idMapBrushSide::idMapBrushSide( void ) { + plane.Zero(); + texMat[0].Zero(); + texMat[1].Zero(); + origin.Zero(); +} + + +class idMapBrush : public idMapPrimitive { +public: + idMapBrush( void ) { type = TYPE_BRUSH; sides.Resize( 8, 4 ); } + ~idMapBrush( void ) { sides.DeleteContents( true ); } +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + static idMapBrush * Parse( Lexer &src, const idVec3 &origin, bool newFormat = true, int version = CURRENT_MAP_VERSION ); + static idMapBrush * ParseQ3( Lexer &src, const idVec3 &origin ); +// RAVEN END + bool Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const; + int GetNumSides( void ) const { return sides.Num(); } + int AddSide( idMapBrushSide *side ) { return sides.Append( side ); } + idMapBrushSide * GetSide( int i ) const { return sides[i]; } + unsigned int GetGeometryCRC( void ) const; + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + virtual void AdjustOrigin( idVec3 &delta ); +// RAVEN END + +protected: + idList sides; +}; + + +class idMapPatch : public idMapPrimitive, public idSurface_Patch { +public: + idMapPatch( void ); + idMapPatch( int maxPatchWidth, int maxPatchHeight ); + ~idMapPatch( void ) { } +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + static idMapPatch * Parse( Lexer &src, const idVec3 &origin, bool patchDef3 = true, int version = CURRENT_MAP_VERSION ); +// RAVEN END + bool Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const; + const char * GetMaterial( void ) const { return material; } + void SetMaterial( const char *p ) { material = p; } + int GetHorzSubdivisions( void ) const { return horzSubdivisions; } + int GetVertSubdivisions( void ) const { return vertSubdivisions; } + bool GetExplicitlySubdivided( void ) const { return explicitSubdivisions; } + void SetHorzSubdivisions( int n ) { horzSubdivisions = n; } + void SetVertSubdivisions( int n ) { vertSubdivisions = n; } + void SetExplicitlySubdivided( bool b ) { explicitSubdivisions = b; } + unsigned int GetGeometryCRC( void ) const; + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + virtual void AdjustOrigin( idVec3 &delta ); +// RAVEN END + +protected: + idStr material; + int horzSubdivisions; + int vertSubdivisions; + bool explicitSubdivisions; +}; + +ID_INLINE idMapPatch::idMapPatch( void ) { + type = TYPE_PATCH; + horzSubdivisions = vertSubdivisions = 0; + explicitSubdivisions = false; + width = height = 0; + maxWidth = maxHeight = 0; + expanded = false; +} + +ID_INLINE idMapPatch::idMapPatch( int maxPatchWidth, int maxPatchHeight ) { + type = TYPE_PATCH; + horzSubdivisions = vertSubdivisions = 0; + explicitSubdivisions = false; + width = height = 0; + maxWidth = maxPatchWidth; + maxHeight = maxPatchHeight; + verts.SetNum( maxWidth * maxHeight ); + expanded = false; +} + + +class idMapEntity { + friend class idMapFile; + +public: + idDict epairs; + +public: + idMapEntity( void ) { epairs.SetHashSize( 64 ); } + ~idMapEntity( void ) { primitives.DeleteContents( true ); } +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + static idMapEntity * Parse( Lexer &src, bool worldSpawn = false, int version = CURRENT_MAP_VERSION ); +// RAVEN END + bool Write( idFile *fp, int entityNum ) const; + int GetNumPrimitives( void ) const { return primitives.Num(); } + idMapPrimitive * GetPrimitive( int i ) const { return primitives[i]; } + void AddPrimitive( idMapPrimitive *p ) { primitives.Append( p ); } + unsigned int GetGeometryCRC( void ) const; + void RemovePrimitiveData(); + +protected: + idList primitives; +}; + + +class idMapFile { +public: + idMapFile( void ); +// RAVEN BEGIN +// rhummer: moved body to mapfile.cpp to help debug some odd issues with func_groups + ~idMapFile( void ); /*{ entities.DeleteContents( true ); }*/ +// RAVEN END + + // filename does not require an extension + // normally this will use a .reg file instead of a .map file if it exists, + // which is what the game and dmap want, but the editor will want to always + // load a .map file + bool Parse( const char *filename, bool ignoreRegion = false, bool osPath = false ); + +// RAVEN BEGIN +// rjohnson: added resolve + void Resolve( void ); +// rhummer: added boolean to dictate if the Resolve function has been run on this map. + bool HasBeenResloved() { return mHasBeenResolved; } +// rjohnson: added export + bool Write( const char *fileName, const char *ext, bool fromBasePath = true, bool exportOnly = false ); +// RAVEN END + + // get the number of entities in the map + int GetNumEntities( void ) const { return entities.Num(); } + // get the specified entity + idMapEntity * GetEntity( int i ) const { return entities[i]; } + // get the name without file extension + const char * GetName( void ) const { return name; } + // get the file time + unsigned int GetFileTime( void ) const { return fileTime; } + // get CRC for the map geometry + // texture coordinates and entity key/value pairs are not taken into account + unsigned int GetGeometryCRC( void ) const { return geometryCRC; } + // returns true if the file on disk changed + bool NeedsReload(); + + int AddEntity( idMapEntity *mapentity ); + idMapEntity * FindEntity( const char *name ); + void RemoveEntity( idMapEntity *mapEnt ); + void RemoveEntities( const char *classname ); + void RemoveAllEntities(); + void RemovePrimitiveData(); + bool HasPrimitiveData() { return hasPrimitiveData; } + +// RAVEN BEGIN +// rjohnson: added export + bool WriteExport( const char *fileName, bool fromBasePath = true ); + bool ParseExport( const char *filename, bool osPath = false ); + + bool HasExportEntities(void) { return mHasExportEntities; } +// RAVEN END + +protected: + int version; + unsigned int fileTime; + unsigned int geometryCRC; + idList entities; + idStr name; + bool hasPrimitiveData; + +// RAVEN BEGIN +// rjohnson: added export + idList mExportEntities; + bool mHasExportEntities; +// rhummer: Added to inform if func_groups some how disappeared between the loading and saving of the map file. + bool mHasFuncGroups; +// rhummer: Added to notify that this map has been resloved, so func_groups have been removed. + bool mHasBeenResolved; +// RAVEN END + +private: + void SetGeometryCRC( void ); +}; + +ID_INLINE idMapFile::idMapFile( void ) { + version = CURRENT_MAP_VERSION; + fileTime = 0; + geometryCRC = 0; + entities.Resize( 1024, 256 ); + hasPrimitiveData = false; +// RAVEN BEGIN +// rhummer: Used to make sure func_groups don't disappear. + mHasFuncGroups = false; +// RAVEN END +} + +#endif /* !__MAPFILE_H__ */ diff --git a/src/idlib/Parser.cpp b/src/idlib/Parser.cpp new file mode 100644 index 0000000..7bbbe64 --- /dev/null +++ b/src/idlib/Parser.cpp @@ -0,0 +1,3243 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +//#define DEBUG_EVAL +#define MAX_DEFINEPARMS 128 +#define DEFINEHASHSIZE 2048 + +#define TOKEN_FL_RECURSIVE_DEFINE 1 + +define_t * idParser::globaldefines; + +/* +================ +idParser::SetBaseFolder +================ +*/ +void idParser::SetBaseFolder( const char *path) { +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files + Lexer::SetBaseFolder(path); +// RAVEN END +} + +/* +================ +idParser::AddGlobalDefine +================ +*/ +int idParser::AddGlobalDefine( const char *string ) { + define_t *define; + + define = idParser::DefineFromString(string); + if (!define) { + return false; + } + define->next = globaldefines; + globaldefines = define; + return true; +} + +/* +================ +idParser::RemoveGlobalDefine +================ +*/ +int idParser::RemoveGlobalDefine( const char *name ) { + define_t *d, *prev; + + for ( prev = NULL, d = idParser::globaldefines; d; prev = d, d = d->next ) { + if ( !idStr::Cmp( d->name, name ) ) { + break; + } + } + if ( d ) { + if ( prev ) { + prev->next = d->next; + } + else { + idParser::globaldefines = d->next; + } + idParser::FreeDefine( d ); + return true; + } + return false; +} + +/* +================ +idParser::RemoveAllGlobalDefines +================ +*/ +void idParser::RemoveAllGlobalDefines( void ) { + define_t *define; + + for ( define = globaldefines; define; define = globaldefines ) { + globaldefines = globaldefines->next; + idParser::FreeDefine(define); + } +} + + +/* +=============================================================================== + +idParser + +=============================================================================== +*/ + +/* +================ +idParser::PrintDefine +================ +*/ +void idParser::PrintDefine( define_t *define ) { + idLib::common->Printf("define->name = %s\n", define->name); + idLib::common->Printf("define->flags = %d\n", define->flags); + idLib::common->Printf("define->builtin = %d\n", define->builtin); + idLib::common->Printf("define->numparms = %d\n", define->numparms); +} + +/* +================ +PC_PrintDefineHashTable +================ +* / +static void PC_PrintDefineHashTable(define_t **definehash) { + int i; + define_t *d; + + for (i = 0; i < DEFINEHASHSIZE; i++) { + Log_Write("%4d:", i); + for (d = definehash[i]; d; d = d->hashnext) { + Log_Write(" %s", d->name); + } + Log_Write("\n"); + } +} +*/ + +/* +================ +PC_NameHash +================ +*/ +ID_INLINE int PC_NameHash( const char *name ) { + int hash, i; + + hash = 0; + for ( i = 0; name[i] != '\0'; i++ ) { + hash += name[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} + +/* +================ +idParser::AddDefineToHash +================ +*/ +void idParser::AddDefineToHash( define_t *define, define_t **definehash ) { + int hash; + + hash = PC_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; +} + +/* +================ +FindHashedDefine +================ +*/ +define_t *idParser::FindHashedDefine( define_t **definehash, const char *name ) { + define_t *d; + int hash; + + hash = PC_NameHash(name); + for ( d = definehash[hash]; d; d = d->hashnext ) { + if ( !idStr::Cmp(d->name, name) ) { + return d; + } + } + return NULL; +} + +/* +================ +idParser::FindDefine +================ +*/ +define_t *idParser::FindDefine( define_t *defines, const char *name ) { + define_t *d; + + for ( d = defines; d; d = d->next ) { + if ( !idStr::Cmp(d->name, name) ) { + return d; + } + } + return NULL; +} + +/* +================ +idParser::FindDefineParm +================ +*/ +int idParser::FindDefineParm( define_t *define, const char *name ) { + idToken *p; + int i; + + i = 0; + for ( p = define->parms; p; p = p->next ) { + if ( (*p) == name ) { + return i; + } + i++; + } + return -1; +} + +/* +================ +idParser::CopyDefine +================ +*/ +define_t *idParser::CopyDefine( define_t *define ) { + define_t *newdefine; + idToken *token, *newtoken, *lasttoken; +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + newdefine = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(define->name) + 1, MA_PARSER); +//RAVEN END + //copy the define name + newdefine->name = (char *) newdefine + sizeof(define_t); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) { + newtoken = new idToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) { + newtoken = new idToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } + return newdefine; +} + +/* +================ +idParser::FreeDefine +================ +*/ +void idParser::FreeDefine( define_t *define ) { + idToken *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) { + next = t->next; + delete t; + } + //free the define tokens + for (t = define->tokens; t; t = next) { + next = t->next; + delete t; + } + //free the define + Mem_Free( define ); +} + +/* +================ +idParser::DefineFromString +================ +*/ +define_t *idParser::DefineFromString( const char *string ) { + idParser src; + define_t *def; + + if ( !src.LoadMemory(string, strlen(string), "*defineString") ) { + return NULL; + } + // create a define from the source + if ( !src.Directive_define() ) { + src.FreeSource(); + return NULL; + } + def = src.CopyFirstDefine(); + src.FreeSource(); + //if the define was created succesfully + return def; +} + +/* +================ +idParser::Error +================ +*/ +void idParser::Error( const char *str, ... ) const { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + if ( idParser::scriptstack ) { + idParser::scriptstack->Error( text ); + } +} + +/* +================ +idParser::Warning +================ +*/ +void idParser::Warning( const char *str, ... ) const { + char text[MAX_STRING_CHARS]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); + if ( idParser::scriptstack ) { + idParser::scriptstack->Warning( text ); + } +} + +/* +================ +idParser::PushIndent +================ +*/ +void idParser::PushIndent( int type, int skip ) { + indent_t *indent; + +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + indent = (indent_t *) Mem_Alloc(sizeof(indent_t),MA_PARSER); +//RAVEN END + indent->type = type; + indent->script = idParser::scriptstack; + indent->skip = (skip != 0); + idParser::skip += indent->skip; + indent->next = idParser::indentstack; + idParser::indentstack = indent; +} + +/* +================ +idParser::PopIndent +================ +*/ +void idParser::PopIndent( int *type, int *skip ) { + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = idParser::indentstack; + if (!indent) return; + + // must be an indent from the current script + if (idParser::indentstack->script != idParser::scriptstack) { + return; + } + + *type = indent->type; + *skip = indent->skip; + idParser::indentstack = idParser::indentstack->next; + idParser::skip -= indent->skip; + Mem_Free( indent ); +} + +/* +================ +idParser::PushScript +================ +*/ +void idParser::PushScript( idLexer *script ) { + idLexer *s; + + for ( s = idParser::scriptstack; s; s = s->next ) { + if ( !idStr::Icmp(s->GetFileName(), script->GetFileName()) ) { + idParser::Warning( "'%s' recursively included", script->GetFileName() ); + return; + } + } + //push the script on the script stack + script->next = idParser::scriptstack; + idParser::scriptstack = script; +} + +/* +================ +idParser::ReadSourceToken +================ +*/ +int idParser::ReadSourceToken( idToken *token ) { + idToken *t; + idLexer *script; + int type, skip, changedScript; + + if ( !idParser::scriptstack ) { + idLib::common->FatalError( "idParser::ReadSourceToken: not loaded" ); + return false; + } + changedScript = 0; + // if there's no token already available + while( !idParser::tokens ) { + // if there's a token to read from the script + if ( idParser::scriptstack->ReadToken( token ) ) { + token->linesCrossed += changedScript; + + // set the marker based on the start of the token read in + if ( !marker_p ) { + marker_p = token->whiteSpaceEnd_p; + } + return true; + } + // if at the end of the script + if ( idParser::scriptstack->EndOfFile() ) { + // remove all indents of the script + while( idParser::indentstack && idParser::indentstack->script == idParser::scriptstack ) { + idParser::Warning( "missing #endif" ); + idParser::PopIndent( &type, &skip ); + } + changedScript = 1; + } + // if this was the initial script + if ( !idParser::scriptstack->next ) { + return false; + } + // remove the script and return to the previous one + script = idParser::scriptstack; + idParser::scriptstack = idParser::scriptstack->next; + delete script; + } + // copy the already available token + *token = idParser::tokens; + // remove the token from the source + t = idParser::tokens; + idParser::tokens = idParser::tokens->next; + delete t; + return true; +} + +/* +================ +idParser::UnreadSourceToken +================ +*/ +int idParser::UnreadSourceToken( idToken *token ) { + idToken *t; + + t = new idToken(token); + t->next = idParser::tokens; + idParser::tokens = t; + return true; +} + +/* +================ +idParser::ReadDefineParms +================ +*/ +int idParser::ReadDefineParms( define_t *define, idToken **parms, int maxparms ) { + define_t *newdefine; + idToken token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "define '%s' missing parameters", define->name ); + return false; + } + + if ( define->numparms > maxparms ) { + idParser::Error( "define with more than %d parameters", maxparms ); + return false; + } + + for ( i = 0; i < define->numparms; i++ ) { + parms[i] = NULL; + } + // if no leading "(" + if ( token != "(" ) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "define '%s' missing parameters", define->name ); + return false; + } + // read the define parameters + for ( done = 0, numparms = 0, indent = 1; !done; ) { + if ( numparms >= maxparms ) { + idParser::Error( "define '%s' with too many parameters", define->name ); + return false; + } + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while( !done ) { + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "define '%s' incomplete", define->name ); + return false; + } + + if ( token == "," ) { + if ( indent <= 1 ) { + if ( lastcomma ) { + idParser::Warning( "too many comma's" ); + } + if ( numparms >= define->numparms ) { + idParser::Warning( "too many define parameters" ); + } + lastcomma = 1; + break; + } + } + else if ( token == "(" ) { + indent++; + } + else if ( token == ")" ) { + indent--; + if ( indent <= 0 ) { + if ( !parms[define->numparms-1] ) { + idParser::Warning( "too few define parameters" ); + } + done = 1; + break; + } + } + else if ( token.type == TT_NAME ) { + newdefine = FindHashedDefine( idParser::definehash, token.c_str() ); + if ( newdefine ) { + if ( !idParser::ExpandDefineIntoSource( &token, newdefine ) ) { + return false; + } + continue; + } + } + + lastcomma = 0; + + if ( numparms < define->numparms ) { + + t = new idToken( token ); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } + } + numparms++; + } + return true; +} + +/* +================ +idParser::StringizeTokens +================ +*/ +int idParser::StringizeTokens( idToken *tokens, idToken *token ) { + idToken *t; + + token->type = TT_STRING; + token->whiteSpaceStart_p = NULL; + token->whiteSpaceEnd_p = NULL; + (*token) = ""; + for ( t = tokens; t; t = t->next ) { + token->Append( t->c_str() ); + } + return true; +} + +/* +================ +idParser::MergeTokens +================ +*/ +int idParser::MergeTokens( idToken *t1, idToken *t2 ) { + // merging of a name with a name or number + if ( t1->type == TT_NAME && (t2->type == TT_NAME || (t2->type == TT_NUMBER && !(t2->subtype & TT_FLOAT))) ) { + t1->Append( t2->c_str() ); + return true; + } + // merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) { + t1->Append( t2->c_str() ); + return true; + } + // merging of two numbers + if ( t1->type == TT_NUMBER && t2->type == TT_NUMBER && + !(t1->subtype & (TT_HEX|TT_BINARY)) && !(t2->subtype & (TT_HEX|TT_BINARY)) && + (!(t1->subtype & TT_FLOAT) || !(t2->subtype & TT_FLOAT)) ) { + t1->Append( t2->c_str() ); + return true; + } + + return false; +} + +/* +================ +idParser::AddBuiltinDefines +================ +*/ +void idParser::AddBuiltinDefines( void ) { + int i; + define_t *define; + struct builtin + { + char *string; + int id; + } builtin[] = { + { "__LINE__", BUILTIN_LINE }, + { "__FILE__", BUILTIN_FILE }, + { "__DATE__", BUILTIN_DATE }, + { "__TIME__", BUILTIN_TIME }, + { "__STDC__", BUILTIN_STDC }, + { NULL, 0 } + }; + + for (i = 0; builtin[i].string; i++) { +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + define = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(builtin[i].string) + 1,MA_PARSER); +//RAVEN END + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, builtin[i].string); + define->flags = DEFINE_FIXED; + define->builtin = builtin[i].id; + define->numparms = 0; + define->parms = NULL; + define->tokens = NULL; + // add the define to the source + AddDefineToHash(define, idParser::definehash); + } +} + +/* +================ +idParser::CopyFirstDefine +================ +*/ +define_t *idParser::CopyFirstDefine( void ) { + int i; + + for ( i = 0; i < DEFINEHASHSIZE; i++ ) { + if ( idParser::definehash[i] ) { + return CopyDefine(idParser::definehash[i]); + } + } + return NULL; +} + +/* +================ +idParser::ExpandBuiltinDefine +================ +*/ +int idParser::ExpandBuiltinDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { + idToken *token; + time_t t; + char *curtime; + char buf[MAX_STRING_CHARS]; + + token = new idToken(deftoken); + switch( define->builtin ) { + case BUILTIN_LINE: { + sprintf( buf, "%d", deftoken->line ); + (*token) = buf; + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER | TT_VALUESVALID; + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_FILE: { + (*token) = idParser::scriptstack->GetFileName(); + token->type = TT_NAME; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_DATE: { + t = time(NULL); + curtime = ctime(&t); + (*token) = "\""; + token->Append( curtime+4 ); + token[7] = '\0'; + token->Append( curtime+20 ); + token[10] = '\0'; + token->Append( "\"" ); + free(curtime); + token->type = TT_STRING; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_TIME: { + t = time(NULL); + curtime = ctime(&t); + (*token) = "\""; + token->Append( curtime+11 ); + token[8] = '\0'; + token->Append( "\"" ); + free(curtime); + token->type = TT_STRING; + token->subtype = token->Length(); + token->line = deftoken->line; + token->linesCrossed = deftoken->linesCrossed; + token->flags = 0; + *firsttoken = token; + *lasttoken = token; + break; + } + case BUILTIN_STDC: { + idParser::Warning( "__STDC__ not supported\n" ); + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + default: { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } + } + return true; +} + +/* +================ +idParser::ExpandDefine +================ +*/ +int idParser::ExpandDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { + idToken *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + idToken *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + // if it is a builtin define + if ( define->builtin ) { + return idParser::ExpandBuiltinDefine( deftoken, define, firsttoken, lasttoken ); + } + // if the define has parameters + if ( define->numparms ) { + if ( !idParser::ReadDefineParms( define, parms, MAX_DEFINEPARMS ) ) { + return false; + } +#ifdef DEBUG_EVAL + for ( i = 0; i < define->numparms; i++ ) { + Log_Write("define parms %d:", i); + for ( pt = parms[i]; pt; pt = pt->next ) { + Log_Write( "%s", pt->c_str() ); + } + } +#endif //DEBUG_EVAL + } + // empty list at first + first = NULL; + last = NULL; + // create a list with tokens of the expanded define + for ( dt = define->tokens; dt; dt = dt->next ) { + parmnum = -1; + // if the token is a name, it could be a define parameter + if ( dt->type == TT_NAME ) { + parmnum = FindDefineParm( define, dt->c_str() ); + } + // if it is a define parameter + if ( parmnum >= 0 ) { + for ( pt = parms[parmnum]; pt; pt = pt->next ) { + t = new idToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } + } + else { + // if stringizing operator + if ( (*dt) == "#" ) { + // the stringizing operator must be followed by a define parameter + if ( dt->next ) { + parmnum = FindDefineParm( define, dt->next->c_str() ); + } + else { + parmnum = -1; + } + + if ( parmnum >= 0 ) { + // step over the stringizing operator + dt = dt->next; + // stringize the define parameter tokens + if ( !idParser::StringizeTokens( parms[parmnum], &token ) ) { + idParser::Error( "can't stringize tokens" ); + return false; + } + t = new idToken(token); + t->line = deftoken->line; + } + else { + idParser::Warning( "stringizing operator without define parameter" ); + continue; + } + } + else { + t = new idToken(dt); + t->line = deftoken->line; + } + // add the token to the list + t->next = NULL; +// the token being read from the define list should use the line number of +// the original file, not the header file + t->line = deftoken->line; + + if ( last ) last->next = t; + else first = t; + last = t; + } + } + // check for the merging operator + for ( t = first; t; ) { + if ( t->next ) { + // if the merging operator + if ( (*t->next) == "##" ) { + t1 = t; + t2 = t->next->next; + if ( t2 ) { + if ( !idParser::MergeTokens( t1, t2 ) ) { + idParser::Error( "can't merge '%s' with '%s'", t1->c_str(), t2->c_str() ); + return false; + } + delete t1->next; + t1->next = t2->next; + if ( t2 == last ) last = t1; + delete t2; + continue; + } + } + } + t = t->next; + } + // store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + // free all the parameter tokens + for ( i = 0; i < define->numparms; i++ ) { + for ( pt = parms[i]; pt; pt = nextpt ) { + nextpt = pt->next; + delete pt; + } + } + + return true; +} + +/* +================ +idParser::ExpandDefineIntoSource +================ +*/ +int idParser::ExpandDefineIntoSource( idToken *deftoken, define_t *define ) { + idToken *firsttoken, *lasttoken; + + if ( !idParser::ExpandDefine( deftoken, define, &firsttoken, &lasttoken ) ) { + return false; + } + // if the define is not empty + if ( firsttoken && lasttoken ) { + firsttoken->linesCrossed += deftoken->linesCrossed; + lasttoken->next = idParser::tokens; + idParser::tokens = firsttoken; + } + return true; +} + +/* +================ +idParser::ReadLine + +reads a token from the current line, continues reading on the next +line only if a backslash '\' is found +================ +*/ +int idParser::ReadLine( idToken *token ) { + int crossline; + + crossline = 0; + do { + if (!idParser::ReadSourceToken( token )) { + return false; + } + + if (token->linesCrossed > crossline) { + idParser::UnreadSourceToken( token ); + return false; + } + crossline = 1; + } while( (*token) == "\\" ); + return true; +} + +/* +================ +idParser::Directive_include +================ +*/ +int idParser::Directive_include( void ) { + idLexer *script; + idToken token; + idStr path; + + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "#include without file name" ); + return false; + } + if ( token.linesCrossed > 0 ) { + idParser::Error( "#include without file name" ); + return false; + } + if ( token.type == TT_STRING ) { + script = new idLexer; + // try relative to the current file + path = scriptstack->GetFileName(); + path.StripFilename(); +// RAVEN BEGIN +// jscott: avoid the leading slash + if( path.Length() ) + { + path += "/"; + } +// RAVEN END + path += token; + if ( !script->LoadFile( path, OSPath ) ) { + // try absolute path + path = token; + if ( !script->LoadFile( path, OSPath ) ) { + // try from the include path + path = includepath + token; + if ( !script->LoadFile( path, OSPath ) ) { + delete script; + script = NULL; + } + } + } + } + else if ( token.type == TT_PUNCTUATION && token == "<" ) { + path = idParser::includepath; + while( idParser::ReadSourceToken( &token ) ) { + if ( token.linesCrossed > 0 ) { + idParser::UnreadSourceToken( &token ); + break; + } + if ( token.type == TT_PUNCTUATION && token == ">" ) { + break; + } + path += token; + } + if ( token != ">" ) { + idParser::Warning( "#include missing trailing >" ); + } + if ( !path.Length() ) { + idParser::Error( "#include without file name between < >" ); + return false; + } + if ( idParser::flags & LEXFL_NOBASEINCLUDES ) { + return true; + } + script = new idLexer; + if ( !script->LoadFile( includepath + path, OSPath ) ) { + delete script; + script = NULL; + } + } + else { + idParser::Error( "#include without file name" ); + return false; + } + if (!script) { + idParser::Error( "file '%s' not found", path.c_str() ); + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + idParser::PushScript( script ); + return true; +} + +/* +================ +idParser::Directive_undef +================ +*/ +int idParser::Directive_undef( void ) { + idToken token; + define_t *define, *lastdefine; + int hash; + + // + if (!idParser::ReadLine( &token )) { + idParser::Error( "undef without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name but found '%s'", token.c_str() ); + return false; + } + + hash = PC_NameHash( token.c_str() ); + for (lastdefine = NULL, define = idParser::definehash[hash]; define; define = define->hashnext) { + if (!idStr::Cmp(define->name, token.c_str())) + { + if (define->flags & DEFINE_FIXED) { + idParser::Warning( "can't undef '%s'", token.c_str() ); + } + else { + if (lastdefine) { + lastdefine->hashnext = define->hashnext; + } + else { + idParser::definehash[hash] = define->hashnext; + } + FreeDefine(define); + } + break; + } + lastdefine = define; + } + return true; +} + +/* +================ +idParser::Directive_define +================ +*/ +int idParser::Directive_define( void ) { + idToken token, *t, *last; + define_t *define; + + if (!idParser::ReadLine( &token )) { + idParser::Error( "#define without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name after #define, found '%s'", token.c_str() ); + return false; + } + // check if the define already exists + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (define) { + if (define->flags & DEFINE_FIXED) { + idParser::Error( "can't redefine '%s'", token.c_str() ); + return false; + } + idParser::Warning( "redefinition of '%s'", token.c_str() ); + // unread the define name before executing the #undef directive + idParser::UnreadSourceToken( &token ); + if (!idParser::Directive_undef()) + return false; + // if the define was not removed (define->flags & DEFINE_FIXED) + define = FindHashedDefine(idParser::definehash, token.c_str()); + } + // allocate define +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + define = (define_t *) Mem_ClearedAlloc(sizeof(define_t) + token.Length() + 1, MA_PARSER); +//RAVEN END + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, token.c_str()); + // add the define to the source + AddDefineToHash(define, idParser::definehash); + // if nothing is defined, just return + if ( !idParser::ReadLine( &token ) ) { + return true; + } + // if it is a define with parameters + if ( token.WhiteSpaceBeforeToken() == 0 && token == "(" ) { + // read the define parameters + last = NULL; + if ( !idParser::CheckTokenString(")") ) { + while(1) { + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "expected define parameter" ); + return false; + } + // if it isn't a name + if (token.type != TT_NAME) { + idParser::Error( "invalid define parameter" ); + return false; + } + + if (FindDefineParm(define, token.c_str()) >= 0) { + idParser::Error( "two the same define parameters" ); + return false; + } + // add the define parm + t = new idToken(token); + t->ClearTokenWhiteSpace(); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + // read next token + if (!idParser::ReadLine( &token )) { + idParser::Error( "define parameters not terminated" ); + return false; + } + + if ( token == ")" ) { + break; + } + // then it must be a comma + if ( token != "," ) { + idParser::Error( "define not terminated" ); + return false; + } + } + } + if ( !idParser::ReadLine( &token ) ) { + return true; + } + } + // read the defined stuff + last = NULL; + do + { + t = new idToken(token); + if ( t->type == TT_NAME && !idStr::Cmp( t->c_str(), define->name ) ) { + t->flags |= TOKEN_FL_RECURSIVE_DEFINE; + idParser::Warning( "recursive define (removed recursion)" ); + } + t->ClearTokenWhiteSpace(); + t->next = NULL; + if ( last ) last->next = t; + else define->tokens = t; + last = t; + } while( idParser::ReadLine( &token ) ); + + if ( last ) { + // check for merge operators at the beginning or end + if ( (*define->tokens) == "##" || (*last) == "##" ) { + idParser::Error( "define with misplaced ##" ); + return false; + } + } + return true; +} + +/* +================ +idParser::AddDefine +================ +*/ +int idParser::AddDefine( const char *string ) { + define_t *define; + + define = DefineFromString( string ); + if (!define) { + return false; + } + AddDefineToHash(define, idParser::definehash); + return true; +} + +/* +================ +idParser::AddGlobalDefinesToSource +================ +*/ +void idParser::AddGlobalDefinesToSource( void ) { + define_t *define, *newdefine; + + for (define = globaldefines; define; define = define->next) { + newdefine = CopyDefine( define ); + AddDefineToHash(newdefine, idParser::definehash); + } +} + +/* +================ +idParser::Directive_if_def +================ +*/ +int idParser::Directive_if_def( int type ) { + idToken token; + define_t *d; + int skip; + + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "#ifdef without name" ); + return false; + } + if (token.type != TT_NAME) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "expected name after #ifdef, found '%s'", token.c_str() ); + return false; + } + d = FindHashedDefine(idParser::definehash, token.c_str()); + skip = (type == INDENT_IFDEF) == (d == NULL); + idParser::PushIndent( type, skip ); + return true; +} + +/* +================ +idParser::Directive_ifdef +================ +*/ +int idParser::Directive_ifdef( void ) { + return idParser::Directive_if_def( INDENT_IFDEF ); +} + +/* +================ +idParser::Directive_ifndef +================ +*/ +int idParser::Directive_ifndef( void ) { + return idParser::Directive_if_def( INDENT_IFNDEF ); +} + +/* +================ +idParser::Directive_else +================ +*/ +int idParser::Directive_else( void ) { + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type) { + idParser::Error( "misplaced #else" ); + return false; + } + if (type == INDENT_ELSE) { + idParser::Error( "#else after #else" ); + return false; + } + idParser::PushIndent( INDENT_ELSE, !skip ); + return true; +} + +/* +================ +idParser::Directive_endif +================ +*/ +int idParser::Directive_endif( void ) { + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type) { + idParser::Error( "misplaced #endif" ); + return false; + } + return true; +} + +/* +================ +idParser::EvaluateTokens +================ +*/ +typedef struct operator_s +{ + int op; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority(int op) { + switch(op) { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } + return false; +} + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 + +#define AllocValue(val) \ + if ( numvalues >= MAX_VALUES ) { \ + idParser::Error( "out of value space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + val = &value_heap[numvalues++]; \ + } + +#define FreeValue(val) + +#define AllocOperator(op) \ + if ( numoperators >= MAX_OPERATORS ) { \ + idParser::Error( "out of operator space\n" ); \ + error = 1; \ + break; \ + } \ + else { \ + op = &operator_heap[numoperators++]; \ + } + +#define FreeOperator(op) + +int idParser::EvaluateTokens( idToken *tokens, signed long int *intvalue, double *floatvalue, int integer ) { + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + idToken *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = false; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for ( t = tokens; t; t = t->next ) { + switch( t->type ) { + case TT_NAME: + { + if ( lastwasvalue || negativevalue ) { + idParser::Error( "syntax error in #if/#elif" ); + error = 1; + break; + } + if ( (*t) != "defined" ) { + idParser::Error( "undefined name '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + t = t->next; + if ( (*t) == "(" ) { + brace = true; + t = t->next; + } + if (!t || t->type != TT_NAME) { + idParser::Error( "defined() without name in #if/#elif" ); + error = 1; + break; + } + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (FindHashedDefine(idParser::definehash, t->c_str())) { + v->intvalue = 1; + v->floatvalue = 1; + } + else { + v->intvalue = 0; + v->floatvalue = 0; + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) { + t = t->next; + if (!t || (*t) != ")" ) { + idParser::Error( "defined missing ) in #if/#elif" ); + error = 1; + break; + } + } + brace = false; + // defined() creates a value + lastwasvalue = 1; + break; + } + case TT_NUMBER: + { + if (lastwasvalue) { + idParser::Error( "syntax error in #if/#elif" ); + error = 1; + break; + } + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (negativevalue) { + v->intvalue = - t->GetIntValue(); + v->floatvalue = - t->GetFloatValue(); + } + else { + v->intvalue = t->GetIntValue(); + v->floatvalue = t->GetFloatValue(); + } + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } + case TT_PUNCTUATION: + { + if (negativevalue) { + idParser::Error( "misplaced minus sign in #if/#elif" ); + error = 1; + break; + } + if (t->subtype == P_PARENTHESESOPEN) { + parentheses++; + break; + } + else if (t->subtype == P_PARENTHESESCLOSE) { + parentheses--; + if (parentheses < 0) { + idParser::Error( "too many ) in #if/#elsif" ); + error = 1; + } + break; + } + //check for invalid operators on floating point values + if ( !integer ) { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) { + idParser::Error( "illigal operator '%s' on floating point operands\n", t->c_str() ); + error = 1; + break; + } + } + switch( t->subtype ) { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) { + idParser::Error( "! or ~ after value in #if/#elif" ); + error = 1; + break; + } + break; + } + case P_INC: + case P_DEC: + { + idParser::Error( "++ or -- used in #if/#elif" ); + break; + } + case P_SUB: + { + if (!lastwasvalue) { + negativevalue = 1; + break; + } + } + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) { + idParser::Error( "operator '%s' after operator in #if/#elif", t->c_str() ); + error = 1; + break; + } + break; + } + default: + { + idParser::Error( "invalid operator '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + } + if (!error && !negativevalue) { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator(o); + o->op = t->subtype; + o->priority = PC_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } + break; + } + default: + { + idParser::Error( "unknown '%s' in #if/#elif", t->c_str() ); + error = 1; + break; + } + } + if (error) { + break; + } + } + if (!error) { + if (!lastwasvalue) { + idParser::Error( "trailing operator in #if/#elif" ); + error = 1; + } + else if (parentheses) { + idParser::Error( "too many ( in #if/#elif" ); + error = 1; + } + } + // + gotquestmarkvalue = false; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while( !error && firstoperator ) { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) { + break; + } + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) { + break; + } + } + //if the arity of the operator isn't equal to 1 + if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { + v = v->next; + } + //if there's no value or no next value + if (!v) { + idParser::Error( "mising values in #if/#elif" ); + error = 1; + break; + } + } + if (error) { + break; + } + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if (integer) { + Log_Write("operator %s, value1 = %d", idParser::scriptstack->getPunctuationFromId(o->op), v1->intvalue); + if (v2) Log_Write("value2 = %d", v2->intvalue); + } + else { + Log_Write("operator %s, value1 = %f", idParser::scriptstack->getPunctuationFromId(o->op), v1->floatvalue); + if (v2) Log_Write("value2 = %f", v2->floatvalue); + } +#endif //DEBUG_EVAL + switch(o->op) { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + idParser::Error( "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + idParser::Error( "divide by zero in #if/#elif\n" ); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) { + idParser::Error( ": without ? in #if/#elif" ); + error = 1; + break; + } + if (integer) { + if (!questmarkintvalue) + v1->intvalue = v2->intvalue; + } + else { + if (!questmarkfloatvalue) + v1->floatvalue = v2->floatvalue; + } + gotquestmarkvalue = false; + break; + } + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) { + idParser::Error( "? after ? in #if/#elif" ); + error = 1; + break; + } + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = true; + break; + } + } +#ifdef DEBUG_EVAL + if (integer) Log_Write("result value = %d", v1->intvalue); + else Log_Write("result value = %f", v1->floatvalue); +#endif //DEBUG_EVAL + if (error) + break; + lastoperatortype = o->op; + //if not an operator with arity 1 + if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { + //remove the second value if not question mark operator + if (o->op != P_QUESTIONMARK) { + v = v->next; + } + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //FreeMemory(v); + FreeValue(v); + } + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //FreeMemory(o); + FreeOperator(o); + } + if (firstvalue) { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } + for (o = firstoperator; o; o = lastoperator) { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator(o); + } + for (v = firstvalue; v; v = lastvalue) { + lastvalue = v->next; + //FreeMemory(v); + FreeValue(v); + } + if (!error) { + return true; + } + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + return false; +} + +/* +================ +idParser::Evaluate +================ +*/ +int idParser::Evaluate( signed long int *intvalue, double *floatvalue, int integer ) { + idToken token, *firsttoken, *lasttoken; + idToken *t, *nexttoken; + define_t *define; + int defined = false; + + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + // + if ( !idParser::ReadLine( &token ) ) { + idParser::Error( "no value after #if/#elif" ); + return false; + } + firsttoken = NULL; + lasttoken = NULL; + do { + //if the token is a name + if (token.type == TT_NAME) { + if (defined) { + defined = false; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if ( token == "defined" ) { + defined = true; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + //then it must be a define + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (!define) { + idParser::Error( "can't Evaluate '%s', not defined", token.c_str() ); + return false; + } + if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { + return false; + } + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + idParser::Error( "can't Evaluate '%s'", token.c_str() ); + return false; + } + } while(idParser::ReadLine( &token )); + // + if ( !idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer ) ) { + return false; + } + // +#ifdef DEBUG_EVAL + Log_Write("eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->c_str()); +#endif //DEBUG_EVAL + nexttoken = t->next; + delete t; + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("eval result: %d", *intvalue); + else Log_Write("eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return true; +} + +/* +================ +idParser::DollarEvaluate +================ +*/ +int idParser::DollarEvaluate( signed long int *intvalue, double *floatvalue, int integer) { + int indent, defined = false; + idToken token, *firsttoken, *lasttoken; + idToken *t, *nexttoken; + define_t *define; + + if (intvalue) { + *intvalue = 0; + } + if (floatvalue) { + *floatvalue = 0; + } + // + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "no leading ( after $evalint/$evalfloat" ); + return false; + } + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "nothing to Evaluate" ); + return false; + } + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do { + //if the token is a name + if (token.type == TT_NAME) { + if (defined) { + defined = false; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else if ( token == "defined" ) { + defined = true; + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + //then it must be a define + define = FindHashedDefine(idParser::definehash, token.c_str()); + if (!define) { + idParser::Warning( "can't Evaluate '%s', not defined", token.c_str() ); + return false; + } + if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { + return false; + } + } + } + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { + if ( token[0] == '(' ) indent++; + else if ( token[0] == ')' ) indent--; + if (indent <= 0) { + break; + } + t = new idToken(token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } + else { + idParser::Error( "can't Evaluate '%s'", token.c_str() ); + return false; + } + } while(idParser::ReadSourceToken( &token )); + // + if (!idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer)) { + return false; + } + // +#ifdef DEBUG_EVAL + Log_Write("$eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->c_str()); +#endif //DEBUG_EVAL + nexttoken = t->next; + delete t; + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("$eval result: %d", *intvalue); + else Log_Write("$eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return true; +} + +/* +================ +idParser::Directive_elif +================ +*/ +int idParser::Directive_elif( void ) { + signed long int value; + int type, skip; + + idParser::PopIndent( &type, &skip ); + if (!type || type == INDENT_ELSE) { + idParser::Error( "misplaced #elif" ); + return false; + } + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + skip = (value == 0); + idParser::PushIndent( INDENT_ELIF, skip ); + return true; +} + +/* +================ +idParser::Directive_if +================ +*/ +int idParser::Directive_if( void ) { + signed long int value; + int skip; + + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + skip = (value == 0); + idParser::PushIndent( INDENT_IF, skip ); + return true; +} + +/* +================ +idParser::Directive_line +================ +*/ +int idParser::Directive_line( void ) { + idToken token; + + idParser::Error( "#line directive not supported" ); + while( idParser::ReadLine( &token ) ) { + } + return true; +} + +/* +================ +idParser::Directive_error +================ +*/ +int idParser::Directive_error( void ) { + idToken token; + + if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { + idParser::Error( "#error without string" ); + return false; + } + idParser::Error( "#error: %s", token.c_str() ); + return true; +} + +/* +================ +idParser::Directive_warning +================ +*/ +int idParser::Directive_warning( void ) { + idToken token; + + if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { + idParser::Warning( "#warning without string" ); + return false; + } + idParser::Warning( "#warning: %s", token.c_str() ); + return true; +} + +/* +================ +idParser::Directive_pragma +================ +*/ +int idParser::Directive_pragma( void ) { + idToken token; + + idParser::Warning( "#pragma directive not supported" ); + while( idParser::ReadLine( &token ) ) { + } + return true; +} + +/* +================ +idParser::UnreadSignToken +================ +*/ +void idParser::UnreadSignToken( void ) { + idToken token; + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + token = "-"; + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + idParser::UnreadSourceToken( &token ); +} + +/* +================ +idParser::Directive_eval +================ +*/ +int idParser::Directive_eval( void ) { + signed long int value; + idToken token; + char buf[128]; + + if ( !idParser::Evaluate( &value, NULL, true ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf(buf, "%d", abs(value)); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::Directive_evalfloat +================ +*/ +int idParser::Directive_evalfloat( void ) { + double value; + idToken token; + char buf[128]; + + if ( !idParser::Evaluate( NULL, &value, false ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf(buf, "%1.2f", idMath::Fabs(value)); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + idParser::UnreadSourceToken( &token ); + if (value < 0) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::ReadDirective +================ +*/ +int idParser::ReadDirective( void ) { + idToken token; + + //read the directive name + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "found '#' without name" ); + return false; + } + //directive name must be on the same line + if (token.linesCrossed > 0) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "found '#' at end of line" ); + return false; + } + //if if is a name + if (token.type == TT_NAME) { + if ( token == "if" ) { + return idParser::Directive_if(); + } + else if ( token == "ifdef" ) { + return idParser::Directive_ifdef(); + } + else if ( token == "ifndef" ) { + return idParser::Directive_ifndef(); + } + else if ( token == "elif" ) { + return idParser::Directive_elif(); + } + else if ( token == "else" ) { + return idParser::Directive_else(); + } + else if ( token == "endif" ) { + return idParser::Directive_endif(); + } + else if (idParser::skip > 0) { + // skip the rest of the line + while( idParser::ReadLine( &token ) ) { + } + return true; + } + else { + if ( token == "include" ) { + return idParser::Directive_include(); + } + else if ( token == "define" ) { + return idParser::Directive_define(); + } + else if ( token == "undef" ) { + return idParser::Directive_undef(); + } + else if ( token == "line" ) { + return idParser::Directive_line(); + } + else if ( token == "error" ) { + return idParser::Directive_error(); + } + else if ( token == "warning" ) { + return idParser::Directive_warning(); + } + else if ( token == "pragma" ) { + return idParser::Directive_pragma(); + } + else if ( token == "eval" ) { + return idParser::Directive_eval(); + } + else if ( token == "evalfloat" ) { + return idParser::Directive_evalfloat(); + } + } + } + idParser::Error( "unknown precompiler directive '%s'", token.c_str() ); + return false; +} + +/* +================ +idParser::DollarDirective_evalint +================ +*/ +int idParser::DollarDirective_evalint( void ) { + signed long int value; + idToken token; + char buf[128]; + + if ( !idParser::DollarEvaluate( &value, NULL, true ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf( buf, "%d", abs( value ) ); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL | TT_VALUESVALID; + token.intvalue = abs( value ); + token.floatvalue = abs( value ); + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::DollarDirective_evalfloat +================ +*/ +int idParser::DollarDirective_evalfloat( void ) { + double value; + idToken token; + char buf[128]; + + if ( !idParser::DollarEvaluate( NULL, &value, false ) ) { + return false; + } + + token.line = idParser::scriptstack->GetLineNum(); + token.whiteSpaceStart_p = NULL; + token.whiteSpaceEnd_p = NULL; + token.linesCrossed = 0; + token.flags = 0; + sprintf( buf, "%1.2f", fabs( value ) ); + token = buf; + token.type = TT_NUMBER; + token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL | TT_VALUESVALID; + token.intvalue = (unsigned long) fabs( value ); + token.floatvalue = fabs( value ); + idParser::UnreadSourceToken( &token ); + if ( value < 0 ) { + idParser::UnreadSignToken(); + } + return true; +} + +/* +================ +idParser::ReadDollarDirective +================ +*/ +int idParser::ReadDollarDirective( void ) { + idToken token; + + // read the directive name + if ( !idParser::ReadSourceToken( &token ) ) { + idParser::Error( "found '$' without name" ); + return false; + } + // directive name must be on the same line + if ( token.linesCrossed > 0 ) { + idParser::UnreadSourceToken( &token ); + idParser::Error( "found '$' at end of line" ); + return false; + } + // if if is a name + if (token.type == TT_NAME) { + if ( token == "evalint" ) { + return idParser::DollarDirective_evalint(); + } + else if ( token == "evalfloat" ) { + return idParser::DollarDirective_evalfloat(); + } + } + idParser::UnreadSourceToken( &token ); + return false; +} + +/* +================ +idParser::ReadToken +================ +*/ +int idParser::ReadToken( idToken *token ) { + define_t *define; + + while(1) { + if ( !idParser::ReadSourceToken( token ) ) { + return false; + } + // check for precompiler directives + if ( token->type == TT_PUNCTUATION && (*token)[0] == '#' && (*token)[1] == '\0' ) { + // read the precompiler directive + if ( !idParser::ReadDirective() ) { + return false; + } + continue; + } + // if skipping source because of conditional compilation + if ( idParser::skip ) { + continue; + } + // recursively concatenate strings that are behind each other still resolving defines + if ( token->type == TT_STRING && !(idParser::scriptstack->GetFlags() & LEXFL_NOSTRINGCONCAT) ) { + idToken newtoken; + if ( idParser::ReadToken( &newtoken ) ) { + if ( newtoken.type == TT_STRING ) { + token->Append( newtoken.c_str() ); + } + else { + idParser::UnreadSourceToken( &newtoken ); + } + } + } + // + if ( !(idParser::scriptstack->GetFlags() & LEXFL_NODOLLARPRECOMPILE) ) { + // check for special precompiler directives + if ( token->type == TT_PUNCTUATION && (*token)[0] == '$' && (*token)[1] == '\0' ) { + // read the precompiler directive + if ( idParser::ReadDollarDirective() ) { + continue; + } + } + } + // if the token is a name + if ( token->type == TT_NAME && !( token->flags & TOKEN_FL_RECURSIVE_DEFINE ) ) { + // check if the name is a define macro + define = FindHashedDefine( idParser::definehash, token->c_str() ); + // if it is a define macro + if ( define ) { + // expand the defined macro + if ( !idParser::ExpandDefineIntoSource( token, define ) ) { + return false; + } + continue; + } + } + // found a token + return true; + } +} + +/* +================ +idParser::ExpectTokenString +================ +*/ +int idParser::ExpectTokenString( const char *string ) { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't find expected '%s'", string ); + return false; + } + + if ( token != string ) { + idParser::Error( "expected '%s' but found '%s'", string, token.c_str() ); + return false; + } + return true; +} + +/* +================ +idParser::ExpectTokenType +================ +*/ +int idParser::ExpectTokenType( int type, int subtype, idToken *token ) { + idStr str; + + if ( !idParser::ReadToken( token ) ) { + idParser::Error( "couldn't read expected token" ); + return 0; + } + + if ( token->type != type ) { + switch( type ) { + case TT_STRING: str = "string"; break; + case TT_LITERAL: str = "literal"; break; + case TT_NUMBER: str = "number"; break; + case TT_NAME: str = "name"; break; + case TT_PUNCTUATION: str = "punctuation"; break; + default: str = "unknown type"; break; + } + idParser::Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + if ( token->type == TT_NUMBER ) { + if ( (token->subtype & subtype) != subtype ) { + str.Clear(); + if ( subtype & TT_DECIMAL ) str = "decimal "; + if ( subtype & TT_HEX ) str = "hex "; + if ( subtype & TT_OCTAL ) str = "octal "; + if ( subtype & TT_BINARY ) str = "binary "; + if ( subtype & TT_UNSIGNED ) str += "unsigned "; + if ( subtype & TT_LONG ) str += "long "; + if ( subtype & TT_FLOAT ) str += "float "; + if ( subtype & TT_INTEGER ) str += "integer "; + str.StripTrailing( ' ' ); + idParser::Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); + return 0; + } + } + else if ( token->type == TT_PUNCTUATION ) { + if ( subtype < 0 ) { + idParser::Error( "BUG: wrong punctuation subtype" ); + return 0; + } + if ( token->subtype != subtype ) { + idParser::Error( "expected '%s' but found '%s'", scriptstack->GetPunctuationFromId( subtype ), token->c_str() ); + return 0; + } + } + return 1; +} + +/* +================ +idParser::ExpectAnyToken +================ +*/ +int idParser::ExpectAnyToken( idToken *token ) { + if (!idParser::ReadToken( token )) { + idParser::Error( "couldn't read expected token" ); + return false; + } + else { + return true; + } +} + +/* +================ +idParser::CheckTokenString +================ +*/ +int idParser::CheckTokenString( const char *string ) { + idToken tok; + + if (!idParser::ReadToken( &tok )) { + return false; + } + //if the token is available + if ( tok == string ) { + return true; + } + // + idParser::UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::CheckTokenType +================ +*/ +int idParser::CheckTokenType( int type, int subtype, idToken *token ) { + idToken tok; + + if (!idParser::ReadToken( &tok )) { + return false; + } + //if the type matches + if (tok.type == type && (tok.subtype & subtype) == subtype) { + *token = tok; + return true; + } + // + idParser::UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::SkipUntilString +================ +*/ +int idParser::SkipUntilString( const char *string ) { + idToken token; + + while(idParser::ReadToken( &token )) { + if ( token == string ) { + return true; + } + } + return false; +} + +/* +================ +idParser::SkipRestOfLine +================ +*/ +int idParser::SkipRestOfLine( void ) { + idToken token; + + while(idParser::ReadToken( &token )) { + if ( token.linesCrossed ) { + idParser::UnreadSourceToken( &token ); + return true; + } + } + return false; +} + +/* +================= +idParser::SkipBracedSection + +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +int idParser::SkipBracedSection( bool parseFirstBrace ) { + idToken token; + int depth; + + depth = parseFirstBrace ? 0 : 1; + do { + if ( !ReadToken( &token ) ) { + return false; + } + if( token.type == TT_PUNCTUATION ) { + if( token == "{" ) { + depth++; + } else if ( token == "}" ) { + depth--; + } + } + } while( depth ); + return true; +} + +/* +================= +idParser::ParseBracedSectionExact + +The next token should be an open brace. +Parses until a matching close brace is found. +Maintains the exact formating of the braced section + + FIXME: what about precompilation ? +================= +*/ +const char *idParser::ParseBracedSectionExact( idStr &out, int tabs ) { + return scriptstack->ParseBracedSectionExact( out, tabs ); +} + +/* +================= +idParser::ParseBracedSection + +The next token should be an open brace. +Parses until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +const char *idParser::ParseBracedSection( idStr &out, int tabs ) { + idToken token; + int i, depth; + bool doTabs = false; + if (tabs >= 0) { + doTabs = true; + } + + out.Empty(); + if ( !idParser::ExpectTokenString( "{" ) ) { + return out.c_str(); + } + out = "{"; + depth = 1; + do { + if ( !idParser::ReadToken( &token ) ) { + Error( "missing closing brace" ); + return out.c_str(); + } + + // if the token is on a new line + for ( i = 0; i < token.linesCrossed; i++ ) { + out += "\r\n"; + } + + if (doTabs && token.linesCrossed) { + i = tabs; + if (token[0] == '}' && i > 0) { + i--; + } + while (i-- > 0) { + out += "\t"; + } + } + if ( token.type == TT_PUNCTUATION ) { + if ( token[0] == '{' ) { + depth++; + if (doTabs) { + tabs++; + } + } + else if ( token[0] == '}' ) { + depth--; + if (doTabs) { + tabs--; + } + } + } + + if ( token.type == TT_STRING ) { + out += "\"" + token + "\""; + } + else { + out += token; + } + out += " "; + } while( depth ); + + return out.c_str(); +} + +/* +================= +idParser::ParseRestOfLine + + parse the rest of the line +================= +*/ +const char *idParser::ParseRestOfLine( idStr &out ) { + idToken token; + + out.Empty(); + while(idParser::ReadToken( &token )) { + if ( token.linesCrossed ) { + idParser::UnreadSourceToken( &token ); + break; + } + if ( out.Length() ) { + out += " "; + } + out += token; + } + return out.c_str(); +} + +/* +================ +idParser::UnreadToken +================ +*/ +void idParser::UnreadToken( idToken *token ) { + idParser::UnreadSourceToken( token ); +} + +/* +================ +idParser::ReadTokenOnLine +================ +*/ +int idParser::ReadTokenOnLine( idToken *token ) { + idToken tok; + + if (!idParser::ReadToken( &tok )) { + return false; + } + // if no lines were crossed before this token + if ( !tok.linesCrossed ) { + *token = tok; + return true; + } + // + idParser::UnreadSourceToken( &tok ); + return false; +} + +/* +================ +idParser::ParseInt +================ +*/ +int idParser::ParseInt( void ) { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't read expected integer" ); + return 0; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idParser::ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); + return -((signed int) token.GetIntValue()); + } + else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + idParser::Error( "expected integer value, found '%s'", token.c_str() ); + } + return token.GetIntValue(); +} + +/* +================ +idParser::ParseBool +================ +*/ +bool idParser::ParseBool( void ) { + idToken token; + + if ( !idParser::ExpectTokenType( TT_NUMBER, 0, &token ) ) { + idParser::Error( "couldn't read expected boolean" ); + return false; + } + return ( token.GetIntValue() != 0 ); +} + +/* +================ +idParser::ParseFloat +================ +*/ +float idParser::ParseFloat( void ) { + idToken token; + + if ( !idParser::ReadToken( &token ) ) { + idParser::Error( "couldn't read expected floating point number" ); + return 0.0f; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + idParser::ExpectTokenType( TT_NUMBER, 0, &token ); + return -token.GetFloatValue(); + } + else if ( token.type != TT_NUMBER ) { + idParser::Error( "expected float value, found '%s'", token.c_str() ); + } + return token.GetFloatValue(); +} + +/* +================ +idParser::Parse1DMatrix +================ +*/ +int idParser::Parse1DMatrix( int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < x; i++ ) { + m[i] = idParser::ParseFloat(); + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::Parse2DMatrix +================ +*/ +int idParser::Parse2DMatrix( int y, int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0; i < y; i++ ) { + if ( !idParser::Parse1DMatrix( x, m + i * x ) ) { + return false; + } + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::Parse3DMatrix +================ +*/ +int idParser::Parse3DMatrix( int z, int y, int x, float *m ) { + int i; + + if ( !idParser::ExpectTokenString( "(" ) ) { + return false; + } + + for ( i = 0 ; i < z; i++ ) { + if ( !idParser::Parse2DMatrix( y, x, m + i * x*y ) ) { + return false; + } + } + + if ( !idParser::ExpectTokenString( ")" ) ) { + return false; + } + return true; +} + +/* +================ +idParser::GetLastWhiteSpace +================ +*/ +int idParser::GetLastWhiteSpace( idStr &whiteSpace ) const { + if ( scriptstack ) { + scriptstack->GetLastWhiteSpace( whiteSpace ); + } else { + whiteSpace.Clear(); + } + return whiteSpace.Length(); +} + +/* +================ +idParser::SetMarker +================ +*/ +void idParser::SetMarker( void ) { + marker_p = NULL; +} + +/* +================ +idParser::GetStringFromMarker + + FIXME: this is very bad code, the script isn't even garrenteed to still be around +================ +*/ +void idParser::GetStringFromMarker( idStr& out, bool clean ) { + char* p; + char save; + + if ( marker_p == NULL ) { + marker_p = scriptstack->buffer; + } + + if ( tokens ) { + p = (char*)tokens->whiteSpaceStart_p; + } else { + p = (char*)scriptstack->script_p; + } + + // Set the end character to NULL to give us a complete string + save = *p; + *p = 0; + + // If cleaning then reparse + if ( clean ) { + idParser temp( marker_p, strlen( marker_p ), "temp", flags ); + idToken token; + while ( temp.ReadToken ( &token ) ) { + out += token; + } + } else { + out = marker_p; + } + + // restore the character we set to NULL + *p = save; +} + +/* +================ +idParser::SetIncludePath +================ +*/ +void idParser::SetIncludePath( const char *path ) { + idParser::includepath = path; + // add trailing path seperator + if (idParser::includepath[idParser::includepath.Length()-1] != '\\' && + idParser::includepath[idParser::includepath.Length()-1] != '/') { +// RAVEN BEGIN + idParser::includepath += "/"; +// RAVEN END + } +} + +/* +================ +idParser::SetPunctuations +================ +*/ +void idParser::SetPunctuations( const punctuation_t *p ) { + idParser::punctuations = p; +} + +/* +================ +idParser::SetFlags +================ +*/ +void idParser::SetFlags( int flags ) { + idLexer *s; + + idParser::flags = flags; + for ( s = idParser::scriptstack; s; s = s->next ) { + s->SetFlags( flags ); + } +} + +/* +================ +idParser::GetFlags +================ +*/ +int idParser::GetFlags( void ) const { + return idParser::flags; +} + +/* +================ +idParser::LoadFile +================ +*/ +int idParser::LoadFile( const char *filename, bool OSPath ) { + idLexer *script; + + if ( idParser::loaded ) { + idLib::common->FatalError("idParser::loadFile: another source already loaded"); + return false; + } + script = new idLexer( filename, 0, OSPath ); + if ( !script->IsLoaded() ) { + delete script; + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + script->next = NULL; + idParser::OSPath = OSPath; + idParser::filename = filename; + idParser::scriptstack = script; + idParser::tokens = NULL; + idParser::indentstack = NULL; + idParser::skip = 0; + idParser::loaded = true; + + if ( !idParser::definehash ) { + idParser::defines = NULL; +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *), MA_PARSER ); +//RAVEN END + idParser::AddGlobalDefinesToSource(); + } + return true; +} + +/* +================ +idParser::LoadMemory +================ +*/ +int idParser::LoadMemory(const char *ptr, int length, const char *name ) { + idLexer *script; + + if ( idParser::loaded ) { + idLib::common->FatalError("idParser::loadMemory: another source already loaded"); + return false; + } + script = new idLexer( ptr, length, name ); + if ( !script->IsLoaded() ) { + delete script; + return false; + } + script->SetFlags( idParser::flags ); + script->SetPunctuations( idParser::punctuations ); + script->next = NULL; + idParser::filename = name; + idParser::scriptstack = script; + idParser::tokens = NULL; + idParser::indentstack = NULL; + idParser::skip = 0; + idParser::loaded = true; + + if ( !idParser::definehash ) { + idParser::defines = NULL; +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *), MA_PARSER ); +//RAVEN END + idParser::AddGlobalDefinesToSource(); + } + return true; +} + +/* +================ +idParser::FreeSource +================ +*/ +void idParser::FreeSource( bool keepDefines ) { + idLexer *script; + idToken *token; + define_t *define; + indent_t *indent; + int i; + + // free all the scripts + while(idParser::scriptstack) { + script = idParser::scriptstack; + idParser::scriptstack = idParser::scriptstack->next; + delete script; + } + // free all the tokens + while(idParser::tokens) { + token = idParser::tokens; + idParser::tokens = idParser::tokens->next; + delete token; + } + // free all indents + while(idParser::indentstack) { + indent = idParser::indentstack; + idParser::indentstack = idParser::indentstack->next; + Mem_Free( indent ); + } + if ( !keepDefines ) { + // free hash table + if ( idParser::definehash ) { + // free defines + for ( i = 0; i < DEFINEHASHSIZE; i++ ) { + while( idParser::definehash[i] ) { + define = idParser::definehash[i]; + idParser::definehash[i] = idParser::definehash[i]->hashnext; + FreeDefine(define); + } + } + idParser::defines = NULL; + Mem_Free( idParser::definehash ); + idParser::definehash = NULL; + } + } + idParser::loaded = false; +} + +/* +================ +idParser::GetPunctuationFromId +================ +*/ +const char *idParser::GetPunctuationFromId( int id ) { + int i; + + if ( !idParser::punctuations ) { + idLexer lex; + return lex.GetPunctuationFromId( id ); + } + + for (i = 0; idParser::punctuations[i].p; i++) { + if ( idParser::punctuations[i].n == id ) { + return idParser::punctuations[i].p; + } + } + return "unkown punctuation"; +} + +/* +================ +idParser::GetPunctuationId +================ +*/ +int idParser::GetPunctuationId( const char *p ) { + int i; + + if ( !idParser::punctuations ) { + idLexer lex; + return lex.GetPunctuationId( p ); + } + + for (i = 0; idParser::punctuations[i].p; i++) { + if ( !idStr::Cmp(idParser::punctuations[i].p, p) ) { + return idParser::punctuations[i].n; + } + } + return 0; +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser() { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = 0; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + +// RAVEN BEGIN +// bdube: added members + marker_p = NULL; +// RAVEN END +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( int flags ) { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + +// RAVEN BEGIN +// bdube: added members + marker_p = NULL; +// RAVEN END +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( const char *filename, int flags, bool OSPath ) { + this->loaded = false; + this->OSPath = true; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + LoadFile( filename, OSPath ); +} + +/* +================ +idParser::idParser +================ +*/ +idParser::idParser( const char *ptr, int length, const char *name, int flags ) { + this->loaded = false; + this->OSPath = false; + this->punctuations = 0; + this->flags = flags; + this->scriptstack = NULL; + this->indentstack = NULL; + this->definehash = NULL; + this->defines = NULL; + this->tokens = NULL; + this->marker_p = NULL; + LoadMemory( ptr, length, name ); +} + +/* +================ +idParser::~idParser +================ +*/ +idParser::~idParser( void ) { + idParser::FreeSource( false ); +} + diff --git a/src/idlib/Parser.h b/src/idlib/Parser.h new file mode 100644 index 0000000..8bda23a --- /dev/null +++ b/src/idlib/Parser.h @@ -0,0 +1,277 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PARSER_H__ +#define __PARSER_H__ + +/* +=============================================================================== + + C/C++ compatible pre-compiler + +=============================================================================== +*/ + +#define DEFINE_FIXED 0x0001 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +// macro definitions +typedef struct define_s { + char * name; // define name + int flags; // define flags + int builtin; // > 0 if builtin define + int numparms; // number of define parameters + idToken * parms; // define parameters + idToken * tokens; // macro tokens (possibly containing parm tokens) + struct define_s *next; // next defined macro in a list + struct define_s *hashnext; // next define in the hash chain +} define_t; + +// indents used for conditional compilation directives: +// #if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s { + int type; // indent type + int skip; // true if skipping current indent + idLexer * script; // script the indent was in + struct indent_s *next; // next indent on the indent stack +} indent_t; + + +class idParser { + +public: + // constructor + idParser(); + idParser( int flags ); + idParser( const char *filename, int flags = 0, bool OSPath = false ); + idParser( const char *ptr, int length, const char *name, int flags = 0 ); + // destructor + ~idParser(); + // load a source file + int LoadFile( const char *filename, bool OSPath = false ); + // load a source from memory + int LoadMemory( const char *ptr, int length, const char *name ); + // free the current source + void FreeSource( bool keepDefines = false ); + // returns true if a source is loaded + int IsLoaded( void ) const { return idParser::loaded; }; + // read a token from the source + int ReadToken( idToken *token ); + // expect a certain token, reads the token when available + int ExpectTokenString( const char *string ); + // expect a certain token type + int ExpectTokenType( int type, int subtype, idToken *token ); + // expect a token + int ExpectAnyToken( idToken *token ); + // returns true and reads the token when it is available + int CheckTokenString( const char *string ); + // returns true and reads the token when a token with the given type is available + int CheckTokenType( int type, int subtype, idToken *token ); + // skip tokens until the given token string is read + int SkipUntilString( const char *string ); + // skip the rest of the current line + int SkipRestOfLine( void ); + // skip the braced section + int SkipBracedSection( bool parseFirstBrace = true ); + // parse a braced section into a string + const char * ParseBracedSection( idStr &out, int tabs = -1 ); + // parse a braced section into a string, maintaining indents and newlines + const char * ParseBracedSectionExact( idStr &out, int tabs = -1 ); + // parse the rest of the line + const char * ParseRestOfLine( idStr &out ); + // unread the given token + void UnreadToken( idToken *token ); + // read a token only if on the current line + int ReadTokenOnLine( idToken *token ); + // read a signed integer + int ParseInt( void ); + // read a boolean + bool ParseBool( void ); + // read a floating point number + float ParseFloat( void ); + // parse matrices with floats + int Parse1DMatrix( int x, float *m ); + int Parse2DMatrix( int y, int x, float *m ); + int Parse3DMatrix( int z, int y, int x, float *m ); + // get the white space before the last read token + int GetLastWhiteSpace( idStr &whiteSpace ) const; + // Set a marker in the source file (there is only one marker) + void SetMarker( void ); + // Get the string from the marker to the current position + void GetStringFromMarker( idStr& out, bool clean = false ); + // add a define to the source + int AddDefine( const char *string ); + // add builtin defines + void AddBuiltinDefines( void ); + // set the source include path + void SetIncludePath( const char *path ); + // set the punctuation set + void SetPunctuations( const punctuation_t *p ); + // returns a pointer to the punctuation with the given id + const char * GetPunctuationFromId( int id ); + // get the id for the given punctuation + int GetPunctuationId( const char *p ); + // set lexer flags + void SetFlags( int flags ); + // get lexer flags + int GetFlags( void ) const; + // returns the current filename + const char * GetFileName( void ) const; + // get current offset in current script + const int GetFileOffset( void ) const; + // get file time for current script + const unsigned int GetFileTime( void ) const; + // returns the current line number + const int GetLineNum( void ) const; + // print an error message + void Error( const char *str, ... ) const id_attribute((format(printf,2,3))); + // print a warning message + void Warning( const char *str, ... ) const id_attribute((format(printf,2,3))); + + // add a global define that will be added to all opened sources + static int AddGlobalDefine( const char *string ); + // remove the given global define + static int RemoveGlobalDefine( const char *name ); + // remove all global defines + static void RemoveAllGlobalDefines( void ); + // set the base folder to load files from + static void SetBaseFolder( const char *path ); + +private: + + int loaded; // set when a source file is loaded from file or memory + idStr filename; // file name of the script + idStr includepath; // path to include files + bool OSPath; // true if the file was loaded from an OS path + const punctuation_t *punctuations; // punctuations to use + int flags; // flags used for script parsing + idLexer * scriptstack; // stack with scripts of the source + idToken * tokens; // tokens to read first + define_t * defines; // list with macro definitions + define_t ** definehash; // hash chain with defines + indent_t * indentstack; // stack with indents + int skip; // > 0 if skipping conditional code + idToken token; // last read token + const char* marker_p; + + static define_t *globaldefines; // list with global defines added to every source loaded + +private: + void PushIndent( int type, int skip ); + void PopIndent( int *type, int *skip ); + void PushScript( idLexer *script ); + int ReadSourceToken( idToken *token ); + int ReadLine( idToken *token ); + int UnreadSourceToken( idToken *token ); + int ReadDefineParms( define_t *define, idToken **parms, int maxparms ); + int StringizeTokens( idToken *tokens, idToken *token ); + int MergeTokens( idToken *t1, idToken *t2 ); + int ExpandBuiltinDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ); + int ExpandDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ); + int ExpandDefineIntoSource( idToken *deftoken, define_t *define ); + void AddGlobalDefinesToSource( void ); + define_t * CopyDefine( define_t *define ); + define_t * FindHashedDefine(define_t **definehash, const char *name); + int FindDefineParm( define_t *define, const char *name ); + void AddDefineToHash(define_t *define, define_t **definehash); + static void PrintDefine( define_t *define ); + static void FreeDefine( define_t *define ); + static define_t *FindDefine( define_t *defines, const char *name ); + static define_t *DefineFromString( const char *string); + define_t * CopyFirstDefine( void ); + int Directive_include( void ); + int Directive_undef( void ); + int Directive_if_def( int type ); + int Directive_ifdef( void ); + int Directive_ifndef( void ); + int Directive_else( void ); + int Directive_endif( void ); + int EvaluateTokens( idToken *tokens, signed long int *intvalue, double *floatvalue, int integer ); + int Evaluate( signed long int *intvalue, double *floatvalue, int integer ); + int DollarEvaluate( signed long int *intvalue, double *floatvalue, int integer); + int Directive_define( void ); + int Directive_elif( void ); + int Directive_if( void ); + int Directive_line( void ); + int Directive_error( void ); + int Directive_warning( void ); + int Directive_pragma( void ); + void UnreadSignToken( void ); + int Directive_eval( void ); + int Directive_evalfloat( void ); + int ReadDirective( void ); + int DollarDirective_evalint( void ); + int DollarDirective_evalfloat( void ); + int ReadDollarDirective( void ); +}; + +ID_INLINE const char *idParser::GetFileName( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileName(); + } + else { + return ""; + } +} + +ID_INLINE const int idParser::GetFileOffset( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileOffset(); + } + else { + return 0; + } +} + +ID_INLINE const unsigned int idParser::GetFileTime( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetFileTime(); + } + else { + return 0; + } +} + +ID_INLINE const int idParser::GetLineNum( void ) const { + if ( idParser::scriptstack ) { + return idParser::scriptstack->GetLineNum(); + } + else { + return 0; + } +} + +#endif /* !__PARSER_H__ */ + diff --git a/src/idlib/Str.cpp b/src/idlib/Str.cpp new file mode 100644 index 0000000..07322eb --- /dev/null +++ b/src/idlib/Str.cpp @@ -0,0 +1,2255 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +// TTimo - don't do anything funky if you're on a real OS ;-) +#if defined(_WINDOWS) || defined(_XENON) + +// RAVEN BEGIN +// jsinger: attempt at removing the DLL cross boundary issue +// mwhitlock: Dynamic memory consolidation - may want to consier making a totally separate string allocator +#if ( !defined(ID_REDIRECT_NEWDELETE) && !defined(RV_UNIFIED_ALLOCATOR) ) || defined(_RV_MEM_SYS_SUPPORT) +// RAVEN END + #define USE_STRING_DATA_ALLOCATOR +#endif + +#endif + +#ifdef USE_STRING_DATA_ALLOCATOR +static idDynamicBlockAlloc stringDataAllocator; +#endif + +idVec4 g_color_table[16] = +{ + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(1.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_RED + idVec4(0.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_GREEN + idVec4(1.0f, 1.0f, 0.0f, 1.0f), // S_COLOR_YELLOW + idVec4(0.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_BLUE + idVec4(0.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_CYAN + idVec4(1.0f, 0.0f, 1.0f, 1.0f), // S_COLOR_MAGENTA + idVec4(1.0f, 1.0f, 1.0f, 1.0f), // S_COLOR_WHITE + idVec4(0.5f, 0.5f, 0.5f, 1.0f), // S_COLOR_GRAY + idVec4(0.0f, 0.0f, 0.0f, 1.0f), // S_COLOR_BLACK +// RAVEN BEGIN +// bdube: console color + idVec4(0.94f, 0.62f, 0.05f, 1.0f), // S_COLOR_CONSOLE +// RAVEN END + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), + idVec4(0.0f, 0.0f, 0.0f, 1.0f), +}; + +const char *units[2][4] = +{ + { "B", "KB", "MB", "GB" }, + { "B/s", "KB/s", "MB/s", "GB/s" } +}; + +// P means the character is Polish +// C means the character is Czech + +const bool idStr::printableCharacter[256] = +{ +// + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, +// + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, +// ! " # $ & % ' ( ) * + , - . / + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// @ A B C D E F G H I J K L M N O + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// P Q R S T U V W X Y Z [ \ ] ^ _ + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// ` a b c d e f g h i j k l m n o + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// p q r s t u v w x y z { | } ~ + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, +// € X S C Œ P T C Z C Z P + true, true, false, false, false, false, false, false, false, false, true, false, true, true, true, true, +// ™ s C œ P t C z C z P + false, false, false, false, false, false, false, false, false, true, true, false, true, true, true, true, +// ¡ £ P ¤ ¥ P § © « ® P + false, true, false, true, true, true, false, true, false, true, false, true, false, false, true, true, +// ° £ P ´ µ · P » ¿ P + true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, true, +// À Á C Â Ã Ä Å Æ P Ç È C É C Ê P Ë Ì C Í C Î Ï C + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// Ð Ñ P Ò C Ó PC Ô Õ Ö × Ø C Ù C Ú C Û Ü Ý C Þ ß + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// à á C â ã ä å æ P ç è C é C ê P ë ì C í C î ï C + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +// ð ñ P ò C ó PC ô õ ö ÷ ø C ù C ú C û ü ý C þ ÿ + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, +}; + +const char idStr::upperCaseCharacter[256] = +{ +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// ! " # $ & % ' ( ) * + , - . / + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// @ A B C D E F G H I J K L M N O + 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', +// P Q R S T U V W X Y Z [ \ ] ^ _ + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, 0, 0, 0, 0, +// ` a b c d e f g h i j k l m n o + 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', +// p q r s t u v w x y z { | } ~ + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, 0, 0, 0, 0, +// € X S Œ T Z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Š', 0, 'Œ', '', 'Ž', 'Ž', +// TM s œ t z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Š', 0, 'Œ', '', 'Ž', '', +// ¡ £ ¤ ¥ § © « ® + 0, 0, 0, '£', 0, '¥', 0, 0, 0, 0, 0, 0, 0, 0, 0, '¿', +// ° ´ µ · » ¿ + 0, 0, 0, '£', 0, 0, 0, 0, 0, '¥', 0, 0, 0, 0, 0, '¿', +// À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', +// Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0, 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', +// à á â ã ä å æ ç è é ê ë ì í î ï + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', +// ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0, 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', +}; + +const char idStr::lowerCaseCharacter[256] = +{ +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// ! " # $ & % ' ( ) * + , - . / + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// @ A B C D E F G H I J K L M N O + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +// P Q R S T U V W X Y Z [ \ ] ^ _ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0, +// ` a b c d e f g h i j k l m n o + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +// p q r s t u v w x y z { | } ~ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 0, 0, 0, 0, +// € X S Œ T Z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'š', 0, 'œ', '', 'ž', 'Ÿ', +// TM s œ t z + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'š', 0, 'œ', '', 'ž', 'Ÿ', +// ¡ £ ¤ ¥ § © « ® + 0, 0, 0, '³', 0, '¹', 0, 0, 0, 0, 0, 0, 0, 0, 0, '¯', +// ° ´ µ · » ¿ + 0, 0, 0, '³', 0, 0, 0, 0, 0, '¹', 0, 0, 0, 0, 0, '¯', +// À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', +// Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0, 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ß', +// à á â ã ä å æ ç è é ê ë ì í î ï + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', +// ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0, 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ß', +}; +// RAVEN END + +/* +============ +idStr::ColorForIndex +============ +*/ +idVec4 & idStr::ColorForIndex( int i ) { + return g_color_table[ i & 15 ]; +} + +/* +============ +idStr::ReAllocate +============ +*/ +void idStr::ReAllocate( int amount, bool keepold ) { + char *newbuffer; + int newsize; + int mod; + + //assert( data ); + assert( amount > 0 ); + + mod = amount % STR_ALLOC_GRAN; + if ( !mod ) { + newsize = amount; + } + else { + newsize = amount + STR_ALLOC_GRAN - mod; + } + alloced = newsize; + +#ifdef USE_STRING_DATA_ALLOCATOR + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + + newbuffer = stringDataAllocator.Alloc( alloced ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + +#else + newbuffer = new char[ alloced ]; +#endif + if ( keepold && data ) { + data[ len ] = '\0'; + strcpy( newbuffer, data ); + } + + if ( data && data != baseBuffer ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Free( data ); +#else + delete [] data; +#endif + } + + data = newbuffer; +} + +/* +============ +idStr::FreeData +============ +*/ +void idStr::FreeData( void ) { + if ( data && data != baseBuffer ) { +#ifdef USE_STRING_DATA_ALLOCATOR +// RAVEN BEGIN +// jnewquist: Ignore free request if allocator is empty, probably shutdown + if ( stringDataAllocator.GetNumUsedBlocks() > 0 ) { + stringDataAllocator.Free( data ); + } +// RAVEN END +#else + delete[] data; +#endif + +// RAVEN BEGIN +// jsinger: was exhibiting a buffer overrun when an idStr was contained in an idList +// due to having the wrong alloced value. This corrects that + Init(); +// RAVEN END + } +} + +/* +============ +idStr::operator= +============ +*/ +void idStr::operator=( const char *text ) { + int l; + int diff; + int i; + + if ( !text ) { + // safe behaviour if NULL + EnsureAlloced( 1, false ); + data[ 0 ] = '\0'; + len = 0; + return; + } + + if ( text == data ) { + return; // copying same thing + } + + // check if we're aliasing + if ( text >= data && text <= data + len ) { + diff = text - data; + + assert( strlen( text ) < (unsigned)len ); + + for ( i = 0; text[ i ]; i++ ) { + data[ i ] = text[ i ]; + } + + data[ i ] = '\0'; + + len -= diff; + + return; + } + + l = strlen( text ); + EnsureAlloced( l + 1, false ); + strcpy( data, text ); + len = l; +} + +/* +============ +idStr::FindChar + +returns -1 if not found otherwise the index of the char +============ +*/ +int idStr::FindChar( const char *str, const char c, int start, int end ) { + int i; + + if ( end == -1 ) { + end = strlen( str ) - 1; + } + for ( i = start; i <= end; i++ ) { + if ( str[i] == c ) { + return i; + } + } + return -1; +} + +/* +============ +idStr::FindText + +returns -1 if not found otherwise the index of the text +============ +*/ +int idStr::FindText( const char *str, const char *text, bool casesensitive, int start, int end ) { + int l, i, j; + + if ( end == -1 ) { + end = strlen( str ); + } + l = end - strlen( text ); + for ( i = start; i <= l; i++ ) { + if ( casesensitive ) { + for ( j = 0; text[j]; j++ ) { + if ( str[i+j] != text[j] ) { + break; + } + } + } else { + for ( j = 0; text[j]; j++ ) { + if ( ::toupper( str[i+j] ) != ::toupper( text[j] ) ) { + break; + } + } + } + if ( !text[j] ) { + return i; + } + } + return -1; +} + +/* +============ +idStr::Filter + +Returns true if the string conforms the given filter. +Several metacharacter may be used in the filter. + +* match any string of zero or more characters +? match any single character +[abc...] match any of the enclosed characters; a hyphen can + be used to specify a range (e.g. a-z, A-Z, 0-9) + +============ +*/ +bool idStr::Filter( const char *filter, const char *name, bool casesensitive ) { + idStr buf; + int i, found, index; + + while(*filter) { + if (*filter == '*') { + filter++; + buf.Empty(); + for (i = 0; *filter; i++) { + if ( *filter == '*' || *filter == '?' || (*filter == '[' && *(filter+1) != '[') ) { + break; + } + buf += *filter; + if ( *filter == '[' ) { + filter++; + } + filter++; + } + if ( buf.Length() ) { + index = idStr(name).Find( buf.c_str(), casesensitive ); + if ( index == -1 ) { + return false; + } + name += index + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[') { + if ( *(filter+1) == '[' ) { + if ( *name != '[' ) { + return false; + } + filter += 2; + name++; + } + else { + filter++; + found = false; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') { + break; + } + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) { + found = true; + } + } + else { + if ( ::toupper(*name) >= ::toupper(*filter) && ::toupper(*name) <= ::toupper(*(filter+2)) ) { + found = true; + } + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) { + found = true; + } + } + else { + if ( ::toupper(*filter) == ::toupper(*name) ) { + found = true; + } + } + filter++; + } + } + if (!found) { + return false; + } + while(*filter) { + if ( *filter == ']' && *(filter+1) != ']' ) { + break; + } + filter++; + } + filter++; + name++; + } + } + else { + if (casesensitive) { + if (*filter != *name) { + return false; + } + } + else { + if ( ::toupper(*filter) != ::toupper(*name) ) { + return false; + } + } + filter++; + name++; + } + } + return true; +} + +/* +============= +idStr::StripMediaName + + makes the string lower case, replaces backslashes with forward slashes, and removes extension +============= +*/ +void idStr::StripMediaName( const char *name, idStr &mediaName ) { + char c; + + mediaName.Empty(); + + for ( c = *name; c; c = *(++name) ) { + // truncate at an extension + if ( c == '.' ) { + break; + } + // convert backslashes to forward slashes + if ( c == '\\' ) { + mediaName.Append( '/' ); + } else { + mediaName.Append( idStr::ToLower( c ) ); + } + } +} + +/* +============= +idStr::CheckExtension +============= +*/ +bool idStr::CheckExtension( const char *name, const char *ext ) { + const char *s1 = name + Length( name ) - 1; + const char *s2 = ext + Length( ext ) - 1; + int c1, c2, d; + + do { + c1 = *s1--; + c2 = *s2--; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return false; + } + } while( s1 > name && s2 > ext ); + + return ( s1 >= name ); +} + +/* +============= +idStr::FloatArrayToString +============= +*/ +const char *idStr::FloatArrayToString( const float *array, const int length, const int precision ) { + static int index = 0; + static char str[4][16384]; // in case called by nested functions + int i, n; + char format[16], *s; + + // use an array of string so that multiple calls won't collide + s = str[ index ]; + index = (index + 1) & 3; + + idStr::snPrintf( format, sizeof( format ), "%%.%df", precision ); + n = idStr::snPrintf( s, sizeof( str[0] ), format, array[0] ); + if ( precision > 0 ) { + while( n > 0 && s[n-1] == '0' ) s[--n] = '\0'; + while( n > 0 && s[n-1] == '.' ) s[--n] = '\0'; + } + idStr::snPrintf( format, sizeof( format ), " %%.%df", precision ); + for ( i = 1; i < length; i++ ) { + n += idStr::snPrintf( s + n, sizeof( str[0] ) - n, format, array[i] ); + if ( precision > 0 ) { + while( n > 0 && s[n-1] == '0' ) s[--n] = '\0'; + while( n > 0 && s[n-1] == '.' ) s[--n] = '\0'; + } + } + return s; +} + +/* +============ +idStr::Last + +returns -1 if not found otherwise the index of the char +============ +*/ +int idStr::Last( const char c ) const { + int i; + + for( i = Length(); i > 0; i-- ) { + if ( data[ i - 1 ] == c ) { + return i - 1; + } + } + + return -1; +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripLeading( const char c ) { + while( data[ 0 ] == c ) { + memmove( &data[ 0 ], &data[ 1 ], len ); + len--; + } +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripLeading( const char *string ) { + int l; + + l = strlen( string ); + if ( l > 0 ) { + while ( !Cmpn( string, l ) ) { + memmove( data, data + l, len - l + 1 ); + len -= l; + } + } +} + +/* +============ +idStr::StripLeadingOnce +============ +*/ +bool idStr::StripLeadingOnce( const char *string ) { + int l; + + l = strlen( string ); + if ( ( l > 0 ) && !Cmpn( string, l ) ) { + memmove( data, data + l, len - l + 1 ); + len -= l; + return true; + } + return false; +} + +/* +============ +idStr::StripTrailing +============ +*/ +void idStr::StripTrailing( const char c ) { + int i; + + for( i = Length(); i > 0 && data[ i - 1 ] == c; i-- ) { + data[ i - 1 ] = '\0'; + len--; + } +} + +/* +============ +idStr::StripLeading +============ +*/ +void idStr::StripTrailing( const char *string ) { + int l; + + l = strlen( string ); + if ( l > 0 ) { + while ( ( len >= l ) && !Cmpn( string, data + len - l, l ) ) { + len -= l; + data[len] = '\0'; + } + } +} + +/* +============ +idStr::StripTrailingOnce +============ +*/ +bool idStr::StripTrailingOnce( const char *string ) { + int l; + + l = strlen( string ); + if ( ( l > 0 ) && ( len >= l ) && !Cmpn( string, data + len - l, l ) ) { + len -= l; + data[len] = '\0'; + return true; + } + return false; +} + +/* +============ +idStr::Replace +============ +*/ +// RAVEN BEGIN +// scork: this needs to return an int of the replacement count, like MS CString etc, otherwise you can't do things like this: +// +// idStr str; +// while (str.Replace("\n\n","\n")) {}; +// +// ... to remove blank lines from an output string if you have 3 blank lines in a row. Without the while(), you'd just be guessing about +// converting (eg) 3 blank lines to 2, then 2 to 1, depending on how many times you straight-line the Replace() call. +// +int idStr::Replace( const char *old, const char *nw ) { + int iReplaced = 0; + int oldLen, newLen, i, j, count; + idStr oldString( data ); + + oldLen = strlen( old ); + newLen = strlen( nw ); + + // Work out how big the new string will be + count = 0; + for( i = 0; i < oldString.Length(); i++ ) { + if( !idStr::Cmpn( &oldString[i], old, oldLen ) ) { + count++; + i += oldLen - 1; + } + } + + if( count ) { + EnsureAlloced( len + ( ( newLen - oldLen ) * count ) + 2, false ); + + // Replace the old data with the new data + for( i = 0, j = 0; i < oldString.Length(); i++ ) { + if( !idStr::Cmpn( &oldString[i], old, oldLen ) ) { + memcpy( data + j, nw, newLen ); + i += oldLen - 1; + j += newLen; + iReplaced++; + } else { + data[j] = oldString[i]; + j++; + } + } + data[j] = 0; + len = strlen( data ); + } + return iReplaced; +} +// RAVEN END + +/* +============ +idStr::Mid +============ +*/ +const char *idStr::Mid( int start, int len, idStr &result ) const { + int i; + + result.Empty(); + + i = Length(); + if ( i == 0 || len <= 0 || start >= i ) { + return NULL; + } + + if ( start + len >= i ) { + len = i - start; + } + + result.Append( &data[ start ], len ); + return result; +} + +/* +============ +idStr::Mid +============ +*/ +idStr idStr::Mid( int start, int len ) const { + int i; + idStr result; + + i = Length(); + if ( i == 0 || len <= 0 || start >= i ) { + return result; + } + + if ( start + len >= i ) { + len = i - start; + } + + result.Append( &data[ start ], len ); + return result; +} + +/* +============ +idStr::StripTrailingWhitespace +============ +*/ +void idStr::StripTrailingWhitespace( void ) { + int i; + + // cast to unsigned char to prevent stripping off high-ASCII characters + for( i = Length(); i > 0 && (unsigned char)(data[ i - 1 ]) <= ' '; i-- ) { + data[ i - 1 ] = '\0'; + len--; + } +} + +// RAVEN BEGIN +/* +============ +idStr::StripUntil +============ +*/ +void idStr::StripUntil( const char c ) +{ + while( data[ 0 ] != c && len ) { + memmove( &data[ 0 ], &data[ 1 ], len ); + len--; + } +} +// RAVEN END + +/* +===================================================================== + + info strings + +===================================================================== +*/ + +/* +============ +idStr::StripQuotes + +Removes the quotes from the beginning and end of the string +============ +*/ +idStr& idStr::StripQuotes ( void ) +{ + if ( data[0] != '\"' ) + { + return *this; + } + + // Remove the trailing quote first + if ( data[len-1] == '\"' ) + { + data[len-1] = '\0'; + len--; + } + + // Strip the leading quote now + len--; + memmove( &data[ 0 ], &data[ 1 ], len ); + data[len] = '\0'; + + return *this; +} + +/* +===================================================================== + + filename methods + +===================================================================== +*/ + +/* +============ +idStr::FileNameHash +============ +*/ +int idStr::FileNameHash( void ) const { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while( data[i] != '\0' ) { + letter = idStr::ToLower( data[i] ); + if ( letter == '.' ) { + break; // don't include extension + } + if ( letter =='\\' ) { + letter = '/'; + } + hash += (long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +============ +idStr::BackSlashesToSlashes +============ +*/ +idStr &idStr::BackSlashesToSlashes( void ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == '\\' ) { + data[ i ] = '/'; + } + } + return *this; +} + +// RAVEN BEGIN +// jscott: for file systems that require backslashes +/* +============ +idStr::SlashesToBackSlashes +============ +*/ +idStr &idStr::SlashesToBackSlashes( void ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == '/' ) { + data[ i ] = '\\'; + } + } + return *this; +} + +// nmckenzie: char replacing routine + +/* +============ +idStr::SlashesToBackSlashes +============ +*/ + +bool idStr::HasChar( const char check ){ + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == check ) { + return true; + } + } + return false; +} + +/* +============ +idStr::HasChars +============ +*/ + +bool idStr::HasChars( const char *check ){ + int i; + + while( *check ){ + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == *check ) { + return true; + } + } + check++; + } + return false; +} + +/* +============ +idStr::ReplaceChar +============ +*/ + +idStr &idStr::ReplaceChar( const char from, const char to ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == from ) { + data[ i ] = to; + } + } + return *this; +} + +/* +============ +idStr::ReplaceChars +============ +*/ + +idStr &idStr::ReplaceChars( const char *from, const char to ) { + int i; + + while( *from ){ + for ( i = 0; i < len; i++ ) { + if ( data[ i ] == *from ) { + data[ i ] = to; + } + } + from++; + } + return *this; +} + +// RAVEN END + +/* +============ +idStr::SetFileExtension +============ +*/ +idStr &idStr::SetFileExtension( const char *extension ) { + StripFileExtension(); + if ( *extension != '.' ) { + Append( '.' ); + } + Append( extension ); + return *this; +} + +/* +============ +idStr::StripFileExtension +============ +*/ +idStr &idStr::StripFileExtension( void ) { + int i; + + for ( i = len-1; i >= 0; i-- ) { + if ( data[i] == '.' ) { + data[i] = '\0'; + len = i; + break; + } + } + return *this; +} + +/* +============ +idStr::StripAbsoluteFileExtension +============ +*/ +idStr &idStr::StripAbsoluteFileExtension( void ) { + int i; + + for ( i = 0; i < len; i++ ) { + if ( data[i] == '.' ) { + data[i] = '\0'; + len = i; + break; + } + } + + return *this; +} + +/* +================== +idStr::DefaultFileExtension +================== +*/ +idStr &idStr::DefaultFileExtension( const char *extension ) { + int i; + + // do nothing if the string already has an extension + for ( i = len-1; i >= 0; i-- ) { + if ( data[i] == '.' ) { + return *this; + } + } + if ( *extension != '.' ) { + Append( '.' ); + } + Append( extension ); + return *this; +} + +/* +================== +idStr::DefaultPath +================== +*/ +idStr &idStr::DefaultPath( const char *basepath ) { + if ( ( ( *this )[ 0 ] == '/' ) || ( ( *this )[ 0 ] == '\\' ) ) { + // absolute path location + return *this; + } + + *this = basepath + *this; + return *this; +} + +/* +==================== +idStr::AppendPath +==================== +*/ +void idStr::AppendPath( const char *text ) { + int pos; + int i = 0; + + if ( text && text[i] ) { + pos = len; + EnsureAlloced( len + strlen( text ) + 2 ); + + if ( pos ) { + if ( data[ pos-1 ] != '/' ) { + data[ pos++ ] = '/'; + } + } + if ( text[i] == '/' ) { + i++; + } + + for ( ; text[ i ]; i++ ) { + if ( text[ i ] == '\\' ) { + data[ pos++ ] = '/'; + } else { + data[ pos++ ] = text[ i ]; + } + } + len = pos; + data[ pos ] = '\0'; + } +} + +/* +================== +idStr::StripFilename +================== +*/ +idStr &idStr::StripFilename( void ) { + int pos; + + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos ] != '/' ) && ( ( *this )[ pos ] != '\\' ) ) { + pos--; + } + + if ( pos < 0 ) { + pos = 0; + } + + CapLength( pos ); + return *this; +} + +/* +================== +idStr::StripPath +================== +*/ +idStr &idStr::StripPath( void ) { + int pos; + + pos = Length(); + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + *this = Right( Length() - pos ); + return *this; +} + +/* +==================== +idStr::ExtractFilePath +==================== +*/ +void idStr::ExtractFilePath( idStr &dest ) const { + int pos; + + // + // back up until a \ or the start + // + pos = Length(); + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + Left( pos, dest ); +} + +/* +==================== +idStr::ExtractFileName +==================== +*/ +void idStr::ExtractFileName( idStr &dest ) const { + int pos; + + // + // back up until a \ or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + Right( Length() - pos, dest ); +} + +/* +==================== +idStr::ExtractFileBase +==================== +*/ +void idStr::ExtractFileBase( idStr &dest ) const { + int pos; + int start; + + // + // back up until a \ or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '/' ) && ( ( *this )[ pos - 1 ] != '\\' ) ) { + pos--; + } + + start = pos; +// RAVEN BEGIN +// jscott: for getting the file base out of addnormals() style filenames + while( ( pos < Length() ) && ( ( *this )[ pos ] != '.' ) && ( ( *this )[ pos ] != ',' ) ) { +// RAVEN END + pos++; + } + + Mid( start, pos - start, dest ); +} + +/* +==================== +idStr::ExtractFileExtension +==================== +*/ +void idStr::ExtractFileExtension( idStr &dest ) const { + int pos; + + // + // back up until a . or the start + // + pos = Length() - 1; + while( ( pos > 0 ) && ( ( *this )[ pos - 1 ] != '.' ) ) { + pos--; + } + + if ( !pos ) { + // no extension + dest.Empty(); + } else { + Right( Length() - pos, dest ); + } +} + +// RAVEN BEGIN +// twhitaker: Turns a bad file name into a good one or your money back +void idStr::ScrubFileName( void ) +{ + int i; + + RemoveEscapes(); + StripFileExtension(); + + for ( i = 0; i < len; i++ ) { + if( !idStr::upperCaseCharacter[data[i]] && !isdigit(data[i]) ) { + data[i] = '_'; + } + } +} + +// jscott: like the declManager version, but globally accessable +void idStr::MakeNameCanonical( void ) +{ + ToLower(); + BackSlashesToSlashes(); + StripFileExtension(); +} + +void idStr::EnsurePrintable( void ) { + + int i; + + for( i = 0; i < len; i++ ) { + + if( !CharIsPrintable( data[i] ) ) { + + memmove( &data[i], &data[i + 1], len - i ); + len--; + } + } +} +// RAVEN END + +/* +===================================================================== + + char * methods to replace library functions + +===================================================================== +*/ + +/* +============ +idStr::IsNumeric + +Checks a string to see if it contains only numerical values. +============ +*/ +bool idStr::IsNumeric( const char *s ) { + int i; + bool dot; + + if ( *s == '-' ) { + s++; + } + + dot = false; + for ( i = 0; s[i]; i++ ) { + if ( !isdigit( ( byte )s[i] ) ) { + if ( ( s[ i ] == '.' ) && !dot ) { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +/* +============ +idStr::HasLower + +Checks if a string has any lowercase chars +============ +*/ +bool idStr::HasLower( const char *s ) { + if ( !s ) { + return false; + } + + while ( *s ) { + if ( CharIsLower( *s ) ) { + return true; + } + s++; + } + + return false; +} + +/* +============ +idStr::HasUpper + +Checks if a string has any uppercase chars +============ +*/ +bool idStr::HasUpper( const char *s ) { + if ( !s ) { + return false; + } + + while ( *s ) { + if ( CharIsUpper( *s ) ) { + return true; + } + s++; + } + + return false; +} + +/* +================ +idStr::Cmp +================ +*/ +int idStr::Cmp( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + if ( d ) { + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Cmpn +================ +*/ +int idStr::Cmpn( const char *s1, const char *s2, int n ) { + int c1, c2, d; + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + if ( d ) { + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmp +================ +*/ +int idStr::Icmp( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmpn +================ +*/ +int idStr::Icmpn( const char *s1, const char *s2, int n ) { + int c1, c2, d; + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::Icmp +================ +*/ +// RAVEN BEGIN +// bdube: escape codes +int idStr::IcmpNoEscape ( const char *s1, const char *s2 ) { + int c1, c2, d; + + do { + for ( d = idStr::IsEscape( s1 ); d; d = idStr::IsEscape( s1 ) ) { + s1 += d; + } + for ( d = idStr::IsEscape( s2 ); d; d = idStr::IsEscape( s2 ) ) { + s2 += d; + } +// RAVEN END + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; // strings are equal +} + +/* +================ +idStr::IcmpPath +================ +*/ +int idStr::IcmpPath( const char *s1, const char *s2 ) { + int c1, c2, d; + +#if 0 +//#if !defined( _WIN32 ) + idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" ); +#endif + + do { + c1 = *s1++; + c2 = *s2++; + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c1 == '\\' ) { + d += ('/' - '\\'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 == '\\' ) { + d -= ('/' - '\\'); + if ( !d ) { + break; + } + } + // make sure folders come first + while( c1 ) { + if ( c1 == '/' || c1 == '\\' ) { + break; + } + c1 = *s1++; + } + while( c2 ) { + if ( c2 == '/' || c2 == '\\' ) { + break; + } + c2 = *s2++; + } + if ( c1 && !c2 ) { + return -1; + } else if ( !c1 && c2 ) { + return 1; + } + // same folder depth so use the regular compare + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; +} + +/* +================ +idStr::IcmpnPath +================ +*/ +int idStr::IcmpnPath( const char *s1, const char *s2, int n ) { + int c1, c2, d; + +#if 0 +//#if !defined( _WIN32 ) + idLib::common->Printf( "WARNING: IcmpPath used on a case-sensitive filesystem?\n" ); +#endif + + assert( n >= 0 ); + + do { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) { + return 0; // strings are equal until end point + } + + d = c1 - c2; + while( d ) { + if ( c1 <= 'Z' && c1 >= 'A' ) { + d += ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c1 == '\\' ) { + d += ('/' - '\\'); + if ( !d ) { + break; + } + } + if ( c2 <= 'Z' && c2 >= 'A' ) { + d -= ('a' - 'A'); + if ( !d ) { + break; + } + } + if ( c2 == '\\' ) { + d -= ('/' - '\\'); + if ( !d ) { + break; + } + } + // make sure folders come first + while( c1 ) { + if ( c1 == '/' || c1 == '\\' ) { + break; + } + c1 = *s1++; + } + while( c2 ) { + if ( c2 == '/' || c2 == '\\' ) { + break; + } + c2 = *s2++; + } + if ( c1 && !c2 ) { + return -1; + } else if ( !c1 && c2 ) { + return 1; + } + // same folder depth so use the regular compare + return ( INTSIGNBITNOTSET( d ) << 1 ) - 1; + } + } while( c1 ); + + return 0; +} + +/* +============= +idStr::Copynz + +Safe strncpy that ensures a trailing zero +============= +*/ +void idStr::Copynz( char *dest, const char *src, int destsize ) { + if ( !src ) { + idLib::common->Warning( "idStr::Copynz: NULL src" ); + return; + } + if ( destsize < 1 ) { + idLib::common->Warning( "idStr::Copynz: destsize < 1" ); + return; + } + + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} + +/* +================ +idStr::Append + + never goes past bounds or leaves without a terminating 0 +================ +*/ +void idStr::Append( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + idLib::common->Error( "idStr::Append: already overflowed" ); + } + idStr::Copynz( dest + l1, src, size - l1 ); +} + +// bdube: escape codes +/* +================ +idStr::LengthWithoutEscapes +================ +*/ +int idStr::LengthWithoutEscapes( const char *s ) { + int len; + const char *p; + + if ( !s ) { + return 0; + } + + len = 0; + p = s; + while( *p ) { + int esc; + esc = idStr::IsEscape ( p ); + if ( esc ) { + p += esc; + continue; + } + p++; + len++; + } + + return len; +} + +/* +================ +idStr::RemoveEscapes +================ +*/ +char *idStr::RemoveEscapes( char *string, int escapes ) { + char *d; + char *s; + int c; + + s = string; + d = string; + while( (c = *s) != 0 ) { + int esc; + int type; + esc = idStr::IsEscape( s, &type ); + if ( esc && (type & escapes) ) { + s += esc; + continue; + } + else { + *d++ = c; + if ( c == C_COLOR_ESCAPE && *(s+1) ) { + s++; + } + } + s++; + } + *d = '\0'; + + return string; +} + +/* +================ +idStr::IsEscape +================ +*/ +int idStr::IsEscape( const char *s, int* type ) { + if ( !s || *s != C_COLOR_ESCAPE || *(s+1) == C_COLOR_ESCAPE ) { + return 0; + } + if ( type ) { + *type = S_ESCAPE_UNKNOWN; + } + switch ( *(s+1) ) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case ':': + if ( type ) { + *type = S_ESCAPE_COLORINDEX; + } + return 2; + + case '-': case '+': + if ( type ) { + *type = S_ESCAPE_COLOR; + } + return 2; + + case 'r': case 'R': + if ( type ) { + *type = S_ESCAPE_COMMAND; + } + return 2; + + case 'c': case 'C': + if ( *(s+2) ) { + if ( *(s+3) ) { + if ( *(s+4) ) { + if ( type ) { + *type = S_ESCAPE_COLOR; + } + return 5; + } + } + } + return 0; + + case 'n': case 'N': + if ( type ) { + *type = S_ESCAPE_COMMAND; + } + if ( *(s+2) ) { + return 3; + } + return 0; + + case 'i': case 'I': + if ( *(s+2) && *(s+3) && *(s+4) ) { + if ( type ) { + *type = S_ESCAPE_ICON; + } + return 5; + } + return 0; + } + return 0; +} + +// RAVEN END + +/* +================ +idStr::snPrintf +================ +*/ +int idStr::snPrintf( char *dest, int size, const char *fmt, ...) { + int len; + va_list argptr; + char buffer[32000]; // big, but small enough to fit in PPC stack + + va_start( argptr, fmt ); + len = vsprintf( buffer, fmt, argptr ); + va_end( argptr ); + if ( len >= sizeof( buffer ) ) { + idLib::common->Error( "idStr::snPrintf: overflowed buffer" ); + } + if ( len >= size ) { + idLib::common->Warning( "idStr::snPrintf: overflow of %i in %i\n", len, size ); + len = size; + } + idStr::Copynz( dest, buffer, size ); + return len; +} + +/* +============ +idStr::vsnPrintf + +vsnprintf portability: + +C99 standard: vsnprintf returns the number of characters (excluding the trailing +'\0') which would have been written to the final string if enough space had been available +snprintf and vsnprintf do not write more than size bytes (including the trailing '\0') + +win32: _vsnprintf returns the number of characters written, not including the terminating null character, +or a negative value if an output error occurs. If the number of characters to write exceeds count, then count +characters are written and -1 is returned and no trailing '\0' is added. + +idStr::vsnPrintf: always appends a trailing '\0', returns number of characters written (not including terminal \0) +or returns -1 on failure or if the buffer would be overflowed. +============ +*/ +int idStr::vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ) { + int ret; + +#ifdef _WIN32 +#undef _vsnprintf + ret = _vsnprintf( dest, size-1, fmt, argptr ); +#define _vsnprintf use_idStr_vsnPrintf +#else +#undef vsnprintf + ret = vsnprintf( dest, size, fmt, argptr ); +#define vsnprintf use_idStr_vsnPrintf +#endif + dest[size-1] = '\0'; + if ( ret < 0 || ret >= size ) { + return -1; + } + return ret; +} + +/* +============ +sprintf + +Sets the value of the string using a printf interface. +============ +*/ +int sprintf( idStr &string, const char *fmt, ... ) { + int l; + va_list argptr; + char buffer[32000]; + + va_start( argptr, fmt ); + l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr ); + va_end( argptr ); + buffer[sizeof(buffer)-1] = '\0'; + + string = buffer; + return l; +} + +/* +============ +vsprintf + +Sets the value of the string using a vprintf interface. +============ +*/ +int vsprintf( idStr &string, const char *fmt, va_list argptr ) { + int l; + char buffer[32000]; + + l = idStr::vsnPrintf( buffer, sizeof(buffer)-1, fmt, argptr ); + buffer[sizeof(buffer)-1] = '\0'; + + string = buffer; + return l; +} + +/* +============ +va + +does a varargs printf into a temp buffer +NOTE: not thread safe +============ +*/ +char *va( const char *fmt, ... ) { + va_list argptr; + static int index = 0; +// RAVEN BEGIN +// scork: tweaked from 4 to 8, since one of my funcs uses it twice. Better safe than sorry + static char string[VA_NUM_BUFS][VA_BUF_LEN]; // in case called by nested functions + char *buf; + + buf = string[index]; + index = (index + 1) & 7; +// RAVEN END + + va_start( argptr, fmt ); + vsprintf( buf, fmt, argptr ); + va_end( argptr ); + + return buf; +} + +#ifdef _WIN32 +/* +============ +fe + +Used for formatting windows errors +NOTE: not thread safe +============ +*/ +// RAVEN BEGIN +// abahr +char *fe( int errorId ) { + static int index = 0; + static char string[4][256]; // in case called by nested functions + char *buf; + + buf = string[index]; + index = (index + 1) & 3; +#ifndef _XBOX + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errorId, + 0, // Default language + (LPTSTR) buf, + 256, + NULL + ); +#endif + return buf; +} +#endif +// RAVEN END + +/* +============ +idStr::BestUnit +============ +*/ +int idStr::BestUnit( const char *format, float value, Measure_t measure ) { + int unit = 1; + while ( unit <= 3 && ( 1 << ( unit * 10 ) < value ) ) { + unit++; + } + unit--; + value /= 1 << ( unit * 10 ); + sprintf( *this, format, value ); + *this += " "; + *this += units[ measure ][ unit ]; + return unit; +} + +/* +============ +idStr::SetUnit +============ +*/ +void idStr::SetUnit( const char *format, float value, int unit, Measure_t measure ) { + value /= 1 << ( unit * 10 ); + sprintf( *this, format, value ); + *this += " "; + *this += units[ measure ][ unit ]; +} + +/* +================ +idStr::InitMemory +================ +*/ +void idStr::InitMemory( void ) { +#ifdef USE_STRING_DATA_ALLOCATOR +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_STRING); +// RAVEN END + stringDataAllocator.Init(); +#endif +} + +/* +================ +idStr::ShutdownMemory +================ +*/ +void idStr::ShutdownMemory( void ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.Shutdown(); +#endif +} + +/* +================ +idStr::PurgeMemory +================ +*/ +void idStr::PurgeMemory( void ) { +#ifdef USE_STRING_DATA_ALLOCATOR + stringDataAllocator.FreeEmptyBaseBlocks(); +#endif +} + +/* +================ +idStr::ShowMemoryUsage_f +================ +*/ +void idStr::ShowMemoryUsage_f( const idCmdArgs &args ) { +#ifdef USE_STRING_DATA_ALLOCATOR + idLib::common->Printf( "%6d KB string memory (%d KB free in %d blocks, %d empty base blocks)\n", + stringDataAllocator.GetBaseBlockMemory() >> 10, stringDataAllocator.GetFreeBlockMemory() >> 10, + stringDataAllocator.GetNumFreeBlocks(), stringDataAllocator.GetNumEmptyBaseBlocks() ); +#endif +} + +/* +================ +idStr::FormatNumber +================ +*/ +struct formatList_t { + int gran; + int count; +}; + +// elements of list need to decend in size +formatList_t formatList[] = { + { 1000000000, 0 }, + { 1000000, 0 }, + { 1000, 0 } +}; + +int numFormatList = sizeof(formatList) / sizeof( formatList[0] ); + + +idStr idStr::FormatNumber( int number ) { + idStr string; + bool hit; + + // reset + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + li->count = 0; + } + + // main loop + do { + hit = false; + + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + + if ( number >= li->gran ) { + li->count++; + number -= li->gran; + hit = true; + break; + } + } + } while ( hit ); + + // print out + bool found = false; + + for ( int i = 0; i < numFormatList; i++ ) { + formatList_t *li = formatList + i; + + if ( li->count ) { + if ( !found ) { + string += va( "%i,", li->count ); + } else { + string += va( "%3.3i,", li->count ); + } + found = true; + } + else if ( found ) { + string += va( "%3.3i,", li->count ); + } + } + + if ( found ) { + string += va( "%3.3i", number ); + } + else { + string += va( "%i", number ); + } + + // pad to proper size + int count = 11 - string.Length(); + + for ( int i = 0; i < count; i++ ) { + string.Insert( " ", 0 ); + } + + return string; +} + +// RAVEN BEGIN +// abahr +/* +================ +idStr::Split +================ +*/ +void idStr::Split( const char* source, idList& list, const char delimiter, const char groupDelimiter ) { + const idStr localSource( source ); + int sourceLength = localSource.Length(); + idStr element; + int startIndex = 0; + int endIndex = -1; + char currentChar = '\0'; + + list.Clear(); + while( startIndex < sourceLength ) { + currentChar = localSource[ startIndex ]; + if( currentChar == groupDelimiter ) { + endIndex = localSource.Find( groupDelimiter, ++startIndex ); + if( endIndex == -1 ) { + common->Error( "Couldn't find expected char %c in idStr::Split\n", groupDelimiter ); + } + element = localSource.Mid( startIndex, endIndex ); + element.Strip( groupDelimiter ); + list.Append( element ); + element.Clear(); + startIndex = endIndex + 1; + continue; + } else if( currentChar == delimiter ) { + element += '\0'; + list.Append( element ); + element.Clear(); + endIndex = ++startIndex; + continue; + } + + startIndex++; + element += currentChar; + } + + if( element.Length() ) { + element += '\0'; + list.Append( element ); + } +} + +/* +================ +idStr::Split +================ +*/ +void idStr::Split( idList& list, const char delimiter, const char groupDelimiter ) { + Split( c_str(), list, delimiter, groupDelimiter ); +} +// RAVEN END + +idStr idStr::GetLastColorCode( void ) const { + for ( int i = Length(); i > 0; i-- ) { + int escapeType = 0; + int escapeLength = idStr::IsEscape( &data[i-1], &escapeType ); + + if ( escapeLength && ( escapeType == S_ESCAPE_COLORINDEX || S_ESCAPE_COLOR ) ) { + idStr result = ""; + result.Append( &data[i-1], escapeLength ); + return result; + } + } + + return ""; +} diff --git a/src/idlib/Str.h b/src/idlib/Str.h new file mode 100644 index 0000000..d8b2f2c --- /dev/null +++ b/src/idlib/Str.h @@ -0,0 +1,1123 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __STR_H__ +#define __STR_H__ + +/* +=============================================================================== + + Character string + +=============================================================================== +*/ + +// these library functions should not be used for cross platform compatibility +#define strcmp idStr::Cmp // use_idStr_Cmp +#define strncmp use_idStr_Cmpn +#define StrCmpN use_idStr_Cmpn +#define strcmpi use_idStr_Icmp +#define StrCmpI use_idStr_Icmp +#define stricmp idStr::Icmp // use_idStr_Icmp +#define _stricmp use_idStr_Icmp +#define strcasecmp use_idStr_Icmp +#define strnicmp use_idStr_Icmpn +#define _strnicmp use_idStr_Icmpn +#define _memicmp use_idStr_Icmpn +#define StrCmpNI use_idStr_Icmpn +#define snprintf use_idStr_snPrintf +#define _snprintf use_idStr_snPrintf +#define vsnprintf use_idStr_vsnPrintf +#define _vsnprintf use_idStr_vsnPrintf + +#ifdef _XENON +class __declspec(align(16)) idVec4; +#else +class idVec4; +#endif + +#ifndef FILE_HASH_SIZE +#define FILE_HASH_SIZE 1024 +#endif + +// color escape character +const int C_COLOR_ESCAPE = '^'; +const int C_COLOR_DEFAULT = '0'; +const int C_COLOR_RED = '1'; +const int C_COLOR_GREEN = '2'; +const int C_COLOR_YELLOW = '3'; +const int C_COLOR_BLUE = '4'; +const int C_COLOR_CYAN = '5'; +const int C_COLOR_MAGENTA = '6'; +const int C_COLOR_WHITE = '7'; +const int C_COLOR_GRAY = '8'; +const int C_COLOR_BLACK = '9'; +// RAVEN BEGIN +// bdube: added +const int C_COLOR_CONSOLE = ':'; +// RAVEN END + +// color escape string +#define S_COLOR_DEFAULT "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" +#define S_COLOR_GRAY "^8" +#define S_COLOR_BLACK "^9" +// RAVEN BEGIN +// bdube: added +#define S_COLOR_CONSOLE "^:" +// ddynerman: team colors +#define S_COLOR_MARINE "^c683" +#define S_COLOR_STROGG "^c950" +#define S_COLOR_ALERT "^c920" +// ddynerman: MP icons +#define I_VOICE_ENABLED "^ivce" +#define I_VOICE_DISABLED "^ivcd" +#define I_FRIEND_ENABLED "^ifde" +#define I_FRIEND_DISABLED "^ifdd" +#define I_FLAG_MARINE "^iflm" +#define I_FLAG_STROGG "^ifls" +#define I_READY "^iyrd" +#define I_NOT_READY "^inrd" +// shouchard: server browser stuff +#define I_SERVER_DEDICATED "^ids0" +#define I_SERVER_DEDICATED_PB "^idsp" +#define I_SERVER_LOCKED "^isl0" +#define I_SERVER_FAVORITE "^isf0" +const int VA_BUF_LEN = 16384; +const int VA_NUM_BUFS = 8; +// RAVEN END + +// make idStr a multiple of 16 bytes long +// don't make too large to keep memory requirements to a minimum +const int STR_ALLOC_BASE = 20; +const int STR_ALLOC_GRAN = 32; + +typedef enum { + MEASURE_SIZE = 0, + MEASURE_BANDWIDTH +} Measure_t; + +// RAVEN BEGIN +// bdube: string escape codes +#define S_ESCAPE_UNKNOWN BIT(0) +#define S_ESCAPE_COLOR BIT(1) +#define S_ESCAPE_COLORINDEX BIT(2) +#define S_ESCAPE_ICON BIT(3) +#define S_ESCAPE_COMMAND BIT(4) +#define S_ESCAPE_ALL ( S_ESCAPE_COLOR | S_ESCAPE_COLORINDEX | S_ESCAPE_ICON | S_ESCAPE_COMMAND ) +// RAVEN END + +class idStr { + +public: + idStr( void ); + idStr( const idStr &text ); + idStr( const idStr &text, int start, int end ); + idStr( const char *text ); + idStr( const char *text, int start, int end ); + explicit idStr( const bool b ); + explicit idStr( const char c ); + explicit idStr( const int i ); + explicit idStr( const unsigned u ); + explicit idStr( const float f ); + ~idStr( void ); + + size_t Size( void ) const; + const char * c_str( void ) const; + operator const char *( void ) const; + operator const char *( void ); + + char operator[]( int index ) const; + char & operator[]( int index ); + + void operator=( const idStr &text ); + void operator=( const char *text ); + + friend idStr operator+( const idStr &a, const idStr &b ); + friend idStr operator+( const idStr &a, const char *b ); + friend idStr operator+( const char *a, const idStr &b ); + + friend idStr operator+( const idStr &a, const float b ); + friend idStr operator+( const idStr &a, const int b ); + friend idStr operator+( const idStr &a, const unsigned b ); + friend idStr operator+( const idStr &a, const bool b ); + friend idStr operator+( const idStr &a, const char b ); + + idStr & operator+=( const idStr &a ); + idStr & operator+=( const char *a ); + idStr & operator+=( const float a ); + idStr & operator+=( const char a ); + idStr & operator+=( const int a ); + idStr & operator+=( const unsigned a ); + idStr & operator+=( const bool a ); + + // case sensitive compare + friend bool operator==( const idStr &a, const idStr &b ); + friend bool operator==( const idStr &a, const char *b ); + friend bool operator==( const char *a, const idStr &b ); + + // case sensitive compare + friend bool operator!=( const idStr &a, const idStr &b ); + friend bool operator!=( const idStr &a, const char *b ); + friend bool operator!=( const char *a, const idStr &b ); + + // case sensitive compare + int Cmp( const char *text ) const; + int Cmpn( const char *text, int n ) const; + int CmpPrefix( const char *text ) const; + + // case insensitive compare + int Icmp( const char *text ) const; + int Icmpn( const char *text, int n ) const; + int IcmpPrefix( const char *text ) const; + + // case insensitive compare ignoring color +// RAVEN BEGIN +// bdube: changed to escapes + int IcmpNoEscape( const char *text ) const; +// RAVEN END + + // compares paths and makes sure folders come first + int IcmpPath( const char *text ) const; + int IcmpnPath( const char *text, int n ) const; + int IcmpPrefixPath( const char *text ) const; + + int Length( void ) const; + int Allocated( void ) const; + void Empty( void ); + bool IsEmpty( void ) const; + void Clear( void ); + void Append( const char a ); + void Append( const idStr &text ); + void Append( const char *text ); + void Append( const char *text, int len ); + void Insert( const char a, int index ); + void Insert( const char *text, int index ); + void ToLower( void ); + void ToUpper( void ); + bool IsNumeric( void ) const; + bool HasLower( void ) const; + bool HasUpper( void ) const; +// RAVEN BEGIN +// bdube: escapes + int IsEscape( int* type = NULL ) const; + int LengthWithoutEscapes ( void ) const; + idStr & RemoveEscapes ( int escapes = S_ESCAPE_ALL ); +// RAVEN END + void CapLength( int ); + void Fill( const char ch, int newlen ); + int Find( const char c, int start = 0, int end = -1 ) const; + int Find( const char *text, bool casesensitive = true, int start = 0, int end = -1 ) const; + bool Filter( const char *filter, bool casesensitive = true ) const; + int Last( const char c ) const; // return the index to the last occurance of 'c', returns -1 if not found + const char * Left( int len, idStr &result ) const; // store the leftmost 'len' characters in the result + const char * Right( int len, idStr &result ) const; // store the rightmost 'len' characters in the result + const char * Mid( int start, int len, idStr &result ) const; // store 'len' characters starting at 'start' in result + idStr Left( int len ) const; // return the leftmost 'len' characters + idStr Right( int len ) const; // return the rightmost 'len' characters + idStr Mid( int start, int len ) const; // return 'len' characters starting at 'start' + void StripLeading( const char c ); // strip char from front as many times as the char occurs + void StripLeading( const char *string ); // strip string from front as many times as the string occurs + bool StripLeadingOnce( const char *string ); // strip string from front just once if it occurs + void StripTrailing( const char c ); // strip char from end as many times as the char occurs + void StripTrailing( const char *string ); // strip string from end as many times as the string occurs + bool StripTrailingOnce( const char *string ); // strip string from end just once if it occurs + void Strip( const char c ); // strip char from front and end as many times as the char occurs + void Strip( const char *string ); // strip string from front and end as many times as the string occurs + void StripTrailingWhitespace( void ); // strip trailing white space characters +// RAVEN BEGIN + void StripUntil( const char c ); // strip until this character is reached or there is no string left +// RAVEN END + idStr & StripQuotes( void ); // strip quotes around string +// RAVEN BEGIN +// scork: added replacement-count return + int Replace( const char *old, const char *nw ); +// RAVEN END + + + // file name methods + int FileNameHash( void ) const; // hash key for the filename (skips extension) + idStr & BackSlashesToSlashes( void ); // convert slashes +// RAVEN BEGIN +// jscott: + idStr & SlashesToBackSlashes( void ); // convert slashes +// nmckenzie: char swapping routine + bool HasChar( const char check ); + bool HasChars( const char *check ); + idStr & ReplaceChar( const char from, const char to ); + idStr & ReplaceChars( const char *from, const char to ); +// RAVEN END + idStr & SetFileExtension( const char *extension ); // set the given file extension + idStr & StripFileExtension( void ); // remove any file extension + idStr & StripAbsoluteFileExtension( void ); // remove any file extension looking from front (useful if there are multiple .'s) + idStr & DefaultFileExtension( const char *extension ); // if there's no file extension use the default + idStr & DefaultPath( const char *basepath ); // if there's no path use the default + void AppendPath( const char *text ); // append a partial path + idStr & StripFilename( void ); // remove the filename from a path + idStr & StripPath( void ); // remove the path from the filename + void ExtractFilePath( idStr &dest ) const; // copy the file path to another string + void ExtractFileName( idStr &dest ) const; // copy the filename to another string + void ExtractFileBase( idStr &dest ) const; // copy the filename minus the extension to another string + void ExtractFileExtension( idStr &dest ) const; // copy the file extension to another string + bool CheckExtension( const char *ext ); + void ScrubFileName( void ); // Turns a bad file name into a good one or your money back + +// RAVEN BEGIN +// jscott: like the declManager version, but globally accessable + void MakeNameCanonical( void ); + void EnsurePrintable( void ); +// RAVEN END + +// RAVEN BEGIN +// abahr + void Split( idList& list, const char delimiter = ',', const char groupDelimiter = '\'' ); +// RAVEN END + + // char * methods to replace library functions + static int Length( const char *s ); + static char * ToLower( char *s ); + static char * ToUpper( char *s ); + static bool IsNumeric( const char *s ); + static bool HasLower( const char *s ); + static bool HasUpper( const char *s ); +// RAVEN BEGIN +// bdube: escape codes + static int IsEscape( const char *s, int* type = NULL ); + static int LengthWithoutEscapes( const char *s ); + static char * RemoveEscapes( char *s, int escapes = S_ESCAPE_ALL ); +// RAVEN END + static int Cmp( const char *s1, const char *s2 ); + static int Cmpn( const char *s1, const char *s2, int n ); + static int Icmp( const char *s1, const char *s2 ); + static int Icmpn( const char *s1, const char *s2, int n ); +// RAVEN BEGIN +// bdube: escapes + static int IcmpNoEscape( const char *s1, const char *s2 ); +// RAVEN END + static int IcmpPath( const char *s1, const char *s2 ); // compares paths and makes sure folders come first + static int IcmpnPath( const char *s1, const char *s2, int n ); // compares paths and makes sure folders come first + static void Append( char *dest, int size, const char *src ); + static void Copynz( char *dest, const char *src, int destsize ); + static int snPrintf( char *dest, int size, const char *fmt, ... ) id_attribute((format(printf,3,4))); + static int vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ); + static int FindChar( const char *str, const char c, int start = 0, int end = -1 ); + static int FindText( const char *str, const char *text, bool casesensitive = true, int start = 0, int end = -1 ); + static bool Filter( const char *filter, const char *name, bool casesensitive ); + static void StripMediaName( const char *name, idStr &mediaName ); + static bool CheckExtension( const char *name, const char *ext ); + static const char * FloatArrayToString( const float *array, const int length, const int precision ); + + // hash keys + static int Hash( const char *string ); + static int Hash( const char *string, int length ); + static int IHash( const char *string ); // case insensitive + static int IHash( const char *string, int length ); // case insensitive + + // character methods + static char ToLower( byte c ); + static char ToUpper( byte c ); + static bool CharIsPrintable( byte c ); + static bool CharIsLower( byte c ); + static bool CharIsUpper( byte c ); + static bool CharIsAlpha( byte c ); + static bool CharIsNumeric( byte c ); + static bool CharIsNewLine( byte c ); + static bool CharIsTab( byte c ); + static int ColorIndex( int c ); + static idVec4 & ColorForIndex( int i ); + + friend int sprintf( idStr &dest, const char *fmt, ... ); + friend int vsprintf( idStr &dest, const char *fmt, va_list ap ); + + void ReAllocate( int amount, bool keepold ); // reallocate string data buffer + void FreeData( void ); // free allocated string memory + + // format value in the given measurement with the best unit, returns the best unit + int BestUnit( const char *format, float value, Measure_t measure ); + // format value in the requested unit and measurement + void SetUnit( const char *format, float value, int unit, Measure_t measure ); + + static void InitMemory( void ); + static void ShutdownMemory( void ); + static void PurgeMemory( void ); + static void ShowMemoryUsage_f( const idCmdArgs &args ); + + int DynamicMemoryUsed() const; + static idStr FormatNumber( int number ); + + static void Split( const char* source, idList& list, const char delimiter = ',', const char groupDelimiter = '\'' ); + + idStr GetLastColorCode( void ) const; + +protected: + int len; + char * data; + int alloced; + char baseBuffer[ STR_ALLOC_BASE ]; + + void Init( void ); // initialize string using base buffer + void EnsureAlloced( int amount, bool keepold = true ); // ensure string data buffer is large anough + +// RAVEN BEGIN +public: + static const bool printableCharacter[256]; + static const char upperCaseCharacter[256]; + static const char lowerCaseCharacter[256]; +// RAVEN END +}; + +char * va( const char *fmt, ... ) id_attribute((format(printf,1,2))); + +// RAVEN BEGIN +// abahr +char* fe( int errorId ); +// RAVEN END + +ID_INLINE void idStr::EnsureAlloced( int amount, bool keepold ) { + if ( amount > alloced ) { + ReAllocate( amount, keepold ); + } +} + +ID_INLINE void idStr::Init( void ) { + len = 0; + alloced = STR_ALLOC_BASE; + data = baseBuffer; + data[ 0 ] = '\0'; +#ifdef ID_DEBUG_MEMORY + memset( baseBuffer, 0, sizeof( baseBuffer ) ); +#endif +} + +ID_INLINE idStr::idStr( void ) { + Init(); +} + +ID_INLINE idStr::idStr( const idStr &text ) { + int l; + + Init(); + l = text.Length(); + EnsureAlloced( l + 1 ); + strcpy( data, text.data ); + len = l; +} + +ID_INLINE idStr::idStr( const idStr &text, int start, int end ) { + int i; + int l; + + Init(); + if ( end > text.Length() ) { + end = text.Length(); + } + if ( start > text.Length() ) { + start = text.Length(); + } else if ( start < 0 ) { + start = 0; + } + + l = end - start; + if ( l < 0 ) { + l = 0; + } + + EnsureAlloced( l + 1 ); + + for ( i = 0; i < l; i++ ) { + data[ i ] = text[ start + i ]; + } + + data[ l ] = '\0'; + len = l; +} + +ID_INLINE idStr::idStr( const char *text ) { + int l; + + Init(); + if ( text ) { + l = strlen( text ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; + } +} + +ID_INLINE idStr::idStr( const char *text, int start, int end ) { + int i; + int l = strlen( text ); + + Init(); + if ( end > l ) { + end = l; + } + if ( start > l ) { + start = l; + } else if ( start < 0 ) { + start = 0; + } + + l = end - start; + if ( l < 0 ) { + l = 0; + } + + EnsureAlloced( l + 1 ); + + for ( i = 0; i < l; i++ ) { + data[ i ] = text[ start + i ]; + } + + data[ l ] = '\0'; + len = l; +} + +ID_INLINE idStr::idStr( const bool b ) { + Init(); + EnsureAlloced( 2 ); + data[ 0 ] = b ? '1' : '0'; + data[ 1 ] = '\0'; + len = 1; +} + +ID_INLINE idStr::idStr( const char c ) { + Init(); + EnsureAlloced( 2 ); + data[ 0 ] = c; + data[ 1 ] = '\0'; + len = 1; +} + +ID_INLINE idStr::idStr( const int i ) { + char text[ 64 ]; + int l; + + Init(); + l = sprintf( text, "%d", i ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::idStr( const unsigned u ) { + char text[ 64 ]; + int l; + + Init(); + l = sprintf( text, "%u", u ); + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::idStr( const float f ) { + char text[ 64 ]; + int l; + + Init(); + l = idStr::snPrintf( text, sizeof( text ), "%f", f ); + while( l > 0 && text[l-1] == '0' ) text[--l] = '\0'; + while( l > 0 && text[l-1] == '.' ) text[--l] = '\0'; + EnsureAlloced( l + 1 ); + strcpy( data, text ); + len = l; +} + +ID_INLINE idStr::~idStr( void ) { + FreeData(); +} + +ID_INLINE size_t idStr::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +ID_INLINE const char *idStr::c_str( void ) const { + return data; +} + +ID_INLINE idStr::operator const char *( void ) { + return c_str(); +} + +ID_INLINE idStr::operator const char *( void ) const { + return c_str(); +} + +ID_INLINE char idStr::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index <= len ) ); + return data[ index ]; +} + +ID_INLINE char &idStr::operator[]( int index ) { + assert( ( index >= 0 ) && ( index <= len ) ); + return data[ index ]; +} + +ID_INLINE void idStr::operator=( const idStr &text ) { + int l; + + l = text.Length(); + EnsureAlloced( l + 1, false ); + memcpy( data, text.data, l ); + data[l] = '\0'; + len = l; +} + +ID_INLINE idStr operator+( const idStr &a, const idStr &b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const char *b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const char *a, const idStr &b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const bool b ) { + idStr result( a ); + result.Append( b ? "true" : "false" ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const char b ) { + idStr result( a ); + result.Append( b ); + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const float b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%f", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const int b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%d", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr operator+( const idStr &a, const unsigned b ) { + char text[ 64 ]; + idStr result( a ); + + sprintf( text, "%u", b ); + result.Append( text ); + + return result; +} + +ID_INLINE idStr &idStr::operator+=( const float a ) { + char text[ 64 ]; + + sprintf( text, "%f", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const int a ) { + char text[ 64 ]; + + sprintf( text, "%d", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const unsigned a ) { + char text[ 64 ]; + + sprintf( text, "%u", a ); + Append( text ); + + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const idStr &a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const char *a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const char a ) { + Append( a ); + return *this; +} + +ID_INLINE idStr &idStr::operator+=( const bool a ) { + Append( a ? "true" : "false" ); + return *this; +} + +ID_INLINE bool operator==( const idStr &a, const idStr &b ) { + return ( !idStr::Cmp( a.data, b.data ) ); +} + +ID_INLINE bool operator==( const idStr &a, const char *b ) { + assert( b ); + return ( !idStr::Cmp( a.data, b ) ); +} + +ID_INLINE bool operator==( const char *a, const idStr &b ) { + assert( a ); + return ( !idStr::Cmp( a, b.data ) ); +} + +ID_INLINE bool operator!=( const idStr &a, const idStr &b ) { + return !( a == b ); +} + +ID_INLINE bool operator!=( const idStr &a, const char *b ) { + return !( a == b ); +} + +ID_INLINE bool operator!=( const char *a, const idStr &b ) { + return !( a == b ); +} + +ID_INLINE int idStr::Cmp( const char *text ) const { + assert( text ); + return idStr::Cmp( data, text ); +} + +ID_INLINE int idStr::Cmpn( const char *text, int n ) const { + assert( text ); + return idStr::Cmpn( data, text, n ); +} + +ID_INLINE int idStr::CmpPrefix( const char *text ) const { + assert( text ); + return idStr::Cmpn( data, text, strlen( text ) ); +} + +ID_INLINE int idStr::Icmp( const char *text ) const { + assert( text ); + return idStr::Icmp( data, text ); +} + +ID_INLINE int idStr::Icmpn( const char *text, int n ) const { + assert( text ); + return idStr::Icmpn( data, text, n ); +} + +ID_INLINE int idStr::IcmpPrefix( const char *text ) const { + assert( text ); + return idStr::Icmpn( data, text, strlen( text ) ); +} + +// RAVEN BEGIN +// bdube: escapes +ID_INLINE int idStr::IcmpNoEscape( const char *text ) const { + assert( text ); + return idStr::IcmpNoEscape( data, text ); +} +// RAVEN END + +ID_INLINE int idStr::IcmpPath( const char *text ) const { + assert( text ); + return idStr::IcmpPath( data, text ); +} + +ID_INLINE int idStr::IcmpnPath( const char *text, int n ) const { + assert( text ); + return idStr::IcmpnPath( data, text, n ); +} + +ID_INLINE int idStr::IcmpPrefixPath( const char *text ) const { + assert( text ); + return idStr::IcmpnPath( data, text, strlen( text ) ); +} + +ID_INLINE int idStr::Length( void ) const { + return len; +} + +ID_INLINE int idStr::Allocated( void ) const { + if ( data != baseBuffer ) { + return alloced; + } else { + return 0; + } +} + +ID_INLINE void idStr::Empty( void ) { + EnsureAlloced( 1 ); + data[ 0 ] = '\0'; + len = 0; +} + +ID_INLINE bool idStr::IsEmpty( void ) const { + return ( idStr::Cmp( data, "" ) == 0 ); +} + +ID_INLINE void idStr::Clear( void ) { + FreeData(); + Init(); +} + +ID_INLINE void idStr::Append( const char a ) { + EnsureAlloced( len + 2 ); + data[ len ] = a; + len++; + data[ len ] = '\0'; +} + +ID_INLINE void idStr::Append( const idStr &text ) { + int newLen; + int i; + + newLen = len + text.Length(); + EnsureAlloced( newLen + 1 ); + for ( i = 0; i < text.len; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; +} + +ID_INLINE void idStr::Append( const char *text ) { + int newLen; + int i; + + if ( text ) { + newLen = len + strlen( text ); + EnsureAlloced( newLen + 1 ); + for ( i = 0; text[ i ]; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; + } +} + +ID_INLINE void idStr::Append( const char *text, int l ) { + int newLen; + int i; + + if ( text && l ) { + newLen = len + l; + EnsureAlloced( newLen + 1 ); + for ( i = 0; text[ i ] && i < l; i++ ) { + data[ len + i ] = text[ i ]; + } + len = newLen; + data[ len ] = '\0'; + } +} + +ID_INLINE void idStr::Insert( const char a, int index ) { + int i, l; + + if ( index < 0 ) { + index = 0; + } else if ( index > len ) { + index = len; + } + + l = 1; + EnsureAlloced( len + l + 1 ); + for ( i = len; i >= index; i-- ) { + data[i+l] = data[i]; + } + data[index] = a; + len++; +} + +ID_INLINE void idStr::Insert( const char *text, int index ) { + int i, l; + + if ( index < 0 ) { + index = 0; + } else if ( index > len ) { + index = len; + } + + l = strlen( text ); + EnsureAlloced( len + l + 1 ); + for ( i = len; i >= index; i-- ) { + data[i+l] = data[i]; + } + for ( i = 0; i < l; i++ ) { + data[index+i] = text[i]; + } + len += l; +} + +ID_INLINE void idStr::ToLower( void ) { + for (int i = 0; data[i]; i++ ) { +// RAVEN BEGIN + data[i] = ToLower( data[i] ); +// RAVEN END + } +} + +ID_INLINE void idStr::ToUpper( void ) { + for (int i = 0; data[i]; i++ ) { +// RAVEN BEGIN + data[i] = ToUpper( data[i] ); +// RAVEN END + } +} + +ID_INLINE bool idStr::IsNumeric( void ) const { + return idStr::IsNumeric( data ); +} + +ID_INLINE bool idStr::HasLower( void ) const { + return idStr::HasLower( data ); +} + +ID_INLINE bool idStr::HasUpper( void ) const { + return idStr::HasUpper( data ); +} + +ID_INLINE int idStr::IsEscape( int* type ) const { + return idStr::IsEscape( data, type ); +} +ID_INLINE idStr &idStr::RemoveEscapes ( int escapes ) { + idStr::RemoveEscapes( data, escapes ); + len = Length( c_str() ); + return *this; +} +ID_INLINE int idStr::LengthWithoutEscapes( void ) const { + return idStr::LengthWithoutEscapes( data ); +} +// RAVEN END + +ID_INLINE void idStr::CapLength( int newlen ) { + if ( len <= newlen ) { + return; + } + data[ newlen ] = 0; + len = newlen; +} + +ID_INLINE void idStr::Fill( const char ch, int newlen ) { + EnsureAlloced( newlen + 1 ); + len = newlen; + memset( data, ch, len ); + data[ len ] = 0; +} + +ID_INLINE int idStr::Find( const char c, int start, int end ) const { + if ( end == -1 ) { + end = len; + } + return idStr::FindChar( data, c, start, end ); +} + +ID_INLINE int idStr::Find( const char *text, bool casesensitive, int start, int end ) const { + if ( end == -1 ) { + end = len; + } + return idStr::FindText( data, text, casesensitive, start, end ); +} + +ID_INLINE bool idStr::Filter( const char *filter, bool casesensitive ) const { + return idStr::Filter( filter, data, casesensitive ); +} + +ID_INLINE const char *idStr::Left( int len, idStr &result ) const { + return Mid( 0, len, result ); +} + +ID_INLINE const char *idStr::Right( int len, idStr &result ) const { + if ( len >= Length() ) { + result = *this; + return result; + } + return Mid( Length() - len, len, result ); +} + +ID_INLINE idStr idStr::Left( int len ) const { + return Mid( 0, len ); +} + +ID_INLINE idStr idStr::Right( int len ) const { + if ( len >= Length() ) { + return *this; + } + return Mid( Length() - len, len ); +} + +ID_INLINE void idStr::Strip( const char c ) { + StripLeading( c ); + StripTrailing( c ); +} + +ID_INLINE void idStr::Strip( const char *string ) { + StripLeading( string ); + StripTrailing( string ); +} + +ID_INLINE bool idStr::CheckExtension( const char *ext ) { + return idStr::CheckExtension( data, ext ); +} + +ID_INLINE int idStr::Length( const char *s ) { + int i; + for ( i = 0; s[i]; i++ ) {} + return i; +} + +ID_INLINE char *idStr::ToLower( char *s ) { + for ( int i = 0; s[i]; i++ ) { +// RAVEN BEGIN + s[i] = ToLower( s[i] ); +// RAVEN END + } + return s; +} + +ID_INLINE char *idStr::ToUpper( char *s ) { + for ( int i = 0; s[i]; i++ ) { +// RAVEN BEGIN + s[i] = ToUpper( s[i] ); +// RAVEN END + } + return s; +} + +ID_INLINE int idStr::Hash( const char *string ) { + int i, hash = 0; + for ( i = 0; *string != '\0'; i++ ) { + hash += ( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::Hash( const char *string, int length ) { + int i, hash = 0; + for ( i = 0; i < length; i++ ) { + hash += ( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::IHash( const char *string ) { + int i, hash = 0; + for( i = 0; *string != '\0'; i++ ) { + hash += ToLower( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE int idStr::IHash( const char *string, int length ) { + int i, hash = 0; + for ( i = 0; i < length; i++ ) { + hash += ToLower( *string++ ) * ( i + 119 ); + } + return hash; +} + +ID_INLINE char idStr::ToLower( byte c ) { +// RAVEN BEGIN + if( lowerCaseCharacter[c] ) { + return lowerCaseCharacter[c]; + } +// RAVEN END + return c; +} + +ID_INLINE char idStr::ToUpper( byte c ) { +// RAVEN BEGIN + if( upperCaseCharacter[c] ) { + return upperCaseCharacter[c]; + } +// RAVEN END + return c; +} + +ID_INLINE bool idStr::CharIsPrintable( byte c ) { +// RAVEN BEGIN + return printableCharacter[c]; +// RAVEN END +} + +ID_INLINE bool idStr::CharIsLower( byte c ) { +// RAVEN BEGIN + return !!lowerCaseCharacter[c] && ( lowerCaseCharacter[c] == c ); +// RAVEN END +} + +ID_INLINE bool idStr::CharIsUpper( byte c ) { +// RAVEN BEGIN + return !!upperCaseCharacter[c] && ( upperCaseCharacter[c] == c ); +// RAVEN END +} + +ID_INLINE bool idStr::CharIsAlpha( byte c ) { + // test for regular ascii and western European high-ascii chars + return !!upperCaseCharacter[c]; +} + +ID_INLINE bool idStr::CharIsNumeric( byte c ) { + return ( c <= '9' && c >= '0' ); +} + +ID_INLINE bool idStr::CharIsNewLine( byte c ) { + return ( c == '\n' || c == '\r' || c == '\v' ); +} + +ID_INLINE bool idStr::CharIsTab( byte c ) { + return ( c == '\t' ); +} + +ID_INLINE int idStr::ColorIndex( int c ) { + return ( c & 15 ); +} + +ID_INLINE int idStr::DynamicMemoryUsed() const { + return ( data == baseBuffer ) ? 0 : alloced; +} + +#endif /* !__STR_H__ */ diff --git a/src/idlib/TextCompiler.cpp b/src/idlib/TextCompiler.cpp new file mode 100644 index 0000000..77a1ec3 --- /dev/null +++ b/src/idlib/TextCompiler.cpp @@ -0,0 +1,26 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop diff --git a/src/idlib/TextCompiler.h b/src/idlib/TextCompiler.h new file mode 100644 index 0000000..75b9744 --- /dev/null +++ b/src/idlib/TextCompiler.h @@ -0,0 +1,162 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __TEXTCOMPILER_H__ +#define __TEXTCOMPILER_H__ + +namespace TextCompiler +{ + // Write an indirect value + template inline void WriteValue(type const * const ptr, idFile *out, bool byteSwap=false) + { + if(out != NULL) + { + if(!byteSwap) + { + out->Write(ptr, sizeof(type)); + } + else + { + byte const * const p = (byte *)ptr; + + for(int i=sizeof(type)-1;i>=0;i--) + { + out->Write(&(p[i]), 1); + } + } + + } + } + + // Write a direct value + template inline void WriteValue(type const val, idFile *out, bool byteSwap=false) + { + if(out != NULL) + { + if(!byteSwap) + { + out->Write(&val, sizeof(type)); + } + else + { + byte const * const p = (byte *)&val; + + for(int i=sizeof(type)-1;i>=0;i--) + { + out->Write(&(p[i]), 1); + } + } + } + } + + template inline type ReadValue(idFile *in) + { + type ret; + + in->Read(&ret, sizeof(type)); + return ret; + } + + // specialization write for idStr's + template <> inline void WriteValue(idStr const * const ptr, idFile *out, bool byteSwap) + { + if(out != NULL) + { + // if less than 32, then we don't need a trailing null + // this is because 5 bits can be used to represent string length in the token header + // zero length strings end up needing to be null terminated + if((ptr->Length() < 32) && (ptr->Length() != 0)) + { + out->Write(ptr->c_str(), ptr->Length()); + } + else + { + out->Write(ptr->c_str(), ptr->Length()+1); + } + } + } + + // specialization read for idStr's + template <> inline idStr ReadValue(idFile *in) + { + char c; + idStr str; + + in->Read(&c, 1); + while(c != '\0') + { + str.Append(c); + in->Read(&c, 1); + } + + str.Append(c); + + return str; + } + + template inline void WriteArray(type const * const ptr, unsigned int count, idFile *out) + { + WriteValue(&count, out); + for(unsigned int i=0;i(&(ptr[i]), out); + } + } + + template inline type *ReadArray(idFile *in, unsigned int *count=NULL) + { + unsigned int len; + len = ReadValue(in); + type *buffer = (type *)malloc(len*sizeof(type)); + type *ptr = buffer; + for(unsigned int i=0;i(in); + ptr++; + } + if(count != NULL) + *count = len; + return buffer; + } + + template inline void WriteIdList(idList const * const ptr, idFile *out) + { + WriteArray(ptr->Ptr(), ptr->Num(), out); + } + + template inline idList ReadIdList(idFile *in) + { + idList ret; + ret.Clear(); + unsigned int count; + idAutoPtr array(ReadArray(in, &count)); + + for(unsigned int i=0;i. + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +double idTimer::base = -1.0; + + +/* +================= +idTimer::InitBaseClockTicks +================= +*/ +void idTimer::InitBaseClockTicks( void ) const { + idTimer timer; + double ct, b; + int i; + + base = 0.0; + b = -1.0; + for ( i = 0; i < 1000; i++ ) { + timer.Clear(); + timer.Start(); + timer.Stop(); + ct = timer.ClockTicks(); + if ( b < 0.0 || ct < b ) { + b = ct; + } + } + base = b; +} + + +/* +================= +idTimerReport::idTimerReport +================= +*/ +idTimerReport::idTimerReport() { +} + +/* +================= +idTimerReport::SetReportName +================= +*/ +void idTimerReport::SetReportName( const char *name ) { + reportName = ( name ) ? name : "Timer Report"; +} + +/* +================= +idTimerReport::~idTimerReport +================= +*/ +idTimerReport::~idTimerReport() { + Clear(); +} + +/* +================= +idTimerReport::AddReport +================= +*/ +int idTimerReport::AddReport( const char *name ) { + if ( name && *name ) { + names.Append( name ); + return timers.Append( new idTimer() ); + } + return -1; +} + +/* +================= +idTimerReport::Clear +================= +*/ +void idTimerReport::Clear() { + timers.DeleteContents( true ); + names.Clear(); + reportName.Clear(); +} + +/* +================= +idTimerReport::Reset +================= +*/ +void idTimerReport::Reset() { + assert ( timers.Num() == names.Num() ); + for ( int i = 0; i < timers.Num(); i++ ) { + timers[i]->Clear(); + } +} + +/* +================= +idTimerReport::AddTime +================= +*/ +void idTimerReport::AddTime( const char *name, idTimer *time ) { + assert ( timers.Num() == names.Num() ); + int i; + for ( i = 0; i < names.Num(); i++ ) { + if ( names[i].Icmp( name ) == 0 ) { + *timers[i] += *time; + break; + } + } + if ( i == names.Num() ) { + int index = AddReport( name ); + if ( index >= 0 ) { + timers[index]->Clear(); + *timers[index] += *time; + } + } +} + +/* +================= +idTimerReport::PrintReport +================= +*/ +void idTimerReport::PrintReport() { + assert( timers.Num() == names.Num() ); + idLib::common->Printf( "Timing Report for %s\n", reportName.c_str() ); + idLib::common->Printf( "-------------------------------\n" ); + float total = 0.0f; + for ( int i = 0; i < names.Num(); i++ ) { + idLib::common->Printf( "%s consumed %5.2f seconds\n", names[i].c_str(), timers[i]->Milliseconds() * 0.001f ); + total += timers[i]->Milliseconds(); + } + idLib::common->Printf( "Total time for report %s was %5.2f\n\n", reportName.c_str(), total * 0.001f ); +} diff --git a/src/idlib/Timer.h b/src/idlib/Timer.h new file mode 100644 index 0000000..a5bd4a8 --- /dev/null +++ b/src/idlib/Timer.h @@ -0,0 +1,219 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __TIMER_H__ +#define __TIMER_H__ + +/* +=============================================================================== + + Clock tick counter. Should only be used for profiling. + +=============================================================================== +*/ + +class idTimer { +public: + idTimer( void ); + idTimer( double clockTicks ); + ~idTimer( void ); + + idTimer operator+( const idTimer &t ) const; + idTimer operator-( const idTimer &t ) const; + idTimer & operator+=( const idTimer &t ); + idTimer & operator-=( const idTimer &t ); + + void Start( void ); + void Stop( void ); + void Clear( void ); + double ClockTicks( void ) const; + double Milliseconds( void ) const; + +private: + static double base; + enum { + TS_STARTED, + TS_STOPPED + } state; + double start; + double clockTicks; + + void InitBaseClockTicks( void ) const; +}; + +/* +================= +idTimer::idTimer +================= +*/ +ID_INLINE idTimer::idTimer( void ) { + state = TS_STOPPED; + clockTicks = 0.0; +} + +/* +================= +idTimer::idTimer +================= +*/ +ID_INLINE idTimer::idTimer( double _clockTicks ) { + state = TS_STOPPED; + clockTicks = _clockTicks; +} + +/* +================= +idTimer::~idTimer +================= +*/ +ID_INLINE idTimer::~idTimer( void ) { +} + +/* +================= +idTimer::operator+ +================= +*/ +ID_INLINE idTimer idTimer::operator+( const idTimer &t ) const { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + return idTimer( clockTicks + t.clockTicks ); +} + +/* +================= +idTimer::operator- +================= +*/ +ID_INLINE idTimer idTimer::operator-( const idTimer &t ) const { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + return idTimer( clockTicks - t.clockTicks ); +} + +/* +================= +idTimer::operator+= +================= +*/ +ID_INLINE idTimer &idTimer::operator+=( const idTimer &t ) { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + clockTicks += t.clockTicks; + return *this; +} + +/* +================= +idTimer::operator-= +================= +*/ +ID_INLINE idTimer &idTimer::operator-=( const idTimer &t ) { + assert( state == TS_STOPPED && t.state == TS_STOPPED ); + clockTicks -= t.clockTicks; + return *this; +} + +/* +================= +idTimer::Start +================= +*/ +ID_INLINE void idTimer::Start( void ) { + assert( state == TS_STOPPED ); + state = TS_STARTED; + start = idLib::sys->GetClockTicks(); +} + +/* +================= +idTimer::Stop +================= +*/ +ID_INLINE void idTimer::Stop( void ) { + assert( state == TS_STARTED ); + clockTicks += idLib::sys->GetClockTicks() - start; + if ( base < 0.0 ) { + InitBaseClockTicks(); + } + if ( clockTicks > base ) { + clockTicks -= base; + } + state = TS_STOPPED; +} + +/* +================= +idTimer::Clear +================= +*/ +ID_INLINE void idTimer::Clear( void ) { + clockTicks = 0.0; +} + +/* +================= +idTimer::ClockTicks +================= +*/ +ID_INLINE double idTimer::ClockTicks( void ) const { + assert( state == TS_STOPPED ); + return clockTicks; +} + +/* +================= +idTimer::Milliseconds +================= +*/ +ID_INLINE double idTimer::Milliseconds( void ) const { + assert( state == TS_STOPPED ); + return clockTicks / ( idLib::sys->ClockTicksPerSecond() * 0.001 ); +} + + +/* +=============================================================================== + + Report of multiple named timers. + +=============================================================================== +*/ + +class idTimerReport { +public: + idTimerReport( void ); + ~idTimerReport( void ); + + void SetReportName( const char *name ); + int AddReport( const char *name ); + void Clear( void ); + void Reset( void ); + void PrintReport( void ); + void AddTime( const char *name, idTimer *time ); + +private: + idListtimers; + idStrList names; + idStr reportName; +}; + +#endif /* !__TIMER_H__ */ diff --git a/src/idlib/TimingCollection.cpp b/src/idlib/TimingCollection.cpp new file mode 100644 index 0000000..86042f2 --- /dev/null +++ b/src/idlib/TimingCollection.cpp @@ -0,0 +1,421 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#if !defined(__TIMINGCOLLECTION_H_) + #include "TimingCollection.h" +#endif + +#ifdef RV_TIMERCOLLECTION + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::Clear +// +//----------------------------------------------------------------------------- + +void rvSingleTiming::Clear( void ) +{ + mTotalUpdates = 0; + mCurValue = 0; + mTotalValue = 0; + mPeakValue = 0; + mLimit = 0; + mLimitExceeded = 0; + mLimitExceededTimesFive = 0; + mDisplayLevel = 0; + mName = ""; + mParentName = ""; + + mStartFile = ""; + mStartLine = 0; + mEndFile = ""; + mEndLine = 0; +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::rvSingleTiming +// +//----------------------------------------------------------------------------- + +rvSingleTiming::rvSingleTiming() +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::rvSingleTiming +// +//----------------------------------------------------------------------------- + +rvSingleTiming::rvSingleTiming( idStr &newName ) +{ + Clear(); + mName = newName; +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::OutputDataToFile +// +//----------------------------------------------------------------------------- + +void rvSingleTiming::OutputDataToFile( idFile *file, int framesRecorded ) +{ + char buffer[1024]; + idStr outputName = ""; + + for( int i = 0; i < mDisplayLevel; i++ ) + { + outputName += "**"; + } + outputName += mName; + + sprintf(buffer, "%-36s %-9.3f %-9.3f %-9.3f %-9.3f %-9i %-9i %-9.3f\n", outputName.c_str(), + (float)mTotalValue, (float)( mTotalValue / (float)framesRecorded), (float)mPeakValue, + (float)mLimit, mLimitExceeded, mLimitExceededTimesFive, (float)(mTotalUpdates / (float)framesRecorded) ); + file->Write ( buffer, strlen( buffer ) ); +} + +//----------------------------------------------------------------------------- +// +// rvSingleTiming::OutputInfoToFile +// +//----------------------------------------------------------------------------- + +void rvSingleTiming::OutputInfoToFile( idFile *file ) +{ + char buffer[1024]; + + sprintf(buffer, "Name: %s\nParent: %s\n", mName.c_str(), mParentName.c_str() ); + file->Write ( buffer, strlen( buffer ) ); + + sprintf(buffer, "Starting at %s(%d)\nEnding at %s(%d)\n\n", mStartFile.c_str(), mStartLine, mEndFile.c_str(), mEndLine ); + file->Write ( buffer, strlen( buffer ) ); +} + + + + + + + + + + + + + + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::Clear +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::Clear( void ) +{ + mUpdates = 0; + mCurTimer = 0; + mFramesRecorded = 0; + mCurrentlyUpdating = 0; + mTimings.Clear(); + mTimingsIndex.Clear(); +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::rvTimingCollection +// +//----------------------------------------------------------------------------- + +rvTimingCollection::rvTimingCollection() +{ + Clear(); +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::GetTiming +// +//----------------------------------------------------------------------------- + +rvSingleTiming *rvTimingCollection::GetTiming( idStr &timingName ) +{ + int *handle = NULL; + + if( mTimingsIndex.Get( timingName, &handle ) ) + { + return( &mTimings[*handle] ); + } + + rvSingleTiming newTiming( timingName ); + int index = mTimings.Num(); + + mTimingsIndex.Set( timingName, index ); + mTimings.Append( newTiming ); + return &mTimings[index]; +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::DisplayTimingValues +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::DisplayTimingValues( void ) { + // Fixme: the display resulting from this is really, really a mess - quite unreadable. + common->Printf ( "Timer: \n" ); + for( int i = 0; i < mTimings.Num(); i++ ) { + if ( (int)(mTimings[i].mCurValue) ) { + common->Printf( "\t%s %3i\n", mTimings[i].mName.c_str(), (int)(mTimings[i].mCurValue) ); + } + } +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::OutputToFile +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::OutputToFile( void ) +{ + idFile *file = NULL; + idStr name; + char buffer[1024]; + + // Fixme: Do we have any good information for building a better file name here? + + name = "Timings/output.txt"; + + file = fileSystem->OpenFileWrite ( name ); + if ( !file ) + { + return; + } + + sprintf( buffer, "Total frames = %d\n\n", mFramesRecorded ); + file->Write( buffer, strlen( buffer ) ); + + sprintf( buffer, "%-36s %-9s %-9s %-9s %-9s %-9s %-9s %-9s\n\n", + "Name", "Total", "Average", "Peak", "Limit", "Exceeded", "Exceed*5", "Calls" ); + file->Write ( buffer, strlen( buffer ) ); + + for( int i = 0; i < mTimings.Num(); i++ ) + { + mTimings[i].OutputDataToFile( file, mFramesRecorded ); + if( !( ( i + 1 )%3) ) // break up the prints into groups of 3 to make scanning visually easier. + { + sprintf( buffer, "\n" ); + file->Write( buffer, strlen( buffer ) ); + } + } + + sprintf(buffer, "\n\nInformation about categories\n\n" ); + file->Write ( buffer, strlen( buffer ) ); + + for( int i = 0; i < mTimings.Num(); i++ ) + { + mTimings[i].OutputInfoToFile( file ); + } + + fileSystem->CloseFile ( file ); + file = NULL; +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::AppendToDict +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::AppendToDict( idDict *dict ) +{ + if( !mCurrentlyUpdating ) + { + return; + } + + for( int i = 0; i < mTimings.Num(); i++ ) + { + dict->Set ( mTimings[i].mName.c_str(), va( "%0.3g\t%0.3g\t", mTimings[i].mCurValue, mTimings[i].mTotalValue ) ); + } +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::InitFrame +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::InitFrame( bool inUse, bool displayText, bool outputFileWhenDone ) +{ + // Do our quick reject test to ensure this is quick when not in use. + + if( !inUse ) + { + if( mCurrentlyUpdating ) + { + if( outputFileWhenDone ) + { + OutputToFile(); + } + mCurrentlyUpdating = 0; + } + return; + } + + mCurrentlyUpdating = 1; + mUpdates++; + mFramesRecorded++; + + if( mUpdates >= 10 && displayText ) + { + // ?? If we display output every single frame, we get REALLY bogged down. It is bad. + + DisplayTimingValues(); + + mUpdates = 0; + + return; + } + + // Clear only the curValues and record information. + + for( int i = 0; i < mTimings.Num(); i++ ) + { + if( mTimings[i].mCurValue > mTimings[i].mPeakValue ) + { + mTimings[i].mPeakValue = mTimings[i].mCurValue; + } + + if( mTimings[i].mLimit > 0.0 && mTimings[i].mCurValue > mTimings[i].mLimit ) + { + mTimings[i].mLimitExceeded++; + } + + if( mTimings[i].mLimit > 0.0 && mTimings[i].mCurValue > mTimings[i].mLimit * 5 ) + { + mTimings[i].mLimitExceededTimesFive++; + } + + mTimings[i].mCurValue = 0.0f; + } +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::TimingStart +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::_TimingStart( const char *timingName, const char *fileName, const int lineNum ) +{ + // Do our quick reject test to ensure this is quick when not in use. + + if( !mCurrentlyUpdating ) + { + return; + } + + // Go up the timer list. + + mCurTimer++; + assert( mCurTimer < MAX_TIMERS ); + + // Keep track of the current function being timed in case we nest and need to know our parent. + + mTimerName[mCurTimer] = timingName; + + // Set the information about this timer. + + rvSingleTiming *curTiming = GetTiming( mTimerName[mCurTimer] ); + + if( mCurTimer == 0 ) + { + curTiming->mParentName = "base"; + } + else + { + curTiming->mParentName = mTimerName[mCurTimer - 1]; + } + curTiming->mStartFile = fileName; + curTiming->mStartLine = lineNum; + curTiming->mDisplayLevel = mCurTimer - 1; + + // Start the timer; do it last to avoid timing the rest of this function. + + mTimer[mCurTimer].Clear(); + mTimer[mCurTimer].Start(); +} + +//----------------------------------------------------------------------------- +// +// rvTimingCollection::TimingEnd +// +//----------------------------------------------------------------------------- + +void rvTimingCollection::_TimingStop( double msecLimit, const char *fileName, const int lineNum ) +{ + // Do our quick reject test to ensure this is quick when not in use. + + if( !mCurrentlyUpdating ) + { + return; + } + + // Stop our timer first so that we don't log time from the rest of this function. + + mTimer[mCurTimer].Stop(); + + // Update incidental information on the timer. + + rvSingleTiming *curTiming = GetTiming( mTimerName[mCurTimer] ); + + curTiming->mEndFile = fileName; + curTiming->mEndLine = lineNum; + + curTiming->mLimit = msecLimit; + + // Add the actual timing values to the rvSingleTiming + + double frameTime = mTimer[mCurTimer].Milliseconds(); + + curTiming->mCurValue += frameTime; + curTiming->mTotalValue += frameTime; + + curTiming->mTotalUpdates++; + + // Go back down the timer list. + + mCurTimer--; + assert( mCurTimer >= 0 ); +} + +#endif diff --git a/src/idlib/TimingCollection.h b/src/idlib/TimingCollection.h new file mode 100644 index 0000000..578d84b --- /dev/null +++ b/src/idlib/TimingCollection.h @@ -0,0 +1,124 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __TIMINGCOLLECTION_H_ +#define __TIMINGCOLLECTION_H_ + +// Fixme! Move this. It doesn't belong in AI at all, obviously. +#include "../idlib/Str.h" +#include "../idlib/containers/List.h" +#include "../idlib/containers/HashTable.h" +#include "../framework/File.h" +#include "Timer.h" + +//----------------------------------------------------------------------------- +// +// rvTimingCollection +// +//----------------------------------------------------------------------------- + +#define RV_TIMERCOLLECTION + +#define START_PROFILING(a, b) (a)._TimingStart(b, __FILE__, __LINE__) +#define STOP_PROFILING(a, b) (a)._TimingStop(b, __FILE__, __LINE__) + +// Easy and quick way to remove all of this from the codebase +#ifdef RV_TIMERCOLLECTION + +#define MAX_TIMERS 16 + +class rvSingleTiming +{ +public: + int mTotalUpdates; + double mCurValue; + double mTotalValue; + double mPeakValue; + double mLimit; + int mLimitExceeded; + int mLimitExceededTimesFive; + int mDisplayLevel; + + idStr mName; + idStr mParentName; + + idStr mStartFile; + int mStartLine; + idStr mEndFile; + int mEndLine; + + rvSingleTiming(); + rvSingleTiming( idStr &newName ); + + void Clear(); + void OutputDataToFile( idFile *file, int framesRecorded ); + void OutputInfoToFile( idFile *file ); +}; + +class rvTimingCollection +{ +protected: + idTimer mTimer[MAX_TIMERS]; + idStr mTimerName[MAX_TIMERS]; + + idList mTimings; + idHashTable mTimingsIndex; + + int mCurTimer; + int mUpdates; + int mCurrentlyUpdating; + int mFramesRecorded; + + rvSingleTiming *GetTiming( idStr &timingName ); + void DisplayTimingValues( void ); + void OutputToFile( void ); + +public: + rvTimingCollection(); + + void InitFrame( bool inUse, bool displayText, bool outputFileWhenDone ); + void _TimingStart( const char *timingName, const char *fileName, const int lineNum ); + void _TimingStop( double msecLimit, const char *fileName, const int lineNum ); + void AppendToDict( idDict* dict ); + void Clear( void ); +}; + +#else + +class rvTimingCollection +{ +protected: +public: + rvTimingCollection(){} + + void InitFrame( bool inUse, bool displayText, bool outputFileWhenDone ){} + void _TimingStart( const char *timingName, const char *fileName, const int lineNum ){} + void _TimingStop( double msecLimit, const char *fileName, const int lineNum ){} + void AppendToDict( idDict* dict ){} + void Clear( void ){} +}; + +#endif + +#endif diff --git a/src/idlib/Token.cpp b/src/idlib/Token.cpp new file mode 100644 index 0000000..b9eb68f --- /dev/null +++ b/src/idlib/Token.cpp @@ -0,0 +1,175 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + + +/* +================ +idToken::NumberValue +================ +*/ +void idToken::NumberValue( void ) { + int i, pow, div, c; + const char *p; + double m; + + assert( type == TT_NUMBER ); + p = c_str(); + floatvalue = 0; + intvalue = 0; + // floating point number + if ( subtype & TT_FLOAT ) { + if ( subtype & ( TT_INFINITE | TT_INDEFINITE | TT_NAN ) ) { + if ( subtype & TT_INFINITE ) { // 1.#INF + unsigned int inf = 0x7f800000; + floatvalue = (double) *(float*)&inf; + } + else if ( subtype & TT_INDEFINITE ) { // 1.#IND + unsigned int ind = 0xffc00000; + floatvalue = (double) *(float*)&ind; + } + else if ( subtype & TT_NAN ) { // 1.#QNAN + unsigned int nan = 0x7fc00000; + floatvalue = (double) *(float*)&nan; + } + } + else { + while( *p && *p != '.' && *p != 'e' ) { + floatvalue = floatvalue * 10.0 + (double) (*p - '0'); + p++; + } + if ( *p == '.' ) { + p++; + for( m = 0.1; *p && *p != 'e'; p++ ) { + floatvalue = floatvalue + (double) (*p - '0') * m; + m *= 0.1; + } + } + if ( *p == 'e' ) { + p++; + if ( *p == '-' ) { + div = true; + p++; + } + else if ( *p == '+' ) { + div = false; + p++; + } + else { + div = false; + } + pow = 0; + for ( pow = 0; *p; p++ ) { + pow = pow * 10 + (int) (*p - '0'); + } + for ( m = 1.0, i = 0; i < pow; i++ ) { + m *= 10.0; + } + if ( div ) { + floatvalue /= m; + } + else { + floatvalue *= m; + } + } + } + intvalue = idMath::Ftoi( floatvalue ); + } + else if ( subtype & TT_DECIMAL ) { + while( *p ) { + intvalue = intvalue * 10 + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_IPADDRESS ) { + c = 0; + while( *p && *p != ':' ) { + if ( *p == '.' ) { + while( c != 3 ) { + intvalue = intvalue * 10; + c++; + } + c = 0; + } + else { + intvalue = intvalue * 10 + (*p - '0'); + c++; + } + p++; + } + while( c != 3 ) { + intvalue = intvalue * 10; + c++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_OCTAL ) { + // step over the first zero + p += 1; + while( *p ) { + intvalue = (intvalue << 3) + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_HEX ) { + // step over the leading 0x or 0X + p += 2; + while( *p ) { + intvalue <<= 4; + if (*p >= 'a' && *p <= 'f') + intvalue += *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + intvalue += *p - 'A' + 10; + else + intvalue += *p - '0'; + p++; + } + floatvalue = intvalue; + } + else if ( subtype & TT_BINARY ) { + // step over the leading 0b or 0B + p += 2; + while( *p ) { + intvalue = (intvalue << 1) + (*p - '0'); + p++; + } + floatvalue = intvalue; + } + subtype |= TT_VALUESVALID; +} + +/* +================ +idToken::ClearTokenWhiteSpace +================ +*/ +void idToken::ClearTokenWhiteSpace( void ) { + whiteSpaceStart_p = NULL; + whiteSpaceEnd_p = NULL; + linesCrossed = 0; +} diff --git a/src/idlib/Token.h b/src/idlib/Token.h new file mode 100644 index 0000000..862cd8f --- /dev/null +++ b/src/idlib/Token.h @@ -0,0 +1,262 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __TOKEN_H__ +#define __TOKEN_H__ + +/* +=============================================================================== + + idToken is a token read from a file or memory with idLexer or idParser + +=============================================================================== +*/ +// RAVEN BEGIN +// jsinger: defines used by the binary lexer and WriteBinaryToken method of idLexer + +// binary token types +// low order 3 bits form the type +#define BTT_STRING 0 +#define BTT_LITERAL 1 +#define BTT_NUMBER 2 +#define BTT_NAME 3 +#define BTT_PUNCTUATION 4 +#define BTT_PUNCTUATION2 5 // when punctuation, least significant bit is used as part of the punctuation type +// 3 bits +#define BTT_SUBTYPE_INT 0 +#define BTT_SUBTYPE_UNSIGNEDINT 1 +#define BTT_SUBTYPE_LONG 2 +#define BTT_SUBTYPE_UNSIGNEDLONG 3 +#define BTT_SUBTYPE_FLOAT 4 +#define BTT_SUBTYPE_DOUBLE 5 +#define BTT_SUBTYPE_IPADDRESS 6 +#define BTT_SUBTYPE_IPPORT 7 + +// 2 bits +#define BTT_STORED_1BYTE 0 +#define BTT_STORED_2BYTE 1 +#define BTT_STORED_4BYTE 2 +#define BTT_STORED_8BYTE 3 + +// retrieval macros +#define BTT_GET_TYPE(x) ((x) & 0x07) +#define BTT_GET_SUBTYPE(x) (((x) & 0x38)>>3) +#define BTT_GET_STORED_SIZE(x) (((x) & 0xC0)>>6) +#define BTT_GET_STRING_LENGTH(x) (((x) & 0xFC)>>3) +#define BTT_GET_PUNCTUATION(x) (((x)&1)|(((x)>>2)&~1)) + +// punctuation types +#define BTT_PUNC_ASNULLTERMINATED 0 +#define BTT_PUNC_RIGHTPAREN 1 +#define BTT_PUNC_LEFTBRACE 2 +#define BTT_PUNC_RIGHTBRACE 3 +#define BTT_PUNC_MINUS 4 +#define BTT_PUNC_PLUS 5 +#define BTT_PUNC_COMMA 6 +#define BTT_PUNC_PLUSPLUS 7 +#define BTT_PUNC_LEFTBRACKET 8 +#define BTT_PUNC_RIGHTBRACKET 9 +#define BTT_PUNC_EQUAL 10 +#define BTT_PUNC_EQUALEQUAL 11 +#define BTT_PUNC_NOTEQUAL 12 +#define BTT_PUNC_PERCENT 13 +#define BTT_PUNC_LESSTHAN 14 +#define BTT_PUNC_GREATERTHAN 15 +#define BTT_PUNC_LOGICALAND 16 +#define BTT_PUNC_AMPERSAND 17 +#define BTT_PUNC_MINUSMINUS 18 +#define BTT_PUNC_HASH 19 +#define BTT_PUNC_LESSOREQUAL 20 +#define BTT_PUNC_GREATEROREQUAL 21 +#define BTT_PUNC_FORWARDSLASH 22 +#define BTT_PUNC_SHIFTLEFT 23 +#define BTT_PUNC_SHIFTRIGHT 24 +#define BTT_PUNC_LEFTPAREN 25 +#define BTT_PUNC_SEMICOLON 26 +#define BTT_PUNC_ASTERISK 27 +#define BTT_PUNC_PERIOD 28 +#define BTT_PUNC_DOLLARSIGN 29 +#define BTT_PUNC_PLUSEQUAL 30 +#define BTT_PUNC_MINUSEQUAL 31 +#define BTT_PUNC_TILDE 32 +#define BTT_PUNC_EXCLAMATION 33 +#define BTT_PUNC_PIPE 34 +#define BTT_PUNC_BACKSLASH 35 +#define BTT_PUNC_DOUBLEHASH 36 +#define BTT_PUNC_DOUBLECOLON 37 +#define BTT_PUNC_TIMESEQUAL 38 +#define BTT_PUNC_DOUBLEPIPE 39 +#define BTT_PUNC_INVERTEDPLING 40 +#define BTT_PUNC_INVERTEDQUERY 41 + +// set macro +#define BTT_MAKENUMBER_PREFIX(type, subtype, storedsize) ((unsigned char)((type)|(subtype<<3)|(storedsize<<6))) +#define BTT_MAKESTRING_PREFIX(type, length) ((unsigned char)((type)|((((length)<32)?(length):0)<<3))) +#define BTT_MAKEPUNCTUATION_PREFIX(punc) (((punc<<2)&~7)|(punc&1))|BTT_PUNCTUATION +// RAVEN END + +// token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +// number sub types +#define TT_INTEGER 0x00001 // integer +#define TT_DECIMAL 0x00002 // decimal number +#define TT_HEX 0x00004 // hexadecimal number +#define TT_OCTAL 0x00008 // octal number +#define TT_BINARY 0x00010 // binary number +#define TT_LONG 0x00020 // long int +#define TT_UNSIGNED 0x00040 // unsigned int +#define TT_FLOAT 0x00080 // floating point number +#define TT_SINGLE_PRECISION 0x00100 // float +#define TT_DOUBLE_PRECISION 0x00200 // double +#define TT_EXTENDED_PRECISION 0x00400 // long double +#define TT_INFINITE 0x00800 // infinite 1.#INF +#define TT_INDEFINITE 0x01000 // indefinite 1.#IND +#define TT_NAN 0x02000 // NaN +#define TT_IPADDRESS 0x04000 // ip address +#define TT_IPPORT 0x08000 // ip port +#define TT_VALUESVALID 0x10000 // set if intvalue and floatvalue are valid + +// string sub type is the length of the string +// literal sub type is the ASCII code +// punctuation sub type is the punctuation id +// name sub type is the length of the name + +class idToken : public idStr { + + friend class idParser; + friend class idLexer; +// RAVEN BEGIN +// jsinger: added to allow Lexer direct access to token internals as well + friend class Lexer; +// RAVEN END + +public: + int type; // token type + int subtype; // token sub type + int line; // line in script the token was on + int linesCrossed; // number of lines crossed in white space before token + int flags; // token flags, used for recursive defines + +public: + idToken( void ); + idToken( const idToken *token ); + ~idToken( void ); + + void operator=( const idStr& text ); + void operator=( const char *text ); + + double GetDoubleValue( void ); // double value of TT_NUMBER + float GetFloatValue( void ); // float value of TT_NUMBER + unsigned long GetUnsignedLongValue( void ); // unsigned long value of TT_NUMBER + int GetIntValue( void ); // int value of TT_NUMBER + int WhiteSpaceBeforeToken( void ) const;// returns length of whitespace before token + void ClearTokenWhiteSpace( void ); // forget whitespace before token + + void NumberValue( void ); // calculate values for a TT_NUMBER + +private: + unsigned long intvalue; // integer value + double floatvalue; // floating point value + const char * whiteSpaceStart_p; // start of white space before token, only used by idLexer + const char * whiteSpaceEnd_p; // end of white space before token, only used by idLexer + idToken * next; // next token in chain, only used by idParser + + void AppendDirty( const char a ); // append character without adding trailing zero +}; + +// RAVEN BEGIN +// rjohnson: initialized floatvalue to prevent fpu exceptin + +ID_INLINE idToken::idToken( void ) : + floatvalue(0.0) +{ +} + +// RAVEN END + +ID_INLINE idToken::idToken( const idToken *token ) { + *this = *token; +} + +ID_INLINE idToken::~idToken( void ) { +} + +ID_INLINE void idToken::operator=( const char *text) { + *static_cast(this) = text; +} + +ID_INLINE void idToken::operator=( const idStr& text ) { + *static_cast(this) = text; +} + +ID_INLINE double idToken::GetDoubleValue( void ) { + if ( type != TT_NUMBER ) { + return 0.0; + } + if ( !(subtype & TT_VALUESVALID) ) { + NumberValue(); + } + return floatvalue; +} + +ID_INLINE float idToken::GetFloatValue( void ) { + return (float) GetDoubleValue(); +} + +ID_INLINE unsigned long idToken::GetUnsignedLongValue( void ) { + if ( type != TT_NUMBER ) { + return 0; + } + if ( !(subtype & TT_VALUESVALID) ) { + NumberValue(); + } + return intvalue; +} + +ID_INLINE int idToken::GetIntValue( void ) { + return (int) GetUnsignedLongValue(); +} + +ID_INLINE int idToken::WhiteSpaceBeforeToken( void ) const { + return ( whiteSpaceEnd_p > whiteSpaceStart_p ); +} + +ID_INLINE void idToken::AppendDirty( const char a ) { + EnsureAlloced( len + 2, true ); +// RAVEN BEGIN +// jscott: I hate slashes nearly as much as KRABS + if( a == '\\' ) { + data[len++] = '/'; + } else { + data[len++] = a; + } +// RAVEN END +} + +#endif /* !__TOKEN_H__ */ diff --git a/src/idlib/algorithms/MultifieldSort.h b/src/idlib/algorithms/MultifieldSort.h new file mode 100644 index 0000000..6cf6ac3 --- /dev/null +++ b/src/idlib/algorithms/MultifieldSort.h @@ -0,0 +1,224 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// MultifieldSort.cpp +// +// Routines for performing multi-field sorts on idList's +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __MULTIFIELD_SORT_H__ +#define __MULTIFIELD_SORT_H__ + +template< class type > +class rvMultifieldSort { +public: + void Clear( void ); + void AddCompareFunction( typename idList::cmp_t* fn ); + void AddFilterFunction( typename idList::filter_t* fn ); + + void Sort( idList& list ); + +private: + void QSort_Iterative( idList& list, int low, int n ); + int Median3( type* list, int a, int b, int c ); + int Compare( const type* lhs, const type* rhs ); + + // sortingFields - pointers to sorting functions, in order to be sorted + idList::cmp_t*> sortingFields; + // filterFields - pointers to filter functions, in order to be filtered + idList::filter_t*> filterFields; +}; + +template< class type > +void rvMultifieldSort< type >::Clear( void ) { + sortingFields.Clear(); + filterFields.Clear(); +} + +template< class type > +int rvMultifieldSort< type >::Compare( const type* lhs, const type* rhs ) { + for( int i = 0; i < sortingFields.Num(); i++ ) { + int result = (*sortingFields[ i ])( lhs, rhs ); + if( result != 0 ) { + return result; + } + } + + // items are equal on all fields + return 0; +} + +template< class type > +ID_INLINE void rvMultifieldSort< type >::AddCompareFunction( typename idList::cmp_t* fn ) { + sortingFields.Append( fn ); +} + +template< class type > +ID_INLINE void rvMultifieldSort< type >::AddFilterFunction( typename idList::filter_t* fn ) { + filterFields.Append( fn ); +} + +template< class type > +void rvMultifieldSort< type >::Sort( idList& list ) { + int i, j; + + if ( filterFields.Num() ) { + for ( i = 0; i < list.Num(); i++ ) { + for ( j = 0; j < filterFields.Num(); j++ ) { + if( (*filterFields[ j ])( (const type*)(&list[ i ]) ) ) { + list.RemoveIndex( i ); + i--; + break; + } + } + } + } + QSort_Iterative( list, 0, list.Num() ); +} + +template< class type > +ID_INLINE int rvMultifieldSort< type >::Median3( type* list, int a, int b, int c ) { + return Compare( &list[ a ], &list[ b ] ) < 0 ? + ( Compare( &list[ b ], &list[ c ] ) < 0 ? b : ( Compare( &list[ a ], &list[ c ] ) < 0 ? c : a ) ) + : ( Compare( &list[ b ], &list[ c ] ) > 0 ? b : ( Compare( &list[ a ], &list[ c ] ) < 0 ? a : c )); +} + +template< class type > +void rvMultifieldSort< type >::QSort_Iterative( idList& list, int low, int n ) { + int swap_cnt, pivot, lowBound, highBound; + +loop: + swap_cnt = 0; + + // insertion sort on small arrays + if( n < 7 ) { + for( int i = low + 1; i < low + n; i++ ) { + for( int j = i; j > low && Compare( &list[ j - 1 ], &list[ j ] ) > 0; j-- ) { + type t = list[ j - 1 ]; + list[ j - 1 ] = list[ j ]; + list[ j ] = t; + } + } + return; + } + + // pivot selection + pivot = n / 2 + low; + if( n > 7 ) { + lowBound = low; + highBound = low + n - 1; + if (n > 40) { + int d = (n / 8); + lowBound = Median3( list.Ptr(), lowBound, lowBound + d, lowBound + 2 * d ); + pivot = Median3( list.Ptr(), pivot - d, pivot, pivot + d ); + highBound = Median3( list.Ptr(), highBound - 2 * d, highBound - d, highBound ); + } + pivot = Median3( list.Ptr(), lowBound, pivot, highBound ); + } + type t = list[ low ]; + list[ low ] = list[ pivot ]; + list[ pivot ] = t; + + // qsort + int leftA, leftB, rightC, rightD, r; + leftA = leftB = low + 1; + rightC = rightD = low + n - 1; + for (;;) { + while ( leftB <= rightC && (r = Compare( &list[ leftB ], &list[ low ] )) <= 0) { + if (r == 0) { + swap_cnt = 1; + type t = list[ leftA ]; + list[ leftA ] = list[ leftB ]; + list[ leftB ] = t; + leftA++; + } + leftB++; + } + while (leftB <= rightC && (r = Compare( &list[ rightC ], &list[ low ] )) >= 0) { + if (r == 0) { + swap_cnt = 1; + type t = list[ rightC ]; + list[ rightC ] = list[ rightD ]; + list[ rightD ] = t; + rightD--; + } + rightC--; + } + if( leftB > rightC ) { + break; + } + type t = list[ leftB ]; + list[ leftB ] = list[ rightC ]; + list[ rightC ] = t; + swap_cnt = 1; + leftB++; + rightC--; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for( int i = low + 1; i < low + n; i++ ) { + for( int j = i; j > low && Compare( &list[ j - 1 ], &list[ j ] ) > 0; j-- ) { + type t = list[ j ]; + list[ j ] = list[ j - 1 ]; + list[ j - 1 ] = t; + } + } + return; + } + + highBound = low + n; + r = Min( leftA - low, leftB - leftA ); + for( int i = 0; i < r; i++ ) { + type t = list[ low + i ]; + list[ low + i ] = list[ leftB - r + i ]; + list[ leftB - r + i ] = t; + } + + r = Min( rightD - rightC, highBound - rightD - 1 ); + for( int i = 0; i < r; i++ ) { + type t = list[ leftB + i ]; + list[ leftB + i ] = list[ highBound - r + i ]; + list[ highBound - r + i ] = t; + } + + if ( (r = leftB - leftA) > 1 ) { + QSort_Iterative( list, low, r ); + } + if ((r = rightD - rightC) > 1) { + /* Iterate rather than recurse to save stack space */ + low = highBound - r; + n = r; + goto loop; + } + /* qsort(pn - r, r / es, es, cmp);*/ +} + + + +///////// + +#endif + diff --git a/src/idlib/bv/Bounds.cpp b/src/idlib/bv/Bounds.cpp new file mode 100644 index 0000000..5b3102c --- /dev/null +++ b/src/idlib/bv/Bounds.cpp @@ -0,0 +1,491 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +idBounds bounds_zero( vec3_zero, vec3_zero ); + +/* +============ +idBounds::GetRadius +============ +*/ +float idBounds::GetRadius( void ) const { + int i; + float total, b0, b1; + + total = 0.0f; + for ( i = 0; i < 3; i++ ) { + b0 = (float)idMath::Fabs( b[0][i] ); + b1 = (float)idMath::Fabs( b[1][i] ); + if ( b0 > b1 ) { + total += b0 * b0; + } else { + total += b1 * b1; + } + } + return idMath::Sqrt( total ); +} + +/* +============ +idBounds::GetRadius +============ +*/ +float idBounds::GetRadius( const idVec3 ¢er ) const { + int i; + float total, b0, b1; + + total = 0.0f; + for ( i = 0; i < 3; i++ ) { + b0 = (float)idMath::Fabs( center[i] - b[0][i] ); + b1 = (float)idMath::Fabs( b[1][i] - center[i] ); + if ( b0 > b1 ) { + total += b0 * b0; + } else { + total += b1 * b1; + } + } + return idMath::Sqrt( total ); +} + +/* +================ +idBounds::PlaneDistance +================ +*/ +float idBounds::PlaneDistance( const idPlane &plane ) const { + idVec3 center; + float d1, d2; + + center = ( b[0] + b[1] ) * 0.5f; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( ( b[1][0] - center[0] ) * plane.Normal()[0] ) + + idMath::Fabs( ( b[1][1] - center[1] ) * plane.Normal()[1] ) + + idMath::Fabs( ( b[1][2] - center[2] ) * plane.Normal()[2] ); + + if ( d1 - d2 > 0.0f ) { + return d1 - d2; + } + if ( d1 + d2 < 0.0f ) { + return d1 + d2; + } + return 0.0f; +} + +// RAVEN BEGIN +// jscott: for BSE attenuation +/* +================ +idBounds::ShortestDistance +================ +*/ +float idBounds::ShortestDistance( const idVec3 &point ) const { + + int i; + float delta, distance; + + if( ContainsPoint( point ) ) { + + return( 0.0f ); + } + + distance = 0.0f; + for( i = 0; i < 3; i++ ) { + + if( point[i] < b[0][i] ) { + + delta = b[0][i] - point[i]; + distance += delta * delta; + + } else if ( point[i] > b[1][i] ) { + + delta = point[i] - b[1][i]; + distance += delta * delta; + } + } + + return( idMath::Sqrt( distance ) ); +} + +// abahr: +idVec3 idBounds::FindEdgePoint( const idVec3& dir ) const { + return FindEdgePoint( GetCenter(), dir ); +} + +idVec3 idBounds::FindEdgePoint( const idVec3& start, const idVec3& dir ) const { + idVec3 center( GetCenter() ); + float radius = GetRadius( center ); + idVec3 point( start + dir * radius ); + float scale = 0.0f; + + RayIntersection( point, -dir, scale ); + + idVec3 p = point + -dir * scale; + return p; +} + +idVec3 idBounds::FindVectorToEdge( const idVec3& dir ) const { + return idVec3( FindVectorToEdge(GetCenter(), dir) ); +} + +idVec3 idBounds::FindVectorToEdge( const idVec3& start, const idVec3& dir ) const { + return idVec3( FindEdgePoint(start, dir) - start ); +} +// RAVEN END + +/* +================ +idBounds::PlaneSide +================ +*/ +int idBounds::PlaneSide( const idPlane &plane, const float epsilon ) const { + idVec3 center; + float d1, d2; + + center = ( b[0] + b[1] ) * 0.5f; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( ( b[1][0] - center[0] ) * plane.Normal()[0] ) + + idMath::Fabs( ( b[1][1] - center[1] ) * plane.Normal()[1] ) + + idMath::Fabs( ( b[1][2] - center[2] ) * plane.Normal()[2] ); + + if ( d1 - d2 > epsilon ) { + return PLANESIDE_FRONT; + } + if ( d1 + d2 < -epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idBounds::LineIntersection + + Returns true if the line intersects the bounds between the start and end point. +============ +*/ +bool idBounds::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float ld[3]; + idVec3 center = ( b[0] + b[1] ) * 0.5f; + idVec3 extents = b[1] - center; + idVec3 lineDir = 0.5f * ( end - start ); + idVec3 lineCenter = start + lineDir; + idVec3 dir = lineCenter - center; + + ld[0] = idMath::Fabs( lineDir[0] ); + if ( idMath::Fabs( dir[0] ) > extents[0] + ld[0] ) { + return false; + } + + ld[1] = idMath::Fabs( lineDir[1] ); + if ( idMath::Fabs( dir[1] ) > extents[1] + ld[1] ) { + return false; + } + + ld[2] = idMath::Fabs( lineDir[2] ); + if ( idMath::Fabs( dir[2] ) > extents[2] + ld[2] ) { + return false; + } + + idVec3 cross = lineDir.Cross( dir ); + + if ( idMath::Fabs( cross[0] ) > extents[1] * ld[2] + extents[2] * ld[1] ) { + return false; + } + + if ( idMath::Fabs( cross[1] ) > extents[0] * ld[2] + extents[2] * ld[0] ) { + return false; + } + + if ( idMath::Fabs( cross[2] ) > extents[0] * ld[1] + extents[1] * ld[0] ) { + return false; + } + + return true; +} + +/* +============ +idBounds::RayIntersection + + Returns true if the ray intersects the bounds. + The ray can intersect the bounds in both directions from the start point. + If start is inside the bounds it is considered an intersection with scale = 0 +============ +*/ +bool idBounds::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const { + int i, ax0, ax1, ax2, side, inside; + float f; + idVec3 hit; + + ax0 = -1; + inside = 0; + for ( i = 0; i < 3; i++ ) { + if ( start[i] < b[0][i] ) { + side = 0; + } + else if ( start[i] > b[1][i] ) { + side = 1; + } + else { + inside++; + continue; + } + if ( dir[i] == 0.0f ) { + continue; + } + f = ( start[i] - b[side][i] ); + if ( ax0 < 0 || idMath::Fabs( f ) > idMath::Fabs( scale * dir[i] ) ) { + scale = - ( f / dir[i] ); + ax0 = i; + } + } + + if ( ax0 < 0 ) { + scale = 0.0f; + // return true if the start point is inside the bounds + return ( inside == 3 ); + } + + ax1 = (ax0+1)%3; + ax2 = (ax0+2)%3; + hit[ax1] = start[ax1] + scale * dir[ax1]; + hit[ax2] = start[ax2] + scale * dir[ax2]; + + return ( hit[ax1] >= b[0][ax1] && hit[ax1] <= b[1][ax1] && + hit[ax2] >= b[0][ax2] && hit[ax2] <= b[1][ax2] ); +} + +/* +============ +idBounds::FromTransformedBounds +============ +*/ +void idBounds::FromTransformedBounds( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ) { + int i; + idVec3 center, extents, rotatedExtents; + + center = (bounds[0] + bounds[1]) * 0.5f; + extents = bounds[1] - center; + + for ( i = 0; i < 3; i++ ) { + rotatedExtents[i] = idMath::Fabs( extents[0] * axis[0][i] ) + + idMath::Fabs( extents[1] * axis[1][i] ) + + idMath::Fabs( extents[2] * axis[2][i] ); + } + + center = origin + center * axis; + b[0] = center - rotatedExtents; + b[1] = center + rotatedExtents; +} + +/* +============ +idBounds::FromPoints + + Most tight bounds for a point set. +============ +*/ +void idBounds::FromPoints( const idVec3 *points, const int numPoints ) { + SIMDProcessor->MinMax( b[0], b[1], points, numPoints ); +} + +/* +============ +idBounds::FromPointTranslation + + Most tight bounds for the translational movement of the given point. +============ +*/ +void idBounds::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( translation[i] < 0.0f ) { + b[0][i] = point[i] + translation[i]; + b[1][i] = point[i]; + } + else { + b[0][i] = point[i]; + b[1][i] = point[i] + translation[i]; + } + } +} + +/* +============ +idBounds::FromBoundsTranslation + + Most tight bounds for the translational movement of the given bounds. +============ +*/ +void idBounds::FromBoundsTranslation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idVec3 &translation ) { + int i; + + if ( axis.IsRotated() ) { + FromTransformedBounds( bounds, origin, axis ); + } + else { + b[0] = bounds[0] + origin; + b[1] = bounds[1] + origin; + } + for ( i = 0; i < 3; i++ ) { + if ( translation[i] < 0.0f ) { + b[0][i] += translation[i]; + } + else { + b[1][i] += translation[i]; + } + } +} + +/* +================ +BoundsForPointRotation + + only for rotations < 180 degrees +================ +*/ +idBounds BoundsForPointRotation( const idVec3 &start, const idRotation &rotation ) { + int i; + float radiusSqr; + idVec3 v1, v2; + idVec3 origin, axis, end; + idBounds bounds; + + end = start * rotation; + axis = rotation.GetVec(); + origin = rotation.GetOrigin() + axis * ( axis * ( start - rotation.GetOrigin() ) ); + radiusSqr = ( start - origin ).LengthSqr(); + v1 = ( start - origin ).Cross( axis ); + v2 = ( end - origin ).Cross( axis ); + + for ( i = 0; i < 3; i++ ) { + // if the derivative changes sign along this axis during the rotation from start to end + if ( ( v1[i] > 0.0f && v2[i] < 0.0f ) || ( v1[i] < 0.0f && v2[i] > 0.0f ) ) { + if ( ( 0.5f * (start[i] + end[i]) - origin[i] ) > 0.0f ) { + bounds[0][i] = Min( start[i], end[i] ); +// RAVEN BEGIN + bounds[1][i] = origin[i]; + if( axis[i] * axis[i] < 1.0f ) { + bounds[1][i] += idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + } +// RAVEN END + } + else { +// RAVEN BEGIN + bounds[0][i] = origin[i]; + if( axis[i] * axis[i] < 1.0f ) { + bounds[0][i] -= idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) ); + } +// RAVEN END + bounds[1][i] = Max( start[i], end[i] ); + } + } + else if ( start[i] > end[i] ) { + bounds[0][i] = end[i]; + bounds[1][i] = start[i]; + } + else { + bounds[0][i] = start[i]; + bounds[1][i] = end[i]; + } + } + + return bounds; +} + +/* +============ +idBounds::FromPointRotation + + Most tight bounds for the rotational movement of the given point. +============ +*/ +void idBounds::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + float radius; + + if ( idMath::Fabs( rotation.GetAngle() ) < 180.0f ) { + (*this) = BoundsForPointRotation( point, rotation ); + } + else { + + radius = ( point - rotation.GetOrigin() ).Length(); + + // FIXME: these bounds are usually way larger + b[0].Set( -radius, -radius, -radius ); + b[1].Set( radius, radius, radius ); + } +} + +/* +============ +idBounds::FromBoundsRotation + + Most tight bounds for the rotational movement of the given bounds. +============ +*/ +void idBounds::FromBoundsRotation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idRotation &rotation ) { + int i; + float radius; + idVec3 point; + idBounds rBounds; + + if ( idMath::Fabs( rotation.GetAngle() ) < 180.0f ) { + + (*this) = BoundsForPointRotation( bounds[0] * axis + origin, rotation ); + for ( i = 1; i < 8; i++ ) { + point[0] = bounds[(i^(i>>1))&1][0]; + point[1] = bounds[(i>>1)&1][1]; + point[2] = bounds[(i>>2)&1][2]; + (*this) += BoundsForPointRotation( point * axis + origin, rotation ); + } + } + else { + + point = (bounds[1] - bounds[0]) * 0.5f; + radius = (bounds[1] - point).Length() + (point - rotation.GetOrigin()).Length(); + + // FIXME: these bounds are usually way larger + b[0].Set( -radius, -radius, -radius ); + b[1].Set( radius, radius, radius ); + } +} + +/* +============ +idBounds::ToPoints +============ +*/ +void idBounds::ToPoints( idVec3 points[8] ) const { + for ( int i = 0; i < 8; i++ ) { + points[i][0] = b[(i^(i>>1))&1][0]; + points[i][1] = b[(i>>1)&1][1]; + points[i][2] = b[(i>>2)&1][2]; + } +} diff --git a/src/idlib/bv/Bounds.h b/src/idlib/bv/Bounds.h new file mode 100644 index 0000000..17d8ee9 --- /dev/null +++ b/src/idlib/bv/Bounds.h @@ -0,0 +1,472 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BV_BOUNDS_H__ +#define __BV_BOUNDS_H__ + +/* +=============================================================================== + + Axis Aligned Bounding Box + +=============================================================================== +*/ + +class idBounds { +public: + idBounds( void ); + explicit idBounds( const idVec3 &mins, const idVec3 &maxs ); + explicit idBounds( const idVec3 &point ); + + const idVec3 & operator[]( const int index ) const; + idVec3 & operator[]( const int index ); + idBounds operator+( const idVec3 &t ) const; // returns translated bounds + idBounds & operator+=( const idVec3 &t ); // translate the bounds + idBounds operator*( const idMat3 &r ) const; // returns rotated bounds + idBounds & operator*=( const idMat3 &r ); // rotate the bounds + idBounds operator+( const idBounds &a ) const; + idBounds & operator+=( const idBounds &a ); + idBounds operator-( const idBounds &a ) const; + idBounds & operator-=( const idBounds &a ); + + bool Compare( const idBounds &a ) const; // exact compare, no epsilon + bool Compare( const idBounds &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idBounds &a ) const; // exact compare, no epsilon + bool operator!=( const idBounds &a ) const; // exact compare, no epsilon + + void Clear( void ); // inside out bounds + void Zero( void ); // single point at origin + + idVec3 GetCenter( void ) const; // returns center of bounds + float GetRadius( void ) const; // returns the radius relative to the bounds origin + float GetRadius( const idVec3 ¢er ) const; // returns the radius relative to the given center + float GetVolume( void ) const; // returns the volume of the bounds + bool IsCleared( void ) const; // returns true if bounds are inside out + + bool AddPoint( const idVec3 &v ); // add the point, returns true if the bounds expanded + bool AddBounds( const idBounds &a ); // add the bounds, returns true if the bounds expanded + idBounds Intersect( const idBounds &a ) const; // return intersection of this bounds with the given bounds + idBounds & IntersectSelf( const idBounds &a ); // intersect this bounds with the given bounds + idBounds Expand( const float d ) const; // return bounds expanded in all directions with the given value + idBounds & ExpandSelf( const float d ); // expand bounds in all directions with the given value + +// RAVEN BEGIN +// jscott: for BSE + idBounds & ExpandSelf( const idVec3 &d ); // expand bounds in all directions by the given vector + const idVec3 Size( void ) const; + float ShortestDistance( const idVec3 &point ) const; + bool Contains( const idBounds &a ) const; +// jscott: for material types + int GetLargestAxis( void ) const; +// abahr: can be used to get width of bounds + idVec3 FindEdgePoint( const idVec3& dir ) const; + idVec3 FindEdgePoint( const idVec3& start, const idVec3& dir ) const; + idVec3 FindVectorToEdge( const idVec3& dir ) const; + idVec3 FindVectorToEdge( const idVec3& start, const idVec3& dir ) const; +// RAVEN END + + idBounds Translate( const idVec3 &translation ) const; // return translated bounds + idBounds & TranslateSelf( const idVec3 &translation ); // translate this bounds + idBounds Rotate( const idMat3 &rotation ) const; // return rotated bounds + idBounds & RotateSelf( const idMat3 &rotation ); // rotate this bounds + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsBounds( const idBounds &a ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const; + + // most tight bounds for the given transformed bounds + void FromTransformedBounds( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ); + // most tight bounds for a point set + void FromPoints( const idVec3 *points, const int numPoints ); + // most tight bounds for a translation + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromBoundsTranslation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idVec3 &translation ); + // most tight bounds for a rotation + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromBoundsRotation( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis, const idRotation &rotation ); + + void ToPoints( idVec3 points[8] ) const; + idSphere ToSphere( void ) const; + + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idVec3 &origin, const idMat3 &axis, const idVec3 &dir, float &min, float &max ) const; + + idVec3 b[2]; +}; + +extern idBounds bounds_zero; + +ID_INLINE idBounds::idBounds( void ) { +} + +ID_INLINE idBounds::idBounds( const idVec3 &mins, const idVec3 &maxs ) { + b[0] = mins; + b[1] = maxs; +} + +ID_INLINE idBounds::idBounds( const idVec3 &point ) { + b[0] = point; + b[1] = point; +} + +ID_INLINE const idVec3 &idBounds::operator[]( const int index ) const { + return b[index]; +} + +ID_INLINE idVec3 &idBounds::operator[]( const int index ) { + return b[index]; +} + +ID_INLINE idBounds idBounds::operator+( const idVec3 &t ) const { + return idBounds( b[0] + t, b[1] + t ); +} + +ID_INLINE idBounds &idBounds::operator+=( const idVec3 &t ) { + b[0] += t; + b[1] += t; + return *this; +} + +ID_INLINE idBounds idBounds::operator*( const idMat3 &r ) const { + idBounds bounds; + bounds.FromTransformedBounds( *this, vec3_origin, r ); + return bounds; +} + +ID_INLINE idBounds &idBounds::operator*=( const idMat3 &r ) { + this->FromTransformedBounds( *this, vec3_origin, r ); + return *this; +} + +ID_INLINE idBounds idBounds::operator+( const idBounds &a ) const { + idBounds newBounds; + newBounds = *this; + newBounds.AddBounds( a ); + return newBounds; +} + +ID_INLINE idBounds &idBounds::operator+=( const idBounds &a ) { + idBounds::AddBounds( a ); + return *this; +} + +ID_INLINE idBounds idBounds::operator-( const idBounds &a ) const { + assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] && + b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] && + b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] ); + return idBounds( idVec3( b[0][0] + a.b[1][0], b[0][1] + a.b[1][1], b[0][2] + a.b[1][2] ), + idVec3( b[1][0] + a.b[0][0], b[1][1] + a.b[0][1], b[1][2] + a.b[0][2] ) ); +} + +ID_INLINE idBounds &idBounds::operator-=( const idBounds &a ) { + assert( b[1][0] - b[0][0] > a.b[1][0] - a.b[0][0] && + b[1][1] - b[0][1] > a.b[1][1] - a.b[0][1] && + b[1][2] - b[0][2] > a.b[1][2] - a.b[0][2] ); + b[0] += a.b[1]; + b[1] += a.b[0]; + return *this; +} + +ID_INLINE bool idBounds::Compare( const idBounds &a ) const { + return ( b[0].Compare( a.b[0] ) && b[1].Compare( a.b[1] ) ); +} + +ID_INLINE bool idBounds::Compare( const idBounds &a, const float epsilon ) const { + return ( b[0].Compare( a.b[0], epsilon ) && b[1].Compare( a.b[1], epsilon ) ); +} + +ID_INLINE bool idBounds::operator==( const idBounds &a ) const { + return Compare( a ); +} + +ID_INLINE bool idBounds::operator!=( const idBounds &a ) const { + return !Compare( a ); +} + +ID_INLINE void idBounds::Clear( void ) { + b[0][0] = b[0][1] = b[0][2] = idMath::INFINITY; + b[1][0] = b[1][1] = b[1][2] = -idMath::INFINITY; +} + +ID_INLINE void idBounds::Zero( void ) { + b[0][0] = b[0][1] = b[0][2] = + b[1][0] = b[1][1] = b[1][2] = 0; +} + +ID_INLINE idVec3 idBounds::GetCenter( void ) const { + return idVec3( ( b[1][0] + b[0][0] ) * 0.5f, ( b[1][1] + b[0][1] ) * 0.5f, ( b[1][2] + b[0][2] ) * 0.5f ); +} + +ID_INLINE float idBounds::GetVolume( void ) const { + if ( b[0][0] >= b[1][0] || b[0][1] >= b[1][1] || b[0][2] >= b[1][2] ) { + return 0.0f; + } + return ( ( b[1][0] - b[0][0] ) * ( b[1][1] - b[0][1] ) * ( b[1][2] - b[0][2] ) ); +} + +ID_INLINE bool idBounds::IsCleared( void ) const { + return b[0][0] > b[1][0]; +} + +ID_INLINE bool idBounds::AddPoint( const idVec3 &v ) { + bool expanded = false; + if ( v[0] < b[0][0]) { + b[0][0] = v[0]; + expanded = true; + } + if ( v[0] > b[1][0]) { + b[1][0] = v[0]; + expanded = true; + } + if ( v[1] < b[0][1] ) { + b[0][1] = v[1]; + expanded = true; + } + if ( v[1] > b[1][1]) { + b[1][1] = v[1]; + expanded = true; + } + if ( v[2] < b[0][2] ) { + b[0][2] = v[2]; + expanded = true; + } + if ( v[2] > b[1][2]) { + b[1][2] = v[2]; + expanded = true; + } + return expanded; +} + +ID_INLINE bool idBounds::AddBounds( const idBounds &a ) { + bool expanded = false; + if ( a.b[0][0] < b[0][0] ) { + b[0][0] = a.b[0][0]; + expanded = true; + } + if ( a.b[0][1] < b[0][1] ) { + b[0][1] = a.b[0][1]; + expanded = true; + } + if ( a.b[0][2] < b[0][2] ) { + b[0][2] = a.b[0][2]; + expanded = true; + } + if ( a.b[1][0] > b[1][0] ) { + b[1][0] = a.b[1][0]; + expanded = true; + } + if ( a.b[1][1] > b[1][1] ) { + b[1][1] = a.b[1][1]; + expanded = true; + } + if ( a.b[1][2] > b[1][2] ) { + b[1][2] = a.b[1][2]; + expanded = true; + } + return expanded; +} + +ID_INLINE idBounds idBounds::Intersect( const idBounds &a ) const { + idBounds n; + n.b[0][0] = ( a.b[0][0] > b[0][0] ) ? a.b[0][0] : b[0][0]; + n.b[0][1] = ( a.b[0][1] > b[0][1] ) ? a.b[0][1] : b[0][1]; + n.b[0][2] = ( a.b[0][2] > b[0][2] ) ? a.b[0][2] : b[0][2]; + n.b[1][0] = ( a.b[1][0] < b[1][0] ) ? a.b[1][0] : b[1][0]; + n.b[1][1] = ( a.b[1][1] < b[1][1] ) ? a.b[1][1] : b[1][1]; + n.b[1][2] = ( a.b[1][2] < b[1][2] ) ? a.b[1][2] : b[1][2]; + return n; +} + +ID_INLINE idBounds &idBounds::IntersectSelf( const idBounds &a ) { + if ( a.b[0][0] > b[0][0] ) { + b[0][0] = a.b[0][0]; + } + if ( a.b[0][1] > b[0][1] ) { + b[0][1] = a.b[0][1]; + } + if ( a.b[0][2] > b[0][2] ) { + b[0][2] = a.b[0][2]; + } + if ( a.b[1][0] < b[1][0] ) { + b[1][0] = a.b[1][0]; + } + if ( a.b[1][1] < b[1][1] ) { + b[1][1] = a.b[1][1]; + } + if ( a.b[1][2] < b[1][2] ) { + b[1][2] = a.b[1][2]; + } + return *this; +} + +ID_INLINE idBounds idBounds::Expand( const float d ) const { + return idBounds( idVec3( b[0][0] - d, b[0][1] - d, b[0][2] - d ), + idVec3( b[1][0] + d, b[1][1] + d, b[1][2] + d ) ); +} + +ID_INLINE idBounds &idBounds::ExpandSelf( const float d ) { + b[0][0] -= d; + b[0][1] -= d; + b[0][2] -= d; + b[1][0] += d; + b[1][1] += d; + b[1][2] += d; + return *this; +} + +// RAVEN BEGIN +// jscott: for BSE +ID_INLINE idBounds &idBounds::ExpandSelf( const idVec3 &d ) +{ + b[0][0] -= d[0]; + b[0][1] -= d[1]; + b[0][2] -= d[2]; + b[1][0] += d[0]; + b[1][1] += d[1]; + b[1][2] += d[2]; + return( *this ); +} + +ID_INLINE const idVec3 idBounds::Size( void ) const +{ + return( b[1] - b[0] ); +} + +ID_INLINE bool idBounds::Contains( const idBounds &a ) const +{ + int i; + + for( i = 0; i < 3; i++ ) { + if( a[1][i] > b[1][i] ) { + return( false ); + } + if( a[0][i] < b[0][i] ) { + return( false ); + } + } + + return( true ); +} + +// jscott: for material types +ID_INLINE int idBounds::GetLargestAxis( void ) const +{ + idVec3 work = b[1] - b[0]; + int axis = 0; + + if( work[1] > work[0] ) + { + axis = 1; + } + if( work[2] > work[axis] ) + { + axis = 2; + } + return( axis ); +} +// RAVEN END + +ID_INLINE idBounds idBounds::Translate( const idVec3 &translation ) const { + return idBounds( b[0] + translation, b[1] + translation ); +} + +ID_INLINE idBounds &idBounds::TranslateSelf( const idVec3 &translation ) { + b[0] += translation; + b[1] += translation; + return *this; +} + +ID_INLINE idBounds idBounds::Rotate( const idMat3 &rotation ) const { + idBounds bounds; + bounds.FromTransformedBounds( *this, vec3_origin, rotation ); + return bounds; +} + +ID_INLINE idBounds &idBounds::RotateSelf( const idMat3 &rotation ) { + FromTransformedBounds( *this, vec3_origin, rotation ); + return *this; +} + +ID_INLINE bool idBounds::ContainsPoint( const idVec3 &p ) const { + if ( p[0] < b[0][0] || p[1] < b[0][1] || p[2] < b[0][2] + || p[0] > b[1][0] || p[1] > b[1][1] || p[2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE bool idBounds::IntersectsBounds( const idBounds &a ) const { + if ( a.b[1][0] < b[0][0] || a.b[1][1] < b[0][1] || a.b[1][2] < b[0][2] + || a.b[0][0] > b[1][0] || a.b[0][1] > b[1][1] || a.b[0][2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE idSphere idBounds::ToSphere( void ) const { + idSphere sphere; + sphere.SetOrigin( ( b[0] + b[1] ) * 0.5f ); + sphere.SetRadius( ( b[1] - sphere.GetOrigin() ).Length() ); + return sphere; +} + +ID_INLINE void idBounds::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d1, d2; + idVec3 center, extents; + + center = ( b[0] + b[1] ) * 0.5f; + extents = b[1] - center; + + d1 = dir * center; + d2 = idMath::Fabs( extents[0] * dir[0] ) + + idMath::Fabs( extents[1] * dir[1] ) + + idMath::Fabs( extents[2] * dir[2] ); + + min = d1 - d2; + max = d1 + d2; +} + +ID_INLINE void idBounds::AxisProjection( const idVec3 &origin, const idMat3 &axis, const idVec3 &dir, float &min, float &max ) const { + float d1, d2; + idVec3 center, extents; + + center = ( b[0] + b[1] ) * 0.5f; + extents = b[1] - center; + center = origin + center * axis; + + d1 = dir * center; + d2 = idMath::Fabs( extents[0] * ( dir * axis[0] ) ) + + idMath::Fabs( extents[1] * ( dir * axis[1] ) ) + + idMath::Fabs( extents[2] * ( dir * axis[2] ) ); + + min = d1 - d2; + max = d1 + d2; +} + +#endif /* !__BV_BOUNDS_H__ */ diff --git a/src/idlib/bv/Box.cpp b/src/idlib/bv/Box.cpp new file mode 100644 index 0000000..62c5a71 --- /dev/null +++ b/src/idlib/bv/Box.cpp @@ -0,0 +1,843 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +idBox box_zero( vec3_zero, vec3_zero, mat3_identity ); + +/* + 4---{4}---5 + + /| /| + Z {7} {8} {5} | + - / | / {9} + 7--{6}----6 | + | | | | + {11} 0---|-{0}-1 + | / | / - + | {3} {10} {1} Y + |/ |/ + + 3---{2}---2 + + - X + + + plane bits: + 0 = min x + 1 = max x + 2 = min y + 3 = max y + 4 = min z + 5 = max z + +*/ + +static int boxVertPlanes[8] = { + ( (1<<0) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<3) | (1<<5) ), + ( (1<<0) | (1<<3) | (1<<5) ) +}; + +static int boxVertEdges[8][3] = { + // bottom + { 3, 0, 8 }, + { 0, 1, 9 }, + { 1, 2, 10 }, + { 2, 3, 11 }, + // top + { 7, 4, 8 }, + { 4, 5, 9 }, + { 5, 6, 10 }, + { 6, 7, 11 } +}; + +static int boxEdgePlanes[12][2] = { + // bottom + { 4, 2 }, + { 4, 1 }, + { 4, 3 }, + { 4, 0 }, + // top + { 5, 2 }, + { 5, 1 }, + { 5, 3 }, + { 5, 0 }, + // sides + { 0, 2 }, + { 2, 1 }, + { 1, 3 }, + { 3, 0 } +}; + +static int boxEdgeVerts[12][2] = { + // bottom + { 0, 1 }, + { 1, 2 }, + { 2, 3 }, + { 3, 0 }, + // top + { 4, 5 }, + { 5, 6 }, + { 6, 7 }, + { 7, 4 }, + // sides + { 0, 4 }, + { 1, 5 }, + { 2, 6 }, + { 3, 7 } +}; + +static int boxPlaneBitsSilVerts[64][7] = { + { 0, 0, 0, 0, 0, 0, 0 }, // 000000 = 0 + { 4, 7, 4, 0, 3, 0, 0 }, // 000001 = 1 + { 4, 5, 6, 2, 1, 0, 0 }, // 000010 = 2 + { 0, 0, 0, 0, 0, 0, 0 }, // 000011 = 3 + { 4, 4, 5, 1, 0, 0, 0 }, // 000100 = 4 + { 6, 3, 7, 4, 5, 1, 0 }, // 000101 = 5 + { 6, 4, 5, 6, 2, 1, 0 }, // 000110 = 6 + { 0, 0, 0, 0, 0, 0, 0 }, // 000111 = 7 + { 4, 6, 7, 3, 2, 0, 0 }, // 001000 = 8 + { 6, 6, 7, 4, 0, 3, 2 }, // 001001 = 9 + { 6, 5, 6, 7, 3, 2, 1 }, // 001010 = 10 + { 0, 0, 0, 0, 0, 0, 0 }, // 001011 = 11 + { 0, 0, 0, 0, 0, 0, 0 }, // 001100 = 12 + { 0, 0, 0, 0, 0, 0, 0 }, // 001101 = 13 + { 0, 0, 0, 0, 0, 0, 0 }, // 001110 = 14 + { 0, 0, 0, 0, 0, 0, 0 }, // 001111 = 15 + { 4, 0, 1, 2, 3, 0, 0 }, // 010000 = 16 + { 6, 0, 1, 2, 3, 7, 4 }, // 010001 = 17 + { 6, 3, 2, 6, 5, 1, 0 }, // 010010 = 18 + { 0, 0, 0, 0, 0, 0, 0 }, // 010011 = 19 + { 6, 1, 2, 3, 0, 4, 5 }, // 010100 = 20 + { 6, 1, 2, 3, 7, 4, 5 }, // 010101 = 21 + { 6, 2, 3, 0, 4, 5, 6 }, // 010110 = 22 + { 0, 0, 0, 0, 0, 0, 0 }, // 010111 = 23 + { 6, 0, 1, 2, 6, 7, 3 }, // 011000 = 24 + { 6, 0, 1, 2, 6, 7, 4 }, // 011001 = 25 + { 6, 0, 1, 5, 6, 7, 3 }, // 011010 = 26 + { 0, 0, 0, 0, 0, 0, 0 }, // 011011 = 27 + { 0, 0, 0, 0, 0, 0, 0 }, // 011100 = 28 + { 0, 0, 0, 0, 0, 0, 0 }, // 011101 = 29 + { 0, 0, 0, 0, 0, 0, 0 }, // 011110 = 30 + { 0, 0, 0, 0, 0, 0, 0 }, // 011111 = 31 + { 4, 7, 6, 5, 4, 0, 0 }, // 100000 = 32 + { 6, 7, 6, 5, 4, 0, 3 }, // 100001 = 33 + { 6, 5, 4, 7, 6, 2, 1 }, // 100010 = 34 + { 0, 0, 0, 0, 0, 0, 0 }, // 100011 = 35 + { 6, 4, 7, 6, 5, 1, 0 }, // 100100 = 36 + { 6, 3, 7, 6, 5, 1, 0 }, // 100101 = 37 + { 6, 4, 7, 6, 2, 1, 0 }, // 100110 = 38 + { 0, 0, 0, 0, 0, 0, 0 }, // 100111 = 39 + { 6, 6, 5, 4, 7, 3, 2 }, // 101000 = 40 + { 6, 6, 5, 4, 0, 3, 2 }, // 101001 = 41 + { 6, 5, 4, 7, 3, 2, 1 }, // 101010 = 42 + { 0, 0, 0, 0, 0, 0, 0 }, // 101011 = 43 + { 0, 0, 0, 0, 0, 0, 0 }, // 101100 = 44 + { 0, 0, 0, 0, 0, 0, 0 }, // 101101 = 45 + { 0, 0, 0, 0, 0, 0, 0 }, // 101110 = 46 + { 0, 0, 0, 0, 0, 0, 0 }, // 101111 = 47 + { 0, 0, 0, 0, 0, 0, 0 }, // 110000 = 48 + { 0, 0, 0, 0, 0, 0, 0 }, // 110001 = 49 + { 0, 0, 0, 0, 0, 0, 0 }, // 110010 = 50 + { 0, 0, 0, 0, 0, 0, 0 }, // 110011 = 51 + { 0, 0, 0, 0, 0, 0, 0 }, // 110100 = 52 + { 0, 0, 0, 0, 0, 0, 0 }, // 110101 = 53 + { 0, 0, 0, 0, 0, 0, 0 }, // 110110 = 54 + { 0, 0, 0, 0, 0, 0, 0 }, // 110111 = 55 + { 0, 0, 0, 0, 0, 0, 0 }, // 111000 = 56 + { 0, 0, 0, 0, 0, 0, 0 }, // 111001 = 57 + { 0, 0, 0, 0, 0, 0, 0 }, // 111010 = 58 + { 0, 0, 0, 0, 0, 0, 0 }, // 111011 = 59 + { 0, 0, 0, 0, 0, 0, 0 }, // 111100 = 60 + { 0, 0, 0, 0, 0, 0, 0 }, // 111101 = 61 + { 0, 0, 0, 0, 0, 0, 0 }, // 111110 = 62 + { 0, 0, 0, 0, 0, 0, 0 }, // 111111 = 63 +}; + + +/* +============ +idBox::AddPoint +============ +*/ +bool idBox::AddPoint( const idVec3 &v ) { + idMat3 axis2; + idBounds bounds1, bounds2; + + if ( extents[0] < 0.0f ) { + extents.Zero(); + center = v; + axis.Identity(); + return true; + } + + bounds1[0][0] = bounds1[1][0] = center * axis[0]; + bounds1[0][1] = bounds1[1][1] = center * axis[1]; + bounds1[0][2] = bounds1[1][2] = center * axis[2]; + bounds1[0] -= extents; + bounds1[1] += extents; + if ( !bounds1.AddPoint( idVec3( v * axis[0], v * axis[1], v * axis[2] ) ) ) { + // point is contained in the box + return false; + } + + axis2[0] = v - center; + axis2[0].Normalize(); + axis2[1] = axis[ Min3Index( axis2[0] * axis[0], axis2[0] * axis[1], axis2[0] * axis[2] ) ]; + axis2[1] = axis2[1] - ( axis2[1] * axis2[0] ) * axis2[0]; + axis2[1].Normalize(); + axis2[2].Cross( axis2[0], axis2[1] ); + + AxisProjection( axis2, bounds2 ); + bounds2.AddPoint( idVec3( v * axis2[0], v * axis2[1], v * axis2[2] ) ); + + // create new box based on the smallest bounds + if ( bounds1.GetVolume() < bounds2.GetVolume() ) { + center = ( bounds1[0] + bounds1[1] ) * 0.5f; + extents = bounds1[1] - center; + center *= axis; + } + else { + center = ( bounds2[0] + bounds2[1] ) * 0.5f; + extents = bounds2[1] - center; + center *= axis2; + axis = axis2; + } + return true; +} + +/* +============ +idBox::AddBox +============ +*/ +bool idBox::AddBox( const idBox &a ) { + int i, besti; + float v, bestv; + idVec3 dir; + idMat3 ax[4]; + idBounds bounds[4], b; + + if ( a.extents[0] < 0.0f ) { + return false; + } + + if ( extents[0] < 0.0f ) { + center = a.center; + extents = a.extents; + axis = a.axis; + return true; + } + + // test axis of this box + ax[0] = axis; + bounds[0][0][0] = bounds[0][1][0] = center * ax[0][0]; + bounds[0][0][1] = bounds[0][1][1] = center * ax[0][1]; + bounds[0][0][2] = bounds[0][1][2] = center * ax[0][2]; + bounds[0][0] -= extents; + bounds[0][1] += extents; + a.AxisProjection( ax[0], b ); + if ( !bounds[0].AddBounds( b ) ) { + // the other box is contained in this box + return false; + } + + // test axis of other box + ax[1] = a.axis; + bounds[1][0][0] = bounds[1][1][0] = a.center * ax[1][0]; + bounds[1][0][1] = bounds[1][1][1] = a.center * ax[1][1]; + bounds[1][0][2] = bounds[1][1][2] = a.center * ax[1][2]; + bounds[1][0] -= a.extents; + bounds[1][1] += a.extents; + AxisProjection( ax[1], b ); + if ( !bounds[1].AddBounds( b ) ) { + // this box is contained in the other box + center = a.center; + extents = a.extents; + axis = a.axis; + return true; + } + + // test axes aligned with the vector between the box centers and one of the box axis + dir = a.center - center; + dir.Normalize(); + for ( i = 2; i < 4; i++ ) { + ax[i][0] = dir; + ax[i][1] = ax[i-2][ Min3Index( dir * ax[i-2][0], dir * ax[i-2][1], dir * ax[i-2][2] ) ]; + ax[i][1] = ax[i][1] - ( ax[i][1] * dir ) * dir; + ax[i][1].Normalize(); + ax[i][2].Cross( dir, ax[i][1] ); + + AxisProjection( ax[i], bounds[i] ); + a.AxisProjection( ax[i], b ); + bounds[i].AddBounds( b ); + } + + // get the bounds with the smallest volume + bestv = idMath::INFINITY; + besti = 0; + for ( i = 0; i < 4; i++ ) { + v = bounds[i].GetVolume(); + if ( v < bestv ) { + bestv = v; + besti = i; + } + } + + // create a box from the smallest bounds axis pair + center = ( bounds[besti][0] + bounds[besti][1] ) * 0.5f; + extents = bounds[besti][1] - center; + center *= ax[besti]; + axis = ax[besti]; + + return false; +} + +/* +================ +idBox::PlaneDistance +================ +*/ +float idBox::PlaneDistance( const idPlane &plane ) const { + float d1, d2; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( extents[0] * ( plane.Normal() * axis[0] ) ) + + idMath::Fabs( extents[1] * ( plane.Normal() * axis[1] ) ) + + idMath::Fabs( extents[2] * ( plane.Normal() * axis[2] ) ); + + if ( d1 - d2 > 0.0f ) { + return d1 - d2; + } + if ( d1 + d2 < 0.0f ) { + return d1 + d2; + } + return 0.0f; +} + +/* +================ +idBox::PlaneSide +================ +*/ +int idBox::PlaneSide( const idPlane &plane, const float epsilon ) const { + float d1, d2; + + d1 = plane.Distance( center ); + d2 = idMath::Fabs( extents[0] * ( plane.Normal() * axis[0] ) ) + + idMath::Fabs( extents[1] * ( plane.Normal() * axis[1] ) ) + + idMath::Fabs( extents[2] * ( plane.Normal() * axis[2] ) ); + + if ( d1 - d2 > epsilon ) { + return PLANESIDE_FRONT; + } + if ( d1 + d2 < -epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idBox::IntersectsBox +============ +*/ +bool idBox::IntersectsBox( const idBox &a ) const { + idVec3 dir; // vector between centers + float c[3][3]; // matrix c = axis.Transpose() * a.axis + float ac[3][3]; // absolute values of c + float axisdir[3]; // axis[i] * dir + float d, e0, e1; // distance between centers and projected extents + + dir = a.center - center; + + // axis C0 + t * A0 + c[0][0] = axis[0] * a.axis[0]; + c[0][1] = axis[0] * a.axis[1]; + c[0][2] = axis[0] * a.axis[2]; + axisdir[0] = axis[0] * dir; + ac[0][0] = idMath::Fabs( c[0][0] ); + ac[0][1] = idMath::Fabs( c[0][1] ); + ac[0][2] = idMath::Fabs( c[0][2] ); + + d = idMath::Fabs( axisdir[0] ); + e0 = extents[0]; + e1 = a.extents[0] * ac[0][0] + a.extents[1] * ac[0][1] + a.extents[2] * ac[0][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1 + c[1][0] = axis[1] * a.axis[0]; + c[1][1] = axis[1] * a.axis[1]; + c[1][2] = axis[1] * a.axis[2]; + axisdir[1] = axis[1] * dir; + ac[1][0] = idMath::Fabs( c[1][0] ); + ac[1][1] = idMath::Fabs( c[1][1] ); + ac[1][2] = idMath::Fabs( c[1][2] ); + + d = idMath::Fabs( axisdir[1] ); + e0 = extents[1]; + e1 = a.extents[0] * ac[1][0] + a.extents[1] * ac[1][1] + a.extents[2] * ac[1][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2 + c[2][0] = axis[2] * a.axis[0]; + c[2][1] = axis[2] * a.axis[1]; + c[2][2] = axis[2] * a.axis[2]; + axisdir[2] = axis[2] * dir; + ac[2][0] = idMath::Fabs( c[2][0] ); + ac[2][1] = idMath::Fabs( c[2][1] ); + ac[2][2] = idMath::Fabs( c[2][2] ); + + d = idMath::Fabs( axisdir[2] ); + e0 = extents[2]; + e1 = a.extents[0] * ac[2][0] + a.extents[1] * ac[2][1] + a.extents[2] * ac[2][2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B0 + d = idMath::Fabs( a.axis[0] * dir ); + e0 = extents[0] * ac[0][0] + extents[1] * ac[1][0] + extents[2] * ac[2][0]; + e1 = a.extents[0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B1 + d = idMath::Fabs( a.axis[1] * dir ); + e0 = extents[0] * ac[0][1] + extents[1] * ac[1][1] + extents[2] * ac[2][1]; + e1 = a.extents[1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * B2 + d = idMath::Fabs( a.axis[2] * dir ); + e0 = extents[0] * ac[0][2] + extents[1] * ac[1][2] + extents[2] * ac[2][2]; + e1 = a.extents[2]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB0 + d = idMath::Fabs( axisdir[2] * c[1][0] - axisdir[1] * c[2][0] ); + e0 = extents[1] * ac[2][0] + extents[2] * ac[1][0]; + e1 = a.extents[1] * ac[0][2] + a.extents[2] * ac[0][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB1 + d = idMath::Fabs( axisdir[2] * c[1][1] - axisdir[1] * c[2][1] ); + e0 = extents[1] * ac[2][1] + extents[2] * ac[1][1]; + e1 = a.extents[0] * ac[0][2] + a.extents[2] * ac[0][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A0xB2 + d = idMath::Fabs( axisdir[2] * c[1][2] - axisdir[1] * c[2][2] ); + e0 = extents[1] * ac[2][2] + extents[2] * ac[1][2]; + e1 = a.extents[0] * ac[0][1] + a.extents[1] * ac[0][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB0 + d = idMath::Fabs( axisdir[0] * c[2][0] - axisdir[2] * c[0][0] ); + e0 = extents[0] * ac[2][0] + extents[2] * ac[0][0]; + e1 = a.extents[1] * ac[1][2] + a.extents[2] * ac[1][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB1 + d = idMath::Fabs( axisdir[0] * c[2][1] - axisdir[2] * c[0][1] ); + e0 = extents[0] * ac[2][1] + extents[2] * ac[0][1]; + e1 = a.extents[0] * ac[1][2] + a.extents[2] * ac[1][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A1xB2 + d = idMath::Fabs( axisdir[0] * c[2][2] - axisdir[2] * c[0][2] ); + e0 = extents[0] * ac[2][2] + extents[2] * ac[0][2]; + e1 = a.extents[0] * ac[1][1] + a.extents[1] * ac[1][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB0 + d = idMath::Fabs( axisdir[1] * c[0][0] - axisdir[0] * c[1][0] ); + e0 = extents[0] * ac[1][0] + extents[1] * ac[0][0]; + e1 = a.extents[1] * ac[2][2] + a.extents[2] * ac[2][1]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB1 + d = idMath::Fabs( axisdir[1] * c[0][1] - axisdir[0] * c[1][1] ); + e0 = extents[0] * ac[1][1] + extents[1] * ac[0][1]; + e1 = a.extents[0] * ac[2][2] + a.extents[2] * ac[2][0]; + if ( d > e0 + e1 ) { + return false; + } + + // axis C0 + t * A2xB2 + d = idMath::Fabs( axisdir[1] * c[0][2] - axisdir[0] * c[1][2] ); + e0 = extents[0] * ac[1][2] + extents[1] * ac[0][2]; + e1 = a.extents[0] * ac[2][1] + a.extents[1] * ac[2][0]; + if ( d > e0 + e1 ) { + return false; + } + return true; +} + +/* +============ +idBox::LineIntersection + + Returns true if the line intersects the box between the start and end point. +============ +*/ +bool idBox::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float ld[3]; + idVec3 lineDir = 0.5f * ( end - start ); + idVec3 lineCenter = start + lineDir; + idVec3 dir = lineCenter - center; + + ld[0] = idMath::Fabs( lineDir * axis[0] ); + if ( idMath::Fabs( dir * axis[0] ) > extents[0] + ld[0] ) { + return false; + } + + ld[1] = idMath::Fabs( lineDir * axis[1] ); + if ( idMath::Fabs( dir * axis[1] ) > extents[1] + ld[1] ) { + return false; + } + + ld[2] = idMath::Fabs( lineDir * axis[2] ); + if ( idMath::Fabs( dir * axis[2] ) > extents[2] + ld[2] ) { + return false; + } + + idVec3 cross = lineDir.Cross( dir ); + + if ( idMath::Fabs( cross * axis[0] ) > extents[1] * ld[2] + extents[2] * ld[1] ) { + return false; + } + + if ( idMath::Fabs( cross * axis[1] ) > extents[0] * ld[2] + extents[2] * ld[0] ) { + return false; + } + + if ( idMath::Fabs( cross * axis[2] ) > extents[0] * ld[1] + extents[1] * ld[0] ) { + return false; + } + + return true; +} + +/* +============ +BoxPlaneClip +============ +*/ +static bool BoxPlaneClip( const float denom, const float numer, float &scale0, float &scale1 ) { + if ( denom > 0.0f ) { + if ( numer > denom * scale1 ) { + return false; + } + if ( numer > denom * scale0 ) { + scale0 = numer / denom; + } + return true; + } + else if ( denom < 0.0f ) { + if ( numer > denom * scale0 ) { + return false; + } + if ( numer > denom * scale1 ) { + scale1 = numer / denom; + } + return true; + } + else { + return ( numer <= 0.0f ); + } +} + +/* +============ +idBox::RayIntersection + + Returns true if the ray intersects the box. + The ray can intersect the box in both directions from the start point. + If start is inside the box then scale1 < 0 and scale2 > 0. +============ +*/ +bool idBox::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + idVec3 localStart, localDir; + + localStart = ( start - center ) * axis.Transpose(); + localDir = dir * axis.Transpose(); + + scale1 = -idMath::INFINITY; + scale2 = idMath::INFINITY; + return BoxPlaneClip( localDir.x, -localStart.x - extents[0], scale1, scale2 ) && + BoxPlaneClip( -localDir.x, localStart.x - extents[0], scale1, scale2 ) && + BoxPlaneClip( localDir.y, -localStart.y - extents[1], scale1, scale2 ) && + BoxPlaneClip( -localDir.y, localStart.y - extents[1], scale1, scale2 ) && + BoxPlaneClip( localDir.z, -localStart.z - extents[2], scale1, scale2 ) && + BoxPlaneClip( -localDir.z, localStart.z - extents[2], scale1, scale2 ); +} + +/* +============ +idBox::FromPoints + + Tight box for a collection of points. +============ +*/ +void idBox::FromPoints( const idVec3 *points, const int numPoints ) { + int i; + float invNumPoints, sumXX, sumXY, sumXZ, sumYY, sumYZ, sumZZ; + idVec3 dir; + idBounds bounds; + idMatX eigenVectors; + idVecX eigenValues; + + // compute mean of points + center = points[0]; + for ( i = 1; i < numPoints; i++ ) { + center += points[i]; + } + invNumPoints = 1.0f / numPoints; + center *= invNumPoints; + + // compute covariances of points + sumXX = 0.0f; sumXY = 0.0f; sumXZ = 0.0f; + sumYY = 0.0f; sumYZ = 0.0f; sumZZ = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = points[i] - center; + sumXX += dir.x * dir.x; + sumXY += dir.x * dir.y; + sumXZ += dir.x * dir.z; + sumYY += dir.y * dir.y; + sumYZ += dir.y * dir.z; + sumZZ += dir.z * dir.z; + } + sumXX *= invNumPoints; + sumXY *= invNumPoints; + sumXZ *= invNumPoints; + sumYY *= invNumPoints; + sumYZ *= invNumPoints; + sumZZ *= invNumPoints; + + // compute eigenvectors for covariance matrix + eigenValues.SetData( 3, VECX_ALLOCA( 3 ) ); + eigenVectors.SetData( 3, 3, MATX_ALLOCA( 3 * 3 ) ); + + eigenVectors[0][0] = sumXX; + eigenVectors[0][1] = sumXY; + eigenVectors[0][2] = sumXZ; + eigenVectors[1][0] = sumXY; + eigenVectors[1][1] = sumYY; + eigenVectors[1][2] = sumYZ; + eigenVectors[2][0] = sumXZ; + eigenVectors[2][1] = sumYZ; + eigenVectors[2][2] = sumZZ; + eigenVectors.Eigen_SolveSymmetric( eigenValues ); + eigenVectors.Eigen_SortIncreasing( eigenValues ); + + axis[0][0] = eigenVectors[0][0]; + axis[0][1] = eigenVectors[0][1]; + axis[0][2] = eigenVectors[0][2]; + axis[1][0] = eigenVectors[1][0]; + axis[1][1] = eigenVectors[1][1]; + axis[1][2] = eigenVectors[1][2]; + axis[2][0] = eigenVectors[2][0]; + axis[2][1] = eigenVectors[2][1]; + axis[2][2] = eigenVectors[2][2]; + + extents[0] = eigenValues[0]; + extents[1] = eigenValues[0]; + extents[2] = eigenValues[0]; + + // refine by calculating the bounds of the points projected onto the axis and adjusting the center and extents + bounds.Clear(); + for ( i = 0; i < numPoints; i++ ) { + bounds.AddPoint( idVec3( points[i] * axis[0], points[i] * axis[1], points[i] * axis[2] ) ); + } + center = ( bounds[0] + bounds[1] ) * 0.5f; + extents = bounds[1] - center; + center *= axis; +} + +/* +============ +idBox::FromPointTranslation + + Most tight box for the translational movement of the given point. +============ +*/ +void idBox::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + // FIXME: implement +} + +/* +============ +idBox::FromBoxTranslation + + Most tight box for the translational movement of the given box. +============ +*/ +void idBox::FromBoxTranslation( const idBox &box, const idVec3 &translation ) { + // FIXME: implement +} + +/* +============ +idBox::FromPointRotation + + Most tight bounds for the rotational movement of the given point. +============ +*/ +void idBox::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + // FIXME: implement +} + +/* +============ +idBox::FromBoxRotation + + Most tight box for the rotational movement of the given box. +============ +*/ +void idBox::FromBoxRotation( const idBox &box, const idRotation &rotation ) { + // FIXME: implement +} + +/* +============ +idBox::ToPoints +============ +*/ +void idBox::ToPoints( idVec3 points[8] ) const { + idMat3 ax; + idVec3 temp[4]; + + ax[0] = extents[0] * axis[0]; + ax[1] = extents[1] * axis[1]; + ax[2] = extents[2] * axis[2]; + temp[0] = center - ax[0]; + temp[1] = center + ax[0]; + temp[2] = ax[1] - ax[2]; + temp[3] = ax[1] + ax[2]; + points[0] = temp[0] - temp[3]; + points[1] = temp[1] - temp[3]; + points[2] = temp[1] + temp[2]; + points[3] = temp[0] + temp[2]; + points[4] = temp[0] - temp[2]; + points[5] = temp[1] - temp[2]; + points[6] = temp[1] + temp[3]; + points[7] = temp[0] + temp[3]; +} + +/* +============ +idBox::GetProjectionSilhouetteVerts +============ +*/ +int idBox::GetProjectionSilhouetteVerts( const idVec3 &projectionOrigin, idVec3 silVerts[6] ) const { + float f; + int i, planeBits, *index; + idVec3 points[8], dir1, dir2; + + ToPoints( points ); + + dir1 = points[0] - projectionOrigin; + dir2 = points[6] - projectionOrigin; + f = dir1 * axis[0]; + planeBits = FLOATSIGNBITNOTSET( f ); + f = dir2 * axis[0]; + planeBits |= FLOATSIGNBITSET( f ) << 1; + f = dir1 * axis[1]; + planeBits |= FLOATSIGNBITNOTSET( f ) << 2; + f = dir2 * axis[1]; + planeBits |= FLOATSIGNBITSET( f ) << 3; + f = dir1 * axis[2]; + planeBits |= FLOATSIGNBITNOTSET( f ) << 4; + f = dir2 * axis[2]; + planeBits |= FLOATSIGNBITSET( f ) << 5; + + index = boxPlaneBitsSilVerts[planeBits]; + for ( i = 0; i < index[0]; i++ ) { + silVerts[i] = points[index[i+1]]; + } + + return index[0]; +} + +/* +============ +idBox::GetParallelProjectionSilhouetteVerts +============ +*/ +int idBox::GetParallelProjectionSilhouetteVerts( const idVec3 &projectionDir, idVec3 silVerts[6] ) const { + float f; + int i, planeBits, *index; + idVec3 points[8]; + + ToPoints( points ); + + planeBits = 0; + f = projectionDir * axis[0]; + if ( FLOATNOTZERO( f ) ) { + planeBits = 1 << FLOATSIGNBITSET( f ); + } + f = projectionDir * axis[1]; + if ( FLOATNOTZERO( f ) ) { + planeBits |= 4 << FLOATSIGNBITSET( f ); + } + f = projectionDir * axis[2]; + if ( FLOATNOTZERO( f ) ) { + planeBits |= 16 << FLOATSIGNBITSET( f ); + } + + index = boxPlaneBitsSilVerts[planeBits]; + for ( i = 0; i < index[0]; i++ ) { + silVerts[i] = points[index[i+1]]; + } + + return index[0]; +} diff --git a/src/idlib/bv/Box.h b/src/idlib/bv/Box.h new file mode 100644 index 0000000..c5b8a26 --- /dev/null +++ b/src/idlib/bv/Box.h @@ -0,0 +1,293 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BV_BOX_H__ +#define __BV_BOX_H__ + +/* +=============================================================================== + + Oriented Bounding Box + +=============================================================================== +*/ + +class idBox { +public: + idBox( void ); + explicit idBox( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis ); + explicit idBox( const idVec3 &point ); + explicit idBox( const idBounds &bounds ); + explicit idBox( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ); + + idBox operator+( const idVec3 &t ) const; // returns translated box + idBox & operator+=( const idVec3 &t ); // translate the box + idBox operator*( const idMat3 &r ) const; // returns rotated box + idBox & operator*=( const idMat3 &r ); // rotate the box + idBox operator+( const idBox &a ) const; + idBox & operator+=( const idBox &a ); + idBox operator-( const idBox &a ) const; + idBox & operator-=( const idBox &a ); + + bool Compare( const idBox &a ) const; // exact compare, no epsilon + bool Compare( const idBox &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idBox &a ) const; // exact compare, no epsilon + bool operator!=( const idBox &a ) const; // exact compare, no epsilon + + void Clear( void ); // inside out box + void Zero( void ); // single point at origin + + const idVec3 & GetCenter( void ) const; // returns center of the box + const idVec3 & GetExtents( void ) const; // returns extents of the box + const idMat3 & GetAxis( void ) const; // returns the axis of the box + float GetVolume( void ) const; // returns the volume of the box + bool IsCleared( void ) const; // returns true if box are inside out + + bool AddPoint( const idVec3 &v ); // add the point, returns true if the box expanded + bool AddBox( const idBox &a ); // add the box, returns true if the box expanded + idBox Expand( const float d ) const; // return box expanded in all directions with the given value + idBox & ExpandSelf( const float d ); // expand box in all directions with the given value + idBox Translate( const idVec3 &translation ) const; // return translated box + idBox & TranslateSelf( const idVec3 &translation ); // translate this box + idBox Rotate( const idMat3 &rotation ) const; // return rotated box + idBox & RotateSelf( const idMat3 &rotation ); // rotate this box + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsBox( const idBox &a ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection points are (start + dir * scale1) and (start + dir * scale2) + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // tight box for a collection of points + void FromPoints( const idVec3 *points, const int numPoints ); + // most tight box for a translation + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromBoxTranslation( const idBox &box, const idVec3 &translation ); + // most tight box for a rotation + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromBoxRotation( const idBox &box, const idRotation &rotation ); + + void ToPoints( idVec3 points[8] ) const; + idSphere ToSphere( void ) const; + + // calculates the projection of this box onto the given axis + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idMat3 &ax, idBounds &bounds ) const; + + // calculates the silhouette of the box + int GetProjectionSilhouetteVerts( const idVec3 &projectionOrigin, idVec3 silVerts[6] ) const; + int GetParallelProjectionSilhouetteVerts( const idVec3 &projectionDir, idVec3 silVerts[6] ) const; + +private: + idVec3 center; + idVec3 extents; + idMat3 axis; +}; + +extern idBox box_zero; + +ID_INLINE idBox::idBox( void ) { +} + +ID_INLINE idBox::idBox( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis ) { + this->center = center; + this->extents = extents; + this->axis = axis; +} + +ID_INLINE idBox::idBox( const idVec3 &point ) { + this->center = point; + this->extents.Zero(); + this->axis.Identity(); +} + +ID_INLINE idBox::idBox( const idBounds &bounds ) { + this->center = ( bounds[0] + bounds[1] ) * 0.5f; + this->extents = bounds[1] - this->center; + this->axis.Identity(); +} + +ID_INLINE idBox::idBox( const idBounds &bounds, const idVec3 &origin, const idMat3 &axis ) { + this->center = ( bounds[0] + bounds[1] ) * 0.5f; + this->extents = bounds[1] - this->center; + this->center = origin + this->center * axis; + this->axis = axis; +} + +ID_INLINE idBox idBox::operator+( const idVec3 &t ) const { + return idBox( center + t, extents, axis ); +} + +ID_INLINE idBox &idBox::operator+=( const idVec3 &t ) { + center += t; + return *this; +} + +ID_INLINE idBox idBox::operator*( const idMat3 &r ) const { + return idBox( center * r, extents, axis * r ); +} + +ID_INLINE idBox &idBox::operator*=( const idMat3 &r ) { + center *= r; + axis *= r; + return *this; +} + +ID_INLINE idBox idBox::operator+( const idBox &a ) const { + idBox newBox; + newBox = *this; + newBox.AddBox( a ); + return newBox; +} + +ID_INLINE idBox &idBox::operator+=( const idBox &a ) { + idBox::AddBox( a ); + return *this; +} + +ID_INLINE idBox idBox::operator-( const idBox &a ) const { + return idBox( center, extents - a.extents, axis ); +} + +ID_INLINE idBox &idBox::operator-=( const idBox &a ) { + extents -= a.extents; + return *this; +} + +ID_INLINE bool idBox::Compare( const idBox &a ) const { + return ( center.Compare( a.center ) && extents.Compare( a.extents ) && axis.Compare( a.axis ) ); +} + +ID_INLINE bool idBox::Compare( const idBox &a, const float epsilon ) const { + return ( center.Compare( a.center, epsilon ) && extents.Compare( a.extents, epsilon ) && axis.Compare( a.axis, epsilon ) ); +} + +ID_INLINE bool idBox::operator==( const idBox &a ) const { + return Compare( a ); +} + +ID_INLINE bool idBox::operator!=( const idBox &a ) const { + return !Compare( a ); +} + +ID_INLINE void idBox::Clear( void ) { + center.Zero(); + extents[0] = extents[1] = extents[2] = -idMath::INFINITY; + axis.Identity(); +} + +ID_INLINE void idBox::Zero( void ) { + center.Zero(); + extents.Zero(); + axis.Identity(); +} + +ID_INLINE const idVec3 &idBox::GetCenter( void ) const { + return center; +} + +ID_INLINE const idVec3 &idBox::GetExtents( void ) const { + return extents; +} + +ID_INLINE const idMat3 &idBox::GetAxis( void ) const { + return axis; +} + +ID_INLINE float idBox::GetVolume( void ) const { + return ( extents * 2.0f ).LengthSqr(); +} + +ID_INLINE bool idBox::IsCleared( void ) const { + return extents[0] < 0.0f; +} + +ID_INLINE idBox idBox::Expand( const float d ) const { + return idBox( center, extents + idVec3( d, d, d ), axis ); +} + +ID_INLINE idBox &idBox::ExpandSelf( const float d ) { + extents[0] += d; + extents[1] += d; + extents[2] += d; + return *this; +} + +ID_INLINE idBox idBox::Translate( const idVec3 &translation ) const { + return idBox( center + translation, extents, axis ); +} + +ID_INLINE idBox &idBox::TranslateSelf( const idVec3 &translation ) { + center += translation; + return *this; +} + +ID_INLINE idBox idBox::Rotate( const idMat3 &rotation ) const { + return idBox( center * rotation, extents, axis * rotation ); +} + +ID_INLINE idBox &idBox::RotateSelf( const idMat3 &rotation ) { + center *= rotation; + axis *= rotation; + return *this; +} + +ID_INLINE bool idBox::ContainsPoint( const idVec3 &p ) const { + idVec3 lp = p - center; + if ( idMath::Fabs( lp * axis[0] ) > extents[0] || + idMath::Fabs( lp * axis[1] ) > extents[1] || + idMath::Fabs( lp * axis[2] ) > extents[2] ) { + return false; + } + return true; +} + +ID_INLINE idSphere idBox::ToSphere( void ) const { + return idSphere( center, extents.Length() ); +} + +ID_INLINE void idBox::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d1 = dir * center; + float d2 = idMath::Fabs( extents[0] * ( dir * axis[0] ) ) + + idMath::Fabs( extents[1] * ( dir * axis[1] ) ) + + idMath::Fabs( extents[2] * ( dir * axis[2] ) ); + min = d1 - d2; + max = d1 + d2; +} + +ID_INLINE void idBox::AxisProjection( const idMat3 &ax, idBounds &bounds ) const { + for ( int i = 0; i < 3; i++ ) { + float d1 = ax[i] * center; + float d2 = idMath::Fabs( extents[0] * ( ax[i] * axis[0] ) ) + + idMath::Fabs( extents[1] * ( ax[i] * axis[1] ) ) + + idMath::Fabs( extents[2] * ( ax[i] * axis[2] ) ); + bounds[0][i] = d1 - d2; + bounds[1][i] = d1 + d2; + } +} + +#endif /* !__BV_BOX_H__ */ diff --git a/src/idlib/bv/Frustum.cpp b/src/idlib/bv/Frustum.cpp new file mode 100644 index 0000000..88d34ef --- /dev/null +++ b/src/idlib/bv/Frustum.cpp @@ -0,0 +1,2849 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +//#define FRUSTUM_DEBUG + +/* + bit 0 = min x + bit 1 = max x + bit 2 = min y + bit 3 = max y + bit 4 = min z + bit 5 = max z +*/ +static int boxVertPlanes[8] = { + ( (1<<0) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<2) | (1<<4) ), + ( (1<<1) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<3) | (1<<4) ), + ( (1<<0) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<2) | (1<<5) ), + ( (1<<1) | (1<<3) | (1<<5) ), + ( (1<<0) | (1<<3) | (1<<5) ), +}; + +/* +============ +BoxToPoints +============ +*/ +void BoxToPoints( const idVec3 ¢er, const idVec3 &extents, const idMat3 &axis, idVec3 points[8] ) { + idMat3 ax; + idVec3 temp[4]; + + ax[0] = extents[0] * axis[0]; + ax[1] = extents[1] * axis[1]; + ax[2] = extents[2] * axis[2]; + temp[0] = center - ax[0]; + temp[1] = center + ax[0]; + temp[2] = ax[1] - ax[2]; + temp[3] = ax[1] + ax[2]; + points[0] = temp[0] - temp[3]; + points[1] = temp[1] - temp[3]; + points[2] = temp[1] + temp[2]; + points[3] = temp[0] + temp[2]; + points[4] = temp[0] - temp[2]; + points[5] = temp[1] - temp[2]; + points[6] = temp[1] + temp[3]; + points[7] = temp[0] + temp[3]; +} + +/* +================ +idFrustum::PlaneDistance +================ +*/ +float idFrustum::PlaneDistance( const idPlane &plane ) const { + float min, max; + + AxisProjection( plane.Normal(), min, max ); + if ( min + plane[3] > 0.0f ) { + return min + plane[3]; + } + if ( max + plane[3] < 0.0f ) { + return max + plane[3]; + } + return 0.0f; +} + +/* +================ +idFrustum::PlaneSide +================ +*/ +int idFrustum::PlaneSide( const idPlane &plane, const float epsilon ) const { + float min, max; + + AxisProjection( plane.Normal(), min, max ); + if ( min + plane[3] > epsilon ) { + return PLANESIDE_FRONT; + } + if ( max + plane[3] < epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idFrustum::CullPoint +============ +*/ +bool idFrustum::CullPoint( const idVec3 &point ) const { + idVec3 p; + float scale; + + // transform point to frustum space + p = ( point - origin ) * axis.Transpose(); + // test whether or not the point is within the frustum + if ( p.x < dNear || p.y > dFar ) { + return true; + } + scale = p.x * invFar; + if ( idMath::Fabs( p.y ) > dLeft * scale ) { + return true; + } + if ( idMath::Fabs( p.z ) > dUp * scale ) { + return true; + } + return false; +} + +/* +============ +idFrustum::CullLocalBox + + Tests if any of the planes of the frustum can be used as a separating plane. + + 3 muls best case + 25 muls worst case +============ +*/ +bool idFrustum::CullLocalBox( const idVec3 &localOrigin, const idVec3 &extents, const idMat3 &localAxis ) const { + float d1, d2; + idVec3 testOrigin; + idMat3 testAxis; + + // near plane + d1 = dNear - localOrigin.x; + d2 = idMath::Fabs( extents[0] * localAxis[0][0] ) + + idMath::Fabs( extents[1] * localAxis[1][0] ) + + idMath::Fabs( extents[2] * localAxis[2][0] ); + if ( d1 - d2 > 0.0f ) { + return true; + } + + // far plane + d1 = localOrigin.x - dFar; + if ( d1 - d2 > 0.0f ) { + return true; + } + + testOrigin = localOrigin; + testAxis = localAxis; + + if ( testOrigin.y < 0.0f ) { + testOrigin.y = -testOrigin.y; + testAxis[0][1] = -testAxis[0][1]; + testAxis[1][1] = -testAxis[1][1]; + testAxis[2][1] = -testAxis[2][1]; + } + + // test left/right planes + d1 = dFar * testOrigin.y - dLeft * testOrigin.x; + d2 = idMath::Fabs( extents[0] * ( dFar * testAxis[0][1] - dLeft * testAxis[0][0] ) ) + + idMath::Fabs( extents[1] * ( dFar * testAxis[1][1] - dLeft * testAxis[1][0] ) ) + + idMath::Fabs( extents[2] * ( dFar * testAxis[2][1] - dLeft * testAxis[2][0] ) ); + if ( d1 - d2 > 0.0f ) { + return true; + } + + if ( testOrigin.z < 0.0f ) { + testOrigin.z = -testOrigin.z; + testAxis[0][2] = -testAxis[0][2]; + testAxis[1][2] = -testAxis[1][2]; + testAxis[2][2] = -testAxis[2][2]; + } + + // test up/down planes + d1 = dFar * testOrigin.z - dUp * testOrigin.x; + d2 = idMath::Fabs( extents[0] * ( dFar * testAxis[0][2] - dUp * testAxis[0][0] ) ) + + idMath::Fabs( extents[1] * ( dFar * testAxis[1][2] - dUp * testAxis[1][0] ) ) + + idMath::Fabs( extents[2] * ( dFar * testAxis[2][2] - dUp * testAxis[2][0] ) ); + if ( d1 - d2 > 0.0f ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::CullBounds + + Tests if any of the planes of the frustum can be used as a separating plane. + + 24 muls best case + 37 muls worst case +============ +*/ +bool idFrustum::CullBounds( const idBounds &bounds ) const { + idVec3 localOrigin, center, extents; + idMat3 localAxis; + + center = ( bounds[0] + bounds[1] ) * 0.5f; + extents = bounds[1] - center; + + // transform the bounds into the space of this frustum + localOrigin = ( center - origin ) * axis.Transpose(); + localAxis = axis.Transpose(); + + return CullLocalBox( localOrigin, extents, localAxis ); +} + +/* +============ +idFrustum::CullBounds + + Tests if any of the planes of the frustum can be used as a separating plane. + + 39 muls best case + 61 muls worst case +============ +*/ +bool idFrustum::CullBox( const idBox &box ) const { + idVec3 localOrigin; + idMat3 localAxis; + + // transform the box into the space of this frustum + localOrigin = ( box.GetCenter() - origin ) * axis.Transpose(); + localAxis = box.GetAxis() * axis.Transpose(); + + return CullLocalBox( localOrigin, box.GetExtents(), localAxis ); +} + +/* +============ +idFrustum::CullSphere + + Tests if any of the planes of the frustum can be used as a separating plane. + + 9 muls best case + 21 muls worst case +============ +*/ +bool idFrustum::CullSphere( const idSphere &sphere ) const { + float d, r, rs, sFar; + idVec3 center; + + center = ( sphere.GetOrigin() - origin ) * axis.Transpose(); + r = sphere.GetRadius(); + + // test near plane + if ( dNear - center.x > r ) { + return true; + } + + // test far plane + if ( center.x - dFar > r ) { + return true; + } + + rs = r * r; + sFar = dFar * dFar; + + // test left/right planes + d = dFar * idMath::Fabs( center.y ) - dLeft * center.x; + if ( ( d * d ) > rs * ( sFar + dLeft * dLeft ) ) { + return true; + } + + // test up/down planes + d = dFar * idMath::Fabs( center.z ) - dUp * center.x; + if ( ( d * d ) > rs * ( sFar + dUp * dUp ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::CullLocalFrustum + + Tests if any of the planes of this frustum can be used as a separating plane. + + 0 muls best case + 30 muls worst case +============ +*/ +bool idFrustum::CullLocalFrustum( const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const { + int index; + float dx, dy, dz, leftScale, upScale; + + // test near plane + dy = -localFrustum.axis[1].x; + dz = -localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x < dNear ) { + return true; + } + + // test far plane + dy = localFrustum.axis[1].x; + dz = localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x > dFar ) { + return true; + } + + leftScale = dLeft * invFar; + + // test left plane + dy = dFar * localFrustum.axis[1].y - dLeft * localFrustum.axis[1].x; + dz = dFar * localFrustum.axis[2].y - dLeft * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = dFar * cornerVecs[index].y - dLeft * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y > indexPoints[index].x * leftScale ) { + return true; + } + + // test right plane + dy = -dFar * localFrustum.axis[1].y - dLeft * localFrustum.axis[1].x; + dz = -dFar * localFrustum.axis[2].y - dLeft * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -dFar * cornerVecs[index].y - dLeft * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y < -indexPoints[index].x * leftScale ) { + return true; + } + + upScale = dUp * invFar; + + // test up plane + dy = dFar * localFrustum.axis[1].z - dUp * localFrustum.axis[1].x; + dz = dFar * localFrustum.axis[2].z - dUp * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = dFar * cornerVecs[index].z - dUp * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z > indexPoints[index].x * upScale ) { + return true; + } + + // test down plane + dy = -dFar * localFrustum.axis[1].z - dUp * localFrustum.axis[1].x; + dz = -dFar * localFrustum.axis[2].z - dUp * localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -dFar * cornerVecs[index].z - dUp * cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z < -indexPoints[index].x * upScale ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::CullFrustum + + Tests if any of the planes of this frustum can be used as a separating plane. + + 58 muls best case + 88 muls worst case +============ +*/ +bool idFrustum::CullFrustum( const idFrustum &frustum ) const { + idFrustum localFrustum; + idVec3 indexPoints[8], cornerVecs[4]; + + // transform the given frustum into the space of this frustum + localFrustum = frustum; + localFrustum.origin = ( frustum.origin - origin ) * axis.Transpose(); + localFrustum.axis = frustum.axis * axis.Transpose(); + + localFrustum.ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + + return CullLocalFrustum( localFrustum, indexPoints, cornerVecs ); +} + +/* +============ +idFrustum::CullLocalWinding +============ +*/ +bool idFrustum::CullLocalWinding( const idVec3 *points, const int numPoints, int *pointCull ) const { + int i, pCull, culled; + float leftScale, upScale; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + + culled = -1; + for ( i = 0; i < numPoints; i++ ) { + const idVec3 &p = points[i]; + pCull = 0; + if ( p.x < dNear ) { + pCull = 1; + } + else if ( p.x > dFar ) { + pCull = 2; + } + if ( idMath::Fabs( p.y ) > p.x * leftScale ) { + pCull |= 4 << FLOATSIGNBITSET( p.y ); + } + if ( idMath::Fabs( p.z ) > p.x * upScale ) { + pCull |= 16 << FLOATSIGNBITSET( p.z ); + } + culled &= pCull; + pointCull[i] = pCull; + } + + return ( culled != 0 ); +} + +/* +============ +idFrustum::CullWinding +============ +*/ +bool idFrustum::CullWinding( const idWinding &winding ) const { + int i, *pointCull; + idVec3 *localPoints; + idMat3 transpose; + + localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) ); + pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) ); + + transpose = axis.Transpose(); + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose; + } + + return CullLocalWinding( localPoints, winding.GetNumPoints(), pointCull ); +} + +/* +============ +idFrustum::BoundsCullLocalFrustum + + Tests if any of the bounding box planes can be used as a separating plane. +============ +*/ +bool idFrustum::BoundsCullLocalFrustum( const idBounds &bounds, const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const { + int index; + float dx, dy, dz; + + dy = -localFrustum.axis[1].x; + dz = -localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x < bounds[0].x ) { + return true; + } + + dy = localFrustum.axis[1].x; + dz = localFrustum.axis[2].x; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].x; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].x > bounds[1].x ) { + return true; + } + + dy = -localFrustum.axis[1].y; + dz = -localFrustum.axis[2].y; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].y; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y < bounds[0].y ) { + return true; + } + + dy = localFrustum.axis[1].y; + dz = localFrustum.axis[2].y; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].y; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].y > bounds[1].y ) { + return true; + } + + dy = -localFrustum.axis[1].z; + dz = -localFrustum.axis[2].z; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = -cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z < bounds[0].z ) { + return true; + } + + dy = localFrustum.axis[1].z; + dz = localFrustum.axis[2].z; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + + if ( indexPoints[index].z > bounds[1].z ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::LocalLineIntersection + + 7 divs + 30 muls +============ +*/ +bool idFrustum::LocalLineIntersection( const idVec3 &start, const idVec3 &end ) const { + idVec3 dir; + float d1, d2, fstart, fend, lstart, lend, f, x; + float leftScale, upScale; + int startInside = 1; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + dir = end - start; + + // test near plane + if ( dNear > 0.0f ) { + d1 = dNear - start.x; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = dNear - end.x; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dNear * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dNear * upScale ) { + return true; + } + } + } + } + } + + // test far plane + d1 = start.x - dFar; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = end.x - dFar; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dFar * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dFar * upScale ) { + return true; + } + } + } + } + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + // test left plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + return true; + } + } + } + } + + // test right plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = -fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + return true; + } + } + } + } + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + // test up plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + return true; + } + } + } + } + + // test down plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + if ( FLOATNOTZERO( d1 ) ) { + d2 = -fend - lend; + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + return true; + } + } + } + } + + return ( startInside != 0 ); +} + +/* +============ +idFrustum::LocalRayIntersection + + Returns true if the ray starts inside the frustum. + If there was an intersection scale1 <= scale2 +============ +*/ +bool idFrustum::LocalRayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + idVec3 end; + float d1, d2, fstart, fend, lstart, lend, f, x; + float leftScale, upScale; + int startInside = 1; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + end = start + dir; + + scale1 = idMath::INFINITY; + scale2 = -idMath::INFINITY; + + // test near plane + if ( dNear > 0.0f ) { + d1 = dNear - start.x; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = dNear - end.x; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dNear * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dNear * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + } + + // test far plane + d1 = start.x - dFar; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = end.x - dFar; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + if ( idMath::Fabs( start.y + f * dir.y ) <= dFar * leftScale ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= dFar * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + // test left plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + // test right plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = -fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + // test up plane + d1 = fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + // test down plane + d1 = -fstart - lstart; + startInside &= FLOATSIGNBITSET( d1 ); + d2 = -fend - lend; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + x = start.x + f * dir.x; + if ( x >= dNear && x <= dFar ) { + if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + return ( startInside != 0 ); +} + +/* +============ +idFrustum::ContainsPoint +============ +*/ +bool idFrustum::ContainsPoint( const idVec3 &point ) const { + return !CullPoint( point ); +} + +/* +============ +idFrustum::LocalFrustumIntersectsFrustum +============ +*/ +bool idFrustum::LocalFrustumIntersectsFrustum( const idVec3 points[8], const bool testFirstSide ) const { + int i; + + // test if any edges of the other frustum intersect this frustum + for ( i = 0; i < 4; i++ ) { + if ( LocalLineIntersection( points[i], points[4+i] ) ) { + return true; + } + } + if ( testFirstSide ) { + for ( i = 0; i < 4; i++ ) { + if ( LocalLineIntersection( points[i], points[(i+1)&3] ) ) { + return true; + } + } + } + for ( i = 0; i < 4; i++ ) { + if ( LocalLineIntersection( points[4+i], points[4+((i+1)&3)] ) ) { + return true; + } + } + + return false; +} + +/* +============ +idFrustum::LocalFrustumIntersectsBounds +============ +*/ +bool idFrustum::LocalFrustumIntersectsBounds( const idVec3 points[8], const idBounds &bounds ) const { + int i; + + // test if any edges of the other frustum intersect this frustum + for ( i = 0; i < 4; i++ ) { + if ( bounds.LineIntersection( points[i], points[4+i] ) ) { + return true; + } + } + if ( dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + if ( bounds.LineIntersection( points[i], points[(i+1)&3] ) ) { + return true; + } + } + } + for ( i = 0; i < 4; i++ ) { + if ( bounds.LineIntersection( points[4+i], points[4+((i+1)&3)] ) ) { + return true; + } + } + + return false; +} + +/* +============ +idFrustum::IntersectsBounds +============ +*/ +bool idFrustum::IntersectsBounds( const idBounds &bounds ) const { + idVec3 localOrigin, center, extents; + idMat3 localAxis; + + center = ( bounds[0] + bounds[1] ) * 0.5f; + extents = bounds[1] - center; + + localOrigin = ( center - origin ) * axis.Transpose(); + localAxis = axis.Transpose(); + + if ( CullLocalBox( localOrigin, extents, localAxis ) ) { + return false; + } + + idVec3 indexPoints[8], cornerVecs[4]; + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + + if ( BoundsCullLocalFrustum( bounds, *this, indexPoints, cornerVecs ) ) { + return false; + } + + idSwap( indexPoints[2], indexPoints[3] ); + idSwap( indexPoints[6], indexPoints[7] ); + + if ( LocalFrustumIntersectsBounds( indexPoints, bounds ) ) { + return true; + } + + BoxToPoints( localOrigin, extents, localAxis, indexPoints ); + + if ( LocalFrustumIntersectsFrustum( indexPoints, true ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::IntersectsBox +============ +*/ +bool idFrustum::IntersectsBox( const idBox &box ) const { + idVec3 localOrigin; + idMat3 localAxis; + + localOrigin = ( box.GetCenter() - origin ) * axis.Transpose(); + localAxis = box.GetAxis() * axis.Transpose(); + + if ( CullLocalBox( localOrigin, box.GetExtents(), localAxis ) ) { + return false; + } + + idVec3 indexPoints[8], cornerVecs[4]; + idFrustum localFrustum; + + localFrustum = *this; + localFrustum.origin = ( origin - box.GetCenter() ) * box.GetAxis().Transpose(); + localFrustum.axis = axis * box.GetAxis().Transpose(); + localFrustum.ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + + if ( BoundsCullLocalFrustum( idBounds( -box.GetExtents(), box.GetExtents() ), localFrustum, indexPoints, cornerVecs ) ) { + return false; + } + + idSwap( indexPoints[2], indexPoints[3] ); + idSwap( indexPoints[6], indexPoints[7] ); + + if ( LocalFrustumIntersectsBounds( indexPoints, idBounds( -box.GetExtents(), box.GetExtents() ) ) ) { + return true; + } + + BoxToPoints( localOrigin, box.GetExtents(), localAxis, indexPoints ); + + if ( LocalFrustumIntersectsFrustum( indexPoints, true ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::IntersectsSphere + + FIXME: test this +============ +*/ +#define VORONOI_INDEX( x, y, z ) ( x + y * 3 + z * 9 ) + +bool idFrustum::IntersectsSphere( const idSphere &sphere ) const { + int index, x, y, z; + float scale, r, d; + idVec3 p, dir, points[8]; + + if ( CullSphere( sphere ) ) { + return false; + } + + x = y = z = 0; + dir.Zero(); + + p = ( sphere.GetOrigin() - origin ) * axis.Transpose(); + + if ( p.x <= dNear ) { + scale = dNear * invFar; + dir.y = idMath::Fabs( p.y ) - dLeft * scale; + dir.z = idMath::Fabs( p.z ) - dUp * scale; + } + else if ( p.x >= dFar ) { + dir.y = idMath::Fabs( p.y ) - dLeft; + dir.z = idMath::Fabs( p.z ) - dUp; + } + else { + scale = p.x * invFar; + dir.y = idMath::Fabs( p.y ) - dLeft * scale; + dir.z = idMath::Fabs( p.z ) - dUp * scale; + } + if ( dir.y > 0.0f ) { + y = ( 1 + FLOATSIGNBITNOTSET( p.y ) ); + } + if ( dir.z > 0.0f ) { + z = ( 1 + FLOATSIGNBITNOTSET( p.z ) ); + } + if ( p.x < dNear ) { + scale = dLeft * dNear * invFar; + if ( p.x < dNear + ( scale - p.y ) * scale * invFar ) { + scale = dUp * dNear * invFar; + if ( p.x < dNear + ( scale - p.z ) * scale * invFar ) { + x = 1; + } + } + } + else { + if ( p.x > dFar ) { + x = 2; + } + else if ( p.x > dFar + ( dLeft - p.y ) * dLeft * invFar ) { + x = 2; + } + else if ( p.x > dFar + ( dUp - p.z ) * dUp * invFar ) { + x = 2; + } + } + + r = sphere.GetRadius(); + index = VORONOI_INDEX( x, y, z ); + switch( index ) { + case VORONOI_INDEX( 0, 0, 0 ): return true; + case VORONOI_INDEX( 1, 0, 0 ): return ( dNear - p.x < r ); + case VORONOI_INDEX( 2, 0, 0 ): return ( p.x - dFar < r ); + case VORONOI_INDEX( 0, 1, 0 ): d = dFar * p.y - dLeft * p.x; return ( d * d < r * r * ( dFar * dFar + dLeft * dLeft ) ); + case VORONOI_INDEX( 0, 2, 0 ): d = -dFar * p.z - dLeft * p.x; return ( d * d < r * r * ( dFar * dFar + dLeft * dLeft ) ); + case VORONOI_INDEX( 0, 0, 1 ): d = dFar * p.z - dUp * p.x; return ( d * d < r * r * ( dFar * dFar + dUp * dUp ) ); + case VORONOI_INDEX( 0, 0, 2 ): d = -dFar * p.z - dUp * p.x; return ( d * d < r * r * ( dFar * dFar + dUp * dUp ) ); + default: { + ToIndexPoints( points ); + switch( index ) { + case VORONOI_INDEX( 1, 1, 1 ): return sphere.ContainsPoint( points[0] ); + case VORONOI_INDEX( 2, 1, 1 ): return sphere.ContainsPoint( points[4] ); + case VORONOI_INDEX( 1, 2, 1 ): return sphere.ContainsPoint( points[1] ); + case VORONOI_INDEX( 2, 2, 1 ): return sphere.ContainsPoint( points[5] ); + case VORONOI_INDEX( 1, 1, 2 ): return sphere.ContainsPoint( points[2] ); + case VORONOI_INDEX( 2, 1, 2 ): return sphere.ContainsPoint( points[6] ); + case VORONOI_INDEX( 1, 2, 2 ): return sphere.ContainsPoint( points[3] ); + case VORONOI_INDEX( 2, 2, 2 ): return sphere.ContainsPoint( points[7] ); + case VORONOI_INDEX( 1, 1, 0 ): return sphere.LineIntersection( points[0], points[2] ); + case VORONOI_INDEX( 2, 1, 0 ): return sphere.LineIntersection( points[4], points[6] ); + case VORONOI_INDEX( 1, 2, 0 ): return sphere.LineIntersection( points[1], points[3] ); + case VORONOI_INDEX( 2, 2, 0 ): return sphere.LineIntersection( points[5], points[7] ); + case VORONOI_INDEX( 1, 0, 1 ): return sphere.LineIntersection( points[0], points[1] ); + case VORONOI_INDEX( 2, 0, 1 ): return sphere.LineIntersection( points[4], points[5] ); + case VORONOI_INDEX( 0, 1, 1 ): return sphere.LineIntersection( points[0], points[4] ); + case VORONOI_INDEX( 0, 2, 1 ): return sphere.LineIntersection( points[1], points[5] ); + case VORONOI_INDEX( 1, 0, 2 ): return sphere.LineIntersection( points[2], points[3] ); + case VORONOI_INDEX( 2, 0, 2 ): return sphere.LineIntersection( points[6], points[7] ); + case VORONOI_INDEX( 0, 1, 2 ): return sphere.LineIntersection( points[2], points[6] ); + case VORONOI_INDEX( 0, 2, 2 ): return sphere.LineIntersection( points[3], points[7] ); + } + break; + } + } + return false; +} + +/* +============ +idFrustum::IntersectsFrustum +============ +*/ +bool idFrustum::IntersectsFrustum( const idFrustum &frustum ) const { + idVec3 indexPoints2[8], cornerVecs2[4]; + idFrustum localFrustum2; + + localFrustum2 = frustum; + localFrustum2.origin = ( frustum.origin - origin ) * axis.Transpose(); + localFrustum2.axis = frustum.axis * axis.Transpose(); + localFrustum2.ToIndexPointsAndCornerVecs( indexPoints2, cornerVecs2 ); + + if ( CullLocalFrustum( localFrustum2, indexPoints2, cornerVecs2 ) ) { + return false; + } + + idVec3 indexPoints1[8], cornerVecs1[4]; + idFrustum localFrustum1; + + localFrustum1 = *this; + localFrustum1.origin = ( origin - frustum.origin ) * frustum.axis.Transpose(); + localFrustum1.axis = axis * frustum.axis.Transpose(); + localFrustum1.ToIndexPointsAndCornerVecs( indexPoints1, cornerVecs1 ); + + if ( frustum.CullLocalFrustum( localFrustum1, indexPoints1, cornerVecs1 ) ) { + return false; + } + + idSwap( indexPoints2[2], indexPoints2[3] ); + idSwap( indexPoints2[6], indexPoints2[7] ); + + if ( LocalFrustumIntersectsFrustum( indexPoints2, ( localFrustum2.dNear > 0.0f ) ) ) { + return true; + } + + idSwap( indexPoints1[2], indexPoints1[3] ); + idSwap( indexPoints1[6], indexPoints1[7] ); + + if ( frustum.LocalFrustumIntersectsFrustum( indexPoints1, ( localFrustum1.dNear > 0.0f ) ) ) { + return true; + } + + return false; +} + +/* +============ +idFrustum::IntersectsWinding +============ +*/ +bool idFrustum::IntersectsWinding( const idWinding &winding ) const { + int i, j, *pointCull; + float min, max; + idVec3 *localPoints, indexPoints[8], cornerVecs[4]; + idMat3 transpose; + idPlane plane; + + localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) ); + pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) ); + + transpose = axis.Transpose(); + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose; + } + + // if the winding is culled + if ( CullLocalWinding( localPoints, winding.GetNumPoints(), pointCull ) ) { + return false; + } + + winding.GetPlane( plane ); + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + AxisProjection( indexPoints, cornerVecs, plane.Normal(), min, max ); + + // if the frustum does not cross the winding plane + if ( min + plane[3] > 0.0f || max + plane[3] < 0.0f ) { + return false; + } + + // test if any of the winding edges goes through the frustum + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + j = (i+1)%winding.GetNumPoints(); + if ( !( pointCull[i] & pointCull[j] ) ) { + if ( LocalLineIntersection( localPoints[i], localPoints[j] ) ) { + return true; + } + } + } + + idSwap( indexPoints[2], indexPoints[3] ); + idSwap( indexPoints[6], indexPoints[7] ); + + // test if any edges of the frustum intersect the winding + for ( i = 0; i < 4; i++ ) { + if ( winding.LineIntersection( plane, indexPoints[i], indexPoints[4+i] ) ) { + return true; + } + } + if ( dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + if ( winding.LineIntersection( plane, indexPoints[i], indexPoints[(i+1)&3] ) ) { + return true; + } + } + } + for ( i = 0; i < 4; i++ ) { + if ( winding.LineIntersection( plane, indexPoints[4+i], indexPoints[4+((i+1)&3)] ) ) { + return true; + } + } + + return false; +} + +/* +============ +idFrustum::LineIntersection + + Returns true if the line intersects the box between the start and end point. +============ +*/ +bool idFrustum::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + return LocalLineIntersection( ( start - origin ) * axis.Transpose(), ( end - origin ) * axis.Transpose() ); +} + +/* +============ +idFrustum::RayIntersection + + Returns true if the ray intersects the bounds. + The ray can intersect the bounds in both directions from the start point. + If start is inside the frustum then scale1 < 0 and scale2 > 0. +============ +*/ +bool idFrustum::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + if ( LocalRayIntersection( ( start - origin ) * axis.Transpose(), dir * axis.Transpose(), scale1, scale2 ) ) { + return true; + } + if ( scale1 <= scale2 ) { + return true; + } + return false; +} + +/* +============ +idFrustum::FromProjection + + Creates a frustum which contains the projection of the bounds. +============ +*/ +bool idFrustum::FromProjection( const idBounds &bounds, const idVec3 &projectionOrigin, const float dFar ) { + return FromProjection( idBox( bounds, vec3_origin, mat3_identity ), projectionOrigin, dFar ); +} + +/* +============ +idFrustum::FromProjection + + Creates a frustum which contains the projection of the box. +============ +*/ +bool idFrustum::FromProjection( const idBox &box, const idVec3 &projectionOrigin, const float dFar ) { + int i, bestAxis; + float value, bestValue; + idVec3 dir; + + assert( dFar > 0.0f ); + + this->dNear = this->dFar = this->invFar = 0.0f; + + dir = box.GetCenter() - projectionOrigin; + if ( dir.Normalize() == 0.0f ) { + return false; + } + + bestAxis = 0; + bestValue = idMath::Fabs( box.GetAxis()[0] * dir ); + for ( i = 1; i < 3; i++ ) { + value = idMath::Fabs( box.GetAxis()[i] * dir ); + if ( value * box.GetExtents()[bestAxis] * box.GetExtents()[bestAxis] < bestValue * box.GetExtents()[i] * box.GetExtents()[i] ) { + bestValue = value; + bestAxis = i; + } + } + +#if 1 + + int j, minX, minY, maxY, minZ, maxZ; + idVec3 points[8]; + + minX = minY = maxY = minZ = maxZ = 0; + + for ( j = 0; j < 2; j++ ) { + + axis[0] = dir; + axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[0], axis[1] ); + + BoxToPoints( ( box.GetCenter() - projectionOrigin ) * axis.Transpose(), box.GetExtents(), box.GetAxis() * axis.Transpose(), points ); + + if ( points[0].x <= 1.0f ) { + return false; + } + + minX = minY = maxY = minZ = maxZ = 0; + for ( i = 1; i < 8; i++ ) { + if ( points[i].x <= 1.0f ) { + return false; + } + if ( points[i].x < points[minX].x ) { + minX = i; + } + if ( points[minY].x * points[i].y < points[i].x * points[minY].y ) { + minY = i; + } else if ( points[maxY].x * points[i].y > points[i].x * points[maxY].y ) { + maxY = i; + } + if ( points[minZ].x * points[i].z < points[i].x * points[minZ].z ) { + minZ = i; + } else if ( points[maxZ].x * points[i].z > points[i].x * points[maxZ].z ) { + maxZ = i; + } + } + + if ( j == 0 ) { + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( points[minY].y, points[minY].x ) + idMath::ATan16( points[maxY].y, points[maxY].x ) ) ) * axis[1]; + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( points[minZ].z, points[minZ].x ) + idMath::ATan16( points[maxZ].z, points[maxZ].x ) ) ) * axis[2]; + dir.Normalize(); + } + } + + this->origin = projectionOrigin; + this->dNear = points[minX].x; + this->dFar = dFar; + this->dLeft = Max( idMath::Fabs( points[minY].y / points[minY].x ), idMath::Fabs( points[maxY].y / points[maxY].x ) ) * dFar; + this->dUp = Max( idMath::Fabs( points[minZ].z / points[minZ].x ), idMath::Fabs( points[maxZ].z / points[maxZ].x ) ) * dFar; + this->invFar = 1.0f / dFar; + +#elif 1 + + int j; + float f, x; + idBounds b; + idVec3 points[8]; + + for ( j = 0; j < 2; j++ ) { + + axis[0] = dir; + axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[0], axis[1] ); + + BoxToPoints( ( box.GetCenter() - projectionOrigin ) * axis.Transpose(), box.GetExtents(), box.GetAxis() * axis.Transpose(), points ); + + b.Clear(); + for ( i = 0; i < 8; i++ ) { + x = points[i].x; + if ( x <= 1.0f ) { + return false; + } + f = 1.0f / x; + points[i].y *= f; + points[i].z *= f; + b.AddPoint( points[i] ); + } + + if ( j == 0 ) { + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( b[1][1] ) + idMath::ATan16( b[0][1] ) ) ) * axis[1]; + dir += idMath::Tan16( 0.5f * ( idMath::ATan16( b[1][2] ) + idMath::ATan16( b[0][2] ) ) ) * axis[2]; + dir.Normalize(); + } + } + + this->origin = projectionOrigin; + this->dNear = b[0][0]; + this->dFar = dFar; + this->dLeft = Max( idMath::Fabs( b[0][1] ), idMath::Fabs( b[1][1] ) ) * dFar; + this->dUp = Max( idMath::Fabs( b[0][2] ), idMath::Fabs( b[1][2] ) ) * dFar; + this->invFar = 1.0f / dFar; + +#else + + float dist; + idVec3 org; + + axis[0] = dir; + axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[0], axis[1] ); + + for ( i = 0; i < 3; i++ ) { + dist[i] = idMath::Fabs( box.GetExtents()[0] * ( axis[i] * box.GetAxis()[0] ) ) + + idMath::Fabs( box.GetExtents()[1] * ( axis[i] * box.GetAxis()[1] ) ) + + idMath::Fabs( box.GetExtents()[2] * ( axis[i] * box.GetAxis()[2] ) ); + } + + dist[0] = axis[0] * ( box.GetCenter() - projectionOrigin ) - dist[0]; + if ( dist[0] <= 1.0f ) { + return false; + } + float invDist = 1.0f / dist[0]; + + this->origin = projectionOrigin; + this->dNear = dist[0]; + this->dFar = dFar; + this->dLeft = dist[1] * invDist * dFar; + this->dUp = dist[2] * invDist * dFar; + this->invFar = 1.0f / dFar; + +#endif + + return true; +} + +/* +============ +idFrustum::FromProjection + + Creates a frustum which contains the projection of the sphere. +============ +*/ +bool idFrustum::FromProjection( const idSphere &sphere, const idVec3 &projectionOrigin, const float dFar ) { + idVec3 dir; + float d, r, s, x, y; + + assert( dFar > 0.0f ); + + dir = sphere.GetOrigin() - projectionOrigin; + d = dir.Normalize(); + r = sphere.GetRadius(); + + if ( d <= r + 1.0f ) { + this->dNear = this->dFar = this->invFar = 0.0f; + return false; + } + + origin = projectionOrigin; + axis = dir.ToMat3(); + + s = idMath::Sqrt( d * d - r * r ); + x = r / d * s; + y = idMath::Sqrt( s * s - x * x ); + + this->dNear = d - r; + this->dFar = dFar; + this->dLeft = x / y * dFar; + this->dUp = dLeft; + this->invFar = 1.0f / dFar; + + return true; +} + +/* +============ +idFrustum::ConstrainToBounds + + Returns false if no part of the bounds extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToBounds( const idBounds &bounds ) { + float min, max, newdFar; + + bounds.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ConstrainToBox + + Returns false if no part of the box extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToBox( const idBox &box ) { + float min, max, newdFar; + + box.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ConstrainToSphere + + Returns false if no part of the sphere extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToSphere( const idSphere &sphere ) { + float min, max, newdFar; + + sphere.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ConstrainToFrustum + + Returns false if no part of the frustum extends beyond the near plane. +============ +*/ +bool idFrustum::ConstrainToFrustum( const idFrustum &frustum ) { + float min, max, newdFar; + + frustum.AxisProjection( axis[0], min, max ); + newdFar = max - axis[0] * origin; + if ( newdFar <= dNear ) { + MoveFarDistance( dNear + 1.0f ); + return false; + } + MoveFarDistance( newdFar ); + return true; +} + +/* +============ +idFrustum::ToPlanes + + planes point outwards +============ +*/ +void idFrustum::ToPlanes( idPlane planes[6] ) const { + int i; + idVec3 scaled[2]; + idVec3 points[4]; + + planes[0].Normal() = -axis[0]; + planes[0].SetDist( -dNear ); + planes[1].Normal() = axis[0]; + planes[1].SetDist( dFar ); + + scaled[0] = axis[1] * dLeft; + scaled[1] = axis[2] * dUp; + points[0] = scaled[0] + scaled[1]; + points[1] = -scaled[0] + scaled[1]; + points[2] = -scaled[0] - scaled[1]; + points[3] = scaled[0] - scaled[1]; + + for ( i = 0; i < 4; i++ ) { + planes[i+2].Normal() = points[i].Cross( points[(i+1)&3] - points[i] ); + planes[i+2].Normalize(); + planes[i+2].FitThroughPoint( points[i] ); + } +} + +/* +============ +idFrustum::ToPoints +============ +*/ +void idFrustum::ToPoints( idVec3 points[8] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + points[0] = scaled[0] + scaled[1]; + points[1] = scaled[0] - scaled[1]; + points[2] = points[1] - scaled[2]; + points[3] = points[0] - scaled[2]; + points[0] += scaled[2]; + points[1] += scaled[2]; + + scaled[0] = origin + axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + points[4] = scaled[0] + scaled[1]; + points[5] = scaled[0] - scaled[1]; + points[6] = points[5] - scaled[2]; + points[7] = points[4] - scaled[2]; + points[4] += scaled[2]; + points[5] += scaled[2]; +} + +/* +============ +idFrustum::ToClippedPoints +============ +*/ +void idFrustum::ToClippedPoints( const float fractions[4], idVec3 points[8] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + points[0] = scaled[0] + scaled[1]; + points[1] = scaled[0] - scaled[1]; + points[2] = points[1] - scaled[2]; + points[3] = points[0] - scaled[2]; + points[0] += scaled[2]; + points[1] += scaled[2]; + + scaled[0] = axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + points[4] = scaled[0] + scaled[1]; + points[5] = scaled[0] - scaled[1]; + points[6] = points[5] - scaled[2]; + points[7] = points[4] - scaled[2]; + points[4] += scaled[2]; + points[5] += scaled[2]; + + points[4] = origin + fractions[0] * points[4]; + points[5] = origin + fractions[1] * points[5]; + points[6] = origin + fractions[2] * points[6]; + points[7] = origin + fractions[3] * points[7]; +} + +/* +============ +idFrustum::ToIndexPoints +============ +*/ +void idFrustum::ToIndexPoints( idVec3 indexPoints[8] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + indexPoints[0] = scaled[0] - scaled[1]; + indexPoints[2] = scaled[0] + scaled[1]; + indexPoints[1] = indexPoints[0] + scaled[2]; + indexPoints[3] = indexPoints[2] + scaled[2]; + indexPoints[0] -= scaled[2]; + indexPoints[2] -= scaled[2]; + + scaled[0] = origin + axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + indexPoints[4] = scaled[0] - scaled[1]; + indexPoints[6] = scaled[0] + scaled[1]; + indexPoints[5] = indexPoints[4] + scaled[2]; + indexPoints[7] = indexPoints[6] + scaled[2]; + indexPoints[4] -= scaled[2]; + indexPoints[6] -= scaled[2]; +} + +/* +============ +idFrustum::ToIndexPointsAndCornerVecs + + 22 muls +============ +*/ +void idFrustum::ToIndexPointsAndCornerVecs( idVec3 indexPoints[8], idVec3 cornerVecs[4] ) const { + idMat3 scaled; + + scaled[0] = origin + axis[0] * dNear; + scaled[1] = axis[1] * ( dLeft * dNear * invFar ); + scaled[2] = axis[2] * ( dUp * dNear * invFar ); + + indexPoints[0] = scaled[0] - scaled[1]; + indexPoints[2] = scaled[0] + scaled[1]; + indexPoints[1] = indexPoints[0] + scaled[2]; + indexPoints[3] = indexPoints[2] + scaled[2]; + indexPoints[0] -= scaled[2]; + indexPoints[2] -= scaled[2]; + + scaled[0] = axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + cornerVecs[0] = scaled[0] - scaled[1]; + cornerVecs[2] = scaled[0] + scaled[1]; + cornerVecs[1] = cornerVecs[0] + scaled[2]; + cornerVecs[3] = cornerVecs[2] + scaled[2]; + cornerVecs[0] -= scaled[2]; + cornerVecs[2] -= scaled[2]; + + indexPoints[4] = cornerVecs[0] + origin; + indexPoints[5] = cornerVecs[1] + origin; + indexPoints[6] = cornerVecs[2] + origin; + indexPoints[7] = cornerVecs[3] + origin; +} + +/* +============ +idFrustum::AxisProjection + + 18 muls +============ +*/ +void idFrustum::AxisProjection( const idVec3 indexPoints[8], const idVec3 cornerVecs[4], const idVec3 &dir, float &min, float &max ) const { + float dx, dy, dz; + int index; + + dy = dir.x * axis[1].x + dir.y * axis[1].y + dir.z * axis[1].z; + dz = dir.x * axis[2].x + dir.y * axis[2].y + dir.z * axis[2].z; + index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz ); + dx = dir.x * cornerVecs[index].x + dir.y * cornerVecs[index].y + dir.z * cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + min = indexPoints[index] * dir; + index = ~index & 3; + dx = -dir.x * cornerVecs[index].x - dir.y * cornerVecs[index].y - dir.z * cornerVecs[index].z; + index |= ( FLOATSIGNBITSET( dx ) << 2 ); + max = indexPoints[index] * dir; +} + +/* +============ +idFrustum::AxisProjection + + 40 muls +============ +*/ +void idFrustum::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + idVec3 indexPoints[8], cornerVecs[4]; + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + AxisProjection( indexPoints, cornerVecs, dir, min, max ); +} + +/* +============ +idFrustum::AxisProjection + + 76 muls +============ +*/ +void idFrustum::AxisProjection( const idMat3 &ax, idBounds &bounds ) const { + idVec3 indexPoints[8], cornerVecs[4]; + + ToIndexPointsAndCornerVecs( indexPoints, cornerVecs ); + AxisProjection( indexPoints, cornerVecs, ax[0], bounds[0][0], bounds[1][0] ); + AxisProjection( indexPoints, cornerVecs, ax[1], bounds[0][1], bounds[1][1] ); + AxisProjection( indexPoints, cornerVecs, ax[2], bounds[0][2], bounds[1][2] ); +} + +/* +============ +idFrustum::AddLocalLineToProjectionBoundsSetCull +============ +*/ +void idFrustum::AddLocalLineToProjectionBoundsSetCull( const idVec3 &start, const idVec3 &end, int &startCull, int &endCull, idBounds &bounds ) const { + idVec3 dir, p; + float d1, d2, fstart, fend, lstart, lend, f; + float leftScale, upScale; + int cull1, cull2; + +#ifdef FRUSTUM_DEBUG + static idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERER | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + if ( r_showInteractionScissors.GetInteger() > 1 ) { + session->rw->DebugLine( colorGreen, origin + start * axis, origin + end * axis ); + } +#endif + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + dir = end - start; + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + // test left plane + d1 = -fstart + lstart; + d2 = -fend + lend; + cull1 = FLOATSIGNBITSET( d1 ); + cull2 = FLOATSIGNBITSET( d2 ); + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = 1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + + // test right plane + d1 = fstart + lstart; + d2 = fend + lend; + cull1 |= FLOATSIGNBITSET( d1 ) << 1; + cull2 |= FLOATSIGNBITSET( d2 ) << 1; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = -1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + // test up plane + d1 = -fstart + lstart; + d2 = -fend + lend; + cull1 |= FLOATSIGNBITSET( d1 ) << 2; + cull2 |= FLOATSIGNBITSET( d2 ) << 2; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = 1.0f; + bounds.AddPoint( p ); + } + } + } + } + + // test down plane + d1 = fstart + lstart; + d2 = fend + lend; + cull1 |= FLOATSIGNBITSET( d1 ) << 3; + cull2 |= FLOATSIGNBITSET( d2 ) << 3; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = -1.0f; + bounds.AddPoint( p ); + } + } + } + } + + if ( cull1 == 0 && start.x > 0.0f ) { + // add start point to projection bounds + p.x = start.x; + p.y = start.y * dFar / ( start.x * dLeft ); + p.z = start.z * dFar / ( start.x * dUp ); + bounds.AddPoint( p ); + } + + if ( cull2 == 0 && end.x > 0.0f ) { + // add end point to projection bounds + p.x = end.x; + p.y = end.y * dFar / ( end.x * dLeft ); + p.z = end.z * dFar / ( end.x * dUp ); + bounds.AddPoint( p ); + } + + if ( start.x < bounds[0].x ) { + bounds[0].x = start.x < 0.0f ? 0.0f : start.x; + } + if ( end.x < bounds[0].x ) { + bounds[0].x = end.x < 0.0f ? 0.0f : end.x; + } + + startCull = cull1; + endCull = cull2; +} + +/* +============ +idFrustum::AddLocalLineToProjectionBoundsUseCull +============ +*/ +void idFrustum::AddLocalLineToProjectionBoundsUseCull( const idVec3 &start, const idVec3 &end, int startCull, int endCull, idBounds &bounds ) const { + idVec3 dir, p; + float d1, d2, fstart, fend, lstart, lend, f; + float leftScale, upScale; + int clip; + + clip = startCull ^ endCull; + if ( !clip ) { + return; + } + +#ifdef FRUSTUM_DEBUG + static idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERER | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); + if ( r_showInteractionScissors.GetInteger() > 1 ) { + session->rw->DebugLine( colorGreen, origin + start * axis, origin + end * axis ); + } +#endif + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + dir = end - start; + + if ( clip & (1|2) ) { + + fstart = dFar * start.y; + fend = dFar * end.y; + lstart = dLeft * start.x; + lend = dLeft * end.x; + + if ( clip & 1 ) { + // test left plane + d1 = -fstart + lstart; + d2 = -fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = 1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + } + + if ( clip & 2 ) { + // test right plane + d1 = fstart + lstart; + d2 = fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.z = start.z + f * dir.z; + if ( idMath::Fabs( p.z ) <= p.x * upScale ) { + p.y = -1.0f; + p.z = p.z * dFar / ( p.x * dUp ); + bounds.AddPoint( p ); + } + } + } + } + } + } + + if ( clip & (4|8) ) { + + fstart = dFar * start.z; + fend = dFar * end.z; + lstart = dUp * start.x; + lend = dUp * end.x; + + if ( clip & 4 ) { + // test up plane + d1 = -fstart + lstart; + d2 = -fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = 1.0f; + bounds.AddPoint( p ); + } + } + } + } + } + + if ( clip & 8 ) { + // test down plane + d1 = fstart + lstart; + d2 = fend + lend; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( p.x > 0.0f ) { + p.y = start.y + f * dir.y; + if ( idMath::Fabs( p.y ) <= p.x * leftScale ) { + p.y = p.y * dFar / ( p.x * dLeft ); + p.z = -1.0f; + bounds.AddPoint( p ); + } + } + } + } + } + } +} + +/* +============ +idFrustum::BoundsRayIntersection + + Returns true if the ray starts inside the bounds. + If there was an intersection scale1 <= scale2 +============ +*/ +bool idFrustum::BoundsRayIntersection( const idBounds &bounds, const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + idVec3 end, p; + float d1, d2, f; + int i, startInside = 1; + + scale1 = idMath::INFINITY; + scale2 = -idMath::INFINITY; + + end = start + dir; + + for ( i = 0; i < 2; i++ ) { + d1 = start.x - bounds[i].x; + startInside &= FLOATSIGNBITSET( d1 ) ^ i; + d2 = end.x - bounds[i].x; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + p.y = start.y + f * dir.y; + if ( bounds[0].y <= p.y && p.y <= bounds[1].y ) { + p.z = start.z + f * dir.z; + if ( bounds[0].z <= p.z && p.z <= bounds[1].z ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + d1 = start.y - bounds[i].y; + startInside &= FLOATSIGNBITSET( d1 ) ^ i; + d2 = end.y - bounds[i].y; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( bounds[0].x <= p.x && p.x <= bounds[1].x ) { + p.z = start.z + f * dir.z; + if ( bounds[0].z <= p.z && p.z <= bounds[1].z ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + + d1 = start.z - bounds[i].z; + startInside &= FLOATSIGNBITSET( d1 ) ^ i; + d2 = end.z - bounds[i].z; + if ( d1 != d2 ) { + f = d1 / ( d1 - d2 ); + p.x = start.x + f * dir.x; + if ( bounds[0].x <= p.x && p.x <= bounds[1].x ) { + p.y = start.y + f * dir.y; + if ( bounds[0].y <= p.y && p.y <= bounds[1].y ) { + if ( f < scale1 ) scale1 = f; + if ( f > scale2 ) scale2 = f; + } + } + } + } + + return ( startInside != 0 ); +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idBounds &bounds, idBounds &projectionBounds ) const { + return ProjectionBounds( idBox( bounds, vec3_origin, mat3_identity ), projectionBounds ); +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idBox &box, idBounds &projectionBounds ) const { + int i, p1, p2, pointCull[8], culled, outside; + float scale1, scale2; + idFrustum localFrustum; + idVec3 points[8], localOrigin; + idMat3 localAxis, localScaled; + idBounds bounds( -box.GetExtents(), box.GetExtents() ); + + // if the frustum origin is inside the bounds + if ( bounds.ContainsPoint( ( origin - box.GetCenter() ) * box.GetAxis().Transpose() ) ) { + // bounds that cover the whole frustum + float boxMin, boxMax, base; + + base = origin * axis[0]; + box.AxisProjection( axis[0], boxMin, boxMax ); + + projectionBounds[0].x = boxMin - base; + projectionBounds[1].x = boxMax - base; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; + } + + projectionBounds.Clear(); + + // transform the bounds into the space of this frustum + localOrigin = ( box.GetCenter() - origin ) * axis.Transpose(); + localAxis = box.GetAxis() * axis.Transpose(); + BoxToPoints( localOrigin, box.GetExtents(), localAxis, points ); + + // test outer four edges of the bounds + culled = -1; + outside = 0; + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + AddLocalLineToProjectionBoundsSetCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + culled &= pointCull[p1] & pointCull[p2]; + outside |= pointCull[p1] | pointCull[p2]; + } + + // if the bounds are completely outside this frustum + if ( culled ) { + return false; + } + + // if the bounds are completely inside this frustum + if ( !outside ) { + return true; + } + + // test the remaining edges of the bounds + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + // if the bounds extend beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + localOrigin = ( origin - box.GetCenter() ) * box.GetAxis().Transpose(); + localScaled = axis * box.GetAxis().Transpose(); + localScaled[0] *= dFar; + localScaled[1] *= dLeft; + localScaled[2] *= dUp; + + // test the outer edges of this frustum for intersection with the bounds + if ( (outside & 2) && (outside & 8) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] - localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, -1.0f ) ); + } + } + if ( (outside & 2) && (outside & 4) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] - localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, 1.0f ) ); + } + } + if ( (outside & 1) && (outside & 8) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] + localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, -1.0f ) ); + } + } + if ( (outside & 1) && (outside & 2) ) { + BoundsRayIntersection( bounds, localOrigin, localScaled[0] + localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, 1.0f ) ); + } + } + } + + return true; +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idSphere &sphere, idBounds &projectionBounds ) const { + float d, r, rs, sFar; + idVec3 center; + + projectionBounds.Clear(); + + center = ( sphere.GetOrigin() - origin ) * axis.Transpose(); + r = sphere.GetRadius(); + rs = r * r; + sFar = dFar * dFar; + + // test left/right planes + d = dFar * idMath::Fabs( center.y ) - dLeft * center.x; + if ( ( d * d ) > rs * ( sFar + dLeft * dLeft ) ) { + return false; + } + + // test up/down planes + d = dFar * idMath::Fabs( center.z ) - dUp * center.x; + if ( ( d * d ) > rs * ( sFar + dUp * dUp ) ) { + return false; + } + + // FIXME: implement + + // bounds that cover the whole frustum + projectionBounds[0].x = 0.0f; + projectionBounds[1].x = dFar; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idFrustum &frustum, idBounds &projectionBounds ) const { + int i, p1, p2, pointCull[8], culled, outside; + float scale1, scale2; + idFrustum localFrustum; + idVec3 points[8], localOrigin; + idMat3 localScaled; + + // if the frustum origin is inside the other frustum + if ( frustum.ContainsPoint( origin ) ) { + // bounds that cover the whole frustum + float frustumMin, frustumMax, base; + + base = origin * axis[0]; + frustum.AxisProjection( axis[0], frustumMin, frustumMax ); + + projectionBounds[0].x = frustumMin - base; + projectionBounds[1].x = frustumMax - base; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; + } + + projectionBounds.Clear(); + + // transform the given frustum into the space of this frustum + localFrustum = frustum; + localFrustum.origin = ( frustum.origin - origin ) * axis.Transpose(); + localFrustum.axis = frustum.axis * axis.Transpose(); + localFrustum.ToPoints( points ); + + // test outer four edges of the other frustum + culled = -1; + outside = 0; + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + AddLocalLineToProjectionBoundsSetCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + culled &= pointCull[p1] & pointCull[p2]; + outside |= pointCull[p1] | pointCull[p2]; + } + + // if the other frustum is completely outside this frustum + if ( culled ) { + return false; + } + + // if the other frustum is completely inside this frustum + if ( !outside ) { + return true; + } + + // test the remaining edges of the other frustum + if ( localFrustum.dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + } + + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + // if the other frustum extends beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + localOrigin = ( origin - frustum.origin ) * frustum.axis.Transpose(); + localScaled = axis * frustum.axis.Transpose(); + localScaled[0] *= dFar; + localScaled[1] *= dLeft; + localScaled[2] *= dUp; + + // test the outer edges of this frustum for intersection with the other frustum + if ( (outside & 2) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] - localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, -1.0f ) ); + } + } + if ( (outside & 2) && (outside & 4) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] - localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, 1.0f ) ); + } + } + if ( (outside & 1) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] + localScaled[1] - localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, -1.0f ) ); + } + } + if ( (outside & 1) && (outside & 2) ) { + frustum.LocalRayIntersection( localOrigin, localScaled[0] + localScaled[1] + localScaled[2], scale1, scale2 ); + if ( scale1 <= scale2 && scale1 >= 0.0f ) { + projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, 1.0f ) ); + } + } + } + + return true; +} + +/* +============ +idFrustum::ProjectionBounds +============ +*/ +bool idFrustum::ProjectionBounds( const idWinding &winding, idBounds &projectionBounds ) const { + int i, p1, p2, *pointCull, culled, outside; + float scale; + idVec3 *localPoints; + idMat3 transpose, scaled; + idPlane plane; + + projectionBounds.Clear(); + + // transform the winding points into the space of this frustum + localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) ); + transpose = axis.Transpose(); + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose; + } + + // test the winding edges + culled = -1; + outside = 0; + pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) ); + for ( i = 0; i < winding.GetNumPoints(); i += 2 ) { + p1 = i; + p2 = (i+1)%winding.GetNumPoints(); + AddLocalLineToProjectionBoundsSetCull( localPoints[p1], localPoints[p2], pointCull[p1], pointCull[p2], projectionBounds ); + culled &= pointCull[p1] & pointCull[p2]; + outside |= pointCull[p1] | pointCull[p2]; + } + + // if completely culled + if ( culled ) { + return false; + } + + // if completely inside + if ( !outside ) { + return true; + } + + // test remaining winding edges + for ( i = 1; i < winding.GetNumPoints(); i += 2 ) { + p1 = i; + p2 = (i+1)%winding.GetNumPoints(); + AddLocalLineToProjectionBoundsUseCull( localPoints[p1], localPoints[p2], pointCull[p1], pointCull[p2], projectionBounds ); + } + + // if the winding extends beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + winding.GetPlane( plane ); + scaled[0] = axis[0] * dFar; + scaled[1] = axis[1] * dLeft; + scaled[2] = axis[2] * dUp; + + // test the outer edges of this frustum for intersection with the winding + if ( (outside & 2) && (outside & 8) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] - scaled[1] - scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, -1.0f, -1.0f ) ); + } + } + if ( (outside & 2) && (outside & 4) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] - scaled[1] + scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, -1.0f, 1.0f ) ); + } + } + if ( (outside & 1) && (outside & 8) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] + scaled[1] - scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, 1.0f, -1.0f ) ); + } + } + if ( (outside & 1) && (outside & 2) ) { + if ( winding.RayIntersection( plane, origin, scaled[0] + scaled[1] + scaled[2], scale ) ) { + projectionBounds.AddPoint( idVec3( scale * dFar, 1.0f, 1.0f ) ); + } + } + } + + return true; +} + +/* +============ +idFrustum::ClipFrustumToBox + + Clips the frustum far extents to the box. +============ +*/ +void idFrustum::ClipFrustumToBox( const idBox &box, float clipFractions[4], int clipPlanes[4] ) const { + int i, index; + float f, minf; + idMat3 scaled, localAxis, transpose; + idVec3 localOrigin, cornerVecs[4]; + idBounds bounds; + + transpose = box.GetAxis(); + transpose.TransposeSelf(); + localOrigin = ( origin - box.GetCenter() ) * transpose; + localAxis = axis * transpose; + + scaled[0] = localAxis[0] * dFar; + scaled[1] = localAxis[1] * dLeft; + scaled[2] = localAxis[2] * dUp; + cornerVecs[0] = scaled[0] + scaled[1]; + cornerVecs[1] = scaled[0] - scaled[1]; + cornerVecs[2] = cornerVecs[1] - scaled[2]; + cornerVecs[3] = cornerVecs[0] - scaled[2]; + cornerVecs[0] += scaled[2]; + cornerVecs[1] += scaled[2]; + + bounds[0] = -box.GetExtents(); + bounds[1] = box.GetExtents(); + + minf = ( dNear + 1.0f ) * invFar; + + for ( i = 0; i < 4; i++ ) { +// RAVEN BEGIN +// jscott: made safe + clipFractions[i] = MAX_WORLD_COORD; + clipPlanes[i] = 1; + + if( cornerVecs[i].x != 0.0f ) { + + index = FLOATSIGNBITNOTSET( cornerVecs[i].x ); + f = ( bounds[index].x - localOrigin.x ) / cornerVecs[i].x; + clipFractions[i] = f; + clipPlanes[i] = 1 << index; + } + + if( cornerVecs[i].y != 0.0f ) { + + index = FLOATSIGNBITNOTSET( cornerVecs[i].y ); + f = ( bounds[index].y - localOrigin.y ) / cornerVecs[i].y; + if ( f < clipFractions[i] ) { + clipFractions[i] = f; + clipPlanes[i] = 4 << index; + } + } + + if( cornerVecs[i].z != 0.0f ) { + + index = FLOATSIGNBITNOTSET( cornerVecs[i].z ); + f = ( bounds[index].z - localOrigin.z ) / cornerVecs[i].z; + if ( f < clipFractions[i] ) { + clipFractions[i] = f; + clipPlanes[i] = 16 << index; + } + } +// RAVEN END + // make sure the frustum is not clipped between the frustum origin and the near plane + if ( clipFractions[i] < minf ) { + clipFractions[i] = minf; + } + } +} + +/* +============ +idFrustum::ClipLine + + Returns true if part of the line is inside the frustum. + Does not clip to the near and far plane. +============ +*/ +bool idFrustum::ClipLine( const idVec3 localPoints[8], const idVec3 points[8], int startIndex, int endIndex, idVec3 &start, idVec3 &end, int &startClip, int &endClip ) const { + float d1, d2, fstart, fend, lstart, lend, f, x; + float leftScale, upScale; + float scale1, scale2; + int startCull, endCull; + idVec3 localStart, localEnd, localDir; + + leftScale = dLeft * invFar; + upScale = dUp * invFar; + + localStart = localPoints[startIndex]; + localEnd = localPoints[endIndex]; + localDir = localEnd - localStart; + + startClip = endClip = -1; + scale1 = idMath::INFINITY; + scale2 = -idMath::INFINITY; + + fstart = dFar * localStart.y; + fend = dFar * localEnd.y; + lstart = dLeft * localStart.x; + lend = dLeft * localEnd.x; + + // test left plane + d1 = -fstart + lstart; + d2 = -fend + lend; + startCull = FLOATSIGNBITSET( d1 ); + endCull = FLOATSIGNBITSET( d2 ); + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.z + f * localDir.z ) <= x * upScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 0; } + if ( f > scale2 ) { scale2 = f; endClip = 0; } + } + } + } + } + + // test right plane + d1 = fstart + lstart; + d2 = fend + lend; + startCull |= FLOATSIGNBITSET( d1 ) << 1; + endCull |= FLOATSIGNBITSET( d2 ) << 1; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.z + f * localDir.z ) <= x * upScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 1; } + if ( f > scale2 ) { scale2 = f; endClip = 1; } + } + } + } + } + + fstart = dFar * localStart.z; + fend = dFar * localEnd.z; + lstart = dUp * localStart.x; + lend = dUp * localEnd.x; + + // test up plane + d1 = -fstart + lstart; + d2 = -fend + lend; + startCull |= FLOATSIGNBITSET( d1 ) << 2; + endCull |= FLOATSIGNBITSET( d2 ) << 2; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.y + f * localDir.y ) <= x * leftScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 2; } + if ( f > scale2 ) { scale2 = f; endClip = 2; } + } + } + } + } + + // test down plane + d1 = fstart + lstart; + d2 = fend + lend; + startCull |= FLOATSIGNBITSET( d1 ) << 3; + endCull |= FLOATSIGNBITSET( d2 ) << 3; + if ( FLOATNOTZERO( d1 ) ) { + if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) { + f = d1 / ( d1 - d2 ); + x = localStart.x + f * localDir.x; + if ( x >= 0.0f ) { + if ( idMath::Fabs( localStart.y + f * localDir.y ) <= x * leftScale ) { + if ( f < scale1 ) { scale1 = f; startClip = 3; } + if ( f > scale2 ) { scale2 = f; endClip = 3; } + } + } + } + } + + // if completely inside + if ( !( startCull | endCull ) ) { + start = points[startIndex]; + end = points[endIndex]; + return true; + } + else if ( scale1 <= scale2 ) { + if ( !startCull ) { + start = points[startIndex]; + startClip = -1; + } + else { + start = points[startIndex] + scale1 * ( points[endIndex] - points[startIndex] ); + } + if ( !endCull ) { + end = points[endIndex]; + endClip = -1; + } + else { + end = points[startIndex] + scale2 * ( points[endIndex] - points[startIndex] ); + } + return true; + } + return false; +} + +/* +============ +idFrustum::AddLocalCapsToProjectionBounds +============ +*/ +static int capPointIndex[4][2] = { + { 0, 3 }, + { 1, 2 }, + { 0, 1 }, + { 2, 3 } +}; + +ID_INLINE bool idFrustum::AddLocalCapsToProjectionBounds( const idVec3 endPoints[4], const int endPointCull[4], const idVec3 &point, int pointCull, int pointClip, idBounds &projectionBounds ) const { + int *p; + + if ( pointClip < 0 ) { + return false; + } + p = capPointIndex[pointClip]; + AddLocalLineToProjectionBoundsUseCull( endPoints[p[0]], point, endPointCull[p[0]], pointCull, projectionBounds ); + AddLocalLineToProjectionBoundsUseCull( endPoints[p[1]], point, endPointCull[p[1]], pointCull, projectionBounds ); + return true; +} + +/* +============ +idFrustum::ClippedProjectionBounds +============ +*/ +bool idFrustum::ClippedProjectionBounds( const idFrustum &frustum, const idBox &clipBox, idBounds &projectionBounds ) const { + int i, p1, p2, clipPointCull[8], clipPlanes[4], usedClipPlanes, nearCull, farCull, outside; + int pointCull[2], startClip, endClip, boxPointCull[8]; + float clipFractions[4], s1, s2, t1, t2, leftScale, upScale; + idFrustum localFrustum; + idVec3 clipPoints[8], localPoints1[8], localPoints2[8], localOrigin1, localOrigin2, start, end; + idMat3 localAxis1, localAxis2, transpose; + idBounds clipBounds; + + // if the frustum origin is inside the other frustum + if ( frustum.ContainsPoint( origin ) ) { + // bounds that cover the whole frustum + float clipBoxMin, clipBoxMax, frustumMin, frustumMax, base; + + base = origin * axis[0]; + clipBox.AxisProjection( axis[0], clipBoxMin, clipBoxMax ); + frustum.AxisProjection( axis[0], frustumMin, frustumMax ); + + projectionBounds[0].x = Max( clipBoxMin, frustumMin ) - base; + projectionBounds[1].x = Min( clipBoxMax, frustumMax ) - base; + projectionBounds[0].y = projectionBounds[0].z = -1.0f; + projectionBounds[1].y = projectionBounds[1].z = 1.0f; + return true; + } + + projectionBounds.Clear(); + + // clip the outer edges of the given frustum to the clip bounds + frustum.ClipFrustumToBox( clipBox, clipFractions, clipPlanes ); + usedClipPlanes = clipPlanes[0] | clipPlanes[1] | clipPlanes[2] | clipPlanes[3]; + + // transform the clipped frustum to the space of this frustum + transpose = axis; + transpose.TransposeSelf(); + localFrustum = frustum; + localFrustum.origin = ( frustum.origin - origin ) * transpose; + localFrustum.axis = frustum.axis * transpose; + localFrustum.ToClippedPoints( clipFractions, clipPoints ); + + // test outer four edges of the clipped frustum + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + AddLocalLineToProjectionBoundsSetCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds ); + } + + // get cull bits for the clipped frustum + outside = clipPointCull[0] | clipPointCull[1] | clipPointCull[2] | clipPointCull[3] | + clipPointCull[4] | clipPointCull[5] | clipPointCull[6] | clipPointCull[7]; + nearCull = clipPointCull[0] & clipPointCull[1] & clipPointCull[2] & clipPointCull[3]; + farCull = clipPointCull[4] & clipPointCull[5] & clipPointCull[6] & clipPointCull[7]; + + // if the clipped frustum is not completely inside this frustum + if ( outside ) { + + // test the remaining edges of the clipped frustum + if ( !nearCull && localFrustum.dNear > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + AddLocalLineToProjectionBoundsUseCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds ); + } + } + + if ( !farCull ) { + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + AddLocalLineToProjectionBoundsUseCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds ); + } + } + } + + // if the clipped frustum far end points are inside this frustum + if ( !( farCull && !( nearCull & farCull ) ) && + // if the clipped frustum is not clipped to a single plane of the clip bounds + ( clipPlanes[0] != clipPlanes[1] || clipPlanes[1] != clipPlanes[2] || clipPlanes[2] != clipPlanes[3] ) ) { + + // transform the clip box into the space of the other frustum + transpose = frustum.axis; + transpose.TransposeSelf(); + localOrigin1 = ( clipBox.GetCenter() - frustum.origin ) * transpose; + localAxis1 = clipBox.GetAxis() * transpose; + BoxToPoints( localOrigin1, clipBox.GetExtents(), localAxis1, localPoints1 ); + + // cull the box corners with the other frustum + leftScale = frustum.dLeft * frustum.invFar; + upScale = frustum.dUp * frustum.invFar; + for ( i = 0; i < 8; i++ ) { + idVec3 &p = localPoints1[i]; + if ( !( boxVertPlanes[i] & usedClipPlanes ) || p.x <= 0.0f ) { + boxPointCull[i] = 1|2|4|8; + } + else { + boxPointCull[i] = 0; + if ( idMath::Fabs( p.y ) > p.x * leftScale ) { + boxPointCull[i] |= 1 << FLOATSIGNBITSET( p.y ); + } + if ( idMath::Fabs( p.z ) > p.x * upScale ) { + boxPointCull[i] |= 4 << FLOATSIGNBITSET( p.z ); + } + } + } + + // transform the clip box into the space of this frustum + transpose = axis; + transpose.TransposeSelf(); + localOrigin2 = ( clipBox.GetCenter() - origin ) * transpose; + localAxis2 = clipBox.GetAxis() * transpose; + BoxToPoints( localOrigin2, clipBox.GetExtents(), localAxis2, localPoints2 ); + + // clip the edges of the clip bounds to the other frustum and add the clipped edges to the projection bounds + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = 4 + i; + if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) { + if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) { + AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds ); + outside |= pointCull[0] | pointCull[1]; + } + } + } + + for ( i = 0; i < 4; i++ ) { + p1 = i; + p2 = (i+1)&3; + if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) { + if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) { + AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds ); + outside |= pointCull[0] | pointCull[1]; + } + } + } + + for ( i = 0; i < 4; i++ ) { + p1 = 4 + i; + p2 = 4 + ((i+1)&3); + if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) { + if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) { + AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds ); + AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds ); + outside |= pointCull[0] | pointCull[1]; + } + } + } + } + + // if the clipped frustum extends beyond two or more boundaries of this frustum + if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) { + + // transform this frustum into the space of the other frustum + transpose = frustum.axis; + transpose.TransposeSelf(); + localOrigin1 = ( origin - frustum.origin ) * transpose; + localAxis1 = axis * transpose; + localAxis1[0] *= dFar; + localAxis1[1] *= dLeft; + localAxis1[2] *= dUp; + + // transform this frustum into the space of the clip bounds + transpose = clipBox.GetAxis(); + transpose.TransposeSelf(); + localOrigin2 = ( origin - clipBox.GetCenter() ) * transpose; + localAxis2 = axis * transpose; + localAxis2[0] *= dFar; + localAxis2[1] *= dLeft; + localAxis2[2] *= dUp; + + clipBounds[0] = -clipBox.GetExtents(); + clipBounds[1] = clipBox.GetExtents(); + + // test the outer edges of this frustum for intersection with both the other frustum and the clip bounds + if ( (outside & 2) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] - localAxis1[1] - localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] - localAxis2[1] - localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, -1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, -1.0f, -1.0f ) ); + } + } + } + if ( (outside & 2) && (outside & 4) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] - localAxis1[1] + localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] - localAxis2[1] + localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, -1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, -1.0f, 1.0f ) ); + } + } + } + if ( (outside & 1) && (outside & 8) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] + localAxis1[1] - localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] + localAxis2[1] - localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, 1.0f, -1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, 1.0f, -1.0f ) ); + } + } + } + if ( (outside & 1) && (outside & 2) ) { + frustum.LocalRayIntersection( localOrigin1, localAxis1[0] + localAxis1[1] + localAxis1[2], s1, s2 ); + if ( s1 <= s2 && s1 >= 0.0f ) { + BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] + localAxis2[1] + localAxis2[2], t1, t2 ); + if ( t1 <= t2 && t2 > s1 && t1 < s2 ) { + projectionBounds.AddPoint( idVec3( s1 * dFar, 1.0f, 1.0f ) ); + projectionBounds.AddPoint( idVec3( s2 * dFar, 1.0f, 1.0f ) ); + } + } + } + } + + return true; +} diff --git a/src/idlib/bv/Frustum.h b/src/idlib/bv/Frustum.h new file mode 100644 index 0000000..dc85ac2 --- /dev/null +++ b/src/idlib/bv/Frustum.h @@ -0,0 +1,261 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BV_FRUSTUM_H__ +#define __BV_FRUSTUM_H__ + +/* +=============================================================================== + + Orthogonal Frustum + +=============================================================================== +*/ + +class idFrustum { +public: + idFrustum( void ); + + void SetOrigin( const idVec3 &origin ); + void SetAxis( const idMat3 &axis ); + void SetSize( float dNear, float dFar, float dLeft, float dUp ); + void SetPyramid( float dNear, float dFar ); + void MoveNearDistance( float dNear ); + void MoveFarDistance( float dFar ); + + const idVec3 & GetOrigin( void ) const; // returns frustum origin + const idMat3 & GetAxis( void ) const; // returns frustum orientation + idVec3 GetCenter( void ) const; // returns center of frustum + + bool IsValid( void ) const; // returns true if the frustum is valid + float GetNearDistance( void ) const; // returns distance to near plane + float GetFarDistance( void ) const; // returns distance to far plane + float GetLeft( void ) const; // returns left vector length + float GetUp( void ) const; // returns up vector length + + idFrustum Expand( const float d ) const; // returns frustum expanded in all directions with the given value + idFrustum & ExpandSelf( const float d ); // expands frustum in all directions with the given value + idFrustum Translate( const idVec3 &translation ) const; // returns translated frustum + idFrustum & TranslateSelf( const idVec3 &translation ); // translates frustum + idFrustum Rotate( const idMat3 &rotation ) const; // returns rotated frustum + idFrustum & RotateSelf( const idMat3 &rotation ); // rotates frustum + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + // fast culling but might not cull everything outside the frustum + bool CullPoint( const idVec3 &point ) const; + bool CullBounds( const idBounds &bounds ) const; + bool CullBox( const idBox &box ) const; + bool CullSphere( const idSphere &sphere ) const; + bool CullFrustum( const idFrustum &frustum ) const; + bool CullWinding( const class idWinding &winding ) const; + + // exact intersection tests + bool ContainsPoint( const idVec3 &point ) const; + bool IntersectsBounds( const idBounds &bounds ) const; + bool IntersectsBox( const idBox &box ) const; + bool IntersectsSphere( const idSphere &sphere ) const; + bool IntersectsFrustum( const idFrustum &frustum ) const; + bool IntersectsWinding( const idWinding &winding ) const; + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // returns true if the projection origin is far enough away from the bounding volume to create a valid frustum + bool FromProjection( const idBounds &bounds, const idVec3 &projectionOrigin, const float dFar ); + bool FromProjection( const idBox &box, const idVec3 &projectionOrigin, const float dFar ); + bool FromProjection( const idSphere &sphere, const idVec3 &projectionOrigin, const float dFar ); + + // moves the far plane so it extends just beyond the bounding volume + bool ConstrainToBounds( const idBounds &bounds ); + bool ConstrainToBox( const idBox &box ); + bool ConstrainToSphere( const idSphere &sphere ); + bool ConstrainToFrustum( const idFrustum &frustum ); + + void ToPlanes( idPlane planes[6] ) const; // planes point outwards + void ToPoints( idVec3 points[8] ) const; // 8 corners of the frustum + + // calculates the projection of this frustum onto the given axis + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + void AxisProjection( const idMat3 &ax, idBounds &bounds ) const; + + // calculates the bounds for the projection in this frustum + bool ProjectionBounds( const idBounds &bounds, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idBox &box, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idSphere &sphere, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idFrustum &frustum, idBounds &projectionBounds ) const; + bool ProjectionBounds( const idWinding &winding, idBounds &projectionBounds ) const; + + // calculates the bounds for the projection in this frustum of the given frustum clipped to the given box + bool ClippedProjectionBounds( const idFrustum &frustum, const idBox &clipBox, idBounds &projectionBounds ) const; + +private: + idVec3 origin; // frustum origin + idMat3 axis; // frustum orientation + float dNear; // distance of near plane, dNear >= 0.0f + float dFar; // distance of far plane, dFar > dNear + float dLeft; // half the width at the far plane + float dUp; // half the height at the far plane + float invFar; // 1.0f / dFar + +private: + bool CullLocalBox( const idVec3 &localOrigin, const idVec3 &extents, const idMat3 &localAxis ) const; + bool CullLocalFrustum( const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const; + bool CullLocalWinding( const idVec3 *points, const int numPoints, int *pointCull ) const; + bool BoundsCullLocalFrustum( const idBounds &bounds, const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const; + bool LocalLineIntersection( const idVec3 &start, const idVec3 &end ) const; + bool LocalRayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + bool LocalFrustumIntersectsFrustum( const idVec3 points[8], const bool testFirstSide ) const; + bool LocalFrustumIntersectsBounds( const idVec3 points[8], const idBounds &bounds ) const; + void ToClippedPoints( const float fractions[4], idVec3 points[8] ) const; + void ToIndexPoints( idVec3 indexPoints[8] ) const; + void ToIndexPointsAndCornerVecs( idVec3 indexPoints[8], idVec3 cornerVecs[4] ) const; + void AxisProjection( const idVec3 indexPoints[8], const idVec3 cornerVecs[4], const idVec3 &dir, float &min, float &max ) const; + void AddLocalLineToProjectionBoundsSetCull( const idVec3 &start, const idVec3 &end, int &startCull, int &endCull, idBounds &bounds ) const; + void AddLocalLineToProjectionBoundsUseCull( const idVec3 &start, const idVec3 &end, int startCull, int endCull, idBounds &bounds ) const; + bool AddLocalCapsToProjectionBounds( const idVec3 endPoints[4], const int endPointCull[4], const idVec3 &point, int pointCull, int pointClip, idBounds &projectionBounds ) const; + bool BoundsRayIntersection( const idBounds &bounds, const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + void ClipFrustumToBox( const idBox &box, float clipFractions[4], int clipPlanes[4] ) const; + bool ClipLine( const idVec3 localPoints[8], const idVec3 points[8], int startIndex, int endIndex, idVec3 &start, idVec3 &end, int &startClip, int &endClip ) const; +}; + + +ID_INLINE idFrustum::idFrustum( void ) { + dNear = dFar = 0.0f; +} + +ID_INLINE void idFrustum::SetOrigin( const idVec3 &origin ) { + this->origin = origin; +} + +ID_INLINE void idFrustum::SetAxis( const idMat3 &axis ) { + this->axis = axis; +} + +ID_INLINE void idFrustum::SetSize( float dNear, float dFar, float dLeft, float dUp ) { + assert( dNear >= 0.0f && dFar > dNear && dLeft > 0.0f && dUp > 0.0f ); + this->dNear = dNear; + this->dFar = dFar; + this->dLeft = dLeft; + this->dUp = dUp; + this->invFar = 1.0f / dFar; +} + +ID_INLINE void idFrustum::SetPyramid( float dNear, float dFar ) { + assert( dNear >= 0.0f && dFar > dNear ); + this->dNear = dNear; + this->dFar = dFar; + this->dLeft = dFar; + this->dUp = dFar; + this->invFar = 1.0f / dFar; +} + +ID_INLINE void idFrustum::MoveNearDistance( float dNear ) { + assert( dNear >= 0.0f ); + this->dNear = dNear; +} + +ID_INLINE void idFrustum::MoveFarDistance( float dFar ) { + assert( dFar > this->dNear ); + float scale = dFar / this->dFar; + this->dFar = dFar; + this->dLeft *= scale; + this->dUp *= scale; + this->invFar = 1.0f / dFar; +} + +ID_INLINE const idVec3 &idFrustum::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idMat3 &idFrustum::GetAxis( void ) const { + return axis; +} + +ID_INLINE idVec3 idFrustum::GetCenter( void ) const { + return ( origin + axis[0] * ( ( dFar - dNear ) * 0.5f ) ); +} + +ID_INLINE bool idFrustum::IsValid( void ) const { + return ( dFar > dNear ); +} + +ID_INLINE float idFrustum::GetNearDistance( void ) const { + return dNear; +} + +ID_INLINE float idFrustum::GetFarDistance( void ) const { + return dFar; +} + +ID_INLINE float idFrustum::GetLeft( void ) const { + return dLeft; +} + +ID_INLINE float idFrustum::GetUp( void ) const { + return dUp; +} + +ID_INLINE idFrustum idFrustum::Expand( const float d ) const { + idFrustum f = *this; + f.origin -= d * f.axis[0]; + f.dFar += 2.0f * d; + f.dLeft = f.dFar * dLeft * invFar; + f.dUp = f.dFar * dUp * invFar; + f.invFar = 1.0f / dFar; + return f; +} + +ID_INLINE idFrustum &idFrustum::ExpandSelf( const float d ) { + origin -= d * axis[0]; + dFar += 2.0f * d; + dLeft = dFar * dLeft * invFar; + dUp = dFar * dUp * invFar; + invFar = 1.0f / dFar; + return *this; +} + +ID_INLINE idFrustum idFrustum::Translate( const idVec3 &translation ) const { + idFrustum f = *this; + f.origin += translation; + return f; +} + +ID_INLINE idFrustum &idFrustum::TranslateSelf( const idVec3 &translation ) { + origin += translation; + return *this; +} + +ID_INLINE idFrustum idFrustum::Rotate( const idMat3 &rotation ) const { + idFrustum f = *this; + f.axis *= rotation; + return f; +} + +ID_INLINE idFrustum &idFrustum::RotateSelf( const idMat3 &rotation ) { + axis *= rotation; + return *this; +} + +#endif /* !__BV_FRUSTUM_H__ */ diff --git a/src/idlib/bv/Sphere.cpp b/src/idlib/bv/Sphere.cpp new file mode 100644 index 0000000..023ccc0 --- /dev/null +++ b/src/idlib/bv/Sphere.cpp @@ -0,0 +1,151 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +idSphere sphere_zero( vec3_zero, 0.0f ); + + +/* +================ +idSphere::PlaneDistance +================ +*/ +float idSphere::PlaneDistance( const idPlane &plane ) const { + float d; + + d = plane.Distance( origin ); + if ( d > radius ) { + return d - radius; + } + if ( d < -radius ) { + return d + radius; + } + return 0.0f; +} + +/* +================ +idSphere::PlaneSide +================ +*/ +int idSphere::PlaneSide( const idPlane &plane, const float epsilon ) const { + float d; + + d = plane.Distance( origin ); + if ( d > radius + epsilon ) { + return PLANESIDE_FRONT; + } + if ( d < -radius - epsilon ) { + return PLANESIDE_BACK; + } + return PLANESIDE_CROSS; +} + +/* +============ +idSphere::LineIntersection + + Returns true if the line intersects the sphere between the start and end point. +============ +*/ +bool idSphere::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + idVec3 r, s, e; + float a; + + s = start - origin; + e = end - origin; + r = e - s; + a = -s * r; + if ( a <= 0 ) { + return ( s * s < radius * radius ); + } + else if ( a >= r * r ) { + return ( e * e < radius * radius ); + } + else { + r = s + ( a / ( r * r ) ) * r; + return ( r * r < radius * radius ); + } +} + +/* +============ +idSphere::RayIntersection + + Returns true if the ray intersects the sphere. + The ray can intersect the sphere in both directions from the start point. + If start is inside the sphere then scale1 < 0 and scale2 > 0. +============ +*/ +bool idSphere::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const { + double a, b, c, d, sqrtd; + idVec3 p; + + p = start - origin; + a = dir * dir; + b = dir * p; + c = p * p - radius * radius; + d = b * b - c * a; + + if ( d < 0.0f ) { + return false; + } + + sqrtd = idMath::Sqrt( d ); + a = 1.0f / a; + + scale1 = ( -b + sqrtd ) * a; + scale2 = ( -b - sqrtd ) * a; + + return true; +} + +/* +============ +idSphere::FromPoints + + Tight sphere for a point set. +============ +*/ +void idSphere::FromPoints( const idVec3 *points, const int numPoints ) { + int i; + float radiusSqr, dist; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, points, numPoints ); + + origin = ( mins + maxs ) * 0.5f; + + radiusSqr = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dist = ( points[i] - origin ).LengthSqr(); + if ( dist > radiusSqr ) { + radiusSqr = dist; + } + } + radius = idMath::Sqrt( radiusSqr ); +} diff --git a/src/idlib/bv/Sphere.h b/src/idlib/bv/Sphere.h new file mode 100644 index 0000000..cd6cdd2 --- /dev/null +++ b/src/idlib/bv/Sphere.h @@ -0,0 +1,271 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BV_SPHERE_H__ +#define __BV_SPHERE_H__ + +/* +=============================================================================== + + Sphere + +=============================================================================== +*/ + +class idSphere { +public: + idSphere( void ); + explicit idSphere( const idVec3 &point ); + explicit idSphere( const idVec3 &point, const float r ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idSphere operator+( const idVec3 &t ) const; // returns tranlated sphere + idSphere & operator+=( const idVec3 &t ); // translate the sphere + idSphere operator+( const idSphere &s ) const; + idSphere & operator+=( const idSphere &s ); + + bool Compare( const idSphere &a ) const; // exact compare, no epsilon + bool Compare( const idSphere &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idSphere &a ) const; // exact compare, no epsilon + bool operator!=( const idSphere &a ) const; // exact compare, no epsilon + + void Clear( void ); // inside out sphere + void Zero( void ); // single point at origin + void SetOrigin( const idVec3 &o ); // set origin of sphere + void SetRadius( const float r ); // set square radius + + const idVec3 & GetOrigin( void ) const; // returns origin of sphere + float GetRadius( void ) const; // returns sphere radius + bool IsCleared( void ) const; // returns true if sphere is inside out + + bool AddPoint( const idVec3 &p ); // add the point, returns true if the sphere expanded + bool AddSphere( const idSphere &s ); // add the sphere, returns true if the sphere expanded + idSphere Expand( const float d ) const; // return bounds expanded in all directions with the given value + idSphere & ExpandSelf( const float d ); // expand bounds in all directions with the given value + idSphere Translate( const idVec3 &translation ) const; + idSphere & TranslateSelf( const idVec3 &translation ); + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool ContainsPoint( const idVec3 &p ) const; // includes touching + bool IntersectsSphere( const idSphere &s ) const; // includes touching + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection points are (start + dir * scale1) and (start + dir * scale2) + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const; + + // Tight sphere for a point set. + void FromPoints( const idVec3 *points, const int numPoints ); + // Most tight sphere for a translation. + void FromPointTranslation( const idVec3 &point, const idVec3 &translation ); + void FromSphereTranslation( const idSphere &sphere, const idVec3 &start, const idVec3 &translation ); + // Most tight sphere for a rotation. + void FromPointRotation( const idVec3 &point, const idRotation &rotation ); + void FromSphereRotation( const idSphere &sphere, const idVec3 &start, const idRotation &rotation ); + + void AxisProjection( const idVec3 &dir, float &min, float &max ) const; + +private: + idVec3 origin; + float radius; +}; + +extern idSphere sphere_zero; + +ID_INLINE idSphere::idSphere( void ) { +} + +ID_INLINE idSphere::idSphere( const idVec3 &point ) { + origin = point; + radius = 0.0f; +} + +ID_INLINE idSphere::idSphere( const idVec3 &point, const float r ) { + origin = point; + radius = r; +} + +ID_INLINE float idSphere::operator[]( const int index ) const { + return ((float *) &origin)[index]; +} + +ID_INLINE float &idSphere::operator[]( const int index ) { + return ((float *) &origin)[index]; +} + +ID_INLINE idSphere idSphere::operator+( const idVec3 &t ) const { + return idSphere( origin + t, radius ); +} + +ID_INLINE idSphere &idSphere::operator+=( const idVec3 &t ) { + origin += t; + return *this; +} + +ID_INLINE bool idSphere::Compare( const idSphere &a ) const { + return ( origin.Compare( a.origin ) && radius == a.radius ); +} + +ID_INLINE bool idSphere::Compare( const idSphere &a, const float epsilon ) const { + return ( origin.Compare( a.origin, epsilon ) && idMath::Fabs( radius - a.radius ) <= epsilon ); +} + +ID_INLINE bool idSphere::operator==( const idSphere &a ) const { + return Compare( a ); +} + +ID_INLINE bool idSphere::operator!=( const idSphere &a ) const { + return !Compare( a ); +} + +ID_INLINE void idSphere::Clear( void ) { + origin.Zero(); + radius = -1.0f; +} + +ID_INLINE void idSphere::Zero( void ) { + origin.Zero(); + radius = 0.0f; +} + +ID_INLINE void idSphere::SetOrigin( const idVec3 &o ) { + origin = o; +} + +ID_INLINE void idSphere::SetRadius( const float r ) { + radius = r; +} + +ID_INLINE const idVec3 &idSphere::GetOrigin( void ) const { + return origin; +} + +ID_INLINE float idSphere::GetRadius( void ) const { + return radius; +} + +ID_INLINE bool idSphere::IsCleared( void ) const { + return ( radius < 0.0f ); +} + +ID_INLINE bool idSphere::AddPoint( const idVec3 &p ) { + if ( radius < 0.0f ) { + origin = p; + radius = 0.0f; + return true; + } + else { + float r = ( p - origin ).LengthSqr(); + if ( r > radius * radius ) { + r = idMath::Sqrt( r ); + origin += ( p - origin ) * 0.5f * (1.0f - radius / r ); + radius += 0.5f * ( r - radius ); + return true; + } + return false; + } +} + +ID_INLINE bool idSphere::AddSphere( const idSphere &s ) { + if ( radius < 0.0f ) { + origin = s.origin; + radius = s.radius; + return true; + } + else { + float r = ( s.origin - origin ).LengthSqr(); + if ( r > ( radius + s.radius ) * ( radius + s.radius ) ) { + r = idMath::Sqrt( r ); + origin += ( s.origin - origin ) * 0.5f * (1.0f - radius / ( r + s.radius ) ); + radius += 0.5f * ( ( r + s.radius ) - radius ); + return true; + } + return false; + } +} + +ID_INLINE idSphere idSphere::Expand( const float d ) const { + return idSphere( origin, radius + d ); +} + +ID_INLINE idSphere &idSphere::ExpandSelf( const float d ) { + radius += d; + return *this; +} + +ID_INLINE idSphere idSphere::Translate( const idVec3 &translation ) const { + return idSphere( origin + translation, radius ); +} + +ID_INLINE idSphere &idSphere::TranslateSelf( const idVec3 &translation ) { + origin += translation; + return *this; +} + +ID_INLINE bool idSphere::ContainsPoint( const idVec3 &p ) const { + if ( ( p - origin ).LengthSqr() > radius * radius ) { + return false; + } + return true; +} + +ID_INLINE bool idSphere::IntersectsSphere( const idSphere &s ) const { + float r = s.radius + radius; + if ( ( s.origin - origin ).LengthSqr() > r * r ) { + return false; + } + return true; +} + +ID_INLINE void idSphere::FromPointTranslation( const idVec3 &point, const idVec3 &translation ) { + origin = point + 0.5f * translation; + radius = idMath::Sqrt( 0.5f * translation.LengthSqr() ); +} + +ID_INLINE void idSphere::FromSphereTranslation( const idSphere &sphere, const idVec3 &start, const idVec3 &translation ) { + origin = start + sphere.origin + 0.5f * translation; + radius = idMath::Sqrt( 0.5f * translation.LengthSqr() ) + sphere.radius; +} + +ID_INLINE void idSphere::FromPointRotation( const idVec3 &point, const idRotation &rotation ) { + idVec3 end = rotation * point; + origin = ( point + end ) * 0.5f; + radius = idMath::Sqrt( 0.5f * ( end - point ).LengthSqr() ); +} + +ID_INLINE void idSphere::FromSphereRotation( const idSphere &sphere, const idVec3 &start, const idRotation &rotation ) { + idVec3 end = rotation * sphere.origin; + origin = start + ( sphere.origin + end ) * 0.5f; + radius = idMath::Sqrt( 0.5f * ( end - sphere.origin ).LengthSqr() ) + sphere.radius; +} + +ID_INLINE void idSphere::AxisProjection( const idVec3 &dir, float &min, float &max ) const { + float d; + d = dir * origin; + min = d - radius; + max = d + radius; +} + +#endif /* !__BV_SPHERE_H__ */ diff --git a/src/idlib/containers/BTree.h b/src/idlib/containers/BTree.h new file mode 100644 index 0000000..33cb8ce --- /dev/null +++ b/src/idlib/containers/BTree.h @@ -0,0 +1,519 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BTREE_H__ +#define __BTREE_H__ + +/* +=============================================================================== + + Balanced Search Tree + +=============================================================================== +*/ + +//#define BTREE_CHECK + +template< class objType, class keyType > +class idBTreeNode { +public: + keyType key; // key used for sorting + objType * object; // if != NULL pointer to object stored in leaf node + idBTreeNode * parent; // parent node + idBTreeNode * next; // next sibling + idBTreeNode * prev; // prev sibling + int numChildren; // number of children + idBTreeNode * firstChild; // first child + idBTreeNode * lastChild; // last child +}; + + +template< class objType, class keyType, int maxChildrenPerNode > +class idBTree { +public: + idBTree( void ); + ~idBTree( void ); + + void Init( void ); + void Shutdown( void ); + + idBTreeNode * Add( objType *object, keyType key ); // add an object to the tree + void Remove( idBTreeNode *node ); // remove an object node from the tree + + objType * Find( keyType key ) const; // find an object using the given key + objType * FindSmallestLargerEqual( keyType key ) const; // find an object with the smallest key larger equal the given key + objType * FindLargestSmallerEqual( keyType key ) const; // find an object with the largest key smaller equal the given key + + idBTreeNode * GetRoot( void ) const; // returns the root node of the tree + int GetNodeCount( void ) const; // returns the total number of nodes in the tree + idBTreeNode * GetNext( idBTreeNode *node ) const; // goes through all nodes of the tree + idBTreeNode * GetNextLeaf( idBTreeNode *node ) const; // goes through all leaf nodes of the tree + +private: + idBTreeNode * root; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc,128,MA_DEFAULT> nodeAllocator; +// RAVEN END + + idBTreeNode * AllocNode( void ); + void FreeNode( idBTreeNode *node ); + void SplitNode( idBTreeNode *node ); + idBTreeNode * MergeNodes( idBTreeNode *node1, idBTreeNode *node2 ); + + void CheckTree_r( idBTreeNode *node, int &numNodes ) const; + void CheckTree( void ) const; +}; + + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTree::idBTree( void ) { + assert( maxChildrenPerNode >= 4 ); + root = NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTree::~idBTree( void ) { + Shutdown(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Init( void ) { + root = AllocNode(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Shutdown( void ) { + nodeAllocator.Shutdown(); + root = NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::Add( objType *object, keyType key ) { + idBTreeNode *node, *child, *newNode; + + if ( root->numChildren >= maxChildrenPerNode ) { + newNode = AllocNode(); + newNode->key = root->key; + newNode->firstChild = root; + newNode->lastChild = root; + newNode->numChildren = 1; + root->parent = newNode; + SplitNode( root ); + root = newNode; + } + + newNode = AllocNode(); + newNode->key = key; + newNode->object = object; + + for ( node = root; node->firstChild != NULL; node = child ) { + + if ( key > node->key ) { + node->key = key; + } + + // find the first child with a key larger equal to the key of the new node + for( child = node->firstChild; child->next; child = child->next ) { + if ( key <= child->key ) { + break; + } + } + + if ( child->object ) { + + if ( key <= child->key ) { + // insert new node before child + if ( child->prev ) { + child->prev->next = newNode; + } else { + node->firstChild = newNode; + } + newNode->prev = child->prev; + newNode->next = child; + child->prev = newNode; + } else { + // insert new node after child + if ( child->next ) { + child->next->prev = newNode; + } else { + node->lastChild = newNode; + } + newNode->prev = child; + newNode->next = child->next; + child->next = newNode; + } + + newNode->parent = node; + node->numChildren++; + +#ifdef BTREE_CHECK + CheckTree(); +#endif + + return newNode; + } + + // make sure the child has room to store another node + if ( child->numChildren >= maxChildrenPerNode ) { + SplitNode( child ); + if ( key <= child->prev->key ) { + child = child->prev; + } + } + } + + // we only end up here if the root node is empty + newNode->parent = root; + root->key = key; + root->firstChild = newNode; + root->lastChild = newNode; + root->numChildren++; + +#ifdef BTREE_CHECK + CheckTree(); +#endif + + return newNode; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::Remove( idBTreeNode *node ) { + idBTreeNode *parent; + + assert( node->object != NULL ); + + // unlink the node from it's parent + if ( node->prev ) { + node->prev->next = node->next; + } else { + node->parent->firstChild = node->next; + } + if ( node->next ) { + node->next->prev = node->prev; + } else { + node->parent->lastChild = node->prev; + } + node->parent->numChildren--; + + // make sure there are no parent nodes with a single child + for ( parent = node->parent; parent != root && parent->numChildren <= 1; parent = parent->parent ) { + + if ( parent->next ) { + parent = MergeNodes( parent, parent->next ); + } else if ( parent->prev ) { + parent = MergeNodes( parent->prev, parent ); + } + + // a parent may not use a key higher than the key of it's last child + if ( parent->key > parent->lastChild->key ) { + parent->key = parent->lastChild->key; + } + + if ( parent->numChildren > maxChildrenPerNode ) { + SplitNode( parent ); + break; + } + } + for ( ; parent != NULL && parent->lastChild != NULL; parent = parent->parent ) { + // a parent may not use a key higher than the key of it's last child + if ( parent->key > parent->lastChild->key ) { + parent->key = parent->lastChild->key; + } + } + + // free the node + FreeNode( node ); + + // remove the root node if it has a single internal node as child + if ( root->numChildren == 1 && root->firstChild->object == NULL ) { + idBTreeNode *oldRoot = root; + root->firstChild->parent = NULL; + root = root->firstChild; + FreeNode( oldRoot ); + } + +#ifdef BTREE_CHECK + CheckTree(); +#endif +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::Find( keyType key ) const { + idBTreeNode *node; + + for ( node = root->firstChild; node != NULL; node = node->firstChild ) { + while( node->next ) { + if ( node->key >= key ) { + break; + } + node = node->next; + } + if ( node->object ) { + if ( node->key == key ) { + return node->object; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::FindSmallestLargerEqual( keyType key ) const { + idBTreeNode *node; + + for ( node = root->firstChild; node != NULL; node = node->firstChild ) { + while( node->next ) { + if ( node->key >= key ) { + break; + } + node = node->next; + } + if ( node->object ) { + if ( node->key >= key ) { + return node->object; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE objType *idBTree::FindLargestSmallerEqual( keyType key ) const { + idBTreeNode *node; + + for ( node = root->lastChild; node != NULL; node = node->lastChild ) { + while( node->prev ) { + if ( node->key <= key ) { + break; + } + node = node->prev; + } + if ( node->object ) { + if ( node->key <= key ) { + return node->object; + } else { + return NULL; + } + } + } + return NULL; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetRoot( void ) const { + return root; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE int idBTree::GetNodeCount( void ) const { + return nodeAllocator.GetAllocCount(); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetNext( idBTreeNode *node ) const { + if ( node->firstChild ) { + return node->firstChild; + } else { + while( node && node->next == NULL ) { + node = node->parent; + } + return node; + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::GetNextLeaf( idBTreeNode *node ) const { + if ( node->firstChild ) { + while ( node->firstChild ) { + node = node->firstChild; + } + return node; + } else { + while( node && node->next == NULL ) { + node = node->parent; + } + if ( node ) { + node = node->next; + while ( node->firstChild ) { + node = node->firstChild; + } + return node; + } else { + return NULL; + } + } +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::AllocNode( void ) { + idBTreeNode *node = nodeAllocator.Alloc(); + node->key = 0; + node->parent = NULL; + node->next = NULL; + node->prev = NULL; + node->numChildren = 0; + node->firstChild = NULL; + node->lastChild = NULL; + node->object = NULL; + return node; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::FreeNode( idBTreeNode *node ) { + nodeAllocator.Free( node ); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::SplitNode( idBTreeNode *node ) { + int i; + idBTreeNode *child, *newNode; + + // allocate a new node + newNode = AllocNode(); + newNode->parent = node->parent; + + // divide the children over the two nodes + child = node->firstChild; + child->parent = newNode; + for ( i = 3; i < node->numChildren; i += 2 ) { + child = child->next; + child->parent = newNode; + } + + newNode->key = child->key; + newNode->numChildren = node->numChildren / 2; + newNode->firstChild = node->firstChild; + newNode->lastChild = child; + + node->numChildren -= newNode->numChildren; + node->firstChild = child->next; + + child->next->prev = NULL; + child->next = NULL; + + // add the new child to the parent before the split node + assert( node->parent->numChildren < maxChildrenPerNode ); + + if ( node->prev ) { + node->prev->next = newNode; + } else { + node->parent->firstChild = newNode; + } + newNode->prev = node->prev; + newNode->next = node; + node->prev = newNode; + + node->parent->numChildren++; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE idBTreeNode *idBTree::MergeNodes( idBTreeNode *node1, idBTreeNode *node2 ) { + idBTreeNode *child; + + assert( node1->parent == node2->parent ); + assert( node1->next == node2 && node2->prev == node1 ); + assert( node1->object == NULL && node2->object == NULL ); + assert( node1->numChildren >= 1 && node2->numChildren >= 1 ); + + for ( child = node1->firstChild; child->next; child = child->next ) { + child->parent = node2; + } + child->parent = node2; + child->next = node2->firstChild; + node2->firstChild->prev = child; + node2->firstChild = node1->firstChild; + node2->numChildren += node1->numChildren; + + // unlink the first node from the parent + if ( node1->prev ) { + node1->prev->next = node2; + } else { + node1->parent->firstChild = node2; + } + node2->prev = node1->prev; + node2->parent->numChildren--; + + FreeNode( node1 ); + + return node2; +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::CheckTree_r( idBTreeNode *node, int &numNodes ) const { + int numChildren; + idBTreeNode *child; + + numNodes++; + + // the root node may have zero children and leaf nodes always have zero children, all other nodes should have at least 2 and at most maxChildrenPerNode children + assert( ( node == root ) || ( node->object != NULL && node->numChildren == 0 ) || ( node->numChildren >= 2 && node->numChildren <= maxChildrenPerNode ) ); + // the key of a node may never be larger than the key of it's last child + assert( ( node->lastChild == NULL ) || ( node->key <= node->lastChild->key ) ); + + numChildren = 0; + for ( child = node->firstChild; child; child = child->next ) { + numChildren++; + // make sure the children are properly linked + if ( child->prev == NULL ) { + assert( node->firstChild == child ); + } else { + assert( child->prev->next == child ); + } + if ( child->next == NULL ) { + assert( node->lastChild == child ); + } else { + assert( child->next->prev == child ); + } + // recurse down the tree + CheckTree_r( child, numNodes ); + } + // the number of children should equal the number of linked children + assert( numChildren == node->numChildren ); +} + +template< class objType, class keyType, int maxChildrenPerNode > +ID_INLINE void idBTree::CheckTree( void ) const { + int numNodes = 0; + idBTreeNode *node, *lastNode; + + CheckTree_r( root, numNodes ); + + // the number of nodes in the tree should equal the number of allocated nodes + assert( numNodes == nodeAllocator.GetAllocCount() ); + + // all the leaf nodes should be ordered + lastNode = GetNextLeaf( GetRoot() ); + if ( lastNode ) { + for ( node = GetNextLeaf( lastNode ); node; lastNode = node, node = GetNextLeaf( node ) ) { + assert( lastNode->key <= node->key ); + } + } +} + +#endif /* !__BTREE_H__ */ diff --git a/src/idlib/containers/BinSearch.h b/src/idlib/containers/BinSearch.h new file mode 100644 index 0000000..71f5ccf --- /dev/null +++ b/src/idlib/containers/BinSearch.h @@ -0,0 +1,134 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BINSEARCH_H__ +#define __BINSEARCH_H__ + +/* +=============================================================================== + + Binary Search templates + + The array elements have to be ordered in increasing order. + +=============================================================================== +*/ + +/* +==================== +idBinSearch_GreaterEqual + + Finds the last array element which is smaller than the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_Less( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] < value ) { + offset += mid; + } + len -= mid; + } + return offset; +} + +/* +==================== +idBinSearch_GreaterEqual + + Finds the last array element which is smaller than or equal to the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_LessEqual( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] <= value ) { + offset += mid; + } + len -= mid; + } + return offset; +} + +/* +==================== +idBinSearch_Greater + + Finds the first array element which is greater than the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_Greater( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + int res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] > value ) { + res = 0; + } else { + offset += mid; + res = 1; + } + len -= mid; + } + return offset+res; +} + +/* +==================== +idBinSearch_GreaterEqual + + Finds the first array element which is greater than or equal to the given value. +==================== +*/ +template< class type > +ID_INLINE int idBinSearch_GreaterEqual( const type *array, const int arraySize, const type &value ) { + int len = arraySize; + int mid = len; + int offset = 0; + int res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( array[offset+mid] >= value ) { + res = 0; + } else { + offset += mid; + res = 1; + } + len -= mid; + } + return offset+res; +} + +#endif /* !__BINSEARCH_H__ */ diff --git a/src/idlib/containers/HashIndex.cpp b/src/idlib/containers/HashIndex.cpp new file mode 100644 index 0000000..dae8e5a --- /dev/null +++ b/src/idlib/containers/HashIndex.cpp @@ -0,0 +1,233 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +int idHashIndex::INVALID_INDEX[1] = { -1 }; + +/* +================ +idHashIndex::Init +================ +*/ +void idHashIndex::Init( const int initialHashSize, const int initialIndexSize ) { + assert( idMath::IsPowerOfTwo( initialHashSize ) ); + + hashSize = initialHashSize; + hash = INVALID_INDEX; + indexSize = initialIndexSize; + indexChain = INVALID_INDEX; + granularity = DEFAULT_HASH_GRANULARITY; + hashMask = hashSize - 1; + lookupMask = 0; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + allocatorHeap = 0; +#endif +// RAVEN END +} + +/* +================ +idHashIndex::Allocate +================ +*/ +void idHashIndex::Allocate( const int newHashSize, const int newIndexSize ) { + assert( idMath::IsPowerOfTwo( newHashSize ) ); + + Free(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + hashSize = newHashSize; + hash = new int[hashSize]; + memset( hash, 0xff, hashSize * sizeof( hash[0] ) ); + indexSize = newIndexSize; + indexChain = new int[indexSize]; + memset( indexChain, 0xff, indexSize * sizeof( indexChain[0] ) ); + hashMask = hashSize - 1; + lookupMask = -1; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END +} + +/* +================ +idHashIndex::Free +================ +*/ +void idHashIndex::Free( void ) { + if ( hash != INVALID_INDEX ) { + delete[] hash; + hash = INVALID_INDEX; + } + if ( indexChain != INVALID_INDEX ) { + delete[] indexChain; + indexChain = INVALID_INDEX; + } + lookupMask = 0; +} + +/* +================ +idHashIndex::ResizeIndex +================ +*/ +void idHashIndex::ResizeIndex( const int newIndexSize ) { + int *oldIndexChain, mod, newSize; + + if ( newIndexSize <= indexSize ) { + return; + } + + mod = newIndexSize % granularity; + if ( !mod ) { + newSize = newIndexSize; + } else { + newSize = newIndexSize + granularity - mod; + } + + if ( indexChain == INVALID_INDEX ) { + indexSize = newSize; + return; + } + + oldIndexChain = indexChain; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + indexChain = new int[newSize]; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + memcpy( indexChain, oldIndexChain, indexSize * sizeof(int) ); + memset( indexChain + indexSize, 0xff, (newSize - indexSize) * sizeof(int) ); + delete[] oldIndexChain; + indexSize = newSize; +} + +/* +================ +idHashIndex::GetSpread +================ +*/ +int idHashIndex::GetSpread( void ) const { + int i, index, totalItems, *numHashItems, average, error, e; + + if ( hash == INVALID_INDEX ) { + return 100; + } + + totalItems = 0; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + numHashItems = new int[hashSize]; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + for ( i = 0; i < hashSize; i++ ) { + numHashItems[i] = 0; + for ( index = hash[i]; index >= 0; index = indexChain[index] ) { + numHashItems[i]++; + } + totalItems += numHashItems[i]; + } + // if no items in hash + if ( totalItems <= 1 ) { + delete[] numHashItems; + return 100; + } + average = totalItems / hashSize; + error = 0; + for ( i = 0; i < hashSize; i++ ) { + e = abs( numHashItems[i] - average ); + if ( e > 1 ) { + error += e - 1; + } + } + delete[] numHashItems; + return 100 - (error * 100 / totalItems); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +void idHashIndex::SetAllocatorHeap ( rvHeap* heap ) +{ + assert(heap); + allocatorHeap = heap; +} +#endif +// RAVEN END diff --git a/src/idlib/containers/HashIndex.h b/src/idlib/containers/HashIndex.h new file mode 100644 index 0000000..0cfaad0 --- /dev/null +++ b/src/idlib/containers/HashIndex.h @@ -0,0 +1,439 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __HASHINDEX_H__ +#define __HASHINDEX_H__ + +/* +=============================================================================== + + Fast hash table for indexes and arrays. + Does not allocate memory until the first key/index pair is added. + +=============================================================================== +*/ + +#define DEFAULT_HASH_SIZE 1024 +#define DEFAULT_HASH_GRANULARITY 1024 + +class idHashIndex { +public: + idHashIndex( void ); + idHashIndex( const int initialHashSize, const int initialIndexSize ); + ~idHashIndex( void ); + + // returns total size of allocated memory + size_t Allocated( void ) const; + // returns total size of allocated memory including size of hash index type + size_t Size( void ) const; + + idHashIndex & operator=( const idHashIndex &other ); + // add an index to the hash, assumes the index has not yet been added to the hash + void Add( const int key, const int index ); + // remove an index from the hash + void Remove( const int key, const int index ); + // get the first index from the hash, returns -1 if empty hash entry + int First( const int key ) const; + // get the next index from the hash, returns -1 if at the end of the hash chain + int Next( const int index ) const; + // insert an entry into the index and add it to the hash, increasing all indexes >= index + void InsertIndex( const int key, const int index ); + // remove an entry from the index and remove it from the hash, decreasing all indexes >= index + void RemoveIndex( const int key, const int index ); + // clear the hash + void Clear( void ); + // clear and resize + void Clear( const int newHashSize, const int newIndexSize ); + // free allocated memory + void Free( void ); + // get size of hash table + int GetHashSize( void ) const; + // get size of the index + int GetIndexSize( void ) const; + // set granularity + void SetGranularity( const int newGranularity ); + // force resizing the index, current hash table stays intact + void ResizeIndex( const int newIndexSize ); + // returns number in the range [0-100] representing the spread over the hash table + int GetSpread( void ) const; + // returns a key for a string + int GenerateKey( const char *string, bool caseSensitive = true ) const; + // returns a key for a vector + int GenerateKey( const idVec3 &v ) const; + // returns a key for two integers + int GenerateKey( const int n1, const int n2 ) const; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void SetAllocatorHeap ( rvHeap* heap ); // set the heap used for all allocations +#endif +// RAVEN END + +private: + int hashSize; + int * hash; + int indexSize; + int * indexChain; + int granularity; + int hashMask; + int lookupMask; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rvHeap* allocatorHeap; +#endif +// RAVEN END + + static int INVALID_INDEX[1]; + + void Init( const int initialHashSize, const int initialIndexSize ); + void Allocate( const int newHashSize, const int newIndexSize ); +}; + +/* +================ +idHashIndex::idHashIndex +================ +*/ +ID_INLINE idHashIndex::idHashIndex( void ) { + Init( DEFAULT_HASH_SIZE, DEFAULT_HASH_SIZE ); +} + +/* +================ +idHashIndex::idHashIndex +================ +*/ +ID_INLINE idHashIndex::idHashIndex( const int initialHashSize, const int initialIndexSize ) { + Init( initialHashSize, initialIndexSize ); +} + +/* +================ +idHashIndex::~idHashIndex +================ +*/ +ID_INLINE idHashIndex::~idHashIndex( void ) { + Free(); +} + +/* +================ +idHashIndex::Allocated +================ +*/ +ID_INLINE size_t idHashIndex::Allocated( void ) const { + return hashSize * sizeof( int ) + indexSize * sizeof( int ); +} + +/* +================ +idHashIndex::Size +================ +*/ +ID_INLINE size_t idHashIndex::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +/* +================ +idHashIndex::operator= +================ +*/ +ID_INLINE idHashIndex &idHashIndex::operator=( const idHashIndex &other ) { + granularity = other.granularity; + hashMask = other.hashMask; + lookupMask = other.lookupMask; + + if ( other.lookupMask == 0 ) { + hashSize = other.hashSize; + indexSize = other.indexSize; + Free(); + } + else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + } +#endif +// RAVEN END + + if ( other.hashSize != hashSize || hash == INVALID_INDEX ) { + if ( hash != INVALID_INDEX ) { + delete[] hash; + } + hashSize = other.hashSize; + hash = new int[hashSize]; + } + if ( other.indexSize != indexSize || indexChain == INVALID_INDEX ) { + if ( indexChain != INVALID_INDEX ) { + delete[] indexChain; + } + indexSize = other.indexSize; + indexChain = new int[indexSize]; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + if(allocatorHeap) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + memcpy( hash, other.hash, hashSize * sizeof( hash[0] ) ); + memcpy( indexChain, other.indexChain, indexSize * sizeof( indexChain[0] ) ); + } + + return *this; +} + +/* +================ +idHashIndex::Add +================ +*/ +ID_INLINE void idHashIndex::Add( const int key, const int index ) { + int h; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + + assert( index >= 0 ); + if ( hash == INVALID_INDEX ) { + Allocate( hashSize, index >= indexSize ? index + 1 : indexSize ); + } + else if ( index >= indexSize ) { + ResizeIndex( index + 1 ); + } + h = key & hashMask; + indexChain[index] = hash[h]; + hash[h] = index; +} + +/* +================ +idHashIndex::Remove +================ +*/ +ID_INLINE void idHashIndex::Remove( const int key, const int index ) { + int k = key & hashMask; + + if ( hash == INVALID_INDEX ) { + return; + } + if ( hash[k] == index ) { + hash[k] = indexChain[index]; + } + else { + for ( int i = hash[k]; i != -1; i = indexChain[i] ) { + if ( indexChain[i] == index ) { + indexChain[i] = indexChain[index]; + break; + } + } + } + indexChain[index] = -1; +} + +/* +================ +idHashIndex::First +================ +*/ +ID_INLINE int idHashIndex::First( const int key ) const { + return hash[key & hashMask & lookupMask]; +} + +/* +================ +idHashIndex::Next +================ +*/ +ID_INLINE int idHashIndex::Next( const int index ) const { + assert( index >= 0 && index < indexSize ); + return indexChain[index & lookupMask]; +} + +/* +================ +idHashIndex::InsertIndex +================ +*/ +ID_INLINE void idHashIndex::InsertIndex( const int key, const int index ) { + int i, max; + + if ( hash != INVALID_INDEX ) { + max = index; + for ( i = 0; i < hashSize; i++ ) { + if ( hash[i] >= index ) { + hash[i]++; + if ( hash[i] > max ) { + max = hash[i]; + } + } + } + for ( i = 0; i < indexSize; i++ ) { + if ( indexChain[i] >= index ) { + indexChain[i]++; + if ( indexChain[i] > max ) { + max = indexChain[i]; + } + } + } + if ( max >= indexSize ) { + ResizeIndex( max + 1 ); + } + for ( i = max; i > index; i-- ) { + indexChain[i] = indexChain[i-1]; + } + indexChain[index] = -1; + } + Add( key, index ); +} + +/* +================ +idHashIndex::RemoveIndex +================ +*/ +ID_INLINE void idHashIndex::RemoveIndex( const int key, const int index ) { + int i, max; + + Remove( key, index ); + if ( hash != INVALID_INDEX ) { + max = index; + for ( i = 0; i < hashSize; i++ ) { + if ( hash[i] >= index ) { + if ( hash[i] > max ) { + max = hash[i]; + } + hash[i]--; + } + } + for ( i = 0; i < indexSize; i++ ) { + if ( indexChain[i] >= index ) { + if ( indexChain[i] > max ) { + max = indexChain[i]; + } + indexChain[i]--; + } + } + for ( i = index; i < max; i++ ) { + indexChain[i] = indexChain[i+1]; + } + indexChain[max] = -1; + } +} + +/* +================ +idHashIndex::Clear +================ +*/ +ID_INLINE void idHashIndex::Clear( void ) { + // only clear the hash table because clearing the indexChain is not really needed + if ( hash != INVALID_INDEX ) { + memset( hash, 0xff, hashSize * sizeof( hash[0] ) ); + } +} + +/* +================ +idHashIndex::Clear +================ +*/ +ID_INLINE void idHashIndex::Clear( const int newHashSize, const int newIndexSize ) { + Free(); + hashSize = newHashSize; + indexSize = newIndexSize; +} + +/* +================ +idHashIndex::GetHashSize +================ +*/ +ID_INLINE int idHashIndex::GetHashSize( void ) const { + return hashSize; +} + +/* +================ +idHashIndex::GetIndexSize +================ +*/ +ID_INLINE int idHashIndex::GetIndexSize( void ) const { + return indexSize; +} + +/* +================ +idHashIndex::SetGranularity +================ +*/ +ID_INLINE void idHashIndex::SetGranularity( const int newGranularity ) { + assert( newGranularity > 0 ); + granularity = newGranularity; +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const char *string, bool caseSensitive ) const { + if ( caseSensitive ) { + return ( idStr::Hash( string ) & hashMask ); + } else { + return ( idStr::IHash( string ) & hashMask ); + } +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const idVec3 &v ) const { + return ( (((int) v[0]) + ((int) v[1]) + ((int) v[2])) & hashMask ); +// return ( ( idMath::FtoiFast( v[0] ) + idMath::FtoiFast( v[1] ) + idMath::FtoiFast( v[2] ) ) & hashMask ); +} + +/* +================ +idHashIndex::GenerateKey +================ +*/ +ID_INLINE int idHashIndex::GenerateKey( const int n1, const int n2 ) const { + return ( ( n1 + n2 ) & hashMask ); +} + +#endif /* !__HASHINDEX_H__ */ diff --git a/src/idlib/containers/HashTable.h b/src/idlib/containers/HashTable.h new file mode 100644 index 0000000..7e35824 --- /dev/null +++ b/src/idlib/containers/HashTable.h @@ -0,0 +1,444 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __HASHTABLE_H__ +#define __HASHTABLE_H__ + +/* +=============================================================================== + + General hash table. Slower than idHashIndex but it can also be used for + linked lists and other data structures than just indexes or arrays. + +=============================================================================== +*/ + +template< class Type > +class idHashTable { +public: + idHashTable( int newtablesize = 256 ); + idHashTable( const idHashTable &map ); + ~idHashTable( void ); + + // returns total size of allocated memory + size_t Allocated( void ) const; + // returns total size of allocated memory including size of hash table type + size_t Size( void ) const; + + void Set( const char *key, Type &value ); + bool Get( const char *key, Type **value = NULL ) const; + bool Remove( const char *key ); + + void Clear( void ); + void DeleteContents( void ); + + // the entire contents can be itterated over, but note that the + // exact index for a given element may change when new elements are added + int Num( void ) const; + Type * GetIndex( int index ) const; + + int GetSpread( void ) const; + +private: + struct hashnode_s { + idStr key; + Type value; + hashnode_s *next; + + hashnode_s( const idStr &k, Type v, hashnode_s *n ) : key( k ), value( v ), next( n ) {}; + hashnode_s( const char *k, Type v, hashnode_s *n ) : key( k ), value( v ), next( n ) {}; + }; + + hashnode_s ** heads; + + int tablesize; + int numentries; + int tablesizemask; + + int GetHash( const char *key ) const; +}; + +/* +================ +idHashTable::idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::idHashTable( int newtablesize ) { + + assert( idMath::IsPowerOfTwo( newtablesize ) ); + + tablesize = newtablesize; + assert( tablesize > 0 ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + heads = new hashnode_s *[ tablesize ]; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + memset( heads, 0, sizeof( *heads ) * tablesize ); + + numentries = 0; + + tablesizemask = tablesize - 1; +} + +/* +================ +idHashTable::idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::idHashTable( const idHashTable &map ) { + int i; + hashnode_s *node; + hashnode_s **prev; + + assert( map.tablesize > 0 ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + tablesize = map.tablesize; + heads = new hashnode_s *[ tablesize ]; + numentries = map.numentries; + tablesizemask = map.tablesizemask; + + for( i = 0; i < tablesize; i++ ) { + if ( !map.heads[ i ] ) { + heads[ i ] = NULL; + continue; + } + + prev = &heads[ i ]; + for( node = map.heads[ i ]; node != NULL; node = node->next ) { + *prev = new hashnode_s( node->key, node->value, NULL ); + prev = &( *prev )->next; + } + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END +} + +/* +================ +idHashTable::~idHashTable +================ +*/ +template< class Type > +ID_INLINE idHashTable::~idHashTable( void ) { + Clear(); + delete[] heads; +} + +/* +================ +idHashTable::Allocated +================ +*/ +template< class Type > +ID_INLINE size_t idHashTable::Allocated( void ) const { + return sizeof( heads ) * tablesize + sizeof( *heads ) * numentries; +} + +/* +================ +idHashTable::Size +================ +*/ +template< class Type > +ID_INLINE size_t idHashTable::Size( void ) const { + return sizeof( idHashTable ) + sizeof( heads ) * tablesize + sizeof( *heads ) * numentries; +} + +/* +================ +idHashTable::GetHash +================ +*/ +template< class Type > +ID_INLINE int idHashTable::GetHash( const char *key ) const { + return ( idStr::Hash( key ) & tablesizemask ); +} + +/* +================ +idHashTable::Set +================ +*/ +template< class Type > +ID_INLINE void idHashTable::Set( const char *key, Type &value ) { + hashnode_s *node, **nextPtr; + int hash, s; + + hash = GetHash( key ); + for( nextPtr = &(heads[hash]), node = *nextPtr; node != NULL; nextPtr = &(node->next), node = *nextPtr ) { + s = node->key.Cmp( key ); + if ( s == 0 ) { + node->value = value; + return; + } + if ( s > 0 ) { + break; + } + } + + numentries++; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + *nextPtr = new hashnode_s( key, value, heads[ hash ] ); + (*nextPtr)->next = node; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END +} + +/* +================ +idHashTable::Get +================ +*/ +template< class Type > +ID_INLINE bool idHashTable::Get( const char *key, Type **value ) const { + hashnode_s *node; + int hash, s; + + hash = GetHash( key ); + for( node = heads[ hash ]; node != NULL; node = node->next ) { + s = node->key.Cmp( key ); + if ( s == 0 ) { + if ( value ) { + *value = &node->value; + } + return true; + } + if ( s > 0 ) { + break; + } + } + + if ( value ) { + *value = NULL; + } + + return false; +} + +/* +================ +idHashTable::GetIndex + +the entire contents can be itterated over, but note that the +exact index for a given element may change when new elements are added +================ +*/ +template< class Type > +ID_INLINE Type *idHashTable::GetIndex( int index ) const { + hashnode_s *node; + int count; + int i; + + if ( ( index < 0 ) || ( index > numentries ) ) { + assert( 0 ); + return NULL; + } + + count = 0; + for( i = 0; i < tablesize; i++ ) { + for( node = heads[ i ]; node != NULL; node = node->next ) { + if ( count == index ) { + return &node->value; + } + count++; + } + } + + return NULL; +} + +/* +================ +idHashTable::Remove +================ +*/ +template< class Type > +ID_INLINE bool idHashTable::Remove( const char *key ) { + hashnode_s **head; + hashnode_s *node; + hashnode_s *prev; + int hash; + + hash = GetHash( key ); + head = &heads[ hash ]; + if ( *head ) { + for( prev = NULL, node = *head; node != NULL; prev = node, node = node->next ) { + if ( node->key == key ) { + if ( prev ) { + prev->next = node->next; + } else { + *head = node->next; + } + + delete node; + numentries--; + return true; + } + } + } + + return false; +} + +/* +================ +idHashTable::Clear +================ +*/ +template< class Type > +ID_INLINE void idHashTable::Clear( void ) { + int i; + hashnode_s *node; + hashnode_s *next; + + for( i = 0; i < tablesize; i++ ) { + next = heads[ i ]; + while( next != NULL ) { + node = next; + next = next->next; + delete node; + } + + heads[ i ] = NULL; + } + + numentries = 0; +} + +/* +================ +idHashTable::DeleteContents +================ +*/ +template< class Type > +ID_INLINE void idHashTable::DeleteContents( void ) { + int i; + hashnode_s *node; + hashnode_s *next; + + for( i = 0; i < tablesize; i++ ) { + next = heads[ i ]; + while( next != NULL ) { + node = next; + next = next->next; + delete node->value; + delete node; + } + + heads[ i ] = NULL; + } + + numentries = 0; +} + +/* +================ +idHashTable::Num +================ +*/ +template< class Type > +ID_INLINE int idHashTable::Num( void ) const { + return numentries; +} + +#if !defined(__GNUC__) || __GNUC__ < 4 +/* +================ +idHashTable::GetSpread +================ +*/ +template< class Type > +int idHashTable::GetSpread( void ) const { + int i, average, error, e, numItems; + hashnode_s *node; + + // if no items in hash + if ( !numentries ) { + return 100; + } + average = numentries / tablesize; + error = 0; + for ( i = 0; i < tablesize; i++ ) { + numItems = 0; + for( node = heads[ i ]; node != NULL; node = node->next ) { + numItems++; + } + e = abs( numItems - average ); + if ( e > 1 ) { + error += e - 1; + } + } + return 100 - (error * 100 / numentries); +} +#endif + +#endif /* !__HASHTABLE_H__ */ diff --git a/src/idlib/containers/Hierarchy.h b/src/idlib/containers/Hierarchy.h new file mode 100644 index 0000000..ac773f7 --- /dev/null +++ b/src/idlib/containers/Hierarchy.h @@ -0,0 +1,358 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __HIERARCHY_H__ +#define __HIERARCHY_H__ + +/* +============================================================================== + + idHierarchy + +============================================================================== +*/ + +template< class type > +class idHierarchy { +public: + + idHierarchy(); + ~idHierarchy(); + + void SetOwner( type *object ); + type * Owner( void ) const; + void ParentTo( idHierarchy &node ); + void MakeSiblingAfter( idHierarchy &node ); + bool ParentedBy( const idHierarchy &node ) const; + void RemoveFromParent( void ); + void RemoveFromHierarchy( void ); + + type * GetParent( void ) const; // parent of this node + type * GetChild( void ) const; // first child of this node + type * GetSibling( void ) const; // next node with the same parent + type * GetPriorSibling( void ) const; // previous node with the same parent + type * GetNext( void ) const; // goes through all nodes of the hierarchy + type * GetNextLeaf( void ) const; // goes through all leaf nodes of the hierarchy + +private: + idHierarchy * parent; + idHierarchy * sibling; + idHierarchy * child; + type * owner; + + idHierarchy *GetPriorSiblingNode( void ) const; // previous node with the same parent +}; + +/* +================ +idHierarchy::idHierarchy +================ +*/ +template< class type > +idHierarchy::idHierarchy() { + owner = NULL; + parent = NULL; + sibling = NULL; + child = NULL; +} + +/* +================ +idHierarchy::~idHierarchy +================ +*/ +template< class type > +idHierarchy::~idHierarchy() { + RemoveFromHierarchy(); +} + +/* +================ +idHierarchy::Owner + +Gets the object that is associated with this node. +================ +*/ +template< class type > +type *idHierarchy::Owner( void ) const { + return owner; +} + +/* +================ +idHierarchy::SetOwner + +Sets the object that this node is associated with. +================ +*/ +template< class type > +void idHierarchy::SetOwner( type *object ) { + owner = object; +} + +/* +================ +idHierarchy::ParentedBy +================ +*/ +template< class type > +bool idHierarchy::ParentedBy( const idHierarchy &node ) const { + if ( parent == &node ) { + return true; + } else if ( parent ) { + return parent->ParentedBy( node ); + } + return false; +} + +/* +================ +idHierarchy::ParentTo + +Makes the given node the parent. +================ +*/ +template< class type > +void idHierarchy::ParentTo( idHierarchy &node ) { + RemoveFromParent(); + + parent = &node; + sibling = node.child; + node.child = this; +} + +/* +================ +idHierarchy::MakeSiblingAfter + +Makes the given node a sibling after the passed in node. +================ +*/ +template< class type > +void idHierarchy::MakeSiblingAfter( idHierarchy &node ) { + RemoveFromParent(); + parent = node.parent; + sibling = node.sibling; + node.sibling = this; +} + +/* +================ +idHierarchy::RemoveFromParent +================ +*/ +template< class type > +void idHierarchy::RemoveFromParent( void ) { + idHierarchy *prev; + + if ( parent ) { + prev = GetPriorSiblingNode(); + if ( prev ) { + prev->sibling = sibling; + } else { + parent->child = sibling; + } + } + + parent = NULL; + sibling = NULL; +} + +/* +================ +idHierarchy::RemoveFromHierarchy + +Removes the node from the hierarchy and adds it's children to the parent. +================ +*/ +template< class type > +void idHierarchy::RemoveFromHierarchy( void ) { + idHierarchy *parentNode; + idHierarchy *node; + + parentNode = parent; + RemoveFromParent(); + + if ( parentNode ) { + while( child ) { + node = child; + node->RemoveFromParent(); + node->ParentTo( *parentNode ); + } + } else { + while( child ) { + child->RemoveFromParent(); + } + } +} + +/* +================ +idHierarchy::GetParent +================ +*/ +template< class type > +type *idHierarchy::GetParent( void ) const { + if ( parent ) { + return parent->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetChild +================ +*/ +template< class type > +type *idHierarchy::GetChild( void ) const { + if ( child ) { + return child->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetSibling +================ +*/ +template< class type > +type *idHierarchy::GetSibling( void ) const { + if ( sibling ) { + return sibling->owner; + } + return NULL; +} + +/* +================ +idHierarchy::GetPriorSiblingNode + +Returns NULL if no parent, or if it is the first child. +================ +*/ +template< class type > +idHierarchy *idHierarchy::GetPriorSiblingNode( void ) const { + if ( !parent || ( parent->child == this ) ) { + return NULL; + } + + idHierarchy *prev; + idHierarchy *node; + + node = parent->child; + prev = NULL; + while( ( node != this ) && ( node != NULL ) ) { + prev = node; + node = node->sibling; + } + + if ( node != this ) { + idLib::Error( "idHierarchy::GetPriorSibling: could not find node in parent's list of children" ); + } + + return prev; +} + +/* +================ +idHierarchy::GetPriorSibling + +Returns NULL if no parent, or if it is the first child. +================ +*/ +template< class type > +type *idHierarchy::GetPriorSibling( void ) const { + idHierarchy *prior; + + prior = GetPriorSiblingNode(); + if ( prior ) { + return prior->owner; + } + + return NULL; +} + +/* +================ +idHierarchy::GetNext + +Goes through all nodes of the hierarchy. +================ +*/ +template< class type > +type *idHierarchy::GetNext( void ) const { + const idHierarchy *node; + + if ( child ) { + return child->owner; + } else { + node = this; + while( node && node->sibling == NULL ) { + node = node->parent; + } + if ( node ) { + return node->sibling->owner; + } else { + return NULL; + } + } +} + +/* +================ +idHierarchy::GetNextLeaf + +Goes through all leaf nodes of the hierarchy. +================ +*/ +template< class type > +type *idHierarchy::GetNextLeaf( void ) const { + const idHierarchy *node; + + if ( child ) { + node = child; + while ( node->child ) { + node = node->child; + } + return node->owner; + } else { + node = this; + while( node && node->sibling == NULL ) { + node = node->parent; + } + if ( node ) { + node = node->sibling; + while ( node->child ) { + node = node->child; + } + return node->owner; + } else { + return NULL; + } + } +} + +#endif /* !__HIERARCHY_H__ */ diff --git a/src/idlib/containers/LinkList.h b/src/idlib/containers/LinkList.h new file mode 100644 index 0000000..5fbc494 --- /dev/null +++ b/src/idlib/containers/LinkList.h @@ -0,0 +1,357 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LINKLIST_H__ +#define __LINKLIST_H__ + +/* +============================================================================== + +idLinkList + +Circular linked list template + +============================================================================== +*/ + +template< class type > +class idLinkList { +public: + idLinkList(); + ~idLinkList(); + + bool IsListEmpty( void ) const; + bool InList( void ) const; + int Num( void ) const; + void Clear( void ); + + void InsertBefore( idLinkList &node ); + void InsertAfter( idLinkList &node ); + void AddToEnd( idLinkList &node ); + void AddToFront( idLinkList &node ); + + void Remove( void ); + + type * Next( void ) const; + type * Prev( void ) const; + + type * Owner( void ) const; + void SetOwner( type *object ); + + idLinkList * ListHead( void ) const; + idLinkList * NextNode( void ) const; + idLinkList * PrevNode( void ) const; + +private: + idLinkList * head; + idLinkList * next; + idLinkList * prev; + type * owner; +}; + +/* +================ +idLinkList::idLinkList + +Node is initialized to be the head of an empty list +================ +*/ +template< class type > +idLinkList::idLinkList() { + owner = NULL; + head = this; + next = this; + prev = this; +} + +/* +================ +idLinkList::~idLinkList + +Removes the node from the list, or if it's the head of a list, removes +all the nodes from the list. +================ +*/ +template< class type > +idLinkList::~idLinkList() { + Clear(); +} + +/* +================ +idLinkList::IsListEmpty + +Returns true if the list is empty. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE bool idLinkList::IsListEmpty( void ) const { +// RAVEN END + return head->next == head; +} + +/* +================ +idLinkList::InList + +Returns true if the node is in a list. If called on the head of a list, will always return false. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE bool idLinkList::InList( void ) const { +// RAVEN END + return head != this; +} + +/* +================ +idLinkList::Num + +Returns the number of nodes in the list. +================ +*/ +template< class type > +int idLinkList::Num( void ) const { + idLinkList *node; + int num; + + num = 0; + for( node = head->next; node != head; node = node->next ) { + num++; + } + + return num; +} + +/* +================ +idLinkList::Clear + +If node is the head of the list, clears the list. Otherwise it just removes the node from the list. +================ +*/ +template< class type > +void idLinkList::Clear( void ) { + if ( head == this ) { + while( next != this ) { + next->Remove(); + } + } else { + Remove(); + } +} + +/* +================ +idLinkList::Remove + +Removes node from list +================ +*/ +template< class type > +void idLinkList::Remove( void ) { + prev->next = next; + next->prev = prev; + + next = this; + prev = this; + head = this; +} + +/* +================ +idLinkList::InsertBefore + +Places the node before the existing node in the list. If the existing node is the head, +then the new node is placed at the end of the list. +================ +*/ +template< class type > +void idLinkList::InsertBefore( idLinkList &node ) { + Remove(); + + next = &node; + prev = node.prev; + node.prev = this; + prev->next = this; + head = node.head; +} + +/* +================ +idLinkList::InsertAfter + +Places the node after the existing node in the list. If the existing node is the head, +then the new node is placed at the beginning of the list. +================ +*/ +template< class type > +void idLinkList::InsertAfter( idLinkList &node ) { + Remove(); + + prev = &node; + next = node.next; + node.next = this; + next->prev = this; + head = node.head; +} + +/* +================ +idLinkList::AddToEnd + +Adds node at the end of the list +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE void idLinkList::AddToEnd( idLinkList &node ) { +// RAVEN END + InsertBefore( *node.head ); +} + +/* +================ +idLinkList::AddToFront + +Adds node at the beginning of the list +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE void idLinkList::AddToFront( idLinkList &node ) { +// RAVEN END + InsertAfter( *node.head ); +} + +/* +================ +idLinkList::ListHead + +Returns the head of the list. If the node isn't in a list, it returns +a pointer to itself. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE idLinkList *idLinkList::ListHead( void ) const { +// RAVEN END + return head; +} + +/* +================ +idLinkList::Next + +Returns the next object in the list, or NULL if at the end. +================ +*/ +template< class type > +// RAVEN BEGIN +// bdube: inlined +ID_INLINE type *idLinkList::Next( void ) const { +// RAVEN END + if ( !next || ( next == head ) ) { + return NULL; + } + return next->owner; +} + +/* +================ +idLinkList::Prev + +Returns the previous object in the list, or NULL if at the beginning. +================ +*/ +template< class type > +type *idLinkList::Prev( void ) const { + if ( !prev || ( prev == head ) ) { + return NULL; + } + return prev->owner; +} + +/* +================ +idLinkList::NextNode + +Returns the next node in the list, or NULL if at the end. +================ +*/ +template< class type > +idLinkList *idLinkList::NextNode( void ) const { + if ( next == head ) { + return NULL; + } + return next; +} + +/* +================ +idLinkList::PrevNode + +Returns the previous node in the list, or NULL if at the beginning. +================ +*/ +template< class type > +idLinkList *idLinkList::PrevNode( void ) const { + if ( prev == head ) { + return NULL; + } + return prev; +} + +/* +================ +idLinkList::Owner + +Gets the object that is associated with this node. +================ +*/ +template< class type > +type *idLinkList::Owner( void ) const { + return owner; +} + +/* +================ +idLinkList::SetOwner + +Sets the object that this node is associated with. +================ +*/ +template< class type > +void idLinkList::SetOwner( type *object ) { + owner = object; +} + +#endif /* !__LINKLIST_H__ */ diff --git a/src/idlib/containers/List.h b/src/idlib/containers/List.h new file mode 100644 index 0000000..402371e --- /dev/null +++ b/src/idlib/containers/List.h @@ -0,0 +1,1255 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LIST_H__ +#define __LIST_H__ + +/* +=============================================================================== + + List template + Does not allocate memory until the first item is added. + +=============================================================================== +*/ + + +/* +================ +idListSortCompare +================ +*/ +#ifdef __INTEL_COMPILER +// the intel compiler doesn't do the right thing here +template< class type > +ID_INLINE int idListSortCompare( const type *a, const type *b ) { + assert( 0 ); + return 0; +} +#else +template< class type > +ID_INLINE int idListSortCompare( const type *a, const type *b ) { + return *a - *b; +} +#endif + +/* +================ +idListNewElement +================ +*/ +template< class type > +ID_INLINE type *idListNewElement( void ) { + return new type; +} + +/* +================ +idSwap +================ +*/ +template< class type > +ID_INLINE void idSwap( type &a, type &b ) { + type c = a; + a = b; + b = c; +} + +template< class type > +class idList { +public: + + typedef int cmp_t( const type *, const type * ); + typedef int filter_t( const type * ); + + typedef type new_t( void ); + + idList( int newgranularity = 16 ); + idList( const idList &other ); + ~idList( void ); + + void Clear( void ); // clear the list + int Num( void ) const; // returns number of elements in list + int NumAllocated( void ) const; // returns number of elements allocated for + void SetGranularity( int newgranularity ); // set new granularity + int GetGranularity( void ) const; // get the current granularity +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void SetAllocatorHeap ( rvHeap* heap ); // set the heap used for all allocations +#endif +// RAVEN END + + size_t Allocated( void ) const; // returns total size of allocated memory + size_t Size( void ) const; // returns total size of allocated memory including size of list type + size_t MemoryUsed( void ) const; // returns size of the used elements in the list + + idList & operator=( const idList &other ); + const type & operator[]( int index ) const; + type & operator[]( int index ); + + void Condense( void ); // resizes list to exactly the number of elements it contains + void Resize( int newsize ); // resizes list to the given number of elements + void Resize( int newsize, int newgranularity ); // resizes list and sets new granularity + void SetNum( int newnum, bool resize = true ); // set number of elements in list and resize to exactly this number if necessary + void AssureSize( int newSize); // assure list has given number of elements, but leave them uninitialized + void AssureSize( int newSize, const type &initValue ); // assure list has given number of elements and initialize any new elements + void AssureSizeAlloc( int newSize, new_t *allocator ); // assure the pointer list has the given number of elements and allocate any new elements + + type * Ptr( void ); // returns a pointer to the list + const type * Ptr( void ) const; // returns a pointer to the list + type & Alloc( void ); // returns reference to a new data element at the end of the list + int Append( const type & obj ); // append element + int Append( const idList &other ); // append list + int AddUnique( const type & obj ); // add unique element + int Insert( const type & obj, int index = 0 ); // insert the element at the given index + int FindIndex( const type & obj ) const; // find the index for the given element + type * Find( type const & obj ) const; // find pointer to the given element + int FindNull( void ) const; // find the index for the first NULL pointer in the list + int IndexOf( const type *obj ) const; // returns the index for the pointer to an element in the list + bool RemoveIndex( int index ); // remove the element at the given index + bool Remove( const type & obj ); // remove the element + void Sort( cmp_t *compare = ( cmp_t * )&idListSortCompare ); + void SortSubSection( int startIndex, int endIndex, cmp_t *compare = ( cmp_t * )&idListSortCompare ); + void Swap( idList &other ); // swap the contents of the lists + void DeleteContents( bool clear ); // delete the contents of the list + +//RAVENBEGIN +// cdr : added Heap & Stack & Sort functionality + int FindBinary ( const type & key, cmp_t *compare = ( cmp_t * )&idListSortCompare ) const; + void StackAdd( const type & obj ); // add to the stack + void StackPop( void ); // remove from the stack + type & StackTop( void ); // get the top element of the stack + void HeapAdd( const type & obj ); // add to the heap, and resort + void HeapPop( void ); // pop off the top of the heap & resort +// abahr: + int TypeSize() const { return sizeof(type); } + void RemoveNull(); + // gcc 4.0: see ListGame.h + void RemoveContents( bool clear ); +// ddynerman: range remove + bool RemoveRange( int low, int high ); + void RemoveDuplicates( void ); +//RAVEN END + +private: + int num; + int size; + int granularity; + type * list; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rvHeap* allocatorHeap; +#endif +// RAVEN END +}; + +/* +================ +idList::idList( int ) +================ +*/ +template< class type > +ID_INLINE idList::idList( int newgranularity ) { + assert( newgranularity > 0 ); + + list = NULL; + granularity = newgranularity; + Clear(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + allocatorHeap = 0; +#endif +// RAVEN END +} + +/* +================ +idList::idList( const idList &other ) +================ +*/ +template< class type > +ID_INLINE idList::idList( const idList &other ) { + list = NULL; + *this = other; +} + +/* +================ +idList::~idList +================ +*/ +template< class type > +ID_INLINE idList::~idList( void ) { + Clear(); +} + +/* +================ +idList::Clear + +Frees up the memory allocated by the list. Assumes that type automatically handles freeing up memory. +================ +*/ +template< class type > +ID_INLINE void idList::Clear( void ) { + if ( list ) { + delete[] list; + } + + list = NULL; + num = 0; + size = 0; +} + +/* +================ +idList::DeleteContents + +Calls the destructor of all elements in the list. Conditionally frees up memory used by the list. +Note that this only works on lists containing pointers to objects and will cause a compiler error +if called with non-pointers. Since the list was not responsible for allocating the object, it has +no information on whether the object still exists or not, so care must be taken to ensure that +the pointers are still valid when this function is called. Function will set all pointers in the +list to NULL. +================ +*/ +template< class type > +ID_INLINE void idList::DeleteContents( bool clear ) { + int i; + + for( i = 0; i < num; i++ ) { + delete list[ i ]; + list[ i ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, size * sizeof( type ) ); + } +} + +/* +================ +idList::Allocated + +return total memory allocated for the list in bytes, but doesn't take into account additional memory allocated by type +================ +*/ +template< class type > +ID_INLINE size_t idList::Allocated( void ) const { + return size * sizeof( type ); +} + +/* +================ +idList::Size + +return total size of list in bytes, but doesn't take into account additional memory allocated by type +================ +*/ +template< class type > +ID_INLINE size_t idList::Size( void ) const { + return sizeof( idList ) + Allocated(); +} + +/* +================ +idList::MemoryUsed +================ +*/ +template< class type > +ID_INLINE size_t idList::MemoryUsed( void ) const { + return num * sizeof( *list ); +} + +/* +================ +idList::Num + +Returns the number of elements currently contained in the list. +Note that this is NOT an indication of the memory allocated. +================ +*/ +template< class type > +ID_INLINE int idList::Num( void ) const { + return num; +} + +/* +================ +idList::NumAllocated + +Returns the number of elements currently allocated for. +================ +*/ +template< class type > +ID_INLINE int idList::NumAllocated( void ) const { + return size; +} + +/* +================ +idList::SetNum + +Resize to the exact size specified irregardless of granularity +================ +*/ +template< class type > +ID_INLINE void idList::SetNum( int newnum, bool resize ) { + assert( newnum >= 0 ); + if ( resize || newnum > size ) { + Resize( newnum ); + } + num = newnum; +} + +/* +================ +idList::SetGranularity + +Sets the base size of the array and resizes the array to match. +================ +*/ +template< class type > +ID_INLINE void idList::SetGranularity( int newgranularity ) { + int newsize; + + assert( newgranularity > 0 ); + granularity = newgranularity; + + if ( list ) { + // resize it to the closest level of granularity + newsize = num + granularity - 1; + newsize -= newsize % granularity; + if ( newsize != size ) { + Resize( newsize ); + } + } +} + +/* +================ +idList::GetGranularity + +Get the current granularity. +================ +*/ +template< class type > +ID_INLINE int idList::GetGranularity( void ) const { + return granularity; +} + +/* +================ +idList::Condense + +Resizes the array to exactly the number of elements it contains or frees up memory if empty. +================ +*/ +template< class type > +ID_INLINE void idList::Condense( void ) { + if ( list ) { + if ( num ) { + Resize( num ); + } else { + Clear(); + } + } +} + +/* +================ +idList::Resize + +Allocates memory for the amount of elements requested while keeping the contents intact. +Contents are copied using their = operator so that data is correnctly instantiated. +================ +*/ +template< class type > +ID_INLINE void idList::Resize( int newsize ) { + type *temp; + int i; + + assert( newsize >= 0 ); + + // free up the list if no data is being reserved + if ( newsize <= 0 ) { + Clear(); + return; + } + + if ( newsize == size ) { + // not changing the size, so just exit + return; + } + + temp = list; + size = newsize; + if ( size < num ) { + num = size; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=false; + if(allocatorHeap) + { + RV_PUSH_HEAP_PTR(allocatorHeap); + ok=true; + } + else + { + ok=rvPushHeapContainingMemory(this); + } +#endif +// RAVEN END + + // copy the old list into our new one + list = new type[ size ]; + for( i = 0; i < num; i++ ) { + list[ i ] = temp[ i ]; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + // delete the old list if it exists + if ( temp ) { + delete[] temp; + } +} + +/* +================ +idList::Resize + +Allocates memory for the amount of elements requested while keeping the contents intact. +Contents are copied using their = operator so that data is correnctly instantiated. +================ +*/ +template< class type > +ID_INLINE void idList::Resize( int newsize, int newgranularity ) { + type *temp; + int i; + + assert( newsize >= 0 ); + + assert( newgranularity > 0 ); + granularity = newgranularity; + + // free up the list if no data is being reserved + if ( newsize <= 0 ) { + Clear(); + return; + } + + temp = list; + size = newsize; + if ( size < num ) { + num = size; + } + + // copy the old list into our new one + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + list = new type[ size ]; + for( i = 0; i < num; i++ ) { + list[ i ] = temp[ i ]; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + + // delete the old list if it exists + if ( temp ) { + delete[] temp; + } +} + +/* +================ +idList::AssureSize + +Makes sure the list has at least the given number of elements. +================ +*/ +template< class type > +ID_INLINE void idList::AssureSize( int newSize ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + Resize( newSize ); + } + + num = newNum; +} + +/* +================ +idList::AssureSize + +Makes sure the list has at least the given number of elements and initialize any elements not yet initialized. +================ +*/ +template< class type > +ID_INLINE void idList::AssureSize( int newSize, const type &initValue ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + num = size; + Resize( newSize ); + + for ( int i = num; i < newSize; i++ ) { + list[i] = initValue; + } + } + + num = newNum; +} + +/* +================ +idList::AssureSizeAlloc + +Makes sure the list has at least the given number of elements and allocates any elements using the allocator. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template< class type > +ID_INLINE void idList::AssureSizeAlloc( int newSize, new_t *allocator ) { + int newNum = newSize; + + if ( newSize > size ) { + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + + newSize += granularity - 1; + newSize -= newSize % granularity; + num = size; + Resize( newSize ); + + for ( int i = num; i < newSize; i++ ) { + list[i] = (*allocator)(); + } + } + + num = newNum; +} + +/* +================ +idList::operator= + +Copies the contents and size attributes of another list. +================ +*/ +template< class type > +ID_INLINE idList &idList::operator=( const idList &other ) { + int i; + + Clear(); + + num = other.num; + size = other.size; + granularity = other.granularity; + + if ( size ) { + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + bool ok=rvPushHeapContainingMemory(this); +#endif +// RAVEN END + + list = new type[ size ]; + for( i = 0; i < num; i++ ) { + list[ i ] = other.list[ i ]; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT_CONTAINERS) + if(ok) + { + RV_POP_HEAP(); + } +#endif +// RAVEN END + } + return *this; +} + +/* +================ +idList::operator[] const + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template< class type > +ID_INLINE const type &idList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idList::operator[] + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template< class type > +ID_INLINE type &idList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template< class type > +ID_INLINE type *idList::Ptr( void ) { + return list; +} + +/* +================ +idList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template< class type > +const ID_INLINE type *idList::Ptr( void ) const { + return list; +} + +/* +================ +idList::Alloc + +Returns a reference to a new data element at the end of the list. +================ +*/ +template< class type > +ID_INLINE type &idList::Alloc( void ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + Resize( size + granularity ); + } + + return list[ num++ ]; +} + +/* +================ +idList::Append + +Increases the size of the list by one element and copies the supplied data into it. + +Returns the index of the new element. +================ +*/ +template< class type > +ID_INLINE int idList::Append( type const & obj ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + int newsize; + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + newsize = size + granularity; + Resize( newsize - newsize % granularity ); + } + + list[ num ] = obj; + num++; + + return num - 1; +} + + +/* +================ +idList::Insert + +Increases the size of the list by at leat one element if necessary +and inserts the supplied data into it. + +Returns the index of the new element. +================ +*/ +template< class type > +ID_INLINE int idList::Insert( type const & obj, int index ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + int newsize; + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + newsize = size + granularity; + Resize( newsize - newsize % granularity ); + } + + if ( index < 0 ) { + index = 0; + } + else if ( index > num ) { + index = num; + } + for ( int i = num; i > index; --i ) { + list[i] = list[i-1]; + } + num++; + list[index] = obj; + return index; +} + +/* +================ +idList::Append + +adds the other list to this one + +Returns the size of the new combined list +================ +*/ +template< class type > +ID_INLINE int idList::Append( const idList &other ) { + if ( !list ) { + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + Resize( granularity ); + } + + int n = other.Num(); + for (int i = 0; i < n; i++) { + Append(other[i]); + } + + return Num(); +} + +/* +================ +idList::AddUnique + +Adds the data to the list if it doesn't already exist. Returns the index of the data in the list. +================ +*/ +template< class type > +ID_INLINE int idList::AddUnique( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index < 0 ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idList::FindIndex + +Searches for the specified data in the list and returns it's index. Returns -1 if the data is not found. +================ +*/ +template< class type > +ID_INLINE int idList::FindIndex( type const & obj ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == obj ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idList::Find + +Searches for the specified data in the list and returns it's address. Returns NULL if the data is not found. +================ +*/ +template< class type > +ID_INLINE type *idList::Find( type const & obj ) const { + int i; + + i = FindIndex( obj ); + if ( i >= 0 ) { + return &list[ i ]; + } + + return NULL; +} + +/* +================ +idList::FindNull + +Searches for a NULL pointer in the list. Returns -1 if NULL is not found. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template< class type > +ID_INLINE int idList::FindNull( void ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == NULL ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idList::IndexOf + +Takes a pointer to an element in the list and returns the index of the element. +This is NOT a guarantee that the object is really in the list. +Function will assert in debug builds if pointer is outside the bounds of the list, +but remains silent in release builds. +================ +*/ +template< class type > +ID_INLINE int idList::IndexOf( type const *objptr ) const { + int index; + + index = objptr - list; + + assert( index >= 0 ); + assert( index < num ); + + return index; +} + +/* +================ +idList::RemoveIndex + +Removes the element at the specified index and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the index is outside the bounds of the list. +Note that the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template< class type > +ID_INLINE bool idList::RemoveIndex( int index ) { + int i; + + assert( list != NULL ); + assert( index >= 0 ); + assert( index < num ); + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + for( i = index; i < num; i++ ) { + list[ i ] = list[ i + 1 ]; + } + + return true; +} + +/* +================ +idList::Remove + +Removes the element if it is found within the list and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the data is not found in the list. Note that +the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template< class type > +ID_INLINE bool idList::Remove( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index >= 0 ) { + return RemoveIndex( index ); + } + + return false; +} + +/* +================ +idList::Sort + +Performs a qsort on the list using the supplied comparison function. Note that the data is merely moved around the +list, so any pointers to data within the list may no longer be valid. +================ +*/ +template< class type > +ID_INLINE void idList::Sort( cmp_t *compare ) { + if ( !list ) { + return; + } + typedef int cmp_c(const void *, const void *); + + cmp_c *vCompare = (cmp_c *)compare; + qsort( ( void * )list, ( size_t )num, sizeof( type ), vCompare ); +} + +/* +================ +idList::SortSubSection + +Sorts a subsection of the list. +================ +*/ +template< class type > +ID_INLINE void idList::SortSubSection( int startIndex, int endIndex, cmp_t *compare ) { + if ( !list ) { + return; + } + if ( startIndex < 0 ) { + startIndex = 0; + } + if ( endIndex >= num ) { + endIndex = num - 1; + } + if ( startIndex >= endIndex ) { + return; + } + typedef int cmp_c(const void *, const void *); + + cmp_c *vCompare = (cmp_c *)compare; + qsort( ( void * )( &list[startIndex] ), ( size_t )( endIndex - startIndex + 1 ), sizeof( type ), vCompare ); +} + +/* +================ +idList::Swap + +Swaps the contents of two lists +================ +*/ +template< class type > +ID_INLINE void idList::Swap( idList &other ) { + idSwap( num, other.num ); + idSwap( size, other.size ); + idSwap( granularity, other.granularity ); + idSwap( list, other.list ); +} + + + +//RAVENBEGIN +// cdr : added Heap & Stack & Binary Search functionality + +/* +================ +idList::FindBinary + +Assumes the list is sorted and does a binary search for the given key + +================ +*/ +template< class type > +ID_INLINE int idList::FindBinary ( const type & key, cmp_t *compare ) const { + + typedef int cmp_c(const void *, const void *); + cmp_c *vCompare = (cmp_c *)compare; + + type* found = (type*) bsearch( ( void * )( &key ), ( void * )list, ( size_t )num, sizeof( type ), vCompare ); + if (found) + { + return IndexOf(found); + } + return -1; +} + + +/* +================ +idList::StackAdd + +Adds a value to the list as if the list was a stack + +================ +*/ +template< class type > +ID_INLINE void idList::StackAdd( const type & obj ) { + Append( obj ); +} + +/* +================ +idList::StackPop + +Removes a value to the list as if the list was a stack + +================ +*/ +template< class type > +ID_INLINE void idList::StackPop( void ) { +#if 0 + assert(num>0); + if (num<=0) + { + return; + } + +// RAVEN BEGIN +// jsinger: Without this, this container will leak any dynamically allocated members from +// contained class instances +// TTimo: that would cause a double destructor when the list is deleted in Clear() + list[num].~type(); +// RAVEN END + num--; +#else + RemoveIndex( num - 1 ); +#endif +} + +template< class type > +ID_INLINE type& idList::StackTop( void ){ + assert( num > 0 ); + return list[ num-1 ]; +} + +/* +================ +idList::HeapAdd + +Adds a value to the list as if the list was a heap by doing a bubble swap sort +First it appends the object, then swaps up the heap at successive parent positions +as needed + +Complexity: O[n log n] +================ +*/ +template< class type > +ID_INLINE void idList::HeapAdd( const type & obj ) { + + int pos = Append( obj ); + + while (pos && list[((pos-1)/2)]::HeapPop + +Removes the top element from the heap and +First swaps the top element of the heap with the lowest +element, destroys the lowest element (wich was the top), +and then sorts the new top element down the heap as +needed + +Complexity: O[n log n] +================ +*/ +template< class type > +ID_INLINE void idList::HeapPop( void ) { + + assert(num>0); + if (num<=0) + { + return; + } + + idSwap(list[0], list[num-1]); + num--; + + int pos = 0; + + int largestChild = pos; + if (((2*pos)+1)::RemoveNull +================ +*/ +template< class type > +ID_INLINE void idList::RemoveNull() { + for( int ix = Num() - 1; ix >= 0; --ix ) { + if( !list[ix] ) { + RemoveIndex( ix ); + } + } +} + +/* +================ +idList::RemoveRange + +Removes the specified range of elements [low, high] +Only copies down the array once. +================ +*/ +template< class type > +ID_INLINE bool idList::RemoveRange( int low, int high ) { + int i; + + assert( list != NULL ); + assert( low >= 0 ); + assert( high < num ); + assert( low <= high ); + + if ( ( low < 0 ) || ( high >= num ) || ( low > high ) ) { + return false; + } + + int range = (high - low) + 1; + num -= range; + for( i = low; i < num; i++ ) { + list[ i ] = list[ i + range ]; + } + + return true; +} +//RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +template< class type > +void idList::SetAllocatorHeap ( rvHeap* heap ) +{ + assert(heap); + allocatorHeap = heap; +} +#endif +// RAVEN END + +#endif /* !__LIST_H__ */ diff --git a/src/idlib/containers/ListGame.h b/src/idlib/containers/ListGame.h new file mode 100644 index 0000000..7e57754 --- /dev/null +++ b/src/idlib/containers/ListGame.h @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LIST_GAME_H__ +#define __LIST_GAME_H__ + +// GCC 4 compiles templates when they are encountered in a source file, +// not when they are used. Therefore all references, must be resolved. +// RemoveContents() from idLib\List.h references global variables only +// available in the GAME DLL. + +/* +================ +idList::RemoveContents +================ +*/ +template< class type > +ID_INLINE void idList::RemoveContents( bool clear ) { + RemoveNull(); + + for( int ix = Num() - 1; ix >= 0; --ix ) { + list[ ix ]->PostEventMS( &EV_Remove, 0 ); + list[ ix ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, Allocated() ); + } +} + +#endif // __LIST_GAME_H__ diff --git a/src/idlib/containers/Pair.h b/src/idlib/containers/Pair.h new file mode 100644 index 0000000..d0afdba --- /dev/null +++ b/src/idlib/containers/Pair.h @@ -0,0 +1,82 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Pair.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __PAIR_H__ +#define __PAIR_H__ + +template< class type1, class type2 > +class rvPair { +public: + rvPair() {}; + rvPair( const type1& T1, const type2& T2 ) { first = T1; second = T2; }; + + const type1& First( void ) const { return first; }; + const type2& Second( void ) const { return second; }; + + static int rvPairFirstCompare( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + return b->First() - a->First(); + } + + static int rvPairSecondCompare( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + return b->Second() - a->Second(); + } + + static int rvPairFirstCompareDirect( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + if( b->First() - a->First() < 0.001f || b->First() - a->First() < -0.001f ) { + return 0; + } + + if( a->First() > b->First() ) { + return -1; + } else if( a->First() < b->First() ) { + return 1; + } + return 0; + } + + static int rvPairSecondCompareDirect( const rvPair< type1, type2 > *a, const rvPair< type1, type2 > *b ) { + if( b->Second() - a->Second() < 0.001f || b->Second() - a->Second() < -0.001f ) { + return 0; + } + + if( a->Second() > b->Second() ) { + return -1; + } else if( a->Second() < b->Second() ) { + return 1; + } + return 0; + } + +private: + type1 first; + type2 second; +}; + +#endif diff --git a/src/idlib/containers/PlaneSet.h b/src/idlib/containers/PlaneSet.h new file mode 100644 index 0000000..6d90862 --- /dev/null +++ b/src/idlib/containers/PlaneSet.h @@ -0,0 +1,77 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PLANESET_H__ +#define __PLANESET_H__ + +/* +=============================================================================== + + Plane Set + +=============================================================================== +*/ + +class idPlaneSet : public idList { +public: + + void Clear( void ) { idList::Clear(); hash.Free(); } + + int FindPlane( const idPlane &plane, const float normalEps, const float distEps ); + +private: + idHashIndex hash; +}; + +ID_INLINE int idPlaneSet::FindPlane( const idPlane &plane, const float normalEps, const float distEps ) { + int i, border, hashKey; + + assert( distEps <= 0.125f ); + + hashKey = (int)( idMath::Fabs( plane.Dist() ) * 0.125f ); + for ( border = -1; border <= 1; border++ ) { + for ( i = hash.First( hashKey + border ); i >= 0; i = hash.Next( i ) ) { + if ( (*this)[i].Compare( plane, normalEps, distEps ) ) { + return i; + } + } + } + + if ( plane.Type() >= PLANETYPE_NEGX && plane.Type() < PLANETYPE_TRUEAXIAL ) { + Append( -plane ); + hash.Add( hashKey, Num()-1 ); + Append( plane ); + hash.Add( hashKey, Num()-1 ); + return ( Num() - 1 ); + } + else { + Append( plane ); + hash.Add( hashKey, Num()-1 ); + Append( -plane ); + hash.Add( hashKey, Num()-1 ); + return ( Num() - 2 ); + } +} + +#endif /* !__PLANESET_H__ */ diff --git a/src/idlib/containers/Queue.h b/src/idlib/containers/Queue.h new file mode 100644 index 0000000..6b25d16 --- /dev/null +++ b/src/idlib/containers/Queue.h @@ -0,0 +1,196 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __QUEUE_H__ +#define __QUEUE_H__ + +/* +=============================================================================== + + Queue template + +=============================================================================== +*/ + +template< class type, int nextOffset > +class idQueueTemplate { +public: + idQueueTemplate( void ); + + void Add( type *element ); + type * Get( void ); + +private: + type * first; + type * last; +}; + +#define QUEUE_NEXT_PTR( element ) (*((type**)(((byte*)element)+nextOffset))) + +template< class type, int nextOffset > +idQueueTemplate::idQueueTemplate( void ) { + first = last = NULL; +} + +template< class type, int nextOffset > +void idQueueTemplate::Add( type *element ) { + QUEUE_NEXT_PTR(element) = NULL; + if ( last ) { + QUEUE_NEXT_PTR(last) = element; + } else { + first = element; + } + last = element; +} + +template< class type, int nextOffset > +type *idQueueTemplate::Get( void ) { + type *element; + + element = first; + if ( element ) { + first = QUEUE_NEXT_PTR(first); + if ( last == element ) { + last = NULL; + } + QUEUE_NEXT_PTR(element) = NULL; + } + return element; +} + +/* +=============================================================================== + + Raven Queue template + +=============================================================================== +*/ +template +class rvQueue +{ + TYPE mData[CAPACITY]; // Total capacity of the queue + int mNewest; // Head + int mOldest; // Tail + int mSize; // Size... yes, this could be calculated... + +public: + rvQueue() {clear();} + int size() {return mSize;} + bool empty() {return mSize==0;} + bool full() {return mSize>=CAPACITY;} + int begin() {return (full())?(mOldest+1):(mOldest);} + int end() {return mNewest-1;} + void clear() {mNewest = mOldest = mSize = 0;} + bool valid(int i) {return ((mNewest>=mOldest)?(i>=mOldest && i=mNewest && i=(CAPACITY-1))?(0):(i+1);} + TYPE& first() {assert(!empty()); return mData[mOldest];} + TYPE& last() {assert(!empty()); return mData[mNewest-1];} + TYPE& operator[] (int i) {assert(valid(i)); return mData[i];} + int push(const TYPE& value) {assert(!full()); mData[mNewest]=value; inc(mNewest); return (mSize++);} + int pop() {assert(!empty()); inc(mOldest); return (mSize--);} +}; + +/* +=============================================================================== + + Raven Bit Vector Template + +=============================================================================== +*/ +template +class rvBits +{ + enum + { + BITS_SHIFT = 5, // 5. Such A Nice Number + BITS_INT_SIZE = 32, // Size Of A Single Word + BITS_AND = (BITS_INT_SIZE - 1), // Used For And Operation + ARRAY_SIZE = ((CAPACITY + BITS_AND)/(BITS_INT_SIZE)), // Num Words Used + DATA_BYTE_SIZE = (ARRAY_SIZE*sizeof(unsigned int)), // Num Bytes Used + }; + + unsigned int mData[ARRAY_SIZE]; + +public: + rvBits() {clear();} + void clear() {memset(mData, 0, (size_t)(ARRAY_SIZE * sizeof( unsigned int )));} + bool valid(const int i) {return (i>=0 && i>BITS_SHIFT] &= ~(1<<(i&BITS_AND));} + void set(const int i) {assert(valid(i)); mData[i>>BITS_SHIFT] |= (1<<(i&BITS_AND));} + bool operator[](const int i) {assert(valid(i)); return (mData[i>>BITS_SHIFT] & (1<<(i&BITS_AND)))!=0;} +}; + +/* +=============================================================================== + + Raven Pool + +=============================================================================== +*/ +template +class rvPool +{ + TYPE mData[CAPACITY]; + TYPE* mFree[CAPACITY]; + int mSize; + +public: + rvPool() {clear();} + int size() {return mSize;} + bool empty() {return mSize==0;} + bool full() {return mSize>=CAPACITY;} + void clear() {mSize=0; for (int i=0; i +class rvIndexPool +{ + TYPE mData[CAPACITY]; + TYPE* mFree[CAPACITY]; + TYPE* mIndx[INDEXNUM]; + int mSize; + +public: + rvIndexPool() {clear();} + int size() {return mSize;} + bool empty() {return mSize==0;} + bool full() {return mSize>=CAPACITY;} + void clear() {mSize=0; for (int i=0; i=0 && i. + +=========================================================================== +*/ + +#ifndef __STACK_H__ +#define __STACK_H__ + +/* +=============================================================================== + + Stack template + +=============================================================================== +*/ + +#define idStack( type, next ) idStackTemplatenext)> + +template< class type, int nextOffset > +class idStackTemplate { +public: + idStackTemplate( void ); + + void Add( type *element ); + type * Get( void ); + +private: + type * top; + type * bottom; +}; + +#define STACK_NEXT_PTR( element ) (*(type**)(((byte*)element)+nextOffset)) + +template< class type, int nextOffset > +idStackTemplate::idStackTemplate( void ) { + top = bottom = NULL; +} + +template< class type, int nextOffset > +void idStackTemplate::Add( type *element ) { + STACK_NEXT_PTR(element) = top; + top = element; + if ( !bottom ) { + bottom = element; + } +} + +template< class type, int nextOffset > +type *idStackTemplate::Get( void ) { + type *element; + + element = top; + if ( element ) { + top = STACK_NEXT_PTR(top); + if ( bottom == element ) { + bottom = NULL; + } + STACK_NEXT_PTR(element) = NULL; + } + return element; +} + +#endif /* !__STACK_H__ */ diff --git a/src/idlib/containers/StaticList.h b/src/idlib/containers/StaticList.h new file mode 100644 index 0000000..6d9a521 --- /dev/null +++ b/src/idlib/containers/StaticList.h @@ -0,0 +1,544 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __STATICLIST_H__ +#define __STATICLIST_H__ + +/* +=============================================================================== + + Static list template + A non-growing, memset-able list using no memory allocation. + +=============================================================================== +*/ + +template +class idStaticList { +public: + + idStaticList(); + idStaticList( const idStaticList &other ); + ~idStaticList( void ); + + void Clear( void ); // marks the list as empty. does not deallocate or intialize data. + int Num( void ) const; // returns number of elements in list + int Max( void ) const; // returns the maximum number of elements in the list + void SetNum( int newnum ); // set number of elements in list + + size_t Allocated( void ) const; // returns total size of allocated memory + size_t Size( void ) const; // returns total size of allocated memory including size of list type + size_t MemoryUsed( void ) const; // returns size of the used elements in the list + + const type & operator[]( int index ) const; + type & operator[]( int index ); + + type * Ptr( void ); // returns a pointer to the list + const type * Ptr( void ) const; // returns a pointer to the list + type * Alloc( void ); // returns reference to a new data element at the end of the list. returns NULL when full. + int Append( const type & obj ); // append element + int Append( const idStaticList &other ); // append list + int AddUnique( const type & obj ); // add unique element + int Insert( const type & obj, int index ); // insert the element at the given index + int FindIndex( const type & obj ) const; // find the index for the given element + type * Find( type const & obj ) const; // find pointer to the given element + int FindNull( void ) const; // find the index for the first NULL pointer in the list + int IndexOf( const type *obj ) const; // returns the index for the pointer to an element in the list + bool RemoveIndex( int index ); // remove the element at the given index + bool Remove( const type & obj ); // remove the element + void Swap( idStaticList &other ); // swap the contents of the lists + void DeleteContents( bool clear ); // delete the contents of the list + +private: + int num; + type list[ size ]; +}; + +/* +================ +idStaticList::idStaticList() +================ +*/ +template +ID_INLINE idStaticList::idStaticList() { + num = 0; +} + +/* +================ +idStaticList::idStaticList( const idStaticList &other ) +================ +*/ +template +ID_INLINE idStaticList::idStaticList( const idStaticList &other ) { + *this = other; +} + +/* +================ +idStaticList::~idStaticList +================ +*/ +template +ID_INLINE idStaticList::~idStaticList( void ) { +} + +/* +================ +idStaticList::Clear + +Sets the number of elements in the list to 0. Assumes that type automatically handles freeing up memory. +================ +*/ +template +ID_INLINE void idStaticList::Clear( void ) { + num = 0; +} + +/* +================ +idStaticList::DeleteContents + +Calls the destructor of all elements in the list. Conditionally frees up memory used by the list. +Note that this only works on lists containing pointers to objects and will cause a compiler error +if called with non-pointers. Since the list was not responsible for allocating the object, it has +no information on whether the object still exists or not, so care must be taken to ensure that +the pointers are still valid when this function is called. Function will set all pointers in the +list to NULL. +================ +*/ +template +ID_INLINE void idStaticList::DeleteContents( bool clear ) { + int i; + + for( i = 0; i < size; i++ ) { + delete list[ i ]; + list[ i ] = NULL; + } + + if ( clear ) { + Clear(); + } else { + memset( list, 0, sizeof( list ) ); + } +} + +/* +================ +idStaticList::Num + +Returns the number of elements currently contained in the list. +================ +*/ +template +ID_INLINE int idStaticList::Num( void ) const { + return num; +} + +/* +================ +idStaticList::Num + +Returns the maximum number of elements in the list. +================ +*/ +template +ID_INLINE int idStaticList::Max( void ) const { + return size; +} + +/* +================ +idStaticList::Allocated +================ +*/ +template +ID_INLINE size_t idStaticList::Allocated( void ) const { + return size * sizeof( type ); +} + +/* +================ +idStaticList::Size +================ +*/ +template +ID_INLINE size_t idStaticList::Size( void ) const { + return sizeof( idStaticList ) + Allocated(); +} + +/* +================ +idStaticList::Num +================ +*/ +template +ID_INLINE size_t idStaticList::MemoryUsed( void ) const { + return num * sizeof( list[ 0 ] ); +} + +/* +================ +idStaticList::SetNum + +Set number of elements in list. +================ +*/ +template +ID_INLINE void idStaticList::SetNum( int newnum ) { + assert( newnum >= 0 ); + assert( newnum <= size ); + num = newnum; +} + +/* +================ +idStaticList::operator[] const + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template +ID_INLINE const type &idStaticList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idStaticList::operator[] + +Access operator. Index must be within range or an assert will be issued in debug builds. +Release builds do no range checking. +================ +*/ +template +ID_INLINE type &idStaticList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < num ); + + return list[ index ]; +} + +/* +================ +idStaticList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template +ID_INLINE type *idStaticList::Ptr( void ) { + return &list[ 0 ]; +} + +/* +================ +idStaticList::Ptr + +Returns a pointer to the begining of the array. Useful for iterating through the list in loops. + +Note: may return NULL if the list is empty. + +FIXME: Create an iterator template for this kind of thing. +================ +*/ +template +ID_INLINE const type *idStaticList::Ptr( void ) const { + return &list[ 0 ]; +} + +/* +================ +idStaticList::Alloc + +Returns a pointer to a new data element at the end of the list. +================ +*/ +template +ID_INLINE type *idStaticList::Alloc( void ) { + if ( num >= size ) { + return NULL; + } + + return &list[ num++ ]; +} + +/* +================ +idStaticList::Append + +Increases the size of the list by one element and copies the supplied data into it. + +Returns the index of the new element, or -1 when list is full. +================ +*/ +template +ID_INLINE int idStaticList::Append( type const & obj ) { + assert( num < size ); + if ( num < size ) { + list[ num ] = obj; + num++; + return num - 1; + } + + return -1; +} + + +/* +================ +idStaticList::Insert + +Increases the size of the list by at leat one element if necessary +and inserts the supplied data into it. + +Returns the index of the new element, or -1 when list is full. +================ +*/ +template +ID_INLINE int idStaticList::Insert( type const & obj, int index ) { + int i; + + assert( num < size ); + if ( num >= size ) { + return -1; + } + + assert( index >= 0 ); + if ( index < 0 ) { + index = 0; + } else if ( index > num ) { + index = num; + } + + for( i = num; i > index; --i ) { + list[i] = list[i-1]; + } + + num++; + list[index] = obj; + return index; +} + +/* +================ +idStaticList::Append + +adds the other list to this one + +Returns the size of the new combined list +================ +*/ +template +ID_INLINE int idStaticList::Append( const idStaticList &other ) { + int i; + int n = other.Num(); + + if ( num + n > size ) { + n = size - num; + } + for( i = 0; i < n; i++ ) { + list[i + num] = other.list[i]; + } + num += n; + return Num(); +} + +/* +================ +idStaticList::AddUnique + +Adds the data to the list if it doesn't already exist. Returns the index of the data in the list. +================ +*/ +template +ID_INLINE int idStaticList::AddUnique( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index < 0 ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idStaticList::FindIndex + +Searches for the specified data in the list and returns it's index. Returns -1 if the data is not found. +================ +*/ +template +ID_INLINE int idStaticList::FindIndex( type const & obj ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == obj ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idStaticList::Find + +Searches for the specified data in the list and returns it's address. Returns NULL if the data is not found. +================ +*/ +template +ID_INLINE type *idStaticList::Find( type const & obj ) const { + int i; + + i = FindIndex( obj ); + if ( i >= 0 ) { + return &list[ i ]; + } + + return NULL; +} + +/* +================ +idStaticList::FindNull + +Searches for a NULL pointer in the list. Returns -1 if NULL is not found. + +NOTE: This function can only be called on lists containing pointers. Calling it +on non-pointer lists will cause a compiler error. +================ +*/ +template +ID_INLINE int idStaticList::FindNull( void ) const { + int i; + + for( i = 0; i < num; i++ ) { + if ( list[ i ] == NULL ) { + return i; + } + } + + // Not found + return -1; +} + +/* +================ +idStaticList::IndexOf + +Takes a pointer to an element in the list and returns the index of the element. +This is NOT a guarantee that the object is really in the list. +Function will assert in debug builds if pointer is outside the bounds of the list, +but remains silent in release builds. +================ +*/ +template +ID_INLINE int idStaticList::IndexOf( type const *objptr ) const { + int index; + + index = objptr - list; + + assert( index >= 0 ); + assert( index < num ); + + return index; +} + +/* +================ +idStaticList::RemoveIndex + +Removes the element at the specified index and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the index is outside the bounds of the list. +Note that the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template +ID_INLINE bool idStaticList::RemoveIndex( int index ) { + int i; + + assert( index >= 0 ); + assert( index < num ); + + if ( ( index < 0 ) || ( index >= num ) ) { + return false; + } + + num--; + for( i = index; i < num; i++ ) { + list[ i ] = list[ i + 1 ]; + } + + return true; +} + +/* +================ +idStaticList::Remove + +Removes the element if it is found within the list and moves all data following the element down to fill in the gap. +The number of elements in the list is reduced by one. Returns false if the data is not found in the list. Note that +the element is not destroyed, so any memory used by it may not be freed until the destruction of the list. +================ +*/ +template +ID_INLINE bool idStaticList::Remove( type const & obj ) { + int index; + + index = FindIndex( obj ); + if ( index >= 0 ) { + return RemoveIndex( index ); + } + + return false; +} + +/* +================ +idStaticList::Swap + +Swaps the contents of two lists +================ +*/ +template +ID_INLINE void idStaticList::Swap( idStaticList &other ) { + idStaticList temp = *this; + *this = other; + other = temp; +} + +#endif /* !__STATICLIST_H__ */ diff --git a/src/idlib/containers/StrList.h b/src/idlib/containers/StrList.h new file mode 100644 index 0000000..1ca9db7 --- /dev/null +++ b/src/idlib/containers/StrList.h @@ -0,0 +1,219 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __STRLIST_H__ +#define __STRLIST_H__ + +/* +=============================================================================== + + idStrList + +=============================================================================== +*/ + +typedef idList idStrList; +typedef idList idStrPtrList; +typedef idStr *idStrPtr; + +/* +================ +idListSortCompare + +Compares two pointers to strings. Used to sort a list of string pointers alphabetically in idList::Sort. +================ +*/ +template<> +ID_INLINE int idListSortCompare( const idStrPtr *a, const idStrPtr *b ) { + return ( *a )->Icmp( **b ); +} + +/* +================ +idStrList::Sort + +Sorts the list of strings alphabetically. Creates a list of pointers to the actual strings and sorts the +pointer list. Then copies the strings into another list using the ordered list of pointers. +================ +*/ +template<> +ID_INLINE void idStrList::Sort( cmp_t *compare ) { + int i; + + if ( !num ) { + return; + } + + idList other; + idList pointerList; + + pointerList.SetNum( num ); + for( i = 0; i < num; i++ ) { + pointerList[ i ] = &( *this )[ i ]; + } + + pointerList.Sort(); + + other.SetNum( num ); + other.SetGranularity( granularity ); + for( i = 0; i < other.Num(); i++ ) { + other[ i ] = *pointerList[ i ]; + } + + this->Swap( other ); +} + +/* +================ +idStrList::SortSubSection + +Sorts a subsection of the list of strings alphabetically. +================ +*/ +template<> +ID_INLINE void idStrList::SortSubSection( int startIndex, int endIndex, cmp_t *compare ) { + int i, s; + + if ( !num ) { + return; + } + if ( startIndex < 0 ) { + startIndex = 0; + } + if ( endIndex >= num ) { + endIndex = num - 1; + } + if ( startIndex >= endIndex ) { + return; + } + + idList other; + idList pointerList; + + s = endIndex - startIndex + 1; + other.SetNum( s ); + pointerList.SetNum( s ); + for( i = 0; i < s; i++ ) { + other[ i ] = ( *this )[ startIndex + i ]; + pointerList[ i ] = &other[ i ]; + } + + pointerList.Sort(); + + for( i = 0; i < s; i++ ) { + (*this)[ startIndex + i ] = *pointerList[ i ]; + } +} + +/* +================ +idStrList::Size +================ +*/ +template<> +ID_INLINE size_t idStrList::Size( void ) const { + size_t s; + int i; + + s = sizeof( *this ); + for( i = 0; i < Num(); i++ ) { + s += ( *this )[ i ].Size(); + } + + return s; +} + +/* +================ +idStrList::RemoveDuplicates +================ +*/ +template<> +ID_INLINE void idStrList::RemoveDuplicates( void ) { + int i, j; + + for( i = 0; i < Num() - 1; i++ ) { + for( j = i + 1; j < Num(); ) { + if( ( *this )[i] == ( *this )[j] ) { + RemoveIndex( j ); + } else { + j++; + } + } + } +} +/* +=============================================================================== + + idStrList path sorting + +=============================================================================== +*/ + +/* +================ +idListSortComparePaths + +Compares two pointers to strings. Used to sort a list of string pointers alphabetically in idList::Sort. +================ +*/ +template +ID_INLINE int idListSortComparePaths( const idStrPtr *a, const idStrPtr *b ) { + return ( *a )->IcmpPath( **b ); +} + +/* +================ +idStrListSortPaths + +Sorts the list of path strings alphabetically and makes sure folders come first. +================ +*/ +ID_INLINE void idStrListSortPaths( idStrList &list ) { + int i; + + if ( !list.Num() ) { + return; + } + + idList other; + idList pointerList; + + pointerList.SetNum( list.Num() ); + for( i = 0; i < list.Num(); i++ ) { + pointerList[ i ] = &list[ i ]; + } + + pointerList.Sort( idListSortComparePaths ); + + other.SetNum( list.Num() ); + other.SetGranularity( list.GetGranularity() ); + for( i = 0; i < other.Num(); i++ ) { + other[ i ] = *pointerList[ i ]; + } + + list.Swap( other ); +} + +#endif /* !__STRLIST_H__ */ diff --git a/src/idlib/containers/StrPool.h b/src/idlib/containers/StrPool.h new file mode 100644 index 0000000..6d2f9de --- /dev/null +++ b/src/idlib/containers/StrPool.h @@ -0,0 +1,248 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __STRPOOL_H__ +#define __STRPOOL_H__ + +/* +=============================================================================== + + idStrPool + +=============================================================================== +*/ + +class idStrPool; + +class idPoolStr : public idStr { + friend class idStrPool; + +public: + idPoolStr() { numUsers = 0; } + ~idPoolStr() { assert( numUsers == 0 ); } + + // returns total size of allocated memory + size_t Allocated( void ) const { return idStr::Allocated(); } + // returns total size of allocated memory including size of string pool type + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + // returns a pointer to the pool this string was allocated from + const idStrPool * GetPool( void ) const { return pool; } + +private: + idStrPool * pool; + mutable int numUsers; +}; + +class idStrPool { +public: + idStrPool() { caseSensitive = true; } + + void SetCaseSensitive( bool caseSensitive ); + + int Num( void ) const { return pool.Num(); } + size_t Allocated( void ) const; + size_t Size( void ) const; + + const idPoolStr * operator[]( int index ) const { return pool[index]; } + + const idPoolStr * AllocString( const char *string ); + void FreeString( const idPoolStr *poolStr ); + const idPoolStr * CopyString( const idPoolStr *poolStr ); + void Clear( void ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void SetAllocatorHeap ( rvHeap* heap ) + { + assert(heap); + pool.SetAllocatorHeap(heap); + poolHash.SetAllocatorHeap(heap); + } +#endif +// RAVEN END + +private: + bool caseSensitive; + idList pool; + idHashIndex poolHash; +}; + +/* +================ +idStrPool::SetCaseSensitive +================ +*/ +ID_INLINE void idStrPool::SetCaseSensitive( bool caseSensitive ) { + this->caseSensitive = caseSensitive; +} + +/* +================ +idStrPool::AllocString +================ +*/ +ID_INLINE const idPoolStr *idStrPool::AllocString( const char *string ) { + int i, hash; + idPoolStr *poolStr; + + hash = poolHash.GenerateKey( string, caseSensitive ); + if ( caseSensitive ) { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Cmp( string ) == 0 ) { + pool[i]->numUsers++; + return pool[i]; + } + } + } else { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Icmp( string ) == 0 ) { + pool[i]->numUsers++; + return pool[i]; + } + } + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + poolStr = new idPoolStr; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + *static_cast(poolStr) = string; + poolStr->pool = this; + poolStr->numUsers = 1; + poolHash.Add( hash, pool.Append( poolStr ) ); + return poolStr; +} + +/* +================ +idStrPool::FreeString +================ +*/ +ID_INLINE void idStrPool::FreeString( const idPoolStr *poolStr ) { + int i, hash; + + assert( poolStr->numUsers >= 1 ); + assert( poolStr->pool == this ); + + poolStr->numUsers--; + if ( poolStr->numUsers <= 0 ) { + hash = poolHash.GenerateKey( poolStr->c_str(), caseSensitive ); + if ( caseSensitive ) { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Cmp( poolStr->c_str() ) == 0 ) { + break; + } + } + } else { + for ( i = poolHash.First( hash ); i != -1; i = poolHash.Next( i ) ) { + if ( pool[i]->Icmp( poolStr->c_str() ) == 0 ) { + break; + } + } + } + assert( i != -1 ); + assert( pool[i] == poolStr ); + delete pool[i]; + pool.RemoveIndex( i ); + poolHash.RemoveIndex( hash, i ); + } +} + +/* +================ +idStrPool::CopyString +================ +*/ +ID_INLINE const idPoolStr *idStrPool::CopyString( const idPoolStr *poolStr ) { + + assert( poolStr->numUsers >= 1 ); + + if ( poolStr->pool == this ) { + // the string is from this pool so just increase the user count + poolStr->numUsers++; + return poolStr; + } else { + // the string is from another pool so it needs to be re-allocated from this pool. + return AllocString( poolStr->c_str() ); + } +} + +/* +================ +idStrPool::Clear +================ +*/ +ID_INLINE void idStrPool::Clear( void ) { + int i; + + for ( i = 0; i < pool.Num(); i++ ) { + pool[i]->numUsers = 0; + } + pool.DeleteContents( true ); + poolHash.Free(); +} + +/* +================ +idStrPool::Allocated +================ +*/ +ID_INLINE size_t idStrPool::Allocated( void ) const { + int i; + size_t size; + + size = pool.Allocated() + poolHash.Allocated(); + for ( i = 0; i < pool.Num(); i++ ) { + size += pool[i]->Allocated(); + } + return size; +} + +/* +================ +idStrPool::Size +================ +*/ +ID_INLINE size_t idStrPool::Size( void ) const { + int i; + size_t size; + + size = pool.Size() + poolHash.Size(); + for ( i = 0; i < pool.Num(); i++ ) { + size += pool[i]->Size(); + } + return size; +} + +#endif /* !__STRPOOL_H__ */ diff --git a/src/idlib/containers/VectorSet.h b/src/idlib/containers/VectorSet.h new file mode 100644 index 0000000..e6b117b --- /dev/null +++ b/src/idlib/containers/VectorSet.h @@ -0,0 +1,266 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __VECTORSET_H__ +#define __VECTORSET_H__ + +/* +=============================================================================== + + Vector Set + + Creates a set of vectors without duplicates. + +=============================================================================== +*/ + +template< class type, int dimension > +class idVectorSet : public idList { +public: + idVectorSet( void ); + idVectorSet( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + + // returns total size of allocated memory + size_t Allocated( void ) const { return idList::Allocated() + hash.Allocated(); } + // returns total size of allocated memory including size of type + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + + void Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + void ResizeIndex( const int newSize ); + void Clear( void ); + + int FindVector( const type &v, const float epsilon ); + +private: + idHashIndex hash; + type mins; + type maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; +}; + +template< class type, int dimension > +ID_INLINE idVectorSet::idVectorSet( void ) { + hash.Clear( idMath::IPow( boxHashSize, dimension ), 128 ); + boxHashSize = 16; + memset( boxInvSize, 0, dimension * sizeof( boxInvSize[0] ) ); + memset( boxHalfSize, 0, dimension * sizeof( boxHalfSize[0] ) ); +} + +template< class type, int dimension > +ID_INLINE idVectorSet::idVectorSet( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + Init( mins, maxs, boxHashSize, initialSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + int i; + float boxSize; + + idList::AssureSize( initialSize ); + idList::SetNum( 0, false ); + + hash.Clear( idMath::IPow( boxHashSize, dimension ), initialSize ); + + this->mins = mins; + this->maxs = maxs; + this->boxHashSize = boxHashSize; + + for ( i = 0; i < dimension; i++ ) { + boxSize = ( maxs[i] - mins[i] ) / (float) boxHashSize; + boxInvSize[i] = 1.0f / boxSize; + boxHalfSize[i] = boxSize * 0.5f; + } +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::ResizeIndex( const int newSize ) { + idList::Resize( newSize ); + hash.ResizeIndex( newSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSet::Clear( void ) { + idList::Clear(); + hash.Clear(); +} + +template< class type, int dimension > +ID_INLINE int idVectorSet::FindVector( const type &v, const float epsilon ) { + int i, j, k, hashKey, partialHashKey[dimension]; + + for ( i = 0; i < dimension; i++ ) { + assert( epsilon <= boxHalfSize[i] ); + partialHashKey[i] = (int) ( ( v[i] - mins[i] - boxHalfSize[i] ) * boxInvSize[i] ); + } + + for ( i = 0; i < ( 1 << dimension ); i++ ) { + + hashKey = 0; + for ( j = 0; j < dimension; j++ ) { + hashKey *= boxHashSize; + hashKey += partialHashKey[j] + ( ( i >> j ) & 1 ); + } + + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + const type &lv = (*this)[j]; + for ( k = 0; k < dimension; k++ ) { + if ( idMath::Fabs( lv[k] - v[k] ) > epsilon ) { + break; + } + } + if ( k >= dimension ) { + return j; + } + } + } + + hashKey = 0; + for ( i = 0; i < dimension; i++ ) { + hashKey *= boxHashSize; + hashKey += (int) ( ( v[i] - mins[i] ) * boxInvSize[i] ); + } + + hash.Add( hashKey, idList::Num() ); + Append( v ); + return idList::Num()-1; +} + + +/* +=============================================================================== + + Vector Subset + + Creates a subset without duplicates from an existing list with vectors. + +=============================================================================== +*/ + +template< class type, int dimension > +class idVectorSubset { +public: + idVectorSubset( void ); + idVectorSubset( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + + // returns total size of allocated memory + size_t Allocated( void ) const { return idList::Allocated() + hash.Allocated(); } + // returns total size of allocated memory including size of type + size_t Size( void ) const { return sizeof( *this ) + Allocated(); } + + void Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ); + void Clear( void ); + + // returns either vectorNum or an index to a previously found vector + int FindVector( const type *vectorList, const int vectorNum, const float epsilon ); + +private: + idHashIndex hash; + type mins; + type maxs; + int boxHashSize; + float boxInvSize[dimension]; + float boxHalfSize[dimension]; +}; + +template< class type, int dimension > +ID_INLINE idVectorSubset::idVectorSubset( void ) { + hash.Clear( idMath::IPow( boxHashSize, dimension ), 128 ); + boxHashSize = 16; + memset( boxInvSize, 0, dimension * sizeof( boxInvSize[0] ) ); + memset( boxHalfSize, 0, dimension * sizeof( boxHalfSize[0] ) ); +} + +template< class type, int dimension > +ID_INLINE idVectorSubset::idVectorSubset( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + Init( mins, maxs, boxHashSize, initialSize ); +} + +template< class type, int dimension > +ID_INLINE void idVectorSubset::Init( const type &mins, const type &maxs, const int boxHashSize, const int initialSize ) { + int i; + float boxSize; + + hash.Clear( idMath::IPow( boxHashSize, dimension ), initialSize ); + + this->mins = mins; + this->maxs = maxs; + this->boxHashSize = boxHashSize; + + for ( i = 0; i < dimension; i++ ) { + boxSize = ( maxs[i] - mins[i] ) / (float) boxHashSize; + boxInvSize[i] = 1.0f / boxSize; + boxHalfSize[i] = boxSize * 0.5f; + } +} + +template< class type, int dimension > +ID_INLINE void idVectorSubset::Clear( void ) { + idList::Clear(); + hash.Clear(); +} + +template< class type, int dimension > +ID_INLINE int idVectorSubset::FindVector( const type *vectorList, const int vectorNum, const float epsilon ) { + int i, j, k, hashKey, partialHashKey[dimension]; + const type &v = vectorList[vectorNum]; + + for ( i = 0; i < dimension; i++ ) { + assert( epsilon <= boxHalfSize[i] ); + partialHashKey[i] = (int) ( ( v[i] - mins[i] - boxHalfSize[i] ) * boxInvSize[i] ); + } + + for ( i = 0; i < ( 1 << dimension ); i++ ) { + + hashKey = 0; + for ( j = 0; j < dimension; j++ ) { + hashKey *= boxHashSize; + hashKey += partialHashKey[j] + ( ( i >> j ) & 1 ); + } + + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + const type &lv = vectorList[j]; + for ( k = 0; k < dimension; k++ ) { + if ( idMath::Fabs( lv[k] - v[k] ) > epsilon ) { + break; + } + } + if ( k >= dimension ) { + return j; + } + } + } + + hashKey = 0; + for ( i = 0; i < dimension; i++ ) { + hashKey *= boxHashSize; + hashKey += (int) ( ( v[i] - mins[i] ) * boxInvSize[i] ); + } + + hash.Add( hashKey, vectorNum ); + return vectorNum; +} + +#endif /* !__VECTORSET_H__ */ diff --git a/src/idlib/containers/rvBlockPool.h b/src/idlib/containers/rvBlockPool.h new file mode 100644 index 0000000..9406b59 --- /dev/null +++ b/src/idlib/containers/rvBlockPool.h @@ -0,0 +1,131 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __BLOCKPOOL_H__ +#define __BLOCKPOOL_H__ + +#ifdef _RV_MEM_SYS_SUPPORT + +/* +=============================================================================== + + Block based allocator for fixed size objects. + + All objects of the 'type' are properly constructed. + However, the constructor is not called for re-used objects. + + This is essentially the same as idBlockAlloc found in heap.h. The difference + is that rvBlockPool is aware of which system heap it should allocate into. + +=============================================================================== +*/ + +template +class rvBlockPool { +public: + rvBlockPool( void ); + ~rvBlockPool( void ); + + void Shutdown( void ); + + type * Alloc( void ); + void Free( type *element ); + + int GetTotalCount( void ) const { return total; } + int GetAllocCount( void ) const { return active; } + int GetFreeCount( void ) const { return total - active; } + + size_t Allocated( void ) const { return( total * sizeof( type ) ); } + +private: + typedef struct element_s { + struct element_s * next; + type t; + } element_t; + typedef struct block_s { + element_t elements[blockSize]; + struct block_s * next; + } block_t; + + block_t * blocks; + element_t * free; + int total; + int active; +}; + +template +rvBlockPool::rvBlockPool( void ) { + blocks = NULL; + free = NULL; + total = active = 0; +} + +template +rvBlockPool::~rvBlockPool( void ) { + Shutdown(); +} + +template +type *rvBlockPool::Alloc( void ) { + if ( !free ) { + RV_PUSH_SYS_HEAP_ID(heapID); + block_t *block = new block_t; + RV_POP_HEAP(); + block->next = blocks; + blocks = block; + for ( int i = 0; i < blockSize; i++ ) { + block->elements[i].next = free; + free = &block->elements[i]; + } + total += blockSize; + } + active++; + element_t *element = free; + free = free->next; + element->next = NULL; + return &element->t; +} + +template +void rvBlockPool::Free( type *t ) { + element_t *element = (element_t *)( ( (unsigned char *) t ) - ( (int) &((element_t *)0)->t ) ); + element->next = free; + free = element; + active--; +} + +template +void rvBlockPool::Shutdown( void ) { + while( blocks ) { + block_t *block = blocks; + blocks = blocks->next; + delete block; + } + blocks = NULL; + free = NULL; + total = active = 0; +} + +#endif // _RV_MEM_SYS_SUPPORT +#endif // __BLOCKPOOL_H__ diff --git a/src/idlib/geometry/DrawVert.h b/src/idlib/geometry/DrawVert.h new file mode 100644 index 0000000..f227b9f --- /dev/null +++ b/src/idlib/geometry/DrawVert.h @@ -0,0 +1,176 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DRAWVERT_H__ +#define __DRAWVERT_H__ + +/* +=============================================================================== + + Draw Vertex. + +=============================================================================== +*/ + +class idDrawVert { +public: + idVec3 xyz; + byte color[4]; + idVec3 normal; + byte color2[4]; + idVec3 tangents[2]; + idVec2 st; + + float operator[]( const int index ) const; + float & operator[]( const int index ); + + void Clear( void ); + + const idVec3 & GetNormal( void ) const; + void SetNormal( float x, float y, float z ); + void SetNormal( const idVec3 &n ); + + const idVec3 & GetTangent( void ) const; + void SetTangent( float x, float y, float z ); + void SetTangent( const idVec3 &t ); + + const idVec3 & GetBiTangent( void ) const; // derived from normal, tangent, and tangent flag + void SetBiTangent( float x, float y, float z ); + void SetBiTangent( const idVec3 &t ); + void SetBiTangentSign( float sign ); // either 1.0f or -1.0f + + void Lerp( const idDrawVert &a, const idDrawVert &b, const float f ); + void LerpAll( const idDrawVert &a, const idDrawVert &b, const float f ); + + void Normalize( void ); + + void SetColor( dword color ); + dword GetColor( void ) const; +}; + +// offsets for SIMD code +#define DRAWVERT_SIZE 64 // sizeof( idDrawVert ) +#define DRAWVERT_SIZE_SHIFT 6 // log2( sizeof( idDrawVert ) ) +#define DRAWVERT_XYZ_OFFSET (0*4) // offsetof( idDrawVert, xyz ) +#define DRAWVERT_COLOR_OFFSET (3*4) // offsetof( idDrawVert, color ) +#define DRAWVERT_NORMAL_OFFSET (4*4) // offsetof( idDrawVert, normal ) +#define DRAWVERT_COLOR2_OFFSET (7*4) // offsetof( idDrawVert, color2 ) +#define DRAWVERT_TANGENT0_OFFSET (8*4) // offsetof( idDrawVert, tangents[0] ) +#define DRAWVERT_TANGENT1_OFFSET (11*4) // offsetof( idDrawVert, tangents[1] ) +#define DRAWVERT_ST_OFFSET (14*4) // offsetof( idDrawVert, st ) + +assert_sizeof( idDrawVert, DRAWVERT_SIZE ); +assert_sizeof( idDrawVert, (1<= 0 && index < 5 ); + return ((float *)(&xyz))[index]; +} +ID_INLINE float &idDrawVert::operator[]( const int index ) { + assert( index >= 0 && index < 5 ); + return ((float *)(&xyz))[index]; +} + +ID_INLINE void idDrawVert::Clear( void ) { + xyz.Zero(); + st.Zero(); + normal.Zero(); + tangents[0].Zero(); + tangents[1].Zero(); + color[0] = color[1] = color[2] = color[3] = 0; +} + +ID_INLINE const idVec3 &idDrawVert::GetNormal( void ) const { + return normal; +} + +ID_INLINE void idDrawVert::SetNormal( const idVec3 &n ) { + normal = n; +} + +ID_INLINE void idDrawVert::SetNormal( float x, float y, float z ) { + normal.Set( x, y, z ); +} + +ID_INLINE const idVec3 &idDrawVert::GetTangent( void ) const { + return tangents[0]; +} + +ID_INLINE void idDrawVert::SetTangent( float x, float y, float z ) { + tangents[0].Set( x, y, z ); +} + +ID_INLINE void idDrawVert::SetTangent( const idVec3 &t ) { + tangents[0] = t; +} + +ID_INLINE const idVec3 &idDrawVert::GetBiTangent( void ) const { + return tangents[1]; +} + +ID_INLINE void idDrawVert::SetBiTangent( float x, float y, float z ) { + tangents[1].Set( x, y, z ); +} + +ID_INLINE void idDrawVert::SetBiTangent( const idVec3 &t ) { + tangents[1] = t; +} + +ID_INLINE void idDrawVert::SetBiTangentSign( float sign ) { +// tangents[0][3] = sign; +} + +ID_INLINE void idDrawVert::Lerp( const idDrawVert &a, const idDrawVert &b, const float f ) { + xyz = a.xyz + f * ( b.xyz - a.xyz ); + st = a.st + f * ( b.st - a.st ); +} + +ID_INLINE void idDrawVert::LerpAll( const idDrawVert &a, const idDrawVert &b, const float f ) { + xyz = a.xyz + f * ( b.xyz - a.xyz ); + st = a.st + f * ( b.st - a.st ); + normal = a.normal + f * ( b.normal - a.normal ); + tangents[0] = a.tangents[0] + f * ( b.tangents[0] - a.tangents[0] ); + tangents[1] = a.tangents[1] + f * ( b.tangents[1] - a.tangents[1] ); + color[0] = (byte)( a.color[0] + f * ( b.color[0] - a.color[0] ) ); + color[1] = (byte)( a.color[1] + f * ( b.color[1] - a.color[1] ) ); + color[2] = (byte)( a.color[2] + f * ( b.color[2] - a.color[2] ) ); + color[3] = (byte)( a.color[3] + f * ( b.color[3] - a.color[3] ) ); +} + +ID_INLINE void idDrawVert::SetColor( dword color ) { + *reinterpret_cast(this->color) = color; +} + +ID_INLINE dword idDrawVert::GetColor( void ) const { + return *reinterpret_cast(this->color); +} + +#endif /* !__DRAWVERT_H__ */ diff --git a/src/idlib/geometry/JointTransform.cpp b/src/idlib/geometry/JointTransform.cpp new file mode 100644 index 0000000..6c58f2d --- /dev/null +++ b/src/idlib/geometry/JointTransform.cpp @@ -0,0 +1,83 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +============= +idJointMat::ToJointQuat +============= +*/ +idJointQuat idJointMat::ToJointQuat( void ) const { + idJointQuat jq; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[3] = { 1, 2, 0 }; + + trace = mat[0 * 4 + 0] + mat[1 * 4 + 1] + mat[2 * 4 + 2]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + jq.q[3] = s * t; + jq.q[0] = ( mat[1 * 4 + 2] - mat[2 * 4 + 1] ) * s; + jq.q[1] = ( mat[2 * 4 + 0] - mat[0 * 4 + 2] ) * s; + jq.q[2] = ( mat[0 * 4 + 1] - mat[1 * 4 + 0] ) * s; + + } else { + + i = 0; + if ( mat[1 * 4 + 1] > mat[0 * 4 + 0] ) { + i = 1; + } + if ( mat[2 * 4 + 2] > mat[i * 4 + i] ) { + i = 2; + } + j = next[i]; + k = next[j]; + + t = ( mat[i * 4 + i] - ( mat[j * 4 + j] + mat[k * 4 + k] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + jq.q[i] = s * t; + jq.q[3] = ( mat[j * 4 + k] - mat[k * 4 + j] ) * s; + jq.q[j] = ( mat[i * 4 + j] + mat[j * 4 + i] ) * s; + jq.q[k] = ( mat[i * 4 + k] + mat[k * 4 + i] ) * s; + } + + jq.t[0] = mat[0 * 4 + 3]; + jq.t[1] = mat[1 * 4 + 3]; + jq.t[2] = mat[2 * 4 + 3]; + + return jq; +} diff --git a/src/idlib/geometry/JointTransform.h b/src/idlib/geometry/JointTransform.h new file mode 100644 index 0000000..f37d58c --- /dev/null +++ b/src/idlib/geometry/JointTransform.h @@ -0,0 +1,363 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __JOINTTRANSFORM_H__ +#define __JOINTTRANSFORM_H__ + +/* +=============================================================================== + + Joint Quaternion + +=============================================================================== +*/ + +class idJointQuat { +public: + + idQuat q; + idVec3 t; + float w; + }; + +// offsets for SIMD code +#define JOINTQUAT_SIZE (8*4) // sizeof( idJointQuat ) +#define JOINTQUAT_SIZE_SHIFT 5 // log2( sizeof( idJointQuat ) ) +#define JOINTQUAT_Q_OFFSET (0*4) // offsetof( idJointQuat, q ) +#define JOINTQUAT_T_OFFSET (4*4) // offsetof( idJointQuat, t ) + +assert_sizeof( idJointQuat, JOINTQUAT_SIZE ); +assert_sizeof( idJointQuat, (1< epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idJointMat::operator==( const idJointMat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idJointMat::operator!=( const idJointMat &a ) const { + return !Compare( a ); +} + +ID_INLINE idMat3 idJointMat::ToMat3( void ) const { + return idMat3( mat[0 * 4 + 0], mat[1 * 4 + 0], mat[2 * 4 + 0], + mat[0 * 4 + 1], mat[1 * 4 + 1], mat[2 * 4 + 1], + mat[0 * 4 + 2], mat[1 * 4 + 2], mat[2 * 4 + 2] ); +} + +ID_INLINE idVec3 idJointMat::ToVec3( void ) const { + return idVec3( mat[0 * 4 + 3], mat[1 * 4 + 3], mat[2 * 4 + 3] ); +} + +ID_INLINE const float *idJointMat::ToFloatPtr( void ) const { + return mat; +} + +ID_INLINE float *idJointMat::ToFloatPtr( void ) { + return mat; +} + +ID_INLINE void idJointMat::Mul( idJointMat &result, const idJointMat &mat, const float s ) { + result.mat[0 * 4 + 0] = s * mat.mat[0 * 4 + 0]; + result.mat[0 * 4 + 1] = s * mat.mat[0 * 4 + 1]; + result.mat[0 * 4 + 2] = s * mat.mat[0 * 4 + 2]; + result.mat[0 * 4 + 3] = s * mat.mat[0 * 4 + 3]; + result.mat[1 * 4 + 0] = s * mat.mat[1 * 4 + 0]; + result.mat[1 * 4 + 1] = s * mat.mat[1 * 4 + 1]; + result.mat[1 * 4 + 2] = s * mat.mat[1 * 4 + 2]; + result.mat[1 * 4 + 3] = s * mat.mat[1 * 4 + 3]; + result.mat[2 * 4 + 0] = s * mat.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] = s * mat.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] = s * mat.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] = s * mat.mat[2 * 4 + 3]; +} + +ID_INLINE void idJointMat::Mad( idJointMat &result, const idJointMat &mat, const float s ) { + result.mat[0 * 4 + 0] += s * mat.mat[0 * 4 + 0]; + result.mat[0 * 4 + 1] += s * mat.mat[0 * 4 + 1]; + result.mat[0 * 4 + 2] += s * mat.mat[0 * 4 + 2]; + result.mat[0 * 4 + 3] += s * mat.mat[0 * 4 + 3]; + result.mat[1 * 4 + 0] += s * mat.mat[1 * 4 + 0]; + result.mat[1 * 4 + 1] += s * mat.mat[1 * 4 + 1]; + result.mat[1 * 4 + 2] += s * mat.mat[1 * 4 + 2]; + result.mat[1 * 4 + 3] += s * mat.mat[1 * 4 + 3]; + result.mat[2 * 4 + 0] += s * mat.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] += s * mat.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] += s * mat.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] += s * mat.mat[2 * 4 + 3]; +} + +ID_INLINE void idJointMat::Multiply( idJointMat &result, const idJointMat &m1, const idJointMat &m2 ) { + result.mat[0 * 4 + 0] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[0 * 4 + 1] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[0 * 4 + 2] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[0 * 4 + 3] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[0 * 4 + 3]; + + result.mat[1 * 4 + 0] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[1 * 4 + 1] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[1 * 4 + 2] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[1 * 4 + 3] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[1 * 4 + 3]; + + result.mat[2 * 4 + 0] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 0] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 0]; + result.mat[2 * 4 + 1] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 1] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 1]; + result.mat[2 * 4 + 2] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 2] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 2] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 2]; + result.mat[2 * 4 + 3] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 3] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 3] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 3] + m1.mat[2 * 4 + 3]; +} + +ID_INLINE void idJointMat::InverseMultiply( idJointMat &result, const idJointMat &m1, const idJointMat &m2 ) { + float dst[3]; + + result.mat[0 * 4 + 0] = m1.mat[0 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[0 * 4 + 1] = m1.mat[0 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[0 * 4 + 2] = m1.mat[0 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[0 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[0 * 4 + 2] * m2.mat[2 * 4 + 2]; + + result.mat[1 * 4 + 0] = m1.mat[1 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[1 * 4 + 1] = m1.mat[1 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[1 * 4 + 2] = m1.mat[1 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[1 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[1 * 4 + 2] * m2.mat[2 * 4 + 2]; + + result.mat[2 * 4 + 0] = m1.mat[2 * 4 + 0] * m2.mat[0 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[0 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[0 * 4 + 2]; + result.mat[2 * 4 + 1] = m1.mat[2 * 4 + 0] * m2.mat[1 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[1 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[1 * 4 + 2]; + result.mat[2 * 4 + 2] = m1.mat[2 * 4 + 0] * m2.mat[2 * 4 + 0] + m1.mat[2 * 4 + 1] * m2.mat[2 * 4 + 1] + m1.mat[2 * 4 + 2] * m2.mat[2 * 4 + 2]; + + dst[0] = - ( m2.mat[0 * 4 + 0] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 0] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 0] * m2.mat[2 * 4 + 3] ); + dst[1] = - ( m2.mat[0 * 4 + 1] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 1] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 1] * m2.mat[2 * 4 + 3] ); + dst[2] = - ( m2.mat[0 * 4 + 2] * m2.mat[0 * 4 + 3] + m2.mat[1 * 4 + 2] * m2.mat[1 * 4 + 3] + m2.mat[2 * 4 + 2] * m2.mat[2 * 4 + 3] ); + + result.mat[0 * 4 + 3] = m1.mat[0 * 4 + 0] * dst[0] + m1.mat[0 * 4 + 1] * dst[1] + m1.mat[0 * 4 + 2] * dst[2] + m1.mat[0 * 4 + 3]; + result.mat[1 * 4 + 3] = m1.mat[1 * 4 + 0] * dst[0] + m1.mat[1 * 4 + 1] * dst[1] + m1.mat[1 * 4 + 2] * dst[2] + m1.mat[1 * 4 + 3]; + result.mat[2 * 4 + 3] = m1.mat[2 * 4 + 0] * dst[0] + m1.mat[2 * 4 + 1] * dst[1] + m1.mat[2 * 4 + 2] * dst[2] + m1.mat[2 * 4 + 3]; +} + +// RAVEN BEGIN +// dluetscher: added function to quickly invert an idJointMat - assumes rotation-translation only + +ID_INLINE void idJointMat::Invert( ) { + float m01, m02, m12, tx, ty; + + tx = mat[0 * 4 + 3]; + ty = mat[1 * 4 + 3]; + mat[0 * 4 + 3] = -(tx*mat[0 * 4 + 0] + ty*mat[1 * 4 + 0] + mat[2 * 4 + 3]*mat[2 * 4 + 0]); + mat[1 * 4 + 3] = -(tx*mat[0 * 4 + 1] + ty*mat[1 * 4 + 1] + mat[2 * 4 + 3]*mat[2 * 4 + 1]); + mat[2 * 4 + 3] = -(tx*mat[0 * 4 + 2] + ty*mat[1 * 4 + 2] + mat[2 * 4 + 3]*mat[2 * 4 + 2]); + + m01 = mat[0 * 4 + 1]; + m02 = mat[0 * 4 + 2]; + m12 = mat[1 * 4 + 2]; + mat[0 * 4 + 1] = mat[1 * 4 + 0]; + mat[0 * 4 + 2] = mat[2 * 4 + 0]; + mat[1 * 4 + 0] = m01; + mat[1 * 4 + 2] = mat[2 * 4 + 1]; + mat[2 * 4 + 0] = m02; + mat[2 * 4 + 1] = m12; +} + +// RAVEN END +#endif /* !__JOINTTRANSFORM_H__ */ diff --git a/src/idlib/geometry/Surface.cpp b/src/idlib/geometry/Surface.cpp new file mode 100644 index 0000000..809f89c --- /dev/null +++ b/src/idlib/geometry/Surface.cpp @@ -0,0 +1,926 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +================= +UpdateVertexIndex +================= +*/ +ID_INLINE int UpdateVertexIndex( int vertexIndexNum[2], int *vertexRemap, int *vertexCopyIndex, int vertNum ) { + int s = INTSIGNBITSET( vertexRemap[vertNum] ); + vertexIndexNum[0] = vertexRemap[vertNum]; + vertexRemap[vertNum] = vertexIndexNum[s]; + vertexIndexNum[1] += s; + vertexCopyIndex[vertexRemap[vertNum]] = vertNum; + return vertexRemap[vertNum]; +} + +/* +================= +idSurface::Split +================= +*/ +int idSurface::Split( const idPlane &plane, const float epsilon, idSurface **front, idSurface **back, int *frontOnPlaneEdges, int *backOnPlaneEdges ) const { + float * dists; + float f; + byte * sides; + int counts[3]; + int * edgeSplitVertex; + int numEdgeSplitVertexes; + int * vertexRemap[2]; + int vertexIndexNum[2][2]; + int * vertexCopyIndex[2]; + int * indexPtr[2]; + int indexNum[2]; + int * index; + int * onPlaneEdges[2]; + int numOnPlaneEdges[2]; + int maxOnPlaneEdges; + int i; + idSurface * surface[2]; + idDrawVert v; + + dists = (float *) _alloca( verts.Num() * sizeof( float ) ); + sides = (byte *) _alloca( verts.Num() * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine side for each vertex + for ( i = 0; i < verts.Num(); i++ ) { + dists[i] = f = plane.Distance( verts[i].xyz ); + if ( f > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( f < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + *front = *back = NULL; + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + + f = ( verts[indexes[1]].xyz - verts[indexes[0]].xyz ).Cross( verts[indexes[0]].xyz - verts[indexes[2]].xyz ) * plane.Normal(); + if ( FLOATSIGNBITSET( f ) ) { + *back = new idSurface( *this ); + return SIDE_BACK; + } else { + *front = new idSurface( *this ); + return SIDE_FRONT; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = new idSurface( *this ); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = new idSurface( *this ); + return SIDE_FRONT; + } + + // allocate front and back surface + *front = surface[0] = new idSurface(); + *back = surface[1] = new idSurface(); + + edgeSplitVertex = (int *) _alloca( edges.Num() * sizeof( int ) ); + numEdgeSplitVertexes = 0; + + maxOnPlaneEdges = 4 * counts[SIDE_ON]; + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // split edges + for ( i = 0; i < edges.Num(); i++ ) { + int v0 = edges[i].verts[0]; + int v1 = edges[i].verts[1]; + int sidesOr = ( sides[v0] | sides[v1] ); + + // if both vertexes are on the same side or one is on the clipping plane + if ( !( sides[v0] ^ sides[v1] ) || ( sidesOr & SIDE_ON ) ) { + edgeSplitVertex[i] = -1; + counts[sidesOr & SIDE_BACK]++; + counts[SIDE_ON] += ( sidesOr & SIDE_ON ) >> 1; + } else { + f = dists[v0] / ( dists[v0] - dists[v1] ); + v.LerpAll( verts[v0], verts[v1], f ); + edgeSplitVertex[i] = numEdgeSplitVertexes++; + surface[0]->verts.Append( v ); + surface[1]->verts.Append( v ); + } + } + + // each edge is shared by at most two triangles, as such there can never be more indexes than twice the number of edges + surface[0]->indexes.Resize( ( ( counts[SIDE_FRONT] + counts[SIDE_ON] ) * 2 ) + ( numEdgeSplitVertexes * 4 ) ); + surface[1]->indexes.Resize( ( ( counts[SIDE_BACK] + counts[SIDE_ON] ) * 2 ) + ( numEdgeSplitVertexes * 4 ) ); + + // allocate indexes to construct the triangle indexes for the front and back surface + vertexRemap[0] = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap[0], -1, verts.Num() * sizeof( int ) ); + vertexRemap[1] = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap[1], -1, verts.Num() * sizeof( int ) ); + + vertexCopyIndex[0] = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + vertexCopyIndex[1] = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + + vertexIndexNum[0][0] = vertexIndexNum[1][0] = 0; + vertexIndexNum[0][1] = vertexIndexNum[1][1] = numEdgeSplitVertexes; + + indexPtr[0] = surface[0]->indexes.Ptr(); + indexPtr[1] = surface[1]->indexes.Ptr(); + indexNum[0] = surface[0]->indexes.Num(); + indexNum[1] = surface[1]->indexes.Num(); + + maxOnPlaneEdges += 4 * numEdgeSplitVertexes; + // allocate one more in case no triangles are actually split which may happen for a disconnected surface + onPlaneEdges[0] = (int *) _alloca( ( maxOnPlaneEdges + 1 ) * sizeof( int ) ); + onPlaneEdges[1] = (int *) _alloca( ( maxOnPlaneEdges + 1 ) * sizeof( int ) ); + numOnPlaneEdges[0] = numOnPlaneEdges[1] = 0; + + // split surface triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + int e0, e1, e2, v0, v1, v2, s, n; + + e0 = abs( edgeIndexes[i+0] ); + e1 = abs( edgeIndexes[i+1] ); + e2 = abs( edgeIndexes[i+2] ); + + v0 = indexes[i+0]; + v1 = indexes[i+1]; + v2 = indexes[i+2]; + + switch( ( INTSIGNBITSET( edgeSplitVertex[e0] ) | ( INTSIGNBITSET( edgeSplitVertex[e1] ) << 1 ) | ( INTSIGNBITSET( edgeSplitVertex[e2] ) << 2 ) ) ^ 7 ) { + case 0: { // no edges split + if ( ( sides[v0] & sides[v1] & sides[v2] ) & SIDE_ON ) { + // coplanar + f = ( verts[v1].xyz - verts[v0].xyz ).Cross( verts[v0].xyz - verts[v2].xyz ) * plane.Normal(); + s = FLOATSIGNBITSET( f ); + } else { + s = ( sides[v0] | sides[v1] | sides[v2] ) & SIDE_BACK; + } + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]] = n; + numOnPlaneEdges[s] += ( sides[v0] & sides[v1] ) >> 1; + onPlaneEdges[s][numOnPlaneEdges[s]] = n+1; + numOnPlaneEdges[s] += ( sides[v1] & sides[v2] ) >> 1; + onPlaneEdges[s][numOnPlaneEdges[s]] = n+2; + numOnPlaneEdges[s] += ( sides[v2] & sides[v0] ) >> 1; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + break; + } + case 1: { // first edge split + s = sides[v0] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + break; + } + case 2: { // second edge split + s = sides[v1] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + break; + } + case 3: { // first and second edge split + s = sides[v1] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + break; + } + case 4: { // third edge split + s = sides[v2] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + break; + } + case 5: { // first and third edge split + s = sides[v0] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = edgeSplitVertex[e0]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + index[n++] = edgeSplitVertex[e2]; + indexNum[s] = n; + break; + } + case 6: { // second and third edge split + s = sides[v2] & SIDE_BACK; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v2 ); + indexNum[s] = n; + s ^= 1; + n = indexNum[s]; + onPlaneEdges[s][numOnPlaneEdges[s]++] = n; + index = indexPtr[s]; + index[n++] = edgeSplitVertex[e1]; + index[n++] = edgeSplitVertex[e2]; + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v0 ); + index[n++] = UpdateVertexIndex( vertexIndexNum[s], vertexRemap[s], vertexCopyIndex[s], v1 ); + index[n++] = edgeSplitVertex[e2]; + indexNum[s] = n; + break; + } + } + } + + surface[0]->indexes.SetNum( indexNum[0], false ); + surface[1]->indexes.SetNum( indexNum[1], false ); + + // copy vertexes + surface[0]->verts.SetNum( vertexIndexNum[0][1], false ); + index = vertexCopyIndex[0]; + for ( i = numEdgeSplitVertexes; i < surface[0]->verts.Num(); i++ ) { + surface[0]->verts[i] = verts[index[i]]; + } + surface[1]->verts.SetNum( vertexIndexNum[1][1], false ); + index = vertexCopyIndex[1]; + for ( i = numEdgeSplitVertexes; i < surface[1]->verts.Num(); i++ ) { + surface[1]->verts[i] = verts[index[i]]; + } + + // generate edge indexes + surface[0]->GenerateEdgeIndexes(); + surface[1]->GenerateEdgeIndexes(); + + if ( frontOnPlaneEdges ) { + memcpy( frontOnPlaneEdges, onPlaneEdges[0], numOnPlaneEdges[0] * sizeof( int ) ); + frontOnPlaneEdges[numOnPlaneEdges[0]] = -1; + } + + if ( backOnPlaneEdges ) { + memcpy( backOnPlaneEdges, onPlaneEdges[1], numOnPlaneEdges[1] * sizeof( int ) ); + backOnPlaneEdges[numOnPlaneEdges[1]] = -1; + } + + return SIDE_CROSS; +} + +/* +================= +idSurface::ClipInPlace +================= +*/ +bool idSurface::ClipInPlace( const idPlane &plane, const float epsilon, const bool keepOn ) { + float * dists; + float f; + byte * sides; + int counts[3]; + int i; + int * edgeSplitVertex; + int * vertexRemap; + int vertexIndexNum[2]; + int * vertexCopyIndex; + int * indexPtr; + int indexNum; + int numEdgeSplitVertexes; + idDrawVert v; + idList newVerts; + idList newIndexes; + + dists = (float *) _alloca( verts.Num() * sizeof( float ) ); + sides = (byte *) _alloca( verts.Num() * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine side for each vertex + for ( i = 0; i < verts.Num(); i++ ) { + dists[i] = f = plane.Distance( verts[i].xyz ); + if ( f > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( f < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + + f = ( verts[indexes[1]].xyz - verts[indexes[0]].xyz ).Cross( verts[indexes[0]].xyz - verts[indexes[2]].xyz ) * plane.Normal(); + if ( FLOATSIGNBITSET( f ) ) { + Clear(); + return false; + } else { + return true; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + Clear(); + return false; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return true; + } + + edgeSplitVertex = (int *) _alloca( edges.Num() * sizeof( int ) ); + numEdgeSplitVertexes = 0; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = 0; + + // split edges + for ( i = 0; i < edges.Num(); i++ ) { + int v0 = edges[i].verts[0]; + int v1 = edges[i].verts[1]; + + // if both vertexes are on the same side or one is on the clipping plane + if ( !( sides[v0] ^ sides[v1] ) || ( ( sides[v0] | sides[v1] ) & SIDE_ON ) ) { + edgeSplitVertex[i] = -1; + counts[(sides[v0]|sides[v1]) & SIDE_BACK]++; + } else { + f = dists[v0] / ( dists[v0] - dists[v1] ); + v.LerpAll( verts[v0], verts[v1], f ); + edgeSplitVertex[i] = numEdgeSplitVertexes++; + newVerts.Append( v ); + } + } + + // each edge is shared by at most two triangles, as such there can never be + // more indexes than twice the number of edges + newIndexes.Resize( ( counts[SIDE_FRONT] << 1 ) + ( numEdgeSplitVertexes << 2 ) ); + + // allocate indexes to construct the triangle indexes for the front and back surface + vertexRemap = (int *) _alloca( verts.Num() * sizeof( int ) ); + memset( vertexRemap, -1, verts.Num() * sizeof( int ) ); + + vertexCopyIndex = (int *) _alloca( ( numEdgeSplitVertexes + verts.Num() ) * sizeof( int ) ); + + vertexIndexNum[0] = 0; + vertexIndexNum[1] = numEdgeSplitVertexes; + + indexPtr = newIndexes.Ptr(); + indexNum = newIndexes.Num(); + + // split surface triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + int e0, e1, e2, v0, v1, v2; + + e0 = abs( edgeIndexes[i+0] ); + e1 = abs( edgeIndexes[i+1] ); + e2 = abs( edgeIndexes[i+2] ); + + v0 = indexes[i+0]; + v1 = indexes[i+1]; + v2 = indexes[i+2]; + + switch( ( INTSIGNBITSET( edgeSplitVertex[e0] ) | ( INTSIGNBITSET( edgeSplitVertex[e1] ) << 1 ) | ( INTSIGNBITSET( edgeSplitVertex[e2] ) << 2 ) ) ^ 7 ) { + case 0: { // no edges split + if ( ( sides[v0] | sides[v1] | sides[v2] ) & SIDE_BACK ) { + break; + } + if ( ( sides[v0] & sides[v1] & sides[v2] ) & SIDE_ON ) { + // coplanar + if ( !keepOn ) { + break; + } + f = ( verts[v1].xyz - verts[v0].xyz ).Cross( verts[v0].xyz - verts[v2].xyz ) * plane.Normal(); + if ( FLOATSIGNBITSET( f ) ) { + break; + } + } + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + break; + } + case 1: { // first edge split + if ( !( sides[v0] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + } + break; + } + case 2: { // second edge split + if ( !( sides[v1] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } + break; + } + case 3: { // first and second edge split + if ( !( sides[v1] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = edgeSplitVertex[e0]; + } else { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + } + break; + } + case 4: { // third edge split + if ( !( sides[v2] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + } else { + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + } + break; + } + case 5: { // first and third edge split + if ( !( sides[v0] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } else { + indexPtr[indexNum++] = edgeSplitVertex[e0]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } + break; + } + case 6: { // second and third edge split + if ( !( sides[v2] & SIDE_BACK ) ) { + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v2 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = edgeSplitVertex[e1]; + } else { + indexPtr[indexNum++] = edgeSplitVertex[e2]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e1]; + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v0 ); + indexPtr[indexNum++] = UpdateVertexIndex( vertexIndexNum, vertexRemap, vertexCopyIndex, v1 ); + indexPtr[indexNum++] = edgeSplitVertex[e2]; + } + break; + } + } + } + + newIndexes.SetNum( indexNum, false ); + + // copy vertexes + newVerts.SetNum( vertexIndexNum[1], false ); + for ( i = numEdgeSplitVertexes; i < newVerts.Num(); i++ ) { + newVerts[i] = verts[vertexCopyIndex[i]]; + } + + // copy back to this surface + indexes = newIndexes; + verts = newVerts; + + GenerateEdgeIndexes(); + + return true; +} + +/* +============= +idSurface::IsConnected +============= +*/ +bool idSurface::IsConnected( void ) const { + int i, j, numIslands, numTris; + int queueStart, queueEnd; + int *queue, *islandNum; + int curTri, nextTri, edgeNum; + const int *index; + + numIslands = 0; + numTris = indexes.Num() / 3; + islandNum = (int *) _alloca16( numTris * sizeof( int ) ); + memset( islandNum, -1, numTris * sizeof( int ) ); + queue = (int *) _alloca16( numTris * sizeof( int ) ); + + for ( i = 0; i < numTris; i++ ) { + + if ( islandNum[i] != -1 ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = i; + islandNum[i] = numIslands; + + for ( curTri = queue[queueStart]; queueStart < queueEnd; curTri = queue[++queueStart] ) { + + index = &edgeIndexes[curTri * 3]; + + for ( j = 0; j < 3; j++ ) { + + edgeNum = index[j]; + nextTri = edges[abs(edgeNum)].tris[INTSIGNBITNOTSET(edgeNum)]; + + if ( nextTri == -1 ) { + continue; + } + + nextTri /= 3; + + if ( islandNum[nextTri] != -1 ) { + continue; + } + + queue[queueEnd++] = nextTri; + islandNum[nextTri] = numIslands; + } + } + numIslands++; + } + + return ( numIslands == 1 ); +} + +/* +================= +idSurface::IsClosed +================= +*/ +bool idSurface::IsClosed( void ) const { + for ( int i = 0; i < edges.Num(); i++ ) { + if ( edges[i].tris[0] < 0 || edges[i].tris[1] < 0 ) { + return false; + } + } + return true; +} + +/* +============= +idSurface::IsPolytope +============= +*/ +bool idSurface::IsPolytope( const float epsilon ) const { + int i, j; + idPlane plane; + + if ( !IsClosed() ) { + return false; + } + + for ( i = 0; i < indexes.Num(); i += 3 ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + + for ( j = 0; j < verts.Num(); j++ ) { + if ( plane.Side( verts[j].xyz, epsilon ) == SIDE_FRONT ) { + return false; + } + } + } + return true; +} + +/* +============= +idSurface::PlaneDistance +============= +*/ +float idSurface::PlaneDistance( const idPlane &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < verts.Num(); i++ ) { + d = plane.Distance( verts[i].xyz ); + if ( d < min ) { + min = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( FLOATSIGNBITNOTSET( min ) ) { + return min; + } + if ( FLOATSIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idSurface::PlaneSide +============= +*/ +int idSurface::PlaneSide( const idPlane &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < verts.Num(); i++ ) { + d = plane.Distance( verts[i].xyz ); + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +================= +idSurface::LineIntersection +================= +*/ +bool idSurface::LineIntersection( const idVec3 &start, const idVec3 &end, bool backFaceCull ) const { + float scale; + + RayIntersection( start, end - start, scale, false ); + return ( scale >= 0.0f && scale <= 1.0f ); +} + +/* +================= +idSurface::RayIntersection +================= +*/ +bool idSurface::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull ) const { + int i, i0, i1, i2, s0, s1, s2; + float d, s; + byte *sidedness; + idPluecker rayPl, pl; + idPlane plane; + + sidedness = (byte *)_alloca( edges.Num() * sizeof(byte) ); + scale = idMath::INFINITY; + + rayPl.FromRay( start, dir ); + + // ray sidedness for edges + for ( i = 0; i < edges.Num(); i++ ) { + pl.FromLine( verts[ edges[i].verts[1] ].xyz, verts[ edges[i].verts[0] ].xyz ); + d = pl.PermutedInnerProduct( rayPl ); + sidedness[ i ] = FLOATSIGNBITSET( d ); + } + + // test triangles + for ( i = 0; i < edgeIndexes.Num(); i += 3 ) { + i0 = edgeIndexes[i+0]; + i1 = edgeIndexes[i+1]; + i2 = edgeIndexes[i+2]; + s0 = sidedness[abs(i0)] ^ INTSIGNBITSET( i0 ); + s1 = sidedness[abs(i1)] ^ INTSIGNBITSET( i1 ); + s2 = sidedness[abs(i2)] ^ INTSIGNBITSET( i2 ); + + if ( s0 & s1 & s2 ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + plane.RayIntersection( start, dir, s ); + if ( idMath::Fabs( s ) < idMath::Fabs( scale ) ) { + scale = s; + } + } else if ( !backFaceCull && !(s0 | s1 | s2) ) { + plane.FromPoints( verts[indexes[i+0]].xyz, verts[indexes[i+1]].xyz, verts[indexes[i+2]].xyz ); + plane.RayIntersection( start, dir, s ); + if ( idMath::Fabs( s ) < idMath::Fabs( scale ) ) { + scale = s; + } + } + } + + if ( idMath::Fabs( scale ) < idMath::INFINITY ) { + return true; + } + return false; +} + +/* +================= +idSurface::GenerateEdgeIndexes + + Assumes each edge is shared by at most two triangles. +================= +*/ +void idSurface::GenerateEdgeIndexes( void ) { + int i, j, i0, i1, i2, s, v0, v1, edgeNum; + int *index, *vertexEdges, *edgeChain; + surfaceEdge_t e[3]; + + vertexEdges = (int *) _alloca16( verts.Num() * sizeof( int ) ); + memset( vertexEdges, -1, verts.Num() * sizeof( int ) ); + edgeChain = (int *) _alloca16( indexes.Num() * sizeof( int ) ); + + edgeIndexes.SetNum( indexes.Num(), true ); + + edges.Clear(); + + // the first edge is a dummy + e[0].verts[0] = e[0].verts[1] = e[0].tris[0] = e[0].tris[1] = 0; + edges.Append( e[0] ); + + for ( i = 0; i < indexes.Num(); i += 3 ) { + index = indexes.Ptr() + i; + // vertex numbers + i0 = index[0]; + i1 = index[1]; + i2 = index[2]; + // setup edges each with smallest vertex number first + s = INTSIGNBITSET(i1 - i0); + e[0].verts[0] = index[s]; + e[0].verts[1] = index[s^1]; + s = INTSIGNBITSET(i2 - i1) + 1; + e[1].verts[0] = index[s]; + e[1].verts[1] = index[s^3]; + s = INTSIGNBITSET(i2 - i0) << 1; + e[2].verts[0] = index[s]; + e[2].verts[1] = index[s^2]; + // get edges + for ( j = 0; j < 3; j++ ) { + v0 = e[j].verts[0]; + v1 = e[j].verts[1]; + for ( edgeNum = vertexEdges[v0]; edgeNum >= 0; edgeNum = edgeChain[edgeNum] ) { + if ( edges[edgeNum].verts[1] == v1 ) { + break; + } + } + // if the edge does not yet exist + if ( edgeNum < 0 ) { + e[j].tris[0] = e[j].tris[1] = -1; + edgeNum = edges.Append( e[j] ); + edgeChain[edgeNum] = vertexEdges[v0]; + vertexEdges[v0] = edgeNum; + } + // update edge index and edge tri references + if ( index[j] == v0 ) { + assert( edges[edgeNum].tris[0] == -1 ); // edge may not be shared by more than two triangles + edges[edgeNum].tris[0] = i; + edgeIndexes[i+j] = edgeNum; + } else { + assert( edges[edgeNum].tris[1] == -1 ); // edge may not be shared by more than two triangles + edges[edgeNum].tris[1] = i; + edgeIndexes[i+j] = -edgeNum; + } + } + } +} + +/* +================= +idSurface::FindEdge +================= +*/ +int idSurface::FindEdge( int v1, int v2 ) const { + int i, firstVert, secondVert; + + if ( v1 < v2 ) { + firstVert = v1; + secondVert = v2; + } else { + firstVert = v2; + secondVert = v1; + } + for ( i = 1; i < edges.Num(); i++ ) { + if ( edges[i].verts[0] == firstVert ) { + if ( edges[i].verts[1] == secondVert ) { + break; + } + } + } + if ( i < edges.Num() ) { + return v1 < v2 ? i : -i; + } + return 0; +} diff --git a/src/idlib/geometry/Surface.h b/src/idlib/geometry/Surface.h new file mode 100644 index 0000000..0cd51a2 --- /dev/null +++ b/src/idlib/geometry/Surface.h @@ -0,0 +1,229 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SURFACE_H__ +#define __SURFACE_H__ + +/* +=============================================================================== + + Surface base class. + + A surface is tesselated to a triangle mesh with each edge shared by + at most two triangles. + +=============================================================================== +*/ + +typedef struct surfaceEdge_s { + int verts[2]; // edge vertices always with ( verts[0] < verts[1] ) + int tris[2]; // edge triangles +} surfaceEdge_t; + + +class idSurface { +public: + idSurface( void ); + explicit idSurface( const idSurface &surf ); + explicit idSurface( const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + ~idSurface( void ); + + const idDrawVert & operator[]( const int index ) const; + idDrawVert & operator[]( const int index ); + idSurface & operator+=( const idSurface &surf ); + + int GetNumIndexes( void ) const { return indexes.Num(); } + const int * GetIndexes( void ) const { return indexes.Ptr(); } + int GetNumVertices( void ) const { return verts.Num(); } + const idDrawVert * GetVertices( void ) const { return verts.Ptr(); } + const int * GetEdgeIndexes( void ) const { return edgeIndexes.Ptr(); } + const surfaceEdge_t * GetEdges( void ) const { return edges.Ptr(); } + + void Clear( void ); + void SwapTriangles( idSurface &surf ); + void TranslateSelf( const idVec3 &translation ); + void RotateSelf( const idMat3 &rotation ); + + // splits the surface into a front and back surface, the surface itself stays unchanged + // frontOnPlaneEdges and backOnPlaneEdges optionally store the indexes to the edges that lay on the split plane + // returns a SIDE_? + int Split( const idPlane &plane, const float epsilon, idSurface **front, idSurface **back, int *frontOnPlaneEdges = NULL, int *backOnPlaneEdges = NULL ) const; + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + // returns true if each triangle can be reached from any other triangle by a traversal + bool IsConnected( void ) const; + // returns true if the surface is closed + bool IsClosed( void ) const; + // returns true if the surface is a convex hull + bool IsPolytope( const float epsilon = 0.1f ) const; + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + // returns true if the line intersects one of the surface triangles + bool LineIntersection( const idVec3 &start, const idVec3 &end, bool backFaceCull = false ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull = false ) const; + +protected: + idList verts; // vertices + idList indexes; // 3 references to vertices for each triangle + idList edges; // edges + idList edgeIndexes; // 3 references to edges for each triangle, may be negative for reversed edge + +protected: + void GenerateEdgeIndexes( void ); + int FindEdge( int v1, int v2 ) const; +}; + +/* +==================== +idSurface::idSurface +==================== +*/ +ID_INLINE idSurface::idSurface( void ) { +} + +/* +================= +idSurface::idSurface +================= +*/ +ID_INLINE idSurface::idSurface( const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + assert( verts != NULL && indexes != NULL && numVerts > 0 && numIndexes > 0 ); + this->verts.SetNum( numVerts ); + memcpy( this->verts.Ptr(), verts, numVerts * sizeof( verts[0] ) ); + this->indexes.SetNum( numIndexes ); + memcpy( this->indexes.Ptr(), indexes, numIndexes * sizeof( indexes[0] ) ); + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface::idSurface +==================== +*/ +ID_INLINE idSurface::idSurface( const idSurface &surf ) { + this->verts = surf.verts; + this->indexes = surf.indexes; + this->edges = surf.edges; + this->edgeIndexes = surf.edgeIndexes; +} + +/* +==================== +idSurface::~idSurface +==================== +*/ +ID_INLINE idSurface::~idSurface( void ) { +} + +/* +================= +idSurface::operator[] +================= +*/ +ID_INLINE const idDrawVert &idSurface::operator[]( const int index ) const { + return verts[ index ]; +}; + +/* +================= +idSurface::operator[] +================= +*/ +ID_INLINE idDrawVert &idSurface::operator[]( const int index ) { + return verts[ index ]; +}; + +/* +================= +idSurface::operator+= +================= +*/ +ID_INLINE idSurface &idSurface::operator+=( const idSurface &surf ) { + int i, m, n; + n = verts.Num(); + m = indexes.Num(); + verts.Append( surf.verts ); // merge verts where possible ? + indexes.Append( surf.indexes ); + for ( i = m; i < indexes.Num(); i++ ) { + indexes[i] += n; + } + GenerateEdgeIndexes(); + return *this; +} + +/* +================= +idSurface::Clear +================= +*/ +ID_INLINE void idSurface::Clear( void ) { + verts.Clear(); + indexes.Clear(); + edges.Clear(); + edgeIndexes.Clear(); +} + +/* +================= +idSurface::SwapTriangles +================= +*/ +ID_INLINE void idSurface::SwapTriangles( idSurface &surf ) { + verts.Swap( surf.verts ); + indexes.Swap( surf.indexes ); + edges.Swap( surf.edges ); + edgeIndexes.Swap( surf.edgeIndexes ); +} + +/* +================= +idSurface::TranslateSelf +================= +*/ +ID_INLINE void idSurface::TranslateSelf( const idVec3 &translation ) { + for ( int i = 0; i < verts.Num(); i++ ) { + verts[i].xyz += translation; + } +} + +/* +================= +idSurface::RotateSelf +================= +*/ +ID_INLINE void idSurface::RotateSelf( const idMat3 &rotation ) { + for ( int i = 0; i < verts.Num(); i++ ) { + verts[i].xyz *= rotation; + verts[i].normal *= rotation; + verts[i].tangents[0] *= rotation; + verts[i].tangents[1] *= rotation; + } +} + +#endif /* !__SURFACE_H__ */ diff --git a/src/idlib/geometry/Surface_Patch.cpp b/src/idlib/geometry/Surface_Patch.cpp new file mode 100644 index 0000000..24f482c --- /dev/null +++ b/src/idlib/geometry/Surface_Patch.cpp @@ -0,0 +1,687 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +================= +idSurface_Patch::SetSize +================= +*/ +void idSurface_Patch::SetSize( int patchWidth, int patchHeight ) { + if ( patchWidth < 1 || patchWidth > maxWidth ) { + idLib::common->FatalError("idSurface_Patch::SetSize: invalid patchWidth"); + } + if ( patchHeight < 1 || patchHeight > maxHeight ) { + idLib::common->FatalError("idSurface_Patch::SetSize: invalid patchHeight"); + } + width = patchWidth; + height = patchHeight; + verts.SetNum( width * height, false ); +} + +/* +================= +idSurface_Patch::PutOnCurve + +Expects an expanded patch. +================= +*/ +void idSurface_Patch::PutOnCurve( void ) { + int i, j; + idDrawVert prev, next; + + assert( expanded == true ); + // put all the approximating points on the curve + for ( i = 0; i < width; i++ ) { + for ( j = 1; j < height; j += 2 ) { + LerpVert( verts[j*maxWidth+i], verts[(j+1)*maxWidth+i], prev ); + LerpVert( verts[j*maxWidth+i], verts[(j-1)*maxWidth+i], next ); + LerpVert( prev, next, verts[j*maxWidth+i] ); + } + } + + for ( j = 0; j < height; j++ ) { + for ( i = 1; i < width; i += 2 ) { + LerpVert( verts[j*maxWidth+i], verts[j*maxWidth+i+1], prev ); + LerpVert( verts[j*maxWidth+i], verts[j*maxWidth+i-1], next ); + LerpVert( prev, next, verts[j*maxWidth+i] ); + } + } +} + +/* +================ +idSurface_Patch::ProjectPointOntoVector +================ +*/ +void idSurface_Patch::ProjectPointOntoVector( const idVec3 &point, const idVec3 &vStart, const idVec3 &vEnd, idVec3 &vProj ) { + idVec3 pVec, vec; + + pVec = point - vStart; + vec = vEnd - vStart; + vec.Normalize(); + // project onto the directional vector for this segment + vProj = vStart + (pVec * vec) * vec; +} + +/* +================ +idSurface_Patch::RemoveLinearColumnsRows + +Expects an expanded patch. +================ +*/ +void idSurface_Patch::RemoveLinearColumnsRows( void ) { + int i, j, k; + float len, maxLength; + idVec3 proj, dir; + + assert( expanded == true ); + for ( j = 1; j < width - 1; j++ ) { + maxLength = 0; + for ( i = 0; i < height; i++ ) { + idSurface_Patch::ProjectPointOntoVector( verts[i*maxWidth + j].xyz, + verts[i*maxWidth + j-1].xyz, verts[i*maxWidth + j+1].xyz, proj); + dir = verts[i*maxWidth + j].xyz - proj; + len = dir.LengthSqr(); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < Square( 0.2f ) ) { + width--; + for ( i = 0; i < height; i++ ) { + for ( k = j; k < width; k++ ) { + verts[i*maxWidth + k] = verts[i*maxWidth + k+1]; + } + } + j--; + } + } + for ( j = 1; j < height - 1; j++ ) { + maxLength = 0; + for ( i = 0; i < width; i++ ) { + idSurface_Patch::ProjectPointOntoVector( verts[j*maxWidth + i].xyz, + verts[(j-1)*maxWidth + i].xyz, verts[(j+1)*maxWidth + i].xyz, proj); + dir = verts[j*maxWidth + i].xyz - proj; + len = dir.LengthSqr(); + if ( len > maxLength ) { + maxLength = len; + } + } + if ( maxLength < Square( 0.2f ) ) { + height--; + for ( i = 0; i < width; i++ ) { + for ( k = j; k < height; k++ ) { + verts[k*maxWidth + i] = verts[(k+1)*maxWidth + i]; + } + } + j--; + } + } +} + +/* +================ +idSurface_Patch::ResizeExpanded +================ +*/ +void idSurface_Patch::ResizeExpanded( int newHeight, int newWidth ) { + int i, j; + + assert( expanded == true ); + if ( newHeight <= maxHeight && newWidth <= maxWidth ) { + return; + } + if ( newHeight * newWidth > maxHeight * maxWidth ) { + verts.SetNum( newHeight * newWidth ); + } + // space out verts for new height and width + for ( j = maxHeight-1; j >= 0; j-- ) { + for ( i = maxWidth-1; i >= 0; i-- ) { + verts[j*newWidth + i] = verts[j*maxWidth + i]; + } + } + maxHeight = newHeight; + maxWidth = newWidth; +} + +/* +================ +idSurface_Patch::Collapse +================ +*/ +void idSurface_Patch::Collapse( void ) { + int i, j; + + if ( !expanded ) { + idLib::common->FatalError("idSurface_Patch::Collapse: patch not expanded"); + } + expanded = false; + if ( width != maxWidth ) { + for ( j = 0; j < height; j++ ) { + for ( i = 0; i < width; i++ ) { + verts[j*width + i] = verts[j*maxWidth + i]; + } + } + } + verts.SetNum( width * height, false ); +} + +/* +================ +idSurface_Patch::Expand +================ +*/ +void idSurface_Patch::Expand( void ) { + int i, j; + + if ( expanded ) { + idLib::common->FatalError("idSurface_Patch::Expand: patch alread expanded"); + } + expanded = true; + verts.SetNum( maxWidth * maxHeight, false ); + if ( width != maxWidth ) { + for ( j = height-1; j >= 0; j-- ) { + for ( i = width-1; i >= 0; i-- ) { + verts[j*maxWidth + i] = verts[j*width + i]; + } + } + } +} + +/* +============ +idSurface_Patch::LerpVert +============ +*/ +void idSurface_Patch::LerpVert( const idDrawVert &a, const idDrawVert &b, idDrawVert &out ) const { + out.xyz[0] = 0.5f * ( a.xyz[0] + b.xyz[0] ); + out.xyz[1] = 0.5f * ( a.xyz[1] + b.xyz[1] ); + out.xyz[2] = 0.5f * ( a.xyz[2] + b.xyz[2] ); + out.normal[0] = 0.5f * ( a.normal[0] + b.normal[0] ); + out.normal[1] = 0.5f * ( a.normal[1] + b.normal[1] ); + out.normal[2] = 0.5f * ( a.normal[2] + b.normal[2] ); + out.st[0] = 0.5f * ( a.st[0] + b.st[0] ); + out.st[1] = 0.5f * ( a.st[1] + b.st[1] ); +} + +/* +================= +idSurface_Patch::GenerateNormals + +Handles all the complicated wrapping and degenerate cases +Expects a Not expanded patch. +================= +*/ +#define COPLANAR_EPSILON 0.1f + +void idSurface_Patch::GenerateNormals( void ) { + int i, j, k, dist; + idVec3 norm; + idVec3 sum; + int count; + idVec3 base; + idVec3 delta; + int x, y; + idVec3 around[8], temp; + bool good[8]; + bool wrapWidth, wrapHeight; + static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + assert( expanded == false ); + + // + // if all points are coplanar, set all normals to that plane + // + idVec3 extent[3]; + float offset; + + extent[0] = verts[width - 1].xyz - verts[0].xyz; + extent[1] = verts[(height-1) * width + width - 1].xyz - verts[0].xyz; + extent[2] = verts[(height-1) * width].xyz - verts[0].xyz; + + norm = extent[0].Cross( extent[1] ); + if ( norm.LengthSqr() == 0.0f ) { + norm = extent[0].Cross( extent[2] ); + if ( norm.LengthSqr() == 0.0f ) { + norm = extent[1].Cross( extent[2] ); + } + } + + // wrapped patched may not get a valid normal here + if ( norm.Normalize() != 0.0f ) { + + offset = verts[0].xyz * norm; + for ( i = 1; i < width * height; i++ ) { + float d = verts[i].xyz * norm; + if ( idMath::Fabs( d - offset ) > COPLANAR_EPSILON ) { + break; + } + } + + if ( i == width * height ) { + // all are coplanar + for ( i = 0; i < width * height; i++ ) { + verts[i].normal = norm; + } + return; + } + } + + // check for wrapped edge cases, which should smooth across themselves + wrapWidth = false; + for ( i = 0; i < height; i++ ) { + delta = verts[i * width].xyz - verts[i * width + width-1].xyz; + if ( delta.LengthSqr() > Square( 1.0f ) ) { + break; + } + } + if ( i == height ) { + wrapWidth = true; + } + + wrapHeight = false; + for ( i = 0; i < width; i++ ) { + delta = verts[i].xyz - verts[(height-1) * width + i].xyz; + if ( delta.LengthSqr() > Square( 1.0f ) ) { + break; + } + } + if ( i == width ) { + wrapHeight = true; + } + + for ( i = 0; i < width; i++ ) { + for ( j = 0; j < height; j++ ) { + count = 0; + base = verts[j * width + i].xyz; + for ( k = 0; k < 8; k++ ) { + around[k] = vec3_origin; + good[k] = false; + + for ( dist = 1; dist <= 3; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + temp = verts[y * width + x].xyz - base; + if ( temp.Normalize() == 0.0f ) { + continue; // degenerate edge, get more dist + } else { + good[k] = true; + around[k] = temp; + break; // good edge + } + } + } + + sum = vec3_origin; + for ( k = 0; k < 8; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + norm = around[(k+1)&7].Cross( around[k] ); + if ( norm.Normalize() == 0.0f ) { + continue; + } + sum += norm; + count++; + } + if ( count == 0 ) { + //idLib::common->Printf("bad normal\n"); + count = 1; + } + verts[j * width + i].normal = sum; + verts[j * width + i].normal.Normalize(); + } + } +} + +/* +================= +idSurface_Patch::GenerateIndexes +================= +*/ +void idSurface_Patch::GenerateIndexes( void ) { + int i, j, v1, v2, v3, v4, index; + + indexes.SetNum( (width-1) * (height-1) * 2 * 3, false ); + index = 0; + for ( i = 0; i < width - 1; i++ ) { + for ( j = 0; j < height - 1; j++ ) { + v1 = j * width + i; + v2 = v1 + 1; + v3 = v1 + width + 1; + v4 = v1 + width; + indexes[index++] = v1; + indexes[index++] = v3; + indexes[index++] = v2; + indexes[index++] = v1; + indexes[index++] = v4; + indexes[index++] = v3; + } + } + + GenerateEdgeIndexes(); +} + +/* +=============== +idSurface_Patch::SampleSinglePatchPoint +=============== +*/ +void idSurface_Patch::SampleSinglePatchPoint( const idDrawVert ctrl[3][3], float u, float v, idDrawVert *out ) const { + float vCtrl[3][8]; + int vPoint; + int axis; + + // find the control points for the v coordinate + for ( vPoint = 0; vPoint < 3; vPoint++ ) { + for ( axis = 0; axis < 8; axis++ ) { + float a, b, c; + float qA, qB, qC; + if ( axis < 3 ) { + a = ctrl[0][vPoint].xyz[axis]; + b = ctrl[1][vPoint].xyz[axis]; + c = ctrl[2][vPoint].xyz[axis]; + } else if ( axis < 6 ) { + a = ctrl[0][vPoint].normal[axis-3]; + b = ctrl[1][vPoint].normal[axis-3]; + c = ctrl[2][vPoint].normal[axis-3]; + } else { + a = ctrl[0][vPoint].st[axis-6]; + b = ctrl[1][vPoint].st[axis-6]; + c = ctrl[2][vPoint].st[axis-6]; + } + qA = a - 2.0f * b + c; + qB = 2.0f * b - 2.0f * a; + qC = a; + vCtrl[vPoint][axis] = qA * u * u + qB * u + qC; + } + } + + // interpolate the v value + for ( axis = 0; axis < 8; axis++ ) { + float a, b, c; + float qA, qB, qC; + + a = vCtrl[0][axis]; + b = vCtrl[1][axis]; + c = vCtrl[2][axis]; + qA = a - 2.0f * b + c; + qB = 2.0f * b - 2.0f * a; + qC = a; + + if ( axis < 3 ) { + out->xyz[axis] = qA * v * v + qB * v + qC; + } else if ( axis < 6 ) { + out->normal[axis-3] = qA * v * v + qB * v + qC; + } else { + out->st[axis-6] = qA * v * v + qB * v + qC; + } + } +} + +/* +=================== +idSurface_Patch::SampleSinglePatch +=================== +*/ +void idSurface_Patch::SampleSinglePatch( const idDrawVert ctrl[3][3], int baseCol, int baseRow, int width, int horzSub, int vertSub, idDrawVert *outVerts ) const { + int i, j; + float u, v; + + horzSub++; + vertSub++; + for ( i = 0; i < horzSub; i++ ) { + for ( j = 0; j < vertSub; j++ ) { + u = (float) i / ( horzSub - 1 ); + v = (float) j / ( vertSub - 1 ); + SampleSinglePatchPoint( ctrl, u, v, &outVerts[((baseRow + j) * width) + i + baseCol] ); + } + } +} + +/* +================= +idSurface_Patch::SubdivideExplicit +================= +*/ +void idSurface_Patch::SubdivideExplicit( int horzSubdivisions, int vertSubdivisions, bool genNormals, bool removeLinear ) { + int i, j, k, l; + idDrawVert sample[3][3]; + int outWidth = ((width - 1) / 2 * horzSubdivisions) + 1; + int outHeight = ((height - 1) / 2 * vertSubdivisions) + 1; + idDrawVert *dv = new idDrawVert[ outWidth * outHeight ]; + + // generate normals for the control mesh + if ( genNormals ) { + GenerateNormals(); + } + + int baseCol = 0; + for ( i = 0; i + 2 < width; i += 2 ) { + int baseRow = 0; + for ( j = 0; j + 2 < height; j += 2 ) { + for ( k = 0; k < 3; k++ ) { + for ( l = 0; l < 3; l++ ) { + sample[k][l] = verts[ ((j + l) * width) + i + k ]; + } + } + SampleSinglePatch( sample, baseCol, baseRow, outWidth, horzSubdivisions, vertSubdivisions, dv ); + baseRow += vertSubdivisions; + } + baseCol += horzSubdivisions; + } + verts.SetNum( outWidth * outHeight ); + for ( i = 0; i < outWidth * outHeight; i++ ) { + verts[i] = dv[i]; + } + + delete[] dv; + + width = maxWidth = outWidth; + height = maxHeight = outHeight; + expanded = false; + + if ( removeLinear ) { + Expand(); + RemoveLinearColumnsRows(); + Collapse(); + } + + // normalize all the lerped normals + if ( genNormals ) { + for ( i = 0; i < width * height; i++ ) { + verts[i].normal.Normalize(); + } + } + + GenerateIndexes(); +} + +/* +================= +idSurface_Patch::Subdivide +================= +*/ +void idSurface_Patch::Subdivide( float maxHorizontalError, float maxVerticalError, float maxLength, bool genNormals ) { + int i, j, k, l; + idDrawVert prev, next, mid; + idVec3 prevxyz, nextxyz, midxyz; + idVec3 delta; + float maxHorizontalErrorSqr, maxVerticalErrorSqr, maxLengthSqr; + + // generate normals for the control mesh + if ( genNormals ) { + GenerateNormals(); + } + + maxHorizontalErrorSqr = Square( maxHorizontalError ); + maxVerticalErrorSqr = Square( maxVerticalError ); + maxLengthSqr = Square( maxLength ); + + Expand(); + + // horizontal subdivisions + for ( j = 0; j + 2 < width; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0; i < height; i++ ) { + for ( l = 0; l < 3; l++ ) { + prevxyz[l] = verts[i*maxWidth + j+1].xyz[l] - verts[i*maxWidth + j ].xyz[l]; + nextxyz[l] = verts[i*maxWidth + j+2].xyz[l] - verts[i*maxWidth + j+1].xyz[l]; + midxyz[l] = (verts[i*maxWidth + j ].xyz[l] + verts[i*maxWidth + j+1].xyz[l] * 2.0f + + verts[i*maxWidth + j+2].xyz[l] ) * 0.25f; + } + + if ( maxLength > 0.0f ) { + // if the span length is too long, force a subdivision + if ( prevxyz.LengthSqr() > maxLengthSqr || nextxyz.LengthSqr() > maxLengthSqr ) { + break; + } + } + // see if this midpoint is off far enough to subdivide + delta = verts[i*maxWidth + j+1].xyz - midxyz; + if ( delta.LengthSqr() > maxHorizontalErrorSqr ) { + break; + } + } + + if ( i == height ) { + continue; // didn't need subdivision + } + + if ( width + 2 >= maxWidth ) { + ResizeExpanded( maxHeight, maxWidth + 4 ); + } + + // insert two columns and replace the peak + width += 2; + + for ( i = 0; i < height; i++ ) { + idSurface_Patch::LerpVert( verts[i*maxWidth + j ], verts[i*maxWidth + j+1], prev ); + idSurface_Patch::LerpVert( verts[i*maxWidth + j+1], verts[i*maxWidth + j+2], next ); + idSurface_Patch::LerpVert( prev, next, mid ); + + for ( k = width - 1; k > j + 3; k-- ) { + verts[i*maxWidth + k] = verts[i*maxWidth + k-2]; + } + verts[i*maxWidth + j+1] = prev; + verts[i*maxWidth + j+2] = mid; + verts[i*maxWidth + j+3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + } + + // vertical subdivisions + for ( j = 0; j + 2 < height; j += 2 ) { + // check subdivided midpoints against control points + for ( i = 0; i < width; i++ ) { + for ( l = 0; l < 3; l++ ) { + prevxyz[l] = verts[(j+1)*maxWidth + i].xyz[l] - verts[j*maxWidth + i].xyz[l]; + nextxyz[l] = verts[(j+2)*maxWidth + i].xyz[l] - verts[(j+1)*maxWidth + i].xyz[l]; + midxyz[l] = (verts[j*maxWidth + i].xyz[l] + verts[(j+1)*maxWidth + i].xyz[l] * 2.0f + + verts[(j+2)*maxWidth + i].xyz[l] ) * 0.25f; + } + + if ( maxLength > 0.0f ) { + // if the span length is too long, force a subdivision + if ( prevxyz.LengthSqr() > maxLengthSqr || nextxyz.LengthSqr() > maxLengthSqr ) { + break; + } + } + // see if this midpoint is off far enough to subdivide + delta = verts[(j+1)*maxWidth + i].xyz - midxyz; + if ( delta.LengthSqr() > maxVerticalErrorSqr ) { + break; + } + } + + if ( i == width ) { + continue; // didn't need subdivision + } + + if ( height + 2 >= maxHeight ) { + ResizeExpanded( maxHeight + 4, maxWidth ); + } + + // insert two columns and replace the peak + height += 2; + + for ( i = 0; i < width; i++ ) { + LerpVert( verts[j*maxWidth + i], verts[(j+1)*maxWidth + i], prev ); + LerpVert( verts[(j+1)*maxWidth + i], verts[(j+2)*maxWidth + i], next ); + LerpVert( prev, next, mid ); + + for ( k = height - 1; k > j + 3; k-- ) { + verts[k*maxWidth + i] = verts[(k-2)*maxWidth + i]; + } + verts[(j+1)*maxWidth + i] = prev; + verts[(j+2)*maxWidth + i] = mid; + verts[(j+3)*maxWidth + i] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + } + + PutOnCurve(); + + RemoveLinearColumnsRows(); + + Collapse(); + + // normalize all the lerped normals + if ( genNormals ) { + for ( i = 0; i < width * height; i++ ) { + verts[i].normal.Normalize(); + } + } + + GenerateIndexes(); +} diff --git a/src/idlib/geometry/Surface_Patch.h b/src/idlib/geometry/Surface_Patch.h new file mode 100644 index 0000000..e33279a --- /dev/null +++ b/src/idlib/geometry/Surface_Patch.h @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SURFACE_PATCH_H__ +#define __SURFACE_PATCH_H__ + +/* +=============================================================================== + + Bezier patch surface. + +=============================================================================== +*/ + +class idSurface_Patch : public idSurface { + +public: + idSurface_Patch( void ); + idSurface_Patch( int maxPatchWidth, int maxPatchHeight ); + idSurface_Patch( const idSurface_Patch &patch ); + ~idSurface_Patch( void ); + + void SetSize( int patchWidth, int patchHeight ); + int GetWidth( void ) const; + int GetHeight( void ) const; + + // subdivide the patch mesh based on error + void Subdivide( float maxHorizontalError, float maxVerticalError, float maxLength, bool genNormals = false ); + // subdivide the patch up to an explicit number of horizontal and vertical subdivisions + void SubdivideExplicit( int horzSubdivisions, int vertSubdivisions, bool genNormals, bool removeLinear = false ); + +protected: + int width; // width of patch + int height; // height of patch + int maxWidth; // maximum width allocated for + int maxHeight; // maximum height allocated for + bool expanded; // true if vertices are spaced out + +private: + // put the approximation points on the curve + void PutOnCurve( void ); + // remove columns and rows with all points on one line + void RemoveLinearColumnsRows( void ); + // resize verts buffer + void ResizeExpanded( int height, int width ); + // space points out over maxWidth * maxHeight buffer + void Expand( void ); + // move all points to the start of the verts buffer + void Collapse( void ); + // project a point onto a vector to calculate maximum curve error + void ProjectPointOntoVector( const idVec3 &point, const idVec3 &vStart, const idVec3 &vEnd, idVec3 &vProj ); + // generate normals + void GenerateNormals( void ); + // generate triangle indexes + void GenerateIndexes( void ); + // lerp point from two patch point + void LerpVert( const idDrawVert &a, const idDrawVert &b, idDrawVert &out ) const; + // sample a single 3x3 patch + void SampleSinglePatchPoint( const idDrawVert ctrl[3][3], float u, float v, idDrawVert *out ) const; + void SampleSinglePatch( const idDrawVert ctrl[3][3], int baseCol, int baseRow, int width, int horzSub, int vertSub, idDrawVert *outVerts ) const; +}; + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( void ) { + height = width = maxHeight = maxWidth = 0; + expanded = false; +} + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( int maxPatchWidth, int maxPatchHeight ) { + width = height = 0; + maxWidth = maxPatchWidth; + maxHeight = maxPatchHeight; + verts.SetNum( maxWidth * maxHeight ); + expanded = false; +} + +/* +================= +idSurface_Patch::idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::idSurface_Patch( const idSurface_Patch &patch ) { + (*this) = patch; +} + +/* +================= +idSurface_Patch::~idSurface_Patch +================= +*/ +ID_INLINE idSurface_Patch::~idSurface_Patch() { +} + +/* +================= +idSurface_Patch::GetWidth +================= +*/ +ID_INLINE int idSurface_Patch::GetWidth( void ) const { + return width; +} + +/* +================= +idSurface_Patch::GetHeight +================= +*/ +ID_INLINE int idSurface_Patch::GetHeight( void ) const { + return height; +} + +#endif /* !__SURFACE_PATCH_H__ */ diff --git a/src/idlib/geometry/Surface_Polytope.cpp b/src/idlib/geometry/Surface_Polytope.cpp new file mode 100644 index 0000000..bbc362e --- /dev/null +++ b/src/idlib/geometry/Surface_Polytope.cpp @@ -0,0 +1,332 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#define POLYTOPE_VERTEX_EPSILON 0.1f + + +/* +==================== +idSurface_Polytope::FromPlanes +==================== +*/ +void idSurface_Polytope::FromPlanes( const idPlane *planes, const int numPlanes ) { + int i, j, k, *windingVerts; + idFixedWinding w; + idDrawVert newVert; + + windingVerts = (int *) _alloca( MAX_POINTS_ON_WINDING * sizeof( int ) ); + memset( &newVert, 0, sizeof( newVert ) ); + + for ( i = 0; i < numPlanes; i++ ) { + + w.BaseForPlane( planes[i] ); + + for ( j = 0; j < numPlanes; j++ ) { + if ( j == i ) { + continue; + } + if ( !w.ClipInPlace( -planes[j], ON_EPSILON, true ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; + } + + for ( j = 0; j < w.GetNumPoints(); j++ ) { + for ( k = 0; k < verts.Num(); j++ ) { + if ( verts[k].xyz.Compare( w[j].ToVec3(), POLYTOPE_VERTEX_EPSILON ) ) { + break; + } + } + if ( k >= verts.Num() ) { + newVert.xyz = w[j].ToVec3(); + k = verts.Append( newVert ); + } + windingVerts[j] = k; + } + + for ( j = 2; j < w.GetNumPoints(); j++ ) { + indexes.Append( windingVerts[0] ); + indexes.Append( windingVerts[j-1] ); + indexes.Append( windingVerts[j] ); + } + } + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupTetrahedron +==================== +*/ +void idSurface_Polytope::SetupTetrahedron( const idBounds &bounds ) { + idVec3 center, scale; + float c1, c2, c3; + + c1 = 0.4714045207f; + c2 = 0.8164965809f; + c3 = -0.3333333333f; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 4 ); + verts[0].xyz = center + idVec3( 0.0f, 0.0f, scale.z ); + verts[1].xyz = center + idVec3( 2.0f * c1 * scale.x, 0.0f, c3 * scale.z ); + verts[2].xyz = center + idVec3( -c1 * scale.x, c2 * scale.y, c3 * scale.z ); + verts[3].xyz = center + idVec3( -c1 * scale.x, -c2 * scale.y, c3 * scale.z ); + + indexes.SetNum( 4*3 ); + indexes[0*3+0] = 0; + indexes[0*3+1] = 1; + indexes[0*3+2] = 2; + indexes[1*3+0] = 0; + indexes[1*3+1] = 2; + indexes[1*3+2] = 3; + indexes[2*3+0] = 0; + indexes[2*3+1] = 3; + indexes[2*3+2] = 1; + indexes[3*3+0] = 1; + indexes[3*3+1] = 3; + indexes[3*3+2] = 2; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupHexahedron +==================== +*/ +void idSurface_Polytope::SetupHexahedron( const idBounds &bounds ) { + idVec3 center, scale; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 8 ); + verts[0].xyz = center + idVec3( -scale.x, -scale.y, -scale.z ); + verts[1].xyz = center + idVec3( scale.x, -scale.y, -scale.z ); + verts[2].xyz = center + idVec3( scale.x, scale.y, -scale.z ); + verts[3].xyz = center + idVec3( -scale.x, scale.y, -scale.z ); + verts[4].xyz = center + idVec3( -scale.x, -scale.y, scale.z ); + verts[5].xyz = center + idVec3( scale.x, -scale.y, scale.z ); + verts[6].xyz = center + idVec3( scale.x, scale.y, scale.z ); + verts[7].xyz = center + idVec3( -scale.x, scale.y, scale.z ); + + indexes.SetNum( 12*3 ); + indexes[ 0*3+0] = 0; + indexes[ 0*3+1] = 3; + indexes[ 0*3+2] = 2; + indexes[ 1*3+0] = 0; + indexes[ 1*3+1] = 2; + indexes[ 1*3+2] = 1; + indexes[ 2*3+0] = 0; + indexes[ 2*3+1] = 1; + indexes[ 2*3+2] = 5; + indexes[ 3*3+0] = 0; + indexes[ 3*3+1] = 5; + indexes[ 3*3+2] = 4; + indexes[ 4*3+0] = 0; + indexes[ 4*3+1] = 4; + indexes[ 4*3+2] = 7; + indexes[ 5*3+0] = 0; + indexes[ 5*3+1] = 7; + indexes[ 5*3+2] = 3; + indexes[ 6*3+0] = 6; + indexes[ 6*3+1] = 5; + indexes[ 6*3+2] = 1; + indexes[ 7*3+0] = 6; + indexes[ 7*3+1] = 1; + indexes[ 7*3+2] = 2; + indexes[ 8*3+0] = 6; + indexes[ 8*3+1] = 2; + indexes[ 8*3+2] = 3; + indexes[ 9*3+0] = 6; + indexes[ 9*3+1] = 3; + indexes[ 9*3+2] = 7; + indexes[10*3+0] = 6; + indexes[10*3+1] = 7; + indexes[10*3+2] = 4; + indexes[11*3+0] = 6; + indexes[11*3+1] = 4; + indexes[11*3+2] = 5; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupOctahedron +==================== +*/ +void idSurface_Polytope::SetupOctahedron( const idBounds &bounds ) { + idVec3 center, scale; + + center = bounds.GetCenter(); + scale = bounds[1] - center; + + verts.SetNum( 6 ); + verts[0].xyz = center + idVec3( scale.x, 0.0f, 0.0f ); + verts[1].xyz = center + idVec3( -scale.x, 0.0f, 0.0f ); + verts[2].xyz = center + idVec3( 0.0f, scale.y, 0.0f ); + verts[3].xyz = center + idVec3( 0.0f, -scale.y, 0.0f ); + verts[4].xyz = center + idVec3( 0.0f, 0.0f, scale.z ); + verts[5].xyz = center + idVec3( 0.0f, 0.0f, -scale.z ); + + indexes.SetNum( 8*3 ); + indexes[0*3+0] = 4; + indexes[0*3+1] = 0; + indexes[0*3+2] = 2; + indexes[1*3+0] = 4; + indexes[1*3+1] = 2; + indexes[1*3+2] = 1; + indexes[2*3+0] = 4; + indexes[2*3+1] = 1; + indexes[2*3+2] = 3; + indexes[3*3+0] = 4; + indexes[3*3+1] = 3; + indexes[3*3+2] = 0; + indexes[4*3+0] = 5; + indexes[4*3+1] = 2; + indexes[4*3+2] = 0; + indexes[5*3+0] = 5; + indexes[5*3+1] = 1; + indexes[5*3+2] = 2; + indexes[6*3+0] = 5; + indexes[6*3+1] = 3; + indexes[6*3+2] = 1; + indexes[7*3+0] = 5; + indexes[7*3+1] = 0; + indexes[7*3+2] = 3; + + GenerateEdgeIndexes(); +} + +/* +==================== +idSurface_Polytope::SetupDodecahedron +==================== +*/ +void idSurface_Polytope::SetupDodecahedron( const idBounds &bounds ) { +} + +/* +==================== +idSurface_Polytope::SetupIcosahedron +==================== +*/ +void idSurface_Polytope::SetupIcosahedron( const idBounds &bounds ) { +} + +/* +==================== +idSurface_Polytope::SetupCylinder +==================== +*/ +void idSurface_Polytope::SetupCylinder( const idBounds &bounds, const int numSides ) { +} + +/* +==================== +idSurface_Polytope::SetupCone +==================== +*/ +void idSurface_Polytope::SetupCone( const idBounds &bounds, const int numSides ) { +} + +/* +==================== +idSurface_Polytope::SplitPolytope +==================== +*/ +int idSurface_Polytope::SplitPolytope( const idPlane &plane, const float epsilon, idSurface_Polytope **front, idSurface_Polytope **back ) const { + int side, i, j, s, v0, v1, v2, edgeNum; + idSurface *surface[2]; + idSurface_Polytope *polytopeSurfaces[2], *surf; + int *onPlaneEdges[2]; + + onPlaneEdges[0] = (int *) _alloca( indexes.Num() / 3 * sizeof( int ) ); + onPlaneEdges[1] = (int *) _alloca( indexes.Num() / 3 * sizeof( int ) ); + + side = Split( plane, epsilon, &surface[0], &surface[1], onPlaneEdges[0], onPlaneEdges[1] ); + + *front = polytopeSurfaces[0] = new idSurface_Polytope; + *back = polytopeSurfaces[1] = new idSurface_Polytope; + + for ( s = 0; s < 2; s++ ) { + if ( surface[s] ) { + polytopeSurfaces[s] = new idSurface_Polytope; + polytopeSurfaces[s]->SwapTriangles( *surface[s] ); + delete surface[s]; + surface[s] = NULL; + } + } + + *front = polytopeSurfaces[0]; + *back = polytopeSurfaces[1]; + + if ( side != SIDE_CROSS ) { + return side; + } + + // add triangles to close off the front and back polytope + for ( s = 0; s < 2; s++ ) { + + surf = polytopeSurfaces[s]; + + edgeNum = surf->edgeIndexes[onPlaneEdges[s][0]]; + v0 = surf->edges[abs(edgeNum)].verts[INTSIGNBITSET(edgeNum)]; + v1 = surf->edges[abs(edgeNum)].verts[INTSIGNBITNOTSET(edgeNum)]; + + for ( i = 1; onPlaneEdges[s][i] >= 0; i++ ) { + for ( j = i+1; onPlaneEdges[s][j] >= 0; j++ ) { + edgeNum = surf->edgeIndexes[onPlaneEdges[s][j]]; + if ( v1 == surf->edges[abs(edgeNum)].verts[INTSIGNBITSET(edgeNum)] ) { + v1 = surf->edges[abs(edgeNum)].verts[INTSIGNBITNOTSET(edgeNum)]; + idSwap( onPlaneEdges[s][i], onPlaneEdges[s][j] ); + break; + } + } + } + + for ( i = 2; onPlaneEdges[s][i] >= 0; i++ ) { + edgeNum = surf->edgeIndexes[onPlaneEdges[s][i]]; + v1 = surf->edges[abs(edgeNum)].verts[INTSIGNBITNOTSET(edgeNum)]; + v2 = surf->edges[abs(edgeNum)].verts[INTSIGNBITSET(edgeNum)]; + surf->indexes.Append( v0 ); + surf->indexes.Append( v1 ); + surf->indexes.Append( v2 ); + } + + surf->GenerateEdgeIndexes(); + } + + return side; +} diff --git a/src/idlib/geometry/Surface_Polytope.h b/src/idlib/geometry/Surface_Polytope.h new file mode 100644 index 0000000..0e3552c --- /dev/null +++ b/src/idlib/geometry/Surface_Polytope.h @@ -0,0 +1,66 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SURFACE_POLYTOPE_H__ +#define __SURFACE_POLYTOPE_H__ + +/* +=============================================================================== + + Polytope surface. + + NOTE: vertexes are not duplicated for texture coordinates. + +=============================================================================== +*/ + +class idSurface_Polytope : public idSurface { +public: + idSurface_Polytope( void ); + + void FromPlanes( const idPlane *planes, const int numPlanes ); + + void SetupTetrahedron( const idBounds &bounds ); + void SetupHexahedron( const idBounds &bounds ); + void SetupOctahedron( const idBounds &bounds ); + void SetupDodecahedron( const idBounds &bounds ); + void SetupIcosahedron( const idBounds &bounds ); + void SetupCylinder( const idBounds &bounds, const int numSides ); + void SetupCone( const idBounds &bounds, const int numSides ); + + int SplitPolytope( const idPlane &plane, const float epsilon, idSurface_Polytope **front, idSurface_Polytope **back ) const; + +protected: + +}; + +/* +==================== +idSurface_Polytope::idSurface_Polytope +==================== +*/ +ID_INLINE idSurface_Polytope::idSurface_Polytope( void ) { +} + +#endif /* !__SURFACE_POLYTOPE_H__ */ diff --git a/src/idlib/geometry/Surface_SweptSpline.cpp b/src/idlib/geometry/Surface_SweptSpline.cpp new file mode 100644 index 0000000..2e15572 --- /dev/null +++ b/src/idlib/geometry/Surface_SweptSpline.cpp @@ -0,0 +1,219 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +==================== +idSurface_SweptSpline::SetSpline +==================== +*/ +void idSurface_SweptSpline::SetSpline( idCurve_Spline *spline ) { + if ( this->spline ) { + delete this->spline; + } + this->spline = spline; +} + +/* +==================== +idSurface_SweptSpline::SetSweptSpline +==================== +*/ +void idSurface_SweptSpline::SetSweptSpline( idCurve_Spline *sweptSpline ) { + if ( this->sweptSpline ) { + delete this->sweptSpline; + } + this->sweptSpline = sweptSpline; +} + +/* +==================== +idSurface_SweptSpline::SetSweptCircle + + Sets the swept spline to a NURBS circle. +==================== +*/ +void idSurface_SweptSpline::SetSweptCircle( const float radius ) { + idCurve_NURBS *nurbs = new idCurve_NURBS(); + nurbs->Clear(); + nurbs->AddValue( 0.0f, idVec4( radius, radius, 0.0f, 0.00f ) ); + nurbs->AddValue( 100.0f, idVec4( -radius, radius, 0.0f, 0.25f ) ); + nurbs->AddValue( 200.0f, idVec4( -radius, -radius, 0.0f, 0.50f ) ); + nurbs->AddValue( 300.0f, idVec4( radius, -radius, 0.0f, 0.75f ) ); + nurbs->SetBoundaryType( idCurve_NURBS::BT_CLOSED ); + nurbs->SetCloseTime( 100.0f ); + if ( sweptSpline ) { + delete sweptSpline; + } + sweptSpline = nurbs; +} + +/* +==================== +idSurface_SweptSpline::GetFrame +==================== +*/ +void idSurface_SweptSpline::GetFrame( const idMat3 &previousFrame, const idVec3 dir, idMat3 &newFrame ) { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + float a, c, s, x, y, z; + idVec3 d, v; + idMat3 axis; + + d = dir; + d.Normalize(); + v = d.Cross( previousFrame[2] ); + v.Normalize(); + + a = idMath::ACos( previousFrame[2] * d ) * 0.5f; + c = idMath::Cos( a ); + s = idMath::Sqrt( 1.0f - c * c ); + + x = v[0] * s; + y = v[1] * s; + z = v[2] * s; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + xx = x * x2; + xy = x * y2; + xz = x * z2; + yy = y * y2; + yz = y * z2; + zz = z * z2; + wx = c * x2; + wy = c * y2; + wz = c * z2; + + axis[0][0] = 1.0f - ( yy + zz ); + axis[0][1] = xy - wz; + axis[0][2] = xz + wy; + axis[1][0] = xy + wz; + axis[1][1] = 1.0f - ( xx + zz ); + axis[1][2] = yz - wx; + axis[2][0] = xz - wy; + axis[2][1] = yz + wx; + axis[2][2] = 1.0f - ( xx + yy ); + + newFrame = previousFrame * axis; + + newFrame[2] = dir; + newFrame[2].Normalize(); + newFrame[1].Cross( newFrame[ 2 ], newFrame[ 0 ] ); + newFrame[1].Normalize(); + newFrame[0].Cross( newFrame[ 1 ], newFrame[ 2 ] ); + newFrame[0].Normalize(); +} + +/* +==================== +idSurface_SweptSpline::Tessellate + + tesselate the surface +==================== +*/ +void idSurface_SweptSpline::Tessellate( const int splineSubdivisions, const int sweptSplineSubdivisions ) { + int i, j, offset, baseOffset, splineDiv, sweptSplineDiv; + int i0, i1, j0, j1; + float totalTime, t; + idVec4 splinePos, splineD1; + idMat3 splineMat; + + if ( !spline || !sweptSpline ) { + idSurface::Clear(); + return; + } + + verts.SetNum( splineSubdivisions * sweptSplineSubdivisions, false ); + + // calculate the points and first derivatives for the swept spline + totalTime = sweptSpline->GetTime( sweptSpline->GetNumValues() - 1 ) - sweptSpline->GetTime( 0 ) + sweptSpline->GetCloseTime(); + sweptSplineDiv = sweptSpline->GetBoundaryType() == idCurve_Spline::BT_CLOSED ? sweptSplineSubdivisions : sweptSplineSubdivisions - 1; + baseOffset = (splineSubdivisions-1) * sweptSplineSubdivisions; + for ( i = 0; i < sweptSplineSubdivisions; i++ ) { + t = totalTime * i / sweptSplineDiv; + splinePos = sweptSpline->GetCurrentValue( t ); + splineD1 = sweptSpline->GetCurrentFirstDerivative( t ); + verts[baseOffset+i].xyz = splinePos.ToVec3(); + verts[baseOffset+i].st[0] = splinePos.w; + verts[baseOffset+i].tangents[0] = splineD1.ToVec3(); + } + + // sweep the spline + totalTime = spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) + spline->GetCloseTime(); + splineDiv = spline->GetBoundaryType() == idCurve_Spline::BT_CLOSED ? splineSubdivisions : splineSubdivisions - 1; + splineMat.Identity(); + for ( i = 0; i < splineSubdivisions; i++ ) { + t = totalTime * i / splineDiv; + + splinePos = spline->GetCurrentValue( t ); + splineD1 = spline->GetCurrentFirstDerivative( t ); + + GetFrame( splineMat, splineD1.ToVec3(), splineMat ); + + offset = i * sweptSplineSubdivisions; + for ( j = 0; j < sweptSplineSubdivisions; j++ ) { + idDrawVert *v = &verts[offset+j]; + v->xyz = splinePos.ToVec3() + verts[baseOffset+j].xyz * splineMat; + v->st[0] = verts[baseOffset+j].st[0]; + v->st[1] = splinePos.w; + v->tangents[0] = verts[baseOffset+j].tangents[0] * splineMat; + v->tangents[1] = splineD1.ToVec3(); + v->normal = v->tangents[1].Cross( v->tangents[0] ); + v->normal.Normalize(); + v->color[0] = v->color[1] = v->color[2] = v->color[3] = 0; + } + } + + indexes.SetNum( splineDiv * sweptSplineDiv * 2 * 3, false ); + + // create indexes for the triangles + for ( offset = i = 0; i < splineDiv; i++ ) { + + i0 = (i+0) * sweptSplineSubdivisions; + i1 = (i+1) % splineSubdivisions * sweptSplineSubdivisions; + + for ( j = 0; j < sweptSplineDiv; j++ ) { + + j0 = (j+0); + j1 = (j+1) % sweptSplineSubdivisions; + + indexes[offset++] = i0 + j0; + indexes[offset++] = i0 + j1; + indexes[offset++] = i1 + j1; + + indexes[offset++] = i1 + j1; + indexes[offset++] = i1 + j0; + indexes[offset++] = i0 + j0; + } + } + + GenerateEdgeIndexes(); +} diff --git a/src/idlib/geometry/Surface_SweptSpline.h b/src/idlib/geometry/Surface_SweptSpline.h new file mode 100644 index 0000000..a4a1d62 --- /dev/null +++ b/src/idlib/geometry/Surface_SweptSpline.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SURFACE_SWEPTSPLINE_H__ +#define __SURFACE_SWEPTSPLINE_H__ + +/* +=============================================================================== + + Swept Spline surface. + +=============================================================================== +*/ + +class idSurface_SweptSpline : public idSurface { +public: + idSurface_SweptSpline( void ); + ~idSurface_SweptSpline( void ); + + void SetSpline( idCurve_Spline *spline ); + void SetSweptSpline( idCurve_Spline *sweptSpline ); + void SetSweptCircle( const float radius ); + + void Tessellate( const int splineSubdivisions, const int sweptSplineSubdivisions ); + + void Clear( void ); + +protected: + idCurve_Spline *spline; + idCurve_Spline *sweptSpline; + + void GetFrame( const idMat3 &previousFrame, const idVec3 dir, idMat3 &newFrame ); +}; + +/* +==================== +idSurface_SweptSpline::idSurface_SweptSpline +==================== +*/ +ID_INLINE idSurface_SweptSpline::idSurface_SweptSpline( void ) { + spline = NULL; + sweptSpline = NULL; +} + +/* +==================== +idSurface_SweptSpline::~idSurface_SweptSpline +==================== +*/ +ID_INLINE idSurface_SweptSpline::~idSurface_SweptSpline( void ) { + delete spline; + delete sweptSpline; +} + +/* +==================== +idSurface_SweptSpline::Clear +==================== +*/ +ID_INLINE void idSurface_SweptSpline::Clear( void ) { + idSurface::Clear(); + delete spline; + spline = NULL; + delete sweptSpline; + sweptSpline = NULL; +} + +#endif /* !__SURFACE_SWEPTSPLINE_H__ */ diff --git a/src/idlib/geometry/TraceModel.cpp b/src/idlib/geometry/TraceModel.cpp new file mode 100644 index 0000000..1b7374b --- /dev/null +++ b/src/idlib/geometry/TraceModel.cpp @@ -0,0 +1,1657 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "TraceModel.h" + + +/* +============ +idTraceModel::SetupBox +============ +*/ +void idTraceModel::SetupBox( const idBounds &boxBounds ) { + int i; + + if ( type != TRM_BOX ) { + InitBox(); + } + // offset to center + offset = ( boxBounds[0] + boxBounds[1] ) * 0.5f; + // set box vertices + for ( i = 0; i < 8; i++ ) { + verts[i][0] = boxBounds[(i^(i>>1))&1][0]; + verts[i][1] = boxBounds[(i>>1)&1][1]; + verts[i][2] = boxBounds[(i>>2)&1][2]; + } + // set polygon plane distances + polys[0].dist = -boxBounds[0][2]; + polys[1].dist = boxBounds[1][2]; + polys[2].dist = -boxBounds[0][1]; + polys[3].dist = boxBounds[1][0]; + polys[4].dist = boxBounds[1][1]; + polys[5].dist = -boxBounds[0][0]; + // set polygon bounds + for ( i = 0; i < 6; i++ ) { + polys[i].bounds = boxBounds; + } + polys[0].bounds[1][2] = boxBounds[0][2]; + polys[1].bounds[0][2] = boxBounds[1][2]; + polys[2].bounds[1][1] = boxBounds[0][1]; + polys[3].bounds[0][0] = boxBounds[1][0]; + polys[4].bounds[0][1] = boxBounds[1][1]; + polys[5].bounds[1][0] = boxBounds[0][0]; + + bounds = boxBounds; +} + +/* +============ +idTraceModel::SetupBox + + The origin is placed at the center of the cube. +============ +*/ +void idTraceModel::SetupBox( const float size ) { + idBounds boxBounds; + float halfSize; + + halfSize = size * 0.5f; + boxBounds[0].Set( -halfSize, -halfSize, -halfSize ); + boxBounds[1].Set( halfSize, halfSize, halfSize ); + SetupBox( boxBounds ); +} + +/* +============ +idTraceModel::InitBox + + Initialize size independent box. +============ +*/ +void idTraceModel::InitBox( void ) { + int i; + + type = TRM_BOX; + numVerts = 8; + numEdges = 12; + numPolys = 6; + + // set box edges + for ( i = 0; i < 4; i++ ) { + edges[ i + 1 ].v[0] = i; + edges[ i + 1 ].v[1] = (i + 1) & 3; + edges[ i + 5 ].v[0] = 4 + i; + edges[ i + 5 ].v[1] = 4 + ((i + 1) & 3); + edges[ i + 9 ].v[0] = i; + edges[ i + 9 ].v[1] = 4 + i; + } + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 4; + polys[0].edges[0] = -4; + polys[0].edges[1] = -3; + polys[0].edges[2] = -2; + polys[0].edges[3] = -1; + polys[0].normal.Set( 0.0f, 0.0f, -1.0f ); + + polys[1].numEdges = 4; + polys[1].edges[0] = 5; + polys[1].edges[1] = 6; + polys[1].edges[2] = 7; + polys[1].edges[3] = 8; + polys[1].normal.Set( 0.0f, 0.0f, 1.0f ); + + polys[2].numEdges = 4; + polys[2].edges[0] = 1; + polys[2].edges[1] = 10; + polys[2].edges[2] = -5; + polys[2].edges[3] = -9; + polys[2].normal.Set( 0.0f, -1.0f, 0.0f ); + + polys[3].numEdges = 4; + polys[3].edges[0] = 2; + polys[3].edges[1] = 11; + polys[3].edges[2] = -6; + polys[3].edges[3] = -10; + polys[3].normal.Set( 1.0f, 0.0f, 0.0f ); + + polys[4].numEdges = 4; + polys[4].edges[0] = 3; + polys[4].edges[1] = 12; + polys[4].edges[2] = -7; + polys[4].edges[3] = -11; + polys[4].normal.Set( 0.0f, 1.0f, 0.0f ); + + polys[5].numEdges = 4; + polys[5].edges[0] = 4; + polys[5].edges[1] = 9; + polys[5].edges[2] = -8; + polys[5].edges[3] = -12; + polys[5].normal.Set( -1.0f, 0.0f, 0.0f ); + + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupOctahedron +============ +*/ +void idTraceModel::SetupOctahedron( const idBounds &octBounds ) { + int i, e0, e1, v0, v1, v2; + idVec3 v; + + if ( type != TRM_OCTAHEDRON ) { + InitOctahedron(); + } + + offset = ( octBounds[0] + octBounds[1] ) * 0.5f; + v[0] = octBounds[1][0] - offset[0]; + v[1] = octBounds[1][1] - offset[1]; + v[2] = octBounds[1][2] - offset[2]; + + // set vertices + verts[0].Set( offset.x + v[0], offset.y, offset.z ); + verts[1].Set( offset.x - v[0], offset.y, offset.z ); + verts[2].Set( offset.x, offset.y + v[1], offset.z ); + verts[3].Set( offset.x, offset.y - v[1], offset.z ); + verts[4].Set( offset.x, offset.y, offset.z + v[2] ); + verts[5].Set( offset.x, offset.y, offset.z - v[2] ); + + // set polygons + for ( i = 0; i < numPolys; i++ ) { + e0 = polys[i].edges[0]; + e1 = polys[i].edges[1]; + v0 = edges[abs(e0)].v[INTSIGNBITSET(e0)]; + v1 = edges[abs(e0)].v[INTSIGNBITNOTSET(e0)]; + v2 = edges[abs(e1)].v[INTSIGNBITNOTSET(e1)]; + // polygon plane + polys[i].normal = ( verts[v1] - verts[v0] ).Cross( verts[v2] - verts[v0] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[v0]; + // polygon bounds + polys[i].bounds[0] = polys[i].bounds[1] = verts[v0]; + polys[i].bounds.AddPoint( verts[v1] ); + polys[i].bounds.AddPoint( verts[v2] ); + } + + // trm bounds + bounds = octBounds; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupOctahedron + + The origin is placed at the center of the octahedron. +============ +*/ +void idTraceModel::SetupOctahedron( const float size ) { + idBounds octBounds; + float halfSize; + + halfSize = size * 0.5f; + octBounds[0].Set( -halfSize, -halfSize, -halfSize ); + octBounds[1].Set( halfSize, halfSize, halfSize ); + SetupOctahedron( octBounds ); +} + +/* +============ +idTraceModel::InitOctahedron + + Initialize size independent octahedron. +============ +*/ +void idTraceModel::InitOctahedron( void ) { + + type = TRM_OCTAHEDRON; + numVerts = 6; + numEdges = 12; + numPolys = 8; + + // set edges + edges[ 1].v[0] = 4; edges[ 1].v[1] = 0; + edges[ 2].v[0] = 0; edges[ 2].v[1] = 2; + edges[ 3].v[0] = 2; edges[ 3].v[1] = 4; + edges[ 4].v[0] = 2; edges[ 4].v[1] = 1; + edges[ 5].v[0] = 1; edges[ 5].v[1] = 4; + edges[ 6].v[0] = 1; edges[ 6].v[1] = 3; + edges[ 7].v[0] = 3; edges[ 7].v[1] = 4; + edges[ 8].v[0] = 3; edges[ 8].v[1] = 0; + edges[ 9].v[0] = 5; edges[ 9].v[1] = 2; + edges[10].v[0] = 0; edges[10].v[1] = 5; + edges[11].v[0] = 5; edges[11].v[1] = 1; + edges[12].v[0] = 5; edges[12].v[1] = 3; + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 3; + polys[0].edges[0] = 1; + polys[0].edges[1] = 2; + polys[0].edges[2] = 3; + + polys[1].numEdges = 3; + polys[1].edges[0] = -3; + polys[1].edges[1] = 4; + polys[1].edges[2] = 5; + + polys[2].numEdges = 3; + polys[2].edges[0] = -5; + polys[2].edges[1] = 6; + polys[2].edges[2] = 7; + + polys[3].numEdges = 3; + polys[3].edges[0] = -7; + polys[3].edges[1] = 8; + polys[3].edges[2] = -1; + + polys[4].numEdges = 3; + polys[4].edges[0] = 9; + polys[4].edges[1] = -2; + polys[4].edges[2] = 10; + + polys[5].numEdges = 3; + polys[5].edges[0] = 11; + polys[5].edges[1] = -4; + polys[5].edges[2] = -9; + + polys[6].numEdges = 3; + polys[6].edges[0] = 12; + polys[6].edges[1] = -6; + polys[6].edges[2] = -11; + + polys[7].numEdges = 3; + polys[7].edges[0] = -10; + polys[7].edges[1] = -8; + polys[7].edges[2] = -12; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupDodecahedron +============ +*/ +void idTraceModel::SetupDodecahedron( const idBounds &dodBounds ) { + int i, e0, e1, e2, e3, v0, v1, v2, v3, v4; + float s, d; + idVec3 a, b, c; + + if ( type != TRM_DODECAHEDRON ) { + InitDodecahedron(); + } + + a[0] = a[1] = a[2] = 0.5773502691896257f; // 1.0f / ( 3.0f ) ^ 0.5f; + b[0] = b[1] = b[2] = 0.3568220897730899f; // ( ( 3.0f - ( 5.0f ) ^ 0.5f ) / 6.0f ) ^ 0.5f; + c[0] = c[1] = c[2] = 0.9341723589627156f; // ( ( 3.0f + ( 5.0f ) ^ 0.5f ) / 6.0f ) ^ 0.5f; + d = 0.5f / c[0]; + s = ( dodBounds[1][0] - dodBounds[0][0] ) * d; + a[0] *= s; + b[0] *= s; + c[0] *= s; + s = ( dodBounds[1][1] - dodBounds[0][1] ) * d; + a[1] *= s; + b[1] *= s; + c[1] *= s; + s = ( dodBounds[1][2] - dodBounds[0][2] ) * d; + a[2] *= s; + b[2] *= s; + c[2] *= s; + + offset = ( dodBounds[0] + dodBounds[1] ) * 0.5f; + + // set vertices + verts[ 0].Set( offset.x + a[0], offset.y + a[1], offset.z + a[2] ); + verts[ 1].Set( offset.x + a[0], offset.y + a[1], offset.z - a[2] ); + verts[ 2].Set( offset.x + a[0], offset.y - a[1], offset.z + a[2] ); + verts[ 3].Set( offset.x + a[0], offset.y - a[1], offset.z - a[2] ); + verts[ 4].Set( offset.x - a[0], offset.y + a[1], offset.z + a[2] ); + verts[ 5].Set( offset.x - a[0], offset.y + a[1], offset.z - a[2] ); + verts[ 6].Set( offset.x - a[0], offset.y - a[1], offset.z + a[2] ); + verts[ 7].Set( offset.x - a[0], offset.y - a[1], offset.z - a[2] ); + verts[ 8].Set( offset.x + b[0], offset.y + c[1], offset.z ); + verts[ 9].Set( offset.x - b[0], offset.y + c[1], offset.z ); + verts[10].Set( offset.x + b[0], offset.y - c[1], offset.z ); + verts[11].Set( offset.x - b[0], offset.y - c[1], offset.z ); + verts[12].Set( offset.x + c[0], offset.y , offset.z + b[2] ); + verts[13].Set( offset.x + c[0], offset.y , offset.z - b[2] ); + verts[14].Set( offset.x - c[0], offset.y , offset.z + b[2] ); + verts[15].Set( offset.x - c[0], offset.y , offset.z - b[2] ); + verts[16].Set( offset.x , offset.y + b[1], offset.z + c[2] ); + verts[17].Set( offset.x , offset.y - b[1], offset.z + c[2] ); + verts[18].Set( offset.x , offset.y + b[1], offset.z - c[2] ); + verts[19].Set( offset.x , offset.y - b[1], offset.z - c[2] ); + + // set polygons + for ( i = 0; i < numPolys; i++ ) { + e0 = polys[i].edges[0]; + e1 = polys[i].edges[1]; + e2 = polys[i].edges[2]; + e3 = polys[i].edges[3]; + v0 = edges[abs(e0)].v[INTSIGNBITSET(e0)]; + v1 = edges[abs(e0)].v[INTSIGNBITNOTSET(e0)]; + v2 = edges[abs(e1)].v[INTSIGNBITNOTSET(e1)]; + v3 = edges[abs(e2)].v[INTSIGNBITNOTSET(e2)]; + v4 = edges[abs(e3)].v[INTSIGNBITNOTSET(e3)]; + // polygon plane + polys[i].normal = ( verts[v1] - verts[v0] ).Cross( verts[v2] - verts[v0] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[v0]; + // polygon bounds + polys[i].bounds[0] = polys[i].bounds[1] = verts[v0]; + polys[i].bounds.AddPoint( verts[v1] ); + polys[i].bounds.AddPoint( verts[v2] ); + polys[i].bounds.AddPoint( verts[v3] ); + polys[i].bounds.AddPoint( verts[v4] ); + } + + // trm bounds + bounds = dodBounds; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupDodecahedron + + The origin is placed at the center of the octahedron. +============ +*/ +void idTraceModel::SetupDodecahedron( const float size ) { + idBounds dodBounds; + float halfSize; + + halfSize = size * 0.5f; + dodBounds[0].Set( -halfSize, -halfSize, -halfSize ); + dodBounds[1].Set( halfSize, halfSize, halfSize ); + SetupDodecahedron( dodBounds ); +} + +/* +============ +idTraceModel::InitDodecahedron + + Initialize size independent dodecahedron. +============ +*/ +void idTraceModel::InitDodecahedron( void ) { + + type = TRM_DODECAHEDRON; + numVerts = 20; + numEdges = 30; + numPolys = 12; + + // set edges + edges[ 1].v[0] = 0; edges[ 1].v[1] = 8; + edges[ 2].v[0] = 8; edges[ 2].v[1] = 9; + edges[ 3].v[0] = 9; edges[ 3].v[1] = 4; + edges[ 4].v[0] = 4; edges[ 4].v[1] = 16; + edges[ 5].v[0] = 16; edges[ 5].v[1] = 0; + edges[ 6].v[0] = 16; edges[ 6].v[1] = 17; + edges[ 7].v[0] = 17; edges[ 7].v[1] = 2; + edges[ 8].v[0] = 2; edges[ 8].v[1] = 12; + edges[ 9].v[0] = 12; edges[ 9].v[1] = 0; + edges[10].v[0] = 2; edges[10].v[1] = 10; + edges[11].v[0] = 10; edges[11].v[1] = 3; + edges[12].v[0] = 3; edges[12].v[1] = 13; + edges[13].v[0] = 13; edges[13].v[1] = 12; + edges[14].v[0] = 9; edges[14].v[1] = 5; + edges[15].v[0] = 5; edges[15].v[1] = 15; + edges[16].v[0] = 15; edges[16].v[1] = 14; + edges[17].v[0] = 14; edges[17].v[1] = 4; + edges[18].v[0] = 3; edges[18].v[1] = 19; + edges[19].v[0] = 19; edges[19].v[1] = 18; + edges[20].v[0] = 18; edges[20].v[1] = 1; + edges[21].v[0] = 1; edges[21].v[1] = 13; + edges[22].v[0] = 7; edges[22].v[1] = 11; + edges[23].v[0] = 11; edges[23].v[1] = 6; + edges[24].v[0] = 6; edges[24].v[1] = 14; + edges[25].v[0] = 15; edges[25].v[1] = 7; + edges[26].v[0] = 1; edges[26].v[1] = 8; + edges[27].v[0] = 18; edges[27].v[1] = 5; + edges[28].v[0] = 6; edges[28].v[1] = 17; + edges[29].v[0] = 11; edges[29].v[1] = 10; + edges[30].v[0] = 19; edges[30].v[1] = 7; + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 5; + polys[0].edges[0] = 1; + polys[0].edges[1] = 2; + polys[0].edges[2] = 3; + polys[0].edges[3] = 4; + polys[0].edges[4] = 5; + + polys[1].numEdges = 5; + polys[1].edges[0] = -5; + polys[1].edges[1] = 6; + polys[1].edges[2] = 7; + polys[1].edges[3] = 8; + polys[1].edges[4] = 9; + + polys[2].numEdges = 5; + polys[2].edges[0] = -8; + polys[2].edges[1] = 10; + polys[2].edges[2] = 11; + polys[2].edges[3] = 12; + polys[2].edges[4] = 13; + + polys[3].numEdges = 5; + polys[3].edges[0] = 14; + polys[3].edges[1] = 15; + polys[3].edges[2] = 16; + polys[3].edges[3] = 17; + polys[3].edges[4] = -3; + + polys[4].numEdges = 5; + polys[4].edges[0] = 18; + polys[4].edges[1] = 19; + polys[4].edges[2] = 20; + polys[4].edges[3] = 21; + polys[4].edges[4] = -12; + + polys[5].numEdges = 5; + polys[5].edges[0] = 22; + polys[5].edges[1] = 23; + polys[5].edges[2] = 24; + polys[5].edges[3] = -16; + polys[5].edges[4] = 25; + + polys[6].numEdges = 5; + polys[6].edges[0] = -9; + polys[6].edges[1] = -13; + polys[6].edges[2] = -21; + polys[6].edges[3] = 26; + polys[6].edges[4] = -1; + + polys[7].numEdges = 5; + polys[7].edges[0] = -26; + polys[7].edges[1] = -20; + polys[7].edges[2] = 27; + polys[7].edges[3] = -14; + polys[7].edges[4] = -2; + + polys[8].numEdges = 5; + polys[8].edges[0] = -4; + polys[8].edges[1] = -17; + polys[8].edges[2] = -24; + polys[8].edges[3] = 28; + polys[8].edges[4] = -6; + + polys[9].numEdges = 5; + polys[9].edges[0] = -23; + polys[9].edges[1] = 29; + polys[9].edges[2] = -10; + polys[9].edges[3] = -7; + polys[9].edges[4] = -28; + + polys[10].numEdges = 5; + polys[10].edges[0] = -25; + polys[10].edges[1] = -15; + polys[10].edges[2] = -27; + polys[10].edges[3] = -19; + polys[10].edges[4] = 30; + + polys[11].numEdges = 5; + polys[11].edges[0] = -30; + polys[11].edges[1] = -18; + polys[11].edges[2] = -11; + polys[11].edges[3] = -29; + polys[11].edges[4] = -22; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupCylinder +alt_alignment makes even-sided cylinders align faces with the x/y axes +============ +*/ +void idTraceModel::SetupCylinder( const idBounds &cylBounds, const int numSides, const bool alt_alignment ) { + int i, n, ii, n2; + float angle; + idVec3 halfSize; + + n = numSides; + if ( n < 3 ) { + n = 3; + } + if ( n * 2 > MAX_TRACEMODEL_VERTS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many vertices\n" ); + n = MAX_TRACEMODEL_VERTS / 2; + } + if ( n * 3 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many sides\n" ); + n = MAX_TRACEMODEL_EDGES / 3; + } + if ( n + 2 > MAX_TRACEMODEL_POLYS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCylinder: too many polygons\n" ); + n = MAX_TRACEMODEL_POLYS - 2; + } + + float angle_bias = alt_alignment ? (idMath::PI / n) : 0.0f; + + type = TRM_CYLINDER; + numVerts = n * 2; + numEdges = n * 3; + numPolys = n + 2; + offset = ( cylBounds[0] + cylBounds[1] ) * 0.5f; + halfSize = cylBounds[1] - offset; + for ( i = 0; i < n; i++ ) { + // verts + angle = (idMath::TWO_PI * i / n) + angle_bias; + verts[i].x = idMath::Cos( angle ) * halfSize.x + offset.x; + verts[i].y = idMath::Sin( angle ) * halfSize.y + offset.y; + verts[i].z = -halfSize.z + offset.z; + verts[n+i].x = verts[i].x; + verts[n+i].y = verts[i].y; + verts[n+i].z = halfSize.z + offset.z; + // edges + ii = i + 1; + n2 = n << 1; + edges[ii].v[0] = i; + edges[ii].v[1] = ii % n; + edges[n+ii].v[0] = edges[ii].v[0] + n; + edges[n+ii].v[1] = edges[ii].v[1] + n; + edges[n2+ii].v[0] = i; + edges[n2+ii].v[1] = n + i; + // vertical polygon edges + polys[i].numEdges = 4; + polys[i].edges[0] = ii; + polys[i].edges[1] = n2 + (ii % n) + 1; + polys[i].edges[2] = -(n + ii); + polys[i].edges[3] = -(n2 + ii); + // bottom and top polygon edges + polys[n].edges[i] = -(n - i); + polys[n+1].edges[i] = n + ii; + } + // bottom and top polygon numEdges + polys[n].numEdges = n; + polys[n+1].numEdges = n; + // polygons + for ( i = 0; i < n; i++ ) { + // vertical polygon plane + polys[i].normal = (verts[(i+1)%n] - verts[i]).Cross( verts[n+i] - verts[i] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[i]; + // vertical polygon bounds + polys[i].bounds.Clear(); + polys[i].bounds.AddPoint( verts[i] ); + polys[i].bounds.AddPoint( verts[(i+1)%n] ); + polys[i].bounds[0][2] = -halfSize.z + offset.z; + polys[i].bounds[1][2] = halfSize.z + offset.z; + } + // bottom and top polygon plane + polys[n].normal.Set( 0.0f, 0.0f, -1.0f ); + polys[n].dist = -cylBounds[0][2]; + polys[n+1].normal.Set( 0.0f, 0.0f, 1.0f ); + polys[n+1].dist = cylBounds[1][2]; + // trm bounds + bounds = cylBounds; + // bottom and top polygon bounds + polys[n].bounds = bounds; + polys[n].bounds[1][2] = bounds[0][2]; + polys[n+1].bounds = bounds; + polys[n+1].bounds[0][2] = bounds[1][2]; + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupCylinder + + The origin is placed at the center of the cylinder. +============ +*/ +void idTraceModel::SetupCylinder( const float height, const float width, const int numSides ) { + idBounds cylBounds; + float halfHeight, halfWidth; + + halfHeight = height * 0.5f; + halfWidth = width * 0.5f; + cylBounds[0].Set( -halfWidth, -halfWidth, -halfHeight ); + cylBounds[1].Set( halfWidth, halfWidth, halfHeight ); + SetupCylinder( cylBounds, numSides ); +} + +/* +============ +idTraceModel::SetupCone +============ +*/ +void idTraceModel::SetupCone( const idBounds &coneBounds, const int numSides ) { + int i, n, ii; + float angle; + idVec3 halfSize; + + n = numSides; + if ( n < 2 ) { + n = 3; + } + if ( n + 1 > MAX_TRACEMODEL_VERTS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many vertices\n" ); + n = MAX_TRACEMODEL_VERTS - 1; + } + if ( n * 2 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many edges\n" ); + n = MAX_TRACEMODEL_EDGES / 2; + } + if ( n + 1 > MAX_TRACEMODEL_POLYS ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupCone: too many polygons\n" ); + n = MAX_TRACEMODEL_POLYS - 1; + } + + type = TRM_CONE; + numVerts = n + 1; + numEdges = n * 2; + numPolys = n + 1; + offset = ( coneBounds[0] + coneBounds[1] ) * 0.5f; + halfSize = coneBounds[1] - offset; + verts[n].Set( 0.0f, 0.0f, halfSize.z + offset.z ); + for ( i = 0; i < n; i++ ) { + // verts + angle = idMath::TWO_PI * i / n; +// RAVEN BEGIN + verts[i].x = idMath::Cos( angle ) * halfSize.x + offset.x; + verts[i].y = idMath::Sin( angle ) * halfSize.y + offset.y; +// RAVEN END + verts[i].z = -halfSize.z + offset.z; + // edges + ii = i + 1; + edges[ii].v[0] = i; + edges[ii].v[1] = ii % n; + edges[n+ii].v[0] = i; + edges[n+ii].v[1] = n; + // vertical polygon edges + polys[i].numEdges = 3; + polys[i].edges[0] = ii; + polys[i].edges[1] = n + (ii % n) + 1; + polys[i].edges[2] = -(n + ii); + // bottom polygon edges + polys[n].edges[i] = -(n - i); + } + // bottom polygon numEdges + polys[n].numEdges = n; + + // polygons + for ( i = 0; i < n; i++ ) { + // polygon plane + polys[i].normal = (verts[(i+1)%n] - verts[i]).Cross( verts[n] - verts[i] ); + polys[i].normal.Normalize(); + polys[i].dist = polys[i].normal * verts[i]; + // polygon bounds + polys[i].bounds.Clear(); + polys[i].bounds.AddPoint( verts[i] ); + polys[i].bounds.AddPoint( verts[(i+1)%n] ); + polys[i].bounds.AddPoint( verts[n] ); + } + // bottom polygon plane + polys[n].normal.Set( 0.0f, 0.0f, -1.0f ); + polys[n].dist = -coneBounds[0][2]; + // trm bounds + bounds = coneBounds; + // bottom polygon bounds + polys[n].bounds = bounds; + polys[n].bounds[1][2] = bounds[0][2]; + // convex model + isConvex = true; + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::SetupCone + + The origin is placed at the apex of the cone. +============ +*/ +void idTraceModel::SetupCone( const float height, const float width, const int numSides ) { + idBounds coneBounds; + float halfWidth; + + halfWidth = width * 0.5f; + coneBounds[0].Set( -halfWidth, -halfWidth, -height ); + coneBounds[1].Set( halfWidth, halfWidth, 0.0f ); + SetupCone( coneBounds, numSides ); +} + +/* +============ +idTraceModel::SetupBone + + The origin is placed at the center of the bone. +============ +*/ +void idTraceModel::SetupBone( const float length, const float width ) { + int i, j, edgeNum; + float halfLength = length * 0.5f; + + if ( type != TRM_BONE ) { + InitBone(); + } + // offset to center + offset.Set( 0.0f, 0.0f, 0.0f ); + // set vertices + verts[0].Set( 0.0f, 0.0f, -halfLength ); + verts[1].Set( 0.0f, width * -0.5f, 0.0f ); + verts[2].Set( width * 0.5f, width * 0.25f, 0.0f ); + verts[3].Set( width * -0.5f, width * 0.25f, 0.0f ); + verts[4].Set( 0.0f, 0.0f, halfLength ); + // set bounds + bounds[0].Set( width * -0.5f, width * -0.5f, -halfLength ); + bounds[1].Set( width * 0.5f, width * 0.25f, halfLength ); + // poly plane normals + polys[0].normal = ( verts[2] - verts[0] ).Cross( verts[1] - verts[0] ); + polys[0].normal.Normalize(); + polys[2].normal.Set( -polys[0].normal[0], polys[0].normal[1], polys[0].normal[2] ); + polys[3].normal.Set( polys[0].normal[0], polys[0].normal[1], -polys[0].normal[2] ); + polys[5].normal.Set( -polys[0].normal[0], polys[0].normal[1], -polys[0].normal[2] ); + polys[1].normal = (verts[3] - verts[0]).Cross(verts[2] - verts[0]); + polys[1].normal.Normalize(); + polys[4].normal.Set( polys[1].normal[0], polys[1].normal[1], -polys[1].normal[2] ); + // poly plane distances + for ( i = 0; i < 6; i++ ) { + polys[i].dist = polys[i].normal * verts[ edges[ abs(polys[i].edges[0]) ].v[0] ]; + polys[i].bounds.Clear(); + for ( j = 0; j < 3; j++ ) { + edgeNum = polys[i].edges[ j ]; + polys[i].bounds.AddPoint( verts[ edges[abs(edgeNum)].v[edgeNum < 0] ] ); + } + } + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::InitBone + + Initialize size independent bone. +============ +*/ +void idTraceModel::InitBone( void ) { + int i; + + type = TRM_BONE; + numVerts = 5; + numEdges = 9; + numPolys = 6; + + // set bone edges + for ( i = 0; i < 3; i++ ) { + edges[ i + 1 ].v[0] = 0; + edges[ i + 1 ].v[1] = i + 1; + edges[ i + 4 ].v[0] = 1 + i; + edges[ i + 4 ].v[1] = 1 + ((i + 1) % 3); + edges[ i + 7 ].v[0] = i + 1; + edges[ i + 7 ].v[1] = 4; + } + + // all edges of a polygon go counter clockwise + polys[0].numEdges = 3; + polys[0].edges[0] = 2; + polys[0].edges[1] = -4; + polys[0].edges[2] = -1; + + polys[1].numEdges = 3; + polys[1].edges[0] = 3; + polys[1].edges[1] = -5; + polys[1].edges[2] = -2; + + polys[2].numEdges = 3; + polys[2].edges[0] = 1; + polys[2].edges[1] = -6; + polys[2].edges[2] = -3; + + polys[3].numEdges = 3; + polys[3].edges[0] = 4; + polys[3].edges[1] = 8; + polys[3].edges[2] = -7; + + polys[4].numEdges = 3; + polys[4].edges[0] = 5; + polys[4].edges[1] = 9; + polys[4].edges[2] = -8; + + polys[5].numEdges = 3; + polys[5].edges[0] = 6; + polys[5].edges[1] = 7; + polys[5].edges[2] = -9; + + // convex model + isConvex = true; +} + +/* +============ +idTraceModel::SetupPolygon +============ +*/ +void idTraceModel::SetupPolygon( const idVec3 *v, const int count ) { + int i, j; + idVec3 mid; + + type = TRM_POLYGON; + numVerts = count; + // times three because we need to be able to turn the polygon into a volume + if ( numVerts * 3 > MAX_TRACEMODEL_EDGES ) { + idLib::common->Printf( "WARNING: idTraceModel::SetupPolygon: too many vertices\n" ); + numVerts = MAX_TRACEMODEL_EDGES / 3; + } + + numEdges = numVerts; + numPolys = 2; + // set polygon planes + polys[0].numEdges = numEdges; + polys[0].normal = ( v[1] - v[0] ).Cross( v[2] - v[0] ); + polys[0].normal.Normalize(); + polys[0].dist = polys[0].normal * v[0]; + polys[1].numEdges = numEdges; + polys[1].normal = -polys[0].normal; + polys[1].dist = -polys[0].dist; + // setup verts, edges and polygons + polys[0].bounds.Clear(); + mid = vec3_origin; + for ( i = 0, j = 1; i < numVerts; i++, j++ ) { + if ( j >= numVerts ) { + j = 0; + } + verts[i] = v[i]; + edges[i+1].v[0] = i; + edges[i+1].v[1] = j; + edges[i+1].normal = polys[0].normal.Cross( v[i] - v[j] ); + edges[i+1].normal.Normalize(); + polys[0].edges[i] = i + 1; + polys[1].edges[i] = -(numVerts - i); + polys[0].bounds.AddPoint( verts[i] ); + mid += v[i]; + } + polys[1].bounds = polys[0].bounds; + // offset to center + offset = mid * (1.0f / numVerts); + // total bounds + bounds = polys[0].bounds; + // considered non convex because the model has no volume + isConvex = false; +} + +/* +============ +idTraceModel::SetupPolygon +============ +*/ +void idTraceModel::SetupPolygon( const idWinding &w ) { + int i; + idVec3 *verts; + + verts = (idVec3 *) _alloca16( w.GetNumPoints() * sizeof( idVec3 ) ); + for ( i = 0; i < w.GetNumPoints(); i++ ) { + verts[i] = w[i].ToVec3(); + } + SetupPolygon( verts, w.GetNumPoints() ); +} + +/* +============ +idTraceModel::VolumeFromPolygon +============ +*/ +void idTraceModel::VolumeFromPolygon( idTraceModel &trm, float thickness ) const { + int i; + + trm = *this; + trm.type = TRM_POLYGONVOLUME; + trm.numVerts = numVerts * 2; + trm.numEdges = numEdges * 3; + trm.numPolys = numEdges + 2; + for ( i = 0; i < numEdges; i++ ) { + trm.verts[ numVerts + i ] = verts[i] - thickness * polys[0].normal; + trm.edges[ numEdges + i + 1 ].v[0] = numVerts + i; + trm.edges[ numEdges + i + 1 ].v[1] = numVerts + (i+1) % numVerts; + trm.edges[ numEdges * 2 + i + 1 ].v[0] = i; + trm.edges[ numEdges * 2 + i + 1 ].v[1] = numVerts + i; + trm.polys[1].edges[i] = -(numEdges + i + 1); + trm.polys[2+i].numEdges = 4; + trm.polys[2+i].edges[0] = -(i + 1); + trm.polys[2+i].edges[1] = numEdges*2 + i + 1; + trm.polys[2+i].edges[2] = numEdges + i + 1; + trm.polys[2+i].edges[3] = -(numEdges*2 + (i+1) % numEdges + 1); + trm.polys[2+i].normal = (verts[(i + 1) % numVerts] - verts[i]).Cross( polys[0].normal ); + trm.polys[2+i].normal.Normalize(); + trm.polys[2+i].dist = trm.polys[2+i].normal * verts[i]; + } + trm.polys[1].dist = trm.polys[1].normal * trm.verts[ numEdges ]; + + trm.GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::GenerateEdgeNormals +============ +*/ +#define SHARP_EDGE_DOT -0.7f + +int idTraceModel::GenerateEdgeNormals( void ) { + int i, j, edgeNum, numSharpEdges; + float dot; + idVec3 dir; + traceModelPoly_t *poly; + traceModelEdge_t *edge; + + for ( i = 0; i <= numEdges; i++ ) { + edges[i].normal.Zero(); + } + + numSharpEdges = 0; + + for ( i = 0; i < numPolys; i++ ) { + poly = polys + i; + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = edges + abs( edgeNum ); + if ( edge->normal[0] == 0.0f && edge->normal[1] == 0.0f && edge->normal[2] == 0.0f ) { + edge->normal = poly->normal; + } else { + dot = edge->normal * poly->normal; + // if the two planes make a very sharp edge + if ( dot < SHARP_EDGE_DOT ) { + // max length normal pointing outside both polygons + dir = verts[ edge->v[edgeNum > 0]] - verts[ edge->v[edgeNum < 0]]; + edge->normal = edge->normal.Cross( dir ) + poly->normal.Cross( -dir ); + edge->normal *= ( 1.0f / ( 1.0f + SHARP_EDGE_DOT ) ) / edge->normal.Length(); + numSharpEdges++; + } else { + edge->normal = ( 1.0f / ( 1.0f + dot ) ) * ( edge->normal + poly->normal ); + } + } + } + } + + return numSharpEdges; +} + +/* +============ +idTraceModel::TestConvexity +============ +*/ +void idTraceModel::TestConvexity( void ) { + int i, j; + + // assume convex + isConvex = true; + // check if really convex + for ( i = 0; i < numPolys; i++ ) { + // to be convex no vertices should be in front of any polygon plane + for ( j = 0; j < numVerts; j++ ) { + if ( polys[ i ].normal * verts[ j ] - polys[ i ].dist > 0.01f ) { + isConvex = false; + break; + } + } + if ( j < numVerts ) { + break; + } + } +} + +/* +============ +idTraceModel::Translate +============ +*/ +void idTraceModel::Translate( const idVec3 &translation ) { + int i; + + for ( i = 0; i < numVerts; i++ ) { + verts[i] += translation; + } + for ( i = 0; i < numPolys; i++ ) { + polys[i].dist += polys[i].normal * translation; + polys[i].bounds[0] += translation; + polys[i].bounds[1] += translation; + } + offset += translation; + bounds[0] += translation; + bounds[1] += translation; +} + +/* +============ +idTraceModel::Rotate +============ +*/ +void idTraceModel::Rotate( const idMat3 &rotation ) { + int i, j, edgeNum; + + for ( i = 0; i < numVerts; i++ ) { + verts[i] *= rotation; + } + + bounds.Clear(); + for ( i = 0; i < numPolys; i++ ) { + polys[i].normal *= rotation; + polys[i].bounds.Clear(); + edgeNum = 0; + for ( j = 0; j < polys[i].numEdges; j++ ) { + edgeNum = polys[i].edges[j]; + polys[i].bounds.AddPoint( verts[edges[abs(edgeNum)].v[INTSIGNBITSET(edgeNum)]] ); + } + polys[i].dist = polys[i].normal * verts[edges[abs(edgeNum)].v[INTSIGNBITSET(edgeNum)]]; + bounds += polys[i].bounds; + } + + GenerateEdgeNormals(); +} + +/* +============ +idTraceModel::Shrink +============ +*/ +void idTraceModel::Shrink( const float m ) { + int i, j, edgeNum, vertexNum; + float d, bestd, n, invDet, f0, f1; + traceModelPoly_t *poly, *poly1, *poly2; + traceModelEdge_t *edge; + idVec3 start, dir; + int vertexPolys[MAX_TRACEMODEL_VERTS][MAX_TRACEMODEL_POLYS]; + int vertexNumPolys[MAX_TRACEMODEL_VERTS]; + + // special case for single polygon + if ( type == TRM_POLYGON ) { + for ( i = 0; i < numEdges; i++ ) { + edgeNum = polys[0].edges[i]; + edge = &edges[abs(edgeNum)]; + dir = verts[ edge->v[ INTSIGNBITSET(edgeNum) ] ] - verts[ edge->v[ INTSIGNBITNOTSET(edgeNum) ] ]; + if ( dir.Normalize() < 2.0f * m ) { + continue; + } + dir *= m; + verts[ edge->v[ 0 ] ] -= dir; + verts[ edge->v[ 1 ] ] += dir; + } + return; + } + + // the trace model should be a closed surface + assert( IsClosedSurface() ); + + memset( vertexPolys, 0, sizeof( vertexPolys ) ); + memset( vertexNumPolys, 0, sizeof( vertexNumPolys ) ); + + // move polygon planes and find the vertex polygons + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + + poly->dist -= m; + + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = &edges[abs(edgeNum)]; + vertexNum = edge->v[ INTSIGNBITSET( edgeNum ) ]; + + vertexPolys[vertexNum][vertexNumPolys[vertexNum]] = i; + vertexNumPolys[vertexNum]++; + } + } + + // move vertices + for ( i = 0; i < numVerts; i++ ) { + + assert( vertexNumPolys[i] >= 3 ); + + poly1 = &polys[vertexPolys[i][0]]; + poly2 = NULL; + + // find the polygon that is most orthogonal to the first polygon + bestd = 1.0f; + for ( j = 1; j < vertexNumPolys[i]; j++ ) { + d = fabs( poly1->normal * polys[vertexPolys[i][j]].normal ); + if ( d < bestd ) { + bestd = d; + poly2 = &polys[vertexPolys[i][j]]; + } + } + + // calculate intersection line between planes + n = poly1->normal * poly2->normal; + invDet = 1.0f / ( 1.0f - n * n ); + f0 = ( poly1->dist - n * poly2->dist ) * invDet; + f1 = ( poly2->dist - n * poly1->dist ) * invDet; + start = f0 * poly1->normal + f1 * poly2->normal; + dir = poly1->normal.Cross( poly2->normal ); + + // find the polygon that is most orthogonal to the plane intersection ray + bestd = 0.0f; + for ( j = 1; j < vertexNumPolys[i]; j++ ) { + d = fabs( dir * polys[vertexPolys[i][j]].normal ); + if ( d > bestd ) { + bestd = d; + poly2 = &polys[vertexPolys[i][j]]; + } + } + + // calculate intersection with plane intersection ray + f0 = poly2->normal * start - poly2->dist; + f1 = poly2->normal * dir; + verts[i] = start - dir * ( f0 / f1 ); + } + + Verify(); +} + +/* +============ +idTraceModel::ClearUnused +============ +*/ +void idTraceModel::ClearUnused( void ) { + int i, j; + + for ( i = numVerts; i < MAX_TRACEMODEL_VERTS; i++ ) { + verts[i].Zero(); + } + memset( &edges[0], 0, sizeof( edges[0] ) ); + for ( i = numEdges+1; i < MAX_TRACEMODEL_EDGES+1; i++ ) { + memset( &edges[i], 0, sizeof( edges[i] ) ); + } + for ( i = 0; i < numPolys; i++ ) { + for ( j = polys[i].numEdges; j < MAX_TRACEMODEL_POLYEDGES; j++ ) { + polys[i].edges[j] = 0; + } + } + for ( i = numPolys; i < MAX_TRACEMODEL_POLYS; i++ ) { + memset( &polys[i], 0, sizeof( polys[i] ) ); + } +} + +/* +============ +idTraceModel::Verify +============ +*/ +bool idTraceModel::Verify( void ) { + int i, j, edgeNum, vertexNum; + traceModelPoly_t *poly; + traceModelEdge_t *edge; + + // test whether or not the vertices are on the polygon planes + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + + for ( j = 0; j < polys[i].numEdges; j++ ) { + edgeNum = poly->edges[j]; + edge = &edges[abs(edgeNum)]; + vertexNum = edge->v[ INTSIGNBITSET( edgeNum ) ]; + float d = poly->normal * verts[vertexNum] - poly->dist; + if ( fabs( d ) > 1e-4f ) { + return false; + } + } + } + + return true; +} + +/* +============ +idTraceModel::Compare +============ +*/ +bool idTraceModel::Compare( const idTraceModel &trm ) const { + int i; + + if ( type != trm.type || numVerts != trm.numVerts || + numEdges != trm.numEdges || numPolys != trm.numPolys ) { + return false; + } + if ( bounds != trm.bounds || offset != trm.offset ) { + return false; + } + + switch( type ) { + case TRM_INVALID: + case TRM_BOX: + case TRM_OCTAHEDRON: + case TRM_DODECAHEDRON: + case TRM_CYLINDER: + case TRM_CONE: + break; + case TRM_BONE: + case TRM_POLYGON: + case TRM_POLYGONVOLUME: + case TRM_CUSTOM: + for ( i = 0; i < trm.numVerts; i++ ) { + if ( verts[i] != trm.verts[i] ) { + return false; + } + } + break; + } + return true; +} + +/* +============ +idTraceModel::IsClosedSurface +============ +*/ +bool idTraceModel::IsClosedSurface( void ) const { + int i, j, numEdgeUsers[MAX_TRACEMODEL_EDGES+1]; + + // each edge should be used exactly twice + memset( numEdgeUsers, 0, sizeof(numEdgeUsers) ); + for ( i = 0; i < numPolys; i++ ) { + for ( j = 0; j < polys[i].numEdges; j++ ) { + numEdgeUsers[ abs( polys[i].edges[j] ) ]++; + } + } + for ( i = 1; i <= numEdges; i++ ) { + if ( numEdgeUsers[i] != 2 ) { + return false; + } + } + return true; +} + +/* +============ +idTraceModel::GetPolygonArea +============ +*/ +float idTraceModel::GetPolygonArea( int polyNum ) const { + int i; + idVec3 base, v1, v2, cross; + float total; + const traceModelPoly_t *poly; + + if ( polyNum < 0 || polyNum >= numPolys ) { + return 0.0f; + } + poly = &polys[polyNum]; + total = 0.0f; + base = verts[ edges[ abs(poly->edges[0]) ].v[ INTSIGNBITSET( poly->edges[0] ) ] ]; + for ( i = 0; i < poly->numEdges; i++ ) { + v1 = verts[ edges[ abs(poly->edges[i]) ].v[ INTSIGNBITSET( poly->edges[i] ) ] ] - base; + v2 = verts[ edges[ abs(poly->edges[i]) ].v[ INTSIGNBITNOTSET( poly->edges[i] ) ] ] - base; + cross = v1.Cross( v2 ); + total += cross.Length(); + } + return total * 0.5f; +} + +/* +============ +idTraceModel::GetOrderedSilhouetteEdges +============ +*/ +int idTraceModel::GetOrderedSilhouetteEdges( const int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1], int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum, numSilEdges, nextSilVert; + int unsortedSilEdges[MAX_TRACEMODEL_EDGES+1]; + + numSilEdges = 0; + for ( i = 1; i <= numEdges; i++ ) { + if ( edgeIsSilEdge[i] ) { + unsortedSilEdges[numSilEdges++] = i; + } + } + + silEdges[0] = unsortedSilEdges[0]; + unsortedSilEdges[0] = -1; + nextSilVert = edges[silEdges[0]].v[0]; + for ( i = 1; i < numSilEdges; i++ ) { + for ( j = 1; j < numSilEdges; j++ ) { + edgeNum = unsortedSilEdges[j]; + if ( edgeNum >= 0 ) { + if ( edges[edgeNum].v[0] == nextSilVert ) { + nextSilVert = edges[edgeNum].v[1]; + silEdges[i] = edgeNum; + break; + } + if ( edges[edgeNum].v[1] == nextSilVert ) { + nextSilVert = edges[edgeNum].v[0]; + silEdges[i] = -edgeNum; + break; + } + } + } + if ( j >= numSilEdges ) { + silEdges[i] = 1; // shouldn't happen + } + unsortedSilEdges[j] = -1; + } + return numSilEdges; +} + +/* +============ +idTraceModel::GetProjectionSilhouetteEdges +============ +*/ +int idTraceModel::GetProjectionSilhouetteEdges( const idVec3 &projectionOrigin, int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum; + int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1]; + const traceModelPoly_t *poly; + idVec3 dir; + + memset( edgeIsSilEdge, 0, sizeof( edgeIsSilEdge ) ); + + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + edgeNum = poly->edges[0]; + dir = verts[ edges[abs(edgeNum)].v[ INTSIGNBITSET(edgeNum) ] ] - projectionOrigin; + if ( dir * poly->normal < 0.0f ) { + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edgeIsSilEdge[abs(edgeNum)] ^= 1; + } + } + } + + return GetOrderedSilhouetteEdges( edgeIsSilEdge, silEdges ); +} + +/* +============ +idTraceModel::GetParallelProjectionSilhouetteEdges +============ +*/ +int idTraceModel::GetParallelProjectionSilhouetteEdges( const idVec3 &projectionDir, int silEdges[MAX_TRACEMODEL_EDGES] ) const { + int i, j, edgeNum; + int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1]; + const traceModelPoly_t *poly; + + memset( edgeIsSilEdge, 0, sizeof( edgeIsSilEdge ) ); + + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + if ( projectionDir * poly->normal < 0.0f ) { + for ( j = 0; j < poly->numEdges; j++ ) { + edgeNum = poly->edges[j]; + edgeIsSilEdge[abs(edgeNum)] ^= 1; + } + } + } + + return GetOrderedSilhouetteEdges( edgeIsSilEdge, silEdges ); +} + + +/* + + credits to Brian Mirtich for his paper "Fast and Accurate Computation of Polyhedral Mass Properties" + +*/ + +struct projectionIntegrals_t { + float P1; + float Pa, Pb; + float Paa, Pab, Pbb; + float Paaa, Paab, Pabb, Pbbb; +}; + +/* +============ +idTraceModel::ProjectionIntegrals +============ +*/ +void idTraceModel::ProjectionIntegrals( int polyNum, int a, int b, projectionIntegrals_t &integrals ) const { + const traceModelPoly_t *poly; + int i, edgeNum; + idVec3 v1, v2; + float a0, a1, da; + float b0, b1, db; + float a0_2, a0_3, a0_4, b0_2, b0_3, b0_4; + float a1_2, a1_3, b1_2, b1_3; + float C1, Ca, Caa, Caaa, Cb, Cbb, Cbbb; + float Cab, Kab, Caab, Kaab, Cabb, Kabb; + + memset(&integrals, 0, sizeof(projectionIntegrals_t)); + poly = &polys[polyNum]; + for ( i = 0; i < poly->numEdges; i++ ) { + edgeNum = poly->edges[i]; + v1 = verts[ edges[ abs(edgeNum) ].v[ edgeNum < 0 ] ]; + v2 = verts[ edges[ abs(edgeNum) ].v[ edgeNum > 0 ] ]; + a0 = v1[a]; + b0 = v1[b]; + a1 = v2[a]; + b1 = v2[b]; + da = a1 - a0; + db = b1 - b0; + a0_2 = a0 * a0; + a0_3 = a0_2 * a0; + a0_4 = a0_3 * a0; + b0_2 = b0 * b0; + b0_3 = b0_2 * b0; + b0_4 = b0_3 * b0; + a1_2 = a1 * a1; + a1_3 = a1_2 * a1; + b1_2 = b1 * b1; + b1_3 = b1_2 * b1; + + C1 = a1 + a0; + Ca = a1 * C1 + a0_2; + Caa = a1 * Ca + a0_3; + Caaa = a1 * Caa + a0_4; + Cb = b1 * (b1 + b0) + b0_2; + Cbb = b1 * Cb + b0_3; + Cbbb = b1 * Cbb + b0_4; + Cab = 3 * a1_2 + 2 * a1 * a0 + a0_2; + Kab = a1_2 + 2 * a1 * a0 + 3 * a0_2; + Caab = a0 * Cab + 4 * a1_3; + Kaab = a1 * Kab + 4 * a0_3; + Cabb = 4 * b1_3 + 3 * b1_2 * b0 + 2 * b1 * b0_2 + b0_3; + Kabb = b1_3 + 2 * b1_2 * b0 + 3 * b1 * b0_2 + 4 * b0_3; + + integrals.P1 += db * C1; + integrals.Pa += db * Ca; + integrals.Paa += db * Caa; + integrals.Paaa += db * Caaa; + integrals.Pb += da * Cb; + integrals.Pbb += da * Cbb; + integrals.Pbbb += da * Cbbb; + integrals.Pab += db * (b1 * Cab + b0 * Kab); + integrals.Paab += db * (b1 * Caab + b0 * Kaab); + integrals.Pabb += da * (a1 * Cabb + a0 * Kabb); + } + + integrals.P1 *= (1.0f / 2.0f); + integrals.Pa *= (1.0f / 6.0f); + integrals.Paa *= (1.0f / 12.0f); + integrals.Paaa *= (1.0f / 20.0f); + integrals.Pb *= (1.0f / -6.0f); + integrals.Pbb *= (1.0f / -12.0f); + integrals.Pbbb *= (1.0f / -20.0f); + integrals.Pab *= (1.0f / 24.0f); + integrals.Paab *= (1.0f / 60.0f); + integrals.Pabb *= (1.0f / -60.0f); +} + +struct polygonIntegrals_t { + float Fa, Fb, Fc; + float Faa, Fbb, Fcc; + float Faaa, Fbbb, Fccc; + float Faab, Fbbc, Fcca; +}; + +/* +============ +idTraceModel::PolygonIntegrals +============ +*/ +void idTraceModel::PolygonIntegrals( int polyNum, int a, int b, int c, polygonIntegrals_t &integrals ) const { + projectionIntegrals_t pi; + idVec3 n; + float w; + float k1, k2, k3, k4; + + ProjectionIntegrals( polyNum, a, b, pi ); + + n = polys[polyNum].normal; + w = -polys[polyNum].dist; + k1 = 1 / n[c]; + k2 = k1 * k1; + k3 = k2 * k1; + k4 = k3 * k1; + + integrals.Fa = k1 * pi.Pa; + integrals.Fb = k1 * pi.Pb; + integrals.Fc = -k2 * (n[a] * pi.Pa + n[b] * pi.Pb + w * pi.P1); + + integrals.Faa = k1 * pi.Paa; + integrals.Fbb = k1 * pi.Pbb; + integrals.Fcc = k3 * (Square(n[a]) * pi.Paa + 2 * n[a] * n[b] * pi.Pab + Square(n[b]) * pi.Pbb + + w * (2 * (n[a] * pi.Pa + n[b] * pi.Pb) + w * pi.P1)); + + integrals.Faaa = k1 * pi.Paaa; + integrals.Fbbb = k1 * pi.Pbbb; + integrals.Fccc = -k4 * (Cube(n[a]) * pi.Paaa + 3 * Square(n[a]) * n[b] * pi.Paab + + 3 * n[a] * Square(n[b]) * pi.Pabb + Cube(n[b]) * pi.Pbbb + + 3 * w * (Square(n[a]) * pi.Paa + 2 * n[a] * n[b] * pi.Pab + Square(n[b]) * pi.Pbb) + + w * w * (3 * (n[a] * pi.Pa + n[b] * pi.Pb) + w * pi.P1)); + + integrals.Faab = k1 * pi.Paab; + integrals.Fbbc = -k2 * (n[a] * pi.Pabb + n[b] * pi.Pbbb + w * pi.Pbb); + integrals.Fcca = k3 * (Square(n[a]) * pi.Paaa + 2 * n[a] * n[b] * pi.Paab + Square(n[b]) * pi.Pabb + + w * (2 * (n[a] * pi.Paa + n[b] * pi.Pab) + w * pi.Pa)); +} + +struct volumeIntegrals_t { + float T0; + idVec3 T1; + idVec3 T2; + idVec3 TP; +}; + +/* +============ +idTraceModel::VolumeIntegrals +============ +*/ +void idTraceModel::VolumeIntegrals( volumeIntegrals_t &integrals ) const { + const traceModelPoly_t *poly; + polygonIntegrals_t pi; + int i, a, b, c; + float nx, ny, nz; + + memset( &integrals, 0, sizeof(volumeIntegrals_t) ); + for ( i = 0; i < numPolys; i++ ) { + poly = &polys[i]; + + nx = idMath::Fabs( poly->normal[0] ); + ny = idMath::Fabs( poly->normal[1] ); + nz = idMath::Fabs( poly->normal[2] ); + if ( nx > ny && nx > nz ) { + c = 0; + } + else { + c = (ny > nz) ? 1 : 2; + } + a = (c + 1) % 3; + b = (a + 1) % 3; + + PolygonIntegrals( i, a, b, c, pi ); + + integrals.T0 += poly->normal[0] * ((a == 0) ? pi.Fa : ((b == 0) ? pi.Fb : pi.Fc)); + + integrals.T1[a] += poly->normal[a] * pi.Faa; + integrals.T1[b] += poly->normal[b] * pi.Fbb; + integrals.T1[c] += poly->normal[c] * pi.Fcc; + integrals.T2[a] += poly->normal[a] * pi.Faaa; + integrals.T2[b] += poly->normal[b] * pi.Fbbb; + integrals.T2[c] += poly->normal[c] * pi.Fccc; + integrals.TP[a] += poly->normal[a] * pi.Faab; + integrals.TP[b] += poly->normal[b] * pi.Fbbc; + integrals.TP[c] += poly->normal[c] * pi.Fcca; + } + + integrals.T1 *= 0.5f; + integrals.T2 *= (1.0f / 3.0f); + integrals.TP *= 0.5f; +} + +/* +============ +idTraceModel::GetMassProperties +============ +*/ +void idTraceModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + volumeIntegrals_t integrals; + + // if polygon trace model + if ( type == TRM_POLYGON ) { + idTraceModel trm; + + VolumeFromPolygon( trm, 1.0f ); + trm.GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + return; + } + + VolumeIntegrals( integrals ); + + // if no volume + if ( integrals.T0 == 0.0f ) { + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + return; + } + + // mass of model + mass = density * integrals.T0; + // center of mass + centerOfMass = integrals.T1 / integrals.T0; + // compute inertia tensor + inertiaTensor[0][0] = density * (integrals.T2[1] + integrals.T2[2]); + inertiaTensor[1][1] = density * (integrals.T2[2] + integrals.T2[0]); + inertiaTensor[2][2] = density * (integrals.T2[0] + integrals.T2[1]); + inertiaTensor[0][1] = inertiaTensor[1][0] = - density * integrals.TP[0]; + inertiaTensor[1][2] = inertiaTensor[2][1] = - density * integrals.TP[1]; + inertiaTensor[2][0] = inertiaTensor[0][2] = - density * integrals.TP[2]; + // translate inertia tensor to center of mass + inertiaTensor[0][0] -= mass * (centerOfMass[1]*centerOfMass[1] + centerOfMass[2]*centerOfMass[2]); + inertiaTensor[1][1] -= mass * (centerOfMass[2]*centerOfMass[2] + centerOfMass[0]*centerOfMass[0]); + inertiaTensor[2][2] -= mass * (centerOfMass[0]*centerOfMass[0] + centerOfMass[1]*centerOfMass[1]); + inertiaTensor[0][1] = inertiaTensor[1][0] += mass * centerOfMass[0] * centerOfMass[1]; + inertiaTensor[1][2] = inertiaTensor[2][1] += mass * centerOfMass[1] * centerOfMass[2]; + inertiaTensor[2][0] = inertiaTensor[0][2] += mass * centerOfMass[2] * centerOfMass[0]; +} diff --git a/src/idlib/geometry/TraceModel.h b/src/idlib/geometry/TraceModel.h new file mode 100644 index 0000000..8103b18 --- /dev/null +++ b/src/idlib/geometry/TraceModel.h @@ -0,0 +1,197 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __TRACEMODEL_H__ +#define __TRACEMODEL_H__ + +/* +=============================================================================== + + A trace model is an arbitrary polygonal model which is used by the + collision detection system to find collisions, contacts or the contents + of a volume. For collision detection speed reasons the number of vertices + and edges are limited. The trace model can have any shape. However convex + models are usually preferred. + +=============================================================================== +*/ + +class idVec3; +class idMat3; +class idBounds; + +// trace model type +enum traceModel_t { + TRM_INVALID, // invalid trm + TRM_BOX, // box + TRM_OCTAHEDRON, // octahedron + TRM_DODECAHEDRON, // dodecahedron + TRM_CYLINDER, // cylinder approximation + TRM_CONE, // cone approximation + TRM_BONE, // two tetrahedrons attached to each other + TRM_POLYGON, // arbitrary convex polygon + TRM_POLYGONVOLUME, // volume for arbitrary convex polygon + TRM_CUSTOM // loaded from map model or ASE/LWO +}; + +// these are bit cache limits +#define MAX_TRACEMODEL_VERTS 32 +#define MAX_TRACEMODEL_EDGES 32 +#define MAX_TRACEMODEL_POLYS 16 +#define MAX_TRACEMODEL_POLYEDGES 16 + +typedef idVec3 traceModelVert_t; + +struct traceModelEdge_t { + int v[2]; + idVec3 normal; +}; + +struct traceModelPoly_t { + idVec3 normal; + float dist; + idBounds bounds; + int numEdges; + int edges[MAX_TRACEMODEL_POLYEDGES]; +}; + +class idTraceModel { + +public: + traceModel_t type; + int numVerts; + traceModelVert_t verts[MAX_TRACEMODEL_VERTS]; + int numEdges; + traceModelEdge_t edges[MAX_TRACEMODEL_EDGES+1]; // edges[0] is unused because edge index signs are used for direction + int numPolys; + traceModelPoly_t polys[MAX_TRACEMODEL_POLYS]; + idVec3 offset; // offset to center of model + idBounds bounds; // bounds of model + bool isConvex; // true when model is convex + +public: + idTraceModel( void ); + // axial bounding box + idTraceModel( const idBounds &boxBounds ); + // cylinder approximation + idTraceModel( const idBounds &cylBounds, const int numSides, const bool alt_alignment = false ); + // bone + idTraceModel( const float length, const float width ); + + // axial box + void SetupBox( const idBounds &boxBounds ); + void SetupBox( const float size ); + // octahedron + void SetupOctahedron( const idBounds &octBounds ); + void SetupOctahedron( const float size ); + // dodecahedron + void SetupDodecahedron( const idBounds &dodBounds ); + void SetupDodecahedron( const float size ); + // cylinder approximation + void SetupCylinder( const idBounds &cylBounds, const int numSides, const bool alt_alignment = false ); + void SetupCylinder( const float height, const float width, const int numSides ); + // cone approximation + void SetupCone( const idBounds &coneBounds, const int numSides ); + void SetupCone( const float height, const float width, const int numSides ); + // two tetrahedrons attached to each other + void SetupBone( const float length, const float width ); + // arbitrary convex polygon + void SetupPolygon( const idVec3 *v, const int count ); + void SetupPolygon( const idWinding &w ); + + // generate edge normals + int GenerateEdgeNormals( void ); + // test whether or not the model is convex and set isConvex accordingly + void TestConvexity( void ); + // translate the trm + void Translate( const idVec3 &translation ); + // rotate the trm + void Rotate( const idMat3 &rotation ); + // shrink the model m units on all sides + void Shrink( const float m ); + // clear unused spots in the arrays + void ClearUnused( void ); + // make sure the trace model is well formed + bool Verify( void ); + + // compare + bool Compare( const idTraceModel &trm ) const; + bool operator==( const idTraceModel &trm ) const; + bool operator!=( const idTraceModel &trm ) const; + + // returns true of the model is a closed surface + bool IsClosedSurface( void ) const; + + // get the area of one of the polygons + float GetPolygonArea( int polyNum ) const; + // get the silhouette edges + int GetProjectionSilhouetteEdges( const idVec3 &projectionOrigin, int silEdges[MAX_TRACEMODEL_EDGES] ) const; + int GetParallelProjectionSilhouetteEdges( const idVec3 &projectionDir, int silEdges[MAX_TRACEMODEL_EDGES] ) const; + // calculate mass properties assuming an uniform density + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + +private: + void InitBox( void ); + void InitOctahedron( void ); + void InitDodecahedron( void ); + void InitBone( void ); + + void ProjectionIntegrals( int polyNum, int a, int b, struct projectionIntegrals_t &integrals ) const; + void PolygonIntegrals( int polyNum, int a, int b, int c, struct polygonIntegrals_t &integrals ) const; + void VolumeIntegrals( struct volumeIntegrals_t &integrals ) const; + void VolumeFromPolygon( idTraceModel &trm, float thickness ) const; + int GetOrderedSilhouetteEdges( const int edgeIsSilEdge[MAX_TRACEMODEL_EDGES+1], int silEdges[MAX_TRACEMODEL_EDGES] ) const; +}; + + +ID_INLINE idTraceModel::idTraceModel( void ) { + type = TRM_INVALID; + numVerts = numEdges = numPolys = 0; + bounds.Zero(); +} + +ID_INLINE idTraceModel::idTraceModel( const idBounds &boxBounds ) { + InitBox(); + SetupBox( boxBounds ); +} + +ID_INLINE idTraceModel::idTraceModel( const idBounds &cylBounds, const int numSides, const bool alt_alignment ) { + SetupCylinder( cylBounds, numSides, alt_alignment ); +} + +ID_INLINE idTraceModel::idTraceModel( const float length, const float width ) { + InitBone(); + SetupBone( length, width ); +} + +ID_INLINE bool idTraceModel::operator==( const idTraceModel &trm ) const { + return Compare( trm ); +} + +ID_INLINE bool idTraceModel::operator!=( const idTraceModel &trm ) const { + return !Compare( trm ); +} + +#endif /* !__TRACEMODEL_H__ */ + diff --git a/src/idlib/geometry/Winding.cpp b/src/idlib/geometry/Winding.cpp new file mode 100644 index 0000000..f6c5e4d --- /dev/null +++ b/src/idlib/geometry/Winding.cpp @@ -0,0 +1,1647 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +//=============================================================== +// +// idWinding +// +//=============================================================== + +/* +============= +idWinding::ReAllocate +============= +*/ +bool idWinding::ReAllocate( int n, bool keep ) { + idVec5 *oldP; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + oldP = p; + n = (n+3) & ~3; // align up to multiple of four + p = new idVec5[n]; + if ( oldP ) { + if ( keep ) { + memcpy( p, oldP, numPoints * sizeof(p[0]) ); + } + delete[] oldP; + } + allocedSize = n; + + return true; +} + +/* +============= +idWinding::BaseForPlane +============= +*/ +void idWinding::BaseForPlane( const idVec3 &normal, const float dist ) { + idVec3 org, vright, vup; + + org = normal * dist; + + normal.NormalVectors( vup, vright ); + vup *= MAX_WORLD_SIZE; + vright *= MAX_WORLD_SIZE; + + EnsureAlloced( 4 ); + numPoints = 4; + p[0].ToVec3() = org - vright + vup; + p[0].s = p[0].t = 0.0f; + p[1].ToVec3() = org + vright + vup; + p[1].s = p[1].t = 0.0f; + p[2].ToVec3() = org + vright - vup; + p[2].s = p[2].t = 0.0f; + p[3].ToVec3() = org - vright - vup; + p[3].s = p[3].t = 0.0f; +} + +/* +============= +idWinding::Split +============= +*/ +int idWinding::Split( const idPlane &plane, const float epsilon, idWinding **front, idWinding **back ) const { + float * dists; + byte * sides; + int counts[3]; + float dot; + int i, j; + const idVec5 * p1, *p2; + idVec5 mid; + idWinding * f, *b; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + // if coplanar, put on the front side if the normals match + if ( !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + idPlane windingPlane; + + GetPlane( windingPlane ); + if ( windingPlane.Normal() * plane.Normal() > 0.0f ) { + *front = Copy(); + return SIDE_FRONT; + } else { + *back = Copy(); + return SIDE_BACK; + } + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = Copy(); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = Copy(); + return SIDE_FRONT; + } + + maxpts = numPoints+4; // cant use counts[0]+2 because of fp grouping errors + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + *front = f = new idWinding(maxpts); + *back = b = new idWinding(maxpts); + + for (i = 0; i < numPoints; i++) { + p1 = &p[i]; + + if ( sides[i] == SIDE_ON ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + b->p[b->numPoints] = *p1; + b->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + } + + if ( sides[i] == SIDE_BACK ) { + b->p[b->numPoints] = *p1; + b->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + // always calculate the split going from the same side + // or minor epsilon issues can happen + if ( sides[i] == SIDE_FRONT ) { + dot = dists[i] / ( dists[i] - dists[i+1] ); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + } else { + dot = dists[i+1] / ( dists[i+1] - dists[i] ); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p2)[j] + dot * ( (*p1)[j] - (*p2)[j] ); + } + } + mid.s = p2->s + dot * ( p1->s - p2->s ); + mid.t = p2->t + dot * ( p1->t - p2->t ); + } + + f->p[f->numPoints] = mid; + f->numPoints++; + b->p[b->numPoints] = mid; + b->numPoints++; + } + + if ( f->numPoints > maxpts || b->numPoints > maxpts ) { + idLib::common->FatalError( "idWinding::Split: points exceeded estimate." ); + } + + return SIDE_CROSS; +} + +/* +============= +idWinding::Clip +============= +*/ +idWinding *idWinding::Clip( const idPlane &plane, const float epsilon, const bool keepOn ) { + float * dists; + byte * sides; + idVec5 * newPoints; + int newNumPoints; + int counts[3]; + float dot; + int i, j; + idVec5 * p1, *p2; + idVec5 mid; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return this; + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + delete this; + return NULL; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return this; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + + newPoints = (idVec5 *) _alloca16( maxpts * sizeof( idVec5 ) ); + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return this; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return this; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( !EnsureAlloced( newNumPoints, false ) ) { + return this; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec5) ); + + return this; +} + +/* +============= +idWinding::ClipInPlace +============= +*/ +bool idWinding::ClipInPlace( const idPlane &plane, const float epsilon, const bool keepOn ) { + float* dists; + byte * sides; + idVec5 * newPoints; + int newNumPoints; + int counts[3]; + float dot; + int i, j; + idVec5 * p1, *p2; + idVec5 mid; + int maxpts; + + assert( this ); + + dists = (float *) _alloca( (numPoints+4) * sizeof( float ) ); + sides = (byte *) _alloca( (numPoints+4) * sizeof( byte ) ); + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return true; + } + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + numPoints = 0; + return false; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + return true; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + + newPoints = (idVec5 *) _alloca16( maxpts * sizeof( idVec5 ) ); + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( !EnsureAlloced( newNumPoints, false ) ) { + return true; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec5) ); + + return true; +} + +/* +============= +idWinding::Copy +============= +*/ +idWinding *idWinding::Copy( void ) const { + idWinding *w; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + w = new idWinding( numPoints ); + w->numPoints = numPoints; + memcpy( w->p, p, numPoints * sizeof(p[0]) ); + return w; +} + +/* +============= +idWinding::Reverse +============= +*/ +idWinding *idWinding::Reverse( void ) const { + idWinding *w; + int i; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + w = new idWinding( numPoints ); + w->numPoints = numPoints; + for ( i = 0; i < numPoints; i++ ) { + w->p[ numPoints - i - 1 ] = p[i]; + } + return w; +} + +/* +============= +idWinding::ReverseSelf +============= +*/ +void idWinding::ReverseSelf( void ) { + idVec5 v; + int i; + + for ( i = 0; i < (numPoints>>1); i++ ) { + v = p[i]; + p[i] = p[numPoints - i - 1]; + p[numPoints - i - 1] = v; + } +} + +/* +============= +idWinding::Check +============= +*/ +bool idWinding::Check( bool print ) const { + int i, j; + float d, edgedist; + idVec3 dir, edgenormal; + float area; + idPlane plane; + + if ( numPoints < 3 ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: only %i points.", numPoints ); + } + return false; + } + + area = GetArea(); + if ( area < 1.0f ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: tiny area: %f", area ); + } + return false; + } + + GetPlane( plane ); + + for ( i = 0; i < numPoints; i++ ) { + const idVec3 &p1 = p[i].ToVec3(); + + // check if the winding is huge + for ( j = 0; j < 3; j++ ) { + if ( p1[j] >= MAX_WORLD_COORD || p1[j] <= MIN_WORLD_COORD ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: point %d outside world %c-axis: %f", i, 'X'+j, p1[j] ); + } + return false; + } + } + + j = i + 1 == numPoints ? 0 : i + 1; + + // check if the point is on the face plane + d = p1 * plane.Normal() + plane[3]; + if ( d < -ON_EPSILON || d > ON_EPSILON ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: point %d off plane.", i ); + } + return false; + } + + // check if the edge isn't degenerate + const idVec3 &p2 = p[j].ToVec3(); + dir = p2 - p1; + + if ( dir.Length() < ON_EPSILON) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: edge %d is degenerate.", i ); + } + return false; + } + + // check if the winding is convex + edgenormal = plane.Normal().Cross( dir ); + edgenormal.Normalize(); + edgedist = p1 * edgenormal; + edgedist += ON_EPSILON; + + // all other points must be on front side + for ( j = 0; j < numPoints; j++ ) { + if ( j == i ) { + continue; + } + d = p[j].ToVec3() * edgenormal; + if ( d > edgedist ) { + if ( print ) { + idLib::common->Printf( "idWinding::Check: non-convex." ); + } + return false; + } + } + } + return true; +} + +/* +============= +idWinding::GetArea +============= +*/ +float idWinding::GetArea( void ) const { + int i; + idVec3 d1, d2, cross; + float total; + + total = 0.0f; + for ( i = 2; i < numPoints; i++ ) { + d1 = p[i-1].ToVec3() - p[0].ToVec3(); + d2 = p[i].ToVec3() - p[0].ToVec3(); + cross = d1.Cross( d2 ); + total += cross.Length(); + } + return total * 0.5f; +} + +/* +============= +idWinding::GetRadius +============= +*/ +float idWinding::GetRadius( const idVec3 ¢er ) const { + int i; + float radius, r; + idVec3 dir; + + radius = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = p[i].ToVec3() - center; + r = dir * dir; + if ( r > radius ) { + radius = r; + } + } + return idMath::Sqrt( radius ); +} + +/* +============= +idWinding::GetCenter +============= +*/ +idVec3 idWinding::GetCenter( void ) const { + int i; + idVec3 center; + + center.Zero(); + for ( i = 0; i < numPoints; i++ ) { + center += p[i].ToVec3(); + } + center *= ( 1.0f / numPoints ); + return center; +} + +// RAVEN BEGIN +// scork: Splash Damage's light-resize code +/* +============= +idWinding::GetNormal +============= +*/ +idVec3 idWinding::GetNormal( void ) const { + idVec3 v1, v2, center, normal; + + if ( numPoints < 3 ) { + normal.Zero(); + return normal; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + normal = v2.Cross( v1 ); + normal.Normalize(); + return normal; +} +// RAVEN END + +/* +============= +idWinding::GetPlane +============= +*/ +void idWinding::GetPlane( idVec3 &normal, float &dist ) const { + idVec3 v1, v2, center; + + if ( numPoints < 3 ) { + normal.Zero(); + dist = 0.0f; + return; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + normal = v2.Cross( v1 ); + normal.Normalize(); + dist = p[0].ToVec3() * normal; +} + +/* +============= +idWinding::GetPlane +============= +*/ +void idWinding::GetPlane( idPlane &plane ) const { + idVec3 v1, v2; + idVec3 center; + + if ( numPoints < 3 ) { + plane.Zero(); + return; + } + + center = GetCenter(); + v1 = p[0].ToVec3() - center; + v2 = p[1].ToVec3() - center; + plane.SetNormal( v2.Cross( v1 ) ); + plane.Normalize(); + plane.FitThroughPoint( p[0].ToVec3() ); +} + +/* +============= +idWinding::GetBounds +============= +*/ +void idWinding::GetBounds( idBounds &bounds ) const { + int i; + + if ( !numPoints ) { + bounds.Clear(); + return; + } + + bounds[0] = bounds[1] = p[0].ToVec3(); + for ( i = 1; i < numPoints; i++ ) { + if ( p[i].x < bounds[0].x ) { + bounds[0].x = p[i].x; + } else if ( p[i].x > bounds[1].x ) { + bounds[1].x = p[i].x; + } + if ( p[i].y < bounds[0].y ) { + bounds[0].y = p[i].y; + } else if ( p[i].y > bounds[1].y ) { + bounds[1].y = p[i].y; + } + if ( p[i].z < bounds[0].z ) { + bounds[0].z = p[i].z; + } else if ( p[i].z > bounds[1].z ) { + bounds[1].z = p[i].z; + } + } +} + +/* +============= +idWinding::RemoveEqualPoints +============= +*/ +void idWinding::RemoveEqualPoints( const float epsilon ) { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + if ( (p[i].ToVec3() - p[(i+numPoints-1)%numPoints].ToVec3()).LengthSqr() >= Square( epsilon ) ) { + continue; + } + numPoints--; + for ( j = i; j < numPoints; j++ ) { + p[j] = p[j+1]; + } + i--; + } +} + +/* +============= +idWinding::RemoveColinearPoints +============= +*/ +void idWinding::RemoveColinearPoints( const idVec3 &normal, const float epsilon ) { + int i, j; + idVec3 edgeNormal; + float dist; + + if ( numPoints <= 3 ) { + return; + } + + for ( i = 0; i < numPoints; i++ ) { + + // create plane through edge orthogonal to winding plane + edgeNormal = (p[i].ToVec3() - p[(i+numPoints-1)%numPoints].ToVec3()).Cross( normal ); + edgeNormal.Normalize(); + dist = edgeNormal * p[i].ToVec3(); + + if ( idMath::Fabs( edgeNormal * p[(i+1)%numPoints].ToVec3() - dist ) > epsilon ) { + continue; + } + + numPoints--; + for ( j = i; j < numPoints; j++ ) { + p[j] = p[j+1]; + } + i--; + } +} + +/* +============= +idWinding::AddToConvexHull + + Adds the given winding to the convex hull. + Assumes the current winding already is a convex hull with three or more points. +============= +*/ +void idWinding::AddToConvexHull( const idWinding *winding, const idVec3 &normal, const float epsilon ) { + int i, j, k; + idVec3 dir; + float d; + int maxPts; + idVec3 * hullDirs; + bool * hullSide; + bool outside; + int numNewHullPoints; + idVec5 * newHullPoints; + + if ( !winding ) { + return; + } + + maxPts = this->numPoints + winding->numPoints; + + if ( !this->EnsureAlloced( maxPts, true ) ) { + return; + } + + newHullPoints = (idVec5 *) _alloca( maxPts * sizeof( idVec5 ) ); + hullDirs = (idVec3 *) _alloca( maxPts * sizeof( idVec3 ) ); + hullSide = (bool *) _alloca( maxPts * sizeof( bool ) ); + + for ( i = 0; i < winding->numPoints; i++ ) { + const idVec5 &p1 = winding->p[i]; + + // calculate hull edge vectors + for ( j = 0; j < this->numPoints; j++ ) { + dir = this->p[ (j + 1) % this->numPoints ].ToVec3() - this->p[ j ].ToVec3(); + dir.Normalize(); + hullDirs[j] = normal.Cross( dir ); + } + + // calculate side for each hull edge + outside = false; + for ( j = 0; j < this->numPoints; j++ ) { + dir = p1.ToVec3() - this->p[j].ToVec3(); + d = dir * hullDirs[j]; + if ( d >= epsilon ) { + outside = true; + } + if ( d >= -epsilon ) { + hullSide[j] = true; + } else { + hullSide[j] = false; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0; j < this->numPoints; j++ ) { + if ( !hullSide[ j ] && hullSide[ (j + 1) % this->numPoints ] ) { + break; + } + } + if ( j >= this->numPoints ) { + continue; + } + + // insert the point here + newHullPoints[0] = p1; + numNewHullPoints = 1; + + // copy over all points that aren't double fronts + j = (j+1) % this->numPoints; + for ( k = 0; k < this->numPoints; k++ ) { + if ( hullSide[ (j+k) % this->numPoints ] && hullSide[ (j+k+1) % this->numPoints ] ) { + continue; + } + newHullPoints[numNewHullPoints] = this->p[ (j+k+1) % this->numPoints ]; + numNewHullPoints++; + } + + this->numPoints = numNewHullPoints; + memcpy( this->p, newHullPoints, numNewHullPoints * sizeof(idVec5) ); + } +} + +/* +============= +idWinding::AddToConvexHull + + Add a point to the convex hull. + The current winding must be convex but may be degenerate and can have less than three points. +============= +*/ +void idWinding::AddToConvexHull( const idVec3 &point, const idVec3 &normal, const float epsilon ) { + int j, k, numHullPoints; + idVec3 dir; + float d; + idVec3 * hullDirs; + bool * hullSide; + idVec5 * hullPoints; + bool outside; + + switch( numPoints ) { + case 0: { + p[0] = point; + numPoints++; + return; + } + case 1: { + // don't add the same point second + if ( p[0].ToVec3().Compare( point, epsilon ) ) { + return; + } + p[1].ToVec3() = point; + numPoints++; + return; + } + case 2: { + // don't add a point if it already exists + if ( p[0].ToVec3().Compare( point, epsilon ) || p[1].ToVec3().Compare( point, epsilon ) ) { + return; + } + // if only two points make sure we have the right ordering according to the normal + dir = point - p[0].ToVec3(); + dir = dir.Cross( p[1].ToVec3() - p[0].ToVec3() ); + if ( dir[0] == 0.0f && dir[1] == 0.0f && dir[2] == 0.0f ) { + // points don't make a plane + return; + } + if ( dir * normal > 0.0f ) { + p[2].ToVec3() = point; + } + else { + p[2] = p[1]; + p[1].ToVec3() = point; + } + numPoints++; + return; + } + } + + hullDirs = (idVec3 *) _alloca( numPoints * sizeof( idVec3 ) ); + hullSide = (bool *) _alloca( numPoints * sizeof( bool ) ); + + // calculate hull edge vectors + for ( j = 0; j < numPoints; j++ ) { + dir = p[(j + 1) % numPoints].ToVec3() - p[j].ToVec3(); + hullDirs[j] = normal.Cross( dir ); + } + + // calculate side for each hull edge + outside = false; + for ( j = 0; j < numPoints; j++ ) { + dir = point - p[j].ToVec3(); + d = dir * hullDirs[j]; + if ( d >= epsilon ) { + outside = true; + } + if ( d >= -epsilon ) { + hullSide[j] = true; + } else { + hullSide[j] = false; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + return; + } + + // find the back side to front side transition + for ( j = 0; j < numPoints; j++ ) { + if ( !hullSide[ j ] && hullSide[ (j + 1) % numPoints ] ) { + break; + } + } + if ( j >= numPoints ) { + return; + } + + hullPoints = (idVec5 *) _alloca( (numPoints+1) * sizeof( idVec5 ) ); + + // insert the point here + hullPoints[0] = point; + numHullPoints = 1; + + // copy over all points that aren't double fronts + j = (j+1) % numPoints; + for ( k = 0; k < numPoints; k++ ) { + if ( hullSide[ (j+k) % numPoints ] && hullSide[ (j+k+1) % numPoints ] ) { + continue; + } + hullPoints[numHullPoints] = p[ (j+k+1) % numPoints ]; + numHullPoints++; + } + + if ( !EnsureAlloced( numHullPoints, false ) ) { + return; + } + numPoints = numHullPoints; + memcpy( p, hullPoints, numHullPoints * sizeof(idVec5) ); +} + +/* +============= +idWinding::TryMerge +============= +*/ +#define CONTINUOUS_EPSILON 0.005f + +idWinding *idWinding::TryMerge( const idWinding &w, const idVec3 &planenormal, int keep ) const { + idVec3 *p1, *p2, *p3, *p4, *back; + idWinding *newf; + const idWinding *f1, *f2; + int i, j, k, l; + idVec3 normal, delta; + float dot; + bool keep1, keep2; + + f1 = this; + f2 = &w; + // + // find a idLib::common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; + + for ( i = 0; i < f1->numPoints; i++ ) { + p1 = &f1->p[i].ToVec3(); + p2 = &f1->p[(i+1) % f1->numPoints].ToVec3(); + for ( j = 0; j < f2->numPoints; j++ ) { + p3 = &f2->p[j].ToVec3(); + p4 = &f2->p[(j+1) % f2->numPoints].ToVec3(); + for (k = 0; k < 3; k++ ) { + if ( idMath::Fabs((*p1)[k] - (*p4)[k]) > 0.1f ) { + break; + } + if ( idMath::Fabs((*p2)[k] - (*p3)[k]) > 0.1f ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j < f2->numPoints ) { + break; + } + } + + if ( i == f1->numPoints ) { + return NULL; // no matching edges + } + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = &f1->p[(i+f1->numPoints-1)%f1->numPoints].ToVec3(); + delta = (*p1) - (*back); + normal = planenormal.Cross(delta); + normal.Normalize(); + + back = &f2->p[(j+2)%f2->numPoints].ToVec3(); + delta = (*back) - (*p1); + dot = delta * normal; + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + + keep1 = (bool)(dot < -CONTINUOUS_EPSILON); + + back = &f1->p[(i+2)%f1->numPoints].ToVec3(); + delta = (*back) - (*p2); + normal = planenormal.Cross( delta ); + normal.Normalize(); + + back = &f2->p[(j+f2->numPoints-1)%f2->numPoints].ToVec3(); + delta = (*back) - (*p2); + dot = delta * normal; + if ( dot > CONTINUOUS_EPSILON ) { + return NULL; // not a convex polygon + } + + keep2 = (bool)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + newf = new idWinding( f1->numPoints + f2->numPoints ); + + // copy first polygon + for ( k = (i+1) % f1->numPoints; k != i; k = (k+1) % f1->numPoints ) { + if ( !keep && k == (i+1) % f1->numPoints && !keep2 ) { + continue; + } + + newf->p[newf->numPoints] = f1->p[k]; + newf->numPoints++; + } + + // copy second polygon + for ( l = (j+1) % f2->numPoints; l != j; l = (l+1) % f2->numPoints ) { + if ( !keep && l == (j+1) % f2->numPoints && !keep1 ) { + continue; + } + newf->p[newf->numPoints] = f2->p[l]; + newf->numPoints++; + } + + return newf; +} + +/* +============= +idWinding::RemovePoint +============= +*/ +void idWinding::RemovePoint( int point ) { + if ( point < 0 || point >= numPoints ) { + idLib::common->FatalError( "idWinding::removePoint: point out of range" ); + } + if ( point < numPoints - 1) { + memmove(&p[point], &p[point+1], (numPoints - point - 1) * sizeof(p[0]) ); + } + numPoints--; +} + +/* +============= +idWinding::InsertPoint +============= +*/ +void idWinding::InsertPoint( const idVec3 &point, int spot ) { + int i; + + if ( spot > numPoints ) { + idLib::common->FatalError( "idWinding::insertPoint: spot > numPoints" ); + } + + if ( spot < 0 ) { + idLib::common->FatalError( "idWinding::insertPoint: spot < 0" ); + } + + EnsureAlloced( numPoints+1, true ); + for ( i = numPoints; i > spot; i-- ) { + p[i] = p[i-1]; + } + p[spot] = point; + numPoints++; +} + +/* +============= +idWinding::InsertPointIfOnEdge +============= +*/ +bool idWinding::InsertPointIfOnEdge( const idVec3 &point, const idPlane &plane, const float epsilon ) { + int i; + float dist, dot; + idVec3 normal; + + // point may not be too far from the winding plane + if ( idMath::Fabs( plane.Distance( point ) ) > epsilon ) { + return false; + } + + for ( i = 0; i < numPoints; i++ ) { + + // create plane through edge orthogonal to winding plane + normal = (p[(i+1)%numPoints].ToVec3() - p[i].ToVec3()).Cross( plane.Normal() ); + normal.Normalize(); + dist = normal * p[i].ToVec3(); + + if ( idMath::Fabs( normal * point - dist ) > epsilon ) { + continue; + } + + normal = plane.Normal().Cross( normal ); + dot = normal * point; + + dist = dot - normal * p[i].ToVec3(); + + if ( dist < epsilon ) { + // if the winding already has the point + if ( dist > -epsilon ) { + return false; + } + continue; + } + + dist = dot - normal * p[(i+1)%numPoints].ToVec3(); + + if ( dist > -epsilon ) { + // if the winding already has the point + if ( dist < epsilon ) { + return false; + } + continue; + } + + InsertPoint( point, i+1 ); + return true; + } + return false; +} + +/* +============= +idWinding::IsTiny +============= +*/ +#define EDGE_LENGTH 0.2f + +bool idWinding::IsTiny( void ) const { + int i; + float len; + idVec3 delta; + int edges; + + edges = 0; + for ( i = 0; i < numPoints; i++ ) { + delta = p[(i+1)%numPoints].ToVec3() - p[i].ToVec3(); + len = delta.Length(); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return false; + } + } + } + return true; +} + +/* +============= +idWinding::IsHuge +============= +*/ +bool idWinding::IsHuge( void ) const { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( p[i][j] <= MIN_WORLD_COORD || p[i][j] >= MAX_WORLD_COORD ) { + return true; + } + } + } + return false; +} + +/* +============= +idWinding::Print +============= +*/ +void idWinding::Print( void ) const { + int i; + + for ( i = 0; i < numPoints; i++ ) { + idLib::common->Printf( "(%5.1f, %5.1f, %5.1f)\n", p[i][0], p[i][1], p[i][2] ); + } +} + +/* +============= +idWinding::PlaneDistance +============= +*/ +float idWinding::PlaneDistance( const idPlane &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < numPoints; i++ ) { + d = plane.Distance( p[i].ToVec3() ); + if ( d < min ) { + min = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( FLOATSIGNBITNOTSET( min ) ) { + return min; + } + if ( FLOATSIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idWinding::PlaneSide +============= +*/ +int idWinding::PlaneSide( const idPlane &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < numPoints; i++ ) { + d = plane.Distance( p[i].ToVec3() ); + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +============= +idWinding::PlanesConcave +============= +*/ +#define WCONVEX_EPSILON 0.2f + +bool idWinding::PlanesConcave( const idWinding &w2, const idVec3 &normal1, const idVec3 &normal2, float dist1, float dist2 ) const { + int i; + + // check if one of the points of winding 1 is at the back of the plane of winding 2 + for ( i = 0; i < numPoints; i++ ) { + if ( normal2 * p[i].ToVec3() - dist2 > WCONVEX_EPSILON ) { + return true; + } + } + // check if one of the points of winding 2 is at the back of the plane of winding 1 + for ( i = 0; i < w2.numPoints; i++ ) { + if ( normal1 * w2.p[i].ToVec3() - dist1 > WCONVEX_EPSILON ) { + return true; + } + } + + return false; +} + +/* +============= +idWinding::PointInside +============= +*/ +bool idWinding::PointInside( const idVec3 &normal, const idVec3 &point, const float epsilon ) const { + int i; + idVec3 dir, n, pointvec; + + for ( i = 0; i < numPoints; i++ ) { + dir = p[(i+1) % numPoints].ToVec3() - p[i].ToVec3(); + pointvec = point - p[i].ToVec3(); + + n = dir.Cross( normal ); + + if ( pointvec * n < -epsilon ) { + return false; + } + } + return true; +} + +/* +============= +idWinding::LineIntersection +============= +*/ +bool idWinding::LineIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &end, bool backFaceCull ) const { + float front, back, frac; + idVec3 mid; + + front = windingPlane.Distance( start ); + back = windingPlane.Distance( end ); + + // if both points at the same side of the plane + if ( front < 0.0f && back < 0.0f ) { + return false; + } + + if ( front > 0.0f && back > 0.0f ) { + return false; + } + + // if back face culled + if ( backFaceCull && front < 0.0f ) { + return false; + } + + // get point of intersection with winding plane + if ( idMath::Fabs(front - back) < 0.0001f ) { + mid = end; + } + else { + frac = front / (front - back); + mid[0] = start[0] + (end[0] - start[0]) * frac; + mid[1] = start[1] + (end[1] - start[1]) * frac; + mid[2] = start[2] + (end[2] - start[2]) * frac; + } + + return PointInside( windingPlane.Normal(), mid, 0.0f ); +} + +/* +============= +idWinding::RayIntersection +============= +*/ +bool idWinding::RayIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull ) const { + int i; + bool side, lastside = false; + idPluecker pl1, pl2; + + scale = 0.0f; + pl1.FromRay( start, dir ); + for ( i = 0; i < numPoints; i++ ) { + pl2.FromLine( p[i].ToVec3(), p[(i+1)%numPoints].ToVec3() ); + side = pl1.PermutedInnerProduct( pl2 ) > 0.0f; + if ( i && side != lastside ) { + return false; + } + lastside = side; + } + if ( !backFaceCull || lastside ) { + windingPlane.RayIntersection( start, dir, scale ); + return true; + } + return false; +} + +/* +================= +idWinding::TriangleArea +================= +*/ +float idWinding::TriangleArea( const idVec3 &a, const idVec3 &b, const idVec3 &c ) { + idVec3 v1, v2; + idVec3 cross; + + v1 = b - a; + v2 = c - a; + cross = v1.Cross( v2 ); + return 0.5f * cross.Length(); +} + + +//=============================================================== +// +// idFixedWinding +// +//=============================================================== + +/* +============= +idFixedWinding::ReAllocate +============= +*/ +bool idFixedWinding::ReAllocate( int n, bool keep ) { + + assert( n <= MAX_POINTS_ON_WINDING ); + + if ( n > MAX_POINTS_ON_WINDING ) { + idLib::common->Printf("WARNING: idFixedWinding -> MAX_POINTS_ON_WINDING overflowed\n"); + return false; + } + return true; +} + +/* +============= +idFixedWinding::Split +============= +*/ +int idFixedWinding::Split( idFixedWinding *back, const idPlane &plane, const float epsilon ) { + int counts[3]; + float dists[MAX_POINTS_ON_WINDING+4]; + byte sides[MAX_POINTS_ON_WINDING+4]; + float dot; + int i, j; + idVec5 *p1, *p2; + idVec5 mid; + idFixedWinding out; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.Distance( p[i].ToVec3() ); + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if ( !counts[SIDE_BACK] ) { + if ( !counts[SIDE_FRONT] ) { + return SIDE_ON; + } + else { + return SIDE_FRONT; + } + } + + if ( !counts[SIDE_FRONT] ) { + return SIDE_BACK; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + out.numPoints = 0; + back->numPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( !out.EnsureAlloced( out.numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + if ( !back->EnsureAlloced( back->numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + out.p[out.numPoints] = *p1; + out.numPoints++; + back->p[back->numPoints] = *p1; + back->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + out.p[out.numPoints] = *p1; + out.numPoints++; + } + if ( sides[i] == SIDE_BACK ) { + back->p[back->numPoints] = *p1; + back->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( !out.EnsureAlloced( out.numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + if ( !back->EnsureAlloced( back->numPoints+1, true ) ) { + return SIDE_FRONT; // can't split -- fall back to original + } + + // generate a split point + j = i + 1; + if ( j >= numPoints ) { + p2 = &p[0]; + } + else { + p2 = &p[j]; + } + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 3; j++ ) { + // avoid round off error when possible + if ( plane.Normal()[j] == 1.0f ) { + mid[j] = plane.Dist(); + } else if ( plane.Normal()[j] == -1.0f ) { + mid[j] = -plane.Dist(); + } else { + mid[j] = (*p1)[j] + dot * ( (*p2)[j] - (*p1)[j] ); + } + } + mid.s = p1->s + dot * ( p2->s - p1->s ); + mid.t = p1->t + dot * ( p2->t - p1->t ); + + out.p[out.numPoints] = mid; + out.numPoints++; + back->p[back->numPoints] = mid; + back->numPoints++; + } + for ( i = 0; i < out.numPoints; i++ ) { + p[i] = out.p[i]; + } + numPoints = out.numPoints; + + return SIDE_CROSS; +} diff --git a/src/idlib/geometry/Winding.h b/src/idlib/geometry/Winding.h new file mode 100644 index 0000000..323eb7e --- /dev/null +++ b/src/idlib/geometry/Winding.h @@ -0,0 +1,420 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __WINDING_H__ +#define __WINDING_H__ + +/* +=============================================================================== + + A winding is an arbitrary convex polygon defined by an array of points. + +=============================================================================== +*/ + +class idWinding { + +public: + idWinding( void ); + explicit idWinding( const int n ); // allocate for n points + explicit idWinding( const idVec3 *verts, const int n ); // winding from points + explicit idWinding( const idVec3 &normal, const float dist ); // base winding for plane + explicit idWinding( const idPlane &plane ); // base winding for plane + explicit idWinding( const idWinding &winding ); + virtual ~idWinding( void ); + + idWinding & operator=( const idWinding &winding ); + const idVec5 & operator[]( const int index ) const; + idVec5 & operator[]( const int index ); + + // add a point to the end of the winding point array + idWinding & operator+=( const idVec3 &v ); + idWinding & operator+=( const idVec5 &v ); +// RAVEN BEGIN + idWinding & operator+=( const idDrawVert &dv ); +// RAVEN END + void AddPoint( const idVec3 &v ); + void AddPoint( const idVec5 &v ); + + // number of points on winding + int GetNumPoints( void ) const; + void SetNumPoints( int n ); + virtual void Clear( void ); + + // huge winding for plane, the points go counter clockwise when facing the front of the plane + void BaseForPlane( const idVec3 &normal, const float dist ); + void BaseForPlane( const idPlane &plane ); + + // splits the winding into a front and back winding, the winding itself stays unchanged + // returns a SIDE_? + int Split( const idPlane &plane, const float epsilon, idWinding **front, idWinding **back ) const; + // returns the winding fragment at the front of the clipping plane, + // if there is nothing at the front the winding itself is destroyed and NULL is returned + idWinding * Clip( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idPlane &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + // returns a copy of the winding + idWinding * Copy( void ) const; + idWinding * Reverse( void ) const; + void ReverseSelf( void ); + void RemoveEqualPoints( const float epsilon = ON_EPSILON ); + void RemoveColinearPoints( const idVec3 &normal, const float epsilon = ON_EPSILON ); + void RemovePoint( int point ); + void InsertPoint( const idVec3 &point, int spot ); + bool InsertPointIfOnEdge( const idVec3 &point, const idPlane &plane, const float epsilon = ON_EPSILON ); + // add a winding to the convex hull + void AddToConvexHull( const idWinding *winding, const idVec3 &normal, const float epsilon = ON_EPSILON ); + // add a point to the convex hull + void AddToConvexHull( const idVec3 &point, const idVec3 &normal, const float epsilon = ON_EPSILON ); + // tries to merge 'this' with the given winding, returns NULL if merge fails, both 'this' and 'w' stay intact + // 'keep' tells if the contacting points should stay even if they create colinear edges + idWinding * TryMerge( const idWinding &w, const idVec3 &normal, int keep = false ) const; + // check whether the winding is valid or not + bool Check( bool print = true ) const; + + float GetArea( void ) const; + idVec3 GetCenter( void ) const; + +// RAVEN BEGIN +// scork: Splash Damage's light-resize code + idVec3 GetNormal( void ) const; +// RAVEN END + float GetRadius( const idVec3 ¢er ) const; + void GetPlane( idVec3 &normal, float &dist ) const; + void GetPlane( idPlane &plane ) const; + void GetBounds( idBounds &bounds ) const; + + bool IsTiny( void ) const; + bool IsHuge( void ) const; // base winding for a plane is typically huge + void Print( void ) const; + + float PlaneDistance( const idPlane &plane ) const; + int PlaneSide( const idPlane &plane, const float epsilon = ON_EPSILON ) const; + + bool PlanesConcave( const idWinding &w2, const idVec3 &normal1, const idVec3 &normal2, float dist1, float dist2 ) const; + + bool PointInside( const idVec3 &normal, const idVec3 &point, const float epsilon ) const; + // returns true if the line or ray intersects the winding + bool LineIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &end, bool backFaceCull = false ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idPlane &windingPlane, const idVec3 &start, const idVec3 &dir, float &scale, bool backFaceCull = false ) const; + + static float TriangleArea( const idVec3 &a, const idVec3 &b, const idVec3 &c ); + +protected: + int numPoints; // number of points + idVec5 * p; // pointer to point data + int allocedSize; + + bool EnsureAlloced( int n, bool keep = false ); + virtual bool ReAllocate( int n, bool keep = false ); +}; + +ID_INLINE idWinding::idWinding( void ) { + numPoints = allocedSize = 0; + p = NULL; +} + +ID_INLINE idWinding::idWinding( int n ) { + numPoints = allocedSize = 0; + p = NULL; + EnsureAlloced( n ); +} + +ID_INLINE idWinding::idWinding( const idVec3 *verts, const int n ) { + int i; + + numPoints = allocedSize = 0; + p = NULL; + if ( !EnsureAlloced( n ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < n; i++ ) { + p[i].ToVec3() = verts[i]; + p[i].s = p[i].t = 0.0f; + } + numPoints = n; +} + +ID_INLINE idWinding::idWinding( const idVec3 &normal, const float dist ) { + numPoints = allocedSize = 0; + p = NULL; + BaseForPlane( normal, dist ); +} + +ID_INLINE idWinding::idWinding( const idPlane &plane ) { + numPoints = allocedSize = 0; + p = NULL; + BaseForPlane( plane ); +} + +ID_INLINE idWinding::idWinding( const idWinding &winding ) { + int i; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idWinding::~idWinding( void ) { + delete[] p; + p = NULL; +} + +ID_INLINE idWinding &idWinding::operator=( const idWinding &winding ) { + int i; + + if ( !EnsureAlloced( winding.numPoints ) ) { + numPoints = 0; + return *this; + } + for ( i = 0; i < winding.numPoints; i++ ) { + p[i] = winding.p[i]; + } + numPoints = winding.numPoints; + return *this; +} + +ID_INLINE const idVec5 &idWinding::operator[]( const int index ) const { + //assert( index >= 0 && index < numPoints ); + return p[ index ]; +} + +ID_INLINE idVec5 &idWinding::operator[]( const int index ) { + //assert( index >= 0 && index < numPoints ); + return p[ index ]; +} + +ID_INLINE idWinding &idWinding::operator+=( const idVec3 &v ) { + AddPoint( v ); + return *this; +} + +ID_INLINE idWinding &idWinding::operator+=( const idVec5 &v ) { + AddPoint( v ); + return *this; +} + +// RAVEN BEGIN +ID_INLINE idWinding &idWinding::operator+=( const idDrawVert &dv ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return *this; + } + p[numPoints] = idVec5( dv.xyz, dv.st ); + numPoints++; + return *this; +} +// RAVEN END + +ID_INLINE void idWinding::AddPoint( const idVec3 &v ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return; + } + p[numPoints] = v; + numPoints++; +} + +ID_INLINE void idWinding::AddPoint( const idVec5 &v ) { + if ( !EnsureAlloced(numPoints+1, true) ) { + return; + } + p[numPoints] = v; + numPoints++; +} + +ID_INLINE int idWinding::GetNumPoints( void ) const { + return numPoints; +} + +ID_INLINE void idWinding::SetNumPoints( int n ) { + if ( !EnsureAlloced( n, true ) ) { + return; + } + numPoints = n; +} + +ID_INLINE void idWinding::Clear( void ) { + numPoints = 0; + // RAVENBEGIN + // cdr: need to clear the allocated size too, or we wont' be able to readd points + allocedSize = 0; + // RAVENEND + delete[] p; + p = NULL; +} + +ID_INLINE void idWinding::BaseForPlane( const idPlane &plane ) { + BaseForPlane( plane.Normal(), plane.Dist() ); +} + +ID_INLINE bool idWinding::EnsureAlloced( int n, bool keep ) { + if ( n > allocedSize ) { + return ReAllocate( n, keep ); + } + return true; +} + + +/* +=============================================================================== + + idFixedWinding is a fixed buffer size winding not using + memory allocations. + + When an operation would overflow the fixed buffer a warning + is printed and the operation is safely cancelled. + +=============================================================================== +*/ + +#define MAX_POINTS_ON_WINDING 64 + +class idFixedWinding : public idWinding { + +public: + idFixedWinding( void ); + explicit idFixedWinding( const int n ); + explicit idFixedWinding( const idVec3 *verts, const int n ); + explicit idFixedWinding( const idVec3 &normal, const float dist ); + explicit idFixedWinding( const idPlane &plane ); + explicit idFixedWinding( const idWinding &winding ); + explicit idFixedWinding( const idFixedWinding &winding ); + virtual ~idFixedWinding( void ); + + idFixedWinding &operator=( const idWinding &winding ); + + virtual void Clear( void ); + + // splits the winding in a back and front part, 'this' becomes the front part + // returns a SIDE_? + int Split( idFixedWinding *back, const idPlane &plane, const float epsilon = ON_EPSILON ); + +protected: + idVec5 data[MAX_POINTS_ON_WINDING]; // point data + + virtual bool ReAllocate( int n, bool keep = false ); +}; + +ID_INLINE idFixedWinding::idFixedWinding( void ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; +} + +ID_INLINE idFixedWinding::idFixedWinding( int n ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; +} + +ID_INLINE idFixedWinding::idFixedWinding( const idVec3 *verts, const int n ) { + int i; + + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( n ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < n; i++ ) { + p[i].ToVec3() = verts[i]; + p[i].s = p[i].t = 0; + } + numPoints = n; +} + +ID_INLINE idFixedWinding::idFixedWinding( const idVec3 &normal, const float dist ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + BaseForPlane( normal, dist ); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idPlane &plane ) { + numPoints = 0; + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + BaseForPlane( plane ); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idWinding &winding ) { + int i; + + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idFixedWinding::idFixedWinding( const idFixedWinding &winding ) { + int i; + + p = data; + allocedSize = MAX_POINTS_ON_WINDING; + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); +} + +ID_INLINE idFixedWinding::~idFixedWinding( void ) { + p = NULL; // otherwise it tries to free the fixed buffer +} + +ID_INLINE idFixedWinding &idFixedWinding::operator=( const idWinding &winding ) { + int i; + + if ( !EnsureAlloced( winding.GetNumPoints() ) ) { + numPoints = 0; + return *this; + } + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + p[i] = winding[i]; + } + numPoints = winding.GetNumPoints(); + return *this; +} + +ID_INLINE void idFixedWinding::Clear( void ) { + numPoints = 0; +} +#endif /* !__WINDING_H__ */ diff --git a/src/idlib/geometry/Winding2D.cpp b/src/idlib/geometry/Winding2D.cpp new file mode 100644 index 0000000..10d20f9 --- /dev/null +++ b/src/idlib/geometry/Winding2D.cpp @@ -0,0 +1,750 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Winding2D.h" + + +/* +============ +GetAxialBevel +============ +*/ +bool GetAxialBevel( const idVec3 &plane1, const idVec3 &plane2, const idVec2 &point, idVec3 &bevel ) { + if ( FLOATSIGNBITSET( plane1.x ) ^ FLOATSIGNBITSET( plane2.x ) ) { + if ( idMath::Fabs( plane1.x ) > 0.1f && idMath::Fabs( plane2.x ) > 0.1f ) { + bevel.x = 0.0f; + if ( FLOATSIGNBITSET( plane1.y ) ) { + bevel.y = -1.0f; + } + else { + bevel.y = 1.0f; + } + bevel.z = - ( point.x * bevel.x + point.y * bevel.y ); + return true; + } + } + if ( FLOATSIGNBITSET( plane1.y ) ^ FLOATSIGNBITSET( plane2.y ) ) { + if ( idMath::Fabs( plane1.y ) > 0.1f && idMath::Fabs( plane2.y ) > 0.1f ) { + bevel.y = 0.0f; + if ( FLOATSIGNBITSET( plane1.x ) ) { + bevel.x = -1.0f; + } + else { + bevel.x = 1.0f; + } + bevel.z = - ( point.x * bevel.x + point.y * bevel.y ); + return true; + } + } + return false; +} + +/* +============ +idWinding2D::ExpandForAxialBox +============ +*/ +void idWinding2D::ExpandForAxialBox( const idVec2 bounds[2] ) { + int i, j, numPlanes; + idVec2 v; + idVec3 planes[MAX_POINTS_ON_WINDING_2D], plane, bevel; + + // get planes for the edges and add bevels + for ( numPlanes = i = 0; i < numPoints; i++ ) { + j = (i+1) % numPoints; + if ( ( p[j] - p[i] ).LengthSqr() < 0.01f ) { + continue; + } + plane = Plane2DFromPoints( p[i], p[j], true ); + if ( i ) { + if ( GetAxialBevel( planes[numPlanes-1], plane, p[i], bevel ) ) { + planes[numPlanes++] = bevel; + } + } + assert( numPlanes < MAX_POINTS_ON_WINDING_2D ); + planes[numPlanes++] = plane; + } + if ( GetAxialBevel( planes[numPlanes-1], planes[0], p[0], bevel ) ) { + planes[numPlanes++] = bevel; + } + + // expand the planes + for ( i = 0; i < numPlanes; i++ ) { + v.x = bounds[ FLOATSIGNBITSET( planes[i].x ) ].x; + v.y = bounds[ FLOATSIGNBITSET( planes[i].y ) ].y; + planes[i].z += v.x * planes[i].x + v.y * planes[i].y; + } + + // get intersection points of the planes + for ( numPoints = i = 0; i < numPlanes; i++ ) { + if ( Plane2DIntersection( planes[(i+numPlanes-1) % numPlanes], planes[i], p[numPoints] ) ) { + numPoints++; + } + } +} + +/* +============ +idWinding2D::Expand +============ +*/ +void idWinding2D::Expand( const float d ) { + int i; + idVec2 edgeNormals[MAX_POINTS_ON_WINDING_2D]; + + for ( i = 0; i < numPoints; i++ ) { + idVec2 &start = p[i]; + idVec2 &end = p[(i+1)%numPoints]; + edgeNormals[i].x = start.y - end.y; + edgeNormals[i].y = end.x - start.x; + edgeNormals[i].Normalize(); + edgeNormals[i] *= d; + } + + for ( i = 0; i < numPoints; i++ ) { + p[i] += edgeNormals[i] + edgeNormals[(i+numPoints-1)%numPoints]; + } +} + +/* +============= +idWinding2D::Split +============= +*/ +int idWinding2D::Split( const idVec3 &plane, const float epsilon, idWinding2D **front, idWinding2D **back ) const { + float dists[MAX_POINTS_ON_WINDING_2D]; + byte sides[MAX_POINTS_ON_WINDING_2D]; + int counts[3]; + float dot; + int i, j; + const idVec2 * p1, *p2; + idVec2 mid; + idWinding2D * f; + idWinding2D * b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + // if nothing at the front of the clipping plane + if ( !counts[SIDE_FRONT] ) { + *back = Copy(); + return SIDE_BACK; + } + // if nothing at the back of the clipping plane + if ( !counts[SIDE_BACK] ) { + *front = Copy(); + return SIDE_FRONT; + } + + maxpts = numPoints+4; // cant use counts[0]+2 because of fp grouping errors + + *front = f = new idWinding2D; + *back = b = new idWinding2D; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( sides[i] == SIDE_ON ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + b->p[b->numPoints] = *p1; + b->numPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + f->p[f->numPoints] = *p1; + f->numPoints++; + } + + if ( sides[i] == SIDE_BACK ) { + b->p[b->numPoints] = *p1; + b->numPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + // always calculate the split going from the same side + // or minor epsilon issues can happen + if ( sides[i] == SIDE_FRONT ) { + dot = dists[i] / ( dists[i] - dists[i+1] ); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p1)[j] + dot * ((*p2)[j] - (*p1)[j]); + } + } + } else { + dot = dists[i+1] / ( dists[i+1] - dists[i] ); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p2)[j] + dot * ( (*p1)[j] - (*p2)[j] ); + } + } + } + + f->p[f->numPoints] = mid; + f->numPoints++; + b->p[b->numPoints] = mid; + b->numPoints++; + } + + return SIDE_CROSS; +} + +/* +============ +idWinding2D::ClipInPlace +============ +*/ +bool idWinding2D::ClipInPlace( const idVec3 &plane, const float epsilon, const bool keepOn ) { + int i, j, maxpts, newNumPoints; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float dot, dists[MAX_POINTS_ON_WINDING_2D+1]; + idVec2 *p1, *p2, mid, newPoints[MAX_POINTS_ON_WINDING_2D+4]; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + for ( i = 0; i < numPoints; i++ ) { + dists[i] = dot = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + // if the winding is on the plane and we should keep it + if ( keepOn && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) { + return true; + } + if ( !counts[SIDE_FRONT] ) { + numPoints = 0; + return false; + } + if ( !counts[SIDE_BACK] ) { + return true; + } + + maxpts = numPoints + 4; // cant use counts[0]+2 because of fp grouping errors + newNumPoints = 0; + + for ( i = 0; i < numPoints; i++ ) { + p1 = &p[i]; + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + if ( sides[i] == SIDE_ON ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + newPoints[newNumPoints] = *p1; + newNumPoints++; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + if ( newNumPoints+1 > maxpts ) { + return true; // can't split -- fall back to original + } + + // generate a split point + p2 = &p[(i+1)%numPoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + for ( j = 0; j < 2; j++ ) { + // avoid round off error when possible + if ( plane[j] == 1.0f ) { + mid[j] = plane.z; + } else if ( plane[j] == -1.0f ) { + mid[j] = -plane.z; + } else { + mid[j] = (*p1)[j] + dot * ((*p2)[j] - (*p1)[j]); + } + } + + newPoints[newNumPoints] = mid; + newNumPoints++; + } + + if ( newNumPoints >= MAX_POINTS_ON_WINDING_2D ) { + return true; + } + + numPoints = newNumPoints; + memcpy( p, newPoints, newNumPoints * sizeof(idVec2) ); + + return true; +} + +/* +============= +idWinding2D::Copy +============= +*/ +idWinding2D *idWinding2D::Copy( void ) const { + idWinding2D *w; + + w = new idWinding2D; + w->numPoints = numPoints; + memcpy( w->p, p, numPoints * sizeof( p[0] ) ); + return w; +} + +/* +============= +idWinding2D::Reverse +============= +*/ +idWinding2D *idWinding2D::Reverse( void ) const { + idWinding2D *w; + int i; + + w = new idWinding2D; + w->numPoints = numPoints; + for ( i = 0; i < numPoints; i++ ) { + w->p[ numPoints - i - 1 ] = p[i]; + } + return w; +} + +/* +============ +idWinding2D::GetArea +============ +*/ +float idWinding2D::GetArea( void ) const { + int i; + idVec2 d1, d2; + float total; + + total = 0.0f; + for ( i = 2; i < numPoints; i++ ) { + d1 = p[i-1] - p[0]; + d2 = p[i] - p[0]; + total += d1.x * d2.y - d1.y * d2.x; + } + return total * 0.5f; +} + +/* +============ +idWinding2D::GetCenter +============ +*/ +idVec2 idWinding2D::GetCenter( void ) const { + int i; + idVec2 center; + + center.Zero(); + for ( i = 0; i < numPoints; i++ ) { + center += p[i]; + } + center *= ( 1.0f / numPoints ); + return center; +} + +/* +============ +idWinding2D::GetRadius +============ +*/ +float idWinding2D::GetRadius( const idVec2 ¢er ) const { + int i; + float radius, r; + idVec2 dir; + + radius = 0.0f; + for ( i = 0; i < numPoints; i++ ) { + dir = p[i] - center; + r = dir * dir; + if ( r > radius ) { + radius = r; + } + } + return idMath::Sqrt( radius ); +} + +/* +============ +idWinding2D::GetBounds +============ +*/ +void idWinding2D::GetBounds( idVec2 bounds[2] ) const { + int i; + + if ( !numPoints ) { + bounds[0].x = bounds[0].y = idMath::INFINITY; + bounds[1].x = bounds[1].y = -idMath::INFINITY; + return; + } + bounds[0] = bounds[1] = p[0]; + for ( i = 1; i < numPoints; i++ ) { + if ( p[i].x < bounds[0].x ) { + bounds[0].x = p[i].x; + } else if ( p[i].x > bounds[1].x ) { + bounds[1].x = p[i].x; + } + if ( p[i].y < bounds[0].y ) { + bounds[0].y = p[i].y; + } else if ( p[i].y > bounds[1].y ) { + bounds[1].y = p[i].y; + } + } +} + +/* +============= +idWinding2D::IsTiny +============= +*/ +#define EDGE_LENGTH 0.2f + +bool idWinding2D::IsTiny( void ) const { + int i; + float len; + idVec2 delta; + int edges; + + edges = 0; + for ( i = 0; i < numPoints; i++ ) { + delta = p[(i+1)%numPoints] - p[i]; + len = delta.Length(); + if ( len > EDGE_LENGTH ) { + if ( ++edges == 3 ) { + return false; + } + } + } + return true; +} + +/* +============= +idWinding2D::IsHuge +============= +*/ +bool idWinding2D::IsHuge( void ) const { + int i, j; + + for ( i = 0; i < numPoints; i++ ) { + for ( j = 0; j < 2; j++ ) { + if ( p[i][j] <= MIN_WORLD_COORD || p[i][j] >= MAX_WORLD_COORD ) { + return true; + } + } + } + return false; +} + +/* +============= +idWinding2D::Print +============= +*/ +void idWinding2D::Print( void ) const { + int i; + + for ( i = 0; i < numPoints; i++ ) { + idLib::common->Printf( "(%5.1f, %5.1f)\n", p[i][0], p[i][1] ); + } +} + +/* +============= +idWinding2D::PlaneDistance +============= +*/ +float idWinding2D::PlaneDistance( const idVec3 &plane ) const { + int i; + float d, min, max; + + min = idMath::INFINITY; + max = -min; + for ( i = 0; i < numPoints; i++ ) { + d = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d < min ) { + min = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + if ( d > max ) { + max = d; + if ( FLOATSIGNBITSET( min ) & FLOATSIGNBITNOTSET( max ) ) { + return 0.0f; + } + } + } + if ( FLOATSIGNBITNOTSET( min ) ) { + return min; + } + if ( FLOATSIGNBITSET( max ) ) { + return max; + } + return 0.0f; +} + +/* +============= +idWinding2D::PlaneSide +============= +*/ +int idWinding2D::PlaneSide( const idVec3 &plane, const float epsilon ) const { + bool front, back; + int i; + float d; + + front = false; + back = false; + for ( i = 0; i < numPoints; i++ ) { + d = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d < -epsilon ) { + if ( front ) { + return SIDE_CROSS; + } + back = true; + continue; + } + else if ( d > epsilon ) { + if ( back ) { + return SIDE_CROSS; + } + front = true; + continue; + } + } + + if ( back ) { + return SIDE_BACK; + } + if ( front ) { + return SIDE_FRONT; + } + return SIDE_ON; +} + +/* +============ +idWinding2D::PointInside +============ +*/ +bool idWinding2D::PointInside( const idVec2 &point, const float epsilon ) const { + int i; + float d; + idVec3 plane; + + for ( i = 0; i < numPoints; i++ ) { + plane = Plane2DFromPoints( p[i], p[(i+1) % numPoints] ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d > epsilon ) { + return false; + } + } + return true; +} + +/* +============ +idWinding2D::LineIntersection +============ +*/ +bool idWinding2D::LineIntersection( const idVec2 &start, const idVec2 &end ) const { + int i, numEdges; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float d1, d2, epsilon = 0.1f; + idVec3 plane, edges[2]; + + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + plane = Plane2DFromPoints( start, end ); + for ( i = 0; i < numPoints; i++ ) { + d1 = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d1 > epsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( d1 < -epsilon ) { + sides[i] = SIDE_BACK; + } + else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + + if ( !counts[SIDE_FRONT] ) { + return false; + } + if ( !counts[SIDE_BACK] ) { + return false; + } + + numEdges = 0; + for ( i = 0; i < numPoints; i++ ) { + if ( sides[i] != sides[i+1] && sides[i+1] != SIDE_ON ) { + edges[numEdges++] = Plane2DFromPoints( p[i], p[(i+1)%numPoints] ); + if ( numEdges >= 2 ) { + break; + } + } + } + if ( numEdges < 2 ) { + return false; + } + + d1 = edges[0].x * start.x + edges[0].y * start.y + edges[0].z; + d2 = edges[0].x * end.x + edges[0].y * end.y + edges[0].z; + if ( FLOATSIGNBITNOTSET( d1 ) & FLOATSIGNBITNOTSET( d2 ) ) { + return false; + } + d1 = edges[1].x * start.x + edges[1].y * start.y + edges[1].z; + d2 = edges[1].x * end.x + edges[1].y * end.y + edges[1].z; + if ( FLOATSIGNBITNOTSET( d1 ) & FLOATSIGNBITNOTSET( d2 ) ) { + return false; + } + return true; +} + +/* +============ +idWinding2D::RayIntersection +============ +*/ +bool idWinding2D::RayIntersection( const idVec2 &start, const idVec2 &dir, float &scale1, float &scale2, int *edgeNums ) const { + int i, numEdges, localEdgeNums[2]; + int sides[MAX_POINTS_ON_WINDING_2D+1], counts[3]; + float d1, d2, epsilon = 0.1f; + idVec3 plane, edges[2]; + + scale1 = scale2 = 0.0f; + counts[SIDE_FRONT] = counts[SIDE_BACK] = counts[SIDE_ON] = 0; + + plane = Plane2DFromVecs( start, dir ); + for ( i = 0; i < numPoints; i++ ) { + d1 = plane.x * p[i].x + plane.y * p[i].y + plane.z; + if ( d1 > epsilon ) { + sides[i] = SIDE_FRONT; + } + else if ( d1 < -epsilon ) { + sides[i] = SIDE_BACK; + } + else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + + if ( !counts[SIDE_FRONT] ) { + return false; + } + if ( !counts[SIDE_BACK] ) { + return false; + } + + numEdges = 0; + for ( i = 0; i < numPoints; i++ ) { + if ( sides[i] != sides[i+1] && sides[i+1] != SIDE_ON ) { + localEdgeNums[numEdges] = i; + edges[numEdges++] = Plane2DFromPoints( p[i], p[(i+1)%numPoints] ); + if ( numEdges >= 2 ) { + break; + } + } + } + if ( numEdges < 2 ) { + return false; + } + + d1 = edges[0].x * start.x + edges[0].y * start.y + edges[0].z; + d2 = - ( edges[0].x * dir.x + edges[0].y * dir.y ); + if ( d2 == 0.0f ) { + return false; + } + scale1 = d1 / d2; + d1 = edges[1].x * start.x + edges[1].y * start.y + edges[1].z; + d2 = - ( edges[1].x * dir.x + edges[1].y * dir.y ); + if ( d2 == 0.0f ) { + return false; + } + scale2 = d1 / d2; + + if ( idMath::Fabs( scale1 ) > idMath::Fabs( scale2 ) ) { + idSwap( scale1, scale2 ); + idSwap( localEdgeNums[0], localEdgeNums[1] ); + } + + if ( edgeNums ) { + edgeNums[0] = localEdgeNums[0]; + edgeNums[1] = localEdgeNums[1]; + } + return true; +} diff --git a/src/idlib/geometry/Winding2D.h b/src/idlib/geometry/Winding2D.h new file mode 100644 index 0000000..db3e278 --- /dev/null +++ b/src/idlib/geometry/Winding2D.h @@ -0,0 +1,165 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __WINDING2D_H__ +#define __WINDING2D_H__ + +/* +=============================================================================== + + A 2D winding is an arbitrary convex 2D polygon defined by an array of points. + +=============================================================================== +*/ + +#define MAX_POINTS_ON_WINDING_2D 16 + + +class idWinding2D { +public: + idWinding2D( void ); + + idWinding2D & operator=( const idWinding2D &winding ); + const idVec2 & operator[]( const int index ) const; + idVec2 & operator[]( const int index ); + + void Clear( void ); + void AddPoint( const idVec2 &point ); + int GetNumPoints( void ) const; + + void Expand( const float d ); + void ExpandForAxialBox( const idVec2 bounds[2] ); + + // splits the winding into a front and back winding, the winding itself stays unchanged + // returns a SIDE_? + int Split( const idVec3 &plane, const float epsilon, idWinding2D **front, idWinding2D **back ) const; + // cuts off the part at the back side of the plane, returns true if some part was at the front + // if there is nothing at the front the number of points is set to zero + bool ClipInPlace( const idVec3 &plane, const float epsilon = ON_EPSILON, const bool keepOn = false ); + + idWinding2D * Copy( void ) const; + idWinding2D * Reverse( void ) const; + + float GetArea( void ) const; + idVec2 GetCenter( void ) const; + float GetRadius( const idVec2 ¢er ) const; + void GetBounds( idVec2 bounds[2] ) const; + + bool IsTiny( void ) const; + bool IsHuge( void ) const; // base winding for a plane is typically huge + void Print( void ) const; + + float PlaneDistance( const idVec3 &plane ) const; + int PlaneSide( const idVec3 &plane, const float epsilon = ON_EPSILON ) const; + + bool PointInside( const idVec2 &point, const float epsilon ) const; + bool LineIntersection( const idVec2 &start, const idVec2 &end ) const; + bool RayIntersection( const idVec2 &start, const idVec2 &dir, float &scale1, float &scale2, int *edgeNums = NULL ) const; + + static idVec3 Plane2DFromPoints( const idVec2 &start, const idVec2 &end, const bool normalize = false ); + static idVec3 Plane2DFromVecs( const idVec2 &start, const idVec2 &dir, const bool normalize = false ); + static bool Plane2DIntersection( const idVec3 &plane1, const idVec3 &plane2, idVec2 &point ); + +private: + int numPoints; + idVec2 p[MAX_POINTS_ON_WINDING_2D]; +}; + +ID_INLINE idWinding2D::idWinding2D( void ) { + numPoints = 0; +} + +ID_INLINE idWinding2D &idWinding2D::operator=( const idWinding2D &winding ) { + int i; + + for ( i = 0; i < winding.numPoints; i++ ) { + p[i] = winding.p[i]; + } + numPoints = winding.numPoints; + return *this; +} + +ID_INLINE const idVec2 &idWinding2D::operator[]( const int index ) const { + return p[ index ]; +} + +ID_INLINE idVec2 &idWinding2D::operator[]( const int index ) { + return p[ index ]; +} + +ID_INLINE void idWinding2D::Clear( void ) { + numPoints = 0; +} + +ID_INLINE void idWinding2D::AddPoint( const idVec2 &point ) { + p[numPoints++] = point; +} + +ID_INLINE int idWinding2D::GetNumPoints( void ) const { + return numPoints; +} + +ID_INLINE idVec3 idWinding2D::Plane2DFromPoints( const idVec2 &start, const idVec2 &end, const bool normalize ) { + idVec3 plane; + plane.x = start.y - end.y; + plane.y = end.x - start.x; + if ( normalize ) { + plane.ToVec2().Normalize(); + } + plane.z = - ( start.x * plane.x + start.y * plane.y ); + return plane; +} + +ID_INLINE idVec3 idWinding2D::Plane2DFromVecs( const idVec2 &start, const idVec2 &dir, const bool normalize ) { + idVec3 plane; + plane.x = -dir.y; + plane.y = dir.x; + if ( normalize ) { + plane.ToVec2().Normalize(); + } + plane.z = - ( start.x * plane.x + start.y * plane.y ); + return plane; +} + +ID_INLINE bool idWinding2D::Plane2DIntersection( const idVec3 &plane1, const idVec3 &plane2, idVec2 &point ) { + float n00, n01, n11, det, invDet, f0, f1; + + n00 = plane1.x * plane1.x + plane1.y * plane1.y; + n01 = plane1.x * plane2.x + plane1.y * plane2.y; + n11 = plane2.x * plane2.x + plane2.y * plane2.y; + det = n00 * n11 - n01 * n01; + + if ( idMath::Fabs(det) < 1e-6f ) { + return false; + } + + invDet = 1.0f / det; + f0 = ( n01 * plane2.z - n11 * plane1.z ) * invDet; + f1 = ( n01 * plane1.z - n00 * plane2.z ) * invDet; + point.x = f0 * plane1.x + f1 * plane2.x; + point.y = f0 * plane1.y + f1 * plane2.y; + return true; +} + +#endif /* !__WINDING2D_H__ */ diff --git a/src/idlib/geometry/rvVertex.h b/src/idlib/geometry/rvVertex.h new file mode 100644 index 0000000..d1799b7 --- /dev/null +++ b/src/idlib/geometry/rvVertex.h @@ -0,0 +1,105 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// rvVertex.h - Describes some commonly used vertices (alternatives to the idDrawVert) +// Date: 1/10/05 +// Created by: Dwight Luetscher - Raven Software +// + +#ifndef __RV_VERTEX_H__ +#define __RV_VERTEX_H__ + +// +// rvBlend4DrawVert +// +// a vertex that is used to communicate data to drawing vertices stored in vertex buffers +// +class rvBlend4DrawVert { +public: + idVec3 xyz; + int blendIndex[4]; + float blendWeight[4]; // NOTE: the vertex stored in the actual buffer that is actually used for drawing may leave out the last weight (implied 1 - sum of other weights) + idVec3 normal; + idVec3 tangent; + idVec3 binormal; + byte color[4]; // diffuse color, [0] red, [1] green, [2] blue, [3] alpha + idVec2 st; +}; + +// +// rvSilTraceVertT +// +// a transformed vert that typically resides in system-memory and is used for operations +// like silhouette determination and trace testing +// +class rvSilTraceVertT { +public: + idVec4 xyzw; + + float operator[]( const int index ) const; + float & operator[]( const int index ); + + void Clear( void ); + + void Lerp( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ); + void LerpAll( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ); +}; + +#define SILTRACEVERT_SIZE_SHIFT 4 +#define SILTRACEVERT_SIZE (1 << SILTRACEVERT_SIZE_SHIFT) +#define SILTRACEVERT_XYZW_OFFSET 0 + +assert_sizeof( rvSilTraceVertT, SILTRACEVERT_SIZE ); +assert_sizeof( rvSilTraceVertT, (1<= 0 && index < 4 ); + return ((float *)(&xyzw))[index]; +} + +ID_INLINE float &rvSilTraceVertT::operator[]( const int index ) +{ + assert( index >= 0 && index < 4 ); + return ((float *)(&xyzw))[index]; +} + +ID_INLINE void rvSilTraceVertT::Clear( void ) +{ + xyzw.Zero(); +} + +ID_INLINE void rvSilTraceVertT::Lerp( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ) +{ + xyzw = a.xyzw + f * ( b.xyzw - a.xyzw ); +} + +ID_INLINE void rvSilTraceVertT::LerpAll( const rvSilTraceVertT &a, const rvSilTraceVertT &b, const float f ) +{ + xyzw = a.xyzw + f * ( b.xyzw - a.xyzw ); +} + +#endif // #ifndef __RV_VERTEX_H__ diff --git a/src/idlib/hashing/CRC16.cpp b/src/idlib/hashing/CRC16.cpp new file mode 100644 index 0000000..cad271e --- /dev/null +++ b/src/idlib/hashing/CRC16.cpp @@ -0,0 +1,141 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +/* + CRC-16 + + 16 bit, non-reflected CRC using the polynomial 0x1021 + and the initial and final xor values shown below. + in other words, the CCITT standard CRC-16 +*/ + +#define CRC16_INIT_VALUE 0xffff +#define CRC16_XOR_VALUE 0x0000 + +#ifdef CREATE_CRC_TABLE + +static unsigned short crctable[256]; + +/* + Generate a table for a byte-wise 16-bit CRC calculation on the polynomial: + x^16 + x^12 + x^5 + x^0 +*/ + +void make_crc_table( void ) { + int i, j; + unsigned long poly, c; + /* terms of polynomial defining this crc (except x^16): */ + static const byte p[] = {0,5,12}; + + /* make exclusive-or pattern from polynomial (0x1021) */ + poly = 0L; + for ( i = 0; i < sizeof( p ) / sizeof( byte ); i++ ) { + poly |= 1L << p[i]; + } + + for ( i = 0; i < 256; i++ ) { + c = i << 8; + for ( j = 0; j < 8; j++ ) { + c = ( c & 0x8000 ) ? poly ^ ( c << 1 ) : ( c << 1 ); + } + crctable[i] = (unsigned short) c; + } +} + +#else + +/* + Table of CRC-16's of all single-byte values (made by make_crc_table) +*/ +static unsigned short crctable[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +#endif + +void CRC16_InitChecksum( unsigned short &crcvalue ) { + crcvalue = CRC16_INIT_VALUE; +} + +void CRC16_Update( unsigned short &crcvalue, const byte data ) { + crcvalue = ( crcvalue << 8 ) ^ crctable[ ( crcvalue >> 8 ) ^ data ]; +} + +void CRC16_UpdateChecksum( unsigned short &crcvalue, const void *data, int length ) { + unsigned short crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = ( crc << 8 ) ^ crctable[ ( crc >> 8 ) ^ *buf++ ]; + } + crcvalue = crc; +} + +void CRC16_FinishChecksum( unsigned short &crcvalue ) { + crcvalue ^= CRC16_XOR_VALUE; +} + +unsigned short CRC16_BlockChecksum( const void *data, int length ) { + unsigned short crc; + + CRC16_InitChecksum( crc ); + CRC16_UpdateChecksum( crc, data, length ); + CRC16_FinishChecksum( crc ); + return crc; +} diff --git a/src/idlib/hashing/CRC16.h b/src/idlib/hashing/CRC16.h new file mode 100644 index 0000000..dfe714e --- /dev/null +++ b/src/idlib/hashing/CRC16.h @@ -0,0 +1,42 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CRC16_H__ +#define __CRC16_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the CCITT standard CRC-16. + +=============================================================================== +*/ + +void CRC16_InitChecksum( unsigned short &crcvalue ); +void CRC16_UpdateChecksum( unsigned short &crcvalue, const void *data, int length ); +void CRC16_FinishChecksum( unsigned short &crcvalue ); +unsigned short CRC16_BlockChecksum( const void *data, int length ); + +#endif /* !__CRC16_H__ */ diff --git a/src/idlib/hashing/CRC32.cpp b/src/idlib/hashing/CRC32.cpp new file mode 100644 index 0000000..008690e --- /dev/null +++ b/src/idlib/hashing/CRC32.cpp @@ -0,0 +1,190 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +/* + CRC-32 + Copyright (C) 1995-1998 Mark Adler +*/ + +#define CRC32_INIT_VALUE 0xffffffffL +#define CRC32_XOR_VALUE 0xffffffffL + +#ifdef CREATE_CRC_TABLE + +static unsigned long crctable[256]; + +/* + Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x^1+x^0), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+x^0), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all + the information needed to generate CRC's on data a byte at a time for all + combinations of CRC register values and incoming bytes. +*/ + +void make_crc_table( void ) { + int i, j; + unsigned long c, poly; + /* terms of polynomial defining this crc (except x^32): */ + static const byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* make exclusive-or pattern from polynomial (0xedb88320L) */ + poly = 0L; + for ( i = 0; i < sizeof( p ) / sizeof( byte ); i++ ) { + poly |= 1L << ( 31 - p[i] ); + } + + for ( i = 0; i < 256; i++ ) { + c = (unsigned long)i; + for ( j = 0; j < 8; j++ ) { + c = ( c & 1 ) ? poly ^ ( c >> 1 ) : ( c >> 1 ); + } + crctable[i] = c; + } +} + +#else + +/* + Table of CRC-32's of all single-byte values (made by make_crc_table) +*/ +static unsigned long crctable[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, + 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, + 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, + 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, + 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, + 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, + 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, + 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, + 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, + 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, + 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, + 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, + 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, + 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, + 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, + 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, + 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, + 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, + 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, + 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, + 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, + 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, + 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, + 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL +}; + +#endif + +void CRC32_InitChecksum( unsigned long &crcvalue ) { + crcvalue = CRC32_INIT_VALUE; +} + +void CRC32_Update( unsigned long &crcvalue, const byte data ) { + crcvalue = crctable[ ( crcvalue ^ data ) & 0xff ] ^ ( crcvalue >> 8 ); +} + +void CRC32_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ) { + unsigned long crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = crctable[ ( crc ^ ( *buf++ ) ) & 0xff ] ^ ( crc >> 8 ); + } + crcvalue = crc; +} + +void CRC32_FinishChecksum( unsigned long &crcvalue ) { + crcvalue ^= CRC32_XOR_VALUE; +} + +unsigned long CRC32_BlockChecksum( const void *data, int length ) { + unsigned long crc; + + CRC32_InitChecksum( crc ); + CRC32_UpdateChecksum( crc, data, length ); + CRC32_FinishChecksum( crc ); + return crc; +} diff --git a/src/idlib/hashing/CRC32.h b/src/idlib/hashing/CRC32.h new file mode 100644 index 0000000..c5750b6 --- /dev/null +++ b/src/idlib/hashing/CRC32.h @@ -0,0 +1,42 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CRC32_H__ +#define __CRC32_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the CRC-32. + +=============================================================================== +*/ + +void CRC32_InitChecksum( unsigned long &crcvalue ); +void CRC32_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ); +void CRC32_FinishChecksum( unsigned long &crcvalue ); +unsigned long CRC32_BlockChecksum( const void *data, int length ); + +#endif /* !__CRC32_H__ */ diff --git a/src/idlib/hashing/CRC8.cpp b/src/idlib/hashing/CRC8.cpp new file mode 100644 index 0000000..efab3c5 --- /dev/null +++ b/src/idlib/hashing/CRC8.cpp @@ -0,0 +1,137 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +/* + CRC-8 +*/ + +#define CRC8_INIT_VALUE 0x0000 +#define CRC8_XOR_VALUE 0x0000 + +#ifdef CREATE_CRC_TABLE + +static byte crctable[256]; + +/* + Generate a table for a byte-wise 8-bit CRC calculation on the polynomial: + x^8 + x^2 + x^1 + x^0 +*/ + +void make_crc_table( void ) { + int i, j; + unsigned long poly, c; + /* terms of polynomial defining this crc (except x^8): */ + static const byte p[] = {0,1,2}; + + /* make exclusive-or pattern from polynomial (0x07) */ + poly = 0L; + for ( i = 0; i < sizeof( p ) / sizeof( byte ); i++ ) { + poly |= 1L << p[i]; + } + + for ( i = 0; i < 256; i++ ) { + c = i; + for ( j = 0; j < 8; j++ ) { + c = ( c & 0x80 ) ? poly ^ ( c << 1 ) : ( c << 1 ); + } + crctable[i] = (byte) c; + } +} + +#else + +/* + Table of CRC-8's of all single-byte values (made by make_crc_table) +*/ +static byte crctable[256] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, + 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, + 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, + 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, + 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, + 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, + 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, + 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, + 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, + 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, + 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, + 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +#endif + +void CRC8_InitChecksum( unsigned char &crcvalue ) { + crcvalue = CRC8_INIT_VALUE; +} + +void CRC8_Update( unsigned char &crcvalue, const byte data ) { + crcvalue = crctable[crcvalue ^ data]; +} + +void CRC8_UpdateChecksum( unsigned char &crcvalue, const void *data, int length ) { + unsigned char crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = crctable[crc ^ *buf++]; + } + crcvalue = crc; +} + +void CRC8_FinishChecksum( unsigned char &crcvalue ) { + crcvalue ^= CRC8_XOR_VALUE; +} + +unsigned char CRC8_BlockChecksum( const void *data, int length ) { + unsigned char crc; + + CRC8_InitChecksum( crc ); + CRC8_UpdateChecksum( crc, data, length ); + CRC8_FinishChecksum( crc ); + return crc; +} diff --git a/src/idlib/hashing/CRC8.h b/src/idlib/hashing/CRC8.h new file mode 100644 index 0000000..b5c319a --- /dev/null +++ b/src/idlib/hashing/CRC8.h @@ -0,0 +1,42 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CRC8_H__ +#define __CRC8_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the CRC-8. + +=============================================================================== +*/ + +void CRC8_InitChecksum( unsigned char &crcvalue ); +void CRC8_UpdateChecksum( unsigned char &crcvalue, const void *data, int length ); +void CRC8_FinishChecksum( unsigned char &crcvalue ); +unsigned char CRC8_BlockChecksum( const void *data, int length ); + +#endif /* !__CRC8_H__ */ diff --git a/src/idlib/hashing/Honeyman.cpp b/src/idlib/hashing/Honeyman.cpp new file mode 100644 index 0000000..5dbeade --- /dev/null +++ b/src/idlib/hashing/Honeyman.cpp @@ -0,0 +1,139 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#define HONEYMAN_INIT_VALUE 0x00000000L +#define HONEYMAN_XOR_VALUE 0x00000000L + +#ifdef CREATE_CRC_TABLE + +static unsigned long crctable[256]; + +/* + Create the CRC table for the simplified version of the pathalias hashing function. + Thanks to Steve Belovin and Peter Honeyman + + This fast table calculation works only if POLY is a prime polynomial + in the field of integers modulo 2. Since the coefficients of a + 32-bit polynomial won't fit in a 32-bit word, the high-order bit is + implicit. IT MUST ALSO BE THE CASE that the coefficients of orders + 31 down to 25 are zero. Happily, we have candidates, from + E. J. Watson, "Primitive Polynomials (Mod 2)", Math. Comp. 16 (1962): + x^32 + x^7 + x^5 + x^3 + x^2 + x^1 + x^0 + x^31 + x^3 + x^0 + + We reverse the bits to get: + 111101010000000000000000000000001 but drop the last 1 + f 5 0 0 0 0 0 0 + 010010000000000000000000000000001 ditto, for 31-bit crc + 4 8 0 0 0 0 0 0 +*/ + +void make_crc_table( void ) { + + #define POLY 0x48000000L /* 31-bit polynomial (avoids sign problems) */ + + for ( int i = 0; i < 128; i++ ) { + int sum = 0; + for ( int j = 7 - 1; j >= 0; --j ) { + if ( i & ( 1 << j ) ) { + sum ^= POLY >> j; + } + } + crctable[i] = sum; + } +} + +#else + +static unsigned long crctable[256] = { + 0x00000000L, 0x48000000L, 0x24000000L, 0x6c000000L, + 0x12000000L, 0x5a000000L, 0x36000000L, 0x7e000000L, + 0x09000000L, 0x41000000L, 0x2d000000L, 0x65000000L, + 0x1b000000L, 0x53000000L, 0x3f000000L, 0x77000000L, + 0x04800000L, 0x4c800000L, 0x20800000L, 0x68800000L, + 0x16800000L, 0x5e800000L, 0x32800000L, 0x7a800000L, + 0x0d800000L, 0x45800000L, 0x29800000L, 0x61800000L, + 0x1f800000L, 0x57800000L, 0x3b800000L, 0x73800000L, + 0x02400000L, 0x4a400000L, 0x26400000L, 0x6e400000L, + 0x10400000L, 0x58400000L, 0x34400000L, 0x7c400000L, + 0x0b400000L, 0x43400000L, 0x2f400000L, 0x67400000L, + 0x19400000L, 0x51400000L, 0x3d400000L, 0x75400000L, + 0x06c00000L, 0x4ec00000L, 0x22c00000L, 0x6ac00000L, + 0x14c00000L, 0x5cc00000L, 0x30c00000L, 0x78c00000L, + 0x0fc00000L, 0x47c00000L, 0x2bc00000L, 0x63c00000L, + 0x1dc00000L, 0x55c00000L, 0x39c00000L, 0x71c00000L, + 0x01200000L, 0x49200000L, 0x25200000L, 0x6d200000L, + 0x13200000L, 0x5b200000L, 0x37200000L, 0x7f200000L, + 0x08200000L, 0x40200000L, 0x2c200000L, 0x64200000L, + 0x1a200000L, 0x52200000L, 0x3e200000L, 0x76200000L, + 0x05a00000L, 0x4da00000L, 0x21a00000L, 0x69a00000L, + 0x17a00000L, 0x5fa00000L, 0x33a00000L, 0x7ba00000L, + 0x0ca00000L, 0x44a00000L, 0x28a00000L, 0x60a00000L, + 0x1ea00000L, 0x56a00000L, 0x3aa00000L, 0x72a00000L, + 0x03600000L, 0x4b600000L, 0x27600000L, 0x6f600000L, + 0x11600000L, 0x59600000L, 0x35600000L, 0x7d600000L, + 0x0a600000L, 0x42600000L, 0x2e600000L, 0x66600000L, + 0x18600000L, 0x50600000L, 0x3c600000L, 0x74600000L, + 0x07e00000L, 0x4fe00000L, 0x23e00000L, 0x6be00000L, + 0x15e00000L, 0x5de00000L, 0x31e00000L, 0x79e00000L, + 0x0ee00000L, 0x46e00000L, 0x2ae00000L, 0x62e00000L, + 0x1ce00000L, 0x54e00000L, 0x38e00000L, 0x70e00000L +}; + +#endif + +void Honeyman_InitChecksum( unsigned long &crcvalue ) { + crcvalue = HONEYMAN_INIT_VALUE; +} + +void Honeyman_Update( unsigned long &crcvalue, const byte data ) { + crcvalue = ( ( crcvalue >> 7 ) ^ crctable[ ( crcvalue ^ data ) & 0x7f ] ); +} + +void Honeyman_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ) { + unsigned long crc; + const unsigned char *buf = (const unsigned char *) data; + + crc = crcvalue; + while( length-- ) { + crc = ( ( crc >> 7 ) ^ crctable[ ( crc ^ *buf++ ) & 0x7f ] ); + } + crcvalue = crc; +} + +void Honeyman_FinishChecksum( unsigned long &crcvalue ) { + crcvalue ^= HONEYMAN_XOR_VALUE; +} + +unsigned long Honeyman_BlockChecksum( const void *data, int length ) { + unsigned long crc; + + Honeyman_InitChecksum( crc ); + Honeyman_UpdateChecksum( crc, data, length ); + Honeyman_FinishChecksum( crc ); + return crc; +} diff --git a/src/idlib/hashing/Honeyman.h b/src/idlib/hashing/Honeyman.h new file mode 100644 index 0000000..56df0e8 --- /dev/null +++ b/src/idlib/hashing/Honeyman.h @@ -0,0 +1,43 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __HONEYMAN_H__ +#define __HONEYMAN_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the simplified version of the pathalias hashing + function by Steve Belovin and Peter Honeyman. + +=============================================================================== +*/ + +void Honeyman_InitChecksum( unsigned long &crcvalue ); +void Honeyman_UpdateChecksum( unsigned long &crcvalue, const void *data, int length ); +void Honeyman_FinishChecksum( unsigned long &crcvalue ); +unsigned long Honeyman_BlockChecksum( const void *data, int length ); + +#endif /* !__HONEYMAN_H__ */ diff --git a/src/idlib/hashing/MD4.cpp b/src/idlib/hashing/MD4.cpp new file mode 100644 index 0000000..ece52a1 --- /dev/null +++ b/src/idlib/hashing/MD4.cpp @@ -0,0 +1,282 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +/* + RSA Data Security, Inc., MD4 message-digest algorithm. (RFC1320) +*/ + +/* + +Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD4 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD4 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode( unsigned char *output, UINT4 *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode( UINT4 *output, const unsigned char *input, unsigned int len ) { + unsigned int i, j; + + for ( i = 0, j = 0; j < len; i++, j += 4 ) { + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); + } +} + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4_Transform( UINT4 state[4], const unsigned char block[64] ) { + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11); /* 1 */ + FF (d, a, b, c, x[ 1], S12); /* 2 */ + FF (c, d, a, b, x[ 2], S13); /* 3 */ + FF (b, c, d, a, x[ 3], S14); /* 4 */ + FF (a, b, c, d, x[ 4], S11); /* 5 */ + FF (d, a, b, c, x[ 5], S12); /* 6 */ + FF (c, d, a, b, x[ 6], S13); /* 7 */ + FF (b, c, d, a, x[ 7], S14); /* 8 */ + FF (a, b, c, d, x[ 8], S11); /* 9 */ + FF (d, a, b, c, x[ 9], S12); /* 10 */ + FF (c, d, a, b, x[10], S13); /* 11 */ + FF (b, c, d, a, x[11], S14); /* 12 */ + FF (a, b, c, d, x[12], S11); /* 13 */ + FF (d, a, b, c, x[13], S12); /* 14 */ + FF (c, d, a, b, x[14], S13); /* 15 */ + FF (b, c, d, a, x[15], S14); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 0], S21); /* 17 */ + GG (d, a, b, c, x[ 4], S22); /* 18 */ + GG (c, d, a, b, x[ 8], S23); /* 19 */ + GG (b, c, d, a, x[12], S24); /* 20 */ + GG (a, b, c, d, x[ 1], S21); /* 21 */ + GG (d, a, b, c, x[ 5], S22); /* 22 */ + GG (c, d, a, b, x[ 9], S23); /* 23 */ + GG (b, c, d, a, x[13], S24); /* 24 */ + GG (a, b, c, d, x[ 2], S21); /* 25 */ + GG (d, a, b, c, x[ 6], S22); /* 26 */ + GG (c, d, a, b, x[10], S23); /* 27 */ + GG (b, c, d, a, x[14], S24); /* 28 */ + GG (a, b, c, d, x[ 3], S21); /* 29 */ + GG (d, a, b, c, x[ 7], S22); /* 30 */ + GG (c, d, a, b, x[11], S23); /* 31 */ + GG (b, c, d, a, x[15], S24); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 0], S31); /* 33 */ + HH (d, a, b, c, x[ 8], S32); /* 34 */ + HH (c, d, a, b, x[ 4], S33); /* 35 */ + HH (b, c, d, a, x[12], S34); /* 36 */ + HH (a, b, c, d, x[ 2], S31); /* 37 */ + HH (d, a, b, c, x[10], S32); /* 38 */ + HH (c, d, a, b, x[ 6], S33); /* 39 */ + HH (b, c, d, a, x[14], S34); /* 40 */ + HH (a, b, c, d, x[ 1], S31); /* 41 */ + HH (d, a, b, c, x[ 9], S32); /* 42 */ + HH (c, d, a, b, x[ 5], S33); /* 43 */ + HH (b, c, d, a, x[13], S34); /* 44 */ + HH (a, b, c, d, x[ 3], S31); /* 45 */ + HH (d, a, b, c, x[11], S32); /* 46 */ + HH (c, d, a, b, x[ 7], S33); /* 47 */ + HH (b, c, d, a, x[15], S34); /* 48 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information.*/ + memset ((POINTER)x, 0, sizeof (x)); +} + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4_Init( MD4_CTX *context ) { + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants.*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4_Update( MD4_CTX *context, const unsigned char *input, unsigned int inputLen ) { + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) { + context->count[1]++; + } + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if ( inputLen >= partLen ) { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4_Transform (context->state, context->buffer); + + for ( i = partLen; i + 63 < inputLen; i += 64 ) { + MD4_Transform (context->state, &input[i]); + } + + index = 0; + } else { + i = 0; + } + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the message digest and zeroizing the context. */ +void MD4_Final( MD4_CTX *context, unsigned char digest[16] ) { + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode( bits, context->count, 8 ); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4_Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4_Update( context, bits, 8 ); + + /* Store state in digest */ + Encode( digest, context->state, 16 ); + + /* Zeroize sensitive information.*/ + memset ((POINTER)context, 0, sizeof (*context)); +} + +/* +=============== +MD4_BlockChecksum +=============== +*/ +unsigned long MD4_BlockChecksum( const void *data, int length ) { + unsigned long digest[4]; + unsigned long val; + MD4_CTX ctx; + + MD4_Init( &ctx ); + MD4_Update( &ctx, (unsigned char *)data, length ); + MD4_Final( &ctx, (unsigned char *)digest ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/src/idlib/hashing/MD4.h b/src/idlib/hashing/MD4.h new file mode 100644 index 0000000..d0956be --- /dev/null +++ b/src/idlib/hashing/MD4.h @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MD4_H__ +#define __MD4_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the MD4 message-digest algorithm. + +=============================================================================== +*/ + +unsigned long MD4_BlockChecksum( const void *data, int length ); + +#endif /* !__MD4_H__ */ diff --git a/src/idlib/hashing/MD5.cpp b/src/idlib/hashing/MD5.cpp new file mode 100644 index 0000000..4db3d2b --- /dev/null +++ b/src/idlib/hashing/MD5.cpp @@ -0,0 +1,295 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +/* + MD5 Message Digest Algorithm. (RFC1321) +*/ + +/* + +This code implements the MD5 message-digest algorithm. +The algorithm is due to Ron Rivest. This code was +written by Colin Plumb in 1993, no copyright is claimed. +This code is in the public domain; do with it what you wish. + +Equivalent code is available from RSA Data Security, Inc. +This code has been tested against that, and is equivalent, +except that you don't need to include two pages of legalese +with every copy. + +To compute the message digest of a chunk of bytes, declare an +MD5Context structure, pass it to MD5Init, call MD5Update as +needed on buffers full of bytes, and then call MD5Final, which +will fill a supplied 16-byte array with the digest. + +*/ + +/* MD5 context. */ +typedef struct +{ + unsigned int state[4]; + unsigned int bits[2]; + unsigned char in[64]; +} MD5_CTX; + +/* The four core functions - F1 is optimized somewhat */ +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* +================= +MD5_Transform + +The core of the MD5 algorithm, this alters an existing MD5 hash to +reflect the addition of 16 longwords of new data. MD5Update blocks +the data and converts bytes into longwords for this routine. +================= +*/ +void MD5_Transform( unsigned int state[4], unsigned int const in[16] ) { + register unsigned int a, b, c, d; + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + + LittleRevBytes( const_cast< unsigned int * >( in ), sizeof(unsigned int), 16 ); + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + LittleRevBytes( const_cast< unsigned int * >( in ), sizeof(unsigned int), 16 ); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; +} + +/* +================== +MD5_Init + +MD5 initialization. Begins an MD5 operation, writing a new context. +================== +*/ +void MD5_Init( MD5_CTX *ctx ) { + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xefcdab89; + ctx->state[2] = 0x98badcfe; + ctx->state[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* +=================== +MD5_Update + +MD5 block update operation. Continues an MD5 message-digest operation, +processing another message block, and updating the context. +=================== +*/ +void MD5_Update( MD5_CTX *ctx, unsigned char const *buf, unsigned int len ) { + unsigned int t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ( ( ctx->bits[0] = t + ( (unsigned int) len << 3 ) ) < t ) { + ctx->bits[1]++; /* Carry from low to high */ + } + ctx->bits[1] += len >> 29; + + t = ( t >> 3 ) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if ( len < t ) { + memcpy( p, buf, len ); + return; + } + memcpy( p, buf, t ); + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while( len >= 64 ) { + memcpy( ctx->in, buf, 64 ); + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy( ctx->in, buf, len ); +} + +/* +=============== +MD5_Final + +MD5 finalization. Ends an MD5 message-digest operation, +writing the message digest and zeroizing the context. +=============== +*/ +void MD5_Final( MD5_CTX *ctx, unsigned char digest[16] ) { + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = ( ctx->bits[0] >> 3 ) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if ( count < 8 ) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset( p, 0, count ); + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + + /* Now fill the next block with 56 bytes */ + memset( ctx->in, 0, 56 ); + } else { + /* Pad block to 56 bytes */ + memset( p, 0, count - 8 ); + } + + /* Append length in bits and transform */ + unsigned int val0 = ctx->bits[0]; + unsigned int val1 = ctx->bits[1]; + + ((unsigned int *) ctx->in)[14] = LittleLong( val0 ); + ((unsigned int *) ctx->in)[15] = LittleLong( val1 ); + + MD5_Transform( ctx->state, (unsigned int *) ctx->in ); + memcpy( digest, ctx->state, 16 ); + memset( ctx, 0, sizeof( ctx ) ); /* In case it's sensitive */ +} + +/* +=============== +MD5_BlockChecksum +=============== +*/ +unsigned long MD5_BlockChecksum( const void *data, int length ) { + unsigned long digest[4]; + unsigned long val; + MD5_CTX ctx; + + MD5_Init( &ctx ); + MD5_Update( &ctx, (unsigned char *)data, length ); + MD5_Final( &ctx, (unsigned char *)digest ); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/src/idlib/hashing/MD5.h b/src/idlib/hashing/MD5.h new file mode 100644 index 0000000..7131bef --- /dev/null +++ b/src/idlib/hashing/MD5.h @@ -0,0 +1,39 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MD5_H__ +#define __MD5_H__ + +/* +=============================================================================== + + Calculates a checksum for a block of data + using the MD5 message-digest algorithm. + +=============================================================================== +*/ + +unsigned long MD5_BlockChecksum( const void *data, int length ); + +#endif /* !__MD5_H__ */ diff --git a/src/idlib/mapfile.cpp b/src/idlib/mapfile.cpp new file mode 100644 index 0000000..1ab092f --- /dev/null +++ b/src/idlib/mapfile.cpp @@ -0,0 +1,1389 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "precompiled.h" +#pragma hdrstop + +#ifdef _XENON +#define PACIFIER_UPDATE session->PacifierUpdate() +#else +#define PACIFIER_UPDATE +#endif + + +/* +=============== +FloatCRC +=============== +*/ +ID_INLINE unsigned int FloatCRC( float f ) { + return *(unsigned int *)&f; +} + +/* +=============== +StringCRC +=============== +*/ +ID_INLINE unsigned int StringCRC( const char *str ) { + unsigned int i, crc; + const unsigned char *ptr; + + crc = 0; + ptr = reinterpret_cast(str); + for ( i = 0; str[i]; i++ ) { + crc ^= str[i] << (i & 3); + } + return crc; +} + +/* +================= +ComputeAxisBase + +WARNING : special case behaviour of atan2(y,x) <-> atan(y/x) might not be the same everywhere when x == 0 +rotation by (0,RotY,RotZ) assigns X to normal +================= +*/ +static void ComputeAxisBase( const idVec3 &normal, idVec3 &texS, idVec3 &texT ) { + float RotY, RotZ; + idVec3 n; + + // do some cleaning + n[0] = ( idMath::Fabs( normal[0] ) < 1e-6f ) ? 0.0f : normal[0]; + n[1] = ( idMath::Fabs( normal[1] ) < 1e-6f ) ? 0.0f : normal[1]; + n[2] = ( idMath::Fabs( normal[2] ) < 1e-6f ) ? 0.0f : normal[2]; + + RotY = -idMath::ATan( n[2], idMath::Sqrt( n[1] * n[1] + n[0] * n[0]) ); + RotZ = idMath::ATan( n[1], n[0] ); + // rotate (0,1,0) and (0,0,1) to compute texS and texT +// RAVEN BEGIN +// jscott: routed thru idMath + texS[0] = -idMath::Sin( RotZ ); + texS[1] = idMath::Cos( RotZ ); + texS[2] = 0; + // the texT vector is along -Z ( T texture coorinates axis ) + texT[0] = -idMath::Sin( RotY ) * idMath::Cos( RotZ ); + texT[1] = -idMath::Sin( RotY ) * idMath::Sin( RotZ ); + texT[2] = -idMath::Cos( RotY ); +// RAVEN END +} + +// RAVEN BEGIN +// rhummer: Trying to debug why func_groups disappear when editing a map when they shouldn't. +idMapFile::~idMapFile( void ) +{ + entities.DeleteContents( true ); + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2done with .map file ^0%s\n", name.c_str() ); + } +} +// RAVEN END + +/* +================= +idMapBrushSide::GetTextureVectors +================= +*/ +void idMapBrushSide::GetTextureVectors( idVec4 v[2] ) const { + int i; + idVec3 texX, texY; + + ComputeAxisBase( plane.Normal(), texX, texY ); + for ( i = 0; i < 2; i++ ) { + v[i][0] = texX[0] * texMat[i][0] + texY[0] * texMat[i][1]; + v[i][1] = texX[1] * texMat[i][0] + texY[1] * texMat[i][1]; + v[i][2] = texX[2] * texMat[i][0] + texY[2] * texMat[i][1]; + v[i][3] = texMat[i][2] + ( origin * v[i].ToVec3() ); + } +} + +/* +================= +idMapPatch::Parse +================= +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapPatch *idMapPatch::Parse( Lexer &src, const idVec3 &origin, bool patchDef3, int version ) { +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + float info[7]; + idDrawVert *vert; + idToken token; + int i, j; + + if ( !src.ExpectTokenString( "{" ) ) { + return NULL; + } + + // read the material (we had an implicit 'textures/' in the old format...) + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapPatch::Parse: unexpected EOF" ); + return NULL; + } + + // Parse it + if (patchDef3) { + if ( !src.Parse1DMatrix( 7, info ) ) { + src.Error( "idMapPatch::Parse: unable to Parse patchDef3 info" ); + return NULL; + } + } else { + if ( !src.Parse1DMatrix( 5, info ) ) { + src.Error( "idMapPatch::Parse: unable to parse patchDef2 info" ); + return NULL; + } + } + + idMapPatch *patch = new idMapPatch( info[0], info[1] ); + patch->SetSize( info[0], info[1] ); + if ( version < 2 ) { + patch->SetMaterial( "textures/" + token ); + } else { + patch->SetMaterial( token ); + } + + if ( patchDef3 ) { + patch->SetHorzSubdivisions( info[2] ); + patch->SetVertSubdivisions( info[3] ); + patch->SetExplicitlySubdivided( true ); + } + + if ( patch->GetWidth() < 0 || patch->GetHeight() < 0 ) { + src.Error( "idMapPatch::Parse: bad size" ); + delete patch; + return NULL; + } + + // these were written out in the wrong order, IMHO + if ( !src.ExpectTokenString( "(" ) ) { + src.Error( "idMapPatch::Parse: bad patch vertex data" ); + delete patch; + return NULL; + } + for ( j = 0; j < patch->GetWidth(); j++ ) { + if ( !src.ExpectTokenString( "(" ) ) { + src.Error( "idMapPatch::Parse: bad vertex row data" ); + delete patch; + return NULL; + } + for ( i = 0; i < patch->GetHeight(); i++ ) { + float v[5]; + + if ( !src.Parse1DMatrix( 5, v ) ) { + src.Error( "idMapPatch::Parse: bad vertex column data" ); + delete patch; + return NULL; + } + + vert = &((*patch)[i * patch->GetWidth() + j]); + vert->xyz[0] = v[0] - origin[0]; + vert->xyz[1] = v[1] - origin[1]; + vert->xyz[2] = v[2] - origin[2]; + vert->st[0] = v[3]; + vert->st[1] = v[4]; + } + if ( !src.ExpectTokenString( ")" ) ) { + delete patch; + src.Error( "idMapPatch::Parse: unable to parse patch control points" ); + return NULL; + } + } + if ( !src.ExpectTokenString( ")" ) || + !src.ExpectTokenString( "}" ) || + !src.ExpectTokenString( "}" ) ) { + src.Error( "idMapPatch::Parse: unable to parse patch control points, no closure" ); + delete patch; + return NULL; + } + + return patch; +} + +/* +============ +idMapPatch::Write +============ +*/ +bool idMapPatch::Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const { + int i, j; + const idDrawVert *v; + + if ( GetExplicitlySubdivided() ) { + fp->WriteFloatString( "// primitive %d\n{\n patchDef3\n {\n", primitiveNum ); + fp->WriteFloatString( " \"%s\"\n ( %d %d %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight(), GetHorzSubdivisions(), GetVertSubdivisions()); + } else { + fp->WriteFloatString( "// primitive %d\n{\n patchDef2\n {\n", primitiveNum ); + fp->WriteFloatString( " \"%s\"\n ( %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight()); + } + + fp->WriteFloatString( " (\n" ); + for ( i = 0; i < GetWidth(); i++ ) { + fp->WriteFloatString( " ( " ); + for ( j = 0; j < GetHeight(); j++ ) { + v = &verts[ j * GetWidth() + i ]; + fp->WriteFloatString( " ( %f %f %f %f %f )", v->xyz[0] + origin[0], + v->xyz[1] + origin[1], v->xyz[2] + origin[2], v->st[0], v->st[1] ); + } + fp->WriteFloatString( " )\n" ); + } + fp->WriteFloatString( " )\n }\n}\n" ); + + return true; +} + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data +/* +=============== +idMapPatch::AdjustOrigin +=============== +*/ +void idMapPatch::AdjustOrigin( idVec3 &delta ) { + TranslateSelf( delta ); +} +// RAVEN END + +/* +=============== +idMapPatch::GetGeometryCRC +=============== +*/ +unsigned int idMapPatch::GetGeometryCRC( void ) const { + int i, j; + unsigned int crc; + + crc = GetHorzSubdivisions() ^ GetVertSubdivisions(); + for ( i = 0; i < GetWidth(); i++ ) { + for ( j = 0; j < GetHeight(); j++ ) { + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.x ); + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.y ); + crc ^= FloatCRC( verts[j * GetWidth() + i].xyz.z ); + } + } + + crc ^= StringCRC( GetMaterial() ); + + return crc; +} + +/* +================= +idMapBrush::Parse +================= +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapBrush *idMapBrush::Parse( Lexer &src, const idVec3 &origin, bool newFormat, int version ) { +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idVec3 planepts[3]; + idToken token; + idList sides; + idMapBrushSide *side; + idDict epairs; + + if ( !src.ExpectTokenString( "{" ) ) { + return NULL; + } + + do { + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapBrush::Parse: unexpected EOF" ); + sides.DeleteContents( true ); + return NULL; + } + if ( token == "}" ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is a brace + if ( token == "(" ) { + break; + } + // the token should be a key string for a key/value pair + if ( token.type != TT_STRING ) { + src.Error( "idMapBrush::Parse: unexpected %s, expected ( or epair key string", token.c_str() ); + sides.DeleteContents( true ); + return NULL; + } + + idStr key = token; + + if ( !src.ReadTokenOnLine( &token ) || token.type != TT_STRING ) { + src.Error( "idMapBrush::Parse: expected epair value string not found" ); + sides.DeleteContents( true ); + return NULL; + } + + epairs.Set( key, token ); + + // try to read the next key + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapBrush::Parse: unexpected EOF" ); + sides.DeleteContents( true ); + return NULL; + } + } while (1); + + src.UnreadToken( &token ); + + side = new idMapBrushSide(); + sides.Append(side); + + if ( newFormat ) { + if ( !src.Parse1DMatrix( 4, side->plane.ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + } else { + // read the three point plane definition + if (!src.Parse1DMatrix( 3, planepts[0].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[1].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[2].ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + + planepts[0] -= origin; + planepts[1] -= origin; + planepts[2] -= origin; + + side->plane.FromPoints( planepts[0], planepts[1], planepts[2] ); + } + + // read the texture matrix + // this is odd, because the texmat is 2D relative to default planar texture axis + if ( !src.Parse2DMatrix( 2, 3, side->texMat[0].ToFloatPtr() ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side texture matrix" ); + sides.DeleteContents( true ); + return NULL; + } + side->origin = origin; + + // read the material + if ( !src.ReadTokenOnLine( &token ) ) { + src.Error( "idMapBrush::Parse: unable to read brush side material" ); + sides.DeleteContents( true ); + return NULL; + } + + // we had an implicit 'textures/' in the old format... + if ( version < 2 ) { + side->material = "textures/" + token; + } else { + side->material = token; + } + +// RAVEN BEGIN +// jscott: make sure the material is properly parsed + declManager->FindMaterial( token ); +// RAVEN END + +// RAVEN BEGIN +// jscott: removed these in later versions + if( version < 3 ) { +// RAVEN END + // Q2 allowed override of default flags and values, but we don't any more + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + } + } + } + } + } while( 1 ); + + if ( !src.ExpectTokenString( "}" ) ) { + sides.DeleteContents( true ); + return NULL; + } + + idMapBrush *brush = new idMapBrush(); + for ( i = 0; i < sides.Num(); i++ ) { + brush->AddSide( sides[i] ); + } + + brush->epairs = epairs; + + return brush; +} + +/* +================= +idMapBrush::ParseQ3 +================= +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapBrush *idMapBrush::ParseQ3( Lexer &src, const idVec3 &origin ) { +// RAVEN END + int i, shift[2], rotate; + float scale[2]; + idVec3 planepts[3]; + idToken token; + idList sides; + idMapBrushSide *side; + idDict epairs; + + do { + if ( src.CheckTokenString( "}" ) ) { + break; + } + + side = new idMapBrushSide(); + sides.Append( side ); + + // read the three point plane definition + if (!src.Parse1DMatrix( 3, planepts[0].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[1].ToFloatPtr() ) || + !src.Parse1DMatrix( 3, planepts[2].ToFloatPtr() ) ) { + src.Error( "idMapBrush::ParseQ3: unable to read brush side plane definition" ); + sides.DeleteContents( true ); + return NULL; + } + + planepts[0] -= origin; + planepts[1] -= origin; + planepts[2] -= origin; + + side->plane.FromPoints( planepts[0], planepts[1], planepts[2] ); + + // read the material + if ( !src.ReadTokenOnLine( &token ) ) { + src.Error( "idMapBrush::ParseQ3: unable to read brush side material" ); + sides.DeleteContents( true ); + return NULL; + } + + // we have an implicit 'textures/' in the old format + side->material = "textures/" + token; + + // read the texture shift, rotate and scale + shift[0] = src.ParseInt(); + shift[1] = src.ParseInt(); + rotate = src.ParseInt(); + scale[0] = src.ParseFloat(); + scale[1] = src.ParseFloat(); + side->texMat[0] = idVec3( 0.03125f, 0.0f, 0.0f ); + side->texMat[1] = idVec3( 0.0f, 0.03125f, 0.0f ); + side->origin = origin; + + // Q2 allowed override of default flags and values, but we don't any more + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + } + } + } + } while( 1 ); + + idMapBrush *brush = new idMapBrush(); + for ( i = 0; i < sides.Num(); i++ ) { + brush->AddSide( sides[i] ); + } + + brush->epairs = epairs; + + return brush; +} + +/* +============ +idMapBrush::Write +============ +*/ +bool idMapBrush::Write( idFile *fp, int primitiveNum, const idVec3 &origin ) const { + int i; + idMapBrushSide *side; + + fp->WriteFloatString( "// primitive %d\n{\n brushDef3\n {\n", primitiveNum ); + + // write brush epairs + for ( i = 0; i < epairs.GetNumKeyVals(); i++) { + fp->WriteFloatString( " \"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); + } + + // write brush sides + for ( i = 0; i < GetNumSides(); i++ ) { + side = GetSide( i ); + fp->WriteFloatString( " ( %f %f %f %f ) ", side->plane[0], side->plane[1], side->plane[2], side->plane[3] ); +// RAVEN BEGIN + fp->WriteFloatString( "( ( %f %f %f ) ( %f %f %f ) ) \"%s\"\n", +// RAVEN END + side->texMat[0][0], side->texMat[0][1], side->texMat[0][2], + side->texMat[1][0], side->texMat[1][1], side->texMat[1][2], + side->material.c_str() ); + } + + fp->WriteFloatString( " }\n}\n" ); + + return true; +} + +/* +=============== +idMapBrush::GetGeometryCRC +=============== +*/ +unsigned int idMapBrush::GetGeometryCRC( void ) const { + int i, j; + idMapBrushSide *mapSide; + unsigned int crc; + + crc = 0; + for ( i = 0; i < GetNumSides(); i++ ) { + mapSide = GetSide(i); + for ( j = 0; j < 4; j++ ) { + crc ^= FloatCRC( mapSide->GetPlane()[j] ); + } + crc ^= StringCRC( mapSide->GetMaterial() ); + } + + return crc; +} + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + +// This is taken from tools/radiant/EditorBrushPrimit.cpp +float SarrusDet(idVec3 a, idVec3 b, idVec3 c) { + return (float)a[0] * (float)b[1] * (float)c[2] + (float)b[0] * (float)c[1] * (float)a[2] + (float)c[0] * (float)a[1] * (float)b[2] - (float)c[0] * (float)b[1] * (float)a[2] - (float)a[1] * (float)b[0] * (float)c[2] - (float)a[0] * (float)b[2] * (float)c[1]; +} + +/* +=============== +idMapBrush::AdjustOrigin +=============== +*/ +void idMapBrush::AdjustOrigin( idVec3 &delta ) { + int i; + idMapBrushSide *mapSide; + + for ( i = 0; i < GetNumSides(); i++ ) { + mapSide = GetSide(i); + + mapSide->SetPlane( mapSide->GetPlane().Translate( delta ) ); + + // This is taken from Face_MoveTexture_BrushPrimit() in tools/radiant/EditorBrushPrimit.cpp + idVec3 texS, texT; + float tx, ty; + idVec3 M[3]; // columns of the matrix .. easier that way + float det; + idVec3 D[2]; + + // compute plane axis base ( doesn't change with translation ) + ComputeAxisBase( mapSide->GetPlane().Normal(), texS, texT); + + // compute translation vector in plane axis base + tx = DotProduct(delta, texS); + ty = DotProduct(delta, texT); + + // fill the data vectors + M[0][0] = tx; + M[0][1] = 1.0f + tx; + M[0][2] = tx; + M[1][0] = ty; + M[1][1] = ty; + M[1][2] = 1.0f + ty; + M[2][0] = 1.0f; + M[2][1] = 1.0f; + M[2][2] = 1.0f; + + idVec3 tm[2]; + mapSide->GetTextureMatrix( tm[0], tm[1] ); + + D[0][0] = tm[0][2]; + D[0][1] = tm[0][0] + tm[0][2]; + D[0][2] = tm[0][1] + tm[0][2]; + D[1][0] = tm[1][2]; + D[1][1] = tm[1][0] + tm[1][2]; + D[1][2] = tm[1][1] + tm[1][2]; + + // solve + det = SarrusDet(M[0], M[1], M[2]); + if ( det != 0. ) { + tm[0][0] = SarrusDet(D[0], M[1], M[2]) / det; + tm[0][1] = SarrusDet(M[0], D[0], M[2]) / det; + tm[0][2] = SarrusDet(M[0], M[1], D[0]) / det; + tm[1][0] = SarrusDet(D[1], M[1], M[2]) / det; + tm[1][1] = SarrusDet(M[0], D[1], M[2]) / det; + tm[1][2] = SarrusDet(M[0], M[1], D[1]) / det; + mapSide->SetTextureMatrix(tm); + } + } +} +// RAVEN END + +/* +================ +idMapEntity::Parse +================ +*/ +// RAVEN BEGIN +// jsinger: changed to be Lexer instead of idLexer so that we have the ability to read binary files +idMapEntity *idMapEntity::Parse( Lexer &src, bool worldSpawn, int version ) { +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + idToken token; + idMapEntity *mapEnt; + idMapPatch *mapPatch; + idMapBrush *mapBrush; + bool worldent; + idVec3 origin; + double v1, v2, v3; + + if ( !src.ReadToken(&token) ) { + return NULL; + } + + if ( token != "{" ) { + src.Error( "idMapEntity::Parse: { not found, found %s", token.c_str() ); + return NULL; + } + + mapEnt = new idMapEntity(); + + if ( worldSpawn ) { + mapEnt->primitives.Resize( 1024, 256 ); + } + + origin.Zero(); + worldent = false; + do { + if ( !src.ReadToken(&token) ) { + src.Error( "idMapEntity::Parse: EOF without closing brace" ); + return NULL; + } + if ( token == "}" ) { + break; + } + + if ( token == "{" ) { + // parse a brush or patch + if ( !src.ReadToken( &token ) ) { + src.Error( "idMapEntity::Parse: unexpected EOF" ); + return NULL; + } + + if ( worldent ) { + origin.Zero(); + } + + // if is it a brush: brush, brushDef, brushDef2, brushDef3 + if ( token.Icmpn( "brush", 5 ) == 0 ) { + mapBrush = idMapBrush::Parse( src, origin, ( !token.Icmp( "brushDef2" ) || !token.Icmp( "brushDef3" ) ), version ); + if ( !mapBrush ) { + return NULL; + } + mapEnt->AddPrimitive( mapBrush ); + } + // if is it a patch: patchDef2, patchDef3 + else if ( token.Icmpn( "patch", 5 ) == 0 ) { + mapPatch = idMapPatch::Parse( src, origin, !token.Icmp( "patchDef3" ), version ); + if ( !mapPatch ) { + return NULL; + } + mapEnt->AddPrimitive( mapPatch ); + } + // assume it's a brush in Q3 or older style + else { + src.UnreadToken( &token ); + mapBrush = idMapBrush::ParseQ3( src, origin ); + if ( !mapBrush ) { + return NULL; + } + mapEnt->AddPrimitive( mapBrush ); + } + } else { + idStr key, value; + + // parse a key / value pair + key = token; + src.ReadTokenOnLine( &token ); + value = token; + + // strip trailing spaces that sometimes get accidentally + // added in the editor + value.StripTrailingWhitespace(); + key.StripTrailingWhitespace(); + + mapEnt->epairs.Set( key, value ); + + if ( !idStr::Icmp( key, "origin" ) ) { + // scanf into doubles, then assign, so it is idVec size independent + v1 = v2 = v3 = 0; + sscanf( value, "%lf %lf %lf", &v1, &v2, &v3 ); + origin.x = v1; + origin.y = v2; + origin.z = v3; + } + else if ( !idStr::Icmp( key, "classname" ) && !idStr::Icmp( value, "worldspawn" ) ) { + worldent = true; + } + } + } while( 1 ); + + return mapEnt; +} + +/* +============ +idMapEntity::Write +============ +*/ +bool idMapEntity::Write( idFile *fp, int entityNum ) const { + int i; + idMapPrimitive *mapPrim; + idVec3 origin; + + fp->WriteFloatString( "// entity %d\n{\n", entityNum ); + + // write entity epairs + for ( i = 0; i < epairs.GetNumKeyVals(); i++) { + fp->WriteFloatString( "\"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); + } + + epairs.GetVector( "origin", "0 0 0", origin ); + + // write pritimives + for ( i = 0; i < GetNumPrimitives(); i++ ) { + mapPrim = GetPrimitive( i ); + + switch( mapPrim->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: + static_cast(mapPrim)->Write( fp, i, origin ); + break; + case idMapPrimitive::TYPE_PATCH: + static_cast(mapPrim)->Write( fp, i, origin ); + break; + } + } + + fp->WriteFloatString( "}\n" ); + + return true; +} + +/* +=============== +idMapEntity::RemovePrimitiveData +=============== +*/ +void idMapEntity::RemovePrimitiveData() { + primitives.DeleteContents(true); +} + +/* +=============== +idMapEntity::GetGeometryCRC +=============== +*/ +unsigned int idMapEntity::GetGeometryCRC( void ) const { + int i; + unsigned int crc; + idMapPrimitive *mapPrim; + + crc = 0; + for ( i = 0; i < GetNumPrimitives(); i++ ) { + mapPrim = GetPrimitive( i ); + + switch( mapPrim->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: + crc ^= static_cast(mapPrim)->GetGeometryCRC(); + if ( epairs.GetString( "model" ) ) { + crc ^= StringCRC( epairs.GetString( "model" ) ); + } + break; + case idMapPrimitive::TYPE_PATCH: + crc ^= static_cast(mapPrim)->GetGeometryCRC(); + if ( epairs.GetString( "model" ) ) { + crc ^= StringCRC( epairs.GetString( "model" ) ); + } + break; + } + } + + return crc; +} + +/* +=============== +idMapFile::Parse +=============== +*/ +bool idMapFile::Parse( const char *filename, bool ignoreRegion, bool osPath ) { + // no string concatenation for epairs and allow path names for materials +// RAVEN BEGIN +// jsinger: Done this way to reduce the amount of code change. The auto pointer will +// delete the lexer when this method exits + idAutoPtr lexer(LexerFactory::MakeLexer( LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES )); + Lexer &src(*lexer); +// RAVEN END + idToken token; + idStr fullName; + idMapEntity *mapEnt; + + TIME_THIS_SCOPE( __FUNCLINE__); + + name = filename; + name.StripFileExtension(); + fullName = name; + hasPrimitiveData = false; + + if ( !ignoreRegion ) { + // try loading a .reg file first + fullName.SetFileExtension( "reg" ); + src.LoadFile( fullName, osPath ); + } + + if ( !src.IsLoaded() ) { + + PACIFIER_UPDATE; + + // now try a .map file + fullName.SetFileExtension( "map" ); + src.LoadFile( fullName, osPath ); + + if ( !src.IsLoaded() ) { + // didn't get anything at all + return false; + } + } + + version = OLD_MAP_VERSION; + fileTime = src.GetFileTime(); + entities.DeleteContents( true ); + + if ( src.CheckTokenString( "Version" ) ) { + src.ReadTokenOnLine( &token ); + version = token.GetIntValue(); + } + + while( 1 ) { + PACIFIER_UPDATE; + mapEnt = idMapEntity::Parse( src, ( entities.Num() == 0 ), version ); + if ( !mapEnt ) { + break; + } +// RAVEN BEGIN +// rhummer: Check to see if there are func_groups in the map file. + if ( !mHasFuncGroups && !idStr::Icmp( mapEnt->epairs.GetString( "classname" ), "func_group" ) ) { + mHasFuncGroups = true; + } +// RAVEN END + entities.Append( mapEnt ); + } + + PACIFIER_UPDATE; + ParseExport(filename, osPath); + + SetGeometryCRC(); +// RAVEN BEGIN +// rhummer: Trying to debug why func_groups disappear when editing a map when they shouldn't. + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2loaded .map file ^0%s\n", filename ); + } + mHasBeenResolved = false; +// RAVEN END + + hasPrimitiveData = true; + return true; +} + +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data +void idMapFile::Resolve( void ) +{ + int i, j, k; + idMapEntity *mapEnt; + + if ( !hasPrimitiveData ) { + return; + } + + // if the map has a worldspawn + if ( entities.Num() ) { + + // "removeEntities" "classname" can be set in the worldspawn to remove all entities with the given classname + const idKeyValue *removeEntities = entities[0]->epairs.MatchPrefix( "removeEntities", NULL ); + while ( removeEntities ) { + RemoveEntities( removeEntities->GetValue() ); + removeEntities = entities[0]->epairs.MatchPrefix( "removeEntities", removeEntities ); + } + + // "overrideMaterial" "material" can be set in the worldspawn to reset all materials + idStr material; + if ( entities[0]->epairs.GetString( "overrideMaterial", "", material ) ) { + for ( i = 0; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + for ( j = 0; j < mapEnt->GetNumPrimitives(); j++ ) { + idMapPrimitive *mapPrimitive = mapEnt->GetPrimitive( j ); + switch( mapPrimitive->GetType() ) { + case idMapPrimitive::TYPE_BRUSH: { + idMapBrush *mapBrush = static_cast(mapPrimitive); + for ( k = 0; k < mapBrush->GetNumSides(); k++ ) { + mapBrush->GetSide( k )->SetMaterial( material ); + } + break; + } + case idMapPrimitive::TYPE_PATCH: { + static_cast(mapPrimitive)->SetMaterial( material ); + break; + } + } + } + } + } + + // force all entities to have a name key/value pair + if ( entities[0]->epairs.GetBool( "forceEntityNames" ) ) { + for ( i = 1; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + if ( !mapEnt->epairs.FindKey( "name" ) ) { + mapEnt->epairs.Set( "name", va( "%s%d", mapEnt->epairs.GetString( "classname", "forcedName" ), i ) ); + } + } + } + + // move the primitives of any func_group entities to the worldspawn + for ( i = 1; i < entities.Num(); i++ ) { + mapEnt = entities[i]; + if ( idStr::Icmp( mapEnt->epairs.GetString( "classname" ), "func_group" ) == 0 ) { + idVec3 delta; + + mapEnt->epairs.GetVector( "origin", "0 0 0", delta ); + + for( j = 0; j < mapEnt->primitives.Num(); j++ ) { + idMapPrimitive *mapPrim = mapEnt->primitives[j]; + + mapPrim->AdjustOrigin( delta ); + } + entities[0]->primitives.Append( mapEnt->primitives ); + mapEnt->primitives.Clear(); + } + } + RemoveEntities( "func_group" ); + } +// rhummer: + mHasBeenResolved = true; + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2idMapFile::Resolve has been run on ^0%s^2 so no func_groups exist.\n", name.c_str() ); + } +} + +// rjohnson: added export +/* +============ +idMapFile::Write +============ +*/ +bool idMapFile::Write( const char *fileName, const char *ext, bool fromBasePath, bool exportOnly ) { + int i; + idStr qpath; + idFile *fp; +// RAVEN BEGIN +// rhummer: Used to verify func_groups didn't disappear somehow. + bool funcGroupFound = false; +// RAVEN END + + qpath = fileName; + qpath.SetFileExtension( ext ); + + idLib::common->Printf( "writing %s...\n", qpath.c_str() ); + + if ( fromBasePath ) { + fp = idLib::fileSystem->OpenFileWrite( qpath, "fs_devpath" ); + } + else { + fp = idLib::fileSystem->OpenExplicitFileWrite( qpath ); + } + + if ( !fp ) { + idLib::common->Warning( "Couldn't open %s\n", qpath.c_str() ); + return false; + } + + fp->WriteFloatString( "Version %d\n", CURRENT_MAP_VERSION ); + + if (exportOnly) + { + for ( i = 0; i < entities.Num(); i++ ) + { + if (entities[i]->epairs.GetInt("export")) + { + entities[i]->Write( fp, i ); + } + } + } + else + { + for ( i = 0; i < entities.Num(); i++ ) { + entities[i]->Write( fp, i ); +// RAVEN BEGIN +// rhummer: See if there are func_groups, there should be if there was during loading.. + if ( !idStr::Icmp( entities[i]->epairs.GetString( "classname" ), "func_group" ) ) { + funcGroupFound = true; + } +// RAVEN END + } + } + +// RAVEN BEGIN +// rhummer: If this is true..something bad happened to cause all func_groups to go away between loading and saving. + if ( mHasFuncGroups && !funcGroupFound && cvarSystem->GetCVarBool( "developer" ) ) { + common->Warning( "^5Did not find any ^2func_groups^0 during save, but there were ^2func_groups^0 found during loading. Was this intended?\n"); + } +// RAVEN END + + idLib::fileSystem->CloseFile( fp ); + + return true; +} +// RAVEN END + +/* +=============== +idMapFile::SetGeometryCRC +=============== +*/ +void idMapFile::SetGeometryCRC( void ) { + int i; + + geometryCRC = 0; + for ( i = 0; i < entities.Num(); i++ ) { + geometryCRC ^= entities[i]->GetGeometryCRC(); + } +} + +/* +=============== +idMapFile::AddEntity +=============== +*/ +int idMapFile::AddEntity( idMapEntity *mapEnt ) { + int ret = entities.Append( mapEnt ); + return ret; +} + +/* +=============== +idMapFile::FindEntity +=============== +*/ +idMapEntity *idMapFile::FindEntity( const char *name ) { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + if ( idStr::Icmp( ent->epairs.GetString( "name" ), name ) == 0 ) { + return ent; + } + } + return NULL; +} + +/* +=============== +idMapFile::RemoveEntity +=============== +*/ +void idMapFile::RemoveEntity( idMapEntity *mapEnt ) { + entities.Remove( mapEnt ); + delete mapEnt; +} + +/* +=============== +idMapFile::RemoveEntity +=============== +*/ +void idMapFile::RemoveEntities( const char *classname ) { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + if ( idStr::Icmp( ent->epairs.GetString( "classname" ), classname ) == 0 ) { + delete entities[i]; + entities.RemoveIndex( i ); + i--; + } + } +} + +/* +=============== +idMapFile::RemoveAllEntities +=============== +*/ +void idMapFile::RemoveAllEntities() { + entities.DeleteContents( true ); + hasPrimitiveData = false; +} + +/* +=============== +idMapFile::RemovePrimitiveData +=============== +*/ +void idMapFile::RemovePrimitiveData() { + for ( int i = 0; i < entities.Num(); i++ ) { + idMapEntity *ent = entities[i]; + ent->RemovePrimitiveData(); + } + hasPrimitiveData = false; +// RAVEN BEGIN +// rhummer: debugging stuff.. + if ( cvarSystem->GetCVarBool( "developer" ) ) { + common->Printf( "^2Removed all primitive data from ^0%s\n", name.c_str() ); + } +// RAVEN END +} + +/* +=============== +idMapFile::NeedsReload +=============== +*/ +bool idMapFile::NeedsReload() { + if ( name.Length() ) { + unsigned int time = (unsigned int)-1; + if ( idLib::fileSystem->ReadFile( name, NULL, &time ) > 0 ) { + return ( time > fileTime ); + } + } + return true; +} + +bool idMapFile::WriteExport( const char *fileName, bool fromBasePath ) +{ + int i, j; + idDict newPairs; + + for ( i = 0; i < entities.Num(); i++ ) + { + idMapEntity *ent = entities[i]; + + if ( ent->epairs.GetInt("export") == 1 ) + { +/* for ( j = 0; j < ent->epairs.GetNumKeyVals(); j++) + { + const idKeyValue *kv = ent->epairs.GetKeyVal(j); + + if (kv->key.CmpPrefix("export_") == 0) + { + ent->epairs.Delete(kv->key.c_str()); + j--; + continue; + } + } +*/ + newPairs.Clear(); + + for ( j = 0; j < ent->epairs.GetNumKeyVals(); j++) + { + const idKeyValue *kv = ent->epairs.GetKeyVal(j); + + if (kv->GetKey().CmpPrefix("export_") != 0) + { + char newName[1024]; + + sprintf(newName, "export_%s", kv->GetKey().c_str()); + + if (!ent->epairs.GetString(newName, 0)) + { // don't overwrite an existing export + newPairs.Set (newName, kv->GetValue().c_str()); + } + } + } + + ent->epairs.Copy(newPairs); + } + } + + return Write(fileName, "export", fromBasePath, true); +} + +bool idMapFile::ParseExport( const char *filename, bool osPath ) +{ + // no string concatenation for epairs and allow path names for materials +// RAVEN BEGIN +// jsinger: Done this way to reduce the amount of code to change. +// The auto pointer will delete the lexer when this method exits. + idAutoPtr lexer(LexerFactory::MakeLexer( LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ) ); + Lexer &src(*lexer); +// RAVEN END + idToken token; + idStr fullName; + idMapEntity *mapEnt; + int i, j; + int numMerged, numNotMerged, numAdded, numRemoved; + + numMerged = numNotMerged = numAdded = numRemoved = 0; + mHasExportEntities = false; + + fullName = filename; + fullName.StripFileExtension(); + fullName.SetFileExtension( "export" ); + src.LoadFile( fullName, osPath ); + if ( !src.IsLoaded() ) { + // didn't get anything at all + return false; + } + + version = OLD_MAP_VERSION; + fileTime = src.GetFileTime(); + mExportEntities.DeleteContents( true ); + + if ( src.CheckTokenString( "Version" ) ) { + src.ReadTokenOnLine( &token ); + version = token.GetIntValue(); + } + + while( 1 ) { + mapEnt = idMapEntity::Parse( src, false, version ); + if ( !mapEnt ) { + break; + } + mExportEntities.Append( mapEnt ); + } + + if (mExportEntities.Num()) + { + mHasExportEntities = true; + } + + for(i=0;iepairs.GetString("name"); + mapEnt = FindEntity(name); + + if (!mapEnt) + { // check to see if we have an export_name + name = ent->epairs.GetString("export_name", 0); + if (!name || ent->epairs.GetInt("original") ) + { // this is a new entity + ent->epairs.SetInt("export", 2); // 2 = when re-exporting, don't duplicated the export_ fields + ent->epairs.SetInt("merged", 1); + ent->epairs.SetInt("original", 0); // 0 = now the entity in map is always treated as master + entities.Append(ent); + mExportEntities.RemoveIndex(i); + i--; + numAdded++; + continue; + } + else + { + mapEnt = FindEntity(name); + } + } + + if (!mapEnt) + { // this export entity has been removed from the original map + numNotMerged++; + } + else + { // exists in both, do a merge! + mapEnt->epairs.SetInt("export", 2); // 2 = when re-exporting, don't duplicated the export_ fields + mapEnt->epairs.SetInt("merged", 1); + for ( j = 0; j < ent->epairs.GetNumKeyVals(); j++) + { + char origName[1024]; + const idKeyValue *exportKV = ent->epairs.GetKeyVal(j); + const idKeyValue *origKV, *mapKV; + + if (exportKV->GetKey().CmpPrefix("export_") != 0) + { + sprintf(origName, "export_%s", exportKV->GetKey().c_str()); + origKV = ent->epairs.FindKey(origName); + mapKV = mapEnt->epairs.FindKey(exportKV->GetKey().c_str()); + + if (origKV && mapKV) + { // keys exist in all spots, compare + if (origKV->GetValue() == exportKV->GetValue()) + { // export hasn't change + } + else if (mapKV->GetValue() == origKV->GetValue()) + { // map is same as the orig, yet export has changed, so bring it over + mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); + } + } + else if (origKV) + { // map has removed this key, so we shouldn't merge it + } + else if (mapKV) + { // map has added this key, so we'll ignore the exported value, which has also been added + } + else + { // this was added to the export, so merge it in + mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); + } + } + else + { + mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); + } + } + numMerged++; + } + } + + // check for any entities that have been marked as exported in the original map but have been removed from the export file + for(i=0;iepairs.GetInt("export")) + { + if (!ent->epairs.GetInt("merged")) + { + entities.RemoveIndex(i); + numRemoved++; + i--; + } + else + { + ent->epairs.Delete("merged"); + } + } + } + + common->Printf("Export Merge: %d added, %d merged, %d not merged, %d removed\n", numAdded, numMerged, numNotMerged, numRemoved); + + mExportEntities.DeleteContents( true ); + + return true; +} diff --git a/src/idlib/math/Angles.cpp b/src/idlib/math/Angles.cpp new file mode 100644 index 0000000..c5e3b6a --- /dev/null +++ b/src/idlib/math/Angles.cpp @@ -0,0 +1,252 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include + +idAngles ang_zero( 0.0f, 0.0f, 0.0f ); + + +/* +================= +idAngles::Normalize360 + +returns angles normalized to the range [0 <= angle < 360] +================= +*/ +idAngles& idAngles::Normalize360( void ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( ( (*this)[i] >= 360.0f ) || ( (*this)[i] < 0.0f ) ) { + (*this)[i] -= floor( (*this)[i] / 360.0f ) * 360.0f; + + if ( (*this)[i] >= 360.0f ) { + (*this)[i] -= 360.0f; + } + if ( (*this)[i] < 0.0f ) { + (*this)[i] += 360.0f; + } + } + } + + return *this; +} + +/* +================= +idAngles::Normalize180 + +returns angles normalized to the range [-180 < angle <= 180] +================= +*/ +idAngles& idAngles::Normalize180( void ) { + Normalize360(); + + if ( pitch > 180.0f ) { + pitch -= 360.0f; + } + + if ( yaw > 180.0f ) { + yaw -= 360.0f; + } + + if ( roll > 180.0f ) { + roll -= 360.0f; + } + return *this; +} + +/* +================= +idAngles::ToVectors +================= +*/ +void idAngles::ToVectors( idVec3 *forward, idVec3 *right, idVec3 *up ) const { + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + if ( forward ) { + forward->Set( cp * cy, cp * sy, -sp ); + } + + if ( right ) { + right->Set( -sr * sp * cy + cr * sy, -sr * sp * sy + -cr * cy, -sr * cp ); + } + + if ( up ) { + up->Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + } +} + +/* +================= +idAngles::ToForward +================= +*/ +idVec3 idAngles::ToForward( void ) const { + float sp, sy, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + + return idVec3( cp * cy, cp * sy, -sp ); +} + +/* +================= +idAngles::ToQuat +================= +*/ +idQuat idAngles::ToQuat( void ) const { + float sx, cx, sy, cy, sz, cz; + float sxcy, cxcy, sxsy, cxsy; + + idMath::SinCos( DEG2RAD( yaw ) * 0.5f, sz, cz ); + idMath::SinCos( DEG2RAD( pitch ) * 0.5f, sy, cy ); + idMath::SinCos( DEG2RAD( roll ) * 0.5f, sx, cx ); + + sxcy = sx * cy; + cxcy = cx * cy; + sxsy = sx * sy; + cxsy = cx * sy; + + return idQuat( cxsy*sz - sxcy*cz, -cxsy*cz - sxcy*sz, sxsy*cz - cxcy*sz, cxcy*cz + sxsy*sz ); +} + +/* +================= +idAngles::ToRotation +================= +*/ +idRotation idAngles::ToRotation( void ) const { + idVec3 vec; + float angle, w; + float sx, cx, sy, cy, sz, cz; + float sxcy, cxcy, sxsy, cxsy; + + if ( pitch == 0.0f ) { + if ( yaw == 0.0f ) { + return idRotation( vec3_origin, idVec3( -1.0f, 0.0f, 0.0f ), roll ); + } + if ( roll == 0.0f ) { + return idRotation( vec3_origin, idVec3( 0.0f, 0.0f, -1.0f ), yaw ); + } + } else if ( yaw == 0.0f && roll == 0.0f ) { + return idRotation( vec3_origin, idVec3( 0.0f, -1.0f, 0.0f ), pitch ); + } + + idMath::SinCos( DEG2RAD( yaw ) * 0.5f, sz, cz ); + idMath::SinCos( DEG2RAD( pitch ) * 0.5f, sy, cy ); + idMath::SinCos( DEG2RAD( roll ) * 0.5f, sx, cx ); + + sxcy = sx * cy; + cxcy = cx * cy; + sxsy = sx * sy; + cxsy = cx * sy; + + vec.x = cxsy * sz - sxcy * cz; + vec.y = -cxsy * cz - sxcy * sz; + vec.z = sxsy * cz - cxcy * sz; + w = cxcy * cz + sxsy * sz; + angle = idMath::ACos( w ); + if ( angle == 0.0f ) { + vec.Set( 0.0f, 0.0f, 1.0f ); + } else { + //vec *= (1.0f / sin( angle )); + vec.Normalize(); + vec.FixDegenerateNormal(); + angle *= 2.0f * idMath::M_RAD2DEG; + } + return idRotation( vec3_origin, vec, angle ); +} + +/* +================= +idAngles::ToMat3 +================= +*/ +idMat3 idAngles::ToMat3( void ) const { + idMat3 mat; + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + + mat[ 0 ].Set( cp * cy, cp * sy, -sp ); + mat[ 1 ].Set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + mat[ 2 ].Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + + return mat; +} + +// RAVEN BEGIN +// jscott: slightly quicker version without the copy +idMat3 &idAngles::ToMat3( idMat3 &mat ) const { + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( DEG2RAD( yaw ), sy, cy ); + idMath::SinCos( DEG2RAD( pitch ), sp, cp ); + idMath::SinCos( DEG2RAD( roll ), sr, cr ); + + mat[0].Set( cp * cy, cp * sy, -sp ); + mat[1].Set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + mat[2].Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + + return mat; +} +// RAVEN END + +/* +================= +idAngles::ToMat4 +================= +*/ +idMat4 idAngles::ToMat4( void ) const { + return ToMat3().ToMat4(); +} + +/* +================= +idAngles::ToAngularVelocity +================= +*/ +idVec3 idAngles::ToAngularVelocity( void ) const { + idRotation rotation = idAngles::ToRotation(); + return rotation.GetVec() * DEG2RAD( rotation.GetAngle() ); +} + +/* +============= +idAngles::ToString +============= +*/ +const char *idAngles::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/src/idlib/math/Angles.h b/src/idlib/math/Angles.h new file mode 100644 index 0000000..9000f39 --- /dev/null +++ b/src/idlib/math/Angles.h @@ -0,0 +1,329 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_ANGLES_H__ +#define __MATH_ANGLES_H__ + +/* +=============================================================================== + + Euler angles + +=============================================================================== +*/ + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#ifndef M_PI +# define M_PI (3.1415926536f) +#endif + +class idVec3; +class idQuat; +class idRotation; +class idMat3; +class idMat4; + +class idAngles { +public: + float pitch; + float yaw; + float roll; + + idAngles( void ); + idAngles( float pitch, float yaw, float roll ); + explicit idAngles( const idVec3 &v ); + + void Set( float pitch, float yaw, float roll ); + idAngles & Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idAngles operator-() const; // negate angles, in general not the inverse rotation + idAngles & operator=( const idAngles &a ); + idAngles operator+( const idAngles &a ) const; + idAngles & operator+=( const idAngles &a ); + idAngles operator-( const idAngles &a ) const; + idAngles & operator-=( const idAngles &a ); + idAngles operator*( const float a ) const; + idAngles & operator*=( const float a ); + idAngles operator/( const float a ) const; + idAngles & operator/=( const float a ); + + friend idAngles operator*( const float a, const idAngles &b ); + + bool Compare( const idAngles &a ) const; // exact compare, no epsilon + bool Compare( const idAngles &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idAngles &a ) const; // exact compare, no epsilon + bool operator!=( const idAngles &a ) const; // exact compare, no epsilon + + idAngles & Normalize360( void ); // normalizes 'this' + idAngles & Normalize180( void ); // normalizes 'this' + + float Length( void ) const; + float LengthSqr( void ) const; + void Clamp( const idAngles &min, const idAngles &max ); + + int GetDimension( void ) const; + + void ToVectors( idVec3 *forward, idVec3 *right = NULL, idVec3 *up = NULL ) const; + idVec3 ToForward( void ) const; + idQuat ToQuat( void ) const; + idRotation ToRotation( void ) const; + idMat3 ToMat3( void ) const; + +// RAVEN BEGIN + idMat3 &ToMat3( idMat3 &mat ) const; +// abahr + idAngles Random( const idVec3& range, idRandom& random ) const; + idAngles& Scale( const idAngles& scalar ); + idAngles& Remap( const int map[], const int dirMap[] ); +// RAVEN END + + idMat4 ToMat4( void ) const; + idVec3 ToAngularVelocity( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + bool FixDenormals( void ); +}; + +extern idAngles ang_zero; + +ID_INLINE idAngles::idAngles( void ) { +} + +ID_INLINE idAngles::idAngles( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE idAngles::idAngles( const idVec3 &v ) { + this->pitch = v[0]; + this->yaw = v[1]; + this->roll = v[2]; +} + +ID_INLINE void idAngles::Set( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE idAngles &idAngles::Zero( void ) { + pitch = yaw = roll = 0.0f; + return *this; +} + +ID_INLINE float idAngles::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +ID_INLINE float &idAngles::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +ID_INLINE idAngles idAngles::operator-() const { + return idAngles( -pitch, -yaw, -roll ); +} + +ID_INLINE idAngles &idAngles::operator=( const idAngles &a ) { + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; + return *this; +} + +ID_INLINE idAngles idAngles::operator+( const idAngles &a ) const { + return idAngles( pitch + a.pitch, yaw + a.yaw, roll + a.roll ); +} + +ID_INLINE idAngles& idAngles::operator+=( const idAngles &a ) { + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + + return *this; +} + +ID_INLINE idAngles idAngles::operator-( const idAngles &a ) const { + return idAngles( pitch - a.pitch, yaw - a.yaw, roll - a.roll ); +} + +ID_INLINE idAngles& idAngles::operator-=( const idAngles &a ) { + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + + return *this; +} + +ID_INLINE idAngles idAngles::operator*( const float a ) const { + return idAngles( pitch * a, yaw * a, roll * a ); +} + +ID_INLINE idAngles& idAngles::operator*=( float a ) { + pitch *= a; + yaw *= a; + roll *= a; + return *this; +} + +ID_INLINE idAngles idAngles::operator/( const float a ) const { + float inva = 1.0f / a; + return idAngles( pitch * inva, yaw * inva, roll * inva ); +} + +ID_INLINE idAngles& idAngles::operator/=( float a ) { + float inva = 1.0f / a; + pitch *= inva; + yaw *= inva; + roll *= inva; + return *this; +} + +ID_INLINE idAngles operator*( const float a, const idAngles &b ) { + return idAngles( a * b.pitch, a * b.yaw, a * b.roll ); +} + +ID_INLINE bool idAngles::Compare( const idAngles &a ) const { + return ( ( a.pitch == pitch ) && ( a.yaw == yaw ) && ( a.roll == roll ) ); +} + +ID_INLINE bool idAngles::Compare( const idAngles &a, const float epsilon ) const { + if ( idMath::Fabs( pitch - a.pitch ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( yaw - a.yaw ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( roll - a.roll ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idAngles::operator==( const idAngles &a ) const { + return Compare( a ); +} + +ID_INLINE bool idAngles::operator!=( const idAngles &a ) const { + return !Compare( a ); +} + +ID_INLINE float idAngles::Length( void ) const { + return idMath::Sqrt( yaw * yaw + pitch * pitch + roll * roll ); +} + +ID_INLINE float idAngles::LengthSqr( void ) const { + return ( yaw * yaw + pitch * pitch + roll * roll ); +} + +ID_INLINE void idAngles::Clamp( const idAngles &min, const idAngles &max ) { + if ( pitch < min.pitch ) { + pitch = min.pitch; + } else if ( pitch > max.pitch ) { + pitch = max.pitch; + } + if ( yaw < min.yaw ) { + yaw = min.yaw; + } else if ( yaw > max.yaw ) { + yaw = max.yaw; + } + if ( roll < min.roll ) { + roll = min.roll; + } else if ( roll > max.roll ) { + roll = max.roll; + } +} + +ID_INLINE int idAngles::GetDimension( void ) const { + return 3; +} + +ID_INLINE const float *idAngles::ToFloatPtr( void ) const { + return &pitch; +} + +ID_INLINE float *idAngles::ToFloatPtr( void ) { + return &pitch; +} + +// RAVEN BEGIN +// abahr +ID_INLINE idAngles idAngles::Random( const idVec3& range, idRandom& random ) const { + idAngles a( *this ); + for( int ix = 0; ix < GetDimension(); ++ix ) { + a[ ix ] += a[ ix ] * range[ix] * random.CRandomFloat(); + } + return a; +} + +ID_INLINE idAngles& idAngles::Scale( const idAngles& scalar ) { + for( int ix = 0; ix < GetDimension(); ++ix ) { + (*this)[ix] *= scalar[ix]; + } + + return *this; +} + +ID_INLINE idAngles& idAngles::Remap( const int map[], const int dirMap[] ) { + idAngles ang( *this ); + for( int ix = 0; ix < GetDimension(); ++ix ) { + (*this)[ ix ] = SignZero(dirMap[ix]) * ang[ abs(map[ix]) ]; + } + + return *this; +} +// RAVEN END + +ID_INLINE bool idAngles::FixDenormals( void ) { + bool denormal = false; + if ( fabs( yaw ) < 1e-30f ) { + yaw = 0.0f; + denormal = true; + } + if ( fabs( pitch ) < 1e-30f ) { + pitch = 0.0f; + denormal = true; + } + if ( fabs( roll ) < 1e-30f ) { + roll = 0.0f; + denormal = true; + } + return denormal; + +} + +#endif /* !__MATH_ANGLES_H__ */ diff --git a/src/idlib/math/Complex.cpp b/src/idlib/math/Complex.cpp new file mode 100644 index 0000000..7b24712 --- /dev/null +++ b/src/idlib/math/Complex.cpp @@ -0,0 +1,37 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +idComplex complex_origin( 0.0f, 0.0f ); + +/* +============= +idComplex::ToString +============= +*/ +const char *idComplex::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/src/idlib/math/Complex.h b/src/idlib/math/Complex.h new file mode 100644 index 0000000..5e2b1a5 --- /dev/null +++ b/src/idlib/math/Complex.h @@ -0,0 +1,344 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_COMPLEX_H__ +#define __MATH_COMPLEX_H__ + +/* +=============================================================================== + + Complex number + +=============================================================================== +*/ + +class idComplex { +public: + float r; // real part + float i; // imaginary part + + idComplex( void ); + idComplex( const float r, const float i ); + + void Set( const float r, const float i ); + void Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + idComplex operator-() const; + idComplex & operator=( const idComplex &a ); + + idComplex operator*( const idComplex &a ) const; + idComplex operator/( const idComplex &a ) const; + idComplex operator+( const idComplex &a ) const; + idComplex operator-( const idComplex &a ) const; + + idComplex & operator*=( const idComplex &a ); + idComplex & operator/=( const idComplex &a ); + idComplex & operator+=( const idComplex &a ); + idComplex & operator-=( const idComplex &a ); + + idComplex operator*( const float a ) const; + idComplex operator/( const float a ) const; + idComplex operator+( const float a ) const; + idComplex operator-( const float a ) const; + + idComplex & operator*=( const float a ); + idComplex & operator/=( const float a ); + idComplex & operator+=( const float a ); + idComplex & operator-=( const float a ); + + friend idComplex operator*( const float a, const idComplex &b ); + friend idComplex operator/( const float a, const idComplex &b ); + friend idComplex operator+( const float a, const idComplex &b ); + friend idComplex operator-( const float a, const idComplex &b ); + + bool Compare( const idComplex &a ) const; // exact compare, no epsilon + bool Compare( const idComplex &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idComplex &a ) const; // exact compare, no epsilon + bool operator!=( const idComplex &a ) const; // exact compare, no epsilon + + idComplex Reciprocal( void ) const; + idComplex Sqrt( void ) const; + float Abs( void ) const; + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; +}; + +extern idComplex complex_origin; +#define complex_zero complex_origin + +ID_INLINE idComplex::idComplex( void ) { +} + +ID_INLINE idComplex::idComplex( const float r, const float i ) { + this->r = r; + this->i = i; +} + +ID_INLINE void idComplex::Set( const float r, const float i ) { + this->r = r; + this->i = i; +} + +ID_INLINE void idComplex::Zero( void ) { + r = i = 0.0f; +} + +ID_INLINE float idComplex::operator[]( int index ) const { + assert( index >= 0 && index < 2 ); + return ( &r )[ index ]; +} + +ID_INLINE float& idComplex::operator[]( int index ) { + assert( index >= 0 && index < 2 ); + return ( &r )[ index ]; +} + +ID_INLINE idComplex idComplex::operator-() const { + return idComplex( -r, -i ); +} + +ID_INLINE idComplex &idComplex::operator=( const idComplex &a ) { + r = a.r; + i = a.i; + return *this; +} + +ID_INLINE idComplex idComplex::operator*( const idComplex &a ) const { + return idComplex( r * a.r - i * a.i, i * a.r + r * a.i ); +} + +ID_INLINE idComplex idComplex::operator/( const idComplex &a ) const { + float s, t; + if ( idMath::Fabs( a.r ) >= idMath::Fabs( a.i ) ) { + s = a.i / a.r; + t = 1.0f / ( a.r + s * a.i ); + return idComplex( ( r + s * i ) * t, ( i - s * r ) * t ); + } else { + s = a.r / a.i; + t = 1.0f / ( s * a.r + a.i ); + return idComplex( ( r * s + i ) * t, ( i * s - r ) * t ); + } +} + +ID_INLINE idComplex idComplex::operator+( const idComplex &a ) const { + return idComplex( r + a.r, i + a.i ); +} + +ID_INLINE idComplex idComplex::operator-( const idComplex &a ) const { + return idComplex( r - a.r, i - a.i ); +} + +ID_INLINE idComplex &idComplex::operator*=( const idComplex &a ) { + *this = idComplex( r * a.r - i * a.i, i * a.r + r * a.i ); + return *this; +} + +ID_INLINE idComplex &idComplex::operator/=( const idComplex &a ) { + float s, t; + if ( idMath::Fabs( a.r ) >= idMath::Fabs( a.i ) ) { + s = a.i / a.r; + t = 1.0f / ( a.r + s * a.i ); + *this = idComplex( ( r + s * i ) * t, ( i - s * r ) * t ); + } else { + s = a.r / a.i; + t = 1.0f / ( s * a.r + a.i ); + *this = idComplex( ( r * s + i ) * t, ( i * s - r ) * t ); + } + return *this; +} + +ID_INLINE idComplex &idComplex::operator+=( const idComplex &a ) { + r += a.r; + i += a.i; + return *this; +} + +ID_INLINE idComplex &idComplex::operator-=( const idComplex &a ) { + r -= a.r; + i -= a.i; + return *this; +} + +ID_INLINE idComplex idComplex::operator*( const float a ) const { + return idComplex( r * a, i * a ); +} + +ID_INLINE idComplex idComplex::operator/( const float a ) const { + float s = 1.0f / a; + return idComplex( r * s, i * s ); +} + +ID_INLINE idComplex idComplex::operator+( const float a ) const { + return idComplex( r + a, i ); +} + +ID_INLINE idComplex idComplex::operator-( const float a ) const { + return idComplex( r - a, i ); +} + +ID_INLINE idComplex &idComplex::operator*=( const float a ) { + r *= a; + i *= a; + return *this; +} + +ID_INLINE idComplex &idComplex::operator/=( const float a ) { + float s = 1.0f / a; + r *= s; + i *= s; + return *this; +} + +ID_INLINE idComplex &idComplex::operator+=( const float a ) { + r += a; + return *this; +} + +ID_INLINE idComplex &idComplex::operator-=( const float a ) { + r -= a; + return *this; +} + +ID_INLINE idComplex operator*( const float a, const idComplex &b ) { + return idComplex( a * b.r, a * b.i ); +} + +ID_INLINE idComplex operator/( const float a, const idComplex &b ) { + float s, t; + if ( idMath::Fabs( b.r ) >= idMath::Fabs( b.i ) ) { + s = b.i / b.r; + t = a / ( b.r + s * b.i ); + return idComplex( t, - s * t ); + } else { + s = b.r / b.i; + t = a / ( s * b.r + b.i ); + return idComplex( s * t, - t ); + } +} + +ID_INLINE idComplex operator+( const float a, const idComplex &b ) { + return idComplex( a + b.r, b.i ); +} + +ID_INLINE idComplex operator-( const float a, const idComplex &b ) { + return idComplex( a - b.r, -b.i ); +} + +ID_INLINE idComplex idComplex::Reciprocal( void ) const { + float s, t; + if ( idMath::Fabs( r ) >= idMath::Fabs( i ) ) { + s = i / r; + t = 1.0f / ( r + s * i ); + return idComplex( t, - s * t ); + } else { + s = r / i; + t = 1.0f / ( s * r + i ); + return idComplex( s * t, - t ); + } +} + +ID_INLINE idComplex idComplex::Sqrt( void ) const { + float x, y, w; + + if ( r == 0.0f && i == 0.0f ) { + return idComplex( 0.0f, 0.0f ); + } + x = idMath::Fabs( r ); + y = idMath::Fabs( i ); + if ( x >= y ) { + w = y / x; + w = idMath::Sqrt( x ) * idMath::Sqrt( 0.5f * ( 1.0f + idMath::Sqrt( 1.0f + w * w ) ) ); + } else { + w = x / y; + w = idMath::Sqrt( y ) * idMath::Sqrt( 0.5f * ( w + idMath::Sqrt( 1.0f + w * w ) ) ); + } + if ( w == 0.0f ) { + return idComplex( 0.0f, 0.0f ); + } + if ( r >= 0.0f ) { + return idComplex( w, 0.5f * i / w ); + } else { + return idComplex( 0.5f * y / w, ( i >= 0.0f ) ? w : -w ); + } +} + +ID_INLINE float idComplex::Abs( void ) const { + float x, y, t; + x = idMath::Fabs( r ); + y = idMath::Fabs( i ); + if ( x == 0.0f ) { + return y; + } else if ( y == 0.0f ) { + return x; + } else if ( x > y ) { + t = y / x; + return x * idMath::Sqrt( 1.0f + t * t ); + } else { + t = x / y; + return y * idMath::Sqrt( 1.0f + t * t ); + } +} + +ID_INLINE bool idComplex::Compare( const idComplex &a ) const { + return ( ( r == a.r ) && ( i == a.i ) ); +} + +ID_INLINE bool idComplex::Compare( const idComplex &a, const float epsilon ) const { + if ( idMath::Fabs( r - a.r ) > epsilon ) { + return false; + } + if ( idMath::Fabs( i - a.i ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idComplex::operator==( const idComplex &a ) const { + return Compare( a ); +} + +ID_INLINE bool idComplex::operator!=( const idComplex &a ) const { + return !Compare( a ); +} + +ID_INLINE int idComplex::GetDimension( void ) const { + return 2; +} + +ID_INLINE const float *idComplex::ToFloatPtr( void ) const { + return &r; +} + +ID_INLINE float *idComplex::ToFloatPtr( void ) { + return &r; +} + +#endif /* !__MATH_COMPLEX_H__ */ diff --git a/src/idlib/math/Curve.h b/src/idlib/math/Curve.h new file mode 100644 index 0000000..4e27851 --- /dev/null +++ b/src/idlib/math/Curve.h @@ -0,0 +1,2595 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_CURVE_H__ +#define __MATH_CURVE_H__ + +/* +=============================================================================== + + Curve base template. + +=============================================================================== +*/ + +template< class type > +class idCurve { +public: + idCurve( void ); + virtual ~idCurve( void ); + + virtual int AddValue( const float time, const type &value ); + virtual void RemoveIndex( const int index ) { values.RemoveIndex(index); times.RemoveIndex(index); changed = true; } + virtual void Clear( void ) { values.Clear(); times.Clear(); currentIndex = -1; changed = true; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + + virtual bool IsDone( const float time ) const; + +// RAVEN BEGIN +// jscott: added + void SetGranularity( int gran ) { times.SetGranularity( gran ); values.SetGranularity( gran ); } +// RAVEN END + + int GetNumValues( void ) const { return values.Num(); } + void SetValue( const int index, const type &value ) { values[index] = value; changed = true; } + type GetValue( const int index ) const { return values[index]; } + type * GetValueAddress( const int index ) { return &values[index]; } + float GetTime( const int index ) const { return times[index]; } + + float GetLengthForTime( const float time ) const; + float GetTimeForLength( const float length, const float epsilon = 0.1f ) const; + float GetLengthBetweenKnots( const int i0, const int i1 ) const; + + void MakeUniform( const float totalTime ); + void SetConstantSpeed( const float totalTime ); + void ShiftTime( const float deltaTime ); + void Translate( const type &translation ); + +// RAVEN BEGIN +// ddynerman: spline joining + virtual bool Weld( idCurve* c ) const; +// RAVEN END + +protected: + idList times; // knots + idList values; // knot values + mutable int currentIndex; // cached index for fast lookup + mutable bool changed; // set whenever the curve changes + + int IndexForTime( const float time ) const; + float TimeForIndex( const int index ) const; + type ValueForIndex( const int index ) const; + + float GetSpeed( const float time ) const; + float RombergIntegral( const float t0, const float t1, const int order ) const; +}; + +/* +==================== +idCurve::idCurve +==================== +*/ +template< class type > +ID_INLINE idCurve::idCurve( void ) { + currentIndex = -1; + changed = false; +} + +/* +==================== +idCurve::~idCurve +==================== +*/ +template< class type > +ID_INLINE idCurve::~idCurve( void ) { +} + +/* +==================== +idCurve::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve::AddValue( const float time, const type &value ) { + int i; + + i = IndexForTime( time ); + times.Insert( time, i ); + values.Insert( value, i ); + changed = true; + return i; +} + +/* +==================== +idCurve::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentValue( const float time ) const { + int i; + + i = IndexForTime( time ); + if ( i >= values.Num() ) { + return values[values.Num() - 1]; + } else { + return values[i]; + } +} + +/* +==================== +idCurve::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentFirstDerivative( const float time ) const { + return ( values[0] - values[0] ); +} + +/* +==================== +idCurve::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::GetCurrentSecondDerivative( const float time ) const { + return ( values[0] - values[0] ); +} + +/* +==================== +idCurve::IsDone +==================== +*/ +template< class type > +ID_INLINE bool idCurve::IsDone( const float time ) const { + return ( time >= times[ times.Num() - 1 ] ); +} + +/* +==================== +idCurve::GetSpeed +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetSpeed( const float time ) const { + int i; + float speed; + type value; + + value = GetCurrentFirstDerivative( time ); + for ( speed = 0.0f, i = 0; i < value.GetDimension(); i++ ) { + speed += value[i] * value[i]; + } + return idMath::Sqrt( speed ); +} + +/* +==================== +idCurve::RombergIntegral +==================== +*/ +template< class type > +ID_INLINE float idCurve::RombergIntegral( const float t0, const float t1, const int order ) const { + int i, j, k, m, n; + float sum, delta; + float *temp[2]; + + temp[0] = (float *) _alloca16( order * sizeof( float ) ); + temp[1] = (float *) _alloca16( order * sizeof( float ) ); + + delta = t1 - t0; + temp[0][0] = 0.5f * delta * ( GetSpeed( t0 ) + GetSpeed( t1 ) ); + + for ( i = 2, m = 1; i <= order; i++, m *= 2, delta *= 0.5f ) { + + // approximate using the trapezoid rule + sum = 0.0f; + for ( j = 1; j <= m; j++ ) { + sum += GetSpeed( t0 + delta * ( j - 0.5f ) ); + } + + // Richardson extrapolation + temp[1][0] = 0.5f * ( temp[0][0] + delta * sum ); + for ( k = 1, n = 4; k < i; k++, n *= 4 ) { + temp[1][k] = ( n * temp[1][k-1] - temp[0][k-1] ) / ( n - 1 ); + } + + for ( j = 0; j < i; j++ ) { + temp[0][j] = temp[1][j]; + } + } + return temp[0][order-1]; +} + +/* +==================== +idCurve::GetLengthBetweenKnots +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetLengthBetweenKnots( const int i0, const int i1 ) const { + float length = 0.0f; + for ( int i = i0; i < i1; i++ ) { + length += RombergIntegral( times[i], times[i+1], 5 ); + } + return length; +} + +/* +==================== +idCurve::GetLengthForTime +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetLengthForTime( const float time ) const { + float length = 0.0f; + int index = IndexForTime( time ); + for ( int i = 0; i < index; i++ ) { + length += RombergIntegral( times[i], times[i+1], 5 ); + } + length += RombergIntegral( times[index], time, 5 ); + return length; +} + +/* +==================== +idCurve::GetTimeForLength +==================== +*/ +template< class type > +ID_INLINE float idCurve::GetTimeForLength( const float length, const float epsilon ) const { + int i, index; + float *accumLength, totalLength, len0, len1, t, diff; + + if ( length <= 0.0f ) { + return times[0]; + } + + accumLength = (float *) _alloca16( values.Num() * sizeof( float ) ); + totalLength = 0.0f; + for ( index = 0; index < values.Num() - 1; index++ ) { + totalLength += GetLengthBetweenKnots( index, index + 1 ); + accumLength[index] = totalLength; + if ( length < accumLength[index] ) { + break; + } + } + + if ( index >= values.Num() - 1 ) { + return times[times.Num() - 1]; + } + + if ( index == 0 ) { + len0 = length; + len1 = accumLength[0]; + } else { + len0 = length - accumLength[index-1]; + len1 = accumLength[index] - accumLength[index-1]; + } + + // invert the arc length integral using Newton's method + t = ( times[index+1] - times[index] ) * len0 / len1; + for ( i = 0; i < 32; i++ ) { + diff = RombergIntegral( times[index], times[index] + t, 5 ) - len0; + if ( idMath::Fabs( diff ) <= epsilon ) { + return times[index] + t; + } + t -= diff / GetSpeed( times[index] + t ); + } + return times[index] + t; +} + +/* +==================== +idCurve::MakeUniform +==================== +*/ +template< class type > +ID_INLINE void idCurve::MakeUniform( const float totalTime ) { + int i, n; + + n = times.Num() - 1; + for ( i = 0; i <= n; i++ ) { + times[i] = i * totalTime / n; + } + changed = true; +} + +/* +==================== +idCurve::SetConstantSpeed +==================== +*/ +template< class type > +ID_INLINE void idCurve::SetConstantSpeed( const float totalTime ) { +// RAVEN BEGIN +// bdube: fixed warning + int i; +// RAVEN END + float *length, totalLength, scale, t; + + length = (float *) _alloca16( values.Num() * sizeof( float ) ); + totalLength = 0.0f; + for ( i = 0; i < values.Num() - 1; i++ ) { + length[i] = GetLengthBetweenKnots( i, i + 1 ); + totalLength += length[i]; + } + scale = totalTime / totalLength; + for ( t = 0.0f, i = 0; i < times.Num() - 1; i++ ) { + times[i] = t; + t += scale * length[i]; + } + times[times.Num() - 1] = totalTime; + changed = true; +} + +/* +==================== +idCurve::ShiftTime +==================== +*/ +template< class type > +ID_INLINE void idCurve::ShiftTime( const float deltaTime ) { + for ( int i = 0; i < times.Num(); i++ ) { + times[i] += deltaTime; + } + changed = true; +} + +/* +==================== +idCurve::Translate +==================== +*/ +template< class type > +ID_INLINE void idCurve::Translate( const type &translation ) { + for ( int i = 0; i < values.Num(); i++ ) { + values[i] += translation; + } + changed = true; +} + +/* +==================== +idCurve::IndexForTime + + find the index for the first time greater than or equal to the given time +==================== +*/ +template< class type > +ID_INLINE int idCurve::IndexForTime( const float time ) const { + int len, mid, offset, res; + + if ( currentIndex >= 0 && currentIndex <= times.Num() ) { + // use the cached index if it is still valid + if ( currentIndex == 0 ) { + if ( time <= times[currentIndex] ) { + return currentIndex; + } + } else if ( currentIndex == times.Num() ) { + if ( time > times[currentIndex-1] ) { + return currentIndex; + } + } else if ( time > times[currentIndex-1] && time <= times[currentIndex] ) { + return currentIndex; + } else if ( time > times[currentIndex] && ( currentIndex+1 == times.Num() || time <= times[currentIndex+1] ) ) { + // use the next index + currentIndex++; + return currentIndex; + } + } + + // use binary search to find the index for the given time + len = times.Num(); + mid = len; + offset = 0; + res = 0; + while( mid > 0 ) { + mid = len >> 1; + if ( time == times[offset+mid] ) { + return offset+mid; + } else if ( time > times[offset+mid] ) { + offset += mid; + len -= mid; + res = 1; + } else { + len -= mid; + res = 0; + } + } + currentIndex = offset+res; + return currentIndex; +} + +/* +==================== +idCurve::ValueForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve::ValueForIndex( const int index ) const { + int n = values.Num()-1; + + if ( index < 0 ) { + return values[0] + index * ( values[1] - values[0] ); + } else if ( index > n ) { + return values[n] + ( index - n ) * ( values[n] - values[n-1] ); + } + return values[index]; +} + +/* +==================== +idCurve::TimeForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE float idCurve::TimeForIndex( const int index ) const { + int n = times.Num()-1; + + if ( index < 0 ) { + return times[0] + index * ( times[1] - times[0] ); + } else if ( index > n ) { + return times[n] + ( index - n ) * ( times[n] - times[n-1] ); + } + return times[index]; +} + +// RAVEN BEGIN +// ddynerman: spline welding +/* +==================== +idCurve::Weld + + Weld splines, implementation specific - parent version does nothing +==================== +*/ +template< class type > +ID_INLINE bool idCurve::Weld( idCurve* c ) const { + return false; +} +// RAVEN END + + +/* +=============================================================================== + + Bezier Curve template. + The degree of the polynomial equals the number of knots minus one. + +=============================================================================== +*/ + +template< class type > +class idCurve_Bezier : public idCurve { +public: + idCurve_Bezier( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int order, const float t, float *bvals ) const; + void BasisFirstDerivative( const int order, const float t, float *bvals ) const; + void BasisSecondDerivative( const int order, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_Bezier::idCurve_Bezier +==================== +*/ +template< class type > +ID_INLINE idCurve_Bezier::idCurve_Bezier( void ) { +} + +/* +==================== +idCurve_Bezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentValue( const float time ) const { + int i; + float *bvals; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + Basis( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + return v; +} + +/* +==================== +idCurve_Bezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentFirstDerivative( const float time ) const { + int i; + float *bvals, d; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + BasisFirstDerivative( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + d = ( this->times[this->times.Num()-1] - this->times[0] ); + return ( (float) (this->values.Num()-1) / d ) * v; +} + +/* +==================== +idCurve_Bezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Bezier::GetCurrentSecondDerivative( const float time ) const { + int i; + float *bvals, d; + type v; + + bvals = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + + BasisSecondDerivative( this->values.Num(), time, bvals ); + v = bvals[0] * this->values[0]; + for ( i = 1; i < this->values.Num(); i++ ) { + v += bvals[i] * this->values[i]; + } + d = ( this->times[this->times.Num()-1] - this->times[0] ); + return ( (float) (this->values.Num()-2) * (this->values.Num()-1) / ( d * d ) ) * v; +} + +/* +==================== +idCurve_Bezier::Basis + + bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::Basis( const int order, const float t, float *bvals ) const { + int i, j, d; + float *c, c1, c2, s, o, ps, po; + + bvals[0] = 1.0f; + d = order - 1; + if ( d <= 0 ) { + return; + } + + c = (float *) _alloca16( (d+1) * sizeof( float ) ); + s = (float) ( t - this->times[0] ) / ( this->times[this->times.Num()-1] - this->times[0] ); + o = 1.0f - s; + ps = s; + po = o; + + for ( i = 1; i < d; i++ ) { + c[i] = 1.0f; + } + for ( i = 1; i < d; i++ ) { + c[i-1] = 0.0f; + c1 = c[i]; + c[i] = 1.0f; + for ( j = i+1; j <= d; j++ ) { + c2 = c[j]; + c[j] = c1 + c[j-1]; + c1 = c2; + } + bvals[i] = c[d] * ps; + ps *= s; + } + for ( i = d-1; i >= 0; i-- ) { + bvals[i] *= po; + po *= o; + } + bvals[d] = ps; +} + +/* +==================== +idCurve_Bezier::BasisFirstDerivative + + first derivative of bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::BasisFirstDerivative( const int order, const float t, float *bvals ) const { + int i; + + Basis( order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + } +} + +/* +==================== +idCurve_Bezier::BasisSecondDerivative + + second derivative of bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_Bezier::BasisSecondDerivative( const int order, const float t, float *bvals ) const { + int i; + + BasisFirstDerivative( order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + } +} + + +/* +=============================================================================== + + Quadratic Bezier Curve template. + Should always have exactly three knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_QuadraticBezier : public idCurve { +public: + idCurve_QuadraticBezier( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const float t, float *bvals ) const; + void BasisFirstDerivative( const float t, float *bvals ) const; + void BasisSecondDerivative( const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_QuadraticBezier::idCurve_QuadraticBezier +==================== +*/ +template< class type > +ID_INLINE idCurve_QuadraticBezier::idCurve_QuadraticBezier( void ) { +} + + +/* +==================== +idCurve_QuadraticBezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentValue( const float time ) const { + float bvals[3]; + assert( this->values.Num() == 3 ); + Basis( time, bvals ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ); +} + +/* +==================== +idCurve_QuadraticBezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentFirstDerivative( const float time ) const { + float bvals[3], d; + assert( this->values.Num() == 3 ); + BasisFirstDerivative( time, bvals ); + d = ( this->times[2] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ) / d; +} + +/* +==================== +idCurve_QuadraticBezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_QuadraticBezier::GetCurrentSecondDerivative( const float time ) const { + float bvals[3], d; + assert( this->values.Num() == 3 ); + BasisSecondDerivative( time, bvals ); + d = ( this->times[2] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] ) / ( d * d ); +} + +/* +==================== +idCurve_QuadraticBezier::Basis + + quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::Basis( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + float s2 = s1 * s1; + bvals[0] = s2 - 2.0f * s1 + 1.0f; + bvals[1] = -2.0f * s2 + 2.0f * s1; + bvals[2] = s2; +} + +/* +==================== +idCurve_QuadraticBezier::BasisFirstDerivative + + first derivative of quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::BasisFirstDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + bvals[0] = 2.0f * s1 - 2.0f; + bvals[1] = -4.0f * s1 + 2.0f; + bvals[2] = 2.0f * s1; +} + +/* +==================== +idCurve_QuadraticBezier::BasisSecondDerivative + + second derivative of quadratic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_QuadraticBezier::BasisSecondDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[2] - this->times[0] ); + bvals[0] = 2.0f; + bvals[1] = -4.0f; + bvals[2] = 2.0f; +} + + +/* +=============================================================================== + + Cubic Bezier Curve template. + Should always have exactly four knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_CubicBezier : public idCurve { +public: + idCurve_CubicBezier( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const float t, float *bvals ) const; + void BasisFirstDerivative( const float t, float *bvals ) const; + void BasisSecondDerivative( const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_CubicBezier::idCurve_CubicBezier +==================== +*/ +template< class type > +ID_INLINE idCurve_CubicBezier::idCurve_CubicBezier( void ) { +} + + +/* +==================== +idCurve_CubicBezier::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentValue( const float time ) const { + float bvals[4]; + assert( this->values.Num() == 4 ); + Basis( time, bvals ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ); +} + +/* +==================== +idCurve_CubicBezier::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentFirstDerivative( const float time ) const { + float bvals[4], d; + assert( this->values.Num() == 4 ); + BasisFirstDerivative( time, bvals ); + d = ( this->times[3] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ) / d; +} + +/* +==================== +idCurve_CubicBezier::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CubicBezier::GetCurrentSecondDerivative( const float time ) const { + float bvals[4], d; + assert( this->values.Num() == 4 ); + BasisSecondDerivative( time, bvals ); + d = ( this->times[3] - this->times[0] ); + return ( bvals[0] * this->values[0] + bvals[1] * this->values[1] + bvals[2] * this->values[2] + bvals[3] * this->values[3] ) / ( d * d ); +} + +/* +==================== +idCurve_CubicBezier::Basis + + cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::Basis( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + float s2 = s1 * s1; + float s3 = s2 * s1; + bvals[0] = -s3 + 3.0f * s2 - 3.0f * s1 + 1.0f; + bvals[1] = 3.0f * s3 - 6.0f * s2 + 3.0f * s1; + bvals[2] = -3.0f * s3 + 3.0f * s2; + bvals[3] = s3; +} + +/* +==================== +idCurve_CubicBezier::BasisFirstDerivative + + first derivative of cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::BasisFirstDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + float s2 = s1 * s1; + bvals[0] = -3.0f * s2 + 6.0f * s1 - 3.0f; + bvals[1] = 9.0f * s2 - 12.0f * s1 + 3.0f; + bvals[2] = -9.0f * s2 + 6.0f * s1; + bvals[3] = 3.0f * s2; +} + +/* +==================== +idCurve_CubicBezier::BasisSecondDerivative + + second derivative of cubic bezier basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CubicBezier::BasisSecondDerivative( const float t, float *bvals ) const { + float s1 = (float) ( t - this->times[0] ) / ( this->times[3] - this->times[0] ); + bvals[0] = -6.0f * s1 + 6.0f; + bvals[1] = 18.0f * s1 - 12.0f; + bvals[2] = -18.0f * s1 + 6.0f; + bvals[3] = 6.0f * s1; +} + + +/* +=============================================================================== + + Spline base template. + +=============================================================================== +*/ + +template< class type > +class idCurve_Spline : public idCurve { +public: + enum boundary_t { BT_FREE, BT_CLAMPED, BT_CLOSED }; + + idCurve_Spline( void ); + + virtual bool IsDone( const float time ) const; + + virtual void SetBoundaryType( const boundary_t bt ) { boundaryType = bt; this->changed = true; } + virtual boundary_t GetBoundaryType( void ) const { return boundaryType; } + + virtual void SetCloseTime( const float t ) { closeTime = t; this->changed = true; } +// RAVEN BEGIN +// jsinger: this->changed to be const so that we can call it during binary serialization + virtual float GetCloseTime( void ) const { return boundaryType == BT_CLOSED ? closeTime : 0.0f; } +// RAVEN END + +protected: + boundary_t boundaryType; + float closeTime; + + type ValueForIndex( const int index ) const; + float TimeForIndex( const int index ) const; + float ClampedTime( const float t ) const; +}; + +/* +==================== +idCurve_Spline::idCurve_Spline +==================== +*/ +template< class type > +ID_INLINE idCurve_Spline::idCurve_Spline( void ) { + boundaryType = BT_FREE; + closeTime = 0.0f; +} + +/* +==================== +idCurve_Spline::ValueForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_Spline::ValueForIndex( const int index ) const { + int n = this->values.Num()-1; + + if ( index < 0 ) { + if ( boundaryType == BT_CLOSED ) { + return this->values[ this->values.Num() + index % this->values.Num() ]; + } + else { + return this->values[0] + (float)index * ( this->values[1] - this->values[0] ); + } + } + else if ( index > n ) { + if ( boundaryType == BT_CLOSED ) { + return this->values[ index % this->values.Num() ]; + } + else { + return this->values[n] + (float)( index - n ) * ( this->values[n] - this->values[n-1] ); + } + } + return this->values[index]; +} + +/* +==================== +idCurve_Spline::TimeForIndex + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE float idCurve_Spline::TimeForIndex( const int index ) const { + int n = this->times.Num()-1; + + if ( index < 0 ) { + if ( boundaryType == BT_CLOSED ) { + return ( index / this->times.Num() ) * ( this->times[n] + closeTime ) - ( this->times[n] + closeTime - this->times[this->times.Num() + index % this->times.Num()] ); + } + else { + return this->times[0] + index * ( this->times[1] - this->times[0] ); + } + } + else if ( index > n ) { + if ( boundaryType == BT_CLOSED ) { + return ( index / this->times.Num() ) * ( this->times[n] + closeTime ) + this->times[index % this->times.Num()]; + } + else { + return this->times[n] + ( index - n ) * ( this->times[n] - this->times[n-1] ); + } + } + return this->times[index]; +} + +/* +==================== +idCurve_Spline::ClampedTime + + return the clamped time based on the boundary type +==================== +*/ +template< class type > +ID_INLINE float idCurve_Spline::ClampedTime( const float t ) const { + if ( boundaryType == BT_CLAMPED ) { + if ( t < this->times[0] ) { + return this->times[0]; + } + else if ( t >= this->times[this->times.Num()-1] ) { + return this->times[this->times.Num()-1]; + } + } + return t; +} + +/* +==================== +idCurve_Spline::IsDone +==================== +*/ +template< class type > +ID_INLINE bool idCurve_Spline::IsDone( const float time ) const { + return ( boundaryType != BT_CLOSED && time >= this->times[ this->times.Num() - 1 ] ); +} + + +/* +=============================================================================== + + Cubic Interpolating Spline template. + The curve goes through all the knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_NaturalCubicSpline : public idCurve_Spline { +public: + idCurve_NaturalCubicSpline( void ); + + virtual void Clear( void ) { idCurve_Spline::Clear(); this->values.Clear(); b.Clear(); c.Clear(); d.Clear(); } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + mutable idListb; + mutable idListc; + mutable idListd; + + void Setup( void ) const; + void SetupFree( void ) const; + void SetupClamped( void ) const; + void SetupClosed( void ) const; +}; + +/* +==================== +idCurve_NaturalCubicSpline::idCurve_NaturalCubicSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_NaturalCubicSpline::idCurve_NaturalCubicSpline( void ) { +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentValue( const float time ) const { + float clampedTime = idCurve_Spline::ClampedTime( time ); + int i = idCurve::IndexForTime( clampedTime ); + float s = time - idCurve_Spline::TimeForIndex( i ); + Setup(); + return ( this->values[i] + s * ( b[i] + s * ( c[i] + s * d[i] ) ) ); +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentFirstDerivative( const float time ) const { + float clampedTime = idCurve_Spline::ClampedTime( time ); + int i = idCurve::IndexForTime( clampedTime ); + float s = time - idCurve_Spline::TimeForIndex( i ); + Setup(); + return ( b[i] + s * ( 2.0f * c[i] + 3.0f * s * d[i] ) ); +} + +/* +==================== +idCurve_NaturalCubicSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NaturalCubicSpline::GetCurrentSecondDerivative( const float time ) const { + float clampedTime = idCurve_Spline::ClampedTime( time ); + int i = idCurve::IndexForTime( clampedTime ); + float s = time - idCurve_Spline::TimeForIndex( i ); + Setup(); + return ( 2.0f * c[i] + 6.0f * s * d[i] ); +} + +/* +==================== +idCurve_NaturalCubicSpline::Setup +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::Setup( void ) const { + if ( this->changed ) { + switch( this->boundaryType ) { + case idCurve_Spline::BT_FREE: SetupFree(); break; + case idCurve_Spline::BT_CLAMPED: SetupClamped(); break; + case idCurve_Spline::BT_CLOSED: SetupClosed(); break; + } + this->changed = false; + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupFree +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupFree( void ) const { + int i; + float inv; + float *d0, *d1, *beta, *gamma; + type *alpha, *delta; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + d1 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + alpha = (type *) _alloca16( ( this->values.Num() - 1 ) * sizeof( type ) ); + beta = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + gamma = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + delta = (type *) _alloca16( this->values.Num() * sizeof( type ) ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + d1[i] = this->times[i+1] - this->times[i-1]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + type sum = 3.0f * ( d0[i-1] * this->values[i+1] - d1[i] * this->values[i] + d0[i] * this->values[i-1] ); + inv = 1.0f / ( d0[i-1] * d0[i] ); + alpha[i] = inv * sum; + } + + beta[0] = 1.0f; + gamma[0] = 0.0f; + delta[0] = this->values[0] - this->values[0]; + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + beta[i] = 2.0f * d1[i] - d0[i-1] * gamma[i-1]; + inv = 1.0f / beta[i]; + gamma[i] = inv * d0[i]; + delta[i] = inv * ( alpha[i] - d0[i-1] * delta[i-1] ); + } + beta[this->values.Num() - 1] = 1.0f; + delta[this->values.Num() - 1] = this->values[0] - this->values[0]; + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + c[this->values.Num() - 1] = this->values[0] - this->values[0]; + + for ( i = this->values.Num() - 2; i >= 0; i-- ) { + c[i] = delta[i] - gamma[i] * c[i+1]; + inv = 1.0f / d0[i]; + b[i] = inv * ( this->values[i+1] - this->values[i] ) - ( 1.0f / 3.0f ) * d0[i] * ( c[i+1] + 2.0f * c[i] ); + d[i] = ( 1.0f / 3.0f ) * inv * ( c[i+1] - c[i] ); + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupClamped +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupClamped( void ) const { + int i; + float inv; + float *d0, *d1, *beta, *gamma; + type *alpha, *delta; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + d1 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + alpha = (type *) _alloca16( ( this->values.Num() - 1 ) * sizeof( type ) ); + beta = (float *) _alloca16( this->values.Num() * sizeof( float ) ); + gamma = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + delta = (type *) _alloca16( this->values.Num() * sizeof( type ) ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + d1[i] = this->times[i+1] - this->times[i-1]; + } + + inv = 1.0f / d0[0]; + alpha[0] = 3.0f * ( inv - 1.0f ) * ( this->values[1] - this->values[0] ); + inv = 1.0f / d0[this->values.Num() - 2]; + alpha[this->values.Num() - 1] = 3.0f * ( 1.0f - inv ) * ( this->values[this->values.Num() - 1] - this->values[this->values.Num() - 2] ); + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + type sum = 3.0f * ( d0[i-1] * this->values[i+1] - d1[i] * this->values[i] + d0[i] * this->values[i-1] ); + inv = 1.0f / ( d0[i-1] * d0[i] ); + alpha[i] = inv * sum; + } + + beta[0] = 2.0f * d0[0]; + gamma[0] = 0.5f; + inv = 1.0f / beta[0]; + delta[0] = inv * alpha[0]; + + for ( i = 1; i < this->values.Num() - 1; i++ ) { + beta[i] = 2.0f * d1[i] - d0[i-1] * gamma[i-1]; + inv = 1.0f / beta[i]; + gamma[i] = inv * d0[i]; + delta[i] = inv * ( alpha[i] - d0[i-1] * delta[i-1] ); + } + + beta[this->values.Num() - 1] = d0[this->values.Num() - 2] * ( 2.0f - gamma[this->values.Num() - 2] ); + inv = 1.0f / beta[this->values.Num() - 1]; + delta[this->values.Num() - 1] = inv * ( alpha[this->values.Num() - 1] - d0[this->values.Num() - 2] * delta[this->values.Num() - 2] ); + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + c[this->values.Num() - 1] = delta[this->values.Num() - 1]; + + for ( i = this->values.Num() - 2; i >= 0; i-- ) { + c[i] = delta[i] - gamma[i] * c[i+1]; + inv = 1.0f / d0[i]; + b[i] = inv * ( this->values[i+1] - this->values[i] ) - ( 1.0f / 3.0f ) * d0[i]* ( c[i+1] + 2.0f * c[i] ); + d[i] = ( 1.0f / 3.0f ) * inv * ( c[i+1] - c[i] ); + } +} + +/* +==================== +idCurve_NaturalCubicSpline::SetupClosed +==================== +*/ +template< class type > +ID_INLINE void idCurve_NaturalCubicSpline::SetupClosed( void ) const { + int i, j; + float c0, c1; + float *d0; + idMatX mat; + idVecX x; + + d0 = (float *) _alloca16( ( this->values.Num() - 1 ) * sizeof( float ) ); + x.SetData( this->values.Num(), VECX_ALLOCA( this->values.Num() ) ); + mat.SetData( this->values.Num(), this->values.Num(), MATX_ALLOCA( this->values.Num() * this->values.Num() ) ); + + b.AssureSize( this->values.Num() ); + c.AssureSize( this->values.Num() ); + d.AssureSize( this->values.Num() ); + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + d0[i] = this->times[i+1] - this->times[i]; + } + + // matrix of system + mat[0][0] = 1.0f; + mat[0][this->values.Num() - 1] = -1.0f; + for ( i = 1; i <= this->values.Num() - 2; i++ ) { + mat[i][i-1] = d0[i-1]; + mat[i][i ] = 2.0f * ( d0[i-1] + d0[i] ); + mat[i][i+1] = d0[i]; + } + mat[this->values.Num() - 1][this->values.Num() - 2] = d0[this->values.Num() - 2]; + mat[this->values.Num() - 1][0] = 2.0f * ( d0[this->values.Num() - 2] + d0[0] ); + mat[this->values.Num() - 1][1] = d0[0]; + + // right-hand side + c[0].Zero(); + for ( i = 1; i <= this->values.Num() - 2; i++ ) { + c0 = 1.0f / d0[i]; + c1 = 1.0f / d0[i-1]; + c[i] = 3.0f * ( c0 * ( this->values[i + 1] - this->values[i] ) - c1 * ( this->values[i] - this->values[i - 1] ) ); + } + c0 = 1.0f / d0[0]; + c1 = 1.0f / d0[this->values.Num() - 2]; + c[this->values.Num() - 1] = 3.0f * ( c0 * ( this->values[1] - this->values[0] ) - c1 * ( this->values[0] - this->values[this->values.Num() - 2] ) ); + + // solve system for each dimension + mat.LU_Factor( NULL ); + for ( i = 0; i < this->values[0].GetDimension(); i++ ) { + for ( j = 0; j < this->values.Num(); j++ ) { + x[j] = c[j][i]; + } + mat.LU_Solve( x, x, NULL ); + for ( j = 0; j < this->values.Num(); j++ ) { + c[j][i] = x[j]; + } + } + + for ( i = 0; i < this->values.Num() - 1; i++ ) { + c0 = 1.0f / d0[i]; + b[i] = c0 * ( this->values[i + 1] - this->values[i] ) - ( 1.0f / 3.0f ) * ( c[i+1] + 2.0f * c[i] ) * d0[i]; + d[i] = ( 1.0f / 3.0f ) * c0 * ( c[i + 1] - c[i] ); + } +} + + +/* +=============================================================================== + + Uniform Cubic Interpolating Spline template. + The curve goes through all the knots. + +=============================================================================== +*/ + +template< class type > +class idCurve_CatmullRomSpline : public idCurve_Spline { +public: + idCurve_CatmullRomSpline( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_CatmullRomSpline::idCurve_CatmullRomSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_CatmullRomSpline::idCurve_CatmullRomSpline( void ) { +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float bvals[4], clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_CatmullRomSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_CatmullRomSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_CatmullRomSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( ( -s + 2.0f ) * s - 1.0f ) * s * 0.5f; // -0.5f s * s * s + s * s - 0.5f * s + bvals[1] = ( ( ( 3.0f * s - 5.0f ) * s ) * s + 2.0f ) * 0.5f; // 1.5f * s * s * s - 2.5f * s * s + 1.0f + bvals[2] = ( ( -3.0f * s + 4.0f ) * s + 1.0f ) * s * 0.5f; // -1.5f * s * s * s - 2.0f * s * s + 0.5f s + bvals[3] = ( ( s - 1.0f ) * s * s ) * 0.5f; // 0.5f * s * s * s - 0.5f * s * s +} + +/* +==================== +idCurve_CatmullRomSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( -1.5f * s + 2.0f ) * s - 0.5f; // -1.5f * s * s + 2.0f * s - 0.5f + bvals[1] = ( 4.5f * s - 5.0f ) * s; // 4.5f * s * s - 5.0f * s + bvals[2] = ( -4.5 * s + 4.0f ) * s + 0.5f; // -4.5 * s * s + 4.0f * s + 0.5f + bvals[3] = 1.5f * s * s - s; // 1.5f * s * s - s +} + +/* +==================== +idCurve_CatmullRomSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_CatmullRomSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = -3.0f * s + 2.0f; + bvals[1] = 9.0f * s - 5.0f; + bvals[2] = -9.0f * s + 4.0f; + bvals[3] = 3.0f * s - 1.0f; +} + + +/* +=============================================================================== + + Cubic Interpolating Spline template. + The curve goes through all the knots. + The curve becomes the Catmull-Rom spline if the tension, + continuity and bias are all set to zero. + +=============================================================================== +*/ + +template< class type > +class idCurve_KochanekBartelsSpline : public idCurve_Spline { +public: + idCurve_KochanekBartelsSpline( void ); + + virtual int AddValue( const float time, const type &value ); + virtual int AddValue( const float time, const type &value, const float tension, const float continuity, const float bias ); + virtual void RemoveIndex( const int index ) { this->values.RemoveIndex(index); this->times.RemoveIndex(index); tension.RemoveIndex(index); continuity.RemoveIndex(index); bias.RemoveIndex(index); } + virtual void Clear( void ) { this->values.Clear(); this->times.Clear(); tension.Clear(); continuity.Clear(); bias.Clear(); this->currentIndex = -1; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + idList tension; + idList continuity; + idList bias; + + void TangentsForIndex( const int index, type &t0, type &t1 ) const; + + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_KochanekBartelsSpline::idCurve_KochanekBartelsSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_KochanekBartelsSpline::idCurve_KochanekBartelsSpline( void ) { +} + +/* +==================== +idCurve_KochanekBartelsSpline::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_KochanekBartelsSpline::AddValue( const float time, const type &value ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + tension.Insert( 0.0f, i ); + continuity.Insert( 0.0f, i ); + bias.Insert( 0.0f, i ); + return i; +} + +/* +==================== +idCurve_KochanekBartelsSpline::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_KochanekBartelsSpline::AddValue( const float time, const type &value, const float tension, const float continuity, const float bias ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + this->tension.Insert( tension, i ); + this->continuity.Insert( continuity, i ); + this->bias.Insert( bias, i ); + return i; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentValue( const float time ) const { + int i; + float bvals[4], clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + Basis( i - 1, clampedTime, bvals ); + v = bvals[0] * idCurve_Spline::ValueForIndex( i - 1 ); + v += bvals[1] * idCurve_Spline::ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + return v; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentFirstDerivative( const float time ) const { + int i; + float bvals[4], d, clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + BasisFirstDerivative( i - 1, clampedTime, bvals ); + v = bvals[0] * idCurve_Spline::ValueForIndex( i - 1 ); + v += bvals[1] * idCurve_Spline::ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_KochanekBartelsSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_KochanekBartelsSpline::GetCurrentSecondDerivative( const float time ) const { + int i; + float bvals[4], d, clampedTime; + type v, t0, t1; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + TangentsForIndex( i - 1, t0, t1 ); + BasisSecondDerivative( i - 1, clampedTime, bvals ); + v = bvals[0] * idCurve_Spline::ValueForIndex( i - 1 ); + v += bvals[1] * idCurve_Spline::ValueForIndex( i ); + v += bvals[2] * t0; + v += bvals[3] * t1; + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_KochanekBartelsSpline::TangentsForIndex +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::TangentsForIndex( const int index, type &t0, type &t1 ) const { + float dt, omt, omc, opc, omb, opb, adj, s0, s1; + type delta; + + delta = idCurve_Spline::ValueForIndex( index + 1 ) - idCurve_Spline::ValueForIndex( index ); + dt = idCurve_Spline::TimeForIndex( index + 1 ) - idCurve_Spline::TimeForIndex( index ); + + omt = 1.0f - tension[index]; + omc = 1.0f - continuity[index]; + opc = 1.0f + continuity[index]; + omb = 1.0f - bias[index]; + opb = 1.0f + bias[index]; + adj = 2.0f * dt / ( idCurve_Spline::TimeForIndex( index + 1 ) - idCurve_Spline::TimeForIndex( index - 1 ) ); + s0 = 0.5f * adj * omt * opc * opb; + s1 = 0.5f * adj * omt * omc * omb; + + // outgoing tangent at first point + t0 = s1 * delta + s0 * ( idCurve_Spline::ValueForIndex( index ) - idCurve_Spline::ValueForIndex( index - 1 ) ); + + omt = 1.0f - tension[index + 1]; + omc = 1.0f - continuity[index + 1]; + opc = 1.0f + continuity[index + 1]; + omb = 1.0f - bias[index + 1]; + opb = 1.0f + bias[index + 1]; + adj = 2.0f * dt / ( idCurve_Spline::TimeForIndex( index + 2 ) - idCurve_Spline::TimeForIndex( index ) ); + s0 = 0.5f * adj * omt * omc * opb; + s1 = 0.5f * adj * omt * opc * omb; + + // incoming tangent at second point + t1 = s1 * ( idCurve_Spline::ValueForIndex( index + 2 ) - idCurve_Spline::ValueForIndex( index + 1 ) ) + s0 * delta; +} + +/* +==================== +idCurve_KochanekBartelsSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( ( 2.0f * s - 3.0f ) * s ) * s + 1.0f; // 2.0f * s * s * s - 3.0f * s * s + 1.0f + bvals[1] = ( ( -2.0f * s + 3.0f ) * s ) * s; // -2.0f * s * s * s + 3.0f * s * s + bvals[2] = ( ( s - 2.0f ) * s ) * s + s; // s * s * s - 2.0f * s * s + s + bvals[3] = ( ( s - 1.0f ) * s ) * s; // s * s * s - s * s +} + +/* +==================== +idCurve_KochanekBartelsSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( 6.0f * s - 6.0f ) * s; // 6.0f * s * s - 6.0f * s + bvals[1] = ( -6.0f * s + 6.0f ) * s; // -6.0f * s * s + 6.0f * s + bvals[2] = ( 3.0f * s - 4.0f ) * s + 1.0f; // 3.0f * s * s - 4.0f * s + 1.0f + bvals[3] = ( 3.0f * s - 2.0f ) * s; // 3.0f * s * s - 2.0f * s +} + +/* +==================== +idCurve_KochanekBartelsSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_KochanekBartelsSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = 12.0f * s - 6.0f; + bvals[1] = -12.0f * s + 6.0f; + bvals[2] = 6.0f * s - 4.0f; + bvals[3] = 6.0f * s - 2.0f; +} + + +/* +=============================================================================== + + B-Spline base template. Uses recursive definition and is slow. + Use idCurve_UniformCubicBSpline or idCurve_NonUniformBSpline instead. + +=============================================================================== +*/ + +template< class type > +class idCurve_BSpline : public idCurve_Spline { +public: + idCurve_BSpline( void ); + + virtual int GetOrder( void ) const { return order; } + virtual void SetOrder( const int i ) { assert( i > 0 && i < 10 ); order = i; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + int order; + + float Basis( const int index, const int order, const float t ) const; + float BasisFirstDerivative( const int index, const int order, const float t ) const; + float BasisSecondDerivative( const int index, const int order, const float t ) const; +}; + +/* +==================== +idCurve_BSpline::idCurve_NaturalCubicSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_BSpline::idCurve_BSpline( void ) { + order = 4; // default to cubic +} + +/* +==================== +idCurve_BSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += Basis( k-2, order, clampedTime ) * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += BasisFirstDerivative( k-2, order, clampedTime ) * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_BSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < order; j++ ) { + k = i + j - ( order >> 1 ); + v += BasisSecondDerivative( k-2, order, clampedTime ) * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_BSpline::Basis + + spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::Basis( const int index, const int order, const float t ) const { + if ( order <= 1 ) { + if ( idCurve_Spline::TimeForIndex( index ) < t && t <= idCurve_Spline::TimeForIndex( index + 1 ) ) { + return 1.0f; + } else { + return 0.0f; + } + } else { + float sum = 0.0f; + float d1 = idCurve_Spline::TimeForIndex( index+order-1 ) - idCurve_Spline::TimeForIndex( index ); + if ( d1 != 0.0f ) { + sum += (float) ( t - idCurve_Spline::TimeForIndex( index ) ) * Basis( index, order-1, t ) / d1; + } + + float d2 = idCurve_Spline::TimeForIndex( index+order ) - idCurve_Spline::TimeForIndex( index+1 ); + if ( d2 != 0.0f ) { + sum += (float) ( idCurve_Spline::TimeForIndex( index+order ) - t ) * Basis( index+1, order-1, t ) / d2; + } + return sum; + } +} + +/* +==================== +idCurve_BSpline::BasisFirstDerivative + + first derivative of spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::BasisFirstDerivative( const int index, const int order, const float t ) const { + return ( Basis( index, order-1, t ) - Basis( index+1, order-1, t ) ) * + (float) ( order - 1 ) / ( idCurve_Spline::TimeForIndex( index + ( order - 1 ) - 2 ) - idCurve_Spline::TimeForIndex( index - 2 ) ); +} + +/* +==================== +idCurve_BSpline::BasisSecondDerivative + + second derivative of spline basis function +==================== +*/ +template< class type > +ID_INLINE float idCurve_BSpline::BasisSecondDerivative( const int index, const int order, const float t ) const { + return ( BasisFirstDerivative( index, order-1, t ) - BasisFirstDerivative( index+1, order-1, t ) ) * + (float) ( order - 1 ) / ( idCurve_Spline::TimeForIndex( index + ( order - 1 ) - 2 ) - idCurve_Spline::TimeForIndex( index - 2 ) ); +} + + +/* +=============================================================================== + + Uniform Non-Rational Cubic B-Spline template. + +=============================================================================== +*/ + +template< class type > +class idCurve_UniformCubicBSpline : public idCurve_BSpline { +public: + idCurve_UniformCubicBSpline( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + void Basis( const int index, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_UniformCubicBSpline::idCurve_UniformCubicBSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_UniformCubicBSpline::idCurve_UniformCubicBSpline( void ) { + this->order = 4; // always cubic +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float bvals[4], clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / d; +} + +/* +==================== +idCurve_UniformCubicBSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_UniformCubicBSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float bvals[4], d, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < 4; j++ ) { + k = i + j - 2; + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + d = ( idCurve_Spline::TimeForIndex( i ) - idCurve_Spline::TimeForIndex( i-1 ) ); + return v / ( d * d ); +} + +/* +==================== +idCurve_UniformCubicBSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::Basis( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = ( ( ( -s + 3.0f ) * s - 3.0f ) * s + 1.0f ) * ( 1.0f / 6.0f ); + bvals[1] = ( ( ( 3.0f * s - 6.0f ) * s ) * s + 4.0f ) * ( 1.0f / 6.0f ); + bvals[2] = ( ( ( -3.0f * s + 3.0f ) * s + 3.0f ) * s + 1.0f ) * ( 1.0f / 6.0f ); + bvals[3] = ( s * s * s ) * ( 1.0f / 6.0f ); +} + +/* +==================== +idCurve_UniformCubicBSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::BasisFirstDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = -0.5f * s * s + s - 0.5f; + bvals[1] = 1.5f * s * s - 2.0f * s; + bvals[2] = -1.5f * s * s + s + 0.5f; + bvals[3] = 0.5f * s * s; +} + +/* +==================== +idCurve_UniformCubicBSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_UniformCubicBSpline::BasisSecondDerivative( const int index, const float t, float *bvals ) const { + float s = (float) ( t - idCurve_Spline::TimeForIndex( index ) ) / ( idCurve_Spline::TimeForIndex( index+1 ) - idCurve_Spline::TimeForIndex( index ) ); + bvals[0] = -s + 1.0f; + bvals[1] = 3.0f * s - 2.0f; + bvals[2] = -3.0f * s + 1.0f; + bvals[3] = s; +} + + +/* +=============================================================================== + + Non-Uniform Non-Rational B-Spline (NUBS) template. + +=============================================================================== +*/ + +template< class type > +class idCurve_NonUniformBSpline : public idCurve_BSpline { +public: + idCurve_NonUniformBSpline( void ); + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; +// RAVEN BEGIN +// ddynerman: spline welding + virtual bool Weld( idCurve* c ) const; +// RAVEN END + +protected: + void Basis( const int index, const int order, const float t, float *bvals ) const; + void BasisFirstDerivative( const int index, const int order, const float t, float *bvals ) const; + void BasisSecondDerivative( const int index, const int order, const float t, float *bvals ) const; +}; + +/* +==================== +idCurve_NonUniformBSpline::idCurve_NonUniformBSpline +==================== +*/ +template< class type > +ID_INLINE idCurve_NonUniformBSpline::idCurve_NonUniformBSpline( void ) { +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentValue( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisFirstDerivative( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NonUniformBSpline::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float clampedTime; + type v; + float *bvals = (float *) _alloca16( this->order * sizeof(float) ); + + if ( this->times.Num() == 1 ) { + return ( this->values[0] - this->values[0] ); + } + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + BasisSecondDerivative( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + v += bvals[j] * idCurve_Spline::ValueForIndex( k ); + } + return v; +} + +/* +==================== +idCurve_NonUniformBSpline::Basis + + spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::Basis( const int index, const int order, const float t, float *bvals ) const { + int r, s, i; + float omega; + + bvals[order-1] = 1.0f; + for ( r = 2; r <= order; r++ ) { + i = index - r + 1; + bvals[order - r] = 0.0f; + for ( s = order - r + 1; s < order; s++ ) { + i++; + omega = (float) ( t - idCurve_Spline::TimeForIndex( i ) ) / ( idCurve_Spline::TimeForIndex( i + r - 1 ) - idCurve_Spline::TimeForIndex( i ) ); + bvals[s - 1] += ( 1.0f - omega ) * bvals[s]; + bvals[s] *= omega; + } + } +} + +/* +==================== +idCurve_NonUniformBSpline::BasisFirstDerivative + + first derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::BasisFirstDerivative( const int index, const int order, const float t, float *bvals ) const { + int i; + + Basis( index, order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); + } + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); +} + +/* +==================== +idCurve_NonUniformBSpline::BasisSecondDerivative + + second derivative of spline basis functions +==================== +*/ +template< class type > +ID_INLINE void idCurve_NonUniformBSpline::BasisSecondDerivative( const int index, const int order, const float t, float *bvals ) const { + int i; + + BasisFirstDerivative( index, order-1, t, bvals+1 ); + bvals[0] = 0.0f; + for ( i = 0; i < order-1; i++ ) { + bvals[i] -= bvals[i+1]; + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); + } + bvals[i] *= (float) ( order - 1) / ( idCurve_Spline::TimeForIndex( index + i + (order-1) - 2 ) - idCurve_Spline::TimeForIndex( index + i - 2 ) ); +} + +// RAVEN BEGIN +// ddynerman: spline welding +/* +==================== +idCurve_NonUniformBSpline::Weld + + Attach two B-Splines together +==================== +*/ +template< class type > +ID_INLINE bool idCurve_NonUniformBSpline::Weld( idCurve* c ) const { + idCurve_NonUniformBSpline* spline = dynamic_cast*>(c); + + if( spline == NULL ) { + return false; + } + + type refLine = this->values[ this->values.Num() - 1 ] - this->values[ this->values.Num() - 2 ]; + float length = (spline->values[ 0 ] - spline->values[ 1 ]).Length(); + + bool valuesChanged = spline->values[ 0 ] != this->values[ this->values.Num() - 1 ]; + spline->values[ 0 ] = this->values[ this->values.Num() - 1 ]; + + type deltaPos = ~refLine * length; + type newValue = spline->values[ 0 ] + deltaPos; + + valuesChanged = (spline->values[ 1 ] != newValue) || valuesChanged; + + spline->values[ 1 ] = newValue; + + return valuesChanged; +} +// RAVEN END + +/* +=============================================================================== + + Non-Uniform Rational B-Spline (NURBS) template. + +=============================================================================== +*/ + +template< class type > +class idCurve_NURBS : public idCurve_NonUniformBSpline { +public: + idCurve_NURBS( void ); + + virtual int AddValue( const float time, const type &value ); + virtual int AddValue( const float time, const type &value, const float weight ); + virtual void RemoveIndex( const int index ) { this->values.RemoveIndex(index); this->times.RemoveIndex(index); weights.RemoveIndex(index); } + virtual void Clear( void ) { this->values.Clear(); this->times.Clear(); weights.Clear(); this->currentIndex = -1; } + + virtual type GetCurrentValue( const float time ) const; + virtual type GetCurrentFirstDerivative( const float time ) const; + virtual type GetCurrentSecondDerivative( const float time ) const; + +protected: + idList weights; + + float WeightForIndex( const int index ) const; +}; + +/* +==================== +idCurve_NURBS::idCurve_NURBS +==================== +*/ +template< class type > +ID_INLINE idCurve_NURBS::idCurve_NURBS( void ) { +} + +/* +==================== +idCurve_NURBS::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_NURBS::AddValue( const float time, const type &value ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + weights.Insert( 1.0f, i ); + return i; +} + +/* +==================== +idCurve_NURBS::AddValue + + add a timed/value pair to the spline + returns the index to the inserted pair +==================== +*/ +template< class type > +ID_INLINE int idCurve_NURBS::AddValue( const float time, const type &value, const float weight ) { + int i; + + i = idCurve::IndexForTime( time ); + this->times.Insert( time, i ); + this->values.Insert( value, i ); + weights.Insert( weight, i ); + return i; +} + +/* +==================== +idCurve_NURBS::GetCurrentValue + + get the value for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentValue( const float time ) const { + int i, j, k; + float w, b, *bvals, clampedTime; + type v; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + v = this->values[0] - this->values[0]; + w = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + b = bvals[j] * WeightForIndex( k ); + w += b; + v += b * idCurve_Spline::ValueForIndex( k ); + } + return v / w; +} + +/* +==================== +idCurve_NURBS::GetCurrentFirstDerivative + + get the first derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentFirstDerivative( const float time ) const { + int i, j, k; + float w, wb, wd1, b, d1, *bvals, *d1vals, clampedTime; + type v, vb, vd1; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + d1vals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + BasisFirstDerivative( i-1, this->order, clampedTime, d1vals ); + vb = vd1 = this->values[0] - this->values[0]; + wb = wd1 = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + w = WeightForIndex( k ); + b = bvals[j] * w; + d1 = d1vals[j] * w; + wb += b; + wd1 += d1; + v = idCurve_Spline::ValueForIndex( k ); + vb += b * v; + vd1 += d1 * v; + } + return ( wb * vd1 - vb * wd1 ) / ( wb * wb ); +} + +/* +==================== +idCurve_NURBS::GetCurrentSecondDerivative + + get the second derivative for the given time +==================== +*/ +template< class type > +ID_INLINE type idCurve_NURBS::GetCurrentSecondDerivative( const float time ) const { + int i, j, k; + float w, wb, wd1, wd2, b, d1, d2, *bvals, *d1vals, *d2vals, clampedTime; + type v, vb, vd1, vd2; + + if ( this->times.Num() == 1 ) { + return this->values[0]; + } + + bvals = (float *) _alloca16( this->order * sizeof(float) ); + d1vals = (float *) _alloca16( this->order * sizeof(float) ); + d2vals = (float *) _alloca16( this->order * sizeof(float) ); + + clampedTime = idCurve_Spline::ClampedTime( time ); + i = idCurve::IndexForTime( clampedTime ); + Basis( i-1, this->order, clampedTime, bvals ); + BasisFirstDerivative( i-1, this->order, clampedTime, d1vals ); + BasisSecondDerivative( i-1, this->order, clampedTime, d2vals ); + vb = vd1 = vd2 = this->values[0] - this->values[0]; + wb = wd1 = wd2 = 0.0f; + for ( j = 0; j < this->order; j++ ) { + k = i + j - ( this->order >> 1 ); + w = WeightForIndex( k ); + b = bvals[j] * w; + d1 = d1vals[j] * w; + d2 = d2vals[j] * w; + wb += b; + wd1 += d1; + wd2 += d2; + v = idCurve_Spline::ValueForIndex( k ); + vb += b * v; + vd1 += d1 * v; + vd2 += d2 * v; + } + return ( ( wb * wb ) * ( wb * vd2 - vb * wd2 ) - ( wb * vd1 - vb * wd1 ) * 2.0f * wb * wd1 ) / ( wb * wb * wb * wb ); +} + +/* +==================== +idCurve_NURBS::WeightForIndex + + get the weight for the given index +==================== +*/ +template< class type > +ID_INLINE float idCurve_NURBS::WeightForIndex( const int index ) const { + int n = weights.Num()-1; + + if ( index < 0 ) { + if ( this->boundaryType == idCurve_Spline::BT_CLOSED ) { + return weights[ weights.Num() + index % weights.Num() ]; + } else { + return weights[0] + index * ( weights[1] - weights[0] ); + } + } else if ( index > n ) { + if ( this->boundaryType == idCurve_Spline::BT_CLOSED ) { + return weights[ index % weights.Num() ]; + } else { + return weights[n] + ( index - n ) * ( weights[n] - weights[n-1] ); + } + } + return weights[index]; +} + +#endif /* !__MATH_CURVE_H__ */ diff --git a/src/idlib/math/Extrapolate.h b/src/idlib/math/Extrapolate.h new file mode 100644 index 0000000..9b4b758 --- /dev/null +++ b/src/idlib/math/Extrapolate.h @@ -0,0 +1,237 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_EXTRAPOLATE_H__ +#define __MATH_EXTRAPOLATE_H__ + +/* +============================================================================================== + + Extrapolation + +============================================================================================== +*/ + +typedef enum { + EXTRAPOLATION_NONE = 0x01, // no extrapolation, covered distance = duration * 0.001 * ( baseSpeed ) + EXTRAPOLATION_LINEAR = 0x02, // linear extrapolation, covered distance = duration * 0.001 * ( baseSpeed + speed ) + EXTRAPOLATION_ACCELLINEAR = 0x04, // linear acceleration, covered distance = duration * 0.001 * ( baseSpeed + 0.5 * speed ) + EXTRAPOLATION_DECELLINEAR = 0x08, // linear deceleration, covered distance = duration * 0.001 * ( baseSpeed + 0.5 * speed ) + EXTRAPOLATION_ACCELSINE = 0x10, // sinusoidal acceleration, covered distance = duration * 0.001 * ( baseSpeed + sqrt( 0.5 ) * speed ) + EXTRAPOLATION_DECELSINE = 0x20, // sinusoidal deceleration, covered distance = duration * 0.001 * ( baseSpeed + sqrt( 0.5 ) * speed ) + EXTRAPOLATION_NOSTOP = 0x40 // do not stop at startTime + duration +} extrapolation_t; + +template< class type > +class idExtrapolate { +public: + idExtrapolate(); + + void Init( const float startTime, const float duration, const type &startValue, const type &baseSpeed, const type &speed, const extrapolation_t extrapolationType ); + type GetCurrentValue( float time ) const; + type GetCurrentSpeed( float time ) const; + bool IsDone( float time ) const { return ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && time >= startTime + duration ); } + void SetStartTime( float time ) { startTime = time; currentTime = -1; } + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && duration > 0 ) ? startTime + duration : 0; } + float GetDuration( void ) const { return duration; } + void SetStartValue( const type &value ) { startValue = value; currentTime = -1; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetBaseSpeed( void ) const { return baseSpeed; } + const type & GetSpeed( void ) const { return speed; } + extrapolation_t GetExtrapolationType( void ) const { return extrapolationType; } + +private: + extrapolation_t extrapolationType; + float startTime; + float duration; + type startValue; + type baseSpeed; + type speed; + mutable float currentTime; + mutable type currentValue; +}; + +/* +==================== +idExtrapolate::idExtrapolate +==================== +*/ +template< class type > +ID_INLINE idExtrapolate::idExtrapolate() { + extrapolationType = EXTRAPOLATION_NONE; + startTime = duration = 0.0f; + memset( &startValue, 0, sizeof( startValue ) ); + memset( &baseSpeed, 0, sizeof( baseSpeed ) ); + memset( &speed, 0, sizeof( speed ) ); + currentTime = -1; + currentValue = startValue; +} + +/* +==================== +idExtrapolate::Init +==================== +*/ +template< class type > +ID_INLINE void idExtrapolate::Init( const float startTime, const float duration, const type &startValue, const type &baseSpeed, const type &speed, const extrapolation_t extrapolationType ) { + this->extrapolationType = extrapolationType; + this->startTime = startTime; + this->duration = duration; + this->startValue = startValue; + this->baseSpeed = baseSpeed; + this->speed = speed; + currentTime = -1; + currentValue = startValue; +} + +/* +==================== +idExtrapolate::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idExtrapolate::GetCurrentValue( float time ) const { + float deltaTime, s; + + if ( time == currentTime ) { + return currentValue; + } + + currentTime = time; + + if ( time < startTime ) { + return startValue; + } + + if ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && ( time > startTime + duration ) ) { + time = startTime + duration; + } + + switch( extrapolationType & ~EXTRAPOLATION_NOSTOP ) { + case EXTRAPOLATION_NONE: { + deltaTime = ( time - startTime ) * 0.001f; + currentValue = startValue + deltaTime * baseSpeed; + break; + } + case EXTRAPOLATION_LINEAR: { + deltaTime = ( time - startTime ) * 0.001f; + currentValue = startValue + deltaTime * ( baseSpeed + speed ); + break; + } + case EXTRAPOLATION_ACCELLINEAR: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = ( 0.5f * deltaTime * deltaTime ) * ( duration * 0.001f ); + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + case EXTRAPOLATION_DECELLINEAR: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = ( deltaTime - ( 0.5f * deltaTime * deltaTime ) ) * ( duration * 0.001f ); + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + case EXTRAPOLATION_ACCELSINE: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = ( 1.0f - idMath::Cos( deltaTime * idMath::HALF_PI ) ) * duration * 0.001f * idMath::SQRT_1OVER2; + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + case EXTRAPOLATION_DECELSINE: { + if ( !duration ) { + currentValue = startValue; + } else { + deltaTime = ( time - startTime ) / duration; + s = idMath::Sin( deltaTime * idMath::HALF_PI ) * duration * 0.001f * idMath::SQRT_1OVER2; + currentValue = startValue + deltaTime * baseSpeed + s * speed; + } + break; + } + } + return currentValue; +} + +/* +==================== +idExtrapolate::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idExtrapolate::GetCurrentSpeed( float time ) const { + float deltaTime, s; + + if ( time < startTime || !duration ) { + return ( startValue - startValue ); + } + + if ( !( extrapolationType & EXTRAPOLATION_NOSTOP ) && ( time > startTime + duration ) ) { + return ( startValue - startValue ); + } + + switch( extrapolationType & ~EXTRAPOLATION_NOSTOP ) { + case EXTRAPOLATION_NONE: { + return baseSpeed; + } + case EXTRAPOLATION_LINEAR: { + return baseSpeed + speed; + } + case EXTRAPOLATION_ACCELLINEAR: { + deltaTime = ( time - startTime ) / duration; + s = deltaTime; + return baseSpeed + s * speed; + } + case EXTRAPOLATION_DECELLINEAR: { + deltaTime = ( time - startTime ) / duration; + s = 1.0f - deltaTime; + return baseSpeed + s * speed; + } + case EXTRAPOLATION_ACCELSINE: { + deltaTime = ( time - startTime ) / duration; + s = idMath::Sin( deltaTime * idMath::HALF_PI ); + return baseSpeed + s * speed; + } + case EXTRAPOLATION_DECELSINE: { + deltaTime = ( time - startTime ) / duration; + s = idMath::Cos( deltaTime * idMath::HALF_PI ); + return baseSpeed + s * speed; + } + default: { + return baseSpeed; + } + } +} + +#endif /* !__MATH_EXTRAPOLATE_H__ */ diff --git a/src/idlib/math/FFT.cpp b/src/idlib/math/FFT.cpp new file mode 100644 index 0000000..788009f --- /dev/null +++ b/src/idlib/math/FFT.cpp @@ -0,0 +1,169 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +typedef struct { + double re; + double im; +} cpxDouble_t; + +/* +=================== + + "A New Principle for Fast Fourier Transformation", Charles M. Rader and N.M. Brenner, I.E.E.E. Transactions on Acoustics + "Programs for Digital Signal Processing", published by I.E.E.E. Press, chapter 1, section 1.1, 1979. + +=================== +*/ +// RAVEN BEGIN +// jscott: added stride +void idFFT::FFT1D( cpxFloat_t *data, int N, int ISI, int stride ) +{ +// RAVEN END + int i, j, m, mmax, istep; + cpxFloat_t cfTemp; + cpxDouble_t cdTemp1, cdTemp2; + double theta, dTemp; + const double pi = idMath::PI; + + // first operation puts data in bit-reversed order + j = 0; + for(i = 0; i < N; i++) { + if(i < j) { +// RAVEN BEGIN + cfTemp.re = data[j * stride].re; + cfTemp.im = data[j * stride].im; + + data[j * stride].re = data[i * stride].re; + data[j * stride].im = data[i * stride].im; + + data[i * stride].re = cfTemp.re; + data[i * stride].im = cfTemp.im; +// RAVEN END + } + m = N / 2; + while(j >= m) { + j = j - m; + m = m / 2; + if(m == 0){ + break; + } + } + j = j + m; + } + + // second operation computes the butterflies + mmax = 1; + while (mmax < N) { + istep = 2 * mmax; + theta = pi * ISI / mmax; + dTemp = idMath::Sin(theta / 2.0); + cdTemp2.re = -2.0 * dTemp * dTemp; + cdTemp2.im = idMath::Sin(theta); + cdTemp1.re = 1.0; + cdTemp1.im = 0.0; + for(m = 0; m < mmax; m++) { + for(i = m; i < N; i += istep) { + j = i + mmax; +// RAVEN BEGIN + cfTemp.re = (float)(cdTemp1.re * data[j * stride].re - cdTemp1.im * data[j * stride].im); + cfTemp.im = (float)(cdTemp1.re * data[j * stride].im + cdTemp1.im * data[j * stride].re); + data[j * stride].re = data[i * stride].re - cfTemp.re; + data[j * stride].im = data[i * stride].im - cfTemp.im; + data[i * stride].re += cfTemp.re; + data[i * stride].im += cfTemp.im; +// RAVEN END + } + dTemp = cdTemp1.re; + cdTemp1.re = cdTemp1.re * cdTemp2.re - cdTemp1.im * cdTemp2.im + cdTemp1.re; + cdTemp1.im = cdTemp1.im * cdTemp2.re + dTemp * cdTemp2.im + cdTemp1.im; + } + mmax = istep; + } +} + +// RAVEN BEGIN +// jscott: added 2d Fourier transform - cost 2N +void idFFT::FFT2D( cpxFloat_t *data, int N, int ISI ) +{ + cpxFloat_t *orig; + int i; + + orig = data; + + // 1D horizontal transform + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, 1 ); + data += N; + } + + // 1D vertical transform + data = orig; + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, N ); + data++; + } +} + +// jscott: added 3d Fourier transform - cost 3N^2 +void idFFT::FFT3D( cpxFloat_t *data, int N, int ISI ) +{ + cpxFloat_t *orig; + int i, j; + + orig = data; + + // Transform each slice + for( j = 0; j < N; j++ ) + { + // 1D transform + data = orig + j * N * N; + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, 1 ); + data += N; + } + + // 1D transform + data = orig + j * N * N; + for( i = 0; i < N; i++ ) + { + FFT1D( data, N, ISI, N ); + data++; + } + } + + // Transform the volume + data = orig; + for( j = 0; j < N * N; j++ ) + { + FFT1D( data, N, ISI, N * N ); + data++; + } +} +// RAVEN END diff --git a/src/idlib/math/FFT.h b/src/idlib/math/FFT.h new file mode 100644 index 0000000..47b5ef7 --- /dev/null +++ b/src/idlib/math/FFT.h @@ -0,0 +1,52 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_FFT_H__ +#define __MATH_FFT_H__ + +/* +=============================================================================== + + Fast Fourier Transform + +=============================================================================== +*/ + +// complex number +typedef struct { + float re; + float im; +} cpxFloat_t; + +class idFFT { +public: +// RAVEN BEGIN +// jscott: added stride to 1D, created 2D + static void FFT1D( cpxFloat_t *data, int N, int ISI, int stride = 1 ); + static void FFT2D( cpxFloat_t *data, int N, int ISI ); + static void FFT3D( cpxFloat_t *data, int N, int ISI ); +// RAVEN END +}; + +#endif /* !__MATH_FFT_H__ */ diff --git a/src/idlib/math/Interpolate.h b/src/idlib/math/Interpolate.h new file mode 100644 index 0000000..1983e53 --- /dev/null +++ b/src/idlib/math/Interpolate.h @@ -0,0 +1,490 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_INTERPOLATE_H__ +#define __MATH_INTERPOLATE_H__ + +/* +============================================================================================== + + Linear interpolation. + +============================================================================================== +*/ + +template< class type > +class idInterpolate { +public: + idInterpolate(); + virtual ~idInterpolate() { } + + void Init( const float startTime, const float duration, const type &startValue, const type &endValue ); + void SetStartTime( float time ) { this->startTime = time; } + void SetDuration( float duration ) { this->duration = duration; } + void SetStartValue( const type &startValue ) { this->startValue = startValue; } + void SetEndValue( const type &endValue ) { this->endValue = endValue; } + +// RAVEN BEGIN +// abahr: made virtual + virtual type GetCurrentValue( float time ) const; + virtual type GetDeltaValue( float startTime, float endTime ) const; +// RAVEN END + bool IsDone( float time ) const { return ( time >= startTime + duration ); } + + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return startTime + duration; } + float GetDuration( void ) const { return duration; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetEndValue( void ) const { return endValue; } + +// RAVEN BEGIN +// abahr: changed to protected +protected: +// RAVEN END + float startTime; + float duration; + type startValue; + type endValue; + mutable float currentTime; + mutable type currentValue; +}; + +/* +==================== +idInterpolate::idInterpolate +==================== +*/ +template< class type > +ID_INLINE idInterpolate::idInterpolate() { + currentTime = startTime = duration = 0; + memset( ¤tValue, 0, sizeof( currentValue ) ); + startValue = endValue = currentValue; +} + +/* +==================== +idInterpolate::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolate::Init( const float startTime, const float duration, const type &startValue, const type &endValue ) { + this->startTime = startTime; + this->duration = duration; + this->startValue = startValue; + this->endValue = endValue; + this->currentTime = startTime - 1; + this->currentValue = startValue; +} + +/* +==================== +idInterpolate::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolate::GetCurrentValue( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( time != currentTime ) { + currentTime = time; + if ( deltaTime <= 0 ) { + currentValue = startValue; + } else if ( deltaTime >= duration ) { + currentValue = endValue; + } else { + currentValue = startValue + ( endValue - startValue ) * ( (float) deltaTime / duration ); + } + } + return currentValue; +} + +// RAVEN BEGIN +// abahr +/* +==================== +idInterpolate::GetDeltaValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolate::GetDeltaValue( float startTime, float endTime ) const { + return GetCurrentValue(endTime) - GetCurrentValue(startTime); +} + +ID_INLINE float Linear( float frac ) { + return frac; +} + +ID_INLINE float SinusoidalMidPoint( float frac ) { + return idMath::Sin( DEG2RAD(idMath::MidPointLerp(0.0f, 60.0f, 90.0f, frac)) ); +} + +/* +============================================================================================== + + Spherical interpolation. + +============================================================================================== +*/ +typedef float (*TimeManipFunc) ( float ); +class rvSphericalInterpolate : public idInterpolate { +public: + rvSphericalInterpolate(); + virtual idQuat GetCurrentValue( float time ) const; + + void SetTimeFunction( TimeManipFunc func ) { timeFunc = func; } + +protected: + TimeManipFunc timeFunc; +}; + +/* +==================== +rvSphericalInterpolate::rvSphericalInterpolate +==================== +*/ +ID_INLINE rvSphericalInterpolate::rvSphericalInterpolate() : + idInterpolate() { + SetTimeFunction( SinusoidalMidPoint ); +} + +/* +==================== +rvSphericalInterpolate::GetCurrentValue +==================== +*/ +ID_INLINE idQuat rvSphericalInterpolate::GetCurrentValue( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( time != currentTime ) { + currentTime = time; + if( duration == 0.0f ) { + currentValue = endValue; + } else { + currentValue.Slerp( startValue, endValue, timeFunc((float)deltaTime / duration) ); + } + } + return currentValue; +} +// RAVEN END + +/* +============================================================================================== + + Continuous interpolation with linear acceleration and deceleration phase. + The velocity is continuous but the acceleration is not. + +============================================================================================== +*/ + +template< class type > +class idInterpolateAccelDecelLinear { +public: + idInterpolateAccelDecelLinear(); + + void Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ); + void SetStartTime( float time ) { startTime = time; Invalidate(); } + void SetStartValue( const type &startValue ) { this->startValue = startValue; Invalidate(); } + void SetEndValue( const type &endValue ) { this->endValue = endValue; Invalidate(); } + + type GetCurrentValue( float time ) const; + type GetCurrentSpeed( float time ) const; + bool IsDone( float time ) const { return ( time >= startTime + accelTime + linearTime + decelTime ); } + + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return startTime + accelTime + linearTime + decelTime; } + float GetDuration( void ) const { return accelTime + linearTime + decelTime; } + float GetAcceleration( void ) const { return accelTime; } + float GetDeceleration( void ) const { return decelTime; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetEndValue( void ) const { return endValue; } + +private: + float startTime; + float accelTime; + float linearTime; + float decelTime; + type startValue; + type endValue; + mutable idExtrapolate extrapolate; + + void Invalidate( void ); + void SetPhase( float time ) const; +}; + +/* +==================== +idInterpolateAccelDecelLinear::idInterpolateAccelDecelLinear +==================== +*/ +template< class type > +ID_INLINE idInterpolateAccelDecelLinear::idInterpolateAccelDecelLinear() { + startTime = accelTime = linearTime = decelTime = 0; + memset( &startValue, 0, sizeof( startValue ) ); + endValue = startValue; +} + +/* +==================== +idInterpolateAccelDecelLinear::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ) { + type speed; + + this->startTime = startTime; + this->accelTime = accelTime; + this->decelTime = decelTime; + this->startValue = startValue; + this->endValue = endValue; + + if ( duration <= 0.0f ) { + return; + } + + if ( this->accelTime + this->decelTime > duration ) { + this->accelTime = this->accelTime * duration / ( this->accelTime + this->decelTime ); + this->decelTime = duration - this->accelTime; + } + this->linearTime = duration - this->accelTime - this->decelTime; + speed = ( endValue - startValue ) * ( 1000.0f / ( (float) this->linearTime + ( this->accelTime + this->decelTime ) * 0.5f ) ); + + if ( this->accelTime ) { + extrapolate.Init( startTime, this->accelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_ACCELLINEAR ); + } else if ( this->linearTime ) { + extrapolate.Init( startTime, this->linearTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_LINEAR ); + } else { + extrapolate.Init( startTime, this->decelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_DECELLINEAR ); + } +} + +/* +==================== +idInterpolateAccelDecelLinear::Invalidate +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::Invalidate( void ) { + extrapolate.Init( 0, 0, extrapolate.GetStartValue(), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_NONE ); +} + +/* +==================== +idInterpolateAccelDecelLinear::SetPhase +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelLinear::SetPhase( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( deltaTime < accelTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_ACCELLINEAR ) { + extrapolate.Init( startTime, accelTime, startValue, extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_ACCELLINEAR ); + } + } else if ( deltaTime < accelTime + linearTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_LINEAR ) { + extrapolate.Init( startTime + accelTime, linearTime, startValue + extrapolate.GetSpeed() * ( accelTime * 0.001f * 0.5f ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_LINEAR ); + } + } else { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_DECELLINEAR ) { + extrapolate.Init( startTime + accelTime + linearTime, decelTime, endValue - ( extrapolate.GetSpeed() * ( decelTime * 0.001f * 0.5f ) ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_DECELLINEAR ); + } + } +} + +/* +==================== +idInterpolateAccelDecelLinear::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelLinear::GetCurrentValue( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentValue( time ); +} + +/* +==================== +idInterpolateAccelDecelLinear::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelLinear::GetCurrentSpeed( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentSpeed( time ); +} + + +/* +============================================================================================== + + Continuous interpolation with sinusoidal acceleration and deceleration phase. + Both the velocity and acceleration are continuous. + +============================================================================================== +*/ + +template< class type > +class idInterpolateAccelDecelSine { +public: + idInterpolateAccelDecelSine(); + + void Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ); + void SetStartTime( float time ) { startTime = time; Invalidate(); } + void SetStartValue( const type &startValue ) { this->startValue = startValue; Invalidate(); } + void SetEndValue( const type &endValue ) { this->endValue = endValue; Invalidate(); } + + type GetCurrentValue( float time ) const; + type GetCurrentSpeed( float time ) const; + bool IsDone( float time ) const { return ( time >= startTime + accelTime + linearTime + decelTime ); } + + float GetStartTime( void ) const { return startTime; } + float GetEndTime( void ) const { return startTime + accelTime + linearTime + decelTime; } + float GetDuration( void ) const { return accelTime + linearTime + decelTime; } + float GetAcceleration( void ) const { return accelTime; } + float GetDeceleration( void ) const { return decelTime; } + const type & GetStartValue( void ) const { return startValue; } + const type & GetEndValue( void ) const { return endValue; } + +private: + float startTime; + float accelTime; + float linearTime; + float decelTime; + type startValue; + type endValue; + mutable idExtrapolate extrapolate; + + void Invalidate( void ); + void SetPhase( float time ) const; +}; + +/* +==================== +idInterpolateAccelDecelSine::idInterpolateAccelDecelSine +==================== +*/ +template< class type > +ID_INLINE idInterpolateAccelDecelSine::idInterpolateAccelDecelSine() { + startTime = accelTime = linearTime = decelTime = 0; + memset( &startValue, 0, sizeof( startValue ) ); + endValue = startValue; +} + +/* +==================== +idInterpolateAccelDecelSine::Init +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::Init( const float startTime, const float accelTime, const float decelTime, const float duration, const type &startValue, const type &endValue ) { + type speed; + + this->startTime = startTime; + this->accelTime = accelTime; + this->decelTime = decelTime; + this->startValue = startValue; + this->endValue = endValue; + + if ( duration <= 0.0f ) { + return; + } + + if ( this->accelTime + this->decelTime > duration ) { + this->accelTime = this->accelTime * duration / ( this->accelTime + this->decelTime ); + this->decelTime = duration - this->accelTime; + } + this->linearTime = duration - this->accelTime - this->decelTime; + speed = ( endValue - startValue ) * ( 1000.0f / ( (float) this->linearTime + ( this->accelTime + this->decelTime ) * idMath::SQRT_1OVER2 ) ); + + if ( this->accelTime ) { + extrapolate.Init( startTime, this->accelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_ACCELSINE ); + } else if ( this->linearTime ) { + extrapolate.Init( startTime, this->linearTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_LINEAR ); + } else { + extrapolate.Init( startTime, this->decelTime, startValue, ( startValue - startValue ), speed, EXTRAPOLATION_DECELSINE ); + } +} + +/* +==================== +idInterpolateAccelDecelSine::Invalidate +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::Invalidate( void ) { + extrapolate.Init( 0, 0, extrapolate.GetStartValue(), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_NONE ); +} + +/* +==================== +idInterpolateAccelDecelSine::SetPhase +==================== +*/ +template< class type > +ID_INLINE void idInterpolateAccelDecelSine::SetPhase( float time ) const { + float deltaTime; + + deltaTime = time - startTime; + if ( deltaTime < accelTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_ACCELSINE ) { + extrapolate.Init( startTime, accelTime, startValue, extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_ACCELSINE ); + } + } else if ( deltaTime < accelTime + linearTime ) { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_LINEAR ) { + extrapolate.Init( startTime + accelTime, linearTime, startValue + extrapolate.GetSpeed() * ( accelTime * 0.001f * idMath::SQRT_1OVER2 ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_LINEAR ); + } + } else { + if ( extrapolate.GetExtrapolationType() != EXTRAPOLATION_DECELSINE ) { + extrapolate.Init( startTime + accelTime + linearTime, decelTime, endValue - ( extrapolate.GetSpeed() * ( decelTime * 0.001f * idMath::SQRT_1OVER2 ) ), extrapolate.GetBaseSpeed(), extrapolate.GetSpeed(), EXTRAPOLATION_DECELSINE ); + } + } +} + +/* +==================== +idInterpolateAccelDecelSine::GetCurrentValue +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelSine::GetCurrentValue( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentValue( time ); +} + +/* +==================== +idInterpolateAccelDecelSine::GetCurrentSpeed +==================== +*/ +template< class type > +ID_INLINE type idInterpolateAccelDecelSine::GetCurrentSpeed( float time ) const { + SetPhase( time ); + return extrapolate.GetCurrentSpeed( time ); +} + +#endif /* !__MATH_INTERPOLATE_H__ */ diff --git a/src/idlib/math/Lcp.cpp b/src/idlib/math/Lcp.cpp new file mode 100644 index 0000000..4786051 --- /dev/null +++ b/src/idlib/math/Lcp.cpp @@ -0,0 +1,1640 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +static idCVar lcp_showFailures( "lcp_showFailures", "0", CVAR_SYSTEM | CVAR_BOOL, "show LCP solver failures" ); + +const float LCP_BOUND_EPSILON = 1e-5f; +const float LCP_ACCEL_EPSILON = 1e-5f; +const float LCP_DELTA_ACCEL_EPSILON = 1e-9f; +const float LCP_DELTA_FORCE_EPSILON = 1e-9f; + +#define IGNORE_UNSATISFIABLE_VARIABLES + +//=============================================================== +// M +// idLCP_Square MrE +// E +//=============================================================== + +class idLCP_Square : public idLCP { +public: + virtual bool Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ); + +private: + idMatX m; // original matrix + idVecX b; // right hand side + idVecX lo, hi; // low and high bounds + idVecX f, a; // force and acceleration + idVecX delta_f, delta_a; // delta force and delta acceleration + idMatX clamped; // LU factored sub matrix for clamped variables + idVecX diagonal; // reciprocal of diagonal of U of the LU factored sub matrix for clamped variables + int numUnbounded; // number of unbounded variables + int numClamped; // number of clamped variables + float ** rowPtrs; // pointers to the rows of m + int * boxIndex; // box index + int * side; // tells if a variable is at the low boundary = -1, high boundary = 1 or inbetween = 0 + int * permuted; // index to keep track of the permutation + bool padded; // set to true if the rows of the initial matrix are 16 byte padded + +private: + bool FactorClamped( void ); + void SolveClamped( idVecX &x, const float *b ); + void Swap( int i, int j ); + void AddClamped( int r ); + void RemoveClamped( int r ); + void CalcForceDelta( int d, float dir ); + void CalcAccelDelta( int d ); + void ChangeForce( int d, float step ); + void ChangeAccel( int d, float step ); + void GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const; +}; + +/* +============ +idLCP_Square::FactorClamped +============ +*/ +bool idLCP_Square::FactorClamped( void ) { + int i, j, k; + float s, d; + + for ( i = 0; i < numClamped; i++ ) { + memcpy( clamped[i], rowPtrs[i], numClamped * sizeof( float ) ); + } + + for ( i = 0; i < numClamped; i++ ) { + + s = idMath::Fabs( clamped[i][i] ); + + if ( s == 0.0f ) { + return false; + } + + diagonal[i] = d = 1.0f / clamped[i][i]; + for ( j = i + 1; j < numClamped; j++ ) { + clamped[j][i] *= d; + } + + for ( j = i + 1; j < numClamped; j++ ) { + d = clamped[j][i]; + for ( k = i + 1; k < numClamped; k++ ) { + clamped[j][k] -= d * clamped[i][k]; + } + } + } + + return true; +} + +/* +============ +idLCP_Square::SolveClamped +============ +*/ +void idLCP_Square::SolveClamped( idVecX &x, const float *b ) { + int i, j; + float sum; + + // solve L + for ( i = 0; i < numClamped; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= clamped[i][j] * x[j]; + } + x[i] = sum; + } + + // solve U + for ( i = numClamped - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numClamped; j++ ) { + sum -= clamped[i][j] * x[j]; + } + x[i] = sum * diagonal[i]; + } +} + +/* +============ +idLCP_Square::Swap +============ +*/ +void idLCP_Square::Swap( int i, int j ) { + + if ( i == j ) { + return; + } + + idSwap( rowPtrs[i], rowPtrs[j] ); + m.SwapColumns( i, j ); + b.SwapElements( i, j ); + lo.SwapElements( i, j ); + hi.SwapElements( i, j ); + a.SwapElements( i, j ); + f.SwapElements( i, j ); + if ( boxIndex ) { + idSwap( boxIndex[i], boxIndex[j] ); + } + idSwap( side[i], side[j] ); + idSwap( permuted[i], permuted[j] ); +} + +/* +============ +idLCP_Square::AddClamped +============ +*/ +void idLCP_Square::AddClamped( int r ) { + int i, j; + float sum; + + assert( r >= numClamped ); + + // add a row at the bottom and a column at the right of the factored + // matrix for the clamped variables + + Swap( numClamped, r ); + + // add row to L + for ( i = 0; i < numClamped; i++ ) { + sum = rowPtrs[numClamped][i]; + for ( j = 0; j < i; j++ ) { + sum -= clamped[numClamped][j] * clamped[j][i]; + } + clamped[numClamped][i] = sum * diagonal[i]; + } + + // add column to U + for ( i = 0; i <= numClamped; i++ ) { + sum = rowPtrs[i][numClamped]; + for ( j = 0; j < i; j++ ) { + sum -= clamped[i][j] * clamped[j][numClamped]; + } + clamped[i][numClamped] = sum; + } + + diagonal[numClamped] = 1.0f / clamped[numClamped][numClamped]; + + numClamped++; +} + +/* +============ +idLCP_Square::RemoveClamped +============ +*/ +void idLCP_Square::RemoveClamped( int r ) { + int i, j; + float *y0, *y1, *z0, *z1; + double diag, beta0, beta1, p0, p1, q0, q1, d; + + assert( r < numClamped ); + + numClamped--; + + // no need to swap and update the factored matrix when the last row and column are removed + if ( r == numClamped ) { + return; + } + + y0 = (float *) _alloca16( numClamped * sizeof( float ) ); + z0 = (float *) _alloca16( numClamped * sizeof( float ) ); + y1 = (float *) _alloca16( numClamped * sizeof( float ) ); + z1 = (float *) _alloca16( numClamped * sizeof( float ) ); + + // the row/column need to be subtracted from the factorization + for ( i = 0; i < numClamped; i++ ) { + y0[i] = -rowPtrs[i][r]; + } + + memset( y1, 0, numClamped * sizeof( float ) ); + y1[r] = 1.0f; + + memset( z0, 0, numClamped * sizeof( float ) ); + z0[r] = 1.0f; + + for ( i = 0; i < numClamped; i++ ) { + z1[i] = -rowPtrs[r][i]; + } + + // swap the to be removed row/column with the last row/column + Swap( r, numClamped ); + + // the swapped last row/column need to be added to the factorization + for ( i = 0; i < numClamped; i++ ) { + y0[i] += rowPtrs[i][r]; + } + + for ( i = 0; i < numClamped; i++ ) { + z1[i] += rowPtrs[r][i]; + } + z1[r] = 0.0f; + + // update the beginning of the to be updated row and column + for ( i = 0; i < r; i++ ) { + p0 = y0[i]; + beta1 = z1[i] * diagonal[i]; + + clamped[i][r] += p0; + for ( j = i+1; j < numClamped; j++ ) { + z1[j] -= beta1 * clamped[i][j]; + } + for ( j = i+1; j < numClamped; j++ ) { + y0[j] -= p0 * clamped[j][i]; + } + clamped[r][i] += beta1; + } + + // update the lower right corner starting at r,r + for ( i = r; i < numClamped; i++ ) { + diag = clamped[i][i]; + + p0 = y0[i]; + p1 = z0[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Square::RemoveClamped: updating factorization failed\n" ); + return; + } + + beta0 = p1 / diag; + + q0 = y1[i]; + q1 = z1[i]; + diag += q0 * q1; + + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Square::RemoveClamped: updating factorization failed\n" ); + return; + } + + d = 1.0f / diag; + beta1 = q1 * d; + + clamped[i][i] = diag; + diagonal[i] = d; + + for ( j = i+1; j < numClamped; j++ ) { + + d = clamped[i][j]; + + d += p0 * z0[j]; + z0[j] -= beta0 * d; + + d += q0 * z1[j]; + z1[j] -= beta1 * d; + + clamped[i][j] = d; + } + + for ( j = i+1; j < numClamped; j++ ) { + + d = clamped[j][i]; + + y0[j] -= p0 * d; + d += beta0 * y0[j]; + + y1[j] -= q0 * d; + d += beta1 * y1[j]; + + clamped[j][i] = d; + } + } + return; +} + +/* +============ +idLCP_Square::CalcForceDelta + + modifies this->delta_f +============ +*/ +ID_INLINE void idLCP_Square::CalcForceDelta( int d, float dir ) { + int i; + float *ptr; + + delta_f[d] = dir; + + if ( numClamped == 0 ) { + return; + } + + // get column d of matrix + ptr = (float *) _alloca16( numClamped * sizeof( float ) ); + for ( i = 0; i < numClamped; i++ ) { + ptr[i] = rowPtrs[i][d]; + } + + // solve force delta + SolveClamped( delta_f, ptr ); + + // flip force delta based on direction + if ( dir > 0.0f ) { + ptr = delta_f.ToFloatPtr(); + for ( i = 0; i < numClamped; i++ ) { + ptr[i] = - ptr[i]; + } + } +} + +/* +============ +idLCP_Square::CalcAccelDelta + + modifies this->delta_a and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Square::CalcAccelDelta( int d ) { + int j; + float dot; + + // only the not clamped variables, including the current variable, can have a change in acceleration + for ( j = numClamped; j <= d; j++ ) { + // only the clamped variables and the current variable have a force delta unequal zero + SIMDProcessor->Dot( dot, rowPtrs[j], delta_f.ToFloatPtr(), numClamped ); + delta_a[j] = dot + rowPtrs[j][d] * delta_f[d]; + } +} + +/* +============ +idLCP_Square::ChangeForce + + modifies this->f and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Square::ChangeForce( int d, float step ) { + // only the clamped variables and current variable have a force delta unequal zero + SIMDProcessor->MulAdd( f.ToFloatPtr(), step, delta_f.ToFloatPtr(), numClamped ); + f[d] += step * delta_f[d]; +} + +/* +============ +idLCP_Square::ChangeAccel + + modifies this->a and uses this->delta_a +============ +*/ +ID_INLINE void idLCP_Square::ChangeAccel( int d, float step ) { + // only the not clamped variables, including the current variable, can have an acceleration unequal zero + SIMDProcessor->MulAdd( a.ToFloatPtr() + numClamped, step, delta_a.ToFloatPtr() + numClamped, d - numClamped + 1 ); +} + +/* +============ +idLCP_Square::GetMaxStep +============ +*/ +void idLCP_Square::GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const { + int i; + float s; + + // default to a full step for the current variable + if ( idMath::Fabs( delta_a[d] ) > LCP_DELTA_ACCEL_EPSILON ) { + maxStep = -a[d] / delta_a[d]; + } else { + maxStep = 0.0f; + } + limit = d; + limitSide = 0; + + // test the current variable + if ( dir < 0.0f ) { + if ( lo[d] != -idMath::INFINITY ) { + s = ( lo[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = -1; + } + } + } else { + if ( hi[d] != idMath::INFINITY ) { + s = ( hi[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = 1; + } + } + } + + // test the clamped bounded variables + for ( i = numUnbounded; i < numClamped; i++ ) { + if ( delta_f[i] < -LCP_DELTA_FORCE_EPSILON ) { + // if there is a low boundary + if ( lo[i] != -idMath::INFINITY ) { + s = ( lo[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = -1; + } + } + } else if ( delta_f[i] > LCP_DELTA_FORCE_EPSILON ) { + // if there is a high boundary + if ( hi[i] != idMath::INFINITY ) { + s = ( hi[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 1; + } + } + } + } + + // test the not clamped bounded variables + for ( i = numClamped; i < d; i++ ) { + if ( side[i] == -1 ) { + if ( delta_a[i] >= -LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else if ( side[i] == 1 ) { + if ( delta_a[i] <= LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else { + continue; + } + // ignore variables for which the force is not allowed to take any substantial value + if ( lo[i] >= -LCP_BOUND_EPSILON && hi[i] <= LCP_BOUND_EPSILON ) { + continue; + } + s = -a[i] / delta_a[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 0; + } + } +} + +/* +============ +idLCP_Square::Solve +============ +*/ +bool idLCP_Square::Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ) { + int i, j, n, limit, limitSide, boxStartIndex; + float dir, maxStep, dot, s; + char *failed; + + // true when the matrix rows are 16 byte padded + padded = ((o_m.GetNumRows()+3)&~3) == o_m.GetNumColumns(); + + assert( padded || o_m.GetNumRows() == o_m.GetNumColumns() ); + assert( o_x.GetSize() == o_m.GetNumRows() ); + assert( o_b.GetSize() == o_m.GetNumRows() ); + assert( o_lo.GetSize() == o_m.GetNumRows() ); + assert( o_hi.GetSize() == o_m.GetNumRows() ); + + // allocate memory for permuted input + f.SetData( o_m.GetNumRows(), VECX_ALLOCA( o_m.GetNumRows() ) ); + a.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + b.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + lo.SetData( o_lo.GetSize(), VECX_ALLOCA( o_lo.GetSize() ) ); + hi.SetData( o_hi.GetSize(), VECX_ALLOCA( o_hi.GetSize() ) ); + if ( o_boxIndex ) { + boxIndex = (int *)_alloca16( o_x.GetSize() * sizeof( int ) ); + memcpy( boxIndex, o_boxIndex, o_x.GetSize() * sizeof( int ) ); + } else { + boxIndex = NULL; + } + + // we override the const on o_m here but on exit the matrix is unchanged + m.SetData( o_m.GetNumRows(), o_m.GetNumColumns(), const_cast(o_m[0]) ); + f.Zero(); + a.Zero(); + b = o_b; + lo = o_lo; + hi = o_hi; + + // pointers to the rows of m + rowPtrs = (float **) _alloca16( m.GetNumRows() * sizeof( float * ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + rowPtrs[i] = m[i]; + } + + // tells if a variable is at the low boundary, high boundary or inbetween + side = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + + // index to keep track of the permutation + permuted = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + permuted[i] = i; + } + + // permute input so all unbounded variables come first + numUnbounded = 0; + for ( i = 0; i < m.GetNumRows(); i++ ) { + if ( lo[i] == -idMath::INFINITY && hi[i] == idMath::INFINITY ) { + if ( numUnbounded != i ) { + Swap( numUnbounded, i ); + } + numUnbounded++; + } + } + + // permute input so all variables using the boxIndex come last + boxStartIndex = m.GetNumRows(); + if ( boxIndex ) { + for ( i = m.GetNumRows() - 1; i >= numUnbounded; i-- ) { + if ( boxIndex[i] >= 0 && ( lo[i] != -idMath::INFINITY || hi[i] != idMath::INFINITY ) ) { + boxStartIndex--; + if ( boxStartIndex != i ) { + Swap( boxStartIndex, i ); + } + } + } + } + + // sub matrix for factorization + clamped.SetData( m.GetNumRows(), m.GetNumColumns(), MATX_ALLOCA( m.GetNumRows() * m.GetNumColumns() ) ); + diagonal.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // all unbounded variables are clamped + numClamped = numUnbounded; + + // if there are unbounded variables + if ( numUnbounded ) { + + // factor and solve for unbounded variables + if ( !FactorClamped() ) { + idLib::common->Printf( "idLCP_Square::Solve: unbounded factorization failed\n" ); + return false; + } + SolveClamped( f, b.ToFloatPtr() ); + + // if there are no bounded variables we are done + if ( numUnbounded == m.GetNumRows() ) { + o_x = f; // the vector is not permuted + return true; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + int numIgnored = 0; +#endif + + // allocate for delta force and delta acceleration + delta_f.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + delta_a.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // solve for bounded variables + failed = NULL; + for ( i = numUnbounded; i < m.GetNumRows(); i++ ) { + + // once we hit the box start index we can initialize the low and high boundaries of the variables using the box index + if ( i == boxStartIndex ) { + for ( j = 0; j < boxStartIndex; j++ ) { + o_x[permuted[j]] = f[j]; + } + for ( j = boxStartIndex; j < m.GetNumRows(); j++ ) { + s = o_x[boxIndex[j]]; + if ( lo[j] != -idMath::INFINITY ) { + lo[j] = - idMath::Fabs( lo[j] * s ); + } + if ( hi[j] != idMath::INFINITY ) { + hi[j] = idMath::Fabs( hi[j] * s ); + } + } + } + + // calculate acceleration for current variable + SIMDProcessor->Dot( dot, rowPtrs[i], f.ToFloatPtr(), i ); + a[i] = dot - b[i]; + + // if already at the low boundary + if ( lo[i] >= -LCP_BOUND_EPSILON && a[i] >= -LCP_ACCEL_EPSILON ) { + side[i] = -1; + continue; + } + + // if already at the high boundary + if ( hi[i] <= LCP_BOUND_EPSILON && a[i] <= LCP_ACCEL_EPSILON ) { + side[i] = 1; + continue; + } + + // if inside the clamped region + if ( idMath::Fabs( a[i] ) <= LCP_ACCEL_EPSILON ) { + side[i] = 0; + AddClamped( i ); + continue; + } + + // drive the current variable into a valid region + for ( n = 0; n < maxIterations; n++ ) { + + // direction to move + if ( a[i] <= 0.0f ) { + dir = 1.0f; + } else { + dir = -1.0f; + } + + // calculate force delta + CalcForceDelta( i, dir ); + + // calculate acceleration delta: delta_a = m * delta_f; + CalcAccelDelta( i ); + + // maximum step we can take + GetMaxStep( i, dir, maxStep, limit, limitSide ); + + if ( maxStep <= 0.0f ) { +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + // ignore the current variable completely + lo[i] = hi[i] = 0.0f; + f[i] = 0.0f; + side[i] = -1; + numIgnored++; +#else + failed = va( "invalid step size %.4f", maxStep ); +#endif + break; + } + + // change force + ChangeForce( i, maxStep ); + + // change acceleration + ChangeAccel( i, maxStep ); + + // clamp/unclamp the variable that limited this step + side[limit] = limitSide; + switch( limitSide ) { + case 0: { + a[limit] = 0.0f; + AddClamped( limit ); + break; + } + case -1: { + f[limit] = lo[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + case 1: { + f[limit] = hi[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + } + + // if the current variable limited the step we can continue with the next variable + if ( limit == i ) { + break; + } + } + + if ( n >= maxIterations ) { + failed = va( "max iterations %d", maxIterations ); + break; + } + + if ( failed ) { + break; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + if ( numIgnored ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: %d of %d bounded variables ignored\n", numIgnored, m.GetNumRows() - numUnbounded ); + } + } +#endif + + // if failed clear remaining forces + if ( failed ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Square::Solve: %s (%d of %d bounded variables ignored)\n", failed, m.GetNumRows() - i, m.GetNumRows() - numUnbounded ); + } + for ( j = i; j < m.GetNumRows(); j++ ) { + f[j] = 0.0f; + } + } + +#if defined(_DEBUG) && 0 + if ( !failed ) { + // test whether or not the solution satisfies the complementarity conditions + for ( i = 0; i < m.GetNumRows(); i++ ) { + a[i] = -b[i]; + for ( j = 0; j < m.GetNumRows(); j++ ) { + a[i] += rowPtrs[i][j] * f[j]; + } + + if ( f[i] == lo[i] ) { + if ( lo[i] != hi[i] && a[i] < -LCP_ACCEL_EPSILON ) { + int bah1 = 1; + } + } else if ( f[i] == hi[i] ) { + if ( lo[i] != hi[i] && a[i] > LCP_ACCEL_EPSILON ) { + int bah2 = 1; + } + } else if ( f[i] < lo[i] || f[i] > hi[i] || idMath::Fabs( a[i] ) > 1.0f ) { + int bah3 = 1; + } + } + } +#endif + + // unpermute result + for ( i = 0; i < f.GetSize(); i++ ) { + o_x[permuted[i]] = f[i]; + } + + // unpermute original matrix + for ( i = 0; i < m.GetNumRows(); i++ ) { + for ( j = 0; j < m.GetNumRows(); j++ ) { + if ( permuted[j] == i ) { + break; + } + } + if ( i != j ) { + m.SwapColumns( i, j ); + idSwap( permuted[i], permuted[j] ); + } + } + + return true; +} + + +//=============================================================== +// M +// idLCP_Symmetric MrE +// E +//=============================================================== + +class idLCP_Symmetric : public idLCP { +public: + virtual bool Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ); + +private: + idMatX m; // original matrix + idVecX b; // right hand side + idVecX lo, hi; // low and high bounds + idVecX f, a; // force and acceleration + idVecX delta_f, delta_a; // delta force and delta acceleration + idMatX clamped; // LDLt factored sub matrix for clamped variables + idVecX diagonal; // reciprocal of diagonal of LDLt factored sub matrix for clamped variables + idVecX solveCache1; // intermediate result cached in SolveClamped + idVecX solveCache2; // " + int numUnbounded; // number of unbounded variables + int numClamped; // number of clamped variables + int clampedChangeStart; // lowest row/column changed in the clamped matrix during an iteration + float ** rowPtrs; // pointers to the rows of m + int * boxIndex; // box index + int * side; // tells if a variable is at the low boundary = -1, high boundary = 1 or inbetween = 0 + int * permuted; // index to keep track of the permutation + bool padded; // set to true if the rows of the initial matrix are 16 byte padded + +private: + bool FactorClamped( void ); + void SolveClamped( idVecX &x, const float *b ); + void Swap( int i, int j ); + void AddClamped( int r, bool useSolveCache ); + void RemoveClamped( int r ); + void CalcForceDelta( int d, float dir ); + void CalcAccelDelta( int d ); + void ChangeForce( int d, float step ); + void ChangeAccel( int d, float step ); + void GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const; +}; + +/* +============ +idLCP_Symmetric::FactorClamped +============ +*/ +bool idLCP_Symmetric::FactorClamped( void ) { + + clampedChangeStart = 0; + + for ( int i = 0; i < numClamped; i++ ) { + memcpy( clamped[i], rowPtrs[i], numClamped * sizeof( float ) ); + } + return SIMDProcessor->MatX_LDLTFactor( clamped, diagonal, numClamped ); +} + +/* +============ +idLCP_Symmetric::SolveClamped +============ +*/ +void idLCP_Symmetric::SolveClamped( idVecX &x, const float *b ) { + + // solve L + SIMDProcessor->MatX_LowerTriangularSolve( clamped, solveCache1.ToFloatPtr(), b, numClamped, clampedChangeStart ); + + // solve D + SIMDProcessor->Mul( solveCache2.ToFloatPtr(), solveCache1.ToFloatPtr(), diagonal.ToFloatPtr(), numClamped ); + + // solve Lt + SIMDProcessor->MatX_LowerTriangularSolveTranspose( clamped, x.ToFloatPtr(), solveCache2.ToFloatPtr(), numClamped ); + + clampedChangeStart = numClamped; +} + +/* +============ +idLCP_Symmetric::Swap +============ +*/ +void idLCP_Symmetric::Swap( int i, int j ) { + + if ( i == j ) { + return; + } + + idSwap( rowPtrs[i], rowPtrs[j] ); + m.SwapColumns( i, j ); + b.SwapElements( i, j ); + lo.SwapElements( i, j ); + hi.SwapElements( i, j ); + a.SwapElements( i, j ); + f.SwapElements( i, j ); + if ( boxIndex ) { + idSwap( boxIndex[i], boxIndex[j] ); + } + idSwap( side[i], side[j] ); + idSwap( permuted[i], permuted[j] ); +} + +/* +============ +idLCP_Symmetric::AddClamped +============ +*/ +void idLCP_Symmetric::AddClamped( int r, bool useSolveCache ) { + float d, dot; + + assert( r >= numClamped ); + + if ( numClamped < clampedChangeStart ) { + clampedChangeStart = numClamped; + } + + // add a row at the bottom and a column at the right of the factored + // matrix for the clamped variables + + Swap( numClamped, r ); + + // solve for v in L * v = rowPtr[numClamped] + if ( useSolveCache ) { + + // the lower triangular solve was cached in SolveClamped called by CalcForceDelta + memcpy( clamped[numClamped], solveCache2.ToFloatPtr(), numClamped * sizeof( float ) ); + // calculate row dot product + SIMDProcessor->Dot( dot, solveCache2.ToFloatPtr(), solveCache1.ToFloatPtr(), numClamped ); + + } else { + + float *v = (float *) _alloca16( numClamped * sizeof( float ) ); + + SIMDProcessor->MatX_LowerTriangularSolve( clamped, v, rowPtrs[numClamped], numClamped ); + // add bottom row to L + SIMDProcessor->Mul( clamped[numClamped], v, diagonal.ToFloatPtr(), numClamped ); + // calculate row dot product + SIMDProcessor->Dot( dot, clamped[numClamped], v, numClamped ); + } + + // update diagonal[numClamped] + d = rowPtrs[numClamped][numClamped] - dot; + + if ( d == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::AddClamped: updating factorization failed\n" ); + numClamped++; + return; + } + + clamped[numClamped][numClamped] = d; + diagonal[numClamped] = 1.0f / d; + + numClamped++; +} + +/* +============ +idLCP_Symmetric::RemoveClamped +============ +*/ +void idLCP_Symmetric::RemoveClamped( int r ) { + int i, j, n; + float *addSub, *original, *v, *ptr, *v1, *v2, dot; + double sum, diag, newDiag, invNewDiag, p1, p2, alpha1, alpha2, beta1, beta2; + + assert( r < numClamped ); + + if ( r < clampedChangeStart ) { + clampedChangeStart = r; + } + + numClamped--; + + // no need to swap and update the factored matrix when the last row and column are removed + if ( r == numClamped ) { + return; + } + + // swap the to be removed row/column with the last row/column + Swap( r, numClamped ); + + // update the factored matrix + addSub = (float *) _alloca16( numClamped * sizeof( float ) ); + + if ( r == 0 ) { + + if ( numClamped == 1 ) { + diag = rowPtrs[0][0]; + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + clamped[0][0] = diag; + diagonal[0] = 1.0f / diag; + return; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + original = rowPtrs[numClamped]; + ptr = rowPtrs[r]; + addSub[0] = ptr[0] - original[numClamped]; + for ( i = 1; i < numClamped; i++ ) { + addSub[i] = ptr[i] - original[i]; + } + + } else { + + v = (float *) _alloca16( numClamped * sizeof( float ) ); + + // solve for v in L * v = rowPtr[r] + SIMDProcessor->MatX_LowerTriangularSolve( clamped, v, rowPtrs[r], r ); + + // update removed row + SIMDProcessor->Mul( clamped[r], v, diagonal.ToFloatPtr(), r ); + + // if the last row/column of the matrix is updated + if ( r == numClamped - 1 ) { + // only calculate new diagonal + SIMDProcessor->Dot( dot, clamped[r], v, r ); + diag = rowPtrs[r][r] - dot; + if ( diag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + clamped[r][r] = diag; + diagonal[r] = 1.0f / diag; + return; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = 0; i < r; i++ ) { + v[i] = clamped[r][i] * clamped[i][i]; + } + for ( i = r; i < numClamped; i++ ) { + if ( i == r ) { + sum = clamped[r][r]; + } else { + sum = clamped[r][r] * clamped[i][r]; + } + ptr = clamped[i]; + for ( j = 0; j < r; j++ ) { + sum += ptr[j] * v[j]; + } + addSub[i] = rowPtrs[r][i] - sum; + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + + v1 = (float *) _alloca16( numClamped * sizeof( float ) ); + v2 = (float *) _alloca16( numClamped * sizeof( float ) ); + + diag = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * diag; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * diag; + for ( i = r+1; i < numClamped; i++ ) { + v1[i] = v2[i] = addSub[i] * diag; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + n = clamped.GetNumColumns(); + for ( i = r; i < numClamped; i++ ) { + + diag = clamped[i][i]; + p1 = v1[i]; + newDiag = diag + alpha1 * p1 * p1; + + if ( newDiag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + + alpha1 /= newDiag; + beta1 = p1 * alpha1; + alpha1 *= diag; + + diag = newDiag; + p2 = v2[i]; + newDiag = diag + alpha2 * p2 * p2; + + if ( newDiag == 0.0f ) { + idLib::common->Printf( "idLCP_Symmetric::RemoveClamped: updating factorization failed\n" ); + return; + } + + clamped[i][i] = newDiag; + diagonal[i] = invNewDiag = 1.0f / newDiag; + + alpha2 *= invNewDiag; + beta2 = p2 * alpha2; + alpha2 *= diag; + + // update column below diagonal (i,i) + ptr = clamped.ToFloatPtr() + i; + + for ( j = i+1; j < numClamped - 1; j += 2 ) { + + float sum0 = ptr[(j+0)*n]; + float sum1 = ptr[(j+1)*n]; + + v1[j+0] -= p1 * sum0; + v1[j+1] -= p1 * sum1; + + sum0 += beta1 * v1[j+0]; + sum1 += beta1 * v1[j+1]; + + v2[j+0] -= p2 * sum0; + v2[j+1] -= p2 * sum1; + + sum0 += beta2 * v2[j+0]; + sum1 += beta2 * v2[j+1]; + + ptr[(j+0)*n] = sum0; + ptr[(j+1)*n] = sum1; + } + + for ( ; j < numClamped; j++ ) { + + sum = ptr[j*n]; + + v1[j] -= p1 * sum; + sum += beta1 * v1[j]; + + v2[j] -= p2 * sum; + sum += beta2 * v2[j]; + + ptr[j*n] = sum; + } + } +} + +/* +============ +idLCP_Symmetric::CalcForceDelta + + modifies this->delta_f +============ +*/ +ID_INLINE void idLCP_Symmetric::CalcForceDelta( int d, float dir ) { + int i; + float *ptr; + + delta_f[d] = dir; + + if ( numClamped == 0 ) { + return; + } + + // solve force delta + SolveClamped( delta_f, rowPtrs[d] ); + + // flip force delta based on direction + if ( dir > 0.0f ) { + ptr = delta_f.ToFloatPtr(); + for ( i = 0; i < numClamped; i++ ) { + ptr[i] = - ptr[i]; + } + } +} + +/* +============ +idLCP_Symmetric::CalcAccelDelta + + modifies this->delta_a and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Symmetric::CalcAccelDelta( int d ) { + int j; + float dot; + + // only the not clamped variables, including the current variable, can have a change in acceleration + for ( j = numClamped; j <= d; j++ ) { + // only the clamped variables and the current variable have a force delta unequal zero + SIMDProcessor->Dot( dot, rowPtrs[j], delta_f.ToFloatPtr(), numClamped ); + delta_a[j] = dot + rowPtrs[j][d] * delta_f[d]; + } +} + +/* +============ +idLCP_Symmetric::ChangeForce + + modifies this->f and uses this->delta_f +============ +*/ +ID_INLINE void idLCP_Symmetric::ChangeForce( int d, float step ) { + // only the clamped variables and current variable have a force delta unequal zero + SIMDProcessor->MulAdd( f.ToFloatPtr(), step, delta_f.ToFloatPtr(), numClamped ); + f[d] += step * delta_f[d]; +} + +/* +============ +idLCP_Symmetric::ChangeAccel + + modifies this->a and uses this->delta_a +============ +*/ +ID_INLINE void idLCP_Symmetric::ChangeAccel( int d, float step ) { + // only the not clamped variables, including the current variable, can have an acceleration unequal zero + SIMDProcessor->MulAdd( a.ToFloatPtr() + numClamped, step, delta_a.ToFloatPtr() + numClamped, d - numClamped + 1 ); +} + +/* +============ +idLCP_Symmetric::GetMaxStep +============ +*/ +void idLCP_Symmetric::GetMaxStep( int d, float dir, float &maxStep, int &limit, int &limitSide ) const { + int i; + float s; + + // default to a full step for the current variable + if ( idMath::Fabs( delta_a[d] ) > LCP_DELTA_ACCEL_EPSILON ) { + maxStep = -a[d] / delta_a[d]; + } else { + maxStep = 0.0f; + } + limit = d; + limitSide = 0; + + // test the current variable + if ( dir < 0.0f ) { + if ( lo[d] != -idMath::INFINITY ) { + s = ( lo[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = -1; + } + } + } else { + if ( hi[d] != idMath::INFINITY ) { + s = ( hi[d] - f[d] ) / dir; + if ( s < maxStep ) { + maxStep = s; + limitSide = 1; + } + } + } + + // test the clamped bounded variables + for ( i = numUnbounded; i < numClamped; i++ ) { + if ( delta_f[i] < -LCP_DELTA_FORCE_EPSILON ) { + // if there is a low boundary + if ( lo[i] != -idMath::INFINITY ) { + s = ( lo[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = -1; + } + } + } else if ( delta_f[i] > LCP_DELTA_FORCE_EPSILON ) { + // if there is a high boundary + if ( hi[i] != idMath::INFINITY ) { + s = ( hi[i] - f[i] ) / delta_f[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 1; + } + } + } + } + + // test the not clamped bounded variables + for ( i = numClamped; i < d; i++ ) { + if ( side[i] == -1 ) { + if ( delta_a[i] >= -LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else if ( side[i] == 1 ) { + if ( delta_a[i] <= LCP_DELTA_ACCEL_EPSILON ) { + continue; + } + } else { + continue; + } + // ignore variables for which the force is not allowed to take any substantial value + if ( lo[i] >= -LCP_BOUND_EPSILON && hi[i] <= LCP_BOUND_EPSILON ) { + continue; + } + s = -a[i] / delta_a[i]; + if ( s < maxStep ) { + maxStep = s; + limit = i; + limitSide = 0; + } + } +} + +/* +============ +idLCP_Symmetric::Solve +============ +*/ +bool idLCP_Symmetric::Solve( const idMatX &o_m, idVecX &o_x, const idVecX &o_b, const idVecX &o_lo, const idVecX &o_hi, const int *o_boxIndex ) { + int i, j, n, limit, limitSide, boxStartIndex; + float dir, maxStep, dot, s; + char *failed; + + // true when the matrix rows are 16 byte padded + padded = ((o_m.GetNumRows()+3)&~3) == o_m.GetNumColumns(); + + assert( padded || o_m.GetNumRows() == o_m.GetNumColumns() ); + assert( o_x.GetSize() == o_m.GetNumRows() ); + assert( o_b.GetSize() == o_m.GetNumRows() ); + assert( o_lo.GetSize() == o_m.GetNumRows() ); + assert( o_hi.GetSize() == o_m.GetNumRows() ); + + // allocate memory for permuted input + f.SetData( o_m.GetNumRows(), VECX_ALLOCA( o_m.GetNumRows() ) ); + a.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + b.SetData( o_b.GetSize(), VECX_ALLOCA( o_b.GetSize() ) ); + lo.SetData( o_lo.GetSize(), VECX_ALLOCA( o_lo.GetSize() ) ); + hi.SetData( o_hi.GetSize(), VECX_ALLOCA( o_hi.GetSize() ) ); + if ( o_boxIndex ) { + boxIndex = (int *)_alloca16( o_x.GetSize() * sizeof( int ) ); + memcpy( boxIndex, o_boxIndex, o_x.GetSize() * sizeof( int ) ); + } else { + boxIndex = NULL; + } + + // we override the const on o_m here but on exit the matrix is unchanged + m.SetData( o_m.GetNumRows(), o_m.GetNumColumns(), const_cast(o_m[0]) ); + f.Zero(); + a.Zero(); + b = o_b; + lo = o_lo; + hi = o_hi; + + // pointers to the rows of m + rowPtrs = (float **) _alloca16( m.GetNumRows() * sizeof( float * ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + rowPtrs[i] = m[i]; + } + + // tells if a variable is at the low boundary, high boundary or inbetween + side = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + + // index to keep track of the permutation + permuted = (int *) _alloca16( m.GetNumRows() * sizeof( int ) ); + for ( i = 0; i < m.GetNumRows(); i++ ) { + permuted[i] = i; + } + + // permute input so all unbounded variables come first + numUnbounded = 0; + for ( i = 0; i < m.GetNumRows(); i++ ) { + if ( lo[i] == -idMath::INFINITY && hi[i] == idMath::INFINITY ) { + if ( numUnbounded != i ) { + Swap( numUnbounded, i ); + } + numUnbounded++; + } + } + + // permute input so all variables using the boxIndex come last + boxStartIndex = m.GetNumRows(); + if ( boxIndex ) { + for ( i = m.GetNumRows() - 1; i >= numUnbounded; i-- ) { + if ( boxIndex[i] >= 0 && ( lo[i] != -idMath::INFINITY || hi[i] != idMath::INFINITY ) ) { + boxStartIndex--; + if ( boxStartIndex != i ) { + Swap( boxStartIndex, i ); + } + } + } + } + + // sub matrix for factorization + clamped.SetData( m.GetNumRows(), m.GetNumColumns(), MATX_ALLOCA( m.GetNumRows() * m.GetNumColumns() ) ); + diagonal.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + solveCache1.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + solveCache2.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // all unbounded variables are clamped + numClamped = numUnbounded; + + // if there are unbounded variables + if ( numUnbounded ) { + + // factor and solve for unbounded variables + if ( !FactorClamped() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: unbounded factorization failed\n" ); + return false; + } + SolveClamped( f, b.ToFloatPtr() ); + + // if there are no bounded variables we are done + if ( numUnbounded == m.GetNumRows() ) { + o_x = f; // the vector is not permuted + return true; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + int numIgnored = 0; +#endif + + // allocate for delta force and delta acceleration + delta_f.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + delta_a.SetData( m.GetNumRows(), VECX_ALLOCA( m.GetNumRows() ) ); + + // solve for bounded variables + failed = NULL; + for ( i = numUnbounded; i < m.GetNumRows(); i++ ) { + + clampedChangeStart = 0; + + // once we hit the box start index we can initialize the low and high boundaries of the variables using the box index + if ( i == boxStartIndex ) { + for ( j = 0; j < boxStartIndex; j++ ) { + o_x[permuted[j]] = f[j]; + } + for ( j = boxStartIndex; j < m.GetNumRows(); j++ ) { + s = o_x[boxIndex[j]]; + if ( lo[j] != -idMath::INFINITY ) { + lo[j] = - idMath::Fabs( lo[j] * s ); + } + if ( hi[j] != idMath::INFINITY ) { + hi[j] = idMath::Fabs( hi[j] * s ); + } + } + } + + // calculate acceleration for current variable + SIMDProcessor->Dot( dot, rowPtrs[i], f.ToFloatPtr(), i ); + a[i] = dot - b[i]; + + // if already at the low boundary + if ( lo[i] >= -LCP_BOUND_EPSILON && a[i] >= -LCP_ACCEL_EPSILON ) { + side[i] = -1; + continue; + } + + // if already at the high boundary + if ( hi[i] <= LCP_BOUND_EPSILON && a[i] <= LCP_ACCEL_EPSILON ) { + side[i] = 1; + continue; + } + + // if inside the clamped region + if ( idMath::Fabs( a[i] ) <= LCP_ACCEL_EPSILON ) { + side[i] = 0; + AddClamped( i, false ); + continue; + } + + // drive the current variable into a valid region + for ( n = 0; n < maxIterations; n++ ) { + + // direction to move + if ( a[i] <= 0.0f ) { + dir = 1.0f; + } else { + dir = -1.0f; + } + + // calculate force delta + CalcForceDelta( i, dir ); + + // calculate acceleration delta: delta_a = m * delta_f; + CalcAccelDelta( i ); + + // maximum step we can take + GetMaxStep( i, dir, maxStep, limit, limitSide ); + + if ( maxStep <= 0.0f ) { +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + // ignore the current variable completely + lo[i] = hi[i] = 0.0f; + f[i] = 0.0f; + side[i] = -1; + numIgnored++; +#else + failed = va( "invalid step size %.4f", maxStep ); +#endif + break; + } + + // change force + ChangeForce( i, maxStep ); + + // change acceleration + ChangeAccel( i, maxStep ); + + // clamp/unclamp the variable that limited this step + side[limit] = limitSide; + switch( limitSide ) { + case 0: { + a[limit] = 0.0f; + AddClamped( limit, ( limit == i ) ); + break; + } + case -1: { + f[limit] = lo[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + case 1: { + f[limit] = hi[limit]; + if ( limit != i ) { + RemoveClamped( limit ); + } + break; + } + } + + // if the current variable limited the step we can continue with the next variable + if ( limit == i ) { + break; + } + } + + if ( n >= maxIterations ) { + failed = va( "max iterations %d", maxIterations ); + break; + } + + if ( failed ) { + break; + } + } + +#ifdef IGNORE_UNSATISFIABLE_VARIABLES + if ( numIgnored ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: %d of %d bounded variables ignored\n", numIgnored, m.GetNumRows() - numUnbounded ); + } + } +#endif + + // if failed clear remaining forces + if ( failed ) { + if ( lcp_showFailures.GetBool() ) { + idLib::common->Printf( "idLCP_Symmetric::Solve: %s (%d of %d bounded variables ignored)\n", failed, m.GetNumRows() - i, m.GetNumRows() - numUnbounded ); + } + for ( j = i; j < m.GetNumRows(); j++ ) { + f[j] = 0.0f; + } + } + +#if defined(_DEBUG) && 0 + if ( !failed ) { + // test whether or not the solution satisfies the complementarity conditions + for ( i = 0; i < m.GetNumRows(); i++ ) { + a[i] = -b[i]; + for ( j = 0; j < m.GetNumRows(); j++ ) { + a[i] += rowPtrs[i][j] * f[j]; + } + + if ( f[i] == lo[i] ) { + if ( lo[i] != hi[i] && a[i] < -LCP_ACCEL_EPSILON ) { + int bah1 = 1; + } + } else if ( f[i] == hi[i] ) { + if ( lo[i] != hi[i] && a[i] > LCP_ACCEL_EPSILON ) { + int bah2 = 1; + } + } else if ( f[i] < lo[i] || f[i] > hi[i] || idMath::Fabs( a[i] ) > 1.0f ) { + int bah3 = 1; + } + } + } +#endif + + // unpermute result + for ( i = 0; i < f.GetSize(); i++ ) { + o_x[permuted[i]] = f[i]; + } + + // unpermute original matrix + for ( i = 0; i < m.GetNumRows(); i++ ) { + for ( j = 0; j < m.GetNumRows(); j++ ) { + if ( permuted[j] == i ) { + break; + } + } + if ( i != j ) { + m.SwapColumns( i, j ); + idSwap( permuted[i], permuted[j] ); + } + } + + return true; +} + + +//=============================================================== +// +// idLCP +// +//=============================================================== + +/* +============ +idLCP::AllocSquare +============ +*/ +idLCP *idLCP::AllocSquare( void ) { + idLCP *lcp = new idLCP_Square; + lcp->SetMaxIterations( 32 ); + return lcp; +} + +/* +============ +idLCP::AllocSymmetric +============ +*/ +idLCP *idLCP::AllocSymmetric( void ) { + idLCP *lcp = new idLCP_Symmetric; + lcp->SetMaxIterations( 32 ); + return lcp; +} + +/* +============ +idLCP::~idLCP +============ +*/ +idLCP::~idLCP( void ) { +} + +/* +============ +idLCP::SetMaxIterations +============ +*/ +void idLCP::SetMaxIterations( int max ) { + maxIterations = max; +} + +/* +============ +idLCP::GetMaxIterations +============ +*/ +int idLCP::GetMaxIterations( void ) { + return maxIterations; +} diff --git a/src/idlib/math/Lcp.h b/src/idlib/math/Lcp.h new file mode 100644 index 0000000..596a285 --- /dev/null +++ b/src/idlib/math/Lcp.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_LCP_H__ +#define __MATH_LCP_H__ + +/* +=============================================================================== + + Box Constrained Mixed Linear Complementarity Problem solver + + A is a matrix of dimension n*n and x, b, lo, hi are vectors of dimension n + + Solve: Ax = b + t, where t is a vector of dimension n, with + complementarity condition: (x[i] - lo[i]) * (x[i] - hi[i]) * t[i] = 0 + such that for each 0 <= i < n one of the following holds: + + 1. lo[i] < x[i] < hi[i], t[i] == 0 + 2. x[i] == lo[i], t[i] >= 0 + 3. x[i] == hi[i], t[i] <= 0 + + Partly bounded or unbounded variables can have lo[i] and/or hi[i] + set to negative/positive idMath::INFITITY respectively. + + If boxIndex != NULL and boxIndex[i] != -1 then + + lo[i] = - fabs( lo[i] * x[boxIndex[i]] ) + hi[i] = fabs( hi[i] * x[boxIndex[i]] ) + boxIndex[boxIndex[i]] must be -1 + + Before calculating any of the bounded x[i] with boxIndex[i] != -1 the + solver calculates all unbounded x[i] and all x[i] with boxIndex[i] == -1. + +=============================================================================== +*/ + +class idLCP { +public: + static idLCP * AllocSquare( void ); // A must be a square matrix + static idLCP * AllocSymmetric( void ); // A must be a symmetric matrix + + virtual ~idLCP( void ); + + virtual bool Solve( const idMatX &A, idVecX &x, const idVecX &b, const idVecX &lo, const idVecX &hi, const int *boxIndex = NULL ) = 0; + virtual void SetMaxIterations( int max ); + virtual int GetMaxIterations( void ); + +protected: + int maxIterations; +}; + +#endif /* !__MATH_LCP_H__ */ diff --git a/src/idlib/math/Mat3x4.h b/src/idlib/math/Mat3x4.h new file mode 100644 index 0000000..150a5f5 --- /dev/null +++ b/src/idlib/math/Mat3x4.h @@ -0,0 +1,309 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MAT3X4_H__ +#define __MAT3X4_H__ + +/* +=============================================================================== + + row-major 3x4 matrix + + idMat3 m; + idVec3 t; + + m[0][0], m[1][0], m[2][0], t[0] + m[0][1], m[1][1], m[2][1], t[1] + m[0][2], m[1][2], m[2][2], t[2] + +=============================================================================== +*/ + +class idMat3x4 { +public: + + void SetRotation( const idMat3 &m ); + void SetTranslation( const idVec3 &t ); + + bool Compare( const idMat3x4 &a ) const; // exact compare, no epsilon + bool Compare( const idMat3x4 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat3x4 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat3x4 &a ) const; // exact compare, no epsilon + + void Identity( void ); + void Invert( void ); + + void LeftMultiply( const idMat3x4 &m ); + void LeftMultiply( const idMat3 &m ); + void RightMultiply( const idMat3x4 &m ); + void RightMultiply( const idMat3 &m ); + + void Transform( idVec3 &result, const idVec3 &v ) const; + void Rotate( idVec3 &result, const idVec3 &v ) const; + + idMat3 ToMat3( void ) const; + idVec3 ToVec3( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float mat[3*4]; +}; + + +ID_INLINE void idMat3x4::SetRotation( const idMat3 &m ) { + // NOTE: idMat3 is transposed because it is column-major + mat[0 * 4 + 0] = m[0][0]; + mat[0 * 4 + 1] = m[1][0]; + mat[0 * 4 + 2] = m[2][0]; + mat[1 * 4 + 0] = m[0][1]; + mat[1 * 4 + 1] = m[1][1]; + mat[1 * 4 + 2] = m[2][1]; + mat[2 * 4 + 0] = m[0][2]; + mat[2 * 4 + 1] = m[1][2]; + mat[2 * 4 + 2] = m[2][2]; +} + +ID_INLINE void idMat3x4::SetTranslation( const idVec3 &t ) { + mat[0 * 4 + 3] = t[0]; + mat[1 * 4 + 3] = t[1]; + mat[2 * 4 + 3] = t[2]; +} + +ID_INLINE bool idMat3x4::Compare( const idMat3x4 &a ) const { + int i; + + for ( i = 0; i < 12; i++ ) { + if ( mat[i] != a.mat[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat3x4::Compare( const idMat3x4 &a, const float epsilon ) const { + int i; + + for ( i = 0; i < 12; i++ ) { + if ( idMath::Fabs( mat[i] - a.mat[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat3x4::operator==( const idMat3x4 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat3x4::operator!=( const idMat3x4 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat3x4::Identity( void ) { + mat[0 * 4 + 0] = 1.0f; mat[0 * 4 + 1] = 0.0f; mat[0 * 4 + 2] = 0.0f; mat[0 * 4 + 3] = 0.0f; + mat[0 * 4 + 0] = 0.0f; mat[0 * 4 + 1] = 1.0f; mat[0 * 4 + 2] = 0.0f; mat[0 * 4 + 3] = 0.0f; + mat[0 * 4 + 0] = 0.0f; mat[0 * 4 + 1] = 0.0f; mat[0 * 4 + 2] = 1.0f; mat[0 * 4 + 3] = 0.0f; +} + +ID_INLINE void idMat3x4::Invert( void ) { + float tmp[3]; + + // negate inverse rotated translation part + tmp[0] = mat[0 * 4 + 0] * mat[0 * 4 + 3] + mat[1 * 4 + 0] * mat[1 * 4 + 3] + mat[2 * 4 + 0] * mat[2 * 4 + 3]; + tmp[1] = mat[0 * 4 + 1] * mat[0 * 4 + 3] + mat[1 * 4 + 1] * mat[1 * 4 + 3] + mat[2 * 4 + 1] * mat[2 * 4 + 3]; + tmp[2] = mat[0 * 4 + 2] * mat[0 * 4 + 3] + mat[1 * 4 + 2] * mat[1 * 4 + 3] + mat[2 * 4 + 2] * mat[2 * 4 + 3]; + mat[0 * 4 + 3] = -tmp[0]; + mat[1 * 4 + 3] = -tmp[1]; + mat[2 * 4 + 3] = -tmp[2]; + + // transpose rotation part + tmp[0] = mat[0 * 4 + 1]; + mat[0 * 4 + 1] = mat[1 * 4 + 0]; + mat[1 * 4 + 0] = tmp[0]; + tmp[1] = mat[0 * 4 + 2]; + mat[0 * 4 + 2] = mat[2 * 4 + 0]; + mat[2 * 4 + 0] = tmp[1]; + tmp[2] = mat[1 * 4 + 2]; + mat[1 * 4 + 2] = mat[2 * 4 + 1]; + mat[2 * 4 + 1] = tmp[2]; +} + +ID_INLINE void idMat3x4::LeftMultiply( const idMat3x4 &m ) { + float t0, t1; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 0] + m.mat[0 * 4 + 1] * mat[1 * 4 + 0] + m.mat[0 * 4 + 2] * mat[2 * 4 + 0]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 0] + m.mat[1 * 4 + 1] * mat[1 * 4 + 0] + m.mat[1 * 4 + 2] * mat[2 * 4 + 0]; + mat[2 * 4 + 0] = m.mat[2 * 4 + 0] * mat[0 * 4 + 0] + m.mat[2 * 4 + 1] * mat[1 * 4 + 0] + m.mat[2 * 4 + 2] * mat[2 * 4 + 0]; + + mat[1 * 4 + 0] = t1; + mat[0 * 4 + 0] = t0; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 1] + m.mat[0 * 4 + 1] * mat[1 * 4 + 1] + m.mat[0 * 4 + 2] * mat[2 * 4 + 1]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 1] + m.mat[1 * 4 + 1] * mat[1 * 4 + 1] + m.mat[1 * 4 + 2] * mat[2 * 4 + 1]; + mat[2 * 4 + 1] = m.mat[2 * 4 + 0] * mat[0 * 4 + 1] + m.mat[2 * 4 + 1] * mat[1 * 4 + 1] + m.mat[2 * 4 + 2] * mat[2 * 4 + 1]; + + mat[1 * 4 + 1] = t1; + mat[0 * 4 + 1] = t0; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 2] + m.mat[0 * 4 + 1] * mat[1 * 4 + 2] + m.mat[0 * 4 + 2] * mat[2 * 4 + 2]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 2] + m.mat[1 * 4 + 1] * mat[1 * 4 + 2] + m.mat[1 * 4 + 2] * mat[2 * 4 + 2]; + mat[2 * 4 + 2] = m.mat[2 * 4 + 0] * mat[0 * 4 + 2] + m.mat[2 * 4 + 1] * mat[1 * 4 + 2] + m.mat[2 * 4 + 2] * mat[2 * 4 + 2]; + + mat[1 * 4 + 2] = t1; + mat[0 * 4 + 2] = t0; + + t0 = m.mat[0 * 4 + 0] * mat[0 * 4 + 3] + m.mat[0 * 4 + 1] * mat[1 * 4 + 3] + m.mat[0 * 4 + 2] * mat[2 * 4 + 3] + m.mat[0 * 4 + 3]; + t1 = m.mat[1 * 4 + 0] * mat[0 * 4 + 3] + m.mat[1 * 4 + 1] * mat[1 * 4 + 3] + m.mat[1 * 4 + 2] * mat[2 * 4 + 3] + m.mat[1 * 4 + 3]; + mat[2 * 4 + 3] = m.mat[2 * 4 + 0] * mat[0 * 4 + 3] + m.mat[2 * 4 + 1] * mat[1 * 4 + 3] + m.mat[2 * 4 + 2] * mat[2 * 4 + 3] + m.mat[2 * 4 + 3]; + + mat[1 * 4 + 3] = t1; + mat[0 * 4 + 3] = t0; +} + +ID_INLINE void idMat3x4::LeftMultiply( const idMat3 &m ) { + float t0, t1; + + // NOTE: idMat3 is column-major + t0 = m[0][0] * mat[0 * 4 + 0] + m[1][0] * mat[1 * 4 + 0] + m[2][0] * mat[2 * 4 + 0]; + t1 = m[0][1] * mat[0 * 4 + 0] + m[1][1] * mat[1 * 4 + 0] + m[2][1] * mat[2 * 4 + 0]; + mat[2 * 4 + 0] = m[0][2] * mat[0 * 4 + 0] + m[1][2] * mat[1 * 4 + 0] + m[2][2] * mat[2 * 4 + 0]; + + mat[1 * 4 + 0] = t1; + mat[0 * 4 + 0] = t0; + + t0 = m[0][0] * mat[0 * 4 + 1] + m[1][0] * mat[1 * 4 + 1] + m[2][0] * mat[2 * 4 + 1]; + t1 = m[0][1] * mat[0 * 4 + 1] + m[1][1] * mat[1 * 4 + 1] + m[2][1] * mat[2 * 4 + 1]; + mat[2 * 4 + 1] = m[0][2] * mat[0 * 4 + 1] + m[1][2] * mat[1 * 4 + 1] + m[2][2] * mat[2 * 4 + 1]; + + mat[1 * 4 + 1] = t1; + mat[0 * 4 + 1] = t0; + + t0 = m[0][0] * mat[0 * 4 + 2] + m[1][0] * mat[1 * 4 + 2] + m[2][0] * mat[2 * 4 + 2]; + t1 = m[0][1] * mat[0 * 4 + 2] + m[1][1] * mat[1 * 4 + 2] + m[2][1] * mat[2 * 4 + 2]; + mat[2 * 4 + 2] = m[0][2] * mat[0 * 4 + 2] + m[1][2] * mat[1 * 4 + 2] + m[2][2] * mat[2 * 4 + 2]; + + mat[1 * 4 + 2] = t1; + mat[0 * 4 + 2] = t0; + + t0 = m[0][0] * mat[0 * 4 + 3] + m[1][0] * mat[1 * 4 + 3] + m[2][0] * mat[2 * 4 + 3]; + t1 = m[0][1] * mat[0 * 4 + 3] + m[1][1] * mat[1 * 4 + 3] + m[2][1] * mat[2 * 4 + 3]; + mat[2 * 4 + 3] = m[0][2] * mat[0 * 4 + 3] + m[1][2] * mat[1 * 4 + 3] + m[2][2] * mat[2 * 4 + 3]; + + mat[1 * 4 + 3] = t1; + mat[0 * 4 + 3] = t0; +} + +ID_INLINE void idMat3x4::RightMultiply( const idMat3x4 &m ) { + float t0, t1, t2; + + t0 = mat[0 * 4 + 0] * m.mat[0 * 4 + 0] + mat[0 * 4 + 1] * m.mat[1 * 4 + 0] + mat[0 * 4 + 2] * m.mat[2 * 4 + 0]; + t1 = mat[0 * 4 + 0] * m.mat[0 * 4 + 1] + mat[0 * 4 + 1] * m.mat[1 * 4 + 1] + mat[0 * 4 + 2] * m.mat[2 * 4 + 1]; + t2 = mat[0 * 4 + 0] * m.mat[0 * 4 + 2] + mat[0 * 4 + 1] * m.mat[1 * 4 + 2] + mat[0 * 4 + 2] * m.mat[2 * 4 + 2]; + mat[0 * 4 + 3] = mat[0 * 4 + 0] * m.mat[0 * 4 + 3] + mat[0 * 4 + 1] * m.mat[1 * 4 + 3] + mat[0 * 4 + 2] * m.mat[2 * 4 + 3] + mat[0 * 4 + 3]; + + mat[0 * 4 + 0] = t0; + mat[0 * 4 + 1] = t1; + mat[0 * 4 + 2] = t2; + + t0 = mat[1 * 4 + 0] * m.mat[0 * 4 + 0] + mat[1 * 4 + 1] * m.mat[1 * 4 + 0] + mat[1 * 4 + 2] * m.mat[2 * 4 + 0]; + t1 = mat[1 * 4 + 0] * m.mat[0 * 4 + 1] + mat[1 * 4 + 1] * m.mat[1 * 4 + 1] + mat[1 * 4 + 2] * m.mat[2 * 4 + 1]; + t2 = mat[1 * 4 + 0] * m.mat[0 * 4 + 2] + mat[1 * 4 + 1] * m.mat[1 * 4 + 2] + mat[1 * 4 + 2] * m.mat[2 * 4 + 2]; + mat[1 * 4 + 3] = mat[1 * 4 + 0] * m.mat[0 * 4 + 3] + mat[1 * 4 + 1] * m.mat[1 * 4 + 3] + mat[1 * 4 + 2] * m.mat[2 * 4 + 3] + mat[1 * 4 + 3]; + + mat[1 * 4 + 0] = t0; + mat[1 * 4 + 1] = t1; + mat[1 * 4 + 2] = t2; + + t0 = mat[2 * 4 + 0] * m.mat[0 * 4 + 0] + mat[2 * 4 + 1] * m.mat[1 * 4 + 0] + mat[2 * 4 + 2] * m.mat[2 * 4 + 0]; + t1 = mat[2 * 4 + 0] * m.mat[0 * 4 + 1] + mat[2 * 4 + 1] * m.mat[1 * 4 + 1] + mat[2 * 4 + 2] * m.mat[2 * 4 + 1]; + t2 = mat[2 * 4 + 0] * m.mat[0 * 4 + 2] + mat[2 * 4 + 1] * m.mat[1 * 4 + 2] + mat[2 * 4 + 2] * m.mat[2 * 4 + 2]; + mat[2 * 4 + 3] = mat[2 * 4 + 0] * m.mat[0 * 4 + 3] + mat[2 * 4 + 1] * m.mat[1 * 4 + 3] + mat[2 * 4 + 2] * m.mat[2 * 4 + 3] + mat[2 * 4 + 3]; + + mat[2 * 4 + 0] = t0; + mat[2 * 4 + 1] = t1; + mat[2 * 4 + 2] = t2; +} + +ID_INLINE void idMat3x4::RightMultiply( const idMat3 &m ) { + float t0, t1, t2; + + // NOTE: idMat3 is column-major + t0 = mat[0 * 4 + 0] * m[0][0] + mat[0 * 4 + 1] * m[0][1] + mat[0 * 4 + 2] * m[0][2]; + t1 = mat[0 * 4 + 0] * m[1][0] + mat[0 * 4 + 1] * m[1][1] + mat[0 * 4 + 2] * m[1][2]; + t2 = mat[0 * 4 + 0] * m[2][0] + mat[0 * 4 + 1] * m[2][1] + mat[0 * 4 + 2] * m[2][2]; + + mat[0 * 4 + 0] = t0; + mat[0 * 4 + 1] = t1; + mat[0 * 4 + 2] = t2; + + t0 = mat[1 * 4 + 0] * m[0][0] + mat[1 * 4 + 1] * m[0][1] + mat[1 * 4 + 2] * m[0][2]; + t1 = mat[1 * 4 + 0] * m[1][0] + mat[1 * 4 + 1] * m[1][1] + mat[1 * 4 + 2] * m[1][2]; + t2 = mat[1 * 4 + 0] * m[2][0] + mat[1 * 4 + 1] * m[2][1] + mat[1 * 4 + 2] * m[2][2]; + + mat[1 * 4 + 0] = t0; + mat[1 * 4 + 1] = t1; + mat[1 * 4 + 2] = t2; + + t0 = mat[2 * 4 + 0] * m[0][0] + mat[2 * 4 + 1] * m[0][1] + mat[2 * 4 + 2] * m[0][2]; + t1 = mat[2 * 4 + 0] * m[1][0] + mat[2 * 4 + 1] * m[1][1] + mat[2 * 4 + 2] * m[1][2]; + t2 = mat[2 * 4 + 0] * m[2][0] + mat[2 * 4 + 1] * m[2][1] + mat[2 * 4 + 2] * m[2][2]; + + mat[2 * 4 + 0] = t0; + mat[2 * 4 + 1] = t1; + mat[2 * 4 + 2] = t2; +} + +ID_INLINE void idMat3x4::Transform( idVec3 &result, const idVec3 &v ) const { + result.x = mat[0 * 4 + 0] * v.x + mat[0 * 4 + 1] * v.y + mat[0 * 4 + 2] * v.z + mat[0 * 4 + 3]; + result.y = mat[1 * 4 + 0] * v.x + mat[1 * 4 + 1] * v.y + mat[1 * 4 + 2] * v.z + mat[1 * 4 + 3]; + result.z = mat[2 * 4 + 0] * v.x + mat[2 * 4 + 1] * v.y + mat[2 * 4 + 2] * v.z + mat[2 * 4 + 3]; +} + +ID_INLINE void idMat3x4::Rotate( idVec3 &result, const idVec3 &v ) const { + result.x = mat[0 * 4 + 0] * v.x + mat[0 * 4 + 1] * v.y + mat[0 * 4 + 2] * v.z; + result.y = mat[1 * 4 + 0] * v.x + mat[1 * 4 + 1] * v.y + mat[1 * 4 + 2] * v.z; + result.z = mat[2 * 4 + 0] * v.x + mat[2 * 4 + 1] * v.y + mat[2 * 4 + 2] * v.z; +} + +ID_INLINE idMat3 idMat3x4::ToMat3( void ) const { + return idMat3( mat[0 * 4 + 0], mat[1 * 4 + 0], mat[2 * 4 + 0], + mat[0 * 4 + 1], mat[1 * 4 + 1], mat[2 * 4 + 1], + mat[0 * 4 + 2], mat[1 * 4 + 2], mat[2 * 4 + 2] ); +} + +ID_INLINE idVec3 idMat3x4::ToVec3( void ) const { + return idVec3( mat[0 * 4 + 3], mat[1 * 4 + 3], mat[2 * 4 + 3] ); +} + +ID_INLINE const float *idMat3x4::ToFloatPtr( void ) const { + return mat; +} + +ID_INLINE float *idMat3x4::ToFloatPtr( void ) { + return mat; +} + +#endif /* !__MAT3X4_H__ */ diff --git a/src/idlib/math/Math.cpp b/src/idlib/math/Math.cpp new file mode 100644 index 0000000..fb29052 --- /dev/null +++ b/src/idlib/math/Math.cpp @@ -0,0 +1,296 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +const float idMath::PI = 3.14159265358979323846f; +const float idMath::TWO_PI = 2.0f * PI; +const float idMath::HALF_PI = 0.5f * PI; +const float idMath::ONEFOURTH_PI = 0.25f * PI; +const float idMath::E = 2.71828182845904523536f; +const float idMath::SQRT_TWO = 1.41421356237309504880f; +const float idMath::SQRT_THREE = 1.73205080756887729352f; +// RAVEN BEGIN +const float idMath::THREEFOURTHS_PI = 0.75f * PI; +// RAVEN END +const float idMath::SQRT_1OVER2 = 0.70710678118654752440f; +const float idMath::SQRT_1OVER3 = 0.57735026918962576450f; +const float idMath::M_DEG2RAD = PI / 180.0f; +const float idMath::M_RAD2DEG = 180.0f / PI; +const float idMath::M_SEC2MS = 1000.0f; +const float idMath::M_MS2SEC = 0.001f; +const float idMath::INFINITY = 1e30f; +// RAVEN BEGIN +// jscott: renamed to prevent name clash +const float idMath::FLOAT_EPSILON = 1.192092896e-07f; +// ddynerman: added, from limits.h +const int idMath::INT_MIN = (-2147483647 - 1); +const int idMath::INT_MAX = 2147483647; +// RAVEN END + +bool idMath::initialized = false; +#ifdef _FAST_MATH +dword idMath::iSqrt[SQRT_TABLE_SIZE]; // inverse square root lookup table +#endif + +#ifdef ID_WIN_X86_SSE +const float idMath::SSE_FLOAT_ZERO = 0.0f; +const float idMath::SSE_FLOAT_255 = 255.0f; +#endif + +/* +=============== +idMath::Init +=============== +*/ +void idMath::Init( void ) { +#ifdef _FAST_MATH + union _flint fi, fo; + + for ( int i = 0; i < SQRT_TABLE_SIZE; i++ ) { + fi.i = ((EXP_BIAS-1) << EXP_POS) | (i << LOOKUP_POS); + fo.f = (float)( 1.0 / sqrt( fi.f ) ); + iSqrt[i] = ((dword)(((fo.i + (1<<(SEED_POS-2))) >> SEED_POS) & 0xFF))<= 2 && exponentBits <= 8 ); + assert( mantissaBits >= 2 && mantissaBits <= 23 ); + + int maxBits = ( ( ( 1 << ( exponentBits - 1 ) ) - 1 ) << mantissaBits ) | ( ( 1 << mantissaBits ) - 1 ); + int minBits = ( ( ( 1 << exponentBits ) - 2 ) << mantissaBits ) | 1; + + float max = BitsToFloat( maxBits, exponentBits, mantissaBits ); + float min = BitsToFloat( minBits, exponentBits, mantissaBits ); + + if ( f >= 0.0f ) { + if ( f >= max ) { + return maxBits; + } else if ( f <= min ) { + return minBits; + } + } else { + if ( f <= -max ) { + return ( maxBits | ( 1 << ( exponentBits + mantissaBits ) ) ); + } else if ( f >= -min ) { + return ( minBits | ( 1 << ( exponentBits + mantissaBits ) ) ); + } + } + + exponentBits--; + i = *reinterpret_cast(&f); + sign = ( i >> IEEE_FLT_SIGN_BIT ) & 1; + exponent = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + mantissa = i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ); + value = sign << ( 1 + exponentBits + mantissaBits ); + value |= ( ( INTSIGNBITSET( exponent ) << exponentBits ) | ( abs( exponent ) & ( ( 1 << exponentBits ) - 1 ) ) ) << mantissaBits; + value |= mantissa >> ( IEEE_FLT_MANTISSA_BITS - mantissaBits ); + return value; +} + +/* +================ +idMath::BitsToFloat +================ +*/ +float idMath::BitsToFloat( int i, int exponentBits, int mantissaBits ) { + static int exponentSign[2] = { 1, -1 }; + int sign, exponent, mantissa, value; + assert( exponentBits >= 2 && exponentBits <= 8 ); + assert( mantissaBits >= 2 && mantissaBits <= 23 ); + + exponentBits--; + sign = i >> ( 1 + exponentBits + mantissaBits ); + exponent = ( ( i >> mantissaBits ) & ( ( 1 << exponentBits ) - 1 ) ) * exponentSign[( i >> ( exponentBits + mantissaBits ) ) & 1]; + mantissa = ( i & ( ( 1 << mantissaBits ) - 1 ) ) << ( IEEE_FLT_MANTISSA_BITS - mantissaBits ); + value = sign << IEEE_FLT_SIGN_BIT | ( exponent + IEEE_FLT_EXPONENT_BIAS ) << IEEE_FLT_MANTISSA_BITS | mantissa; + return *reinterpret_cast(&value); +} + +// RAVEN BEGIN +// bdube: added block + +void idMath::ArtesianFromPolar( idVec3 &result, idVec3 view ) +{ + float s1, c1, s2, c2; + + idMath::SinCos( view[1], s1, c1 ); + idMath::SinCos( view[2], s2, c2 ); + + result[0] = c1 * s2 * view[0]; + result[1] = s1 * s2 * view[0]; + result[2] = c2 * view[0]; +} + +void idMath::PolarFromArtesian( idVec3 &view, idVec3 artesian ) +{ + float length; + view[0] = artesian.Length(); + view[1] = idMath::ATan( artesian[1], artesian[0] ); + length = sqrtf( ( artesian[0] * artesian[0] ) + ( artesian[1] * artesian[1] ) ); + view[2] = idMath::ATan( length, artesian[2] ); +} + +// ================================================================================================ +// jscott: fast and reliable random routines +// ================================================================================================ + +unsigned long rvRandom::mSeed; + +float rvRandom::flrand( float min, float max ) +{ + float result; + + mSeed = ( mSeed * 214013L ) + 2531011; + // Note: the shift and divide cannot be combined as this breaks the routine + result = ( float )( mSeed >> 17 ); // 0 - 32767 range + result = ( ( result * ( max - min ) ) * ( 1.0f / 32768.0f ) ) + min; + return( result ); +} + +float rvRandom::flrand() { + return flrand( 0.0f, 1.0f ); +} + +float rvRandom::flrand( const idVec2& v ) { + return flrand( v[0], v[1] ); +} + +int rvRandom::irand( int min, int max ) +{ + int result; + + max++; + mSeed = ( mSeed * 214013L ) + 2531011; + result = mSeed >> 17; + result = ( ( result * ( max - min ) ) >> 15 ) + min; + return( result ); +} + +// Try to get a seed independent of the random number system + +int rvRandom::Init( void ) +{ + mSeed *= ( unsigned long )sys->Milliseconds(); + + return( mSeed ); +} + +// ================================================================================================ +// Barycentric texture coordinate functions +// Get the *SIGNED* area of a triangle required for barycentric +// ================================================================================================ +float idMath::BarycentricTriangleArea( const idVec3 &normal, const idVec3 &a, const idVec3 &b, const idVec3 &c ) +{ + idVec3 v1, v2; + idVec3 cross; + float area; + + v1 = b - a; + v2 = c - a; + cross = v1.Cross( v2 ); + area = 0.5f * DotProduct( cross, normal ); + + return( area ); +} + +void idMath::BarycentricEvaluate( idVec2 &result, const idVec3 &point, const idVec3 &normal, const float area, const idVec3 t[3], const idVec2 tc[3] ) +{ + float b1, b2, b3; + + b1 = idMath::BarycentricTriangleArea( normal, point, t[1], t[2] ) / area; + b2 = idMath::BarycentricTriangleArea( normal, t[0], point, t[2] ) / area; + b3 = idMath::BarycentricTriangleArea( normal, t[0], t[1], point ) / area; + + result[0] = ( b1 * tc[0][0] ) + ( b2 * tc[1][0] ) + ( b3 * tc[2][0] ); + result[1] = ( b1 * tc[0][1] ) + ( b2 * tc[1][1] ) + ( b3 * tc[2][1] ); +} + +// abahr: +float idMath::Lerp( const idVec2& range, float frac ) { + return Lerp( range[0], range[1], frac ); +} + +// abahr: +float idMath::Lerp( float start, float end, float frac ) { + if( frac >= 1.0f ) { + return end; + } + + if( frac <= 0.0f ) { + return start; + } + + return start + (end - start) * frac; +} + +// abahr: +float idMath::MidPointLerp( float start, float mid, float end, float frac ) { + if( frac < 0.5f ) { + return Lerp( start, mid, 2.0f * frac ); + } + + return Lerp( mid, end, 2.0f * (frac - 0.5f) ); +} + +float idMath::dBToScale( float db ) { + + if( db < -60.0f ) { + + return( 0.0f ); + + } else { + + return( powf( 2.0f, db * ( 1.0f / 6.0f ) ) ); + } +} + +float idMath::ScaleToDb( float scale ) { + + if( scale <= 0.0f ) { + + return( -60.0f ); + + } else { + + return( 6.0f * idMath::Log( scale ) / idMath::Log( 2 ) ); + } +} + +// RAVEN END diff --git a/src/idlib/math/Math.h b/src/idlib/math/Math.h new file mode 100644 index 0000000..e58efbe --- /dev/null +++ b/src/idlib/math/Math.h @@ -0,0 +1,1069 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_MATH_H__ +#define __MATH_MATH_H__ + +/* +=============================================================================== + + Math + +=============================================================================== +*/ + +#ifdef INFINITY +#undef INFINITY +#endif + +// RAVEN BEGIN +// jscott: renamed to prevent name clash +#ifdef FLOAT_EPSILON +#undef FLOAT_EPSILON +#endif +// ddynerman: name clash prevention +#ifdef INT_MIN +#undef INT_MIN +#endif + +#ifdef INT_MAX +#undef INT_MAX +#endif + +// jscott: uncomment this to use id's sqrt and trig approximations +#if defined( __linux__ ) || defined( MACOS_X ) + // TTimo - enabling for OSes I'm covering + // (14:34:20) mrelusive: in the general case we don't use those functions + // (14:34:28) mrelusive: they are only for specific cases where they are faster + #define _FAST_MATH +#endif +// RAVEN END + +#define DEG2RAD(a) ( (a) * idMath::M_DEG2RAD ) +#define RAD2DEG(a) ( (a) * idMath::M_RAD2DEG ) + +#define SEC2MS(t) ( idMath::FtoiFast( (t) * idMath::M_SEC2MS ) ) +#define MS2SEC(t) ( (t) * idMath::M_MS2SEC ) + +#define ANGLE2SHORT(x) ( idMath::FtoiFast( (x) * 65536.0f / 360.0f ) & 65535 ) +#define SHORT2ANGLE(x) ( (x) * ( 360.0f / 65536.0f ) ) + +#define ANGLE2BYTE(x) ( idMath::FtoiFast( (x) * 256.0f / 360.0f ) & 255 ) +#define BYTE2ANGLE(x) ( (x) * ( 360.0f / 256.0f ) ) + +#define FLOATSIGNBITSET(f) ((*(const unsigned long *)&(f)) >> 31) +#define FLOATSIGNBITNOTSET(f) ((~(*(const unsigned long *)&(f))) >> 31) +#define FLOATNOTZERO(f) ((*(const unsigned long *)&(f)) & ~(1<<31) ) +#define INTSIGNBITSET(i) (((const unsigned long)(i)) >> 31) +#define INTSIGNBITNOTSET(i) ((~((const unsigned long)(i))) >> 31) + +#define FLOAT_IS_NAN(x) (((*(const unsigned long *)&x) & 0x7f800000) == 0x7f800000) +#define FLOAT_IS_INF(x) (((*(const unsigned long *)&x) & 0x7fffffff) == 0x7f800000) +#define FLOAT_IS_IND(x) ((*(const unsigned long *)&x) == 0xffc00000) +#define FLOAT_IS_DENORMAL(x) (((*(const unsigned long *)&x) & 0x7f800000) == 0x00000000 && \ + ((*(const unsigned long *)&x) & 0x007fffff) != 0x00000000 ) + +#define IEEE_FLT_MANTISSA_BITS 23 +#define IEEE_FLT_EXPONENT_BITS 8 +#define IEEE_FLT_EXPONENT_BIAS 127 +#define IEEE_FLT_SIGN_BIT 31 + +#define IEEE_DBL_MANTISSA_BITS 52 +#define IEEE_DBL_EXPONENT_BITS 11 +#define IEEE_DBL_EXPONENT_BIAS 1023 +#define IEEE_DBL_SIGN_BIT 63 + +#define IEEE_DBLE_MANTISSA_BITS 63 +#define IEEE_DBLE_EXPONENT_BITS 15 +#define IEEE_DBLE_EXPONENT_BIAS 0 +#define IEEE_DBLE_SIGN_BIT 79 + +template ID_INLINE T Max( T x, T y ) { return ( x > y ) ? x : y; } +template ID_INLINE T Min( T x, T y ) { return ( x < y ) ? x : y; } +template ID_INLINE int MaxIndex( T x, T y ) { return ( x > y ) ? 0 : 1; } +template ID_INLINE int MinIndex( T x, T y ) { return ( x < y ) ? 0 : 1; } + +template ID_INLINE T Max3( T x, T y, T z ) { return ( x > y ) ? ( ( x > z ) ? x : z ) : ( ( y > z ) ? y : z ); } +template ID_INLINE T Min3( T x, T y, T z ) { return ( x < y ) ? ( ( x < z ) ? x : z ) : ( ( y < z ) ? y : z ); } +template ID_INLINE int Max3Index( T x, T y, T z ) { return ( x > y ) ? ( ( x > z ) ? 0 : 2 ) : ( ( y > z ) ? 1 : 2 ); } +template ID_INLINE int Min3Index( T x, T y, T z ) { return ( x < y ) ? ( ( x < z ) ? 0 : 2 ) : ( ( y < z ) ? 1 : 2 ); } +template ID_INLINE T Sign( T f ) { return ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 0 ); } +// RAVEN BEGIN +// abahr: I know its not correct but return 1 if zero +template ID_INLINE T SignZero( T f ) { return ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 1 ); } +// RAVEN END +template ID_INLINE T Square( T x ) { return x * x; } +template ID_INLINE T Cube( T x ) { return x * x * x; } + +class idVec2; +class idVec3; + +class idMath { +public: + + static void Init( void ); + + static float RSqrt( float x ); // reciprocal square root, returns huge number when x == 0.0 + +#ifdef _FAST_MATH + static float InvSqrt( float x ); // inverse square root with 32 bits precision, returns huge number when x == 0.0 + static float InvSqrt16( float x ); // inverse square root with 16 bits precision, returns huge number when x == 0.0 + static double InvSqrt64( float x ); // inverse square root with 64 bits precision, returns huge number when x == 0.0 + + static float Sqrt( float x ); // square root with 32 bits precision + static float Sqrt16( float x ); // square root with 16 bits precision + static double Sqrt64( float x ); // square root with 64 bits precision + + static float Sin( float a ); // sine with 32 bits precision + static float Sin16( float a ); // sine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Sin64( float a ); // sine with 64 bits precision + + static float Cos( float a ); // cosine with 32 bits precision + static float Cos16( float a ); // cosine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Cos64( float a ); // cosine with 64 bits precision + + static float Tan( float a ); // tangent with 32 bits precision + static float Tan16( float a ); // tangent with 16 bits precision, maximum absolute error is 1.8897e-08 + static double Tan64( float a ); // tangent with 64 bits precision + + static float ATan( float a ); // arc tangent with 32 bits precision + static float ATan16( float a ); // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float a ); // arc tangent with 64 bits precision + + static float ATan( float y, float x ); // arc tangent with 32 bits precision + static float ATan16( float y, float x ); // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float y, float x ); // arc tangent with 64 bits precision +#else + static float InvSqrt( float x ) { assert( x > 0.0f ); return( 1.0f / sqrtf( x ) ); } // inverse square root with 32 bits precision, returns huge number when x == 0.0 + static float InvSqrt16( float x ) { assert( x > 0.0f ); return( 1.0f / sqrt( x ) ); } // inverse square root with 16 bits precision, returns huge number when x == 0.0 + static double InvSqrt64( float x ) { assert( x > 0.0f ); return( 1.0f / sqrt( x ) ); } // inverse square root with 64 bits precision, returns huge number when x == 0.0 + + static float Sqrt( float x ) { assert( x >= 0.0f ); return( sqrtf( x ) ); } // square root with 32 bits precision + static float Sqrt16( float x ) { assert( x >= 0.0f ); return( sqrtf( x ) ); } // square root with 16 bits precision + static double Sqrt64( float x ) { assert( x >= 0.0f ); return( sqrt( x ) ); } // square root with 64 bits precision + + static float Sin( float a ) { return( sinf( a ) ); } // sine with 32 bits precision + static float Sin16( float a ) { return( sinf( a ) ); } // sine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Sin64( float a ) { return( sin( a ) ); } // sine with 64 bits precision + + static float Cos( float a ) { return( cosf( a ) ); } // cosine with 32 bits precision + static float Cos16( float a ) { return( cosf( a ) ); } // cosine with 16 bits precision, maximum absolute error is 2.3082e-09 + static double Cos64( float a ) { return( cos( a ) ); } // cosine with 64 bits precision + + static float Tan( float a ) { return( tanf( a ) ); } // tangent with 32 bits precision + static float Tan16( float a ) { return( tanf( a ) ); } // tangent with 16 bits precision, maximum absolute error is 1.8897e-08 + static double Tan64( float a ) { return( tan( a ) ); } // tangent with 64 bits precision + + static float ATan( float a ) { return( atanf( a ) ); } // arc tangent with 32 bits precision + static float ATan16( float a ) { return( atanf( a ) ); } // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float a ) { return( atan( a ) ); } // arc tangent with 64 bits precision + + static float ATan( float y, float x ) { return( atan2f( y, x ) ); } // arc tangent with 32 bits precision + static float ATan16( float y, float x ) { return( atan2f( y, x ) ); } // arc tangent with 16 bits precision, maximum absolute error is 1.3593e-08 + static double ATan64( float y, float x ) { return( atan2( y, x ) ); } // arc tangent with 64 bits precision +#endif + + static void SinCos( float a, float &s, float &c ); // sine and cosine with 32 bits precision + static void SinCos16( float a, float &s, float &c ); // sine and cosine with 16 bits precision + static void SinCos64( float a, double &s, double &c ); // sine and cosine with 64 bits precision + + static float ASin( float a ); // arc sine with 32 bits precision, input is clamped to [-1, 1] to avoid a silent NaN + static float ASin16( float a ); // arc sine with 16 bits precision, maximum absolute error is 6.7626e-05 + static double ASin64( float a ); // arc sine with 64 bits precision + + static float ACos( float a ); // arc cosine with 32 bits precision, input is clamped to [-1, 1] to avoid a silent NaN + static float ACos16( float a ); // arc cosine with 16 bits precision, maximum absolute error is 6.7626e-05 + static double ACos64( float a ); // arc cosine with 64 bits precision + + static float Pow( float x, float y ); // x raised to the power y with 32 bits precision + static float Pow16( float x, float y ); // x raised to the power y with 16 bits precision + static double Pow64( float x, float y ); // x raised to the power y with 64 bits precision + + static float Exp( float f ); // e raised to the power f with 32 bits precision + static float Exp16( float f ); // e raised to the power f with 16 bits precision + static double Exp64( float f ); // e raised to the power f with 64 bits precision + + static float Log( float f ); // natural logarithm with 32 bits precision + static float Log16( float f ); // natural logarithm with 16 bits precision + static double Log64( float f ); // natural logarithm with 64 bits precision + + static int IPow( int x, int y ); // integral x raised to the power y + static int ILog2( float f ); // integral base-2 logarithm of the floating point value + static int ILog2( int i ); // integral base-2 logarithm of the integer value + + static int BitsForFloat( float f ); // minumum number of bits required to represent ceil( f ) + static int BitsForInteger( int i ); // minumum number of bits required to represent i + static int MaskForFloatSign( float f );// returns 0x00000000 if x >= 0.0f and returns 0xFFFFFFFF if x <= -0.0f + static int MaskForIntegerSign( int i );// returns 0x00000000 if x >= 0 and returns 0xFFFFFFFF if x < 0 + static int FloorPowerOfTwo( int x ); // round x down to the nearest power of 2 + static int CeilPowerOfTwo( int x ); // round x up to the nearest power of 2 + static bool IsPowerOfTwo( int x ); // returns true if x is a power of 2 + static int BitCount( int x ); // returns the number of 1 bits in x + static int BitReverse( int x ); // returns the bit reverse of x + + static int Abs( int x ); // returns the absolute value of the integer value (for reference only) + static float Fabs( float f ); // returns the absolute value of the floating point value + static float Floor( float f ); // returns the largest integer that is less than or equal to the given value + static float Ceil( float f ); // returns the smallest integer that is greater than or equal to the given value + static float Rint( float f ); // returns the nearest integer + static int Ftoi( float f ); // float to int conversion + static int FtoiFast( float f ); // fast float to int conversion but uses current FPU round mode (default round nearest) + static byte Ftob( float f ); // float to byte conversion, the result is clamped to the range [0-255] + + static signed char ClampChar( int i ); +// RAVEN BEGIN + static byte ClampByte( int i ); +// RAVEN END + static signed short ClampShort( int i ); + static int ClampInt( int min, int max, int value ); + static float ClampFloat( float min, float max, float value ); + + static float AngleNormalize360( float angle ); + static float AngleNormalize180( float angle ); + static float AngleDelta( float angle1, float angle2 ); + + static int FloatToBits( float f, int exponentBits, int mantissaBits ); + static float BitsToFloat( int i, int exponentBits, int mantissaBits ); + + static int FloatHash( const float *array, const int numFloats ); + + static const float PI; // pi + static const float TWO_PI; // pi * 2 + static const float HALF_PI; // pi / 2 + static const float ONEFOURTH_PI; // pi / 4 + static const float E; // e + static const float SQRT_TWO; // sqrt( 2 ) + static const float SQRT_THREE; // sqrt( 3 ) +// RAVEN BEGIN + static const float THREEFOURTHS_PI; // 3 * pi / 4 +// RAVEN END + static const float SQRT_1OVER2; // sqrt( 1 / 2 ) + static const float SQRT_1OVER3; // sqrt( 1 / 3 ) + static const float M_DEG2RAD; // degrees to radians multiplier + static const float M_RAD2DEG; // radians to degrees multiplier + static const float M_SEC2MS; // seconds to milliseconds multiplier + static const float M_MS2SEC; // milliseconds to seconds multiplier + static const float INFINITY; // huge number which should be larger than any valid number used +// RAVEN BEGIN +// jscott: renamed to prevent name clash + static const float FLOAT_EPSILON; // smallest positive number such that 1.0+FLT_EPSILON != 1.0 +// ddynerman: added from limits.h + static const int INT_MIN; + static const int INT_MAX; + +// bdube: moved here from modview + static void ArtesianFromPolar( idVec3 &result, idVec3 view ); + static void PolarFromArtesian( idVec3 &view, idVec3 artesian ); +// jscott: for material type collision + static float BarycentricTriangleArea( const idVec3 &normal, const idVec3 &a, const idVec3 &b, const idVec3 &c ); + static void BarycentricEvaluate( idVec2 &result, const idVec3 &point, const idVec3 &normal, const float area, const idVec3 t[3], const idVec2 tc[3] ); +// abahr + static float Lerp( const idVec2& range, float frac ); + static float Lerp( float start, float end, float frac ); + static float MidPointLerp( float start, float mid, float end, float frac ); +// jscott: for sound system + static float dBToScale( float db ); + static float ScaleToDb( float scale ); +// RAVEN END + + +private: + enum { + LOOKUP_BITS = 8, + EXP_POS = 23, + EXP_BIAS = 127, + LOOKUP_POS = (EXP_POS-LOOKUP_BITS), + SEED_POS = (EXP_POS-8), + SQRT_TABLE_SIZE = (2<( &x ); + i = 0x5f3759df - ( i >> 1 ); + r = *reinterpret_cast( &i ); + r = r * ( 1.5f - r * r * y ); + return r; +} + +#ifdef _FAST_MATH +ID_INLINE float idMath::InvSqrt16( float x ) { + dword a = ((union _flint*)(&x))->i; + union _flint seed; + + assert( initialized ); + + double y = x * 0.5f; + seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK]; + double r = seed.f; + r = r * ( 1.5f - r * r * y ); + return (float) r; +} + +ID_INLINE float idMath::InvSqrt( float x ) { + dword a = ((union _flint*)(&x))->i; + union _flint seed; + + assert( initialized ); + + double y = x * 0.5f; + seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK]; + double r = seed.f; + r = r * ( 1.5f - r * r * y ); + r = r * ( 1.5f - r * r * y ); + return (float) r; +} + +ID_INLINE double idMath::InvSqrt64( float x ) { + dword a = ((union _flint*)(&x))->i; + union _flint seed; + + assert( initialized ); + + double y = x * 0.5f; + seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK]; + double r = seed.f; + r = r * ( 1.5f - r * r * y ); + r = r * ( 1.5f - r * r * y ); + r = r * ( 1.5f - r * r * y ); + return r; +} + +ID_INLINE float idMath::Sqrt16( float x ) { + return x * InvSqrt16( x ); +} + +ID_INLINE float idMath::Sqrt( float x ) { + return x * InvSqrt( x ); +} + +ID_INLINE double idMath::Sqrt64( float x ) { + return x * InvSqrt64( x ); +} + +ID_INLINE float idMath::Sin( float a ) { + return sinf( a ); +} + +ID_INLINE float idMath::Sin16( float a ) { + float s; + + if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) { + a -= floorf( a / TWO_PI ) * TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + } else { + a = PI - a; + } + } +#else + a = PI - a; + if ( fabs( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + } +#endif + s = a * a; + return a * ( ( ( ( ( -2.39e-08f * s + 2.7526e-06f ) * s - 1.98409e-04f ) * s + 8.3333315e-03f ) * s - 1.666666664e-01f ) * s + 1.0f ); +} + +ID_INLINE double idMath::Sin64( float a ) { + return sin( a ); +} + +ID_INLINE float idMath::Cos( float a ) { + return cosf( a ); +} + +ID_INLINE float idMath::Cos16( float a ) { + float s, d; + + if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) { + a -= floorf( a / TWO_PI ) * TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + d = -1.0f; + } else { + d = 1.0f; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + d = 1.0f; + } else { + a = PI - a; + d = -1.0f; + } + } +#else + a = PI - a; + if ( fabs( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } +#endif + s = a * a; + return d * ( ( ( ( ( -2.605e-07f * s + 2.47609e-05f ) * s - 1.3888397e-03f ) * s + 4.16666418e-02f ) * s - 4.999999963e-01f ) * s + 1.0f ); +} + +ID_INLINE double idMath::Cos64( float a ) { + return cos( a ); +} +#endif + +ID_INLINE void idMath::SinCos( float a, float &s, float &c ) { +#ifdef ID_WIN_X86_ASM + _asm { + fld a + fsincos + mov ecx, c + mov edx, s + fstp dword ptr [ecx] + fstp dword ptr [edx] + } +#else + s = sinf( a ); + c = cosf( a ); +#endif +} + +ID_INLINE void idMath::SinCos16( float a, float &s, float &c ) { + float t, d; + + if ( ( a < 0.0f ) || ( a >= idMath::TWO_PI ) ) { + a -= floorf( a / idMath::TWO_PI ) * idMath::TWO_PI; + } +#if 1 + if ( a < PI ) { + if ( a > HALF_PI ) { + a = PI - a; + d = -1.0f; + } else { + d = 1.0f; + } + } else { + if ( a > PI + HALF_PI ) { + a = a - TWO_PI; + d = 1.0f; + } else { + a = PI - a; + d = -1.0f; + } + } +#else + a = PI - a; + if ( fabs( a ) >= HALF_PI ) { + a = ( ( a < 0.0f ) ? -PI : PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } +#endif + t = a * a; + s = a * ( ( ( ( ( -2.39e-08f * t + 2.7526e-06f ) * t - 1.98409e-04f ) * t + 8.3333315e-03f ) * t - 1.666666664e-01f ) * t + 1.0f ); + c = d * ( ( ( ( ( -2.605e-07f * t + 2.47609e-05f ) * t - 1.3888397e-03f ) * t + 4.16666418e-02f ) * t - 4.999999963e-01f ) * t + 1.0f ); +} + +ID_INLINE void idMath::SinCos64( float a, double &s, double &c ) { +#ifdef ID_WIN_X86_ASM + _asm { + fld a + fsincos + mov ecx, c + mov edx, s + fstp qword ptr [ecx] + fstp qword ptr [edx] + } +#else + s = sin( a ); + c = cos( a ); +#endif +} + +#ifdef _FAST_MATH +ID_INLINE float idMath::Tan( float a ) { + return tanf( a ); +} + +ID_INLINE float idMath::Tan16( float a ) { + float s; + bool reciprocal; + + if ( ( a < 0.0f ) || ( a >= PI ) ) { + a -= floorf( a / PI ) * PI; + } +#if 1 + if ( a < HALF_PI ) { + if ( a > ONEFOURTH_PI ) { + a = HALF_PI - a; + reciprocal = true; + } else { + reciprocal = false; + } + } else { + if ( a > HALF_PI + ONEFOURTH_PI ) { + a = a - PI; + reciprocal = false; + } else { + a = HALF_PI - a; + reciprocal = true; + } + } +#else + a = HALF_PI - a; + if ( fabs( a ) >= ONEFOURTH_PI ) { + a = ( ( a < 0.0f ) ? -HALF_PI : HALF_PI ) - a; + reciprocal = false; + } else { + reciprocal = true; + } +#endif + s = a * a; + s = a * ( ( ( ( ( ( 9.5168091e-03f * s + 2.900525e-03f ) * s + 2.45650893e-02f ) * s + 5.33740603e-02f ) * s + 1.333923995e-01f ) * s + 3.333314036e-01f ) * s + 1.0f ); + if ( reciprocal ) { + return 1.0f / s; + } else { + return s; + } +} + +ID_INLINE double idMath::Tan64( float a ) { + return tan( a ); +} +#endif + +ID_INLINE float idMath::ASin( float a ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + if ( a >= 1.0f ) { + return HALF_PI; + } + return asinf( a ); +} + +ID_INLINE float idMath::ASin16( float a ) { + if ( FLOATSIGNBITSET( a ) ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + a = fabs( a ); + return ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ) - HALF_PI; + } else { + if ( a >= 1.0f ) { + return HALF_PI; + } + return HALF_PI - ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ); + } +} + +ID_INLINE double idMath::ASin64( float a ) { + if ( a <= -1.0f ) { + return -HALF_PI; + } + if ( a >= 1.0f ) { + return HALF_PI; + } + return asin( a ); +} + +ID_INLINE float idMath::ACos( float a ) { + if ( a <= -1.0f ) { + return PI; + } + if ( a >= 1.0f ) { + return 0.0f; + } + return acosf( a ); +} + +ID_INLINE float idMath::ACos16( float a ) { + if ( FLOATSIGNBITSET( a ) ) { + if ( a <= -1.0f ) { + return PI; + } + a = fabs( a ); + return PI - ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ); + } else { + if ( a >= 1.0f ) { + return 0.0f; + } + return ( ( ( -0.0187293f * a + 0.0742610f ) * a - 0.2121144f ) * a + 1.5707288f ) * sqrt( 1.0f - a ); + } +} + +ID_INLINE double idMath::ACos64( float a ) { + if ( a <= -1.0f ) { + return PI; + } + if ( a >= 1.0f ) { + return 0.0f; + } + return acos( a ); +} + +#ifdef _FAST_MATH +ID_INLINE float idMath::ATan( float a ) { + return atanf( a ); +} + +ID_INLINE float idMath::ATan16( float a ) { + float s; + + if ( fabs( a ) > 1.0f ) { + a = 1.0f / a; + s = a * a; + s = - ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + if ( FLOATSIGNBITSET( a ) ) { + return s - HALF_PI; + } else { + return s + HALF_PI; + } + } else { + s = a * a; + return ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + } +} + +ID_INLINE double idMath::ATan64( float a ) { + return atan( a ); +} + +ID_INLINE float idMath::ATan( float y, float x ) { + return atan2f( y, x ); +} + +ID_INLINE float idMath::ATan16( float y, float x ) { + float a, s; + + if ( fabs( y ) > fabs( x ) ) { + a = x / y; + s = a * a; + s = - ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + if ( FLOATSIGNBITSET( a ) ) { + return s - HALF_PI; + } else { + return s + HALF_PI; + } + } else { + a = y / x; + s = a * a; + return ( ( ( ( ( ( ( ( ( 0.0028662257f * s - 0.0161657367f ) * s + 0.0429096138f ) * s - 0.0752896400f ) + * s + 0.1065626393f ) * s - 0.1420889944f ) * s + 0.1999355085f ) * s - 0.3333314528f ) * s ) + 1.0f ) * a; + } +} + +ID_INLINE double idMath::ATan64( float y, float x ) { + return atan2( y, x ); +} +#endif + +ID_INLINE float idMath::Pow( float x, float y ) { + return powf( x, y ); +} + +ID_INLINE float idMath::Pow16( float x, float y ) { + return Exp16( y * Log16( x ) ); +} + +ID_INLINE double idMath::Pow64( float x, float y ) { + return pow( x, y ); +} + +ID_INLINE float idMath::Exp( float f ) { + return expf( f ); +} + +ID_INLINE float idMath::Exp16( float f ) { + int i, s, e, m, exponent; + float x, x2, y, p, q; + + x = f * 1.44269504088896340f; // multiply with ( 1 / log( 2 ) ) +#if 1 + i = *reinterpret_cast( &x ); + s = ( i >> IEEE_FLT_SIGN_BIT ); + e = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + m = ( i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ) ) | ( 1 << IEEE_FLT_MANTISSA_BITS ); + i = ( ( m >> ( IEEE_FLT_MANTISSA_BITS - e ) ) & ~( e >> 31 ) ) ^ s; +#else + i = (int) x; + if ( x < 0.0f ) { + i--; + } +#endif + exponent = ( i + IEEE_FLT_EXPONENT_BIAS ) << IEEE_FLT_MANTISSA_BITS; + y = *reinterpret_cast( &exponent ); + x -= (float) i; + if ( x >= 0.5f ) { + x -= 0.5f; + y *= 1.4142135623730950488f; // multiply with sqrt( 2 ) + } + x2 = x * x; + p = x * ( 7.2152891511493f + x2 * 0.0576900723731f ); + q = 20.8189237930062f + x2; + x = y * ( q + p ) / ( q - p ); + return x; +} + +ID_INLINE double idMath::Exp64( float f ) { + return exp( f ); +} + +ID_INLINE float idMath::Log( float f ) { + return logf( f ); +} + +ID_INLINE float idMath::Log16( float f ) { + int i, exponent; + float y, y2; + + i = *reinterpret_cast( &f ); + exponent = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + i -= ( exponent + 1 ) << IEEE_FLT_MANTISSA_BITS; // get value in the range [.5, 1> + y = *reinterpret_cast( &i ); + y *= 1.4142135623730950488f; // multiply with sqrt( 2 ) + y = ( y - 1.0f ) / ( y + 1.0f ); + y2 = y * y; + y = y * ( 2.000000000046727f + y2 * ( 0.666666635059382f + y2 * ( 0.4000059794795f + y2 * ( 0.28525381498f + y2 * 0.2376245609f ) ) ) ); + y += 0.693147180559945f * ( (float)exponent + 0.5f ); + return y; +} + +ID_INLINE double idMath::Log64( float f ) { + return log( f ); +} + +ID_INLINE int idMath::IPow( int x, int y ) { + int r; for( r = x; y > 1; y-- ) { r *= x; } return r; +} + +ID_INLINE int idMath::ILog2( float f ) { + return ( ( ( *reinterpret_cast( &f ) ) >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; +} + +ID_INLINE int idMath::ILog2( int i ) { + return ILog2( (float)i ); +} + +ID_INLINE int idMath::BitsForFloat( float f ) { + return ILog2( f ) + 1; +} + +ID_INLINE int idMath::BitsForInteger( int i ) { + return ILog2( (float)i ) + 1; +} + +ID_INLINE int idMath::MaskForFloatSign( float f ) { + return ( ( *reinterpret_cast( &f ) ) >> 31 ); +} + +ID_INLINE int idMath::MaskForIntegerSign( int i ) { + return ( i >> 31 ); +} + +ID_INLINE int idMath::FloorPowerOfTwo( int x ) { + return CeilPowerOfTwo( x ) >> 1; +} + +ID_INLINE int idMath::CeilPowerOfTwo( int x ) { + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return x; +} + +ID_INLINE bool idMath::IsPowerOfTwo( int x ) { + return ( x & ( x - 1 ) ) == 0 && x > 0; +} + +ID_INLINE int idMath::BitCount( int x ) { + x -= ( ( x >> 1 ) & 0x55555555 ); + x = ( ( ( x >> 2 ) & 0x33333333 ) + ( x & 0x33333333 ) ); + x = ( ( ( x >> 4 ) + x ) & 0x0f0f0f0f ); + x += ( x >> 8 ); + return ( ( x + ( x >> 16 ) ) & 0x0000003f ); +} + +ID_INLINE int idMath::BitReverse( int x ) { + x = ( ( ( x >> 1 ) & 0x55555555 ) | ( ( x & 0x55555555 ) << 1 ) ); + x = ( ( ( x >> 2 ) & 0x33333333 ) | ( ( x & 0x33333333 ) << 2 ) ); + x = ( ( ( x >> 4 ) & 0x0f0f0f0f ) | ( ( x & 0x0f0f0f0f ) << 4 ) ); + x = ( ( ( x >> 8 ) & 0x00ff00ff ) | ( ( x & 0x00ff00ff ) << 8 ) ); + return ( ( x >> 16 ) | ( x << 16 ) ); +} + +ID_INLINE int idMath::Abs( int x ) { + int y = x >> 31; + return ( ( x ^ y ) - y ); +} + +ID_INLINE float idMath::Fabs( float f ) { + int tmp = *reinterpret_cast( &f ); + tmp &= 0x7FFFFFFF; + return *reinterpret_cast( &tmp ); +} + +ID_INLINE float idMath::Floor( float f ) { + return floorf( f ); +} + +ID_INLINE float idMath::Ceil( float f ) { + return ceilf( f ); +} + +ID_INLINE float idMath::Rint( float f ) { + return floorf( f + 0.5f ); +} + +ID_INLINE int idMath::Ftoi( float f ) { +#ifdef ID_WIN_X86_SSE + // If a converted result is larger than the maximum signed doubleword integer, + // the floating-point invalid exception is raised, and if this exception is masked, + // the indefinite integer value (80000000H) is returned. + int i; + __asm cvttss2si eax, f + __asm mov i, eax + return i; +#else + // If a converted result is larger than the maximum signed doubleword integer the result is undefined. + return (int) f; +#endif +} + +ID_INLINE int idMath::FtoiFast( float f ) { +#ifdef ID_WIN_X86_ASM + int i; + __asm fld f + __asm fistp i // use default rouding mode (round nearest) + return i; +#elif 0 // round chop (C/C++ standard) + int i, s, e, m, shift; + i = *reinterpret_cast( &f ); + s = i >> IEEE_FLT_SIGN_BIT; + e = ( ( i >> IEEE_FLT_MANTISSA_BITS ) & ( ( 1 << IEEE_FLT_EXPONENT_BITS ) - 1 ) ) - IEEE_FLT_EXPONENT_BIAS; + m = ( i & ( ( 1 << IEEE_FLT_MANTISSA_BITS ) - 1 ) ) | ( 1 << IEEE_FLT_MANTISSA_BITS ); + shift = e - IEEE_FLT_MANTISSA_BITS; + return ( ( ( ( m >> -shift ) | ( m << shift ) ) & ~( e >> 31 ) ) ^ s ) - s; +#elif defined( __linux__ ) + #ifdef __i386__ + int i; + __asm__ __volatile__ ( + "flds %1\n\t" + "fistpl %0\n\t" + : "=m"(i) : "m"(f)); + return i; + #else + // lrintf is equivalent but only inlines at -O3 + // although that should be more portable + return lrintf( f ); + #endif +#elif defined( MACOS_X ) + return lrintf( f ); +#else + return (int) f; +#endif +} + +ID_INLINE byte idMath::Ftob( float f ) { +#ifdef ID_WIN_X86_SSE + // If a converted result is negative the value (0) is returned and if the + // converted result is larger than the maximum byte the value (255) is returned. + byte b; + __asm movss xmm0, f + __asm maxss xmm0, SSE_FLOAT_ZERO + __asm minss xmm0, SSE_FLOAT_255 + __asm cvttss2si eax, xmm0 + __asm mov b, al + return b; +#else + // If a converted result is clamped to the range [0-255]. + int i; + i = (int) f; + if ( i < 0 ) { + return 0; + } else if ( i > 255 ) { + return 255; + } + return i; +#endif +} + +ID_INLINE signed char idMath::ClampChar( int i ) { + if ( i < -128 ) { + return -128; + } + if ( i > 127 ) { + return 127; + } + return i; +} + +// RAVEN BEGIN +ID_INLINE byte idMath::ClampByte( int i ) +{ + if( i < 0 ) + { + return( 0 ); + } + if( i > 255 ) + { + return( 255 ); + } + return( i ); +} +// RAVEN END + +ID_INLINE signed short idMath::ClampShort( int i ) { + if ( i < -32768 ) { + return -32768; + } + if ( i > 32767 ) { + return 32767; + } + return i; +} + +ID_INLINE int idMath::ClampInt( int min, int max, int value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +ID_INLINE float idMath::ClampFloat( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +ID_INLINE float idMath::AngleNormalize360( float angle ) { + if ( ( angle >= 360.0f ) || ( angle < 0.0f ) ) { + angle -= floor( angle / 360.0f ) * 360.0f; + } + return angle; +} + +ID_INLINE float idMath::AngleNormalize180( float angle ) { + angle = AngleNormalize360( angle ); + if ( angle > 180.0f ) { + angle -= 360.0f; + } + return angle; +} + +ID_INLINE float idMath::AngleDelta( float angle1, float angle2 ) { + return AngleNormalize180( angle1 - angle2 ); +} + +ID_INLINE int idMath::FloatHash( const float *array, const int numFloats ) { + int i, hash = 0; + const int *ptr; + + ptr = reinterpret_cast( array ); + for ( i = 0; i < numFloats; i++ ) { + hash ^= ptr[i]; + } + return hash; +} + +// RAVEN BEGIN +// jscott: fast and reliable random routines + +// This is the VC libc version of rand() without multiple seeds per thread or 12 levels +// of subroutine calls. +// Both calls have been designed to minimise the inherent number of float <--> int +// conversions and the additional math required to get the desired value. +// eg the typical tint = (rand() * 255) / 32768 +// becomes tint = rvRandom::irand( 0, 255 ) + +class rvRandom { +private: + static unsigned long mSeed; +public: + rvRandom( void ) { mSeed = 0x89abcdef; } + + // for a non seed based init + static int Init( void ); + + // Init the seed to a unique number + static void Init( unsigned long seed ) { mSeed = seed; } + + // Returns a float min <= x < max (exclusive; will get max - 0.00001; but never max) + static float flrand( float min, float max ); + + // Returns a float min <= 0 < 1.0 + static float flrand(); + + static float flrand( const idVec2& v ); + + // Returns an integer min <= x <= max (ie inclusive) + static int irand( int min, int max ); +}; + +// RAVEN END + +#endif /* !__MATH_MATH_H__ */ diff --git a/src/idlib/math/Matrix.cpp b/src/idlib/math/Matrix.cpp new file mode 100644 index 0000000..67de945 --- /dev/null +++ b/src/idlib/math/Matrix.cpp @@ -0,0 +1,8252 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +//=============================================================== +// +// idMat2 +// +//=============================================================== + +idMat2 mat2_zero( idVec2( 0, 0 ), idVec2( 0, 0 ) ); +idMat2 mat2_identity( idVec2( 1, 0 ), idVec2( 0, 1 ) ); + +/* +============ +idMat2::InverseSelf +============ +*/ +bool idMat2::InverseSelf( void ) { + // 2+4 = 6 multiplications + // 1 division + double det, invDet, a; + + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = mat[0][0]; + mat[0][0] = mat[1][1] * invDet; + mat[0][1] = - mat[0][1] * invDet; + mat[1][0] = - mat[1][0] * invDet; + mat[1][1] = a * invDet; + + return true; +} + +/* +============ +idMat2::InverseFastSelf +============ +*/ +bool idMat2::InverseFastSelf( void ) { +#if 1 + // 2+4 = 6 multiplications + // 1 division + double det, invDet, a; + + det = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = mat[0][0]; + mat[0][0] = mat[1][1] * invDet; + mat[0][1] = - mat[0][1] * invDet; + mat[1][0] = - mat[1][0] * invDet; + mat[1][1] = a * invDet; + + return true; +#else + // 2*4 = 8 multiplications + // 2 division + float *mat = reinterpret_cast(this); + double d, di; + float s; + + di = mat[0]; + s = di; + mat[0*2+0] = d = 1.0f / di; + mat[0*2+1] *= d; + d = -d; + mat[1*2+0] *= d; + d = mat[1*2+0] * di; + mat[1*2+1] += mat[0*2+1] * d; + di = mat[1*2+1]; + s *= di; + mat[1*2+1] = d = 1.0f / di; + mat[1*2+0] *= d; + d = -d; + mat[0*2+1] *= d; + d = mat[0*2+1] * di; + mat[0*2+0] += mat[1*2+0] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#endif +} + +/* +============= +idMat2::ToString +============= +*/ +const char *idMat2::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat3 +// +//=============================================================== + +idMat3 mat3_zero( idVec3( 0, 0, 0 ), idVec3( 0, 0, 0 ), idVec3( 0, 0, 0 ) ); +idMat3 mat3_identity( idVec3( 1, 0, 0 ), idVec3( 0, 1, 0 ), idVec3( 0, 0, 1 ) ); + +/* +============ +idMat3::ToAngles +============ +*/ +idAngles idMat3::ToAngles( void ) const { + idAngles angles; + double theta; + double cp; + float sp; + + sp = mat[ 0 ][ 2 ]; + + // cap off our sin value so that we don't get any NANs + if ( sp > 1.0f ) { + sp = 1.0f; + } else if ( sp < -1.0f ) { + sp = -1.0f; + } + + theta = -asin( sp ); + cp = idMath::Cos( theta ); + +// RAVEN BEGIN +// jscott: renamed to prevent name clash + if ( cp > 8192.0f * idMath::FLOAT_EPSILON ) { +// RAVEN END + angles.pitch = RAD2DEG( theta ); + angles.yaw = RAD2DEG( idMath::ATan( mat[ 0 ][ 1 ], mat[ 0 ][ 0 ] ) ); + angles.roll = RAD2DEG( idMath::ATan( mat[ 1 ][ 2 ], mat[ 2 ][ 2 ] ) ); + } else { + angles.pitch = RAD2DEG( theta ); + angles.yaw = RAD2DEG( -idMath::ATan( mat[ 1 ][ 0 ], mat[ 1 ][ 1 ] ) ); + angles.roll = 0; + } + return angles; +} + +// RAVEN BEGIN + +// idMat3::RotateAbsolute -- rotates around world x, y, or z +void idMat3::RotateAbsolute(int whichAxis, float howManyDegrees) +{ + idMat3 rotation; + +// RAVEN BEGIN + float sinVal, cosVal; + + idMath::SinCos( DEG2RAD( howManyDegrees), sinVal, cosVal ); +// RAVEN END + + rotation.Identity(); + switch(whichAxis) + { + case 0: // x-axis + rotation[1][1] = cosVal; + rotation[1][2] = sinVal; + rotation[2][1] = -sinVal; + rotation[2][2] = cosVal; + break; + case 1: // y-axis + rotation[0][0] = cosVal; + rotation[0][2] = -sinVal; + rotation[2][0] = sinVal; + rotation[2][2] = cosVal; + break; + case 2: // z-axis + rotation[0][0] = cosVal; + rotation[0][1] = sinVal; + rotation[1][0] = -sinVal; + rotation[1][1] = cosVal; + break; + default: + assert(!"idMat3::RotateAbsolute -- valid axis numbers are 0, 1, and 2"); + return; + } + *this *= rotation; +} + +// idMat3::RotateRelative -- rotates around the fwd, left, or up vector of the matrix +void idMat3::RotateRelative(int whichAxis, float howManyDegrees) +{ + idMat3 rotation; + +// RAVEN BEGIN + float sinVal, cosVal; + + idMath::SinCos( DEG2RAD( howManyDegrees), sinVal, cosVal ); +// RAVEN END + + rotation.Identity(); + switch(whichAxis) + { + case 0: // x-axis + rotation[1][1] = cosVal; + rotation[1][2] = sinVal; + rotation[2][1] = -sinVal; + rotation[2][2] = cosVal; + break; + case 1: // y-axis + rotation[0][0] = cosVal; + rotation[0][2] = -sinVal; + rotation[2][0] = sinVal; + rotation[2][2] = cosVal; + break; + case 2: // z-axis + rotation[0][0] = cosVal; + rotation[0][1] = sinVal; + rotation[1][0] = -sinVal; + rotation[1][1] = cosVal; + break; + default: + assert(!"idMat3::RotateRelative -- valid axis numbers are 0, 1, and 2"); + return; + } + *this = rotation * (*this); +} + +// idMat3::RotateArbitrary -- rotates around the given unit vector +void idMat3::RotateArbitrary(const idVec3 &rotAxis, float howManyDegrees) +{ +// RAVEN BEGIN + float sinVal, cosVal; + + idMath::SinCos( DEG2RAD( howManyDegrees), sinVal, cosVal ); +// RAVEN END + + float d = idMath::Sqrt(rotAxis[1]*rotAxis[1] + rotAxis[2]*rotAxis[2]); + + // if the rotation axis turns out to be one of the world axes, just do a RotateAbsolute on it + if (rotAxis.Compare(idVec3(1, 0, 0))) + { + RotateAbsolute(0, howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(-1, 0, 0))) + { + RotateAbsolute(0, -howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, 1, 0))) + { + RotateAbsolute(1, howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, -1, 0))) + { + RotateAbsolute(1, -howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, 0, 1))) + { + RotateAbsolute(2, howManyDegrees); + return; + } + if (rotAxis.Compare(idVec3(0, 0, -1))) + { + RotateAbsolute(2, -howManyDegrees); + return; + } + + + idMat3 rotationX(1, 0, 0, 0, rotAxis[2]/d, rotAxis[1]/d, 0, -rotAxis[1]/d, rotAxis[2]/d); + idMat3 rotationY(d, 0, rotAxis[0], 0, 1, 0, -rotAxis[0], 0, d); + idMat3 rotationZ(cosVal, sinVal, 0, -sinVal, cosVal, 0, 0, 0, 1); + idMat3 rotationXinv = rotationX.Inverse(); + idMat3 rotationYinv = rotationY.Inverse(); + idMat3 tempMat; + + tempMat = rotationYinv * rotationXinv; + tempMat = rotationZ * tempMat; + tempMat = rotationY * tempMat; + tempMat = rotationX * tempMat; + *this = *this * tempMat; +} + +// RAVEN END + +/* +============ +idMat3::ToQuat +============ +*/ +idQuat idMat3::ToQuat( void ) const { + idQuat q; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[ 3 ] = { 1, 2, 0 }; + + trace = mat[ 0 ][ 0 ] + mat[ 1 ][ 1 ] + mat[ 2 ][ 2 ]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + q[3] = s * t; + q[0] = ( mat[ 2 ][ 1 ] - mat[ 1 ][ 2 ] ) * s; + q[1] = ( mat[ 0 ][ 2 ] - mat[ 2 ][ 0 ] ) * s; + q[2] = ( mat[ 1 ][ 0 ] - mat[ 0 ][ 1 ] ) * s; + + } else { + + i = 0; + if ( mat[ 1 ][ 1 ] > mat[ 0 ][ 0 ] ) { + i = 1; + } + if ( mat[ 2 ][ 2 ] > mat[ i ][ i ] ) { + i = 2; + } + j = next[ i ]; + k = next[ j ]; + + t = ( mat[ i ][ i ] - ( mat[ j ][ j ] + mat[ k ][ k ] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + q[i] = s * t; + q[3] = ( mat[ k ][ j ] - mat[ j ][ k ] ) * s; + q[j] = ( mat[ j ][ i ] + mat[ i ][ j ] ) * s; + q[k] = ( mat[ k ][ i ] + mat[ i ][ k ] ) * s; + } + return q; +} + +/* +============ +idMat3::ToCQuat +============ +*/ +idCQuat idMat3::ToCQuat( void ) const { + idQuat q = ToQuat(); + if ( q.w < 0.0f ) { + return idCQuat( -q.x, -q.y, -q.z ); + } + return idCQuat( q.x, q.y, q.z ); +} + +/* +============ +idMat3::ToRotation +============ +*/ +idRotation idMat3::ToRotation( void ) const { + idRotation r; + float trace; + float s; + float t; + int i; + int j; + int k; + static int next[ 3 ] = { 1, 2, 0 }; + + trace = mat[ 0 ][ 0 ] + mat[ 1 ][ 1 ] + mat[ 2 ][ 2 ]; + if ( trace > 0.0f ) { + + t = trace + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + r.angle = s * t; + r.vec[0] = ( mat[ 2 ][ 1 ] - mat[ 1 ][ 2 ] ) * s; + r.vec[1] = ( mat[ 0 ][ 2 ] - mat[ 2 ][ 0 ] ) * s; + r.vec[2] = ( mat[ 1 ][ 0 ] - mat[ 0 ][ 1 ] ) * s; + + } else { + + i = 0; + if ( mat[ 1 ][ 1 ] > mat[ 0 ][ 0 ] ) { + i = 1; + } + if ( mat[ 2 ][ 2 ] > mat[ i ][ i ] ) { + i = 2; + } + j = next[ i ]; + k = next[ j ]; + + t = ( mat[ i ][ i ] - ( mat[ j ][ j ] + mat[ k ][ k ] ) ) + 1.0f; + s = idMath::InvSqrt( t ) * 0.5f; + + r.vec[i] = s * t; + r.angle = ( mat[ k ][ j ] - mat[ j ][ k ] ) * s; + r.vec[j] = ( mat[ j ][ i ] + mat[ i ][ j ] ) * s; + r.vec[k] = ( mat[ k ][ i ] + mat[ i ][ k ] ) * s; + } + r.angle = idMath::ACos( r.angle ); + if ( idMath::Fabs( r.angle ) < 1e-10f ) { + r.vec.Set( 0.0f, 0.0f, 1.0f ); + r.angle = 0.0f; + } else { + //vec *= (1.0f / sin( angle )); + r.vec.Normalize(); + r.vec.FixDegenerateNormal(); + r.angle *= 2.0f * idMath::M_RAD2DEG; + } + + r.origin.Zero(); + r.axis = *this; + r.axisValid = true; + return r; +} + +/* +================= +idMat3::ToAngularVelocity +================= +*/ +idVec3 idMat3::ToAngularVelocity( void ) const { + idRotation rotation = ToRotation(); + return rotation.GetVec() * DEG2RAD( rotation.GetAngle() ); +} + +/* +============ +idMat3::Determinant +============ +*/ +float idMat3::Determinant( void ) const { + + float det2_12_01 = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + float det2_12_02 = mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]; + float det2_12_12 = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + + return mat[0][0] * det2_12_12 - mat[0][1] * det2_12_02 + mat[0][2] * det2_12_01; +} + +/* +============ +idMat3::InverseSelf +============ +*/ +bool idMat3::InverseSelf( void ) { + // 18+3+9 = 30 multiplications + // 1 division + idMat3 inverse; + double det, invDet; + + inverse[0][0] = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + inverse[1][0] = mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]; + inverse[2][0] = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + + det = mat[0][0] * inverse[0][0] + mat[0][1] * inverse[1][0] + mat[0][2] * inverse[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + inverse[0][1] = mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]; + inverse[0][2] = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + inverse[1][1] = mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]; + inverse[1][2] = mat[0][2] * mat[1][0] - mat[0][0] * mat[1][2]; + inverse[2][1] = mat[0][1] * mat[2][0] - mat[0][0] * mat[2][1]; + inverse[2][2] = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + mat[0][0] = inverse[0][0] * invDet; + mat[0][1] = inverse[0][1] * invDet; + mat[0][2] = inverse[0][2] * invDet; + + mat[1][0] = inverse[1][0] * invDet; + mat[1][1] = inverse[1][1] * invDet; + mat[1][2] = inverse[1][2] * invDet; + + mat[2][0] = inverse[2][0] * invDet; + mat[2][1] = inverse[2][1] * invDet; + mat[2][2] = inverse[2][2] * invDet; + + return true; +} + +/* +============ +idMat3::InverseFastSelf +============ +*/ +bool idMat3::InverseFastSelf( void ) { +#if 1 + // 18+3+9 = 30 multiplications + // 1 division + idMat3 inverse; + double det, invDet; + + inverse[0][0] = mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]; + inverse[1][0] = mat[1][2] * mat[2][0] - mat[1][0] * mat[2][2]; + inverse[2][0] = mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]; + + det = mat[0][0] * inverse[0][0] + mat[0][1] * inverse[1][0] + mat[0][2] * inverse[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + inverse[0][1] = mat[0][2] * mat[2][1] - mat[0][1] * mat[2][2]; + inverse[0][2] = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + inverse[1][1] = mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]; + inverse[1][2] = mat[0][2] * mat[1][0] - mat[0][0] * mat[1][2]; + inverse[2][1] = mat[0][1] * mat[2][0] - mat[0][0] * mat[2][1]; + inverse[2][2] = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + + mat[0][0] = inverse[0][0] * invDet; + mat[0][1] = inverse[0][1] * invDet; + mat[0][2] = inverse[0][2] * invDet; + + mat[1][0] = inverse[1][0] * invDet; + mat[1][1] = inverse[1][1] * invDet; + mat[1][2] = inverse[1][2] * invDet; + + mat[2][0] = inverse[2][0] * invDet; + mat[2][1] = inverse[2][1] * invDet; + mat[2][2] = inverse[2][2] * invDet; + + return true; +#elif 0 + // 3*10 = 30 multiplications + // 3 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + d = -d; + mat[3] *= d; + mat[6] *= d; + d = mat[3] * di; + mat[4] += mat[1] * d; + mat[5] += mat[2] * d; + d = mat[6] * di; + mat[7] += mat[1] * d; + mat[8] += mat[2] * d; + di = mat[4]; + s *= di; + mat[4] = d = 1.0f / di; + mat[3] *= d; + mat[5] *= d; + d = -d; + mat[1] *= d; + mat[7] *= d; + d = mat[1] * di; + mat[0] += mat[3] * d; + mat[2] += mat[5] * d; + d = mat[7] * di; + mat[6] += mat[3] * d; + mat[8] += mat[5] * d; + di = mat[8]; + s *= di; + mat[8] = d = 1.0f / di; + mat[6] *= d; + mat[7] *= d; + d = -d; + mat[2] *= d; + mat[5] *= d; + d = mat[2] * di; + mat[0] += mat[6] * d; + mat[1] += mat[7] * d; + d = mat[5] * di; + mat[3] += mat[6] * d; + mat[4] += mat[7] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 4*2+4*4 = 24 multiplications + // 2*1 = 2 divisions + idMat2 r0; + float r1[2], r2[2], r3; + float det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); // 2x2 + det = mat[0*3+0] * mat[1*3+1] - mat[0*3+1] * mat[1*3+0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = mat[1*3+1] * invDet; + r0[0][1] = - mat[0*3+1] * invDet; + r0[1][0] = - mat[1*3+0] * invDet; + r0[1][1] = mat[0*3+0] * invDet; + + // r1 = r0 * m1; // 2x1 = 2x2 * 2x1 + r1[0] = r0[0][0] * mat[0*3+2] + r0[0][1] * mat[1*3+2]; + r1[1] = r0[1][0] * mat[0*3+2] + r0[1][1] * mat[1*3+2]; + + // r2 = m2 * r1; // 1x1 = 1x2 * 2x1 + r2[0] = mat[2*3+0] * r1[0] + mat[2*3+1] * r1[1]; + + // r3 = r2 - m3; // 1x1 = 1x1 - 1x1 + r3 = r2[0] - mat[2*3+2]; + + // r3.InverseSelf(); + if ( idMath::Fabs( r3 ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + r3 = 1.0f / r3; + + // r2 = m2 * r0; // 1x2 = 1x2 * 2x2 + r2[0] = mat[2*3+0] * r0[0][0] + mat[2*3+1] * r0[1][0]; + r2[1] = mat[2*3+0] * r0[0][1] + mat[2*3+1] * r0[1][1]; + + // m2 = r3 * r2; // 1x2 = 1x1 * 1x2 + mat[2*3+0] = r3 * r2[0]; + mat[2*3+1] = r3 * r2[1]; + + // m0 = r0 - r1 * m2; // 2x2 - 2x1 * 1x2 + mat[0*3+0] = r0[0][0] - r1[0] * mat[2*3+0]; + mat[0*3+1] = r0[0][1] - r1[0] * mat[2*3+1]; + mat[1*3+0] = r0[1][0] - r1[1] * mat[2*3+0]; + mat[1*3+1] = r0[1][1] - r1[1] * mat[2*3+1]; + + // m1 = r1 * r3; // 2x1 = 2x1 * 1x1 + mat[0*3+2] = r1[0] * r3; + mat[1*3+2] = r1[1] * r3; + + // m3 = -r3; + mat[2*3+2] = -r3; + + return true; +#endif +} + +/* +============ +idMat3::InertiaTranslate +============ +*/ +idMat3 idMat3::InertiaTranslate( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) const { + idMat3 m; + idVec3 newCenter; + + newCenter = centerOfMass + translation; + + m[0][0] = mass * ( ( centerOfMass[1] * centerOfMass[1] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[1] * newCenter[1] + newCenter[2] * newCenter[2] ) ); + m[1][1] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[0] * newCenter[0] + newCenter[2] * newCenter[2] ) ); + m[2][2] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[1] * centerOfMass[1] ) + - ( newCenter[0] * newCenter[0] + newCenter[1] * newCenter[1] ) ); + + m[0][1] = m[1][0] = mass * ( newCenter[0] * newCenter[1] - centerOfMass[0] * centerOfMass[1] ); + m[1][2] = m[2][1] = mass * ( newCenter[1] * newCenter[2] - centerOfMass[1] * centerOfMass[2] ); + m[0][2] = m[2][0] = mass * ( newCenter[0] * newCenter[2] - centerOfMass[0] * centerOfMass[2] ); + + return (*this) + m; +} + +/* +============ +idMat3::InertiaTranslateSelf +============ +*/ +idMat3 &idMat3::InertiaTranslateSelf( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) { + idMat3 m; + idVec3 newCenter; + + newCenter = centerOfMass + translation; + + m[0][0] = mass * ( ( centerOfMass[1] * centerOfMass[1] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[1] * newCenter[1] + newCenter[2] * newCenter[2] ) ); + m[1][1] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[2] * centerOfMass[2] ) + - ( newCenter[0] * newCenter[0] + newCenter[2] * newCenter[2] ) ); + m[2][2] = mass * ( ( centerOfMass[0] * centerOfMass[0] + centerOfMass[1] * centerOfMass[1] ) + - ( newCenter[0] * newCenter[0] + newCenter[1] * newCenter[1] ) ); + + m[0][1] = m[1][0] = mass * ( newCenter[0] * newCenter[1] - centerOfMass[0] * centerOfMass[1] ); + m[1][2] = m[2][1] = mass * ( newCenter[1] * newCenter[2] - centerOfMass[1] * centerOfMass[2] ); + m[0][2] = m[2][0] = mass * ( newCenter[0] * newCenter[2] - centerOfMass[0] * centerOfMass[2] ); + + (*this) += m; + + return (*this); +} + +/* +============ +idMat3::InertiaRotate +============ +*/ +idMat3 idMat3::InertiaRotate( const idMat3 &rotation ) const { + // NOTE: the rotation matrix is stored column-major + return rotation.Transpose() * (*this) * rotation; +} + +/* +============ +idMat3::InertiaRotateSelf +============ +*/ +idMat3 &idMat3::InertiaRotateSelf( const idMat3 &rotation ) { + // NOTE: the rotation matrix is stored column-major + *this = rotation.Transpose() * (*this) * rotation; + return *this; +} + +/* +============= +idMat3::ToString +============= +*/ +const char *idMat3::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat4 +// +//=============================================================== + +idMat4 mat4_zero( idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ), idVec4( 0, 0, 0, 0 ) ); +idMat4 mat4_identity( idVec4( 1, 0, 0, 0 ), idVec4( 0, 1, 0, 0 ), idVec4( 0, 0, 1, 0 ), idVec4( 0, 0, 0, 1 ) ); + +/* +============ +idMat4::Transpose +============ +*/ +idMat4 idMat4::Transpose( void ) const { + idMat4 transpose; + int i, j; + + for( i = 0; i < 4; i++ ) { + for( j = 0; j < 4; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat4::TransposeSelf +============ +*/ +idMat4 &idMat4::TransposeSelf( void ) { + float temp; + int i, j; + + for( i = 0; i < 4; i++ ) { + for( j = i + 1; j < 4; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat4::Determinant +============ +*/ +float idMat4::Determinant( void ) const { + + // 2x2 sub-determinants + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + return ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); +} + +/* +============ +idMat4::InverseSelf +============ +*/ +bool idMat4::InverseSelf( void ) { + // 84+4+16 = 104 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 4x4 determinant + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants required to calculate 4x4 determinant + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + det = ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_03_01 = mat[0][0] * mat[3][1] - mat[0][1] * mat[3][0]; + float det2_03_02 = mat[0][0] * mat[3][2] - mat[0][2] * mat[3][0]; + float det2_03_03 = mat[0][0] * mat[3][3] - mat[0][3] * mat[3][0]; + float det2_03_12 = mat[0][1] * mat[3][2] - mat[0][2] * mat[3][1]; + float det2_03_13 = mat[0][1] * mat[3][3] - mat[0][3] * mat[3][1]; + float det2_03_23 = mat[0][2] * mat[3][3] - mat[0][3] * mat[3][2]; + + float det2_13_01 = mat[1][0] * mat[3][1] - mat[1][1] * mat[3][0]; + float det2_13_02 = mat[1][0] * mat[3][2] - mat[1][2] * mat[3][0]; + float det2_13_03 = mat[1][0] * mat[3][3] - mat[1][3] * mat[3][0]; + float det2_13_12 = mat[1][1] * mat[3][2] - mat[1][2] * mat[3][1]; + float det2_13_13 = mat[1][1] * mat[3][3] - mat[1][3] * mat[3][1]; + float det2_13_23 = mat[1][2] * mat[3][3] - mat[1][3] * mat[3][2]; + + // remaining 3x3 sub-determinants + float det3_203_012 = mat[2][0] * det2_03_12 - mat[2][1] * det2_03_02 + mat[2][2] * det2_03_01; + float det3_203_013 = mat[2][0] * det2_03_13 - mat[2][1] * det2_03_03 + mat[2][3] * det2_03_01; + float det3_203_023 = mat[2][0] * det2_03_23 - mat[2][2] * det2_03_03 + mat[2][3] * det2_03_02; + float det3_203_123 = mat[2][1] * det2_03_23 - mat[2][2] * det2_03_13 + mat[2][3] * det2_03_12; + + float det3_213_012 = mat[2][0] * det2_13_12 - mat[2][1] * det2_13_02 + mat[2][2] * det2_13_01; + float det3_213_013 = mat[2][0] * det2_13_13 - mat[2][1] * det2_13_03 + mat[2][3] * det2_13_01; + float det3_213_023 = mat[2][0] * det2_13_23 - mat[2][2] * det2_13_03 + mat[2][3] * det2_13_02; + float det3_213_123 = mat[2][1] * det2_13_23 - mat[2][2] * det2_13_13 + mat[2][3] * det2_13_12; + + float det3_301_012 = mat[3][0] * det2_01_12 - mat[3][1] * det2_01_02 + mat[3][2] * det2_01_01; + float det3_301_013 = mat[3][0] * det2_01_13 - mat[3][1] * det2_01_03 + mat[3][3] * det2_01_01; + float det3_301_023 = mat[3][0] * det2_01_23 - mat[3][2] * det2_01_03 + mat[3][3] * det2_01_02; + float det3_301_123 = mat[3][1] * det2_01_23 - mat[3][2] * det2_01_13 + mat[3][3] * det2_01_12; + + mat[0][0] = - det3_213_123 * invDet; + mat[1][0] = + det3_213_023 * invDet; + mat[2][0] = - det3_213_013 * invDet; + mat[3][0] = + det3_213_012 * invDet; + + mat[0][1] = + det3_203_123 * invDet; + mat[1][1] = - det3_203_023 * invDet; + mat[2][1] = + det3_203_013 * invDet; + mat[3][1] = - det3_203_012 * invDet; + + mat[0][2] = + det3_301_123 * invDet; + mat[1][2] = - det3_301_023 * invDet; + mat[2][2] = + det3_301_013 * invDet; + mat[3][2] = - det3_301_012 * invDet; + + mat[0][3] = - det3_201_123 * invDet; + mat[1][3] = + det3_201_023 * invDet; + mat[2][3] = - det3_201_013 * invDet; + mat[3][3] = + det3_201_012 * invDet; + + return true; +} + +/* +============ +idMat4::InverseFastSelf +============ +*/ +bool idMat4::InverseFastSelf( void ) { +#if 0 + // 84+4+16 = 104 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 4x4 determinant + float det2_01_01 = mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; + float det2_01_02 = mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]; + float det2_01_03 = mat[0][0] * mat[1][3] - mat[0][3] * mat[1][0]; + float det2_01_12 = mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]; + float det2_01_13 = mat[0][1] * mat[1][3] - mat[0][3] * mat[1][1]; + float det2_01_23 = mat[0][2] * mat[1][3] - mat[0][3] * mat[1][2]; + + // 3x3 sub-determinants required to calculate 4x4 determinant + float det3_201_012 = mat[2][0] * det2_01_12 - mat[2][1] * det2_01_02 + mat[2][2] * det2_01_01; + float det3_201_013 = mat[2][0] * det2_01_13 - mat[2][1] * det2_01_03 + mat[2][3] * det2_01_01; + float det3_201_023 = mat[2][0] * det2_01_23 - mat[2][2] * det2_01_03 + mat[2][3] * det2_01_02; + float det3_201_123 = mat[2][1] * det2_01_23 - mat[2][2] * det2_01_13 + mat[2][3] * det2_01_12; + + det = ( - det3_201_123 * mat[3][0] + det3_201_023 * mat[3][1] - det3_201_013 * mat[3][2] + det3_201_012 * mat[3][3] ); + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_03_01 = mat[0][0] * mat[3][1] - mat[0][1] * mat[3][0]; + float det2_03_02 = mat[0][0] * mat[3][2] - mat[0][2] * mat[3][0]; + float det2_03_03 = mat[0][0] * mat[3][3] - mat[0][3] * mat[3][0]; + float det2_03_12 = mat[0][1] * mat[3][2] - mat[0][2] * mat[3][1]; + float det2_03_13 = mat[0][1] * mat[3][3] - mat[0][3] * mat[3][1]; + float det2_03_23 = mat[0][2] * mat[3][3] - mat[0][3] * mat[3][2]; + + float det2_13_01 = mat[1][0] * mat[3][1] - mat[1][1] * mat[3][0]; + float det2_13_02 = mat[1][0] * mat[3][2] - mat[1][2] * mat[3][0]; + float det2_13_03 = mat[1][0] * mat[3][3] - mat[1][3] * mat[3][0]; + float det2_13_12 = mat[1][1] * mat[3][2] - mat[1][2] * mat[3][1]; + float det2_13_13 = mat[1][1] * mat[3][3] - mat[1][3] * mat[3][1]; + float det2_13_23 = mat[1][2] * mat[3][3] - mat[1][3] * mat[3][2]; + + // remaining 3x3 sub-determinants + float det3_203_012 = mat[2][0] * det2_03_12 - mat[2][1] * det2_03_02 + mat[2][2] * det2_03_01; + float det3_203_013 = mat[2][0] * det2_03_13 - mat[2][1] * det2_03_03 + mat[2][3] * det2_03_01; + float det3_203_023 = mat[2][0] * det2_03_23 - mat[2][2] * det2_03_03 + mat[2][3] * det2_03_02; + float det3_203_123 = mat[2][1] * det2_03_23 - mat[2][2] * det2_03_13 + mat[2][3] * det2_03_12; + + float det3_213_012 = mat[2][0] * det2_13_12 - mat[2][1] * det2_13_02 + mat[2][2] * det2_13_01; + float det3_213_013 = mat[2][0] * det2_13_13 - mat[2][1] * det2_13_03 + mat[2][3] * det2_13_01; + float det3_213_023 = mat[2][0] * det2_13_23 - mat[2][2] * det2_13_03 + mat[2][3] * det2_13_02; + float det3_213_123 = mat[2][1] * det2_13_23 - mat[2][2] * det2_13_13 + mat[2][3] * det2_13_12; + + float det3_301_012 = mat[3][0] * det2_01_12 - mat[3][1] * det2_01_02 + mat[3][2] * det2_01_01; + float det3_301_013 = mat[3][0] * det2_01_13 - mat[3][1] * det2_01_03 + mat[3][3] * det2_01_01; + float det3_301_023 = mat[3][0] * det2_01_23 - mat[3][2] * det2_01_03 + mat[3][3] * det2_01_02; + float det3_301_123 = mat[3][1] * det2_01_23 - mat[3][2] * det2_01_13 + mat[3][3] * det2_01_12; + + mat[0][0] = - det3_213_123 * invDet; + mat[1][0] = + det3_213_023 * invDet; + mat[2][0] = - det3_213_013 * invDet; + mat[3][0] = + det3_213_012 * invDet; + + mat[0][1] = + det3_203_123 * invDet; + mat[1][1] = - det3_203_023 * invDet; + mat[2][1] = + det3_203_013 * invDet; + mat[3][1] = - det3_203_012 * invDet; + + mat[0][2] = + det3_301_123 * invDet; + mat[1][2] = - det3_301_023 * invDet; + mat[2][2] = + det3_301_013 * invDet; + mat[3][2] = - det3_301_012 * invDet; + + mat[0][3] = - det3_201_123 * invDet; + mat[1][3] = + det3_201_023 * invDet; + mat[2][3] = - det3_201_013 * invDet; + mat[3][3] = + det3_201_012 * invDet; + + return true; +#elif 0 + // 4*18 = 72 multiplications + // 4 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + d = -d; + mat[4] *= d; + mat[8] *= d; + mat[12] *= d; + d = mat[4] * di; + mat[5] += mat[1] * d; + mat[6] += mat[2] * d; + mat[7] += mat[3] * d; + d = mat[8] * di; + mat[9] += mat[1] * d; + mat[10] += mat[2] * d; + mat[11] += mat[3] * d; + d = mat[12] * di; + mat[13] += mat[1] * d; + mat[14] += mat[2] * d; + mat[15] += mat[3] * d; + di = mat[5]; + s *= di; + mat[5] = d = 1.0f / di; + mat[4] *= d; + mat[6] *= d; + mat[7] *= d; + d = -d; + mat[1] *= d; + mat[9] *= d; + mat[13] *= d; + d = mat[1] * di; + mat[0] += mat[4] * d; + mat[2] += mat[6] * d; + mat[3] += mat[7] * d; + d = mat[9] * di; + mat[8] += mat[4] * d; + mat[10] += mat[6] * d; + mat[11] += mat[7] * d; + d = mat[13] * di; + mat[12] += mat[4] * d; + mat[14] += mat[6] * d; + mat[15] += mat[7] * d; + di = mat[10]; + s *= di; + mat[10] = d = 1.0f / di; + mat[8] *= d; + mat[9] *= d; + mat[11] *= d; + d = -d; + mat[2] *= d; + mat[6] *= d; + mat[14] *= d; + d = mat[2] * di; + mat[0] += mat[8] * d; + mat[1] += mat[9] * d; + mat[3] += mat[11] * d; + d = mat[6] * di; + mat[4] += mat[8] * d; + mat[5] += mat[9] * d; + mat[7] += mat[11] * d; + d = mat[14] * di; + mat[12] += mat[8] * d; + mat[13] += mat[9] * d; + mat[15] += mat[11] * d; + di = mat[15]; + s *= di; + mat[15] = d = 1.0f / di; + mat[12] *= d; + mat[13] *= d; + mat[14] *= d; + d = -d; + mat[3] *= d; + mat[7] *= d; + mat[11] *= d; + d = mat[3] * di; + mat[0] += mat[12] * d; + mat[1] += mat[13] * d; + mat[2] += mat[14] * d; + d = mat[7] * di; + mat[4] += mat[12] * d; + mat[5] += mat[13] * d; + mat[6] += mat[14] * d; + d = mat[11] * di; + mat[8] += mat[12] * d; + mat[9] += mat[13] * d; + mat[10] += mat[14] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 6*8+2*6 = 60 multiplications + // 2*1 = 2 divisions + idMat2 r0, r1, r2, r3; + float a, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); + det = mat[0*4+0] * mat[1*4+1] - mat[0*4+1] * mat[1*4+0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = mat[1*4+1] * invDet; + r0[0][1] = - mat[0*4+1] * invDet; + r0[1][0] = - mat[1*4+0] * invDet; + r0[1][1] = mat[0*4+0] * invDet; + + // r1 = r0 * m1; + r1[0][0] = r0[0][0] * mat[0*4+2] + r0[0][1] * mat[1*4+2]; + r1[0][1] = r0[0][0] * mat[0*4+3] + r0[0][1] * mat[1*4+3]; + r1[1][0] = r0[1][0] * mat[0*4+2] + r0[1][1] * mat[1*4+2]; + r1[1][1] = r0[1][0] * mat[0*4+3] + r0[1][1] * mat[1*4+3]; + + // r2 = m2 * r1; + r2[0][0] = mat[2*4+0] * r1[0][0] + mat[2*4+1] * r1[1][0]; + r2[0][1] = mat[2*4+0] * r1[0][1] + mat[2*4+1] * r1[1][1]; + r2[1][0] = mat[3*4+0] * r1[0][0] + mat[3*4+1] * r1[1][0]; + r2[1][1] = mat[3*4+0] * r1[0][1] + mat[3*4+1] * r1[1][1]; + + // r3 = r2 - m3; + r3[0][0] = r2[0][0] - mat[2*4+2]; + r3[0][1] = r2[0][1] - mat[2*4+3]; + r3[1][0] = r2[1][0] - mat[3*4+2]; + r3[1][1] = r2[1][1] - mat[3*4+3]; + + // r3.InverseSelf(); + det = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + a = r3[0][0]; + r3[0][0] = r3[1][1] * invDet; + r3[0][1] = - r3[0][1] * invDet; + r3[1][0] = - r3[1][0] * invDet; + r3[1][1] = a * invDet; + + // r2 = m2 * r0; + r2[0][0] = mat[2*4+0] * r0[0][0] + mat[2*4+1] * r0[1][0]; + r2[0][1] = mat[2*4+0] * r0[0][1] + mat[2*4+1] * r0[1][1]; + r2[1][0] = mat[3*4+0] * r0[0][0] + mat[3*4+1] * r0[1][0]; + r2[1][1] = mat[3*4+0] * r0[0][1] + mat[3*4+1] * r0[1][1]; + + // m2 = r3 * r2; + mat[2*4+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0]; + mat[2*4+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1]; + mat[3*4+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0]; + mat[3*4+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1]; + + // m0 = r0 - r1 * m2; + mat[0*4+0] = r0[0][0] - r1[0][0] * mat[2*4+0] - r1[0][1] * mat[3*4+0]; + mat[0*4+1] = r0[0][1] - r1[0][0] * mat[2*4+1] - r1[0][1] * mat[3*4+1]; + mat[1*4+0] = r0[1][0] - r1[1][0] * mat[2*4+0] - r1[1][1] * mat[3*4+0]; + mat[1*4+1] = r0[1][1] - r1[1][0] * mat[2*4+1] - r1[1][1] * mat[3*4+1]; + + // m1 = r1 * r3; + mat[0*4+2] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0]; + mat[0*4+3] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1]; + mat[1*4+2] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0]; + mat[1*4+3] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1]; + + // m3 = -r3; + mat[2*4+2] = -r3[0][0]; + mat[2*4+3] = -r3[0][1]; + mat[3*4+2] = -r3[1][0]; + mat[3*4+3] = -r3[1][1]; + + return true; +#endif +} + +/* +============= +idMat4::ToString +============= +*/ +const char *idMat4::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat5 +// +//=============================================================== + +idMat5 mat5_zero( idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ), idVec5( 0, 0, 0, 0, 0 ) ); +idMat5 mat5_identity( idVec5( 1, 0, 0, 0, 0 ), idVec5( 0, 1, 0, 0, 0 ), idVec5( 0, 0, 1, 0, 0 ), idVec5( 0, 0, 0, 1, 0 ), idVec5( 0, 0, 0, 0, 1 ) ); + +/* +============ +idMat5::Transpose +============ +*/ +idMat5 idMat5::Transpose( void ) const { + idMat5 transpose; + int i, j; + + for( i = 0; i < 5; i++ ) { + for( j = 0; j < 5; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat5::TransposeSelf +============ +*/ +idMat5 &idMat5::TransposeSelf( void ) { + float temp; + int i, j; + + for( i = 0; i < 5; i++ ) { + for( j = i + 1; j < 5; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat5::Determinant +============ +*/ +float idMat5::Determinant( void ) const { + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + return mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; +} + +/* +============ +idMat5::InverseSelf +============ +*/ +bool idMat5::InverseSelf( void ) { + // 280+5+25 = 310 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + det = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + + if( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_23_01 = mat[2][0] * mat[3][1] - mat[2][1] * mat[3][0]; + float det2_23_02 = mat[2][0] * mat[3][2] - mat[2][2] * mat[3][0]; + float det2_23_03 = mat[2][0] * mat[3][3] - mat[2][3] * mat[3][0]; + float det2_23_04 = mat[2][0] * mat[3][4] - mat[2][4] * mat[3][0]; + float det2_23_12 = mat[2][1] * mat[3][2] - mat[2][2] * mat[3][1]; + float det2_23_13 = mat[2][1] * mat[3][3] - mat[2][3] * mat[3][1]; + float det2_23_14 = mat[2][1] * mat[3][4] - mat[2][4] * mat[3][1]; + float det2_23_23 = mat[2][2] * mat[3][3] - mat[2][3] * mat[3][2]; + float det2_23_24 = mat[2][2] * mat[3][4] - mat[2][4] * mat[3][2]; + float det2_23_34 = mat[2][3] * mat[3][4] - mat[2][4] * mat[3][3]; + float det2_24_01 = mat[2][0] * mat[4][1] - mat[2][1] * mat[4][0]; + float det2_24_02 = mat[2][0] * mat[4][2] - mat[2][2] * mat[4][0]; + float det2_24_03 = mat[2][0] * mat[4][3] - mat[2][3] * mat[4][0]; + float det2_24_04 = mat[2][0] * mat[4][4] - mat[2][4] * mat[4][0]; + float det2_24_12 = mat[2][1] * mat[4][2] - mat[2][2] * mat[4][1]; + float det2_24_13 = mat[2][1] * mat[4][3] - mat[2][3] * mat[4][1]; + float det2_24_14 = mat[2][1] * mat[4][4] - mat[2][4] * mat[4][1]; + float det2_24_23 = mat[2][2] * mat[4][3] - mat[2][3] * mat[4][2]; + float det2_24_24 = mat[2][2] * mat[4][4] - mat[2][4] * mat[4][2]; + float det2_24_34 = mat[2][3] * mat[4][4] - mat[2][4] * mat[4][3]; + + // remaining 3x3 sub-determinants + float det3_123_012 = mat[1][0] * det2_23_12 - mat[1][1] * det2_23_02 + mat[1][2] * det2_23_01; + float det3_123_013 = mat[1][0] * det2_23_13 - mat[1][1] * det2_23_03 + mat[1][3] * det2_23_01; + float det3_123_014 = mat[1][0] * det2_23_14 - mat[1][1] * det2_23_04 + mat[1][4] * det2_23_01; + float det3_123_023 = mat[1][0] * det2_23_23 - mat[1][2] * det2_23_03 + mat[1][3] * det2_23_02; + float det3_123_024 = mat[1][0] * det2_23_24 - mat[1][2] * det2_23_04 + mat[1][4] * det2_23_02; + float det3_123_034 = mat[1][0] * det2_23_34 - mat[1][3] * det2_23_04 + mat[1][4] * det2_23_03; + float det3_123_123 = mat[1][1] * det2_23_23 - mat[1][2] * det2_23_13 + mat[1][3] * det2_23_12; + float det3_123_124 = mat[1][1] * det2_23_24 - mat[1][2] * det2_23_14 + mat[1][4] * det2_23_12; + float det3_123_134 = mat[1][1] * det2_23_34 - mat[1][3] * det2_23_14 + mat[1][4] * det2_23_13; + float det3_123_234 = mat[1][2] * det2_23_34 - mat[1][3] * det2_23_24 + mat[1][4] * det2_23_23; + float det3_124_012 = mat[1][0] * det2_24_12 - mat[1][1] * det2_24_02 + mat[1][2] * det2_24_01; + float det3_124_013 = mat[1][0] * det2_24_13 - mat[1][1] * det2_24_03 + mat[1][3] * det2_24_01; + float det3_124_014 = mat[1][0] * det2_24_14 - mat[1][1] * det2_24_04 + mat[1][4] * det2_24_01; + float det3_124_023 = mat[1][0] * det2_24_23 - mat[1][2] * det2_24_03 + mat[1][3] * det2_24_02; + float det3_124_024 = mat[1][0] * det2_24_24 - mat[1][2] * det2_24_04 + mat[1][4] * det2_24_02; + float det3_124_034 = mat[1][0] * det2_24_34 - mat[1][3] * det2_24_04 + mat[1][4] * det2_24_03; + float det3_124_123 = mat[1][1] * det2_24_23 - mat[1][2] * det2_24_13 + mat[1][3] * det2_24_12; + float det3_124_124 = mat[1][1] * det2_24_24 - mat[1][2] * det2_24_14 + mat[1][4] * det2_24_12; + float det3_124_134 = mat[1][1] * det2_24_34 - mat[1][3] * det2_24_14 + mat[1][4] * det2_24_13; + float det3_124_234 = mat[1][2] * det2_24_34 - mat[1][3] * det2_24_24 + mat[1][4] * det2_24_23; + float det3_134_012 = mat[1][0] * det2_34_12 - mat[1][1] * det2_34_02 + mat[1][2] * det2_34_01; + float det3_134_013 = mat[1][0] * det2_34_13 - mat[1][1] * det2_34_03 + mat[1][3] * det2_34_01; + float det3_134_014 = mat[1][0] * det2_34_14 - mat[1][1] * det2_34_04 + mat[1][4] * det2_34_01; + float det3_134_023 = mat[1][0] * det2_34_23 - mat[1][2] * det2_34_03 + mat[1][3] * det2_34_02; + float det3_134_024 = mat[1][0] * det2_34_24 - mat[1][2] * det2_34_04 + mat[1][4] * det2_34_02; + float det3_134_034 = mat[1][0] * det2_34_34 - mat[1][3] * det2_34_04 + mat[1][4] * det2_34_03; + float det3_134_123 = mat[1][1] * det2_34_23 - mat[1][2] * det2_34_13 + mat[1][3] * det2_34_12; + float det3_134_124 = mat[1][1] * det2_34_24 - mat[1][2] * det2_34_14 + mat[1][4] * det2_34_12; + float det3_134_134 = mat[1][1] * det2_34_34 - mat[1][3] * det2_34_14 + mat[1][4] * det2_34_13; + float det3_134_234 = mat[1][2] * det2_34_34 - mat[1][3] * det2_34_24 + mat[1][4] * det2_34_23; + + // remaining 4x4 sub-determinants + float det4_0123_0123 = mat[0][0] * det3_123_123 - mat[0][1] * det3_123_023 + mat[0][2] * det3_123_013 - mat[0][3] * det3_123_012; + float det4_0123_0124 = mat[0][0] * det3_123_124 - mat[0][1] * det3_123_024 + mat[0][2] * det3_123_014 - mat[0][4] * det3_123_012; + float det4_0123_0134 = mat[0][0] * det3_123_134 - mat[0][1] * det3_123_034 + mat[0][3] * det3_123_014 - mat[0][4] * det3_123_013; + float det4_0123_0234 = mat[0][0] * det3_123_234 - mat[0][2] * det3_123_034 + mat[0][3] * det3_123_024 - mat[0][4] * det3_123_023; + float det4_0123_1234 = mat[0][1] * det3_123_234 - mat[0][2] * det3_123_134 + mat[0][3] * det3_123_124 - mat[0][4] * det3_123_123; + float det4_0124_0123 = mat[0][0] * det3_124_123 - mat[0][1] * det3_124_023 + mat[0][2] * det3_124_013 - mat[0][3] * det3_124_012; + float det4_0124_0124 = mat[0][0] * det3_124_124 - mat[0][1] * det3_124_024 + mat[0][2] * det3_124_014 - mat[0][4] * det3_124_012; + float det4_0124_0134 = mat[0][0] * det3_124_134 - mat[0][1] * det3_124_034 + mat[0][3] * det3_124_014 - mat[0][4] * det3_124_013; + float det4_0124_0234 = mat[0][0] * det3_124_234 - mat[0][2] * det3_124_034 + mat[0][3] * det3_124_024 - mat[0][4] * det3_124_023; + float det4_0124_1234 = mat[0][1] * det3_124_234 - mat[0][2] * det3_124_134 + mat[0][3] * det3_124_124 - mat[0][4] * det3_124_123; + float det4_0134_0123 = mat[0][0] * det3_134_123 - mat[0][1] * det3_134_023 + mat[0][2] * det3_134_013 - mat[0][3] * det3_134_012; + float det4_0134_0124 = mat[0][0] * det3_134_124 - mat[0][1] * det3_134_024 + mat[0][2] * det3_134_014 - mat[0][4] * det3_134_012; + float det4_0134_0134 = mat[0][0] * det3_134_134 - mat[0][1] * det3_134_034 + mat[0][3] * det3_134_014 - mat[0][4] * det3_134_013; + float det4_0134_0234 = mat[0][0] * det3_134_234 - mat[0][2] * det3_134_034 + mat[0][3] * det3_134_024 - mat[0][4] * det3_134_023; + float det4_0134_1234 = mat[0][1] * det3_134_234 - mat[0][2] * det3_134_134 + mat[0][3] * det3_134_124 - mat[0][4] * det3_134_123; + float det4_0234_0123 = mat[0][0] * det3_234_123 - mat[0][1] * det3_234_023 + mat[0][2] * det3_234_013 - mat[0][3] * det3_234_012; + float det4_0234_0124 = mat[0][0] * det3_234_124 - mat[0][1] * det3_234_024 + mat[0][2] * det3_234_014 - mat[0][4] * det3_234_012; + float det4_0234_0134 = mat[0][0] * det3_234_134 - mat[0][1] * det3_234_034 + mat[0][3] * det3_234_014 - mat[0][4] * det3_234_013; + float det4_0234_0234 = mat[0][0] * det3_234_234 - mat[0][2] * det3_234_034 + mat[0][3] * det3_234_024 - mat[0][4] * det3_234_023; + float det4_0234_1234 = mat[0][1] * det3_234_234 - mat[0][2] * det3_234_134 + mat[0][3] * det3_234_124 - mat[0][4] * det3_234_123; + + mat[0][0] = det4_1234_1234 * invDet; + mat[0][1] = -det4_0234_1234 * invDet; + mat[0][2] = det4_0134_1234 * invDet; + mat[0][3] = -det4_0124_1234 * invDet; + mat[0][4] = det4_0123_1234 * invDet; + + mat[1][0] = -det4_1234_0234 * invDet; + mat[1][1] = det4_0234_0234 * invDet; + mat[1][2] = -det4_0134_0234 * invDet; + mat[1][3] = det4_0124_0234 * invDet; + mat[1][4] = -det4_0123_0234 * invDet; + + mat[2][0] = det4_1234_0134 * invDet; + mat[2][1] = -det4_0234_0134 * invDet; + mat[2][2] = det4_0134_0134 * invDet; + mat[2][3] = -det4_0124_0134 * invDet; + mat[2][4] = det4_0123_0134 * invDet; + + mat[3][0] = -det4_1234_0124 * invDet; + mat[3][1] = det4_0234_0124 * invDet; + mat[3][2] = -det4_0134_0124 * invDet; + mat[3][3] = det4_0124_0124 * invDet; + mat[3][4] = -det4_0123_0124 * invDet; + + mat[4][0] = det4_1234_0123 * invDet; + mat[4][1] = -det4_0234_0123 * invDet; + mat[4][2] = det4_0134_0123 * invDet; + mat[4][3] = -det4_0124_0123 * invDet; + mat[4][4] = det4_0123_0123 * invDet; + + return true; +} + +/* +============ +idMat5::InverseFastSelf +============ +*/ +bool idMat5::InverseFastSelf( void ) { +#if 0 + // 280+5+25 = 310 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 5x5 determinant + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + + // 3x3 sub-determinants required to calculate 5x5 determinant + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + + // 4x4 sub-determinants required to calculate 5x5 determinant + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + + // determinant of 5x5 matrix + det = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + + if( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_23_01 = mat[2][0] * mat[3][1] - mat[2][1] * mat[3][0]; + float det2_23_02 = mat[2][0] * mat[3][2] - mat[2][2] * mat[3][0]; + float det2_23_03 = mat[2][0] * mat[3][3] - mat[2][3] * mat[3][0]; + float det2_23_04 = mat[2][0] * mat[3][4] - mat[2][4] * mat[3][0]; + float det2_23_12 = mat[2][1] * mat[3][2] - mat[2][2] * mat[3][1]; + float det2_23_13 = mat[2][1] * mat[3][3] - mat[2][3] * mat[3][1]; + float det2_23_14 = mat[2][1] * mat[3][4] - mat[2][4] * mat[3][1]; + float det2_23_23 = mat[2][2] * mat[3][3] - mat[2][3] * mat[3][2]; + float det2_23_24 = mat[2][2] * mat[3][4] - mat[2][4] * mat[3][2]; + float det2_23_34 = mat[2][3] * mat[3][4] - mat[2][4] * mat[3][3]; + float det2_24_01 = mat[2][0] * mat[4][1] - mat[2][1] * mat[4][0]; + float det2_24_02 = mat[2][0] * mat[4][2] - mat[2][2] * mat[4][0]; + float det2_24_03 = mat[2][0] * mat[4][3] - mat[2][3] * mat[4][0]; + float det2_24_04 = mat[2][0] * mat[4][4] - mat[2][4] * mat[4][0]; + float det2_24_12 = mat[2][1] * mat[4][2] - mat[2][2] * mat[4][1]; + float det2_24_13 = mat[2][1] * mat[4][3] - mat[2][3] * mat[4][1]; + float det2_24_14 = mat[2][1] * mat[4][4] - mat[2][4] * mat[4][1]; + float det2_24_23 = mat[2][2] * mat[4][3] - mat[2][3] * mat[4][2]; + float det2_24_24 = mat[2][2] * mat[4][4] - mat[2][4] * mat[4][2]; + float det2_24_34 = mat[2][3] * mat[4][4] - mat[2][4] * mat[4][3]; + + // remaining 3x3 sub-determinants + float det3_123_012 = mat[1][0] * det2_23_12 - mat[1][1] * det2_23_02 + mat[1][2] * det2_23_01; + float det3_123_013 = mat[1][0] * det2_23_13 - mat[1][1] * det2_23_03 + mat[1][3] * det2_23_01; + float det3_123_014 = mat[1][0] * det2_23_14 - mat[1][1] * det2_23_04 + mat[1][4] * det2_23_01; + float det3_123_023 = mat[1][0] * det2_23_23 - mat[1][2] * det2_23_03 + mat[1][3] * det2_23_02; + float det3_123_024 = mat[1][0] * det2_23_24 - mat[1][2] * det2_23_04 + mat[1][4] * det2_23_02; + float det3_123_034 = mat[1][0] * det2_23_34 - mat[1][3] * det2_23_04 + mat[1][4] * det2_23_03; + float det3_123_123 = mat[1][1] * det2_23_23 - mat[1][2] * det2_23_13 + mat[1][3] * det2_23_12; + float det3_123_124 = mat[1][1] * det2_23_24 - mat[1][2] * det2_23_14 + mat[1][4] * det2_23_12; + float det3_123_134 = mat[1][1] * det2_23_34 - mat[1][3] * det2_23_14 + mat[1][4] * det2_23_13; + float det3_123_234 = mat[1][2] * det2_23_34 - mat[1][3] * det2_23_24 + mat[1][4] * det2_23_23; + float det3_124_012 = mat[1][0] * det2_24_12 - mat[1][1] * det2_24_02 + mat[1][2] * det2_24_01; + float det3_124_013 = mat[1][0] * det2_24_13 - mat[1][1] * det2_24_03 + mat[1][3] * det2_24_01; + float det3_124_014 = mat[1][0] * det2_24_14 - mat[1][1] * det2_24_04 + mat[1][4] * det2_24_01; + float det3_124_023 = mat[1][0] * det2_24_23 - mat[1][2] * det2_24_03 + mat[1][3] * det2_24_02; + float det3_124_024 = mat[1][0] * det2_24_24 - mat[1][2] * det2_24_04 + mat[1][4] * det2_24_02; + float det3_124_034 = mat[1][0] * det2_24_34 - mat[1][3] * det2_24_04 + mat[1][4] * det2_24_03; + float det3_124_123 = mat[1][1] * det2_24_23 - mat[1][2] * det2_24_13 + mat[1][3] * det2_24_12; + float det3_124_124 = mat[1][1] * det2_24_24 - mat[1][2] * det2_24_14 + mat[1][4] * det2_24_12; + float det3_124_134 = mat[1][1] * det2_24_34 - mat[1][3] * det2_24_14 + mat[1][4] * det2_24_13; + float det3_124_234 = mat[1][2] * det2_24_34 - mat[1][3] * det2_24_24 + mat[1][4] * det2_24_23; + float det3_134_012 = mat[1][0] * det2_34_12 - mat[1][1] * det2_34_02 + mat[1][2] * det2_34_01; + float det3_134_013 = mat[1][0] * det2_34_13 - mat[1][1] * det2_34_03 + mat[1][3] * det2_34_01; + float det3_134_014 = mat[1][0] * det2_34_14 - mat[1][1] * det2_34_04 + mat[1][4] * det2_34_01; + float det3_134_023 = mat[1][0] * det2_34_23 - mat[1][2] * det2_34_03 + mat[1][3] * det2_34_02; + float det3_134_024 = mat[1][0] * det2_34_24 - mat[1][2] * det2_34_04 + mat[1][4] * det2_34_02; + float det3_134_034 = mat[1][0] * det2_34_34 - mat[1][3] * det2_34_04 + mat[1][4] * det2_34_03; + float det3_134_123 = mat[1][1] * det2_34_23 - mat[1][2] * det2_34_13 + mat[1][3] * det2_34_12; + float det3_134_124 = mat[1][1] * det2_34_24 - mat[1][2] * det2_34_14 + mat[1][4] * det2_34_12; + float det3_134_134 = mat[1][1] * det2_34_34 - mat[1][3] * det2_34_14 + mat[1][4] * det2_34_13; + float det3_134_234 = mat[1][2] * det2_34_34 - mat[1][3] * det2_34_24 + mat[1][4] * det2_34_23; + + // remaining 4x4 sub-determinants + float det4_0123_0123 = mat[0][0] * det3_123_123 - mat[0][1] * det3_123_023 + mat[0][2] * det3_123_013 - mat[0][3] * det3_123_012; + float det4_0123_0124 = mat[0][0] * det3_123_124 - mat[0][1] * det3_123_024 + mat[0][2] * det3_123_014 - mat[0][4] * det3_123_012; + float det4_0123_0134 = mat[0][0] * det3_123_134 - mat[0][1] * det3_123_034 + mat[0][3] * det3_123_014 - mat[0][4] * det3_123_013; + float det4_0123_0234 = mat[0][0] * det3_123_234 - mat[0][2] * det3_123_034 + mat[0][3] * det3_123_024 - mat[0][4] * det3_123_023; + float det4_0123_1234 = mat[0][1] * det3_123_234 - mat[0][2] * det3_123_134 + mat[0][3] * det3_123_124 - mat[0][4] * det3_123_123; + float det4_0124_0123 = mat[0][0] * det3_124_123 - mat[0][1] * det3_124_023 + mat[0][2] * det3_124_013 - mat[0][3] * det3_124_012; + float det4_0124_0124 = mat[0][0] * det3_124_124 - mat[0][1] * det3_124_024 + mat[0][2] * det3_124_014 - mat[0][4] * det3_124_012; + float det4_0124_0134 = mat[0][0] * det3_124_134 - mat[0][1] * det3_124_034 + mat[0][3] * det3_124_014 - mat[0][4] * det3_124_013; + float det4_0124_0234 = mat[0][0] * det3_124_234 - mat[0][2] * det3_124_034 + mat[0][3] * det3_124_024 - mat[0][4] * det3_124_023; + float det4_0124_1234 = mat[0][1] * det3_124_234 - mat[0][2] * det3_124_134 + mat[0][3] * det3_124_124 - mat[0][4] * det3_124_123; + float det4_0134_0123 = mat[0][0] * det3_134_123 - mat[0][1] * det3_134_023 + mat[0][2] * det3_134_013 - mat[0][3] * det3_134_012; + float det4_0134_0124 = mat[0][0] * det3_134_124 - mat[0][1] * det3_134_024 + mat[0][2] * det3_134_014 - mat[0][4] * det3_134_012; + float det4_0134_0134 = mat[0][0] * det3_134_134 - mat[0][1] * det3_134_034 + mat[0][3] * det3_134_014 - mat[0][4] * det3_134_013; + float det4_0134_0234 = mat[0][0] * det3_134_234 - mat[0][2] * det3_134_034 + mat[0][3] * det3_134_024 - mat[0][4] * det3_134_023; + float det4_0134_1234 = mat[0][1] * det3_134_234 - mat[0][2] * det3_134_134 + mat[0][3] * det3_134_124 - mat[0][4] * det3_134_123; + float det4_0234_0123 = mat[0][0] * det3_234_123 - mat[0][1] * det3_234_023 + mat[0][2] * det3_234_013 - mat[0][3] * det3_234_012; + float det4_0234_0124 = mat[0][0] * det3_234_124 - mat[0][1] * det3_234_024 + mat[0][2] * det3_234_014 - mat[0][4] * det3_234_012; + float det4_0234_0134 = mat[0][0] * det3_234_134 - mat[0][1] * det3_234_034 + mat[0][3] * det3_234_014 - mat[0][4] * det3_234_013; + float det4_0234_0234 = mat[0][0] * det3_234_234 - mat[0][2] * det3_234_034 + mat[0][3] * det3_234_024 - mat[0][4] * det3_234_023; + float det4_0234_1234 = mat[0][1] * det3_234_234 - mat[0][2] * det3_234_134 + mat[0][3] * det3_234_124 - mat[0][4] * det3_234_123; + + mat[0][0] = det4_1234_1234 * invDet; + mat[0][1] = -det4_0234_1234 * invDet; + mat[0][2] = det4_0134_1234 * invDet; + mat[0][3] = -det4_0124_1234 * invDet; + mat[0][4] = det4_0123_1234 * invDet; + + mat[1][0] = -det4_1234_0234 * invDet; + mat[1][1] = det4_0234_0234 * invDet; + mat[1][2] = -det4_0134_0234 * invDet; + mat[1][3] = det4_0124_0234 * invDet; + mat[1][4] = -det4_0123_0234 * invDet; + + mat[2][0] = det4_1234_0134 * invDet; + mat[2][1] = -det4_0234_0134 * invDet; + mat[2][2] = det4_0134_0134 * invDet; + mat[2][3] = -det4_0124_0134 * invDet; + mat[2][4] = det4_0123_0134 * invDet; + + mat[3][0] = -det4_1234_0124 * invDet; + mat[3][1] = det4_0234_0124 * invDet; + mat[3][2] = -det4_0134_0124 * invDet; + mat[3][3] = det4_0124_0124 * invDet; + mat[3][4] = -det4_0123_0124 * invDet; + + mat[4][0] = det4_1234_0123 * invDet; + mat[4][1] = -det4_0234_0123 * invDet; + mat[4][2] = det4_0134_0123 * invDet; + mat[4][3] = -det4_0124_0123 * invDet; + mat[4][4] = det4_0123_0123 * invDet; + + return true; +#elif 0 + // 5*28 = 140 multiplications + // 5 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + mat[4] *= d; + d = -d; + mat[5] *= d; + mat[10] *= d; + mat[15] *= d; + mat[20] *= d; + d = mat[5] * di; + mat[6] += mat[1] * d; + mat[7] += mat[2] * d; + mat[8] += mat[3] * d; + mat[9] += mat[4] * d; + d = mat[10] * di; + mat[11] += mat[1] * d; + mat[12] += mat[2] * d; + mat[13] += mat[3] * d; + mat[14] += mat[4] * d; + d = mat[15] * di; + mat[16] += mat[1] * d; + mat[17] += mat[2] * d; + mat[18] += mat[3] * d; + mat[19] += mat[4] * d; + d = mat[20] * di; + mat[21] += mat[1] * d; + mat[22] += mat[2] * d; + mat[23] += mat[3] * d; + mat[24] += mat[4] * d; + di = mat[6]; + s *= di; + mat[6] = d = 1.0f / di; + mat[5] *= d; + mat[7] *= d; + mat[8] *= d; + mat[9] *= d; + d = -d; + mat[1] *= d; + mat[11] *= d; + mat[16] *= d; + mat[21] *= d; + d = mat[1] * di; + mat[0] += mat[5] * d; + mat[2] += mat[7] * d; + mat[3] += mat[8] * d; + mat[4] += mat[9] * d; + d = mat[11] * di; + mat[10] += mat[5] * d; + mat[12] += mat[7] * d; + mat[13] += mat[8] * d; + mat[14] += mat[9] * d; + d = mat[16] * di; + mat[15] += mat[5] * d; + mat[17] += mat[7] * d; + mat[18] += mat[8] * d; + mat[19] += mat[9] * d; + d = mat[21] * di; + mat[20] += mat[5] * d; + mat[22] += mat[7] * d; + mat[23] += mat[8] * d; + mat[24] += mat[9] * d; + di = mat[12]; + s *= di; + mat[12] = d = 1.0f / di; + mat[10] *= d; + mat[11] *= d; + mat[13] *= d; + mat[14] *= d; + d = -d; + mat[2] *= d; + mat[7] *= d; + mat[17] *= d; + mat[22] *= d; + d = mat[2] * di; + mat[0] += mat[10] * d; + mat[1] += mat[11] * d; + mat[3] += mat[13] * d; + mat[4] += mat[14] * d; + d = mat[7] * di; + mat[5] += mat[10] * d; + mat[6] += mat[11] * d; + mat[8] += mat[13] * d; + mat[9] += mat[14] * d; + d = mat[17] * di; + mat[15] += mat[10] * d; + mat[16] += mat[11] * d; + mat[18] += mat[13] * d; + mat[19] += mat[14] * d; + d = mat[22] * di; + mat[20] += mat[10] * d; + mat[21] += mat[11] * d; + mat[23] += mat[13] * d; + mat[24] += mat[14] * d; + di = mat[18]; + s *= di; + mat[18] = d = 1.0f / di; + mat[15] *= d; + mat[16] *= d; + mat[17] *= d; + mat[19] *= d; + d = -d; + mat[3] *= d; + mat[8] *= d; + mat[13] *= d; + mat[23] *= d; + d = mat[3] * di; + mat[0] += mat[15] * d; + mat[1] += mat[16] * d; + mat[2] += mat[17] * d; + mat[4] += mat[19] * d; + d = mat[8] * di; + mat[5] += mat[15] * d; + mat[6] += mat[16] * d; + mat[7] += mat[17] * d; + mat[9] += mat[19] * d; + d = mat[13] * di; + mat[10] += mat[15] * d; + mat[11] += mat[16] * d; + mat[12] += mat[17] * d; + mat[14] += mat[19] * d; + d = mat[23] * di; + mat[20] += mat[15] * d; + mat[21] += mat[16] * d; + mat[22] += mat[17] * d; + mat[24] += mat[19] * d; + di = mat[24]; + s *= di; + mat[24] = d = 1.0f / di; + mat[20] *= d; + mat[21] *= d; + mat[22] *= d; + mat[23] *= d; + d = -d; + mat[4] *= d; + mat[9] *= d; + mat[14] *= d; + mat[19] *= d; + d = mat[4] * di; + mat[0] += mat[20] * d; + mat[1] += mat[21] * d; + mat[2] += mat[22] * d; + mat[3] += mat[23] * d; + d = mat[9] * di; + mat[5] += mat[20] * d; + mat[6] += mat[21] * d; + mat[7] += mat[22] * d; + mat[8] += mat[23] * d; + d = mat[14] * di; + mat[10] += mat[20] * d; + mat[11] += mat[21] * d; + mat[12] += mat[22] * d; + mat[13] += mat[23] * d; + d = mat[19] * di; + mat[15] += mat[20] * d; + mat[16] += mat[21] * d; + mat[17] += mat[22] * d; + mat[18] += mat[23] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 86+30+6 = 122 multiplications + // 2*1 = 2 divisions + idMat3 r0, r1, r2, r3; + float c0, c1, c2, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); // 3x3 + c0 = mat[1*5+1] * mat[2*5+2] - mat[1*5+2] * mat[2*5+1]; + c1 = mat[1*5+2] * mat[2*5+0] - mat[1*5+0] * mat[2*5+2]; + c2 = mat[1*5+0] * mat[2*5+1] - mat[1*5+1] * mat[2*5+0]; + + det = mat[0*5+0] * c0 + mat[0*5+1] * c1 + mat[0*5+2] * c2; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = c0 * invDet; + r0[0][1] = ( mat[0*5+2] * mat[2*5+1] - mat[0*5+1] * mat[2*5+2] ) * invDet; + r0[0][2] = ( mat[0*5+1] * mat[1*5+2] - mat[0*5+2] * mat[1*5+1] ) * invDet; + r0[1][0] = c1 * invDet; + r0[1][1] = ( mat[0*5+0] * mat[2*5+2] - mat[0*5+2] * mat[2*5+0] ) * invDet; + r0[1][2] = ( mat[0*5+2] * mat[1*5+0] - mat[0*5+0] * mat[1*5+2] ) * invDet; + r0[2][0] = c2 * invDet; + r0[2][1] = ( mat[0*5+1] * mat[2*5+0] - mat[0*5+0] * mat[2*5+1] ) * invDet; + r0[2][2] = ( mat[0*5+0] * mat[1*5+1] - mat[0*5+1] * mat[1*5+0] ) * invDet; + + // r1 = r0 * m1; // 3x2 = 3x3 * 3x2 + r1[0][0] = r0[0][0] * mat[0*5+3] + r0[0][1] * mat[1*5+3] + r0[0][2] * mat[2*5+3]; + r1[0][1] = r0[0][0] * mat[0*5+4] + r0[0][1] * mat[1*5+4] + r0[0][2] * mat[2*5+4]; + r1[1][0] = r0[1][0] * mat[0*5+3] + r0[1][1] * mat[1*5+3] + r0[1][2] * mat[2*5+3]; + r1[1][1] = r0[1][0] * mat[0*5+4] + r0[1][1] * mat[1*5+4] + r0[1][2] * mat[2*5+4]; + r1[2][0] = r0[2][0] * mat[0*5+3] + r0[2][1] * mat[1*5+3] + r0[2][2] * mat[2*5+3]; + r1[2][1] = r0[2][0] * mat[0*5+4] + r0[2][1] * mat[1*5+4] + r0[2][2] * mat[2*5+4]; + + // r2 = m2 * r1; // 2x2 = 2x3 * 3x2 + r2[0][0] = mat[3*5+0] * r1[0][0] + mat[3*5+1] * r1[1][0] + mat[3*5+2] * r1[2][0]; + r2[0][1] = mat[3*5+0] * r1[0][1] + mat[3*5+1] * r1[1][1] + mat[3*5+2] * r1[2][1]; + r2[1][0] = mat[4*5+0] * r1[0][0] + mat[4*5+1] * r1[1][0] + mat[4*5+2] * r1[2][0]; + r2[1][1] = mat[4*5+0] * r1[0][1] + mat[4*5+1] * r1[1][1] + mat[4*5+2] * r1[2][1]; + + // r3 = r2 - m3; // 2x2 = 2x2 - 2x2 + r3[0][0] = r2[0][0] - mat[3*5+3]; + r3[0][1] = r2[0][1] - mat[3*5+4]; + r3[1][0] = r2[1][0] - mat[4*5+3]; + r3[1][1] = r2[1][1] - mat[4*5+4]; + + // r3.InverseSelf(); // 2x2 + det = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + c0 = r3[0][0]; + r3[0][0] = r3[1][1] * invDet; + r3[0][1] = - r3[0][1] * invDet; + r3[1][0] = - r3[1][0] * invDet; + r3[1][1] = c0 * invDet; + + // r2 = m2 * r0; // 2x3 = 2x3 * 3x3 + r2[0][0] = mat[3*5+0] * r0[0][0] + mat[3*5+1] * r0[1][0] + mat[3*5+2] * r0[2][0]; + r2[0][1] = mat[3*5+0] * r0[0][1] + mat[3*5+1] * r0[1][1] + mat[3*5+2] * r0[2][1]; + r2[0][2] = mat[3*5+0] * r0[0][2] + mat[3*5+1] * r0[1][2] + mat[3*5+2] * r0[2][2]; + r2[1][0] = mat[4*5+0] * r0[0][0] + mat[4*5+1] * r0[1][0] + mat[4*5+2] * r0[2][0]; + r2[1][1] = mat[4*5+0] * r0[0][1] + mat[4*5+1] * r0[1][1] + mat[4*5+2] * r0[2][1]; + r2[1][2] = mat[4*5+0] * r0[0][2] + mat[4*5+1] * r0[1][2] + mat[4*5+2] * r0[2][2]; + + // m2 = r3 * r2; // 2x3 = 2x2 * 2x3 + mat[3*5+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0]; + mat[3*5+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1]; + mat[3*5+2] = r3[0][0] * r2[0][2] + r3[0][1] * r2[1][2]; + mat[4*5+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0]; + mat[4*5+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1]; + mat[4*5+2] = r3[1][0] * r2[0][2] + r3[1][1] * r2[1][2]; + + // m0 = r0 - r1 * m2; // 3x3 = 3x3 - 3x2 * 2x3 + mat[0*5+0] = r0[0][0] - r1[0][0] * mat[3*5+0] - r1[0][1] * mat[4*5+0]; + mat[0*5+1] = r0[0][1] - r1[0][0] * mat[3*5+1] - r1[0][1] * mat[4*5+1]; + mat[0*5+2] = r0[0][2] - r1[0][0] * mat[3*5+2] - r1[0][1] * mat[4*5+2]; + mat[1*5+0] = r0[1][0] - r1[1][0] * mat[3*5+0] - r1[1][1] * mat[4*5+0]; + mat[1*5+1] = r0[1][1] - r1[1][0] * mat[3*5+1] - r1[1][1] * mat[4*5+1]; + mat[1*5+2] = r0[1][2] - r1[1][0] * mat[3*5+2] - r1[1][1] * mat[4*5+2]; + mat[2*5+0] = r0[2][0] - r1[2][0] * mat[3*5+0] - r1[2][1] * mat[4*5+0]; + mat[2*5+1] = r0[2][1] - r1[2][0] * mat[3*5+1] - r1[2][1] * mat[4*5+1]; + mat[2*5+2] = r0[2][2] - r1[2][0] * mat[3*5+2] - r1[2][1] * mat[4*5+2]; + + // m1 = r1 * r3; // 3x2 = 3x2 * 2x2 + mat[0*5+3] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0]; + mat[0*5+4] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1]; + mat[1*5+3] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0]; + mat[1*5+4] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1]; + mat[2*5+3] = r1[2][0] * r3[0][0] + r1[2][1] * r3[1][0]; + mat[2*5+4] = r1[2][0] * r3[0][1] + r1[2][1] * r3[1][1]; + + // m3 = -r3; // 2x2 = - 2x2 + mat[3*5+3] = -r3[0][0]; + mat[3*5+4] = -r3[0][1]; + mat[4*5+3] = -r3[1][0]; + mat[4*5+4] = -r3[1][1]; + + return true; +#endif +} + +/* +============= +idMat5::ToString +============= +*/ +const char *idMat5::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMat6 +// +//=============================================================== + +idMat6 mat6_zero( idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ), idVec6( 0, 0, 0, 0, 0, 0 ) ); +idMat6 mat6_identity( idVec6( 1, 0, 0, 0, 0, 0 ), idVec6( 0, 1, 0, 0, 0, 0 ), idVec6( 0, 0, 1, 0, 0, 0 ), idVec6( 0, 0, 0, 1, 0, 0 ), idVec6( 0, 0, 0, 0, 1, 0 ), idVec6( 0, 0, 0, 0, 0, 1 ) ); + +/* +============ +idMat6::Transpose +============ +*/ +idMat6 idMat6::Transpose( void ) const { + idMat6 transpose; + int i, j; + + for( i = 0; i < 6; i++ ) { + for( j = 0; j < 6; j++ ) { + transpose[ i ][ j ] = mat[ j ][ i ]; + } + } + return transpose; +} + +/* +============ +idMat6::TransposeSelf +============ +*/ +idMat6 &idMat6::TransposeSelf( void ) { + float temp; + int i, j; + + for( i = 0; i < 6; i++ ) { + for( j = i + 1; j < 6; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } + return *this; +} + +/* +============ +idMat6::Determinant +============ +*/ +float idMat6::Determinant( void ) const { + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + return mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; +} + +/* +============ +idMat6::InverseSelf +============ +*/ +bool idMat6::InverseSelf( void ) { + // 810+6+36 = 852 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + det = mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_05 = mat[3][0] * mat[4][5] - mat[3][5] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_15 = mat[3][1] * mat[4][5] - mat[3][5] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_25 = mat[3][2] * mat[4][5] - mat[3][5] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + float det2_34_35 = mat[3][3] * mat[4][5] - mat[3][5] * mat[4][3]; + float det2_34_45 = mat[3][4] * mat[4][5] - mat[3][5] * mat[4][4]; + float det2_35_01 = mat[3][0] * mat[5][1] - mat[3][1] * mat[5][0]; + float det2_35_02 = mat[3][0] * mat[5][2] - mat[3][2] * mat[5][0]; + float det2_35_03 = mat[3][0] * mat[5][3] - mat[3][3] * mat[5][0]; + float det2_35_04 = mat[3][0] * mat[5][4] - mat[3][4] * mat[5][0]; + float det2_35_05 = mat[3][0] * mat[5][5] - mat[3][5] * mat[5][0]; + float det2_35_12 = mat[3][1] * mat[5][2] - mat[3][2] * mat[5][1]; + float det2_35_13 = mat[3][1] * mat[5][3] - mat[3][3] * mat[5][1]; + float det2_35_14 = mat[3][1] * mat[5][4] - mat[3][4] * mat[5][1]; + float det2_35_15 = mat[3][1] * mat[5][5] - mat[3][5] * mat[5][1]; + float det2_35_23 = mat[3][2] * mat[5][3] - mat[3][3] * mat[5][2]; + float det2_35_24 = mat[3][2] * mat[5][4] - mat[3][4] * mat[5][2]; + float det2_35_25 = mat[3][2] * mat[5][5] - mat[3][5] * mat[5][2]; + float det2_35_34 = mat[3][3] * mat[5][4] - mat[3][4] * mat[5][3]; + float det2_35_35 = mat[3][3] * mat[5][5] - mat[3][5] * mat[5][3]; + float det2_35_45 = mat[3][4] * mat[5][5] - mat[3][5] * mat[5][4]; + + // remaining 3x3 sub-determinants + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_015 = mat[2][0] * det2_34_15 - mat[2][1] * det2_34_05 + mat[2][5] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_025 = mat[2][0] * det2_34_25 - mat[2][2] * det2_34_05 + mat[2][5] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_035 = mat[2][0] * det2_34_35 - mat[2][3] * det2_34_05 + mat[2][5] * det2_34_03; + float det3_234_045 = mat[2][0] * det2_34_45 - mat[2][4] * det2_34_05 + mat[2][5] * det2_34_04; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_125 = mat[2][1] * det2_34_25 - mat[2][2] * det2_34_15 + mat[2][5] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_135 = mat[2][1] * det2_34_35 - mat[2][3] * det2_34_15 + mat[2][5] * det2_34_13; + float det3_234_145 = mat[2][1] * det2_34_45 - mat[2][4] * det2_34_15 + mat[2][5] * det2_34_14; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + float det3_234_235 = mat[2][2] * det2_34_35 - mat[2][3] * det2_34_25 + mat[2][5] * det2_34_23; + float det3_234_245 = mat[2][2] * det2_34_45 - mat[2][4] * det2_34_25 + mat[2][5] * det2_34_24; + float det3_234_345 = mat[2][3] * det2_34_45 - mat[2][4] * det2_34_35 + mat[2][5] * det2_34_34; + float det3_235_012 = mat[2][0] * det2_35_12 - mat[2][1] * det2_35_02 + mat[2][2] * det2_35_01; + float det3_235_013 = mat[2][0] * det2_35_13 - mat[2][1] * det2_35_03 + mat[2][3] * det2_35_01; + float det3_235_014 = mat[2][0] * det2_35_14 - mat[2][1] * det2_35_04 + mat[2][4] * det2_35_01; + float det3_235_015 = mat[2][0] * det2_35_15 - mat[2][1] * det2_35_05 + mat[2][5] * det2_35_01; + float det3_235_023 = mat[2][0] * det2_35_23 - mat[2][2] * det2_35_03 + mat[2][3] * det2_35_02; + float det3_235_024 = mat[2][0] * det2_35_24 - mat[2][2] * det2_35_04 + mat[2][4] * det2_35_02; + float det3_235_025 = mat[2][0] * det2_35_25 - mat[2][2] * det2_35_05 + mat[2][5] * det2_35_02; + float det3_235_034 = mat[2][0] * det2_35_34 - mat[2][3] * det2_35_04 + mat[2][4] * det2_35_03; + float det3_235_035 = mat[2][0] * det2_35_35 - mat[2][3] * det2_35_05 + mat[2][5] * det2_35_03; + float det3_235_045 = mat[2][0] * det2_35_45 - mat[2][4] * det2_35_05 + mat[2][5] * det2_35_04; + float det3_235_123 = mat[2][1] * det2_35_23 - mat[2][2] * det2_35_13 + mat[2][3] * det2_35_12; + float det3_235_124 = mat[2][1] * det2_35_24 - mat[2][2] * det2_35_14 + mat[2][4] * det2_35_12; + float det3_235_125 = mat[2][1] * det2_35_25 - mat[2][2] * det2_35_15 + mat[2][5] * det2_35_12; + float det3_235_134 = mat[2][1] * det2_35_34 - mat[2][3] * det2_35_14 + mat[2][4] * det2_35_13; + float det3_235_135 = mat[2][1] * det2_35_35 - mat[2][3] * det2_35_15 + mat[2][5] * det2_35_13; + float det3_235_145 = mat[2][1] * det2_35_45 - mat[2][4] * det2_35_15 + mat[2][5] * det2_35_14; + float det3_235_234 = mat[2][2] * det2_35_34 - mat[2][3] * det2_35_24 + mat[2][4] * det2_35_23; + float det3_235_235 = mat[2][2] * det2_35_35 - mat[2][3] * det2_35_25 + mat[2][5] * det2_35_23; + float det3_235_245 = mat[2][2] * det2_35_45 - mat[2][4] * det2_35_25 + mat[2][5] * det2_35_24; + float det3_235_345 = mat[2][3] * det2_35_45 - mat[2][4] * det2_35_35 + mat[2][5] * det2_35_34; + float det3_245_012 = mat[2][0] * det2_45_12 - mat[2][1] * det2_45_02 + mat[2][2] * det2_45_01; + float det3_245_013 = mat[2][0] * det2_45_13 - mat[2][1] * det2_45_03 + mat[2][3] * det2_45_01; + float det3_245_014 = mat[2][0] * det2_45_14 - mat[2][1] * det2_45_04 + mat[2][4] * det2_45_01; + float det3_245_015 = mat[2][0] * det2_45_15 - mat[2][1] * det2_45_05 + mat[2][5] * det2_45_01; + float det3_245_023 = mat[2][0] * det2_45_23 - mat[2][2] * det2_45_03 + mat[2][3] * det2_45_02; + float det3_245_024 = mat[2][0] * det2_45_24 - mat[2][2] * det2_45_04 + mat[2][4] * det2_45_02; + float det3_245_025 = mat[2][0] * det2_45_25 - mat[2][2] * det2_45_05 + mat[2][5] * det2_45_02; + float det3_245_034 = mat[2][0] * det2_45_34 - mat[2][3] * det2_45_04 + mat[2][4] * det2_45_03; + float det3_245_035 = mat[2][0] * det2_45_35 - mat[2][3] * det2_45_05 + mat[2][5] * det2_45_03; + float det3_245_045 = mat[2][0] * det2_45_45 - mat[2][4] * det2_45_05 + mat[2][5] * det2_45_04; + float det3_245_123 = mat[2][1] * det2_45_23 - mat[2][2] * det2_45_13 + mat[2][3] * det2_45_12; + float det3_245_124 = mat[2][1] * det2_45_24 - mat[2][2] * det2_45_14 + mat[2][4] * det2_45_12; + float det3_245_125 = mat[2][1] * det2_45_25 - mat[2][2] * det2_45_15 + mat[2][5] * det2_45_12; + float det3_245_134 = mat[2][1] * det2_45_34 - mat[2][3] * det2_45_14 + mat[2][4] * det2_45_13; + float det3_245_135 = mat[2][1] * det2_45_35 - mat[2][3] * det2_45_15 + mat[2][5] * det2_45_13; + float det3_245_145 = mat[2][1] * det2_45_45 - mat[2][4] * det2_45_15 + mat[2][5] * det2_45_14; + float det3_245_234 = mat[2][2] * det2_45_34 - mat[2][3] * det2_45_24 + mat[2][4] * det2_45_23; + float det3_245_235 = mat[2][2] * det2_45_35 - mat[2][3] * det2_45_25 + mat[2][5] * det2_45_23; + float det3_245_245 = mat[2][2] * det2_45_45 - mat[2][4] * det2_45_25 + mat[2][5] * det2_45_24; + float det3_245_345 = mat[2][3] * det2_45_45 - mat[2][4] * det2_45_35 + mat[2][5] * det2_45_34; + + // remaining 4x4 sub-determinants + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0125 = mat[1][0] * det3_234_125 - mat[1][1] * det3_234_025 + mat[1][2] * det3_234_015 - mat[1][5] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0135 = mat[1][0] * det3_234_135 - mat[1][1] * det3_234_035 + mat[1][3] * det3_234_015 - mat[1][5] * det3_234_013; + float det4_1234_0145 = mat[1][0] * det3_234_145 - mat[1][1] * det3_234_045 + mat[1][4] * det3_234_015 - mat[1][5] * det3_234_014; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_0235 = mat[1][0] * det3_234_235 - mat[1][2] * det3_234_035 + mat[1][3] * det3_234_025 - mat[1][5] * det3_234_023; + float det4_1234_0245 = mat[1][0] * det3_234_245 - mat[1][2] * det3_234_045 + mat[1][4] * det3_234_025 - mat[1][5] * det3_234_024; + float det4_1234_0345 = mat[1][0] * det3_234_345 - mat[1][3] * det3_234_045 + mat[1][4] * det3_234_035 - mat[1][5] * det3_234_034; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + float det4_1234_1235 = mat[1][1] * det3_234_235 - mat[1][2] * det3_234_135 + mat[1][3] * det3_234_125 - mat[1][5] * det3_234_123; + float det4_1234_1245 = mat[1][1] * det3_234_245 - mat[1][2] * det3_234_145 + mat[1][4] * det3_234_125 - mat[1][5] * det3_234_124; + float det4_1234_1345 = mat[1][1] * det3_234_345 - mat[1][3] * det3_234_145 + mat[1][4] * det3_234_135 - mat[1][5] * det3_234_134; + float det4_1234_2345 = mat[1][2] * det3_234_345 - mat[1][3] * det3_234_245 + mat[1][4] * det3_234_235 - mat[1][5] * det3_234_234; + float det4_1235_0123 = mat[1][0] * det3_235_123 - mat[1][1] * det3_235_023 + mat[1][2] * det3_235_013 - mat[1][3] * det3_235_012; + float det4_1235_0124 = mat[1][0] * det3_235_124 - mat[1][1] * det3_235_024 + mat[1][2] * det3_235_014 - mat[1][4] * det3_235_012; + float det4_1235_0125 = mat[1][0] * det3_235_125 - mat[1][1] * det3_235_025 + mat[1][2] * det3_235_015 - mat[1][5] * det3_235_012; + float det4_1235_0134 = mat[1][0] * det3_235_134 - mat[1][1] * det3_235_034 + mat[1][3] * det3_235_014 - mat[1][4] * det3_235_013; + float det4_1235_0135 = mat[1][0] * det3_235_135 - mat[1][1] * det3_235_035 + mat[1][3] * det3_235_015 - mat[1][5] * det3_235_013; + float det4_1235_0145 = mat[1][0] * det3_235_145 - mat[1][1] * det3_235_045 + mat[1][4] * det3_235_015 - mat[1][5] * det3_235_014; + float det4_1235_0234 = mat[1][0] * det3_235_234 - mat[1][2] * det3_235_034 + mat[1][3] * det3_235_024 - mat[1][4] * det3_235_023; + float det4_1235_0235 = mat[1][0] * det3_235_235 - mat[1][2] * det3_235_035 + mat[1][3] * det3_235_025 - mat[1][5] * det3_235_023; + float det4_1235_0245 = mat[1][0] * det3_235_245 - mat[1][2] * det3_235_045 + mat[1][4] * det3_235_025 - mat[1][5] * det3_235_024; + float det4_1235_0345 = mat[1][0] * det3_235_345 - mat[1][3] * det3_235_045 + mat[1][4] * det3_235_035 - mat[1][5] * det3_235_034; + float det4_1235_1234 = mat[1][1] * det3_235_234 - mat[1][2] * det3_235_134 + mat[1][3] * det3_235_124 - mat[1][4] * det3_235_123; + float det4_1235_1235 = mat[1][1] * det3_235_235 - mat[1][2] * det3_235_135 + mat[1][3] * det3_235_125 - mat[1][5] * det3_235_123; + float det4_1235_1245 = mat[1][1] * det3_235_245 - mat[1][2] * det3_235_145 + mat[1][4] * det3_235_125 - mat[1][5] * det3_235_124; + float det4_1235_1345 = mat[1][1] * det3_235_345 - mat[1][3] * det3_235_145 + mat[1][4] * det3_235_135 - mat[1][5] * det3_235_134; + float det4_1235_2345 = mat[1][2] * det3_235_345 - mat[1][3] * det3_235_245 + mat[1][4] * det3_235_235 - mat[1][5] * det3_235_234; + float det4_1245_0123 = mat[1][0] * det3_245_123 - mat[1][1] * det3_245_023 + mat[1][2] * det3_245_013 - mat[1][3] * det3_245_012; + float det4_1245_0124 = mat[1][0] * det3_245_124 - mat[1][1] * det3_245_024 + mat[1][2] * det3_245_014 - mat[1][4] * det3_245_012; + float det4_1245_0125 = mat[1][0] * det3_245_125 - mat[1][1] * det3_245_025 + mat[1][2] * det3_245_015 - mat[1][5] * det3_245_012; + float det4_1245_0134 = mat[1][0] * det3_245_134 - mat[1][1] * det3_245_034 + mat[1][3] * det3_245_014 - mat[1][4] * det3_245_013; + float det4_1245_0135 = mat[1][0] * det3_245_135 - mat[1][1] * det3_245_035 + mat[1][3] * det3_245_015 - mat[1][5] * det3_245_013; + float det4_1245_0145 = mat[1][0] * det3_245_145 - mat[1][1] * det3_245_045 + mat[1][4] * det3_245_015 - mat[1][5] * det3_245_014; + float det4_1245_0234 = mat[1][0] * det3_245_234 - mat[1][2] * det3_245_034 + mat[1][3] * det3_245_024 - mat[1][4] * det3_245_023; + float det4_1245_0235 = mat[1][0] * det3_245_235 - mat[1][2] * det3_245_035 + mat[1][3] * det3_245_025 - mat[1][5] * det3_245_023; + float det4_1245_0245 = mat[1][0] * det3_245_245 - mat[1][2] * det3_245_045 + mat[1][4] * det3_245_025 - mat[1][5] * det3_245_024; + float det4_1245_0345 = mat[1][0] * det3_245_345 - mat[1][3] * det3_245_045 + mat[1][4] * det3_245_035 - mat[1][5] * det3_245_034; + float det4_1245_1234 = mat[1][1] * det3_245_234 - mat[1][2] * det3_245_134 + mat[1][3] * det3_245_124 - mat[1][4] * det3_245_123; + float det4_1245_1235 = mat[1][1] * det3_245_235 - mat[1][2] * det3_245_135 + mat[1][3] * det3_245_125 - mat[1][5] * det3_245_123; + float det4_1245_1245 = mat[1][1] * det3_245_245 - mat[1][2] * det3_245_145 + mat[1][4] * det3_245_125 - mat[1][5] * det3_245_124; + float det4_1245_1345 = mat[1][1] * det3_245_345 - mat[1][3] * det3_245_145 + mat[1][4] * det3_245_135 - mat[1][5] * det3_245_134; + float det4_1245_2345 = mat[1][2] * det3_245_345 - mat[1][3] * det3_245_245 + mat[1][4] * det3_245_235 - mat[1][5] * det3_245_234; + float det4_1345_0123 = mat[1][0] * det3_345_123 - mat[1][1] * det3_345_023 + mat[1][2] * det3_345_013 - mat[1][3] * det3_345_012; + float det4_1345_0124 = mat[1][0] * det3_345_124 - mat[1][1] * det3_345_024 + mat[1][2] * det3_345_014 - mat[1][4] * det3_345_012; + float det4_1345_0125 = mat[1][0] * det3_345_125 - mat[1][1] * det3_345_025 + mat[1][2] * det3_345_015 - mat[1][5] * det3_345_012; + float det4_1345_0134 = mat[1][0] * det3_345_134 - mat[1][1] * det3_345_034 + mat[1][3] * det3_345_014 - mat[1][4] * det3_345_013; + float det4_1345_0135 = mat[1][0] * det3_345_135 - mat[1][1] * det3_345_035 + mat[1][3] * det3_345_015 - mat[1][5] * det3_345_013; + float det4_1345_0145 = mat[1][0] * det3_345_145 - mat[1][1] * det3_345_045 + mat[1][4] * det3_345_015 - mat[1][5] * det3_345_014; + float det4_1345_0234 = mat[1][0] * det3_345_234 - mat[1][2] * det3_345_034 + mat[1][3] * det3_345_024 - mat[1][4] * det3_345_023; + float det4_1345_0235 = mat[1][0] * det3_345_235 - mat[1][2] * det3_345_035 + mat[1][3] * det3_345_025 - mat[1][5] * det3_345_023; + float det4_1345_0245 = mat[1][0] * det3_345_245 - mat[1][2] * det3_345_045 + mat[1][4] * det3_345_025 - mat[1][5] * det3_345_024; + float det4_1345_0345 = mat[1][0] * det3_345_345 - mat[1][3] * det3_345_045 + mat[1][4] * det3_345_035 - mat[1][5] * det3_345_034; + float det4_1345_1234 = mat[1][1] * det3_345_234 - mat[1][2] * det3_345_134 + mat[1][3] * det3_345_124 - mat[1][4] * det3_345_123; + float det4_1345_1235 = mat[1][1] * det3_345_235 - mat[1][2] * det3_345_135 + mat[1][3] * det3_345_125 - mat[1][5] * det3_345_123; + float det4_1345_1245 = mat[1][1] * det3_345_245 - mat[1][2] * det3_345_145 + mat[1][4] * det3_345_125 - mat[1][5] * det3_345_124; + float det4_1345_1345 = mat[1][1] * det3_345_345 - mat[1][3] * det3_345_145 + mat[1][4] * det3_345_135 - mat[1][5] * det3_345_134; + float det4_1345_2345 = mat[1][2] * det3_345_345 - mat[1][3] * det3_345_245 + mat[1][4] * det3_345_235 - mat[1][5] * det3_345_234; + + // remaining 5x5 sub-determinants + float det5_01234_01234 = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + float det5_01234_01235 = mat[0][0] * det4_1234_1235 - mat[0][1] * det4_1234_0235 + mat[0][2] * det4_1234_0135 - mat[0][3] * det4_1234_0125 + mat[0][5] * det4_1234_0123; + float det5_01234_01245 = mat[0][0] * det4_1234_1245 - mat[0][1] * det4_1234_0245 + mat[0][2] * det4_1234_0145 - mat[0][4] * det4_1234_0125 + mat[0][5] * det4_1234_0124; + float det5_01234_01345 = mat[0][0] * det4_1234_1345 - mat[0][1] * det4_1234_0345 + mat[0][3] * det4_1234_0145 - mat[0][4] * det4_1234_0135 + mat[0][5] * det4_1234_0134; + float det5_01234_02345 = mat[0][0] * det4_1234_2345 - mat[0][2] * det4_1234_0345 + mat[0][3] * det4_1234_0245 - mat[0][4] * det4_1234_0235 + mat[0][5] * det4_1234_0234; + float det5_01234_12345 = mat[0][1] * det4_1234_2345 - mat[0][2] * det4_1234_1345 + mat[0][3] * det4_1234_1245 - mat[0][4] * det4_1234_1235 + mat[0][5] * det4_1234_1234; + float det5_01235_01234 = mat[0][0] * det4_1235_1234 - mat[0][1] * det4_1235_0234 + mat[0][2] * det4_1235_0134 - mat[0][3] * det4_1235_0124 + mat[0][4] * det4_1235_0123; + float det5_01235_01235 = mat[0][0] * det4_1235_1235 - mat[0][1] * det4_1235_0235 + mat[0][2] * det4_1235_0135 - mat[0][3] * det4_1235_0125 + mat[0][5] * det4_1235_0123; + float det5_01235_01245 = mat[0][0] * det4_1235_1245 - mat[0][1] * det4_1235_0245 + mat[0][2] * det4_1235_0145 - mat[0][4] * det4_1235_0125 + mat[0][5] * det4_1235_0124; + float det5_01235_01345 = mat[0][0] * det4_1235_1345 - mat[0][1] * det4_1235_0345 + mat[0][3] * det4_1235_0145 - mat[0][4] * det4_1235_0135 + mat[0][5] * det4_1235_0134; + float det5_01235_02345 = mat[0][0] * det4_1235_2345 - mat[0][2] * det4_1235_0345 + mat[0][3] * det4_1235_0245 - mat[0][4] * det4_1235_0235 + mat[0][5] * det4_1235_0234; + float det5_01235_12345 = mat[0][1] * det4_1235_2345 - mat[0][2] * det4_1235_1345 + mat[0][3] * det4_1235_1245 - mat[0][4] * det4_1235_1235 + mat[0][5] * det4_1235_1234; + float det5_01245_01234 = mat[0][0] * det4_1245_1234 - mat[0][1] * det4_1245_0234 + mat[0][2] * det4_1245_0134 - mat[0][3] * det4_1245_0124 + mat[0][4] * det4_1245_0123; + float det5_01245_01235 = mat[0][0] * det4_1245_1235 - mat[0][1] * det4_1245_0235 + mat[0][2] * det4_1245_0135 - mat[0][3] * det4_1245_0125 + mat[0][5] * det4_1245_0123; + float det5_01245_01245 = mat[0][0] * det4_1245_1245 - mat[0][1] * det4_1245_0245 + mat[0][2] * det4_1245_0145 - mat[0][4] * det4_1245_0125 + mat[0][5] * det4_1245_0124; + float det5_01245_01345 = mat[0][0] * det4_1245_1345 - mat[0][1] * det4_1245_0345 + mat[0][3] * det4_1245_0145 - mat[0][4] * det4_1245_0135 + mat[0][5] * det4_1245_0134; + float det5_01245_02345 = mat[0][0] * det4_1245_2345 - mat[0][2] * det4_1245_0345 + mat[0][3] * det4_1245_0245 - mat[0][4] * det4_1245_0235 + mat[0][5] * det4_1245_0234; + float det5_01245_12345 = mat[0][1] * det4_1245_2345 - mat[0][2] * det4_1245_1345 + mat[0][3] * det4_1245_1245 - mat[0][4] * det4_1245_1235 + mat[0][5] * det4_1245_1234; + float det5_01345_01234 = mat[0][0] * det4_1345_1234 - mat[0][1] * det4_1345_0234 + mat[0][2] * det4_1345_0134 - mat[0][3] * det4_1345_0124 + mat[0][4] * det4_1345_0123; + float det5_01345_01235 = mat[0][0] * det4_1345_1235 - mat[0][1] * det4_1345_0235 + mat[0][2] * det4_1345_0135 - mat[0][3] * det4_1345_0125 + mat[0][5] * det4_1345_0123; + float det5_01345_01245 = mat[0][0] * det4_1345_1245 - mat[0][1] * det4_1345_0245 + mat[0][2] * det4_1345_0145 - mat[0][4] * det4_1345_0125 + mat[0][5] * det4_1345_0124; + float det5_01345_01345 = mat[0][0] * det4_1345_1345 - mat[0][1] * det4_1345_0345 + mat[0][3] * det4_1345_0145 - mat[0][4] * det4_1345_0135 + mat[0][5] * det4_1345_0134; + float det5_01345_02345 = mat[0][0] * det4_1345_2345 - mat[0][2] * det4_1345_0345 + mat[0][3] * det4_1345_0245 - mat[0][4] * det4_1345_0235 + mat[0][5] * det4_1345_0234; + float det5_01345_12345 = mat[0][1] * det4_1345_2345 - mat[0][2] * det4_1345_1345 + mat[0][3] * det4_1345_1245 - mat[0][4] * det4_1345_1235 + mat[0][5] * det4_1345_1234; + float det5_02345_01234 = mat[0][0] * det4_2345_1234 - mat[0][1] * det4_2345_0234 + mat[0][2] * det4_2345_0134 - mat[0][3] * det4_2345_0124 + mat[0][4] * det4_2345_0123; + float det5_02345_01235 = mat[0][0] * det4_2345_1235 - mat[0][1] * det4_2345_0235 + mat[0][2] * det4_2345_0135 - mat[0][3] * det4_2345_0125 + mat[0][5] * det4_2345_0123; + float det5_02345_01245 = mat[0][0] * det4_2345_1245 - mat[0][1] * det4_2345_0245 + mat[0][2] * det4_2345_0145 - mat[0][4] * det4_2345_0125 + mat[0][5] * det4_2345_0124; + float det5_02345_01345 = mat[0][0] * det4_2345_1345 - mat[0][1] * det4_2345_0345 + mat[0][3] * det4_2345_0145 - mat[0][4] * det4_2345_0135 + mat[0][5] * det4_2345_0134; + float det5_02345_02345 = mat[0][0] * det4_2345_2345 - mat[0][2] * det4_2345_0345 + mat[0][3] * det4_2345_0245 - mat[0][4] * det4_2345_0235 + mat[0][5] * det4_2345_0234; + float det5_02345_12345 = mat[0][1] * det4_2345_2345 - mat[0][2] * det4_2345_1345 + mat[0][3] * det4_2345_1245 - mat[0][4] * det4_2345_1235 + mat[0][5] * det4_2345_1234; + + mat[0][0] = det5_12345_12345 * invDet; + mat[0][1] = -det5_02345_12345 * invDet; + mat[0][2] = det5_01345_12345 * invDet; + mat[0][3] = -det5_01245_12345 * invDet; + mat[0][4] = det5_01235_12345 * invDet; + mat[0][5] = -det5_01234_12345 * invDet; + + mat[1][0] = -det5_12345_02345 * invDet; + mat[1][1] = det5_02345_02345 * invDet; + mat[1][2] = -det5_01345_02345 * invDet; + mat[1][3] = det5_01245_02345 * invDet; + mat[1][4] = -det5_01235_02345 * invDet; + mat[1][5] = det5_01234_02345 * invDet; + + mat[2][0] = det5_12345_01345 * invDet; + mat[2][1] = -det5_02345_01345 * invDet; + mat[2][2] = det5_01345_01345 * invDet; + mat[2][3] = -det5_01245_01345 * invDet; + mat[2][4] = det5_01235_01345 * invDet; + mat[2][5] = -det5_01234_01345 * invDet; + + mat[3][0] = -det5_12345_01245 * invDet; + mat[3][1] = det5_02345_01245 * invDet; + mat[3][2] = -det5_01345_01245 * invDet; + mat[3][3] = det5_01245_01245 * invDet; + mat[3][4] = -det5_01235_01245 * invDet; + mat[3][5] = det5_01234_01245 * invDet; + + mat[4][0] = det5_12345_01235 * invDet; + mat[4][1] = -det5_02345_01235 * invDet; + mat[4][2] = det5_01345_01235 * invDet; + mat[4][3] = -det5_01245_01235 * invDet; + mat[4][4] = det5_01235_01235 * invDet; + mat[4][5] = -det5_01234_01235 * invDet; + + mat[5][0] = -det5_12345_01234 * invDet; + mat[5][1] = det5_02345_01234 * invDet; + mat[5][2] = -det5_01345_01234 * invDet; + mat[5][3] = det5_01245_01234 * invDet; + mat[5][4] = -det5_01235_01234 * invDet; + mat[5][5] = det5_01234_01234 * invDet; + + return true; +} + +/* +============ +idMat6::InverseFastSelf +============ +*/ +bool idMat6::InverseFastSelf( void ) { +#if 0 + // 810+6+36 = 852 multiplications + // 1 division + double det, invDet; + + // 2x2 sub-determinants required to calculate 6x6 determinant + float det2_45_01 = mat[4][0] * mat[5][1] - mat[4][1] * mat[5][0]; + float det2_45_02 = mat[4][0] * mat[5][2] - mat[4][2] * mat[5][0]; + float det2_45_03 = mat[4][0] * mat[5][3] - mat[4][3] * mat[5][0]; + float det2_45_04 = mat[4][0] * mat[5][4] - mat[4][4] * mat[5][0]; + float det2_45_05 = mat[4][0] * mat[5][5] - mat[4][5] * mat[5][0]; + float det2_45_12 = mat[4][1] * mat[5][2] - mat[4][2] * mat[5][1]; + float det2_45_13 = mat[4][1] * mat[5][3] - mat[4][3] * mat[5][1]; + float det2_45_14 = mat[4][1] * mat[5][4] - mat[4][4] * mat[5][1]; + float det2_45_15 = mat[4][1] * mat[5][5] - mat[4][5] * mat[5][1]; + float det2_45_23 = mat[4][2] * mat[5][3] - mat[4][3] * mat[5][2]; + float det2_45_24 = mat[4][2] * mat[5][4] - mat[4][4] * mat[5][2]; + float det2_45_25 = mat[4][2] * mat[5][5] - mat[4][5] * mat[5][2]; + float det2_45_34 = mat[4][3] * mat[5][4] - mat[4][4] * mat[5][3]; + float det2_45_35 = mat[4][3] * mat[5][5] - mat[4][5] * mat[5][3]; + float det2_45_45 = mat[4][4] * mat[5][5] - mat[4][5] * mat[5][4]; + + // 3x3 sub-determinants required to calculate 6x6 determinant + float det3_345_012 = mat[3][0] * det2_45_12 - mat[3][1] * det2_45_02 + mat[3][2] * det2_45_01; + float det3_345_013 = mat[3][0] * det2_45_13 - mat[3][1] * det2_45_03 + mat[3][3] * det2_45_01; + float det3_345_014 = mat[3][0] * det2_45_14 - mat[3][1] * det2_45_04 + mat[3][4] * det2_45_01; + float det3_345_015 = mat[3][0] * det2_45_15 - mat[3][1] * det2_45_05 + mat[3][5] * det2_45_01; + float det3_345_023 = mat[3][0] * det2_45_23 - mat[3][2] * det2_45_03 + mat[3][3] * det2_45_02; + float det3_345_024 = mat[3][0] * det2_45_24 - mat[3][2] * det2_45_04 + mat[3][4] * det2_45_02; + float det3_345_025 = mat[3][0] * det2_45_25 - mat[3][2] * det2_45_05 + mat[3][5] * det2_45_02; + float det3_345_034 = mat[3][0] * det2_45_34 - mat[3][3] * det2_45_04 + mat[3][4] * det2_45_03; + float det3_345_035 = mat[3][0] * det2_45_35 - mat[3][3] * det2_45_05 + mat[3][5] * det2_45_03; + float det3_345_045 = mat[3][0] * det2_45_45 - mat[3][4] * det2_45_05 + mat[3][5] * det2_45_04; + float det3_345_123 = mat[3][1] * det2_45_23 - mat[3][2] * det2_45_13 + mat[3][3] * det2_45_12; + float det3_345_124 = mat[3][1] * det2_45_24 - mat[3][2] * det2_45_14 + mat[3][4] * det2_45_12; + float det3_345_125 = mat[3][1] * det2_45_25 - mat[3][2] * det2_45_15 + mat[3][5] * det2_45_12; + float det3_345_134 = mat[3][1] * det2_45_34 - mat[3][3] * det2_45_14 + mat[3][4] * det2_45_13; + float det3_345_135 = mat[3][1] * det2_45_35 - mat[3][3] * det2_45_15 + mat[3][5] * det2_45_13; + float det3_345_145 = mat[3][1] * det2_45_45 - mat[3][4] * det2_45_15 + mat[3][5] * det2_45_14; + float det3_345_234 = mat[3][2] * det2_45_34 - mat[3][3] * det2_45_24 + mat[3][4] * det2_45_23; + float det3_345_235 = mat[3][2] * det2_45_35 - mat[3][3] * det2_45_25 + mat[3][5] * det2_45_23; + float det3_345_245 = mat[3][2] * det2_45_45 - mat[3][4] * det2_45_25 + mat[3][5] * det2_45_24; + float det3_345_345 = mat[3][3] * det2_45_45 - mat[3][4] * det2_45_35 + mat[3][5] * det2_45_34; + + // 4x4 sub-determinants required to calculate 6x6 determinant + float det4_2345_0123 = mat[2][0] * det3_345_123 - mat[2][1] * det3_345_023 + mat[2][2] * det3_345_013 - mat[2][3] * det3_345_012; + float det4_2345_0124 = mat[2][0] * det3_345_124 - mat[2][1] * det3_345_024 + mat[2][2] * det3_345_014 - mat[2][4] * det3_345_012; + float det4_2345_0125 = mat[2][0] * det3_345_125 - mat[2][1] * det3_345_025 + mat[2][2] * det3_345_015 - mat[2][5] * det3_345_012; + float det4_2345_0134 = mat[2][0] * det3_345_134 - mat[2][1] * det3_345_034 + mat[2][3] * det3_345_014 - mat[2][4] * det3_345_013; + float det4_2345_0135 = mat[2][0] * det3_345_135 - mat[2][1] * det3_345_035 + mat[2][3] * det3_345_015 - mat[2][5] * det3_345_013; + float det4_2345_0145 = mat[2][0] * det3_345_145 - mat[2][1] * det3_345_045 + mat[2][4] * det3_345_015 - mat[2][5] * det3_345_014; + float det4_2345_0234 = mat[2][0] * det3_345_234 - mat[2][2] * det3_345_034 + mat[2][3] * det3_345_024 - mat[2][4] * det3_345_023; + float det4_2345_0235 = mat[2][0] * det3_345_235 - mat[2][2] * det3_345_035 + mat[2][3] * det3_345_025 - mat[2][5] * det3_345_023; + float det4_2345_0245 = mat[2][0] * det3_345_245 - mat[2][2] * det3_345_045 + mat[2][4] * det3_345_025 - mat[2][5] * det3_345_024; + float det4_2345_0345 = mat[2][0] * det3_345_345 - mat[2][3] * det3_345_045 + mat[2][4] * det3_345_035 - mat[2][5] * det3_345_034; + float det4_2345_1234 = mat[2][1] * det3_345_234 - mat[2][2] * det3_345_134 + mat[2][3] * det3_345_124 - mat[2][4] * det3_345_123; + float det4_2345_1235 = mat[2][1] * det3_345_235 - mat[2][2] * det3_345_135 + mat[2][3] * det3_345_125 - mat[2][5] * det3_345_123; + float det4_2345_1245 = mat[2][1] * det3_345_245 - mat[2][2] * det3_345_145 + mat[2][4] * det3_345_125 - mat[2][5] * det3_345_124; + float det4_2345_1345 = mat[2][1] * det3_345_345 - mat[2][3] * det3_345_145 + mat[2][4] * det3_345_135 - mat[2][5] * det3_345_134; + float det4_2345_2345 = mat[2][2] * det3_345_345 - mat[2][3] * det3_345_245 + mat[2][4] * det3_345_235 - mat[2][5] * det3_345_234; + + // 5x5 sub-determinants required to calculate 6x6 determinant + float det5_12345_01234 = mat[1][0] * det4_2345_1234 - mat[1][1] * det4_2345_0234 + mat[1][2] * det4_2345_0134 - mat[1][3] * det4_2345_0124 + mat[1][4] * det4_2345_0123; + float det5_12345_01235 = mat[1][0] * det4_2345_1235 - mat[1][1] * det4_2345_0235 + mat[1][2] * det4_2345_0135 - mat[1][3] * det4_2345_0125 + mat[1][5] * det4_2345_0123; + float det5_12345_01245 = mat[1][0] * det4_2345_1245 - mat[1][1] * det4_2345_0245 + mat[1][2] * det4_2345_0145 - mat[1][4] * det4_2345_0125 + mat[1][5] * det4_2345_0124; + float det5_12345_01345 = mat[1][0] * det4_2345_1345 - mat[1][1] * det4_2345_0345 + mat[1][3] * det4_2345_0145 - mat[1][4] * det4_2345_0135 + mat[1][5] * det4_2345_0134; + float det5_12345_02345 = mat[1][0] * det4_2345_2345 - mat[1][2] * det4_2345_0345 + mat[1][3] * det4_2345_0245 - mat[1][4] * det4_2345_0235 + mat[1][5] * det4_2345_0234; + float det5_12345_12345 = mat[1][1] * det4_2345_2345 - mat[1][2] * det4_2345_1345 + mat[1][3] * det4_2345_1245 - mat[1][4] * det4_2345_1235 + mat[1][5] * det4_2345_1234; + + // determinant of 6x6 matrix + det = mat[0][0] * det5_12345_12345 - mat[0][1] * det5_12345_02345 + mat[0][2] * det5_12345_01345 - + mat[0][3] * det5_12345_01245 + mat[0][4] * det5_12345_01235 - mat[0][5] * det5_12345_01234; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + // remaining 2x2 sub-determinants + float det2_34_01 = mat[3][0] * mat[4][1] - mat[3][1] * mat[4][0]; + float det2_34_02 = mat[3][0] * mat[4][2] - mat[3][2] * mat[4][0]; + float det2_34_03 = mat[3][0] * mat[4][3] - mat[3][3] * mat[4][0]; + float det2_34_04 = mat[3][0] * mat[4][4] - mat[3][4] * mat[4][0]; + float det2_34_05 = mat[3][0] * mat[4][5] - mat[3][5] * mat[4][0]; + float det2_34_12 = mat[3][1] * mat[4][2] - mat[3][2] * mat[4][1]; + float det2_34_13 = mat[3][1] * mat[4][3] - mat[3][3] * mat[4][1]; + float det2_34_14 = mat[3][1] * mat[4][4] - mat[3][4] * mat[4][1]; + float det2_34_15 = mat[3][1] * mat[4][5] - mat[3][5] * mat[4][1]; + float det2_34_23 = mat[3][2] * mat[4][3] - mat[3][3] * mat[4][2]; + float det2_34_24 = mat[3][2] * mat[4][4] - mat[3][4] * mat[4][2]; + float det2_34_25 = mat[3][2] * mat[4][5] - mat[3][5] * mat[4][2]; + float det2_34_34 = mat[3][3] * mat[4][4] - mat[3][4] * mat[4][3]; + float det2_34_35 = mat[3][3] * mat[4][5] - mat[3][5] * mat[4][3]; + float det2_34_45 = mat[3][4] * mat[4][5] - mat[3][5] * mat[4][4]; + float det2_35_01 = mat[3][0] * mat[5][1] - mat[3][1] * mat[5][0]; + float det2_35_02 = mat[3][0] * mat[5][2] - mat[3][2] * mat[5][0]; + float det2_35_03 = mat[3][0] * mat[5][3] - mat[3][3] * mat[5][0]; + float det2_35_04 = mat[3][0] * mat[5][4] - mat[3][4] * mat[5][0]; + float det2_35_05 = mat[3][0] * mat[5][5] - mat[3][5] * mat[5][0]; + float det2_35_12 = mat[3][1] * mat[5][2] - mat[3][2] * mat[5][1]; + float det2_35_13 = mat[3][1] * mat[5][3] - mat[3][3] * mat[5][1]; + float det2_35_14 = mat[3][1] * mat[5][4] - mat[3][4] * mat[5][1]; + float det2_35_15 = mat[3][1] * mat[5][5] - mat[3][5] * mat[5][1]; + float det2_35_23 = mat[3][2] * mat[5][3] - mat[3][3] * mat[5][2]; + float det2_35_24 = mat[3][2] * mat[5][4] - mat[3][4] * mat[5][2]; + float det2_35_25 = mat[3][2] * mat[5][5] - mat[3][5] * mat[5][2]; + float det2_35_34 = mat[3][3] * mat[5][4] - mat[3][4] * mat[5][3]; + float det2_35_35 = mat[3][3] * mat[5][5] - mat[3][5] * mat[5][3]; + float det2_35_45 = mat[3][4] * mat[5][5] - mat[3][5] * mat[5][4]; + + // remaining 3x3 sub-determinants + float det3_234_012 = mat[2][0] * det2_34_12 - mat[2][1] * det2_34_02 + mat[2][2] * det2_34_01; + float det3_234_013 = mat[2][0] * det2_34_13 - mat[2][1] * det2_34_03 + mat[2][3] * det2_34_01; + float det3_234_014 = mat[2][0] * det2_34_14 - mat[2][1] * det2_34_04 + mat[2][4] * det2_34_01; + float det3_234_015 = mat[2][0] * det2_34_15 - mat[2][1] * det2_34_05 + mat[2][5] * det2_34_01; + float det3_234_023 = mat[2][0] * det2_34_23 - mat[2][2] * det2_34_03 + mat[2][3] * det2_34_02; + float det3_234_024 = mat[2][0] * det2_34_24 - mat[2][2] * det2_34_04 + mat[2][4] * det2_34_02; + float det3_234_025 = mat[2][0] * det2_34_25 - mat[2][2] * det2_34_05 + mat[2][5] * det2_34_02; + float det3_234_034 = mat[2][0] * det2_34_34 - mat[2][3] * det2_34_04 + mat[2][4] * det2_34_03; + float det3_234_035 = mat[2][0] * det2_34_35 - mat[2][3] * det2_34_05 + mat[2][5] * det2_34_03; + float det3_234_045 = mat[2][0] * det2_34_45 - mat[2][4] * det2_34_05 + mat[2][5] * det2_34_04; + float det3_234_123 = mat[2][1] * det2_34_23 - mat[2][2] * det2_34_13 + mat[2][3] * det2_34_12; + float det3_234_124 = mat[2][1] * det2_34_24 - mat[2][2] * det2_34_14 + mat[2][4] * det2_34_12; + float det3_234_125 = mat[2][1] * det2_34_25 - mat[2][2] * det2_34_15 + mat[2][5] * det2_34_12; + float det3_234_134 = mat[2][1] * det2_34_34 - mat[2][3] * det2_34_14 + mat[2][4] * det2_34_13; + float det3_234_135 = mat[2][1] * det2_34_35 - mat[2][3] * det2_34_15 + mat[2][5] * det2_34_13; + float det3_234_145 = mat[2][1] * det2_34_45 - mat[2][4] * det2_34_15 + mat[2][5] * det2_34_14; + float det3_234_234 = mat[2][2] * det2_34_34 - mat[2][3] * det2_34_24 + mat[2][4] * det2_34_23; + float det3_234_235 = mat[2][2] * det2_34_35 - mat[2][3] * det2_34_25 + mat[2][5] * det2_34_23; + float det3_234_245 = mat[2][2] * det2_34_45 - mat[2][4] * det2_34_25 + mat[2][5] * det2_34_24; + float det3_234_345 = mat[2][3] * det2_34_45 - mat[2][4] * det2_34_35 + mat[2][5] * det2_34_34; + float det3_235_012 = mat[2][0] * det2_35_12 - mat[2][1] * det2_35_02 + mat[2][2] * det2_35_01; + float det3_235_013 = mat[2][0] * det2_35_13 - mat[2][1] * det2_35_03 + mat[2][3] * det2_35_01; + float det3_235_014 = mat[2][0] * det2_35_14 - mat[2][1] * det2_35_04 + mat[2][4] * det2_35_01; + float det3_235_015 = mat[2][0] * det2_35_15 - mat[2][1] * det2_35_05 + mat[2][5] * det2_35_01; + float det3_235_023 = mat[2][0] * det2_35_23 - mat[2][2] * det2_35_03 + mat[2][3] * det2_35_02; + float det3_235_024 = mat[2][0] * det2_35_24 - mat[2][2] * det2_35_04 + mat[2][4] * det2_35_02; + float det3_235_025 = mat[2][0] * det2_35_25 - mat[2][2] * det2_35_05 + mat[2][5] * det2_35_02; + float det3_235_034 = mat[2][0] * det2_35_34 - mat[2][3] * det2_35_04 + mat[2][4] * det2_35_03; + float det3_235_035 = mat[2][0] * det2_35_35 - mat[2][3] * det2_35_05 + mat[2][5] * det2_35_03; + float det3_235_045 = mat[2][0] * det2_35_45 - mat[2][4] * det2_35_05 + mat[2][5] * det2_35_04; + float det3_235_123 = mat[2][1] * det2_35_23 - mat[2][2] * det2_35_13 + mat[2][3] * det2_35_12; + float det3_235_124 = mat[2][1] * det2_35_24 - mat[2][2] * det2_35_14 + mat[2][4] * det2_35_12; + float det3_235_125 = mat[2][1] * det2_35_25 - mat[2][2] * det2_35_15 + mat[2][5] * det2_35_12; + float det3_235_134 = mat[2][1] * det2_35_34 - mat[2][3] * det2_35_14 + mat[2][4] * det2_35_13; + float det3_235_135 = mat[2][1] * det2_35_35 - mat[2][3] * det2_35_15 + mat[2][5] * det2_35_13; + float det3_235_145 = mat[2][1] * det2_35_45 - mat[2][4] * det2_35_15 + mat[2][5] * det2_35_14; + float det3_235_234 = mat[2][2] * det2_35_34 - mat[2][3] * det2_35_24 + mat[2][4] * det2_35_23; + float det3_235_235 = mat[2][2] * det2_35_35 - mat[2][3] * det2_35_25 + mat[2][5] * det2_35_23; + float det3_235_245 = mat[2][2] * det2_35_45 - mat[2][4] * det2_35_25 + mat[2][5] * det2_35_24; + float det3_235_345 = mat[2][3] * det2_35_45 - mat[2][4] * det2_35_35 + mat[2][5] * det2_35_34; + float det3_245_012 = mat[2][0] * det2_45_12 - mat[2][1] * det2_45_02 + mat[2][2] * det2_45_01; + float det3_245_013 = mat[2][0] * det2_45_13 - mat[2][1] * det2_45_03 + mat[2][3] * det2_45_01; + float det3_245_014 = mat[2][0] * det2_45_14 - mat[2][1] * det2_45_04 + mat[2][4] * det2_45_01; + float det3_245_015 = mat[2][0] * det2_45_15 - mat[2][1] * det2_45_05 + mat[2][5] * det2_45_01; + float det3_245_023 = mat[2][0] * det2_45_23 - mat[2][2] * det2_45_03 + mat[2][3] * det2_45_02; + float det3_245_024 = mat[2][0] * det2_45_24 - mat[2][2] * det2_45_04 + mat[2][4] * det2_45_02; + float det3_245_025 = mat[2][0] * det2_45_25 - mat[2][2] * det2_45_05 + mat[2][5] * det2_45_02; + float det3_245_034 = mat[2][0] * det2_45_34 - mat[2][3] * det2_45_04 + mat[2][4] * det2_45_03; + float det3_245_035 = mat[2][0] * det2_45_35 - mat[2][3] * det2_45_05 + mat[2][5] * det2_45_03; + float det3_245_045 = mat[2][0] * det2_45_45 - mat[2][4] * det2_45_05 + mat[2][5] * det2_45_04; + float det3_245_123 = mat[2][1] * det2_45_23 - mat[2][2] * det2_45_13 + mat[2][3] * det2_45_12; + float det3_245_124 = mat[2][1] * det2_45_24 - mat[2][2] * det2_45_14 + mat[2][4] * det2_45_12; + float det3_245_125 = mat[2][1] * det2_45_25 - mat[2][2] * det2_45_15 + mat[2][5] * det2_45_12; + float det3_245_134 = mat[2][1] * det2_45_34 - mat[2][3] * det2_45_14 + mat[2][4] * det2_45_13; + float det3_245_135 = mat[2][1] * det2_45_35 - mat[2][3] * det2_45_15 + mat[2][5] * det2_45_13; + float det3_245_145 = mat[2][1] * det2_45_45 - mat[2][4] * det2_45_15 + mat[2][5] * det2_45_14; + float det3_245_234 = mat[2][2] * det2_45_34 - mat[2][3] * det2_45_24 + mat[2][4] * det2_45_23; + float det3_245_235 = mat[2][2] * det2_45_35 - mat[2][3] * det2_45_25 + mat[2][5] * det2_45_23; + float det3_245_245 = mat[2][2] * det2_45_45 - mat[2][4] * det2_45_25 + mat[2][5] * det2_45_24; + float det3_245_345 = mat[2][3] * det2_45_45 - mat[2][4] * det2_45_35 + mat[2][5] * det2_45_34; + + // remaining 4x4 sub-determinants + float det4_1234_0123 = mat[1][0] * det3_234_123 - mat[1][1] * det3_234_023 + mat[1][2] * det3_234_013 - mat[1][3] * det3_234_012; + float det4_1234_0124 = mat[1][0] * det3_234_124 - mat[1][1] * det3_234_024 + mat[1][2] * det3_234_014 - mat[1][4] * det3_234_012; + float det4_1234_0125 = mat[1][0] * det3_234_125 - mat[1][1] * det3_234_025 + mat[1][2] * det3_234_015 - mat[1][5] * det3_234_012; + float det4_1234_0134 = mat[1][0] * det3_234_134 - mat[1][1] * det3_234_034 + mat[1][3] * det3_234_014 - mat[1][4] * det3_234_013; + float det4_1234_0135 = mat[1][0] * det3_234_135 - mat[1][1] * det3_234_035 + mat[1][3] * det3_234_015 - mat[1][5] * det3_234_013; + float det4_1234_0145 = mat[1][0] * det3_234_145 - mat[1][1] * det3_234_045 + mat[1][4] * det3_234_015 - mat[1][5] * det3_234_014; + float det4_1234_0234 = mat[1][0] * det3_234_234 - mat[1][2] * det3_234_034 + mat[1][3] * det3_234_024 - mat[1][4] * det3_234_023; + float det4_1234_0235 = mat[1][0] * det3_234_235 - mat[1][2] * det3_234_035 + mat[1][3] * det3_234_025 - mat[1][5] * det3_234_023; + float det4_1234_0245 = mat[1][0] * det3_234_245 - mat[1][2] * det3_234_045 + mat[1][4] * det3_234_025 - mat[1][5] * det3_234_024; + float det4_1234_0345 = mat[1][0] * det3_234_345 - mat[1][3] * det3_234_045 + mat[1][4] * det3_234_035 - mat[1][5] * det3_234_034; + float det4_1234_1234 = mat[1][1] * det3_234_234 - mat[1][2] * det3_234_134 + mat[1][3] * det3_234_124 - mat[1][4] * det3_234_123; + float det4_1234_1235 = mat[1][1] * det3_234_235 - mat[1][2] * det3_234_135 + mat[1][3] * det3_234_125 - mat[1][5] * det3_234_123; + float det4_1234_1245 = mat[1][1] * det3_234_245 - mat[1][2] * det3_234_145 + mat[1][4] * det3_234_125 - mat[1][5] * det3_234_124; + float det4_1234_1345 = mat[1][1] * det3_234_345 - mat[1][3] * det3_234_145 + mat[1][4] * det3_234_135 - mat[1][5] * det3_234_134; + float det4_1234_2345 = mat[1][2] * det3_234_345 - mat[1][3] * det3_234_245 + mat[1][4] * det3_234_235 - mat[1][5] * det3_234_234; + float det4_1235_0123 = mat[1][0] * det3_235_123 - mat[1][1] * det3_235_023 + mat[1][2] * det3_235_013 - mat[1][3] * det3_235_012; + float det4_1235_0124 = mat[1][0] * det3_235_124 - mat[1][1] * det3_235_024 + mat[1][2] * det3_235_014 - mat[1][4] * det3_235_012; + float det4_1235_0125 = mat[1][0] * det3_235_125 - mat[1][1] * det3_235_025 + mat[1][2] * det3_235_015 - mat[1][5] * det3_235_012; + float det4_1235_0134 = mat[1][0] * det3_235_134 - mat[1][1] * det3_235_034 + mat[1][3] * det3_235_014 - mat[1][4] * det3_235_013; + float det4_1235_0135 = mat[1][0] * det3_235_135 - mat[1][1] * det3_235_035 + mat[1][3] * det3_235_015 - mat[1][5] * det3_235_013; + float det4_1235_0145 = mat[1][0] * det3_235_145 - mat[1][1] * det3_235_045 + mat[1][4] * det3_235_015 - mat[1][5] * det3_235_014; + float det4_1235_0234 = mat[1][0] * det3_235_234 - mat[1][2] * det3_235_034 + mat[1][3] * det3_235_024 - mat[1][4] * det3_235_023; + float det4_1235_0235 = mat[1][0] * det3_235_235 - mat[1][2] * det3_235_035 + mat[1][3] * det3_235_025 - mat[1][5] * det3_235_023; + float det4_1235_0245 = mat[1][0] * det3_235_245 - mat[1][2] * det3_235_045 + mat[1][4] * det3_235_025 - mat[1][5] * det3_235_024; + float det4_1235_0345 = mat[1][0] * det3_235_345 - mat[1][3] * det3_235_045 + mat[1][4] * det3_235_035 - mat[1][5] * det3_235_034; + float det4_1235_1234 = mat[1][1] * det3_235_234 - mat[1][2] * det3_235_134 + mat[1][3] * det3_235_124 - mat[1][4] * det3_235_123; + float det4_1235_1235 = mat[1][1] * det3_235_235 - mat[1][2] * det3_235_135 + mat[1][3] * det3_235_125 - mat[1][5] * det3_235_123; + float det4_1235_1245 = mat[1][1] * det3_235_245 - mat[1][2] * det3_235_145 + mat[1][4] * det3_235_125 - mat[1][5] * det3_235_124; + float det4_1235_1345 = mat[1][1] * det3_235_345 - mat[1][3] * det3_235_145 + mat[1][4] * det3_235_135 - mat[1][5] * det3_235_134; + float det4_1235_2345 = mat[1][2] * det3_235_345 - mat[1][3] * det3_235_245 + mat[1][4] * det3_235_235 - mat[1][5] * det3_235_234; + float det4_1245_0123 = mat[1][0] * det3_245_123 - mat[1][1] * det3_245_023 + mat[1][2] * det3_245_013 - mat[1][3] * det3_245_012; + float det4_1245_0124 = mat[1][0] * det3_245_124 - mat[1][1] * det3_245_024 + mat[1][2] * det3_245_014 - mat[1][4] * det3_245_012; + float det4_1245_0125 = mat[1][0] * det3_245_125 - mat[1][1] * det3_245_025 + mat[1][2] * det3_245_015 - mat[1][5] * det3_245_012; + float det4_1245_0134 = mat[1][0] * det3_245_134 - mat[1][1] * det3_245_034 + mat[1][3] * det3_245_014 - mat[1][4] * det3_245_013; + float det4_1245_0135 = mat[1][0] * det3_245_135 - mat[1][1] * det3_245_035 + mat[1][3] * det3_245_015 - mat[1][5] * det3_245_013; + float det4_1245_0145 = mat[1][0] * det3_245_145 - mat[1][1] * det3_245_045 + mat[1][4] * det3_245_015 - mat[1][5] * det3_245_014; + float det4_1245_0234 = mat[1][0] * det3_245_234 - mat[1][2] * det3_245_034 + mat[1][3] * det3_245_024 - mat[1][4] * det3_245_023; + float det4_1245_0235 = mat[1][0] * det3_245_235 - mat[1][2] * det3_245_035 + mat[1][3] * det3_245_025 - mat[1][5] * det3_245_023; + float det4_1245_0245 = mat[1][0] * det3_245_245 - mat[1][2] * det3_245_045 + mat[1][4] * det3_245_025 - mat[1][5] * det3_245_024; + float det4_1245_0345 = mat[1][0] * det3_245_345 - mat[1][3] * det3_245_045 + mat[1][4] * det3_245_035 - mat[1][5] * det3_245_034; + float det4_1245_1234 = mat[1][1] * det3_245_234 - mat[1][2] * det3_245_134 + mat[1][3] * det3_245_124 - mat[1][4] * det3_245_123; + float det4_1245_1235 = mat[1][1] * det3_245_235 - mat[1][2] * det3_245_135 + mat[1][3] * det3_245_125 - mat[1][5] * det3_245_123; + float det4_1245_1245 = mat[1][1] * det3_245_245 - mat[1][2] * det3_245_145 + mat[1][4] * det3_245_125 - mat[1][5] * det3_245_124; + float det4_1245_1345 = mat[1][1] * det3_245_345 - mat[1][3] * det3_245_145 + mat[1][4] * det3_245_135 - mat[1][5] * det3_245_134; + float det4_1245_2345 = mat[1][2] * det3_245_345 - mat[1][3] * det3_245_245 + mat[1][4] * det3_245_235 - mat[1][5] * det3_245_234; + float det4_1345_0123 = mat[1][0] * det3_345_123 - mat[1][1] * det3_345_023 + mat[1][2] * det3_345_013 - mat[1][3] * det3_345_012; + float det4_1345_0124 = mat[1][0] * det3_345_124 - mat[1][1] * det3_345_024 + mat[1][2] * det3_345_014 - mat[1][4] * det3_345_012; + float det4_1345_0125 = mat[1][0] * det3_345_125 - mat[1][1] * det3_345_025 + mat[1][2] * det3_345_015 - mat[1][5] * det3_345_012; + float det4_1345_0134 = mat[1][0] * det3_345_134 - mat[1][1] * det3_345_034 + mat[1][3] * det3_345_014 - mat[1][4] * det3_345_013; + float det4_1345_0135 = mat[1][0] * det3_345_135 - mat[1][1] * det3_345_035 + mat[1][3] * det3_345_015 - mat[1][5] * det3_345_013; + float det4_1345_0145 = mat[1][0] * det3_345_145 - mat[1][1] * det3_345_045 + mat[1][4] * det3_345_015 - mat[1][5] * det3_345_014; + float det4_1345_0234 = mat[1][0] * det3_345_234 - mat[1][2] * det3_345_034 + mat[1][3] * det3_345_024 - mat[1][4] * det3_345_023; + float det4_1345_0235 = mat[1][0] * det3_345_235 - mat[1][2] * det3_345_035 + mat[1][3] * det3_345_025 - mat[1][5] * det3_345_023; + float det4_1345_0245 = mat[1][0] * det3_345_245 - mat[1][2] * det3_345_045 + mat[1][4] * det3_345_025 - mat[1][5] * det3_345_024; + float det4_1345_0345 = mat[1][0] * det3_345_345 - mat[1][3] * det3_345_045 + mat[1][4] * det3_345_035 - mat[1][5] * det3_345_034; + float det4_1345_1234 = mat[1][1] * det3_345_234 - mat[1][2] * det3_345_134 + mat[1][3] * det3_345_124 - mat[1][4] * det3_345_123; + float det4_1345_1235 = mat[1][1] * det3_345_235 - mat[1][2] * det3_345_135 + mat[1][3] * det3_345_125 - mat[1][5] * det3_345_123; + float det4_1345_1245 = mat[1][1] * det3_345_245 - mat[1][2] * det3_345_145 + mat[1][4] * det3_345_125 - mat[1][5] * det3_345_124; + float det4_1345_1345 = mat[1][1] * det3_345_345 - mat[1][3] * det3_345_145 + mat[1][4] * det3_345_135 - mat[1][5] * det3_345_134; + float det4_1345_2345 = mat[1][2] * det3_345_345 - mat[1][3] * det3_345_245 + mat[1][4] * det3_345_235 - mat[1][5] * det3_345_234; + + // remaining 5x5 sub-determinants + float det5_01234_01234 = mat[0][0] * det4_1234_1234 - mat[0][1] * det4_1234_0234 + mat[0][2] * det4_1234_0134 - mat[0][3] * det4_1234_0124 + mat[0][4] * det4_1234_0123; + float det5_01234_01235 = mat[0][0] * det4_1234_1235 - mat[0][1] * det4_1234_0235 + mat[0][2] * det4_1234_0135 - mat[0][3] * det4_1234_0125 + mat[0][5] * det4_1234_0123; + float det5_01234_01245 = mat[0][0] * det4_1234_1245 - mat[0][1] * det4_1234_0245 + mat[0][2] * det4_1234_0145 - mat[0][4] * det4_1234_0125 + mat[0][5] * det4_1234_0124; + float det5_01234_01345 = mat[0][0] * det4_1234_1345 - mat[0][1] * det4_1234_0345 + mat[0][3] * det4_1234_0145 - mat[0][4] * det4_1234_0135 + mat[0][5] * det4_1234_0134; + float det5_01234_02345 = mat[0][0] * det4_1234_2345 - mat[0][2] * det4_1234_0345 + mat[0][3] * det4_1234_0245 - mat[0][4] * det4_1234_0235 + mat[0][5] * det4_1234_0234; + float det5_01234_12345 = mat[0][1] * det4_1234_2345 - mat[0][2] * det4_1234_1345 + mat[0][3] * det4_1234_1245 - mat[0][4] * det4_1234_1235 + mat[0][5] * det4_1234_1234; + float det5_01235_01234 = mat[0][0] * det4_1235_1234 - mat[0][1] * det4_1235_0234 + mat[0][2] * det4_1235_0134 - mat[0][3] * det4_1235_0124 + mat[0][4] * det4_1235_0123; + float det5_01235_01235 = mat[0][0] * det4_1235_1235 - mat[0][1] * det4_1235_0235 + mat[0][2] * det4_1235_0135 - mat[0][3] * det4_1235_0125 + mat[0][5] * det4_1235_0123; + float det5_01235_01245 = mat[0][0] * det4_1235_1245 - mat[0][1] * det4_1235_0245 + mat[0][2] * det4_1235_0145 - mat[0][4] * det4_1235_0125 + mat[0][5] * det4_1235_0124; + float det5_01235_01345 = mat[0][0] * det4_1235_1345 - mat[0][1] * det4_1235_0345 + mat[0][3] * det4_1235_0145 - mat[0][4] * det4_1235_0135 + mat[0][5] * det4_1235_0134; + float det5_01235_02345 = mat[0][0] * det4_1235_2345 - mat[0][2] * det4_1235_0345 + mat[0][3] * det4_1235_0245 - mat[0][4] * det4_1235_0235 + mat[0][5] * det4_1235_0234; + float det5_01235_12345 = mat[0][1] * det4_1235_2345 - mat[0][2] * det4_1235_1345 + mat[0][3] * det4_1235_1245 - mat[0][4] * det4_1235_1235 + mat[0][5] * det4_1235_1234; + float det5_01245_01234 = mat[0][0] * det4_1245_1234 - mat[0][1] * det4_1245_0234 + mat[0][2] * det4_1245_0134 - mat[0][3] * det4_1245_0124 + mat[0][4] * det4_1245_0123; + float det5_01245_01235 = mat[0][0] * det4_1245_1235 - mat[0][1] * det4_1245_0235 + mat[0][2] * det4_1245_0135 - mat[0][3] * det4_1245_0125 + mat[0][5] * det4_1245_0123; + float det5_01245_01245 = mat[0][0] * det4_1245_1245 - mat[0][1] * det4_1245_0245 + mat[0][2] * det4_1245_0145 - mat[0][4] * det4_1245_0125 + mat[0][5] * det4_1245_0124; + float det5_01245_01345 = mat[0][0] * det4_1245_1345 - mat[0][1] * det4_1245_0345 + mat[0][3] * det4_1245_0145 - mat[0][4] * det4_1245_0135 + mat[0][5] * det4_1245_0134; + float det5_01245_02345 = mat[0][0] * det4_1245_2345 - mat[0][2] * det4_1245_0345 + mat[0][3] * det4_1245_0245 - mat[0][4] * det4_1245_0235 + mat[0][5] * det4_1245_0234; + float det5_01245_12345 = mat[0][1] * det4_1245_2345 - mat[0][2] * det4_1245_1345 + mat[0][3] * det4_1245_1245 - mat[0][4] * det4_1245_1235 + mat[0][5] * det4_1245_1234; + float det5_01345_01234 = mat[0][0] * det4_1345_1234 - mat[0][1] * det4_1345_0234 + mat[0][2] * det4_1345_0134 - mat[0][3] * det4_1345_0124 + mat[0][4] * det4_1345_0123; + float det5_01345_01235 = mat[0][0] * det4_1345_1235 - mat[0][1] * det4_1345_0235 + mat[0][2] * det4_1345_0135 - mat[0][3] * det4_1345_0125 + mat[0][5] * det4_1345_0123; + float det5_01345_01245 = mat[0][0] * det4_1345_1245 - mat[0][1] * det4_1345_0245 + mat[0][2] * det4_1345_0145 - mat[0][4] * det4_1345_0125 + mat[0][5] * det4_1345_0124; + float det5_01345_01345 = mat[0][0] * det4_1345_1345 - mat[0][1] * det4_1345_0345 + mat[0][3] * det4_1345_0145 - mat[0][4] * det4_1345_0135 + mat[0][5] * det4_1345_0134; + float det5_01345_02345 = mat[0][0] * det4_1345_2345 - mat[0][2] * det4_1345_0345 + mat[0][3] * det4_1345_0245 - mat[0][4] * det4_1345_0235 + mat[0][5] * det4_1345_0234; + float det5_01345_12345 = mat[0][1] * det4_1345_2345 - mat[0][2] * det4_1345_1345 + mat[0][3] * det4_1345_1245 - mat[0][4] * det4_1345_1235 + mat[0][5] * det4_1345_1234; + float det5_02345_01234 = mat[0][0] * det4_2345_1234 - mat[0][1] * det4_2345_0234 + mat[0][2] * det4_2345_0134 - mat[0][3] * det4_2345_0124 + mat[0][4] * det4_2345_0123; + float det5_02345_01235 = mat[0][0] * det4_2345_1235 - mat[0][1] * det4_2345_0235 + mat[0][2] * det4_2345_0135 - mat[0][3] * det4_2345_0125 + mat[0][5] * det4_2345_0123; + float det5_02345_01245 = mat[0][0] * det4_2345_1245 - mat[0][1] * det4_2345_0245 + mat[0][2] * det4_2345_0145 - mat[0][4] * det4_2345_0125 + mat[0][5] * det4_2345_0124; + float det5_02345_01345 = mat[0][0] * det4_2345_1345 - mat[0][1] * det4_2345_0345 + mat[0][3] * det4_2345_0145 - mat[0][4] * det4_2345_0135 + mat[0][5] * det4_2345_0134; + float det5_02345_02345 = mat[0][0] * det4_2345_2345 - mat[0][2] * det4_2345_0345 + mat[0][3] * det4_2345_0245 - mat[0][4] * det4_2345_0235 + mat[0][5] * det4_2345_0234; + float det5_02345_12345 = mat[0][1] * det4_2345_2345 - mat[0][2] * det4_2345_1345 + mat[0][3] * det4_2345_1245 - mat[0][4] * det4_2345_1235 + mat[0][5] * det4_2345_1234; + + mat[0][0] = det5_12345_12345 * invDet; + mat[0][1] = -det5_02345_12345 * invDet; + mat[0][2] = det5_01345_12345 * invDet; + mat[0][3] = -det5_01245_12345 * invDet; + mat[0][4] = det5_01235_12345 * invDet; + mat[0][5] = -det5_01234_12345 * invDet; + + mat[1][0] = -det5_12345_02345 * invDet; + mat[1][1] = det5_02345_02345 * invDet; + mat[1][2] = -det5_01345_02345 * invDet; + mat[1][3] = det5_01245_02345 * invDet; + mat[1][4] = -det5_01235_02345 * invDet; + mat[1][5] = det5_01234_02345 * invDet; + + mat[2][0] = det5_12345_01345 * invDet; + mat[2][1] = -det5_02345_01345 * invDet; + mat[2][2] = det5_01345_01345 * invDet; + mat[2][3] = -det5_01245_01345 * invDet; + mat[2][4] = det5_01235_01345 * invDet; + mat[2][5] = -det5_01234_01345 * invDet; + + mat[3][0] = -det5_12345_01245 * invDet; + mat[3][1] = det5_02345_01245 * invDet; + mat[3][2] = -det5_01345_01245 * invDet; + mat[3][3] = det5_01245_01245 * invDet; + mat[3][4] = -det5_01235_01245 * invDet; + mat[3][5] = det5_01234_01245 * invDet; + + mat[4][0] = det5_12345_01235 * invDet; + mat[4][1] = -det5_02345_01235 * invDet; + mat[4][2] = det5_01345_01235 * invDet; + mat[4][3] = -det5_01245_01235 * invDet; + mat[4][4] = det5_01235_01235 * invDet; + mat[4][5] = -det5_01234_01235 * invDet; + + mat[5][0] = -det5_12345_01234 * invDet; + mat[5][1] = det5_02345_01234 * invDet; + mat[5][2] = -det5_01345_01234 * invDet; + mat[5][3] = det5_01245_01234 * invDet; + mat[5][4] = -det5_01235_01234 * invDet; + mat[5][5] = det5_01234_01234 * invDet; + + return true; +#elif 0 + // 6*40 = 240 multiplications + // 6 divisions + float *mat = reinterpret_cast(this); + float s; + double d, di; + + di = mat[0]; + s = di; + mat[0] = d = 1.0f / di; + mat[1] *= d; + mat[2] *= d; + mat[3] *= d; + mat[4] *= d; + mat[5] *= d; + d = -d; + mat[6] *= d; + mat[12] *= d; + mat[18] *= d; + mat[24] *= d; + mat[30] *= d; + d = mat[6] * di; + mat[7] += mat[1] * d; + mat[8] += mat[2] * d; + mat[9] += mat[3] * d; + mat[10] += mat[4] * d; + mat[11] += mat[5] * d; + d = mat[12] * di; + mat[13] += mat[1] * d; + mat[14] += mat[2] * d; + mat[15] += mat[3] * d; + mat[16] += mat[4] * d; + mat[17] += mat[5] * d; + d = mat[18] * di; + mat[19] += mat[1] * d; + mat[20] += mat[2] * d; + mat[21] += mat[3] * d; + mat[22] += mat[4] * d; + mat[23] += mat[5] * d; + d = mat[24] * di; + mat[25] += mat[1] * d; + mat[26] += mat[2] * d; + mat[27] += mat[3] * d; + mat[28] += mat[4] * d; + mat[29] += mat[5] * d; + d = mat[30] * di; + mat[31] += mat[1] * d; + mat[32] += mat[2] * d; + mat[33] += mat[3] * d; + mat[34] += mat[4] * d; + mat[35] += mat[5] * d; + di = mat[7]; + s *= di; + mat[7] = d = 1.0f / di; + mat[6] *= d; + mat[8] *= d; + mat[9] *= d; + mat[10] *= d; + mat[11] *= d; + d = -d; + mat[1] *= d; + mat[13] *= d; + mat[19] *= d; + mat[25] *= d; + mat[31] *= d; + d = mat[1] * di; + mat[0] += mat[6] * d; + mat[2] += mat[8] * d; + mat[3] += mat[9] * d; + mat[4] += mat[10] * d; + mat[5] += mat[11] * d; + d = mat[13] * di; + mat[12] += mat[6] * d; + mat[14] += mat[8] * d; + mat[15] += mat[9] * d; + mat[16] += mat[10] * d; + mat[17] += mat[11] * d; + d = mat[19] * di; + mat[18] += mat[6] * d; + mat[20] += mat[8] * d; + mat[21] += mat[9] * d; + mat[22] += mat[10] * d; + mat[23] += mat[11] * d; + d = mat[25] * di; + mat[24] += mat[6] * d; + mat[26] += mat[8] * d; + mat[27] += mat[9] * d; + mat[28] += mat[10] * d; + mat[29] += mat[11] * d; + d = mat[31] * di; + mat[30] += mat[6] * d; + mat[32] += mat[8] * d; + mat[33] += mat[9] * d; + mat[34] += mat[10] * d; + mat[35] += mat[11] * d; + di = mat[14]; + s *= di; + mat[14] = d = 1.0f / di; + mat[12] *= d; + mat[13] *= d; + mat[15] *= d; + mat[16] *= d; + mat[17] *= d; + d = -d; + mat[2] *= d; + mat[8] *= d; + mat[20] *= d; + mat[26] *= d; + mat[32] *= d; + d = mat[2] * di; + mat[0] += mat[12] * d; + mat[1] += mat[13] * d; + mat[3] += mat[15] * d; + mat[4] += mat[16] * d; + mat[5] += mat[17] * d; + d = mat[8] * di; + mat[6] += mat[12] * d; + mat[7] += mat[13] * d; + mat[9] += mat[15] * d; + mat[10] += mat[16] * d; + mat[11] += mat[17] * d; + d = mat[20] * di; + mat[18] += mat[12] * d; + mat[19] += mat[13] * d; + mat[21] += mat[15] * d; + mat[22] += mat[16] * d; + mat[23] += mat[17] * d; + d = mat[26] * di; + mat[24] += mat[12] * d; + mat[25] += mat[13] * d; + mat[27] += mat[15] * d; + mat[28] += mat[16] * d; + mat[29] += mat[17] * d; + d = mat[32] * di; + mat[30] += mat[12] * d; + mat[31] += mat[13] * d; + mat[33] += mat[15] * d; + mat[34] += mat[16] * d; + mat[35] += mat[17] * d; + di = mat[21]; + s *= di; + mat[21] = d = 1.0f / di; + mat[18] *= d; + mat[19] *= d; + mat[20] *= d; + mat[22] *= d; + mat[23] *= d; + d = -d; + mat[3] *= d; + mat[9] *= d; + mat[15] *= d; + mat[27] *= d; + mat[33] *= d; + d = mat[3] * di; + mat[0] += mat[18] * d; + mat[1] += mat[19] * d; + mat[2] += mat[20] * d; + mat[4] += mat[22] * d; + mat[5] += mat[23] * d; + d = mat[9] * di; + mat[6] += mat[18] * d; + mat[7] += mat[19] * d; + mat[8] += mat[20] * d; + mat[10] += mat[22] * d; + mat[11] += mat[23] * d; + d = mat[15] * di; + mat[12] += mat[18] * d; + mat[13] += mat[19] * d; + mat[14] += mat[20] * d; + mat[16] += mat[22] * d; + mat[17] += mat[23] * d; + d = mat[27] * di; + mat[24] += mat[18] * d; + mat[25] += mat[19] * d; + mat[26] += mat[20] * d; + mat[28] += mat[22] * d; + mat[29] += mat[23] * d; + d = mat[33] * di; + mat[30] += mat[18] * d; + mat[31] += mat[19] * d; + mat[32] += mat[20] * d; + mat[34] += mat[22] * d; + mat[35] += mat[23] * d; + di = mat[28]; + s *= di; + mat[28] = d = 1.0f / di; + mat[24] *= d; + mat[25] *= d; + mat[26] *= d; + mat[27] *= d; + mat[29] *= d; + d = -d; + mat[4] *= d; + mat[10] *= d; + mat[16] *= d; + mat[22] *= d; + mat[34] *= d; + d = mat[4] * di; + mat[0] += mat[24] * d; + mat[1] += mat[25] * d; + mat[2] += mat[26] * d; + mat[3] += mat[27] * d; + mat[5] += mat[29] * d; + d = mat[10] * di; + mat[6] += mat[24] * d; + mat[7] += mat[25] * d; + mat[8] += mat[26] * d; + mat[9] += mat[27] * d; + mat[11] += mat[29] * d; + d = mat[16] * di; + mat[12] += mat[24] * d; + mat[13] += mat[25] * d; + mat[14] += mat[26] * d; + mat[15] += mat[27] * d; + mat[17] += mat[29] * d; + d = mat[22] * di; + mat[18] += mat[24] * d; + mat[19] += mat[25] * d; + mat[20] += mat[26] * d; + mat[21] += mat[27] * d; + mat[23] += mat[29] * d; + d = mat[34] * di; + mat[30] += mat[24] * d; + mat[31] += mat[25] * d; + mat[32] += mat[26] * d; + mat[33] += mat[27] * d; + mat[35] += mat[29] * d; + di = mat[35]; + s *= di; + mat[35] = d = 1.0f / di; + mat[30] *= d; + mat[31] *= d; + mat[32] *= d; + mat[33] *= d; + mat[34] *= d; + d = -d; + mat[5] *= d; + mat[11] *= d; + mat[17] *= d; + mat[23] *= d; + mat[29] *= d; + d = mat[5] * di; + mat[0] += mat[30] * d; + mat[1] += mat[31] * d; + mat[2] += mat[32] * d; + mat[3] += mat[33] * d; + mat[4] += mat[34] * d; + d = mat[11] * di; + mat[6] += mat[30] * d; + mat[7] += mat[31] * d; + mat[8] += mat[32] * d; + mat[9] += mat[33] * d; + mat[10] += mat[34] * d; + d = mat[17] * di; + mat[12] += mat[30] * d; + mat[13] += mat[31] * d; + mat[14] += mat[32] * d; + mat[15] += mat[33] * d; + mat[16] += mat[34] * d; + d = mat[23] * di; + mat[18] += mat[30] * d; + mat[19] += mat[31] * d; + mat[20] += mat[32] * d; + mat[21] += mat[33] * d; + mat[22] += mat[34] * d; + d = mat[29] * di; + mat[24] += mat[30] * d; + mat[25] += mat[31] * d; + mat[26] += mat[32] * d; + mat[27] += mat[33] * d; + mat[28] += mat[34] * d; + + return ( s != 0.0f && !FLOAT_IS_NAN( s ) ); +#else + // 6*27+2*30 = 222 multiplications + // 2*1 = 2 divisions + idMat3 r0, r1, r2, r3; + float c0, c1, c2, det, invDet; + float *mat = reinterpret_cast(this); + + // r0 = m0.Inverse(); + c0 = mat[1*6+1] * mat[2*6+2] - mat[1*6+2] * mat[2*6+1]; + c1 = mat[1*6+2] * mat[2*6+0] - mat[1*6+0] * mat[2*6+2]; + c2 = mat[1*6+0] * mat[2*6+1] - mat[1*6+1] * mat[2*6+0]; + + det = mat[0*6+0] * c0 + mat[0*6+1] * c1 + mat[0*6+2] * c2; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r0[0][0] = c0 * invDet; + r0[0][1] = ( mat[0*6+2] * mat[2*6+1] - mat[0*6+1] * mat[2*6+2] ) * invDet; + r0[0][2] = ( mat[0*6+1] * mat[1*6+2] - mat[0*6+2] * mat[1*6+1] ) * invDet; + r0[1][0] = c1 * invDet; + r0[1][1] = ( mat[0*6+0] * mat[2*6+2] - mat[0*6+2] * mat[2*6+0] ) * invDet; + r0[1][2] = ( mat[0*6+2] * mat[1*6+0] - mat[0*6+0] * mat[1*6+2] ) * invDet; + r0[2][0] = c2 * invDet; + r0[2][1] = ( mat[0*6+1] * mat[2*6+0] - mat[0*6+0] * mat[2*6+1] ) * invDet; + r0[2][2] = ( mat[0*6+0] * mat[1*6+1] - mat[0*6+1] * mat[1*6+0] ) * invDet; + + // r1 = r0 * m1; + r1[0][0] = r0[0][0] * mat[0*6+3] + r0[0][1] * mat[1*6+3] + r0[0][2] * mat[2*6+3]; + r1[0][1] = r0[0][0] * mat[0*6+4] + r0[0][1] * mat[1*6+4] + r0[0][2] * mat[2*6+4]; + r1[0][2] = r0[0][0] * mat[0*6+5] + r0[0][1] * mat[1*6+5] + r0[0][2] * mat[2*6+5]; + r1[1][0] = r0[1][0] * mat[0*6+3] + r0[1][1] * mat[1*6+3] + r0[1][2] * mat[2*6+3]; + r1[1][1] = r0[1][0] * mat[0*6+4] + r0[1][1] * mat[1*6+4] + r0[1][2] * mat[2*6+4]; + r1[1][2] = r0[1][0] * mat[0*6+5] + r0[1][1] * mat[1*6+5] + r0[1][2] * mat[2*6+5]; + r1[2][0] = r0[2][0] * mat[0*6+3] + r0[2][1] * mat[1*6+3] + r0[2][2] * mat[2*6+3]; + r1[2][1] = r0[2][0] * mat[0*6+4] + r0[2][1] * mat[1*6+4] + r0[2][2] * mat[2*6+4]; + r1[2][2] = r0[2][0] * mat[0*6+5] + r0[2][1] * mat[1*6+5] + r0[2][2] * mat[2*6+5]; + + // r2 = m2 * r1; + r2[0][0] = mat[3*6+0] * r1[0][0] + mat[3*6+1] * r1[1][0] + mat[3*6+2] * r1[2][0]; + r2[0][1] = mat[3*6+0] * r1[0][1] + mat[3*6+1] * r1[1][1] + mat[3*6+2] * r1[2][1]; + r2[0][2] = mat[3*6+0] * r1[0][2] + mat[3*6+1] * r1[1][2] + mat[3*6+2] * r1[2][2]; + r2[1][0] = mat[4*6+0] * r1[0][0] + mat[4*6+1] * r1[1][0] + mat[4*6+2] * r1[2][0]; + r2[1][1] = mat[4*6+0] * r1[0][1] + mat[4*6+1] * r1[1][1] + mat[4*6+2] * r1[2][1]; + r2[1][2] = mat[4*6+0] * r1[0][2] + mat[4*6+1] * r1[1][2] + mat[4*6+2] * r1[2][2]; + r2[2][0] = mat[5*6+0] * r1[0][0] + mat[5*6+1] * r1[1][0] + mat[5*6+2] * r1[2][0]; + r2[2][1] = mat[5*6+0] * r1[0][1] + mat[5*6+1] * r1[1][1] + mat[5*6+2] * r1[2][1]; + r2[2][2] = mat[5*6+0] * r1[0][2] + mat[5*6+1] * r1[1][2] + mat[5*6+2] * r1[2][2]; + + // r3 = r2 - m3; + r3[0][0] = r2[0][0] - mat[3*6+3]; + r3[0][1] = r2[0][1] - mat[3*6+4]; + r3[0][2] = r2[0][2] - mat[3*6+5]; + r3[1][0] = r2[1][0] - mat[4*6+3]; + r3[1][1] = r2[1][1] - mat[4*6+4]; + r3[1][2] = r2[1][2] - mat[4*6+5]; + r3[2][0] = r2[2][0] - mat[5*6+3]; + r3[2][1] = r2[2][1] - mat[5*6+4]; + r3[2][2] = r2[2][2] - mat[5*6+5]; + + // r3.InverseSelf(); + r2[0][0] = r3[1][1] * r3[2][2] - r3[1][2] * r3[2][1]; + r2[1][0] = r3[1][2] * r3[2][0] - r3[1][0] * r3[2][2]; + r2[2][0] = r3[1][0] * r3[2][1] - r3[1][1] * r3[2][0]; + + det = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0] + r3[0][2] * r2[2][0]; + + if ( idMath::Fabs( det ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + + invDet = 1.0f / det; + + r2[0][1] = r3[0][2] * r3[2][1] - r3[0][1] * r3[2][2]; + r2[0][2] = r3[0][1] * r3[1][2] - r3[0][2] * r3[1][1]; + r2[1][1] = r3[0][0] * r3[2][2] - r3[0][2] * r3[2][0]; + r2[1][2] = r3[0][2] * r3[1][0] - r3[0][0] * r3[1][2]; + r2[2][1] = r3[0][1] * r3[2][0] - r3[0][0] * r3[2][1]; + r2[2][2] = r3[0][0] * r3[1][1] - r3[0][1] * r3[1][0]; + + r3[0][0] = r2[0][0] * invDet; + r3[0][1] = r2[0][1] * invDet; + r3[0][2] = r2[0][2] * invDet; + r3[1][0] = r2[1][0] * invDet; + r3[1][1] = r2[1][1] * invDet; + r3[1][2] = r2[1][2] * invDet; + r3[2][0] = r2[2][0] * invDet; + r3[2][1] = r2[2][1] * invDet; + r3[2][2] = r2[2][2] * invDet; + + // r2 = m2 * r0; + r2[0][0] = mat[3*6+0] * r0[0][0] + mat[3*6+1] * r0[1][0] + mat[3*6+2] * r0[2][0]; + r2[0][1] = mat[3*6+0] * r0[0][1] + mat[3*6+1] * r0[1][1] + mat[3*6+2] * r0[2][1]; + r2[0][2] = mat[3*6+0] * r0[0][2] + mat[3*6+1] * r0[1][2] + mat[3*6+2] * r0[2][2]; + r2[1][0] = mat[4*6+0] * r0[0][0] + mat[4*6+1] * r0[1][0] + mat[4*6+2] * r0[2][0]; + r2[1][1] = mat[4*6+0] * r0[0][1] + mat[4*6+1] * r0[1][1] + mat[4*6+2] * r0[2][1]; + r2[1][2] = mat[4*6+0] * r0[0][2] + mat[4*6+1] * r0[1][2] + mat[4*6+2] * r0[2][2]; + r2[2][0] = mat[5*6+0] * r0[0][0] + mat[5*6+1] * r0[1][0] + mat[5*6+2] * r0[2][0]; + r2[2][1] = mat[5*6+0] * r0[0][1] + mat[5*6+1] * r0[1][1] + mat[5*6+2] * r0[2][1]; + r2[2][2] = mat[5*6+0] * r0[0][2] + mat[5*6+1] * r0[1][2] + mat[5*6+2] * r0[2][2]; + + // m2 = r3 * r2; + mat[3*6+0] = r3[0][0] * r2[0][0] + r3[0][1] * r2[1][0] + r3[0][2] * r2[2][0]; + mat[3*6+1] = r3[0][0] * r2[0][1] + r3[0][1] * r2[1][1] + r3[0][2] * r2[2][1]; + mat[3*6+2] = r3[0][0] * r2[0][2] + r3[0][1] * r2[1][2] + r3[0][2] * r2[2][2]; + mat[4*6+0] = r3[1][0] * r2[0][0] + r3[1][1] * r2[1][0] + r3[1][2] * r2[2][0]; + mat[4*6+1] = r3[1][0] * r2[0][1] + r3[1][1] * r2[1][1] + r3[1][2] * r2[2][1]; + mat[4*6+2] = r3[1][0] * r2[0][2] + r3[1][1] * r2[1][2] + r3[1][2] * r2[2][2]; + mat[5*6+0] = r3[2][0] * r2[0][0] + r3[2][1] * r2[1][0] + r3[2][2] * r2[2][0]; + mat[5*6+1] = r3[2][0] * r2[0][1] + r3[2][1] * r2[1][1] + r3[2][2] * r2[2][1]; + mat[5*6+2] = r3[2][0] * r2[0][2] + r3[2][1] * r2[1][2] + r3[2][2] * r2[2][2]; + + // m0 = r0 - r1 * m2; + mat[0*6+0] = r0[0][0] - r1[0][0] * mat[3*6+0] - r1[0][1] * mat[4*6+0] - r1[0][2] * mat[5*6+0]; + mat[0*6+1] = r0[0][1] - r1[0][0] * mat[3*6+1] - r1[0][1] * mat[4*6+1] - r1[0][2] * mat[5*6+1]; + mat[0*6+2] = r0[0][2] - r1[0][0] * mat[3*6+2] - r1[0][1] * mat[4*6+2] - r1[0][2] * mat[5*6+2]; + mat[1*6+0] = r0[1][0] - r1[1][0] * mat[3*6+0] - r1[1][1] * mat[4*6+0] - r1[1][2] * mat[5*6+0]; + mat[1*6+1] = r0[1][1] - r1[1][0] * mat[3*6+1] - r1[1][1] * mat[4*6+1] - r1[1][2] * mat[5*6+1]; + mat[1*6+2] = r0[1][2] - r1[1][0] * mat[3*6+2] - r1[1][1] * mat[4*6+2] - r1[1][2] * mat[5*6+2]; + mat[2*6+0] = r0[2][0] - r1[2][0] * mat[3*6+0] - r1[2][1] * mat[4*6+0] - r1[2][2] * mat[5*6+0]; + mat[2*6+1] = r0[2][1] - r1[2][0] * mat[3*6+1] - r1[2][1] * mat[4*6+1] - r1[2][2] * mat[5*6+1]; + mat[2*6+2] = r0[2][2] - r1[2][0] * mat[3*6+2] - r1[2][1] * mat[4*6+2] - r1[2][2] * mat[5*6+2]; + + // m1 = r1 * r3; + mat[0*6+3] = r1[0][0] * r3[0][0] + r1[0][1] * r3[1][0] + r1[0][2] * r3[2][0]; + mat[0*6+4] = r1[0][0] * r3[0][1] + r1[0][1] * r3[1][1] + r1[0][2] * r3[2][1]; + mat[0*6+5] = r1[0][0] * r3[0][2] + r1[0][1] * r3[1][2] + r1[0][2] * r3[2][2]; + mat[1*6+3] = r1[1][0] * r3[0][0] + r1[1][1] * r3[1][0] + r1[1][2] * r3[2][0]; + mat[1*6+4] = r1[1][0] * r3[0][1] + r1[1][1] * r3[1][1] + r1[1][2] * r3[2][1]; + mat[1*6+5] = r1[1][0] * r3[0][2] + r1[1][1] * r3[1][2] + r1[1][2] * r3[2][2]; + mat[2*6+3] = r1[2][0] * r3[0][0] + r1[2][1] * r3[1][0] + r1[2][2] * r3[2][0]; + mat[2*6+4] = r1[2][0] * r3[0][1] + r1[2][1] * r3[1][1] + r1[2][2] * r3[2][1]; + mat[2*6+5] = r1[2][0] * r3[0][2] + r1[2][1] * r3[1][2] + r1[2][2] * r3[2][2]; + + // m3 = -r3; + mat[3*6+3] = -r3[0][0]; + mat[3*6+4] = -r3[0][1]; + mat[3*6+5] = -r3[0][2]; + mat[4*6+3] = -r3[1][0]; + mat[4*6+4] = -r3[1][1]; + mat[4*6+5] = -r3[1][2]; + mat[5*6+3] = -r3[2][0]; + mat[5*6+4] = -r3[2][1]; + mat[5*6+5] = -r3[2][2]; + + return true; +#endif +} + +/* +============= +idMat6::ToString +============= +*/ +const char *idMat6::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + + +//=============================================================== +// +// idMatX +// +//=============================================================== + +// RAVEN BEGIN +// jscott: avoid pointer hackery pokery and use the compiler +float idMatX::tempPtr[MATX_MAX_TEMP]; +//float * idMatX::tempPtr = (float *) ( ( (int) idMatX::temp + 15 ) & ~15 ); +// RAVEN END +int idMatX::tempIndex = 0; + + +/* +============ +idMatX::ChangeSize +============ +*/ +void idMatX::ChangeSize( int rows, int columns, bool makeZero ) { + int alloc = ( rows * columns + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + float *oldMat = mat; +// RAVEN BEGIN + mat = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +// RAVEN END + if ( makeZero ) { + memset( mat, 0, alloc * sizeof( float ) ); + } + alloced = alloc; + if ( oldMat ) { + int minRow = Min( numRows, rows ); + int minColumn = Min( numColumns, columns ); + for ( int i = 0; i < minRow; i++ ) { + for ( int j = 0; j < minColumn; j++ ) { + mat[ i * columns + j ] = oldMat[ i * numColumns + j ]; + } + } + Mem_Free16( oldMat ); + } + } else { + if ( columns < numColumns ) { + int minRow = Min( numRows, rows ); + for ( int i = 0; i < minRow; i++ ) { + for ( int j = 0; j < columns; j++ ) { + mat[ i * columns + j ] = mat[ i * numColumns + j ]; + } + } + } else if ( columns > numColumns ) { + for ( int i = Min( numRows, rows ) - 1; i >= 0; i-- ) { + if ( makeZero ) { + for ( int j = columns - 1; j >= numColumns; j-- ) { + mat[ i * columns + j ] = 0.0f; + } + } + for ( int j = numColumns - 1; j >= 0; j-- ) { + mat[ i * columns + j ] = mat[ i * numColumns + j ]; + } + } + } + if ( makeZero && rows > numRows ) { + memset( mat + numRows * columns, 0, ( rows - numRows ) * columns * sizeof( float ) ); + } + } + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +/* +============ +idMatX::RemoveRow +============ +*/ +idMatX &idMatX::RemoveRow( int r ) { + int i; + + assert( r < numRows ); + + numRows--; + + for ( i = r; i < numRows; i++ ) { + memcpy( &mat[i * numColumns], &mat[( i + 1 ) * numColumns], numColumns * sizeof( float ) ); + } + + return *this; +} + +/* +============ +idMatX::RemoveColumn +============ +*/ +idMatX &idMatX::RemoveColumn( int r ) { + int i; + + assert( r < numColumns ); + + numColumns--; + + for ( i = 0; i < numRows - 1; i++ ) { + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + + return *this; +} + +/* +============ +idMatX::RemoveRowColumn +============ +*/ +idMatX &idMatX::RemoveRowColumn( int r ) { + int i; + + assert( r < numRows && r < numColumns ); + + numRows--; + numColumns--; + + if ( r > 0 ) { + for ( i = 0; i < r - 1; i++ ) { + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memmove( &mat[i * numColumns + r], &mat[i * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + } + + memcpy( &mat[r * numColumns], &mat[( r + 1 ) * ( numColumns + 1 )], r * sizeof( float ) ); + + for ( i = r; i < numRows - 1; i++ ) { + memcpy( &mat[i * numColumns + r], &mat[( i + 1 ) * ( numColumns + 1 ) + r + 1], numColumns * sizeof( float ) ); + } + memcpy( &mat[i * numColumns + r], &mat[( i + 1 ) * ( numColumns + 1 ) + r + 1], ( numColumns - r ) * sizeof( float ) ); + + return *this; +} + +/* +============ +idMatX::IsOrthogonal + + returns true if (*this) * this->Transpose() == Identity +============ +*/ +bool idMatX::IsOrthogonal( const float epsilon ) const { + float *ptr1, *ptr2, sum; + + if ( !IsSquare() ) { + return false; + } + + ptr1 = mat; + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + ptr2 = mat + j; + sum = ptr1[0] * ptr2[0] - (float) ( i == j ); + for ( int n = 1; n < numColumns; n++ ) { + ptr2 += numColumns; + sum += ptr1[n] * ptr2[0]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + ptr1 += numColumns; + } + return true; +} + +/* +============ +idMatX::IsOrthonormal + + returns true if (*this) * this->Transpose() == Identity and the length of each column vector is 1 +============ +*/ +bool idMatX::IsOrthonormal( const float epsilon ) const { + float *ptr1, *ptr2, sum; + + if ( !IsSquare() ) { + return false; + } + + ptr1 = mat; + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + ptr2 = mat + j; + sum = ptr1[0] * ptr2[0] - (float) ( i == j ); + for ( int n = 1; n < numColumns; n++ ) { + ptr2 += numColumns; + sum += ptr1[n] * ptr2[0]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + ptr1 += numColumns; + + ptr2 = mat + i; + sum = ptr2[0] * ptr2[0] - 1.0f; + for ( i = 1; i < numRows; i++ ) { + ptr2 += numColumns; + sum += ptr2[i] * ptr2[i]; + } + if ( idMath::Fabs( sum ) > epsilon ) { + return false; + } + } + return true; +} + +/* +============ +idMatX::IsPMatrix + + returns true if the matrix is a P-matrix + A square matrix is a P-matrix if all its principal minors are positive. +============ +*/ +bool idMatX::IsPMatrix( const float epsilon ) const { + int i, j; + float d; + idMatX m; + + if ( !IsSquare() ) { + return false; + } + + if ( numRows <= 0 ) { + return true; + } + + if ( (*this)[0][0] <= epsilon ) { + return false; + } + + if ( numRows <= 1 ) { + return true; + } + + m.SetData( numRows - 1, numColumns - 1, MATX_ALLOCA( ( numRows - 1 ) * ( numColumns - 1 ) ) ); + + for ( i = 1; i < numRows; i++ ) { + for ( j = 1; j < numColumns; j++ ) { + m[i-1][j-1] = (*this)[i][j]; + } + } + + if ( !m.IsPMatrix( epsilon ) ) { + return false; + } + + for ( i = 1; i < numRows; i++ ) { + d = (*this)[i][0] / (*this)[0][0]; + for ( j = 1; j < numColumns; j++ ) { + m[i-1][j-1] = (*this)[i][j] - d * (*this)[0][j]; + } + } + + if ( !m.IsPMatrix( epsilon ) ) { + return false; + } + + return true; +} + +/* +============ +idMatX::IsZMatrix + + returns true if the matrix is a Z-matrix + A square matrix M is a Z-matrix if M[i][j] <= 0 for all i != j. +============ +*/ +bool idMatX::IsZMatrix( const float epsilon ) const { + int i, j; + + if ( !IsSquare() ) { + return false; + } + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + if ( (*this)[i][j] > epsilon && i != j ) { + return false; + } + } + } + return true; +} + +/* +============ +idMatX::IsPositiveDefinite + + returns true if the matrix is Positive Definite (PD) + A square matrix M of order n is said to be PD if y'My > 0 for all vectors y of dimension n, y != 0. +============ +*/ +bool idMatX::IsPositiveDefinite( const float epsilon ) const { + int i, j, k; + float d, s; + idMatX m; + + // the matrix must be square + if ( !IsSquare() ) { + return false; + } + + // copy matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // add transpose + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + m[i][j] += (*this)[j][i]; + } + } + + // test Positive Definiteness with Gaussian pivot steps + for ( i = 0; i < numRows; i++ ) { + + for ( j = i; j < numColumns; j++ ) { + if ( m[j][j] <= epsilon ) { + return false; + } + } + + d = 1.0f / m[i][i]; + for ( j = i + 1; j < numColumns; j++ ) { + s = d * m[j][i]; + m[j][i] = 0.0f; + for ( k = i + 1; k < numRows; k++ ) { + m[j][k] -= s * m[i][k]; + } + } + } + + return true; +} + +/* +============ +idMatX::IsSymmetricPositiveDefinite + + returns true if the matrix is Symmetric Positive Definite (PD) +============ +*/ +bool idMatX::IsSymmetricPositiveDefinite( const float epsilon ) const { + idMatX m; + + // the matrix must be symmetric + if ( !IsSymmetric( epsilon ) ) { + return false; + } + + // copy matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // being able to obtain Cholesky factors is both a necessary and sufficient condition for positive definiteness + return m.Cholesky_Factor(); +} + +/* +============ +idMatX::IsPositiveSemiDefinite + + returns true if the matrix is Positive Semi Definite (PSD) + A square matrix M of order n is said to be PSD if y'My >= 0 for all vectors y of dimension n, y != 0. +============ +*/ +bool idMatX::IsPositiveSemiDefinite( const float epsilon ) const { + int i, j, k; + float d, s; + idMatX m; + + // the matrix must be square + if ( !IsSquare() ) { + return false; + } + + // copy original matrix + m.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + m = *this; + + // add transpose + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + m[i][j] += (*this)[j][i]; + } + } + + // test Positive Semi Definiteness with Gaussian pivot steps + for ( i = 0; i < numRows; i++ ) { + + for ( j = i; j < numColumns; j++ ) { + if ( m[j][j] < -epsilon ) { + return false; + } + if ( m[j][j] > epsilon ) { + continue; + } + for ( k = 0; k < numRows; k++ ) { + if ( idMath::Fabs( m[k][j] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( m[j][k] ) > epsilon ) { + return false; + } + } + } + + if ( m[i][i] <= epsilon ) { + continue; + } + + d = 1.0f / m[i][i]; + for ( j = i + 1; j < numColumns; j++ ) { + s = d * m[j][i]; + m[j][i] = 0.0f; + for ( k = i + 1; k < numRows; k++ ) { + m[j][k] -= s * m[i][k]; + } + } + } + + return true; +} + +/* +============ +idMatX::IsSymmetricPositiveSemiDefinite + + returns true if the matrix is Symmetric Positive Semi Definite (PSD) +============ +*/ +bool idMatX::IsSymmetricPositiveSemiDefinite( const float epsilon ) const { + + // the matrix must be symmetric + if ( !IsSymmetric( epsilon ) ) { + return false; + } + + return IsPositiveSemiDefinite( epsilon ); +} + +/* +============ +idMatX::LowerTriangularInverse + + in-place inversion of the lower triangular matrix +============ +*/ +bool idMatX::LowerTriangularInverse( void ) { + int i, j, k; + double d, sum; + + for ( i = 0; i < numRows; i++ ) { + d = (*this)[i][i]; + if ( d == 0.0f ) { + return false; + } + (*this)[i][i] = d = 1.0f / d; + + for ( j = 0; j < i; j++ ) { + sum = 0.0f; + for ( k = j; k < i; k++ ) { + sum -= (*this)[i][k] * (*this)[k][j]; + } + (*this)[i][j] = sum * d; + } + } + return true; +} + +/* +============ +idMatX::UpperTriangularInverse + + in-place inversion of the upper triangular matrix +============ +*/ +bool idMatX::UpperTriangularInverse( void ) { + int i, j, k; + double d, sum; + + for ( i = numRows-1; i >= 0; i-- ) { + d = (*this)[i][i]; + if ( d == 0.0f ) { + return false; + } + (*this)[i][i] = d = 1.0f / d; + + for ( j = numRows-1; j > i; j-- ) { + sum = 0.0f; + for ( k = j; k > i; k-- ) { + sum -= (*this)[i][k] * (*this)[k][j]; + } + (*this)[i][j] = sum * d; + } + } + return true; +} + +/* +============= +idMatX::ToString +============= +*/ +const char *idMatX::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============ +idMatX::Update_RankOne + + Updates the matrix to obtain the matrix: A + alpha * v * w' +============ +*/ +void idMatX::Update_RankOne( const idVecX &v, const idVecX &w, float alpha ) { + int i, j; + float s; + + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + + for ( i = 0; i < numRows; i++ ) { + s = alpha * v[i]; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] += s * w[j]; + } + } +} + +/* +============ +idMatX::Update_RankOneSymmetric + + Updates the matrix to obtain the matrix: A + alpha * v * v' +============ +*/ +void idMatX::Update_RankOneSymmetric( const idVecX &v, float alpha ) { + int i, j; + float s; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + s = alpha * v[i]; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] += s * v[j]; + } + } +} + +/* +============ +idMatX::Update_RowColumn + + Updates the matrix to obtain the matrix: + + [ 0 a 0 ] + A + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +void idMatX::Update_RowColumn( const idVecX &v, const idVecX &w, int r ) { + int i; + + assert( w[r] == 0.0f ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + (*this)[i][r] += v[i]; + } + for ( i = 0; i < numColumns; i++ ) { + (*this)[r][i] += w[i]; + } +} + +/* +============ +idMatX::Update_RowColumnSymmetric + + Updates the matrix to obtain the matrix: + + [ 0 a 0 ] + A + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +void idMatX::Update_RowColumnSymmetric( const idVecX &v, int r ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + + for ( i = 0; i < r; i++ ) { + (*this)[i][r] += v[i]; + (*this)[r][i] += v[i]; + } + (*this)[r][r] += v[r]; + for ( i = r+1; i < numRows; i++ ) { + (*this)[i][r] += v[i]; + (*this)[r][i] += v[i]; + } +} + +/* +============ +idMatX::Update_Increment + + Updates the matrix to obtain the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1]], w[numColumns] = 0 +============ +*/ +void idMatX::Update_Increment( const idVecX &v, const idVecX &w ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + for ( i = 0; i < numRows; i++ ) { + (*this)[i][numColumns-1] = v[i]; + } + for ( i = 0; i < numColumns-1; i++ ) { + (*this)[numRows-1][i] = w[i]; + } +} + +/* +============ +idMatX::Update_IncrementSymmetric + + Updates the matrix to obtain the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +void idMatX::Update_IncrementSymmetric( const idVecX &v ) { + int i; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + for ( i = 0; i < numRows-1; i++ ) { + (*this)[i][numColumns-1] = v[i]; + } + for ( i = 0; i < numColumns; i++ ) { + (*this)[numRows-1][i] = v[i]; + } +} + +/* +============ +idMatX::Update_Decrement + + Updates the matrix to obtain a matrix with row r and column r removed. +============ +*/ +void idMatX::Update_Decrement( int r ) { + RemoveRowColumn( r ); +} + +/* +============ +idMatX::Inverse_GaussJordan + + in-place inversion using Gauss-Jordan elimination +============ +*/ +bool idMatX::Inverse_GaussJordan( void ) { + int i, j, k, r, c; + float d, max; + + assert( numRows == numColumns ); + + int *columnIndex = (int *) _alloca16( numRows * sizeof( int ) ); + int *rowIndex = (int *) _alloca16( numRows * sizeof( int ) ); + bool *pivot = (bool *) _alloca16( numRows * sizeof( bool ) ); + + memset( pivot, 0, numRows * sizeof( bool ) ); + + // elimination with full pivoting + for ( i = 0; i < numRows; i++ ) { + + // search the whole matrix except for pivoted rows for the maximum absolute value + max = 0.0f; + r = c = 0; + for ( j = 0; j < numRows; j++ ) { + if ( !pivot[j] ) { + for ( k = 0; k < numRows; k++ ) { + if ( !pivot[k] ) { + d = idMath::Fabs( (*this)[j][k] ); + if ( d > max ) { + max = d; + r = j; + c = k; + } + } + } + } + } + + if ( max == 0.0f ) { + // matrix is not invertible + return false; + } + + pivot[c] = true; + + // swap rows such that entry (c,c) has the pivot entry + if ( r != c ) { + SwapRows( r, c ); + } + + // keep track of the row permutation + rowIndex[i] = r; + columnIndex[i] = c; + + // scale the row to make the pivot entry equal to 1 + d = 1.0f / (*this)[c][c]; + (*this)[c][c] = 1.0f; + for ( k = 0; k < numRows; k++ ) { + (*this)[c][k] *= d; + } + + // zero out the pivot column entries in the other rows + for ( j = 0; j < numRows; j++ ) { + if ( j != c ) { + d = (*this)[j][c]; + (*this)[j][c] = 0.0f; + for ( k = 0; k < numRows; k++ ) { + (*this)[j][k] -= (*this)[c][k] * d; + } + } + } + } + + // reorder rows to store the inverse of the original matrix + for ( j = numRows - 1; j >= 0; j-- ) { + if ( rowIndex[j] != columnIndex[j] ) { + for ( k = 0; k < numRows; k++ ) { + d = (*this)[k][rowIndex[j]]; + (*this)[k][rowIndex[j]] = (*this)[k][columnIndex[j]]; + (*this)[k][columnIndex[j]] = d; + } + } + } + + return true; +} + +/* +============ +idMatX::Inverse_UpdateRankOne + + Updates the in-place inverse using the Sherman-Morrison formula to obtain the inverse for the matrix: A + alpha * v * w' +============ +*/ +bool idMatX::Inverse_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha ) { + int i, j; + float beta, s; + idVecX y, z; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + y.SetData( numRows, VECX_ALLOCA( numRows ) ); + z.SetData( numRows, VECX_ALLOCA( numRows ) ); + + Multiply( y, v ); + TransposeMultiply( z, w ); + beta = 1.0f + ( w * y ); + + if ( beta == 0.0f ) { + return false; + } + + alpha /= beta; + + for ( i = 0; i < numRows; i++ ) { + s = y[i] * alpha; + for ( j = 0; j < numColumns; j++ ) { + (*this)[i][j] -= s * z[j]; + } + } + return true; +} + +/* +============ +idMatX::Inverse_UpdateRowColumn + + Updates the in-place inverse to obtain the inverse for the matrix: + + [ 0 a 0 ] + A + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::Inverse_UpdateRowColumn( const idVecX &v, const idVecX &w, int r ) { + idVecX s; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !Inverse_UpdateRankOne( v, s, 1.0f ) ) { + return false; + } + if ( !Inverse_UpdateRankOne( s, w, 1.0f ) ) { + return false; + } + return true; +} + +/* +============ +idMatX::Inverse_UpdateIncrement + + Updates the in-place inverse to obtain the inverse for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::Inverse_UpdateIncrement( const idVecX &v, const idVecX &w ) { + idVecX v2; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + (*this)[numRows-1][numRows-1] = 1.0f; + + v2.SetData( numRows, VECX_ALLOCA( numRows ) ); + v2 = v; + v2[numRows-1] -= 1.0f; + + return Inverse_UpdateRowColumn( v2, w, numRows-1 ); +} + +/* +============ +idMatX::Inverse_UpdateDecrement + + Updates the in-place inverse to obtain the inverse of the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. +============ +*/ +bool idMatX::Inverse_UpdateDecrement( const idVecX &v, const idVecX &w, int r ) { + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !Inverse_UpdateRowColumn( v1, w1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::Inverse_Solve + + Solve Ax = b with A inverted +============ +*/ +void idMatX::Inverse_Solve( idVecX &x, const idVecX &b ) const { + Multiply( x, b ); +} + +/* +============ +idMatX::LU_Factor + + in-place factorization: LU + L is a triangular matrix stored in the lower triangle. + L has ones on the diagonal that are not stored. + U is a triangular matrix stored in the upper triangle. + If index != NULL partial pivoting is used for numerical stability. + If index != NULL it must point to an array of numRow integers and is used to keep track of the row permutation. + If det != NULL the determinant of the matrix is calculated and stored. +============ +*/ +bool idMatX::LU_Factor( int *index, float *det ) { + int i, j, k, newi, min; + double s, t, d, w; + + // if partial pivoting should be used + if ( index ) { + for ( i = 0; i < numRows; i++ ) { + index[i] = i; + } + } + + w = 1.0f; + min = Min( numRows, numColumns ); + for ( i = 0; i < min; i++ ) { + + newi = i; + s = idMath::Fabs( (*this)[i][i] ); + + if ( index ) { + // find the largest absolute pivot + for ( j = i + 1; j < numRows; j++ ) { + t = idMath::Fabs( (*this)[j][i] ); + if ( t > s ) { + newi = j; + s = t; + } + } + } + + if ( s == 0.0f ) { + return false; + } + + if ( newi != i ) { + + w = -w; + + // swap index elements + k = index[i]; + index[i] = index[newi]; + index[newi] = k; + + // swap rows + for ( j = 0; j < numColumns; j++ ) { + t = (*this)[newi][j]; + (*this)[newi][j] = (*this)[i][j]; + (*this)[i][j] = t; + } + } + + if ( i < numRows ) { + d = 1.0f / (*this)[i][i]; + for ( j = i + 1; j < numRows; j++ ) { + (*this)[j][i] *= d; + } + } + + if ( i < min-1 ) { + for ( j = i + 1; j < numRows; j++ ) { + d = (*this)[j][i]; + for ( k = i + 1; k < numColumns; k++ ) { + (*this)[j][k] -= d * (*this)[i][k]; + } + } + } + } + + if ( det ) { + for ( i = 0; i < numRows; i++ ) { + w *= (*this)[i][i]; + } + *det = w; + } + + return true; +} + +/* +============ +idMatX::LU_UpdateRankOne + + Updates the in-place LU factorization to obtain the factors for the matrix: LU + alpha * v * w' +============ +*/ +bool idMatX::LU_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha, int *index ) { + int i, j, max; + float *y, *z; + double diag, beta, p0, p1, d; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + + if ( index != NULL ) { + for ( i = 0; i < numRows; i++ ) { + y[i] = alpha * v[index[i]]; + } + } else { + for ( i = 0; i < numRows; i++ ) { + y[i] = alpha * v[i]; + } + } + + memcpy( z, w.ToFloatPtr(), w.GetSize() * sizeof( float ) ); + + max = Min( numRows, numColumns ); + for ( i = 0; i < max; i++ ) { + diag = (*this)[i][i]; + + p0 = y[i]; + p1 = z[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + return false; + } + + beta = p1 / diag; + + (*this)[i][i] = diag; + + for ( j = i+1; j < numColumns; j++ ) { + + d = (*this)[i][j]; + + d += p0 * z[j]; + z[j] -= beta * d; + + (*this)[i][j] = d; + } + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y[j] -= p0 * d; + d += beta * y[j]; + + (*this)[j][i] = d; + } + } + return true; +} + +/* +============ +idMatX::LU_UpdateRowColumn + + Updates the in-place LU factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LU + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::LU_UpdateRowColumn( const idVecX &v, const idVecX &w, int r, int *index ) { +#if 0 + + idVecX s; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !LU_UpdateRankOne( v, s, 1.0f, index ) ) { + return false; + } + if ( !LU_UpdateRankOne( s, w, 1.0f, index ) ) { + return false; + } + return true; + +#else + + int i, j, min, max, rp; + float *y0, *y1, *z0, *z1; + double diag, beta0, beta1, p0, p1, q0, q1, d; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numColumns && r < numRows ); + assert( w[r] == 0.0f ); + + y0 = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z0 = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + y1 = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + z1 = (float *) _alloca16( w.GetSize() * sizeof( float ) ); + + if ( index != NULL ) { + for ( i = 0; i < numRows; i++ ) { + y0[i] = v[index[i]]; + } + rp = r; + for ( i = 0; i < numRows; i++ ) { + if ( index[i] == r ) { + rp = i; + break; + } + } + } else { + memcpy( y0, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + rp = r; + } + + memset( y1, 0, v.GetSize() * sizeof( float ) ); + y1[rp] = 1.0f; + + memset( z0, 0, w.GetSize() * sizeof( float ) ); + z0[r] = 1.0f; + + memcpy( z1, w.ToFloatPtr(), w.GetSize() * sizeof( float ) ); + + // update the beginning of the to be updated row and column + min = Min( r, rp ); + for ( i = 0; i < min; i++ ) { + p0 = y0[i]; + beta1 = z1[i] / (*this)[i][i]; + + (*this)[i][r] += p0; + for ( j = i+1; j < numColumns; j++ ) { + z1[j] -= beta1 * (*this)[i][j]; + } + for ( j = i+1; j < numRows; j++ ) { + y0[j] -= p0 * (*this)[j][i]; + } + (*this)[rp][i] += beta1; + } + + // update the lower right corner starting at r,r + max = Min( numRows, numColumns ); + for ( i = min; i < max; i++ ) { + diag = (*this)[i][i]; + + p0 = y0[i]; + p1 = z0[i]; + diag += p0 * p1; + + if ( diag == 0.0f ) { + return false; + } + + beta0 = p1 / diag; + + q0 = y1[i]; + q1 = z1[i]; + diag += q0 * q1; + + if ( diag == 0.0f ) { + return false; + } + + beta1 = q1 / diag; + + (*this)[i][i] = diag; + + for ( j = i+1; j < numColumns; j++ ) { + + d = (*this)[i][j]; + + d += p0 * z0[j]; + z0[j] -= beta0 * d; + + d += q0 * z1[j]; + z1[j] -= beta1 * d; + + (*this)[i][j] = d; + } + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y0[j] -= p0 * d; + d += beta0 * y0[j]; + + y1[j] -= q0 * d; + d += beta1 * y1[j]; + + (*this)[j][i] = d; + } + } + return true; + +#endif +} + +/* +============ +idMatX::LU_UpdateIncrement + + Updates the in-place LU factorization to obtain the factors for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::LU_UpdateIncrement( const idVecX &v, const idVecX &w, int *index ) { + int i, j; + float sum; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + + // add row to L + for ( i = 0; i < numRows - 1; i++ ) { + sum = w[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[numRows - 1][j] * (*this)[j][i]; + } + (*this)[numRows - 1 ][i] = sum / (*this)[i][i]; + } + + // add row to the permutation index + if ( index != NULL ) { + index[numRows - 1] = numRows - 1; + } + + // add column to U + for ( i = 0; i < numRows; i++ ) { + if ( index != NULL ) { + sum = v[index[i]]; + } else { + sum = v[i]; + } + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * (*this)[j][numRows - 1]; + } + (*this)[i][numRows - 1] = sum; + } + + return true; +} + +/* +============ +idMatX::LU_UpdateDecrement + + Updates the in-place LU factorization to obtain the factors for the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. + If index != NULL then u should store row index[r] of the original matrix. If index == NULL then u = w. +============ +*/ +bool idMatX::LU_UpdateDecrement( const idVecX &v, const idVecX &w, const idVecX &u, int r, int *index ) { + int i, p; + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + if ( index != NULL ) { + + // find the pivot row + for ( p = i = 0; i < numRows; i++ ) { + if ( index[i] == r ) { + p = i; + break; + } + } + + // update the row and column to identity + v1 = -v; + w1 = -u; + + if ( p != r ) { + idSwap( v1[index[r]], v1[index[p]] ); + idSwap( index[r], index[p] ); + } + + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !LU_UpdateRowColumn( v1, w1, r, index ) ) { + return false; + } + + if ( p != r ) { + + if ( idMath::Fabs( u[p] ) < 1e-4f ) { + // NOTE: an additional row interchange is required for numerical stability + } + + // move row index[r] of the original matrix to row index[p] of the original matrix + v1.Zero(); + v1[index[p]] = 1.0f; + w1 = u - w; + + if ( !LU_UpdateRankOne( v1, w1, 1.0f, index ) ) { + return false; + } + } + + // remove the row from the permutation index + for ( i = r; i < numRows - 1; i++ ) { + index[i] = index[i+1]; + } + for ( i = 0; i < numRows - 1; i++ ) { + if ( index[i] > r ) { + index[i]--; + } + } + + } else { + + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !LU_UpdateRowColumn( v1, w1, r, index ) ) { + return false; + } + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::LU_Solve + + Solve Ax = b with A factored in-place as: LU +============ +*/ +void idMatX::LU_Solve( idVecX &x, const idVecX &b, const int *index ) const { + int i, j; + double sum; + + assert( x.GetSize() == numColumns && b.GetSize() == numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + if ( index != NULL ) { + sum = b[index[i]]; + } else { + sum = b[i]; + } + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // solve U + for ( i = numRows - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } +} + +/* +============ +idMatX::LU_Inverse + + Calculates the inverse of the matrix which is factored in-place as LU +============ +*/ +void idMatX::LU_Inverse( idMatX &inv, const int *index ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + LU_Solve( x, b, index ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::LU_UnpackFactors + + Unpacks the in-place LU factorization. +============ +*/ +void idMatX::LU_UnpackFactors( idMatX &L, idMatX &U ) const { + int i, j; + + L.Zero( numRows, numColumns ); + U.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < i; j++ ) { + L[i][j] = (*this)[i][j]; + } + L[i][i] = 1.0f; + for ( j = i; j < numColumns; j++ ) { + U[i][j] = (*this)[i][j]; + } + } +} + +/* +============ +idMatX::LU_MultiplyFactors + + Multiplies the factors of the in-place LU factorization to form the original matrix. +============ +*/ +void idMatX::LU_MultiplyFactors( idMatX &m, const int *index ) const { + int r, rp, i, j; + double sum; + + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + if ( index != NULL ) { + rp = index[r]; + } else { + rp = r; + } + + // calculate row of matrix + for ( i = 0; i < numColumns; i++ ) { + if ( i >= r ) { + sum = (*this)[r][i]; + } else { + sum = 0.0f; + } + for ( j = 0; j <= i && j < r; j++ ) { + sum += (*this)[r][j] * (*this)[j][i]; + } + m[rp][i] = sum; + } + } +} + +/* +============ +idMatX::QR_Factor + + in-place factorization: QR + Q is an orthogonal matrix represented as a product of Householder matrices stored in the lower triangle and c. + R is a triangular matrix stored in the upper triangle except for the diagonal elements which are stored in d. + The initial matrix has to be square. +============ +*/ +bool idMatX::QR_Factor( idVecX &c, idVecX &d ) { + int i, j, k; + double scale, s, t, sum; + bool singular = false; + + assert( numRows == numColumns ); + assert( c.GetSize() >= numRows && d.GetSize() >= numRows ); + + for ( k = 0; k < numRows-1; k++ ) { + + scale = 0.0f; + for ( i = k; i < numRows; i++ ) { + s = idMath::Fabs( (*this)[i][k] ); + if ( s > scale ) { + scale = s; + } + } + if ( scale == 0.0f ) { + singular = true; + c[k] = d[k] = 0.0f; + } else { + + s = 1.0f / scale; + for ( i = k; i < numRows; i++ ) { + (*this)[i][k] *= s; + } + + sum = 0.0f; + for ( i = k; i < numRows; i++ ) { + s = (*this)[i][k]; + sum += s * s; + } + + s = idMath::Sqrt( sum ); + if ( (*this)[k][k] < 0.0f ) { + s = -s; + } + (*this)[k][k] += s; + c[k] = s * (*this)[k][k]; + d[k] = -scale * s; + + for ( j = k+1; j < numRows; j++ ) { + + sum = 0.0f; + for ( i = k; i < numRows; i++ ) { + sum += (*this)[i][k] * (*this)[i][j]; + } + t = sum / c[k]; + for ( i = k; i < numRows; i++ ) { + (*this)[i][j] -= t * (*this)[i][k]; + } + } + } + } + d[numRows-1] = (*this)[ (numRows-1) ][ (numRows-1) ]; + if ( d[numRows-1] == 0.0f ) { + singular = true; + } + + return !singular; +} + +/* +============ +idMatX::QR_Rotate + + Performs a Jacobi rotation on the rows i and i+1 of the unpacked QR factors. +============ +*/ +void idMatX::QR_Rotate( idMatX &R, int i, float a, float b ) { + int j; + float f, c, s, w, y; + + if ( a == 0.0f ) { + c = 0.0f; + s = ( b >= 0.0f ) ? 1.0f : -1.0f; + } else if ( idMath::Fabs( a ) > idMath::Fabs( b ) ) { + f = b / a; + c = idMath::Fabs( 1.0f / idMath::Sqrt( 1.0f + f * f ) ); + if ( a < 0.0f ) { + c = -c; + } + s = f * c; + } else { + f = a / b; + s = idMath::Fabs( 1.0f / idMath::Sqrt( 1.0f + f * f ) ); + if ( b < 0.0f ) { + s = -s; + } + c = f * s; + } + for ( j = i; j < numRows; j++ ) { + y = R[i][j]; + w = R[i+1][j]; + R[i][j] = c * y - s * w; + R[i+1][j] = s * y + c * w; + } + for ( j = 0; j < numRows; j++ ) { + y = (*this)[j][i]; + w = (*this)[j][i+1]; + (*this)[j][i] = c * y - s * w; + (*this)[j][i+1] = s * y + c * w; + } +} + +/* +============ +idMatX::QR_UpdateRankOne + + Updates the unpacked QR factorization to obtain the factors for the matrix: QR + alpha * v * w' +============ +*/ +bool idMatX::QR_UpdateRankOne( idMatX &R, const idVecX &v, const idVecX &w, float alpha ) { + int i, k; + float f; + idVecX u; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + + u.SetData( v.GetSize(), VECX_ALLOCA( v.GetSize() ) ); + TransposeMultiply( u, v ); + u *= alpha; + + for ( k = v.GetSize()-1; k > 0; k-- ) { + if ( u[k] != 0.0f ) { + break; + } + } + for ( i = k-1; i >= 0; i-- ) { + QR_Rotate( R, i, u[i], -u[i+1] ); + if ( u[i] == 0.0f ) { + u[i] = idMath::Fabs( u[i+1] ); + } else if ( idMath::Fabs( u[i] ) > idMath::Fabs( u[i+1] ) ) { + f = u[i+1] / u[i]; + u[i] = idMath::Fabs( u[i] ) * idMath::Sqrt( 1.0f + f * f ); + } else { + f = u[i] / u[i+1]; + u[i] = idMath::Fabs( u[i+1] ) * idMath::Sqrt( 1.0f + f * f ); + } + } + for ( i = 0; i < v.GetSize(); i++ ) { + R[0][i] += u[0] * w[i]; + } + for ( i = 0; i < k; i++ ) { + QR_Rotate( R, i, -R[i][i], R[i+1][i] ); + } + return true; +} + +/* +============ +idMatX::QR_UpdateRowColumn + + Updates the unpacked QR factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + QR + [ d b e ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1], d = w[0,r-1], w[r] = 0.0f, e = w[r+1,numColumns-1] +============ +*/ +bool idMatX::QR_UpdateRowColumn( idMatX &R, const idVecX &v, const idVecX &w, int r ) { + idVecX s; + + assert( v.GetSize() >= numColumns ); + assert( w.GetSize() >= numRows ); + assert( r >= 0 && r < numRows && r < numColumns ); + assert( w[r] == 0.0f ); + + s.SetData( Max( numRows, numColumns ), VECX_ALLOCA( Max( numRows, numColumns ) ) ); + s.Zero(); + s[r] = 1.0f; + + if ( !QR_UpdateRankOne( R, v, s, 1.0f ) ) { + return false; + } + if ( !QR_UpdateRankOne( R, s, w, 1.0f ) ) { + return false; + } + return true; +} + +/* +============ +idMatX::QR_UpdateIncrement + + Updates the unpacked QR factorization to obtain the factors for the matrix: + + [ A a ] + [ c b ] + + where: a = v[0,numRows-1], b = v[numRows], c = w[0,numColumns-1], w[numColumns] = 0 +============ +*/ +bool idMatX::QR_UpdateIncrement( idMatX &R, const idVecX &v, const idVecX &w ) { + idVecX v2; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + assert( w.GetSize() >= numColumns+1 ); + + ChangeSize( numRows+1, numColumns+1, true ); + (*this)[numRows-1][numRows-1] = 1.0f; + + R.ChangeSize( R.numRows+1, R.numColumns+1, true ); + R[R.numRows-1][R.numRows-1] = 1.0f; + + v2.SetData( numRows, VECX_ALLOCA( numRows ) ); + v2 = v; + v2[numRows-1] -= 1.0f; + + return QR_UpdateRowColumn( R, v2, w, numRows-1 ); +} + +/* +============ +idMatX::QR_UpdateDecrement + + Updates the unpacked QR factorization to obtain the factors for the matrix with row r and column r removed. + v and w should store the column and row of the original matrix respectively. +============ +*/ +bool idMatX::QR_UpdateDecrement( idMatX &R, const idVecX &v, const idVecX &w, int r ) { + idVecX v1, w1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( w.GetSize() >= numColumns ); + assert( r >= 0 && r < numRows && r < numColumns ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + w1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + w1 = -w; + v1[r] += 1.0f; + w1[r] = 0.0f; + + if ( !QR_UpdateRowColumn( R, v1, w1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + R.Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::QR_Solve + + Solve Ax = b with A factored in-place as: QR +============ +*/ +void idMatX::QR_Solve( idVecX &x, const idVecX &b, const idVecX &c, const idVecX &d ) const { + int i, j; + double sum, t; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + assert( c.GetSize() >= numRows && d.GetSize() >= numRows ); + + for ( i = 0; i < numRows; i++ ) { + x[i] = b[i]; + } + + // multiply b with transpose of Q + for ( i = 0; i < numRows-1; i++ ) { + + sum = 0.0f; + for ( j = i; j < numRows; j++ ) { + sum += (*this)[j][i] * x[j]; + } + t = sum / c[i]; + for ( j = i; j < numRows; j++ ) { + x[j] -= t * (*this)[j][i]; + } + } + + // backsubstitution with R + for ( i = numRows-1; i >= 0; i-- ) { + + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / d[i]; + } +} + +/* +============ +idMatX::QR_Solve + + Solve Ax = b with A factored as: QR +============ +*/ +void idMatX::QR_Solve( idVecX &x, const idVecX &b, const idMatX &R ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + + // multiply b with transpose of Q + TransposeMultiply( x, b ); + + // backsubstitution with R + for ( i = numRows-1; i >= 0; i-- ) { + + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= R[i][j] * x[j]; + } + x[i] = sum / R[i][i]; + } +} + +/* +============ +idMatX::QR_Inverse + + Calculates the inverse of the matrix which is factored in-place as: QR +============ +*/ +void idMatX::QR_Inverse( idMatX &inv, const idVecX &c, const idVecX &d ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + QR_Solve( x, b, c, d ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::QR_UnpackFactors + + Unpacks the in-place QR factorization. +============ +*/ +void idMatX::QR_UnpackFactors( idMatX &Q, idMatX &R, const idVecX &c, const idVecX &d ) const { + int i, j, k; + double sum; + + Q.Identity( numRows, numColumns ); + for ( i = 0; i < numColumns-1; i++ ) { + if ( c[i] == 0.0f ) { + continue; + } + for ( j = 0; j < numRows; j++ ) { + sum = 0.0f; + for ( k = i; k < numColumns; k++ ) { + sum += (*this)[k][i] * Q[j][k]; + } + sum /= c[i]; + for ( k = i; k < numColumns; k++ ) { + Q[j][k] -= sum * (*this)[k][i]; + } + } + } + + R.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + R[i][i] = d[i]; + for ( j = i+1; j < numColumns; j++ ) { + R[i][j] = (*this)[i][j]; + } + } +} + +/* +============ +idMatX::QR_MultiplyFactors + + Multiplies the factors of the in-place QR factorization to form the original matrix. +============ +*/ +void idMatX::QR_MultiplyFactors( idMatX &m, const idVecX &c, const idVecX &d ) const { + int i, j, k; + double sum; + idMatX Q; + + Q.Identity( numRows, numColumns ); + for ( i = 0; i < numColumns-1; i++ ) { + if ( c[i] == 0.0f ) { + continue; + } + for ( j = 0; j < numRows; j++ ) { + sum = 0.0f; + for ( k = i; k < numColumns; k++ ) { + sum += (*this)[k][i] * Q[j][k]; + } + sum /= c[i]; + for ( k = i; k < numColumns; k++ ) { + Q[j][k] -= sum * (*this)[k][i]; + } + } + } + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + sum = Q[i][j] * d[i]; + for ( k = 0; k < i; k++ ) { + sum += Q[i][k] * (*this)[j][k]; + } + m[i][j] = sum; + } + } +} + +/* +============ +idMatX::Pythag + + Computes (a^2 + b^2)^1/2 without underflow or overflow. +============ +*/ +float idMatX::Pythag( float a, float b ) const { + double at, bt, ct; + + at = idMath::Fabs( a ); + bt = idMath::Fabs( b ); + if ( at > bt ) { + ct = bt / at; + return at * idMath::Sqrt( 1.0f + ct * ct ); + } else { + if ( bt ) { + ct = at / bt; + return bt * idMath::Sqrt( 1.0f + ct * ct ); + } else { + return 0.0f; + } + } +} + +/* +============ +idMatX::SVD_BiDiag +============ +*/ +void idMatX::SVD_BiDiag( idVecX &w, idVecX &rv1, float &anorm ) { + int i, j, k, l; + double f, h, r, g, s, scale; + + anorm = 0.0f; + g = s = scale = 0.0f; + for ( i = 0; i < numColumns; i++ ) { + l = i + 1; + rv1[i] = scale * g; + g = s = scale = 0.0f; + if ( i < numRows ) { + for ( k = i; k < numRows; k++ ) { + scale += idMath::Fabs( (*this)[k][i] ); + } + if ( scale ) { + for ( k = i; k < numRows; k++ ) { + (*this)[k][i] /= scale; + s += (*this)[k][i] * (*this)[k][i]; + } + f = (*this)[i][i]; + g = idMath::Sqrt( s ); + if ( f >= 0.0f ) { + g = -g; + } + h = f * g - s; + (*this)[i][i] = f - g; + if ( i != (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = i; k < numRows; k++ ) { + s += (*this)[k][i] * (*this)[k][j]; + } + f = s / h; + for ( k = i; k < numRows; k++ ) { + (*this)[k][j] += f * (*this)[k][i]; + } + } + } + for ( k = i; k < numRows; k++ ) { + (*this)[k][i] *= scale; + } + } + } + w[i] = scale * g; + g = s = scale = 0.0f; + if ( i < numRows && i != (numColumns-1) ) { + for ( k = l; k < numColumns; k++ ) { + scale += idMath::Fabs( (*this)[i][k] ); + } + if ( scale ) { + for ( k = l; k < numColumns; k++ ) { + (*this)[i][k] /= scale; + s += (*this)[i][k] * (*this)[i][k]; + } + f = (*this)[i][l]; + g = idMath::Sqrt( s ); + if ( f >= 0.0f ) { + g = -g; + } + h = 1.0f / ( f * g - s ); + (*this)[i][l] = f - g; + for ( k = l; k < numColumns; k++ ) { + rv1[k] = (*this)[i][k] * h; + } + if ( i != (numRows-1) ) { + for ( j = l; j < numRows; j++ ) { + for ( s = 0.0f, k = l; k < numColumns; k++ ) { + s += (*this)[j][k] * (*this)[i][k]; + } + for ( k = l; k < numColumns; k++ ) { + (*this)[j][k] += s * rv1[k]; + } + } + } + for ( k = l; k < numColumns; k++ ) { + (*this)[i][k] *= scale; + } + } + } + r = idMath::Fabs( w[i] ) + idMath::Fabs( rv1[i] ); + if ( r > anorm ) { + anorm = r; + } + } +} + +/* +============ +idMatX::SVD_InitialWV +============ +*/ +void idMatX::SVD_InitialWV( idVecX &w, idMatX &V, idVecX &rv1 ) { + int i, j, k, l; + double f, g, s; + + g = 0.0f; + for ( i = (numColumns-1); i >= 0; i-- ) { + l = i + 1; + if ( i < ( numColumns - 1 ) ) { + if ( g ) { + for ( j = l; j < numColumns; j++ ) { + V[j][i] = ((*this)[i][j] / (*this)[i][l]) / g; + } + // double division to reduce underflow + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = l; k < numColumns; k++ ) { + s += (*this)[i][k] * V[k][j]; + } + for ( k = l; k < numColumns; k++ ) { + V[k][j] += s * V[k][i]; + } + } + } + for ( j = l; j < numColumns; j++ ) { + V[i][j] = V[j][i] = 0.0f; + } + } + V[i][i] = 1.0f; + g = rv1[i]; + } + for ( i = numColumns - 1 ; i >= 0; i-- ) { + l = i + 1; + g = w[i]; + if ( i < (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + (*this)[i][j] = 0.0f; + } + } + if ( g ) { + g = 1.0f / g; + if ( i != (numColumns-1) ) { + for ( j = l; j < numColumns; j++ ) { + for ( s = 0.0f, k = l; k < numRows; k++ ) { + s += (*this)[k][i] * (*this)[k][j]; + } + f = (s / (*this)[i][i]) * g; + for ( k = i; k < numRows; k++ ) { + (*this)[k][j] += f * (*this)[k][i]; + } + } + } + for ( j = i; j < numRows; j++ ) { + (*this)[j][i] *= g; + } + } + else { + for ( j = i; j < numRows; j++ ) { + (*this)[j][i] = 0.0f; + } + } + (*this)[i][i] += 1.0f; + } +} + +/* +============ +idMatX::SVD_Factor + + in-place factorization: U * Diag(w) * V.Transpose() + known as the Singular Value Decomposition. + U is a column-orthogonal matrix which overwrites the original matrix. + w is a diagonal matrix with all elements >= 0 which are the singular values. + V is the transpose of an orthogonal matrix. +============ +*/ +bool idMatX::SVD_Factor( idVecX &w, idMatX &V ) { + int flag, i, its, j, jj, k, l, nm; + double c, f, h, s, x, y, z, r, g = 0.0f; + float anorm = 0.0f; + idVecX rv1; + + if ( numRows < numColumns ) { + return false; + } + + rv1.SetData( numColumns, VECX_ALLOCA( numColumns ) ); + rv1.Zero(); + w.Zero( numColumns ); + V.Zero( numColumns, numColumns ); + + SVD_BiDiag( w, rv1, anorm ); + SVD_InitialWV( w, V, rv1 ); + + for ( k = numColumns - 1; k >= 0; k-- ) { + for ( its = 1; its <= 30; its++ ) { + flag = 1; + nm = 0; + for ( l = k; l >= 0; l-- ) { + nm = l - 1; + if ( ( idMath::Fabs( rv1[l] ) + anorm ) == anorm /* idMath::Fabs( rv1[l] ) < idMath::FLT_EPSILON */ ) { + flag = 0; + break; + } + if ( ( idMath::Fabs( w[nm] ) + anorm ) == anorm /* idMath::Fabs( w[nm] ) < idMath::FLT_EPSILON */ ) { + break; + } + } + if ( flag ) { + c = 0.0f; + s = 1.0f; + for ( i = l; i <= k; i++ ) { + f = s * rv1[i]; + + if ( ( idMath::Fabs( f ) + anorm ) != anorm /* idMath::Fabs( f ) > idMath::FLT_EPSILON */ ) { + g = w[i]; + h = Pythag( f, g ); + w[i] = h; + h = 1.0f / h; + c = g * h; + s = -f * h; + for ( j = 0; j < numRows; j++ ) { + y = (*this)[j][nm]; + z = (*this)[j][i]; + (*this)[j][nm] = y * c + z * s; + (*this)[j][i] = z * c - y * s; + } + } + } + } + z = w[k]; + if ( l == k ) { + if ( z < 0.0f ) { + w[k] = -z; + for ( j = 0; j < numColumns; j++ ) { + V[j][k] = -V[j][k]; + } + } + break; + } + if ( its == 30 ) { + return false; // no convergence + } + x = w[l]; + nm = k - 1; + y = w[nm]; + g = rv1[nm]; + h = rv1[k]; + f = ( ( y - z ) * ( y + z ) + ( g - h ) * ( g + h ) ) / ( 2.0f * h * y ); + g = Pythag( f, 1.0f ); + r = ( f >= 0.0f ? g : - g ); + f= ( ( x - z ) * ( x + z ) + h * ( ( y / ( f + r ) ) - h ) ) / x; + c = s = 1.0f; + for ( j = l; j <= nm; j++ ) { + i = j + 1; + g = rv1[i]; + y = w[i]; + h = s * g; + g = c * g; + z = Pythag( f, h ); + rv1[j] = z; + c = f / z; + s = h / z; + f = x * c + g * s; + g = g * c - x * s; + h = y * s; + y = y * c; + for ( jj = 0; jj < numColumns; jj++ ) { + x = V[jj][j]; + z = V[jj][i]; + V[jj][j] = x * c + z * s; + V[jj][i] = z * c - x * s; + } + z = Pythag( f, h ); + w[j] = z; + if ( z ) { + z = 1.0f / z; + c = f * z; + s = h * z; + } + f = ( c * g ) + ( s * y ); + x = ( c * y ) - ( s * g ); + for ( jj = 0; jj < numRows; jj++ ) { + y = (*this)[jj][j]; + z = (*this)[jj][i]; + (*this)[jj][j] = y * c + z * s; + (*this)[jj][i] = z * c - y * s; + } + } + rv1[l] = 0.0f; + rv1[k] = f; + w[k] = x; + } + } + return true; +} + +/* +============ +idMatX::SVD_Solve + + Solve Ax = b with A factored as: U * Diag(w) * V.Transpose() +============ +*/ +void idMatX::SVD_Solve( idVecX &x, const idVecX &b, const idVecX &w, const idMatX &V ) const { + int i, j; + double sum; + idVecX tmp; + + assert( x.GetSize() >= numColumns ); + assert( b.GetSize() >= numColumns ); + assert( w.GetSize() == numColumns ); + assert( V.GetNumRows() == numColumns && V.GetNumColumns() == numColumns ); + + tmp.SetData( numColumns, VECX_ALLOCA( numColumns ) ); + + for ( i = 0; i < numColumns; i++ ) { + sum = 0.0f; +// RAVEN BEGIN +// jscott: renamed to prevent name clash + if ( w[i] >= idMath::FLOAT_EPSILON ) { +// RAVEN END + for ( j = 0; j < numRows; j++ ) { + sum += (*this)[j][i] * b[j]; + } + sum /= w[i]; + } + tmp[i] = sum; + } + for ( i = 0; i < numColumns; i++ ) { + sum = 0.0f; + for ( j = 0; j < numColumns; j++ ) { + sum += V[i][j] * tmp[j]; + } + x[i] = sum; + } +} + +/* +============ +idMatX::SVD_Inverse + + Calculates the inverse of the matrix which is factored in-place as: U * Diag(w) * V.Transpose() +============ +*/ +void idMatX::SVD_Inverse( idMatX &inv, const idVecX &w, const idMatX &V ) const { + int i, j, k; + double wi, sum; + idMatX V2; + + assert( numRows == numColumns ); + + V2 = V; + + // V * [diag(1/w[i])] + for ( i = 0; i < numRows; i++ ) { + wi = w[i]; +// RAVEN BEGIN +// jscott: renamed to prevent name clash + wi = ( wi < idMath::FLOAT_EPSILON ) ? 0.0f : 1.0f / wi; +// RAVEN END + for ( j = 0; j < numColumns; j++ ) { + V2[j][i] *= wi; + } + } + + // V * [diag(1/w[i])] * Ut + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + sum = V2[i][0] * (*this)[j][0]; + for ( k = 1; k < numColumns; k++ ) { + sum += V2[i][k] * (*this)[j][k]; + } + inv[i][j] = sum; + } + } +} + +/* +============ +idMatX::SVD_MultiplyFactors + + Multiplies the factors of the in-place SVD factorization to form the original matrix. +============ +*/ +void idMatX::SVD_MultiplyFactors( idMatX &m, const idVecX &w, const idMatX &V ) const { + int r, i, j; + double sum; + + m.SetSize( numRows, V.GetNumRows() ); + + for ( r = 0; r < numRows; r++ ) { + // calculate row of matrix +// RAVEN BEGIN +// jscott: renamed to prevent name clash + if ( w[r] >= idMath::FLOAT_EPSILON ) { +// RAVEN END + for ( i = 0; i < V.GetNumRows(); i++ ) { + sum = 0.0f; + for ( j = 0; j < numColumns; j++ ) { + sum += (*this)[r][j] * V[i][j]; + } + m[r][i] = sum * w[r]; + } + } else { + for ( i = 0; i < V.GetNumRows(); i++ ) { + m[r][i] = 0.0f; + } + } + } +} + +/* +============ +idMatX::Cholesky_Factor + + in-place Cholesky factorization: LL' + L is a triangular matrix stored in the lower triangle. + The upper triangle is not cleared. + The initial matrix has to be symmetric positive definite. +============ +*/ +bool idMatX::Cholesky_Factor( void ) { + int i, j, k; + float *invSqrt; + double sum; + + assert( numRows == numColumns ); + + invSqrt = (float *) _alloca16( numRows * sizeof( float ) ); + + for ( i = 0; i < numRows; i++ ) { + + for ( j = 0; j < i; j++ ) { + + sum = (*this)[i][j]; + for ( k = 0; k < j; k++ ) { + sum -= (*this)[i][k] * (*this)[j][k]; + } + (*this)[i][j] = sum * invSqrt[j]; + } + + sum = (*this)[i][i]; + for ( k = 0; k < i; k++ ) { + sum -= (*this)[i][k] * (*this)[i][k]; + } + + if ( sum <= 0.0f ) { + return false; + } + + invSqrt[i] = idMath::InvSqrt( sum ); + (*this)[i][i] = invSqrt[i] * sum; + } + return true; +} + +/* +============ +idMatX::Cholesky_UpdateRankOne + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: LL' + alpha * v * v' + If offset > 0 only the lower right corner starting at (offset, offset) is updated. +============ +*/ +bool idMatX::Cholesky_UpdateRankOne( const idVecX &v, float alpha, int offset ) { + int i, j; + float *y; + double diag, invDiag, diagSqr, newDiag, newDiagSqr, beta, p, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( offset >= 0 && offset < numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + memcpy( y, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + + for ( i = offset; i < numColumns; i++ ) { + p = y[i]; + diag = (*this)[i][i]; + invDiag = 1.0f / diag; + diagSqr = diag * diag; + newDiagSqr = diagSqr + alpha * p * p; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + (*this)[i][i] = newDiag = idMath::Sqrt( newDiagSqr ); + + alpha /= newDiagSqr; + beta = p * alpha; + alpha *= diagSqr; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i] * invDiag; + + y[j] -= p * d; + d += beta * y[j]; + + (*this)[j][i] = d * newDiag; + } + } + return true; +} + +/* +============ +idMatX::Cholesky_UpdateRowColumn + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LL' + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +bool idMatX::Cholesky_UpdateRowColumn( const idVecX &v, int r ) { + + int i, j; +// RAVEN BEGIN +// jscott: VC7.1 fix + float sum; +// RAVEN END + float *original, *y; + idVecX addSub; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + addSub.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + if ( r == 0 ) { + + if ( numColumns == 1 ) { + sum = (*this)[0][0]; +// RAVEN BEGIN +// jscott: VC7.1 fix + sum = ( sum * sum ) + v[0]; +// RAVEN END + if ( sum <= 0.0f ) { + return false; + } + (*this)[0][0] = idMath::Sqrt( sum ); + return true; + } + for ( i = 0; i < numColumns; i++ ) { + addSub[i] = v[i]; + } + + } else { + + original = (float *) _alloca16( numColumns * sizeof( float ) ); + y = (float *) _alloca16( numColumns * sizeof( float ) ); + + // calculate original row/column of matrix + for ( i = 0; i < numRows; i++ ) { + sum = 0.0f; + for ( j = 0; j <= i; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + original[i] = sum; + } + + // solve for y in L * y = original + v + for ( i = 0; i < r; i++ ) { + sum = original[i] + v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[r][j] * (*this)[i][j]; + } + (*this)[r][i] = sum / (*this)[i][i]; + } + + // if the last row/column of the matrix is updated + if ( r == numColumns - 1 ) { + // only calculate new diagonal + sum = original[r] + v[r]; + for ( j = 0; j < r; j++) { + sum -= (*this)[r][j] * (*this)[r][j]; + } + if ( sum <= 0.0f ) { + return false; + } + (*this)[r][r] = idMath::Sqrt( sum ); + return true; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + sum = 0.0f; + for ( j = 0; j <= r; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + addSub[i] = v[i] - ( sum - original[i] ); + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + +#if 0 + + idVecX v1, v2; + double d; + + v1.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + v2.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + // update + if ( !Cholesky_UpdateRankOne( v1, 1.0f, r ) ) { + return false; + } + // downdate + if ( !Cholesky_UpdateRankOne( v2, -1.0f, r ) ) { + return false; + } + +#else + + float *v1, *v2; + double diag, invDiag, diagSqr, newDiag, newDiagSqr; + double alpha1, alpha2, beta1, beta2, p1, p2, d; + + v1 = (float *) _alloca16( numColumns * sizeof( float ) ); + v2 = (float *) _alloca16( numColumns * sizeof( float ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + p1 = v1[i]; + diag = (*this)[i][i]; + invDiag = 1.0f / diag; + diagSqr = diag * diag; + newDiagSqr = diagSqr + alpha1 * p1 * p1; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + alpha1 /= newDiagSqr; + beta1 = p1 * alpha1; + alpha1 *= diagSqr; + + p2 = v2[i]; + diagSqr = newDiagSqr; + newDiagSqr = diagSqr + alpha2 * p2 * p2; + + if ( newDiagSqr <= 0.0f ) { + return false; + } + + (*this)[i][i] = newDiag = idMath::Sqrt( newDiagSqr ); + + alpha2 /= newDiagSqr; + beta2 = p2 * alpha2; + alpha2 *= diagSqr; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i] * invDiag; + + v1[j] -= p1 * d; + d += beta1 * v1[j]; + + v2[j] -= p2 * d; + d += beta2 * v2[j]; + + (*this)[j][i] = d * newDiag; + } + } + +#endif + return true; +} + +/* +============ +idMatX::Cholesky_UpdateIncrement + + Updates the in-place Cholesky factorization to obtain the factors for the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +bool idMatX::Cholesky_UpdateIncrement( const idVecX &v ) { + int i, j; + float *x; + double sum; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + x = (float *) _alloca16( numRows * sizeof( float ) ); + + // solve for x in L * x = v + for ( i = 0; i < numRows - 1; i++ ) { + sum = v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } + + // calculate new row of L and calculate the square of the diagonal entry + sum = v[numRows - 1]; + for ( i = 0; i < numRows - 1; i++ ) { + (*this)[numRows - 1][i] = x[i]; + sum -= x[i] * x[i]; + } + + if ( sum <= 0.0f ) { + return false; + } + + // store the diagonal entry + (*this)[numRows - 1][numRows - 1] = idMath::Sqrt( sum ); + + return true; +} + +/* +============ +idMatX::Cholesky_UpdateDecrement + + Updates the in-place Cholesky factorization to obtain the factors for the matrix with row r and column r removed. + v should store the row of the original matrix. +============ +*/ +bool idMatX::Cholesky_UpdateDecrement( const idVecX &v, int r ) { + idVecX v1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + v1[r] += 1.0f; + + if ( !Cholesky_UpdateRowColumn( v1, r ) ) { + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::Cholesky_Solve + + Solve Ax = b with A factored in-place as: LL' +============ +*/ +void idMatX::Cholesky_Solve( idVecX &x, const idVecX &b ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } + + // solve Lt + for ( i = numRows - 1; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[j][i] * x[j]; + } + x[i] = sum / (*this)[i][i]; + } +} + +/* +============ +idMatX::Cholesky_Inverse + + Calculates the inverse of the matrix which is factored in-place as: LL' +============ +*/ +void idMatX::Cholesky_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + Cholesky_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::Cholesky_MultiplyFactors + + Multiplies the factors of the in-place Cholesky factorization to form the original matrix. +============ +*/ +void idMatX::Cholesky_MultiplyFactors( idMatX &m ) const { + int r, i, j; + double sum; + + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + // calculate row of matrix + for ( i = 0; i < numRows; i++ ) { + sum = 0.0f; + for ( j = 0; j <= i && j <= r; j++ ) { + sum += (*this)[r][j] * (*this)[i][j]; + } + m[r][i] = sum; + } + } +} + +/* +============ +idMatX::LDLT_Factor + + in-place factorization: LDL' + L is a triangular matrix stored in the lower triangle. + L has ones on the diagonal that are not stored. + D is a diagonal matrix stored on the diagonal. + The upper triangle is not cleared. + The initial matrix has to be symmetric. +============ +*/ +bool idMatX::LDLT_Factor( void ) { + int i, j, k; + float *v; + double d, sum; + + assert( numRows == numColumns ); + + v = (float *) _alloca16( numRows * sizeof( float ) ); + + for ( i = 0; i < numRows; i++ ) { + + sum = (*this)[i][i]; + for ( j = 0; j < i; j++ ) { + d = (*this)[i][j]; + v[j] = (*this)[j][j] * d; + sum -= v[j] * d; + } + + if ( sum == 0.0f ) { + return false; + } + + (*this)[i][i] = sum; + d = 1.0f / sum; + + for ( j = i + 1; j < numRows; j++ ) { + sum = (*this)[j][i]; + for ( k = 0; k < i; k++ ) { + sum -= (*this)[j][k] * v[k]; + } + (*this)[j][i] = sum * d; + } + } + + return true; +} + +/* +============ +idMatX::LDLT_UpdateRankOne + + Updates the in-place LDL' factorization to obtain the factors for the matrix: LDL' + alpha * v * v' + If offset > 0 only the lower right corner starting at (offset, offset) is updated. +============ +*/ +bool idMatX::LDLT_UpdateRankOne( const idVecX &v, float alpha, int offset ) { + int i, j; + float *y; + double diag, newDiag, beta, p, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( offset >= 0 && offset < numRows ); + + y = (float *) _alloca16( v.GetSize() * sizeof( float ) ); + memcpy( y, v.ToFloatPtr(), v.GetSize() * sizeof( float ) ); + + for ( i = offset; i < numColumns; i++ ) { + p = y[i]; + diag = (*this)[i][i]; + (*this)[i][i] = newDiag = diag + alpha * p * p; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha /= newDiag; + beta = p * alpha; + alpha *= diag; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + y[j] -= p * d; + d += beta * y[j]; + + (*this)[j][i] = d; + } + } + + return true; +} + +/* +============ +idMatX::LDLT_UpdateRowColumn + + Updates the in-place LDL' factorization to obtain the factors for the matrix: + + [ 0 a 0 ] + LDL' + [ a b c ] + [ 0 c 0 ] + + where: a = v[0,r-1], b = v[r], c = v[r+1,numRows-1] +============ +*/ +bool idMatX::LDLT_UpdateRowColumn( const idVecX &v, int r ) { + int i, j; + double sum; + float *original, *y; + idVecX addSub; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + addSub.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + if ( r == 0 ) { + + if ( numColumns == 1 ) { + (*this)[0][0] += v[0]; + return true; + } + for ( i = 0; i < numColumns; i++ ) { + addSub[i] = v[i]; + } + + } else { + + original = (float *) _alloca16( numColumns * sizeof( float ) ); + y = (float *) _alloca16( numColumns * sizeof( float ) ); + + // calculate original row/column of matrix + for ( i = 0; i < r; i++ ) { + y[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = 0; i < numColumns; i++ ) { + if ( i < r ) { + sum = (*this)[i][i] * (*this)[r][i]; + } else if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < i && j < r; j++ ) { + sum += (*this)[i][j] * y[j]; + } + original[i] = sum; + } + + // solve for y in L * y = original + v + for ( i = 0; i < r; i++ ) { + sum = original[i] + v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * y[j]; + } + y[i] = sum; + } + + // calculate new row of L + for ( i = 0; i < r; i++ ) { + (*this)[r][i] = y[i] / (*this)[i][i]; + } + + // if the last row/column of the matrix is updated + if ( r == numColumns - 1 ) { + // only calculate new diagonal + sum = original[r] + v[r]; + for ( j = 0; j < r; j++ ) { + sum -= (*this)[r][j] * y[j]; + } + if ( sum == 0.0f ) { + return false; + } + (*this)[r][r] = sum; + return true; + } + + // calculate the row/column to be added to the lower right sub matrix starting at (r, r) + for ( i = 0; i < r; i++ ) { + y[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = r; i < numColumns; i++ ) { + if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < r; j++ ) { + sum += (*this)[i][j] * y[j]; + } + addSub[i] = v[i] - ( sum - original[i] ); + } + } + + // add row/column to the lower right sub matrix starting at (r, r) + +#if 0 + + idVecX v1, v2; + double d; + + v1.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + v2.SetData( numColumns, (float *) _alloca16( numColumns * sizeof( float ) ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + // update + if ( !LDLT_UpdateRankOne( v1, 1.0f, r ) ) { + return false; + } + // downdate + if ( !LDLT_UpdateRankOne( v2, -1.0f, r ) ) { + return false; + } + +#else + + float *v1, *v2; + double d, diag, newDiag, p1, p2, alpha1, alpha2, beta1, beta2; + + v1 = (float *) _alloca16( numColumns * sizeof( float ) ); + v2 = (float *) _alloca16( numColumns * sizeof( float ) ); + + d = idMath::SQRT_1OVER2; + v1[r] = ( 0.5f * addSub[r] + 1.0f ) * d; + v2[r] = ( 0.5f * addSub[r] - 1.0f ) * d; + for ( i = r+1; i < numColumns; i++ ) { + v1[i] = v2[i] = addSub[i] * d; + } + + alpha1 = 1.0f; + alpha2 = -1.0f; + + // simultaneous update/downdate of the sub matrix starting at (r, r) + for ( i = r; i < numColumns; i++ ) { + + diag = (*this)[i][i]; + p1 = v1[i]; + newDiag = diag + alpha1 * p1 * p1; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha1 /= newDiag; + beta1 = p1 * alpha1; + alpha1 *= diag; + + diag = newDiag; + p2 = v2[i]; + newDiag = diag + alpha2 * p2 * p2; + + if ( newDiag == 0.0f ) { + return false; + } + + alpha2 /= newDiag; + beta2 = p2 * alpha2; + alpha2 *= diag; + + (*this)[i][i] = newDiag; + + for ( j = i+1; j < numRows; j++ ) { + + d = (*this)[j][i]; + + v1[j] -= p1 * d; + d += beta1 * v1[j]; + + v2[j] -= p2 * d; + d += beta2 * v2[j]; + + (*this)[j][i] = d; + } + } + +#endif + + return true; +} + +/* +============ +idMatX::LDLT_UpdateIncrement + + Updates the in-place LDL' factorization to obtain the factors for the matrix: + + [ A a ] + [ a b ] + + where: a = v[0,numRows-1], b = v[numRows] +============ +*/ +bool idMatX::LDLT_UpdateIncrement( const idVecX &v ) { + int i, j; + float *x; + double sum, d; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows+1 ); + + ChangeSize( numRows+1, numColumns+1, false ); + + x = (float *) _alloca16( numRows * sizeof( float ) ); + + // solve for x in L * x = v + for ( i = 0; i < numRows - 1; i++ ) { + sum = v[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // calculate new row of L and calculate the diagonal entry + sum = v[numRows - 1]; + for ( i = 0; i < numRows - 1; i++ ) { + (*this)[numRows - 1][i] = d = x[i] / (*this)[i][i]; + sum -= d * x[i]; + } + + if ( sum == 0.0f ) { + return false; + } + + // store the diagonal entry + (*this)[numRows - 1][numRows - 1] = sum; + + return true; +} + +/* +============ +idMatX::LDLT_UpdateDecrement + + Updates the in-place LDL' factorization to obtain the factors for the matrix with row r and column r removed. + v should store the row of the original matrix. +============ +*/ +bool idMatX::LDLT_UpdateDecrement( const idVecX &v, int r ) { + idVecX v1; + + assert( numRows == numColumns ); + assert( v.GetSize() >= numRows ); + assert( r >= 0 && r < numRows ); + + v1.SetData( numRows, VECX_ALLOCA( numRows ) ); + + // update the row and column to identity + v1 = -v; + v1[r] += 1.0f; + + // NOTE: msvc compiler bug: the this pointer stored in edi is expected to stay + // untouched when calling LDLT_UpdateRowColumn in the if statement +#if 0 + if ( !LDLT_UpdateRowColumn( v1, r ) ) { +#else + bool ret = LDLT_UpdateRowColumn( v1, r ); + if ( !ret ) { +#endif + return false; + } + + // physically remove the row and column + Update_Decrement( r ); + + return true; +} + +/* +============ +idMatX::LDLT_Solve + + Solve Ax = b with A factored in-place as: LDL' +============ +*/ +void idMatX::LDLT_Solve( idVecX &x, const idVecX &b ) const { + int i, j; + double sum; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + // solve L + for ( i = 0; i < numRows; i++ ) { + sum = b[i]; + for ( j = 0; j < i; j++ ) { + sum -= (*this)[i][j] * x[j]; + } + x[i] = sum; + } + + // solve D + for ( i = 0; i < numRows; i++ ) { + x[i] /= (*this)[i][i]; + } + + // solve Lt + for ( i = numRows - 2; i >= 0; i-- ) { + sum = x[i]; + for ( j = i + 1; j < numRows; j++ ) { + sum -= (*this)[j][i] * x[j]; + } + x[i] = sum; + } +} + +/* +============ +idMatX::LDLT_Inverse + + Calculates the inverse of the matrix which is factored in-place as: LDL' +============ +*/ +void idMatX::LDLT_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + LDLT_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::LDLT_UnpackFactors + + Unpacks the in-place LDL' factorization. +============ +*/ +void idMatX::LDLT_UnpackFactors( idMatX &L, idMatX &D ) const { + int i, j; + + L.Zero( numRows, numColumns ); + D.Zero( numRows, numColumns ); + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < i; j++ ) { + L[i][j] = (*this)[i][j]; + } + L[i][i] = 1.0f; + D[i][i] = (*this)[i][i]; + } +} + +/* +============ +idMatX::LDLT_MultiplyFactors + + Multiplies the factors of the in-place LDL' factorization to form the original matrix. +============ +*/ +void idMatX::LDLT_MultiplyFactors( idMatX &m ) const { + int r, i, j; + float *v; + double sum; + + v = (float *) _alloca16( numRows * sizeof( float ) ); + m.SetSize( numRows, numColumns ); + + for ( r = 0; r < numRows; r++ ) { + + // calculate row of matrix + for ( i = 0; i < r; i++ ) { + v[i] = (*this)[r][i] * (*this)[i][i]; + } + for ( i = 0; i < numColumns; i++ ) { + if ( i < r ) { + sum = (*this)[i][i] * (*this)[r][i]; + } else if ( i == r ) { + sum = (*this)[r][r]; + } else { + sum = (*this)[r][r] * (*this)[i][r]; + } + for ( j = 0; j < i && j < r; j++ ) { + sum += (*this)[i][j] * v[j]; + } + m[r][i] = sum; + } + } +} + +/* +============ +idMatX::TriDiagonal_ClearTriangles +============ +*/ +void idMatX::TriDiagonal_ClearTriangles( void ) { + int i, j; + + assert( numRows == numColumns ); + for ( i = 0; i < numRows-2; i++ ) { + for ( j = i+2; j < numColumns; j++ ) { + (*this)[i][j] = 0.0f; + (*this)[j][i] = 0.0f; + } + } +} + +/* +============ +idMatX::TriDiagonal_Solve + + Solve Ax = b with A being tridiagonal. +============ +*/ +bool idMatX::TriDiagonal_Solve( idVecX &x, const idVecX &b ) const { + int i; + float d; + idVecX tmp; + + assert( numRows == numColumns ); + assert( x.GetSize() >= numRows && b.GetSize() >= numRows ); + + tmp.SetData( numRows, VECX_ALLOCA( numRows ) ); + + d = (*this)[0][0]; + if ( d == 0.0f ) { + return false; + } + d = 1.0f / d; + x[0] = b[0] * d; + for ( i = 1; i < numRows; i++ ) { + tmp[i] = (*this)[i-1][i] * d; + d = (*this)[i][i] - (*this)[i][i-1] * tmp[i]; + if ( d == 0.0f ) { + return false; + } + d = 1.0f / d; + x[i] = ( b[i] - (*this)[i][i-1] * x[i-1] ) * d; + } + for ( i = numRows - 2; i >= 0; i-- ) { + x[i] -= tmp[i+1] * x[i+1]; + } + return true; +} + +/* +============ +idMatX::TriDiagonal_Inverse + + Calculates the inverse of a tri-diagonal matrix. +============ +*/ +void idMatX::TriDiagonal_Inverse( idMatX &inv ) const { + int i, j; + idVecX x, b; + + assert( numRows == numColumns ); + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + inv.SetSize( numRows, numColumns ); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + TriDiagonal_Solve( x, b ); + for ( j = 0; j < numRows; j++ ) { + inv[j][i] = x[j]; + } + b[i] = 0.0f; + } +} + +/* +============ +idMatX::HouseholderReduction + + Householder reduction to symmetric tri-diagonal form. + The original matrix is replaced by an orthogonal matrix effecting the accumulated householder transformations. + The diagonal elements of the diagonal matrix are stored in diag. + The off-diagonal elements of the diagonal matrix are stored in subd. + The initial matrix has to be symmetric. +============ +*/ +void idMatX::HouseholderReduction( idVecX &diag, idVecX &subd ) { + int i0, i1, i2, i3; + float h, f, g, invH, halfFdivH, scale, invScale, sum; + + assert( numRows == numColumns ); + + diag.SetSize( numRows ); + subd.SetSize( numRows ); + + for ( i0 = numRows-1, i3 = numRows-2; i0 >= 1; i0--, i3-- ) { + h = 0.0f; + scale = 0.0f; + + if ( i3 > 0 ) { + for ( i2 = 0; i2 <= i3; i2++ ) { + scale += idMath::Fabs( (*this)[i0][i2] ); + } + if ( scale == 0 ) { + subd[i0] = (*this)[i0][i3]; + } else { + invScale = 1.0f / scale; + for (i2 = 0; i2 <= i3; i2++) + { + (*this)[i0][i2] *= invScale; + h += (*this)[i0][i2] * (*this)[i0][i2]; + } + f = (*this)[i0][i3]; + g = idMath::Sqrt( h ); + if ( f > 0.0f ) { + g = -g; + } + subd[i0] = scale * g; + h -= f * g; + (*this)[i0][i3] = f - g; + f = 0.0f; + invH = 1.0f / h; + for (i1 = 0; i1 <= i3; i1++) { + (*this)[i1][i0] = (*this)[i0][i1] * invH; + g = 0.0f; + for (i2 = 0; i2 <= i1; i2++) { + g += (*this)[i1][i2] * (*this)[i0][i2]; + } + for (i2 = i1+1; i2 <= i3; i2++) { + g += (*this)[i2][i1] * (*this)[i0][i2]; + } + subd[i1] = g * invH; + f += subd[i1] * (*this)[i0][i1]; + } + halfFdivH = 0.5f * f * invH; + for ( i1 = 0; i1 <= i3; i1++ ) { + f = (*this)[i0][i1]; + g = subd[i1] - halfFdivH * f; + subd[i1] = g; + for ( i2 = 0; i2 <= i1; i2++ ) { + (*this)[i1][i2] -= f * subd[i2] + g * (*this)[i0][i2]; + } + } + } + } else { + subd[i0] = (*this)[i0][i3]; + } + + diag[i0] = h; + } + + diag[0] = 0.0f; + subd[0] = 0.0f; + for ( i0 = 0, i3 = -1; i0 <= numRows-1; i0++, i3++ ) { + if ( diag[i0] ) { + for ( i1 = 0; i1 <= i3; i1++ ) { + sum = 0.0f; + for (i2 = 0; i2 <= i3; i2++) { + sum += (*this)[i0][i2] * (*this)[i2][i1]; + } + for ( i2 = 0; i2 <= i3; i2++ ) { + (*this)[i2][i1] -= sum * (*this)[i2][i0]; + } + } + } + diag[i0] = (*this)[i0][i0]; + (*this)[i0][i0] = 1.0f; + for ( i1 = 0; i1 <= i3; i1++ ) { + (*this)[i1][i0] = 0.0f; + (*this)[i0][i1] = 0.0f; + } + } + + // re-order + for ( i0 = 1, i3 = 0; i0 < numRows; i0++, i3++ ) { + subd[i3] = subd[i0]; + } + subd[numRows-1] = 0.0f; +} + +/* +============ +idMatX::QL + + QL algorithm with implicit shifts to determine the eigenvalues and eigenvectors of a symmetric tri-diagonal matrix. + diag contains the diagonal elements of the symmetric tri-diagonal matrix on input and is overwritten with the eigenvalues. + subd contains the off-diagonal elements of the symmetric tri-diagonal matrix and is destroyed. + This matrix has to be either the identity matrix to determine the eigenvectors for a symmetric tri-diagonal matrix, + or the matrix returned by the Householder reduction to determine the eigenvalues for the original symmetric matrix. +============ +*/ +bool idMatX::QL( idVecX &diag, idVecX &subd ) { + const int maxIter = 32; + int i0, i1, i2, i3; + float a, b, f, g, r, p, s, c; + + assert( numRows == numColumns ); + + for ( i0 = 0; i0 < numRows; i0++ ) { + for ( i1 = 0; i1 < maxIter; i1++ ) { + for ( i2 = i0; i2 <= numRows - 2; i2++ ) { + a = idMath::Fabs( diag[i2] ) + idMath::Fabs( diag[i2+1] ); + if ( idMath::Fabs( subd[i2] ) + a == a ) { + break; + } + } + if ( i2 == i0 ) { + break; + } + + g = ( diag[i0+1] - diag[i0] ) / ( 2.0f * subd[i0] ); + r = idMath::Sqrt( g * g + 1.0f ); + if ( g < 0.0f ) { + g = diag[i2] - diag[i0] + subd[i0] / ( g - r ); + } else { + g = diag[i2] - diag[i0] + subd[i0] / ( g + r ); + } + s = 1.0f; + c = 1.0f; + p = 0.0f; + for ( i3 = i2 - 1; i3 >= i0; i3-- ) { + f = s * subd[i3]; + b = c * subd[i3]; + if ( idMath::Fabs( f ) >= idMath::Fabs( g ) ) { + c = g / f; + r = idMath::Sqrt( c * c + 1.0f ); + subd[i3+1] = f * r; + s = 1.0f / r; + c *= s; + } else { + s = f / g; + r = idMath::Sqrt( s * s + 1.0f ); + subd[i3+1] = g * r; + c = 1.0f / r; + s *= c; + } + g = diag[i3+1] - p; + r = ( diag[i3] - g ) * s + 2.0f * b * c; + p = s * r; + diag[i3+1] = g + p; + g = c * r - b; + + for ( int i4 = 0; i4 < numRows; i4++ ) { + f = (*this)[i4][i3+1]; + (*this)[i4][i3+1] = s * (*this)[i4][i3] + c * f; + (*this)[i4][i3] = c * (*this)[i4][i3] - s * f; + } + } + diag[i0] -= p; + subd[i0] = g; + subd[i2] = 0.0f; + } + if ( i1 == maxIter ) { + return false; + } + } + return true; +} + +/* +============ +idMatX::Eigen_SolveSymmetricTriDiagonal + + Determine eigen values and eigen vectors for a symmetric tri-diagonal matrix. + The eigen values are stored in 'eigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the eigenValues[i]. + The initial matrix has to be symmetric tri-diagonal. +============ +*/ +bool idMatX::Eigen_SolveSymmetricTriDiagonal( idVecX &eigenValues ) { + int i; + idVecX subd; + + assert( numRows == numColumns ); + + subd.SetData( numRows, VECX_ALLOCA( numRows ) ); + eigenValues.SetSize( numRows ); + + for ( i = 0; i < numRows-1; i++ ) { + eigenValues[i] = (*this)[i][i]; + subd[i] = (*this)[i+1][i]; + } + eigenValues[numRows-1] = (*this)[numRows-1][numRows-1]; + + Identity(); + + return QL( eigenValues, subd ); +} + +/* +============ +idMatX::Eigen_SolveSymmetric + + Determine eigen values and eigen vectors for a symmetric matrix. + The eigen values are stored in 'eigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the eigenValues[i]. + The initial matrix has to be symmetric. +============ +*/ +bool idMatX::Eigen_SolveSymmetric( idVecX &eigenValues ) { + idVecX subd; + + assert( numRows == numColumns ); + + subd.SetData( numRows, VECX_ALLOCA( numRows ) ); + eigenValues.SetSize( numRows ); + + HouseholderReduction( eigenValues, subd ); + return QL( eigenValues, subd ); +} + +/* +============ +idMatX::HessenbergReduction + + Reduction to Hessenberg form. +============ +*/ +void idMatX::HessenbergReduction( idMatX &H ) { + int i, j, m; + int low = 0; + int high = numRows - 1; + float scale, f, g, h; + idVecX v; + + v.SetData( numRows, VECX_ALLOCA( numRows ) ); + + for ( m = low + 1; m <= high - 1; m++ ) { + + scale = 0.0f; + for ( i = m; i <= high; i++ ) { + scale = scale + idMath::Fabs( H[i][m-1] ); + } + if ( scale != 0.0f ) { + + // compute Householder transformation. + h = 0.0f; + for ( i = high; i >= m; i-- ) { + v[i] = H[i][m-1] / scale; + h += v[i] * v[i]; + } + g = idMath::Sqrt( h ); + if ( v[m] > 0.0f ) { + g = -g; + } + h = h - v[m] * g; + v[m] = v[m] - g; + + // apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for ( j = m; j < numRows; j++) { + f = 0.0f; + for ( i = high; i >= m; i-- ) { + f += v[i] * H[i][j]; + } + f = f / h; + for ( i = m; i <= high; i++ ) { + H[i][j] -= f * v[i]; + } + } + + for ( i = 0; i <= high; i++ ) { + f = 0.0f; + for ( j = high; j >= m; j-- ) { + f += v[j] * H[i][j]; + } + f = f / h; + for ( j = m; j <= high; j++ ) { + H[i][j] -= f * v[j]; + } + } + v[m] = scale * v[m]; + H[m][m-1] = scale * g; + } + } + + // accumulate transformations + Identity(); + for ( int m = high - 1; m >= low + 1; m-- ) { + if ( H[m][m-1] != 0.0f ) { + for ( i = m + 1; i <= high; i++ ) { + v[i] = H[i][m-1]; + } + for ( j = m; j <= high; j++ ) { + g = 0.0f; + for ( i = m; i <= high; i++ ) { + g += v[i] * (*this)[i][j]; + } + // float division to avoid possible underflow + g = ( g / v[m] ) / H[m][m-1]; + for ( i = m; i <= high; i++ ) { + (*this)[i][j] += g * v[i]; + } + } + } + } +} + +/* +============ +idMatX::ComplexDivision + + Complex scalar division. +============ +*/ +void idMatX::ComplexDivision( float xr, float xi, float yr, float yi, float &cdivr, float &cdivi ) { + float r, d; + if ( idMath::Fabs( yr ) > idMath::Fabs( yi ) ) { + r = yi / yr; + d = yr + r * yi; + cdivr = ( xr + r * xi ) / d; + cdivi = ( xi - r * xr ) / d; + } else { + r = yr / yi; + d = yi + r * yr; + cdivr = ( r * xr + xi ) / d; + cdivi = ( r * xi - xr ) / d; + } +} + +/* +============ +idMatX::HessenbergToRealSchur + + Reduction from Hessenberg to real Schur form. +============ +*/ +bool idMatX::HessenbergToRealSchur( idMatX &H, idVecX &realEigenValues, idVecX &imaginaryEigenValues ) { + int i, j, k; + int n = numRows - 1; + int low = 0; + int high = numRows - 1; + float eps = 2e-16f, exshift = 0.0f; + float p = 0.0f, q = 0.0f, r = 0.0f, s = 0.0f, z = 0.0f, t, w, x, y; + + // store roots isolated by balanc and compute matrix norm + float norm = 0.0f; + for ( i = 0; i < numRows; i++ ) { + if ( i < low || i > high ) { + realEigenValues[i] = H[i][i]; + imaginaryEigenValues[i] = 0.0f; + } + for ( j = Max( i - 1, 0 ); j < numRows; j++ ) { + norm = norm + idMath::Fabs( H[i][j] ); + } + } + + int iter = 0; + while( n >= low ) { + + // look for single small sub-diagonal element + int l = n; + while ( l > low ) { + s = idMath::Fabs( H[l-1][l-1] ) + idMath::Fabs( H[l][l] ); + if ( s == 0.0f ) { + s = norm; + } + if ( idMath::Fabs( H[l][l-1] ) < eps * s ) { + break; + } + l--; + } + + // check for convergence + if ( l == n ) { // one root found + H[n][n] = H[n][n] + exshift; + realEigenValues[n] = H[n][n]; + imaginaryEigenValues[n] = 0.0f; + n--; + iter = 0; + } else if ( l == n-1 ) { // two roots found + w = H[n][n-1] * H[n-1][n]; + p = ( H[n-1][n-1] - H[n][n] ) / 2.0f; + q = p * p + w; + z = idMath::Sqrt( idMath::Fabs( q ) ); + H[n][n] = H[n][n] + exshift; + H[n-1][n-1] = H[n-1][n-1] + exshift; + x = H[n][n]; + + if ( q >= 0.0f ) { // real pair + if ( p >= 0.0f ) { + z = p + z; + } else { + z = p - z; + } + realEigenValues[n-1] = x + z; + realEigenValues[n] = realEigenValues[n-1]; + if ( z != 0.0f ) { + realEigenValues[n] = x - w / z; + } + imaginaryEigenValues[n-1] = 0.0f; + imaginaryEigenValues[n] = 0.0f; + x = H[n][n-1]; + s = idMath::Fabs( x ) + idMath::Fabs( z ); + p = x / s; + q = z / s; + r = idMath::Sqrt( p * p + q * q ); + p = p / r; + q = q / r; + + // modify row + for ( j = n-1; j < numRows; j++ ) { + z = H[n-1][j]; + H[n-1][j] = q * z + p * H[n][j]; + H[n][j] = q * H[n][j] - p * z; + } + + // modify column + for ( i = 0; i <= n; i++ ) { + z = H[i][n-1]; + H[i][n-1] = q * z + p * H[i][n]; + H[i][n] = q * H[i][n] - p * z; + } + + // accumulate transformations + for ( i = low; i <= high; i++ ) { + z = (*this)[i][n-1]; + (*this)[i][n-1] = q * z + p * (*this)[i][n]; + (*this)[i][n] = q * (*this)[i][n] - p * z; + } + } else { // complex pair + realEigenValues[n-1] = x + p; + realEigenValues[n] = x + p; + imaginaryEigenValues[n-1] = z; + imaginaryEigenValues[n] = -z; + } + n = n - 2; + iter = 0; + + } else { // no convergence yet + + // form shift + x = H[n][n]; + y = 0.0f; + w = 0.0f; + if ( l < n ) { + y = H[n-1][n-1]; + w = H[n][n-1] * H[n-1][n]; + } + + // Wilkinson's original ad hoc shift + if ( iter == 10 ) { + exshift += x; + for ( i = low; i <= n; i++ ) { + H[i][i] -= x; + } + s = idMath::Fabs( H[n][n-1] ) + idMath::Fabs( H[n-1][n-2] ); + x = y = 0.75f * s; + w = -0.4375f * s * s; + } + + // new ad hoc shift + if ( iter == 30 ) { + s = ( y - x ) / 2.0f; + s = s * s + w; + if ( s > 0 ) { + s = idMath::Sqrt( s ); + if ( y < x ) { + s = -s; + } + s = x - w / ( ( y - x ) / 2.0f + s ); + for ( i = low; i <= n; i++ ) { + H[i][i] -= s; + } + exshift += s; + x = y = w = 0.964f; + } + } + + iter = iter + 1; + + // look for two consecutive small sub-diagonal elements + int m; + for( m = n-2; m >= l; m-- ) { + z = H[m][m]; + r = x - z; + s = y - z; + p = ( r * s - w ) / H[m+1][m] + H[m][m+1]; + q = H[m+1][m+1] - z - r - s; + r = H[m+2][m+1]; + s = idMath::Fabs( p ) + idMath::Fabs( q ) + idMath::Fabs( r ); + p = p / s; + q = q / s; + r = r / s; + if ( m == l ) { + break; + } + if ( idMath::Fabs( H[m][m-1] ) * ( idMath::Fabs( q ) + idMath::Fabs( r ) ) < + eps * ( idMath::Fabs( p ) * ( idMath::Fabs( H[m-1][m-1] ) + idMath::Fabs( z ) + idMath::Fabs( H[m+1][m+1] ) ) ) ) { + break; + } + } + + for ( i = m+2; i <= n; i++ ) { + H[i][i-2] = 0.0f; + if ( i > m+2 ) { + H[i][i-3] = 0.0f; + } + } + + // double QR step involving rows l:n and columns m:n + for ( k = m; k <= n-1; k++ ) { + bool notlast = ( k != n-1 ); + if ( k != m ) { + p = H[k][k-1]; + q = H[k+1][k-1]; + r = ( notlast ? H[k+2][k-1] : 0.0f ); + x = idMath::Fabs( p ) + idMath::Fabs( q ) + idMath::Fabs( r ); + if ( x != 0.0f ) { + p = p / x; + q = q / x; + r = r / x; + } + } + if ( x == 0.0f ) { + break; + } + s = idMath::Sqrt( p * p + q * q + r * r ); + if ( p < 0.0f ) { + s = -s; + } + if ( s != 0.0f ) { + if ( k != m ) { + H[k][k-1] = -s * x; + } else if ( l != m ) { + H[k][k-1] = -H[k][k-1]; + } + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // modify row + for ( j = k; j < numRows; j++ ) { + p = H[k][j] + q * H[k+1][j]; + if ( notlast ) { + p = p + r * H[k+2][j]; + H[k+2][j] = H[k+2][j] - p * z; + } + H[k][j] = H[k][j] - p * x; + H[k+1][j] = H[k+1][j] - p * y; + } + + // modify column + for ( i = 0; i <= Min( n, k + 3 ); i++ ) { + p = x * H[i][k] + y * H[i][k+1]; + if ( notlast ) { + p = p + z * H[i][k+2]; + H[i][k+2] = H[i][k+2] - p * r; + } + H[i][k] = H[i][k] - p; + H[i][k+1] = H[i][k+1] - p * q; + } + + // accumulate transformations + for ( i = low; i <= high; i++ ) { + p = x * (*this)[i][k] + y * (*this)[i][k+1]; + if ( notlast ) { + p = p + z * (*this)[i][k+2]; + (*this)[i][k+2] = (*this)[i][k+2] - p * r; + } + (*this)[i][k] = (*this)[i][k] - p; + (*this)[i][k+1] = (*this)[i][k+1] - p * q; + } + } + } + } + } + + // backsubstitute to find vectors of upper triangular form + if ( norm == 0.0f ) { + return false; + } + + for ( n = numRows-1; n >= 0; n-- ) { + p = realEigenValues[n]; + q = imaginaryEigenValues[n]; + + if ( q == 0.0f ) { // real vector + int l = n; + H[n][n] = 1.0f; + for ( i = n-1; i >= 0; i-- ) { + w = H[i][i] - p; + r = 0.0f; + for ( j = l; j <= n; j++ ) { + r = r + H[i][j] * H[j][n]; + } + if ( imaginaryEigenValues[i] < 0.0f ) { + z = w; + s = r; + } else { + l = i; + if ( imaginaryEigenValues[i] == 0.0f ) { + if ( w != 0.0f ) { + H[i][n] = -r / w; + } else { + H[i][n] = -r / ( eps * norm ); + } + } else { // solve real equations + x = H[i][i+1]; + y = H[i+1][i]; + q = ( realEigenValues[i] - p ) * ( realEigenValues[i] - p ) + imaginaryEigenValues[i] * imaginaryEigenValues[i]; + t = ( x * s - z * r ) / q; + H[i][n] = t; + if ( idMath::Fabs(x) > idMath::Fabs( z ) ) { + H[i+1][n] = ( -r - w * t ) / x; + } else { + H[i+1][n] = ( -s - y * t ) / z; + } + } + + // overflow control + t = idMath::Fabs(H[i][n]); + if ( ( eps * t ) * t > 1 ) { + for ( j = i; j <= n; j++ ) { + H[j][n] = H[j][n] / t; + } + } + } + } + } else if ( q < 0.0f ) { // complex vector + int l = n-1; + + // last vector component imaginary so matrix is triangular + if ( idMath::Fabs( H[n][n-1] ) > idMath::Fabs( H[n-1][n] ) ) { + H[n-1][n-1] = q / H[n][n-1]; + H[n-1][n] = -( H[n][n] - p ) / H[n][n-1]; + } else { + ComplexDivision( 0.0f, -H[n-1][n], H[n-1][n-1]-p, q, H[n-1][n-1], H[n-1][n] ); + } + H[n][n-1] = 0.0f; + H[n][n] = 1.0f; + for ( i = n-2; i >= 0; i-- ) { + float ra, sa, vr, vi; + ra = 0.0f; + sa = 0.0f; + for ( j = l; j <= n; j++ ) { + ra = ra + H[i][j] * H[j][n-1]; + sa = sa + H[i][j] * H[j][n]; + } + w = H[i][i] - p; + + if ( imaginaryEigenValues[i] < 0.0f ) { + z = w; + r = ra; + s = sa; + } else { + l = i; + if ( imaginaryEigenValues[i] == 0.0f ) { + ComplexDivision( -ra, -sa, w, q, H[i][n-1], H[i][n] ); + } else { + // solve complex equations + x = H[i][i+1]; + y = H[i+1][i]; + vr = ( realEigenValues[i] - p ) * ( realEigenValues[i] - p ) + imaginaryEigenValues[i] * imaginaryEigenValues[i] - q * q; + vi = ( realEigenValues[i] - p ) * 2.0f * q; + if ( vr == 0.0f && vi == 0.0f ) { + vr = eps * norm * ( idMath::Fabs( w ) + idMath::Fabs( q ) + idMath::Fabs( x ) + idMath::Fabs( y ) + idMath::Fabs( z ) ); + } + ComplexDivision( x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi, H[i][n-1], H[i][n] ); + if ( idMath::Fabs( x ) > ( idMath::Fabs( z ) + idMath::Fabs( q ) ) ) { + H[i+1][n-1] = ( -ra - w * H[i][n-1] + q * H[i][n] ) / x; + H[i+1][n] = ( -sa - w * H[i][n] - q * H[i][n-1] ) / x; + } else { + ComplexDivision( -r - y * H[i][n-1], -s - y * H[i][n], z, q, H[i+1][n-1], H[i+1][n] ); + } + } + + // overflow control + t = Max( idMath::Fabs( H[i][n-1] ), idMath::Fabs( H[i][n] ) ); + if ( ( eps * t ) * t > 1 ) { + for ( j = i; j <= n; j++ ) { + H[j][n-1] = H[j][n-1] / t; + H[j][n] = H[j][n] / t; + } + } + } + } + } + } + + // vectors of isolated roots + for ( i = 0; i < numRows; i++ ) { + if ( i < low || i > high ) { + for ( j = i; j < numRows; j++ ) { + (*this)[i][j] = H[i][j]; + } + } + } + + // back transformation to get eigenvectors of original matrix + for ( j = numRows - 1; j >= low; j-- ) { + for ( i = low; i <= high; i++ ) { + z = 0.0f; + for ( k = low; k <= Min( j, high ); k++ ) { + z = z + (*this)[i][k] * H[k][j]; + } + (*this)[i][j] = z; + } + } + + return true; +} + +/* +============ +idMatX::Eigen_Solve + + Determine eigen values and eigen vectors for a square matrix. + The eigen values are stored in 'realEigenValues' and 'imaginaryEigenValues'. + Column i of the original matrix will store the eigen vector corresponding to the realEigenValues[i] and imaginaryEigenValues[i]. +============ +*/ +bool idMatX::Eigen_Solve( idVecX &realEigenValues, idVecX &imaginaryEigenValues ) { + idMatX H; + + assert( numRows == numColumns ); + + realEigenValues.SetSize( numRows ); + imaginaryEigenValues.SetSize( numRows ); + + H = *this; + + // reduce to Hessenberg form + HessenbergReduction( H ); + + // reduce Hessenberg to real Schur form + return HessenbergToRealSchur( H, realEigenValues, imaginaryEigenValues ); +} + +/* +============ +idMatX::Eigen_SortIncreasing +============ +*/ +void idMatX::Eigen_SortIncreasing( idVecX &eigenValues ) { + int i, j, k; + float min; + + for ( i = 0, j; i <= numRows - 2; i++ ) { + j = i; + min = eigenValues[j]; + for ( k = i + 1; k < numRows; k++ ) { + if ( eigenValues[k] < min ) { + j = k; + min = eigenValues[j]; + } + } + if ( j != i ) { + eigenValues.SwapElements( i, j ); + SwapColumns( i, j ); + } + } +} + +/* +============ +idMatX::Eigen_SortDecreasing +============ +*/ +void idMatX::Eigen_SortDecreasing( idVecX &eigenValues ) { + int i, j, k; + float max; + + for ( i = 0, j; i <= numRows - 2; i++ ) { + j = i; + max = eigenValues[j]; + for ( k = i + 1; k < numRows; k++ ) { + if ( eigenValues[k] > max ) { + j = k; + max = eigenValues[j]; + } + } + if ( j != i ) { + eigenValues.SwapElements( i, j ); + SwapColumns( i, j ); + } + } +} + +/* +============ +idMatX::DeterminantGeneric +============ +*/ +float idMatX::DeterminantGeneric( void ) const { + int *index; + float det; + idMatX tmp; + + index = (int *) _alloca16( numRows * sizeof( int ) ); + tmp.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + tmp = *this; + + if ( !tmp.LU_Factor( index, &det ) ) { + return 0.0f; + } + + return det; +} + +/* +============ +idMatX::InverseSelfGeneric +============ +*/ +bool idMatX::InverseSelfGeneric( void ) { + int i, j, *index; + idMatX tmp; + idVecX x, b; + + index = (int *) _alloca16( numRows * sizeof( int ) ); + tmp.SetData( numRows, numColumns, MATX_ALLOCA( numRows * numColumns ) ); + tmp = *this; + + if ( !tmp.LU_Factor( index ) ) { + return false; + } + + x.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.SetData( numRows, VECX_ALLOCA( numRows ) ); + b.Zero(); + + for ( i = 0; i < numRows; i++ ) { + + b[i] = 1.0f; + tmp.LU_Solve( x, b, index ); + for ( j = 0; j < numRows; j++ ) { + (*this)[j][i] = x[j]; + } + b[i] = 0.0f; + } + return true; +} + +/* +============ +idMatX::Test +============ +*/ +void idMatX::Test( void ) { + idMatX original, m1, m2, m3, q1, q2, r1, r2; + idVecX v, w, u, c, d; + int offset, size, *index1, *index2; + + size = 6; + original.Random( size, size, 0 ); + original = original * original.Transpose(); + + index1 = (int *) _alloca16( ( size + 1 ) * sizeof( index1[0] ) ); + index2 = (int *) _alloca16( ( size + 1 ) * sizeof( index2[0] ) ); + + /* + idMatX::LowerTriangularInverse + */ + + m1 = original; + m1.ClearUpperTriangle(); + m2 = m1; + + m2.InverseSelf(); + m1.LowerTriangularInverse(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LowerTriangularInverse failed" ); + } + + /* + idMatX::UpperTriangularInverse + */ + + m1 = original; + m1.ClearLowerTriangle(); + m2 = m1; + + m2.InverseSelf(); + m1.UpperTriangularInverse(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::UpperTriangularInverse failed" ); + } + + /* + idMatX::Inverse_GaussJordan + */ + + m1 = original; + + m1.Inverse_GaussJordan(); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_GaussJordan failed" ); + } + + /* + idMatX::Inverse_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 1 ); + v.Random( size, 2 ); + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateRankOne( v, w, 1.0f ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateRankOne failed" ); + } + + /* + idMatX::Inverse_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateRowColumn( v, w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateRowColumn failed" ); + } + } + + /* + idMatX::Inverse_UpdateIncrement + */ + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_Increment( v, w ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateIncrement( v, w ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateIncrement failed" ); + } + + /* + idMatX::Inverse_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // invert m1 + m1.Inverse_GaussJordan(); + + // modify and invert m2 + m2.Update_Decrement( offset ); + if ( !m2.Inverse_GaussJordan() ) { + assert( 0 ); + } + + // update inverse of m1 + m1.Inverse_UpdateDecrement( v, w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Inverse_UpdateDecrement failed" ); + } + } + + /* + idMatX::LU_Factor + */ + + m1 = original; + + m1.LU_Factor( NULL ); // no pivoting + m1.LU_UnpackFactors( m2, m3 ); + m1 = m2 * m3; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_Factor failed" ); + } + + /* + idMatX::LU_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 1 ); + v.Random( size, 2 ); + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update factored m1 + m1.LU_UpdateRankOne( v, w, 1.0f, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateRankOne failed" ); + } + + /* + idMatX::LU_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update m1 + m1.LU_UpdateRowColumn( v, w, offset, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateRowColumn failed" ); + } + } + + /* + idMatX::LU_UpdateIncrement + */ + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_Increment( v, w ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + // update factored m1 + m1.LU_UpdateIncrement( v, w, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateIncrement failed" ); + } + + /* + idMatX::LU_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // factor m1 + m1.LU_Factor( index1 ); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.LU_Factor( index2 ) ) { + assert( 0 ); + } + m2.LU_MultiplyFactors( m3, index2 ); + m2 = m3; + + u.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + u[i] = original[index1[offset]][i]; + } + + // update factors of m1 + m1.LU_UpdateDecrement( v, w, u, offset, index1 ); + m1.LU_MultiplyFactors( m3, index1 ); + m1 = m3; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LU_UpdateDecrement failed" ); + } + } + + /* + idMatX::LU_Inverse + */ + + m2 = original; + + m2.LU_Factor( NULL ); + m2.LU_Inverse( m1, NULL ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::LU_Inverse failed" ); + } + + /* + idMatX::QR_Factor + */ + + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + m1 = q1 * r1; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_Factor failed" ); + } + + /* + idMatX::QR_UpdateRankOne + */ + + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + v = w; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_RankOne( v, w, 1.0f ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factored m1 + q1.QR_UpdateRankOne( r1, v, w, 1.0f ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateRankOne failed" ); + } + + /* + idMatX::QR_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + c.SetSize( size ); + d.SetSize( size ); + + m1 = original; + m2 = original; + + v.Random( size, 1 ); + w.Random( size, 2 ); + w[offset] = 0.0f; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_RowColumn( v, w, offset ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update m1 + q1.QR_UpdateRowColumn( r1, v, w, offset ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateRowColumn failed" ); + } + } + + /* + idMatX::QR_UpdateIncrement + */ + + c.SetSize( size+1 ); + d.SetSize( size+1 ); + + m1 = original; + m2 = original; + + v.Random( size + 1, 1 ); + w.Random( size + 1, 2 ); + w[size] = 0.0f; + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_Increment( v, w ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factored m1 + q1.QR_UpdateIncrement( r1, v, w ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateIncrement failed" ); + } + + /* + idMatX::QR_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + c.SetSize( size+1 ); + d.SetSize( size+1 ); + + m1 = original; + m2 = original; + + v.SetSize( 6 ); + w.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + w[i] = original[offset][i]; + } + + // factor m1 + m1.QR_Factor( c, d ); + m1.QR_UnpackFactors( q1, r1, c, d ); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.QR_Factor( c, d ) ) { + assert( 0 ); + } + m2.QR_UnpackFactors( q2, r2, c, d ); + m2 = q2 * r2; + + // update factors of m1 + q1.QR_UpdateDecrement( r1, v, w, offset ); + m1 = q1 * r1; + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::QR_UpdateDecrement failed" ); + } + } + + /* + idMatX::QR_Inverse + */ + + m2 = original; + + m2.QR_Factor( c, d ); + m2.QR_Inverse( m1, c, d ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::QR_Inverse failed" ); + } + + /* + idMatX::SVD_Factor + */ + + m1 = original; + m3.Zero( size, size ); + w.Zero( size ); + + m1.SVD_Factor( w, m3 ); + m2.Diag( w ); + m3.TransposeSelf(); + m1 = m1 * m2 * m3; + + if ( !original.Compare( m1, 1e-4f ) ) { + idLib::common->Warning( "idMatX::SVD_Factor failed" ); + } + + /* + idMatX::SVD_Inverse + */ + + m2 = original; + + m2.SVD_Factor( w, m3 ); + m2.SVD_Inverse( m1, w, m3 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::SVD_Inverse failed" ); + } + + /* + idMatX::Cholesky_Factor + */ + + m1 = original; + + m1.Cholesky_Factor(); + m1.Cholesky_MultiplyFactors( m2 ); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_Factor failed" ); + } + + /* + idMatX::Cholesky_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.Cholesky_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RankOneSymmetric( w, 1.0f ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update factored m1 + m1.Cholesky_UpdateRankOne( w, 1.0f, 0 ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateRankOne failed" ); + } + + /* + idMatX::Cholesky_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + // factor m1 + m1.Cholesky_Factor(); + m1.ClearUpperTriangle(); + + int pdtable[] = { 1, 0, 1, 0, 0, 0 }; + w.Random( size, pdtable[offset] ); + w *= 0.1f; + + // modify and factor m2 + m2.Update_RowColumnSymmetric( w, offset ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update m1 + m1.Cholesky_UpdateRowColumn( w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateRowColumn failed" ); + } + } + + /* + idMatX::Cholesky_UpdateIncrement + */ + + m1.Random( size + 1, size + 1, 0 ); + m3 = m1 * m1.Transpose(); + + m1.SquareSubMatrix( m3, size ); + m2 = m1; + + w.SetSize( size + 1 ); + for ( int i = 0; i < size + 1; i++ ) { + w[i] = m3[size][i]; + } + + // factor m1 + m1.Cholesky_Factor(); + + // modify and factor m2 + m2.Update_IncrementSymmetric( w ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + + // update factored m1 + m1.Cholesky_UpdateIncrement( w ); + + m1.ClearUpperTriangle(); + m2.ClearUpperTriangle(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateIncrement failed" ); + } + + /* + idMatX::Cholesky_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset += size - 1 ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + } + + // factor m1 + m1.Cholesky_Factor(); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.Cholesky_Factor() ) { + assert( 0 ); + } + + // update factors of m1 + m1.Cholesky_UpdateDecrement( v, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::Cholesky_UpdateDecrement failed" ); + } + } + + /* + idMatX::Cholesky_Inverse + */ + + m2 = original; + + m2.Cholesky_Factor(); + m2.Cholesky_Inverse( m1 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::Cholesky_Inverse failed" ); + } + + /* + idMatX::LDLT_Factor + */ + + m1 = original; + + m1.LDLT_Factor(); + m1.LDLT_MultiplyFactors( m2 ); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Factor failed" ); + } + + m1.LDLT_UnpackFactors( m2, m3 ); + m2 = m2 * m3 * m2.Transpose(); + + if ( !original.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Factor failed" ); + } + + /* + idMatX::LDLT_UpdateRankOne + */ + + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.LDLT_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RankOneSymmetric( w, 1.0f ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update factored m1 + m1.LDLT_UpdateRankOne( w, 1.0f, 0 ); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateRankOne failed" ); + } + + /* + idMatX::LDLT_UpdateRowColumn + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + w.Random( size, 0 ); + + // factor m1 + m1.LDLT_Factor(); + m1.ClearUpperTriangle(); + + // modify and factor m2 + m2.Update_RowColumnSymmetric( w, offset ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + m2.ClearUpperTriangle(); + + // update m1 + m1.LDLT_UpdateRowColumn( w, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateRowColumn failed" ); + } + } + + /* + idMatX::LDLT_UpdateIncrement + */ + + m1.Random( size + 1, size + 1, 0 ); + m3 = m1 * m1.Transpose(); + + m1.SquareSubMatrix( m3, size ); + m2 = m1; + + w.SetSize( size + 1 ); + for ( int i = 0; i < size + 1; i++ ) { + w[i] = m3[size][i]; + } + + // factor m1 + m1.LDLT_Factor(); + + // modify and factor m2 + m2.Update_IncrementSymmetric( w ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + + // update factored m1 + m1.LDLT_UpdateIncrement( w ); + + m1.ClearUpperTriangle(); + m2.ClearUpperTriangle(); + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateIncrement failed" ); + } + + /* + idMatX::LDLT_UpdateDecrement + */ + + for ( offset = 0; offset < size; offset++ ) { + m1 = original; + m2 = original; + + v.SetSize( 6 ); + for ( int i = 0; i < size; i++ ) { + v[i] = original[i][offset]; + } + + // factor m1 + m1.LDLT_Factor(); + + // modify and factor m2 + m2.Update_Decrement( offset ); + if ( !m2.LDLT_Factor() ) { + assert( 0 ); + } + + // update factors of m1 + m1.LDLT_UpdateDecrement( v, offset ); + + if ( !m1.Compare( m2, 1e-3f ) ) { + idLib::common->Warning( "idMatX::LDLT_UpdateDecrement failed" ); + } + } + + /* + idMatX::LDLT_Inverse + */ + + m2 = original; + + m2.LDLT_Factor(); + m2.LDLT_Inverse( m1 ); + m1 *= original; + + if ( !m1.IsIdentity( 1e-4f ) ) { + idLib::common->Warning( "idMatX::LDLT_Inverse failed" ); + } + + /* + idMatX::Eigen_SolveSymmetricTriDiagonal + */ + + m3 = original; + m3.TriDiagonal_ClearTriangles(); + m1 = m3; + + v.SetSize( size ); + + m1.Eigen_SolveSymmetricTriDiagonal( v ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_SolveSymmetricTriDiagonal failed" ); + } + + /* + idMatX::Eigen_SolveSymmetric + */ + + m3 = original; + m1 = m3; + + v.SetSize( size ); + + m1.Eigen_SolveSymmetric( v ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_SolveSymmetric failed" ); + } + + /* + idMatX::Eigen_Solve + */ + + m3 = original; + m1 = m3; + + v.SetSize( size ); + w.SetSize( size ); + + m1.Eigen_Solve( v, w ); + + m3.TransposeMultiply( m2, m1 ); + + for ( int i = 0; i < size; i++ ) { + for ( int j = 0; j < size; j++ ) { + m1[i][j] *= v[j]; + } + } + + if ( !m1.Compare( m2, 1e-4f ) ) { + idLib::common->Warning( "idMatX::Eigen_Solve failed" ); + } +} diff --git a/src/idlib/math/Matrix.h b/src/idlib/math/Matrix.h new file mode 100644 index 0000000..c7ce236 --- /dev/null +++ b/src/idlib/math/Matrix.h @@ -0,0 +1,3039 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_MATRIX_H__ +#define __MATH_MATRIX_H__ + +/* +=============================================================================== + + Matrix classes, all matrices are row-major except idMat3 + +=============================================================================== +*/ + +#define MATRIX_INVERSE_EPSILON 1e-14 +#define MATRIX_EPSILON 1e-6 + +class idAngles; +class idQuat; +class idCQuat; +class idRotation; +class idMat4; + +//=============================================================== +// +// idMat2 - 2x2 matrix +// +//=============================================================== + +class idMat2 { +public: + idMat2( void ); + explicit idMat2( const idVec2 &x, const idVec2 &y ); + explicit idMat2( const float xx, const float xy, const float yx, const float yy ); + explicit idMat2( const float src[ 2 ][ 2 ] ); + + const idVec2 & operator[]( int index ) const; + idVec2 & operator[]( int index ); + idMat2 operator-() const; + idMat2 operator*( const float a ) const; + idVec2 operator*( const idVec2 &vec ) const; + idMat2 operator*( const idMat2 &a ) const; + idMat2 operator+( const idMat2 &a ) const; + idMat2 operator-( const idMat2 &a ) const; + idMat2 & operator*=( const float a ); + idMat2 & operator*=( const idMat2 &a ); + idMat2 & operator+=( const idMat2 &a ); + idMat2 & operator-=( const idMat2 &a ); + + friend idMat2 operator*( const float a, const idMat2 &mat ); + friend idVec2 operator*( const idVec2 &vec, const idMat2 &mat ); + friend idVec2 & operator*=( idVec2 &vec, const idMat2 &mat ); + + bool Compare( const idMat2 &a ) const; // exact compare, no epsilon + bool Compare( const idMat2 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat2 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat2 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + float Trace( void ) const; + float Determinant( void ) const; + idMat2 Transpose( void ) const; // returns transpose + idMat2 & TransposeSelf( void ); + idMat2 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat2 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec2 mat[ 2 ]; +}; + +extern idMat2 mat2_zero; +extern idMat2 mat2_identity; +#define mat2_default mat2_identity + +ID_INLINE idMat2::idMat2( void ) { +} + +ID_INLINE idMat2::idMat2( const idVec2 &x, const idVec2 &y ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; +} + +ID_INLINE idMat2::idMat2( const float xx, const float xy, const float yx, const float yy ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; +} + +ID_INLINE idMat2::idMat2( const float src[ 2 ][ 2 ] ) { + memcpy( mat, src, 2 * 2 * sizeof( float ) ); +} + +ID_INLINE const idVec2 &idMat2::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 2 ) ); + return mat[ index ]; +} + +ID_INLINE idVec2 &idMat2::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 2 ) ); + return mat[ index ]; +} + +ID_INLINE idMat2 idMat2::operator-() const { + return idMat2( -mat[0][0], -mat[0][1], + -mat[1][0], -mat[1][1] ); +} + +ID_INLINE idVec2 idMat2::operator*( const idVec2 &vec ) const { + return idVec2( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y ); +} + +ID_INLINE idMat2 idMat2::operator*( const idMat2 &a ) const { + return idMat2( + mat[0].x * a[0].x + mat[0].y * a[1].x, + mat[0].x * a[0].y + mat[0].y * a[1].y, + mat[1].x * a[0].x + mat[1].y * a[1].x, + mat[1].x * a[0].y + mat[1].y * a[1].y ); +} + +ID_INLINE idMat2 idMat2::operator*( const float a ) const { + return idMat2( + mat[0].x * a, mat[0].y * a, + mat[1].x * a, mat[1].y * a ); +} + +ID_INLINE idMat2 idMat2::operator+( const idMat2 &a ) const { + return idMat2( + mat[0].x + a[0].x, mat[0].y + a[0].y, + mat[1].x + a[1].x, mat[1].y + a[1].y ); +} + +ID_INLINE idMat2 idMat2::operator-( const idMat2 &a ) const { + return idMat2( + mat[0].x - a[0].x, mat[0].y - a[0].y, + mat[1].x - a[1].x, mat[1].y - a[1].y ); +} + +ID_INLINE idMat2 &idMat2::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; + mat[1].x *= a; mat[1].y *= a; + + return *this; +} + +ID_INLINE idMat2 &idMat2::operator*=( const idMat2 &a ) { + float x, y; + x = mat[0].x; y = mat[0].y; + mat[0].x = x * a[0].x + y * a[1].x; + mat[0].y = x * a[0].y + y * a[1].y; + x = mat[1].x; y = mat[1].y; + mat[1].x = x * a[0].x + y * a[1].x; + mat[1].y = x * a[0].y + y * a[1].y; + return *this; +} + +ID_INLINE idMat2 &idMat2::operator+=( const idMat2 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; + mat[1].x += a[1].x; mat[1].y += a[1].y; + + return *this; +} + +ID_INLINE idMat2 &idMat2::operator-=( const idMat2 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; + + return *this; +} + +ID_INLINE idVec2 operator*( const idVec2 &vec, const idMat2 &mat ) { + return mat * vec; +} + +ID_INLINE idMat2 operator*( const float a, idMat2 const &mat ) { + return mat * a; +} + +ID_INLINE idVec2 &operator*=( idVec2 &vec, const idMat2 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat2::Compare( const idMat2 &a ) const { + if ( mat[0].Compare( a[0] ) && + mat[1].Compare( a[1] ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat2::Compare( const idMat2 &a, const float epsilon ) const { + if ( mat[0].Compare( a[0], epsilon ) && + mat[1].Compare( a[1], epsilon ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat2::operator==( const idMat2 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat2::operator!=( const idMat2 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat2::Zero( void ) { + mat[0].Zero(); + mat[1].Zero(); +} + +ID_INLINE void idMat2::Identity( void ) { + *this = mat2_identity; +} + +ID_INLINE bool idMat2::IsIdentity( const float epsilon ) const { + return Compare( mat2_identity, epsilon ); +} + +ID_INLINE bool idMat2::IsSymmetric( const float epsilon ) const { + return ( idMath::Fabs( mat[0][1] - mat[1][0] ) < epsilon ); +} + +ID_INLINE bool idMat2::IsDiagonal( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] ) > epsilon || + idMath::Fabs( mat[1][0] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE float idMat2::Trace( void ) const { + return ( mat[0][0] + mat[1][1] ); +} + +ID_INLINE float idMat2::Determinant( void ) const { + return mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; +} + +ID_INLINE idMat2 idMat2::Transpose( void ) const { + return idMat2( mat[0][0], mat[1][0], + mat[0][1], mat[1][1] ); +} + +ID_INLINE idMat2 &idMat2::TransposeSelf( void ) { + float tmp; + + tmp = mat[0][1]; + mat[0][1] = mat[1][0]; + mat[1][0] = tmp; + + return *this; +} + +ID_INLINE idMat2 idMat2::Inverse( void ) const { + idMat2 invMat; + + invMat = *this; +#ifdef _DEBUG + assert( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat2 idMat2::InverseFast( void ) const { + idMat2 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE int idMat2::GetDimension( void ) const { + return 4; +} + +ID_INLINE const float *idMat2::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat2::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat3 - 3x3 matrix +// +// NOTE: matrix is column-major +// +//=============================================================== + +class idMat3 { +public: + idMat3( void ); + explicit idMat3( const idVec3 &x, const idVec3 &y, const idVec3 &z ); + explicit idMat3( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ); + explicit idMat3( const float src[ 3 ][ 3 ] ); + + const idVec3 & operator[]( int index ) const; + idVec3 & operator[]( int index ); + idMat3 operator-() const; + idMat3 operator*( const float a ) const; + idVec3 operator*( const idVec3 &vec ) const; + idMat3 operator*( const idMat3 &a ) const; +// RAVEN BEGIN +// jscott: multiply by the transpose + idVec3 operator/( const idVec3 &vec ) const; + idMat3 operator/( const idMat3 &a ) const; +// RAVEN END + idMat3 operator+( const idMat3 &a ) const; + idMat3 operator-( const idMat3 &a ) const; + idMat3 & operator*=( const float a ); + idMat3 & operator*=( const idMat3 &a ); + idMat3 & operator+=( const idMat3 &a ); + idMat3 & operator-=( const idMat3 &a ); + + friend idMat3 operator*( const float a, const idMat3 &mat ); + friend idVec3 operator*( const idVec3 &vec, const idMat3 &mat ); + friend idVec3 & operator*=( idVec3 &vec, const idMat3 &mat ); + + bool Compare( const idMat3 &a ) const; // exact compare, no epsilon + bool Compare( const idMat3 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat3 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat3 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsRotated( void ) const; + + void ProjectVector( const idVec3 &src, idVec3 &dst ) const; + void UnprojectVector( const idVec3 &src, idVec3 &dst ) const; + + bool FixDegeneracies( void ); // fix degenerate axial cases + bool FixDenormals( void ); // change tiny numbers to zero + + float Trace( void ) const; + float Determinant( void ) const; + idMat3 OrthoNormalize( void ) const; + idMat3 & OrthoNormalizeSelf( void ); + idMat3 Transpose( void ) const; // returns transpose + idMat3 & TransposeSelf( void ); + idMat3 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat3 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + idMat3 TransposeMultiply( const idMat3 &b ) const; + + idMat3 InertiaTranslate( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ) const; + idMat3 & InertiaTranslateSelf( const float mass, const idVec3 ¢erOfMass, const idVec3 &translation ); + idMat3 InertiaRotate( const idMat3 &rotation ) const; + idMat3 & InertiaRotateSelf( const idMat3 &rotation ); + + int GetDimension( void ) const; +// RAVEN BEGIN +// abahr: + int GetVec3Dimension( void ) const; +// RAVEN END + + idAngles ToAngles( void ) const; +// RAVEN BEGIN + void RotateAbsolute(int whichAxis, float howManyDegrees); + void RotateRelative(int whichAxis, float howManyDegrees); + void RotateArbitrary(const idVec3 &rotAxis, float howManyDegrees); +// RAVEN END + idQuat ToQuat( void ) const; + idCQuat ToCQuat( void ) const; + idRotation ToRotation( void ) const; + idMat4 ToMat4( void ) const; + idVec3 ToAngularVelocity( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + friend void TransposeMultiply( const idMat3 &inv, const idMat3 &b, idMat3 &dst ); + friend idMat3 SkewSymmetric( idVec3 const &src ); + +private: + idVec3 mat[ 3 ]; +}; + +extern idMat3 mat3_zero; +extern idMat3 mat3_identity; +#define mat3_default mat3_identity + +ID_INLINE idMat3::idMat3( void ) { +} + +ID_INLINE idMat3::idMat3( const idVec3 &x, const idVec3 &y, const idVec3 &z ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; mat[ 0 ].z = x.z; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; mat[ 1 ].z = y.z; + mat[ 2 ].x = z.x; mat[ 2 ].y = z.y; mat[ 2 ].z = z.z; +} + +ID_INLINE idMat3::idMat3( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; mat[ 0 ].z = xz; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; mat[ 1 ].z = yz; + mat[ 2 ].x = zx; mat[ 2 ].y = zy; mat[ 2 ].z = zz; +} + +ID_INLINE idMat3::idMat3( const float src[ 3 ][ 3 ] ) { + memcpy( mat, src, 3 * 3 * sizeof( float ) ); +} + +ID_INLINE const idVec3 &idMat3::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3 &idMat3::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idMat3 idMat3::operator-() const { + return idMat3( -mat[0][0], -mat[0][1], -mat[0][2], + -mat[1][0], -mat[1][1], -mat[1][2], + -mat[2][0], -mat[2][1], -mat[2][2] ); +} + +ID_INLINE idVec3 idMat3::operator*( const idVec3 &vec ) const { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE idMat3 idMat3::operator*( const idMat3 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat3 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ]; + dstPtr++; + } + m1Ptr += 3; + } + return dst; +} + +// RAVEN BEGIN +// jscott: divide is overridden to multiply by transpose +ID_INLINE idVec3 idMat3::operator/( const idVec3 &vec ) const +{ + return( idVec3( + mat[0].x * vec.x + mat[0].y * vec.y + mat[0].z * vec.z, + mat[1].x * vec.x + mat[1].y * vec.y + mat[1].z * vec.z, + mat[2].x * vec.x + mat[2].y * vec.y + mat[2].z * vec.z ) ); +} + +ID_INLINE idMat3 idMat3::operator/( const idMat3 &a ) const +{ + idMat3 dst; + + dst[0].x = mat[0].x * a.mat[0].x + mat[0].y * a.mat[0].y + mat[0].z * a.mat[0].z; + dst[0].y = mat[0].x * a.mat[1].x + mat[0].y * a.mat[1].y + mat[0].z * a.mat[1].z; + dst[0].z = mat[0].x * a.mat[2].x + mat[0].y * a.mat[2].y + mat[0].z * a.mat[2].z; + + dst[1].x = mat[1].x * a.mat[0].x + mat[1].y * a.mat[0].y + mat[1].z * a.mat[0].z; + dst[1].y = mat[1].x * a.mat[1].x + mat[1].y * a.mat[1].y + mat[1].z * a.mat[1].z; + dst[1].z = mat[1].x * a.mat[2].x + mat[1].y * a.mat[2].y + mat[1].z * a.mat[2].z; + + dst[2].x = mat[2].x * a.mat[0].x + mat[2].y * a.mat[0].y + mat[2].z * a.mat[0].z; + dst[2].y = mat[2].x * a.mat[1].x + mat[2].y * a.mat[1].y + mat[2].z * a.mat[1].z; + dst[2].z = mat[2].x * a.mat[2].x + mat[2].y * a.mat[2].y + mat[2].z * a.mat[2].z; + + return( dst ); +} +// RAVEN END + +ID_INLINE idMat3 idMat3::operator*( const float a ) const { + return idMat3( + mat[0].x * a, mat[0].y * a, mat[0].z * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a ); +} + +ID_INLINE idMat3 idMat3::operator+( const idMat3 &a ) const { + return idMat3( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z ); +} + +ID_INLINE idMat3 idMat3::operator-( const idMat3 &a ) const { + return idMat3( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z ); +} + +ID_INLINE idMat3 &idMat3::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; + + return *this; +} + +ID_INLINE idMat3 &idMat3::operator*=( const idMat3 &a ) { + int i, j; + const float *m2Ptr; + float *m1Ptr, dst[3]; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + dst[j] = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ]; + } + m1Ptr[0] = dst[0]; m1Ptr[1] = dst[1]; m1Ptr[2] = dst[2]; + m1Ptr += 3; + } + return *this; +} + +ID_INLINE idMat3 &idMat3::operator+=( const idMat3 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; + + return *this; +} + +ID_INLINE idMat3 &idMat3::operator-=( const idMat3 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; + + return *this; +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const idMat3 &mat ) { + return mat * vec; +} + +ID_INLINE idMat3 operator*( const float a, const idMat3 &mat ) { + return mat * a; +} + +ID_INLINE idVec3 &operator*=( idVec3 &vec, const idMat3 &mat ) { + float x = mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z; + float y = mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z; + vec.z = mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z; + vec.x = x; + vec.y = y; + return vec; +} + +ID_INLINE bool idMat3::Compare( const idMat3 &a ) const { + if ( mat[0].Compare( a[0] ) && + mat[1].Compare( a[1] ) && + mat[2].Compare( a[2] ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat3::Compare( const idMat3 &a, const float epsilon ) const { + if ( mat[0].Compare( a[0], epsilon ) && + mat[1].Compare( a[1], epsilon ) && + mat[2].Compare( a[2], epsilon ) ) { + return true; + } + return false; +} + +ID_INLINE bool idMat3::operator==( const idMat3 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat3::operator!=( const idMat3 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat3::Zero( void ) { + memset( mat, 0, sizeof( idMat3 ) ); +} + +ID_INLINE void idMat3::Identity( void ) { + *this = mat3_identity; +} + +ID_INLINE bool idMat3::IsIdentity( const float epsilon ) const { + return Compare( mat3_identity, epsilon ); +} + +ID_INLINE bool idMat3::IsSymmetric( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] - mat[1][0] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( mat[0][2] - mat[2][0] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( mat[1][2] - mat[2][1] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idMat3::IsDiagonal( const float epsilon ) const { + if ( idMath::Fabs( mat[0][1] ) > epsilon || + idMath::Fabs( mat[0][2] ) > epsilon || + idMath::Fabs( mat[1][0] ) > epsilon || + idMath::Fabs( mat[1][2] ) > epsilon || + idMath::Fabs( mat[2][0] ) > epsilon || + idMath::Fabs( mat[2][1] ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idMat3::IsRotated( void ) const { + // NOTE: assumes the 3x3 matrix is orthonormal + return ( mat[0][0] != 1.0f || mat[1][1] != 1.0f || mat[2][2] != 1.0f ); +} + +ID_INLINE void idMat3::ProjectVector( const idVec3 &src, idVec3 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; +} + +ID_INLINE void idMat3::UnprojectVector( const idVec3 &src, idVec3 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z; +} + +ID_INLINE bool idMat3::FixDegeneracies( void ) { + bool r = mat[0].FixDegenerateNormal(); + r |= mat[1].FixDegenerateNormal(); + r |= mat[2].FixDegenerateNormal(); + return r; +} + +ID_INLINE bool idMat3::FixDenormals( void ) { + bool r = mat[0].FixDenormals(); + r |= mat[1].FixDenormals(); + r |= mat[2].FixDenormals(); + return r; +} + +ID_INLINE float idMat3::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] ); +} + +ID_INLINE idMat3 idMat3::OrthoNormalize( void ) const { + idMat3 ortho; + + ortho = *this; + ortho[ 0 ].Normalize(); + ortho[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + ortho[ 2 ].Normalize(); + ortho[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + ortho[ 1 ].Normalize(); + return ortho; +} + +ID_INLINE idMat3 &idMat3::OrthoNormalizeSelf( void ) { + mat[ 0 ].Normalize(); + mat[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + mat[ 2 ].Normalize(); + mat[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + mat[ 1 ].Normalize(); + return *this; +} + +ID_INLINE idMat3 idMat3::Transpose( void ) const { + return idMat3( mat[0][0], mat[1][0], mat[2][0], + mat[0][1], mat[1][1], mat[2][1], + mat[0][2], mat[1][2], mat[2][2] ); +} + +ID_INLINE idMat3 &idMat3::TransposeSelf( void ) { + float tmp0, tmp1, tmp2; + + tmp0 = mat[0][1]; + mat[0][1] = mat[1][0]; + mat[1][0] = tmp0; + tmp1 = mat[0][2]; + mat[0][2] = mat[2][0]; + mat[2][0] = tmp1; + tmp2 = mat[1][2]; + mat[1][2] = mat[2][1]; + mat[2][1] = tmp2; + + return *this; +} + +ID_INLINE idMat3 idMat3::Inverse( void ) const { + idMat3 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat3 idMat3::InverseFast( void ) const { + idMat3 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE idMat3 idMat3::TransposeMultiply( const idMat3 &b ) const { + return idMat3( mat[0].x * b[0].x + mat[1].x * b[1].x + mat[2].x * b[2].x, + mat[0].x * b[0].y + mat[1].x * b[1].y + mat[2].x * b[2].y, + mat[0].x * b[0].z + mat[1].x * b[1].z + mat[2].x * b[2].z, + mat[0].y * b[0].x + mat[1].y * b[1].x + mat[2].y * b[2].x, + mat[0].y * b[0].y + mat[1].y * b[1].y + mat[2].y * b[2].y, + mat[0].y * b[0].z + mat[1].y * b[1].z + mat[2].y * b[2].z, + mat[0].z * b[0].x + mat[1].z * b[1].x + mat[2].z * b[2].x, + mat[0].z * b[0].y + mat[1].z * b[1].y + mat[2].z * b[2].y, + mat[0].z * b[0].z + mat[1].z * b[1].z + mat[2].z * b[2].z ); +} + +ID_INLINE void TransposeMultiply( const idMat3 &transpose, const idMat3 &b, idMat3 &dst ) { + dst[0].x = transpose[0].x * b[0].x + transpose[1].x * b[1].x + transpose[2].x * b[2].x; + dst[0].y = transpose[0].x * b[0].y + transpose[1].x * b[1].y + transpose[2].x * b[2].y; + dst[0].z = transpose[0].x * b[0].z + transpose[1].x * b[1].z + transpose[2].x * b[2].z; + dst[1].x = transpose[0].y * b[0].x + transpose[1].y * b[1].x + transpose[2].y * b[2].x; + dst[1].y = transpose[0].y * b[0].y + transpose[1].y * b[1].y + transpose[2].y * b[2].y; + dst[1].z = transpose[0].y * b[0].z + transpose[1].y * b[1].z + transpose[2].y * b[2].z; + dst[2].x = transpose[0].z * b[0].x + transpose[1].z * b[1].x + transpose[2].z * b[2].x; + dst[2].y = transpose[0].z * b[0].y + transpose[1].z * b[1].y + transpose[2].z * b[2].y; + dst[2].z = transpose[0].z * b[0].z + transpose[1].z * b[1].z + transpose[2].z * b[2].z; +} + +ID_INLINE idMat3 SkewSymmetric( idVec3 const &src ) { + return idMat3( 0.0f, -src.z, src.y, src.z, 0.0f, -src.x, -src.y, src.x, 0.0f ); +} + +ID_INLINE int idMat3::GetDimension( void ) const { + return 9; +} + +// RAVEN BEGIN +// abahr: made version for when getting vectors +ID_INLINE int idMat3::GetVec3Dimension( void ) const { + return 3; +} + +ID_INLINE const float *idMat3::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat3::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat4 - 4x4 matrix +// +//=============================================================== + +class idMat4 { +public: + idMat4( void ); + explicit idMat4( const idVec4 &x, const idVec4 &y, const idVec4 &z, const idVec4 &w ); + explicit idMat4(const float xx, const float xy, const float xz, const float xw, + const float yx, const float yy, const float yz, const float yw, + const float zx, const float zy, const float zz, const float zw, + const float wx, const float wy, const float wz, const float ww ); + explicit idMat4( const idMat3 &rotation, const idVec3 &translation ); + explicit idMat4( const float src[ 4 ][ 4 ] ); + + const idVec4 & operator[]( int index ) const; + idVec4 & operator[]( int index ); + idMat4 operator*( const float a ) const; + idVec4 operator*( const idVec4 &vec ) const; + idVec3 operator*( const idVec3 &vec ) const; + idMat4 operator*( const idMat4 &a ) const; + idMat4 operator+( const idMat4 &a ) const; + idMat4 operator-( const idMat4 &a ) const; + idMat4 & operator*=( const float a ); + idMat4 & operator*=( const idMat4 &a ); + idMat4 & operator+=( const idMat4 &a ); + idMat4 & operator-=( const idMat4 &a ); + + friend idMat4 operator*( const float a, const idMat4 &mat ); + friend idVec4 operator*( const idVec4 &vec, const idMat4 &mat ); + friend idVec3 operator*( const idVec3 &vec, const idMat4 &mat ); + friend idVec4 & operator*=( idVec4 &vec, const idMat4 &mat ); + friend idVec3 & operator*=( idVec3 &vec, const idMat4 &mat ); + + bool Compare( const idMat4 &a ) const; // exact compare, no epsilon + bool Compare( const idMat4 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat4 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat4 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsRotated( void ) const; + + void ProjectVector( const idVec4 &src, idVec4 &dst ) const; + void UnprojectVector( const idVec4 &src, idVec4 &dst ) const; + + float Trace( void ) const; + float Determinant( void ) const; + idMat4 Transpose( void ) const; // returns transpose + idMat4 & TransposeSelf( void ); + idMat4 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat4 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + idMat4 TransposeMultiply( const idMat4 &b ) const; + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec4 mat[ 4 ]; +}; + +extern idMat4 mat4_zero; +extern idMat4 mat4_identity; +#define mat4_default mat4_identity + +ID_INLINE idMat4::idMat4( void ) { +} + +ID_INLINE idMat4::idMat4( const idVec4 &x, const idVec4 &y, const idVec4 &z, const idVec4 &w ) { + mat[ 0 ] = x; + mat[ 1 ] = y; + mat[ 2 ] = z; + mat[ 3 ] = w; +} + +ID_INLINE idMat4::idMat4( const float xx, const float xy, const float xz, const float xw, + const float yx, const float yy, const float yz, const float yw, + const float zx, const float zy, const float zz, const float zw, + const float wx, const float wy, const float wz, const float ww ) { + mat[0][0] = xx; mat[0][1] = xy; mat[0][2] = xz; mat[0][3] = xw; + mat[1][0] = yx; mat[1][1] = yy; mat[1][2] = yz; mat[1][3] = yw; + mat[2][0] = zx; mat[2][1] = zy; mat[2][2] = zz; mat[2][3] = zw; + mat[3][0] = wx; mat[3][1] = wy; mat[3][2] = wz; mat[3][3] = ww; +} + +ID_INLINE idMat4::idMat4( const idMat3 &rotation, const idVec3 &translation ) { + // NOTE: idMat3 is transposed because it is column-major + mat[ 0 ][ 0 ] = rotation[0][0]; + mat[ 0 ][ 1 ] = rotation[1][0]; + mat[ 0 ][ 2 ] = rotation[2][0]; + mat[ 0 ][ 3 ] = translation[0]; + mat[ 1 ][ 0 ] = rotation[0][1]; + mat[ 1 ][ 1 ] = rotation[1][1]; + mat[ 1 ][ 2 ] = rotation[2][1]; + mat[ 1 ][ 3 ] = translation[1]; + mat[ 2 ][ 0 ] = rotation[0][2]; + mat[ 2 ][ 1 ] = rotation[1][2]; + mat[ 2 ][ 2 ] = rotation[2][2]; + mat[ 2 ][ 3 ] = translation[2]; + mat[ 3 ][ 0 ] = 0.0f; + mat[ 3 ][ 1 ] = 0.0f; + mat[ 3 ][ 2 ] = 0.0f; + mat[ 3 ][ 3 ] = 1.0f; +} + +ID_INLINE idMat4::idMat4( const float src[ 4 ][ 4 ] ) { + memcpy( mat, src, 4 * 4 * sizeof( float ) ); +} + +ID_INLINE const idVec4 &idMat4::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 4 ) ); + return mat[ index ]; +} + +ID_INLINE idVec4 &idMat4::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 4 ) ); + return mat[ index ]; +} + +ID_INLINE idMat4 idMat4::operator*( const float a ) const { + return idMat4( + mat[0].x * a, mat[0].y * a, mat[0].z * a, mat[0].w * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, mat[1].w * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a, mat[2].w * a, + mat[3].x * a, mat[3].y * a, mat[3].z * a, mat[3].w * a ); +} + +ID_INLINE idVec4 idMat4::operator*( const idVec4 &vec ) const { + return idVec4( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w * vec.w, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w * vec.w, + mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w * vec.w, + mat[ 3 ].x * vec.x + mat[ 3 ].y * vec.y + mat[ 3 ].z * vec.z + mat[ 3 ].w * vec.w ); +} + +ID_INLINE idVec3 idMat4::operator*( const idVec3 &vec ) const { + float s = mat[ 3 ].x * vec.x + mat[ 3 ].y * vec.y + mat[ 3 ].z * vec.z + mat[ 3 ].w; + if ( s == 0.0f ) { + return idVec3( 0.0f, 0.0f, 0.0f ); + } + if ( s == 1.0f ) { + return idVec3( + mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w, + mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w, + mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w ); + } + else { + float invS = 1.0f / s; + return idVec3( + (mat[ 0 ].x * vec.x + mat[ 0 ].y * vec.y + mat[ 0 ].z * vec.z + mat[ 0 ].w) * invS, + (mat[ 1 ].x * vec.x + mat[ 1 ].y * vec.y + mat[ 1 ].z * vec.z + mat[ 1 ].w) * invS, + (mat[ 2 ].x * vec.x + mat[ 2 ].y * vec.y + mat[ 2 ].z * vec.z + mat[ 2 ].w) * invS ); + } +} + +ID_INLINE idMat4 idMat4::operator*( const idMat4 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat4 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 4; i++ ) { + for ( j = 0; j < 4; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 4 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 4 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 4 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 4 + j ]; + dstPtr++; + } + m1Ptr += 4; + } + return dst; +} + +ID_INLINE idMat4 idMat4::operator+( const idMat4 &a ) const { + return idMat4( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, mat[0].w + a[0].w, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, mat[1].w + a[1].w, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z, mat[2].w + a[2].w, + mat[3].x + a[3].x, mat[3].y + a[3].y, mat[3].z + a[3].z, mat[3].w + a[3].w ); +} + +ID_INLINE idMat4 idMat4::operator-( const idMat4 &a ) const { + return idMat4( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, mat[0].w - a[0].w, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, mat[1].w - a[1].w, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z, mat[2].w - a[2].w, + mat[3].x - a[3].x, mat[3].y - a[3].y, mat[3].z - a[3].z, mat[3].w - a[3].w ); +} + +ID_INLINE idMat4 &idMat4::operator*=( const float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; mat[0].w *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; mat[1].w *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; mat[2].w *= a; + mat[3].x *= a; mat[3].y *= a; mat[3].z *= a; mat[3].w *= a; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator*=( const idMat4 &a ) { + *this = (*this) * a; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator+=( const idMat4 &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; mat[0].w += a[0].w; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; mat[1].w += a[1].w; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; mat[2].w += a[2].w; + mat[3].x += a[3].x; mat[3].y += a[3].y; mat[3].z += a[3].z; mat[3].w += a[3].w; + return *this; +} + +ID_INLINE idMat4 &idMat4::operator-=( const idMat4 &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; mat[0].w -= a[0].w; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; mat[1].w -= a[1].w; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; mat[2].w -= a[2].w; + mat[3].x -= a[3].x; mat[3].y -= a[3].y; mat[3].z -= a[3].z; mat[3].w -= a[3].w; + return *this; +} + +ID_INLINE idMat4 operator*( const float a, const idMat4 &mat ) { + return mat * a; +} + +ID_INLINE idVec4 operator*( const idVec4 &vec, const idMat4 &mat ) { + return mat * vec; +} + +ID_INLINE idVec3 operator*( const idVec3 &vec, const idMat4 &mat ) { + return mat * vec; +} + +ID_INLINE idVec4 &operator*=( idVec4 &vec, const idMat4 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE idVec3 &operator*=( idVec3 &vec, const idMat4 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat4::Compare( const idMat4 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 4*4; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat4::Compare( const idMat4 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 4*4; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat4::operator==( const idMat4 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat4::operator!=( const idMat4 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat4::Zero( void ) { + memset( mat, 0, sizeof( idMat4 ) ); +} + +ID_INLINE void idMat4::Identity( void ) { + *this = mat4_identity; +} + +ID_INLINE bool idMat4::IsIdentity( const float epsilon ) const { + return Compare( mat4_identity, epsilon ); +} + +ID_INLINE bool idMat4::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 4; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat4::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 4; i++ ) { + for ( int j = 0; j < 4; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat4::IsRotated( void ) const { + if ( !mat[ 0 ][ 1 ] && !mat[ 0 ][ 2 ] && + !mat[ 1 ][ 0 ] && !mat[ 1 ][ 2 ] && + !mat[ 2 ][ 0 ] && !mat[ 2 ][ 1 ] ) { + return false; + } + return true; +} + +ID_INLINE void idMat4::ProjectVector( const idVec4 &src, idVec4 &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; + dst.w = src * mat[ 3 ]; +} + +ID_INLINE void idMat4::UnprojectVector( const idVec4 &src, idVec4 &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z + mat[ 3 ] * src.w; +} + +ID_INLINE float idMat4::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] ); +} + +ID_INLINE idMat4 idMat4::Inverse( void ) const { + idMat4 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat4 idMat4::InverseFast( void ) const { + idMat4 invMat; + + invMat = *this; +#ifdef _DEBUG + assert( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE idMat4 idMat3::ToMat4( void ) const { + // NOTE: idMat3 is transposed because it is column-major + return idMat4( mat[0][0], mat[1][0], mat[2][0], 0.0f, + mat[0][1], mat[1][1], mat[2][1], 0.0f, + mat[0][2], mat[1][2], mat[2][2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f ); +} + +ID_INLINE int idMat4::GetDimension( void ) const { + return 16; +} + +ID_INLINE const float *idMat4::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat4::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat5 - 5x5 matrix +// +//=============================================================== + +class idMat5 { +public: + idMat5( void ); + explicit idMat5( const idVec5 &v0, const idVec5 &v1, const idVec5 &v2, const idVec5 &v3, const idVec5 &v4 ); + explicit idMat5( const float src[ 5 ][ 5 ] ); + + const idVec5 & operator[]( int index ) const; + idVec5 & operator[]( int index ); + idMat5 operator*( const float a ) const; + idVec5 operator*( const idVec5 &vec ) const; + idMat5 operator*( const idMat5 &a ) const; + idMat5 operator+( const idMat5 &a ) const; + idMat5 operator-( const idMat5 &a ) const; + idMat5 & operator*=( const float a ); + idMat5 & operator*=( const idMat5 &a ); + idMat5 & operator+=( const idMat5 &a ); + idMat5 & operator-=( const idMat5 &a ); + + friend idMat5 operator*( const float a, const idMat5 &mat ); + friend idVec5 operator*( const idVec5 &vec, const idMat5 &mat ); + friend idVec5 & operator*=( idVec5 &vec, const idMat5 &mat ); + + bool Compare( const idMat5 &a ) const; // exact compare, no epsilon + bool Compare( const idMat5 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat5 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat5 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + float Trace( void ) const; + float Determinant( void ) const; + idMat5 Transpose( void ) const; // returns transpose + idMat5 & TransposeSelf( void ); + idMat5 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat5 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec5 mat[ 5 ]; +}; + +extern idMat5 mat5_zero; +extern idMat5 mat5_identity; +#define mat5_default mat5_identity + +ID_INLINE idMat5::idMat5( void ) { +} + +ID_INLINE idMat5::idMat5( const float src[ 5 ][ 5 ] ) { + memcpy( mat, src, 5 * 5 * sizeof( float ) ); +} + +ID_INLINE idMat5::idMat5( const idVec5 &v0, const idVec5 &v1, const idVec5 &v2, const idVec5 &v3, const idVec5 &v4 ) { + mat[0] = v0; + mat[1] = v1; + mat[2] = v2; + mat[3] = v3; + mat[4] = v4; +} + +ID_INLINE const idVec5 &idMat5::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 5 ) ); + return mat[ index ]; +} + +ID_INLINE idVec5 &idMat5::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 5 ) ); + return mat[ index ]; +} + +ID_INLINE idMat5 idMat5::operator*( const idMat5 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat5 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 5; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 5 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 5 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 5 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 5 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 5 + j ]; + dstPtr++; + } + m1Ptr += 5; + } + return dst; +} + +ID_INLINE idMat5 idMat5::operator*( const float a ) const { + return idMat5( + idVec5( mat[0][0] * a, mat[0][1] * a, mat[0][2] * a, mat[0][3] * a, mat[0][4] * a ), + idVec5( mat[1][0] * a, mat[1][1] * a, mat[1][2] * a, mat[1][3] * a, mat[1][4] * a ), + idVec5( mat[2][0] * a, mat[2][1] * a, mat[2][2] * a, mat[2][3] * a, mat[2][4] * a ), + idVec5( mat[3][0] * a, mat[3][1] * a, mat[3][2] * a, mat[3][3] * a, mat[3][4] * a ), + idVec5( mat[4][0] * a, mat[4][1] * a, mat[4][2] * a, mat[4][3] * a, mat[4][4] * a ) ); +} + +ID_INLINE idVec5 idMat5::operator*( const idVec5 &vec ) const { + return idVec5( + mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3] + mat[0][4] * vec[4], + mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3] + mat[1][4] * vec[4], + mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3] + mat[2][4] * vec[4], + mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3] + mat[3][4] * vec[4], + mat[4][0] * vec[0] + mat[4][1] * vec[1] + mat[4][2] * vec[2] + mat[4][3] * vec[3] + mat[4][4] * vec[4] ); +} + +ID_INLINE idMat5 idMat5::operator+( const idMat5 &a ) const { + return idMat5( + idVec5( mat[0][0] + a[0][0], mat[0][1] + a[0][1], mat[0][2] + a[0][2], mat[0][3] + a[0][3], mat[0][4] + a[0][4] ), + idVec5( mat[1][0] + a[1][0], mat[1][1] + a[1][1], mat[1][2] + a[1][2], mat[1][3] + a[1][3], mat[1][4] + a[1][4] ), + idVec5( mat[2][0] + a[2][0], mat[2][1] + a[2][1], mat[2][2] + a[2][2], mat[2][3] + a[2][3], mat[2][4] + a[2][4] ), + idVec5( mat[3][0] + a[3][0], mat[3][1] + a[3][1], mat[3][2] + a[3][2], mat[3][3] + a[3][3], mat[3][4] + a[3][4] ), + idVec5( mat[4][0] + a[4][0], mat[4][1] + a[4][1], mat[4][2] + a[4][2], mat[4][3] + a[4][3], mat[4][4] + a[4][4] ) ); +} + +ID_INLINE idMat5 idMat5::operator-( const idMat5 &a ) const { + return idMat5( + idVec5( mat[0][0] - a[0][0], mat[0][1] - a[0][1], mat[0][2] - a[0][2], mat[0][3] - a[0][3], mat[0][4] - a[0][4] ), + idVec5( mat[1][0] - a[1][0], mat[1][1] - a[1][1], mat[1][2] - a[1][2], mat[1][3] - a[1][3], mat[1][4] - a[1][4] ), + idVec5( mat[2][0] - a[2][0], mat[2][1] - a[2][1], mat[2][2] - a[2][2], mat[2][3] - a[2][3], mat[2][4] - a[2][4] ), + idVec5( mat[3][0] - a[3][0], mat[3][1] - a[3][1], mat[3][2] - a[3][2], mat[3][3] - a[3][3], mat[3][4] - a[3][4] ), + idVec5( mat[4][0] - a[4][0], mat[4][1] - a[4][1], mat[4][2] - a[4][2], mat[4][3] - a[4][3], mat[4][4] - a[4][4] ) ); +} + +ID_INLINE idMat5 &idMat5::operator*=( const float a ) { + mat[0][0] *= a; mat[0][1] *= a; mat[0][2] *= a; mat[0][3] *= a; mat[0][4] *= a; + mat[1][0] *= a; mat[1][1] *= a; mat[1][2] *= a; mat[1][3] *= a; mat[1][4] *= a; + mat[2][0] *= a; mat[2][1] *= a; mat[2][2] *= a; mat[2][3] *= a; mat[2][4] *= a; + mat[3][0] *= a; mat[3][1] *= a; mat[3][2] *= a; mat[3][3] *= a; mat[3][4] *= a; + mat[4][0] *= a; mat[4][1] *= a; mat[4][2] *= a; mat[4][3] *= a; mat[4][4] *= a; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator*=( const idMat5 &a ) { + *this = *this * a; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator+=( const idMat5 &a ) { + mat[0][0] += a[0][0]; mat[0][1] += a[0][1]; mat[0][2] += a[0][2]; mat[0][3] += a[0][3]; mat[0][4] += a[0][4]; + mat[1][0] += a[1][0]; mat[1][1] += a[1][1]; mat[1][2] += a[1][2]; mat[1][3] += a[1][3]; mat[1][4] += a[1][4]; + mat[2][0] += a[2][0]; mat[2][1] += a[2][1]; mat[2][2] += a[2][2]; mat[2][3] += a[2][3]; mat[2][4] += a[2][4]; + mat[3][0] += a[3][0]; mat[3][1] += a[3][1]; mat[3][2] += a[3][2]; mat[3][3] += a[3][3]; mat[3][4] += a[3][4]; + mat[4][0] += a[4][0]; mat[4][1] += a[4][1]; mat[4][2] += a[4][2]; mat[4][3] += a[4][3]; mat[4][4] += a[4][4]; + return *this; +} + +ID_INLINE idMat5 &idMat5::operator-=( const idMat5 &a ) { + mat[0][0] -= a[0][0]; mat[0][1] -= a[0][1]; mat[0][2] -= a[0][2]; mat[0][3] -= a[0][3]; mat[0][4] -= a[0][4]; + mat[1][0] -= a[1][0]; mat[1][1] -= a[1][1]; mat[1][2] -= a[1][2]; mat[1][3] -= a[1][3]; mat[1][4] -= a[1][4]; + mat[2][0] -= a[2][0]; mat[2][1] -= a[2][1]; mat[2][2] -= a[2][2]; mat[2][3] -= a[2][3]; mat[2][4] -= a[2][4]; + mat[3][0] -= a[3][0]; mat[3][1] -= a[3][1]; mat[3][2] -= a[3][2]; mat[3][3] -= a[3][3]; mat[3][4] -= a[3][4]; + mat[4][0] -= a[4][0]; mat[4][1] -= a[4][1]; mat[4][2] -= a[4][2]; mat[4][3] -= a[4][3]; mat[4][4] -= a[4][4]; + return *this; +} + +ID_INLINE idVec5 operator*( const idVec5 &vec, const idMat5 &mat ) { + return mat * vec; +} + +ID_INLINE idMat5 operator*( const float a, idMat5 const &mat ) { + return mat * a; +} + +ID_INLINE idVec5 &operator*=( idVec5 &vec, const idMat5 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat5::Compare( const idMat5 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 5*5; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat5::Compare( const idMat5 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 5*5; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat5::operator==( const idMat5 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat5::operator!=( const idMat5 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat5::Zero( void ) { + memset( mat, 0, sizeof( idMat5 ) ); +} + +ID_INLINE void idMat5::Identity( void ) { + *this = mat5_identity; +} + +ID_INLINE bool idMat5::IsIdentity( const float epsilon ) const { + return Compare( mat5_identity, epsilon ); +} + +ID_INLINE bool idMat5::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 5; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat5::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 5; i++ ) { + for ( int j = 0; j < 5; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE float idMat5::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] + mat[4][4] ); +} + +ID_INLINE idMat5 idMat5::Inverse( void ) const { + idMat5 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat5 idMat5::InverseFast( void ) const { + idMat5 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE int idMat5::GetDimension( void ) const { + return 25; +} + +ID_INLINE const float *idMat5::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat5::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMat6 - 6x6 matrix +// +//=============================================================== + +class idMat6 { +public: + idMat6( void ); + explicit idMat6( const idVec6 &v0, const idVec6 &v1, const idVec6 &v2, const idVec6 &v3, const idVec6 &v4, const idVec6 &v5 ); + explicit idMat6( const idMat3 &m0, const idMat3 &m1, const idMat3 &m2, const idMat3 &m3 ); + explicit idMat6( const float src[ 6 ][ 6 ] ); + + const idVec6 & operator[]( int index ) const; + idVec6 & operator[]( int index ); + idMat6 operator*( const float a ) const; + idVec6 operator*( const idVec6 &vec ) const; + idMat6 operator*( const idMat6 &a ) const; + idMat6 operator+( const idMat6 &a ) const; + idMat6 operator-( const idMat6 &a ) const; + idMat6 & operator*=( const float a ); + idMat6 & operator*=( const idMat6 &a ); + idMat6 & operator+=( const idMat6 &a ); + idMat6 & operator-=( const idMat6 &a ); + + friend idMat6 operator*( const float a, const idMat6 &mat ); + friend idVec6 operator*( const idVec6 &vec, const idMat6 &mat ); + friend idVec6 & operator*=( idVec6 &vec, const idMat6 &mat ); + + bool Compare( const idMat6 &a ) const; // exact compare, no epsilon + bool Compare( const idMat6 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMat6 &a ) const; // exact compare, no epsilon + bool operator!=( const idMat6 &a ) const; // exact compare, no epsilon + + void Zero( void ); + void Identity( void ); + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + + idMat3 SubMat3( int n ) const; + float Trace( void ) const; + float Determinant( void ) const; + idMat6 Transpose( void ) const; // returns transpose + idMat6 & TransposeSelf( void ); + idMat6 Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMat6 InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + idVec6 mat[ 6 ]; +}; + +extern idMat6 mat6_zero; +extern idMat6 mat6_identity; +#define mat6_default mat6_identity + +ID_INLINE idMat6::idMat6( void ) { +} + +ID_INLINE idMat6::idMat6( const idMat3 &m0, const idMat3 &m1, const idMat3 &m2, const idMat3 &m3 ) { + mat[0] = idVec6( m0[0][0], m0[0][1], m0[0][2], m1[0][0], m1[0][1], m1[0][2] ); + mat[1] = idVec6( m0[1][0], m0[1][1], m0[1][2], m1[1][0], m1[1][1], m1[1][2] ); + mat[2] = idVec6( m0[2][0], m0[2][1], m0[2][2], m1[2][0], m1[2][1], m1[2][2] ); + mat[3] = idVec6( m2[0][0], m2[0][1], m2[0][2], m3[0][0], m3[0][1], m3[0][2] ); + mat[4] = idVec6( m2[1][0], m2[1][1], m2[1][2], m3[1][0], m3[1][1], m3[1][2] ); + mat[5] = idVec6( m2[2][0], m2[2][1], m2[2][2], m3[2][0], m3[2][1], m3[2][2] ); +} + +ID_INLINE idMat6::idMat6( const idVec6 &v0, const idVec6 &v1, const idVec6 &v2, const idVec6 &v3, const idVec6 &v4, const idVec6 &v5 ) { + mat[0] = v0; + mat[1] = v1; + mat[2] = v2; + mat[3] = v3; + mat[4] = v4; + mat[5] = v5; +} + +ID_INLINE idMat6::idMat6( const float src[ 6 ][ 6 ] ) { + memcpy( mat, src, 6 * 6 * sizeof( float ) ); +} + +ID_INLINE const idVec6 &idMat6::operator[]( int index ) const { + //assert( ( index >= 0 ) && ( index < 6 ) ); + return mat[ index ]; +} + +ID_INLINE idVec6 &idMat6::operator[]( int index ) { + //assert( ( index >= 0 ) && ( index < 6 ) ); + return mat[ index ]; +} + +ID_INLINE idMat6 idMat6::operator*( const idMat6 &a ) const { + int i, j; + const float *m1Ptr, *m2Ptr; + float *dstPtr; + idMat6 dst; + + m1Ptr = reinterpret_cast(this); + m2Ptr = reinterpret_cast(&a); + dstPtr = reinterpret_cast(&dst); + + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 6; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 6 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 6 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 6 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 6 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 6 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 6 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return dst; +} + +ID_INLINE idMat6 idMat6::operator*( const float a ) const { + return idMat6( + idVec6( mat[0][0] * a, mat[0][1] * a, mat[0][2] * a, mat[0][3] * a, mat[0][4] * a, mat[0][5] * a ), + idVec6( mat[1][0] * a, mat[1][1] * a, mat[1][2] * a, mat[1][3] * a, mat[1][4] * a, mat[1][5] * a ), + idVec6( mat[2][0] * a, mat[2][1] * a, mat[2][2] * a, mat[2][3] * a, mat[2][4] * a, mat[2][5] * a ), + idVec6( mat[3][0] * a, mat[3][1] * a, mat[3][2] * a, mat[3][3] * a, mat[3][4] * a, mat[3][5] * a ), + idVec6( mat[4][0] * a, mat[4][1] * a, mat[4][2] * a, mat[4][3] * a, mat[4][4] * a, mat[4][5] * a ), + idVec6( mat[5][0] * a, mat[5][1] * a, mat[5][2] * a, mat[5][3] * a, mat[5][4] * a, mat[5][5] * a ) ); +} + +ID_INLINE idVec6 idMat6::operator*( const idVec6 &vec ) const { + return idVec6( + mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2] + mat[0][3] * vec[3] + mat[0][4] * vec[4] + mat[0][5] * vec[5], + mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2] + mat[1][3] * vec[3] + mat[1][4] * vec[4] + mat[1][5] * vec[5], + mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2] + mat[2][3] * vec[3] + mat[2][4] * vec[4] + mat[2][5] * vec[5], + mat[3][0] * vec[0] + mat[3][1] * vec[1] + mat[3][2] * vec[2] + mat[3][3] * vec[3] + mat[3][4] * vec[4] + mat[3][5] * vec[5], + mat[4][0] * vec[0] + mat[4][1] * vec[1] + mat[4][2] * vec[2] + mat[4][3] * vec[3] + mat[4][4] * vec[4] + mat[4][5] * vec[5], + mat[5][0] * vec[0] + mat[5][1] * vec[1] + mat[5][2] * vec[2] + mat[5][3] * vec[3] + mat[5][4] * vec[4] + mat[5][5] * vec[5] ); +} + +ID_INLINE idMat6 idMat6::operator+( const idMat6 &a ) const { + return idMat6( + idVec6( mat[0][0] + a[0][0], mat[0][1] + a[0][1], mat[0][2] + a[0][2], mat[0][3] + a[0][3], mat[0][4] + a[0][4], mat[0][5] + a[0][5] ), + idVec6( mat[1][0] + a[1][0], mat[1][1] + a[1][1], mat[1][2] + a[1][2], mat[1][3] + a[1][3], mat[1][4] + a[1][4], mat[1][5] + a[1][5] ), + idVec6( mat[2][0] + a[2][0], mat[2][1] + a[2][1], mat[2][2] + a[2][2], mat[2][3] + a[2][3], mat[2][4] + a[2][4], mat[2][5] + a[2][5] ), + idVec6( mat[3][0] + a[3][0], mat[3][1] + a[3][1], mat[3][2] + a[3][2], mat[3][3] + a[3][3], mat[3][4] + a[3][4], mat[3][5] + a[3][5] ), + idVec6( mat[4][0] + a[4][0], mat[4][1] + a[4][1], mat[4][2] + a[4][2], mat[4][3] + a[4][3], mat[4][4] + a[4][4], mat[4][5] + a[4][5] ), + idVec6( mat[5][0] + a[5][0], mat[5][1] + a[5][1], mat[5][2] + a[5][2], mat[5][3] + a[5][3], mat[5][4] + a[5][4], mat[5][5] + a[5][5] ) ); +} + +ID_INLINE idMat6 idMat6::operator-( const idMat6 &a ) const { + return idMat6( + idVec6( mat[0][0] - a[0][0], mat[0][1] - a[0][1], mat[0][2] - a[0][2], mat[0][3] - a[0][3], mat[0][4] - a[0][4], mat[0][5] - a[0][5] ), + idVec6( mat[1][0] - a[1][0], mat[1][1] - a[1][1], mat[1][2] - a[1][2], mat[1][3] - a[1][3], mat[1][4] - a[1][4], mat[1][5] - a[1][5] ), + idVec6( mat[2][0] - a[2][0], mat[2][1] - a[2][1], mat[2][2] - a[2][2], mat[2][3] - a[2][3], mat[2][4] - a[2][4], mat[2][5] - a[2][5] ), + idVec6( mat[3][0] - a[3][0], mat[3][1] - a[3][1], mat[3][2] - a[3][2], mat[3][3] - a[3][3], mat[3][4] - a[3][4], mat[3][5] - a[3][5] ), + idVec6( mat[4][0] - a[4][0], mat[4][1] - a[4][1], mat[4][2] - a[4][2], mat[4][3] - a[4][3], mat[4][4] - a[4][4], mat[4][5] - a[4][5] ), + idVec6( mat[5][0] - a[5][0], mat[5][1] - a[5][1], mat[5][2] - a[5][2], mat[5][3] - a[5][3], mat[5][4] - a[5][4], mat[5][5] - a[5][5] ) ); +} + +ID_INLINE idMat6 &idMat6::operator*=( const float a ) { + mat[0][0] *= a; mat[0][1] *= a; mat[0][2] *= a; mat[0][3] *= a; mat[0][4] *= a; mat[0][5] *= a; + mat[1][0] *= a; mat[1][1] *= a; mat[1][2] *= a; mat[1][3] *= a; mat[1][4] *= a; mat[1][5] *= a; + mat[2][0] *= a; mat[2][1] *= a; mat[2][2] *= a; mat[2][3] *= a; mat[2][4] *= a; mat[2][5] *= a; + mat[3][0] *= a; mat[3][1] *= a; mat[3][2] *= a; mat[3][3] *= a; mat[3][4] *= a; mat[3][5] *= a; + mat[4][0] *= a; mat[4][1] *= a; mat[4][2] *= a; mat[4][3] *= a; mat[4][4] *= a; mat[4][5] *= a; + mat[5][0] *= a; mat[5][1] *= a; mat[5][2] *= a; mat[5][3] *= a; mat[5][4] *= a; mat[5][5] *= a; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator*=( const idMat6 &a ) { + *this = *this * a; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator+=( const idMat6 &a ) { + mat[0][0] += a[0][0]; mat[0][1] += a[0][1]; mat[0][2] += a[0][2]; mat[0][3] += a[0][3]; mat[0][4] += a[0][4]; mat[0][5] += a[0][5]; + mat[1][0] += a[1][0]; mat[1][1] += a[1][1]; mat[1][2] += a[1][2]; mat[1][3] += a[1][3]; mat[1][4] += a[1][4]; mat[1][5] += a[1][5]; + mat[2][0] += a[2][0]; mat[2][1] += a[2][1]; mat[2][2] += a[2][2]; mat[2][3] += a[2][3]; mat[2][4] += a[2][4]; mat[2][5] += a[2][5]; + mat[3][0] += a[3][0]; mat[3][1] += a[3][1]; mat[3][2] += a[3][2]; mat[3][3] += a[3][3]; mat[3][4] += a[3][4]; mat[3][5] += a[3][5]; + mat[4][0] += a[4][0]; mat[4][1] += a[4][1]; mat[4][2] += a[4][2]; mat[4][3] += a[4][3]; mat[4][4] += a[4][4]; mat[4][5] += a[4][5]; + mat[5][0] += a[5][0]; mat[5][1] += a[5][1]; mat[5][2] += a[5][2]; mat[5][3] += a[5][3]; mat[5][4] += a[5][4]; mat[5][5] += a[5][5]; + return *this; +} + +ID_INLINE idMat6 &idMat6::operator-=( const idMat6 &a ) { + mat[0][0] -= a[0][0]; mat[0][1] -= a[0][1]; mat[0][2] -= a[0][2]; mat[0][3] -= a[0][3]; mat[0][4] -= a[0][4]; mat[0][5] -= a[0][5]; + mat[1][0] -= a[1][0]; mat[1][1] -= a[1][1]; mat[1][2] -= a[1][2]; mat[1][3] -= a[1][3]; mat[1][4] -= a[1][4]; mat[1][5] -= a[1][5]; + mat[2][0] -= a[2][0]; mat[2][1] -= a[2][1]; mat[2][2] -= a[2][2]; mat[2][3] -= a[2][3]; mat[2][4] -= a[2][4]; mat[2][5] -= a[2][5]; + mat[3][0] -= a[3][0]; mat[3][1] -= a[3][1]; mat[3][2] -= a[3][2]; mat[3][3] -= a[3][3]; mat[3][4] -= a[3][4]; mat[3][5] -= a[3][5]; + mat[4][0] -= a[4][0]; mat[4][1] -= a[4][1]; mat[4][2] -= a[4][2]; mat[4][3] -= a[4][3]; mat[4][4] -= a[4][4]; mat[4][5] -= a[4][5]; + mat[5][0] -= a[5][0]; mat[5][1] -= a[5][1]; mat[5][2] -= a[5][2]; mat[5][3] -= a[5][3]; mat[5][4] -= a[5][4]; mat[5][5] -= a[5][5]; + return *this; +} + +ID_INLINE idVec6 operator*( const idVec6 &vec, const idMat6 &mat ) { + return mat * vec; +} + +ID_INLINE idMat6 operator*( const float a, idMat6 const &mat ) { + return mat * a; +} + +ID_INLINE idVec6 &operator*=( idVec6 &vec, const idMat6 &mat ) { + vec = mat * vec; + return vec; +} + +ID_INLINE bool idMat6::Compare( const idMat6 &a ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 6*6; i++ ) { + if ( ptr1[i] != ptr2[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat6::Compare( const idMat6 &a, const float epsilon ) const { + dword i; + const float *ptr1, *ptr2; + + ptr1 = reinterpret_cast(mat); + ptr2 = reinterpret_cast(a.mat); + for ( i = 0; i < 6*6; i++ ) { + if ( idMath::Fabs( ptr1[i] - ptr2[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMat6::operator==( const idMat6 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMat6::operator!=( const idMat6 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMat6::Zero( void ) { + memset( mat, 0, sizeof( idMat6 ) ); +} + +ID_INLINE void idMat6::Identity( void ) { + *this = mat6_identity; +} + +ID_INLINE bool idMat6::IsIdentity( const float epsilon ) const { + return Compare( mat6_identity, epsilon ); +} + +ID_INLINE bool idMat6::IsSymmetric( const float epsilon ) const { + for ( int i = 1; i < 6; i++ ) { + for ( int j = 0; j < i; j++ ) { + if ( idMath::Fabs( mat[i][j] - mat[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMat6::IsDiagonal( const float epsilon ) const { + for ( int i = 0; i < 6; i++ ) { + for ( int j = 0; j < 6; j++ ) { + if ( i != j && idMath::Fabs( mat[i][j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE idMat3 idMat6::SubMat3( int n ) const { + assert( n >= 0 && n < 4 ); + int b0 = ((n & 2) >> 1) * 3; + int b1 = (n & 1) * 3; + return idMat3( + mat[b0 + 0][b1 + 0], mat[b0 + 0][b1 + 1], mat[b0 + 0][b1 + 2], + mat[b0 + 1][b1 + 0], mat[b0 + 1][b1 + 1], mat[b0 + 1][b1 + 2], + mat[b0 + 2][b1 + 0], mat[b0 + 2][b1 + 1], mat[b0 + 2][b1 + 2] ); +} + +ID_INLINE float idMat6::Trace( void ) const { + return ( mat[0][0] + mat[1][1] + mat[2][2] + mat[3][3] + mat[4][4] + mat[5][5] ); +} + +ID_INLINE idMat6 idMat6::Inverse( void ) const { + idMat6 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE idMat6 idMat6::InverseFast( void ) const { + idMat6 invMat; + + invMat = *this; +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE int idMat6::GetDimension( void ) const { + return 36; +} + +ID_INLINE const float *idMat6::ToFloatPtr( void ) const { + return mat[0].ToFloatPtr(); +} + +ID_INLINE float *idMat6::ToFloatPtr( void ) { + return mat[0].ToFloatPtr(); +} + + +//=============================================================== +// +// idMatX - arbitrary sized dense real matrix +// +// The matrix lives on 16 byte aligned and 16 byte padded memory. +// +// NOTE: due to the temporary memory pool idMatX cannot be used by multiple threads. +// +//=============================================================== + +#define MATX_MAX_TEMP 1024 +#define MATX_QUAD( x ) ( ( ( ( x ) + 3 ) & ~3 ) * sizeof( float ) ) +#define MATX_CLEAREND() int s = numRows * numColumns; while( s < ( ( s + 3 ) & ~3 ) ) { mat[s++] = 0.0f; } +#define MATX_ALLOCA( n ) ( (float *) _alloca16( MATX_QUAD( n ) ) ) +#define MATX_SIMD + +// RAVEN BEGIN +// jsinger: this is broken at the moment because idSIMDProcessor is no longer virtual +#ifdef _XENON +#undef MATX_SIMD +#endif +// RAVEN END +class idMatX { +public: + idMatX( void ); + explicit idMatX( int rows, int columns ); + explicit idMatX( int rows, int columns, float *src ); + ~idMatX( void ); + + void Set( int rows, int columns, const float *src ); + void Set( const idMat3 &m1, const idMat3 &m2 ); + void Set( const idMat3 &m1, const idMat3 &m2, const idMat3 &m3, const idMat3 &m4 ); + + const float * operator[]( int index ) const; + float * operator[]( int index ); + idMatX & operator=( const idMatX &a ); + idMatX operator*( const float a ) const; + idVecX operator*( const idVecX &vec ) const; + idMatX operator*( const idMatX &a ) const; + idMatX operator+( const idMatX &a ) const; + idMatX operator-( const idMatX &a ) const; + idMatX & operator*=( const float a ); + idMatX & operator*=( const idMatX &a ); + idMatX & operator+=( const idMatX &a ); + idMatX & operator-=( const idMatX &a ); + + friend idMatX operator*( const float a, const idMatX &m ); + friend idVecX operator*( const idVecX &vec, const idMatX &m ); + friend idVecX & operator*=( idVecX &vec, const idMatX &m ); + + bool Compare( const idMatX &a ) const; // exact compare, no epsilon + bool Compare( const idMatX &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idMatX &a ) const; // exact compare, no epsilon + bool operator!=( const idMatX &a ) const; // exact compare, no epsilon + + void SetSize( int rows, int columns ); // set the number of rows/columns + void ChangeSize( int rows, int columns, bool makeZero = false ); // change the size keeping data intact where possible + int GetNumRows( void ) const { return numRows; } // get the number of rows + int GetNumColumns( void ) const { return numColumns; } // get the number of columns + void SetData( int rows, int columns, float *data ); // set float array pointer + void Zero( void ); // clear matrix + void Zero( int rows, int columns ); // set size and clear matrix + void Identity( void ); // clear to identity matrix + void Identity( int rows, int columns ); // set size and clear to identity matrix + void Diag( const idVecX &v ); // create diagonal matrix from vector + void Random( int seed, float l = 0.0f, float u = 1.0f ); // fill matrix with random values + void Random( int rows, int columns, int seed, float l = 0.0f, float u = 1.0f ); + void Negate( void ); // (*this) = - (*this) + void Clamp( float min, float max ); // clamp all values + idMatX & SwapRows( int r1, int r2 ); // swap rows + idMatX & SwapColumns( int r1, int r2 ); // swap columns + idMatX & SwapRowsColumns( int r1, int r2 ); // swap rows and columns + idMatX & RemoveRow( int r ); // remove a row + idMatX & RemoveColumn( int r ); // remove a column + idMatX & RemoveRowColumn( int r ); // remove a row and column + void ClearUpperTriangle( void ); // clear the upper triangle + void ClearLowerTriangle( void ); // clear the lower triangle + void SquareSubMatrix( const idMatX &m, int size ); // get square sub-matrix from 0,0 to size,size + float MaxDifference( const idMatX &m ) const; // return maximum element difference between this and m + + bool IsSquare( void ) const { return ( numRows == numColumns ); } + bool IsZero( const float epsilon = MATRIX_EPSILON ) const; + bool IsIdentity( const float epsilon = MATRIX_EPSILON ) const; + bool IsDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsTriDiagonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetric( const float epsilon = MATRIX_EPSILON ) const; + bool IsOrthogonal( const float epsilon = MATRIX_EPSILON ) const; + bool IsOrthonormal( const float epsilon = MATRIX_EPSILON ) const; + bool IsPMatrix( const float epsilon = MATRIX_EPSILON ) const; + bool IsZMatrix( const float epsilon = MATRIX_EPSILON ) const; + bool IsPositiveDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetricPositiveDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsPositiveSemiDefinite( const float epsilon = MATRIX_EPSILON ) const; + bool IsSymmetricPositiveSemiDefinite( const float epsilon = MATRIX_EPSILON ) const; + + float Trace( void ) const; // returns product of diagonal elements + float Determinant( void ) const; // returns determinant of matrix + idMatX Transpose( void ) const; // returns transpose + idMatX & TransposeSelf( void ); // transposes the matrix itself + idMatX Inverse( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseSelf( void ); // returns false if determinant is zero + idMatX InverseFast( void ) const; // returns the inverse ( m * m.Inverse() = identity ) + bool InverseFastSelf( void ); // returns false if determinant is zero + + bool LowerTriangularInverse( void ); // in-place inversion, returns false if determinant is zero + bool UpperTriangularInverse( void ); // in-place inversion, returns false if determinant is zero + + idVecX Multiply( const idVecX &vec ) const; // (*this) * vec + idVecX TransposeMultiply( const idVecX &vec ) const; // this->Transpose() * vec + + idMatX Multiply( const idMatX &a ) const; // (*this) * a + idMatX TransposeMultiply( const idMatX &a ) const; // this->Transpose() * a + + void Multiply( idVecX &dst, const idVecX &vec ) const; // dst = (*this) * vec + void MultiplyAdd( idVecX &dst, const idVecX &vec ) const; // dst += (*this) * vec + void MultiplySub( idVecX &dst, const idVecX &vec ) const; // dst -= (*this) * vec + void TransposeMultiply( idVecX &dst, const idVecX &vec ) const; // dst = this->Transpose() * vec + void TransposeMultiplyAdd( idVecX &dst, const idVecX &vec ) const; // dst += this->Transpose() * vec + void TransposeMultiplySub( idVecX &dst, const idVecX &vec ) const; // dst -= this->Transpose() * vec + + void Multiply( idMatX &dst, const idMatX &a ) const; // dst = (*this) * a + void TransposeMultiply( idMatX &dst, const idMatX &a ) const; // dst = this->Transpose() * a + + int GetDimension( void ) const; // returns total number of values in matrix + + const idVec6 & SubVec6( int row ) const; // interpret beginning of row as a const idVec6 + idVec6 & SubVec6( int row ); // interpret beginning of row as an idVec6 + const idVecX SubVecX( int row ) const; // interpret complete row as a const idVecX + idVecX SubVecX( int row ); // interpret complete row as an idVecX + const float * ToFloatPtr( void ) const; // pointer to const matrix float array + float * ToFloatPtr( void ); // pointer to matrix float array + const char * ToString( int precision = 2 ) const; + + void Update_RankOne( const idVecX &v, const idVecX &w, float alpha ); + void Update_RankOneSymmetric( const idVecX &v, float alpha ); + void Update_RowColumn( const idVecX &v, const idVecX &w, int r ); + void Update_RowColumnSymmetric( const idVecX &v, int r ); + void Update_Increment( const idVecX &v, const idVecX &w ); + void Update_IncrementSymmetric( const idVecX &v ); + void Update_Decrement( int r ); + + bool Inverse_GaussJordan( void ); // invert in-place with Gauss-Jordan elimination + bool Inverse_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha ); + bool Inverse_UpdateRowColumn( const idVecX &v, const idVecX &w, int r ); + bool Inverse_UpdateIncrement( const idVecX &v, const idVecX &w ); + bool Inverse_UpdateDecrement( const idVecX &v, const idVecX &w, int r ); + void Inverse_Solve( idVecX &x, const idVecX &b ) const; + + bool LU_Factor( int *index, float *det = NULL ); // factor in-place: L * U + bool LU_UpdateRankOne( const idVecX &v, const idVecX &w, float alpha, int *index ); + bool LU_UpdateRowColumn( const idVecX &v, const idVecX &w, int r, int *index ); + bool LU_UpdateIncrement( const idVecX &v, const idVecX &w, int *index ); + bool LU_UpdateDecrement( const idVecX &v, const idVecX &w, const idVecX &u, int r, int *index ); + void LU_Solve( idVecX &x, const idVecX &b, const int *index ) const; + void LU_Inverse( idMatX &inv, const int *index ) const; + void LU_UnpackFactors( idMatX &L, idMatX &U ) const; + void LU_MultiplyFactors( idMatX &m, const int *index ) const; + + bool QR_Factor( idVecX &c, idVecX &d ); // factor in-place: Q * R + bool QR_UpdateRankOne( idMatX &R, const idVecX &v, const idVecX &w, float alpha ); + bool QR_UpdateRowColumn( idMatX &R, const idVecX &v, const idVecX &w, int r ); + bool QR_UpdateIncrement( idMatX &R, const idVecX &v, const idVecX &w ); + bool QR_UpdateDecrement( idMatX &R, const idVecX &v, const idVecX &w, int r ); + void QR_Solve( idVecX &x, const idVecX &b, const idVecX &c, const idVecX &d ) const; + void QR_Solve( idVecX &x, const idVecX &b, const idMatX &R ) const; + void QR_Inverse( idMatX &inv, const idVecX &c, const idVecX &d ) const; + void QR_UnpackFactors( idMatX &Q, idMatX &R, const idVecX &c, const idVecX &d ) const; + void QR_MultiplyFactors( idMatX &m, const idVecX &c, const idVecX &d ) const; + + bool SVD_Factor( idVecX &w, idMatX &V ); // factor in-place: U * Diag(w) * V.Transpose() + void SVD_Solve( idVecX &x, const idVecX &b, const idVecX &w, const idMatX &V ) const; + void SVD_Inverse( idMatX &inv, const idVecX &w, const idMatX &V ) const; + void SVD_MultiplyFactors( idMatX &m, const idVecX &w, const idMatX &V ) const; + + bool Cholesky_Factor( void ); // factor in-place: L * L.Transpose() + bool Cholesky_UpdateRankOne( const idVecX &v, float alpha, int offset = 0 ); + bool Cholesky_UpdateRowColumn( const idVecX &v, int r ); + bool Cholesky_UpdateIncrement( const idVecX &v ); + bool Cholesky_UpdateDecrement( const idVecX &v, int r ); + void Cholesky_Solve( idVecX &x, const idVecX &b ) const; + void Cholesky_Inverse( idMatX &inv ) const; + void Cholesky_MultiplyFactors( idMatX &m ) const; + + bool LDLT_Factor( void ); // factor in-place: L * D * L.Transpose() + bool LDLT_UpdateRankOne( const idVecX &v, float alpha, int offset = 0 ); + bool LDLT_UpdateRowColumn( const idVecX &v, int r ); + bool LDLT_UpdateIncrement( const idVecX &v ); + bool LDLT_UpdateDecrement( const idVecX &v, int r ); + void LDLT_Solve( idVecX &x, const idVecX &b ) const; + void LDLT_Inverse( idMatX &inv ) const; + void LDLT_UnpackFactors( idMatX &L, idMatX &D ) const; + void LDLT_MultiplyFactors( idMatX &m ) const; + + void TriDiagonal_ClearTriangles( void ); + bool TriDiagonal_Solve( idVecX &x, const idVecX &b ) const; + void TriDiagonal_Inverse( idMatX &inv ) const; + + bool Eigen_SolveSymmetricTriDiagonal( idVecX &eigenValues ); + bool Eigen_SolveSymmetric( idVecX &eigenValues ); + bool Eigen_Solve( idVecX &realEigenValues, idVecX &imaginaryEigenValues ); + void Eigen_SortIncreasing( idVecX &eigenValues ); + void Eigen_SortDecreasing( idVecX &eigenValues ); + + static void Test( void ); + +private: + int numRows; // number of rows + int numColumns; // number of columns + int alloced; // floats allocated, if -1 then mat points to data set with SetData + float * mat; // memory the matrix is stored + +// RAVEN BEGIN +// jscott: avoid pointer hackery pokery and use the compiler + ALIGN16( static float tempPtr[MATX_MAX_TEMP] ); +// static float * tempPtr; // pointer to 16 byte aligned temporary memory +// RAVEN END + static int tempIndex; // index into memory pool, wraps around + +private: + void SetTempSize( int rows, int columns ); + float DeterminantGeneric( void ) const; + bool InverseSelfGeneric( void ); + void QR_Rotate( idMatX &R, int i, float a, float b ); + float Pythag( float a, float b ) const; + void SVD_BiDiag( idVecX &w, idVecX &rv1, float &anorm ); + void SVD_InitialWV( idVecX &w, idMatX &V, idVecX &rv1 ); + void HouseholderReduction( idVecX &diag, idVecX &subd ); + bool QL( idVecX &diag, idVecX &subd ); + void HessenbergReduction( idMatX &H ); + void ComplexDivision( float xr, float xi, float yr, float yi, float &cdivr, float &cdivi ); + bool HessenbergToRealSchur( idMatX &H, idVecX &realEigenValues, idVecX &imaginaryEigenValues ); +}; + +ID_INLINE idMatX::idMatX( void ) { + numRows = numColumns = alloced = 0; + mat = NULL; +} + +ID_INLINE idMatX::~idMatX( void ) { + // if not temp memory + if ( mat != NULL && ( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( mat ); + } +} + +ID_INLINE idMatX::idMatX( int rows, int columns ) { + numRows = numColumns = alloced = 0; + mat = NULL; + SetSize( rows, columns ); +} + +ID_INLINE idMatX::idMatX( int rows, int columns, float *src ) { + numRows = numColumns = alloced = 0; + mat = NULL; + SetData( rows, columns, src ); +} + +ID_INLINE void idMatX::Set( int rows, int columns, const float *src ) { + SetSize( rows, columns ); + memcpy( this->mat, src, rows * columns * sizeof( float ) ); +} + +ID_INLINE void idMatX::Set( const idMat3 &m1, const idMat3 &m2 ) { + int i, j; + + SetSize( 3, 6 ); + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + mat[(i+0) * numColumns + (j+0)] = m1[i][j]; + mat[(i+0) * numColumns + (j+3)] = m2[i][j]; + } + } +} + +ID_INLINE void idMatX::Set( const idMat3 &m1, const idMat3 &m2, const idMat3 &m3, const idMat3 &m4 ) { + int i, j; + + SetSize( 6, 6 ); + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + mat[(i+0) * numColumns + (j+0)] = m1[i][j]; + mat[(i+0) * numColumns + (j+3)] = m2[i][j]; + mat[(i+3) * numColumns + (j+0)] = m3[i][j]; + mat[(i+3) * numColumns + (j+3)] = m4[i][j]; + } + } +} + +ID_INLINE const float *idMatX::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < numRows ) ); + return mat + index * numColumns; +} + +ID_INLINE float *idMatX::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < numRows ) ); + return mat + index * numColumns; +} + +ID_INLINE idMatX &idMatX::operator=( const idMatX &a ) { + SetSize( a.numRows, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Copy16( mat, a.mat, a.numRows * a.numColumns ); +#else + memcpy( mat, a.mat, a.numRows * a.numColumns * sizeof( float ) ); +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX idMatX::operator*( const float a ) const { + idMatX m; + + m.SetTempSize( numRows, numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Mul16( m.mat, mat, a, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + m.mat[i] = mat[i] * a; + } +#endif + return m; +} + +ID_INLINE idVecX idMatX::operator*( const idVecX &vec ) const { + idVecX dst; + + assert( numColumns == vec.GetSize() ); + + dst.SetTempSize( numRows ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyVecX( dst, *this, vec ); +#else + Multiply( dst, vec ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::operator*( const idMatX &a ) const { + idMatX dst; + + assert( numColumns == a.numRows ); + + dst.SetTempSize( numRows, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyMatX( dst, *this, a ); +#else + Multiply( dst, a ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::operator+( const idMatX &a ) const { + idMatX m; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + m.SetTempSize( numRows, numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Add16( m.mat, mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + m.mat[i] = mat[i] + a.mat[i]; + } +#endif + return m; +} + +ID_INLINE idMatX idMatX::operator-( const idMatX &a ) const { + idMatX m; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + m.SetTempSize( numRows, numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Sub16( m.mat, mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + m.mat[i] = mat[i] - a.mat[i]; + } +#endif + return m; +} + +ID_INLINE idMatX &idMatX::operator*=( const float a ) { +#ifdef MATX_SIMD + SIMDProcessor->MulAssign16( mat, a, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] *= a; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX &idMatX::operator*=( const idMatX &a ) { + *this = *this * a; + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX &idMatX::operator+=( const idMatX &a ) { + assert( numRows == a.numRows && numColumns == a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->AddAssign16( mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] += a.mat[i]; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX &idMatX::operator-=( const idMatX &a ) { + assert( numRows == a.numRows && numColumns == a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->SubAssign16( mat, a.mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] -= a.mat[i]; + } +#endif + idMatX::tempIndex = 0; + return *this; +} + +ID_INLINE idMatX operator*( const float a, idMatX const &m ) { + return m * a; +} + +ID_INLINE idVecX operator*( const idVecX &vec, const idMatX &m ) { + return m * vec; +} + +ID_INLINE idVecX &operator*=( idVecX &vec, const idMatX &m ) { + vec = m * vec; + return vec; +} + +ID_INLINE bool idMatX::Compare( const idMatX &a ) const { + int i, s; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + if ( mat[i] != a.mat[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMatX::Compare( const idMatX &a, const float epsilon ) const { + int i, s; + + assert( numRows == a.numRows && numColumns == a.numColumns ); + + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + if ( idMath::Fabs( mat[i] - a.mat[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idMatX::operator==( const idMatX &a ) const { + return Compare( a ); +} + +ID_INLINE bool idMatX::operator!=( const idMatX &a ) const { + return !Compare( a ); +} + +ID_INLINE void idMatX::SetSize( int rows, int columns ) { + assert( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ); + int alloc = ( rows * columns + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + if ( mat != NULL ) { + Mem_Free16( mat ); + } +//RAVEN BEGIN +//amccarthy: Added allocation tag + mat = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +//RAVEN END + alloced = alloc; + } + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +ID_INLINE void idMatX::SetTempSize( int rows, int columns ) { + int newSize; + + newSize = ( rows * columns + 3 ) & ~3; + assert( newSize < MATX_MAX_TEMP ); + if ( idMatX::tempIndex + newSize > MATX_MAX_TEMP ) { + idMatX::tempIndex = 0; + } + mat = idMatX::tempPtr + idMatX::tempIndex; + idMatX::tempIndex += newSize; + alloced = newSize; + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +ID_INLINE void idMatX::SetData( int rows, int columns, float *data ) { + assert( mat < idMatX::tempPtr || mat > idMatX::tempPtr + MATX_MAX_TEMP ); + if ( mat != NULL && alloced != -1 ) { + Mem_Free16( mat ); + } + assert( ( ( (int) data ) & 15 ) == 0 ); // data must be 16 byte aligned + mat = data; + alloced = -1; + numRows = rows; + numColumns = columns; + MATX_CLEAREND(); +} + +ID_INLINE void idMatX::Zero( void ) { +#ifdef MATX_SIMD + SIMDProcessor->Zero16( mat, numRows * numColumns ); +#else + memset( mat, 0, numRows * numColumns * sizeof( float ) ); +#endif +} + +ID_INLINE void idMatX::Zero( int rows, int columns ) { + SetSize( rows, columns ); +#ifdef MATX_SIMD + SIMDProcessor->Zero16( mat, numRows * numColumns ); +#else + memset( mat, 0, rows * columns * sizeof( float ) ); +#endif +} + +ID_INLINE void idMatX::Identity( void ) { + assert( numRows == numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->Zero16( mat, numRows * numColumns ); +#else + memset( mat, 0, numRows * numColumns * sizeof( float ) ); +#endif + for ( int i = 0; i < numRows; i++ ) { + mat[i * numColumns + i] = 1.0f; + } +} + +ID_INLINE void idMatX::Identity( int rows, int columns ) { + assert( rows == columns ); + SetSize( rows, columns ); + idMatX::Identity(); +} + +ID_INLINE void idMatX::Diag( const idVecX &v ) { + Zero( v.GetSize(), v.GetSize() ); + for ( int i = 0; i < v.GetSize(); i++ ) { + mat[i * numColumns + i] = v[i]; + } +} + +ID_INLINE void idMatX::Random( int seed, float l, float u ) { + int i, s; + float c; + idRandom rnd(seed); + + c = u - l; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idMatX::Random( int rows, int columns, int seed, float l, float u ) { + int i, s; + float c; + idRandom rnd(seed); + + SetSize( rows, columns ); + c = u - l; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idMatX::Negate( void ) { +#ifdef MATX_SIMD + SIMDProcessor->Negate16( mat, numRows * numColumns ); +#else + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + mat[i] = -mat[i]; + } +#endif +} + +ID_INLINE void idMatX::Clamp( float min, float max ) { + int i, s; + s = numRows * numColumns; + for ( i = 0; i < s; i++ ) { + if ( mat[i] < min ) { + mat[i] = min; + } else if ( mat[i] > max ) { + mat[i] = max; + } + } +} + +ID_INLINE idMatX &idMatX::SwapRows( int r1, int r2 ) { + float *ptr; + + ptr = (float *) _alloca16( numColumns * sizeof( float ) ); + memcpy( ptr, mat + r1 * numColumns, numColumns * sizeof( float ) ); + memcpy( mat + r1 * numColumns, mat + r2 * numColumns, numColumns * sizeof( float ) ); + memcpy( mat + r2 * numColumns, ptr, numColumns * sizeof( float ) ); + + return *this; +} + +ID_INLINE idMatX &idMatX::SwapColumns( int r1, int r2 ) { + int i; + float tmp, *ptr; + + for ( i = 0; i < numRows; i++ ) { + ptr = mat + i * numColumns; + tmp = ptr[r1]; + ptr[r1] = ptr[r2]; + ptr[r2] = tmp; + } + + return *this; +} + +ID_INLINE idMatX &idMatX::SwapRowsColumns( int r1, int r2 ) { + + SwapRows( r1, r2 ); + SwapColumns( r1, r2 ); + return *this; +} + +ID_INLINE void idMatX::ClearUpperTriangle( void ) { + assert( numRows == numColumns ); + for ( int i = numRows-2; i >= 0; i-- ) { + memset( mat + i * numColumns + i + 1, 0, (numColumns - 1 - i) * sizeof(float) ); + } +} + +ID_INLINE void idMatX::ClearLowerTriangle( void ) { + assert( numRows == numColumns ); + for ( int i = 1; i < numRows; i++ ) { + memset( mat + i * numColumns, 0, i * sizeof(float) ); + } +} + +ID_INLINE void idMatX::SquareSubMatrix( const idMatX &m, int size ) { + int i; + assert( size <= m.numRows && size <= m.numColumns ); + SetSize( size, size ); + for ( i = 0; i < size; i++ ) { + memcpy( mat + i * numColumns, m.mat + i * m.numColumns, size * sizeof( float ) ); + } +} + +ID_INLINE float idMatX::MaxDifference( const idMatX &m ) const { + int i, j; + float diff, maxDiff; + + assert( numRows == m.numRows && numColumns == m.numColumns ); + + maxDiff = -1.0f; + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + diff = idMath::Fabs( mat[ i * numColumns + j ] - m[i][j] ); + if ( maxDiff < 0.0f || diff > maxDiff ) { + maxDiff = diff; + } + } + } + return maxDiff; +} + +ID_INLINE bool idMatX::IsZero( const float epsilon ) const { + // returns true if (*this) == Zero + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[i * numColumns + j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsIdentity( const float epsilon ) const { + // returns true if (*this) == Identity + assert( numRows == numColumns ); + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[i * numColumns + j] - (float)( i == j ) ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsDiagonal( const float epsilon ) const { + // returns true if all elements are zero except for the elements on the diagonal + assert( numRows == numColumns ); + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( i != j && idMath::Fabs( mat[i * numColumns + j] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsTriDiagonal( const float epsilon ) const { + // returns true if all elements are zero except for the elements on the diagonal plus or minus one column + + if ( numRows != numColumns ) { + return false; + } + for ( int i = 0; i < numRows-2; i++ ) { + for ( int j = i+2; j < numColumns; j++ ) { + if ( idMath::Fabs( (*this)[i][j] ) > epsilon ) { + return false; + } + if ( idMath::Fabs( (*this)[j][i] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE bool idMatX::IsSymmetric( const float epsilon ) const { + // (*this)[i][j] == (*this)[j][i] + if ( numRows != numColumns ) { + return false; + } + for ( int i = 0; i < numRows; i++ ) { + for ( int j = 0; j < numColumns; j++ ) { + if ( idMath::Fabs( mat[ i * numColumns + j ] - mat[ j * numColumns + i ] ) > epsilon ) { + return false; + } + } + } + return true; +} + +ID_INLINE float idMatX::Trace( void ) const { + float trace = 0.0f; + + assert( numRows == numColumns ); + + // sum of elements on the diagonal + for ( int i = 0; i < numRows; i++ ) { + trace += mat[i * numRows + i]; + } + return trace; +} + +ID_INLINE float idMatX::Determinant( void ) const { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + return mat[0]; + case 2: + return reinterpret_cast(mat)->Determinant(); + case 3: + return reinterpret_cast(mat)->Determinant(); + case 4: + return reinterpret_cast(mat)->Determinant(); + case 5: + return reinterpret_cast(mat)->Determinant(); + case 6: + return reinterpret_cast(mat)->Determinant(); + default: + return DeterminantGeneric(); + } + return 0.0f; +} + +ID_INLINE idMatX idMatX::Transpose( void ) const { + idMatX transpose; + int i, j; + + transpose.SetTempSize( numColumns, numRows ); + + for ( i = 0; i < numRows; i++ ) { + for ( j = 0; j < numColumns; j++ ) { + transpose.mat[j * transpose.numColumns + i] = mat[i * numColumns + j]; + } + } + + return transpose; +} + +ID_INLINE idMatX &idMatX::TransposeSelf( void ) { + *this = Transpose(); + return *this; +} + +ID_INLINE idMatX idMatX::Inverse( void ) const { + idMatX invMat; + + invMat.SetTempSize( numRows, numColumns ); + memcpy( invMat.mat, mat, numRows * numColumns * sizeof( float ) ); +#ifdef _DEBUG + assert ( invMat.InverseSelf() ); +#else + invMat.InverseSelf(); +#endif + return invMat; +} + +ID_INLINE bool idMatX::InverseSelf( void ) { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + if ( idMath::Fabs( mat[0] ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + mat[0] = 1.0f / mat[0]; + return true; + case 2: + return reinterpret_cast(mat)->InverseSelf(); + case 3: + return reinterpret_cast(mat)->InverseSelf(); + case 4: + return reinterpret_cast(mat)->InverseSelf(); + case 5: + return reinterpret_cast(mat)->InverseSelf(); + case 6: + return reinterpret_cast(mat)->InverseSelf(); + default: + return InverseSelfGeneric(); + } +} + +ID_INLINE idMatX idMatX::InverseFast( void ) const { + idMatX invMat; + + invMat.SetTempSize( numRows, numColumns ); + memcpy( invMat.mat, mat, numRows * numColumns * sizeof( float ) ); +#ifdef _DEBUG + assert ( invMat.InverseFastSelf() ); +#else + invMat.InverseFastSelf(); +#endif + return invMat; +} + +ID_INLINE bool idMatX::InverseFastSelf( void ) { + + assert( numRows == numColumns ); + + switch( numRows ) { + case 1: + if ( idMath::Fabs( mat[0] ) < MATRIX_INVERSE_EPSILON ) { + return false; + } + mat[0] = 1.0f / mat[0]; + return true; + case 2: + return reinterpret_cast(mat)->InverseFastSelf(); + case 3: + return reinterpret_cast(mat)->InverseFastSelf(); + case 4: + return reinterpret_cast(mat)->InverseFastSelf(); + case 5: + return reinterpret_cast(mat)->InverseFastSelf(); + case 6: + return reinterpret_cast(mat)->InverseFastSelf(); + default: + return InverseSelfGeneric(); + } +//unreachable +//return false; +} + +ID_INLINE idVecX idMatX::Multiply( const idVecX &vec ) const { + idVecX dst; + + assert( numColumns == vec.GetSize() ); + + dst.SetTempSize( numRows ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyVecX( dst, *this, vec ); +#else + Multiply( dst, vec ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::Multiply( const idMatX &a ) const { + idMatX dst; + + assert( numColumns == a.numRows ); + + dst.SetTempSize( numRows, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyMatX( dst, *this, a ); +#else + Multiply( dst, a ); +#endif + return dst; +} + +ID_INLINE idVecX idMatX::TransposeMultiply( const idVecX &vec ) const { + idVecX dst; + + assert( numRows == vec.GetSize() ); + + dst.SetTempSize( numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyVecX( dst, *this, vec ); +#else + TransposeMultiply( dst, vec ); +#endif + return dst; +} + +ID_INLINE idMatX idMatX::TransposeMultiply( const idMatX &a ) const { + idMatX dst; + + assert( numRows == a.numRows ); + + dst.SetTempSize( numColumns, a.numColumns ); +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyMatX( dst, *this, a ); +#else + TransposeMultiply( dst, a ); +#endif + return dst; +} + +ID_INLINE void idMatX::Multiply( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + mPtr = mat; + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] = sum; + mPtr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::MultiplyAdd( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyAddVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + mPtr = mat; + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] += sum; + mPtr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::MultiplySub( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplySubVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + mPtr = mat; + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] -= sum; + mPtr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiply( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] = sum; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiplyAdd( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyAddVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] += sum; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiplySub( idVecX &dst, const idVecX &vec ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplySubVecX( dst, *this, vec ); +#else + int i, j; + const float *mPtr, *vPtr; + float *dstPtr; + + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] -= sum; + } +#endif +} + +ID_INLINE void idMatX::Multiply( idMatX &dst, const idMatX &a ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_MultiplyMatX( dst, *this, a ); +#else + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( numColumns == a.numRows ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = ToFloatPtr(); + m2Ptr = a.ToFloatPtr(); + k = numRows; + l = a.GetNumColumns(); + + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m2Ptr = a.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < numColumns; n++ ) { + m2Ptr += l; + sum += m1Ptr[n] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + m1Ptr += numColumns; + } +#endif +} + +ID_INLINE void idMatX::TransposeMultiply( idMatX &dst, const idMatX &a ) const { +#ifdef MATX_SIMD + SIMDProcessor->MatX_TransposeMultiplyMatX( dst, *this, a ); +#else + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( numRows == a.numRows ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = ToFloatPtr(); + k = numColumns; + l = a.numColumns; + + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m1Ptr = ToFloatPtr() + i; + m2Ptr = a.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < numRows; n++ ) { + m1Ptr += numColumns; + m2Ptr += a.numColumns; + sum += m1Ptr[0] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + } +#endif +} + +ID_INLINE int idMatX::GetDimension( void ) const { + return numRows * numColumns; +} + +ID_INLINE const idVec6 &idMatX::SubVec6( int row ) const { + assert( numColumns >= 6 && row >= 0 && row < numRows ); + return *reinterpret_cast(mat + row * numColumns); +} + +ID_INLINE idVec6 &idMatX::SubVec6( int row ) { + assert( numColumns >= 6 && row >= 0 && row < numRows ); + return *reinterpret_cast(mat + row * numColumns); +} + +ID_INLINE const idVecX idMatX::SubVecX( int row ) const { + idVecX v; + assert( row >= 0 && row < numRows ); + v.SetData( numColumns, mat + row * numColumns ); + return v; +} + +ID_INLINE idVecX idMatX::SubVecX( int row ) { + idVecX v; + assert( row >= 0 && row < numRows ); + v.SetData( numColumns, mat + row * numColumns ); + return v; +} + +ID_INLINE const float *idMatX::ToFloatPtr( void ) const { + return mat; +} + +ID_INLINE float *idMatX::ToFloatPtr( void ) { + return mat; +} + +#endif /* !__MATH_MATRIX_H__ */ diff --git a/src/idlib/math/Ode.cpp b/src/idlib/math/Ode.cpp new file mode 100644 index 0000000..0ac79cd --- /dev/null +++ b/src/idlib/math/Ode.cpp @@ -0,0 +1,351 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +//=============================================================== +// +// idODE_Euler +// +//=============================================================== + +/* +============= +idODE_Euler::idODE_Euler +============= +*/ +idODE_Euler::idODE_Euler( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derivatives = new float[dim]; + derive = dr; + userData = ud; +} + +/* +============= +idODE_Euler::~idODE_Euler +============= +*/ +idODE_Euler::~idODE_Euler( void ) { + delete[] derivatives; +} + +/* +============= +idODE_Euler::Evaluate +============= +*/ +float idODE_Euler::Evaluate( const float *state, float *newState, float t0, float t1 ) { + float delta; + int i; + + derive( t0, userData, state, derivatives ); + delta = t1 - t0; + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + delta * derivatives[i]; + } + return delta; +} + +//=============================================================== +// +// idODE_Midpoint +// +//=============================================================== + +/* +============= +idODE_Midpoint::idODE_Midpoint +============= +*/ +idODE_Midpoint::idODE_Midpoint( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + tmpState = new float[dim]; + derivatives = new float[dim]; + derive = dr; + userData = ud; +} + +/* +============= +idODE_Midpoint::~idODE_Midpoint +============= +*/ +idODE_Midpoint::~idODE_Midpoint( void ) { + delete tmpState; + delete derivatives; +} + +/* +============= +idODE_Midpoint::~Evaluate +============= +*/ +float idODE_Midpoint::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta; + int i; + + delta = t1 - t0; + halfDelta = delta * 0.5; + // first step + derive( t0, userData, state, derivatives ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * derivatives[i]; + } + // second step + derive( t0 + halfDelta, userData, tmpState, derivatives ); + + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + delta * derivatives[i]; + } + return delta; +} + +//=============================================================== +// +// idODE_RK4 +// +//=============================================================== + +/* +============= +idODE_RK4::idODE_RK4 +============= +*/ +idODE_RK4::idODE_RK4( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derive = dr; + userData = ud; + tmpState = new float[dim]; + d1 = new float[dim]; + d2 = new float[dim]; + d3 = new float[dim]; + d4 = new float[dim]; +} + +/* +============= +idODE_RK4::~idODE_RK4 +============= +*/ +idODE_RK4::~idODE_RK4( void ) { + delete tmpState; + delete d1; + delete d2; + delete d3; + delete d4; +} + +/* +============= +idODE_RK4::Evaluate +============= +*/ +float idODE_RK4::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta, sixthDelta; + int i; + + delta = t1 - t0; + halfDelta = delta * 0.5; + // first step + derive( t0, userData, state, d1 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d1[i]; + } + // second step + derive( t0 + halfDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d2[i]; + } + // third step + derive( t0 + halfDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + delta * d3[i]; + } + // fourth step + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = delta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + return delta; +} + +//=============================================================== +// +// idODE_RK4Adaptive +// +//=============================================================== + +/* +============= +idODE_RK4Adaptive::idODE_RK4Adaptive +============= +*/ +idODE_RK4Adaptive::idODE_RK4Adaptive( const int dim, deriveFunction_t dr, const void *ud ) { + dimension = dim; + derive = dr; + userData = ud; + maxError = 0.01f; + tmpState = new float[dim]; + d1 = new float[dim]; + d1half = new float [dim]; + d2 = new float[dim]; + d3 = new float[dim]; + d4 = new float[dim]; +} + +/* +============= +idODE_RK4Adaptive::~idODE_RK4Adaptive +============= +*/ +idODE_RK4Adaptive::~idODE_RK4Adaptive( void ) { + delete tmpState; + delete d1; + delete d1half; + delete d2; + delete d3; + delete d4; +} + +/* +============= +idODE_RK4Adaptive::SetMaxError +============= +*/ +void idODE_RK4Adaptive::SetMaxError( const float err ) { + if ( err > 0.0f ) { + maxError = err; + } +} + +/* +============= +idODE_RK4Adaptive::Evaluate +============= +*/ +float idODE_RK4Adaptive::Evaluate( const float *state, float *newState, float t0, float t1 ) { + double delta, halfDelta, fourthDelta, sixthDelta; + double error, max; + int i, n; + + delta = t1 - t0; + + for ( n = 0; n < 4; n++ ) { + + halfDelta = delta * 0.5; + fourthDelta = delta * 0.25; + + // first step of first half delta + derive( t0, userData, state, d1 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d1[i]; + } + // second step of first half delta + derive( t0 + fourthDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d2[i]; + } + // third step of first half delta + derive( t0 + fourthDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d3[i]; + } + // fourth step of first half delta + derive( t0 + halfDelta, userData, tmpState, d4 ); + + sixthDelta = halfDelta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // first step of second half delta + derive( t0 + halfDelta, userData, tmpState, d1half ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d1half[i]; + } + // second step of second half delta + derive( t0 + halfDelta + fourthDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + fourthDelta * d2[i]; + } + // third step of second half delta + derive( t0 + halfDelta + fourthDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d3[i]; + } + // fourth step of second half delta + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = halfDelta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + newState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // first step of full delta + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d1[i]; + } + // second step of full delta + derive( t0 + halfDelta, userData, tmpState, d2 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + halfDelta * d2[i]; + } + // third step of full delta + derive( t0 + halfDelta, userData, tmpState, d3 ); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + delta * d3[i]; + } + // fourth step of full delta + derive( t0 + delta, userData, tmpState, d4 ); + + sixthDelta = delta * (1.0/6.0); + for ( i = 0; i < dimension; i++ ) { + tmpState[i] = state[i] + sixthDelta * (d1[i] + 2.0 * (d2[i] + d3[i]) + d4[i]); + } + + // get max estimated error + max = 0.0; + for ( i = 0; i < dimension; i++ ) { + error = idMath::Fabs( (newState[i] - tmpState[i]) / (delta * d1[i] + 1e-10) ); + if ( error > max ) { + max = error; + } + } + error = max / maxError; + + if ( error <= 1.0f ) { + return delta * 4.0; + } + if ( delta <= 1e-7 ) { + return delta; + } + delta *= 0.25; + } + return delta; +} + diff --git a/src/idlib/math/Ode.h b/src/idlib/math/Ode.h new file mode 100644 index 0000000..b3806d0 --- /dev/null +++ b/src/idlib/math/Ode.h @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_ODE_H__ +#define __MATH_ODE_H__ + +/* +=============================================================================== + + Numerical solvers for ordinary differential equations. + +=============================================================================== +*/ + + +//=============================================================== +// +// idODE +// +//=============================================================== + +typedef void (*deriveFunction_t)( const float t, const void *userData, const float *state, float *derivatives ); + +class idODE { + +public: + virtual ~idODE( void ) {} + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ) = 0; + +protected: + int dimension; // dimension in floats allocated for + deriveFunction_t derive; // derive function + const void * userData; // client data +}; + +//=============================================================== +// +// idODE_Euler +// +//=============================================================== + +class idODE_Euler : public idODE { + +public: + idODE_Euler( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_Euler( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * derivatives; // space to store derivatives +}; + +//=============================================================== +// +// idODE_Midpoint +// +//=============================================================== + +class idODE_Midpoint : public idODE { + +public: + idODE_Midpoint( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_Midpoint( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * tmpState; + float * derivatives; // space to store derivatives +}; + +//=============================================================== +// +// idODE_RK4 +// +//=============================================================== + +class idODE_RK4 : public idODE { + +public: + idODE_RK4( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_RK4( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + +protected: + float * tmpState; + float * d1; // derivatives + float * d2; + float * d3; + float * d4; +}; + +//=============================================================== +// +// idODE_RK4Adaptive +// +//=============================================================== + +class idODE_RK4Adaptive : public idODE { + +public: + idODE_RK4Adaptive( const int dim, const deriveFunction_t dr, const void *ud ); + virtual ~idODE_RK4Adaptive( void ); + + virtual float Evaluate( const float *state, float *newState, float t0, float t1 ); + void SetMaxError( const float err ); + +protected: + float maxError; // maximum allowed error + float * tmpState; + float * d1; // derivatives + float * d1half; + float * d2; + float * d3; + float * d4; +}; + +#endif /* !__MATH_ODE_H__ */ diff --git a/src/idlib/math/Plane.cpp b/src/idlib/math/Plane.cpp new file mode 100644 index 0000000..e05a3a8 --- /dev/null +++ b/src/idlib/math/Plane.cpp @@ -0,0 +1,150 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +idPlane plane_origin( 0.0f, 0.0f, 0.0f, 0.0f ); + +/* +================ +idPlane::Type +================ +*/ +int idPlane::Type( void ) const { + if ( Normal()[0] == 0.0f ) { + if ( Normal()[1] == 0.0f ) { + return Normal()[2] > 0.0f ? PLANETYPE_Z : PLANETYPE_NEGZ; + } + else if ( Normal()[2] == 0.0f ) { + return Normal()[1] > 0.0f ? PLANETYPE_Y : PLANETYPE_NEGY; + } + else { + return PLANETYPE_ZEROX; + } + } + else if ( Normal()[1] == 0.0f ) { + if ( Normal()[2] == 0.0f ) { + return Normal()[0] > 0.0f ? PLANETYPE_X : PLANETYPE_NEGX; + } + else { + return PLANETYPE_ZEROY; + } + } + else if ( Normal()[2] == 0.0f ) { + return PLANETYPE_ZEROZ; + } + else { + return PLANETYPE_NONAXIAL; + } +} + +/* +================ +idPlane::HeightFit +================ +*/ +bool idPlane::HeightFit( const idVec3 *points, const int numPoints ) { + int i; + float sumXX = 0.0f, sumXY = 0.0f, sumXZ = 0.0f; + float sumYY = 0.0f, sumYZ = 0.0f; + idVec3 sum, average, dir; + + if ( numPoints == 1 ) { + a = 0.0f; + b = 0.0f; + c = 1.0f; + d = -points[0].z; + return true; + } + if ( numPoints == 2 ) { + dir = points[1] - points[0]; + Normal() = dir.Cross( idVec3( 0, 0, 1 ) ).Cross( dir ); + Normalize(); + d = -( Normal() * points[0] ); + return true; + } + + sum.Zero(); + for ( i = 0; i < numPoints; i++) { + sum += points[i]; + } + average = sum / numPoints; + + for ( i = 0; i < numPoints; i++ ) { + dir = points[i] - average; + sumXX += dir.x * dir.x; + sumXY += dir.x * dir.y; + sumXZ += dir.x * dir.z; + sumYY += dir.y * dir.y; + sumYZ += dir.y * dir.z; + } + + idMat2 m( sumXX, sumXY, sumXY, sumYY ); + if ( !m.InverseSelf() ) { + return false; + } + + a = - sumXZ * m[0][0] - sumYZ * m[0][1]; + b = - sumXZ * m[1][0] - sumYZ * m[1][1]; + c = 1.0f; + Normalize(); + d = -( a * average.x + b * average.y + c * average.z ); + return true; +} + +/* +================ +idPlane::PlaneIntersection +================ +*/ +bool idPlane::PlaneIntersection( const idPlane &plane, idVec3 &start, idVec3 &dir ) const { + double n00, n01, n11, det, invDet, f0, f1; + + n00 = Normal().LengthSqr(); + n01 = Normal() * plane.Normal(); + n11 = plane.Normal().LengthSqr(); + det = n00 * n11 - n01 * n01; + + if ( idMath::Fabs(det) < 1e-6f ) { + return false; + } + + invDet = 1.0f / det; + f0 = ( n01 * plane.d - n11 * d ) * invDet; + f1 = ( n01 * d - n00 * plane.d ) * invDet; + + dir = Normal().Cross( plane.Normal() ); + start = f0 * Normal() + f1 * plane.Normal(); + return true; +} + +/* +============= +idPlane::ToString +============= +*/ +const char *idPlane::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/src/idlib/math/Plane.h b/src/idlib/math/Plane.h new file mode 100644 index 0000000..1939dff --- /dev/null +++ b/src/idlib/math/Plane.h @@ -0,0 +1,387 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_PLANE_H__ +#define __MATH_PLANE_H__ + +/* +=============================================================================== + + 3D plane with equation: a * x + b * y + c * z + d = 0 + +=============================================================================== +*/ + + +class idVec3; +class idMat3; + +#define ON_EPSILON 0.1f +#define DEGENERATE_DIST_EPSILON 1e-4f + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +// plane sides +#define PLANESIDE_FRONT 0 +#define PLANESIDE_BACK 1 +#define PLANESIDE_ON 2 +#define PLANESIDE_CROSS 3 + +// plane types +#define PLANETYPE_X 0 +#define PLANETYPE_Y 1 +#define PLANETYPE_Z 2 +#define PLANETYPE_NEGX 3 +#define PLANETYPE_NEGY 4 +#define PLANETYPE_NEGZ 5 +#define PLANETYPE_TRUEAXIAL 6 // all types < 6 are true axial planes +#define PLANETYPE_ZEROX 6 +#define PLANETYPE_ZEROY 7 +#define PLANETYPE_ZEROZ 8 +#define PLANETYPE_NONAXIAL 9 + +class idPlane { +public: + idPlane( void ); + idPlane( float a, float b, float c, float d ); + idPlane( const idVec3 &normal, const float dist ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idPlane operator-() const; // flips plane + idPlane & operator=( const idVec3 &v ); // sets normal and sets idPlane::d to zero + idPlane operator+( const idPlane &p ) const; // add plane equations + idPlane operator-( const idPlane &p ) const; // subtract plane equations + idPlane & operator*=( const idMat3 &m ); // Normal() *= m + + bool Compare( const idPlane &p ) const; // exact compare, no epsilon + bool Compare( const idPlane &p, const float epsilon ) const; // compare with epsilon + bool Compare( const idPlane &p, const float normalEps, const float distEps ) const; // compare with epsilon + bool operator==( const idPlane &p ) const; // exact compare, no epsilon + bool operator!=( const idPlane &p ) const; // exact compare, no epsilon + + void Zero( void ); // zero plane + void SetNormal( const idVec3 &normal ); // sets the normal + const idVec3 & Normal( void ) const; // reference to const normal + idVec3 & Normal( void ); // reference to normal + float Normalize( bool fixDegenerate = true ); // only normalizes the plane normal, does not adjust d + bool FixDegenerateNormal( void ); // fix degenerate normal + bool FixDegeneracies( float distEpsilon ); // fix degenerate normal and dist + float Dist( void ) const; // returns: -d + void SetDist( const float dist ); // sets: d = -dist + int Type( void ) const; // returns plane type + + bool FromPoints( const idVec3 &p1, const idVec3 &p2, const idVec3 &p3, bool fixDegenerate = true ); + bool FromVecs( const idVec3 &dir1, const idVec3 &dir2, const idVec3 &p, bool fixDegenerate = true ); + void FitThroughPoint( const idVec3 &p ); // assumes normal is valid + bool HeightFit( const idVec3 *points, const int numPoints ); + idPlane Translate( const idVec3 &translation ) const; + idPlane & TranslateSelf( const idVec3 &translation ); + idPlane Rotate( const idVec3 &origin, const idMat3 &axis ) const; + idPlane & RotateSelf( const idVec3 &origin, const idMat3 &axis ); + + float Distance( const idVec3 &v ) const; + int Side( const idVec3 &v, const float epsilon = 0.0f ) const; + + bool LineIntersection( const idVec3 &start, const idVec3 &end ) const; + // intersection point is start + dir * scale + bool RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const; + bool PlaneIntersection( const idPlane &plane, idVec3 &start, idVec3 &dir ) const; + + int GetDimension( void ) const; + + const idVec4 & ToVec4( void ) const; + idVec4 & ToVec4( void ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float a; + float b; + float c; + float d; +}; + +extern idPlane plane_origin; +#define plane_zero plane_origin + +ID_INLINE idPlane::idPlane( void ) { +} + +ID_INLINE idPlane::idPlane( float a, float b, float c, float d ) { + this->a = a; + this->b = b; + this->c = c; + this->d = d; +} + +ID_INLINE idPlane::idPlane( const idVec3 &normal, const float dist ) { + this->a = normal.x; + this->b = normal.y; + this->c = normal.z; + this->d = -dist; +} + +ID_INLINE float idPlane::operator[]( int index ) const { + return ( &a )[ index ]; +} + +ID_INLINE float& idPlane::operator[]( int index ) { + return ( &a )[ index ]; +} + +ID_INLINE idPlane idPlane::operator-() const { + return idPlane( -a, -b, -c, -d ); +} + +ID_INLINE idPlane &idPlane::operator=( const idVec3 &v ) { + a = v.x; + b = v.y; + c = v.z; + d = 0; + return *this; +} + +ID_INLINE idPlane idPlane::operator+( const idPlane &p ) const { + return idPlane( a + p.a, b + p.b, c + p.c, d + p.d ); +} + +ID_INLINE idPlane idPlane::operator-( const idPlane &p ) const { + return idPlane( a - p.a, b - p.b, c - p.c, d - p.d ); +} + +ID_INLINE idPlane &idPlane::operator*=( const idMat3 &m ) { + Normal() *= m; + return *this; +} + +ID_INLINE bool idPlane::Compare( const idPlane &p ) const { + return ( a == p.a && b == p.b && c == p.c && d == p.d ); +} + +ID_INLINE bool idPlane::Compare( const idPlane &p, const float epsilon ) const { + if ( idMath::Fabs( a - p.a ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( b - p.b ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( c - p.c ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( d - p.d ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idPlane::Compare( const idPlane &p, const float normalEps, const float distEps ) const { + if ( idMath::Fabs( d - p.d ) > distEps ) { + return false; + } + if ( !Normal().Compare( p.Normal(), normalEps ) ) { + return false; + } + return true; +} + +ID_INLINE bool idPlane::operator==( const idPlane &p ) const { + return Compare( p ); +} + +ID_INLINE bool idPlane::operator!=( const idPlane &p ) const { + return !Compare( p ); +} + +ID_INLINE void idPlane::Zero( void ) { + a = b = c = d = 0.0f; +} + +ID_INLINE void idPlane::SetNormal( const idVec3 &normal ) { + a = normal.x; + b = normal.y; + c = normal.z; +} + +ID_INLINE const idVec3 &idPlane::Normal( void ) const { + return *reinterpret_cast(&a); +} + +ID_INLINE idVec3 &idPlane::Normal( void ) { + return *reinterpret_cast(&a); +} + +ID_INLINE float idPlane::Normalize( bool fixDegenerate ) { + float length = reinterpret_cast(&a)->Normalize(); + + if ( fixDegenerate ) { + FixDegenerateNormal(); + } + return length; +} + +ID_INLINE bool idPlane::FixDegenerateNormal( void ) { + return Normal().FixDegenerateNormal(); +} + +ID_INLINE bool idPlane::FixDegeneracies( float distEpsilon ) { + bool fixedNormal = FixDegenerateNormal(); + // only fix dist if the normal was degenerate + if ( fixedNormal ) { + if ( idMath::Fabs( d - idMath::Rint( d ) ) < distEpsilon ) { + d = idMath::Rint( d ); + } + } + return fixedNormal; +} + +ID_INLINE float idPlane::Dist( void ) const { + return -d; +} + +ID_INLINE void idPlane::SetDist( const float dist ) { + d = -dist; +} + +ID_INLINE bool idPlane::FromPoints( const idVec3 &p1, const idVec3 &p2, const idVec3 &p3, bool fixDegenerate ) { + Normal() = (p1 - p2).Cross( p3 - p2 ); + if ( Normalize( fixDegenerate ) == 0.0f ) { + return false; + } + d = -( Normal() * p2 ); + return true; +} + +ID_INLINE bool idPlane::FromVecs( const idVec3 &dir1, const idVec3 &dir2, const idVec3 &p, bool fixDegenerate ) { + Normal() = dir1.Cross( dir2 ); + if ( Normalize( fixDegenerate ) == 0.0f ) { + return false; + } + d = -( Normal() * p ); + return true; +} + +ID_INLINE void idPlane::FitThroughPoint( const idVec3 &p ) { + d = -( Normal() * p ); +} + +ID_INLINE idPlane idPlane::Translate( const idVec3 &translation ) const { + return idPlane( a, b, c, d - translation * Normal() ); +} + +ID_INLINE idPlane &idPlane::TranslateSelf( const idVec3 &translation ) { + d -= translation * Normal(); + return *this; +} + +ID_INLINE idPlane idPlane::Rotate( const idVec3 &origin, const idMat3 &axis ) const { + idPlane p; + p.Normal() = Normal() * axis; + p.d = d + origin * Normal() - origin * p.Normal(); + return p; +} + +ID_INLINE idPlane &idPlane::RotateSelf( const idVec3 &origin, const idMat3 &axis ) { + d += origin * Normal(); + Normal() *= axis; + d -= origin * Normal(); + return *this; +} + +ID_INLINE float idPlane::Distance( const idVec3 &v ) const { + return a * v.x + b * v.y + c * v.z + d; +} + +ID_INLINE int idPlane::Side( const idVec3 &v, const float epsilon ) const { + float dist = Distance( v ); + if ( dist > epsilon ) { + return PLANESIDE_FRONT; + } + else if ( dist < -epsilon ) { + return PLANESIDE_BACK; + } + else { + return PLANESIDE_ON; + } +} + +ID_INLINE bool idPlane::LineIntersection( const idVec3 &start, const idVec3 &end ) const { + float d1, d2, fraction; + + d1 = Normal() * start + d; + d2 = Normal() * end + d; + if ( d1 == d2 ) { + return false; + } + if ( d1 > 0.0f && d2 > 0.0f ) { + return false; + } + if ( d1 < 0.0f && d2 < 0.0f ) { + return false; + } + fraction = ( d1 / ( d1 - d2 ) ); + return ( fraction >= 0.0f && fraction <= 1.0f ); +} + +ID_INLINE bool idPlane::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale ) const { + float d1, d2; + + d1 = Normal() * start + d; + d2 = Normal() * dir; + if ( d2 == 0.0f ) { + return false; + } + scale = -( d1 / d2 ); + return true; +} + +ID_INLINE int idPlane::GetDimension( void ) const { + return 4; +} + +ID_INLINE const idVec4 &idPlane::ToVec4( void ) const { + return *reinterpret_cast(&a); +} + +ID_INLINE idVec4 &idPlane::ToVec4( void ) { + return *reinterpret_cast(&a); +} + +ID_INLINE const float *idPlane::ToFloatPtr( void ) const { + return reinterpret_cast(&a); +} + +ID_INLINE float *idPlane::ToFloatPtr( void ) { + return reinterpret_cast(&a); +} + +#endif /* !__MATH_PLANE_H__ */ diff --git a/src/idlib/math/Pluecker.cpp b/src/idlib/math/Pluecker.cpp new file mode 100644 index 0000000..95594a3 --- /dev/null +++ b/src/idlib/math/Pluecker.cpp @@ -0,0 +1,82 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +idPluecker pluecker_origin( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ); + +/* +================ +idPluecker::FromPlanes + + pluecker coordinate for the intersection of two planes +================ +*/ +bool idPluecker::FromPlanes( const idPlane &p1, const idPlane &p2 ) { + + p[0] = -( p1[2] * -p2[3] - p2[2] * -p1[3] ); + p[1] = -( p2[1] * -p1[3] - p1[1] * -p2[3] ); + p[2] = p1[1] * p2[2] - p2[1] * p1[2]; + + p[3] = -( p1[0] * -p2[3] - p2[0] * -p1[3] ); + p[4] = p1[0] * p2[1] - p2[0] * p1[1]; + p[5] = p1[0] * p2[2] - p2[0] * p1[2]; + + return ( p[2] != 0.0f || p[5] != 0.0f || p[4] != 0.0f ); +} + +/* +================ +idPluecker::Distance3DSqr + + calculates square of shortest distance between the two + 3D lines represented by their pluecker coordinates +================ +*/ +float idPluecker::Distance3DSqr( const idPluecker &a ) const { + float d, s; + idVec3 dir; + + dir[0] = -a.p[5] * p[4] - a.p[4] * -p[5]; + dir[1] = a.p[4] * p[2] - a.p[2] * p[4]; + dir[2] = a.p[2] * -p[5] - -a.p[5] * p[2]; + if ( dir[0] == 0.0f && dir[1] == 0.0f && dir[2] == 0.0f ) { + return -1.0f; // FIXME: implement for parallel lines + } + d = a.p[4] * ( p[2] * dir[1] - -p[5] * dir[0] ) + + a.p[5] * ( p[2] * dir[2] - p[4] * dir[0] ) + + a.p[2] * ( -p[5] * dir[2] - p[4] * dir[1] ); + s = PermutedInnerProduct( a ) / d; + return ( dir * dir ) * ( s * s ); +} + +/* +============= +idPluecker::ToString +============= +*/ +const char *idPluecker::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/src/idlib/math/Pluecker.h b/src/idlib/math/Pluecker.h new file mode 100644 index 0000000..12d9c79 --- /dev/null +++ b/src/idlib/math/Pluecker.h @@ -0,0 +1,405 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_PLUECKER_H__ +#define __MATH_PLUECKER_H__ + +/* +=============================================================================== + + Pluecker coordinate + +=============================================================================== +*/ + +class idPluecker { +public: + idPluecker( void ); + explicit idPluecker( const float *a ); + explicit idPluecker( const idVec3 &start, const idVec3 &end ); + explicit idPluecker( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idPluecker operator-() const; // flips the direction + idPluecker operator*( const float a ) const; + idPluecker operator/( const float a ) const; + float operator*( const idPluecker &a ) const; // permuted inner product + idPluecker operator-( const idPluecker &a ) const; + idPluecker operator+( const idPluecker &a ) const; + idPluecker & operator*=( const float a ); + idPluecker & operator/=( const float a ); + idPluecker & operator+=( const idPluecker &a ); + idPluecker & operator-=( const idPluecker &a ); + + bool Compare( const idPluecker &a ) const; // exact compare, no epsilon + bool Compare( const idPluecker &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idPluecker &a ) const; // exact compare, no epsilon + bool operator!=( const idPluecker &a ) const; // exact compare, no epsilon + + void Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + void Zero( void ); + + idPluecker Rotate( const idMat3 &rotation ) const; + idPluecker Translate( const idVec3 &translation ) const; + idPluecker TranslateAndRotate( const idVec3 &translation, const idMat3 &rotation ) const; + + void FromLine( const idVec3 &start, const idVec3 &end ); // pluecker from line + void FromRay( const idVec3 &start, const idVec3 &dir ); // pluecker from ray + bool FromPlanes( const idPlane &p1, const idPlane &p2 ); // pluecker from intersection of planes + bool ToLine( idVec3 &start, idVec3 &end ) const; // pluecker to line + bool ToRay( idVec3 &start, idVec3 &dir ) const; // pluecker to ray + void ToDir( idVec3 &dir ) const; // pluecker to direction + float PermutedInnerProduct( const idPluecker &a ) const; // pluecker permuted inner product + float Distance3DSqr( const idPluecker &a ) const; // pluecker line distance + + float Length( void ) const; // pluecker length + float LengthSqr( void ) const; // pluecker squared length + idPluecker Normalize( void ) const; // pluecker normalize + float NormalizeSelf( void ); // pluecker normalize + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float p[6]; +}; + +extern idPluecker pluecker_origin; +#define pluecker_zero pluecker_origin + +ID_INLINE idPluecker::idPluecker( void ) { +} + +ID_INLINE idPluecker::idPluecker( const float *a ) { + memcpy( p, a, 6 * sizeof( float ) ); +} + +ID_INLINE idPluecker::idPluecker( const idVec3 &start, const idVec3 &end ) { + FromLine( start, end ); +} + +ID_INLINE idPluecker::idPluecker( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE idPluecker idPluecker::operator-() const { + return idPluecker( -p[0], -p[1], -p[2], -p[3], -p[4], -p[5] ); +} + +ID_INLINE float idPluecker::operator[]( const int index ) const { + return p[index]; +} + +ID_INLINE float &idPluecker::operator[]( const int index ) { + return p[index]; +} + +ID_INLINE idPluecker idPluecker::operator*( const float a ) const { + return idPluecker( p[0]*a, p[1]*a, p[2]*a, p[3]*a, p[4]*a, p[5]*a ); +} + +ID_INLINE float idPluecker::operator*( const idPluecker &a ) const { + return p[0] * a.p[4] + p[1] * a.p[5] + p[2] * a.p[3] + p[4] * a.p[0] + p[5] * a.p[1] + p[3] * a.p[2]; +} + +ID_INLINE idPluecker idPluecker::operator/( const float a ) const { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + return idPluecker( p[0]*inva, p[1]*inva, p[2]*inva, p[3]*inva, p[4]*inva, p[5]*inva ); +} + +ID_INLINE idPluecker idPluecker::operator+( const idPluecker &a ) const { + return idPluecker( p[0] + a[0], p[1] + a[1], p[2] + a[2], p[3] + a[3], p[4] + a[4], p[5] + a[5] ); +} + +ID_INLINE idPluecker idPluecker::operator-( const idPluecker &a ) const { + return idPluecker( p[0] - a[0], p[1] - a[1], p[2] - a[2], p[3] - a[3], p[4] - a[4], p[5] - a[5] ); +} + +ID_INLINE idPluecker &idPluecker::operator*=( const float a ) { + p[0] *= a; + p[1] *= a; + p[2] *= a; + p[3] *= a; + p[4] *= a; + p[5] *= a; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator/=( const float a ) { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + p[0] *= inva; + p[1] *= inva; + p[2] *= inva; + p[3] *= inva; + p[4] *= inva; + p[5] *= inva; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator+=( const idPluecker &a ) { + p[0] += a[0]; + p[1] += a[1]; + p[2] += a[2]; + p[3] += a[3]; + p[4] += a[4]; + p[5] += a[5]; + return *this; +} + +ID_INLINE idPluecker &idPluecker::operator-=( const idPluecker &a ) { + p[0] -= a[0]; + p[1] -= a[1]; + p[2] -= a[2]; + p[3] -= a[3]; + p[4] -= a[4]; + p[5] -= a[5]; + return *this; +} + +ID_INLINE bool idPluecker::Compare( const idPluecker &a ) const { + return ( ( p[0] == a[0] ) && ( p[1] == a[1] ) && ( p[2] == a[2] ) && + ( p[3] == a[3] ) && ( p[4] == a[4] ) && ( p[5] == a[5] ) ); +} + +ID_INLINE bool idPluecker::Compare( const idPluecker &a, const float epsilon ) const { + if ( idMath::Fabs( p[0] - a[0] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[1] - a[1] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[2] - a[2] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[3] - a[3] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[4] - a[4] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[5] - a[5] ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idPluecker::operator==( const idPluecker &a ) const { + return Compare( a ); +} + +ID_INLINE bool idPluecker::operator!=( const idPluecker &a ) const { + return !Compare( a ); +} + +ID_INLINE void idPluecker::Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE void idPluecker::Zero( void ) { + p[0] = p[1] = p[2] = p[3] = p[4] = p[5] = 0.0f; +} + +ID_INLINE idPluecker idPluecker::Rotate( const idMat3 &rotation ) const { + idPluecker result; + result[0] = rotation[ 0 ].z * p[3] - rotation[ 1 ].z * p[1] + rotation[ 2 ].z * p[0]; + result[1] = - rotation[ 0 ].y * p[3] + rotation[ 1 ].y * p[1] - rotation[ 2 ].y * p[0]; + result[2] = rotation[ 0 ].x * p[2] - rotation[ 1 ].x * p[5] + rotation[ 2 ].x * p[4]; + result[3] = rotation[ 0 ].x * p[3] - rotation[ 1 ].x * p[1] + rotation[ 2 ].x * p[0]; + result[4] = rotation[ 0 ].z * p[2] - rotation[ 1 ].z * p[5] + rotation[ 2 ].z * p[4]; + result[5] = - rotation[ 0 ].y * p[2] + rotation[ 1 ].y * p[5] - rotation[ 2 ].y * p[4]; + return result; +} + +ID_INLINE idPluecker idPluecker::Translate( const idVec3 &translation ) const { + idPluecker result; + result[0] = p[0] + translation[0] * p[5] + p[2] * translation[1]; + result[1] = p[1] - translation[0] * p[4] + p[2] * translation[2]; + result[2] = p[2]; + result[3] = p[3] - translation[1] * p[4] - p[5] * translation[2]; + result[4] = p[4]; + result[5] = p[5]; + return result; +} + +ID_INLINE idPluecker idPluecker::TranslateAndRotate( const idVec3 &translation, const idMat3 &rotation ) const { + idVec3 t; + idPluecker result; + t[0] = p[0] + translation[0] * p[5] + p[2] * translation[1]; + t[1] = p[1] - translation[0] * p[4] + p[2] * translation[2]; + t[2] = p[3] - translation[1] * p[4] - p[5] * translation[2]; + result[0] = rotation[ 0 ].z * t[2] - rotation[ 1 ].z * t[1] + rotation[ 2 ].z * t[0]; + result[1] = - rotation[ 0 ].y * t[2] + rotation[ 1 ].y * t[1] - rotation[ 2 ].y * t[0]; + result[2] = rotation[ 0 ].x * p[2] - rotation[ 1 ].x * p[5] + rotation[ 2 ].x * p[4]; + result[3] = rotation[ 0 ].x * t[2] - rotation[ 1 ].x * t[1] + rotation[ 2 ].x * t[0]; + result[4] = rotation[ 0 ].z * p[2] - rotation[ 1 ].z * p[5] + rotation[ 2 ].z * p[4]; + result[5] = - rotation[ 0 ].y * p[2] + rotation[ 1 ].y * p[5] - rotation[ 2 ].y * p[4]; + return result; +} + +ID_INLINE void idPluecker::FromLine( const idVec3 &start, const idVec3 &end ) { + p[0] = start[0] * end[1] - end[0] * start[1]; + p[1] = start[0] * end[2] - end[0] * start[2]; + p[2] = start[0] - end[0]; + p[3] = start[1] * end[2] - end[1] * start[2]; + p[4] = start[2] - end[2]; + p[5] = end[1] - start[1]; +} + +ID_INLINE void idPluecker::FromRay( const idVec3 &start, const idVec3 &dir ) { + p[0] = start[0] * dir[1] - dir[0] * start[1]; + p[1] = start[0] * dir[2] - dir[0] * start[2]; + p[2] = -dir[0]; + p[3] = start[1] * dir[2] - dir[1] * start[2]; + p[4] = -dir[2]; + p[5] = dir[1]; +} + +ID_INLINE bool idPluecker::ToLine( idVec3 &start, idVec3 &end ) const { + idVec3 dir1, dir2; + float d; + + dir1[0] = p[3]; + dir1[1] = -p[1]; + dir1[2] = p[0]; + + dir2[0] = -p[2]; + dir2[1] = p[5]; + dir2[2] = -p[4]; + + d = dir2 * dir2; + if ( d == 0.0f ) { + return false; // pluecker coordinate does not represent a line + } + + start = dir2.Cross(dir1) * (1.0f / d); + end = start + dir2; + return true; +} + +ID_INLINE bool idPluecker::ToRay( idVec3 &start, idVec3 &dir ) const { + idVec3 dir1; + float d; + + dir1[0] = p[3]; + dir1[1] = -p[1]; + dir1[2] = p[0]; + + dir[0] = -p[2]; + dir[1] = p[5]; + dir[2] = -p[4]; + + d = dir * dir; + if ( d == 0.0f ) { + return false; // pluecker coordinate does not represent a line + } + + start = dir.Cross(dir1) * (1.0f / d); + return true; +} + +ID_INLINE void idPluecker::ToDir( idVec3 &dir ) const { + dir[0] = -p[2]; + dir[1] = p[5]; + dir[2] = -p[4]; +} + +ID_INLINE float idPluecker::PermutedInnerProduct( const idPluecker &a ) const { + return p[0] * a.p[4] + p[1] * a.p[5] + p[2] * a.p[3] + p[4] * a.p[0] + p[5] * a.p[1] + p[3] * a.p[2]; +} + +ID_INLINE float idPluecker::Length( void ) const { + return ( float )idMath::Sqrt( p[5] * p[5] + p[4] * p[4] + p[2] * p[2] ); +} + +ID_INLINE float idPluecker::LengthSqr( void ) const { + return ( p[5] * p[5] + p[4] * p[4] + p[2] * p[2] ); +} + +ID_INLINE float idPluecker::NormalizeSelf( void ) { + float l, d; + + l = LengthSqr(); + if ( l == 0.0f ) { + return l; // pluecker coordinate does not represent a line + } + d = idMath::InvSqrt( l ); + p[0] *= d; + p[1] *= d; + p[2] *= d; + p[3] *= d; + p[4] *= d; + p[5] *= d; + return d * l; +} + +ID_INLINE idPluecker idPluecker::Normalize( void ) const { + float d; + + d = LengthSqr(); + if ( d == 0.0f ) { + return *this; // pluecker coordinate does not represent a line + } + d = idMath::InvSqrt( d ); + return idPluecker( p[0]*d, p[1]*d, p[2]*d, p[3]*d, p[4]*d, p[5]*d ); +} + +ID_INLINE int idPluecker::GetDimension( void ) const { + return 6; +} + +ID_INLINE const float *idPluecker::ToFloatPtr( void ) const { + return p; +} + +ID_INLINE float *idPluecker::ToFloatPtr( void ) { + return p; +} + +#endif /* !__MATH_PLUECKER_H__ */ diff --git a/src/idlib/math/Polynomial.cpp b/src/idlib/math/Polynomial.cpp new file mode 100644 index 0000000..847591b --- /dev/null +++ b/src/idlib/math/Polynomial.cpp @@ -0,0 +1,238 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +const float EPSILON = 1e-6f; + +/* +============= +idPolynomial::Laguer +============= +*/ +int idPolynomial::Laguer( const idComplex *coef, const int degree, idComplex &x ) const { + const int MT = 10, MAX_ITERATIONS = MT * 8; + static const float frac[] = { 0.0f, 0.5f, 0.25f, 0.75f, 0.13f, 0.38f, 0.62f, 0.88f, 1.0f }; + int i, j; + float abx, abp, abm, err; + idComplex dx, cx, b, d, f, g, s, gps, gms, g2; + + for ( i = 1; i <= MAX_ITERATIONS; i++ ) { + b = coef[degree]; + err = b.Abs(); + d.Zero(); + f.Zero(); + abx = x.Abs(); + for ( j = degree - 1; j >= 0; j-- ) { + f = x * f + d; + d = x * d + b; + b = x * b + coef[j]; + err = b.Abs() + abx * err; + } + if ( b.Abs() < err * EPSILON ) { + return i; + } + g = d / b; + g2 = g * g; + s = ( ( degree - 1 ) * ( degree * ( g2 - 2.0f * f / b ) - g2 ) ).Sqrt(); + gps = g + s; + gms = g - s; + abp = gps.Abs(); + abm = gms.Abs(); + if ( abp < abm ) { + gps = gms; + } + if ( Max( abp, abm ) > 0.0f ) { + dx = degree / gps; + } else { + dx = idMath::Exp( idMath::Log( 1.0f + abx ) ) * idComplex( idMath::Cos( i ), idMath::Sin( i ) ); + } + cx = x - dx; + if ( x == cx ) { + return i; + } + if ( i % MT == 0 ) { + x = cx; + } else { + x -= frac[i/MT] * dx; + } + } + return i; +} + +/* +============= +idPolynomial::GetRoots +============= +*/ +int idPolynomial::GetRoots( idComplex *roots ) const { + int i, j; + idComplex x, b, c, *coef; + + coef = (idComplex *) _alloca16( ( degree + 1 ) * sizeof( idComplex ) ); + for ( i = 0; i <= degree; i++ ) { + coef[i].Set( coefficient[i], 0.0f ); + } + + for ( i = degree - 1; i >= 0; i-- ) { + x.Zero(); + Laguer( coef, i + 1, x ); + if ( idMath::Fabs( x.i ) < 2.0f * EPSILON * idMath::Fabs( x.r ) ) { + x.i = 0.0f; + } + roots[i] = x; + b = coef[i+1]; + for ( j = i; j >= 0; j-- ) { + c = coef[j]; + coef[j] = b; + b = x * b + c; + } + } + + for ( i = 0; i <= degree; i++ ) { + coef[i].Set( coefficient[i], 0.0f ); + } + for ( i = 0; i < degree; i++ ) { + Laguer( coef, degree, roots[i] ); + } + + for ( i = 1; i < degree; i++ ) { + x = roots[i]; + for ( j = i - 1; j >= 0; j-- ) { + if ( roots[j].r <= x.r ) { + break; + } + roots[j+1] = roots[j]; + } + roots[j+1] = x; + } + + return degree; +} + +/* +============= +idPolynomial::GetRoots +============= +*/ +int idPolynomial::GetRoots( float *roots ) const { + int i, num; + idComplex *complexRoots; + + switch( degree ) { + case 0: return 0; + case 1: return GetRoots1( coefficient[1], coefficient[0], roots ); + case 2: return GetRoots2( coefficient[2], coefficient[1], coefficient[0], roots ); + case 3: return GetRoots3( coefficient[3], coefficient[2], coefficient[1], coefficient[0], roots ); + case 4: return GetRoots4( coefficient[4], coefficient[3], coefficient[2], coefficient[1], coefficient[0], roots ); + } + + // The Abel-Ruffini theorem states that there is no general solution + // in radicals to polynomial equations of degree five or higher. + // A polynomial equation can be solved by radicals if and only if + // its Galois group is a solvable group. + + complexRoots = (idComplex *) _alloca16( degree * sizeof( idComplex ) ); + + GetRoots( complexRoots ); + + for ( num = i = 0; i < degree; i++ ) { + if ( complexRoots[i].i == 0.0f ) { + roots[i] = complexRoots[i].r; + num++; + } + } + return num; +} + +/* +============= +idPolynomial::ToString +============= +*/ +const char *idPolynomial::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +idPolynomial::Test +============= +*/ +void idPolynomial::Test( void ) { + int i, num; + float roots[4], value; + idComplex complexRoots[4], complexValue; + idPolynomial p; + + p = idPolynomial( -5.0f, 4.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( -5.0f, 4.0f, 3.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 1.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 5.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( -5.0f, 4.0f, 3.0f, 2.0f, 1.0f ); + num = p.GetRoots( roots ); + for ( i = 0; i < num; i++ ) { + value = p.GetValue( roots[i] ); + assert( idMath::Fabs( value ) < 1e-4f ); + } + + p = idPolynomial( 1.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( complexRoots ); + for ( i = 0; i < num; i++ ) { + complexValue = p.GetValue( complexRoots[i] ); + assert( idMath::Fabs( complexValue.r ) < 1e-4f && idMath::Fabs( complexValue.i ) < 1e-4f ); + } + + p = idPolynomial( 5.0f, 4.0f, 3.0f, -2.0f ); + num = p.GetRoots( complexRoots ); + for ( i = 0; i < num; i++ ) { + complexValue = p.GetValue( complexRoots[i] ); + assert( idMath::Fabs( complexValue.r ) < 1e-4f && idMath::Fabs( complexValue.i ) < 1e-4f ); + } +} diff --git a/src/idlib/math/Polynomial.h b/src/idlib/math/Polynomial.h new file mode 100644 index 0000000..bad0239 --- /dev/null +++ b/src/idlib/math/Polynomial.h @@ -0,0 +1,625 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_POLYNOMIAL_H__ +#define __MATH_POLYNOMIAL_H__ + +/* +=============================================================================== + + Polynomial of arbitrary degree with real coefficients. + +=============================================================================== +*/ + + +class idPolynomial { +public: + idPolynomial( void ); + explicit idPolynomial( int d ); + explicit idPolynomial( float a, float b ); + explicit idPolynomial( float a, float b, float c ); + explicit idPolynomial( float a, float b, float c, float d ); + explicit idPolynomial( float a, float b, float c, float d, float e ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + idPolynomial operator-() const; + idPolynomial & operator=( const idPolynomial &p ); + + idPolynomial operator+( const idPolynomial &p ) const; + idPolynomial operator-( const idPolynomial &p ) const; + idPolynomial operator*( const float s ) const; + idPolynomial operator/( const float s ) const; + + idPolynomial & operator+=( const idPolynomial &p ); + idPolynomial & operator-=( const idPolynomial &p ); + idPolynomial & operator*=( const float s ); + idPolynomial & operator/=( const float s ); + + bool Compare( const idPolynomial &p ) const; // exact compare, no epsilon + bool Compare( const idPolynomial &p, const float epsilon ) const;// compare with epsilon + bool operator==( const idPolynomial &p ) const; // exact compare, no epsilon + bool operator!=( const idPolynomial &p ) const; // exact compare, no epsilon + + void Zero( void ); + void Zero( int d ); + + int GetDimension( void ) const; // get the degree of the polynomial + int GetDegree( void ) const; // get the degree of the polynomial + float GetValue( const float x ) const; // evaluate the polynomial with the given real value + idComplex GetValue( const idComplex &x ) const; // evaluate the polynomial with the given complex value + idPolynomial GetDerivative( void ) const; // get the first derivative of the polynomial + idPolynomial GetAntiDerivative( void ) const; // get the anti derivative of the polynomial + + int GetRoots( idComplex *roots ) const; // get all roots + int GetRoots( float *roots ) const; // get the real roots + + static int GetRoots1( float a, float b, float *roots ); + static int GetRoots2( float a, float b, float c, float *roots ); + static int GetRoots3( float a, float b, float c, float d, float *roots ); + static int GetRoots4( float a, float b, float c, float d, float e, float *roots ); + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + static void Test( void ); + +private: + int degree; + int allocated; + float * coefficient; + + void Resize( int d, bool keep ); + int Laguer( const idComplex *coef, const int degree, idComplex &r ) const; +}; + +ID_INLINE idPolynomial::idPolynomial( void ) { + degree = -1; + allocated = 0; + coefficient = NULL; +} + +ID_INLINE idPolynomial::idPolynomial( int d ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( d, false ); +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 1, false ); + coefficient[0] = b; + coefficient[1] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 2, false ); + coefficient[0] = c; + coefficient[1] = b; + coefficient[2] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c, float d ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 3, false ); + coefficient[0] = d; + coefficient[1] = c; + coefficient[2] = b; + coefficient[3] = a; +} + +ID_INLINE idPolynomial::idPolynomial( float a, float b, float c, float d, float e ) { + degree = -1; + allocated = 0; + coefficient = NULL; + Resize( 4, false ); + coefficient[0] = e; + coefficient[1] = d; + coefficient[2] = c; + coefficient[3] = b; + coefficient[4] = a; +} + +ID_INLINE float idPolynomial::operator[]( int index ) const { + assert( index >= 0 && index <= degree ); + return coefficient[ index ]; +} + +ID_INLINE float& idPolynomial::operator[]( int index ) { + assert( index >= 0 && index <= degree ); + return coefficient[ index ]; +} + +ID_INLINE idPolynomial idPolynomial::operator-() const { + int i; + idPolynomial n; + + n = *this; + for ( i = 0; i <= degree; i++ ) { + n[i] = -n[i]; + } + return n; +} + +ID_INLINE idPolynomial &idPolynomial::operator=( const idPolynomial &p ) { + Resize( p.degree, false ); + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = p.coefficient[i]; + } + return *this; +} + +ID_INLINE idPolynomial idPolynomial::operator+( const idPolynomial &p ) const { + int i; + idPolynomial n; + + if ( degree > p.degree ) { + n.Resize( degree, false ); + for ( i = 0; i <= p.degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + } + for ( ; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i]; + } + n.degree = degree; + } else if ( p.degree > degree ) { + n.Resize( p.degree, false ); + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + n.coefficient[i] = p.coefficient[i]; + } + n.degree = p.degree; + } else { + n.Resize( degree, false ); + n.degree = 0; + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] + p.coefficient[i]; + if ( n.coefficient[i] != 0.0f ) { + n.degree = i; + } + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator-( const idPolynomial &p ) const { + int i; + idPolynomial n; + + if ( degree > p.degree ) { + n.Resize( degree, false ); + for ( i = 0; i <= p.degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + } + for ( ; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i]; + } + n.degree = degree; + } else if ( p.degree >= degree ) { + n.Resize( p.degree, false ); + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + n.coefficient[i] = - p.coefficient[i]; + } + n.degree = p.degree; + } else { + n.Resize( degree, false ); + n.degree = 0; + for ( i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] - p.coefficient[i]; + if ( n.coefficient[i] != 0.0f ) { + n.degree = i; + } + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator*( const float s ) const { + idPolynomial n; + + if ( s == 0.0f ) { + n.degree = 0; + } else { + n.Resize( degree, false ); + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] * s; + } + } + return n; +} + +ID_INLINE idPolynomial idPolynomial::operator/( const float s ) const { + float invs; + idPolynomial n; + + assert( s != 0.0f ); + n.Resize( degree, false ); + invs = 1.0f / s; + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i] = coefficient[i] * invs; + } + return n; +} + +ID_INLINE idPolynomial &idPolynomial::operator+=( const idPolynomial &p ) { + int i; + + if ( degree > p.degree ) { + for ( i = 0; i <= p.degree; i++ ) { + coefficient[i] += p.coefficient[i]; + } + } else if ( p.degree > degree ) { + Resize( p.degree, true ); + for ( i = 0; i <= degree; i++ ) { + coefficient[i] += p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + coefficient[i] = p.coefficient[i]; + } + } else { + for ( i = 0; i <= degree; i++ ) { + coefficient[i] += p.coefficient[i]; + if ( coefficient[i] != 0.0f ) { + degree = i; + } + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator-=( const idPolynomial &p ) { + int i; + + if ( degree > p.degree ) { + for ( i = 0; i <= p.degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + } + } else if ( p.degree > degree ) { + Resize( p.degree, true ); + for ( i = 0; i <= degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + } + for ( ; i <= p.degree; i++ ) { + coefficient[i] = - p.coefficient[i]; + } + } else { + for ( i = 0; i <= degree; i++ ) { + coefficient[i] -= p.coefficient[i]; + if ( coefficient[i] != 0.0f ) { + degree = i; + } + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator*=( const float s ) { + if ( s == 0.0f ) { + degree = 0; + } else { + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] *= s; + } + } + return *this; +} + +ID_INLINE idPolynomial &idPolynomial::operator/=( const float s ) { + float invs; + + assert( s != 0.0f ); + invs = 1.0f / s; + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = invs; + } + return *this;; +} + +ID_INLINE bool idPolynomial::Compare( const idPolynomial &p ) const { + if ( degree != p.degree ) { + return false; + } + for ( int i = 0; i <= degree; i++ ) { + if ( coefficient[i] != p.coefficient[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idPolynomial::Compare( const idPolynomial &p, const float epsilon ) const { + if ( degree != p.degree ) { + return false; + } + for ( int i = 0; i <= degree; i++ ) { + if ( idMath::Fabs( coefficient[i] - p.coefficient[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idPolynomial::operator==( const idPolynomial &p ) const { + return Compare( p ); +} + +ID_INLINE bool idPolynomial::operator!=( const idPolynomial &p ) const { + return !Compare( p ); +} + +ID_INLINE void idPolynomial::Zero( void ) { + degree = 0; +} + +ID_INLINE void idPolynomial::Zero( int d ) { + Resize( d, false ); + for ( int i = 0; i <= degree; i++ ) { + coefficient[i] = 0.0f; + } +} + +ID_INLINE int idPolynomial::GetDimension( void ) const { + return degree; +} + +ID_INLINE int idPolynomial::GetDegree( void ) const { + return degree; +} + +ID_INLINE float idPolynomial::GetValue( const float x ) const { + float y, z; + y = coefficient[0]; + z = x; + for ( int i = 1; i <= degree; i++ ) { + y += coefficient[i] * z; + z *= x; + } + return y; +} + +ID_INLINE idComplex idPolynomial::GetValue( const idComplex &x ) const { + idComplex y, z; + y.Set( coefficient[0], 0.0f ); + z = x; + for ( int i = 1; i <= degree; i++ ) { + y += coefficient[i] * z; + z *= x; + } + return y; +} + +ID_INLINE idPolynomial idPolynomial::GetDerivative( void ) const { + idPolynomial n; + + if ( degree == 0 ) { + return n; + } + n.Resize( degree - 1, false ); + for ( int i = 1; i <= degree; i++ ) { + n.coefficient[i-1] = i * coefficient[i]; + } +} + +ID_INLINE idPolynomial idPolynomial::GetAntiDerivative( void ) const { + idPolynomial n; + + if ( degree == 0 ) { + return n; + } + n.Resize( degree + 1, false ); + n.coefficient[0] = 0.0f; + for ( int i = 0; i <= degree; i++ ) { + n.coefficient[i+1] = coefficient[i] / ( i + 1 ); + } +} + +ID_INLINE int idPolynomial::GetRoots1( float a, float b, float *roots ) { + assert( a != 0.0f ); + roots[0] = - b / a; + return 1; +} + +ID_INLINE int idPolynomial::GetRoots2( float a, float b, float c, float *roots ) { + float inva, ds; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + c *= inva; + b *= inva; + } + ds = b * b - 4.0f * c; + if ( ds < 0.0f ) { + return 0; + } else if ( ds > 0.0f ) { + ds = idMath::Sqrt( ds ); + roots[0] = 0.5f * ( -b - ds ); + roots[1] = 0.5f * ( -b + ds ); + return 2; + } else { + roots[0] = 0.5f * -b; + return 1; + } +} + +ID_INLINE int idPolynomial::GetRoots3( float a, float b, float c, float d, float *roots ) { + float inva, f, g, halfg, ofs, ds, dist, angle, cs, ss, t; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + d *= inva; + c *= inva; + b *= inva; + } + + f = ( 1.0f / 3.0f ) * ( 3.0f * c - b * b ); + g = ( 1.0f / 27.0f ) * ( 2.0f * b * b * b - 9.0f * c * b + 27.0f * d ); + halfg = 0.5f * g; + ofs = ( 1.0f / 3.0f ) * b; + ds = 0.25f * g * g + ( 1.0f / 27.0f ) * f * f * f; + + if ( ds < 0.0f ) { + dist = idMath::Sqrt( ( -1.0f / 3.0f ) * f ); + angle = ( 1.0f / 3.0f ) * idMath::ATan( idMath::Sqrt( -ds ), -halfg ); + cs = idMath::Cos( angle ); + ss = idMath::Sin( angle ); + roots[0] = 2.0f * dist * cs - ofs; + roots[1] = -dist * ( cs + idMath::SQRT_THREE * ss ) - ofs; + roots[2] = -dist * ( cs - idMath::SQRT_THREE * ss ) - ofs; + return 3; + } else if ( ds > 0.0f ) { + ds = idMath::Sqrt( ds ); + t = -halfg + ds; + if ( t >= 0.0f ) { + roots[0] = idMath::Pow( t, ( 1.0f / 3.0f ) ); + } else { + roots[0] = -idMath::Pow( -t, ( 1.0f / 3.0f ) ); + } + t = -halfg - ds; + if ( t >= 0.0f ) { + roots[0] += idMath::Pow( t, ( 1.0f / 3.0f ) ); + } else { + roots[0] -= idMath::Pow( -t, ( 1.0f / 3.0f ) ); + } + roots[0] -= ofs; + return 1; + } else { + if ( halfg >= 0.0f ) { + t = -idMath::Pow( halfg, ( 1.0f / 3.0f ) ); + } else { + t = idMath::Pow( -halfg, ( 1.0f / 3.0f ) ); + } + roots[0] = 2.0f * t - ofs; + roots[1] = -t - ofs; + roots[2] = roots[1]; + return 3; + } +} + +ID_INLINE int idPolynomial::GetRoots4( float a, float b, float c, float d, float e, float *roots ) { + int count; + float inva, y, ds, r, s1, s2, t1, t2, tp, tm; + float roots3[3]; + + if ( a != 1.0f ) { + assert( a != 0.0f ); + inva = 1.0f / a; + e *= inva; + d *= inva; + c *= inva; + b *= inva; + } + + count = 0; + + GetRoots3( 1.0f, -c, b * d - 4.0f * e, -b * b * e + 4.0f * c * e - d * d, roots3 ); + y = roots3[0]; + ds = 0.25f * b * b - c + y; + + if ( ds < 0.0f ) { + return 0; + } else if ( ds > 0.0f ) { + r = idMath::Sqrt( ds ); + t1 = 0.75f * b * b - r * r - 2.0f * c; + t2 = ( 4.0f * b * c - 8.0f * d - b * b * b ) / ( 4.0f * r ); + tp = t1 + t2; + tm = t1 - t2; + + if ( tp >= 0.0f ) { + s1 = idMath::Sqrt( tp ); + roots[count++] = -0.25f * b + 0.5f * ( r + s1 ); + roots[count++] = -0.25f * b + 0.5f * ( r - s1 ); + } + if ( tm >= 0.0f ) { + s2 = idMath::Sqrt( tm ); + roots[count++] = -0.25f * b + 0.5f * ( s2 - r ); + roots[count++] = -0.25f * b - 0.5f * ( s2 + r ); + } + return count; + } else { + t2 = y * y - 4.0f * e; + if ( t2 >= 0.0f ) { + t2 = 2.0f * idMath::Sqrt( t2 ); + t1 = 0.75f * b * b - 2.0f * c; + if ( t1 + t2 >= 0.0f ) { + s1 = idMath::Sqrt( t1 + t2 ); + roots[count++] = -0.25f * b + 0.5f * s1; + roots[count++] = -0.25f * b - 0.5f * s1; + } + if ( t1 - t2 >= 0.0f ) { + s2 = idMath::Sqrt( t1 - t2 ); + roots[count++] = -0.25f * b + 0.5f * s2; + roots[count++] = -0.25f * b - 0.5f * s2; + } + } + return count; + } +} + +ID_INLINE const float *idPolynomial::ToFloatPtr( void ) const { + return coefficient; +} + +ID_INLINE float *idPolynomial::ToFloatPtr( void ) { + return coefficient; +} + +ID_INLINE void idPolynomial::Resize( int d, bool keep ) { + int alloc = ( d + 1 + 3 ) & ~3; + if ( alloc > allocated ) { +// RAVEN BEGIN + float *ptr = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +// RAVEN END + if ( coefficient != NULL ) { + if ( keep ) { + for ( int i = 0; i <= degree; i++ ) { + ptr[i] = coefficient[i]; + } + } + Mem_Free16( coefficient ); + } + allocated = alloc; + coefficient = ptr; + } + degree = d; +} + +#endif /* !__MATH_POLYNOMIAL_H__ */ diff --git a/src/idlib/math/Quat.cpp b/src/idlib/math/Quat.cpp new file mode 100644 index 0000000..fc5fb5e --- /dev/null +++ b/src/idlib/math/Quat.cpp @@ -0,0 +1,251 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +/* +===================== +idQuat::ToAngles +===================== +*/ +idAngles idQuat::ToAngles( void ) const { + return ToMat3().ToAngles(); +} + +/* +===================== +idQuat::ToRotation +===================== +*/ +idRotation idQuat::ToRotation( void ) const { + idVec3 vec; + float angle; + + vec.x = x; + vec.y = y; + vec.z = z; + angle = idMath::ACos( w ); + if ( angle == 0.0f ) { + vec.Set( 0.0f, 0.0f, 1.0f ); + } else { + //vec *= (1.0f / sin( angle )); + vec.Normalize(); + vec.FixDegenerateNormal(); + angle *= 2.0f * idMath::M_RAD2DEG; + } + return idRotation( vec3_origin, vec, angle ); +} + +/* +===================== +idQuat::ToMat3 +===================== +*/ +idMat3 idQuat::ToMat3( void ) const { + idMat3 mat; + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + + xx = x * x2; + xy = x * y2; + xz = x * z2; + + yy = y * y2; + yz = y * z2; + zz = z * z2; + + wx = w * x2; + wy = w * y2; + wz = w * z2; + + mat[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + mat[ 0 ][ 1 ] = xy - wz; + mat[ 0 ][ 2 ] = xz + wy; + + mat[ 1 ][ 0 ] = xy + wz; + mat[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + mat[ 1 ][ 2 ] = yz - wx; + + mat[ 2 ][ 0 ] = xz - wy; + mat[ 2 ][ 1 ] = yz + wx; + mat[ 2 ][ 2 ] = 1.0f - ( xx + yy ); + + return mat; +} + +/* +===================== +idQuat::ToMat4 +===================== +*/ +idMat4 idQuat::ToMat4( void ) const { + return ToMat3().ToMat4(); +} + +/* +===================== +idQuat::ToCQuat +===================== +*/ +idCQuat idQuat::ToCQuat( void ) const { + if ( w < 0.0f ) { + return idCQuat( -x, -y, -z ); + } + return idCQuat( x, y, z ); +} + +/* +============ +idQuat::ToAngularVelocity +============ +*/ +idVec3 idQuat::ToAngularVelocity( void ) const { + idVec3 vec; + + vec.x = x; + vec.y = y; + vec.z = z; + vec.Normalize(); + return vec * idMath::ACos( w ); +} + +/* +============= +idQuat::ToString +============= +*/ +const char *idQuat::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +===================== +idQuat::Slerp + +Spherical linear interpolation between two quaternions. +===================== +*/ +idQuat &idQuat::Slerp( const idQuat &from, const idQuat &to, float t ) { +#ifdef _XENON +#else + idQuat temp; + float omega, cosom, sinom, scale0, scale1; + + if ( t <= 0.0f ) { + *this = from; + return *this; + } + + if ( t >= 1.0f ) { + *this = to; + return *this; + } + + if ( from == to ) { + *this = to; + return *this; + } + + cosom = from.x * to.x + from.y * to.y + from.z * to.z + from.w * to.w; + if ( cosom < 0.0f ) { + temp = -to; + cosom = -cosom; + } else { + temp = to; + } + + if ( ( 1.0f - cosom ) > 1e-6f ) { +#if 0 + omega = acos( cosom ); + sinom = 1.0f / idMath::Sin( omega ); + scale0 = idMath::Sin( ( 1.0f - t ) * omega ) * sinom; + scale1 = idMath::Sin( t * omega ) * sinom; +#else + scale0 = 1.0f - cosom * cosom; + sinom = idMath::InvSqrt( scale0 ); + omega = idMath::ATan16( scale0 * sinom, cosom ); + scale0 = idMath::Sin16( ( 1.0f - t ) * omega ) * sinom; + scale1 = idMath::Sin16( t * omega ) * sinom; +#endif + } else { + scale0 = 1.0f - t; + scale1 = t; + } + + *this = ( scale0 * from ) + ( scale1 * temp ); + return *this; +#endif +} + +/* +============= +idCQuat::ToAngles +============= +*/ +idAngles idCQuat::ToAngles( void ) const { + return ToQuat().ToAngles(); +} + +/* +============= +idCQuat::ToRotation +============= +*/ +idRotation idCQuat::ToRotation( void ) const { + return ToQuat().ToRotation(); +} + +/* +============= +idCQuat::ToMat3 +============= +*/ +idMat3 idCQuat::ToMat3( void ) const { + return ToQuat().ToMat3(); +} + +/* +============= +idCQuat::ToMat4 +============= +*/ +idMat4 idCQuat::ToMat4( void ) const { + return ToQuat().ToMat4(); +} + +/* +============= +idCQuat::ToString +============= +*/ +const char *idCQuat::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/src/idlib/math/Quat.h b/src/idlib/math/Quat.h new file mode 100644 index 0000000..32c645e --- /dev/null +++ b/src/idlib/math/Quat.h @@ -0,0 +1,401 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_QUAT_H__ +#define __MATH_QUAT_H__ + +/* +=============================================================================== + + Quaternion + +=============================================================================== +*/ + + +class idVec3; +class idAngles; +class idRotation; +class idMat3; +class idMat4; +class idCQuat; + +class idQuat { +public: + float x; + float y; + float z; + float w; + + idQuat( void ); + idQuat( float x, float y, float z, float w ); + + void Set( float x, float y, float z, float w ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idQuat operator-() const; + idQuat & operator=( const idQuat &a ); + idQuat operator+( const idQuat &a ) const; + idQuat & operator+=( const idQuat &a ); + idQuat operator-( const idQuat &a ) const; + idQuat & operator-=( const idQuat &a ); + idQuat operator*( const idQuat &a ) const; + idVec3 operator*( const idVec3 &a ) const; + idQuat operator*( float a ) const; + idQuat & operator*=( const idQuat &a ); + idQuat & operator*=( float a ); + + friend idQuat operator*( const float a, const idQuat &b ); + friend idVec3 operator*( const idVec3 &a, const idQuat &b ); + + bool Compare( const idQuat &a ) const; // exact compare, no epsilon + bool Compare( const idQuat &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idQuat &a ) const; // exact compare, no epsilon + bool operator!=( const idQuat &a ) const; // exact compare, no epsilon + + idQuat Inverse( void ) const; + float Length( void ) const; + idQuat & Normalize( void ); + + float CalcW( void ) const; + int GetDimension( void ) const; + + idAngles ToAngles( void ) const; + idRotation ToRotation( void ) const; + idMat3 ToMat3( void ) const; + idMat4 ToMat4( void ) const; + idCQuat ToCQuat( void ) const; + idVec3 ToAngularVelocity( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + idQuat & Slerp( const idQuat &from, const idQuat &to, float t ); +}; + +ID_INLINE idQuat::idQuat( void ) { +} + +ID_INLINE idQuat::idQuat( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE float idQuat::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +ID_INLINE float& idQuat::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +ID_INLINE idQuat idQuat::operator-() const { + return idQuat( -x, -y, -z, -w ); +} + +ID_INLINE idQuat &idQuat::operator=( const idQuat &a ) { + x = a.x; + y = a.y; + z = a.z; + w = a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator+( const idQuat &a ) const { + return idQuat( x + a.x, y + a.y, z + a.z, w + a.w ); +} + +ID_INLINE idQuat& idQuat::operator+=( const idQuat &a ) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator-( const idQuat &a ) const { + return idQuat( x - a.x, y - a.y, z - a.z, w - a.w ); +} + +ID_INLINE idQuat& idQuat::operator-=( const idQuat &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +ID_INLINE idQuat idQuat::operator*( const idQuat &a ) const { + return idQuat( w*a.x + x*a.w + y*a.z - z*a.y, + w*a.y + y*a.w + z*a.x - x*a.z, + w*a.z + z*a.w + x*a.y - y*a.x, + w*a.w - x*a.x - y*a.y - z*a.z ); +} + +ID_INLINE idVec3 idQuat::operator*( const idVec3 &a ) const { +#if 0 + // it's faster to do the conversion to a 3x3 matrix and multiply the vector by this 3x3 matrix + return ( ToMat3() * a ); +#else + // result = this->Inverse() * idQuat( a.x, a.y, a.z, 0.0f ) * (*this) + float xx = x * x; + float zz = z * z; + float ww = w * w; + float yy = y * y; + + float xw2 = x*w*2.0f; + float xy2 = x*y*2.0f; + float xz2 = x*z*2.0f; + float yw2 = y*w*2.0f; + float yz2 = y*z*2.0f; + float zw2 = z*w*2.0f; + + return idVec3( + ( xx - yy - zz + ww ) * a.x + ( xy2 + zw2 ) * a.y + ( xz2 - yw2 ) * a.z, + ( xy2 - zw2 ) * a.x + ( -xx + yy - zz + ww )* a.y + ( yz2 + xw2 ) * a.z, + ( xz2 + yw2 ) * a.x + ( yz2 - xw2 ) * a.y + ( -xx - yy + zz + ww )* a.z ); +#endif +} + +ID_INLINE idQuat idQuat::operator*( float a ) const { + return idQuat( x * a, y * a, z * a, w * a ); +} + +ID_INLINE idQuat operator*( const float a, const idQuat &b ) { + return b * a; +} + +ID_INLINE idVec3 operator*( const idVec3 &a, const idQuat &b ) { + return b * a; +} + +ID_INLINE idQuat& idQuat::operator*=( const idQuat &a ) { + *this = *this * a; + + return *this; +} + +ID_INLINE idQuat& idQuat::operator*=( float a ) { + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +ID_INLINE bool idQuat::Compare( const idQuat &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) && ( w == a.w ) ); +} + +ID_INLINE bool idQuat::Compare( const idQuat &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + if ( idMath::Fabs( w - a.w ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idQuat::operator==( const idQuat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idQuat::operator!=( const idQuat &a ) const { + return !Compare( a ); +} + +ID_INLINE void idQuat::Set( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE idQuat idQuat::Inverse( void ) const { + return idQuat( -x, -y, -z, w ); +} + +ID_INLINE float idQuat::Length( void ) const { + float len; + + len = x * x + y * y + z * z + w * w; + return idMath::Sqrt( len ); +} + +ID_INLINE idQuat& idQuat::Normalize( void ) { + float len; + float ilength; + + len = this->Length(); + if ( len ) { + ilength = 1 / len; + x *= ilength; + y *= ilength; + z *= ilength; + w *= ilength; + } + return *this; +} + +ID_INLINE float idQuat::CalcW( void ) const { + // take the absolute value because floating point rounding may cause the dot of x,y,z to be larger than 1 + return idMath::Sqrt( fabs( 1.0f - ( x * x + y * y + z * z ) ) ); +} + +ID_INLINE int idQuat::GetDimension( void ) const { + return 4; +} + +ID_INLINE const float *idQuat::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idQuat::ToFloatPtr( void ) { + return &x; +} + + +/* +=============================================================================== + + Compressed quaternion + +=============================================================================== +*/ + +class idCQuat { +public: + float x; + float y; + float z; + + idCQuat( void ); + idCQuat( float x, float y, float z ); + + void Set( float x, float y, float z ); + + float operator[]( int index ) const; + float & operator[]( int index ); + + bool Compare( const idCQuat &a ) const; // exact compare, no epsilon + bool Compare( const idCQuat &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idCQuat &a ) const; // exact compare, no epsilon + bool operator!=( const idCQuat &a ) const; // exact compare, no epsilon + + int GetDimension( void ) const; + + idAngles ToAngles( void ) const; + idRotation ToRotation( void ) const; + idMat3 ToMat3( void ) const; + idMat4 ToMat4( void ) const; + idQuat ToQuat( void ) const; + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; +}; + +ID_INLINE idCQuat::idCQuat( void ) { +} + +ID_INLINE idCQuat::idCQuat( float x, float y, float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE void idCQuat::Set( float x, float y, float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE float idCQuat::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +ID_INLINE float& idCQuat::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +ID_INLINE bool idCQuat::Compare( const idCQuat &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) ); +} + +ID_INLINE bool idCQuat::Compare( const idCQuat &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + return true; +} + +ID_INLINE bool idCQuat::operator==( const idCQuat &a ) const { + return Compare( a ); +} + +ID_INLINE bool idCQuat::operator!=( const idCQuat &a ) const { + return !Compare( a ); +} + +ID_INLINE int idCQuat::GetDimension( void ) const { + return 3; +} + +ID_INLINE idQuat idCQuat::ToQuat( void ) const { + // take the absolute value because floating point rounding may cause the dot of x,y,z to be larger than 1 + return idQuat( x, y, z, idMath::Sqrt( fabs( 1.0f - ( x * x + y * y + z * z ) ) ) ); +} + +ID_INLINE const float *idCQuat::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idCQuat::ToFloatPtr( void ) { + return &x; +} + +#endif /* !__MATH_QUAT_H__ */ diff --git a/src/idlib/math/Radians.cpp b/src/idlib/math/Radians.cpp new file mode 100644 index 0000000..402af57 --- /dev/null +++ b/src/idlib/math/Radians.cpp @@ -0,0 +1,159 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include + +/* +================= +rvAngles::NormalizeFull + +returns angles normalized to the range [0 <= angle < TWO_PI] +================= +*/ +rvAngles &rvAngles::NormalizeFull( void ) +{ + // Get to -TWO_PI < a < TWO_PI + pitch = fmodf( pitch, idMath::TWO_PI ); + yaw = fmodf( yaw, idMath::TWO_PI ); + roll = fmodf( roll, idMath::TWO_PI ); + + // Fix any negatives + if( pitch < 0.0f ) + { + pitch += idMath::TWO_PI; + } + + if( yaw < 0.0f ) + { + yaw += idMath::TWO_PI; + } + + if( roll < 0.0f ) + { + roll += idMath::TWO_PI; + } + + return( *this ); +} + +/* +================= +rvAngles::NormalizeHalf + +returns angles normalized to the range [-PI < angle <= PI] +================= +*/ +rvAngles &rvAngles::NormalizeHalf( void ) +{ + int i; + + // Get to -TWO_PI < a < TWO_PI + pitch = fmodf( pitch, idMath::TWO_PI ); + yaw = fmodf( yaw, idMath::TWO_PI ); + roll = fmodf( roll, idMath::TWO_PI ); + + for( i = 0; i < 3; i++ ) + { + if( ( *this )[i] < -idMath::PI ) + { + ( *this )[i] += idMath::TWO_PI; + } + + if( ( *this )[i] > idMath::PI ) + { + ( *this )[i] -= idMath::TWO_PI; + } + } + + return( *this ); +} + +/* +================= +rvAngles::ToVectors +================= +*/ +void rvAngles::ToVectors( idVec3 *forward, idVec3 *right, idVec3 *up ) const +{ + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( yaw, sy, cy ); + idMath::SinCos( pitch, sp, cp ); + idMath::SinCos( roll, sr, cr ); + + if( forward ) + { + forward->Set( cp * cy, cp * sy, -sp ); + } + + if( right ) + { + right->Set( -sr * sp * cy + cr * sy, -sr * sp * sy + -cr * cy, -sr * cp ); + } + + if( up ) + { + up->Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + } +} + +/* +================= +rvAngles::ToForward +================= +*/ +idVec3 rvAngles::ToForward( void ) const +{ + float sp, sy, cp, cy; + + idMath::SinCos( yaw, sy, cy ); + idMath::SinCos( pitch, sp, cp ); + + return( idVec3( cp * cy, cp * sy, -sp ) ); +} + +/* +================= +rvAngles::ToMat3 +================= +*/ +idMat3 &rvAngles::ToMat3( idMat3 &mat ) const +{ + float sr, sp, sy, cr, cp, cy; + + idMath::SinCos( yaw, sy, cy ); + idMath::SinCos( pitch, sp, cp ); + idMath::SinCos( roll, sr, cr ); + + mat[0].Set( cp * cy, cp * sy, -sp ); + mat[1].Set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + mat[2].Set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + + return( mat ); +} + +// end diff --git a/src/idlib/math/Radians.h b/src/idlib/math/Radians.h new file mode 100644 index 0000000..4a8b681 --- /dev/null +++ b/src/idlib/math/Radians.h @@ -0,0 +1,276 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _MATH_RADIANS_H_INC_ +#define _MATH_RADIANS_H_INC_ + +/* +=============================================================================== + Euler angles + + This is basically a duplicate of idAngles, but used radians rather than + degrees (to avoid the conversion before trig calls) + + All trig calls use float precision + + ToMat3 passes in workspace to avoid a memcpy + +=============================================================================== +*/ + +#ifndef M_PI +# define M_PI (3.1415926536f) +#endif + +class idVec3; +class idMat3; + +class rvAngles +{ +public: + float pitch; + float yaw; + float roll; + + rvAngles( void ) {} + rvAngles( float pitch, float yaw, float roll ); + explicit rvAngles( const idVec3 &v ); + + void Set( float pitch, float yaw, float roll ); + rvAngles & Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + rvAngles operator-() const; + rvAngles & operator=( const rvAngles &a ); + rvAngles & operator=( const idVec3 &a ); + rvAngles operator+( const rvAngles &a ) const; + rvAngles operator+( const idVec3 &a ) const; + rvAngles & operator+=( const rvAngles &a ); + rvAngles & operator+=( const idVec3 &a ); + rvAngles operator-( const rvAngles &a ) const; + rvAngles operator-( const idVec3 &a ) const; + rvAngles & operator-=( const rvAngles &a ); + rvAngles & operator-=( const idVec3 &a ); + rvAngles operator*( const float a ) const; + rvAngles & operator*=( const float a ); + + friend rvAngles operator+( const idVec3 &a, const rvAngles &b ); + friend rvAngles operator-( const idVec3 &a, const rvAngles &b ); + friend rvAngles operator*( const float a, const rvAngles &b ); + + bool Compare( const rvAngles &a ) const; // exact compare, no epsilon + bool Compare( const rvAngles &a, const float epsilon ) const; // compare with epsilon + bool operator==( const rvAngles &a ) const; // exact compare, no epsilon + bool operator!=( const rvAngles &a ) const; // exact compare, no epsilon + + rvAngles & NormalizeFull( void ); // normalizes 'this' + rvAngles & NormalizeHalf( void ); // normalizes 'this' + + void ToVectors( idVec3 *forward, idVec3 *right = NULL, idVec3 *up = NULL ) const; + idVec3 ToForward( void ) const; + idMat3 &ToMat3( idMat3 &mat ) const; + + const float * ToFloatPtr( void ) const { return( &pitch ); } + float * ToFloatPtr( void ) { return( &pitch ); } +}; + +ID_INLINE rvAngles::rvAngles( float pitch, float yaw, float roll ) +{ + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE rvAngles::rvAngles( const idVec3 &v ) +{ + this->pitch = v[0]; + this->yaw = v[1]; + this->roll = v[2]; +} + +ID_INLINE void rvAngles::Set( float pitch, float yaw, float roll ) +{ + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +ID_INLINE rvAngles &rvAngles::Zero( void ) +{ + pitch = 0.0f; + yaw = 0.0f; + roll = 0.0f; + return( *this ); +} + +ID_INLINE float rvAngles::operator[]( int index ) const +{ + assert( ( index >= 0 ) && ( index < 3 ) ); + return( ( &pitch )[ index ] ); +} + +ID_INLINE float &rvAngles::operator[]( int index ) +{ + assert( ( index >= 0 ) && ( index < 3 ) ); + return( ( &pitch )[ index ] ); +} + +ID_INLINE rvAngles rvAngles::operator-( void ) const +{ + return( rvAngles( -pitch, -yaw, -roll ) ); +} + +ID_INLINE rvAngles &rvAngles::operator=( const rvAngles &a ) +{ + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; + return( *this ); +} + +ID_INLINE rvAngles &rvAngles::operator=( const idVec3 &a ) +{ + pitch = a.x; + yaw = a.y; + roll = a.z; + return( *this ); +} + +ID_INLINE rvAngles rvAngles::operator+( const rvAngles &a ) const +{ + return( rvAngles( pitch + a.pitch, yaw + a.yaw, roll + a.roll ) ); +} + +ID_INLINE rvAngles rvAngles::operator+( const idVec3 &a ) const +{ + return( rvAngles( pitch + a.x, yaw + a.y, roll + a.z ) ); +} + +ID_INLINE rvAngles& rvAngles::operator+=( const rvAngles &a ) +{ + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + return( *this ); +} + +ID_INLINE rvAngles& rvAngles::operator+=( const idVec3 &a ) +{ + pitch += a.x; + yaw += a.y; + roll += a.z; + return( *this ); +} + +ID_INLINE rvAngles rvAngles::operator-( const rvAngles &a ) const +{ + return( rvAngles( pitch - a.pitch, yaw - a.yaw, roll - a.roll ) ); +} + +ID_INLINE rvAngles rvAngles::operator-( const idVec3 &a ) const +{ + return( rvAngles( pitch - a.x, yaw - a.y, roll - a.z ) ); +} + +ID_INLINE rvAngles& rvAngles::operator-=( const rvAngles &a ) +{ + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + return( *this ); +} + +ID_INLINE rvAngles& rvAngles::operator-=( const idVec3 &a ) +{ + pitch -= a.x; + yaw -= a.y; + roll -= a.z; + return( *this ); +} + +ID_INLINE rvAngles rvAngles::operator*( const float a ) const +{ + return( rvAngles( pitch * a, yaw * a, roll * a ) ); +} + +ID_INLINE rvAngles& rvAngles::operator*=( float a ) +{ + pitch *= a; + yaw *= a; + roll *= a; + return( *this ); +} + +ID_INLINE rvAngles operator+( const idVec3 &a, const rvAngles &b ) +{ + return( rvAngles( a.x + b.pitch, a.y + b.yaw, a.z + b.roll ) ); +} + +ID_INLINE rvAngles operator-( const idVec3 &a, const rvAngles &b ) +{ + return( rvAngles( a.x - b.pitch, a.y - b.yaw, a.z - b.roll ) ); +} + +ID_INLINE rvAngles operator*( const float a, const rvAngles &b ) +{ + return( rvAngles( a * b.pitch, a * b.yaw, a * b.roll ) ); +} + +ID_INLINE bool rvAngles::Compare( const rvAngles &a ) const +{ + return( ( a.pitch == pitch ) && ( a.yaw == yaw ) && ( a.roll == roll ) ); +} + +ID_INLINE bool rvAngles::Compare( const rvAngles &a, const float epsilon ) const +{ + if( idMath::Fabs( pitch - a.pitch ) > epsilon ) + { + return( false ); + } + + if( idMath::Fabs( yaw - a.yaw ) > epsilon ) + { + return( false ); + } + + if( idMath::Fabs( roll - a.roll ) > epsilon ) + { + return( false ); + } + + return( true ); +} + +ID_INLINE bool rvAngles::operator==( const rvAngles &a ) const +{ + return( Compare( a ) ); +} + +ID_INLINE bool rvAngles::operator!=( const rvAngles &a ) const +{ + return( !Compare( a ) ); +} + +#endif // _MATH_RADIANS_H_INC_ diff --git a/src/idlib/math/Random.h b/src/idlib/math/Random.h new file mode 100644 index 0000000..b1473e0 --- /dev/null +++ b/src/idlib/math/Random.h @@ -0,0 +1,154 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_RANDOM_H__ +#define __MATH_RANDOM_H__ + +/* +=============================================================================== + + Random number generator + +=============================================================================== +*/ + +class idRandom { +public: + idRandom( int seed = 0 ); + + void SetSeed( int seed ); + int GetSeed( void ) const; + + int RandomInt( void ); // random integer in the range [0, MAX_RAND] + int RandomInt( int max ); // random integer in the range [0, max[ + float RandomFloat( void ); // random number in the range [0.0f, 1.0f] + float CRandomFloat( void ); // random number in the range [-1.0f, 1.0f] + + static const int MAX_RAND = 0x7fff; + +private: + int seed; +}; + +ID_INLINE idRandom::idRandom( int seed ) { + this->seed = seed; +} + +ID_INLINE void idRandom::SetSeed( int seed ) { + this->seed = seed; +} + +ID_INLINE int idRandom::GetSeed( void ) const { + return seed; +} + +ID_INLINE int idRandom::RandomInt( void ) { + seed = 69069 * seed + 1; + return ( seed & idRandom::MAX_RAND ); +} + +ID_INLINE int idRandom::RandomInt( int max ) { + if ( max == 0 ) { + return 0; // avoid divide by zero error + } + return RandomInt() % max; +} + +ID_INLINE float idRandom::RandomFloat( void ) { + return ( RandomInt() / ( float )( idRandom::MAX_RAND + 1 ) ); +} + +ID_INLINE float idRandom::CRandomFloat( void ) { + return ( 2.0f * ( RandomFloat() - 0.5f ) ); +} + + +/* +=============================================================================== + + Random number generator + +=============================================================================== +*/ + +class idRandom2 { +public: + idRandom2( unsigned long seed = 0 ); + + void SetSeed( unsigned long seed ); + unsigned long GetSeed( void ) const; + + int RandomInt( void ); // random integer in the range [0, MAX_RAND] + int RandomInt( int max ); // random integer in the range [0, max] + float RandomFloat( void ); // random number in the range [0.0f, 1.0f] + float CRandomFloat( void ); // random number in the range [-1.0f, 1.0f] + + static const int MAX_RAND = 0x7fff; + +private: + unsigned long seed; + + static const unsigned long IEEE_ONE = 0x3f800000; + static const unsigned long IEEE_MASK = 0x007fffff; +}; + +ID_INLINE idRandom2::idRandom2( unsigned long seed ) { + this->seed = seed; +} + +ID_INLINE void idRandom2::SetSeed( unsigned long seed ) { + this->seed = seed; +} + +ID_INLINE unsigned long idRandom2::GetSeed( void ) const { + return seed; +} + +ID_INLINE int idRandom2::RandomInt( void ) { + seed = 1664525L * seed + 1013904223L; + return ( (int) seed & idRandom2::MAX_RAND ); +} + +ID_INLINE int idRandom2::RandomInt( int max ) { + if ( max == 0 ) { + return 0; // avoid divide by zero error + } + return ( RandomInt() >> ( 16 - idMath::BitsForInteger( max ) ) ) % max; +} + +ID_INLINE float idRandom2::RandomFloat( void ) { + unsigned long i; + seed = 1664525L * seed + 1013904223L; + i = idRandom2::IEEE_ONE | ( seed & idRandom2::IEEE_MASK ); + return ( ( *(float *)&i ) - 1.0f ); +} + +ID_INLINE float idRandom2::CRandomFloat( void ) { + unsigned long i; + seed = 1664525L * seed + 1013904223L; + i = idRandom2::IEEE_ONE | ( seed & idRandom2::IEEE_MASK ); + return ( 2.0f * ( *(float *)&i ) - 3.0f ); +} + +#endif /* !__MATH_RANDOM_H__ */ diff --git a/src/idlib/math/Rotation.cpp b/src/idlib/math/Rotation.cpp new file mode 100644 index 0000000..7f33dab --- /dev/null +++ b/src/idlib/math/Rotation.cpp @@ -0,0 +1,153 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + + +/* +============ +idRotation::ToAngles +============ +*/ +idAngles idRotation::ToAngles( void ) const { + return ToMat3().ToAngles(); +} + +/* +============ +idRotation::ToQuat +============ +*/ +idQuat idRotation::ToQuat( void ) const { + float a, s, c; + + a = angle * ( idMath::M_DEG2RAD * 0.5f ); + idMath::SinCos( a, s, c ); + return idQuat( vec.x * s, vec.y * s, vec.z * s, c ); +} + +/* +============ +idRotation::toMat3 +============ +*/ +const idMat3 &idRotation::ToMat3( void ) const { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + float a, c, s, x, y, z; + + if ( axisValid ) { + return axis; + } + + a = angle * ( idMath::M_DEG2RAD * 0.5f ); + idMath::SinCos( a, s, c ); + + x = vec[0] * s; + y = vec[1] * s; + z = vec[2] * s; + + x2 = x + x; + y2 = y + y; + z2 = z + z; + + xx = x * x2; + xy = x * y2; + xz = x * z2; + + yy = y * y2; + yz = y * z2; + zz = z * z2; + + wx = c * x2; + wy = c * y2; + wz = c * z2; + + axis[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + axis[ 0 ][ 1 ] = xy - wz; + axis[ 0 ][ 2 ] = xz + wy; + + axis[ 1 ][ 0 ] = xy + wz; + axis[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + axis[ 1 ][ 2 ] = yz - wx; + + axis[ 2 ][ 0 ] = xz - wy; + axis[ 2 ][ 1 ] = yz + wx; + axis[ 2 ][ 2 ] = 1.0f - ( xx + yy ); + + axisValid = true; + + return axis; +} + +/* +============ +idRotation::ToMat4 +============ +*/ +idMat4 idRotation::ToMat4( void ) const { + return ToMat3().ToMat4(); +} + +/* +============ +idRotation::ToAngularVelocity +============ +*/ +idVec3 idRotation::ToAngularVelocity( void ) const { + return vec * DEG2RAD( angle ); +} + +/* +============ +idRotation::Normalize180 +============ +*/ +void idRotation::Normalize180( void ) { + angle -= floor( angle / 360.0f ) * 360.0f; + if ( angle > 180.0f ) { + angle -= 360.0f; + } + else if ( angle < -180.0f ) { + angle += 360.0f; + } +} + +/* +============ +idRotation::Normalize360 +============ +*/ +void idRotation::Normalize360( void ) { + angle -= floor( angle / 360.0f ) * 360.0f; + if ( angle > 360.0f ) { + angle -= 360.0f; + } + else if ( angle < 0.0f ) { + angle += 360.0f; + } +} diff --git a/src/idlib/math/Rotation.h b/src/idlib/math/Rotation.h new file mode 100644 index 0000000..717a2b4 --- /dev/null +++ b/src/idlib/math/Rotation.h @@ -0,0 +1,207 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_ROTATION_H__ +#define __MATH_ROTATION_H__ + +/* +=============================================================================== + + Describes a complete rotation in degrees about an abritray axis. + A local rotation matrix is stored for fast rotation of multiple points. + +=============================================================================== +*/ + + +class idAngles; +class idQuat; +class idMat3; + +class idRotation { + + friend class idAngles; + friend class idQuat; + friend class idMat3; + +public: + idRotation( void ); + idRotation( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ); + + void Set( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ); + void SetOrigin( const idVec3 &rotationOrigin ); + void SetVec( const idVec3 &rotationVec ); // has to be normalized + void SetVec( const float x, const float y, const float z ); // has to be normalized + void SetAngle( const float rotationAngle ); + void Scale( const float s ); + void ReCalculateMatrix( void ); + const idVec3 & GetOrigin( void ) const; + const idVec3 & GetVec( void ) const; + float GetAngle( void ) const; + + idRotation operator-() const; // flips rotation + idRotation operator*( const float s ) const; // scale rotation + idRotation operator/( const float s ) const; // scale rotation + idRotation & operator*=( const float s ); // scale rotation + idRotation & operator/=( const float s ); // scale rotation + idVec3 operator*( const idVec3 &v ) const; // rotate vector + + friend idRotation operator*( const float s, const idRotation &r ); // scale rotation + friend idVec3 operator*( const idVec3 &v, const idRotation &r ); // rotate vector + friend idVec3 & operator*=( idVec3 &v, const idRotation &r ); // rotate vector + + idAngles ToAngles( void ) const; + idQuat ToQuat( void ) const; + const idMat3 & ToMat3( void ) const; + idMat4 ToMat4( void ) const; + idVec3 ToAngularVelocity( void ) const; + + void RotatePoint( idVec3 &point ) const; + + void Normalize180( void ); + void Normalize360( void ); + +private: + idVec3 origin; // origin of rotation + idVec3 vec; // normalized vector to rotate around + float angle; // angle of rotation in degrees + mutable idMat3 axis; // rotation axis + mutable bool axisValid; // true if rotation axis is valid +}; + + +ID_INLINE idRotation::idRotation( void ) { +} + +ID_INLINE idRotation::idRotation( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ) { + origin = rotationOrigin; + vec = rotationVec; + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::Set( const idVec3 &rotationOrigin, const idVec3 &rotationVec, const float rotationAngle ) { + origin = rotationOrigin; + vec = rotationVec; + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::SetOrigin( const idVec3 &rotationOrigin ) { + origin = rotationOrigin; +} + +ID_INLINE void idRotation::SetVec( const idVec3 &rotationVec ) { + vec = rotationVec; + axisValid = false; +} + +ID_INLINE void idRotation::SetVec( float x, float y, float z ) { + vec[0] = x; + vec[1] = y; + vec[2] = z; + axisValid = false; +} + +ID_INLINE void idRotation::SetAngle( const float rotationAngle ) { + angle = rotationAngle; + axisValid = false; +} + +ID_INLINE void idRotation::Scale( const float s ) { + angle *= s; + axisValid = false; +} + +ID_INLINE void idRotation::ReCalculateMatrix( void ) { + axisValid = false; + ToMat3(); +} + +ID_INLINE const idVec3 &idRotation::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idVec3 &idRotation::GetVec( void ) const { + return vec; +} + +ID_INLINE float idRotation::GetAngle( void ) const { + return angle; +} + +ID_INLINE idRotation idRotation::operator-() const { + return idRotation( origin, vec, -angle ); +} + +ID_INLINE idRotation idRotation::operator*( const float s ) const { + return idRotation( origin, vec, angle * s ); +} + +ID_INLINE idRotation idRotation::operator/( const float s ) const { + assert( s != 0.0f ); + return idRotation( origin, vec, angle / s ); +} + +ID_INLINE idRotation &idRotation::operator*=( const float s ) { + angle *= s; + axisValid = false; + return *this; +} + +ID_INLINE idRotation &idRotation::operator/=( const float s ) { + assert( s != 0.0f ); + angle /= s; + axisValid = false; + return *this; +} + +ID_INLINE idVec3 idRotation::operator*( const idVec3 &v ) const { + if ( !axisValid ) { + ToMat3(); + } + return ((v - origin) * axis + origin); +} + +ID_INLINE idRotation operator*( const float s, const idRotation &r ) { + return r * s; +} + +ID_INLINE idVec3 operator*( const idVec3 &v, const idRotation &r ) { + return r * v; +} + +ID_INLINE idVec3 &operator*=( idVec3 &v, const idRotation &r ) { + v = r * v; + return v; +} + +ID_INLINE void idRotation::RotatePoint( idVec3 &point ) const { + if ( !axisValid ) { + ToMat3(); + } + point = ((point - origin) * axis + origin); +} + +#endif /* !__MATH_ROTATION_H__ */ diff --git a/src/idlib/math/Simd.cpp b/src/idlib/math/Simd.cpp new file mode 100644 index 0000000..0f1d876 --- /dev/null +++ b/src/idlib/math/Simd.cpp @@ -0,0 +1,4372 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#ifdef _WINDOWS +#include "Simd_MMX.h" +#include "Simd_3DNow.h" +#include "Simd_SSE.h" +#include "Simd_SSE2.h" +#include "Simd_SSE3.h" +#endif // _WINDOWS +#ifdef MACOS_X +#include "Simd_AltiVec.h" +#endif +#ifdef __linux__ +#include "Simd_MMX.h" +#endif +#ifdef _XENON +// RAVEN BEGIN +// jsinger: Adding Xenon processor support +#include "Simd_Xenon.h" +// RAVEN END +#endif + + +idSIMDProcessor * processor = NULL; // pointer to SIMD processor +idSIMDProcessor * generic = NULL; // pointer to generic SIMD implementation +idSIMDProcessor * SIMDProcessor = NULL; + + +/* +================ +idSIMD::Init +================ +*/ +void idSIMD::Init( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// jsinger: xenon uses idSIMD_Xenon always +#ifdef _XENON + generic = new idSIMD_Xenon; +#else + generic = new idSIMD_Generic; + generic->cpuid = CPUID_GENERIC; +#endif +// RAVEN END + processor = NULL; + SIMDProcessor = generic; +} + +/* +============ +idSIMD::InitProcessor +============ +*/ +void idSIMD::InitProcessor( const char *module, bool forceGeneric ) { + cpuid_t cpuid; + idSIMDProcessor *newProcessor; + + cpuid = idLib::sys->GetProcessorId(); + + if ( forceGeneric ) { + + newProcessor = generic; + + } else { + + if ( !processor ) { +// RAVEN BEGIN +// jsinger: on xenon even the generic is idSIMD_Xenon so this is exactly what we want +#ifdef _XENON + processor = generic; // generic will actually be the xenon SIMD processor on xenon +#else + if ( ( cpuid & CPUID_ALTIVEC ) ) { +#ifdef MACOS_X + processor = new idSIMD_AltiVec; +#else + assert( false ); + processor = generic; +#endif +#ifdef _WINDOWS + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_SSE ) && ( cpuid & CPUID_SSE2 ) && ( cpuid & CPUID_SSE3 ) ) { + processor = new idSIMD_SSE3; + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_SSE ) && ( cpuid & CPUID_SSE2 ) ) { + processor = new idSIMD_SSE2; + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_SSE ) ) { + processor = new idSIMD_SSE; + } else if ( ( cpuid & CPUID_MMX ) && ( cpuid & CPUID_3DNOW ) ) { + processor = new idSIMD_3DNow; + } else if ( ( cpuid & CPUID_MMX ) ) { + processor = new idSIMD_MMX; +#endif // _WINDOWS +#if defined( __linux__ ) && defined( ID_GCC_X86_ASM ) + } else if ( ( cpuid & CPUID_MMX ) ) { + processor = new idSIMD_MMX; +#endif + } else { + processor = generic; + } + processor->cpuid = cpuid; +#endif +// RAVEN END + } + + newProcessor = processor; + } + + if ( newProcessor != SIMDProcessor ) { + SIMDProcessor = newProcessor; + idLib::common->Printf( "%s using %s for SIMD processing\n", module, SIMDProcessor->GetName() ); + } + + if ( cpuid & CPUID_FTZ ) { + idLib::sys->FPU_SetFTZ( true ); + idLib::common->Printf( "enabled Flush-To-Zero mode\n" ); + } + + if ( cpuid & CPUID_DAZ ) { + idLib::sys->FPU_SetDAZ( true ); + idLib::common->Printf( "enabled Denormals-Are-Zero mode\n" ); + } +} + +/* +================ +idSIMD::Shutdown +================ +*/ +void idSIMD::Shutdown( void ) { + if ( processor != generic ) { + delete processor; + } + delete generic; + generic = NULL; + processor = NULL; + SIMDProcessor = NULL; +} + + +//=============================================================== +// +// Test code +// +//=============================================================== + +#define COUNT 1024 // data count +#define NUMTESTS 2048 // number of tests + +#define RANDOM_SEED 1013904223L //((int)idLib::sys->GetClockTicks()) + +idSIMDProcessor *p_simd; +idSIMDProcessor *p_generic; +long baseClocks = 0; + +#ifdef _WINDOWS + +#define TIME_TYPE int + +#pragma warning(disable : 4731) // frame pointer register 'ebx' modified by inline assembly code + +long saved_ebx = 0; + +#define StartRecordTime( start ) \ + __asm mov saved_ebx, ebx \ + __asm xor eax, eax \ + __asm cpuid \ + __asm rdtsc \ + __asm mov start, eax \ + __asm xor eax, eax \ + __asm cpuid + +#define StopRecordTime( end ) \ + __asm xor eax, eax \ + __asm cpuid \ + __asm rdtsc \ + __asm mov end, eax \ + __asm mov ebx, saved_ebx \ + __asm xor eax, eax \ + __asm cpuid + +#elif defined(MACOS_X) + +#include +#include // this is for sleep() +#include +#include +#include + +double ticksPerNanosecond; + +#define TIME_TYPE uint64_t + +/* + + .text + .align 2 + .globl _GetTB +_GetTB: + +loop: + mftbu r4 ; load from TBU + mftb r5 ; load from TBL + mftbu r6 ; load from TBU + cmpw r6, r4 ; see if old == new + bne loop ; if not, carry occured, therefore loop + + stw r4, 0(r3) + stw r5, 4(r3) + +done: + blr ; return + +*/ + +/* + +typedef struct { + unsigned int hi; + unsigned int lo; +} U64; + +double TBToDoubleNano( U64 startTime, U64 stopTime, double ticksPerNanosecond ); + +#if __MWERKS__ +asm void GetTB( U64 * ); +#else +void GetTB( U64 * ); +#endif + +double TBToDoubleNano( U64 startTime, U64 stopTime, double ticksPerNanosecond ) { + #define K_2POWER32 4294967296.0 + #define TICKS_PER_NANOSECOND 0.025 + double nanoTime; + U64 diffTime; + + // calc the difference in TB ticks + diffTime.hi = stopTime.hi - startTime.hi; + diffTime.lo = stopTime.lo - startTime.lo; + + // convert TB ticks into time + nanoTime = (double)(diffTime.hi)*((double)K_2POWER32) + (double)(diffTime.lo); + nanoTime = nanoTime/ticksPerNanosecond; + return (nanoTime); +} + +TIME_TYPE time_in_millisec( void ) { + #define K_2POWER32 4294967296.0 + #define TICKS_PER_NANOSECOND 0.025 + + U64 the_time; + double nanoTime, milliTime; + + GetTB( &the_time ); + + // convert TB ticks into time + nanoTime = (double)(the_time.hi)*((double)K_2POWER32) + (double)(the_time.lo); + nanoTime = nanoTime/ticksPerNanosecond; + + // nanoseconds are 1 billionth of a second. I want milliseconds + milliTime = nanoTime * 1000000.0; + + printf( "ticks per nanosec -- %lf\n", ticksPerNanosecond ); + printf( "nanoTime is %lf -- milliTime is %lf -- as int is %i\n", nanoTime, milliTime, (int)milliTime ); + + return (int)milliTime; +} + +#define StartRecordTime( start ) \ + start = time_in_millisec(); + +#define StopRecordTime( end ) \ + end = time_in_millisec(); + +*/ + +#define StartRecordTime( start ) \ + start = mach_absolute_time(); + +#define StopRecordTime( end ) \ + end = mach_absolute_time(); + +#else + +#define TIME_TYPE int + +#define StartRecordTime( start ) \ + start = 0; + +#define StopRecordTime( end ) \ + end = 1; + +#endif // _WINDOWS + +#define GetBest( start, end, best ) \ + if ( !best || end - start < best ) { \ + best = end - start; \ + } + + +/* +============ +PrintClocks +============ +*/ +void PrintClocks( char *string, int dataCount, int clocks, int otherClocks = 0 ) { + int i; + + idLib::common->Printf( string ); +// RAVEN BEGIN +// bdube: escape codes + for ( i = idStr::LengthWithoutEscapes(string); i < 48; i++ ) { +// RAVEN END + idLib::common->Printf(" "); + } + clocks -= baseClocks; + if ( otherClocks ) { + otherClocks -= baseClocks; + int p = (int) ( (float) ( otherClocks - clocks ) * 100.0f / (float) otherClocks ); + idLib::common->Printf( "c = %4d, clcks = %5d, %d%%\n", dataCount, clocks, p ); + } else { + idLib::common->Printf( "c = %4d, clcks = %5d\n", dataCount, clocks ); + } +} + +/* +============ +GetBaseClocks +============ +*/ +void GetBaseClocks( void ) { + int i, start, end, bestClocks; + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + baseClocks = bestClocks; +} + +/* +============ +TestAdd +============ +*/ +void TestAdd( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Add( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Add( float + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Add( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Add( float + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Add( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Add( float[] + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Add( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Add( float[] + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestSub +============ +*/ +void TestSub( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Sub( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Sub( float + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Sub( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Sub( float + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Sub( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Sub( float[] + float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Sub( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Sub( float[] + float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMul +============ +*/ +void TestMul( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Mul( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Mul( float * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Mul( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Mul( float * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Mul( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Mul( float[] * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Mul( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Mul( float[] * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDiv +============ +*/ +void TestDiv( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + do { + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + } while( idMath::Fabs( fsrc1[i] ) < 0.1f ); + } + + idLib::common->Printf("====================================\n" ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Div( fdst0, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Div( float * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Div( fdst1, 4.0f, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Div( float * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Div( fdst0, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Div( float[] * float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Div( fdst1, fsrc0, fsrc1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-3f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Div( float[] * float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMulAdd +============ +*/ +void TestMulAdd( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + for ( j = 0; j < 50 && j < COUNT; j++ ) { + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst0[k] = k; + } + StartRecordTime( start ); + p_generic->MulAdd( fdst0, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->MulAdd( float * float[%2d] )", j ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst1[k] = k; + } + StartRecordTime( start ); + p_simd->MulAdd( fdst1, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MulAdd( float * float[%2d] ) %s", j, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMulSub +============ +*/ +void TestMulSub( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + for ( j = 0; j < 50 && j < COUNT; j++ ) { + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst0[k] = k; + } + StartRecordTime( start ); + p_generic->MulSub( fdst0, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->MulSub( float * float[%2d] )", j ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( int k = 0; k < COUNT; k++ ) { + fdst1[k] = k; + } + StartRecordTime( start ); + p_simd->MulSub( fdst1, 0.123f, fsrc0, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MulSub( float * float[%2d] ) %s", j, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestDot +============ +*/ +void TestDot( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( float fsrc1[COUNT] ); + ALIGN16( idVec3 v3src0[COUNT] ); + ALIGN16( idVec3 v3src1[COUNT] ); + ALIGN16( idVec3 v3constant( 1.0f, 2.0f, 3.0f ) ); + ALIGN16( idPlane v4src0[COUNT] ); + ALIGN16( idPlane v4constant(1.0f, 2.0f, 3.0f, 4.0f) ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + fsrc1[i] = srnd.CRandomFloat() * 10.0f; + for ( j = 0; j < 3; j++ ) { + v3src0[i][j] = srnd.CRandomFloat() * 10.0f; + v3src1[i][j] = srnd.CRandomFloat() * 10.0f; + } + v4src0[i] = v3src0[i]; + v4src0[i][3] = srnd.CRandomFloat() * 10.0f; + drawVerts[i].xyz = v3src0[i]; + } + + idLib::common->Printf("====================================\n" ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3 * idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3 * idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3 * idPlane[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3 * idPlane[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3 * idDrawVert[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3 * idDrawVert[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v4constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idPlane * idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v4constant, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idPlane * idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v4constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idPlane * idPlane[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v4constant, v4src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idPlane * idPlane[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v4constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idPlane * idDrawVert[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v4constant, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-5f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idPlane * idDrawVert[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( fdst0, v3src0, v3src1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Dot( idVec3[] * idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( fdst1, v3src0, v3src1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( idMath::Fabs( fdst0[i] - fdst1[i] ) > 1e-4f ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( idVec3[] * idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + idLib::common->Printf("====================================\n" ); + + float dot1 = 0.0f, dot2 = 0.0f; + for ( j = 0; j < 50 && j < COUNT; j++ ) { + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Dot( dot1, fsrc0, fsrc1, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->Dot( float[%2d] * float[%2d] )", j, j ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Dot( dot2, fsrc0, fsrc1, j ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + result = idMath::Fabs( dot1 - dot2 ) < 1e-4f ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Dot( float[%2d] * float[%2d] ) %s", j, j, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestCompare +============ +*/ +void TestCompare( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( byte bytedst[COUNT] ); + ALIGN16( byte bytedst2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpGT( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGT( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpGT( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGT( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpGT( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGT( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpGT( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGT( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + // ====================== + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpGE( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGE( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpGE( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGE( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpGE( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpGE( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpGE( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpGE( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + // ====================== + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpLT( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLT( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpLT( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLT( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpLT( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLT( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpLT( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLT( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + // ====================== + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CmpLE( bytedst, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLE( float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CmpLE( bytedst2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLE( float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst, 0, COUNT ); + StartRecordTime( start ); + p_generic->CmpLE( bytedst, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CmpLE( 2, float[] >= float )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + memset( bytedst2, 0, COUNT ); + StartRecordTime( start ); + p_simd->CmpLE( bytedst2, 2, fsrc0, 0.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( bytedst[i] != bytedst2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CmpLE( 2, float[] >= float ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMinMax +============ +*/ +void TestMinMax( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fsrc0[COUNT] ); + ALIGN16( idVec2 v2src0[COUNT] ); + ALIGN16( idVec3 v3src0[COUNT] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( int indexes[COUNT] ); + float min = 0.0f, max = 0.0f, min2 = 0.0f, max2 = 0.0f; + idVec2 v2min, v2max, v2min2, v2max2; + idVec3 vmin, vmax, vmin2, vmax2; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + v2src0[i][0] = srnd.CRandomFloat() * 10.0f; + v2src0[i][1] = srnd.CRandomFloat() * 10.0f; + v3src0[i][0] = srnd.CRandomFloat() * 10.0f; + v3src0[i][1] = srnd.CRandomFloat() * 10.0f; + v3src0[i][2] = srnd.CRandomFloat() * 10.0f; + drawVerts[i].xyz = v3src0[i]; + indexes[i] = i; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + min = idMath::INFINITY; + max = -idMath::INFINITY; + StartRecordTime( start ); + p_generic->MinMax( min, max, fsrc0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( min2, max2, fsrc0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( min == min2 && max == max2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( v2min, v2max, v2src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idVec2[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( v2min2, v2max2, v2src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( v2min == v2min2 && v2max == v2max2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idVec2[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idVec3[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, v3src0, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idVec3[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idDrawVert[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idDrawVert[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->MinMax( vmin, vmax, drawVerts, indexes, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MinMax( idDrawVert[], indexes[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->MinMax( vmin2, vmax2, drawVerts, indexes, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( vmin == vmin2 && vmax == vmax2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MinMax( idDrawVert[], indexes[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestClamp +============ +*/ +void TestClamp( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float fdst0[COUNT] ); + ALIGN16( float fdst1[COUNT] ); + ALIGN16( float fsrc0[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + fsrc0[i] = srnd.CRandomFloat() * 10.0f; + } + + idLib::common->Printf("====================================\n" ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->Clamp( fdst0, fsrc0, -1.0f, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->Clamp( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->Clamp( fdst1, fsrc0, -1.0f, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( fdst0[i] != fdst1[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->Clamp( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ClampMin( fdst0, fsrc0, -1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ClampMin( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ClampMin( fdst1, fsrc0, -1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( fdst0[i] != fdst1[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ClampMin( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ClampMax( fdst0, fsrc0, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ClampMax( float[] )", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ClampMax( fdst1, fsrc0, 1.0f, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( fdst0[i] != fdst1[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ClampMax( float[] ) %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMemcpy +============ +*/ +void TestMemcpy( void ) { + int i, j; + byte test0[8192]; + byte test1[8192]; + + idRandom random( RANDOM_SEED ); + + idLib::common->Printf("====================================\n" ); + + for ( i = 5; i < 8192; i += 31 ) { + for ( j = 0; j < i; j++ ) { + test0[j] = random.RandomInt( 255 ); + } + p_simd->Memcpy( test1, test0, 8192 ); + for ( j = 0; j < i; j++ ) { + if ( test1[j] != test0[j] ) { + idLib::common->Printf( " simd->Memcpy() " S_COLOR_RED "X\n" ); + return; + } + } + } + idLib::common->Printf( " simd->Memcpy() ok\n" ); +} + +/* +============ +TestMemset +============ +*/ +void TestMemset( void ) { + int i, j, k; + byte test[8192]; + + for ( i = 0; i < 8192; i++ ) { + test[i] = 0; + } + + for ( i = 5; i < 8192; i += 31 ) { + for ( j = -1; j <= 1; j++ ) { + p_simd->Memset( test, j, i ); + for ( k = 0; k < i; k++ ) { + if ( test[k] != (byte)j ) { + idLib::common->Printf( " simd->Memset() " S_COLOR_RED "X\n" ); + return; + } + } + } + } + idLib::common->Printf( " simd->Memset() ok\n" ); +} + +#define MATX_SIMD_EPSILON 1e-5f + +/* +============ +TestMatXMultiplyVecX +============ +*/ +void TestMatXMultiplyVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= NxN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyVecX %dx%d*%dx1", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyVecX %dx%d*%dx1 %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= Nx6 * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyVecX %dx6*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyVecX %dx6*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyVecX 6x%d*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyVecX 6x%d*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXMultiplyAddVecX +============ +*/ +void TestMatXMultiplyAddVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= NxN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyAddVecX %dx%d*%dx1", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyAddVecX %dx%d*%dx1 %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= Nx6 * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyAddVecX %dx6*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyAddVecX %dx6*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyAddVecX 6x%d*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_MultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyAddVecX 6x%d*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXTransposeMultiplyVecX +============ +*/ +void TestMatXTransposeMultiplyVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= Nx6 * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulVecX %dx6*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulVecX %dx6*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulVecX 6x%d*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulVecX 6x%d*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXTransposeMultiplyAddVecX +============ +*/ +void TestMatXTransposeMultiplyAddVecX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX mat; + idVecX src(6); + idVecX dst(6); + idVecX tst(6); + + src[0] = 1.0f; + src[1] = 2.0f; + src[2] = 3.0f; + src[3] = 4.0f; + src[4] = 5.0f; + src[5] = 6.0f; + + idLib::common->Printf("================= Nx6 * Nx1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( i, 6, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulAddVecX %dx6*%dx1", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulAddVecX %dx6*%dx1 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * 6x1 ===================\n" ); + + for ( i = 1; i <= 6; i++ ) { + mat.Random( 6, i, RANDOM_SEED, -10.0f, 10.0f ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransposeMulAddVecX 6x%d*6x1", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + dst.Zero(); + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyAddVecX( dst, mat, src ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransposeMulAddVecX 6x%d*6x1 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXMultiplyMatX +============ +*/ +#define TEST_VALUE_RANGE 10.0f +#define MATX_MATX_SIMD_EPSILON 1e-4f + +void TestMatXMultiplyMatX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX m1, m2, dst, tst; + + idLib::common->Printf("================= NxN * Nx6 ===================\n" ); + + // NxN * Nx6 + for ( i = 1; i <= 5; i++ ) { + m1.Random( i, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( i, 6 ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX %dx%d*%dx6", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX %dx%d*%dx6 %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * Nx6 ===================\n" ); + + // 6xN * Nx6 + for ( i = 1; i <= 5; i++ ) { + m1.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( 6, 6 ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX 6x%d*%dx6", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX 6x%d*%dx6 %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= Nx6 * 6xN ===================\n" ); + + // Nx6 * 6xN + for ( i = 1; i <= 5; i++ ) { + m1.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( i, i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX %dx6*6x%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX %dx6*6x%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6x6 * 6xN ===================\n" ); + + // 6x6 * 6xN + for ( i = 1; i <= 6; i++ ) { + m1.Random( 6, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( 6, i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_MultiplyMatX 6x6*6x%d", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_MultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_MultiplyMatX 6x6*6x%d %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXTransposeMultiplyMatX +============ +*/ +void TestMatXTransposeMultiplyMatX( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX m1, m2, dst, tst; + + idLib::common->Printf("================= Nx6 * NxN ===================\n" ); + + // Nx6 * NxN + for ( i = 1; i <= 5; i++ ) { + m1.Random( i, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( i, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( 6, i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransMultiplyMatX %dx6*%dx%d", i, i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransMultiplyMatX %dx6*%dx%d %s", i, i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } + + idLib::common->Printf("================= 6xN * 6x6 ===================\n" ); + + // 6xN * 6x6 + for ( i = 1; i <= 6; i++ ) { + m1.Random( 6, i, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + m2.Random( 6, 6, RANDOM_SEED, -TEST_VALUE_RANGE, TEST_VALUE_RANGE ); + dst.SetSize( i, 6 ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = dst; + + PrintClocks( va( "generic->MatX_TransMultiplyMatX 6x%d*6x6", i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_TransposeMultiplyMatX( dst, m1, m2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = dst.Compare( tst, MATX_MATX_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_TransMultiplyMatX 6x%d*6x6 %s", i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +#define MATX_LTS_SIMD_EPSILON 1.0f +#define MATX_LTS_SOLVE_SIZE 100 + +/* +============ +TestMatXLowerTriangularSolve +============ +*/ +void TestMatXLowerTriangularSolve( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX L; + idVecX x, b, tst; + + idLib::common->Printf("====================================\n" ); + + L.Random( MATX_LTS_SOLVE_SIZE, MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + x.SetSize( MATX_LTS_SOLVE_SIZE ); + b.Random( MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + + for ( i = 1; i < MATX_LTS_SOLVE_SIZE; i++ ) { + + x.Zero( i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_LowerTriangularSolve( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = x; + x.Zero(); + + PrintClocks( va( "generic->MatX_LowerTriangularSolve %dx%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_LowerTriangularSolve( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = x.Compare( tst, MATX_LTS_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_LowerTriangularSolve %dx%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestMatXLowerTriangularSolveTranspose +============ +*/ +void TestMatXLowerTriangularSolveTranspose( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX L; + idVecX x, b, tst; + + idLib::common->Printf("====================================\n" ); + + L.Random( MATX_LTS_SOLVE_SIZE, MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + x.SetSize( MATX_LTS_SOLVE_SIZE ); + b.Random( MATX_LTS_SOLVE_SIZE, 0, -1.0f, 1.0f ); + + for ( i = 1; i < MATX_LTS_SOLVE_SIZE; i++ ) { + + x.Zero( i ); + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_generic->MatX_LowerTriangularSolveTranspose( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + tst = x; + x.Zero(); + + PrintClocks( va( "generic->MatX_LowerTriangularSolveT %dx%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + StartRecordTime( start ); + p_simd->MatX_LowerTriangularSolveTranspose( L, x.ToFloatPtr(), b.ToFloatPtr(), i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = x.Compare( tst, MATX_LTS_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_LowerTriangularSolveT %dx%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +#define MATX_LDLT_SIMD_EPSILON 0.1f +#define MATX_LDLT_FACTOR_SOLVE_SIZE 64 + +/* +============ +TestMatXLDLTFactor +============ +*/ +void TestMatXLDLTFactor( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + const char *result; + idMatX src, original, mat1, mat2; + idVecX invDiag1, invDiag2; + + idLib::common->Printf("====================================\n" ); + + original.SetSize( MATX_LDLT_FACTOR_SOLVE_SIZE, MATX_LDLT_FACTOR_SOLVE_SIZE ); + src.Random( MATX_LDLT_FACTOR_SOLVE_SIZE, MATX_LDLT_FACTOR_SOLVE_SIZE, 0, -1.0f, 1.0f ); + src.TransposeMultiply( original, src ); + + for ( i = 1; i < MATX_LDLT_FACTOR_SOLVE_SIZE; i++ ) { + + bestClocksGeneric = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + mat1 = original; + invDiag1.Zero( MATX_LDLT_FACTOR_SOLVE_SIZE ); + StartRecordTime( start ); + p_generic->MatX_LDLTFactor( mat1, invDiag1, i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + + PrintClocks( va( "generic->MatX_LDLTFactor %dx%d", i, i ), 1, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( j = 0; j < NUMTESTS; j++ ) { + mat2 = original; + invDiag2.Zero( MATX_LDLT_FACTOR_SOLVE_SIZE ); + StartRecordTime( start ); + p_simd->MatX_LDLTFactor( mat2, invDiag2, i ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = mat1.Compare( mat2, MATX_LDLT_SIMD_EPSILON ) && invDiag1.Compare( invDiag2, MATX_LDLT_SIMD_EPSILON ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MatX_LDLTFactor %dx%d %s", i, i, result ), 1, bestClocksSIMD, bestClocksGeneric ); + } +} + +/* +============ +TestBlendJoints +============ +*/ +void TestBlendJoints( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointQuat baseJoints[COUNT] ); + ALIGN16( idJointQuat joints1[COUNT] ); + ALIGN16( idJointQuat joints2[COUNT] ); + ALIGN16( idJointQuat blendJoints[COUNT] ); + ALIGN16( int index[COUNT] ); + float lerp = 0.3f; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].q = angles.ToQuat(); + baseJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].w = 0.0f; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + blendJoints[i].q = angles.ToQuat(); + blendJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + blendJoints[i].w = 0.0f; + index[i] = i; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints1[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_generic->BlendJoints( joints1, blendJoints, lerp, index, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->BlendJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + joints2[j] = baseJoints[j]; + } + StartRecordTime( start ); + p_simd->BlendJoints( joints2, blendJoints, lerp, index, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].t.Compare( joints2[i].t, 1e-3f ) ) { + break; + } + if ( !joints1[i].q.Compare( joints2[i].q, 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->BlendJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestConvertJointQuatsToJointMats +============ +*/ +void TestConvertJointQuatsToJointMats( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointQuat baseJoints[COUNT] ); + ALIGN16( idJointMat joints1[COUNT] ); + ALIGN16( idJointMat joints2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].q = angles.ToQuat(); + baseJoints[i].t[0] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[1] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].t[2] = srnd.CRandomFloat() * 10.0f; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ConvertJointQuatsToJointMats( joints1, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ConvertJointQuatsToJointMats()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ConvertJointQuatsToJointMats( joints2, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].Compare( joints2[i], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ConvertJointQuatsToJointMats() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestConvertJointMatsToJointQuats +============ +*/ +void TestConvertJointMatsToJointQuats( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointMat baseJoints[COUNT] ); + ALIGN16( idJointQuat joints1[COUNT] ); + ALIGN16( idJointQuat joints2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + baseJoints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 10.0f; + v[1] = srnd.CRandomFloat() * 10.0f; + v[2] = srnd.CRandomFloat() * 10.0f; + baseJoints[i].SetTranslation( v ); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->ConvertJointMatsToJointQuats( joints1, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ConvertJointMatsToJointQuats()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->ConvertJointMatsToJointQuats( joints2, baseJoints, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i].q.Compare( joints2[i].q, 1e-4f ) ) { + break; + } + if ( !joints1[i].t.Compare( joints2[i].t, 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ConvertJointMatsToJointQuats() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestTransformJoints +============ +*/ +void TestTransformJoints( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointMat joints[COUNT+1] ); + ALIGN16( idJointMat joints1[COUNT+1] ); + ALIGN16( idJointMat joints2[COUNT+1] ); + ALIGN16( int parents[COUNT+1] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i <= COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + parents[i] = i - 1; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints1[j] = joints[j]; + } + StartRecordTime( start ); + p_generic->TransformJoints( joints1, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints2[j] = joints[j]; + } + StartRecordTime( start ); + p_simd->TransformJoints( joints2, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i+1].Compare( joints2[i+1], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestUntransformJoints +============ +*/ +void TestUntransformJoints( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idJointMat joints[COUNT+1] ); + ALIGN16( idJointMat joints1[COUNT+1] ); + ALIGN16( idJointMat joints2[COUNT+1] ); + ALIGN16( int parents[COUNT+1] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i <= COUNT; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + parents[i] = i - 1; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints1[j] = joints[j]; + } + StartRecordTime( start ); + p_generic->UntransformJoints( joints1, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->UntransformJoints()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j <= COUNT; j++ ) { + joints2[j] = joints[j]; + } + StartRecordTime( start ); + p_simd->UntransformJoints( joints2, parents, 1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !joints1[i+1].Compare( joints2[i+1], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->UntransformJoints() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestTransformVertsNew +============ +*/ +#undef NUMJOINTS +#undef NUMVERTS + +#define NUMJOINTS 64 +#define NUMVERTS COUNT +#define NUMWEIGHTSPERVERT 2 +#define NUMWEIGHTS (NUMVERTS*NUMWEIGHTSPERVERT) + +void TestTransformVertsNew( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[NUMVERTS] ); + ALIGN16( idDrawVert drawVerts2[NUMVERTS] ); + ALIGN16( idJointMat joints[NUMJOINTS] ); + ALIGN16( idVec4 base[NUMWEIGHTS*3] ); + ALIGN16( jointWeight_t weights[NUMWEIGHTS] ); + idBounds bounds1, bounds2; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < NUMJOINTS; i++ ) { + idAngles angles; + angles[0] = srnd.CRandomFloat() * 180.0f; + angles[1] = srnd.CRandomFloat() * 180.0f; + angles[2] = srnd.CRandomFloat() * 180.0f; + joints[i].SetRotation( angles.ToMat3() ); + idVec3 v; + v[0] = srnd.CRandomFloat() * 2.0f; + v[1] = srnd.CRandomFloat() * 2.0f; + v[2] = srnd.CRandomFloat() * 2.0f; + joints[i].SetTranslation( v ); + } + + for ( i = 0; i < NUMWEIGHTS*3; i++ ) { + base[i][0] = srnd.CRandomFloat() * 2.0f; + base[i][1] = srnd.CRandomFloat() * 2.0f; + base[i][2] = srnd.CRandomFloat() * 2.0f; + base[i][3] = srnd.CRandomFloat(); + } + + for ( i = 0; i < NUMWEIGHTS; i++ ) { + weights[i].weight = srnd.CRandomFloat(); + weights[i].jointMatOffset = idMath::ClampInt( 0, NUMJOINTS-1, i * NUMJOINTS / NUMWEIGHTS + 0 ) * JOINTMAT_SIZE; + weights[i].nextVertexOffset = ( NUMWEIGHTSPERVERT - ( i % NUMWEIGHTSPERVERT ) ) * JOINTWEIGHT_SIZE; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TransformVertsNew( drawVerts1, NUMVERTS, bounds1, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformVertsNew()", NUMVERTS, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TransformVertsNew( drawVerts2, NUMVERTS, bounds2, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < NUMVERTS; i++ ) { + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 0.5f ) ) { + break; + } + } + result = ( i >= NUMVERTS && bounds1.Compare( bounds2, 1e-4f ) ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformVertsNew() %s", result ), NUMVERTS, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TransformVertsAndTangents( drawVerts1, NUMVERTS, bounds1, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformVertsAndTangents()", NUMVERTS, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TransformVertsAndTangents( drawVerts2, NUMVERTS, bounds2, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < NUMVERTS; i++ ) { + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 0.5f ) ) { + break; + } + if ( !drawVerts1[i].normal.Compare( drawVerts2[i].normal, 0.1f ) ) { + break; + } + if ( !drawVerts1[i].tangents[0].Compare( drawVerts2[i].tangents[0], 0.1f ) ) { + break; + } + } + result = ( i >= NUMVERTS && bounds1.Compare( bounds2, 1e-4f ) ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformVertsAndTangents() %s", result ), NUMVERTS, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TransformVertsAndTangentsFast( drawVerts1, NUMVERTS, bounds1, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TransformVertsAndTangentsFast()", NUMVERTS, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TransformVertsAndTangentsFast( drawVerts2, NUMVERTS, bounds2, joints, base, weights, NUMWEIGHTS ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < NUMVERTS; i++ ) { + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 0.5f ) ) { + break; + } + if ( !drawVerts1[i].normal.Compare( drawVerts2[i].normal, 0.1f ) ) { + break; + } + if ( !drawVerts1[i].tangents[0].Compare( drawVerts2[i].tangents[0], 0.1f ) ) { + break; + } + } + result = ( i >= NUMVERTS && bounds1.Compare( bounds2, 1e-4f ) ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TransformVertsAndTangentsFast() %s", result ), NUMVERTS, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestTracePointCull +============ +*/ +void TestTracePointCull( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idPlane planes[4] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( byte cullBits1[COUNT] ); + ALIGN16( byte cullBits2[COUNT] ); + byte totalOr1 = 0, totalOr2 = 0; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + planes[0].SetNormal( idVec3( 1, 0, 0 ) ); + planes[1].SetNormal( idVec3( -1, 0, 0 ) ); + planes[2].SetNormal( idVec3( 0, 1, 0 ) ); + planes[3].SetNormal( idVec3( 0, -1, 0 ) ); + planes[0][3] = -5.3f; + planes[1][3] = 5.3f; + planes[2][3] = -3.4f; + planes[3][3] = 3.4f; + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->TracePointCull( cullBits1, totalOr1, 0.0f, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->TracePointCull()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->TracePointCull( cullBits2, totalOr2, 0.0f, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( cullBits1[i] != cullBits2[i] ) { + break; + } + } + result = ( i >= COUNT && totalOr1 == totalOr2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->TracePointCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDecalPointCull +============ +*/ +void TestDecalPointCull( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idPlane planes[6] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( byte cullBits1[COUNT] ); + ALIGN16( byte cullBits2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + planes[0].SetNormal( idVec3( 1, 0, 0 ) ); + planes[1].SetNormal( idVec3( -1, 0, 0 ) ); + planes[2].SetNormal( idVec3( 0, 1, 0 ) ); + planes[3].SetNormal( idVec3( 0, -1, 0 ) ); + planes[4].SetNormal( idVec3( 0, 0, 1 ) ); + planes[5].SetNormal( idVec3( 0, 0, -1 ) ); + planes[0][3] = -5.3f; + planes[1][3] = 5.3f; + planes[2][3] = -4.4f; + planes[3][3] = 4.4f; + planes[4][3] = -3.5f; + planes[5][3] = 3.5f; + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DecalPointCull( cullBits1, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DecalPointCull()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DecalPointCull( cullBits2, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( cullBits1[i] != cullBits2[i] ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DecalPointCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestOverlayPointCull +============ +*/ +void TestOverlayPointCull( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idPlane planes[2] ); + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( byte cullBits1[COUNT] ); + ALIGN16( byte cullBits2[COUNT] ); + ALIGN16( idVec2 texCoords1[COUNT] ); + ALIGN16( idVec2 texCoords2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + planes[0].SetNormal( idVec3( 0.3f, 0.2f, 0.9f ) ); + planes[1].SetNormal( idVec3( 0.9f, 0.2f, 0.3f ) ); + planes[0][3] = -5.3f; + planes[1][3] = -4.3f; + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->OverlayPointCull( cullBits1, texCoords1, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->OverlayPointCull()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->OverlayPointCull( cullBits2, texCoords2, planes, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( cullBits1[i] != cullBits2[i] ) { + break; + } + if ( !texCoords1[i].Compare( texCoords2[i], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->OverlayPointCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDeriveTriPlanes +============ +*/ +void TestDeriveTriPlanes( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + ALIGN16( idPlane planes1[COUNT] ); + ALIGN16( idPlane planes2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + for ( j = 0; j < 2; j++ ) { + drawVerts1[i].st[j] = srnd.CRandomFloat(); + } + drawVerts2[i] = drawVerts1[i]; + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DeriveTriPlanes( planes1, drawVerts1, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DeriveTriPlanes()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DeriveTriPlanes( planes2, drawVerts2, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !planes1[i].Compare( planes2[i], 1e-1f, 1e-1f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DeriveTriPlanes() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDeriveTangents +============ +*/ +void TestDeriveTangents( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + ALIGN16( idPlane planes1[COUNT] ); + ALIGN16( idPlane planes2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + for ( j = 0; j < 2; j++ ) { + drawVerts1[i].st[j] = srnd.CRandomFloat(); + } + drawVerts2[i] = drawVerts1[i]; + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DeriveTangents( planes1, drawVerts1, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DeriveTangents()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DeriveTangents( planes2, drawVerts2, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + idVec3 v1, v2; + + v1 = drawVerts1[i].normal; + v1.Normalize(); + v2 = drawVerts2[i].normal; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[0]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[0]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[1]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[1]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + if ( !planes1[i].Compare( planes2[i], 1e-1f, 1e-1f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DeriveTangents() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestDeriveUnsmoothedTangents +============ +*/ +void TestDeriveUnsmoothedTangents( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + ALIGN16( dominantTri_s dominantTris[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].xyz[j] = srnd.CRandomFloat() * 10.0f; + } + for ( j = 0; j < 2; j++ ) { + drawVerts1[i].st[j] = srnd.CRandomFloat(); + } + drawVerts2[i] = drawVerts1[i]; + + dominantTris[i].v2 = ( i + 1 + srnd.RandomInt( 8 ) ) % COUNT; + dominantTris[i].v3 = ( i + 9 + srnd.RandomInt( 8 ) ) % COUNT; + dominantTris[i].normalizationScale[0] = srnd.CRandomFloat(); + dominantTris[i].normalizationScale[1] = srnd.CRandomFloat(); + dominantTris[i].normalizationScale[2] = srnd.CRandomFloat(); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->DeriveUnsmoothedTangents( drawVerts1, dominantTris, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->DeriveUnsmoothedTangents()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->DeriveUnsmoothedTangents( drawVerts2, dominantTris, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + idVec3 v1, v2; + + v1 = drawVerts1[i].normal; + v1.Normalize(); + v2 = drawVerts2[i].normal; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[0]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[0]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + v1 = drawVerts1[i].tangents[1]; + v1.Normalize(); + v2 = drawVerts2[i].tangents[1]; + v2.Normalize(); + if ( !v1.Compare( v2, 1e-1f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->DeriveUnsmoothedTangents() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestNormalizeTangents +============ +*/ +void TestNormalizeTangents( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts1[COUNT] ); + ALIGN16( idDrawVert drawVerts2[COUNT] ); + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts1[i].normal[j] = srnd.CRandomFloat() * 10.0f; + drawVerts1[i].tangents[0][j] = srnd.CRandomFloat() * 10.0f; + drawVerts1[i].tangents[1][j] = srnd.CRandomFloat() * 10.0f; + } + drawVerts2[i] = drawVerts1[i]; + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->NormalizeTangents( drawVerts1, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->NormalizeTangents()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->NormalizeTangents( drawVerts2, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !drawVerts1[i].normal.Compare( drawVerts2[i].normal, 1e-2f ) ) { + break; + } + if ( !drawVerts1[i].tangents[0].Compare( drawVerts2[i].tangents[0], 1e-2f ) ) { + break; + } + if ( !drawVerts1[i].tangents[1].Compare( drawVerts2[i].tangents[1], 1e-2f ) ) { + break; + } + + // since we're doing a lot of unaligned work, added this check to + // make sure xyz wasn't getting overwritten + if ( !drawVerts1[i].xyz.Compare( drawVerts2[i].xyz, 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->NormalizeTangents() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestGetTextureSpaceLightVectors +============ +*/ +void TestGetTextureSpaceLightVectors( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( idVec4 texCoords1[COUNT] ); + ALIGN16( idVec4 texCoords2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + ALIGN16( idVec3 lightVectors1[COUNT] ); + ALIGN16( idVec3 lightVectors2[COUNT] ); + idVec3 lightOrigin; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 100.0f; + drawVerts[i].normal[j] = srnd.CRandomFloat(); + drawVerts[i].tangents[0][j] = srnd.CRandomFloat(); + drawVerts[i].tangents[1][j] = srnd.CRandomFloat(); + } + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + lightOrigin[0] = srnd.CRandomFloat() * 100.0f; + lightOrigin[1] = srnd.CRandomFloat() * 100.0f; + lightOrigin[2] = srnd.CRandomFloat() * 100.0f; + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CreateTextureSpaceLightVectors( lightVectors1, lightOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateTextureSpaceLightVectors()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CreateTextureSpaceLightVectors( lightVectors2, lightOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !lightVectors1[i].Compare( lightVectors2[i], 1e-4f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateTextureSpaceLightVectors() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestGetSpecularTextureCoords +============ +*/ +void TestGetSpecularTextureCoords( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( idVec4 texCoords1[COUNT] ); + ALIGN16( idVec4 texCoords2[COUNT] ); + ALIGN16( int indexes[COUNT*3] ); + ALIGN16( idVec3 lightVectors1[COUNT] ); + ALIGN16( idVec3 lightVectors2[COUNT] ); + idVec3 lightOrigin, viewOrigin; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 100.0f; + drawVerts[i].normal[j] = srnd.CRandomFloat(); + drawVerts[i].tangents[0][j] = srnd.CRandomFloat(); + drawVerts[i].tangents[1][j] = srnd.CRandomFloat(); + } + } + + for ( i = 0; i < COUNT; i++ ) { + indexes[i*3+0] = ( i + 0 ) % COUNT; + indexes[i*3+1] = ( i + 1 ) % COUNT; + indexes[i*3+2] = ( i + 2 ) % COUNT; + } + + lightOrigin[0] = srnd.CRandomFloat() * 100.0f; + lightOrigin[1] = srnd.CRandomFloat() * 100.0f; + lightOrigin[2] = srnd.CRandomFloat() * 100.0f; + viewOrigin[0] = srnd.CRandomFloat() * 100.0f; + viewOrigin[1] = srnd.CRandomFloat() * 100.0f; + viewOrigin[2] = srnd.CRandomFloat() * 100.0f; + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CreateSpecularTextureCoords( texCoords1, lightOrigin, viewOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateSpecularTextureCoords()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CreateSpecularTextureCoords( texCoords2, lightOrigin, viewOrigin, drawVerts, COUNT, indexes, COUNT*3 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !texCoords1[i].Compare( texCoords2[i], 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateSpecularTextureCoords() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestCreateShadowCache +============ +*/ +void TestCreateShadowCache( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( idDrawVert drawVerts[COUNT] ); + ALIGN16( idVec4 vertexCache1[COUNT*2] ); + ALIGN16( idVec4 vertexCache2[COUNT*2] ); + ALIGN16( int originalVertRemap[COUNT] ); + ALIGN16( int vertRemap1[COUNT] ); + ALIGN16( int vertRemap2[COUNT] ); + ALIGN16( idVec3 lightOrigin ); + int numVerts1 = 0, numVerts2 = 0; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < COUNT; i++ ) { + for ( j = 0; j < 3; j++ ) { + drawVerts[i].xyz[j] = srnd.CRandomFloat() * 100.0f; + } + originalVertRemap[i] = ( srnd.CRandomFloat() > 0.0f ) ? -1 : 0; + } + lightOrigin[0] = srnd.CRandomFloat() * 100.0f; + lightOrigin[1] = srnd.CRandomFloat() * 100.0f; + lightOrigin[2] = srnd.CRandomFloat() * 100.0f; + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + vertRemap1[j] = originalVertRemap[j]; + } + StartRecordTime( start ); + numVerts1 = p_generic->CreateShadowCache( vertexCache1, vertRemap1, lightOrigin, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateShadowCache()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < COUNT; j++ ) { + vertRemap2[j] = originalVertRemap[j]; + } + StartRecordTime( start ); + numVerts2 = p_simd->CreateShadowCache( vertexCache2, vertRemap2, lightOrigin, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( i < ( numVerts1 / 2 ) ) { + if ( !vertexCache1[i*2+0].Compare( vertexCache2[i*2+0], 1e-2f ) ) { + break; + } + if ( !vertexCache1[i*2+1].Compare( vertexCache2[i*2+1], 1e-2f ) ) { + break; + } + } + if ( vertRemap1[i] != vertRemap2[i] ) { + break; + } + } + + result = ( i >= COUNT && numVerts1 == numVerts2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateShadowCache() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->CreateVertexProgramShadowCache( vertexCache1, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->CreateVertexProgramShadowCache()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->CreateVertexProgramShadowCache( vertexCache2, drawVerts, COUNT ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < COUNT; i++ ) { + if ( !vertexCache1[i*2+0].Compare( vertexCache2[i*2+0], 1e-2f ) ) { + break; + } + if ( !vertexCache1[i*2+1].Compare( vertexCache2[i*2+1], 1e-2f ) ) { + break; + } + } + result = ( i >= COUNT ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->CreateVertexProgramShadowCache() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} + +#if 0 +#include "Simd_TestData.h" + +/* +============ +TestShadowVolumes +============ +*/ +void TestShadowVolumes( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( int shadowIndexes1[8129] ); + ALIGN16( int shadowIndexes2[8129] ); + int numFacing1, numFacing2; + const char *result; + + numFacing1 = numFacing2 = 0; + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CountFacing( SIMD_testFacing, SIMD_numTestFacing ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CountFacing()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CountFacing( SIMD_testFacing, SIMD_numTestFacing ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CountFacing() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CountFacingCull( SIMD_testFacing, SIMD_numTestFacing, SIMD_testIndexes, SIMD_testCullBits ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CountFacing()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CountFacingCull( SIMD_testFacing, SIMD_numTestFacing, SIMD_testIndexes, SIMD_testCullBits ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + result = ( numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CountFacingCull() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CreateSilTriangles( shadowIndexes1, SIMD_testFacing, ( const silEdge_s *)SIMD_testSilEdges, SIMD_numTestSilEdges ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CreateSilTriangles()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CreateSilTriangles( shadowIndexes2, SIMD_testFacing, ( const silEdge_s *)SIMD_testSilEdges, SIMD_numTestSilEdges ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < numFacing1; i++ ) { + if ( shadowIndexes1[i] != shadowIndexes2[i] ) { + break; + } + } + + result = ( i >= numFacing1 && numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CreateSilTriangles() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing1 = p_generic->ShadowVolume_CreateCapTriangles( shadowIndexes1, SIMD_testFacing, SIMD_testIndexes, SIMD_numTestIndexes ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->ShadowVolume_CreateCapTriangles()", COUNT, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + numFacing2 = p_simd->ShadowVolume_CreateCapTriangles( shadowIndexes2, SIMD_testFacing, SIMD_testIndexes, SIMD_numTestIndexes ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < numFacing1; i++ ) { + if ( shadowIndexes1[i] != shadowIndexes2[i] ) { + break; + } + } + + result = ( i >= numFacing1 && numFacing1 == numFacing2 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->ShadowVolume_CreateCapTriangles() %s", result ), COUNT, bestClocksSIMD, bestClocksGeneric ); +} +#endif + +/* +============ +TestSoundUpSampling +============ +*/ +#define SOUND_UPSAMPLE_EPSILON 1.0f + +void TestSoundUpSampling( void ) { + int i; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( short pcm[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float ogg0[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float ogg1[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float samples1[MIXBUFFER_SAMPLES*2] ); + ALIGN16( float samples2[MIXBUFFER_SAMPLES*2] ); + float *ogg[2]; + int kHz, numSpeakers; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < MIXBUFFER_SAMPLES*2; i++ ) { + pcm[i] = srnd.RandomInt( (1<<16) ) - (1<<15); + ogg0[i] = srnd.RandomFloat(); + ogg1[i] = srnd.RandomFloat(); + } + + ogg[0] = ogg0; + ogg[1] = ogg1; + + for ( numSpeakers = 1; numSpeakers <= 2; numSpeakers++ ) { + + for ( kHz = 11025; kHz <= 44100; kHz *= 2 ) { + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->UpSamplePCMTo44kHz( samples1, pcm, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->UpSamplePCMTo44kHz( %d, %d )", kHz, numSpeakers ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->UpSamplePCMTo44kHz( samples2, pcm, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*numSpeakers; i++ ) { + if ( idMath::Fabs( samples1[i] - samples2[i] ) > SOUND_UPSAMPLE_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*numSpeakers ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->UpSamplePCMTo44kHz( %d, %d ) %s", kHz, numSpeakers, result ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksSIMD, bestClocksGeneric ); + } + } + + for ( numSpeakers = 1; numSpeakers <= 2; numSpeakers++ ) { + + for ( kHz = 11025; kHz <= 44100; kHz *= 2 ) { + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_generic->UpSampleOGGTo44kHz( samples1, ogg, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( va( "generic->UpSampleOGGTo44kHz( %d, %d )", kHz, numSpeakers ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + p_simd->UpSampleOGGTo44kHz( samples2, ogg, MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, kHz, numSpeakers ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*numSpeakers; i++ ) { + if ( idMath::Fabs( samples1[i] - samples2[i] ) > SOUND_UPSAMPLE_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->UpSampleOGGTo44kHz( %d, %d ) %s", kHz, numSpeakers, result ), MIXBUFFER_SAMPLES*numSpeakers*kHz/44100, bestClocksSIMD, bestClocksGeneric ); + } + } +} + +/* +============ +TestSoundMixing +============ +*/ +#define SOUND_MIX_EPSILON 2.0f + +void TestSoundMixing( void ) { + int i, j; + TIME_TYPE start, end, bestClocksGeneric, bestClocksSIMD; + ALIGN16( float origMixBuffer[MIXBUFFER_SAMPLES*6] ); + ALIGN16( float mixBuffer1[MIXBUFFER_SAMPLES*6] ); + ALIGN16( float mixBuffer2[MIXBUFFER_SAMPLES*6] ); + ALIGN16( float samples[MIXBUFFER_SAMPLES*6] ); + ALIGN16( short outSamples1[MIXBUFFER_SAMPLES*6] ); + ALIGN16( short outSamples2[MIXBUFFER_SAMPLES*6] ); + float lastV[6]; + float currentV[6]; + const char *result; + + idRandom srnd( RANDOM_SEED ); + + for ( i = 0; i < 6; i++ ) { + lastV[i] = srnd.CRandomFloat(); + currentV[i] = srnd.CRandomFloat(); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + origMixBuffer[i] = srnd.CRandomFloat(); + samples[i] = srnd.RandomInt( (1<<16) ) - (1<<15); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundTwoSpeakerMono( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundTwoSpeakerMono()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundTwoSpeakerMono( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundTwoSpeakerMono() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundTwoSpeakerStereo( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundTwoSpeakerStereo()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundTwoSpeakerStereo( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundTwoSpeakerStereo() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundSixSpeakerMono( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundSixSpeakerMono()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundSixSpeakerMono( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundSixSpeakerMono() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixSoundSixSpeakerStereo( mixBuffer1, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixSoundSixSpeakerStereo()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixSoundSixSpeakerStereo( mixBuffer2, samples, MIXBUFFER_SAMPLES, lastV, currentV ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( idMath::Fabs( mixBuffer1[i] - mixBuffer2[i] ) > SOUND_MIX_EPSILON ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixSoundSixSpeakerStereo() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); + + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + origMixBuffer[i] = srnd.RandomInt( (1<<17) ) - (1<<16); + } + + bestClocksGeneric = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer1[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_generic->MixedSoundToSamples( outSamples1, mixBuffer1, MIXBUFFER_SAMPLES*6 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksGeneric ); + } + PrintClocks( "generic->MixedSoundToSamples()", MIXBUFFER_SAMPLES, bestClocksGeneric ); + + bestClocksSIMD = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + for ( j = 0; j < MIXBUFFER_SAMPLES*6; j++ ) { + mixBuffer2[j] = origMixBuffer[j]; + } + StartRecordTime( start ); + p_simd->MixedSoundToSamples( outSamples2, mixBuffer2, MIXBUFFER_SAMPLES*6 ); + StopRecordTime( end ); + GetBest( start, end, bestClocksSIMD ); + } + + for ( i = 0; i < MIXBUFFER_SAMPLES*6; i++ ) { + if ( outSamples1[i] != outSamples2[i] ) { + break; + } + } + result = ( i >= MIXBUFFER_SAMPLES*6 ) ? "ok" : S_COLOR_RED"X"; + PrintClocks( va( " simd->MixedSoundToSamples() %s", result ), MIXBUFFER_SAMPLES, bestClocksSIMD, bestClocksGeneric ); +} + +/* +============ +TestMath +============ +*/ +void TestMath( void ) { + int i; + TIME_TYPE start, end, bestClocks; + + idLib::common->Printf("====================================\n" ); + + float tst = -1.0f; + float tst2 = 1.0f; + float testvar = 1.0f; + idRandom rnd; + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = fabs( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " fabs( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + int tmp = * ( int * ) &tst; + tmp &= 0x7FFFFFFF; + tst = * ( float * ) &tmp; + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Fabs( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = 10.0f + 100.0f * rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = sqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.01f; + tst = 10.0f + 100.0f * rnd.RandomFloat(); + } + PrintClocks( " sqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sqrt64( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::Sqrt64( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.RandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = tst * idMath::RSqrt( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.RandomFloat(); + } + PrintClocks( " idMath::RSqrt( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sin( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Sin( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Sin16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Sin16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Cos( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Cos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Cos16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Cos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + idMath::SinCos( tst, tst, tst2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::SinCos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + idMath::SinCos16( tst, tst, tst2 ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( "idMath::SinCos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Tan( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Tan( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Tan16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Tan16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ASin( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ASin( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ASin16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ASin16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ACos( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ACos( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ACos16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * ( 1.0f / idMath::PI ); + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ACos16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ATan( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ATan( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::ATan16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::ATan16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Pow( 2.7f, tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Pow( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Pow16( 2.7f, tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Pow16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Exp( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Exp( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + tst = idMath::Exp16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst * 0.1f; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Exp16( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + tst = fabs( tst ) + 1.0f; + StartRecordTime( start ); + tst = idMath::Log( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Log( tst )", 1, bestClocks ); + + bestClocks = 0; + tst = rnd.CRandomFloat(); + for ( i = 0; i < NUMTESTS; i++ ) { + tst = fabs( tst ) + 1.0f; + StartRecordTime( start ); + tst = idMath::Log16( tst ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + testvar = ( testvar + tst ) * tst; + tst = rnd.CRandomFloat(); + } + PrintClocks( " idMath::Log16( tst )", 1, bestClocks ); + + idLib::common->Printf( "testvar = %f\n", testvar ); + + idMat3 resultMat3; + idQuat fromQuat, toQuat, resultQuat; + idCQuat cq; + idAngles ang; + + fromQuat = idAngles( 30, 45, 0 ).ToQuat(); + toQuat = idAngles( 45, 0, 0 ).ToQuat(); + cq = idAngles( 30, 45, 0 ).ToQuat().ToCQuat(); + ang = idAngles( 30, 40, 50 ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultMat3 = fromQuat.ToMat3(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idQuat::ToMat3()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat.Slerp( fromQuat, toQuat, 0.3f ); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idQuat::Slerp()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat = cq.ToQuat(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idCQuat::ToQuat()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultQuat = ang.ToQuat(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idAngles::ToQuat()", 1, bestClocks ); + + bestClocks = 0; + for ( i = 0; i < NUMTESTS; i++ ) { + StartRecordTime( start ); + resultMat3 = ang.ToMat3(); + StopRecordTime( end ); + GetBest( start, end, bestClocks ); + } + PrintClocks( " idAngles::ToMat3()", 1, bestClocks ); +} + +/* +============ +idSIMD::Test_f +============ +*/ +void idSIMD::Test_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// jsinger: at the moment this doesn't compile on xenon, but we also shouldn't need it anyway +#ifndef _XENON +#ifdef _WINDOWS + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL ); +#endif // _WINDOWS + + p_simd = processor; + p_generic = generic; + + if ( idStr::Length( args.Argv( 1 ) ) != 0 ) { + cpuid_t cpuid = idLib::sys->GetProcessorId(); + idStr argString = args.Args(); + + argString.Replace( " ", "" ); + +#ifdef _WINDOWS + if ( idStr::Icmp( argString, "MMX" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) ) { + common->Printf( "CPU does not support MMX\n" ); + return; + } + p_simd = new idSIMD_MMX; + } else if ( idStr::Icmp( argString, "3DNow" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_3DNOW ) ) { + common->Printf( "CPU does not support MMX & 3DNow\n" ); + return; + } + p_simd = new idSIMD_3DNow; + } else if ( idStr::Icmp( argString, "SSE" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_SSE ) ) { + common->Printf( "CPU does not support MMX & SSE\n" ); + return; + } + p_simd = new idSIMD_SSE; + } else if ( idStr::Icmp( argString, "SSE2" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_SSE ) || !( cpuid & CPUID_SSE2 ) ) { + common->Printf( "CPU does not support MMX & SSE & SSE2\n" ); + return; + } + p_simd = new idSIMD_SSE2; + } else if ( idStr::Icmp( argString, "SSE3" ) == 0 ) { + if ( !( cpuid & CPUID_MMX ) || !( cpuid & CPUID_SSE ) || !( cpuid & CPUID_SSE2 ) || !( cpuid & CPUID_SSE3 ) ) { + common->Printf( "CPU does not support MMX & SSE & SSE2 & SSE3\n" ); + return; + } + p_simd = new idSIMD_SSE3(); + } else +#endif // _WINDOWS + if ( idStr::Icmp( argString, "AltiVec" ) == 0 ) { + if ( !( cpuid & CPUID_ALTIVEC ) ) { + common->Printf( "CPU does not support AltiVec\n" ); + return; + } +#ifdef MACOS_X + p_simd = new idSIMD_AltiVec(); +#endif + } else { + common->Printf( "invalid argument, use: MMX, 3DNow, SSE, SSE2, SSE3, AltiVec\n" ); + return; + } + } + + idLib::common->SetRefreshOnPrint( true ); + + idLib::common->Printf( "using %s for SIMD processing\n", p_simd->GetName() ); + + GetBaseClocks(); +/* + TestMath(); + TestAdd(); + TestSub(); + TestMul(); + TestDiv(); + TestMulAdd(); + TestMulSub(); + TestDot(); + TestCompare(); + TestMinMax(); + TestClamp(); + TestMemcpy(); + TestMemset(); + + TestMatXMultiplyVecX(); + TestMatXMultiplyAddVecX(); + TestMatXTransposeMultiplyVecX(); + TestMatXTransposeMultiplyAddVecX(); + TestMatXMultiplyMatX(); + TestMatXTransposeMultiplyMatX(); + TestMatXLowerTriangularSolve(); + TestMatXLowerTriangularSolveTranspose(); + TestMatXLDLTFactor(); + + idLib::common->Printf("====================================\n" ); +*/ + TestBlendJoints(); + TestConvertJointQuatsToJointMats(); + TestConvertJointMatsToJointQuats(); + TestTransformJoints(); + TestUntransformJoints(); + TestTransformVertsNew(); +/* TestTracePointCull(); + TestDecalPointCull(); + TestOverlayPointCull(); + TestDeriveTriPlanes(); + TestDeriveTangents(); + TestDeriveUnsmoothedTangents(); + TestNormalizeTangents(); + TestGetTextureSpaceLightVectors(); + TestGetSpecularTextureCoords(); + TestCreateShadowCache(); + + idLib::common->Printf("====================================\n" ); + + TestSoundUpSampling(); + TestSoundMixing(); +*/ + idLib::common->SetRefreshOnPrint( false ); + + if ( p_simd != processor ) { + delete p_simd; + } + p_simd = NULL; + p_generic = NULL; + +#ifdef _WINDOWS + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL ); +#endif // _WINDOWS +#endif +// RAVEN END +} diff --git a/src/idlib/math/Simd.h b/src/idlib/math/Simd.h new file mode 100644 index 0000000..bb8eada --- /dev/null +++ b/src/idlib/math/Simd.h @@ -0,0 +1,253 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_H__ +#define __MATH_SIMD_H__ + +/* +=============================================================================== + + Single Instruction Multiple Data (SIMD) + + For optimal use data should be aligned on a 16 byte boundary. + All idSIMDProcessor routines are thread safe. + +=============================================================================== +*/ + +class idSIMD { +public: + static void Init( void ); + static void InitProcessor( const char *module, bool forceGeneric ); + static void Shutdown( void ); + static void Test_f( const class idCmdArgs &args ); +}; + + +/* +=============================================================================== + + virtual base class for different SIMD processors + +=============================================================================== +*/ + +#ifdef _WIN32 +#define VPCALL __fastcall +#else +#define VPCALL +#endif + +class idVec2; +class idVec3; +#ifdef _XENON +class __declspec(align(16)) idVec4; +#else +class idVec4; +#endif +class idVec5; +class idVec6; +class idVecX; +class idMat2; +class idMat3; +class idMat4; +class idMat5; +class idMat6; +class idMatX; +class idPlane; +class idBounds; +class idDrawVert; +class idJointQuat; +class idJointMat; +struct dominantTri_s; +struct jointWeight_t; +struct silEdge_s; + +// RAVEN BEGIN +// dluetscher: declared new vertex format +#ifdef _MD5R_SUPPORT +class rvSilTraceVertT; +#endif +// RAVEN END + +const int MIXBUFFER_SAMPLES = 4096; + +typedef enum { + SPEAKER_LEFT = 0, + SPEAKER_RIGHT, + SPEAKER_CENTER, + SPEAKER_LFE, + SPEAKER_BACKLEFT, + SPEAKER_BACKRIGHT +} speakerLabel; + + +// RAVEN BEGIN +// jsinger: forward declare and use a typedef so that xenon doesn't have to use inheritence for the SIMD stuff +#ifdef _XENON +class idSIMD_Xenon; +typedef idSIMD_Xenon idSIMDProcessor; +#else +// RAVEN END +class idSIMDProcessor { +public: + idSIMDProcessor( void ) { cpuid = CPUID_NONE; } + virtual ~idSIMDProcessor( void ) { } + + cpuid_t cpuid; + + virtual const char * VPCALL GetName( void ) const = 0; + + virtual void VPCALL Add( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Add( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL Sub( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Sub( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL Mul( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Mul( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL Div( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL Div( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL MulAdd( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL MulAdd( float *dst, const float *src0, const float *src1, const int count ) = 0; + virtual void VPCALL MulSub( float *dst, const float constant, const float *src, const int count ) = 0; + virtual void VPCALL MulSub( float *dst, const float *src0, const float *src1, const int count ) = 0; + + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idVec3 *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idPlane *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idDrawVert *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ) = 0; + virtual void VPCALL Dot( float &dot, const float *src1, const float *src2, const int count ) = 0; + + virtual void VPCALL CmpGT( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpGE( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLT( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLE( byte *dst, const float *src0, const float constant, const int count ) = 0; + virtual void VPCALL CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) = 0; + + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) = 0; + + virtual void VPCALL Clamp( float *dst, const float *src, const float min, const float max, const int count ) = 0; + virtual void VPCALL ClampMin( float *dst, const float *src, const float min, const int count ) = 0; + virtual void VPCALL ClampMax( float *dst, const float *src, const float max, const int count ) = 0; + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ) = 0; + virtual void VPCALL Memset( void *dst, const int val, const int count ) = 0; + + // these assume 16 byte aligned and 16 byte padded memory + virtual void VPCALL Zero16( float *dst, const int count ) = 0; + virtual void VPCALL Negate16( float *dst, const int count ) = 0; + virtual void VPCALL Copy16( float *dst, const float *src, const int count ) = 0; + virtual void VPCALL Add16( float *dst, const float *src1, const float *src2, const int count ) = 0; + virtual void VPCALL Sub16( float *dst, const float *src1, const float *src2, const int count ) = 0; + virtual void VPCALL Mul16( float *dst, const float *src1, const float constant, const int count ) = 0; + virtual void VPCALL AddAssign16( float *dst, const float *src, const int count ) = 0; + virtual void VPCALL SubAssign16( float *dst, const float *src, const int count ) = 0; + virtual void VPCALL MulAssign16( float *dst, const float constant, const int count ) = 0; + + // idMatX operations + virtual void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) = 0; + virtual void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) = 0; + virtual void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) = 0; + virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ) = 0; + virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) = 0; + virtual bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) = 0; + + // rendering + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) = 0; + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) = 0; + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) = 0; + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) = 0; + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) = 0; + virtual void VPCALL MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ) = 0; + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) = 0; + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) = 0; + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) = 0; + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) = 0; + virtual void VPCALL NormalizeTangents( idDrawVert *verts, const int numVerts ) = 0; + virtual void VPCALL CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual int VPCALL CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) = 0; + virtual int VPCALL CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) = 0; + virtual int VPCALL ShadowVolume_CountFacing( const byte *facing, const int numFaces ) = 0; + virtual int VPCALL ShadowVolume_CountFacingCull( byte *facing, const int numFaces, const int *indexes, const byte *cull ) = 0; + virtual int VPCALL ShadowVolume_CreateSilTriangles( int *shadowIndexes, const byte *facing, const silEdge_s *silEdges, const int numSilEdges ) = 0; + virtual int VPCALL ShadowVolume_CreateCapTriangles( int *shadowIndexes, const byte *facing, const int *indexes, const int numIndexes ) = 0; + + // sound mixing + virtual void VPCALL UpSamplePCMTo44kHz( float *dest, const short *pcm, const int numSamples, const int kHz, const int numChannels ) = 0; + virtual void VPCALL UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) = 0; + virtual void VPCALL MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) = 0; + virtual void VPCALL MixSoundTwoSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) = 0; + virtual void VPCALL MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) = 0; + virtual void VPCALL MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) = 0; + virtual void VPCALL MixSoundSixSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) = 0; + virtual void VPCALL MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) = 0; + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) = 0; + + // rvSilTraceVertT operations +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts +#ifdef _MD5R_SUPPORT + virtual void VPCALL JointMat_MultiplyMats( float *destMats, const idJointMat *src1Mats, const idJointMat *src2Mats, int *transformPalette, int transformCount ) = 0; + virtual void VPCALL TransformVertsMinMax4Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ) = 0; // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL TransformVertsMinMax1Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ) = 0; // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const rvSilTraceVertT *src, const int count ) = 0; + virtual void VPCALL Dot( float *dst, const idPlane &constant, const rvSilTraceVertT *src, const int count ) = 0; + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) = 0; + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) = 0; + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) = 0; + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const int *indexes, const int numIndexes ) = 0; + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const unsigned short *indexes, const int numIndexes ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int count ) = 0; + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int *indexes, const int count ) = 0; +#endif +// RAVEN END +}; +// RAVEN BEGIN +#endif +// RAVEN END + +// pointer to SIMD processor +extern idSIMDProcessor *SIMDProcessor; + +#endif /* !__MATH_SIMD_H__ */ diff --git a/src/idlib/math/Simd_3DNow.cpp b/src/idlib/math/Simd_3DNow.cpp new file mode 100644 index 0000000..534b7e0 --- /dev/null +++ b/src/idlib/math/Simd_3DNow.cpp @@ -0,0 +1,293 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_3DNow.h" + + +//=============================================================== +// +// 3DNow! implementation of idSIMDProcessor +// +//=============================================================== + +#ifdef _WINDOWS + +/* +============ +idSIMD_3DNow::GetName +============ +*/ +const char * idSIMD_3DNow::GetName( void ) const { + return "MMX & 3DNow!"; +} + +// Very optimized memcpy() routine for all AMD Athlon and Duron family. +// This code uses any of FOUR different basic copy methods, depending +// on the transfer size. +// NOTE: Since this code uses MOVNTQ (also known as "Non-Temporal MOV" or +// "Streaming Store"), and also uses the software prefetchnta instructions, +// be sure you're running on Athlon/Duron or other recent CPU before calling! + +#define TINY_BLOCK_COPY 64 // upper limit for movsd type copy +// The smallest copy uses the X86 "movsd" instruction, in an optimized +// form which is an "unrolled loop". + +#define IN_CACHE_COPY 64 * 1024 // upper limit for movq/movq copy w/SW prefetch +// Next is a copy that uses the MMX registers to copy 8 bytes at a time, +// also using the "unrolled loop" optimization. This code uses +// the software prefetch instruction to get the data into the cache. + +#define UNCACHED_COPY 197 * 1024 // upper limit for movq/movntq w/SW prefetch +// For larger blocks, which will spill beyond the cache, it's faster to +// use the Streaming Store instruction MOVNTQ. This write instruction +// bypasses the cache and writes straight to main memory. This code also +// uses the software prefetch instruction to pre-read the data. +// USE 64 * 1024 FOR THIS VALUE IF YOU'RE ALWAYS FILLING A "CLEAN CACHE" + +#define BLOCK_PREFETCH_COPY infinity // no limit for movq/movntq w/block prefetch +#define CACHEBLOCK 80h // number of 64-byte blocks (cache lines) for block prefetch +// For the largest size blocks, a special technique called Block Prefetch +// can be used to accelerate the read operations. Block Prefetch reads +// one address per cache line, for a series of cache lines, in a short loop. +// This is faster than using software prefetch. The technique is great for +// getting maximum read bandwidth, especially in DDR memory systems. + +/* +================ +idSIMD_3DNow::Memcpy + + optimized memory copy routine that handles all alignment cases and block sizes efficiently +================ +*/ +void VPCALL idSIMD_3DNow::Memcpy( void *dest, const void *src, const int n ) { + __asm { + + mov ecx, [n] // number of bytes to copy + mov edi, [dest] // destination + mov esi, [src] // source + mov ebx, ecx // keep a copy of count + + cld + cmp ecx, TINY_BLOCK_COPY + jb $memcpy_ic_3 // tiny? skip mmx copy + + cmp ecx, 32*1024 // don't align between 32k-64k because + jbe $memcpy_do_align // it appears to be slower + cmp ecx, 64*1024 + jbe $memcpy_align_done +$memcpy_do_align: + mov ecx, 8 // a trick that's faster than rep movsb... + sub ecx, edi // align destination to qword + and ecx, 111b // get the low bits + sub ebx, ecx // update copy count + neg ecx // set up to jump into the array + add ecx, offset $memcpy_align_done + jmp ecx // jump to array of movsb's + +align 4 + movsb + movsb + movsb + movsb + movsb + movsb + movsb + movsb + +$memcpy_align_done: // destination is dword aligned + mov ecx, ebx // number of bytes left to copy + shr ecx, 6 // get 64-byte block count + jz $memcpy_ic_2 // finish the last few bytes + + cmp ecx, IN_CACHE_COPY/64 // too big 4 cache? use uncached copy + jae $memcpy_uc_test + +// This is small block copy that uses the MMX registers to copy 8 bytes +// at a time. It uses the "unrolled loop" optimization, and also uses +// the software prefetch instruction to get the data into the cache. +align 16 +$memcpy_ic_1: // 64-byte block copies, in-cache copy + + prefetchnta [esi + (200*64/34+192)] // start reading ahead + + movq mm0, [esi+0] // read 64 bits + movq mm1, [esi+8] + movq [edi+0], mm0 // write 64 bits + movq [edi+8], mm1 // note: the normal movq writes the + movq mm2, [esi+16] // data to cache; a cache line will be + movq mm3, [esi+24] // allocated as needed, to store the data + movq [edi+16], mm2 + movq [edi+24], mm3 + movq mm0, [esi+32] + movq mm1, [esi+40] + movq [edi+32], mm0 + movq [edi+40], mm1 + movq mm2, [esi+48] + movq mm3, [esi+56] + movq [edi+48], mm2 + movq [edi+56], mm3 + + add esi, 64 // update source pointer + add edi, 64 // update destination pointer + dec ecx // count down + jnz $memcpy_ic_1 // last 64-byte block? + +$memcpy_ic_2: + mov ecx, ebx // has valid low 6 bits of the byte count +$memcpy_ic_3: + shr ecx, 2 // dword count + and ecx, 1111b // only look at the "remainder" bits + neg ecx // set up to jump into the array + add ecx, offset $memcpy_last_few + jmp ecx // jump to array of movsd's + +$memcpy_uc_test: + cmp ecx, UNCACHED_COPY/64 // big enough? use block prefetch copy + jae $memcpy_bp_1 + +$memcpy_64_test: + or ecx, ecx // tail end of block prefetch will jump here + jz $memcpy_ic_2 // no more 64-byte blocks left + +// For larger blocks, which will spill beyond the cache, it's faster to +// use the Streaming Store instruction MOVNTQ. This write instruction +// bypasses the cache and writes straight to main memory. This code also +// uses the software prefetch instruction to pre-read the data. +align 16 +$memcpy_uc_1: // 64-byte blocks, uncached copy + + prefetchnta [esi + (200*64/34+192)] // start reading ahead + + movq mm0,[esi+0] // read 64 bits + add edi,64 // update destination pointer + movq mm1,[esi+8] + add esi,64 // update source pointer + movq mm2,[esi-48] + movntq [edi-64], mm0 // write 64 bits, bypassing the cache + movq mm0,[esi-40] // note: movntq also prevents the CPU + movntq [edi-56], mm1 // from READING the destination address + movq mm1,[esi-32] // into the cache, only to be over-written + movntq [edi-48], mm2 // so that also helps performance + movq mm2,[esi-24] + movntq [edi-40], mm0 + movq mm0,[esi-16] + movntq [edi-32], mm1 + movq mm1,[esi-8] + movntq [edi-24], mm2 + movntq [edi-16], mm0 + dec ecx + movntq [edi-8], mm1 + jnz $memcpy_uc_1 // last 64-byte block? + + jmp $memcpy_ic_2 // almost done + +// For the largest size blocks, a special technique called Block Prefetch +// can be used to accelerate the read operations. Block Prefetch reads +// one address per cache line, for a series of cache lines, in a short loop. +// This is faster than using software prefetch, in this case. +// The technique is great for getting maximum read bandwidth, +// especially in DDR memory systems. +$memcpy_bp_1: // large blocks, block prefetch copy + + cmp ecx, CACHEBLOCK // big enough to run another prefetch loop? + jl $memcpy_64_test // no, back to regular uncached copy + + mov eax, CACHEBLOCK / 2 // block prefetch loop, unrolled 2X + add esi, CACHEBLOCK * 64 // move to the top of the block +align 16 +$memcpy_bp_2: + mov edx, [esi-64] // grab one address per cache line + mov edx, [esi-128] // grab one address per cache line + sub esi, 128 // go reverse order + dec eax // count down the cache lines + jnz $memcpy_bp_2 // keep grabbing more lines into cache + + mov eax, CACHEBLOCK // now that it's in cache, do the copy +align 16 +$memcpy_bp_3: + movq mm0, [esi ] // read 64 bits + movq mm1, [esi+ 8] + movq mm2, [esi+16] + movq mm3, [esi+24] + movq mm4, [esi+32] + movq mm5, [esi+40] + movq mm6, [esi+48] + movq mm7, [esi+56] + add esi, 64 // update source pointer + movntq [edi ], mm0 // write 64 bits, bypassing cache + movntq [edi+ 8], mm1 // note: movntq also prevents the CPU + movntq [edi+16], mm2 // from READING the destination address + movntq [edi+24], mm3 // into the cache, only to be over-written, + movntq [edi+32], mm4 // so that also helps performance + movntq [edi+40], mm5 + movntq [edi+48], mm6 + movntq [edi+56], mm7 + add edi, 64 // update dest pointer + + dec eax // count down + + jnz $memcpy_bp_3 // keep copying + sub ecx, CACHEBLOCK // update the 64-byte block count + jmp $memcpy_bp_1 // keep processing chunks + +// The smallest copy uses the X86 "movsd" instruction, in an optimized +// form which is an "unrolled loop". Then it handles the last few bytes. +align 4 + movsd + movsd // perform last 1-15 dword copies + movsd + movsd + movsd + movsd + movsd + movsd + movsd + movsd // perform last 1-7 dword copies + movsd + movsd + movsd + movsd + movsd + movsd + +$memcpy_last_few: // dword aligned from before movsd's + mov ecx, ebx // has valid low 2 bits of the byte count + and ecx, 11b // the last few cows must come home + jz $memcpy_final // no more, let's leave + rep movsb // the last 1, 2, or 3 bytes + +$memcpy_final: + emms // clean up the MMX state + sfence // flush the write buffer + mov eax, [dest] // ret value = destination pointer + + } +} + +#endif // _WINDOWS diff --git a/src/idlib/math/Simd_3DNow.h b/src/idlib/math/Simd_3DNow.h new file mode 100644 index 0000000..6543e24 --- /dev/null +++ b/src/idlib/math/Simd_3DNow.h @@ -0,0 +1,46 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_3DNOW_H__ +#define __MATH_SIMD_3DNOW_H__ + +/* +=============================================================================== + + 3DNow! implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_3DNow : public idSIMD_MMX { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ); + +#endif +}; + +#endif /* !__MATH_SIMD_3DNOW_H__ */ diff --git a/src/idlib/math/Simd_AltiVec.cpp b/src/idlib/math/Simd_AltiVec.cpp new file mode 100644 index 0000000..be94ccb --- /dev/null +++ b/src/idlib/math/Simd_AltiVec.cpp @@ -0,0 +1,11183 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_AltiVec.h" +#include + +#if defined(MACOS_X) && defined(__ppc__) + +#ifdef PPC_INTRINSICS + #include +#endif + +#if defined(bool) && __GNUC__ < 4 + #undef bool +#endif + +// Doom3 SIMD Library version 0.5 +// Patrick Flanagan (pflanagan@apple.com) +// Sanjay Patel (spatel@apple.com) +// Architecture & Performance Group, Apple Computer + + +//=============================================================== +// +// AltiVec implementation of idSIMDProcessor +// +//=============================================================== + +// Data struct sizes + +// 64 bytes +#define DRAWVERT_OFFSET 16 +// 16 bytes each, 4 floats +#define PLANE_OFFSET 4 +// 16 bytes each, 4 floats +#define IDVEC4_OFFSET 4 + +// Alignment tests +#define IS_16BYTE_ALIGNED( x ) ( ( (unsigned long)&x & 0x0F ) == 0 ) +#define NOT_16BYTE_ALIGNED( x ) ( ( (unsigned long)&x & 0x0F) != 0 ) + +// Aligned storing floats +#define ALIGNED_STORE2( ADDR, V0, V1 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ) + +#define ALIGNED_STORE3( ADDR, V0, V1, V2 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ) + +#define ALIGNED_STORE4( ADDR, V0, V1, V2, V3 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ); \ + vec_st( V3, 48, ADDR ) + +#define ALIGNED_STORE6( ADDR, V0, V1, V2, V3, V4, V5 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ); \ + vec_st( V3, 48, ADDR ); \ + vec_st( V4, 64, ADDR ); \ + vec_st( V5, 80, ADDR ) + +#define ALIGNED_STORE8( ADDR, V0, V1, V2, V3, V4, V5, V6, V7 ) \ + vec_st( V0, 0, ADDR ); \ + vec_st( V1, 16, ADDR ); \ + vec_st( V2, 32, ADDR ); \ + vec_st( V3, 48, ADDR ); \ + vec_st( V4, 64, ADDR ); \ + vec_st( V5, 80, ADDR ); \ + vec_st( V6, 96, ADDR ); \ + vec_st( V7, 112, ADDR ) + +// Unaligned storing floats. These assume that we can trash the input +#define UNALIGNED_STORE1( ADDR, V0 ) { \ + /* use store element */ \ + vector unsigned char ULStoreMacroPerm = vec_lvsr( 0, ADDR ); \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + vec_ste( V0, 0, ADDR ); \ + vec_ste( V0, 4, ADDR ); \ + vec_ste( V0, 8, ADDR ); \ + vec_ste( V0, 12, ADDR ); \ + } + +#define UNALIGNED_STORE2( ADDR, V0, V1 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 31, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); } + +#define UNALIGNED_STORE3( ADDR, V0, V1, V2 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 47, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); } + +#define UNALIGNED_STORE4( ADDR, V0, V1, V2, V3 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 63, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + V3 = vec_perm( V3, V3, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4, ULStoreVal5; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, V3, ULStoreMacroMask ); \ + ULStoreVal5 = vec_sel( V3, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); \ + vec_st( ULStoreVal5, 63, ADDR ); } + +#define UNALIGNED_STORE6( ADDR, V0, V1, V2, V3, V4, V5 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 95, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + V3 = vec_perm( V3, V3, ULStoreMacroPerm ); \ + V4 = vec_perm( V4, V4, ULStoreMacroPerm ); \ + V5 = vec_perm( V5, V5, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4, ULStoreVal5, ULStoreVal6, ULStoreVal7; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, V3, ULStoreMacroMask ); \ + ULStoreVal5 = vec_sel( V3, V4, ULStoreMacroMask ); \ + ULStoreVal6 = vec_sel( V4, V5, ULStoreMacroMask ); \ + ULStoreVal7 = vec_sel( V5, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); \ + vec_st( ULStoreVal5, 63, ADDR ); \ + vec_st( ULStoreVal6, 79, ADDR ); \ + vec_st( ULStoreVal7, 95, ADDR ); } + +#define UNALIGNED_STORE9( ADDR, V0, V1, V2, V3, V4, V5, V6, V7, V8 ) { \ + /* load up the values that are there now */ \ + vector float ULStoreMacro1 = vec_ld( 0, ADDR ); \ + vector float ULStoreMacro2 = vec_ld( 143, ADDR ); \ + /* generate permute vector and mask */ \ + vector unsigned char ULStoreMacroPerm = vec_sub( vec_lvsr( 15, ADDR ), (vector unsigned char)(1) ); \ + vector unsigned int ULStoreMacroMask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), ULStoreMacroPerm ); \ + /* right rotate input data */ \ + V0 = vec_perm( V0, V0, ULStoreMacroPerm ); \ + V1 = vec_perm( V1, V1, ULStoreMacroPerm ); \ + V2 = vec_perm( V2, V2, ULStoreMacroPerm ); \ + V3 = vec_perm( V3, V3, ULStoreMacroPerm ); \ + V4 = vec_perm( V4, V4, ULStoreMacroPerm ); \ + V5 = vec_perm( V5, V5, ULStoreMacroPerm ); \ + V6 = vec_perm( V6, V6, ULStoreMacroPerm ); \ + V7 = vec_perm( V7, V7, ULStoreMacroPerm ); \ + V8 = vec_perm( V8, V8, ULStoreMacroPerm ); \ + /* setup the output vectors */ \ + vector float ULStoreVal1, ULStoreVal2, ULStoreVal3, ULStoreVal4, ULStoreVal5, ULStoreVal6, ULStoreVal7; \ + vector float ULStoreVal8, ULStoreVal9, ULStoreVal10; \ + ULStoreVal1 = vec_sel( ULStoreMacro1, V0, ULStoreMacroMask ); \ + ULStoreVal2 = vec_sel( V0, V1, ULStoreMacroMask ); \ + ULStoreVal3 = vec_sel( V1, V2, ULStoreMacroMask ); \ + ULStoreVal4 = vec_sel( V2, V3, ULStoreMacroMask ); \ + ULStoreVal5 = vec_sel( V3, V4, ULStoreMacroMask ); \ + ULStoreVal6 = vec_sel( V4, V5, ULStoreMacroMask ); \ + ULStoreVal7 = vec_sel( V5, V6, ULStoreMacroMask ); \ + ULStoreVal8 = vec_sel( V6, V7, ULStoreMacroMask ); \ + ULStoreVal9 = vec_sel( V7, V8, ULStoreMacroMask ); \ + ULStoreVal10 = vec_sel( V8, ULStoreMacro2, ULStoreMacroMask ); \ + /* store results */ \ + vec_st( ULStoreVal1, 0, ADDR ); \ + vec_st( ULStoreVal2, 15, ADDR ); \ + vec_st( ULStoreVal3, 31, ADDR ); \ + vec_st( ULStoreVal4, 47, ADDR ); \ + vec_st( ULStoreVal5, 63, ADDR ); \ + vec_st( ULStoreVal6, 79, ADDR ); \ + vec_st( ULStoreVal7, 95, ADDR ); \ + vec_st( ULStoreVal8, 111, ADDR ); \ + vec_st( ULStoreVal9, 127, ADDR ); \ + vec_st( ULStoreVal10, 143, ADDR ); } + + +/* +============ +idSIMD_AltiVec::GetName +============ +*/ +const char *idSIMD_AltiVec::GetName( void ) const { + return "AltiVec"; +} + +/* + Helper Functions +*/ +#if 0 +// Prints the values of a vector, useful for debugging but +// should never be called in real code +inline void debugPrintVector( vector float v, char *msg ) { + printf("%s -- %vf\n", msg, v ); +} + +inline void debugPrintVector( vector unsigned int v, char *msg ) { + printf("%s -- %vd\n", msg, v ); +} + +inline void debugPrintVector( vector bool int v, char *msg ) { + printf("%s -- %vi\n", msg, v ); +} + +inline void debugPrintVector( vector unsigned char v, char *msg ) { + printf("%s -- %vuc\n", msg, v ); +} + +inline void debugPrintVector( vector unsigned short v, char *msg ) { + printf("%s -- %vs\n", msg, v ); +} +#endif +/* +=============== + Reciprocal + + For each element in vector: + n = 1 / n +=============== +*/ + +// Use Newton-Raphson to calculate reciprocal of a vector +inline vector float Reciprocal( vector float v ) { + //Get the reciprocal estimate + vector float estimate = vec_re( v ); + //One round of Newton-Raphson refinement + return vec_madd( vec_nmsub( estimate, v, (vector float) (1.0) ), estimate, estimate ); +} + +/* +=============== + ReciprocalSquareRoot + + For each element in vector: + n = 1 / sqrt(n) +=============== +*/ +// Reciprocal square root estimate of a vector +inline vector float ReciprocalSquareRoot( vector float v ) { + //Get the square root reciprocal estimate + vector float zero = (vector float)(0); + vector float oneHalf = (vector float)(0.5); + vector float one = (vector float)(1.0); + vector float estimate = vec_rsqrte( vec_max( v, (vector float)(FLT_MIN) ) ); + + //One round of Newton-Raphson refinement + vector float estimateSquared = vec_madd( estimate, estimate, zero ); + vector float halfEstimate = vec_madd( estimate, oneHalf, zero ); + return vec_madd( vec_nmsub( v, estimateSquared, one ), halfEstimate, estimate ); +} + + +/* +=============== + Divide + + For each element in vectors: + n = a / b +=============== +*/ +// Use reciprocal estimate and multiply to divide a vector +inline vector float Divide( vector float a, vector float b ) { + return vec_madd( a, Reciprocal( b ), (vector float)(0) ); +} + +/* +=============== + loadSplatUnalignedScalar + + For each element in vector: + n = s +=============== +*/ +inline vector float loadSplatUnalignedScalar( const float *s ) { + vector unsigned char splatMap = vec_lvsl( 0, s ); + vector float v = vec_ld( 0, s ); + splatMap = (vector unsigned char) vec_splat( (vector float) splatMap, 0 ); + return vec_perm( v, v, splatMap ); +} + +/* +=============== + VectorATan16 + + For each element in vector: + n = idMath::ATan16( x, y ) +=============== +*/ +// calculates arc tangent of a vector with 16 bits of precision, based on atan16 in idMath +inline vector float VectorATan16( vector float x, vector float y ) { + + vector float xDivY = Divide( x, y ); + vector float yDivX = Divide( y, x ); + vector float zeroVector = (vector float)(0); + + vector bool int vecCmp = vec_cmpgt( vec_abs( y ), vec_abs( x ) ); + vector float vecA = vec_sel( yDivX, xDivY, vecCmp ); + vector bool int vecCmp2 = vec_cmplt( vecA, zeroVector ); + vector float vecS = vec_madd( vecA, vecA, (vector float)(0) ); + + // do calculation for S + vector float vecWork1 = vec_madd( (vector float)(0.0028662257f), vecS, (vector float)(-0.0161657367f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(0.0429096138f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(-0.0752896400f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(0.1065626393f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(-0.1420889944f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(0.1999355085f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(-0.3333314528f) ); + vecWork1 = vec_madd( vecWork1, vecS, (vector float)(1) ); + + // get the regular S value + vecS = vec_madd( vecWork1, vecA, (vector float)(0) ); + + // calculate what to return if y > x + vector float negSPlusHalfPI = vec_madd( vecS, (vector float)(-1), (vector float)(0.5f * 3.14159265358979323846f) ); + vector float negSMinusHalfPI = vec_madd( vecS, (vector float)(-1), (vector float)(-0.5f * 3.14159265358979323846f) ); + vector float modRet = vec_sel( negSPlusHalfPI, negSMinusHalfPI, vecCmp2 ); + + return vec_sel( modRet, vecS, vecCmp ); +} + +/* +=============== + VectorSin16 + + For each element in vector: + n = idMath::Sin16( v ) +=============== +*/ +inline vector float VectorSin16( vector float v ) { + vector float zero = (vector float)(0); + +#if 0 + // load up half PI and use it to calculate the rest of the values. This is + // sometimes cheaper than loading them from memory + + vector float halfPI = (vector float) ( 0.5f * 3.14159265358979323846f ); + vector float PI = vec_add( halfPI, halfPI ); + vector float oneandhalfPI = vec_add( PI, halfPI ); + vector float twoPI = vec_add( oneandhalfPI, halfPI ); +#else + vector float halfPI = (vector float) ( 0.5f * 3.14159265358979323846f ); + vector float PI = (vector float)(3.14159265358979323846f); + vector float oneandhalfPI = (vector float)(3.14159265358979323846f + ( 0.5f * 3.14159265358979323846f ) ); + vector float twoPI = (vector float)( 2.0f * 3.14159265358979323846f); +#endif + + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4; + + vector float vecMod; + vector float vecResult; + + // fix the range if needbe + vecMod = vec_floor( Divide( v, twoPI ) ); + vecResult = vec_nmsub( vecMod, twoPI, v ); + + vector float vecPIminusA = vec_sub( PI, vecResult ); + vector float vecAminus2PI = vec_sub( vecResult, twoPI ); + + vecCmp1 = vec_cmplt( vecResult, PI ); + vecCmp2 = vec_cmpgt( vecResult, halfPI ); + + // these are the ones where a > PI + HALF_PI so set a = a - TWO_PI + vecCmp3 = vec_cmpgt( vecResult, oneandhalfPI ); + + // we also want to set a = PI - a everywhere that !(a < PI) and !(a > PI + HALF_PI) + vecCmp4 = vec_and( vec_xor( vecCmp3, (vector bool int)(1) ), vec_xor( vecCmp1, (vector bool int)(1) ) ); // everywhere that both of those are false + + // these are ones where a < PI and a > HALF_PI so we set a = PI - a + vecCmp1 = vec_and( vecCmp1, vecCmp2 ); + vecCmp1 = vec_or( vecCmp1, vecCmp4 ); + + // put the correct values into place + vecResult = vec_sel( vecResult, vecPIminusA, vecCmp1 ); + vecResult = vec_sel( vecResult, vecAminus2PI, vecCmp3 ); + + // calculate answer + vector float vecASquared = vec_madd( vecResult, vecResult, zero ); + vector float vecEst = vec_madd( (vector float)(-2.39e-08f), vecASquared, (vector float)(2.7526e-06f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(-1.98409e-04f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(8.3333315e-03f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(-1.666666664e-01f) ); + vecEst = vec_madd( vecEst, vecASquared, (vector float)(1.0f) ); + return vec_madd( vecResult, vecEst, zero ); +} + +/* +=============== + vecSplatWithRunTime + + For each element in vector: + n = v(i) +=============== +*/ +// splats an element across a vector using a runtime variable +inline vector float vecSplatWithRunTime( vector float v, int i ) { + vector unsigned char rotate = vec_lvsl( i * sizeof( float ), (int*) 0L ); + v = vec_perm( v, v, rotate ); + return vec_splat( v, 0 ); +} + + +/* +=============== + FastScalarInvSqrt + + n = 1 / sqrt( f ) +=============== +*/ +inline float FastScalarInvSqrt( float f ) { +#ifdef PPC_INTRINSICS + float estimate; + const float kSmallestFloat = FLT_MIN; + + //Calculate a 5 bit starting estimate for the reciprocal sqrt + estimate = __frsqrte ( f + kSmallestFloat ); + + //if you require less precision, you may reduce the number of loop iterations. + // This will do 2 rounds of NR + estimate = estimate + 0.5f * estimate * ( 1.0f - f * estimate * estimate ); + estimate = estimate + 0.5f * estimate * ( 1.0f - f * estimate * estimate ); + return estimate; +#else + return idMath::InvSqrt( f ); +#endif +} + +/* +=============== + FastScalarInvSqrt_x3 + + arg1 = 1 / sqrt( arg1 ) + arg2 = 1 / sqrt( arg2 ) + arg3 = 1 / sqrt( arg3 ) +=============== +*/ +inline void FastScalarInvSqrt_x3( float *arg1, float *arg2, float *arg3 ) { +#ifdef PPC_INTRINSICS + register float estimate1, estimate2, estimate3; + const float kSmallestFloat = FLT_MIN; + + //Calculate a 5 bit starting estimate for the reciprocal sqrt of each + estimate1 = __frsqrte ( *arg1 + kSmallestFloat ); + estimate2 = __frsqrte ( *arg2 + kSmallestFloat ); + estimate3 = __frsqrte ( *arg3 + kSmallestFloat ); + + // two rounds newton-raphson + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + + *arg1 = estimate1; + *arg2 = estimate2; + *arg3 = estimate3; +#else + *arg1 = idMath::InvSqrt( *arg1 ); + *arg2 = idMath::InvSqrt( *arg2 ); + *arg3 = idMath::InvSqrt( *arg3 ); +#endif +} + +/* +=============== + FastScalarInvSqrt_x6 + + arg1 = 1 / sqrt( arg1 ) + arg2 = 1 / sqrt( arg2 ) + arg3 = 1 / sqrt( arg3 ) + arg4 = 1 / sqrt( arg4 ) + arg5 = 1 / sqrt( arg5 ) + arg6 = 1 / sqrt( arg6 ) + + On a G5, you've got 2 pipeline stages to fill. (2 FPU's with 6 stages each) +=============== +*/ +inline void FastScalarInvSqrt_x6( float *arg1, float *arg2, float *arg3, float *arg4, float *arg5, float *arg6 ) { +#ifdef PPC_INTRINSICS + register float estimate1, estimate2, estimate3, estimate4, estimate5, estimate6; + const float kSmallestFloat = FLT_MIN; + + //Calculate a 5 bit starting estimate for the reciprocal sqrt of each + estimate1 = __frsqrte ( *arg1 + kSmallestFloat ); + estimate2 = __frsqrte ( *arg2 + kSmallestFloat ); + estimate3 = __frsqrte ( *arg3 + kSmallestFloat ); + estimate4 = __frsqrte ( *arg4 + kSmallestFloat ); + estimate5 = __frsqrte ( *arg5 + kSmallestFloat ); + estimate6 = __frsqrte ( *arg6 + kSmallestFloat ); + + // two rounds newton-raphson + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + estimate4 = estimate4 + 0.5f * estimate4 * ( 1.0f - *arg4 * estimate4 * estimate4 ); + estimate5 = estimate5 + 0.5f * estimate5 * ( 1.0f - *arg5 * estimate5 * estimate5 ); + estimate6 = estimate6 + 0.5f * estimate6 * ( 1.0f - *arg6 * estimate6 * estimate6 ); + + estimate1 = estimate1 + 0.5f * estimate1 * ( 1.0f - *arg1 * estimate1 * estimate1 ); + estimate2 = estimate2 + 0.5f * estimate2 * ( 1.0f - *arg2 * estimate2 * estimate2 ); + estimate3 = estimate3 + 0.5f * estimate3 * ( 1.0f - *arg3 * estimate3 * estimate3 ); + estimate4 = estimate4 + 0.5f * estimate4 * ( 1.0f - *arg4 * estimate4 * estimate4 ); + estimate5 = estimate5 + 0.5f * estimate5 * ( 1.0f - *arg5 * estimate5 * estimate5 ); + estimate6 = estimate6 + 0.5f * estimate6 * ( 1.0f - *arg6 * estimate6 * estimate6 ); + + *arg1 = estimate1; + *arg2 = estimate2; + *arg3 = estimate3; + *arg4 = estimate4; + *arg5 = estimate5; + *arg6 = estimate6; +#else + *arg1 = idMath::InvSqrt( *arg1 ); + *arg2 = idMath::InvSqrt( *arg2 ); + *arg3 = idMath::InvSqrt( *arg3 ); + *arg4 = idMath::InvSqrt( *arg4 ); + *arg5 = idMath::InvSqrt( *arg5 ); + *arg6 = idMath::InvSqrt( *arg6 ); +#endif +} + + +// End Helper Functions + +#ifdef ENABLE_SIMPLE_MATH + +/* +============ +idSIMD_AltiVec::Add + + dst[i] = constant + src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Add( float *dst, const float constant, const float *src, const int count ) { + vector float v0, v1, v2, v3; + vector float v0_low, v0_hi, v1_hi; + vector unsigned char permVec; + vector float constVec; + int i; + + // handle unaligned cases at beginning + for ( i = 0; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant + src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), (vector unsigned char)(1) ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v0_hi, v1_hi, permVec ); + + v2 = vec_add( v0, constVec ); + v3 = vec_add( v1, constVec ); + + // store results + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant + src[i]; + } +} + +/* +============ +idSIMD_AltiVec::Add + + dst[i] = src0[i] + src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Add( float *dst, const float *src0, const float *src1, const int count ) { + + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + + int i; + + //unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src0[i] + src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + v4 = vec_add( v0, v1 ); + v5 = vec_add( v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] + src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::Sub + + dst[i] = constant - src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Sub( float *dst, const float constant, const float *src, const int count ) { + + register vector float v0, v1, v2, v3; + register vector float v0_low, v0_hi, v1_low, v1_hi; + register vector unsigned char permVec; + register vector float constVec; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant - src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute vector and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = vec_sub( constVec, v0 ); + v3 = vec_sub( constVec, v1 ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant - src[i]; + } +} + +/* +============ +idSIMD_AltiVec::Sub + + dst[i] = src0[i] - src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Sub( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src0[i] - src1[i]; + } + + //calculate permute and do first loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + v4 = vec_sub( v0, v1 ); + v5 = vec_sub( v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] - src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::Mul + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Mul( float *dst, const float constant, const float *src, const int count) { + register vector float v0, v0_low, v0_hi, v1_low, v1_hi, v1, v2, v3; + register vector float constVec; + register vector unsigned char permVec; + vector unsigned char oneCharVector = (vector unsigned char)(1); + register vector float zeroVector = (vector float)(0.0); + int i; + + // handle unaligned data at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = constant * src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = vec_madd( constVec, v0, zeroVector ); + v3 = vec_madd( constVec, v1, zeroVector ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant * src[i]; + } +} + +/* +============ +idSIMD_AltiVec::Mul + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Mul( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + register vector float constVec = (vector float)(0.0); + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] * src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //no such thing as regular multiply so we do + //multiply then add zero + v4 = vec_madd( v0, v1, constVec ); + v5 = vec_madd( v2, v3, constVec ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] * src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::Div + + dst[i] = constant / divisor[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Div( float *dst, const float constant, const float *divisor, const int count ) { + register vector float v0, v1, v2, v3; + register vector float v0_low, v0_hi, v1_low, v1_hi; + register vector unsigned char permVec; + register vector float constVec; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = constant / divisor[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do first loads + permVec = vec_add( vec_lvsl( -1, (int*) &divisor[i] ), oneCharVector ); + v1_hi = vec_ld( 0, &divisor[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &divisor[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &divisor[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = Divide( constVec, v0 ); + v3 = Divide( constVec, v1 ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = constant / divisor[i]; + } +} + +/* +============ +idSIMD_AltiVec::Div + + dst[i] = src0[i] / src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Div( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] / src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + v4 = Divide( v0, v1 ); + v5 = Divide( v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] / src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulAdd + + dst[i] += constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulAdd( float *dst, const float constant, const float *src, const int count ) { + + register vector float v0, v1, v2, v3, v4, v5; + register vector float constVec; + //src + register vector float v0_low, v0_hi, v2_low, v2_hi; + //permute vectors + register vector unsigned char permVec1; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] += constant * src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + + // at this point, dst is known to be aligned + v1 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + + v4 = vec_madd( constVec, v0, v1 ); + v5 = vec_madd( constVec, v2, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] += constant * src[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulAdd + + dst[i] += src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulAdd( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + + int i; + + //unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] += src0[i] * src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + // load sources + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //we know dst is aligned because we handled unaligned cases + //up front + v4 = vec_ld( 0, &dst[i] ); + v5 = vec_ld( 16, &dst[i] ); + + v6 = vec_madd( v0, v1, v4 ); + v7 = vec_madd( v2, v3, v5 ); + + ALIGNED_STORE2( &dst[i], v6, v7 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] += src0[i] * src1[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulSub + + dst[i] -= constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulSub( float *dst, const float constant, const float *src, const int count ) { + register vector float v0, v1, v2, v3, v4, v5; + register vector float constVec; + //src + register vector float v0_low, v0_hi, v2_low, v2_hi; + //permute vectors + register vector unsigned char permVec1; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] -= constant * src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + + //we know dst will be aligned here because we already handled the preceeding + //unaligned cases + v1 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + + v4 = vec_nmsub( v0, constVec, v1 ); + v5 = vec_nmsub( v2, constVec, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] -= constant * src[i]; + } +} + +/* +============ +idSIMD_AltiVec::MulSub + + dst[i] -= src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::MulSub( float *dst, const float *src0, const float *src1, const int count ) { + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i; + + //unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] -= src0[i] * src1[i]; + } + + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src0[i] ); + v3_hi = vec_ld( 0, &src1[i] ); + + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + // load sources + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src0[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src1[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src1[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //we know dst is aligned because we handled unaligned cases + //up front + v4 = vec_ld( 0, &dst[i] ); + v5 = vec_ld( 16, &dst[i] ); + + v6 = vec_nmsub( v0, v1, v4 ); + v7 = vec_nmsub( v2, v3, v5 ); + + ALIGNED_STORE2( &dst[i], v6, v7 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] -= src0[i] * src1[i]; + } +} + +#endif /* ENABLE_SIMPLE_MATH */ + +#ifdef ENABLE_DOT +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ) { + + register vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + register vector float vecX, vecY, vecZ; + vector float vecX2, vecY2, vecZ2; + const float *addr = src[0].ToFloatPtr(); + float tempVal[4]; + float constVal[4]; + register vector float zeroVector = (vector float)(0.0); + register vector float vecConstX, vecConstY, vecConstZ; + + // permute vectors + register vector unsigned char permX1 = (vector unsigned char)(0,1,2,3,12,13,14,15,24,25,26,27,28,29,30,31); //last 4 bytes are junk + register vector unsigned char permX2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,20,21,22,23); + + register vector unsigned char permY1 = (vector unsigned char)(4,5,6,7,16,17,18,19,28,29,30,31,0,1,2,3); //last 4 bytes are junk + register vector unsigned char permY2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,24,25,26,27); + + register vector unsigned char permZ1 = (vector unsigned char)(8,9,10,11,20,21,22,23,0,1,2,3,4,5,6,7); //last 8 bytes are junk + register vector unsigned char permZ2 = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,28,29,30,31); + + int i; + + // for scalar cleanup, if necessary + constVal[0] = constant[0]; + constVal[1] = constant[1]; + constVal[2] = constant[2]; + constVal[3] = 0; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vecLd1 = vec_ld( 0, constant.ToFloatPtr() ); + vecLd2 = vec_ld( 11, constant.ToFloatPtr() ); + vecLd1 = vec_perm( vecLd1, vecLd2, constPerm ); + + + // populate const vectors + vecConstX = vec_splat( vecLd1, 0 ); + vecConstY = vec_splat( vecLd1, 1 ); + vecConstZ = vec_splat( vecLd1, 2 ); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, addr ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i]; + } + + for ( ; i + 7 < count; i += 8 ) { + float *vecPtr = (float*)( addr + (i*3) ); + vector float v0, v1, v2, v3, v4, v5; + + v0 = vecOld; //vec_ld( 0, vecPtr ); + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + v3 = vec_ld( 47, vecPtr ); + v4 = vec_ld( 63, vecPtr ); + v5 = vec_ld( 79, vecPtr ); + vecOld = vec_ld( 95, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, v3, permVec ); + + vecLd4 = vec_perm( v3, v4, permVec ); + vecLd5 = vec_perm( v4, v5, permVec ); + vecLd6 = vec_perm( v5, vecOld, permVec ); + + // permute into X Y Z vectors + vecX = vec_perm( vecLd1, vecLd2, permX1 ); + vecY = vec_perm( vecLd1, vecLd2, permY1 ); + vecZ = vec_perm( vecLd1, vecLd2, permZ1 ); + vecX = vec_perm( vecX, vecLd3, permX2 ); + vecY = vec_perm( vecY, vecLd3, permY2 ); + vecZ = vec_perm( vecZ, vecLd3, permZ2 ); + + vecX2 = vec_perm( vecLd4, vecLd5, permX1 ); + vecY2 = vec_perm( vecLd4, vecLd5, permY1 ); + vecZ2 = vec_perm( vecLd4, vecLd5, permZ1 ); + vecX2 = vec_perm( vecX2, vecLd6, permX2 ); + vecY2 = vec_perm( vecY2, vecLd6, permY2 ); + vecZ2 = vec_perm( vecZ2, vecLd6, permZ2 ); + + // do multiply + vecX = vec_madd( vecX, vecConstX, zeroVector ); + vecY = vec_madd( vecY, vecConstY, vecX ); + vecZ = vec_madd( vecZ, vecConstZ, vecY ); + + vecX2 = vec_madd( vecX2, vecConstX, zeroVector ); + vecY2 = vec_madd( vecY2, vecConstY, vecX2 ); + vecZ2 = vec_madd( vecZ2, vecConstZ, vecY2 ); + + // store out results + ALIGNED_STORE2( &dst[i], vecZ, vecZ2 ); + } + + //cleanup + for ( ; i < count; i++ ) { + // look up whats at the address we want, cast it as float pointer, then + // dereference that pointer + tempVal[0] = *( addr + (i*3) + 0 ); + tempVal[1] = *( addr + (i*3) + 1 ); + tempVal[2] = *( addr + (i*3) + 2 ); + dst[i] = constVal[0] * tempVal[0] + constVal[1] * tempVal[1] + constVal[2] * tempVal[2]; + } +} + + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i].Normal() + src[i][3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ) { +//#define OPER(X) dst[(X)] = constant * src[(X)].Normal() + src[(X)][3]; + + assert( sizeof(idPlane) == PLANE_OFFSET * sizeof(float) ); + + int i; + float constVal[4]; + float srcVal[3]; + float srcI3; + float tempVal; + + vector float vecPlaneLd1, vecPlaneLd2, vecPlaneLd3, vecPlaneLd4; + vector float vecPlaneLd5, vecPlaneLd6, vecPlaneLd7, vecPlaneLd8; + vector float vecX, vecY, vecZ, vecI3; + vector float vecX2, vecY2, vecZ2, vecI32; + vector float vecConstX, vecConstY, vecConstZ; + + constVal[0] = constant[0]; + constVal[1] = constant[1]; + constVal[2] = constant[2]; + constVal[3] = 1; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vector float v0 = vec_ld( 0, constant.ToFloatPtr() ); + vector float v1 = vec_ld( 11, constant.ToFloatPtr() ); + vector float vecConst = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( vecConst, 0 ); + vecConstY = vec_splat( vecConst, 1 ); + vecConstZ = vec_splat( vecConst, 2 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i].Normal() + src[i][3]; + } + + const float *addr = src[i].ToFloatPtr(); + vector unsigned char permVec = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, addr ); + + for ( ; i + 7 < count; i += 8 ) { + float *planePtr = (float*)( addr + (i*PLANE_OFFSET) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + v0 = vecOld; //vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + v2 = vec_ld( 31, planePtr ); + v3 = vec_ld( 47, planePtr ); + v4 = vec_ld( 63, planePtr ); + v5 = vec_ld( 79, planePtr ); + v6 = vec_ld( 95, planePtr ); + v7 = vec_ld( 111, planePtr ); + vecOld = vec_ld( 127, planePtr ); + + vecPlaneLd1 = vec_perm( v0, v1, permVec ); + vecPlaneLd2 = vec_perm( v1, v2, permVec ); + vecPlaneLd3 = vec_perm( v2, v3, permVec ); + vecPlaneLd4 = vec_perm( v3, v4, permVec ); + + vecPlaneLd5 = vec_perm( v4, v5, permVec ); + vecPlaneLd6 = vec_perm( v5, v6, permVec ); + vecPlaneLd7 = vec_perm( v6, v7, permVec ); + vecPlaneLd8 = vec_perm( v7, vecOld, permVec ); + + // permute into X Y Z vectors, since this is square its basically + // a matrix transpose + v0 = vec_mergeh( vecPlaneLd1, vecPlaneLd3 ); + v1 = vec_mergeh( vecPlaneLd2, vecPlaneLd4 ); + v2 = vec_mergel( vecPlaneLd1, vecPlaneLd3 ); + v3 = vec_mergel( vecPlaneLd2, vecPlaneLd4 ); + + vecX = vec_mergeh( v0, v1 ); + vecY = vec_mergel( v0, v1 ); + vecZ = vec_mergeh( v2, v3 ); + vecI3 = vec_mergel( v2, v3 ); + + v4 = vec_mergeh( vecPlaneLd5, vecPlaneLd7 ); + v5 = vec_mergeh( vecPlaneLd6, vecPlaneLd8 ); + v6 = vec_mergel( vecPlaneLd5, vecPlaneLd7 ); + v7 = vec_mergel( vecPlaneLd6, vecPlaneLd8 ); + + vecX2 = vec_mergeh( v4, v5 ); + vecY2 = vec_mergel( v4, v5 ); + vecZ2 = vec_mergeh( v6, v7 ); + vecI32 = vec_mergel( v6, v7 ); + + // do calculation + v6 = vec_madd( vecZ, vecConstZ, vecI3 ); + v5 = vec_madd( vecY, vecConstY, v6 ); + v4 = vec_madd( vecX, vecConstX, v5 ); + + v0 = vec_madd( vecZ2, vecConstZ, vecI32 ); + v1 = vec_madd( vecY2, vecConstY, v0 ); + v2 = vec_madd( vecX2, vecConstX, v1 ); + + // store results + ALIGNED_STORE2( &dst[i], v4, v2 ); + } + + // cleanup + for ( ; i < count; i++ ) { + // populate srcVal with src X Y Z + srcVal[0] = *(addr + (i*PLANE_OFFSET) + 0 ); + srcVal[1] = *(addr + (i*PLANE_OFFSET) + 1 ); + srcVal[2] = *(addr + (i*PLANE_OFFSET) + 2 ); + + // put src[i][3] into srcI3 + srcI3 = *(addr + (i*PLANE_OFFSET) + 3 ); + + tempVal = constVal[0] * srcVal[0] + constVal[1] * srcVal[1] + constVal[2] * srcVal[2]; + dst[i] = tempVal + srcI3; + } +} + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant * src[(X)].xyz; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + int i; + register vector float vecConstX, vecConstY, vecConstZ; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 11, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + // permute into constant vectors + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i].xyz; + } + + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < count ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) src[i].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) src[i+1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) src[i+2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) src[i+3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + for ( ; i+3 < count; i += 4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 11, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 11, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 11, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 11, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + + // store results + vec_st( vecSrcZ1, 0, &dst[i] ); + } + + for ( ; i < count; i++ ) { + dst[i] = constant * src[i].xyz; + } +} +#else +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant * src[(X)].xyz; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + int i; + register vector float vecConstX, vecConstY, vecConstZ; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 11, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + // permute into constant vectors + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant * src[i].xyz; + } + + for ( ; i+3 < count; i += 4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + + // store results + vec_st( vecSrcZ1, 0, &dst[i] ); + } + + for ( ; i < count; i++ ) { + dst[i] = constant * src[i].xyz; + } +} + +#endif /* DRAWVERT_PADDED */ + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i] + constant[3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idVec3 *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)] + constant[3]; + + register vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + register vector float vecX, vecY, vecZ, vecX2, vecY2, vecZ2; + register vector float zeroVector = (vector float)(0.0); + register vector float vecConstX, vecConstY, vecConstZ; + register vector float vecConst3; + + idVec3 constNormal = constant.Normal(); + float const3 = constant[3]; + + // permute vectors + register vector unsigned char permX1 = (vector unsigned char)(0,1,2,3,12,13,14,15,24,25,26,27,28,29,30,31); //last 4 bytes are junk + register vector unsigned char permX2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,20,21,22,23); + + register vector unsigned char permY1 = (vector unsigned char)(4,5,6,7,16,17,18,19,28,29,30,31,0,1,2,3); //last 4 bytes are junk + register vector unsigned char permY2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,24,25,26,27); + + register vector unsigned char permZ1 = (vector unsigned char)(8,9,10,11,20,21,22,23,0,1,2,3,4,5,6,7); //last 8 bytes are junk + register vector unsigned char permZ2 = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,28,29,30,31); + + int i; + + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vecLd1 = vec_ld( 0, constant.ToFloatPtr() ); + vecLd2 = vec_ld( 15, constant.ToFloatPtr() ); + vecLd1 = vec_perm( vecLd1, vecLd2, constPerm ); + + // populate const vec + vecConstX = vec_splat( vecLd1, 0 ); + vecConstY = vec_splat( vecLd1, 1 ); + vecConstZ = vec_splat( vecLd1, 2 ); + + // put constant to add in vector + vecConst3 = loadSplatUnalignedScalar( &const3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i] + constant[3]; + } + + const float *addr = src[i].ToFloatPtr(); + vector unsigned char permVec = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, addr ); + + for ( ; i+7 < count; i += 8 ) { + float *vecPtr = (float*)( addr + (i*3) ); + vector float v0, v1, v2, v3, v4, v5; + + v0 = vecOld; //vec_ld( 0, vecPtr ); + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + v3 = vec_ld( 47, vecPtr ); + v4 = vec_ld( 63, vecPtr ); + v5 = vec_ld( 79, vecPtr ); + vecOld = vec_ld( 95, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, v3, permVec ); + + vecLd4 = vec_perm( v3, v4, permVec ); + vecLd5 = vec_perm( v4, v5, permVec ); + vecLd6 = vec_perm( v5, vecOld, permVec ); + + // permute into X Y Z vectors + vecX = vec_perm( vecLd1, vecLd2, permX1 ); + vecY = vec_perm( vecLd1, vecLd2, permY1 ); + vecZ = vec_perm( vecLd1, vecLd2, permZ1 ); + vecX = vec_perm( vecX, vecLd3, permX2 ); + vecY = vec_perm( vecY, vecLd3, permY2 ); + vecZ = vec_perm( vecZ, vecLd3, permZ2 ); + + vecX2 = vec_perm( vecLd4, vecLd5, permX1 ); + vecY2 = vec_perm( vecLd4, vecLd5, permY1 ); + vecZ2 = vec_perm( vecLd4, vecLd5, permZ1 ); + vecX2 = vec_perm( vecX2, vecLd6, permX2 ); + vecY2 = vec_perm( vecY2, vecLd6, permY2 ); + vecZ2 = vec_perm( vecZ2, vecLd6, permZ2 ); + + // calculate dot product + vecX = vec_madd( vecX, vecConstX, zeroVector ); + vecY = vec_madd( vecY, vecConstY, vecX ); + vecZ = vec_madd( vecZ, vecConstZ, vecY ); + + vecX2 = vec_madd( vecX2, vecConstX, zeroVector ); + vecY2 = vec_madd( vecY2, vecConstY, vecX2 ); + vecZ2 = vec_madd( vecZ2, vecConstZ, vecY2 ); + + // add in constant[3] + vecZ = vec_add( vecZ, vecConst3 ); + vecZ2 = vec_add( vecZ2, vecConst3 ); + + // store out results + ALIGNED_STORE2( &dst[i], vecZ, vecZ2 ); + } + + //cleanup + for ( ; i < count; i++ ) { + dst[i] = constNormal * src[i] + const3; + } +} + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idPlane *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].Normal() + constant[3] * src[(X)][3]; + + // check plane size + assert( sizeof(idPlane) == PLANE_OFFSET * sizeof(float) ); + + float constVal[4]; + float srcVal[4]; + + int i; + const float *constPtr = constant.ToFloatPtr(); + + register vector float vecX, vecY, vecZ, vecI3; + register vector float vecX2, vecY2, vecZ2, vecI32; + + vector float vecPlaneLd1, vecPlaneLd2, vecPlaneLd3, vecPlaneLd4; + vector float vecPlaneLd5, vecPlaneLd6, vecPlaneLd7, vecPlaneLd8; + register vector float zeroVector = (vector float)(0.0); + register vector float vecConstX, vecConstY, vecConstZ, vecConstI3; + + constVal[0] = *(constPtr); + constVal[1] = *(constPtr+1); + constVal[2] = *(constPtr+2); + constVal[3] = *(constPtr+3); + + // populate const vector + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + vector float v0 = vec_ld( 0, constant.ToFloatPtr() ); + vector float v1 = vec_ld( 15, constant.ToFloatPtr() ); + vector float vecConst = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( vecConst, 0 ); + vecConstY = vec_splat( vecConst, 1 ); + vecConstZ = vec_splat( vecConst, 2 ); + vecConstI3 = vec_splat( vecConst, 3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; + } + + const float *srcPtr = src[i].ToFloatPtr(); + vector unsigned char permVec = vec_add( vec_lvsl( -1, srcPtr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, srcPtr ); + + for ( ; i+7 < count; i += 8 ) { + float *planePtr = (float*)( srcPtr + (i*PLANE_OFFSET) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + v0 = vecOld; // vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + v2 = vec_ld( 31, planePtr ); + v3 = vec_ld( 47, planePtr ); + v4 = vec_ld( 63, planePtr ); + v5 = vec_ld( 79, planePtr ); + v6 = vec_ld( 95, planePtr ); + v7 = vec_ld( 111, planePtr ); + vecOld = vec_ld( 127, planePtr ); + + vecPlaneLd1 = vec_perm( v0, v1, permVec ); + vecPlaneLd2 = vec_perm( v1, v2, permVec ); + vecPlaneLd3 = vec_perm( v2, v3, permVec ); + vecPlaneLd4 = vec_perm( v3, v4, permVec ); + + vecPlaneLd5 = vec_perm( v4, v5, permVec ); + vecPlaneLd6 = vec_perm( v5, v6, permVec ); + vecPlaneLd7 = vec_perm( v6, v7, permVec ); + vecPlaneLd8 = vec_perm( v7, vecOld, permVec ); + + // permute into X Y Z vectors, since this is square its basically + // a matrix transpose + v0 = vec_mergeh( vecPlaneLd1, vecPlaneLd3 ); + v1 = vec_mergeh( vecPlaneLd2, vecPlaneLd4 ); + v2 = vec_mergel( vecPlaneLd1, vecPlaneLd3 ); + v3 = vec_mergel( vecPlaneLd2, vecPlaneLd4 ); + + vecX = vec_mergeh( v0, v1 ); + vecY = vec_mergel( v0, v1 ); + vecZ = vec_mergeh( v2, v3 ); + vecI3 = vec_mergel( v2, v3 ); + + v4 = vec_mergeh( vecPlaneLd5, vecPlaneLd7 ); + v5 = vec_mergeh( vecPlaneLd6, vecPlaneLd8 ); + v6 = vec_mergel( vecPlaneLd5, vecPlaneLd7 ); + v7 = vec_mergel( vecPlaneLd6, vecPlaneLd8 ); + + vecX2 = vec_mergeh( v4, v5 ); + vecY2 = vec_mergel( v4, v5 ); + vecZ2 = vec_mergeh( v6, v7 ); + vecI32 = vec_mergel( v6, v7 ); + + // do calculation + v4 = vec_madd( vecConstX, vecX, zeroVector ); + v5 = vec_madd( vecConstY, vecY, v4 ); + v6 = vec_madd( vecConstZ, vecZ, v5 ); + v7 = vec_madd( vecConstI3, vecI3, v6 ); + + v0 = vec_madd( vecConstX, vecX2, zeroVector ); + v1 = vec_madd( vecConstY, vecY2, v0 ); + v2 = vec_madd( vecConstZ, vecZ2, v1 ); + v3 = vec_madd( vecConstI3, vecI32, v2 ); + + //store result + ALIGNED_STORE2( &dst[i], v7, v3 ); + } + + // cleanup + for ( ; i < count; i++ ) { + //dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; + srcVal[0] = *(srcPtr + (i*PLANE_OFFSET) + 0 ); + srcVal[1] = *(srcPtr + (i*PLANE_OFFSET) + 1 ); + srcVal[2] = *(srcPtr + (i*PLANE_OFFSET) + 2 ); + srcVal[3] = *(srcPtr + (i*PLANE_OFFSET) + 3 ); + dst[i] = srcVal[0] * constVal[0] + srcVal[1] * constVal[1] + srcVal[2] * constVal[2] + constVal[3] * srcVal[3]; + } +} + + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].xyz + constant[3]; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + int i; + const float *constPtr = constant.ToFloatPtr(); + const float *srcPtr = src[0].xyz.ToFloatPtr(); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecConstX, vecConstY, vecConstZ, vecConstI3; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float vecDest1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + float constVal[4]; + float srcVal[3]; + + constVal[0] = *(constPtr+0); + constVal[1] = *(constPtr+1); + constVal[2] = *(constPtr+2); + constVal[3] = *(constPtr+3); + + // populate const vec + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 15, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + vecConstI3 = vec_splat( v0, 3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i].xyz + constant[3]; + } + + // every fourth one will have the same alignment, so can store these. Make sure we + // have enough so we don't run off the end of the array + if ( i+3 < count ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) src[i].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) src[i+1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) src[i+2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) src[i+3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + for ( ; i+3 < count; i+=4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 11, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 11, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 11, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 11, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + vecDest1 = vec_add( vecSrcZ1, vecConstI3 ); + + // store results + vec_st( vecDest1, 0, &dst[i] ); + } + + // cleanup + for ( ; i < count; i++ ) { + srcVal[0] = *(srcPtr + (i*DRAWVERT_OFFSET) + 0 ); + srcVal[1] = *(srcPtr + (i*DRAWVERT_OFFSET) + 1 ); + srcVal[2] = *(srcPtr + (i*DRAWVERT_OFFSET) + 2 ); + // dst[i] = constant.Normal() * src[i].xyz + constant[3]; + + dst[i] = constVal[0] * srcVal[0] + constVal[1] * srcVal[1] + constVal[2] * srcVal[2]; + dst[i] += constVal[3]; + } +} +#else +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idPlane &constant, const idDrawVert *src, const int count ) { +//#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].xyz + constant[3]; + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof( float ) ); + + int i; + const float *constPtr = constant.ToFloatPtr(); + const float *srcPtr = src[0].xyz.ToFloatPtr(); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecConstX, vecConstY, vecConstZ, vecConstI3; + register vector float vecSrcX1, vecSrcY1, vecSrcZ1; + register vector float vecDest1; + register vector float zeroVector = (vector float)(0.0); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + float constVal[4]; + float srcVal[3]; + + constVal[0] = *(constPtr+0); + constVal[1] = *(constPtr+1); + constVal[2] = *(constPtr+2); + constVal[3] = *(constPtr+3); + + // populate const vec + vector unsigned char constPerm = vec_lvsl( 0, constant.ToFloatPtr() ); + v0 = vec_ld( 0, constant.ToFloatPtr() ); + v1 = vec_ld( 15, constant.ToFloatPtr() ); + v0 = vec_perm( v0, v1, constPerm ); + + vecConstX = vec_splat( v0, 0 ); + vecConstY = vec_splat( v0, 1 ); + vecConstZ = vec_splat( v0, 2 ); + vecConstI3 = vec_splat( v0, 3 ); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = constant.Normal() * src[i].xyz + constant[3]; + } + + for ( ; i+3 < count; i+=4 ) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + // transpose into X Y Z vectors + v1 = vec_mergeh( v0, v4 ); + v3 = vec_mergeh( v2, v6 ); + v5 = vec_mergel( v0, v4 ); + v7 = vec_mergel( v2, v6 ); + + vecSrcX1 = vec_mergeh( v1, v3 ); + vecSrcY1 = vec_mergel( v1, v3 ); + vecSrcZ1 = vec_mergeh( v5, v7 ); + + // now calculate dot product + vecSrcX1 = vec_madd( vecSrcX1, vecConstX, zeroVector ); + vecSrcY1 = vec_madd( vecSrcY1, vecConstY, vecSrcX1 ); + vecSrcZ1 = vec_madd( vecSrcZ1, vecConstZ, vecSrcY1 ); + vecDest1 = vec_add( vecSrcZ1, vecConstI3 ); + + // store results + vec_st( vecDest1, 0, &dst[i] ); + } + + // cleanup + for ( ; i < count; i++ ) { + srcVal[0] = *(srcPtr + (i*DRAWVERT_OFFSET) + 0 ); + srcVal[1] = *(srcPtr + (i*DRAWVERT_OFFSET) + 1 ); + srcVal[2] = *(srcPtr + (i*DRAWVERT_OFFSET) + 2 ); + // dst[i] = constant.Normal() * src[i].xyz + constant[3]; + + dst[i] = constVal[0] * srcVal[0] + constVal[1] * srcVal[1] + constVal[2] * srcVal[2]; + dst[i] += constVal[3]; + } +} + +#endif /* DRAWVERT_PADDED */ + +/* +============ +idSIMD_AltiVec::Dot + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] * src1[(X)]; + + int i; + float src0Val[3]; + float src1Val[3]; + + register vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + vector float vecLd7, vecLd8, vecLd9, vecLd10, vecLd11, vecLd12; + register vector float vecX0, vecY0, vecZ0, vecX1, vecY1, vecZ1; + register vector float vecX02, vecY02, vecZ02, vecX12, vecY12, vecZ12; + register vector float zeroVector = (vector float)(0.0); + // permute vectors + register vector unsigned char permX1 = (vector unsigned char)(0,1,2,3,12,13,14,15,24,25,26,27,28,29,30,31); //last 4 bytes are junk + register vector unsigned char permX2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,20,21,22,23); + register vector unsigned char permY1 = (vector unsigned char)(4,5,6,7,16,17,18,19,28,29,30,31,0,1,2,3); //last 4 bytes are junk + register vector unsigned char permY2 = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,24,25,26,27); + register vector unsigned char permZ1 = (vector unsigned char)(8,9,10,11,20,21,22,23,0,1,2,3,4,5,6,7); //last 8 bytes are junk + register vector unsigned char permZ2 = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,28,29,30,31); + + // handle unaligned case at beginning + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src0[i] * src1[i]; + } + + const float *src0Ptr = src0[i].ToFloatPtr(); + const float *src1Ptr = src1[i].ToFloatPtr(); + vector unsigned char permVec1 = vec_add( vec_lvsl( -1, src0Ptr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, src1Ptr ), (vector unsigned char)(1) ); + vector float vecOld0 = vec_ld( 0, src0Ptr ); + vector float vecOld1 = vec_ld( 0, src1Ptr ); + + for ( i = 0; i+7 < count; i += 8 ) { + float *s0Ptr = (float*)( src0Ptr + (i*3) ); + float *s1Ptr = (float*)( src1Ptr + (i*3) ); + + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11; + v0 = vecOld0; + v1 = vec_ld( 15, s0Ptr ); + v2 = vec_ld( 31, s0Ptr ); + v3 = vec_ld( 47, s0Ptr ); + v4 = vec_ld( 63, s0Ptr ); + v5 = vec_ld( 79, s0Ptr ); + vecOld0 = vec_ld( 95, s0Ptr ); + + v6 = vecOld1; + v7 = vec_ld( 15, s1Ptr ); + v8 = vec_ld( 31, s1Ptr ); + v9 = vec_ld( 47, s1Ptr ); + v10 = vec_ld( 63, s1Ptr ); + v11 = vec_ld( 79, s1Ptr ); + vecOld1 = vec_ld( 95, s1Ptr ); + + vecLd1 = vec_perm( v0, v1, permVec1 ); + vecLd2 = vec_perm( v1, v2, permVec1 ); + vecLd3 = vec_perm( v2, v3, permVec1 ); + vecLd4 = vec_perm( v3, v4, permVec1 ); + vecLd5 = vec_perm( v4, v5, permVec1 ); + vecLd6 = vec_perm( v5, vecOld0, permVec1 ); + + vecLd7 = vec_perm( v6, v7, permVec2 ); + vecLd8 = vec_perm( v7, v8, permVec2 ); + vecLd9 = vec_perm( v8, v9, permVec2 ); + vecLd10 = vec_perm( v9, v10, permVec2 ); + vecLd11 = vec_perm( v10, v11, permVec2 ); + vecLd12 = vec_perm( v11, vecOld1, permVec2 ); + + // permute into X Y Z vectors + vecX0 = vec_perm( vecLd1, vecLd2, permX1 ); + vecY0 = vec_perm( vecLd1, vecLd2, permY1 ); + vecZ0 = vec_perm( vecLd1, vecLd2, permZ1 ); + vecX0 = vec_perm( vecX0, vecLd3, permX2 ); + vecY0 = vec_perm( vecY0, vecLd3, permY2 ); + vecZ0 = vec_perm( vecZ0, vecLd3, permZ2 ); + + vecX02 = vec_perm( vecLd4, vecLd5, permX1 ); + vecY02 = vec_perm( vecLd4, vecLd5, permY1 ); + vecZ02 = vec_perm( vecLd4, vecLd5, permZ1 ); + vecX02 = vec_perm( vecX02, vecLd6, permX2 ); + vecY02 = vec_perm( vecY02, vecLd6, permY2 ); + vecZ02 = vec_perm( vecZ02, vecLd6, permZ2 ); + + vecX1 = vec_perm( vecLd7, vecLd8, permX1 ); + vecY1 = vec_perm( vecLd7, vecLd8, permY1 ); + vecZ1 = vec_perm( vecLd7, vecLd8, permZ1 ); + vecX1 = vec_perm( vecX1, vecLd9, permX2 ); + vecY1 = vec_perm( vecY1, vecLd9, permY2 ); + vecZ1 = vec_perm( vecZ1, vecLd9, permZ2 ); + + vecX12 = vec_perm( vecLd10, vecLd11, permX1 ); + vecY12 = vec_perm( vecLd10, vecLd11, permY1 ); + vecZ12 = vec_perm( vecLd10, vecLd11, permZ1 ); + vecX12 = vec_perm( vecX12, vecLd12, permX2 ); + vecY12 = vec_perm( vecY12, vecLd12, permY2 ); + vecZ12 = vec_perm( vecZ12, vecLd12, permZ2 ); + + // do multiply + vecX0 = vec_madd( vecX0, vecX1, zeroVector ); + vecY0 = vec_madd( vecY0, vecY1, vecX0 ); + vecZ0 = vec_madd( vecZ0, vecZ1, vecY0 ); + vecX02 = vec_madd( vecX02, vecX12, zeroVector ); + vecY02 = vec_madd( vecY02, vecY12, vecX02 ); + vecZ02 = vec_madd( vecZ02, vecZ12, vecY02 ); + + // store out results + ALIGNED_STORE2( &dst[i], vecZ0, vecZ02 ); + } + + // cleanup + for ( ; i < count; i++ ) { + // dst[i] = src0[i] * src1[i]; + src0Val[0] = *( src0Ptr + (i*3) + 0 ); + src0Val[1] = *( src0Ptr + (i*3) + 1 ); + src0Val[2] = *( src0Ptr + (i*3) + 2 ); + + src1Val[0] = *( src1Ptr + (i*3) + 0 ); + src1Val[1] = *( src1Ptr + (i*3) + 1 ); + src1Val[2] = *( src1Ptr + (i*3) + 2 ); + + dst[i] = src0Val[0] * src1Val[0] + src0Val[1] * src1Val[1] + src0Val[2] * src1Val[2]; + } +} + +/* +============ +idSIMD_AltiVec::Dot + + dot = src1[0] * src2[0] + src1[1] * src2[1] + src1[2] * src2[2] + ... +============ +*/ +void VPCALL idSIMD_AltiVec::Dot( float &dot, const float *src1, const float *src2, const int count ) { + dot = 0.0f; + + register vector float v0, v1, v2, v3; + register vector float zeroVector; + register vector float runningTotal1, runningTotal2; + //src0 + register vector float v0_low, v0_hi, v2_low, v2_hi; + //src1 + register vector float v1_low, v1_hi, v3_low, v3_hi; + //permute vectors + register vector unsigned char permVec1, permVec2; + vector unsigned char oneCharVector = (vector unsigned char)(1); + + int i = 0; + + runningTotal1 = (vector float)(0.0); + runningTotal2 = (vector float)(0.0); + zeroVector = (vector float)(0.0); + + if ( count >= 8 ) { + //calculate permute and do loads + permVec1 = vec_add( vec_lvsl( -1, (int*) &src1[i] ), oneCharVector ); + permVec2 = vec_add( vec_lvsl( -1, (int*) &src2[i] ), oneCharVector ); + v2_hi = vec_ld( 0, &src1[i] ); + v3_hi = vec_ld( 0, &src2[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load sources + v0_low = v2_hi; + v0_hi = vec_ld( 15, &src1[i] ); + v2_low = v0_hi; + v2_hi = vec_ld( 31, &src1[i] ); + + v1_low = v3_hi; + v1_hi = vec_ld( 15, &src2[i] ); + v3_low = v1_hi; + v3_hi = vec_ld( 31, &src2[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec1 ); + v1 = vec_perm( v1_low, v1_hi, permVec2 ); + v2 = vec_perm( v2_low, v2_hi, permVec1 ); + v3 = vec_perm( v3_low, v3_hi, permVec2 ); + + //multiply together and keep running sum + runningTotal1 = vec_madd( v0, v1, runningTotal1 ); + runningTotal2 = vec_madd( v2, v3, runningTotal2 ); + } + + runningTotal1 = vec_add( runningTotal1, runningTotal2 ); + + // sum accross vector + v0 = vec_add( runningTotal1, vec_sld( runningTotal1, runningTotal1, 8 ) ); + v1 = vec_add( v0, vec_sld( v0, v0, 4 ) ); + runningTotal1 = vec_splat( v1, 0 ); + vec_ste( runningTotal1, 0, &dot ); + } + + //handle cleanup. when profiling the game, we found that most of the counts to this function were small, so it + // spends a lot of time in this scalar code. It's already really really fast (eg 1 TB tick) for scalar code for + // counts less than 50, so not much point in trying to get vector code in on the action + for ( ; i < count ; i++ ) { + dot += src1[i] * src2[i]; + } + +} +#endif /* ENABLE_DOT */ + +#ifdef ENABLE_COMPARES + +/* +============ +idSIMD_AltiVec::CmpGT + + dst[i] = src0[i] > constant; +============ +*/ + +void VPCALL idSIMD_AltiVec::CmpGT( byte *dst, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] > constant; + + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] > constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmpgt( v0, constVec ); + vr2 = vec_cmpgt( v1, constVec ); + vr3 = vec_cmpgt( v2, constVec ); + vr4 = vec_cmpgt( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] > constant; + } +} + + +/* +============ +idSIMD_AltiVec::CmpGT + + dst[i] |= ( src0[i] > constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] |= ( src0[(X)] > constant ) << bitNum; + + // Temp vector registers + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] > constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmpgt( vs0, constVec ); + vtbi1 = vec_cmpgt( vs1, constVec ); + vtbi2 = vec_cmpgt( vs2, constVec ); + vtbi3 = vec_cmpgt( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1 to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] > constant ) << bitNum; + } +} + +/* +============ +idSIMD_AltiVec::CmpGE + + dst[i] = src0[i] >= constant; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpGE( byte *dst, const float *src0, const float constant, const int count ) { + + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] >= constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmpge( v0, constVec ); + vr2 = vec_cmpge( v1, constVec ); + vr3 = vec_cmpge( v2, constVec ); + vr4 = vec_cmpge( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] >= constant; + } +} + +/* +============ +idSIMD_AltiVec::CmpGE + + dst[i] |= ( src0[i] >= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] >= constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmpge( vs0, constVec ); + vtbi1 = vec_cmpge( vs1, constVec ); + vtbi2 = vec_cmpge( vs2, constVec ); + vtbi3 = vec_cmpge( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1L to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] >= constant ) << bitNum; + } +} + + +/* +============ +idSIMD_AltiVec::CmpLT + + dst[i] = src0[i] < constant; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLT( byte *dst, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] < constant; + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] < constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmplt( v0, constVec ); + vr2 = vec_cmplt( v1, constVec ); + vr3 = vec_cmplt( v2, constVec ); + vr4 = vec_cmplt( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] < constant; + } +} + +/* +============ +idSIMD_AltiVec::CmpLT + + dst[i] |= ( src0[i] < constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] |= ( src0[(X)] < constant ) << bitNum; + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( i = 0 ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] < constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmplt( vs0, constVec ); + vtbi1 = vec_cmplt( vs1, constVec ); + vtbi2 = vec_cmplt( vs2, constVec ); + vtbi3 = vec_cmplt( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1L to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] < constant ) << bitNum; + } + +} +//#endif + +/* +============ +idSIMD_AltiVec::CmpLE + + dst[i] = src0[i] <= constant; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLE( byte *dst, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src0[(X)] <= constant; + register vector float v0, v1, v2, v3; + register vector bool int vr1, vr2, vr3, vr4; + register vector bool short vs1, vs2; + register vector float v0_low, v0_hi, v1_low, v1_hi, v2_low, v2_hi, v3_low, v3_hi; + register vector unsigned char vc1; + register vector bool char vbc1; + register vector float constVec; + register vector unsigned char oneVector = (vector unsigned char)(1); + register vector unsigned char permVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] = src0[i] <= constant; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + v3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + // load values + v0_low = v3_hi; + v0_hi = vec_ld( 15, &src0[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src0[i] ); + v2_low = v1_hi; + v2_hi = vec_ld( 47, &src0[i] ); + v3_low = v2_hi; + v3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_perm( v2_low, v2_hi, permVec ); + v3 = vec_perm( v3_low, v3_hi, permVec ); + + //do comparison + vr1 = vec_cmple( v0, constVec ); + vr2 = vec_cmple( v1, constVec ); + vr3 = vec_cmple( v2, constVec ); + vr4 = vec_cmple( v3, constVec ); + + // pack results into shorts + vs1 = vec_pack(vr1, vr2); + vs2 = vec_pack(vr3, vr4); + + // pack results into byte + vbc1 = vec_pack(vs1, vs2); + + //AND with 1 to get true=1 not true=255 + vc1 = vec_and( vbc1, oneVector ); + + //store results + vec_st( vc1, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src0[i] <= constant; + } +} + +/* +============ +idSIMD_AltiVec::CmpLE + + dst[i] |= ( src0[i] <= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_AltiVec::CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { +//#define OPER(X) dst[(X)] |= ( src0[(X)] <= constant ) << bitNum; + register vector bool int vtbi0, vtbi1, vtbi2, vtbi3; + register vector bool short vtbs0, vtbs1; + register vector bool char vtbc0; + register vector unsigned char vtuc0; + register vector unsigned char permVec, permVec2; + + // dest vectors + register vector unsigned char vd; + // bitNum vectors + register vector unsigned char bitNumVec; + // src0 vectors + register vector float vs0, vs1, vs2, vs3; + register vector float vs0_low, vs0_hi, vs1_low, vs1_hi, vs2_low, vs2_hi, vs3_low, vs3_hi; + // constant vector + register vector float constVec; + // all one's + register vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count );i++ ) { + dst[i] |= ( src0[i] <= constant ) << bitNum; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //bitNum is unaligned. + permVec2 = vec_lvsl( 0, &bitNum ); + vtuc0 = vec_ld( 0, &bitNum ); + bitNumVec = vec_perm( vtuc0, vtuc0, permVec2 ); + bitNumVec = vec_splat( bitNumVec, 0 ); + + //calculate permute and do loads + permVec = vec_add( vec_lvsl( -1, (int*) &src0[i] ), oneVector ); + vs3_hi = vec_ld( 0, &src0[i] ); + + //vectorize! + for ( ; i+15 < count; i += 16 ) { + //load sources (floats) + vs0_low = vs3_hi; + vs0_hi = vec_ld( 15, &src0[i] ); + vs1_low = vs0_hi; + vs1_hi = vec_ld( 31, &src0[i] ); + vs2_low = vs1_hi; + vs2_hi = vec_ld( 47, &src0[i] ); + vs3_low = vs2_hi; + vs3_hi = vec_ld( 63, &src0[i] ); + + //permute into the vectors we want + vs0 = vec_perm( vs0_low, vs0_hi, permVec ); + vs1 = vec_perm( vs1_low, vs1_hi, permVec ); + vs2 = vec_perm( vs2_low, vs2_hi, permVec ); + vs3 = vec_perm( vs3_low, vs3_hi, permVec ); + + //load dest (bytes) as unsigned char + vd = vec_ld( 0, &dst[i] ); + + // do comparison and get bool int result + vtbi0 = vec_cmple( vs0, constVec ); + vtbi1 = vec_cmple( vs1, constVec ); + vtbi2 = vec_cmple( vs2, constVec ); + vtbi3 = vec_cmple( vs3, constVec ); + + // pack results into shorts + vtbs0 = vec_pack(vtbi0, vtbi1); + vtbs1 = vec_pack(vtbi2, vtbi3); + + // pack results into byte + vtbc0 = vec_pack(vtbs0, vtbs1); + + //and with 1L to get true=1 instead of true=255 + vtuc0 = vec_and(vtbc0, oneVector); + vtuc0 = vec_sl(vtuc0, bitNumVec ); + + //or with original + vd = vec_or( vd, vtuc0 ); + + vec_st( vd, 0, &dst[i] ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] |= ( src0[i] <= constant ) << bitNum; + } +} +#endif /* ENABLE_COMPARES */ + +#ifdef ENABLE_MINMAX + +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( float &min, float &max, const float *src, const int count ) { + min = idMath::INFINITY; max = -idMath::INFINITY; +//#define OPER(X) if ( src[(X)] < min ) {min = src[(X)];} if ( src[(X)] > max ) {max = src[(X)];} + + register vector float v0, v1, v2, v3; + register vector float maxVec, minVec, tempMin, tempMax; + register vector unsigned char permVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + vector unsigned char oneCharVector = (vector unsigned char)(1); + int i = 0; + + if ( count >= 4 ) { + + //calculate permute and do first load to + //get a starting point for min and max + permVec = vec_add( vec_lvsl( -1, (int*) &src[0] ), oneCharVector ); + v1_hi = vec_ld( 0, &src[0] ); + + maxVec = loadSplatUnalignedScalar( &max ); + minVec = loadSplatUnalignedScalar( &min ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load sources + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + // minimum + v2 = vec_min( v0, v1 ); + minVec = vec_min( minVec, v2 ); + // maximum + v3 = vec_max( v0, v1 ); + maxVec = vec_max( maxVec, v3 ); + } + + //minVec and maxVec hold the min/max elements from the array, but now + //we need to figure out which particular element it is + + tempMin = minVec; + tempMax = maxVec; + + // rotate vector around and compare to itself to find the real min/max + tempMin = vec_min( tempMin, vec_sld( tempMin, tempMin, 8 ) ); + tempMax = vec_max( tempMax, vec_sld( tempMax, tempMax, 8 ) ); + tempMin = vec_min( tempMin, vec_sld( tempMin, tempMin, 4 ) ); + tempMax = vec_max( tempMax, vec_sld( tempMax, tempMax, 4 ) ); + minVec = vec_splat( tempMin, 0 ); + maxVec = vec_splat( tempMax, 0 ); + vec_ste( minVec, 0, &min ); + vec_ste( maxVec, 0, &max ); + } + + //cleanup + for ( ; i < count; i++ ) { + if ( src[i] < min ) { + min = src[i]; + } + if ( src[i] > max ) { + max = src[i]; + } + } +} + +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) { + min[0] = min[1] = idMath::INFINITY; max[0] = max[1] = -idMath::INFINITY; +//#define OPER(X) const idVec2 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } + + idVec2 v; + int i = 0; + int j; + + const float *srcPtr = src[0].ToFloatPtr(); + register vector float vecLd1, vecLd2, vecLd3, vecLd4; + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3; + + if ( count > 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, srcPtr ), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, srcPtr ); + + for ( i = 0, j = 0; i+7 < count; i += 8, j += 4) { + // load data + float *vecPtr = (float*)( srcPtr + (j*4) ); + vector float v0, v1, v2, v3; + + v0 = vecOld; + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + v3 = vec_ld( 47, vecPtr ); + vecOld = vec_ld( 63, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, v3, permVec ); + vecLd4 = vec_perm( v3, vecOld, permVec ); + + // each of these vectors contains 2 elements + // looks like | X Y X Y | X Y X Y + v0 = vec_min( vecLd1, vecLd2 ); + v1 = vec_min( vecLd3, vecLd4 ); + v0 = vec_min( v0, v1 ); + + v2 = vec_max( vecLd1, vecLd2 ); + v3 = vec_max( vecLd3, vecLd4 ); + v2 = vec_max( v2, v3 ); + + // since its always X Y X Y we don't have to re-merge each time. we can wait + // until the end + vecMin = vec_min( v0, vecMin ); + vecMax = vec_max( v2, vecMax ); + } + + vecMin = vec_min( vecMin, vec_sld( vecMin, vecMin, 8 ) ); + vecMax = vec_max( vecMax, vec_sld( vecMax, vecMax, 8 ) ); + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMax, 0 ); + v3 = vec_splat( vecMax, 1 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &max[0] ); + vec_ste( v3, 0, &max[1] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[i]; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + } +} + +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +//#define OPER(X) const idVec3 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + + int i = 0; + const float *srcPtr = src[0].ToFloatPtr(); + idVec3 v; + + register vector float vecLd1, vecLd2, vecLd3; + register vector float vecMin, vecMax; + register vector float vecSrc1, vecSrc2, vecSrc3, vecSrc4; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, srcPtr), (vector unsigned char)(1) ); + vector float vecOld = vec_ld( 0, srcPtr ); + + // 4 elements at a time + for ( ; i+3 < count; i += 4 ) { + float *vecPtr = (float*)( srcPtr + (i*3) ); + vector float v0, v1, v2; + + v0 = vecOld; + v1 = vec_ld( 15, vecPtr ); + v2 = vec_ld( 31, vecPtr ); + vecOld = vec_ld( 47, vecPtr ); + + vecLd1 = vec_perm( v0, v1, permVec ); + vecLd2 = vec_perm( v1, v2, permVec ); + vecLd3 = vec_perm( v2, vecOld, permVec ); + + // put each idVec3 into its own vector as X Y Z (crap) + vecSrc1 = vecLd1; + vecSrc2 = vec_sld( vecLd1, vecLd2, 12 ); + vecSrc3 = vec_sld( vecLd2, vecLd3, 8 ); + vecSrc4 = vec_sld( vecLd3, vecLd3, 4 ); + + // do min and max + vecMin1 = vec_min( vecSrc1, vecSrc2 ); + vecMin2 = vec_min( vecSrc3, vecSrc4 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( vecSrc1, vecSrc2 ); + vecMax2 = vec_max( vecSrc3, vecSrc4 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax1, vecMax ); + } + + // store results + vector float v0, v1, v2, v3, v4, v5; + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i ++ ) { + v = src[i]; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + } +} + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) { + + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + idVec3 v; + int i = 0; + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char vertPerm1 = vec_add( vec_lvsl( -1, (float*) src[i].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vector unsigned char vertPerm2 = vec_add( vec_lvsl( -1, (float*) src[i+1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vector unsigned char vertPerm3 = vec_add( vec_lvsl( -1, (float*) src[i+2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vector unsigned char vertPerm4 = vec_add( vec_lvsl( -1, (float*) src[i+3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 11, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 11, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 11, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 11, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[i].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} +#else +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) { + + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + idVec3 v; + int i = 0; + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[i].xyz.ToFloatPtr(); + const float *vertPtr2 = src[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = src[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = src[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[i].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} + +#endif /* DRAWVERT_PADDED */ + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + + idVec3 v; + int i = 0; + + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char vertPerm1; + vector unsigned char vertPerm2; + vector unsigned char vertPerm3; + vector unsigned char vertPerm4; + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[indexes[i]].xyz.ToFloatPtr(); + const float *vertPtr2 = src[indexes[i+1]].xyz.ToFloatPtr(); + const float *vertPtr3 = src[indexes[i+2]].xyz.ToFloatPtr(); + const float *vertPtr4 = src[indexes[i+3]].xyz.ToFloatPtr(); + + vertPerm1 = vec_add( vec_lvsl( -1, vertPtr ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, vertPtr2 ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, vertPtr3 ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, vertPtr4 ), (vector unsigned char)(1) ); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v2 = vec_perm( v2, v3, vertPerm2 ); + v4 = vec_perm( v4, v5, vertPerm3 ); + v6 = vec_perm( v6, v7, vertPerm4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[indexes[i]].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} +#else +/* +============ +idSIMD_AltiVec::MinMax +============ +*/ +void VPCALL idSIMD_AltiVec::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) { + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; + + idVec3 v; + int i = 0; + + register vector float vecMin, vecMax; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float vecMin1, vecMin2, vecMax1, vecMax2; + + if ( count >= 4 ) { + + vecMin = (vector float)(FLT_MAX); + vecMax = (vector float)(FLT_MIN); + + vector unsigned char vertPerm1; + vector unsigned char vertPerm2; + vector unsigned char vertPerm3; + vector unsigned char vertPerm4; + + for ( ; i+3 < count; i += 4) { + const float *vertPtr = src[indexes[i]].xyz.ToFloatPtr(); + const float *vertPtr2 = src[indexes[i+1]].xyz.ToFloatPtr(); + const float *vertPtr3 = src[indexes[i+2]].xyz.ToFloatPtr(); + const float *vertPtr4 = src[indexes[i+3]].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + vecMin1 = vec_min( v0, v2 ); + vecMin2 = vec_min( v4, v6 ); + vecMin1 = vec_min( vecMin1, vecMin2 ); + vecMin = vec_min( vecMin, vecMin1 ); + + vecMax1 = vec_max( v0, v2 ); + vecMax2 = vec_max( v4, v6 ); + vecMax1 = vec_max( vecMax1, vecMax2 ); + vecMax = vec_max( vecMax, vecMax1 ); + } + + // now we have min/max vectors in X Y Z form, store out + v0 = vec_splat( vecMin, 0 ); + v1 = vec_splat( vecMin, 1 ); + v2 = vec_splat( vecMin, 2 ); + v3 = vec_splat( vecMax, 0 ); + v4 = vec_splat( vecMax, 1 ); + v5 = vec_splat( vecMax, 2 ); + + vec_ste( v0, 0, &min[0] ); + vec_ste( v1, 0, &min[1] ); + vec_ste( v2, 0, &min[2] ); + vec_ste( v3, 0, &max[0] ); + vec_ste( v4, 0, &max[1] ); + vec_ste( v5, 0, &max[2] ); + } + + // cleanup + for ( ; i < count; i++ ) { + v = src[indexes[i]].xyz; + + if ( v[0] < min[0] ) { + min[0] = v[0]; + } + if ( v[0] > max[0] ) { + max[0] = v[0]; + } + + if ( v[1] < min[1] ) { + min[1] = v[1]; + } + if ( v[1] > max[1] ) { + max[1] = v[1]; + } + + if ( v[2] > max[2] ) { + max[2] = v[2]; + } + + if ( v[2] < min[2] ) { + min[2] = v[2]; + } + } +} + + +#endif /* DRAWVERT_PADDED */ + +#endif /* ENABLE_MINMAX */ + +#ifdef ENABLE_CLAMP + +/* +============ +idSIMD_AltiVec::Clamp +============ +*/ +void VPCALL idSIMD_AltiVec::Clamp( float *dst, const float *src, const float min, const float max, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)] > max ? max : src[(X)]; + register vector float v0, v1, v2, v3, v4, v5; + register vector unsigned char permVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + vector unsigned char oneVector = (vector unsigned char)(1); + register vector float minVec, maxVec; + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src[i] < min ? min : src[i] > max ? max : src[i]; + } + + //splat min/max into a vector + minVec = loadSplatUnalignedScalar( &min ); + maxVec = loadSplatUnalignedScalar( &max ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneVector ); + v1_hi = vec_ld( 0, &src[i] ); + + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + //apply minimum + v2 = vec_max( v0, minVec ); + v3 = vec_max( v1, minVec ); + + //apply maximum + v4 = vec_min( v2, maxVec ); + v5 = vec_min( v3, maxVec ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src[i] < min ? min : src[i] > max ? max : src[i]; + } +} + +/* +============ +idSIMD_AltiVec::ClampMin +============ +*/ +void VPCALL idSIMD_AltiVec::ClampMin( float *dst, const float *src, const float min, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)]; + register vector float v0, v1, v2, v3; + register vector unsigned char permVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + register vector float constVec; + vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src[i] < min ? min : src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &min ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + + v2 = vec_max( v0, constVec ); + v3 = vec_max( v1, constVec ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src[i] < min ? min : src[i]; + } + } + +/* +============ +idSIMD_AltiVec::ClampMax +============ +*/ +void VPCALL idSIMD_AltiVec::ClampMax( float *dst, const float *src, const float max, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] > max ? max : src[(X)]; + register vector float v0, v1, v2, v3; + register vector unsigned char permVec; + register vector float constVec; + register vector float v0_low, v0_hi, v1_low, v1_hi; + vector unsigned char oneVector = (vector unsigned char)(1); + int i = 0; + + //handle unaligned at start + for ( ; NOT_16BYTE_ALIGNED( dst[i] ) && ( i < count ); i++ ) { + dst[i] = src[i] < max ? max : src[i]; + } + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &max ); + + //calculate permute and do first load + permVec = vec_add( vec_lvsl( -1, (int*) &src[i] ), oneVector ); + v1_hi = vec_ld( 0, &src[i] ); + + //vectorize! + for ( ; i+7 < count; i += 8 ) { + //load source + v0_low = v1_hi; + v0_hi = vec_ld( 15, &src[i] ); + v1_low = v0_hi; + v1_hi = vec_ld( 31, &src[i] ); + + v0 = vec_perm( v0_low, v0_hi, permVec ); + v1 = vec_perm( v1_low, v1_hi, permVec ); + v2 = vec_min( v0, constVec ); + v3 = vec_min( v1, constVec ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + //handle cleanup + for ( ; i < count ; i++ ) { + dst[i] = src[i] < max ? max : src[i]; + } +} + +#endif /* ENABLE_CLAMP */ + +#ifdef ENABLE_16ROUTINES + +/* +============ +idSIMD_AltiVec::Zero16 +============ +*/ +void VPCALL idSIMD_AltiVec::Zero16( float *dst, const int count ) { + memset( dst, 0, count * sizeof( float ) ); +} + +/* +============ +idSIMD_AltiVec::Negate16 + + Assumptions: + dst is aligned +============ +*/ +void VPCALL idSIMD_AltiVec::Negate16( float *dst, const int count ) { +//#define OPER(X) ptr[(X)] ^= ( 1 << 31 ) // IEEE 32 bits float sign bit + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + int i = 0; + vector float v0, v1, v2, v3; + + //know its 16-byte aligned + for ( ; i + 7 < count2; i += 8 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_ld( 16, &dst[i] ); + + v2 = vec_sub( (vector float)(0), v0 ); + v3 = vec_sub( (vector float)(0), v1 ); + + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_sub( (vector float)(0), v0 ); + vec_st( v1, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::Copy16 +============ +*/ +void VPCALL idSIMD_AltiVec::Copy16( float *dst, const float *src, const int count ) { +//#define OPER(X) dst[(X)] = src[(X)] + memcpy( dst, src, sizeof(float) * count ); +} + +/* +============ +idSIMD_AltiVec::Add16 + + Assumptions: + Assumes dst, src1, src2 all start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::Add16( float *dst, const float *src1, const float *src2, const int count ) { +//#define OPER(X) dst[(X)] = src1[(X)] + src2[(X)] + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src1 is aligned + assert( IS_16BYTE_ALIGNED( src1[0] ) ); + // src2 is aligned + assert( IS_16BYTE_ALIGNED( src2[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3, v4, v5; + int i = 0; + + //know all data is 16-byte aligned, so vectorize! + for ( ; i+7 < count2; i += 8 ) { + //load sources + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 16, &src1[i] ); + v2 = vec_ld( 0, &src2[i] ); + v3 = vec_ld( 16, &src2[i] ); + v4 = vec_add( v0, v2 ); + v5 = vec_add( v1, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 0, &src2[i] ); + v2 = vec_add( v0, v1 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::Sub16 + + Assumptions: + Assumes that dst, src1, and src2 all start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::Sub16( float *dst, const float *src1, const float *src2, const int count ) { +//#define OPER(X) dst[(X)] = src1[(X)] - src2[(X)] + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src1 is aligned + assert( IS_16BYTE_ALIGNED( src1[0] ) ); + // src2 is aligned + assert( IS_16BYTE_ALIGNED( src2[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3, v4, v5; + int i = 0; + + //know data is aligned, so vectorize! + for ( ; i+7 < count2; i += 8 ) { + //load sources + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 16, &src1[i] ); + v2 = vec_ld( 0, &src2[i] ); + v3 = vec_ld( 16, &src2[i] ); + v4 = vec_sub( v0, v2 ); + v5 = vec_sub( v1, v3 ); + + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 0, &src2[i] ); + v2 = vec_sub( v0, v1 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::Mul16 + + Assumptions: + Assumes that dst and src1 start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::Mul16( float *dst, const float *src1, const float constant, const int count ) { +//#define OPER(X) dst[(X)] = src1[(X)] * constant + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src1 is aligned + assert( IS_16BYTE_ALIGNED( src1[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3; + register vector float constVec; + register vector float zeroVector = (vector float)(0.0); + int i = 0; + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //know data is aligned, so vectorize! + for ( ; i+7 < count2; i += 8 ) { + //load source + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_ld( 16, &src1[i] ); + v2 = vec_madd( constVec, v0, zeroVector ); + v3 = vec_madd( constVec, v1, zeroVector ); + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src1[i] ); + v1 = vec_madd( constVec, v0, zeroVector ); + vec_st( v1, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::AddAssign16 + + Assumptions: + Assumes that dst and src start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::AddAssign16( float *dst, const float *src, const int count ) { +//#define OPER(X) dst[(X)] += src[(X)] + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src is aligned + assert( IS_16BYTE_ALIGNED( src[0] ) ); + + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3, v4, v5; + int i = 0; + + //vectorize! + for ( ; i+7 < count2; i += 8 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 16, &src[i] ); + v2 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + v4 = vec_add( v0, v2 ); + v5 = vec_add( v1, v3 ); + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 0, &dst[i] ); + v2 = vec_add( v0, v1 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::SubAssign16 + + Assumptions: + Assumes that dst and src start at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::SubAssign16( float *dst, const float *src, const int count ) { +//#define OPER(X) dst[(X)] -= src[(X)] + register vector float v0, v1, v2, v3, v4, v5; + int i=0; + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // src is aligned + assert( IS_16BYTE_ALIGNED( src[0] ) ); + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + //vectorize! + for ( ; i+7 < count2; i += 8 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 16, &src[i] ); + v2 = vec_ld( 0, &dst[i] ); + v3 = vec_ld( 16, &dst[i] ); + v4 = vec_sub( v2, v0 ); + v5 = vec_sub( v3, v1 ); + ALIGNED_STORE2( &dst[i], v4, v5 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &src[i] ); + v1 = vec_ld( 0, &dst[i] ); + v2 = vec_sub( v1, v0 ); + vec_st( v2, 0, &dst[i] ); + } +} + +/* +============ +idSIMD_AltiVec::MulAssign16 + + Assumptions: + Assumes that dst starts at aligned address and count is multiple of 4 +============ +*/ +void VPCALL idSIMD_AltiVec::MulAssign16( float *dst, const float constant, const int count ) { +//#define OPER(X) dst[(X)] *= constant + + // dst is aligned + assert( IS_16BYTE_ALIGNED( dst[0] ) ); + // round count up to next 4 if needbe + int count2 = ( count + 3 ) & ~3; + + register vector float v0, v1, v2, v3; + register vector float constVec; + int i = 0; + register vector float zeroVector = (vector float)(0.0); + + //splat constant into a vector + constVec = loadSplatUnalignedScalar( &constant ); + + //vectorize! + for ( ; i+7 < count2; i += 8 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_ld( 16, &dst[i] ); + v2 = vec_madd( v0, constVec, zeroVector ); + v3 = vec_madd( v1, constVec, zeroVector ); + ALIGNED_STORE2( &dst[i], v2, v3 ); + } + + for ( ; i < count2; i += 4 ) { + v0 = vec_ld( 0, &dst[i] ); + v1 = vec_madd( v0, constVec, zeroVector ); + vec_st( v1, 0, &dst[i] ); + } +} + +#endif /* ENABLE_16ROUTINES */ + +#ifdef ENABLE_LOWER_TRIANGULAR + +/* +============ +idSIMD_AltiVec::MatX_LowerTriangularSolve + + solves x in L * x = b for the first n rows of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ + +void VPCALL idSIMD_AltiVec::MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip ) { + + int i, j; + const float *lptr; + const float *lptr2; + const float *lptr3; + const float *lptr4; + float sum; + float sum2; + float sum3; + float sum4; + float tempSum; + float tempSum2; + float tempSum3; + float tempSum4; + vector float vecSum1 = (vector float)(0.0); + vector float vecSum2 = (vector float)(0.0); + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9; + vector float zeroVector = (vector float)(0.0); + vector float vecSum3, vecSum4, vecSum5, vecSum6, vecSum7, vecSum8; + + vector unsigned char vecPermX = vec_add( vec_lvsl( -1, &x[0] ), (vector unsigned char)(1) ); + + // unrolled this loop a bit + for ( i = skip; i+3 < n; i+=4 ) { + sum = b[i]; + sum2 = b[i+1]; + sum3 = b[i+2]; + sum4 = b[i+3]; + + vecSum1 = zeroVector; + vecSum2 = zeroVector; + vecSum3 = vecSum4 = vecSum5 = vecSum6 = vecSum7 = vecSum8 = zeroVector; + lptr = L[i]; + lptr2 = L[i+1]; + lptr3 = L[i+2]; + lptr4 = L[i+3]; + + vector unsigned char vecPermLptr1 = vec_add( vec_lvsl( -1, lptr ), (vector unsigned char)(1) ); + vector unsigned char vecPermLptr2 = vec_add( vec_lvsl( -1, lptr2 ), (vector unsigned char)(1) ); + vector unsigned char vecPermLptr3 = vec_add( vec_lvsl( -1, lptr3 ), (vector unsigned char)(1) ); + vector unsigned char vecPermLptr4 = vec_add( vec_lvsl( -1, lptr4 ), (vector unsigned char)(1) ); + + for ( j = 0 ; j+7 < i; j+=8 ) { + + v0 = vec_ld( 0, &x[j] ); + v1 = vec_ld( 15, &x[j] ); + vector float vecExtraX = vec_ld( 31, &x[j] ); + v0 = vec_perm( v0, v1, vecPermX ); + v1 = vec_perm( v1, vecExtraX, vecPermX ); + + v2 = vec_ld( 0, lptr + j ); + v3 = vec_ld( 15, lptr + j ); + vector float vecExtra1 = vec_ld( 31, lptr + j ); + v2 = vec_perm( v2, v3, vecPermLptr1 ); + v3 = vec_perm( v3, vecExtra1, vecPermLptr1 ); + + v4 = vec_ld( 0, lptr2 + j ); + v5 = vec_ld( 15, lptr2 + j ); + vector float vecExtra2 = vec_ld( 31, lptr2 + j ); + v4 = vec_perm( v4, v5, vecPermLptr2 ); + v5 = vec_perm( v5, vecExtra2, vecPermLptr2 ); + + v6 = vec_ld( 0, lptr3 + j ); + v7 = vec_ld( 15, lptr3 + j ); + vector float vecExtra3 = vec_ld( 31, lptr3 + j ); + v6 = vec_perm( v6, v7, vecPermLptr3 ); + v7 = vec_perm( v7, vecExtra3, vecPermLptr3 ); + + v8 = vec_ld( 0, lptr4 + j ); + v9 = vec_ld( 15, lptr4 + j ); + vector float vecExtra4 = vec_ld( 31, lptr4 + j ); + v8 = vec_perm( v8, v9, vecPermLptr4 ); + v9 = vec_perm( v9, vecExtra4, vecPermLptr4 ); + + vecSum1 = vec_madd( v2, v0, vecSum1 ); + vecSum2 = vec_madd( v3, v1, vecSum2 ); + + vecSum3 = vec_madd( v4, v0, vecSum3 ); + vecSum4 = vec_madd( v5, v1, vecSum4 ); + + vecSum5 = vec_madd( v6, v0, vecSum5 ); + vecSum6 = vec_madd( v7, v1, vecSum6 ); + + vecSum7 = vec_madd( v8, v0, vecSum7 ); + vecSum8 = vec_madd( v9, v1, vecSum8 ); + } + + // if we ran the unrolled code, we need to sum accross the vectors + // to find out how much to subtract from sum + if ( j > 0 ) { + vecSum1 = vec_add( vecSum1, vecSum2 ); + vecSum3 = vec_add( vecSum3, vecSum4 ); + vecSum5 = vec_add( vecSum5, vecSum6 ); + vecSum7 = vec_add( vecSum7, vecSum8 ); + //sum accross the vectors + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 8 ) ); + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 4 ) ); + + vecSum3 = vec_add( vecSum3, vec_sld( vecSum3, vecSum3, 8 ) ); + vecSum3 = vec_add( vecSum3, vec_sld( vecSum3, vecSum3, 4 ) ); + + vecSum5 = vec_add( vecSum5, vec_sld( vecSum5, vecSum5, 8 ) ); + vecSum5 = vec_add( vecSum5, vec_sld( vecSum5, vecSum5, 4 ) ); + + vecSum7 = vec_add( vecSum7, vec_sld( vecSum7, vecSum7, 8 ) ); + vecSum7 = vec_add( vecSum7, vec_sld( vecSum7, vecSum7, 4 ) ); + + //move the result to the FPU + vec_ste( vec_splat( vecSum1, 0 ), 0, &tempSum ); + vec_ste( vec_splat( vecSum3, 0 ), 0, &tempSum2 ); + vec_ste( vec_splat( vecSum5, 0 ), 0, &tempSum3 ); + vec_ste( vec_splat( vecSum7, 0 ), 0, &tempSum4 ); + + sum -= tempSum; + sum2 -= tempSum2; + sum3 -= tempSum3; + sum4 -= tempSum4; + } + + //cleanup + for ( ; j < i; j++ ) { + sum -= lptr[j] * x[j]; + sum2 -= lptr2[j] * x[j]; + sum3 -= lptr3[j] * x[j]; + sum4 -= lptr4[j] * x[j]; + } + + // store the 4 results at a time + sum2 -= ( lptr2[i] * sum ); + sum3 = sum3 - ( lptr3[i+1] * sum2 ) - ( lptr3[i] * sum ); + sum4 = sum4 - ( lptr4[i+2] * sum3 ) - ( lptr4[i+1] * sum2 ) - ( lptr4[i] * sum ); + + x[i] = sum; + x[i+1] = sum2; + x[i+2] = sum3; + x[i+3] = sum4; + } + + // cleanup + for ( ; i < n; i++ ) { + sum = b[i]; + vecSum1 = zeroVector; + vecSum2 = zeroVector; + lptr = L[i]; + vector unsigned char vecPermLptr = vec_add( vec_lvsl( -1, lptr ), (vector unsigned char)(1) ); + + for ( j = 0 ; j+7 < i; j+=8 ) { + + v0 = vec_ld( 0, &x[j] ); + v2 = vec_ld( 15, &x[j] ); + vector float vecExtraX = vec_ld( 31, &x[j] ); + v0 = vec_perm( v0, v2, vecPermX ); + v2 = vec_perm( v2, vecExtraX, vecPermX ); + + v1 = vec_ld( 0, lptr + j ); + v3 = vec_ld( 15, lptr + j ); + vector float vecExtra = vec_ld( 31, lptr + j ); + v1 = vec_perm( v1, v3, vecPermLptr ); + v3 = vec_perm( v3, vecExtra, vecPermLptr ); + + vecSum1 = vec_madd( v1, v0, vecSum1 ); + vecSum2 = vec_madd( v3, v2, vecSum2 ); + } + + // if we ran the unrolled code, we need to sum accross the vectors + // to find out how much to subtract from sum + if ( j > 0 ) { + //sum accross the vectors + vecSum1 = vec_add( vecSum1, vecSum2 ); + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 8 ) ); + vecSum1 = vec_add( vecSum1, vec_sld( vecSum1, vecSum1, 4 ) ); + + //move the result to the FPU + vec_ste( vec_splat( vecSum1, 0 ), 0, &tempSum ); + sum -= tempSum; + } + + //cleanup + for ( ; j < i; j++ ) { + sum -= lptr[j] * x[j]; + } + x[i] = sum; + } +} + +/* +============ +idSIMD_AltiVec::MatX_LowerTriangularSolveTranspose + + solves x in L.Transpose() * x = b for the first n rows of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_AltiVec::MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) { + + int nc; + const float *lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + float x0, x1, x2, x3, x4, x5, x6; + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + // using local variables to avoid aliasing issues + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x1 = b[1]; + x0 = b[0] - lptr[1*nc+0] * x1; + + x[1] = x1; + x[0] = x0; + return; + case 3: + x2 = b[2]; + x1 = b[1] - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[2] = x2; + x[1] = x1; + x[0] = x0; + return; + case 4: + x3 = b[3]; + x2 = b[2] - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + + return; + case 5: + x4 = b[4]; + x3 = b[3] - lptr[4*nc+3] * x4; + x2 = b[2] - lptr[4*nc+2] * x4 - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[4*nc+1] * x4 - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[4*nc+0] * x4 - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[4] = x4; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + return; + case 6: + x5 = b[5]; + x4 = b[4] - lptr[5*nc+4] * x5; + x3 = b[3] - lptr[5*nc+3] * x5 - lptr[4*nc+3] * x4; + x2 = b[2] - lptr[5*nc+2] * x5 - lptr[4*nc+2] * x4 - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[5*nc+1] * x5 - lptr[4*nc+1] * x4 - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[5*nc+0] * x5 - lptr[4*nc+0] * x4 - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[5] = x5; + x[4] = x4; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + + return; + case 7: + x6 = b[6]; + x5 = b[5] - lptr[6*nc+5] * x6; + x4 = b[4] - lptr[6*nc+4] * x6 - lptr[5*nc+4] * x5; + x3 = b[3] - lptr[6*nc+3] * x6 - lptr[5*nc+3] * x5 - lptr[4*nc+3] * x4; + x2 = b[2] - lptr[6*nc+2] * x6 - lptr[5*nc+2] * x5 - lptr[4*nc+2] * x4 - lptr[3*nc+2] * x3; + x1 = b[1] - lptr[6*nc+1] * x6 - lptr[5*nc+1] * x5 - lptr[4*nc+1] * x4 - lptr[3*nc+1] * x3 - lptr[2*nc+1] * x2; + x0 = b[0] - lptr[6*nc+0] * x6 - lptr[5*nc+0] * x5 - lptr[4*nc+0] * x4 - lptr[3*nc+0] * x3 - lptr[2*nc+0] * x2 - lptr[1*nc+0] * x1; + + x[6] = x6; + x[5] = x5; + x[4] = x4; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + return; + } + return; + } + + int i, j; + register float s0, s1, s2, s3; + float *xptr; + + lptr = L.ToFloatPtr() + n * nc + n - 4; + xptr = x + n; + + // process 4 rows at a time + for ( i = n; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + for ( j = 0; j < n-i; j += 4 ) { + s0 -= lptr[(j+0)*nc+0] * xptr[j+0]; + s1 -= lptr[(j+0)*nc+1] * xptr[j+0]; + s2 -= lptr[(j+0)*nc+2] * xptr[j+0]; + s3 -= lptr[(j+0)*nc+3] * xptr[j+0]; + s0 -= lptr[(j+1)*nc+0] * xptr[j+1]; + s1 -= lptr[(j+1)*nc+1] * xptr[j+1]; + s2 -= lptr[(j+1)*nc+2] * xptr[j+1]; + s3 -= lptr[(j+1)*nc+3] * xptr[j+1]; + s0 -= lptr[(j+2)*nc+0] * xptr[j+2]; + s1 -= lptr[(j+2)*nc+1] * xptr[j+2]; + s2 -= lptr[(j+2)*nc+2] * xptr[j+2]; + s3 -= lptr[(j+2)*nc+3] * xptr[j+2]; + s0 -= lptr[(j+3)*nc+0] * xptr[j+3]; + s1 -= lptr[(j+3)*nc+1] * xptr[j+3]; + s2 -= lptr[(j+3)*nc+2] * xptr[j+3]; + s3 -= lptr[(j+3)*nc+3] * xptr[j+3]; + } + // process left over of the 4 rows + s0 -= lptr[0-1*nc] * s3; + s1 -= lptr[1-1*nc] * s3; + s2 -= lptr[2-1*nc] * s3; + s0 -= lptr[0-2*nc] * s2; + s1 -= lptr[1-2*nc] * s2; + s0 -= lptr[0-3*nc] * s1; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4 + 4 * nc; + xptr -= 4; + } + // process left over rows + for ( i--; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } +} + +/* +============ +idSIMD_AltiVec::MatX_LDLTFactor +============ +*/ +#if __GNUC__ >= 4 +bool VPCALL idSIMD_AltiVec::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { +#else +unsigned char VPCALL idSIMD_AltiVec::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { +#endif + int i, j, k, nc; + float *v, *diag, *mptr; + float s0, s1, s2, s3, sum, d; + float s0_2, s1_2, s2_2, s3_2, sum_2; + float *mptr2; + + v = (float *) _alloca16( n * sizeof( float ) ); + diag = (float *) _alloca16( n * sizeof( float ) ); + + nc = mat.GetNumColumns(); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiag[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiag[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiag[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiag[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + for ( i = 4; i < n; i++ ) { + + mptr = mat[i]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + v[3] = diag[3] * mptr[3]; s3 = v[3] * mptr[3]; + for ( k = 4; k < i-3; k += 4 ) { + v[k+0] = diag[k+0] * mptr[k+0]; s0 += v[k+0] * mptr[k+0]; + v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + v[k+2] = diag[k+2] * mptr[k+2]; s2 += v[k+2] * mptr[k+2]; + v[k+3] = diag[k+3] * mptr[k+3]; s3 += v[k+3] * mptr[k+3]; + } + switch( i - k ) { + case 3: v[k+2] = diag[k+2] * mptr[k+2]; s0 += v[k+2] * mptr[k+2]; + case 2: v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + case 1: v[k+0] = diag[k+0] * mptr[k+0]; s2 += v[k+0] * mptr[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum = mptr[i] - sum; + + if ( sum == 0.0f ) { + return false; + } + + mat[i][i] = sum; + diag[i] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + return true; + } + + // unrolling madness! + mptr = mat[i+1]; + mptr2 = mat[i+1] + nc; + + for ( j = i+1; j+1 < n; j+=2 ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + + s0_2 = mptr2[0] * v[0]; + s1_2 = mptr2[1] * v[1]; + s2_2 = mptr2[2] * v[2]; + s3_2 = mptr2[3] * v[3]; + + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + + s0_2 += mptr2[k+0] * v[k+0]; + s1_2 += mptr2[k+1] * v[k+1]; + s2_2 += mptr2[k+2] * v[k+2]; + s3_2 += mptr2[k+3] * v[k+3]; + s0_2 += mptr2[k+4] * v[k+4]; + s1_2 += mptr2[k+5] * v[k+5]; + s2_2 += mptr2[k+6] * v[k+6]; + s3_2 += mptr2[k+7] * v[k+7]; + } + + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; s0_2 += mptr2[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; s1_2 += mptr2[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; s2_2 += mptr2[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; s3_2 += mptr2[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; s0_2 += mptr2[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; s1_2 += mptr2[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; s2_2 += mptr2[k+0] * v[k+0]; + } + // disassociate these adds + s3 += s2; + s1 += s0; + sum = s1 + s3; + + s3_2 += s2_2; + s1_2 += s0_2; + sum_2 = s1_2 + s3_2; + + mptr[i] = ( mptr[i] - sum ) * d; + mptr2[i] = ( mptr2[i] - sum_2 ) * d; + + mptr += nc*2; + mptr2 += nc*2; + } + + // cleanup + for ( ; j < n; j++ ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + } + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; + } + // disassociate these adds + s3 += s2; + s1 += s0; + sum = s1 + s3; + mptr[i] = ( mptr[i] - sum ) * d; + mptr += nc; + } + } + return true; +} +#endif /* ENABLE_LOWER_TRIANGULAR */ + + +#ifdef LIVE_VICARIOUSLY +/* +============ +idSIMD_AltiVec::BlendJoints +============ +*/ +void VPCALL idSIMD_AltiVec::BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { + int i; + + // since lerp is a constant, we can special case the two cases if they're true + if ( lerp <= 0.0f ) { + // this sets joints back to joints. No sense in doing no work, so just return + return; + } + + if ( lerp >= 1.0f ) { + // this copies each q from blendJoints to joints and copies each t from blendJoints to joints + memcpy( joints[0].q.ToFloatPtr(), blendJoints[0].q.ToFloatPtr(), sizeof(idJointQuat) * numJoints ); + return; + } + + vector float vecLerp = loadSplatUnalignedScalar( &lerp ); + vector float zeroVector = (vector float)(0); + + for ( i = 0; i+3 < numJoints; i+=4 ) { + int j = index[i]; + int j2 = index[i+1]; + int j3 = index[i+2]; + int j4 = index[i+3]; + + // slerp + const float *jointPtr = joints[j].q.ToFloatPtr(); + const float *blendPtr = blendJoints[j].q.ToFloatPtr(); + const float *jointPtr2 = joints[j2].q.ToFloatPtr(); + const float *blendPtr2 = blendJoints[j2].q.ToFloatPtr(); + const float *jointPtr3 = joints[j3].q.ToFloatPtr(); + const float *blendPtr3 = blendJoints[j3].q.ToFloatPtr(); + const float *jointPtr4 = joints[j4].q.ToFloatPtr(); + const float *blendPtr4 = blendJoints[j4].q.ToFloatPtr(); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, jointPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, jointPtr2 ), (vector unsigned char)(1) ); + vector unsigned char permVec3 = vec_add( vec_lvsl( -1, jointPtr3 ), (vector unsigned char)(1) ); + vector unsigned char permVec4 = vec_add( vec_lvsl( -1, jointPtr4 ), (vector unsigned char)(1) ); + + vector unsigned char permVec5 = vec_add( vec_lvsl( -1, blendPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec6 = vec_add( vec_lvsl( -1, blendPtr2 ), (vector unsigned char)(1) ); + vector unsigned char permVec7 = vec_add( vec_lvsl( -1, blendPtr3 ), (vector unsigned char)(1) ); + vector unsigned char permVec8 = vec_add( vec_lvsl( -1, blendPtr4 ), (vector unsigned char)(1) ); + + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11; + vector float v12, v13, v14, v15, v16; + vector float vecFromX, vecFromY, vecFromZ, vecFromW; + vector float vecToX, vecToY, vecToZ, vecToW; + + // load up the the idJointQuats from joints + v0 = vec_ld( 0, jointPtr ); + v1 = vec_ld( 15, jointPtr ); + v2 = vec_perm( v0, v1, permVec ); + + v3 = vec_ld( 0, jointPtr2 ); + v4 = vec_ld( 15, jointPtr2 ); + v5 = vec_perm( v3, v4, permVec2 ); + + v6 = vec_ld( 0, jointPtr3 ); + v7 = vec_ld( 15, jointPtr3 ); + v8 = vec_perm( v6, v7, permVec3 ); + + v9 = vec_ld( 0, jointPtr4 ); + v10 = vec_ld( 15, jointPtr4 ); + v11 = vec_perm( v9, v10, permVec4 ); + + // planarizing, so put each x y z w into its own vector + v0 = vec_mergeh( v2, v8 ); + v1 = vec_mergeh( v5, v11 ); + v3 = vec_mergel( v2, v8 ); + v4 = vec_mergel( v5, v11 ); + + vecFromX = vec_mergeh( v0, v1 ); + vecFromY = vec_mergel( v0, v1 ); + vecFromZ = vec_mergeh( v3, v4 ); + vecFromW = vec_mergel( v3, v4 ); + + // load up idJointQuats from blendJoints + v5 = vec_ld( 0, blendPtr ); + v6 = vec_ld( 15, blendPtr ); + v7 = vec_perm( v5, v6, permVec5 ); + + v8 = vec_ld( 0, blendPtr2 ); + v9 = vec_ld( 15, blendPtr2 ); + v10 = vec_perm( v8, v9, permVec6 ); + + v11 = vec_ld( 0, blendPtr3 ); + v12 = vec_ld( 15, blendPtr3 ); + v13 = vec_perm( v11, v12, permVec7 ); + + v14 = vec_ld( 0, blendPtr4 ); + v15 = vec_ld( 15, blendPtr4 ); + v16 = vec_perm( v14, v15, permVec8 ); + + // put these into their own vectors too + v5 = vec_mergeh( v7, v13 ); + v6 = vec_mergeh( v10, v16 ); + v8 = vec_mergel( v7, v13 ); + v9 = vec_mergel( v10, v16 ); + + vecToX = vec_mergeh( v5, v6 ); + vecToY = vec_mergel( v5, v6 ); + vecToZ = vec_mergeh( v8, v9 ); + vecToW = vec_mergel( v8, v9 ); + + // calculate cosom + vector float vecCosom = vec_madd( vecFromX, vecToX, (vector float)(0) ); + vecCosom = vec_madd( vecFromY, vecToY, vecCosom ); + vecCosom = vec_madd( vecFromZ, vecToZ, vecCosom ); + vecCosom = vec_madd( vecFromW, vecToW, vecCosom ); + + // if cosom is < 0, negate it and set temp to negated elements in to. otherwise, set temp to + // to + vector bool int vecCmp, vecCmp2; + vecCmp = vec_cmplt( vecCosom, zeroVector ); + + // negate if needed + vecToX = vec_sel( vecToX, vec_madd( vecToX, (vector float)(-1), zeroVector ), vecCmp ); + vecToY = vec_sel( vecToY, vec_madd( vecToY, (vector float)(-1), zeroVector ), vecCmp ); + vecToZ = vec_sel( vecToZ, vec_madd( vecToZ, (vector float)(-1), zeroVector ), vecCmp ); + vecToW = vec_sel( vecToW, vec_madd( vecToW, (vector float)(-1), zeroVector ), vecCmp ); + vecCosom = vec_sel( vecCosom, vec_madd( vecCosom, (vector float)(-1), zeroVector ), vecCmp ); + + // check if we need to calculate scale + vecCmp2 = vec_cmpgt( vec_sub( (vector float)(1), vecCosom ), (vector float)(1e-6f) ); + vector float vecScale0 = vec_sub( (vector float)(1), vecLerp ); + vector float vecScale1 = vec_splat( vecLerp, 0 ); + + vector float vecWork1 = vec_sub( (vector float)(1), vec_madd( vecCosom, vecCosom, zeroVector ) ); + vector float vecWork2 = ReciprocalSquareRoot( vecWork1 ); + vector float vecWork3 = VectorATan16( vec_madd( vecWork1, vecWork2, zeroVector ), vecCosom ); + + vecWork1 = vec_madd( VectorSin16( vec_madd( vecScale0, vecWork3, zeroVector ) ), vecWork2, zeroVector ); + vecWork2 = vec_madd( VectorSin16( vec_madd( vecLerp, vecWork3, zeroVector ) ), vecWork2, zeroVector ); + + // see which ones we have to insert into our scale0 and scale1 vectors + vecScale0 = vec_sel( vecScale0, vecWork1, vecCmp2 ); + vecScale1 = vec_sel( vecScale1, vecWork2, vecCmp2 ); + + // multiply each element by the scale + vecFromX = vec_madd( vecFromX, vecScale0, zeroVector ); + vecFromY = vec_madd( vecFromY, vecScale0, zeroVector ); + vecFromZ = vec_madd( vecFromZ, vecScale0, zeroVector ); + vecFromW = vec_madd( vecFromW, vecScale0, zeroVector ); + + // multiply temp by scale and add to result + vecFromX = vec_madd( vecToX, vecScale1, vecFromX ); + vecFromY = vec_madd( vecToY, vecScale1, vecFromY ); + vecFromZ = vec_madd( vecToZ, vecScale1, vecFromZ ); + vecFromW = vec_madd( vecToW, vecScale1, vecFromW ); + + // do a transform again to get the results back to vectors we can store out + v5 = vec_mergeh( vecFromX, vecFromZ ); + v6 = vec_mergeh( vecFromY, vecFromW ); + v8 = vec_mergel( vecFromX, vecFromZ ); + v9 = vec_mergel( vecFromY, vecFromW ); + + vecToX = vec_mergeh( v5, v6 ); + vecToY = vec_mergel( v5, v6 ); + vecToZ = vec_mergeh( v8, v9 ); + vecToW = vec_mergel( v8, v9 ); + + vector unsigned char storePerm1 = vec_lvsr( 0, jointPtr ); + vector unsigned char storePerm2 = vec_lvsr( 0, jointPtr2 ); + vector unsigned char storePerm3 = vec_lvsr( 0, jointPtr3 ); + vector unsigned char storePerm4 = vec_lvsr( 0, jointPtr4 ); + + // right rotate the input data + vecToX = vec_perm( vecToX, vecToX, storePerm1 ); + vecToY = vec_perm( vecToY, vecToY, storePerm2 ); + vecToZ = vec_perm( vecToZ, vecToZ, storePerm3 ); + vecToW = vec_perm( vecToW, vecToW, storePerm4 ); + + vec_ste( vecToX, 0, (float*) jointPtr ); + vec_ste( vecToX, 4, (float*) jointPtr ); + vec_ste( vecToX, 8, (float*) jointPtr ); + vec_ste( vecToX, 12, (float*) jointPtr ); + + vec_ste( vecToY, 0, (float*) jointPtr2 ); + vec_ste( vecToY, 4, (float*) jointPtr2 ); + vec_ste( vecToY, 8, (float*) jointPtr2 ); + vec_ste( vecToY, 12, (float*) jointPtr2 ); + + vec_ste( vecToZ, 0, (float*) jointPtr3 ); + vec_ste( vecToZ, 4, (float*) jointPtr3 ); + vec_ste( vecToZ, 8, (float*) jointPtr3 ); + vec_ste( vecToZ, 12, (float*) jointPtr3 ); + + vec_ste( vecToW, 0, (float*) jointPtr4 ); + vec_ste( vecToW, 4, (float*) jointPtr4 ); + vec_ste( vecToW, 8, (float*) jointPtr4 ); + vec_ste( vecToW, 12, (float*) jointPtr4 ); + + // lerp is v1 + l * ( v2 - v1 ); + // the idVec3 T is going to be 12 bytes after the Q, so we can do this without calling ToFloatPtr() again. since its + float *jointVecPtr = (float*)( jointPtr + 4 ); + float *jointVecPtr2 = (float*)( jointPtr2 + 4 ); + float *jointVecPtr3 = (float*)( jointPtr3 + 4 ); + float *jointVecPtr4 = (float*)( jointPtr4 + 4 ); + + v0 = vec_ld( 0, jointVecPtr ); + v1 = vec_ld( 11, jointVecPtr ); + vector float vecLd1 = vec_perm( v0, v1, vec_add( vec_lvsl( -1, jointVecPtr ), (vector unsigned char)(1) ) ); + + v2 = vec_ld( 0, jointVecPtr2 ); + v3 = vec_ld( 11, jointVecPtr2 ); + vector float vecLd2 = vec_perm( v2, v3, vec_add( vec_lvsl( -1, jointVecPtr2 ), (vector unsigned char)(1) ) ); + + v4 = vec_ld( 0, jointVecPtr3 ); + v5 = vec_ld( 11, jointVecPtr3 ); + vector float vecLd3 = vec_perm( v4, v5, vec_add( vec_lvsl( -1, jointVecPtr3 ), (vector unsigned char)(1) ) ); + + v6 = vec_ld( 0, jointVecPtr4 ); + v7 = vec_ld( 11, jointVecPtr4 ); + vector float vecLd4 = vec_perm( v6, v7, vec_add( vec_lvsl( -1, jointVecPtr4 ), (vector unsigned char)(1) ) ); + + vector float vecVecX, vecVecY, vecVecZ; + vecVecX = vecVecY = vecVecZ = zeroVector; + + // planarize + v0 = vec_mergeh( vecLd1, vecLd3 ); + v1 = vec_mergeh( vecLd2, vecLd4 ); + v3 = vec_mergel( vecLd1, vecLd3 ); + v4 = vec_mergel( vecLd2, vecLd4 ); + + vecVecX = vec_mergeh( v0, v1 ); + vecVecY = vec_mergel( v0, v1 ); + vecVecZ = vec_mergeh( v3, v4 ); + + // load blend joint idvec3's + float *blendVecPtr = (float*)( blendPtr + 4 ); + float *blendVecPtr2 =(float*)( blendPtr2 + 4 ); + float *blendVecPtr3 = (float*)( blendPtr3 + 4 ); + float *blendVecPtr4 = (float*)( blendPtr4 + 4 ); + + v0 = vec_ld( 0, blendVecPtr ); + v1 = vec_ld( 11, blendVecPtr ); + vector float vecLd5 = vec_perm( v0, v1, vec_add( vec_lvsl( -1, blendVecPtr ), (vector unsigned char)(1) ) ); + + v2 = vec_ld( 0, blendVecPtr2 ); + v3 = vec_ld( 11, blendVecPtr2 ); + vector float vecLd6 = vec_perm( v2, v3, vec_add( vec_lvsl( -1, blendVecPtr2 ), (vector unsigned char)(1) ) ); + + v4 = vec_ld( 0, blendVecPtr3 ); + v5 = vec_ld( 11, blendVecPtr3 ); + vector float vecLd7 = vec_perm( v4, v5, vec_add( vec_lvsl( -1, blendVecPtr3 ), (vector unsigned char)(1) ) ); + + v6 = vec_ld( 0, blendVecPtr4 ); + v7 = vec_ld( 11, blendVecPtr4 ); + vector float vecLd8 = vec_perm( v6, v7, vec_add( vec_lvsl( -1, blendVecPtr4 ), (vector unsigned char)(1) ) ); + + vector float vecBlendX, vecBlendY, vecBlendZ; + vecBlendX = vecBlendY = vecBlendZ = zeroVector; + + // planarize + v0 = vec_mergeh( vecLd5, vecLd7 ); + v1 = vec_mergeh( vecLd6, vecLd8 ); + v3 = vec_mergel( vecLd5, vecLd7 ); + v4 = vec_mergel( vecLd6, vecLd8 ); + + vecBlendX = vec_mergeh( v0, v1 ); + vecBlendY = vec_mergel( v0, v1 ); + vecBlendZ = vec_mergeh( v3, v4 ); + + // do subtraction + vecWork1 = vec_sub( vecBlendX, vecVecX ); + vecWork2 = vec_sub( vecBlendY, vecVecY ); + vecWork3 = vec_sub( vecBlendZ, vecVecZ ); + + // multiply by lerp and add to v1 + vecVecX = vec_madd( vecWork1, vecLerp, vecVecX ); + vecVecY = vec_madd( vecWork2, vecLerp, vecVecY ); + vecVecZ = vec_madd( vecWork3, vecLerp, vecVecZ ); + + // put it back in original form + v0 = vec_mergeh( vecVecX, vecVecZ ); + v1 = vec_mergeh( vecVecY, zeroVector ); + v3 = vec_mergel( vecVecX, vecVecZ ); + v4 = vec_mergel( vecVecY, zeroVector ); + + // generate vectors to store + vecWork1 = vec_mergeh( v0, v1 ); + vecWork2 = vec_mergel( v0, v1 ); + vecWork3 = vec_mergeh( v3, v4 ); + vector float vecWork4 = vec_mergel( v3, v4 ); + + // store the T values + storePerm1 = vec_lvsr( 0, jointVecPtr ); + storePerm2 = vec_lvsr( 0, jointVecPtr2 ); + storePerm3 = vec_lvsr( 0, jointVecPtr3 ); + storePerm4 = vec_lvsr( 0, jointVecPtr4 ); + + // right rotate the input data + vecWork1 = vec_perm( vecWork1, vecWork1, storePerm1 ); + vecWork2 = vec_perm( vecWork2, vecWork2, storePerm2 ); + vecWork3 = vec_perm( vecWork3, vecWork3, storePerm3 ); + vecWork4 = vec_perm( vecWork4, vecWork4, storePerm4 ); + + vec_ste( vecWork1, 0, (float*) jointVecPtr ); + vec_ste( vecWork1, 4, (float*) jointVecPtr ); + vec_ste( vecWork1, 8, (float*) jointVecPtr ); + + vec_ste( vecWork2, 0, (float*) jointVecPtr2 ); + vec_ste( vecWork2, 4, (float*) jointVecPtr2 ); + vec_ste( vecWork2, 8, (float*) jointVecPtr2 ); + + vec_ste( vecWork3, 0, (float*) jointVecPtr3 ); + vec_ste( vecWork3, 4, (float*) jointVecPtr3 ); + vec_ste( vecWork3, 8, (float*) jointVecPtr3 ); + + vec_ste( vecWork4, 0, (float*) jointVecPtr4 ); + vec_ste( vecWork4, 4, (float*) jointVecPtr4 ); + vec_ste( vecWork4, 8, (float*) jointVecPtr4 ); + } + + // cleanup + for ( ; i < numJoints; i++ ) { + int j = index[i]; + joints[j].q.Slerp( joints[j].q, blendJoints[j].q, lerp ); + joints[j].t.Lerp( joints[j].t, blendJoints[j].t, lerp ); + } +} + +/* +============ +idSIMD_AltiVec::ConvertJointQuatsToJointMats +============ +*/ + +// SSE doesn't vectorize this, and I don't think we should either. Its mainly just copying data, there's very little math involved and +// it's not easily parallelizable +void VPCALL idSIMD_AltiVec::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { + + for ( int i = 0; i < numJoints; i++ ) { + + const float *q = jointQuats[i].q.ToFloatPtr(); + float *m = jointMats[i].ToFloatPtr(); + + m[0*4+3] = q[4]; + m[1*4+3] = q[5]; + m[2*4+3] = q[6]; + + float x2 = q[0] + q[0]; + float y2 = q[1] + q[1]; + float z2 = q[2] + q[2]; + + { + float xx = q[0] * x2; + float yy = q[1] * y2; + float zz = q[2] * z2; + + m[0*4+0] = 1.0f - yy - zz; + m[1*4+1] = 1.0f - xx - zz; + m[2*4+2] = 1.0f - xx - yy; + } + + { + float yz = q[1] * z2; + float wx = q[3] * x2; + + m[2*4+1] = yz - wx; + m[1*4+2] = yz + wx; + } + + { + float xy = q[0] * y2; + float wz = q[3] * z2; + + m[1*4+0] = xy - wz; + m[0*4+1] = xy + wz; + } + + { + float xz = q[0] * z2; + float wy = q[3] * y2; + + m[0*4+2] = xz - wy; + m[2*4+0] = xz + wy; + } + } +} + +/* +============ +idSIMD_AltiVec::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_AltiVec::ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) { + + int index; + + // Since we use very little of the data we have to pull in for the altivec version, we end up with + // a lot of wasted math. Rather than try to force it to use altivec, I wrote an optimized version + // of InvSqrt for the G5, and made it use that instead. With only this change, we get a little + // bigger than 50% speedup, which is not too shabby. Should really replace idMath::InvSqrt with + // my function so everyone can benefit on G5. + + for ( index = 0; index < numJoints; index++ ) { + + idJointQuat jq; + float trace; + float s; + float t; + int i; + int j; + int k; + + static int next[3] = { 1, 2, 0 }; + + float *mat = (float*)( jointMats[index].ToFloatPtr() ); + trace = mat[0 * 4 + 0] + mat[1 * 4 + 1] + mat[2 * 4 + 2]; + + if ( trace > 0.0f ) { + + t = trace + 1.0f; + //s = idMath::InvSqrt( t ) * 0.5f; + s = FastScalarInvSqrt( t ) * 0.5f; + + jq.q[3] = s * t; + jq.q[0] = ( mat[1 * 4 + 2] - mat[2 * 4 + 1] ) * s; + jq.q[1] = ( mat[2 * 4 + 0] - mat[0 * 4 + 2] ) * s; + jq.q[2] = ( mat[0 * 4 + 1] - mat[1 * 4 + 0] ) * s; + + } else { + + i = 0; + if ( mat[1 * 4 + 1] > mat[0 * 4 + 0] ) { + i = 1; + } + if ( mat[2 * 4 + 2] > mat[i * 4 + i] ) { + i = 2; + } + j = next[i]; + k = next[j]; + + t = ( mat[i * 4 + i] - ( mat[j * 4 + j] + mat[k * 4 + k] ) ) + 1.0f; + //s = idMath::InvSqrt( t ) * 0.5f; + s = FastScalarInvSqrt( t ) * 0.5f; + + jq.q[i] = s * t; + jq.q[3] = ( mat[j * 4 + k] - mat[k * 4 + j] ) * s; + jq.q[j] = ( mat[i * 4 + j] + mat[j * 4 + i] ) * s; + jq.q[k] = ( mat[i * 4 + k] + mat[k * 4 + i] ) * s; + } + + jq.t[0] = mat[0 * 4 + 3]; + jq.t[1] = mat[1 * 4 + 3]; + jq.t[2] = mat[2 * 4 + 3]; + jointQuats[index] = jq; + } +} + +/* +============ +idSIMD_AltiVec::TransformJoints +============ +*/ +void VPCALL idSIMD_AltiVec::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + int i; +#if 0 + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } +#else + + // I don't think you can unroll this since the next iteration of the loop might depending on the previous iteration, depending + // on what the parents array looks like. This is true in the test code. + for ( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + float *jointPtr = jointMats[i].ToFloatPtr(); + float *parentPtr = jointMats[parents[i]].ToFloatPtr(); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, jointPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, parentPtr ), (vector unsigned char)(1) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + // we need to load up 12 float elements that make up the Mat + v0 = vec_ld( 0, jointPtr ); + v1 = vec_ld( 15, jointPtr ); + v2 = vec_ld( 31, jointPtr ); + v3 = vec_ld( 47, jointPtr ); + + // load parents + v4 = vec_ld( 0, parentPtr ); + v5 = vec_ld( 15, parentPtr ); + v6 = vec_ld( 31, parentPtr ); + v7 = vec_ld( 47, parentPtr ); + + // permute into vectors + vector float vecJointMat1 = vec_perm( v0, v1, permVec ); + vector float vecJointMat2 = vec_perm( v1, v2, permVec ); + vector float vecJointMat3 = vec_perm( v2, v3, permVec ); + + vector float vecParentMat1 = vec_perm( v4, v5, permVec2 ); + vector float vecParentMat2 = vec_perm( v5, v6, permVec2 ); + vector float vecParentMat3 = vec_perm( v6, v7, permVec2 ); + + vector float zero = (vector float)(0); + vector float C1, C2, C3; + + // matrix multiply + C1 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 0 ), zero ); // m(0 to 3) * a(0) + C2 = vec_madd( vecJointMat1, vec_splat( vecParentMat2, 0 ), zero ); // m(4 to 7) * a(4) + C3 = vec_madd( vecJointMat1, vec_splat( vecParentMat3, 0 ), zero ); // m(8 to 11) * a(8) + + C1 = vec_madd( vecJointMat2, vec_splat( vecParentMat1, 1 ), C1 ); // add in m(4 to 7) * a(1) + C2 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 1 ), C2 ); // add in m(4 to 7) * a(5) + C3 = vec_madd( vecJointMat2, vec_splat( vecParentMat3, 1 ), C3 ); // add in m(4 to 7) * a(9) + + C1 = vec_madd( vecJointMat3, vec_splat( vecParentMat1, 2 ), C1 ); + C2 = vec_madd( vecJointMat3, vec_splat( vecParentMat2, 2 ), C2 ); + C3 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 2 ), C3 ); + + // do the addition at the end + vector unsigned char permZeroAndLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,28,29,30,31); + C1 = vec_add( C1, vec_perm( zero, vecParentMat1, permZeroAndLast ) ); + C2 = vec_add( C2, vec_perm( zero, vecParentMat2, permZeroAndLast ) ); + C3 = vec_add( C3, vec_perm( zero, vecParentMat3, permZeroAndLast ) ); + + // store results + UNALIGNED_STORE3( (float*) jointPtr, C1, C2, C3 ); + } +#endif +} + +/* +============ +idSIMD_AltiVec::UntransformJoints +============ +*/ +void VPCALL idSIMD_AltiVec::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { + int i; +#if 0 + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } +#else + // I don't think you can unroll this since the next iteration of the loop might depending on the previous iteration, depending + // on what the parents array looks like. This is true in the test code. + for ( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + float *jointPtr = jointMats[i].ToFloatPtr(); + float *parentPtr = jointMats[parents[i]].ToFloatPtr(); + + vector unsigned char permVec = vec_add( vec_lvsl( -1, jointPtr ), (vector unsigned char)(1) ); + vector unsigned char permVec2 = vec_add( vec_lvsl( -1, parentPtr ), (vector unsigned char)(1) ); + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + // we need to load up 12 float elements that make up the Mat + v0 = vec_ld( 0, jointPtr ); + v1 = vec_ld( 15, jointPtr ); + v2 = vec_ld( 31, jointPtr ); + v3 = vec_ld( 47, jointPtr ); + + // load parents + v4 = vec_ld( 0, parentPtr ); + v5 = vec_ld( 15, parentPtr ); + v6 = vec_ld( 31, parentPtr ); + v7 = vec_ld( 47, parentPtr ); + + // permute into vectors + vector float vecJointMat1 = vec_perm( v0, v1, permVec ); + vector float vecJointMat2 = vec_perm( v1, v2, permVec ); + vector float vecJointMat3 = vec_perm( v2, v3, permVec ); + + vector float vecParentMat1 = vec_perm( v4, v5, permVec2 ); + vector float vecParentMat2 = vec_perm( v5, v6, permVec2 ); + vector float vecParentMat3 = vec_perm( v6, v7, permVec2 ); + + vector float zero = (vector float)(0); + vector float C1, C2, C3; + + // do subtraction at the beginning + vector unsigned char permZeroAndLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,28,29,30,31); + vecJointMat1 = vec_sub( vecJointMat1, vec_perm( zero, vecParentMat1, permZeroAndLast ) ); + vecJointMat2 = vec_sub( vecJointMat2, vec_perm( zero, vecParentMat2, permZeroAndLast ) ); + vecJointMat3 = vec_sub( vecJointMat3, vec_perm( zero, vecParentMat3, permZeroAndLast ) ); + + // matrix multiply + C1 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 0 ), zero ); + C2 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 1 ), zero ); + C3 = vec_madd( vecJointMat1, vec_splat( vecParentMat1, 2 ), zero ); + + C1 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 0 ), C1 ); + C2 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 1 ), C2 ); + C3 = vec_madd( vecJointMat2, vec_splat( vecParentMat2, 2 ), C3 ); + + C1 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 0 ), C1 ); + C2 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 1 ), C2 ); + C3 = vec_madd( vecJointMat3, vec_splat( vecParentMat3, 2 ), C3 ); + + // store results back + vector unsigned char storePerm = vec_lvsr( 0, jointPtr ); + + // right rotate the input data + C1 = vec_perm( C1, C1, storePerm ); + C2 = vec_perm( C2, C2, storePerm ); + C3 = vec_perm( C3, C3, storePerm ); + + vec_ste( C1, 0, (float*) jointPtr ); + vec_ste( C1, 4, (float*) jointPtr ); + vec_ste( C1, 8, (float*) jointPtr ); + vec_ste( C1, 12, (float*) jointPtr ); + + vec_ste( C2, 16, (float*) jointPtr ); + vec_ste( C2, 20, (float*) jointPtr ); + vec_ste( C2, 24, (float*) jointPtr ); + vec_ste( C2, 28, (float*) jointPtr ); + + vec_ste( C3, 32, (float*) jointPtr ); + vec_ste( C3, 36, (float*) jointPtr ); + vec_ste( C3, 40, (float*) jointPtr ); + vec_ste( C3, 44, (float*) jointPtr ); + } + +#endif +} + +#endif /* LIVE_VICARIOUSLY */ + +#ifdef ENABLE_CULL + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::TracePointCull +============ +*/ +void VPCALL idSIMD_AltiVec::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + byte tOr; + tOr = 0; + + // pointers + const float *planePtr = planes[0].ToFloatPtr(); + + vector unsigned int vecShift1 = (vector unsigned int)(0,1,2,3); + vector unsigned int vecShift2 = (vector unsigned int)(4,5,6,7); + vector unsigned int vecFlipBits = (vector unsigned int)(0x0F); + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector float zeroVector = (vector float)(0); + vector float vecRadius; + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float vec1Sum1, vec1Sum2, vec1Sum3, vec1Sum4; + vector unsigned char vecPermLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector float vecDPlusRadius1, vecDPlusRadius2, vecDPlusRadius3, vecDPlusRadius4; + vector float vecDMinusRadius1, vecDMinusRadius2, vecDMinusRadius3, vecDMinusRadius4; + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4, vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecTotals; + vector unsigned int tempIntSum; + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + // load constants + vecRadius = loadSplatUnalignedScalar( &radius ); + + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + int i = 0; + + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + vecXYZ1 = vec_perm( v0, v1, vertPerm1 ); + vecXYZ2 = vec_perm( v2, v3, vertPerm2 ); + vecXYZ3 = vec_perm( v4, v5, vertPerm3 ); + vecXYZ4 = vec_perm( v6, v7, vertPerm4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane3 ); + + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec1Sum3 ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec1Sum3 ); + vec1Sum3 = vec_add( vec1Sum3, vecPlane3 ); + + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec1Sum4 ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec1Sum4 ); + vec1Sum4 = vec_add( vec1Sum4, vecPlane3 ); + + // vec1Sum1 now holds d0, d1, d2, d3. calculate the + // difference with +radius and -radius + vecDPlusRadius1 = vec_add( vec1Sum1, vecRadius ); + vecDMinusRadius1 = vec_sub( vec1Sum1, vecRadius ); + vecDPlusRadius2 = vec_add( vec1Sum2, vecRadius ); + vecDMinusRadius2 = vec_sub( vec1Sum2, vecRadius ); + vecDPlusRadius3 = vec_add( vec1Sum3, vecRadius ); + vecDMinusRadius3 = vec_sub( vec1Sum3, vecRadius ); + vecDPlusRadius4 = vec_add( vec1Sum4, vecRadius ); + vecDMinusRadius4 = vec_sub( vec1Sum4, vecRadius ); + + // do compare + vecCmp1 = vec_cmplt( vecDPlusRadius1, zeroVector ); + vecCmp2 = vec_cmplt( vecDMinusRadius1, zeroVector ); + vecCmp3 = vec_cmplt( vecDPlusRadius2, zeroVector ); + vecCmp4 = vec_cmplt( vecDMinusRadius2, zeroVector ); + vecCmp5 = vec_cmplt( vecDPlusRadius3, zeroVector ); + vecCmp6 = vec_cmplt( vecDMinusRadius3, zeroVector ); + vecCmp7 = vec_cmplt( vecDPlusRadius4, zeroVector ); + vecCmp8 = vec_cmplt( vecDMinusRadius4, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + // OR (add) them all together + vecBitShifted1 = vec_add( vecBitShifted1, vecBitShifted2 ); + vecBitShifted3 = vec_add( vecBitShifted3, vecBitShifted4 ); + vecBitShifted5 = vec_add( vecBitShifted5, vecBitShifted6 ); + vecBitShifted7 = vec_add( vecBitShifted7, vecBitShifted8 ); + + vecTotals = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecTotals = vec_add( vecTotals, vec_sld( vecTotals, vecTotals, 4 ) ); + tempIntSum = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_mergeh( vecTotals, tempIntSum ); + tempIntSum = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermHalves ); + tempIntSum = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermLast ); + + // store out results + vector unsigned int tempSt = vec_xor( vecTotals, vecFlipBits ); + tempSt = vec_perm( tempSt, tempSt, cullBitPerm ); + vec_ste( tempSt, 0, &cullBitVal[0] ); + vec_ste( tempSt, 4, &cullBitVal[0] ); + vec_ste( tempSt, 8, &cullBitVal[0] ); + vec_ste( tempSt, 12, &cullBitVal[0] ); + + tOr |= cullBitVal[0]; + tOr |= cullBitVal[1]; + tOr |= cullBitVal[2]; + tOr |= cullBitVal[3]; + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} +#else + +/* +============ +idSIMD_AltiVec::TracePointCull +============ +*/ +void VPCALL idSIMD_AltiVec::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + byte tOr; + tOr = 0; + + // pointers + const float *planePtr = planes[0].ToFloatPtr(); + + vector unsigned int vecShift1 = (vector unsigned int)(0,1,2,3); + vector unsigned int vecShift2 = (vector unsigned int)(4,5,6,7); + vector unsigned int vecFlipBits = (vector unsigned int)(0x0F); + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector float zeroVector = (vector float)(0); + vector float vecRadius; + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float vec1Sum1, vec1Sum2, vec1Sum3, vec1Sum4; + vector unsigned char vecPermLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector float vecDPlusRadius1, vecDPlusRadius2, vecDPlusRadius3, vecDPlusRadius4; + vector float vecDMinusRadius1, vecDMinusRadius2, vecDMinusRadius3, vecDMinusRadius4; + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4, vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecTotals; + vector unsigned int tempIntSum; + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + // load constants + vecRadius = loadSplatUnalignedScalar( &radius ); + + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + int i = 0; + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + vecXYZ1 = vec_ld( 0, vertPtr ); + vecXYZ2 = vec_ld( 0, vertPtr2 ); + vecXYZ3 = vec_ld( 0, vertPtr3 ); + vecXYZ4 = vec_ld( 0, vertPtr4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane3 ); + + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec1Sum3 ); + vec1Sum3 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec1Sum3 ); + vec1Sum3 = vec_add( vec1Sum3, vecPlane3 ); + + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec1Sum4 ); + vec1Sum4 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec1Sum4 ); + vec1Sum4 = vec_add( vec1Sum4, vecPlane3 ); + + // vec1Sum1 now holds d0, d1, d2, d3. calculate the + // difference with +radius and -radius + vecDPlusRadius1 = vec_add( vec1Sum1, vecRadius ); + vecDMinusRadius1 = vec_sub( vec1Sum1, vecRadius ); + vecDPlusRadius2 = vec_add( vec1Sum2, vecRadius ); + vecDMinusRadius2 = vec_sub( vec1Sum2, vecRadius ); + vecDPlusRadius3 = vec_add( vec1Sum3, vecRadius ); + vecDMinusRadius3 = vec_sub( vec1Sum3, vecRadius ); + vecDPlusRadius4 = vec_add( vec1Sum4, vecRadius ); + vecDMinusRadius4 = vec_sub( vec1Sum4, vecRadius ); + + // do compare + vecCmp1 = vec_cmplt( vecDPlusRadius1, zeroVector ); + vecCmp2 = vec_cmplt( vecDMinusRadius1, zeroVector ); + vecCmp3 = vec_cmplt( vecDPlusRadius2, zeroVector ); + vecCmp4 = vec_cmplt( vecDMinusRadius2, zeroVector ); + vecCmp5 = vec_cmplt( vecDPlusRadius3, zeroVector ); + vecCmp6 = vec_cmplt( vecDMinusRadius3, zeroVector ); + vecCmp7 = vec_cmplt( vecDPlusRadius4, zeroVector ); + vecCmp8 = vec_cmplt( vecDMinusRadius4, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + // OR (add) them all together + vecBitShifted1 = vec_add( vecBitShifted1, vecBitShifted2 ); + vecBitShifted3 = vec_add( vecBitShifted3, vecBitShifted4 ); + vecBitShifted5 = vec_add( vecBitShifted5, vecBitShifted6 ); + vecBitShifted7 = vec_add( vecBitShifted7, vecBitShifted8 ); + + vecTotals = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecTotals = vec_add( vecTotals, vec_sld( vecTotals, vecTotals, 4 ) ); + tempIntSum = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_mergeh( vecTotals, tempIntSum ); + tempIntSum = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermHalves ); + tempIntSum = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + tempIntSum = vec_add( tempIntSum, vec_sld( tempIntSum, tempIntSum, 4 ) ); + vecTotals = vec_perm( vecTotals, tempIntSum, vecPermLast ); + + // store out results + vector unsigned int tempSt = vec_xor( vecTotals, vecFlipBits ); + tempSt = vec_perm( tempSt, tempSt, cullBitPerm ); + vec_ste( tempSt, 0, &cullBitVal[0] ); + vec_ste( tempSt, 4, &cullBitVal[0] ); + vec_ste( tempSt, 8, &cullBitVal[0] ); + vec_ste( tempSt, 12, &cullBitVal[0] ); + + tOr |= cullBitVal[0]; + tOr |= cullBitVal[1]; + tOr |= cullBitVal[2]; + tOr |= cullBitVal[3]; + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} + +#endif /* DRAWVERT_PADDED */ + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::DecalPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + const float *planePtr = planes[0].ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3, vecPlane4, vecPlane5, vecPlane6, vecPlane7; + vector float zeroVector = (vector float)(0.0); + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 16 ); + v1 = vec_ld( 15, planePtr + 16 ); + vecPlane4 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 20 ); + v3 = vec_ld( 15, planePtr + 20 ); + vecPlane5 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + v0 = vec_mergeh( vecPlane4, zeroVector ); + v1 = vec_mergeh( vecPlane5, zeroVector ); + v2 = vec_mergel( vecPlane4, zeroVector ); + v3 = vec_mergel( vecPlane5, zeroVector ); + + vecPlane4 = vec_mergeh( v0, v1 ); + vecPlane5 = vec_mergel( v0, v1 ); + vecPlane6 = vec_mergeh( v2, v3 ); + vecPlane7 = vec_mergel( v2, v3 ); + + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector bool int oneIntVector = (vector bool int)(1); + vector float vec1Sum1, vec1Sum2, vec2Sum1, vec2Sum2, vec3Sum1, vec3Sum2, vec4Sum1, vec4Sum2; + vector unsigned int vecShift1 = (vector unsigned int)(0, 1, 2, 3 ); + vector unsigned int vecShift2 = (vector unsigned int)(4, 5, 0, 0 ); + + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4; + vector unsigned int vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecFlipBits = (vector unsigned int)( 0x3F, 0x3F, 0x3F, 0x3F ); + vector unsigned int vecR1, vecR2, vecR3, vecR4; + vector unsigned char permHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int vBits[4]; + vector unsigned char vBitPerm = vec_lvsr( 0, &vBits[4] ); + + i = 0; + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + vecXYZ1 = vec_perm( v0, v1, vertPerm1 ); + vecXYZ2 = vec_perm( v2, v3, vertPerm2 ); + vecXYZ3 = vec_perm( v4, v5, vertPerm3 ); + vecXYZ4 = vec_perm( v6, v7, vertPerm4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane4, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane5, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane6, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane7 ); + + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec2Sum1 ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec2Sum1 ); + vec2Sum1 = vec_add( vec2Sum1, vecPlane3 ); + + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane4, zeroVector ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane5, vec2Sum2 ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane6, vec2Sum2 ); + vec2Sum2 = vec_add( vec2Sum2, vecPlane7 ); + + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec3Sum1 ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec3Sum1 ); + vec3Sum1 = vec_add( vec3Sum1, vecPlane3 ); + + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane4, zeroVector ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane5, vec3Sum2 ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane6, vec3Sum2 ); + vec3Sum2 = vec_add( vec3Sum2, vecPlane7 ); + + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec4Sum1 ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec4Sum1 ); + vec4Sum1 = vec_add( vec4Sum1, vecPlane3 ); + + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane4, zeroVector ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane5, vec4Sum2 ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane6, vec4Sum2 ); + vec4Sum2 = vec_add( vec4Sum2, vecPlane7 ); + + vecCmp1 = vec_cmplt( vec1Sum1, zeroVector ); + vecCmp2 = vec_cmplt( vec1Sum2, zeroVector ); + vecCmp3 = vec_cmplt( vec2Sum1, zeroVector ); + vecCmp4 = vec_cmplt( vec2Sum2, zeroVector ); + vecCmp5 = vec_cmplt( vec3Sum1, zeroVector ); + vecCmp6 = vec_cmplt( vec3Sum2, zeroVector ); + vecCmp7 = vec_cmplt( vec4Sum1, zeroVector ); + vecCmp8 = vec_cmplt( vec4Sum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + //OR them all together (this is the same as adding them, since they're all only 1 bit set) + vecR1 = (vector unsigned int)(0); //zeroIntVector; + vecR1 = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecR1 = vec_add( vecR1, vec_sld( vecR1, vecR1, 4 ) ); + vecR1 = vec_add(vecR1, vecBitShifted2 ); + vecR1 = vec_or( vecR1, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + vecR2 = (vector unsigned int)(0); //zeroIntVector; + vecR2 = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + vecR2 = vec_add( vecR2, vec_sld( vecR2, vecR2, 4 ) ); + vecR2 = vec_add(vecR2, vecBitShifted4 ); + vecR2 = vec_or( vecR2, vec_sld( vecBitShifted4, vecBitShifted4, 4 ) ); + + vecR3 = (vector unsigned int)(0); //zeroIntVector; + vecR3 = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + vecR3 = vec_add( vecR3, vec_sld( vecR3, vecR3, 4 ) ); + vecR3 = vec_add(vecR3, vecBitShifted6 ); + vecR3 = vec_or( vecR3, vec_sld( vecBitShifted6, vecBitShifted6, 4 ) ); + + vecR4 = (vector unsigned int)(0); //zeroIntVector; + vecR4 = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + vecR4 = vec_add( vecR4, vec_sld( vecR4, vecR4, 4 ) ); + vecR4 = vec_add(vecR4, vecBitShifted8 ); + vecR4 = vec_or( vecR4, vec_sld( vecBitShifted8, vecBitShifted8, 4 ) ); + + // take the first element from each vector and put them into vecR1 + vecR1 = vec_mergeh( vecR1, vecR2 ); + vecR3 = vec_mergeh( vecR3, vecR4 ); + vecR1 = vec_perm( vecR1, vecR3, permHalves ); + + // XOR with 0x3F to flip lower 6 bits + vecR1 = vec_xor( vecR1, vecFlipBits ); + + // store out results. don't have 16 at a time so let's just + // do this and avoid alignment concerns + vecR1 = vec_perm( vecR1, vecR1, vBitPerm ); + vec_ste( vecR1, 0, &vBits[0] ); + vec_ste( vecR1, 4, &vBits[0] ); + vec_ste( vecR1, 8, &vBits[0] ); + vec_ste( vecR1, 12, &vBits[0] ); + + cullBits[i] = vBits[0]; + cullBits[i+1] = vBits[1]; + cullBits[i+2] = vBits[2]; + cullBits[i+3] = vBits[3]; + } + + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + // they check if the sign bit is set by casting as long and shifting right 31 places. + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + +#else + +/* +============ +idSIMD_AltiVec::DecalPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + const float *planePtr = planes[0].ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3, vecPlane4, vecPlane5, vecPlane6, vecPlane7; + vector float zeroVector = (vector float)(0.0); + vector unsigned char vecPerm; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + + // populate planes + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 4 ); + v3 = vec_ld( 15, planePtr + 4 ); + vecPlane1 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 8 ); + v1 = vec_ld( 15, planePtr + 8 ); + vecPlane2 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 12 ); + v3 = vec_ld( 15, planePtr + 12 ); + vecPlane3 = vec_perm( v2, v3, vecPerm ); + + v0 = vec_ld( 0, planePtr + 16 ); + v1 = vec_ld( 15, planePtr + 16 ); + vecPlane4 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 0, planePtr + 20 ); + v3 = vec_ld( 15, planePtr + 20 ); + vecPlane5 = vec_perm( v2, v3, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane2 ); + v1 = vec_mergeh( vecPlane1, vecPlane3 ); + v2 = vec_mergel( vecPlane0, vecPlane2 ); + v3 = vec_mergel( vecPlane1, vecPlane3 ); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + v0 = vec_mergeh( vecPlane4, zeroVector ); + v1 = vec_mergeh( vecPlane5, zeroVector ); + v2 = vec_mergel( vecPlane4, zeroVector ); + v3 = vec_mergel( vecPlane5, zeroVector ); + + vecPlane4 = vec_mergeh( v0, v1 ); + vecPlane5 = vec_mergel( v0, v1 ); + vecPlane6 = vec_mergeh( v2, v3 ); + vecPlane7 = vec_mergel( v2, v3 ); + + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector bool int oneIntVector = (vector bool int)(1); + vector float vec1Sum1, vec1Sum2, vec2Sum1, vec2Sum2, vec3Sum1, vec3Sum2, vec4Sum1, vec4Sum2; + vector unsigned int vecShift1 = (vector unsigned int)(0, 1, 2, 3 ); + vector unsigned int vecShift2 = (vector unsigned int)(4, 5, 0, 0 ); + + vector bool int vecCmp1, vecCmp2, vecCmp3, vecCmp4, vecCmp5, vecCmp6, vecCmp7, vecCmp8; + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted3, vecBitShifted4; + vector unsigned int vecBitShifted5, vecBitShifted6, vecBitShifted7, vecBitShifted8; + vector unsigned int vecFlipBits = (vector unsigned int)( 0x3F, 0x3F, 0x3F, 0x3F ); + vector unsigned int vecR1, vecR2, vecR3, vecR4; + vector unsigned char permHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int vBits[4]; + vector unsigned char vBitPerm = vec_lvsr( 0, &vBits[4] ); + + i = 0; + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane0, zeroVector ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane1, vec1Sum1 ); + vec1Sum1 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane2, vec1Sum1 ); + vec1Sum1 = vec_add( vec1Sum1, vecPlane3 ); + + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 0 ), vecPlane4, zeroVector ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 1 ), vecPlane5, vec1Sum2 ); + vec1Sum2 = vec_madd( vec_splat( vecXYZ1, 2 ), vecPlane6, vec1Sum2 ); + vec1Sum2 = vec_add( vec1Sum2, vecPlane7 ); + + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane0, zeroVector ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane1, vec2Sum1 ); + vec2Sum1 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane2, vec2Sum1 ); + vec2Sum1 = vec_add( vec2Sum1, vecPlane3 ); + + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 0 ), vecPlane4, zeroVector ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 1 ), vecPlane5, vec2Sum2 ); + vec2Sum2 = vec_madd( vec_splat( vecXYZ2, 2 ), vecPlane6, vec2Sum2 ); + vec2Sum2 = vec_add( vec2Sum2, vecPlane7 ); + + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane0, zeroVector ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane1, vec3Sum1 ); + vec3Sum1 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane2, vec3Sum1 ); + vec3Sum1 = vec_add( vec3Sum1, vecPlane3 ); + + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 0 ), vecPlane4, zeroVector ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 1 ), vecPlane5, vec3Sum2 ); + vec3Sum2 = vec_madd( vec_splat( vecXYZ3, 2 ), vecPlane6, vec3Sum2 ); + vec3Sum2 = vec_add( vec3Sum2, vecPlane7 ); + + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane0, zeroVector ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane1, vec4Sum1 ); + vec4Sum1 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane2, vec4Sum1 ); + vec4Sum1 = vec_add( vec4Sum1, vecPlane3 ); + + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 0 ), vecPlane4, zeroVector ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 1 ), vecPlane5, vec4Sum2 ); + vec4Sum2 = vec_madd( vec_splat( vecXYZ4, 2 ), vecPlane6, vec4Sum2 ); + vec4Sum2 = vec_add( vec4Sum2, vecPlane7 ); + + vecCmp1 = vec_cmplt( vec1Sum1, zeroVector ); + vecCmp2 = vec_cmplt( vec1Sum2, zeroVector ); + vecCmp3 = vec_cmplt( vec2Sum1, zeroVector ); + vecCmp4 = vec_cmplt( vec2Sum2, zeroVector ); + vecCmp5 = vec_cmplt( vec3Sum1, zeroVector ); + vecCmp6 = vec_cmplt( vec3Sum2, zeroVector ); + vecCmp7 = vec_cmplt( vec4Sum1, zeroVector ); + vecCmp8 = vec_cmplt( vec4Sum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + vecCmp3 = vec_and( vecCmp3, oneIntVector ); + vecCmp4 = vec_and( vecCmp4, oneIntVector ); + vecCmp5 = vec_and( vecCmp5, oneIntVector ); + vecCmp6 = vec_and( vecCmp6, oneIntVector ); + vecCmp7 = vec_and( vecCmp7, oneIntVector ); + vecCmp8 = vec_and( vecCmp8, oneIntVector ); + + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift1 ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift2 ); + vecBitShifted3 = vec_sl( (vector unsigned int)vecCmp3, vecShift1 ); + vecBitShifted4 = vec_sl( (vector unsigned int)vecCmp4, vecShift2 ); + vecBitShifted5 = vec_sl( (vector unsigned int)vecCmp5, vecShift1 ); + vecBitShifted6 = vec_sl( (vector unsigned int)vecCmp6, vecShift2 ); + vecBitShifted7 = vec_sl( (vector unsigned int)vecCmp7, vecShift1 ); + vecBitShifted8 = vec_sl( (vector unsigned int)vecCmp8, vecShift2 ); + + //OR them all together (this is the same as adding them, since they're all only 1 bit set) + vecR1 = (vector unsigned int)(0); //zeroIntVector; + vecR1 = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 8 ) ); + vecR1 = vec_add( vecR1, vec_sld( vecR1, vecR1, 4 ) ); + vecR1 = vec_add(vecR1, vecBitShifted2 ); + vecR1 = vec_or( vecR1, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + vecR2 = (vector unsigned int)(0); //zeroIntVector; + vecR2 = vec_add( vecBitShifted3, vec_sld( vecBitShifted3, vecBitShifted3, 8 ) ); + vecR2 = vec_add( vecR2, vec_sld( vecR2, vecR2, 4 ) ); + vecR2 = vec_add(vecR2, vecBitShifted4 ); + vecR2 = vec_or( vecR2, vec_sld( vecBitShifted4, vecBitShifted4, 4 ) ); + + vecR3 = (vector unsigned int)(0); //zeroIntVector; + vecR3 = vec_add( vecBitShifted5, vec_sld( vecBitShifted5, vecBitShifted5, 8 ) ); + vecR3 = vec_add( vecR3, vec_sld( vecR3, vecR3, 4 ) ); + vecR3 = vec_add(vecR3, vecBitShifted6 ); + vecR3 = vec_or( vecR3, vec_sld( vecBitShifted6, vecBitShifted6, 4 ) ); + + vecR4 = (vector unsigned int)(0); //zeroIntVector; + vecR4 = vec_add( vecBitShifted7, vec_sld( vecBitShifted7, vecBitShifted7, 8 ) ); + vecR4 = vec_add( vecR4, vec_sld( vecR4, vecR4, 4 ) ); + vecR4 = vec_add(vecR4, vecBitShifted8 ); + vecR4 = vec_or( vecR4, vec_sld( vecBitShifted8, vecBitShifted8, 4 ) ); + + // take the first element from each vector and put them into vecR1 + vecR1 = vec_mergeh( vecR1, vecR2 ); + vecR3 = vec_mergeh( vecR3, vecR4 ); + vecR1 = vec_perm( vecR1, vecR3, permHalves ); + + // XOR with 0x3F to flip lower 6 bits + vecR1 = vec_xor( vecR1, vecFlipBits ); + + // store out results. don't have 16 at a time so let's just + // do this and avoid alignment concerns + vecR1 = vec_perm( vecR1, vecR1, vBitPerm ); + vec_ste( vecR1, 0, &vBits[0] ); + vec_ste( vecR1, 4, &vBits[0] ); + vec_ste( vecR1, 8, &vBits[0] ); + vec_ste( vecR1, 12, &vBits[0] ); + + cullBits[i] = vBits[0]; + cullBits[i+1] = vBits[1]; + cullBits[i+2] = vBits[2]; + cullBits[i+3] = vBits[3]; + } + + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + // they check if the sign bit is set by casting as long and shifting right 31 places. + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + + +#endif /*DRAWVERT_PADDED */ + +#ifndef DRAWVERT_PADDED +/* +============ +idSIMD_AltiVec::OverlayPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + + float p0x, p0y, p0z, p0d; + float p1x, p1y, p1z, p1d; + + const float *planePtr = planes[0].ToFloatPtr(); + const float *vertPtr = verts[0].xyz.ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector unsigned char vecPerm; + vector float zeroVector = (vector float)(0); + + p0x = *(planePtr + 0); + p0y = *(planePtr + 1); + p0z = *(planePtr + 2); + p0d = *(planePtr + 3); + p1x = *(planePtr + 4); + p1y = *(planePtr + 5); + p1z = *(planePtr + 6); + p1d = *(planePtr + 7); + + // populate the planes + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 31, planePtr ); + vecPlane1 = vec_perm( v1, v2, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane0 ); + v1 = vec_mergeh( vecPlane1, vecPlane1 ); + v2 = vec_mergel( vecPlane0, vecPlane0 ); + v3 = vec_mergel( vecPlane1, vecPlane1); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float oneVector = (vector float)(1); + + vector float vecSum1, vecSum2, vecSum1Inv,vecSum2Inv; + + vector bool int vecCmp1, vecCmp2, vecCmp1Inv, vecCmp2Inv; + vector float negTwoVector = (vector float)(-2); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted1Inv, vecBitShifted2Inv; + vector unsigned int vecShift = (vector unsigned int)( 0, 1, 0, 1 ); + vector unsigned int vecShiftInv = (vector unsigned int)( 2, 3, 2, 3 ); + vector unsigned char vecPermFirstThird = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + + i = 0; + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } + + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + vecXYZ1 = vec_perm( v0, v1, vertPerm1 ); + vecXYZ2 = vec_perm( v2, v3, vertPerm2 ); + vecXYZ3 = vec_perm( v4, v5, vertPerm3 ); + vecXYZ4 = vec_perm( v6, v7, vertPerm4 ); + + // like a splat, but only doing halves + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum1 ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum1 ); + vecSum1 = vec_add( vecSum1, vecPlane3 ); + + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum2 ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum2 ); + vecSum2 = vec_add( vecSum2, vecPlane3 ); + + // store out results + UNALIGNED_STORE2( &texCoords[i][0], vecSum1, vecSum2 ); + + // bit manipulation + vecCmp1 = vec_cmplt( vecSum1, zeroVector ); + vecCmp2 = vec_cmplt( vecSum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + + // store out and write to cullBits + // finally, a use for algebra! 1-x = x + 1 - 2x + vecSum1Inv = vec_madd( vecSum1, negTwoVector, vecSum1 ); + vecSum2Inv = vec_madd( vecSum2, negTwoVector, vecSum2 ); + vecSum1Inv = vec_add( vecSum1Inv, oneVector ); + vecSum2Inv = vec_add( vecSum2Inv, oneVector ); + + // do the same comparisons for the inverted d0/d1 + vecCmp1Inv = vec_cmplt( vecSum1Inv, zeroVector ); + vecCmp2Inv = vec_cmplt( vecSum2Inv, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1Inv = vec_and( vecCmp1Inv, oneIntVector ); + vecCmp2Inv = vec_and( vecCmp2Inv, oneIntVector ); + + // shift them as needed + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift ); + vecBitShifted1Inv = vec_sl( (vector unsigned int)vecCmp1Inv, vecShiftInv ); + vecBitShifted2Inv = vec_sl( (vector unsigned int)vecCmp2Inv, vecShiftInv ); + + // OR them all together. since only 1 bit is set for each value, thats + // the same as adding them. add up d0 + d1 + d0Inv + d1Inv + vector unsigned int vecResult; + vector unsigned int vecResult2; + vector unsigned int vecResult3; + vecResult = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 4 ) ); + + vecResult2 = vec_add( vecBitShifted2, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + // vecResult now holds the values without the inverses yet, so add those + vecResult = vec_perm( vecResult, vecResult2, vecPermFirstThird ); + vecResult2 = vec_add( vecBitShifted1Inv, vec_sld( vecBitShifted1Inv, vecBitShifted1Inv, 4 ) ); + vecResult3 = vec_add( vecBitShifted2Inv, vec_sld( vecBitShifted2Inv, vecBitShifted2Inv, 4 ) ); + vecResult2 = vec_perm( vecResult2, vecResult3, vecPermFirstThird ); + + vecResult = vec_add( vecResult, vecResult2 ); + + //store out results + vecResult = vec_perm( vecResult, vecResult, cullBitPerm ); + vec_ste( vecResult, 0, &cullBitVal[0] ); + vec_ste( vecResult, 4, &cullBitVal[0] ); + vec_ste( vecResult, 8, &cullBitVal[0] ); + vec_ste( vecResult, 12, &cullBitVal[0] ); + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1; + float vx, vy, vz; + + vx = *( vertPtr + (i*DRAWVERT_OFFSET) + 0 ); + vy = *( vertPtr + (i*DRAWVERT_OFFSET) + 1 ); + vz = *( vertPtr + (i*DRAWVERT_OFFSET) + 2 ); + + d0 = p0x * vx + p0y * vy + p0z * vz + p0d; + d1 = p1x * vx + p1y * vy + p1z * vz + p1d; + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = ( d0 >= 0 ) ? 0 : 1; + d0 = 1.0f - d0; + bits |= ( d1 >= 0 ) ? 0 : 1*2; + d1 = 1.0f - d1; + + bits |= ( d0 >= 0 ) ? 0: 1*4; + bits |= ( d1 >= 0 ) ? 0: 1*8; + + cullBits[i] = bits; + } +} +#else + +/* +============ +idSIMD_AltiVec::OverlayPointCull +============ +*/ +void VPCALL idSIMD_AltiVec::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + int i; + + float p0x, p0y, p0z, p0d; + float p1x, p1y, p1z, p1d; + + const float *planePtr = planes[0].ToFloatPtr(); + const float *vertPtr = verts[0].xyz.ToFloatPtr(); + + vector float vecPlane0, vecPlane1, vecPlane2, vecPlane3; + vector float v0, v1, v2, v3, v4, v5, v6, v7; + vector unsigned char vecPerm; + vector float zeroVector = (vector float)(0); + + p0x = *(planePtr + 0); + p0y = *(planePtr + 1); + p0z = *(planePtr + 2); + p0d = *(planePtr + 3); + p1x = *(planePtr + 4); + p1y = *(planePtr + 5); + p1z = *(planePtr + 6); + p1d = *(planePtr + 7); + + // populate the planes + vecPerm = vec_add( vec_lvsl( -1, planePtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, planePtr ); + v1 = vec_ld( 15, planePtr ); + vecPlane0 = vec_perm( v0, v1, vecPerm ); + + v2 = vec_ld( 31, planePtr ); + vecPlane1 = vec_perm( v1, v2, vecPerm ); + + // transpose + v0 = vec_mergeh( vecPlane0, vecPlane0 ); + v1 = vec_mergeh( vecPlane1, vecPlane1 ); + v2 = vec_mergel( vecPlane0, vecPlane0 ); + v3 = vec_mergel( vecPlane1, vecPlane1); + + vecPlane0 = vec_mergeh( v0, v1 ); + vecPlane1 = vec_mergel( v0, v1 ); + vecPlane2 = vec_mergeh( v2, v3 ); + vecPlane3 = vec_mergel( v2, v3 ); + + vector float vecXYZ1, vecXYZ2, vecXYZ3, vecXYZ4; + vector float oneVector = (vector float)(1); + + vector float vecSum1, vecSum2, vecSum1Inv,vecSum2Inv; + + vector bool int vecCmp1, vecCmp2, vecCmp1Inv, vecCmp2Inv; + vector float negTwoVector = (vector float)(-2); + vector unsigned int vecBitShifted1, vecBitShifted2, vecBitShifted1Inv, vecBitShifted2Inv; + vector unsigned int vecShift = (vector unsigned int)( 0, 1, 0, 1 ); + vector unsigned int vecShiftInv = (vector unsigned int)( 2, 3, 2, 3 ); + vector unsigned char vecPermFirstThird = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); + vector bool int oneIntVector = (vector bool int)(1); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + unsigned int cullBitVal[4]; + vector unsigned char cullBitPerm = vec_lvsr( 0, &cullBitVal[0] ); + + i = 0; + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + + vecXYZ1 = vec_ld( 0, vertPtr ); + vecXYZ2 = vec_ld( 0, vertPtr2 ); + vecXYZ3 = vec_ld( 0, vertPtr3 ); + vecXYZ4 = vec_ld( 0, vertPtr4 ); + + // like a splat, but only doing halves + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum1 ); + vecSum1 = vec_madd( vec_perm( vecXYZ1, vecXYZ2, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum1 ); + vecSum1 = vec_add( vecSum1, vecPlane3 ); + + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(0,1,2,3,0,1,2,3,16,17,18,19,16,17,18,19) ), vecPlane0, zeroVector ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(4,5,6,7,4,5,6,7,20,21,22,23,20,21,22,23) ) , vecPlane1, vecSum2 ); + vecSum2 = vec_madd( vec_perm( vecXYZ3, vecXYZ4, (vector unsigned char)(8,9,10,11,8,9,10,11,24,25,26,27,24,25,26,27) ), vecPlane2, vecSum2 ); + vecSum2 = vec_add( vecSum2, vecPlane3 ); + + // store out results + UNALIGNED_STORE2( &texCoords[i][0], vecSum1, vecSum2 ); + + // bit manipulation + vecCmp1 = vec_cmplt( vecSum1, zeroVector ); + vecCmp2 = vec_cmplt( vecSum2, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1 = vec_and( vecCmp1, oneIntVector ); + vecCmp2 = vec_and( vecCmp2, oneIntVector ); + + // store out and write to cullBits + // finally, a use for algebra! 1-x = x + 1 - 2x + vecSum1Inv = vec_madd( vecSum1, negTwoVector, vecSum1 ); + vecSum2Inv = vec_madd( vecSum2, negTwoVector, vecSum2 ); + vecSum1Inv = vec_add( vecSum1Inv, oneVector ); + vecSum2Inv = vec_add( vecSum2Inv, oneVector ); + + // do the same comparisons for the inverted d0/d1 + vecCmp1Inv = vec_cmplt( vecSum1Inv, zeroVector ); + vecCmp2Inv = vec_cmplt( vecSum2Inv, zeroVector ); + + //and it with 1 so we multiply by 1 not 1111's + vecCmp1Inv = vec_and( vecCmp1Inv, oneIntVector ); + vecCmp2Inv = vec_and( vecCmp2Inv, oneIntVector ); + + // shift them as needed + vecBitShifted1 = vec_sl( (vector unsigned int)vecCmp1, vecShift ); + vecBitShifted2 = vec_sl( (vector unsigned int)vecCmp2, vecShift ); + vecBitShifted1Inv = vec_sl( (vector unsigned int)vecCmp1Inv, vecShiftInv ); + vecBitShifted2Inv = vec_sl( (vector unsigned int)vecCmp2Inv, vecShiftInv ); + + // OR them all together. since only 1 bit is set for each value, thats + // the same as adding them. add up d0 + d1 + d0Inv + d1Inv + vector unsigned int vecResult; + vector unsigned int vecResult2; + vector unsigned int vecResult3; + vecResult = vec_add( vecBitShifted1, vec_sld( vecBitShifted1, vecBitShifted1, 4 ) ); + + vecResult2 = vec_add( vecBitShifted2, vec_sld( vecBitShifted2, vecBitShifted2, 4 ) ); + + // vecResult now holds the values without the inverses yet, so add those + vecResult = vec_perm( vecResult, vecResult2, vecPermFirstThird ); + vecResult2 = vec_add( vecBitShifted1Inv, vec_sld( vecBitShifted1Inv, vecBitShifted1Inv, 4 ) ); + vecResult3 = vec_add( vecBitShifted2Inv, vec_sld( vecBitShifted2Inv, vecBitShifted2Inv, 4 ) ); + vecResult2 = vec_perm( vecResult2, vecResult3, vecPermFirstThird ); + + vecResult = vec_add( vecResult, vecResult2 ); + + //store out results + vecResult = vec_perm( vecResult, vecResult, cullBitPerm ); + vec_ste( vecResult, 0, &cullBitVal[0] ); + vec_ste( vecResult, 4, &cullBitVal[0] ); + vec_ste( vecResult, 8, &cullBitVal[0] ); + vec_ste( vecResult, 12, &cullBitVal[0] ); + + cullBits[i] = cullBitVal[0]; + cullBits[i+1] = cullBitVal[1]; + cullBits[i+2] = cullBitVal[2]; + cullBits[i+3] = cullBitVal[3]; + } + + // cleanup + for ( ; i < numVerts; i++ ) { + byte bits; + float d0, d1; + float vx, vy, vz; + + vx = *( vertPtr + (i*DRAWVERT_OFFSET) + 0 ); + vy = *( vertPtr + (i*DRAWVERT_OFFSET) + 1 ); + vz = *( vertPtr + (i*DRAWVERT_OFFSET) + 2 ); + + d0 = p0x * vx + p0y * vy + p0z * vz + p0d; + d1 = p1x * vx + p1y * vy + p1z * vz + p1d; + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = ( d0 >= 0 ) ? 0 : 1; + d0 = 1.0f - d0; + bits |= ( d1 >= 0 ) ? 0 : 1*2; + d1 = 1.0f - d1; + + bits |= ( d0 >= 0 ) ? 0: 1*4; + bits |= ( d1 >= 0 ) ? 0: 1*8; + + cullBits[i] = bits; + } +} + + +#endif /* DRAWVERT_PADDED */ + +#endif /* ENABLE_CULL */ + +#ifdef ENABLE_DERIVE +/* +============ +idSIMD_AltiVec::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_AltiVec::DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // idPlane size + assert( sizeof(idPlane) == PLANE_OFFSET * sizeof(float) ); + int i; + + vector float vecD0, vecD1, vecD2, vecD3, vecD4, vecD5, vecD6, vecD7; + vector float vecVertA, vecVertB, vecVertC; + vector float vecVertA2, vecVertB2, vecVertC2; + vector float vecVertA3, vecVertB3, vecVertC3; + vector float vecVertA4, vecVertB4, vecVertC4; + + vector float vecN, vecN2, vecN3, vecN4; + vector float vecWork1, vecWork2, vecWork3, vecWork4, vecWork5, vecWork6, vecWork7, vecWork8; + vector unsigned char vecPerm1 = (vector unsigned char)(4,5,6,7,8,9,10,11,0,1,2,3,12,13,14,15); + vector unsigned char vecPerm2 = (vector unsigned char)(8,9,10,11,0,1,2,3,4,5,6,7,12,13,14,15); + vector float vecF; + vector float vecF1, vecF2, vecF3, vecF4; + vector float zeroVector = (vector float)(0); + vector float vecNegOne = (vector float)(-1); + vector float vecSecondHalf, vecFirstHalf, vecSecondHalf2, vecFirstHalf2, vecSecondHalf3, vecFirstHalf3, vecFirstHalf4, vecSecondHalf4; + + vector unsigned char vecPermA, vecPermA2, vecPermA3, vecPermA4; + vector unsigned char vecPermB, vecPermB2, vecPermB3, vecPermB4; + vector unsigned char vecPermC, vecPermC2, vecPermC3, vecPermC4; + + vector unsigned char oneVector = (vector unsigned char)(1); + vector float vecLd1, vecLd2, vecLd3, vecLd4, vecLd5, vecLd6; + vector unsigned char vecPermZeroLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + + const float *xyzPtr = verts[0].xyz.ToFloatPtr(); + float *planePtr = planes[0].ToFloatPtr(); + + int j; + for ( j = 0, i = 0; i+11 < numIndexes; i += 12, j += 4 ) { + +#ifndef DRAWVERT_PADDED + // calculate permute vectors to load as needed. these are all + // triangle indexes and are usaully pretty close together but + // not guaranteed to be in any particular order + vecPermA = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermA2 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB2 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC2 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermA3 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB3 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC3 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermA4 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermB4 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ), oneVector ); + vecPermC4 = vec_add( vec_lvsl( -1, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ), oneVector ); +#endif + +#ifndef DRAWVERT_PADDED + // load first A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ); + + vecVertA = vec_perm( vecLd1, vecLd2, vecPermA ); + vecVertB = vec_perm( vecLd3, vecLd4, vecPermB ); + vecVertC = vec_perm( vecLd5, vecLd6, vecPermC ); + + // set the last element to 0 + vecVertA = vec_perm( vecVertA, zeroVector, vecPermZeroLast ); + vecVertB = vec_perm( vecVertB, zeroVector, vecPermZeroLast ); + vecVertC = vec_perm( vecVertC, zeroVector, vecPermZeroLast ); + + // load second A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ); + + vecVertA2 = vec_perm( vecLd1, vecLd2, vecPermA2 ); + vecVertB2 = vec_perm( vecLd3, vecLd4, vecPermB2 ); + vecVertC2 = vec_perm( vecLd5, vecLd6, vecPermC2 ); + + // set the last element to 0 + vecVertA2 = vec_perm( vecVertA2, zeroVector, vecPermZeroLast ); + vecVertB2 = vec_perm( vecVertB2, zeroVector, vecPermZeroLast ); + vecVertC2 = vec_perm( vecVertC2, zeroVector, vecPermZeroLast ); + + // load third A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ); + + vecVertA3 = vec_perm( vecLd1, vecLd2, vecPermA3 ); + vecVertB3 = vec_perm( vecLd3, vecLd4, vecPermB3 ); + vecVertC3 = vec_perm( vecLd5, vecLd6, vecPermC3 ); + + // set the last element to 0 + vecVertA2 = vec_perm( vecVertA2, zeroVector, vecPermZeroLast ); + vecVertB2 = vec_perm( vecVertB2, zeroVector, vecPermZeroLast ); + vecVertC2 = vec_perm( vecVertC2, zeroVector, vecPermZeroLast ); + + // load the fourth A B C + vecLd1 = vec_ld( 0, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 15, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 0, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ); + vecLd4 = vec_ld( 15, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ); + vecLd5 = vec_ld( 0, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ); + vecLd6 = vec_ld( 15, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ); + + vecVertA4 = vec_perm( vecLd1, vecLd2, vecPermA4 ); + vecVertB4 = vec_perm( vecLd3, vecLd4, vecPermB4 ); + vecVertC4 = vec_perm( vecLd5, vecLd6, vecPermC4 ); + + // set the last element to 0 + vecVertA4 = vec_perm( vecVertA4, zeroVector, vecPermZeroLast ); + vecVertB4 = vec_perm( vecVertB4, zeroVector, vecPermZeroLast ); + vecVertC4 = vec_perm( vecVertC4, zeroVector, vecPermZeroLast ); +#else + // load first A B C + vecVertA = vec_ld( 0, xyzPtr + ( indexes[i+0] * DRAWVERT_OFFSET ) ); + vecVertB = vec_ld( 0, xyzPtr + ( indexes[i+1] * DRAWVERT_OFFSET ) ); + vecVertC = vec_ld( 0, xyzPtr + ( indexes[i+2] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA = vec_perm( vecVertA, zeroVector, vecPermZeroLast ); + vecVertB = vec_perm( vecVertB, zeroVector, vecPermZeroLast ); + vecVertC = vec_perm( vecVertC, zeroVector, vecPermZeroLast ); + + // load second A B C + vecVertA2 = vec_ld( 0, xyzPtr + ( indexes[i+3] * DRAWVERT_OFFSET ) ); + vecVertB2 = vec_ld( 0, xyzPtr + ( indexes[i+4] * DRAWVERT_OFFSET ) ); + vecVertC2 = vec_ld( 0, xyzPtr + ( indexes[i+5] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA2 = vec_perm( vecVertA2, zeroVector, vecPermZeroLast ); + vecVertB2 = vec_perm( vecVertB2, zeroVector, vecPermZeroLast ); + vecVertC2 = vec_perm( vecVertC2, zeroVector, vecPermZeroLast ); + + // load third A B C + vecVertA3 = vec_ld( 0, xyzPtr + ( indexes[i+6] * DRAWVERT_OFFSET ) ); + vecVertB3 = vec_ld( 0, xyzPtr + ( indexes[i+7] * DRAWVERT_OFFSET ) ); + vecVertC3 = vec_ld( 0, xyzPtr + ( indexes[i+8] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA3 = vec_perm( vecVertA3, zeroVector, vecPermZeroLast ); + vecVertB3 = vec_perm( vecVertB3, zeroVector, vecPermZeroLast ); + vecVertC3 = vec_perm( vecVertC3, zeroVector, vecPermZeroLast ); + + // load the fourth A B C + vecVertA4 = vec_ld( 0, xyzPtr + ( indexes[i+9] * DRAWVERT_OFFSET ) ); + vecVertB4 = vec_ld( 0, xyzPtr + ( indexes[i+10] * DRAWVERT_OFFSET ) ); + vecVertC4 = vec_ld( 0, xyzPtr + ( indexes[i+11] * DRAWVERT_OFFSET ) ); + + // set the last element to 0 + vecVertA4 = vec_perm( vecVertA4, zeroVector, vecPermZeroLast ); + vecVertB4 = vec_perm( vecVertB4, zeroVector, vecPermZeroLast ); + vecVertC4 = vec_perm( vecVertC4, zeroVector, vecPermZeroLast ); +#endif + // calculate d0 and d1 for each + vecD0 = vec_sub( vecVertB, vecVertA ); + vecD1 = vec_sub( vecVertC, vecVertA ); + + vecD2 = vec_sub( vecVertB2, vecVertA2 ); + vecD3 = vec_sub( vecVertC2, vecVertA2 ); + + vecD4 = vec_sub( vecVertB3, vecVertA3 ); + vecD5 = vec_sub( vecVertC3, vecVertA3 ); + + vecD6 = vec_sub( vecVertB4, vecVertA4 ); + vecD7 = vec_sub( vecVertC4, vecVertA4 ); + + vecWork1 = vec_perm( vecD0, vecD0, vecPerm1 ); + vecWork2 = vec_perm( vecD1, vecD1, vecPerm2 ); + vecWork3 = vec_perm( vecD2, vecD2, vecPerm1 ); + vecWork4 = vec_perm( vecD3, vecD3, vecPerm2 ); + vecWork5 = vec_perm( vecD4, vecD4, vecPerm1 ); + vecWork6 = vec_perm( vecD5, vecD5, vecPerm2 ); + vecWork7 = vec_perm( vecD6, vecD6, vecPerm1 ); + vecWork8 = vec_perm( vecD7, vecD7, vecPerm2 ); + + vecSecondHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecSecondHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecSecondHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecSecondHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecD1, vecD1, vecPerm1 ); + vecWork2 = vec_perm( vecD0, vecD0, vecPerm2 ); + vecWork3 = vec_perm( vecD3, vecD3, vecPerm1 ); + vecWork4 = vec_perm( vecD2, vecD2, vecPerm2 ); + vecWork5 = vec_perm( vecD5, vecD5, vecPerm1 ); + vecWork6 = vec_perm( vecD4, vecD4, vecPerm2 ); + vecWork7 = vec_perm( vecD7, vecD7, vecPerm1 ); + vecWork8 = vec_perm( vecD6, vecD6, vecPerm2 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecN = vec_madd( vecSecondHalf, vecNegOne, vecFirstHalf ); + vecN2 = vec_madd( vecSecondHalf2, vecNegOne, vecFirstHalf2 ); + vecN3 = vec_madd( vecSecondHalf3, vecNegOne, vecFirstHalf3 ); + vecN4 = vec_madd( vecSecondHalf4, vecNegOne, vecFirstHalf4 ); + + // transpose vecNs + vector float v0, v1, v2, v3; + v0 = vec_mergeh( vecN, vecN3 ); + v1 = vec_mergeh( vecN2, vecN4 ); + v2 = vec_mergel( vecN, vecN3 ); + v3 = vec_mergel( vecN2, vecN4 ); + + vecN = vec_mergeh( v0, v1 ); + vecN2 = vec_mergel( v0, v1 ); + vecN3 = vec_mergeh( v2, v3 ); + vecN4 = vec_mergel( v2, v3 ); + + vecF = vec_madd( vecN, vecN, zeroVector ); + vecF = vec_madd( vecN2, vecN2, vecF ); + vecF = vec_madd( vecN3, vecN3, vecF ); + + vecF = ReciprocalSquareRoot( vecF ); + + vecF1 = vec_madd( vecF, vecN, zeroVector ); + vecF2 = vec_madd( vecF, vecN2, zeroVector ); + vecF3 = vec_madd( vecF, vecN3, zeroVector ); + vecF4 = vec_madd( vecF, vecN4, zeroVector ); + + vector float v8, v9, v10, v11; + v8 = vecF1; + v9 = vecF2; + v10 = vecF3; + v11 = vecF4; + + // transpose vecVerts + v0 = vec_mergeh( vecVertA, vecVertA3 ); + v1 = vec_mergeh( vecVertA2, vecVertA4 ); + v2 = vec_mergel( vecVertA, vecVertA3 ); + v3 = vec_mergel( vecVertA2, vecVertA4 ); + + vecVertA = vec_mergeh( v0, v1 ); + vecVertA2 = vec_mergel( v0, v1 ); + vecVertA3 = vec_mergeh( v2, v3 ); + vecVertA4 = vec_mergel( v2, v3 ); + + vector float vecTotals; + vecTotals = vec_madd( vecVertA, v8, zeroVector ); + vecTotals = vec_madd( vecVertA2, v9, vecTotals ); + vecTotals = vec_madd( vecVertA3, v10, vecTotals ); + vecTotals = vec_madd( vecVertA4, v11, vecTotals ); + vecF = vec_madd( vecTotals, vecNegOne, zeroVector ); + + // transpose vecFs + v0 = vec_mergeh( vecF1, vecF3 ); + v1 = vec_mergeh( vecF2, vecF ); + v2 = vec_mergel( vecF1, vecF3 ); + v3 = vec_mergel( vecF2, vecF ); + + vecF1 = vec_mergeh( v0, v1 ); + vecF2 = vec_mergel( v0, v1 ); + vecF3 = vec_mergeh( v2, v3 ); + vecF4 = vec_mergel( v2, v3 ); + + // store results + UNALIGNED_STORE4( planePtr + ( j * PLANE_OFFSET ), vecF1, vecF2, vecF3, vecF4 ); + } + + // cleanup + for ( ; i < numIndexes; i += 3, j++ ) { + const idDrawVert *a, *b, *c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = FastScalarInvSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + //idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes[j].SetNormal( n ); + planes[j].FitThroughPoint( a->xyz ); + } +} + +/* +============ +idSIMD_AltiVec::DeriveTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from all triangles + using the vertex which results in smooth tangents across the mesh. + In the process the triangle planes are calculated as well. + +============ +*/ +void VPCALL idSIMD_AltiVec::DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + int i; + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + idPlane *planesPtr = planes; + for ( i = 0; i < numIndexes; i += 3 ) { + idDrawVert *a, *b, *c; + // unsigned long signBit; + float d0[5], d1[5], area; + idVec3 n, t0, t1; + float f1, f2, f3; + + int v0 = indexes[i + 0]; + int v1 = indexes[i + 1]; + int v2 = indexes[i + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + // normal + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f1 = n.x * n.x + n.y * n.y + n.z * n.z; + + // area sign bit + area = d0[3] * d1[4] - d0[4] * d1[3]; + + // first tangent + t0[0] = d0[0] * d1[4] - d0[4] * d1[0]; + t0[1] = d0[1] * d1[4] - d0[4] * d1[1]; + t0[2] = d0[2] * d1[4] - d0[4] * d1[2]; + + f2 = t0.x * t0.x + t0.y * t0.y + t0.z * t0.z; + + // second tangent + t1[0] = d0[3] * d1[0] - d0[0] * d1[3]; + t1[1] = d0[3] * d1[1] - d0[1] * d1[3]; + t1[2] = d0[3] * d1[2] - d0[2] * d1[3]; + + f3 = t1.x * t1.x + t1.y * t1.y + t1.z * t1.z; + + // Behold! The power of the pipeline + FastScalarInvSqrt_x3( &f1, &f2, &f3 ); +#ifdef PPC_INTRINSICS + f2 = __fsel( area, f2, -f2 ); + f3 = __fsel( area, f3, -f3 ); +#else + f2 = ( area < 0.0f ) ? -f2 : f2; + f3 = ( area < 0.0f ) ? -f3 : f3; +#endif + t0.x *= f2; + t0.y *= f2; + t0.z *= f2; + + n.x *= f1; + n.y *= f1; + n.z *= f1; + + planesPtr->SetNormal( n ); + planesPtr->FitThroughPoint( a->xyz ); + planesPtr++; + + t1.x *= f3; + t1.y *= f3; + t1.z *= f3; + + if ( used[v0] ) { + a->normal += n; + a->tangents[0] += t0; + a->tangents[1] += t1; + } else { + a->normal = n; + a->tangents[0] = t0; + a->tangents[1] = t1; + used[v0] = true; + } + + if ( used[v1] ) { + b->normal += n; + b->tangents[0] += t0; + b->tangents[1] += t1; + } else { + b->normal = n; + b->tangents[0] = t0; + b->tangents[1] = t1; + used[v1] = true; + } + + if ( used[v2] ) { + c->normal += n; + c->tangents[0] += t0; + c->tangents[1] += t1; + } else { + c->normal = n; + c->tangents[0] = t0; + c->tangents[1] = t1; + used[v2] = true; + } + } +} + + +#ifdef DERIVE_UNSMOOTH_DRAWVERT_ALIGNED + +/* +============ +idSIMD_AltiVec::DeriveUnsmoothedTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from a single dominant triangle. +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT +void VPCALL idSIMD_AltiVec::DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) { + + int i; + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // drawverts aligned + assert( IS_16BYTE_ALIGNED( verts[0] ) ); + + vector float vecVertA, vecVertB, vecVertC; + vector float vecVertA2, vecVertB2, vecVertC2; + vector float vecVertA3, vecVertB3, vecVertC3; + vector float vecVertA4, vecVertB4, vecVertC4; + + vector float v0, v1, v2, v3, v4, v5, v6, v7, v8; + vector float vecS0, vecS1, vecS2; + vector float vecS0_2, vecS1_2, vecS2_2; + vector float vecS0_3, vecS1_3, vecS2_3; + vector float vecS0_4, vecS1_4, vecS2_4; + + vector float vecD1, vecD2, vecD3, vecD4, vecD5, vecD6; + vector float vecD7, vecD8, vecD9, vecD10, vecD11, vecD12; + vector float vecT1, vecT1_2, vecT1_3, vecT1_4, vecT2, vecT2_2, vecT2_3, vecT2_4; + vector float vecWork1, vecWork2, vecWork3, vecWork4, vecWork5, vecWork6, vecWork7, vecWork8; + vector float vecN, vecN2, vecN3, vecN4; + + vector unsigned char vecPermN0 = (vector unsigned char)(8,9,10,11,0,1,2,3,4,5,6,7,12,13,14,15); + vector unsigned char vecPermN1 = (vector unsigned char)(4,5,6,7,8,9,10,11,0,1,2,3,12,13,14,15); + vector unsigned char vecPermT0 = (vector unsigned char)(0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3); + vector unsigned char vecPermT1 = (vector unsigned char)(8,9,10,11,8,9,10,11,8,9,10,11,8,9,10,11); + vector float zeroVector = (vector float)(0); + + vector float vecNegOne = (vector float)(-1.0); + + vector float vecStore1, vecStore2, vecStore3; + vector unsigned char vecPermFirstThreeLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermStoreSecond = (vector unsigned char)(4,5,6,7,8,9,10,11,16,17,18,19,20,21,22,23); + vector unsigned char vecPermLeadAndThree = (vector unsigned char)(0,1,2,3,16,17,18,19,20,21,22,23,24,25,26,27); + vector unsigned char vecPermStore2 = (vector unsigned char)(4,5,6,7,8,9,10,11,24,25,26,27,28,29,30,31); + vector unsigned char vecPermStore3 = (vector unsigned char)(4,5,6,7,8,9,10,11,16,17,18,19,20,21,22,23); + vector unsigned char vecPermStore4 = (vector unsigned char)(8,9,10,11,16,17,18,19,20,21,22,23,24,25,26,27); + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + + vector float vecLd1, vecLd2, vecLd3; + vector unsigned char vecPerm0, vecPerm1, vecPerm2, vecPerm3, vecPerm4; + + float *normalPtr = verts[0].normal.ToFloatPtr(); + float *xyzPtr = verts[0].xyz.ToFloatPtr(); + + vector float vecFirstHalf, vecSecondHalf; + vector float vecFirstHalf2, vecSecondHalf2; + vector float vecFirstHalf3, vecSecondHalf3; + vector float vecFirstHalf4, vecSecondHalf4; + + for ( i = 0; i+3 < numVerts; i+=4 ) { + int bOffset1, bOffset2, bOffset3, bOffset4; + int cOffset1, cOffset2, cOffset3, cOffset4; + + bOffset1 = dominantTris[i].v2; + cOffset1 = dominantTris[i].v3; + bOffset2 = dominantTris[i+1].v2; + cOffset2 = dominantTris[i+1].v3; + bOffset3 = dominantTris[i+2].v2; + cOffset3 = dominantTris[i+2].v3; + bOffset4 = dominantTris[i+3].v2; + cOffset4 = dominantTris[i+3].v3; + + vecPerm0 = vec_lvsl( 0, xyzPtr + ( i * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + (i * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + (i * DRAWVERT_OFFSET ) ); + vecVertA = vec_perm( v0, v1, vecPerm0 ); + + vecPerm1 = vec_lvsl( 0, xyzPtr + (bOffset1 * DRAWVERT_OFFSET ) ); + v2 = vec_ld( 0, xyzPtr + ( bOffset1 * DRAWVERT_OFFSET ) ); + v3 = vec_ld( 16, xyzPtr + ( bOffset1 * DRAWVERT_OFFSET ) ); + vecVertB = vec_perm( v2, v3, vecPerm1 ); + + vecPerm2 = vec_lvsl( 0, xyzPtr + ( cOffset1 * DRAWVERT_OFFSET ) ); + v4 = vec_ld( 0, xyzPtr + ( cOffset1 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 16, xyzPtr + ( cOffset1 * DRAWVERT_OFFSET ) ); + vecVertC = vec_perm( v4, v5, vecPerm2 ); + + // put remainder into v2 + v1 = vec_perm( v1, v1, vecPerm0 ); + v3 = vec_perm( v3, v3, vecPerm1 ); + v5 = vec_perm( v5, v5, vecPerm2 ); + + v1 = vec_mergeh( v1, v5 ); + v2 = vec_mergeh( v3, zeroVector ); + v2 = vec_mergeh( v1, v2 ); + v2 = vec_perm( v2, v2, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // load second one + vecPerm0 = vec_lvsl( 0, xyzPtr + ((i+1) * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + ((i+1) * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + ((i+1) * DRAWVERT_OFFSET ) ); + vecVertA2 = vec_perm( v0, v1, vecPerm0 ); + + vecPerm3 = vec_lvsl( 0, xyzPtr + (bOffset2 * DRAWVERT_OFFSET ) ); + v3 = vec_ld( 0, xyzPtr + ( bOffset2 * DRAWVERT_OFFSET ) ); + v4 = vec_ld( 16, xyzPtr + ( bOffset2 * DRAWVERT_OFFSET ) ); + vecVertB2 = vec_perm( v3, v4, vecPerm3 ); + + vecPerm4 = vec_lvsl( 0, xyzPtr + ( cOffset2 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 0, xyzPtr + ( cOffset2 * DRAWVERT_OFFSET ) ); + v6 = vec_ld( 16, xyzPtr + ( cOffset2 * DRAWVERT_OFFSET ) ); + vecVertC2 = vec_perm( v5, v6, vecPerm4 ); + + // put remainder into v3 + v1 = vec_perm( v1, v1, vecPerm0 ); + v4 = vec_perm( v4, v4, vecPerm3 ); + v5 = vec_perm( v6, v6, vecPerm4 ); + + v1 = vec_mergeh( v1, v5 ); + v3 = vec_mergeh( v4, zeroVector ); + v3 = vec_mergeh( v1, v3 ); + v3 = vec_perm( v3, v3, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // load third one + vecPerm0 = vec_lvsl( 0, xyzPtr + ((i+2) * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + ((i+2) * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + ((i+2) * DRAWVERT_OFFSET ) ); + vecVertA3 = vec_perm( v0, v1, vecPerm0 ); + + vecPerm1 = vec_lvsl( 0, xyzPtr + (bOffset3 * DRAWVERT_OFFSET ) ); + v4 = vec_ld( 0, xyzPtr + ( bOffset3 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 16, xyzPtr + ( bOffset3 * DRAWVERT_OFFSET ) ); + vecVertB3 = vec_perm( v4, v5, vecPerm1 ); + + vecPerm2 = vec_lvsl( 0, xyzPtr + ( cOffset3 * DRAWVERT_OFFSET ) ); + v6 = vec_ld( 0, xyzPtr + ( cOffset3 * DRAWVERT_OFFSET ) ); + v7 = vec_ld( 16, xyzPtr + ( cOffset3 * DRAWVERT_OFFSET ) ); + vecVertC3 = vec_perm( v6, v7, vecPerm2 ); + + // put remainder into v4 + v1 = vec_perm( v1, v1, vecPerm0 ); + v5 = vec_perm( v5, v5, vecPerm1 ); + v7 = vec_perm( v7, v7, vecPerm2 ); + + v1 = vec_mergeh( v1, v7 ); + v4 = vec_mergeh( v5, zeroVector ); + v4 = vec_mergeh( v1, v4 ); + v4 = vec_perm( v4, v4, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // load fourth one + vecPerm0 = vec_lvsl( 0, xyzPtr + ((i+3) * DRAWVERT_OFFSET ) ); + v0 = vec_ld( 0, xyzPtr + ((i+3) * DRAWVERT_OFFSET ) ); + v1 = vec_ld( 16, xyzPtr + ((i+3) * DRAWVERT_OFFSET ) ); + vecVertA4 = vec_perm( v0, v1, vecPerm0 ); + + vecPerm3 = vec_lvsl( 0, xyzPtr + (bOffset4 * DRAWVERT_OFFSET ) ); + v5 = vec_ld( 0, xyzPtr + ( bOffset4 * DRAWVERT_OFFSET ) ); + v6 = vec_ld( 16, xyzPtr + ( bOffset4 * DRAWVERT_OFFSET ) ); + vecVertB4 = vec_perm( v5, v6, vecPerm3 ); + + vecPerm4 = vec_lvsl( 0, xyzPtr + ( cOffset4 * DRAWVERT_OFFSET ) ); + v7 = vec_ld( 0, xyzPtr + ( cOffset4 * DRAWVERT_OFFSET ) ); + v8 = vec_ld( 16, xyzPtr + ( cOffset4 * DRAWVERT_OFFSET ) ); + vecVertC4 = vec_perm( v7, v8, vecPerm4 ); + + // put remainder into v5 + v1 = vec_perm( v1, v1, vecPerm0 ); + v6 = vec_perm( v6, v6, vecPerm3 ); + v8 = vec_perm( v8, v8, vecPerm4 ); + + v1 = vec_mergeh( v1, v8 ); + v5 = vec_mergeh( v6, zeroVector ); + v5 = vec_mergeh( v1, v5 ); + v5 = vec_perm( v5, v5, (vector unsigned char)(4,5,6,7,0,1,2,3,8,9,10,11,0,1,2,3) ); + + // remainder vectors look like b->st[1], a->st[1], c->st[1], a->st[1] + + //vecD1 now holds d0, d1, d2, d3 + vecD1 = vec_sub( vecVertB, vecVertA ); + vecD4 = vec_sub( vecVertB2, vecVertA2 ); + vecD7 = vec_sub( vecVertB3, vecVertA3 ); + vecD10 = vec_sub( vecVertB4, vecVertA4 ); + + // vecD2 how holds d5, d6, d7, d8 + vecD2 = vec_sub( vecVertC, vecVertA ); + vecD5 = vec_sub( vecVertC2, vecVertA2 ); + vecD8 = vec_sub( vecVertC3, vecVertA3 ); + vecD11 = vec_sub( vecVertC4, vecVertA4 ); + + // vecD3 now holds d4, crap, d9, crap + vecD3 = vec_sub( v2, vec_sld( v2, v2, 4 ) ); + vecD6 = vec_sub( v3, vec_sld( v3, v3, 4 ) ); + vecD9 = vec_sub( v4, vec_sld( v4, v4, 4 ) ); + vecD12 = vec_sub( v5, vec_sld( v5, v5, 4 ) ); + + // get permute vectors for loading from dt + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i].normalizationScale[0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i+1].normalizationScale[0] ), (vector unsigned char)(1) ); + vecPerm3 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i+2].normalizationScale[0] ), (vector unsigned char)(1) ); + vecPerm4 = vec_add( vec_lvsl( -1, (int*) &dominantTris[i+3].normalizationScale[0] ), (vector unsigned char)(1) ); + + // load S values from dominantTris + v0 = vec_ld( 0, &dominantTris[i].normalizationScale[0] ); + v1 = vec_ld( 11, &dominantTris[i].normalizationScale[0] ); + v2 = vec_ld( 0, &dominantTris[i+1].normalizationScale[0] ); + v3 = vec_ld( 11, &dominantTris[i+1].normalizationScale[0] ); + v4 = vec_ld( 0, &dominantTris[i+2].normalizationScale[0] ); + v5 = vec_ld( 11, &dominantTris[i+2].normalizationScale[0] ); + v6 = vec_ld( 0, &dominantTris[i+3].normalizationScale[0] ); + v7 = vec_ld( 11, &dominantTris[i+3].normalizationScale[0] ); + + v0 = vec_perm( v0, v1, vecPerm1 ); + v2 = vec_perm( v2, v3, vecPerm2 ); + v4 = vec_perm( v4, v5, vecPerm3 ); + v6 = vec_perm( v6, v7, vecPerm4 ); + + vecS0 = vec_splat( v0, 0 ); + vecS1 = vec_splat( v0, 1 ); + vecS2 = vec_splat( v0, 2 ); + + vecS0_2 = vec_splat( v2, 0); + vecS1_2 = vec_splat( v2, 1 ); + vecS2_2 = vec_splat( v2, 2 ); + + vecS0_3 = vec_splat( v4, 0 ); + vecS1_3 = vec_splat( v4, 1 ); + vecS2_3 = vec_splat( v4, 2 ); + + vecS0_4 = vec_splat( v6, 0 ); + vecS1_4 = vec_splat( v6, 1 ); + vecS2_4 = vec_splat( v6, 2 ); + + // do calculation + vecWork1 = vec_perm( vecD2, vecD2, vecPermN1 ); + vecWork2 = vec_perm( vecD1, vecD1, vecPermN0 ); + vecWork3 = vec_perm( vecD5, vecD5, vecPermN1 ); + vecWork4 = vec_perm( vecD4, vecD4, vecPermN0 ); + vecWork5 = vec_perm( vecD8, vecD8, vecPermN1 ); + vecWork6 = vec_perm( vecD7, vecD7, vecPermN0 ); + vecWork7 = vec_perm( vecD11, vecD11, vecPermN1 ); + vecWork8 = vec_perm( vecD10, vecD10, vecPermN0 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecD2, vecD2, vecPermN0 ); + vecWork2 = vec_perm( vecD1, vecD1, vecPermN1 ); + vecWork3 = vec_perm( vecD5, vecD5, vecPermN0 ); + vecWork4 = vec_perm( vecD4, vecD4, vecPermN1 ); + vecWork5 = vec_perm( vecD8, vecD8, vecPermN0 ); + vecWork6 = vec_perm( vecD7, vecD7, vecPermN1 ); + vecWork7 = vec_perm( vecD11, vecD11, vecPermN0 ); + vecWork8 = vec_perm( vecD10, vecD10, vecPermN1 ); + + vecSecondHalf = vec_nmsub( vecWork1, vecWork2, vecFirstHalf ); + vecSecondHalf2 = vec_nmsub( vecWork3, vecWork4, vecFirstHalf2 ); + vecSecondHalf3 = vec_nmsub( vecWork5, vecWork6, vecFirstHalf3 ); + vecSecondHalf4 = vec_nmsub( vecWork7, vecWork8, vecFirstHalf4 ); + + + // calculate N values + vecN = vec_madd( vecS2, vecSecondHalf, zeroVector ); + vecN2 = vec_madd( vecS2_2, vecSecondHalf2, zeroVector ); + vecN3 = vec_madd( vecS2_3, vecSecondHalf3, zeroVector ); + vecN4 = vec_madd( vecS2_4, vecSecondHalf4, zeroVector ); + + // calculate both halves of the calculation for t + vecWork1 = vecD1; + vecWork2 = vec_perm( vecD3, vecD3, vecPermT1 ); + vecWork3 = vecD4; + vecWork4 = vec_perm( vecD6, vecD6, vecPermT1 ); + vecWork5 = vecD7; + vecWork6 = vec_perm( vecD9, vecD9, vecPermT1 ); + vecWork7 = vecD10; + vecWork8 = vec_perm( vecD12, vecD12, vecPermT1 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vecD2; + vecWork2 = vec_perm( vecD3, vecD3, vecPermT0 ); + vecWork3 = vecD5; + vecWork4 = vec_perm( vecD6, vecD6, vecPermT0 ); + vecWork5 = vecD8; + vecWork6 = vec_perm( vecD9, vecD9, vecPermT0 ); + vecWork7 = vecD11; + vecWork8 = vec_perm( vecD12, vecD12, vecPermT0 ); + + vecSecondHalf = vec_nmsub( vecWork1, vecWork2, vecFirstHalf ); + vecSecondHalf2 = vec_nmsub( vecWork3, vecWork4, vecFirstHalf2 ); + vecSecondHalf3 = vec_nmsub( vecWork5, vecWork6, vecFirstHalf3 ); + vecSecondHalf4 = vec_nmsub( vecWork7, vecWork8, vecFirstHalf4 ); + + // calculate T values + vecT1 = vec_madd( vecS0, vecSecondHalf, zeroVector ); + vecT1_2 = vec_madd( vecS0_2, vecSecondHalf2, zeroVector ); + vecT1_3 = vec_madd( vecS0_3, vecSecondHalf3, zeroVector ); + vecT1_4 = vec_madd( vecS0_4, vecSecondHalf4, zeroVector ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + vecWork1 = vecD1; + vecWork2 = vec_perm( vecD2, vecD2, vecPermT2 ); + vecWork3 = vecD4; + vecWork4 = vec_perm( vecD5, vecD5, vecPermT2 ); + vecWork5 = vecD7; + vecWork6 = vec_perm( vecD8, vecD8, vecPermT2 ); + vecWork7 = vecD10; + vecWork8 = vec_perm( vecD11, vecD11, vecPermT2 ); + + vecSecondHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecSecondHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecSecondHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecSecondHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecD1, vecD1, vecPermT2 ); + vecWork2 = vecD2; + vecWork3 = vec_perm( vecD4, vecD4, vecPermT2 ); + vecWork4 = vecD5; + vecWork5 = vec_perm( vecD7, vecD7, vecPermT2 ); + vecWork6 = vecD8; + vecWork7 = vec_perm( vecD10, vecD10, vecPermT2 ); + vecWork8 = vecD11; + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + +#else + vecWork1 = vec_perm( vecN, vecN, vecPermN1 ); + vecWork2 = vec_perm( vecT1, vecT1, vecPermN0 ); + vecWork3 = vec_perm( vecN2, vecN2, vecPermN1 ); + vecWork4 = vec_perm( vecT1_2, vecT1_2, vecPermN0 ); + vecWork5 = vec_perm( vecN3, vecN3, vecPermN1 ); + vecWork6 = vec_perm( vecT1_3, vecT1_3, vecPermN0 ); + vecWork7 = vec_perm( vecN4, vecN4, vecPermN1 ); + vecWork8 = vec_perm( vecT1_4, vecT1_4, vecPermN0 ); + + vecSecondHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecSecondHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecSecondHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecSecondHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); + + vecWork1 = vec_perm( vecN, vecN, vecPermN0 ); + vecWork2 = vec_perm( vecT1, vecT1, vecPermN1 ); + vecWork3 = vec_perm( vecN2, vecN2, vecPermN0 ); + vecWork4 = vec_perm( vecT1_2, vecT1_2, vecPermN1 ); + vecWork5 = vec_perm( vecN3, vecN3, vecPermN0 ); + vecWork6 = vec_perm( vecT1_3, vecT1_3, vecPermN1 ); + vecWork7 = vec_perm( vecN4, vecN4, vecPermN0 ); + vecWork8 = vec_perm( vecT1_4, vecT1_4, vecPermN1 ); + + vecFirstHalf = vec_madd( vecWork1, vecWork2, zeroVector ); + vecFirstHalf2 = vec_madd( vecWork3, vecWork4, zeroVector ); + vecFirstHalf3 = vec_madd( vecWork5, vecWork6, zeroVector ); + vecFirstHalf4 = vec_madd( vecWork7, vecWork8, zeroVector ); +#endif + // finish the calculation + vecSecondHalf = vec_madd( vecSecondHalf, vecNegOne, vecFirstHalf ); + vecSecondHalf2 = vec_madd( vecSecondHalf2, vecNegOne, vecFirstHalf2 ); + vecSecondHalf3 = vec_madd( vecSecondHalf3, vecNegOne, vecFirstHalf3 ); + vecSecondHalf4 = vec_madd( vecSecondHalf4, vecNegOne, vecFirstHalf4 ); + + vecT2 = vec_madd( vecS1, vecSecondHalf, zeroVector ); + vecT2_2 = vec_madd( vecS1_2, vecSecondHalf2, zeroVector ); + vecT2_3 = vec_madd( vecS1_3, vecSecondHalf3, zeroVector ); + vecT2_4 = vec_madd( vecS1_4, vecSecondHalf4, zeroVector ); + + // Store results + + // read values that we need to preserve + vecLd1 = vec_ld( 0, normalPtr + ( i * DRAWVERT_OFFSET ) ); + vecLd2 = vec_ld( 32, normalPtr + ( i * DRAWVERT_OFFSET ) ); + + //generate vectors to store + vecStore1 = vec_perm( vecLd1, vecN, vecPermLeadAndThree ); + vecStore2 = vec_perm( vecT1, vecT2, vecPermFirstThreeLast ); + vecStore3 = vec_perm( vecT2, vecLd2, vecPermStore2 ); + + // store out results + ALIGNED_STORE3( normalPtr + ( i * DRAWVERT_OFFSET ), vecStore1, vecStore2, vecStore3 ); + + // read values that we need to preserve + vecLd3 = vec_ld( 32, normalPtr + ( (i+1) * DRAWVERT_OFFSET )); + + // generate vectors to store + vecStore1 = vec_perm( vecN2, vecT1_2, vecPermFirstThreeLast ); + vecStore2 = vec_perm( vecT1_2, vecT2_2, vecPermStoreSecond ); + vecStore3 = vec_perm( vecT2_2, vecLd3, (vector unsigned char)(8,9,10,11,20,21,22,23,24,25,26,27,28,29,30,31) ); + + // instead of doing permute, shift it where it needs to be and use vec_ste + // store out vectors + ALIGNED_STORE3( normalPtr + ((i+1) * DRAWVERT_OFFSET), vecStore1, vecStore2, vecStore3 ); + + // read values that we need to preserve + vecLd1 = vec_ld( 0, normalPtr + ( (i+2) * DRAWVERT_OFFSET ) ); + + // generate vectors to store + vecStore1 = vec_perm( vecLd1, vecN3, vecPermFirstThreeLast ); + vecStore2 = vec_perm( vecN3, vecT1_3, vecPermStore3 ); + vecStore3 = vec_perm( vecT1_3, vecT2_3, vecPermStore4 ); + + // store out vectors + ALIGNED_STORE3( normalPtr + ((i+2) * DRAWVERT_OFFSET), vecStore1, vecStore2, vecStore3 ); + + // read values that we need to preserve + vecLd2 = vec_ld( 0, normalPtr + ((i+3) * DRAWVERT_OFFSET ) ); + vecLd3 = vec_ld( 32, normalPtr + ((i+3) * DRAWVERT_OFFSET ) ); + + // generate vectors to store + vecStore1 = vec_perm( vecLd2, vecN4, vecPermHalves ); + vecStore2 = vec_perm( vecN4, vecT1_4, vecPermStore4 ); + vecStore3 = vec_perm( vecT2_4, vecLd3, vecPermFirstThreeLast ); + + // store out vectors + ALIGNED_STORE3( normalPtr + ((i+3) * DRAWVERT_OFFSET ), vecStore1, vecStore2, vecStore3 ); + } + + // cleanup + for ( ; i < numVerts; i++ ) { + idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + + d9 = c->st[1] - a->st[1]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } +} + +#else +/* +============ +idSIMD_AltiVec::DeriveUnsmoothedTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from a single dominant triangle. +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT + +void VPCALL idSIMD_AltiVec::DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) { + int i; + + for ( i = 0; i < numVerts; i++ ) { + idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + + d9 = c->st[1] - a->st[1]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } + +} +#endif /* DERIVE_UNSMOOTH_DRAWVERT_ALIGNED */ + +/* +============ +idSIMD_AltiVec::NormalizeTangents + + Normalizes each vertex normal and projects and normalizes the + tangent vectors onto the plane orthogonal to the vertex normal. +============ +*/ +void VPCALL idSIMD_AltiVec::NormalizeTangents( idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + + float *addr = verts[0].normal.ToFloatPtr(); + float *tAddr = verts[0].tangents[0].ToFloatPtr(); + + // v0 through v3 maintain originally loaded values so we don't take + // as much hit for unaligned stores + vector float v0, v1, v2, v3; + // v5 through v8 are the "working" values of the vectors + vector float v5, v6, v7, v8; + // working values + vector float vec1T0, vec1T1, vec2T0, vec2T1, vec3T0, vec3T1, vec4T0, vec4T1; + vector float vecSum, vecTSum1, vecTSum2, tempSum, tempSum2, tempSum3; + vector float vecF, vecF2; + vector float vecTemp, vecTemp2, vecTemp3, vecTemp4; + + register vector float zeroVector = (vector float)(0.0); + + vector unsigned char vecPermHalves = (vector unsigned char)(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23); + vector unsigned char vecPermLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vecPermSplatFirstWithZero = (vector unsigned char)(0,1,2,3,0,1,2,3,0,1,2,3,16,17,18,19); + vector unsigned char vecPerm0, vecPerm1, vecPerm2, vecPerm3; + vector unsigned char storePerm0, storePerm1, storePerm2, storePerm3; + + vector float vecTan11, vecTan12, vecTan13, vecTan21, vecTan22, vecTan23; + vector float vecTan31, vecTan32, vecTan33, vecTan41, vecTan42, vecTan43; + + vector unsigned char vec1T0Perm, vec1T1Perm, vec2T0Perm, vec2T1Perm, vec3T0Perm, vec3T1Perm, vec4T0Perm, vec4T1Perm; + vector unsigned char storeT11, storeT12, storeT21, storeT22, storeT31, storeT32; + vector unsigned char storeT41, storeT42; + + int i = 0; + + if ( i+3 < numVerts ) { + // for loading normal from idDrawVert + vecPerm0 = vec_add( vec_lvsl( -1, addr ), (vector unsigned char)(1) ); + vecPerm1 = vec_add( vec_lvsl( -1, addr + ( 1 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, addr + ( 2 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vecPerm3 = vec_add( vec_lvsl( -1, addr + ( 3 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + + // for loading tangents from idDrawVert + vec1T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 0 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec1T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 0 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + vec2T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 1 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec2T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 1 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + vec3T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 2 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec3T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 2 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + vec4T0Perm = vec_add( vec_lvsl( -1, tAddr + ( 3 * DRAWVERT_OFFSET ) ), (vector unsigned char)(1) ); + vec4T1Perm = vec_add( vec_lvsl( -1, tAddr + ( 3 * DRAWVERT_OFFSET ) + 3 ), (vector unsigned char)(1) ); + + // generate permute vectors to store normals + storePerm0 = vec_lvsr( 0, addr ); + storePerm1 = vec_lvsr( 0, addr + ( 1 * DRAWVERT_OFFSET ) ); + storePerm2 = vec_lvsr( 0, addr + ( 2 * DRAWVERT_OFFSET ) ); + storePerm3 = vec_lvsr( 0, addr + ( 3 * DRAWVERT_OFFSET ) ); + + // generate permute vectors to store tangents + storeT11 = vec_lvsr( 0, tAddr + ( 0 * DRAWVERT_OFFSET ) ); + storeT12 = vec_lvsr( 12, tAddr + ( 0 * DRAWVERT_OFFSET ) ); + + storeT21 = vec_lvsr( 0, tAddr + ( 1 * DRAWVERT_OFFSET ) ); + storeT22 = vec_lvsr( 12, tAddr + ( 1 * DRAWVERT_OFFSET ) ); + + storeT31 = vec_lvsr( 0, tAddr + ( 2 * DRAWVERT_OFFSET ) ); + storeT32 = vec_lvsr( 12, tAddr + ( 2 * DRAWVERT_OFFSET ) ); + + storeT41 = vec_lvsr( 0, tAddr + ( 3 * DRAWVERT_OFFSET ) ); + storeT42 = vec_lvsr( 12, tAddr + ( 3 * DRAWVERT_OFFSET ) ); + } + + for ( ; i+3 < numVerts; i+=4 ) { + + // load normals + vector float vecNormal11 = vec_ld( 0, addr + ( i * DRAWVERT_OFFSET ) ); + vector float vecNormal12 = vec_ld( 15, addr + ( i * DRAWVERT_OFFSET ) ); + v0 = vec_perm( vecNormal11, vecNormal12, vecPerm0 ); + + vector float vecNormal21 = vec_ld( 0, addr + ((i+1) * DRAWVERT_OFFSET ) ); + vector float vecNormal22 = vec_ld( 15, addr + ((i+1) * DRAWVERT_OFFSET ) ); + v1 = vec_perm( vecNormal21, vecNormal22, vecPerm1 ); + + vector float vecNormal31 = vec_ld( 0, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + vector float vecNormal32 = vec_ld( 15, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + v2 = vec_perm( vecNormal31, vecNormal32, vecPerm2 ); + + vector float vecNormal41 = vec_ld( 0, addr + ((i+3) * DRAWVERT_OFFSET ) ); + vector float vecNormal42 = vec_ld( 15, addr + ((i+3) * DRAWVERT_OFFSET ) ); + v3 = vec_perm( vecNormal41, vecNormal42, vecPerm3 ); + + // zero out the last element of each useless vector + v0 = vec_perm( v0, zeroVector, vecPermLast ); + v1 = vec_perm( v1, zeroVector, vecPermLast ); + v2 = vec_perm( v2, zeroVector, vecPermLast ); + v3 = vec_perm( v3, zeroVector, vecPermLast ); + + // got 4 vectors in v0 through v3, sum them each accross + // and put into one vector + vecTemp = vec_madd( v0, v0, zeroVector ); + + vecSum = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + vecSum = vec_add( vecSum, vec_sld( vecSum, vecSum, 4 ) ); + // element 0 of vecSum now has sum of v0 + + vecTemp2 = vec_madd( v1, v1, zeroVector ); + tempSum = vec_add( vecTemp2, vec_sld( vecTemp2, vecTemp2, 8 ) ); + tempSum = vec_add( tempSum, vec_sld( tempSum, tempSum, 4 ) ); + // put this into vecSum + vecSum = vec_mergeh( vecSum, tempSum ); + + vecTemp3 = vec_madd( v2, v2, zeroVector ); + tempSum = vec_add( vecTemp3, vec_sld( vecTemp3, vecTemp3, 8 ) ); + tempSum = vec_add( tempSum, vec_sld( tempSum, tempSum, 4 ) ); + // put this into vecSum + vecSum = vec_perm( vecSum, tempSum, vecPermHalves ); + + vecTemp4 = vec_madd( v3, v3, zeroVector ); + tempSum = vec_add( vecTemp4, vec_sld( vecTemp4, vecTemp4, 8 ) ); + tempSum = vec_add( tempSum, vec_sld( tempSum, tempSum, 4 ) ); + // put this into vecSum + vecSum = vec_perm( vecSum, tempSum, vecPermLast ); + + // take reciprocal square roots of these + vecF = ReciprocalSquareRoot( vecSum ); + + // multiply each vector by f + v5 = vec_madd( v0, vec_splat( vecF, 0 ), zeroVector ); + v6 = vec_madd( v1, vec_splat( vecF, 1 ), zeroVector ); + v7 = vec_madd( v2, vec_splat( vecF, 2 ), zeroVector ); + v8 = vec_madd( v3, vec_splat( vecF, 3 ), zeroVector ); + + // load tangents as unaligned + vecTan11 = vec_ld( 0, tAddr + ( i * DRAWVERT_OFFSET ) ); + vecTan12 = vec_ld( 11, tAddr + ( i * DRAWVERT_OFFSET ) ); + vecTan13 = vec_ld( 23, tAddr + ( i * DRAWVERT_OFFSET ) ); + + vecTan21 = vec_ld( 0, tAddr + ( (i+1) * DRAWVERT_OFFSET ) ); + vecTan22 = vec_ld( 11, tAddr + ( (i+1) * DRAWVERT_OFFSET ) ); + vecTan23 = vec_ld( 23, tAddr + ( (i+1) * DRAWVERT_OFFSET ) ); + + vecTan31 = vec_ld( 0, tAddr + ( (i+2) * DRAWVERT_OFFSET ) ); + vecTan32 = vec_ld( 11, tAddr + ( (i+2) * DRAWVERT_OFFSET ) ); + vecTan33 = vec_ld( 23, tAddr + ( (i+2) * DRAWVERT_OFFSET ) ); + + vecTan41 = vec_ld( 0, tAddr + ( (i+3) * DRAWVERT_OFFSET ) ); + vecTan42 = vec_ld( 11, tAddr + ( (i+3) * DRAWVERT_OFFSET ) ); + vecTan43 = vec_ld( 23, tAddr + ( (i+3) * DRAWVERT_OFFSET ) ); + + vec1T0 = vec_perm( vecTan11, vecTan12, vec1T0Perm ); + vec1T1 = vec_perm( vecTan12, vecTan13, vec1T1Perm ); + vec2T0 = vec_perm( vecTan21, vecTan22, vec2T0Perm ); + vec2T1 = vec_perm( vecTan22, vecTan23, vec2T1Perm ); + vec3T0 = vec_perm( vecTan31, vecTan32, vec3T0Perm ); + vec3T1 = vec_perm( vecTan32, vecTan33, vec3T1Perm ); + vec4T0 = vec_perm( vecTan41, vecTan42, vec4T0Perm ); + vec4T1 = vec_perm( vecTan42, vecTan43, vec4T1Perm ); + + //zero out last element of tangents + vec1T0 = vec_perm( vec1T0, zeroVector, vecPermLast ); + vec1T1 = vec_perm( vec1T1, zeroVector, vecPermLast ); + vec2T0 = vec_perm( vec2T0, zeroVector, vecPermLast ); + vec2T1 = vec_perm( vec2T1, zeroVector, vecPermLast ); + vec3T0 = vec_perm( vec3T0, zeroVector, vecPermLast ); + vec3T1 = vec_perm( vec3T1, zeroVector, vecPermLast ); + vec4T0 = vec_perm( vec4T0, zeroVector, vecPermLast ); + vec4T1 = vec_perm( vec4T1, zeroVector, vecPermLast ); + + // all tangents[0] + tempSum = zeroVector; + tempSum = vec_madd( vec1T0, v5, tempSum ); + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + // put tempSum splatted accross vecTSum1 + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v5, zeroVector ); + + //vec1T0 now contains what needs to be rsqrt'd and multiplied by f + vec1T0 = vec_sub( vec1T0, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec2T0, v6, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v6, zeroVector ); + vec2T0 = vec_sub( vec2T0, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec3T0, v7, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v7, zeroVector ); + vec3T0 = vec_sub( vec3T0, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec4T0, v8, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v8, zeroVector ); + vec4T0 = vec_sub( vec4T0, vecTSum1 ); + + // all tangents[1] + tempSum = zeroVector; + tempSum = vec_madd( vec1T1, v5, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v5, zeroVector ); + + //vec1T0 now contains what needs to be rsqrt'd and multiplied by f + vec1T1 = vec_sub( vec1T1, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec2T1, v6, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v6, zeroVector ); + vec2T1 = vec_sub( vec2T1, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec3T1, v7, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v7, zeroVector ); + vec3T1 = vec_sub( vec3T1, vecTSum1 ); + + tempSum = zeroVector; + tempSum = vec_madd( vec4T1, v8, tempSum ); + + //sum accross tempSum + vecTSum1 = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + vecTSum1 = vec_perm( vecTSum1, zeroVector, vecPermSplatFirstWithZero ); + vecTSum1 = vec_madd( vecTSum1, v8, zeroVector ); + vec4T1 = vec_sub( vec4T1, vecTSum1 ); + + + // sum accross vectors and put into one vector + vecTemp = vec_madd( vec1T0, vec1T0, zeroVector ); + vecTSum1 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + vecTSum1 = vec_add( vecTSum1, vec_sld( vecTSum1, vecTSum1, 4 ) ); + + // element 0 of vecSum now has sum of v0 + vecTemp = vec_madd( vec2T0, vec2T0, zeroVector ); + tempSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum2 = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 4 ) ); + // put this into vecSum + vecTemp = vec_madd( vec3T0, vec3T0, zeroVector ); + vecTSum1 = vec_mergeh( vecTSum1, tempSum2 ); + tempSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum2 = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 4 ) ); + // put this into vecSum + vecTSum1 = vec_perm( vecTSum1, tempSum2, vecPermHalves ); + vecTemp = vec_madd( vec4T0, vec4T0, zeroVector ); + tempSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum2 = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 4 ) ); + // put this into vecSum + vecTSum1 = vec_perm( vecTSum1, tempSum2, vecPermLast ); + + vecTemp = vec_madd( vec1T1, vec1T1, zeroVector ); + vecTSum2 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + vecTSum2 = vec_add( vecTSum2, vec_sld( vecTSum2, vecTSum2, 4 ) ); + // element 0 of vecSum now has sum of v0 + vecTemp = vec_madd( vec2T1, vec2T1, zeroVector ); + tempSum3 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum3 = vec_add( tempSum3, vec_sld( tempSum3, tempSum3, 4 ) ); + // put this into vecSum + vecTSum2 = vec_mergeh( vecTSum2, tempSum3 ); + vecTemp = vec_madd( vec3T1, vec3T1, zeroVector ); + tempSum3 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum3 = vec_add( tempSum3, vec_sld( tempSum3, tempSum3, 4 ) ); + // put this into vecSum + vecTSum2 = vec_perm( vecTSum2, tempSum3, vecPermHalves ); + vecTemp = vec_madd( vec4T1, vec4T1, zeroVector ); + tempSum3 = vec_add( vecTemp, vec_sld( vecTemp, vecTemp, 8 ) ); + tempSum3 = vec_add( tempSum3, vec_sld( tempSum3, tempSum3, 4 ) ); + // put this into vecSum + vecTSum2 = vec_perm( vecTSum2, tempSum3, vecPermLast ); + + // tangents[0] + vecF = ReciprocalSquareRoot( vecTSum1 ); + // tangents[1] + vecF2 = ReciprocalSquareRoot( vecTSum2 ); + + // multiply each tangent vector by f + + vec1T0 = vec_madd( vec1T0, vec_splat( vecF, 0 ), zeroVector ); + vec2T0 = vec_madd( vec2T0, vec_splat( vecF, 1 ), zeroVector ); + vec3T0 = vec_madd( vec3T0, vec_splat( vecF, 2 ), zeroVector ); + vec4T0 = vec_madd( vec4T0, vec_splat( vecF, 3 ), zeroVector ); + + vec1T1 = vec_madd( vec1T1, vec_splat( vecF2, 0 ), zeroVector ); + vec2T1 = vec_madd( vec2T1, vec_splat( vecF2, 1 ), zeroVector ); + vec3T1 = vec_madd( vec3T1, vec_splat( vecF2, 2 ), zeroVector ); + vec4T1 = vec_madd( vec4T1, vec_splat( vecF2, 3 ), zeroVector ); + + // rotate input data + v5 = vec_perm( v5, v5, storePerm0 ); + v6 = vec_perm( v6, v6, storePerm1 ); + v7 = vec_perm( v7, v7, storePerm2 ); + v8 = vec_perm( v8, v8, storePerm3 ); + + vec_ste( v5, 0, addr + ( (i+0) * DRAWVERT_OFFSET ) ); + vec_ste( v5, 4, addr + ( (i+0) * DRAWVERT_OFFSET ) ); + vec_ste( v5, 8, addr + ( (i+0) * DRAWVERT_OFFSET ) ); + + vec_ste( v6, 0, addr + ( (i+1) * DRAWVERT_OFFSET ) ); + vec_ste( v6, 4, addr + ( (i+1) * DRAWVERT_OFFSET ) ); + vec_ste( v6, 8, addr + ( (i+1) * DRAWVERT_OFFSET ) ); + + vec_ste( v7, 0, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + vec_ste( v7, 4, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + vec_ste( v7, 8, addr + ( (i+2) * DRAWVERT_OFFSET ) ); + + vec_ste( v8, 0, addr + ( (i+3) * DRAWVERT_OFFSET ) ); + vec_ste( v8, 4, addr + ( (i+3) * DRAWVERT_OFFSET ) ); + vec_ste( v8, 8, addr + ( (i+3) * DRAWVERT_OFFSET ) ); + + // store tangents[0] and tangents[1] + vec1T0 = vec_perm( vec1T0, vec1T0, storeT11 ); + vec1T1 = vec_perm( vec1T1, vec1T1, storeT12 ); + + vec_ste( vec1T0, 0, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T0, 4, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T0, 8, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T1, 12, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T1, 16, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + vec_ste( vec1T1, 20, tAddr + ((i+0) * DRAWVERT_OFFSET ) ); + + // store second tangents[0] and tangents[1] + vec2T0 = vec_perm( vec2T0, vec2T0, storeT21 ); + vec2T1 = vec_perm( vec2T1, vec2T1, storeT22 ); + + vec_ste( vec2T0, 0, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T0, 4, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T0, 8, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T1, 12, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T1, 16, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + vec_ste( vec2T1, 20, tAddr + ((i+1) * DRAWVERT_OFFSET ) ); + + // store third tangents[0] and tangents[1] + vec3T0 = vec_perm( vec3T0, vec3T0, storeT31 ); + vec3T1 = vec_perm( vec3T1, vec3T1, storeT32 ); + + vec_ste( vec3T0, 0, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T0, 4, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T0, 8, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T1, 12, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T1, 16, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + vec_ste( vec3T1, 20, tAddr + ((i+2) * DRAWVERT_OFFSET ) ); + + // store fourth tangents[0] and tangents[1] + vec4T0 = vec_perm( vec4T0, vec4T0, storeT41 ); + vec4T1 = vec_perm( vec4T1, vec4T1, storeT42 ); + + vec_ste( vec4T0, 0, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T0, 4, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T0, 8, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T1, 12, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T1, 16, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + vec_ste( vec4T1, 20, tAddr + ((i+3) * DRAWVERT_OFFSET ) ); + } + + // cleanup + for ( ; i < numVerts; i++ ) { + idVec3 &v = verts[i].normal; + float f; + + //f = idMath::RSqrt( v.x * v.x + v.y * v.y + v.z * v.z ); + f = FastScalarInvSqrt( v.x * v.x + v.y * v.y + v.z * v.z ); + v.x *= f; v.y *= f; v.z *= f; + + for ( int j = 0; j < 2; j++ ) { + idVec3 &t = verts[i].tangents[j]; + + t -= ( t * v ) * v; + // f = idMath::RSqrt( t.x * t.x + t.y * t.y + t.z * t.z ); + f = FastScalarInvSqrt( t.x * t.x + t.y * t.y + t.z * t.z ); + t.x *= f; t.y *= f; t.z *= f; + } + } +} +#endif /* ENABLE_DERIVE */ + +#ifdef ENABLE_CREATE + +/* +============ +idSIMD_AltiVec::CreateTextureSpaceLightVectors + + Calculates light vectors in texture space for the given triangle vertices. + For each vertex the direction towards the light origin is projected onto texture space. + The light vectors are only calculated for the vertices referenced by the indexes. +============ +*/ + +void VPCALL idSIMD_AltiVec::CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + int i; + for ( i = 0; i+7 < numIndexes; i+= 8 ) { + used[indexes[i]] = true; + used[indexes[i+1]] = true; + used[indexes[i+2]] = true; + used[indexes[i+3]] = true; + used[indexes[i+4]] = true; + used[indexes[i+5]] = true; + used[indexes[i+6]] = true; + used[indexes[i+7]] = true; + } + + for ( ; i < numIndexes; i++ ) { + used[indexes[i]] = true; + } + + for ( i = 0; i+1 < numVerts; i+=2 ) { + + const idDrawVert *v = &verts[i]; + const idDrawVert *v2 = &verts[i+1]; + + float x, y, z; + float x2, y2, z2; + idVec3 lightDir, lightDir2; + + lightDir[0] = lightOrigin[0] - v->xyz[0]; + lightDir[1] = lightOrigin[1] - v->xyz[1]; + lightDir[2] = lightOrigin[2] - v->xyz[2]; + + lightDir2[0] = lightOrigin[0] - v2->xyz[0]; + lightDir2[1] = lightOrigin[1] - v2->xyz[1]; + lightDir2[2] = lightOrigin[2] - v2->xyz[2]; + + x = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + y = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + z = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + + x2 = lightDir2[0] * v2->tangents[0][0] + lightDir2[1] * v2->tangents[0][1] + lightDir2[2] * v2->tangents[0][2]; + y2 = lightDir2[0] * v2->tangents[1][0] + lightDir2[1] * v2->tangents[1][1] + lightDir2[2] * v2->tangents[1][2]; + z2 = lightDir2[0] * v2->normal[0] + lightDir2[1] * v2->normal[1] + lightDir2[2] * v2->normal[2]; + + if ( used[i] ) { + lightVectors[i][0] = x; + lightVectors[i][1] = y; + lightVectors[i][2] = z; + } + + if ( used[i+1] ) { + lightVectors[i+1][0] = x2; + lightVectors[i+1][1] = y2; + lightVectors[i+1][2] = z2; + } + } + + // cleanup + for ( ; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + idVec3 lightDir; + + lightDir[0] = lightOrigin[0] - v->xyz[0]; + lightDir[1] = lightOrigin[1] - v->xyz[1]; + lightDir[2] = lightOrigin[2] - v->xyz[2]; + + lightVectors[i][0] = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + lightVectors[i][1] = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + lightVectors[i][2] = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + } +} + +#if 1 +/* +============ +idSIMD_AltiVec::CreateSpecularTextureCoords + + Calculates specular texture coordinates for the given triangle vertices. + For each vertex the normalized direction towards the light origin is added to the + normalized direction towards the view origin and the result is projected onto texture space. + The texture coordinates are only calculated for the vertices referenced by the indexes. +============ +*/ +void VPCALL idSIMD_AltiVec::CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + int i; + for ( i = 0; i+7 < numIndexes; i+= 8 ) { + used[indexes[i]] = true; + used[indexes[i+1]] = true; + used[indexes[i+2]] = true; + used[indexes[i+3]] = true; + used[indexes[i+4]] = true; + used[indexes[i+5]] = true; + used[indexes[i+6]] = true; + used[indexes[i+7]] = true; + } + + for ( ; i < numIndexes; i++ ) { + used[indexes[i]] = true; + } + + // load lightOrigin and viewOrigin into vectors + const float *lightOriginPtr = lightOrigin.ToFloatPtr(); + const float *viewOriginPtr = viewOrigin.ToFloatPtr(); + vector unsigned char permVec = vec_lvsl( 0, lightOriginPtr ); + vector unsigned char permVec2 = vec_lvsl( 0, viewOriginPtr ); + vector float v0 = vec_ld( 0, lightOriginPtr ); + vector float v1 = vec_ld( 15, lightOriginPtr ); + vector float v2 = vec_ld( 0, viewOriginPtr ); + vector float v3 = vec_ld( 15, viewOriginPtr ); + vector float vecLightOrigin = vec_perm( v0, v1, permVec ); + vector float vecViewOrigin = vec_perm( v2, v3, permVec2 ); + const vector float zeroVector = (vector float)(0); + int index; + + for ( index = 0; index+1 < numVerts; index+=2 ) { + const float *vertPtr = verts[index].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[index+1].xyz.ToFloatPtr(); + + permVec = vec_add( vec_lvsl( -1, vertPtr ), (vector unsigned char)(1) ); + permVec2 = vec_add( vec_lvsl( -1, vertPtr2 ), (vector unsigned char)(1) ); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + vector float v2 = vec_ld( 31, vertPtr ); + vector float v3 = vec_ld( 47, vertPtr ); + vector float v4 = vec_ld( 63, vertPtr ); + + vector float v5 = vec_ld( 0, vertPtr2 ); + vector float v6 = vec_ld( 15, vertPtr2 ); + vector float v7 = vec_ld( 31, vertPtr2 ); + vector float v8 = vec_ld( 47, vertPtr2 ); + vector float v9 = vec_ld( 63, vertPtr2 ); + + // figure out what values go where + vector float vecXYZ = vec_perm( v0, v1, permVec ); + vector float vecNormal = vec_perm( v1, v2, permVec ); + vecNormal = vec_sld( vecNormal, vecNormal, 4 ); + const vector float vecTangent0 = vec_perm( v2, v3, permVec ); + permVec = vec_add( permVec, (vector unsigned char)(-4) ); //shift permute right 3 elements + const vector float vecTangent1 = vec_perm( v3, v4, permVec ); + + vector float vecXYZ2 = vec_perm( v5, v6, permVec2 ); + vector float vecNormal2 = vec_perm( v6, v7, permVec2 ); + vecNormal2 = vec_sld( vecNormal2, vecNormal2, 4 ); + const vector float vecTangent02 = vec_perm( v7, v8, permVec2 ); + permVec2 = vec_add( permVec2, (vector unsigned char)(-4) ); + const vector float vecTangent12 = vec_perm( v8, v9, permVec2 ); + + // calculate lightDir + vector float vecLightDir = vec_sub( vecLightOrigin, vecXYZ ); + vector float vecViewDir = vec_sub( vecViewOrigin, vecXYZ ); + + vector float vecLightDir2 = vec_sub( vecLightOrigin, vecXYZ2 ); + vector float vecViewDir2 = vec_sub( vecViewOrigin, vecXYZ2 ); + + // calculate distance + vector float vecTempLight = vec_madd( vecLightDir, vecLightDir, zeroVector ); + vector float vecTempView = vec_madd( vecViewDir, vecViewDir, zeroVector ); + + vector float vecTempLight2 = vec_madd( vecLightDir2, vecLightDir2, zeroVector ); + vector float vecTempView2 = vec_madd( vecViewDir2, vecViewDir2, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum = vec_add( vecTempLight, vec_sld( vecTempLight, vecTempLight, 4 ) ); + vecTempLight = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vector float tempSum2 = vec_add( vecTempView, vec_sld( vecTempView, vecTempView, 4 ) ); + vecTempView = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 8 ) ); + + vector float tempSum4 = vec_add( vecTempLight2, vec_sld( vecTempLight2, vecTempLight2, 4 ) ); + vecTempLight2 = vec_add( tempSum4, vec_sld( tempSum4, tempSum4, 8 ) ); + vector float tempSum5 = vec_add( vecTempView2, vec_sld( vecTempView2, vecTempView2, 4 ) ); + vecTempView2 = vec_add( tempSum5, vec_sld( tempSum5, tempSum5, 8 ) ); + + // splat sum accross the whole vector + vecTempLight = vec_splat( vecTempLight, 0 ); + vecTempView = vec_splat( vecTempView, 0 ); + + vecTempLight2 = vec_splat( vecTempLight2, 0 ); + vecTempView2 = vec_splat( vecTempView2, 0 ); + + vecTempLight = ReciprocalSquareRoot( vecTempLight ); + vecTempView = ReciprocalSquareRoot( vecTempView ); + + vecTempLight2 = ReciprocalSquareRoot( vecTempLight2 ); + vecTempView2 = ReciprocalSquareRoot( vecTempView2 ); + + // modify light and view vectors based on ilength + vecViewDir = vec_madd( vecViewDir, vecTempView, zeroVector ); + vecLightDir = vec_madd( vecLightDir, vecTempLight, vecViewDir ); + + vecViewDir2 = vec_madd( vecViewDir2, vecTempView2, zeroVector ); + vecLightDir2 = vec_madd( vecLightDir2, vecTempLight2, vecViewDir2 ); + + // calculate what to store in each texture coord + vector float vecTC0 = vec_madd( vecLightDir, vecTangent0, zeroVector ); + vector float vecTC1 = vec_madd( vecLightDir, vecTangent1, zeroVector ); + vector float vecTC2 = vec_madd( vecLightDir, vecNormal, zeroVector ); + + vector float vecTC3 = vec_madd( vecLightDir2, vecTangent02, zeroVector ); + vector float vecTC4 = vec_madd( vecLightDir2, vecTangent12, zeroVector ); + vector float vecTC5 = vec_madd( vecLightDir2, vecNormal2, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum3; + tempSum = vec_add( vecTC0, vec_sld( vecTC0, vecTC0, 4 ) ); + vecTC0 = vec_add( tempSum, vec_sld( vecTC0, vecTC0, 8 ) ); + tempSum2 = vec_add( vecTC1, vec_sld( vecTC1, vecTC1, 4 ) ); + vecTC1 = vec_add( tempSum2, vec_sld( vecTC1, vecTC1, 8 ) ); + tempSum3 = vec_add( vecTC2, vec_sld( vecTC2, vecTC2, 4 ) ); + vecTC2 = vec_add( tempSum3, vec_sld( vecTC2, vecTC2, 8 ) ); + + tempSum4 = vec_add( vecTC3, vec_sld( vecTC3, vecTC3, 4 ) ); + vecTC3 = vec_add( tempSum4, vec_sld( vecTC3, vecTC3, 8 ) ); + tempSum5 = vec_add( vecTC4, vec_sld( vecTC4, vecTC4, 4 ) ); + vecTC4 = vec_add( tempSum5, vec_sld( vecTC4, vecTC4, 8 ) ); + vector float tempSum6 = vec_add( vecTC5, vec_sld( vecTC5, vecTC5, 4 ) ); + vecTC5 = vec_add( tempSum6, vec_sld( vecTC5, vecTC5, 8 ) ); + + vecTC0 = vec_splat( vecTC0, 0 ); + vecTC1 = vec_splat( vecTC1, 0 ); + vecTC2 = vec_splat( vecTC2, 0 ); + + vecTC3 = vec_splat( vecTC3, 0 ); + vecTC4 = vec_splat( vecTC4, 0 ); + vecTC5 = vec_splat( vecTC5, 0 ); + + if ( used[index] ) { + // store out results + vec_ste( vecTC0, 0, &texCoords[index][0] ); + vec_ste( vecTC1, 0, &texCoords[index][1] ); + vec_ste( vecTC2, 0, &texCoords[index][2] ); + vec_ste( (vector float)(1.0), 0, &texCoords[index][3] ); + } + + if ( used[index+1] ) { + vec_ste( vecTC3, 0, &texCoords[index+1][0] ); + vec_ste( vecTC4, 0, &texCoords[index+1][1] ); + vec_ste( vecTC5, 0, &texCoords[index+1][2] ); + vec_ste( (vector float)(1.0), 0, &texCoords[index+1][3] ); + } + } + + // cleanup + for ( ; index < numVerts; index++ ) { + if ( !used[index] ) { + continue; + } + + const float *vertPtr = verts[index].xyz.ToFloatPtr(); + + permVec = vec_add( vec_lvsl( -1, vertPtr ), (vector unsigned char)(1) ); + + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + vector float v2 = vec_ld( 31, vertPtr ); + vector float v3 = vec_ld( 47, vertPtr ); + vector float v4 = vec_ld( 63, vertPtr ); + + // figure out what values go where + vector float vecXYZ = vec_perm( v0, v1, permVec ); + vector float vecNormal = vec_perm( v1, v2, permVec ); + vecNormal = vec_sld( vecNormal, vecNormal, 4 ); + const vector float vecTangent0 = vec_perm( v2, v3, permVec ); + permVec = vec_add( permVec, (vector unsigned char)(-4) ); //shift permute right 3 elements + const vector float vecTangent1 = vec_perm( v3, v4, permVec ); + + // calculate lightDir + vector float vecLightDir = vec_sub( vecLightOrigin, vecXYZ ); + vector float vecViewDir = vec_sub( vecViewOrigin, vecXYZ ); + + // calculate distance + vector float vecTempLight = vec_madd( vecLightDir, vecLightDir, zeroVector ); + vector float vecTempView = vec_madd( vecViewDir, vecViewDir, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum = vec_add( vecTempLight, vec_sld( vecTempLight, vecTempLight, 4 ) ); + vecTempLight = vec_add( tempSum, vec_sld( tempSum, tempSum, 8 ) ); + vector float tempSum2 = vec_add( vecTempView, vec_sld( vecTempView, vecTempView, 4 ) ); + vecTempView = vec_add( tempSum2, vec_sld( tempSum2, tempSum2, 8 ) ); + + // splat sum accross the whole vector + vecTempLight = vec_splat( vecTempLight, 0 ); + vecTempView = vec_splat( vecTempView, 0 ); + + vecTempLight = ReciprocalSquareRoot( vecTempLight ); + vecTempView = ReciprocalSquareRoot( vecTempView ); + + // modify light and view vectors based on ilength + vecViewDir = vec_madd( vecViewDir, vecTempView, zeroVector ); + vecLightDir = vec_madd( vecLightDir, vecTempLight, vecViewDir ); + + // calculate what to store in each texture coord + vector float vecTC0 = vec_madd( vecLightDir, vecTangent0, zeroVector ); + vector float vecTC1 = vec_madd( vecLightDir, vecTangent1, zeroVector ); + vector float vecTC2 = vec_madd( vecLightDir, vecNormal, zeroVector ); + + // sum accross first 3 elements of vector + vector float tempSum3; + tempSum = vec_add( vecTC0, vec_sld( vecTC0, vecTC0, 4 ) ); + vecTC0 = vec_add( tempSum, vec_sld( vecTC0, vecTC0, 8 ) ); + tempSum2 = vec_add( vecTC1, vec_sld( vecTC1, vecTC1, 4 ) ); + vecTC1 = vec_add( tempSum2, vec_sld( vecTC1, vecTC1, 8 ) ); + tempSum3 = vec_add( vecTC2, vec_sld( vecTC2, vecTC2, 4 ) ); + vecTC2 = vec_add( tempSum3, vec_sld( vecTC2, vecTC2, 8 ) ); + + vecTC0 = vec_splat( vecTC0, 0 ); + vecTC1 = vec_splat( vecTC1, 0 ); + vecTC2 = vec_splat( vecTC2, 0 ); + + // store out results + vec_ste( vecTC0, 0, &texCoords[index][0] ); + vec_ste( vecTC1, 0, &texCoords[index][1] ); + vec_ste( vecTC2, 0, &texCoords[index][2] ); + vec_ste( (vector float)(1.0), 0, &texCoords[index][3] ); + + } +} +#endif /* 0 for disable spec coord */ + +#if 1 + +#ifdef VERTEXCACHE_ALIGNED +/* +============ +idSIMD_AltiVec::CreateShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) { + int outVerts = 0; + int i = 0; + + assert( IS_16BYTE_ALIGNED( vertexCache[0] ) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector unsigned char vecPerm, vecPerm2, vecPerm3, vecPerm4, vecPerm5; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermZeroLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + + const float *lPtr = lightOrigin.ToFloatPtr(); + const float *vPtr; + const float *vPtr2; + const float *vPtr3; + const float *vPtr4; + + // put values into a vector + vecPerm = vec_add( vec_lvsl( -1, lPtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, lPtr ); + v1 = vec_ld( 15, lPtr ); + v0 = vec_perm( v0, v1, vecPerm ); + v0 = vec_perm( v0, zeroVector, vecPermZeroLast ); + + //v0 now contains lightOrigin[0], lightOrigin[1], lightOrigin[2], 0 + for ( ; i+3 < numVerts; i+= 4 ) { + if ( ! vertRemap[i] ) { + vPtr = verts[i].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + vecPerm2 = vec_add( vec_lvsl( -1, vPtr ), (vector unsigned char)(1) ); + v2 = vec_ld( 0, vPtr ); + v3 = vec_ld( 15, vPtr ); + v7 = vec_perm( v2, v3, vecPerm2 ); +#else + v7 = vec_ld( 0, vPtr ); +#endif + v2 = vec_perm( v7, zeroVector, vecPermZeroLast ); + v3 = vec_perm( v7, oneVector, vecPermZeroLast ); + v1 = vec_sub( v2, v0 ); + + vec_st( v3, 0, &vertexCache[outVerts][0] ); + vec_st( v1, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+1] ) { + vPtr2 = verts[i+1].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + vecPerm3 = vec_add( vec_lvsl( -1, vPtr2 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr2 ); + v5 = vec_ld( 15, vPtr2 ); + v6 = vec_perm( v4, v5, vecPerm3 ); +#else + v6 = vec_ld( 0, vPtr2 ); +#endif + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + vec_st( v5, 0, &vertexCache[outVerts][0] ); + vec_st( v6, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i+1] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+2] ) { + vPtr3 = verts[i+2].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + vecPerm4 = vec_add( vec_lvsl( -1, vPtr3 ), (vector unsigned char)(1) ); + v1 = vec_ld( 0, vPtr3 ); + v2 = vec_ld( 15, vPtr3 ); + v3 = vec_perm( v1, v2, vecPerm4 ); +#else + v3 = vec_ld( 0, vPtr3 ); +#endif + v1 = vec_perm( v3, zeroVector, vecPermZeroLast ); + v2 = vec_perm( v3, oneVector, vecPermZeroLast ); + v3 = vec_sub( v1, v0 ); + + vec_st( v2, 0, &vertexCache[outVerts][0] ); + vec_st( v3, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i+2] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+3] ) { + vPtr4 = verts[i+3].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm5 = vec_add( vec_lvsl( -1, vPtr4 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr4 ); + v5 = vec_ld( 16, vPtr4 ); + v6 = vec_perm( v4, v5, vecPerm5 ); +#else + v6 = vec_ld( 0, vPtr4 ); +#endif + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + vec_st( v5, 0, &vertexCache[outVerts][0] ); + vec_st( v6, 0, &vertexCache[outVerts+1][0] ); + + vertRemap[i+3] = outVerts; + outVerts += 2; + } + } + + // cleanup + for (; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; +} + +#else + +/* +============ +idSIMD_AltiVec::CreateShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) { + int outVerts = 0; + int i = 0; + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector unsigned char vecPerm, vecPerm2, vecPerm3, vecPerm4, vecPerm5; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermZeroLast = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + + const float *lPtr = lightOrigin.ToFloatPtr(); + const float *vPtr; + const float *vPtr2; + const float *vPtr3; + const float *vPtr4; + + // put values into a vector + vecPerm = vec_add( vec_lvsl( -1, lPtr ), (vector unsigned char)(1) ); + v0 = vec_ld( 0, lPtr ); + v1 = vec_ld( 15, lPtr ); + v0 = vec_perm( v0, v1, vecPerm ); + v0 = vec_perm( v0, zeroVector, vecPermZeroLast ); + + //v0 now contains lightOrigin[0], lightOrigin[1], lightOrigin[2], 0 + for ( ; i+3 < numVerts; i+= 4 ) { + if ( ! vertRemap[i] ) { + vPtr = verts[i].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm2 = vec_add( vec_lvsl( -1, vPtr ), (vector unsigned char)(1) ); + v2 = vec_ld( 0, vPtr ); + v3 = vec_ld( 15, vPtr ); + v7 = vec_perm( v2, v3, vecPerm2 ); +#else + v7 = vec_ld( 0, vPtr ); +#endif + v2 = vec_perm( v7, zeroVector, vecPermZeroLast ); + v3 = vec_perm( v7, oneVector, vecPermZeroLast ); + v1 = vec_sub( v2, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v3, v1 ); + + vertRemap[i] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+1] ) { + vPtr2 = verts[i+1].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm3 = vec_add( vec_lvsl( -1, vPtr2 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr2 ); + v5 = vec_ld( 15, vPtr2 ); + v6 = vec_perm( v4, v5, vecPerm3 ); +#else + v6 = vec_ld( 0, vPtr2 ); +#endif + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v5, v6 ); + + vertRemap[i+1] = outVerts; + outVerts += 2; + } + + if ( ! vertRemap[i+2] ) { + vPtr3 = verts[i+2].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm4 = vec_add( vec_lvsl( -1, vPtr3 ), (vector unsigned char)(1) ); + v1 = vec_ld( 0, vPtr3 ); + v2 = vec_ld( 15, vPtr3 ); + v3 = vec_perm( v1, v2, vecPerm4 ); +#else + v3 = vec_ld( 0, vPtr3 ); +#endif + v1 = vec_perm( v3, zeroVector, vecPermZeroLast ); + v2 = vec_perm( v3, oneVector, vecPermZeroLast ); + v3 = vec_sub( v1, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v2, v3 ); + + vertRemap[i+2] = outVerts; + outVerts += 2; + } + if ( ! vertRemap[i+3] ) { + vPtr4 = verts[i+3].xyz.ToFloatPtr(); +#ifndef DRAWVERT_PADDED + vecPerm5 = vec_add( vec_lvsl( -1, vPtr4 ), (vector unsigned char)(1) ); + v4 = vec_ld( 0, vPtr4 ); + v5 = vec_ld( 16, vPtr4 ); + v6 = vec_perm( v4, v5, vecPerm5 ); +#else + v6 = vec_ld( 0, vPtr4 ); +#endif + + v4 = vec_perm( v6, zeroVector, vecPermZeroLast ); + v5 = vec_perm( v6, oneVector, vecPermZeroLast ); + v6 = vec_sub( v4, v0 ); + + // store results + UNALIGNED_STORE2( &vertexCache[outVerts][0], v5, v6 ); + + + vertRemap[i+3] = outVerts; + outVerts += 2; + } + } + + // cleanup + for (; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; +} +#endif /* VERTEXCACHE_ALIGNED */ + +#endif /* 0 to disable shadow cache */ + +#if 1 + +#ifdef VERTEXCACHE_ALIGNED +/* +============ +idSIMD_AltiVec::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) { + + // vertexCache aligned + assert( IS_16BYTE_ALIGNED( vertexCache[0] ) ); + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // idVec4 size + assert( sizeof(idVec4) == IDVEC4_OFFSET * sizeof(float) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermThreeOne = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + int i = 0; + +#ifndef DRAWVERT_PADDED + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } +#endif + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v1 = vec_perm( v2, v3, vertPerm2 ); + v2 = vec_perm( v4, v5, vertPerm3 ); + v3 = vec_perm( v6, v7, vertPerm4 ); +#else + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 0, vertPtr2 ); + v2 = vec_ld( 0, vertPtr3 ); + v3 = vec_ld( 0, vertPtr4 ); +#endif + + v0 = vec_perm( v0, oneVector, vecPermThreeOne ); + v4 = vec_perm( v0, zeroVector, vecPermThreeOne ); + + v1 = vec_perm( v1, oneVector, vecPermThreeOne ); + v5 = vec_perm( v1, zeroVector, vecPermThreeOne ); + + v2 = vec_perm( v2, oneVector, vecPermThreeOne ); + v6 = vec_perm( v2, zeroVector, vecPermThreeOne ); + + v3 = vec_perm( v3, oneVector, vecPermThreeOne ); + v7 = vec_perm( v3, zeroVector, vecPermThreeOne ); + + // store results + ALIGNED_STORE4( &vertexCache[i*2][0], v0, v4, v1, v5 ); + ALIGNED_STORE4( &vertexCache[(i+2)*2][0], v2, v6, v3, v7 ); + + } + + // cleanup + for ( ; i < numVerts; i++ ) { + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; +} + +#else +/* +============ +idSIMD_AltiVec::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_AltiVec::CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) { + + // idDrawVert size + assert( sizeof(idDrawVert) == DRAWVERT_OFFSET * sizeof(float) ); + // idVec4 size + assert( sizeof(idVec4) == IDVEC4_OFFSET * sizeof(float) ); + + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector float zeroVector = (vector float)(0.0); + register vector float oneVector = (vector float)(1); + register vector unsigned char vecPermThreeOne = (vector unsigned char)(0,1,2,3,4,5,6,7,8,9,10,11,16,17,18,19); + vector unsigned char vertPerm1, vertPerm2, vertPerm3, vertPerm4; + int i = 0; + +#ifndef DRAWVERT_PADDED + // every fourth one will have the same alignment. Make sure we've got enough here + if ( i+3 < numVerts ) { + vertPerm1 = vec_add( vec_lvsl( -1, (float*) verts[0].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm2 = vec_add( vec_lvsl( -1, (float*) verts[1].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm3 = vec_add( vec_lvsl( -1, (float*) verts[2].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + vertPerm4 = vec_add( vec_lvsl( -1, (float*) verts[3].xyz.ToFloatPtr() ), (vector unsigned char)(1) ); + } +#endif + + for ( ; i+3 < numVerts; i+=4 ) { + const float *vertPtr = verts[i].xyz.ToFloatPtr(); + const float *vertPtr2 = verts[i+1].xyz.ToFloatPtr(); + const float *vertPtr3 = verts[i+2].xyz.ToFloatPtr(); + const float *vertPtr4 = verts[i+3].xyz.ToFloatPtr(); + +#ifndef DRAWVERT_PADDED + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 15, vertPtr ); + v2 = vec_ld( 0, vertPtr2 ); + v3 = vec_ld( 15, vertPtr2 ); + v4 = vec_ld( 0, vertPtr3 ); + v5 = vec_ld( 15, vertPtr3 ); + v6 = vec_ld( 0, vertPtr4 ); + v7 = vec_ld( 15, vertPtr4 ); + + v0 = vec_perm( v0, v1, vertPerm1 ); + v1 = vec_perm( v2, v3, vertPerm2 ); + v2 = vec_perm( v4, v5, vertPerm3 ); + v3 = vec_perm( v6, v7, vertPerm4 ); +#else + v0 = vec_ld( 0, vertPtr ); + v1 = vec_ld( 0, vertPtr2 ); + v2 = vec_ld( 0, vertPtr3 ); + v3 = vec_ld( 0, vertPtr4 ); +#endif + + v0 = vec_perm( v0, oneVector, vecPermThreeOne ); + v4 = vec_perm( v0, zeroVector, vecPermThreeOne ); + + v1 = vec_perm( v1, oneVector, vecPermThreeOne ); + v5 = vec_perm( v1, zeroVector, vecPermThreeOne ); + + v2 = vec_perm( v2, oneVector, vecPermThreeOne ); + v6 = vec_perm( v2, zeroVector, vecPermThreeOne ); + + v3 = vec_perm( v3, oneVector, vecPermThreeOne ); + v7 = vec_perm( v3, zeroVector, vecPermThreeOne ); + + // store results as unaligned + vector unsigned char storePerm = vec_sub( vec_lvsr( 15, &vertexCache[i*2][0] ), (vector unsigned char)(1) ); + vector unsigned int mask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), storePerm ); + vector float vc1 = vec_ld( 0, &vertexCache[i*2][0] ); + vector float vc2 = vec_ld( 127, &vertexCache[i*2][0] ); + + // right rotate input data + v0 = vec_perm( v0, v0, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v2 = vec_perm( v2, v2, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + + vec_st( vec_sel( vc1, v0, mask ), 0 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v0, v4, mask ), 15 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v4, v1, mask ), 31 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v1, v5, mask ), 47 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v5, v2, mask ), 63 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v2, v6, mask ), 79 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v6, v3, mask ), 95 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v3, v7, mask ), 111 , &vertexCache[i*2][0] ); + vec_st( vec_sel( v7, vc2, mask ), 127 , &vertexCache[i*2][0] ); + } + + // cleanup + for ( ; i < numVerts; i++ ) { + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; +} + +#endif /* VERTEXCACHE_ALIGNED */ + +#endif /* 0 to kill VP shader cache */ + +#endif /* ENABLE_CREATE */ + +#ifdef ENABLE_SOUND_ROUTINES + +#ifdef SOUND_DEST_ALIGNED +/* +============ +idSIMD_AltiVec::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + Assumes that dest starts at aligned address +============ +*/ +void idSIMD_AltiVec::UpSamplePCMTo44kHz( float *dest, const short *src, const int numSamples, const int kHz, const int numChannels ) { + + // dest is aligned + assert( IS_16BYTE_ALIGNED( dest[0] ) ); + + vector signed short vs0, vs1; + register vector signed int vi0, vi1; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9; + // permute vectors + register vector unsigned char vecFirstHalf = (vector unsigned char)(0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7); + register vector unsigned char vecSecondHalf = (vector unsigned char)(8,9,10,11,12,13,14,15,8,9,10,11,12,13,14,15); + + register vector unsigned char vecBottom = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + register vector unsigned char vecTop = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + // If this can be assumed true, we can eliminate another conditional that checks to see if we can + // load up a vector before the loop + assert( numSamples >= 12 ); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // 8 at a time + int i = 0; + + vector signed short vsOld = vec_ld( 0, &src[i] ); + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[i] ), (vector unsigned char)(1) ); + + for ( ; i+7 < numSamples; i+= 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // permute into vectors in the order to store + + v2 = vec_splat( v0, 0 ); + v3 = vec_splat( v0, 1 ); + v4 = vec_splat( v0, 2 ); + v5 = vec_splat( v0, 3 ); + v6 = vec_splat( v1, 0 ); + v7 = vec_splat( v1, 1 ); + v8 = vec_splat( v1, 2 ); + v9 = vec_splat( v1, 3 ); + + // store results + ALIGNED_STORE8( &dest[i*4], v2, v3, v4, v5, v6, v7, v8, v9 ); + } + // cleanup + for (; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = (float) src[i+0]; + } + } else { + int i = 0; + + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( ; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = v2; + v4 = vec_perm( v0, v0, vecSecondHalf ); + v5 = v4; + v6 = vec_perm( v1, v1, vecFirstHalf ); + v7 = v6; + v8 = vec_perm (v1, v1, vecSecondHalf ); + v9 = v8; + + // store results + ALIGNED_STORE8( &dest[i*4], v2, v3, v4, v5, v6, v7, v8, v9 ); + } + + for ( ; i < numSamples; i += 2 ) { + dest[i*4+0] = dest[i*4+2] = dest[i*4+4] = dest[i*4+6] = (float) src[i+0]; + dest[i*4+1] = dest[i*4+3] = dest[i*4+5] = dest[i*4+7] = (float) src[i+1]; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 0, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecBottom ); + v3 = vec_perm( v0, v0, vecTop ); + v4 = vec_perm( v1, v1, vecBottom ); + v5 = vec_perm (v1, v1, vecTop ); + + // store results + ALIGNED_STORE4( &dest[i*2], v2, v3, v4, v5 ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = (float) src[i+0]; + } + } else { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = vec_perm( v0, v0, vecSecondHalf ); + v4 = vec_perm( v1, v1, vecFirstHalf ); + v5 = vec_perm (v1, v1, vecSecondHalf ); + + // store results + ALIGNED_STORE4( &dest[i*2], v2, v3, v4, v5 ); + } + // cleanup + for ( ; i < numSamples; i += 2 ) { + dest[i*2+0] = dest[i*2+2] = (float) src[i+0]; + dest[i*2+1] = dest[i*2+3] = (float) src[i+1]; + } + } + } else if ( kHz == 44100 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + + //unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + + //convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + + //store results + ALIGNED_STORE2( &dest[i], v0, v1 ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i] = (float) src[i]; + } + } else { + assert( 0 ); + } +} + +#else + +/* +============ +idSIMD_AltiVec::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + No assumptions +============ +*/ +void idSIMD_AltiVec::UpSamplePCMTo44kHz( float *dest, const short *src, const int numSamples, const int kHz, const int numChannels ) { + + vector signed short vs0, vs1; + register vector signed int vi0, vi1; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9; + // permute vectors + register vector unsigned char vecFirstHalf = (vector unsigned char)(0,1,2,3,4,5,6,7,0,1,2,3,4,5,6,7); + register vector unsigned char vecSecondHalf = (vector unsigned char)(8,9,10,11,12,13,14,15,8,9,10,11,12,13,14,15); + + register vector unsigned char vecBottom = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + register vector unsigned char vecTop = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + // calculate perm vector and masks for stores + vector unsigned char storePerm = vec_sub( vec_lvsr( 15, &dest[0] ), (vector unsigned char)(1) ); + // original values of dest + vector float vecDest = vec_ld( 0, &dest[0] ); + vector unsigned int mask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), storePerm ); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // 8 at a time + int i = 0; + + vector signed short vsOld = vec_ld( 0, &src[i] ); + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[i] ), (vector unsigned char)(1) ); + + for ( ; i+7 < numSamples; i+= 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 127, &dest[i*4] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // permute into vectors in the order to store + + v2 = vec_splat( v0, 0 ); + v3 = vec_splat( v0, 1 ); + v4 = vec_splat( v0, 2 ); + v5 = vec_splat( v0, 3 ); + v6 = vec_splat( v1, 0 ); + v7 = vec_splat( v1, 1 ); + v8 = vec_splat( v1, 2 ); + v9 = vec_splat( v1, 3 ); + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + v8 = vec_perm( v8, v8, storePerm ); + v9 = vec_perm( v9, v9, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*4] ); + vec_st( vec_sel( v5, v6, mask ), 63, &dest[i*4] ); + vec_st( vec_sel( v6, v7, mask ), 79, &dest[i*4] ); + vec_st( vec_sel( v7, v8, mask ), 95, &dest[i*4] ); + vec_st( vec_sel( v8, v9, mask ), 111, &dest[i*4] ); + vecDest = vec_sel( v9, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*4] ); + } + // cleanup + for (; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = (float) src[i+0]; + } + } else { + int i = 0; + + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( ; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 127, &dest[i*4] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = v2; + v4 = vec_perm( v0, v0, vecSecondHalf ); + v5 = v4; + v6 = vec_perm( v1, v1, vecFirstHalf ); + v7 = v6; + v8 = vec_perm (v1, v1, vecSecondHalf ); + v9 = v8; + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + v8 = vec_perm( v8, v8, storePerm ); + v9 = vec_perm( v9, v9, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*4] ); + vec_st( vec_sel( v5, v6, mask ), 63, &dest[i*4] ); + vec_st( vec_sel( v6, v7, mask ), 79, &dest[i*4] ); + vec_st( vec_sel( v7, v8, mask ), 95, &dest[i*4] ); + vec_st( vec_sel( v8, v9, mask ), 111, &dest[i*4] ); + vecDest = vec_sel( v9, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*4] ); + } + + for ( ; i < numSamples; i += 2 ) { + dest[i*4+0] = dest[i*4+2] = dest[i*4+4] = dest[i*4+6] = (float) src[i+0]; + dest[i*4+1] = dest[i*4+3] = dest[i*4+5] = dest[i*4+7] = (float) src[i+1]; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 0, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 63, &dest[i*2] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecBottom ); + v3 = vec_perm( v0, v0, vecTop ); + v4 = vec_perm( v1, v1, vecBottom ); + v5 = vec_perm (v1, v1, vecTop ); + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*2] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*2] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*2] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*2] ); + vecDest = vec_sel( v5, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*2] ); + + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = (float) src[i+0]; + } + } else { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load src + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 63, &dest[i*2] ); + + // unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + // convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + // put into vectors in order to store + v2 = vec_perm( v0, v0, vecFirstHalf ); + v3 = vec_perm( v0, v0, vecSecondHalf ); + v4 = vec_perm( v1, v1, vecFirstHalf ); + v5 = vec_perm (v1, v1, vecSecondHalf ); + + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*2] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*2] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*2] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*2] ); + vecDest = vec_sel( v5, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*2] ); + } + // cleanup + for ( ; i < numSamples; i += 2 ) { + dest[i*2+0] = dest[i*2+2] = (float) src[i+0]; + dest[i*2+1] = dest[i*2+3] = (float) src[i+1]; + } + } + } else if ( kHz == 44100 ) { + int i; + vector unsigned char permVec = vec_add( vec_lvsl( -1, &src[0] ), (vector unsigned char)(1) ); + vector signed short vsOld = vec_ld( 0, &src[0] ); + + for ( i = 0; i+7 < numSamples; i += 8 ) { + //vs0 = vec_ld( 0, &src[i] ); + vs1 = vec_ld( 15, &src[i] ); + vs0 = vec_perm( vsOld, vs1, permVec ); + vsOld = vs1; + vector float vecDestEnd = vec_ld( 31, &dest[i] ); + + //unpack shorts to ints + vi0 = vec_unpackh( vs0 ); + vi1 = vec_unpackl( vs0 ); + + //convert ints to floats + v0 = vec_ctf( vi0, 0 ); + v1 = vec_ctf( vi1, 0 ); + + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i] ); + vecDest = vec_sel( v1, vecDestEnd, mask ); + vec_st( vecDest, 31, &dest[i] ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i] = (float) src[i]; + } + } else { + assert( 0 ); + } +} + +#endif + +#ifdef SOUND_DEST_ALIGNED +/* +============ +idSIMD_AltiVec::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + Assumes that dest starts at aligned address +============ +*/ +void idSIMD_AltiVec::UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) { + // dest is aligned + assert( IS_16BYTE_ALIGNED( dest[0] ) ); + + register vector float oggVec1, oggVec2, oggVec3, oggVec4, oggVec5, oggVec6, oggVec7, oggVec8; + register vector float constVec, zeroVector; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10; + vector unsigned char vecPerm1; + vector unsigned char vecPerm2; + + vector unsigned char vecOneTwo = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + vector unsigned char vecThreeFour = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + vector unsigned char vecFirst = (vector unsigned char)(0,1,2,3,16,17,18,19,0,1,2,3,16,17,18,19); + vector unsigned char vecSecond = (vector unsigned char)(4,5,6,7,20,21,22,23,4,5,6,7,20,21,22,23); + vector unsigned char vecThird = (vector unsigned char)(8,9,10,11,24,25,26,27,8,9,10,11,24,25,26,27); + vector unsigned char vecFourth = (vector unsigned char)(12,13,14,15,28,29,30,31,12,13,14,15,28,29,30,31); + + constVec = (vector float)(32768.0f); + zeroVector = (vector float)(0.0); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+7 < numSamples; i += 8 ) { + // as it happens, ogg[0][i] through ogg[0][i+3] are contiguous in memory + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + v0 = vec_madd( oggVec1, constVec, zeroVector ); + v1 = vec_madd( oggVec2, constVec, zeroVector ); + v2 = vec_madd( oggVec3, constVec, zeroVector ); + v3 = vec_madd( oggVec4, constVec, zeroVector ); + v4 = vec_madd( oggVec5, constVec, zeroVector ); + v5 = vec_madd( oggVec6, constVec, zeroVector ); + v6 = vec_madd( oggVec7, constVec, zeroVector ); + v7 = vec_madd( oggVec8, constVec, zeroVector ); + + //store results + ALIGNED_STORE8( &dest[i*4], v0, v1, v2, v3, v4, v5, v6, v7 ); + + } + + //cleanup + for ( ; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = ogg[0][i] * 32768.0f; + } + + } else { + + // calculate perm vec for ogg + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i+=4 ) { // +1 += 2 + // load and splat from the array ( ogg[0][i] to ogg[0][i+3] ) + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + + // load and splat from the array ( ogg[1][i] to ogg[1][i+3] ) + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + oggVec1 = vec_madd( oggVec1, constVec, zeroVector ); // ogg[0][i] * 32768 + oggVec2 = vec_madd( oggVec2, constVec, zeroVector ); // ogg[0][i+1] * 32768 + oggVec3 = vec_madd( oggVec3, constVec, zeroVector ); // ogg[0][i+2] * 32768 + oggVec4 = vec_madd( oggVec4, constVec, zeroVector ); // ogg[0][i+3] * 32768 + oggVec5 = vec_madd( oggVec5, constVec, zeroVector ); // ogg[1][i] * 32768 + oggVec6 = vec_madd( oggVec6, constVec, zeroVector ); // ogg[1][i+1] * 32768 + oggVec7 = vec_madd( oggVec7, constVec, zeroVector ); // ogg[1][i+2] * 32768 + oggVec8 = vec_madd( oggVec8, constVec, zeroVector ); // ogg[1][i+3] * 32768 + + //merge generates the interleaved pattern that we want and it + //doesn't require a permute vector, so use that instead + v0 = vec_mergeh( oggVec1, oggVec5 ); + v1 = vec_mergel( oggVec1, oggVec5 ); + v2 = vec_mergeh( oggVec2, oggVec6 ); + v3 = vec_mergel( oggVec2, oggVec6 ); + + v4 = vec_mergeh( oggVec3, oggVec7 ); + v5 = vec_mergel( oggVec3, oggVec7 ); + v6 = vec_mergeh( oggVec4, oggVec8 ); + v10 = vec_mergel( oggVec4, oggVec8 ); + + //store results + ALIGNED_STORE8( &dest[i*8], v0, v1, v2, v3, v4, v5, v6, v10 ); + } + + //cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*8+0] = dest[i*8+2] = dest[i*8+4] = dest[i*8+6] = ogg[0][i] * 32768.0f; + dest[i*8+1] = dest[i*8+3] = dest[i*8+5] = dest[i*8+7] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load values from ogg + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // permute into results vectors to store + v5 = vec_perm( v0, v0, vecOneTwo ); + v6 = vec_perm( v0, v0, vecThreeFour); + v7 = vec_perm( v1, v1, vecOneTwo ); + v8 = vec_perm( v1, v1, vecThreeFour ); + + //store results + ALIGNED_STORE4( &dest[i*2], v5, v6, v7, v8 ); + } + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + // load ogg[0][i] to ogg[0][i+4] + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors to store + v2 = vec_perm( v0, v1, vecFirst ); + v3 = vec_perm( v0, v1, vecSecond ); + v4 = vec_perm( v0, v1, vecThird ); + v5 = vec_perm( v0, v1, vecFourth ); + + // store results + ALIGNED_STORE4( &dest[i*4], v2, v3, v4, v5 ); + } + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*4+0] = dest[i*4+2] = ogg[0][i] * 32768.0f; + dest[i*4+1] = dest[i*4+3] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load values from ogg + v8 = v9; + v7 = vec_ld( 15, &ogg[0][i] ); + v6 = v7; + v9 = vec_ld( 31, &ogg[0][i] ); + + v0 = vec_perm( v8, v7, vecPerm1 ); + v1 = vec_perm( v6, v9, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + ALIGNED_STORE2( &dest[i], v0, v1 ); + } + + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*1+0] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors + v2 = vec_mergeh( v0, v1 ); + v3 = vec_mergel( v0, v1 ); + + // store results + ALIGNED_STORE2( &dest[i*2], v2, v3 ); + } + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*2+0] = ogg[0][i] * 32768.0f; + dest[i*2+1] = ogg[1][i] * 32768.0f; + } + } + } else { + assert( 0 ); + } +} + +#else + +/* +============ +idSIMD_AltiVec::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. + + Assumptions: + No assumptions +============ +*/ +void idSIMD_AltiVec::UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) { + + register vector float oggVec1, oggVec2, oggVec3, oggVec4, oggVec5, oggVec6, oggVec7, oggVec8; + register vector float constVec, zeroVector; + register vector float v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10; + vector unsigned char vecPerm1; + vector unsigned char vecPerm2; + + vector unsigned char vecOneTwo = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + vector unsigned char vecThreeFour = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + vector unsigned char vecFirst = (vector unsigned char)(0,1,2,3,16,17,18,19,0,1,2,3,16,17,18,19); + vector unsigned char vecSecond = (vector unsigned char)(4,5,6,7,20,21,22,23,4,5,6,7,20,21,22,23); + vector unsigned char vecThird = (vector unsigned char)(8,9,10,11,24,25,26,27,8,9,10,11,24,25,26,27); + vector unsigned char vecFourth = (vector unsigned char)(12,13,14,15,28,29,30,31,12,13,14,15,28,29,30,31); + + vector unsigned char storePerm; + + constVec = (vector float)(32768.0f); + zeroVector = (vector float)(0.0); + + // calculate perm vector and masks for stores + storePerm = vec_sub( vec_lvsr( 15, &dest[0] ), (vector unsigned char)(1) ); + // original values of dest + vector float vecDest = vec_ld( 0, &dest[0] ); + vector unsigned int mask = vec_perm( (vector unsigned int)(0), (vector unsigned int)(-1), storePerm ); + + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+7 < numSamples; i += 8 ) { + // as it happens, ogg[0][i] through ogg[0][i+3] are contiguous in memory + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 127, &dest[i*4] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + v0 = vec_madd( oggVec1, constVec, zeroVector ); + v1 = vec_madd( oggVec2, constVec, zeroVector ); + v2 = vec_madd( oggVec3, constVec, zeroVector ); + v3 = vec_madd( oggVec4, constVec, zeroVector ); + v4 = vec_madd( oggVec5, constVec, zeroVector ); + v5 = vec_madd( oggVec6, constVec, zeroVector ); + v6 = vec_madd( oggVec7, constVec, zeroVector ); + v7 = vec_madd( oggVec8, constVec, zeroVector ); + + // rotate input data + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v1, v2, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 47, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 63, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 79, &dest[i*4] ); + vec_st( vec_sel( v5, v6, mask ), 95, &dest[i*4] ); + vec_st( vec_sel( v6, v7, mask ), 111, &dest[i*4] ); + vecDest = vec_sel( v7, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*4] ); + } + + //cleanup + for ( ; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = ogg[0][i] * 32768.0f; + } + + } else { + + // calculate perm vec for ogg + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i+=4 ) { // +1 += 2 + // load and splat from the array ( ogg[0][i] to ogg[0][i+3] ) + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 127, &dest[i*8] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec1 = vec_splat( v0, 0 ); + oggVec2 = vec_splat( v0, 1 ); + oggVec3 = vec_splat( v0, 2 ); + oggVec4 = vec_splat( v0, 3 ); + + // load and splat from the array ( ogg[1][i] to ogg[1][i+3] ) + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // now we have the elements in a vector, we want + // to splat them each accross their own vector + oggVec5 = vec_splat( v1, 0 ); + oggVec6 = vec_splat( v1, 1 ); + oggVec7 = vec_splat( v1, 2 ); + oggVec8 = vec_splat( v1, 3 ); + + oggVec1 = vec_madd( oggVec1, constVec, zeroVector ); // ogg[0][i] * 32768 + oggVec2 = vec_madd( oggVec2, constVec, zeroVector ); // ogg[0][i+1] * 32768 + oggVec3 = vec_madd( oggVec3, constVec, zeroVector ); // ogg[0][i+2] * 32768 + oggVec4 = vec_madd( oggVec4, constVec, zeroVector ); // ogg[0][i+3] * 32768 + oggVec5 = vec_madd( oggVec5, constVec, zeroVector ); // ogg[1][i] * 32768 + oggVec6 = vec_madd( oggVec6, constVec, zeroVector ); // ogg[1][i+1] * 32768 + oggVec7 = vec_madd( oggVec7, constVec, zeroVector ); // ogg[1][i+2] * 32768 + oggVec8 = vec_madd( oggVec8, constVec, zeroVector ); // ogg[1][i+3] * 32768 + + //merge generates the interleaved pattern that we want and it + //doesn't require a permute vector, so use that instead + v0 = vec_mergeh( oggVec1, oggVec5 ); + v1 = vec_mergel( oggVec1, oggVec5 ); + v2 = vec_mergeh( oggVec2, oggVec6 ); + v3 = vec_mergel( oggVec2, oggVec6 ); + + v4 = vec_mergeh( oggVec3, oggVec7 ); + v5 = vec_mergel( oggVec3, oggVec7 ); + v6 = vec_mergeh( oggVec4, oggVec8 ); + v10 = vec_mergel( oggVec4, oggVec8 ); + + // rotate input data + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v10 = vec_perm( v10, v10, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i*8] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i*8] ); + vec_st( vec_sel( v1, v2, mask ), 31, &dest[i*8] ); + vec_st( vec_sel( v2, v3, mask ), 47, &dest[i*8] ); + vec_st( vec_sel( v3, v4, mask ), 63, &dest[i*8] ); + vec_st( vec_sel( v4, v5, mask ), 79, &dest[i*8] ); + vec_st( vec_sel( v5, v6, mask ), 95, &dest[i*8] ); + vec_st( vec_sel( v6, v10, mask ), 111, &dest[i*8] ); + vecDest = vec_sel( v10, vecDestEnd, mask ); + vec_st( vecDest, 127, &dest[i*8] ); + } + + //cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*8+0] = dest[i*8+2] = dest[i*8+4] = dest[i*8+6] = ogg[0][i] * 32768.0f; + dest[i*8+1] = dest[i*8+3] = dest[i*8+5] = dest[i*8+7] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + v10 = vec_ld( 0, &ogg[0][0] ); + + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + + // load values from ogg + v8 = v10; + v9 = vec_ld( 15, &ogg[0][i] ); + v10 = vec_ld( 31, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 63, &dest[i*2] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + v1 = vec_perm( v9, v10, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // permute into results vectors to store + v5 = vec_perm( v0, v0, vecOneTwo ); + v6 = vec_perm( v0, v0, vecThreeFour); + v7 = vec_perm( v1, v1, vecOneTwo ); + v8 = vec_perm( v1, v1, vecThreeFour ); + + // rotate input data + v5 = vec_perm( v5, v5, storePerm ); + v6 = vec_perm( v6, v6, storePerm ); + v7 = vec_perm( v7, v7, storePerm ); + v8 = vec_perm( v8, v8, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v5, mask ), 0, &dest[i*2] ); + vec_st( vec_sel( v5, v6, mask ), 15, &dest[i*2] ); + vec_st( vec_sel( v6, v7, mask ), 31, &dest[i*2] ); + vec_st( vec_sel( v7, v8, mask ), 47, &dest[i*2] ); + vecDest = vec_sel( v8, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*2] ); + } + + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + + int i; + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + // load ogg[0][i] to ogg[0][i+4] + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 63, &dest[i*4] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors to store + v2 = vec_perm( v0, v1, vecFirst ); + v3 = vec_perm( v0, v1, vecSecond ); + v4 = vec_perm( v0, v1, vecThird ); + v5 = vec_perm( v0, v1, vecFourth ); + + // rotate input data + v2 = vec_perm( v2, v2, storePerm ); + v3 = vec_perm( v3, v3, storePerm ); + v4 = vec_perm( v4, v4, storePerm ); + v5 = vec_perm( v5, v5, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v2, mask ), 0, &dest[i*4] ); + vec_st( vec_sel( v2, v3, mask ), 15, &dest[i*4] ); + vec_st( vec_sel( v3, v4, mask ), 31, &dest[i*4] ); + vec_st( vec_sel( v4, v5, mask ), 47, &dest[i*4] ); + vecDest = vec_sel( v5, vecDestEnd, mask ); + vec_st( vecDest, 63, &dest[i*4] ); + } + + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*4+0] = dest[i*4+2] = ogg[0][i] * 32768.0f; + dest[i*4+1] = dest[i*4+3] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+7 < numSamples; i += 8 ) { + // load values from ogg + v8 = v9; + v7 = vec_ld( 15, &ogg[0][i] ); + v6 = v7; + v9 = vec_ld( 31, &ogg[0][i] ); + vector float vecDestEnd = vec_ld( 31, &dest[i] ); + + v0 = vec_perm( v8, v7, vecPerm1 ); + v1 = vec_perm( v6, v9, vecPerm1 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // rotate data + v0 = vec_perm( v0, v0, storePerm ); + v1 = vec_perm( v1, v1, storePerm ); + + // store results + vec_st( vec_sel( vecDest, v0, mask ), 0, &dest[i] ); + vec_st( vec_sel( v0, v1, mask ), 15, &dest[i] ); + vecDest = vec_sel( v1, vecDestEnd, mask ); + vec_st( vecDest, 31, &dest[i] ); + } + + // cleanup + for ( ; i < numSamples; i++ ) { + dest[i*1+0] = ogg[0][i] * 32768.0f; + } + } else { + + // calculate perm vector and do first load + vecPerm1 = vec_add( vec_lvsl( -1, (int*) &ogg[0][0] ), (vector unsigned char)(1) ); + vecPerm2 = vec_add( vec_lvsl( -1, (int*) &ogg[1][0] ), (vector unsigned char)(1) ); + v7 = vec_ld( 0, &ogg[1][0] ); + v9 = vec_ld( 0, &ogg[0][0] ); + int i; + + for ( i = 0; i+3 < numSamples >> 1; i += 4 ) { + v8 = v9; + v9 = vec_ld( 15, &ogg[0][i] ); + v0 = vec_perm( v8, v9, vecPerm1 ); + + // load ogg[1][i] to ogg[1][i+3] + v6 = v7; + v7 = vec_ld( 15, &ogg[1][i] ); + v1 = vec_perm( v6, v7, vecPerm2 ); + + // multiply + v0 = vec_madd( v0, constVec, zeroVector ); + v1 = vec_madd( v1, constVec, zeroVector ); + + // generate result vectors + v2 = vec_mergeh( v0, v1 ); + v3 = vec_mergel( v0, v1 ); + + // store results + UNALIGNED_STORE2( &dest[i*2], v2, v3 ); + } + // cleanup + for ( ; i < numSamples >> 1; i++ ) { + dest[i*2+0] = ogg[0][i] * 32768.0f; + dest[i*2+1] = ogg[1][i] * 32768.0f; + } + } + } else { + assert( 0 ); + } +} +#endif /* SOUND_DEST_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerMono + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + int i; + float inc[2]; + float spkr[4]; + + register vector float vecInc; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + register vector float vecSamplesLd1, vecSamplesLd2; + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + + register vector unsigned char permVec1 = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); //0,0,1,1 + register vector unsigned char permVec2 = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); //2,2,3,3 + register vector unsigned char permVec3 = (vector unsigned char)(16,17,18,19,16,17,18,19,20,21,22,23,20,21,22,23); //4,4,5,5 + register vector unsigned char permVec4 = (vector unsigned char)(24,25,26,27,24,25,26,27,28,29,30,31,28,29,30,31); //6,6,7,7 + + //constants + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] *= 2; + inc[1] *= 2; + + //load data into registers + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i=0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + + //load samples and mix buffers + vecSamplesLd1 = vecSamplesLast; //vec_ld( 0, &samples[i] ); + vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLast = vec_ld( 31, &samples[i] ); + + vecSamplesLd1 = vec_perm( vecSamplesLd1, vecSamplesLd2, samplesPerm ); + vecSamplesLd2 = vec_perm( vecSamplesLd2, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*2] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[i*2+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[i*2+8] ); + vecMixBuffer4 = vec_ld( 0, &mixBuffer[i*2+12] ); + + vecSamples1 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec1 ); + vecSamples2 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec2 ); + vecSamples3 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec3 ); + vecSamples4 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec4 ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + // store results + ALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + + //add for next iteration + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + } +} + +#else + +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerMono + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + + int i; + float inc[2]; + float spkr[4]; + + register vector float vecInc; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + register vector float vecSamplesLd1, vecSamplesLd2; + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + + register vector unsigned char permVec1 = (vector unsigned char)(0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); //0,0,1,1 + register vector unsigned char permVec2 = (vector unsigned char)(8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); //2,2,3,3 + register vector unsigned char permVec3 = (vector unsigned char)(16,17,18,19,16,17,18,19,20,21,22,23,20,21,22,23); //4,4,5,5 + register vector unsigned char permVec4 = (vector unsigned char)(24,25,26,27,24,25,26,27,28,29,30,31,28,29,30,31); //6,6,7,7 + + //constants + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] *= 2; + inc[1] *= 2; + + //load data into registers + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0]), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i=0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + + //load samples and mix buffers + vecSamplesLd1 = vecSamplesLast; + vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLast = vec_ld( 31, &samples[i] ); + + vecSamplesLd1 = vec_perm( vecSamplesLd1, vecSamplesLd2, samplesPerm ); + vecSamplesLd2 = vec_perm( vecSamplesLd2, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*2] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*2] ); + vecMixBuffer4 = vec_ld( 47, &mixBuffer[i*2] ); + vector float vecDestEnd = vec_ld( 63, &mixBuffer[i*2] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecMixBuffer4, mixBufferPerm ); + vecMixBuffer4 = vec_perm( vecMixBuffer4, vecDestEnd, mixBufferPerm ); + + vecSamples1 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec1 ); + vecSamples2 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec2 ); + vecSamples3 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec3 ); + vecSamples4 = vec_perm( vecSamplesLd1, vecSamplesLd2, permVec4 ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + // store results + UNALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + + //add for next iteration + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + } +} + +#endif /* SOUND_MIX_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerStereo + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + int i, k; + float inc[2]; + float spkr[4]; + + // loading buffers + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + // loading buffers + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecInc; + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + for ( k = 0; k < 2; k++ ) { + inc[k] *= 2; + } + + // load data in vectors + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i = 0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + // load mix buffers and samples + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*2] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[i*2+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[i*2+8] ); + vecMixBuffer4 = vec_ld( 0, &mixBuffer[i*2+12] ); + + vecSamples1 = vecSamplesLast; + vecSamples2 = vec_ld( 15, &samples[i*2] ); + vecSamples3 = vec_ld( 31, &samples[i*2] ); + vecSamples4 = vec_ld( 47, &samples[i*2] ); + vecSamplesLast = vec_ld( 63, &samples[i*2] ); + + vecSamples1 = vec_perm( vecSamples1, vecSamples2, samplesPerm ); + vecSamples2 = vec_perm( vecSamples2, vecSamples3, samplesPerm ); + vecSamples3 = vec_perm( vecSamples3, vecSamples4, samplesPerm ); + vecSamples4 = vec_perm( vecSamples4, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + + //store results + ALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + } +} +#else + +/* +============ +idSIMD_AltiVec::MixSoundTwoSpeakerStereo + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + + int i, k; + float inc[2]; + float spkr[4]; + // loading buffers + register vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4; + // loading buffers + register vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4; + register vector float vecSpeaker1, vecSpeaker2, vecSpeaker3, vecSpeaker4; + register vector float vecInc; + vector float fourVec = (vector float)(4.0); + vector float zeroVec = (vector float)(0.0); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + inc[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + inc[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + spkr[0] = lastV[0]; + spkr[1] = lastV[1]; + spkr[2] = lastV[0] + inc[0]; + spkr[3] = lastV[1] + inc[1]; + + for ( k = 0; k < 2; k++ ) { + inc[k] *= 2; + } + + // load data in vectors + vector float v0 = loadSplatUnalignedScalar( &inc[0] ); + vector float v1 = loadSplatUnalignedScalar( &inc[1] ); + vecInc = vec_mergeh( v0, v1 ); + + vector float v2 = loadSplatUnalignedScalar( &spkr[0] ); + vector float v3 = loadSplatUnalignedScalar( &spkr[1] ); + vector float v4 = loadSplatUnalignedScalar( &spkr[2] ); + vector float v5 = loadSplatUnalignedScalar( &spkr[3] ); + + // load spkr array + v0 = vec_mergeh( v2, v4 ); + v1 = vec_mergeh( v3, v5 ); + vecSpeaker1 = vec_mergeh( v0, v1 ); + + vecSpeaker2 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker3, vecInc ); + vecInc = vec_madd( vecInc, fourVec, zeroVec ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 8, we don't + //need a cleanup loop + for( i = 0 ; i+7 < MIXBUFFER_SAMPLES; i += 8 ) { + // load mix buffers and samples + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*2] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*2] ); + vecMixBuffer4 = vec_ld( 47, &mixBuffer[i*2] ); + vector float vecDestEnd = vec_ld( 63, &mixBuffer[i*2] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecMixBuffer4, mixBufferPerm ); + vecMixBuffer4 = vec_perm( vecMixBuffer4, vecDestEnd, mixBufferPerm ); + + vecSamples1 = vecSamplesLast; + vecSamples2 = vec_ld( 15, &samples[i*2] ); + vecSamples3 = vec_ld( 31, &samples[i*2] ); + vecSamples4 = vec_ld( 47, &samples[i*2] ); + vecSamplesLast = vec_ld( 63, &samples[i*2] ); + + vecSamples1 = vec_perm( vecSamples1, vecSamples2, samplesPerm ); + vecSamples2 = vec_perm( vecSamples2, vecSamples3, samplesPerm ); + vecSamples3 = vec_perm( vecSamples3, vecSamples4, samplesPerm ); + vecSamples4 = vec_perm( vecSamples4, vecSamplesLast, samplesPerm ); + + vecMixBuffer1 = vec_madd( vecSamples1, vecSpeaker1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSpeaker2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSpeaker3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSpeaker4, vecMixBuffer4 ); + + vecSpeaker1 = vec_add( vecSpeaker1, vecInc ); + vecSpeaker2 = vec_add( vecSpeaker2, vecInc ); + vecSpeaker3 = vec_add( vecSpeaker3, vecInc ); + vecSpeaker4 = vec_add( vecSpeaker4, vecInc ); + + // store results + UNALIGNED_STORE4( &mixBuffer[i*2], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4 ); + } +} + +#endif /* SOUND_DEST_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerMono + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + float incL[24]; + float sL[24]; + int i, k; + + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4, vecIncl5, vecIncl6, vecIncl7; + vector float vecSL1, vecSL2, vecSL3, vecSL4, vecSL5, vecSL6, vecSL7; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4, vecSamples5, vecSamples6; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6; + // permute vectors for sample + vector unsigned char samplePerm2 = (vector unsigned char)( 0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + vector unsigned char samplePerm5 = (vector unsigned char)( 8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = incL[12] = incL[18] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = incL[13] = incL[19] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = incL[14] = incL[20] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = incL[15] = incL[21] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = incL[16] = incL[22] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = incL[17] = incL[23] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + for ( k = 0; k < 6; k++ ) { + sL[k] = lastV[k]; + } + for ( k = 6; k < 12; k++ ) { + sL[k] = lastV[k-6] + incL[k]; + } + for ( k = 12; k < 18; k++ ) { + sL[k] = lastV[k-12] + incL[k] + incL[k]; + } + for ( k = 18; k < 24; k++ ) { + sL[k] = lastV[k-18] + incL[k] + incL[k] + incL[k]; + } + + // multiply by 2 since doing 12 at a time + for ( k = 0; k < 24; k++ ) { + incL[k] *= 4; + } + + //load the data + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + vecIncl5 = vec_ld( 63, &incL[0] ); + vecIncl6 = vec_ld( 79, &incL[0] ); + vecIncl7 = vec_ld( 95, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + vecIncl4 = vec_perm( vecIncl4, vecIncl5, incPerm ); + vecIncl5 = vec_perm( vecIncl5, vecIncl6, incPerm ); + vecIncl6 = vec_perm( vecIncl6, vecIncl7, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + vecSL5 = vec_ld( 63, &sL[0] ); + vecSL6 = vec_ld( 79, &sL[0] ); + vecSL7 = vec_ld( 95, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + vecSL4 = vec_perm( vecSL4, vecSL5, slPerm ); + vecSL5 = vec_perm( vecSL5, vecSL6, slPerm ); + vecSL6 = vec_perm( vecSL6, vecSL7, slPerm ); + + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 4, we don't + //need a cleanup loop + for( i = 0; i <= MIXBUFFER_SAMPLES - 4; i += 4 ) { + //load mix buffer into vectors, assume aligned + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*6] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[(i*6)+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[(i*6)+8] ); + vecMixBuffer4 = vec_ld( 0, &mixBuffer[(i*6)+12] ); + vecMixBuffer5 = vec_ld( 0, &mixBuffer[(i*6)+16] ); + vecMixBuffer6 = vec_ld( 0, &mixBuffer[(i*6)+20] ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want + vecSamples1 = vec_splat( vecSamplesLd, 0 ); + vecSamples2 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm2 ); + vecSamples3 = vec_splat( vecSamplesLd, 1 ); + vecSamples4 = vec_splat( vecSamplesLd, 2 ); + vecSamples5 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm5 ); + vecSamples6 = vec_splat( vecSamplesLd, 3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSL4, vecMixBuffer4 ); + vecMixBuffer5 = vec_madd( vecSamples5, vecSL5, vecMixBuffer5 ); + vecMixBuffer6 = vec_madd( vecSamples6, vecSL6, vecMixBuffer6 ); + + //store out results + ALIGNED_STORE6( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + vecSL4 = vec_add( vecSL4, vecIncl4 ); + vecSL5 = vec_add( vecSL5, vecIncl5 ); + vecSL6 = vec_add( vecSL6, vecIncl6 ); + } +} +#else + +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerMono + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + float incL[24]; + float sL[24]; + int i, k; + + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4, vecIncl5, vecIncl6, vecIncl7; + vector float vecSL1, vecSL2, vecSL3, vecSL4, vecSL5, vecSL6, vecSL7; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3, vecSamples4, vecSamples5, vecSamples6; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6; + // permute vectors for sample + register vector unsigned char samplePerm2 = (vector unsigned char)( 0,1,2,3,0,1,2,3,4,5,6,7,4,5,6,7); + register vector unsigned char samplePerm5 = (vector unsigned char)( 8,9,10,11,8,9,10,11,12,13,14,15,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = incL[12] = incL[18] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = incL[13] = incL[19] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = incL[14] = incL[20] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = incL[15] = incL[21] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = incL[16] = incL[22] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = incL[17] = incL[23] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + for ( k = 0; k < 6; k++ ) { + sL[k] = lastV[k]; + } + for ( k = 6; k < 12; k++ ) { + sL[k] = lastV[k-6] + incL[k]; + } + for ( k = 12; k < 18; k++ ) { + sL[k] = lastV[k-12] + incL[k] + incL[k]; + } + for ( k = 18; k < 24; k++ ) { + sL[k] = lastV[k-18] + incL[k] + incL[k] + incL[k]; + } + + // multiply by 2 since doing 12 at a time + for ( k = 0; k < 24; k++ ) { + incL[k] *= 4; + } + + // load the data + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + vecIncl5 = vec_ld( 63, &incL[0] ); + vecIncl6 = vec_ld( 79, &incL[0] ); + vecIncl7 = vec_ld( 95, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + vecIncl4 = vec_perm( vecIncl4, vecIncl5, incPerm ); + vecIncl5 = vec_perm( vecIncl5, vecIncl6, incPerm ); + vecIncl6 = vec_perm( vecIncl6, vecIncl7, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + vecSL5 = vec_ld( 63, &sL[0] ); + vecSL6 = vec_ld( 79, &sL[0] ); + vecSL7 = vec_ld( 95, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + vecSL4 = vec_perm( vecSL4, vecSL5, slPerm ); + vecSL5 = vec_perm( vecSL5, vecSL6, slPerm ); + vecSL6 = vec_perm( vecSL6, vecSL7, slPerm ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + //since MIXBUFFER_SAMPLES is a multiple of 4, we don't + //need a cleanup loop + for( i = 0; i <= MIXBUFFER_SAMPLES - 4; i += 4 ) { + //load mix buffer into vectors + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*6] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*6] ); + vecMixBuffer4 = vec_ld( 47, &mixBuffer[i*6] ); + vecMixBuffer5 = vec_ld( 63, &mixBuffer[i*6] ); + vecMixBuffer6 = vec_ld( 79, &mixBuffer[i*6] ); + vector float vecDestEnd = vec_ld( 95, &mixBuffer[i*6] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecMixBuffer4, mixBufferPerm ); + vecMixBuffer4 = vec_perm( vecMixBuffer4, vecMixBuffer5, mixBufferPerm ); + vecMixBuffer5 = vec_perm( vecMixBuffer5, vecMixBuffer6, mixBufferPerm ); + vecMixBuffer6 = vec_perm( vecMixBuffer6, vecDestEnd, mixBufferPerm ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want + vecSamples1 = vec_splat( vecSamplesLd, 0 ); + vecSamples2 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm2 ); + vecSamples3 = vec_splat( vecSamplesLd, 1 ); + vecSamples4 = vec_splat( vecSamplesLd, 2 ); + vecSamples5 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm5 ); + vecSamples6 = vec_splat( vecSamplesLd, 3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + vecMixBuffer4 = vec_madd( vecSamples4, vecSL4, vecMixBuffer4 ); + vecMixBuffer5 = vec_madd( vecSamples5, vecSL5, vecMixBuffer5 ); + vecMixBuffer6 = vec_madd( vecSamples6, vecSL6, vecMixBuffer6 ); + + // store results + UNALIGNED_STORE6( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3, vecMixBuffer4, vecMixBuffer5, vecMixBuffer6 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + vecSL4 = vec_add( vecSL4, vecIncl4 ); + vecSL5 = vec_add( vecSL5, vecIncl5 ); + vecSL6 = vec_add( vecSL6, vecIncl6 ); + } +} + +#endif /* SOUND_MIX_ALIGNED */ + +#ifdef SOUND_MIX_ALIGNED +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerStereo + + Assumptions: + Assumes that mixBuffer starts at aligned address +============ +*/ + +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + // mixBuffer is aligned + assert( IS_16BYTE_ALIGNED( mixBuffer[0] ) ); + + float incL[12]; + float sL[12]; + int i; + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4; + vector float vecSL1, vecSL2, vecSL3, vecSL4; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3; + // permute vectors for sample + vector unsigned char samplePerm1 = (vector unsigned char)( 0,1,2,3,4,5,6,7,0,1,2,3,0,1,2,3); + vector unsigned char samplePerm3 = (vector unsigned char)( 8,9,10,11,8,9,10,11,8,9,10,11,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + sL[0] = lastV[0]; + sL[1] = lastV[1]; + sL[2] = lastV[2]; + sL[3] = lastV[3]; + sL[4] = lastV[4]; + sL[5] = lastV[5]; + sL[6] = lastV[0] + incL[0]; + sL[7] = lastV[1] + incL[1]; + sL[8] = lastV[2] + incL[2]; + sL[9] = lastV[3] + incL[3]; + sL[10] = lastV[4] + incL[4]; + sL[11] = lastV[5] + incL[5]; + + // multiply by 2 since doing 12 at a time + incL[0] *= 2; + incL[1] *= 2; + incL[2] *= 2; + incL[3] *= 2; + incL[4] *= 2; + incL[5] *= 2; + incL[6] *= 2; + incL[7] *= 2; + incL[8] *= 2; + incL[9] *= 2; + incL[10] *= 2; + incL[11] *= 2; + + //we aligned this data, so load it up + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + + //load mix buffer into vectors, assume aligned + vecMixBuffer1 = vec_ld( 0, &mixBuffer[i*6] ); + vecMixBuffer2 = vec_ld( 0, &mixBuffer[(i*6)+4] ); + vecMixBuffer3 = vec_ld( 0, &mixBuffer[(i*6)+8] ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i*2] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want. For the 2nd vector, + //the order happens to be the same as the order we loaded them + //in, so there's no need to permute that one + vecSamples1 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm1 ); + vecSamples2 = vecSamplesLd; + vecSamples3 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + + //store out results + ALIGNED_STORE3( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + } +} +#else + +/* +============ +idSIMD_AltiVec::MixSoundSixSpeakerStereo + + Assumptions: + No assumptions +============ +*/ +void VPCALL idSIMD_AltiVec::MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + + float incL[12]; + float sL[12]; + + int i; + vector float vecIncl1, vecIncl2, vecIncl3, vecIncl4; + vector float vecSL1, vecSL2, vecSL3, vecSL4; + vector float vecSamplesLd; + vector float vecSamples1, vecSamples2, vecSamples3; + vector float vecMixBuffer1, vecMixBuffer2, vecMixBuffer3; + // permute vectors for sample + vector unsigned char samplePerm1 = (vector unsigned char)( 0,1,2,3,4,5,6,7,0,1,2,3,0,1,2,3); + vector unsigned char samplePerm3 = (vector unsigned char)( 8,9,10,11,8,9,10,11,8,9,10,11,12,13,14,15); + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + // incL array, 6 elements repeated + incL[0] = incL[6] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL[1] = incL[7] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL[2] = incL[8] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL[3] = incL[9] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL[4] = incL[10] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL[5] = incL[11] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + // sL array repeated + sL[0] = lastV[0]; + sL[1] = lastV[1]; + sL[2] = lastV[2]; + sL[3] = lastV[3]; + sL[4] = lastV[4]; + sL[5] = lastV[5]; + sL[6] = lastV[0] + incL[0]; + sL[7] = lastV[1] + incL[1]; + sL[8] = lastV[2] + incL[2]; + sL[9] = lastV[3] + incL[3]; + sL[10] = lastV[4] + incL[4]; + sL[11] = lastV[5] + incL[5]; + + // multiply by 2 since doing 12 at a time + incL[0] *= 2; + incL[1] *= 2; + incL[2] *= 2; + incL[3] *= 2; + incL[4] *= 2; + incL[5] *= 2; + incL[6] *= 2; + incL[7] *= 2; + incL[8] *= 2; + incL[9] *= 2; + incL[10] *= 2; + incL[11] *= 2; + + // load the data + vector unsigned char incPerm = vec_add( vec_lvsl( -1, &incL[0] ), (vector unsigned char)(1) ); + vector unsigned char slPerm = vec_add( vec_lvsl( -1, &sL[0] ), (vector unsigned char)(1) ); + vecIncl1 = vec_ld( 0, &incL[0] ); + vecIncl2 = vec_ld( 15, &incL[0] ); + vecIncl3 = vec_ld( 31, &incL[0] ); + vecIncl4 = vec_ld( 47, &incL[0] ); + + vecIncl1 = vec_perm( vecIncl1, vecIncl2, incPerm ); + vecIncl2 = vec_perm( vecIncl2, vecIncl3, incPerm ); + vecIncl3 = vec_perm( vecIncl3, vecIncl4, incPerm ); + + vecSL1 = vec_ld( 0, &sL[0] ); + vecSL2 = vec_ld( 15, &sL[0] ); + vecSL3 = vec_ld( 31, &sL[0] ); + vecSL4 = vec_ld( 47, &sL[0] ); + + vecSL1 = vec_perm( vecSL1, vecSL2, slPerm ); + vecSL2 = vec_perm( vecSL2, vecSL3, slPerm ); + vecSL3 = vec_perm( vecSL3, vecSL4, slPerm ); + + vector unsigned char samplesPerm = vec_add( vec_lvsl( -1, &samples[0] ), (vector unsigned char)(1) ); + vector unsigned char mixBufferPerm = vec_add( vec_lvsl( -1, &mixBuffer[0] ), (vector unsigned char)(1) ); + vector float vecSamplesLast = vec_ld( 0, &samples[0] ); + vector float vecDest = vec_ld( 0, &mixBuffer[0] ); + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + + //load mix buffer into vectors + vecMixBuffer1 = vecDest; + vecMixBuffer2 = vec_ld( 15, &mixBuffer[i*6] ); + vecMixBuffer3 = vec_ld( 31, &mixBuffer[i*6] ); + vector float vecDestEnd = vec_ld( 47, &mixBuffer[i*6] ); + + vecMixBuffer1 = vec_perm( vecMixBuffer1, vecMixBuffer2, mixBufferPerm ); + vecMixBuffer2 = vec_perm( vecMixBuffer2, vecMixBuffer3, mixBufferPerm ); + vecMixBuffer3 = vec_perm( vecMixBuffer3, vecDestEnd, mixBufferPerm ); + + //load samples into vector + vector float vecSamplesLd2 = vec_ld( 15, &samples[i*2] ); + vecSamplesLd = vec_perm( vecSamplesLast, vecSamplesLd2, samplesPerm ); + vecSamplesLast = vecSamplesLd2; + + //permute to get them ordered how we want. For the 2nd vector, + //the order happens to be the same as the order we loaded them + //in, so there's no need to permute that one + vecSamples1 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm1 ); + vecSamples2 = vecSamplesLd; + vecSamples3 = vec_perm( vecSamplesLd, vecSamplesLd, samplePerm3 ); + + //do calculation + vecMixBuffer1 = vec_madd( vecSamples1, vecSL1, vecMixBuffer1 ); + vecMixBuffer2 = vec_madd( vecSamples2, vecSL2, vecMixBuffer2 ); + vecMixBuffer3 = vec_madd( vecSamples3, vecSL3, vecMixBuffer3 ); + + // store results + UNALIGNED_STORE3( &mixBuffer[i*6], vecMixBuffer1, vecMixBuffer2, vecMixBuffer3 ); + + // add for next iteration + vecSL1 = vec_add( vecSL1, vecIncl1 ); + vecSL2 = vec_add( vecSL2, vecIncl2 ); + vecSL3 = vec_add( vecSL3, vecIncl3 ); + } +} + +#endif //SOUND_MIX_ALIGNED + +/* +============ +idSIMD_AltiVec::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_AltiVec::MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) { + //this is basically a clamp for sound mixing + register vector float v0, v1, v2, v3, v4, v5, v6, v7; + register vector signed int vi0, vi1, vi2, vi3; + register vector signed short vs0, vs1; + register vector float minVec, maxVec, constVec; + int i = 0; + + //unaligned at start, since samples is not 16-byte aligned + for ( ; NOT_16BYTE_ALIGNED( samples[i] ) && ( i < numSamples ); i++ ) { + samples[i] = mixBuffer[i] <= -32768.0f ? -32768 : mixBuffer[i] >= 32767.0f ? 32767 : (short) mixBuffer[i]; + } + + constVec = (vector float)(65536.0f); + + //splat min/max into a vector + minVec = (vector float)(-32768.0f); + maxVec = (vector float)(32767.0f); + + vector float vecOld = vec_ld( 0, &mixBuffer[i] ); + vector unsigned char permVec = vec_add( vec_lvsl( -1, &mixBuffer[i] ), (vector unsigned char)(1) ); + + //vectorize! + for ( ; i+15 < numSamples; i += 16 ) { + //load source + v0 = vecOld; + v1 = vec_ld( 15, &mixBuffer[i] ); + v2 = vec_ld( 31, &mixBuffer[i] ); + v3 = vec_ld( 31, &mixBuffer[i] ); + vecOld = vec_ld( 47, &mixBuffer[i] ); + + v0 = vec_perm( v0, v1, permVec ); + v1 = vec_perm( v1, v2, permVec ); + v2 = vec_perm( v2, v3, permVec ); + v3 = vec_perm( v3, vecOld, permVec ); + + //apply minimum + v4 = vec_max( v0, minVec ); + v5 = vec_max( v1, minVec ); + v6 = vec_max( v2, minVec ); + v7 = vec_max( v3, minVec ); + + //apply maximum + v4 = vec_min( v4, maxVec ); + v5 = vec_min( v5, maxVec ); + v6 = vec_min( v6, maxVec ); + v7 = vec_min( v7, maxVec ); + + // convert floats to ints + vi0 = vec_cts( v4, 0 ); + vi1 = vec_cts( v5, 0 ); + vi2 = vec_cts( v6, 0 ); + vi3 = vec_cts( v7, 0 ); + + // pack ints into shorts + vs0 = vec_pack( vi0, vi1 ); + vs1 = vec_pack( vi2, vi3 ); + ALIGNED_STORE2( &samples[i], vs0, vs1 ); + } + + //handle cleanup + for ( ; i < numSamples ; i++ ) { + samples[i] = mixBuffer[i] <= -32768.0f ? -32768 : mixBuffer[i] >= 32767.0f ? 32767 : (short) mixBuffer[i]; + } +} +#endif /* ENABLE_SOUND_ROUTINES */ + +#endif /* MACOS_X */ diff --git a/src/idlib/math/Simd_AltiVec.h b/src/idlib/math/Simd_AltiVec.h new file mode 100644 index 0000000..d7ea722 --- /dev/null +++ b/src/idlib/math/Simd_AltiVec.h @@ -0,0 +1,256 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MATH_SIMD_ALTIVEC_H__ +#define __MATH_SIMD_ALTIVEC_H__ + +/* +=============================================================================== + + AltiVec implementation of idSIMDProcessor + +=============================================================================== +*/ + +// Defines for enabling parts of the library + +// Turns on/off the simple math routines (add, sub, div, etc) +#define ENABLE_SIMPLE_MATH + +// Disable dot routines since they introduce artifacts with the shadows. I can't measure +// a performance improvement with sampling tools with DOT enabled, so disable for now +// Turns on/off the dot routines +//#define ENABLE_DOT + +// Turns on/off the compare routines +#define ENABLE_COMPARES + +// The MinMax routines introduce a couple of bugs. In the bathroom of the alphalabs2 map, the +// wrong surface appears in the mirror at times. It also introduces a noticable delay when map +// data is loaded such as going through doors. +// Turns on/off MinMax routines +//#define ENABLE_MINMAX + +// Turns on/off Clamp routines +#define ENABLE_CLAMP + +// Disabling because members of idAFConstraint aren't aligned properly +// Turns on/off XXX16 routines +//#define ENABLE_16ROUTINES + +// Turns on/off LowerTriangularSolve, LowerTriangularSolveTranspose, and MatX_LDLTFactor +#define ENABLE_LOWER_TRIANGULAR + +// Turns on/off TracePointCull, DecalPointCull, and OverlayPoint +// The Enable_Cull routines breaks the g_decals functionality, DecalPointCull is +// the likely suspect. Bullet holes do not appear on the walls when this optimization +// is enabled. +//#define ENABLE_CULL + +// Turns on/off DeriveTriPlanes, DeriveTangents, DeriveUnsmoothedTangents, NormalizeTangents +#define ENABLE_DERIVE + +// Turns on/off CreateTextureSpaceLightVectors, CreateShadowCache, CreateVertexProgramShadowCache +#define ENABLE_CREATE + +// Turns on/off the sound routines +#define ENABLE_SOUND_ROUTINES + +// Turns on/off the stuff that isn't on elsewhere +// Currently: BlendJoints, TransformJoints, UntransformJoints, ConvertJointQuatsToJointMats, and +// ConvertJointMatsToJointQuats +//#define LIVE_VICARIOUSLY + +// This assumes that the dest array to the sound functions is aligned. If this is not true, we take a large +// performance hit from having to do unaligned stores +//#define SOUND_DEST_ALIGNED + +// This assumes that the finalMixBuffer array to the sound functions is aligned. If this is not true, we take a large +// performance hit from having to do unaligned stores +//#define SOUND_MIX_ALIGNED + +// This assumes that the vertexCache array to CreateShadowCache and CreateVertexProgramShadowCache is aligned. If it's not, +// then we take a big performance hit from unaligned stores. +//#define VERTEXCACHE_ALIGNED + +// This turns on support for PPC intrinsics in the SIMD_AltiVec.cpp file. Right now it's only used for frsqrte. GCC +// supports these intrinsics but XLC does not. +#define PPC_INTRINSICS + +// This assumes that the idDrawVert array that is used in DeriveUnsmoothedTangents is aligned. If its not aligned, +// then we don't get any speedup +//#define DERIVE_UNSMOOTH_DRAWVERT_ALIGNED + +// The QuakeIV definition of idDrawVert is 64 bytes, so enable this optimization +#define DRAWVERT_PADDED + +class idSIMD_AltiVec : public idSIMD_Generic { +#if defined(MACOS_X) && defined(__ppc__) +public: + + virtual const char * VPCALL GetName( void ) const; + +#ifdef ENABLE_SIMPLE_MATH + // Basic math, works for both aligned and unaligned data + virtual void VPCALL Add( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Add( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Sub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Sub( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Mul( float *dst, const float constant, const float *src, const int count); + virtual void VPCALL Mul( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Div( float *dst, const float constant, const float *divisor, const int count ); + virtual void VPCALL Div( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulAdd( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulAdd( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulSub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulSub( float *dst, const float *src0, const float *src1, const int count ); +#endif + +#ifdef ENABLE_DOT + // Dot products, expects data structures in contiguous memory + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ); + virtual void VPCALL Dot( float &dot, const float *src1, const float *src2, const int count ); +#endif + +#ifdef ENABLE_COMPARES + // Comparisons, works for both aligned and unaligned data + virtual void VPCALL CmpGT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); +#endif + +#ifdef ENABLE_MINMAX + // Min/Max. Expects data structures in contiguous memory + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ); + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ); +#endif + +#ifdef ENABLE_CLAMP + // Clamp operations. Works for both aligned and unaligned data + virtual void VPCALL Clamp( float *dst, const float *src, const float min, const float max, const int count ); + virtual void VPCALL ClampMin( float *dst, const float *src, const float min, const int count ); + virtual void VPCALL ClampMax( float *dst, const float *src, const float max, const int count ); +#endif + + // These are already using memcpy and memset functions. Leaving default implementation +// virtual void VPCALL Memcpy( void *dst, const void *src, const int count ); +// virtual void VPCALL Memset( void *dst, const int val, const int count ); + +#ifdef ENABLE_16ROUTINES + // Operations that expect 16-byte aligned data and 16-byte padded memory (with zeros), generally faster + virtual void VPCALL Zero16( float *dst, const int count ); + virtual void VPCALL Negate16( float *dst, const int count ); + virtual void VPCALL Copy16( float *dst, const float *src, const int count ); + virtual void VPCALL Add16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Sub16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Mul16( float *dst, const float *src1, const float constant, const int count ); + virtual void VPCALL AddAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL SubAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL MulAssign16( float *dst, const float constant, const int count ); +#endif + +// Most of these deal with tiny matrices or vectors, generally not worth altivec'ing since +// the scalar code is already really fast + +// virtual void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); +// virtual void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); +// virtual void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + +#ifdef ENABLE_LOWER_TRIANGULAR + virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ); + virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ); +#if __GNUC__ >= 4 + virtual bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); +#else + virtual unsigned char VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); +#endif +#endif +#ifdef LIVE_VICARIOUSLY + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ); +#endif + +#ifdef LIVE_VICARIOUSLY + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); +#endif + +#ifdef ENABLE_CULL + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ); +#endif + +#ifdef ENABLE_DERIVE + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ); + virtual void VPCALL NormalizeTangents( idDrawVert *verts, const int numVerts ); +#endif + +#ifdef ENABLE_CREATE + virtual void VPCALL CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual int VPCALL CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ); + virtual int VPCALL CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ); +#endif + +#ifdef ENABLE_SOUND_ROUTINES + // Sound upsampling and mixing routines, works for aligned and unaligned data + virtual void VPCALL UpSamplePCMTo44kHz( float *dest, const short *pcm, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ); +#endif +#endif + +}; + +#endif /* !__MATH_SIMD_ALTIVEC_H__ */ diff --git a/src/idlib/math/Simd_InstructionMacros.h b/src/idlib/math/Simd_InstructionMacros.h new file mode 100644 index 0000000..ce368db --- /dev/null +++ b/src/idlib/math/Simd_InstructionMacros.h @@ -0,0 +1,523 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SIMD_SSE_MACROS_H__ +#define __SIMD_SSE_MACROS_H__ + +//#include // MSVC intrinsic SSE is very poor + +/* +=============================================================================== + + Instruction Macros. + + The first argument of an instruction macro is the destination and + the second argument is the source operand. For most instructions + the destination operand can be _xmm0 to _xmm7 only. The source + operand can be any one of the registers _xmm0 to _xmm7 or _eax, + _ecx, _edx, _esp, _ebp, _ebx, _esi, or _edi that contains the + effective address. + + For instance: haddps xmm0, xmm1 + becomes: _haddps( _xmm0, _xmm1 ) + and: haddps xmm0, [esi] + becomes: _haddps( _xmm0, _esi ) + + The ADDRESS_ADD_C macro can be used when the effective source address + is formed by adding a constant to a general purpose register. + The constant must be in the range [-128, 127]. Use the ADDRESS_ADD_LC + macro if the constant is not in this range. + + For instance: haddps xmm0, [esi+48] + becomes: _haddps( _xmm0, ADDRESS_ADD_C( _esi, 48 ) ) + + The ADDRESS_ADD_R macro can be used when the effective source address + is formed by adding two general purpose registers. + + For instance: haddps xmm0, [esi+eax] + becomes: _haddps( _xmm0, ADDRESS_ADD_R( _esi, _eax ) ) + + The ADDRESS_ADD_RC macro can be used when the effective source address + is formed by adding two general purpose registers and a constant. + The constant must be in the range [-128, 127]. Use the ADDRESS_ADD_RLC + macro if the constant is not in this range. + + For instance: haddps xmm0, [esi+eax+48] + becomes: _haddps( _xmm0, ADDRESS_ADD_RC( _esi, _eax, 48 ) ) + + The ADDRESS_ADD_RS macro can be used when the effective source address is formed + by adding a scaled general purpose register to another general purpose register. + The scale must be either 1, 2, 4 or 8. + + For instance: haddps xmm0, [esi+eax*4] + becomes: _haddps( _xmm0, ADDRESS_ADD_RS( _esi, _eax, 4 ) ) + + The ADDRESS_ADD_RSC macro can be used when the effective source address is formed + by adding a scaled general purpose register to another general purpose register and + also adding a constant. The scale must be either 1, 2, 4 or 8. The constant must + be in the range [-128, 127]. Use the ADDRESS_ADD_RSLC macro if the constant is not + in this range. + + For instance: haddps xmm0, [esi+eax*4+64] + becomes: _haddps( _xmm0, ADDRESS_ADD_RSC( _esi, _eax, 4, 64 ) ) + +=============================================================================== +*/ + +/* +=============================================================================== + + Register constants. + +=============================================================================== +*/ + +#define _eax 0x00 +#define _ecx 0x01 +#define _edx 0x02 +#define _ebx 0x03 +#define _esp 0x04 +#define _ebp 0x05 +#define _esi 0x06 +#define _edi 0x07 + +#define _mm0 0xC0 +#define _mm1 0xC1 +#define _mm2 0xC2 +#define _mm3 0xC3 +#define _mm4 0xC4 +#define _mm5 0xC5 +#define _mm6 0xC6 +#define _mm7 0xC7 + +#define _xmm0 0xC0 +#define _xmm1 0xC1 +#define _xmm2 0xC2 +#define _xmm3 0xC3 +#define _xmm4 0xC4 +#define _xmm5 0xC5 +#define _xmm6 0xC6 +#define _xmm7 0xC7 + +/* +=============================================================================== + + Addressing modes. + +=============================================================================== +*/ + +#define RSCALE( s ) ( ((s)&2)<<5 ) | ( ((s)&4)<<5 ) | ( ((s)&8)<<3 ) | ( ((s)&8)<<4 ) + +#define ADDRESS_ADD_C( reg0, constant ) 0x40 | ( reg0 & 7 ) \ + _asm _emit constant + +#define ADDRESS_ADD_LC( reg0, constant ) 0x80 | ( reg0 & 7 ) \ + _asm _emit ( ( (constant) >> 0 ) & 255 ) \ + _asm _emit ( ( (constant) >> 8 ) & 255 ) \ + _asm _emit ( ( (constant) >> 16 ) & 255 ) \ + _asm _emit ( ( (constant) >> 24 ) & 255 ) + +#define ADDRESS_ADD_R( reg0, reg1 ) 0x04 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) + +#define ADDRESS_ADD_RC( reg0, reg1, constant ) 0x44 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) \ + _asm _emit constant + +#define ADDRESS_ADD_RLC( reg0, reg1, constant ) 0x84 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) \ + _asm _emit ( ( (constant) >> 0 ) & 255 ) \ + _asm _emit ( ( (constant) >> 8 ) & 255 ) \ + _asm _emit ( ( (constant) >> 16 ) & 255 ) \ + _asm _emit ( ( (constant) >> 24 ) & 255 ) + +#define ADDRESS_ADD_RS( reg0, reg1, scale ) 0x04 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) | RSCALE( scale ) + +#define ADDRESS_ADD_RSC( reg0, reg1, scale, constant ) 0x44 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) | RSCALE( scale ) \ + _asm _emit constant + +#define ADDRESS_ADD_RSLC( reg0, reg1, scale, constant ) 0x84 \ + _asm _emit ( ( reg1 & 7 ) << 3 ) | ( reg0 & 7 ) | RSCALE( scale ) \ + _asm _emit ( ( (constant) >> 0 ) & 255 ) \ + _asm _emit ( ( (constant) >> 8 ) & 255 ) \ + _asm _emit ( ( (constant) >> 16 ) & 255 ) \ + _asm _emit ( ( (constant) >> 24 ) & 255 ) + +/* +=============================================================================== + + Macros for third operand of shuffle/swizzle instructions. + +=============================================================================== +*/ + +#define SHUFFLE_PS( x, y, z, w ) (( (x) & 3 ) << 6 | ( (y) & 3 ) << 4 | ( (z) & 3 ) << 2 | ( (w) & 3 )) +#define R_SHUFFLE_PS( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 )) + +#define SHUFFLE_PD( x, y ) (( (x) & 1 ) << 1 | ( (y) & 1 )) +#define R_SHUFFLE_PD( x, y ) (( (y) & 1 ) << 1 | ( (x) & 1 )) + +#define SHUFFLE_D( x, y, z, w ) (( (x) & 3 ) << 6 | ( (y) & 3 ) << 4 | ( (z) & 3 ) << 2 | ( (w) & 3 )) +#define R_SHUFFLE_D( x, y, z, w ) (( (w) & 3 ) << 6 | ( (z) & 3 ) << 4 | ( (y) & 3 ) << 2 | ( (x) & 3 )) + +/* +=============================================================================== + + SSE compare instructions. + +=============================================================================== +*/ + +#define _EQ 0 +#define _LT 1 +#define _LE 2 +#define _UNORDERED 3 +#define _NEQ 4 +#define _NLT 5 +#define _NLE 6 +#define _ORDERED 7 + +#define _cmpps( dst, src, cond ) \ + _asm _emit 0x0F \ + _asm _emit 0xC2 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src \ + _asm _emit cond + +// Equal ( dst[0-3] = ( dst[0-3] == src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpeps( dst, src ) _cmpps( dst, src, _EQ ) +#define _cmpeqps( dst, src ) _cmpps( dst, src, _EQ ) + +// Not Equal ( dst[0-3] = ( dst[0-3] != src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpneps( dst, src ) _cmpps( dst, src, _NEQ ) +#define _cmpneqps( dst, src ) _cmpps( dst, src, _NEQ ) + +// Less Than ( dst[0-3] = ( dst[0-3] < src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpltps( dst, src ) _cmpps( dst, src, _LT ) + +// Less Equal ( dst[0-3] = ( dst[0-3] <= src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpleps( dst, src ) _cmpps( dst, src, _LE ) + +// Greater Equal ( dst[0-3] = ( dst[0-3] >= src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgeps( dst, src ) _cmpps( dst, src, _NLT ) + +// Greater Than ( dst[0-3] = ( dst[0-3] > src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgtps( dst, src ) _cmpps( dst, src, _NLE ) + +// Not Less Than ( dst[0-3] = ( dst[0-3] >= src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnltps( dst, src ) _cmpps( dst, src, _NLT ) + +// Not Less Equal ( dst[0-3] = ( dst[0-3] > src[0-3] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnleps( dst, src ) _cmpps( dst, src, _NLE ) + +// Ordered ( dst[0-3] = ( dst[0-3] != NaN && src[0-3] != NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpordps( dst, src ) _cmpps( dst, src, _ORDERED ) + +// Unordered ( dst[0-3] = ( dst[0-3] == NaN || src[0-3] == NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpunordps( dst, src ) _cmpps( dst, src, _UNORDERED ) + +#define _cmpss( dst, src, cond ) \ + _asm _eint 0xF3 \ + _asm _emit 0x0F \ + _asm _emit 0xC2 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src \ + _asm _emit cond + +// Equal ( dst[0] = ( dst[0] == src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpess( dst, src ) _cmpss( dst, src, _EQ ) +#define _cmpeqss( dst, src ) _cmpss( dst, src, _EQ ) + +// Not Equal ( dst[0] = ( dst[0] != src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpness( dst, src ) _cmpss( dst, src, _NEQ ) +#define _cmpneqss( dst, src ) _cmpss( dst, src, _NEQ ) + +// Less Than ( dst[0] = ( dst[0] < src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpltss( dst, src ) _cmpss( dst, src, _LT ) + +// Less Equal ( dst[0] = ( dst[0] <= src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpless( dst, src ) _cmpss( dst, src, _LE ) + +// Greater Equal ( dst[0] = ( dst[0] >= src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgess( dst, src ) _cmpss( dst, src, _NLT ) + +// Greater Than ( dst[0] = ( dst[0] > src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpgtss( dst, src ) _cmpss( dst, src, _NLE ) + +// Not Less Than ( dst[0] = ( dst[0] >= src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnltss( dst, src ) _cmpss( dst, src, _NLT ) + +// Not Less Equal ( dst[0] = ( dst[0] > src[0] ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpnless( dst, src ) _cmpss( dst, src, _NLE ) + +// Ordered ( dst[0] = ( dst[0] != NaN && src[0] != NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpordss( dst, src ) _cmpss( dst, src, _ORDERED ) + +// Unordered ( dst[0] = ( dst[0] == NaN || src[0] == NaN ) ? 0xFFFFFFFF : 0x00000000 ) +#define _cmpunordss( dst, src ) _cmpss( dst, src, _UNORDERED ) + +/* +=============================================================================== + + SSE3 instructions. + +=============================================================================== +*/ + +// Packed Single-FP Add/Subtract ( dst[0]=dst[0]+src[0], dst[1]=dst[1]-src[1], dst[2]=dst[2]+src[2], dst[3]=dst[3]-src[3] ) +#define _addsubps( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0xD0 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Double-FP Add/Subtract ( dst[0]=dst[0]+src[0], dst[1]=dst[1]-src[1] ) +#define _addsubpd( dst, src ) \ + _asm _emit 0x66 \ + _asm _emit 0x0F \ + _asm _emit 0xD0 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Single-FP Horizontal Add ( dst[0]=dst[0]+dst[1], dst[1]=dst[2]+dst[3], dst[2]=src[0]+src[1], dst[3]=src[2]+src[3] ) +#define _haddps( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x7C \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Double-FP Horizontal Add ( dst[0]=dst[0]+dst[1], dst[1]=src[0]+src[1] ) +#define _haddpd( dst, src ) \ + _asm _emit 0x66 \ + _asm _emit 0x0F \ + _asm _emit 0x7C \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Single-FP Horizontal Subtract ( dst[0]=dst[0]-dst[1], dst[1]=dst[2]-dst[3], dst[2]=src[0]-src[1], dst[3]=src[2]-src[3] ) +#define _hsubps( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x7D \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Packed Double-FP Horizontal Subtract ( dst[0]=dst[0]-dst[1], dst[1]=src[0]-src[1] ) +#define _hsubpd( dst, src ) \ + _asm _emit 0x66 \ + _asm _emit 0x0F \ + _asm _emit 0x7D \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move Packed Single-FP Low and Duplicate ( dst[0]=src[0], dst[1]=src[0], dst[2]=src[2], dst[3]=src[2] ) +#define _movsldup( dst, src ) \ + _asm _emit 0xF3 \ + _asm _emit 0x0F \ + _asm _emit 0x12 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move One Double-FP Low and Duplicate ( dst[0]=src[0], dst[1]=src[0] ) +#define _movdldup( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x12 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move Packed Single-FP High and Duplicate ( dst[0]=src[1], dst[1]=src[1], dst[2]=src[3], dst[3]=src[3] ) +#define _movshdup( dst, src ) \ + _asm _emit 0xF3 \ + _asm _emit 0x0F \ + _asm _emit 0x16 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Move One Double-FP High and Duplicate ( dst[0]=src[1], dst[1]=src[1] ) +#define _movdhdup( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0x16 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + +// Load Unaligned Integer 128 bits +#define _lddqu( dst, src ) \ + _asm _emit 0xF2 \ + _asm _emit 0x0F \ + _asm _emit 0xF0 \ + _asm _emit ( ( dst & 7 ) << 3 ) | src + + +/* +=============================================================================== + + SSE transpose macros + +=============================================================================== +*/ + +// transpose a 4x4 matrix loaded into 4 xmm registers (reg4 is temporary) +#define TRANSPOSE_4x4( reg0, reg1, reg2, reg3, reg4 ) \ + __asm movaps reg4, reg2 /* reg4 = 8, 9, 10, 11 */ \ + __asm unpcklps reg2, reg3 /* reg2 = 8, 12, 9, 13 */ \ + __asm unpckhps reg4, reg3 /* reg4 = 10, 14, 11, 15 */ \ + __asm movaps reg3, reg0 /* reg3 = 0, 1, 2, 3 */ \ + __asm unpcklps reg0, reg1 /* reg0 = 0, 4, 1, 5 */ \ + __asm unpckhps reg3, reg1 /* reg3 = 2, 6, 3, 7 */ \ + __asm movaps reg1, reg0 /* reg1 = 0, 4, 1, 5 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 0, 1, 0, 1 ) /* reg0 = 0, 4, 8, 12 */ \ + __asm shufps reg1, reg2, R_SHUFFLE_PS( 2, 3, 2, 3 ) /* reg1 = 1, 5, 9, 13 */ \ + __asm movaps reg2, reg3 /* reg2 = 2, 6, 3, 7 */ \ + __asm shufps reg2, reg4, R_SHUFFLE_PS( 0, 1, 0, 1 ) /* reg2 = 2, 6, 10, 14 */ \ + __asm shufps reg3, reg4, R_SHUFFLE_PS( 2, 3, 2, 3 ) /* reg3 = 3, 7, 11, 15 */ + +// transpose a 4x4 matrix from memory into 4 xmm registers (reg4 is temporary) +#define TRANPOSE_4x4_FROM_MEMORY( address, reg0, reg1, reg2, reg3, reg4 ) \ + __asm movlps reg1, [address+ 0] /* reg1 = 0, 1, X, X */ \ + __asm movlps reg3, [address+ 8] /* reg3 = 2, 3, X, X */ \ + __asm movhps reg1, [address+16] /* reg1 = 0, 1, 4, 5 */ \ + __asm movhps reg3, [address+24] /* reg3 = 2, 3, 6, 7 */ \ + __asm movlps reg2, [address+32] /* reg2 = 8, 9, X, X */ \ + __asm movlps reg4, [address+40] /* reg4 = 10, 11, X, X */ \ + __asm movhps reg2, [address+48] /* reg2 = 8, 9, 12, 13 */ \ + __asm movhps reg4, [address+56] /* reg4 = 10, 11, 14, 15 */ \ + __asm movaps reg0, reg1 /* reg0 = 0, 1, 4, 5 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* reg0 = 0, 4, 8, 12 */ \ + __asm shufps reg1, reg2, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* reg1 = 1, 5, 9, 13 */ \ + __asm movaps reg2, reg3 /* reg2 = 2, 3, 6, 7 */ \ + __asm shufps reg2, reg4, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* reg2 = 2, 6, 10, 14 */ \ + __asm shufps reg3, reg4, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* reg3 = 3, 7, 11, 15 */ + +// transpose a 4x4 matrix to memory from 4 xmm registers (reg4 is temporary) +#define TRANPOSE_4x4_TO_MEMORY( address, reg0, reg1, reg2, reg3, reg4 ) \ + __asm movaps reg4, reg0 /* reg4 = 0, 4, 8, 12 */ \ + __asm unpcklps reg0, reg1 /* reg0 = 0, 1, 4, 5 */ \ + __asm unpckhps reg4, reg1 /* reg4 = 8, 9, 12, 13 */ \ + __asm movaps reg1, reg2 /* reg1 = 2, 6, 10, 14 */ \ + __asm unpcklps reg2, reg3 /* reg2 = 2, 3, 6, 7 */ \ + __asm unpckhps reg1, reg3 /* reg1 = 10, 11, 14, 15 */ \ + __asm movlps [address+ 0], reg0 /* mem0 = 0, 1, X, X */ \ + __asm movlps [address+ 8], reg2 /* mem0 = 0, 1, 2, 3 */ \ + __asm movhps [address+16], reg0 /* mem1 = 4, 5, X, X */ \ + __asm movhps [address+24], reg2 /* mem1 = 4, 5, 6, 7 */ \ + __asm movlps [address+32], reg4 /* mem2 = 8, 9, X, X */ \ + __asm movlps [address+40], reg1 /* mem2 = 8, 9, 10, 11 */ \ + __asm movhps [address+48], reg4 /* mem3 = 12, 13, X, X */ \ + __asm movhps [address+56], reg1 /* mem3 = 12, 13, 14, 15 */ + +// transpose a 4x3 matrix loaded into 3 xmm registers (reg3 is temporary) +// NOTE: the middle two colums are interchanged which is much faster and fine for most calculations +#define TRANSPOSE_4x3( reg0, reg1, reg2, reg3 ) \ + __asm movaps reg3, reg2 /* reg3 = 8, 9, 10, 11 */ \ + __asm shufps reg3, reg1, R_SHUFFLE_PS( 2, 3, 0, 1 ) /* reg3 = 10, 11, 4, 5 */ \ + __asm shufps reg2, reg0, R_SHUFFLE_PS( 0, 1, 2, 3 ) /* reg2 = 8, 9, 2, 3 */ \ + __asm shufps reg1, reg0, R_SHUFFLE_PS( 2, 3, 0, 1 ) /* reg1 = 6, 7, 0, 1 */ \ + __asm movaps reg0, reg1 /* reg0 = 6, 7, 0, 1 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 2, 0, 3, 1 ) /* reg0 = 0, 6, 3, 9 */ \ + __asm shufps reg1, reg3, R_SHUFFLE_PS( 3, 1, 2, 0 ) /* reg1 = 1, 7, 4, 10 */ \ + __asm shufps reg2, reg3, R_SHUFFLE_PS( 2, 0, 3, 1 ) /* reg2 = 2, 8, 5, 11 */ + +// transpose a 4x3 matrix from memory into 3 xmm registers (reg3 is temporary) +// NOTE: the middle two colums are interchanged which is much faster and fine for most calculations +#define TRANSPOSE_4x3_FROM_MEMORY( address, reg0, reg1, reg2, reg3 ) \ + __asm movlps reg1, [address+ 0] /* reg1 = 0, 1, X, X */ \ + __asm movlps reg2, [address+ 8] /* reg2 = 2, 3, X, X */ \ + __asm movlps reg3, [address+16] /* reg3 = 4, 5, X, X */ \ + __asm movhps reg1, [address+24] /* reg1 = 0, 1, 6, 7 */ \ + __asm movhps reg2, [address+32] /* reg2 = 2, 3, 8, 9 */ \ + __asm movhps reg3, [address+40] /* reg3 = 4, 5, 10, 11 */ \ + __asm movaps reg0, reg1 /* reg0 = 0, 1, 6, 7 */ \ + __asm shufps reg0, reg2, R_SHUFFLE_PS( 0, 2, 1, 3 ) /* reg0 = 0, 6, 3, 9 */ \ + __asm shufps reg1, reg3, R_SHUFFLE_PS( 1, 3, 0, 2 ) /* reg1 = 1, 7, 4, 10 */ \ + __asm shufps reg2, reg3, R_SHUFFLE_PS( 0, 2, 1, 3 ) /* reg2 = 2, 8, 5, 11 */ + +// transpose a 4x3 matrix to memory from 3 xmm registers (reg3 is temporary) +// NOTE: the middle two colums are interchanged which is much faster and fine for most calculations +#define TRANSPOSE_4x3_TO_MEMORY( address, reg0, reg1, reg2, reg3 ) \ + __asm movhlps reg3, reg0 /* reg3 = 3, 9, X, X */ \ + __asm unpcklps reg0, reg1 /* reg0 = 0, 1, 6, 7 */ \ + __asm unpckhps reg1, reg2 /* reg1 = 4, 5, 10, 11 */ \ + __asm unpcklps reg2, reg3 /* reg2 = 2, 3, 8, 9 */ \ + __asm movlps [address+ 0], reg0 /* mem0 = 0, 1, X, X */ \ + __asm movlps [address+ 8], reg2 /* mem0 = 0, 1, 2, 3 */ \ + __asm movlps [address+16], reg1 /* mem1 = 4, 5, X, X */ \ + __asm movhps [address+24], reg0 /* mem1 = 4, 5, 6, 7 */ \ + __asm movhps [address+32], reg2 /* mem2 = 8, 9, X, X */ \ + __asm movhps [address+40], reg1 /* mem2 = 8, 9, 10, 11 */ + +/* +=============================================================================== + + SSE2 transpose macros + +=============================================================================== +*/ + +// transpose a 4x4 integer matrix loaded into 4 xmm registers (reg4 is temporary) +// this is faster than the SSE float version because punpcklqdq has a lower latency than shufps +#define TRANSPOSE_4x4_INT( reg0, reg1, reg2, reg3, reg4 ) \ + __asm movdqa reg4, reg2 /* reg4 = 8, 9, 10, 11 */ \ + __asm punpckldq reg2, reg3 /* reg2 = 8, 12, 9, 13 */ \ + __asm punpckhdq reg4, reg3 /* reg4 = 10, 14, 11, 15 */ \ + __asm movdqa reg3, reg0 /* reg3 = 0, 1, 2, 3 */ \ + __asm punpckldq reg0, reg1 /* reg0 = 0, 4, 1, 5 */ \ + __asm punpckhdq reg3, reg1 /* reg3 = 2, 6, 3, 7 */ \ + __asm movdqa reg1, reg0 /* reg1 = 0, 4, 1, 5 */ \ + __asm punpcklqdq reg0, reg2 /* reg0 = 0, 4, 8, 12 */ \ + __asm punpckhqdq reg1, reg2 /* reg1 = 1, 5, 9, 13 */ \ + __asm movdqa reg2, reg3 /* reg2 = 2, 6, 3, 7 */ \ + __asm punpcklqdq reg2, reg4 /* reg2 = 2, 6, 10, 14 */ \ + __asm punpckhqdq reg3, reg4 /* reg3 = 3, 7, 11, 15 */ + + +/* +=============================================================================== + + Macros to create SSE constants. + +=============================================================================== +*/ + +#define ALIGN4_INIT1( X, I0 ) ALIGN16( static X[4] ) = { I0, I0, I0, I0 } +#define ALIGN4_INIT4( X, I0, I1, I2, I3 ) ALIGN16( static X[4] ) = { I0, I1, I2, I3 } +#define ALIGN8_INIT1( X, I0 ) ALIGN16( static X[8] ) = { I0, I0, I0, I0, I0, I0, I0, I0 } +#define ALIGN16_INIT1( X, I0 ) ALIGN16( static X[16] ) = { I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0, I0 } + +#define IEEE_SP_ZERO 0 +#define IEEE_SP_ONE 0x3f800000 +#define IEEE_SP_SIGN ((unsigned long) ( 1 << 31 )) +#define IEEE_SP_INF ((unsigned long) ( 1 << 23 )) + +/* +=============================================================================== + + Macro to remove boundary check from switch statements. + +=============================================================================== +*/ + +#ifdef _DEBUG +#define NODEFAULT default: assert( 0 ) +#elif _WIN32 +#define NODEFAULT default: __assume( 0 ) +#else +#define NODEFAULT +#endif + +#endif /* !__SIMD_SSE_MACROS_H__ */ diff --git a/src/idlib/math/Simd_MMX.cpp b/src/idlib/math/Simd_MMX.cpp new file mode 100644 index 0000000..3b93ec6 --- /dev/null +++ b/src/idlib/math/Simd_MMX.cpp @@ -0,0 +1,607 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" + + +//=============================================================== +// +// MMX implementation of idSIMDProcessor +// +//=============================================================== + +/* +gcc inline assembly: +inline assembly for the MMX SIMD processor written there mostly as an experiment +does not increase performance on timedemos ( nor did I expect it to, libc-i686 does the job very well already ) + +although the newer gcc can read inline asm using the intel syntax ( with minor reformatting and escaping of register names ), +it's still a long way from providing an easy compatibility with MSVC inline assembly +mostly because of the input/output registers, the clobber lists +and generally all the things gcc tries to be clever about when you give it a piece of inline assembly +( typically, compiling this at -O1 or better will produce bad code, and some of it won't compile with -fPIC either ) + +at this point, writing everything in nasm from the ground up, or using intel's compiler to produce the Simd_*.o objects is +still the best alternative +*/ + +#if defined( _WINDOWS ) || defined( __linux__ ) + +#ifdef _WINDOWS +#define EMMS_INSTRUCTION __asm emms +#else +#define EMMS_INSTRUCTION __asm__ __volatile__ ( "emms\n\t" ); +#endif + +/* +============ +idSIMD_MMX::GetName +============ +*/ +const char * idSIMD_MMX::GetName( void ) const { + return "MMX"; +} + +/* +================ +MMX_Memcpy8B +================ +*/ +void MMX_Memcpy8B( void *dest, const void *src, const int count ) { +#ifdef _MSC_VER + _asm { + mov esi, src + mov edi, dest + mov ecx, count + shr ecx, 3 // 8 bytes per iteration + +loop1: + movq mm1, 0[ESI] // Read in source data + movntq 0[EDI], mm1 // Non-temporal stores + + add esi, 8 + add edi, 8 + dec ecx + jnz loop1 + + } + EMMS_INSTRUCTION +#elif 0 + /* +not using constraints, so no double escape of registers +not necessary to push edi/esi + */ + __asm__ __volatile__ ( + // "mov %edi, dest\n\t" +"mov %edi, DWORD PTR [%ebp+8]\n\t" + // "mov %esi, src\n\t" +"mov %esi, DWORD PTR [%ebp+12]\n\t" + // "mov %ecx, count\n\t" +"mov %ecx, DWORD PTR [%ebp+16]\n\t" + + "shr %ecx, 3\n\t" // 8 bytes per iteration +"loop1_1:\n\t" + "movq %mm1, 0[%ESI]\n\t" // Read in source data + "movntq 0[%EDI], %mm1\n\t" // Non-temporal stores + "add %esi, 8\n\t" + "add %edi, 8\n\t" + "dec %ecx \n\t" + "jnz loop1_1\n\t" + "emms\n\t" + ); +#elif 1 + __asm__ __volatile__ ( + // "mov %esi, src\n\t" + // "mov %edi, dest\n\t" + // "mov %ecx, count\n\t" + "shr %%ecx, 3\n\t" // 8 bytes per iteration +"0:\n\t" + "movq %%mm1, 0[%%esi]\n\t" // Read in source data + "movntq 0[%%edi], %%mm1\n\t" // Non-temporal stores + "add %%esi, 8\n\t" + "add %%edi, 8\n\t" + "dec %%ecx \n\t" + "jnz 0b\n\t" + "emms\n\t" + : /* no outputs */ + : "S" (src), "D" (dest), "c" (count) + ); +#endif +} + +/* +================ +MMX_Memcpy64B + + 165MB/sec +================ +*/ +void MMX_Memcpy64B( void *dest, const void *src, const int count ) { +#ifdef _MSC_VER + _asm { + mov esi, src + mov edi, dest + mov ecx, count + shr ecx, 6 // 64 bytes per iteration + +loop1: + prefetchnta 64[ESI] // Prefetch next loop, non-temporal + prefetchnta 96[ESI] + + movq mm1, 0[ESI] // Read in source data + movq mm2, 8[ESI] + movq mm3, 16[ESI] + movq mm4, 24[ESI] + movq mm5, 32[ESI] + movq mm6, 40[ESI] + movq mm7, 48[ESI] + movq mm0, 56[ESI] + + movntq 0[EDI], mm1 // Non-temporal stores + movntq 8[EDI], mm2 + movntq 16[EDI], mm3 + movntq 24[EDI], mm4 + movntq 32[EDI], mm5 + movntq 40[EDI], mm6 + movntq 48[EDI], mm7 + movntq 56[EDI], mm0 + + add esi, 64 + add edi, 64 + dec ecx + jnz loop1 + } + EMMS_INSTRUCTION +#else + __asm__ __volatile__ ( + //"mov %%esi, src \n\t" + //"mov %%edi, dest \n\t" + //"mov %%ecx, count \n\t" +"shr %%ecx, 6 \n\t"// 64 bytes per iteration +"\n\t" +"1: \n\t" +"prefetchnta 64[%%ESI] \n\t"// Prefetch next loop, non-temporal +"prefetchnta 96[%%ESI] \n\t" +"\n\t" +"movq %%mm1, 0[%%ESI] \n\t"// Read in source data +"movq %%mm2, 8[%%ESI] \n\t" +"movq %%mm3, 16[%%ESI] \n\t" +"movq %%mm4, 24[%%ESI] \n\t" +"movq %%mm5, 32[%%ESI] \n\t" +"movq %%mm6, 40[%%ESI] \n\t" +"movq %%mm7, 48[%%ESI] \n\t" +"movq %%mm0, 56[%%ESI] \n\t" +"\n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"movntq 8[%%EDI], %%mm2 \n\t" +"movntq 16[%%EDI], %%mm3 \n\t" +"movntq 24[%%EDI], %%mm4 \n\t" +"movntq 32[%%EDI], %%mm5 \n\t" +"movntq 40[%%EDI], %%mm6 \n\t" +"movntq 48[%%EDI], %%mm7 \n\t" +"movntq 56[%%EDI], %%mm0 \n\t" +"\n\t" +"add %%esi, 64 \n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz 1b \n\t" +"emms \n\t" +: +: "S" (src), "D" (dest), "c" (count) + ); +#endif +} + +/* +================ +MMX_Memcpy2kB + + 240MB/sec +================ +*/ +void MMX_Memcpy2kB( void *dest, const void *src, const int count ) { + byte *tbuf = (byte *)_alloca16(2048); +#ifdef _MSC_VER + __asm { + push ebx + mov esi, src + mov ebx, count + shr ebx, 11 // 2048 bytes at a time + mov edi, dest + +loop2k: + push edi // copy 2k into temporary buffer + mov edi, tbuf + mov ecx, 32 + +loopMemToL1: + prefetchnta 64[ESI] // Prefetch next loop, non-temporal + prefetchnta 96[ESI] + + movq mm1, 0[ESI] // Read in source data + movq mm2, 8[ESI] + movq mm3, 16[ESI] + movq mm4, 24[ESI] + movq mm5, 32[ESI] + movq mm6, 40[ESI] + movq mm7, 48[ESI] + movq mm0, 56[ESI] + + movq 0[EDI], mm1 // Store into L1 + movq 8[EDI], mm2 + movq 16[EDI], mm3 + movq 24[EDI], mm4 + movq 32[EDI], mm5 + movq 40[EDI], mm6 + movq 48[EDI], mm7 + movq 56[EDI], mm0 + add esi, 64 + add edi, 64 + dec ecx + jnz loopMemToL1 + + pop edi // Now copy from L1 to system memory + push esi + mov esi, tbuf + mov ecx, 32 + +loopL1ToMem: + movq mm1, 0[ESI] // Read in source data from L1 + movq mm2, 8[ESI] + movq mm3, 16[ESI] + movq mm4, 24[ESI] + movq mm5, 32[ESI] + movq mm6, 40[ESI] + movq mm7, 48[ESI] + movq mm0, 56[ESI] + + movntq 0[EDI], mm1 // Non-temporal stores + movntq 8[EDI], mm2 + movntq 16[EDI], mm3 + movntq 24[EDI], mm4 + movntq 32[EDI], mm5 + movntq 40[EDI], mm6 + movntq 48[EDI], mm7 + movntq 56[EDI], mm0 + + add esi, 64 + add edi, 64 + dec ecx + jnz loopL1ToMem + + pop esi // Do next 2k block + dec ebx + jnz loop2k + pop ebx + } + EMMS_INSTRUCTION +#else + +#ifdef __PIC__ + memcpy( dest, src, count ); +#else + /* +ebx problem: +when not compiling with -fPIC, compiles fine. No need to push/pop ebx, the constraints setup will save and restore ( or so it seems with no optimizations ) + +when compiling with -fPIC: +if not putting ebx in clobber list, "can't find a register in class 'BREG' while reloading 'asm'" +if putting ebx in clobber list, "PIC register 'ebx' clobbered in 'asm'" +but really, you don't want to put it in clobber list, you want to push/pop it + +BREG error due to -masm=intel? ( doesn't sound likely - could test with the cpuid thing? ) + +tbuf constrained in memory since the loop loads it up in edi + */ + __asm__ __volatile__ ( +"push %%ebx \n\t" +//"mov %%esi, src \n\t" +//"mov %%ebx, count \n\t" +"shr %%ebx, 11 \n\t"// 2048 bytes at a time +//"mov %%edi, dest \n\t" +"\n\t" +"loop2k: \n\t" +"push %%edi \n\t"// copy 2k into temporary buffer +//"mov %%edi, tbuf \n\t" +"mov %%edi, %0 \n\t" +"mov %%ecx, 32 \n\t" +"\n\t" +"loopMemToL1: \n\t" +"prefetchnta 64[%%ESI] \n\t"// Prefetch next loop, non-temporal +"prefetchnta 96[%%ESI] \n\t" +"\n\t" +"movq %%mm1, 0[%%ESI] \n\t"// Read in source data +"movq %%mm2, 8[%%ESI] \n\t" +"movq %%mm3, 16[%%ESI] \n\t" +"movq %%mm4, 24[%%ESI] \n\t" +"movq %%mm5, 32[%%ESI] \n\t" +"movq %%mm6, 40[%%ESI] \n\t" +"movq %%mm7, 48[%%ESI] \n\t" +"movq %%mm0, 56[%%ESI] \n\t" +"\n\t" +"movq 0[%%EDI], %%mm1 \n\t"// Store into L1 +"movq 8[%%EDI], %%mm2 \n\t" +"movq 16[%%EDI], %%mm3 \n\t" +"movq 24[%%EDI], %%mm4 \n\t" +"movq 32[%%EDI], %%mm5 \n\t" +"movq 40[%%EDI], %%mm6 \n\t" +"movq 48[%%EDI], %%mm7 \n\t" +"movq 56[%%EDI], %%mm0 \n\t" +"add %%esi, 64 \n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz loopMemToL1 \n\t" +"\n\t" +"pop %%edi \n\t"// Now copy from L1 to system memory +"push %%esi \n\t" +//"mov %%esi, tbuf \n\t" +"mov %%esi, %0 \n\t" +"mov %%ecx, 32 \n\t" +"\n\t" +"loopL1ToMem: \n\t" +"movq %%mm1, 0[%%ESI] \n\t"// Read in source data from L1 +"movq %%mm2, 8[%%ESI] \n\t" +"movq %%mm3, 16[%%ESI] \n\t" +"movq %%mm4, 24[%%ESI] \n\t" +"movq %%mm5, 32[%%ESI] \n\t" +"movq %%mm6, 40[%%ESI] \n\t" +"movq %%mm7, 48[%%ESI] \n\t" +"movq %%mm0, 56[%%ESI] \n\t" +"\n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"movntq 8[%%EDI], %%mm2 \n\t" +"movntq 16[%%EDI], %%mm3 \n\t" +"movntq 24[%%EDI], %%mm4 \n\t" +"movntq 32[%%EDI], %%mm5 \n\t" +"movntq 40[%%EDI], %%mm6 \n\t" +"movntq 48[%%EDI], %%mm7 \n\t" +"movntq 56[%%EDI], %%mm0 \n\t" +"\n\t" +"add %%esi, 64 \n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz loopL1ToMem \n\t" +"\n\t" +"pop %%esi \n\t"// Do next 2k block +"dec %%ebx \n\t" +"jnz loop2k \n\t" +"pop %%ebx \n\t" +"emms \n\t" +: +: "m" (tbuf), "S" (src), "D" (dest), "b" (count) +//: "ebx" + ); + +#endif // !ID_PIC + +#endif +} + + +/* +================ +idSIMD_MMX::Memcpy + + optimized memory copy routine that handles all alignment cases and block sizes efficiently +================ +*/ +void VPCALL idSIMD_MMX::Memcpy( void *dest0, const void *src0, const int count0 ) { +#ifndef _WIN32 + memcpy( dest0, src0, count0 ); +#else + // if copying more than 16 bytes and we can copy 8 byte aligned + if ( count0 > 16 && !( ( (int)dest0 ^ (int)src0 ) & 7 ) ) { + byte *dest = (byte *)dest0; + byte *src = (byte *)src0; + + // copy up to the first 8 byte aligned boundary + int count = ((int)dest) & 7; + memcpy( dest, src, count ); + dest += count; + src += count; + count = count0 - count; + + // if there are multiple blocks of 2kB + if ( count & ~4095 ) { + MMX_Memcpy2kB( dest, src, count ); + src += (count & ~2047); + dest += (count & ~2047); + count &= 2047; + } + + // if there are blocks of 64 bytes + if ( count & ~63 ) { + MMX_Memcpy64B( dest, src, count ); + src += (count & ~63); + dest += (count & ~63); + count &= 63; + } + + // if there are blocks of 8 bytes + if ( count & ~7 ) { + MMX_Memcpy8B( dest, src, count ); + src += (count & ~7); + dest += (count & ~7); + count &= 7; + } + + // copy any remaining bytes + memcpy( dest, src, count ); + } else { + // use the regular one if we cannot copy 8 byte aligned + memcpy( dest0, src0, count0 ); + } +#endif // _WIN32 +} + +/* +================ +idSIMD_MMX::Memset +================ +*/ +void VPCALL idSIMD_MMX::Memset( void* dest0, const int val, const int count0 ) { +#ifndef _WIN32 +#else + union { + byte bytes[8]; + word words[4]; + dword dwords[2]; + } dat; + + byte *dest = (byte *)dest0; + int count = count0; + + while( count > 0 && (((int)dest) & 7) ) { + *dest = val; + dest++; + count--; + } + if ( !count ) { + return; + } + + dat.bytes[0] = val; + dat.bytes[1] = val; + dat.words[1] = dat.words[0]; + dat.dwords[1] = dat.dwords[0]; + + if ( count >= 64 ) { +#ifdef _MSC_VER + __asm { + mov edi, dest + mov ecx, count + shr ecx, 6 // 64 bytes per iteration + movq mm1, dat // Read in source data + movq mm2, mm1 + movq mm3, mm1 + movq mm4, mm1 + movq mm5, mm1 + movq mm6, mm1 + movq mm7, mm1 + movq mm0, mm1 +loop1: + movntq 0[EDI], mm1 // Non-temporal stores + movntq 8[EDI], mm2 + movntq 16[EDI], mm3 + movntq 24[EDI], mm4 + movntq 32[EDI], mm5 + movntq 40[EDI], mm6 + movntq 48[EDI], mm7 + movntq 56[EDI], mm0 + + add edi, 64 + dec ecx + jnz loop1 + } +#else +/* +dat constrained in memory +*/ + __asm__ __volatile__ ( + //"mov %%edi, dest \n\t" + //"mov %%ecx, count \n\t" +"shr %%ecx, 6 \n\t"// 64 bytes per iteration +//"movq %%mm1, dat \n\t"// Read in source data +"movq %%mm1, %0 \n\t" +"movq %%mm2, %%mm1 \n\t" +"movq %%mm3, %%mm1 \n\t" +"movq %%mm4, %%mm1 \n\t" +"movq %%mm5, %%mm1 \n\t" +"movq %%mm6, %%mm1 \n\t" +"movq %%mm7, %%mm1 \n\t" +"movq %%mm0, %%mm1 \n\t" +"loop1_3: \n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"movntq 8[%%EDI], %%mm2 \n\t" +"movntq 16[%%EDI], %%mm3 \n\t" +"movntq 24[%%EDI], %%mm4 \n\t" +"movntq 32[%%EDI], %%mm5 \n\t" +"movntq 40[%%EDI], %%mm6 \n\t" +"movntq 48[%%EDI], %%mm7 \n\t" +"movntq 56[%%EDI], %%mm0 \n\t" +"\n\t" +"add %%edi, 64 \n\t" +"dec %%ecx \n\t" +"jnz loop1_3 \n\t" +: +: "m" (dat), "D" (dest), "c" (count) + ); +#endif + dest += ( count & ~63 ); + count &= 63; + } + + if ( count >= 8 ) { +#ifdef _MSC_VER + __asm { + mov edi, dest + mov ecx, count + shr ecx, 3 // 8 bytes per iteration + movq mm1, dat // Read in source data +loop2: + movntq 0[EDI], mm1 // Non-temporal stores + + add edi, 8 + dec ecx + jnz loop2 + } +#else +/* +dat constrained in memory +*/ + __asm__ __volatile__ ( + //"mov %%edi, dest \n\t" + //"mov %%ecx, count \n\t" +"shr %%ecx, 3 \n\t"// 8 bytes per iteration +//"movq %%mm1, dat \n\t"// Read in source data +"movq %%mm1, %0 \n\t"// Read in source data +"loop2: \n\t" +"movntq 0[%%EDI], %%mm1 \n\t"// Non-temporal stores +"\n\t" +"add %%edi, 8 \n\t" +"dec %%ecx \n\t" +"jnz loop2 \n\t" +: +: "m" (dat), "D" (dest), "c" (count) + ); +#endif + dest += (count & ~7); + count &= 7; + } + + while( count > 0 ) { + *dest = val; + dest++; + count--; + } + + EMMS_INSTRUCTION +#endif // _WIN32 +} + +#endif diff --git a/src/idlib/math/Simd_MMX.h b/src/idlib/math/Simd_MMX.h new file mode 100644 index 0000000..7cc600c --- /dev/null +++ b/src/idlib/math/Simd_MMX.h @@ -0,0 +1,47 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_MMX_H__ +#define __MATH_SIMD_MMX_H__ + +/* +=============================================================================== + + MMX implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_MMX : public idSIMD_Generic { +#if defined( _WIN32 ) || defined( __linux__ ) +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL Memcpy( void *dst, const void *src, const int count ); + virtual void VPCALL Memset( void *dst, const int val, const int count ); + +#endif +}; + +#endif /* !__MATH_SIMD_MMX_H__ */ diff --git a/src/idlib/math/Simd_SSE.cpp b/src/idlib/math/Simd_SSE.cpp new file mode 100644 index 0000000..620da29 --- /dev/null +++ b/src/idlib/math/Simd_SSE.cpp @@ -0,0 +1,19825 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_SSE.h" +#include "Simd_InstructionMacros.h" + + +//=============================================================== +// M +// SSE implementation of idSIMDProcessor MrE +// E +//=============================================================== + +#if defined( _WINDOWS ) + +// with alignment +#define KFLOATINITS( SRC0, COUNT, PRE, POST ) KFLOATINITDSS( SRC0,SRC0,SRC0,COUNT,PRE,POST ) +#define KFLOATINITD( DST, COUNT, PRE, POST ) KFLOATINITDSS( DST,DST,DST,COUNT,PRE,POST ) +#define KFLOATINITDS( DST, SRC0, COUNT, PRE, POST ) KFLOATINITDSS( DST,SRC0,SRC0,COUNT,PRE,POST ) + +#define KFLOATINITDSS( DST, SRC0, SRC1, COUNT, PRE, POST )\ + __asm mov ecx,DST \ + __asm shr ecx,2 \ + __asm mov ebx,COUNT \ + __asm neg ecx \ + __asm mov edx,SRC0 \ + __asm and ecx,3 \ + __asm mov esi,SRC1 \ + __asm sub ebx,ecx \ + __asm jge noUnderFlow \ + __asm xor ebx,ebx \ + __asm mov ecx,COUNT \ + __asm noUnderFlow: \ + __asm mov PRE,ecx \ + __asm mov eax,ebx \ + __asm mov edi,DST \ + __asm and eax,8-1 \ + __asm mov POST,eax \ + __asm and ebx,0xfffffff8 \ + __asm jle done \ + __asm shl ebx,2 \ + __asm lea ecx,[ecx*4+ebx] \ + __asm neg ebx \ + __asm add edx,ecx \ + __asm add esi,ecx \ + __asm add edi,ecx \ + __asm mov eax,edx \ + __asm or eax,esi + +// without alignment (pre==0) +#define KFLOATINITS_NA( SRC0, COUNT, PRE, POST ) KFLOATINITDSS_NA( SRC0,SRC0,SRC0,COUNT,PRE,POST ) +#define KFLOATINITD_NA( DST, COUNT, PRE, POST ) KFLOATINITDSS_NA( DST,DST,DST,COUNT,PRE,POST ) +#define KFLOATINITDS_NA( DST, SRC0, COUNT, PRE, POST ) KFLOATINITDSS_NA( DST,SRC0,SRC0,COUNT,PRE,POST ) +#define KFLOATINITDSS_NA( DST, SRC0, SRC1, COUNT, PRE, POST )\ + __asm mov eax,COUNT \ + __asm mov PRE,0 \ + __asm and eax,8-1 \ + __asm mov ebx,COUNT \ + __asm mov POST,eax \ + __asm and ebx,0xfffffff8 \ + __asm je done \ + __asm shl ebx,2 \ + __asm mov edx,SRC0 \ + __asm mov esi,SRC1 \ + __asm mov edi,DST \ + __asm add edx,ebx \ + __asm add esi,ebx \ + __asm add edi,ebx \ + __asm mov eax,edx \ + __asm or eax,esi \ + __asm or eax,edi \ + __asm neg ebx \ + +/* + when OPER is called: + edx = s0 + esi = s1 + edi = d + ebx = index*4 + + xmm0 & xmm1 must not be trashed +*/ +#define KMOVDS1( DST, SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm movss DST,xmm2 +#define KMOVDS4( DST, SRC0 ) \ + __asm movups xmm2,SRC0 \ + __asm movups DST,xmm2 +#define KMINDS1( DST, SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm minss DST,xmm2 +#define KMAXDS1( DST, SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm maxss DST,xmm2 + +// general ALU operation +#define KALUDSS1( OP, DST, SRC0, SRC1 ) \ + __asm movss xmm2,SRC0 \ + __asm OP##ss xmm2,SRC1 \ + __asm movss DST,xmm2 +#define KALUDSS4( OP, DST, SRC0, SRC1 ) \ + __asm movups xmm2,SRC0 \ + __asm movups xmm3,SRC1 \ + __asm OP##ps xmm2,xmm3 \ + __asm movups DST,xmm2 + +#define KADDDSS1( DST, SRC0, SRC1 ) KALUDSS1( add, DST,SRC0,SRC1 ) +#define KADDDSS4( DST, SRC0, SRC1 ) KALUDSS4( add, DST,SRC0,SRC1 ) +#define KSUBDSS1( DST, SRC0, SRC1 ) KALUDSS1( sub, DST,SRC0,SRC1 ) +#define KSUBDSS4( DST, SRC0, SRC1 ) KALUDSS4( sub, DST,SRC0,SRC1 ) +#define KMULDSS1( DST, SRC0, SRC1 ) KALUDSS1( mul, DST,SRC0,SRC1 ) +#define KMULDSS4( DST, SRC0, SRC1 ) KALUDSS4( mul, DST,SRC0,SRC1 ) + +#define KDIVDSS1( DST, SRC0, SRC1 ) \ + __asm movss xmm2,SRC1 \ + __asm rcpss xmm3,xmm2 \ + __asm mulss xmm2,xmm3 \ + __asm mulss xmm2,xmm3 \ + __asm addss xmm3,xmm3 \ + __asm subss xmm3,xmm2 \ + __asm mulss xmm3,SRC0 \ + __asm movss DST,xmm3 +#define KDIVDSS4( DST, SRC0, SRC1 ) \ + __asm movups xmm2,SRC1 \ + __asm rcpps xmm3,xmm2 \ + __asm mulps xmm2,xmm3 \ + __asm mulps xmm2,xmm3 \ + __asm addps xmm3,xmm3 \ + __asm subps xmm3,xmm2 \ + __asm movups xmm2,SRC0 \ + __asm mulps xmm3,xmm2 \ + __asm movups DST,xmm3 +#define KF2IDS1( SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm cvttps2pi mm2,xmm2 \ + __asm movd [edi+ebx],mm2 +#define KF2IDS4( SRC0 ) \ + __asm movups xmm2,SRC0 \ + __asm cvttps2pi mm2,xmm2 \ + __asm movq [edi+ebx+0],mm2 \ + __asm shufps xmm2,xmm2,SHUFFLE_PS(1,0,3,2) \ + __asm cvttps2pi mm2,xmm2 \ + __asm movq [edi+ebx+8],mm2 +#define KISQRTDS1( DST,SRC0 ) \ + __asm movss xmm2,SRC0 \ + __asm rsqrtss xmm3,xmm2 \ + __asm mulss xmm2,xmm3 \ + __asm mulss xmm2,xmm3 \ + __asm subss xmm2,xmm1 \ + __asm mulss xmm3,xmm0 \ + __asm mulss xmm3,xmm2 \ + __asm movss DST,xmm3 +#define KISQRTDS4( DST,SRC0 ) \ + __asm movups xmm2,SRC0 \ + __asm rsqrtps xmm3,xmm2 \ + __asm mulps xmm2,xmm3 \ + __asm mulps xmm2,xmm3 \ + __asm subps xmm2,xmm1 \ + __asm mulps xmm3,xmm0 \ + __asm mulps xmm3,xmm2 \ + __asm movups DST,xmm3 + +// this is used in vector4 implementation to shift constant V4 +#define KANDREGDSV( DST, SRC0, VALUE ) \ + __asm mov DST,SRC0 \ + __asm and DST,VALUE + +// this is used in vector4 code to operate with float arrays as sources +#define KEXPANDFLOAT( DST, SRC ) \ + __asm movss DST,SRC \ + __asm shufps DST,DST,0 + +#define KADDDS1( DST,SRC ) KADDDSS1( DST,DST,SRC ) +#define KADDDS4( DST,SRC ) KADDDSS4( DST,DST,SRC ) +#define KSUBDS1( DST,SRC ) KSUBDSS1( DST,DST,SRC ) +#define KSUBDS4( DST,SRC ) KSUBDSS4( DST,DST,SRC ) +#define KMULDS1( DST,SRC ) KMULDSS1( DST,DST,SRC ) +#define KMULDS4( DST,SRC ) KMULDSS4( DST,DST,SRC ) +#define KDIVDS1( DST,SRC ) KDIVDSS1( DST,DST,SRC ) +#define KDIVDS4( DST,SRC ) KDIVDSS4( DST,DST,SRC ) + +// handles pre & post leftovers +#define KFLOATOPER( OPER, OPER4, COUNT ) \ + __asm mov ecx,pre \ + __asm mov ebx,COUNT \ + __asm cmp ebx,ecx \ + __asm cmovl ecx,COUNT \ + __asm test ecx,ecx \ + __asm je preDone \ + __asm xor ebx,ebx \ + __asm lpPre: \ + OPER \ + __asm add ebx,4 \ + __asm dec ecx \ + __asm jg lpPre \ + __asm preDone: \ + __asm mov ecx,post \ + __asm mov ebx,COUNT \ + __asm sub ebx,ecx \ + __asm shl ebx,2 \ + __asm cmp ecx,4 \ + __asm jl post4Done \ + OPER4 \ + __asm sub ecx,4 \ + __asm add ebx,4*4 \ + __asm post4Done: \ + __asm test ecx,ecx \ + __asm je postDone \ + __asm lpPost: \ + OPER \ + __asm add ebx,4 \ + __asm dec ecx \ + __asm jg lpPost \ + __asm postDone: + +// operate on a constant and a float array +#define KFLOAT_CA( ALUOP, DST, SRC, CONSTANT, COUNT ) \ + int pre,post; \ + __asm movss xmm0,CONSTANT \ + __asm shufps xmm0,xmm0,0 \ + KFLOATINITDS( DST, SRC, COUNT, pre, post ) \ + __asm and eax,15 \ + __asm jne lpNA \ + __asm jmp lpA \ + __asm align 16 \ + __asm lpA: \ + __asm prefetchnta [edx+ebx+64] \ + __asm movaps xmm1,xmm0 \ + __asm movaps xmm2,xmm0 \ + __asm ALUOP##ps xmm1,[edx+ebx] \ + __asm ALUOP##ps xmm2,[edx+ebx+16] \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpA \ + __asm jmp done \ + __asm align 16 \ + __asm lpNA: \ + __asm prefetchnta [edx+ebx+64] \ + __asm movaps xmm1,xmm0 \ + __asm movaps xmm2,xmm0 \ + __asm movups xmm3,[edx+ebx] \ + __asm movups xmm4,[edx+ebx+16] \ + __asm ALUOP##ps xmm1,xmm3 \ + __asm ALUOP##ps xmm2,xmm4 \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpNA \ + __asm done: \ + __asm mov edx,SRC \ + __asm mov edi,DST \ + __asm KFLOATOPER( KALUDSS1( ALUOP, [edi+ebx],xmm0,[edx+ebx] ), \ + __asm KALUDSS4( ALUOP, [edi+ebx],xmm0,[edx+ebx] ), COUNT ) + +// operate on two float arrays +#define KFLOAT_AA( ALUOP, DST, SRC0, SRC1, COUNT ) \ + int pre,post; \ + KFLOATINITDSS( DST, SRC0, SRC1, COUNT, pre, post ) \ + __asm and eax,15 \ + __asm jne lpNA \ + __asm jmp lpA \ + __asm align 16 \ + __asm lpA: \ + __asm movaps xmm1,[edx+ebx] \ + __asm movaps xmm2,[edx+ebx+16] \ + __asm ALUOP##ps xmm1,[esi+ebx] \ + __asm ALUOP##ps xmm2,[esi+ebx+16] \ + __asm prefetchnta [edx+ebx+64] \ + __asm prefetchnta [esi+ebx+64] \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpA \ + __asm jmp done \ + __asm align 16 \ + __asm lpNA: \ + __asm movups xmm1,[edx+ebx] \ + __asm movups xmm2,[edx+ebx+16] \ + __asm movups xmm3,[esi+ebx] \ + __asm movups xmm4,[esi+ebx+16] \ + __asm prefetchnta [edx+ebx+64] \ + __asm prefetchnta [esi+ebx+64] \ + __asm ALUOP##ps xmm1,xmm3 \ + __asm ALUOP##ps xmm2,xmm4 \ + __asm movaps [edi+ebx],xmm1 \ + __asm movaps [edi+ebx+16],xmm2 \ + __asm add ebx,16*2 \ + __asm jl lpNA \ + __asm done: \ + __asm mov edx,SRC0 \ + __asm mov esi,SRC1 \ + __asm mov edi,DST \ + KFLOATOPER( KALUDSS1( ALUOP, [edi+ebx],[edx+ebx],[esi+ebx] ), \ + KALUDSS4( ALUOP, [edi+ebx],[edx+ebx],[esi+ebx] ), COUNT ) + + +ALIGN8_INIT1( unsigned short SIMD_W_zero, 0 ); +ALIGN8_INIT1( unsigned short SIMD_W_maxShort, 1<<15 ); + +ALIGN4_INIT4( unsigned long SIMD_SP_firstSignBit, (unsigned long) ( 1 << 31 ), 0, 0, 0 ); +ALIGN4_INIT1( unsigned long SIMD_SP_signBit, (unsigned long) ( 1 << 31 ) ); +ALIGN4_INIT1( unsigned long SIMD_SP_absMask, (unsigned long) ~( 1 << 31 ) ); +ALIGN4_INIT1( unsigned long SIMD_SP_infinityMask, (unsigned long) ~( 1 << 23 ) ); +ALIGN4_INIT1( unsigned long SIMD_SP_not, 0xFFFFFFFF ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearLast, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearFirstThree, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF ); + +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle0, (3<<0)|(2<<8)|(1<<16)|(0<<24) ); +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle1, (0<<0)|(1<<8)|(2<<16)|(3<<24) ); +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle2, (1<<0)|(0<<8)|(3<<16)|(2<<24) ); +ALIGN4_INIT1( unsigned long SIMD_DW_mat2quatShuffle3, (2<<0)|(3<<8)|(0<<16)|(1<<24) ); + +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x0, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x1, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x2, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); + +ALIGN4_INIT1( float SIMD_SP_zero, 0.0f ); +ALIGN4_INIT1( float SIMD_SP_half, 0.5f ); +ALIGN4_INIT1( float SIMD_SP_one, 1.0f ); +ALIGN4_INIT1( float SIMD_SP_two, 2.0f ); +ALIGN4_INIT1( float SIMD_SP_three, 3.0f ); +ALIGN4_INIT1( float SIMD_SP_four, 4.0f ); +ALIGN4_INIT1( float SIMD_SP_maxShort, (1<<15) ); +ALIGN4_INIT1( float SIMD_SP_tiny, 1e-10f ); +ALIGN4_INIT1( float SIMD_SP_PI, idMath::PI ); +ALIGN4_INIT1( float SIMD_SP_halfPI, idMath::HALF_PI ); +ALIGN4_INIT1( float SIMD_SP_twoPI, idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_oneOverTwoPI, 1.0f / idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_infinity, idMath::INFINITY ); +ALIGN4_INIT1( float SIMD_SP_negInfinity, -idMath::INFINITY ); +ALIGN4_INIT4( float SIMD_SP_lastOne, 0.0f, 0.0f, 0.0f, 1.0f ); + +ALIGN4_INIT1( float SIMD_SP_rsqrt_c0, 3.0f ); +ALIGN4_INIT1( float SIMD_SP_rsqrt_c1, -0.5f ); +ALIGN4_INIT1( float SIMD_SP_mat2quat_rsqrt_c1, -0.5f*0.5f ); + +ALIGN4_INIT1( float SIMD_SP_sin_c0, -2.39e-08f ); +ALIGN4_INIT1( float SIMD_SP_sin_c1, 2.7526e-06f ); +ALIGN4_INIT1( float SIMD_SP_sin_c2, -1.98409e-04f ); +ALIGN4_INIT1( float SIMD_SP_sin_c3, 8.3333315e-03f ); +ALIGN4_INIT1( float SIMD_SP_sin_c4, -1.666666664e-01f ); + +ALIGN4_INIT1( float SIMD_SP_cos_c0, -2.605e-07f ); +ALIGN4_INIT1( float SIMD_SP_cos_c1, 2.47609e-05f ); +ALIGN4_INIT1( float SIMD_SP_cos_c2, -1.3888397e-03f ); +ALIGN4_INIT1( float SIMD_SP_cos_c3, 4.16666418e-02f ); +ALIGN4_INIT1( float SIMD_SP_cos_c4, -4.999999963e-01f ); + +ALIGN4_INIT1( float SIMD_SP_atan_c0, 0.0028662257f ); +ALIGN4_INIT1( float SIMD_SP_atan_c1, -0.0161657367f ); +ALIGN4_INIT1( float SIMD_SP_atan_c2, 0.0429096138f ); +ALIGN4_INIT1( float SIMD_SP_atan_c3, -0.0752896400f ); +ALIGN4_INIT1( float SIMD_SP_atan_c4, 0.1065626393f ); +ALIGN4_INIT1( float SIMD_SP_atan_c5, -0.1420889944f ); +ALIGN4_INIT1( float SIMD_SP_atan_c6, 0.1999355085f ); +ALIGN4_INIT1( float SIMD_SP_atan_c7, -0.3333314528f ); + +/* +============ +SSE_InvSqrt +============ +*/ +float SSE_InvSqrt( float x ) { + float y; + + __asm { + movss xmm0, x + rsqrtss xmm1, xmm0 + mulss xmm0, xmm1 + mulss xmm0, xmm1 + subss xmm0, SIMD_SP_rsqrt_c0 + mulss xmm1, SIMD_SP_rsqrt_c1 + mulss xmm0, xmm1 + movss y, xmm0 + } + return y; +} + +/* +============ +SSE_InvSqrt4 +============ +*/ +void SSE_InvSqrt4( float x[4] ) { + __asm { + mov edi, x + movaps xmm0, [edi] + rsqrtps xmm1, xmm0 + mulps xmm0, xmm1 + mulps xmm0, xmm1 + subps xmm0, SIMD_SP_rsqrt_c0 + mulps xmm1, SIMD_SP_rsqrt_c1 + mulps xmm0, xmm1 + movaps [edi], xmm0 + } +} + +/* +============ +SSE_SinZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +float SSE_SinZeroHalfPI( float a ) { +#if 1 + + float t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + __asm { + movss xmm0, a + movss xmm1, xmm0 + mulss xmm1, xmm1 + movss xmm2, SIMD_SP_sin_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + movss t, xmm2 + } + + return t; + +#else + + float s, t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + s = a * a; + t = -2.39e-08f; + t *= s; + t += 2.7526e-06f; + t *= s; + t += -1.98409e-04f; + t *= s; + t += 8.3333315e-03f; + t *= s; + t += -1.666666664e-01f; + t *= s; + t += 1.0f; + t *= a; + + return t; + +#endif +} + +/* +============ +SSE_Sin4ZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +void SSE_Sin4ZeroHalfPI( float a[4], float s[4] ) { + __asm { + mov edi, a + mov esi, s + movaps xmm0, [edi] + movaps xmm1, xmm0 + mulps xmm1, xmm1 + movaps xmm2, SIMD_SP_sin_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + movaps [esi], xmm2 + } +} + +/* +============ +SSE_Sin +============ +*/ +float SSE_Sin( float a ) { +#if 1 + + float t; + + __asm { + movss xmm1, a + movss xmm2, xmm1 + movss xmm3, xmm1 + mulss xmm2, SIMD_SP_oneOverTwoPI + cvttss2si ecx, xmm2 + cmpltss xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + cvtsi2ss xmm2, ecx + subss xmm2, xmm3 + mulss xmm2, SIMD_SP_twoPI + subss xmm1, xmm2 + + movss xmm0, SIMD_SP_PI // xmm0 = PI + subss xmm0, xmm1 // xmm0 = PI - a + movss xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movss xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltss xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movss xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movss xmm1, xmm0 + mulss xmm1, xmm1 + movss xmm2, SIMD_SP_sin_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_sin_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + movss t, xmm2 + } + + return t; + +#else + + float s, t; + + if ( ( a < 0.0f ) || ( a >= idMath::TWO_PI ) ) { + a -= floorf( a / idMath::TWO_PI ) * idMath::TWO_PI; + } + + a = idMath::PI - a; + if ( fabs( a ) >= idMath::HALF_PI ) { + a = ( ( a < 0.0f ) ? -idMath::PI : idMath::PI ) - a; + } + + s = a * a; + t = -2.39e-08f; + t *= s; + t += 2.7526e-06f; + t *= s; + t += -1.98409e-04f; + t *= s; + t += 8.3333315e-03f; + t *= s; + t += -1.666666664e-01f; + t *= s; + t += 1.0f; + t *= a; + + return t; + +#endif +} + +/* +============ +SSE_Sin4 +============ +*/ +void SSE_Sin4( float a[4], float s[4] ) { + __asm { + mov edi, a + mov esi, s + movaps xmm1, [edi] + movaps xmm2, xmm1 + mulps xmm2, SIMD_SP_oneOverTwoPI + movhlps xmm3, xmm2 + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 0, 0, 0 ) + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 0, 1, 0 ) + movaps xmm3, xmm1 + cmpltps xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + subps xmm2, xmm3 + mulps xmm2, SIMD_SP_twoPI + subps xmm1, xmm2 + + movaps xmm0, SIMD_SP_PI // xmm0 = PI + subps xmm0, xmm1 // xmm0 = PI - a + movaps xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movaps xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltps xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movaps xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movaps xmm1, xmm0 + mulps xmm1, xmm1 + movaps xmm2, SIMD_SP_sin_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_sin_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + movaps [esi], xmm2 + } +} + +/* +============ +SSE_CosZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +float SSE_CosZeroHalfPI( float a ) { +#if 1 + + float t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + __asm { + movss xmm0, a + mulss xmm0, xmm0 + movss xmm1, SIMD_SP_cos_c0 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c1 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c2 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c3 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c4 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_one + movss t, xmm1 + } + + return t; + +#else + + float s, t; + + assert( a >= 0.0f && a <= idMath::HALF_PI ); + + s = a * a; + t = -2.605e-07f; + t *= s; + t += 2.47609e-05f; + t *= s; + t += -1.3888397e-03f; + t *= s; + t += 4.16666418e-02f; + t *= s; + t += -4.999999963e-01f; + t *= s; + t += 1.0f; + + return t; + +#endif +} + +/* +============ +SSE_Cos4ZeroHalfPI + + The angle must be between zero and half PI. +============ +*/ +void SSE_Cos4ZeroHalfPI( float a[4], float c[4] ) { + __asm { + mov edi, a + mov esi, c + movaps xmm0, [edi] + mulps xmm0, xmm0 + movaps xmm1, SIMD_SP_cos_c0 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c1 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c2 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c3 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c4 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_one + movaps [esi], xmm2 + } +} + +/* +============ +SSE_Cos +============ +*/ +float SSE_Cos( float a ) { +#if 1 + + float t; + + __asm { + movss xmm1, a + movss xmm2, xmm1 + movss xmm3, xmm1 + mulss xmm2, SIMD_SP_oneOverTwoPI + cvttss2si ecx, xmm2 + cmpltss xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + cvtsi2ss xmm2, ecx + subss xmm2, xmm3 + mulss xmm2, SIMD_SP_twoPI + subss xmm1, xmm2 + + movss xmm0, SIMD_SP_PI // xmm0 = PI + subss xmm0, xmm1 // xmm0 = PI - a + movss xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movss xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltss xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movss xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + mulss xmm0, xmm0 + movss xmm1, SIMD_SP_cos_c0 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c1 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c2 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c3 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_cos_c4 + mulss xmm1, xmm0 + addss xmm1, SIMD_SP_one + xorps xmm2, SIMD_SP_signBit + xorps xmm1, xmm2 + movss t, xmm1 + } + + return t; + +#else + + float s, t; + + if ( ( a < 0.0f ) || ( a >= idMath::TWO_PI ) ) { + a -= floorf( a / idMath::TWO_PI ) * idMath::TWO_PI; + } + + a = idMath::PI - a; + if ( fabs( a ) >= idMath::HALF_PI ) { + a = ( ( a < 0.0f ) ? -idMath::PI : idMath::PI ) - a; + d = 1.0f; + } else { + d = -1.0f; + } + + s = a * a; + t = -2.605e-07f; + t *= s; + t += 2.47609e-05f; + t *= s; + t += -1.3888397e-03f; + t *= s; + t += 4.16666418e-02f; + t *= s; + t += -4.999999963e-01f; + t *= s; + t += 1.0f; + t *= d; + + return t; + +#endif +} + +/* +============ +SSE_Cos4 +============ +*/ +void SSE_Cos4( float a[4], float c[4] ) { + __asm { + mov edi, a + mov esi, c + movaps xmm1, [edi] + movaps xmm2, xmm1 + mulps xmm2, SIMD_SP_oneOverTwoPI + movhlps xmm3, xmm2 + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 0, 0, 0 ) + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 0, 1, 0 ) + movaps xmm3, xmm1 + cmpltps xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + subps xmm2, xmm3 + mulps xmm2, SIMD_SP_twoPI + subps xmm1, xmm2 + + movaps xmm0, SIMD_SP_PI // xmm0 = PI + subps xmm0, xmm1 // xmm0 = PI - a + movaps xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movaps xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltps xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movaps xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + mulps xmm0, xmm0 + movaps xmm1, SIMD_SP_cos_c0 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c1 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c2 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c3 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_cos_c4 + mulps xmm1, xmm0 + addps xmm1, SIMD_SP_one + xorps xmm2, SIMD_SP_signBit + xorps xmm1, xmm2 + movaps [esi], xmm1 + } +} + +/* +============ +SSE_SinCos +============ +*/ +void SSE_SinCos( float a, float &s, float &c ) { + __asm { + mov edi, s + mov esi, c + movss xmm1, a + movss xmm2, xmm1 + movss xmm3, xmm1 + mulss xmm2, SIMD_SP_oneOverTwoPI + cvttss2si ecx, xmm2 + cmpltss xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + cvtsi2ss xmm2, ecx + subss xmm2, xmm3 + mulss xmm2, SIMD_SP_twoPI + subss xmm1, xmm2 + + movss xmm0, SIMD_SP_PI // xmm0 = PI + subss xmm0, xmm1 // xmm0 = PI - a + movss xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movss xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltss xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movss xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movss xmm1, xmm0 + mulss xmm1, xmm1 + movss xmm3, SIMD_SP_sin_c0 + movss xmm4, SIMD_SP_cos_c0 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c1 + addss xmm4, SIMD_SP_cos_c1 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c2 + addss xmm4, SIMD_SP_cos_c2 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c3 + addss xmm4, SIMD_SP_cos_c3 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_sin_c4 + addss xmm4, SIMD_SP_cos_c4 + mulss xmm3, xmm1 + mulss xmm4, xmm1 + addss xmm3, SIMD_SP_one + addss xmm4, SIMD_SP_one + mulss xmm3, xmm0 + xorps xmm2, SIMD_SP_signBit + xorps xmm4, xmm2 + movss [edi], xmm2 + movss [esi], xmm3 + } +} + +/* +============ +SSE_SinCos4 +============ +*/ +void SSE_SinCos4( float a[4], float s[4], float c[4] ) { + __asm { + mov eax, a + mov edi, s + mov esi, c + movaps xmm1, [eax] + movaps xmm2, xmm1 + mulps xmm2, SIMD_SP_oneOverTwoPI + movhlps xmm3, xmm2 + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 0, 0, 0 ) + cvttss2si ecx, xmm2 + cvtsi2ss xmm2, ecx + cvttss2si edx, xmm3 + cvtsi2ss xmm3, edx + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 0, 1, 0 ) + movaps xmm3, xmm1 + cmpltps xmm3, SIMD_SP_zero + andps xmm3, SIMD_SP_one + subps xmm2, xmm3 + mulps xmm2, SIMD_SP_twoPI + subps xmm1, xmm2 + + movaps xmm0, SIMD_SP_PI // xmm0 = PI + subps xmm0, xmm1 // xmm0 = PI - a + movaps xmm1, xmm0 // xmm1 = PI - a + andps xmm1, SIMD_SP_signBit // xmm1 = signbit( PI - a ) + movaps xmm2, xmm0 // xmm2 = PI - a + xorps xmm2, xmm1 // xmm2 = fabs( PI - a ) + cmpnltps xmm2, SIMD_SP_halfPI // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? 0xFFFFFFFF : 0x00000000 + movaps xmm3, SIMD_SP_PI // xmm3 = PI + xorps xmm3, xmm1 // xmm3 = PI ^ signbit( PI - a ) + andps xmm3, xmm2 // xmm3 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? ( PI ^ signbit( PI - a ) ) : 0.0f + andps xmm2, SIMD_SP_signBit // xmm2 = ( fabs( PI - a ) >= idMath::HALF_PI ) ? SIMD_SP_signBit : 0.0f + xorps xmm0, xmm2 + addps xmm0, xmm3 + + movaps xmm0, [eax] + movaps xmm1, xmm0 + mulps xmm1, xmm1 + movaps xmm3, SIMD_SP_sin_c0 + movaps xmm4, SIMD_SP_cos_c0 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c1 + addps xmm4, SIMD_SP_cos_c1 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c2 + addps xmm4, SIMD_SP_cos_c2 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c3 + addps xmm4, SIMD_SP_cos_c3 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_sin_c4 + addps xmm4, SIMD_SP_cos_c4 + mulps xmm3, xmm1 + mulps xmm4, xmm1 + addps xmm3, SIMD_SP_one + addps xmm4, SIMD_SP_one + mulps xmm3, xmm0 + xorps xmm2, SIMD_SP_signBit + xorps xmm4, xmm2 + movaps [edi], xmm3 + movaps [esi], xmm4 + } +} + +/* +============ +SSE_ATanPositive + + Both 'x' and 'y' must be positive. +============ +*/ +float SSE_ATanPositive( float y, float x ) { +#if 1 + + float t; + + assert( y >= 0.0f && x >= 0.0f ); + + __asm { + movss xmm0, x + movss xmm3, xmm0 + movss xmm1, y + minss xmm0, xmm1 + maxss xmm1, xmm3 + cmpeqss xmm3, xmm0 + rcpss xmm2, xmm1 + mulss xmm1, xmm2 + mulss xmm1, xmm2 + addss xmm2, xmm2 + subss xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulss xmm0, xmm2 // xmm0 = x / y or y / x + movss xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + andps xmm3, SIMD_SP_halfPI // xmm3 = HALF_PI or 0.0f + movss xmm1, xmm0 + mulss xmm1, xmm1 // xmm1 = s + movss xmm2, SIMD_SP_atan_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c5 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c6 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c7 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + addss xmm2, xmm3 + movss t, xmm2 + } + + return t; + +#else + + float a, d, s, t; + + assert( y >= 0.0f && x >= 0.0f ); + + if ( y > x ) { + a = -x / y; + d = idMath::HALF_PI; + } else { + a = y / x; + d = 0.0f; + } + s = a * a; + t = 0.0028662257f; + t *= s; + t += -0.0161657367f; + t *= s; + t += 0.0429096138f; + t *= s; + t += -0.0752896400f; + t *= s; + t += 0.1065626393f; + t *= s; + t += -0.1420889944f; + t *= s; + t += 0.1999355085f; + t *= s; + t += -0.3333314528f; + t *= s; + t += 1.0f; + t *= a; + t += d; + + return t; + +#endif +} + +/* +============ +SSE_ATan4Positive + + Both 'x' and 'y' must be positive. +============ +*/ +void SSE_ATan4Positive( float y[4], float x[4], float at[4] ) { + __asm { + mov esi, x + mov edi, y + mov edx, at + movaps xmm0, [esi] + movaps xmm3, xmm0 + movaps xmm1, [edi] + minps xmm0, xmm1 + maxps xmm1, xmm3 + cmpeqps xmm3, xmm0 + rcpps xmm2, xmm1 + mulps xmm1, xmm2 + mulps xmm1, xmm2 + addps xmm2, xmm2 + subps xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulps xmm0, xmm2 // xmm0 = x / y or y / x + movaps xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + andps xmm3, SIMD_SP_halfPI // xmm3 = HALF_PI or 0.0f + movaps xmm1, xmm0 + mulps xmm1, xmm1 // xmm1 = s + movaps xmm2, SIMD_SP_atan_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c5 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c6 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c7 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + addps xmm2, xmm3 + movaps [edx], xmm2 + } +} + +/* +============ +SSE_ATan +============ +*/ +float SSE_ATan( float y, float x ) { +#if 1 + + float t; + + __asm { + movss xmm0, x + movss xmm3, xmm0 + movss xmm4, xmm0 + andps xmm0, SIMD_SP_absMask + movss xmm1, y + xorps xmm4, xmm1 + andps xmm1, SIMD_SP_absMask + andps xmm4, SIMD_SP_signBit + minss xmm0, xmm1 + maxss xmm1, xmm3 + cmpeqss xmm3, xmm0 + rcpss xmm2, xmm1 + mulss xmm1, xmm2 + mulss xmm1, xmm2 + addss xmm2, xmm2 + subss xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulss xmm0, xmm2 // xmm0 = x / y or y / x + xorps xmm0, xmm4 + movss xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + orps xmm4, SIMD_SP_halfPI // xmm4 = +/- HALF_PI + andps xmm3, xmm4 // xmm3 = +/- HALF_PI or 0.0f + movss xmm1, xmm0 + mulss xmm1, xmm1 // xmm1 = s + movss xmm2, SIMD_SP_atan_c0 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c1 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c2 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c3 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c4 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c5 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c6 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_atan_c7 + mulss xmm2, xmm1 + addss xmm2, SIMD_SP_one + mulss xmm2, xmm0 + addss xmm2, xmm3 + movss t, xmm2 + } + + return t; + +#else + + float a, d, s, t; + + if ( fabs( y ) > fabs( x ) ) { + a = -x / y; + d = idMath::HALF_PI; + *((unsigned long *)&d) ^= ( *((unsigned long *)&x) ^ *((unsigned long *)&y) ) & (1<<31); + } else { + a = y / x; + d = 0.0f; + } + + s = a * a; + t = 0.0028662257f; + t *= s; + t += -0.0161657367f; + t *= s; + t += 0.0429096138f; + t *= s; + t += -0.0752896400f; + t *= s; + t += 0.1065626393f; + t *= s; + t += -0.1420889944f; + t *= s; + t += 0.1999355085f; + t *= s; + t += -0.3333314528f; + t *= s; + t += 1.0f; + t *= a; + t += d; + + return t; + +#endif +} + +/* +============ +SSE_ATan4 +============ +*/ +void SSE_ATan4( float y[4], float x[4], float at[4] ) { + __asm { + mov esi, x + mov edi, y + mov edx, at + movaps xmm0, [esi] + movaps xmm3, xmm0 + movaps xmm4, xmm0 + andps xmm0, SIMD_SP_absMask + movaps xmm1, [edi] + xorps xmm4, xmm1 + andps xmm1, SIMD_SP_absMask + andps xmm4, SIMD_SP_signBit + minps xmm0, xmm1 + maxps xmm1, xmm3 + cmpeqps xmm3, xmm0 + rcpps xmm2, xmm1 + mulps xmm1, xmm2 + mulps xmm1, xmm2 + addps xmm2, xmm2 + subps xmm2, xmm1 // xmm2 = 1 / y or 1 / x + mulps xmm0, xmm2 // xmm0 = x / y or y / x + xorps xmm0, xmm4 + movaps xmm1, xmm3 + andps xmm1, SIMD_SP_signBit + xorps xmm0, xmm1 // xmm0 = -x / y or y / x + orps xmm4, SIMD_SP_halfPI // xmm4 = +/- HALF_PI + andps xmm3, xmm4 // xmm3 = +/- HALF_PI or 0.0f + movaps xmm1, xmm0 + mulps xmm1, xmm1 // xmm1 = s + movaps xmm2, SIMD_SP_atan_c0 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c1 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c2 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c3 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c4 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c5 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c6 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_atan_c7 + mulps xmm2, xmm1 + addps xmm2, SIMD_SP_one + mulps xmm2, xmm0 + addps xmm2, xmm3 + movaps [edx], xmm2 + } +} + +/* +============ +SSE_TestTrigonometry +============ +*/ +void SSE_TestTrigonometry( void ) { + int i; + float a, s1, s2, c1, c2; + + for ( i = 0; i < 100; i++ ) { + a = i * idMath::HALF_PI / 100.0f; + + s1 = idMath::Sin( a ); + s2 = SSE_SinZeroHalfPI( a ); + + if ( fabs( s1 - s2 ) > 1e-7f ) { + assert( 0 ); + } + + c1 = idMath::Cos( a ); + c2 = SSE_CosZeroHalfPI( a ); + + if ( fabs( c1 - c2 ) > 1e-7f ) { + assert( 0 ); + } + } + + for ( i = -200; i < 200; i++ ) { + a = i * idMath::TWO_PI / 100.0f; + + s1 = idMath::Sin( a ); + s2 = SSE_Sin( a ); + + if ( fabs( s1 - s2 ) > 1e-6f ) { + assert( 0 ); + } + + c1 = idMath::Cos( a ); + c2 = SSE_Cos( a ); + + if ( fabs( c1 - c2 ) > 1e-6f ) { + assert( 0 ); + } + + SSE_SinCos( a, s2, c2 ); + if ( fabs( s1 - s2 ) > 1e-6f || fabs( c1 - c2 ) > 1e-6f ) { + assert( 0 ); + } + } +} + +/* +============ +idSIMD_SSE::GetName +============ +*/ +const char * idSIMD_SSE::GetName( void ) const { + return "MMX & SSE"; +} + +/* +============ +idSIMD_SSE::Add + + dst[i] = constant + src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Add( float *dst, const float constant, const float *src, const int count ) { + KFLOAT_CA( add, dst, src, constant, count ) +} + +/* +============ +idSIMD_SSE::Add + + dst[i] = src0[i] + src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Add( float *dst, const float *src0, const float *src1, const int count ) { + KFLOAT_AA( add, dst, src0, src1, count ) +} + +/* +============ +idSIMD_SSE::Sub + + dst[i] = constant - src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Sub( float *dst, const float constant, const float *src, const int count ) { + KFLOAT_CA( sub, dst, src, constant, count ) +} + +/* +============ +idSIMD_SSE::Sub + + dst[i] = src0[i] - src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Sub( float *dst, const float *src0, const float *src1, const int count ) { + KFLOAT_AA( sub, dst, src0, src1, count ) +} + +/* +============ +idSIMD_SSE::Mul + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Mul( float *dst, const float constant, const float *src, const int count ) { + KFLOAT_CA( mul, dst, src, constant, count ) +} + +/* +============ +idSIMD_SSE::Mul + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Mul( float *dst, const float *src0, const float *src1, const int count ) { + KFLOAT_AA( mul, dst, src0, src1, count ) +} + +/* +============ +idSIMD_SSE::Div + + dst[i] = constant / src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Div( float *dst, const float constant, const float *src, const int count ) { + int pre, post; + + // 1 / x = 2 * rcpps(x) - (x * rcpps(x) * rcpps(x)); + __asm + { + movss xmm1,constant + shufps xmm1,xmm1,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [edx+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + mulps xmm4,xmm1 + mulps xmm5,xmm1 + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpA + jmp done + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [edx+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + mulps xmm4,xmm1 + mulps xmm5,xmm1 + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpNA +done: + mov edx,src + mov edi,dst + KFLOATOPER( KDIVDSS1( [edi+ebx],xmm1,[edx+ebx] ), + KDIVDSS4( [edi+ebx],xmm1,[edx+ebx] ), count ) + } +} + +/* +============ +idSIMD_SSE::Div + + dst[i] = src0[i] / src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Div( float *dst, const float *src0, const float *src1, const int count ) { + int pre,post; + + // 1 / x = 2 * rcpps(x) - (x * rcpps(x) * rcpps(x)); + __asm + { + KFLOATINITDSS( dst, src0, src1, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[esi+ebx] + movaps xmm3,[esi+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [esi+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + mulps xmm4,[edx+ebx] + mulps xmm5,[edx+ebx+16] + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpA + jmp done + align 16 +lpNA: + movups xmm2,[esi+ebx] + movups xmm3,[esi+ebx+16] + rcpps xmm4,xmm2 + rcpps xmm5,xmm3 + prefetchnta [esi+ebx+64] + mulps xmm2,xmm4 + mulps xmm2,xmm4 + mulps xmm3,xmm5 + mulps xmm3,xmm5 + addps xmm4,xmm4 + addps xmm5,xmm5 + subps xmm4,xmm2 + subps xmm5,xmm3 + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + mulps xmm4,xmm2 + mulps xmm5,xmm3 + movaps [edi+ebx],xmm4 + movaps [edi+ebx+16],xmm5 + add ebx,16*2 + jl lpNA +done: + mov edx,src0 + mov esi,src1 + mov edi,dst + KFLOATOPER( KDIVDSS1( [edi+ebx],[edx+ebx],[esi+ebx] ), + KDIVDSS4( [edi+ebx],[edx+ebx],[esi+ebx] ), count ) + } +} +/* +============ +Simd_MulAdd + + assumes count >= 7 +============ +*/ +static void Simd_MulAdd( float *dst, const float constant, const float *src, const int count ) { + __asm mov esi, dst + __asm mov edi, src + __asm mov eax, count + __asm shl eax, 2 + __asm mov ecx, esi + __asm mov edx, eax + __asm or ecx, edi + __asm fld constant + __asm and ecx, 15 + __asm jz SimdMulAdd16 + __asm and ecx, 3 + __asm jnz SimdMulAdd8 + __asm mov ecx, esi + __asm xor ecx, edi + __asm and ecx, 15 + __asm jnz MulAdd8 + __asm mov ecx, esi + __asm and ecx, 15 + __asm neg ecx + __asm add ecx, 16 + __asm sub eax, ecx + __asm add edi, ecx + __asm add esi, ecx + __asm neg ecx + __asm mov edx, eax + __asm loopPreMulAdd16: + __asm fld st + __asm fmul dword ptr [edi+ecx] + __asm fadd dword ptr [esi+ecx] + __asm fstp dword ptr [esi+ecx] + __asm add ecx, 4 + __asm jl loopPreMulAdd16 + __asm SimdMulAdd16: + __asm and eax, ~15 + __asm movss xmm1, constant + __asm shufps xmm1, xmm1, 0x00 + __asm add esi, eax + __asm add edi, eax + __asm neg eax + __asm align 16 + __asm loopMulAdd16: + __asm movaps xmm0, [edi+eax] + __asm mulps xmm0, xmm1 + __asm addps xmm0, [esi+eax] + __asm movaps [esi+eax], xmm0 + __asm add eax, 16 + __asm jl loopMulAdd16 + __asm jmp postMulAdd + __asm MulAdd8: + __asm mov ecx, esi + __asm and ecx, 7 + __asm jz SimdMulAdd8 + __asm sub eax, ecx + __asm add esi, ecx + __asm add edi, ecx + __asm neg ecx + __asm mov edx, eax + __asm loopPreMulAdd8: + __asm fld st + __asm fmul dword ptr [edi+ecx] + __asm fadd dword ptr [esi+ecx] + __asm fstp dword ptr [esi+ecx] + __asm add ecx, 4 + __asm jl loopPreMulAdd8 + __asm SimdMulAdd8: + __asm and eax, ~15 + __asm movss xmm1, constant + __asm shufps xmm1, xmm1, 0x00 + __asm add esi, eax + __asm add edi, eax + __asm neg eax + __asm align 16 + __asm loopMulAdd8: + __asm movlps xmm0, [edi+eax] + __asm movhps xmm0, [edi+eax+8] + __asm mulps xmm0, xmm1 + __asm movlps xmm2, [esi+eax] + __asm movhps xmm2, [esi+eax+8] + __asm addps xmm0, xmm2 + __asm movlps [esi+eax], xmm0 + __asm movhps [esi+eax+8], xmm0 + __asm add eax, 16 + __asm jl loopMulAdd8 + __asm jmp postMulAdd + __asm postMulAdd: + __asm and edx, 15 + __asm jz MulAddDone + __asm add esi, edx + __asm add edi, edx + __asm neg edx + __asm loopPostMulAdd: + __asm fld st + __asm fmul dword ptr [edi+edx] + __asm fadd dword ptr [esi+edx] + __asm fstp dword ptr [esi+edx] + __asm add edx, 4 + __asm jl loopPostMulAdd + __asm MulAddDone: + __asm fstp st +} + +#define MULADD_FEW( OPER ) \ +switch( count ) { \ + case 0: \ + return; \ + case 1: \ + dst[0] OPER c * src[0]; \ + return; \ + case 2: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; \ + return; \ + case 3: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; \ + return; \ + case 4: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + return; \ + case 5: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; \ + return; \ + case 6: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; \ + return; \ + case 7: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; \ + return; \ + case 8: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + return; \ + case 9: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + dst[8] OPER c * src[8]; \ + return; \ + case 10: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + dst[8] OPER c * src[8]; dst[9] OPER c * src[9]; \ + return; \ + case 11: \ + dst[0] OPER c * src[0]; dst[1] OPER c * src[1]; dst[2] OPER c * src[2]; dst[3] OPER c * src[3]; \ + dst[4] OPER c * src[4]; dst[5] OPER c * src[5]; dst[6] OPER c * src[6]; dst[7] OPER c * src[7]; \ + dst[8] OPER c * src[8]; dst[9] OPER c * src[9]; dst[10] OPER c * src[10]; \ + return; \ +} + +/* +============ +idSIMD_SSE::MulAdd + + dst[i] += constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulAdd( float *dst, const float constant, const float *src, const int count ) { + float c = constant; + MULADD_FEW( += ) + Simd_MulAdd( dst, constant, src, count ); +} + +/* +============ +idSIMD_SSE::MulAdd + + dst[i] += src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulAdd( float *dst, const float *src0, const float *src1, const int count ) { + for ( int i = 0; i < count; i++ ) { + dst[i] += src0[i] + src1[i]; + } +} + +/* +============ +idSIMD_SSE::MulSub + + dst[i] -= constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulSub( float *dst, const float constant, const float *src, const int count ) { + float c = constant; + MULADD_FEW( -= ) + Simd_MulAdd( dst, -constant, src, count ); +} + +/* +============ +idSIMD_SSE::MulSub + + dst[i] -= src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::MulSub( float *dst, const float *src0, const float *src1, const int count ) { + for ( int i = 0; i < count; i++ ) { + dst[i] -= src0[i] + src1[i]; + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ) { + __asm + { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz done4 + imul eax, 12 + add esi, eax + neg eax + + loop4: + movlps xmm1, [esi+eax+ 0] + movlps xmm2, [esi+eax+ 8] + movlps xmm3, [esi+eax+16] + movhps xmm1, [esi+eax+24] + movhps xmm2, [esi+eax+32] + movhps xmm3, [esi+eax+40] + movaps xmm0, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 0, 2 ) + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + add ecx, 16 + add eax, 4*12 + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loop4 + + done4: + and edx, 3 + jz done1 + + loop1: + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm2, [esi+eax+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + add ecx, 4 + addss xmm0, xmm1 + add eax, 12 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loop1 + + done1: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i].Normal() + src[i][3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ) { + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm5, [edi+0] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + imul eax, 16 + add esi, eax + neg eax + + loopVert4: + + movlps xmm1, [esi+eax+ 0] + movlps xmm3, [esi+eax+ 8] + movhps xmm1, [esi+eax+16] + movhps xmm3, [esi+eax+24] + movlps xmm2, [esi+eax+32] + movlps xmm4, [esi+eax+40] + movhps xmm2, [esi+eax+48] + movhps xmm4, [esi+eax+56] + movaps xmm0, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm1, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm2, xmm3 + shufps xmm2, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm3, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) + + add ecx, 16 + add eax, 4*16 + + mulps xmm0, xmm5 + mulps xmm1, xmm6 + mulps xmm2, xmm7 + addps xmm0, xmm3 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm2, [esi+eax+8] + mulss xmm0, xmm5 + mulss xmm1, xmm6 + mulss xmm2, xmm7 + addss xmm0, [esi+eax+12] + add ecx, 4 + addss xmm0, xmm1 + add eax, 16 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ) { + + assert_16_byte_aligned( src ); + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert4: + movlps xmm1, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, X, X */ + movss xmm2, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, X, X */ + movhps xmm1, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, 4, 5 */ + movhps xmm2, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, 6, 7 */ + movlps xmm3, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, X, X */ + movss xmm7, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, X, X */ + movhps xmm3, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, 14, 15 */ + movaps xmm0, xmm1 /* xmm0 = 0, 1, 4, 5 */ + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm0 = 0, 4, 8, 12 */ + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm1 = 1, 5, 9, 13 */ + shufps xmm2, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm2 = 2, 6, 10, 14 */ + + add ecx, 16 + add eax, 4*DRAWVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_XYZ_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + add ecx, 4 + addss xmm0, xmm1 + add eax, DRAWVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i] + constant[3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const idVec3 *src, const int count ) { + __asm + { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz done4 + imul eax, 12 + add esi, eax + neg eax + + loop4: + movlps xmm1, [esi+eax+ 0] + movlps xmm2, [esi+eax+ 8] + movlps xmm3, [esi+eax+16] + movhps xmm1, [esi+eax+24] + movhps xmm2, [esi+eax+32] + movhps xmm3, [esi+eax+40] + movaps xmm0, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 0, 2 ) + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + + add ecx, 16 + add eax, 4*12 + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 2, 1, 3 ) + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loop4 + + done4: + and edx, 3 + jz done1 + + loop1: + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm2, [esi+eax+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + addss xmm0, xmm7 + add ecx, 4 + addss xmm0, xmm1 + add eax, 12 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loop1 + + done1: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i].Normal() + constant[3] * src[i][3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const idPlane *src, const int count ) { + +#define SINGLE_OP(SRC, DEST) \ + __asm movlps xmm0,[SRC] \ + __asm movlps xmm1,[SRC+8] \ + __asm mulps xmm0,xmm4 \ + __asm mulps xmm1,xmm5 \ + __asm addps xmm0,xmm1 \ + __asm movaps xmm1,xmm0 \ + __asm shufps xmm1,xmm1,SHUFFLE_PS(1,1,1,1) \ + __asm addss xmm0,xmm1 \ + __asm movss [DEST],xmm0 \ + __asm add SRC,16 \ + __asm add DEST,4 + +#define DUAL_OP(SRC, DEST) \ + __asm movlps xmm0,[SRC] \ + __asm movlps xmm1,[SRC+8] \ + __asm movhps xmm0,[SRC+16] \ + __asm movhps xmm1,[SRC+24] \ + __asm mulps xmm0,xmm4 \ + __asm mulps xmm1,xmm5 \ + __asm addps xmm0,xmm1 \ + __asm shufps xmm1,xmm0,SHUFFLE_PS(2,0,1,0) \ + __asm shufps xmm0,xmm0,SHUFFLE_PS(3,1,2,0) \ + __asm addps xmm0,xmm1 \ + __asm movhps [DEST],xmm0 \ + __asm add SRC,32 \ + __asm add DEST,8 + + __asm { + mov edx, dst + mov eax, src + mov ebx, constant + mov ecx, count + + movlps xmm4, [ebx] + shufps xmm4, xmm4, SHUFFLE_PS(1,0,1,0) + movlps xmm5, [ebx+8] + shufps xmm5, xmm5, SHUFFLE_PS(1,0,1,0) + + xorps xmm0, xmm0 + xorps xmm1, xmm1 + + _lpAlignDest: + test edx, 0x0f + jz _destAligned + SINGLE_OP(eax,edx) + dec ecx + jnz _lpAlignDest + jmp _vpExit + + _destAligned: + push ecx + + cmp ecx, 4 + jl _post + + and ecx, ~3 + shl ecx, 2 + lea eax, [eax+ecx*4] + add edx, ecx + neg ecx + + movlps xmm0, [eax+ecx*4] + movhps xmm0, [eax+ecx*4+16] + movlps xmm2, [eax+ecx*4+32] + movhps xmm2, [eax+ecx*4+48] + jmp _lpStart + + align 16 + _lp: + prefetchnta [eax+ecx*4+128] + addps xmm1, xmm0 + movlps xmm0, [eax+ecx*4] + movhps xmm0, [eax+ecx*4+16] + movlps xmm2, [eax+ecx*4+32] + movhps xmm2, [eax+ecx*4+48] + movaps [edx+ecx-16],xmm1 + _lpStart: + movlps xmm1, [eax+ecx*4+8] + movhps xmm1, [eax+ecx*4+24] + movlps xmm3, [eax+ecx*4+40] + movhps xmm3, [eax+ecx*4+56] + add ecx, 16 + mulps xmm1, xmm5 + mulps xmm2, xmm4 + mulps xmm3, xmm5 + addps xmm2, xmm3 // y3+w3 x3+z3 y2+w2 x2+z2 + mulps xmm0, xmm4 + addps xmm0, xmm1 // y1+w1 x1+z1 y0+w0 x0+z0 + movaps xmm1, xmm0 + shufps xmm0, xmm2, SHUFFLE_PS(2,0,2,0) // x3+z3 x2+z2 x1+z1 x0+z0 + shufps xmm1, xmm2, SHUFFLE_PS(3,1,3,1) // y3+w3 y2+w2 y1+w1 y0+w0 + js _lp + addps xmm1, xmm0 + movaps [edx+ecx-16], xmm1 + _post: + pop ecx + and ecx, 0x3 + cmp ecx, 2 + jl _post1 + DUAL_OP(eax,edx) + sub ecx, 2 + _post1: + cmp ecx, 1 + jne _vpExit + SINGLE_OP(eax,edx) + _vpExit: + } + +#undef DUAL_OP +#undef SINGLE_OP + +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const idDrawVert *src, const int count ) { + assert_16_byte_aligned( src ); + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert4: + movlps xmm1, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, X, X */ + movss xmm2, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, X, X */ + movhps xmm1, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, 4, 5 */ + movhps xmm2, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, 3, 6, 7 */ + movlps xmm3, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, X, X */ + movhps xmm3, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] /* xmm3 = 8, 9, 12, 13 */ + movaps xmm0, xmm1 /* xmm0 = 0, 1, 4, 5 */ + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm0 = 0, 4, 8, 12 */ + shufps xmm1, xmm3, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm1 = 1, 5, 9, 13 */ + movss xmm3, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, X, X */ + movhps xmm3, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] /* xmm3 = 10, 11, 14, 15 */ + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm2 = 2, 6, 10, 14 */ + + add ecx, 16 + add eax, 4*DRAWVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_XYZ_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + addss xmm0, xmm7 + add ecx, 4 + addss xmm0, xmm1 + add eax, DRAWVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = src0[i] * src1[i]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ) { + __asm + { + mov eax, count + mov edi, src0 + mov edx, eax + mov esi, src1 + mov ecx, dst + and eax, ~3 + + jz done4 + imul eax, 12 + add edi, eax + add esi, eax + neg eax + + loop4: + movlps xmm0, [esi+eax] // 0, 1, X, X + movlps xmm3, [edi+eax] // 0, 1, X, X + movlps xmm1, [esi+eax+8] // 2, 3, X, X + movlps xmm4, [edi+eax+8] // 2, 3, X, X + movhps xmm0, [esi+eax+24] // 0, 1, 6, 7 + movhps xmm3, [edi+eax+24] // 0, 1, 6, 7 + movhps xmm1, [esi+eax+32] // 2, 3, 8, 9 + movhps xmm4, [edi+eax+32] // 2, 3, 8, 9 + movlps xmm2, [esi+eax+16] // 4, 5, X, X + movlps xmm5, [edi+eax+16] // 4, 5, X, X + movhps xmm2, [esi+eax+40] // 4, 5, 10, 11 + movhps xmm5, [edi+eax+40] // 4, 5, 10, 11 + + add ecx, 16 + add eax, 48 + + mulps xmm0, xmm3 + mulps xmm1, xmm4 + mulps xmm2, xmm5 + movaps xmm7, xmm0 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) // 0, 6, 3, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 1, 3, 0, 2 ) // 1, 7, 4, 10 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 2, 1, 3 ) // 2, 8, 5, 11 + addps xmm7, xmm0 + addps xmm7, xmm1 + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 2, 1, 3 ) + + movlps [ecx-16+0], xmm7 + movhps [ecx-16+8], xmm7 + jl loop4 + + done4: + and edx, 3 + jz done1 + + loop1: + movss xmm0, [esi+eax+0] + movss xmm3, [edi+eax+0] + movss xmm1, [esi+eax+4] + movss xmm4, [edi+eax+4] + movss xmm2, [esi+eax+8] + movss xmm5, [edi+eax+8] + mulss xmm0, xmm3 + mulss xmm1, xmm4 + mulss xmm2, xmm5 + add ecx, 4 + addss xmm0, xmm1 + add eax, 12 + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loop1 + + done1: + } +} + +/* +============ +idSIMD_SSE::Dot + + dot = src1[0] * src2[0] + src1[1] * src2[1] + src1[2] * src2[2] + ... +============ +*/ +void VPCALL idSIMD_SSE::Dot( float &dot, const float *src1, const float *src2, const int count ) { + switch( count ) { + case 0: + dot = 0.0f; + return; + case 1: + dot = src1[0] * src2[0]; + return; + case 2: + dot = src1[0] * src2[0] + src1[1] * src2[1]; + return; + case 3: + dot = src1[0] * src2[0] + src1[1] * src2[1] + src1[2] * src2[2]; + return; + default: + __asm { + mov ecx, src1 + mov edx, src2 + mov eax, ecx + or eax, edx + and eax, 15 + jz alignedDot + // unaligned + mov eax, count + shr eax, 2 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + movups xmm0, [ecx+eax] + movups xmm1, [edx+eax] + mulps xmm0, xmm1 + add eax, 16 + jz doneDot + loopUnalignedDot: + movups xmm1, [ecx+eax] + movups xmm2, [edx+eax] + mulps xmm1, xmm2 + addps xmm0, xmm1 + add eax, 16 + jl loopUnalignedDot + jmp doneDot + // aligned + alignedDot: + mov eax, count + shr eax, 2 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + movaps xmm0, [ecx+eax] + movaps xmm1, [edx+eax] + mulps xmm0, xmm1 + add eax, 16 + jz doneDot + loopAlignedDot: + movaps xmm1, [ecx+eax] + movaps xmm2, [edx+eax] + mulps xmm1, xmm2 + addps xmm0, xmm1 + add eax, 16 + jl loopAlignedDot + doneDot: + } + switch( count & 3 ) { + case 1: + __asm { + movss xmm1, [ecx] + movss xmm2, [edx] + mulss xmm1, xmm2 + addss xmm0, xmm1 + } + break; + case 2: + __asm { + xorps xmm2, xmm2 + movlps xmm1, [ecx] + movlps xmm2, [edx] + mulps xmm1, xmm2 + addps xmm0, xmm1 + } + break; + case 3: + __asm { + movss xmm1, [ecx] + movhps xmm1, [ecx+4] + movss xmm2, [edx] + movhps xmm2, [edx+4] + mulps xmm1, xmm2 + addps xmm0, xmm1 + } + break; + } + __asm { + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + mov eax, dot + movss [eax], xmm0 + } + return; + } +} + +// +// cmpeqps == Equal +// cmpneqps != Not Equal +// cmpltps < Less Than +// cmpnltps >= Not Less Than +// cmpnleps > Not Less Or Equal +// +#define FLIP not al +#define NOFLIP + +#define COMPARECONSTANT( DST, SRC0, CONSTANT, COUNT, CMP, CMPSIMD, DOFLIP ) \ + int i, cnt, pre, post; \ + float *aligned; \ + \ + /* if the float array is not aligned on a 4 byte boundary */ \ + if ( ((int) SRC0) & 3 ) { \ + /* unaligned memory access */ \ + pre = 0; \ + cnt = COUNT >> 2; \ + post = COUNT - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, SRC0 \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm mov ecx, 0x01010101 \ + __asm loopNA: \ + __asm movups xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, ecx \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopNA \ + __asm pop ebx \ + } \ + else { \ + /* aligned memory access */ \ + aligned = (float *) ((((int) SRC0) + 15) & ~15); \ + if ( (int)aligned > ((int)src0) + COUNT ) { \ + pre = COUNT; \ + post = 0; \ + } \ + else { \ + pre = aligned - SRC0; \ + cnt = (COUNT - pre) >> 2; \ + post = COUNT - pre - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, aligned \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm add edi, pre \ + __asm mov ecx, 0x01010101 \ + __asm loopA: \ + __asm movaps xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, ecx \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopA \ + __asm pop ebx \ + } \ + } \ + doneCmp: \ + double c = constant; \ + for ( i = 0; i < pre; i++ ) { \ + dst[i] = src0[i] CMP c; \ + } \ + for ( i = count - post; i < count; i++ ) { \ + dst[i] = src0[i] CMP c; \ + } + +#define COMPAREBITCONSTANT( DST, BITNUM, SRC0, CONSTANT, COUNT, CMP, CMPSIMD, DOFLIP ) \ + int i, cnt, pre, post; \ + float *aligned; \ + \ + /* if the float array is not aligned on a 4 byte boundary */ \ + if ( ((int) SRC0) & 3 ) { \ + /* unaligned memory access */ \ + pre = 0; \ + cnt = COUNT >> 2; \ + post = COUNT - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, SRC0 \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm mov cl, bitNum \ + __asm loopNA: \ + __asm movups xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, 0x01010101 \ + __asm shl ebx, cl \ + __asm or ebx, dword ptr [edi] \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopNA \ + __asm pop ebx \ + } \ + else { \ + /* aligned memory access */ \ + aligned = (float *) ((((int) SRC0) + 15) & ~15); \ + if ( (int)aligned > ((int)src0) + COUNT ) { \ + pre = COUNT; \ + post = 0; \ + } \ + else { \ + pre = aligned - SRC0; \ + cnt = (COUNT - pre) >> 2; \ + post = COUNT - pre - (cnt<<2); \ + __asm mov edx, cnt \ + __asm test edx, edx \ + __asm je doneCmp \ + __asm push ebx \ + __asm neg edx \ + __asm mov esi, aligned \ + __asm prefetchnta [esi+64] \ + __asm movss xmm1, CONSTANT \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mov edi, DST \ + __asm add edi, pre \ + __asm mov cl, bitNum \ + __asm loopA: \ + __asm movaps xmm0, [esi] \ + __asm prefetchnta [esi+128] \ + __asm CMPSIMD xmm0, xmm1 \ + __asm movmskps eax, xmm0 \ + __asm DOFLIP \ + __asm mov ah, al \ + __asm shr ah, 1 \ + __asm mov bx, ax \ + __asm shl ebx, 14 \ + __asm mov bx, ax \ + __asm and ebx, 0x01010101 \ + __asm shl ebx, cl \ + __asm or ebx, dword ptr [edi] \ + __asm mov dword ptr [edi], ebx \ + __asm add esi, 16 \ + __asm add edi, 4 \ + __asm inc edx \ + __asm jl loopA \ + __asm pop ebx \ + } \ + } \ + doneCmp: \ + float c = constant; \ + for ( i = 0; i < pre; i++ ) { \ + dst[i] |= ( src0[i] CMP c ) << BITNUM; \ + } \ + for ( i = count - post; i < count; i++ ) { \ + dst[i] |= ( src0[i] CMP c ) << BITNUM; \ + } + +/* +============ +idSIMD_SSE::CmpGT + + dst[i] = src0[i] > constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpGT( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, >, cmpnleps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpGT + + dst[i] |= ( src0[i] > constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, >, cmpnleps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpGE + + dst[i] = src0[i] >= constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpGE( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, >=, cmpnltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpGE + + dst[i] |= ( src0[i] >= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, >=, cmpnltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpLT + + dst[i] = src0[i] < constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpLT( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, <, cmpltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpLT + + dst[i] |= ( src0[i] < constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, <, cmpltps, NOFLIP ) +} + +/* +============ +idSIMD_SSE::CmpLE + + dst[i] = src0[i] <= constant; +============ +*/ +void VPCALL idSIMD_SSE::CmpLE( byte *dst, const float *src0, const float constant, const int count ) { + COMPARECONSTANT( dst, src0, constant, count, <=, cmpnleps, FLIP ) +} + +/* +============ +idSIMD_SSE::CmpLE + + dst[i] |= ( src0[i] <= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_SSE::CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ) { + COMPAREBITCONSTANT( dst, bitNum, src0, constant, count, <=, cmpnleps, FLIP ) +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( float &min, float &max, const float *src, const int count ) { + int i, pre, post; + + min = idMath::INFINITY; max = -idMath::INFINITY; + + __asm + { + push ebx + mov eax, min + mov ebx, max + movss xmm0, [eax] + movss xmm1, [ebx] + shufps xmm0, xmm0, 0 + shufps xmm1, xmm1, 0 + + KFLOATINITS( src, count, pre, post ) + and eax, 15 + jz lpA + jmp lpNA + align 16 +lpNA: + movups xmm2, [edx+ebx] + movups xmm3, [edx+ebx+16] + minps xmm0, xmm2 + maxps xmm1, xmm2 + prefetchnta [edx+ebx+64] + minps xmm0, xmm3 + maxps xmm1, xmm3 + add ebx, 16*2 + jl lpNA + jmp done2 +lpA: + movaps xmm2, [edx+ebx] + movaps xmm3, [edx+ebx+16] + minps xmm0, xmm2 + maxps xmm1, xmm2 + prefetchnta [edx+ebx+64] + minps xmm0, xmm3 + maxps xmm1, xmm3 + add ebx, 16*2 + jl lpA + jmp done2 + align 16 +done2: + movaps xmm2, xmm0 + movaps xmm3, xmm1 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + minss xmm0, xmm2 + maxss xmm1, xmm3 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + minss xmm0, xmm2 + maxss xmm1, xmm3 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + minss xmm0, xmm2 + maxss xmm1, xmm3 + mov eax, min + mov ebx, max + movss [eax], xmm0 + movss [ebx], xmm1 +done: + pop ebx + } + + for ( i = 0; i < pre; i++ ) { + float tmp = src[i]; + if ( tmp > max ) { + max = tmp; + } + if ( tmp < min ) { + min = tmp; + } + } + for ( i = count - post; i < count; i++ ) { + float tmp = src[i]; + if ( tmp > max ) { + max = tmp; + } + if ( tmp < min ) { + min = tmp; + } + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ) { + __asm { + mov eax, count + test eax, eax + movss xmm0, idMath::INFINITY + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + jz done + mov esi, src + test eax, 1 + jz startLoop + movlps xmm2, [esi] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + add esi, 2*4 + dec eax + minps xmm0, xmm2 + maxps xmm1, xmm2 + jz done + startLoop: + shl eax, 3 + add esi, eax + neg eax + loopVert: + movlps xmm2, [esi+eax] + movhps xmm2, [esi+eax+8] + add eax, 4*4 + minps xmm0, xmm2 + maxps xmm1, xmm2 + jl loopVert + done: + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 3, 0, 1 ) + minps xmm0, xmm2 + mov esi, min + movlps [esi], xmm0 + movaps xmm3, xmm1 + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 3, 0, 1 ) + maxps xmm1, xmm3 + mov edi, max + movlps [edi], xmm1 + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ) { + __asm { + + movss xmm0, idMath::INFINITY + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + imul eax, 12 + add esi, eax + neg eax + + loop4: +// prefetchnta [esi+4*12] + + movss xmm4, [esi+eax+0*12+8] + movhps xmm4, [esi+eax+0*12+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + movss xmm5, [esi+eax+1*12+0] + movhps xmm5, [esi+eax+1*12+4] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + movss xmm6, [esi+eax+2*12+8] + movhps xmm6, [esi+eax+2*12+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + movss xmm7, [esi+eax+3*12+0] + movhps xmm7, [esi+eax+3*12+4] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*12 + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + imul eax, 12 + add esi, eax + neg eax + + loop1: + movss xmm4, [esi+eax+0*12+8] + movhps xmm4, [esi+eax+0*12+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, 12 + jl loop1 + + done1: + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 1, 0, 2 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 1, 0, 2 ) + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ) { + + __asm { + + movss xmm0, SIMD_SP_infinity + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loop4: + movss xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + movss xmm5, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm5, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + movss xmm6, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm6, [esi+eax+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + movss xmm7, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm7, [esi+eax+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*DRAWVERT_SIZE + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loop1: + movss xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, DRAWVERT_SIZE + jl loop1 + + done1: + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ) { + + __asm { + + movss xmm0, SIMD_SP_infinity + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov edi, indexes + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + shl eax, 2 + add edi, eax + neg eax + + loop4: + mov edx, [edi+eax+0] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + mov edx, [edi+eax+4] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm5, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm5, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + mov edx, [edi+eax+8] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + mov edx, [edi+eax+12] + shl edx, DRAWVERT_SIZE_SHIFT + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*4 + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + shl eax, 2 + add edi, eax + neg eax + + loop1: + mov edx, [edi+eax+0] + imul edx, DRAWVERT_SIZE; + movss xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + movhps xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, 4 + jl loop1 + + done1: + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +} + +/* +============ +idSIMD_SSE::Clamp +============ +*/ +void VPCALL idSIMD_SSE::Clamp( float *dst, const float *src, const float min, const float max, const int count ) { + int i, pre, post; + + __asm + { + movss xmm0,min + movss xmm1,max + shufps xmm0,xmm0,0 + shufps xmm1,xmm1,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + maxps xmm3,xmm0 + prefetchnta [edx+ebx+64] + minps xmm2,xmm1 + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpA + jmp done + + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + maxps xmm3,xmm0 + prefetchnta [edx+ebx+64] + minps xmm2,xmm1 + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpNA +done: + } + + for ( i = 0; i < pre; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } + + for( i = count - post; i < count; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } +} + +/* +============ +idSIMD_SSE::ClampMin +============ +*/ +void VPCALL idSIMD_SSE::ClampMin( float *dst, const float *src, const float min, const int count ) { + int i, pre, post; + + __asm + { + movss xmm0,min + shufps xmm0,xmm0,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + prefetchnta [edx+ebx+64] + maxps xmm3,xmm0 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpA + jmp done + + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + maxps xmm2,xmm0 + prefetchnta [edx+ebx+64] + maxps xmm3,xmm0 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpNA +done: + } + + for( i = 0; i < pre; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else + dst[i] = src[i]; + } + for( i = count - post; i < count; i++ ) { + if ( src[i] < min ) + dst[i] = min; + else + dst[i] = src[i]; + } +} + +/* +============ +idSIMD_SSE::ClampMax +============ +*/ +void VPCALL idSIMD_SSE::ClampMax( float *dst, const float *src, const float max, const int count ) { + int i, pre, post; + + __asm + { + movss xmm1,max + shufps xmm1,xmm1,0 + + KFLOATINITDS( dst, src, count, pre, post ) + and eax,15 + jne lpNA + jmp lpA + align 16 +lpA: + movaps xmm2,[edx+ebx] + movaps xmm3,[edx+ebx+16] + minps xmm2,xmm1 + prefetchnta [edx+ebx+64] + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpA + jmp done + + align 16 +lpNA: + movups xmm2,[edx+ebx] + movups xmm3,[edx+ebx+16] + minps xmm2,xmm1 + prefetchnta [edx+ebx+64] + minps xmm3,xmm1 + movaps [edi+ebx],xmm2 + movaps [edi+ebx+16],xmm3 + add ebx,16*2 + jl lpNA +done: + } + + for( i = 0; i < pre; i++ ) { + if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } + + for( i = count - post; i < count; i++ ) { + if ( src[i] > max ) + dst[i] = max; + else + dst[i] = src[i]; + } +} + +/* +============ +idSIMD_SSE::Zero16 +============ +*/ +void VPCALL idSIMD_SSE::Zero16( float *dst, const int count ) { + __asm { + mov edx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneZero16 + shl eax, 4 + add edx, eax + neg eax + xorps xmm0, xmm0 + loopZero16: + movaps [edx+eax], xmm0 + add eax, 16 + jl loopZero16 + doneZero16: + } +} + +/* +============ +idSIMD_SSE::Negate16 +============ +*/ +void VPCALL idSIMD_SSE::Negate16( float *dst, const int count ) { + __asm { + mov edx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneNegate16 + shl eax, 4 + add edx, eax + neg eax + movss xmm0, SIMD_SP_signBit + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + loopNegate16: + movaps xmm1, [edx+eax] + xorps xmm1, xmm0 + movaps [edx+eax], xmm1 + add eax, 16 + jl loopNegate16 + doneNegate16: + } +} + +/* +============ +idSIMD_SSE::Copy16 +============ +*/ +void VPCALL idSIMD_SSE::Copy16( float *dst, const float *src, const int count ) { + __asm { + mov ecx, src + mov edx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneCopy16 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + loopCopy16: + movaps xmm0, [ecx+eax] + movaps [edx+eax], xmm0 + add eax, 16 + jl loopCopy16 + doneCopy16: + } +} + +/* +============ +idSIMD_SSE::Add16 +============ +*/ +void VPCALL idSIMD_SSE::Add16( float *dst, const float *src1, const float *src2, const int count ) { + __asm { + mov ecx, src1 + mov edx, src2 + mov esi, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneAdd16 + shl eax, 4 + add esi, eax + add ecx, eax + add edx, eax + neg eax + loopAdd16: + movaps xmm0, [ecx+eax] + addps xmm0, [edx+eax] + movaps [esi+eax], xmm0 + add eax, 16 + jl loopAdd16 + doneAdd16: + } +} + +/* +============ +idSIMD_SSE::Sub16 +============ +*/ +void VPCALL idSIMD_SSE::Sub16( float *dst, const float *src1, const float *src2, const int count ) { + __asm { + mov ecx, src1 + mov edx, src2 + mov esi, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneSub16 + shl eax, 4 + add esi, eax + add ecx, eax + add edx, eax + neg eax + loopSub16: + movaps xmm0, [ecx+eax] + subps xmm0, [edx+eax] + movaps [esi+eax], xmm0 + add eax, 16 + jl loopSub16 + doneSub16: + } +} + +/* +============ +idSIMD_SSE::Mul16 +============ +*/ +void VPCALL idSIMD_SSE::Mul16( float *dst, const float *src1, const float constant, const int count ) { + __asm { + mov ecx, dst + mov edx, src1 + mov eax, count + add eax, 3 + shr eax, 2 + jz doneMulScalar16 + movss xmm1, constant + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + shufps xmm1, xmm1, 0x00 + loopMulScalar16: + movaps xmm0, [edx+eax] + mulps xmm0, xmm1 + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopMulScalar16 + doneMulScalar16: + } +} + +/* +============ +idSIMD_SSE::AddAssign16 +============ +*/ +void VPCALL idSIMD_SSE::AddAssign16( float *dst, const float *src, const int count ) { + __asm { + mov ecx, dst + mov edx, src + mov eax, count + add eax, 3 + shr eax, 2 + jz doneAddAssign16 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + loopAddAssign16: + movaps xmm0, [ecx+eax] + addps xmm0, [edx+eax] + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopAddAssign16 + doneAddAssign16: + } +} + +/* +============ +idSIMD_SSE::SubAssign16 +============ +*/ +void VPCALL idSIMD_SSE::SubAssign16( float *dst, const float *src, const int count ) { + __asm { + mov ecx, dst + mov edx, src + mov eax, count + add eax, 3 + shr eax, 2 + jz doneSubAssign16 + shl eax, 4 + add ecx, eax + add edx, eax + neg eax + loopSubAssign16: + movaps xmm0, [ecx+eax] + subps xmm0, [edx+eax] + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopSubAssign16 + doneSubAssign16: + } +} + +/* +============ +idSIMD_SSE::MulAssign16 +============ +*/ +void VPCALL idSIMD_SSE::MulAssign16( float *dst, const float constant, const int count ) { + __asm { + mov ecx, dst + mov eax, count + add eax, 3 + shr eax, 2 + jz doneMulAssign16 + movss xmm1, constant + shl eax, 4 + add ecx, eax + neg eax + shufps xmm1, xmm1, 0x00 + loopMulAssign16: + movaps xmm0, [ecx+eax] + mulps xmm0, xmm1 + movaps [ecx+eax], xmm0 + add eax, 16 + jl loopMulAssign16 + doneMulAssign16: + } +} + +/* +============ +idSIMD_SSE::MatX_MultiplyVecX + + optimizes the following matrix multiplications: + + NxN * Nx1 + Nx6 * 6x1 + 6xN * Nx1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss [eax+offset], reg1 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps [eax+offset], reg1 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 \ + __asm movhps [eax+offset+8], reg1 +#define STOREC = + + int numRows; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: { + switch( numRows ) { + case 1: { // 1x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + STORE1( 0, xmm0, xmm1 ) + } + return; + } + case 6: { // 6x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0]; + mPtr++; + } + return; + } + } + break; + } + case 2: { + switch( numRows ) { + case 2: { // 2x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm1, [esi+4] + movss xmm2, [edi] + mulss xmm2, xmm0 + movss xmm3, [edi+4] + mulss xmm3, xmm1 + addss xmm2, xmm3 + STORE1( 0, xmm2, xmm4 ) + mulss xmm0, [edi+8] + mulss xmm1, [edi+8+4] + addss xmm0, xmm1 + STORE1( 4, xmm0, xmm4 ) + } + return; + } + case 6: { // 6x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, [esi] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movaps xmm0, [edi] + mulps xmm0, xmm7 + movaps xmm1, [edi+16] + mulps xmm1, xmm7 + movaps xmm2, xmm0 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm2, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm3, [edi+32] + addps xmm0, xmm2 + mulps xmm3, xmm7 + STORE4( 0, xmm0, xmm4 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm1, xmm3 + addps xmm3, xmm1 + STORE2LO( 16, xmm3, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + return; + } + } + break; + } + case 3: { + switch( numRows ) { + case 3: { // 3x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm4, [edi] + mulss xmm4, xmm0 + movss xmm1, [esi+4] + movss xmm5, [edi+4] + mulss xmm5, xmm1 + addss xmm4, xmm5 + movss xmm2, [esi+8] + movss xmm6, [edi+8] + mulss xmm6, xmm2 + addss xmm4, xmm6 + movss xmm3, [edi+12] + mulss xmm3, xmm0 + STORE1( 0, xmm4, xmm7 ); + movss xmm5, [edi+12+4] + mulss xmm5, xmm1 + addss xmm3, xmm5 + movss xmm6, [edi+12+8] + mulss xmm6, xmm2 + addss xmm3, xmm6 + mulss xmm0, [edi+24] + mulss xmm1, [edi+24+4] + STORE1( 4, xmm3, xmm7 ); + addss xmm0, xmm1 + mulss xmm2, [edi+24+8] + addss xmm0, xmm2 + STORE1( 8, xmm0, xmm7 ); + } + return; + } + case 6: { // 6x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm5, [esi] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm0, [edi] // xmm0 = 0, 1, 2, 3 + movlps xmm1, [edi+4*4] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm1 = 4, 5, 1, 2 + movlps xmm2, [edi+6*4] + movhps xmm2, [edi+8*4] // xmm2 = 6, 7, 8, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 3, 0, 3 ) // xmm0 = 0, 3, 6, 9 + mulps xmm0, xmm5 + movlps xmm3, [edi+10*4] + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 0, 1 ) // xmm2 = 7, 8, 10, 11 + movaps xmm3, xmm1 + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 0, 2 ) // xmm1 = 1, 4, 7, 10 + mulps xmm1, xmm6 + shufps xmm3, xmm2, R_SHUFFLE_PS( 3, 1, 1, 3 ) // xmm3 = 2, 5, 8, 11 + mulps xmm3, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm3 + STORE4( 0, xmm0, xmm4 ) + movss xmm1, [edi+12*4] + mulss xmm1, xmm5 + movss xmm2, [edi+13*4] + mulss xmm2, xmm6 + movss xmm3, [edi+14*4] + mulss xmm3, xmm7 + addss xmm1, xmm2 + addss xmm1, xmm3 + STORE1( 16, xmm1, xmm4 ) + mulss xmm5, [edi+15*4] + mulss xmm6, [edi+16*4] + mulss xmm7, [edi+17*4] + addss xmm5, xmm6 + addss xmm5, xmm7 + STORE1( 20, xmm5, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + return; + } + } + break; + } + case 4: { + switch( numRows ) { + case 4: { // 4x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi ] + movlps xmm0, qword ptr [edi ] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + STORE4( 0, xmm0, xmm2 ) + } + return; + } + case 6: { // 6x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi+ 0] + movlps xmm0, qword ptr [edi+ 0] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + movlps xmm1, qword ptr [edi+64] + movhps xmm1, qword ptr [edi+80] + STORE4( 0, xmm0, xmm4 ) + mulps xmm1, xmm6 + movlps xmm2, qword ptr [edi+72] + movhps xmm2, qword ptr [edi+88] + mulps xmm2, xmm7 + addps xmm1, xmm2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm3, xmm1 + addps xmm1, xmm3 + STORE2LO( 16, xmm1, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3]; + mPtr += 4; + } + return; + } + } + break; + } + case 5: { + switch( numRows ) { + case 5: { // 5x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+5*4] // xmm0 = 5, X, X, X + movhps xmm0, [edi+0*4] // xmm0 = 5, X, 0, 1 + movss xmm5, [edi+15*4] // xmm4 = 15, X, X, X + movhps xmm5, [edi+10*4] // xmm5 = 15, X, 10, 11 + movaps xmm1, xmm0 // xmm1 = 5, X, 0, 1 + shufps xmm0, xmm5, R_SHUFFLE_PS( 2, 0, 2, 0 ) // xmm0 = 0, 5, 10, 15 + movlps xmm1, [edi+6*4] // xmm1 = 6, 7, 0, 1 + movlps xmm5, [edi+16*4] // xmm5 = 16, 17, 10, 11 + movaps xmm2, xmm1 // xmm2 = 6, 7, 0, 1 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm1 = 1, 6, 11, 16 + movhps xmm2, [edi+2*4] // xmm2 = 6, 7, 2, 3 + movhps xmm5, [edi+12*4] // xmm5 = 16, 17, 12, 13 + movaps xmm3, xmm2 // xmm3 = 6, 7, 2, 3 + shufps xmm2, xmm5, R_SHUFFLE_PS( 2, 1, 2, 1 ) // xmm2 = 2, 7, 12, 17 + movlps xmm3, [edi+8*4] // xmm3 = 8, 9, 2, 3 + movlps xmm5, [edi+18*4] // xmm5 = 18, 19, 12, 13 + movss xmm4, [edi+4*4] // xmm4 = 4, X, X, X + movlhps xmm4, xmm3 // xmm4 = 4, X, 8, 9 + shufps xmm3, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm3 = 3, 8, 13, 18 + movhps xmm5, [edi+14*4] // xmm6 = 18, 19, 14, 15 + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 3, 2, 1 ) // xmm4 = 4, 9, 14, 19 + movss xmm7, [esi+0*4] + shufps xmm7, xmm7, 0 + mulps xmm0, xmm7 + movss xmm5, [esi+1*4] + shufps xmm5, xmm5, 0 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, 0 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movss xmm1, [esi+3*4] + shufps xmm1, xmm1, 0 + mulps xmm3, xmm1 + addps xmm0, xmm3 + movss xmm2, [esi+4*4] + shufps xmm2, xmm2, 0 + mulps xmm4, xmm2 + addps xmm0, xmm4 + mulss xmm7, [edi+20*4] + mulss xmm5, [edi+21*4] + addps xmm7, xmm5 + mulss xmm6, [edi+22*4] + addps xmm7, xmm6 + mulss xmm1, [edi+23*4] + addps xmm7, xmm1 + mulss xmm2, [edi+24*4] + addps xmm7, xmm2 + STORE4( 0, xmm0, xmm3 ) + STORE1( 16, xmm7, xmm4 ) + } + return; + } + case 6: { // 6x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [esi] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm0, [edi] + movhps xmm3, [edi+8] + movaps xmm1, [edi+16] + movlps xmm2, [edi+32] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm0 = 0, 1, 5, 6 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 4, 7, 8, 9 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 2, 3, 7, 8 + mulps xmm0, xmm6 + mulps xmm3, xmm7 + movlps xmm2, [edi+40] + addps xmm0, xmm3 // xmm0 + xmm1 + movhps xmm5, [edi+40+8] + movlps xmm3, [edi+40+16] + movhps xmm3, [edi+40+24] + movlps xmm4, [edi+40+32] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm2 = 10, 11, 15, 16 + shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm3 = 14, 17, 18, 19 + shufps xmm5, xmm3, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm5 = 12, 13, 17, 18 + mulps xmm2, xmm6 + mulps xmm5, xmm7 + addps xmm2, xmm5 // xmm2 + xmm3 + movss xmm5, [esi+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm4, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 0, 3, 0, 3 ) + addps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + STORE4( 0, xmm0, xmm2 ) + movlps xmm4, [edi+80] + movhps xmm3, [edi+80+8] + movaps xmm1, [edi+80+16] + movlps xmm2, [edi+80+32] + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm4 = 20, 21, 25, 26 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 24, 27, 28, 29 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 22, 23, 27, 28 + mulps xmm4, xmm6 + mulps xmm3, xmm7 + mulps xmm1, xmm5 + addps xmm4, xmm3 // xmm4 + xmm1 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 3, 0, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 3, 0, 0 ) + addps xmm4, xmm1 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm4, xmm1 + STORE2LO( 16, xmm4, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + return; + } + } + break; + } + case 6: { + switch( numRows ) { + case 1: { // 1x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + movss xmm1, [esi+4] + mulss xmm1, [edi+4] + movss xmm2, [esi+8] + addss xmm0, xmm1 + mulss xmm2, [edi+8] + movss xmm3, [esi+12] + addss xmm0, xmm2 + mulss xmm3, [edi+12] + movss xmm4, [esi+16] + addss xmm0, xmm3 + mulss xmm4, [edi+16] + movss xmm5, [esi+20] + addss xmm0, xmm4 + mulss xmm5, [edi+20] + movss xmm6, [esi+24] + addss xmm0, xmm5 + mulss xmm6, [edi+24] + addss xmm0, xmm6 + STORE1( 0, xmm0, xmm7 ) + } + return; + } + case 2: { // 2x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + } + return; + } + case 3: { // 3x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + // row 2 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + STORE1( 8, xmm0, xmm3 ) + } + return; + } + case 4: { // 4x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm4 ) + } + return; + } + case 5: { // 5x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm3 ) + // row 5 + movaps xmm0, [edi+96] + movaps xmm1, [edi+96+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x01 + addss xmm0, xmm1 + STORE1( 16, xmm0, xmm3 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + STORE4( 0, xmm0, xmm3 ) + STORE2LO( 16, xmm2, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + return; + } + } + break; + } + default: { + int numColumns = mat.GetNumColumns(); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] STOREC sum; + mPtr += numColumns; + } + break; + } + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_MultiplyAddVecX + + optimizes the following matrix multiplications: + + NxN * Nx1 + Nx6 * 6x1 + 6xN * Nx1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm addss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC += + + int numRows; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: { + switch( numRows ) { + case 1: { // 1x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + STORE1( 0, xmm0, xmm1 ) + } + return; + } + case 6: { // 6x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0]; + mPtr++; + } + return; + } + } + break; + } + case 2: { + switch( numRows ) { + case 2: { // 2x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm1, [esi+4] + movss xmm2, [edi] + mulss xmm2, xmm0 + movss xmm3, [edi+4] + mulss xmm3, xmm1 + addss xmm2, xmm3 + STORE1( 0, xmm2, xmm4 ) + mulss xmm0, [edi+8] + mulss xmm1, [edi+8+4] + addss xmm0, xmm1 + STORE1( 4, xmm0, xmm4 ) + } + return; + } + case 6: { // 6x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, [esi] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movaps xmm0, [edi] + mulps xmm0, xmm7 + movaps xmm1, [edi+16] + mulps xmm1, xmm7 + movaps xmm2, xmm0 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm2, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm3, [edi+32] + addps xmm0, xmm2 + mulps xmm3, xmm7 + STORE4( 0, xmm0, xmm4 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm1, xmm3 + addps xmm3, xmm1 + STORE2LO( 16, xmm3, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + return; + } + } + break; + } + case 3: { + switch( numRows ) { + case 3: { // 3x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm4, [edi] + mulss xmm4, xmm0 + movss xmm1, [esi+4] + movss xmm5, [edi+4] + mulss xmm5, xmm1 + addss xmm4, xmm5 + movss xmm2, [esi+8] + movss xmm6, [edi+8] + mulss xmm6, xmm2 + addss xmm4, xmm6 + movss xmm3, [edi+12] + mulss xmm3, xmm0 + STORE1( 0, xmm4, xmm7 ); + movss xmm5, [edi+12+4] + mulss xmm5, xmm1 + addss xmm3, xmm5 + movss xmm6, [edi+12+8] + mulss xmm6, xmm2 + addss xmm3, xmm6 + mulss xmm0, [edi+24] + mulss xmm1, [edi+24+4] + STORE1( 4, xmm3, xmm7 ); + addss xmm0, xmm1 + mulss xmm2, [edi+24+8] + addss xmm0, xmm2 + STORE1( 8, xmm0, xmm7 ); + } + return; + } + case 6: { // 6x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm5, [esi] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm0, [edi] // xmm0 = 0, 1, 2, 3 + movlps xmm1, [edi+4*4] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm1 = 4, 5, 1, 2 + movlps xmm2, [edi+6*4] + movhps xmm2, [edi+8*4] // xmm2 = 6, 7, 8, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 3, 0, 3 ) // xmm0 = 0, 3, 6, 9 + mulps xmm0, xmm5 + movlps xmm3, [edi+10*4] + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 0, 1 ) // xmm2 = 7, 8, 10, 11 + movaps xmm3, xmm1 + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 0, 2 ) // xmm1 = 1, 4, 7, 10 + mulps xmm1, xmm6 + shufps xmm3, xmm2, R_SHUFFLE_PS( 3, 1, 1, 3 ) // xmm3 = 2, 5, 8, 11 + mulps xmm3, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm3 + STORE4( 0, xmm0, xmm4 ) + movss xmm1, [edi+12*4] + mulss xmm1, xmm5 + movss xmm2, [edi+13*4] + mulss xmm2, xmm6 + movss xmm3, [edi+14*4] + mulss xmm3, xmm7 + addss xmm1, xmm2 + addss xmm1, xmm3 + STORE1( 16, xmm1, xmm4 ) + mulss xmm5, [edi+15*4] + mulss xmm6, [edi+16*4] + mulss xmm7, [edi+17*4] + addss xmm5, xmm6 + addss xmm5, xmm7 + STORE1( 20, xmm5, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + return; + } + } + break; + } + case 4: { + switch( numRows ) { + case 4: { // 4x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi ] + movlps xmm0, qword ptr [edi ] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + STORE4( 0, xmm0, xmm2 ) + } + return; + } + case 6: { // 6x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi+ 0] + movlps xmm0, qword ptr [edi+ 0] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + movlps xmm1, qword ptr [edi+64] + movhps xmm1, qword ptr [edi+80] + STORE4( 0, xmm0, xmm4 ) + mulps xmm1, xmm6 + movlps xmm2, qword ptr [edi+72] + movhps xmm2, qword ptr [edi+88] + mulps xmm2, xmm7 + addps xmm1, xmm2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm3, xmm1 + addps xmm1, xmm3 + STORE2LO( 16, xmm1, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3]; + mPtr += 4; + } + return; + } + } + break; + } + case 5: { + switch( numRows ) { + case 5: { // 5x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+5*4] // xmm0 = 5, X, X, X + movhps xmm0, [edi+0*4] // xmm0 = 5, X, 0, 1 + movss xmm5, [edi+15*4] // xmm4 = 15, X, X, X + movhps xmm5, [edi+10*4] // xmm5 = 15, X, 10, 11 + movaps xmm1, xmm0 // xmm1 = 5, X, 0, 1 + shufps xmm0, xmm5, R_SHUFFLE_PS( 2, 0, 2, 0 ) // xmm0 = 0, 5, 10, 15 + movlps xmm1, [edi+6*4] // xmm1 = 6, 7, 0, 1 + movlps xmm5, [edi+16*4] // xmm5 = 16, 17, 10, 11 + movaps xmm2, xmm1 // xmm2 = 6, 7, 0, 1 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm1 = 1, 6, 11, 16 + movhps xmm2, [edi+2*4] // xmm2 = 6, 7, 2, 3 + movhps xmm5, [edi+12*4] // xmm5 = 16, 17, 12, 13 + movaps xmm3, xmm2 // xmm3 = 6, 7, 2, 3 + shufps xmm2, xmm5, R_SHUFFLE_PS( 2, 1, 2, 1 ) // xmm2 = 2, 7, 12, 17 + movlps xmm3, [edi+8*4] // xmm3 = 8, 9, 2, 3 + movlps xmm5, [edi+18*4] // xmm5 = 18, 19, 12, 13 + movss xmm4, [edi+4*4] // xmm4 = 4, X, X, X + movlhps xmm4, xmm3 // xmm4 = 4, X, 8, 9 + shufps xmm3, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm3 = 3, 8, 13, 18 + movhps xmm5, [edi+14*4] // xmm6 = 18, 19, 14, 15 + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 3, 2, 1 ) // xmm4 = 4, 9, 14, 19 + movss xmm7, [esi+0*4] + shufps xmm7, xmm7, 0 + mulps xmm0, xmm7 + movss xmm5, [esi+1*4] + shufps xmm5, xmm5, 0 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, 0 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movss xmm1, [esi+3*4] + shufps xmm1, xmm1, 0 + mulps xmm3, xmm1 + addps xmm0, xmm3 + movss xmm2, [esi+4*4] + shufps xmm2, xmm2, 0 + mulps xmm4, xmm2 + addps xmm0, xmm4 + mulss xmm7, [edi+20*4] + mulss xmm5, [edi+21*4] + addps xmm7, xmm5 + mulss xmm6, [edi+22*4] + addps xmm7, xmm6 + mulss xmm1, [edi+23*4] + addps xmm7, xmm1 + mulss xmm2, [edi+24*4] + addps xmm7, xmm2 + STORE4( 0, xmm0, xmm3 ) + STORE1( 16, xmm7, xmm4 ) + } + return; + } + case 6: { // 6x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [esi] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm0, [edi] + movhps xmm3, [edi+8] + movaps xmm1, [edi+16] + movlps xmm2, [edi+32] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm0 = 0, 1, 5, 6 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 4, 7, 8, 9 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 2, 3, 7, 8 + mulps xmm0, xmm6 + mulps xmm3, xmm7 + movlps xmm2, [edi+40] + addps xmm0, xmm3 // xmm0 + xmm1 + movhps xmm5, [edi+40+8] + movlps xmm3, [edi+40+16] + movhps xmm3, [edi+40+24] + movlps xmm4, [edi+40+32] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm2 = 10, 11, 15, 16 + shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm3 = 14, 17, 18, 19 + shufps xmm5, xmm3, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm5 = 12, 13, 17, 18 + mulps xmm2, xmm6 + mulps xmm5, xmm7 + addps xmm2, xmm5 // xmm2 + xmm3 + movss xmm5, [esi+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm4, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 0, 3, 0, 3 ) + addps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + STORE4( 0, xmm0, xmm2 ) + movlps xmm4, [edi+80] + movhps xmm3, [edi+80+8] + movaps xmm1, [edi+80+16] + movlps xmm2, [edi+80+32] + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm4 = 20, 21, 25, 26 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 24, 27, 28, 29 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 22, 23, 27, 28 + mulps xmm4, xmm6 + mulps xmm3, xmm7 + mulps xmm1, xmm5 + addps xmm4, xmm3 // xmm4 + xmm1 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 3, 0, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 3, 0, 0 ) + addps xmm4, xmm1 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm4, xmm1 + STORE2LO( 16, xmm4, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + return; + } + } + break; + } + case 6: { + switch( numRows ) { + case 1: { // 1x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + movss xmm1, [esi+4] + mulss xmm1, [edi+4] + movss xmm2, [esi+8] + addss xmm0, xmm1 + mulss xmm2, [edi+8] + movss xmm3, [esi+12] + addss xmm0, xmm2 + mulss xmm3, [edi+12] + movss xmm4, [esi+16] + addss xmm0, xmm3 + mulss xmm4, [edi+16] + movss xmm5, [esi+20] + addss xmm0, xmm4 + mulss xmm5, [edi+20] + movss xmm6, [esi+24] + addss xmm0, xmm5 + mulss xmm6, [edi+24] + addss xmm0, xmm6 + STORE1( 0, xmm0, xmm7 ) + } + return; + } + case 2: { // 2x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + } + return; + } + case 3: { // 3x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + // row 2 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + STORE1( 8, xmm0, xmm3 ) + } + return; + } + case 4: { // 4x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm4 ) + } + return; + } + case 5: { // 5x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm3 ) + // row 5 + movaps xmm0, [edi+96] + movaps xmm1, [edi+96+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x01 + addss xmm0, xmm1 + STORE1( 16, xmm0, xmm3 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + STORE4( 0, xmm0, xmm3 ) + STORE2LO( 16, xmm2, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + return; + } + } + break; + } + default: { + int numColumns = mat.GetNumColumns(); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] STOREC sum; + mPtr += numColumns; + } + break; + } + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_MultiplySubVecX + + optimizes the following matrix multiplications: + + NxN * Nx1 + Nx6 * 6x1 + 6xN * Nx1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm subss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC -= + + int numRows; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: { + switch( numRows ) { + case 1: { // 1x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + STORE1( 0, xmm0, xmm1 ) + } + return; + } + case 6: { // 6x1 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0]; + mPtr++; + } + return; + } + } + break; + } + case 2: { + switch( numRows ) { + case 2: { // 2x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm1, [esi+4] + movss xmm2, [edi] + mulss xmm2, xmm0 + movss xmm3, [edi+4] + mulss xmm3, xmm1 + addss xmm2, xmm3 + STORE1( 0, xmm2, xmm4 ) + mulss xmm0, [edi+8] + mulss xmm1, [edi+8+4] + addss xmm0, xmm1 + STORE1( 4, xmm0, xmm4 ) + } + return; + } + case 6: { // 6x2 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, [esi] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movaps xmm0, [edi] + mulps xmm0, xmm7 + movaps xmm1, [edi+16] + mulps xmm1, xmm7 + movaps xmm2, xmm0 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm2, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + movaps xmm3, [edi+32] + addps xmm0, xmm2 + mulps xmm3, xmm7 + STORE4( 0, xmm0, xmm4 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm1, xmm3 + addps xmm3, xmm1 + STORE2LO( 16, xmm3, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + return; + } + } + break; + } + case 3: { + switch( numRows ) { + case 3: { // 3x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + movss xmm4, [edi] + mulss xmm4, xmm0 + movss xmm1, [esi+4] + movss xmm5, [edi+4] + mulss xmm5, xmm1 + addss xmm4, xmm5 + movss xmm2, [esi+8] + movss xmm6, [edi+8] + mulss xmm6, xmm2 + addss xmm4, xmm6 + movss xmm3, [edi+12] + mulss xmm3, xmm0 + STORE1( 0, xmm4, xmm7 ); + movss xmm5, [edi+12+4] + mulss xmm5, xmm1 + addss xmm3, xmm5 + movss xmm6, [edi+12+8] + mulss xmm6, xmm2 + addss xmm3, xmm6 + mulss xmm0, [edi+24] + mulss xmm1, [edi+24+4] + STORE1( 4, xmm3, xmm7 ); + addss xmm0, xmm1 + mulss xmm2, [edi+24+8] + addss xmm0, xmm2 + STORE1( 8, xmm0, xmm7 ); + } + return; + } + case 6: { // 6x3 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm5, [esi] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm0, [edi] // xmm0 = 0, 1, 2, 3 + movlps xmm1, [edi+4*4] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm1 = 4, 5, 1, 2 + movlps xmm2, [edi+6*4] + movhps xmm2, [edi+8*4] // xmm2 = 6, 7, 8, 9 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 3, 0, 3 ) // xmm0 = 0, 3, 6, 9 + mulps xmm0, xmm5 + movlps xmm3, [edi+10*4] + shufps xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 0, 1 ) // xmm2 = 7, 8, 10, 11 + movaps xmm3, xmm1 + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 0, 2 ) // xmm1 = 1, 4, 7, 10 + mulps xmm1, xmm6 + shufps xmm3, xmm2, R_SHUFFLE_PS( 3, 1, 1, 3 ) // xmm3 = 2, 5, 8, 11 + mulps xmm3, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm3 + STORE4( 0, xmm0, xmm4 ) + movss xmm1, [edi+12*4] + mulss xmm1, xmm5 + movss xmm2, [edi+13*4] + mulss xmm2, xmm6 + movss xmm3, [edi+14*4] + mulss xmm3, xmm7 + addss xmm1, xmm2 + addss xmm1, xmm3 + STORE1( 16, xmm1, xmm4 ) + mulss xmm5, [edi+15*4] + mulss xmm6, [edi+16*4] + mulss xmm7, [edi+17*4] + addss xmm5, xmm6 + addss xmm5, xmm7 + STORE1( 20, xmm5, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + return; + } + } + break; + } + case 4: { + switch( numRows ) { + case 4: { // 4x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi ] + movlps xmm0, qword ptr [edi ] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + STORE4( 0, xmm0, xmm2 ) + } + return; + } + case 6: { // 6x4 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, qword ptr [esi+ 0] + movlps xmm0, qword ptr [edi+ 0] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm0, qword ptr [edi+16] + mulps xmm0, xmm6 + movlps xmm7, qword ptr [esi+ 8] + movlps xmm2, qword ptr [edi+ 8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movhps xmm2, qword ptr [edi+24] + mulps xmm2, xmm7 + movlps xmm1, qword ptr [edi+32] + movhps xmm1, qword ptr [edi+48] + mulps xmm1, xmm6 + movlps xmm3, qword ptr [edi+40] + addps xmm0, xmm2 + movhps xmm3, qword ptr [edi+56] + mulps xmm3, xmm7 + movaps xmm4, xmm0 + addps xmm1, xmm3 + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm4 + movlps xmm1, qword ptr [edi+64] + movhps xmm1, qword ptr [edi+80] + STORE4( 0, xmm0, xmm4 ) + mulps xmm1, xmm6 + movlps xmm2, qword ptr [edi+72] + movhps xmm2, qword ptr [edi+88] + mulps xmm2, xmm7 + addps xmm1, xmm2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm3, xmm1 + addps xmm1, xmm3 + STORE2LO( 16, xmm1, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3]; + mPtr += 4; + } + return; + } + } + break; + } + case 5: { + switch( numRows ) { + case 5: { // 5x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+5*4] // xmm0 = 5, X, X, X + movhps xmm0, [edi+0*4] // xmm0 = 5, X, 0, 1 + movss xmm5, [edi+15*4] // xmm4 = 15, X, X, X + movhps xmm5, [edi+10*4] // xmm5 = 15, X, 10, 11 + movaps xmm1, xmm0 // xmm1 = 5, X, 0, 1 + shufps xmm0, xmm5, R_SHUFFLE_PS( 2, 0, 2, 0 ) // xmm0 = 0, 5, 10, 15 + movlps xmm1, [edi+6*4] // xmm1 = 6, 7, 0, 1 + movlps xmm5, [edi+16*4] // xmm5 = 16, 17, 10, 11 + movaps xmm2, xmm1 // xmm2 = 6, 7, 0, 1 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm1 = 1, 6, 11, 16 + movhps xmm2, [edi+2*4] // xmm2 = 6, 7, 2, 3 + movhps xmm5, [edi+12*4] // xmm5 = 16, 17, 12, 13 + movaps xmm3, xmm2 // xmm3 = 6, 7, 2, 3 + shufps xmm2, xmm5, R_SHUFFLE_PS( 2, 1, 2, 1 ) // xmm2 = 2, 7, 12, 17 + movlps xmm3, [edi+8*4] // xmm3 = 8, 9, 2, 3 + movlps xmm5, [edi+18*4] // xmm5 = 18, 19, 12, 13 + movss xmm4, [edi+4*4] // xmm4 = 4, X, X, X + movlhps xmm4, xmm3 // xmm4 = 4, X, 8, 9 + shufps xmm3, xmm5, R_SHUFFLE_PS( 3, 0, 3, 0 ) // xmm3 = 3, 8, 13, 18 + movhps xmm5, [edi+14*4] // xmm6 = 18, 19, 14, 15 + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 3, 2, 1 ) // xmm4 = 4, 9, 14, 19 + movss xmm7, [esi+0*4] + shufps xmm7, xmm7, 0 + mulps xmm0, xmm7 + movss xmm5, [esi+1*4] + shufps xmm5, xmm5, 0 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, 0 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movss xmm1, [esi+3*4] + shufps xmm1, xmm1, 0 + mulps xmm3, xmm1 + addps xmm0, xmm3 + movss xmm2, [esi+4*4] + shufps xmm2, xmm2, 0 + mulps xmm4, xmm2 + addps xmm0, xmm4 + mulss xmm7, [edi+20*4] + mulss xmm5, [edi+21*4] + addps xmm7, xmm5 + mulss xmm6, [edi+22*4] + addps xmm7, xmm6 + mulss xmm1, [edi+23*4] + addps xmm7, xmm1 + mulss xmm2, [edi+24*4] + addps xmm7, xmm2 + STORE4( 0, xmm0, xmm3 ) + STORE1( 16, xmm7, xmm4 ) + } + return; + } + case 6: { // 6x5 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [esi] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm7, [esi+8] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movlps xmm0, [edi] + movhps xmm3, [edi+8] + movaps xmm1, [edi+16] + movlps xmm2, [edi+32] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm0 = 0, 1, 5, 6 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 4, 7, 8, 9 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 2, 3, 7, 8 + mulps xmm0, xmm6 + mulps xmm3, xmm7 + movlps xmm2, [edi+40] + addps xmm0, xmm3 // xmm0 + xmm1 + movhps xmm5, [edi+40+8] + movlps xmm3, [edi+40+16] + movhps xmm3, [edi+40+24] + movlps xmm4, [edi+40+32] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm2 = 10, 11, 15, 16 + shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm3 = 14, 17, 18, 19 + shufps xmm5, xmm3, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm5 = 12, 13, 17, 18 + mulps xmm2, xmm6 + mulps xmm5, xmm7 + addps xmm2, xmm5 // xmm2 + xmm3 + movss xmm5, [esi+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm4, xmm2, R_SHUFFLE_PS( 1, 3, 1, 3 ) + shufps xmm1, xmm3, R_SHUFFLE_PS( 0, 3, 0, 3 ) + addps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + STORE4( 0, xmm0, xmm2 ) + movlps xmm4, [edi+80] + movhps xmm3, [edi+80+8] + movaps xmm1, [edi+80+16] + movlps xmm2, [edi+80+32] + shufps xmm4, xmm1, R_SHUFFLE_PS( 0, 1, 1, 2 ) // xmm4 = 20, 21, 25, 26 + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 3, 0, 1 ) // xmm1 = 24, 27, 28, 29 + shufps xmm3, xmm1, R_SHUFFLE_PS( 2, 3, 1, 2 ) // xmm3 = 22, 23, 27, 28 + mulps xmm4, xmm6 + mulps xmm3, xmm7 + mulps xmm1, xmm5 + addps xmm4, xmm3 // xmm4 + xmm1 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 3, 0, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 3, 0, 0 ) + addps xmm4, xmm1 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm4, xmm1 + STORE2LO( 16, xmm4, xmm2 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + return; + } + } + break; + } + case 6: { + switch( numRows ) { + case 1: { // 1x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + mulss xmm0, [edi] + movss xmm1, [esi+4] + mulss xmm1, [edi+4] + movss xmm2, [esi+8] + addss xmm0, xmm1 + mulss xmm2, [edi+8] + movss xmm3, [esi+12] + addss xmm0, xmm2 + mulss xmm3, [edi+12] + movss xmm4, [esi+16] + addss xmm0, xmm3 + mulss xmm4, [edi+16] + movss xmm5, [esi+20] + addss xmm0, xmm4 + mulss xmm5, [edi+20] + movss xmm6, [esi+24] + addss xmm0, xmm5 + mulss xmm6, [edi+24] + addss xmm0, xmm6 + STORE1( 0, xmm0, xmm7 ) + } + return; + } + case 2: { // 2x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + } + return; + } + case 3: { // 3x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 1, 3 ) + movhlps xmm0, xmm1 + addps xmm0, xmm1 + STORE2LO( 0, xmm0, xmm3 ) + // row 2 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + STORE1( 8, xmm0, xmm3 ) + } + return; + } + case 4: { // 4x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm4 ) + } + return; + } + case 5: { // 5x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + // load idVecX + movlps xmm4, [esi] + movhps xmm4, [esi+8] + movlps xmm5, [esi+16] + movlhps xmm5, xmm4 + movhlps xmm6, xmm4 + movlhps xmm6, xmm5 + // row 0 and 1 + movaps xmm0, [edi] + movaps xmm1, [edi+16] + movaps xmm2, [edi+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm7, xmm0 + movlhps xmm7, xmm2 + addps xmm7, xmm1 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm7, xmm0 + // row 2 and 3 + movaps xmm0, [edi+48] + movaps xmm1, [edi+48+16] + movaps xmm2, [edi+48+32] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + movhlps xmm3, xmm0 + movlhps xmm3, xmm2 + addps xmm1, xmm3 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 2, 3 ) + addps xmm1, xmm0 + // last 4 additions for the first 4 rows and store result + movaps xmm0, xmm7 + shufps xmm7, xmm1, R_SHUFFLE_PS( 0, 2, 0, 2 ) + shufps xmm0, xmm1, R_SHUFFLE_PS( 1, 3, 1, 3 ) + addps xmm0, xmm7 + STORE4( 0, xmm0, xmm3 ) + // row 5 + movaps xmm0, [edi+96] + movaps xmm1, [edi+96+16] + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, 0x01 + addss xmm0, xmm1 + STORE1( 16, xmm0, xmm3 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + STORE4( 0, xmm0, xmm3 ) + STORE2LO( 16, xmm2, xmm4 ) + } + return; + } + default: { + for ( int i = 0; i < numRows; i++ ) { + dstPtr[i] STOREC mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + return; + } + } + break; + } + default: { + int numColumns = mat.GetNumColumns(); + for ( int i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] STOREC sum; + mPtr += numColumns; + } + break; + } + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_TransposeMultiplyVecX + + optimizes the following matrix multiplications: + + Nx6 * Nx1 + 6xN * 6x1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss [eax+offset], reg1 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps [eax+offset], reg1 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps [eax+offset], reg1 \ + __asm movhps [eax+offset+8], reg1 +#define STOREC = + + int numColumns; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + switch( numColumns ) { + case 6: { // 1x6 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm3 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0]; + mPtr++; + } + return; + } + } + break; + case 2: + switch( numColumns ) { + case 6: { // 2x6 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, [edi] + mulps xmm2, xmm0 + movlps xmm3, [edi+24] + movhps xmm3, [edi+32] + mulps xmm3, xmm1 + addps xmm2, xmm3 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+16] + movhps xmm4, [edi+40] + mulps xmm4, xmm0 + movhlps xmm3, xmm4 + addps xmm3, xmm4 + STORE4( 0, xmm2, xmm5 ) + STORE2LO( 16, xmm3, xmm6 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + return; + } + } + break; + case 3: + switch( numColumns ) { + case 6: { // 3x6 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movss xmm1, [esi+2*4] + movlps xmm3, [edi+(0*6+0)*4] + movhps xmm3, [edi+(0*6+2)*4] + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(2*6+4)*4] + mulps xmm5, xmm1 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + return; + } + } + break; + case 4: + switch( numColumns ) { + case 6: { // 4x6 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + return; + } + } + break; + case 5: + switch( numColumns ) { + case 6: { // 5x6 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movss xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm2 + mulps xmm4, [edi+(4*6+0)*4] + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + return; + } + } + break; + case 6: + switch( numColumns ) { + case 1: { // 6x1 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movhps xmm0, [esi+8] + movlps xmm1, [esi+16] + mulps xmm0, [edi] + mulps xmm1, [edi+16] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 3, 2 ) + addps xmm0, xmm1 + movhlps xmm2, xmm0 + addss xmm2, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm2, xmm0 + STORE1( 0, xmm2, xmm3 ) + } + return; + } + case 2: { // 6x2 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, [edi+0*4] + mulps xmm6, xmm0 + movlps xmm1, [esi+2*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+4*4] + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm2, [esi+4*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+8*4] + mulps xmm7, xmm2 + addps xmm6, xmm7 + movhlps xmm3, xmm6 + addps xmm3, xmm6 + STORE2LO( 0, xmm3, xmm7 ) + } + return; + } + case 3: { // 6x3 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+(0*3+2)*4] + movhps xmm0, [edi+(0*3+0)*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm6, [esi+0*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm1, [edi+(1*3+0)*4] + movhps xmm1, [edi+(1*3+1)*4] + movss xmm7, [esi+1*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm2, [edi+(2*3+2)*4] + movhps xmm2, [edi+(2*3+0)*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+2*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movss xmm3, [edi+(3*3+0)*4] + movhps xmm3, [edi+(3*3+1)*4] + movss xmm7, [esi+3*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movss xmm4, [edi+(4*3+2)*4] + movhps xmm4, [edi+(4*3+0)*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+4*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movss xmm5, [edi+(5*3+0)*4] + movhps xmm5, [edi+(5*3+1)*4] + movss xmm7, [esi+5*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE1( 0, xmm6, xmm7 ) + STORE2HI( 4, xmm6, xmm7 ) + } + return; + } + case 4: { // 6x4 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm3, [edi+(0*4+0)*4] + movhps xmm3, [edi+(0*4+2)*4] + movss xmm4, [esi+0*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*4+0)*4] + movhps xmm5, [edi+(1*4+2)*4] + movss xmm6, [esi+1*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*4+0)*4] + movhps xmm4, [edi+(2*4+2)*4] + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*4+0)*4] + movhps xmm5, [edi+(3*4+2)*4] + movss xmm6, [esi+3*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(4*4+0)*4] + movhps xmm4, [edi+(4*4+2)*4] + movss xmm6, [esi+4*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(5*4+0)*4] + movhps xmm5, [edi+(5*4+2)*4] + movss xmm6, [esi+5*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + } + return; + } + case 5: { // 6x5 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [edi+(0*5+0)*4] + movhps xmm6, [edi+(0*5+2)*4] + movss xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movlps xmm7, [edi+(1*5+0)*4] + movhps xmm7, [edi+(1*5+2)*4] + movss xmm1, [esi+1*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm7, [edi+(2*5+0)*4] + movhps xmm7, [edi+(2*5+2)*4] + movss xmm2, [esi+2*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movlps xmm7, [edi+(3*5+0)*4] + movhps xmm7, [edi+(3*5+2)*4] + movss xmm3, [esi+3*4] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movlps xmm7, [edi+(4*5+0)*4] + movhps xmm7, [edi+(4*5+2)*4] + movss xmm4, [esi+4*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movlps xmm7, [edi+(5*5+0)*4] + movhps xmm7, [edi+(5*5+2)*4] + movss xmm5, [esi+5*4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE4( 0, xmm6, xmm7 ) + movss xmm6, [edi+(0*5+4)*4] + mulss xmm6, xmm0 + movss xmm7, [edi+(1*5+4)*4] + mulss xmm7, xmm1 + addss xmm6, xmm7 + movss xmm7, [edi+(2*5+4)*4] + mulss xmm7, xmm2 + addss xmm6, xmm7 + movss xmm7, [edi+(3*5+4)*4] + mulss xmm7, xmm3 + addss xmm6, xmm7 + movss xmm7, [edi+(4*5+4)*4] + mulss xmm7, xmm4 + addss xmm6, xmm7 + movss xmm7, [edi+(5*5+4)*4] + mulss xmm7, xmm5 + addss xmm6, xmm7 + STORE1( 16, xmm6, xmm7 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movlps xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(4*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(5*6+0)*4] + movhps xmm5, [edi+(5*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + movhps xmm5, [edi+(5*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + return; + } + } + break; + default: + int numRows = mat.GetNumRows(); + for ( int i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] STOREC sum; + } + break; + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_TransposeMultiplyAddVecX + + optimizes the following matrix multiplications: + + Nx6 * Nx1 + 6xN * 6x1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm addss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm addps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm addps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC += + + int numColumns; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + switch( numColumns ) { + case 6: { // 1x6 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm3 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0]; + mPtr++; + } + return; + } + } + break; + case 2: + switch( numColumns ) { + case 6: { // 2x6 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, [edi] + mulps xmm2, xmm0 + movlps xmm3, [edi+24] + movhps xmm3, [edi+32] + mulps xmm3, xmm1 + addps xmm2, xmm3 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+16] + movhps xmm4, [edi+40] + mulps xmm4, xmm0 + movhlps xmm3, xmm4 + addps xmm3, xmm4 + STORE4( 0, xmm2, xmm5 ) + STORE2LO( 16, xmm3, xmm6 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + return; + } + } + break; + case 3: + switch( numColumns ) { + case 6: { // 3x6 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movss xmm1, [esi+2*4] + movlps xmm3, [edi+(0*6+0)*4] + movhps xmm3, [edi+(0*6+2)*4] + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(2*6+4)*4] + mulps xmm5, xmm1 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + return; + } + } + break; + case 4: + switch( numColumns ) { + case 6: { // 4x6 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + return; + } + } + break; + case 5: + switch( numColumns ) { + case 6: { // 5x6 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movss xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm2 + mulps xmm4, [edi+(4*6+0)*4] + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + return; + } + } + break; + case 6: + switch( numColumns ) { + case 1: { // 6x1 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movhps xmm0, [esi+8] + movlps xmm1, [esi+16] + mulps xmm0, [edi] + mulps xmm1, [edi+16] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 3, 2 ) + addps xmm0, xmm1 + movhlps xmm2, xmm0 + addss xmm2, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm2, xmm0 + STORE1( 0, xmm2, xmm3 ) + } + return; + } + case 2: { // 6x2 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, [edi+0*4] + mulps xmm6, xmm0 + movlps xmm1, [esi+2*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+4*4] + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm2, [esi+4*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+8*4] + mulps xmm7, xmm2 + addps xmm6, xmm7 + movhlps xmm3, xmm6 + addps xmm3, xmm6 + STORE2LO( 0, xmm3, xmm7 ) + } + return; + } + case 3: { // 6x3 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+(0*3+2)*4] + movhps xmm0, [edi+(0*3+0)*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm6, [esi+0*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm1, [edi+(1*3+0)*4] + movhps xmm1, [edi+(1*3+1)*4] + movss xmm7, [esi+1*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm2, [edi+(2*3+2)*4] + movhps xmm2, [edi+(2*3+0)*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+2*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movss xmm3, [edi+(3*3+0)*4] + movhps xmm3, [edi+(3*3+1)*4] + movss xmm7, [esi+3*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movss xmm4, [edi+(4*3+2)*4] + movhps xmm4, [edi+(4*3+0)*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+4*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movss xmm5, [edi+(5*3+0)*4] + movhps xmm5, [edi+(5*3+1)*4] + movss xmm7, [esi+5*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE1( 0, xmm6, xmm7 ) + STORE2HI( 4, xmm6, xmm7 ) + } + return; + } + case 4: { // 6x4 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm3, [edi+(0*4+0)*4] + movhps xmm3, [edi+(0*4+2)*4] + movss xmm4, [esi+0*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*4+0)*4] + movhps xmm5, [edi+(1*4+2)*4] + movss xmm6, [esi+1*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*4+0)*4] + movhps xmm4, [edi+(2*4+2)*4] + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*4+0)*4] + movhps xmm5, [edi+(3*4+2)*4] + movss xmm6, [esi+3*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(4*4+0)*4] + movhps xmm4, [edi+(4*4+2)*4] + movss xmm6, [esi+4*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(5*4+0)*4] + movhps xmm5, [edi+(5*4+2)*4] + movss xmm6, [esi+5*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + } + return; + } + case 5: { // 6x5 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [edi+(0*5+0)*4] + movhps xmm6, [edi+(0*5+2)*4] + movss xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movlps xmm7, [edi+(1*5+0)*4] + movhps xmm7, [edi+(1*5+2)*4] + movss xmm1, [esi+1*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm7, [edi+(2*5+0)*4] + movhps xmm7, [edi+(2*5+2)*4] + movss xmm2, [esi+2*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movlps xmm7, [edi+(3*5+0)*4] + movhps xmm7, [edi+(3*5+2)*4] + movss xmm3, [esi+3*4] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movlps xmm7, [edi+(4*5+0)*4] + movhps xmm7, [edi+(4*5+2)*4] + movss xmm4, [esi+4*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movlps xmm7, [edi+(5*5+0)*4] + movhps xmm7, [edi+(5*5+2)*4] + movss xmm5, [esi+5*4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE4( 0, xmm6, xmm7 ) + movss xmm6, [edi+(0*5+4)*4] + mulss xmm6, xmm0 + movss xmm7, [edi+(1*5+4)*4] + mulss xmm7, xmm1 + addss xmm6, xmm7 + movss xmm7, [edi+(2*5+4)*4] + mulss xmm7, xmm2 + addss xmm6, xmm7 + movss xmm7, [edi+(3*5+4)*4] + mulss xmm7, xmm3 + addss xmm6, xmm7 + movss xmm7, [edi+(4*5+4)*4] + mulss xmm7, xmm4 + addss xmm6, xmm7 + movss xmm7, [edi+(5*5+4)*4] + mulss xmm7, xmm5 + addss xmm6, xmm7 + STORE1( 16, xmm6, xmm7 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movlps xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(4*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(5*6+0)*4] + movhps xmm5, [edi+(5*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + movhps xmm5, [edi+(5*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + return; + } + } + break; + default: + int numRows = mat.GetNumRows(); + for ( int i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] STOREC sum; + } + break; + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +void idSIMD_SSE::MatX_TransposeMultiplySubVecX + + optimizes the following matrix multiplications: + + Nx6 * Nx1 + 6xN * 6x1 + + with N in the range [1-6] +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { +#define STORE1( offset, reg1, reg2 ) \ + __asm movss reg2, [eax+offset] \ + __asm subss reg2, reg1 \ + __asm movss [eax+offset], reg2 +#define STORE2LO( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 +#define STORE2HI( offset, reg1, reg2 ) \ + __asm movhps reg2, [eax+offset] \ + __asm subps reg2, reg1 \ + __asm movhps [eax+offset], reg2 +#define STORE4( offset, reg1, reg2 ) \ + __asm movlps reg2, [eax+offset] \ + __asm movhps reg2, [eax+offset+8] \ + __asm subps reg2, reg1 \ + __asm movlps [eax+offset], reg2 \ + __asm movhps [eax+offset+8], reg2 +#define STOREC -= + + int numColumns; + const float *mPtr, *vPtr; + float *dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + switch( numColumns ) { + case 6: { // 1x6 * 1x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + STORE4( 0, xmm0, xmm2 ) + STORE2LO( 16, xmm1, xmm3 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0]; + mPtr++; + } + return; + } + } + break; + case 2: + switch( numColumns ) { + case 6: { // 2x6 * 2x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, [edi] + mulps xmm2, xmm0 + movlps xmm3, [edi+24] + movhps xmm3, [edi+32] + mulps xmm3, xmm1 + addps xmm2, xmm3 + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+16] + movhps xmm4, [edi+40] + mulps xmm4, xmm0 + movhlps xmm3, xmm4 + addps xmm3, xmm4 + STORE4( 0, xmm2, xmm5 ) + STORE2LO( 16, xmm3, xmm6 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + return; + } + } + break; + case 3: + switch( numColumns ) { + case 6: { // 3x6 * 3x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movss xmm1, [esi+2*4] + movlps xmm3, [edi+(0*6+0)*4] + movhps xmm3, [edi+(0*6+2)*4] + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(2*6+4)*4] + mulps xmm5, xmm1 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + return; + } + } + break; + case 4: + switch( numColumns ) { + case 6: { // 4x6 * 4x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*6+0)*4] + movhps xmm4, [edi+(2*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + return; + } + } + break; + case 5: + switch( numColumns ) { + case 6: { // 5x6 * 5x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movss xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm4, xmm2 + mulps xmm4, [edi+(4*6+0)*4] + addps xmm3, xmm4 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + return; + } + } + break; + case 6: + switch( numColumns ) { + case 1: { // 6x1 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi] + movhps xmm0, [esi+8] + movlps xmm1, [esi+16] + mulps xmm0, [edi] + mulps xmm1, [edi+16] + shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 3, 2 ) + addps xmm0, xmm1 + movhlps xmm2, xmm0 + addss xmm2, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm2, xmm0 + STORE1( 0, xmm2, xmm3 ) + } + return; + } + case 2: { // 6x2 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, [edi+0*4] + mulps xmm6, xmm0 + movlps xmm1, [esi+2*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+4*4] + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm2, [esi+4*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm7, [edi+8*4] + mulps xmm7, xmm2 + addps xmm6, xmm7 + movhlps xmm3, xmm6 + addps xmm3, xmm6 + STORE2LO( 0, xmm3, xmm7 ) + } + return; + } + case 3: { // 6x3 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movss xmm0, [edi+(0*3+2)*4] + movhps xmm0, [edi+(0*3+0)*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm6, [esi+0*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm1, [edi+(1*3+0)*4] + movhps xmm1, [edi+(1*3+1)*4] + movss xmm7, [esi+1*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm2, [edi+(2*3+2)*4] + movhps xmm2, [edi+(2*3+0)*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+2*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movss xmm3, [edi+(3*3+0)*4] + movhps xmm3, [edi+(3*3+1)*4] + movss xmm7, [esi+3*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movss xmm4, [edi+(4*3+2)*4] + movhps xmm4, [edi+(4*3+0)*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 1, 3, 0 ) + movss xmm7, [esi+4*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movss xmm5, [edi+(5*3+0)*4] + movhps xmm5, [edi+(5*3+1)*4] + movss xmm7, [esi+5*4] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE1( 0, xmm6, xmm7 ) + STORE2HI( 4, xmm6, xmm7 ) + } + return; + } + case 4: { // 6x4 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm3, [edi+(0*4+0)*4] + movhps xmm3, [edi+(0*4+2)*4] + movss xmm4, [esi+0*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm4 + movlps xmm5, [edi+(1*4+0)*4] + movhps xmm5, [edi+(1*4+2)*4] + movss xmm6, [esi+1*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(2*4+0)*4] + movhps xmm4, [edi+(2*4+2)*4] + movss xmm6, [esi+2*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(3*4+0)*4] + movhps xmm5, [edi+(3*4+2)*4] + movss xmm6, [esi+3*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movlps xmm4, [edi+(4*4+0)*4] + movhps xmm4, [edi+(4*4+2)*4] + movss xmm6, [esi+4*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm6 + addps xmm3, xmm4 + movlps xmm5, [edi+(5*4+0)*4] + movhps xmm5, [edi+(5*4+2)*4] + movss xmm6, [esi+5*4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + } + return; + } + case 5: { // 6x5 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm6, [edi+(0*5+0)*4] + movhps xmm6, [edi+(0*5+2)*4] + movss xmm0, [esi+0*4] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movlps xmm7, [edi+(1*5+0)*4] + movhps xmm7, [edi+(1*5+2)*4] + movss xmm1, [esi+1*4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movlps xmm7, [edi+(2*5+0)*4] + movhps xmm7, [edi+(2*5+2)*4] + movss xmm2, [esi+2*4] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + movlps xmm7, [edi+(3*5+0)*4] + movhps xmm7, [edi+(3*5+2)*4] + movss xmm3, [esi+3*4] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm3 + addps xmm6, xmm7 + movlps xmm7, [edi+(4*5+0)*4] + movhps xmm7, [edi+(4*5+2)*4] + movss xmm4, [esi+4*4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movlps xmm7, [edi+(5*5+0)*4] + movhps xmm7, [edi+(5*5+2)*4] + movss xmm5, [esi+5*4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + STORE4( 0, xmm6, xmm7 ) + movss xmm6, [edi+(0*5+4)*4] + mulss xmm6, xmm0 + movss xmm7, [edi+(1*5+4)*4] + mulss xmm7, xmm1 + addss xmm6, xmm7 + movss xmm7, [edi+(2*5+4)*4] + mulss xmm7, xmm2 + addss xmm6, xmm7 + movss xmm7, [edi+(3*5+4)*4] + mulss xmm7, xmm3 + addss xmm6, xmm7 + movss xmm7, [edi+(4*5+4)*4] + mulss xmm7, xmm4 + addss xmm6, xmm7 + movss xmm7, [edi+(5*5+4)*4] + mulss xmm7, xmm5 + addss xmm6, xmm7 + STORE1( 16, xmm6, xmm7 ) + } + return; + } + case 6: { // 6x6 * 6x1 + __asm { + mov esi, vPtr + mov edi, mPtr + mov eax, dstPtr + movlps xmm0, [esi+0*4] + movlps xmm1, [esi+2*4] + movlps xmm2, [esi+4*4] + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, [edi+(0*6+0)*4] + movlps xmm5, [edi+(1*6+0)*4] + movhps xmm5, [edi+(1*6+2)*4] + movaps xmm6, xmm0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(2*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(3*6+0)*4] + movhps xmm5, [edi+(3*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, [edi+(4*6+0)*4] + addps xmm3, xmm6 + movaps xmm6, xmm2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movlps xmm5, [edi+(5*6+0)*4] + movhps xmm5, [edi+(5*6+2)*4] + mulps xmm5, xmm6 + addps xmm3, xmm5 + STORE4( 0, xmm3, xmm7 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movlps xmm3, [edi+(0*6+4)*4] + movhps xmm3, [edi+(1*6+4)*4] + mulps xmm3, xmm0 + movlps xmm4, [edi+(2*6+4)*4] + movhps xmm4, [edi+(3*6+4)*4] + mulps xmm4, xmm1 + addps xmm3, xmm4 + movlps xmm5, [edi+(4*6+4)*4] + movhps xmm5, [edi+(5*6+4)*4] + mulps xmm5, xmm2 + addps xmm3, xmm5 + movhlps xmm4, xmm3 + addps xmm3, xmm4 + STORE2LO( 16, xmm3, xmm7 ) + } + return; + } + default: { + for ( int i = 0; i < numColumns; i++ ) { + dstPtr[i] STOREC *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + return; + } + } + break; + default: + int numRows = mat.GetNumRows(); + for ( int i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] STOREC sum; + } + break; + } + +#undef STOREC +#undef STORE4 +#undef STORE2HI +#undef STORE2LO +#undef STORE1 +} + +/* +============ +idSIMD_SSE::MatX_MultiplyMatX + + optimizes the following matrix multiplications: + + NxN * Nx6 + 6xN * Nx6 + Nx6 * 6xN + 6x6 * 6xN + + with N in the range [1-6]. + + The hot cache clock cycle counts are generally better for the SIMD version than the + FPU version. At times up to 40% less clock cycles on a P3. In practise however, + the results are poor probably due to memory access. +============ +*/ +void VPCALL idSIMD_SSE::MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( m1.GetNumColumns() == m2.GetNumRows() ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + k = m1.GetNumRows(); + l = m2.GetNumColumns(); + n = m1.GetNumColumns(); + + switch( n ) { + case 1: { + if ( !(l^6) ) { + switch( k ) { + case 1: { // 1x1 * 1x6, no precision loss compared to FPU version + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movss xmm0, [edi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, [esi] + mulps xmm1, xmm0 + movaps [eax], xmm1 + movlps xmm2, [esi+16] + mulps xmm2, xmm0 + movlps [eax+16], xmm2 + } + return; + } + case 6: { // 6x1 * 1x6, no precision loss compared to FPU version + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + xorps xmm1, xmm1 + movaps xmm0, [edi] + movlps xmm1, [edi+16] + movlhps xmm1, xmm0 + movhlps xmm2, xmm0 + movlhps xmm2, xmm1 + // row 0 and 1 + movaps xmm3, [esi] + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) + movaps xmm6, xmm3 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + movaps [eax], xmm4 + movaps [eax+16], xmm5 + movaps [eax+32], xmm6 + // row 2 and 3 + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 2, 2 ) + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 2, 3, 3 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 3, 3, 3 ) + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm3, xmm2 + movaps [eax+48], xmm4 + movaps [eax+64], xmm5 + movaps [eax+80], xmm3 + // row 4 and 5 + movlps xmm3, [esi+16] + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm3, xmm2 + movaps [eax+96], xmm4 + movaps [eax+112], xmm5 + movaps [eax+128], xmm3 + } + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + } + case 2: { + if ( !(l^6) ) { + switch( k ) { + case 2: { // 2x2 * 2x6 + + #define MUL_Nx2_2x6_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movlps xmm1, [esi+16] \ + __asm movhps xmm1, [esi+40] \ + __asm movlps xmm2, [esi+24] \ + __asm movhps xmm2, [esi+32] + + #define MUL_Nx2_2x6_ROW2( row ) \ + __asm movaps xmm3, [edi+row*16] \ + __asm movaps xmm5, xmm0 \ + __asm movaps xmm4, xmm3 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm4 \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm6, xmm2 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 1, 1, 1 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm5, xmm6 \ + __asm movaps [eax+row*48], xmm5 \ + __asm movaps xmm4, xmm3 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movaps xmm7, xmm1 \ + __asm mulps xmm7, xmm4 \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm5, xmm0 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 2, 2 ) \ + __asm mulps xmm5, xmm4 \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm6, xmm2 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 3, 3, 3 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm movaps xmm6, xmm1 \ + __asm mulps xmm6, xmm3 \ + __asm movaps xmm4, xmm7 \ + __asm movlhps xmm7, xmm6 \ + __asm movhlps xmm6, xmm4 \ + __asm addps xmm6, xmm7 \ + __asm movlps [eax+row*48+16], xmm6 \ + __asm movlps [eax+row*48+24], xmm5 \ + __asm movhps [eax+row*48+32], xmm5 \ + __asm movhps [eax+row*48+40], xmm6 + + MUL_Nx2_2x6_INIT + MUL_Nx2_2x6_ROW2( 0 ) + + return; + } + case 6: { // 6x2 * 2x6 + + MUL_Nx2_2x6_INIT + MUL_Nx2_2x6_ROW2( 0 ) + MUL_Nx2_2x6_ROW2( 1 ) + MUL_Nx2_2x6_ROW2( 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr += 2; + } + break; + } + case 3: { + if ( !(l^6) ) { + switch( k ) { + case 3: { // 3x3 * 3x6 + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movaps xmm5, xmmword ptr [esi] + movlps xmm6, qword ptr [esi+24] + movhps xmm6, qword ptr [esi+32] + movaps xmm7, xmmword ptr [esi+48] + movss xmm0, dword ptr [edi] + shufps xmm0, xmm0, 0 + mulps xmm0, xmm5 + movss xmm1, dword ptr [edi+4] + shufps xmm1, xmm1, 0 + mulps xmm1, xmm6 + movss xmm2, dword ptr [edi+8] + shufps xmm2, xmm2, 0 + mulps xmm2, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + movaps xmmword ptr [eax], xmm0 + movss xmm3, dword ptr [edi+12] + shufps xmm3, xmm3, 0 + mulps xmm3, xmm5 + movss xmm4, dword ptr [edi+16] + shufps xmm4, xmm4, 0 + mulps xmm4, xmm6 + movss xmm0, dword ptr [edi+20] + shufps xmm0, xmm0, 0 + mulps xmm0, xmm7 + addps xmm3, xmm4 + addps xmm0, xmm3 + movlps qword ptr [eax+24], xmm0 + movhps qword ptr [eax+32], xmm0 + movss xmm1, dword ptr [edi+24] + shufps xmm1, xmm1, 0 + mulps xmm1, xmm5 + movss xmm2, dword ptr [edi+28] + shufps xmm2, xmm2, 0 + mulps xmm2, xmm6 + movss xmm3, dword ptr [edi+32] + shufps xmm3, xmm3, 0 + mulps xmm3, xmm7 + addps xmm1, xmm2 + addps xmm1, xmm3 + movaps xmmword ptr [eax+48], xmm1 + movlps xmm5, qword ptr [esi+16] + movlps xmm6, qword ptr [esi+40] + movlps xmm7, qword ptr [esi+64] + shufps xmm5, xmm5, 0x44 + shufps xmm6, xmm6, 0x44 + shufps xmm7, xmm7, 0x44 + movaps xmm3, xmmword ptr [edi] + movlps xmm4, qword ptr [edi+16] + movaps xmm0, xmm3 + shufps xmm0, xmm0, 0xF0 + mulps xmm0, xmm5 + movaps xmm1, xmm3 + shufps xmm1, xmm4, 0x05 + mulps xmm1, xmm6 + shufps xmm3, xmm4, 0x5A + mulps xmm3, xmm7 + addps xmm1, xmm0 + addps xmm1, xmm3 + movlps qword ptr [eax+16], xmm1 + movhps qword ptr [eax+40], xmm1 + movss xmm0, dword ptr [edi+24] + shufps xmm0, xmm0, 0 + mulps xmm0, xmm5 + movss xmm2, dword ptr [edi+28] + shufps xmm2, xmm2, 0 + mulps xmm2, xmm6 + movss xmm4, dword ptr [edi+32] + shufps xmm4, xmm4, 0 + mulps xmm4, xmm7 + addps xmm0, xmm2 + addps xmm0, xmm4 + movlps qword ptr [eax+64], xmm0 + } + return; + } + case 6: { // 6x3 * 3x6 + #define MUL_Nx3_3x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] + + #define MUL_Nx3_3x6_FIRST4COLUMNS_ROW( row ) \ + __asm movss xmm3, [edi+(row*3+0)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm3, xmm0 \ + __asm movss xmm4, [edi+(row*3+1)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm4, xmm1 \ + __asm addps xmm3, xmm4 \ + __asm movss xmm5, [edi+(row*3+2)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm5 \ + __asm movlps [eax+(row*6+0)*4], xmm3 \ + __asm movhps [eax+(row*6+2)*4], xmm3 + + #define MUL_Nx3_3x6_LAST2COLUMNS_ROW6 \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm movlps xmm2, [esi+16*4] \ + __asm shufps xmm0, xmm0, 0x44 \ + __asm shufps xmm1, xmm1, 0x44 \ + __asm shufps xmm2, xmm2, 0x44 \ + __asm movlps xmm3, [edi+0*4] \ + __asm movhps xmm3, [edi+2*4] \ + __asm movaps xmm4, xmm3 \ + __asm movaps xmm5, xmm3 \ + __asm shufps xmm3, xmm3, 0xF0 \ + __asm mulps xmm3, xmm0 \ + __asm movlps xmm6, [edi+4*4] \ + __asm movhps xmm6, [edi+6*4] \ + __asm shufps xmm4, xmm6, 0x05 \ + __asm mulps xmm4, xmm1 \ + __asm addps xmm3, xmm4 \ + __asm shufps xmm5, xmm6, 0x5A \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm5 \ + __asm movlps [eax+4*4], xmm3 \ + __asm movhps [eax+10*4], xmm3 \ + __asm movaps xmm5, xmm6 \ + __asm movlps xmm3, [edi+8*4] \ + __asm movhps xmm3, [edi+10*4] \ + __asm movaps xmm4, xmm3 \ + __asm shufps xmm5, xmm3, 0x5A \ + __asm mulps xmm5, xmm0 \ + __asm shufps xmm6, xmm3, 0xAF \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm4, xmm4, 0xF0 \ + __asm mulps xmm4, xmm2 \ + __asm addps xmm4, xmm5 \ + __asm movlps [eax+16*4], xmm4 \ + __asm movhps [eax+22*4], xmm4 \ + __asm movlps xmm6, [edi+12*4] \ + __asm movhps xmm6, [edi+14*4] \ + __asm movaps xmm5, xmm6 \ + __asm movaps xmm4, xmm6 \ + __asm shufps xmm6, xmm6, 0xF0 \ + __asm mulps xmm6, xmm0 \ + __asm movlps xmm3, [edi+16*4] \ + __asm shufps xmm5, xmm3, 0x05 \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm4, xmm3, 0x5A \ + __asm mulps xmm4, xmm2 \ + __asm addps xmm4, xmm5 \ + __asm movlps [eax+28*4], xmm4 \ + __asm movhps [eax+34*4], xmm4 + + MUL_Nx3_3x6_FIRST4COLUMNS_INIT + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx3_3x6_FIRST4COLUMNS_ROW( 5 ) + MUL_Nx3_3x6_LAST2COLUMNS_ROW6 + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr += 3; + } + break; + } + case 4: { + if ( !(l^6) ) { + switch( k ) { + case 4: { // 4x4 * 4x6 + + #define MUL_Nx4_4x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] \ + __asm movlps xmm3, [esi+18*4] \ + __asm movhps xmm3, [esi+20*4] + + #define MUL_Nx4_4x6_FIRST4COLUMNS_ROW( row ) \ + __asm movss xmm4, [edi+row*16+0*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm4, xmm0 \ + __asm movss xmm5, [edi+row*16+1*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm4, xmm5 \ + __asm movss xmm6, [edi+row*16+2*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm4, xmm6 \ + __asm movss xmm7, [edi+row*16+3*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm3 \ + __asm addps xmm4, xmm7 \ + __asm movlps [eax+row*24+0], xmm4 \ + __asm movhps [eax+row*24+8], xmm4 + + #define MUL_Nx4_4x6_LAST2COLUMNS_INIT \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm movlps xmm2, [esi+16*4] \ + __asm movlps xmm3, [esi+22*4] \ + __asm shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm1, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm3, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_Nx4_4x6_LAST2COLUMNS_ROW2( row ) \ + __asm movlps xmm7, [edi+row*32+ 0*4] \ + __asm movhps xmm7, [edi+row*32+ 4*4] \ + __asm movaps xmm6, xmm7 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 3, 3 ) \ + __asm mulps xmm6, xmm0 \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 1, 2, 2 ) \ + __asm mulps xmm7, xmm1 \ + __asm addps xmm6, xmm7 \ + __asm movlps xmm4, [edi+row*32+ 2*4] \ + __asm movhps xmm4, [edi+row*32+ 6*4] \ + __asm movaps xmm5, xmm4 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 3, 3 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 1, 2, 2 ) \ + __asm mulps xmm4, xmm3 \ + __asm addps xmm6, xmm4 \ + __asm movlps [eax+row*48+ 4*4], xmm6 \ + __asm movhps [eax+row*48+10*4], xmm6 + + MUL_Nx4_4x6_FIRST4COLUMNS_INIT + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx4_4x6_LAST2COLUMNS_INIT + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 1 ) + + return; + } + case 6: { // 6x4 * 4x6 + + MUL_Nx4_4x6_FIRST4COLUMNS_INIT + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx4_4x6_FIRST4COLUMNS_ROW( 5 ) + MUL_Nx4_4x6_LAST2COLUMNS_INIT + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 1 ) + MUL_Nx4_4x6_LAST2COLUMNS_ROW2( 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr += 4; + } + break; + } + case 5: { + if ( !(l^6) ) { + switch( k ) { + case 5: { // 5x5 * 5x6 + + #define MUL_Nx5_5x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] \ + __asm movlps xmm3, [esi+18*4] \ + __asm movhps xmm3, [esi+20*4] \ + __asm movlps xmm4, [esi+24*4] \ + __asm movhps xmm4, [esi+26*4] + + #define MUL_Nx5_5x6_FIRST4COLUMNS_ROW( row ) \ + __asm movss xmm6, [edi+row*20+0*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm0 \ + __asm movss xmm5, [edi+row*20+1*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+row*20+2*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+row*20+3*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm3 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+row*20+4*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm movlps [eax+row*24+0], xmm6 \ + __asm movhps [eax+row*24+8], xmm6 + + #define MUL_Nx5_5x6_LAST2COLUMNS_INIT \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm movlps xmm2, [esi+16*4] \ + __asm movlps xmm3, [esi+22*4] \ + __asm movlps xmm4, [esi+28*4] \ + __asm shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm3, xmm4, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm4, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_Nx5_5x6_LAST2COLUMNS_ROW2( row ) \ + __asm movlps xmm7, [edi+row*40+ 0*4] \ + __asm movhps xmm7, [edi+row*40+ 6*4] \ + __asm movaps xmm6, xmm7 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 2, 2 ) \ + __asm mulps xmm6, xmm0 \ + __asm movaps xmm5, xmm7 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 3, 3 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm6, xmm5 \ + __asm movlps xmm7, [edi+row*40+ 2*4] \ + __asm movhps xmm7, [edi+row*40+ 8*4] \ + __asm movaps xmm5, xmm7 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 2, 2 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm movaps xmm5, xmm7 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 3, 3 ) \ + __asm mulps xmm5, xmm3 \ + __asm addps xmm6, xmm5 \ + __asm movlps xmm5, [edi+row*40+ 4*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm movlps [eax+row*48+ 4*4], xmm6 \ + __asm movhps [eax+row*48+10*4], xmm6 + + #define MUL_Nx5_5x6_LAST2COLUMNS_ROW( row ) \ + __asm movlps xmm6, [edi+20*4+0*4] \ + __asm unpcklps xmm6, xmm6 \ + __asm mulps xmm6, xmm0 \ + __asm movlps xmm5, [edi+20*4+2*4] \ + __asm unpcklps xmm5, xmm5 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm movss xmm5, [edi+20*4+4*4] \ + __asm unpcklps xmm5, xmm5 \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm movhlps xmm7, xmm6 \ + __asm addps xmm6, xmm7 \ + __asm movlps [eax+row*24+4*4], xmm6 + + MUL_Nx5_5x6_FIRST4COLUMNS_INIT + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx5_5x6_LAST2COLUMNS_INIT + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 1 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW( 4 ) + + return; + } + case 6: { // 6x5 * 5x6 + + MUL_Nx5_5x6_FIRST4COLUMNS_INIT + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 0 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 1 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 2 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 3 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 4 ) + MUL_Nx5_5x6_FIRST4COLUMNS_ROW( 5 ) + MUL_Nx5_5x6_LAST2COLUMNS_INIT + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 0 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 1 ) + MUL_Nx5_5x6_LAST2COLUMNS_ROW2( 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr += 5; + } + break; + } + case 6: { + switch( k ) { + case 1: { + if ( !(l^1) ) { // 1x6 * 6x1 + dstPtr[0] = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[1] + m1Ptr[2] * m2Ptr[2] + + m1Ptr[3] * m2Ptr[3] + m1Ptr[4] * m2Ptr[4] + m1Ptr[5] * m2Ptr[5]; + return; + } + break; + } + case 2: { + if ( !(l^2) ) { // 2x6 * 6x2 + + #define MUL_Nx6_6x2_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movaps xmm1, [esi+16] \ + __asm movaps xmm2, [esi+32] + + #define MUL_Nx6_6x2_ROW2( row ) \ + __asm movaps xmm7, [edi+row*48+0*4] \ + __asm movaps xmm6, xmm7 \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm7, xmm0 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movaps xmm6, [edi+row*48+4*4] \ + __asm movaps xmm5, xmm6 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm mulps xmm5, xmm0 \ + __asm movaps xmm6, [edi+row*48+24+2*4] \ + __asm movaps xmm4, xmm6 \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm5, xmm6 \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 3, 3 ) \ + __asm mulps xmm4, xmm2 \ + __asm addps xmm5, xmm4 \ + __asm movaps xmm4, xmm5 \ + __asm movhlps xmm5, xmm7 \ + __asm movlhps xmm7, xmm4 \ + __asm addps xmm7, xmm5 \ + __asm movaps [eax+row*16], xmm7 + + MUL_Nx6_6x2_INIT + MUL_Nx6_6x2_ROW2( 0 ) + + return; + } + break; + } + case 3: { + if ( !(l^3) ) { // 3x6 * 6x3 + + #define MUL_Nx6_6x3_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movss xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 1*4] \ + __asm movss xmm1, [esi+ 3*4] \ + __asm movhps xmm1, [esi+ 4*4] \ + __asm movss xmm2, [esi+ 6*4] \ + __asm movhps xmm2, [esi+ 7*4] \ + __asm movss xmm3, [esi+ 9*4] \ + __asm movhps xmm3, [esi+10*4] \ + __asm movss xmm4, [esi+12*4] \ + __asm movhps xmm4, [esi+13*4] \ + __asm movss xmm5, [esi+15*4] \ + __asm movhps xmm5, [esi+16*4] + + #define MUL_Nx6_6x3_ROW( row ) \ + __asm movss xmm7, [edi+row*24+0] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+row*24+4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+8] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+12] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+16] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+20] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movss [eax+row*12+0], xmm7 \ + __asm movhps [eax+row*12+4], xmm7 + + MUL_Nx6_6x3_INIT + MUL_Nx6_6x3_ROW( 0 ) + MUL_Nx6_6x3_ROW( 1 ) + MUL_Nx6_6x3_ROW( 2 ) + + return; + } + break; + } + case 4: { + if ( !(l^4) ) { // 4x6 * 6x4 + + #define MUL_Nx6_6x4_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movaps xmm1, [esi+16] \ + __asm movaps xmm2, [esi+32] \ + __asm movaps xmm3, [esi+48] \ + __asm movaps xmm4, [esi+64] \ + __asm movaps xmm5, [esi+80] + + #define MUL_Nx6_6x4_ROW( row ) \ + __asm movss xmm7, [edi+row*24+0] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+row*24+4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+8] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+12] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+16] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+row*24+20] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movaps [eax+row*16], xmm7 + + MUL_Nx6_6x4_INIT + MUL_Nx6_6x4_ROW( 0 ) + MUL_Nx6_6x4_ROW( 1 ) + MUL_Nx6_6x4_ROW( 2 ) + MUL_Nx6_6x4_ROW( 3 ) + + return; + } + break; + } + case 5: { + if ( !(l^5) ) { // 5x6 * 6x5 + + #define MUL_Nx6_6x5_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movlps xmm1, [esi+20] \ + __asm movhps xmm1, [esi+28] \ + __asm movlps xmm2, [esi+40] \ + __asm movhps xmm2, [esi+48] \ + __asm movlps xmm3, [esi+60] \ + __asm movhps xmm3, [esi+68] \ + __asm movaps xmm4, [esi+80] \ + __asm movlps xmm5, [esi+100] \ + __asm movhps xmm5, [esi+108] + + #define MUL_Nx6_6x5_ROW( row ) \ + __asm movss xmm7, [edi+row*24+0] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm fld dword ptr [edi+(row*6+0)*4] \ + __asm fmul dword ptr [esi+(4+0*5)*4] \ + __asm movss xmm6, [edi+row*24+4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+1)*4] \ + __asm fmul dword ptr [esi+(4+1*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+8] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+2)*4] \ + __asm fmul dword ptr [esi+(4+2*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+12] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+3)*4] \ + __asm fmul dword ptr [esi+(4+3*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+16] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+4)*4] \ + __asm fmul dword ptr [esi+(4+4*5)*4] \ + __asm faddp st(1),st \ + __asm movss xmm6, [edi+row*24+20] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm fld dword ptr [edi+(row*6+5)*4] \ + __asm fmul dword ptr [esi+(4+5*5)*4] \ + __asm faddp st(1),st \ + __asm fstp dword ptr [eax+(row*5+4)*4] \ + __asm movlps [eax+row*20], xmm7 \ + __asm movhps [eax+row*20+8], xmm7 + + MUL_Nx6_6x5_INIT + MUL_Nx6_6x5_ROW( 0 ) + MUL_Nx6_6x5_ROW( 1 ) + MUL_Nx6_6x5_ROW( 2 ) + MUL_Nx6_6x5_ROW( 3 ) + MUL_Nx6_6x5_ROW( 4 ) + + return; + } + break; + } + case 6: { + switch( l ) { + case 1: { // 6x6 * 6x1 + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movlps xmm7, qword ptr [esi] + movlps xmm6, qword ptr [esi+8] + shufps xmm7, xmm7, 0x44 + shufps xmm6, xmm6, 0x44 + movlps xmm0, qword ptr [edi ] + movhps xmm0, qword ptr [edi+ 24] + mulps xmm0, xmm7 + movlps xmm3, qword ptr [edi+ 8] + movhps xmm3, qword ptr [edi+ 32] + mulps xmm3, xmm6 + movlps xmm1, qword ptr [edi+ 48] + movhps xmm1, qword ptr [edi+ 72] + mulps xmm1, xmm7 + movlps xmm2, qword ptr [edi+ 96] + movhps xmm2, qword ptr [edi+120] + mulps xmm2, xmm7 + movlps xmm4, qword ptr [edi+ 56] + movhps xmm4, qword ptr [edi+ 80] + movlps xmm5, qword ptr [edi+104] + movhps xmm5, qword ptr [edi+128] + mulps xmm4, xmm6 + movlps xmm7, qword ptr [esi+16] + addps xmm0, xmm3 + shufps xmm7, xmm7, 0x44 + mulps xmm5, xmm6 + addps xmm1, xmm4 + movlps xmm3, qword ptr [edi+ 16] + movhps xmm3, qword ptr [edi+ 40] + addps xmm2, xmm5 + movlps xmm4, qword ptr [edi+ 64] + movhps xmm4, qword ptr [edi+ 88] + mulps xmm3, xmm7 + movlps xmm5, qword ptr [edi+112] + movhps xmm5, qword ptr [edi+136] + addps xmm0, xmm3 + mulps xmm4, xmm7 + mulps xmm5, xmm7 + addps xmm1, xmm4 + addps xmm2, xmm5 + movaps xmm6, xmm0 + shufps xmm0, xmm1, 0x88 + shufps xmm6, xmm1, 0xDD + movaps xmm7, xmm2 + shufps xmm7, xmm2, 0x88 + shufps xmm2, xmm2, 0xDD + addps xmm0, xmm6 + addps xmm2, xmm7 + movlps [eax], xmm0 + movhps [eax+8], xmm0 + movlps [eax+16], xmm2 + } + return; + } + case 2: { // 6x6 * 6x2 + + MUL_Nx6_6x2_INIT + MUL_Nx6_6x2_ROW2( 0 ) + MUL_Nx6_6x2_ROW2( 1 ) + MUL_Nx6_6x2_ROW2( 2 ) + + return; + } + case 3: { // 6x6 * 6x3 + + MUL_Nx6_6x3_INIT + MUL_Nx6_6x3_ROW( 0 ) + MUL_Nx6_6x3_ROW( 1 ) + MUL_Nx6_6x3_ROW( 2 ) + MUL_Nx6_6x3_ROW( 3 ) + MUL_Nx6_6x3_ROW( 4 ) + MUL_Nx6_6x3_ROW( 5 ) + + return; + } + case 4: { // 6x6 * 6x4 + + MUL_Nx6_6x4_INIT + MUL_Nx6_6x4_ROW( 0 ) + MUL_Nx6_6x4_ROW( 1 ) + MUL_Nx6_6x4_ROW( 2 ) + MUL_Nx6_6x4_ROW( 3 ) + MUL_Nx6_6x4_ROW( 4 ) + MUL_Nx6_6x4_ROW( 5 ) + + return; + } + case 5: { // 6x6 * 6x5 + + MUL_Nx6_6x5_INIT + MUL_Nx6_6x5_ROW( 0 ) + MUL_Nx6_6x5_ROW( 1 ) + MUL_Nx6_6x5_ROW( 2 ) + MUL_Nx6_6x5_ROW( 3 ) + MUL_Nx6_6x5_ROW( 4 ) + MUL_Nx6_6x5_ROW( 5 ) + + return; + } + case 6: { // 6x6 * 6x6 + __asm { + mov ecx, dword ptr m2Ptr + movlps xmm3, qword ptr [ecx+72] + mov edx, dword ptr m1Ptr + // Loading first 4 columns (upper 4 rows) of m2Ptr. + movaps xmm0, xmmword ptr [ecx] + movlps xmm1, qword ptr [ecx+24] + movhps xmm1, qword ptr [ecx+32] + movaps xmm2, xmmword ptr [ecx+48] + movhps xmm3, qword ptr [ecx+80] + // Calculating first 4 elements in the first row of the destination matrix. + movss xmm4, dword ptr [edx] + movss xmm5, dword ptr [edx+4] + mov eax, dword ptr dstPtr + shufps xmm4, xmm4, 0 + movss xmm6, dword ptr [edx+8] + shufps xmm5, xmm5, 0 + movss xmm7, dword ptr [edx+12] + mulps xmm4, xmm0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + addps xmm5, xmm4 + mulps xmm7, xmm3 + addps xmm6, xmm5 + addps xmm7, xmm6 + movaps xmmword ptr [eax], xmm7 + // Calculating first 4 elements in the second row of the destination matrix. + movss xmm4, dword ptr [edx+24] + shufps xmm4, xmm4, 0 + mulps xmm4, xmm0 + movss xmm5, dword ptr [edx+28] + shufps xmm5, xmm5, 0 + mulps xmm5, xmm1 + movss xmm6, dword ptr [edx+32] + shufps xmm6, xmm6, 0 + movss xmm7, dword ptr [edx+36] + shufps xmm7, xmm7, 0 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm7, xmm6 + addps xmm5, xmm4 + addps xmm7, xmm5 + // Calculating first 4 elements in the third row of the destination matrix. + movss xmm4, dword ptr [edx+48] + movss xmm5, dword ptr [edx+52] + movlps qword ptr [eax+24], xmm7 ; save 2nd + movhps qword ptr [eax+32], xmm7 ; row + movss xmm6, dword ptr [edx+56] + movss xmm7, dword ptr [edx+60] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm5, xmm4 + addps xmm7, xmm6 + addps xmm7, xmm5 + movaps xmmword ptr [eax+48], xmm7 + // Calculating first 4 elements in the fourth row of the destination matrix. + movss xmm4, dword ptr [edx+72] + movss xmm5, dword ptr [edx+76] + movss xmm6, dword ptr [edx+80] + movss xmm7, dword ptr [edx+84] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm4, xmm5 + addps xmm6, xmm4 + addps xmm7, xmm6 + movlps qword ptr [eax+72], xmm7 + movhps qword ptr [eax+80], xmm7 + // Calculating first 4 elements in the fifth row of the destination matrix. + movss xmm4, dword ptr [edx+96] + movss xmm5, dword ptr [edx+100] + movss xmm6, dword ptr [edx+104] + movss xmm7, dword ptr [edx+108] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm5, xmm4 + addps xmm7, xmm6 + addps xmm7, xmm5 + movaps xmmword ptr [eax+96], xmm7 + // Calculating first 4 elements in the sixth row of the destination matrix. + movss xmm4, dword ptr [edx+120] + movss xmm5, dword ptr [edx+124] + movss xmm6, dword ptr [edx+128] + movss xmm7, dword ptr [edx+132] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm6, xmm6, 0 + shufps xmm7, xmm7, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + mulps xmm7, xmm3 + addps xmm4, xmm5 + addps xmm6, xmm4 + addps xmm7, xmm6 + movhps qword ptr [eax+128], xmm7 + movlps qword ptr [eax+120], xmm7 + // Loading first 4 columns (lower 2 rows) of m2Ptr. + movlps xmm0, qword ptr [ecx+96] + movhps xmm0, qword ptr [ecx+104] + movlps xmm1, qword ptr [ecx+120] + movhps xmm1, qword ptr [ecx+128] + // Calculating first 4 elements in the first row of the destination matrix. + movss xmm2, dword ptr [edx+16] + shufps xmm2, xmm2, 0 + movss xmm4, dword ptr [edx+40] + movss xmm3, dword ptr [edx+20] + movss xmm5, dword ptr [edx+44] + movaps xmm6, xmmword ptr [eax] + movlps xmm7, qword ptr [eax+24] + shufps xmm3, xmm3, 0 + shufps xmm5, xmm5, 0 + movhps xmm7, qword ptr [eax+32] + shufps xmm4, xmm4, 0 + mulps xmm5, xmm1 + mulps xmm2, xmm0 + mulps xmm3, xmm1 + mulps xmm4, xmm0 + addps xmm6, xmm2 + addps xmm7, xmm4 + addps xmm7, xmm5 + addps xmm6, xmm3 + movlps qword ptr [eax+24], xmm7 + movaps xmmword ptr [eax], xmm6 + movhps qword ptr [eax+32], xmm7 + // Calculating first 4 elements in the third row of the destination matrix. + movss xmm2, dword ptr [edx+64] + movss xmm4, dword ptr [edx+88] + movss xmm5, dword ptr [edx+92] + movss xmm3, dword ptr [edx+68] + movaps xmm6, xmmword ptr [eax+48] + movlps xmm7, qword ptr [eax+72] + movhps xmm7, qword ptr [eax+80] + shufps xmm2, xmm2, 0 + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + shufps xmm3, xmm3, 0 + mulps xmm2, xmm0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm3, xmm1 + addps xmm6, xmm2 + addps xmm6, xmm3 + addps xmm7, xmm4 + addps xmm7, xmm5 + movlps qword ptr [eax+72], xmm7 + movaps xmmword ptr [eax+48], xmm6 + movhps qword ptr [eax+80], xmm7 + // Calculating first 4 elements in the fifth row of the destination matrix. + movss xmm2, dword ptr [edx+112] + movss xmm3, dword ptr [edx+116] + movaps xmm6, xmmword ptr [eax+96] + shufps xmm2, xmm2, 0 + shufps xmm3, xmm3, 0 + mulps xmm2, xmm0 + mulps xmm3, xmm1 + addps xmm6, xmm2 + addps xmm6, xmm3 + movaps xmmword ptr [eax+96], xmm6 + // Calculating first 4 elements in the sixth row of the destination matrix. + movss xmm4, dword ptr [edx+136] + movss xmm5, dword ptr [edx+140] + movhps xmm7, qword ptr [eax+128] + movlps xmm7, qword ptr [eax+120] + shufps xmm4, xmm4, 0 + shufps xmm5, xmm5, 0 + mulps xmm4, xmm0 + mulps xmm5, xmm1 + addps xmm7, xmm4 + addps xmm7, xmm5 + // Calculating last 2 columns of the destination matrix. + movlps xmm0, qword ptr [ecx+16] + movhps xmm0, qword ptr [ecx+40] + movhps qword ptr [eax+128], xmm7 + movlps qword ptr [eax+120], xmm7 + movlps xmm2, qword ptr [ecx+64] + movhps xmm2, qword ptr [ecx+88] + movaps xmm3, xmm2 + shufps xmm3, xmm3, 4Eh + movlps xmm4, qword ptr [ecx+112] + movhps xmm4, qword ptr [ecx+136] + movaps xmm5, xmm4 + shufps xmm5, xmm5, 4Eh + movlps xmm6, qword ptr [edx] + movhps xmm6, qword ptr [edx+24] + movaps xmm7, xmm6 + shufps xmm7, xmm7, 0F0h + mulps xmm7, xmm0 + shufps xmm6, xmm6, 0A5h + movaps xmm1, xmm0 + shufps xmm1, xmm1, 4Eh + mulps xmm1, xmm6 + addps xmm7, xmm1 + movlps xmm6, qword ptr [edx+8] + movhps xmm6, qword ptr [edx+32] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm2 + mulps xmm6, xmm3 + addps xmm7, xmm1 + addps xmm7, xmm6 + movhps xmm6, qword ptr [edx+40] + movlps xmm6, qword ptr [edx+16] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm4 + mulps xmm6, xmm5 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps qword ptr [eax+16], xmm7 + movhps qword ptr [eax+40], xmm7 + movlps xmm6, qword ptr [edx+48] + movhps xmm6, qword ptr [edx+72] + movaps xmm7, xmm6 + shufps xmm7, xmm7, 0F0h + mulps xmm7, xmm0 + shufps xmm6, xmm6, 0A5h + movaps xmm1, xmm0 + shufps xmm1, xmm1, 4Eh + mulps xmm1, xmm6 + addps xmm7, xmm1 + movhps xmm6, qword ptr [edx+80] + movlps xmm6, qword ptr [edx+56] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm2 + mulps xmm6, xmm3 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps xmm6, qword ptr [edx+64] + movhps xmm6, qword ptr [edx+88] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm4 + mulps xmm6, xmm5 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps qword ptr [eax+64], xmm7 + movhps qword ptr [eax+88], xmm7 + movlps xmm6, qword ptr [edx+96] + movhps xmm6, qword ptr [edx+120] + movaps xmm7, xmm6 + shufps xmm7, xmm7, 0F0h + mulps xmm7, xmm0 + shufps xmm6, xmm6, 0A5h + movaps xmm1, xmm0 + shufps xmm1, xmm1, 4Eh + mulps xmm1, xmm6 + addps xmm7, xmm1 + movlps xmm6, qword ptr [edx+104] + movhps xmm6, qword ptr [edx+128] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm2 + mulps xmm6, xmm3 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps xmm6, qword ptr [edx+112] + movhps xmm6, qword ptr [edx+136] + movaps xmm1, xmm6 + shufps xmm1, xmm1, 0F0h + shufps xmm6, xmm6, 0A5h + mulps xmm1, xmm4 + mulps xmm6, xmm5 + addps xmm7, xmm1 + addps xmm7, xmm6 + movlps qword ptr [eax+112], xmm7 + movhps qword ptr [eax+136], xmm7 + } + return; + } + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l] + m1Ptr[5] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr += 6; + } + break; + } + default: { + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumColumns(); n++ ) { + m2Ptr += l; + sum += m1Ptr[n] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + m1Ptr += m1.GetNumColumns(); + } + break; + } + } +} + +/* +============ +idSIMD_SSE::MatX_TransposeMultiplyMatX + + optimizes the following transpose matrix multiplications: + + Nx6 * NxN + 6xN * 6x6 + + with N in the range [1-6]. +============ +*/ +void VPCALL idSIMD_SSE::MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + int i, j, k, l, n; + float *dstPtr; + const float *m1Ptr, *m2Ptr; + double sum; + + assert( m1.GetNumRows() == m2.GetNumRows() ); + + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + k = m1.GetNumColumns(); + l = m2.GetNumColumns(); + + switch( m1.GetNumRows() ) { + case 1: + if ( !((k^6)|(l^1)) ) { // 1x6 * 1x1 + __asm { + mov esi, m2Ptr + mov edi, m1Ptr + mov eax, dstPtr + movss xmm0, [esi] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movaps xmm1, xmm0 + mulps xmm0, [edi] + mulps xmm1, [edi+16] + movaps [eax], xmm0 + movlps [eax+16], xmm1 + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 2: + if ( !((k^6)|(l^2)) ) { // 2x6 * 2x2 + #define MUL_2xN_2x2_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi] \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm movlps xmm1, [esi+8] \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_2xN_2x2_ROW2( N, row ) \ + __asm movlps xmm6, [edi+(row+0*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movlps xmm7, [edi+(row+1*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm0 \ + __asm mulps xmm7, xmm1 \ + __asm addps xmm6, xmm7 \ + __asm movaps [eax+(row*2)*4], xmm6 + + MUL_2xN_2x2_INIT + MUL_2xN_2x2_ROW2( 6, 0 ) + MUL_2xN_2x2_ROW2( 6, 2 ) + MUL_2xN_2x2_ROW2( 6, 4 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 3: + if ( !((k^6)|(l^3)) ) { // 3x6 * 3x3 + + #define MUL_3xN_3x3_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movss xmm0, [esi+(0*3+0)*4] \ + __asm movhps xmm0, [esi+(0*3+1)*4] \ + __asm movss xmm1, [esi+(1*3+0)*4] \ + __asm movhps xmm1, [esi+(1*3+1)*4] \ + __asm movss xmm2, [esi+(2*3+0)*4] \ + __asm movhps xmm2, [esi+(2*3+1)*4] + + #define MUL_3xN_3x3_INIT_ROW4 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 2, 3, 0 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 3, 0 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 2, 3, 0 ) + + #define MUL_3xN_3x3_ROW4( N, row ) \ + __asm movlps xmm3, [edi+(row+0*N+0)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 1 ) \ + __asm movlps xmm4, [edi+(row+1*N+0)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 1 ) \ + __asm movlps xmm5, [edi+(row+2*N+0)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 1 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movaps [eax+(row*3+0)*4], xmm3 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm movlps xmm3, [edi+(row+0*N+1)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movlps xmm4, [edi+(row+1*N+1)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm movlps xmm5, [edi+(row+2*N+1)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movaps [eax+(row*3+4)*4], xmm3 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 1 ) \ + __asm movlps xmm3, [edi+(row+0*N+2)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 1, 1, 1 ) \ + __asm movlps xmm4, [edi+(row+1*N+2)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 1, 1, 1 ) \ + __asm movlps xmm5, [edi+(row+2*N+2)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 1, 1, 1 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movaps [eax+(row*3+8)*4], xmm3 + + #define MUL_3xN_3x3_INIT_ROW4_ROW4 \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + #define MUL_3xN_3x3_INIT_ROW4_ROW \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 1, 2, 3 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 2, 3 ) \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 1, 2, 3 ) + + #define MUL_3xN_3x3_ROW( N, row ) \ + __asm movss xmm3, [edi+(row+0*N)*4] \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm movss xmm4, [edi+(row+1*N)*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm movss xmm5, [edi+(row+2*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm3, xmm0 \ + __asm mulps xmm4, xmm1 \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm3, xmm4 \ + __asm addps xmm3, xmm5 \ + __asm movss [eax+(row*3+0)*4], xmm3 \ + __asm movhps [eax+(row*3+1)*4], xmm3 + + MUL_3xN_3x3_INIT + MUL_3xN_3x3_INIT_ROW4 + MUL_3xN_3x3_ROW4( 6, 0 ) + MUL_3xN_3x3_INIT_ROW4_ROW + MUL_3xN_3x3_ROW( 6, 4 ) + MUL_3xN_3x3_ROW( 6, 5 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 4: + if ( !((k^6)|(l^4)) ) { // 4x6 * 4x4 + + #define MUL_4xN_4x4_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movaps xmm0, [esi] \ + __asm movaps xmm1, [esi+16] \ + __asm movaps xmm2, [esi+32] \ + __asm movaps xmm3, [esi+48] + + #define MUL_4xN_4x4_ROW( N, row ) \ + __asm movss xmm7, [edi+(row+0*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+(row+1*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+2*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+3*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movaps [eax+row*16], xmm7 + + MUL_4xN_4x4_INIT + MUL_4xN_4x4_ROW( 6, 0 ) + MUL_4xN_4x4_ROW( 6, 1 ) + MUL_4xN_4x4_ROW( 6, 2 ) + MUL_4xN_4x4_ROW( 6, 3 ) + MUL_4xN_4x4_ROW( 6, 4 ) + MUL_4xN_4x4_ROW( 6, 5 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 5: + if ( !((k^6)|(l^5)) ) { // 5x6 * 5x5 + + #define MUL_5xN_5x5_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 5*4] \ + __asm movhps xmm1, [esi+ 7*4] \ + __asm movlps xmm2, [esi+10*4] \ + __asm movhps xmm2, [esi+12*4] \ + __asm movlps xmm3, [esi+15*4] \ + __asm movhps xmm3, [esi+17*4] \ + __asm movlps xmm4, [esi+20*4] \ + __asm movhps xmm4, [esi+22*4] + + #define MUL_5xN_5x5_ROW( N, row ) \ + __asm movss xmm6, [edi+(row+0*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm0 \ + __asm fld dword ptr [edi+(row+0*N)*4] \ + __asm fmul dword ptr [esi+ 4*4] \ + __asm movss xmm5, [edi+(row+1*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm1 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+1*N)*4] \ + __asm fmul dword ptr [esi+ 9*4] \ + __asm faddp st(1),st \ + __asm movss xmm5, [edi+(row+2*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm2 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+2*N)*4] \ + __asm fmul dword ptr [esi+14*4] \ + __asm faddp st(1),st \ + __asm movss xmm5, [edi+(row+3*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm3 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+3*N)*4] \ + __asm fmul dword ptr [esi+19*4] \ + __asm faddp st(1),st \ + __asm movss xmm5, [edi+(row+4*N)*4] \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm5, xmm4 \ + __asm addps xmm6, xmm5 \ + __asm fld dword ptr [edi+(row+4*N)*4] \ + __asm fmul dword ptr [esi+24*4] \ + __asm faddp st(1),st \ + __asm fstp dword ptr [eax+(row*5+4)*4] \ + __asm movlps [eax+(row*5+0)*4], xmm6 \ + __asm movhps [eax+(row*5+2)*4], xmm6 + + MUL_5xN_5x5_INIT + MUL_5xN_5x5_ROW( 6, 0 ) + MUL_5xN_5x5_ROW( 6, 1 ) + MUL_5xN_5x5_ROW( 6, 2 ) + MUL_5xN_5x5_ROW( 6, 3 ) + MUL_5xN_5x5_ROW( 6, 4 ) + MUL_5xN_5x5_ROW( 6, 5 ) + + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 6: + if ( !(l^6) ) { + switch( k ) { + case 1: { // 6x1 * 6x6 + #define MUL_6xN_6x6_FIRST4COLUMNS_INIT \ + __asm mov esi, m2Ptr \ + __asm mov edi, m1Ptr \ + __asm mov eax, dstPtr \ + __asm movlps xmm0, [esi+ 0*4] \ + __asm movhps xmm0, [esi+ 2*4] \ + __asm movlps xmm1, [esi+ 6*4] \ + __asm movhps xmm1, [esi+ 8*4] \ + __asm movlps xmm2, [esi+12*4] \ + __asm movhps xmm2, [esi+14*4] \ + __asm movlps xmm3, [esi+18*4] \ + __asm movhps xmm3, [esi+20*4] \ + __asm movlps xmm4, [esi+24*4] \ + __asm movhps xmm4, [esi+26*4] \ + __asm movlps xmm5, [esi+30*4] \ + __asm movhps xmm5, [esi+32*4] + + #define MUL_6xN_6x6_FIRST4COLUMNS_ROW( N, row ) \ + __asm movss xmm7, [edi+(row+0*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+(row+1*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+2*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+3*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+4*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(row+5*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movlps [eax+(row*6+0)*4], xmm7 \ + __asm movhps [eax+(row*6+2)*4], xmm7 + + #define MUL_6xN_6x6_LAST2COLUMNS_INIT \ + __asm movlps xmm0, [esi+ 4*4] \ + __asm movlps xmm1, [esi+10*4] \ + __asm shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm movlps xmm2, [esi+16*4] \ + __asm movlps xmm3, [esi+22*4] \ + __asm shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm movlps xmm4, [esi+28*4] \ + __asm movlps xmm5, [esi+34*4] \ + __asm shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 1, 0, 1 ) \ + __asm shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 1, 0, 1 ) + + #define MUL_6xN_6x6_LAST2COLUMNS_ROW2( N, row ) \ + __asm movlps xmm7, [edi+(row*2+0*N)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm7, xmm0 \ + __asm movlps xmm6, [edi+(row*2+1*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+2*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+3*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+4*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movlps xmm6, [edi+(row*2+5*N)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 1, 1 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movlps [eax+(row*12+ 4)*4], xmm7 \ + __asm movhps [eax+(row*12+10)*4], xmm7 + + #define MUL_6xN_6x6_LAST2COLUMNS_ROW( N, row ) \ + __asm movss xmm7, [edi+(1*N-1)*4] \ + __asm shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm7, xmm0 \ + __asm movss xmm6, [edi+(2*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm1 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(3*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm2 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(4*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm3 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(5*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm4 \ + __asm addps xmm7, xmm6 \ + __asm movss xmm6, [edi+(6*N-1)*4] \ + __asm shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) \ + __asm mulps xmm6, xmm5 \ + __asm addps xmm7, xmm6 \ + __asm movlps [eax+(row*6+4)*4], xmm7 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 1, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW( 1, 0 ) + + return; + } + case 2: { // 6x2 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 2, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 2, 1 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 2, 0 ) + + return; + } + case 3: { // 6x3 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 3, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 3, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 3, 2 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 3, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW( 3, 2 ) + + return; + } + case 4: { // 6x4 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 2 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 4, 3 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 4, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 4, 1 ) + + return; + } + case 5: { // 6x5 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 2 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 3 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 5, 4 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 5, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 5, 1 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW( 5, 4 ) + + return; + } + case 6: { // 6x6 * 6x6 + + MUL_6xN_6x6_FIRST4COLUMNS_INIT + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 0 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 1 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 2 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 3 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 4 ) + MUL_6xN_6x6_FIRST4COLUMNS_ROW( 6, 5 ) + MUL_6xN_6x6_LAST2COLUMNS_INIT + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 6, 0 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 6, 1 ) + MUL_6xN_6x6_LAST2COLUMNS_ROW2( 6, 2 ) + + return; + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l] + m1Ptr[5*k] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + default: + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m1Ptr = m1.ToFloatPtr() + i; + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumRows(); n++ ) { + m1Ptr += k; + m2Ptr += l; + sum += m1Ptr[0] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + } + break; + } +} + +/* +============ +idSIMD_SSE::MatX_LowerTriangularSolve + + solves x in Lx = b for the n * n sub-matrix of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE::MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip ) { + int nc; + const float *lptr; + + if ( skip >= n ) { + return; + } + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + #define NSKIP( n, s ) ((n<<3)|(s&7)) + switch( NSKIP( n, skip ) ) { + case NSKIP( 1, 0 ): x[0] = b[0]; + return; + case NSKIP( 2, 0 ): x[0] = b[0]; + case NSKIP( 2, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + return; + case NSKIP( 3, 0 ): x[0] = b[0]; + case NSKIP( 3, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 3, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + return; + case NSKIP( 4, 0 ): x[0] = b[0]; + case NSKIP( 4, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 4, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 4, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + return; + case NSKIP( 5, 0 ): x[0] = b[0]; + case NSKIP( 5, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 5, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 5, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 5, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + return; + case NSKIP( 6, 0 ): x[0] = b[0]; + case NSKIP( 6, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 6, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 6, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 6, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 6, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + return; + case NSKIP( 7, 0 ): x[0] = b[0]; + case NSKIP( 7, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 7, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 7, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 7, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 7, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + case NSKIP( 7, 6 ): x[6] = b[6] - lptr[6*nc+0] * x[0] - lptr[6*nc+1] * x[1] - lptr[6*nc+2] * x[2] - lptr[6*nc+3] * x[3] - lptr[6*nc+4] * x[4] - lptr[6*nc+5] * x[5]; + return; + } + return; + } + + // process first 4 rows + switch( skip ) { + case 0: x[0] = b[0]; + case 1: x[1] = b[1] - lptr[1*nc+0] * x[0]; + case 2: x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case 3: x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + skip = 4; + } + + lptr = L[skip]; + + // this code assumes n > 4 + __asm { + push ebx + mov eax, skip // eax = i + shl eax, 2 // eax = i*4 + mov edx, n // edx = n + shl edx, 2 // edx = n*4 + mov esi, x // esi = x + mov edi, lptr // edi = lptr + add esi, eax + add edi, eax + mov ebx, b // ebx = b + + // check for aligned memory + mov ecx, nc + shl ecx, 2 + or ecx, esi + or ecx, edi + and ecx, 15 + jnz loopurow + + // aligned + looprow: + mov ecx, eax + neg ecx + movaps xmm0, [esi+ecx] + mulps xmm0, [edi+ecx] + add ecx, 12*4 + jg donedot8 + dot8: + movaps xmm1, [esi+ecx-(8*4)] + mulps xmm1, [edi+ecx-(8*4)] + addps xmm0, xmm1 + movaps xmm3, [esi+ecx-(4*4)] + mulps xmm3, [edi+ecx-(4*4)] + addps xmm0, xmm3 + add ecx, 8*4 + jle dot8 + donedot8: + sub ecx, 4*4 + jg donedot4 + //dot4: + movaps xmm1, [esi+ecx-(4*4)] + mulps xmm1, [edi+ecx-(4*4)] + addps xmm0, xmm1 + add ecx, 4*4 + donedot4: + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + sub ecx, 4*4 + jz dot0 + add ecx, 4 + jz dot1 + add ecx, 4 + jz dot2 + //dot3: + movss xmm1, [esi-(3*4)] + mulss xmm1, [edi-(3*4)] + addss xmm0, xmm1 + dot2: + movss xmm3, [esi-(2*4)] + mulss xmm3, [edi-(2*4)] + addss xmm0, xmm3 + dot1: + movss xmm5, [esi-(1*4)] + mulss xmm5, [edi-(1*4)] + addss xmm0, xmm5 + dot0: + movss xmm1, [ebx+eax] + subss xmm1, xmm0 + movss [esi], xmm1 + add eax, 4 + cmp eax, edx + jge done + add esi, 4 + mov ecx, nc + shl ecx, 2 + add edi, ecx + add edi, 4 + jmp looprow + + // unaligned + loopurow: + mov ecx, eax + neg ecx + movups xmm0, [esi+ecx] + movups xmm1, [edi+ecx] + mulps xmm0, xmm1 + add ecx, 12*4 + jg doneudot8 + udot8: + movups xmm1, [esi+ecx-(8*4)] + movups xmm2, [edi+ecx-(8*4)] + mulps xmm1, xmm2 + addps xmm0, xmm1 + movups xmm3, [esi+ecx-(4*4)] + movups xmm4, [edi+ecx-(4*4)] + mulps xmm3, xmm4 + addps xmm0, xmm3 + add ecx, 8*4 + jle udot8 + doneudot8: + sub ecx, 4*4 + jg doneudot4 + //udot4: + movups xmm1, [esi+ecx-(4*4)] + movups xmm2, [edi+ecx-(4*4)] + mulps xmm1, xmm2 + addps xmm0, xmm1 + add ecx, 4*4 + doneudot4: + movhlps xmm1, xmm0 + addps xmm0, xmm1 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm1 + sub ecx, 4*4 + jz udot0 + add ecx, 4 + jz udot1 + add ecx, 4 + jz udot2 + //udot3: + movss xmm1, [esi-(3*4)] + movss xmm2, [edi-(3*4)] + mulss xmm1, xmm2 + addss xmm0, xmm1 + udot2: + movss xmm3, [esi-(2*4)] + movss xmm4, [edi-(2*4)] + mulss xmm3, xmm4 + addss xmm0, xmm3 + udot1: + movss xmm5, [esi-(1*4)] + movss xmm6, [edi-(1*4)] + mulss xmm5, xmm6 + addss xmm0, xmm5 + udot0: + movss xmm1, [ebx+eax] + subss xmm1, xmm0 + movss [esi], xmm1 + add eax, 4 + cmp eax, edx + jge done + add esi, 4 + mov ecx, nc + shl ecx, 2 + add edi, ecx + add edi, 4 + jmp loopurow + done: + pop ebx + } +} + +/* +============ +idSIMD_SSE::MatX_LowerTriangularSolveTranspose + + solves x in L'x = b for the n * n sub-matrix of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE::MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) { + int nc; + const float *lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x[1] = b[1]; + x[0] = b[0] - lptr[1*nc+0] * x[1]; + return; + case 3: + x[2] = b[2]; + x[1] = b[1] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 4: + x[3] = b[3]; + x[2] = b[2] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 5: + x[4] = b[4]; + x[3] = b[3] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 6: + x[5] = b[5]; + x[4] = b[4] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 7: + x[6] = b[6]; + x[5] = b[5] - lptr[6*nc+5] * x[6]; + x[4] = b[4] - lptr[6*nc+4] * x[6] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[6*nc+3] * x[6] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[6*nc+2] * x[6] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[6*nc+1] * x[6] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[6*nc+0] * x[6] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + } + return; + } + +#if 1 + + int i, j, m; + float *xptr; + double s0; + + // if the number of columns is not a multiple of 2 we're screwed for alignment. + // however, if the number of columns is a multiple of 2 but the number of to be + // processed rows is not a multiple of 2 we can still run 8 byte aligned + m = n; + if ( m & 1 ) { + + m--; + x[m] = b[m]; + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows_1: + movlps xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + movhps xmm0, [ebx+eax*4-8] // load b[i-4], b[i-3] + xor ecx, ecx + sub eax, m + neg eax + jz done4x4_1 + process4x4_1: // process 4x4 blocks + movlps xmm2, [edi+0] + movhps xmm2, [edi+8] + add edi, edx + movss xmm1, [esi+4*ecx+0] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm3, [edi+0] + movhps xmm3, [edi+8] + add edi, edx + mulps xmm1, xmm2 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+0] + movhps xmm4, [edi+8] + add edi, edx + mulps xmm1, xmm3 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+8] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm5, [edi+0] + movhps xmm5, [edi+8] + add edi, edx + mulps xmm1, xmm4 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+12] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add ecx, 4 + cmp ecx, eax + mulps xmm1, xmm5 + subps xmm0, xmm1 + jl process4x4_1 + done4x4_1: // process left over of the 4 rows + movlps xmm2, [edi+0] + movhps xmm2, [edi+8] + movss xmm1, [esi+4*ecx] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm2 + subps xmm0, xmm1 + imul ecx, edx + sub edi, ecx + neg eax + + add eax, m + sub eax, 4 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 2, 2, 2 ) + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 3, 3, 3 ) + sub edi, edx + movss [esi-4], xmm3 // xptr[-1] = s3 + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [edi+8] // lptr[-1*nc+2] * s3 + mulss xmm4, [edi+4] // lptr[-1*nc+1] * s3 + mulss xmm5, [edi+0] // lptr[-1*nc+0] * s3 + subss xmm2, xmm3 + movss [esi-8], xmm2 // xptr[-2] = s2 + movss xmm6, xmm2 + sub edi, edx + subss xmm0, xmm5 + subss xmm1, xmm4 + mulss xmm2, [edi+4] // lptr[-2*nc+1] * s2 + mulss xmm6, [edi+0] // lptr[-2*nc+0] * s2 + subss xmm1, xmm2 + movss [esi-12], xmm1 // xptr[-3] = s1 + subss xmm0, xmm6 + sub edi, edx + cmp eax, 4 + mulss xmm1, [edi+0] // lptr[-3*nc+0] * s1 + subss xmm0, xmm1 + movss [esi-16], xmm0 // xptr[-4] = s0 + jl done4rows_1 + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows_1 + done4rows_1: + pop ebx + } + + } else { + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows: + movlps xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + movhps xmm0, [ebx+eax*4-8] // load b[i-4], b[i-3] + sub eax, m + jz done4x4 + neg eax + xor ecx, ecx + process4x4: // process 4x4 blocks + movlps xmm2, [edi+0] + movhps xmm2, [edi+8] + add edi, edx + movss xmm1, [esi+4*ecx+0] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm3, [edi+0] + movhps xmm3, [edi+8] + add edi, edx + mulps xmm1, xmm2 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm4, [edi+0] + movhps xmm4, [edi+8] + add edi, edx + mulps xmm1, xmm3 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+8] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps xmm5, [edi+0] + movhps xmm5, [edi+8] + add edi, edx + mulps xmm1, xmm4 + subps xmm0, xmm1 + movss xmm1, [esi+4*ecx+12] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add ecx, 4 + cmp ecx, eax + mulps xmm1, xmm5 + subps xmm0, xmm1 + jl process4x4 + imul ecx, edx + sub edi, ecx + neg eax + done4x4: // process left over of the 4 rows + add eax, m + sub eax, 4 + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 2, 2, 2 ) + movaps xmm3, xmm0 + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 3, 3, 3 ) + sub edi, edx + movss [esi-4], xmm3 // xptr[-1] = s3 + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [edi+8] // lptr[-1*nc+2] * s3 + mulss xmm4, [edi+4] // lptr[-1*nc+1] * s3 + mulss xmm5, [edi+0] // lptr[-1*nc+0] * s3 + subss xmm2, xmm3 + movss [esi-8], xmm2 // xptr[-2] = s2 + movss xmm6, xmm2 + sub edi, edx + subss xmm0, xmm5 + subss xmm1, xmm4 + mulss xmm2, [edi+4] // lptr[-2*nc+1] * s2 + mulss xmm6, [edi+0] // lptr[-2*nc+0] * s2 + subss xmm1, xmm2 + movss [esi-12], xmm1 // xptr[-3] = s1 + subss xmm0, xmm6 + sub edi, edx + cmp eax, 4 + mulss xmm1, [edi+0] // lptr[-3*nc+0] * s1 + subss xmm0, xmm1 + movss [esi-16], xmm0 // xptr[-4] = s0 + jl done4rows + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows + done4rows: + pop ebx + } + } + + // process left over rows + for ( i = (m&3)-1; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } + +#else + + int i, j, m; + double s0, s1, s2, s3, t; + const float *lptr2; + float *xptr, *xptr2; + + m = n; + if ( m & 1 ) { + + m--; + x[m] = b[m]; + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + // process 4 rows at a time + for ( i = m; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + xptr2 = xptr; // x + i; + lptr2 = lptr; // ptr = L[i] + i - 4; + for ( j = 0; j < m-i; j += 4 ) { + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + } + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + // process left over of the 4 rows + lptr -= nc; + s0 -= lptr[0] * s3; + s1 -= lptr[1] * s3; + s2 -= lptr[2] * s3; + lptr -= nc; + s0 -= lptr[0] * s2; + s1 -= lptr[1] * s2; + lptr -= nc; + s0 -= lptr[0] * s1; + lptr -= nc; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4; + xptr -= 4; + } + + } else { + + lptr = L.ToFloatPtr() + m * nc + m - 4; + xptr = x + m; + // process 4 rows at a time + for ( i = m; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + xptr2 = xptr; // x + i; + lptr2 = lptr; // ptr = L[i] + i - 4; + for ( j = 0; j < m-i; j += 4 ) { + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + t = xptr2[0]; + s0 -= lptr2[0] * t; + s1 -= lptr2[1] * t; + s2 -= lptr2[2] * t; + s3 -= lptr2[3] * t; + lptr2 += nc; + xptr2++; + } + // process left over of the 4 rows + lptr -= nc; + s0 -= lptr[0] * s3; + s1 -= lptr[1] * s3; + s2 -= lptr[2] * s3; + lptr -= nc; + s0 -= lptr[0] * s2; + s1 -= lptr[1] * s2; + lptr -= nc; + s0 -= lptr[0] * s1; + lptr -= nc; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4; + xptr -= 4; + } + } + // process left over rows + for ( i--; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < m; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } + +#endif +} + +/* +============ +idSIMD_SSE::MatX_LDLTFactor + + in-place factorization LDL' of the n * n sub-matrix of mat + the reciprocal of the diagonal elements are stored in invDiag + currently assumes the number of columns of mat is a multiple of 4 +============ +*/ +bool VPCALL idSIMD_SSE::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { +#if 1 + + int j, nc; + float *v, *diag, *invDiagPtr, *mptr; + double s0, s1, s2, sum, d; + + v = (float *) _alloca16( n * sizeof( float ) ); + diag = (float *) _alloca16( n * sizeof( float ) ); + invDiagPtr = invDiag.ToFloatPtr(); + + nc = mat.GetNumColumns(); + + assert( ( nc & 3 ) == 0 ); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiagPtr[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiagPtr[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiagPtr[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiagPtr[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + int ncf = nc * sizeof( float ); + mptr = mat[0]; + + __asm { + xorps xmm2, xmm2 + xorps xmm3, xmm3 + xorps xmm4, xmm4 + + push ebx + mov ebx, 4 + + loopRow: + cmp ebx, n + jge done + + mov ecx, ebx // esi = i + shl ecx, 2 // esi = i * 4 + mov edx, diag // edx = diag + add edx, ecx // edx = &diag[i] + mov edi, ebx // edi = i + imul edi, ncf // edi = i * nc * sizeof( float ) + add edi, mptr // edi = mat[i] + add edi, ecx // edi = &mat[i][i] + mov esi, v // ecx = v + add esi, ecx // ecx = &v[i] + mov eax, invDiagPtr // eax = invDiagPtr + add eax, ecx // eax = &invDiagPtr[i] + neg ecx + + movaps xmm0, [edx+ecx] + mulps xmm0, [edi+ecx] + movaps [esi+ecx], xmm0 + mulps xmm0, [edi+ecx] + add ecx, 12*4 + jg doneDot8 + dot8: + movaps xmm1, [edx+ecx-(8*4)] + mulps xmm1, [edi+ecx-(8*4)] + movaps [esi+ecx-(8*4)], xmm1 + mulps xmm1, [edi+ecx-(8*4)] + addps xmm0, xmm1 + movaps xmm2, [edx+ecx-(4*4)] + mulps xmm2, [edi+ecx-(4*4)] + movaps [esi+ecx-(4*4)], xmm2 + mulps xmm2, [edi+ecx-(4*4)] + addps xmm0, xmm2 + add ecx, 8*4 + jle dot8 + doneDot8: + sub ecx, 4*4 + jg doneDot4 + movaps xmm1, [edx+ecx-(4*4)] + mulps xmm1, [edi+ecx-(4*4)] + movaps [esi+ecx-(4*4)], xmm1 + mulps xmm1, [edi+ecx-(4*4)] + addps xmm0, xmm1 + add ecx, 4*4 + doneDot4: + sub ecx, 2*4 + jg doneDot2 + movlps xmm3, [edx+ecx-(2*4)] + movlps xmm4, [edi+ecx-(2*4)] + mulps xmm3, xmm4 + movlps [esi+ecx-(2*4)], xmm3 + mulps xmm3, xmm4 + addps xmm0, xmm3 + add ecx, 2*4 + doneDot2: + sub ecx, 1*4 + jg doneDot1 + movss xmm3, [edx+ecx-(1*4)] + movss xmm4, [edi+ecx-(1*4)] + mulss xmm3, xmm4 + movss [esi+ecx-(1*4)], xmm3 + mulss xmm3, xmm4 + addss xmm0, xmm3 + doneDot1: + movhlps xmm2, xmm0 + addps xmm0, xmm2 + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm2 + movss xmm1, [edi] + subss xmm1, xmm0 + movss [edi], xmm1 // mptr[i] = sum; + movss [edx], xmm1 // diag[i] = sum; + + // if ( sum == 0.0f ) return false; + movaps xmm2, xmm1 + cmpeqss xmm2, SIMD_SP_zero + andps xmm2, SIMD_SP_tiny + orps xmm1, xmm2 + + rcpss xmm7, xmm1 + mulss xmm1, xmm7 + mulss xmm1, xmm7 + addss xmm7, xmm7 + subss xmm7, xmm1 + movss [eax], xmm7 // invDiagPtr[i] = 1.0f / sum; + + mov edx, n // edx = n + sub edx, ebx // edx = n - i + dec edx // edx = n - i - 1 + jle doneSubRow // if ( i + 1 >= n ) return true; + + mov eax, ebx // eax = i + shl eax, 2 // eax = i * 4 + neg eax + + loopSubRow: + add edi, ncf + mov ecx, eax + movaps xmm0, [esi+ecx] + mulps xmm0, [edi+ecx] + add ecx, 12*4 + jg doneSubDot8 + subDot8: + movaps xmm1, [esi+ecx-(8*4)] + mulps xmm1, [edi+ecx-(8*4)] + addps xmm0, xmm1 + movaps xmm2, [esi+ecx-(4*4)] + mulps xmm2, [edi+ecx-(4*4)] + addps xmm0, xmm2 + add ecx, 8*4 + jle subDot8 + doneSubDot8: + sub ecx, 4*4 + jg doneSubDot4 + movaps xmm1, [esi+ecx-(4*4)] + mulps xmm1, [edi+ecx-(4*4)] + addps xmm0, xmm1 + add ecx, 4*4 + doneSubDot4: + sub ecx, 2*4 + jg doneSubDot2 + movlps xmm3, [esi+ecx-(2*4)] + movlps xmm4, [edi+ecx-(2*4)] + mulps xmm3, xmm4 + addps xmm0, xmm3 + add ecx, 2*4 + doneSubDot2: + sub ecx, 1*4 + jg doneSubDot1 + movss xmm3, [esi+ecx-(1*4)] + movss xmm4, [edi+ecx-(1*4)] + mulss xmm3, xmm4 + addss xmm0, xmm3 + doneSubDot1: + movhlps xmm2, xmm0 + addps xmm0, xmm2 + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 0 ) + addss xmm0, xmm2 + movss xmm1, [edi] + subss xmm1, xmm0 + mulss xmm1, xmm7 + movss [edi], xmm1 + dec edx + jg loopSubRow + doneSubRow: + inc ebx + jmp loopRow + done: + pop ebx + } + + return true; + +#else + + int i, j, k, nc; + float *v, *diag, *mptr; + double s0, s1, s2, s3, sum, d; + + v = (float *) _alloca16( n * sizeof( float ) ); + diag = (float *) _alloca16( n * sizeof( float ) ); + + nc = mat.GetNumColumns(); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiag[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiag[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiag[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiag[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + for ( i = 4; i < n; i++ ) { + + mptr = mat[i]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + v[3] = diag[3] * mptr[3]; s3 = v[3] * mptr[3]; + for ( k = 4; k < i-3; k += 4 ) { + v[k+0] = diag[k+0] * mptr[k+0]; s0 += v[k+0] * mptr[k+0]; + v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + v[k+2] = diag[k+2] * mptr[k+2]; s2 += v[k+2] * mptr[k+2]; + v[k+3] = diag[k+3] * mptr[k+3]; s3 += v[k+3] * mptr[k+3]; + } + switch( i - k ) { + case 3: v[k+2] = diag[k+2] * mptr[k+2]; s0 += v[k+2] * mptr[k+2]; + case 2: v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + case 1: v[k+0] = diag[k+0] * mptr[k+0]; s2 += v[k+0] * mptr[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum = mptr[i] - sum; + + if ( sum == 0.0f ) { + return false; + } + + mat[i][i] = sum; + diag[i] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + return true; + } + + mptr = mat[i+1]; + for ( j = i+1; j < n; j++ ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + } + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + mptr[i] = ( mptr[i] - sum ) * d; + mptr += nc; + } + } + + return true; + +#endif +} + +/* +============ +idSIMD_SSE::BlendJoints +============ +*/ +void VPCALL idSIMD_SSE::BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( blendJoints ); + assert_16_byte_aligned( JOINTQUAT_Q_OFFSET ); + assert_16_byte_aligned( JOINTQUAT_T_OFFSET ); + + ALIGN16( float jointQuat0[4]; ) + ALIGN16( float jointQuat1[4]; ) + ALIGN16( float jointQuat2[4]; ) + ALIGN16( float jointQuat3[4]; ) + ALIGN16( float blendQuat0[4]; ) + ALIGN16( float blendQuat1[4]; ) + ALIGN16( float blendQuat2[4]; ) + ALIGN16( float blendQuat3[4]; ) + int a0, a1, a2, a3; + + __asm { + movss xmm7, lerp + cmpnless xmm7, SIMD_SP_zero + movmskps ecx, xmm7 + test ecx, 1 + jz done1 + + mov eax, numJoints + shl eax, 2 + mov esi, joints + mov edi, blendJoints + mov edx, index + + add edx, eax + neg eax + jz done1 + + movss xmm7, lerp + cmpnltss xmm7, SIMD_SP_one + movmskps ecx, xmm7 + test ecx, 1 + jz lerpJoints + + loopCopy: + mov ecx, [edx+eax] + shl ecx, JOINTQUAT_SIZE_SHIFT + + add eax, 1*4 + + movaps xmm0, [edi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm1, [edi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_Q_OFFSET], xmm0 + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm1 + + jl loopCopy + + jmp done1 + + lerpJoints: + add eax, 4*4 + jge done4 + + loopJoint4: + movss xmm3, lerp + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov ecx, [edx+eax-4*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a0, ecx + + // lerp first translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load first quaternions + movaps xmm0, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm4, [edi+ecx+JOINTQUAT_Q_OFFSET] + + mov ecx, [edx+eax-3*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a1, ecx + + // lerp second translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load second quaternions + movaps xmm1, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm5, [edi+ecx+JOINTQUAT_Q_OFFSET] + + mov ecx, [edx+eax-2*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a2, ecx + + // lerp third translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load third quaternions + movaps xmm2, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm6, [edi+ecx+JOINTQUAT_Q_OFFSET] + + mov ecx, [edx+eax-1*4] + shl ecx, JOINTQUAT_SIZE_SHIFT + mov a3, ecx + + // lerp fourth translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load fourth quaternions + movaps xmm3, [esi+ecx+JOINTQUAT_Q_OFFSET] + + TRANSPOSE_4x4( xmm0, xmm1, xmm2, xmm3, xmm7 ) + + movaps jointQuat0, xmm0 + movaps jointQuat1, xmm1 + movaps jointQuat2, xmm2 + movaps jointQuat3, xmm3 + + movaps xmm7, [edi+ecx+JOINTQUAT_Q_OFFSET] + + TRANSPOSE_4x4( xmm4, xmm5, xmm6, xmm7, xmm3 ) + + movaps blendQuat0, xmm4 + movaps blendQuat1, xmm5 + movaps blendQuat2, xmm6 + movaps blendQuat3, xmm7 + + // lerp quaternions + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + mulps xmm2, xmm6 + addps xmm0, xmm2 + movaps xmm3, jointQuat3 + mulps xmm3, blendQuat3 + addps xmm0, xmm3 // xmm0 = cosom + + movaps xmm1, xmm0 + movaps xmm2, xmm0 + andps xmm1, SIMD_SP_signBit // xmm1 = signBit + xorps xmm0, xmm1 + mulps xmm2, xmm2 + + xorps xmm4, xmm4 + movaps xmm3, SIMD_SP_one + subps xmm3, xmm2 // xmm3 = scale0 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 + + movaps xmm2, xmm3 + rsqrtps xmm4, xmm2 + mulps xmm2, xmm4 + mulps xmm2, xmm4 + subps xmm2, SIMD_SP_rsqrt_c0 + mulps xmm4, SIMD_SP_rsqrt_c1 + mulps xmm2, xmm4 + mulps xmm3, xmm2 // xmm3 = sqrt( scale0 ) + + // omega0 = atan2( xmm3, xmm0 ) + movaps xmm4, xmm0 + minps xmm0, xmm3 + maxps xmm3, xmm4 + cmpeqps xmm4, xmm0 + + rcpps xmm5, xmm3 + mulps xmm3, xmm5 + mulps xmm3, xmm5 + addps xmm5, xmm5 + subps xmm5, xmm3 // xmm5 = 1 / y or 1 / x + mulps xmm0, xmm5 // xmm0 = x / y or y / x + movaps xmm3, xmm4 + andps xmm3, SIMD_SP_signBit + xorps xmm0, xmm3 // xmm0 = -x / y or y / x + andps xmm4, SIMD_SP_halfPI // xmm4 = HALF_PI or 0.0f + movaps xmm3, xmm0 + mulps xmm3, xmm3 // xmm3 = s + movaps xmm5, SIMD_SP_atan_c0 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c1 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c2 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c3 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c4 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c5 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c6 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_atan_c7 + mulps xmm5, xmm3 + addps xmm5, SIMD_SP_one + mulps xmm5, xmm0 + addps xmm5, xmm4 // xmm5 = omega0 + + movss xmm6, lerp // xmm6 = lerp + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm5 // xmm6 = omega1 + subps xmm5, xmm6 // xmm5 = omega0 + + // scale0 = sin( xmm5 ) * xmm2 + // scale1 = sin( xmm6 ) * xmm2 + movaps xmm3, xmm5 + movaps xmm7, xmm6 + mulps xmm3, xmm3 + mulps xmm7, xmm7 + movaps xmm4, SIMD_SP_sin_c0 + movaps xmm0, SIMD_SP_sin_c0 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c1 + addps xmm0, SIMD_SP_sin_c1 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c2 + addps xmm0, SIMD_SP_sin_c2 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c3 + addps xmm0, SIMD_SP_sin_c3 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_sin_c4 + addps xmm0, SIMD_SP_sin_c4 + mulps xmm4, xmm3 + mulps xmm0, xmm7 + addps xmm4, SIMD_SP_one + addps xmm0, SIMD_SP_one + mulps xmm5, xmm4 + mulps xmm6, xmm0 + mulps xmm5, xmm2 // xmm5 = scale0 + mulps xmm6, xmm2 // xmm6 = scale1 + + xorps xmm6, xmm1 + + movaps xmm0, jointQuat0 + mulps xmm0, xmm5 + movaps xmm1, blendQuat0 + mulps xmm1, xmm6 + addps xmm0, xmm1 + + movaps xmm1, jointQuat1 + mulps xmm1, xmm5 + movaps xmm2, blendQuat1 + mulps xmm2, xmm6 + addps xmm1, xmm2 + + movaps xmm2, jointQuat2 + mulps xmm2, xmm5 + movaps xmm3, blendQuat2 + mulps xmm3, xmm6 + addps xmm2, xmm3 + + movaps xmm3, jointQuat3 + mulps xmm3, xmm5 + movaps xmm4, blendQuat3 + mulps xmm4, xmm6 + addps xmm3, xmm4 + + add eax, 4*4 + + // transpose xmm0, xmm1, xmm2, xmm3 to memory + movaps xmm7, xmm0 + movaps xmm6, xmm2 + + unpcklps xmm0, xmm1 + unpcklps xmm2, xmm3 + + mov ecx, a0 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm0 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm2 + + mov ecx, a1 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm0 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm2 + + unpckhps xmm7, xmm1 + unpckhps xmm6, xmm3 + + mov ecx, a2 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm7 + movlps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm6 + + mov ecx, a3 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+0], xmm7 + movhps [esi+ecx+JOINTQUAT_Q_OFFSET+8], xmm6 + + jle loopJoint4 + + done4: + sub eax, 4*4 + jz done1 + + loopJoint1: + movss xmm3, lerp + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov ecx, [edx+eax] + shl ecx, JOINTQUAT_SIZE_SHIFT + + // lerp first translations + movaps xmm7, [edi+ecx+JOINTQUAT_T_OFFSET] + subps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + mulps xmm7, xmm3 + addps xmm7, [esi+ecx+JOINTQUAT_T_OFFSET] + movaps [esi+ecx+JOINTQUAT_T_OFFSET], xmm7 + + // load first quaternions + movaps xmm0, [esi+ecx+JOINTQUAT_Q_OFFSET] + movaps xmm1, [edi+ecx+JOINTQUAT_Q_OFFSET] + + movaps jointQuat0, xmm0 + movaps blendQuat0, xmm1 + + // lerp quaternions + mulps xmm1, xmm0 + movhlps xmm0, xmm1 + addps xmm1, xmm0 + movaps xmm0, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 0, 2, 3 ) + addss xmm0, xmm1 // xmm0 = cosom + + movss xmm1, xmm0 + movss xmm2, xmm0 + andps xmm1, SIMD_SP_signBit // xmm1 = signBit + xorps xmm0, xmm1 + mulss xmm2, xmm2 + + xorps xmm4, xmm4 + movss xmm3, SIMD_SP_one + subss xmm3, xmm2 // xmm3 = scale0 + cmpeqss xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 + + movss xmm2, xmm3 + rsqrtss xmm4, xmm2 + mulss xmm2, xmm4 + mulss xmm2, xmm4 + subss xmm2, SIMD_SP_rsqrt_c0 + mulss xmm4, SIMD_SP_rsqrt_c1 + mulss xmm2, xmm4 + mulss xmm3, xmm2 // xmm3 = sqrt( scale0 ) + + // omega0 = atan2( xmm3, xmm0 ) + movss xmm4, xmm0 + minss xmm0, xmm3 + maxss xmm3, xmm4 + cmpeqss xmm4, xmm0 + + rcpss xmm5, xmm3 + mulss xmm3, xmm5 + mulss xmm3, xmm5 + addss xmm5, xmm5 + subss xmm5, xmm3 // xmm5 = 1 / y or 1 / x + mulss xmm0, xmm5 // xmm0 = x / y or y / x + movss xmm3, xmm4 + andps xmm3, SIMD_SP_signBit + xorps xmm0, xmm3 // xmm0 = -x / y or y / x + andps xmm4, SIMD_SP_halfPI // xmm4 = HALF_PI or 0.0f + movss xmm3, xmm0 + mulss xmm3, xmm3 // xmm3 = s + movss xmm5, SIMD_SP_atan_c0 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c1 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c2 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c3 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c4 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c5 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c6 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_atan_c7 + mulss xmm5, xmm3 + addss xmm5, SIMD_SP_one + mulss xmm5, xmm0 + addss xmm5, xmm4 // xmm5 = omega0 + + movss xmm6, lerp // xmm6 = lerp + mulss xmm6, xmm5 // xmm6 = omega1 + subss xmm5, xmm6 // xmm5 = omega0 + + // scale0 = sin( xmm5 ) * xmm2 + // scale1 = sin( xmm6 ) * xmm2 + movss xmm3, xmm5 + movss xmm7, xmm6 + mulss xmm3, xmm3 + mulss xmm7, xmm7 + movss xmm4, SIMD_SP_sin_c0 + movss xmm0, SIMD_SP_sin_c0 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c1 + addss xmm0, SIMD_SP_sin_c1 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c2 + addss xmm0, SIMD_SP_sin_c2 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c3 + addss xmm0, SIMD_SP_sin_c3 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_sin_c4 + addss xmm0, SIMD_SP_sin_c4 + mulss xmm4, xmm3 + mulss xmm0, xmm7 + addss xmm4, SIMD_SP_one + addss xmm0, SIMD_SP_one + mulss xmm5, xmm4 + mulss xmm6, xmm0 + mulss xmm5, xmm2 // xmm5 = scale0 + mulss xmm6, xmm2 // xmm6 = scale1 + + xorps xmm6, xmm1 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, jointQuat0 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, blendQuat0 + addps xmm5, xmm6 + + movaps [esi+ecx+JOINTQUAT_Q_OFFSET], xmm5 + + add eax, 1*4 + jl loopJoint1 + + done1: + } + +#else + + int i; + + if ( lerp <= 0.0f ) { + return; + } else if ( lerp >= 1.0f ) { + for ( i = 0; i < numJoints; i++ ) { + int j = index[i]; + joints[j] = blendJoints[j]; + } + return; + } + + for ( i = 0; i <= numJoints - 4; i += 4 ) { + ALIGN16( float jointQuat0[4]; ) + ALIGN16( float jointQuat1[4]; ) + ALIGN16( float jointQuat2[4]; ) + ALIGN16( float jointQuat3[4]; ) + ALIGN16( float blendQuat0[4]; ) + ALIGN16( float blendQuat1[4]; ) + ALIGN16( float blendQuat2[4]; ) + ALIGN16( float blendQuat3[4]; ) + + for ( int j = 0; j < 4; j++ ) { + int n = index[i+j]; + + joints[n].t[0] = lerp * ( blendJoints[n].t[0] - joints[n].t[0] ); + joints[n].t[1] = lerp * ( blendJoints[n].t[1] - joints[n].t[1] ); + joints[n].t[2] = lerp * ( blendJoints[n].t[2] - joints[n].t[2] ); + + jointQuat0[j] = joints[n].q[0]; + jointQuat1[j] = joints[n].q[1]; + jointQuat2[j] = joints[n].q[2]; + jointQuat3[j] = joints[n].q[3]; + + blendQuat0[j] = blendJoints[n].q[0]; + blendQuat1[j] = blendJoints[n].q[1]; + blendQuat2[j] = blendJoints[n].q[2]; + blendQuat3[j] = blendJoints[n].q[3]; + } + + jointVert0[0] += lerp * ( blendVert0[0] - jointVert0[0] ); + jointVert0[1] += lerp * ( blendVert0[1] - jointVert0[1] ); + jointVert0[2] += lerp * ( blendVert0[2] - jointVert0[2] ); + jointVert0[3] += lerp * ( blendVert0[3] - jointVert0[3] ); + + jointVert1[0] += lerp * ( blendVert1[0] - jointVert1[0] ); + jointVert1[1] += lerp * ( blendVert1[1] - jointVert1[1] ); + jointVert1[2] += lerp * ( blendVert1[2] - jointVert1[2] ); + jointVert1[3] += lerp * ( blendVert1[3] - jointVert1[3] ); + + jointVert2[0] += lerp * ( blendVert2[0] - jointVert2[0] ); + jointVert2[1] += lerp * ( blendVert2[1] - jointVert2[1] ); + jointVert2[2] += lerp * ( blendVert2[2] - jointVert2[2] ); + jointVert2[3] += lerp * ( blendVert2[3] - jointVert2[3] ); + + ALIGN16( float cosom[4]; ) + ALIGN16( float sinom[4]; ) + ALIGN16( float omega0[4]; ) + ALIGN16( float omega1[4]; ) + ALIGN16( float scale0[4]; ) + ALIGN16( float scale1[4]; ) + ALIGN16( unsigned long signBit[4]; ) + + cosom[0] = jointQuat0[0] * blendQuat0[0]; + cosom[1] = jointQuat0[1] * blendQuat0[1]; + cosom[2] = jointQuat0[2] * blendQuat0[2]; + cosom[3] = jointQuat0[3] * blendQuat0[3]; + + cosom[0] += jointQuat1[0] * blendQuat1[0]; + cosom[1] += jointQuat1[1] * blendQuat1[1]; + cosom[2] += jointQuat1[2] * blendQuat1[2]; + cosom[3] += jointQuat1[3] * blendQuat1[3]; + + cosom[0] += jointQuat2[0] * blendQuat2[0]; + cosom[1] += jointQuat2[1] * blendQuat2[1]; + cosom[2] += jointQuat2[2] * blendQuat2[2]; + cosom[3] += jointQuat2[3] * blendQuat2[3]; + + cosom[0] += jointQuat3[0] * blendQuat3[0]; + cosom[1] += jointQuat3[1] * blendQuat3[1]; + cosom[2] += jointQuat3[2] * blendQuat3[2]; + cosom[3] += jointQuat3[3] * blendQuat3[3]; + + signBit[0] = (*(unsigned long *)&cosom[0]) & ( 1 << 31 ); + signBit[1] = (*(unsigned long *)&cosom[1]) & ( 1 << 31 ); + signBit[2] = (*(unsigned long *)&cosom[2]) & ( 1 << 31 ); + signBit[3] = (*(unsigned long *)&cosom[3]) & ( 1 << 31 ); + + (*(unsigned long *)&cosom[0]) ^= signBit[0]; + (*(unsigned long *)&cosom[1]) ^= signBit[1]; + (*(unsigned long *)&cosom[2]) ^= signBit[2]; + (*(unsigned long *)&cosom[3]) ^= signBit[3]; + + scale0[0] = 1.0f - cosom[0] * cosom[0]; + scale0[1] = 1.0f - cosom[1] * cosom[1]; + scale0[2] = 1.0f - cosom[2] * cosom[2]; + scale0[3] = 1.0f - cosom[3] * cosom[3]; + + scale0[0] = ( scale0[0] <= 0.0f ) ? SIMD_SP_tiny[0] : scale0[0]; + scale0[1] = ( scale0[1] <= 0.0f ) ? SIMD_SP_tiny[1] : scale0[1]; + scale0[2] = ( scale0[2] <= 0.0f ) ? SIMD_SP_tiny[2] : scale0[2]; + scale0[3] = ( scale0[3] <= 0.0f ) ? SIMD_SP_tiny[3] : scale0[3]; + + sinom[0] = SSE_ReciprocalSqrt( scale0[0] ); + sinom[1] = SSE_ReciprocalSqrt( scale0[1] ); + sinom[2] = SSE_ReciprocalSqrt( scale0[2] ); + sinom[3] = SSE_ReciprocalSqrt( scale0[3] ); + + scale0[0] *= sinom[0]; + scale0[1] *= sinom[1]; + scale0[2] *= sinom[2]; + scale0[3] *= sinom[3]; + + // NOTE: scale0 and cosom are always positive + omega0[0] = SSE_ATanPositive( scale0[0], cosom[0] ); + omega0[1] = SSE_ATanPositive( scale0[1], cosom[1] ); + omega0[2] = SSE_ATanPositive( scale0[2], cosom[2] ); + omega0[3] = SSE_ATanPositive( scale0[3], cosom[3] ); + + omega1[0] = lerp * omega0[0]; + omega1[1] = lerp * omega0[1]; + omega1[2] = lerp * omega0[2]; + omega1[3] = lerp * omega0[3]; + + omega0[0] -= omega1[0]; + omega0[1] -= omega1[1]; + omega0[2] -= omega1[2]; + omega0[3] -= omega1[3]; + + // NOTE: omega0 is always in the range [0, PI/2] + scale0[0] = SSE_SinZeroHalfPI( omega0[0] ) * sinom[0]; + scale0[1] = SSE_SinZeroHalfPI( omega0[1] ) * sinom[1]; + scale0[2] = SSE_SinZeroHalfPI( omega0[2] ) * sinom[2]; + scale0[3] = SSE_SinZeroHalfPI( omega0[3] ) * sinom[3]; + + // NOTE: omega1 is always in the range [0, PI/2] + scale1[0] = SSE_SinZeroHalfPI( omega1[0] ) * sinom[0]; + scale1[1] = SSE_SinZeroHalfPI( omega1[1] ) * sinom[1]; + scale1[2] = SSE_SinZeroHalfPI( omega1[2] ) * sinom[2]; + scale1[3] = SSE_SinZeroHalfPI( omega1[3] ) * sinom[3]; + + (*(unsigned long *)&scale1[0]) ^= signBit[0]; + (*(unsigned long *)&scale1[1]) ^= signBit[1]; + (*(unsigned long *)&scale1[2]) ^= signBit[2]; + (*(unsigned long *)&scale1[3]) ^= signBit[3]; + + jointQuat0[0] = scale0[0] * jointQuat0[0] + scale1[0] * blendQuat0[0]; + jointQuat0[1] = scale0[1] * jointQuat0[1] + scale1[1] * blendQuat0[1]; + jointQuat0[2] = scale0[2] * jointQuat0[2] + scale1[2] * blendQuat0[2]; + jointQuat0[3] = scale0[3] * jointQuat0[3] + scale1[3] * blendQuat0[3]; + + jointQuat1[0] = scale0[0] * jointQuat1[0] + scale1[0] * blendQuat1[0]; + jointQuat1[1] = scale0[1] * jointQuat1[1] + scale1[1] * blendQuat1[1]; + jointQuat1[2] = scale0[2] * jointQuat1[2] + scale1[2] * blendQuat1[2]; + jointQuat1[3] = scale0[3] * jointQuat1[3] + scale1[3] * blendQuat1[3]; + + jointQuat2[0] = scale0[0] * jointQuat2[0] + scale1[0] * blendQuat2[0]; + jointQuat2[1] = scale0[1] * jointQuat2[1] + scale1[1] * blendQuat2[1]; + jointQuat2[2] = scale0[2] * jointQuat2[2] + scale1[2] * blendQuat2[2]; + jointQuat2[3] = scale0[3] * jointQuat2[3] + scale1[3] * blendQuat2[3]; + + jointQuat3[0] = scale0[0] * jointQuat3[0] + scale1[0] * blendQuat3[0]; + jointQuat3[1] = scale0[1] * jointQuat3[1] + scale1[1] * blendQuat3[1]; + jointQuat3[2] = scale0[2] * jointQuat3[2] + scale1[2] * blendQuat3[2]; + jointQuat3[3] = scale0[3] * jointQuat3[3] + scale1[3] * blendQuat3[3]; + + for ( int j = 0; j < 4; j++ ) { + int n = index[i+j]; + + joints[n].q[0] = jointQuat0[j]; + joints[n].q[1] = jointQuat1[j]; + joints[n].q[2] = jointQuat2[j]; + joints[n].q[3] = jointQuat3[j]; + } + } + + for ( ; i < numJoints; i++ ) { + int n = index[i]; + + idVec3 &jointVert = joints[n].t; + const idVec3 &blendVert = blendJoints[n].t; + + jointVert[0] += lerp * ( blendVert[0] - jointVert[0] ); + jointVert[1] += lerp * ( blendVert[1] - jointVert[1] ); + jointVert[2] += lerp * ( blendVert[2] - jointVert[2] ); + + idQuat &jointQuat = joints[n].q; + const idQuat &blendQuat = blendJoints[n].q; + + float cosom; + float sinom; + float omega; + float scale0; + float scale1; + unsigned long signBit; + + cosom = jointQuat.x * blendQuat.x + jointQuat.y * blendQuat.y + jointQuat.z * blendQuat.z + jointQuat.w * blendQuat.w; + + signBit = (*(unsigned long *)&cosom) & ( 1 << 31 ); + + (*(unsigned long *)&cosom) ^= signBit; + + scale0 = 1.0f - cosom * cosom; + scale0 = ( scale0 <= 0.0f ) ? SIMD_SP_tiny[0] : scale0; + sinom = SSE_ReciprocalSqrt( scale0 ); + omega = SSE_ATanPositive( scale0 * sinom, cosom ); + scale0 = SSE_SinZeroHalfPI( ( 1.0f - lerp ) * omega ) * sinom; + scale1 = SSE_SinZeroHalfPI( lerp * omega ) * sinom; + + (*(unsigned long *)&scale1) ^= signBit; + + jointQuat.x = scale0 * jointQuat.x + scale1 * blendQuat.x; + jointQuat.y = scale0 * jointQuat.y + scale1 * blendQuat.y; + jointQuat.z = scale0 * jointQuat.z + scale1 * blendQuat.z; + jointQuat.w = scale0 * jointQuat.w + scale1 * blendQuat.w; + } + +#endif +} + +/* +============ +idSIMD_SSE::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_SSE::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + assert_16_byte_aligned( jointQuats ); + + __asm { + mov eax, numJoints + shl eax, JOINTQUAT_SIZE_SHIFT + mov esi, jointQuats + mov edi, jointMats + + add esi, eax + neg eax + jz done + + loopQuat: + movaps xmm0, [esi+eax+JOINTQUAT_Q_OFFSET] // xmm0 = q.x, q.y, q.z, q.w + movaps xmm6, [esi+eax+JOINTQUAT_T_OFFSET] // xmm6 = t.x, t.y, t.z, w + + add edi, JOINTMAT_SIZE + + movaps xmm1, xmm0 // xmm1 = x, y, z, w + addps xmm1, xmm1 // xmm1 = x2, y2, z2, w2 + + add eax, JOINTQUAT_SIZE + + movaps xmm2, xmm0 + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 0, 0, 1 ) // xmm2 = y, x, x, y + movaps xmm3, xmm1 + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 1, 2, 2 ) // xmm3 = y2, y2, z2, z2 + mulps xmm2, xmm3 // xmm2 = yy2, xy2, xz2, yz2 + + movaps xmm4, xmm0 + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 3, 3, 3 ) // xmm4 = z, w, w, w + movaps xmm5, xmm1 + shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 2, 1, 0 ) // xmm5 = z2, z2, y2, x2 + mulps xmm4, xmm5 // xmm4 = zz2, wz2, wy2, wx2 + + mulss xmm0, xmm1 // xmm0 = xx2, y2, z2, w2 + + // calculate the last two elements of the third row + movss xmm7, SIMD_SP_one // xmm7 = 1, 0, 0, 0 + subss xmm7, xmm0 // xmm7 = -xx2+1, 0, 0, 0 + subss xmm7, xmm2 // xmm7 = -xx2-yy2+1, 0, 0, 0 + shufps xmm7, xmm6, R_SHUFFLE_PS( 0, 1, 2, 3 ) // xmm7 = -xx2-yy2+1, 0, t.z, w + + // calcluate first row + xorps xmm2, SIMD_SP_quat2mat_x0 // xmm2 = yy2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x1 // xmm4 = -zz2, wz2, -wy2, -wx2 + addss xmm4, SIMD_SP_one // xmm4 = -zz2+1, wz2, -wy2, -wx2 + movaps xmm3, xmm4 // xmm3 = -zz2+1, wz2, -wy2, -wx2 + subps xmm3, xmm2 // xmm3 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movaps [edi-JOINTMAT_SIZE+0*16+0*4], xmm3 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movss [edi-JOINTMAT_SIZE+0*16+3*4], xmm6 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, t.x + + // calculate second row + movss xmm2, xmm0 // xmm2 = xx2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x2 // xmm4 = -zz2+1, -wz2, wy2, wx2 + subps xmm4, xmm2 // xmm4 = -xx2-zz2+1, xy2-wz2, xz2+wy2, yz2+wx2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) // xmm6 = t.y, t.z, w, t.x + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 0, 3, 2 ) // xmm4 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movaps [edi-JOINTMAT_SIZE+1*16+0*4], xmm4 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movss [edi-JOINTMAT_SIZE+1*16+3*4], xmm6 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, t.y + + // calculate third row + movhlps xmm3, xmm4 // xmm3 = yz2+wx2, xz2+wy2, xz2-wy2, yz2-wx2 + shufps xmm3, xmm7, R_SHUFFLE_PS( 1, 3, 0, 2 ) // xmm3 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + movaps [edi-JOINTMAT_SIZE+2*16+0*4], xmm3 // row2 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + + jl loopQuat + + done: + } + +#else + + for ( int i = 0; i < numJoints; i++ ) { + const float *q = &jointQuats[i].q; + float *m = jointMats[i].mat; + + float x2 = q[0] + q[0]; + float y2 = q[1] + q[1]; + float z2 = q[2] + q[2]; + float w2 = q[3] + q[3]; + + float yy2 = q[1] * y2; + float xy2 = q[0] * y2; + float xz2 = q[0] * z2; + float yz2 = q[1] * z2; + + float zz2 = q[2] * z2; + float wz2 = q[3] * z2; + float wy2 = q[3] * y2; + float wx2 = q[3] * x2; + + float xx2 = q[0] * x2; + + m[0*4+0] = - yy2 - zz2 + 1.0f; + m[0*4+1] = xy2 + wz2; + m[0*4+2] = xz2 - wy2; + m[0*4+3] = q[4]; + + m[1*4+0] = xy2 - wz2; + m[1*4+1] = - xx2 - zz2 + 1.0f; + m[1*4+2] = yz2 + wx2; + m[1*4+3] = q[5]; + + m[2*4+0] = xz2 + wy2; + m[2*4+1] = yz2 - wx2; + m[2*4+2] = - xx2 - yy2 + 1.0f; + m[2*4+3] = q[6]; + } + +#endif +} + +/* +============ +idSIMD_SSE::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_SSE::ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ) { +#if 1 + + ALIGN16( byte shuffle[16]; ) + + __asm { + mov eax, numJoints + mov esi, jointMats + mov edi, jointQuats + and eax, ~3 + jz done4 + imul eax, JOINTMAT_SIZE + add esi, eax + neg eax + + loopMat4: + movss xmm5, [esi+eax+3*JOINTMAT_SIZE+0*16+0*4] + movss xmm6, [esi+eax+3*JOINTMAT_SIZE+1*16+1*4] + movss xmm7, [esi+eax+3*JOINTMAT_SIZE+2*16+2*4] + + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm0, [esi+eax+2*JOINTMAT_SIZE+0*16+0*4] + movss xmm1, [esi+eax+2*JOINTMAT_SIZE+1*16+1*4] + movss xmm2, [esi+eax+2*JOINTMAT_SIZE+2*16+2*4] + + movss xmm5, xmm0 + movss xmm6, xmm1 + movss xmm7, xmm2 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm0, [esi+eax+1*JOINTMAT_SIZE+0*16+0*4] + movss xmm1, [esi+eax+1*JOINTMAT_SIZE+1*16+1*4] + movss xmm2, [esi+eax+1*JOINTMAT_SIZE+2*16+2*4] + + movss xmm5, xmm0 + movss xmm6, xmm1 + movss xmm7, xmm2 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm0, [esi+eax+0*JOINTMAT_SIZE+0*16+0*4] + movss xmm1, [esi+eax+0*JOINTMAT_SIZE+1*16+1*4] + movss xmm2, [esi+eax+0*JOINTMAT_SIZE+2*16+2*4] + + movss xmm5, xmm0 + movss xmm6, xmm1 + movss xmm7, xmm2 + + // ------------------- + + movaps xmm0, xmm5 + addps xmm0, xmm6 + addps xmm0, xmm7 + cmpnltps xmm0, SIMD_SP_zero // xmm0 = m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f + + movaps xmm1, xmm5 + movaps xmm2, xmm5 + cmpnltps xmm1, xmm6 + cmpnltps xmm2, xmm7 + andps xmm2, xmm1 // xmm2 = m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2] + + movaps xmm4, xmm6 + cmpnltps xmm4, xmm7 // xmm3 = m[1 * 4 + 1] > m[2 * 4 + 2] + + movaps xmm1, xmm0 + andnps xmm1, xmm2 + orps xmm2, xmm0 + movaps xmm3, xmm2 + andnps xmm2, xmm4 + orps xmm3, xmm2 + xorps xmm3, SIMD_SP_not + + andps xmm0, SIMD_DW_mat2quatShuffle0 + movaps xmm4, xmm1 + andps xmm4, SIMD_DW_mat2quatShuffle1 + orps xmm0, xmm4 + movaps xmm4, xmm2 + andps xmm4, SIMD_DW_mat2quatShuffle2 + orps xmm0, xmm4 + movaps xmm4, xmm3 + andps xmm4, SIMD_DW_mat2quatShuffle3 + orps xmm4, xmm0 + + movaps shuffle, xmm4 + + movaps xmm0, xmm2 + orps xmm0, xmm3 // xmm0 = xmm2 | xmm3 = s0 + orps xmm2, xmm1 // xmm2 = xmm1 | xmm2 = s2 + orps xmm1, xmm3 // xmm1 = xmm1 | xmm3 = s1 + + andps xmm0, SIMD_SP_signBit + andps xmm1, SIMD_SP_signBit + andps xmm2, SIMD_SP_signBit + + xorps xmm5, xmm0 + xorps xmm6, xmm1 + xorps xmm7, xmm2 + addps xmm5, xmm6 + addps xmm7, SIMD_SP_one + addps xmm5, xmm7 // xmm5 = t + + movaps xmm7, xmm5 // xmm7 = t + rsqrtps xmm6, xmm5 + mulps xmm5, xmm6 + mulps xmm5, xmm6 + subps xmm5, SIMD_SP_rsqrt_c0 + mulps xmm6, SIMD_SP_mat2quat_rsqrt_c1 + mulps xmm6, xmm5 // xmm5 = s + + mulps xmm7, xmm6 // xmm7 = s * t + xorps xmm6, SIMD_SP_signBit // xmm6 = -s + + // ------------------- + + add edi, 4*JOINTQUAT_SIZE + + movzx ecx, byte ptr shuffle[0*4+0] // ecx = k0 + movss [edi+ecx*4-4*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[0*4+1] // edx = k1 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-4*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[0*4+2] // ecx = k2 + movss xmm3, [esi+eax+0*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+0*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-4*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[0*4+3] // edx = k3 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-4*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+0*JOINTMAT_SIZE+0*16+3*4] + mov [edi-4*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+0*JOINTMAT_SIZE+1*16+3*4] + mov [edi-4*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+0*JOINTMAT_SIZE+2*16+3*4] + mov [edi-4*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-4*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movzx ecx, byte ptr shuffle[1*4+0] // ecx = k0 + movss [edi+ecx*4-3*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[1*4+1] // edx = k1 + movss xmm4, [esi+eax+1*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+1*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-3*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[1*4+2] // ecx = k2 + movss xmm3, [esi+eax+1*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+1*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-3*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[1*4+3] // edx = k3 + movss xmm4, [esi+eax+1*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+1*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-3*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+1*JOINTMAT_SIZE+0*16+3*4] + mov [edi-3*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+1*JOINTMAT_SIZE+1*16+3*4] + mov [edi-3*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+1*JOINTMAT_SIZE+2*16+3*4] + mov [edi-3*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-3*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movzx ecx, byte ptr shuffle[2*4+0] // ecx = k0 + movss [edi+ecx*4-2*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[2*4+1] // edx = k1 + movss xmm4, [esi+eax+2*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+2*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-2*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[2*4+2] // ecx = k2 + movss xmm3, [esi+eax+2*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+2*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-2*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[2*4+3] // edx = k3 + movss xmm4, [esi+eax+2*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+2*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-2*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+2*JOINTMAT_SIZE+0*16+3*4] + mov [edi-2*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+2*JOINTMAT_SIZE+1*16+3*4] + mov [edi-2*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+2*JOINTMAT_SIZE+2*16+3*4] + mov [edi-2*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-2*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movzx ecx, byte ptr shuffle[3*4+0] // ecx = k0 + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[3*4+1] // edx = k1 + movss xmm4, [esi+eax+3*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+3*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[3*4+2] // ecx = k2 + movss xmm3, [esi+eax+3*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+3*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[3*4+3] // edx = k3 + movss xmm4, [esi+eax+3*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+3*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+3*JOINTMAT_SIZE+0*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+3*JOINTMAT_SIZE+1*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+3*JOINTMAT_SIZE+2*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-1*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + add eax, 4*JOINTMAT_SIZE + jl loopMat4 + + done4: + mov eax, numJoints + and eax, 3 + jz done1 + imul eax, JOINTMAT_SIZE + add esi, eax + neg eax + + loopMat1: + movss xmm5, [esi+eax+0*JOINTMAT_SIZE+0*16+0*4] + movss xmm6, [esi+eax+0*JOINTMAT_SIZE+1*16+1*4] + movss xmm7, [esi+eax+0*JOINTMAT_SIZE+2*16+2*4] + + // ------------------- + + movaps xmm0, xmm5 + addss xmm0, xmm6 + addss xmm0, xmm7 + cmpnltss xmm0, SIMD_SP_zero // xmm0 = m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f + + movaps xmm1, xmm5 + movaps xmm2, xmm5 + cmpnltss xmm1, xmm6 + cmpnltss xmm2, xmm7 + andps xmm2, xmm1 // xmm2 = m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2] + + movaps xmm4, xmm6 + cmpnltss xmm4, xmm7 // xmm3 = m[1 * 4 + 1] > m[2 * 4 + 2] + + movaps xmm1, xmm0 + andnps xmm1, xmm2 + orps xmm2, xmm0 + movaps xmm3, xmm2 + andnps xmm2, xmm4 + orps xmm3, xmm2 + xorps xmm3, SIMD_SP_not + + andps xmm0, SIMD_DW_mat2quatShuffle0 + movaps xmm4, xmm1 + andps xmm4, SIMD_DW_mat2quatShuffle1 + orps xmm0, xmm4 + movaps xmm4, xmm2 + andps xmm4, SIMD_DW_mat2quatShuffle2 + orps xmm0, xmm4 + movaps xmm4, xmm3 + andps xmm4, SIMD_DW_mat2quatShuffle3 + orps xmm4, xmm0 + + movss shuffle, xmm4 + + movaps xmm0, xmm2 + orps xmm0, xmm3 // xmm0 = xmm2 | xmm3 = s0 + orps xmm2, xmm1 // xmm2 = xmm1 | xmm2 = s2 + orps xmm1, xmm3 // xmm1 = xmm1 | xmm3 = s1 + + andps xmm0, SIMD_SP_signBit + andps xmm1, SIMD_SP_signBit + andps xmm2, SIMD_SP_signBit + + xorps xmm5, xmm0 + xorps xmm6, xmm1 + xorps xmm7, xmm2 + addss xmm5, xmm6 + addss xmm7, SIMD_SP_one + addss xmm5, xmm7 // xmm5 = t + + movss xmm7, xmm5 // xmm7 = t + rsqrtss xmm6, xmm5 + mulss xmm5, xmm6 + mulss xmm5, xmm6 + subss xmm5, SIMD_SP_rsqrt_c0 + mulss xmm6, SIMD_SP_mat2quat_rsqrt_c1 + mulss xmm6, xmm5 // xmm5 = s + + mulss xmm7, xmm6 // xmm7 = s * t + xorps xmm6, SIMD_SP_signBit // xmm6 = -s + + // ------------------- + + movzx ecx, byte ptr shuffle[0] // ecx = k0 + add edi, JOINTQUAT_SIZE + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm7 // q[k0] = s * t; + + movzx edx, byte ptr shuffle[1] // edx = k1 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+0*4] + xorps xmm4, xmm2 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+0*16+1*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + + movzx ecx, byte ptr shuffle[2] // ecx = k2 + movss xmm3, [esi+eax+0*JOINTMAT_SIZE+0*16+2*4] + xorps xmm3, xmm1 + subss xmm3, [esi+eax+0*JOINTMAT_SIZE+2*16+0*4] + mulss xmm3, xmm6 + movss [edi+ecx*4-1*JOINTQUAT_SIZE], xmm3 // q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + + movzx edx, byte ptr shuffle[3] // edx = k3 + movss xmm4, [esi+eax+0*JOINTMAT_SIZE+2*16+1*4] + xorps xmm4, xmm0 + subss xmm4, [esi+eax+0*JOINTMAT_SIZE+1*16+2*4] + mulss xmm4, xmm6 + movss [edi+edx*4-1*JOINTQUAT_SIZE], xmm4 // q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + mov ecx, [esi+eax+0*JOINTMAT_SIZE+0*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+16], ecx // q[4] = m[0 * 4 + 3]; + mov edx, [esi+eax+0*JOINTMAT_SIZE+1*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+20], edx // q[5] = m[1 * 4 + 3]; + mov ecx, [esi+eax+0*JOINTMAT_SIZE+2*16+3*4] + mov [edi-1*JOINTQUAT_SIZE+24], ecx // q[6] = m[2 * 4 + 3]; + mov dword ptr [edi-1*JOINTQUAT_SIZE+28], 0 // q[7] = 0.0f; + + add eax, JOINTMAT_SIZE + jl loopMat1 + + done1: + } + +#else + + compile_time_assert( (UINT_PTR)(&((idJointQuat *)0)->t) == (UINT_PTR)(&((idJointQuat *)0)->q) + (UINT_PTR)sizeof( ((idJointQuat *)0)->q ) ); + + for ( int i = 0; i < numJoints; i++ ) { + float s0, s1, s2; + int k0, k1, k2, k3; + + float *q = jointQuats[i].q.ToFloatPtr(); + const float *m = jointMats[i].ToFloatPtr(); + + if ( m[0 * 4 + 0] + m[1 * 4 + 1] + m[2 * 4 + 2] > 0.0f ) { + + k0 = 3; + k1 = 2; + k2 = 1; + k3 = 0; + s0 = 1.0f; + s1 = 1.0f; + s2 = 1.0f; + + } else if ( m[0 * 4 + 0] > m[1 * 4 + 1] && m[0 * 4 + 0] > m[2 * 4 + 2] ) { + + k0 = 0; + k1 = 1; + k2 = 2; + k3 = 3; + s0 = 1.0f; + s1 = -1.0f; + s2 = -1.0f; + + } else if ( m[1 * 4 + 1] > m[2 * 4 + 2] ) { + + k0 = 1; + k1 = 0; + k2 = 3; + k3 = 2; + s0 = -1.0f; + s1 = 1.0f; + s2 = -1.0f; + + } else { + + k0 = 2; + k1 = 3; + k2 = 0; + k3 = 1; + s0 = -1.0f; + s1 = -1.0f; + s2 = 1.0f; + + } + + float t = s0 * m[0 * 4 + 0] + s1 * m[1 * 4 + 1] + s2 * m[2 * 4 + 2] + 1.0f; + float s = idMath::InvSqrt( t ) * 0.5f; + + q[k0] = s * t; + q[k1] = ( m[0 * 4 + 1] - s2 * m[1 * 4 + 0] ) * s; + q[k2] = ( m[2 * 4 + 0] - s1 * m[0 * 4 + 2] ) * s; + q[k3] = ( m[1 * 4 + 2] - s0 * m[2 * 4 + 1] ) * s; + + q[4] = m[0 * 4 + 3]; + q[5] = m[1 * 4 + 3]; + q[6] = m[2 * 4 + 3]; + q[7] = 0.0f; + } + +#endif +} + +/* +============ +idSIMD_SSE::TransformJoints +============ +*/ +void VPCALL idSIMD_SSE::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov ecx, firstJoint + mov eax, lastJoint + sub eax, ecx + jl done + shl ecx, 2 // ecx = firstJoint * 4 + mov edi, parents + add edi, ecx // edx = &parents[firstJoint] + lea ecx, [ecx+ecx*2] + shl ecx, 2 // ecx = firstJoint * JOINTMAT_SIZE + mov esi, jointMats // esi = jointMats + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + add edi, eax + neg eax + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movaps xmm7, [esi+edx+ 0] + movaps xmm4, xmm7 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm0 + movaps xmm5, xmm7 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm5, xmm1 + addps xmm4, xmm5 + + add ecx, JOINTMAT_SIZE + add eax, 4 + + movaps xmm6, xmm7 + shufps xmm6, xmm6, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm6, xmm2 + addps xmm4, xmm6 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm4, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+ 0], xmm4 + + movaps xmm3, [esi+edx+16] + movaps xmm5, xmm3 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm0 + movaps xmm6, xmm3 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm6, xmm1 + addps xmm5, xmm6 + movaps xmm4, xmm3 + shufps xmm4, xmm4, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm4, xmm2 + addps xmm5, xmm4 + andps xmm3, SIMD_SP_clearFirstThree + addps xmm5, xmm3 + + movaps [esi+ecx-JOINTMAT_SIZE+16], xmm5 + + movaps xmm7, [esi+edx+32] + movaps xmm6, xmm7 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm4, xmm7 + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm6, xmm4 + movaps xmm3, xmm7 + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm3, xmm2 + addps xmm6, xmm3 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm6, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+32], xmm6 + + jle loopJoint + done: + } + +#else + + int i; + + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE::UntransformJoints +============ +*/ +void VPCALL idSIMD_SSE::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov edx, firstJoint + mov eax, lastJoint + mov ecx, eax + sub eax, edx + jl done + mov esi, jointMats // esi = jointMats + lea ecx, [ecx+ecx*2] + shl ecx, 4 // ecx = lastJoint * JOINTMAT_SIZE + shl edx, 2 + mov edi, parents + add edi, edx // edi = &parents[firstJoint] + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movss xmm6, [esi+edx+12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm0, xmm6 + movss xmm7, [esi+edx+28] + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm1, xmm7 + movss xmm3, [esi+edx+44] + shufps xmm3, xmm3, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm2, xmm3 + + sub ecx, JOINTMAT_SIZE + sub eax, 4 + + movss xmm4, [esi+edx+ 0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm0 + movss xmm5, [esi+edx+16] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm1 + addps xmm4, xmm5 + movss xmm6, [esi+edx+32] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm2 + addps xmm4, xmm6 + + movaps [esi+ecx+JOINTMAT_SIZE+ 0], xmm4 + + movss xmm5, [esi+edx+ 4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm0 + movss xmm6, [esi+edx+20] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm1 + addps xmm5, xmm6 + movss xmm7, [esi+edx+36] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm5, xmm7 + + movaps [esi+ecx+JOINTMAT_SIZE+16], xmm5 + + movss xmm6, [esi+edx+ 8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movss xmm7, [esi+edx+24] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movss xmm3, [esi+edx+40] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm2 + addps xmm6, xmm3 + + movaps [esi+ecx+JOINTMAT_SIZE+32], xmm6 + + jge loopJoint + done: + } + +#else + + int i; + + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE::MultiplyJoints +============ +*/ +void VPCALL idSIMD_SSE::MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( result ); + assert_16_byte_aligned( joints1 ); + assert_16_byte_aligned( joints2 ); + + __asm { + + mov eax, numJoints + test eax, eax + jz done + mov ecx, joints1 + mov edx, joints2 + mov edi, result + imul eax, JOINTMAT_SIZE + add ecx, eax + add edx, eax + add edi, eax + neg eax + + loopJoint: + + movaps xmm0, [edx+eax+0] + movaps xmm1, [edx+eax+16] + movaps xmm2, [edx+eax+32] + + movss xmm3, [ecx+eax+0] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + movss xmm4, [ecx+eax+4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + movss xmm5, [ecx+eax+8] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + movss xmm6, [ecx+eax+12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + addps xmm3, xmm6 + + movaps [edi+eax+0], xmm3 + + movss xmm7, [ecx+eax+16] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm0 + movss xmm4, [ecx+eax+20] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm7, xmm4 + movss xmm5, [ecx+eax+24] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm2 + addps xmm7, xmm5 + movss xmm6, [ecx+eax+28] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + addps xmm7, xmm6 + + movaps [edi+eax+16], xmm7 + + movss xmm3, [ecx+eax+32] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + movss xmm4, [ecx+eax+36] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + movss xmm5, [ecx+eax+40] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + movss xmm6, [ecx+eax+44] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + addps xmm3, xmm6 + + movaps [edi+eax+32], xmm3 + + add eax, JOINTMAT_SIZE + jl loopJoint + done: + } + +#else + + int i; + + for ( i = 0; i < numJoints; i++ ) { + idJointMat::Multiply( result[i], joints1[i], joints2[i] ); + } + +#endif +} + +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +/* +============ +idSIMD_SSE::TransformVertsNew +============ +*/ +void VPCALL idSIMD_SSE::TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm2, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + add esi, BASEVECTOR_SIZE + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm5, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + add esi, BASEVECTOR_SIZE + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add eax, DRAWVERT_SIZE + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + movaps xmm5, xmm6 // xmm5 = m6+m8, m7+t2 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm5 = m7+t2, m6+m8 + addss xmm5, xmm6 // xmm5 = m6+m8+m7+t2 + movss xmm6, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm5 + + movaps xmm7, xmm6 + minps xmm7, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm7 + movaps tmpMax, xmm6 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_SSE::TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + movss xmm2, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + movss xmm5, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add esi, 4*BASEVECTOR_SIZE + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + movaps xmm7, xmm6 + shufps xmm7, xmm7, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_SSE::TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + + add esi, 4*BASEVECTOR_SIZE + + movaps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + movaps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + movaps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + add edx, dword ptr [edx+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET] + + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + movaps xmm7, xmm6 // xmm7 = m6+m8, m7+t2 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + movaps xmm7, xmm6 + shufps xmm7, xmm7, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE::TracePointCull +============ +*/ +void VPCALL idSIMD_SSE::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { +#if 1 + + assert( sizeof( idDrawVert ) == DRAWVERT_SIZE ); + assert( (int)&((idDrawVert *)0)->xyz == DRAWVERT_XYZ_OFFSET ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + + mov edi, planes + movlps xmm1, [edi] // xmm1 = 0, 1, X, X + movhps xmm1, [edi+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [edi+8] // xmm3 = 2, 3, X, X + movhps xmm3, [edi+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [edi+32] // xmm4 = 8, 9, X, X + movhps xmm4, [edi+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [edi+40] // xmm5 = 10, 11, X, X + movhps xmm5, [edi+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + movss xmm7, radius + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + xor edx, edx + mov esi, verts + mov edi, cullBits + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert: + movss xmm4, [esi+eax+DRAWVERT_XYZ_OFFSET+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [esi+eax+DRAWVERT_XYZ_OFFSET+4] + mulps xmm4, xmm0 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+8] + mulps xmm5, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + addps xmm4, xmm5 + mulps xmm6, xmm2 + addps xmm4, xmm3 + addps xmm4, xmm6 + movaps xmm5, xmm4 + xorps xmm5, SIMD_SP_signBit + cmpltps xmm4, xmm7 + movmskps ecx, xmm4 + cmpltps xmm5, xmm7 + movmskps ebx, xmm5 + shl cx, 4 + or cl, bl + inc edi + or dl, cl + add eax, DRAWVERT_SIZE + mov byte ptr [edi-1], cl + jl loopVert + + done: + mov esi, totalOr + mov byte ptr [esi], dl + pop ebx + } + +#else + + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; + +#endif +} + +/* +============ +idSIMD_SSE::DecalPointCull +============ +*/ +void VPCALL idSIMD_SSE::DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { +#if 1 + + ALIGN16( float p0[4]; ) + ALIGN16( float p1[4]; ) + ALIGN16( float p2[4]; ) + ALIGN16( float p3[4]; ) + ALIGN16( float p4[4]; ) + ALIGN16( float p5[4]; ) + ALIGN16( float p6[4]; ) + ALIGN16( float p7[4]; ) + + __asm { + mov ecx, planes + movlps xmm1, [ecx] // xmm1 = 0, 1, X, X + movhps xmm1, [ecx+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [ecx+8] // xmm3 = 2, 3, X, X + movhps xmm3, [ecx+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [ecx+32] // xmm4 = 8, 9, X, X + movhps xmm4, [ecx+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [ecx+40] // xmm5 = 10, 11, X, X + movhps xmm5, [ecx+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + + movaps p0, xmm0 + movaps p1, xmm1 + movaps p2, xmm2 + movaps p3, xmm3 + + movlps xmm4, [ecx+64] // xmm4 = p40, p41, X, X + movhps xmm4, [ecx+80] // xmm4 = p40, p41, p50, p51 + movaps xmm5, xmm4 // xmm5 = p40, p41, p50, p51 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm4 = p40, p50, p40, p50 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm5 = p41, p51, p41, p51 + movlps xmm6, [ecx+72] // xmm6 = p42, p43, X, X + movhps xmm6, [ecx+88] // xmm6 = p42, p43, p52, p53 + movaps xmm7, xmm6 // xmm7 = p42, p43, p52, p53 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm6 = p42, p52, p42, p52 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm7 = p43, p53, p43, p53 + + movaps p4, xmm4 + movaps p5, xmm5 + movaps p6, xmm6 + movaps p7, xmm7 + + mov esi, verts + mov edi, cullBits + mov eax, numVerts + and eax, ~1 + jz done2 + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + + loopVert2: + movaps xmm6, p0 + movss xmm0, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+eax+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + movaps xmm6, p0 + movss xmm3, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm3 + movaps xmm7, p1 + movss xmm4, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm5, [esi+eax+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps edx, xmm6 + mov ch, dl + + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, p4 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, p5 + addps xmm0, xmm1 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + add edi, 2 + + mov dh, dl + shl dl, 4 + shl dh, 2 + and edx, (3<<4)|(3<<12) + or ecx, edx + + add eax, 2*DRAWVERT_SIZE + mov word ptr [edi-2], cx + jl loopVert2 + + done2: + + mov eax, numVerts + and eax, 1 + jz done + + movaps xmm6, p0 + movss xmm0, [esi+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + mulps xmm0, p4 + mulps xmm1, p5 + addps xmm0, xmm1 + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + and edx, 3 + shl edx, 4 + or ecx, edx + + mov byte ptr [edi], cl + + done: + } + + +#else + + int i; + + for ( i = 0; i < numVerts; i += 2 ) { + unsigned short bits0, bits1; + float d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11; + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = planes[0][0] * v0[0] + planes[0][1] * v0[1] + planes[0][2] * v0[2] + planes[0][3]; + d1 = planes[1][0] * v0[0] + planes[1][1] * v0[1] + planes[1][2] * v0[2] + planes[1][3]; + d2 = planes[2][0] * v0[0] + planes[2][1] * v0[1] + planes[2][2] * v0[2] + planes[2][3]; + d3 = planes[3][0] * v0[0] + planes[3][1] * v0[1] + planes[3][2] * v0[2] + planes[3][3]; + + d4 = planes[4][0] * v0[0] + planes[4][1] * v0[1] + planes[4][2] * v0[2] + planes[4][3]; + d5 = planes[5][0] * v0[0] + planes[5][1] * v0[1] + planes[5][2] * v0[2] + planes[5][3]; + d10 = planes[4][0] * v1[0] + planes[4][1] * v1[1] + planes[4][2] * v1[2] + planes[4][3]; + d11 = planes[5][0] * v1[0] + planes[5][1] * v1[1] + planes[5][2] * v1[2] + planes[5][3]; + + d6 = planes[0][0] * v1[0] + planes[0][1] * v1[1] + planes[0][2] * v1[2] + planes[0][3]; + d7 = planes[1][0] * v1[0] + planes[1][1] * v1[1] + planes[1][2] * v1[2] + planes[1][3]; + d8 = planes[2][0] * v1[0] + planes[2][1] * v1[1] + planes[2][2] * v1[2] + planes[2][3]; + d9 = planes[3][0] * v1[0] + planes[3][1] * v1[1] + planes[3][2] * v1[2] + planes[3][3]; + + bits0 = FLOATSIGNBITSET( d0 ) << (0+0); + bits0 |= FLOATSIGNBITSET( d1 ) << (0+1); + bits0 |= FLOATSIGNBITSET( d2 ) << (0+2); + bits0 |= FLOATSIGNBITSET( d3 ) << (0+3); + bits0 |= FLOATSIGNBITSET( d4 ) << (0+4); + bits0 |= FLOATSIGNBITSET( d5 ) << (0+5); + + bits1 = FLOATSIGNBITSET( d6 ) << (8+0); + bits1 |= FLOATSIGNBITSET( d7 ) << (8+1); + bits1 |= FLOATSIGNBITSET( d8 ) << (8+2); + bits1 |= FLOATSIGNBITSET( d9 ) << (8+3); + bits1 |= FLOATSIGNBITSET( d10 ) << (8+4); + bits1 |= FLOATSIGNBITSET( d11 ) << (8+5); + + *(unsigned short *)(cullBits + i) = ( bits0 | bits1 ) ^ 0x3F3F; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[numVerts - 1].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + d4 = planes[4][0] * v[0] + planes[4][1] * v[1] + planes[4][2] * v[2] + planes[4][3]; + d5 = planes[5][0] * v[0] + planes[5][1] * v[1] + planes[5][2] * v[2] + planes[5][3]; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[numVerts - 1] = bits ^ 0x3F; // flip lower 6 bits + } + +#endif +} + +/* +============ +idSIMD_SSE::OverlayPointCull +============ +*/ +void VPCALL idSIMD_SSE::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ) { +#if 1 + + __asm { + mov eax, numVerts + mov edx, verts + mov esi, texCoords + mov edi, cullBits + + mov ecx, planes + movss xmm4, [ecx+ 0] + movss xmm5, [ecx+16] + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm5, [ecx+ 4] + movss xmm6, [ecx+20] + shufps xmm5, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm6, [ecx+ 8] + movss xmm7, [ecx+24] + shufps xmm6, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm7, [ecx+12] + movss xmm0, [ecx+28] + shufps xmm7, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) + + and eax, ~1 + jz done2 + add edi, eax + neg eax + + loopVert2: + movss xmm0, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [edx+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [edx+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movss xmm3, [edx+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movaps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 3, 2, 3 ) + add edx, 2*DRAWVERT_SIZE + movmskps ecx, xmm0 + mov byte ptr [edi+eax+0], cl + add esi, 4*4 + movmskps ecx, xmm1 + mov byte ptr [edi+eax+1], cl + add eax, 2 + jl loopVert2 + + done2: + mov eax, numVerts + and eax, 1 + jz done + + movss xmm0, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movlps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movmskps ecx, xmm0 + mov byte ptr [edi], cl + + done: + } + +#else + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + + for ( int i = 0; i < numVerts - 1; i += 2 ) { + unsigned short bits; + float d0, d1, d2, d3; + + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + d2 = p0[0] * v1[0] + p0[1] * v1[1] + p0[2] * v1[2] + p0[3]; + d3 = p1[0] * v1[0] + p1[1] * v1[1] + p1[2] * v1[2] + p1[3]; + + texCoords[i+0][0] = d0; + texCoords[i+0][1] = d1; + texCoords[i+1][0] = d2; + texCoords[i+1][1] = d3; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 8; + bits |= FLOATSIGNBITSET( d3 ) << 9; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + d2 = 1.0f - d2; + d3 = 1.0f - d3; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + bits |= FLOATSIGNBITSET( d2 ) << 10; + bits |= FLOATSIGNBITSET( d3 ) << 11; + + *(unsigned short *)(cullBits + i) = bits; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1; + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + const idVec3 &v0 = verts[numVerts - 1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[numVerts - 1] = bits; + } + +#endif +} + +/* +============ +idSIMD_SSE::DeriveTriPlanes +============ +*/ +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +void VPCALL idSIMD_SSE::DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { +#if 1 + + int d, a; + int n = numIndexes / 3; + ALIGN16( float x0[4] ); + ALIGN16( float x1[4] ); + ALIGN16( float x2[4] ); + + __asm { + push ebx + mov eax, n + shl eax, 4 + mov esi, verts + mov edi, indexes + mov edx, planes + + add edx, eax + neg eax + + mov d, edx + + add eax, 4*16 + jge done4 + + loopPlane4: + mov a, eax + + mov ecx, [edi+0*12+0] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+1*12+0] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+2*12+0] + shl edx, DRAWVERT_SIZE_SHIFT + mov eax, [edi+3*12+0] + shl eax, DRAWVERT_SIZE_SHIFT + + movlps xmm4, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, X, X */ + movss xmm5, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, X, X */ + movhps xmm4, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, 4, 5 */ + movhps xmm5, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, 6, X */ + movlps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, X, X */ + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, X, X */ + movhps xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, 14, X */ + movaps xmm3, xmm4 /* xmm3 = 0, 1, 4, 5 */ + shufps xmm3, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm3 = 0, 4, 8, 12 */ + shufps xmm4, xmm6, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm4 = 1, 5, 9, 13 */ + shufps xmm5, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm5 = 2, 6, 10, 14 */ + + mov ecx, [edi+0*12+4] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+1*12+4] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+2*12+4] + shl edx, DRAWVERT_SIZE_SHIFT + mov eax, [edi+3*12+4] + shl eax, DRAWVERT_SIZE_SHIFT + + movaps x0, xmm3 + movaps x1, xmm4 + movaps x2, xmm5 + + movlps xmm1, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, X, X */ + movss xmm2, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, X, X, X */ + movhps xmm1, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] /* xmm1 = 0, 1, 4, 5 */ + movhps xmm2, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] /* xmm2 = 2, X, 6, X */ + movlps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, X, X */ + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, X, X */ + movhps xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, 14, X */ + movaps xmm0, xmm1 /* xmm0 = 0, 1, 4, 5 */ + shufps xmm0, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm0 = 0, 4, 8, 12 */ + shufps xmm1, xmm6, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm1 = 1, 5, 9, 13 */ + shufps xmm2, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm2 = 2, 6, 10, 14 */ + + mov ecx, [edi+0*12+8] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+1*12+8] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+2*12+8] + shl edx, DRAWVERT_SIZE_SHIFT + mov eax, [edi+3*12+8] + shl eax, DRAWVERT_SIZE_SHIFT + + subps xmm0, xmm3 + subps xmm1, xmm4 + subps xmm2, xmm5 + + movlps xmm4, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, X, X */ + movss xmm5, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, X, X */ + movhps xmm4, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] /* xmm4 = 0, 1, 4, 5 */ + movhps xmm5, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] /* xmm5 = 2, X, 6, X */ + movlps xmm6, [esi+edx+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, X, X */ + movss xmm7, [esi+edx+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, X, X */ + movhps xmm6, [esi+eax+DRAWVERT_XYZ_OFFSET+0] /* xmm6 = 8, 9, 12, 13 */ + movhps xmm7, [esi+eax+DRAWVERT_XYZ_OFFSET+8] /* xmm6 = 10, X, 14, X */ + movaps xmm3, xmm4 /* xmm3 = 0, 1, 4, 5 */ + shufps xmm3, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm3 = 0, 4, 8, 12 */ + shufps xmm4, xmm6, R_SHUFFLE_PS( 1, 3, 1, 3 ) /* xmm4 = 1, 5, 9, 13 */ + shufps xmm5, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) /* xmm5 = 2, 6, 10, 14 */ + + mov eax, a + mov edx, d + add edi, 4*12 + + subps xmm3, x0 + subps xmm4, x1 + subps xmm5, x2 + + movaps xmm6, xmm4 + mulps xmm6, xmm2 + movaps xmm7, xmm5 + mulps xmm7, xmm1 + subps xmm6, xmm7 + + mulps xmm5, xmm0 + mulps xmm2, xmm3 + subps xmm5, xmm2 + + mulps xmm3, xmm1 + mulps xmm4, xmm0 + subps xmm3, xmm4 + + add eax, 4*16 + + movaps xmm0, xmm6 + mulps xmm6, xmm6 + movaps xmm1, xmm5 + mulps xmm5, xmm5 + movaps xmm2, xmm3 + mulps xmm3, xmm3 + + addps xmm3, xmm5 + addps xmm3, xmm6 + rsqrtps xmm3, xmm3 + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm4, x0 + movaps xmm5, x1 + movaps xmm6, x2 + + mulps xmm4, xmm0 + mulps xmm5, xmm1 + mulps xmm6, xmm2 + + addps xmm4, xmm5 + addps xmm4, xmm6 + xorps xmm4, SIMD_SP_signBit + + // transpose xmm0, xmm1, xmm2, xmm4 to memory + movaps xmm7, xmm0 + movaps xmm5, xmm2 + + unpcklps xmm0, xmm1 + unpcklps xmm2, xmm4 + + movlps [edx+eax-8*16+0], xmm0 + movlps [edx+eax-8*16+8], xmm2 + + movhps [edx+eax-7*16+0], xmm0 + movhps [edx+eax-7*16+8], xmm2 + + unpckhps xmm7, xmm1 + unpckhps xmm5, xmm4 + + movlps [edx+eax-6*16+0], xmm7 + movlps [edx+eax-6*16+8], xmm5 + + movhps [edx+eax-5*16+0], xmm7 + movhps [edx+eax-5*16+8], xmm5 + + jle loopPlane4 + + done4: + + sub eax, 4*16 + jge done + + loopPlane1: + mov ecx, [edi+0] + shl ecx, DRAWVERT_SIZE_SHIFT + mov ebx, [edi+4] + shl ebx, DRAWVERT_SIZE_SHIFT + mov edx, [edi+8] + shl edx, DRAWVERT_SIZE_SHIFT + + movss xmm0, [esi+ebx+DRAWVERT_XYZ_OFFSET+0] + subss xmm0, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] + movss xmm1, [esi+ebx+DRAWVERT_XYZ_OFFSET+4] + subss xmm1, [esi+ecx+DRAWVERT_XYZ_OFFSET+4] + movss xmm2, [esi+ebx+DRAWVERT_XYZ_OFFSET+8] + subss xmm2, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] + + movss xmm3, [esi+edx+DRAWVERT_XYZ_OFFSET+0] + subss xmm3, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] + movss xmm4, [esi+edx+DRAWVERT_XYZ_OFFSET+4] + subss xmm4, [esi+ecx+DRAWVERT_XYZ_OFFSET+4] + movss xmm5, [esi+edx+DRAWVERT_XYZ_OFFSET+8] + subss xmm5, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] + + movss xmm6, xmm4 + mulss xmm6, xmm2 + movss xmm7, xmm5 + mulss xmm7, xmm1 + subss xmm6, xmm7 + + add edi, 1*12 + + mulss xmm5, xmm0 + mulss xmm2, xmm3 + subss xmm5, xmm2 + + mulss xmm3, xmm1 + mulss xmm4, xmm0 + subss xmm3, xmm4 + + mov edx, d + + movss xmm0, xmm6 + mulss xmm6, xmm6 + movss xmm1, xmm5 + mulss xmm5, xmm5 + movss xmm2, xmm3 + mulss xmm3, xmm3 + + add eax, 1*16 + + addss xmm3, xmm5 + addss xmm3, xmm6 + rsqrtss xmm3, xmm3 + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [edx+eax-1*16+0], xmm0 + movss [edx+eax-1*16+4], xmm1 + movss [edx+eax-1*16+8], xmm2 + + mulss xmm0, [esi+ecx+DRAWVERT_XYZ_OFFSET+0] + mulss xmm1, [esi+ecx+DRAWVERT_XYZ_OFFSET+4] + mulss xmm2, [esi+ecx+DRAWVERT_XYZ_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx+eax-1*16+12], xmm0 + + jl loopPlane1 + + done: + pop ebx + } + +#else + + int i, j; + + for ( i = 0; i <= numIndexes - 12; i += 12 ) { + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + + for ( j = 0; j < 4; j++ ) { + const idDrawVert *a, *b, *c; + + a = verts + indexes[i + j * 3 + 0]; + b = verts + indexes[i + j * 3 + 1]; + c = verts + indexes[i + j * 3 + 2]; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + + d3[j] = c->xyz[0] - a->xyz[0]; + d4[j] = c->xyz[1] - a->xyz[1]; + d5[j] = c->xyz[2] - a->xyz[2]; + } + + ALIGN16( float tmp[4]; ) + + n0[0] = d4[0] * d2[0]; + n0[1] = d4[1] * d2[1]; + n0[2] = d4[2] * d2[2]; + n0[3] = d4[3] * d2[3]; + + n0[0] -= d5[0] * d1[0]; + n0[1] -= d5[1] * d1[1]; + n0[2] -= d5[2] * d1[2]; + n0[3] -= d5[3] * d1[3]; + + n1[0] = d5[0] * d0[0]; + n1[1] = d5[1] * d0[1]; + n1[2] = d5[2] * d0[2]; + n1[3] = d5[3] * d0[3]; + + n1[0] -= d3[0] * d2[0]; + n1[1] -= d3[1] * d2[1]; + n1[2] -= d3[2] * d2[2]; + n1[3] -= d3[3] * d2[3]; + + n2[0] = d3[0] * d1[0]; + n2[1] = d3[1] * d1[1]; + n2[2] = d3[2] * d1[2]; + n2[3] = d3[3] * d1[3]; + + n2[0] -= d4[0] * d0[0]; + n2[1] -= d4[1] * d0[1]; + n2[2] -= d4[2] * d0[2]; + n2[3] -= d4[3] * d0[3]; + + tmp[0] = n0[0] * n0[0]; + tmp[1] = n0[1] * n0[1]; + tmp[2] = n0[2] * n0[2]; + tmp[3] = n0[3] * n0[3]; + + tmp[0] += n1[0] * n1[0]; + tmp[1] += n1[1] * n1[1]; + tmp[2] += n1[2] * n1[2]; + tmp[3] += n1[3] * n1[3]; + + tmp[0] += n2[0] * n2[0]; + tmp[1] += n2[1] * n2[1]; + tmp[2] += n2[2] * n2[2]; + tmp[3] += n2[3] * n2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + n0[0] *= tmp[0]; + n0[1] *= tmp[1]; + n0[2] *= tmp[2]; + n0[3] *= tmp[3]; + + n1[0] *= tmp[0]; + n1[1] *= tmp[1]; + n1[2] *= tmp[2]; + n1[3] *= tmp[3]; + + n2[0] *= tmp[0]; + n2[1] *= tmp[1]; + n2[2] *= tmp[2]; + n2[3] *= tmp[3]; + + for ( j = 0; j < 4; j++ ) { + const idDrawVert *a; + + a = verts + indexes[i + j * 3]; + + planes->Normal()[0] = n0[j]; + planes->Normal()[1] = n1[j]; + planes->Normal()[2] = n2[j]; + planes->FitThroughPoint( a->xyz ); + planes++; + } + } + + for ( ; i < numIndexes; i += 3 ) { + const idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4, d5; + float n0, n1, n2; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + + d3 = c->xyz[0] - a->xyz[0]; + d4 = c->xyz[1] - a->xyz[1]; + d5 = c->xyz[2] - a->xyz[2]; + + float tmp; + + n0 = d4 * d2 - d5 * d1; + n1 = d5 * d0 - d3 * d2; + n2 = d3 * d1 - d4 * d0; + + tmp = idMath::RSqrt( n0 * n0 + n1 * n1 + n2 * n2 ); + + n0 *= tmp; + n1 *= tmp; + n2 *= tmp; + + planes->Normal()[0] = n0; + planes->Normal()[1] = n1; + planes->Normal()[2] = n2; + planes->FitThroughPoint( a->xyz ); + planes++; + } + +#endif +} + +/* +============ +idSIMD_SSE::DeriveTangents +============ +*/ +//#define REFINE_TANGENT_SQUAREROOT +#define FIX_DEGENERATE_TANGENT + +void VPCALL idSIMD_SSE::DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + int i; + + assert( planes != NULL ); + assert( verts != NULL ); + assert( numVerts >= 0 ); + +#ifdef REFINE_TANGENT_SQUAREROOT + __asm { + movaps xmm6, SIMD_SP_rsqrt_c0 + movaps xmm7, SIMD_SP_rsqrt_c1 + } +#endif + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( i = 0; i <= numIndexes - 12; i += 12 ) { + idDrawVert *a, *b, *c; + ALIGN16( unsigned long signBit[4]; ) + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float d6[4]; ) + ALIGN16( float d7[4]; ) + ALIGN16( float d8[4]; ) + ALIGN16( float d9[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + ALIGN16( float t0[4]; ) + ALIGN16( float t1[4]; ) + ALIGN16( float t2[4]; ) + ALIGN16( float t3[4]; ) + ALIGN16( float t4[4]; ) + ALIGN16( float t5[4]; ) + + for ( int j = 0; j < 4; j++ ) { + + a = verts + indexes[i + j * 3 + 0]; + b = verts + indexes[i + j * 3 + 1]; + c = verts + indexes[i + j * 3 + 2]; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + d3[j] = b->st[0] - a->st[0]; + d4[j] = b->st[1] - a->st[1]; + + d5[j] = c->xyz[0] - a->xyz[0]; + d6[j] = c->xyz[1] - a->xyz[1]; + d7[j] = c->xyz[2] - a->xyz[2]; + d8[j] = c->st[0] - a->st[0]; + d9[j] = c->st[1] - a->st[1]; + } + +#if 1 + + __asm { + // normal + movaps xmm0, d6 + mulps xmm0, d2 + movaps xmm1, d7 + mulps xmm1, d1 + subps xmm0, xmm1 + + movaps xmm1, d7 + mulps xmm1, d0 + movaps xmm2, d5 + mulps xmm2, d2 + subps xmm1, xmm2 + + movaps xmm2, d5 + mulps xmm2, d1 + movaps xmm3, d6 + mulps xmm3, d0 + subps xmm2, xmm3 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + mulps xmm0, xmm3 + movaps n0, xmm0 + mulps xmm1, xmm3 + movaps n1, xmm1 + mulps xmm2, xmm3 + movaps n2, xmm2 + + // area sign bit + movaps xmm0, d3 + mulps xmm0, d9 + movaps xmm1, d4 + mulps xmm1, d8 + subps xmm0, xmm1 + andps xmm0, SIMD_SP_signBit + movaps signBit, xmm0 + + // first tangent + movaps xmm0, d0 + mulps xmm0, d9 + movaps xmm1, d4 + mulps xmm1, d5 + subps xmm0, xmm1 + + movaps xmm1, d1 + mulps xmm1, d9 + movaps xmm2, d4 + mulps xmm2, d6 + subps xmm1, xmm2 + + movaps xmm2, d2 + mulps xmm2, d9 + movaps xmm3, d4 + mulps xmm3, d7 + subps xmm2, xmm3 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulps xmm0, xmm3 + movaps t0, xmm0 + mulps xmm1, xmm3 + movaps t1, xmm1 + mulps xmm2, xmm3 + movaps t2, xmm2 + + // second tangent + movaps xmm0, d3 + mulps xmm0, d5 + movaps xmm1, d0 + mulps xmm1, d8 + subps xmm0, xmm1 + + movaps xmm1, d3 + mulps xmm1, d6 + movaps xmm2, d1 + mulps xmm2, d8 + subps xmm1, xmm2 + + movaps xmm2, d3 + mulps xmm2, d7 + movaps xmm3, d2 + mulps xmm3, d8 + subps xmm2, xmm3 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulps xmm0, xmm3 + movaps t3, xmm0 + mulps xmm1, xmm3 + movaps t4, xmm1 + mulps xmm2, xmm3 + movaps t5, xmm2 + } + +#else + + ALIGN16( float tmp[4]; ) + + // normal + n0[0] = d6[0] * d2[0]; + n0[1] = d6[1] * d2[1]; + n0[2] = d6[2] * d2[2]; + n0[3] = d6[3] * d2[3]; + + n0[0] -= d7[0] * d1[0]; + n0[1] -= d7[1] * d1[1]; + n0[2] -= d7[2] * d1[2]; + n0[3] -= d7[3] * d1[3]; + + n1[0] = d7[0] * d0[0]; + n1[1] = d7[1] * d0[1]; + n1[2] = d7[2] * d0[2]; + n1[3] = d7[3] * d0[3]; + + n1[0] -= d5[0] * d2[0]; + n1[1] -= d5[1] * d2[1]; + n1[2] -= d5[2] * d2[2]; + n1[3] -= d5[3] * d2[3]; + + n2[0] = d5[0] * d1[0]; + n2[1] = d5[1] * d1[1]; + n2[2] = d5[2] * d1[2]; + n2[3] = d5[3] * d1[3]; + + n2[0] -= d6[0] * d0[0]; + n2[1] -= d6[1] * d0[1]; + n2[2] -= d6[2] * d0[2]; + n2[3] -= d6[3] * d0[3]; + + tmp[0] = n0[0] * n0[0]; + tmp[1] = n0[1] * n0[1]; + tmp[2] = n0[2] * n0[2]; + tmp[3] = n0[3] * n0[3]; + + tmp[0] += n1[0] * n1[0]; + tmp[1] += n1[1] * n1[1]; + tmp[2] += n1[2] * n1[2]; + tmp[3] += n1[3] * n1[3]; + + tmp[0] += n2[0] * n2[0]; + tmp[1] += n2[1] * n2[1]; + tmp[2] += n2[2] * n2[2]; + tmp[3] += n2[3] * n2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + n0[0] *= tmp[0]; + n0[1] *= tmp[1]; + n0[2] *= tmp[2]; + n0[3] *= tmp[3]; + + n1[0] *= tmp[0]; + n1[1] *= tmp[1]; + n1[2] *= tmp[2]; + n1[3] *= tmp[3]; + + n2[0] *= tmp[0]; + n2[1] *= tmp[1]; + n2[2] *= tmp[2]; + n2[3] *= tmp[3]; + + // area sign bit + tmp[0] = d3[0] * d9[0]; + tmp[1] = d3[1] * d9[1]; + tmp[2] = d3[2] * d9[2]; + tmp[3] = d3[3] * d9[3]; + + tmp[0] -= d4[0] * d8[0]; + tmp[1] -= d4[1] * d8[1]; + tmp[2] -= d4[2] * d8[2]; + tmp[3] -= d4[3] * d8[3]; + + signBit[0] = ( *(unsigned long *)&tmp[0] ) & ( 1 << 31 ); + signBit[1] = ( *(unsigned long *)&tmp[1] ) & ( 1 << 31 ); + signBit[2] = ( *(unsigned long *)&tmp[2] ) & ( 1 << 31 ); + signBit[3] = ( *(unsigned long *)&tmp[3] ) & ( 1 << 31 ); + + // first tangent + t0[0] = d0[0] * d9[0]; + t0[1] = d0[1] * d9[1]; + t0[2] = d0[2] * d9[2]; + t0[3] = d0[3] * d9[3]; + + t0[0] -= d4[0] * d5[0]; + t0[1] -= d4[1] * d5[1]; + t0[2] -= d4[2] * d5[2]; + t0[3] -= d4[3] * d5[3]; + + t1[0] = d1[0] * d9[0]; + t1[1] = d1[1] * d9[1]; + t1[2] = d1[2] * d9[2]; + t1[3] = d1[3] * d9[3]; + + t1[0] -= d4[0] * d6[0]; + t1[1] -= d4[1] * d6[1]; + t1[2] -= d4[2] * d6[2]; + t1[3] -= d4[3] * d6[3]; + + t2[0] = d2[0] * d9[0]; + t2[1] = d2[1] * d9[1]; + t2[2] = d2[2] * d9[2]; + t2[3] = d2[3] * d9[3]; + + t2[0] -= d4[0] * d7[0]; + t2[1] -= d4[1] * d7[1]; + t2[2] -= d4[2] * d7[2]; + t2[3] -= d4[3] * d7[3]; + + tmp[0] = t0[0] * t0[0]; + tmp[1] = t0[1] * t0[1]; + tmp[2] = t0[2] * t0[2]; + tmp[3] = t0[3] * t0[3]; + + tmp[0] += t1[0] * t1[0]; + tmp[1] += t1[1] * t1[1]; + tmp[2] += t1[2] * t1[2]; + tmp[3] += t1[3] * t1[3]; + + tmp[0] += t2[0] * t2[0]; + tmp[1] += t2[1] * t2[1]; + tmp[2] += t2[2] * t2[2]; + tmp[3] += t2[3] * t2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + *(unsigned long *)&tmp[0] ^= signBit[0]; + *(unsigned long *)&tmp[1] ^= signBit[1]; + *(unsigned long *)&tmp[2] ^= signBit[2]; + *(unsigned long *)&tmp[3] ^= signBit[3]; + + t0[0] *= tmp[0]; + t0[1] *= tmp[1]; + t0[2] *= tmp[2]; + t0[3] *= tmp[3]; + + t1[0] *= tmp[0]; + t1[1] *= tmp[1]; + t1[2] *= tmp[2]; + t1[3] *= tmp[3]; + + t2[0] *= tmp[0]; + t2[1] *= tmp[1]; + t2[2] *= tmp[2]; + t2[3] *= tmp[3]; + + // second tangent + t3[0] = d3[0] * d5[0]; + t3[1] = d3[1] * d5[1]; + t3[2] = d3[2] * d5[2]; + t3[3] = d3[3] * d5[3]; + + t3[0] -= d0[0] * d8[0]; + t3[1] -= d0[1] * d8[1]; + t3[2] -= d0[2] * d8[2]; + t3[3] -= d0[3] * d8[3]; + + t4[0] = d3[0] * d6[0]; + t4[1] = d3[1] * d6[1]; + t4[2] = d3[2] * d6[2]; + t4[3] = d3[3] * d6[3]; + + t4[0] -= d1[0] * d8[0]; + t4[1] -= d1[1] * d8[1]; + t4[2] -= d1[2] * d8[2]; + t4[3] -= d1[3] * d8[3]; + + t5[0] = d3[0] * d7[0]; + t5[1] = d3[1] * d7[1]; + t5[2] = d3[2] * d7[2]; + t5[3] = d3[3] * d7[3]; + + t5[0] -= d2[0] * d8[0]; + t5[1] -= d2[1] * d8[1]; + t5[2] -= d2[2] * d8[2]; + t5[3] -= d2[3] * d8[3]; + + tmp[0] = t3[0] * t3[0]; + tmp[1] = t3[1] * t3[1]; + tmp[2] = t3[2] * t3[2]; + tmp[3] = t3[3] * t3[3]; + + tmp[0] += t4[0] * t4[0]; + tmp[1] += t4[1] * t4[1]; + tmp[2] += t4[2] * t4[2]; + tmp[3] += t4[3] * t4[3]; + + tmp[0] += t5[0] * t5[0]; + tmp[1] += t5[1] * t5[1]; + tmp[2] += t5[2] * t5[2]; + tmp[3] += t5[3] * t5[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + *(unsigned long *)&tmp[0] ^= signBit[0]; + *(unsigned long *)&tmp[1] ^= signBit[1]; + *(unsigned long *)&tmp[2] ^= signBit[2]; + *(unsigned long *)&tmp[3] ^= signBit[3]; + + t3[0] *= tmp[0]; + t3[1] *= tmp[1]; + t3[2] *= tmp[2]; + t3[3] *= tmp[3]; + + t4[0] *= tmp[0]; + t4[1] *= tmp[1]; + t4[2] *= tmp[2]; + t4[3] *= tmp[3]; + + t5[0] *= tmp[0]; + t5[1] *= tmp[1]; + t5[2] *= tmp[2]; + t5[3] *= tmp[3]; + +#endif + + for ( int j = 0; j < 4; j++ ) { + + const int v0 = indexes[i + j * 3 + 0]; + const int v1 = indexes[i + j * 3 + 1]; + const int v2 = indexes[i + j * 3 + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + planes->Normal()[0] = n0[j]; + planes->Normal()[1] = n1[j]; + planes->Normal()[2] = n2[j]; + planes->FitThroughPoint( a->xyz ); + planes++; + + if ( used[v0] ) { + a->normal[0] += n0[j]; + a->normal[1] += n1[j]; + a->normal[2] += n2[j]; + + a->tangents[0][0] += t0[j]; + a->tangents[0][1] += t1[j]; + a->tangents[0][2] += t2[j]; + + a->tangents[1][0] += t3[j]; + a->tangents[1][1] += t4[j]; + a->tangents[1][2] += t5[j]; + } else { + a->normal[0] = n0[j]; + a->normal[1] = n1[j]; + a->normal[2] = n2[j]; + + a->tangents[0][0] = t0[j]; + a->tangents[0][1] = t1[j]; + a->tangents[0][2] = t2[j]; + + a->tangents[1][0] = t3[j]; + a->tangents[1][1] = t4[j]; + a->tangents[1][2] = t5[j]; + + used[v0] = true; + } + + if ( used[v1] ) { + b->normal[0] += n0[j]; + b->normal[1] += n1[j]; + b->normal[2] += n2[j]; + + b->tangents[0][0] += t0[j]; + b->tangents[0][1] += t1[j]; + b->tangents[0][2] += t2[j]; + + b->tangents[1][0] += t3[j]; + b->tangents[1][1] += t4[j]; + b->tangents[1][2] += t5[j]; + } else { + b->normal[0] = n0[j]; + b->normal[1] = n1[j]; + b->normal[2] = n2[j]; + + b->tangents[0][0] = t0[j]; + b->tangents[0][1] = t1[j]; + b->tangents[0][2] = t2[j]; + + b->tangents[1][0] = t3[j]; + b->tangents[1][1] = t4[j]; + b->tangents[1][2] = t5[j]; + + used[v1] = true; + } + + if ( used[v2] ) { + c->normal[0] += n0[j]; + c->normal[1] += n1[j]; + c->normal[2] += n2[j]; + + c->tangents[0][0] += t0[j]; + c->tangents[0][1] += t1[j]; + c->tangents[0][2] += t2[j]; + + c->tangents[1][0] += t3[j]; + c->tangents[1][1] += t4[j]; + c->tangents[1][2] += t5[j]; + } else { + c->normal[0] = n0[j]; + c->normal[1] = n1[j]; + c->normal[2] = n2[j]; + + c->tangents[0][0] = t0[j]; + c->tangents[0][1] = t1[j]; + c->tangents[0][2] = t2[j]; + + c->tangents[1][0] = t3[j]; + c->tangents[1][1] = t4[j]; + c->tangents[1][2] = t5[j]; + + used[v2] = true; + } + } + } + + for ( ; i < numIndexes; i += 3 ) { + idDrawVert *a, *b, *c; + ALIGN16( unsigned long signBit[4]; ) + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const int v0 = indexes[i + 0]; + const int v1 = indexes[i + 1]; + const int v2 = indexes[i + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + d9 = c->st[1] - a->st[1]; + +#if 1 + + __asm { + // normal + movss xmm0, d6 + mulss xmm0, d2 + movss xmm1, d7 + mulss xmm1, d1 + subss xmm0, xmm1 + + movss xmm1, d7 + mulss xmm1, d0 + movss xmm2, d5 + mulss xmm2, d2 + subss xmm1, xmm2 + + movss xmm2, d5 + mulss xmm2, d1 + movss xmm3, d6 + mulss xmm3, d0 + subss xmm2, xmm3 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + mulss xmm0, xmm3 + movss n0, xmm0 + mulss xmm1, xmm3 + movss n1, xmm1 + mulss xmm2, xmm3 + movss n2, xmm2 + + // area sign bit + movss xmm0, d3 + mulss xmm0, d9 + movss xmm1, d4 + mulss xmm1, d8 + subss xmm0, xmm1 + andps xmm0, SIMD_SP_signBit + movaps signBit, xmm0 + + // first tangent + movss xmm0, d0 + mulss xmm0, d9 + movss xmm1, d4 + mulss xmm1, d5 + subss xmm0, xmm1 + + movss xmm1, d1 + mulss xmm1, d9 + movss xmm2, d4 + mulss xmm2, d6 + subss xmm1, xmm2 + + movss xmm2, d2 + mulss xmm2, d9 + movss xmm3, d4 + mulss xmm3, d7 + subss xmm2, xmm3 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulss xmm0, xmm3 + movss t0, xmm0 + mulss xmm1, xmm3 + movss t1, xmm1 + mulss xmm2, xmm3 + movss t2, xmm2 + + // second tangent + movss xmm0, d3 + mulss xmm0, d5 + movss xmm1, d0 + mulss xmm1, d8 + subss xmm0, xmm1 + + movss xmm1, d3 + mulss xmm1, d6 + movss xmm2, d1 + mulss xmm2, d8 + subss xmm1, xmm2 + + movss xmm2, d3 + mulss xmm2, d7 + movss xmm3, d2 + mulss xmm3, d8 + subss xmm2, xmm3 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef FIX_DEGENERATE_TANGENT + xorps xmm4, xmm4 + cmpeqps xmm4, xmm3 + andps xmm4, SIMD_SP_tiny // if values are zero replace them with a tiny number + andps xmm3, SIMD_SP_absMask // make sure the values are positive + orps xmm3, xmm4 +#endif + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + xorps xmm3, signBit + + mulss xmm0, xmm3 + movss t3, xmm0 + mulss xmm1, xmm3 + movss t4, xmm1 + mulss xmm2, xmm3 + movss t5, xmm2 + } + +#else + + float tmp; + + // normal + n0 = d6 * d2 - d7 * d1; + n1 = d7 * d0 - d5 * d2; + n2 = d5 * d1 - d6 * d0; + + tmp = idMath::RSqrt( n0 * n0 + n1 * n1 + n2 * n2 ); + + n0 *= tmp; + n1 *= tmp; + n2 *= tmp; + + // area sign bit + tmp = d3 * d9 - d4 * d8; + signBit[0] = ( *(unsigned long *)&tmp ) & ( 1 << 31 ); + + // first tangent + t0 = d0 * d9 - d4 * d5; + t1 = d1 * d9 - d4 * d6; + t2 = d2 * d9 - d4 * d7; + + tmp = idMath::RSqrt( t0 * t0 + t1 * t1 + t2 * t2 ); + *(unsigned long *)&tmp ^= signBit[0]; + + t0 *= tmp; + t1 *= tmp; + t2 *= tmp; + + // second tangent + t3 = d3 * d5 - d0 * d8; + t4 = d3 * d6 - d1 * d8; + t5 = d3 * d7 - d2 * d8; + + tmp = idMath::RSqrt( t3 * t3 + t4 * t4 + t5 * t5 ); + *(unsigned long *)&tmp ^= signBit[0]; + + t3 *= tmp; + t4 *= tmp; + t5 *= tmp; + +#endif + + planes->Normal()[0] = n0; + planes->Normal()[1] = n1; + planes->Normal()[2] = n2; + planes->FitThroughPoint( a->xyz ); + planes++; + + if ( used[v0] ) { + a->normal[0] += n0; + a->normal[1] += n1; + a->normal[2] += n2; + + a->tangents[0][0] += t0; + a->tangents[0][1] += t1; + a->tangents[0][2] += t2; + + a->tangents[1][0] += t3; + a->tangents[1][1] += t4; + a->tangents[1][2] += t5; + } else { + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + + used[v0] = true; + } + + if ( used[v1] ) { + b->normal[0] += n0; + b->normal[1] += n1; + b->normal[2] += n2; + + b->tangents[0][0] += t0; + b->tangents[0][1] += t1; + b->tangents[0][2] += t2; + + b->tangents[1][0] += t3; + b->tangents[1][1] += t4; + b->tangents[1][2] += t5; + } else { + b->normal[0] = n0; + b->normal[1] = n1; + b->normal[2] = n2; + + b->tangents[0][0] = t0; + b->tangents[0][1] = t1; + b->tangents[0][2] = t2; + + b->tangents[1][0] = t3; + b->tangents[1][1] = t4; + b->tangents[1][2] = t5; + + used[v1] = true; + } + + if ( used[v2] ) { + c->normal[0] += n0; + c->normal[1] += n1; + c->normal[2] += n2; + + c->tangents[0][0] += t0; + c->tangents[0][1] += t1; + c->tangents[0][2] += t2; + + c->tangents[1][0] += t3; + c->tangents[1][1] += t4; + c->tangents[1][2] += t5; + } else { + c->normal[0] = n0; + c->normal[1] = n1; + c->normal[2] = n2; + + c->tangents[0][0] = t0; + c->tangents[0][1] = t1; + c->tangents[0][2] = t2; + + c->tangents[1][0] = t3; + c->tangents[1][1] = t4; + c->tangents[1][2] = t5; + + used[v2] = true; + } + } +} + +/* +============ +idSIMD_SSE::DeriveUnsmoothedTangents +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT + +void VPCALL idSIMD_SSE::DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ) { + int i, j; + + for ( i = 0; i <= numVerts - 4; i += 4 ) { + ALIGN16( float s0[4]; ) + ALIGN16( float s1[4]; ) + ALIGN16( float s2[4]; ) + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float d6[4]; ) + ALIGN16( float d7[4]; ) + ALIGN16( float d8[4]; ) + ALIGN16( float d9[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + ALIGN16( float t0[4]; ) + ALIGN16( float t1[4]; ) + ALIGN16( float t2[4]; ) + ALIGN16( float t3[4]; ) + ALIGN16( float t4[4]; ) + ALIGN16( float t5[4]; ) + + for ( j = 0; j < 4; j++ ) { + const idDrawVert *a, *b, *c; + + const dominantTri_s &dt = dominantTris[i+j]; + + s0[j] = dt.normalizationScale[0]; + s1[j] = dt.normalizationScale[1]; + s2[j] = dt.normalizationScale[2]; + + a = verts + i + j; + b = verts + dt.v2; + c = verts + dt.v3; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + d3[j] = b->st[0] - a->st[0]; + d4[j] = b->st[1] - a->st[1]; + + d5[j] = c->xyz[0] - a->xyz[0]; + d6[j] = c->xyz[1] - a->xyz[1]; + d7[j] = c->xyz[2] - a->xyz[2]; + d8[j] = c->st[0] - a->st[0]; + d9[j] = c->st[1] - a->st[1]; + } + +#if 1 + + __asm { + + movaps xmm0, d6 + mulps xmm0, d2 + movaps xmm1, d7 + mulps xmm1, d1 + + movaps xmm2, d7 + mulps xmm2, d0 + movaps xmm3, d5 + mulps xmm3, d2 + + movaps xmm4, d5 + mulps xmm4, d1 + movaps xmm5, d6 + mulps xmm5, d0 + + subps xmm0, xmm1 + subps xmm2, xmm3 + movaps xmm7, s2 + subps xmm4, xmm5 + + mulps xmm0, xmm7 + movaps n0, xmm0 + mulps xmm2, xmm7 + movaps n1, xmm2 + mulps xmm4, xmm7 + movaps n2, xmm4 + + movaps xmm0, d0 + mulps xmm0, d9 + movaps xmm1, d4 + mulps xmm1, d5 + + movaps xmm2, d1 + mulps xmm2, d9 + movaps xmm3, d4 + mulps xmm3, d6 + + movaps xmm4, d2 + mulps xmm4, d9 + movaps xmm5, d4 + mulps xmm5, d7 + + subps xmm0, xmm1 + subps xmm2, xmm3 + movaps xmm7, s0 + subps xmm4, xmm5 + + mulps xmm0, xmm7 + movaps t0, xmm0 + mulps xmm2, xmm7 + movaps t1, xmm2 + mulps xmm4, xmm7 + movaps t2, xmm4 + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + movaps xmm0, d3 + mulps xmm0, d5 + movaps xmm1, d0 + mulps xmm1, d8 + + movaps xmm2, d3 + mulps xmm2, d6 + movaps xmm3, d1 + mulps xmm3, d8 + + movaps xmm4, d3 + mulps xmm4, d7 + movaps xmm5, d2 + mulps xmm5, d8 +#else + movaps xmm0, n2 + mulps xmm0, t1 + movaps xmm1, n1 + mulps xmm1, t2 + + movaps xmm2, n0 + mulps xmm2, t2 + movaps xmm3, n2 + mulps xmm3, t0 + + movaps xmm4, n1 + mulps xmm4, t0 + movaps xmm5, n0 + mulps xmm5, t1 +#endif + subps xmm0, xmm1 + subps xmm2, xmm3 + movaps xmm7, s1 + subps xmm4, xmm5 + + mulps xmm0, xmm7 + movaps t3, xmm0 + mulps xmm2, xmm7 + movaps t4, xmm2 + mulps xmm4, xmm7 + movaps t5, xmm4 + } + +#else + + n0[0] = d6[0] * d2[0]; + n0[1] = d6[1] * d2[1]; + n0[2] = d6[2] * d2[2]; + n0[3] = d6[3] * d2[3]; + + n1[0] = d7[0] * d0[0]; + n1[1] = d7[1] * d0[1]; + n1[2] = d7[2] * d0[2]; + n1[3] = d7[3] * d0[3]; + + n2[0] = d5[0] * d1[0]; + n2[1] = d5[1] * d1[1]; + n2[2] = d5[2] * d1[2]; + n2[3] = d5[3] * d1[3]; + + n0[0] -= d7[0] * d1[0]; + n0[1] -= d7[1] * d1[1]; + n0[2] -= d7[2] * d1[2]; + n0[3] -= d7[3] * d1[3]; + + n1[0] -= d5[0] * d2[0]; + n1[1] -= d5[1] * d2[1]; + n1[2] -= d5[2] * d2[2]; + n1[3] -= d5[3] * d2[3]; + + n2[0] -= d6[0] * d0[0]; + n2[1] -= d6[1] * d0[1]; + n2[2] -= d6[2] * d0[2]; + n2[3] -= d6[3] * d0[3]; + + n0[0] *= s2[0]; + n0[1] *= s2[1]; + n0[2] *= s2[2]; + n0[3] *= s2[3]; + + n1[0] *= s2[0]; + n1[1] *= s2[1]; + n1[2] *= s2[2]; + n1[3] *= s2[3]; + + n2[0] *= s2[0]; + n2[1] *= s2[1]; + n2[2] *= s2[2]; + n2[3] *= s2[3]; + + t0[0] = d0[0] * d9[0]; + t0[1] = d0[1] * d9[1]; + t0[2] = d0[2] * d9[2]; + t0[3] = d0[3] * d9[3]; + + t1[0] = d1[0] * d9[0]; + t1[1] = d1[1] * d9[1]; + t1[2] = d1[2] * d9[2]; + t1[3] = d1[3] * d9[3]; + + t2[0] = d2[0] * d9[0]; + t2[1] = d2[1] * d9[1]; + t2[2] = d2[2] * d9[2]; + t2[3] = d2[3] * d9[3]; + + t0[0] -= d4[0] * d5[0]; + t0[1] -= d4[1] * d5[1]; + t0[2] -= d4[2] * d5[2]; + t0[3] -= d4[3] * d5[3]; + + t1[0] -= d4[0] * d6[0]; + t1[1] -= d4[1] * d6[1]; + t1[2] -= d4[2] * d6[2]; + t1[3] -= d4[3] * d6[3]; + + t2[0] -= d4[0] * d7[0]; + t2[1] -= d4[1] * d7[1]; + t2[2] -= d4[2] * d7[2]; + t2[3] -= d4[3] * d7[3]; + + t0[0] *= s0[0]; + t0[1] *= s0[1]; + t0[2] *= s0[2]; + t0[3] *= s0[3]; + + t1[0] *= s0[0]; + t1[1] *= s0[1]; + t1[2] *= s0[2]; + t1[3] *= s0[3]; + + t2[0] *= s0[0]; + t2[1] *= s0[1]; + t2[2] *= s0[2]; + t2[3] *= s0[3]; + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3[0] = d3[0] * d5[0]; + t3[1] = d3[1] * d5[1]; + t3[2] = d3[2] * d5[2]; + t3[3] = d3[3] * d5[3]; + + t4[0] = d3[0] * d6[0]; + t4[1] = d3[1] * d6[1]; + t4[2] = d3[2] * d6[2]; + t4[3] = d3[3] * d6[3]; + + t5[0] = d3[0] * d7[0]; + t5[1] = d3[1] * d7[1]; + t5[2] = d3[2] * d7[2]; + t5[3] = d3[3] * d7[3]; + + t3[0] -= d0[0] * d8[0]; + t3[1] -= d0[1] * d8[1]; + t3[2] -= d0[2] * d8[2]; + t3[3] -= d0[3] * d8[3]; + + t4[0] -= d1[0] * d8[0]; + t4[1] -= d1[1] * d8[1]; + t4[2] -= d1[2] * d8[2]; + t4[3] -= d1[3] * d8[3]; + + t5[0] -= d2[0] * d8[0]; + t5[1] -= d2[1] * d8[1]; + t5[2] -= d2[2] * d8[2]; + t5[3] -= d2[3] * d8[3]; +#else + t3[0] = n2[0] * t1[0]; + t3[1] = n2[1] * t1[1]; + t3[2] = n2[2] * t1[2]; + t3[3] = n2[3] * t1[3]; + + t4[0] = n0[0] * t2[0]; + t4[1] = n0[1] * t2[1]; + t4[2] = n0[2] * t2[2]; + t4[3] = n0[3] * t2[3]; + + t5[0] = n1[0] * t0[0]; + t5[1] = n1[1] * t0[1]; + t5[2] = n1[2] * t0[2]; + t5[3] = n1[3] * t0[3]; + + t3[0] -= n1[0] * t2[0]; + t3[1] -= n1[1] * t2[1]; + t3[2] -= n1[2] * t2[2]; + t3[3] -= n1[3] * t2[3]; + + t4[0] -= n2[0] * t0[0]; + t4[1] -= n2[1] * t0[1]; + t4[2] -= n2[2] * t0[2]; + t4[3] -= n2[3] * t0[3]; + + t5[0] -= n0[0] * t1[0]; + t5[1] -= n0[1] * t1[1]; + t5[2] -= n0[2] * t1[2]; + t5[3] -= n0[3] * t1[3]; +#endif + t3[0] *= s1[0]; + t3[1] *= s1[1]; + t3[2] *= s1[2]; + t3[3] *= s1[3]; + + t4[0] *= s1[0]; + t4[1] *= s1[1]; + t4[2] *= s1[2]; + t4[3] *= s1[3]; + + t5[0] *= s1[0]; + t5[1] *= s1[1]; + t5[2] *= s1[2]; + t5[3] *= s1[3]; + +#endif + + for ( j = 0; j < 4; j++ ) { + idDrawVert *a; + + a = verts + i + j; + + a->normal[0] = n0[j]; + a->normal[1] = n1[j]; + a->normal[2] = n2[j]; + + a->tangents[0][0] = t0[j]; + a->tangents[0][1] = t1[j]; + a->tangents[0][2] = t2[j]; + + a->tangents[1][0] = t3[j]; + a->tangents[1][1] = t4[j]; + a->tangents[1][2] = t5[j]; + } + } + + for ( ; i < numVerts; i++ ) { + idDrawVert *a, *b, *c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + d9 = c->st[1] - a->st[1]; + +#if 1 + + __asm { + + movss xmm0, d6 + mulss xmm0, d2 + movss xmm1, d7 + mulss xmm1, d1 + + movss xmm2, d7 + mulss xmm2, d0 + movss xmm3, d5 + mulss xmm3, d2 + + movss xmm4, d5 + mulss xmm4, d1 + movss xmm5, d6 + mulss xmm5, d0 + + subss xmm0, xmm1 + subss xmm2, xmm3 + movss xmm7, s2 + subss xmm4, xmm5 + + mulss xmm0, xmm7 + movss n0, xmm0 + mulss xmm2, xmm7 + movss n1, xmm2 + mulss xmm4, xmm7 + movss n2, xmm4 + + movss xmm0, d0 + mulss xmm0, d9 + movss xmm1, d4 + mulss xmm1, d5 + + movss xmm2, d1 + mulss xmm2, d9 + movss xmm3, d4 + mulss xmm3, d6 + + movss xmm4, d2 + mulss xmm4, d9 + movss xmm5, d4 + mulss xmm5, d7 + + subss xmm0, xmm1 + subss xmm2, xmm3 + movss xmm7, s0 + subss xmm4, xmm5 + + mulss xmm0, xmm7 + movss t0, xmm0 + mulss xmm2, xmm7 + movss t1, xmm2 + mulss xmm4, xmm7 + movss t2, xmm4 + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + movss xmm0, d3 + mulss xmm0, d5 + movss xmm1, d0 + mulss xmm1, d8 + + movss xmm2, d3 + mulss xmm2, d6 + movss xmm3, d1 + mulss xmm3, d8 + + movss xmm4, d3 + mulss xmm4, d7 + movss xmm5, d2 + mulss xmm5, d8 +#else + movss xmm0, n2 + mulss xmm0, t1 + movss xmm1, n1 + mulss xmm1, t2 + + movss xmm2, n0 + mulss xmm2, t2 + movss xmm3, n2 + mulss xmm3, t0 + + movss xmm4, n1 + mulss xmm4, t0 + movss xmm5, n0 + mulss xmm5, t1 +#endif + subss xmm0, xmm1 + subss xmm2, xmm3 + movss xmm7, s1 + subss xmm4, xmm5 + + mulss xmm0, xmm7 + movss t3, xmm0 + mulss xmm2, xmm7 + movss t4, xmm2 + mulss xmm4, xmm7 + movss t5, xmm4 + } + +#else + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } +} + +/* +============ +idSIMD_SSE::NormalizeTangents +============ +*/ +void VPCALL idSIMD_SSE::NormalizeTangents( idDrawVert *verts, const int numVerts ) { + ALIGN16( float normal[12] ); + + assert( verts != NULL ); + assert( numVerts >= 0 ); + + __asm { + mov eax, numVerts + test eax, eax + jz done +#ifdef REFINE_TANGENT_SQUAREROOT + movaps xmm6, SIMD_SP_rsqrt_c0 + movaps xmm7, SIMD_SP_rsqrt_c1 +#endif + mov esi, verts + imul eax, DRAWVERT_SIZE + add esi, eax + neg eax + add eax, DRAWVERT_SIZE*4 + jle loopVert4 + + sub eax, DRAWVERT_SIZE*4 + jl loopVert1 + + loopVert4: + + sub eax, DRAWVERT_SIZE*4 + + // normalize 4 idDrawVert::normal + + movss xmm0, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+0] // 0, X, X, X + movhps xmm0, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+0] // 0, X, 3, 4 + movss xmm2, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+8] // 5, X, X, X + movhps xmm2, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+4] // 5, X, 1, 2 + movss xmm4, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+0] // 6, X, X, X + movhps xmm4, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+0] // 6, X, 9, 10 + movss xmm3, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+8] // 11, X, X, X + movhps xmm3, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+4] // 11, X, 7, 8 + + movaps xmm1, xmm0 + movaps xmm5, xmm2 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // 0, 3, 6, 9 + shufps xmm2, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 2, 5, 8, 11 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 4, 4, 1, 1 + shufps xmm4, xmm3, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 10, 10, 7, 7 + shufps xmm1, xmm4, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 1, 4, 7, 10 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + // save the 4 idDrawVert::normal to project the tangents + + movaps [normal+ 0], xmm0 + movaps [normal+16], xmm1 + movaps [normal+32], xmm2 + + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + // project and normalize 4 idDrawVert::tangent[0] + + movss xmm0, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+0] // 0, X, X, X + movhps xmm0, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+0] // 0, X, 3, 4 + movss xmm2, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+8] // 5, X, X, X + movhps xmm2, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+4] // 5, X, 1, 2 + movss xmm4, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+0] // 6, X, X, X + movhps xmm4, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+0] // 6, X, 9, 10 + movss xmm3, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+8] // 11, X, X, X + movhps xmm3, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+4] // 11, X, 7, 8 + + movaps xmm1, xmm0 + movaps xmm5, xmm2 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // 0, 3, 6, 9 + shufps xmm2, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 2, 5, 8, 11 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 4, 4, 1, 1 + shufps xmm4, xmm3, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 10, 10, 7, 7 + shufps xmm1, xmm4, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 1, 4, 7, 10 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + addps xmm3, xmm4 + addps xmm3, xmm5 + + movaps xmm4, xmm3 + movaps xmm5, xmm3 + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + subps xmm0, xmm3 + subps xmm1, xmm4 + subps xmm2, xmm5 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + // project and normalize 4 idDrawVert::tangent[1] + + movss xmm0, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+0] // 0, X, X, X + movhps xmm0, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+0] // 0, X, 3, 4 + movss xmm2, [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+8] // 5, X, X, X + movhps xmm2, [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+4] // 5, X, 1, 2 + movss xmm4, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+0] // 6, X, X, X + movhps xmm4, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+0] // 6, X, 9, 10 + movss xmm3, [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+8] // 11, X, X, X + movhps xmm3, [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+4] // 11, X, 7, 8 + + movaps xmm1, xmm0 + movaps xmm5, xmm2 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // 0, 3, 6, 9 + shufps xmm2, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 2, 5, 8, 11 + shufps xmm1, xmm5, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 4, 4, 1, 1 + shufps xmm4, xmm3, R_SHUFFLE_PS( 3, 3, 2, 2 ) // 10, 10, 7, 7 + shufps xmm1, xmm4, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 1, 4, 7, 10 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + addps xmm3, xmm4 + addps xmm3, xmm5 + + movaps xmm4, xmm3 + movaps xmm5, xmm3 + mulps xmm3, [normal+ 0] + mulps xmm4, [normal+16] + mulps xmm5, [normal+32] + subps xmm0, xmm3 + subps xmm1, xmm4 + subps xmm2, xmm5 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + movaps xmm5, xmm2 + + mulps xmm3, xmm3 + mulps xmm4, xmm4 + mulps xmm5, xmm5 + addps xmm3, xmm4 + addps xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtps xmm4, xmm3 + mulps xmm3, xmm4 + mulps xmm3, xmm4 + subps xmm3, xmm6 + mulps xmm4, xmm7 + mulps xmm3, xmm4 +#else + rsqrtps xmm3, xmm3 +#endif + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*0+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*1+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*2+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_SIZE*3+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + add eax, DRAWVERT_SIZE*8 + + jle loopVert4 + + sub eax, DRAWVERT_SIZE*4 + jge done + + loopVert1: + + // normalize one idDrawVert::normal + + movss xmm0, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [esi+eax+DRAWVERT_NORMAL_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_NORMAL_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_NORMAL_OFFSET+8], xmm2 + + // project and normalize one idDrawVert::tangent[0] + + movss xmm0, [esi+eax+DRAWVERT_TANGENT0_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_TANGENT0_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_TANGENT0_OFFSET+8] + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + addss xmm3, xmm4 + addss xmm3, xmm5 + + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + subss xmm0, xmm3 + subss xmm1, xmm4 + subss xmm2, xmm5 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [esi+eax+DRAWVERT_TANGENT0_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_TANGENT0_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_TANGENT0_OFFSET+8], xmm2 + + // project and normalize one idDrawVert::tangent[1] + + movss xmm0, [esi+eax+DRAWVERT_TANGENT1_OFFSET+0] + movss xmm1, [esi+eax+DRAWVERT_TANGENT1_OFFSET+4] + movss xmm2, [esi+eax+DRAWVERT_TANGENT1_OFFSET+8] + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + addss xmm3, xmm4 + addss xmm3, xmm5 + + movss xmm4, xmm3 + movss xmm5, xmm3 + mulss xmm3, [esi+eax+DRAWVERT_NORMAL_OFFSET+0] + mulss xmm4, [esi+eax+DRAWVERT_NORMAL_OFFSET+4] + mulss xmm5, [esi+eax+DRAWVERT_NORMAL_OFFSET+8] + subss xmm0, xmm3 + subss xmm1, xmm4 + subss xmm2, xmm5 + + movss xmm3, xmm0 + movss xmm4, xmm1 + movss xmm5, xmm2 + + mulss xmm3, xmm3 + mulss xmm4, xmm4 + mulss xmm5, xmm5 + addss xmm3, xmm4 + addss xmm3, xmm5 + +#ifdef REFINE_TANGENT_SQUAREROOT + rsqrtss xmm4, xmm3 + mulss xmm3, xmm4 + mulss xmm3, xmm4 + subss xmm3, xmm6 + mulss xmm4, xmm7 + mulss xmm3, xmm4 +#else + rsqrtss xmm3, xmm3 +#endif + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [esi+eax+DRAWVERT_TANGENT1_OFFSET+0], xmm0 + movss [esi+eax+DRAWVERT_TANGENT1_OFFSET+4], xmm1 + movss [esi+eax+DRAWVERT_TANGENT1_OFFSET+8], xmm2 + + add eax, DRAWVERT_SIZE + + jl loopVert1 + done: + } +} + +/* +============ +idSIMD_SSE::CreateTextureSpaceLightVectors +============ +*/ +void VPCALL idSIMD_SSE::CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + +#if 0 + + __asm { + + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + mov ecx, lightOrigin + movss xmm7, [ecx+0] + movhps xmm7, [ecx+4] + + mov ecx, lightVectors + sub ecx, 3*4 + + loopVert: + inc eax + jge done + + add edi, DRAWVERT_SIZE + add ecx, 3*4 + + cmp byte ptr [esi+eax], 0 + je loopVert + + movaps xmm0, xmm7 + movss xmm1, [edi+DRAWVERT_XYZ_OFFSET+0] + movhps xmm1, [edi+DRAWVERT_XYZ_OFFSET+4] + subps xmm0, xmm1 + + // 0, X, 1, 2 + // 3, X, 4, 5 + // 6, X, 7, 8 + + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movhps xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+4] + mulps xmm2, xmm0 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movhps xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+4] + mulps xmm3, xmm0 + + movaps xmm5, xmm2 // xmm5 = 0, X, 1, 2 + unpcklps xmm5, xmm3 // xmm5 = 0, 3, X, X + unpckhps xmm2, xmm3 // xmm2 = 1, 4, 2, 5 + + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+0] + movhps xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + mulps xmm4, xmm0 + + movlhps xmm5, xmm4 // xmm5 = 0, 3, 6, X + movhlps xmm4, xmm2 // xmm4 = 2, 5, 7, 8 + shufps xmm2, xmm4, R_SHUFFLE_PS( 0, 1, 3, 2 ) // xmm2 = 2, 5, 8, 7 + + addps xmm5, xmm4 + addps xmm5, xmm2 + movlps [ecx+0], xmm5 + shufps xmm5, xmm5, R_SHUFFLE_PS( 2, 3, 0, 1 ) + movss [ecx+8], xmm5 + + jmp loopVert + + done: + } + +#elif 1 + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + idVec3 lightDir; + + lightDir[0] = lightOrigin[0] - v->xyz[0]; + lightDir[1] = lightOrigin[1] - v->xyz[1]; + lightDir[2] = lightOrigin[2] - v->xyz[2]; + + lightVectors[i][0] = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + lightVectors[i][1] = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + lightVectors[i][2] = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + } + +#elif 1 + + ALIGN16( int usedVertNums[4]; ) + ALIGN16( float lightDir0[4]; ) + ALIGN16( float lightDir1[4]; ) + ALIGN16( float lightDir2[4]; ) + ALIGN16( float normal0[4]; ) + ALIGN16( float normal1[4]; ) + ALIGN16( float normal2[4]; ) + ALIGN16( float tangent0[4]; ) + ALIGN16( float tangent1[4]; ) + ALIGN16( float tangent2[4]; ) + ALIGN16( float tangent3[4]; ) + ALIGN16( float tangent4[4]; ) + ALIGN16( float tangent5[4]; ) + idVec3 localLightOrigin = lightOrigin; + + __asm { + + xor ecx, ecx + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + loopVert4: + inc eax + jge done4 + + add edi, DRAWVERT_SIZE + + cmp byte ptr [esi+eax], 0 + je loopVert4 + + mov usedVertNums[ecx*4], eax + + inc ecx + cmp ecx, 4 + + movss xmm0, localLightOrigin[0] + movss xmm1, localLightOrigin[4] + movss xmm2, localLightOrigin[8] + + subss xmm0, [edi+DRAWVERT_XYZ_OFFSET+0] + subss xmm1, [edi+DRAWVERT_XYZ_OFFSET+4] + subss xmm2, [edi+DRAWVERT_XYZ_OFFSET+8] + + movss lightDir0[ecx*4-4], xmm0 + movss lightDir1[ecx*4-4], xmm1 + movss lightDir2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_NORMAL_OFFSET+0] + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + movss xmm5, [edi+DRAWVERT_NORMAL_OFFSET+8] + + movss normal0[ecx*4-4], xmm3 + movss normal1[ecx*4-4], xmm4 + movss normal2[ecx*4-4], xmm5 + + movss xmm0, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movss xmm1, [edi+DRAWVERT_TANGENT0_OFFSET+4] + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+8] + + movss tangent0[ecx*4-4], xmm0 + movss tangent1[ecx*4-4], xmm1 + movss tangent2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movss xmm4, [edi+DRAWVERT_TANGENT1_OFFSET+4] + movss xmm5, [edi+DRAWVERT_TANGENT1_OFFSET+8] + + movss tangent3[ecx*4-4], xmm3 + movss tangent4[ecx*4-4], xmm4 + movss tangent5[ecx*4-4], xmm5 + + jl loopVert4 + + movaps xmm0, lightDir0 + movaps xmm1, lightDir1 + movaps xmm2, lightDir2 + + movaps xmm3, tangent0 + mulps xmm3, xmm0 + movaps xmm4, tangent1 + mulps xmm4, xmm1 + movaps xmm5, tangent2 + mulps xmm5, xmm2 + + addps xmm3, xmm4 + addps xmm5, xmm3 + + movaps xmm3, tangent3 + mulps xmm3, xmm0 + movaps xmm4, tangent4 + mulps xmm4, xmm1 + movaps xmm6, tangent5 + mulps xmm6, xmm2 + + addps xmm3, xmm4 + addps xmm6, xmm3 + + mulps xmm0, normal0 + mulps xmm1, normal1 + mulps xmm2, normal2 + + addps xmm0, xmm1 + addps xmm0, xmm2 + + mov ecx, numVerts + imul ecx, 12 + mov edx, usedVertNums[0] + add ecx, lightVectors + imul edx, 12 + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + imul edx, 12 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + imul edx, 12 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + imul edx, 12 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + + xor ecx, ecx + jmp loopVert4 + + done4: + test ecx, ecx + jz done + xor eax, eax + mov edi, numVerts + imul edi, 12 + add edi, lightVectors + + loopVert1: + movss xmm0, lightDir0[eax*4] + movss xmm1, lightDir1[eax*4] + movss xmm2, lightDir2[eax*4] + + mov edx, usedVertNums[eax*4] + imul edx, 12 + + movss xmm3, tangent0[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent1[eax*4] + mulss xmm4, xmm1 + movss xmm5, tangent2[eax*4] + mulss xmm5, xmm2 + + addss xmm3, xmm4 + addss xmm5, xmm3 + movss [edi+edx+0], xmm5 + + movss xmm3, tangent3[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent4[eax*4] + mulss xmm4, xmm1 + movss xmm6, tangent5[eax*4] + mulss xmm6, xmm2 + + addss xmm3, xmm4 + addss xmm6, xmm3 + movss [edi+edx+4], xmm6 + + mulss xmm0, normal0[eax*4] + mulss xmm1, normal1[eax*4] + mulss xmm2, normal2[eax*4] + + addss xmm0, xmm1 + addss xmm0, xmm2 + movss [edi+edx+8], xmm0 + + inc eax + dec ecx + jg loopVert1 + + done: + } + +#else + + ALIGN16( float lightVectors0[4]; ) + ALIGN16( float lightVectors1[4]; ) + ALIGN16( float lightVectors2[4]; ) + int numUsedVerts = 0; + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + + lightDir0[numUsedVerts] = lightOrigin[0] - v->xyz[0]; + lightDir1[numUsedVerts] = lightOrigin[1] - v->xyz[1]; + lightDir2[numUsedVerts] = lightOrigin[2] - v->xyz[2]; + + normal0[numUsedVerts] = v->normal[0]; + normal1[numUsedVerts] = v->normal[1]; + normal2[numUsedVerts] = v->normal[2]; + + tangent0[numUsedVerts] = v->tangents[0][0]; + tangent1[numUsedVerts] = v->tangents[0][1]; + tangent2[numUsedVerts] = v->tangents[0][2]; + + tangent3[numUsedVerts] = v->tangents[1][0]; + tangent4[numUsedVerts] = v->tangents[1][1]; + tangent5[numUsedVerts] = v->tangents[1][2]; + + usedVertNums[numUsedVerts++] = i; + if ( numUsedVerts < 4 ) { + continue; + } + + lightVectors0[0] = lightDir0[0] * tangent0[0]; + lightVectors0[1] = lightDir0[1] * tangent0[1]; + lightVectors0[2] = lightDir0[2] * tangent0[2]; + lightVectors0[3] = lightDir0[3] * tangent0[3]; + + lightVectors0[0] += lightDir1[0] * tangent1[0]; + lightVectors0[1] += lightDir1[1] * tangent1[1]; + lightVectors0[2] += lightDir1[2] * tangent1[2]; + lightVectors0[3] += lightDir1[3] * tangent1[3]; + + lightVectors0[0] += lightDir2[0] * tangent2[0]; + lightVectors0[1] += lightDir2[1] * tangent2[1]; + lightVectors0[2] += lightDir2[2] * tangent2[2]; + lightVectors0[3] += lightDir2[3] * tangent2[3]; + + lightVectors1[0] = lightDir0[0] * tangent3[0]; + lightVectors1[1] = lightDir0[1] * tangent3[1]; + lightVectors1[2] = lightDir0[2] * tangent3[2]; + lightVectors1[3] = lightDir0[3] * tangent3[3]; + + lightVectors1[0] += lightDir1[0] * tangent4[0]; + lightVectors1[1] += lightDir1[1] * tangent4[1]; + lightVectors1[2] += lightDir1[2] * tangent4[2]; + lightVectors1[3] += lightDir1[3] * tangent4[3]; + + lightVectors1[0] += lightDir2[0] * tangent5[0]; + lightVectors1[1] += lightDir2[1] * tangent5[1]; + lightVectors1[2] += lightDir2[2] * tangent5[2]; + lightVectors1[3] += lightDir2[3] * tangent5[3]; + + lightVectors2[0] = lightDir0[0] * normal0[0]; + lightVectors2[1] = lightDir0[1] * normal0[1]; + lightVectors2[2] = lightDir0[2] * normal0[2]; + lightVectors2[3] = lightDir0[3] * normal0[3]; + + lightVectors2[0] += lightDir1[0] * normal1[0]; + lightVectors2[1] += lightDir1[1] * normal1[1]; + lightVectors2[2] += lightDir1[2] * normal1[2]; + lightVectors2[3] += lightDir1[3] * normal1[3]; + + lightVectors2[0] += lightDir2[0] * normal2[0]; + lightVectors2[1] += lightDir2[1] * normal2[1]; + lightVectors2[2] += lightDir2[2] * normal2[2]; + lightVectors2[3] += lightDir2[3] * normal2[3]; + + + for ( int j = 0; j < 4; j++ ) { + int n = usedVertNums[j]; + + lightVectors[n][0] = lightVectors0[j]; + lightVectors[n][1] = lightVectors1[j]; + lightVectors[n][2] = lightVectors2[j]; + } + + numUsedVerts = 0; + } + + for ( int i = 0; i < numUsedVerts; i++ ) { + + lightVectors0[i] = lightDir0[i] * tangent0[i] + lightDir1[i] * tangent1[i] + lightDir2[i] * tangent2[i]; + lightVectors1[i] = lightDir0[i] * tangent3[i] + lightDir1[i] * tangent4[i] + lightDir2[i] * tangent5[i]; + lightVectors2[i] = lightDir0[i] * normal0[i] + lightDir1[i] * normal1[i] + lightDir2[i] * normal2[i]; + + int n = usedVertNums[i]; + lightVectors[n][0] = lightVectors0[i]; + lightVectors[n][1] = lightVectors1[i]; + lightVectors[n][2] = lightVectors2[i]; + } + +#endif +} + +/* +============ +idSIMD_SSE::CreateSpecularTextureCoords +============ +*/ +void VPCALL idSIMD_SSE::CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ) { + + bool *used = (bool *)_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + +#if 0 + + __asm { + + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + mov ecx, viewOrigin + movss xmm6, [ecx+0] + movhps xmm6, [ecx+4] + + mov ecx, lightOrigin + movss xmm7, [ecx+0] + movhps xmm7, [ecx+4] + + mov ecx, texCoords + sub ecx, 4*4 + + loopVert: + inc eax + jge done + + add edi, DRAWVERT_SIZE + add ecx, 4*4 + + cmp byte ptr [esi+eax], 0 + je loopVert + + movaps xmm0, xmm7 + movaps xmm1, xmm6 + movss xmm2, [edi+DRAWVERT_XYZ_OFFSET+0] + movhps xmm2, [edi+DRAWVERT_XYZ_OFFSET+4] + subps xmm0, xmm2 + subps xmm1, xmm2 + + movaps xmm3, xmm0 + movaps xmm4, xmm1 + mulps xmm3, xmm3 + mulps xmm4, xmm4 + + // 0, X, 1, 2 + // 3, X, 4, 5 + + movaps xmm5, xmm3 // xmm5 = 0, X, 1, 2 + unpcklps xmm5, xmm4 // xmm5 = 0, 3, X, X + unpckhps xmm3, xmm4 // xmm3 = 1, 4, 2, 5 + movhlps xmm4, xmm3 // xmm4 = 2, 5, 4, 5 + + addps xmm5, xmm3 + addps xmm5, xmm4 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 1, 0, 1 ) + rsqrtps xmm5, xmm5 + + movaps xmm4, xmm5 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 1, 1, 1 ) + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + addps xmm0, xmm1 + + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movhps xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+4] + mulps xmm2, xmm0 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movhps xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+4] + mulps xmm3, xmm0 + + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+0] + movhps xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + mulps xmm4, xmm0 + + movaps xmm5, xmm2 // xmm5 = 0, X, 1, 2 + unpcklps xmm5, xmm3 // xmm5 = 0, 3, X, X + unpckhps xmm2, xmm3 // xmm2 = 1, 4, 2, 5 + + movlhps xmm5, xmm4 // xmm5 = 0, 3, 6, X + movhlps xmm4, xmm2 // xmm4 = 2, 5, 7, 8 + shufps xmm2, xmm4, R_SHUFFLE_PS( 0, 1, 3, 2 ) // xmm2 = 2, 5, 8, 7 + + movaps xmm3, SIMD_SP_one + + addps xmm5, xmm4 + addps xmm5, xmm2 + movaps [ecx+0], xmm5 + movss [ecx+12], xmm3 + + jmp loopVert + + done: + } + +#elif 0 + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + + idVec3 lightDir = lightOrigin - v->xyz; + idVec3 viewDir = viewOrigin - v->xyz; + + float ilength; + + ilength = idMath::RSqrt( lightDir[0] * lightDir[0] + lightDir[1] * lightDir[1] + lightDir[2] * lightDir[2] ); + lightDir[0] *= ilength; + lightDir[1] *= ilength; + lightDir[2] *= ilength; + + ilength = idMath::RSqrt( viewDir[0] * viewDir[0] + viewDir[1] * viewDir[1] + viewDir[2] * viewDir[2] ); + viewDir[0] *= ilength; + viewDir[1] *= ilength; + viewDir[2] *= ilength; + + lightDir += viewDir; + + texCoords[i][0] = lightDir[0] * v->tangents[0][0] + lightDir[1] * v->tangents[0][1] + lightDir[2] * v->tangents[0][2]; + texCoords[i][1] = lightDir[0] * v->tangents[1][0] + lightDir[1] * v->tangents[1][1] + lightDir[2] * v->tangents[1][2]; + texCoords[i][2] = lightDir[0] * v->normal[0] + lightDir[1] * v->normal[1] + lightDir[2] * v->normal[2]; + texCoords[i][3] = 1.0f; + } + + +#elif 1 + + ALIGN16( int usedVertNums[4]; ) + ALIGN16( float lightDir0[4]; ) + ALIGN16( float lightDir1[4]; ) + ALIGN16( float lightDir2[4]; ) + ALIGN16( float viewDir0[4]; ) + ALIGN16( float viewDir1[4]; ) + ALIGN16( float viewDir2[4]; ) + ALIGN16( float normal0[4]; ) + ALIGN16( float normal1[4]; ) + ALIGN16( float normal2[4]; ) + ALIGN16( float tangent0[4]; ) + ALIGN16( float tangent1[4]; ) + ALIGN16( float tangent2[4]; ) + ALIGN16( float tangent3[4]; ) + ALIGN16( float tangent4[4]; ) + ALIGN16( float tangent5[4]; ) + idVec3 localLightOrigin = lightOrigin; + idVec3 localViewOrigin = viewOrigin; + + __asm { + + xor ecx, ecx + mov eax, numVerts + + mov esi, used + add esi, eax + + mov edi, verts + sub edi, DRAWVERT_SIZE + + neg eax + dec eax + + loopVert4: + inc eax + jge done4 + + add edi, DRAWVERT_SIZE + + cmp byte ptr [esi+eax], 0 + je loopVert4 + + mov usedVertNums[ecx*4], eax + + inc ecx + cmp ecx, 4 + + movss xmm3, localLightOrigin[0] + movss xmm4, localLightOrigin[4] + movss xmm5, localLightOrigin[8] + + subss xmm3, [edi+DRAWVERT_XYZ_OFFSET+0] + subss xmm4, [edi+DRAWVERT_XYZ_OFFSET+4] + subss xmm5, [edi+DRAWVERT_XYZ_OFFSET+8] + + movss lightDir0[ecx*4-4], xmm3 + movss lightDir1[ecx*4-4], xmm4 + movss lightDir2[ecx*4-4], xmm5 + + movss xmm0, localViewOrigin[0] + movss xmm1, localViewOrigin[4] + movss xmm2, localViewOrigin[8] + + subss xmm0, [edi+DRAWVERT_XYZ_OFFSET+0] + subss xmm1, [edi+DRAWVERT_XYZ_OFFSET+4] + subss xmm2, [edi+DRAWVERT_XYZ_OFFSET+8] + + movss viewDir0[ecx*4-4], xmm0 + movss viewDir1[ecx*4-4], xmm1 + movss viewDir2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_NORMAL_OFFSET+0] + movss xmm4, [edi+DRAWVERT_NORMAL_OFFSET+4] + movss xmm5, [edi+DRAWVERT_NORMAL_OFFSET+8] + + movss normal0[ecx*4-4], xmm3 + movss normal1[ecx*4-4], xmm4 + movss normal2[ecx*4-4], xmm5 + + movss xmm0, [edi+DRAWVERT_TANGENT0_OFFSET+0] + movss xmm1, [edi+DRAWVERT_TANGENT0_OFFSET+4] + movss xmm2, [edi+DRAWVERT_TANGENT0_OFFSET+8] + + movss tangent0[ecx*4-4], xmm0 + movss tangent1[ecx*4-4], xmm1 + movss tangent2[ecx*4-4], xmm2 + + movss xmm3, [edi+DRAWVERT_TANGENT1_OFFSET+0] + movss xmm4, [edi+DRAWVERT_TANGENT1_OFFSET+4] + movss xmm5, [edi+DRAWVERT_TANGENT1_OFFSET+8] + + movss tangent3[ecx*4-4], xmm3 + movss tangent4[ecx*4-4], xmm4 + movss tangent5[ecx*4-4], xmm5 + + jl loopVert4 + + movaps xmm6, lightDir0 + movaps xmm0, xmm6 + mulps xmm6, xmm6 + movaps xmm7, lightDir1 + movaps xmm1, xmm7 + mulps xmm7, xmm7 + addps xmm6, xmm7 + movaps xmm5, lightDir2 + movaps xmm2, xmm5 + mulps xmm5, xmm5 + addps xmm6, xmm5 + rsqrtps xmm6, xmm6 + + mulps xmm0, xmm6 + mulps xmm1, xmm6 + mulps xmm2, xmm6 + + movaps xmm3, viewDir0 + movaps xmm7, xmm3 + mulps xmm7, xmm7 + movaps xmm4, viewDir1 + movaps xmm6, xmm4 + mulps xmm6, xmm6 + addps xmm7, xmm6 + movaps xmm5, viewDir2 + movaps xmm6, xmm5 + mulps xmm6, xmm6 + addps xmm7, xmm6 + rsqrtps xmm7, xmm7 + + mulps xmm3, xmm7 + addps xmm0, xmm3 + mulps xmm4, xmm7 + addps xmm1, xmm4 + mulps xmm5, xmm7 + addps xmm2, xmm5 + + movaps xmm3, tangent0 + mulps xmm3, xmm0 + movaps xmm4, tangent1 + mulps xmm4, xmm1 + addps xmm3, xmm4 + movaps xmm5, tangent2 + mulps xmm5, xmm2 + addps xmm5, xmm3 + + movaps xmm3, tangent3 + mulps xmm3, xmm0 + movaps xmm4, tangent4 + mulps xmm4, xmm1 + addps xmm3, xmm4 + movaps xmm6, tangent5 + mulps xmm6, xmm2 + addps xmm6, xmm3 + + mulps xmm0, normal0 + mulps xmm1, normal1 + addps xmm0, xmm1 + mulps xmm2, normal2 + addps xmm0, xmm2 + + mov ecx, numVerts + shl ecx, 4 + mov edx, usedVertNums[0] + add ecx, texCoords + shl edx, 4 + movss xmm3, SIMD_SP_one + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shl edx, 4 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shl edx, 4 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + mov edx, usedVertNums[12] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shl edx, 4 + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [ecx+edx+0], xmm5 + movss [ecx+edx+4], xmm6 + movss [ecx+edx+8], xmm0 + movss [ecx+edx+12], xmm3 + + xor ecx, ecx + jmp loopVert4 + + done4: + test ecx, ecx + jz done + xor eax, eax + mov edi, numVerts + shl edi, 4 + add edi, texCoords + + loopVert1: + movss xmm6, lightDir0[eax*4] + movss xmm0, xmm6 + mulss xmm6, xmm6 + movss xmm7, lightDir1[eax*4] + movss xmm1, xmm7 + mulss xmm7, xmm7 + addss xmm6, xmm7 + movss xmm5, lightDir2[eax*4] + movss xmm2, xmm5 + mulss xmm5, xmm5 + addss xmm6, xmm5 + rsqrtss xmm6, xmm6 + + mulss xmm0, xmm6 + mulss xmm1, xmm6 + mulss xmm2, xmm6 + + movss xmm3, viewDir0[eax*4] + movss xmm7, xmm3 + mulss xmm7, xmm7 + movss xmm4, viewDir1[eax*4] + movss xmm6, xmm4 + mulss xmm6, xmm6 + addss xmm7, xmm6 + movss xmm5, viewDir2[eax*4] + movss xmm6, xmm5 + mulss xmm6, xmm6 + addss xmm7, xmm6 + rsqrtss xmm7, xmm7 + + mulss xmm3, xmm7 + addss xmm0, xmm3 + mulss xmm4, xmm7 + addss xmm1, xmm4 + mulss xmm5, xmm7 + addss xmm2, xmm5 + + mov edx, usedVertNums[eax*4] + shl edx, 4 + + movss xmm3, tangent0[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent1[eax*4] + mulss xmm4, xmm1 + addss xmm3, xmm4 + movss xmm5, tangent2[eax*4] + mulss xmm5, xmm2 + addss xmm5, xmm3 + movss [edi+edx+0], xmm5 + + movss xmm3, tangent3[eax*4] + mulss xmm3, xmm0 + movss xmm4, tangent4[eax*4] + mulss xmm4, xmm1 + addss xmm3, xmm4 + movss xmm6, tangent5[eax*4] + mulss xmm6, xmm2 + addss xmm6, xmm3 + movss [edi+edx+4], xmm6 + + mulss xmm0, normal0[eax*4] + mulss xmm1, normal1[eax*4] + addss xmm0, xmm1 + mulss xmm2, normal2[eax*4] + addss xmm0, xmm2 + movss [edi+edx+8], xmm0 + + movss xmm3, SIMD_SP_one + movss [edi+edx+12], xmm3 + + inc eax + dec ecx + jg loopVert1 + + done: + } + +#else + + ALIGN16( int usedVertNums[4]; ) + ALIGN16( float lightDir0[4]; ) + ALIGN16( float lightDir1[4]; ) + ALIGN16( float lightDir2[4]; ) + ALIGN16( float viewDir0[4]; ) + ALIGN16( float viewDir1[4]; ) + ALIGN16( float viewDir2[4]; ) + ALIGN16( float normal0[4]; ) + ALIGN16( float normal1[4]; ) + ALIGN16( float normal2[4]; ) + ALIGN16( float tangent0[4]; ) + ALIGN16( float tangent1[4]; ) + ALIGN16( float tangent2[4]; ) + ALIGN16( float tangent3[4]; ) + ALIGN16( float tangent4[4]; ) + ALIGN16( float tangent5[4]; ) + ALIGN16( float texCoords0[4]; ) + ALIGN16( float texCoords1[4]; ) + ALIGN16( float texCoords2[4]; ) + idVec3 localLightOrigin = lightOrigin; + idVec3 localViewOrigin = viewOrigin; + int numUsedVerts = 0; + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert *v = &verts[i]; + + lightDir0[numUsedVerts] = localLightOrigin[0] - v->xyz[0]; + lightDir1[numUsedVerts] = localLightOrigin[1] - v->xyz[1]; + lightDir2[numUsedVerts] = localLightOrigin[2] - v->xyz[2]; + + viewDir0[numUsedVerts] = localViewOrigin[0] - v->xyz[0]; + viewDir1[numUsedVerts] = localViewOrigin[1] - v->xyz[1]; + viewDir2[numUsedVerts] = localViewOrigin[2] - v->xyz[2]; + + normal0[numUsedVerts] = v->normal[0]; + normal1[numUsedVerts] = v->normal[1]; + normal2[numUsedVerts] = v->normal[2]; + + tangent0[numUsedVerts] = v->tangents[0][0]; + tangent1[numUsedVerts] = v->tangents[0][1]; + tangent2[numUsedVerts] = v->tangents[0][2]; + + tangent3[numUsedVerts] = v->tangents[1][0]; + tangent4[numUsedVerts] = v->tangents[1][1]; + tangent5[numUsedVerts] = v->tangents[1][2]; + + usedVertNums[numUsedVerts++] = i; + if ( numUsedVerts < 4 ) { + continue; + } + + ALIGN16( float temp[4]; ) + + temp[0] = lightDir0[0] * lightDir0[0]; + temp[1] = lightDir0[1] * lightDir0[1]; + temp[2] = lightDir0[2] * lightDir0[2]; + temp[3] = lightDir0[3] * lightDir0[3]; + + temp[0] += lightDir1[0] * lightDir1[0]; + temp[1] += lightDir1[1] * lightDir1[1]; + temp[2] += lightDir1[2] * lightDir1[2]; + temp[3] += lightDir1[3] * lightDir1[3]; + + temp[0] += lightDir2[0] * lightDir2[0]; + temp[1] += lightDir2[1] * lightDir2[1]; + temp[2] += lightDir2[2] * lightDir2[2]; + temp[3] += lightDir2[3] * lightDir2[3]; + + temp[0] = idMath::RSqrt( temp[0] ); + temp[1] = idMath::RSqrt( temp[1] ); + temp[2] = idMath::RSqrt( temp[2] ); + temp[3] = idMath::RSqrt( temp[3] ); + + lightDir0[0] *= temp[0]; + lightDir0[1] *= temp[1]; + lightDir0[2] *= temp[2]; + lightDir0[3] *= temp[3]; + + lightDir1[0] *= temp[0]; + lightDir1[1] *= temp[1]; + lightDir1[2] *= temp[2]; + lightDir1[3] *= temp[3]; + + lightDir2[0] *= temp[0]; + lightDir2[1] *= temp[1]; + lightDir2[2] *= temp[2]; + lightDir2[3] *= temp[3]; + + temp[0] = viewDir0[0] * viewDir0[0]; + temp[1] = viewDir0[1] * viewDir0[1]; + temp[2] = viewDir0[2] * viewDir0[2]; + temp[3] = viewDir0[3] * viewDir0[3]; + + temp[0] += viewDir1[0] * viewDir1[0]; + temp[1] += viewDir1[1] * viewDir1[1]; + temp[2] += viewDir1[2] * viewDir1[2]; + temp[3] += viewDir1[3] * viewDir1[3]; + + temp[0] += viewDir2[0] * viewDir2[0]; + temp[1] += viewDir2[1] * viewDir2[1]; + temp[2] += viewDir2[2] * viewDir2[2]; + temp[3] += viewDir2[3] * viewDir2[3]; + + temp[0] = idMath::RSqrt( temp[0] ); + temp[1] = idMath::RSqrt( temp[1] ); + temp[2] = idMath::RSqrt( temp[2] ); + temp[3] = idMath::RSqrt( temp[3] ); + + viewDir0[0] *= temp[0]; + viewDir0[1] *= temp[1]; + viewDir0[2] *= temp[2]; + viewDir0[3] *= temp[3]; + + viewDir1[0] *= temp[0]; + viewDir1[1] *= temp[1]; + viewDir1[2] *= temp[2]; + viewDir1[3] *= temp[3]; + + viewDir2[0] *= temp[0]; + viewDir2[1] *= temp[1]; + viewDir2[2] *= temp[2]; + viewDir2[3] *= temp[3]; + + lightDir0[0] += viewDir0[0]; + lightDir0[1] += viewDir0[1]; + lightDir0[2] += viewDir0[2]; + lightDir0[3] += viewDir0[3]; + + lightDir1[0] += viewDir1[0]; + lightDir1[1] += viewDir1[1]; + lightDir1[2] += viewDir1[2]; + lightDir1[3] += viewDir1[3]; + + lightDir2[0] += viewDir2[0]; + lightDir2[1] += viewDir2[1]; + lightDir2[2] += viewDir2[2]; + lightDir2[3] += viewDir2[3]; + + texCoords0[0] = lightDir0[0] * tangent0[0]; + texCoords0[1] = lightDir0[1] * tangent0[1]; + texCoords0[2] = lightDir0[2] * tangent0[2]; + texCoords0[3] = lightDir0[3] * tangent0[3]; + + texCoords0[0] += lightDir1[0] * tangent1[0]; + texCoords0[1] += lightDir1[1] * tangent1[1]; + texCoords0[2] += lightDir1[2] * tangent1[2]; + texCoords0[3] += lightDir1[3] * tangent1[3]; + + texCoords0[0] += lightDir2[0] * tangent2[0]; + texCoords0[1] += lightDir2[1] * tangent2[1]; + texCoords0[2] += lightDir2[2] * tangent2[2]; + texCoords0[3] += lightDir2[3] * tangent2[3]; + + texCoords1[0] = lightDir0[0] * tangent3[0]; + texCoords1[1] = lightDir0[1] * tangent3[1]; + texCoords1[2] = lightDir0[2] * tangent3[2]; + texCoords1[3] = lightDir0[3] * tangent3[3]; + + texCoords1[0] += lightDir1[0] * tangent4[0]; + texCoords1[1] += lightDir1[1] * tangent4[1]; + texCoords1[2] += lightDir1[2] * tangent4[2]; + texCoords1[3] += lightDir1[3] * tangent4[3]; + + texCoords1[0] += lightDir2[0] * tangent5[0]; + texCoords1[1] += lightDir2[1] * tangent5[1]; + texCoords1[2] += lightDir2[2] * tangent5[2]; + texCoords1[3] += lightDir2[3] * tangent5[3]; + + texCoords2[0] = lightDir0[0] * normal0[0]; + texCoords2[1] = lightDir0[1] * normal0[1]; + texCoords2[2] = lightDir0[2] * normal0[2]; + texCoords2[3] = lightDir0[3] * normal0[3]; + + texCoords2[0] += lightDir1[0] * normal1[0]; + texCoords2[1] += lightDir1[1] * normal1[1]; + texCoords2[2] += lightDir1[2] * normal1[2]; + texCoords2[3] += lightDir1[3] * normal1[3]; + + texCoords2[0] += lightDir2[0] * normal2[0]; + texCoords2[1] += lightDir2[1] * normal2[1]; + texCoords2[2] += lightDir2[2] * normal2[2]; + texCoords2[3] += lightDir2[3] * normal2[3]; + + for ( int j = 0; j < 4; j++ ) { + int n = usedVertNums[j]; + + texCoords[n][0] = texCoords0[j]; + texCoords[n][1] = texCoords1[j]; + texCoords[n][2] = texCoords2[j]; + texCoords[n][3] = 1.0f; + } + + numUsedVerts = 0; + } + + for ( int i = 0; i < numUsedVerts; i++ ) { + float temp; + + temp = lightDir0[i] * lightDir0[i] + lightDir1[i] * lightDir1[i] + lightDir2[i] * lightDir2[i]; + temp = idMath::RSqrt( temp ); + + lightDir0[i] *= temp; + lightDir1[i] *= temp; + lightDir2[i] *= temp; + + temp = viewDir0[i] * viewDir0[i] + viewDir1[i] * viewDir1[i] + viewDir2[i] * viewDir2[i]; + temp = idMath::RSqrt( temp ); + + viewDir0[i] *= temp; + viewDir1[i] *= temp; + viewDir2[i] *= temp; + + lightDir0[i] += viewDir0[i]; + lightDir1[i] += viewDir1[i]; + lightDir2[i] += viewDir2[i]; + + texCoords0[i] = lightDir0[i] * tangent0[i] + lightDir1[i] * tangent1[i] + lightDir2[i] * tangent2[i]; + texCoords1[i] = lightDir0[i] * tangent3[i] + lightDir1[i] * tangent4[i] + lightDir2[i] * tangent5[i]; + texCoords2[i] = lightDir0[i] * normal0[i] + lightDir1[i] * normal1[i] + lightDir2[i] * normal2[i]; + + int n = usedVertNums[i]; + texCoords[n][0] = texCoords0; + texCoords[n][1] = texCoords1; + texCoords[n][2] = texCoords2; + texCoords[n][3] = 1.0f; + } + +#endif +} + +/* +============ +idSIMD_SSE::CreateShadowCache +============ +*/ +int VPCALL idSIMD_SSE::CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ) { +#if 1 + int outVerts; + + __asm { + push ebx + + mov esi, lightOrigin + movaps xmm5, SIMD_SP_lastOne + movss xmm6, [esi+0] + movhps xmm6, [esi+4] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 3, 1 ) + orps xmm6, SIMD_SP_lastOne + movaps xmm7, xmm6 + + xor ebx, ebx + xor ecx, ecx + + mov edx, vertRemap + mov esi, verts + mov edi, vertexCache + mov eax, numVerts + and eax, ~3 + jz done4 + shl eax, 2 + add edx, eax + neg eax + + loop4: + prefetchnta [edx+128] + prefetchnta [esi+4*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + + cmp dword ptr [edx+eax+0], ebx + jne skip1 + + mov dword ptr [edx+eax+0], ecx + movss xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + add ecx, 2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ); + orps xmm0, xmm5 + movaps [edi+0*16], xmm0 + subps xmm0, xmm6 + movaps [edi+1*16], xmm0 + add edi, 2*16 + + skip1: + cmp dword ptr [edx+eax+4], ebx + jne skip2 + + mov dword ptr [edx+eax+4], ecx + movss xmm1, [esi+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + movhps xmm1, [esi+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + add ecx, 2 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 2, 3, 1 ) + orps xmm1, xmm5 + movaps [edi+0*16], xmm1 + subps xmm1, xmm7 + movaps [edi+1*16], xmm1 + add edi, 2*16 + + skip2: + cmp dword ptr [edx+eax+8], ebx + jne skip3 + + mov dword ptr [edx+eax+8], ecx + movss xmm2, [esi+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm2, [esi+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + add ecx, 2 + shufps xmm2, xmm2, R_SHUFFLE_PS( 2, 3, 0, 1 ); + orps xmm2, xmm5 + movaps [edi+0*16], xmm2 + subps xmm2, xmm6 + movaps [edi+1*16], xmm2 + add edi, 2*16 + + skip3: + cmp dword ptr [edx+eax+12], ebx + jne skip4 + + mov dword ptr [edx+eax+12], ecx + movss xmm3, [esi+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + movhps xmm3, [esi+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+4] + add ecx, 2 + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 2, 3, 1 ) + orps xmm3, xmm5 + movaps [edi+0*16], xmm3 + subps xmm3, xmm7 + movaps [edi+1*16], xmm3 + add edi, 2*16 + + skip4: + add esi, 4*DRAWVERT_SIZE + add eax, 4*4 + jl loop4 + + done4: + mov eax, numVerts + and eax, 3 + jz done1 + shl eax, 2 + add edx, eax + neg eax + + loop1: + cmp dword ptr [edx+eax+0], ebx + jne skip0 + + mov dword ptr [edx+eax+0], ecx + movss xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8] + movhps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0] + add ecx, 2 + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ) + orps xmm0, xmm5 + movaps [edi+0*16], xmm0 + subps xmm0, xmm6 + movaps [edi+1*16], xmm0 + add edi, 2*16 + + skip0: + + add esi, DRAWVERT_SIZE + add eax, 4 + jl loop1 + + done1: + pop ebx + mov outVerts, ecx + } + return outVerts; + +#else + + int outVerts = 0; + for ( int i = 0; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; + +#endif +} + +/* +============ +idSIMD_SSE::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_SSE::CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ) { +#if 1 + + assert_16_byte_aligned( vertexCache ); + assert_16_byte_aligned( verts ); + assert_16_byte_aligned( DRAWVERT_SIZE ); + assert_16_byte_aligned( DRAWVERT_XYZ_OFFSET ); + + __asm { + movaps xmm4, SIMD_SP_clearLast + movaps xmm5, SIMD_SP_lastOne + movaps xmm6, xmm4 + movaps xmm7, xmm5 + + mov esi, verts + mov edi, vertexCache + mov eax, numVerts + and eax, ~3 + jz done4 + shl eax, 5 + add edi, eax + neg eax + + loop4: + prefetchnta [esi+4*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + + movaps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm0, xmm4 + movaps [edi+eax+1*16], xmm0 + orps xmm0, xmm5 + movaps [edi+eax+0*16], xmm0 + + movaps xmm1, [esi+1*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm1, xmm6 + movaps [edi+eax+3*16], xmm1 + orps xmm1, xmm7 + movaps [edi+eax+2*16], xmm1 + + movaps xmm2, [esi+2*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm2, xmm4 + movaps [edi+eax+5*16], xmm2 + orps xmm2, xmm5 + movaps [edi+eax+4*16], xmm2 + + movaps xmm3, [esi+3*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm3, xmm6 + movaps [edi+eax+7*16], xmm3 + orps xmm3, xmm7 + movaps [edi+eax+6*16], xmm3 + + add esi, 4*DRAWVERT_SIZE + add eax, 4*8*4 + jl loop4 + + done4: + mov eax, numVerts + and eax, 3 + jz done1 + shl eax, 5 + add edi, eax + neg eax + + loop1: + movaps xmm0, [esi+0*DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET] + andps xmm0, xmm4 + movaps [edi+eax+1*16], xmm0 + orps xmm0, xmm5 + movaps [edi+eax+0*16], xmm0 + + add esi, DRAWVERT_SIZE + add eax, 8*4 + jl loop1 + + done1: + } + return numVerts * 2; + +#else + + for ( int i = 0; i < numVerts; i++ ) { + const float *v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; + +#endif +} + +/* +============ +SSE_UpSample11kHzMonoPCMTo44kHz +============ +*/ +static void SSE_UpSample11kHzMonoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*4*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-2*4*4+0], xmm0 + movhps [edi-2*4*4+8], xmm0 + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-1*4*4+0], xmm1 + movhps [edi-1*4*4+8], xmm1 + + add eax, 2*2 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movsx ecx, word ptr [esi] + cvtsi2ss xmm0, ecx + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi+0], xmm0 + movhps [edi+8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample11kHzStereoPCMTo44kHz +============ +*/ +static void SSE_UpSample11kHzStereoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + test eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 8*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + + unpcklps xmm0, xmm1 + + movlps [edi-8*4+0], xmm0 + movlps [edi-8*4+8], xmm0 + movlps [edi-4*4+0], xmm0 + movlps [edi-4*4+8], xmm0 + + add eax, 2*2 + jl loop2 + + done2: + } +} + +/* +============ +SSE_UpSample22kHzMonoPCMTo44kHz +============ +*/ +static void SSE_UpSample22kHzMonoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 4*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-4*4+0], xmm0 + movhps [edi-4*4+8], xmm0 + + add eax, 2*2 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movsx ecx, word ptr [esi] + cvtsi2ss xmm0, ecx + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample22kHzStereoPCMTo44kHz +============ +*/ +static void SSE_UpSample22kHzStereoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + test eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 4*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + movss [edi-4*4], xmm0 + movss [edi-2*4], xmm0 + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + movss [edi-3*4], xmm1 + movss [edi-1*4], xmm1 + + add eax, 2*2 + jl loop2 + + done2: + } +} + +/* +============ +SSE_UpSample44kHzMonoPCMTo44kHz +============ +*/ +static void SSE_UpSample44kHzMonoPCMTo44kHz( float *dest, const short *src, const int numSamples ) { + __asm { + mov esi, src + mov edi, dest + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*4 + + movsx ecx, word ptr [esi+eax+0] + cvtsi2ss xmm0, ecx + movss [edi-2*4], xmm0 + + movsx edx, word ptr [esi+eax+2] + cvtsi2ss xmm1, edx + movss [edi-1*4], xmm1 + + add eax, 2*2 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movsx ecx, word ptr [esi] + cvtsi2ss xmm0, ecx + movss [edi], xmm0 + + done: + } +} + +/* +============ +idSIMD_SSE::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_SSE::UpSamplePCMTo44kHz( float *dest, const short *src, const int numSamples, const int kHz, const int numChannels ) { + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + SSE_UpSample11kHzMonoPCMTo44kHz( dest, src, numSamples ); + } else { + SSE_UpSample11kHzStereoPCMTo44kHz( dest, src, numSamples ); + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + SSE_UpSample22kHzMonoPCMTo44kHz( dest, src, numSamples ); + } else { + SSE_UpSample22kHzStereoPCMTo44kHz( dest, src, numSamples ); + } + } else if ( kHz == 44100 ) { + SSE_UpSample44kHzMonoPCMTo44kHz( dest, src, numSamples ); + } else { + assert( 0 ); + } +} + +/* +============ +SSE_UpSample11kHzMonoOGGTo44kHz +============ +*/ +static void SSE_UpSample11kHzMonoOGGTo44kHz( float *dest, const float *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 2 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*16 + + movss xmm0, [esi+eax+0] + mulss xmm0, xmm7 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-32], xmm0 + movlps [edi-24], xmm0 + + movss xmm1, [esi+eax+4] + mulss xmm1, xmm7 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi-16], xmm1 + movlps [edi- 8], xmm1 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [esi] + mulss xmm0, xmm7 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi+0], xmm0 + movlps [edi+8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample11kHzStereoOGGTo44kHz +============ +*/ +static void SSE_UpSample11kHzStereoOGGTo44kHz( float *dest, const float * const *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov ecx, [esi+0] + mov edx, [esi+4] + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add ecx, eax + add edx, eax + neg eax + + align 16 + loop2: + add edi, 4*16 + + movlps xmm0, [ecx+eax] + movlps xmm1, [edx+eax] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi-8*8], xmm0 + movlps [edi-7*8], xmm0 + movlps [edi-6*8], xmm0 + movlps [edi-5*8], xmm0 + movhps [edi-4*8], xmm0 + movhps [edi-3*8], xmm0 + movhps [edi-2*8], xmm0 + movhps [edi-1*8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [ecx] + movss xmm1, [edx] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi+0*8], xmm0 + movlps [edi+1*8], xmm0 + movlps [edi+2*8], xmm0 + movlps [edi+3*8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample22kHzMonoOGGTo44kHz +============ +*/ +static void SSE_UpSample22kHzMonoOGGTo44kHz( float *dest, const float *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 2 + add esi, eax + neg eax + + align 16 + loop2: + add edi, 2*8 + + movss xmm0, [esi+eax+0] + movss xmm1, [esi+eax+4] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm7 + movlps [edi-16], xmm0 + movhps [edi- 8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [esi] + mulss xmm0, xmm7 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movlps [edi+0], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample22kHzStereoOGGTo44kHz +============ +*/ +static void SSE_UpSample22kHzStereoOGGTo44kHz( float *dest, const float * const *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov ecx, [esi+0] + mov edx, [esi+4] + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add ecx, eax + add edx, eax + neg eax + + align 16 + loop2: + add edi, 2*16 + + movlps xmm0, [ecx+eax] + movlps xmm1, [edx+eax] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi-4*8], xmm0 + movlps [edi-3*8], xmm0 + movhps [edi-2*8], xmm0 + movhps [edi-1*8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [ecx] + movss xmm1, [edx] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi+0*8], xmm0 + movlps [edi+1*8], xmm0 + + done: + } +} + +/* +============ +SSE_UpSample44kHzMonoOGGTo44kHz +============ +*/ +static void SSE_UpSample44kHzMonoOGGTo44kHz( float *dest, const float *src, const int numSamples ) { + float constant = 32768.0f; + KFLOAT_CA( mul, dest, src, constant, numSamples ) +} + +/* +============ +SSE_UpSample44kHzStereoOGGTo44kHz +============ +*/ +static void SSE_UpSample44kHzStereoOGGTo44kHz( float *dest, const float * const *src, const int numSamples ) { + float constant = 32768.0f; + __asm { + mov esi, src + mov ecx, [esi+0] + mov edx, [esi+4] + mov edi, dest + movss xmm7, constant + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + mov eax, numSamples + and eax, ~1 + jz done2 + shl eax, 1 + add ecx, eax + add edx, eax + neg eax + + align 16 + loop2: + add edi, 16 + + movlps xmm0, [ecx+eax] + movlps xmm1, [edx+eax] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi-2*8], xmm0 + movhps [edi-1*8], xmm0 + + add eax, 2*4 + jl loop2 + + done2: + mov eax, numSamples + and eax, 1 + jz done + + movss xmm0, [ecx] + movss xmm1, [edx] + unpcklps xmm0, xmm1 + mulps xmm0, xmm7 + movlps [edi+0*8], xmm0 + + done: + } +} + +/* +============ +idSIMD_SSE::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_SSE::UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ) { + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + SSE_UpSample11kHzMonoOGGTo44kHz( dest, ogg[0], numSamples ); + } else { + SSE_UpSample11kHzStereoOGGTo44kHz( dest, ogg, numSamples ); + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + SSE_UpSample22kHzMonoOGGTo44kHz( dest, ogg[0], numSamples ); + } else { + SSE_UpSample22kHzStereoOGGTo44kHz( dest, ogg, numSamples ); + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + SSE_UpSample44kHzMonoOGGTo44kHz( dest, ogg[0], numSamples ); + } else { + SSE_UpSample44kHzStereoOGGTo44kHz( dest, ogg, numSamples ); + } + } else { + assert( 0 ); + } +} + +/* +============ +idSIMD_SSE::MixSoundTwoSpeakerMono +============ +*/ +void VPCALL idSIMD_SSE::MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { +#if 1 + + ALIGN16( float incs[2]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm6, [ecx] + xorps xmm7, xmm7 + movhps xmm7, incs + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + addps xmm6, xmm7 + shufps xmm7, xmm7, R_SHUFFLE_PS( 2, 3, 2, 3 ) + addps xmm7, xmm7 + + loop16: + add edi, 4*4*4 + + movaps xmm0, [esi+eax+0*4*4] + movaps xmm1, xmm0 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + mulps xmm0, xmm6 + addps xmm0, [edi-4*4*4] + addps xmm6, xmm7 + movaps [edi-4*4*4], xmm0 + + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 3, 3 ) + mulps xmm1, xmm6 + addps xmm1, [edi-3*4*4] + addps xmm6, xmm7 + movaps [edi-3*4*4], xmm1 + + movaps xmm2, [esi+eax+1*4*4] + movaps xmm3, xmm2 + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + mulps xmm2, xmm6 + addps xmm2, [edi-2*4*4] + addps xmm6, xmm7 + movaps [edi-2*4*4], xmm2 + + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 3, 3 ) + mulps xmm3, xmm6 + addps xmm3, [edi-1*4*4] + addps xmm6, xmm7 + movaps [edi-1*4*4], xmm3 + + add eax, 2*4*4 + + jl loop16 + } + +#else + + int i; + float incL; + float incR; + float sL0, sL1; + float sR0, sR1; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sR0 = lastV[1]; + sL1 = lastV[0] + incL; + sR1 = lastV[1] + incR; + + incL *= 2; + incR *= 2; + + for( i = 0; i < MIXBUFFER_SAMPLES; i += 2 ) { + mixBuffer[i*2+0] += samples[i+0] * sL0; + mixBuffer[i*2+1] += samples[i+0] * sR0; + mixBuffer[i*2+2] += samples[i+1] * sL1; + mixBuffer[i*2+3] += samples[i+1] * sR1; + sL0 += incL; + sR0 += incR; + sL1 += incL; + sR1 += incR; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundTwoSpeakerMono +============ +*/ +void VPCALL idSIMD_SSE::MixSoundTwoSpeakerMonoSimple( float *mixBuffer, const float *samples, const int numSamples ) { +#if 1 + + assert( numSamples == MIXBUFFER_SAMPLES ); + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add esi, eax + neg eax + + loop16: + add edi, 4*4*4 + + movups xmm0, [esi+eax+0*4*4] + movups xmm1, xmm0 + movups xmm2, [esi+eax+1*4*4] + movups xmm3, xmm2 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 3, 3 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 1, 1 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 2, 2, 3, 3 ) + + addps xmm0, [edi-4*4*4] + addps xmm1, [edi-3*4*4] + addps xmm2, [edi-2*4*4] + addps xmm3, [edi-1*4*4] + + movaps [edi-4*4*4], xmm0 + movaps [edi-3*4*4], xmm1 + movaps [edi-2*4*4], xmm2 + movaps [edi-1*4*4], xmm3 + + add eax, 2*4*4 + + jl loop16 + } + +#else + + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerMonoSimple"); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j += 2 ) { + mixBuffer[j*2+0] += samples[j]; + mixBuffer[j*2+1] += samples[j]; + mixBuffer[j*2+2] += samples[j+1]; + mixBuffer[j*2+3] += samples[j+1]; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundTwoSpeakerStereo +============ +*/ +void VPCALL idSIMD_SSE::MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ) { +#if 1 + + ALIGN16( float incs[2]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 3 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm6, [ecx] + xorps xmm7, xmm7 + movhps xmm7, incs + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 1, 0, 1 ) + addps xmm6, xmm7 + shufps xmm7, xmm7, R_SHUFFLE_PS( 2, 3, 2, 3 ) + addps xmm7, xmm7 + + loop16: + add edi, 4*4*4 + + movaps xmm0, [esi+eax+0*4*4] + mulps xmm0, xmm6 + addps xmm0, [edi-4*4*4] + addps xmm6, xmm7 + movaps [edi-4*4*4], xmm0 + + movaps xmm2, [esi+eax+1*4*4] + mulps xmm2, xmm6 + addps xmm2, [edi-3*4*4] + addps xmm6, xmm7 + movaps [edi-3*4*4], xmm2 + + movaps xmm3, [esi+eax+2*4*4] + mulps xmm3, xmm6 + addps xmm3, [edi-2*4*4] + addps xmm6, xmm7 + movaps [edi-2*4*4], xmm3 + + movaps xmm4, [esi+eax+3*4*4] + mulps xmm4, xmm6 + addps xmm4, [edi-1*4*4] + addps xmm6, xmm7 + movaps [edi-1*4*4], xmm4 + + add eax, 4*4*4 + + jl loop16 + } + +#else + + int i; + float incL; + float incR; + float sL0, sL1; + float sR0, sR1; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sR0 = lastV[1]; + sL1 = lastV[0] + incL; + sR1 = lastV[1] + incR; + + incL *= 2; + incR *= 2; + + for( i = 0; i < MIXBUFFER_SAMPLES; i += 2 ) { + mixBuffer[i*2+0] += samples[i*2+0] * sL0; + mixBuffer[i*2+1] += samples[i*2+1] * sR0; + mixBuffer[i*2+2] += samples[i*2+2] * sL1; + mixBuffer[i*2+3] += samples[i*2+3] * sR1; + sL0 += incL; + sR0 += incR; + sL1 += incL; + sR1 += incR; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundSixSpeakerMono +============ +*/ +void VPCALL idSIMD_SSE::MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { +#if 1 + + ALIGN16( float incs[6]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incs[2] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incs[3] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incs[4] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incs[5] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm2, [ecx+ 0] + movhps xmm2, [ecx+ 8] + movlps xmm3, [ecx+16] + movaps xmm4, xmm2 + shufps xmm3, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm4, xmm3, R_SHUFFLE_PS( 2, 3, 0, 1 ) + + xorps xmm5, xmm5 + movhps xmm5, incs + movlps xmm7, incs+8 + movhps xmm7, incs+16 + addps xmm3, xmm5 + addps xmm4, xmm7 + shufps xmm5, xmm7, R_SHUFFLE_PS( 2, 3, 0, 1 ) + movaps xmm6, xmm7 + shufps xmm6, xmm5, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm5, xmm5 + addps xmm6, xmm6 + addps xmm7, xmm7 + + loop24: + add edi, 6*16 + + movaps xmm0, [esi+eax] + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm2 + addps xmm1, [edi-6*16] + addps xmm2, xmm5 + movaps [edi-6*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 1, 1 ) + mulps xmm1, xmm3 + addps xmm1, [edi-5*16] + addps xmm3, xmm6 + movaps [edi-5*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 1, 1, 1 ) + mulps xmm1, xmm4 + addps xmm1, [edi-4*16] + addps xmm4, xmm7 + movaps [edi-4*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 2, 2 ) + mulps xmm1, xmm2 + addps xmm1, [edi-3*16] + addps xmm2, xmm5 + movaps [edi-3*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 2, 2, 3, 3 ) + mulps xmm1, xmm3 + addps xmm1, [edi-2*16] + addps xmm3, xmm6 + movaps [edi-2*16], xmm1 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 3, 3, 3 ) + mulps xmm0, xmm4 + addps xmm0, [edi-1*16] + addps xmm4, xmm7 + movaps [edi-1*16], xmm0 + + add eax, 4*4 + + jl loop24 + } + +#else + + int i; + float sL0, sL1, sL2, sL3, sL4, sL5, sL6, sL7, sL8, sL9, sL10, sL11; + float incL0, incL1, incL2, incL3, incL4, incL5; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sL1 = lastV[1]; + sL2 = lastV[2]; + sL3 = lastV[3]; + sL4 = lastV[4]; + sL5 = lastV[5]; + + sL6 = lastV[0] + incL0; + sL7 = lastV[1] + incL1; + sL8 = lastV[2] + incL2; + sL9 = lastV[3] + incL3; + sL10 = lastV[4] + incL4; + sL11 = lastV[5] + incL5; + + incL0 *= 2; + incL1 *= 2; + incL2 *= 2; + incL3 *= 2; + incL4 *= 2; + incL5 *= 2; + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + mixBuffer[i*6+ 0] += samples[i+0] * sL0; + mixBuffer[i*6+ 1] += samples[i+0] * sL1; + mixBuffer[i*6+ 2] += samples[i+0] * sL2; + mixBuffer[i*6+ 3] += samples[i+0] * sL3; + + mixBuffer[i*6+ 4] += samples[i+0] * sL4; + mixBuffer[i*6+ 5] += samples[i+0] * sL5; + mixBuffer[i*6+ 6] += samples[i+1] * sL6; + mixBuffer[i*6+ 7] += samples[i+1] * sL7; + + mixBuffer[i*6+ 8] += samples[i+1] * sL8; + mixBuffer[i*6+ 9] += samples[i+1] * sL9; + mixBuffer[i*6+10] += samples[i+1] * sL10; + mixBuffer[i*6+11] += samples[i+1] * sL11; + + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + + sL4 += incL4; + sL5 += incL5; + sL6 += incL0; + sL7 += incL1; + + sL8 += incL2; + sL9 += incL3; + sL10 += incL4; + sL11 += incL5; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixSoundSixSpeakerStereo +============ +*/ +void VPCALL idSIMD_SSE::MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ) { +#if 1 + + ALIGN16( float incs[6]; ) + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + incs[0] = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incs[1] = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incs[2] = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incs[3] = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incs[4] = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incs[5] = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + __asm { + mov eax, MIXBUFFER_SAMPLES + mov edi, mixBuffer + mov esi, samples + shl eax, 3 + add esi, eax + neg eax + + mov ecx, lastV + movlps xmm2, [ecx+ 0] + movhps xmm2, [ecx+ 8] + movlps xmm3, [ecx+16] + movaps xmm4, xmm2 + shufps xmm3, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm4, xmm3, R_SHUFFLE_PS( 2, 3, 0, 1 ) + + xorps xmm5, xmm5 + movhps xmm5, incs + movlps xmm7, incs+ 8 + movhps xmm7, incs+16 + addps xmm3, xmm5 + addps xmm4, xmm7 + shufps xmm5, xmm7, R_SHUFFLE_PS( 2, 3, 0, 1 ) + movaps xmm6, xmm7 + shufps xmm6, xmm5, R_SHUFFLE_PS( 2, 3, 0, 1 ) + addps xmm5, xmm5 + addps xmm6, xmm6 + addps xmm7, xmm7 + + loop12: + add edi, 3*16 + + movaps xmm0, [esi+eax+0] + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 0, 0 ) + mulps xmm1, xmm2 + addps xmm1, [edi-3*16] + addps xmm2, xmm5 + movaps [edi-3*16], xmm1 + + movaps xmm1, xmm0 + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 1, 2, 3 ) + mulps xmm1, xmm3 + addps xmm1, [edi-2*16] + addps xmm3, xmm6 + movaps [edi-2*16], xmm1 + + add eax, 4*4 + + shufps xmm0, xmm0, R_SHUFFLE_PS( 2, 2, 2, 3 ) + mulps xmm0, xmm4 + addps xmm0, [edi-1*16] + addps xmm4, xmm7 + movaps [edi-1*16], xmm0 + + jl loop12 + + emms + } + +#else + + int i; + float sL0, sL1, sL2, sL3, sL4, sL5, sL6, sL7, sL8, sL9, sL10, sL11; + float incL0, incL1, incL2, incL3, incL4, incL5; + + assert( numSamples == MIXBUFFER_SAMPLES ); + assert( SPEAKER_RIGHT == 1 ); + assert( SPEAKER_BACKRIGHT == 5 ); + + incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + sL0 = lastV[0]; + sL1 = lastV[1]; + sL2 = lastV[2]; + sL3 = lastV[3]; + sL4 = lastV[4]; + sL5 = lastV[5]; + + sL6 = lastV[0] + incL0; + sL7 = lastV[1] + incL1; + sL8 = lastV[2] + incL2; + sL9 = lastV[3] + incL3; + sL10 = lastV[4] + incL4; + sL11 = lastV[5] + incL5; + + incL0 *= 2; + incL1 *= 2; + incL2 *= 2; + incL3 *= 2; + incL4 *= 2; + incL5 *= 2; + + for( i = 0; i <= MIXBUFFER_SAMPLES - 2; i += 2 ) { + mixBuffer[i*6+ 0] += samples[i*2+0+0] * sL0; + mixBuffer[i*6+ 1] += samples[i*2+0+1] * sL1; + mixBuffer[i*6+ 2] += samples[i*2+0+0] * sL2; + mixBuffer[i*6+ 3] += samples[i*2+0+0] * sL3; + + mixBuffer[i*6+ 4] += samples[i*2+0+0] * sL4; + mixBuffer[i*6+ 5] += samples[i*2+0+1] * sL5; + mixBuffer[i*6+ 6] += samples[i*2+2+0] * sL6; + mixBuffer[i*6+ 7] += samples[i*2+2+1] * sL7; + + mixBuffer[i*6+ 8] += samples[i*2+2+0] * sL8; + mixBuffer[i*6+ 9] += samples[i*2+2+0] * sL9; + mixBuffer[i*6+10] += samples[i*2+2+0] * sL10; + mixBuffer[i*6+11] += samples[i*2+2+1] * sL11; + + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + + sL4 += incL4; + sL5 += incL5; + sL6 += incL0; + sL7 += incL1; + + sL8 += incL2; + sL9 += incL3; + sL10 += incL4; + sL11 += incL5; + } + +#endif +} + +/* +============ +idSIMD_SSE::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_SSE::MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) { +#if 1 + + assert( ( numSamples % MIXBUFFER_SAMPLES ) == 0 ); + + __asm { + + mov eax, numSamples + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add edi, eax + neg eax + + loop16: + + movaps xmm0, [edi+eax+0*16] + movaps xmm2, [edi+eax+1*16] + movaps xmm4, [edi+eax+2*16] + movaps xmm6, [edi+eax+3*16] + + add esi, 4*4*2 + + movhlps xmm1, xmm0 + movhlps xmm3, xmm2 + movhlps xmm5, xmm4 + movhlps xmm7, xmm6 + + prefetchnta [edi+eax+64] + + cvtps2pi mm0, xmm0 + cvtps2pi mm2, xmm2 + cvtps2pi mm4, xmm4 + cvtps2pi mm6, xmm6 + + prefetchnta [edi+eax+128] + + cvtps2pi mm1, xmm1 + cvtps2pi mm3, xmm3 + cvtps2pi mm5, xmm5 + cvtps2pi mm7, xmm7 + + add eax, 4*16 + + packssdw mm0, mm1 + packssdw mm2, mm3 + packssdw mm4, mm5 + packssdw mm6, mm7 + + movq [esi-4*4*2], mm0 + movq [esi-3*4*2], mm2 + movq [esi-2*4*2], mm4 + movq [esi-1*4*2], mm6 + + jl loop16 + + emms + } + +#else + + for ( int i = 0; i < numSamples; i++ ) { + if ( mixBuffer[i] <= -32768.0f ) { + samples[i] = -32768; + } else if ( mixBuffer[i] >= 32767.0f ) { + samples[i] = 32767; + } else { + samples[i] = (short) mixBuffer[i]; + } + } + +#endif +} + +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT +/* +============ +idSIMD_SSE::TransformVertsMinMax4Bone +============ +*/ +// dluetscher: added TransformVertsMinMax to transform an array of index-weighted vertices into +// an array of idSilTraceVerts, while simulatenously calculating the bounds +static ALIGN16( float packedOnes[4] ) = { 1.0f, 1.0f, 1.0f, 1.0f }; + +void VPCALL idSIMD_SSE::TransformVertsMinMax4Bone( rvSilTraceVertT *silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte *vertexInputData, + int vertStride, int numVerts, + float *skinToModelTransforms ) { + byte *vertexOutputData, *endVertexInputData; + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + vertexOutputData = (byte*) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + __asm + { + mov eax, vertexInputData + mov ecx, skinToModelTransforms + + ; + ; load the indices into ebx, and 4 weights into xmm0 + ; + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + shl edx, 6 ; multiply i0 by 64 + movaps xmm0, [eax+16] ; xmm0 = w0, w1, w2, w3 + and edx, 0x3FC0 + add edx, ecx + movaps xmm7, xmm0 + + ; + ; load the first matrix, scale by first weight + ; + movaps xmm1, [edx] ; load first matrix + shufps xmm7, xmm0, 0x00 ; broadcast w0 to all 4 values + movaps xmm2, [edx+16] + movaps xmm3, [edx+32] + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + mulps xmm1, xmm7 ; scale first matrix by first weight + shr edx, 2 ; multiply i1 by 64 (right shift by 8, left shift by 6) + mulps xmm2, xmm7 + and edx, 0x3FC0 + mulps xmm3, xmm7 + add edx, ecx + movaps xmm7, xmm0 + + ; + ; load the second matrix, scale by the second weight, accumulate + ; + movaps xmm4, [edx] ; load second matrix + shufps xmm7, xmm0, 0x55 ; broadcast w1 to all 4 values + movaps xmm5, [edx+16] + movaps xmm6, [edx+32] + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + mulps xmm4, xmm7 ; scale second matrix by second weight + shr edx, 10 ; multiply i2 by 64 (right shift by 16, left shift by 6) + mulps xmm5, xmm7 + addps xmm1, xmm4 + and edx, 0x3FC0 + mulps xmm6, xmm7 + addps xmm2, xmm5 + add edx, ecx + movaps xmm7, xmm0 + addps xmm3, xmm6 + + ; + ; load the third matrix, scale by the third weight, accumulate + ; + movaps xmm4, [edx] ; load third matrix + shufps xmm7, xmm0, 0xAA ; broadcast w3 to all 4 values + movaps xmm5, [edx+16] + movaps xmm6, [edx+32] + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + mulps xmm4, xmm7 ; scale third matrix by third weight + shr edx, 18 ; multiply i3 by 64 (right shift by 24, left shift by 6) + mulps xmm5, xmm7 + addps xmm1, xmm4 + and edx, 0x3FC0 + mulps xmm6, xmm7 + addps xmm2, xmm5 + add edx, ecx + movaps xmm7, xmm0 + addps xmm3, xmm6 + + ; + ; load the fourth matrix, scale by the fourth weight, accumulate + ; + movaps xmm4, [edx] ; load fourth matrix + shufps xmm7, xmm0, 0xFF ; broadcast w4 to all 4 values + movaps xmm5, [edx+16] + movaps xmm6, [edx+32] + movaps xmm0, [eax] ; xmm0 = x, y, z + + mulps xmm4, xmm7 ; scale fourth matrix by fourth weight + mulps xmm5, xmm7 + mulps xmm6, xmm7 + movaps xmm7, packedOnes + + addps xmm1, xmm4 + movaps xmm4, xmm0 + addps xmm2, xmm5 + addps xmm3, xmm6 + movss xmm4, xmm7 + + ; + ; transform the position by the combined matrix + ; + mov eax, vertexOutputData + mov edx, curMin + shufps xmm0, xmm4, 0x24 + mov ecx, curMax + + mulps xmm1, xmm0 + mulps xmm2, xmm0 + mulps xmm3, xmm0 + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + shufps xmm4, xmm4, 0x4E + shufps xmm5, xmm5, 0x4E + shufps xmm6, xmm6, 0x4E + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + movaps xmm0, [edx] + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + movaps xmm7, [ecx] + + shufps xmm4, xmm4, 0x11 + shufps xmm5, xmm5, 0x11 + shufps xmm6, xmm6, 0x11 + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + shufps xmm1, xmm2, 0 + shufps xmm1, xmm3, 0x8 + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps [eax], xmm1 + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +void VPCALL idSIMD_SSE::TransformVertsMinMax1Bone( rvSilTraceVertT *silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte *vertexInputData, + int vertStride, int numVerts, + float *skinToModelTransforms ) { + byte *vertexOutputData, *endVertexInputData; + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + vertexOutputData = (byte*) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + __asm + { + mov eax, vertexInputData + mov ecx, skinToModelTransforms + + ; + ; load the indices into ebx + ; + mov edx, [eax+12] ; edx = i0, i1, i2, i3 + movaps xmm0, [eax] ; xmm0 = x, y, z + shl edx, 6 ; multiply i0 by 64 + movaps xmm7, packedOnes + and edx, 0x3FC0 + movaps xmm4, xmm0 + add edx, ecx + movss xmm4, xmm7 + + ; + ; load the first matrix + ; + movaps xmm1, [edx] ; load first matrix + movaps xmm2, [edx+16] + movaps xmm3, [edx+32] + + ; + ; transform the position by the combined matrix + ; + mov eax, vertexOutputData + mov edx, curMin + shufps xmm0, xmm4, 0x24 + mov ecx, curMax + + mulps xmm1, xmm0 + mulps xmm2, xmm0 + mulps xmm3, xmm0 + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + shufps xmm4, xmm4, 0x4E + shufps xmm5, xmm5, 0x4E + shufps xmm6, xmm6, 0x4E + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + movaps xmm0, [edx] + + movaps xmm4, xmm1 + movaps xmm5, xmm2 + movaps xmm6, xmm3 + + movaps xmm7, [ecx] + + shufps xmm4, xmm4, 0x11 + shufps xmm5, xmm5, 0x11 + shufps xmm6, xmm6, 0x11 + + addps xmm1, xmm4 + addps xmm2, xmm5 + addps xmm3, xmm6 + + shufps xmm1, xmm2, 0 + shufps xmm1, xmm3, 0x8 + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps [eax], xmm1 + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idVec3 &constant, const rvSilTraceVertT *src, const int count ) { + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + // 0, 1, 2 + // 3, 4, 5 + // 6, 7, 8 + // 9, 10, 11 + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert4: + movss xmm0, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, X, X + movss xmm2, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 2, X, X, X + movhps xmm0, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, 0, 1 + movaps xmm1, xmm0 // 3, X, 0, 1 + + movlps xmm1, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 4, 5, 0, 1 + shufps xmm2, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) // 2, X, 4, 5 + + movss xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, X, X + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, 6, 7 + shufps xmm0, xmm3, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 0, 3, 6, 9 + + movlps xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 10, 11, 6, 7 + shufps xmm1, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 1, 4, 7, 10 + + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 10, 11, 8, X + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 3, 2, 1 ) // 2, 5, 8, 11 + + add ecx, 16 + add eax, 4*SILTRACEVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm1, [esi+eax+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm2, [esi+eax+SILTRACEVERT_XYZW_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + add ecx, 4 + addss xmm0, xmm1 + add eax, SILTRACEVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + +/* +============ +idSIMD_SSE::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_SSE::Dot( float *dst, const idPlane &constant, const rvSilTraceVertT *src, const int count ) { + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + // 0, 1, 2 + // 3, 4, 5 + // 6, 7, 8 + // 9, 10, 11 + + __asm { + mov eax, count + mov edi, constant + mov edx, eax + mov esi, src + mov ecx, dst + and eax, ~3 + + movss xmm4, [edi+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [edi+4] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [edi+8] + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm7, [edi+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + jz startVert1 + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert4: + movss xmm0, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, X, X + movss xmm2, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 2, X, X, X + movhps xmm0, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 3, X, 0, 1 + movaps xmm1, xmm0 // 3, X, 0, 1 + + movlps xmm1, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 4, 5, 0, 1 + shufps xmm2, xmm1, R_SHUFFLE_PS( 0, 1, 0, 1 ) // 2, X, 4, 5 + + movss xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, X, X + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] // 9, X, 6, 7 + shufps xmm0, xmm3, R_SHUFFLE_PS( 2, 0, 2, 0 ) // 0, 3, 6, 9 + + movlps xmm3, [esi+eax+3*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] // 10, 11, 6, 7 + shufps xmm1, xmm3, R_SHUFFLE_PS( 3, 0, 3, 0 ) // 1, 4, 7, 10 + + movhps xmm3, [esi+eax+2*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] // 10, 11, 8, X + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 3, 2, 1 ) // 2, 5, 8, 11 + + add ecx, 16 + add eax, 4*SILTRACEVERT_SIZE + + mulps xmm0, xmm4 + mulps xmm1, xmm5 + mulps xmm2, xmm6 + addps xmm0, xmm7 + addps xmm0, xmm1 + addps xmm0, xmm2 + + movlps [ecx-16+0], xmm0 + movhps [ecx-16+8], xmm0 + jl loopVert4 + + startVert1: + and edx, 3 + jz done + + loopVert1: + movss xmm0, [esi+eax+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm1, [esi+eax+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm2, [esi+eax+SILTRACEVERT_XYZW_OFFSET+8] + mulss xmm0, xmm4 + mulss xmm1, xmm5 + mulss xmm2, xmm6 + addss xmm0, xmm7 + add ecx, 4 + addss xmm0, xmm1 + add eax, SILTRACEVERT_SIZE + addss xmm0, xmm2 + dec edx + movss [ecx-4], xmm0 + jnz loopVert1 + + done: + } +} + + +/* +============ +idSIMD_SSE::TracePointCull +============ +*/ +void VPCALL idSIMD_SSE::TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) { +#if 1 + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + + mov edi, planes + movlps xmm1, [edi] // xmm1 = 0, 1, X, X + movhps xmm1, [edi+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [edi+8] // xmm3 = 2, 3, X, X + movhps xmm3, [edi+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [edi+32] // xmm4 = 8, 9, X, X + movhps xmm4, [edi+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [edi+40] // xmm5 = 10, 11, X, X + movhps xmm5, [edi+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + movss xmm7, radius + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + + xor edx, edx + mov esi, verts + mov edi, cullBits + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert: + movss xmm4, [esi+eax+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm5, [esi+eax+SILTRACEVERT_XYZW_OFFSET+4] + mulps xmm4, xmm0 + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + movss xmm6, [esi+eax+SILTRACEVERT_XYZW_OFFSET+8] + mulps xmm5, xmm1 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + addps xmm4, xmm5 + mulps xmm6, xmm2 + addps xmm4, xmm3 + addps xmm4, xmm6 + movaps xmm5, xmm4 + xorps xmm5, SIMD_SP_signBit + cmpltps xmm4, xmm7 + movmskps ecx, xmm4 + cmpltps xmm5, xmm7 + movmskps ebx, xmm5 + shl cx, 4 + or cl, bl + inc edi + or dl, cl + add eax, SILTRACEVERT_SIZE + mov byte ptr [edi-1], cl + jl loopVert + + done: + mov esi, totalOr + mov byte ptr [esi], dl + pop ebx + } + +#else + + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; + +#endif +} + +/* +============ +idSIMD_SSE::DecalPointCull +============ +*/ +void VPCALL idSIMD_SSE::DecalPointCull( byte *cullBits, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) { +#if 1 + ALIGN16( float p0[4]; ) + ALIGN16( float p1[4]; ) + ALIGN16( float p2[4]; ) + ALIGN16( float p3[4]; ) + ALIGN16( float p4[4]; ) + ALIGN16( float p5[4]; ) + ALIGN16( float p6[4]; ) + ALIGN16( float p7[4]; ) + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + mov ecx, planes + movlps xmm1, [ecx] // xmm1 = 0, 1, X, X + movhps xmm1, [ecx+16] // xmm1 = 0, 1, 4, 5 + movlps xmm3, [ecx+8] // xmm3 = 2, 3, X, X + movhps xmm3, [ecx+24] // xmm3 = 2, 3, 6, 7 + movlps xmm4, [ecx+32] // xmm4 = 8, 9, X, X + movhps xmm4, [ecx+48] // xmm4 = 8, 9, 12, 13 + movlps xmm5, [ecx+40] // xmm5 = 10, 11, X, X + movhps xmm5, [ecx+56] // xmm5 = 10, 11, 14, 15 + movaps xmm0, xmm1 // xmm0 = 0, 1, 4, 5 + shufps xmm0, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm0 = 0, 4, 8, 12 + shufps xmm1, xmm4, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm1 = 1, 5, 9, 13 + movaps xmm2, xmm3 // xmm2 = 2, 3, 6, 7 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm2 = 2, 6, 10, 14 + shufps xmm3, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm3 = 3, 7, 11, 15 + + movaps p0, xmm0 + movaps p1, xmm1 + movaps p2, xmm2 + movaps p3, xmm3 + + movlps xmm4, [ecx+64] // xmm4 = p40, p41, X, X + movhps xmm4, [ecx+80] // xmm4 = p40, p41, p50, p51 + movaps xmm5, xmm4 // xmm5 = p40, p41, p50, p51 + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm4 = p40, p50, p40, p50 + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm5 = p41, p51, p41, p51 + movlps xmm6, [ecx+72] // xmm6 = p42, p43, X, X + movhps xmm6, [ecx+88] // xmm6 = p42, p43, p52, p53 + movaps xmm7, xmm6 // xmm7 = p42, p43, p52, p53 + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) // xmm6 = p42, p52, p42, p52 + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 3, 1, 3 ) // xmm7 = p43, p53, p43, p53 + + movaps p4, xmm4 + movaps p5, xmm5 + movaps p6, xmm6 + movaps p7, xmm7 + + mov esi, verts + mov edi, cullBits + mov eax, numVerts + and eax, ~1 + jz done2 + shl eax, SILTRACEVERT_SIZE_SHIFT + add esi, eax + neg eax + + loopVert2: + movaps xmm6, p0 + movss xmm0, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+eax+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + movaps xmm6, p0 + movss xmm3, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm3 + movaps xmm7, p1 + movss xmm4, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm4 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm5, [esi+eax+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm5 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps edx, xmm6 + mov ch, dl + + shufps xmm0, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, p4 + shufps xmm1, xmm4, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, p5 + addps xmm0, xmm1 + shufps xmm2, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + add edi, 2 + + mov dh, dl + shl dl, 4 + shl dh, 2 + and edx, (3<<4)|(3<<12) + or ecx, edx + + add eax, 2*SILTRACEVERT_SIZE + mov word ptr [edi-2], cx + jl loopVert2 + + done2: + + mov eax, numVerts + and eax, 1 + jz done + + movaps xmm6, p0 + movss xmm0, [esi+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + movaps xmm7, p1 + movss xmm1, [esi+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm1 + addps xmm6, xmm7 + movaps xmm7, p2 + movss xmm2, [esi+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm7, xmm2 + addps xmm6, xmm7 + addps xmm6, p3 + + cmpnltps xmm6, SIMD_SP_zero + movmskps ecx, xmm6 + + mulps xmm0, p4 + mulps xmm1, p5 + addps xmm0, xmm1 + mulps xmm2, p6 + addps xmm0, xmm2 + addps xmm0, p7 + + cmpnltps xmm0, SIMD_SP_zero + movmskps edx, xmm0 + + and edx, 3 + shl edx, 4 + or ecx, edx + + mov byte ptr [edi], cl + + done: + } + + +#else + + int i; + + for ( i = 0; i < numVerts; i += 2 ) { + unsigned short bits0, bits1; + float d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11; + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = planes[0][0] * v0[0] + planes[0][1] * v0[1] + planes[0][2] * v0[2] + planes[0][3]; + d1 = planes[1][0] * v0[0] + planes[1][1] * v0[1] + planes[1][2] * v0[2] + planes[1][3]; + d2 = planes[2][0] * v0[0] + planes[2][1] * v0[1] + planes[2][2] * v0[2] + planes[2][3]; + d3 = planes[3][0] * v0[0] + planes[3][1] * v0[1] + planes[3][2] * v0[2] + planes[3][3]; + + d4 = planes[4][0] * v0[0] + planes[4][1] * v0[1] + planes[4][2] * v0[2] + planes[4][3]; + d5 = planes[5][0] * v0[0] + planes[5][1] * v0[1] + planes[5][2] * v0[2] + planes[5][3]; + d10 = planes[4][0] * v1[0] + planes[4][1] * v1[1] + planes[4][2] * v1[2] + planes[4][3]; + d11 = planes[5][0] * v1[0] + planes[5][1] * v1[1] + planes[5][2] * v1[2] + planes[5][3]; + + d6 = planes[0][0] * v1[0] + planes[0][1] * v1[1] + planes[0][2] * v1[2] + planes[0][3]; + d7 = planes[1][0] * v1[0] + planes[1][1] * v1[1] + planes[1][2] * v1[2] + planes[1][3]; + d8 = planes[2][0] * v1[0] + planes[2][1] * v1[1] + planes[2][2] * v1[2] + planes[2][3]; + d9 = planes[3][0] * v1[0] + planes[3][1] * v1[1] + planes[3][2] * v1[2] + planes[3][3]; + + bits0 = FLOATSIGNBITSET( d0 ) << (0+0); + bits0 |= FLOATSIGNBITSET( d1 ) << (0+1); + bits0 |= FLOATSIGNBITSET( d2 ) << (0+2); + bits0 |= FLOATSIGNBITSET( d3 ) << (0+3); + bits0 |= FLOATSIGNBITSET( d4 ) << (0+4); + bits0 |= FLOATSIGNBITSET( d5 ) << (0+5); + + bits1 = FLOATSIGNBITSET( d6 ) << (8+0); + bits1 |= FLOATSIGNBITSET( d7 ) << (8+1); + bits1 |= FLOATSIGNBITSET( d8 ) << (8+2); + bits1 |= FLOATSIGNBITSET( d9 ) << (8+3); + bits1 |= FLOATSIGNBITSET( d10 ) << (8+4); + bits1 |= FLOATSIGNBITSET( d11 ) << (8+5); + + *(unsigned short *)(cullBits + i) = ( bits0 | bits1 ) ^ 0x3F3F; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[numVerts - 1].xyz; + + d0 = planes[0][0] * v[0] + planes[0][1] * v[1] + planes[0][2] * v[2] + planes[0][3]; + d1 = planes[1][0] * v[0] + planes[1][1] * v[1] + planes[1][2] * v[2] + planes[1][3]; + d2 = planes[2][0] * v[0] + planes[2][1] * v[1] + planes[2][2] * v[2] + planes[2][3]; + d3 = planes[3][0] * v[0] + planes[3][1] * v[1] + planes[3][2] * v[2] + planes[3][3]; + + d4 = planes[4][0] * v[0] + planes[4][1] * v[1] + planes[4][2] * v[2] + planes[4][3]; + d5 = planes[5][0] * v[0] + planes[5][1] * v[1] + planes[5][2] * v[2] + planes[5][3]; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[numVerts - 1] = bits ^ 0x3F; // flip lower 6 bits + } + +#endif +} + +/* +============ +idSIMD_SSE::OverlayPointCull +============ +*/ +void VPCALL idSIMD_SSE::OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ) { +#if 1 + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + mov eax, numVerts + mov edx, verts + mov esi, texCoords + mov edi, cullBits + + mov ecx, planes + movss xmm4, [ecx+ 0] + movss xmm5, [ecx+16] + shufps xmm4, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm5, [ecx+ 4] + movss xmm6, [ecx+20] + shufps xmm5, xmm6, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm6, [ecx+ 8] + movss xmm7, [ecx+24] + shufps xmm6, xmm7, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm6, xmm6, R_SHUFFLE_PS( 0, 2, 0, 2 ) + movss xmm7, [ecx+12] + movss xmm0, [ecx+28] + shufps xmm7, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + shufps xmm7, xmm7, R_SHUFFLE_PS( 0, 2, 0, 2 ) + + and eax, ~1 + jz done2 + add edi, eax + neg eax + + loopVert2: + movss xmm0, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm1, [edx+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm2, [edx+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm3, [edx+1*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movaps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + shufps xmm1, xmm2, R_SHUFFLE_PS( 2, 3, 2, 3 ) + add edx, 2*SILTRACEVERT_SIZE + movmskps ecx, xmm0 + mov byte ptr [edi+eax+0], cl + add esi, 4*4 + movmskps ecx, xmm1 + mov byte ptr [edi+eax+1], cl + add eax, 2 + jl loopVert2 + + done2: + mov eax, numVerts + and eax, 1 + jz done + + movss xmm0, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+0] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm0, xmm4 + movss xmm1, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+4] + shufps xmm1, xmm1, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm1, xmm5 + movss xmm2, [edx+0*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET+8] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + mulps xmm2, xmm6 + addps xmm0, xmm1 + addps xmm0, xmm2 + addps xmm0, xmm7 + movlps [esi], xmm0 + movaps xmm1, xmm0 + movaps xmm2, SIMD_SP_one + subps xmm2, xmm0 + shufps xmm0, xmm2, R_SHUFFLE_PS( 0, 1, 0, 1 ) + movmskps ecx, xmm0 + mov byte ptr [edi], cl + + done: + } + +#else + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + + for ( int i = 0; i < numVerts - 1; i += 2 ) { + unsigned short bits; + float d0, d1, d2, d3; + + const idVec3 &v0 = verts[i+0].xyz; + const idVec3 &v1 = verts[i+1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + d2 = p0[0] * v1[0] + p0[1] * v1[1] + p0[2] * v1[2] + p0[3]; + d3 = p1[0] * v1[0] + p1[1] * v1[1] + p1[2] * v1[2] + p1[3]; + + texCoords[i+0][0] = d0; + texCoords[i+0][1] = d1; + texCoords[i+1][0] = d2; + texCoords[i+1][1] = d3; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 8; + bits |= FLOATSIGNBITSET( d3 ) << 9; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + d2 = 1.0f - d2; + d3 = 1.0f - d3; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + bits |= FLOATSIGNBITSET( d2 ) << 10; + bits |= FLOATSIGNBITSET( d3 ) << 11; + + *(unsigned short *)(cullBits + i) = bits; + } + + if ( numVerts & 1 ) { + byte bits; + float d0, d1; + + const idPlane &p0 = planes[0]; + const idPlane &p1 = planes[1]; + const idVec3 &v0 = verts[numVerts - 1].xyz; + + d0 = p0[0] * v0[0] + p0[1] * v0[1] + p0[2] * v0[2] + p0[3]; + d1 = p1[0] * v0[0] + p1[1] * v0[1] + p1[2] * v0[2] + p1[3]; + + texCoords[i][0] = d0; + texCoords[i][1] = d1; + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + + d0 = 1.0f - d0; + d1 = 1.0f - d1; + + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[numVerts - 1] = bits; + } + +#endif +} + +/* +============ +idSIMD_SSE::DeriveTriPlanes +============ +*/ +void VPCALL idSIMD_SSE::DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const int *indexes, const int numIndexes ) { +#if 1 + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + mov eax, numIndexes + shl eax, 2 + mov esi, verts + mov edi, indexes + mov edx, planes + + add edi, eax + neg eax + + add eax, 4*12 + jge done4 + + loopPlane4: + mov ebx, [edi+eax-4*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-4*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + movss xmm0, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm1, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm2, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + mov ebx, [edi+eax-4*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm3, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm3, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm4, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm4, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm5, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm5, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + mov ebx, [edi+eax-3*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-3*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm0, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm1, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm2, xmm6 + + mov ebx, [edi+eax-3*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm3, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm4, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm5, xmm7 + + mov ebx, [edi+eax-2*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-2*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm0, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm1, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm2, xmm6 + + mov ebx, [edi+eax-2*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm3, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm4, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm5, xmm7 + + mov ebx, [edi+eax-1*12+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax-1*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm4, xmm4, R_SHUFFLE_PS( 3, 0, 1, 2 ) + shufps xmm5, xmm5, R_SHUFFLE_PS( 3, 0, 1, 2 ) + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm0, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm1, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm2, xmm6 + + mov ebx, [edi+eax-1*12+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + movss xmm3, xmm7 + + movss xmm6, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm6, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + movss xmm4, xmm6 + + movss xmm7, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm7, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + movss xmm5, xmm7 + + movaps xmm6, xmm4 + mulps xmm6, xmm2 + movaps xmm7, xmm5 + mulps xmm7, xmm1 + subps xmm6, xmm7 + + mulps xmm5, xmm0 + mulps xmm2, xmm3 + subps xmm5, xmm2 + + mulps xmm3, xmm1 + mulps xmm4, xmm0 + subps xmm3, xmm4 + + movaps xmm0, xmm6 + mulps xmm6, xmm6 + movaps xmm1, xmm5 + mulps xmm5, xmm5 + movaps xmm2, xmm3 + mulps xmm3, xmm3 + + addps xmm3, xmm5 + addps xmm3, xmm6 + rsqrtps xmm3, xmm3 + + add edx, 4*16 + mov ecx, [edi+eax-1*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movss [edx-1*16+0], xmm0 + movss [edx-1*16+4], xmm1 + movss [edx-1*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-1*16+12], xmm0 + + mov ecx, [edi+eax-2*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [edx-2*16+0], xmm0 + movss [edx-2*16+4], xmm1 + movss [edx-2*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-2*16+12], xmm0 + + mov ecx, [edi+eax-3*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [edx-3*16+0], xmm0 + movss [edx-3*16+4], xmm1 + movss [edx-3*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-3*16+12], xmm0 + + mov ecx, [edi+eax-4*12+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + shufps xmm0, xmm0, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm1, xmm1, R_SHUFFLE_PS( 1, 2, 3, 0 ) + shufps xmm2, xmm2, R_SHUFFLE_PS( 1, 2, 3, 0 ) + + movss [edx-4*16+0], xmm0 + movss [edx-4*16+4], xmm1 + movss [edx-4*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-4*16+12], xmm0 + + add eax, 4*12 + jle loopPlane4 + + done4: + + sub eax, 4*12 + jge done + + loopPlane1: + mov ebx, [edi+eax+4] + shl ebx, SILTRACEVERT_SIZE_SHIFT + mov ecx, [edi+eax+0] + shl ecx, SILTRACEVERT_SIZE_SHIFT + + movss xmm0, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm1, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm2, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + mov ebx, [edi+eax+8] + shl ebx, SILTRACEVERT_SIZE_SHIFT + + movss xmm3, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+0] + subss xmm3, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + + movss xmm4, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+4] + subss xmm4, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + + movss xmm5, [esi+ebx+SILTRACEVERT_XYZW_OFFSET+8] + subss xmm5, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + movss xmm6, xmm4 + mulss xmm6, xmm2 + movss xmm7, xmm5 + mulss xmm7, xmm1 + subss xmm6, xmm7 + + mulss xmm5, xmm0 + mulss xmm2, xmm3 + subss xmm5, xmm2 + + mulss xmm3, xmm1 + mulss xmm4, xmm0 + subss xmm3, xmm4 + + movss xmm0, xmm6 + mulss xmm6, xmm6 + movss xmm1, xmm5 + mulss xmm5, xmm5 + movss xmm2, xmm3 + mulss xmm3, xmm3 + + addss xmm3, xmm5 + addss xmm3, xmm6 + rsqrtss xmm3, xmm3 + + add edx, 1*16 + + mulss xmm0, xmm3 + mulss xmm1, xmm3 + mulss xmm2, xmm3 + + movss [edx-1*16+0], xmm0 + movss [edx-1*16+4], xmm1 + movss [edx-1*16+8], xmm2 + + mulss xmm0, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+0] + mulss xmm1, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+4] + mulss xmm2, [esi+ecx+SILTRACEVERT_XYZW_OFFSET+8] + + xorps xmm0, SIMD_SP_firstSignBit + subss xmm0, xmm1 + subss xmm0, xmm2 + movss [edx-1*16+12], xmm0 + + add eax, 1*12 + jl loopPlane1 + + done: + } + +#else + + int i, j; + + for ( i = 0; i <= numIndexes - 12; i += 12 ) { + ALIGN16( float d0[4]; ) + ALIGN16( float d1[4]; ) + ALIGN16( float d2[4]; ) + ALIGN16( float d3[4]; ) + ALIGN16( float d4[4]; ) + ALIGN16( float d5[4]; ) + ALIGN16( float n0[4]; ) + ALIGN16( float n1[4]; ) + ALIGN16( float n2[4]; ) + + for ( j = 0; j < 4; j++ ) { + const rvSilTraceVertT *a, *b, *c; + + a = verts + indexes[i + j * 3 + 0]; + b = verts + indexes[i + j * 3 + 1]; + c = verts + indexes[i + j * 3 + 2]; + + d0[j] = b->xyz[0] - a->xyz[0]; + d1[j] = b->xyz[1] - a->xyz[1]; + d2[j] = b->xyz[2] - a->xyz[2]; + + d3[j] = c->xyz[0] - a->xyz[0]; + d4[j] = c->xyz[1] - a->xyz[1]; + d5[j] = c->xyz[2] - a->xyz[2]; + } + + ALIGN16( float tmp[4]; ) + + n0[0] = d4[0] * d2[0]; + n0[1] = d4[1] * d2[1]; + n0[2] = d4[2] * d2[2]; + n0[3] = d4[3] * d2[3]; + + n0[0] -= d5[0] * d1[0]; + n0[1] -= d5[1] * d1[1]; + n0[2] -= d5[2] * d1[2]; + n0[3] -= d5[3] * d1[3]; + + n1[0] = d5[0] * d0[0]; + n1[1] = d5[1] * d0[1]; + n1[2] = d5[2] * d0[2]; + n1[3] = d5[3] * d0[3]; + + n1[0] -= d3[0] * d2[0]; + n1[1] -= d3[1] * d2[1]; + n1[2] -= d3[2] * d2[2]; + n1[3] -= d3[3] * d2[3]; + + n2[0] = d3[0] * d1[0]; + n2[1] = d3[1] * d1[1]; + n2[2] = d3[2] * d1[2]; + n2[3] = d3[3] * d1[3]; + + n2[0] -= d4[0] * d0[0]; + n2[1] -= d4[1] * d0[1]; + n2[2] -= d4[2] * d0[2]; + n2[3] -= d4[3] * d0[3]; + + tmp[0] = n0[0] * n0[0]; + tmp[1] = n0[1] * n0[1]; + tmp[2] = n0[2] * n0[2]; + tmp[3] = n0[3] * n0[3]; + + tmp[0] += n1[0] * n1[0]; + tmp[1] += n1[1] * n1[1]; + tmp[2] += n1[2] * n1[2]; + tmp[3] += n1[3] * n1[3]; + + tmp[0] += n2[0] * n2[0]; + tmp[1] += n2[1] * n2[1]; + tmp[2] += n2[2] * n2[2]; + tmp[3] += n2[3] * n2[3]; + + tmp[0] = idMath::RSqrt( tmp[0] ); + tmp[1] = idMath::RSqrt( tmp[1] ); + tmp[2] = idMath::RSqrt( tmp[2] ); + tmp[3] = idMath::RSqrt( tmp[3] ); + + n0[0] *= tmp[0]; + n0[1] *= tmp[1]; + n0[2] *= tmp[2]; + n0[3] *= tmp[3]; + + n1[0] *= tmp[0]; + n1[1] *= tmp[1]; + n1[2] *= tmp[2]; + n1[3] *= tmp[3]; + + n2[0] *= tmp[0]; + n2[1] *= tmp[1]; + n2[2] *= tmp[2]; + n2[3] *= tmp[3]; + + + for ( j = 0; j < 4; j++ ) { + const rvSilTraceVertT *a; + + a = verts + indexes[i + j * 3]; + + planes->Normal()[0] = n0[j]; + planes->Normal()[1] = n1[j]; + planes->Normal()[2] = n2[j]; + planes->FitThroughPoint( a->xyz ); + planes++; + } + } + + for ( ; i < numIndexes; i += 3 ) { + const rvSilTraceVertT *a, *b, *c; + float d0, d1, d2, d3, d4, d5; + float n0, n1, n2; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + + d3 = c->xyz[0] - a->xyz[0]; + d4 = c->xyz[1] - a->xyz[1]; + d5 = c->xyz[2] - a->xyz[2]; + + float tmp; + + n0 = d4 * d2 - d5 * d1; + n1 = d5 * d0 - d3 * d2; + n2 = d3 * d1 - d4 * d0; + + tmp = idMath::RSqrt( n0 * n0 + n1 * n1 + n2 * n2 ); + + n0 *= tmp; + n1 *= tmp; + n2 *= tmp; + + planes->Normal()[0] = n0; + planes->Normal()[1] = n1; + planes->Normal()[2] = n2; + planes->FitThroughPoint( a->xyz ); + planes++; + } + +#endif +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *vertexInputData, const int numVerts ) { + + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + const rvSilTraceVertT *endVertexInputData; + + assert( numVerts > 0 ); + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + endVertexInputData = vertexInputData + numVerts; + + __asm + { + mov edx, curMin + mov ecx, curMax + mov eax, vertexInputData + mov esi, endVertexInputData + movaps xmm0, [edx] + movaps xmm7, [ecx] + + loop1: + movaps xmm1, [eax] + add eax, SILTRACEVERT_SIZE + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + cmp eax, esi + jne loop1 + + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +/* +============ +idSIMD_SSE::MinMax +============ +*/ +void VPCALL idSIMD_SSE::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *vertexInputData, const int *indices, const int count ) { + +#if 1 + ALIGN16( float minStore[4]; ) + ALIGN16( float maxStore[4]; ) + float *curMin = minStore, *curMax = maxStore; + const int *endIndices; + + if ( count <= 0 ) + { + return; + } + + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( count % 3 == 0 ); + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + curMin[3] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + curMax[3] = -FLT_MAX; + + endIndices = indices + count; + + __asm + { + mov edx, curMin + mov ecx, curMax + mov esi, vertexInputData + mov edi, indices + movaps xmm0, [edx] + movaps xmm7, [ecx] + + loop1: + mov eax, [edi] + shl eax, SILTRACEVERT_SIZE_SHIFT + add eax, esi + + movaps xmm1, [eax] + + mov eax, [edi+4] + shl eax, SILTRACEVERT_SIZE_SHIFT + add eax, esi + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps xmm1, [eax] + + mov eax, [edi+8] + add edi, 12 + shl eax, SILTRACEVERT_SIZE_SHIFT + add eax, esi + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + movaps xmm1, [eax] + mov eax, endIndices + + minps xmm0, xmm1 + maxps xmm7, xmm1 + + cmp edi, eax + jl loop1 + + movaps [edx], xmm0 + movaps [ecx], xmm7 + } + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +#else + assert( sizeof( rvSilTraceVertT ) == SILTRACEVERT_SIZE ); + assert( (int)&((rvSilTraceVertT *)0)->xyzw == SILTRACEVERT_XYZW_OFFSET ); + + __asm { + + movss xmm0, idMath::INFINITY + xorps xmm1, xmm1 + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + subps xmm1, xmm0 + movaps xmm2, xmm0 + movaps xmm3, xmm1 + + mov edi, indexes + mov esi, src + mov eax, count + and eax, ~3 + jz done4 + shl eax, 2 + add edi, eax + neg eax + + loop4: +// prefetchnta [edi+128] +// prefetchnta [esi+4*SILTRACEVERT_SIZE+SILTRACEVERT_XYZW_OFFSET] + + mov edx, [edi+eax+0] + imul edx, SILTRACEVERT_SIZE + movss xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+8] + movhps xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + mov edx, [edi+eax+4] + imul edx, SILTRACEVERT_SIZE + movss xmm5, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + movhps xmm5, [esi+edx+SILTRACEVERT_XYZW_OFFSET+4] + minps xmm2, xmm5 + maxps xmm3, xmm5 + + mov edx, [edi+eax+8] + imul edx, SILTRACEVERT_SIZE + movss xmm6, [esi+edx+SILTRACEVERT_XYZW_OFFSET+8] + movhps xmm6, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + minps xmm0, xmm6 + maxps xmm1, xmm6 + + mov edx, [edi+eax+12] + imul edx, SILTRACEVERT_SIZE + movss xmm7, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + movhps xmm7, [esi+edx+SILTRACEVERT_XYZW_OFFSET+4] + minps xmm2, xmm7 + maxps xmm3, xmm7 + + add eax, 4*4 + jl loop4 + + done4: + mov eax, count + and eax, 3 + jz done1 + shl eax, 2 + add edi, eax + neg eax + + loop1: + mov edx, [edi+eax+0] + imul edx, SILTRACEVERT_SIZE; + movss xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+8] + movhps xmm4, [esi+edx+SILTRACEVERT_XYZW_OFFSET+0] + minps xmm0, xmm4 + maxps xmm1, xmm4 + + add eax, 4 + jl loop1 + + done1: + shufps xmm2, xmm2, R_SHUFFLE_PS( 3, 1, 0, 2 ) + shufps xmm3, xmm3, R_SHUFFLE_PS( 3, 1, 0, 2 ) + minps xmm0, xmm2 + maxps xmm1, xmm3 + mov esi, min + movhps [esi], xmm0 + movss [esi+8], xmm0 + mov edi, max + movhps [edi], xmm1 + movss [edi+8], xmm1 + } +#endif +} + +#endif // #ifdef _MD5R_SUPPORT +// RAVEN END + +#endif // _WINDOWS diff --git a/src/idlib/math/Simd_SSE.h b/src/idlib/math/Simd_SSE.h new file mode 100644 index 0000000..0ae90df --- /dev/null +++ b/src/idlib/math/Simd_SSE.h @@ -0,0 +1,152 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_SSE_H__ +#define __MATH_SIMD_SSE_H__ + +/* +=============================================================================== + + SSE implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_SSE : public idSIMD_MMX { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL Add( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Add( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Sub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Sub( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Mul( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Mul( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL Div( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL Div( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulAdd( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulAdd( float *dst, const float *src0, const float *src1, const int count ); + virtual void VPCALL MulSub( float *dst, const float constant, const float *src, const int count ); + virtual void VPCALL MulSub( float *dst, const float *src0, const float *src1, const int count ); + + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idVec3 *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idPlane *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant,const idDrawVert *src, const int count ); + virtual void VPCALL Dot( float *dst, const idVec3 *src0, const idVec3 *src1, const int count ); + virtual void VPCALL Dot( float &dot, const float *src1, const float *src2, const int count ); + + virtual void VPCALL CmpGT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpGE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLT( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const float *src0, const float constant, const int count ); + virtual void VPCALL CmpLE( byte *dst, const byte bitNum, const float *src0, const float constant, const int count ); + + virtual void VPCALL MinMax( float &min, float &max, const float *src, const int count ); + virtual void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert *src, const int *indexes, const int count ); + + virtual void VPCALL Clamp( float *dst, const float *src, const float min, const float max, const int count ); + virtual void VPCALL ClampMin( float *dst, const float *src, const float min, const int count ); + virtual void VPCALL ClampMax( float *dst, const float *src, const float max, const int count ); + + virtual void VPCALL Zero16( float *dst, const int count ); + virtual void VPCALL Negate16( float *dst, const int count ); + virtual void VPCALL Copy16( float *dst, const float *src, const int count ); + virtual void VPCALL Add16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Sub16( float *dst, const float *src1, const float *src2, const int count ); + virtual void VPCALL Mul16( float *dst, const float *src1, const float constant, const int count ); + virtual void VPCALL AddAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL SubAssign16( float *dst, const float *src, const int count ); + virtual void VPCALL MulAssign16( float *dst, const float constant, const int count ); + + virtual void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + virtual void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + virtual void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ); + virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ); + virtual bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); + + virtual void VPCALL BlendJoints( idJointQuat *joints, const idJointQuat *blendJoints, const float lerp, const int *index, const int numJoints ); + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL ConvertJointMatsToJointQuats( idJointQuat *jointQuats, const idJointMat *jointMats, const int numJoints ); + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ); + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const idDrawVert *verts, const int numVerts ); + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveTangents( idPlane *planes, idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL DeriveUnsmoothedTangents( idDrawVert *verts, const dominantTri_s *dominantTris, const int numVerts ); + virtual void VPCALL NormalizeTangents( idDrawVert *verts, const int numVerts ); + virtual void VPCALL CreateTextureSpaceLightVectors( idVec3 *lightVectors, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL CreateSpecularTextureCoords( idVec4 *texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual int VPCALL CreateShadowCache( idVec4 *vertexCache, int *vertRemap, const idVec3 &lightOrigin, const idDrawVert *verts, const int numVerts ); + virtual int VPCALL CreateVertexProgramShadowCache( idVec4 *vertexCache, const idDrawVert *verts, const int numVerts ); + + virtual void VPCALL UpSamplePCMTo44kHz( float *dest, const short *pcm, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL UpSampleOGGTo44kHz( float *dest, const float * const *ogg, const int numSamples, const int kHz, const int numChannels ); + virtual void VPCALL MixSoundTwoSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundTwoSpeakerMonoSimple( float *mixBuffer, const float *samples, const int numSamples ); + virtual void VPCALL MixSoundTwoSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[2], const float currentV[2] ); + virtual void VPCALL MixSoundSixSpeakerMono( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixSoundSixSpeakerStereo( float *mixBuffer, const float *samples, const int numSamples, const float lastV[6], const float currentV[6] ); + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ); + +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT + virtual void VPCALL TransformVertsMinMax4Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL TransformVertsMinMax1Bone( rvSilTraceVertT *silTraceVertOutputData, idVec3 &min, idVec3 &max, byte *vertexInputData, int vertStride, int numVerts, float *skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + virtual void VPCALL Dot( float *dst, const idVec3 &constant, const rvSilTraceVertT *src, const int count ); + virtual void VPCALL Dot( float *dst, const idPlane &constant, const rvSilTraceVertT *src, const int count ); + virtual void VPCALL TracePointCull( byte *cullBits, byte &totalOr, const float radius, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ); + virtual void VPCALL DecalPointCull( byte *cullBits, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ); + virtual void VPCALL OverlayPointCull( byte *cullBits, idVec2 *texCoords, const idPlane *planes, const rvSilTraceVertT *verts, const int numVerts ); + virtual void VPCALL DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *verts, const int numVerts, const int *indexes, const int numIndexes ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int count ); + virtual void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT *src, const int *indexes, const int count ); +#endif +// RAVEN END +#endif +}; + +#endif /* !__MATH_SIMD_SSE_H__ */ diff --git a/src/idlib/math/Simd_SSE2.cpp b/src/idlib/math/Simd_SSE2.cpp new file mode 100644 index 0000000..0cef279 --- /dev/null +++ b/src/idlib/math/Simd_SSE2.cpp @@ -0,0 +1,2234 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_SSE.h" +#include "Simd_SSE2.h" +#include "Simd_InstructionMacros.h" + + +//=============================================================== +// +// SSE2 implementation of idSIMDProcessor +// +//=============================================================== + +#ifdef _WINDOWS + +#include "Simd_InstructionMacros.h" + +ALIGN8_INIT1( unsigned short SIMD_W_zero, 0 ); +ALIGN8_INIT1( unsigned short SIMD_W_maxShort, 1<<15 ); + +ALIGN4_INIT4( unsigned long SIMD_SP_firstSignBit, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_ZERO, IEEE_SP_ZERO ); +ALIGN4_INIT1( unsigned long SIMD_SP_signBit, IEEE_SP_SIGN ); +ALIGN4_INIT1( unsigned long SIMD_SP_absMask, ~IEEE_SP_SIGN ); +ALIGN4_INIT1( unsigned long SIMD_SP_infinityMask, ~IEEE_SP_INF ); +ALIGN4_INIT1( unsigned long SIMD_SP_not, 0xFFFFFFFF ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearLast, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 ); +ALIGN4_INIT4( unsigned long SIMD_SP_clearFirstThree, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF ); + +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x0, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x1, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quat2mat_x2, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN, IEEE_SP_SIGN ); + +ALIGN4_INIT4( unsigned long SIMD_DW_zero, 0, 0, 0, 0 ); +ALIGN16_INIT1( unsigned char SIMD_B_one, 1 ); + +ALIGN4_INIT4( unsigned long SIMD_DW_capTris_c0, 0, 0, 0, 1 ); +ALIGN4_INIT4( unsigned long SIMD_DW_capTris_c1, 1, 1, 0, 0 ); +ALIGN4_INIT4( unsigned long SIMD_DW_capTris_c2, 0, 1, 0, 0 ); + +ALIGN4_INIT1( float SIMD_SP_zero, 0.0f ); +ALIGN4_INIT1( float SIMD_SP_one, 1.0f ); +ALIGN4_INIT1( float SIMD_SP_two, 2.0f ); +ALIGN4_INIT1( float SIMD_SP_three, 3.0f ); +ALIGN4_INIT1( float SIMD_SP_four, 4.0f ); +ALIGN4_INIT1( float SIMD_SP_maxShort, (1<<15) ); +ALIGN4_INIT1( float SIMD_SP_tiny, 1e-10f ); +ALIGN4_INIT1( float SIMD_SP_PI, idMath::PI ); +ALIGN4_INIT1( float SIMD_SP_halfPI, idMath::HALF_PI ); +ALIGN4_INIT1( float SIMD_SP_twoPI, idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_oneOverTwoPI, 1.0f / idMath::TWO_PI ); +ALIGN4_INIT1( float SIMD_SP_infinity, idMath::INFINITY ); +ALIGN4_INIT1( float SIMD_SP_negInfinity, -idMath::INFINITY ); + + +/* +============ +idSIMD_SSE2::GetName +============ +*/ +const char * idSIMD_SSE2::GetName( void ) const { + return "MMX & SSE & SSE2"; +} + +#if 0 // the SSE2 code is ungodly slow + +/* +============ +idSIMD_SSE2::MatX_LowerTriangularSolve + + solves x in Lx = b for the n * n sub-matrix of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE2::MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip ) { + int nc; + const float *lptr; + + if ( skip >= n ) { + return; + } + + lptr = L[skip]; + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + #define NSKIP( n, s ) ((n<<3)|(s&7)) + switch( NSKIP( n, skip ) ) { + case NSKIP( 1, 0 ): x[0] = b[0]; + return; + case NSKIP( 2, 0 ): x[0] = b[0]; + case NSKIP( 2, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + return; + case NSKIP( 3, 0 ): x[0] = b[0]; + case NSKIP( 3, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 3, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + return; + case NSKIP( 4, 0 ): x[0] = b[0]; + case NSKIP( 4, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 4, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 4, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + return; + case NSKIP( 5, 0 ): x[0] = b[0]; + case NSKIP( 5, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 5, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 5, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 5, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + return; + case NSKIP( 6, 0 ): x[0] = b[0]; + case NSKIP( 6, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 6, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 6, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 6, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 6, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + return; + case NSKIP( 7, 0 ): x[0] = b[0]; + case NSKIP( 7, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 7, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 7, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 7, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 7, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + case NSKIP( 7, 6 ): x[6] = b[6] - lptr[6*nc+0] * x[0] - lptr[6*nc+1] * x[1] - lptr[6*nc+2] * x[2] - lptr[6*nc+3] * x[3] - lptr[6*nc+4] * x[4] - lptr[6*nc+5] * x[5]; + return; + } + return; + } + + // process first 4 rows + switch( skip ) { + case 0: x[0] = b[0]; + case 1: x[1] = b[1] - lptr[1*nc+0] * x[0]; + case 2: x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case 3: x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + skip = 4; + } + + lptr = L[skip]; + + __asm { + push ebx + mov eax, skip // eax = i + shl eax, 2 // eax = i*4 + mov edx, n // edx = n + shl edx, 2 // edx = n*4 + mov esi, x // esi = x + mov edi, lptr // edi = lptr + add esi, eax + add edi, eax + mov ebx, b // ebx = b + // aligned + looprow: + mov ecx, eax + neg ecx + cvtps2pd xmm0, [esi+ecx] + cvtps2pd xmm2, [edi+ecx] + mulpd xmm0, xmm2 + cvtps2pd xmm1, [esi+ecx+8] + cvtps2pd xmm3, [edi+ecx+8] + mulpd xmm1, xmm3 + add ecx, 20*4 + jg donedot16 + dot16: + cvtps2pd xmm2, [esi+ecx-(16*4)] + cvtps2pd xmm3, [edi+ecx-(16*4)] + cvtps2pd xmm4, [esi+ecx-(14*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(14*4)] + addpd xmm0, xmm2 + cvtps2pd xmm2, [esi+ecx-(12*4)] + mulpd xmm4, xmm5 + cvtps2pd xmm3, [edi+ecx-(12*4)] + addpd xmm1, xmm4 + cvtps2pd xmm4, [esi+ecx-(10*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(10*4)] + addpd xmm0, xmm2 + cvtps2pd xmm2, [esi+ecx-(8*4)] + mulpd xmm4, xmm5 + cvtps2pd xmm3, [edi+ecx-(8*4)] + addpd xmm1, xmm4 + cvtps2pd xmm4, [esi+ecx-(6*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(6*4)] + addpd xmm0, xmm2 + cvtps2pd xmm2, [esi+ecx-(4*4)] + mulpd xmm4, xmm5 + cvtps2pd xmm3, [edi+ecx-(4*4)] + addpd xmm1, xmm4 + cvtps2pd xmm4, [esi+ecx-(2*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(2*4)] + addpd xmm0, xmm2 + add ecx, 16*4 + mulpd xmm4, xmm5 + addpd xmm1, xmm4 + jle dot16 + donedot16: + sub ecx, 8*4 + jg donedot8 + dot8: + cvtps2pd xmm2, [esi+ecx-(8*4)] + cvtps2pd xmm3, [edi+ecx-(8*4)] + cvtps2pd xmm7, [esi+ecx-(6*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(6*4)] + addpd xmm0, xmm2 + cvtps2pd xmm6, [esi+ecx-(4*4)] + mulpd xmm7, xmm5 + cvtps2pd xmm3, [edi+ecx-(4*4)] + addpd xmm1, xmm7 + cvtps2pd xmm4, [esi+ecx-(2*4)] + mulpd xmm6, xmm3 + cvtps2pd xmm7, [edi+ecx-(2*4)] + addpd xmm0, xmm6 + add ecx, 8*4 + mulpd xmm4, xmm7 + addpd xmm1, xmm4 + donedot8: + sub ecx, 4*4 + jg donedot4 + dot4: + cvtps2pd xmm2, [esi+ecx-(4*4)] + cvtps2pd xmm3, [edi+ecx-(4*4)] + cvtps2pd xmm4, [esi+ecx-(2*4)] + mulpd xmm2, xmm3 + cvtps2pd xmm5, [edi+ecx-(2*4)] + addpd xmm0, xmm2 + add ecx, 4*4 + mulpd xmm4, xmm5 + addpd xmm1, xmm4 + donedot4: + addpd xmm0, xmm1 + movaps xmm1, xmm0 + shufpd xmm1, xmm1, R_SHUFFLE_PD( 1, 0 ) + addsd xmm0, xmm1 + sub ecx, 4*4 + jz dot0 + add ecx, 4 + jz dot1 + add ecx, 4 + jz dot2 + //dot3: + cvtss2sd xmm1, [esi-(3*4)] + cvtss2sd xmm2, [edi-(3*4)] + mulsd xmm1, xmm2 + addsd xmm0, xmm1 + dot2: + cvtss2sd xmm3, [esi-(2*4)] + cvtss2sd xmm4, [edi-(2*4)] + mulsd xmm3, xmm4 + addsd xmm0, xmm3 + dot1: + cvtss2sd xmm5, [esi-(1*4)] + cvtss2sd xmm6, [edi-(1*4)] + mulsd xmm5, xmm6 + addsd xmm0, xmm5 + dot0: + cvtss2sd xmm1, [ebx+eax] + subsd xmm1, xmm0 + cvtsd2ss xmm0, xmm1 + movss [esi], xmm0 + add eax, 4 + cmp eax, edx + jge done + add esi, 4 + mov ecx, nc + shl ecx, 2 + add edi, ecx + add edi, 4 + jmp looprow + // done + done: + pop ebx + } +} + +/* +============ +idSIMD_SSE2::MatX_LowerTriangularSolveTranspose + + solves x in L'x = b for the n * n sub-matrix of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_SSE2::MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ) { + int nc; + const float *lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x[1] = b[1]; + x[0] = b[0] - lptr[1*nc+0] * x[1]; + return; + case 3: + x[2] = b[2]; + x[1] = b[1] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 4: + x[3] = b[3]; + x[2] = b[2] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 5: + x[4] = b[4]; + x[3] = b[3] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 6: + x[5] = b[5]; + x[4] = b[4] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 7: + x[6] = b[6]; + x[5] = b[5] - lptr[6*nc+5] * x[6]; + x[4] = b[4] - lptr[6*nc+4] * x[6] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[6*nc+3] * x[6] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[6*nc+2] * x[6] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[6*nc+1] * x[6] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[6*nc+0] * x[6] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + } + return; + } + + int i, j, m; + float *xptr; + double s0; + + // if the number of columns is not a multiple of 2 we're screwed for alignment. + // however, if the number of columns is a multiple of 2 but the number of to be + // processed rows is not a multiple of 2 we can still run 8 byte aligned + m = n; + if ( m & 1 ) { + m--; + x[m] = b[m]; + + lptr = L[m] + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows_1: + cvtps2pd xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + cvtps2pd xmm2, [ebx+eax*4-8] // load b[i-4], b[i-3] + xor ecx, ecx + sub eax, m + neg eax + jz done4x4_1 + process4x4_1: // process 4x4 blocks + cvtps2pd xmm3, [edi] + cvtps2pd xmm4, [edi+8] + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+0] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+4] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + cvtps2pd xmm3, [edi] + mulpd xmm6, xmm7 + cvtps2pd xmm4, [edi+8] + subpd xmm0, xmm1 + subpd xmm2, xmm6 + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+8] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+12] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + add ecx, 4 + mulpd xmm6, xmm7 + cmp ecx, eax + subpd xmm0, xmm1 + subpd xmm2, xmm6 + jl process4x4_1 + done4x4_1: // process left over of the 4 rows + cvtps2pd xmm3, [edi] + cvtps2pd xmm4, [edi+8] + cvtss2sd xmm5, [esi+4*ecx] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + mulpd xmm4, xmm5 + subpd xmm0, xmm3 + subpd xmm2, xmm4 + imul ecx, edx + sub edi, ecx + neg eax + + add eax, m + sub eax, 4 + movapd xmm1, xmm0 + shufpd xmm1, xmm1, R_SHUFFLE_PD( 1, 1 ) + movapd xmm3, xmm2 + shufpd xmm3, xmm3, R_SHUFFLE_PD( 1, 1 ) + sub edi, edx + cvtsd2ss xmm7, xmm3 + movss [esi-4], xmm7 // xptr[-1] = s3 + movsd xmm4, xmm3 + movsd xmm5, xmm3 + cvtss2sd xmm7, [edi+8] + mulsd xmm3, xmm7 // lptr[-1*nc+2] * s3 + cvtss2sd xmm7, [edi+4] + mulsd xmm4, xmm7 // lptr[-1*nc+1] * s3 + cvtss2sd xmm7, [edi] + mulsd xmm5, xmm7 // lptr[-1*nc+0] * s3 + subsd xmm2, xmm3 + cvtsd2ss xmm7, xmm2 + movss [esi-8], xmm7 // xptr[-2] = s2 + movsd xmm6, xmm2 + sub edi, edx + subsd xmm0, xmm5 + subsd xmm1, xmm4 + cvtss2sd xmm7, [edi+4] + mulsd xmm2, xmm7 // lptr[-2*nc+1] * s2 + cvtss2sd xmm7, [edi] + mulsd xmm6, xmm7 // lptr[-2*nc+0] * s2 + subsd xmm1, xmm2 + cvtsd2ss xmm7, xmm1 + movss [esi-12], xmm7 // xptr[-3] = s1 + subsd xmm0, xmm6 + sub edi, edx + cmp eax, 4 + cvtss2sd xmm7, [edi] + mulsd xmm1, xmm7 // lptr[-3*nc+0] * s1 + subsd xmm0, xmm1 + cvtsd2ss xmm7, xmm0 + movss [esi-16], xmm7 // xptr[-4] = s0 + jl done4rows_1 + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows_1 + done4rows_1: + pop ebx + } + } + else { + lptr = L.ToFloatPtr() + m * L.GetNumColumns() + m - 4; + xptr = x + m; + __asm { + push ebx + mov eax, m // eax = i + mov esi, xptr // esi = xptr + mov edi, lptr // edi = lptr + mov ebx, b // ebx = b + mov edx, nc // edx = nc*sizeof(float) + shl edx, 2 + process4rows: + cvtps2pd xmm0, [ebx+eax*4-16] // load b[i-2], b[i-1] + cvtps2pd xmm2, [ebx+eax*4-8] // load b[i-4], b[i-3] + sub eax, m + jz done4x4 + neg eax + xor ecx, ecx + process4x4: // process 4x4 blocks + cvtps2pd xmm3, [edi] + cvtps2pd xmm4, [edi+8] + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+0] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+4] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + cvtps2pd xmm3, [edi] + mulpd xmm6, xmm7 + cvtps2pd xmm4, [edi+8] + subpd xmm0, xmm1 + subpd xmm2, xmm6 + add edi, edx + cvtss2sd xmm5, [esi+4*ecx+8] + shufpd xmm5, xmm5, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm3, xmm5 + cvtps2pd xmm1, [edi] + mulpd xmm4, xmm5 + cvtps2pd xmm6, [edi+8] + subpd xmm0, xmm3 + subpd xmm2, xmm4 + add edi, edx + cvtss2sd xmm7, [esi+4*ecx+12] + shufpd xmm7, xmm7, R_SHUFFLE_PD( 0, 0 ) + mulpd xmm1, xmm7 + add ecx, 4 + mulpd xmm6, xmm7 + cmp ecx, eax + subpd xmm0, xmm1 + subpd xmm2, xmm6 + jl process4x4 + imul ecx, edx + sub edi, ecx + neg eax + done4x4: // process left over of the 4 rows + add eax, m + sub eax, 4 + movapd xmm1, xmm0 + shufpd xmm1, xmm1, R_SHUFFLE_PD( 1, 1 ) + movapd xmm3, xmm2 + shufpd xmm3, xmm3, R_SHUFFLE_PD( 1, 1 ) + sub edi, edx + cvtsd2ss xmm7, xmm3 + movss [esi-4], xmm7 // xptr[-1] = s3 + movsd xmm4, xmm3 + movsd xmm5, xmm3 + cvtss2sd xmm7, [edi+8] + mulsd xmm3, xmm7 // lptr[-1*nc+2] * s3 + cvtss2sd xmm7, [edi+4] + mulsd xmm4, xmm7 // lptr[-1*nc+1] * s3 + cvtss2sd xmm7, [edi] + mulsd xmm5, xmm7 // lptr[-1*nc+0] * s3 + subsd xmm2, xmm3 + cvtsd2ss xmm7, xmm2 + movss [esi-8], xmm7 // xptr[-2] = s2 + movsd xmm6, xmm2 + sub edi, edx + subsd xmm0, xmm5 + subsd xmm1, xmm4 + cvtss2sd xmm7, [edi+4] + mulsd xmm2, xmm7 // lptr[-2*nc+1] * s2 + cvtss2sd xmm7, [edi] + mulsd xmm6, xmm7 // lptr[-2*nc+0] * s2 + subsd xmm1, xmm2 + cvtsd2ss xmm7, xmm1 + movss [esi-12], xmm7 // xptr[-3] = s1 + subsd xmm0, xmm6 + sub edi, edx + cmp eax, 4 + cvtss2sd xmm7, [edi] + mulsd xmm1, xmm7 // lptr[-3*nc+0] * s1 + subsd xmm0, xmm1 + cvtsd2ss xmm7, xmm0 + movss [esi-16], xmm7 // xptr[-4] = s0 + jl done4rows + sub edi, edx + sub edi, 16 + sub esi, 16 + jmp process4rows + done4rows: + pop ebx + } + } + + // process left over rows + for ( i = (m&3)-1; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[i+1] + i; + for ( j = i + 1; j < m; j++ ) { + s0 -= lptr[0] * x[j]; + lptr += nc; + } + x[i] = s0; + } +} + +#endif + +/* +============ +idSIMD_SSE2::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_SSE2::ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + assert_16_byte_aligned( jointQuats ); + + __asm { + mov eax, numJoints + shl eax, JOINTQUAT_SIZE_SHIFT + mov esi, jointQuats + mov edi, jointMats + + add esi, eax + neg eax + jz done + + loopQuat: + movaps xmm0, [esi+eax+JOINTQUAT_Q_OFFSET] // xmm0 = q.x, q.y, q.z, q.w + movaps xmm6, [esi+eax+JOINTQUAT_T_OFFSET] // xmm6 = t.x, t.y, t.z, w + + add edi, JOINTMAT_SIZE + + movaps xmm1, xmm0 // xmm1 = x, y, z, w + addps xmm1, xmm1 // xmm1 = x2, y2, z2, w2 + + add eax, JOINTQUAT_SIZE + + // calculate the 9 products + pshufd xmm2, xmm0, R_SHUFFLE_D( 1, 0, 0, 1 ) // xmm2 = y, x, x, y + pshufd xmm3, xmm1, R_SHUFFLE_D( 1, 1, 2, 2 ) // xmm3 = y2, y2, z2, z2 + mulps xmm2, xmm3 // xmm2 = yy2, xy2, xz2, yz2 + + pshufd xmm4, xmm0, R_SHUFFLE_D( 2, 3, 3, 3 ) // xmm4 = z, w, w, w + pshufd xmm5, xmm1, R_SHUFFLE_D( 2, 2, 1, 0 ) // xmm5 = z2, z2, y2, x2 + mulps xmm4, xmm5 // xmm4 = zz2, wz2, wy2, wx2 + + mulss xmm0, xmm1 // xmm0 = xx2, y2, z2, w2 + + // calculate the last two elements of the third row + movss xmm7, SIMD_SP_one // xmm7 = 1, 0, 0, 0 + subss xmm7, xmm0 // xmm7 = -xx2+1, 0, 0, 0 + subss xmm7, xmm2 // xmm7 = -xx2-yy2+1, 0, 0, 0 + shufps xmm7, xmm6, R_SHUFFLE_PS( 0, 1, 2, 3 ) // xmm7 = -xx2-yy2+1, 0, t.z, w + + // calcluate first row + xorps xmm2, SIMD_SP_quat2mat_x0 // xmm2 = yy2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x1 // xmm4 = -zz2, wz2, -wy2, -wx2 + addss xmm4, SIMD_SP_one // xmm4 = -zz2+1, wz2, -wy2, -wx2 + movaps xmm3, xmm4 // xmm3 = -zz2+1, wz2, -wy2, -wx2 + subps xmm3, xmm2 // xmm3 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movaps [edi-JOINTMAT_SIZE+0*16+0*4], xmm3 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, yz2-wx2 + movss [edi-JOINTMAT_SIZE+0*16+3*4], xmm6 // row0 = -yy2-zz2+1, xy2+wz2, xz2-wy2, t.x + + // calculate second row + movss xmm2, xmm0 // xmm2 = xx2, -xy2, -xz2, -yz2 + xorps xmm4, SIMD_SP_quat2mat_x2 // xmm4 = -zz2+1, -wz2, wy2, wx2 + subps xmm4, xmm2 // xmm4 = -xx2-zz2+1, xy2-wz2, xz2+wy2, yz2+wx2 + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) // xmm6 = t.y, t.z, w, t.x + shufps xmm4, xmm4, R_SHUFFLE_PS( 1, 0, 3, 2 ) // xmm4 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movaps [edi-JOINTMAT_SIZE+1*16+0*4], xmm4 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, xz2+wy2 + movss [edi-JOINTMAT_SIZE+1*16+3*4], xmm6 // row1 = xy2-wz2, -xx2-zz2+1, yz2+wx2, t.y + + // calculate third row + movhlps xmm3, xmm4 // xmm3 = yz2+wx2, xz2+wy2, xz2-wy2, yz2-wx2 + shufps xmm3, xmm7, R_SHUFFLE_PS( 1, 3, 0, 2 ) // xmm3 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + movaps [edi-JOINTMAT_SIZE+2*16+0*4], xmm3 // row2 = xz2+wy2, yz2-wx2, -xx2-yy2+1, t.z + + jl loopQuat + + done: + } + +#else + + for ( int i = 0; i < numJoints; i++ ) { + const float *q = &jointQuats[i].q; + float *m = jointMats[i].mat; + + float x2 = q[0] + q[0]; + float y2 = q[1] + q[1]; + float z2 = q[2] + q[2]; + float w2 = q[3] + q[3]; + + float yy2 = q[1] * y2; + float xy2 = q[0] * y2; + float xz2 = q[0] * z2; + float yz2 = q[1] * z2; + + float zz2 = q[2] * z2; + float wz2 = q[3] * z2; + float wy2 = q[3] * y2; + float wx2 = q[3] * x2; + + float xx2 = q[0] * x2; + + m[0*4+0] = - yy2 - zz2 + 1.0f; + m[0*4+1] = xy2 + wz2; + m[0*4+2] = xz2 - wy2; + m[0*4+3] = q[4]; + + m[1*4+0] = xy2 - wz2; + m[1*4+1] = - xx2 - zz2 + 1.0f; + m[1*4+2] = yz2 + wx2; + m[1*4+3] = q[5]; + + m[2*4+0] = xz2 + wy2; + m[2*4+1] = yz2 - wx2; + m[2*4+2] = - xx2 - yy2 + 1.0f; + m[2*4+3] = q[6]; + } + +#endif +} + +/* +============ +idSIMD_SSE2::MultiplyJoints +============ +*/ +/* +ALIGN4_INIT4( unsigned long SIMD_SP_quatmul_xor0, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quatmul_xor1, IEEE_SP_ZERO, IEEE_SP_ZERO, IEEE_SP_SIGN, IEEE_SP_SIGN ); +ALIGN4_INIT4( unsigned long SIMD_SP_quatmul_xor2, IEEE_SP_SIGN, IEEE_SP_ZERO, IEEE_SP_ZERO, IEEE_SP_SIGN ); + +void VPCALL idSIMD_SSE2::MultiplyJoints( idJointQuat *result, const idJointQuat *joints1, const idJointQuat *joints2, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( result ); + assert_16_byte_aligned( joints1 ); + assert_16_byte_aligned( joints2 ); + + __asm { + + mov eax, numJoints + test eax, eax + jz done + mov ecx, joints1 + mov edx, joints2 + mov edi, result + imul eax, JOINTMAT_SIZE + add ecx, eax + add edx, eax + add edi, eax + neg eax + + loopQuat: + movaps xmm0, [ecx+eax] // Ax, Ay, Az, Aw 6/1 + movaps xmm1, [edx+eax] // Bx, By, Bz, Bw 6/1 + pshufd xmm2, xmm0, 0x00 // Ax, Ax, Ax, Ax 4/2 + pshufd xmm3, xmm1, 0x1B // BW, BZ, BY, BX 4/2 + pshufd xmm4, xmm0, 0x55 // Ay, Ay, Ay, Ay 4/2 + pshufd xmm5, xmm1, 0x4E // Bz, Bw, Bx, By 4/2 + mulps xmm2, xmm3 // Ax*Bw, Ax*Bz, Ax*By, Ax*Bx 6/2 + pshufd xmm6, xmm0, 0xAA // Az, Az, Az, Az 4/2 + mulps xmm4, xmm5 // Ay*Bz, Ay*Bw, Ay*Bx, Ay*By 6/2 + pshufd xmm7, xmm1, 0xB1 // By, Bx, Bw, Bz 4/2 + xorps xmm2, SIMD_SP_quatmul_xor0 // Ax*Bw, -Ax*Bz, Ax*By, -Ax*Bx 4/2 + pshufd xmm0, xmm0, 0xFF // Aw, Aw, Aw, Aw 4/2 + mulps xmm6, xmm7 // Az*By, Az*Bx, Az*Bw, Az*Bz 6/2 + xorps xmm4, SIMD_SP_quatmul_xor1 // Ay*Bz, Ay*Bw, -Ay*Bx, -Ay*By 4/2 + mulps xmm0, xmm1 // Aw*Bx, Aw*By, Aw*Bz, Aw*Bw 6/2 + addps xmm2, xmm4 // Ax*Bw+Ay*Bz, -Ax*Bz+Ay*Bw, Ax*By-Ay*Bx, -Ax*Bx-Ay*By 4/2 + xorps xmm6, SIMD_SP_quatmul_xor2 // -Az*By, Az*Bx, Az*Bw, -Az*Bz 4/2 + addps xmm0, xmm2 // add to the result 4/2 + addps xmm0, xmm6 // add to the result 4/2 + movaps [edi+eax], xmm0 // save result 6/1 + add eax, JOINTQUAT_SIZE + jl loopQuat + } + +#else + + int i; + + for ( i = 0; i < numJoints; i++ ) { + result[i].q = joints1[i].q * joints2[i].q; + } + +#endif +} +*/ + +/* +============ +idSIMD_SSE2::TransformJoints +============ +*/ +void VPCALL idSIMD_SSE2::TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov ecx, firstJoint + mov eax, lastJoint + sub eax, ecx + jl done + shl ecx, 2 // ecx = firstJoint * 4 + mov edi, parents + add edi, ecx // edx = &parents[firstJoint] + lea ecx, [ecx+ecx*2] + shl ecx, 2 // ecx = firstJoint * JOINTMAT_SIZE + mov esi, jointMats // esi = jointMats + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + add edi, eax + neg eax + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movaps xmm7, [esi+edx+ 0] + pshufd xmm4, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm4, xmm0 + pshufd xmm5, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm5, xmm1 + addps xmm4, xmm5 + + add ecx, JOINTMAT_SIZE + add eax, 4 + + pshufd xmm6, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm6, xmm2 + addps xmm4, xmm6 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm4, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+ 0], xmm4 + + movaps xmm3, [esi+edx+16] + pshufd xmm5, xmm3, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm5, xmm0 + pshufd xmm6, xmm3, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm6, xmm1 + addps xmm5, xmm6 + pshufd xmm4, xmm3, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm4, xmm2 + addps xmm5, xmm4 + andps xmm3, SIMD_SP_clearFirstThree + addps xmm5, xmm3 + + movaps [esi+ecx-JOINTMAT_SIZE+16], xmm5 + + movaps xmm7, [esi+edx+32] + pshufd xmm6, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm6, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm6, xmm4 + pshufd xmm3, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm3, xmm2 + addps xmm6, xmm3 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm6, xmm7 + + movaps [esi+ecx-JOINTMAT_SIZE+32], xmm6 + + jle loopJoint + done: + } + +#else + + int i; + + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE2::UntransformJoints +============ +*/ +void VPCALL idSIMD_SSE2::UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ) { +#if 1 + + assert_16_byte_aligned( jointMats ); + + __asm { + + mov edx, firstJoint + mov eax, lastJoint + mov ecx, eax + sub eax, edx + jl done + mov esi, jointMats // esi = jointMats + lea ecx, [ecx+ecx*2] + shl ecx, 4 // ecx = lastJoint * JOINTMAT_SIZE + shl edx, 2 + mov edi, parents + add edi, edx // edi = &parents[firstJoint] + shl eax, 2 // eax = ( lastJoint - firstJoint ) * 4 + + loopJoint: + + mov edx, [edi+eax] + movaps xmm0, [esi+ecx+ 0] // xmm0 = m0, m1, m2, t0 + lea edx, [edx+edx*2] + movaps xmm1, [esi+ecx+16] // xmm1 = m2, m3, m4, t1 + shl edx, 4 // edx = parents[i] * JOINTMAT_SIZE + movaps xmm2, [esi+ecx+32] // xmm2 = m5, m6, m7, t2 + + movss xmm7, [esi+edx+12] + shufps xmm7, xmm7, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm0, xmm7 + movss xmm6, [esi+edx+28] + shufps xmm6, xmm6, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm1, xmm6 + movss xmm5, [esi+edx+44] + shufps xmm5, xmm5, R_SHUFFLE_PS( 1, 2, 3, 0 ) + subps xmm2, xmm5 + + sub ecx, JOINTMAT_SIZE + sub eax, 4 + + movaps xmm7, [esi+edx+ 0] + + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm0 + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm0 + + movaps xmm7, [esi+edx+16] + + pshufd xmm0, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm0, xmm1 + addps xmm3, xmm0 + pshufd xmm6, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm6, xmm1 + addps xmm4, xmm6 + pshufd xmm0, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm0, xmm1 + addps xmm5, xmm0 + + movaps xmm7, [esi+edx+32] + + pshufd xmm6, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm6, xmm2 + addps xmm3, xmm6 + + movaps [esi+ecx+JOINTMAT_SIZE+ 0], xmm3 + + pshufd xmm1, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm1, xmm2 + addps xmm4, xmm1 + + movaps [esi+ecx+JOINTMAT_SIZE+16], xmm4 + + pshufd xmm6, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm6, xmm2 + addps xmm5, xmm6 + + movaps [esi+ecx+JOINTMAT_SIZE+32], xmm5 + + jge loopJoint + done: + } + +#else + + int i; + + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } + +#endif +} + +/* +============ +idSIMD_SSE2::MultiplyJoints +============ +*/ +void VPCALL idSIMD_SSE2::MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ) { +#if 1 + + assert_16_byte_aligned( result ); + assert_16_byte_aligned( joints1 ); + assert_16_byte_aligned( joints2 ); + + __asm { + + mov eax, numJoints + test eax, eax + jz done + mov ecx, joints1 + mov edx, joints2 + mov edi, result + imul eax, JOINTMAT_SIZE + add ecx, eax + add edx, eax + add edi, eax + neg eax + + loopJoint: + + movaps xmm0, [edx+eax+ 0] + movaps xmm1, [edx+eax+16] + movaps xmm2, [edx+eax+32] + + movaps xmm7, [ecx+eax+ 0] + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + + add eax, JOINTMAT_SIZE + + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm3, xmm7 + + movaps [edi+eax-JOINTMAT_SIZE+0], xmm3 + + movaps xmm7, [ecx+eax-JOINTMAT_SIZE+16] + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm3, xmm7 + + movaps [edi+eax-JOINTMAT_SIZE+16], xmm3 + + movaps xmm7, [ecx+eax-JOINTMAT_SIZE+32] + pshufd xmm3, xmm7, R_SHUFFLE_D( 0, 0, 0, 0 ) + mulps xmm3, xmm0 + pshufd xmm4, xmm7, R_SHUFFLE_D( 1, 1, 1, 1 ) + mulps xmm4, xmm1 + addps xmm3, xmm4 + pshufd xmm5, xmm7, R_SHUFFLE_D( 2, 2, 2, 2 ) + mulps xmm5, xmm2 + addps xmm3, xmm5 + andps xmm7, SIMD_SP_clearFirstThree + addps xmm3, xmm7 + + movaps [edi+eax-JOINTMAT_SIZE+32], xmm3 + + jl loopJoint + done: + } + +#else + + int i; + + for ( i = 0; i < numJoints; i++ ) { + idJointMat::Multiply( result[i], joints1[i], joints2[i] ); + } + +#endif +} + +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +/* +============ +idSIMD_SSE2::TransformVertsNew +============ +*/ +void VPCALL idSIMD_SSE2::TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm2, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + add esi, BASEVECTOR_SIZE + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm5, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + add esi, BASEVECTOR_SIZE + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add eax, DRAWVERT_SIZE + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + pshufd xmm5, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm5, xmm6 // xmm5 = m6+m8+m7+t2 + movss xmm6, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm5 + + movaps xmm7, xmm6 + minps xmm7, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm7 + movaps tmpMax, xmm6 + + jl loopVert + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE2::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_SSE2::TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + movss xmm0, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm0, xmm0, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm1, xmm0 + movaps xmm2, xmm0 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + movss xmm3, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm3, xmm3, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add esi, 4*BASEVECTOR_SIZE + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE2::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_SSE2::TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + ALIGN16( float tmpMin[4] ); + ALIGN16( float tmpMax[4] ); + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + movaps tmpMin, xmm6 + movaps tmpMax, xmm7 + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + + add esi, 4*BASEVECTOR_SIZE + + movaps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + movaps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + movaps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + add edx, dword ptr [edx+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET] + + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm4 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + movss xmm6, xmm7 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm7 + + movaps xmm5, xmm6 + minps xmm5, tmpMin + maxps xmm6, tmpMax + movaps tmpMin, xmm5 + movaps tmpMax, xmm6 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm7 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + movaps xmm6, xmm3 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm4 // xmm6 = m0, m3, m1, m4 + unpckhps xmm3, xmm4 // xmm3 = m2, m5, t0, t1 + addps xmm6, xmm3 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm5 // xmm7 = m6, m7, m8, t2 + movlhps xmm5, xmm6 // xmm5 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm5 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm7 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + movaps xmm6, xmm0 // xmm6 = m0, m1, m2, t0 + unpcklps xmm6, xmm1 // xmm6 = m0, m3, m1, m4 + unpckhps xmm0, xmm1 // xmm1 = m2, m5, t0, t1 + addps xmm6, xmm0 // xmm6 = m0+m2, m3+m5, m1+t0, m4+t1 + + movaps xmm7, xmm2 // xmm7 = m6, m7, m8, t2 + movlhps xmm2, xmm6 // xmm2 = m6, m7, m0+m2, m3+m5 + movhlps xmm6, xmm7 // xmm6 = m8, t2, m1+t0, m4+t1 + addps xmm6, xmm2 // xmm6 = m6+m8, m7+t2, m0+m1+m2+t0, m3+m4+m5+t1 + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm6 + + pshufd xmm7, xmm6, R_SHUFFLE_D( 1, 0, 2, 3 ) // xmm7 = m7+t2, m6+m8 + addss xmm7, xmm6 // xmm7 = m6+m8+m7+t2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm7 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movaps xmm6, tmpMin + movaps xmm7, tmpMax + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE2::ShadowVolume_CountFacing +============ +*/ +int VPCALL idSIMD_SSE2::ShadowVolume_CountFacing( const byte *facing, const int numFaces ) { +#if 1 + ALIGN16( int n[4]; ) + + __asm { + + mov eax, numFaces + mov edi, facing + test eax, eax + jz done + + pxor xmm6, xmm6 + pxor xmm7, xmm7 + + sub eax, 256 + jl run16 + + loop256: + movdqa xmm0, [edi+ 0*16] + movdqa xmm1, [edi+ 1*16] + movdqa xmm2, [edi+ 2*16] + movdqa xmm3, [edi+ 3*16] + paddusb xmm0, [edi+ 4*16] + paddusb xmm1, [edi+ 5*16] + paddusb xmm2, [edi+ 6*16] + paddusb xmm3, [edi+ 7*16] + paddusb xmm0, [edi+ 8*16] + paddusb xmm1, [edi+ 9*16] + paddusb xmm2, [edi+10*16] + paddusb xmm3, [edi+11*16] + paddusb xmm0, [edi+12*16] + paddusb xmm1, [edi+13*16] + paddusb xmm2, [edi+14*16] + paddusb xmm3, [edi+15*16] + paddusb xmm0, xmm1 + paddusb xmm2, xmm3 + paddusb xmm0, xmm2 + + add edi, 256 + sub eax, 256 + + movdqa xmm1, xmm0 + punpcklbw xmm0, xmm7 + punpckhbw xmm1, xmm7 + paddusw xmm0, xmm1 + movdqa xmm1, xmm0 + punpcklwd xmm0, xmm7 + punpckhwd xmm1, xmm7 + paddd xmm0, xmm1 + paddd xmm6, xmm0 + + jge loop256 + + run16: + pxor xmm0, xmm0 + add eax, 256 - 16 + jl run4 + + loop16: + paddusb xmm0, [edi] + add edi, 16 + sub eax, 16 + jge loop16 + + run4: + add eax, 16 - 4 + jl run1 + + pxor xmm1, xmm1 + + loop4: + movd xmm1, [edi] + paddusb xmm0, xmm1 + add edi, 4 + sub eax, 4 + jge loop4 + + run1: + movdqa xmm1, xmm0 + punpcklbw xmm0, xmm7 + punpckhbw xmm1, xmm7 + paddusw xmm0, xmm1 + movdqa xmm1, xmm0 + punpcklwd xmm0, xmm7 + punpckhwd xmm1, xmm7 + paddd xmm0, xmm1 + paddd xmm6, xmm0 + + movdqa n, xmm6 + add eax, 4-1 + jl done + + mov edx, dword ptr n + + loop1: + movzx ecx, [edi] + add edx, ecx + add edi, 1 + sub eax, 1 + jge loop1 + + mov dword ptr n, edx + + done: + } + + return n[0] + n[1] + n[2] + n[3]; + +#else + + int i, n0, n1, n2, n3; + + n0 = n1 = n2 = n3 = 0; + for ( i = 0; i < numFaces - 3; i += 4 ) { + n0 += facing[i+0]; + n1 += facing[i+1]; + n2 += facing[i+2]; + n3 += facing[i+3]; + } + for ( ; i < numFaces; i++ ) { + n0 += facing[i]; + } + return n0 + n1 + n2 + n3; + +#endif +} + +/* +============ +idSIMD_Generic::ShadowVolume_CountFacingCull +============ +*/ +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +int VPCALL idSIMD_SSE2::ShadowVolume_CountFacingCull( byte *facing, const int numFaces, const int *indexes, const byte *cull ) { +#if 1 + + ALIGN16( int n[4]; ) + + __asm { + push ebx + mov eax, numFaces + mov esi, indexes + mov edi, cull + mov ebx, facing + test eax, eax + jz done + add ebx, eax + neg eax + + pxor xmm5, xmm5 + pxor xmm6, xmm6 + movdqa xmm7, SIMD_B_one + + add eax, 4 + jg run1 + + loop4: + + mov ecx, dword ptr [esi+0*4] + movzx edx, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+1*4] + and dl, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+2*4] + and dl, byte ptr [edi+ecx] + + mov ecx, dword ptr [esi+3*4] + mov dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+4*4] + and dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+5*4] + and dh, byte ptr [edi+ecx] + movd xmm0, edx + + mov ecx, dword ptr [esi+6*4] + movzx edx, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+7*4] + and dl, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+8*4] + and dl, byte ptr [edi+ecx] + + mov ecx, dword ptr [esi+9*4] + mov dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+10*4] + and dh, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+11*4] + and dh, byte ptr [edi+ecx] + pinsrw xmm0, edx, 1 + + add esi, 12*4 + + movd xmm1, [ebx+eax-4] + pcmpgtb xmm0, xmm6 + pand xmm0, xmm7 + por xmm1, xmm0 + movd [ebx+eax-4], xmm1 + + add eax, 4 + + punpcklbw xmm1, xmm6 + punpcklwd xmm1, xmm6 + paddd xmm5, xmm1 + + jle loop4 + + run1: + sub eax, 4 + jge done + + loop1: + mov ecx, dword ptr [esi+0*4] + movzx edx, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+1*4] + and dl, byte ptr [edi+ecx] + mov ecx, dword ptr [esi+2*4] + and dl, byte ptr [edi+ecx] + + neg edx + shr edx, 31 + movzx ecx, byte ptr [ebx+eax] + or ecx, edx + mov byte ptr [ebx+eax], cl + movd xmm0, ecx + paddd xmm5, xmm0 + + add esi, 3*4 + add eax, 1 + jl loop1 + + done: + pop ebx + movdqa dword ptr n, xmm5 + } + + return n[0] + n[1] + n[2] + n[3]; + +#else + + int i, n; + + n = 0; + for ( i = 0; i < numFaces - 3; i += 4 ) { + int c0 = cull[indexes[0]] & cull[indexes[ 1]] & cull[indexes[ 2]]; + int c1 = cull[indexes[3]] & cull[indexes[ 4]] & cull[indexes[ 5]]; + int c2 = cull[indexes[6]] & cull[indexes[ 7]] & cull[indexes[ 8]]; + int c3 = cull[indexes[9]] & cull[indexes[10]] & cull[indexes[11]]; + facing[i+0] |= ( (-c0) >> 31 ) & 1; + facing[i+1] |= ( (-c1) >> 31 ) & 1; + facing[i+2] |= ( (-c2) >> 31 ) & 1; + facing[i+3] |= ( (-c3) >> 31 ) & 1; + n += facing[i+0]; + n += facing[i+1]; + n += facing[i+2]; + n += facing[i+3]; + indexes += 12; + } + for ( ; i < numFaces; i++ ) { + int c = cull[indexes[0]] & cull[indexes[1]] & cull[indexes[2]]; + c = ( (-c) >> 31 ) & 1; + facing[i] |= c; + n += facing[i]; + indexes += 3; + } + return n; + +#endif +} + +/* +============ +idSIMD_SSE2::ShadowVolume_CreateSilTriangles +============ +*/ +int VPCALL idSIMD_SSE2::ShadowVolume_CreateSilTriangles( int *shadowIndexes, const byte *facing, const silEdge_s *silEdges, const int numSilEdges ) { +#if 1 + + int num; + + __asm { + push ebx + mov eax, numSilEdges + mov ebx, shadowIndexes + mov esi, facing + mov edi, silEdges + shl eax, 4 + jz done + add edi, eax + neg eax + shr ebx, 3 + + add eax, 4*16 + jg run1 + + loop4: + mov ecx, dword ptr [edi+eax-4*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm2, ecx + mov edx, dword ptr [edi+eax-4*16+4] + movzx edx, byte ptr [esi+edx] + pinsrw xmm2, edx, 2 + movq xmm0, qword ptr [edi+eax-4*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, edx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea edx, [ecx*2+ecx] + pxor xmm0, xmm1 + add edx, ebx + movlps qword ptr [ebx*8+0*4], xmm0 + pxor xmm2, xmm0 + movhps qword ptr [ebx*8+2*4], xmm0 + movlps qword ptr [ebx*8+4*4], xmm2 + + mov ecx, dword ptr [edi+eax-3*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm3, ecx + mov ebx, dword ptr [edi+eax-3*16+4] + movzx ebx, byte ptr [esi+ebx] + pinsrw xmm3, ebx, 2 + movq xmm0, qword ptr [edi+eax-3*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm3, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, ebx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea ebx, [ecx*2+ecx] + pxor xmm0, xmm1 + add ebx, edx + movlps qword ptr [edx*8+0*4], xmm0 + pxor xmm3, xmm0 + movhps qword ptr [edx*8+2*4], xmm0 + movlps qword ptr [edx*8+4*4], xmm3 + + mov ecx, dword ptr [edi+eax-2*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm2, ecx + mov edx, dword ptr [edi+eax-2*16+4] + movzx edx, byte ptr [esi+edx] + pinsrw xmm2, edx, 2 + movq xmm0, qword ptr [edi+eax-2*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, edx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea edx, [ecx*2+ecx] + pxor xmm0, xmm1 + add edx, ebx + movlps qword ptr [ebx*8+0*4], xmm0 + pxor xmm2, xmm0 + movhps qword ptr [ebx*8+2*4], xmm0 + movlps qword ptr [ebx*8+4*4], xmm2 + + mov ecx, dword ptr [edi+eax-1*16+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm3, ecx + mov ebx, dword ptr [edi+eax-1*16+4] + movzx ebx, byte ptr [esi+ebx] + pinsrw xmm3, ebx, 2 + movq xmm0, qword ptr [edi+eax-1*16+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm3, R_SHUFFLE_PS( 2, 0, 1, 1 ) + xor ecx, ebx + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + lea ebx, [ecx*2+ecx] + pxor xmm0, xmm1 + add ebx, edx + movlps qword ptr [edx*8+0*4], xmm0 + pxor xmm3, xmm0 + movhps qword ptr [edx*8+2*4], xmm0 + add eax, 4*16 + movlps qword ptr [edx*8+4*4], xmm3 + + jle loop4 + + run1: + sub eax, 4*16 + jge done + + loop1: + mov ecx, dword ptr [edi+eax+0] + movzx ecx, byte ptr [esi+ecx] + movd xmm2, ecx + mov edx, dword ptr [edi+eax+4] + movzx edx, byte ptr [esi+edx] + pinsrw xmm2, edx, 2 + movq xmm0, qword ptr [edi+eax+8] + paddd xmm0, xmm0 // + pshufd xmm1, xmm2, R_SHUFFLE_PS( 2, 0, 1, 1 ) + pshufd xmm0, xmm0, R_SHUFFLE_PS( 0, 1, 1, 0 ) + pxor xmm0, xmm1 + movlps qword ptr [ebx*8+0*4], xmm0 + movhps qword ptr [ebx*8+2*4], xmm0 + pxor xmm2, xmm0 + movlps qword ptr [ebx*8+4*4], xmm2 + xor ecx, edx + lea edx, [ecx*2+ecx] + add ebx, edx + + add eax, 16 + jl loop1 + + done: + shl ebx, 3 + mov num, ebx + pop ebx + } + + return ( num - (int)shadowIndexes ) >> 2; + +#else + + int i; + const silEdge_t *sil; + int *si; + + si = shadowIndexes; + for ( sil = silEdges, i = numSilEdges; i > 0; i--, sil++ ) { + + int f1 = facing[sil->p1]; + int f2 = facing[sil->p2]; + + if ( !( f1 ^ f2 ) ) { + continue; + } + + int v1 = sil->v1 << 1; + int v2 = sil->v2 << 1; + + // set the two triangle winding orders based on facing + // without using a poorly-predictable branch + + si[0] = v1; + si[1] = v2 ^ f1; + si[2] = v2 ^ f2; + si[3] = v1 ^ f2; + si[4] = v1 ^ f1; + si[5] = v2 ^ 1; + + si += 6; + } + return si - shadowIndexes; + +#endif +} + +/* +============ +idSIMD_SSE2::ShadowVolume_CreateCapTriangles +============ +*/ +int VPCALL idSIMD_SSE2::ShadowVolume_CreateCapTriangles( int *shadowIndexes, const byte *facing, const int *indexes, const int numIndexes ) { +#if 1 + + int num = numIndexes / 3; + + __asm { + push ebx + mov eax, numIndexes + mov ebx, shadowIndexes + mov esi, facing + mov edi, indexes + shl eax, 2 + jz done + add edi, eax + mov eax, num + add esi, eax + neg eax + shr ebx, 3 + + movaps xmm6, SIMD_DW_capTris_c0 + movaps xmm7, SIMD_DW_capTris_c1 + movaps xmm5, SIMD_DW_capTris_c2 + + add eax, 4 + lea edx, [eax*2+eax] + jg run1 + + loop4: + movdqa xmm0, [edi+edx*4-4*3*4+0] // xmm0 = 0, 1, 2, 3 + paddd xmm0, xmm0 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 1, 0, 0 ) // xmm1 = 2, 1, 0, 0 + movzx ecx, byte ptr [esi+eax-4] + pshufd xmm2, xmm0, R_SHUFFLE_PS( 1, 2, 1, 2 ) // xmm2 = 1, 2, 1, 2 + sub ecx, 1 + pxor xmm1, xmm6 + and ecx, 3 + movlps qword ptr [ebx*8+0*4], xmm1 + add ecx, ebx + movhps qword ptr [ebx*8+2*4], xmm1 + pxor xmm2, xmm7 + movlps qword ptr [ebx*8+4*4], xmm2 + + movdqa xmm3, [edi+edx*4-3*3*4+4] // xmm3 = 4, 5, 6, 7 + paddd xmm3, xmm3 + shufps xmm0, xmm3, R_SHUFFLE_PS( 3, 3, 1, 0 ) // xmm0 = 3 3, 5, 4 + movzx ebx, byte ptr [esi+eax-3] + movdqa xmm2, xmm3 // xmm2 = 4, 5, 6, 7 + sub ebx, 1 + pxor xmm0, xmm5 + and ebx, 3 + movhps qword ptr [ecx*8+0*4], xmm0 + add ebx, ecx + movlps qword ptr [ecx*8+2*4], xmm0 + pxor xmm2, xmm7 + movlps qword ptr [ecx*8+4*4], xmm2 + + movdqa xmm0, [edi+edx*4-1*3*4-4] // xmm0 = 8, 9, 10, 11 + paddd xmm0, xmm0 + shufps xmm3, xmm0, R_SHUFFLE_PS( 2, 3, 0, 1 ) // xmm3 = 6, 7, 8, 9 + pshufd xmm1, xmm3, R_SHUFFLE_PS( 2, 1, 0, 0 ) // xmm1 = 8, 7, 6, 6 + movzx ecx, byte ptr [esi+eax-2] + pshufd xmm2, xmm3, R_SHUFFLE_PS( 1, 2, 1, 2 ) // xmm2 = 7, 8, 7, 8 + sub ecx, 1 + pxor xmm1, xmm6 + and ecx, 3 + movlps qword ptr [ebx*8+0*4], xmm1 + add ecx, ebx + movhps qword ptr [ebx*8+2*4], xmm1 + pxor xmm2, xmm7 + movlps qword ptr [ebx*8+4*4], xmm2 + + pshufd xmm1, xmm0, R_SHUFFLE_PS( 3, 2, 1, 1 ) + movzx ebx, byte ptr [esi+eax-1] + pshufd xmm2, xmm0, R_SHUFFLE_PS( 2, 3, 2, 3 ) + sub ebx, 1 + pxor xmm1, xmm6 + and ebx, 3 + movlps qword ptr [ecx*8+0*4], xmm1 + add ebx, ecx + movhps qword ptr [ecx*8+2*4], xmm1 + pxor xmm2, xmm7 + movlps qword ptr [ecx*8+4*4], xmm2 + + add edx, 3*4 + add eax, 4 + jle loop4 + + run1: + sub eax, 4 + jge done + + loop1: + lea edx, [eax*2+eax] + movdqu xmm0, [edi+edx*4+0] + paddd xmm0, xmm0 + pshufd xmm1, xmm0, R_SHUFFLE_PS( 2, 1, 0, 0 ) + pshufd xmm2, xmm0, R_SHUFFLE_PS( 1, 2, 1, 2 ) + pxor xmm1, xmm6 + movlps qword ptr [ebx*8+0*4], xmm1 + pxor xmm2, xmm7 + movhps qword ptr [ebx*8+2*4], xmm1 + movzx ecx, byte ptr [esi+eax] + movlps qword ptr [ebx*8+4*4], xmm2 + sub ecx, 1 + and ecx, 3 + add ebx, ecx + + add eax, 1 + jl loop1 + + done: + shl ebx, 3 + mov num, ebx + pop ebx + } + + return ( num - (int)shadowIndexes ) >> 2; + +#else + + int i, j; + int *si; + + si = shadowIndexes; + for ( i = 0, j = 0; i < numIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + + int i0 = indexes[i+0]; + int i1 = indexes[i+1]; + int i2 = indexes[i+2]; + + i0 += i0; + i1 += i1; + i2 += i2; + + si[0] = i2; + si[1] = i1; + si[2] = i0; + si[3] = i0 ^ 1; + si[4] = i1 ^ 1; + si[5] = i2 ^ 1; + + si += 6; + } + return si - shadowIndexes; + +#endif +} + +/* +============ +idSIMD_SSE2::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_SSE2::MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ) { + + assert( ( numSamples % MIXBUFFER_SAMPLES ) == 0 ); + + __asm { + + mov eax, numSamples + mov edi, mixBuffer + mov esi, samples + shl eax, 2 + add edi, eax + neg eax + + loop16: + + movaps xmm0, [edi+eax+0*16] + movaps xmm1, [edi+eax+1*16] + movaps xmm2, [edi+eax+2*16] + movaps xmm3, [edi+eax+3*16] + + add esi, 4*4*2 + + cvtps2dq xmm4, xmm0 + cvtps2dq xmm5, xmm1 + cvtps2dq xmm6, xmm2 + cvtps2dq xmm7, xmm3 + + prefetchnta [edi+eax+128] + + packssdw xmm4, xmm5 + packssdw xmm6, xmm7 + + add eax, 4*16 + + movlps [esi-4*4*2], xmm4 // FIXME: should not use movlps/movhps to move integer data + movhps [esi-3*4*2], xmm4 + movlps [esi-2*4*2], xmm6 + movhps [esi-1*4*2], xmm6 + + jl loop16 + } +} + +#endif /* _WINDOWS */ diff --git a/src/idlib/math/Simd_SSE2.h b/src/idlib/math/Simd_SSE2.h new file mode 100644 index 0000000..ee7a5f0 --- /dev/null +++ b/src/idlib/math/Simd_SSE2.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_SSE2_H__ +#define __MATH_SIMD_SSE2_H__ + +/* +=============================================================================== + + SSE2 implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_SSE2 : public idSIMD_SSE { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + //virtual void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float *x, const float *b, const int n, int skip = 0 ); + //virtual void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float *x, const float *b, const int n ); + + virtual void VPCALL ConvertJointQuatsToJointMats( idJointMat *jointMats, const idJointQuat *jointQuats, const int numJoints ); + virtual void VPCALL TransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL UntransformJoints( idJointMat *jointMats, const int *parents, const int firstJoint, const int lastJoint ); + virtual void VPCALL MultiplyJoints( idJointMat *result, const idJointMat *joints1, const idJointMat *joints2, const int numJoints ); + + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + + virtual int VPCALL ShadowVolume_CountFacing( const byte *facing, const int numFaces ); + virtual int VPCALL ShadowVolume_CountFacingCull( byte *facing, const int numFaces, const int *indexes, const byte *cull ); + virtual int VPCALL ShadowVolume_CreateSilTriangles( int *shadowIndexes, const byte *facing, const silEdge_s *silEdges, const int numSilEdges ); + virtual int VPCALL ShadowVolume_CreateCapTriangles( int *shadowIndexes, const byte *facing, const int *indexes, const int numIndexes ); + + virtual void VPCALL MixedSoundToSamples( short *samples, const float *mixBuffer, const int numSamples ); + +#endif +}; + +#endif /* !__MATH_SIMD_SSE2_H__ */ diff --git a/src/idlib/math/Simd_SSE3.cpp b/src/idlib/math/Simd_SSE3.cpp new file mode 100644 index 0000000..55470f6 --- /dev/null +++ b/src/idlib/math/Simd_SSE3.cpp @@ -0,0 +1,460 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" +#include "Simd_MMX.h" +#include "Simd_SSE.h" +#include "Simd_SSE2.h" +#include "Simd_SSE3.h" +#include "Simd_InstructionMacros.h" +#include "../geometry/JointTransform.h" + +//=============================================================== +// +// SSE3 implementation of idSIMDProcessor +// +//=============================================================== + +#ifdef _WINDOWS + +#include "Simd_InstructionMacros.h" + +ALIGN4_INIT1( float SIMD_SP_infinity, idMath::INFINITY ); +ALIGN4_INIT1( float SIMD_SP_negInfinity, -idMath::INFINITY ); + +/* +============ +SSE3_Dot +============ +*/ +float SSE3_Dot( const idVec4 &v1, const idVec4 &v2 ) { + float d; + __asm { + mov esi, v1 + mov edi, v2 + movaps xmm0, [esi] + mulps xmm0, [edi] + _haddps(_xmm0, _xmm0 ) + _haddps(_xmm0, _xmm0 ) + movss d, xmm0 + } + return d; +} + +/* +============ +idSIMD_SSE3::GetName +============ +*/ +const char * idSIMD_SSE3::GetName( void ) const { + return "MMX & SSE & SSE2 & SSE3"; +} + +#pragma warning( disable : 4731 ) // frame pointer register 'ebx' modified by inline assembly code + +/* +============ +idSIMD_SSE3::TransformVertsNew +============ +*/ +void VPCALL idSIMD_SSE3::TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm2, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + add esi, BASEVECTOR_SIZE + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + movaps xmm5, [esi] + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + add esi, BASEVECTOR_SIZE + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add eax, DRAWVERT_SIZE + + _haddps( _xmm0, _xmm1 ) + _haddps( _xmm2, _xmm0 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm2 + + pshufd xmm3, xmm2, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm3, xmm2 + movss xmm2, xmm3 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm3 + + minps xmm6, xmm2 + maxps xmm7, xmm2 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE3::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_SSE3::TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + + loopVert: + movss xmm2, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm2, xmm2, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm0, xmm2 + movaps xmm1, xmm2 + + mulps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + mulps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + mulps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + je doneWeight + + loopWeight: + movss xmm5, [edx+JOINTWEIGHT_WEIGHT_OFFSET] + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + shufps xmm5, xmm5, R_SHUFFLE_PS( 0, 0, 0, 0 ) + add edx, JOINTWEIGHT_SIZE + movaps xmm3, xmm5 + movaps xmm4, xmm5 + + mulps xmm3, [edi+ebx+ 0] // xmm3 = m0, m1, m2, t0 + mulps xmm4, [edi+ebx+16] // xmm4 = m3, m4, m5, t1 + mulps xmm5, [edi+ebx+32] // xmm5 = m6, m7, m8, t2 + + cmp dword ptr [edx-JOINTWEIGHT_SIZE+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET], JOINTWEIGHT_SIZE + + addps xmm0, xmm3 + addps xmm1, xmm4 + addps xmm2, xmm5 + + jne loopWeight + + doneWeight: + add esi, 4*BASEVECTOR_SIZE + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + movss xmm5, xmm4 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm4 + + minps xmm6, xmm5 + maxps xmm7, xmm5 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm4 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm4 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + _haddps( _xmm0, _xmm1 ) + _haddps( _xmm2, _xmm0 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm2 + + pshufd xmm4, xmm2, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm4 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +/* +============ +idSIMD_SSE3::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_SSE3::TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ) { + + assert_16_byte_aligned( joints ); + assert_16_byte_aligned( base ); + + __asm { + push ebx + mov eax, numVerts + test eax, eax + jz done + imul eax, DRAWVERT_SIZE + + mov ecx, verts + mov edx, weights + mov esi, base + mov edi, joints + + add ecx, eax + neg eax + + movaps xmm6, SIMD_SP_infinity + movaps xmm7, SIMD_SP_negInfinity + + loopVert: + mov ebx, dword ptr [edx+JOINTWEIGHT_JOINTMATOFFSET_OFFSET] + + add esi, 4*BASEVECTOR_SIZE + + movaps xmm0, [edi+ebx+ 0] // xmm0 = m0, m1, m2, t0 + movaps xmm1, [edi+ebx+16] // xmm1 = m3, m4, m5, t1 + movaps xmm2, [edi+ebx+32] // xmm2 = m6, m7, m8, t2 + + add edx, dword ptr [edx+JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET] + + add eax, DRAWVERT_SIZE + + // transform vertex + movaps xmm3, [esi-4*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + movss xmm5, xmm4 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_XYZ_OFFSET+8], xmm4 + + minps xmm6, xmm5 + maxps xmm7, xmm5 + + // transform normal + movaps xmm3, [esi-3*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_NORMAL_OFFSET+8], xmm4 + + // transform first tangent + movaps xmm3, [esi-2*BASEVECTOR_SIZE] + movaps xmm4, xmm3 + movaps xmm5, xmm3 + + mulps xmm3, xmm0 + mulps xmm4, xmm1 + mulps xmm5, xmm2 + + _haddps( _xmm3, _xmm4 ) + _haddps( _xmm5, _xmm3 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+0], xmm5 + + pshufd xmm4, xmm5, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm5 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT0_OFFSET+8], xmm4 + + // transform second tangent + movaps xmm3, [esi-1*BASEVECTOR_SIZE] + + mulps xmm0, xmm3 + mulps xmm1, xmm3 + mulps xmm2, xmm3 + + _haddps( _xmm0, _xmm1 ) + _haddps( _xmm2, _xmm0 ) + + movhps [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+0], xmm2 + + pshufd xmm4, xmm2, R_SHUFFLE_D( 1, 0, 2, 3 ) + addss xmm4, xmm2 + + movss [ecx+eax-DRAWVERT_SIZE+DRAWVERT_TANGENT1_OFFSET+8], xmm4 + + jl loopVert + + done: + pop ebx + mov esi, bounds + movhps [esi+ 0], xmm6 + movss [esi+ 8], xmm6 + movhps [esi+12], xmm7 + movss [esi+20], xmm7 + } +} + +#endif /* _WINDOWS */ diff --git a/src/idlib/math/Simd_SSE3.h b/src/idlib/math/Simd_SSE3.h new file mode 100644 index 0000000..24f5e53 --- /dev/null +++ b/src/idlib/math/Simd_SSE3.h @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_SSE3_H__ +#define __MATH_SIMD_SSE3_H__ + +/* +=============================================================================== + + SSE3 implementation of idSIMDProcessor + +=============================================================================== +*/ + +class idSIMD_SSE3 : public idSIMD_SSE2 { +#ifdef _WIN32 +public: + virtual const char * VPCALL GetName( void ) const; + + virtual void VPCALL TransformVertsNew( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangents( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + virtual void VPCALL TransformVertsAndTangentsFast( idDrawVert *verts, const int numVerts, idBounds &bounds, const idJointMat *joints, const idVec4 *base, const jointWeight_t *weights, const int numWeights ); + +#endif +}; + +#endif /* !__MATH_SIMD_SSE3_H__ */ diff --git a/src/idlib/math/Simd_generic.cpp b/src/idlib/math/Simd_generic.cpp new file mode 100644 index 0000000..829541a --- /dev/null +++ b/src/idlib/math/Simd_generic.cpp @@ -0,0 +1,3798 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +#include "Simd_generic.h" + + +//=============================================================== +// +// Generic implementation of idSIMDProcessor +// +//=============================================================== + +#define UNROLL1(Y) { int _IX; for (_IX=0;_IX constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpGT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGT"); +#define OPER(X) dst[(X)] = src0[(X)] > constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpGT + + dst[i] |= ( src0[i] > constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpGT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGT bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] > constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpGE + + dst[i] = src0[i] >= constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpGE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGE float-float"); +#define OPER(X) dst[(X)] = src0[(X)] >= constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpGE + + dst[i] |= ( src0[i] >= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpGE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpGE bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] >= constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLT + + dst[i] = src0[i] < constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpLT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLT"); +#define OPER(X) dst[(X)] = src0[(X)] < constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLT + + dst[i] |= ( src0[i] < constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpLT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLT bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] < constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLE + + dst[i] = src0[i] <= constant; +============ +*/ +void VPCALL idSIMD_Generic::CmpLE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLE"); +#define OPER(X) dst[(X)] = src0[(X)] <= constant; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::CmpLE + + dst[i] |= ( src0[i] <= constant ) << bitNum; +============ +*/ +void VPCALL idSIMD_Generic::CmpLE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD CmpLE bitNum"); +#define OPER(X) dst[(X)] |= ( src0[(X)] <= constant ) << bitNum; + UNROLL4(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( float &min, float &max, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax float"); + min = idMath::INFINITY; max = -idMath::INFINITY; +#define OPER(X) if ( src[(X)] < min ) {min = src[(X)];} if ( src[(X)] > max ) {max = src[(X)];} + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec2 &min, idVec2 &max, const idVec2 * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idVec2"); + min[0] = min[1] = idMath::INFINITY; max[0] = max[1] = -idMath::INFINITY; +#define OPER(X) const idVec2 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idVec3 * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idVec3"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)]; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idDrawVert"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)].xyz; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int *indexes, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax idDrawVert indexed"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[indexes[(X)]].xyz; if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Clamp +============ +*/ +void VPCALL idSIMD_Generic::Clamp( float * RESTRICT dst, const float * RESTRICT src, const float min, const float max, const int count ) { + TIME_THIS_SCOPE("SIMD Clamp"); +#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)] > max ? max : src[(X)]; + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::ClampMin +============ +*/ +void VPCALL idSIMD_Generic::ClampMin( float * RESTRICT dst, const float * RESTRICT src, const float min, const int count ) { + TIME_THIS_SCOPE("SIMD ClampMin"); +#define OPER(X) dst[(X)] = src[(X)] < min ? min : src[(X)]; + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::ClampMax +============ +*/ +void VPCALL idSIMD_Generic::ClampMax( float * RESTRICT dst, const float * RESTRICT src, const float max, const int count ) { + TIME_THIS_SCOPE("SIMD ClampMax"); +#define OPER(X) dst[(X)] = src[(X)] > max ? max : src[(X)]; + UNROLL1(OPER) +#undef OPER +} + +/* +================ +idSIMD_Generic::Memcpy +================ +*/ +void VPCALL idSIMD_Generic::Memcpy( void * RESTRICT dst, const void * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Memcpy"); + memcpy( dst, src, count ); +} + +/* +================ +idSIMD_Generic::Memset +================ +*/ +void VPCALL idSIMD_Generic::Memset( void * RESTRICT dst, const int val, const int count ) { + TIME_THIS_SCOPE("SIMD Memset"); + memset( dst, val, count ); +} + +/* +============ +idSIMD_Generic::Zero16 +============ +*/ +void VPCALL idSIMD_Generic::Zero16( float * RESTRICT dst, const int count ) { + TIME_THIS_SCOPE("SIMD Zero16"); + memset( dst, 0, count * sizeof( float ) ); +} + +/* +============ +idSIMD_Generic::Negate16 +============ +*/ +void VPCALL idSIMD_Generic::Negate16( float * RESTRICT dst, const int count ) { + TIME_THIS_SCOPE("SIMD Negate16"); + unsigned int * RESTRICT ptr = reinterpret_cast(dst); +#define OPER(X) ptr[(X)] ^= ( 1 << 31 ) // IEEE 32 bits float sign bit + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Copy16 +============ +*/ +void VPCALL idSIMD_Generic::Copy16( float * RESTRICT dst, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Copy16"); +#define OPER(X) dst[(X)] = src[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Add16 +============ +*/ +void VPCALL idSIMD_Generic::Add16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ) { + TIME_THIS_SCOPE("SIMD Add16"); +#define OPER(X) dst[(X)] = src1[(X)] + src2[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Sub16 +============ +*/ +void VPCALL idSIMD_Generic::Sub16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ) { + TIME_THIS_SCOPE("SIMD Sub16"); +#define OPER(X) dst[(X)] = src1[(X)] - src2[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Mul16 +============ +*/ +void VPCALL idSIMD_Generic::Mul16( float * RESTRICT dst, const float * RESTRICT src1, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD Mul16"); +#define OPER(X) dst[(X)] = src1[(X)] * constant + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::AddAssign16 +============ +*/ +void VPCALL idSIMD_Generic::AddAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD AddAssign16"); +#define OPER(X) dst[(X)] += src[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::SubAssign16 +============ +*/ +void VPCALL idSIMD_Generic::SubAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD SubAssign16"); +#define OPER(X) dst[(X)] -= src[(X)] + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MulAssign16 +============ +*/ +void VPCALL idSIMD_Generic::MulAssign16( float * RESTRICT dst, const float constant, const int count ) { + TIME_THIS_SCOPE("SIMD MulAssign16"); +#define OPER(X) dst[(X)] *= constant + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MatX_MultiplyVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplyVecX"); + int i, j, numRows; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + break; + case 3: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + break; + case 4: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3]; + mPtr += 4; + } + break; + case 5: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + break; + case 6: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] = mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + break; + default: + int numColumns = mat.GetNumColumns(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] = sum; + mPtr += numColumns; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_MultiplyAddVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplyAddVecX"); + int i, j, numRows; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + break; + case 3: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + break; + case 4: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3]; + mPtr += 4; + } + break; + case 5: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + break; + case 6: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] += mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + break; + default: + int numColumns = mat.GetNumColumns(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] += sum; + mPtr += numColumns; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_MultiplySubVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplySubVecX"); + int i, j, numRows; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumColumns() ); + assert( dst.GetSize() >= mat.GetNumRows() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numRows = mat.GetNumRows(); + switch( mat.GetNumColumns() ) { + case 1: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1]; + mPtr += 2; + } + break; + case 3: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2]; + mPtr += 3; + } + break; + case 4: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3]; + mPtr += 4; + } + break; + case 5: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4]; + mPtr += 5; + } + break; + case 6: + for ( i = 0; i < numRows; i++ ) { + dstPtr[i] -= mPtr[0] * vPtr[0] + mPtr[1] * vPtr[1] + mPtr[2] * vPtr[2] + + mPtr[3] * vPtr[3] + mPtr[4] * vPtr[4] + mPtr[5] * vPtr[5]; + mPtr += 6; + } + break; + default: + int numColumns = mat.GetNumColumns(); + for ( i = 0; i < numRows; i++ ) { + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numColumns; j++ ) { + sum += mPtr[j] * vPtr[j]; + } + dstPtr[i] -= sum; + mPtr += numColumns; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplyVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplyVecX"); + int i, j, numColumns; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + break; + case 3: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + break; + case 4: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + break; + case 5: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + break; + case 6: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] = *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + break; + default: + int numRows = mat.GetNumRows(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] = sum; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplyAddVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplyAddVecX"); + int i, j, numColumns; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + break; + case 3: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + break; + case 4: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + break; + case 5: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + break; + case 6: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] += *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + break; + default: + int numRows = mat.GetNumRows(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] += sum; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplySubVecX +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplySubVecX"); + int i, numColumns; + const float * RESTRICT mPtr, * RESTRICT vPtr; + float * RESTRICT dstPtr; + + assert( vec.GetSize() >= mat.GetNumRows() ); + assert( dst.GetSize() >= mat.GetNumColumns() ); + + mPtr = mat.ToFloatPtr(); + vPtr = vec.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + numColumns = mat.GetNumColumns(); + switch( mat.GetNumRows() ) { + case 1: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0]; + mPtr++; + } + break; + case 2: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1]; + mPtr++; + } + break; + case 3: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2]; + mPtr++; + } + break; + case 4: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3]; + mPtr++; + } + break; + case 5: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4]; + mPtr++; + } + break; + case 6: + for ( i = 0; i < numColumns; i++ ) { + dstPtr[i] -= *(mPtr) * vPtr[0] + *(mPtr+numColumns) * vPtr[1] + *(mPtr+2*numColumns) * vPtr[2] + + *(mPtr+3*numColumns) * vPtr[3] + *(mPtr+4*numColumns) * vPtr[4] + *(mPtr+5*numColumns) * vPtr[5]; + mPtr++; + } + break; + default: + int numRows = mat.GetNumRows(); + for ( i = 0; i < numColumns; i++ ) { + mPtr = mat.ToFloatPtr() + i; + float sum = mPtr[0] * vPtr[0]; + for ( int j = 1; j < numRows; j++ ) { + mPtr += numColumns; + sum += mPtr[0] * vPtr[j]; + } + dstPtr[i] -= sum; + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_MultiplyMatX + + optimizes the following matrix multiplications: + + NxN * Nx6 + 6xN * Nx6 + Nx6 * 6xN + 6x6 * 6xN + + with N in the range [1-6]. +============ +*/ +void VPCALL idSIMD_Generic::MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + TIME_THIS_SCOPE("SIMD MatX_MultiplyMatX"); + int i, j, k, l, n; + float * RESTRICT dstPtr; + const float * RESTRICT m1Ptr, * RESTRICT m2Ptr; + double sum; + + assert( m1.GetNumColumns() == m2.GetNumRows() ); + + dstPtr = dst.ToFloatPtr(); + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + k = m1.GetNumRows(); + l = m2.GetNumColumns(); + + switch( m1.GetNumColumns() ) { + case 1: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx1 * 1x6 + *dstPtr++ = m1Ptr[i] * m2Ptr[0]; + *dstPtr++ = m1Ptr[i] * m2Ptr[1]; + *dstPtr++ = m1Ptr[i] * m2Ptr[2]; + *dstPtr++ = m1Ptr[i] * m2Ptr[3]; + *dstPtr++ = m1Ptr[i] * m2Ptr[4]; + *dstPtr++ = m1Ptr[i] * m2Ptr[5]; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + } + case 2: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx2 * 2x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11]; + m1Ptr += 2; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr += 2; + } + break; + } + case 3: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx3 * 3x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6] + m1Ptr[2] * m2Ptr[12]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7] + m1Ptr[2] * m2Ptr[13]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8] + m1Ptr[2] * m2Ptr[14]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9] + m1Ptr[2] * m2Ptr[15]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10] + m1Ptr[2] * m2Ptr[16]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11] + m1Ptr[2] * m2Ptr[17]; + m1Ptr += 3; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr += 3; + } + break; + } + case 4: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx4 * 4x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6] + m1Ptr[2] * m2Ptr[12] + m1Ptr[3] * m2Ptr[18]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7] + m1Ptr[2] * m2Ptr[13] + m1Ptr[3] * m2Ptr[19]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8] + m1Ptr[2] * m2Ptr[14] + m1Ptr[3] * m2Ptr[20]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9] + m1Ptr[2] * m2Ptr[15] + m1Ptr[3] * m2Ptr[21]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10] + m1Ptr[2] * m2Ptr[16] + m1Ptr[3] * m2Ptr[22]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11] + m1Ptr[2] * m2Ptr[17] + m1Ptr[3] * m2Ptr[23]; + m1Ptr += 4; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr += 4; + } + break; + } + case 5: { + if ( l == 6 ) { + for ( i = 0; i < k; i++ ) { // Nx5 * 5x6 + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[6] + m1Ptr[2] * m2Ptr[12] + m1Ptr[3] * m2Ptr[18] + m1Ptr[4] * m2Ptr[24]; + *dstPtr++ = m1Ptr[0] * m2Ptr[1] + m1Ptr[1] * m2Ptr[7] + m1Ptr[2] * m2Ptr[13] + m1Ptr[3] * m2Ptr[19] + m1Ptr[4] * m2Ptr[25]; + *dstPtr++ = m1Ptr[0] * m2Ptr[2] + m1Ptr[1] * m2Ptr[8] + m1Ptr[2] * m2Ptr[14] + m1Ptr[3] * m2Ptr[20] + m1Ptr[4] * m2Ptr[26]; + *dstPtr++ = m1Ptr[0] * m2Ptr[3] + m1Ptr[1] * m2Ptr[9] + m1Ptr[2] * m2Ptr[15] + m1Ptr[3] * m2Ptr[21] + m1Ptr[4] * m2Ptr[27]; + *dstPtr++ = m1Ptr[0] * m2Ptr[4] + m1Ptr[1] * m2Ptr[10] + m1Ptr[2] * m2Ptr[16] + m1Ptr[3] * m2Ptr[22] + m1Ptr[4] * m2Ptr[28]; + *dstPtr++ = m1Ptr[0] * m2Ptr[5] + m1Ptr[1] * m2Ptr[11] + m1Ptr[2] * m2Ptr[17] + m1Ptr[3] * m2Ptr[23] + m1Ptr[4] * m2Ptr[29]; + m1Ptr += 5; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr += 5; + } + break; + } + case 6: { + switch( k ) { + case 1: { + if ( l == 1 ) { // 1x6 * 6x1 + dstPtr[0] = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[1] + m1Ptr[2] * m2Ptr[2] + + m1Ptr[3] * m2Ptr[3] + m1Ptr[4] * m2Ptr[4] + m1Ptr[5] * m2Ptr[5]; + return; + } + break; + } + case 2: { + if ( l == 2 ) { // 2x6 * 6x2 + for ( i = 0; i < 2; i++ ) { + for ( j = 0; j < 2; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 2 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 2 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 2 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 2 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 2 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 2 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + break; + } + case 3: { + if ( l == 3 ) { // 3x6 * 6x3 + for ( i = 0; i < 3; i++ ) { + for ( j = 0; j < 3; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 3 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 3 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 3 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + break; + } + case 4: { + if ( l == 4 ) { // 4x6 * 6x4 + for ( i = 0; i < 4; i++ ) { + for ( j = 0; j < 4; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 4 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 4 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 4 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 4 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 4 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 4 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + } + case 5: { + if ( l == 5 ) { // 5x6 * 6x5 + for ( i = 0; i < 5; i++ ) { + for ( j = 0; j < 5; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 5 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 5 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 5 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 5 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 5 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 5 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + } + case 6: { + switch( l ) { + case 1: { // 6x6 * 6x1 + for ( i = 0; i < 6; i++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 1 ] + + m1Ptr[1] * m2Ptr[ 1 * 1 ] + + m1Ptr[2] * m2Ptr[ 2 * 1 ] + + m1Ptr[3] * m2Ptr[ 3 * 1 ] + + m1Ptr[4] * m2Ptr[ 4 * 1 ] + + m1Ptr[5] * m2Ptr[ 5 * 1 ]; + dstPtr++; + m1Ptr += 6; + } + return; + } + case 2: { // 6x6 * 6x2 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 2; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 2 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 2 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 2 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 2 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 2 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 2 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 3: { // 6x6 * 6x3 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 3; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 3 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 3 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 3 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 3 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 3 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 3 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 4: { // 6x6 * 6x4 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 4; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 4 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 4 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 4 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 4 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 4 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 4 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 5: { // 6x6 * 6x5 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 5; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 5 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 5 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 5 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 5 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 5 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 5 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + case 6: { // 6x6 * 6x6 + for ( i = 0; i < 6; i++ ) { + for ( j = 0; j < 6; j++ ) { + *dstPtr = m1Ptr[0] * m2Ptr[ 0 * 6 + j ] + + m1Ptr[1] * m2Ptr[ 1 * 6 + j ] + + m1Ptr[2] * m2Ptr[ 2 * 6 + j ] + + m1Ptr[3] * m2Ptr[ 3 * 6 + j ] + + m1Ptr[4] * m2Ptr[ 4 * 6 + j ] + + m1Ptr[5] * m2Ptr[ 5 * 6 + j ]; + dstPtr++; + } + m1Ptr += 6; + } + return; + } + } + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[1] * m2Ptr[l] + m1Ptr[2] * m2Ptr[2*l] + + m1Ptr[3] * m2Ptr[3*l] + m1Ptr[4] * m2Ptr[4*l] + m1Ptr[5] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr += 6; + } + break; + } + default: { + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumColumns(); n++ ) { + m2Ptr += l; + sum += m1Ptr[n] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + m1Ptr += m1.GetNumColumns(); + } + break; + } + } +} + +/* +============ +idSIMD_Generic::MatX_TransposeMultiplyMatX + + optimizes the following tranpose matrix multiplications: + + Nx6 * NxN + 6xN * 6x6 + + with N in the range [1-6]. +============ +*/ +void VPCALL idSIMD_Generic::MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ) { + TIME_THIS_SCOPE("SIMD MatX_TransposeMultiplyMatX"); + int i, j, k, l, n; + float * RESTRICT dstPtr; + const float * RESTRICT m1Ptr, * RESTRICT m2Ptr; + double sum; + + assert( m1.GetNumRows() == m2.GetNumRows() ); + + m1Ptr = m1.ToFloatPtr(); + m2Ptr = m2.ToFloatPtr(); + dstPtr = dst.ToFloatPtr(); + k = m1.GetNumColumns(); + l = m2.GetNumColumns(); + + switch( m1.GetNumRows() ) { + case 1: + if ( k == 6 && l == 1 ) { // 1x6 * 1x1 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 2: + if ( k == 6 && l == 2 ) { // 2x6 * 2x2 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*2+0] + m1Ptr[1*6] * m2Ptr[1*2+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*2+1] + m1Ptr[1*6] * m2Ptr[1*2+1]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 3: + if ( k == 6 && l == 3 ) { // 3x6 * 3x3 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*3+0] + m1Ptr[1*6] * m2Ptr[1*3+0] + m1Ptr[2*6] * m2Ptr[2*3+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*3+1] + m1Ptr[1*6] * m2Ptr[1*3+1] + m1Ptr[2*6] * m2Ptr[2*3+1]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*3+2] + m1Ptr[1*6] * m2Ptr[1*3+2] + m1Ptr[2*6] * m2Ptr[2*3+2]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 4: + if ( k == 6 && l == 4 ) { // 4x6 * 4x4 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+0] + m1Ptr[1*6] * m2Ptr[1*4+0] + m1Ptr[2*6] * m2Ptr[2*4+0] + m1Ptr[3*6] * m2Ptr[3*4+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+1] + m1Ptr[1*6] * m2Ptr[1*4+1] + m1Ptr[2*6] * m2Ptr[2*4+1] + m1Ptr[3*6] * m2Ptr[3*4+1]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+2] + m1Ptr[1*6] * m2Ptr[1*4+2] + m1Ptr[2*6] * m2Ptr[2*4+2] + m1Ptr[3*6] * m2Ptr[3*4+2]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*4+3] + m1Ptr[1*6] * m2Ptr[1*4+3] + m1Ptr[2*6] * m2Ptr[2*4+3] + m1Ptr[3*6] * m2Ptr[3*4+3]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 5: + if ( k == 6 && l == 5 ) { // 5x6 * 5x5 + for ( i = 0; i < 6; i++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+0] + m1Ptr[1*6] * m2Ptr[1*5+0] + m1Ptr[2*6] * m2Ptr[2*5+0] + m1Ptr[3*6] * m2Ptr[3*5+0] + m1Ptr[4*6] * m2Ptr[4*5+0]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+1] + m1Ptr[1*6] * m2Ptr[1*5+1] + m1Ptr[2*6] * m2Ptr[2*5+1] + m1Ptr[3*6] * m2Ptr[3*5+1] + m1Ptr[4*6] * m2Ptr[4*5+1]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+2] + m1Ptr[1*6] * m2Ptr[1*5+2] + m1Ptr[2*6] * m2Ptr[2*5+2] + m1Ptr[3*6] * m2Ptr[3*5+2] + m1Ptr[4*6] * m2Ptr[4*5+2]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+3] + m1Ptr[1*6] * m2Ptr[1*5+3] + m1Ptr[2*6] * m2Ptr[2*5+3] + m1Ptr[3*6] * m2Ptr[3*5+3] + m1Ptr[4*6] * m2Ptr[4*5+3]; + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*5+4] + m1Ptr[1*6] * m2Ptr[1*5+4] + m1Ptr[2*6] * m2Ptr[2*5+4] + m1Ptr[3*6] * m2Ptr[3*5+4] + m1Ptr[4*6] * m2Ptr[4*5+4]; + m1Ptr++; + } + return; + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + case 6: + if ( l == 6 ) { + switch( k ) { + case 1: // 6x1 * 6x6 + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*1] * m2Ptr[0*6] + + m1Ptr[1*1] * m2Ptr[1*6] + + m1Ptr[2*1] * m2Ptr[2*6] + + m1Ptr[3*1] * m2Ptr[3*6] + + m1Ptr[4*1] * m2Ptr[4*6] + + m1Ptr[5*1] * m2Ptr[5*6]; + m2Ptr++; + } + return; + case 2: // 6x2 * 6x6 + for ( i = 0; i < 2; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*2] * m2Ptr[0*6] + + m1Ptr[1*2] * m2Ptr[1*6] + + m1Ptr[2*2] * m2Ptr[2*6] + + m1Ptr[3*2] * m2Ptr[3*6] + + m1Ptr[4*2] * m2Ptr[4*6] + + m1Ptr[5*2] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 3: // 6x3 * 6x6 + for ( i = 0; i < 3; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*3] * m2Ptr[0*6] + + m1Ptr[1*3] * m2Ptr[1*6] + + m1Ptr[2*3] * m2Ptr[2*6] + + m1Ptr[3*3] * m2Ptr[3*6] + + m1Ptr[4*3] * m2Ptr[4*6] + + m1Ptr[5*3] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 4: // 6x4 * 6x6 + for ( i = 0; i < 4; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*4] * m2Ptr[0*6] + + m1Ptr[1*4] * m2Ptr[1*6] + + m1Ptr[2*4] * m2Ptr[2*6] + + m1Ptr[3*4] * m2Ptr[3*6] + + m1Ptr[4*4] * m2Ptr[4*6] + + m1Ptr[5*4] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 5: // 6x5 * 6x6 + for ( i = 0; i < 5; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*5] * m2Ptr[0*6] + + m1Ptr[1*5] * m2Ptr[1*6] + + m1Ptr[2*5] * m2Ptr[2*6] + + m1Ptr[3*5] * m2Ptr[3*6] + + m1Ptr[4*5] * m2Ptr[4*6] + + m1Ptr[5*5] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + case 6: // 6x6 * 6x6 + for ( i = 0; i < 6; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < 6; j++ ) { + *dstPtr++ = m1Ptr[0*6] * m2Ptr[0*6] + + m1Ptr[1*6] * m2Ptr[1*6] + + m1Ptr[2*6] * m2Ptr[2*6] + + m1Ptr[3*6] * m2Ptr[3*6] + + m1Ptr[4*6] * m2Ptr[4*6] + + m1Ptr[5*6] * m2Ptr[5*6]; + m2Ptr++; + } + m1Ptr++; + } + return; + } + } + for ( i = 0; i < k; i++ ) { + m2Ptr = m2.ToFloatPtr(); + for ( j = 0; j < l; j++ ) { + *dstPtr++ = m1Ptr[0] * m2Ptr[0] + m1Ptr[k] * m2Ptr[l] + m1Ptr[2*k] * m2Ptr[2*l] + + m1Ptr[3*k] * m2Ptr[3*l] + m1Ptr[4*k] * m2Ptr[4*l] + m1Ptr[5*k] * m2Ptr[5*l]; + m2Ptr++; + } + m1Ptr++; + } + break; + default: + for ( i = 0; i < k; i++ ) { + for ( j = 0; j < l; j++ ) { + m1Ptr = m1.ToFloatPtr() + i; + m2Ptr = m2.ToFloatPtr() + j; + sum = m1Ptr[0] * m2Ptr[0]; + for ( n = 1; n < m1.GetNumRows(); n++ ) { + m1Ptr += k; + m2Ptr += l; + sum += m1Ptr[0] * m2Ptr[0]; + } + *dstPtr++ = sum; + } + } + break; + } +} + +/* +============ +idSIMD_Generic::MatX_LowerTriangularSolve + + solves x in Lx = b for the n * n sub-matrix of L + if skip > 0 the first skip elements of x are assumed to be valid already + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_Generic::MatX_LowerTriangularSolve( const idMatX &L, float * RESTRICT x, const float * RESTRICT b, const int n, int skip ) { + TIME_THIS_SCOPE("SIMD MatX_LowerTriangularSolve"); +#if 1 + + int nc; + const float * RESTRICT lptr; + + if ( skip >= n ) { + return; + } + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + #define NSKIP( n, s ) ((n<<3)|(s&7)) + switch( NSKIP( n, skip ) ) { + case NSKIP( 1, 0 ): x[0] = b[0]; + return; + case NSKIP( 2, 0 ): x[0] = b[0]; + case NSKIP( 2, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + return; + case NSKIP( 3, 0 ): x[0] = b[0]; + case NSKIP( 3, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 3, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + return; + case NSKIP( 4, 0 ): x[0] = b[0]; + case NSKIP( 4, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 4, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 4, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + return; + case NSKIP( 5, 0 ): x[0] = b[0]; + case NSKIP( 5, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 5, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 5, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 5, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + return; + case NSKIP( 6, 0 ): x[0] = b[0]; + case NSKIP( 6, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 6, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 6, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 6, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 6, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + return; + case NSKIP( 7, 0 ): x[0] = b[0]; + case NSKIP( 7, 1 ): x[1] = b[1] - lptr[1*nc+0] * x[0]; + case NSKIP( 7, 2 ): x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case NSKIP( 7, 3 ): x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + case NSKIP( 7, 4 ): x[4] = b[4] - lptr[4*nc+0] * x[0] - lptr[4*nc+1] * x[1] - lptr[4*nc+2] * x[2] - lptr[4*nc+3] * x[3]; + case NSKIP( 7, 5 ): x[5] = b[5] - lptr[5*nc+0] * x[0] - lptr[5*nc+1] * x[1] - lptr[5*nc+2] * x[2] - lptr[5*nc+3] * x[3] - lptr[5*nc+4] * x[4]; + case NSKIP( 7, 6 ): x[6] = b[6] - lptr[6*nc+0] * x[0] - lptr[6*nc+1] * x[1] - lptr[6*nc+2] * x[2] - lptr[6*nc+3] * x[3] - lptr[6*nc+4] * x[4] - lptr[6*nc+5] * x[5]; + return; + } + return; + } + + // process first 4 rows + switch( skip ) { + case 0: x[0] = b[0]; + case 1: x[1] = b[1] - lptr[1*nc+0] * x[0]; + case 2: x[2] = b[2] - lptr[2*nc+0] * x[0] - lptr[2*nc+1] * x[1]; + case 3: x[3] = b[3] - lptr[3*nc+0] * x[0] - lptr[3*nc+1] * x[1] - lptr[3*nc+2] * x[2]; + skip = 4; + } + + lptr = L[skip]; + + int i, j; + register double s0, s1, s2, s3; + + for ( i = skip; i < n; i++ ) { + s0 = lptr[0] * x[0]; + s1 = lptr[1] * x[1]; + s2 = lptr[2] * x[2]; + s3 = lptr[3] * x[3]; + for ( j = 4; j < i-7; j += 8 ) { + s0 += lptr[j+0] * x[j+0]; + s1 += lptr[j+1] * x[j+1]; + s2 += lptr[j+2] * x[j+2]; + s3 += lptr[j+3] * x[j+3]; + s0 += lptr[j+4] * x[j+4]; + s1 += lptr[j+5] * x[j+5]; + s2 += lptr[j+6] * x[j+6]; + s3 += lptr[j+7] * x[j+7]; + } + switch( i - j ) { + case 7: s0 += lptr[j+6] * x[j+6]; + case 6: s1 += lptr[j+5] * x[j+5]; + case 5: s2 += lptr[j+4] * x[j+4]; + case 4: s3 += lptr[j+3] * x[j+3]; + case 3: s0 += lptr[j+2] * x[j+2]; + case 2: s1 += lptr[j+1] * x[j+1]; + case 1: s2 += lptr[j+0] * x[j+0]; + } + double sum; + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum -= b[i]; + x[i] = -sum; + lptr += nc; + } + +#else + + int i, j; + const float * RESTRICT lptr; + double sum; + + for ( i = skip; i < n; i++ ) { + sum = b[i]; + lptr = L[i]; + for ( j = 0; j < i; j++ ) { + sum -= lptr[j] * x[j]; + } + x[i] = sum; + } + +#endif +} + +/* +============ +idSIMD_Generic::MatX_LowerTriangularSolveTranspose + + solves x in L'x = b for the n * n sub-matrix of L + L has to be a lower triangular matrix with (implicit) ones on the diagonal + x == b is allowed +============ +*/ +void VPCALL idSIMD_Generic::MatX_LowerTriangularSolveTranspose( const idMatX &L, float * RESTRICT x, const float * RESTRICT b, const int n ) { + TIME_THIS_SCOPE("SIMD MatX_LowerTriangularSolveTranspose"); +#if 1 + + int nc; + const float * RESTRICT lptr; + + lptr = L.ToFloatPtr(); + nc = L.GetNumColumns(); + + // unrolled cases for n < 8 + if ( n < 8 ) { + switch( n ) { + case 0: + return; + case 1: + x[0] = b[0]; + return; + case 2: + x[1] = b[1]; + x[0] = b[0] - lptr[1*nc+0] * x[1]; + return; + case 3: + x[2] = b[2]; + x[1] = b[1] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 4: + x[3] = b[3]; + x[2] = b[2] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 5: + x[4] = b[4]; + x[3] = b[3] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 6: + x[5] = b[5]; + x[4] = b[4] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + case 7: + x[6] = b[6]; + x[5] = b[5] - lptr[6*nc+5] * x[6]; + x[4] = b[4] - lptr[6*nc+4] * x[6] - lptr[5*nc+4] * x[5]; + x[3] = b[3] - lptr[6*nc+3] * x[6] - lptr[5*nc+3] * x[5] - lptr[4*nc+3] * x[4]; + x[2] = b[2] - lptr[6*nc+2] * x[6] - lptr[5*nc+2] * x[5] - lptr[4*nc+2] * x[4] - lptr[3*nc+2] * x[3]; + x[1] = b[1] - lptr[6*nc+1] * x[6] - lptr[5*nc+1] * x[5] - lptr[4*nc+1] * x[4] - lptr[3*nc+1] * x[3] - lptr[2*nc+1] * x[2]; + x[0] = b[0] - lptr[6*nc+0] * x[6] - lptr[5*nc+0] * x[5] - lptr[4*nc+0] * x[4] - lptr[3*nc+0] * x[3] - lptr[2*nc+0] * x[2] - lptr[1*nc+0] * x[1]; + return; + } + return; + } + + int i, j; + register double s0, s1, s2, s3; + float * RESTRICT xptr; + + lptr = L.ToFloatPtr() + n * nc + n - 4; + xptr = x + n; + + // process 4 rows at a time + for ( i = n; i >= 4; i -= 4 ) { + s0 = b[i-4]; + s1 = b[i-3]; + s2 = b[i-2]; + s3 = b[i-1]; + // process 4x4 blocks + for ( j = 0; j < n-i; j += 4 ) { + s0 -= lptr[(j+0)*nc+0] * xptr[j+0]; + s1 -= lptr[(j+0)*nc+1] * xptr[j+0]; + s2 -= lptr[(j+0)*nc+2] * xptr[j+0]; + s3 -= lptr[(j+0)*nc+3] * xptr[j+0]; + s0 -= lptr[(j+1)*nc+0] * xptr[j+1]; + s1 -= lptr[(j+1)*nc+1] * xptr[j+1]; + s2 -= lptr[(j+1)*nc+2] * xptr[j+1]; + s3 -= lptr[(j+1)*nc+3] * xptr[j+1]; + s0 -= lptr[(j+2)*nc+0] * xptr[j+2]; + s1 -= lptr[(j+2)*nc+1] * xptr[j+2]; + s2 -= lptr[(j+2)*nc+2] * xptr[j+2]; + s3 -= lptr[(j+2)*nc+3] * xptr[j+2]; + s0 -= lptr[(j+3)*nc+0] * xptr[j+3]; + s1 -= lptr[(j+3)*nc+1] * xptr[j+3]; + s2 -= lptr[(j+3)*nc+2] * xptr[j+3]; + s3 -= lptr[(j+3)*nc+3] * xptr[j+3]; + } + // process left over of the 4 rows + s0 -= lptr[0-1*nc] * s3; + s1 -= lptr[1-1*nc] * s3; + s2 -= lptr[2-1*nc] * s3; + s0 -= lptr[0-2*nc] * s2; + s1 -= lptr[1-2*nc] * s2; + s0 -= lptr[0-3*nc] * s1; + // store result + xptr[-4] = s0; + xptr[-3] = s1; + xptr[-2] = s2; + xptr[-1] = s3; + // update pointers for next four rows + lptr -= 4 + 4 * nc; + xptr -= 4; + } + // process left over rows + for ( i--; i >= 0; i-- ) { + s0 = b[i]; + lptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + s0 -= lptr[j*nc] * x[j]; + } + x[i] = s0; + } + +#else + + int i, j, nc; + const float * RESTRICT ptr; + double sum; + + nc = L.GetNumColumns(); + for ( i = n - 1; i >= 0; i-- ) { + sum = b[i]; + ptr = L[0] + i; + for ( j = i + 1; j < n; j++ ) { + sum -= ptr[j*nc] * x[j]; + } + x[i] = sum; + } + +#endif +} + +/* +============ +idSIMD_Generic::MatX_LDLTFactor + + in-place factorization LDL' of the n * n sub-matrix of mat + the reciprocal of the diagonal elements are stored in invDiag +============ +*/ +bool VPCALL idSIMD_Generic::MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ) { + TIME_THIS_SCOPE("SIMD MatX_LDLTFactor"); +#if 1 + + int i, j, k, nc; + float * RESTRICT v, * RESTRICT diag, * RESTRICT mptr; + double s0, s1, s2, s3, sum, d; + + v = (float * RESTRICT ) _alloca16( n * sizeof( float ) ); + diag = (float * RESTRICT ) _alloca16( n * sizeof( float ) ); + + nc = mat.GetNumColumns(); + + if ( n <= 0 ) { + return true; + } + + mptr = mat[0]; + + sum = mptr[0]; + + if ( sum == 0.0f ) { + return false; + } + + diag[0] = sum; + invDiag[0] = d = 1.0f / sum; + + if ( n <= 1 ) { + return true; + } + + mptr = mat[0]; + for ( j = 1; j < n; j++ ) { + mptr[j*nc+0] = ( mptr[j*nc+0] ) * d; + } + + mptr = mat[1]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + sum = mptr[1] - s0; + + if ( sum == 0.0f ) { + return false; + } + + mat[1][1] = sum; + diag[1] = sum; + invDiag[1] = d = 1.0f / sum; + + if ( n <= 2 ) { + return true; + } + + mptr = mat[0]; + for ( j = 2; j < n; j++ ) { + mptr[j*nc+1] = ( mptr[j*nc+1] - v[0] * mptr[j*nc+0] ) * d; + } + + mptr = mat[2]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + sum = mptr[2] - s0 - s1; + + if ( sum == 0.0f ) { + return false; + } + + mat[2][2] = sum; + diag[2] = sum; + invDiag[2] = d = 1.0f / sum; + + if ( n <= 3 ) { + return true; + } + + mptr = mat[0]; + for ( j = 3; j < n; j++ ) { + mptr[j*nc+2] = ( mptr[j*nc+2] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] ) * d; + } + + mptr = mat[3]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + sum = mptr[3] - s0 - s1 - s2; + + if ( sum == 0.0f ) { + return false; + } + + mat[3][3] = sum; + diag[3] = sum; + invDiag[3] = d = 1.0f / sum; + + if ( n <= 4 ) { + return true; + } + + mptr = mat[0]; + for ( j = 4; j < n; j++ ) { + mptr[j*nc+3] = ( mptr[j*nc+3] - v[0] * mptr[j*nc+0] - v[1] * mptr[j*nc+1] - v[2] * mptr[j*nc+2] ) * d; + } + + for ( i = 4; i < n; i++ ) { + + mptr = mat[i]; + + v[0] = diag[0] * mptr[0]; s0 = v[0] * mptr[0]; + v[1] = diag[1] * mptr[1]; s1 = v[1] * mptr[1]; + v[2] = diag[2] * mptr[2]; s2 = v[2] * mptr[2]; + v[3] = diag[3] * mptr[3]; s3 = v[3] * mptr[3]; + for ( k = 4; k < i-3; k += 4 ) { + v[k+0] = diag[k+0] * mptr[k+0]; s0 += v[k+0] * mptr[k+0]; + v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + v[k+2] = diag[k+2] * mptr[k+2]; s2 += v[k+2] * mptr[k+2]; + v[k+3] = diag[k+3] * mptr[k+3]; s3 += v[k+3] * mptr[k+3]; + } + switch( i - k ) { + case 3: v[k+2] = diag[k+2] * mptr[k+2]; s0 += v[k+2] * mptr[k+2]; + case 2: v[k+1] = diag[k+1] * mptr[k+1]; s1 += v[k+1] * mptr[k+1]; + case 1: v[k+0] = diag[k+0] * mptr[k+0]; s2 += v[k+0] * mptr[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + sum = mptr[i] - sum; + + if ( sum == 0.0f ) { + return false; + } + + mat[i][i] = sum; + diag[i] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + return true; + } + + mptr = mat[i+1]; + for ( j = i+1; j < n; j++ ) { + s0 = mptr[0] * v[0]; + s1 = mptr[1] * v[1]; + s2 = mptr[2] * v[2]; + s3 = mptr[3] * v[3]; + for ( k = 4; k < i-7; k += 8 ) { + s0 += mptr[k+0] * v[k+0]; + s1 += mptr[k+1] * v[k+1]; + s2 += mptr[k+2] * v[k+2]; + s3 += mptr[k+3] * v[k+3]; + s0 += mptr[k+4] * v[k+4]; + s1 += mptr[k+5] * v[k+5]; + s2 += mptr[k+6] * v[k+6]; + s3 += mptr[k+7] * v[k+7]; + } + switch( i - k ) { + case 7: s0 += mptr[k+6] * v[k+6]; + case 6: s1 += mptr[k+5] * v[k+5]; + case 5: s2 += mptr[k+4] * v[k+4]; + case 4: s3 += mptr[k+3] * v[k+3]; + case 3: s0 += mptr[k+2] * v[k+2]; + case 2: s1 += mptr[k+1] * v[k+1]; + case 1: s2 += mptr[k+0] * v[k+0]; + } + sum = s3; + sum += s2; + sum += s1; + sum += s0; + mptr[i] = ( mptr[i] - sum ) * d; + mptr += nc; + } + } + + return true; + +#else + + int i, j, k, nc; + float * RESTRICT v, * RESTRICT ptr, * RESTRICT diagPtr; + double d, sum; + + v = (float * RESTRICT ) _alloca16( n * sizeof( float ) ); + nc = mat.GetNumColumns(); + + for ( i = 0; i < n; i++ ) { + + ptr = mat[i]; + diagPtr = mat[0]; + sum = ptr[i]; + for ( j = 0; j < i; j++ ) { + d = ptr[j]; + v[j] = diagPtr[0] * d; + sum -= v[j] * d; + diagPtr += nc + 1; + } + + if ( sum == 0.0f ) { + return false; + } + + diagPtr[0] = sum; + invDiag[i] = d = 1.0f / sum; + + if ( i + 1 >= n ) { + continue; + } + + ptr = mat[i+1]; + for ( j = i + 1; j < n; j++ ) { + sum = ptr[i]; + for ( k = 0; k < i; k++ ) { + sum -= ptr[k] * v[k]; + } + ptr[i] = sum * d; + ptr += nc; + } + } + + return true; + +#endif +} + +/* +============ +idSIMD_Generic::BlendJoints +============ +*/ +// RAVEN BEGIN +// jsinger: BlendJoints() moved to be inline for xenon +// RAVEN END + +/* +============ +idSIMD_Generic::ConvertJointQuatsToJointMats +============ +*/ +void VPCALL idSIMD_Generic::ConvertJointQuatsToJointMats( idJointMat * RESTRICT jointMats, const idJointQuat * RESTRICT jointQuats, const int numJoints ) { + TIME_THIS_SCOPE("SIMD ConvertJointQuatsToJointMats"); + int i; + + for ( i = 0; i < numJoints; i++ ) { + jointMats[i].SetRotation( jointQuats[i].q.ToMat3() ); + jointMats[i].SetTranslation( jointQuats[i].t ); + } +} + +/* +============ +idSIMD_Generic::ConvertJointMatsToJointQuats +============ +*/ +void VPCALL idSIMD_Generic::ConvertJointMatsToJointQuats( idJointQuat * RESTRICT jointQuats, const idJointMat * RESTRICT jointMats, const int numJoints ) { + TIME_THIS_SCOPE("SIMD ConvertJointMatsToJointQuats"); + int i; + + for ( i = 0; i < numJoints; i++ ) { + jointQuats[i] = jointMats[i].ToJointQuat(); + } +} + +/* +============ +idSIMD_Generic::TransformJoints +============ +*/ +void VPCALL idSIMD_Generic::TransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ) { + TIME_THIS_SCOPE("SIMD TransformJoints"); + int i; + + for( i = firstJoint; i <= lastJoint; i++ ) { + assert( parents[i] < i ); + jointMats[i] *= jointMats[parents[i]]; + } +} + +/* +============ +idSIMD_Generic::UntransformJoints +============ +*/ +void VPCALL idSIMD_Generic::UntransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ) { + TIME_THIS_SCOPE("SIMD UntransformJoints"); + int i; + + for( i = lastJoint; i >= firstJoint; i-- ) { + assert( parents[i] < i ); + jointMats[i] /= jointMats[parents[i]]; + } +} + +/* +============ +idSIMD_Generic::MultiplyJoints +============ +*/ +void VPCALL idSIMD_Generic::MultiplyJoints( idJointMat * RESTRICT result, const idJointMat * RESTRICT joints1, const idJointMat * RESTRICT joints2, const int numJoints ) { + TIME_THIS_SCOPE("SIMD MultiplyJoints"); + int i; + + for ( i = 0; i < numJoints; i++ ) { + idJointMat::Multiply( result[i], joints1[i], joints2[i] ); + } +} + + +/* +============ +idBounds_AddPoint +============ +*/ +ID_INLINE void idBounds_AddPoint( idBounds &b, const idVec3 &v ) { + float p = ( b[0].x + v.x ) * 0.5f; + b[0].x = p - fabs( b[0].x - p ); + float q = ( b[0].y + v.y ) * 0.5f; + b[0].y = q - fabs( b[0].y - q ); + float r = ( b[0].z + v.z ) * 0.5f; + b[0].z = r - fabs( b[0].z - r ); + float s = ( b[1].x + v.x ) * 0.5f; + b[1].x = s + fabs( b[1].x - s ); + float t = ( b[1].y + v.y ) * 0.5f; + b[1].y = t + fabs( b[1].y - t ); + float u = ( b[1].z + v.z ) * 0.5f; + b[1].z = u + fabs( b[1].z - u ); +} + +/* +============ +idSIMD_Generic::TransformVertsNew +============ +*/ +void VPCALL idSIMD_Generic::TransformVertsNew( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, int numWeights ) { + TIME_THIS_SCOPE("SIMD TransformVertsNew"); + int i, j; + const byte * RESTRICT jointsPtr = (byte * RESTRICT )joints; + + bounds.Zero(); + for( j = 0, i = 0; i < numVerts; i++, j++ ) { + idVec3 v; + + v = ( *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ) ) * base[j]; + while( weights[j].nextVertexOffset != JOINTWEIGHT_SIZE ) { + j++; + v += ( *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ) ) * base[j]; + } + + verts[i].xyz = v; + + idBounds_AddPoint( bounds, v ); + } +} + +/* +============ +idSIMD_Generic::TransformVertsAndTangents +============ +*/ +void VPCALL idSIMD_Generic::TransformVertsAndTangents( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ) { + TIME_THIS_SCOPE("SIMD TransformVertsAndTangents"); + int i, j; + const byte * RESTRICT jointsPtr = (byte * RESTRICT )joints; + + bounds.Zero(); + for( j = i = 0; i < numVerts; i++, j++ ) { + idJointMat mat; + + idJointMat::Mul( mat, *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ), weights[j].weight ); + while( weights[j].nextVertexOffset != JOINTWEIGHT_SIZE ) { + j++; + idJointMat::Mad( mat, *(idJointMat *) ( jointsPtr + weights[j].jointMatOffset ), weights[j].weight ); + } + + verts[i].xyz = mat * base[i*4+0]; + verts[i].normal = mat * base[i*4+1]; + verts[i].tangents[0] = mat * base[i*4+2]; + verts[i].tangents[1] = mat * base[i*4+3]; + + idBounds_AddPoint( bounds, verts[i].xyz ); + } +} + +/* +============ +idSIMD_Generic::TransformVertsAndTangentsFast +============ +*/ +void VPCALL idSIMD_Generic::TransformVertsAndTangentsFast( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ) { + TIME_THIS_SCOPE("SIMD TransformVertsAndTangentsFast"); + int i; + const byte * RESTRICT jointsPtr = (byte * RESTRICT )joints; + const byte * RESTRICT weightsPtr = (byte * RESTRICT )weights; + + bounds.Zero(); + for( i = 0; i < numVerts; i++ ) { + const idJointMat &mat = *(idJointMat *) ( jointsPtr + ((jointWeight_t *)weightsPtr)->jointMatOffset ); + + weightsPtr += ((jointWeight_t *)weightsPtr)->nextVertexOffset; + + verts[i].xyz = mat * base[i*4+0]; + verts[i].normal = mat * base[i*4+1]; + verts[i].tangents[0] = mat * base[i*4+2]; + verts[i].tangents[1] = mat * base[i*4+3]; + + idBounds_AddPoint( bounds, verts[i].xyz ); + } +} + +/* +============ +idSIMD_Generic::TracePointCull +============ +*/ +void VPCALL idSIMD_Generic::TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("idSIMD_Generic::TracePointCull idDrawVert"); + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} + +/* +============ +idSIMD_Generic::DecalPointCull +============ +*/ +void VPCALL idSIMD_Generic::DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD DecalPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyz; + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + +/* +============ +idSIMD_Generic::OverlayPointCull +============ +*/ +void VPCALL idSIMD_Generic::OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD OverlayPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1; + const idVec3 &v = verts[i].xyz; + + texCoords[i][0] = d0 = planes[0].Distance( v ); + texCoords[i][1] = d1 = planes[1].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[i] = bits; + } +} + +/* +============ +idSIMD_Generic::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTriPlanes( idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTriPlanes idDrawVert"); + int i; + + for ( i = 0; i < numIndexes; i += 3 ) { + const idDrawVert * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes->SetNormal( n ); + planes->FitThroughPoint( a->xyz ); + planes++; + } +} + +/* +============ +idSIMD_Generic::DeriveTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from all triangles + using the vertex which results in smooth tangents across the mesh. + In the process the triangle planes are calculated as well. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTangents( idPlane * RESTRICT planes, idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTangents"); + int i; + + bool * RESTRICT used = (bool * RESTRICT )_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + idPlane * RESTRICT planesPtr = planes; + for ( i = 0; i < numIndexes; i += 3 ) { + idDrawVert * RESTRICT a, * RESTRICT b, * RESTRICT c; + unsigned long signBit; + float d0[5], d1[5], f, area; + idVec3 n, t0, t1; + + int v0 = indexes[i + 0]; + int v1 = indexes[i + 1]; + int v2 = indexes[i + 2]; + + a = verts + v0; + b = verts + v1; + c = verts + v2; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + // normal + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planesPtr->SetNormal( n ); + planesPtr->FitThroughPoint( a->xyz ); + planesPtr++; + + // area sign bit + area = d0[3] * d1[4] - d0[4] * d1[3]; + signBit = ( *(unsigned long *)&area ) & ( 1 << 31 ); + + // first tangent + t0[0] = d0[0] * d1[4] - d0[4] * d1[0]; + t0[1] = d0[1] * d1[4] - d0[4] * d1[1]; + t0[2] = d0[2] * d1[4] - d0[4] * d1[2]; + + f = idMath::RSqrt( t0.x * t0.x + t0.y * t0.y + t0.z * t0.z ); + *(unsigned long *)&f ^= signBit; + + t0.x *= f; + t0.y *= f; + t0.z *= f; + + // second tangent + t1[0] = d0[3] * d1[0] - d0[0] * d1[3]; + t1[1] = d0[3] * d1[1] - d0[1] * d1[3]; + t1[2] = d0[3] * d1[2] - d0[2] * d1[3]; + + f = idMath::RSqrt( t1.x * t1.x + t1.y * t1.y + t1.z * t1.z ); + *(unsigned long *)&f ^= signBit; + + t1.x *= f; + t1.y *= f; + t1.z *= f; + + if ( used[v0] ) { + a->normal += n; + a->tangents[0] += t0; + a->tangents[1] += t1; + } else { + a->normal = n; + a->tangents[0] = t0; + a->tangents[1] = t1; + used[v0] = true; + } + + if ( used[v1] ) { + b->normal += n; + b->tangents[0] += t0; + b->tangents[1] += t1; + } else { + b->normal = n; + b->tangents[0] = t0; + b->tangents[1] = t1; + used[v1] = true; + } + + if ( used[v2] ) { + c->normal += n; + c->tangents[0] += t0; + c->tangents[1] += t1; + } else { + c->normal = n; + c->tangents[0] = t0; + c->tangents[1] = t1; + used[v2] = true; + } + } +} + +/* +============ +idSIMD_Generic::DeriveUnsmoothedTangents + + Derives the normal and orthogonal tangent vectors for the triangle vertices. + For each vertex the normal and tangent vectors are derived from a single dominant triangle. +============ +*/ +#define DERIVE_UNSMOOTHED_BITANGENT + +void VPCALL idSIMD_Generic::DeriveUnsmoothedTangents( idDrawVert * RESTRICT verts, const dominantTri_s * RESTRICT dominantTris, const int numVerts ) { + TIME_THIS_SCOPE("SIMD DeriveUnsmoothedTangents"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + idDrawVert * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0, d1, d2, d3, d4; + float d5, d6, d7, d8, d9; + float s0, s1, s2; + float n0, n1, n2; + float t0, t1, t2; + float t3, t4, t5; + + const dominantTri_s &dt = dominantTris[i]; + + a = verts + i; + b = verts + dt.v2; + c = verts + dt.v3; + + d0 = b->xyz[0] - a->xyz[0]; + d1 = b->xyz[1] - a->xyz[1]; + d2 = b->xyz[2] - a->xyz[2]; + d3 = b->st[0] - a->st[0]; + d4 = b->st[1] - a->st[1]; + + d5 = c->xyz[0] - a->xyz[0]; + d6 = c->xyz[1] - a->xyz[1]; + d7 = c->xyz[2] - a->xyz[2]; + d8 = c->st[0] - a->st[0]; + d9 = c->st[1] - a->st[1]; + + s0 = dt.normalizationScale[0]; + s1 = dt.normalizationScale[1]; + s2 = dt.normalizationScale[2]; + + n0 = s2 * ( d6 * d2 - d7 * d1 ); + n1 = s2 * ( d7 * d0 - d5 * d2 ); + n2 = s2 * ( d5 * d1 - d6 * d0 ); + + t0 = s0 * ( d0 * d9 - d4 * d5 ); + t1 = s0 * ( d1 * d9 - d4 * d6 ); + t2 = s0 * ( d2 * d9 - d4 * d7 ); + +#ifndef DERIVE_UNSMOOTHED_BITANGENT + t3 = s1 * ( d3 * d5 - d0 * d8 ); + t4 = s1 * ( d3 * d6 - d1 * d8 ); + t5 = s1 * ( d3 * d7 - d2 * d8 ); +#else + t3 = s1 * ( n2 * t1 - n1 * t2 ); + t4 = s1 * ( n0 * t2 - n2 * t0 ); + t5 = s1 * ( n1 * t0 - n0 * t1 ); +#endif + + a->normal[0] = n0; + a->normal[1] = n1; + a->normal[2] = n2; + + a->tangents[0][0] = t0; + a->tangents[0][1] = t1; + a->tangents[0][2] = t2; + + a->tangents[1][0] = t3; + a->tangents[1][1] = t4; + a->tangents[1][2] = t5; + } +} + +/* +============ +idSIMD_Generic::NormalizeTangents + + Normalizes each vertex normal and projects and normalizes the + tangent vectors onto the plane orthogonal to the vertex normal. +============ +*/ +void VPCALL idSIMD_Generic::NormalizeTangents( idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD NormalizeTangents"); + + for ( int i = 0; i < numVerts; i++ ) { + idVec3 &v = verts[i].normal; + float f; + + f = idMath::RSqrt( v.x * v.x + v.y * v.y + v.z * v.z ); + v.x *= f; v.y *= f; v.z *= f; + + for ( int j = 0; j < 2; j++ ) { + idVec3 &t = verts[i].tangents[j]; + + t -= ( t * v ) * v; + f = idMath::RSqrt( t.x * t.x + t.y * t.y + t.z * t.z ); + t.x *= f; t.y *= f; t.z *= f; + } + } +} + +/* +============ +idSIMD_Generic::CreateTextureSpaceLightVectors + + Calculates light vectors in texture space for the given triangle vertices. + For each vertex the direction towards the light origin is projected onto texture space. + The light vectors are only calculated for the vertices referenced by the indexes. +============ +*/ +void VPCALL idSIMD_Generic::CreateTextureSpaceLightVectors( idVec3 * RESTRICT lightVectors, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD CreateTextureSpaceLightVectors"); + + bool * RESTRICT used = (bool * RESTRICT )_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert * RESTRICT v = &verts[i]; + + idVec3 lightDir = lightOrigin - v->xyz; + + lightVectors[i][0] = lightDir * v->tangents[0]; + lightVectors[i][1] = lightDir * v->tangents[1]; + lightVectors[i][2] = lightDir * v->normal; + } +} + +/* +============ +idSIMD_Generic::CreateSpecularTextureCoords + + Calculates specular texture coordinates for the given triangle vertices. + For each vertex the normalized direction towards the light origin is added to the + normalized direction towards the view origin and the result is projected onto texture space. + The texture coordinates are only calculated for the vertices referenced by the indexes. +============ +*/ +void VPCALL idSIMD_Generic::CreateSpecularTextureCoords( idVec4 * RESTRICT texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD CreateSpecularTextureCoords"); + + bool * RESTRICT used = (bool * RESTRICT )_alloca16( numVerts * sizeof( used[0] ) ); + memset( used, 0, numVerts * sizeof( used[0] ) ); + + for ( int i = numIndexes - 1; i >= 0; i-- ) { + used[indexes[i]] = true; + } + + for ( int i = 0; i < numVerts; i++ ) { + if ( !used[i] ) { + continue; + } + + const idDrawVert * RESTRICT v = &verts[i]; + + idVec3 lightDir = lightOrigin - v->xyz; + idVec3 viewDir = viewOrigin - v->xyz; + + float ilength; + + ilength = idMath::RSqrt( lightDir * lightDir ); + lightDir[0] *= ilength; + lightDir[1] *= ilength; + lightDir[2] *= ilength; + + ilength = idMath::RSqrt( viewDir * viewDir ); + viewDir[0] *= ilength; + viewDir[1] *= ilength; + viewDir[2] *= ilength; + + lightDir += viewDir; + + texCoords[i][0] = lightDir * v->tangents[0]; + texCoords[i][1] = lightDir * v->tangents[1]; + texCoords[i][2] = lightDir * v->normal; + texCoords[i][3] = 1.0f; + } +} + +/* +============ +idSIMD_Generic::CreateShadowCache +============ +*/ +int VPCALL idSIMD_Generic::CreateShadowCache( idVec4 * RESTRICT vertexCache, int * RESTRICT vertRemap, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD CreateShadowCache"); + int outVerts = 0; + + for ( int i = 0; i < numVerts; i++ ) { + if ( vertRemap[i] ) { + continue; + } + const float * RESTRICT v = verts[i].xyz.ToFloatPtr(); + vertexCache[outVerts+0][0] = v[0]; + vertexCache[outVerts+0][1] = v[1]; + vertexCache[outVerts+0][2] = v[2]; + vertexCache[outVerts+0][3] = 1.0f; + + // R_SetupProjection() builds the projection matrix with a slight crunch + // for depth, which keeps this w=0 division from rasterizing right at the + // wrap around point and causing depth fighting with the rear caps + vertexCache[outVerts+1][0] = v[0] - lightOrigin[0]; + vertexCache[outVerts+1][1] = v[1] - lightOrigin[1]; + vertexCache[outVerts+1][2] = v[2] - lightOrigin[2]; + vertexCache[outVerts+1][3] = 0.0f; + vertRemap[i] = outVerts; + outVerts += 2; + } + return outVerts; +} + +/* +============ +idSIMD_Generic::CreateVertexProgramShadowCache +============ +*/ +int VPCALL idSIMD_Generic::CreateVertexProgramShadowCache( idVec4 * RESTRICT vertexCache, const idDrawVert * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD CreateVertexProgramShadowCache"); + for ( int i = 0; i < numVerts; i++ ) { + const float * RESTRICT v = verts[i].xyz.ToFloatPtr(); + vertexCache[i*2+0][0] = v[0]; + vertexCache[i*2+1][0] = v[0]; + vertexCache[i*2+0][1] = v[1]; + vertexCache[i*2+1][1] = v[1]; + vertexCache[i*2+0][2] = v[2]; + vertexCache[i*2+1][2] = v[2]; + vertexCache[i*2+0][3] = 1.0f; + vertexCache[i*2+1][3] = 0.0f; + } + return numVerts * 2; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CountFacing +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CountFacing( const byte * RESTRICT facing, const int numFaces ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CountFacing"); + int i, n; + + n = 0; + for ( i = 0; i < numFaces; i++ ) { + n += facing[i]; + } + return n; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CountFacingCull +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CountFacingCull( byte * RESTRICT facing, const int numFaces, const int * RESTRICT indexes, const byte * RESTRICT cull ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CountFacingCull"); + int i, n; + + n = 0; + for ( i = 0; i < numFaces; i++ ) { + if ( !facing[i] ) { + int i1 = indexes[0]; + int i2 = indexes[1]; + int i3 = indexes[2]; + if ( cull[i1] & cull[i2] & cull[i3] ) { + facing[i] = 1; + n++; + } + } else { + n++; + } + indexes += 3; + } + return n; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CreateSilTriangles +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CreateSilTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const silEdge_s * RESTRICT silEdges, const int numSilEdges ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CreateSilTriangles"); + int i; + const silEdge_t * RESTRICT sil; + int * RESTRICT si; + + si = shadowIndexes; + for ( sil = ( silEdge_t * RESTRICT )silEdges, i = numSilEdges; i > 0; i--, sil++ ) { + + int f1 = facing[sil->p1]; + int f2 = facing[sil->p2]; + + if ( !( f1 ^ f2 ) ) { + continue; + } + + int v1 = sil->v1 << 1; + int v2 = sil->v2 << 1; + + // set the two triangle winding orders based on facing + // without using a poorly-predictable branch + + si[0] = v1; + si[1] = v2 ^ f1; + si[2] = v2 ^ f2; + si[3] = v1 ^ f2; + si[4] = v1 ^ f1; + si[5] = v2 ^ 1; + + si += 6; + } + return si - shadowIndexes; +} + +/* +============ +idSIMD_Generic::ShadowVolume_CreateCapTriangles +============ +*/ +int VPCALL idSIMD_Generic::ShadowVolume_CreateCapTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD ShadowVolume_CreateCapTriangles"); + int i, j; + int * RESTRICT si; + + si = shadowIndexes; + for ( i = 0, j = 0; i < numIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + + int i0 = indexes[i+0] << 1; + si[2] = i0; + si[3] = i0 ^ 1; + int i1 = indexes[i+1] << 1; + si[1] = i1; + si[4] = i1 ^ 1; + int i2 = indexes[i+2] << 1; + si[0] = i2; + si[5] = i2 ^ 1; + + si += 6; + } + return si - shadowIndexes; +} + +/* +============ +idSIMD_Generic::UpSamplePCMTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_Generic::UpSamplePCMTo44kHz( float * RESTRICT dest, const short * RESTRICT src, const int numSamples, const int kHz, const int numChannels ) { + TIME_THIS_SCOPE("SIMD UpSamplePCMTo44kHz"); + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = (float) src[i+0]; + } + } else { + for ( int i = 0; i < numSamples; i += 2 ) { + dest[i*4+0] = dest[i*4+2] = dest[i*4+4] = dest[i*4+6] = (float) src[i+0]; + dest[i*4+1] = dest[i*4+3] = dest[i*4+5] = dest[i*4+7] = (float) src[i+1]; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = (float) src[i+0]; + } + } else { + for ( int i = 0; i < numSamples; i += 2 ) { + dest[i*2+0] = dest[i*2+2] = (float) src[i+0]; + dest[i*2+1] = dest[i*2+3] = (float) src[i+1]; + } + } + } else if ( kHz == 44100 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i] = (float) src[i]; + } + } else { + assert( 0 ); + } +} + +/* +============ +idSIMD_Generic::UpSampleOGGTo44kHz + + Duplicate samples for 44kHz output. +============ +*/ +void idSIMD_Generic::UpSampleOGGTo44kHz( float * RESTRICT dest, const float * const * RESTRICT ogg, const int numSamples, const int kHz, const int numChannels ) { + TIME_THIS_SCOPE("SIMD UpSampleOGGTo44kHz"); + if ( kHz == 11025 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*4+0] = dest[i*4+1] = dest[i*4+2] = dest[i*4+3] = ogg[0][i] * 32768.0f; + } + } else { + for ( int i = 0; i < numSamples >> 1; i++ ) { + dest[i*8+0] = dest[i*8+2] = dest[i*8+4] = dest[i*8+6] = ogg[0][i] * 32768.0f; + dest[i*8+1] = dest[i*8+3] = dest[i*8+5] = dest[i*8+7] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 22050 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*2+0] = dest[i*2+1] = ogg[0][i] * 32768.0f; + } + } else { + for ( int i = 0; i < numSamples >> 1; i++ ) { + dest[i*4+0] = dest[i*4+2] = ogg[0][i] * 32768.0f; + dest[i*4+1] = dest[i*4+3] = ogg[1][i] * 32768.0f; + } + } + } else if ( kHz == 44100 ) { + if ( numChannels == 1 ) { + for ( int i = 0; i < numSamples; i++ ) { + dest[i*1+0] = ogg[0][i] * 32768.0f; + } + } else { + for ( int i = 0; i < numSamples >> 1; i++ ) { + dest[i*2+0] = ogg[0][i] * 32768.0f; + dest[i*2+1] = ogg[1][i] * 32768.0f; + } + } + } else { + assert( 0 ); + } +} + +/* +============ +idSIMD_Generic::MixSoundTwoSpeakerMono +============ +*/ +void VPCALL idSIMD_Generic::MixSoundTwoSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerMono"); + float sL = lastV[0]; + float sR = lastV[1]; + float incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + mixBuffer[j*2+0] += samples[j] * sL; + mixBuffer[j*2+1] += samples[j] * sR; + sL += incL; + sR += incR; + } +} + +/* +============ +idSIMD_Generic::MixSoundTwoSpeakerMonoSimple +============ +*/ +void VPCALL idSIMD_Generic::MixSoundTwoSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) { + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerMonoSimple"); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + mixBuffer[j*2+0] += samples[j]; + mixBuffer[j*2+1] += samples[j]; + } +} + +/* +============ +idSIMD_Generic::MixSoundTwoSpeakerStereo +============ +*/ +void VPCALL idSIMD_Generic::MixSoundTwoSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ) { + TIME_THIS_SCOPE("SIMD MixSoundTwoSpeakerStereo"); + float sL = lastV[0]; + float sR = lastV[1]; + float incL = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incR = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + mixBuffer[j*2+0] += samples[j*2+0] * sL; + mixBuffer[j*2+1] += samples[j*2+1] * sR; + sL += incL; + sR += incR; + } +} + +/* +============ +idSIMD_Generic::MixSoundSixSpeakerMono +============ +*/ +void VPCALL idSIMD_Generic::MixSoundSixSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + TIME_THIS_SCOPE("SIMD MixSoundSixSpeakerMono"); + float sL0 = lastV[0]; + float sL1 = lastV[1]; + float sL2 = lastV[2]; + float sL3 = lastV[3]; + float sL4 = lastV[4]; + float sL5 = lastV[5]; + + float incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + float incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + float incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + float incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + float incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int i = 0; i < MIXBUFFER_SAMPLES; i++ ) { + mixBuffer[i*6+0] += samples[i] * sL0; + mixBuffer[i*6+1] += samples[i] * sL1; + mixBuffer[i*6+2] += samples[i] * sL2; + mixBuffer[i*6+3] += samples[i] * sL3; + mixBuffer[i*6+4] += samples[i] * sL4; + mixBuffer[i*6+5] += samples[i] * sL5; + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + sL4 += incL4; + sL5 += incL5; + } +} + +/* +============ +idSIMD_Generic::MixSoundSixSpeakerMonoSimple +============ +*/ +void VPCALL idSIMD_Generic::MixSoundSixSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ) { + TIME_THIS_SCOPE("SIMD MixSoundSixSpeakerMono"); + + assert( numSamples == MIXBUFFER_SAMPLES ); + + // Just mix the front 2 speakers - the others are unchanged + for( int i = 0; i < MIXBUFFER_SAMPLES; i++ ) { + mixBuffer[i*6+0] += samples[i]; + mixBuffer[i*6+1] += samples[i]; + } +} + +/* +============ +idSIMD_Generic::MixSoundSixSpeakerStereo +============ +*/ +void VPCALL idSIMD_Generic::MixSoundSixSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ) { + TIME_THIS_SCOPE("SIMD MixSoundSixSpeakerStereo"); + float sL0 = lastV[0]; + float sL1 = lastV[1]; + float sL2 = lastV[2]; + float sL3 = lastV[3]; + float sL4 = lastV[4]; + float sL5 = lastV[5]; + + float incL0 = ( currentV[0] - lastV[0] ) / MIXBUFFER_SAMPLES; + float incL1 = ( currentV[1] - lastV[1] ) / MIXBUFFER_SAMPLES; + float incL2 = ( currentV[2] - lastV[2] ) / MIXBUFFER_SAMPLES; + float incL3 = ( currentV[3] - lastV[3] ) / MIXBUFFER_SAMPLES; + float incL4 = ( currentV[4] - lastV[4] ) / MIXBUFFER_SAMPLES; + float incL5 = ( currentV[5] - lastV[5] ) / MIXBUFFER_SAMPLES; + + assert( numSamples == MIXBUFFER_SAMPLES ); + + for( int i = 0; i < MIXBUFFER_SAMPLES; i++ ) { + mixBuffer[i*6+0] += samples[i*2+0] * sL0; + mixBuffer[i*6+1] += samples[i*2+1] * sL1; + mixBuffer[i*6+2] += samples[i*2+0] * sL2; + mixBuffer[i*6+3] += samples[i*2+0] * sL3; + mixBuffer[i*6+4] += samples[i*2+0] * sL4; + mixBuffer[i*6+5] += samples[i*2+1] * sL5; + sL0 += incL0; + sL1 += incL1; + sL2 += incL2; + sL3 += incL3; + sL4 += incL4; + sL5 += incL5; + } +} + +/* +============ +idSIMD_Generic::MixedSoundToSamples +============ +*/ +void VPCALL idSIMD_Generic::MixedSoundToSamples( short * RESTRICT samples, const float * RESTRICT mixBuffer, const int numSamples ) { + TIME_THIS_SCOPE("SIMD MixedSoundToSamples"); + + for ( int i = 0; i < numSamples; i++ ) { + if ( mixBuffer[i] <= -32768.0f ) { + samples[i] = -32768; + } else if ( mixBuffer[i] >= 32767.0f ) { + samples[i] = 32767; + } else { + samples[i] = (short) mixBuffer[i]; + } + } +} + +// RAVEN BEGIN +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT +/* +============ +idSIMD_Generic::JointMat_MultiplyMats +// dluetscher: added support for concatenating matrices from two idJointMat arrays, based on the given palette mapping, +// storing the resulting transform palette in an array of 4x4 matrices, +// stored in row-major array ordering, with translation in last column (column-major matrix) +// +// For example the following matrix: Xx Yx Zx Tx +// Xy Yy Zx Ty +// Xz Yz Zz Tz +// 0 0 0 1 +// +// is stored in the resulting order: Xx Yx Zx Tx Xy Yy Zx Ty Xz Yz Zz Tz 0 0 0 1 +============ +*/ +void VPCALL idSIMD_Generic::JointMat_MultiplyMats( float * RESTRICT destMats, + const idJointMat * RESTRICT src1Mats, + const idJointMat * RESTRICT src2Mats, + int * RESTRICT transformPalette, + int transformCount ) { + TIME_THIS_SCOPE("SIMD JointMat_MultiplyMats"); + float * RESTRICT destPtr; + const float * RESTRICT src1Ptr, * RESTRICT src2Ptr; + int curTransform, matOffset; + + for ( curTransform = 0; curTransform < transformCount; curTransform++ ) { + + matOffset = transformPalette[ curTransform ]; + + src1Ptr = src1Mats[matOffset].ToFloatPtr(); + src2Ptr = src2Mats[matOffset].ToFloatPtr(); + destPtr = destMats + (curTransform << 4); + + destPtr[0 * 4 + 0] = src1Ptr[0 * 4 + 0] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 0] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 0] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 0] = src1Ptr[0 * 4 + 0] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 0] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 0] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 0] = src1Ptr[0 * 4 + 0] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 0] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 0] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 0] = 0.f; + + destPtr[0 * 4 + 1] = src1Ptr[0 * 4 + 1] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 1] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 1] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 1] = src1Ptr[0 * 4 + 1] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 1] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 1] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 1] = src1Ptr[0 * 4 + 1] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 1] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 1] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 1] = 0.f; + + destPtr[0 * 4 + 2] = src1Ptr[0 * 4 + 2] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 2] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 2] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 2] = src1Ptr[0 * 4 + 2] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 2] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 2] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 2] = src1Ptr[0 * 4 + 2] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 2] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 2] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 2] = 0.f; + + destPtr[0 * 4 + 3] = src1Ptr[0 * 4 + 3] * src2Ptr[0 * 4 + 0] + src1Ptr[1 * 4 + 3] * src2Ptr[0 * 4 + 1] + src1Ptr[2 * 4 + 3] * src2Ptr[0 * 4 + 2]; + destPtr[1 * 4 + 3] = src1Ptr[0 * 4 + 3] * src2Ptr[1 * 4 + 0] + src1Ptr[1 * 4 + 3] * src2Ptr[1 * 4 + 1] + src1Ptr[2 * 4 + 3] * src2Ptr[1 * 4 + 2]; + destPtr[2 * 4 + 3] = src1Ptr[0 * 4 + 3] * src2Ptr[2 * 4 + 0] + src1Ptr[1 * 4 + 3] * src2Ptr[2 * 4 + 1] + src1Ptr[2 * 4 + 3] * src2Ptr[2 * 4 + 2]; + destPtr[3 * 4 + 3] = 1.f; + + destPtr[0 * 4 + 3] += src2Ptr[0 * 4 + 3]; + destPtr[1 * 4 + 3] += src2Ptr[1 * 4 + 3]; + destPtr[2 * 4 + 3] += src2Ptr[2 * 4 + 3]; + } +} +#endif +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added TransformVertsMinMax to transform an array of index-weighted vertices into +// an array of idSilTraceVerts, while simulatenously calculating the bounds +#ifdef _MD5R_SUPPORT +void VPCALL idSIMD_Generic::TransformVertsMinMax4Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte * RESTRICT vertexInputData, + int vertStride, int numVerts, + float * RESTRICT skinToModelTransforms ) { + TIME_THIS_SCOPE("SIMD TransformVertsMinMax4Bone"); + float curMin[3], curMax[3]; + float * RESTRICT curTransform, * RESTRICT vertexPos, * RESTRICT blendWeights, * RESTRICT transformedPos; + byte * RESTRICT vertexOutputData, * RESTRICT blendIndices, * RESTRICT endVertexInputData; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + + vertexOutputData = (byte* RESTRICT ) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + vertexPos = (float * RESTRICT ) vertexInputData; + blendIndices = vertexInputData + sizeof(float)*3; + blendWeights = (float * RESTRICT ) (vertexInputData + sizeof(float)*3 + sizeof(byte)*4); + transformedPos = (float * RESTRICT ) vertexOutputData; + + curTransform = skinToModelTransforms + ((dword) blendIndices[0] << 4); + transformedPos[0] = blendWeights[0]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] = blendWeights[0]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] = blendWeights[0]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curTransform = skinToModelTransforms + ((dword) blendIndices[1] << 4); + transformedPos[0] += blendWeights[1]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] += blendWeights[1]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] += blendWeights[1]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curTransform = skinToModelTransforms + ((dword) blendIndices[2] << 4); + transformedPos[0] += blendWeights[2]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] += blendWeights[2]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] += blendWeights[2]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curTransform = skinToModelTransforms + ((dword) blendIndices[3] << 4); + transformedPos[0] += blendWeights[3]*(vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] += blendWeights[3]*(vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] += blendWeights[3]*(vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curMin[0] = transformedPos[0] < curMin[0] ? transformedPos[0] : curMin[0]; + curMin[1] = transformedPos[1] < curMin[1] ? transformedPos[1] : curMin[1]; + curMin[2] = transformedPos[2] < curMin[2] ? transformedPos[2] : curMin[2]; + + curMax[0] = transformedPos[0] > curMax[0] ? transformedPos[0] : curMax[0]; + curMax[1] = transformedPos[1] > curMax[1] ? transformedPos[1] : curMax[1]; + curMax[2] = transformedPos[2] > curMax[2] ? transformedPos[2] : curMax[2]; + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +void VPCALL idSIMD_Generic::TransformVertsMinMax1Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, + idVec3 &min, idVec3 &max, + byte * RESTRICT vertexInputData, + int vertStride, int numVerts, + float * RESTRICT skinToModelTransforms ) { + TIME_THIS_SCOPE("SIMD TransformVertsMinMax1Bone"); + float curMin[3], curMax[3]; + float * RESTRICT curTransform, * RESTRICT vertexPos, * RESTRICT transformedPos; + byte * RESTRICT vertexOutputData, * RESTRICT blendIndices, * RESTRICT endVertexInputData; + + curMin[0] = FLT_MAX; + curMin[1] = FLT_MAX; + curMin[2] = FLT_MAX; + + curMax[0] = -FLT_MAX; + curMax[1] = -FLT_MAX; + curMax[2] = -FLT_MAX; + + vertexOutputData = (byte* RESTRICT ) silTraceVertOutputData; + endVertexInputData = vertexInputData + vertStride*numVerts; + do + { + vertexPos = (float * RESTRICT ) vertexInputData; + blendIndices = vertexInputData + sizeof(float)*3; + transformedPos = (float * RESTRICT ) vertexOutputData; + + curTransform = skinToModelTransforms + ((dword) blendIndices[0] << 4); + transformedPos[0] = (vertexPos[0]*curTransform[0] + vertexPos[1]*curTransform[1] + vertexPos[2]*curTransform[2] + curTransform[3]); + transformedPos[1] = (vertexPos[0]*curTransform[4] + vertexPos[1]*curTransform[5] + vertexPos[2]*curTransform[6] + curTransform[7]); + transformedPos[2] = (vertexPos[0]*curTransform[8] + vertexPos[1]*curTransform[9] + vertexPos[2]*curTransform[10] + curTransform[11]); + + curMin[0] = transformedPos[0] < curMin[0] ? transformedPos[0] : curMin[0]; + curMin[1] = transformedPos[1] < curMin[1] ? transformedPos[1] : curMin[1]; + curMin[2] = transformedPos[2] < curMin[2] ? transformedPos[2] : curMin[2]; + + curMax[0] = transformedPos[0] > curMax[0] ? transformedPos[0] : curMax[0]; + curMax[1] = transformedPos[1] > curMax[1] ? transformedPos[1] : curMax[1]; + curMax[2] = transformedPos[2] > curMax[2] ? transformedPos[2] : curMax[2]; + + vertexInputData += vertStride; + vertexOutputData += sizeof(rvSilTraceVertT); + + } + while ( vertexInputData < endVertexInputData ); + + min.x = curMin[0]; + min.y = curMin[1]; + min.z = curMin[2]; + + max.x = curMax[0]; + max.y = curMax[1]; + max.z = curMax[2]; +} + +/* +============ +idSIMD_Generic::Dot + + dst[i] = constant * src[i].xyz; +============ +*/ +void VPCALL idSIMD_Generic::Dot( float * RESTRICT dst, const idVec3 &constant, const rvSilTraceVertT * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Dot idVec3-rvSilTraceVertT"); +#define OPER(X) dst[(X)] = constant * src[(X)].xyzw.ToVec3(); + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::Dot + + dst[i] = constant.Normal() * src[i].xyz + constant[3]; +============ +*/ +void VPCALL idSIMD_Generic::Dot( float * RESTRICT dst, const idPlane &constant, const rvSilTraceVertT * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD Dot idPlane-rvSilTraceVertT"); +#define OPER(X) dst[(X)] = constant.Normal() * src[(X)].xyzw.ToVec3() + constant[3]; + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::TracePointCull +============ +*/ +void VPCALL idSIMD_Generic::TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD TracePointCull"); + + int i; + byte tOr; + + tOr = 0; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, t; + const idVec3 &v = verts[i].xyzw.ToVec3(); + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + + t = d0 + radius; + bits = FLOATSIGNBITSET( t ) << 0; + t = d1 + radius; + bits |= FLOATSIGNBITSET( t ) << 1; + t = d2 + radius; + bits |= FLOATSIGNBITSET( t ) << 2; + t = d3 + radius; + bits |= FLOATSIGNBITSET( t ) << 3; + + t = d0 - radius; + bits |= FLOATSIGNBITSET( t ) << 4; + t = d1 - radius; + bits |= FLOATSIGNBITSET( t ) << 5; + t = d2 - radius; + bits |= FLOATSIGNBITSET( t ) << 6; + t = d3 - radius; + bits |= FLOATSIGNBITSET( t ) << 7; + + bits ^= 0x0F; // flip lower four bits + + tOr |= bits; + cullBits[i] = bits; + } + + totalOr = tOr; +} + +/* +============ +idSIMD_Generic::DecalPointCull +============ +*/ +void VPCALL idSIMD_Generic::DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD DecalPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1, d2, d3, d4, d5; + const idVec3 &v = verts[i].xyzw.ToVec3(); + + d0 = planes[0].Distance( v ); + d1 = planes[1].Distance( v ); + d2 = planes[2].Distance( v ); + d3 = planes[3].Distance( v ); + d4 = planes[4].Distance( v ); + d5 = planes[5].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits ^ 0x3F; // flip lower 6 bits + } +} + +/* +============ +idSIMD_Generic::OverlayPointCull +============ +*/ +void VPCALL idSIMD_Generic::OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ) { + TIME_THIS_SCOPE("SIMD OverlayPointCull"); + int i; + + for ( i = 0; i < numVerts; i++ ) { + byte bits; + float d0, d1; + const idVec3 &v = verts[i].xyzw.ToVec3(); + + texCoords[i][0] = d0 = planes[0].Distance( v ); + texCoords[i][1] = d1 = planes[1].Distance( v ); + + bits = FLOATSIGNBITSET( d0 ) << 0; + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 2; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + cullBits[i] = bits; + } +} + +/* +============ +idSIMD_Generic::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTriPlanes rvSilTraceVertT-int"); + int i; + + for ( i = 0; i < numIndexes; i += 3 ) { + const rvSilTraceVertT * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyzw[0] - a->xyzw[0]; + d0[1] = b->xyzw[1] - a->xyzw[1]; + d0[2] = b->xyzw[2] - a->xyzw[2]; + + d1[0] = c->xyzw[0] - a->xyzw[0]; + d1[1] = c->xyzw[1] - a->xyzw[1]; + d1[2] = c->xyzw[2] - a->xyzw[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes->SetNormal( n ); + planes->FitThroughPoint( a->xyzw.ToVec3() ); + planes++; + } +} + +/* +============ +idSIMD_Generic::DeriveTriPlanes + + Derives a plane equation for each triangle. +============ +*/ +void VPCALL idSIMD_Generic::DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const unsigned short * RESTRICT indexes, const int numIndexes ) { + TIME_THIS_SCOPE("SIMD DeriveTriPlanes rvSilTraceVertT-ushort"); + int i; + + for ( i = 0; i < numIndexes; i += 3 ) { + const rvSilTraceVertT * RESTRICT a, * RESTRICT b, * RESTRICT c; + float d0[3], d1[3], f; + idVec3 n; + + a = verts + indexes[i + 0]; + b = verts + indexes[i + 1]; + c = verts + indexes[i + 2]; + + d0[0] = b->xyzw[0] - a->xyzw[0]; + d0[1] = b->xyzw[1] - a->xyzw[1]; + d0[2] = b->xyzw[2] - a->xyzw[2]; + + d1[0] = c->xyzw[0] - a->xyzw[0]; + d1[1] = c->xyzw[1] - a->xyzw[1]; + d1[2] = c->xyzw[2] - a->xyzw[2]; + + n[0] = d1[1] * d0[2] - d1[2] * d0[1]; + n[1] = d1[2] * d0[0] - d1[0] * d0[2]; + n[2] = d1[0] * d0[1] - d1[1] * d0[0]; + + f = idMath::RSqrt( n.x * n.x + n.y * n.y + n.z * n.z ); + + n.x *= f; + n.y *= f; + n.z *= f; + + planes->SetNormal( n ); + planes->FitThroughPoint( a->xyzw.ToVec3() ); + planes++; + } +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax rvSilTraceVertT"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[(X)].xyzw.ToVec3(); if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} + +/* +============ +idSIMD_Generic::MinMax +============ +*/ +void VPCALL idSIMD_Generic::MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int * RESTRICT indexes, const int count ) { + TIME_THIS_SCOPE("SIMD MinMax rvSilTraceVertT indexed"); + min[0] = min[1] = min[2] = idMath::INFINITY; max[0] = max[1] = max[2] = -idMath::INFINITY; +#define OPER(X) const idVec3 &v = src[indexes[(X)]].xyzw.ToVec3(); if ( v[0] < min[0] ) { min[0] = v[0]; } if ( v[0] > max[0] ) { max[0] = v[0]; } if ( v[1] < min[1] ) { min[1] = v[1]; } if ( v[1] > max[1] ) { max[1] = v[1]; } if ( v[2] < min[2] ) { min[2] = v[2]; } if ( v[2] > max[2] ) { max[2] = v[2]; } + UNROLL1(OPER) +#undef OPER +} +#endif // #ifdef _MD5R_SUPPORT diff --git a/src/idlib/math/Simd_generic.h b/src/idlib/math/Simd_generic.h new file mode 100644 index 0000000..3220e64 --- /dev/null +++ b/src/idlib/math/Simd_generic.h @@ -0,0 +1,181 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_SIMD_GENERIC_H__ +#define __MATH_SIMD_GENERIC_H__ + +/* +=============================================================================== + + Generic implementation of idSIMDProcessor + +=============================================================================== +*/ + +// RAVEN BEGIN +// jsinger: should not be virtual on xenon otherwise the mispredicted branch far outways the +// savings we get from doing SIMD in the first place. Also inheritence is unnecessary +#ifdef _XENON +#define VIRTUAL +class idSIMD_Generic { +#else +#define VIRTUAL virtual +class idSIMD_Generic : public idSIMDProcessor { +#endif +public: + VIRTUAL const char * VPCALL GetName( void ) const; + + VIRTUAL void VPCALL Add( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Add( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Sub( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Sub( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Mul( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Mul( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Div( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Div( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL MulAdd( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MulAdd( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + VIRTUAL void VPCALL MulSub( float * RESTRICT dst, const float constant, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MulSub( float * RESTRICT dst, const float * RESTRICT src0, const float * RESTRICT src1, const int count ); + + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const idVec3 * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const idPlane * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const idDrawVert * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant,const idVec3 * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant,const idPlane * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant,const idDrawVert * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 * RESTRICT src0, const idVec3 * RESTRICT src1, const int count ); + VIRTUAL void VPCALL Dot( float &dot, const float * RESTRICT src1, const float * RESTRICT src2, const int count ); + + VIRTUAL void VPCALL CmpGT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpGT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpGE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpGE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLT( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLT( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLE( byte * RESTRICT dst, const float * RESTRICT src0, const float constant, const int count ); + VIRTUAL void VPCALL CmpLE( byte * RESTRICT dst, const byte bitNum, const float * RESTRICT src0, const float constant, const int count ); + + VIRTUAL void VPCALL MinMax( float &min, float &max, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec2 &min, idVec2 &max, const idVec2 * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const idVec3 * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const idDrawVert * RESTRICT src, const int * RESTRICT indexes, const int count ); + + VIRTUAL void VPCALL Clamp( float * RESTRICT dst, const float * RESTRICT src, const float min, const float max, const int count ); + VIRTUAL void VPCALL ClampMin( float * RESTRICT dst, const float * RESTRICT src, const float min, const int count ); + VIRTUAL void VPCALL ClampMax( float * RESTRICT dst, const float * RESTRICT src, const float max, const int count ); + + VIRTUAL void VPCALL Memcpy( void * RESTRICT dst, const void * RESTRICT src, const int count ); + VIRTUAL void VPCALL Memset( void * RESTRICT dst, const int val, const int count ); + + VIRTUAL void VPCALL Zero16( float * RESTRICT dst, const int count ); + VIRTUAL void VPCALL Negate16( float * RESTRICT dst, const int count ); + VIRTUAL void VPCALL Copy16( float * RESTRICT dst, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL Add16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ); + VIRTUAL void VPCALL Sub16( float * RESTRICT dst, const float * RESTRICT src1, const float * RESTRICT src2, const int count ); + VIRTUAL void VPCALL Mul16( float * RESTRICT dst, const float * RESTRICT src1, const float constant, const int count ); + VIRTUAL void VPCALL AddAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL SubAssign16( float * RESTRICT dst, const float * RESTRICT src, const int count ); + VIRTUAL void VPCALL MulAssign16( float * RESTRICT dst, const float constant, const int count ); + + VIRTUAL void VPCALL MatX_MultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_MultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_MultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_TransposeMultiplyVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_TransposeMultiplyAddVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_TransposeMultiplySubVecX( idVecX &dst, const idMatX &mat, const idVecX &vec ); + VIRTUAL void VPCALL MatX_MultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + VIRTUAL void VPCALL MatX_TransposeMultiplyMatX( idMatX &dst, const idMatX &m1, const idMatX &m2 ); + VIRTUAL void VPCALL MatX_LowerTriangularSolve( const idMatX &L, float * RESTRICT x, const float * RESTRICT b, const int n, int skip = 0 ); + VIRTUAL void VPCALL MatX_LowerTriangularSolveTranspose( const idMatX &L, float * RESTRICT x, const float *b, const int n ); + VIRTUAL bool VPCALL MatX_LDLTFactor( idMatX &mat, idVecX &invDiag, const int n ); + + ID_INLINE void VPCALL BlendJoints( idJointQuat * RESTRICT joints, const idJointQuat * RESTRICT blendJoints, const float lerp, const int * RESTRICT index, const int numJoints ); + VIRTUAL void VPCALL ConvertJointQuatsToJointMats( idJointMat * RESTRICT jointMats, const idJointQuat * RESTRICT jointQuats, const int numJoints ); + VIRTUAL void VPCALL ConvertJointMatsToJointQuats( idJointQuat * RESTRICT jointQuats, const idJointMat * RESTRICT jointMats, const int numJoints ); + VIRTUAL void VPCALL TransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ); + VIRTUAL void VPCALL UntransformJoints( idJointMat * RESTRICT jointMats, const int * RESTRICT parents, const int firstJoint, const int lastJoint ); + VIRTUAL void VPCALL MultiplyJoints( idJointMat * RESTRICT result, const idJointMat * RESTRICT joints1, const idJointMat * RESTRICT joints2, const int numJoints ); + VIRTUAL void VPCALL TransformVertsNew( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ); + VIRTUAL void VPCALL TransformVertsAndTangents( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ); + VIRTUAL void VPCALL TransformVertsAndTangentsFast( idDrawVert * RESTRICT verts, const int numVerts, idBounds &bounds, const idJointMat * RESTRICT joints, const idVec4 * RESTRICT base, const jointWeight_t * RESTRICT weights, const int numWeights ); + VIRTUAL void VPCALL TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DeriveTriPlanes( idPlane * RESTRICT planes, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL DeriveTangents( idPlane * RESTRICT planes, idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL DeriveUnsmoothedTangents( idDrawVert * RESTRICT verts, const dominantTri_s * RESTRICT dominantTris, const int numVerts ); + VIRTUAL void VPCALL NormalizeTangents( idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL CreateTextureSpaceLightVectors( idVec3 * RESTRICT lightVectors, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL CreateSpecularTextureCoords( idVec4 * RESTRICT texCoords, const idVec3 &lightOrigin, const idVec3 &viewOrigin, const idDrawVert * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL int VPCALL CreateShadowCache( idVec4 * RESTRICT vertexCache, int * RESTRICT vertRemap, const idVec3 &lightOrigin, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL int VPCALL CreateVertexProgramShadowCache( idVec4 * RESTRICT vertexCache, const idDrawVert * RESTRICT verts, const int numVerts ); + VIRTUAL int VPCALL ShadowVolume_CountFacing( const byte * RESTRICT facing, const int numFaces ); + VIRTUAL int VPCALL ShadowVolume_CountFacingCull( byte * RESTRICT facing, const int numFaces, const int * RESTRICT indexes, const byte * RESTRICT cull ); + VIRTUAL int VPCALL ShadowVolume_CreateSilTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const silEdge_s * RESTRICT silEdges, const int numSilEdges ); + VIRTUAL int VPCALL ShadowVolume_CreateCapTriangles( int * RESTRICT shadowIndexes, const byte * RESTRICT facing, const int * RESTRICT indexes, const int numIndexes ); + + VIRTUAL void VPCALL UpSamplePCMTo44kHz( float * RESTRICT dest, const short * RESTRICT pcm, const int numSamples, const int kHz, const int numChannels ); + VIRTUAL void VPCALL UpSampleOGGTo44kHz( float * RESTRICT dest, const float * const * RESTRICT ogg, const int numSamples, const int kHz, const int numChannels ); + VIRTUAL void VPCALL MixSoundTwoSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ); + VIRTUAL void VPCALL MixSoundTwoSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ); + VIRTUAL void VPCALL MixSoundTwoSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[2], const float currentV[2] ); + VIRTUAL void VPCALL MixSoundSixSpeakerMono( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ); + VIRTUAL void VPCALL MixSoundSixSpeakerMonoSimple( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples ); + VIRTUAL void VPCALL MixSoundSixSpeakerStereo( float * RESTRICT mixBuffer, const float * RESTRICT samples, const int numSamples, const float lastV[6], const float currentV[6] ); + VIRTUAL void VPCALL MixedSoundToSamples( short * RESTRICT samples, const float * RESTRICT mixBuffer, const int numSamples ); + + // rvSilTraceVertT operations +// dluetscher: added support for operations on idSilTraceVerts and idJointMats +#ifdef _MD5R_SUPPORT + VIRTUAL void VPCALL JointMat_MultiplyMats( float * RESTRICT destMats, const idJointMat * RESTRICT src1Mats, const idJointMat * RESTRICT src2Mats, int * RESTRICT transformPalette, int transformCount ); + VIRTUAL void VPCALL TransformVertsMinMax4Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, idVec3 &min, idVec3 &max, byte * RESTRICT vertexInputData, int vertStride, int numVerts, float * RESTRICT skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + VIRTUAL void VPCALL TransformVertsMinMax1Bone( rvSilTraceVertT * RESTRICT silTraceVertOutputData, idVec3 &min, idVec3 &max, byte * RESTRICT vertexInputData, int vertStride, int numVerts, float * RESTRICT skinToModelTransforms ); // transforms an array of index-weighted vertices into an array of idSilTraceVerts, while simulatenously calculating the bounds + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idVec3 &constant, const rvSilTraceVertT * RESTRICT src, const int count ); + VIRTUAL void VPCALL Dot( float * RESTRICT dst, const idPlane &constant, const rvSilTraceVertT * RESTRICT src, const int count ); + VIRTUAL void VPCALL TracePointCull( byte * RESTRICT cullBits, byte &totalOr, const float radius, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DecalPointCull( byte * RESTRICT cullBits, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL OverlayPointCull( byte * RESTRICT cullBits, idVec2 * RESTRICT texCoords, const idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts ); + VIRTUAL void VPCALL DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const int * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL DeriveTriPlanes( idPlane * RESTRICT planes, const rvSilTraceVertT * RESTRICT verts, const int numVerts, const unsigned short * RESTRICT indexes, const int numIndexes ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int count ); + VIRTUAL void VPCALL MinMax( idVec3 &min, idVec3 &max, const rvSilTraceVertT * RESTRICT src, const int * RESTRICT indexes, const int count ); +#endif +}; + +// jsinger: inlined during profiling with Microsoft. This shows up pretty high on our profiles +// inlining reduced the many call overhead and every little bit helps on xenon +void VPCALL idSIMD_Generic::BlendJoints( idJointQuat * RESTRICT joints, const idJointQuat * RESTRICT blendJoints, const float lerp, const int * RESTRICT index, const int count ) { +#define UNROLL4(Y) { int _IX, _NM = count&0xfffffffc; for (_IX=0;_IX<_NM;_IX+=4){Y(_IX+0);Y(_IX+1);Y(_IX+2);Y(_IX+3);}for(;_IX. + +=========================================================================== +*/ + +#include "../precompiled.h" +#pragma hdrstop + +idVec2 vec2_origin( 0.0f, 0.0f ); +idVec3 vec3_origin( 0.0f, 0.0f, 0.0f ); +idVec4 vec4_origin( 0.0f, 0.0f, 0.0f, 0.0f ); +idVec4 vec4_one( 1.0f, 1.0f, 1.0f, 1.0f ); +idVec5 vec5_origin( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ); +idVec6 vec6_origin( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ); +idVec6 vec6_infinity( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ); + + +//=============================================================== +// +// idVec2 +// +//=============================================================== + +/* +============= +idVec2::ToString +============= +*/ +const char *idVec2::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +void idVec2::Lerp( const idVec2 &v1, const idVec2 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} + + +//=============================================================== +// +// idVec3 +// +//=============================================================== + +/* +============= +idVec3::ToYaw +============= +*/ +float idVec3::ToYaw( void ) const { + float yaw; + + if ( ( y == 0.0f ) && ( x == 0.0f ) ) { + yaw = 0.0f; + } else { + yaw = RAD2DEG( idMath::ATan( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + } + + return yaw; +} + +/* +============= +idVec3::ToPitch +============= +*/ +float idVec3::ToPitch( void ) const { + float forward; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( idMath::ATan( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + + return pitch; +} + +/* +============= +idVec3::ToAngles +============= +*/ +idAngles idVec3::ToAngles( void ) const { + float forward; + float yaw; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + yaw = 0.0f; + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + yaw = RAD2DEG( idMath::ATan( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( idMath::ATan( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + + return idAngles( -pitch, yaw, 0.0f ); +} + +// RAVEN BEGIN +/* +============= +idVec3::ToRadians +============= +*/ +rvAngles idVec3::ToRadians( void ) const +{ + float forward; + float yaw; + float pitch; + + if( !x && !y ) + { + yaw = 0.0f; + if( z > 0.0f ) + { + pitch = idMath::HALF_PI; + } + else + { + pitch = idMath::THREEFOURTHS_PI; + } + } + else + { + yaw = idMath::ATan( y, x ); + if( yaw < 0.0f ) + { + yaw += idMath::TWO_PI; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = idMath::ATan( z, forward ); + if( pitch < 0.0f ) + { + pitch += idMath::TWO_PI; + } + } + + return( rvAngles( -pitch, yaw, 0.0f ) ); +} +// RAVEN END + +/* +============= +idVec3::ToPolar +============= +*/ +idPolar3 idVec3::ToPolar( void ) const { + float forward; + float yaw; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) { + yaw = 0.0f; + if ( z > 0.0f ) { + pitch = 90.0f; + } else { + pitch = 270.0f; + } + } else { + yaw = RAD2DEG( idMath::ATan( y, x ) ); + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + + forward = ( float )idMath::Sqrt( x * x + y * y ); + pitch = RAD2DEG( idMath::ATan( z, forward ) ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + return idPolar3( idMath::Sqrt( x * x + y * y + z * z ), yaw, -pitch ); +} + +/* +============= +idVec3::ToMat3 +============= +*/ +idMat3 idVec3::ToMat3( void ) const { + idMat3 mat; + float d; + + mat[0] = *this; + d = x * x + y * y; + if ( !d ) { + mat[1][0] = 1.0f; + mat[1][1] = 0.0f; + mat[1][2] = 0.0f; + } else { + d = idMath::InvSqrt( d ); + mat[1][0] = -y * d; + mat[1][1] = x * d; + mat[1][2] = 0.0f; + } + mat[2] = Cross( mat[1] ); + + return mat; +} + +/* +============= +idVec3::ToMat3 +============= +*/ +// RAVEN BEGIN +// abahr: added axis so we can create matrix with non-x vector +idMat3 idVec3::ToMat3( int axis ) const { + idMat3 mat; + float d; + int index_x = axis % GetDimension(); + int index_y = (axis + 1) % GetDimension(); + int index_z = (axis + 2) % GetDimension(); + float local_x = (*this)[index_x]; + float local_y = (*this)[index_y]; + + mat[axis] = *this; + d = local_x * local_x + local_y * local_y; + if ( !d ) { + mat[index_y][index_x] = 1.0f; + mat[index_y][index_y] = 0.0f; + mat[index_y][index_z] = 0.0f; + } else { + d = idMath::InvSqrt( d ); + mat[index_y][index_x] = -local_y * d; + mat[index_y][index_y] = local_x * d; + mat[index_y][index_z] = 0.0f; + } + mat[index_z] = Cross( mat[index_y] ); + + return mat; +} +// RAVEN END + +// RAVEN BEGIN +// jscott: slightly quicker version without the copy +idMat3 &idVec3::ToMat3( idMat3 &mat ) const +{ + float d; + + mat[0] = *this; + d = x * x + y * y; + if ( !d ) + { + mat[1][0] = 1.0f; + mat[1][1] = 0.0f; + mat[1][2] = 0.0f; + } + else + { + d = idMath::InvSqrt( d ); + mat[1][0] = -y * d; + mat[1][1] = x * d; + mat[1][2] = 0.0f; + } + mat[2] = Cross( mat[1] ); + return( mat ); +} +// RAVEN END + +/* +============= +idVec3::ToString +============= +*/ +const char *idVec3::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +void idVec3::Lerp( const idVec3 &v1, const idVec3 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} + +/* +============= +SLerp + +Spherical linear interpolation from v1 to v2. +Vectors are expected to be normalized. +============= +*/ +#define LERP_DELTA 1e-6 + +void idVec3::SLerp( const idVec3 &v1, const idVec3 &v2, const float t ) { + float omega, cosom, sinom, scale0, scale1; + + if ( t <= 0.0f ) { + (*this) = v1; + return; + } else if ( t >= 1.0f ) { + (*this) = v2; + return; + } + + cosom = v1 * v2; + if ( ( 1.0f - cosom ) > LERP_DELTA ) { + omega = acos( cosom ); + sinom = idMath::Sin( omega ); + scale0 = idMath::Sin( ( 1.0f - t ) * omega ) / sinom; + scale1 = idMath::Sin( t * omega ) / sinom; + } else { + scale0 = 1.0f - t; + scale1 = t; + } + + (*this) = ( v1 * scale0 + v2 * scale1 ); +} + +/* +============= +ProjectSelfOntoSphere + +Projects the z component onto a sphere. +============= +*/ +void idVec3::ProjectSelfOntoSphere( const float radius ) { + float rsqr = radius * radius; + float len = Length(); + if ( len < rsqr * 0.5f ) { + z = idMath::Sqrt( rsqr - len ); + } else { + z = rsqr / ( 2.0f * idMath::Sqrt( len ) ); + } +} + +//=============================================================== +// +// idVec4 +// +//=============================================================== + +/* +============= +idVec4::ToString +============= +*/ +const char *idVec4::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +Lerp + +Linearly inperpolates one vector to another. +============= +*/ +// jsinger: moved to inline in the header file for Xenon + +//=============================================================== +// +// idVec5 +// +//=============================================================== + +/* +============= +idVec5::ToString +============= +*/ +const char *idVec5::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +/* +============= +idVec5::Lerp +============= +*/ +void idVec5::Lerp( const idVec5 &v1, const idVec5 &v2, const float l ) { + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + x = v1.x + l * ( v2.x - v1.x ); + y = v1.y + l * ( v2.y - v1.y ); + z = v1.z + l * ( v2.z - v1.z ); + s = v1.s + l * ( v2.s - v1.s ); + t = v1.t + l * ( v2.t - v1.t ); + } +} + + +//=============================================================== +// +// idVec6 +// +//=============================================================== + +/* +============= +idVec6::ToString +============= +*/ +const char *idVec6::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} + +//=============================================================== +// +// idVecX +// +//=============================================================== + +// RAVEN BEGIN +float idVecX::tempPtr[VECX_MAX_TEMP]; +//float * idVecX::tempPtr = (float *) ( ( (int) idVecX::temp + 15 ) & ~15 ); +// RAVEN END +int idVecX::tempIndex = 0; + +/* +============= +idVecX::ToString +============= +*/ +const char *idVecX::ToString( int precision ) const { + return idStr::FloatArrayToString( ToFloatPtr(), GetDimension(), precision ); +} diff --git a/src/idlib/math/Vector.h b/src/idlib/math/Vector.h new file mode 100644 index 0000000..4c6aec1 --- /dev/null +++ b/src/idlib/math/Vector.h @@ -0,0 +1,2516 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MATH_VECTOR_H__ +#define __MATH_VECTOR_H__ + +/* +=============================================================================== + + Vector classes + +=============================================================================== +*/ + +#define VECTOR_EPSILON 0.001f + +class idAngles; +class idPolar3; +class idMat3; + +// RAVEN BEGIN +class rvAngles; +// RAVEN END + +//=============================================================== +// +// idVec2 - 2D vector +// +//=============================================================== + +class idVec2 { +public: + float x; + float y; + + idVec2( void ); + explicit idVec2( const float x, const float y ); + + void Set( const float x, const float y ); + void Zero( void ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idVec2 operator-() const; + float operator*( const idVec2 &a ) const; + idVec2 operator*( const float a ) const; + idVec2 operator/( const float a ) const; + idVec2 operator+( const idVec2 &a ) const; + idVec2 operator-( const idVec2 &a ) const; + idVec2 & operator+=( const idVec2 &a ); + idVec2 & operator-=( const idVec2 &a ); + idVec2 & operator/=( const idVec2 &a ); + idVec2 & operator/=( const float a ); + idVec2 & operator*=( const float a ); + + friend idVec2 operator*( const float a, const idVec2 b ); + + bool Compare( const idVec2 &a ) const; // exact compare, no epsilon + bool Compare( const idVec2 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec2 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec2 &a ) const; // exact compare, no epsilon + +// RAVEN BEGIN +// ddynerman: vector normalization operator + idVec2 & operator~( void ); +// RAVEN END + + float Length( void ) const; + float LengthFast( void ) const; + float LengthSqr( void ) const; + float Normalize( void ); // returns length + float NormalizeFast( void ); // returns length + idVec2 & Truncate( float length ); // cap length + void Clamp( const idVec2 &min, const idVec2 &max ); + void Snap( void ); // snap to closest integer value + void SnapInt( void ); // snap towards integer (floor) + + int GetDimension( void ) const; + + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + void Lerp( const idVec2 &v1, const idVec2 &v2, const float l ); + +// RAVEN BEGIN +// jscott: Ensures second element greater than first + void EnsureIncremental( void ); +// RAVEN END +}; + +extern idVec2 vec2_origin; +#define vec2_zero vec2_origin + +ID_INLINE idVec2::idVec2( void ) { +} + +ID_INLINE idVec2::idVec2( const float x, const float y ) { + this->x = x; + this->y = y; +} + +ID_INLINE void idVec2::Set( const float x, const float y ) { + this->x = x; + this->y = y; +} + +ID_INLINE void idVec2::Zero( void ) { + x = y = 0.0f; +} + +ID_INLINE bool idVec2::Compare( const idVec2 &a ) const { + return ( ( x == a.x ) && ( y == a.y ) ); +} + +ID_INLINE bool idVec2::Compare( const idVec2 &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec2::operator==( const idVec2 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec2::operator!=( const idVec2 &a ) const { + return !Compare( a ); +} + +ID_INLINE float idVec2::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec2::operator[]( int index ) { + return ( &x )[ index ]; +} + +// RAVEN BEGIN +// ddynerman: vector normalization +ID_INLINE idVec2& idVec2::operator~( void ) { + Normalize(); + return *this; +} +// RAVEN END + +ID_INLINE float idVec2::Length( void ) const { + return ( float )idMath::Sqrt( x * x + y * y ); +} + +ID_INLINE float idVec2::LengthFast( void ) const { + float sqrLength; + + sqrLength = x * x + y * y; + return sqrLength * idMath::RSqrt( sqrLength ); +} + +ID_INLINE float idVec2::LengthSqr( void ) const { + return ( x * x + y * y ); +} + +ID_INLINE float idVec2::Normalize( void ) { + float sqrLength, invLength; + + sqrLength = x * x + y * y; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec2::NormalizeFast( void ) { + float lengthSqr, invLength; + + lengthSqr = x * x + y * y; + invLength = idMath::RSqrt( lengthSqr ); + x *= invLength; + y *= invLength; + return invLength * lengthSqr; +} + +ID_INLINE idVec2 &idVec2::Truncate( float length ) { + float length2; + float ilength; + + if ( !length ) { + Zero(); + } + else { + length2 = LengthSqr(); + if ( length2 > length * length ) { + ilength = length * idMath::InvSqrt( length2 ); + x *= ilength; + y *= ilength; + } + } + + return *this; +} + +ID_INLINE void idVec2::Clamp( const idVec2 &min, const idVec2 &max ) { + if ( x < min.x ) { + x = min.x; + } else if ( x > max.x ) { + x = max.x; + } + if ( y < min.y ) { + y = min.y; + } else if ( y > max.y ) { + y = max.y; + } +} + +ID_INLINE void idVec2::Snap( void ) { + x = floor( x + 0.5f ); + y = floor( y + 0.5f ); +} + +ID_INLINE void idVec2::SnapInt( void ) { + x = float( int( x ) ); + y = float( int( y ) ); +} + +ID_INLINE idVec2 idVec2::operator-() const { + return idVec2( -x, -y ); +} + +ID_INLINE idVec2 idVec2::operator-( const idVec2 &a ) const { + return idVec2( x - a.x, y - a.y ); +} + +ID_INLINE float idVec2::operator*( const idVec2 &a ) const { + return x * a.x + y * a.y; +} + +ID_INLINE idVec2 idVec2::operator*( const float a ) const { + return idVec2( x * a, y * a ); +} + +ID_INLINE idVec2 idVec2::operator/( const float a ) const { + float inva = 1.0f / a; + return idVec2( x * inva, y * inva ); +} + +ID_INLINE idVec2 operator*( const float a, const idVec2 b ) { + return idVec2( b.x * a, b.y * a ); +} + +ID_INLINE idVec2 idVec2::operator+( const idVec2 &a ) const { + return idVec2( x + a.x, y + a.y ); +} + +ID_INLINE idVec2 &idVec2::operator+=( const idVec2 &a ) { + x += a.x; + y += a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator/=( const idVec2 &a ) { + x /= a.x; + y /= a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator-=( const idVec2 &a ) { + x -= a.x; + y -= a.y; + + return *this; +} + +ID_INLINE idVec2 &idVec2::operator*=( const float a ) { + x *= a; + y *= a; + + return *this; +} + +ID_INLINE int idVec2::GetDimension( void ) const { + return 2; +} + +ID_INLINE const float *idVec2::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idVec2::ToFloatPtr( void ) { + return &x; +} + +// RAVEN BEGIN +// jscott: ensures the second element is greater than the first +ID_INLINE void idVec2::EnsureIncremental( void ) +{ + float temp; + + if( x < y ) + { + return; + } + temp = x; + x = y; + y = temp; +} +// RAVEN END + +//=============================================================== +// +// idVec3 - 3D vector +// +//=============================================================== + +class idVec3 { +public: + float x; + float y; + float z; + + idVec3( void ); + explicit idVec3( const float x, const float y, const float z ); + + void Set( const float x, const float y, const float z ); + void Zero( void ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVec3 operator-() const; + idVec3 & operator=( const idVec3 &a ); // required because of a msvc 6 & 7 bug +// RAVEN BEGIN +// bdube: added vec2 equal + idVec3 & operator=( const idVec2 &a ); + idVec3 & operator*=( const idVec3 &a ); +// RAVEN END + float operator*( const idVec3 &a ) const; + idVec3 operator*( const float a ) const; + idVec3 operator/( const float a ) const; + idVec3 operator+( const idVec3 &a ) const; + idVec3 operator-( const idVec3 &a ) const; + idVec3 & operator+=( const idVec3 &a ); + idVec3 & operator-=( const idVec3 &a ); + idVec3 & operator/=( const idVec3 &a ); + idVec3 & operator/=( const float a ); + idVec3 & operator*=( const float a ); + + friend idVec3 operator*( const float a, const idVec3 b ); + + bool Compare( const idVec3 &a ) const; // exact compare, no epsilon + bool Compare( const idVec3 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec3 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec3 &a ) const; // exact compare, no epsilon + + bool FixDegenerateNormal( void ); // fix degenerate axial cases + bool FixDenormals( void ); // change tiny numbers to zero + + idVec3 Cross( const idVec3 &a ) const; + idVec3 & Cross( const idVec3 &a, const idVec3 &b ); + +// RAVEN BEGIN +// ddynerman: vector normalization operator + idVec3 & operator~( void ); +// RAVEN END + + float Length( void ) const; + float LengthSqr( void ) const; + float LengthFast( void ) const; + float Normalize( void ); // returns length + float NormalizeFast( void ); // returns length + idVec3 & Truncate( float length ); // cap length + void Clamp( const idVec3 &min, const idVec3 &max ); + void ClampMin( const float &minx, const float &miny, const float &minz ); + void Snap( void ); // snap to closest integer value + void SnapInt( void ); // snap towards integer (floor) + + int GetDimension( void ) const; + + float ToYaw( void ) const; + float ToPitch( void ) const; + idAngles ToAngles( void ) const; + idPolar3 ToPolar( void ) const; + idMat3 ToMat3( void ) const; // vector should be normalized + const idVec2 & ToVec2( void ) const; + idVec2 & ToVec2( void ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +// RAVEN BEGIN +// abahr: added axis so we can create matrix with non-x vector + idMat3 ToMat3( int axis ) const; // vector should be normalized +// abahr: + idVec3 ToNormal() const { idVec3 v( *this ); v.Normalize(); return v; } + idVec3 Random( const idVec3& range, idRandom& random ) const; +// RAVEN END + + void NormalVectors( idVec3 &left, idVec3 &down ) const; // vector should be normalized + void OrthogonalBasis( idVec3 &left, idVec3 &up ) const; + + void ProjectOntoPlane( const idVec3 &normal, const float overBounce = 1.0f ); + bool ProjectAlongPlane( const idVec3 &normal, const float epsilon, const float overBounce = 1.0f ); + void ProjectSelfOntoSphere( const float radius ); + +// RAVEN BEGIN + float ProjectOntoVector(const idVec3 &U); +// RAVEN END + + +// RAVEN BEGIN +// cdr - Extremely useful Vector opterations for Computational Geometry + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Parallel Pipid (2D) + // + // Given two more points, this function calculates the area of the parallel pipid + // formed. + // + // Note: This function CAN return a negative "area" if (this) is above or right of + // (A) and (B)... We do not take the abs because the sign of the "area" is needed + // for the left right test + // + // + // ___---( ... ) + // (A)---/ / + // / / + // / / + // / / + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaParallelPipid(const idVec3 &A, const idVec3 &B) const { + return ((A.x*B.y - A.y*B.x) + (B.x*y - x*B.y) + (x*A.y - A.x*y)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Left Right Test (2D) + // + // Given a line segment (Start->End) and a tolerance for *right on*, this function + // evaluates which side the point is of the line. + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool IsLeftOf(const idVec3 &Start, const idVec3 &End) const { + return (AreaParallelPipid(Start, End)>0.0f); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line Segment + // + // Given a line segment (Start->End) this function will return the distance of the + // given point (this) to the segment. You must also pass in a Scale argument which + // will tell you: + // Scale (-INF, 0.0f) => (this) projected before (Start) + // Scale [0.0f, 1.0f] => (this) projected inside the segment (Start->End) + // Scale (1.0f, +INF) => (this) projected beyond (End) + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + float DistToLineSeg(const idVec3 &Start, const idVec3 &End, float& Scale) const { + static idVec3 U; + static idVec3 V; + static float ULen2; + + V = (*this) - Start; // Compute V + U = End - Start; // Compute U + ULen2 = U.LengthSqr(); // Normalize U + Scale = (V*U / ULen2); // Find the scale of this vector on U + if (Scale<0.0f) {return Dist(Start);} // If Negative Scale, Projected In Front Of Start + if (Scale>1.0f) {return Dist(End);} // If Positive Scale, Projected In Beyond End + return Dist(Start + (U*Scale)); // Otherwise, project U to new location and return dist + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project On To Line Segment + // + // Given a line segment (Start->End) this function will project the point onto the + // segment. + // + // (this) ___---(End) + // \ ---/ + // __ (Result) + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + bool ProjectToLineSeg(const idVec3 &Start, const idVec3 &End) { + static idVec3 U; + static idVec3 V; + static float ULen2; + static float Scale; + + V = (*this) - Start; // Compute V + U = End - Start; // Compute U + ULen2 = U.LengthSqr(); // Normalize U + Scale = (V*U / ULen2); // Find the scale of this vector on U + + if (Scale<0.0f) { + (*this) = Start; // If Negative Scale, Projected In Front Of Start + return false; // Off The End + } else if (Scale>1.0f) { + (*this) = End; // If Positive Scale, Projected In Beyond End + return false; // Off The End + } else { + (*this) = Start + (U*Scale); // Otherwise, project U to new location and return dist + } + return true; // Perpendicular Intersection Is On The Segment + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance Squared (Much Faster than Dist) + //////////////////////////////////////////////////////////////////////////////////// + float Dist2(const idVec3& Pt) const + { + return ((Pt.x-x)*(Pt.x-x) + (Pt.y-y)*(Pt.y-y) + (Pt.z-z)*(Pt.z-z)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance Squared OnlyXY + //////////////////////////////////////////////////////////////////////////////////// + float Dist2XY(const idVec3& Pt) const + { + return ((Pt.x-x)*(Pt.x-x) + (Pt.y-y)*(Pt.y-y)); + } + //////////////////////////////////////////////////////////////////////////////////// + // Distance OnlyXY + //////////////////////////////////////////////////////////////////////////////////// + float DistXY(const idVec3& Pt) const + { + idVec3 delta(x,y,Pt.z); + delta = delta - Pt; + return delta.LengthFast(); + } +// RAVEN END + + void Lerp( const idVec3 &v1, const idVec3 &v2, const float l ); + void SLerp( const idVec3 &v1, const idVec3 &v2, const float l ); + +// RAVEN BEGIN +// jscott: Ensures second element greater than first + void EnsureIncremental( void ); +// jscott: for BSE + int GetLargestAxis( void ) const; +// jscott: for rvAngles + rvAngles ToRadians( void ) const; + idMat3 &ToMat3( idMat3 &mat ) const; // vector should be normalized + float Dist(const idVec3 &Pt) const + { + idVec3 delta(x,y,z); + delta = delta - Pt; + return delta.LengthFast(); + } + +// RAVEN END + + bool IsZero( void ) const; +}; + +extern idVec3 vec3_origin; +#define vec3_zero vec3_origin + +ID_INLINE idVec3::idVec3( void ) { +} + +ID_INLINE idVec3::idVec3( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE float idVec3::operator[]( const int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float &idVec3::operator[]( const int index ) { + return ( &x )[ index ]; +} + +ID_INLINE void idVec3::Set( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE void idVec3::Zero( void ) { + x = y = z = 0.0f; +} + +ID_INLINE idVec3 idVec3::operator-() const { + return idVec3( -x, -y, -z ); +} + +ID_INLINE idVec3 &idVec3::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + return *this; +} + +// RAVEN BEGIN +// bdube: added vec3 from vec2 assignment +ID_INLINE idVec3 &idVec3::operator=( const idVec2 &a ) { + x = a.x; + y = a.y; + return *this; +} +ID_INLINE idVec3 &idVec3::operator*=( const idVec3 &a ) +{ + x *= a.x; + y *= a.y; + z *= a.z; + return *this; +} +// RAVEN END + +ID_INLINE idVec3 idVec3::operator-( const idVec3 &a ) const { + return idVec3( x - a.x, y - a.y, z - a.z ); +} + +ID_INLINE float idVec3::operator*( const idVec3 &a ) const { + return x * a.x + y * a.y + z * a.z; +} + +ID_INLINE idVec3 idVec3::operator*( const float a ) const { + return idVec3( x * a, y * a, z * a ); +} + +ID_INLINE idVec3 idVec3::operator/( const float a ) const { + float inva = 1.0f / a; + return idVec3( x * inva, y * inva, z * inva ); +} + +ID_INLINE idVec3 operator*( const float a, const idVec3 b ) { + return idVec3( b.x * a, b.y * a, b.z * a ); +} + +ID_INLINE idVec3 idVec3::operator+( const idVec3 &a ) const { + return idVec3( x + a.x, y + a.y, z + a.z ); +} + +ID_INLINE idVec3 &idVec3::operator+=( const idVec3 &a ) { + x += a.x; + y += a.y; + z += a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator/=( const idVec3 &a ) { + x /= a.x; + y /= a.y; + z /= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + z *= inva; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator-=( const idVec3 &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + + return *this; +} + +ID_INLINE idVec3 &idVec3::operator*=( const float a ) { + x *= a; + y *= a; + z *= a; + + return *this; +} + +ID_INLINE bool idVec3::Compare( const idVec3 &a ) const { + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) ); +} + +ID_INLINE bool idVec3::Compare( const idVec3 &a, const float epsilon ) const { + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec3::operator==( const idVec3 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec3::operator!=( const idVec3 &a ) const { + return !Compare( a ); +} + +ID_INLINE float idVec3::NormalizeFast( void ) { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; + invLength = idMath::RSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; +} + +ID_INLINE bool idVec3::FixDegenerateNormal( void ) { + if ( x == 0.0f ) { + if ( y == 0.0f ) { + if ( z > 0.0f ) { + if ( z != 1.0f ) { + z = 1.0f; + return true; + } + } else { + if ( z != -1.0f ) { + z = -1.0f; + return true; + } + } + return false; + } else if ( z == 0.0f ) { + if ( y > 0.0f ) { + if ( y != 1.0f ) { + y = 1.0f; + return true; + } + } else { + if ( y != -1.0f ) { + y = -1.0f; + return true; + } + } + return false; + } + } else if ( y == 0.0f ) { + if ( z == 0.0f ) { + if ( x > 0.0f ) { + if ( x != 1.0f ) { + x = 1.0f; + return true; + } + } else { + if ( x != -1.0f ) { + x = -1.0f; + return true; + } + } + return false; + } + } + if ( idMath::Fabs( x ) == 1.0f ) { + if ( y != 0.0f || z != 0.0f ) { + y = z = 0.0f; + return true; + } + return false; + } else if ( idMath::Fabs( y ) == 1.0f ) { + if ( x != 0.0f || z != 0.0f ) { + x = z = 0.0f; + return true; + } + return false; + } else if ( idMath::Fabs( z ) == 1.0f ) { + if ( x != 0.0f || y != 0.0f ) { + x = y = 0.0f; + return true; + } + return false; + } + return false; +} + +ID_INLINE bool idVec3::FixDenormals( void ) { + bool denormal = false; + if ( fabs( x ) < 1e-30f ) { + x = 0.0f; + denormal = true; + } + if ( fabs( y ) < 1e-30f ) { + y = 0.0f; + denormal = true; + } + if ( fabs( z ) < 1e-30f ) { + z = 0.0f; + denormal = true; + } + return denormal; +} + +ID_INLINE idVec3 idVec3::Cross( const idVec3 &a ) const { + return idVec3( y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x ); +} + +ID_INLINE idVec3 &idVec3::Cross( const idVec3 &a, const idVec3 &b ) { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + + return *this; +} + +// RAVEN BEGIN +// ddynerman: vector normalization operator +ID_INLINE idVec3& idVec3::operator~( void ) { + Normalize(); + return *this; +} +// RAVEN END + +ID_INLINE float idVec3::Length( void ) const { + return ( float )idMath::Sqrt( x * x + y * y + z * z ); +} + +ID_INLINE float idVec3::LengthSqr( void ) const { + return ( x * x + y * y + z * z ); +} + +ID_INLINE float idVec3::LengthFast( void ) const { + float sqrLength; + + sqrLength = x * x + y * y + z * z; + return sqrLength * idMath::RSqrt( sqrLength ); +} + +ID_INLINE float idVec3::Normalize( void ) { + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z; +// RAVEN BEGIN +// jscott: fixed degenerate case + if ( !sqrLength ) { + return 0.0f; + } +// RAVEN END + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + return invLength * sqrLength; +} + +ID_INLINE idVec3 &idVec3::Truncate( float length ) { + float length2; + float ilength; + + if ( !length ) { + Zero(); + } + else { + length2 = LengthSqr(); + if ( length2 > length * length ) { + ilength = length * idMath::InvSqrt( length2 ); + x *= ilength; + y *= ilength; + z *= ilength; + } + } + + return *this; +} + +ID_INLINE void idVec3::Clamp( const idVec3 &min, const idVec3 &max ) { + if ( x < min.x ) { + x = min.x; + } else if ( x > max.x ) { + x = max.x; + } + if ( y < min.y ) { + y = min.y; + } else if ( y > max.y ) { + y = max.y; + } + if ( z < min.z ) { + z = min.z; + } else if ( z > max.z ) { + z = max.z; + } +} + +ID_INLINE void idVec3::ClampMin( const float &minx, const float &miny, const float &minz ) { + if ( x < minx ) { + x = minx; + } + if ( y < miny ) { + y = miny; + } + if ( z < minz ) { + z = minz; + } +} + +ID_INLINE void idVec3::Snap( void ) { + x = floor( x + 0.5f ); + y = floor( y + 0.5f ); + z = floor( z + 0.5f ); +} + +ID_INLINE void idVec3::SnapInt( void ) { + x = float( int( x ) ); + y = float( int( y ) ); + z = float( int( z ) ); +} + +ID_INLINE int idVec3::GetDimension( void ) const { + return 3; +} + +ID_INLINE const idVec2 &idVec3::ToVec2( void ) const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec2 &idVec3::ToVec2( void ) { + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec3::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idVec3::ToFloatPtr( void ) { + return &x; +} + +ID_INLINE void idVec3::NormalVectors( idVec3 &left, idVec3 &down ) const { + float d; + + d = x * x + y * y; + if ( !d ) { + left[0] = 1; + left[1] = 0; + left[2] = 0; + } else { + d = idMath::InvSqrt( d ); + left[0] = -y * d; + left[1] = x * d; + left[2] = 0; + } + down = left.Cross( *this ); +} + +ID_INLINE void idVec3::OrthogonalBasis( idVec3 &left, idVec3 &up ) const { + float l, s; + + if ( idMath::Fabs( z ) > 0.7f ) { + l = y * y + z * z; + s = idMath::InvSqrt( l ); + up[0] = 0; + up[1] = z * s; + up[2] = -y * s; + left[0] = l * s; + left[1] = -x * up[2]; + left[2] = x * up[1]; + } + else { + l = x * x + y * y; + s = idMath::InvSqrt( l ); + left[0] = -y * s; + left[1] = x * s; + left[2] = 0; + up[0] = -z * left[1]; + up[1] = z * left[0]; + up[2] = l * s; + } +} + +ID_INLINE void idVec3::ProjectOntoPlane( const idVec3 &normal, const float overBounce ) { + float backoff; + + backoff = *this * normal; + + if ( overBounce != 1.0 ) { + if ( backoff < 0 ) { + backoff *= overBounce; + } else { + backoff /= overBounce; + } + } + + *this -= backoff * normal; +} + +// RAVEN BEGIN +//////////////////////////////////////////////////////////////////////////////////// +// Project +// +// Standard projection function. Take the (this) and project it onto the vector +// (U). Imagine drawing a line perpendicular to U from the endpoint of the (this) +// Vector. That then becomes the new vector. +// +// The value returned is the scale of the new vector with respect to the one passed +// to the function. If the scale is less than (1.0) then the new vector is shorter +// than (U). If the scale is negative, then the vector is going in the opposite +// direction of (U). +// +// _ (U) +// /| +// / _ (this) +// / RESULTS-> /| +// / / +// / __\ (this) / +// /___--- / / +// +//////////////////////////////////////////////////////////////////////////////////// +ID_INLINE float idVec3::ProjectOntoVector(const idVec3 &U) +{ + float Scale = ((*this)*(U) / U.LengthSqr()); // Find the scale of this vector on U + (*this)=U; // Copy U onto this vector + (*this)*=Scale; // Use the previously calculated scale to get the right length. + return Scale; +} +// RAVEN END + + +ID_INLINE bool idVec3::ProjectAlongPlane( const idVec3 &normal, const float epsilon, const float overBounce ) { + idVec3 cross; + float len; + + cross = this->Cross( normal ).Cross( (*this) ); + // normalize so a fixed epsilon can be used + cross.Normalize(); + len = normal * cross; + if ( idMath::Fabs( len ) < epsilon ) { + return false; + } + cross *= overBounce * ( normal * (*this) ) / len; + (*this) -= cross; + return true; +} + +// RAVEN BEGIN +// jscott: ensures the second element is greater than the first and that the third is greater than the 2nd +ID_INLINE void idVec3::EnsureIncremental( void ) +{ + float temp; + + if ( y < x ) + { + temp = x; + x = y; + y = temp; + } + + if ( z < y ) + { + temp = y; + y = z; + z = temp; + } + + if ( y < x ) + { + temp = x; + x = y; + y = temp; + } +} + +ID_INLINE int idVec3::GetLargestAxis( void ) const +{ + float a = fabs( x ); + float b = fabs( y ); + float c = fabs( z ); + + if( a >= b && a >= c ) + { + return( 0 ); + } + if( b >= a && b >= c ) + { + return( 1 ); + } + if( c >= a && c >= b ) + { + return( 2 ); + } + return( 0 ); +} + +// abahr +ID_INLINE idVec3 idVec3::Random( const idVec3& range, idRandom& random ) const { + idVec3 v( *this ); + for( int ix = 0; ix < GetDimension(); ++ix ) { + v[ ix ] += v[ ix ] * range[ix] * random.CRandomFloat(); + } + return v; +} +// RAVEN END + +ID_INLINE bool idVec3::IsZero( void ) const { + return ( ( ( *( const unsigned long * ) &( x ) ) | ( *( const unsigned long * ) &( y ) ) | ( *( const unsigned long * ) &( z ) ) ) & ~( 1<<31 ) ) == 0; +} + +//=============================================================== +// +// idVec4 - 4D vector +// +//=============================================================== +#ifdef _XENON +#else +class idVec4 { +#endif +public: +#ifndef _XENON + float x; + float y; + float z; + float w; +#else +#endif + ID_INLINE idVec4( void ); + ID_INLINE explicit idVec4( const float x, const float y, const float z, const float w ); + + ID_INLINE void Set( const float x, const float y, const float z, const float w ); + ID_INLINE void Zero( void ); + + ID_INLINE float operator[]( const int index ) const; + ID_INLINE float & operator[]( const int index ); + ID_INLINE idVec4 operator-() const; + ID_INLINE float operator*( const idVec4 &a ) const; + ID_INLINE idVec4 operator*( const float a ) const; + ID_INLINE idVec4 operator/( const float a ) const; + ID_INLINE idVec4 operator+( const idVec4 &a ) const; + ID_INLINE idVec4 operator-( const idVec4 &a ) const; + ID_INLINE idVec4 & operator+=( const idVec4 &a ); + ID_INLINE idVec4 & operator-=( const idVec4 &a ); + ID_INLINE idVec4 & operator/=( const idVec4 &a ); + ID_INLINE idVec4 & operator/=( const float a ); + ID_INLINE idVec4 & operator*=( const float a ); + + ID_INLINE friend idVec4 operator*( const float a, const idVec4 b ); + +// RAVEN BEGIN +// jscott: optimised vector to byte array function + ID_INLINE operator int ( void ); +// RAVEN END + + ID_INLINE bool Compare( const idVec4 &a ) const; // exact compare, no epsilon + ID_INLINE bool Compare( const idVec4 &a, const float epsilon ) const; // compare with epsilon + ID_INLINE bool operator==( const idVec4 &a ) const; // exact compare, no epsilon + ID_INLINE bool operator!=( const idVec4 &a ) const; // exact compare, no epsilon + +// RAVEN BEGIN +// ddynerman: unary normalization operator + ID_INLINE idVec4& operator~( void ); +// RAVEN END + + ID_INLINE float Length( void ) const; + ID_INLINE float LengthSqr( void ) const; + ID_INLINE float Normalize( void ); // returns length + ID_INLINE float NormalizeFast( void ); // returns length + + ID_INLINE int GetDimension( void ) const; + + ID_INLINE const idVec2 & ToVec2( void ) const; + ID_INLINE idVec2 & ToVec2( void ); + ID_INLINE const idVec3 & ToVec3( void ) const; + ID_INLINE idVec3 & ToVec3( void ); + ID_INLINE const float * ToFloatPtr( void ) const; + ID_INLINE float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + ID_INLINE void Lerp( const idVec4 &v1, const idVec4 &v2, const float l ); + +}; + +extern idVec4 vec4_origin; +extern idVec4 vec4_one; + +#define vec4_zero vec4_origin + +#ifndef _XENON +ID_INLINE idVec4::idVec4( void ) { + AlignmentChecker::UpdateCount(&x); +} + +ID_INLINE idVec4::idVec4( const float x, const float y, const float z, const float w ) { + AlignmentChecker::UpdateCount(&x); + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE void idVec4::Set( const float x, const float y, const float z, const float w ) { + AlignmentChecker::UpdateCount(&x); + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +ID_INLINE void idVec4::Zero( void ) { + AlignmentChecker::UpdateCount(&x); + x = y = z = w = 0.0f; +} + +ID_INLINE float idVec4::operator[]( int index ) const { + AlignmentChecker::UpdateCount(&x); + return ( &x )[ index ]; +} + +ID_INLINE float& idVec4::operator[]( int index ) { + AlignmentChecker::UpdateCount(&x); + return ( &x )[ index ]; +} + +ID_INLINE idVec4 idVec4::operator-() const { + AlignmentChecker::UpdateCount(&x); + return idVec4( -x, -y, -z, -w ); +} + +ID_INLINE idVec4 idVec4::operator-( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return idVec4( x - a.x, y - a.y, z - a.z, w - a.w ); +} + +ID_INLINE float idVec4::operator*( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return x * a.x + y * a.y + z * a.z + w * a.w; +} + +ID_INLINE idVec4 idVec4::operator*( const float a ) const { + AlignmentChecker::UpdateCount(&x); + return idVec4( x * a, y * a, z * a, w * a ); +} + +ID_INLINE idVec4 idVec4::operator/( const float a ) const { + AlignmentChecker::UpdateCount(&x); + float inva = 1.0f / a; + return idVec4( x * inva, y * inva, z * inva, w * inva ); +} + +ID_INLINE idVec4 operator*( const float a, const idVec4 b ) { + AlignmentChecker::UpdateCount(&b.x); + return idVec4( b.x * a, b.y * a, b.z * a, b.w * a ); +} + + +ID_INLINE idVec4 idVec4::operator+( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return idVec4( x + a.x, y + a.y, z + a.z, w + a.w ); +} + +ID_INLINE idVec4 &idVec4::operator+=( const idVec4 &a ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator/=( const idVec4 &a ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + x /= a.x; + y /= a.y; + z /= a.z; + w /= a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator/=( const float a ) { + AlignmentChecker::UpdateCount(&x); + float inva = 1.0f / a; + x *= inva; + y *= inva; + z *= inva; + w *= inva; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator-=( const idVec4 &a ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +ID_INLINE idVec4 &idVec4::operator*=( const float a ) { + AlignmentChecker::UpdateCount(&x); + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +// RAVEN BEGIN +// jscott: Opt float array to byte array conversion +#ifdef _WINDOWS +ID_INLINE idVec4::operator int( void ) +{ + int tmp, retval; + float constant = 255.0f; + + _asm + { + push edx + mov edx, this + fld dword ptr[edx + 0] + fld dword ptr[edx + 4] + fld dword ptr[edx + 8] + fld dword ptr[edx + 12] + + fmul [constant] + fistp tmp + mov ah, byte ptr [tmp] + + fmul [constant] + fistp tmp + mov al, byte ptr [tmp] + + shl eax, 16 + + fmul [constant] + fistp tmp + mov ah, byte ptr [tmp] + + fmul [constant] + fistp tmp + mov al, byte ptr [tmp] + + mov [retval], eax + pop edx + } + return( retval ); +} +#else +ID_INLINE idVec4::operator int( void ) +{ + AlignmentChecker::UpdateCount(&x); + int retval; +// RAVEN BEGIN +// jnewquist: byte ordering depends on platform +#ifdef _LITTLE_ENDIAN + retval = ( ( int )( x * 255.0f ) ) << 0; + retval |= ( ( int )( y * 255.0f ) ) << 8; + retval |= ( ( int )( z * 255.0f ) ) << 16; + retval |= ( ( int )( w * 255.0f ) ) << 24; +#else + retval = ( ( int )( x * 255.0f ) ) << 24; + retval |= ( ( int )( y * 255.0f ) ) << 16; + retval |= ( ( int )( z * 255.0f ) ) << 8; + retval |= ( ( int )( w * 255.0f ) ) << 0; +#endif +// RAVEN END + + return( retval ); +} +#endif +// RAVEN END + +ID_INLINE bool idVec4::Compare( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return ( ( x == a.x ) && ( y == a.y ) && ( z == a.z ) && w == a.w ); +} + +ID_INLINE bool idVec4::Compare( const idVec4 &a, const float epsilon ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + if ( idMath::Fabs( x - a.x ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( y - a.y ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( z - a.z ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( w - a.w ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec4::operator==( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return Compare( a ); +} + +ID_INLINE bool idVec4::operator!=( const idVec4 &a ) const { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&a.x); + return !Compare( a ); +} + +// RAVEN BEGIN +// ddynerman: unary normalization operator +ID_INLINE idVec4& idVec4::operator~( void ) { + AlignmentChecker::UpdateCount(&x); + Normalize(); + return *this; +} +// RAVEN END + +ID_INLINE float idVec4::Length( void ) const { + AlignmentChecker::UpdateCount(&x); + return ( float )idMath::Sqrt( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4::LengthSqr( void ) const { + AlignmentChecker::UpdateCount(&x); + return ( x * x + y * y + z * z + w * w ); +} + +ID_INLINE float idVec4::Normalize( void ) { + AlignmentChecker::UpdateCount(&x); + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::InvSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec4::NormalizeFast( void ) { + AlignmentChecker::UpdateCount(&x); + float sqrLength, invLength; + + sqrLength = x * x + y * y + z * z + w * w; + invLength = idMath::RSqrt( sqrLength ); + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + return invLength * sqrLength; +} + +ID_INLINE int idVec4::GetDimension( void ) const { + AlignmentChecker::UpdateCount(&x); + return 4; +} + +ID_INLINE const idVec2 &idVec4::ToVec2( void ) const { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE idVec2 &idVec4::ToVec2( void ) { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE const idVec3 &idVec4::ToVec3( void ) const { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE idVec3 &idVec4::ToVec3( void ) { + AlignmentChecker::UpdateCount(&x); + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec4::ToFloatPtr( void ) const { + AlignmentChecker::UpdateCount(&x); + return &x; +} + +ID_INLINE float *idVec4::ToFloatPtr( void ) { + AlignmentChecker::UpdateCount(&x); + return &x; +} + +ID_INLINE void idVec4::Lerp( const idVec4 &v1, const idVec4 &v2, const float l ) { + AlignmentChecker::UpdateCount(&x); + AlignmentChecker::UpdateCount(&v1.x); + AlignmentChecker::UpdateCount(&v2.x); + if ( l <= 0.0f ) { + (*this) = v1; + } else if ( l >= 1.0f ) { + (*this) = v2; + } else { + (*this) = v1 + l * ( v2 - v1 ); + } +} +#endif + +//=============================================================== +// +// idVec5 - 5D vector +// +//=============================================================== + +class idVec5 { +public: + float x; + float y; + float z; + float s; + float t; + + idVec5( void ); + explicit idVec5( const idVec3 &xyz, const idVec2 &st ); + explicit idVec5( const float x, const float y, const float z, const float s, const float t ); + + float operator[]( int index ) const; + float & operator[]( int index ); + idVec5 & operator=( const idVec3 &a ); + + int GetDimension( void ) const; + + const idVec3 & ToVec3( void ) const; + idVec3 & ToVec3( void ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + + void Lerp( const idVec5 &v1, const idVec5 &v2, const float l ); +}; + +extern idVec5 vec5_origin; +#define vec5_zero vec5_origin + +ID_INLINE idVec5::idVec5( void ) { +} + +ID_INLINE idVec5::idVec5( const idVec3 &xyz, const idVec2 &st ) { + x = xyz.x; + y = xyz.y; + z = xyz.z; + s = st[0]; + t = st[1]; +} + +ID_INLINE idVec5::idVec5( const float x, const float y, const float z, const float s, const float t ) { + this->x = x; + this->y = y; + this->z = z; + this->s = s; + this->t = t; +} + +ID_INLINE float idVec5::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec5::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec5 &idVec5::operator=( const idVec3 &a ) { + x = a.x; + y = a.y; + z = a.z; + s = t = 0; + return *this; +} + +ID_INLINE int idVec5::GetDimension( void ) const { + return 5; +} + +ID_INLINE const idVec3 &idVec5::ToVec3( void ) const { + return *reinterpret_cast(this); +} + +ID_INLINE idVec3 &idVec5::ToVec3( void ) { + return *reinterpret_cast(this); +} + +ID_INLINE const float *idVec5::ToFloatPtr( void ) const { + return &x; +} + +ID_INLINE float *idVec5::ToFloatPtr( void ) { + return &x; +} + + +//=============================================================== +// +// idVec6 - 6D vector +// +//=============================================================== + +class idVec6 { +public: + idVec6( void ); + explicit idVec6( const float *a ); + explicit idVec6( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + + void Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ); + void Zero( void ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVec6 operator-() const; + idVec6 operator*( const float a ) const; + idVec6 operator/( const float a ) const; + float operator*( const idVec6 &a ) const; + idVec6 operator-( const idVec6 &a ) const; + idVec6 operator+( const idVec6 &a ) const; + idVec6 & operator*=( const float a ); + idVec6 & operator/=( const float a ); + idVec6 & operator+=( const idVec6 &a ); + idVec6 & operator-=( const idVec6 &a ); + + friend idVec6 operator*( const float a, const idVec6 b ); + + bool Compare( const idVec6 &a ) const; // exact compare, no epsilon + bool Compare( const idVec6 &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVec6 &a ) const; // exact compare, no epsilon + bool operator!=( const idVec6 &a ) const; // exact compare, no epsilon + + float Length( void ) const; + float LengthSqr( void ) const; + float Normalize( void ); // returns length + float NormalizeFast( void ); // returns length + + int GetDimension( void ) const; + + const idVec3 & SubVec3( int index ) const; + idVec3 & SubVec3( int index ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + float p[6]; +}; + +extern idVec6 vec6_origin; +#define vec6_zero vec6_origin +extern idVec6 vec6_infinity; + +ID_INLINE idVec6::idVec6( void ) { +} + +ID_INLINE idVec6::idVec6( const float *a ) { + memcpy( p, a, 6 * sizeof( float ) ); +} + +ID_INLINE idVec6::idVec6( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE idVec6 idVec6::operator-() const { + return idVec6( -p[0], -p[1], -p[2], -p[3], -p[4], -p[5] ); +} + +ID_INLINE float idVec6::operator[]( const int index ) const { + return p[index]; +} + +ID_INLINE float &idVec6::operator[]( const int index ) { + return p[index]; +} + +ID_INLINE idVec6 idVec6::operator*( const float a ) const { + return idVec6( p[0]*a, p[1]*a, p[2]*a, p[3]*a, p[4]*a, p[5]*a ); +} + +ID_INLINE float idVec6::operator*( const idVec6 &a ) const { + return p[0] * a[0] + p[1] * a[1] + p[2] * a[2] + p[3] * a[3] + p[4] * a[4] + p[5] * a[5]; +} + +ID_INLINE idVec6 idVec6::operator/( const float a ) const { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + return idVec6( p[0]*inva, p[1]*inva, p[2]*inva, p[3]*inva, p[4]*inva, p[5]*inva ); +} + +ID_INLINE idVec6 idVec6::operator+( const idVec6 &a ) const { + return idVec6( p[0] + a[0], p[1] + a[1], p[2] + a[2], p[3] + a[3], p[4] + a[4], p[5] + a[5] ); +} + +ID_INLINE idVec6 idVec6::operator-( const idVec6 &a ) const { + return idVec6( p[0] - a[0], p[1] - a[1], p[2] - a[2], p[3] - a[3], p[4] - a[4], p[5] - a[5] ); +} + +ID_INLINE idVec6 &idVec6::operator*=( const float a ) { + p[0] *= a; + p[1] *= a; + p[2] *= a; + p[3] *= a; + p[4] *= a; + p[5] *= a; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator/=( const float a ) { + float inva; + + assert( a != 0.0f ); + inva = 1.0f / a; + p[0] *= inva; + p[1] *= inva; + p[2] *= inva; + p[3] *= inva; + p[4] *= inva; + p[5] *= inva; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator+=( const idVec6 &a ) { + p[0] += a[0]; + p[1] += a[1]; + p[2] += a[2]; + p[3] += a[3]; + p[4] += a[4]; + p[5] += a[5]; + return *this; +} + +ID_INLINE idVec6 &idVec6::operator-=( const idVec6 &a ) { + p[0] -= a[0]; + p[1] -= a[1]; + p[2] -= a[2]; + p[3] -= a[3]; + p[4] -= a[4]; + p[5] -= a[5]; + return *this; +} + +ID_INLINE idVec6 operator*( const float a, const idVec6 b ) { + return b * a; +} + +ID_INLINE bool idVec6::Compare( const idVec6 &a ) const { + return ( ( p[0] == a[0] ) && ( p[1] == a[1] ) && ( p[2] == a[2] ) && + ( p[3] == a[3] ) && ( p[4] == a[4] ) && ( p[5] == a[5] ) ); +} + +ID_INLINE bool idVec6::Compare( const idVec6 &a, const float epsilon ) const { + if ( idMath::Fabs( p[0] - a[0] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[1] - a[1] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[2] - a[2] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[3] - a[3] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[4] - a[4] ) > epsilon ) { + return false; + } + + if ( idMath::Fabs( p[5] - a[5] ) > epsilon ) { + return false; + } + + return true; +} + +ID_INLINE bool idVec6::operator==( const idVec6 &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVec6::operator!=( const idVec6 &a ) const { + return !Compare( a ); +} + +ID_INLINE void idVec6::Set( const float a1, const float a2, const float a3, const float a4, const float a5, const float a6 ) { + p[0] = a1; + p[1] = a2; + p[2] = a3; + p[3] = a4; + p[4] = a5; + p[5] = a6; +} + +ID_INLINE void idVec6::Zero( void ) { + p[0] = p[1] = p[2] = p[3] = p[4] = p[5] = 0.0f; +} + +ID_INLINE float idVec6::Length( void ) const { + return ( float )idMath::Sqrt( p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5] ); +} + +ID_INLINE float idVec6::LengthSqr( void ) const { + return ( p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5] ); +} + +ID_INLINE float idVec6::Normalize( void ) { + float sqrLength, invLength; + + sqrLength = p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5]; + invLength = idMath::InvSqrt( sqrLength ); + p[0] *= invLength; + p[1] *= invLength; + p[2] *= invLength; + p[3] *= invLength; + p[4] *= invLength; + p[5] *= invLength; + return invLength * sqrLength; +} + +ID_INLINE float idVec6::NormalizeFast( void ) { + float sqrLength, invLength; + + sqrLength = p[0] * p[0] + p[1] * p[1] + p[2] * p[2] + p[3] * p[3] + p[4] * p[4] + p[5] * p[5]; + invLength = idMath::RSqrt( sqrLength ); + p[0] *= invLength; + p[1] *= invLength; + p[2] *= invLength; + p[3] *= invLength; + p[4] *= invLength; + p[5] *= invLength; + return invLength * sqrLength; +} + +ID_INLINE int idVec6::GetDimension( void ) const { + return 6; +} + +ID_INLINE const idVec3 &idVec6::SubVec3( int index ) const { + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE idVec3 &idVec6::SubVec3( int index ) { + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE const float *idVec6::ToFloatPtr( void ) const { + return p; +} + +ID_INLINE float *idVec6::ToFloatPtr( void ) { + return p; +} + + +//=============================================================== +// +// idVecX - arbitrary sized vector +// +// The vector lives on 16 byte aligned and 16 byte padded memory. +// +// NOTE: due to the temporary memory pool idVecX cannot be used by multiple threads +// +//=============================================================== + +#define VECX_MAX_TEMP 1024 +#define VECX_QUAD( x ) ( ( ( ( x ) + 3 ) & ~3 ) * sizeof( float ) ) +#define VECX_CLEAREND() int s = size; while( s < ( ( s + 3) & ~3 ) ) { p[s++] = 0.0f; } +#define VECX_ALLOCA( n ) ( (float *) _alloca16( VECX_QUAD( n ) ) ) +#define VECX_SIMD + +// RAVEN BEGIN +// jsinger: this is broken at the moment because idSIMDProcessor is no longer virtual +#ifdef _XENON +#undef VECX_SIMD +#endif +// RAVEN END + +class idVecX { + friend class idMatX; + +public: + idVecX( void ); + explicit idVecX( int length ); + explicit idVecX( int length, float *data ); + ~idVecX( void ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idVecX operator-() const; + idVecX & operator=( const idVecX &a ); + idVecX operator*( const float a ) const; + idVecX operator/( const float a ) const; + float operator*( const idVecX &a ) const; + idVecX operator-( const idVecX &a ) const; + idVecX operator+( const idVecX &a ) const; + idVecX & operator*=( const float a ); + idVecX & operator/=( const float a ); + idVecX & operator+=( const idVecX &a ); + idVecX & operator-=( const idVecX &a ); + + friend idVecX operator*( const float a, const idVecX b ); + + bool Compare( const idVecX &a ) const; // exact compare, no epsilon + bool Compare( const idVecX &a, const float epsilon ) const; // compare with epsilon + bool operator==( const idVecX &a ) const; // exact compare, no epsilon + bool operator!=( const idVecX &a ) const; // exact compare, no epsilon + + void SetSize( int size ); + void ChangeSize( int size, bool makeZero = false ); + int GetSize( void ) const { return size; } + void SetData( int length, float *data ); + void Zero( void ); + void Zero( int length ); + void Random( int seed, float l = 0.0f, float u = 1.0f ); + void Random( int length, int seed, float l = 0.0f, float u = 1.0f ); + void Negate( void ); + void Clamp( float min, float max ); + idVecX & SwapElements( int e1, int e2 ); + + float Length( void ) const; + float LengthSqr( void ) const; + idVecX Normalize( void ) const; + float NormalizeSelf( void ); + + int GetDimension( void ) const; + + const idVec3 & SubVec3( int index ) const; + idVec3 & SubVec3( int index ); + const idVec6 & SubVec6( int index ) const; + idVec6 & SubVec6( int index ); + const float * ToFloatPtr( void ) const; + float * ToFloatPtr( void ); + const char * ToString( int precision = 2 ) const; + +private: + int size; // size of the vector + int alloced; // if -1 p points to data set with SetData + float * p; // memory the vector is stored + +// RAVEN BEGIN + ALIGN16( static float tempPtr[VECX_MAX_TEMP] ); // used to store intermediate results +// static float * tempPtr; // pointer to 16 byte aligned temporary memory +// RAVEN END + static int tempIndex; // index into memory pool, wraps around + +private: + void SetTempSize( int size ); +}; + + +ID_INLINE idVecX::idVecX( void ) { + size = alloced = 0; + p = NULL; +} + +ID_INLINE idVecX::idVecX( int length ) { + size = alloced = 0; + p = NULL; + SetSize( length ); +} + +ID_INLINE idVecX::idVecX( int length, float *data ) { + size = alloced = 0; + p = NULL; + SetData( length, data ); +} + +ID_INLINE idVecX::~idVecX( void ) { + // if not temp memory + if ( p && ( p < idVecX::tempPtr || p >= idVecX::tempPtr + VECX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( p ); + } +} + +ID_INLINE float idVecX::operator[]( const int index ) const { + assert( index >= 0 && index < size ); + return p[index]; +} + +ID_INLINE float &idVecX::operator[]( const int index ) { + assert( index >= 0 && index < size ); + return p[index]; +} + +ID_INLINE idVecX idVecX::operator-() const { + int i; + idVecX m; + + m.SetTempSize( size ); + for ( i = 0; i < size; i++ ) { + m.p[i] = -p[i]; + } + return m; +} + +ID_INLINE idVecX &idVecX::operator=( const idVecX &a ) { + SetSize( a.size ); +#ifdef VECX_SIMD + SIMDProcessor->Copy16( p, a.p, a.size ); +#else + memcpy( p, a.p, a.size * sizeof( float ) ); +#endif + idVecX::tempIndex = 0; + return *this; +} + +ID_INLINE idVecX idVecX::operator+( const idVecX &a ) const { + idVecX m; + + assert( size == a.size ); + m.SetTempSize( size ); +#ifdef VECX_SIMD + SIMDProcessor->Add16( m.p, p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] + a.p[i]; + } +#endif + return m; +} + +ID_INLINE idVecX idVecX::operator-( const idVecX &a ) const { + idVecX m; + + assert( size == a.size ); + m.SetTempSize( size ); +#ifdef VECX_SIMD + SIMDProcessor->Sub16( m.p, p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] - a.p[i]; + } +#endif + return m; +} + +ID_INLINE idVecX &idVecX::operator+=( const idVecX &a ) { + assert( size == a.size ); +#ifdef VECX_SIMD + SIMDProcessor->AddAssign16( p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] += a.p[i]; + } +#endif + idVecX::tempIndex = 0; + return *this; +} + +ID_INLINE idVecX &idVecX::operator-=( const idVecX &a ) { + assert( size == a.size ); +#ifdef VECX_SIMD + SIMDProcessor->SubAssign16( p, a.p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] -= a.p[i]; + } +#endif + idVecX::tempIndex = 0; + return *this; +} + +ID_INLINE idVecX idVecX::operator*( const float a ) const { + idVecX m; + + m.SetTempSize( size ); +#ifdef VECX_SIMD + SIMDProcessor->Mul16( m.p, p, a, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] * a; + } +#endif + return m; +} + +ID_INLINE idVecX &idVecX::operator*=( const float a ) { +#ifdef VECX_SIMD + SIMDProcessor->MulAssign16( p, a, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] *= a; + } +#endif + return *this; +} + +ID_INLINE idVecX idVecX::operator/( const float a ) const { + assert( a != 0.0f ); + return (*this) * ( 1.0f / a ); +} + +ID_INLINE idVecX &idVecX::operator/=( const float a ) { + assert( a != 0.0f ); + (*this) *= ( 1.0f / a ); + return *this; +} + +ID_INLINE idVecX operator*( const float a, const idVecX b ) { + return b * a; +} + +ID_INLINE float idVecX::operator*( const idVecX &a ) const { + int i; + float sum = 0.0f; + + assert( size == a.size ); + for ( i = 0; i < size; i++ ) { + sum += p[i] * a.p[i]; + } + return sum; +} + +ID_INLINE bool idVecX::Compare( const idVecX &a ) const { + int i; + + assert( size == a.size ); + for ( i = 0; i < size; i++ ) { + if ( p[i] != a.p[i] ) { + return false; + } + } + return true; +} + +ID_INLINE bool idVecX::Compare( const idVecX &a, const float epsilon ) const { + int i; + + assert( size == a.size ); + for ( i = 0; i < size; i++ ) { + if ( idMath::Fabs( p[i] - a.p[i] ) > epsilon ) { + return false; + } + } + return true; +} + +ID_INLINE bool idVecX::operator==( const idVecX &a ) const { + return Compare( a ); +} + +ID_INLINE bool idVecX::operator!=( const idVecX &a ) const { + return !Compare( a ); +} + +ID_INLINE void idVecX::SetSize( int newSize ) { + int alloc = ( newSize + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + if ( p ) { + Mem_Free16( p ); + } +//RAVEN BEGIN +//amccarthy: Added allocation tag + p = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +//RAVEN END + alloced = alloc; + } + size = newSize; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::ChangeSize( int newSize, bool makeZero ) { + int alloc = ( newSize + 3 ) & ~3; + if ( alloc > alloced && alloced != -1 ) { + float *oldVec = p; +//RAVEN BEGIN +//amccarthy: Added allocation tag + p = (float *) Mem_Alloc16( alloc * sizeof( float ), MA_MATH ); +//RAVEN END + alloced = alloc; + if ( oldVec ) { + for ( int i = 0; i < size; i++ ) { + p[i] = oldVec[i]; + } + Mem_Free16( oldVec ); + } + if ( makeZero ) { + // zero any new elements + for ( int i = size; i < newSize; i++ ) { + p[i] = 0.0f; + } + } + } + size = newSize; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::SetTempSize( int newSize ) { + + size = newSize; + alloced = ( newSize + 3 ) & ~3; + assert( alloced < VECX_MAX_TEMP ); + if ( idVecX::tempIndex + alloced > VECX_MAX_TEMP ) { + idVecX::tempIndex = 0; + } + p = idVecX::tempPtr + idVecX::tempIndex; + idVecX::tempIndex += alloced; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::SetData( int length, float *data ) { + if ( p && ( p < idVecX::tempPtr || p >= idVecX::tempPtr + VECX_MAX_TEMP ) && alloced != -1 ) { + Mem_Free16( p ); + } + assert( ( ( (int) data ) & 15 ) == 0 ); // data must be 16 byte aligned + p = data; + size = length; + alloced = -1; + VECX_CLEAREND(); +} + +ID_INLINE void idVecX::Zero( void ) { +#ifdef VECX_SIMD + SIMDProcessor->Zero16( p, size ); +#else + memset( p, 0, size * sizeof( float ) ); +#endif +} + +ID_INLINE void idVecX::Zero( int length ) { + SetSize( length ); +#ifdef VECX_SIMD + SIMDProcessor->Zero16( p, length ); +#else + memset( p, 0, size * sizeof( float ) ); +#endif +} + +ID_INLINE void idVecX::Random( int seed, float l, float u ) { + int i; + float c; + idRandom rnd( seed ); + + c = u - l; + for ( i = 0; i < size; i++ ) { + p[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idVecX::Random( int length, int seed, float l, float u ) { + int i; + float c; + idRandom rnd( seed ); + + SetSize( length ); + c = u - l; + for ( i = 0; i < size; i++ ) { + p[i] = l + rnd.RandomFloat() * c; + } +} + +ID_INLINE void idVecX::Negate( void ) { +#ifdef VECX_SIMD + SIMDProcessor->Negate16( p, size ); +#else + int i; + for ( i = 0; i < size; i++ ) { + p[i] = -p[i]; + } +#endif +} + +ID_INLINE void idVecX::Clamp( float min, float max ) { + int i; + for ( i = 0; i < size; i++ ) { + if ( p[i] < min ) { + p[i] = min; + } else if ( p[i] > max ) { + p[i] = max; + } + } +} + +ID_INLINE idVecX &idVecX::SwapElements( int e1, int e2 ) { + float tmp; + tmp = p[e1]; + p[e1] = p[e2]; + p[e2] = tmp; + return *this; +} + +ID_INLINE float idVecX::Length( void ) const { + int i; + float sum = 0.0f; + + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + return idMath::Sqrt( sum ); +} + +ID_INLINE float idVecX::LengthSqr( void ) const { + int i; + float sum = 0.0f; + + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + return sum; +} + +ID_INLINE idVecX idVecX::Normalize( void ) const { + int i; + idVecX m; + float invSqrt, sum = 0.0f; + + m.SetTempSize( size ); + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + invSqrt = idMath::InvSqrt( sum ); + for ( i = 0; i < size; i++ ) { + m.p[i] = p[i] * invSqrt; + } + return m; +} + +ID_INLINE float idVecX::NormalizeSelf( void ) { + float invSqrt, sum = 0.0f; + int i; + for ( i = 0; i < size; i++ ) { + sum += p[i] * p[i]; + } + invSqrt = idMath::InvSqrt( sum ); + for ( i = 0; i < size; i++ ) { + p[i] *= invSqrt; + } + return invSqrt * sum; +} + +ID_INLINE int idVecX::GetDimension( void ) const { + return size; +} + +ID_INLINE idVec3 &idVecX::SubVec3( int index ) { + assert( index >= 0 && index * 3 + 3 <= size ); + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE const idVec3 &idVecX::SubVec3( int index ) const { + assert( index >= 0 && index * 3 + 3 <= size ); + return *reinterpret_cast(p + index * 3); +} + +ID_INLINE idVec6 &idVecX::SubVec6( int index ) { + assert( index >= 0 && index * 6 + 6 <= size ); + return *reinterpret_cast(p + index * 6); +} + +ID_INLINE const idVec6 &idVecX::SubVec6( int index ) const { + assert( index >= 0 && index * 6 + 6 <= size ); + return *reinterpret_cast(p + index * 6); +} + +ID_INLINE const float *idVecX::ToFloatPtr( void ) const { + return p; +} + +ID_INLINE float *idVecX::ToFloatPtr( void ) { + return p; +} + + +//=============================================================== +// +// idPolar3 +// +//=============================================================== + +class idPolar3 { +public: + float radius, theta, phi; + + idPolar3( void ); + explicit idPolar3( const float radius, const float theta, const float phi ); + + void Set( const float radius, const float theta, const float phi ); + + float operator[]( const int index ) const; + float & operator[]( const int index ); + idPolar3 operator-() const; + idPolar3 & operator=( const idPolar3 &a ); + + idVec3 ToVec3( void ) const; +}; + +ID_INLINE idPolar3::idPolar3( void ) { +} + +ID_INLINE idPolar3::idPolar3( const float radius, const float theta, const float phi ) { + assert( radius > 0 ); + this->radius = radius; + this->theta = theta; + this->phi = phi; +} + +ID_INLINE void idPolar3::Set( const float radius, const float theta, const float phi ) { + assert( radius > 0 ); + this->radius = radius; + this->theta = theta; + this->phi = phi; +} + +ID_INLINE float idPolar3::operator[]( const int index ) const { + return ( &radius )[ index ]; +} + +ID_INLINE float &idPolar3::operator[]( const int index ) { + return ( &radius )[ index ]; +} + +ID_INLINE idPolar3 idPolar3::operator-() const { + return idPolar3( radius, -theta, -phi ); +} + +ID_INLINE idPolar3 &idPolar3::operator=( const idPolar3 &a ) { + radius = a.radius; + theta = a.theta; + phi = a.phi; + return *this; +} + +ID_INLINE idVec3 idPolar3::ToVec3( void ) const { + float sp, cp, st, ct; + idMath::SinCos( phi, sp, cp ); + idMath::SinCos( theta, st, ct ); + return idVec3( cp * radius * ct, cp * radius * st, radius * sp ); +} + + +/* +=============================================================================== + + Old 3D vector macros, should no longer be used. + +=============================================================================== +*/ + +#define DotProduct( a, b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) +#define VectorSubtract( a, b, c ) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd( a, b, c ) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorScale( v, s, o ) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define VectorMA( v, s, b, o ) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) +#define VectorCopy( a, b ) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) + + +#endif /* !__MATH_VECTOR_H__ */ diff --git a/src/idlib/precompiled.h b/src/idlib/precompiled.h new file mode 100644 index 0000000..189c0df --- /dev/null +++ b/src/idlib/precompiled.h @@ -0,0 +1,435 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PRECOMPILED_H__ +#define __PRECOMPILED_H__ + +#ifdef __cplusplus + +#ifndef ID_TIME_T +#define ID_TIME_T unsigned int +#endif + +// RAVEN BEGIN +// nrausch: conditional cvar archive flag so that the pc build will archive certain cvars +#ifdef _XENON + +#undef _WINDOWS +#define PC_CVAR_ARCHIVE CVAR_NOCHEAT //so it doesn't clobber +#else +#define PC_CVAR_ARCHIVE CVAR_ARCHIVE +#endif +// RAVEN END +class ThreadedAlloc; // class that is only used to expand the AutoCrit template to tag allocs/frees called from inside the R_AddModelSurfaces call graph + + +//----------------------------------------------------- +// RAVEN BEGIN +// jscott: set up conditional compiles +#ifdef _DEBUG_MEMORY +#define ID_REDIRECT_NEWDELETE // Doesn't work with Radiant +#define ID_DEBUG_MEMORY +#endif + +#if defined( _FINAL ) && !defined( _MPBETA ) + #define ID_CONSOLE_LOCK +#endif + +#ifdef _WINDOWS + + // _WIN32 always defined + // _WIN64 also defined for x64 target + #if !defined( _WIN64 ) + #define ID_WIN_X86_ASM + #define ID_WIN_X86_MMX + #define ID_WIN_X86_SSE + //#define ID_WIN_X86_SSE2 + #endif + + // we should never rely on this define in our code. this is here so dodgy external libraries don't get confused + #ifndef WIN32 + #define WIN32 + #endif + + #undef _XBOX + #undef _CONSOLE // Used to comment out code that can't be used on a console + #define _OPENGL + #define _LITTLE_ENDIAN + #undef _CASE_SENSITIVE_FILESYSTEM + #define _USE_OPENAL + #define _USE_VOICECHAT + #ifndef Q4_NO_PUNKBUSTER + #define __WITH_PB__ + #endif + //#define _RV_MEM_SYS_SUPPORT + // when using the PC to make Xenon builds, enable _MD5R_SUPPORT / _MD5R_WRITE_SUPPORT and run with fs_game q4baseXenon + #ifdef Q4SDK + // the SDK can't be compiled with _MD5R_SUPPORT, but since the PC version is we need to maintain ABI + // to make things worse, only the windows version was compiled with _MD5R enabled, the Linux and Mac builds didn't + #define Q4SDK_MD5R + #else // Q4SDK + #define _MD5R_SUPPORT + #define _MD5R_WRITE_SUPPORT + #endif // !Q4SDK + #define _GLVAS_SUPPPORT + //#define RV_BINARYDECLS + #define RV_SINGLE_DECL_FILE + // this can't be used with _RV_MEM_SYS_SUPPORT and actually shouldn't be used at all on the Xenon at present + #if !defined(_RV_MEM_SYS_SUPPORT) && !defined(ID_REDIRECT_NEWDELETE) + #define RV_UNIFIED_ALLOCATOR + #endif + + // SMP support for running the backend on a 2nd thread + #define ENABLE_INTEL_SMP + // Enables the batching of vertex cache request in SMP mode. + // Note (TTimo): is tied to ENABLE_INTEL_SMP + #define ENABLE_INTEL_VERTEXCACHE_OPT + + // Empty define for Xbox 360 compatibility + #define RESTRICT + #define TIME_THIS_SCOPE(x) + + #define NEWLINE "\r\n" + + #pragma warning( disable : 4100 ) // unreferenced formal parameter + #pragma warning( disable : 4127 ) // conditional expression is constant + #pragma warning( disable : 4201 ) // non standard extension nameless struct or union + #pragma warning( disable : 4244 ) // conversion to smaller type, possible loss of data + #pragma warning( disable : 4245 ) // signed/unsigned mismatch + #pragma warning( disable : 4389 ) // signed/unsigned mismatch + #pragma warning( disable : 4714 ) // function marked as __forceinline not inlined + #pragma warning( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + + class AlignmentChecker + { + public: + static void UpdateCount(void const * const ptr) {} + static void ClearCount() {} + static void Print() {} + }; + +#endif // _WINDOWS + +#ifdef __linux__ + +// for offsetof +#include +// FLT_MAX and such +#include +#include + + #define __WITH_PB__ + #undef WIN32 + #undef _XBOX + #undef _CONSOLE + #define _OPENGL + #define _LITTLE_ENDIAN + #define _CASE_SENSITIVE_FILESYSTEM + + #define NEWLINE "\n" + + #define _GLVAS_SUPPPORT + + class AlignmentChecker + { + public: + static void UpdateCount(void const * const ptr) {} + static void ClearCount() {} + static void Print() {} + }; + + #define RESTRICT + #define TIME_THIS_SCOPE(x) + + // we release both a non-SMP and an SMP binary for Linux + #ifdef ENABLE_INTEL_SMP + // Enables the batching of vertex cache request in SMP mode. + // Note (TTimo): is tied to ENABLE_INTEL_SMP + #define ENABLE_INTEL_VERTEXCACHE_OPT + #endif + +#endif + +#ifdef MACOS_X + +// for offsetof +#include + +#include // for square root estimate instruction +#include +#include // for FLT_MIN + + // SMP support for running the backend on a 2nd thread +#ifndef ENABLE_INTEL_SMP + #define ENABLE_INTEL_SMP +#endif + // Enables the batching of vertex cache request in SMP mode. + // Note (TTimo): is tied to ENABLE_INTEL_SMP + #define ENABLE_INTEL_VERTEXCACHE_OPT + + #define __WITH_PB__ + #undef WIN32 + #undef _XBOX + #undef _CONSOLE + #define _OPENGL +#ifdef __ppc__ + #undef _LITTLE_ENDIAN +#else + #define _LITTLE_ENDIAN +#endif + #define _CASE_SENSITIVE_FILESYSTEM + #define _USE_OPENAL + #define ID_INLINE inline + #define NEWLINE "\n" + + #define _GLVAS_SUPPPORT + + class AlignmentChecker + { + public: + static void UpdateCount(void const * const ptr) {} + static void ClearCount() {} + static void Print() {} + }; + + #define RESTRICT + #define TIME_THIS_SCOPE(x) +#endif + +#ifdef _WINDOWS + +#ifndef Q4SDK + +#if !defined( GAME_DLL ) && !defined( GAME_MONO ) + +#define _WIN32_WINNT 0x501 +#define WINVER 0x501 + +#ifdef ID_DEDICATED +// dedicated sets windows version here +#define WIN32_LEAN_AND_MEAN +#else +#ifdef TOOL_DLL +// non-dedicated includes MFC and sets windows verion here +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // prevent auto literal to string conversion +#include "../tools/comafx/StdAfx.h" +#endif // TOOL_DLL +#endif // ID_DEDICATED + +#include +#include +#include + +#define DIRECTINPUT_VERSION 0x0800 +#define DIRECTSOUND_VERSION 0x0800 + +#include +#include +#include + +#endif // GAME_DLL +#endif // !Q4SDK + +#include // no malloc.h on mac or unix +#include // for qgl.h + +// RAVEN BEGIN +// bdube: for dual monitor support in tools +#ifndef GET_X_LPARAM +#define GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam)) +#endif +#ifndef GET_Y_LPARAM +#define GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam)) +#endif +// RAVEN END + +#undef FindText // stupid namespace poluting Microsoft monkeys + +#endif // _WINDOWS + +//----------------------------------------------------- + +#if !defined( _DEBUG ) && !defined( NDEBUG ) + // don't generate asserts + #define NDEBUG +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------- + +// non-portable system services +#include "../sys/sys_public.h" + +// id lib +#include "../idlib/Lib.h" + +#if !defined( Q4SDK ) && defined( __WITH_PB__ ) + #include "../punkbuster/pbcommon.h" +#endif + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +// jsinger: Serializable class support +#include "../serialization/Serializable.h" +#endif +// RAVEN END + +// framework +#include "../framework/BuildVersion.h" +#include "../framework/BuildDefines.h" +#include "../framework/licensee.h" +#include "../framework/CmdSystem.h" +#include "../framework/CVarSystem.h" +#include "../framework/Common.h" +#include "../framework/File.h" +#include "../framework/FileSystem.h" +#include "../framework/UsercmdGen.h" + +// decls +#include "../framework/declManager.h" +#include "../framework/declTable.h" +#include "../framework/declSkin.h" +#include "../framework/declEntityDef.h" +// RAVEN BEGIN +// jscott: not using +//#include "../framework/DeclFX.h" +//#include "../framework/DeclParticle.h" +// RAVEN END +#include "../framework/declAF.h" +#include "../framework/DeclPDA.h" +#include "../framework/DeclPlayerModel.h" +// RAVEN BEGIN +// jscott: new decl types +#include "../framework/declMatType.h" +#include "../framework/declLipSync.h" +#include "../framework/declPlayback.h" +// RAVEN END + +// We have expression parsing and evaluation code in multiple places: +// materials, sound shaders, and guis. We should unify them. +const int MAX_EXPRESSION_OPS = 4096; +const int MAX_EXPRESSION_REGISTERS = 4096; + +// Sanity check for any axis in bounds +const float MAX_BOUND_SIZE = 65536.0f; + +// renderer +#include "../renderer/qgl.h" +#include "../renderer/Cinematic.h" +#include "../renderer/Material.h" +#include "../renderer/Model.h" +#include "../renderer/ModelManager.h" +#include "../renderer/RenderSystem.h" +#include "../renderer/RenderWorld.h" + +// sound engine +#include "../sound/sound.h" + +// RAVEN BEGIN +// jscott: Effects system interface +#include "../bse/BSEInterface.h" +// RAVEN END + +// asynchronous networking +#include "../framework/async/NetworkSystem.h" + +// user interfaces +#include "../ui/ListGUI.h" +#include "../ui/UserInterface.h" + +// collision detection system +#include "../cm/CollisionModel.h" + +// AAS files and manager +#include "../aas/AASFile.h" +#include "../aas/AASFileManager.h" + +// game +#include "../game/Game.h" + +//----------------------------------------------------- + +#if defined( Q4SDK ) || ( defined( Q4_RECON_SEED ) && !defined( Q4_RECON_ENGINE_PRIVATE ) ) || defined( GAME_DLL ) || defined( GAME_MONO ) + +#ifdef GAME_MPAPI +#include "../mpgame/Game_local.h" +#else +#include "../game/Game_local.h" +#endif + +#if defined( Q4_RECON_SEED ) +#include "../framework/Session.h" +#endif + +#else + +#include "../framework/DemoChecksum.h" + +// framework +#include "../framework/Compressor.h" +#include "../framework/EventLoop.h" +#include "../framework/KeyInput.h" +#include "../framework/EditField.h" +#include "../framework/Console.h" +#include "../framework/DemoFile.h" +#include "../framework/Session.h" + +// asynchronous networking +#include "../framework/async/AsyncNetwork.h" + +// RAVEN BEGIN +#if !defined( Q4_RECON_SEED ) +#include "../tools/Tools.h" +#endif +// RAVEN END + +#endif /* !GAME_DLL */ + +// RAVEN BEGIN +// jsinger: add AutoPtr and text-to-binary compiler support +#include "AutoPtr.h" +#include "LexerFactory.h" +#include "TextCompiler.h" +// jsinger: AutoCrit.h contains classes which aid in code synchronization +// AutoAcquire.h contains a class that aids in thread acquisition of the direct3D device for xenon +// Both compile out completely if the #define's above are not present +#include "threads/AutoCrit.h" +// RAVEN END + +//----------------------------------------------------- + +#endif /* __cplusplus */ + +#endif /* !__PRECOMPILED_H__ */ diff --git a/src/idlib/rvHeap.cpp b/src/idlib/rvHeap.cpp new file mode 100644 index 0000000..6dcd3a2 --- /dev/null +++ b/src/idlib/rvHeap.cpp @@ -0,0 +1,1580 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// rvHeap.cpp - Heap object +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifdef _RV_MEM_SYS_SUPPORT + +//#define DETECT_MEM_OVERWRITES + +// Define some structures used by each heap +struct rvMemoryBlock_s +{ + rvMemoryBlock_s* m_prev; // previous block in memory (next is implied by address of memory block plus distToNextBlock) + dword m_dwordDistToNextBlock : NUM_MEMORY_BLOCK_SIZE_BITS; // distance, in double words (4 bytes), from the first byte of this rvMemoryBlock_s header to the first byte of the next rvMemoryBlock_s header + dword m_tag : NUM_TAG_BITS; // bits used to tag the type of allocation and state of memory block (zero for free) + + // GetDistToNextBlock + // + // returns: the distance to the next block, in bytes + ID_INLINE dword GetDistToNextBlock() const + { + return m_dwordDistToNextBlock << 2; + } + + // SetDistToNextBlock + // + // sets the distance to the next block, in bytes + ID_INLINE void SetDistToNextBlock( dword distBytes ) + { + assert( !(distBytes & 0x03) ); + m_dwordDistToNextBlock = distBytes >> 2; + } +}; + +struct rvFreeMemoryBlock_s : rvMemoryBlock_s +{ + rvFreeMemoryBlock_s* m_prevFree; + rvFreeMemoryBlock_s* m_nextFree; +}; + +static const dword rvHeapAlignmentPadding = 0xFFFFFFFF; // fixed value used for padding an allocation for alignment + +#ifdef DETECT_MEM_OVERWRITES +// a header and a trailer is stored at the front and rear of each allocation for +// the purposes of detecting if an allocation has been written past its bounds +static const uint OVERWRITE_HEADER_SIZE = 4; +static const uint OVERWRITE_TRAILER_SIZE = 4; +static const char rvHeapAllocHeader[OVERWRITE_HEADER_SIZE] = { '{', 'R', 'a', 'V' }; +static const char rvHeapAllocTrailer[OVERWRITE_TRAILER_SIZE] = { 'e', 'N', '1', '}' }; +#else +static const uint OVERWRITE_HEADER_SIZE = 0; +static const uint OVERWRITE_TRAILER_SIZE = 0; +#endif + +#define SIZE_TO_SMALL_TABLE_OFFSET(sizeBytes) ((((sizeBytes) + SMALL_BLOCK_ALIGN_SIZE_MASK) >> SMALL_BLOCK_ALIGN_SIZE_SHIFT) - 1) +#define SMALL_TABLE_OFFSET_TO_SIZE(tableOffset) (((tableOffset) + 1) << SMALL_BLOCK_ALIGN_SIZE_SHIFT) + +// rvHeap +// +// constructor +rvHeap::rvHeap( ) : + mDebugID(-1) +{ + // ResetValues(); do this in the Init() call instead (due to the fact that other constructors could call rvHeap::Init() before this constructor is called) +} + +// ~rvHeap +// +// destructor +rvHeap::~rvHeap( ) +{ + Shutdown(); +} + +// Init +// +// initializes this heap for use within the given arena, and with the given size limit that it can grow to +// +// heapArena heap arena that this heap is associated with +// maxHeapSizeBytes maximum number of bytes this heap can grow to +// flags flags describing the capabilities of this heap +void rvHeap::Init( rvHeapArena &heapArena, uint maxHeapSizeBytes, uint flags ) +{ + DWORD protection = PAGE_READWRITE; + DWORD allocType; + uint numPageTableBytes, numTablePages; + uint largeFreeRangeByteSize; + byte* pageTableData; + + ResetValues(); + + // create the critical section used by this heap + InitializeCriticalSection( &m_criticalSection ); + + // determine how many pages the virtual address range will consume + m_numPages = (maxHeapSizeBytes + PAGE_SIZE - 1) >> PAGE_SIZE_SHIFT; + m_maxHeapSizeBytes = m_numPages << PAGE_SIZE_SHIFT; + + m_heapRangeBytes = m_maxHeapSizeBytes + PAGE_SIZE; // add a page at the beginning for zero-length allocations (not part of page table and is never committed) + + assert(sizeof(dword) == 4); // assumed by following code + m_pageCommitStateArrayByteSize = ((m_numPages + 31) >> 5)*sizeof(dword); + + m_smallFreePageRangeByteSize = m_numPages*sizeof(freePageRange_t); + m_largeFreeRangeStorageSize = m_numPages/MAX_SMALL_PAGE_RANGE; + if ( m_largeFreeRangeStorageSize < 1 ) + { + m_largeFreeRangeStorageSize = 1; + } + largeFreeRangeByteSize = m_largeFreeRangeStorageSize*sizeof(freeLargePageRange_t); + + numPageTableBytes = m_pageCommitStateArrayByteSize; // allocate storage for the m_pageCommitStateTable array + numPageTableBytes += m_smallFreePageRangeByteSize; // allocate storage for the m_smallFreePageRangeTable array + numPageTableBytes += largeFreeRangeByteSize; // allocate storage for the m_largeFreeRangeStorage array + + numTablePages = (numPageTableBytes + PAGE_SIZE - 1) >> PAGE_SIZE_SHIFT; + numPageTableBytes = numTablePages << PAGE_SIZE_SHIFT; + + // reserve the range of virtual memory needed by this heap + if ( (flags & rvHeapFlagWriteCombine) != 0 ) + { + protection |= PAGE_WRITECOMBINE; + } + if ( (flags & rvHeapFlagNoCache) != 0 ) + { + protection |= PAGE_NOCACHE; + } + allocType = MEM_RESERVE; +#ifdef _XENON + if ( PAGE_SIZE_SHIFT == 16 ) + { + allocType |= MEM_LARGE_PAGES; + } + allocType |= MEM_NOZERO; +#endif + m_baseAddress = (byte *) VirtualAlloc( NULL, numPageTableBytes+m_heapRangeBytes, allocType, protection ); + if ( NULL == m_baseAddress ) + { + common->FatalError( "Unable to reserve desired size for heap.\n" ); + return; + } + m_zeroLengthAllocPage = m_baseAddress + numPageTableBytes; + m_heapStorageStart = m_zeroLengthAllocPage + PAGE_SIZE; + + // commit the initial block of that range for use by the page table + // Note: We want this to zero the memory for the page table init! + allocType = MEM_COMMIT; +#ifdef _XENON + if ( PAGE_SIZE_SHIFT == 16 ) + { + allocType |= MEM_LARGE_PAGES; + } +#endif + pageTableData = (byte *) VirtualAlloc( m_baseAddress, numPageTableBytes, allocType, PAGE_READWRITE ); + if ( NULL == pageTableData ) + { + common->FatalError( "Unable to commit pages needed for heap's page table.\n" ); + VirtualFree( m_baseAddress, 0, MEM_RELEASE ); + return; + } + m_committedSizeBytes = numPageTableBytes; + + m_pageCommitStateTable = (dword*) pageTableData; + pageTableData += m_pageCommitStateArrayByteSize; + + m_smallFreePageRangeTable = (freePageRange_t *) pageTableData; + pageTableData += m_smallFreePageRangeByteSize; + + m_largeFreeRangeStorage = (freeLargePageRange_t *) pageTableData; + BuildLargeFreeRangeReuseList(); + + // set up the information members + m_flags = flags; + + heapArena.InitHeap( *this ); +} + +// Shutdown +// +// releases this heap from use +void rvHeap::Shutdown( ) +{ + if ( m_arena != NULL ) + { + VirtualFree( m_baseAddress, 0, MEM_RELEASE ); + m_arena->ShutdownHeap( *this ); + + DeleteCriticalSection( &m_criticalSection ); + } + ResetValues(); +} + +// ResetValues +// +// resets the data members to their pre-initialized state +void rvHeap::ResetValues( ) +{ + m_arena = NULL; + m_next = NULL; + memset( &m_criticalSection, 0, sizeof(m_criticalSection) ); + m_baseAddress = NULL; + m_zeroLengthAllocPage = NULL; + m_heapStorageStart = NULL; + memset( m_smallFreeBlocks, 0, sizeof(m_smallFreeBlocks) ); + m_largeFreeBlocks = NULL; + m_pageCommitStateTable = NULL; + m_smallFreePageRangeTable = NULL; + memset( m_smallFreePageRangeLists, 0, sizeof(m_smallFreePageRangeLists) ); + m_largeFreePageRangeList = NULL; + m_largeFreeRangeStorage = NULL; + m_largeFreeRangeReuseList = NULL; + m_largeFreeRangeStorageSize = 0; + m_numPages = 0; + m_heapRangeBytes = 0; + m_committedSizeBytes = 0; + m_maxHeapSizeBytes = 0; + m_numBytesRequested = 0; + m_numBytesAllocated = 0; + memset(m_allocatedBytesByTag, 0, sizeof(m_allocatedBytesByTag)); + memset(m_peekAllocatedBytesByTag, 0, sizeof(m_peekAllocatedBytesByTag)); + memset(m_numAllocationsByTag, 0, sizeof(m_numAllocationsByTag)); + m_numAllocations = 0; + m_flags = 0; + m_numZeroLengthAllocations = 0; + memset(m_name, 0, sizeof(m_name)); +} + +// BuildLargeFreeRangeReuseList +// +// Builds the m_largeFreeRangeReuseList linked-list from the m_largeFreeRangeStorage[] array. +void rvHeap::BuildLargeFreeRangeReuseList( ) +{ + uint nOffset; + + m_largeFreeRangeReuseList = m_largeFreeRangeStorage + 1; + for ( nOffset = 1; nOffset < m_largeFreeRangeStorageSize-1; nOffset++ ) + { + m_largeFreeRangeStorage[ nOffset ].m_nextFree = &m_largeFreeRangeStorage[ nOffset + 1 ]; + } + m_largeFreeRangeStorage[ m_largeFreeRangeStorageSize-1 ].m_nextFree = NULL; + + m_largeFreePageRangeList = m_largeFreeRangeStorage; + m_largeFreePageRangeList->m_nextFree = NULL; + m_largeFreePageRangeList->m_firstPageOffset = 0; + m_largeFreePageRangeList->m_numContiguousPages = m_numPages; +} + +// AllocateMemory +// +// allocates memory from this heap. +// +// sizeBytes Size, in bytes, of desired allocation. +// allocationTag Tag stored with this allocation (describing type of allocation) +// align16Flag true if allocation must be aligned on a 16-byte boundary, false otherwise. +// +// returns: a pointer to the allocated memory, NULL if enough memory does not exist for allocation +byte *rvHeap::AllocateMemory( uint sizeBytes, int allocationTag, bool align16Flag ) +{ +// align16Flag=true; + byte *allocation; + rvMemoryBlock_s *allocatedBlock; + uint actualSizeBytes, extraBytes, distToNextBlock; + + if ( allocationTag == MA_NONE || allocationTag >= MA_DO_NOT_USE ) + { + allocationTag = MA_DEFAULT; + } + + EnterHeapCriticalSection( ); + + // check for a zero-length allocation - returns a valid virtual address that will page fault should + // it be written to, or read from (uncommitted page) + if ( !sizeBytes ) + { + // NOTE: The following is an attempt to provide an address within the range of this heap + // that takes up no space (with the exception of a single virtual, uncommitted page). + // The uniqueness of the address returned here is not guaranteed and is not aligned. + m_numZeroLengthAllocations++; + m_numAllocations++; + allocation = (byte *) (m_zeroLengthAllocPage + (m_numZeroLengthAllocations & PAGE_SIZE_MASK)); + + ExitHeapCriticalSection( ); + return allocation; + } + + // determine how big the allocation is really going to need to be + actualSizeBytes = sizeof(rvMemoryBlock_s) + sizeBytes + OVERWRITE_HEADER_SIZE + OVERWRITE_TRAILER_SIZE; + extraBytes = OVERWRITE_HEADER_SIZE + OVERWRITE_TRAILER_SIZE; + if ( align16Flag ) + { + actualSizeBytes += 3*sizeof(rvHeapAlignmentPadding); // for alignment + extraBytes += 3*sizeof(rvHeapAlignmentPadding); + } + + if ( actualSizeBytes > PAGE_SIZE ) + { + // if greater than a page, and the tail just barely crosses a page boundary, + // ask for a size that is a multiple of free memory block structure (so there is room for such a structure at the end) + if ( (actualSizeBytes & PAGE_SIZE_MASK) < sizeof(rvFreeMemoryBlock_s) ) + { + actualSizeBytes = (actualSizeBytes + (uint) sizeof(rvFreeMemoryBlock_s) - 1) & ~((uint) sizeof(rvFreeMemoryBlock_s) - 1); + } + } + else if ( actualSizeBytes < sizeof(rvFreeMemoryBlock_s) ) + { + actualSizeBytes = sizeof(rvFreeMemoryBlock_s); + + // make sure that the size is 4-byte aligned + actualSizeBytes = ( actualSizeBytes + 3 ) & ~3; + } + + // make sure that the size is 4-byte aligned + actualSizeBytes = ( actualSizeBytes + 3 ) & ~3; + + if ( actualSizeBytes > MAX_SINGLE_ALLOCATION_SIZE ) + { + // beyond what any heap can handle + ExitHeapCriticalSection( ); + return NULL; + } + + if ( actualSizeBytes <= MAX_SMALL_BLOCK_SIZE ) + { + // perform a small block allocation + allocatedBlock = AllocateMemorySmallBlock( actualSizeBytes ); + } + else + { + // perform a large block allocation + allocatedBlock = AllocateMemoryLargeBlock( actualSizeBytes ); + } + + if ( NULL == allocatedBlock ) + { + ExitHeapCriticalSection( ); + return NULL; + } + + distToNextBlock = allocatedBlock->GetDistToNextBlock(); + + assert( allocationTag < MA_MAX ); + allocatedBlock->m_tag = (dword) allocationTag; + m_allocatedBytesByTag[ allocationTag ] += distToNextBlock; + if ( m_allocatedBytesByTag[ allocationTag ] > m_peekAllocatedBytesByTag[ allocationTag ] ) + { + m_peekAllocatedBytesByTag[ allocationTag ] = m_allocatedBytesByTag[ allocationTag ]; + } + m_numAllocationsByTag[ allocationTag ]++; + + m_numBytesAllocated += distToNextBlock; + m_numBytesRequested += (distToNextBlock - sizeof(rvMemoryBlock_s) - extraBytes); + m_numAllocations++; + + allocation = (byte *) allocatedBlock + sizeof(rvMemoryBlock_s); + +#ifdef DETECT_MEM_OVERWRITES + memcpy( allocation, rvHeapAllocHeader, OVERWRITE_HEADER_SIZE ); + allocation += OVERWRITE_HEADER_SIZE; + byte *nextBlockAddress = (byte*) allocatedBlock + distToNextBlock; + nextBlockAddress -= OVERWRITE_TRAILER_SIZE; + memcpy( nextBlockAddress, rvHeapAllocTrailer, OVERWRITE_TRAILER_SIZE ); +#endif + + if ( align16Flag ) + { + while ( ((ulong) allocation & 0x0F) != 0 ) + { + *(dword*)allocation = rvHeapAlignmentPadding; + allocation += sizeof(rvHeapAlignmentPadding); + } + } + + ExitHeapCriticalSection( ); + return allocation; +} + +// AllocateMemorySmallBlock +// +// perform a small block allocation - try to use an existing free block pointed to +// by the table. +// +// returns: a pointer to the allocation header in front of the newly allocated block of memory, +// NULL if enough memory does not exist for allocation +rvMemoryBlock_s *rvHeap::AllocateMemorySmallBlock( uint sizeBytes ) +{ + uint smallBlockTableOffset, stopOffset; //, largerBlockOffset; + + smallBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(sizeBytes); + assert(smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES); + +#if 1 + stopOffset = smallBlockTableOffset + NUM_SMALL_BLOCK_TABLE_ENTRIES; +#else + stopOffset = smallBlockTableOffset << 1; +#endif + if ( stopOffset > m_largestFreeSmallBlockOffset ) + { + stopOffset = m_largestFreeSmallBlockOffset; + } + + while ( smallBlockTableOffset < stopOffset ) + { + // check to see if there is a block that matches exactly + if ( m_smallFreeBlocks[smallBlockTableOffset] != NULL ) + { + // there is a block that is just the right size, remove from free list and return it. + return AllocateBlockFromTable( smallBlockTableOffset, sizeBytes ); + } + +/* + NOTE: The following code is experimental and seems to result in greater fragmentation. + + // check to see if there is a block that matches at some multiple of the exact size + largerBlockOffset = smallBlockTableOffset << 1; + while ( largerBlockOffset <= m_largestFreeSmallBlockOffset ) + { + if ( m_smallFreeBlocks[largerBlockOffset] != NULL ) + { + // found a larger block to allocate from + return AllocateBlockFromTable( largerBlockOffset, sizeBytes ); + } + largerBlockOffset = largerBlockOffset << 1; + } +*/ + + smallBlockTableOffset++; + } + + // an existing small block could not be found, try a larger block + return AllocateMemoryLargeBlock( sizeBytes ); +} + +// AllocateBlockFromTable +// +// Allocates the a small memory block from the free block table +// and partitions it, if necessary, into an allocated chunk +// and a free chunk (which remains in the m_smallFreeBlocks[] table). +// +// returns: a pointer to the allocation header in front of the newly allocated block of memory, +// NULL if enough memory does not exist for allocation +rvMemoryBlock_s *rvHeap::AllocateBlockFromTable( uint smallBlockTableOffset, uint sizeBytes ) +{ + rvFreeMemoryBlock_s* allocatedBlockHeader, *newFreeBlock; + uint originalBlockSize, newBlockSize, newBlockTableOffset; + + allocatedBlockHeader = m_smallFreeBlocks[smallBlockTableOffset]; + assert( sizeBytes <= allocatedBlockHeader->GetDistToNextBlock() ); + + originalBlockSize = allocatedBlockHeader->GetDistToNextBlock(); + assert( originalBlockSize == SMALL_TABLE_OFFSET_TO_SIZE(smallBlockTableOffset) ); + + // remove the free block from the table + m_smallFreeBlocks[smallBlockTableOffset] = m_smallFreeBlocks[smallBlockTableOffset]->m_nextFree; + if ( m_smallFreeBlocks[smallBlockTableOffset] != NULL ) + { + m_smallFreeBlocks[smallBlockTableOffset]->m_prevFree = NULL; + } + + // see if the requested size essentially covers the entire free block + if ( sizeBytes + sizeof(rvFreeMemoryBlock_s) <= allocatedBlockHeader->GetDistToNextBlock() ) + { + // this block is large enough to be split into two - an allocated chunk and a free chunk. + // re-add the now smaller free chunk back into smallFreeBlocks[] table + newBlockSize = allocatedBlockHeader->GetDistToNextBlock() - sizeBytes; + + newFreeBlock = (rvFreeMemoryBlock_s*) ((byte *) allocatedBlockHeader + sizeBytes); + + newFreeBlock->m_prev = allocatedBlockHeader; + newFreeBlock->SetDistToNextBlock( newBlockSize ); + newFreeBlock->m_tag = MA_NONE; + + newBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(newBlockSize); + if ( m_smallFreeBlocks[newBlockTableOffset] != NULL ) + { + m_smallFreeBlocks[newBlockTableOffset]->m_prevFree = newFreeBlock; + } + + newFreeBlock->m_prevFree = NULL; + newFreeBlock->m_nextFree = m_smallFreeBlocks[newBlockTableOffset]; + m_smallFreeBlocks[newBlockTableOffset] = newFreeBlock; + + FixUpNextBlocksPrev( (byte *) newFreeBlock, newBlockSize ); + + allocatedBlockHeader->SetDistToNextBlock( sizeBytes ); + } + + allocatedBlockHeader->m_tag = MA_DEFAULT; + + // check to see if the largest free small block has moved down + TestLargestFreeSmallBlockOffset( smallBlockTableOffset ); + + return allocatedBlockHeader; +} + +// TestLargestFreeSmallBlockOffset +// +// checks to see if the largest free small block has moved down +void rvHeap::TestLargestFreeSmallBlockOffset( uint smallBlockTableOffset ) +{ + if ( smallBlockTableOffset >= m_largestFreeSmallBlockOffset ) { + + // find the next largest available small block (if table entry is NULL) + uint nextAvailableBlock = smallBlockTableOffset; + while ( nextAvailableBlock > 0 && NULL == m_smallFreeBlocks[nextAvailableBlock] ) + { + nextAvailableBlock--; + } + m_largestFreeSmallBlockOffset = nextAvailableBlock; + } +} + +// AllocateMemoryLargeBlock +// +// perform a large block allocation - try to use an existing free block from within +// the large free block linked-list. +// +// returns: a pointer to the allocation header in front of the newly allocated block of memory, +// NULL if enough memory does not exist for allocation +rvMemoryBlock_s *rvHeap::AllocateMemoryLargeBlock( uint sizeBytes ) +{ + rvFreeMemoryBlock_s **prevNextBlock, *curFreeBlock, *freeBlock; + rvMemoryBlock_s *block = NULL; + byte *newPageStart; + uint numPages, totalAllocated = 0, remainingSize; + + prevNextBlock = &m_largeFreeBlocks; + curFreeBlock = m_largeFreeBlocks; + while ( curFreeBlock != NULL ) + { + if ( sizeBytes <= curFreeBlock->GetDistToNextBlock() ) + { + // we found a block that is big enough, remove it from the large free block list + *prevNextBlock = curFreeBlock->m_nextFree; + if ( curFreeBlock->m_nextFree != NULL ) + { + curFreeBlock->m_nextFree->m_prevFree = curFreeBlock->m_prevFree; + } + totalAllocated = curFreeBlock->GetDistToNextBlock(); + block = (rvMemoryBlock_s *) curFreeBlock; + break; + } + + prevNextBlock = &curFreeBlock->m_nextFree; + curFreeBlock = curFreeBlock->m_nextFree; + } + + if ( NULL == block ) + { + // an existing block could not be found, commit a new page range for use + numPages = (sizeBytes + PAGE_SIZE - 1) >> PAGE_SIZE_SHIFT; + newPageStart = (byte *) PageCommit( numPages ); + if ( NULL == newPageStart ) + { + // out of memory error + return NULL; + } + + // break the new page up into an allocated chunk (returned) and a free chunk (added to m_smallFreeBlocks[] or m_largeFreeBlocks) + block = (rvMemoryBlock_s *) newPageStart; + block->m_prev = NULL; + + totalAllocated = numPages << PAGE_SIZE_SHIFT; + } + + block->m_tag = MA_DEFAULT; + + remainingSize = totalAllocated - sizeBytes; + if ( remainingSize < sizeof(rvFreeMemoryBlock_s) ) + { + // it is not worth creating a free block for what remains of last page + block->SetDistToNextBlock( totalAllocated ); + } + else + { + // there is a enough space left at the end of the last page to generate + // a free block + block->SetDistToNextBlock( sizeBytes ); + freeBlock = (rvFreeMemoryBlock_s *) ((byte *) block + sizeBytes); + assert( !((uint) freeBlock & 0x03) ); + + if ( ((uint) block >> PAGE_SIZE_SHIFT) == (((uint) block + sizeBytes) >> PAGE_SIZE_SHIFT) ) + { + // new free block is on the same page + freeBlock->m_prev = block; + } + else + { + // new free block is on a different page + freeBlock->m_prev = NULL; + } + freeBlock->SetDistToNextBlock( remainingSize ); + freeBlock->m_tag = MA_NONE; + + FixUpNextBlocksPrev( (byte *) freeBlock, remainingSize ); + + AddFreeBlock( freeBlock ); + } + return block; +} + +// FixUpNextBlocksPrev +// +// Fixes up the previous pointer of the memory block that lies immediately +// past the given newBlock, if it remains on the same page. +void rvHeap::FixUpNextBlocksPrev( byte *newBlock, uint blockSize ) +{ + rvMemoryBlock_s* nextBlockPastNew; + + if ( ((uint) newBlock >> PAGE_SIZE_SHIFT) == ((uint) (newBlock + blockSize) >> PAGE_SIZE_SHIFT) ) + { + // the next block remains on the same page, the previous pointer must be fixed up + nextBlockPastNew = (rvMemoryBlock_s*) (newBlock + blockSize); + nextBlockPastNew->m_prev = (rvMemoryBlock_s*) newBlock; + } +} + +// AddFreeBlock +// +// Adds the given free block to the small allocation table or large allocation list. +void rvHeap::AddFreeBlock( rvFreeMemoryBlock_s *freeBlock ) +{ + uint smallBlockTableOffset; + + if ( freeBlock->GetDistToNextBlock() <= MAX_SMALL_BLOCK_SIZE ) + { + // add the block to the small free block table + smallBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(freeBlock->GetDistToNextBlock()); + assert(smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES); + + if ( m_smallFreeBlocks[smallBlockTableOffset] != NULL ) + { + m_smallFreeBlocks[smallBlockTableOffset]->m_prevFree = freeBlock; + } + else if ( smallBlockTableOffset > m_largestFreeSmallBlockOffset ) + { + m_largestFreeSmallBlockOffset = smallBlockTableOffset; + } + + freeBlock->m_prevFree = NULL; + freeBlock->m_nextFree = m_smallFreeBlocks[smallBlockTableOffset]; + m_smallFreeBlocks[smallBlockTableOffset] = freeBlock; + } + else + { + if ( m_largeFreeBlocks != NULL ) + { + m_largeFreeBlocks->m_prevFree = freeBlock; + } + freeBlock->m_prevFree = NULL; + freeBlock->m_nextFree = m_largeFreeBlocks; + m_largeFreeBlocks = freeBlock; + } +} + +// RemoveFreeBlock +// +// Removes the given block from the small allocation table or large allocation list. +void rvHeap::RemoveFreeBlock( rvFreeMemoryBlock_s *freeBlock ) +{ + uint smallBlockTableOffset; + + if ( freeBlock->GetDistToNextBlock() <= MAX_SMALL_BLOCK_SIZE ) + { + if ( NULL == freeBlock->m_prevFree ) + { + smallBlockTableOffset = SIZE_TO_SMALL_TABLE_OFFSET(freeBlock->GetDistToNextBlock()); + + assert( smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES ); + assert( m_smallFreeBlocks[smallBlockTableOffset] == freeBlock ); + + m_smallFreeBlocks[smallBlockTableOffset] = freeBlock->m_nextFree; + + // check to see if the largest free small block has moved down + TestLargestFreeSmallBlockOffset( smallBlockTableOffset ); + } + else + { + freeBlock->m_prevFree->m_nextFree = freeBlock->m_nextFree; + } + } + else + { + if ( NULL == freeBlock->m_prevFree ) + { + assert( m_largeFreeBlocks == freeBlock ); + + m_largeFreeBlocks = freeBlock->m_nextFree; + } + else + { + freeBlock->m_prevFree->m_nextFree = freeBlock->m_nextFree; + } + } + + if ( freeBlock->m_nextFree != NULL ) + { + freeBlock->m_nextFree->m_prevFree = freeBlock->m_prevFree; + } +} + +// Free +// +// free memory +void rvHeap::Free( void *p ) +{ + rvMemoryBlock_s *block, *prevBlock, *nextBlock; + rvFreeMemoryBlock_s *freeBlock; + byte *nextBlockAddress, *allocation; + byte *pageAddress; + uint numPages, remainingSize, extraBytes, allocationTag, distToNextBlock; + + EnterHeapCriticalSection( ); + + assert( m_numAllocations > 0 ); + assert( p != NULL ); + assert( DoesAllocBelong( p ) ); + + m_numAllocations--; + + if ( p < m_zeroLengthAllocPage+PAGE_SIZE ) + { + // this is a zero-length allocation + assert(m_numZeroLengthAllocations>0); + m_numZeroLengthAllocations--; +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + TestFreeBlockValidity(); +#endif + ExitHeapCriticalSection( ); + return; + } + + assert( !((uint) p & 0x03) ); + + // back up over any heap alignment padding + allocation = (byte *) p; + extraBytes = 0; + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + } + } + extraBytes += 3*sizeof(rvHeapAlignmentPadding); + } + + // make sure that the overwrite header and trailer are intact +#ifdef DETECT_MEM_OVERWRITES + allocation -= OVERWRITE_HEADER_SIZE; + extraBytes += OVERWRITE_HEADER_SIZE + OVERWRITE_TRAILER_SIZE; + if ( memcmp( allocation, rvHeapAllocHeader, OVERWRITE_HEADER_SIZE ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block header overwrite (0x%x)", (dword) allocation ); + } +#endif + + // restore the memory block pointer + block = (rvMemoryBlock_s *) (allocation - sizeof(rvMemoryBlock_s)); + +#ifdef DETECT_MEM_OVERWRITES + nextBlockAddress = (byte*) block + block->GetDistToNextBlock(); + nextBlockAddress -= OVERWRITE_TRAILER_SIZE; + if ( memcmp( nextBlockAddress, rvHeapAllocTrailer, OVERWRITE_TRAILER_SIZE ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block trailer overwrite (0x%x)", (dword) nextBlockAddress ); + } +#endif + + pageAddress = (byte *) (((dword) allocation) & ~PAGE_SIZE_MASK); + + if ( ( block->m_prev != NULL && (byte *) block->m_prev < pageAddress ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block header corrupted (0x%x)", (dword) allocation ); + } + + if ( block->m_tag == MA_NONE ) + { + idLib::common->FatalError( "rvHeap::Free: attempt to free allocation that is already freed (0x%x)", (dword) allocation ); + } + + assert( (byte*) block >= m_heapStorageStart && ((byte*) block + block->GetDistToNextBlock()) <= (m_heapStorageStart + m_maxHeapSizeBytes) ); + + distToNextBlock = block->GetDistToNextBlock(); + assert( distToNextBlock <= m_numBytesAllocated ); + m_numBytesAllocated -= distToNextBlock; + m_numBytesRequested -= (distToNextBlock - sizeof(rvMemoryBlock_s) - extraBytes); + + allocationTag = block->m_tag; + assert( distToNextBlock <= m_allocatedBytesByTag[ allocationTag ] && m_numAllocationsByTag[ allocationTag ] > 0 ); + m_allocatedBytesByTag[ allocationTag ] -= distToNextBlock; + m_numAllocationsByTag[ allocationTag ]--; + + if ( block->GetDistToNextBlock() > PAGE_SIZE ) + { + // this allocation that is being freed spans multiple pages - uncommit the ones + // before the last one + numPages = block->GetDistToNextBlock() >> PAGE_SIZE_SHIFT; + + remainingSize = block->GetDistToNextBlock() - (numPages << PAGE_SIZE_SHIFT); + + PageUncommit( pageAddress, numPages ); + + if ( !remainingSize ) + { + // there are no remaining blocks +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + TestFreeBlockValidity(); +#endif + ExitHeapCriticalSection( ); + return; + } + + pageAddress += (numPages << PAGE_SIZE_SHIFT); + + block = (rvMemoryBlock_s *) pageAddress; + block->m_prev = NULL; + block->SetDistToNextBlock( remainingSize ); + block->m_tag = MA_NONE; + + FixUpNextBlocksPrev( (byte *) block, remainingSize ); + } + + nextBlockAddress = (byte*) block + block->GetDistToNextBlock(); + + // mark the block as free + block->m_tag = MA_NONE; + + // determine if the block we are freeing should be merged with the previous block + prevBlock = block->m_prev; + if ( prevBlock != NULL ) + { + if ( prevBlock->m_tag == MA_NONE ) + { + // the previous block is free, merge the block we are freeing to its previous neighbor + RemoveFreeBlock( (rvFreeMemoryBlock_s *) prevBlock ); + prevBlock->SetDistToNextBlock( prevBlock->GetDistToNextBlock() + block->GetDistToNextBlock() ); + block = prevBlock; + + FixUpNextBlocksPrev( (byte *) prevBlock, prevBlock->GetDistToNextBlock() ); + } + } + + // determine if the block we are freeing should be merged with the next block + if ( nextBlockAddress < pageAddress+PAGE_SIZE ) + { + nextBlock = (rvMemoryBlock_s *) nextBlockAddress; + if ( nextBlock->m_tag == MA_NONE ) + { + // the next block is free, merge the block we are freeing to its next neighbor + RemoveFreeBlock( (rvFreeMemoryBlock_s *) nextBlock ); + + block->SetDistToNextBlock( block->GetDistToNextBlock() + nextBlock->GetDistToNextBlock() ); + + FixUpNextBlocksPrev( (byte *) block, block->GetDistToNextBlock() ); + } + } + + // determine if block being freed should be added to small allocation table, large + // allocation list, or if the page itself should be uncommitted + freeBlock = (rvFreeMemoryBlock_s *) block; + if ( freeBlock->GetDistToNextBlock() < PAGE_SIZE ) + { + AddFreeBlock( freeBlock ); + } + else + { + // this free block has reached the size of an entire page, uncommit it + PageUncommit( pageAddress, 1 ); + } + +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + TestFreeBlockValidity(); +#endif + + ExitHeapCriticalSection( ); +} + +// Msize +// +// returns: the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). +int rvHeap::Msize( void *p ) +{ + rvMemoryBlock_s *block; + byte *allocation; + dword size; + + EnterHeapCriticalSection( ); + + assert( m_numAllocations > 0 ); + assert( p != NULL ); + assert( DoesAllocBelong( p ) ); + + if ( p < m_zeroLengthAllocPage+PAGE_SIZE ) + { + // this is a zero-length allocation + ExitHeapCriticalSection( ); + return 0; + } + + assert( !((uint) p & 0x03) ); + + // back up over any heap alignment padding + allocation = (byte *) p; + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + if ( *((dword*) allocation - 1) == rvHeapAlignmentPadding ) + { + allocation -= sizeof(rvHeapAlignmentPadding); + } + } + } + + // make sure that the overwrite header and trailer are intact +#ifdef DETECT_MEM_OVERWRITES + allocation -= OVERWRITE_HEADER_SIZE; + if ( memcmp( allocation, rvHeapAllocHeader, OVERWRITE_HEADER_SIZE ) ) + { + idLib::common->FatalError( "rvHeap::Free: memory block header overwrite (0x%x)", (dword) allocation ); + } +#endif + + // restore the memory block pointer + block = (rvMemoryBlock_s *) (allocation - sizeof(rvMemoryBlock_s)); + + size = block->GetDistToNextBlock(); + size -= ((dword)p-(dword)block) + OVERWRITE_TRAILER_SIZE; + + ExitHeapCriticalSection( ); + + return size; +} + +// FreeAll +// +// frees all the allocations from this heap (and decommits all pages) +void rvHeap::FreeAll( ) +{ + if ( NULL == m_baseAddress ) + { + return; + } + + EnterHeapCriticalSection( ); + + VirtualFree( m_heapStorageStart, m_maxHeapSizeBytes, MEM_DECOMMIT ); // this decommits all of the physical pages (frees physical memory), but leaves the virtual address range reserved to this heap + + memset( m_smallFreeBlocks, 0, sizeof(rvFreeMemoryBlock_s*)*NUM_SMALL_BLOCK_TABLE_ENTRIES ); + m_largeFreeBlocks = NULL; + + m_committedSizeBytes = 0; + m_numBytesAllocated = 0; + m_numBytesRequested = 0; + + m_numAllocations = 0; + m_numZeroLengthAllocations = 0; + + m_largestFreeSmallBlockOffset = 0; + + memset( m_pageCommitStateTable, 0, m_pageCommitStateArrayByteSize ); + memset( m_smallFreePageRangeTable, 0, m_smallFreePageRangeByteSize ); + BuildLargeFreeRangeReuseList( ); + + ExitHeapCriticalSection( ); +} + +// PageCommit +// +// commits the given number of contiguous pages to physical memory +// (actually allocates the physical pages). +// +// returns: pointer to the first virtual address of the first committed page, +// NULL if commit failed. +void *rvHeap::PageCommit( uint numDesiredPages ) +{ + freePageRange_t *freePageRange; + freeLargePageRange_t *freeLargePageRange, **prevNextLargePageRange; + uint listOffset, tableOffset, numRemainingPages, remainingPagesOffset, remainingListOffset; + + if ( numDesiredPages <= MAX_SMALL_PAGE_RANGE ) + { + // the desired range of contiguous pages is considered "small" and the retrieval + // of such a range is accelerated by the m_smallFreePageRangeLists[] table + listOffset = numDesiredPages - 1; + do + { + if ( m_smallFreePageRangeLists[listOffset] != NULL ) + { + // we have found a contiguous range of pages that will hold the desired amount + freePageRange = m_smallFreePageRangeLists[listOffset]; + m_smallFreePageRangeLists[listOffset] = m_smallFreePageRangeLists[listOffset]->m_nextFree; + + // determine the starting table offset of the page range (starting page number) + tableOffset = freePageRange - m_smallFreePageRangeTable; + + // re-add any remainder back into the m_smallFreePageRangeTable[] + numRemainingPages = listOffset + 1 - numDesiredPages; + if ( numRemainingPages > 0 ) + { + remainingListOffset = numRemainingPages - 1; + remainingPagesOffset = tableOffset + numDesiredPages; + m_smallFreePageRangeTable[ remainingPagesOffset ].m_nextFree = m_smallFreePageRangeLists[ remainingListOffset ]; + m_smallFreePageRangeLists[ remainingListOffset ] = &m_smallFreePageRangeTable[ remainingPagesOffset ]; + } + + // actually commit the pages to physical memory and mark the pages as committed within the m_pageCommitStateTable[] + return CommitPageRange( tableOffset, numDesiredPages ); + } + + listOffset++; + } + while ( listOffset < MAX_SMALL_PAGE_RANGE ); + } + + // try find a suitable contiguous range of pages within the m_largeFreePageRangeList linked list + freeLargePageRange = m_largeFreePageRangeList; + prevNextLargePageRange = &m_largeFreePageRangeList; + while ( freeLargePageRange != NULL ) + { + if ( freeLargePageRange->m_numContiguousPages >= numDesiredPages ) + { + // found a block that is big enough - split it if necessary + tableOffset = freeLargePageRange->m_firstPageOffset; + numRemainingPages = freeLargePageRange->m_numContiguousPages - numDesiredPages; + remainingPagesOffset = tableOffset + numDesiredPages; + + if ( numRemainingPages <= MAX_SMALL_PAGE_RANGE ) + { + // the freeLargePageRange_t structure is no longer needed in the m_largeFreePageRangeList list, remove it + *prevNextLargePageRange = freeLargePageRange->m_nextFree; + freeLargePageRange->m_nextFree = m_largeFreeRangeReuseList; + m_largeFreeRangeReuseList = freeLargePageRange; + + if ( numRemainingPages > 0 ) + { + // add the remainder into the m_smallFreePageRangeTable[] + remainingListOffset = numRemainingPages - 1; + m_smallFreePageRangeTable[ remainingPagesOffset ].m_nextFree = m_smallFreePageRangeLists[ remainingListOffset ]; + m_smallFreePageRangeLists[ remainingListOffset ] = &m_smallFreePageRangeTable[ remainingPagesOffset ]; + } + } + else + { + // add the remainder back into the m_largeFreePageRangeList linked-list (NOTE: structure is still part of list) + freeLargePageRange->m_firstPageOffset = remainingPagesOffset; + freeLargePageRange->m_numContiguousPages = numRemainingPages; + } + + return CommitPageRange( tableOffset, numDesiredPages ); + } + + prevNextLargePageRange = &freeLargePageRange->m_nextFree; + freeLargePageRange = freeLargePageRange->m_nextFree; + } + + return NULL; +} + +// PageUncommit +// +// uncommits the given range of contiguous pages back to physical memory +// (actually frees the physical pages). +void rvHeap::PageUncommit( byte *pageAddress, uint numPages ) +{ + freeLargePageRange_t *freeLargePageRange = NULL; + uint startPageOffset, prevPageOffset, nextPageOffset, listOffset; + uint curDWordOffset, freeBlockPageCount, largeRangeCount, orgStartPageOffset, orgNumPages; + + pageAddress = (byte *) (((dword) pageAddress) & ~PAGE_SIZE_MASK); + + startPageOffset = (uint) (pageAddress - m_heapStorageStart) >> PAGE_SIZE_SHIFT; + + orgStartPageOffset = startPageOffset; + orgNumPages = numPages; + + // determine if there is a free block of pages immediately previous and/or immediately following + // this newly freed block of pages - merge into one big block if we can. + if ( startPageOffset > 0 ) + { + prevPageOffset = startPageOffset - 1; + curDWordOffset = prevPageOffset >> 5; + if ( !(m_pageCommitStateTable[ curDWordOffset ] & (1 << (prevPageOffset & 0x1F))) ) + { + // the previous block is free, so now we need to determine what linked-list it + // is on (how big is the continuous block of pages) + freeBlockPageCount = 1; + while ( prevPageOffset > 0 && freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + curDWordOffset = (prevPageOffset - 1) >> 5; + if ( (m_pageCommitStateTable[ curDWordOffset ] & (1 << ((prevPageOffset - 1) & 0x1F))) != 0 ) + { + break; + } + prevPageOffset--; + freeBlockPageCount++; + } + + if ( freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + RemoveFromSmallFreeRangeList( prevPageOffset, freeBlockPageCount ); + startPageOffset = prevPageOffset; + numPages += freeBlockPageCount; + } + else + { + RemoveFromLargeFreeRangeList( prevPageOffset, startPageOffset, largeRangeCount ); + numPages += largeRangeCount; + } + } + } + + if ( startPageOffset + numPages < m_numPages ) + { + nextPageOffset = startPageOffset + numPages; + curDWordOffset = nextPageOffset >> 5; + if ( !(m_pageCommitStateTable[ curDWordOffset ] & (1 << (nextPageOffset & 0x1F))) ) + { + // the next block is free, so now we need to determine what linked-list it + // is on (how big is the continuous block of pages) + freeBlockPageCount = 1; + while ( nextPageOffset < m_numPages && freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + nextPageOffset++; + curDWordOffset = nextPageOffset >> 5; + if ( (m_pageCommitStateTable[ curDWordOffset ] & (1 << (nextPageOffset & 0x1F))) != 0 ) + { + break; + } + freeBlockPageCount++; + } + + if ( freeBlockPageCount <= MAX_SMALL_PAGE_RANGE ) + { + RemoveFromSmallFreeRangeList( startPageOffset + numPages, freeBlockPageCount ); + numPages += freeBlockPageCount; + } + else + { + RemoveFromLargeFreeRangeList( startPageOffset + numPages, nextPageOffset, largeRangeCount ); + numPages += largeRangeCount; + } + } + } + + if ( numPages <= MAX_SMALL_PAGE_RANGE ) + { + // add the uncommitted page block into the m_smallFreePageRangeTable[] + listOffset = numPages - 1; + m_smallFreePageRangeTable[ startPageOffset ].m_nextFree = m_smallFreePageRangeLists[ listOffset ]; + m_smallFreePageRangeLists[ listOffset ] = &m_smallFreePageRangeTable[ startPageOffset ]; + } + else + { + // add the uncommitted page block onto the m_largeFreePageRangeList linked-list + assert( m_largeFreeRangeReuseList != NULL ); + freeLargePageRange = m_largeFreeRangeReuseList; + m_largeFreeRangeReuseList = m_largeFreeRangeReuseList->m_nextFree; + + freeLargePageRange->m_nextFree = m_largeFreePageRangeList; + m_largeFreePageRangeList = freeLargePageRange; + + freeLargePageRange->m_firstPageOffset = startPageOffset; + freeLargePageRange->m_numContiguousPages = numPages; + } + + // actually perform the decommit of the pages and clear the bits in the page table + UncommitPageRange( orgStartPageOffset, orgNumPages ); +} + +// RemoveFromSmallFreeRangeList +// +// Removes the given page range from the m_smallFreePageRangeLists[] +void rvHeap::RemoveFromSmallFreeRangeList( uint pageOffset, uint freeBlockPageCount ) +{ + freePageRange_t *freePageRange, **prevNextFreePageRange; + uint curOffset; + + assert( freeBlockPageCount <= MAX_SMALL_PAGE_RANGE && freeBlockPageCount > 0 ); + + prevNextFreePageRange = &m_smallFreePageRangeLists[ freeBlockPageCount - 1 ]; + freePageRange = m_smallFreePageRangeLists[ freeBlockPageCount - 1 ]; + while ( freePageRange != NULL ) + { + curOffset = (uint) (freePageRange - m_smallFreePageRangeTable); + if ( curOffset == pageOffset ) + { + *prevNextFreePageRange = freePageRange->m_nextFree; + return; + } + prevNextFreePageRange = &freePageRange->m_nextFree; + freePageRange = freePageRange->m_nextFree; + } + assert(0); // we should not reach this +} + +// RemoveFromLargeFreeRangeList +// +// Removes the given page range from the m_largeFreePageRangeList +void rvHeap::RemoveFromLargeFreeRangeList( uint pageOffset, uint &startPageOffset, uint &pageCount ) +{ + freeLargePageRange_t *freeLargePageRange, ** prevNextFreeLargePageRange; + + prevNextFreeLargePageRange = &m_largeFreePageRangeList; + freeLargePageRange = m_largeFreePageRangeList; + while ( freeLargePageRange != NULL ) + { + if ( pageOffset >= freeLargePageRange->m_firstPageOffset && + pageOffset < freeLargePageRange->m_firstPageOffset + freeLargePageRange->m_numContiguousPages ) + { + // found it! + *prevNextFreeLargePageRange = freeLargePageRange->m_nextFree; + startPageOffset = freeLargePageRange->m_firstPageOffset; + pageCount = freeLargePageRange->m_numContiguousPages; + + freeLargePageRange->m_nextFree = m_largeFreeRangeReuseList; + m_largeFreeRangeReuseList = freeLargePageRange; + return; + } + + prevNextFreeLargePageRange = &freeLargePageRange->m_nextFree; + freeLargePageRange = freeLargePageRange->m_nextFree; + } + assert(0); // we should not reach this +} + +// CommitPageRange +// +// Commits the given range of pages and flags them as committed within the m_pageCommitStateTable array +void *rvHeap::CommitPageRange( uint startPageOffset, uint numPages ) +{ + void* committedBaseAddress; + uint curDWordOffset, endDWordOffset, commitSize, tailShift; + dword mask; + DWORD allocType; + + assert( startPageOffset + numPages <= m_numPages ); + + // implement the commit + commitSize = numPages << PAGE_SIZE_SHIFT; + m_committedSizeBytes += commitSize; + + allocType = MEM_COMMIT; +#ifdef _XENON + if ( PAGE_SIZE_SHIFT == 16 ) + { + allocType |= MEM_LARGE_PAGES; + } + allocType |= MEM_NOZERO; +#endif + committedBaseAddress = VirtualAlloc( m_heapStorageStart + (startPageOffset << PAGE_SIZE_SHIFT), commitSize, allocType, PAGE_READWRITE ); + if ( NULL == committedBaseAddress) + { + common->Warning( "Out of physical memory - unable to commit requested page range.\n" ); + return NULL; + } + + assert(sizeof(dword) == 4); // the following code assumes that a dword is 4 bytes + + // enable the bits that correspond to the pages being committed within the m_pageCommitStateTable array + curDWordOffset = startPageOffset >> 5; + endDWordOffset = (startPageOffset + numPages - 1) >> 5; + + if ( curDWordOffset == endDWordOffset ) + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + tailShift = (startPageOffset + numPages) & 0x1F; + if ( tailShift != 0 ) + { + mask ^= (0xFFFFFFFF << tailShift); + } + m_pageCommitStateTable[ curDWordOffset ] |= mask; + } + else + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + m_pageCommitStateTable[ curDWordOffset++ ] |= mask; + + while ( curDWordOffset < endDWordOffset ) + { + m_pageCommitStateTable[ curDWordOffset ] |= 0xFFFFFFFF; + curDWordOffset++; + } + + mask = 0xFFFFFFFF >> (32 - (startPageOffset + numPages - (endDWordOffset << 5))); + m_pageCommitStateTable[ curDWordOffset ] |= mask; + } + return committedBaseAddress; +} + +// UncommitPageRange +// +// Uncommits the given range of pages and clears their flags within the m_pageCommitStateTable array +void rvHeap::UncommitPageRange( uint startPageOffset, uint numPages ) +{ + BOOL rtnValue; + uint curDWordOffset, endDWordOffset, decommitSize, tailShift; + dword mask; + + assert( startPageOffset + numPages <= m_numPages ); + + // implement the free + decommitSize = numPages << PAGE_SIZE_SHIFT; + assert( decommitSize <= m_committedSizeBytes ); + m_committedSizeBytes -= decommitSize; + + rtnValue = VirtualFree( m_heapStorageStart + (startPageOffset << PAGE_SIZE_SHIFT), + decommitSize, + MEM_DECOMMIT ); + assert( rtnValue ); + assert(sizeof(dword) == 4); // the following code assumes that a dword is 4 bytes + + // disable the bits that correspond to the pages being decommitted within the m_pageCommitStateTable array + curDWordOffset = startPageOffset >> 5; + endDWordOffset = (startPageOffset + numPages - 1) >> 5; + + if ( curDWordOffset == endDWordOffset ) + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + tailShift = (startPageOffset + numPages) & 0x1F; + if ( tailShift != 0 ) + { + mask ^= (0xFFFFFFFF << tailShift); + } + m_pageCommitStateTable[ curDWordOffset ] &= ~mask; + } + else + { + mask = 0xFFFFFFFF << (startPageOffset & 0x1F); + m_pageCommitStateTable[ curDWordOffset++ ] &= ~mask; + + while ( curDWordOffset < endDWordOffset ) + { + m_pageCommitStateTable[ curDWordOffset++ ] = 0; + } + + mask = 0xFFFFFFFF >> (32 - (startPageOffset + numPages - (endDWordOffset << 5))); + m_pageCommitStateTable[ curDWordOffset ] &= ~mask; + } +} + +// EnterHeapCriticalSection +// +// enters this heap's critical section +void rvHeap::EnterHeapCriticalSection() +{ + ::EnterCriticalSection( &m_criticalSection ); +} + +// ExitHeapCriticalSection +// +// exits this heap's critical section +void rvHeap::ExitHeapCriticalSection() +{ + ::LeaveCriticalSection( &m_criticalSection ); +} + +// GetSmallBlockFreeCount +// +// Get number of free blocks for this block type +// If there's a lot of free blocks, then it may be bad. +int rvHeap::GetSmallBlockFreeCount( int block ) const +{ + return GetBlockFreeCount( m_smallFreeBlocks[block] ); +} + + +// GetSmallBlockFreeSize +// +// Get the actual physical storage committed for empty space in this block +dword rvHeap::GetSmallBlockFreeSize( int block ) const +{ + return GetBlockFreeSize( m_smallFreeBlocks[block] ); +} + +void rvHeap::SetName(const char* name) +{ + if(name) + { + idStr::Copynz(m_name, name, sizeof(m_name)); + } +} + +const char* rvHeap::GetName(void) const +{ + if(!m_name[0]) + { + return "UN-NAMED"; + } + return m_name; +} + +// GetLargeBlockFreeCount +// +int rvHeap::GetLargeBlockFreeCount() const +{ + return GetBlockFreeCount( m_largeFreeBlocks ); +} + +// GetLargeBlockFreeSize +// +dword rvHeap::GetLargeBlockFreeSize() const +{ + return GetBlockFreeSize( m_largeFreeBlocks ); +} + +// GetBlockFreeCount +// +int rvHeap::GetBlockFreeCount( rvFreeMemoryBlock_s* currentBlock ) const +{ + dword freeCount = 0; + while ( currentBlock ) + { + currentBlock = currentBlock->m_nextFree; + ++freeCount; + } + return freeCount; +} + +// GetBlockFreeSize +// +dword rvHeap::GetBlockFreeSize( rvFreeMemoryBlock_s* currentBlock ) const +{ + dword freeSize = 0; + while (currentBlock) + { + dword blockSize = currentBlock->m_dwordDistToNextBlock << 2; + + if ( blockSize > PAGE_SIZE ) + { +#ifdef _DEBUG + dword initialBlockSize = blockSize; +#endif + // Round up to the next block boundary + dword rem = (dword)currentBlock & ~PAGE_SIZE_MASK; + blockSize-=rem; + + // Subtract off the pages that should be decomitted + while ( blockSize > PAGE_SIZE ) + { + blockSize-=PAGE_SIZE; + } + + // Make sure we didn't wrap +#ifdef _DEBUG + assert( initialBlockSize > blockSize ); +#endif + freeSize+=blockSize; + } + else + { + // Does not span blocks, so just add + freeSize+=blockSize; + } + + currentBlock = currentBlock->m_nextFree; + } + + return freeSize; +} + +// GetSmallBlockFreeSize +// +void Mem_FragmentationStats_f( const idCmdArgs &args ) +{ + rvHeap *heapArray[MAX_SYSTEM_HEAPS]; + rvGetAllSysHeaps( heapArray ); + + dword unusedMem = 0; + dword totalFragments = 0; + + for ( int i = 0; i < MAX_SYSTEM_HEAPS; ++i ) + { + rvHeap *curr = heapArray[i]; + if ( curr ) + { + for ( int j = 0; j < curr->SmallBlockCount(); ++j ) + { + int freeCount = curr->GetSmallBlockFreeCount( j ); + if ( freeCount ) + { + dword unused = curr->GetSmallBlockFreeSize(j); + idLib::common->Printf( "i:%d c:%d t:%d - ", j, freeCount, unused ); + unusedMem+=unused; + totalFragments+=freeCount; + } + } + + dword unused = curr->GetLargeBlockFreeSize(); + dword freeCount = curr->GetLargeBlockFreeCount(); + unusedMem+=unused; + totalFragments+=freeCount; + idLib::common->Printf( "i:large c:%d t:%d\n", freeCount, unused ); + +// dword comSize = curr->GetCommittedSize(); + dword bytesAl = curr->GetBytesAllocated(); + + idLib::common->Printf( "Total fragments: %d Fragment memory: %d\n", totalFragments, unusedMem ); + idLib::common->Printf( "Fragmentation: %f\n", float(unusedMem) / float(bytesAl) ); + + // We only want the first one, since they all appear to be the same heap right now. + break; + } + } +} + +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS +// TestFreeBlockValidity +// +// Tests for free memory block merge problems +void rvHeap::TestFreeBlockValidity() +{ + byte *pageAddress; + rvMemoryBlock_s *nextBlock; + rvFreeMemoryBlock_s *freeBlock; + int smallBlockTableOffset; + + for ( smallBlockTableOffset = 0; + smallBlockTableOffset < NUM_SMALL_BLOCK_TABLE_ENTRIES; + smallBlockTableOffset++ ) + { + // check to see if there is a block that matches exactly + freeBlock = m_smallFreeBlocks[smallBlockTableOffset]; + while ( freeBlock != NULL ) + { + // check the blocks around this block + if ( freeBlock->m_prev != NULL && freeBlock->m_prev->m_tag == MA_NONE ) + { + common->Warning( "Previous block failed to merge" ); + } + + nextBlock = (rvMemoryBlock_s *) ((byte*) freeBlock + freeBlock->GetDistToNextBlock()); + pageAddress = (byte *) (((dword) freeBlock) & ~PAGE_SIZE_MASK); + + if ( (byte*) nextBlock < pageAddress+PAGE_SIZE && nextBlock->m_tag == MA_NONE ) + { + common->Warning( "Next block failed to merge" ); + } + + freeBlock = freeBlock->m_nextFree; + } + } +} +#endif + +#endif // #ifdef _RV_MEM_SYS_SUPPORT diff --git a/src/idlib/rvHeap.h b/src/idlib/rvHeap.h new file mode 100644 index 0000000..64732ae --- /dev/null +++ b/src/idlib/rvHeap.h @@ -0,0 +1,401 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// rvHeap.h - Heap object (replacing the idHeap class) +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#ifndef __RV_HEAP_H__ +#define __RV_HEAP_H__ + +class rvHeapArena; +struct rvMemoryBlock_s; +struct rvFreeMemoryBlock_s; + +//#define _DETECT_BLOCK_MERGE_PROBLEMS + +// Define some flags used in describing various properties of allocations +// coming from this heap (these flags are passed into rvHeap::Init()). +static const uint rvHeapFlagDefault = 0; +static const uint rvHeapFlagWriteCombine = 0x0001; // flag specified if the memory allocated from this heap is intended to have a write-combine cache policy +static const uint rvHeapFlagNoCache = 0x0002; // flag specified if the memory allocated from this heap is intended to have a no cache policy + +// Define some limits used by each heap +static const uint NUM_MEMORY_BLOCK_SIZE_BITS = 27; +static const uint NUM_TAG_BITS = 5; + +static const uint MAX_SINGLE_ALLOCATION_SIZE = ((1 << (NUM_MEMORY_BLOCK_SIZE_BITS+2)) - 1); // maximum number of bytes that can be allocated in a single allocation +static const uint NUM_SMALL_BLOCK_TABLE_ENTRIES = 2048; // number of entries within each heap's small free memory block table + +static const uint SMALL_BLOCK_ALIGN_SIZE_SHIFT = 2; // left shift of the size that all allocations are rounded to +static const uint SMALL_BLOCK_ALIGN_SIZE = 1 << SMALL_BLOCK_ALIGN_SIZE_SHIFT; // size that all allocations are rounded to +static const uint SMALL_BLOCK_ALIGN_SIZE_MASK = SMALL_BLOCK_ALIGN_SIZE-1; // mask of the bits used for the size that all allocations are rounded to +static const uint MAX_SMALL_BLOCK_SIZE = (NUM_SMALL_BLOCK_TABLE_ENTRIES << SMALL_BLOCK_ALIGN_SIZE_SHIFT) - 1; // maximum size of an allocation to be considered a "small block" allocation + +static const uint PAGE_SIZE_SHIFT = 16; // left shift of the page size used by this heap object +static const uint PAGE_SIZE = 1 << PAGE_SIZE_SHIFT; // page size used by this heap object +static const uint PAGE_SIZE_MASK = PAGE_SIZE - 1; + +static const uint MAX_SMALL_PAGE_RANGE = 128; // maximum number of contiguous pages for a single range to be considered "small" +static const uint MIN_LARGE_PAGE_RANGE = MAX_SMALL_PAGE_RANGE+1; // minimum number of contiguous pages for a single range to be considered "large" + +// Define the freePageRange_t structure used by this rvHeap used to maintain linked-lists of contiguous page ranges below LARGE_PAGE_RANGE +typedef struct freePageRange_s +{ + freePageRange_s* m_nextFree; // next range of free pages in a linked-list +} +freePageRange_t; + +// Define the freePageRange_t structure used by this rvHeap used to maintain a linked-list of contiguous page ranges above LARGE_PAGE_RANGE +typedef struct freeLargePageRange_s +{ + freeLargePageRange_s* m_nextFree; // next large range of free pages in a linked-list + uint m_firstPageOffset; // offset of the first page in this range + uint m_numContiguousPages; // count of the number of contiguous pages in this range +} +freeLargePageRange_t; + +class rvHeap +{ +public: + rvHeap( ); // constructor + ~rvHeap( ); // destructor + + void Init( rvHeapArena &heapArena, uint maxHeapSizeBytes, uint flags = rvHeapFlagDefault ); // initializes this heap for use within the given arena, and with the given size limit that it can grow to + void Shutdown( ); // releases this heap from use + + ID_INLINE bool IsInitialized( ) const; // returns true if this heap is currently initialized, and not shutdown + + ID_INLINE void *Allocate( uint sizeBytes, int debugTag = 0 ); // allocate memory + ID_INLINE void *Allocate16( uint sizeBytes, int debugTag = 0 ); // allocate memory aligned on a 16-byte boundary + void Free( void *allocation ); // free memory + + int Msize( void *allocation ); // returns the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). + + void FreeAll( ); // frees all the allocations from this heap (and decommits all pages) + + ID_INLINE bool DoesAllocBelong( void *allocBase ); // returns true if the given address was allocated from this heap, false otherwise + + ID_INLINE rvHeapArena *GetArena( ); // returns the arena this heap is associated with + + ID_INLINE void PushCurrent( ); // makes this heap the new top of stack for its associated arena, thus making it current + ID_INLINE void PopCurrent( ); // pops this heap, or any other heap, from the top of stack of this heap's associated arena, thus making its predecessor current + + ID_INLINE uint GetSize( ) const; // returns the current size that this heap has grown to, in bytes + ID_INLINE uint GetMaxSize( ) const; // returns the maximum size that this heap can grow to, in bytes + + ID_INLINE uint GetCommittedSize( ) const; // returns the number of physical bytes this heap is currently using (total bytes of all committed pages) + ID_INLINE uint GetBytesAllocated( ) const; // returns the number of bytes currently allocated from this heap (actual memory footprint of all active allocations including internal headers and alignment bytes) + ID_INLINE uint GetBytesRequested( ) const; + ID_INLINE int GetNumAllocations( ) const; // returns the number of allocations made from this heap that are still active (have not been freed) + + ID_INLINE uint GetBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const; // returns the number of bytes currently allocated from this heap with the given allocation tag (actual memory footprint of all active allocations including internal headers and alignment bytes) + ID_INLINE uint GetPeekBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const; // returns the peek number of bytes allocated from this heap with the given allocation tag (actual memory footprint of all active allocations including internal headers and alignment bytes) + ID_INLINE int GetNumAllocationsByTag( Mem_Alloc_Types_t allocType ) const; // returns the current number of allocation from this heap with the given allocation tag + + ID_INLINE bool IsWriteCombine( ) const; // returns true if memory allocated from this heap has a write-combine cache policy, false otherwise + ID_INLINE bool IsNoCache( ) const; // returns true if memory allocated from this heap has a no-cache policy, false otherwise + + int SmallBlockCount() const { return NUM_SMALL_BLOCK_TABLE_ENTRIES; } + int GetSmallBlockFreeCount( int block ) const; + dword GetSmallBlockFreeSize( int block ) const; + + int GetLargeBlockFreeCount() const; + dword GetLargeBlockFreeSize() const; + + int GetBlockFreeCount( rvFreeMemoryBlock_s* currentBlock ) const; + dword GetBlockFreeSize( rvFreeMemoryBlock_s* currentBlock ) const; + + void SetName(const char* name); + const char* GetName(void) const; + void SetDebugID(byte debugID) {mDebugID=debugID;} + byte DebugID(void) const {return mDebugID;} + +protected: + rvHeapArena *m_arena; // arena associated with this heap + rvHeap *m_next; // next heap belonging to the same arena + CRITICAL_SECTION m_criticalSection; // critical section associated with this heap + + byte *m_baseAddress; // base address of this heap (virtual) + byte *m_zeroLengthAllocPage; // special page for zero-length allocations (remains uncommitted) + byte *m_heapStorageStart; // address of the start of this heap's storage (just past page table) + + rvFreeMemoryBlock_s *m_smallFreeBlocks[NUM_SMALL_BLOCK_TABLE_ENTRIES]; // table used to manage linked-lists of small free memory blocks + rvFreeMemoryBlock_s *m_largeFreeBlocks; // linked-list of all of the large free memory blocks + + dword *m_pageCommitStateTable; // a very long bit mask (1-bit per page) that describes whether the page has been committed or not to physical memory + freePageRange_t *m_smallFreePageRangeTable; // array of structures that each correspond to a particular page - the structures representing the pages at the start of a small range are added to lists within the m_smallFreePageRangeLists[] array below + + freePageRange_t *m_smallFreePageRangeLists[MAX_SMALL_PAGE_RANGE]; // array where each entry is a linked-list of structures that individually describe a contiguous range of pages (where the range count matches the index into this array plus 1) + freeLargePageRange_t *m_largeFreePageRangeList; // linked-list of structures that individually describe a large contiguous range of pages + + // the following two data members are just used to manage the storage of the freeLargePageRange_t structures + freeLargePageRange_t *m_largeFreeRangeReuseList; // linked-list of freeLargePageRange_t that are currently not part of the m_largeFreePageRangeList linked-list and are available for use + freeLargePageRange_t *m_largeFreeRangeStorage; // array that acts as a store of the freeLargePageRange_t structures + uint m_largeFreeRangeStorageSize; // number of entries in the m_largeFreeRangeStorage[] array + uint m_pageCommitStateArrayByteSize; // size of the m_pageCommitStateTable[] array in bytes + uint m_smallFreePageRangeByteSize; // size of the m_smallFreePageRangeTable[] array in bytes + + uint m_numPages; // number of pages that can span this heap entirely + + uint m_heapRangeBytes; // the number of bytes in the virtual address space of this heap (including zero-length allocation page) + + uint m_committedSizeBytes; // the number of physical bytes this heap is currently using (total bytes of all committed pages) + uint m_maxHeapSizeBytes; // the maximum size that this heap can grow to, in bytes (allocatable space) + + uint m_numBytesRequested; // the number of bytes currently requested for allocation from this heap (memory footprint without the overhaed of internal headers and alignment bytes) + uint m_numBytesAllocated; // the number of bytes currently allocated from this heap (actual memory footprint of all active allocations including internal headers and alignment bytes) + + uint m_allocatedBytesByTag[MA_MAX]; // the number of bytes allocated from this heap - organized by usage tag + uint m_peekAllocatedBytesByTag[MA_MAX]; // the peek number of bytes allocated from this heap - organized by usage tag + int m_numAllocationsByTag[MA_MAX]; // the number of allocations - organized by usage tag + + int m_numAllocations; // the number of allocations made from this heap that are still active (have not been freed) + int m_numZeroLengthAllocations; // the number of zero length allocations made from this heap + + uint m_largestFreeSmallBlockOffset; // small block table offset of the largest free small block available (block that is part of m_smallFreeBlocks[] table) + + uint m_flags; // flags that describe various properties of this heap + + byte mDebugID; // ID that identifies heap. Needed to absolutely identify a heap. + char m_name[20]; // for debug display + + void ResetValues( ); // resets the data members to their pre-initialized state + + void BuildLargeFreeRangeReuseList( ); // builds the m_largeFreeRangeReuseList linked-list from the m_largeFreeRangeStorage[] array. + + byte *AllocateMemory( uint sizeBytes, int debugTag, bool align16Flag ); // performs memory allocation for the given number of bytes from this heap + + rvMemoryBlock_s *AllocateMemorySmallBlock( uint sizeBytes ); // perform a small block allocation - try to use an existing free block pointed to by the table. + rvMemoryBlock_s *AllocateBlockFromTable( uint smallBlockTableOffset, uint sizeBytes ); // allocates the a small memory block from the free block table and partitions it, if necessary, into an allocated chunk and a free chunk (which remains in the m_smallFreeBlocks[] table). + rvMemoryBlock_s *AllocateMemoryLargeBlock( uint sizeBytes ); // perform a large block allocation - try to use an existing free block from within the large free block linked-list. + + void TestLargestFreeSmallBlockOffset( uint smallBlockTableOffset ); // checks to see if the largest free small block has moved down + + void FixUpNextBlocksPrev( byte *newBlock, uint blockSize ); // fixes up the previous pointer of the memory block that lies immediately past the given newBlock, if it remains on the same page. + + void AddFreeBlock( rvFreeMemoryBlock_s *freeBlock ); // adds the given free block to the small allocation table or large allocation list. + void RemoveFreeBlock( rvFreeMemoryBlock_s *freeBlock ); // removes the given block from the small allocation table or large allocation list. + + void *PageCommit( uint numDesiredPages ); // commits the given number of contiguous pages to physical memory + void PageUncommit( byte *pageAddress, uint numPages ); // uncommits the given range of contiguous pages back to physical memory + + void RemoveFromSmallFreeRangeList( uint pageOffset, uint freeBlockPageCount ); // removes the given page range from the m_smallFreePageRangeLists[] + void RemoveFromLargeFreeRangeList( uint pageOffset, uint &startPageOffset, uint &pageCount ); // removes the given page range from the m_largeFreePageRangeList + + void *CommitPageRange( uint startPageOffset, uint numPages ); // commits the given range of pages and flags them as committed within the m_pageCommitStateTable array + void UncommitPageRange( uint startPageOffset, uint numPages ); // uncommits the given range of pages and clears their flags within the m_pageCommitStateTable array + + void EnterHeapCriticalSection(); // enters this heap's critical section + void ExitHeapCriticalSection(); // exits this heap's critical section + +#ifdef _DETECT_BLOCK_MERGE_PROBLEMS + void TestFreeBlockValidity(); // test for free memory block merge problems +#endif + + friend class rvHeapArena; // give the rvHeapArena class access to the following methods + // { + ID_INLINE void SetArena( rvHeapArena *arena ); // sets the arena this heap is associated with + ID_INLINE void SetNext( rvHeap *next ); // sets the next heap associated with the same arena + ID_INLINE rvHeap *GetNext( ); // returns the next heap associated with the same arena + // } +}; + +// IsInitialized +// +// returns: true if this heap is currently initialized, and not shutdown +ID_INLINE bool rvHeap::IsInitialized( ) const +{ + return m_arena != NULL; +} + +// Allocate +// +// allocate memory +void *rvHeap::Allocate( uint sizeBytes, int debugTag ) +{ + return AllocateMemory( sizeBytes, debugTag, false ); +} + +// Allocate16 +// +// allocate memory aligned on a 16-byte boundary +void *rvHeap::Allocate16( uint sizeBytes, int debugTag ) +{ + return AllocateMemory( sizeBytes, debugTag, true ); +} + +// DoesAllocBelong +// +// returns: true if the given address was allocated from this heap, false otherwise +ID_INLINE bool rvHeap::DoesAllocBelong( void *allocBase ) +{ + return allocBase >= m_zeroLengthAllocPage && allocBase < m_heapStorageStart + m_heapRangeBytes; +} + +// GetArena +// +// returns: the arena this heap is associated with +ID_INLINE rvHeapArena *rvHeap::GetArena( ) +{ + return m_arena; +} + +// GetNext +// +// returns: the next heap belonging to the same arena +ID_INLINE rvHeap *rvHeap::GetNext( ) +{ + return m_next; +} + +// PushCurrent +// +// makes this heap the new top of stack for its associated arena, thus making it current. +ID_INLINE void rvHeap::PushCurrent( ) +{ + m_arena->Push( *this ); +} + +// PopCurrent +// +// pops this heap, or any other heap, from the top of stack of this heap's associated arena, +// thus making its predecessor current +ID_INLINE void rvHeap::PopCurrent( ) +{ + m_arena->Pop( ); +} + +// GetCommittedSize +// +// returns: the number of physical bytes this heap is currently using (total bytes of all committed pages) +ID_INLINE uint rvHeap::GetCommittedSize( ) const +{ + return m_committedSizeBytes; +} + +// GetMaxSize +// +// returns the maximum size that this heap can grow to, in bytes +ID_INLINE uint rvHeap::GetMaxSize( ) const +{ + return m_maxHeapSizeBytes; +} + +// GetBytesAllocated +// +// returns: the number of bytes currently allocated from this heap +// (actual memory footprint of all active allocations +// including internal headers and alignment bytes) +// +ID_INLINE uint rvHeap::GetBytesAllocated( ) const +{ + return m_numBytesAllocated; +} + +// GetBytesRequested +// +// returns: the number of bytes currently requested from this heap +// (i.e. without overhead) +// +ID_INLINE uint rvHeap::GetBytesRequested( ) const +{ + return m_numBytesRequested; +} + +// GetNumAllocations +// +// returns: the number of allocations made from this heap that are still active (have not been freed) +ID_INLINE int rvHeap::GetNumAllocations( ) const +{ + return m_numAllocations; +} + +// GetBytesAllocatedByTag +// +// returns: the number of bytes currently allocated from this heap with the given allocation tag +// (actual memory footprint of all active allocations including internal headers and +// alignment bytes). +ID_INLINE uint rvHeap::GetBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const +{ + assert( allocType < MA_MAX ); + return m_allocatedBytesByTag[(uint) allocType]; +} + +// GetPeekBytesAllocatedByTag +// +// returns: the peek number of bytes allocated from this heap with the given allocation tag (actual +// memory footprint of all active allocations including internal headers and alignment bytes) +ID_INLINE uint rvHeap::GetPeekBytesAllocatedByTag( Mem_Alloc_Types_t allocType ) const +{ + assert( allocType < MA_MAX ); + return m_peekAllocatedBytesByTag[(uint) allocType]; +} + +// GetNumAllocationsByTag +// +// returns: the current number of allocation from this heap with the given allocation tag +ID_INLINE int rvHeap::GetNumAllocationsByTag( Mem_Alloc_Types_t allocType ) const +{ + assert( allocType < MA_MAX ); + return m_numAllocationsByTag[(uint) allocType]; +} + +// IsWriteCombine +// +// returns: true if memory allocated from this heap has a write-combine cache policy, false otherwise +ID_INLINE bool rvHeap::IsWriteCombine( ) const +{ + return (m_flags & rvHeapFlagWriteCombine) != 0; +} + +// IsNoCache +// +// returns: true if memory allocated from this heap has a no-cache policy, false otherwise +ID_INLINE bool rvHeap::IsNoCache( ) const +{ + return (m_flags & rvHeapFlagNoCache) != 0; +} + +// SetArena +// +// sets the arena this heap is associated with +ID_INLINE void rvHeap::SetArena( rvHeapArena *arena ) +{ + m_arena = arena; +} + +// SetNext +// +// sets the next heap associated with the same arena +ID_INLINE void rvHeap::SetNext( rvHeap *next ) +{ + m_next = next; +} + +extern void Mem_FragmentationStats_f( const class idCmdArgs &args ); + +#endif // #ifndef __RV_HEAP_H__ diff --git a/src/idlib/rvHeapArena.cpp b/src/idlib/rvHeapArena.cpp new file mode 100644 index 0000000..b79c642 --- /dev/null +++ b/src/idlib/rvHeapArena.cpp @@ -0,0 +1,327 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// rvHeapArena.cpp - Heap arena object that manages a set of heaps +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifdef _RV_MEM_SYS_SUPPORT + +// rvHeapArena +// +// constructor +rvHeapArena::rvHeapArena() +{ + // ResetValues(); do this in the Init() call instead (due to the fact that other constructors could call rvHeapArena::Init() before this constructor is called) +} + +// ~rvHeapArena +// +// destructor +rvHeapArena::~rvHeapArena() +{ + Shutdown(); +} + +// Init +// +// initializes this heap arena for use +void rvHeapArena::Init( ) +{ + if ( m_isInitialized ) + { + return; + } + + ResetValues(); + m_isInitialized = true; + + // create the critical section used by this heap arena + InitializeCriticalSection( &m_criticalSection ); +} + +// Shutdown +// +// releases this heap arena from use (shutting down all associated heaps) +void rvHeapArena::Shutdown( ) +{ + // shutdown each heap from this arena's list + rvHeap *curHeap = m_heapList, *nextHeap; + while ( curHeap != NULL ) + { + nextHeap = curHeap->GetNext(); + + curHeap->Shutdown(); + + curHeap = nextHeap; + } + DeleteCriticalSection( &m_criticalSection ); + ResetValues(); +} + +// ResetValues +// +// resets the data members to their pre-initialized state +void rvHeapArena::ResetValues( ) +{ + memset( m_heapStack, 0, sizeof(m_heapStack) ); + memset( &m_criticalSection, 0, sizeof(m_criticalSection) ); + m_tos = -1; + m_heapList = NULL; + m_isInitialized = false; +} + +// Push +// +// pushes the given heap onto the top of the stack making it the active one for this arena +void rvHeapArena::Push( rvHeap &newActiveHeap ) +{ + EnterArenaCriticalSection(); + assert( newActiveHeap.GetArena() == this ); + assert(m_tos+1 < maxHeapStackDepth); // stack overflow? + if (m_tos+1 < maxHeapStackDepth) + { + m_heapStack[++m_tos] = &newActiveHeap; + } + ExitArenaCriticalSection(); +} + +// Pop +// +// pops the top of the stack, restoring the previous heap as the active heap for this arena +void rvHeapArena::Pop( ) +{ + EnterArenaCriticalSection(); + assert(m_tos > -1); // stack underflow? + if (m_tos > -1) + { + m_tos--; + } + ExitArenaCriticalSection(); +} + +// GetHeap +// +// returns: the heap that the given allocation was made from, NULL for none +rvHeap *rvHeapArena::GetHeap( void *p ) +{ + EnterArenaCriticalSection(); + if ( !m_isInitialized ) + { + ExitArenaCriticalSection(); + return NULL; + } + + rvHeap *curHeap = m_heapList; + while ( curHeap != NULL && !curHeap->DoesAllocBelong(p) ) + { + curHeap = curHeap->GetNext(); + } + ExitArenaCriticalSection(); + + return curHeap; +} + + +// Allocate +// +// allocates the given amount of memory from this arena. +void *rvHeapArena::Allocate( unsigned int sizeBytes, int debugTag ) +{ + rvHeap *curHeap; + + EnterArenaCriticalSection(); + assert( m_tos >= 0 && m_tos < maxHeapStackDepth ); + if ( m_tos < 0 ) + { + ExitArenaCriticalSection(); + return NULL; + } + curHeap = m_heapStack[ m_tos ]; + ExitArenaCriticalSection(); + + return curHeap->Allocate( sizeBytes, debugTag ); +} + +// Allocate16 +// +// allocates the given amount of memory from this arena, +// aligned on a 16-byte boundary. +void *rvHeapArena::Allocate16( unsigned int sizeBytes, int debugTag ) +{ + rvHeap *curHeap; + + EnterArenaCriticalSection(); + assert( m_tos >= 0 && m_tos < maxHeapStackDepth ); + if ( m_tos < 0 ) + { + ExitArenaCriticalSection(); + return NULL; + } + curHeap = m_heapStack[ m_tos ]; + ExitArenaCriticalSection(); + + return curHeap->Allocate16( sizeBytes, debugTag ); +} + +// Free +// +// free memory back to this arena +void rvHeapArena::Free( void *p ) +{ + rvHeap *heap = GetHeap( p ); // arena critical section protection is in GetHeap() + if (heap != NULL) + { + heap->Free( p ); + } +} + +// Msize +// +// returns: the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). +int rvHeapArena::Msize( void *p ) +{ + rvHeap *heap = GetHeap( p ); // arena critical section protection is in GetHeap() + if (heap != NULL) + { + return heap->Msize( p ); + } + return 0; +} + +// InitHeap +// +// initializes the given heap to be under the care of this arena +void rvHeapArena::InitHeap( rvHeap &newActiveHeap ) +{ + assert( newActiveHeap.GetArena() == NULL ); + + newActiveHeap.SetArena( this ); + newActiveHeap.SetNext( m_heapList ); + m_heapList = &newActiveHeap; +} + +// ShutdownHeap +// +// releases the given heap from the care of this arena +void rvHeapArena::ShutdownHeap( rvHeap &activeHeap ) +{ + int stackPos, copyPos; + + assert( activeHeap.GetArena() == this ); + + activeHeap.SetArena( NULL ); + + // make sure that the heap is removed from the stack + for ( stackPos = 0; stackPos <= m_tos; stackPos++ ) + { + if ( m_heapStack[stackPos] == &activeHeap ) + { + for ( copyPos = stackPos; copyPos < m_tos; copyPos++ ) + { + m_heapStack[copyPos] = m_heapStack[copyPos+1]; + } + m_tos--; + } + } + + // remove the heap from this arena's list + rvHeap *curHeap = m_heapList, * prevHeap = NULL; + while ( curHeap != NULL ) + { + if ( curHeap == &activeHeap ) + { + if ( NULL == prevHeap ) + { + m_heapList = m_heapList->GetNext(); + } + else + { + prevHeap->SetNext( curHeap->GetNext() ); + } + break; + } + prevHeap = curHeap; + curHeap = curHeap->GetNext(); + } +} + +// GetNextHeap +// +// returns: that follows the given one (associated with this arena), NULL for none +rvHeap *rvHeapArena::GetNextHeap( rvHeap &rfPrevHeap ) +{ + rvHeap *nextHeap; + + EnterArenaCriticalSection(); + if ( rfPrevHeap.GetArena() != this ) + { + nextHeap = NULL; + } + else + { + nextHeap = rfPrevHeap.GetNext(); + } + ExitArenaCriticalSection(); + + return nextHeap; +} + +// GetTagStats +// +// returns: the total stats for a particular tag type (across all heaps managed by this arena) +void rvHeapArena::GetTagStats(int tag, int &num, int &size, int &peak) +{ + int curPeak; + + assert( tag < MA_MAX ); + + EnterArenaCriticalSection(); + + num = size = peak = 0; + + rvHeap *curHeap = m_heapList; + while ( curHeap != NULL ) + { + num += curHeap->GetNumAllocationsByTag( (Mem_Alloc_Types_t) tag ); + size += curHeap->GetBytesAllocatedByTag( (Mem_Alloc_Types_t) tag ); + + curPeak = curHeap->GetPeekBytesAllocatedByTag( (Mem_Alloc_Types_t) tag ); + if ( curPeak > peak ) + { + peak = curPeak; + } + + curHeap = curHeap->GetNext(); + } + + ExitArenaCriticalSection(); +} + +#endif // #ifdef _RV_MEM_SYS_SUPPORT diff --git a/src/idlib/rvHeapArena.h b/src/idlib/rvHeapArena.h new file mode 100644 index 0000000..0a844ad --- /dev/null +++ b/src/idlib/rvHeapArena.h @@ -0,0 +1,163 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// rvHeapArena.h - Heap arena object that manages a set of heaps +// Date: 12/13/04 +// Created by: Dwight Luetscher +// + +#ifndef __RV_HEAP_ARENA_H__ +#define __RV_HEAP_ARENA_H__ + +class rvHeap; + +// Define some limits used by a heap arena +static const int maxNumHeapsPerArena = 16; // maximum number of heaps that can be simultaneously initialized with the same arena +static const int maxHeapStackDepth = maxNumHeapsPerArena*2; // maximum depth of each arena's heap stack + + +class rvHeapArena { +public: + rvHeapArena(); // constructor + ~rvHeapArena(); // destructor + + void Init( ); // initializes this heap arena for use + void Shutdown( ); // releases this heap arena from use (shutting down all associated heaps) + + ID_INLINE bool IsInitialized( ) const; // returns true if this rvHeapArena object is currently initialized and not released, false otherwise + + // Push the current heap through the heap object itself (you can Pop() there as well) + void Pop( ); // pops the top of the stack, restoring the previous heap as the active heap for this arena + ID_INLINE bool IsStackFull( ); // returns true if the heap arena stack is full, false otherwise + + ID_INLINE rvHeap *GetActiveHeap( ); // returns the active heap for this arena (from the top of the stack, NULL if stack is empty) + + ID_INLINE void EnterArenaCriticalSection(); // enters this heap arena's critical section + ID_INLINE void ExitArenaCriticalSection(); // exits this heap arena's critical section + + void *Allocate( unsigned int sizeBytes, int debugTag = 0); // allocates the given amount of memory from this arena + void *Allocate16( unsigned int sizeBytes, int debugTag = 0); // allocates the given amount of memory from this arena, aligned on a 16-byte boundary + void Free( void *p ); // free memory back to this arena + + int Msize( void *p ); // returns the size, in bytes, of the allocation at the given address (including header, alignment bytes, etc). + + rvHeap *GetHeap( void *p ); // returns the heap that the given allocation was made from, NULL for none + + ID_INLINE rvHeap *GetFirstHeap( ); // returns the first heap associated with this arena, NULL for none + rvHeap *GetNextHeap( rvHeap &rfPrevHeap ); // returns that follows the given one (associated with this arena), NULL for none + + void GetTagStats(int tag, int &num, int &size, int &peak); // returns the total stats for a particular tag type (across all heaps managed by this arena) + +protected: + // NOTE: we cannot use idList here - dynamic memory is not yet available. + rvHeap *m_heapStack[maxHeapStackDepth]; // stack of heap object pointers + CRITICAL_SECTION m_criticalSection; // critical section associated with this heap + int m_tos; // top of stack + rvHeap *m_heapList; // linked-list of all the heaps that are actively associated with this arena + bool m_isInitialized; // set to true if this rvHeapArena object is currently initialized and not released + + void ResetValues( ); // resets the data members to their pre-initialized state + + friend class rvHeap; // give the rvHeap class access to the following methods + // { + void Push( rvHeap &newActiveHeap ); // pushes the given heap onto the top of the stack making it the active one for this arena + void InitHeap( rvHeap &newActiveHeap ); // initializes the given heap to be under the care of this arena + void ShutdownHeap( rvHeap &newActiveHeap ); // releases the given heap from the care of this arena + // } +}; + +// IsInitialized +// +// returns: true if this rvHeapArena object is currently initialized and not released, false otherwise +ID_INLINE bool rvHeapArena::IsInitialized( ) const +{ + return m_isInitialized; +} + +// EnterArenaCriticalSection +// +// enters this heap arena's critical section +ID_INLINE void rvHeapArena::EnterArenaCriticalSection() +{ + ::EnterCriticalSection( &m_criticalSection ); +} + +// ExitArenaCriticalSection +// +// exits this heap arena's critical section +ID_INLINE void rvHeapArena::ExitArenaCriticalSection() +{ + ::LeaveCriticalSection( &m_criticalSection ); +} + +// IsStackFull +// +// returns: true if the heap arena stack is full, false otherwise +ID_INLINE bool rvHeapArena::IsStackFull( ) +{ + bool full; + + EnterArenaCriticalSection(); + full = m_tos >= (maxHeapStackDepth - 1); + ExitArenaCriticalSection(); + + return full; +} + +// GetActiveHeap +// +// returns: the active heap for this arean (from the top of the stack, NULL if stack is empty) +ID_INLINE rvHeap *rvHeapArena::GetActiveHeap( ) +{ + rvHeap *tos; + EnterArenaCriticalSection(); + if ( m_tos < 0 ) + { + ExitArenaCriticalSection(); + return NULL; + } + tos = m_heapStack[ m_tos ]; + ExitArenaCriticalSection(); + return tos; +} + +// GetFirstHeap +// +// returns: the first heap associated with this arena, NULL for none +ID_INLINE rvHeap *rvHeapArena::GetFirstHeap( ) +{ + rvHeap *firstHeap; + + EnterArenaCriticalSection(); + firstHeap = m_heapList; + ExitArenaCriticalSection(); + + return firstHeap; +} + +#endif // #ifndef __RV_HEAP_MANAGER_H__ + + + diff --git a/src/idlib/rvMemSys.cpp b/src/idlib/rvMemSys.cpp new file mode 100644 index 0000000..394e87e --- /dev/null +++ b/src/idlib/rvMemSys.cpp @@ -0,0 +1,1292 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// rvMemSys.cpp - Memory management system +// Date: 12/17/04 +// Created by: Dwight Luetscher +// + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#ifdef _RV_MEM_SYS_SUPPORT + +static memoryStats_t mem_total_allocs = { 0, 0x0fffffff, -1, 0 }; +static memoryStats_t mem_frame_allocs; +static memoryStats_t mem_frame_frees; + +rvHeapArena mainHeapArena; +rvHeapArena *currentHeapArena; // this is the main heap arena that all other heaps use +rvHeap defaultHeap; // this is the default system heap + +static rvHeap *systemHeapArray[MAX_SYSTEM_HEAPS]; // array of pointers to rvHeaps that are common to idLib, Game, and executable + +#if defined(_WIN32) && !defined(_XENON) +static LPVOID sharedMem = NULL; // pointer to shared memory +static HANDLE hMapObject = NULL; // handle to file mapping +#endif + +// Descriptions that go with each tag. When updating the tag enum in Heap.h please +// update this list as well. +// (also update the list in Heap.cpp) +char *TagNames[] = { + "none", + "New operation", + "default", + "Lexer", + "Parser", + "AAS routing", + "Class", + "Script program", + "Collision Model", + "CVar", + "Decl System", + "File System", + "Images", + "Materials", + "Models", + "Fonts", + "Main renderer", + "Vertex data", + "Sound", + "Window", + "Event loop", + "Math - Matrices and vectors", + "Animation", + "Dynamic Blocks", + "Strings", + "GUI", + "Effects", + "Entities", + "Physics", + "AI", + "Network", + "Not Used" +}; + +template +class TagTableCheck +{ +private: + TagTableCheck(); +}; + +template<> +class TagTableCheck<1> +{ +}; + +// An error here means you need to synchronize TagNames and Mem_Alloc_Types_t +TagTableCheck TagTableCheckedHere; +// An error here means there are too many tags. No more than 32! +TagTableCheck TagMaxCheckedHere; + +#ifndef ENABLE_INTEL_SMP +MemScopedTag* MemScopedTag::mTop = NULL; +#endif + +/* +================== +GetMemAllocStats + +Gets some memory allocation stats based on a particular tag. +================== +*/ +const char *GetMemAllocStats(int tag, int &num, int &size, int &peak) +{ + assert( tag < MA_MAX ); + + currentHeapArena->GetTagStats( tag, num, size, peak ); + return TagNames[tag]; +} + +/* +================== +rvSetSysHeap + +sets the system heap that is associated with the given heap ID value +================== +*/ +void rvSetSysHeap(Rv_Sys_Heap_ID_t sysHeapID, rvHeap *heapPtr) +{ + assert( (uint) sysHeapID < (uint) rv_heap_ID_max_count ); + if ( NULL == heapPtr ) + { + // set the system heap back to the default heap + systemHeapArray[ (uint) sysHeapID ] = &defaultHeap; + } + else + { + systemHeapArray[ (uint) sysHeapID ] = heapPtr; + } +} + +/* +================== + rvPushSysHeap + +pushes the system heap associated with the given identifier to the top of the arena stack, making it current. +================== +*/ +void rvPushSysHeap(Rv_Sys_Heap_ID_t sysHeapID) +{ + assert( (uint) sysHeapID < (uint) rv_heap_ID_max_count ); + assert( systemHeapArray[ (uint) sysHeapID ] != NULL ); + systemHeapArray[ (uint) sysHeapID ]->PushCurrent( ); +} + +/* +================== + rvEnterArenaCriticalSection + +enters the heap arena critical section. +================== +*/ +void rvEnterArenaCriticalSection( ) +{ + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + assert( currentHeapArena != NULL ); + currentHeapArena->EnterArenaCriticalSection( ); +} + +/* +================== + rvExitArenaCriticalSection + +exits the heap arena critical section +================== +*/ +void rvExitArenaCriticalSection( ) +{ + assert( currentHeapArena != NULL ); + currentHeapArena->ExitArenaCriticalSection( ); +} + +/* +================== +Mem_Init + +initializes the memory system for use +================== +*/ +void Mem_Init( void ) +{ + if ( NULL == currentHeapArena ) + { + +#if defined(_WIN32) && !defined(_XENON) + hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, // use paging file + NULL, // default security attributes + PAGE_READWRITE, // read/write access + 0, // size: high 32-bits + sizeof(systemHeapArray)+sizeof(currentHeapArena), // size: low 32-bits + "quake4share"); // name of map object + if ( hMapObject == NULL ) + { + return; + } + + // The first process to attach initializes memory. + bool firstInit = (GetLastError() != ERROR_ALREADY_EXISTS); + + // Get a pointer to the file-mapped shared memory. + sharedMem = MapViewOfFile(hMapObject, // object to map view of + FILE_MAP_WRITE, // read/write access + 0, // high offset: map from + 0, // low offset: beginning + 0); // default: map entire file + if ( sharedMem == NULL ) + { + return; + } + + if ( !firstInit ) + { + memcpy( ¤tHeapArena, sharedMem, sizeof(currentHeapArena) ); + memcpy( &systemHeapArray, (byte*) sharedMem + sizeof(currentHeapArena), sizeof(systemHeapArray) ); + + UnmapViewOfFile( sharedMem ); + CloseHandle( hMapObject ); + + sharedMem = NULL; + hMapObject = NULL; + return; + } +#endif + + currentHeapArena = &mainHeapArena; + currentHeapArena->Init(); + defaultHeap.Init( *currentHeapArena, 256*1024*1024 ); + defaultHeap.SetDebugID(0); + defaultHeap.SetName("DEFAULT"); + rvSetSysHeap( RV_HEAP_ID_DEFAULT, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_PERMANENT, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_LEVEL, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_MULTIPLE_FRAME, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_SINGLE_FRAME, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_TEMPORARY, &defaultHeap ); + rvSetSysHeap( RV_HEAP_ID_IO_TEMP, &defaultHeap ); + RV_PUSH_SYS_HEAP_ID( RV_HEAP_ID_DEFAULT ); + +#if defined(_WIN32) && !defined(_XENON) + memcpy( sharedMem, ¤tHeapArena, sizeof(currentHeapArena) ); + memcpy( (byte*) sharedMem + sizeof(currentHeapArena), &systemHeapArray, sizeof(systemHeapArray) ); +#endif + } +} + +/* +================== +Mem_Shutdown + +Shuts down the memory system from all further use +================== +*/ +void Mem_Shutdown( void ) +{ +#if defined(_WIN32) && !defined(_XENON) + if ( sharedMem != NULL ) + { + UnmapViewOfFile( sharedMem ); + CloseHandle( hMapObject ); + sharedMem = NULL; + hMapObject = NULL; + } +#endif + + if ( currentHeapArena == &mainHeapArena ) + { + memset(systemHeapArray, 0, sizeof(systemHeapArray)); + defaultHeap.Shutdown(); + currentHeapArena->Shutdown(); + } +} + +void Mem_EnableLeakTest( const char *name ) +{ + +} + +/* +================== +Mem_UpdateStats +================== +*/ +void Mem_UpdateStats( memoryStats_t &stats, int size ) +{ + stats.num++; + if ( size < stats.minSize ) { + stats.minSize = size; + } + if ( size > stats.maxSize ) { + stats.maxSize = size; + } + stats.totalSize += size; +} + +/* +================== +Mem_UpdateAllocStats +================== +*/ +void Mem_UpdateAllocStats( int size ) +{ + Mem_UpdateStats( mem_frame_allocs, size ); + Mem_UpdateStats( mem_total_allocs, size ); +} + +/* +================== +Mem_UpdateFreeStats +================== +*/ +void Mem_UpdateFreeStats( int size ) { + Mem_UpdateStats( mem_frame_frees, size ); + mem_total_allocs.num--; + mem_total_allocs.totalSize -= size; +} + +/* +================== +Mem_ClearFrameStats +================== +*/ +void Mem_ClearFrameStats( void ) +{ + mem_frame_allocs.num = mem_frame_frees.num = 0; + mem_frame_allocs.minSize = mem_frame_frees.minSize = 0x0fffffff; + mem_frame_allocs.maxSize = mem_frame_frees.maxSize = -1; + mem_frame_allocs.totalSize = mem_frame_frees.totalSize = 0; +} + +/* +================== +Mem_GetFrameStats +================== +*/ +void Mem_GetFrameStats( memoryStats_t &allocs, memoryStats_t &frees ) +{ + allocs = mem_frame_allocs; + frees = mem_frame_frees; +} + +/* +================== +Mem_GetStats +================== +*/ +void Mem_GetStats( memoryStats_t &stats ) +{ + stats = mem_total_allocs; +} + +void Mem_AllocDefragBlock( void ) +{ + +} + +/* +================== +Mem_ShowMemAlloc_f +================== +*/ +void Mem_ShowMemAlloc_f( const idCmdArgs &args ) +{ + const char *tagName; + int tag, num, size, peak; + DWORD totalOutstanding = 0; + + for ( tag = 1; tag < MA_DO_NOT_USE; tag++ ) + { + tagName = GetMemAllocStats( tag, num, size, peak ); + + if ( size || peak ) + { + idLib::common->Printf("%-25s peak %9d curr %9d count %9d\n", tagName, peak, size, num ); + totalOutstanding += size; + } + } + idLib::common->Printf("Mem_Alloc Outstanding: %d\n", totalOutstanding); +} + +#ifdef ID_DEBUG_MEMORY + +#undef Mem_Alloc +#undef Mem_ClearedAlloc +#undef Com_ClearedReAlloc +#undef Mem_Free +#undef Mem_CopyString +#undef Mem_Alloc16 +#undef Mem_Free16 + +#define MAX_CALLSTACK_DEPTH 10 + +// size of this struct must be a multiple of 16 bytes +typedef struct debugMemory_s { + const char * fileName; + short lineNumber; + byte heapId; + byte memTag; + int frameNumber; + int size; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct debugMemory_s * prev; + struct debugMemory_s * next; +} debugMemory_t; + +static debugMemory_t * mem_debugMemory = NULL; +static char mem_leakName[256] = ""; + +/* +================== +Mem_CleanupFileName +================== +*/ +const char *Mem_CleanupFileName( const char *fileName ) +{ + int i1, i2; + idStr newFileName; + static char newFileNames[4][MAX_STRING_CHARS]; + static int index; + + newFileName = fileName; + newFileName.BackSlashesToSlashes(); + i1 = newFileName.Find( "neo", false ); + if ( i1 >= 0 ) { + i1 = newFileName.Find( "/", false, i1 ); + newFileName = newFileName.Right( newFileName.Length() - ( i1 + 1 ) ); + } + while( 1 ) { + i1 = newFileName.Find( "/../" ); + if ( i1 <= 0 ) { + break; + } + i2 = i1 - 1; + while( i2 > 1 && newFileName[i2-1] != '/' ) { + i2--; + } + newFileName = newFileName.Left( i2 - 1 ) + newFileName.Right( newFileName.Length() - ( i1 + 4 ) ); + } + index = ( index + 1 ) & 3; + strncpy( newFileNames[index], newFileName.c_str(), sizeof( newFileNames[index] ) ); + return newFileNames[index]; +} + +/* +================== +Mem_Dump +================== +*/ +void Mem_Dump( const char *fileName ) +{ + int i, numBlocks, totalSize; + char dump[32], *ptr; + debugMemory_t *b; + idStr module, funcName; + FILE *f; + + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + totalSize = 0; + for ( numBlocks = 0, b = mem_debugMemory; b; b = b->next, numBlocks++ ) { + ptr = ((char *) b) + sizeof(debugMemory_t); + totalSize += b->size; + for ( i = 0; i < (sizeof(dump)-1) && i < b->size; i++) { + if ( ptr[i] >= 32 && ptr[i] < 127 ) { + dump[i] = ptr[i]; + } else { + dump[i] = '_'; + } + } + dump[i] = '\0'; + if ( ( b->size >> 10 ) != 0 ) { + fprintf( f, "size: %6d KB: %s, line: %d [%s], call stack: %s\r\n", ( b->size >> 10 ), Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + else { + fprintf( f, "size: %7d B: %s, line: %d [%s], call stack: %s\r\n", b->size, Mem_CleanupFileName(b->fileName), b->lineNumber, dump, idLib::sys->GetCallStackStr( b->callStack, MAX_CALLSTACK_DEPTH ) ); + } + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +} + +/* +================== +Mem_Dump_f +================== +*/ +void Mem_Dump_f( const idCmdArgs &args ) +{ + const char *fileName; + + if ( args.Argc() >= 2 ) { + fileName = args.Argv( 1 ); + } + else { + fileName = "memorydump.txt"; + } + Mem_Dump( fileName ); +} + +typedef struct allocInfo_s +{ + const char * fileName; + short lineNumber; + byte heapId; + byte memTag; + int size; + int numAllocs; + address_t callStack[MAX_CALLSTACK_DEPTH]; + struct allocInfo_s * next; +} allocInfo_t; + +typedef enum +{ + MEMSORT_SIZE, + MEMSORT_LOCATION, + MEMSORT_NUMALLOCS, + MEMSORT_CALLSTACK, + MEMSORT_HEAPID, + MEMSORT_MEMTAG +} memorySortType_t; + +/* +================== +Mem_DumpCompressed +================== +*/ +void Mem_DumpCompressed( const char *fileName, memorySortType_t memSort, int sortCallStack, int numFrames, bool verbose ) +{ + int numBlocks, totalSize, r, j; + debugMemory_t *b; + allocInfo_t *a, *nexta, *allocInfo = NULL, *sortedAllocInfo = NULL, *prevSorted, *nextSorted; + idStr module, funcName; + + // build list with memory allocations + totalSize = 0; + numBlocks = 0; + nextSorted = NULL; + + for ( b = mem_debugMemory; b; b = b->next ) { + + if ( numFrames && b->frameNumber < idLib::frameNumber - numFrames ) { + continue; + } + + numBlocks++; + totalSize += b->size; + + // search for an allocation from the same source location + for ( a = allocInfo; a; a = a->next ) + { + if ( a->lineNumber != b->lineNumber ) + { + continue; + } +#ifndef _XENON + // removed the call stack info for better consolidation of info and speed of dump + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + if ( a->callStack[j] != b->callStack[j] ) { + break; + } + } + if ( j < MAX_CALLSTACK_DEPTH ) { + continue; + } +#endif + if ( idStr::Cmp( a->fileName, b->fileName ) != 0 ) { + continue; + } + + if ( a->heapId != b->heapId ) + { + continue; + } + + a->numAllocs++; + a->size += b->size; + break; + } + + // if this is an allocation from a new source location + if ( !a ) + { + a = (allocInfo_t *) currentHeapArena->Allocate( sizeof( allocInfo_t ) ); + a->fileName = b->fileName; + a->lineNumber = b->lineNumber; + a->heapId = b->heapId; + a->memTag = b->memTag; + a->size = b->size; + a->numAllocs = 1; + for ( j = 0; j < MAX_CALLSTACK_DEPTH; j++ ) { + a->callStack[j] = b->callStack[j]; + } + a->next = allocInfo; + allocInfo = a; + } + } + + // sort list + for ( a = allocInfo; a; a = nexta ) + { + nexta = a->next; + + prevSorted = NULL; + switch( memSort ) { + // sort on size + case MEMSORT_SIZE: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->size > nextSorted->size ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on file name and line number + case MEMSORT_LOCATION: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + r = idStr::Cmp( Mem_CleanupFileName( a->fileName ), Mem_CleanupFileName( nextSorted->fileName ) ); + if ( r < 0 || ( r == 0 && a->lineNumber < nextSorted->lineNumber ) ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on the number of allocations + case MEMSORT_NUMALLOCS: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->numAllocs > nextSorted->numAllocs ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on call stack + case MEMSORT_CALLSTACK: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->callStack[sortCallStack] < nextSorted->callStack[sortCallStack] ) { + break; + } + prevSorted = nextSorted; + } + break; + } + // sort on heap ID + case MEMSORT_HEAPID: { + for ( nextSorted = sortedAllocInfo; nextSorted; nextSorted = nextSorted->next ) { + if ( a->heapId > nextSorted->heapId ) { + break; + } + prevSorted = nextSorted; + } + break; + } + } + if ( !prevSorted ) { + a->next = sortedAllocInfo; + sortedAllocInfo = a; + } + else { + prevSorted->next = a; + a->next = nextSorted; + } + } + +// RAVEN BEGIN +// dluetscher: changed xenon version to output anything above 1K to the console +// mwhitlock: added verbose option to show < 1K also since I need to see leaks, +// no matter how small. +#ifdef _XENON + + unsigned int notShownSize = 0; + unsigned int notShownNumAllocs = 0; + + // write list to debug output and console + char buff[256]; + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + + if((a->size >> 10) > 0) + { + idStr::snPrintf(buff, sizeof(buff), "%6d K",( a->size >> 10)); + } + else + { + idStr::snPrintf(buff, sizeof(buff), "%6d B", a->size); + } + + if ( verbose || ((a->size >> 10) > 0 ) ) + { + idLib::common->Printf("size: %s, allocs: %5d: %s, line: %d, heap: %d, call stack: %s\r\n", + buff, a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, a->heapId, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); + } + else + { + notShownSize+=(unsigned int)a->size; + notShownNumAllocs+=(unsigned int)a->numAllocs; + } + + currentHeapArena->Free( a ); + } + + idLib::sys->ShutdownSymbols(); + + idLib::common->Printf("%8d bytes in %d allocs not shown\r\n", notShownSize, notShownNumAllocs ); + idLib::common->Printf("%8d total memory blocks allocated\r\n", numBlocks ); + idLib::common->Printf("%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + +#else +// RAVEN END + FILE *f; + +// in case you want to flip the above ifdef and write to disc on the xenon + f = fopen( fileName, "wb" ); + if ( !f ) { + return; + } + + // write list to file + for ( a = sortedAllocInfo; a; a = nexta ) { + nexta = a->next; + fprintf( f, "size: %6d KB, allocs: %5d: %s, line: %d, call stack: %s\r\n", + (a->size >> 10), a->numAllocs, Mem_CleanupFileName(a->fileName), + a->lineNumber, idLib::sys->GetCallStackStr( a->callStack, MAX_CALLSTACK_DEPTH ) ); +// RAVEN BEGIN +// jnewquist: send all allocations through one place on the Xenon + currentHeapArena->Free( a ); +// RAVEN END + } + + idLib::sys->ShutdownSymbols(); + + fprintf( f, "%8d total memory blocks allocated\r\n", numBlocks ); + fprintf( f, "%8d KB memory allocated\r\n", ( totalSize >> 10 ) ); + + fclose( f ); +#endif +} + +/* +================== +Mem_DumpCompressed_f +================== +*/ +void Mem_DumpCompressed_f( const idCmdArgs &args ) +{ + int argNum; + const char *arg, *fileName; + memorySortType_t memSort = MEMSORT_LOCATION; + int sortCallStack = 0, numFrames = 0; + bool verbose = false; + + // get cmd-line options + argNum = 1; + arg = args.Argv( argNum ); + while( arg[0] == '-' ) { + arg = args.Argv( ++argNum ); + if ( idStr::Icmp( arg, "s" ) == 0 ) { + memSort = MEMSORT_SIZE; + } else if ( idStr::Icmp( arg, "l" ) == 0 ) { + memSort = MEMSORT_LOCATION; + } else if ( idStr::Icmp( arg, "a" ) == 0 ) { + memSort = MEMSORT_NUMALLOCS; + } else if ( idStr::Icmp( arg, "cs1" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 2; + } else if ( idStr::Icmp( arg, "cs2" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 1; + } else if ( idStr::Icmp( arg, "cs3" ) == 0 ) { + memSort = MEMSORT_CALLSTACK; + sortCallStack = 0; + } else if ( idStr::Icmp( arg, "h" ) == 0 ) { + memSort = MEMSORT_HEAPID; + } else if ( idStr::Icmp( arg, "v" ) == 0 ) { + verbose = true; + } else if ( arg[0] == 'f' ) { + numFrames = atoi( arg + 1 ); + } else { + idLib::common->Printf( "memoryDumpCompressed [options] [filename]\n" + "options:\n" + " -s sort on size\n" + " -l sort on location\n" + " -a sort on the number of allocations\n" + " -cs1 sort on first function on call stack\n" + " -cs2 sort on second function on call stack\n" + " -cs3 sort on third function on call stack\n" + " -h sort on heap ID\n" + " -f only report allocations the last X frames\n" + " -v verbose (list all, even those totalling less than 1K\n" + "By default the memory allocations are sorted on location.\n" + "By default a 'memorydump.txt' is written if no file name is specified.\n" ); + return; + } + arg = args.Argv( ++argNum ); + } + if ( argNum >= args.Argc() ) { + fileName = "memorydump.txt"; + } else { + fileName = arg; + } + Mem_DumpCompressed( fileName, memSort, sortCallStack, numFrames, verbose ); +} + +/* +================== +Mem_AllocDebugMemory +================== +*/ +void *Mem_AllocDebugMemory( const int size, const char *fileName, const int lineNumber, const bool align16, byte tag ) +{ + void *p; + debugMemory_t *m; + + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + + if ( align16 ) + { + p = currentHeapArena->Allocate16( size + sizeof( debugMemory_t ), tag ); + } + else + { + p = currentHeapArena->Allocate( size + sizeof( debugMemory_t ), tag ); + } + + if ( NULL == p ) + { + return NULL; + } + + Mem_UpdateAllocStats( currentHeapArena->Msize( p ) ); + + m = (debugMemory_t *) p; + m->fileName = fileName; + m->lineNumber = lineNumber; + m->heapId = currentHeapArena->GetHeap(p)->DebugID(); + m->memTag = tag; + m->frameNumber = idLib::frameNumber; + m->size = size; + m->next = mem_debugMemory; + m->prev = NULL; + if ( mem_debugMemory ) { + mem_debugMemory->prev = m; + } + mem_debugMemory = m; + + if ( idLib::sys != NULL ) + { + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + } + else + { + memset( m->callStack, 0, sizeof(m->callStack) ); + } + + return ( ( (byte *) p ) + sizeof( debugMemory_t ) ); +} + +/* +================== +Mem_FreeDebugMemory +================== +*/ +void Mem_FreeDebugMemory( void *p, const char *fileName, const int lineNumber, const bool align16 ) +{ + debugMemory_t *m; + + if ( !p ) + { + return; + } + + m = (debugMemory_t *) ( ( (byte *) p ) - sizeof( debugMemory_t ) ); + + if ( m->size < 0 ) + { + idLib::common->FatalError( "memory freed twice, first from %s, now from %s", idLib::sys->GetCallStackStr( m->callStack, MAX_CALLSTACK_DEPTH ), idLib::sys->GetCallStackCurStr( MAX_CALLSTACK_DEPTH ) ); + } + + Mem_UpdateFreeStats( currentHeapArena->Msize( m ) ); + + if ( m->next ) + { + m->next->prev = m->prev; + } + + if ( m->prev ) + { + m->prev->next = m->next; + } + else + { + mem_debugMemory = m->next; + } + + m->fileName = fileName; + m->lineNumber = lineNumber; + m->frameNumber = idLib::frameNumber; + m->size = -m->size; + idLib::sys->GetCallStack( m->callStack, MAX_CALLSTACK_DEPTH ); + + assert( currentHeapArena != NULL ); + currentHeapArena->Free( m ); +} + +/* +================== +Mem_Alloc +================== +*/ +void *Mem_Alloc( const int size, const char *fileName, const int lineNumber, byte tag ) +{ + return Mem_AllocDebugMemory( size, fileName, lineNumber, false, tag ); +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr, const char *fileName, const int lineNumber ) +{ + if ( !ptr ) + { + return; + } + Mem_FreeDebugMemory( ptr, fileName, lineNumber, false ); +} + +/* +================== +Mem_Alloc16 +================== +*/ +void *Mem_Alloc16( const int size, const char *fileName, const int lineNumber, byte tag ) +{ + void *mem = Mem_AllocDebugMemory( size, fileName, lineNumber, true, tag ); + // make sure the memory is 16 byte aligned + assert( ( ((int)mem) & 15) == 0 ); + return mem; +} + +/* +================== +Mem_Free16 +================== +*/ +void Mem_Free16( void *ptr, const char *fileName, const int lineNumber ) +{ + if ( !ptr ) { + return; + } + // make sure the memory is 16 byte aligned + assert( ( ((int)ptr) & 15) == 0 ); + Mem_FreeDebugMemory( ptr, fileName, lineNumber, true ); +} + +/* +================== +Mem_ClearedAlloc +================== +*/ +void *Mem_ClearedAlloc( const int size, const char *fileName, const int lineNumber, byte tag ) +{ + void *mem = Mem_Alloc( size, fileName, lineNumber, tag ); + + if ( mem != NULL ) + { + SIMDProcessor->Memset( mem, 0, size ); + } + else + { + idLib::common->FatalError( "Ran out of memory during a cleared allocation" ); + } + return mem; +} + +/* +================== +Mem_CopyString +================== +*/ +char *Mem_CopyString( const char *in, const char *fileName, const int lineNumber ) +{ + char *out; + + out = (char *)Mem_Alloc( strlen(in) + 1, fileName, lineNumber ); + strcpy( out, in ); + return out; +} + +/* +================== +Mem_Size +================== +*/ +int Mem_Size( void *ptr ) +{ + if ( !ptr ) + { + return 0; + } + assert( currentHeapArena != NULL ); + return currentHeapArena->Msize( (byte*)ptr - sizeof( debugMemory_t ) ); +} + +#else // #ifdef ID_DEBUG_MEMORY + +void Mem_Dump_f( const class idCmdArgs &args ) +{ + +} + +void Mem_DumpCompressed_f( const class idCmdArgs &args ) +{ + +} + +/* +================== +Mem_Alloc + +Allocate memory from the heap at the top of the current arena stack +================== +*/ +void *Mem_Alloc( const int size, byte tag ) +{ + void *p; + + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + + p = currentHeapArena->Allocate( size, tag ); + if ( NULL == p ) + { + return NULL; + } + + Mem_UpdateAllocStats( currentHeapArena->Msize( p ) ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnAlloc(p, size); +#endif + + return p; +} + +// Mem_ClearedAlloc +// +// Allocate memory from the heap at the top of the current arena stack, +// clear that memory to zero before returning it. +void *Mem_ClearedAlloc( const int size, byte tag ) +{ + byte *allocation = (byte *) Mem_Alloc( size, tag ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnAlloc(allocation, size); +#endif + + if ( allocation != NULL ) + { + SIMDProcessor->Memset( allocation, 0, size ); + } + else + { + idLib::common->FatalError( "Ran out of memory during a cleared allocation" ); + } + return allocation; + +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free( void *ptr ) +{ + if ( !ptr ) + { + return; + } + assert( currentHeapArena != NULL ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnDelete(ptr); +#endif + + Mem_UpdateFreeStats( currentHeapArena->Msize( ptr ) ); + + currentHeapArena->Free( ptr ); +} + +/* +================== +Mem_CopyString + +Allocates memory for a copy of the given string, and copies +that string into the new allocation. +================== +*/ +char *Mem_CopyString( const char *in ) +{ + char *out; + + out = (char *)Mem_Alloc( strlen(in) + 1, MA_STRING ); + if ( out != NULL ) + { + strcpy( out, in ); + } + else + { + idLib::common->FatalError( "Ran out of memory during string copy allocation" ); + } + return out; +} + +/* +================== +Mem_Alloc16 + +Allocate memory from the heap at the top of the current arena +stack that is aligned on a 16-byte boundary. +================== +*/ +void *Mem_Alloc16( const int size, byte tag ) +{ + // the following is necessary for memory allocations that take place + // in static/global object constructors + if ( NULL == currentHeapArena ) + { + Mem_Init(); + } + void *mem = currentHeapArena->Allocate16( size, tag ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnAlloc(mem, size); +#endif + + return mem; +} + +/* +================== +Mem_Free +================== +*/ +void Mem_Free16( void *ptr ) +{ + if ( !ptr ) + { + return; + } + assert( currentHeapArena != NULL ); + currentHeapArena->Free( ptr ); + +#if defined( _XENON ) && !defined( _FINAL ) + MemTracker::OnDelete(ptr); +#endif +} + +/* +================== +Mem_Size +================== +*/ +int Mem_Size( void *ptr ) +{ + if ( !ptr ) + { + return 0; + } + assert( currentHeapArena != NULL ); + return currentHeapArena->Msize( ptr ); +} + +#endif // #else #ifdef ID_DEBUG_MEMORY + +/* +================== +rvGetAllSysHeaps + +retrieves the specified system heap +================== +*/ + +rvHeap* rvGetSysHeap( Rv_Sys_Heap_ID_t sysHeapID ) +{ + assert( (uint) sysHeapID < (uint) rv_heap_ID_max_count ); + return systemHeapArray[ (uint) sysHeapID ]; +} + +/* +================== +rvGetAllSysHeaps + +retrieves all the MAX_SYSTEM_HEAPS heap pointers into the given array +================== +*/ +void rvGetAllSysHeaps( rvHeap *destSystemHeapArray[MAX_SYSTEM_HEAPS] ) +{ + memcpy(destSystemHeapArray, systemHeapArray, sizeof(systemHeapArray)); +} + +/* +================== +rvSetAllSysHeaps + +associates all the MAX_SYSTEM_HEAPS heap pointers from the given array with their corresponding id value +================== +*/ +void rvSetAllSysHeaps( rvHeap *srcSystemHeapArray[MAX_SYSTEM_HEAPS] ) +{ + memcpy(systemHeapArray, srcSystemHeapArray, sizeof(systemHeapArray)); +} + +#if 0 +/* +================== +rvGetHeapContainingMemory + +Returns pointer to the heap containg the memory specified. If the memory specified +is not in a heap, returns 0. +================== +*/ +rvHeap* rvGetHeapContainingMemory( const void* mem ) +{ + return (currentHeapArena!=0)?currentHeapArena->GetHeap(const_cast(mem)):0; +} +#endif + +/* +================== +rvPushHeapContainingMemory + +Pushes the heap containg the memory specified. If the memory specified +is not in a heap, does nothing and returns false, otherwise returns +true on success. +================== +*/ +bool rvPushHeapContainingMemory( const void* mem ) +{ + if(currentHeapArena) + { + rvHeap* heap=currentHeapArena->GetHeap(const_cast(mem)); + if(heap) + { + heap->PushCurrent(); + return true; + } + } + return false; +} + +#endif // #ifdef _RV_MEM_SYS_SUPPORT diff --git a/src/idlib/rvMemSys.h b/src/idlib/rvMemSys.h new file mode 100644 index 0000000..89ee697 --- /dev/null +++ b/src/idlib/rvMemSys.h @@ -0,0 +1,168 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// rvMemSys.h - Memory management system +// Date: 12/17/04 +// Created by: Dwight Luetscher +// +// Date: 04/01/05 +// Modified by: Marcus Whitlock +// Added permanent heap and new heap push function to push the heap that contains +// a specified piece of memory. +// +// Date: 04/04/05 +// Modified by: Marcus Whitlock +// Added rvAutoHeapCtxt class for push and auto-pop of heap context. +// + +#ifndef __RV_MEM_SYS_H__ +#define __RV_MEM_SYS_H__ + +typedef enum +{ + RV_HEAP_ID_DEFAULT, // heap that exists on application startup + RV_HEAP_ID_PERMANENT, // heap for allocations that have permanent (application scope) lifetime + RV_HEAP_ID_LEVEL, // heap for allocations that have a level lifetime + RV_HEAP_ID_MULTIPLE_FRAME, // heap for run-time allocations that have a lifetime of multiple draw frames + RV_HEAP_ID_SINGLE_FRAME, // heap for run-time allocations that have a lifetime of a single draw frame + RV_HEAP_ID_TEMPORARY, // heap for objects that have a short lifetime (temporaries generally used for level loading) + RV_HEAP_ID_IO_TEMP, // heap for allocations that are temporaries used in I/O operations like level loading or writing out data + rv_heap_ID_max_count // just a count, not a valid type +} +Rv_Sys_Heap_ID_t; + +static const uint MAX_SYSTEM_HEAPS = (uint) rv_heap_ID_max_count; + +#ifdef _RV_MEM_SYS_SUPPORT + +// +// _RV_MEM_SYS_SUPPORT is defined +// + +extern rvHeapArena *currentHeapArena; + +// Functions for getting and setting the system heaps. +void rvSetSysHeap( Rv_Sys_Heap_ID_t sysHeapID, rvHeap *heapPtr ); // associates a heap with the given system heap ID value +rvHeap* rvGetSysHeap( Rv_Sys_Heap_ID_t sysHeapID ); // retrieves the specified system heap +void rvGetAllSysHeaps( rvHeap *destSystemHeapArray[MAX_SYSTEM_HEAPS] ); // retrieves all the MAX_SYSTEM_HEAPS heap pointers into the given array +void rvSetAllSysHeaps( rvHeap *srcSystemHeapArray[MAX_SYSTEM_HEAPS] ); // associates all the MAX_SYSTEM_HEAPS heap pointers from the given array with their corresponding id value +bool rvPushHeapContainingMemory( const void* mem ); // pushes the heap containg the memory specified to the top of the arena stack, making it current - mwhitlock + +void rvEnterArenaCriticalSection( ); // enters the heap arena critical section +void rvExitArenaCriticalSection( ); // exits the heap arena critical section +void rvPushSysHeap(Rv_Sys_Heap_ID_t sysHeapID); // pushes the system heap associated with the given identifier to the top of the arena stack, making it current + +// Useful in situations where a heap is pushed, but a return on error or an +// exception could cause the stack to be unwound, bypassing the heap pop +// operation - mwhitlock. +class rvAutoHeapCtxt +{ + bool mPushed; + +public: + rvAutoHeapCtxt(void) : + mPushed(false) + { + // Should never call this. + assert(0); + } + + rvAutoHeapCtxt(Rv_Sys_Heap_ID_t sysHeapID) : + mPushed(false) + { + rvPushSysHeap(sysHeapID); + mPushed = true; + } + + rvAutoHeapCtxt(const void* mem) : + mPushed(false) + { + mPushed = rvPushHeapContainingMemory( mem ); + } + + ~rvAutoHeapCtxt(void) + { + if(mPushed) + { + currentHeapArena->Pop(); + } + } +}; + +// +// RV_PUSH_SYS_HEAP_ID() +// Push system heaps by their ID (always available to idLib, Game and executable). +// +#define RV_PUSH_SYS_HEAP_ID(sysHeapID) rvPushSysHeap(sysHeapID) +#define RV_PUSH_SYS_HEAP_ID_AUTO(varName,sysHeapID) rvAutoHeapCtxt varName(sysHeapID) + +// +// RV_PUSH_HEAP_MEM() +// Push the heap containing the piece of memory pointed to. Note that if the +// piece of memory is not from a heap, no heap will be pushed. +// +#define RV_PUSH_HEAP_MEM(memPtr) rvPushHeapContainingMemory(memPtr) +#define RV_PUSH_HEAP_MEM_AUTO(varName,memPtr) rvAutoHeapCtxt varName(memPtr) + +// +// RV_PUSH_HEAP_PTR() +// Local heaps used mainly by executable (idLib and Game would use these only +// if heap was passed in) +// +#define RV_PUSH_HEAP_PTR(heapPtr) ( (heapPtr)->PushCurrent() ) + +// +// RV_PUSH_SYS_HEAP() +// Pop top of heap stack, regardless of how it was pushed. +// +#define RV_POP_HEAP() ( currentHeapArena->Pop() ) + +// The following versions enter/exit the heap arena's critical section so that +// critical section protection remains active between a push/pop pair (NOTE that +// the heap and heap arena are always protected by critical sections within a single method call) +#define RV_PUSH_SYS_HEAP_ENTER_CRIT_SECT(sysHeapID) { rvEnterArenaCriticalSection( ); rvPushSysHeap( sysHeapID ); } +#define RV_PUSH_HEAP_ENTER_CRIT_SECT(heapPtr) { rvEnterArenaCriticalSection( ); (heapPtr)->PushCurrent( ); } +#define RV_POP_HEAP_EXIT_CRIT_SECT() { currentHeapArena->Pop( ); rvExitArenaCriticalSection( ); } + +#else // #ifdef _RV_MEM_SYS_SUPPORT + +// +// _RV_MEM_SYS_SUPPORT is not defined +// + +#define RV_PUSH_SYS_HEAP_ID(sysHeapID) +#define RV_PUSH_SYS_HEAP_ID_AUTO(varName,sysHeapID) +#define RV_PUSH_HEAP_MEM(memPtr) +#define RV_PUSH_HEAP_MEM_AUTO(varName,memPtr) +#define RV_PUSH_HEAP_PTR(heapPtr) +#define RV_POP_HEAP() + +#define RV_PUSH_SYS_HEAP_ENTER_CRIT_SECT(sysHeapID) +#define RV_PUSH_HEAP_ENTER_CRIT_SECT(heapPtr) +#define RV_POP_HEAP_EXIT_CRIT_SECT() + +#endif // #else not #ifdef _RV_MEM_SYS_SUPPORT + +#endif // #ifndef __RV_MEM_SYS_H__ diff --git a/src/idlib/threads/AutoCrit.h b/src/idlib/threads/AutoCrit.h new file mode 100644 index 0000000..4db657d --- /dev/null +++ b/src/idlib/threads/AutoCrit.h @@ -0,0 +1,246 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AUTOCRIT_H__ +#define __AUTOCRIT_H__ + +#ifdef RV_USE_CRITICALSECTION +// This class eliminates the need to figure out where to call InitializeCriticalSection +// and DeleteCriticalSection. It makes critical sections into a meaningful class. More +// importantly, now when they are created as statics, they are already initialized, so +// the AutoCrit doesn't have to worry about checking to see if they've been initialized yet +class CriticalSection +{ +public: + ID_INLINE CriticalSection() + { + InitializeCriticalSection(&mCrit); + } + ID_INLINE ~CriticalSection() + { + DeleteCriticalSection(&mCrit); + } + + ID_INLINE void Enter() + { + while(!Try()) + { + Sleep(0); // give another thread a chance to run to try to eliminate deadlock + } + } + ID_INLINE void Leave() + { + LeaveCriticalSection(&mCrit); + } + + ID_INLINE bool Try() + { + return TryEnterCriticalSection(&mCrit) != 0; + } + +private: + CRITICAL_SECTION mCrit; +}; +#endif + +#ifdef RV_USE_AUTOCRIT +// Enters a critical section unique to the type of object t on construction +// Exits that critical section on destruction. Effectively synchronizes calls +// to ANY instance of type t +template +class AutoCrit +{ +public: + ID_INLINE AutoCrit() + { + sCrit.Enter(); + } + ID_INLINE ~AutoCrit() + { + sCrit.Leave(); + } +private: + static CriticalSection sCrit; +}; + +template +CriticalSection AutoCrit::sCrit; + +template +class ConditionalAutoCrit +{ +public: + ID_INLINE ConditionalAutoCrit() + { + if(sThreading) + { + sCrit.Enter(); + mMustLeave = true; + } + else + { + mMustLeave = false; + } + } + ID_INLINE ~ConditionalAutoCrit() + { + if(mMustLeave) + { + sCrit.Leave(); + } + } + + static ID_INLINE SetThreading(bool threading) + { + // not threadsafe...must be called BEFORE entering and AFTER leaving a threaded section, not during + sThreading = threading; + } + + static ID_INLINE bool IsThreading() + { + return sThreading; + } + +private: + static bool sThreading; + static CriticalSection sCrit; + bool mMustLeave; +}; + +template +CriticalSection ConditionalAutoCrit::sCrit; + +template +bool ConditionalAutoCrit::sThreading=false; + +#else +// these compile out completely in release and removes the need +// for ifdefs all over the code +template +class AutoCrit +{ +public: + AutoCrit() { } + ~AutoCrit() { } +}; + +template +class ConditionalAutoCrit +{ +public: + ConditionalAutoCrit() { } + ~ConditionalAutoCrit() { } +}; +#endif + +#ifdef RV_USE_AUTOINSTANCECRIT + +class CritInfo +{ +public: + ID_INLINE CritInfo() { refCount = 0; } + int refCount; + CriticalSection crit; +}; + + +// Enters a critical section unique to the specific instance of an object of type t on construction +// Exits that critical section on destruction. This is templated instead of using void * in order +// to reduce search time when entering and leaving. Effectively synchronizes on a single instance +// of an object. This would allow containers for example to not be synchronized to other containers +// of the same type. +template +class AutoInstanceCrit +{ +public: + + ID_INLINE AutoInstanceCrit(t *ptr) + { + CritInfo *critPtr = NULL; + //AutoCrit > crit; // protect access to the map + critSect.Enter(); + mPtr = ptr; + int i = sPtrs.FindIndex(mPtr); + + if(i == -1) // not found + { + i = sPtrs.Num(); + sPtrs.Append(mPtr); + sInfos.Append(CritInfo()); + } + critPtr = &(sInfos[i]); + critPtr->refCount++; + critSect.Leave(); + + critPtr->crit.Enter(); + } + + ID_INLINE ~AutoInstanceCrit() + { + CritInfo *critPtr = NULL; + { + //AutoCrit > crit; // protect access to the map + critSect.Enter(); + int i = sPtrs.FindIndex(mPtr); + assert(i != -1); + critPtr = &(sInfos[i]); + critPtr->refCount--; // decrement refcount + critSect.Leave(); + } + critPtr->crit.Leave(); + /* + if(critPtr->refCount == 0) + { + sPtrs.RemoveIndex(i); + sInfos.RemoveIndex(i); + }*/ + } + +private: + t *mPtr; // the pointer we're protecting + static CriticalSection critSect; + static idList sPtrs; + static idList sInfos; +}; +template +idList AutoInstanceCrit::sPtrs; + +template +idList AutoInstanceCrit::sInfos; + +template +CriticalSection AutoInstanceCrit::critSect; + +#else +// this compiles out completely in release and removes the need +// for ifdefs all over the code +template +class AutoInstanceCrit +{ +public: + ID_INLINE AutoInstanceCrit(t *ptr) { } +}; +#endif + +#endif diff --git a/src/mpgame/AF.cpp b/src/mpgame/AF.cpp new file mode 100644 index 0000000..804ecab --- /dev/null +++ b/src/mpgame/AF.cpp @@ -0,0 +1,1359 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ +#define ARTICULATED_FIGURE_ANIM "af_pose" +#define POSE_BOUNDS_EXPANSION 5.0f + +/* +================ +idAF::idAF +================ +*/ +idAF::idAF( void ) { + self = NULL; + animator = NULL; + modifiedAnim = 0; + baseOrigin.Zero(); + baseAxis.Identity(); + poseTime = -1; + restStartTime = -1; + isLoaded = false; + isActive = false; + hasBindConstraints = false; +} + +/* +================ +idAF::~idAF +================ +*/ +idAF::~idAF( void ) { +} + +/* +================ +idAF::Save +================ +*/ +void idAF::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + savefile->WriteString( GetName() ); + savefile->WriteBool( hasBindConstraints ); + savefile->WriteVec3( baseOrigin ); + savefile->WriteMat3( baseAxis ); + savefile->WriteInt( poseTime ); + savefile->WriteInt( restStartTime ); + savefile->WriteBool( isLoaded ); + savefile->WriteBool( isActive ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idAF::Restore +================ +*/ +void idAF::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( name ); + savefile->ReadBool( hasBindConstraints ); + savefile->ReadVec3( baseOrigin ); + savefile->ReadMat3( baseAxis ); + savefile->ReadInt( poseTime ); + savefile->ReadInt( restStartTime ); + savefile->ReadBool( isLoaded ); + savefile->ReadBool( isActive ); + + animator = NULL; + modifiedAnim = 0; + + if ( self ) { + SetAnimator( self->GetAnimator() ); + Load( self, name ); + if ( hasBindConstraints ) { + AddBindConstraints(); + } + } + + savefile->ReadStaticObject( physicsObj ); + + if ( self ) { + if ( isActive ) { + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + + // switch to articulated figure physics + self->RestorePhysics( &physicsObj ); + physicsObj.EnableClip(); + } + UpdateAnimation(); + } +} + +/* +================ +idAF::UpdateAnimation +================ +*/ +bool idAF::UpdateAnimation( void ) { + int i; + idVec3 origin, renderOrigin, bodyOrigin; + idMat3 axis, renderAxis, bodyAxis; + renderEntity_t *renderEntity; + + if ( !IsLoaded() ) { + return false; + } + + if ( !IsActive() ) { + return false; + } + + renderEntity = self->GetRenderEntity(); + if ( !renderEntity ) { + return false; + } + + if ( physicsObj.IsAtRest() ) { + if ( restStartTime == physicsObj.GetRestStartTime() ) { + return false; + } + restStartTime = physicsObj.GetRestStartTime(); + } + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // create an animation frame which reflects the current pose of the articulated figure + animator->InitAFPose(); + for ( i = 0; i < jointMods.Num(); i++ ) { + // check for the origin joint + if ( jointMods[i].jointHandle == 0 ) { + continue; + } + bodyOrigin = physicsObj.GetOrigin( jointMods[i].bodyId ); + bodyAxis = physicsObj.GetAxis( jointMods[i].bodyId ); + axis = jointMods[i].jointBodyAxis.Transpose() * ( bodyAxis * renderAxis.Transpose() ); + origin = ( bodyOrigin - jointMods[i].jointBodyOrigin * axis - renderOrigin ) * renderAxis.Transpose(); + animator->SetAFPoseJointMod( jointMods[i].jointHandle, jointMods[i].jointMod, axis, origin ); + } + animator->FinishAFPose( modifiedAnim, GetBounds().Expand( POSE_BOUNDS_EXPANSION ), gameLocal.time ); + animator->SetAFPoseBlendWeight( 1.0f ); + + return true; +} + +/* +================ +idAF::GetBounds + + returns bounds for the current pose +================ +*/ +idBounds idAF::GetBounds( void ) const { + int i; + idAFBody *body; + idVec3 origin, entityOrigin; + idMat3 axis, entityAxis; + idBounds bounds, b; + + bounds.Clear(); + + // get model base transform + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + + entityAxis = baseAxis.Transpose() * axis; + entityOrigin = origin - baseOrigin * entityAxis; + + // get bounds relative to base + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + origin = ( body->GetWorldOrigin() - entityOrigin ) * entityAxis.Transpose(); + axis = body->GetWorldAxis() * entityAxis.Transpose(); + b.FromTransformedBounds( body->GetClipModel()->GetBounds(), origin, axis ); + + bounds += b; + } + + return bounds; +} + +/* +================ +idAF::SetupPose + + Transforms the articulated figure to match the current animation pose of the given entity. +================ +*/ +void idAF::SetupPose( idEntity *ent, int time ) { + int i; + idAFBody *body; + idVec3 origin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + } + + if ( isActive ) { + physicsObj.UpdateClipModels(); + } +} + +/* +================ +idAF::ChangePose + + Change the articulated figure to match the current animation pose of the given entity + and set the velocity relative to the previous pose. +================ +*/ +void idAF::ChangePose( idEntity *ent, int time ) { + int i; + float invDelta; + idAFBody *body; + idVec3 origin, lastOrigin; + idMat3 axis; + idAnimator *animatorPtr; + renderEntity_t *renderEntity; + + if ( !IsLoaded() || !ent ) { + return; + } + + animatorPtr = ent->GetAnimator(); + if ( !animatorPtr ) { + return; + } + + renderEntity = ent->GetRenderEntity(); + if ( !renderEntity ) { + return; + } + + // if the animation is driven by the physics + if ( self->GetPhysics() == &physicsObj ) { + return; + } + + // if the pose was already updated this frame + if ( poseTime == time ) { + return; + } + invDelta = 1.0f / MS2SEC( time - poseTime ); + poseTime = time; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + animatorPtr->GetJointTransform( jointMods[i].jointHandle, time, origin, axis ); + lastOrigin = body->GetWorldOrigin(); + body->SetWorldOrigin( renderEntity->origin + ( origin + jointMods[i].jointBodyOrigin * axis ) * renderEntity->axis ); + body->SetWorldAxis( jointMods[i].jointBodyAxis * axis * renderEntity->axis ); + body->SetLinearVelocity( ( body->GetWorldOrigin() - lastOrigin ) * invDelta ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::EntitiesTouchingAF +================ +*/ +int idAF::EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const { + int i, j, numClipModels; + idAFBody *body; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + int numTouching; + + if ( !IsLoaded() ) { + return 0; + } + + numTouching = 0; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( self, physicsObj.GetAbsBounds(), -1, clipModels, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + for ( j = 0; j < numClipModels; j++ ) { + cm = clipModels[j]; + + if ( !cm || cm->GetEntity() == self ) { + continue; + } + + if ( !cm->IsTraceModel() ) { + continue; + } + + if ( !body->GetClipModel()->GetAbsBounds().IntersectsBounds( cm->GetAbsBounds() ) ) { + continue; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.ContentsModel( self, body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), -1, cm->GetCollisionModel(), cm->GetOrigin(), cm->GetAxis() ) ) { +// RAVEN END + touchList[ numTouching ].touchedByBody = body; + touchList[ numTouching ].touchedClipModel = cm; + touchList[ numTouching ].touchedEnt = cm->GetEntity(); + numTouching++; + clipModels[j] = NULL; + } + } + } + + return numTouching; +} + +/* +================ +idAF::BodyForClipModelId +================ +*/ +int idAF::BodyForClipModelId( int id ) const { + if ( id >= 0 ) { + return id; + } else { + id = CLIPMODEL_ID_TO_JOINT_HANDLE( id ); + if ( id < jointBody.Num() ) { + return jointBody[id]; + } else { + return 0; + } + } +} + +/* +================ +idAF::GetPhysicsToVisualTransform +================ +*/ +void idAF::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const { + origin = - baseOrigin; + axis = baseAxis.Transpose(); +} + +/* +================ +idAF::GetImpactInfo +================ +*/ +void idAF::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + SetupPose( self, gameLocal.time ); + physicsObj.GetImpactInfo( BodyForClipModelId( id ), point, info ); +} + +/* +================ +idAF::ApplyImpulse +================ +*/ +void idAF::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) { + SetupPose( self, gameLocal.time ); + physicsObj.ApplyImpulse( BodyForClipModelId( id ), point, impulse ); +} + +/* +================ +idAF::AddForce +================ +*/ +void idAF::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + SetupPose( self, gameLocal.time ); + physicsObj.AddForce( BodyForClipModelId( id ), point, force ); +} + +/* +================ +idAF::AddBody + + Adds a body. +================ +*/ +void idAF::AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ) { + int index; + jointHandle_t handle; + idVec3 origin; + idMat3 axis; + + handle = animator->GetJointHandle( jointName ); + if ( handle == INVALID_JOINT ) { + gameLocal.Error( "idAF for entity '%s' at (%s) modifies unknown joint '%s'", self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), jointName ); + } + + assert( handle < animator->NumJoints() ); + origin = joints[ handle ].ToVec3(); + axis = joints[ handle ].ToMat3(); + + index = jointMods.Num(); + jointMods.SetNum( index + 1, false ); + jointMods[index].bodyId = physicsObj.GetBodyId( body ); + jointMods[index].jointHandle = handle; + jointMods[index].jointMod = mod; + jointMods[index].jointBodyOrigin = ( body->GetWorldOrigin() - origin ) * axis.Transpose(); + jointMods[index].jointBodyAxis = body->GetWorldAxis() * axis.Transpose(); +} + +/* +================ +idAF::SetBase + + Sets the base body. +================ +*/ +void idAF::SetBase( idAFBody *body, const idJointMat *joints ) { + physicsObj.ForceBodyId( body, 0 ); + baseOrigin = body->GetWorldOrigin(); + baseAxis = body->GetWorldAxis(); + AddBody( body, joints, animator->GetJointName( animator->GetFirstChild( "origin" ) ), AF_JOINTMOD_AXIS ); +} + +/* +================ +idAF::LoadBody +================ +*/ +bool idAF::LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ) { + int id, i; + float length, mass; + idTraceModel trm; + idClipModel *clip; + idAFBody *body; + idMat3 axis, inertiaTensor; + idVec3 centerOfMass, origin; + idBounds bounds; + idList jointList; + + origin = fb->origin.ToVec3(); + axis = fb->angles.ToMat3(); + bounds[0] = fb->v1.ToVec3(); + bounds[1] = fb->v2.ToVec3(); + + switch( fb->modelType ) { + case TRM_BOX: { + trm.SetupBox( bounds ); + break; + } + case TRM_OCTAHEDRON: { + trm.SetupOctahedron( bounds ); + break; + } + case TRM_DODECAHEDRON: { + trm.SetupDodecahedron( bounds ); + break; + } + case TRM_CYLINDER: { + trm.SetupCylinder( bounds, fb->numSides ); + break; + } + case TRM_CONE: { + // place the apex at the origin + bounds[0].z -= bounds[1].z; + bounds[1].z = 0.0f; + trm.SetupCone( bounds, fb->numSides ); + break; + } + case TRM_BONE: { + // direction of bone + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + length = axis[2].Normalize(); + // axis of bone trace model + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + // create bone trace model + trm.SetupBone( length, fb->width ); + break; + } + default: + assert( 0 ); + break; + } + + trm.GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + trm.Translate( -centerOfMass ); + origin += centerOfMass * axis; + + body = physicsObj.GetBody( fb->name ); + if ( body ) { + clip = body->GetClipModel(); + if ( !clip->IsEqual( trm ) ) { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body->SetClipModel( clip ); + } + clip->SetContents( fb->contents ); + body->SetDensity( fb->density, fb->inertiaScale ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( axis ); + id = physicsObj.GetBodyId( body ); + } + else { + clip = new idClipModel( trm ); + clip->SetContents( fb->contents ); + clip->Link( self, 0, origin, axis ); + body = new idAFBody( fb->name, clip, fb->density ); + if ( fb->inertiaScale != mat3_identity ) { + body->SetDensity( fb->density, fb->inertiaScale ); + } + id = physicsObj.AddBody( body ); + } + if ( fb->linearFriction != -1.0f ) { + body->SetFriction( fb->linearFriction, fb->angularFriction, fb->contactFriction ); + } + body->SetClipMask( fb->clipMask ); + body->SetSelfCollision( fb->selfCollision ); + + if ( fb->jointName == "origin" ) { + SetBase( body, joints ); + } else { + AFJointModType_t mod; + if ( fb->jointMod == DECLAF_JOINTMOD_AXIS ) { + mod = AF_JOINTMOD_AXIS; + } else if ( fb->jointMod == DECLAF_JOINTMOD_ORIGIN ) { + mod = AF_JOINTMOD_ORIGIN; + } else if ( fb->jointMod == DECLAF_JOINTMOD_BOTH ) { + mod = AF_JOINTMOD_BOTH; + } else { + mod = AF_JOINTMOD_AXIS; + } + AddBody( body, joints, fb->jointName, mod ); + } + + if ( fb->frictionDirection.ToVec3() != vec3_origin ) { + body->SetFrictionDirection( fb->frictionDirection.ToVec3() ); + } + if ( fb->contactMotorDirection.ToVec3() != vec3_origin ) { + body->SetContactMotorDirection( fb->contactMotorDirection.ToVec3() ); + } + + // update table to find the nearest articulated figure body for a joint of the skeletal model + animator->GetJointList( fb->containedJoints, jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + if ( jointBody[ jointList[ i ] ] != -1 ) { + +// RAVEN BEGIN +// kfuller: better load time warning for joints contained by multiple bodies + gameLocal.Warning( "%s: body '%s': joint '%s' is already contained by body '%s'", + name.c_str(), fb->name.c_str(), + animator->GetJointName( (jointHandle_t)jointList[i] ), + physicsObj.GetBody( jointBody[ jointList[ i ] ] )->GetName().c_str() ); +// RAVEN END + } + jointBody[ jointList[ i ] ] = id; + } + + return true; +} + +/* +================ +idAF::LoadConstraint +================ +*/ +bool idAF::LoadConstraint( const idDeclAF_Constraint *fc ) { + idAFBody *body1, *body2; + + body1 = physicsObj.GetBody( fc->body1 ); + body2 = physicsObj.GetBody( fc->body2 ); + + switch( fc->type ) { + case DECLAF_CONSTRAINT_FIXED: { + idAFConstraint_Fixed *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Fixed( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + break; + } + case DECLAF_CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_BallAndSocketJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0], fc->shaft[0].ToVec3() ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1], fc->shaft[0].ToVec3() ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_UniversalJoint( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetShafts( fc->shaft[0].ToVec3(), fc->shaft[1].ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + c->SetConeLimit( fc->limitAxis.ToVec3(), fc->limitAngles[0] ); + break; + } + case idDeclAF_Constraint::LIMIT_PYRAMID: { + idAngles angles = fc->limitAxis.ToVec3().ToAngles(); + angles.roll = fc->limitAngles[2]; + idMat3 axis = angles.ToMat3(); + c->SetPyramidLimit( axis[0], axis[1], fc->limitAngles[0], fc->limitAngles[1] ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_HINGE: { + idAFConstraint_Hinge *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Hinge( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3() ); + c->SetAxis( fc->axis.ToVec3() ); + c->SetFriction( fc->friction ); + switch( fc->limit ) { + case idDeclAF_Constraint::LIMIT_CONE: { + idVec3 left, up, axis, shaft; + fc->axis.ToVec3().OrthogonalBasis( left, up ); + axis = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[0] ); + shaft = left * idRotation( vec3_origin, fc->axis.ToVec3(), fc->limitAngles[2] ); + c->SetLimit( axis, fc->limitAngles[1], shaft ); + break; + } + default: { + c->SetNoLimit(); + break; + } + } + break; + } + case DECLAF_CONSTRAINT_SLIDER: { + idAFConstraint_Slider *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Slider( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAxis( fc->axis.ToVec3() ); + break; + } + case DECLAF_CONSTRAINT_SPRING: { + idAFConstraint_Spring *c; + c = static_cast(physicsObj.GetConstraint( fc->name )); + if ( c ) { + c->SetBody1( body1 ); + c->SetBody2( body2 ); + } + else { + c = new idAFConstraint_Spring( fc->name, body1, body2 ); + physicsObj.AddConstraint( c ); + } + c->SetAnchor( fc->anchor.ToVec3(), fc->anchor2.ToVec3() ); + c->SetSpring( fc->stretch, fc->compress, fc->damping, fc->restLength ); + c->SetLimit( fc->minLength, fc->maxLength ); + break; + } + } + return true; +} + +/* +================ +GetJointTransform +================ +*/ +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + jointHandle_t joint; + + joint = reinterpret_cast(model)->GetJointHandle( jointName ); + if ( ( joint >= 0 ) && ( joint < reinterpret_cast(model)->NumJoints() ) ) { + origin = frame[ joint ].ToVec3(); + axis = frame[ joint ].ToMat3(); + return true; + } else { + return false; + } +} + +/* +================ +idAF::Load +================ +*/ +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one +bool idAF::Load( idEntity *ent, const char *fileName, bool purgeAF /* = false */ ) { +// RAVEN END + int i, j; + const idDeclAF *file; + const idDeclModelDef *modelDef; + idRenderModel *model; + int numJoints; + idJointMat *joints; + + assert( ent ); + + self = ent; + physicsObj.SetSelf( self ); + + if ( animator == NULL ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s): NULL animator\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + name = fileName; + name.StripFileExtension(); + + file = static_cast( declManager->FindType( DECL_AF, name ) ); + if ( !file ) { + gameLocal.Warning( "Couldn't load af '%s' for entity '%s' at (%s)\n", name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + if ( file->bodies.Num() == 0 || file->bodies[0]->jointName != "origin" ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no body which modifies the origin joint.", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + modelDef = animator->ModelDef(); + if ( modelDef == NULL || modelDef->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted modelDef '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), modelDef ? modelDef->GetName() : "" ); + return false; + } + + model = animator->ModelHandle(); + if ( model == NULL || model->IsDefaultModel() ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no or defaulted model '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), model ? model->Name() : "" ); + return false; + } + + // get the modified animation + modifiedAnim = animator->GetAnim( ARTICULATED_FIGURE_ANIM ); + if ( !modifiedAnim ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) has no modified animation '%s'", + name.c_str(), ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(0), ARTICULATED_FIGURE_ANIM ); + return false; + } + + // create the animation frame used to setup the articulated figure + numJoints = animator->NumJoints(); + joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + gameEdit->ANIM_CreateAnimFrame( model, animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset(), animator->RemoveOrigin() ); + + // set all vector positions from model joints + file->Finish( GetJointTransform, joints, animator ); + + // initialize articulated figure physics + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( file->clipMask ); + physicsObj.SetDefaultFriction( file->defaultLinearFriction, file->defaultAngularFriction, file->defaultContactFriction ); + physicsObj.SetSuspendSpeed( file->suspendVelocity, file->suspendAcceleration ); + physicsObj.SetSuspendTolerance( file->noMoveTime, file->noMoveTranslation, file->noMoveRotation ); + physicsObj.SetSuspendTime( file->minMoveTime, file->maxMoveTime ); + physicsObj.SetSelfCollision( file->selfCollision ); +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + physicsObj.SetFastEval( file->fastEval ); +// RAVEN END + + // clear the list with transforms from joints to bodies + jointMods.SetNum( 0, false ); + + // clear the joint to body conversion list + jointBody.AssureSize( animator->NumJoints() ); + for ( i = 0; i < jointBody.Num(); i++ ) { + jointBody[i] = -1; + } + +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + // delete any bodies in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + physicsObj.DeleteBody( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + idAFBody *body = physicsObj.GetBody( i ); + for ( j = 0; j < file->bodies.Num(); j++ ) { + if ( file->bodies[j]->name.Icmp( body->GetName() ) == 0 ) { + break; + } + } + if ( j >= file->bodies.Num() ) { + physicsObj.DeleteBody( i ); + i--; + } + } + } + + // delete any constraints in the physicsObj that are no longer in the idDeclAF + if( purgeAF ) { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } else { + for ( i = 0; i < physicsObj.GetNumConstraints(); i++ ) { + idAFConstraint *constraint = physicsObj.GetConstraint( i ); + for ( j = 0; j < file->constraints.Num(); j++ ) { + if ( file->constraints[j]->name.Icmp( constraint->GetName() ) == 0 && + file->constraints[j]->type == constraint->GetType() ) { + break; + } + } + if ( j >= file->constraints.Num() ) { + physicsObj.DeleteConstraint( i ); + i--; + } + } + } +// RAVEN END + // load bodies from the file + for ( i = 0; i < file->bodies.Num(); i++ ) { + LoadBody( file->bodies[i], joints ); + } + + // load constraints from the file + for ( i = 0; i < file->constraints.Num(); i++ ) { + LoadConstraint( file->constraints[i] ); + } + + physicsObj.UpdateClipModels(); + + // check if each joint is contained by a body + for( i = 0; i < animator->NumJoints(); i++ ) { + if ( jointBody[i] == -1 ) { + gameLocal.Warning( "idAF::Load: articulated figure '%s' for entity '%s' at (%s) joint '%s' is not contained by a body", + name.c_str(), self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0), animator->GetJointName( (jointHandle_t)i ) ); + } + } + + physicsObj.SetMass( file->totalMass ); + physicsObj.SetChanged(); + + // disable the articulated figure for collision detection until activated + physicsObj.DisableClip(); + + isLoaded = true; + + poseTime = -1; + + return true; +} + +/* +================ +idAF::Start +================ +*/ +void idAF::Start( void ) { + if ( !IsLoaded() ) { + return; + } + // clear all animations + animator->ClearAllAnims( gameLocal.time, 0 ); + animator->ClearAllJoints(); + // switch to articulated figure physics + self->SetPhysics( &physicsObj ); + // start the articulated figure physics simulation + physicsObj.EnableClip(); + + physicsObj.Activate(); + isActive = true; +} + +/* +================ +idAF::TestSolid +================ +*/ +bool idAF::TestSolid( void ) const { + int i; + idAFBody *body; + trace_t trace; + idStr str; + bool solid; + + if ( !IsLoaded() ) { + return false; + } + + if ( !af_testSolid.GetBool() ) { + return false; + } + + solid = false; + + for ( i = 0; i < physicsObj.GetNumBodies(); i++ ) { + body = physicsObj.GetBody( i ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Translation( self, trace, body->GetWorldOrigin(), body->GetWorldOrigin(), body->GetClipModel(), body->GetWorldAxis(), body->GetClipMask(), self ) ) { +// RAVEN END + float depth = idMath::Fabs( trace.c.point * trace.c.normal - trace.c.dist ); + + body->SetWorldOrigin( body->GetWorldOrigin() + trace.c.normal * ( depth + 8.0f ) ); + + gameLocal.DWarning( "%s: body '%s' stuck in %d (normal = %.2f %.2f %.2f, depth = %.2f)", self->name.c_str(), + body->GetName().c_str(), trace.c.contents, trace.c.normal.x, trace.c.normal.y, trace.c.normal.z, depth ); + solid = true; + + } + } + return solid; +} + +/* +================ +idAF::StartFromCurrentPose +================ +*/ +void idAF::StartFromCurrentPose( int inheritVelocityTime ) { + if ( !IsLoaded() ) { + return; + } + + // if the ragdoll should inherit velocity from the animation + if ( inheritVelocityTime > 0 ) { + + // make sure the ragdoll is at rest + physicsObj.PutToRest(); + + // set the pose for some time back + SetupPose( self, gameLocal.time - inheritVelocityTime ); + + // change the pose for the current time and set velocities + ChangePose( self, gameLocal.time ); + } + else { + // transform the articulated figure to reflect the current animation pose + SetupPose( self, gameLocal.time ); + } + + physicsObj.UpdateClipModels(); + + TestSolid(); + + Start(); + + UpdateAnimation(); + + // update the render entity origin and axis + self->UpdateModel(); + + // make sure the renderer gets the updated origin and axis + self->Present(); +} + +/* +================ +idAF::Stop +================ +*/ +void idAF::Stop( void ) { + // disable the articulated figure for collision detection + physicsObj.UnlinkClip(); + isActive = false; +} + +/* +================ +idAF::Rest +================ +*/ +void idAF::Rest( void ) { + physicsObj.PutToRest(); +} + +/* +================ +idAF::SetConstraintPosition + + Only moves constraints that bind the entity to another entity. +================ +*/ +void idAF::SetConstraintPosition( const char *name, const idVec3 &pos ) { + idAFConstraint *constraint; + + constraint = GetPhysics()->GetConstraint( name ); + + if ( !constraint ) { + gameLocal.Warning( "can't find a constraint with the name '%s'", name ); + return; + } + + if ( constraint->GetBody2() != NULL ) { + gameLocal.Warning( "constraint '%s' does not bind to another entity", name ); + return; + } + + switch( constraint->GetType() ) { + case CONSTRAINT_BALLANDSOCKETJOINT: { + idAFConstraint_BallAndSocketJoint *bs = static_cast(constraint); + bs->Translate( pos - bs->GetAnchor() ); + break; + } + case CONSTRAINT_UNIVERSALJOINT: { + idAFConstraint_UniversalJoint *uj = static_cast(constraint); + uj->Translate( pos - uj->GetAnchor() ); + break; + } + case CONSTRAINT_HINGE: { + idAFConstraint_Hinge *hinge = static_cast(constraint); + hinge->Translate( pos - hinge->GetAnchor() ); + break; + } + default: { + gameLocal.Warning( "cannot set the constraint position for '%s'", name ); + break; + } + } +} + +/* +================ +idAF::SaveState +================ +*/ +void idAF::SaveState( idDict &args ) const { + int i; + idAFBody *body; + idStr key, value; + + for ( i = 0; i < jointMods.Num(); i++ ) { + body = physicsObj.GetBody( jointMods[i].bodyId ); + + key = "body " + body->GetName(); + value = body->GetWorldOrigin().ToString( 8 ); + value += " "; + value += body->GetWorldAxis().ToAngles().ToString( 8 ); + args.Set( key, value ); + } +} + +/* +================ +idAF::LoadState +================ +*/ +void idAF::LoadState( const idDict &args ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idVec3 origin; + idAngles angles; + + kv = args.MatchPrefix( "body ", NULL ); + while ( kv ) { + + name = kv->GetKey(); + name.Strip( "body " ); + body = physicsObj.GetBody( name ); + if ( body ) { + sscanf( kv->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + body->SetWorldOrigin( origin ); + body->SetWorldAxis( angles.ToMat3() ); + } else { + gameLocal.Warning("Unknown body part %s in articulated figure %s", name.c_str(), this->name.c_str()); + } + + kv = args.MatchPrefix( "body ", kv ); + } + + physicsObj.UpdateClipModels(); +} + +/* +================ +idAF::AddBindConstraints +================ +*/ +void idAF::AddBindConstraints( void ) { + const idKeyValue *kv; + idStr name; + idAFBody *body; + idLexer lexer; + idToken type, bodyName, jointName; + idVec3 origin, renderOrigin; + idMat3 axis, renderAxis; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + idToken jointFriction; +// RAVEN END + + // get the render position + origin = physicsObj.GetOrigin( 0 ); + axis = physicsObj.GetAxis( 0 ); + renderAxis = baseAxis.Transpose() * axis; + renderOrigin = origin - baseOrigin * renderAxis; + + // parse all the bind constraints + for ( kv = args.MatchPrefix( "bindConstraint ", NULL ); kv; kv = args.MatchPrefix( "bindConstraint ", kv ) ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + + lexer.ReadToken( &bodyName ); + body = physicsObj.GetBody( bodyName ); + if ( !body ) { + gameLocal.Warning( "idAF::AddBindConstraints: body '%s' not found on entity '%s'", bodyName.c_str(), self->name.c_str() ); + lexer.FreeSource(); + continue; + } + + if ( type.Icmp( "fixed" ) == 0 ) { + idAFConstraint_Fixed *c; + + c = new idAFConstraint_Fixed( name, body, NULL ); + physicsObj.AddConstraint( c ); + } + else if ( type.Icmp( "ballAndSocket" ) == 0 ) { + idAFConstraint_BallAndSocketJoint *c; + + c = new idAFConstraint_BallAndSocketJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if ( type.Icmp( "universal" ) == 0 ) { + idAFConstraint_UniversalJoint *c; + + c = new idAFConstraint_UniversalJoint( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + +// RAVEN BEGIN +// kfuller: I want joint friction as a spawn arg + if (lexer.ReadToken(&jointFriction)) { + c->SetFriction(jointFriction.GetFloatValue()); + } +// RAVEN END + } + else if (type.Icmp( "hinge" ) == 0 ) + { + idAFConstraint_Hinge *c; + c = new idAFConstraint_Hinge( name, body, NULL ); + physicsObj.AddConstraint( c ); + lexer.ReadToken( &jointName ); + + jointHandle_t joint = animator->GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) + { + gameLocal.Warning( "idAF::AddBindConstraints: joint '%s' not found\n", jointName.c_str() ); + } + animator->GetJointTransform( joint, gameLocal.time, origin, axis ); + c->SetAnchor( renderOrigin + origin * renderAxis ); + c->SetAxis(renderAxis[1]); + c->SetNoLimit(); + if (lexer.ReadToken(&jointFriction)) + { + float frictionValue = 0; + + sscanf(jointFriction.c_str(), "%f", &frictionValue); + c->SetFriction(frictionValue); + } + idToken hingeAxis; + if (lexer.ReadToken(&hingeAxis)) + { + int hingeAxisValue = 1; + + sscanf(hingeAxis.c_str(), "%d", &hingeAxisValue); + if (hingeAxisValue >= 0 && hingeAxisValue <= 2) + { + c->SetAxis(renderAxis[hingeAxisValue]); + } + } + } +// RAVEN END + else { + gameLocal.Warning( "idAF::AddBindConstraints: unknown constraint type '%s' on entity '%s'", type.c_str(), self->name.c_str() ); + } + + lexer.FreeSource(); + } + + hasBindConstraints = true; +} + +/* +================ +idAF::RemoveBindConstraints +================ +*/ +void idAF::RemoveBindConstraints( void ) { + const idKeyValue *kv; + + if ( !IsLoaded() ) { + return; + } + + const idDict &args = self->spawnArgs; + idStr name; + + kv = args.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + name = kv->GetKey(); + name.Strip( "bindConstraint " ); + + if ( physicsObj.GetConstraint( name ) ) { + physicsObj.DeleteConstraint( name ); + } + + kv = args.MatchPrefix( "bindConstraint ", kv ); + } + + hasBindConstraints = false; +} diff --git a/src/mpgame/AF.h b/src/mpgame/AF.h new file mode 100644 index 0000000..eb31abf --- /dev/null +++ b/src/mpgame/AF.h @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_AF_H__ +#define __GAME_AF_H__ + + +/* +=============================================================================== + + Articulated figure controller. + +=============================================================================== +*/ + +typedef struct jointConversion_s { + int bodyId; // id of the body + jointHandle_t jointHandle; // handle of joint this body modifies + AFJointModType_t jointMod; // modify joint axis, origin or both + idVec3 jointBodyOrigin; // origin of body relative to joint + idMat3 jointBodyAxis; // axis of body relative to joint +} jointConversion_t; + +typedef struct afTouch_s { + idEntity * touchedEnt; + idClipModel * touchedClipModel; + idAFBody * touchedByBody; +} afTouch_t; + +class idAF { +public: + idAF( void ); + ~idAF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetAnimator( idAnimator *a ) { animator = a; } +// RAVEN BEGIN +// ddynerman: purge constraints/joints before loading a new one + bool Load( idEntity *ent, const char *fileName, bool purgeAF = false ); +// RAVEN END + bool IsLoaded( void ) const { return isLoaded && self != NULL; } + const char * GetName( void ) const { return name.c_str(); } + void SetupPose( idEntity *ent, int time ); + void ChangePose( idEntity *ent, int time ); + int EntitiesTouchingAF( afTouch_t touchList[ MAX_GENTITIES ] ) const; + void Start( void ); + void StartFromCurrentPose( int inheritVelocityTime ); + void Stop( void ); + void Rest( void ); + bool IsActive( void ) const { return isActive; } + void SetConstraintPosition( const char *name, const idVec3 &pos ); + + idPhysics_AF * GetPhysics( void ) { return &physicsObj; } + const idPhysics_AF * GetPhysics( void ) const { return &physicsObj; } + idBounds GetBounds( void ) const; + bool UpdateAnimation( void ); + + void GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) const; + void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + idPhysics_AF physicsObj; // articulated figure physics + bool TestSolid( void ) const; + +protected: + idStr name; // name of the loaded .af file + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // anim to modify + idVec3 baseOrigin; // offset of base body relative to skeletal model origin + idMat3 baseAxis; // axis of base body relative to skeletal model origin + idListjointMods; // list with transforms from skeletal model joints to articulated figure bodies + idList jointBody; // table to find the nearest articulated figure body for a joint of the skeletal model + int poseTime; // last time the articulated figure was transformed to reflect the current animation pose + int restStartTime; // time the articulated figure came to rest + bool isLoaded; // true when the articulated figure is properly loaded + bool isActive; // true if the articulated figure physics is active + bool hasBindConstraints; // true if the bind constraints have been added + +protected: + void SetBase( idAFBody *body, const idJointMat *joints ); + void AddBody( idAFBody *body, const idJointMat *joints, const char *jointName, const AFJointModType_t mod ); + + bool LoadBody( const idDeclAF_Body *fb, const idJointMat *joints ); + bool LoadConstraint( const idDeclAF_Constraint *fc ); + +}; + +#endif /* !__GAME_AF_H__ */ diff --git a/src/mpgame/AFEntity.cpp b/src/mpgame/AFEntity.cpp new file mode 100644 index 0000000..78cb50f --- /dev/null +++ b/src/mpgame/AFEntity.cpp @@ -0,0 +1,3226 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + + idMultiModelAF + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMultiModelAF ) +END_CLASS + +/* +================ +idMultiModelAF::Spawn +================ +*/ +void idMultiModelAF::Spawn( void ) { + physicsObj.SetSelf( this ); +} + +/* +================ +idMultiModelAF::~idMultiModelAF +================ +*/ +idMultiModelAF::~idMultiModelAF( void ) { + int i; + + for ( i = 0; i < modelDefHandles.Num(); i++ ) { + if ( modelDefHandles[i] != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandles[i] ); + modelDefHandles[i] = -1; + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMultiModelAF::SetModelForId +================ +*/ +void idMultiModelAF::SetModelForId( int id, const idStr &modelName ) { + modelHandles.AssureSize( id+1, NULL ); + modelDefHandles.AssureSize( id+1, -1 ); + modelHandles[id] = renderModelManager->FindModel( modelName ); +} + +/* +================ +idMultiModelAF::Present +================ +*/ +void idMultiModelAF::Present( void ) { + int i; + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + for ( i = 0; i < modelHandles.Num(); i++ ) { + + if ( !modelHandles[i] ) { + continue; + } + + renderEntity.origin = physicsObj.GetOrigin( i ); + renderEntity.axis = physicsObj.GetAxis( i ); + renderEntity.hModel = modelHandles[i]; + renderEntity.bodyId = i; + + // add to refresh list + if ( modelDefHandles[i] == -1 ) { + modelDefHandles[i] = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandles[i], &renderEntity ); + } + } +} + +/* +================ +idMultiModelAF::Think +================ +*/ +void idMultiModelAF::Think( void ) { + RunPhysics(); + Present(); +} + + +/* +=============================================================================== + + idChain + +=============================================================================== +*/ + +CLASS_DECLARATION( idMultiModelAF, idChain ) +END_CLASS + +/* +================ +idChain::BuildChain + + builds a chain hanging down from the ceiling + the highest link is a child of the link below it etc. + this allows an object to be attached to multiple chains while keeping a single tree structure +================ +*/ +void idChain::BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld ) { + int i; + float halfLinkLength = linkLength * 0.5f; + idTraceModel trm; + idClipModel *clip; + idAFBody *body, *lastBody; + idAFConstraint_BallAndSocketJoint *bsj; + idAFConstraint_UniversalJoint *uj; + idVec3 org; + + // create a trace model + trm = idTraceModel( linkLength, linkWidth ); + trm.Translate( -trm.offset ); + + org = origin - idVec3( 0, 0, halfLinkLength ); + + lastBody = NULL; + for ( i = 0; i < numLinks; i++ ) { + + // add body +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clip = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip->SetContents( CONTENTS_SOLID ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, org, mat3_identity ); +// RAVEN END +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + body = new idAFBody( name + idStr(i), clip, density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.AddBody( body ); + + // visual model for body + SetModelForId( physicsObj.GetBodyId( body ), spawnArgs.GetString( "model" ) ); + + // add constraint + if ( bindToWorld ) { + if ( !lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), body, lastBody ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, -1 ), idVec3( 0, 0, 1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, -1 ), 30.0f ); + //uj->SetPyramidLimit( idVec3( 0, 0, -1 ), idVec3( 1, 0, 0 ), 90.0f, 30.0f ); + } + else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + uj = new idAFConstraint_UniversalJoint( name + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + uj->SetShafts( idVec3( 0, 0, 1 ), idVec3( 0, 0, -1 ) ); + //uj->SetConeLimit( idVec3( 0, 0, 1 ), 30.0f ); + } + uj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + uj->SetFriction( 0.9f ); + physicsObj.AddConstraint( uj ); + } + else { + if ( lastBody ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + bsj = new idAFConstraint_BallAndSocketJoint( "joint" + idStr(i), lastBody, body ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + bsj->SetAnchor( org + idVec3( 0, 0, halfLinkLength ) ); + bsj->SetConeLimit( idVec3( 0, 0, 1 ), 60.0f, idVec3( 0, 0, 1 ) ); + physicsObj.AddConstraint( bsj ); + } + } + + org[2] -= linkLength; + + lastBody = body; + } +} + +/* +================ +idChain::Spawn +================ +*/ +void idChain::Spawn( void ) { + int numLinks; + float length, linkLength, linkWidth, density; + bool drop; + idVec3 origin; + + spawnArgs.GetBool( "drop", "0", drop ); + spawnArgs.GetInt( "links", "3", numLinks ); + spawnArgs.GetFloat( "length", idStr( numLinks * 32.0f ), length ); + spawnArgs.GetFloat( "width", "8", linkWidth ); + spawnArgs.GetFloat( "density", "0.2", density ); + linkLength = length / numLinks; + origin = GetPhysics()->GetOrigin(); + + // initialize physics + physicsObj.SetSelf( this ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY ); + SetPhysics( &physicsObj ); + + BuildChain( "link", origin, linkLength, linkWidth, density, numLinks, !drop ); +} + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +const idEventDef EV_ClearAnims( "clearAnims" ); + +CLASS_DECLARATION( idAnimatedEntity, idAFAttachment ) +// RAVEN BEGIN +// jshepard: we'd like these to animate. + EVENT( AI_PlayAnim, idAFAttachment::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAFAttachment::Event_PlayCycle ) + EVENT( EV_ClearAnims, idAFAttachment::Event_ClearAnims ) +// RAVEN END +END_CLASS + +/* +===================== +idAFAttachment::idAFAttachment +===================== +*/ +idAFAttachment::idAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; + +// RAVEN BEGIN +// jscott: Lip Syncing variables + lipSyncAnim = 0; + lipSyncData = NULL; + soundJoint = INVALID_JOINT; +// MCG + noPlayerImpactFX = false; +// RAVEN END +} + +/* +===================== +idAFAttachment::~idAFAttachment +===================== +*/ +idAFAttachment::~idAFAttachment( void ) { + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN + EndLipSyncing(); +// RAVEN END +} + +/* +===================== +idAFAttachment::Spawn +===================== +*/ +void idAFAttachment::Spawn( void ) { + idStr jointName; + + idleAnim = animator.GetAnim( "idle" ); + +// RAVEN BEGIN + // Init the lip sync data + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } + + // Grab the joint to play sounds off + soundJoint = INVALID_JOINT; + spawnArgs.GetString( "sound_bone", "", jointName ); + + // Do we want a bone to play sounds on? + if( jointName.Length() ) { + + // Try to find the specified bone + soundJoint = animator.GetJointHandle( jointName ); + + if ( soundJoint == INVALID_JOINT ) { + // The def specified a bone for this and we can't find it - warn them. + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +// RAVEN END +} + +/* +===================== +idAFAttachment::InitCopyJoints +===================== +*/ +void idAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on entity %s", jointName, name.c_str() ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %s", kv->GetValue().c_str(), name.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +idAFAttachment::CopyJointsFromBody +===================== +*/ +void idAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +idAFAttachment::SetBody +===================== +*/ +void idAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + fl.takedamage = true; + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +idAFAttachment::ClearBody +===================== +*/ +void idAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +idAFAttachment::GetBody +===================== +*/ +idEntity *idAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Save + +archive object for savegame file +================ +*/ +void idAFAttachment::Save( idSaveGame *savefile ) const { + int i; + + body.Save ( savefile ); + savefile->WriteInt( idleAnim ); + savefile->WriteJoint( damageJoint ); + savefile->WriteJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->WriteInt( lipSyncAnim ); +// RAVEN END + + savefile->WriteInt( copyJoints.Num() ); + for( i = 0; i < copyJoints.Num(); i++ ) { + savefile->WriteInt( copyJoints[i].mod ); + savefile->WriteJoint( copyJoints[i].from ); + savefile->WriteJoint( copyJoints[i].to ); + } + + bool hadCombatModel = false; + if ( combatModel ) { + hadCombatModel = true; + } + savefile->WriteBool( hadCombatModel ); + + savefile->WriteBool( noPlayerImpactFX ); +} + +/* +================ +idAFAttachment::Restore + +unarchives object from save game file +================ +*/ +void idAFAttachment::Restore( idRestoreGame *savefile ) { + body.Restore ( savefile ); + savefile->ReadInt( idleAnim ); + savefile->ReadJoint( damageJoint ); + savefile->ReadJoint( soundJoint ); + +// RAVEN BEGIN +// jscott: for lipsyncing + savefile->ReadInt( lipSyncAnim ); +// jscott: difficult to start a sound mid sentence and impossible to sync up the timing with the animation + lipSyncData = NULL; +// RAVEN END + + int i; + int num; + savefile->ReadInt( num ); + copyJoints.SetNum( num ); + for( i = 0; i < num; i++ ) { + int val; + savefile->ReadInt( val ); + copyJoints[i].mod = static_cast( val ); + savefile->ReadJoint( copyJoints[i].from ); + savefile->ReadJoint( copyJoints[i].to ); + } + + bool hadCombatModel; + savefile->ReadBool( hadCombatModel ); + if ( hadCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + savefile->ReadBool( noPlayerImpactFX ); + + lipSyncAnim = 0; + if( spawnArgs.GetInt( "uses_lip_syncing", "0" ) ) { + lipSyncAnim = animator.GetAnim( "lipsync" ); + } +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void idAFAttachment::Hide( void ) { + idEntity::Hide(); + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void idAFAttachment::Show( void ) { + idEntity::Show(); + LinkCombat(); +} + +/* +============ +idAFAttachment::Damage + +Pass damage to body at the bindjoint +============ +*/ +void idAFAttachment::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + if ( body ) { + body->Damage( inflictor, attacker, dir, damageDefName, damageScale, damageJoint ); + } +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void idAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +idAFAttachment::GetImpactInfo +================ +*/ +void idAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFAttachment::CanPlayImpactEffect +================ +*/ +bool idAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFAttachment::GetNoPlayerImpactFX +================ +*/ +bool idAFAttachment::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFAttachment::ApplyImpulse +================ +*/ +void idAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFAttachment::AddForce +================ +*/ +void idAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + idEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +idAFAttachment::PlayIdleAnim +================ +*/ +// RAVEN BEGIN +// bdube: added channel +void idAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +// RAVEN END +} + +/* +================ +idAfAttachment::Think +================ +*/ +void idAFAttachment::Think( void ) { +// RAVEN BEGIN +// jscott: Lip sync main code + HandleLipSync(); +// RAVEN END + + idAnimatedEntity::Think(); +} + +// RAVEN BEGIN +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAFAttachment::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = GetPhysics()->GetAxis(); + } else { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); + } + + return true; +} +// RAVEN END + +/* +================ +idAFAttachment::UpdateAnimationControllers +================ +*/ +bool idAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return idAnimatedEntity::UpdateAnimationControllers( ); +} + +/* +================ +idAFAttachment::SetCombatModel +================ +*/ +void idAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + combatModel->SetOwner( body ); +} + +/* +================ +idAFAttachment::GetCombatModel +================ +*/ +idClipModel *idAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFAttachment::LinkCombat +================ +*/ +void idAFAttachment::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFAttachment::UnlinkCombat +================ +*/ +void idAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +// RAVEN BEGIN +// bdube: return the body entity for damage +/* +================ +idAFAttachment::GetDamageEntity +================ +*/ +idEntity* idAFAttachment::GetDamageEntity ( void ) { + return body ? body : this; +} + +// jshepard: we need to animate these +/* +================ +idAFAttachment::Event_PlayAnim +================ +*/ +void idAFAttachment::Event_PlayAnim ( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animTime - gameLocal.time ) ); + } + blendFrames = 0; +} + +// jdischler: and we want cycling, too. +/* +=============== +idAFAttachment::Event_PlayCycle +=============== +*/ +void idAFAttachment::Event_PlayCycle( int channel, const char *animname ) { + int anim; + float animTime; + float blendFrames = 4; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animTime = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +================ +idAFAttachment::Event_ClearAnims + +Clears any animation running on the idAFAttachment +================ +*/ +void idAFAttachment::Event_ClearAnims( void ) { + //animator.ClearAllAnims( gameLocal.time, 100 ); + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, 0 ); +} + +// RAVEN END + +/* +=============================================================================== + + idAFEntity_Base + +=============================================================================== +*/ + +const idEventDef EV_SetConstraintPosition( "SetConstraintPosition", "sv" ); +// RAVEN BEGIN +// kfuller: added +const idEventDef EV_TPose( "tpose", NULL ); +// RAVEN END + +CLASS_DECLARATION( idAnimatedEntity, idAFEntity_Base ) + EVENT( EV_SetConstraintPosition, idAFEntity_Base::Event_SetConstraintPosition ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +idAFEntity_Base::idAFEntity_Base +================ +*/ +idAFEntity_Base::idAFEntity_Base( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); + noPlayerImpactFX = false; +} + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +idAFEntity_Base::~idAFEntity_Base( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +idAFEntity_Base::Save +================ +*/ +void idAFEntity_Base::Save( idSaveGame *savefile ) const { + savefile->WriteBool( noPlayerImpactFX ); + savefile->WriteInt( combatModelContents ); + savefile->WriteClipModel( combatModel ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( nextSoundTime ); + af.Save( savefile ); +} + +/* +================ +idAFEntity_Base::Restore +================ +*/ +void idAFEntity_Base::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( noPlayerImpactFX ); + savefile->ReadInt( combatModelContents ); + savefile->ReadClipModel( combatModel ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( nextSoundTime ); + LinkCombat(); + + af.Restore( savefile ); +} + +/* +================ +idAFEntity_Base::Spawn +================ +*/ +void idAFEntity_Base::Spawn( void ) { + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + nextSoundTime = 0; + + noPlayerImpactFX = spawnArgs.GetBool( "noPlayerImpactFX", "0" ); +} + +/* +================ +idAFEntity_Base::LoadAF +================ +*/ +bool idAFEntity_Base::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + if ( !af.Load( this, fileName ) ) { + gameLocal.Error( "idAFEntity_Base::LoadAF: Couldn't load af file '%s' on entity '%s'", fileName.c_str(), name.c_str() ); + } + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +idAFEntity_Base::Think +================ +*/ +void idAFEntity_Base::Think( void ) { + RunPhysics(); + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + +// RAVEN BEGIN +// kfuller: added + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } + +// rjohnson: added check to see if we no longer need to think! + if (GetPhysics()->IsAtRest() && !animator.IsAnimating( gameLocal.time, true ) ) { + BecomeInactive( TH_ANIMATE ); + } +// RAVEN END +} + +/* +================ +idAFEntity_Base::BodyForClipModelId +================ +*/ +int idAFEntity_Base::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +idAFEntity_Base::SaveState +================ +*/ +void idAFEntity_Base::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +idAFEntity_Base::LoadState +================ +*/ +void idAFEntity_Base::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +idAFEntity_Base::AddBindConstraints +================ +*/ +void idAFEntity_Base::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +idAFEntity_Base::RemoveBindConstraints +================ +*/ +void idAFEntity_Base::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +idAFEntity_Base::GetImpactInfo +================ +*/ +void idAFEntity_Base::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + idEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +idAFEntity_Base::ApplyImpulse +================ +*/ +void idAFEntity_Base::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( !splash && ( noPlayerImpactFX || GetNoPlayerImpactFX( ) ) ) { + if ( ent->IsType( idPlayer::GetClassType() ) ) + {//player + return; + } + if ( ent->IsType( idProjectile::GetClassType() ) ) + {//projectile + if ( ((idProjectile*)ent)->GetOwner() && ((idProjectile*)ent)->GetOwner()->IsType( idPlayer::GetClassType() ) ) + {//owned by player + return; + } + } + } + + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + idEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +idAFEntity_Base::AddForce +================ +*/ +void idAFEntity_Base::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + idEntity::AddForce( ent, id, point, force ); + } +} + +bool idAFEntity_Base::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + if ( ( noPlayerImpactFX && GetNoPlayerImpactFX( ) ) && attacker && attacker->IsType( idPlayer::GetClassType() ) ) { + return false; + } + + return idAnimatedEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +idAFEntity_Base::Collide +================ +*/ +bool idAFEntity_Base::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +idAFEntity_Base::GetPhysicsToVisualTransform +================ +*/ +bool idAFEntity_Base::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return idEntity::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +idAFEntity_Base::UpdateAnimationControllers +================ +*/ +bool idAFEntity_Base::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +idAFEntity_Base::SetCombatModel +================ +*/ +void idAFEntity_Base::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::GetCombatModel +================ +*/ +idClipModel *idAFEntity_Base::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idAFEntity_Base::SetCombatContents +================ +*/ +void idAFEntity_Base::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +idAFEntity_Base::LinkCombat +================ +*/ +void idAFEntity_Base::LinkCombat( void ) { + if ( fl.hidden ) { + return; + } + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } +} + +/* +================ +idAFEntity_Base::UnlinkCombat +================ +*/ +void idAFEntity_Base::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +idAFEntity_Base::FreeModelDef +================ +*/ +void idAFEntity_Base::FreeModelDef( void ) { + UnlinkCombat(); + idEntity::FreeModelDef(); +} + +/* +=============== +idAFEntity_Base::ShowEditingDialog +=============== +*/ +void idAFEntity_Base::ShowEditingDialog( void ) { + common->InitTool( EDITOR_AF, &spawnArgs ); +} + +/* +================ +idAFEntity_Base::DropAFs + + The entity should have the following key/value pairs set: + "def_dropAF" "af def" + "dropSkin" "skin name" + To drop multiple articulated figures the following key/value pairs can be used: + "def_dropAF*" "af def" + where * is an aribtrary string. +================ +*/ +void idAFEntity_Base::DropAFs( idEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName; + idEntity *newEnt; + idAFEntity_Base *af; + idDict args; + const idDeclSkin *skin; + + // drop the articulated figures + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), NULL ); + while ( kv ) { + + args.Set( "classname", kv->GetValue() ); + gameLocal.SpawnEntityDef( args, &newEnt ); + + if ( newEnt && newEnt->IsType( idAFEntity_Base::Type ) ) { + af = static_cast(newEnt); + af->GetPhysics()->SetOrigin( ent->GetPhysics()->GetOrigin() ); + af->GetPhysics()->SetAxis( ent->GetPhysics()->GetAxis() ); + af->af.SetupPose( ent, gameLocal.time ); + if ( list ) { + list->Append( af ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sAF", type ), kv ); + } + + // change the skin to hide all the dropped articulated figures + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +================ +idAFEntity_Base::GetNoPlayerImpactFX +================ +*/ +bool idAFEntity_Base::GetNoPlayerImpactFX( void ) { + if ( GetTeamMaster( ) && this != GetTeamMaster( ) && GetTeamMaster( )->IsType( idAFEntity_Base::GetClassType( ) ) ) { + return static_cast( GetTeamMaster( ) )->GetNoPlayerImpactFX( ); + } else { + return noPlayerImpactFX; + } +} + +/* +================ +idAFEntity_Base::Event_SetConstraintPosition +================ +*/ +void idAFEntity_Base::Event_SetConstraintPosition( const char *name, const idVec3 &pos ) { + af.SetConstraintPosition( name, pos ); +} + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +const idEventDef EV_Gib( "gib", "s" ); +const idEventDef EV_Gibbed( "" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Gibbable ) + EVENT( EV_Gib, idAFEntity_Gibbable::Event_Gib ) + EVENT( EV_Gibbed, idAFEntity_Base::Event_Remove ) +END_CLASS + + +/* +================ +idAFEntity_Gibbable::idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::idAFEntity_Gibbable( void ) { + skeletonModel = NULL; + skeletonModelDefHandle = -1; + gibbed = false; +} + +/* +================ +idAFEntity_Gibbable::~idAFEntity_Gibbable +================ +*/ +idAFEntity_Gibbable::~idAFEntity_Gibbable() { + if ( skeletonModelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( skeletonModelDefHandle ); + skeletonModelDefHandle = -1; + } +} + +/* +================ +idAFEntity_Gibbable::Save +================ +*/ +void idAFEntity_Gibbable::Save( idSaveGame *savefile ) const { + savefile->WriteBool( gibbed ); + savefile->WriteBool( combatModel != NULL ); +} + +/* +================ +idAFEntity_Gibbable::Restore +================ +*/ +void idAFEntity_Gibbable::Restore( idRestoreGame *savefile ) { + bool hasCombatModel; + + savefile->ReadBool( gibbed ); + savefile->ReadBool( hasCombatModel ); + + InitSkeletonModel(); + + if ( hasCombatModel ) { + SetCombatModel(); + LinkCombat(); + } + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::Spawn +================ +*/ +void idAFEntity_Gibbable::Spawn( void ) { + InitSkeletonModel(); + + gibbed = false; + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_Gib", false, false ); +} + +/* +================ +idAFEntity_Gibbable::InitSkeletonModel +================ +*/ +void idAFEntity_Gibbable::InitSkeletonModel( void ) { + const char *modelName; + const idDeclModelDef *modelDef; + + skeletonModel = NULL; + skeletonModelDefHandle = -1; + + modelName = spawnArgs.GetString( "model_gib" ); + + modelDef = NULL; + if ( modelName[0] != '\0' ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + skeletonModel = modelDef->ModelHandle(); + } else { + skeletonModel = renderModelManager->FindModel( modelName ); + } + if ( skeletonModel != NULL && renderEntity.hModel != NULL ) { + if ( skeletonModel->NumJoints() != renderEntity.hModel->NumJoints() ) { + gameLocal.Error( "gib model '%s' has different number of joints than model '%s'", + skeletonModel->Name(), renderEntity.hModel->Name() ); + } + } + } +} + +/* +================ +idAFEntity_Gibbable::Present +================ +*/ +void idAFEntity_Gibbable::Present( void ) { + renderEntity_t skeleton; + + if ( !gameLocal.isNewFrame ) { + return; + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // update skeleton model + if ( gibbed && !IsHidden() && skeletonModel != NULL ) { + skeleton = renderEntity; + skeleton.hModel = skeletonModel; + // add to refresh list + if ( skeletonModelDefHandle == -1 ) { + skeletonModelDefHandle = gameRenderWorld->AddEntityDef( &skeleton ); + } else { + gameRenderWorld->UpdateEntityDef( skeletonModelDefHandle, &skeleton ); + } + } + + idEntity::Present(); +} + +/* +================ +idAFEntity_Gibbable::Damage +================ +*/ +void idAFEntity_Gibbable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + idAFEntity_Base::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( health < -20 && spawnArgs.GetBool( "gib" ) ) { + Gib( dir, damageDefName ); + } +} + +/* +===================== +idAFEntity_Gibbable::SpawnGibs +===================== +*/ +void idAFEntity_Gibbable::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + int i; + bool gibNonSolid; + idVec3 entityCenter, velocity; + idList list; + + assert( !gameLocal.isClient ); + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + // spawn gib articulated figures + idAFEntity_Base::DropAFs( this, "gib", &list ); + + // spawn gib items + idMoveableItem::DropItems( this, "gib", &list ); + + // blow out the gibs in the given direction away from the center of the entity + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + gibNonSolid = damageDef->GetBool( "gibNonSolid" ); + for ( i = 0; i < list.Num(); i++ ) { + if ( gibNonSolid ) { + list[i]->GetPhysics()->SetContents( 0 ); + list[i]->GetPhysics()->SetClipMask( 0 ); + list[i]->GetPhysics()->UnlinkClip(); + list[i]->GetPhysics()->PutToRest(); + } else { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + list[i]->GetPhysics()->SetLinearVelocity( velocity * 225.0f ); + } + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; +// RAVEN BEGIN +// dluetscher: reduced the gib lifetime from 4. to 1.5 + list[i]->PostEventSec( &EV_Remove, 1.5f ); +// RAVEN END + } +} + +/* +============ +idAFEntity_Gibbable::Gib +============ +*/ +void idAFEntity_Gibbable::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); +// RAVEN END + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + if ( damageDef->GetBool( "gibNonSolid" ) ) { + GetAFPhysics()->SetContents( 0 ); + GetAFPhysics()->SetClipMask( 0 ); + GetAFPhysics()->UnlinkClip(); + GetAFPhysics()->PutToRest(); + } else { + GetAFPhysics()->SetContents( CONTENTS_CORPSE ); + GetAFPhysics()->SetClipMask( CONTENTS_SOLID ); + } + + UnlinkCombat(); +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( g_decals.GetBool() ) { +// RAVEN END + if ( gameLocal.time > gameLocal.GetGibTime() ) { + gameLocal.SetGibTime( gameLocal.time + GIB_DELAY ); + SpawnGibs( dir, damageDefName ); + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + StartSound( "snd_gibbed", SND_CHANNEL_ANY, 0, false, NULL ); + gibbed = true; + } + } else { + gibbed = true; + } + +// RAVEN BEGIN +// bdube: default is to remove the character immediately + // however, this is bad in the case of the player. What was happening is that the + // player was being removed right away, but the respawn logic runs off of + // idPlayer::EvaluateControls, which no longer gets run when the player is gibbed and removed. + // So...the game effectively sits locked on a black screen and you never get a menu.. + if ( !gameLocal.isMultiplayer && this->IsType( idPlayer::GetClassType() )) { + return; + } + PostEventSec( &EV_Gibbed, spawnArgs.GetFloat ( "gibRemoveDelay", "0" ) ); +// RAVEN END +} + +/* +============ +idAFEntity_Gibbable::Event_Gib +============ +*/ +void idAFEntity_Gibbable::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_Generic ) + EVENT( EV_Activate, idAFEntity_Generic::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_Generic::idAFEntity_Generic +================ +*/ +idAFEntity_Generic::idAFEntity_Generic( void ) { + keepRunningPhysics = false; +} + +/* +================ +idAFEntity_Generic::~idAFEntity_Generic +================ +*/ +idAFEntity_Generic::~idAFEntity_Generic( void ) { +} + +/* +================ +idAFEntity_Generic::Save +================ +*/ +void idAFEntity_Generic::Save( idSaveGame *savefile ) const { + savefile->WriteBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Restore +================ +*/ +void idAFEntity_Generic::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( keepRunningPhysics ); +} + +/* +================ +idAFEntity_Generic::Think +================ +*/ +void idAFEntity_Generic::Think( void ) { + idAFEntity_Base::Think(); + + if ( keepRunningPhysics ) { + BecomeActive( TH_PHYSICS ); + } +} + +/* +================ +idAFEntity_Generic::Spawn +================ +*/ +void idAFEntity_Generic::Spawn( void ) { + if ( !LoadAF() ) { + gameLocal.Error( "Couldn't load af file on entity '%s'", name.c_str() ); + } + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + +// RAVEN BEGIN +// kfuller: we just put the guy to rest so don't be affected by an attractor +/* + const char *attractor = spawnArgs.GetString("attractor"); + if (attractor && attractor[0]) { + rvRagdollAttractor *attractorEnt = dynamic_cast(gameLocal.FindEntity(attractor)); + if (attractorEnt) { + attractorEnt->RemoveAttractee(entityNumber); + } else { + gameLocal.Warning("idAFEntity::LoadAF -- failed to find attractor '%s'", attractor); + } + } +*/ +// RAVEN END + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; +} + +/* +================ +idAFEntity_Generic::Event_Activate +================ +*/ +void idAFEntity_Generic::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Gibbable, idAFEntity_WithAttachedHead ) + EVENT( EV_Gib, idAFEntity_WithAttachedHead::Event_Gib ) + EVENT( EV_Activate, idAFEntity_WithAttachedHead::Event_Activate ) +END_CLASS + +/* +================ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::idAFEntity_WithAttachedHead() { + head = NULL; +} + +/* +================ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead +================ +*/ +idAFEntity_WithAttachedHead::~idAFEntity_WithAttachedHead() { + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Spawn +================ +*/ +void idAFEntity_WithAttachedHead::Spawn( void ) { + SetupHead(); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + af.GetPhysics()->PutToRest(); + if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + af.GetPhysics()->Activate(); + } + + fl.takedamage = true; + + if ( head.GetEntity() ) { + int anim = head.GetEntity()->GetAnimator()->GetAnim( "dead" ); + + if ( anim ) { +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + head.GetEntity()->GetAnimator()->SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + } + } + + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Save +================ +*/ +void idAFEntity_WithAttachedHead::Save( idSaveGame *savefile ) const { + head.Save( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::Restore +================ +*/ +void idAFEntity_WithAttachedHead::Restore( idRestoreGame *savefile ) { + head.Restore( savefile ); +} + +/* +================ +idAFEntity_WithAttachedHead::SetupHead +================ +*/ +void idAFEntity_WithAttachedHead::SetupHead( const char* headDefName ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); + } + + if ( headDefName[ 0 ] ) { + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headDefName, joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + + head->InitCopyJoints ( ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } +} + +/* +================ +idAFEntity_WithAttachedHead::Think +================ +*/ +void idAFEntity_WithAttachedHead::Think( void ) { + idAFEntity_Base::Think(); +} + +/* +================ +idAFEntity_WithAttachedHead::LinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::UnlinkCombat +================ +*/ +void idAFEntity_WithAttachedHead::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idAFEntity_WithAttachedHead::Hide +================ +*/ +void idAFEntity_WithAttachedHead::Hide( void ) { + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + UnlinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::Show +================ +*/ +void idAFEntity_WithAttachedHead::Show( void ) { + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + LinkCombat(); +} + +/* +================ +idAFEntity_WithAttachedHead::ProjectOverlay +================ +*/ +void idAFEntity_WithAttachedHead::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + + idEntity::ProjectOverlay( origin, dir, size, material ); + + if ( head.GetEntity() ) { + head.GetEntity()->ProjectOverlay( origin, dir, size, material ); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Gib +============ +*/ +void idAFEntity_WithAttachedHead::Gib( const idVec3 &dir, const char *damageDefName ) { + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } +} + +/* +============ +idAFEntity_WithAttachedHead::Event_Gib +============ +*/ +void idAFEntity_WithAttachedHead::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +================ +idAFEntity_WithAttachedHead::Event_Activate +================ +*/ +void idAFEntity_WithAttachedHead::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + af.GetPhysics()->EnableImpact(); + af.GetPhysics()->Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + af.GetPhysics()->SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } +} + + +/* +=============================================================================== + + idAFEntity_Vehicle + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_Vehicle ) +END_CLASS + +/* +================ +idAFEntity_Vehicle::idAFEntity_Vehicle +================ +*/ +idAFEntity_Vehicle::idAFEntity_Vehicle( void ) { + player = NULL; + eyesJoint = INVALID_JOINT; + steeringWheelJoint = INVALID_JOINT; + wheelRadius = 0.0f; + steerAngle = 0.0f; + steerSpeed = 0.0f; +// dustSmoke = NULL; +} + +/* +================ +idAFEntity_Vehicle::Spawn +================ +*/ +void idAFEntity_Vehicle::Spawn( void ) { + const char *eyesJointName = spawnArgs.GetString( "eyesJoint", "eyes" ); + const char *steeringWheelJointName = spawnArgs.GetString( "steeringWheelJoint", "steeringWheel" ); + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + if ( !eyesJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no eyes joint specified", name.c_str() ); + } + eyesJoint = animator.GetJointHandle( eyesJointName ); + if ( !steeringWheelJointName[0] ) { + gameLocal.Error( "idAFEntity_Vehicle '%s' no steering wheel joint specified", name.c_str() ); + } + steeringWheelJoint = animator.GetJointHandle( steeringWheelJointName ); + + spawnArgs.GetFloat( "wheelRadius", "20", wheelRadius ); + spawnArgs.GetFloat( "steerSpeed", "5", steerSpeed ); + + player = NULL; + steerAngle = 0.0f; + +/* + const char *smokeName = spawnArgs.GetString( "smoke_vehicle_dust", "muzzlesmoke" ); + if ( *smokeName != '\0' ) { + dustSmoke = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + } +*/ +} + +/* +================ +idAFEntity_Vehicle::Use +================ +*/ +void idAFEntity_Vehicle::Use( idPlayer *other ) { + idVec3 origin; + idMat3 axis; + + if ( player ) { + if ( player == other ) { + other->Unbind(); + player = NULL; + + af.GetPhysics()->SetComeToRest( true ); + } + } + else { + player = other; + animator.GetJointTransform( eyesJoint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + player->GetPhysics()->SetOrigin( origin ); + player->BindToBody( this, 0, true ); + + af.GetPhysics()->SetComeToRest( false ); + af.GetPhysics()->Activate(); + } +} + +/* +================ +idAFEntity_Vehicle::GetSteerAngle +================ +*/ +float idAFEntity_Vehicle::GetSteerAngle( void ) { + float idealSteerAngle, angleDelta; + + idealSteerAngle = player->usercmd.rightmove * ( 30.0f / 128.0f ); + angleDelta = idealSteerAngle - steerAngle; + + if ( angleDelta > steerSpeed ) { + steerAngle += steerSpeed; + } else if ( angleDelta < -steerSpeed ) { + steerAngle -= steerSpeed; + } else { + steerAngle = idealSteerAngle; + } + + return steerAngle; +} + + +/* +=============================================================================== + + idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleFourWheels ) +END_CLASS + + +/* +================ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels +================ +*/ +idAFEntity_VehicleFourWheels::idAFEntity_VehicleFourWheels( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; +} + +/* +================ +idAFEntity_VehicleFourWheels::Spawn +================ +*/ +void idAFEntity_VehicleFourWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 4; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 2; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleFourWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleFourWheels::Think +================ +*/ +void idAFEntity_VehicleFourWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 2; i++ ) { + wheels[2+i]->SetContactMotorVelocity( velocity ); + wheels[2+i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + wheels[2]->SetContactMotorVelocity( velocity * 0.5f ); + } + else if ( steerAngle > 0.0f ) { + wheels[3]->SetContactMotorVelocity( velocity * 0.5f ); + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + for ( i = 0; i < 2; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 4; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Vehicle, idAFEntity_VehicleSixWheels ) +END_CLASS + + /* +================ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels +================ +*/ +idAFEntity_VehicleSixWheels::idAFEntity_VehicleSixWheels( void ) { + int i; + + for ( i = 0; i < 6; i++ ) { + wheels[i] = NULL; + wheelJoints[i] = INVALID_JOINT; + wheelAngles[i] = 0.0f; + } + steering[0] = NULL; + steering[1] = NULL; + steering[2] = NULL; + steering[3] = NULL; +} + +/* +================ +idAFEntity_VehicleSixWheels::Spawn +================ +*/ +void idAFEntity_VehicleSixWheels::Spawn( void ) { + int i; + static const char *wheelBodyKeys[] = { + "wheelBodyFrontLeft", + "wheelBodyFrontRight", + "wheelBodyMiddleLeft", + "wheelBodyMiddleRight", + "wheelBodyRearLeft", + "wheelBodyRearRight" + }; + static const char *wheelJointKeys[] = { + "wheelJointFrontLeft", + "wheelJointFrontRight", + "wheelJointMiddleLeft", + "wheelJointMiddleRight", + "wheelJointRearLeft", + "wheelJointRearRight" + }; + static const char *steeringHingeKeys[] = { + "steeringHingeFrontLeft", + "steeringHingeFrontRight", + "steeringHingeRearLeft", + "steeringHingeRearRight" + }; + + const char *wheelBodyName, *wheelJointName, *steeringHingeName; + + for ( i = 0; i < 6; i++ ) { + wheelBodyName = spawnArgs.GetString( wheelBodyKeys[i], "" ); + if ( !wheelBodyName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelBodyKeys[i] ); + } + wheels[i] = af.GetPhysics()->GetBody( wheelBodyName ); + if ( !wheels[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel body '%s'", name.c_str(), wheelBodyName ); + } + wheelJointName = spawnArgs.GetString( wheelJointKeys[i], "" ); + if ( !wheelJointName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), wheelJointKeys[i] ); + } + wheelJoints[i] = animator.GetJointHandle( wheelJointName ); + if ( wheelJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' can't find wheel joint '%s'", name.c_str(), wheelJointName ); + } + } + + for ( i = 0; i < 4; i++ ) { + steeringHingeName = spawnArgs.GetString( steeringHingeKeys[i], "" ); + if ( !steeringHingeName[0] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s' no '%s' specified", name.c_str(), steeringHingeKeys[i] ); + } + steering[i] = static_cast(af.GetPhysics()->GetConstraint( steeringHingeName )); + if ( !steering[i] ) { + gameLocal.Error( "idAFEntity_VehicleSixWheels '%s': can't find steering hinge '%s'", name.c_str(), steeringHingeName ); + } + } + + memset( wheelAngles, 0, sizeof( wheelAngles ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_VehicleSixWheels::Think +================ +*/ +void idAFEntity_VehicleSixWheels::Think( void ) { + int i; + float force = 0.0f, velocity = 0.0f, steerAngle = 0.0f; + idVec3 origin; + idMat3 axis; + idRotation rotation; + + if ( thinkFlags & TH_THINK ) { + + if ( player ) { + // capture the input from a player + velocity = g_vehicleVelocity.GetFloat(); + if ( player->usercmd.forwardmove < 0 ) { + velocity = -velocity; + } + force = idMath::Fabs( player->usercmd.forwardmove * g_vehicleForce.GetFloat() ) * (1.0f / 128.0f); + steerAngle = GetSteerAngle(); + } + + // update the wheel motor force + for ( i = 0; i < 6; i++ ) { + wheels[i]->SetContactMotorVelocity( velocity ); + wheels[i]->SetContactMotorForce( force ); + } + + // adjust wheel velocity for better steering because there are no differentials between the wheels + if ( steerAngle < 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + else if ( steerAngle > 0.0f ) { + for ( i = 0; i < 3; i++ ) { + wheels[1+(i<<1)]->SetContactMotorVelocity( velocity * 0.5f ); + } + } + + // update the wheel steering + steering[0]->SetSteerAngle( steerAngle ); + steering[1]->SetSteerAngle( steerAngle ); + steering[2]->SetSteerAngle( -steerAngle ); + steering[3]->SetSteerAngle( -steerAngle ); + for ( i = 0; i < 4; i++ ) { + steering[i]->SetSteerSpeed( 3.0f ); + } + + // update the steering wheel + animator.GetJointTransform( steeringWheelJoint, gameLocal.time, origin, axis ); + rotation.SetVec( axis[2] ); + rotation.SetAngle( -steerAngle ); + animator.SetJointAxis( steeringWheelJoint, JOINTMOD_WORLD, rotation.ToMat3() ); + + // run the physics + RunPhysics(); + + // rotate the wheels visually + for ( i = 0; i < 6; i++ ) { + if ( force == 0.0f ) { + velocity = wheels[i]->GetLinearVelocity() * wheels[i]->GetWorldAxis()[0]; + } +// RAVEN BEGIN +// bdube: msec to GetMsec + wheelAngles[i] += velocity * MS2SEC( gameLocal.GetMSec() ) / wheelRadius; +// RAVEN END + // give the wheel joint an additional rotation about the wheel axis + rotation.SetAngle( RAD2DEG( wheelAngles[i] ) ); + axis = af.GetPhysics()->GetAxis( 0 ); + rotation.SetVec( (wheels[i]->GetWorldAxis() * axis.Transpose())[2] ); + animator.SetJointAxis( wheelJoints[i], JOINTMOD_WORLD, rotation.ToMat3() ); + } + } + + UpdateAnimation(); + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } +} + + +/* +=============================================================================== + + idAFEntity_SteamPipe + +=============================================================================== +*/ + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_SteamPipe ) +END_CLASS + + +/* +================ +idAFEntity_SteamPipe::idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::idAFEntity_SteamPipe( void ) { + steamBody = 0; + steamForce = 0.0f; + steamUpForce = 0.0f; + steamModelDefHandle = -1; + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); +} + +/* +================ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe +================ +*/ +idAFEntity_SteamPipe::~idAFEntity_SteamPipe( void ) { + if ( steamModelDefHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( steamModelDefHandle ); + } +} + +/* +================ +idAFEntity_SteamPipe::Save +================ +*/ +void idAFEntity_SteamPipe::Save( idSaveGame *savefile ) const { +} + +/* +================ +idAFEntity_SteamPipe::Restore +================ +*/ +void idAFEntity_SteamPipe::Restore( idRestoreGame *savefile ) { + Spawn(); +} + +/* +================ +idAFEntity_SteamPipe::Spawn +================ +*/ +void idAFEntity_SteamPipe::Spawn( void ) { + idVec3 steamDir; + const char *steamBodyName; + + LoadAF(); + + SetCombatModel(); + + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + steamBodyName = spawnArgs.GetString( "steamBody", "" ); + steamForce = spawnArgs.GetFloat( "steamForce", "2000" ); + steamUpForce = spawnArgs.GetFloat( "steamUpForce", "10" ); + steamDir = af.GetPhysics()->GetAxis( steamBody )[2]; + steamBody = af.GetPhysics()->GetBodyId( steamBodyName ); + force.SetPosition( af.GetPhysics(), steamBody, af.GetPhysics()->GetOrigin( steamBody ) ); + force.SetForce( steamDir * -steamForce ); + + InitSteamRenderEntity(); + + BecomeActive( TH_THINK ); +} + +/* +================ +idAFEntity_SteamPipe::InitSteamRenderEntity +================ +*/ +void idAFEntity_SteamPipe::InitSteamRenderEntity( void ) { + const char *temp; + const idDeclModelDef *modelDef; + + memset( &steamRenderEntity, 0, sizeof( steamRenderEntity ) ); + steamRenderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + steamRenderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + modelDef = NULL; + temp = spawnArgs.GetString ( "model_steam" ); + if ( *temp != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + steamRenderEntity.hModel = modelDef->ModelHandle(); + } + } + + if ( !steamRenderEntity.hModel ) { + steamRenderEntity.hModel = renderModelManager->FindModel( temp ); + } + + if ( steamRenderEntity.hModel ) { + steamRenderEntity.bounds = steamRenderEntity.hModel->Bounds( &steamRenderEntity ); + } else { + steamRenderEntity.bounds.Zero(); + } + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + steamModelDefHandle = gameRenderWorld->AddEntityDef( &steamRenderEntity ); + } +} + +/* +================ +idAFEntity_SteamPipe::Think +================ +*/ +void idAFEntity_SteamPipe::Think( void ) { + idVec3 steamDir; + + if ( thinkFlags & TH_THINK ) { + steamDir.x = gameLocal.random.CRandomFloat() * steamForce; + steamDir.y = gameLocal.random.CRandomFloat() * steamForce; + steamDir.z = steamUpForce; + force.SetForce( steamDir ); + force.Evaluate( gameLocal.time ); + //gameRenderWorld->DebugArrow( colorWhite, af.GetPhysics()->GetOrigin( steamBody ), af.GetPhysics()->GetOrigin( steamBody ) - 10.0f * steamDir, 4 ); + } + + if ( steamModelDefHandle >= 0 ){ + steamRenderEntity.origin = af.GetPhysics()->GetOrigin( steamBody ); + steamRenderEntity.axis = af.GetPhysics()->GetAxis( steamBody ); + gameRenderWorld->UpdateEntityDef( steamModelDefHandle, &steamRenderEntity ); + } + + idAFEntity_Base::Think(); +} + + +/* +=============================================================================== + + idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +const idEventDef EV_SetFingerAngle( "setFingerAngle", "f" ); +const idEventDef EV_StopFingers( "stopFingers" ); + +CLASS_DECLARATION( idAFEntity_Base, idAFEntity_ClawFourFingers ) + EVENT( EV_SetFingerAngle, idAFEntity_ClawFourFingers::Event_SetFingerAngle ) + EVENT( EV_StopFingers, idAFEntity_ClawFourFingers::Event_StopFingers ) +END_CLASS + +static const char *clawConstraintNames[] = { + "claw1", "claw2", "claw3", "claw4" +}; + +/* +================ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers +================ +*/ +idAFEntity_ClawFourFingers::idAFEntity_ClawFourFingers( void ) { + fingers[0] = NULL; + fingers[1] = NULL; + fingers[2] = NULL; + fingers[3] = NULL; +} + +/* +================ +idAFEntity_ClawFourFingers::Save +================ +*/ +void idAFEntity_ClawFourFingers::Save( idSaveGame *savefile ) const { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->Save( savefile ); + } +} + +/* +================ +idAFEntity_ClawFourFingers::Restore +================ +*/ +void idAFEntity_ClawFourFingers::Restore( idRestoreGame *savefile ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + fingers[i]->Restore( savefile ); + } + + SetCombatModel(); + LinkCombat(); +} + +/* +================ +idAFEntity_ClawFourFingers::Spawn +================ +*/ +void idAFEntity_ClawFourFingers::Spawn( void ) { + int i; + + LoadAF(); + + SetCombatModel(); + + af.GetPhysics()->LockWorldConstraints( true ); + af.GetPhysics()->SetForcePushable( true ); + SetPhysics( af.GetPhysics() ); + + fl.takedamage = true; + + for ( i = 0; i < 4; i++ ) { + fingers[i] = static_cast(af.GetPhysics()->GetConstraint( clawConstraintNames[i] )); + if ( !fingers[i] ) { + gameLocal.Error( "idClaw_FourFingers '%s': can't find claw constraint '%s'", name.c_str(), clawConstraintNames[i] ); + } + } +} + +/* +================ +idAFEntity_ClawFourFingers::Event_SetFingerAngle +================ +*/ +void idAFEntity_ClawFourFingers::Event_SetFingerAngle( float angle ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( angle ); + fingers[i]->SetSteerSpeed( 0.5f ); + } + af.GetPhysics()->Activate(); +} + +/* +================ +idAFEntity_ClawFourFingers::Event_StopFingers +================ +*/ +void idAFEntity_ClawFourFingers::Event_StopFingers( void ) { + int i; + + for ( i = 0; i < 4; i++ ) { + fingers[i]->SetSteerAngle( fingers[i]->GetAngle() ); + } +} + + +/* +=============================================================================== + + editor support routines + +=============================================================================== +*/ + + +/* +================ +idGameEdit::AF_SpawnEntity +================ +*/ +bool idGameEdit::AF_SpawnEntity( const char *fileName ) { + idDict args; + idPlayer *player; + idAFEntity_Generic *ent; + const idDeclAF *af; + idVec3 org; + float yaw; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return false; + } + + af = static_cast( declManager->FindType( DECL_AF, fileName ) ); + if ( !af ) { + return false; + } + + yaw = player->viewAngles.yaw; + args.Set( "angle", va( "%f", yaw + 180 ) ); + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + args.Set( "origin", org.ToString() ); + args.Set( "spawnclass", "idAFEntity_Generic" ); + if ( af->model[0] ) { + args.Set( "model", af->model.c_str() ); + } else { + args.Set( "model", fileName ); + } + if ( af->skin[0] ) { + args.Set( "skin", af->skin.c_str() ); + } + args.Set( "articulatedFigure", fileName ); + args.Set( "nodrop", "1" ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + ent = static_cast(gameLocal.SpawnEntityType( idAFEntity_Generic::GetClassType(), &args)); +// RAVEN END + + // always update this entity + ent->BecomeActive( TH_THINK ); + ent->KeepRunningPhysics(); + ent->fl.forcePhysicsUpdate = true; + + player->dragEntity.SetSelected( ent ); + + return true; +} + +/* +================ +idGameEdit::AF_UpdateEntities +================ +*/ +void idGameEdit::AF_UpdateEntities( const char *fileName ) { + idEntity *ent; + idAFEntity_Base *af; + idStr name; + + name = fileName; + name.StripFileExtension(); + + // reload any idAFEntity_Generic which uses the given articulated figure file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( name.Icmp( af->GetAFName() ) == 0 ) { + af->LoadAF(); + af->GetAFPhysics()->PutToRest(); + } + } + } +} + +/* +================ +idGameEdit::AF_UndoChanges +================ +*/ +void idGameEdit::AF_UndoChanges( void ) { + int i, c; + idEntity *ent; + idAFEntity_Base *af; + idDeclAF *decl; + + c = declManager->GetNumDecls( DECL_AF ); + for ( i = 0; i < c; i++ ) { + decl = static_cast( const_cast( declManager->DeclByIndex( DECL_AF, i, false ) ) ); + if ( !decl->modified ) { + continue; + } + + decl->Invalidate(); + declManager->FindType( DECL_AF, decl->GetName() ); + + // reload all AF entities using the file + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = static_cast(ent); + if ( idStr::Icmp( decl->GetName(), af->GetAFName() ) == 0 ) { + af->LoadAF(); + } + } + } + } +} + +/* +================ +GetJointTransform +================ +*/ +typedef struct { + renderEntity_t *ent; + const idMD5Joint *joints; +} jointTransformData_t; + +static bool GetJointTransform( void *model, const idJointMat *frame, const char *jointName, idVec3 &origin, idMat3 &axis ) { + int i; + jointTransformData_t *data = reinterpret_cast(model); + + for ( i = 0; i < data->ent->numJoints; i++ ) { + if ( data->joints[i].name.Icmp( jointName ) == 0 ) { + break; + } + } + if ( i >= data->ent->numJoints ) { + return false; + } + origin = frame[i].ToVec3(); + axis = frame[i].ToMat3(); + return true; +} + +/* +================ +GetArgString +================ +*/ +static const char *GetArgString( const idDict &args, const idDict *defArgs, const char *key ) { + const char *s; + + s = args.GetString( key ); + if ( !s[0] && defArgs ) { + s = defArgs->GetString( key ); + } + return s; +} + +/* +================ +idGameEdit::AF_CreateMesh +================ +*/ +idRenderModel *idGameEdit::AF_CreateMesh( const idDict &args, idVec3 &meshOrigin, idMat3 &meshAxis, bool &poseIsSet ) { + int i, jointNum; + const idDeclAF *af; + const idDeclAF_Body *fb = NULL; + renderEntity_t ent; + idVec3 origin, *bodyOrigin, *newBodyOrigin, *modifiedOrigin; + idMat3 axis, *bodyAxis, *newBodyAxis, *modifiedAxis; + declAFJointMod_t *jointMod; + idAngles angles; + const idDict *defArgs; + const idKeyValue *arg; + idStr name; + jointTransformData_t data; + const char *classname, *afName, *modelName; + idRenderModel *md5; + const idDeclModelDef *modelDef; + const idMD5Anim *MD5anim; + const idMD5Joint *MD5joint; + const idMD5Joint *MD5joints; + int numMD5joints; + idJointMat *originalJoints; + int parentNum; + + poseIsSet = false; + meshOrigin.Zero(); + meshAxis.Identity(); + + classname = args.GetString( "classname" ); + defArgs = gameLocal.FindEntityDefDict( classname ); + + // get the articulated figure + afName = GetArgString( args, defArgs, "articulatedFigure" ); + af = static_cast( declManager->FindType( DECL_AF, afName ) ); + if ( !af ) { + return NULL; + } + + // get the md5 model + modelName = GetArgString( args, defArgs, "model" ); + modelDef = static_cast< const idDeclModelDef *>( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( !modelDef ) { + return NULL; + } + + // make sure model hasn't been purged + if ( modelDef->ModelHandle() && !modelDef->ModelHandle()->IsLoaded() ) { + modelDef->ModelHandle()->LoadModel(); + } + + // get the md5 + md5 = modelDef->ModelHandle(); + if ( !md5 || md5->IsDefaultModel() ) { + return NULL; + } + + // get the articulated figure pose anim + int animNum = modelDef->GetAnim( "af_pose" ); + if ( !animNum ) { + return NULL; + } + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + MD5anim = anim->MD5Anim( 0 ); + MD5joints = md5->GetJoints(); + numMD5joints = md5->NumJoints(); + + // setup a render entity + memset( &ent, 0, sizeof( ent ) ); + ent.customSkin = modelDef->GetSkin(); + ent.bounds.Clear(); + ent.numJoints = numMD5joints; + ent.joints = ( idJointMat * )_alloca16( ent.numJoints * sizeof( *ent.joints ) ); + + // create animation from of the af_pose + ANIM_CreateAnimFrame( md5, MD5anim, ent.numJoints, ent.joints, 1, modelDef->GetVisualOffset(), false ); + + // buffers to store the initial origin and axis for each body + bodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + bodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + newBodyOrigin = (idVec3 *) _alloca16( af->bodies.Num() * sizeof( idVec3 ) ); + newBodyAxis = (idMat3 *) _alloca16( af->bodies.Num() * sizeof( idMat3 ) ); + + // finish the AF positions + data.ent = &ent; + data.joints = MD5joints; + af->Finish( GetJointTransform, ent.joints, &data ); + + // get the initial origin and axis for each AF body + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->modelType == TRM_BONE ) { + // axis of bone trace model + axis[2] = fb->v2.ToVec3() - fb->v1.ToVec3(); + axis[2].Normalize(); + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + } else { + axis = fb->angles.ToMat3(); + } + + newBodyOrigin[i] = bodyOrigin[i] = fb->origin.ToVec3(); + newBodyAxis[i] = bodyAxis[i] = axis; + } + + // get any new body transforms stored in the key/value pairs + for ( arg = args.MatchPrefix( "body ", NULL ); arg; arg = args.MatchPrefix( "body ", arg ) ) { + name = arg->GetKey(); + name.Strip( "body " ); + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + if ( fb->name.Icmp( name ) == 0 ) { + break; + } + } + if ( i >= af->bodies.Num() ) { + continue; + } + sscanf( arg->GetValue(), "%f %f %f %f %f %f", &origin.x, &origin.y, &origin.z, &angles.pitch, &angles.yaw, &angles.roll ); + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + meshAxis = bodyAxis[i].Transpose() * angles.ToMat3(); + meshOrigin = origin - bodyOrigin[i] * meshAxis; + poseIsSet = true; + } else { + newBodyOrigin[i] = origin; + newBodyAxis[i] = angles.ToMat3(); + } + } + + // save the original joints + originalJoints = ( idJointMat * )_alloca16( numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( originalJoints, ent.joints, numMD5joints * sizeof( originalJoints[0] ) ); +// RAVEN END + + // buffer to store the joint mods + jointMod = (declAFJointMod_t *) _alloca16( numMD5joints * sizeof( declAFJointMod_t ) ); + memset( jointMod, -1, numMD5joints * sizeof( declAFJointMod_t ) ); + modifiedOrigin = (idVec3 *) _alloca16( numMD5joints * sizeof( idVec3 ) ); + memset( modifiedOrigin, 0, numMD5joints * sizeof( idVec3 ) ); + modifiedAxis = (idMat3 *) _alloca16( numMD5joints * sizeof( idMat3 ) ); + memset( modifiedAxis, 0, numMD5joints * sizeof( idMat3 ) ); + + // get all the joint modifications + for ( i = 0; i < af->bodies.Num(); i++ ) { + fb = af->bodies[i]; + + if ( fb->jointName.Icmp( "origin" ) == 0 ) { + continue; + } + + for ( jointNum = 0; jointNum < numMD5joints; jointNum++ ) { + if ( MD5joints[jointNum].name.Icmp( fb->jointName ) == 0 ) { + break; + } + } + + if ( jointNum >= 0 && jointNum < ent.numJoints ) { + jointMod[ jointNum ] = fb->jointMod; + modifiedAxis[ jointNum ] = ( bodyAxis[i] * originalJoints[jointNum].ToMat3().Transpose() ).Transpose() * ( newBodyAxis[i] * meshAxis.Transpose() ); + // FIXME: calculate correct modifiedOrigin + modifiedOrigin[ jointNum ] = originalJoints[ jointNum ].ToVec3(); + } + } + + // apply joint modifications to the skeleton + MD5joint = MD5joints + 1; + for( i = 1; i < numMD5joints; i++, MD5joint++ ) { + + parentNum = MD5joint->parent - MD5joints; + idMat3 parentAxis = originalJoints[ parentNum ].ToMat3(); + idMat3 localm = originalJoints[i].ToMat3() * parentAxis.Transpose(); + idVec3 localt = ( originalJoints[i].ToVec3() - originalJoints[ parentNum ].ToVec3() ) * parentAxis.Transpose(); + + switch( jointMod[i] ) { + case DECLAF_JOINTMOD_ORIGIN: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + case DECLAF_JOINTMOD_AXIS: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + case DECLAF_JOINTMOD_BOTH: { + ent.joints[ i ].SetRotation( modifiedAxis[ i ] ); + ent.joints[ i ].SetTranslation( modifiedOrigin[ i ] ); + break; + } + default: { + ent.joints[ i ].SetRotation( localm * ent.joints[ parentNum ].ToMat3() ); + ent.joints[ i ].SetTranslation( ent.joints[ parentNum ].ToVec3() + localt * ent.joints[ parentNum ].ToMat3() ); + break; + } + } + } + + // instantiate a mesh using the joint information from the render entity + return md5->InstantiateDynamicModel( &ent, NULL, NULL ); +} + +// RAVEN BEGIN +// bdube: af attractors + +/* +=============================================================================== + + rvAFAttractor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAFAttractor ) +END_CLASS + +// RAVEN END diff --git a/src/mpgame/AFEntity.h b/src/mpgame/AFEntity.h new file mode 100644 index 0000000..642a411 --- /dev/null +++ b/src/mpgame/AFEntity.h @@ -0,0 +1,531 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_AFENTITY_H__ +#define __GAME_AFENTITY_H__ + + +/* +=============================================================================== + +idMultiModelAF + +Entity using multiple separate visual models animated with a single +articulated figure. Only used for debugging! + +=============================================================================== +*/ +const int GIB_DELAY = 200; // only gib this often to keep performace hits when blowing up several mobs + +class idMultiModelAF : public idEntity { +public: + CLASS_PROTOTYPE( idMultiModelAF ); + + void Spawn( void ); + ~idMultiModelAF( void ); + + virtual void Think( void ); + virtual void Present( void ); + +protected: + idPhysics_AF physicsObj; + + void SetModelForId( int id, const idStr &modelName ); + +private: + idList modelHandles; + idList modelDefHandles; +}; + + +/* +=============================================================================== + +idChain + +Chain hanging down from the ceiling. Only used for debugging! + +=============================================================================== +*/ + +class idChain : public idMultiModelAF { +public: + CLASS_PROTOTYPE( idChain ); + + void Spawn( void ); + +protected: + void BuildChain( const idStr &name, const idVec3 &origin, float linkLength, float linkWidth, float density, int numLinks, bool bindToWorld = true ); +}; + + +/* +=============================================================================== + +idAFAttachment + +=============================================================================== +*/ + +typedef struct { + jointModTransform_t mod; + jointHandle_t from; + jointHandle_t to; +} copyJoints_t; + +class idAFAttachment : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFAttachment ); + + idAFAttachment( void ); + virtual ~idAFAttachment( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + +// RAVEN BEGIN +// bdube: added channel + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + // for getting th speaker position + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// jshepard: animations for heads + void Event_PlayAnim ( int channel, const char *animname ); +// jdischler: animations for heads + void Event_PlayCycle ( int channel, const char *animname ); + void Event_ClearAnims ( void ); + + +// RAVEN END + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + int StartLipSyncing( const char *speechDecl ); + void HandleLipSync( void ); + void EndLipSyncing( void ); + bool IsLipSyncing( void ) const; + + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + + + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + jointHandle_t soundJoint; + + int lipSyncAnim; // Anim that contains the visemes + class rvLipSyncData* lipSyncData; // The current instance of lip syncing data + + idList copyJoints; // copied from the body animation to the head model + + bool noPlayerImpactFX; +}; + +// RAVEN BEGIN +// bdube: inlines +ID_INLINE bool idAFAttachment::IsLipSyncing( void ) const { + return !!lipSyncData; +} + +ID_INLINE void idAFAttachment::SetDamageJoint ( jointHandle_t _damageJoint ) { + damageJoint = _damageJoint; +} +// RAVEN END + +/* +=============================================================================== + +idAFEntity_Base + +=============================================================================== +*/ + +class idAFEntity_Base : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idAFEntity_Base ); + + idAFEntity_Base( void ); + virtual ~idAFEntity_Base( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeModelDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + virtual void ShowEditingDialog( void ); + + static void DropAFs( idEntity *ent, const char *type, idList *list ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound + + bool noPlayerImpactFX; + + void Event_SetConstraintPosition( const char *name, const idVec3 &pos ); +}; + +/* +=============================================================================== + +idAFEntity_Gibbable + +=============================================================================== +*/ + +extern const idEventDef EV_Gib; +extern const idEventDef EV_Gibbed; + +class idAFEntity_Gibbable : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Gibbable ); + + idAFEntity_Gibbable( void ); + ~idAFEntity_Gibbable( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Present( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +protected: + idRenderModel * skeletonModel; + int skeletonModelDefHandle; + bool gibbed; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void InitSkeletonModel( void ); + + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + idAFEntity_Generic + +=============================================================================== +*/ + +class idAFEntity_Generic : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_Generic ); + + idAFEntity_Generic( void ); + ~idAFEntity_Generic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void KeepRunningPhysics( void ) { keepRunningPhysics = true; } + +private: + void Event_Activate( idEntity *activator ); + + bool keepRunningPhysics; +}; + + +/* +=============================================================================== + +idAFEntity_WithAttachedHead + +=============================================================================== +*/ + +class idAFEntity_WithAttachedHead : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAFEntity_WithAttachedHead ); + + idAFEntity_WithAttachedHead(); + ~idAFEntity_WithAttachedHead(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void SetupHead( const char* headDefName = "" ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + +protected: + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + + idEntityPtr head; // safe pointer to attached head + +private: + + void Event_Gib( const char *damageDefName ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idAFEntity_Vehicle + +=============================================================================== +*/ + +class idAFEntity_Vehicle : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_Vehicle ); + + idAFEntity_Vehicle( void ); + + void Spawn( void ); + void Use( idPlayer *player ); + +protected: + idPlayer * player; + jointHandle_t eyesJoint; + jointHandle_t steeringWheelJoint; + float wheelRadius; + float steerAngle; + float steerSpeed; +// const idDeclParticle * dustSmoke; + + float GetSteerAngle( void ); +}; + + +/* +=============================================================================== + +idAFEntity_VehicleFourWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleFourWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleFourWheels ); + + idAFEntity_VehicleFourWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +protected: + idAFBody * wheels[4]; + idAFConstraint_Hinge * steering[2]; + jointHandle_t wheelJoints[4]; + float wheelAngles[4]; +}; + + +/* +=============================================================================== + +idAFEntity_VehicleSixWheels + +=============================================================================== +*/ + +class idAFEntity_VehicleSixWheels : public idAFEntity_Vehicle { +public: + CLASS_PROTOTYPE( idAFEntity_VehicleSixWheels ); + + idAFEntity_VehicleSixWheels( void ); + + void Spawn( void ); + virtual void Think( void ); + +private: + idAFBody * wheels[6]; + idAFConstraint_Hinge * steering[4]; + jointHandle_t wheelJoints[6]; + float wheelAngles[6]; +}; + + +/* +=============================================================================== + +idAFEntity_SteamPipe + +=============================================================================== +*/ + +class idAFEntity_SteamPipe : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_SteamPipe ); + + idAFEntity_SteamPipe( void ); + ~idAFEntity_SteamPipe( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + int steamBody; + float steamForce; + float steamUpForce; + idForce_Constant force; + renderEntity_t steamRenderEntity; + qhandle_t steamModelDefHandle; + + void InitSteamRenderEntity( void ); +}; + + +/* +=============================================================================== + +idAFEntity_ClawFourFingers + +=============================================================================== +*/ + +class idAFEntity_ClawFourFingers : public idAFEntity_Base { +public: + CLASS_PROTOTYPE( idAFEntity_ClawFourFingers ); + + idAFEntity_ClawFourFingers( void ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idAFConstraint_Hinge * fingers[4]; + + void Event_SetFingerAngle( float angle ); + void Event_StopFingers( void ); +}; + +// RAVEN BEGIN +// bdube: AFAttractor + +/* +=============================================================================== + +idAFAttractor + +=============================================================================== +*/ + +class rvAFAttractor : public idEntity { +public: + CLASS_PROTOTYPE( rvAFAttractor ); + + rvAFAttractor( void ) { } + +private: +}; + +// RAVEN END + +#endif /* !__GAME_AFENTITY_H__ */ diff --git a/src/mpgame/Actor.cpp b/src/mpgame/Actor.cpp new file mode 100644 index 0000000..2b16004 --- /dev/null +++ b/src/mpgame/Actor.cpp @@ -0,0 +1,3877 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "Vehicle/Vehicle.h" +#endif + +#include "ai/AI.h" +#include "ai/AI_Manager.h" + +/*********************************************************************** + + idAnimState + +***********************************************************************/ + +/* +===================== +idAnimState::idAnimState +===================== +*/ +idAnimState::idAnimState() { + self = NULL; + animator = NULL; + idleAnim = true; + disabled = true; + channel = ANIMCHANNEL_ALL; + animBlendFrames = 0; + lastAnimBlendFrames = 0; +} + +/* +===================== +idAnimState::~idAnimState +===================== +*/ +idAnimState::~idAnimState() { +} + +/* +===================== +idAnimState::Save +===================== +*/ +void idAnimState::Save( idSaveGame *savefile ) const { + + savefile->WriteBool( idleAnim ); + savefile->WriteInt( animBlendFrames ); + savefile->WriteInt( lastAnimBlendFrames ); + + savefile->WriteObject( self ); + + // Save the entity owner of the animator + savefile->WriteObject( animator->GetEntity() ); + + savefile->WriteInt( channel ); + savefile->WriteBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Save( savefile ); +// RAVEN END +} + +/* +===================== +idAnimState::Restore +===================== +*/ +void idAnimState::Restore( idRestoreGame *savefile ) { + + savefile->ReadBool( idleAnim ); + savefile->ReadInt( animBlendFrames ); + savefile->ReadInt( lastAnimBlendFrames ); + + savefile->ReadObject( reinterpret_cast( self ) ); + + idEntity *animowner; + savefile->ReadObject( reinterpret_cast( animowner ) ); + if ( animowner ) { + animator = animowner->GetAnimator(); + } + + savefile->ReadInt( channel ); + savefile->ReadBool( disabled ); + +// RAVEN BEGIN +// abahr: + stateThread.Restore( savefile, self ); +// RAVEN END +} + +/* +===================== +idAnimState::Init +===================== +*/ +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it +void idAnimState::Init( idEntity *owner, idAnimator *_animator, int animchannel ) { +// RAVEN BEGIN + assert( owner ); + assert( _animator ); + self = owner; + animator = _animator; + channel = animchannel; + + stateThread.SetName ( va("%s_anim_%d", owner->GetName(), animchannel ) ); + stateThread.SetOwner ( owner ); +} + +/* +===================== +idAnimState::Shutdown +===================== +*/ +void idAnimState::Shutdown( void ) { + stateThread.Clear ( true ); +} + +/* +===================== +idAnimState::PostState +===================== +*/ +void idAnimState::PostState ( const char* statename, int blendFrames, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, blendFrames, delay, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + disabled = false; +} + +/* +===================== +idAnimState::SetState +===================== +*/ +void idAnimState::SetState( const char *statename, int blendFrames, int flags ) { + if ( SRESULT_OK != stateThread.SetState ( statename, blendFrames, 0, flags ) ) { + gameLocal.Error ( "Could not find state function '%s' for entity '%s'", statename, self->GetName() ); + } + + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + disabled = false; + idleAnim = false; +} + +/* +===================== +idAnimState::StopAnim +===================== +*/ +void idAnimState::StopAnim( int frames ) { + animBlendFrames = 0; + animator->Clear( channel, gameLocal.time, FRAME2MS( frames ) ); +} + +/* +===================== +idAnimState::PlayAnim +===================== +*/ +void idAnimState::PlayAnim( int anim ) { + if ( anim ) { + animator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::CycleAnim +===================== +*/ +void idAnimState::CycleAnim( int anim ) { + if ( anim ) { + animator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( animBlendFrames ) ); + } + animBlendFrames = 0; +} + +/* +===================== +idAnimState::BecomeIdle +===================== +*/ +void idAnimState::BecomeIdle( void ) { + idleAnim = true; +} + +/* +===================== +idAnimState::Disabled +===================== +*/ +bool idAnimState::Disabled( void ) const { + return disabled; +} + +/* +===================== +idAnimState::AnimDone +===================== +*/ +bool idAnimState::AnimDone( int blendFrames ) const { + int animDoneTime; + + animDoneTime = animator->CurrentAnim( channel )->GetEndTime(); + if ( animDoneTime < 0 ) { + // playing a cycle + return false; + } else if ( animDoneTime - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } else { + return false; + } +} + +/* +===================== +idAnimState::IsIdle +===================== +*/ +bool idAnimState::IsIdle( void ) const { + return disabled || idleAnim; +} + +/* +===================== +idAnimState::GetAnimFlags +===================== +*/ +animFlags_t idAnimState::GetAnimFlags( void ) const { + animFlags_t flags; + + memset( &flags, 0, sizeof( flags ) ); + if ( !disabled && !AnimDone( 0 ) ) { + flags = animator->GetAnimFlags( animator->CurrentAnim( channel )->AnimNum() ); + } + + return flags; +} + +/* +===================== +idAnimState::Enable +===================== +*/ +void idAnimState::Enable( int blendFrames ) { + if ( disabled ) { + disabled = false; + animBlendFrames = blendFrames; + lastAnimBlendFrames = blendFrames; + } +} + +/* +===================== +idAnimState::Disable +===================== +*/ +void idAnimState::Disable( void ) { + disabled = true; + idleAnim = false; +} + +/* +===================== +idAnimState::UpdateState +===================== +*/ +bool idAnimState::UpdateState( void ) { + if ( disabled ) { + return false; + } + + stateThread.Execute ( ); + + return true; +} + +/*********************************************************************** + + idActor + +***********************************************************************/ + +const idEventDef AI_EnableEyeFocus( "enableEyeFocus" ); +const idEventDef AI_DisableEyeFocus( "disableEyeFocus" ); +const idEventDef AI_EnableBlink( "enableBlinking" ); +const idEventDef AI_DisableBlink( "disableBlinking" ); +const idEventDef EV_Footstep( "footstep" ); +const idEventDef EV_FootstepLeft( "leftFoot" ); +const idEventDef EV_FootstepRight( "rightFoot" ); +const idEventDef EV_EnableWalkIK( "EnableWalkIK" ); +const idEventDef EV_DisableWalkIK( "DisableWalkIK" ); +const idEventDef EV_EnableLegIK( "EnableLegIK", "d" ); +const idEventDef EV_DisableLegIK( "DisableLegIK", "d" ); +const idEventDef AI_StopAnim( "stopAnim", "dd" ); +const idEventDef AI_PlayAnim( "playAnim", "ds", 'd' ); +const idEventDef AI_PlayCycle( "playCycle", "ds", 'd' ); +const idEventDef AI_IdleAnim( "idleAnim", "ds", 'd' ); +const idEventDef AI_SetSyncedAnimWeight( "setSyncedAnimWeight", "ddf" ); +const idEventDef AI_SetBlendFrames( "setBlendFrames", "dd" ); +const idEventDef AI_GetBlendFrames( "getBlendFrames", "d", 'd' ); +const idEventDef AI_AnimDone( "animDone", "dd", 'd' ); +const idEventDef AI_OverrideAnim( "overrideAnim", "d" ); +const idEventDef AI_EnableAnim( "enableAnim", "dd" ); +const idEventDef AI_PreventPain( "preventPain", "f" ); +const idEventDef AI_DisablePain( "disablePain" ); +const idEventDef AI_EnablePain( "enablePain" ); +const idEventDef AI_SetAnimPrefix( "setAnimPrefix", "s" ); +const idEventDef AI_HasEnemies( "hasEnemies", NULL, 'd' ); +const idEventDef AI_NextEnemy( "nextEnemy", "E", 'e' ); +const idEventDef AI_ClosestEnemyToPoint( "closestEnemyToPoint", "v", 'e' ); +const idEventDef AI_GetHead( "getHead", NULL, 'e' ); + + +// RAVEN BEGIN +// bdube: added +const idEventDef AI_Flashlight("flashlight", "d" ); +const idEventDef AI_Teleport("teleport", "vv"); +const idEventDef AI_EnterVehicle ( "enterVehicle", "e" ); +const idEventDef AI_ExitVehicle ( "exitVehicle", "d" ); +const idEventDef AI_PostExitVehicle ( "", "d" ); + +//jshepard: change animation rate +const idEventDef AI_SetAnimRate ( "setAnimRate","f"); +//MCG: damage over time +const idEventDef EV_DamageOverTime ( "damageOverTime","ddEEvsfd" ); +const idEventDef EV_DamageOverTimeEffect ( "damageOverTimeEffect","dds" ); +// MCG: script-callable joint crawl effect +const idEventDef EV_JointCrawlEffect ( "jointCrawlEffect","sf" ); + +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idActor ) + EVENT( AI_EnableEyeFocus, idActor::Event_EnableEyeFocus ) + EVENT( AI_DisableEyeFocus, idActor::Event_DisableEyeFocus ) + EVENT( AI_EnableBlink, idActor::Event_EnableBlink ) + EVENT( AI_DisableBlink, idActor::Event_DisableBlink ) + EVENT( EV_Footstep, idActor::Event_Footstep ) + EVENT( EV_FootstepLeft, idActor::Event_Footstep ) + EVENT( EV_FootstepRight, idActor::Event_Footstep ) + EVENT( EV_EnableWalkIK, idActor::Event_EnableWalkIK ) + EVENT( EV_DisableWalkIK, idActor::Event_DisableWalkIK ) + EVENT( EV_EnableLegIK, idActor::Event_EnableLegIK ) + EVENT( EV_DisableLegIK, idActor::Event_DisableLegIK ) + EVENT( AI_PreventPain, idActor::Event_PreventPain ) + EVENT( AI_DisablePain, idActor::Event_DisablePain ) + EVENT( AI_EnablePain, idActor::Event_EnablePain ) + EVENT( AI_SetAnimPrefix, idActor::Event_SetAnimPrefix ) + EVENT( AI_SetSyncedAnimWeight, idActor::Event_SetSyncedAnimWeight ) + EVENT( AI_SetBlendFrames, idActor::Event_SetBlendFrames ) + EVENT( AI_GetBlendFrames, idActor::Event_GetBlendFrames ) + EVENT( AI_OverrideAnim, idActor::Event_OverrideAnim ) + EVENT( AI_EnableAnim, idActor::Event_EnableAnim ) + EVENT( AI_HasEnemies, idActor::Event_HasEnemies ) + EVENT( AI_NextEnemy, idActor::Event_NextEnemy ) + EVENT( AI_ClosestEnemyToPoint, idActor::Event_ClosestEnemyToPoint ) + EVENT( EV_StopSound, idActor::Event_StopSound ) + EVENT( AI_GetHead, idActor::Event_GetHead ) + +// RAVEN BEGIN +// bdube: added + EVENT( AI_Flashlight, idActor::Event_Flashlight ) + EVENT( AI_Teleport, idActor::Event_Teleport ) + EVENT( AI_EnterVehicle, idActor::Event_EnterVehicle ) + + // twhitaker: Yeah... this just got confusing. + // basically, I need a delay in between the time the space bar is hit and the time the person actually get's ejected. + // this is mostly for things such as screen fades when exiting a vehicle. This was the least obtrusive way I could think of. + EVENT( AI_ExitVehicle, idActor::Event_PreExitVehicle ) + EVENT( AI_PostExitVehicle, idActor::Event_ExitVehicle ) + +// jshepard: added + EVENT( AI_SetAnimRate, idActor::Event_SetAnimRate ) + +// twhitaker: added animation support (mostly for vehicle purposes) + EVENT( AI_PlayAnim, idActor::Event_PlayAnim ) + +// MCG: added recurring damage + EVENT( EV_DamageOverTime, idActor::Event_DamageOverTime ) + EVENT( EV_DamageOverTimeEffect, idActor::Event_DamageOverTimeEffect ) +// MCG: script-callable joint crawl effect + EVENT( EV_JointCrawlEffect, idActor::Event_JointCrawlEffect ) +// RAVEN END + +END_CLASS + +CLASS_STATES_DECLARATION ( idActor ) + STATE ( "Wait_Frame", idActor::State_Wait_Frame ) + STATE ( "Wait_LegsAnim", idActor::State_Wait_LegsAnim ) + STATE ( "Wait_TorsoAnim", idActor::State_Wait_TorsoAnim ) +END_CLASS_STATES + +// RAVEN END + +/* +===================== +idActor::idActor +===================== +*/ +idActor::idActor( void ) +{ + viewAxis.Identity(); + + use_combat_bbox = false; + head = NULL; + + eyeOffset.Zero(); + chestOffset.Zero(); + modelOffset.Zero(); + + team = 0; + rank = 0; + fovDot = 0.0f; + pain_debounce_time = 0; + pain_delay = 0; + + leftEyeJoint = INVALID_JOINT; + rightEyeJoint = INVALID_JOINT; + soundJoint = INVALID_JOINT; + eyeOffsetJoint = INVALID_JOINT; + chestOffsetJoint = INVALID_JOINT; + neckJoint = INVALID_JOINT; + headJoint = INVALID_JOINT; + + deltaViewAngles.Zero(); + + painTime = 0; + inDamageEvent = false; + disablePain = true; + allowEyeFocus = false; + + blink_anim = NULL; + blink_time = 0; + blink_min = 0; + blink_max = 0; + + finalBoss = false; + + attachments.SetGranularity( 1 ); + + enemyNode.SetOwner( this ); + enemyList.SetOwner( this ); + + teamNode.SetOwner ( this ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlightHandle = -1; + + deathPushTime = 0; + + lightningEffects = 0; + lightningNextTime = 0; +} + +/* +===================== +idActor::~idActor +===================== +*/ +idActor::~idActor( void ) { +// RAVEN BEGIN +// bdube: flashlights + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef( flashlightHandle ); + flashlightHandle = -1; + } +// RAVEN END + + int i; + idEntity *ent; + + StopSound( SND_CHANNEL_ANY, false ); + + delete combatModel; + combatModel = NULL; + + if ( head.GetEntity() ) { + head.GetEntity()->ClearBody(); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + ShutdownThreads(); +} + +/* +===================== +idActor::Spawn +===================== +*/ +void idActor::Spawn( void ) { + idEntity *ent; + idStr jointName; + float fovDegrees; + float fovDegreesClose; + + animPrefix = ""; + + spawnArgs.GetInt( "rank", "0", rank ); + spawnArgs.GetInt( "team", "0", team ); + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + spawnArgs.GetBool( "use_combat_bbox", "0", use_combat_bbox ); + + viewAxis = GetPhysics()->GetAxis(); + + spawnArgs.GetFloat( "fov", "90", fovDegrees ); + spawnArgs.GetFloat( "fovClose", "200", fovDegreesClose ); + spawnArgs.GetFloat( "fovCloseRange", "180", fovCloseRange ); + SetFOV( fovDegrees, fovDegreesClose ); + + pain_debounce_time = 0; + + pain_delay = SEC2MS( spawnArgs.GetFloat( "pain_delay" ) ); + + LoadAF ( ); + + walkIK.Init( this, IK_ANIM, modelOffset ); + + // the animation used to be set to the IK_ANIM at this point, but that was fixed, resulting in + // attachments not binding correctly, so we're stuck setting the IK_ANIM before attaching things. + animator.ClearAllAnims( gameLocal.time, 0 ); +// RAVEN BEGIN +// jscott: new setframe stuff + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, animator.GetAnim( IK_ANIM ), frameBlend ); +// RAVEN END + + // spawn any attachments we might have + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_attach", NULL ); + while ( kv ) { + idDict args; + + args.Set( "classname", kv->GetValue().c_str() ); + + // make items non-touchable so the player can't take them out of the character's hands + args.Set( "no_touch", "1" ); + + // don't let them drop to the floor + args.Set( "dropToFloor", "0" ); + + gameLocal.SpawnEntityDef( args, &ent ); + if ( !ent ) { + gameLocal.Error( "Couldn't spawn '%s' to attach to entity '%s'", kv->GetValue().c_str(), name.c_str() ); + } else { + Attach( ent ); + } + kv = spawnArgs.MatchPrefix( "def_attach", kv ); + } + + SetupDamageGroups(); + + // MP sets up heads on players from UpdateModelSetup() + if( !gameLocal.isMultiplayer || !IsType( idPlayer::GetClassType() ) ) { + SetupHead ( ); + } + + // clear the bind anim + animator.ClearAllAnims( gameLocal.time, 0 ); + + idEntity *headEnt = head.GetEntity(); + idAnimator *headAnimator; + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = &animator; + } + + // set up blinking + blink_anim = headAnimator->GetAnim( "blink" ); + blink_time = 0; // it's ok to blink right away + blink_min = SEC2MS( spawnArgs.GetFloat( "blink_min", "0.5" ) ); + blink_max = SEC2MS( spawnArgs.GetFloat( "blink_max", "8" ) ); + fl.allowAutoBlink = spawnArgs.GetBool( "allowAutoBlink", "1" ); + + if ( spawnArgs.GetString( "sound_bone", "", jointName ) ) { + soundJoint = animator.GetJointHandle( jointName ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), jointName.c_str() ); + } + } + + finalBoss = spawnArgs.GetBool( "finalBoss" ); + +// RAVEN BEGIN +// bdube: flashlight + flashlightJoint = animator.GetJointHandle( spawnArgs.GetString ( "joint_flashlight", "flashlight" ) ); + + memset( &flashlight, 0, sizeof( flashlight ) ); + flashlight.suppressLightInViewID = entityNumber + 1; + flashlight.allowLightInViewID = 0; + flashlight.lightId = 1 + entityNumber; + + flashlight.allowLightInViewID = 1; + + idVec3 color; + spawnArgs.GetVector ( "flashlightColor", "1 1 1", color ); + + flashlight.pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + flashlight.shader = declManager->FindMaterial( spawnArgs.GetString( "mtr_flashlight", "muzzleflash" ), false ); + flashlight.shaderParms[ SHADERPARM_RED ] = color[0]; + flashlight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + flashlight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + flashlight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + flashlight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + flashlight.lightRadius[0] = flashlight.lightRadius[1] = + flashlight.lightRadius[2] = spawnArgs.GetFloat ( "flashlightRadius" ); + + if ( !flashlight.pointLight ) { + flashlight.target = spawnArgs.GetVector( "flashlightTarget" ); + flashlight.up = spawnArgs.GetVector( "flashlightUp" ); + flashlight.right = spawnArgs.GetVector( "flashlightRight" ); + flashlight.end = spawnArgs.GetVector( "flashlightTarget" ); + } + + spawnArgs.GetVector ( "flashlightOffset", "0 0 0", flashlightOffset ); + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + +// RAVEN END + + stateThread.SetName ( GetName() ); + stateThread.SetOwner ( this ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = true; +// RAVEN END + + FinishSetup(); +} + +/* +================ +idActor::FinishSetup +================ +*/ +void idActor::FinishSetup( void ) { + if ( spawnArgs.GetBool ( "flashlight", "0" ) ) { + FlashlightUpdate ( true ); + } + + SetupBody(); +} + +/* +================ +idActor::SetupHead +================ +*/ +void idActor::SetupHead( const char* headDefName, idVec3 headOffset ) { + idAFAttachment *headEnt; + idStr jointName; + jointHandle_t joint; + const idKeyValue *sndKV; + + if ( gameLocal.isClient && head.GetEntity() == NULL ) { + return; + } + + // If we don't pass in a specific head model, try looking it up + if( !headDefName[ 0 ] ) { + headDefName = spawnArgs.GetString( "def_head", "" ); +// jshepard: allow for heads to override persona defs + headDefName = spawnArgs.GetString( "override_head", headDefName ); + } + + if ( headDefName[ 0 ] ) { + // free the old head if we want a new one + if( gameLocal.isServer ) { + if( head && idStr::Icmp( head->spawnArgs.GetString( "classname" ), headDefName ) ) { + head->SetName( va( "%s_oldhead", name.c_str() ) ); + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } else if( head ) { + // the current head is OK + return; + } + } + + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", jointName.c_str(), name.c_str() ); + } + + // copy any sounds in case we have frame commands on the head + idDict args; + sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + if ( !gameLocal.isClient ) { + args.Set( "classname", headDefName ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idActor::SetupHead() - Unknown head model '%s'\n", headDefName ); + return; + } + headEnt->spawnArgs.Set( "classname", headDefName ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + head = headEnt; + } else { + // we got our spawnid from the server + headEnt = head.GetEntity(); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + } + + headEnt->BindToJoint( this, joint, true ); + headEnt->GetPhysics()->SetOrigin( vec3_origin + headOffset ); + headEnt->GetPhysics()->SetAxis( mat3_identity ); + } else if ( head ) { + head->PostEventMS( &EV_Remove, 0 ); + head = NULL; + } + + if ( head ) { + int i; + // set the damage joint to be part of the head damage group + for( i = 0; i < damageGroups.Num(); i++ ) { + if ( damageGroups[ i ] == "head" ) { + head->SetDamageJoint ( static_cast( i ) ); + break; + } + } + + head->InitCopyJoints ( ); + head->SetInstance( instance ); + } +} + +/* +================ +idActor::Restart +================ +*/ +void idActor::Restart( void ) { + assert( !head.GetEntity() ); + // MP sets up heads from UpdateModelSetup() + if( !gameLocal.isMultiplayer ) { + SetupHead(); + } + FinishSetup(); +} + +/* +================ +idActor::Save + +archive object for savegame file +================ +*/ +void idActor::Save( idSaveGame *savefile ) const { + idActor *ent; + int i; + + savefile->WriteInt( team ); + + // cnicholson: This line was already commented out, so we aint changing it. + // No need to write/read teamNode + // idLinkList teamNode; + + savefile->WriteInt( rank ); + savefile->WriteMat3( viewAxis ); + +// twhitaker: this confuses me... should we be writing out enemyList.Num() or enemyList.Next()->enemyNode->Num(). I'm not sure what these variables represent. +// cnicholson: bdube said to do it how id does it, so here goes: + savefile->WriteInt( enemyList.Num() ); + for ( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + savefile->WriteObject( ent ); + } + + savefile->WriteInt( lightningNextTime );// cnicholson: Added unwritten var + savefile->WriteInt( lightningEffects ); // cnicholson: Added unwritten var + + savefile->WriteFloat( fovDot ); + savefile->WriteFloat( fovCloseDot ); + savefile->WriteFloat( fovCloseRange ); + savefile->WriteVec3( eyeOffset ); + savefile->WriteVec3( chestOffset ); + savefile->WriteVec3( modelOffset ); + savefile->WriteAngles( deltaViewAngles ); + + savefile->WriteInt( pain_debounce_time ); + savefile->WriteInt( pain_delay ); + + savefile->WriteInt( damageGroups.Num() ); + for( i = 0; i < damageGroups.Num(); i++ ) { + savefile->WriteString( damageGroups[ i ] ); + } + + savefile->WriteInt( damageScale.Num() ); + for( i = 0; i < damageScale.Num(); i++ ) { + savefile->WriteFloat( damageScale[ i ] ); + } +//MCG + savefile->WriteBool( inDamageEvent ); + + savefile->WriteBool( use_combat_bbox ); + + savefile->WriteJoint( leftEyeJoint ); + savefile->WriteJoint( rightEyeJoint ); + savefile->WriteJoint( soundJoint ); + savefile->WriteJoint( eyeOffsetJoint ); + savefile->WriteJoint( chestOffsetJoint ); + savefile->WriteJoint( neckJoint ); + savefile->WriteJoint( headJoint ); + + walkIK.Save( savefile ); + + savefile->WriteString( animPrefix ); + savefile->WriteString( painType ); + savefile->WriteString( painAnim ); + + savefile->WriteInt( blink_anim ); + savefile->WriteInt( blink_time ); + savefile->WriteInt( blink_min ); + savefile->WriteInt( blink_max ); + + headAnim.Save( savefile ); + torsoAnim.Save( savefile ); + legsAnim.Save( savefile ); + + stateThread.Save( savefile ); + + // idEntityPtr head; + head.Save( savefile ); // cnicholson: Added unwritten var + + savefile->WriteBool( disablePain ); + savefile->WriteBool( allowEyeFocus ); + savefile->WriteBool( finalBoss ); + + savefile->WriteInt( painTime ); + + savefile->WriteInt( attachments.Num() ); + for ( i = 0; i < attachments.Num(); i++ ) { + attachments[i].ent.Save( savefile ); + savefile->WriteInt( attachments[i].channel ); + } + + vehicleController.Save ( savefile ); + + // These aren't saved in the same order as they're declared, due to the dependency (I didn't see the need to change the order of declaration) + savefile->WriteInt ( flashlightHandle ); + savefile->WriteJoint ( flashlightJoint ); + savefile->WriteVec3 ( flashlightOffset ); + savefile->WriteRenderLight ( flashlight ); + + savefile->WriteInt( deathPushTime ); + savefile->WriteVec3( deathPushForce ); + savefile->WriteJoint( deathPushJoint ); +} + +/* +================ +idActor::Restore + +unarchives object from save game file +================ +*/ +void idActor::Restore( idRestoreGame *savefile ) { + int i, num; + idActor *ent; + + savefile->ReadInt( team ); + +// cnicholson: This line was already commented out, so we aint changing it. +// No need to write/read teamNode + // idLinkList teamNode; + + savefile->ReadInt( rank ); + savefile->ReadMat3( viewAxis ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->enemyNode.AddToEnd( enemyList ); + } + } + + savefile->ReadInt( lightningEffects ); + savefile->ReadInt( lightningNextTime ); + + savefile->ReadFloat( fovDot ); + savefile->ReadFloat( fovCloseDot ); + savefile->ReadFloat( fovCloseRange ); + savefile->ReadVec3( eyeOffset ); + savefile->ReadVec3( chestOffset ); + savefile->ReadVec3( modelOffset ); + savefile->ReadAngles( deltaViewAngles ); + + savefile->ReadInt( pain_debounce_time ); + savefile->ReadInt( pain_delay ); + + savefile->ReadInt( num ); + damageGroups.SetGranularity( 1 ); + damageGroups.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadString( damageGroups[ i ] ); + } + + savefile->ReadInt( num ); + damageScale.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadFloat( damageScale[ i ] ); + } +//MCG + savefile->ReadBool( inDamageEvent ); + + savefile->ReadBool( use_combat_bbox ); + + savefile->ReadJoint( leftEyeJoint ); + savefile->ReadJoint( rightEyeJoint ); + savefile->ReadJoint( soundJoint ); + savefile->ReadJoint( eyeOffsetJoint ); + savefile->ReadJoint( chestOffsetJoint ); + savefile->ReadJoint( neckJoint ); + savefile->ReadJoint( headJoint ); + + walkIK.Restore( savefile ); + + savefile->ReadString( animPrefix ); + savefile->ReadString( painType ); + savefile->ReadString( painAnim ); + + savefile->ReadInt( blink_anim ); + savefile->ReadInt( blink_time ); + savefile->ReadInt( blink_min ); + savefile->ReadInt( blink_max ); + + headAnim.Restore( savefile ); + torsoAnim.Restore( savefile ); + legsAnim.Restore( savefile ); + + stateThread.Restore( savefile, this ); + +// cnicholson: Restore unread var + // idEntityPtr head; + head.Restore(savefile); + + savefile->ReadBool( disablePain ); + savefile->ReadBool( allowEyeFocus ); + savefile->ReadBool( finalBoss ); + + savefile->ReadInt( painTime ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idAttachInfo &attach = attachments.Alloc(); + attach.ent.Restore( savefile ); + savefile->ReadInt( attach.channel ); + } + +// RAVEN BEGIN +// bdube: added + vehicleController.Restore ( savefile ); + + savefile->ReadInt ( flashlightHandle ); + savefile->ReadJoint ( flashlightJoint ); + savefile->ReadVec3 ( flashlightOffset ); + savefile->ReadRenderLight ( flashlight ); + if ( flashlightHandle != -1 ) { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + + savefile->ReadInt( deathPushTime ); + savefile->ReadVec3( deathPushForce ); + savefile->ReadJoint( deathPushJoint ); + +// mekberg: update this + FlashlightUpdate( ); +// RAVEN END +} + +/* +================ +idActor::Hide +================ +*/ +void idActor::Hide( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Hide(); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Hide(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Off(); + } + } + } + UnlinkCombat(); +} + +/* +================ +idActor::Show +================ +*/ +void idActor::Show( void ) { + idEntity *ent; + idEntity *next; + + idAFEntity_Base::Show(); + if ( head.GetEntity() ) { + head.GetEntity()->Show(); + } + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + ent->Show(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->On(); + } + } + } + LinkCombat(); +} + +/* +============== +idActor::GetDefaultSurfaceType +============== +*/ +int idActor::GetDefaultSurfaceType( void ) const { + return SURFTYPE_FLESH; +} + +/* +================ +idActor::ProjectOverlay +================ +*/ +void idActor::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + idEntity *ent; + idEntity *next; + + idEntity::ProjectOverlay( origin, dir, size, material ); + + for( ent = GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == this ) { + if ( ent->fl.takedamage && ent->spawnArgs.GetBool( "bleed" ) ) { + ent->ProjectOverlay( origin, dir, size, material ); + } + } + } +} + +/* +================ +idActor::LoadAF +================ +*/ +bool idActor::LoadAF( const char* keyname, bool purgeAF /* = false */ ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) || !fileName.Length() ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName, purgeAF ); +} + +/* +===================== +idActor::SetupBody +===================== +*/ +void idActor::SetupBody( void ) { + const char* jointname; + idAnimator* headAnimator; + float height; + + animator.ClearAllAnims( gameLocal.time, 0 ); + animator.ClearAllJoints(); + + // Cache the head entity pointer and determine which animator to use + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + } else { + headAnimator = GetAnimator( ); + } + + // Get left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_leftEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_leftEye" ); + } + leftEyeJoint = headAnimator->GetJointHandle( jointname ); + + // Get right eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString ( "joint_rightEye", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_rightEye" ); + } + rightEyeJoint = headAnimator->GetJointHandle( jointname ); + + // If head height is specified, just use that + if ( spawnArgs.GetFloat( "eye_height", "0", height ) ) { + SetEyeHeight( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + if ( !headEnt || !headEnt->spawnArgs.GetString( "joint_eyeOffset", "", &jointname ) ) { + jointname = spawnArgs.GetString( "joint_eyeOffset" ); + } + // Get the eye offset joint + eyeOffsetJoint = headAnimator->GetJointHandle( jointname ); + } + + // If eye height is specified, just use that + if ( spawnArgs.GetFloat( "chest_height", "0", height ) ) { + SetChestHeight( height ); + } else { + // See if there is an eye offset joint specified, if not just use the left eye joint + spawnArgs.GetString( "joint_chestOffset", "", &jointname ); + // Get the chest offset joint + chestOffsetJoint = animator.GetJointHandle( jointname ); + } + + // Get the neck joint + spawnArgs.GetString( "joint_look_neck", "", &jointname ); + neckJoint = animator.GetJointHandle( jointname ); + + // Get the head joint + spawnArgs.GetString( "joint_look_head", "", &jointname ); + headJoint = animator.GetJointHandle( jointname ); + + headAnim.Init( this, &animator, ANIMCHANNEL_HEAD ); + torsoAnim.Init( this, &animator, ANIMCHANNEL_TORSO ); + legsAnim.Init( this, &animator, ANIMCHANNEL_LEGS ); +} + +/* +===================== +idActor::CheckBlink +===================== +*/ +void idActor::CheckBlink( void ) { + // check if it's time to blink + if ( !blink_anim || ( health <= 0 ) || ( blink_time > gameLocal.time ) || !fl.allowAutoBlink ) { + return; + } + + idAnimator *animator = head.GetEntity() ? head->GetAnimator() : &this->animator; + animator->PlayAnim( ANIMCHANNEL_EYELIDS, blink_anim, gameLocal.time, 1 ); + + // set the next blink time + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +================ +idActor::GetPhysicsToVisualTransform +================ +*/ +bool idActor::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } +// RAVEN BEGIN +// bdube: position player in seat (nmckenzie: copy and paste from the player version of this call) + if ( vehicleController.IsDriving ( ) ) { + vehicleController.GetDriverPosition ( origin, axis ); + return true; + } +// RAVEN END + + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +idActor::GetPhysicsToSoundTransform +================ +*/ +bool idActor::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + if ( soundJoint != INVALID_JOINT ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + origin += modelOffset; + axis = viewAxis; + } else { + origin = GetPhysics()->GetGravityNormal() * -eyeOffset.z; + axis.Identity(); + } + return true; +} + +/*********************************************************************** + + script state management + +***********************************************************************/ + +/* +================ +idActor::ShutdownThreads +================ +*/ +void idActor::ShutdownThreads( void ) { + headAnim.Shutdown(); + torsoAnim.Shutdown(); + legsAnim.Shutdown(); +} + +/* +===================== +idActor::OnStateThreadClear +===================== +*/ +void idActor::OnStateThreadClear( const char *statename, int flags ) { +} + +/* +===================== +idActor::SetState +===================== +*/ +void idActor::SetState( const char *statename, int flags ) { + OnStateThreadClear( statename, flags ); + stateThread.SetState ( statename, 0, 0, flags ); +} + +/* +===================== +idActor::PostState +===================== +*/ +void idActor::PostState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.PostState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::InterruptState +===================== +*/ +void idActor::InterruptState ( const char* statename, int delay, int flags ) { + if ( SRESULT_OK != stateThread.InterruptState ( statename, 0, delay, flags ) ) { + gameLocal.Error ( "unknown state '%s' on entity '%s'", statename, GetName() ); + } +} + +/* +===================== +idActor::UpdateState +===================== +*/ +void idActor::UpdateState ( void ) { + stateThread.Execute ( ); +} + +/*********************************************************************** + + vision + +***********************************************************************/ + +/* +===================== +idActor::setFov +===================== +*/ +void idActor::SetFOV( float fov, float fovClose ) { + fovDot = idMath::Cos( DEG2RAD( fov * 0.5f ) ); + fovCloseDot = idMath::Cos( DEG2RAD( fovClose * 0.5f ) ); +} + +/* +===================== +idActor::SetEyeHeight +===================== +*/ +void idActor::SetEyeHeight( float height ) { + eyeOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::EyeHeight +===================== +*/ +float idActor::EyeHeight( void ) const { + return eyeOffset.z; +} + +/* +===================== +idActor::SetChestHeight +===================== +*/ +void idActor::SetChestHeight( float height ) { + chestOffset = GetPhysics()->GetGravityNormal() * -height; +} + +/* +===================== +idActor::GetEyePosition +===================== +*/ +idVec3 idActor::GetEyePosition( void ) const { + return GetPhysics()->GetOrigin() + eyeOffset * viewAxis; +} + +/* +===================== +idActor::GetChestPosition +===================== +*/ +idVec3 idActor::GetChestPosition( void ) const { + return GetPhysics()->GetOrigin() + chestOffset * viewAxis; +} + +// RAVEN BEGIN +// bdube: flashlights + +/* +===================== +idActor::GetGroundEntity +===================== +*/ +idEntity* idActor::GetGroundEntity( void ) const { + return static_cast(GetPhysics())->GetGroundEntity(); +} + +/* +===================== +idActor::Present +===================== +*/ +void idActor::Present( void ) { + idAFEntity_Gibbable::Present(); + + FlashlightUpdate(); +} + +/* +===================== +idActor::Event_Teleport +===================== +*/ +void idActor::Event_Teleport( idVec3 &newPos, idVec3 &newAngles ) { + Teleport ( newPos, newAngles.ToAngles(), NULL ); +} + +/* +===================== +idActor::Event_EnterVehicle +===================== +*/ +void idActor::Event_EnterVehicle ( idEntity* vehicle ) { + if ( IsInVehicle ( ) ) { + return; + } + EnterVehicle ( vehicle ); +} + +/* +===================== +idActor::Event_ExitVehicle +===================== +*/ +void idActor::Event_ExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + ExitVehicle ( force ); +} + +/* +===================== +idActor::Event_PreExitVehicle +===================== +*/ +void idActor::Event_PreExitVehicle ( bool force ) { + if ( !IsInVehicle ( ) ) { + return; + } + + // call the script func regardless of the fact that we may not be getting out just yet. + // this allows things in the script to happen before ejection actually occurs (such as screen fades). + vehicleController.GetVehicle()->OnExit(); + + // this is done because having an exit delay when the player is dead was causing bustedness if you died in the walker + // specifically the restart menu would not appear if you were still in the walker + if ( health > 0 ) { + PostEventMS( &AI_PostExitVehicle, vehicleController.GetVehicle()->spawnArgs.GetInt( "exit_vehicle_delay" ), force ); + } + else { + ExitVehicle(true); + } +} + +/* +===================== +idActor::Event_Flashlight +===================== +*/ +void idActor::Event_Flashlight( bool on ) { + if ( on ) { + FlashlightUpdate(true); + } else { + if ( flashlightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( flashlightHandle ); + flashlightHandle = -1; + } + + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + HideSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + StopEffect( "fx_flashlight", true ); + } + } +} + +/* +===================== +idActor::FlashlightUpdate +===================== +*/ +void idActor::FlashlightUpdate ( bool forceOn ) { + + // Dont do anything if flashlight is off and its not being forced on + if ( !forceOn && flashlightHandle == -1 ) { + return; + } + + if ( !flashlight.lightRadius[0] || flashlightJoint == INVALID_JOINT ) { + return; + } + + if ( forceOn && flashlightHandle == -1 ) { + //first time turning it on + if ( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ) { + ShowSurface( spawnArgs.GetString( "flashlight_flaresurf", NULL ) ); + } + if ( spawnArgs.GetString( "fx_flashlight", NULL ) ) { + PlayEffect( "fx_flashlight", flashlightJoint, true ); + } + } + + // the flash has an explicit joint for locating it + GetJointWorldTransform ( flashlightJoint, gameLocal.time, flashlight.origin, flashlight.axis ); + flashlight.origin += flashlightOffset * flashlight.axis; + + if ( flashlightHandle != -1 ) { + gameRenderWorld->UpdateLightDef( flashlightHandle, &flashlight ); + } else { + flashlightHandle = gameRenderWorld->AddLightDef( &flashlight ); + } + +} + +// RAVEN END + +/* +===================== +idActor::GetViewPos +===================== +*/ +void idActor::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + origin = GetEyePosition(); + axis = viewAxis; +} + +/* +===================== +idActor::CheckFOV +===================== +*/ +bool idActor::CheckFOV( const idVec3 &pos, float ang ) const { + float testAng = ( ang != -1.0f ) ? idMath::Cos( DEG2RAD( ang * 0.5f ) ) : fovDot; + if ( testAng == 1.0f ) { + return true; + } + + if(!GetPhysics()) { + return false; + } + + float dot; + float dist; + idVec3 delta; + + delta = pos - GetEyePosition(); + dist = delta.LengthFast(); + + //NOTE!!! + //This logic is BACKWARDS, but it's too late in the project + //for me to feel comfortable fixing this. It SHOULD be: + //if ( ang == -1.0f ) + // - MCG + if ( ang != -1.0f ) + {//not overriding dot test value + if (distGetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + dot = viewAxis[ 0 ] * delta; + + return ( dot >= testAng ); +} + +/* +===================== +idActor::HasFOV +===================== +*/ + +bool idActor::HasFOV( idEntity *ent ) +{ + // Fixme: Make this do something, anything. + return true; +} +// RAVEN END + +/* +===================== +idActor::CanSee +===================== +*/ +bool idActor::CanSee( const idEntity *ent, bool useFov ) const { + return CanSeeFrom ( GetEyePosition ( ), ent, useFov ); +} + +/* +===================== +idActor::CanSeeFrom +===================== +*/ +bool idActor::CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFov ) const { + idVec3 toPos; + + if ( !ent || ent->IsHidden() ) { + return false; + } + + if ( ent->IsType( idActor::Type ) ) { + toPos = ((idActor*)ent)->GetEyePosition(); + } else { + toPos = ent->GetPhysics()->GetAbsBounds().GetCenter ( ); + } + + return CanSeeFrom ( from, toPos, useFov ); +} + +bool idActor::CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFov ) const { + trace_t tr; + + if ( useFov && !CheckFOV( toPos ) ) { + return false; + } + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + return true; + } + gameLocal.TracePoint( this, tr, from, toPos, MASK_OPAQUE, this ); + if ( tr.fraction >= 1.0f ) { // || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + return true; + } + + return false; +} + +/* +===================== +idActor::GetRenderView +===================== +*/ +renderView_t *idActor::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->viewaxis = viewAxis; + rv->vieworg = GetEyePosition(); + return rv; +} + +/*********************************************************************** + + Model/Ragdoll + +***********************************************************************/ + +/* +================ +idActor::SetCombatModel +================ +*/ +void idActor::SetCombatModel( void ) { + idAFAttachment *headEnt; + +// RAVEN BEGIN +// bdube: set the combat model reguardless + if ( 1 ) { // !use_combat_bbox ) { +// RAVEN END + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( modelDefHandle ); + } else { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->SetCombatModel(); + } + } +} + +/* +================ +idActor::GetCombatModel +================ +*/ +idClipModel *idActor::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +idActor::LinkCombat +================ +*/ +void idActor::LinkCombat( void ) { + idAFAttachment *headEnt; + + if ( fl.hidden || use_combat_bbox ) { + return; + } + + if ( combatModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->LinkCombat(); + } +} + +/* +================ +idActor::UnlinkCombat +================ +*/ +void idActor::UnlinkCombat( void ) { + idAFAttachment *headEnt; + + if ( combatModel ) { + combatModel->Unlink(); + } + headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->UnlinkCombat(); + } +} + +/* +================ +idActor::StartRagdoll +================ +*/ +bool idActor::StartRagdoll( void ) { + float slomoStart, slomoEnd; + float jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd; + float contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd; + + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // Raise the origin up 5 units to help ensure the ragdoll doesnt start in the ground + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -5.0f ); + UpdateModelTransform(); + + // disable the monster bounding box + GetPhysics()->DisableClip(); + + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + slomoStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoStart", "-1.6" ); + slomoEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_slomoEnd", "0.8" ); + + // do the first part of the death in slow motion + af.GetPhysics()->SetTimeScaleRamp( slomoStart, slomoEnd ); + + jointFrictionDent = spawnArgs.GetFloat( "ragdoll_jointFrictionDent", "0.1" ); + jointFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionStart", "0.2" ); + jointFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_jointFrictionEnd", "1.2" ); + + // set joint friction dent + af.GetPhysics()->SetJointFrictionDent( jointFrictionDent, jointFrictionDentStart, jointFrictionDentEnd ); + + contactFrictionDent = spawnArgs.GetFloat( "ragdoll_contactFrictionDent", "0.1" ); + contactFrictionDentStart = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionStart", "1.0" ); + contactFrictionDentEnd = MS2SEC( gameLocal.time ) + spawnArgs.GetFloat( "ragdoll_contactFrictionEnd", "2.0" ); + + // set contact friction dent + af.GetPhysics()->SetContactFrictionDent( contactFrictionDent, contactFrictionDentStart, contactFrictionDentEnd ); + + // drop any items the actor is holding + idList list; + idMoveableItem::DropItems( this, "death", &list ); + for ( int i = 0; i < list.Num(); i++ ) { + if ( list[i] && list[i]->GetPhysics() ) + { + idVec3 velocity; + float pitchDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float yawDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + float rollDir = gameLocal.random.CRandomFloat()>0.0f?1.0f:-1.0f; + velocity.Set( pitchDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + yawDir*((gameLocal.random.RandomFloat() * 200.0f) + 50.0f), + (gameLocal.random.RandomFloat() * 300.0f) + 100.0f ); + list[i]->GetPhysics()->SetAngularVelocity( idVec3( pitchDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + yawDir*((gameLocal.random.RandomFloat() * 6.0f) + 2.0f), + rollDir*((gameLocal.random.RandomFloat() * 10.0f) + 3.0f))); + if ( gibbed ) { + //only throw them if we end up gibbed? + list[i]->GetPhysics()->SetLinearVelocity( velocity ); + } + } + } + + // drop any articulated figures the actor is holding + idAFEntity_Base::DropAFs( this, "death", NULL ); + + RemoveAttachments(); + +// RAVEN BEGIN +// bdube: evaluate one ragdoll frame + RunPhysics(); +// RAVEN END + + return true; +} + +/* +================ +idActor::StopRagdoll +================ +*/ +void idActor::StopRagdoll( void ) { + if ( af.IsActive() ) { + af.Stop(); + } +} + +/* +================ +idActor::UpdateAnimationControllers +================ +*/ +bool idActor::UpdateAnimationControllers( void ) { + + if ( af.IsActive() ) { + return idAFEntity_Gibbable::UpdateAnimationControllers(); + } else { + animator.ClearAFPose(); + } + + if ( walkIK.IsInitialized() ) { + walkIK.Evaluate(); + return true; + } + + idMat3 axis; + idAnimatedEntity* headEnt = head.GetEntity( ); + if ( !headEnt ) { + headEnt = this; + } + + // Dynamically update the eye offset if a joint was specified + if ( eyeOffsetJoint != INVALID_JOINT ) { + headEnt->GetJointWorldTransform( eyeOffsetJoint, gameLocal.time, eyeOffset, axis ); + eyeOffset = (eyeOffset - GetPhysics()->GetOrigin()) * viewAxis.Transpose( ); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorRed, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + // Dynamically update the chest offset if a joint was specified + if ( chestOffsetJoint != INVALID_JOINT ) { + GetJointWorldTransform( chestOffsetJoint, gameLocal.time, chestOffset, axis ); + chestOffset = ( chestOffset - GetPhysics()->GetOrigin() ) * viewAxis.Transpose(); + } + + if ( DebugFilter( ai_debugMove ) ) { // RED = Eye Pos & orientation + gameRenderWorld->DebugArrow( colorPink, GetChestPosition(), GetChestPosition() + viewAxis[ 0 ] * 32.0f, 4, gameLocal.msec ); + } + + return false; +} + +/* +================ +idActor::RemoveAttachments +================ +*/ +void idActor::RemoveAttachments( void ) { + int i; + idEntity *ent; + + // remove any attached entities + for( i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && ent->spawnArgs.GetBool( "remove" ) ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } +} + +/* +================ +idActor::Attach +================ +*/ +void idActor::Attach( idEntity *ent ) { + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + idStr jointName; + idAttachInfo &attach = attachments.Alloc(); + idAngles angleOffset; + idVec3 originOffset; + + jointName = ent->spawnArgs.GetString( "joint" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for attaching '%s' on '%s'", jointName.c_str(), ent->GetClassname(), name.c_str() ); + } + + angleOffset = ent->spawnArgs.GetAngles( "angles" ); + originOffset = ent->spawnArgs.GetVector( "origin" ); + + attach.channel = animator.GetChannelForJoint( joint ); + GetJointWorldTransform( joint, gameLocal.time, origin, axis ); + attach.ent = ent; + + ent->SetOrigin( origin + originOffset * renderEntity.axis ); + idMat3 rotate = angleOffset.ToMat3(); + idMat3 newAxis = rotate * axis; + ent->SetAxis( newAxis ); + ent->BindToJoint( this, joint, true ); + ent->cinematic = cinematic; +} + +idEntity* idActor::FindAttachment( const char* attachmentName ) +{ + idEntity *ent = NULL; + const char* fullName = va("idAFAttachment_%s",attachmentName); + // find the specified attachment + for( int i = 0; i < attachments.Num(); i++ ) { + ent = attachments[ i ].ent.GetEntity(); + if ( ent && !ent->name.CmpPrefix(fullName) ) { + return ent; + } + } + return NULL; +} + +void idActor::HideAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Hide(); + } +} + +void idActor::ShowAttachment( const char* attachmentName ) +{ + idEntity *ent = FindAttachment( attachmentName ); + if ( ent ) + { + ent->Show(); + } +} + +/* +================ +idActor::Teleport +================ +*/ +void idActor::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + viewAxis = angles.ToMat3(); + + UpdateVisuals(); + + if ( !IsHidden() ) { + // kill anything at the new position + gameLocal.KillBox( this ); + } +} + +/* +================ +idActor::GetDeltaViewAngles +================ +*/ +const idAngles &idActor::GetDeltaViewAngles( void ) const { + return deltaViewAngles; +} + +/* +================ +idActor::SetDeltaViewAngles +================ +*/ +void idActor::SetDeltaViewAngles( const idAngles &delta ) { + deltaViewAngles = delta; +} + +/* +================ +idActor::HasEnemies +================ +*/ +bool idActor::HasEnemies( void ) const { + idActor *ent; + + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden ) { + return true; + } + } + + return false; +} + +/* +================ +idActor::ClosestEnemyToPoint +================ +*/ +idActor *idActor::ClosestEnemyToPoint( const idVec3 &pos, float maxRange, bool returnFirst, bool checkPVS ) { + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + pvsHandle_t pvs; + + //just to supress the compiler warning + pvs.i = 0; + + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + } + + bestDistSquared = maxRange?(maxRange*maxRange):idMath::INFINITY; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - pos; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + if ( checkPVS ) { + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + } + bestEnt = ent; + bestDistSquared = distSquared; + if ( returnFirst ) { + break; + } + } + } + + if ( checkPVS ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + return bestEnt; +} + +/* +================ +idActor::EnemyWithMostHealth +================ +*/ +idActor *idActor::EnemyWithMostHealth() { + idActor *ent; + idActor *bestEnt; + + int most = -9999; + bestEnt = NULL; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( !ent->fl.hidden && ( ent->health > most ) ) { + bestEnt = ent; + most = ent->health; + } + } + return bestEnt; +} + +/* +================ +idActor::OnLadder +================ +*/ +bool idActor::OnLadder( void ) const { + return false; +} + +/* +============== +idActor::GetAASLocation +============== +*/ +void idActor::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + idVec3 size; + idBounds bounds; + + GetFloorPos( 64.0f, pos ); + if ( !aas ) { + areaNum = 0; + return; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aas->PushPointIntoAreaNum( areaNum, pos ); + } +} + +/*********************************************************************** + + animation state + +***********************************************************************/ + +/* +===================== +idActor::StopAnimState +===================== +*/ +void idActor::StopAnimState( int channel ) { + GetAnimState( channel ).Shutdown ( ); +} + +/* +===================== +idActor::PostAnimState +===================== +*/ +void idActor::PostAnimState( int channel, const char* statename, int blendFrames, int delay, int flags ) { + GetAnimState( channel ).PostState( statename, blendFrames, delay, flags ); +} + +/* +===================== +idActor::SetAnimState +===================== +*/ +void idActor::SetAnimState( int channel, const char *statename, int blendFrames, int flags ) { + switch ( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.GetStateThread().Clear(); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.GetStateThread().Clear(); + legsAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.GetStateThread().Clear(); + torsoAnim.Enable( blendFrames ); + break; + } + + OnStateChange( channel ); + + PostAnimState( channel, statename, blendFrames, flags ); +} + +/* +===================== +idActor::OnStateChange +===================== +*/ +void idActor::OnStateChange ( int channel ) { + allowEyeFocus = true; + + // Only clear eye focus on head channel change + if ( channel == ANIMCHANNEL_HEAD ) { + return; + } + + disablePain = false; +} + +/* +===================== +idActor::OnFriendlyFire +===================== +*/ +void idActor::OnFriendlyFire ( idActor* attacker ) { +} + +/* +===================== +idActor::UpdateAnimState +===================== +*/ +void idActor::UpdateAnimState( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ANIM); +// RAVEN END + headAnim.UpdateState(); + torsoAnim.UpdateState(); + legsAnim.UpdateState(); +} + +/* +===================== +idActor::GetAnim +===================== +*/ +int idActor::GetAnim( int channel, const char *animname, bool forcePrefix ) { + int anim; + const char *temp; + idAnimator *animatorPtr; + + if ( channel == ANIMCHANNEL_HEAD ) { + if ( !head.GetEntity() ) { + return 0; + } + animatorPtr = head.GetEntity()->GetAnimator(); + } else { + animatorPtr = &animator; + } + + // Allow for anim substitution + animname = spawnArgs.GetString ( va("anim %s", animname ), animname ); + + if ( animPrefix.Length() ) { + temp = va( "%s_%s", animPrefix.c_str(), animname ); + anim = animatorPtr->GetAnim( temp ); + if ( anim ) { + return anim; + } else if ( forcePrefix ) { + return NULL; + } + } + + anim = animatorPtr->GetAnim( animname ); + + return anim; +} + +/* +=============== +idActor::SyncAnimChannels +=============== +*/ +void idActor::SyncAnimChannels( int channel, int syncToChannel, int blendFrames ) { + idAnimator *headAnimator; + idAFAttachment *headEnt; + int anim; + idAnimBlend *syncAnim; + int starttime; + int blendTime; + int cycle; + + blendTime = FRAME2MS( blendFrames ); + if ( channel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = animator.CurrentAnim( syncToChannel ); + if ( syncAnim ) { + anim = headAnimator->GetAnim( syncAnim->AnimFullName() ); + if ( !anim ) { + anim = headAnimator->GetAnim( syncAnim->AnimName() ); + } + if ( anim ) { + cycle = animator.CurrentAnim( syncToChannel )->GetCycleCount(); + starttime = animator.CurrentAnim( syncToChannel )->GetStartTime(); + headAnimator->PlayAnim( ANIMCHANNEL_LEGS, anim, gameLocal.time, blendTime ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetCycleCount( cycle ); + headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->SetStartTime( starttime ); + } else { + headEnt->PlayIdleAnim( ANIMCHANNEL_LEGS, blendTime ); + } + } + } + } else if ( syncToChannel == ANIMCHANNEL_HEAD ) { + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnimator = headEnt->GetAnimator(); + syncAnim = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS ); + if ( syncAnim ) { + anim = GetAnim( channel, syncAnim->AnimFullName() ); + if ( !anim ) { + anim = GetAnim( channel, syncAnim->AnimName() ); + } + if ( anim ) { + cycle = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetCycleCount(); + starttime = headAnimator->CurrentAnim( ANIMCHANNEL_LEGS )->GetStartTime(); + animator.PlayAnim( channel, anim, gameLocal.time, blendTime ); + animator.CurrentAnim( channel )->SetCycleCount( cycle ); + animator.CurrentAnim( channel )->SetStartTime( starttime ); + } + } + } + } else { + animator.SyncAnimChannels( channel, syncToChannel, gameLocal.time, blendTime ); + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idActor::Gib +============ +*/ +void idActor::Gib( const idVec3 &dir, const char *damageDefName ) { + // for multiplayer we use client-side models + if ( gameLocal.isMultiplayer ) { + return; + } + // only gib once + if ( gibbed ) { + return; + } + idAFEntity_Gibbable::Gib( dir, damageDefName ); + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + StopSound( SND_CHANNEL_VOICE, false ); + + gameLocal.PlayEffect ( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +} + +void idActor::CheckDeathObjectives( void ) +{ + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player || !player->GetObjectiveHud() ) { + return; + } + + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_failed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_failed" ) ) ); + + player->FailObjective( spawnArgs.GetString( "objectivetitle_failed" ) ); + } + + if ( spawnArgs.GetString( "objectivetitle_completed", NULL ) ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext_completed" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle_completed" ) ) ); + + player->CompleteObjective( spawnArgs.GetString( "objectivetitle_completed" ) ); + } +} +/* +============ +idActor::Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +Bleeding wounds and surface overlays are applied in the collision code that +calls Damage() +============ +*/ +void idActor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ) * damageScale; + damage = GetDamageForLocation( damage, location ); + + // friendly fire damage + bool noDmgFeedback = false; + if ( attacker->IsType ( idActor::Type ) && static_cast(attacker)->team == team ) { + + OnFriendlyFire ( static_cast(attacker) ); + + // jshepard: + // if the player deals friendly fire damage it is reduced to 0. If the damage is splash damage, + // then the victim should use a pain anim. + if( static_cast( attacker ) == gameLocal.GetLocalPlayer() ) { + + //play pain (maybe one day a special anim?) for damages that have the cower keyword + if ( damageDef->GetBool( "cower" )) { + Pain( inflictor, attacker, damage, dir, location ); + } + + //reduce the damage + damage = 0; + noDmgFeedback = true; + } + + // reduce friendly fire damage by the teamscale + damage = floor( damage * damageDef->GetFloat ( "teamScale", "0.5" ) ); + + + } + + if ( !IsType( idPlayer::GetClassType() ) && attacker->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)attacker)->aifl.killerGuard ) { + //Hard-coded to do lots of damage + damage = 100; + } + } + + if ( !noDmgFeedback ) { + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + } + +// RAVEN BEGIN +// jnewquist: FIXME - Was this removed from Xenon intentionally? +#ifdef _XENON + // singleplayer stat reporting. + if(!gameLocal.isMultiplayer) { + int methodOfDeath = -1; + // jnewquist: Use accessor for static class type + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + // RAVEN END + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::GetClassType() ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + if( methodOfDeath != -1 && attacker && attacker->IsType( idActor::Type ) ) { +// jnewquist: Fix Xenon compile warning + statManager->WeaponHit( static_cast (attacker) , this, methodOfDeath, !!damage ); + } + } +#endif +// RAVEN END + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + if ( damage > 0 ) { + int oldHealth = health; + AdjustHealthByDamage ( damage ); + if ( health <= 0 ) { + + //allow for quick burning + if (damageDef->GetFloat( "quickburn", "0" ) && !spawnArgs.GetFloat("no_quickburn", "0")) { + fl.quickBurn = true; + } + + if ( health < -999 ) { + health = -999; + } + //annoying hack for StartRagdoll + bool saveGibbed = gibbed; + bool canDMG_Gib = (spawnArgs.GetBool( "gib" ) | spawnArgs.GetBool( "DMG_gib" )); + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + gibbed = true; + } + } + Killed( inflictor, attacker, damage, dir, location ); + gibbed = saveGibbed; + if ( health < -20 ) + { + if ( (spawnArgs.GetBool( "gib" ) && damageDef->GetBool( "gib" )) || + (canDMG_Gib && damageDef->GetBool( "DMG_gib"))) + { + Gib( dir, damageDefName ); + } + } + + if ( oldHealth > 0 && !gibbed && !fl.quickBurn) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::GetClassType() ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, damageDef, pushScale ); + } + } else { + painType = damageDef->GetString ( "pain" ); + Pain( inflictor, attacker, damage, dir, location ); + } + } else { + // don't accumulate knockback + /* + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calli/ng af.Rest() + BecomeActive( TH_PHYSICS ); + } + */ + } +} + +/* +===================== +idActor::InitDeathPush +===================== +*/ +void idActor::InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale ) { + idVec2 forceMin; + idVec2 forceMax; + + if( !af.IsActive() ) { + return; + } + + if ( deathPushTime > gameLocal.time ) { + return; + } + + if ( !damageDict->GetInt ( "deathPush", "0", deathPushTime ) || deathPushTime <= 0 ) { + return; + } + + damageDict->GetVec2( "deathPushMin", "", forceMin ); + damageDict->GetVec2( "deathPushMax", "", forceMax ); + +/* + forceMin *= (pushScale * GetPhysics()->GetMass()); + forceMax *= (pushScale * GetPhysics()->GetMass()); +*/ + forceMin *= (pushScale); + forceMax *= (pushScale); + + deathPushForce = dir; + deathPushForce.Normalize ( ); + deathPushForce = rvRandom::flrand ( forceMin.x, forceMax.x ) * deathPushForce + + -rvRandom::flrand ( forceMin.y, forceMax.y ) * GetPhysics()->GetGravityNormal ( ); + + deathPushTime += gameLocal.time; + deathPushJoint = (jointHandle_t) location; +} + +/* +===================== +idActor::DeathPush +===================== +*/ +void idActor::DeathPush ( void ) { + if ( deathPushTime <= gameLocal.time ) { + return; + } + + idVec3 center; + center = GetPhysics()->GetAbsBounds ( ).GetCenter(); + + GetPhysics()->ApplyImpulse ( 0, center, -0.5f * GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()) * GetPhysics()->GetGravity ( ) ); + + if ( deathPushJoint != INVALID_JOINT ) { + idVec3 origin; + idMat3 axis; + GetJointWorldTransform ( deathPushJoint, gameLocal.time, origin, axis ); + GetPhysics()->ApplyImpulse ( 0, origin, deathPushForce ); + } else { + GetPhysics()->ApplyImpulse ( 0, center, deathPushForce ); + } +} + +/* +===================== +idActor::SkipImpulse +===================== +*/ +bool idActor::SkipImpulse( idEntity* ent, int id ) { + return idAFEntity_Gibbable::SkipImpulse( ent, id ) || health <= 0 || gibbed || ent->IsType( idActor::GetClassType() ) || ent->IsType( idProjectile::GetClassType() ); +} + +/* +===================== +idActor::AddDamageEffect +===================== +*/ +void idActor::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !gameLocal.isMultiplayer && inflictor && inflictor->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast(inflictor)->team == team ) { + return; + } + } + idAFEntity_Gibbable::AddDamageEffect( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +idActor::ClearPain +===================== +*/ +void idActor::ClearPain( void ) { + pain_debounce_time = 0; +} + +/* +===================== +idActor::Pain +===================== +*/ +bool idActor::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + if ( gameLocal.time < pain_debounce_time ) { + return false; + } + + // No pain if being hit by a friendly target + // jshepard: friendly targets can now cause pain +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return false; + } + } +*/ + + // don't play pain sounds more than necessary + pain_debounce_time = gameLocal.time + pain_delay; + + float f; +// RAVEN BEGIN +// mekberg: fixed divide by zero + float spawnHealth = spawnArgs.GetFloat ( "health", "1" ); + if (spawnHealth<1.0f) { + spawnHealth = 1.0f; // more devide by zero nonsense + } + f = (float)damage / spawnHealth; +// RAVEN END + if( gameLocal.isMultiplayer && IsType( idPlayer::GetClassType() ) && (health < 0.25f * ((idPlayer*)this)->inventory.maxHealth) ) { + StartSound( "snd_pain_low_health", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + if ( f > 0.75f ) { + StartSound( "snd_pain_huge", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.5f ) { + StartSound( "snd_pain_large", SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( f > 0.25f ) { + StartSound( "snd_pain_medium", SND_CHANNEL_VOICE, 0, false, NULL ); + } else { + StartSound( "snd_pain_small", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( disablePain || ( gameLocal.time < painTime ) ) { + // don't play a pain anim + return false; + } + + // set the pain anim + idStr damageGroup = GetDamageGroup( location ); + + painAnim.Clear ( ); + + // If we have both a damage group and a pain type then check that combination first + if ( damageGroup.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s_%s", painType.c_str(), damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the pain type? + if ( !painAnim.Length ( ) && painType.Length ( ) ) { + painAnim = va ( "pain_%s", painType.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + // Do we have a pain anim for just the damage group? + if ( !painAnim.Length ( ) && damageGroup.Length ( ) ) { + painAnim = va ( "pain_%s", damageGroup.c_str() ); + if ( !animator.HasAnim ( painAnim ) ) { + painAnim.Clear ( ); + } + } + + if ( !painAnim.Length() ) { + painAnim = "pain"; + } + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s', anim '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + damageGroup.c_str(), painAnim.c_str() ); + } + + return true; +} + +/* +===================== +idActor::SpawnGibs +===================== +*/ +void idActor::SpawnGibs( const idVec3 &dir, const char *damageDefName ) { + idAFEntity_Gibbable::SpawnGibs( dir, damageDefName ); + RemoveAttachments(); +} + +/* +===================== +idActor::SetupDamageGroups + +FIXME: only store group names once and store an index for each joint +===================== +*/ +void idActor::SetupDamageGroups( void ) { + int i; + const idKeyValue *arg; + idStr groupname; + idList jointList; + int jointnum; + float scale; + + // create damage zones + damageGroups.SetNum( animator.NumJoints() ); + arg = spawnArgs.MatchPrefix( "damage_zone ", NULL ); + while ( arg ) { + groupname = arg->GetKey(); + groupname.Strip( "damage_zone " ); + animator.GetJointList( arg->GetValue(), jointList ); + for( i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + damageGroups[ jointnum ] = groupname; + } + jointList.Clear(); + arg = spawnArgs.MatchPrefix( "damage_zone ", arg ); + } + + // initilize the damage zones to normal damage + damageScale.SetNum( animator.NumJoints() ); + for( i = 0; i < damageScale.Num(); i++ ) { + damageScale[ i ] = 1.0f; + } + + // set the percentage on damage zones + arg = spawnArgs.MatchPrefix( "damage_scale ", NULL ); + while ( arg ) { + scale = atof( arg->GetValue() ); + groupname = arg->GetKey(); + groupname.Strip( "damage_scale " ); + for( i = 0; i < damageScale.Num(); i++ ) { + if ( damageGroups[ i ] == groupname ) { + damageScale[ i ] = scale; + } + } + arg = spawnArgs.MatchPrefix( "damage_scale ", arg ); + } +} + +/* +===================== +idActor::GetDamageForLocation +===================== +*/ +int idActor::GetDamageForLocation( int damage, int location ) { + if ( ( location < 0 ) || ( location >= damageScale.Num() ) ) { + return damage; + } + + return (int)ceil( damage * damageScale[ location ] ); +} + +/* +===================== +idActor::GetDamageGroup +===================== +*/ +const char *idActor::GetDamageGroup( int location ) { + if ( ( location < 0 ) || ( location >= damageGroups.Num() ) ) { + return ""; + } + + return damageGroups[ location ]; +} + + +// RAVEN BEGIN +// bdube: added for vehicle +/* +============== +idActor::ExitVehicle +============== +*/ +bool idActor::ExitVehicle ( bool force ) { + idMat3 axis; + idVec3 origin; + + if ( !IsInVehicle ( ) ) { + return false; + } + + if ( vehicleController.GetVehicle()->IsLocked() ) { + if ( force ) { + vehicleController.GetVehicle()->Unlock(); + } else { + return false; + } + } + + if( !vehicleController.FindClearExitPoint(origin, axis) ) { + if ( force ) { + origin = GetPhysics()->GetOrigin() + idVec3( spawnArgs.GetVector( "forced_exit_offset", "-100 0 0" ) ); + axis = GetPhysics()->GetAxis(); + } else { + return false; + } + } + + vehicleController.Eject ( force ); + + GetPhysics()->SetOrigin( origin ); + viewAxis = axis[0].ToMat3(); + GetPhysics()->SetAxis( mat3_identity ); + GetPhysics()->SetLinearVelocity( vec3_origin ); + + return true; +} + +/* +===================== +idActor::EnterVehicle +===================== +*/ +bool idActor::EnterVehicle ( idEntity* ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsInVehicle ( ) || !ent->IsType ( rvVehicle::GetClassType() ) ) { +// RAVEN END + return false ; + } + + // Get in the vehicle + if ( !vehicleController.Drive ( static_cast(ent), this ) ) { + return false; + } + + return true; +} + +// RAVEN END + + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +===================== +idActor::FootStep +===================== +*/ +void idActor::FootStep( void ) { + const char* sound; + const rvDeclMatType* materialType; + + if ( !GetPhysics()->HasGroundContacts() ) { + return; + } + + // start footstep sound based on material type + materialType = GetPhysics()->GetContact( 0 ).materialType; + sound = NULL; + + // Sound based on material type? + if ( materialType ) { + sound = spawnArgs.GetString( va( "snd_footstep_%s", materialType->GetName() ) ); + } + if ( !sound || !*sound ) { + sound = spawnArgs.GetString( "snd_footstep" ); + } + + // If we have a sound then play it + if ( sound && *sound ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } +} + +/* +===================== +idActor::Event_EnableEyeFocus +===================== +*/ +void idActor::Event_EnableEyeFocus( void ) { + allowEyeFocus = true; + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableEyeFocus +===================== +*/ +void idActor::Event_DisableEyeFocus( void ) { + allowEyeFocus = false; + + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetAnimator()->Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } else { + animator.Clear( ANIMCHANNEL_EYELIDS, gameLocal.time, FRAME2MS( 2 ) ); + } +} + +/* +===================== +idActor::Event_EnableBlink +===================== +*/ +void idActor::Event_EnableBlink( void ) { + blink_time = gameLocal.time + blink_min + gameLocal.random.RandomFloat() * ( blink_max - blink_min ); +} + +/* +===================== +idActor::Event_DisableBlink +===================== +*/ +void idActor::Event_DisableBlink( void ) { + blink_time = 0x7FFFFFFF; +} + +/* +=============== +idActor::Event_Footstep +=============== +*/ +void idActor::Event_Footstep( void ) { + FootStep ( ); +} + +/* +===================== +idActor::Event_EnableWalkIK +===================== +*/ +void idActor::Event_EnableWalkIK( void ) { + walkIK.EnableAll(); +} + +/* +===================== +idActor::Event_DisableWalkIK +===================== +*/ +void idActor::Event_DisableWalkIK( void ) { + walkIK.DisableAll(); +} + +/* +===================== +idActor::Event_EnableLegIK +===================== +*/ +void idActor::Event_EnableLegIK( int num ) { + walkIK.EnableLeg( num ); +} + +/* +===================== +idActor::Event_DisableLegIK +===================== +*/ +void idActor::Event_DisableLegIK( int num ) { + walkIK.DisableLeg( num ); +} + +/* +===================== +idActor::Event_PreventPain +===================== +*/ +void idActor::Event_PreventPain( float duration ) { + painTime = gameLocal.time + SEC2MS( duration ); +} + +/* +=============== +idActor::Event_DisablePain +=============== +*/ +void idActor::Event_DisablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = true; +// RAVEN END +} + +/* +=============== +idActor::Event_EnablePain +=============== +*/ +void idActor::Event_EnablePain( void ) { +// RAVEN BEGIN +// bdube: reversed var + disablePain = false; +// RAVEN END +} + +/* +===================== +idActor::Event_SetAnimPrefix +===================== +*/ +void idActor::Event_SetAnimPrefix( const char *prefix ) { + animPrefix = prefix; +} + +/* +=============== +idActor::Event_StopAnim +=============== +*/ +void idActor::Event_StopAnim( int channel, int frames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.StopAnim( frames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.StopAnim( frames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_PlayAnim +=============== +*/ +void idActor::Event_PlayAnim( int channel, const char *animname ) { + idThread::ReturnFloat( MS2SEC(PlayAnim(channel, animname, -1)) ); +} + +/* +=============== +idActor::Event_PlayCycle +=============== +*/ +void idActor::Event_PlayCycle( int channel, const char *animname ) { + PlayCycle ( channel, animname, -1 ); + idThread::ReturnInt( true ); +} + +/* +===================== +idAI::DebugFilter +===================== +*/ +bool idActor::DebugFilter ( const idCVar& test ) const { + return ( health>0 && (test.GetBool() || test.GetInteger()>0) && (!ai_debugFilterString.GetString()[0] || !stricmp( name.c_str(), ai_debugFilterString.GetString() ))); +} + +/* +=============== +idActor::Event_IdleAnim +=============== +*/ +void idActor::Event_IdleAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( false ); + return; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso body if it doesn't override idle anims + headAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + // everything is idle, so play the anim on the head and copy it to the torso and legs + headAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } else if ( torsoAnim.IsIdle() ) { + // sync the head and torso to the legs + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, headAnim.animBlendFrames ); + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } else { + // sync the head to the torso + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, headAnim.animBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.BecomeIdle(); + if ( legsAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to legs if legs anim doesn't override idle anims + torsoAnim.CycleAnim( anim ); + } else if ( legsAnim.IsIdle() ) { + // play the anim in both legs and torso + torsoAnim.CycleAnim( anim ); + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + // sync the anim to the legs + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, torsoAnim.animBlendFrames ); + } + + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.BecomeIdle(); + if ( torsoAnim.GetAnimFlags().prevent_idle_override ) { + // don't sync to torso if torso anim doesn't override idle anims + legsAnim.CycleAnim( anim ); + } else if ( torsoAnim.IsIdle() ) { + // play the anim in both legs and torso + legsAnim.CycleAnim( anim ); + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } else { + // sync the anim to the torso + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, legsAnim.animBlendFrames ); + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + idThread::ReturnInt( true ); +} + +/* +================ +idActor::Event_SetSyncedAnimWeight +================ +*/ +void idActor::Event_SetSyncedAnimWeight( int channel, int anim, float weight ) { + idEntity *headEnt; + + headEnt = head.GetEntity(); + switch( channel ) { + case ANIMCHANNEL_HEAD : + if ( headEnt ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } else { + animator.CurrentAnim( ANIMCHANNEL_HEAD )->SetSyncedAnimWeight( anim, weight ); + } + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + case ANIMCHANNEL_TORSO : + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( legsAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + break; + + case ANIMCHANNEL_LEGS : + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + if ( torsoAnim.IsIdle() ) { + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( anim, weight ); + if ( headEnt && headAnim.IsIdle() ) { + headEnt->GetAnimator()->CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( anim, weight ); + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } +} + +/* +=============== +idActor::Event_OverrideAnim +=============== +*/ +void idActor::Event_OverrideAnim( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Disable(); + if ( !torsoAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } else { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Disable(); + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_EnableAnim +=============== +*/ +void idActor::Event_EnableAnim( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.Enable( blendFrames ); + break; + + case ANIMCHANNEL_LEGS : + legsAnim.Enable( blendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_SetBlendFrames +=============== +*/ +void idActor::Event_SetBlendFrames( int channel, int blendFrames ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.animBlendFrames = blendFrames; + headAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.animBlendFrames = blendFrames; + torsoAnim.lastAnimBlendFrames = blendFrames; + break; + + case ANIMCHANNEL_LEGS : + legsAnim.animBlendFrames = blendFrames; + legsAnim.lastAnimBlendFrames = blendFrames; + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +=============== +idActor::Event_GetBlendFrames +=============== +*/ +void idActor::Event_GetBlendFrames( int channel ) { + switch( channel ) { + case ANIMCHANNEL_HEAD : + idThread::ReturnInt( headAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_TORSO : + idThread::ReturnInt( torsoAnim.animBlendFrames ); + break; + + case ANIMCHANNEL_LEGS : + idThread::ReturnInt( legsAnim.animBlendFrames ); + break; + + default: + gameLocal.Error( "Unknown anim group" ); + break; + } +} + +/* +================ +idActor::Event_HasEnemies +================ +*/ +void idActor::Event_HasEnemies( void ) { + bool hasEnemy; + + hasEnemy = HasEnemies(); + idThread::ReturnInt( hasEnemy ); +} + +/* +================ +idActor::Event_NextEnemy +================ +*/ +void idActor::Event_NextEnemy( idEntity *ent ) { + idActor *actor; + + if ( !ent || ( ent == this ) ) { + actor = enemyList.Next(); + } else { + if ( !ent->IsType( idActor::Type ) ) { + gameLocal.Error( "'%s' cannot be an enemy", ent->name.c_str() ); + } + + actor = static_cast( ent ); + if ( actor->enemyNode.ListHead() != &enemyList ) { + gameLocal.Error( "'%s' is not in '%s' enemy list", actor->name.c_str(), name.c_str() ); + } + } + + for( ; actor != NULL; actor = actor->enemyNode.Next() ) { + if ( !actor->fl.hidden ) { + idThread::ReturnEntity( actor ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +================ +idActor::Event_ClosestEnemyToPoint +================ +*/ +void idActor::Event_ClosestEnemyToPoint( const idVec3 &pos ) { + idActor *bestEnt = ClosestEnemyToPoint( pos ); + idThread::ReturnEntity( bestEnt ); +} + +/* +================ +idActor::Event_StopSound +================ +*/ +void idActor::Event_StopSound( int channel, int netSync ) { + if ( channel == SND_CHANNEL_VOICE ) { + idEntity *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->StopSound( channel, ( netSync != 0 ) ); + } + } + StopSound( channel, ( netSync != 0 ) ); +} + +/* +===================== +idActor::Event_GetHead +===================== +*/ +void idActor::Event_GetHead( void ) { + idThread::ReturnEntity( head.GetEntity() ); +} + +// RAVEN BEGIN +// jshepard: added +/* +===================== +idActor::Event_SetAnimRate +===================== +*/ +void idActor::Event_SetAnimRate( float multiplier ) { + animator.SetPlaybackRate(multiplier); +} + + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idActor::State_Wait_Frame + +Stop a state thread for a single frame +================ +*/ +stateResult_t idActor::State_Wait_Frame ( const stateParms_t& parms ) { + return SRESULT_DONE_WAIT; +} + +/* +================ +idActor::State_Wait_LegsAnim + +Stop a state thread until the animation running on the legs channel is finished +================ +*/ +stateResult_t idActor::State_Wait_LegsAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::State_Wait_TorsoAnim + +Stop a state thread until the animation running on the torso channel is finished +================ +*/ +stateResult_t idActor::State_Wait_TorsoAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idActor::PlayAnim +================ +*/ +int idActor::PlayAnim ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + idEntity *headEnt; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + + if( ai_animShow.GetBool() ){ + gameLocal.DPrintf( "Playing animation '%s' on '%s' (%s)\n", animname, name.c_str(), spawnArgs.GetString( "head", "" ) ); + } + + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return 0; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headEnt = head.GetEntity(); + if ( headEnt ) { + headAnim.idleAnim = false; + headAnim.PlayAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.PlayAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.PlayAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default : + gameLocal.Error( "Unknown anim group" ); + break; + } + + return animator.CurrentAnim( channel )->Length(); +} + +/* +================ +idActor::PlayCycle +================ +*/ +bool idActor::PlayCycle ( int channel, const char *animname, int blendFrames ) { + animFlags_t flags; + int anim; + + if ( blendFrames != -1 ) { + Event_SetBlendFrames ( channel, blendFrames ); + } + + anim = GetAnim( channel, animname ); + if ( !anim ) { + if ( ( channel == ANIMCHANNEL_HEAD ) && head.GetEntity() ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), spawnArgs.GetString( "def_head", "" ) ); + } else { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + } + return false; + } + + switch( channel ) { + case ANIMCHANNEL_HEAD : + headAnim.idleAnim = false; + headAnim.CycleAnim( anim ); + flags = headAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() && legsAnim.IsIdle() ) { + torsoAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + legsAnim.animBlendFrames = headAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_HEAD, headAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_TORSO : + torsoAnim.idleAnim = false; + torsoAnim.CycleAnim( anim ); + flags = torsoAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + if ( legsAnim.IsIdle() ) { + legsAnim.animBlendFrames = torsoAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_LEGS, ANIMCHANNEL_TORSO, torsoAnim.lastAnimBlendFrames ); + } + } + break; + + case ANIMCHANNEL_LEGS : + legsAnim.idleAnim = false; + legsAnim.CycleAnim( anim ); + flags = legsAnim.GetAnimFlags(); + if ( !flags.prevent_idle_override ) { + if ( torsoAnim.IsIdle() ) { + torsoAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_TORSO, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + if ( headAnim.IsIdle() ) { + headAnim.animBlendFrames = legsAnim.lastAnimBlendFrames; + SyncAnimChannels( ANIMCHANNEL_HEAD, ANIMCHANNEL_LEGS, legsAnim.lastAnimBlendFrames ); + } + } + } + break; + + default: + gameLocal.Error( "Unknown anim group" ); + } + + return true; +} + +void idActor::IdleAnim ( int channel, const char *name, int blendFrames ) { + Event_SetBlendFrames ( channel, blendFrames ); + Event_IdleAnim ( channel, name ); +} + +void idActor::OverrideAnim ( int channel ) { + Event_OverrideAnim ( channel ); +} + +idAnimState& idActor::GetAnimState ( int channel ) { + switch ( channel ) { + case ANIMCHANNEL_LEGS: return legsAnim; + case ANIMCHANNEL_TORSO: return torsoAnim; + case ANIMCHANNEL_HEAD: return headAnim; + default: + gameLocal.Error( "idActor::GetAnimState: Unknown anim channel" ); + return torsoAnim; + } +} + +void idActor::DisableAnimState ( int channel ) { + Event_OverrideAnim ( channel ); +// GetAnimState ( channel ).Disable ( ); +} + +void idActor::EnableAnimState ( int channel ) { + GetAnimState ( channel ).Enable ( 4 ); +} + +bool idActor::HasAnim ( int channel, const char* animname, bool forcePrefix ) { + return GetAnim( channel, animname, forcePrefix ) != NULL; +} + +bool idActor::AnimDone ( int channel, int blendFrames ) { + return GetAnimState( channel ).AnimDone ( blendFrames ); +} + +/* +===================== +idActor::GetDebugInfo +===================== +*/ +void idActor::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAFEntity_Gibbable::GetDebugInfo ( proc, userData ); + + proc ( "idActor", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); + + proc ( "idActor", "legs_state", legsAnim.GetStateThread().GetState()?legsAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "legs_disable", legsAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "legs_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) : "", userData ); + + proc ( "idActor", "torso_state", torsoAnim.GetStateThread().GetState()?torsoAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "torso_disabled", torsoAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "torso_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) : "", userData ); + + proc ( "idActor", "head_state", headAnim.GetStateThread().GetState()?headAnim.GetStateThread().GetState()->state->name:"", userData ); + proc ( "idActor", "head_disabled", headAnim.Disabled()?"true":"false", userData ); + proc ( "idActor", "head_anim", GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD ) ? GetAnimator()->CurrentAnim ( ANIMCHANNEL_HEAD )->AnimName ( ) : "", userData ); + + proc ( "idActor", "painAnim", painAnim.c_str(), userData ); + proc ( "idActor", "animPrefix", animPrefix.c_str(), userData ); +} + +//MCG: damage over time +void idActor::Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + inDamageEvent = true; + Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + inDamageEvent = false; + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + } +} + +void idActor::Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef* damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), this, interval ); + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +// MCG: script-callable joint crawl effect +void idActor::Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ) { + if ( effectKeyName ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( spawnArgs, effectKeyName ), this, 100 ); + effect->Play ( gameLocal.GetTime(), false ); + crawlSecs -= 0.1f; + if ( crawlSecs >= 0.1f ) { + PostEventMS( &EV_JointCrawlEffect, 100, effectKeyName, crawlSecs ); + } + } +} + +idEntity* idActor::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + + if ( testElevator && groundEnt != testElevator ) { + return groundEnt; + } + + idEntity* traceEnt; + idVec3 testPoint = GetPhysics()->GetOrigin(); + idVec3 testBottom; + testPoint.z += 1.0f; + + for ( int x = 0; x < 2; x++ ) { + testPoint.x = GetPhysics()->GetAbsBounds()[x].x; + for ( int y = 0; y < 2; y++ ) { + testPoint.y = GetPhysics()->GetAbsBounds()[y].y; + testBottom = testPoint; + testBottom.z -= 65.0f; + + trace_t tr; + gameLocal.TracePoint( this, tr, testPoint, testBottom, GetPhysics()->GetClipMask(), this ); + traceEnt = gameLocal.FindEntity( tr.c.entityNum ); + if ( !traceEnt ) { + return NULL; + } + while ( traceEnt->GetBindMaster() ) { + traceEnt = traceEnt->GetBindMaster(); + } + if ( traceEnt != groundEnt ) { + return traceEnt; + } + if ( testElevator && traceEnt != testElevator ) { + return traceEnt; + } + } + } + + return groundEnt; +} + +void idActor::GuidedProjectileIncoming( idGuidedProjectile *projectile ) +{ + if ( IsInVehicle() ) + { + if ( vehicleController.GetVehicle() ) + { + vehicleController.GetVehicle()->GuidedProjectileIncoming( projectile ); + } + } +} +// RAVEN END diff --git a/src/mpgame/Actor.h b/src/mpgame/Actor.h new file mode 100644 index 0000000..8ed03dd --- /dev/null +++ b/src/mpgame/Actor.h @@ -0,0 +1,450 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ACTOR_H__ +#define __GAME_ACTOR_H__ + +/* +=============================================================================== + + idActor + +=============================================================================== +*/ + +extern const idEventDef AI_EnableEyeFocus; +extern const idEventDef AI_DisableEyeFocus; +extern const idEventDef EV_Footstep; +extern const idEventDef EV_FootstepLeft; +extern const idEventDef EV_FootstepRight; +extern const idEventDef EV_EnableWalkIK; +extern const idEventDef EV_DisableWalkIK; +extern const idEventDef EV_EnableLegIK; +extern const idEventDef EV_DisableLegIK; +extern const idEventDef AI_SetAnimPrefix; +extern const idEventDef AI_PlayAnim; +extern const idEventDef AI_PlayCycle; +extern const idEventDef AI_AnimDone; +extern const idEventDef AI_SetBlendFrames; +extern const idEventDef AI_GetBlendFrames; +extern const idEventDef AI_ScriptedMove; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; + +// RAVEN BEGIN +// bdube: added flashlight +extern const idEventDef AI_Flashlight; +extern const idEventDef AI_EnterVehicle; +extern const idEventDef AI_ExitVehicle; +// nmckenzie: +extern const idEventDef AI_OverrideAnim; +extern const idEventDef AI_IdleAnim; +extern const idEventDef AI_SetState; +// jshepard: adjust animation speed +extern const idEventDef AI_SetAnimRate; +//MCG: damage over time +extern const idEventDef EV_DamageOverTime; +extern const idEventDef EV_DamageOverTimeEffect; +//MCG: script-callable joint crawl effect +extern const idEventDef EV_JointCrawlEffect; + +// abahr: +extern const idEventDef AI_LookAt; +extern const idEventDef AI_FaceEnemy; +extern const idEventDef AI_FaceEntity; +extern const idEventDef AI_JumpDown; +extern const idEventDef AI_SetLeader; +// RAVEN END + +class idAnimState { +public: + + bool idleAnim; + int animBlendFrames; + int lastAnimBlendFrames; // allows override anims to blend based on the last transition time + +public: + idAnimState(); + ~idAnimState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Init( idEntity *owner, idAnimator *_animator, int animchannel ); + void Shutdown( void ); + void SetState ( const char *name, int blendFrames, int flags = 0 ); + void PostState ( const char* name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnim( int frames ); + void PlayAnim( int anim ); + void CycleAnim( int anim ); + void BecomeIdle( void ); + bool UpdateState( void ); + bool Disabled( void ) const; + void Enable( int blendFrames ); + void Disable( void ); + bool AnimDone( int blendFrames ) const; + bool IsIdle( void ) const; + animFlags_t GetAnimFlags( void ) const; + + rvStateThread& GetStateThread ( void ); + + idAnimator * GetAnimator( void ) const {return animator;}; +private: +// RAVEN BEGIN +// bdube: converted self to entity ptr so any entity can use it + idEntity * self; +// RAVEN END + idAnimator * animator; + int channel; + bool disabled; + + rvStateThread stateThread; +}; + +ID_INLINE rvStateThread& idAnimState::GetStateThread ( void ) { + return stateThread; +} + +class idAttachInfo { +public: + idEntityPtr ent; + int channel; +}; + +class idActor : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idActor ); + + int team; + idLinkList teamNode; + int rank; // monsters don't fight back if the attacker's rank is higher + idMat3 viewAxis; // view axis of the actor + + idLinkList enemyNode; // node linked into an entity's enemy list for quick lookups of who is attacking him + idLinkList enemyList; // list of characters that have targeted the player as their enemy + +public: + idActor( void ); + virtual ~idActor( void ); + + void Spawn( void ); + virtual void Restart( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Hide( void ); + virtual void Show( void ); + virtual int GetDefaultSurfaceType( void ) const; + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + virtual bool LoadAF( const char* keyname = NULL, bool purgeAF = false ); + void SetupBody( void ); + + virtual void CheckBlink( void ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + // script state management + void ShutdownThreads ( void ); + void UpdateState ( void ); + + virtual void OnStateThreadClear ( const char *statename, int flags = 0 ); + void SetState ( const char *statename, int flags = 0 ); + void PostState ( const char* statename, int delay = 0, int flags = 0 ); + void InterruptState ( const char* statename, int delay = 0, int flags = 0 ); + + // vision testing + void SetEyeHeight( float height ); + void SetChestHeight ( float height ); + float EyeHeight( void ) const; + + virtual idVec3 GetEyePosition( void ) const; + virtual idVec3 GetChestPosition ( void ) const; + idEntity* GetGroundEntity ( void ) const; + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + void Present( void ); + + virtual void GetViewPos ( idVec3 &origin, idMat3 &axis ) const; + void SetFOV ( float fov, float fovClose ); + bool CheckFOV ( const idVec3 &pos, float ang = -1.0f ) const; + virtual bool HasFOV ( idEntity *ent ); + virtual bool CanSee ( const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idEntity *ent, bool useFOV ) const; + virtual bool CanSeeFrom ( const idVec3& from, const idVec3& toPos, bool useFOV ) const; + + // damage + void SetupDamageGroups( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); +// RAVEN BEGIN +// nmckenzie: a final hook in the middle of the damage function + virtual void AdjustHealthByDamage ( int inDamage ){health -= inDamage;} +// RAVEN END + + virtual int GetDamageForLocation( int damage, int location ); + const char * GetDamageGroup( int location ); + void ClearPain( void ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + // model/combat model/ragdoll + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + bool StartRagdoll( void ); + void StopRagdoll( void ); + virtual bool UpdateAnimationControllers( void ); + + // delta view angles to allow movers to rotate the view of the actor + const idAngles & GetDeltaViewAngles( void ) const; + void SetDeltaViewAngles( const idAngles &delta ); + + bool HasEnemies( void ) const; + idActor * ClosestEnemyToPoint( const idVec3 &pos, float maxRange=0.0f, bool returnFirst=false, bool checkPVS=false ); + idActor * EnemyWithMostHealth(); + + virtual bool OnLadder ( void ) const; + virtual void OnStateChange ( int channel ); + virtual void OnFriendlyFire ( idActor* attacker ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + + void Attach( idEntity *ent ); + idEntity* FindAttachment( const char* attachmentName ); + void HideAttachment( const char* attachmentName ); + void ShowAttachment( const char* attachmentName ); + idEntity* GetHead() { return head; } + + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual renderView_t * GetRenderView(); + + // Animation + int PlayAnim ( int channel, const char *name, int blendFrames ); + bool PlayCycle ( int channel, const char *name, int blendFrames ); + void IdleAnim ( int channel, const char *name, int blendFrames ); + void OverrideAnim ( int channel ); + bool HasAnim ( int channel, const char *name, bool forcePrefix = false ); + int GetAnim ( int channel, const char *name, bool forcePrefix = false ); + bool AnimDone ( int channel, int blendFrames ); + + // animation state control + void UpdateAnimState ( void ); + void SetAnimState ( int channel, const char *name, int blendFrames = 0, int flags = 0 ); + void PostAnimState ( int channel, const char *name, int blendFrames = 0, int delay = 0, int flags = 0 ); + void StopAnimState ( int channel ); + bool InAnimState ( int channel, const char *name ); + + virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); + +// RAVEN BEGIN +// bdube: added for vehicle + bool IsInVehicle ( void ) const; + rvVehicleController& GetVehicleController ( void ); + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); + + bool DebugFilter (const idCVar& test) const; +// RAVEN END + virtual bool IsCrouching ( void ) const {return false;}; + + virtual bool SkipImpulse( idEntity* ent, int id ); + + int lightningNextTime; + int lightningEffects; + +protected: + friend class idAnimState; + + float fovDot; // cos( fovDegrees ) + float fovCloseDot; // cos( fovDegreesClose ) + float fovCloseRange; // range within to use fovCloseDot + idVec3 eyeOffset; // offset of eye relative to physics origin + idVec3 chestOffset; // offset of chest relative to physics origin + idVec3 modelOffset; // offset of visual model relative to the physics origin + + idAngles deltaViewAngles; // delta angles relative to view input angles + + int pain_debounce_time; // next time the actor can show pain + int pain_delay; // time between playing pain sound + + idStrList damageGroups; // body damage groups + idList damageScale; // damage scale per damage gruop + bool inDamageEvent; // hacky-ass bool to prevent us from starting a new EV_DamageOverTime in our ::Damage + + bool use_combat_bbox; // whether to use the bounding box for combat collision + + // joint handles + jointHandle_t leftEyeJoint; + jointHandle_t rightEyeJoint; + jointHandle_t soundJoint; + jointHandle_t eyeOffsetJoint; + jointHandle_t chestOffsetJoint; + jointHandle_t neckJoint; + jointHandle_t headJoint; + + idIK_Walk walkIK; + + idStr animPrefix; + idStr painType; + idStr painAnim; + + // blinking + int blink_anim; + int blink_time; + int blink_min; + int blink_max; + + idAnimState headAnim; + idAnimState torsoAnim; + idAnimState legsAnim; + + rvStateThread stateThread; + + idEntityPtr head; // safe pointer to attached head + + bool disablePain; + bool allowEyeFocus; + bool finalBoss; + + int painTime; + + idList attachments; + + virtual void Gib( const idVec3 &dir, const char *damageDefName ); + void CheckDeathObjectives( void ); + +// RAVEN BEGIN +// bdube: vehicles + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); +// RAVEN END + + // removes attachments with "remove" set for when character dies + void RemoveAttachments( void ); + +// RAVEN BEGIN +// bdube: vehicles + rvVehicleController vehicleController; +// bdube: flashlights + renderLight_t flashlight; + int flashlightHandle; + jointHandle_t flashlightJoint; + idVec3 flashlightOffset; + +// bdube: death force + int deathPushTime; + idVec3 deathPushForce; + jointHandle_t deathPushJoint; + + void FlashlightUpdate ( bool forceOn = false ); + void InitDeathPush ( const idVec3& dir, int location, const idDict* damageDict, float pushScale = 1.0f ); + void DeathPush ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +protected: + + virtual void FootStep ( void ); + virtual void SetupHead( const char* headDefName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + void SyncAnimChannels( int channel, int syncToChannel, int blendFrames ); + void FinishSetup( void ); + + void Event_EnableEyeFocus( void ); + void Event_DisableEyeFocus( void ); + void Event_EnableBlink( void ); + void Event_DisableBlink( void ); + void Event_Footstep( void ); + void Event_EnableWalkIK( void ); + void Event_DisableWalkIK( void ); + void Event_EnableLegIK( int num ); + void Event_DisableLegIK( int num ); + void Event_SetAnimPrefix( const char *name ); + void Event_LookAtEntity( idEntity *ent, float duration ); + void Event_PreventPain( float duration ); + void Event_DisablePain( void ); + void Event_EnablePain( void ); + void Event_StopAnim( int channel, int frames ); + void Event_PlayAnim( int channel, const char *name ); + void Event_PlayCycle( int channel, const char *name ); + void Event_IdleAnim( int channel, const char *name ); + void Event_SetSyncedAnimWeight( int channel, int anim, float weight ); + void Event_OverrideAnim( int channel ); + void Event_EnableAnim( int channel, int blendFrames ); + void Event_SetBlendFrames( int channel, int blendFrames ); + void Event_GetBlendFrames( int channel ); + void Event_HasEnemies( void ); + void Event_NextEnemy( idEntity *ent ); + void Event_ClosestEnemyToPoint( const idVec3 &pos ); + void Event_StopSound( int channel, int netsync ); + void Event_GetHead( void ); + + void Event_Teleport ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Flashlight ( bool enable ); + void Event_EnterVehicle ( idEntity* vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_PreExitVehicle( bool force ); + + void Event_SetAnimRate ( float multiplier ); + void Event_DamageOverTime ( int endTime, int interval, idEntity *inflictor, idEntity *attacker, idVec3 &dir, const char *damageDefName, const float damageScale, int location ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + void Event_JointCrawlEffect ( const char *effectKeyName, float crawlSecs ); + + CLASS_STATES_PROTOTYPE ( idActor ); + +protected: + + // Wait states + stateResult_t State_Wait_LegsAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_TorsoAnim ( const stateParms_t& parms ); + stateResult_t State_Wait_Frame ( const stateParms_t& parms ); + + void DisableAnimState ( int channel ); + void EnableAnimState ( int channel ); + idAnimState& GetAnimState ( int channel ); +}; + +ID_INLINE bool idActor::IsInVehicle( void ) const { + return vehicleController.IsDriving(); +} + +ID_INLINE rvVehicleController& idActor::GetVehicleController( void ) { + return vehicleController; +} + +#endif /* !__GAME_ACTOR_H__ */ + +// RAVEN END diff --git a/src/mpgame/BrittleFracture.cpp b/src/mpgame/BrittleFracture.cpp new file mode 100644 index 0000000..ae45d6c --- /dev/null +++ b/src/mpgame/BrittleFracture.cpp @@ -0,0 +1,1378 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes +#ifdef _MD5R_SUPPORT +#include "../renderer/tr_local.h" +#include "../renderer/Model_local.h" +#endif +// RAVEN END + +CLASS_DECLARATION( idEntity, idBrittleFracture ) + EVENT( EV_Activate, idBrittleFracture::Event_Activate ) + EVENT( EV_Touch, idBrittleFracture::Event_Touch ) +END_CLASS + +const int SHARD_ALIVE_TIME = 5000; +const int SHARD_FADE_START = 2000; + +static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_"; + +/* +================ +idBrittleFracture::idBrittleFracture +================ +*/ +idBrittleFracture::idBrittleFracture( void ) { + material = NULL; + decalMaterial = NULL; + decalSize = 0.0f; + maxShardArea = 0.0f; + maxShatterRadius = 0.0f; + minShatterRadius = 0.0f; + linearVelocityScale = 0.0f; + angularVelocityScale = 0.0f; + shardMass = 0.0f; + density = 0.0f; + friction = 0.0f; + bouncyness = 0.0f; + fxFracture.Clear(); + + bounds.Clear(); + disableFracture = false; + + lastRenderEntityUpdate = -1; + changed = false; + + fl.networkSync = true; +} + +/* +================ +idBrittleFracture::~idBrittleFracture +================ +*/ +idBrittleFracture::~idBrittleFracture( void ) { + int i; + + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->decals.DeleteContents( true ); + delete shards[i]; + } + + // make sure the render entity is freed before the model is freed + FreeModelDef(); + renderModelManager->FreeModel( renderEntity.hModel ); +} + +/* +================ +idBrittleFracture::Save +================ +*/ +void idBrittleFracture::Save( idSaveGame *savefile ) const { + int i, j; + + savefile->WriteInt( health ); + savefile->Write( &fl, sizeof( fl ) ); + + // setttings + savefile->WriteMaterial( material ); + savefile->WriteMaterial( decalMaterial ); + savefile->WriteFloat( decalSize ); + savefile->WriteFloat( maxShardArea ); + savefile->WriteFloat( maxShatterRadius ); + savefile->WriteFloat( minShatterRadius ); + savefile->WriteFloat( linearVelocityScale ); + savefile->WriteFloat( angularVelocityScale ); + savefile->WriteFloat( shardMass ); + savefile->WriteFloat( density ); + savefile->WriteFloat( friction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteString( fxFracture ); + + // state + savefile->WriteBounds( bounds ); + savefile->WriteBool( disableFracture ); + + savefile->WriteInt( lastRenderEntityUpdate ); + savefile->WriteBool( changed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( shards.Num() ); + for ( i = 0; i < shards.Num(); i++ ) { + savefile->WriteWinding( shards[i]->winding ); + + savefile->WriteInt( shards[i]->decals.Num() ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { + savefile->WriteWinding( *shards[i]->decals[j] ); + } + + savefile->WriteInt( shards[i]->neighbours.Num() ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index = shards.FindIndex(shards[i]->neighbours[j]); + assert(index != -1); + savefile->WriteInt( index ); + } + + savefile->WriteInt( shards[i]->edgeHasNeighbour.Num() ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->WriteBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->WriteInt( shards[i]->droppedTime ); + savefile->WriteInt( shards[i]->islandNum ); + savefile->WriteBool( shards[i]->atEdge ); + savefile->WriteStaticObject( shards[i]->physicsObj ); + } +} + +/* +================ +idBrittleFracture::Restore +================ +*/ +void idBrittleFracture::Restore( idRestoreGame *savefile ) { + int i, j , num; + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; + + savefile->ReadInt( health ); + savefile->Read( &fl, sizeof( fl ) ); + + // setttings + savefile->ReadMaterial( material ); + savefile->ReadMaterial( decalMaterial ); + savefile->ReadFloat( decalSize ); + savefile->ReadFloat( maxShardArea ); + savefile->ReadFloat( maxShatterRadius ); + savefile->ReadFloat( minShatterRadius ); + savefile->ReadFloat( linearVelocityScale ); + savefile->ReadFloat( angularVelocityScale ); + savefile->ReadFloat( shardMass ); + savefile->ReadFloat( density ); + savefile->ReadFloat( friction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadString( fxFracture ); + + // state + savefile->ReadBounds(bounds); + savefile->ReadBool( disableFracture ); + + savefile->ReadInt( lastRenderEntityUpdate ); + savefile->ReadBool( changed ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + shards.SetNum( num ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + for ( i = 0; i < num; i++ ) { + shards[i] = new shard_t; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + savefile->ReadWinding( shards[i]->winding ); + + savefile->ReadInt( j ); + shards[i]->decals.SetNum( j ); + for ( j = 0; j < shards[i]->decals.Num(); j++ ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shards[i]->decals[j] = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + savefile->ReadWinding( *shards[i]->decals[j] ); + } + + savefile->ReadInt( j ); + shards[i]->neighbours.SetNum( j ); + for ( j = 0; j < shards[i]->neighbours.Num(); j++ ) { + int index; + savefile->ReadInt( index ); + assert(index != -1); + shards[i]->neighbours[j] = shards[index]; + } + + savefile->ReadInt( j ); + shards[i]->edgeHasNeighbour.SetNum( j ); + for ( j = 0; j < shards[i]->edgeHasNeighbour.Num(); j++ ) { + savefile->ReadBool( shards[i]->edgeHasNeighbour[j] ); + } + + savefile->ReadInt( shards[i]->droppedTime ); + savefile->ReadInt( shards[i]->islandNum ); + savefile->ReadBool( shards[i]->atEdge ); + savefile->ReadStaticObject( shards[i]->physicsObj ); + if ( shards[i]->droppedTime < 0 ) { + shards[i]->clipModel = physicsObj.GetClipModel( i ); + } else { + shards[i]->clipModel = shards[i]->physicsObj.GetClipModel(); + } + } +} + +/* +================ +idBrittleFracture::Spawn +================ +*/ +void idBrittleFracture::Spawn( void ) { + + // get shard properties + decalMaterial = declManager->FindMaterial( spawnArgs.GetString( "mtr_decal" ) ); + decalSize = spawnArgs.GetFloat( "decalSize", "40" ); + maxShardArea = spawnArgs.GetFloat( "maxShardArea", "200" ); + maxShardArea = idMath::ClampFloat( 100, 10000, maxShardArea ); + maxShatterRadius = spawnArgs.GetFloat( "maxShatterRadius", "40" ); + minShatterRadius = spawnArgs.GetFloat( "minShatterRadius", "10" ); + linearVelocityScale = spawnArgs.GetFloat( "linearVelocityScale", "0.1" ); + angularVelocityScale = spawnArgs.GetFloat( "angularVelocityScale", "40" ); + fxFracture = spawnArgs.GetString( "fx" ); + + // get rigid body properties + shardMass = spawnArgs.GetFloat( "shardMass", "20" ); + shardMass = idMath::ClampFloat( 0.001f, 1000.0f, shardMass ); + spawnArgs.GetFloat( "density", "0.1", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.4", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.01", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + disableFracture = spawnArgs.GetBool( "disableFracture", "0" ); + health = spawnArgs.GetInt( "health", "40" ); + fl.takedamage = true; + + // FIXME: set "bleed" so idProjectile calls AddDamageEffect + spawnArgs.SetBool( "bleed", 1 ); + + CreateFractures( renderEntity.hModel ); + + FindNeighbours(); + + renderEntity.hModel = renderModelManager->AllocModel(); + renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); + renderEntity.callback = idBrittleFracture::ModelCallback; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.noDynamicInteractions = false; +} + +/* +================ +idBrittleFracture::AddShard +================ +*/ +void idBrittleFracture::AddShard( idClipModel *clipModel, idFixedWinding &w ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + shard_t *shard = new shard_t; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shard->clipModel = clipModel; + shard->droppedTime = -1; + shard->winding = w; + shard->decals.Clear(); + shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false ); + shard->neighbours.Clear(); + shard->atEdge = false; + shards.Append( shard ); +} + +/* +================ +idBrittleFracture::RemoveShard +================ +*/ +void idBrittleFracture::RemoveShard( int index ) { + int i; + + delete shards[index]; + shards.RemoveIndex( index ); + physicsObj.RemoveIndex( index ); + + for ( i = index; i < shards.Num(); i++ ) { + shards[i]->clipModel->SetId( i ); + } +} + +/* +================ +idBrittleFracture::UpdateRenderEntity +================ +*/ +bool idBrittleFracture::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + int i, j, k, n, msec, numTris, numDecalTris; + float fade; + dword packedColor; + srfTriangles_t *tris, *decalTris; + modelSurface_t surface; + idDrawVert *v; + idPlane plane; + idMat3 tangents; + + // this may be triggered by a model trace or other non-view related source, + // to which we should look like an empty model + if ( !renderView ) { + return false; + } + + // don't regenerate it if it is current + if ( lastRenderEntityUpdate == gameLocal.time || !changed ) { + return false; + } + + lastRenderEntityUpdate = gameLocal.time; + changed = false; + + numTris = 0; + numDecalTris = 0; + for ( i = 0; i < shards.Num(); i++ ) { + n = shards[i]->winding.GetNumPoints(); + if ( n > 2 ) { + numTris += n - 2; + } + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + n = shards[i]->decals[k]->GetNumPoints(); + if ( n > 2 ) { + numDecalTris += n - 2; + } + } + } + + // FIXME: re-use model surfaces + renderEntity->hModel->InitEmpty( brittleFracture_SnapshotName ); + + // allocate triangle surfaces for the fractures and decals + { + ConditionalAutoCrit crit; + + tris = renderEntity->hModel->AllocSurfaceTriangles( numTris * 3, material->ShouldCreateBackSides() ? numTris * 6 : numTris * 3 ); + decalTris = renderEntity->hModel->AllocSurfaceTriangles( numDecalTris * 3, decalMaterial->ShouldCreateBackSides() ? numDecalTris * 6 : numDecalTris * 3 ); + } + + for ( i = 0; i < shards.Num(); i++ ) { + const idVec3 &origin = shards[i]->clipModel->GetOrigin(); + const idMat3 &axis = shards[i]->clipModel->GetAxis(); + + fade = 1.0f; + if ( shards[i]->droppedTime >= 0 ) { + msec = gameLocal.time - shards[i]->droppedTime - SHARD_FADE_START; + if ( msec > 0 ) { + fade = 1.0f - (float) msec / ( SHARD_ALIVE_TIME - SHARD_FADE_START ); + } + } + packedColor = PackColor( idVec4( renderEntity->shaderParms[ SHADERPARM_RED ] * fade, + renderEntity->shaderParms[ SHADERPARM_GREEN ] * fade, + renderEntity->shaderParms[ SHADERPARM_BLUE ] * fade, + fade ) ); + + const idWinding &winding = shards[i]->winding; + + winding.GetPlane( plane ); + tangents = ( plane.Normal() * axis ).ToMat3(); + + for ( j = 2; j < winding.GetNumPoints(); j++ ) { + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[0].ToVec3() * axis; + v->st[0] = winding[0].s; + v->st[1] = winding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j-1].ToVec3() * axis; + v->st[0] = winding[j-1].s; + v->st[1] = winding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &tris->verts[tris->numVerts++]; + v->Clear(); + v->xyz = origin + winding[j].ToVec3() * axis; + v->st[0] = winding[j].s; + v->st[1] = winding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + + if ( material->ShouldCreateBackSides() ) { + + tris->indexes[tris->numIndexes++] = tris->numVerts - 2; + tris->indexes[tris->numIndexes++] = tris->numVerts - 3; + tris->indexes[tris->numIndexes++] = tris->numVerts - 1; + } + } + + for ( k = 0; k < shards[i]->decals.Num(); k++ ) { + const idWinding &decalWinding = *shards[i]->decals[k]; + + for ( j = 2; j < decalWinding.GetNumPoints(); j++ ) { + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[0].ToVec3() * axis; + v->st[0] = decalWinding[0].s; + v->st[1] = decalWinding[0].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j-1].ToVec3() * axis; + v->st[0] = decalWinding[j-1].s; + v->st[1] = decalWinding[j-1].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + v = &decalTris->verts[decalTris->numVerts++]; + v->Clear(); + v->xyz = origin + decalWinding[j].ToVec3() * axis; + v->st[0] = decalWinding[j].s; + v->st[1] = decalWinding[j].t; + v->normal = tangents[0]; + v->tangents[0] = tangents[1]; + v->tangents[1] = tangents[2]; + v->SetColor( packedColor ); + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + + if ( decalMaterial->ShouldCreateBackSides() ) { + + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 2; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 3; + decalTris->indexes[decalTris->numIndexes++] = decalTris->numVerts - 1; + } + } + } + } + + tris->tangentsCalculated = true; + decalTris->tangentsCalculated = true; + + SIMDProcessor->MinMax( tris->bounds[0], tris->bounds[1], tris->verts, tris->numVerts ); + SIMDProcessor->MinMax( decalTris->bounds[0], decalTris->bounds[1], decalTris->verts, decalTris->numVerts ); + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = material; + surface.id = 0; + surface.geometry = tris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + memset( &surface, 0, sizeof( surface ) ); + surface.shader = decalMaterial; + surface.id = 1; + surface.geometry = decalTris; + { + ConditionalAutoCrit crit; + renderEntity->hModel->AddSurface( surface ); + } + + return true; +} + +/* +================ +idBrittleFracture::ModelCallback +================ +*/ +bool idBrittleFracture::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + const idBrittleFracture *ent; + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idBrittleFracture::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idBrittleFracture::Present +================ +*/ +void idBrittleFracture::Present() { + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + renderEntity.bounds = bounds; + renderEntity.origin.Zero(); + renderEntity.axis.Identity(); + + // force an update because the bounds/origin/axis may stay the same while the model changes + renderEntity.forceUpdate = true; + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + + changed = true; +} + +/* +================ +idBrittleFracture::Think +================ +*/ +void idBrittleFracture::Think( void ) { + int i, startTime, endTime, droppedTime; + shard_t *shard; + bool atRest = true, fading = false; + + // remove overdue shards + for ( i = 0; i < shards.Num(); i++ ) { + droppedTime = shards[i]->droppedTime; + if ( droppedTime != -1 ) { + if ( gameLocal.time - droppedTime > SHARD_ALIVE_TIME ) { + RemoveShard( i ); + i--; + } + fading = true; + } + } + + // remove the entity when nothing is visible + if ( !shards.Num() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + if ( thinkFlags & TH_PHYSICS ) { + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + // run physics on shards + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime == -1 ) { + continue; + } + + shard->physicsObj.Evaluate( endTime - startTime, endTime ); + + if ( !shard->physicsObj.IsAtRest() ) { + atRest = false; + } + } + + if ( atRest ) { + BecomeInactive( TH_PHYSICS ); + } else { + BecomeActive( TH_PHYSICS ); + } + } + + if ( !atRest || bounds.IsCleared() ) { + bounds.Clear(); + for ( i = 0; i < shards.Num(); i++ ) { + bounds.AddBounds( shards[i]->clipModel->GetAbsBounds() ); + } + } + + if ( fading ) { + BecomeActive( TH_UPDATEVISUALS | TH_THINK ); + } else { + BecomeInactive( TH_THINK ); + } + + RunPhysics(); + Present(); +} + +/* +================ +idBrittleFracture::ApplyImpulse +================ +*/ +void idBrittleFracture::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.ApplyImpulse( 0, point, impulse ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, impulse, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::AddForce +================ +*/ +void idBrittleFracture::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + + if ( id < 0 || id >= shards.Num() ) { + return; + } + + if ( shards[id]->droppedTime != -1 ) { + shards[id]->physicsObj.AddForce( 0, point, force ); + } else if ( health <= 0 && !disableFracture ) { + Shatter( point, force, gameLocal.time ); + } +} + +/* +================ +idBrittleFracture::ProjectDecal +================ +*/ +void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) { + int i, j, bits, clipBits; + float a, c, s; + idVec2 st[MAX_POINTS_ON_WINDING]; + idVec3 origin; + idMat3 axis, axistemp; + idPlane textureAxis[2]; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( dir[0] ); + msg.WriteFloat( dir[1] ); + msg.WriteFloat( dir[2] ); + ServerSendInstanceEvent( EVENT_PROJECT_DECAL, &msg, true, -1 ); + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; + } + } + + if ( time >= gameLocal.time ) { + // try to get the sound from the damage def + const idDeclEntityDef *damageDef = NULL; + const idSoundShader *sndShader = NULL; + if ( damageDefName ) { + damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + sndShader = declManager->FindSound( damageDef->dict.GetString( "snd_shatter", "" ) ); + } + } + + if ( sndShader ) { + StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL ); + } else { + StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + + axis[2] = -dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + textureAxis[0] = axis[0] * ( 1.0f / decalSize ); + textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f; + + textureAxis[1] = axis[1] * ( 1.0f / decalSize ); + textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f; + + for ( i = 0; i < shards.Num(); i++ ) { + idFixedWinding &winding = shards[i]->winding; + origin = shards[i]->clipModel->GetOrigin(); + axis = shards[i]->clipModel->GetAxis(); + float d0, d1; + + clipBits = -1; + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + idVec3 p = origin + winding[j].ToVec3() * axis; + + st[j].x = d0 = textureAxis[0].Distance( p ); + st[j].y = d1 = textureAxis[1].Distance( p ); + + bits = FLOATSIGNBITSET( d0 ); + d0 = 1.0f - d0; + bits |= FLOATSIGNBITSET( d1 ) << 2; + d1 = 1.0f - d1; + bits |= FLOATSIGNBITSET( d0 ) << 1; + bits |= FLOATSIGNBITSET( d1 ) << 3; + + clipBits &= bits; + } + + if ( clipBits ) { + continue; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idFixedWinding *decal = new idFixedWinding; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + shards[i]->decals.Append( decal ); + + decal->SetNumPoints( winding.GetNumPoints() ); + for ( j = 0; j < winding.GetNumPoints(); j++ ) { + (*decal)[j].ToVec3() = winding[j].ToVec3(); + (*decal)[j].s = st[j].x; + (*decal)[j].t = st[j].y; + } + } + + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idBrittleFracture::DropShard +================ +*/ +void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) { + int i, j, clipModelId; + float dist, f; + idVec3 dir2, origin; + idMat3 axis; + shard_t *neighbour; + + // don't display decals on dropped shards + shard->decals.DeleteContents( true ); + + // remove neighbour pointers of neighbours pointing to this shard + for ( i = 0; i < shard->neighbours.Num(); i++ ) { + neighbour = shard->neighbours[i]; + for ( j = 0; j < neighbour->neighbours.Num(); j++ ) { + if ( neighbour->neighbours[j] == shard ) { + neighbour->neighbours.RemoveIndex( j ); + break; + } + } + } + + // remove neighbour pointers + shard->neighbours.Clear(); + + // remove the clip model from the static physics object + clipModelId = shard->clipModel->GetId(); + physicsObj.SetClipModel( NULL, 1.0f, clipModelId, false ); + + origin = shard->clipModel->GetOrigin(); + axis = shard->clipModel->GetAxis(); + + // set the dropped time for fading + shard->droppedTime = time; + + dir2 = origin - point; + dist = dir2.Normalize(); +// RAVEN BEGIN +// jscott: changed to avoid negative sqrt call which propagated into badness + if( dist > maxShatterRadius ) { + f = 1.0f; + } else if( dist < minShatterRadius ) { + f = 0.0f; + } else { + f = idMath::Sqrt( dist - minShatterRadius ) * idMath::InvSqrt( maxShatterRadius - minShatterRadius ); + } +// RAVEN END + + // setup the physics + shard->physicsObj.SetSelf( this ); + shard->physicsObj.SetClipModel( shard->clipModel, density ); + shard->physicsObj.SetMass( shardMass ); + shard->physicsObj.SetOrigin( origin ); + shard->physicsObj.SetAxis( axis ); + shard->physicsObj.SetBouncyness( bouncyness ); + shard->physicsObj.SetFriction( 0.6f, 0.6f, friction ); + shard->physicsObj.SetGravity( gameLocal.GetGravity() ); + shard->physicsObj.SetContents( CONTENTS_RENDERMODEL ); + shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir ); + shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) ); + + shard->clipModel->SetId( clipModelId ); + + BecomeActive( TH_PHYSICS ); +} + +/* +================ +idBrittleFracture::Shatter +================ +*/ +void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i; + idVec3 dir; + shard_t *shard; + float m; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( point[0] ); + msg.WriteFloat( point[1] ); + msg.WriteFloat( point[2] ); + msg.WriteFloat( impulse[0] ); + msg.WriteFloat( impulse[1] ); + msg.WriteFloat( impulse[2] ); + ServerSendInstanceEvent( EVENT_SHATTER, &msg, true, -1 ); + } + + if ( time > ( gameLocal.time - SHARD_ALIVE_TIME ) ) { + StartSound( "snd_shatter", SND_CHANNEL_ANY, 0, false, NULL ); + } + + if ( !IsBroken() ) { + Break(); + } + +// RAVEN BEGIN +// bdube: raven effect system + PlayEffect ( "fx_shatter", point, GetPhysics()->GetAxis() ); +// RAVEN END + + dir = impulse; + m = dir.Normalize(); + + for ( i = 0; i < shards.Num(); i++ ) { + shard = shards[i]; + + if ( shard->droppedTime != -1 ) { + continue; + } + + if ( ( shard->clipModel->GetOrigin() - point ).LengthSqr() > Square( maxShatterRadius ) ) { + continue; + } + + DropShard( shard, point, dir, m, time ); + } + + DropFloatingIslands( point, impulse, time ); +} + +/* +================ +idBrittleFracture::DropFloatingIslands +================ +*/ +void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) { + int i, j, numIslands; + int queueStart, queueEnd; + shard_t *curShard, *nextShard, **queue; + bool touchesEdge; + idVec3 dir; + + dir = impulse; + dir.Normalize(); + + numIslands = 0; + queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) ); + for ( i = 0; i < shards.Num(); i++ ) { + shards[i]->islandNum = 0; + } + + for ( i = 0; i < shards.Num(); i++ ) { + + if ( shards[i]->droppedTime != -1 ) { + continue; + } + + if ( shards[i]->islandNum ) { + continue; + } + + queueStart = 0; + queueEnd = 1; + queue[0] = shards[i]; + shards[i]->islandNum = numIslands+1; + touchesEdge = false; + + if ( shards[i]->atEdge ) { + touchesEdge = true; + } + + for ( curShard = queue[queueStart]; queueStart < queueEnd; curShard = queue[++queueStart] ) { + + for ( j = 0; j < curShard->neighbours.Num(); j++ ) { + + nextShard = curShard->neighbours[j]; + + if ( nextShard->droppedTime != -1 ) { + continue; + } + + if ( nextShard->islandNum ) { + continue; + } + + queue[queueEnd++] = nextShard; + nextShard->islandNum = numIslands+1; + + if ( nextShard->atEdge ) { + touchesEdge = true; + } + } + } + numIslands++; + + // if the island is not connected to the world at any edges + if ( !touchesEdge ) { + for ( j = 0; j < queueEnd; j++ ) { + DropShard( queue[j], point, dir, 0.0f, time ); + } + } + } +} + +/* +================ +idBrittleFracture::Break +================ +*/ +void idBrittleFracture::Break( void ) { + fl.takedamage = false; + physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER ); +} + +/* +================ +idBrittleFracture::IsBroken +================ +*/ +bool idBrittleFracture::IsBroken( void ) const { + return ( fl.takedamage == false ); +} + +/* +================ +idBrittleFracture::Killed +================ +*/ +void idBrittleFracture::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !disableFracture ) { + ActivateTargets( this ); + Break(); + } +} + +/* +================ +idBrittleFracture::AddDamageEffect +================ +*/ +void idBrittleFracture::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( !disableFracture ) { + ProjectDecal( collision.c.point, collision.c.normal, gameLocal.time, damageDefName ); + } +} + +/* +================ +idBrittleFracture::Fracture_r +================ +*/ +void idBrittleFracture::Fracture_r( idFixedWinding &w ) { + int i, j, bestPlane; + float a, c, s, dist, bestDist; + idVec3 origin; + idPlane windingPlane, splitPlanes[2]; + idMat3 axis, axistemp; + idFixedWinding back; + idTraceModel trm; + idClipModel *clipModel; + + while( 1 ) { + origin = w.GetCenter(); + w.GetPlane( windingPlane ); + + if ( w.GetArea() < maxShardArea ) { + break; + } + + // randomly create a split plane + a = gameLocal.random.RandomFloat() * idMath::TWO_PI; + c = idMath::Cos( a ); + s = -idMath::Sin( a ); + axis[2] = windingPlane.Normal(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; + axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; + + // get the best split plane + bestDist = 0.0f; + bestPlane = 0; + for ( i = 0; i < 2; i++ ) { + splitPlanes[i].SetNormal( axis[i] ); + splitPlanes[i].FitThroughPoint( origin ); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + dist = splitPlanes[i].Distance( w[j].ToVec3() ); + if ( dist > bestDist ) { + bestDist = dist; + bestPlane = i; + } + } + } + + // split the winding + if ( !w.Split( &back, splitPlanes[bestPlane] ) ) { + break; + } + + // recursively create shards for the back winding + Fracture_r( back ); + } + + // translate the winding to it's center + origin = w.GetCenter(); + for ( j = 0; j < w.GetNumPoints(); j++ ) { + w[j].ToVec3() -= origin; + } + w.RemoveEqualPoints(); + + trm.SetupPolygon( w ); + trm.Shrink( CM_CLIP_EPSILON ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetClipModel( clipModel, 1.0f, shards.Num() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + origin, shards.Num() ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), shards.Num() ); + + AddShard( clipModel, w ); +} + +/* +================ +idBrittleFracture::CreateFractures +================ +*/ +void idBrittleFracture::CreateFractures( const idRenderModel *renderModel ) { + int i, j, k; + const modelSurface_t *surf; + const idDrawVert *v; + idFixedWinding w; + + if ( !renderModel ) { + return; + } + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin(), 0 ); + physicsObj.SetAxis( GetPhysics()->GetAxis(), 0 ); + + for ( i = 0; i < 1 /*renderModel->NumSurfaces()*/; i++ ) { + surf = renderModel->Surface( i ); + material = surf->shader; + +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes + srfTriangles_t * tri = surf->geometry; +#ifdef _MD5R_SUPPORT + if ( tri->primBatchMesh != NULL ) { + + srfTriangles_t *tempTri = (srfTriangles_t *) _alloca16( sizeof(srfTriangles_t) ); + memset( tempTri, 0, sizeof( srfTriangles_t ) ); + + assert( tri->silTraceVerts != NULL ); + + tempTri->numVerts = tri->primBatchMesh->GetNumDrawVertices(); + tempTri->numIndexes = tri->primBatchMesh->GetNumDrawIndices(); + + tempTri->indexes = (glIndex_t *) _alloca16( tempTri->numIndexes * sizeof( tempTri->indexes[0] ) ); + tempTri->verts = (idDrawVert *) _alloca16( tempTri->numVerts * sizeof( idDrawVert ) ); + + renderSystem->CopyPrimBatchTriangles(tempTri->verts, tempTri->indexes, tri->primBatchMesh, tri->silTraceVerts ); + + tri = tempTri; + } +#endif +// RAVEN END + + for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) { + w.Clear(); + for ( k = 0; k < 3; k++ ) { +// RAVEN BEGIN +// dluetscher: added support for MD5R meshes (referred to surf->geometry as tri) + v = &tri->verts[ tri->indexes[ j + 2 - k ] ]; +// RAVEN END + w.AddPoint( v->xyz ); + w[k].s = v->st[0]; + w[k].t = v->st[1]; + } + Fracture_r( w ); + } + } + + physicsObj.SetContents( material->GetContentFlags() ); + SetPhysics( &physicsObj ); +} + +/* +================ +idBrittleFracture::FindNeighbours +================ +*/ +void idBrittleFracture::FindNeighbours( void ) { + int i, j, k, l; + idVec3 p1, p2, dir; + idMat3 axis; + idPlane plane[4]; + + for ( i = 0; i < shards.Num(); i++ ) { + + shard_t *shard1 = shards[i]; + const idWinding &w1 = shard1->winding; + const idVec3 &origin1 = shard1->clipModel->GetOrigin(); + const idMat3 &axis1 = shard1->clipModel->GetAxis(); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + + p1 = origin1 + w1[k].ToVec3() * axis1; + p2 = origin1 + w1[(k+1)%w1.GetNumPoints()].ToVec3() * axis1; + dir = p2 - p1; + dir.Normalize(); + axis = dir.ToMat3(); + + plane[0].SetNormal( dir ); + plane[0].FitThroughPoint( p1 ); + plane[1].SetNormal( -dir ); + plane[1].FitThroughPoint( p2 ); + plane[2].SetNormal( axis[1] ); + plane[2].FitThroughPoint( p1 ); + plane[3].SetNormal( axis[2] ); + plane[3].FitThroughPoint( p1 ); + + for ( j = 0; j < shards.Num(); j++ ) { + + if ( i == j ) { + continue; + } + + shard_t *shard2 = shards[j]; + + for ( l = 0; l < shard1->neighbours.Num(); l++ ) { + if ( shard1->neighbours[l] == shard2 ) { + break; + } + } + if ( l < shard1->neighbours.Num() ) { + continue; + } + + const idWinding &w2 = shard2->winding; + const idVec3 &origin2 = shard2->clipModel->GetOrigin(); + const idMat3 &axis2 = shard2->clipModel->GetAxis(); + + for ( l = w2.GetNumPoints()-1; l >= 0; l-- ) { + p1 = origin2 + w2[l].ToVec3() * axis2; + p2 = origin2 + w2[(l-1+w2.GetNumPoints())%w2.GetNumPoints()].ToVec3() * axis2; + if ( plane[0].Side( p2, 0.1f ) == SIDE_FRONT && plane[1].Side( p1, 0.1f ) == SIDE_FRONT ) { + if ( plane[2].Side( p1, 0.1f ) == SIDE_ON && plane[3].Side( p1, 0.1f ) == SIDE_ON ) { + if ( plane[2].Side( p2, 0.1f ) == SIDE_ON && plane[3].Side( p2, 0.1f ) == SIDE_ON ) { + shard1->neighbours.Append( shard2 ); + shard1->edgeHasNeighbour[k] = true; + shard2->neighbours.Append( shard1 ); + shard2->edgeHasNeighbour[(l-1+w2.GetNumPoints())%w2.GetNumPoints()] = true; + break; + } + } + } + } + } + } + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + if ( !shard1->edgeHasNeighbour[k] ) { + break; + } + } + if ( k < w1.GetNumPoints() ) { + shard1->atEdge = true; + } else { + shard1->atEdge = false; + } + } +} + +/* +================ +idBrittleFracture::Event_Activate +================ +*/ +void idBrittleFracture::Event_Activate( idEntity *activator ) { + disableFracture = false; + if ( health <= 0 ) { + Break(); + } +} + +/* +================ +idBrittleFracture::Event_Touch +================ +*/ +void idBrittleFracture::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 point, impulse; + + if ( !IsBroken() ) { + return; + } + + if ( trace->c.id < 0 || trace->c.id >= shards.Num() ) { + return; + } + + point = shards[trace->c.id]->clipModel->GetOrigin(); + impulse = other->GetPhysics()->GetLinearVelocity() * other->GetPhysics()->GetMass(); + + Shatter( point, impulse, gameLocal.time ); +} + +/* +================ +idBrittleFracture::ClientPredictionThink +================ +*/ +void idBrittleFracture::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + + Think(); +} + +/* +================ +idBrittleFracture::ClientReceiveEvent +================ +*/ +bool idBrittleFracture::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 point, dir; + + switch( event ) { + case EVENT_PROJECT_DECAL: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + ProjectDecal( point, dir, time, NULL ); + return true; + } + case EVENT_SHATTER: { + point[0] = msg.ReadFloat(); + point[1] = msg.ReadFloat(); + point[2] = msg.ReadFloat(); + dir[0] = msg.ReadFloat(); + dir[1] = msg.ReadFloat(); + dir[2] = msg.ReadFloat(); + Shatter( point, dir, time ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} diff --git a/src/mpgame/BrittleFracture.h b/src/mpgame/BrittleFracture.h new file mode 100644 index 0000000..479128b --- /dev/null +++ b/src/mpgame/BrittleFracture.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_BRITTLEFRACTURE_H__ +#define __GAME_BRITTLEFRACTURE_H__ + + +/* +=============================================================================== + +B-rep Brittle Fracture - Static entity using the boundary representation +of the render model which can fracture. + +=============================================================================== +*/ + +typedef struct shard_s { + idClipModel * clipModel; + idFixedWinding winding; + idList decals; + idList edgeHasNeighbour; + idList neighbours; + idPhysics_RigidBody physicsObj; + int droppedTime; + bool atEdge; + int islandNum; +} shard_t; + + +class idBrittleFracture : public idEntity { + +public: + CLASS_PROTOTYPE( idBrittleFracture ); + + idBrittleFracture( void ); + virtual ~idBrittleFracture( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Present( void ); + virtual void Think( void ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ); + bool IsBroken( void ) const; + + enum { + EVENT_PROJECT_DECAL = idEntity::EVENT_MAXEVENTS, + EVENT_SHATTER, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + // setttings + const idMaterial * material; + const idMaterial * decalMaterial; + float decalSize; + float maxShardArea; + float maxShatterRadius; + float minShatterRadius; + float linearVelocityScale; + float angularVelocityScale; + float shardMass; + float density; + float friction; + float bouncyness; + idStr fxFracture; + + // state + idPhysics_StaticMulti physicsObj; + idList shards; + idBounds bounds; + bool disableFracture; + + // for rendering + mutable int lastRenderEntityUpdate; + mutable bool changed; + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + void AddShard( idClipModel *clipModel, idFixedWinding &w ); + void RemoveShard( int index ); + void DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ); + void Shatter( const idVec3 &point, const idVec3 &impulse, const int time ); + void DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ); + void Break( void ); + void Fracture_r( idFixedWinding &w ); + void CreateFractures( const idRenderModel *renderModel ); + void FindNeighbours( void ); + + void Event_Activate( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +#endif /* !__GAME_BRITTLEFRACTURE_H__ */ diff --git a/src/mpgame/Camera.cpp b/src/mpgame/Camera.cpp new file mode 100644 index 0000000..71abd1b --- /dev/null +++ b/src/mpgame/Camera.cpp @@ -0,0 +1,2232 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idCamera + + Base class for cameras + +=============================================================================== +*/ + +ABSTRACT_DECLARATION( idEntity, idCamera ) +END_CLASS + +/* +===================== +idCamera::Spawn +===================== +*/ +void idCamera::Spawn( void ) { +} + +/* +===================== +idCamera::GetRenderView +===================== +*/ +renderView_t *idCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + GetViewParms( rv ); + return rv; +} + +/*********************************************************************** + + idCameraView + +***********************************************************************/ +const idEventDef EV_Camera_SetAttachments( "", NULL ); + +// RAVEN BEGIN +// bdube: added events +const idEventDef EV_SetFOV ( "setFOV", "f" ); +const idEventDef EV_GetFOV ( "getFOV", NULL, 'f' ); +const idEventDef EV_BlendFOV ( "blendFOV", "fff"); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraView ) + EVENT( EV_Activate, idCameraView::Event_Activate ) + EVENT( EV_Camera_SetAttachments, idCameraView::Event_SetAttachments ) + +// RAVEN BEGIN +// bdube: added events + EVENT( EV_SetFOV, idCameraView::Event_SetFOV ) + EVENT( EV_BlendFOV, idCameraView::Event_BlendFOV ) + EVENT( EV_GetFOV, idCameraView::Event_GetFOV ) +// RAVEN END +END_CLASS + +/* +=============== +idCameraView::idCameraView +================ +*/ +idCameraView::idCameraView() { +// RAVEN BEGIN +// bdube: interpolate fov +// scork: get it from the cvar, don't assume 90 + fov.Init ( gameLocal.time, 0, g_fov.GetFloat(), g_fov.GetFloat() ); +// RAVEN END + attachedTo = NULL; + attachedView = NULL; +} + +/* +=============== +idCameraView::Save +================ +*/ +void idCameraView::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// bdube: fov interpolated now + savefile->WriteInt( fov.GetDuration() ); + savefile->WriteInt( fov.GetStartTime() ); + savefile->WriteFloat( fov.GetStartValue() ); + savefile->WriteFloat( fov.GetEndValue() ); +// RAVEN END + savefile->WriteObject( attachedTo ); + savefile->WriteObject( attachedView ); +} + +/* +=============== +idCameraView::Restore +================ +*/ +void idCameraView::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// bdube: fov interpolated now + int set; + float setf; + savefile->ReadInt( set ); + fov.SetDuration( set ); + savefile->ReadInt( set ); + fov.SetStartTime( set ); + savefile->ReadFloat( setf ); + fov.SetStartValue( setf ); + savefile->ReadFloat( setf ); + fov.SetEndValue( setf ); +// RAVEN END + savefile->ReadObject( reinterpret_cast( attachedTo ) ); + savefile->ReadObject( reinterpret_cast( attachedView ) ); +} + +/* +=============== +idCameraView::Event_SetAttachments +================ +*/ +void idCameraView::Event_SetAttachments( ) { + SetAttachment( &attachedTo, "attachedTo" ); + SetAttachment( &attachedView, "attachedView" ); +} + +/* +=============== +idCameraView::Event_Activate +================ +*/ +void idCameraView::Event_Activate( idEntity *activator ) { + if (spawnArgs.GetBool("trigger")) { + if (gameLocal.GetCamera() != this) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + gameLocal.SetCamera(this); + } else { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + } + } +} + +/* +===================== +idCameraView::Stop +===================== +*/ +void idCameraView::Stop( void ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + gameLocal.SetCamera(NULL); + ActivateTargets( gameLocal.GetLocalPlayer() ); +} + +// RAVEN BEGIN +// bdube: added events +/* +===================== +idCameraView::Event_SetFOV +===================== +*/ +void idCameraView::Event_SetFOV ( float newfov ) +{ + fov.Init ( gameLocal.time, 0, newfov, newfov ); +} + +/* +===================== +idCameraView::Event_BlendFOV +===================== +*/ +void idCameraView::Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ) +{ + fov.Init ( gameLocal.time, SEC2MS(blendTime), beginFOV, endFOV ); +} + +/* +===================== +idCameraView::Event_GetFOV +===================== +*/ +void idCameraView::Event_GetFOV() +{ + idThread::ReturnFloat(fov.GetCurrentValue(gameLocal.time)); +} +// RAVEN END + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::SetAttachment( idEntity **e, const char *p ) { + const char *cam = spawnArgs.GetString( p ); + if ( strlen ( cam ) ) { + *e = gameLocal.FindEntity( cam ); + } +} + + +/* +===================== +idCameraView::Spawn +===================== +*/ +void idCameraView::Spawn( void ) { + // if no target specified use ourself + const char *cam = spawnArgs.GetString("cameraTarget"); + if ( strlen ( cam ) == 0) { + spawnArgs.Set("cameraTarget", spawnArgs.GetString("name")); + } +// RAVEN BEGIN +// bdube: interpolate fov +// scork: ... but default from the cvar, not hardwired 90 + fov.Init ( gameLocal.time, 0, spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())), spawnArgs.GetFloat("fov", va("%f",g_fov.GetFloat())) ); +// RAVEN END + + PostEventMS( &EV_Camera_SetAttachments, 0 ); + + UpdateChangeableSpawnArgs(NULL); +} + +/* +===================== +idCamera::RenderView +===================== +*/ +void idCameraView::GetViewParms( renderView_t *view ) { + assert( view ); + + if (view == NULL) { + return; + } + + idVec3 dir; + idEntity *ent; + + if ( attachedTo ) { + ent = attachedTo; + } else { + ent = this; + } + + view->vieworg = ent->GetPhysics()->GetOrigin(); + if ( attachedView ) { + dir = attachedView->GetPhysics()->GetOrigin() - view->vieworg; + dir.Normalize(); + view->viewaxis = dir.ToMat3(); + } else { + view->viewaxis = ent->GetPhysics()->GetAxis(); + } + +// RAVEN BEGIN +// bdube: interpolate fov + gameLocal.CalcFov( fov.GetCurrentValue( gameLocal.time ), view->fov_x, view->fov_y ); +// RAVEN END +} + + + + + + + + + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/*********************************************************************** + + rvCameraAnimation + +***********************************************************************/ +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( void ) { + frameRate = 0; +} + +/* +===================== +rvCameraAnimation::rvCameraAnimation +===================== +*/ +rvCameraAnimation::rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ) { + cameraCuts = anim->cameraCuts; + camera = anim->camera; + frameLookup = anim->frameLookup; + frameCommands = anim->frameCommands; + frameRate = anim->frameRate; + name = anim->name; + realname = anim->realname; +} + +/* +===================== +rvCameraAnimation::~rvCameraAnimation +===================== +*/ +rvCameraAnimation::~rvCameraAnimation( void ) { +} + +/* +===================== +rvCameraAnimation::Name +===================== +*/ +const char *rvCameraAnimation::Name( void ) const { + return name; +} + +/* +===================== +rvCameraAnimation::FullName +===================== +*/ +const char *rvCameraAnimation::FullName( void ) const { + return realname; +} + +/* +===================== +rvCameraAnimation::NumFrames +===================== +*/ +int rvCameraAnimation::NumFrames( void ) const { + return camera.Num(); +} + +/* +===================== +rvCameraAnimation::NumCuts +===================== +*/ +int rvCameraAnimation::NumCuts( void ) const { + return cameraCuts.Num(); +} + +void rvCameraAnimation::SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ) { + int version; + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + int numFrames; + int numCuts; + int i; + + filename.SetFileExtension( MD5_CAMERA_EXT ); + if ( !parser.LoadFile( filename ) ) { + gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() ); + } + + cameraCuts.Clear(); + cameraCuts.SetGranularity( 1 ); + camera.Clear(); + camera.SetGranularity( 1 ); + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse framerate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate <= 0 ) { + parser.Error( "Invalid framerate: %d", frameRate ); + } + + // parse num cuts + parser.ExpectTokenString( "numCuts" ); + numCuts = parser.ParseInt(); + if ( ( numCuts < 0 ) || ( numCuts > numFrames ) ) { + parser.Error( "Invalid number of camera cuts: %d", numCuts ); + } + + // parse the camera cuts + parser.ExpectTokenString( "cuts" ); + parser.ExpectTokenString( "{" ); + cameraCuts.SetNum( numCuts ); + for( i = 0; i < numCuts; i++ ) { + cameraCuts[ i ] = parser.ParseInt(); + if ( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) { + parser.Error( "Invalid camera cut" ); + } + } + parser.ExpectTokenString( "}" ); + + // parse the camera frames + parser.ExpectTokenString( "camera" ); + parser.ExpectTokenString( "{" ); + camera.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() ); + camera[ i ].fov = parser.ParseFloat(); + } + parser.ExpectTokenString( "}" ); + +#if 0 + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + idDebugGraph gGraph; + idDebugGraph tGraph; + idDebugGraph qGraph; + idDebugGraph dtGraph; + idDebugGraph dqGraph; + gGraph.SetNumSamples( numFrames ); + tGraph.SetNumSamples( numFrames ); + qGraph.SetNumSamples( numFrames ); + dtGraph.SetNumSamples( numFrames ); + dqGraph.SetNumSamples( numFrames ); + + gameLocal.Printf( "\n\ndelta vec:\n" ); + float diff_t, last_t, t; + float diff_q, last_q, q; + diff_t = last_t = 0.0f; + diff_q = last_q = 0.0f; + for( i = 1; i < numFrames; i++ ) { + t = ( camera[ i ].t - camera[ i - 1 ].t ).Length(); + q = ( camera[ i ].q.ToQuat() - camera[ i - 1 ].q.ToQuat() ).Length(); + diff_t = t - last_t; + diff_q = q - last_q; + gGraph.AddValue( ( i % 10 ) == 0 ); + tGraph.AddValue( t ); + qGraph.AddValue( q ); + dtGraph.AddValue( diff_t ); + dqGraph.AddValue( diff_q ); + + gameLocal.Printf( "%d: %.8f : %.8f, %.8f : %.8f\n", i, t, diff_t, q, diff_q ); + last_t = t; + last_q = q; + } + + gGraph.Draw( colorBlue, 300.0f ); + tGraph.Draw( colorOrange, 60.0f ); + dtGraph.Draw( colorYellow, 6000.0f ); + qGraph.Draw( colorGreen, 60.0f ); + dqGraph.Draw( colorCyan, 6000.0f ); +#endif +} + +/* +===================== +rvCameraAnimation::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *rvCameraAnimation::AddFrameCommand( const idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0].c_str() ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { +/* if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token );*/ + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( camera.Num() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > camera.Num() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, name.c_str(), camera.Num() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +/* +===================== +rvCameraAnimation::CallFrameCommandSound +===================== +*/ +void rvCameraAnimation::CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + ent->StartSound ( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} + +/* +===================== +rvCameraAnimation::CallFrameCommands +===================== +*/ +void rvCameraAnimation::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + if ( !frameLookup.Num() ) { + return; + } + + numframes = NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "Cameraframecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect ( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect ( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect ( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_origin, mat3_identity ); + } else { + cent = gameLocal.PlayEffect ( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent && command.joint2 && ent->IsType ( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + cent->SetEndOrigin ( ent->GetAnimator()->GetJointHandle ( *command.joint2 ) ); + } +// RAVEN END + break; + } + case FC_SKIN: +// ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { + break; + } + case FC_FOOTSTEP : { +// ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { +// ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { +// ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { +// ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { +// ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { +// ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { +// ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: +// ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: +// ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: +// ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: +// ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: +// ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: +// ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: +// ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_ACT_ATTACH_SHOW: +/* + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } +*/ + break; + + case FC_AI_ATTACK_MELEE: +// ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + + + + + + + + + +/*********************************************************************** + + idDeclCameraDef + +***********************************************************************/ + +/* +===================== +idDeclCameraDef::idDeclCameraDef +===================== +*/ +idDeclCameraDef::idDeclCameraDef() { +} + +/* +===================== +idDeclCameraDef::~idDeclCameraDef +===================== +*/ +idDeclCameraDef::~idDeclCameraDef() { + FreeData(); +} + +// RAVEN BEGIN +// jsinger: Added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +/* +===================== +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +===================== +*/ +idDeclCameraDef::idDeclCameraDef( SerialInputStream &stream ) +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::Write( SerialOutputStream &stream ) const +{ + // not supported yet + assert(false); +} + +void idDeclCameraDef::AddReferences() const +{ + // not supported yet + assert(false); +} +#endif +// RAVEN END +/* +================= +idDeclCameraDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclCameraDef::Size( void ) const { + + size_t size; + + size = sizeof( idDeclCameraDef ); + size += anims.Allocated(); + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclCameraDef::CopyDecl +===================== +*/ +void idDeclCameraDef::CopyDecl( const idDeclCameraDef *decl ) { + int i; + + FreeData(); + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new rvCameraAnimation( this, decl->anims[ i ] ); + } +} + +/* +===================== +idDeclCameraDef::FreeData +===================== +*/ +void idDeclCameraDef::FreeData( void ) { + anims.DeleteContents( true ); +} + +/* +================ +idDeclCameraDef::DefaultDefinition +================ +*/ +const char *idDeclCameraDef::DefaultDefinition( void ) const { + return "{ }"; +} + + + +/* +===================== +idDeclCameraDef::Touch +===================== +*/ +void idDeclCameraDef::Touch( void ) const { +} + +/* +===================== +idDeclCameraDef::ParseAnim +===================== +*/ +bool idDeclCameraDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + rvCameraAnimation *anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + + numAnims = 0; + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new rvCameraAnimation(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, token ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + return true; +} + +/* +================ +idDeclCameraDef::Parse +================ +*/ +bool idDeclCameraDef::Parse( const char *text, const int textLength, bool noCaching ) { + idStr filename; + idStr extension; + idLexer src; + idToken token; + idToken token2; + int numDefaultAnims; + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + else if ( token == "anim" ) { + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclCameraDef::Validate +===================== +*/ +bool idDeclCameraDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclCameraDef *pSelf = (idDeclCameraDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclCameraDef::HasAnim +===================== +*/ +bool idDeclCameraDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclCameraDef::NumAnims +===================== +*/ +int idDeclCameraDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclCameraDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclCameraDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclCameraDef::GetAnim +===================== +*/ +int idDeclCameraDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + + + + + + + + + + +/* +=============================================================================== + + idCameraAnim + +=============================================================================== +*/ +const idEventDef EV_Camera_Start( "start", NULL ); +const idEventDef EV_Camera_Stop( "stop", NULL ); + +// RAVEN BEGIN +// mekberg: wait support +const idEventDef EV_Camera_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idCamera, idCameraAnim ) + EVENT( EV_Thread_SetCallback, idCameraAnim::Event_SetCallback ) + EVENT( EV_Camera_Stop, idCameraAnim::Event_Stop ) + EVENT( EV_Camera_Start, idCameraAnim::Event_Start ) + EVENT( EV_Activate, idCameraAnim::Event_Activate ) + + // RAVEN BEGIN + // mekberg: wait support + EVENT( EV_IsActive, idCameraAnim::Event_IsActive ) + // RAVEN END +END_CLASS + + +/* +===================== +idCameraAnim::idCameraAnim +===================== +*/ +idCameraAnim::idCameraAnim() { + threadNum = 0; + offset.Zero(); + cycle = 1; + starttime = 0; + activator = NULL; + lastFrame = -1; +} + +/* +===================== +idCameraAnim::~idCameraAnim +===================== +*/ +idCameraAnim::~idCameraAnim() { + if ( gameLocal.GetCamera() == this ) { + gameLocal.SetCamera( NULL ); + } +} + +/* +=============== +idCameraAnim::Save +================ +*/ +void idCameraAnim::Save( idSaveGame *savefile ) const { + savefile->WriteInt( threadNum ); + savefile->WriteVec3( offset ); + savefile->WriteInt( starttime ); + savefile->WriteInt( cycle ); + savefile->WriteInt( lastFrame ); // cnicholson: Added unsaved var + + activator.Save( savefile ); +} + +/* +=============== +idCameraAnim::Restore +================ +*/ +void idCameraAnim::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( threadNum ); + savefile->ReadVec3( offset ); + savefile->ReadInt( starttime ); + savefile->ReadInt( cycle ); + savefile->ReadInt( lastFrame ); // cnicholson: Added unread var + + activator.Restore( savefile ); + + LoadAnim(); +} + +/* +===================== +idCameraAnim::Spawn +===================== +*/ +void idCameraAnim::Spawn( void ) { + if ( spawnArgs.GetVector( "old_origin", "0 0 0", offset ) ) { + offset = GetPhysics()->GetOrigin() - offset; + } else { + offset.Zero(); + } + + // always think during cinematics + cinematic = true; + + LoadAnim(); + +// RAVEN BEGIN +#ifndef _CONSOLE + // touch the cinematic streaming command file during build + if ( cvarSystem->GetCVarBool("com_makingBuild") && cvarSystem->GetCVarBool("com_Bundler") ) + { + idFile *file; + idStr filename = "cinematics/"; + filename += gameLocal.mapFileNameStripped; + filename += "_"; + filename += name; + filename += ".cincmd"; + file = fileSystem->OpenFileRead( filename ); + fileSystem->CloseFile( file ); + } +#endif +// RAVEN END +} + +/* +================ +idCameraAnim::Load +================ +*/ +void idCameraAnim::LoadAnim( void ) { + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT ); + idToken token; + idStr filename; + const char *key; + + key = spawnArgs.GetString( "camera" ); + const idDecl *mapDecl = declManager->FindType( DECL_CAMERADEF, key, false ); + cameraDef = static_cast( mapDecl ); + + key = spawnArgs.GetString( "anim" ); + if ( !key ) { + gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() ); + } + +} + +/* +=============== +idCameraAnim::Start +================ +*/ +void idCameraAnim::Start( void ) { + cycle = spawnArgs.GetInt( "cycle" ); + if ( !cycle ) { + cycle = 1; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() ); + } + + lastFrame = -1; + starttime = gameLocal.time; + gameLocal.SetCamera( this ); + BecomeActive( TH_THINK ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_BEGIN, cameraDef->GetAnim(1)->GetFrameRate(), GetName() ); +#endif +// RAVEN END + + // if the player has already created the renderview for this frame, have him update it again so that the camera starts this frame +// RAVEN BEGIN +// mekberg: make sure render view is valid. + if ( gameLocal.GetLocalPlayer( )->GetRenderView( ) && gameLocal.GetLocalPlayer()->GetRenderView()->time == gameLocal.time ) { +// RAVEN END + gameLocal.GetLocalPlayer()->CalculateRenderView(); + } +} + +/* +===================== +idCameraAnim::Stop +===================== +*/ +void idCameraAnim::Stop( void ) { + if ( gameLocal.GetCamera() == this ) { + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() ); + } + + BecomeInactive( TH_THINK ); + gameLocal.SetCamera( NULL ); + if ( threadNum ) { + idThread::ObjectMoveDone( threadNum, this ); + threadNum = 0; + } + ActivateTargets( activator.GetEntity() ); + +// RAVEN BEGIN +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_END, cameraDef->GetAnim(1)->GetFrameRate() ); +#endif +// RAVEN END + } +} + +/* +===================== +idCameraAnim::Think +===================== +*/ +void idCameraAnim::Think( void ) { + int frame; + int frameTime; + + if ( thinkFlags & TH_THINK ) { + // check if we're done in the Think function when the cinematic is being skipped (idCameraAnim::GetViewParms isn't called when skipping cinematics). +// RAVEN BEGIN +// abahr: removed '!' + if ( gameLocal.skipCinematic ) { +// RAVEN END + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() < 2 ) { + // 1 frame anims never end + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + } + + if ( frame > cameraDef->GetAnim(1)->NumFrames() + cameraDef->GetAnim(1)->NumCuts() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + lastFrame = -1; + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + } else { + Stop(); + } + } + } +} + +/* +===================== +idCameraAnim::GetViewParms +===================== +*/ +void idCameraAnim::GetViewParms( renderView_t *view ) { + int realFrame; + int frame; + int frameTime; + float lerp; + float invlerp; + const cameraFrame_t *camFrame; + int i; + int cut; + idQuat q1, q2, q3; + + assert( view ); + if ( !view ) { + return; + } +//RAVEN BEGIN +//jshepard: safety first + if( !cameraDef ) { + gameLocal.Warning("Invalid cameraDef in GetViewParms"); + return; + } +//RAVEN END + if ( !cameraDef->GetAnim(1) ) { + return; + } + + if ( cameraDef->GetAnim(1)->NumFrames() == 0 ) { + // we most likely are in the middle of a restore + // FIXME: it would be better to fix it so this doesn't get called during a restore + return; + } + + if ( cameraDef->GetAnim(1)->GetFrameRate() == gameLocal.GetMHz() ) { + frameTime = gameLocal.time - starttime; + frame = frameTime / gameLocal.msec; + lerp = 0.0f; + } else { + frameTime = ( gameLocal.time - starttime ) * cameraDef->GetAnim(1)->GetFrameRate(); + frame = frameTime / 1000; + lerp = ( frameTime % 1000 ) * 0.001f; + } + + // skip any frames where camera cuts occur + realFrame = frame; + cut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( frame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + frame++; + cut++; + } + if ( lastFrame != frame ) { + cameraDef->GetAnim(1)->CallFrameCommands( this, lastFrame, frame ); + lastFrame = frame; + } + + if ( g_debugCinematic.GetBool() ) { + int prevFrameTime = ( gameLocal.time - starttime - gameLocal.msec ) * cameraDef->GetAnim(1)->GetFrameRate(); + int prevFrame = prevFrameTime / 1000; + int prevCut; + + prevCut = 0; + for( i = 0; i < cameraDef->GetAnim(1)->NumCuts(); i++ ) { + if ( prevFrame < cameraDef->GetAnim(1)->GetCut( i ) ) { + break; + } + prevFrame++; + prevCut++; + } + + if ( prevCut != cut ) { + gameLocal.Printf( "%d: '%s' cut %d\n", gameLocal.framenum, GetName(), cut ); + } + } + + // clamp to the first frame. also check if this is a one frame anim. one frame anims would end immediately, + // but since they're mainly used for static cams anyway, just stay on it infinitely. + if ( ( frame < 0 ) || ( cameraDef->GetAnim(1)->NumFrames() < 2 ) ) { + view->viewaxis = cameraDef->GetAnim(1)->GetAnim( 0 )->q.ToQuat().ToMat3(); + view->vieworg = cameraDef->GetAnim(1)->GetAnim( 0 )->t + offset; + view->fov_x = cameraDef->GetAnim(1)->GetAnim( 0 )->fov; + } else if ( frame > cameraDef->GetAnim(1)->NumFrames() - 2 ) { + if ( cycle > 0 ) { + cycle--; + } + + if ( cycle != 0 ) { + // advance start time so that we loop + starttime += ( ( cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ) * 1000 ) / cameraDef->GetAnim(1)->GetFrameRate(); + GetViewParms( view ); + return; + } + + Stop(); + if ( gameLocal.GetCamera() != NULL ) { + // we activated another camera when we stopped, so get it's viewparms instead + gameLocal.GetCamera()->GetViewParms( view ); + return; + } else { + // just use our last frame + camFrame = cameraDef->GetAnim(1)->GetAnim( cameraDef->GetAnim(1)->NumFrames() - 1 ); + view->viewaxis = camFrame->q.ToQuat().ToMat3(); + view->vieworg = camFrame->t + offset; + view->fov_x = camFrame->fov; + } + } else if ( lerp == 0.0f ) { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + view->viewaxis = camFrame[ 0 ].q.ToMat3(); + view->vieworg = camFrame[ 0 ].t + offset; + view->fov_x = camFrame[ 0 ].fov; + } else { + camFrame = cameraDef->GetAnim(1)->GetAnim( frame ); + invlerp = 1.0f - lerp; + q1 = camFrame[ 0 ].q.ToQuat(); + q2 = camFrame[ 1 ].q.ToQuat(); + q3.Slerp( q1, q2, lerp ); + view->viewaxis = q3.ToMat3(); + view->vieworg = camFrame[ 0 ].t * invlerp + camFrame[ 1 ].t * lerp + offset; + view->fov_x = camFrame[ 0 ].fov * invlerp + camFrame[ 1 ].fov * lerp; + } + + gameLocal.CalcFov( view->fov_x, view->fov_x, view->fov_y ); + + // setup the pvs for this frame + UpdatePVSAreas( view->vieworg ); + +#if 0 + static int lastFrame = 0; + static idVec3 lastFrameVec( 0.0f, 0.0f, 0.0f ); + if ( gameLocal.time != lastFrame ) { + gameRenderWorld->DebugBounds( colorCyan, idBounds( view->vieworg ).Expand( 16.0f ), vec3_origin, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, view->vieworg, view->vieworg + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + gameRenderWorld->DebugLine( colorCyan, lastFrameVec, view->vieworg, 10000, false ); + gameRenderWorld->DebugLine( colorYellow, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 66.0f, 10000, false ); + gameRenderWorld->DebugLine( colorOrange, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 64.0f + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false ); + lastFrameVec = view->vieworg; + lastFrame = gameLocal.time; + } +#endif + + if ( g_showcamerainfo.GetBool() ) { + gameLocal.Printf( "^5Frame: ^7%d/%d\n\n\n", realFrame + 1, cameraDef->GetAnim(1)->NumFrames() - cameraDef->GetAnim(1)->NumCuts() ); + } +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + renderSystem->TrackTextureUsage( idRenderSystem::TEXTURE_TRACK_UPDATE, realFrame ); +#endif +} +// RAVEN END + +/* +=============== +idCameraAnim::Event_Activate +================ +*/ +void idCameraAnim::Event_Activate( idEntity *_activator ) { + activator = _activator; + if ( thinkFlags & TH_THINK ) { + Stop(); + } else { + Start(); + } +} + +/* +=============== +idCameraAnim::Event_Start +================ +*/ +void idCameraAnim::Event_Start( void ) { + Start(); +} + +/* +=============== +idCameraAnim::Event_Stop +================ +*/ +void idCameraAnim::Event_Stop( void ) { + Stop(); +} + +/* +================ +idCameraAnim::Event_SetCallback +================ +*/ +void idCameraAnim::Event_SetCallback( void ) { + if ( ( gameLocal.GetCamera() == this ) && !threadNum ) { + threadNum = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// mekberg: wait support +/* +================ +idCameraAnim::Event_IsActive +================ +*/ +void idCameraAnim::Event_IsActive( void ) { + idThread::ReturnFloat( gameLocal.GetCamera( ) ? 1.0f : 0.0f ); +} + +// RAVEN END + +// RAVEN BEGIN +// jscott: portal sky support + +/*********************************************************************** + + rvCameraPortalSky + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPortalSky ) +END_CLASS + +/* +=============== +rvCameraPortalSky::Save +================ +*/ +void rvCameraPortalSky::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPortalSky::Restore +================ +*/ +void rvCameraPortalSky::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPortalSky::Spawn +===================== +*/ +void rvCameraPortalSky::Spawn( void ) +{ + if( gameLocal.GetPortalSky() ) + { + gameLocal.Error( "Only one portal sky camera allowed" ); + } + gameLocal.SetPortalSky( this ); +} + +/* +===================== +rvCameraPortalSky::GetViewParms +===================== +*/ +void rvCameraPortalSky::GetViewParms( renderView_t *view ) +{ + assert( view ); + if( view ) + { + view->vieworg = GetPhysics()->GetOrigin(); + view->viewID = -1; + } +} + + +/*********************************************************************** + + rvCameraPlayback + +***********************************************************************/ + +CLASS_DECLARATION( idCamera, rvCameraPlayback ) +END_CLASS + +/* +=============== +rvCameraPlayback::Save +================ +*/ +void rvCameraPlayback::Save( idSaveGame *savefile ) const +{ +} + +/* +=============== +rvCameraPlayback::Restore +================ +*/ +void rvCameraPlayback::Restore( idRestoreGame *savefile ) +{ + // Run spawn to set default values + Spawn(); +} + +/* +===================== +rvCameraPlayback::Spawn +===================== +*/ +void rvCameraPlayback::Spawn( void ) +{ + startTime = gameLocal.time; + playback = declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); +} + +/* +===================== +rvCameraPlayback::GetViewParms +===================== +*/ +void rvCameraPlayback::GetViewParms( renderView_t *view ) +{ + rvDeclPlaybackData pbd; + + assert( view ); + if( view ) + { + pbd.Init(); + if( declManager->GetPlaybackData( playback, PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, gameLocal.time - startTime, gameLocal.time - startTime, &pbd ) ) + { + startTime = gameLocal.time; + } + + view->vieworg = pbd.GetPosition(); + view->viewaxis = pbd.GetAngles().ToMat3(); + + // field of view +// RAVEN BEGIN +// jshepard: fov as a float for smoove transitions + gameLocal.CalcFov ( g_fov.GetFloat(), view->fov_x, view->fov_y ); + +// RAVEN END + } +} + +// RAVEN END diff --git a/src/mpgame/Camera.h b/src/mpgame/Camera.h new file mode 100644 index 0000000..e9cbb7e --- /dev/null +++ b/src/mpgame/Camera.h @@ -0,0 +1,319 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_CAMERA_H__ +#define __GAME_CAMERA_H__ + +/* +=============================================================================== + +Camera providing an alternative view of the level. + +=============================================================================== +*/ + +class idCamera : public idEntity { +public: + ABSTRACT_PROTOTYPE( idCamera ); + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ) = 0; + virtual renderView_t * GetRenderView(); + virtual void Stop( void ){} ; +}; + +/* +=============================================================================== + +idCameraView + +=============================================================================== +*/ + +extern const idEventDef EV_SetFOV; +extern const idEventDef EV_Camera_Start; +extern const idEventDef EV_Camera_Stop; + +class idCameraView : public idCamera { +public: + CLASS_PROTOTYPE( idCameraView ); + idCameraView(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( ); + virtual void GetViewParms( renderView_t *view ); + virtual void Stop( void ); + +protected: + void Event_Activate( idEntity *activator ); + void Event_SetAttachments(); + void SetAttachment( idEntity **e, const char *p ); +// RAVEN BEGIN +// bdube: changed fov to interpolated value + idInterpolate fov; +// RAVEN END + idEntity *attachedTo; + idEntity *attachedView; + +// RAVEN BEGIN +// bdube: added setfov event + void Event_SetFOV ( float fov ); + void Event_BlendFOV ( float beginFOV, float endFOV, float blendTime ); + void Event_GetFOV ( void ); +// RAVEN END +}; + + + +/* +=============================================================================== + +A camera which follows a path defined by an animation. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + +/* +============================================================================================== + + rvCameraAnimation + +============================================================================================== +*/ +class idDeclCameraDef; + +typedef struct { + idCQuat q; + idVec3 t; + float fov; +} cameraFrame_t; + +class rvCameraAnimation { +private: + idList cameraCuts; + idList camera; + idList frameLookup; + idList frameCommands; + int frameRate; + idStr name; + idStr realname; + +public: + rvCameraAnimation(); + rvCameraAnimation( const idDeclCameraDef *cameraDef, const rvCameraAnimation *anim ); + ~rvCameraAnimation(); + + void SetAnim( const idDeclCameraDef *cameraDef, const char *sourcename, const char *animname, idStr filename ); + const char *Name( void ) const; + const char *FullName( void ) const; + int NumFrames( void ) const; + const cameraFrame_t * GetAnim( int index ) const; + int NumCuts( void ) const; + const int GetCut( int index ) const; + const int GetFrameRate( void ) const; + + const char *AddFrameCommand( const class idDeclCameraDef *cameraDef, const idList& frames, idLexer &src, const idDict *def ); + void CallFrameCommands( idEntity *ent, int from, int to ) const; + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; +}; + +ID_INLINE const cameraFrame_t *rvCameraAnimation::GetAnim( int index ) const { + return &camera[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetCut( int index ) const { + return cameraCuts[ index ]; +} + +ID_INLINE const int rvCameraAnimation::GetFrameRate( void ) const { + return frameRate; +} + +/* +============================================================================================== + + idDeclCameraDef + +============================================================================================== +*/ +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclCameraDef : public idDecl, public Serializable<'IDCD'> { +public: + idDeclCameraDef( SerialInputStream &stream ); + + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; +#else +class idDeclCameraDef : public idDecl { +#endif +// RAVEN END +public: + idDeclCameraDef(); + ~idDeclCameraDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + int NumAnims( void ) const; + const rvCameraAnimation * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + +private: + void CopyDecl( const idDeclCameraDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idList anims; +}; + +ID_INLINE const rvCameraAnimation *idDeclCameraDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idCameraAnim + +============================================================================================== +*/ +class idCameraAnim : public idCamera { +public: + CLASS_PROTOTYPE( idCameraAnim ); + + idCameraAnim(); + ~idCameraAnim(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int threadNum; + idVec3 offset; + int starttime; + int cycle; + const idDeclCameraDef *cameraDef; + int lastFrame; + idEntityPtr activator; + + void Start( void ); + void Stop( void ); + void Think( void ); + + void LoadAnim( void ); + void Event_Start( void ); + void Event_Stop( void ); + void Event_SetCallback( void ); + void Event_Activate( idEntity *activator ); + +// RAVEN BEGIN +// mekberg: wait support + void Event_IsActive( ); + + idList imageTable; + idList imageCmds; +// RAVEN END +}; +// RAVEN END + +// RAVEN BEGIN +/* +=============================================================================== + +rvCameraPortalSky + +=============================================================================== +*/ +// jscott: for portal skies +class rvCameraPortalSky : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPortalSky ); + + rvCameraPortalSky( void ) {} + ~rvCameraPortalSky( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); +}; + +/* +=============================================================================== + +rvCameraPlayback + +=============================================================================== +*/ +class rvCameraPlayback : public idCamera { +public: + CLASS_PROTOTYPE( rvCameraPlayback ); + + rvCameraPlayback( void ) {} + ~rvCameraPlayback( void ) {} + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Spawn( void ); + virtual void GetViewParms( renderView_t *view ); + +private: + int startTime; + const rvDeclPlayback *playback; +}; +// RAVEN END + +#endif /* !__GAME_CAMERA_H__ */ diff --git a/src/mpgame/Effect.cpp b/src/mpgame/Effect.cpp new file mode 100644 index 0000000..f1303cb --- /dev/null +++ b/src/mpgame/Effect.cpp @@ -0,0 +1,487 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Effect.h" +#include "client/ClientEffect.h" + +const idEventDef EV_LookAtTarget( "lookAtTarget", NULL ); +const idEventDef EV_Attenuate( "attenuate", "f" ); + +CLASS_DECLARATION( idEntity, rvEffect ) + EVENT( EV_Activate, rvEffect::Event_Activate ) + EVENT( EV_LookAtTarget, rvEffect::Event_LookAtTarget ) + EVENT( EV_Earthquake, rvEffect::Event_EarthQuake ) + EVENT( EV_Camera_Start, rvEffect::Event_Start ) + EVENT( EV_Camera_Stop, rvEffect::Event_Stop ) + EVENT( EV_Attenuate, rvEffect::Event_Attenuate ) + EVENT( EV_IsActive, rvEffect::Event_IsActive ) +END_CLASS + +/* +================ +rvEffect::rvEffect +================ +*/ +rvEffect::rvEffect ( void ) { + fl.networkSync = true; + loop = false; + lookAtTarget = false; + effect = NULL; + endOrigin.Zero(); +} + +/* +================ +rvEffect::Spawn +================ +*/ +void rvEffect::Spawn( void ) { + const char* fx; + if ( !spawnArgs.GetString ( "fx", "", &fx ) || !*fx ) { + if ( !( gameLocal.editors & EDITOR_FX ) ) { + gameLocal.Warning ( "no effect file specified on effect entity '%s'", name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + } else { + effect = ( const idDecl * )declManager->FindEffect( spawnArgs.GetString ( "fx" ) ); + if( effect->IsImplicit() ) { + common->Warning( "Unknown effect \'%s\' on entity \'%s\'", spawnArgs.GetString ( "fx" ), GetName() ); + } + } + + spawnArgs.GetVector ( "endOrigin", "0 0 0", endOrigin ); + + spawnArgs.GetBool ( "loop", "0", loop ); + + // If look at target is set the effect will continually update itself to look at its target + spawnArgs.GetBool( "lookAtTarget", "0", lookAtTarget ); + + renderEntity.shaderParms[SHADERPARM_ALPHA] = spawnArgs.GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[SHADERPARM_BRIGHTNESS] = spawnArgs.GetFloat ( "_brightness", "1" ); + + if( spawnArgs.GetBool( "start_on", loop ? "1" : "0" ) ) { + ProcessEvent( &EV_Activate, this ); + } +#if 0 + // If anyone ever gets around to a flood fill from the origin rather than the over generous PushVolumeIntoTree bounds, + // this warning will become useful. Until then, it's a bogus warning. + if( gameRenderWorld->PointInArea( GetPhysics()->GetOrigin() ) < 0 ) { + common->Warning( "Effect \'%s\' out of world", name.c_str() ); + } +#endif +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Think( void ) { + + if( clientEntities.IsListEmpty ( ) ) { + BecomeInactive( TH_THINK ); + + // Should the func_fx be removed now? + if( !(gameLocal.editors & EDITOR_FX) && spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + return; + } + else if( lookAtTarget ) { + // If activated and looking at its target then update the target information + ProcessEvent( &EV_LookAtTarget ); + } + + UpdateVisuals(); + Present ( ); +} + +/* +================ +rvEffect::Save +================ +*/ +void rvEffect::Save ( idSaveGame *savefile ) const { + savefile->WriteBool ( loop ); + savefile->WriteBool ( lookAtTarget ); + savefile->WriteString ( effect->GetName() ); + savefile->WriteVec3 ( endOrigin ); + clientEffect.Save ( savefile ); +} + +/* +================ +rvEffect::Restore +================ +*/ +void rvEffect::Restore ( idRestoreGame *savefile ) { + idStr name; + + savefile->ReadBool ( loop ); + savefile->ReadBool ( lookAtTarget ); + savefile->ReadString ( name ); + effect = declManager->FindType( DECL_EFFECT, name ); + savefile->ReadVec3 ( endOrigin ); + clientEffect.Restore ( savefile ); +} + +/* +================ +rvEffect::Think +================ +*/ +void rvEffect::Stop( bool destroyParticles ) { + StopEffect ( effect, destroyParticles ); +} + +/* +================ +rvEffect::Play +================ +*/ +bool rvEffect::Play( void ) { + clientEffect = PlayEffect ( effect, renderEntity.origin, renderEntity.axis, loop, endOrigin ); + if ( clientEffect ) { + + idVec4 color; + color[0] = renderEntity.shaderParms[SHADERPARM_RED]; + color[1] = renderEntity.shaderParms[SHADERPARM_GREEN]; + color[2] = renderEntity.shaderParms[SHADERPARM_BLUE]; + color[3] = renderEntity.shaderParms[SHADERPARM_ALPHA]; + clientEffect->SetColor ( color ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + clientEffect->SetAmbient( true ); + + BecomeActive ( TH_THINK ); + return true; + } + + return false; +} + +/* +================ +rvEffect::Attenuate +================ +*/ +void rvEffect::Attenuate ( float attenuation ) { + rvClientEntity* cent; + for( cent = clientEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { +// RAVEN END + static_cast(cent)->Attenuate ( attenuation ); + } + } +} + +/* +================ +rvEffect::Restart +================ +*/ +void rvEffect::Restart( void ) { + Stop( false ); + + if( loop ) { + Play(); + } +} + +/* +================ +rvEffect::UpdateChangeableSpawnArgs +================ +*/ +void rvEffect::UpdateChangeableSpawnArgs( const idDict *source ) { + const char* fx; + const idDecl *newEffect; + bool newLoop; + + idEntity::UpdateChangeableSpawnArgs(source); + if ( !source ) { + return; + } + + if ( source->GetString ( "fx", "", &fx ) && *fx ) { + newEffect = ( const idDecl * )declManager->FindEffect( fx ); + } else { + newEffect = NULL; + } + + idVec3 color; + source->GetVector( "_color", "1 1 1", color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = source->GetFloat ( "_alpha", "1" ); + renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] = source->GetFloat ( "_brightness", "1" ); + if ( clientEffect ) { + clientEffect->SetColor ( idVec4(color[0],color[1],color[2],renderEntity.shaderParms[ SHADERPARM_ALPHA ]) ); + clientEffect->SetBrightness ( renderEntity.shaderParms[ SHADERPARM_BRIGHTNESS ] ); + } + + source->GetBool ( "loop", "0", newLoop ); + + spawnArgs.Copy( *source ); + + // IF the effect handle has changed or the loop status has changed then restart the effect + if ( newEffect != effect || loop != newLoop ) { + Stop ( false ); + + loop = newLoop; + effect = newEffect; + + if ( effect ) { + Play ( ); + BecomeActive( TH_THINK ); + UpdateVisuals(); + } else { + BecomeInactive ( TH_THINK ); + UpdateVisuals(); + } + } +} + +/* +=============== +rvEffect::ShowEditingDialog +=============== +*/ +void rvEffect::ShowEditingDialog( void ) { + common->InitTool( EDITOR_FX, &spawnArgs ); +} + +/* +================= +rvEffect::WriteToSnapshot +================= +*/ +void rvEffect::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteBits( loop, 1 ); +} + +/* +================= +rvEffect::ReadFromSnapshot +================= +*/ +void rvEffect::ReadFromSnapshot( const idBitMsgDelta &msg ) { + const idDecl *old = effect; + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + loop = ( msg.ReadBits( 1 ) != 0 ); + + if ( effect && !old ) { + // TODO: need to account for when the effect really started + Play(); + } +} + +/* +================= +rvEffect::ClientPredictionThink +================= +*/ +void rvEffect::ClientPredictionThink( void ) { + if ( gameLocal.isNewFrame ) { + Think ( ); + } + RunPhysics(); + Present(); +} + +/* +================ +rvEffect::Event_Start +================ +*/ +void rvEffect::Event_Start ( void ) { + if( !effect || !clientEntities.IsListEmpty ( ) ) { + return; + } + + if( !Play() ) { + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + } else { + gameLocal.Warning( "Unable to play effect '%s'", effect->GetName() ); + } + BecomeInactive ( TH_THINK ); + } + + ProcessEvent( &EV_LookAtTarget ); +} + +/* +================ +rvEffect::Event_Stop +================ +*/ +void rvEffect::Event_Stop ( void ) { + if( !effect ) { + return; + } + + Stop( false ); +} + +/* +================= +rvEffect::Event_Activate +================= +*/ +void rvEffect::Event_Activate( idEntity *activator ) { + // Stop the effect if its already playing + if( !clientEntities.IsListEmpty ( ) ) { + Event_Stop ( ); + } else { + Event_Start ( ); + } + + ActivateTargets( activator ); +} + +/* +================ +rvEffect::Event_LookAtTarget + +Reorients the effect entity towards its target and sets the end origin as well +================ +*/ +void rvEffect::Event_LookAtTarget ( void ) { + const idKeyValue *kv; + idVec3 dir; + + if ( !effect || !clientEffect ) { + return; + } + + kv = spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if( ent ) { + if( !idStr::Icmp( ent->GetEntityDefName(), "target_null" ) ) { + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + + clientEffect->SetEndOrigin ( ent->GetPhysics()->GetOrigin() ); + clientEffect->SetAxis ( dir.ToMat3( ) ); + return; + } + } + kv = spawnArgs.MatchPrefix( "target", kv ); + } +} + +/* +================ +rvEffect::Event_EarthQuake +================ +*/ +void rvEffect::Event_EarthQuake ( float requiresLOS ) { + float quakeChance; + + if ( !spawnArgs.GetFloat("quakeChance", "0", quakeChance) ) { + return; + } + + if ( rvRandom::flrand(0, 1.0f) > quakeChance ) { + // failed its activation roll + return; + } + + if ( requiresLOS ) { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + + // activate this effect now + ProcessEvent ( &EV_Activate, gameLocal.entities[ENTITYNUM_WORLD] ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_Attenuate( float attenuation ) { + Attenuate( attenuation ); +} + +/* +================ +rvEffect::Event_Attenuate +================ +*/ +void rvEffect::Event_IsActive( void ) { + idThread::ReturnFloat( ( !effect || !clientEntities.IsListEmpty() ) ? 0.0f : 1.0f ); +} + +/* +================ +rvEffect::InstanceLeave +================ +*/ +void rvEffect::InstanceLeave( void ) { + idEntity::InstanceLeave(); + Stop( true ); +} + +/* +================ +rvEffect::InstanceJoin +================ +*/ +void rvEffect::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + Restart(); +} diff --git a/src/mpgame/Effect.h b/src/mpgame/Effect.h new file mode 100644 index 0000000..ea74eb5 --- /dev/null +++ b/src/mpgame/Effect.h @@ -0,0 +1,90 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Effect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_EFFECT_H__ +#define __GAME_EFFECT_H__ + +class rvEffect : public idEntity +{ +public: + + CLASS_PROTOTYPE( rvEffect ); + + rvEffect ( void ); + + const bool GetEndOrigin ( idVec3 &result ) const; + void SetEndOrigin ( const idVec3 &origin ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Play ( void ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + void Attenuate ( float attenuation ); + + float GetBrightness ( void ) const; + + bool IsLooping ( void ) { return( loop ); } + + virtual void UpdateChangeableSpawnArgs ( const idDict *source ); + virtual void ShowEditingDialog ( void ); + + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + void ClientPredictionThink ( void ); + virtual void InstanceLeave ( void ); + virtual void InstanceJoin ( void ); + +protected: + + bool loop; + bool lookAtTarget; + const idDecl *effect; + idVec3 endOrigin; + rvClientEntityPtr clientEffect; + +private: + + void Event_Activate ( idEntity *activator ); + void Event_LookAtTarget ( void ); + void Event_EarthQuake ( float requiresLOS ); + + void Event_Start ( void ); + void Event_Stop ( void ); + + void Event_Attenuate ( float attenuation ); + void Event_IsActive ( void ); +}; + +#endif // __GAME_EFFECT_H__ diff --git a/src/mpgame/Entity.cpp b/src/mpgame/Entity.cpp new file mode 100644 index 0000000..c978fbe --- /dev/null +++ b/src/mpgame/Entity.cpp @@ -0,0 +1,6815 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// bdube: client effects +#include "client/ClientEffect.h" +//mcg: need to know team for AddDamageEffects +#include "ai/AI_Manager.h" +// RAVEN END + +/* +=============================================================================== + + idEntity + +=============================================================================== +*/ + +// overridable events +const idEventDef EV_PostSpawn( "", NULL ); +const idEventDef EV_FindTargets( "", NULL ); +const idEventDef EV_Touch( "", "et" ); +const idEventDef EV_GetName( "getName", NULL, 's' ); +const idEventDef EV_SetName( "setName", "s" ); +const idEventDef EV_Activate( "activate", "e" ); +const idEventDef EV_ActivateTargets( "activateTargets", "e" ); +const idEventDef EV_NumTargets( "numTargets", NULL, 'f' ); +const idEventDef EV_GetTarget( "getTarget", "f", 'e' ); +const idEventDef EV_RandomTarget( "randomTarget", "s", 'e' ); +const idEventDef EV_Bind( "bind", "e" ); +const idEventDef EV_BindPosition( "bindPosition", "e" ); +const idEventDef EV_BindToJoint( "bindToJoint", "esf" ); +const idEventDef EV_Unbind( "unbind", NULL ); +const idEventDef EV_RemoveBinds( "removeBinds" ); +const idEventDef EV_SpawnBind( "", NULL ); +const idEventDef EV_SetOwner( "setOwner", "e" ); +const idEventDef EV_SetModel( "setModel", "s" ); +const idEventDef EV_SetSkin( "setSkin", "s" ); +const idEventDef EV_GetWorldOrigin( "getWorldOrigin", NULL, 'v' ); +const idEventDef EV_SetWorldOrigin( "setWorldOrigin", "v" ); +const idEventDef EV_GetOrigin( "getOrigin", NULL, 'v' ); +const idEventDef EV_SetOrigin( "setOrigin", "v" ); +const idEventDef EV_GetAngles( "getAngles", NULL, 'v' ); +const idEventDef EV_SetAngles( "setAngles", "v" ); +const idEventDef EV_GetLinearVelocity( "getLinearVelocity", NULL, 'v' ); +const idEventDef EV_SetLinearVelocity( "setLinearVelocity", "v" ); +const idEventDef EV_GetAngularVelocity( "getAngularVelocity", NULL, 'v' ); +const idEventDef EV_SetAngularVelocity( "setAngularVelocity", "v" ); +const idEventDef EV_GetSize( "getSize", NULL, 'v' ); +const idEventDef EV_SetSize( "setSize", "vv" ); +const idEventDef EV_GetMins( "getMins", NULL, 'v' ); +const idEventDef EV_GetMaxs( "getMaxs", NULL, 'v' ); +const idEventDef EV_IsHidden( "isHidden", NULL, 'd' ); +const idEventDef EV_Hide( "hide", NULL ); +const idEventDef EV_Show( "show", NULL ); +const idEventDef EV_Touches( "touches", "E", 'd' ); +const idEventDef EV_ClearSignal( "clearSignal", "d" ); +const idEventDef EV_GetShaderParm( "getShaderParm", "d", 'f' ); +const idEventDef EV_SetShaderParm( "setShaderParm", "df" ); +const idEventDef EV_SetShaderParms( "setShaderParms", "ffff" ); +const idEventDef EV_SetColor( "setColor", "fff" ); +const idEventDef EV_GetColor( "getColor", NULL, 'v' ); +const idEventDef EV_CacheSoundShader( "cacheSoundShader", "s" ); +const idEventDef EV_StartSoundShader( "startSoundShader", "sd", 'f' ); +const idEventDef EV_StartSound( "startSound", "sdd", 'f' ); +const idEventDef EV_StopSound( "stopSound", "dd" ); +const idEventDef EV_FadeSound( "fadeSound", "dff" ); +const idEventDef EV_SetGuiParm( "setGuiParm", "ss" ); +const idEventDef EV_SetGuiFloat( "setGuiFloat", "sf" ); +const idEventDef EV_GetNextKey( "getNextKey", "ss", 's' ); +const idEventDef EV_SetKey( "setKey", "ss" ); +const idEventDef EV_GetKey( "getKey", "s", 's' ); +const idEventDef EV_GetIntKey( "getIntKey", "s", 'f' ); +const idEventDef EV_GetFloatKey( "getFloatKey", "s", 'f' ); +const idEventDef EV_GetVectorKey( "getVectorKey", "s", 'v' ); +const idEventDef EV_GetEntityKey( "getEntityKey", "s", 'e' ); +const idEventDef EV_RestorePosition( "restorePosition" ); +const idEventDef EV_UpdateCameraTarget( "", NULL ); +const idEventDef EV_DistanceTo( "distanceTo", "E", 'f' ); +const idEventDef EV_DistanceToPoint( "distanceToPoint", "v", 'f' ); +const idEventDef EV_StartFx( "startFx", "s" ); +const idEventDef EV_HasFunction( "hasFunction", "s", 'd' ); +const idEventDef EV_CallFunction( "callFunction", "s" ); +const idEventDef EV_SetNeverDormant( "setNeverDormant", "d" ); + +// RAVEN BEGIN +// bgeisler: go back to default skin +const idEventDef EV_ClearSkin( "clearSkin"); +// kfuller: added events +const idEventDef EV_SetContents( "setContents", "d" ); +const idEventDef EV_GetLastBlocker( "getLastBlocker", NULL, 'e' ); +const idEventDef EV_Earthquake( "earthquake", "f" ); +// we should probably try to integrate this with AI_PlayAnim +const idEventDef EV_PlayAnim("playAnimNoChannel", "s"); +const idEventDef EV_PlayAnimXTimes("playAnimXTimes", "sf"); +// bdube: effect events +const idEventDef EV_PlayEffect( "playEffect", "ssd" ); +const idEventDef EV_StopEffect( "stopEffect", "s" ); +const idEventDef EV_StopAllEffects( "stopAllEffects" ); +const idEventDef EV_GetHealth ( "getHealth", NULL, 'f' ); +// bdube: surface related events +const idEventDef EV_HideSurface( "hideSurface", "s" ); +const idEventDef EV_ShowSurface( "showSurface", "s" ); +// bdube: added gui events +const idEventDef EV_GuiEvent ( "guiEvent", "s" ); +// jscott: for playback button handling +const idEventDef EV_PlaybackCallback( "playbackCallback", "ddd" ); +// nmckenzie: +const idEventDef EV_GetBindMaster( "getBindMaster", NULL, 'e' ); +const idEventDef EV_ApplyImpulse( "applyImpulse", "evv" ); +// abahr: +const idEventDef EV_RemoveNullTargets( "removeNullTargets" ); +const idEventDef EV_IsA( "isA", "s", 'f' ); +const idEventDef EV_IsSameTypeAs( "isSameTypeAs", "e", 'f' ); +const idEventDef EV_MatchPrefix( "matchPrefix", "ss", 's' ); +const idEventDef EV_ClearTargetList( "clearTargetList", "f" ); +// twhitaker: +const idEventDef EV_AppendTarget( "appendTarget", "E", 'f' ); +const idEventDef EV_RemoveTarget( "removeTarget", "e" ); +// mekberg: +const idEventDef EV_SetHealth( "setHealth", "f" ); +// RAVEN END + +ABSTRACT_DECLARATION( idClass, idEntity ) + EVENT( EV_GetName, idEntity::Event_GetName ) + EVENT( EV_SetName, idEntity::Event_SetName ) + EVENT( EV_FindTargets, idEntity::Event_FindTargets ) + EVENT( EV_ActivateTargets, idEntity::Event_ActivateTargets ) + EVENT( EV_NumTargets, idEntity::Event_NumTargets ) + EVENT( EV_GetTarget, idEntity::Event_GetTarget ) + EVENT( EV_RandomTarget, idEntity::Event_RandomTarget ) + EVENT( EV_BindToJoint, idEntity::Event_BindToJoint ) + EVENT( EV_RemoveBinds, idEntity::Event_RemoveBinds ) + EVENT( EV_Bind, idEntity::Event_Bind ) + EVENT( EV_BindPosition, idEntity::Event_BindPosition ) + EVENT( EV_Unbind, idEntity::Event_Unbind ) + EVENT( EV_SpawnBind, idEntity::Event_SpawnBind ) + EVENT( EV_SetOwner, idEntity::Event_SetOwner ) + EVENT( EV_SetModel, idEntity::Event_SetModel ) + EVENT( EV_SetSkin, idEntity::Event_SetSkin ) + EVENT( EV_GetShaderParm, idEntity::Event_GetShaderParm ) + EVENT( EV_SetShaderParm, idEntity::Event_SetShaderParm ) + EVENT( EV_SetShaderParms, idEntity::Event_SetShaderParms ) + EVENT( EV_SetColor, idEntity::Event_SetColor ) + EVENT( EV_GetColor, idEntity::Event_GetColor ) + EVENT( EV_IsHidden, idEntity::Event_IsHidden ) + EVENT( EV_Hide, idEntity::Event_Hide ) + EVENT( EV_Show, idEntity::Event_Show ) + EVENT( EV_CacheSoundShader, idEntity::Event_CacheSoundShader ) + EVENT( EV_StartSoundShader, idEntity::Event_StartSoundShader ) + EVENT( EV_StartSound, idEntity::Event_StartSound ) + EVENT( EV_StopSound, idEntity::Event_StopSound ) + EVENT( EV_FadeSound, idEntity::Event_FadeSound ) + EVENT( EV_GetWorldOrigin, idEntity::Event_GetWorldOrigin ) + EVENT( EV_SetWorldOrigin, idEntity::Event_SetWorldOrigin ) + EVENT( EV_GetOrigin, idEntity::Event_GetOrigin ) + EVENT( EV_SetOrigin, idEntity::Event_SetOrigin ) + EVENT( EV_GetAngles, idEntity::Event_GetAngles ) + EVENT( EV_SetAngles, idEntity::Event_SetAngles ) + EVENT( EV_GetLinearVelocity, idEntity::Event_GetLinearVelocity ) + EVENT( EV_SetLinearVelocity, idEntity::Event_SetLinearVelocity ) + EVENT( EV_GetAngularVelocity, idEntity::Event_GetAngularVelocity ) + EVENT( EV_SetAngularVelocity, idEntity::Event_SetAngularVelocity ) + EVENT( EV_GetSize, idEntity::Event_GetSize ) + EVENT( EV_SetSize, idEntity::Event_SetSize ) + EVENT( EV_GetMins, idEntity::Event_GetMins) + EVENT( EV_GetMaxs, idEntity::Event_GetMaxs ) + EVENT( EV_Touches, idEntity::Event_Touches ) + EVENT( EV_SetGuiParm, idEntity::Event_SetGuiParm ) + EVENT( EV_SetGuiFloat, idEntity::Event_SetGuiFloat ) + EVENT( EV_GetNextKey, idEntity::Event_GetNextKey ) + EVENT( EV_SetKey, idEntity::Event_SetKey ) + EVENT( EV_GetKey, idEntity::Event_GetKey ) + EVENT( EV_GetIntKey, idEntity::Event_GetIntKey ) + EVENT( EV_GetFloatKey, idEntity::Event_GetFloatKey ) + EVENT( EV_GetVectorKey, idEntity::Event_GetVectorKey ) + EVENT( EV_GetEntityKey, idEntity::Event_GetEntityKey ) + EVENT( EV_RestorePosition, idEntity::Event_RestorePosition ) + EVENT( EV_UpdateCameraTarget, idEntity::Event_UpdateCameraTarget ) + EVENT( EV_DistanceTo, idEntity::Event_DistanceTo ) + EVENT( EV_DistanceToPoint, idEntity::Event_DistanceToPoint ) + EVENT( EV_StartFx, idEntity::Event_StartFx ) + EVENT( EV_Thread_WaitFrame, idEntity::Event_WaitFrame ) + EVENT( EV_Thread_Wait, idEntity::Event_Wait ) + EVENT( EV_HasFunction, idEntity::Event_HasFunction ) + EVENT( EV_CallFunction, idEntity::Event_CallFunction ) + EVENT( EV_SetNeverDormant, idEntity::Event_SetNeverDormant ) + +// RAVEN BEGIN +// bgeisler: go back to default skin + EVENT( EV_ClearSkin, idEntity::Event_ClearSkin ) +// kfuller: added events + EVENT( EV_SetContents, idEntity::Event_SetContents ) + EVENT( EV_GetLastBlocker, idEntity::Event_GetLastBlocker) +// bdube: effect events + EVENT( EV_PlayEffect, idEntity::Event_PlayEffect ) + EVENT( EV_StopEffect, idEntity::Event_StopEffect ) + EVENT( EV_StopAllEffects, idEntity::Event_StopAllEffects ) + EVENT( EV_GetHealth, idEntity::Event_GetHealth ) +// bdube: mesh events + EVENT( EV_HideSurface, idEntity::Event_HideSurface ) + EVENT( EV_ShowSurface, idEntity::Event_ShowSurface ) +// bdube: gui events + EVENT( EV_GuiEvent, idEntity::Event_GuiEvent ) +// jscott: playback callback + EVENT( EV_PlaybackCallback, idEntity::Event_PlaybackCallback ) +// nmckenzie: Check who we're bound to. + EVENT( EV_GetBindMaster, idEntity::Event_GetBindMaster ) + EVENT( EV_ApplyImpulse, idEntity::Event_ApplyImpulse ) +// abahr: so we can call this from script + EVENT( EV_RemoveNullTargets, idEntity::Event_RemoveNullTargets ) + EVENT( EV_IsA, idEntity::Event_IsA ) + EVENT( EV_IsSameTypeAs, idEntity::Event_IsSameTypeAs ) + EVENT( EV_MatchPrefix, idEntity::Event_MatchPrefix ) + EVENT( EV_ClearTargetList, idEntity::Event_ClearTargetList ) +// twhitaker: to dynamically add/remove targets in script + EVENT( EV_AppendTarget, idEntity::Event_AppendTarget ) + EVENT( EV_RemoveTarget, idEntity::Event_RemoveTarget ) +// mekberg: added + EVENT( EV_SetHealth, idEntity::Event_SetHealth ) +// RAVEN END +END_CLASS + +/* +================ +UpdateGuiParms +================ +*/ +void UpdateGuiParms( idUserInterface *gui, const idDict *args ) { + if ( gui == NULL || args == NULL ) { + return; + } + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + while( kv ) { + gui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = args->MatchPrefix( "gui_parm", kv ); + } + gui->SetStateBool( "noninteractive", args->GetBool( "gui_noninteractive" ) ) ; + gui->StateChanged( gameLocal.time ); +} + +/* +================ +AddRenderGui +================ +*/ +void AddRenderGui( const char *name, idUserInterface **gui, const idDict *args ) { + + const idKeyValue *kv = args->MatchPrefix( "gui_parm", NULL ); + *gui = uiManager->FindGui( name, true, ( kv != NULL ) || args->GetBool( "gui_noninteractive" ) ); + UpdateGuiParms( *gui, args ); +} + +/* +================ +idGameEdit::ParseSpawnArgsToRenderEntity + +parse the static model parameters +this is the canonical renderEntity parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRenderEntity( const idDict *args, renderEntity_t *renderEntity ) { + int i; + const char *temp; + idVec3 color; + float angle; + const idDeclModelDef *modelDef; + + memset( renderEntity, 0, sizeof( *renderEntity ) ); + + temp = args->GetString( "model" ); + + modelDef = NULL; + if ( temp[0] != '\0' ) { + if ( !strstr( temp, "." ) ) { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, temp, false ) ); + if ( modelDef ) { + renderEntity->hModel = modelDef->ModelHandle(); + if ( renderEntity->hModel && !renderEntity->hModel->IsLoaded() ) { + renderEntity->hModel->LoadModel(); + } + } + } + + if ( !renderEntity->hModel ) { + renderEntity->hModel = renderModelManager->FindModel( temp ); + } + } + if ( renderEntity->hModel ) { + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + } else { + renderEntity->bounds.Zero(); + } + + temp = args->GetString( "skin" ); + if ( temp[0] != '\0' ) { + renderEntity->customSkin = declManager->FindSkin( temp ); + } else if ( modelDef ) { + renderEntity->customSkin = modelDef->GetDefaultSkin(); + } + + temp = args->GetString( "shader" ); + if ( temp[0] != '\0' ) { + renderEntity->customShader = declManager->FindMaterial( temp ); + } + + args->GetVector( "origin", "0 0 0", renderEntity->origin ); + + // get the rotation matrix in either full form, or single angle form + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", renderEntity->axis ) ) { + angle = args->GetFloat( "angle" ); +// RAVEN BEGIN +// abahr: allowing up and down buttons to affect orientation + if( angle == -1.0f ) { + renderEntity->axis = idAngles( -90.0f, 0.0f, 0.0f ).ToMat3(); + } else if( angle == -2.0f ) { + renderEntity->axis = idAngles( 90.0f, 0.0f, 0.0f ).ToMat3(); + } else +// RAVEN END + if ( angle != 0.0f ) { + renderEntity->axis = idAngles( 0.0f, angle, 0.0f ).ToMat3(); + } else { + renderEntity->axis.Identity(); + } + } + +// RAVEN BEGIN + renderEntity->referenceSoundHandle = -1; +// RAVEN END + + // get shader parms + args->GetVector( "_color", "1 1 1", color ); + renderEntity->shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity->shaderParms[ 3 ] = args->GetFloat( "shaderParm3", "1" ); + renderEntity->shaderParms[ 4 ] = args->GetFloat( "shaderParm4", "0" ); + renderEntity->shaderParms[ 5 ] = args->GetFloat( "shaderParm5", "0" ); + renderEntity->shaderParms[ 6 ] = args->GetFloat( "shaderParm6", "0" ); + renderEntity->shaderParms[ 7 ] = args->GetFloat( "shaderParm7", "0" ); + renderEntity->shaderParms[ 8 ] = args->GetFloat( "shaderParm8", "0" ); + renderEntity->shaderParms[ 9 ] = args->GetFloat( "shaderParm9", "0" ); + renderEntity->shaderParms[ 10 ] = args->GetFloat( "shaderParm10", "0" ); + renderEntity->shaderParms[ 11 ] = args->GetFloat( "shaderParm11", "0" ); + + // check noDynamicInteractions flag + renderEntity->noDynamicInteractions = args->GetBool( "noDynamicInteractions" ); + + // check noshadows flag + renderEntity->noShadow = args->GetBool( "noshadows" ); + + // check noselfshadows flag + renderEntity->noSelfShadow = args->GetBool( "noselfshadows" ); + + // init any guis, including entity-specific states + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + temp = args->GetString( i == 0 ? "gui" : va( "gui%d", i + 1 ) ); + if ( temp[ 0 ] != '\0' ) { + AddRenderGui( temp, &renderEntity->gui[ i ], args ); + } + } +} + +/* +================ +idGameEdit::ParseSpawnArgsToRefSound + +parse the sound parameters +this is the canonical refSound parm parsing, +which should be used by dmap and the editor +================ +*/ +void idGameEdit::ParseSpawnArgsToRefSound( const idDict *args, refSound_t *refSound ) { + const char *temp; + + memset( refSound, 0, sizeof( *refSound ) ); + refSound->referenceSoundHandle = -1; + +// RAVEN BEGIN + refSound->parms.minDistance = args->GetFloat( "s_mindistance" ); + refSound->parms.maxDistance = args->GetFloat( "s_maxdistance" ); + // WARNING: This overrides the volume; it does not modify it + if( args->GetFloat( "s_volume" ) != 0.0f ) { + refSound->parms.volume = idMath::dBToScale( args->GetFloat( "s_volume" ) ); + } + + if( refSound->parms.volume < 0.0f || refSound->parms.volume > 5.0f ) { + common->Warning( "Unreasonable volume (%g) on entity \'%s\'", refSound->parms.volume, args->GetString( "name" ) ); + refSound->parms.volume = 5.0f; + } +// RAVEN END + refSound->parms.shakes = args->GetFloat( "s_shakes" ); + + args->GetVector( "origin", "0 0 0", refSound->origin ); + + // if a diversity is not specified, every sound start will make + // a random one. Specifying diversity is usefull to make multiple + // lights all share the same buzz sound offset, for instance. + refSound->diversity = args->GetFloat( "s_diversity", "-1" ); + refSound->waitfortrigger = args->GetBool( "s_waitfortrigger" ); + + if ( args->GetBool( "s_omni" ) ) { + refSound->parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; + } + if ( args->GetBool( "s_looping" ) ) { + refSound->parms.soundShaderFlags |= SSF_LOOPING; + } + if ( args->GetBool( "s_occlusion" ) ) { + refSound->parms.soundShaderFlags |= SSF_NO_OCCLUSION; + } + if ( args->GetBool( "s_global" ) ) { + refSound->parms.soundShaderFlags |= SSF_GLOBAL; + } + if ( args->GetBool( "s_unclamped" ) ) { + refSound->parms.soundShaderFlags |= SSF_UNCLAMPED; + } + if ( args->GetBool( "s_center" ) ) { + refSound->parms.soundShaderFlags |= SSF_CENTER; + } + + refSound->parms.soundClass = args->GetInt( "s_soundClass" ); + + temp = args->GetString( "s_shader" ); + if ( temp[0] != '\0' ) { + refSound->shader = declManager->FindSound( temp ); + } + +// RAVEN BEGIN + if( refSound->parms.maxDistance < refSound->parms.minDistance ) { + common->Warning( "ParseSpawnArgsToRefSound: Max distance less than min distance for entity \'%s\'", args->GetString( "name", "*unknown*" ) ); + } +// RAVEN END +} + +/* +=============== +idEntity::UpdateChangeableSpawnArgs + +Any key val pair that might change during the course of the game ( via a gui or whatever ) +should be initialize here so a gui or other trigger can change something and have it updated +properly. An optional source may be provided if the values reside in an outside dictionary and +first need copied over to spawnArgs +=============== +*/ +void idEntity::UpdateChangeableSpawnArgs( const idDict *source ) { + int i; + const char *target; + + if ( !source ) { + source = &spawnArgs; + } + cameraTarget = NULL; + target = source->GetString( "cameraTarget" ); + if ( target && target[0] ) { +// RAVEN BEGIN +// bdube: EV_UpdateCameraTarget pulls from spawnargs so we need to move the target over + spawnArgs.Set ( "cameraTarget", target ); +// RAVEN END + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], source ); + } +} + +/* +================ +idEntity::idEntity +================ +*/ +idEntity::idEntity() { + + entityNumber = ENTITYNUM_NONE; + entityDefNumber = -1; + + spawnNode.SetOwner( this ); + activeNode.SetOwner( this ); + + snapshotNode.SetOwner( this ); + snapshotSequence = -1; + snapshotBits = 0; + + thinkFlags = 0; + dormantStart = 0; + cinematic = false; + renderView = NULL; + cameraTarget = NULL; + health = 0; + + physics = NULL; + bindMaster = NULL; + bindJoint = INVALID_JOINT; + bindBody = -1; + teamMaster = NULL; + teamChain = NULL; + signals = NULL; + + memset( PVSAreas, 0, sizeof( PVSAreas ) ); + numPVSAreas = -1; + + memset( &fl, 0, sizeof( fl ) ); + fl.neverDormant = true; // most entities never go dormant + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + modelDefHandle = -1; + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + + mpGUIState = -1; + +// RAVEN BEGIN +// rjohnson: added this to persist long thinking entities + mLastLongThinkTime = 0; + mLastLongThinkColor.Zero(); +// ddynerman: instance, clipworld + SetInstance( 0 ); + SetClipWorld( 0 ); + fl.persistAcrossInstances = false; +// twhitaker + forwardDamageEnt = NULL; +// ddynerman: optional preprediction + predictTime = 0; +// RAVEN END +} + +/* +================ +idEntity::Spawn +================ +*/ +void idEntity::Spawn( void ) { + int i; + const char *temp; + idVec3 origin; + idMat3 axis; + const idKeyValue *networkSync; + const char *classname; + const char *scriptObjectName; + + gameLocal.RegisterEntity( this ); + +// bdube: make sure there is a classname before trying to use it + if ( spawnArgs.GetString( "classname", NULL, &classname ) ) { + const idDeclEntityDef *def = gameLocal.FindEntityDef( classname, false ); + if ( def ) { + entityDefNumber = def->Index(); + } + } + + // Persona is a set of keys that augment an entity giving it its own custom persona + const idDict* dict; + dict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_persona", "" ), false ); + if ( dict ) { + spawnArgs.Copy ( *dict ); + } +// RAVEN END + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + +// RAVEN BEGIN +// bdube: added hidesurface + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } +// RAVEN END + + renderEntity.entityNum = entityNumber; + +// RAVEN BEGIN +// ddynerman: LOD code + renderEntity.shadowLODDistance = spawnArgs.GetFloat( "shadow_lod_distance", "768.0" ); + renderEntity.shadowLODDistance *= renderEntity.shadowLODDistance; +// ddynerman: multiple clip worlds + int spawnInstance = spawnArgs.GetInt( "instance" ); + SetInstance( spawnInstance ); +// RAVEN END + + // go dormant within 5 frames so that when the map starts most monsters are dormant + dormantStart = gameLocal.time - DELAY_DORMANT_TIME + gameLocal.msec * 5; + + origin = renderEntity.origin; + axis = renderEntity.axis; + + // do the audio parsing the same way dmap and the editor do + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + // only play SCHANNEL_PRIVATE when sndworld->PlaceListener() is called with this listenerId + // don't spatialize sounds from the same entity + refSound.listenerId = entityNumber + 1; + + cameraTarget = NULL; + temp = spawnArgs.GetString( "cameraTarget" ); + if ( temp && temp[0] ) { + // update the camera taget + PostEventMS( &EV_UpdateCameraTarget, 0 ); + } + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + UpdateGuiParms( renderEntity.gui[ i ], &spawnArgs ); + } + + fl.solidForTeam = spawnArgs.GetBool( "solidForTeam", "0" ); +// RAVEN BEGIN +// bdube: usable + fl.usable = spawnArgs.GetBool ( "usable", "0" ); +// RAVEN END + + fl.neverDormant = spawnArgs.GetBool( "neverDormant", "0" ); + fl.hidden = spawnArgs.GetBool( "hide", "0" ); + if ( fl.hidden ) { + // make sure we're hidden, since a spawn function might not set it up right + PostEventMS( &EV_Hide, 0 ); + } + cinematic = spawnArgs.GetBool( "cinematic", "0" ); + + networkSync = spawnArgs.FindKey( "networkSync" ); + if ( networkSync ) { + fl.networkSync = ( atoi( networkSync->GetValue() ) != 0 ); + } + + // every object will have a unique name + temp = spawnArgs.GetString( "name", va( "%s_%s_%d", GetClassname(), spawnArgs.GetString( "classname" ), entityNumber ) ); + SetName( temp ); + + // if we have targets, wait until all entities are spawned to get them + if ( spawnArgs.MatchPrefix( "target" ) || spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); + + temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + + if ( spawnArgs.GetString( "bind", "", &temp ) ) { + PostEventMS( &EV_SpawnBind, 0 ); + } + + // auto-start a sound on the entity + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + // setup script object + if ( ShouldConstructScriptObjectAtSpawn() && spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + ConstructScriptObject(); + } + +// RAVEN BEGIN + fl.persistAcrossInstances = false; +// bgeisler: added + fl.triggerAnim = spawnArgs.GetBool( "trigger_anim" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +// RAVEN END +} + +/* +================ +idEntity::~idEntity +================ +*/ +idEntity::~idEntity( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + if ( thinkFlags ) { + BecomeInactive( thinkFlags ); + } + activeNode.Remove(); + + Signal( SIG_REMOVED ); + + // we have to set back the default physics object before unbinding because the entity + // specific physics object might be an entity variable and as such could already be destroyed. + SetPhysics( NULL ); + + // remove any entities that are bound to me + RemoveBinds(); + + // unbind from master + Unbind(); + QuitTeam(); + + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + + delete renderView; + renderView = NULL; + + delete signals; + signals = NULL; + +// RAVEN BEGIN +// bdube: make sure all sounds and attached effects are stopped + StopSound( SCHANNEL_ANY, false ); + + RemoveClientEntities(); +// RAVEN END + + FreeModelDef(); + FreeSoundEmitter( false ); + + gameLocal.UnregisterEntity( this ); +} + +/* +================ +idEntity::Save +================ +*/ +void idEntity::Save( idSaveGame *savefile ) const { + int i, j; + rvClientEntity* cent; + + savefile->WriteInt( entityNumber ); + savefile->WriteInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->WriteInt( snapshotSequence ); + savefile->WriteInt( snapshotBits ); + + savefile->WriteString( name ); + savefile->WriteDict( &spawnArgs ); + scriptObject.Save( savefile ); + + savefile->WriteInt( thinkFlags ); + savefile->WriteInt( dormantStart ); + savefile->WriteBool( cinematic ); + + // renderView_t * renderView; + + savefile->WriteObject( cameraTarget ); + + savefile->WriteInt( targets.Num() ); + for( i = 0; i < targets.Num(); i++ ) { + targets[ i ].Save( savefile ); + } + + savefile->WriteInt( health ); + + savefile->WriteInt( clientEntities.Num() ); + for( cent = clientEntities.Next(); cent; cent = cent->bindNode.Next() ) { + savefile->WriteObject( cent ); + } + +// savefile->WriteInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->WriteVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( modelDefHandle ); + savefile->WriteRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper save + forwardDamageEnt.Save ( savefile ); +// RAVEN END + + savefile->WriteStaticObject( defaultPhysicsObj ); + + savefile->WriteObject( bindMaster.GetEntity() ); + savefile->WriteJoint( bindJoint ); + savefile->WriteInt( bindBody ); + savefile->WriteObject( teamMaster ); + savefile->WriteObject( teamChain ); + + savefile->WriteInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->WriteInt( PVSAreas[ i ] ); + } + + if ( !signals ) { + savefile->WriteBool( false ); + } else { + savefile->WriteBool( true ); + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->WriteInt( signals->signal[ i ].Num() ); + for( j = 0; j < signals->signal[ i ].Num(); j++ ) { + savefile->WriteInt( signals->signal[ i ][ j ].threadnum ); + savefile->WriteString( signals->signal[ i ][ j ].function->Name() ); + } + } + } + + savefile->WriteInt( mpGUIState ); + + savefile->WriteInt( instance ); + savefile->WriteInt( clipWorld ); +} + +/* +================ +idEntity::Restore +================ +*/ +void idEntity::Restore( idRestoreGame *savefile ) { + int i, j; + int num; + rvClientEntity *temp; + idStr funcname; + + savefile->ReadInt( entityNumber ); + savefile->ReadInt( entityDefNumber ); + + // spawnNode and activeNode are restored by gameLocal + + // idLinkList snapshotNode; + + savefile->ReadInt( snapshotSequence ); + savefile->ReadInt( snapshotBits ); + + savefile->ReadString( name ); + SetName( name ); + savefile->ReadDict( &spawnArgs ); + + scriptObject.Restore( savefile ); + + savefile->ReadInt( thinkFlags ); + savefile->ReadInt( dormantStart ); + savefile->ReadBool( cinematic ); + + // renderView_t * renderView; + + savefile->ReadObject( reinterpret_cast( cameraTarget ) ); + + targets.Clear(); + savefile->ReadInt( num ); + targets.SetNum( num ); + for( i = 0; i < num; i++ ) { + targets[ i ].Restore( savefile ); + } + + savefile->ReadInt( health ); + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadObject( reinterpret_cast( temp ) ); + if( temp ) { + temp->bindNode.AddToEnd( clientEntities ); + } + } + +// savefile->ReadInt( mLastLongThinkTime ); // Debug vars - don't save +// savefile->ReadVec4( mLastLongThinkColor ); // Debug vars - don't save + + savefile->Read( &fl, sizeof( fl ) ); + +// RAVEN BEGIN + savefile->ReadRenderEntity( renderEntity, &spawnArgs ); +// RAVEN END + savefile->ReadInt( modelDefHandle ); + savefile->ReadRefSound( refSound ); + +// RAVEN BEGIN +// mekberg: proper restore + forwardDamageEnt.Restore ( savefile ); +// RAVEN END + + savefile->ReadStaticObject( defaultPhysicsObj ); + RestorePhysics( &defaultPhysicsObj ); + + idEntity *templol = 0; + savefile->ReadObject( reinterpret_cast( templol ) ); + bindMaster = templol; + + savefile->ReadJoint( bindJoint ); + savefile->ReadInt( bindBody ); + savefile->ReadObject( reinterpret_cast( teamMaster ) ); + savefile->ReadObject( reinterpret_cast( teamChain ) ); + + savefile->ReadInt( numPVSAreas ); + for( i = 0; i < MAX_PVS_AREAS; i++ ) { + savefile->ReadInt( PVSAreas[ i ] ); + } + + bool readsignals; + savefile->ReadBool( readsignals ); + if ( readsignals ) { + signals = new signalList_t; + for( i = 0; i < NUM_SIGNALS; i++ ) { + savefile->ReadInt( num ); + signals->signal[ i ].SetNum( num ); + for( j = 0; j < num; j++ ) { + savefile->ReadInt( signals->signal[ i ][ j ].threadnum ); + savefile->ReadString( funcname ); + signals->signal[ i ][ j ].function = gameLocal.program.FindFunction( funcname ); + if ( !signals->signal[ i ][ j ].function ) { + savefile->Error( "Function '%s' not found", funcname.c_str() ); + } + } + } + } + + savefile->ReadInt( mpGUIState ); + + // restore must retrieve modelDefHandle from the renderer + if ( modelDefHandle != -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + savefile->ReadInt( instance ); + savefile->ReadInt( clipWorld ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_crush", false, false ); +} + +/* +================ +idEntity::GetEntityDefName +================ +*/ +const char * idEntity::GetEntityDefName( void ) const { + if ( entityDefNumber < 0 ) { + return "*unknown*"; + } + return declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); +} + +/* +================ +idEntity::SetName +================ +*/ +void idEntity::SetName( const char *newname ) { + if ( name.Length() ) { + gameLocal.RemoveEntityFromHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, NULL ); + } + + name = newname; + if ( name.Length() ) { + if ( ( name == "NULL" ) || ( name == "null_entity" ) ) { + gameLocal.Error( "Cannot name entity '%s'. '%s' is reserved for script.", name.c_str(), name.c_str() ); + } + gameLocal.AddEntityToHash( name.c_str(), this ); + gameLocal.program.SetEntity( name, this ); + } +} + +/* +================ +idEntity::GetName +================ +*/ +const char * idEntity::GetName( void ) const { + return name.c_str(); +} + + +/*********************************************************************** + + Thinking + +***********************************************************************/ + +/* +================ +idEntity::Think +================ +*/ +void idEntity::Think( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::DoDormantTests + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::DoDormantTests( void ) { + // Never go dormant? + if ( fl.neverDormant || (gameLocal.inCinematic && cinematic) ) { + return false; + } + + // if the monster area is not topologically connected to a player + if ( !gameLocal.InPlayerConnectedArea( this ) ) { + return true; + } else { + // the monster area is topologically connected to a player, but if + // the monster hasn't been woken up before, do the more precise PVS check + if ( !fl.hasAwakened ) { + if ( !gameLocal.InPlayerPVS( this ) ) { + return true; + } + } + } + + return false; +} + +/* +================ +idEntity::CheckDormant + +Monsters and other expensive entities that are completely closed +off from the player can skip all of their work +================ +*/ +bool idEntity::CheckDormant( void ) { + bool dormant; + + dormant = DoDormantTests(); + if ( dormant ) { + if ( dormantStart == 0 ) { + dormantStart = gameLocal.time; + } + if ( gameLocal.time - dormantStart < DELAY_DORMANT_TIME ) { + dormant = false; + } + } else { + dormantStart = 0; + fl.hasAwakened = true; + } + + if ( dormant && !fl.isDormant ) { + fl.isDormant = true; + DormantBegin(); + } else if ( !dormant && fl.isDormant ) { + fl.isDormant = false; + DormantEnd(); + } + + return dormant; +} + +/* +================ +idEntity::DormantBegin + +called when entity becomes dormant +================ +*/ +void idEntity::DormantBegin( void ) { +} + +/* +================ +idEntity::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idEntity::DormantEnd( void ) { +} + +/* +================ +idEntity::IsActive +================ +*/ +bool idEntity::IsActive( void ) const { + return activeNode.InList(); +} + +/* +================ +idEntity::BecomeActive +================ +*/ +void idEntity::BecomeActive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // enable the team master if this entity is part of a physics team + if ( teamMaster && teamMaster != this ) { + teamMaster->BecomeActive( TH_PHYSICS ); + } else if ( !( thinkFlags & TH_PHYSICS ) ) { + // if this is a pusher +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Parametric::GetClassType() ) || physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + gameLocal.sortPushers = true; + } +// RAVEN BEGIN +// abahr: +// jnewquist: Use accessor for static class type + if( physics->IsType( rvPhysics_Spline::GetClassType() ) ) { + gameLocal.sortPushers = true; + } +// RAVEN END + } + } + + int oldFlags = thinkFlags; + thinkFlags |= flags; + if ( thinkFlags ) { + if ( !IsActive() ) { + activeNode.AddToEnd( gameLocal.activeEntities ); + } else if ( !oldFlags ) { + // we became inactive this frame, so we have to decrease the count of entities to deactivate + gameLocal.numEntitiesToDeactivate--; + } + } +} + +/* +================ +idEntity::BecomeInactive +================ +*/ +void idEntity::BecomeInactive( int flags ) { + if ( ( flags & TH_PHYSICS ) ) { + // may only disable physics on a team master if no team members are running physics or bound to a joints + if ( teamMaster == this ) { + for ( idEntity *ent = teamMaster->teamChain; ent; ent = ent->teamChain ) { + if ( ( ent->thinkFlags & TH_PHYSICS ) || ( ( ent->bindMaster == this ) && ( ent->bindJoint != INVALID_JOINT ) ) ) { + flags &= ~TH_PHYSICS; + break; + } + } + } + } + + if ( thinkFlags ) { + thinkFlags &= ~flags; + if ( !thinkFlags && IsActive() ) { + gameLocal.numEntitiesToDeactivate++; + } + } + + if ( ( flags & TH_PHYSICS ) ) { + // if this entity has a team master + if ( teamMaster && teamMaster != this ) { + // if the team master is at rest + if ( teamMaster->IsAtRest() ) { + teamMaster->BecomeInactive( TH_PHYSICS ); + } + } + } +} + +/*********************************************************************** + + Visuals + +***********************************************************************/ + +/* +================ +idEntity::SetShaderParm +================ +*/ +void idEntity::SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Warning( "shader parm index (%d) out of range", parmnum ); + return; + } + + renderEntity.shaderParms[ parmnum ] = value; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( float red, float green, float blue ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = red; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = green; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = blue; + UpdateVisuals(); +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec3 &color ) { + SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec3 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idEntity::SetColor +================ +*/ +void idEntity::SetColor( const idVec4 &color ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + UpdateVisuals(); +} + +/* +================ +idEntity::GetColor +================ +*/ +void idEntity::GetColor( idVec4 &out ) const { + out[ 0 ] = renderEntity.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idEntity::UpdateAnimationControllers +================ +*/ +bool idEntity::UpdateAnimationControllers( void ) { + // any ragdoll and IK animation controllers should be updated here + return false; +} + +/* +================ +idEntity::SetModel +================ +*/ +void idEntity::SetModel( const char *modelname ) { + assert( modelname ); + + FreeModelDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetSkin +================ +*/ +void idEntity::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} +// RAVEN BEGIN +// bgeisler: go back to default skin +/* +================ +idEntity::ClearSkin +================ +*/ +void idEntity::ClearSkin( void ) +{ + if ( GetAnimator() && GetAnimator()->ModelDef() ) { + renderEntity.customSkin = GetAnimator()->ModelDef()->GetDefaultSkin(); + } else { + renderEntity.customSkin = NULL; + } + + UpdateVisuals(); +} +// RAVEN END + +/* +================ +idEntity::GetSkin +================ +*/ +const idDeclSkin *idEntity::GetSkin( void ) const { + return renderEntity.customSkin; +} + +/* +================ +idEntity::FreeModelDef +================ +*/ +void idEntity::FreeModelDef( void ) { + if ( modelDefHandle != -1 ) { + gameRenderWorld->FreeEntityDef( modelDefHandle ); + modelDefHandle = -1; + + rvClientEntity* cent; + + for( cent = clientEntities.Next(); cent != NULL; cent = cent->bindNode.Next() ) { + cent->FreeEntityDef(); + } + } +} + +/* +================ +idEntity::FreeLightDef +================ +*/ +void idEntity::FreeLightDef( void ) { +} + +/* +================ +idEntity::IsHidden +================ +*/ +bool idEntity::IsHidden( void ) const { + return fl.hidden; +} + +/* +================ +idEntity::Hide +================ +*/ +void idEntity::Hide( void ) { + if ( !IsHidden() ) { + fl.hidden = true; + FreeModelDef(); + UpdateVisuals(); + } +} + +/* +================ +idEntity::Show +================ +*/ +void idEntity::Show( void ) { + if ( IsHidden() ) { + fl.hidden = false; + UpdateVisuals(); + } +} + +/* +================ +idEntity::UpdateModelTransform +================ +*/ +void idEntity::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = GetPhysics()->GetAxis(); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +idEntity::UpdateModel +================ +*/ +void idEntity::UpdateModel( void ) { + UpdateModelTransform(); + +// RAVEN BEGIN +// abahr: moved GetAnimator call because its invalid when called from a destructor + UpdateRenderEntityCallback(); +// RAVEN END + + // set to invalid number to force an update the next time the PVS areas are retrieved + ClearPVSAreas(); + + // ensure that we call Present this frame + BecomeActive( TH_UPDATEVISUALS ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEntity::UpdateRenderEntityCallback +================ +*/ +void idEntity::UpdateRenderEntityCallback() { +} +// RAVEN END + +/* +================ +idEntity::UpdateVisuals +================ +*/ +void idEntity::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( void ) { + int localNumPVSAreas, localPVSAreas[32]; + idBounds modelAbsBounds; + int i; + + modelAbsBounds.FromTransformedBounds( renderEntity.bounds, renderEntity.origin, renderEntity.axis ); + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( modelAbsBounds, localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + + // FIXME: some particle systems may have huge bounds and end up in many PVS areas + // the first MAX_PVS_AREAS may not be visible to a network client and as a result the particle system may not show up when it should + if ( localNumPVSAreas > MAX_PVS_AREAS ) { + localNumPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( renderEntity.origin ).Expand( 64.0f ), localPVSAreas, sizeof( localPVSAreas ) / sizeof( localPVSAreas[0] ) ); + } + + for ( numPVSAreas = 0; numPVSAreas < MAX_PVS_AREAS && numPVSAreas < localNumPVSAreas; numPVSAreas++ ) { + PVSAreas[numPVSAreas] = localPVSAreas[numPVSAreas]; + } + + for( i = numPVSAreas; i < MAX_PVS_AREAS; i++ ) { + PVSAreas[ i ] = 0; + } +} + +/* +================ +idEntity::UpdatePVSAreas +================ +*/ +void idEntity::UpdatePVSAreas( const idVec3 &pos ) { + int i; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( pos ), PVSAreas, MAX_PVS_AREAS ); + i = numPVSAreas; + while ( i < MAX_PVS_AREAS ) { + PVSAreas[ i++ ] = 0; + } +} + +/* +================ +idEntity::GetNumPVSAreas +================ +*/ +int idEntity::GetNumPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return numPVSAreas; +} + +/* +================ +idEntity::GetPVSAreas +================ +*/ +const int *idEntity::GetPVSAreas( void ) { + if ( numPVSAreas < 0 ) { + UpdatePVSAreas(); + } + return PVSAreas; +} + +/* +================ +idEntity::ClearPVSAreas +================ +*/ +void idEntity::ClearPVSAreas( void ) { + numPVSAreas = -1; +} + +/* +================ +idEntity::PhysicsTeamInPVS + + FIXME: for networking also return true if any of the entity shadows is in the PVS +================ +*/ +bool idEntity::PhysicsTeamInPVS( pvsHandle_t pvsHandle ) { + idEntity *part; + + if ( teamMaster ) { + for ( part = teamMaster; part; part = part->teamChain ) { + if ( gameLocal.pvs.InCurrentPVS( pvsHandle, part->GetPVSAreas(), part->GetNumPVSAreas() ) ) { + return true; + } + } + } else { + return gameLocal.pvs.InCurrentPVS( pvsHandle, GetPVSAreas(), GetNumPVSAreas() ); + } + return false; +} + +/* +============== +idEntity::ProjectOverlay +============== +*/ +void idEntity::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( modelDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( modelDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +idEntity::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idEntity::Present( void ) { + + if ( !gameLocal.isNewFrame ) { + return; + } + + // if there is no handle yet, go ahead and add it, ignoring the last predict frame early out + // if not, that causes next render frame to have a bunch of spurious primitive draws ( r_showPrimitives ) + // ( we suspect this is because TH_UPDATEVISUALS doesn't get cleared? ) + if ( !gameLocal.isLastPredictFrame && modelDefHandle != -1 ) { + return; + } + +// RAVEN BEGIN +// ddynerman: don't render objects not in our instance (only on server) + if ( gameLocal.isServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != GetInstance() ) { + FreeModelDef(); + return; + } +// RAVEN END + + // don't render server demo stuff that's not in our instance + if ( gameLocal.IsServerDemoPlaying() ) { + if ( instance != 0 ) { + FreeModelDef(); + return; + } + } + + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views +// RAVEN BEGIN +// rjohnson: removed PVS check for when func_static's are not starting in your PVS + if ( cameraTarget ) { // && gameLocal.InPlayerPVS( this ) ) { +// RAVEN END + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idEntity::UpdateRenderEntity +================ +*/ +bool idEntity::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +idEntity::ModelCallback + + NOTE: may not change the game state whatsoever! +================ +*/ +bool idEntity::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + idEntity *ent; + + ent = gameLocal.entities[ renderEntity->entityNum ]; + if ( !ent ) { + gameLocal.Error( "idEntity::ModelCallback: callback with NULL game entity '%d'", renderEntity->entityNum ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +idEntity::GetAnimator + +Subclasses will be responsible for allocating animator. +================ +*/ +idAnimator *idEntity::GetAnimator( void ) { + return NULL; +} + +/* +============= +idEntity::GetRenderView + +This is used by remote camera views to look from an entity +============= +*/ +renderView_t *idEntity::GetRenderView( void ) { + if ( !renderView ) { + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + renderView->vieworg = GetPhysics()->GetOrigin(); + renderView->fov_x = 120; + renderView->fov_y = 120; + renderView->viewaxis = GetPhysics()->GetAxis(); + + // copy global shader parms + for( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + + renderView->time = gameLocal.time; + + return renderView; +} + +// RAVEN BEGIN +// bdube: added convienince functions for effects + +/*********************************************************************** + + effects + +***********************************************************************/ + +/* +================ +idEntity::PlayEffect +================ +*/ +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + if ( joint == INVALID_JOINT ) { + return NULL; + } + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteLong( joint ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + msg.WriteBits( loop, 1 ); + msg.WriteBits( predictBit, 1 ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT_JOINT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } + + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if ( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if ( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( originOffset ); + clientEffect->SetAxis( axisOffset ); + clientEffect->Bind( this, joint ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +rvClientEffect* idEntity::PlayEffect( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + idVec3 localOrigin; + idMat3 localAxis; + + if ( !effect || !gameLocal.isNewFrame ) { + return NULL; + } + + if ( entityNumber == ENTITYNUM_WORLD ) { + return gameLocal.PlayEffect( effect, origin, axis, loop, endOrigin, broadcast, predictBit, category, effectTint ); + } + + // Calculate the local origin and axis from the given globals + localOrigin = ( origin - renderEntity.origin ) * renderEntity.axis.Transpose(); + localAxis = axis * renderEntity.axis.Transpose(); + + if ( !gameLocal.isClient && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + idCQuat quat; + + quat = localAxis.ToCQuat(); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( localOrigin.x ); + msg.WriteFloat( localOrigin.y ); + msg.WriteFloat( localOrigin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteBits( loop, 1 ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + ServerSendInstanceEvent( EVENT_PLAYEFFECT, &msg, false, -1 ); + } + +// RAVEN BEGIN +// rjohnson: no effects on dedicated server + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + // no effects on dedicated server + return NULL; + } + + if( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return( NULL ); + } +// ddynerman: a listen server might get this far re: playing effects, don't actually play out of instance effects + if ( gameLocal.isListenServer && gameLocal.GetLocalPlayer() ) { + if ( GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); +// RAVEN END + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( localOrigin ); + clientEffect->SetAxis( localAxis ); + clientEffect->Bind( this ); + clientEffect->SetGravity( gameLocal.GetCurrentGravity( this ) ); + + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +/* +================ +idEntity::StopAllEffects +================ +*/ +void idEntity::StopAllEffects( bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + static_cast( cent )->Stop( destroyParticles ); + } + } +} + +/* +================ +idEntity::StopEffect +================ +*/ +void idEntity::StopEffect( const idDecl *effect, bool destroyParticles ) { + rvClientEntity* cent; + rvClientEntity* next; + + if( !effect ) { + return; + } + + // Build a list of all the effects to stop + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + + // Is this client entity an effect? + if ( !cent->IsType( rvClientEffect::GetClassType() ) ) { + continue; + } + + // Now check to make sure its the specific effect we want to stop + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex() == effect->Index() ) { + clientEffect->Stop( destroyParticles ); + } + } +} + +void idEntity::StopEffect( const char* effectName, bool destroyParticles ) { + StopEffect( gameLocal.GetEffect( spawnArgs, effectName ), destroyParticles ); +} + +// RAVEN END + +/*********************************************************************** + + Sound + +***********************************************************************/ + +/* +================ +idEntity::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idEntity::CanPlayChatterSounds( void ) const { + return true; +} + +/* +================ +idEntity::StartSound +================ +*/ +bool idEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + +#ifdef _DEBUG + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + idStr soundNameStr = soundName; + if ( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } +#endif + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags, broadcast, length ); +} + +/* +================ +idEntity::StartSoundShader +================ +*/ +bool idEntity::StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + float diversity; + int len; + + if ( length ) { + *length = 0; + } + + if ( !shader ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + return true; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + idGameLocal::WriteDecl( msg, shader ); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STARTSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if ( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return false; + } + + // rjohnson: don't play sounds on a dedicated server! + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return false; + } + + // set a random value for diversity unless one was parsed from the entity + if ( refSound.diversity < 0.0f ) { + diversity = gameLocal.random.RandomFloat(); + } else { + diversity = refSound.diversity; + } + +// RAVEN BEGIN + // if we don't have a soundEmitter allocated yet, get one now + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + len = emitter->StartSound( shader, channel, diversity, soundShaderFlags ); + if ( length ) { + *length = len; + } + } +// RAVEN END + + // set reference to the sound for shader synced effects + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + + return true; +} + +/* +================ +idEntity::StopSound +================ +*/ +void idEntity::StopSound( const s_channelType channel, bool broadcast ) { + if ( !gameLocal.isNewFrame ) { + return; + } + + if ( gameLocal.isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( channel ); + ServerSendInstanceEvent( EVENT_STOPSOUNDSHADER, &msg, false, -1 ); + } + + // in MP, don't play sounds from other instances + if ( gameLocal.isMultiplayer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) { + return; + } + +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->StopSound( channel ); + } +// RAVEN END +} + +/* +================ +idEntity::SetSoundVolume + + Must be called before starting a new sound. +================ +*/ +void idEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + +/* +================ +idEntity::UpdateSound +================ +*/ +void idEntity::UpdateSound( void ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { +// RAVEN END + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + +// RAVEN BEGIN + refSound.velocity = GetPhysics()->GetLinearVelocity(); + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); +// RAVEN END + } +} + +/* +================ +idEntity::GetListenerId +================ +*/ +int idEntity::GetListenerId( void ) const { + return refSound.listenerId; +} + +/* +================ +idEntity::GetSoundEmitter +================ +*/ +// RAVEN BEGIN +int idEntity::GetSoundEmitter( void ) const { + return( refSound.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idEntity::FreeSoundEmitter +================ +*/ +void idEntity::FreeSoundEmitter( bool immediate ) { +// RAVEN BEGIN + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, immediate ); + refSound.referenceSoundHandle = -1; +// RAVEN END +} + +// RAVEN BEGIN +// bdube: client entities + +/*********************************************************************** + + client entities + +***********************************************************************/ + +/* +================ +idEntity::RemoveClientEntities +================ +*/ +void idEntity::RemoveClientEntities( void ) { + rvClientEntity* cent; + // Unbinding should remove the node from the list so keep using the head until + // there are no more entities + for( cent = clientEntities.Next(); cent != NULL; cent = clientEntities.Next() ) { + cent->Unbind( ); + delete cent; + } + clientEntities.Clear( ); +} +// RAVEN END + +/*********************************************************************** + + entity binding + +***********************************************************************/ + +/* +================ +idEntity::PreBind +================ +*/ +void idEntity::PreBind( void ) { +} + +/* +================ +idEntity::PostBind +================ +*/ +void idEntity::PostBind( void ) { +} + +/* +================ +idEntity::PreUnbind +================ +*/ +void idEntity::PreUnbind( void ) { +} + +/* +================ +idEntity::PostUnbind +================ +*/ +void idEntity::PostUnbind( void ) { +} + +/* +================ +idEntity::InitBind +================ +*/ +bool idEntity::InitBind( idEntity *master ) { + + if ( master == this ) { + gameLocal.Error( "Tried to bind an object to itself." ); + return false; + } + + if ( this == gameLocal.world ) { + gameLocal.Error( "Tried to bind world to another entity" ); + return false; + } + + // unbind myself from my master + Unbind(); + + // add any bind constraints to an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master && IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->AddBindConstraints(); + } + + if ( !master || master == gameLocal.world ) { + // this can happen in scripts, so safely exit out. + return false; + } + + return true; +} + +/* +================ +idEntity::FinishBind +================ +*/ +void idEntity::FinishBind( void ) { + + // set the master on the physics object + physics->SetMaster( bindMaster, fl.bindOrientated ); + + // We are now separated from our previous team and are either + // an individual, or have a team of our own. Now we can join + // the new bindMaster's team. Bindmaster must be set before + // joining the team, or we will be placed in the wrong position + // on the team. + JoinTeam( bindMaster ); + + // if our bindMaster is enabled during a cinematic, we must be, too +// RAVEN BEGIN +// rjohnson: players should always have cinematic turned on, no matter what + if ( !IsType ( idPlayer::GetClassType() ) ) { + cinematic = bindMaster->cinematic; + } +// RAVEN END + + // make sure the team master is active so that physics get run + teamMaster->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idEntity::Bind + + bind relative to the visual position of the master +================ +*/ +void idEntity::Bind( idEntity *master, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind( ); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, const char *jointname, bool orientated ) { + jointHandle_t jointnum; + idAnimator *masterAnimator; + + if ( !InitBind( master ) ) { + return; + } + + masterAnimator = master->GetAnimator(); + if ( !masterAnimator ) { + gameLocal.Warning( "idEntity::BindToJoint: entity '%s' cannot support skeletal models.", master->GetName() ); + return; + } + + jointnum = masterAnimator->GetJointHandle( jointname ); + if ( jointnum == INVALID_JOINT ) { + gameLocal.Warning( "idEntity::BindToJoint: joint '%s' not found on entity '%s'.", jointname, master->GetName() ); + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToJoint + + bind relative to a joint of the md5 model used by the master +================ +*/ +void idEntity::BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + PreBind(); + + bindJoint = jointnum; + bindBody = -1; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::BindToBody + + bind relative to a collision model used by the physics of the master +================ +*/ +void idEntity::BindToBody( idEntity *master, int bodyId, bool orientated ) { + + if ( !InitBind( master ) ) { + return; + } + + if ( bodyId < 0 ) { + gameLocal.Warning( "idEntity::BindToBody: body '%d' not found.", bodyId ); + } + + PreBind(); + + bindJoint = INVALID_JOINT; + bindBody = bodyId; + bindMaster = master; + fl.bindOrientated = orientated; + + FinishBind(); + + PostBind(); +} + +/* +================ +idEntity::Unbind +================ +*/ +void idEntity::Unbind( void ) { + idEntity * prev; + idEntity * next; + idEntity * last; + idEntity * ent; + + // remove any bind constraints from an articulated figure +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + static_cast(this)->RemoveBindConstraints(); + } + + if ( !bindMaster ) { + return; + } + + if ( !teamMaster ) { + // Teammaster already has been freed + bindMaster = NULL; + return; + } + + PreUnbind(); + + if ( physics ) { + physics->SetMaster( NULL, fl.bindOrientated ); + } + + // We're still part of a team, so that means I have to extricate myself + // and any entities that are bound to me from the old team. + // Find the node previous to me in the team + prev = teamMaster; + for( ent = teamMaster->teamChain; ent && ( ent != this ); ent = ent->teamChain ) { + prev = ent; + } + + assert( ent == this ); // If ent is not pointing to this, then something is very wrong. + + // Find the last node in my team that is bound to me. + // Also find the first node not bound to me, if one exists. + last = this; + for( next = teamChain; next != NULL; next = next->teamChain ) { + if ( !next->IsBoundTo( this ) ) { + break; + } + + // Tell them I'm now the teamMaster + next->teamMaster = this; + last = next; + } + + // disconnect the last member of our team from the old team + last->teamChain = NULL; + + // connect up the previous member of the old team to the node that + // follow the last node bound to me (if one exists). + if ( teamMaster != this ) { + prev->teamChain = next; + if ( !next && ( teamMaster == prev ) ) { + prev->teamMaster = NULL; + } + } else if ( next ) { + // If we were the teamMaster, then the nodes that were not bound to me are now + // a disconnected chain. Make them into their own team. + for( ent = next; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamMaster = next; + } + next->teamMaster = next; + } + + // If we don't have anyone on our team, then clear the team variables. + if ( teamChain ) { + // make myself my own team + teamMaster = this; + } else { + // no longer a team + teamMaster = NULL; + } + + bindJoint = INVALID_JOINT; + bindBody = -1; + bindMaster = NULL; + + PostUnbind(); +} + +/* +================ +idEntity::RemoveBinds +================ +*/ +void idEntity::RemoveBinds( void ) { + idEntity *ent; + idEntity *next; + + for( ent = teamChain; ent != NULL; ent = next ) { + next = ent->teamChain; + if ( ent->bindMaster == this ) { + ent->Unbind(); + ent->PostEventMS( &EV_Remove, 0 ); + next = teamChain; + } + } +} + +/* +================ +idEntity::IsBound +================ +*/ +bool idEntity::IsBound( void ) const { + if ( bindMaster ) { + return true; + } + return false; +} + +/* +================ +idEntity::IsBoundTo +================ +*/ +// RAVEN BEGIN +// abahr: added const so it can be called from const functions +bool idEntity::IsBoundTo( const idEntity *master ) const { +// RAVEN END + idEntity *ent; + + if ( !bindMaster ) { + return false; + } + + for ( ent = bindMaster; ent != NULL; ent = ent->bindMaster ) { + if ( ent == master ) { + return true; + } + } + + return false; +} + +/* +================ +idEntity::GetBindMaster +================ +*/ +idEntity *idEntity::GetBindMaster( void ) const { + return bindMaster; +} + +/* +================ +idEntity::GetBindJoint +================ +*/ +jointHandle_t idEntity::GetBindJoint( void ) const { + return bindJoint; +} + +/* +================ +idEntity::GetBindBody +================ +*/ +int idEntity::GetBindBody( void ) const { + return bindBody; +} + +/* +================ +idEntity::GetTeamMaster +================ +*/ +idEntity *idEntity::GetTeamMaster( void ) const { + return teamMaster; +} + +/* +================ +idEntity::GetNextTeamEntity +================ +*/ +idEntity *idEntity::GetNextTeamEntity( void ) const { + return teamChain; +} + +/* +===================== +idEntity::ConvertLocalToWorldTransform +===================== +*/ +void idEntity::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + UpdateModelTransform(); + + offset = renderEntity.origin + offset * renderEntity.axis; + axis *= renderEntity.axis; +} + +/* +================ +idEntity::GetLocalVector + +Takes a vector in worldspace and transforms it into the parent +object's localspace. + +Note: Does not take origin into acount. Use getLocalCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetLocalVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetLocalCoordinates + +Takes a vector in world coordinates and transforms it into the parent +object's local coordinates. +================ +*/ +idVec3 idEntity::GetLocalCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.ProjectVector( vec - masterOrigin, pos ); + + return pos; +} + +// RAVEN BEGIN +// kfuller: added method + +/* +================ +idEntity::DistanceTo2d +================ +*/ +float idEntity::DistanceTo2d ( const idVec3& pos ) const { + idVec3 pos1; + idVec3 pos2; + pos1 = pos - (pos * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + pos2 = GetPhysics()->GetOrigin ( ); + pos2 = pos2 - (pos2 * GetPhysics()->GetGravityNormal ( )) * GetPhysics()->GetGravityNormal ( ); + return (pos2 - pos1).LengthFast ( ); +} + +/* +================ +idEntity::GetLocalAngles +================ +*/ +void idEntity::GetLocalAngles(idAngles &localAng) +{ + idVec3 localVec = GetPhysics()->GetAxis()[0]; + + GetLocalVector(localVec); + localAng = localVec.ToAngles(); +} +// RAVEN END + +/* +================ +idEntity::GetWorldVector + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. + +Note: Does not take origin into acount. Use getWorldCoordinate to +convert coordinates. +================ +*/ +idVec3 idEntity::GetWorldVector( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + + return pos; +} + +/* +================ +idEntity::GetWorldCoordinates + +Takes a vector in the parent object's local coordinates and transforms +it into world coordinates. +================ +*/ +idVec3 idEntity::GetWorldCoordinates( const idVec3 &vec ) const { + idVec3 pos; + + if ( !bindMaster ) { + return vec; + } + + idVec3 masterOrigin; + idMat3 masterAxis; + + GetMasterPosition( masterOrigin, masterAxis ); + masterAxis.UnprojectVector( vec, pos ); + pos += masterOrigin; + + return pos; +} + +/* +================ +idEntity::GetMasterPosition +================ +*/ +bool idEntity::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + idVec3 localOrigin; + idMat3 localAxis; + idAnimator *masterAnimator; + + if ( bindMaster ) { + // if bound to a joint of an animated model + if ( bindJoint != INVALID_JOINT ) { + masterAnimator = bindMaster->GetAnimator(); + if ( !masterAnimator ) { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } else { + masterAnimator->GetJointTransform( bindJoint, gameLocal.time, masterOrigin, masterAxis ); + masterAxis *= bindMaster->renderEntity.axis; + masterOrigin = bindMaster->renderEntity.origin + masterOrigin * bindMaster->renderEntity.axis; + } + } else if ( bindBody >= 0 && bindMaster->GetPhysics() ) { + masterOrigin = bindMaster->GetPhysics()->GetOrigin( bindBody ); + masterAxis = bindMaster->GetPhysics()->GetAxis( bindBody ); + } else { + masterOrigin = bindMaster->renderEntity.origin; + masterAxis = bindMaster->renderEntity.axis; + } + return true; + } else { + masterOrigin = vec3_origin; + masterAxis = mat3_identity; + return false; + } +} + +// RAVEN BEGIN +// abahr: needed so client get the correct position +/* +================ +idEntity::GetPosition +================ +*/ +void idEntity::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = renderEntity.origin; + axis = renderEntity.axis; +} +// RAVEN END + +/* +================ +idEntity::GetWorldVelocities +================ +*/ +void idEntity::GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const { + + linearVelocity = physics->GetLinearVelocity(); + angularVelocity = physics->GetAngularVelocity(); + + if ( bindMaster ) { + idVec3 masterOrigin, masterLinearVelocity, masterAngularVelocity; + idMat3 masterAxis; + + // get position of master + GetMasterPosition( masterOrigin, masterAxis ); + + // get master velocities + bindMaster->GetWorldVelocities( masterLinearVelocity, masterAngularVelocity ); + + // linear velocity relative to master plus master linear and angular velocity + linearVelocity = linearVelocity * masterAxis + masterLinearVelocity + + masterAngularVelocity.Cross( GetPhysics()->GetOrigin() - masterOrigin ); + } +} + +/* +================ +idEntity::JoinTeam +================ +*/ +void idEntity::JoinTeam( idEntity *teammember ) { + idEntity *ent; + idEntity *master; + idEntity *prev; + idEntity *next; + + // if we're already on a team, quit it so we can join this one + if ( teamMaster && ( teamMaster != this ) ) { + QuitTeam(); + } + + assert( teammember ); + + if ( teammember == this ) { + teamMaster = this; + return; + } + + // check if our new team mate is already on a team + master = teammember->teamMaster; + if ( !master ) { + // he's not on a team, so he's the new teamMaster + master = teammember; + teammember->teamMaster = teammember; + teammember->teamChain = this; + + // make anyone who's bound to me part of the new team + for( ent = teamChain; ent != NULL; ent = ent->teamChain ) { + ent->teamMaster = master; + } + } else { + // skip past the chain members bound to the entity we're teaming up with + prev = teammember; + next = teammember->teamChain; + if ( bindMaster ) { + // if we have a bindMaster, join after any entities bound to the entity + // we're joining + while( next && next->IsBoundTo( teammember ) ) { + prev = next; + next = next->teamChain; + } + } else { + // if we're not bound to someone, then put us at the end of the team + while( next ) { + prev = next; + next = next->teamChain; + } + } + + // make anyone who's bound to me part of the new team and + // also find the last member of my team + for( ent = this; ent->teamChain != NULL; ent = ent->teamChain ) { + ent->teamChain->teamMaster = master; + } + + prev->teamChain = this; + ent->teamChain = next; + } + + teamMaster = master; + + // reorder the active entity list + gameLocal.sortTeamMasters = true; +} + +/* +================ +idEntity::QuitTeam +================ +*/ +void idEntity::QuitTeam( void ) { + idEntity *ent; + + if ( !teamMaster ) { + return; + } + + // check if I'm the teamMaster + if ( teamMaster == this ) { + // do we have more than one teammate? + if ( !teamChain->teamChain ) { + // no, break up the team + teamChain->teamMaster = NULL; + } else { + // yes, so make the first teammate the teamMaster + for( ent = teamChain; ent; ent = ent->teamChain ) { + ent->teamMaster = teamChain; + } + } + } else { + assert( teamMaster ); + assert( teamMaster->teamChain ); + + // find the previous member of the teamChain + ent = teamMaster; + while( ent->teamChain != this ) { + assert( ent->teamChain ); // this should never happen + ent = ent->teamChain; + } + + // remove this from the teamChain + ent->teamChain = teamChain; + + // if no one is left on the team, break it up + if ( !teamMaster->teamChain ) { + teamMaster->teamMaster = NULL; + } + } + + teamMaster = NULL; + teamChain = NULL; +} + +/*********************************************************************** + + Physics. + +***********************************************************************/ + +/* +================ +idEntity::InitDefaultPhysics +================ +*/ +void idEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( temp ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on entity '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on entity '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); +// RAVEN BEGIN +// bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); +// RAVEN END + } else { + trm.SetupBox( bounds ); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( trm ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { +// RAVEN BEGIN +// jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( this ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; +} + +/* +================ +idEntity::SetPhysics +================ +*/ +void idEntity::SetPhysics( idPhysics *phys ) { + // clear any contacts the current physics object has + if ( physics ) { + physics->ClearContacts(); + } + // set new physics object or set the default physics if NULL + if ( phys != NULL ) { + defaultPhysicsObj.SetClipModel( NULL, 1.0f ); + physics = phys; + physics->Activate(); + } else { + physics = &defaultPhysicsObj; + } + physics->UpdateTime( gameLocal.time ); + physics->SetMaster( bindMaster, fl.bindOrientated ); +} + +/* +================ +idEntity::RestorePhysics +================ +*/ +void idEntity::RestorePhysics( idPhysics *phys ) { + assert( phys != NULL ); + // restore physics pointer + physics = phys; +} + +/* +================ +idEntity::RunPhysics +================ +*/ +bool idEntity::RunPhysics( void ) { + int i, reachedTime, startTime, endTime; + idEntity * part, *blockedPart, *blockingEntity = NULL; + trace_t results; + bool moved; + + moved = false; + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PHYSICS); +// RAVEN END + + // don't run physics if not enabled + if ( !( thinkFlags & TH_PHYSICS ) ) { + // however do update any animation controllers + if ( UpdateAnimationControllers() ) { + BecomeActive( TH_ANIMATE ); + } +// RAVEN BEGIN +// kfuller: we want to be able to debug draw the bbox regardless + physics->DebugDraw(); +// RAVEN END + return false; + } + + // if this entity is a team slave don't do anything because the team master will handle everything + if ( teamMaster && teamMaster != this ) { + return false; + } + + startTime = gameLocal.previousTime; + endTime = gameLocal.time; + + gameLocal.push.InitSavingPushedEntityPositions(); + blockedPart = NULL; + + // save the physics state of the whole team and disable the team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->DisableClip(); + } + part->physics->SaveState(); + } + } + + // move the whole team + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + // run physics +// RAVEN BEGIN +// ddynerman: optional pre-prediction + moved = part->physics->Evaluate( endTime - startTime + part->predictTime, endTime ); + part->predictTime = 0; +// RAVEN END + + // check if the object is blocked + blockingEntity = part->physics->GetBlockingEntity(); + if ( blockingEntity ) { + blockedPart = part; + break; + } + + // if moved or forced to update the visual position and orientation from the physics + if ( moved || part->fl.forcePhysicsUpdate ) { + part->UpdateFromPhysics( false ); + } + + // update any animation controllers here so an entity bound + // to a joint of this entity gets the correct position + if ( part->UpdateAnimationControllers() ) { + part->BecomeActive( TH_ANIMATE ); + } + } + } + + // enable the whole team for collision detection + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + if ( !part->fl.solidForTeam ) { + part->physics->EnableClip(); + } + } + } + + // cdr: Obstacle Avoidance + if (ai_useRVMasterMove.GetBool() && moved && fl.isAIObstacle) { + AI_EntityMoved(this); + } + + // if one of the team entities is a pusher and blocked + if ( blockedPart ) { + // move the parts back to the previous position + for ( part = this; part != blockedPart; part = part->teamChain ) { + + if ( part->physics ) { + + // restore the physics state + part->physics->RestoreState(); + + // move back the visual position and orientation + part->UpdateFromPhysics( true ); + } + } + for ( part = this; part != NULL; part = part->teamChain ) { + if ( part->physics ) { + // update the physics time without moving + part->physics->UpdateTime( endTime ); + } + } + + // restore the positions of any pushed entities + gameLocal.push.RestorePushedEntityPositions(); + + if ( gameLocal.isClient ) { + return false; + } + + // if the master pusher has a "blocked" function, call it + Signal( SIG_BLOCKED ); + ProcessEvent( &EV_TeamBlocked, blockedPart, blockingEntity ); + // call the blocked function on the blocked part + blockedPart->ProcessEvent( &EV_PartBlocked, blockingEntity ); + return false; + } + + // set pushed + for ( i = 0; i < gameLocal.push.GetNumPushedEntities(); i++ ) { + idEntity *ent = gameLocal.push.GetPushedEntity( i ); + ent->physics->SetPushed( endTime - startTime ); + } + + if ( gameLocal.isClient ) { + return true; + } + + // post reached event if the current time is at or past the end point of the motion + for ( part = this; part != NULL; part = part->teamChain ) { + + if ( part->physics ) { + + reachedTime = part->physics->GetLinearEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedPos ); + } + reachedTime = part->physics->GetAngularEndTime(); + if ( startTime < reachedTime && endTime >= reachedTime ) { + part->ProcessEvent( &EV_ReachedAng ); + } + } + } + + return true; +} + +/* +================ +idEntity::UpdateFromPhysics +================ +*/ +void idEntity::UpdateFromPhysics( bool moveBack ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( IsType( idActor::GetClassType() ) ) { +// RAVEN END + idActor *actor = static_cast( this ); + + // set master delta angles for actors + if ( GetBindMaster() ) { + idAngles delta = actor->GetDeltaViewAngles(); + if ( moveBack ) { + delta.yaw -= static_cast(physics)->GetMasterDeltaYaw(); + } else { + delta.yaw += static_cast(physics)->GetMasterDeltaYaw(); + } + actor->SetDeltaViewAngles( delta ); + } + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetOrigin +================ +*/ +void idEntity::SetOrigin( const idVec3 &org ) { + + GetPhysics()->SetOrigin( org ); + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAxis +================ +*/ +void idEntity::SetAxis( const idMat3 &axis ) { + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + static_cast(this)->viewAxis = axis; + } else { + GetPhysics()->SetAxis( axis ); + } + + UpdateVisuals(); +} + +/* +================ +idEntity::SetAngles +================ +*/ +void idEntity::SetAngles( const idAngles &ang ) { + SetAxis( ang.ToMat3() ); +} + +/* +================ +idEntity::GetFloorPos +================ +*/ +bool idEntity::GetFloorPos( float max_dist, idVec3 &floorpos ) const { + trace_t result; + + if ( !GetPhysics()->HasGroundContacts() ) { + GetPhysics()->ClipTranslation( result, GetPhysics()->GetGravityNormal() * max_dist, NULL ); + if ( result.fraction < 1.0f ) { + floorpos = result.endpos; + return true; + } else { + floorpos = GetPhysics()->GetOrigin(); + return false; + } + } else { + floorpos = GetPhysics()->GetOrigin(); + return true; + } +} + +/* +================ +idEntity::GetPhysicsToVisualTransform +================ +*/ +bool idEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +idEntity::GetPhysicsToSoundTransform +================ +*/ +bool idEntity::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + // by default play the sound at the center of the bounding box of the first clip model + if ( GetPhysics()->GetNumClipModels() > 0 ) { + origin = GetPhysics()->GetBounds().GetCenter(); + axis.Identity(); + return true; + } + return false; +} + +/* +================ +idEntity::Collide +================ +*/ +bool idEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + // this entity collides with collision.c.entityNum + return false; +} + +/* +================ +idEntity::GetImpactInfo +================ +*/ +void idEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +idEntity::ApplyImpulse +================ +*/ +void idEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( SkipImpulse(ent, id) ) { + return; + } + + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +idEntity::AddForce +================ +*/ +void idEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +idEntity::ActivatePhysics +================ +*/ +void idEntity::ActivatePhysics( idEntity *ent ) { + GetPhysics()->Activate(); +} + +/* +================ +idEntity::IsAtRest +================ +*/ +bool idEntity::IsAtRest( void ) const { + return GetPhysics()->IsAtRest(); +} + +/* +================ +idEntity::GetRestStartTime +================ +*/ +int idEntity::GetRestStartTime( void ) const { + return GetPhysics()->GetRestStartTime(); +} + +/* +================ +idEntity::AddContactEntity +================ +*/ +void idEntity::AddContactEntity( idEntity *ent ) { + GetPhysics()->AddContactEntity( ent ); +} + +/* +================ +idEntity::RemoveContactEntity +================ +*/ +void idEntity::RemoveContactEntity( idEntity *ent ) { +// RAVEN BEGIN + if( GetPhysics() ) { + + GetPhysics()->RemoveContactEntity( ent ); + } +// RAVEN END +} + + + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +============ +idEntity::CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +// RAVEN BEGIN +// bdube: added ignore entity +bool idEntity::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt ) const { +// RAVEN END + idVec3 dest; + trace_t tr; + idVec3 midpoint; + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin at 0,0,0 + midpoint = ( GetPhysics()->GetAbsBounds()[0] + GetPhysics()->GetAbsBounds()[1] ) * 0.5; + + dest = midpoint; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + // this should probably check in the plane of projection, rather than in world coordinate + dest = midpoint; + dest[0] += 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN ENE + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] += 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] += 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[0] -= 15.0; + dest[1] -= 15.0; +// RAVEN BEGIN +// bdube: added ignore entity + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, ignoreEnt ); +// RAVEN EN + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] += 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + dest = midpoint; + dest[2] -= 15.0; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.TracePoint( this, tr, origin, dest, MASK_SOLID, NULL ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == this ) ) { + damagePoint = tr.endpos; + return true; + } + + return false; +} + +/* +================ +idEntity::DamageFeedback + +callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. +================ +*/ +void idEntity::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + // implemented in subclasses +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted + +inflictor, attacker, dir, and point can be NULL for environmental effects + +============ +*/ +void idEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + return; + } + + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + if ( damage ) { + // do the damage + //jshepard: this is kinda important, no? + health -= damage; + + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } +} + +/* +============ +idEntity::SkipImpulse +============ +*/ +// RAVEN BEGIN +// abahr: push stuff +bool idEntity::SkipImpulse( idEntity *ent, int id ) { + return false;//ent == this; +} + +/* +============ +idEntity::ApplyImpulse +============ +*/ +void idEntity::ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ) { + ApplyImpulse( ent, id, point, dir * damageDef->GetFloat("push", "5000") ); +} +// RAVEN END + +/* +================ +idEntity::AddDamageEffect +================ +*/ +void idEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + const char *sound, *decal, *key; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); +// RAVEN BEGIN +// bdube: impact_blood is now in the damage def + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { +// RAVEN END + return; + } + + const char *materialType = gameLocal.sufaceTypeNames[ collision.c.material->GetSurfaceType() ]; + + // start impact sound based on material type + key = va( "snd_%s", materialType ); + sound = spawnArgs.GetString( key ); + if ( *sound == '\0' ) { + sound = def->dict.GetString( key ); + } + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + } + + if ( g_decals.GetBool() ) { + // place a wound overlay on the model + key = va( "mtr_wound_%s", materialType ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + if ( *decal == '\0' ) { + decal = def->dict.RandomPrefix( key, gameLocal.random ); + } + if ( *decal != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + } + } +} + +/* +================ +idEntity::CanPlayImpactEffect +================ +*/ +bool idEntity::CanPlayImpactEffect ( idEntity* owner, idEntity* ent ) { + if( gameLocal.isMultiplayer ) { + if( gameLocal.IsTeamGame() && !cvarSystem->GetCVarBool("si_teamDamage") && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && ((idPlayer*)owner)->team == ((idPlayer*)ent)->team ) { + return false; + } + + // default to blood + return true; + } else { + idActor* actorOwner; + idAI* aiEnt; + actorOwner = dynamic_cast( owner ); + + if ( ent->IsType ( idAFAttachment::GetClassType ( ) ) ) { + aiEnt = dynamic_cast( static_cast( ent )->GetBody ( ) ); + } else { + aiEnt = dynamic_cast( ent ); + } + + if ( !actorOwner || !aiEnt ) { + return true; + } + + return (actorOwner->team != aiEnt->team); + } +} + +/* +============ +idEntity::Pain + +Called whenever an entity recieves damage. Returns whether the entity responds to the pain. +This is a virtual function that subclasses are expected to implement. +============ +*/ +bool idEntity::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + return false; +} + +/* +============ +idEntity::Killed + +Called whenever an entity's health is reduced to 0 or less. +This is a virtual function that subclasses are expected to implement. +============ +*/ +void idEntity::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +} + +/*********************************************************************** + + Script functions + +***********************************************************************/ + +/* +================ +idEntity::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idEntity::ShouldConstructScriptObjectAtSpawn( void ) const { + return true; +} + +/* +================ +idEntity::ConstructScriptObject + +Called during idEntity::Spawn. Calls the constructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +================ +*/ +idThread *idEntity::ConstructScriptObject( void ) { + idThread *thread; + const function_t *constructor; + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, constructor, true ); + thread->DelayedStart( 0 ); + } else { + thread = NULL; + } + + // clear out the object's memory + scriptObject.ClearObject(); + + return thread; +} + +/* +================ +idEntity::DeconstructScriptObject + +Called during idEntity::~idEntity. Calls the destructor on the script object. +Can be overridden by subclasses when a thread doesn't need to be allocated. +Not called during idGameLocal::MapShutdown. +================ +*/ +void idEntity::DeconstructScriptObject( void ) { + idThread *thread; + const function_t *destructor; + + // don't bother calling the script object's destructor on map shutdown + if ( gameLocal.GameState() == GAMESTATE_SHUTDOWN ) { + return; + } + + // call script object's destructor + destructor = scriptObject.GetDestructor(); + if ( destructor ) { + // start a thread that will run immediately and be destroyed +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_TEMPORARY); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->SetThreadName( name.c_str() ); + thread->CallFunction( this, destructor, true ); + thread->Execute(); + delete thread; + } +} + +/* +================ +idEntity::HasSignal +================ +*/ +bool idEntity::HasSignal( signalNum_t signalnum ) const { + if ( !signals ) { + return false; + } + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + return ( signals->signal[ signalnum ].Num() > 0 ); +} + +/* +================ +idEntity::SetSignal +================ +*/ +void idEntity::SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ) { + int i; + int num; + signal_t sig; + int threadnum; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + signals = new signalList_t; + } + + assert( thread ); + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ][ i ].function = function; + return; + } + } + + if ( num >= MAX_SIGNAL_THREADS ) { + thread->Error( "Exceeded maximum number of signals per object" ); + } + + sig.threadnum = threadnum; + sig.function = function; + signals->signal[ signalnum ].Append( sig ); +} + +/* +================ +idEntity::ClearSignal +================ +*/ +void idEntity::ClearSignal( idThread *thread, signalNum_t signalnum ) { + assert( thread ); + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + signals->signal[ signalnum ].Clear(); +} + +/* +================ +idEntity::ClearSignalThread +================ +*/ +void idEntity::ClearSignalThread( signalNum_t signalnum, idThread *thread ) { + int i; + int num; + int threadnum; + + assert( thread ); + + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + threadnum = thread->GetThreadNum(); + + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + if ( signals->signal[ signalnum ][ i ].threadnum == threadnum ) { + signals->signal[ signalnum ].RemoveIndex( i ); + return; + } + } +} + +/* +================ +idEntity::Signal +================ +*/ +void idEntity::Signal( signalNum_t signalnum ) { + int i; + int num; + signal_t sigs[ MAX_SIGNAL_THREADS ]; + idThread *thread; + + assert( ( signalnum >= 0 ) && ( signalnum < NUM_SIGNALS ) ); + + if ( !signals ) { + return; + } + + // we copy the signal list since each thread has the potential + // to end any of the threads in the list. By copying the list + // we don't have to worry about the list changing as we're + // processing it. + num = signals->signal[ signalnum ].Num(); + for( i = 0; i < num; i++ ) { + sigs[ i ] = signals->signal[ signalnum ][ i ]; + } + + // clear out the signal list so that we don't get into an infinite loop + signals->signal[ signalnum ].Clear(); + + for( i = 0; i < num; i++ ) { + thread = idThread::GetThread( sigs[ i ].threadnum ); + if ( thread ) { + thread->CallFunction( this, sigs[ i ].function, true ); + thread->Execute(); + } + } +} + +/* +================ +idEntity::SignalEvent +================ +*/ +void idEntity::SignalEvent( idThread *thread, signalNum_t signalnum ) { + if ( ( signalnum < 0 ) || ( signalnum >= NUM_SIGNALS ) ) { + gameLocal.Error( "Signal out of range" ); + } + + if ( !signals ) { + return; + } + + Signal( signalnum ); +} + +/*********************************************************************** + + Guis. + +***********************************************************************/ + + +/* +================ +idEntity::TriggerGuis +================ +*/ +void idEntity::TriggerGuis( void ) { + int i; + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->Trigger( gameLocal.time ); + } + } +} + +/* +================ +idEntity::HandleGuiCommands +================ +*/ +bool idEntity::HandleGuiCommands( idEntity *entityGui, const char *cmds ) { + idEntity *targetEnt; + bool ret = false; + if ( entityGui && cmds && *cmds ) { + idLexer src; + idToken token, token2, token3, token4; + src.LoadMemory( cmds, strlen( cmds ), "guiCommands" ); + while( 1 ) { + + if ( !src.ReadToken( &token ) ) { + return ret; + } + + if ( token == ";" ) { + continue; + } + + if ( token.Icmp( "activate" ) == 0 ) { + bool targets = true; + if ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + } else { + targets = false; + } + } + + if ( targets ) { + entityGui->ActivateTargets( this ); + } else { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + } + } + + entityGui->renderEntity.shaderParms[ SHADERPARM_MODE ] = 1.0f; + continue; + } + + + if ( token.Icmp( "runScript" ) == 0 ) { + if ( src.ReadToken( &token2 ) ) { + while( src.CheckTokenString( "::" ) ) { + idToken token3; + if ( !src.ReadToken( &token3 ) ) { + gameLocal.Error( "Expecting function name following '::' in gui for entity '%s'", entityGui->name.c_str() ); + } + token2 += "::" + token3; + } + } +// RAVEN BEGIN +// abahr: allow parms to be passed in +// For some reason the semi colon is used as a delimeter so we need the above code + rvScriptFuncUtility utility; + if( utility.Init(token2) > SFU_ERROR ) { + utility.InsertEntity( entityGui, 0 ); + utility.CallFunc( &entityGui->spawnArgs ); + } +// RAVEN END + continue; + } + + if ( token.Icmp("play") == 0 ) { + if ( src.ReadToken( &token2 ) ) { + const idSoundShader *shader = declManager->FindSound(token2); + entityGui->StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + continue; + } + + if ( token.Icmp( "setkeyval" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) && src.ReadToken( &token4 ) ) { + idEntity *ent = gameLocal.FindEntity( token2 ); + if ( ent ) { + ent->spawnArgs.Set( token3, token4 ); + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + } + } + continue; + } + + if ( token.Icmp( "setshaderparm" ) == 0 ) { + if ( src.ReadToken( &token2 ) && src.ReadToken(&token3) ) { + entityGui->SetShaderParm( atoi( token2 ), atof( token3 ) ); + entityGui->UpdateVisuals(); + } + continue; + } + + if ( token.Icmp("close") == 0 ) { + ret = true; + continue; + } + + // handy for debugging GUI stuff + if ( !token.Icmp( "print" ) ) { + idStr msg; + while ( src.ReadToken( &token2 ) ) { + if ( token2 == ";" ) { + src.UnreadToken( &token2 ); + break; + } + msg += token2.c_str(); + } + common->Printf( "ent gui 0x%x '%s': %s\n", entityNumber, name.c_str(), msg.c_str() ); + continue; + } + + // if we get to this point we don't know how to handle it + src.UnreadToken(&token); + if ( !HandleSingleGuiCommand( entityGui, &src ) ) { + // not handled there see if entity or any of its targets can handle it + // this will only work for one target atm + if ( entityGui->HandleSingleGuiCommand( entityGui, &src ) ) { + continue; + } + + int c = entityGui->targets.Num(); + int i; + for ( i = 0; i < c; i++) { + targetEnt = entityGui->targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->HandleSingleGuiCommand( entityGui, &src ) ) { + break; + } + } + + if ( i == c ) { + // not handled + common->DPrintf( "idEntity::HandleGuiCommands: '%s' not handled\n", token.c_str() ); + src.ReadToken( &token ); + } + } + + } + } + return ret; +} + +/* +================ +idEntity::HandleSingleGuiCommand +================ +*/ +bool idEntity::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + return false; +} + +/*********************************************************************** + + Targets + +***********************************************************************/ + +/* +=============== +idEntity::FindTargets + +We have to wait until all entities are spawned +Used to build lists of targets after the entity is spawned. Since not all entities +have been spawned when the entity is created at map load time, we have to wait +=============== +*/ +void idEntity::FindTargets( void ) { + int i; + + // targets can be a list of multiple names + gameLocal.GetTargets( spawnArgs, targets, "target" ); + + // ensure that we don't target ourselves since that could cause an infinite loop when activating entities + for( i = 0; i < targets.Num(); i++ ) { + if ( targets[ i ].GetEntity() == this ) { + gameLocal.Error( "Entity '%s' is targeting itself", name.c_str() ); + } + } +} + +/* +================ +idEntity::RemoveNullTargets +================ +*/ +void idEntity::RemoveNullTargets( void ) { + int i; + + for( i = targets.Num() - 1; i >= 0; i-- ) { + if ( !targets[ i ].GetEntity() ) { + targets.RemoveIndex( i ); + } + } +} + +/* +============================== +idEntity::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idEntity::ActivateTargets( idEntity *activator ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->renderEntity.gui[ j ] ) { + ent->renderEntity.gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +// RAVEN BEGIN +// twhitaker: added (meant to be used from script) +/* +================ +idEntity::AppendTarget +================ +*/ +int idEntity::AppendTarget( idEntity *appendMe ) { + + int index = -1; + // silently fail if they pass in null + if ( appendMe ) { + index = targets.Append( appendMe ); + RemoveNullTargets(); + } + return index; +} +/* +================ +idEntity::RemoveTarget +================ +*/ +void idEntity::RemoveTarget( idEntity *removeMe ) { + + targets.Remove( removeMe ); + RemoveNullTargets(); +} +/* +================ +idEntity::RemoveTargets +================ +*/ +void idEntity::RemoveTargets( bool destroyContents ) { + if( destroyContents ) { + targets.RemoveContents( true ); + } else { + targets.Clear(); + } +} + +// jshepard: added +/* +================ +idEntity::UnbindTargets +================ +*/ +void idEntity::UnbindTargets( idEntity *activator ) const { + idEntity *ent; + int i; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + ent->Unbind(); + + } +} + +// bdube: added +/* +================ +idEntity::Event_SetContents +================ +*/ +void idEntity::Event_SetContents( int contents ) { + GetPhysics()->SetContents( contents ); +} + +/* +================ +idEntity::Event_GetLastBlocker +================ +*/ +void idEntity::Event_GetLastBlocker(idThread *thread) { + int whichEntNum = GetLastBlocker(); + + if (whichEntNum < 0 || whichEntNum == ENTITYNUM_WORLD) { + thread->ReturnEntity(this); + return; + } + thread->ReturnEntity(gameLocal.entities[whichEntNum]); +} + +/* +================ +idEntity::ShowSurface +================ +*/ +void idEntity::ShowSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask &= (~renderEntity.hModel->GetSurfaceMask ( surface )); +} + +/* +================ +idEntity::Event_ShowSurface +================ +*/ +void idEntity::Event_ShowSurface ( const char* surface ) { + ShowSurface ( surface ); +} + +/* +================ +idEntity::HideSurface +================ +*/ +void idEntity::HideSurface ( const char* surface ) { + if ( !renderEntity.hModel || !surface || !*surface ) { + return; + } + + renderEntity.suppressSurfaceMask |= renderEntity.hModel->GetSurfaceMask ( surface ) ; +} + +/* +================ +idEntity::Event_HideSurface +================ +*/ +void idEntity::Event_HideSurface ( const char* surface ) { + HideSurface ( surface ); +} + +/* +================ +idEntity::Event_GuiEvent +================ +*/ +void idEntity::Event_GuiEvent ( const char* eventName ) { + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( eventName ); + } +} + +/* +================ +idEntity::Event_clearSkin +================ +*/ +void idEntity::Event_ClearSkin( void ) { + ClearSkin(); +} + +/* +================ +idEntity::Event_StopAllEffects +================ +*/ +void idEntity::Event_StopAllEffects ( void ) { + StopAllEffects ( ); +} + +/* +================ +idEntity::Event_GetHealth +================ +*/ +void idEntity::Event_GetHealth ( void ) { + idThread::ReturnFloat( health ); +} + +// jscott: +/* +================ +idEntity::Event_PlaybackCallback +================ +*/ +void idEntity::Event_PlaybackCallback ( int type, int changed, int impulse ) { + common->Printf( "Playback callback type %d - %d/%d\n", type, changed, impulse ); +} + +// nmckenzie: Check who we're bound to. +/* +================ +idEntity::Event_GetBindMaster +================ +*/ + +void idEntity::Event_GetBindMaster ( void ) { + idThread::ReturnEntity( GetBindMaster() ); +} + +/* +================ +idEntity::Event_ApplyImpulse +================ +*/ + +void idEntity::Event_ApplyImpulse( idEntity *source, const idVec3 &point, const idVec3 &impulse ){ + ApplyImpulse( source, 0, point, impulse ); +} + +/* +================ +idEntity::Event_PlayEffect +================ +*/ +void idEntity::Event_PlayEffect( const char *effectName, const char* jointName, bool loop ) { + jointHandle_t joint; + joint = GetAnimator ( ) ? GetAnimator()->GetJointHandle ( jointName ) : INVALID_JOINT; + if ( joint != INVALID_JOINT ) { + PlayEffect ( effectName, joint, loop ); + } else { + PlayEffect ( effectName, renderEntity.origin, renderEntity.axis, loop ); + } +} + +/* +================ +idEntity::Event_StopEffect +================ +*/ +void idEntity::Event_StopEffect( const char *effectName ) { + StopEffect ( effectName ); +} + +// END RAVEN + +/*********************************************************************** + + Misc. + +***********************************************************************/ + +/* +================ +idEntity::Teleport +================ +*/ +void idEntity::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( angles.ToMat3() ); + + UpdateVisuals(); +} + +/* +============ +idEntity::TouchTriggers + + Activate all trigger entities touched at the current position. + + Optionally only activate triggers of ownerType +============ +*/ +bool idEntity::TouchTriggers( const idTypeInfo* ownerType ) const { + int i, numClipModels, numEntities; + idClipModel * cm; + idClipModel * clipModels[ MAX_GENTITIES ]; + idEntity * ent; + trace_t trace; + + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = GetPhysics()->GetOrigin(); + trace.endAxis = GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, GetPhysics()->GetAbsBounds(), CONTENTS_TRIGGER, clipModels, MAX_GENTITIES ); +// RAVEN END + numEntities = 0; + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModels[ i ]; + + // don't touch it if we're the owner + if ( cm->GetOwner() == this ) { + continue; + } + + ent = cm->GetEntity(); + + if ( !ent->RespondsTo( EV_Touch ) && !ent->HasSignal( SIG_TOUCH ) ) { + continue; + } + + if( ownerType && !ent->IsType( *ownerType ) ) { + continue; + } + +// RAVEN BEGIN +// abahr: needed so tram car can has collision model and touch triggers + bool useSimpleClip = spawnArgs.GetBool("useSimpleTriggerClip"); + if ( !useSimpleClip && !GetPhysics()->ClipContents( cm ) ) { +// RAVEN END + continue; + } + + numEntities++; + + trace.c.contents = cm->GetContents(); + trace.c.entityNum = cm->GetEntity()->entityNumber; + trace.c.id = cm->GetId(); + + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, this, &trace ); + + if ( !gameLocal.entities[ entityNumber ] ) { + gameLocal.Printf( "entity was removed while touching triggers\n" ); + return true; + } + } + + return ( numEntities != 0 ); +} + +/* +================ +idEntity::GetSpline +================ +*/ +idCurve_Spline *idEntity::GetSpline( void ) const { + int i, numPoints, t; + const idKeyValue *kv; + idLexer lex; + idVec3 v; + idCurve_Spline *spline; + const char *curveTag = "curve_"; + + kv = spawnArgs.MatchPrefix( curveTag ); + if ( !kv ) { + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + idStr str = kv->GetKey().Right( kv->GetKey().Length() - strlen( curveTag ) ); + if ( str.Icmp( "CatmullRomSpline" ) == 0 ) { + spline = new idCurve_CatmullRomSpline(); + } else if ( str.Icmp( "nubs" ) == 0 ) { + spline = new idCurve_NonUniformBSpline(); + } else if ( str.Icmp( "nurbs" ) == 0 ) { + spline = new idCurve_NURBS(); + } else { + spline = new idCurve_BSpline(); + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + spline->SetBoundaryType( idCurve_Spline::BT_CLAMPED ); + + lex.LoadMemory( kv->GetValue(), kv->GetValue().Length(), curveTag ); + numPoints = lex.ParseInt(); + lex.ExpectTokenString( "(" ); + for ( t = i = 0; i < numPoints; i++, t += 100 ) { + v.x = lex.ParseFloat(); + v.y = lex.ParseFloat(); + v.z = lex.ParseFloat(); + spline->AddValue( t, v ); + } + lex.ExpectTokenString( ")" ); + + return spline; +} + +/* +=============== +idEntity::ShowEditingDialog +=============== +*/ +void idEntity::ShowEditingDialog( void ) { +} + +/*********************************************************************** + + Events + +***********************************************************************/ + +/* +================ +idEntity::Event_GetName +================ +*/ +void idEntity::Event_GetName( void ) { + idThread::ReturnString( name.c_str() ); +} + +/* +================ +idEntity::Event_SetName +================ +*/ +void idEntity::Event_SetName( const char *newname ) { + SetName( newname ); +} + +/* +=============== +idEntity::Event_FindTargets +=============== +*/ +void idEntity::Event_FindTargets( void ) { + FindTargets(); +} + +/* +============ +idEntity::Event_ActivateTargets + +Activates any entities targeted by this entity. Mainly used as an +event to delay activating targets. +============ +*/ +void idEntity::Event_ActivateTargets( idEntity *activator ) { + ActivateTargets( activator ); +} + +// RAVEN BEGIN +// jshepard: added +/* +============ +idEntity::Event_UnbindTargets + +Unbinds all targets of this entity. Useful to make held or clamped items +drop when shot, and for breakable walls. +============ +*/ +void idEntity::Event_UnbindTargets( idEntity *activator ) { + UnbindTargets( activator ); +} + +// RAVEN END + + +/* +================ +idEntity::Event_NumTargets +================ +*/ +void idEntity::Event_NumTargets( void ) { + idThread::ReturnFloat( targets.Num() ); +} + +/* +================ +idEntity::Event_GetTarget +================ +*/ +void idEntity::Event_GetTarget( float index ) { + int i; + + i = ( int )index; + if ( ( i < 0 ) || i >= targets.Num() ) { + idThread::ReturnEntity( NULL ); + } else { + idThread::ReturnEntity( targets[ i ].GetEntity() ); + } +} + +/* +================ +idEntity::Event_RandomTarget +================ +*/ +void idEntity::Event_RandomTarget( const char *ignore ) { + int num; + idEntity *ent; + int i; + int ignoreNum; + + RemoveNullTargets(); + if ( !targets.Num() ) { + idThread::ReturnEntity( NULL ); + return; + } + + ignoreNum = -1; + if ( ignore && ( ignore[ 0 ] != 0 ) && ( targets.Num() > 1 ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ( ent->name == ignore ) ) { + ignoreNum = i; + break; + } + } + } + + if ( ignoreNum >= 0 ) { + num = gameLocal.random.RandomInt( targets.Num() - 1 ); + if ( num >= ignoreNum ) { + num++; + } + } else { + num = gameLocal.random.RandomInt( targets.Num() ); + } + + ent = targets[ num ].GetEntity(); + idThread::ReturnEntity( ent ); +} + +// RAVEN BEGIN +// abahr: so we can call this from script +/* +================ +idEntity::Event_RemoveNullTargets +================ +*/ +void idEntity::Event_RemoveNullTargets() { + RemoveNullTargets(); +} + +// twhitaker: So targets can be added from script +/* +================ +idEntity::Event_AppendTarget +================ +*/ +void idEntity::Event_AppendTarget( idEntity *appendMe ) { + idThread::ReturnFloat( AppendTarget( appendMe ) ); +} + +/* +================ +idEntity::Event_RemoveTarget +================ +*/ +void idEntity::Event_RemoveTarget( idEntity *removeMe ) { + RemoveTarget( removeMe ); +} + +/* +================ +idEntity::Event_ClearTargetList +================ +*/ +void idEntity::Event_ClearTargetList( float destroyContents ) { + RemoveTargets( destroyContents != 0.0f ); +} + +/* +================ +idEntity::Event_MatchPrefix +================ +*/ +void idEntity::Event_MatchPrefix( const char *prefix, const char* previousKey ) { + const idKeyValue* kv = (previousKey[0]) ? spawnArgs.FindKey(previousKey) : NULL; + + kv = spawnArgs.MatchPrefix( prefix, kv ); + if( !kv || !kv->GetValue() ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( kv->GetKey() ); +} + +/* +================ +idEntity::Event_IsA +================ +*/ +void idEntity::Event_IsA( const char* entityDefName ) { + const idDict* dict = gameLocal.FindEntityDefDict( entityDefName ); + if( !dict ) { + idThread::ReturnFloat( false ); + return; + } + + idTypeInfo* info = idClass::GetClass( dict->GetString("spawnclass") ); + if( !info ) { + idThread::ReturnFloat( false ); + return; + } + + idThread::ReturnFloat( IsType(*info) ); +} + +/* +================ +idEntity::Event_IsSameTypeAs +================ +*/ +void idEntity::Event_IsSameTypeAs( const idEntity* ent ) { + assert( ent ); + + idThread::ReturnFloat( IsType(ent->Type) ); +} + +// mekberg: allow sethealth on all entities. +// jshepard: removed clamping +/* +================ +idEntity::Event_SetHealth +================ +*/ +void idEntity::Event_SetHealth( float newHealth ) { + health = newHealth; +} +// RAVEN END + +/* +================ +idEntity::Event_BindToJoint +================ +*/ +void idEntity::Event_BindToJoint( idEntity *master, const char *jointname, float orientated ) { + BindToJoint( master, jointname, ( orientated != 0.0f ) ); +} + +/* +================ +idEntity::Event_RemoveBinds +================ +*/ +void idEntity::Event_RemoveBinds( void ) { + RemoveBinds(); +} + +/* +================ +idEntity::Event_Bind +================ +*/ +void idEntity::Event_Bind( idEntity *master ) { + Bind( master, true ); +} + +/* +================ +idEntity::Event_BindPosition +================ +*/ +void idEntity::Event_BindPosition( idEntity *master ) { + Bind( master, false ); +} + +/* +================ +idEntity::Event_Unbind +================ +*/ +void idEntity::Event_Unbind( void ) { + Unbind(); +} + +/* +================ +idEntity::Event_SpawnBind +================ +*/ +void idEntity::Event_SpawnBind( void ) { + idEntity *parent; + const char *bind, *joint, *bindanim; + jointHandle_t bindJoint; + bool bindOrientated; + int id; + const idAnim *anim; + int animNum; + idAnimator *parentAnimator; + + if ( spawnArgs.GetString( "bind", "", &bind ) ) { + if ( idStr::Icmp( bind, "worldspawn" ) == 0 ) { + //FIXME: Completely unneccessary since the worldspawn is called "world" + parent = gameLocal.world; + } else { + parent = gameLocal.FindEntity( bind ); + } + bindOrientated = spawnArgs.GetBool( "bindOrientated", "1" ); + if ( parent ) { + // bind to a joint of the skeletal model of the parent + if ( spawnArgs.GetString( "bindToJoint", "", &joint ) && *joint ) { + parentAnimator = parent->GetAnimator(); + if ( !parentAnimator ) { + gameLocal.Error( "Cannot bind to joint '%s' on '%s'. Entity does not support skeletal models.", joint, name.c_str() ); + } + bindJoint = parentAnimator->GetJointHandle( joint ); + if ( bindJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for bind on '%s'", joint, name.c_str() ); + } + + // bind it relative to a specific anim + if ( ( parent->spawnArgs.GetString( "bindanim", "", &bindanim ) || parent->spawnArgs.GetString( "anim", "", &bindanim ) ) && *bindanim ) { + animNum = parentAnimator->GetAnim( bindanim ); + if ( !animNum ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + anim = parentAnimator->GetAnim( animNum ); + if ( !anim ) { + gameLocal.Error( "Anim '%s' not found for bind on '%s'", bindanim, name.c_str() ); + } + + // make sure parent's render origin has been set + parent->UpdateModelTransform(); + + //FIXME: need a BindToJoint that accepts a joint position + parentAnimator->CreateFrame( gameLocal.time, true ); + idJointMat *frame = parent->renderEntity.joints; + gameEdit->ANIM_CreateAnimFrame( parentAnimator->ModelHandle(), anim->MD5Anim( 0 ), parent->renderEntity.numJoints, frame, 0, parentAnimator->ModelDef()->GetVisualOffset(), parentAnimator->RemoveOrigin() ); + BindToJoint( parent, joint, bindOrientated ); + parentAnimator->ForceUpdate(); + } else { + BindToJoint( parent, joint, bindOrientated ); + } + } + // bind to a body of the physics object of the parent + else if ( spawnArgs.GetInt( "bindToBody", "0", id ) ) { + BindToBody( parent, id, bindOrientated ); + } + // bind to the parent + else { + Bind( parent, bindOrientated ); + } + } + } +} + +/* +================ +idEntity::Event_SetOwner +================ +*/ +void idEntity::Event_SetOwner( idEntity *owner ) { + int i; + + for ( i = 0; i < GetPhysics()->GetNumClipModels(); i++ ) { + GetPhysics()->GetClipModel( i )->SetOwner( owner ); + } +} + +/* +================ +idEntity::Event_SetModel +================ +*/ +void idEntity::Event_SetModel( const char *modelname ) { + SetModel( modelname ); +} + +/* +================ +idEntity::Event_SetSkin +================ +*/ +void idEntity::Event_SetSkin( const char *skinname ) { + renderEntity.customSkin = declManager->FindSkin( skinname ); + UpdateVisuals(); +} + +/* +================ +idEntity::Event_GetShaderParm +================ +*/ +void idEntity::Event_GetShaderParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderEntity.shaderParms[ parmnum ] ); +} + +/* +================ +idEntity::Event_SetShaderParm +================ +*/ +void idEntity::Event_SetShaderParm( int parmnum, float value ) { + SetShaderParm( parmnum, value ); +} + +/* +================ +idEntity::Event_SetShaderParms +================ +*/ +void idEntity::Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ) { + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + UpdateVisuals(); +} + + +/* +================ +idEntity::Event_SetColor +================ +*/ +void idEntity::Event_SetColor( float red, float green, float blue ) { + SetColor( red, green, blue ); +} + +/* +================ +idEntity::Event_GetColor +================ +*/ +void idEntity::Event_GetColor( void ) { + idVec3 out; + + GetColor( out ); + idThread::ReturnVector( out ); +} + +/* +================ +idEntity::Event_IsHidden +================ +*/ +void idEntity::Event_IsHidden( void ) { + idThread::ReturnInt( fl.hidden ); +} + +/* +================ +idEntity::Event_Hide +================ +*/ +void idEntity::Event_Hide( void ) { + Hide(); +} + +/* +================ +idEntity::Event_Show +================ +*/ +void idEntity::Event_Show( void ) { + Show(); +} + +/* +================ +idEntity::Event_CacheSoundShader +================ +*/ +void idEntity::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idEntity::Event_StartSoundShader +================ +*/ +void idEntity::Event_StartSoundShader( const char *soundName, int channel ) { + int length; + + StartSoundShader( declManager->FindSound( soundName ), (s_channelType)channel, 0, false, &length ); + idThread::ReturnFloat( MS2SEC( length ) ); +} + +/* +================ +idEntity::Event_StopSound +================ +*/ +void idEntity::Event_StopSound( int channel, int netSync ) { + StopSound( channel, ( netSync != 0 ) ); +} + +/* +================ +idEntity::Event_StartSound +================ +*/ +void idEntity::Event_StartSound( const char *soundName, int channel, int netSync ) { + int time; + + StartSound( soundName, ( s_channelType )channel, 0, ( netSync != 0 ), &time ); + idThread::ReturnFloat( MS2SEC( time ) ); +} + +/* +================ +idEntity::Event_FadeSound +================ +*/ +void idEntity::Event_FadeSound( int channel, float to, float over ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + emitter->FadeSound( channel, to, over ); + } +// RAVEN END +} + +/* +================ +idEntity::Event_GetWorldOrigin +================ +*/ +void idEntity::Event_GetWorldOrigin( void ) { + idThread::ReturnVector( GetPhysics()->GetOrigin() ); +} + +/* +================ +idEntity::Event_SetWorldOrigin +================ +*/ +void idEntity::Event_SetWorldOrigin( idVec3 const &org ) { + idVec3 neworg = GetLocalCoordinates( org ); + SetOrigin( neworg ); +} + +/* +================ +idEntity::Event_SetOrigin +================ +*/ +void idEntity::Event_SetOrigin( idVec3 const &org ) { + SetOrigin( org ); +} + +/* +================ +idEntity::Event_GetOrigin +================ +*/ +void idEntity::Event_GetOrigin( void ) { + idThread::ReturnVector( GetLocalCoordinates( GetPhysics()->GetOrigin() ) ); +} + +/* +================ +idEntity::Event_SetAngles +================ +*/ +void idEntity::Event_SetAngles( idAngles const &ang ) { + SetAngles( ang ); +} + +/* +================ +idEntity::Event_GetAngles +================ +*/ +void idEntity::Event_GetAngles( void ) { + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + idThread::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +/* +================ +idEntity::Event_SetLinearVelocity +================ +*/ +void idEntity::Event_SetLinearVelocity( const idVec3 &velocity ) { + GetPhysics()->SetLinearVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetLinearVelocity +================ +*/ +void idEntity::Event_GetLinearVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetLinearVelocity() ); +} + +/* +================ +idEntity::Event_SetAngularVelocity +================ +*/ +void idEntity::Event_SetAngularVelocity( const idVec3 &velocity ) { + GetPhysics()->SetAngularVelocity( velocity ); +} + +/* +================ +idEntity::Event_GetAngularVelocity +================ +*/ +void idEntity::Event_GetAngularVelocity( void ) { + idThread::ReturnVector( GetPhysics()->GetAngularVelocity() ); +} + +/* +================ +idEntity::Event_SetSize +================ +*/ +void idEntity::Event_SetSize( idVec3 const &mins, idVec3 const &maxs ) { + GetPhysics()->SetClipBox( idBounds( mins, maxs ), 1.0f ); +} + +/* +================ +idEntity::Event_GetSize +================ +*/ +void idEntity::Event_GetSize( void ) { + idBounds bounds; + + bounds = GetPhysics()->GetBounds(); + idThread::ReturnVector( bounds[1] - bounds[0] ); +} + +/* +================ +idEntity::Event_GetMins +================ +*/ +void idEntity::Event_GetMins( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[0] ); +} + +/* +================ +idEntity::Event_GetMaxs +================ +*/ +void idEntity::Event_GetMaxs( void ) { + idThread::ReturnVector( GetPhysics()->GetBounds()[1] ); +} + +/* +================ +idEntity::Event_Touches +================ +*/ +void idEntity::Event_Touches( idEntity *ent ) { + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + const idBounds &myBounds = GetPhysics()->GetAbsBounds(); + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + idThread::ReturnInt( myBounds.IntersectsBounds( entBounds ) ); +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiParm( const char *key, const char *val ) { +// RAVEN BEGIN +// mekberg: added + idStr temp = key; + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) ) { + temp.Insert( "gui_", 0 ); + } + spawnArgs.Set( temp.c_str(), val ); +// RAVEN END + + renderEntity.gui[ i ]->SetStateString( key, val ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_SetGuiParm +================ +*/ +void idEntity::Event_SetGuiFloat( const char *key, float f ) { + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( key, va( "%f", f ) ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time ); + } + } +} + +/* +================ +idEntity::Event_GetNextKey +================ +*/ +void idEntity::Event_GetNextKey( const char *prefix, const char *lastMatch ) { + const idKeyValue *kv; + const idKeyValue *previous; + + if ( *lastMatch ) { + previous = spawnArgs.FindKey( lastMatch ); + } else { + previous = NULL; + } + + kv = spawnArgs.MatchPrefix( prefix, previous ); + if ( !kv ) { + idThread::ReturnString( "" ); + } else { + idThread::ReturnString( kv->GetKey() ); + } +} + +/* +================ +idEntity::Event_SetKey +================ +*/ +void idEntity::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +/* +================ +idEntity::Event_GetKey +================ +*/ +void idEntity::Event_GetKey( const char *key ) { + const char *value; + + spawnArgs.GetString( key, "", &value ); + idThread::ReturnString( value ); +} + +/* +================ +idEntity::Event_GetIntKey +================ +*/ +void idEntity::Event_GetIntKey( const char *key ) { + int value; + + spawnArgs.GetInt( key, "0", value ); + + // scripts only support floats + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetFloatKey +================ +*/ +void idEntity::Event_GetFloatKey( const char *key ) { + float value; + + spawnArgs.GetFloat( key, "0", value ); + idThread::ReturnFloat( value ); +} + +/* +================ +idEntity::Event_GetVectorKey +================ +*/ +void idEntity::Event_GetVectorKey( const char *key ) { + idVec3 value; + + spawnArgs.GetVector( key, "0 0 0", value ); + idThread::ReturnVector( value ); +} + +/* +================ +idEntity::Event_GetEntityKey +================ +*/ +void idEntity::Event_GetEntityKey( const char *key ) { + idEntity *ent; + const char *entname; + + if ( !spawnArgs.GetString( key, NULL, &entname ) ) { + idThread::ReturnEntity( NULL ); + return; + } + + ent = gameLocal.FindEntity( entname ); + if ( !ent ) { + gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() ); + } + + idThread::ReturnEntity( ent ); +} + +/* +================ +idEntity::Event_RestorePosition +================ +*/ +void idEntity::Event_RestorePosition( void ) { + idVec3 org; + idAngles angles; + idMat3 axis; + idEntity * part; + + spawnArgs.GetVector( "origin", "0 0 0", org ); + + // get the rotation matrix in either full form, or single angle form + if ( spawnArgs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) { + angles = axis.ToAngles(); + } else { + angles[ 0 ] = 0; + angles[ 1 ] = spawnArgs.GetFloat( "angle" ); + angles[ 2 ] = 0; + } + + Teleport( org, angles, NULL ); + + for ( part = teamChain; part != NULL; part = part->teamChain ) { + if ( part->bindMaster != this ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + if ( static_cast(part->GetPhysics())->IsPusher() ) { + gameLocal.Warning( "teleported '%s' which has the pushing mover '%s' bound to it\n", GetName(), part->GetName() ); + } + } else if ( part->GetPhysics()->IsType( idPhysics_AF::GetClassType() ) ) { +// RAVEN END + gameLocal.Warning( "teleported '%s' which has the articulated figure '%s' bound to it\n", GetName(), part->GetName() ); + } + } +} + +/* +================ +idEntity::Event_UpdateCameraTarget +================ +*/ +void idEntity::Event_UpdateCameraTarget( void ) { + const char *target; + const idKeyValue *kv; + idVec3 dir; + + target = spawnArgs.GetString( "cameraTarget" ); + + cameraTarget = gameLocal.FindEntity( target ); + + if ( cameraTarget ) { + kv = cameraTarget->spawnArgs.MatchPrefix( "target", NULL ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent && idStr::Icmp( ent->GetEntityDefName(), "target_null" ) == 0) { + dir = ent->GetPhysics()->GetOrigin() - cameraTarget->GetPhysics()->GetOrigin(); + dir.Normalize(); + cameraTarget->SetAxis( dir.ToMat3() ); +// RAVEN BEGIN +// rjohnson: if you have a func_cameraview pointing to an info_null via "cameratarget" and +// you have a func_static pointing to the func_cameraview via a "cameratarget" then +// the func_static evaluates 'target' to the func_cameraview and its target it the info null +// the SexAxis() is then applied to the the func_static rather than the func_cameraview +// SetAxis(dir.ToMat3()); +// RAVEN END + break; + } + kv = cameraTarget->spawnArgs.MatchPrefix( "target", kv ); + } + } + UpdateVisuals(); +} + +/* +================ +idEntity::Event_DistanceTo +================ +*/ +void idEntity::Event_DistanceTo( idEntity *ent ) { + if ( !ent ) { + // just say it's really far away + idThread::ReturnFloat( MAX_WORLD_SIZE ); + } else { + float dist = ( GetPhysics()->GetOrigin() - ent->GetPhysics()->GetOrigin() ).LengthFast(); + idThread::ReturnFloat( dist ); + } +} + +/* +================ +idEntity::Event_DistanceToPoint +================ +*/ +void idEntity::Event_DistanceToPoint( const idVec3 &point ) { + float dist = ( GetPhysics()->GetOrigin() - point ).LengthFast(); + idThread::ReturnFloat( dist ); +} + +/* +================ +idEntity::Event_StartFx +================ +*/ +void idEntity::Event_StartFx( const char *fx ) { +// RAVEN BEGIN +// bdube: not used +// idEntityFx::StartFx( fx, NULL, NULL, this, true ); +// RAVEN END +} + +/* +================ +idEntity::Event_WaitFrame +================ +*/ +void idEntity::Event_WaitFrame( void ) { + idThread *thread; + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->WaitFrame(); + } +} + +/* +===================== +idEntity::Event_Wait +===================== +*/ +void idEntity::Event_Wait( float time ) { + idThread *thread = idThread::CurrentThread(); + + if ( !thread ) { + gameLocal.Error( "Event 'wait' called from outside thread" ); + } + + thread->WaitSec( time ); +} + +/* +===================== +idEntity::Event_HasFunction +===================== +*/ +void idEntity::Event_HasFunction( const char *name ) { + const function_t *func; + + func = scriptObject.GetFunction( name ); + if ( func ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +===================== +idEntity::Event_CallFunction +===================== +*/ +void idEntity::Event_CallFunction( const char *funcname ) { +// RAVEN BEGIN +// bdube: states + stateParms_t parms = {0}; + if ( ProcessState ( funcname, parms ) != SRESULT_ERROR ) { + return; + } + gameLocal.CallObjectFrameCommand ( this, funcname ); +// RAVEN END +} + +/* +================ +idEntity::Event_SetNeverDormant +================ +*/ +void idEntity::Event_SetNeverDormant( int enable ) { + fl.neverDormant = ( enable != 0 ); + dormantStart = 0; +} + +/*********************************************************************** + + Network + +***********************************************************************/ + +/* +================ +idEntity::ClientPredictionThink +================ +*/ +void idEntity::ClientPredictionThink( void ) { + RunPhysics(); + Present(); +} + +/* +================ +idEntity::WriteBindToSnapshot +================ +*/ +void idEntity::WriteBindToSnapshot( idBitMsgDelta &msg ) const { + int bindInfo; + + if ( bindMaster ) { + bindInfo = bindMaster->entityNumber; + bindInfo |= ( fl.bindOrientated & 1 ) << GENTITYNUM_BITS; + if ( bindJoint != INVALID_JOINT ) { + bindInfo |= 1 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindJoint << ( 3 + GENTITYNUM_BITS ); + } else if ( bindBody != -1 ) { + bindInfo |= 2 << ( GENTITYNUM_BITS + 1 ); + bindInfo |= bindBody << ( 3 + GENTITYNUM_BITS ); + } + } else { + bindInfo = ENTITYNUM_NONE; + } + msg.WriteBits( bindInfo, GENTITYNUM_BITS + 3 + 9 ); +} + +/* +================ +idEntity::ReadBindFromSnapshot +================ +*/ +void idEntity::ReadBindFromSnapshot( const idBitMsgDelta &msg ) { + int bindInfo, bindEntityNum, bindPos; + bool bindOrientated; + idEntity *master; + + bindInfo = msg.ReadBits( GENTITYNUM_BITS + 3 + 9 ); + bindEntityNum = bindInfo & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + + if ( bindEntityNum != ENTITYNUM_NONE ) { + master = gameLocal.entities[ bindEntityNum ]; + + bindOrientated = ( bindInfo >> GENTITYNUM_BITS ) & 1; + bindPos = ( bindInfo >> ( GENTITYNUM_BITS + 3 ) ); + switch( ( bindInfo >> ( GENTITYNUM_BITS + 1 ) ) & 3 ) { + case 1: { + BindToJoint( master, (jointHandle_t) bindPos, bindOrientated ); + break; + } + case 2: { + BindToBody( master, bindPos, bindOrientated ); + break; + } + default: { + Bind( master, bindOrientated ); + break; + } + } + } else if ( bindMaster ) { + Unbind(); + } +} + +/* +================ +idEntity::WriteColorToSnapshot +================ +*/ +void idEntity::WriteColorToSnapshot( idBitMsgDelta &msg ) const { + idVec4 color; + + color[0] = renderEntity.shaderParms[ SHADERPARM_RED ]; + color[1] = renderEntity.shaderParms[ SHADERPARM_GREEN ]; + color[2] = renderEntity.shaderParms[ SHADERPARM_BLUE ]; + color[3] = renderEntity.shaderParms[ SHADERPARM_ALPHA ]; + msg.WriteLong( PackColor( color ) ); +} + +/* +================ +idEntity::ReadColorFromSnapshot +================ +*/ +void idEntity::ReadColorFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 color; + + UnpackColor( msg.ReadLong(), color ); + renderEntity.shaderParms[ SHADERPARM_RED ] = color[0]; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[3]; +} + +/* +================ +idEntity::WriteGUIToSnapshot +================ +*/ +void idEntity::WriteGUIToSnapshot( idBitMsgDelta &msg ) const { + // no need to loop over MAX_RENDERENTITY_GUI at this time + if ( renderEntity.gui[ 0 ] ) { + msg.WriteByte( renderEntity.gui[ 0 ]->State().GetInt( "networkState" ) ); + } else { + msg.WriteByte( 0 ); + } +} + +/* +================ +idEntity::ReadGUIFromSnapshot +================ +*/ +void idEntity::ReadGUIFromSnapshot( const idBitMsgDelta &msg ) { + int state; + idUserInterface *gui; + state = msg.ReadByte( ); + gui = renderEntity.gui[ 0 ]; + if ( gui && state != mpGUIState ) { + mpGUIState = state; + gui->SetStateInt( "networkState", state ); + gui->HandleNamedEvent( "networkState" ); + } +} + +/* +================ +idEntity::WriteToSnapshot +================ +*/ +void idEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idEntity::ReadFromSnapshot +================ +*/ +void idEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +idEntity::ServerSendEvent + + Saved events are also sent to any client that connects late so all clients + always receive the events nomatter what time they join the game. + ================ + */ +void idEntity::ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ServerSendReliableMessageExcluding( excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ServerSendInstanceEvent +================ +*/ +void idEntity::ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isServer ) { + return; + } + + assert( gameLocal.isNewFrame ); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + gameLocal.ServerSendInstanceReliableMessageExcluding( this, excludeClient, outMsg ); + + if ( saveEvent ) { + gameLocal.Error( "Unsupported saveEvent == true in idEntity::ServerSendEvent" ); + } +} + +/* +================ +idEntity::ClientSendEvent +================ +*/ +void idEntity::ClientSendEvent( int eventId, const idBitMsg *msg ) const { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( !gameLocal.isClient ) { + return; + } + + // prevent dupe events caused by frame re-runs + if ( !gameLocal.isNewFrame ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT ); + outMsg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + outMsg.WriteByte( eventId ); + outMsg.WriteLong( gameLocal.time ); + if ( msg ) { + outMsg.WriteBits( msg->GetSize(), idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + outMsg.WriteData( msg->GetData(), msg->GetSize() ); + } else { + outMsg.WriteBits( 0, idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + } + + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idEntity::ServerReceiveEvent +================ +*/ +bool idEntity::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case 0: { + } + default: { + return false; + } + } +} + +/* +================ +idEntity::ClientReceiveEvent +================ +*/ +bool idEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + const idSoundShader *shader; + s_channelType channel; + + switch( event ) { + case EVENT_STARTSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + if ( time < gameLocal.realClientTime - 300 ) { + // too old, skip it + common->DPrintf( "ent 0x%x: start sound shader too old (%d ms)\n", entityNumber, gameLocal.realClientTime - time ); + return true; + } + shader = static_cast< const idSoundShader* >( idGameLocal::ReadDecl( msg, DECL_SOUND ) ); + channel = (s_channelType)msg.ReadByte(); + StartSoundShader( shader, channel, 0, false, NULL ); + return true; + } + case EVENT_STOPSOUNDSHADER: { + // the sound stuff would early out + assert( gameLocal.isNewFrame ); + channel = (s_channelType)msg.ReadByte(); + StopSound( channel, false ); + return true; + } +// RAVEN BEGIN +// bdube: new events + case EVENT_PLAYEFFECT_JOINT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin; + rvClientEffect* clientEffect; + effectCategory_t category; + jointHandle_t jointHandle; + bool loop; + bool predictBit; + + // NOTE: doesn't actually happen in multiplayer + // the joint version of PlayEffect is triggered client side for CTF flags, but doesn't have a broadcast flag and is therefore not transmitted + // (and that's pretty much the only instance this is used) + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + jointHandle = ( jointHandle_t )msg.ReadLong(); + origin.x = msg.ReadFloat(); + origin.y = msg.ReadFloat(); + origin.z = msg.ReadFloat(); + category = ( effectCategory_t )msg.ReadByte(); + loop = ( msg.ReadBits ( 1 ) != 0 ); + predictBit = ( msg.ReadBits ( 1 ) != 0 ); + + if ( bse->CanPlayRateLimited( category ) && ( !predictBit || !g_predictedProjectiles.GetBool() ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin( vec3_origin ); + clientEffect->SetAxis( mat3_identity ); + clientEffect->Bind( this, jointHandle ); + + clientEffect->Play( time, loop, origin ); + } + return true; + } + + case EVENT_PLAYEFFECT: { + const idDecl* effect; + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* clientEffect; + effectCategory_t category; + bool loop; + + effect = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + loop = ( msg.ReadBits( 1 ) != 0 ); + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + category = ( effectCategory_t )msg.ReadByte(); + + if ( bse->CanPlayRateLimited( category ) ) { + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + clientEffect->SetOrigin ( origin ); + clientEffect->SetAxis ( quat.ToMat3() ); + clientEffect->Bind ( this ); + + clientEffect->Play ( time, loop, origin2 ); + } + return true; + } +// RAVEN END + default: { + return false; + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idEntity::ClientStale +================ +*/ +bool idEntity::ClientStale( void ) { + FreeModelDef(); + UpdateVisuals(); + GetPhysics()->UnlinkClip(); + return false; +} + +/* +================ +idEntity::ClientUnstale +================ +*/ +void idEntity::ClientUnstale( void ) { +} + +/* +================ +idEntity::GetDamageEntity + +Returns the entity that should take damage in place of this entity. The default is the +entity itself. +================ +*/ +idEntity* idEntity::GetDamageEntity( void ) { + return forwardDamageEnt.IsValid() ? forwardDamageEnt.GetEntity() : this; +} + +// rjohnson: moved entity info out of idGameLocal into its own function +/* +================ +idEntity::DrawDebugEntityInfo +================ +*/ +void idEntity::DrawDebugEntityInfo( idBounds *viewBounds, idBounds *viewTextBounds, idVec4 *overrideColor ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idMat3 axis = player->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + // skip if the entity is very far away + if ( viewBounds && !viewBounds->IntersectsBounds( GetPhysics()->GetAbsBounds() ) ) { + return; + } + + const idBounds &entBounds = GetPhysics()->GetAbsBounds(); + + if (overrideColor) { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( *overrideColor, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( *overrideColor, entBounds, vec3_origin ); + } + } else { + int contents = GetPhysics()->GetContents(); + if ( contents & CONTENTS_BODY ) { + gameRenderWorld->DebugBounds ( colorCyan, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_TRIGGER ) { + gameRenderWorld->DebugBounds( colorOrange, entBounds, vec3_origin ); + } else if ( contents & CONTENTS_SOLID ) { + gameRenderWorld->DebugBounds( colorGreen, entBounds, vec3_origin ); + } else { + if ( !entBounds.GetVolume() ) { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds.Expand( 8.0f ), vec3_origin ); + } else { + gameRenderWorld->DebugBounds( colorMdGrey, entBounds, vec3_origin ); + } + } + } + + if ( !viewTextBounds || viewTextBounds->IntersectsBounds( entBounds ) ) { + gameRenderWorld->DrawText( name.c_str(), entBounds.GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), entBounds.GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + + if ( gameLocal.GetLocalPlayer() && this != gameLocal.GetLocalPlayer() && teamMaster != gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + } +} + +/* +===================== +idEntity::SetInstance +===================== +*/ +void idEntity::SetInstance( int newInstance ) { + instance = newInstance; + + if( gameLocal.isServer ) { + SetClipWorld( newInstance ); + } +} + +/* +===================== +idEntity::InstanceJoin +Gets called when the local player joins the same instance as this entity +===================== +*/ +void idEntity::InstanceJoin( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ); + + BecomeActive( TH_UPDATEVISUALS ); + Present(); +} + +/* +===================== +idEntity::InstanceLeave +Gets called when the local player leaves the same instance as this entity +===================== +*/ +void idEntity::InstanceLeave( void ) { + assert( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ); + + FreeLightDef(); + FreeModelDef(); + //RemoveClientEntities(); + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +===================== +idEntity::GetDebugInfo +===================== +*/ +void idEntity::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idClass::GetDebugInfo ( proc, userData ); + + proc ( "idEntity", "health", va("%d",health), userData ); + proc ( "idEntity", "name", name, userData ); + proc ( "idEntity", "entityNumber", va("%d",entityNumber), userData ); + proc ( "idEntity", "origin", renderEntity.origin.ToString ( ), userData ); + + proc ( "idEntity", "notarget", fl.notarget?"true":"false", userData ); + proc ( "idEntity", "takedamage", fl.takedamage?"true":"false", userData ); + proc ( "idEntity", "hidden", fl.hidden?"true":"false", userData ); + proc ( "idEntity", "bindOrientated",fl.bindOrientated?"true":"false", userData ); + proc ( "idEntity", "isDormant", fl.isDormant?"true":"false", userData ); + proc ( "idEntity", "neverDormant", fl.neverDormant?"true":"false", userData ); + proc ( "idEntity", "isAIObstacle", fl.isAIObstacle?"true":"false", userData ); + + proc ( "idEntity", "forwardDamageEnt",forwardDamageEnt.GetEntity() ? forwardDamageEnt.GetEntity()->GetName() : "", userData ); + + proc ( "idEntity", "bindMaster", bindMaster ? bindMaster->GetName() : "", userData ); + proc ( "idEntity", "bindJoint", va("%d",((int)bindJoint)), userData ); + proc ( "idEntity", "bindBody", va("%d",bindBody), userData ); + + proc ( "idEntity", "teamMaster", teamMaster ? teamMaster->GetName() : "", userData ); + proc ( "idEntity", "teamChain", teamChain ? teamChain->GetName() : "", userData ); +} + +// mwhitlock: memory profiling +/* +===================== +idEntity::Size() + +Returns memory size of an idEntity instance +===================== +*/ + +size_t idEntity::Size( void ) const +{ + // TODO: more crap needs to go here! + return sizeof (idEntity); +} +// RAVEN END + + + +/* +=============================================================================== + + idAnimatedEntity + +=============================================================================== +*/ + +const idEventDef EV_GetJointHandle( "getJointHandle", "s", 'd' ); +const idEventDef EV_ClearAllJoints( "clearAllJoints" ); +const idEventDef EV_ClearJoint( "clearJoint", "d" ); +const idEventDef EV_SetJointPos( "setJointPos", "ddv" ); +const idEventDef EV_SetJointAngle( "setJointAngle", "ddv" ); +const idEventDef EV_GetJointPos( "getJointPos", "d", 'v' ); +const idEventDef EV_GetJointAngle( "getJointAngle", "d", 'v' ); + + +// RAVEN BEGIN +// bdube: programmer controlled joint events +const idEventDef EV_SetJointAngularVelocity ( "setJointAngularVelocity", "sfffd" ); +const idEventDef EV_CollapseJoints ( "collapseJoints", "ss" ); +// jshepard: clear out all animations still running on the model +const idEventDef EV_ClearAnims( "clearAnims" ); + +// RAVEN END + +CLASS_DECLARATION( idEntity, idAnimatedEntity ) + EVENT( EV_GetJointHandle, idAnimatedEntity::Event_GetJointHandle ) + EVENT( EV_ClearAllJoints, idAnimatedEntity::Event_ClearAllJoints ) + EVENT( EV_ClearJoint, idAnimatedEntity::Event_ClearJoint ) + EVENT( EV_SetJointPos, idAnimatedEntity::Event_SetJointPos ) + EVENT( EV_SetJointAngle, idAnimatedEntity::Event_SetJointAngle ) + EVENT( EV_GetJointPos, idAnimatedEntity::Event_GetJointPos ) + EVENT( EV_GetJointAngle, idAnimatedEntity::Event_GetJointAngle ) + +// RAVEEN BEGIN +// bdube: programmer controlled joint events + EVENT( EV_SetJointAngularVelocity, idAnimatedEntity::Event_SetJointAngularVelocity ) + EVENT( EV_CollapseJoints, idAnimatedEntity::Event_CollapseJoints ) +// RAVEN END +END_CLASS + +/* +================ +idAnimatedEntity::idAnimatedEntity +================ +*/ +idAnimatedEntity::idAnimatedEntity() { + animator.SetEntity( this ); + damageEffects = NULL; +} + +/* +================ +idAnimatedEntity::~idAnimatedEntity +================ +*/ +idAnimatedEntity::~idAnimatedEntity() { + damageEffect_t *de; + + for ( de = damageEffects; de; de = damageEffects ) { + damageEffects = de->next; + delete de; + } +} + +/* +================ +idAnimatedEntity::Save + +archives object for save game file +================ +*/ +void idAnimatedEntity::Save( idSaveGame *savefile ) const { + animator.Save( savefile ); + + // Wounds are very temporary, ignored at this time + //damageEffect_t *damageEffects; +} + +/* +================ +idAnimatedEntity::Restore + +unarchives object from save game file +================ +*/ +void idAnimatedEntity::Restore( idRestoreGame *savefile ) { + animator.Restore( savefile ); + + // check if the entity has an MD5 model + if ( animator.ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( modelDefHandle != -1 ) { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } + } +} + +/* +================ +idAnimatedEntity::ClientPredictionThink +================ +*/ +void idAnimatedEntity::ClientPredictionThink( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::Think +================ +*/ +void idAnimatedEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + Present(); +} + +/* +================ +idAnimatedEntity::UpdateAnimation +================ +*/ +void idAnimatedEntity::UpdateAnimation( void ) { + // don't do animations if they're not enabled + if ( !( thinkFlags & TH_ANIMATE ) ) { + return; + } + + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + +// RAVEN BEGIN +// bgeisler: for triggered anims + // call any frame commands that have happened in the past frame + if ( !fl.hidden || fl.triggerAnim ) { + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } +// RAVEN END + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() && !fl.hidden ) { + gameLocal.DPrintf( "%d: inside out bounds\n", gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +idAnimatedEntity::GetAnimator +================ +*/ +idAnimator *idAnimatedEntity::GetAnimator( void ) { + return &animator; +} + +/* +================ +idAnimatedEntity::SetModel +================ +*/ +void idAnimatedEntity::SetModel( const char *modelname ) { + FreeModelDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + idEntity::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + UpdateVisuals(); +} + +/* +===================== +idAnimatedEntity::GetJointWorldTransform +===================== +*/ +bool idAnimatedEntity::GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass(); + axis = renderEntity.axis; + return true; + } + + if ( !animator.GetJointTransform( jointHandle, currentTime, offset, axis ) ) { + return false; + } + + ConvertLocalToWorldTransform( offset, axis ); + return true; +} + +/* +============== +idAnimatedEntity::GetJointTransformForAnim +============== +*/ +bool idAnimatedEntity::GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int frameTime, idVec3 &offset, idMat3 &axis ) const { + const idAnim *anim; + int numJoints; + idJointMat *frame; + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = GetPhysics()->GetCenterMass() - GetPhysics()->GetOrigin(); + axis = renderEntity.axis; + return true; + } + + anim = animator.GetAnim( animNum ); + if ( !anim ) { + assert( 0 ); + return false; + } + + numJoints = animator.NumJoints(); + if ( ( jointHandle < 0 ) || ( jointHandle >= numJoints ) ) { + assert( 0 ); + return false; + } + + frame = ( idJointMat * )_alloca16( numJoints * sizeof( idJointMat ) ); + gameEdit->ANIM_CreateAnimFrame( animator.ModelHandle(), anim->MD5Anim( 0 ), renderEntity.numJoints, frame, frameTime, animator.ModelDef()->GetVisualOffset(), animator.RemoveOrigin() ); + + offset = frame[ jointHandle ].ToVec3(); + axis = frame[ jointHandle ].ToMat3(); + + return true; +} + +// RAVEN BEGIN +// ddynerman: removed/merged AddLocalDamageEffect() (redundant math) +/* +============== +idAnimatedEntity::AddDamageEffect + + Dammage effects track the animating impact position, spitting out particles. +============== +*/ +void idAnimatedEntity::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // ddynerman: note, on client the collision struct is incomplete. Only contains impact point and material + const char *splat, *decal, *key; + idVec3 dir; + + const idDeclEntityDef *def = gameLocal.FindEntityDef( damageDefName, false ); + if ( def == NULL || !def->dict.GetBool ( "bleed" ) ) { + return; + } + + if ( !spawnArgs.GetBool( "bleed" ) ) { + return; + } + + dir = velocity; + dir.Normalize(); + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteFloat( collision.c.point[0] ); + msg.WriteFloat( collision.c.point[1] ); + msg.WriteFloat( collision.c.point[2] ); + msg.WriteDir( dir, 24 ); + idGameLocal::WriteDecl( msg, def ); + idGameLocal::WriteDecl( msg, collision.c.material ); + ServerSendInstanceEvent( EVENT_ADD_DAMAGE_EFFECT, &msg, false, -1 ); + } + + if ( !g_decals.GetBool() ) { + return; + } + + if ( gameLocal.GetLocalPlayer() && GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return; // no blood from other instances + } + + // blood splats are thrown onto nearby surfaces + splat = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_splat_%s", collision.c.material->GetMaterialType()->GetName() ); + splat = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !splat || !*splat ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + } + if ( splat && *splat ) { + //jshepard original 64.0f + // dluetscher: changed from 64. to 48. for performance reasons + gameLocal.BloodSplat( this, collision.c.point, dir, 48.0f, splat ); + } + + // can't see wounds on the player model in single player mode + if ( !( IsType( idPlayer::GetClassType() ) && !gameLocal.isMultiplayer ) ) { + //If this is a buddy marine, no wound decals until they're actually dead unless it's mp. + if ( gameLocal.isMultiplayer + || !IsType( idAI::GetClassType() ) + || this->health <= 0 + || ((idAI*)this)->team != AITEAM_MARINE ) { + // place a wound overlay on the model + decal = NULL; + if ( collision.c.material->GetMaterialType() ) { + key = va( "mtr_wound_%s", collision.c.material->GetMaterialType()->GetName() ); + decal = spawnArgs.RandomPrefix( key, gameLocal.random ); + } + if ( !decal || !*decal ) { + decal = spawnArgs.RandomPrefix( "mtr_wound", gameLocal.random ); + } + if ( decal && *decal ) { + ProjectOverlay( collision.c.point, dir, 20.0f, decal ); + if( IsType( idPlayer::GetClassType() ) ) { + ProjectHeadOverlay( collision.c.point, dir, 20.0f, decal ); + } + } + } + } +} +// RAVEN END + +/* +============== +idAnimatedEntity::GetDefaultSurfaceType +============== +*/ +int idAnimatedEntity::GetDefaultSurfaceType( void ) const { + return SURFTYPE_METAL; +} + +/* +================ +idAnimatedEntity::ClientReceiveEvent +================ +*/ +bool idAnimatedEntity::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + idVec3 origin, dir; + + switch( event ) { + case EVENT_ADD_DAMAGE_EFFECT: { + origin[0] = msg.ReadFloat(); + origin[1] = msg.ReadFloat(); + origin[2] = msg.ReadFloat(); + dir = msg.ReadDir( 24 ); + const idDeclEntityDef *damageDef = static_cast< const idDeclEntityDef* >( idGameLocal::ReadDecl( msg, DECL_ENTITYDEF ) ); + const idMaterial *collisionMaterial = static_cast< const idMaterial* >( idGameLocal::ReadDecl( msg, DECL_MATERIAL ) ); +// RAVEN BEGIN +// ddynerman: removed redundant AddLocalDamageEffect() + trace_t collision; + collision.c.point = origin; + collision.c.material = collisionMaterial; + AddDamageEffect( collision, dir, damageDef->GetName(), NULL ); +// RAVEN END + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// abahr: so we don't crash if UpdateModel is called from a destructor +/* +================ +idAnimatedEntity::UpdateRenderEntityCallback +================ +*/ +void idAnimatedEntity::UpdateRenderEntityCallback() { + // check if the entity has an MD5 model + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = idEntity::ModelCallback; + } +} +// RAVEN END + +/* +================ +idAnimatedEntity::Event_GetJointHandle + +looks up the number of the specified joint. returns INVALID_JOINT if the joint is not found. +================ +*/ +void idAnimatedEntity::Event_GetJointHandle( const char *jointname ) { + jointHandle_t joint; + + joint = animator.GetJointHandle( jointname ); + idThread::ReturnInt( joint ); +} + +/* +================ +idAnimatedEntity::Event_ClearAllJoints + +removes any custom transforms on all joints +================ +*/ +void idAnimatedEntity::Event_ClearAllJoints( void ) { + animator.ClearAllJoints(); +} + +/* +================ +idAnimatedEntity::Event_ClearJoint + +removes any custom transforms on the specified joint +================ +*/ +void idAnimatedEntity::Event_ClearJoint( jointHandle_t jointnum ) { + animator.ClearJoint( jointnum ); +} + +/* +================ +idAnimatedEntity::Event_ClearAnims + +Clears any animation running on the animated entity +================ +*/ +void idAnimatedEntity::Event_ClearAnims( void ) { + animator.Clear( ANIMCHANNEL_ALL, gameLocal.GetTime(), gameLocal.GetTime() ); +} + +/* +================ +idAnimatedEntity::Event_SetJointPos + +modifies the position of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { + animator.SetJointPos( jointnum, transform_type, pos ); +} + +/* +================ +idAnimatedEntity::Event_SetJointAngle + +modifies the orientation of the joint based on the transform type +================ +*/ +void idAnimatedEntity::Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ) { + idMat3 mat; + + mat = angles.ToMat3(); + animator.SetJointAxis( jointnum, transform_type, mat ); +} + +/* +================ +idAnimatedEntity::Event_GetJointPos + +returns the position of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idThread::ReturnVector( offset ); +} + +/* +================ +idAnimatedEntity::Event_GetJointAngle + +returns the orientation of the joint in worldspace +================ +*/ +void idAnimatedEntity::Event_GetJointAngle( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + idAngles ang = axis.ToAngles(); + idVec3 vec( ang[ 0 ], ang[ 1 ], ang[ 2 ] ); + idThread::ReturnVector( vec ); +} + +// RAVEN BEGIN +// bdube: moved to idAnimatedEntity +/* +================ +idAnimatedEntity::Event_SetJointAngularVelocity +================ +*/ +void idAnimatedEntity::Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ) { + jointHandle_t joint = animator.GetJointHandle ( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + animator.SetJointAngularVelocity ( joint, idAngles(pitch,yaw,roll), gameLocal.time, blendTime ); +} + +/* +================ +idAnimatedEntity::Event_CollapseJoints +================ +*/ +void idAnimatedEntity::Event_CollapseJoints ( const char* jointnames, const char* collapseTo ) { + jointHandle_t collapseToJoint = animator.GetJointHandle ( collapseTo ); + if ( collapseToJoint == INVALID_JOINT ) { + return; + } + + animator.CollapseJoints ( jointnames, collapseToJoint ); +} +// RAVEN END + diff --git a/src/mpgame/Entity.h b/src/mpgame/Entity.h new file mode 100644 index 0000000..5bb15b3 --- /dev/null +++ b/src/mpgame/Entity.h @@ -0,0 +1,872 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_ENTITY_H__ +#define __GAME_ENTITY_H__ + +/* +=============================================================================== + + Game entity base class. + +=============================================================================== +*/ + +static const int DELAY_DORMANT_TIME = 3000; + +extern const idEventDef EV_PostSpawn; +extern const idEventDef EV_FindTargets; +extern const idEventDef EV_Touch; +extern const idEventDef EV_Use; +extern const idEventDef EV_Activate; +extern const idEventDef EV_ActivateTargets; +extern const idEventDef EV_Hide; +extern const idEventDef EV_Show; +extern const idEventDef EV_GetShaderParm; +extern const idEventDef EV_SetShaderParm; +extern const idEventDef EV_SetOwner; +extern const idEventDef EV_GetAngles; +extern const idEventDef EV_SetAngles; +extern const idEventDef EV_SetLinearVelocity; +extern const idEventDef EV_SetAngularVelocity; +extern const idEventDef EV_SetSkin; +extern const idEventDef EV_StartSoundShader; +extern const idEventDef EV_StopSound; +extern const idEventDef EV_CacheSoundShader; + +// RAVEN BEGIN +extern const idEventDef EV_CallFunction; +extern const idEventDef EV_SetGuiParm; +extern const idEventDef EV_SetGuiFloat; +extern const idEventDef EV_ClearSkin; +// bdube: more global events for idEntity +extern const idEventDef EV_GetFloatKey; +extern const idEventDef EV_HideSurface; +extern const idEventDef EV_ShowSurface; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_StopAllEffects; +extern const idEventDef EV_PlayEffect; +extern const idEventDef EV_Earthquake; +extern const idEventDef EV_GuiEvent; +extern const idEventDef EV_SetKey; +// jscott: +extern const idEventDef EV_PlaybackCallback; +// jshepard: +extern const idEventDef EV_UnbindTargets; +// twhitaker: +extern const idEventDef EV_ApplyImpulse; +// RAVEN END + +class idGuidedProjectile; + +// Think flags +enum { + TH_ALL = -1, + TH_THINK = 1, // run think function each frame + TH_PHYSICS = 2, // run physics each frame + TH_ANIMATE = 4, // update animation each frame + TH_UPDATEVISUALS = 8, // update renderEntity +}; + +// +// Signals +// make sure to change script/doom_defs.script if you add any, or change their order +// +typedef enum { + SIG_TOUCH, // object was touched + SIG_USE, // object was used + SIG_TRIGGER, // object was activated + SIG_REMOVED, // object was removed from the game + SIG_DAMAGE, // object was damaged + SIG_BLOCKED, // object was blocked + + SIG_MOVER_POS1, // mover at position 1 (door closed) + SIG_MOVER_POS2, // mover at position 2 (door open) + SIG_MOVER_1TO2, // mover changing from position 1 to 2 + SIG_MOVER_2TO1, // mover changing from position 2 to 1 + +// RAVEN BEGIN +// kfuller: added signals + // WARNING: these entries are mirrored in scripts/defs.script so make sure if they move + // here that they move there as well + SIG_REACHED, // object reached it's rotation/motion destination +// RAVEN END + + NUM_SIGNALS +} signalNum_t; + +// FIXME: At some point we may want to just limit it to one thread per signal, but +// for now, I'm allowing multiple threads. We should reevaluate this later in the project +#define MAX_SIGNAL_THREADS 16 // probably overkill, but idList uses a granularity of 16 + +struct signal_t { + int threadnum; + const function_t *function; +}; + +class signalList_t { +public: + idList signal[ NUM_SIGNALS ]; +}; + +class idEntity : public idClass { +public: + static const int MAX_PVS_AREAS = 4; + + int entityNumber; // index into the entity list + int entityDefNumber; // index into the entity def list + + idLinkList spawnNode; // for being linked into spawnedEntities list + idLinkList activeNode; // for being linked into activeEntities list + + idLinkList snapshotNode; // for being linked into snapshotEntities list + int snapshotSequence; // last snapshot this entity was in + int snapshotBits; // number of bits this entity occupied in the last snapshot + + idStr name; // name of entity + idDict spawnArgs; // key/value pairs used to spawn and initialize entity + idScriptObject scriptObject; // contains all script defined data for this entity + + int thinkFlags; // TH_? flags + int dormantStart; // time that the entity was first closed off from player + bool cinematic; // during cinematics, entity will only think if cinematic is set + + renderView_t * renderView; // for camera views from this entity + idEntity * cameraTarget; // any remoteRenderMap shaders will use this + + idList< idEntityPtr > targets; // when this entity is activated these entities entity are activated + + int health; // FIXME: do all objects really need health? + +// RAVEN BEGIN +// ddynerman: optional pre-prediction + int predictTime; +// bdube: client entities + idLinkList clientEntities; + +// rjohnson: will now draw entity info for long thinkers + int mLastLongThinkTime; + idVec4 mLastLongThinkColor; +// RAVEN END + + struct entityFlags_s { + bool notarget :1; // if true never attack or target this entity + bool noknockback :1; // if true no knockback from hits + bool takedamage :1; // if true this entity can be damaged + bool hidden :1; // if true this entity is not visible + bool bindOrientated :1; // if true both the master orientation is used for binding + bool solidForTeam :1; // if true this entity is considered solid when a physics team mate pushes entities + bool forcePhysicsUpdate :1; // if true always update from the physics whether the object moved or not + bool selected :1; // if true the entity is selected for editing + bool neverDormant :1; // if true the entity never goes dormant + bool isDormant :1; // if true the entity is dormant + bool hasAwakened :1; // before a monster has been awakened the first time, use full PVS for dormant instead of area-connected + bool networkSync :1; // if true the entity is synchronized over the network + +// RAVEN BEGIN +// bdube: added + bool networkStale :1; // was in the snapshot but isnt anymore +// bgeisler: added block + bool triggerAnim :1; + bool usable :1; // if true the entity is usable by the player +// cdr: Obstacle Avoidance + bool isAIObstacle :1; // if true, this entity will add itself to the obstacles list in each aas area it touches +// RAVEN END + +// RAVEN BEGIN +// ddynerman: exclude this entity from instance-purging + // twhitaker: moved variable to be within the bit flags + bool persistAcrossInstances :1; +// twhitaker: exited vehicle already? + bool exitedVehicle :1; +// twhitaker: blinking + bool allowAutoBlink :1; +// jshepard: instant burnout when destroyed + bool quickBurn :1; + +// RAVEN END + } fl; + +public: + ABSTRACT_PROTOTYPE( idEntity ); + + idEntity(); + ~idEntity(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + const char * GetEntityDefName( void ) const; + void SetName( const char *name ); + const char * GetName( void ) const; + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + // clients generate views based on all the player specific options, + // cameras have custom code, and everything else just uses the axis orientation + virtual renderView_t * GetRenderView(); + + // thinking + virtual void Think( void ); + bool CheckDormant( void ); // dormant == on the active list, but out of PVS + virtual void DormantBegin( void ); // called when entity becomes dormant + virtual void DormantEnd( void ); // called when entity wakes from being dormant + bool IsActive( void ) const; + void BecomeActive( int flags ); + void BecomeInactive( int flags ); + void UpdatePVSAreas( const idVec3 &pos ); + +// RAVEN BEGIN +// abahr: + bool IsActive( int flags ) const { return (flags & thinkFlags) > 0; } + const char* GetEntityDefClassName() const { return spawnArgs.GetString("classname"); } + bool IsEntityDefClass( const char* className ) const { return !idStr::Icmp(className, GetEntityDefClassName()); } + virtual void GetPosition( idVec3& origin, idMat3& axis ) const; +// kfuller: added methods + virtual void GetLocalAngles(idAngles &localAng); +// RAVEN END + + // visuals + virtual void Present( void ); + // instance visuals + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); +// RAVEN BEGIN +// bdube: removed virtual so it could be inlined + renderEntity_t * GetRenderEntity( void ); + int GetModelDefHandle( void ); +// RAVEN END + virtual void SetModel( const char *modelname ); + void SetSkin( const idDeclSkin *skin ); + const idDeclSkin * GetSkin( void ) const; + +// RAVEN BEGIN +// bdube: surfaces + void HideSurface ( const char* surface ); + void ShowSurface ( const char* surface ); + void ClearSkin( void ); +// RAVEN END + + void SetShaderParm( int parmnum, float value ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec3 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec4 &out ) const; + virtual void FreeModelDef( void ); + virtual void FreeLightDef( void ); + virtual void Hide( void ); + virtual void Show( void ); + bool IsHidden( void ) const; + void UpdateVisuals( void ); + void UpdateModel( void ); +// RAVEN BEGIN +// abahr: added virtual to UpdateModelTransform + virtual + void UpdateModelTransform( void ); + virtual void UpdateRenderEntityCallback(); + virtual const idAnimator * GetAnimator( void ) const { return NULL; } // returns animator object used by this entity +// RAVEN END + virtual void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + int GetNumPVSAreas( void ); + const int * GetPVSAreas( void ); + void ClearPVSAreas( void ); + bool PhysicsTeamInPVS( pvsHandle_t pvsHandle ); + + // animation + virtual bool UpdateAnimationControllers( void ); + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + virtual idAnimator * GetAnimator( void ); // returns animator object used by this entity + + // sound + virtual bool CanPlayChatterSounds( void ) const; + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + bool StartSoundShader( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound( const s_channelType channel, bool broadcast ); // pass SND_CHANNEL_ANY to stop all sounds + void SetSoundVolume( float volume ); + void UpdateSound( void ); + int GetListenerId( void ) const; +// RAVEN BEGIN + int GetSoundEmitter( void ) const; +// RAVEN END + void FreeSoundEmitter( bool immediate ); + +// RAVEN BEGIN +// bdube: added effect functions + // effects + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, jointHandle_t joint, const idVec3& originOffset, const idMat3& axisOffset, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + + rvClientEffect* PlayEffect ( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + void StopEffect ( const char* effectName, bool destroyParticles = false ); + void StopEffect ( const idDecl *effect, bool destroyParticles = false ); + void StopAllEffects ( bool destroyParticles = false ); + void UpdateEffects ( void ); + + float DistanceTo ( idEntity* ent ); + float DistanceTo ( const idVec3& pos ) const; + float DistanceTo2d ( idEntity* ent ); + float DistanceTo2d ( const idVec3& pos ) const; + + virtual bool CanTakeDamage ( void ) const; +// RAVEN END + + // entity binding + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void PreUnbind( void ); + virtual void PostUnbind( void ); + void JoinTeam( idEntity *teammember ); + void Bind( idEntity *master, bool orientated ); + void BindToJoint( idEntity *master, const char *jointname, bool orientated ); + void BindToJoint( idEntity *master, jointHandle_t jointnum, bool orientated ); + void BindToBody( idEntity *master, int bodyId, bool orientated ); + void Unbind( void ); + bool IsBound( void ) const; +// RAVEN BEGIN +// abahr: added const so it can be called from const functions + bool IsBoundTo( const idEntity *master ) const; +// RAVEN END + idEntity * GetBindMaster( void ) const; + jointHandle_t GetBindJoint( void ) const; + int GetBindBody( void ) const; + idEntity * GetTeamMaster( void ) const; + idEntity * GetNextTeamEntity( void ) const; +// RAVEN BEGIN +// abahr: added virtual + virtual + void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); +// RAVEN END + idVec3 GetLocalVector( const idVec3 &vec ) const; + idVec3 GetLocalCoordinates( const idVec3 &vec ) const; + idVec3 GetWorldVector( const idVec3 &vec ) const; + idVec3 GetWorldCoordinates( const idVec3 &vec ) const; + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + void GetWorldVelocities( idVec3 &linearVelocity, idVec3 &angularVelocity ) const; + + // physics + // set a new physics object to be used by this entity + void SetPhysics( idPhysics *phys ); + // get the physics object used by this entity + idPhysics * GetPhysics( void ) const; + // restore physics pointer for save games + void RestorePhysics( idPhysics *phys ); + // run the physics for this entity + bool RunPhysics( void ); + // set the origin of the physics object (relative to bindMaster if not NULL) + void SetOrigin( const idVec3 &org ); + // set the axis of the physics object (relative to bindMaster if not NULL) + void SetAxis( const idMat3 &axis ); + // use angles to set the axis of the physics object (relative to bindMaster if not NULL) + void SetAngles( const idAngles &ang ); + // get the floor position underneath the physics object + bool GetFloorPos( float max_dist, idVec3 &floorpos ) const; + // retrieves the transformation going from the physics origin/axis to the visual origin/axis + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + // retrieves the transformation going from the physics origin/axis to the sound origin/axis + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + // called from the physics object when colliding, should return true if the physics simulation should stop + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { hitTeleporter = false; return Collide(collision, velocity); } + // retrieves impact information, 'ent' is the entity retrieving the info + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + // apply an impulse to the physics object, 'ent' is the entity applying the impulse + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + // add a force to the physics object, 'ent' is the entity adding the force + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + // activate the physics object, 'ent' is the entity activating this entity + virtual void ActivatePhysics( idEntity *ent ); + // returns true if the physics object is at rest + virtual bool IsAtRest( void ) const; + // returns the time the physics object came to rest + virtual int GetRestStartTime( void ) const; + // add a contact entity + virtual void AddContactEntity( idEntity *ent ); + // remove a touching entity + virtual void RemoveContactEntity( idEntity *ent ); + +// RAVEN BEGIN +// kfuller: added blocked methods + virtual void LastBlockedBy(int blockedEntNum) {} + virtual int GetLastBlocker(void) { return -1; } +// rjohnson: moved entity info out of idGameLocal into its own function + virtual void DrawDebugEntityInfo( idBounds *viewBounds = 0, idBounds *viewTextBounds = 0, idVec4 *overrideColor = 0 ); +// nmckenzie: Adding ability for non-Actors to be enemies for AI characters. Rename this function at some point, most likely. + virtual idVec3 GetEyePosition( void ) const { return GetPhysics()->GetOrigin(); } +// abahr: + virtual bool SkipImpulse( idEntity *ent, int id ); + virtual void ApplyImpulse( idEntity* ent, int id, const idVec3& point, const idVec3& dir, const idDict* damageDef ); +// RAVEN END + + // damage +// RAVEN BEGIN + // twhitaker: // Sets the damage enitty + void SetDamageEntity ( idEntity * forward ) { forwardDamageEnt = forward; } + + // bdube: added ignore entity + // Returns the entity that should take damage for this entity + virtual idEntity* GetDamageEntity ( void ); + + // returns true if this entity can be damaged from the given origin + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity* ignoreEnt = NULL ) const; +// RAVEN END + // applies damage to this entity + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + // adds a damage effect like overlays, blood, sparks, debris etc. + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + // callback function for when another entity recieved damage from this entity. damage can be adjusted and returned to the caller. + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + // notifies this entity that it is in pain + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + // notifies this entity that is has been killed + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // scripting + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + virtual idThread * ConstructScriptObject( void ); + virtual void DeconstructScriptObject( void ); + void SetSignal( signalNum_t signalnum, idThread *thread, const function_t *function ); + void ClearSignal( idThread *thread, signalNum_t signalnum ); + void ClearSignalThread( signalNum_t signalnum, idThread *thread ); + bool HasSignal( signalNum_t signalnum ) const; + void Signal( signalNum_t signalnum ); + void SignalEvent( idThread *thread, signalNum_t signalnum ); + + // gui + void TriggerGuis( void ); + bool HandleGuiCommands( idEntity *entityGui, const char *cmds ); + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + + // targets +// RAVEN BEGIN +// abahr: made virtual + virtual + void FindTargets( void ); + virtual + void RemoveNullTargets( void ); + virtual + void ActivateTargets( idEntity *activator ) const; +// jshepard: unbind targets + void UnbindTargets( idEntity *activator ) const; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: Add to the list of targets (usually from script) + int AppendTarget( idEntity *appendMe ); + void RemoveTarget( idEntity *removeMe ); + void RemoveTargets( bool destroyContents ); +// RAVEN END + + // misc + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + bool TouchTriggers( const idTypeInfo* ownerType = NULL ) const; + idCurve_Spline *GetSpline( void ) const; + virtual void ShowEditingDialog( void ); + + enum { + EVENT_STARTSOUNDSHADER, + EVENT_STOPSOUNDSHADER, +// RAVEN BEGIN +// bdube: new events + EVENT_PLAYEFFECT, + EVENT_PLAYEFFECT_JOINT, + EVENT_STOPEFFECT, +// RAVEN END + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); +// RAVEN BEGIN + // the entity was not in the snapshot sent by server. means it either went out of PVS, or was deleted server side + // return true if the entity wishes to be deleted locally + // depending on the entity, understanding stale as just another state, or a removal is best + // ( by default, idEntity keeps the entity around after a little cleanup ) + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); +// RAVEN END + + void WriteBindToSnapshot( idBitMsgDelta &msg ) const; + void ReadBindFromSnapshot( const idBitMsgDelta &msg ); + void WriteColorToSnapshot( idBitMsgDelta &msg ) const; + void ReadColorFromSnapshot( const idBitMsgDelta &msg ); + void WriteGUIToSnapshot( idBitMsgDelta &msg ) const; + void ReadGUIFromSnapshot( const idBitMsgDelta &msg ); + + void ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ServerSendInstanceEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; + void ClientSendEvent( int eventId, const idBitMsg *msg ) const; + +// RAVEN BEGIN +// bdube: debugging + virtual void GetDebugInfo( debugInfoProc_t proc, void* userData ); +// mwhitlock: memory profiling + virtual size_t Size( void ) const; +// ddynerman: multiple arenas (for MP) + virtual void SetInstance( int newInstance ); + virtual int GetInstance( void ) const; +// ddynerman: multiple clip world support + virtual int GetClipWorld( void ) const; + virtual void SetClipWorld( int newCW ); +// scork: accessors so sound editor can indicate current-highlit ent + virtual int GetRefSoundShaderFlags( void ) const; + virtual void SetRefSoundShaderFlags( int iFlags ); +// twhitaker: guided projectiles + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ) { } +// RAVEN END + +protected: + renderEntity_t renderEntity; // used to present a model to the renderer + int modelDefHandle; // handle to static renderer model + refSound_t refSound; // used to present sound to the audio engine + idEntityPtr< idEntity > forwardDamageEnt; // damage applied to the invoking object will be forwarded to this entity + idEntityPtr< idEntity > bindMaster; // entity bound to if unequal NULL + jointHandle_t bindJoint; // joint bound to if unequal INVALID_JOINT +private: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics * physics; // physics used for this entity + int bindBody; // body bound to if unequal -1 + idEntity * teamMaster; // master of the physics team + idEntity * teamChain; // next entity in physics team + + int numPVSAreas; // number of renderer areas the entity covers + int PVSAreas[MAX_PVS_AREAS]; // numbers of the renderer areas the entity covers + + signalList_t * signals; + + int mpGUIState; // local cache to avoid systematic SetStateInt +// RAVEN BEGIN +// abahr: changed to protected for access in children classes +protected: +// ddynerman: multiple game instances + int instance; +// ddynerman: multiple collision worlds + int clipWorld; +// RAVEN END + +// RAVEN BEGIN +// bdube: made virtual + virtual bool DoDormantTests( void ); // dormant == on the active list, but out of PVS +// RAVEN END + + // physics + // initialize the default physics + void InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ); + // update visual position from the physics + void UpdateFromPhysics( bool moveBack ); + + // entity binding + bool InitBind( idEntity *master ); // initialize an entity binding + void FinishBind( void ); // finish an entity binding + void RemoveBinds( void ); // deletes any entities bound to this object + void QuitTeam( void ); // leave the current team + + void UpdatePVSAreas( void ); + +// RAVEN BEGIN +// bdube: client entities + void RemoveClientEntities ( void ); // deletes any client entities bound to this object +// RAVEN END + + // events + void Event_GetName( void ); + void Event_SetName( const char *name ); + void Event_FindTargets( void ); + void Event_ActivateTargets( idEntity *activator ); + void Event_NumTargets( void ); + void Event_GetTarget( float index ); + void Event_RandomTarget( const char *ignore ); + void Event_Bind( idEntity *master ); + void Event_BindPosition( idEntity *master ); + void Event_BindToJoint( idEntity *master, const char *jointname, float orientated ); + void Event_Unbind( void ); + void Event_RemoveBinds( void ); + void Event_SpawnBind( void ); + void Event_SetOwner( idEntity *owner ); + void Event_SetModel( const char *modelname ); + void Event_SetSkin( const char *skinname ); + void Event_GetShaderParm( int parmnum ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_SetShaderParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetColor( float red, float green, float blue ); + void Event_GetColor( void ); + void Event_IsHidden( void ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_StartSoundShader( const char *soundName, int channel ); + void Event_StopSound( int channel, int netSync ); + void Event_StartSound( const char *soundName, int channel, int netSync ); + void Event_FadeSound( int channel, float to, float over ); + void Event_GetWorldOrigin( void ); + void Event_SetWorldOrigin( idVec3 const &org ); + void Event_GetOrigin( void ); + void Event_SetOrigin( const idVec3 &org ); + void Event_GetAngles( void ); + void Event_SetAngles( const idAngles &ang ); + void Event_SetLinearVelocity( const idVec3 &velocity ); + void Event_GetLinearVelocity( void ); + void Event_SetAngularVelocity( const idVec3 &velocity ); + void Event_GetAngularVelocity( void ); + void Event_SetSize( const idVec3 &mins, const idVec3 &maxs ); + void Event_GetSize( void ); + void Event_GetMins( void ); + void Event_GetMaxs( void ); + void Event_Touches( idEntity *ent ); + void Event_SetGuiParm( const char *key, const char *val ); + void Event_SetGuiFloat( const char *key, float f ); + void Event_GetNextKey( const char *prefix, const char *lastMatch ); + void Event_SetKey( const char *key, const char *value ); + void Event_GetKey( const char *key ); + void Event_GetIntKey( const char *key ); + void Event_GetFloatKey( const char *key ); + void Event_GetVectorKey( const char *key ); + void Event_GetEntityKey( const char *key ); + void Event_RestorePosition( void ); + void Event_UpdateCameraTarget( void ); + void Event_DistanceTo( idEntity *ent ); + void Event_DistanceToPoint( const idVec3 &point ); + void Event_StartFx( const char *fx ); + void Event_WaitFrame( void ); + void Event_Wait( float time ); + void Event_HasFunction( const char *name ); + void Event_CallFunction( const char *name ); + void Event_SetNeverDormant( int enable ); + +// RAVEN BEGIN +// kfuller: added events + void Event_SetContents ( int contents ); + void Event_GetLastBlocker ( idThread *thread ); +// begisler: added + void Event_ClearSkin ( void ); +// bdube: effect events + void Event_PlayEffect ( const char* effectName, const char* boneName, bool loop ); + void Event_StopEffect ( const char* effectName ); + void Event_StopAllEffects ( void ); + void Event_GetHealth ( void ); +// bdube: mesh events + void Event_ShowSurface ( const char* surface ); + void Event_HideSurface ( const char* surface ); +// bdube: gui events + void Event_GuiEvent ( const char* eventName ); +// jscott: + void Event_PlaybackCallback ( int type, int changed, int impulse ); +// nmckenzie: get bind master + void Event_GetBindMaster ( void ); + void Event_ApplyImpulse ( idEntity *source, const idVec3 &point, const idVec3 &impulse ); +// jshepard: unbind all targets + void Event_UnbindTargets ( idEntity *activator); +// abahr: + void Event_RemoveNullTargets (); + void Event_IsA ( const char* entityDefName ); + void Event_IsSameTypeAs ( const idEntity* ent ); + void Event_MatchPrefix ( const char *prefix, const char* previousKey ); + void Event_ClearTargetList ( float destroyContents ); +// twhitaker: added - to add targets from script + void Event_AppendTarget ( idEntity *appendMe ); + void Event_RemoveTarget ( idEntity *removeMe ); +// mekberg: added + void Event_SetHealth ( float newHealth ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added inlines +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, + bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), origin, axis, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, bool loop, const idVec3& endOrigin, + bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, vec3_origin, mat3_identity, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + +ID_INLINE rvClientEffect* idEntity::PlayEffect( const char* effectName, jointHandle_t jointHandle, const idVec3& originOffset, const idMat3& axisOffset, bool loop, const idVec3& endOrigin, + bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( gameLocal.GetEffect( spawnArgs, effectName ), jointHandle, originOffset, axisOffset, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + + +ID_INLINE idPhysics *idEntity::GetPhysics( void ) const { + return physics; +} + +ID_INLINE renderEntity_t *idEntity::GetRenderEntity( void ) { + return &renderEntity; +} + +ID_INLINE int idEntity::GetModelDefHandle( void ) { + return modelDefHandle; +} + +// scork: accessors so sound editor can indicate current-highlit ent +ID_INLINE int idEntity::GetRefSoundShaderFlags( void ) const +{ + return refSound.parms.soundShaderFlags; +} + +ID_INLINE void idEntity::SetRefSoundShaderFlags( int iFlags ) +{ + refSound.parms.soundShaderFlags = iFlags; +} + + +// RAVEN END + +/* +=============================================================================== + + Animated entity base class. + +=============================================================================== +*/ + +typedef struct damageEffect_s { + jointHandle_t jointNum; + idVec3 localOrigin; + idVec3 localNormal; + int time; +// RAVEN BEGIN +// jscott: not using +// const idDeclParticle* type; +// RAVEN END + struct damageEffect_s * next; +} damageEffect_t; + +class idAnimatedEntity : public idEntity { +public: + CLASS_PROTOTYPE( idAnimatedEntity ); + + idAnimatedEntity(); + ~idAnimatedEntity(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void ClientPredictionThink( void ); + virtual void Think( void ); + + void UpdateAnimation( void ); + + virtual idAnimator * GetAnimator( void ); + virtual void SetModel( const char *modelname ); + + bool GetJointWorldTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + bool GetJointTransformForAnim( jointHandle_t jointHandle, int animNum, int currentTime, idVec3 &offset, idMat3 &axis ) const; + + virtual int GetDefaultSurfaceType( void ) const; + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) {} + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +// RAVEN BEGIN +// abahr: + virtual const idAnimator * GetAnimator( void ) const { return &animator; } + virtual void UpdateRenderEntityCallback(); +// RAVEN BEGIN + + enum { + EVENT_ADD_DAMAGE_EFFECT = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + +protected: + idAnimator animator; + damageEffect_t * damageEffects; + +// RAVEN BEGIN +// jshepard: + void Event_ClearAnims ( void ); +// RAVEN END + +private: + void Event_GetJointHandle( const char *jointname ); + void Event_ClearAllJoints( void ); + void Event_ClearJoint( jointHandle_t jointnum ); + void Event_SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ); + void Event_GetJointPos( jointHandle_t jointnum ); + void Event_GetJointAngle( jointHandle_t jointnum ); + +// RAVEN BEGIN +// bdube: programmer controlled joint events + void Event_SetJointAngularVelocity ( const char* jointName, float pitch, float yaw, float roll, int blendTime ); + void Event_CollapseJoints ( const char* jointnames, const char* collapseTo ); + + + +// RAVEN END +}; + +// RAVEN BEGIN +void UpdateGuiParms( idUserInterface *gui, const idDict *args ); +// RAVEN END + +ID_INLINE float idEntity::DistanceTo ( idEntity* ent ) { + return DistanceTo ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE float idEntity::DistanceTo ( const idVec3& pos ) const { + return (pos - GetPhysics()->GetOrigin()).LengthFast ( ); +} + +ID_INLINE float idEntity::DistanceTo2d ( idEntity* ent ) { + return DistanceTo2d ( ent->GetPhysics()->GetOrigin() ); +} + +ID_INLINE bool idEntity::CanTakeDamage ( void ) const { + return fl.takedamage; +} + +// RAVEN BEGIN +// ddynerman: MP arena stuff +ID_INLINE int idEntity::GetInstance( void ) const { + return instance; +} + +// ddynerman: multiple collision worlds +ID_INLINE int idEntity::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idEntity::SetClipWorld( int newCW ) { + clipWorld = newCW; + if( GetPhysics() ) { + GetPhysics()->UnlinkClip(); + GetPhysics()->LinkClip(); + } +} +// RAVEN END +#endif /* !__GAME_ENTITY_H__ */ diff --git a/src/mpgame/GameEdit.cpp b/src/mpgame/GameEdit.cpp new file mode 100644 index 0000000..5672145 --- /dev/null +++ b/src/mpgame/GameEdit.cpp @@ -0,0 +1,1984 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: added +#include "Effect.h" +// nmckenzie: +//#include "rvAI/AI.h" +#include "ai/AI.h" +#include "client/ClientEffect.h" +// RAVEN END + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idCursor3D ) +END_CLASS + +/* +=============== +idCursor3D::idCursor3D +=============== +*/ +idCursor3D::idCursor3D( void ) { + draggedPosition.Zero(); +} + +/* +=============== +idCursor3D::~idCursor3D +=============== +*/ +idCursor3D::~idCursor3D( void ) { +} + +/* +=============== +idCursor3D::Spawn +=============== +*/ +void idCursor3D::Spawn( void ) { +} + +/* +=============== +idCursor3D::Present +=============== +*/ +void idCursor3D::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + const idVec3 &origin = GetPhysics()->GetOrigin(); + const idMat3 &axis = GetPhysics()->GetAxis(); + gameRenderWorld->DebugArrow( colorYellow, origin + axis[1] * -5.0f + axis[2] * 5.0f, origin, 2 ); + gameRenderWorld->DebugArrow( colorRed, origin, draggedPosition, 2 ); +} + +/* +=============== +idCursor3D::Think +=============== +*/ +void idCursor3D::Think( void ) { + if ( thinkFlags & TH_THINK ) { + drag.Evaluate( gameLocal.time ); + } + Present(); +} + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +#define MAX_DRAG_TRACE_DISTANCE 2048.0f + +/* +============== +idDragEntity::idDragEntity +============== +*/ +idDragEntity::idDragEntity( void ) { + cursor = NULL; + Clear(); +} + +/* +============== +idDragEntity::~idDragEntity +============== +*/ +idDragEntity::~idDragEntity( void ) { + StopDrag(); + selected = NULL; + delete cursor; + cursor = NULL; +} + + +/* +============== +idDragEntity::Clear +============== +*/ +void idDragEntity::Clear() { + dragEnt = NULL; + joint = INVALID_JOINT; + id = 0; + localEntityPoint.Zero(); + localPlayerPoint.Zero(); + bodyName.Clear(); + selected = NULL; +} + +/* +============== +idDragEntity::StopDrag +============== +*/ +void idDragEntity::StopDrag( void ) { + dragEnt = NULL; + if ( cursor ) { + cursor->BecomeInactive( TH_THINK ); + } +} + +/* +============== +idDragEntity::Update +============== +*/ +void idDragEntity::Update( idPlayer *player ) { + idVec3 viewPoint, origin; + idMat3 viewAxis, axis; + trace_t trace; + idEntity *newEnt; + idAngles angles; + jointHandle_t newJoint = INVALID_JOINT; + idStr newBodyName; + + player->GetViewPos( viewPoint, viewAxis ); + + // if no entity selected for dragging + if ( !dragEnt.GetEntity() ) { + + if ( player->usercmd.buttons & BUTTON_ATTACK ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( player, trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + + newEnt = gameLocal.entities[ trace.c.entityNum ]; + if ( newEnt ) { + + if ( newEnt->GetBindMaster() ) { + if ( newEnt->GetBindJoint() ) { + trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() ); + } else { + trace.c.id = newEnt->GetBindBody(); + } + newEnt = newEnt->GetBindMaster(); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( newEnt->IsType( idAFEntity_Base::GetClassType() ) && static_cast(newEnt)->IsActiveAF() ) { +// RAVEN END + idAFEntity_Base *af = static_cast(newEnt); + + // joint being dragged + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + // get the body id from the trace model id which might be a joint handle + trace.c.id = af->BodyForClipModelId( trace.c.id ); + // get the name of the body being dragged + newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( !newEnt->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + + if ( trace.c.id < 0 ) { + newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); + } else { + newJoint = INVALID_JOINT; + } + newBodyName = ""; + + } else { + + newJoint = INVALID_JOINT; + newEnt = NULL; + } + } + if ( newEnt ) { + dragEnt = newEnt; + selected = newEnt; + joint = newJoint; + id = trace.c.id; + bodyName = newBodyName; + + if ( !cursor ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::GetClassType() ); +// RAVEN END + } + + idPhysics *phys = dragEnt.GetEntity()->GetPhysics(); + localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose(); + origin = phys->GetOrigin( id ); + axis = phys->GetAxis( id ); + localEntityPoint = ( trace.c.point - origin ) * axis.Transpose(); + + cursor->drag.Init( g_dragDamping.GetFloat() ); + cursor->drag.SetPhysics( phys, id, localEntityPoint ); + cursor->Show(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( phys->IsType( idPhysics_AF::GetClassType() ) || + phys->IsType( idPhysics_RigidBody::GetClassType() ) || + phys->IsType( idPhysics_Monster::GetClassType() ) ) { +// RAVEN END + cursor->BecomeActive( TH_THINK ); + } + } + } + } + } + + // if there is an entity selected for dragging + idEntity *drag = dragEnt.GetEntity(); + if ( drag ) { + + if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { + StopDrag(); + return; + } + + cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis ); + cursor->SetAxis( viewAxis ); + + cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() ); + + renderEntity_t *renderEntity = drag->GetRenderEntity(); + idAnimator *dragAnimator = drag->GetAnimator(); + + if ( joint != INVALID_JOINT && renderEntity && dragAnimator ) { + dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis ); + cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis; + gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } else { + cursor->draggedPosition = cursor->GetPhysics()->GetOrigin(); + gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); + } + } + + // if there is a selected entity + if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) { + // draw the bbox of the selected entity + renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity(); + if ( renderEntity ) { + gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) ); + } + } +} + +/* +============== +idDragEntity::SetSelected +============== +*/ +void idDragEntity::SetSelected( idEntity *ent ) { + selected = ent; + StopDrag(); +} + +/* +============== +idDragEntity::DeleteSelected +============== +*/ +void idDragEntity::DeleteSelected( void ) { + delete selected.GetEntity(); + selected = NULL; + StopDrag(); +} + +/* +============== +idDragEntity::BindSelected +============== +*/ +void idDragEntity::BindSelected( void ) { + int num, largestNum; + idLexer lexer; + idToken type, bodyName; + idStr key, value, bindBodyName; + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(dragEnt.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + bindBodyName = af->GetAFPhysics()->GetBody( id )->GetName(); + largestNum = 1; + + // parse all the bind constraints + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + key = kv->GetKey(); + key.Strip( "bindConstraint " ); + if ( sscanf( key, "bind%d", &num ) ) { + if ( num >= largestNum ) { + largestNum = num + 1; + } + } + + lexer.LoadMemory( kv->GetValue(), kv->GetValue().Length(), kv->GetKey() ); + lexer.ReadToken( &type ); + lexer.ReadToken( &bodyName ); + lexer.FreeSource(); + + // if there already exists a bind constraint for this body + if ( bodyName.Icmp( bindBodyName ) == 0 ) { + // delete the bind constraint + af->spawnArgs.Delete( kv->GetKey() ); + kv = NULL; + } + + kv = af->spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + sprintf( key, "bindConstraint bind%d", largestNum ); + sprintf( value, "ballAndSocket %s %s", bindBodyName.c_str(), af->GetAnimator()->GetJointName( joint ) ); + + af->spawnArgs.Set( key, value ); + af->spawnArgs.Set( "bind", "worldspawn" ); + af->Bind( gameLocal.world, true ); +} + +/* +============== +idDragEntity::UnbindSelected +============== +*/ +void idDragEntity::UnbindSelected( void ) { + const idKeyValue *kv; + idAFEntity_Base *af; + + af = static_cast(selected.GetEntity()); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af || !af->IsType( idAFEntity_Base::GetClassType() ) || !af->IsActiveAF() ) { +// RAVEN END + return; + } + + // unbind the selected entity + af->Unbind(); + + // delete all the bind constraints + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + selected.GetEntity()->spawnArgs.Delete( kv->GetKey() ); + kv = selected.GetEntity()->spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + } + + // delete any bind information + af->spawnArgs.Delete( "bind" ); + af->spawnArgs.Delete( "bindToJoint" ); + af->spawnArgs.Delete( "bindToBody" ); +} + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ + +/* +============== +idEditEntities::idEditEntities +============== +*/ +idEditEntities::idEditEntities( void ) { + selectableEntityClasses.Clear(); + nextSelectTime = 0; +} + +// RAVEN BEGIN +// bdube: made this special to edit entities +/* +============= +idEditEntities::FindTraceEntity +============= +*/ +idEntity* idEditEntities::FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ) { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent != skip && EntityIsSelectable ( ent ) ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} +// RAVEN END + +/* +============= +idEditEntities::SelectEntity +============= +*/ +bool idEditEntities::SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ) { + idVec3 end; + idEntity *ent; + + if ( !g_editEntityMode.GetInteger() || selectableEntityClasses.Num() == 0 ) { + return false; + } + + if ( gameLocal.time < nextSelectTime ) { + return true; + } + nextSelectTime = gameLocal.time + 300; + + end = origin + dir * 4096.0f; + +// RAVEN BEGIN +// bdube: more generic + ent = NULL; + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + ent = FindTraceEntity( origin, end, skip ); + if ( ent ) { + break; + } + } + if ( !ent ) { + return false; + } + + ClearSelectedEntities(); + + AddSelectedEntity( ent ); + gameLocal.Printf( "entity #%d: %s '%s'\n", ent->entityNumber, ent->GetClassname(), ent->name.c_str() ); + + if ( gameLocal.editors & EDITOR_ENTVIEW ) { + common->InitTool ( EDITOR_ENTVIEW, &ent->spawnArgs ); + } else { + ent->ShowEditingDialog(); + } + + return true; +// RAVEN END +} + +/* +============= +idEditEntities::AddSelectedEntity +============= +*/ +void idEditEntities::AddSelectedEntity(idEntity *ent) { + ent->fl.selected = true; + selectedEntities.AddUnique(ent); +} + +/* +============== +idEditEntities::RemoveSelectedEntity +============== +*/ +void idEditEntities::RemoveSelectedEntity( idEntity *ent ) { + if ( selectedEntities.Find( ent ) ) { + selectedEntities.Remove( ent ); + } +} + +/* +============= +idEditEntities::ClearSelectedEntities +============= +*/ +void idEditEntities::ClearSelectedEntities() { + int i, count; + + count = selectedEntities.Num(); + for ( i = 0; i < count; i++ ) { + selectedEntities[i]->fl.selected = false; + } + selectedEntities.Clear(); +} + + +/* +============= +idEditEntities::EntityIsSelectable +============= +*/ +bool idEditEntities::EntityIsSelectable( idEntity *ent, idVec4 *color, idStr *text ) { +// RAVEN BEGIN +// bdube: no matter what dont let the player or its entities be selectable + idPlayer* player; + if ( 0 != (player = gameLocal.GetLocalPlayer() )) { + if ( ent == player || ent == player->GetWeaponViewModel ( ) || ent == player->GetWeaponWorldModel ( ) ) { + return false; + } + } + for ( int i = 0; i < selectableEntityClasses.Num(); i++ ) { + if ( ent->IsType ( *selectableEntityClasses[i].typeInfo ) ) { +// RAVEN END + if ( text ) { + *text = selectableEntityClasses[i].textKey; + } + if ( color ) { + if ( ent->fl.selected ) { + *color = colorRed; + } else { + switch( i ) { + case 1 : + *color = colorYellow; + break; + case 2 : + *color = colorBlue; + break; + default: + *color = colorGreen; + } + } + } + return true; + } + } + return false; +} + +/* +============= +idEditEntities::DisplayEntities +============= +*/ +void idEditEntities::DisplayEntities( void ) { + idEntity *ent; + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + selectableEntityClasses.Clear(); + selectedTypeInfo_t sit; + + switch( g_editEntityMode.GetInteger() ) { + case 1: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idLight::GetClassType(); +// RAVEN END + sit.textKey = "texture"; + selectableEntityClasses.Append( sit ); + break; + case 2: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idSound::GetClassType(); +// scork: added secondary "name" field as well (Zack request) + sit.textKey = "s_shader|name"; + selectableEntityClasses.Append( sit ); +// scork: Zack (reasonably enough) doesn't want the lights displayed when editing sounds +// sit.typeInfo = &idLight::GetClassType(); +// sit.textKey = "texture"; +// selectableEntityClasses.Append( sit ); +// RAVEN END + break; + case 3: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAFEntity_Base::GetClassType(); +// RAVEN END + sit.textKey = "articulatedFigure"; + selectableEntityClasses.Append( sit ); + break; + case 4: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idFuncEmitter::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; + case 5: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idAI::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 6: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "name"; + selectableEntityClasses.Append( sit ); + break; + case 7: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &idEntity::GetClassType(); +// RAVEN END + sit.textKey = "model"; + selectableEntityClasses.Append( sit ); + break; +// RAVEN BEGIN +// bdube: added fx entities + case 8: +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + sit.typeInfo = &rvEffect::GetClassType(); +// RAVEN END + sit.textKey = "fx"; + selectableEntityClasses.Append ( sit ); + break; +// RAVEN END + default: + return; + } + + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + + viewBounds.ExpandSelf( g_editEntityDistance.GetFloat() ); +// RAVEN BEGIN +// scork: changed from 128 to 256 so we can see speaker ent descriptions before getting right up to them +// rhummer: Added cvar to adjust the distance for the text too. + viewTextBounds.ExpandSelf( g_editEntityTextDistance.GetFloat() ); +// RAVEN END + + idStr textKey; +// RAVEN BEGIN +// scork: secondary field + idStr textKey2; + idStr strOutput; +// RAVEN END + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + idVec4 color; + + textKey = ""; + if ( !EntityIsSelectable( ent, &color, &textKey ) ) { + continue; + } + +// RAVEN BEGIN +// scork: handle optional secondary field + textKey2 = ""; + int iIndex = textKey.Find('|'); + if (iIndex >= 0) + { + textKey2 = textKey.Mid ( iIndex+1, textKey.Length()-(iIndex+1) ); // hmmm, they emulate 99% of MS CString but don't have a single-param Mid() func? + textKey = textKey.Left( iIndex ); + } +// RAVEN END + + + bool drawArrows = false; +// RAVEN BEGIN +// bdube: added + bool drawDirection = false; +// RAVEN END +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetType() == &idAFEntity_Base::GetClassType() ) { +// RAVEN END + if ( !static_cast(ent)->IsActiveAF() ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &idSound::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) + { + drawArrows = true; + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag |= SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + else + { + int iFlag = ent->GetRefSoundShaderFlags(); + iFlag &= ~SSF_HILITE; + ent->SetRefSoundShaderFlags( iFlag ); + } + ent->UpdateSound(); + const idSoundShader * ss = declManager->FindSound( ent->spawnArgs.GetString( textKey ) ); + if ( ss->HasDefaultSound() || ss->base->GetState() == DS_DEFAULTED ) { + color.Set( 1.0f, 0.0f, 1.0f, 1.0f ); + } +// RAVEN BEGIN +// bdube: added +// jnewquist: Use accessor for static class type + } else if ( ent->GetType() == &rvEffect::GetClassType() ) { + drawDirection = true; + if ( ent->fl.selected ) { + drawArrows = true; + } + } else if ( ent->GetType() == &idFuncEmitter::GetClassType() ) { +// RAVEN END + if ( ent->fl.selected ) { + drawArrows = true; + } + } + + if ( !viewBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + continue; + } + + gameRenderWorld->DebugBounds( color, idBounds( ent->GetPhysics()->GetOrigin() ).Expand( 8 ) ); + if ( drawArrows ) { + idVec3 start = ent->GetPhysics()->GetOrigin(); + idVec3 end = start + idVec3( 1, 0, 0 ) * 20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x+", end + idVec3( 4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 1, 0, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorWhite, start, end, 2 ); + gameRenderWorld->DrawText( "x-", end + idVec3( -4, 0, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * +20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y+", end + idVec3( 0, 4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 1, 0 ) * -20.0f; + gameRenderWorld->DebugArrow( colorGreen, start, end, 2 ); + gameRenderWorld->DrawText( "y-", end + idVec3( 0, -4, 0 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * +20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z+", end + idVec3( 0, 0, 4 ), 0.15f, colorWhite, axis ); + end = start + idVec3( 0, 0, 1 ) * -20.0f; + gameRenderWorld->DebugArrow( colorBlue, start, end, 2 ); + gameRenderWorld->DrawText( "z-", end + idVec3( 0, 0, -4 ), 0.15f, colorWhite, axis ); + } + +// RAVEN BEGIN +// bdube: added + if ( drawDirection ) { + idVec3 start = ent->GetPhysics()->GetOrigin ( ); + idVec3 end = start + ent->GetPhysics()->GetAxis()[0] * 35.0f; + gameRenderWorld->DebugArrow ( colorYellow, start, end, 6 ); + } +// RAVEN END + + if ( textKey.Length() ) { +// RAVEN BEGIN +// scork: handle optional secondary field, plus only call GetString when bounds are within view + if ( viewTextBounds.ContainsPoint( ent->GetPhysics()->GetOrigin() ) ) { + strOutput = ent->spawnArgs.GetString( textKey ); + if (!textKey2.IsEmpty()) + { + strOutput += " ( "; + strOutput += ent->spawnArgs.GetString( textKey2 ); + strOutput += " )"; + } + gameRenderWorld->DrawText( strOutput.c_str(), ent->GetPhysics()->GetOrigin() + idVec3(0, 0, 12), 0.25, colorWhite, axis, 1 ); + } +// RAVEN END + } + } +} + + +/* +=============================================================================== + + idGameEdit + +=============================================================================== +*/ + +idGameEdit gameEditLocal; +idGameEdit * gameEdit = &gameEditLocal; + + +/* +============= +idGameEdit::GetSelectedEntities +============= +*/ +int idGameEdit::GetSelectedEntities( idEntity *list[], int max ) { + int num = 0; + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + list[num++] = ent; + if ( num >= max ) { + break; + } + } + } + return num; +} + +/* +============= +idGameEdit::TriggerSelected +============= +*/ +void idGameEdit::TriggerSelected() { + idEntity *ent; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->fl.selected ) { + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + } + } +} + +/* +================ +idGameEdit::ClearEntitySelection +================ +*/ +void idGameEdit::ClearEntitySelection() { + idEntity *ent; + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + ent->fl.selected = false; + } +// RAVEN BEGIN +// bdube: fixed potential crash + if ( gameLocal.editEntities ) { + gameLocal.editEntities->ClearSelectedEntities(); + } +// RAVEN END +} + +/* +================ +idGameEdit::AddSelectedEntity +================ +*/ +void idGameEdit::AddSelectedEntity( idEntity *ent ) { +// RAVEN BEGIN +// mekberg: fixed crash + if ( ent && gameLocal.editEntities ) { + gameLocal.editEntities->AddSelectedEntity( ent ); + } +// RAVEN END +} + +/* +================ +idGameEdit::FindEntityDefDict +================ +*/ +const idDict *idGameEdit::FindEntityDefDict( const char *name, bool makeDefault ) const { + return gameLocal.FindEntityDefDict( name, makeDefault ); +} + +/* +================ +idGameEdit::SpawnEntityDef +================ +*/ +void idGameEdit::SpawnEntityDef( const idDict &args, idEntity **ent ) { + gameLocal.SpawnEntityDef( args, ent ); +} + +/* +================ +idGameEdit::FindEntity +================ +*/ +idEntity *idGameEdit::FindEntity( const char *name ) const { + return gameLocal.FindEntity( name ); +} + +/* +============= +idGameEdit::GetUniqueEntityName + +generates a unique name for a given classname +============= +*/ +const char *idGameEdit::GetUniqueEntityName( const char *classname ) const { + int id; + static char name[1024]; + + // can only have MAX_GENTITIES, so if we have a spot available, we're guaranteed to find one + for( id = 0; id < MAX_GENTITIES; id++ ) { + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + if ( !gameLocal.FindEntity( name ) ) { + return name; + } + } + + // id == MAX_GENTITIES + 1, which can't be in use if we get here + idStr::snPrintf( name, sizeof( name ), "%s_%d", classname, id ); + return name; +} + +/* +================ +idGameEdit::EntityGetOrigin +================ +*/ +void idGameEdit::EntityGetOrigin( idEntity *ent, idVec3 &org ) const { + if ( ent ) { + org = ent->GetPhysics()->GetOrigin(); + } +} + +/* +================ +idGameEdit::EntityGetAxis +================ +*/ +void idGameEdit::EntityGetAxis( idEntity *ent, idMat3 &axis ) const { + if ( ent ) { + axis = ent->GetPhysics()->GetAxis(); + } +} + +/* +================ +idGameEdit::EntitySetOrigin +================ +*/ +void idGameEdit::EntitySetOrigin( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->SetOrigin( org ); + } +} + +/* +================ +idGameEdit::EntitySetAxis +================ +*/ +void idGameEdit::EntitySetAxis( idEntity *ent, const idMat3 &axis ) { + if ( ent ) { + ent->SetAxis( axis ); + } +} + +/* +================ +idGameEdit::EntitySetColor +================ +*/ +void idGameEdit::EntitySetColor( idEntity *ent, const idVec3 color ) { + if ( ent ) { + ent->SetColor( color ); + } +} + +/* +================ +idGameEdit::EntityTranslate +================ +*/ +void idGameEdit::EntityTranslate( idEntity *ent, const idVec3 &org ) { + if ( ent ) { + ent->GetPhysics()->Translate( org ); + } +} + +/* +================ +idGameEdit::EntityGetSpawnArgs +================ +*/ +// RAVEN BEGIN +// scork: const-qualified 'ent' so other things would compile +const idDict *idGameEdit::EntityGetSpawnArgs( const idEntity *ent ) const { +// RAVEN END + if ( ent ) { + return &ent->spawnArgs; + } + return NULL; +} + +/* +================ +idGameEdit::EntityUpdateChangeableSpawnArgs +================ +*/ +void idGameEdit::EntityUpdateChangeableSpawnArgs( idEntity *ent, const idDict *dict ) { + if ( ent ) { + ent->UpdateChangeableSpawnArgs( dict ); + } +} + +/* +================ +idGameEdit::EntityChangeSpawnArgs +================ +*/ +void idGameEdit::EntityChangeSpawnArgs( idEntity *ent, const idDict *newArgs ) { + if ( ent ) { + for ( int i = 0 ; i < newArgs->GetNumKeyVals () ; i ++ ) { + const idKeyValue *kv = newArgs->GetKeyVal( i ); + + if ( kv->GetValue().Length() > 0 ) { + ent->spawnArgs.Set ( kv->GetKey() ,kv->GetValue() ); + } else { + ent->spawnArgs.Delete ( kv->GetKey() ); + } + } + } +} + +/* +================ +idGameEdit::EntityUpdateVisuals +================ +*/ +void idGameEdit::EntityUpdateVisuals( idEntity *ent ) { + if ( ent ) { + ent->UpdateVisuals(); + } +} + +/* +================ +idGameEdit::EntitySetModel +================ +*/ +void idGameEdit::EntitySetModel( idEntity *ent, const char *val ) { + if ( ent ) { + ent->spawnArgs.Set( "model", val ); + ent->SetModel( val ); + } +} + +/* +================ +idGameEdit::EntityStopSound +================ +*/ +void idGameEdit::EntityStopSound( idEntity *ent ) { + if ( ent ) { + ent->StopSound( SND_CHANNEL_ANY, false ); + } +} + +/* +================ +idGameEdit::EntityDelete +================ +*/ +void idGameEdit::EntityDelete( idEntity *ent ) { + delete ent; +} + +// RAVEN BEGIN +// bdube: added +/* +================ +idGameEdit::EntityGetRenderEntity +================ +*/ +renderEntity_t* idGameEdit::EntityGetRenderEntity ( idEntity* ent ) { + return ent->GetRenderEntity(); +} + +/* +================ +idGameEdit::EntityGetName +================ +*/ +const char* idGameEdit::EntityGetName ( idEntity* ent ) const { + if ( !ent ) { + return ""; + } + return ent->GetName(); +} + +/* +================ +idGameEdit::EntityGetClassname +================ +*/ +const char* idGameEdit::EntityGetClassname ( idEntity* ent ) const { + return ent->GetType()->classname; +} + +/* +================ +idGameEdit::EntityIsDerivedFrom +================ +*/ +bool idGameEdit::EntityIsDerivedFrom ( idEntity* ent, const char* classname ) const { + idTypeInfo* type; + type = idClass::GetClass ( classname ); + if ( !type ) { + return false; + } + + return ent->IsType ( *type ); +} + +/* +================ +idGameEdit::EntityIsValid +================ +*/ +int idGameEdit::EntityToSafeId ( idEntity* ent ) const { + if ( !ent ) { + return 0; + } + return ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameEdit::EntityFromSafeId +================ +*/ +idEntity *idGameEdit::EntityFromSafeId( int safeID ) const { + int entityNum = safeID & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( safeID >> GENTITYNUM_BITS ) ) ) { + return gameLocal.entities[ entityNum ]; + } + return NULL; +} + +/* +================ +idGameEdit::EntitySetSkin +================ +*/ +void idGameEdit::EntitySetSkin ( idEntity* ent, const char* temp ) const { + ent->SetSkin ( declManager->FindSkin ( temp ) ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityClearSkin ( idEntity* ent ) const { + ent->ClearSkin ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityShow ( idEntity* ent ) const { + ent->Show ( ); +} + +/* +================ +idGameEdit::EntityClearSkin +================ +*/ +void idGameEdit::EntityHide ( idEntity* ent ) const { + ent->Hide ( ); +} + +/* +================ +idGameEdit::EntityGetBounds +================ +*/ +void idGameEdit::EntityGetBounds ( idEntity* ent, idBounds &bounds ) const { + bounds = ent->GetRenderEntity()->bounds; +} + +/* +================ +idGameEdit::EntityPlayAnim +================ +*/ +int idGameEdit::EntityPlayAnim ( idEntity* ent, int animNum, int time, int blendtime ) { + if ( !ent->GetAnimator ( ) ) { + return 0; + } + ent->GetAnimator()->PlayAnim ( ANIMCHANNEL_ALL, animNum, time, blendtime ); + ent->GetAnimator()->ServiceAnims ( time, time ); + return ent->GetAnimator()->CurrentAnim( ANIMCHANNEL_ALL )->GetEndTime ( ); +} + +/* +================ +idGameEdit::EntitySetFrame +================ +*/ +void idGameEdit::EntitySetFrame ( idEntity* ent, int animNum, int frame, int time, int blendtime ) { + idAnimator* animator; + + animator = ent->GetAnimator ( ); + if ( !animator ) { + return; + } + + animator->ClearAllAnims ( time, time ); + + // Move to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, frame, frame, 1.0f, 0 }; + animator->SetFrame ( ANIMCHANNEL_ALL, animNum, frameBlend ); +// RAVEN END + animator->ForceUpdate ( ); +} + +/* +================ +idGameEdit::EntityGetDelta +================ +*/ +void idGameEdit::EntityGetDelta ( idEntity* ent, int fromTime, int toTime, idVec3& delta ) { + ent->GetAnimator()->GetDelta ( fromTime, toTime, delta ); +} + +/* +================ +idGameEdit::EntityRemoveOriginOffset +================ +*/ +void idGameEdit::EntityRemoveOriginOffset ( idEntity* ent, bool remove ) { + ent->GetAnimator()->RemoveOriginOffset ( remove ); +} + +/* +================ +idGameEdit::EntityStopAllEffects +================ +*/ +void idGameEdit::EntityStopAllEffects ( idEntity* ent ) { + ent->StopAllEffects ( ); + ent->StopSound ( SND_CHANNEL_ANY, false ); +} + +// RAVEN BEGIN +// scork: some accessor functions for various utils +idEntity *idGameEdit::EntityGetNextTeamEntity( idEntity *pEnt ) const { + return pEnt->GetNextTeamEntity(); +} +void idGameEdit::GetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum, idAngles *deltaViewAngles ) const +{ + game->GetPlayerInfo( v3Origin, mat3Axis, PlayerNum, deltaViewAngles ); +} + +void idGameEdit::SetPlayerInfo( idVec3 &v3Origin, idMat3 &mat3Axis, int PlayerNum ) const +{ + game->SetPlayerInfo( v3Origin, mat3Axis, PlayerNum ); +} +void idGameEdit::EntitySetName( idEntity* pEnt, const char *psName ) +{ + pEnt->SetName( psName ); +} +// RAVEN END + +/* +================ +idGameEdit::LightSetParms +================ +*/ +void idGameEdit::LightSetParms ( idEntity* ent, int maxLevel, int currentLevel, float radius ) { + int data; + idLight* light; + + // Switch to a light entity + light = dynamic_cast(ent); + if ( !light ) + { + return; + } + + light->ProcessEvent ( &EV_Light_SetMaxLightLevel, maxLevel ); + light->ProcessEvent ( &EV_Light_SetCurrentLightLevel, (int)currentLevel ); + + (*(float*)&data) = radius; + light->ProcessEventArgPtr ( &EV_Light_SetRadius, &data ); + + light->SetLightLevel(); +} +// RAVEN END + +/* +================ +idGameEdit::PlayerIsValid +================ +*/ +bool idGameEdit::PlayerIsValid() const { + return ( gameLocal.GetLocalPlayer() != NULL ); +} + +/* +================ +idGameEdit::PlayerGetOrigin +================ +*/ +void idGameEdit::PlayerGetOrigin( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); +} + +/* +================ +idGameEdit::PlayerGetAxis +================ +*/ +void idGameEdit::PlayerGetAxis( idMat3 &axis ) const { + axis = gameLocal.GetLocalPlayer()->GetPhysics()->GetAxis(); +} + +/* +================ +idGameEdit::PlayerGetViewAngles +================ +*/ +void idGameEdit::PlayerGetViewAngles( idAngles &angles ) const { + angles = gameLocal.GetLocalPlayer()->viewAngles; +} + +/* +================ +idGameEdit::PlayerGetEyePosition +================ +*/ +void idGameEdit::PlayerGetEyePosition( idVec3 &org ) const { + org = gameLocal.GetLocalPlayer()->GetEyePosition(); +} + + +/* +================ +idGameEdit::MapGetEntityDict +================ +*/ +const idDict *idGameEdit::MapGetEntityDict( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + return &mapent->epairs; + } + } + return NULL; +} + +/* +================ +idGameEdit::MapSave +================ +*/ +void idGameEdit::MapSave( const char *path ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { +// RAVEN BEGIN +// rjohnson: added entity export + if (mapFile->HasExportEntities()) { + mapFile->WriteExport( (path) ? path : mapFile->GetName() ); + } else { + if ( path ) { + mapFile->Write( path, ".map"); + } else { + idStr osPath; + osPath = mapFile->GetName ( ); + osPath.DefaultFileExtension ( ".map" ); + idFile* file = fileSystem->OpenFileRead ( osPath ); + if ( file ) { + osPath = file->GetFullPath ( ); + fileSystem->CloseFile ( file ); + mapFile->Write ( osPath, ".map", false ); + } else { + mapFile->Write ( file->GetName(), ".map" ); + } + } + } +// RAVEN END + } +} + +// RAVEN BEGIN +// rjohnson: added entity export +bool idGameEdit::MapHasExportEntities( void ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if (mapFile) { + return mapFile->HasExportEntities(); + } + return false; +} +// scork: simple query function for the sound editor +// cdr: changed to also return the full string name of the map file (still compatable as a bool test) +const char* idGameEdit::MapLoaded( void ) const { + + const char *psMapName = gameLocal.GetMapName(); + if (psMapName && psMapName[0]) { + return psMapName; + } + return 0; +} + +// cdr: AASTactical +idAASFile* idGameEdit::GetAASFile( int i ) { + if (gameLocal.GetAAS( i )) { + return gameLocal.GetAAS( i )->GetFile(); + } + return 0; +} +// RAVEN END + +/* +================ +idGameEdit::MapSetEntityKeyVal +================ +*/ +void idGameEdit::MapSetEntityKeyVal( const char *name, const char *key, const char *val ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + mapent->epairs.Set( key, val ); + } + } +} + +/* +================ +idGameEdit::MapCopyDictToEntity +================ +*/ +void idGameEdit::MapCopyDictToEntity( const char *name, const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + for ( int i = 0; i < dict->GetNumKeyVals(); i++ ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + const char *key = kv->GetKey(); + const char *val = kv->GetValue(); + mapent->epairs.Set( key, val ); + } + } + } +} + + + +/* +================ +idGameEdit::MapGetUniqueMatchingKeyVals +================ +*/ +int idGameEdit::MapGetUniqueMatchingKeyVals( const char *key, const char *list[], int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + for ( int i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *ent = mapFile->GetEntity( i ); + if ( ent ) { + const char *k = ent->epairs.GetString( key ); + if ( k && *k && count < max ) { + list[count++] = k; + } + } + } + } + return count; +} + +/* +================ +idGameEdit::MapAddEntity +================ +*/ +void idGameEdit::MapAddEntity( const idDict *dict ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = new idMapEntity(); + ent->epairs = *dict; + mapFile->AddEntity( ent ); + } +} + +/* +================ +idGameEdit::MapRemoveEntity +================ +*/ +void idGameEdit::MapRemoveEntity( const char *name ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile ) { + idMapEntity *ent = mapFile->FindEntity( name ); + if ( ent ) { + mapFile->RemoveEntity( ent ); + } + } +} + + +/* +================ +idGameEdit::MapGetEntitiesMatchignClassWithString +================ +*/ +int idGameEdit::MapGetEntitiesMatchingClassWithString( const char *classname, const char *match, const char *list[], const int max ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + int count = 0; + if ( mapFile ) { + int entCount = mapFile->GetNumEntities(); + for ( int i = 0 ; i < entCount; i++ ) { + idMapEntity *ent = mapFile->GetEntity(i); + if (ent) { + idStr work = ent->epairs.GetString("classname"); + if ( work.Icmp( classname ) == 0 ) { + if ( match && *match ) { + work = ent->epairs.GetString( "soundgroup" ); + if ( count < max && work.Icmp( match ) == 0 ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } else if ( count < max ) { + list[count++] = ent->epairs.GetString( "name" ); + } + } + } + } + } + return count; +} + + +/* +================ +idGameEdit::MapEntityTranslate +================ +*/ +void idGameEdit::MapEntityTranslate( const char *name, const idVec3 &v ) const { + idMapFile *mapFile = gameLocal.GetLevelMap(); + if ( mapFile && name && *name ) { + idMapEntity *mapent = mapFile->FindEntity( name ); + if ( mapent ) { + idVec3 origin; + mapent->epairs.GetVector( "origin", "", origin ); + origin += v; + mapent->epairs.SetVector( "origin", origin ); + } + } +} + +// RAVEN BEGIN +// bdube: new game edit stuff +/* +================ +idGameEdit::PlayerTraceFromEye +================ +*/ +bool idGameEdit::PlayerTraceFromEye ( trace_t &results, float length, int contentMask ) { + idVec3 start; + idVec3 end; + idAngles angles; + + PlayerGetEyePosition( start ); + PlayerGetEyePosition( end ); + PlayerGetViewAngles ( angles ); + + end += angles.ToForward() * length; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint ( gameLocal.GetLocalPlayer(), results, start, end, contentMask, gameLocal.GetLocalPlayer() ); +// RAVEN END +} + +/* +================ +idGameEdit::EffectRefreshTemplate +================ +*/ +void idGameEdit::EffectRefreshTemplate ( const idDecl *effect ) const { + rvClientEntity* cent; + + // Restart all effects + for ( cent = gameLocal.clientSpawnedEntities.Next(); cent; cent = cent->spawnNode.Next() ) { + if ( cent->IsType ( rvClientEffect::GetClassType() ) ) { + rvClientEffect* clientEffect; + clientEffect = static_cast( cent ); + if ( clientEffect->GetEffectIndex ( ) == effect->Index() ) { + clientEffect->Restart ( ); + } + } + } +} + +/* +================ +idGameEdit::GetGameTime +================ +*/ +int idGameEdit::GetGameTime ( int *previous ) const { + if ( previous ) { + *previous = gameLocal.previousTime; + } + + return gameLocal.time; +} + +/* +================ +idGameEdit::SetGameTime +================ +*/ +void idGameEdit::SetGameTime ( int time ) const { + gameLocal.time = time; + gameLocal.previousTime = time; +} + +/* +================ +idGameEdit::TracePoint +================ +*/ +bool idGameEdit::TracePoint ( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.TracePoint( gameLocal.GetLocalPlayer(), results, start, end, contentMask, NULL ); +// RAVEN END +} + +/* +================ +idGameEdit::CacheDictionaryMedia +================ +*/ +void idGameEdit::CacheDictionaryMedia ( const idDict* dict ) const { + gameLocal.CacheDictionaryMedia ( dict ); +} + +/* +================ +idGameEdit::SetCamera +================ +*/ +void idGameEdit::SetCamera ( idEntity* camera ) const { + gameLocal.SetCamera ( dynamic_cast(camera) ); +} + +/* +================ +idGameEdit::ScriptGetStatementLineNumber +================ +*/ +int idGameEdit::ScriptGetStatementLineNumber ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).linenumber; +} + +/* +================ +idGameEdit::ScriptGetStatementFileName +================ +*/ +const char* idGameEdit::ScriptGetStatementFileName ( idProgram* program, int instructionPointer ) const { + return program->GetFilename ( program->GetStatement ( instructionPointer ).file ); +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetStatementOperator ( idProgram* program, int instructionPointer ) const { + return program->GetStatement ( instructionPointer ).op; +} + +/* +================ +idGameEdit::ScriptGetCurrentFunction +================ +*/ +void* idGameEdit::ScriptGetCurrentFunction ( idInterpreter* interpreter ) const { + return (void*)interpreter->GetCurrentFunction ( ); +} + +/* +================ +idGameEdit::ScriptGetCurrentFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCurrentFunctionName ( idInterpreter* interpreter ) const { + if ( interpreter->GetCurrentFunction ( ) ) { + return interpreter->GetCurrentFunction ( )->Name(); + } + return ""; +} + +/* +================ +idGameEdit::ScriptGetStatementOperator +================ +*/ +int idGameEdit::ScriptGetCallstackDepth ( idInterpreter* interpreter ) const { + return interpreter->GetCallstackDepth ( ); +} + +/* +================ +idGameEdit::ScriptGetCallstackFunction +================ +*/ +void* idGameEdit::ScriptGetCallstackFunction ( idInterpreter* interpreter, int depth ) const { + return (void*)interpreter->GetCallstack ( )[depth].f; +} + +/* +================ +idGameEdit::ScriptGetCallstackFunctionName +================ +*/ +const char* idGameEdit::ScriptGetCallstackFunctionName ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].f->Name(); +} + +/* +================ +idGameEdit::ScriptGetCallstackStatement +================ +*/ +int idGameEdit::ScriptGetCallstackStatement ( idInterpreter* interpreter, int depth ) const { + return interpreter->GetCallstack()[depth].s; +} + +/* +================ +idGameEdit::ScriptIsReturnOperator +================ +*/ +bool idGameEdit::ScriptIsReturnOperator ( int op ) const { + return op == OP_RETURN; +} + +/* +================ +idGameEdit::ScriptGetRegisterValue +================ +*/ +const char* idGameEdit::ScriptGetRegisterValue ( idInterpreter* interpreter, const char* varname, int callstackDepth ) const { + static char value[4096]; + idStr out; + + value[0] = '\0'; + if ( interpreter->GetRegisterValue ( varname, out, callstackDepth ) ) { + idStr::snPrintf ( value, 4095, out.c_str() ); + } + + return value; +} + +/* +================ +idGameEdit::ScriptGetThread +================ +*/ +idThread* idGameEdit::ScriptGetThread ( idInterpreter* interpreter ) const { + return interpreter->GetThread(); +} + +/* +================ +idGameEdit::ThreadGetCount +================ +*/ +int idGameEdit::ThreadGetCount ( void ) { + return idThread::GetThreads().Num(); +} + +/* +================ +idGameEdit::ThreadGetThread +================ +*/ +idThread* idGameEdit::ThreadGetThread ( int index ) { + return idThread::GetThreads()[index]; +} + +/* +================ +idGameEdit::ThreadGetName +================ +*/ +const char* idGameEdit::ThreadGetName ( idThread* thread ) { + return thread->GetThreadName ( ); +} + +/* +================ +idGameEdit::ThreadGetNumber +================ +*/ +int idGameEdit::ThreadGetNumber ( idThread* thread ) { + return thread->GetThreadNum ( ); +} + +/* +================ +idGameEdit::ThreadGetState +================ +*/ +const char* idGameEdit::ThreadGetState ( idThread* thread ) { + if ( thread->IsDying() ) { + return "Dying"; + } else if ( thread->IsWaiting() ) { + return "Waiting"; + } else if ( thread->IsDoneProcessing() ) { + return "Stopped"; + } + + return "Running"; +} + +/* +================ +idGameEdit::GetClassDebugInfo +================ +*/ +void idGameEdit::GetClassDebugInfo ( const idEntity* entity, debugInfoProc_t proc, void* userdata ) { + const_cast( entity )->GetDebugInfo ( proc, userdata ); +} + +/* +================ +idGameEdit::GetGameEntityRegisterTime +================ +*/ +int idGameEdit::GetGameEntityRegisterTime ( void ) const { + return gameLocal.entityRegisterTime; +} + +/* +================ +idGameEdit::GetFirstSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetFirstSpawnedEntity ( void ) const { + return gameLocal.spawnedEntities.Next(); +} + +/* +================ +idGameEdit::GetNextSpawnedEntity +================ +*/ +idEntity* idGameEdit::GetNextSpawnedEntity ( idEntity* from ) const { + if ( !from ) { + return NULL; + } + return from->spawnNode.Next(); +} + + +// RAVEN END + +// RAVEN BEGIN +// mekberg: access to animationlib functions for radiant +void idGameEdit::FlushUnusedAnims ( void ) { + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END +} + +/* +=============================================================================== + + rvModviewModel + + Actor model for modview + +=============================================================================== +*/ + +class rvModviewModel : public idActor { +public: + CLASS_PROTOTYPE( rvModviewModel ); + + rvModviewModel ( void ); + +private: + + void Event_Speak ( const char* lipsync ); +}; + +CLASS_DECLARATION( idActor, rvModviewModel ) + EVENT( AI_Speak, rvModviewModel::Event_Speak ) +END_CLASS + +/* +===================== +rvModviewModel::rvModviewModel +===================== +*/ +rvModviewModel::rvModviewModel ( void ) { +} + +/* +===================== +rvModviewModel::Event_Speak +===================== +*/ +void rvModviewModel::Event_Speak ( const char* lipsync ) { + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + lipsync = spawnArgs.GetString ( lipsync ); + if ( !lipsync || !*lipsync ) { + return; + } + + if ( head ) { + head->StartLipSyncing( lipsync ); + } else { + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +// RAVEN END + diff --git a/src/mpgame/GameEdit.h b/src/mpgame/GameEdit.h new file mode 100644 index 0000000..53e7a3f --- /dev/null +++ b/src/mpgame/GameEdit.h @@ -0,0 +1,119 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_EDIT_H__ +#define __GAME_EDIT_H__ + + +/* +=============================================================================== + + Ingame cursor. + +=============================================================================== +*/ + +class idCursor3D : public idEntity { +public: + CLASS_PROTOTYPE( idCursor3D ); + + idCursor3D( void ); + ~idCursor3D( void ); + + void Spawn( void ); + void Present( void ); + void Think( void ); + + idForce_Drag drag; + idVec3 draggedPosition; +}; + + +/* +=============================================================================== + + Allows entities to be dragged through the world with physics. + +=============================================================================== +*/ + +class idDragEntity { +public: + idDragEntity( void ); + ~idDragEntity( void ); + + void Clear(); + void Update( idPlayer *player ); + void SetSelected( idEntity *ent ); + idEntity * GetSelected( void ) const { return selected.GetEntity(); } + void DeleteSelected( void ); + void BindSelected( void ); + void UnbindSelected( void ); + +private: + idEntityPtr dragEnt; // entity being dragged + jointHandle_t joint; // joint being dragged + int id; // id of body being dragged + idVec3 localEntityPoint; // dragged point in entity space + idVec3 localPlayerPoint; // dragged point in player space + idStr bodyName; // name of the body being dragged + idCursor3D * cursor; // cursor entity + idEntityPtr selected; // last dragged entity + + void StopDrag( void ); +}; + + +/* +=============================================================================== + + Handles ingame entity editing. + +=============================================================================== +*/ +typedef struct selectedTypeInfo_s { + idTypeInfo *typeInfo; + idStr textKey; +} selectedTypeInfo_t; + +class idEditEntities { +public: + idEditEntities( void ); + bool SelectEntity( const idVec3 &origin, const idVec3 &dir, const idEntity *skip ); + void AddSelectedEntity( idEntity *ent ); + void RemoveSelectedEntity( idEntity *ent ); + void ClearSelectedEntities( void ); + void DisplayEntities( void ); + bool EntityIsSelectable( idEntity *ent, idVec4 *color = NULL, idStr *text = NULL ); +// RAVEN BEGIN +// bdube: added + idEntity* FindTraceEntity( idVec3 start, idVec3 end, const idEntity *skip ); +// RAVEN END +private: + int nextSelectTime; + idList selectableEntityClasses; + idList selectedEntities; +}; + +#endif /* !__GAME_EDIT_H__ */ diff --git a/src/mpgame/Game_Debug.cpp b/src/mpgame/Game_Debug.cpp new file mode 100644 index 0000000..8e71983 --- /dev/null +++ b/src/mpgame/Game_Debug.cpp @@ -0,0 +1,396 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Game_Debug.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +rvGameDebug gameDebug; + +/* +=============================================================================== + + rvGameDebug + +=============================================================================== +*/ + +/* +================ +rvGameDebug::rvGameDebug +================ +*/ +rvGameDebug::rvGameDebug ( void ) { +} + +/* +================ +rvGameDebug::Init +================ +*/ +void rvGameDebug::Init ( void ) { + focusEntity = NULL; + overrideEntity = NULL; + currentHud = NULL; + memset ( &hud, 0, sizeof(hud) ); + + jumpIndex = -1; + jumpPoints.Clear ( ); +} + +/* +================ +rvGameDebug::Shutdown +================ +*/ +void rvGameDebug::Shutdown ( void ) { + nonGameState.Clear ( ); + gameStats.Clear ( ); + + currentHud = NULL; + focusEntity = NULL; + overrideEntity = NULL; + + memset ( &hud, 0, sizeof(hud) ); +} + +/* +================ +rvGameDebug::Think +================ +*/ +void rvGameDebug::BeginFrame ( void ) { + int hudIndex; + + inFrame = true; + + hudIndex = g_showDebugHud.GetInteger(); + if ( hudIndex <= 0 ) { + focusEntity = NULL; + currentHud = NULL; + return; + } + + // Update the current debug hud if the cvar has changed + if ( g_showDebugHud.IsModified() || !currentHud ) { + if ( hudIndex > DBGHUD_MAX ) { + g_showDebugHud.SetInteger( 0 ); + focusEntity = NULL; + currentHud = NULL; + return; + } + + g_showDebugHud.ClearModified( ); + + // If the debug hud hasnt been loaded yet then load it now + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->FindGui( va("guis/debug/hud%d.gui",hudIndex), true, true, true ); + + // If the hud wasnt found auto-generate one + if ( !hud[hudIndex] ) { + hud[hudIndex] = uiManager->Alloc(); + } + } + + // Cache the debug hud state. + currentHud = hud[hudIndex]; + } + + currentHud->ClearState ( ); + + // IF there is an override entity just use that, otherwise find one that + // is in front of the players crosshair + if ( overrideEntity ) { + focusEntity = overrideEntity; + overrideEntity = NULL; + } else { + idPlayer* player; + idVec3 start; + idVec3 end; + trace_t tr; + + player = gameLocal.GetLocalPlayer ( ); + start = player->GetEyePosition(); + end = start + player->viewAngles.ToForward() * 4096.0f; + + gameLocal.TracePoint( player, tr, start, end, MASK_SHOT_BOUNDINGBOX, player ); + if ( tr.fraction < 1.0 && tr.c.entityNum != ENTITYNUM_WORLD ) { + focusEntity = static_cast(gameLocal.entities[ tr.c.entityNum ]); + } else { + focusEntity = NULL; + } + } + + // Automatically add some basic entity information + if ( focusEntity ) { + SetInt ( "entityNumber", focusEntity->entityNumber ); + SetInt ( "entityHealth", focusEntity->health ); + SetString ( "entityName", focusEntity->name ); + SetString ( "entityClass", focusEntity->GetClassname ( ) ); + } + + // General map information + SetString ( "mapname", gameLocal.GetMapName ( ) ); + SetString ( "version", cvarSystem->GetCVarString ( "si_version" ) ); + if ( gameLocal.GetLocalPlayer() ) { + SetString ( "viewpos", gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin().ToString() ); + } +} + +/* +================ +rvGameDebug::EndFrame +================ +*/ +void rvGameDebug::EndFrame ( void ) { + inFrame = false; +} + +/* +================ +rvGameDebug::DrawHud +================ +*/ +void rvGameDebug::DrawHud ( void ) { + if ( !currentHud ) { + return; + } + + // The scratch hud displays key value pairs in a list so + // we need to push the keys into the list + if ( IsHudActive ( DBGHUD_SCRATCH ) ) { + int index; + idDict tempState; + + tempState.Copy ( currentHud->State() ); + currentHud->ClearState ( ); + + for ( index = 0; index < tempState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + + kv = tempState.GetKeyVal ( index ); + SetString ( va("scratchKey_item_%d", index ), kv->GetKey() ); + SetString ( va("scratchValue_item_%d", index ), kv->GetValue() ); + } + } + else { + int index; + for ( index = 0; index < nonGameState.GetNumKeyVals(); index ++ ) { + const idKeyValue* kv; + kv = nonGameState.GetKeyVal ( index ); + currentHud->SetStateString ( kv->GetKey(), kv->GetValue() ); + } + } + + // Activate the hud to ensure lists get updated and redraw it + currentHud->StateChanged ( gameLocal.time ); + currentHud->Redraw( gameLocal.time ); +} + +/* +================ +rvGameDebug::AppendList +================ +*/ +void rvGameDebug::AppendList ( const char* listname, const char* value ) { + if ( !currentHud ) { + return; + } + + int count; + char countName[1024]; + char itemName[1024]; + + idStr::snPrintf ( countName, 1023, "%sCount", listname ); + count = GetInt ( countName ); + + idStr::snPrintf ( itemName, 1023, "%s_item_%d", listname, count ); + SetString ( va("%s_item_%d", listname, count ), value ); + + SetInt ( countName, count + 1 ); +} + +/* +================ +rvGameDebug::Set +================ +*/ +void rvGameDebug::SetInt ( const char* key, int value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateInt( key, value ); + } + } else { + nonGameState.SetInt ( key, value ); + } +} + +void rvGameDebug::SetFloat ( const char* key, float value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateFloat( key, value ); + } + } else { + nonGameState.SetFloat ( key, value ); + } +} + +void rvGameDebug::SetString ( const char* key, const char* value ) { + if ( inFrame ) { + if ( currentHud ) { + currentHud->SetStateString( key, value ); + } + } else { + nonGameState.Set ( key, value ); + } +} + +/* +================ +rvGameDebug::Get +================ +*/ +int rvGameDebug::GetInt ( const char* key ) { + return currentHud ? currentHud->State().GetInt ( key ) : 0; +} + +float rvGameDebug::GetFloat ( const char* key ) { + return currentHud ? currentHud->State().GetFloat ( key ) : 0.0f; +} + +const char* rvGameDebug::GetString ( const char* key ) { + return currentHud ? currentHud->State().GetString ( key ) : ""; +} + +/* +================ +rvGameDebug::SetStat +================ +*/ +void rvGameDebug::SetStatInt ( const char* key, int value ) { + gameStats.SetInt ( key, value ); +} + +void rvGameDebug::SetStatFloat ( const char* key, float value ) { + gameStats.SetFloat ( key, value ); +} + +void rvGameDebug::SetStatString ( const char* key, const char* value ) { + gameStats.Set ( key, value ); +} + +/* +================ +rvGameDebug::GetStat +================ +*/ +int rvGameDebug::GetStatInt ( const char* key ) { + return gameStats.GetInt ( key ); +} + +float rvGameDebug::GetStatFloat ( const char* key ) { + return gameStats.GetFloat ( key ); +} + +const char* rvGameDebug::GetStatString ( const char* key ) { + return gameStats.GetString ( key ); +} + + +/* +================ +rvGameDebug::JumpAdd +================ +*/ +void rvGameDebug::JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ) { + debugJumpPoint_t jump; + jump.name = name; + jump.origin = origin; + jump.angles = angles; + jumpPoints.Append ( jump ); +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( const char* name ) { + int index; + for ( index = 0; index < jumpPoints.Num(); index ++ ) { + if ( !jumpPoints[index].name.Icmp ( name ) ) { + JumpTo ( index ); + return; + } + } +} + +/* +================ +rvGameDebug::JumpTo +================ +*/ +void rvGameDebug::JumpTo ( int jumpIndex ) { + if ( jumpIndex >= jumpPoints.Num() ) { + return; + } + + jumpIndex = jumpIndex; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player ) { + player->Teleport( jumpPoints[jumpIndex].origin, jumpPoints[jumpIndex].angles, NULL ); + } +} + +/* +================ +rvGameDebug::JumpNext +================ +*/ +void rvGameDebug::JumpNext ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + 1 ) % jumpPoints.Num() ); +} + +/* +================ +rvGameDebug::JumpPrev +================ +*/ +void rvGameDebug::JumpPrev ( void ) { + if ( !jumpPoints.Num() ) { + return; + } + JumpTo ( ( jumpIndex + jumpPoints.Num() - 1 ) % jumpPoints.Num() ); +} diff --git a/src/mpgame/Game_Debug.h b/src/mpgame/Game_Debug.h new file mode 100644 index 0000000..0e3cb51 --- /dev/null +++ b/src/mpgame/Game_Debug.h @@ -0,0 +1,119 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Game_Debug.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_DEBUG_H__ +#define __GAME_DEBUG_H__ + +#define DBGHUD_NONE (0) +#define DBGHUD_PLAYER (1<<0) +#define DBGHUD_PHYSICS (1<<1) +#define DBGHUD_AI (1<<2) +#define DBGHUD_VEHICLE (1<<3) +#define DBGHUD_PERFORMANCE (1<<4) +#define DBGHUD_FX (1<<5) +#define DBGHUD_MAPINFO (1<<6) +#define DBGHUD_AI_PERFORM (1<<7) +#define DBGHUD_SCRATCH (1<<31) +#define DBGHUD_ANY (0xFFFFFFFF) + +#define DBGHUD_MAX 32 + +typedef struct debugJumpPoint_s +{ + idStr name; + idVec3 origin; + idAngles angles; + +} debugJumpPoint_t; + +class rvGameDebug { +public: + + rvGameDebug( ); + + void Init ( void ); + void Shutdown ( void ); + void BeginFrame ( void ); + void EndFrame ( void ); + + void SetFocusEntity ( idEntity* focusEnt ); + + bool IsHudActive ( int hudMask, const idEntity* focusEnt = NULL ); + + void DrawHud ( void ); + + void AppendList ( const char* listname, const char* value ); + + void SetInt ( const char* key, int value ); + void SetFloat ( const char* key, float value ); + void SetString ( const char* key, const char* value ); + + int GetInt ( const char* key ); + float GetFloat ( const char* key ); + const char* GetString ( const char* key ); + + void SetStatInt ( const char* key, int value ); + void SetStatFloat ( const char* key, float value ); + void SetStatString ( const char* key, const char* value ); + + int GetStatInt ( const char* key ); + float GetStatFloat ( const char* key ); + const char* GetStatString ( const char* key ); + + void JumpAdd ( const char* name, const idVec3& origin, const idAngles& angles ); + void JumpTo ( const char* name ); + void JumpTo ( int jumpIndex ); + void JumpNext ( void ); + void JumpPrev ( void ); + +private: + + idList jumpPoints; + int jumpIndex; + + idEntityPtr focusEntity; + idEntityPtr overrideEntity; + idUserInterface * hud[DBGHUD_MAX+1]; + idUserInterface * currentHud; + idDict nonGameState, gameStats; + bool inFrame; +}; + +ID_INLINE bool rvGameDebug::IsHudActive ( int hudMask, const idEntity* ent ) { + return (g_showDebugHud.GetInteger() && (hudMask & (1 << (g_showDebugHud.GetInteger()-1))) && (!ent || focusEntity == ent ) ); +} + +ID_INLINE void rvGameDebug::SetFocusEntity ( idEntity* ent ) { + overrideEntity = ent; +} + +extern rvGameDebug gameDebug; + +#endif /* !__GAME_DEBUG_H__ */ diff --git a/src/mpgame/Game_Log.cpp b/src/mpgame/Game_Log.cpp new file mode 100644 index 0000000..0c6cf5b --- /dev/null +++ b/src/mpgame/Game_Log.cpp @@ -0,0 +1,251 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Game_Log.h" + +/* +=============================================================================== + + rvGameLog + +=============================================================================== +*/ + +rvGameLogLocal gameLogLocal; +rvGameLog* gameLog = &gameLogLocal; + +/* +================ +rvGameLogLocal::rvGameLogLocal +================ +*/ +rvGameLogLocal::rvGameLogLocal ( ) { + initialized = false; +} + +/* +================ +rvGameLogLocal::Init +================ +*/ +void rvGameLogLocal::Init ( void ) { + file = NULL; + indexCount = 0; + initialized = true; + + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); +} + +/* +================ +rvGameLogLocal::Shutdown +================ +*/ +void rvGameLogLocal::Shutdown ( void ) { + index.Clear ( ); + frame.Clear ( ); + oldframe.Clear ( ); + + if ( initialized && file ) { + const char* out; + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + file->Flush ( ); + fileSystem->CloseFile ( file ); + file = NULL; + } + initialized = false; +} + +/* +================ +rvGameLogLocal::BeginFrame +================ +*/ +void rvGameLogLocal::BeginFrame ( int time ) { + // See if logging has been turned on or not + if ( g_gamelog.GetBool ( ) != initialized ) { + if ( initialized ) { + Shutdown ( ); + return; + } else { + Init ( ); + } + } else if ( !g_gamelog.GetBool ( ) ) { + return; + } +} + +/* +================ +rvGameLogLocal::EndFrame +================ +*/ +void rvGameLogLocal::EndFrame ( void ) { + int i; + const char* out; + bool wroteTime; + + // Dont do anything if not logging + if ( !g_gamelog.GetBool ( ) ) { + return; + } + + // When not in multiplayer, log the players approx origin and viewangles + if ( !gameLocal.isMultiplayer ) { + idPlayer* player; + player = gameLocal.GetLocalPlayer ( ); + if ( player ) { + Set ( "player0_origin", va("%d %d %d", (int)player->GetPhysics()->GetOrigin()[0], (int)player->GetPhysics()->GetOrigin()[1], (int)player->GetPhysics()->GetOrigin()[2] ) ); + Set ( "player0_angles_yaw", va("%g", (float)player->viewAngles[YAW] ) ); + Set ( "player0_angles_pitch", va("%g", (float)player->viewAngles[PITCH] ) ); + Set ( "player0_buttons", player->usercmd.buttons ); + Set ( "player0_health", player->health ); + Set ( "player0_armor", player->inventory.armor ); + } + } + + if ( !file ) { + idStr mapName; + idStr filename; + mapName = gameLocal.serverInfo.GetString( "si_map" ); + mapName.StripFileExtension ( ); + filename = "logs/" + mapName + "/" + cvarSystem->GetCVarString("win_username") + "_"; + + // Find a unique filename + for ( i = 0; fileSystem->ReadFile( filename + va("%06d.log", i ), NULL, NULL ) > 0; i ++ ); + + // Actually open the file now + file = fileSystem->OpenFileWrite ( filename + va("%06d.log", i ), "fs_cdpath" ); + if ( !file ) { + return; + } + + timer_fps.Stop( ); + timer_fps.Clear ( ); + timer_fps.Start ( ); + } else { + static int fpsIndex; + static float fpsValue[4]; + + timer_fps.Stop ( ); + fpsValue[(fpsIndex++)%4] = 1000.0f / (timer_fps.Milliseconds ( ) + 1); + if ( fpsIndex >= 4 ) { + GAMELOG_SET ( "fps", Min(60,(int)((int)(fpsValue[0] + fpsValue[1] + fpsValue[2] + fpsValue[3]) / 40.0f) * 10) ); + } + timer_fps.Clear ( ); + timer_fps.Start ( ); + } + + // Write out any new indexes that were added this frame + for ( ; indexCount < index.Num(); indexCount ++ ) { + const char* out; + out = va("#%d ", indexCount ); + file->Write ( out, strlen ( out ) ); + file->Write ( index[indexCount].c_str(), index[indexCount].Length() ); + file->Write ( "\r\n", 2 ); + } + + // Write out any data that was added this frame + wroteTime = false; + for ( i = frame.Num() - 1; i >= 0; i -- ) { + // TODO: filter + if ( oldframe[i] != frame[i] ) { + if ( !wroteTime ) { + out = va(":%d %d", gameLocal.time, gameLocal.framenum ); + file->Write ( out, strlen ( out ) ); + wroteTime = true; + } + out = va(" %d \"", i ); + file->Write ( out, strlen(out) ); + file->Write ( frame[i].c_str(), frame[i].Length ( ) ); + file->Write ( "\"", 1 ); + oldframe[i] = frame[i]; + } + } + + if ( wroteTime ) { + file->Write ( "\r\n", 2 ); + file->Flush ( ); + } + + // Clear the frame for next time + for ( i = index.Num() - 1; i >= 0; i -- ) { + frame[i] = ""; + } +} + +/* +================ +rvGameLogLocal::Set +================ +*/ +void rvGameLogLocal::Set ( const char* keyword, const char* value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = value; +} + +void rvGameLogLocal::Set ( const char* keyword, int value ) { + Set ( keyword, va("%d", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, float value ) { + Set ( keyword, va("%g", value ) ); +} + +void rvGameLogLocal::Set ( const char* keyword, bool value ) { + Set ( keyword, va("%d", (int)value ) ); +} + +/* +================ +rvGameLogLocal::Add +================ +*/ +void rvGameLogLocal::Add ( const char* keyword, int value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%d",atoi(frame[i].c_str()) + value ); +} + +void rvGameLogLocal::Add ( const char* keyword, float value ) { + int i; + i = index.AddUnique ( keyword ); + frame.SetNum ( index.Num(), true ); + oldframe.SetNum ( index.Num(), true ); + frame[i] = va("%g",atof(frame[i].c_str()) + value ); +} + + diff --git a/src/mpgame/Game_Log.h b/src/mpgame/Game_Log.h new file mode 100644 index 0000000..8cce723 --- /dev/null +++ b/src/mpgame/Game_Log.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_LOG_H__ +#define __GAME_LOG_H__ + +//============================================================================ + +class rvGameLogLocal : public rvGameLog { +public: + + rvGameLogLocal ( void ); + + virtual void Init ( void ); + virtual void Shutdown ( void ); + + virtual void BeginFrame ( int time ); + virtual void EndFrame ( void ); + + virtual void Set ( const char* keyword, int value ); + virtual void Set ( const char* keyword, float value ); + virtual void Set ( const char* keyword, const char* value ); + virtual void Set ( const char* keyword, bool value ); + + virtual void Add ( const char* keyword, int value ); + virtual void Add ( const char* keyword, float value ); + +protected: + + int lastTime; + int indexCount; + idStrList index; + idStrList frame; + idStrList oldframe; + idFile* file; + bool initialized; + idTimer timer_fps; +}; + +extern rvGameLogLocal gameLogLocal; + +#endif // __GAME_LOG_H__ diff --git a/src/mpgame/Game_local.cpp b/src/mpgame/Game_local.cpp new file mode 100644 index 0000000..4c0ae7f --- /dev/null +++ b/src/mpgame/Game_local.cpp @@ -0,0 +1,8665 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "../sys/AutoVersion.h" + +struct game_name_s { + game_name_s( void ) { + sprintf( string, "Q4MP %s", VERSION_STRING_DOTTED ); + } + char string[256]; +} game_name; + +idCVar gamename( "gamename", game_name.string, CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "" ); + +// RAVEN BEGIN +#include "../bse/BSEInterface.h" +#include "Projectile.h" +#include "client/ClientEffect.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AAS_tactical.h" +#include "Game_Log.h" +// RAVEN END + +//#define UI_DEBUG 1 + +#ifdef GAME_DLL + +idSys * sys = NULL; +idCommon * common = NULL; +idCmdSystem * cmdSystem = NULL; +idCVarSystem * cvarSystem = NULL; +idFileSystem * fileSystem = NULL; +idNetworkSystem * networkSystem = NULL; +idRenderSystem * renderSystem = NULL; +idSoundSystem * soundSystem = NULL; +idRenderModelManager * renderModelManager = NULL; +idUserInterfaceManager * uiManager = NULL; +idDeclManager * declManager = NULL; +idAASFileManager * AASFileManager = NULL; +idCollisionModelManager * collisionModelManager = NULL; + +// RAVEN BEGIN +// jscott: game interface to the fx system +rvBSEManager * bse = NULL; +// RAVEN END + +idCVar * idCVar::staticVars = NULL; + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui +idCVarHelp * idCVarHelp::staticCVarHelps = NULL; +idCVarHelp * idCVarHelp::staticCVarHelpsTail = NULL; +// RAVEN END + +idCVar com_forceGenericSIMD( "com_forceGenericSIMD", "0", CVAR_BOOL|CVAR_SYSTEM, "force generic platform independent SIMD" ); + +#endif + +idRenderWorld * gameRenderWorld = NULL; // all drawing is done to this world + +static gameExport_t gameExport; + +// global animation lib +// RAVEN BEGIN +// jsinger: changed to a pointer to prevent its constructor from allocating +// memory before the unified allocator could be initialized +idAnimManager *animationLib = NULL; +// RAVEN END + +// the rest of the engine will only reference the "game" variable, while all local aspects stay hidden +idGameLocal gameLocal; +idGame * game = &gameLocal; // statically pointed at an idGameLocal + +const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = { + "none", "metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic", + "ricochet", "surftype10", "surftype11", "surftype12", "surftype13", "surftype14", "surftype15" +}; + +/* +=========== +GetGameAPI +============ +*/ + +#if __GNUC__ >= 4 +#pragma GCC visibility push(default) +#endif +extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) { + + if ( import->version == GAME_API_VERSION ) { + + // set interface pointers used by the game + sys = import->sys; + common = import->common; + cmdSystem = import->cmdSystem; + cvarSystem = import->cvarSystem; + fileSystem = import->fileSystem; + networkSystem = import->networkSystem; + renderSystem = import->renderSystem; + soundSystem = import->soundSystem; + renderModelManager = import->renderModelManager; + uiManager = import->uiManager; + declManager = import->declManager; + AASFileManager = import->AASFileManager; + collisionModelManager = import->collisionModelManager; +// RAVEN BEGIN +// jscott: import the fx system + bse = import->bse; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: import the memory system variables +#ifdef _RV_MEM_SYS_SUPPORT + ::currentHeapArena = import->heapArena; + rvSetAllSysHeaps( import->systemHeapArray ); +#endif +// RAVEN END + } + + // set interface pointers used by idLib + idLib::sys = sys; + idLib::common = common; + idLib::cvarSystem = cvarSystem; + idLib::fileSystem = fileSystem; + + // setup export interface + gameExport.version = GAME_API_VERSION; + gameExport.game = game; + gameExport.gameEdit = gameEdit; +// RAVEN BEGIN +// bdube: game log + gameExport.gameLog = gameLog; +// RAVEN END + + return &gameExport; +} +#if __GNUC__ >= 4 +#pragma GCC visibility pop +#endif + +/* +=========== +TestGameAPI +============ +*/ +void TestGameAPI( void ) { + gameImport_t testImport; + gameExport_t testExport; + + testImport.sys = ::sys; + testImport.common = ::common; + testImport.cmdSystem = ::cmdSystem; + testImport.cvarSystem = ::cvarSystem; + testImport.fileSystem = ::fileSystem; + testImport.networkSystem = ::networkSystem; + testImport.renderSystem = ::renderSystem; + testImport.soundSystem = ::soundSystem; + testImport.renderModelManager = ::renderModelManager; + testImport.uiManager = ::uiManager; + testImport.declManager = ::declManager; + testImport.AASFileManager = ::AASFileManager; + testImport.collisionModelManager = ::collisionModelManager; + +// RAVEN BEGIN +// jscott: import the fx system + testImport.bse = ::bse; +// RAVEN END + + testExport = *GetGameAPI( &testImport ); +} + +/* +================ +idGameLocal::BuildModList +================ +*/ +void idGameLocal::BuildModList( ) { + int i; + idStr currentMod; + + int numServers = networkSystem->GetNumScannedServers(); + + if ( filterMod >= 0 && filterMod < modList.Num() ) { + currentMod = modList[ filterMod ]; + } else { + currentMod = ""; + } + + modList.Clear(); + for (i = 0; i < numServers; i++) { + const scannedServer_t *server; + idStr modname; + + server = networkSystem->GetScannedServerInfo( i ); + + server->serverInfo.GetString( "fs_game", "", modname ); + modname.ToLower(); + modList.AddUnique( modname ); + } + + modList.Sort(); + + if ( modList.Num() > 0 && (modList[ 0 ].Cmp( "" ) == 0) ) { + modList.RemoveIndex( 0 ); + } + + filterMod = modList.Num(); + for (i = 0; i < modList.Num(); i++) { + if ( modList[ i ].Icmp( currentMod ) == 0 ) { + filterMod = i; + } + } +} + +/* +================ +FilterByMod +================ +*/ +static int FilterByMod( const int* serverIndex ) { + const scannedServer_t *server; + + if ( gameLocal.filterMod < 0 || gameLocal.filterMod >= gameLocal.modList.Num() ) { + return (int)false; + } + + server = networkSystem->GetScannedServerInfo( *serverIndex ); + + return (int)(gameLocal.modList[ gameLocal.filterMod ].Icmp( server->serverInfo.GetString( "fs_game" ) ) != 0); +} + +static sortInfo_t filterByMod = { + SC_ALL, + NULL, + FilterByMod, + "#str_123006" +}; + +/* +=========== +idGameLocal::idGameLocal +============ +*/ +idGameLocal::idGameLocal() { + Clear(); +} + +/* +=========== +idGameLocal::Clear +============ +*/ +void idGameLocal::Clear( void ) { + int i; + + serverInfo.Clear(); + repeaterInfo.Clear(); + numClients = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + usercmds = NULL; + memset( entities, 0, sizeof( entities ) ); + memset( spawnIds, -1, sizeof( spawnIds ) ); + firstFreeIndex = 0; + num_entities = 0; + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortPushers = false; + sortTeamMasters = false; + persistentLevelInfo.Clear(); + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + random.SetSeed( 0 ); + world = NULL; + frameCommandThread = NULL; + testmodel = NULL; + ShutdownInstances(); + clip.Clear(); + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + clientsPVS[ i ].i = -1; + clientsPVS[ i ].h = -1; + } + freePlayerPVS = false; + pvs.Shutdown(); + sessionCommand.Clear(); + locationEntities = NULL; + editEntities = NULL; + entityHash.Clear( 1024, MAX_GENTITIES ); + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + framenum = 0; + previousTime = 0; + time = 0; + vacuumAreaNum = 0; + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + mapFileName.Clear(); +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped.Clear(); +// RAVEN END + mapFile = NULL; + spawnCount = INITIAL_SPAWN_COUNT; + memset( isMapEntity, 0, sizeof( bool ) * MAX_GENTITIES ); + + camera = NULL; + +// RAVEN BEGIN +// jscott: for portal skies + portalSky = NULL; +// RAVEN END + + aasList.Clear(); + aasNames.Clear(); +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + spawnArgs.Clear(); + gravity.Set( 0, 0, -1 ); + playerPVS.i = -1; + playerPVS.h = -1; + playerConnectedAreas.i = -1; + playerConnectedAreas.h = -1; + gamestate = GAMESTATE_UNINITIALIZED; + skipCinematic = false; + influenceActive = false; + + localClientNum = 0; + isMultiplayer = false; + isServer = false; + isClient = false; + isTVClient = false; + realClientTime = 0; + isNewFrame = true; + entityDefBits = 0; + + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = false; +// RITUAL END + globalMaterial = NULL; + newInfo.Clear(); + lastGUIEnt = NULL; + lastGUI = 0; + + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + ReallocViewers( 0 ); + + maxViewers = 0; + maxViewer = 0; + viewers = NULL; + viewerEntityStates = NULL; + viewerPVS = NULL; + viewerSnapshots = NULL; + viewerUnreliableMessages = NULL; + + eventQueue.Init(); + +// RAVEN BEGIN +// bdube: client entities + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear(); + memset( clientEntities, 0, sizeof( clientEntities ) ); + memset( clientSpawnIds, -1, sizeof( clientSpawnIds ) ); + + gameDebug.Shutdown ( ); + gameLogLocal.Shutdown ( ); + currentThinkingEntity = NULL; +// RAVEN END + + demoState = DEMO_NONE; + serverDemo = false; + timeDemo = false; + + memset( &usercmd, 0, sizeof( usercmd ) ); + memset( &oldUsercmd, 0, sizeof( oldUsercmd ) ); + followPlayer = -1; // start free flying + + demo_protocol = 0; + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + minSpawnIndex = MAX_CLIENTS; + + modList.Clear(); + filterMod = -1; + if ( networkSystem ) { + networkSystem->UseSortFunction( filterByMod, false ); + } + clientAckSequence = -1; +} + +/* +=========== +idGameLocal::Init + + initialize the game object, only happens once at startup, not each level load +============ +*/ +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +extern idHashTable *visemeTable100; +extern idHashTable *visemeTable66; +extern idHashTable *visemeTable33; +#ifdef RV_UNIFIED_ALLOCATOR +void idGameLocal::Init( void *(*allocator)(size_t size), void (*deallocator)( void *ptr ), size_t (*msize)(void *ptr) ) { +#else +void idGameLocal::Init( void ) { +#endif +// RAVEN END + const idDict *dict; + idAAS *aas; + +#ifndef GAME_DLL + + TestGameAPI(); + +#else + + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + Memory::InitAllocator(allocator, deallocator, msize); +#endif +// RAVEN END + // initialize idLib + idLib::Init(); + + // register static cvars declared in the game + idCVar::RegisterStaticVars(); + + // initialize processor specific SIMD + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + +#endif +// RAVEN BEGIN +// jsinger: these need to be initialized after the InitAllocator call above in order +// to avoid crashes when the allocator is used. + animationLib = new idAnimManager(); + visemeTable100 = new idHashTable; + visemeTable66 = new idHashTable; + visemeTable33 = new idHashTable; +// RAVEN END + + Printf( "------------- Initializing Game -------------\n" ); + Printf( "gamename: %s\n", game_name.string ); + Printf( "gamedate: %s\n", __DATE__ ); + +// RAVEN BEGIN +// rjohnson: new help system for cvar ui + idCVarHelp::RegisterStatics(); + +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS + idStr prefix=""; + if(cvarSystem->GetCVarBool("com_binaryDeclRead")) + { + prefix = "binary/"; + } + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator, idDeclStreamAllocator ); + //declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator, idDeclStreamAllocator ); + declManager->RegisterDeclFolderWrapper( prefix + "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( prefix + "af", ".af", DECL_AF ); +#else +// RAVEN END + // register game specific decl types + declManager->RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator ); + declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator ); + +// RAVEN BEGIN +// rjohnson: camera is now contained in a def for frame commands + declManager->RegisterDeclType( "camera", DECL_CAMERADEF, idDeclAllocator ); +// RAVEN END + // register game specific decl folders +// RAVEN BEGIN +#ifndef RV_SINGLE_DECL_FILE + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); +// bdube: not used in quake 4 +// declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); +// declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); +// declManager->RegisterDeclFolderWrapper( "newpdas", ".pda", DECL_PDA ); +#else + if(!cvarSystem->GetCVarBool("com_SingleDeclFile")) + { + declManager->RegisterDeclFolderWrapper( "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolderWrapper( "af", ".af", DECL_AF ); + } + else + { + // loads the second set of decls from the file which will contain + // modles, cameras + declManager->LoadDeclsFromFile(); + declManager->FinishLoadingDecls(); + } +#endif +#endif // RV_BINARYDECLS +// RAVEN END + + cmdSystem->AddCommand( "listModelDefs", idListDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" ); + cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); + + Clear(); + + idEvent::Init(); +// RAVEN BEGIN +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + idClass::RegisterClasses(); +// RAVEN END + idClass::Init(); + + InitConsoleCommands(); + // load default scripts + program.Startup( SCRIPT_DEFAULT ); + + // set up the aas +// RAVEN BEGIN +// ddynerman: added false as 2nd parameter, otherwise def will be created + dict = FindEntityDefDict( "aas_types", false ); +// RAVEN END + if ( !dict ) { + Error( "Unable to find entityDef for 'aas_types'" ); + } + + // allocate space for the aas + const idKeyValue *kv = dict->MatchPrefix( "type" ); + while( kv != NULL ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + aas = idAAS::Alloc(); + aasList.Append( aas ); + aasNames.Append( kv->GetValue() ); + kv = dict->MatchPrefix( "type", kv ); + } + + gamestate = GAMESTATE_NOMAP; + + Printf( "...%d aas types\n", aasList.Num() ); + Printf( "game initialized.\n" ); + Printf( "---------------------------------------------\n" ); + +// RAVEN BEGIN +// bdube: debug stuff + gameDebug.Init(); + gameLogLocal.Init(); + +// jscott: facial animation init + if( !FAS_Init( "annosoft" ) ) { + common->Warning( "Failed to load viseme file" ); + } + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_RENDER); +// shouchard: make sure ban list starts out in a known state + banListLoaded = false; + banListChanged = false; + memset( clientGuids, 0, sizeof( clientGuids ) ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + for(int i=0;iAddSortFunction( filterByMod ); + + incompatibleMaps = 0; +} + +/* +=========== +idGameLocal::Shutdown + + shut down the entire game +============ +*/ +void idGameLocal::Shutdown( void ) { + + int i; + + if ( !common ) { + return; + } + +// RAVEN BEGIN +// jscott: FAS + FAS_Shutdown(); +// shouchard: clean up ban list stuff + SaveBanList(); + FlushBanList(); +// RAVEN END + + Printf( "--------------- Game Shutdown ---------------\n" ); + + networkSystem->RemoveSortFunction( filterByMod ); + + mpGame.Shutdown(); + + MapShutdown(); + + aasList.DeleteContents( true ); + aasNames.Clear(); + + idAI::FreeObstacleAvoidanceNodes(); + + // shutdown the model exporter + idModelExport::Shutdown(); + + idEvent::Shutdown(); + + program.Shutdown(); + + idClass::Shutdown(); + + // clear list with forces + idForce::ClearForceList(); + + // free the program data + program.FreeData(); + + // delete the .map file + delete mapFile; + mapFile = NULL; + + // free the collision map + collisionModelManager->FreeMap( GetMapName() ); + +// RAVEN BEGIN +// jscott: free up static objects + for( i = 0; i < MAX_CLIENTS; i++ ) { + userInfo[i].Clear(); + persistentPlayerInfo[i].Clear(); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[i].Clear(); + } + + serverInfo.Clear(); + repeaterInfo.Clear(); + persistentLevelInfo.Clear(); + sessionCommand.Clear(); + mapFileName.Clear(); + mapFileNameStripped.Clear(); + entityUsageList.Clear(); + + newInfo.Clear(); + spawnArgs.Clear(); + shakeSounds.Clear(); + aiManager.Clear(); +// RAVEN END + + ShutdownConsoleCommands(); + + // free memory allocated by class objects + Clear(); + + // shut down the animation manager +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->Shutdown(); +// RAVEN END + +// RAVEN BEGIN +// rjohnson: entity usage stats + entityUsageList.Clear(); +// RAVEN END + + Printf( "---------------------------------------------\n" ); + + instances.DeleteContents( true ); + +#ifdef GAME_DLL + + // remove auto-completion function pointers pointing into this DLL + cvarSystem->RemoveFlaggedAutoCompletion( CVAR_GAME ); + + // enable leak test + Mem_EnableLeakTest( "game" ); + + // shutdown idLib + idLib::ShutDown(); + +#endif +} + +/* +=========== +idGameLocal::SaveGame + +save the current player state, level name, and level state +the session may have written some data to the file already +============ +*/ +// RAVEN BEGIN +// mekberg: added saveTypes +void idGameLocal::SaveGame( idFile *f, saveType_t saveType ) { +// RAVEN END + int i; + idEntity *ent; + idEntity *link; + + //remove weapon effect entites from the world + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + + GetLocalPlayer()->weapon->PreSave(); + } + + idSaveGame savegame( f ); + + if (g_flushSave.GetBool( ) == true ) { + // force flushing with each write... for tracking down + // save game bugs. + f->ForceFlush(); + } + + savegame.WriteBuildNumber( BUILD_NUMBER ); + + // go through all entities and threads and add them to the object list + for( i = 0; i < MAX_GENTITIES; i++ ) { + ent = entities[i]; + + if ( ent ) { + if ( ent->GetTeamMaster() && ent->GetTeamMaster() != ent ) { + continue; + } + for ( link = ent; link != NULL; link = link->GetNextTeamEntity() ) { + savegame.AddObject( link ); + } + } + } + + idList threads; + threads = idThread::GetThreads(); + + for( i = 0; i < threads.Num(); i++ ) { + savegame.AddObject( threads[i] ); + } + +// RAVEN BEGIN +// abahr: saving clientEntities + rvClientEntity* clientEnt = NULL; + for( i = 0; i < MAX_CENTITIES; ++i ) { + clientEnt = clientEntities[ i ]; + if( !clientEnt ) { + continue; + } +// if( clientEnt->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.AddObject( clientEnt ); + } +// RAVEN END + + // write out complete object list + savegame.WriteObjectList(); + + program.Save( &savegame ); + + savegame.WriteInt( g_skill.GetInteger() ); + + savegame.WriteDict( &serverInfo ); + + savegame.WriteInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't write out userinfo. Grab from cvars +// savegame.WriteDict( &userInfo[ i ] ); +// RAVEN END +// savegame.WriteUsercmd( usercmds[ i ] ); + savegame.WriteDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.WriteObject( entities[ i ] ); + savegame.WriteInt( spawnIds[ i ] ); + } + +// RAVEN BEGIN +// abahr: more clientEntities saving + for( i = 0; i < MAX_CENTITIES; i++ ) { +// if( clientEntities[ i ] && clientEntities[ i ]->IsType( rvClientEffect::GetClassType() )){ +// continue; +// } + savegame.WriteObject( clientEntities[ i ] ); + savegame.WriteInt( clientSpawnIds[ i ] ); + } +// RAVEN END + + savegame.WriteInt( firstFreeIndex ); + savegame.WriteInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.WriteObject( world ); + + savegame.WriteInt( spawnedEntities.Num() ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + savegame.WriteObject( ent ); + } + +// RAVEN BEGIN +// abahr: saving scriptObject proxies + savegame.WriteInt( scriptObjectProxies.Num() ); + for( i = 0; i < scriptObjectProxies.Num(); ++i ) { + scriptObjectProxies[i].Save( &savegame ); + } +// abahr: save out client stuff + savegame.WriteInt( clientSpawnedEntities.Num() ); + for( clientEnt = clientSpawnedEntities.Next(); clientEnt != NULL; clientEnt = clientEnt->spawnNode.Next() ) { + savegame.WriteObject( clientEnt ); + } +// RAVEN END + + savegame.WriteInt( activeEntities.Num() ); + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + savegame.WriteObject( ent ); + } + + savegame.WriteInt( numEntitiesToDeactivate ); + savegame.WriteBool( sortPushers ); + savegame.WriteBool( sortTeamMasters ); + savegame.WriteDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.WriteFloat( globalShaderParms[ i ] ); + } + + savegame.WriteInt( random.GetSeed() ); + savegame.WriteObject( frameCommandThread ); + + // clip + // push + // pvs + + testmodel = NULL; +// RAVEN BEGIN +// bdube: no test fx +// testFx = NULL; +// RAVEN END + + savegame.WriteString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system save itself too. + aiManager.Save( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.WriteInt( cinematicSkipTime ); + savegame.WriteInt( cinematicStopTime ); + savegame.WriteInt( cinematicMaxSkipTime ); + savegame.WriteBool( inCinematic ); + savegame.WriteBool( skipCinematic ); + + savegame.WriteBool( isMultiplayer ); + savegame.WriteInt( gameType ); + + savegame.WriteInt( framenum ); + savegame.WriteInt( previousTime ); + savegame.WriteInt( time ); + + savegame.WriteInt( vacuumAreaNum ); + + savegame.WriteInt( entityDefBits ); + savegame.WriteBool( isServer ); + savegame.WriteBool( isClient ); +// RAVEN BEGIN + savegame.WriteBool( isListenServer ); +// RAVEN END + + savegame.WriteInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.WriteInt( realClientTime ); + savegame.WriteBool( isNewFrame ); + + savegame.WriteBool( mapCycleLoaded ); + savegame.WriteInt( spawnCount ); + + if ( !locationEntities ) { + savegame.WriteInt( 0 ); + } else { + savegame.WriteInt( gameRenderWorld->NumAreas() ); + for( i = 0; i < gameRenderWorld->NumAreas(); i++ ) { + savegame.WriteObject( locationEntities[ i ] ); + } + } + + savegame.WriteObject( camera ); + + savegame.WriteMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertActor.Save( &savegame ); + lastAIAlertEntity.Save( &savegame ); + savegame.WriteInt( lastAIAlertEntityTime ); + savegame.WriteInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.WriteDict( &spawnArgs ); + + savegame.WriteInt( playerPVS.i ); + savegame.WriteInt( playerPVS.h ); + savegame.WriteInt( playerConnectedAreas.i ); + savegame.WriteInt( playerConnectedAreas.h ); + + savegame.WriteVec3( gravity ); + + // gamestate + + savegame.WriteBool( influenceActive ); + savegame.WriteInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // write out pending events + idEvent::Save( &savegame ); + + savegame.Close(); + +// RAVEN BEGIN +// mekberg: added saveTypes and wrapped saveMessage + if ( saveType != ST_AUTO && GetLocalPlayer() && GetLocalPlayer()->GetHud() ) { + GetLocalPlayer()->SaveMessage(); + } + +// jshepard: resume weapon operation + if( GetLocalPlayer() && + !GetLocalPlayer()->IsInVehicle() && + GetLocalPlayer()->weapon ) { + GetLocalPlayer()->weapon->PostSave(); + } +// RAVEN END +} + +/* +=========== +idGameLocal::GetPersistentPlayerInfo +============ +*/ +const idDict &idGameLocal::GetPersistentPlayerInfo( int clientNum ) { + idEntity *ent; + + persistentPlayerInfo[ clientNum ].Clear(); + ent = entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SavePersistantInfo(); + } + + return persistentPlayerInfo[ clientNum ]; +} + +/* +=========== +idGameLocal::SetPersistentPlayerInfo +============ +*/ +void idGameLocal::SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + persistentPlayerInfo[ clientNum ] = playerInfo; +} + +/* +============ +idGameLocal::Printf +============ +*/ +void idGameLocal::Printf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::DPrintf +============ +*/ +void idGameLocal::DPrintf( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Printf( "%s", text ); +} + +/* +============ +idGameLocal::Warning +============ +*/ +void idGameLocal::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + common->Warning( "%s", text ); +} + +/* +============ +idGameLocal::DWarning +============ +*/ +void idGameLocal::DWarning( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + if ( !developer.GetBool() ) { + return; + } + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Warning( "%s", text ); + } else { + common->DWarning( "%s", text ); + } +} + +/* +============ +idGameLocal::Error +============ +*/ +void idGameLocal::Error( const char *fmt, ... ) const { + va_list argptr; + char text[MAX_STRING_CHARS]; + idThread * thread; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN +// scork: some model errors arrive here during validation which kills the whole process, so let's just warn about them instead... + if ( common->DoingDeclValidation() ) { + this->Warning( "%s", text ); + return; + } +// RAVEN END + + thread = idThread::CurrentThread(); + if ( thread ) { + thread->Error( "%s", text ); + } else { + common->Error( "%s", text ); + } +} + +/* +=============== +gameError +=============== +*/ +void gameError( const char *fmt, ... ) { + va_list argptr; + char text[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + idStr::vsnPrintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + gameLocal.Error( "%s", text ); +} + +/* +=========== +idGameLocal::SetLocalClient +============ +*/ +void idGameLocal::SetLocalClient( int clientNum ) { + localClientNum = clientNum; +} + +/* +=========== +idGameLocal::SetUserInfo +============ +*/ +const idDict* idGameLocal::SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + bool modifiedInfo = false; + + this->isClient = isClient; + + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + idGameLocal::userInfo[ clientNum ] = userInfo; + + // server sanity + if ( !isClient ) { + + // don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server + if ( idStr::IsNumeric( this->userInfo[ clientNum ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + } + + // don't allow dupe nicknames + for ( i = 0; i < numClients; i++ ) { + if ( i == clientNum ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp( idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ i ].GetString( "ui_name" ) ) ) { +#if UI_DEBUG + common->Printf( "SetUserInfo: client %d changed name from %s to %s_ because of client %d\n", + clientNum, idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ), i ); +#endif + idGameLocal::userInfo[ clientNum ].Set( "ui_name", va( "%s_", idGameLocal::userInfo[ clientNum ].GetString( "ui_name" ) ) ); + modifiedInfo = true; + i = -1; // rescan + continue; + } + } + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + modifiedInfo |= static_cast( entities[ clientNum ] )->UserInfoChanged(); + } + + if ( !isClient ) { + // now mark this client in game + mpGame.EnterGame( clientNum ); + } + } + + if ( modifiedInfo ) { + newInfo = idGameLocal::userInfo[ clientNum ]; + return &newInfo; + } + return NULL; +} + +/* +=========== +idGameLocal::GetUserInfo +============ +*/ +const idDict* idGameLocal::GetUserInfo( int clientNum ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return &userInfo[ clientNum ]; + } + return NULL; +} + +/* +=========== +idGameLocal::IsClientActive +============ +*/ +bool idGameLocal::IsClientActive( int clientNum ) { + if ( entities[ clientNum ] && entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + return true; + } + + return false; +} + +/* +=========== +idGameLocal::RepeaterSetUserInfo +============ +*/ +const idDict* idGameLocal::RepeaterSetUserInfo( int clientNum, const idDict &userInfo ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + assert( cvarSystem->GetCVarInteger( "ri_maxViewers" ) > clientNum ); + ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) ); + + idDict *info; + + int i; + bool modifiedInfo = false; + + info = &viewers[ clientNum ].info; + *info = userInfo; + + // don't let numeric nicknames, it can be exploited to go around kick and ban commands from the server + if ( idStr::IsNumeric( info->GetString( "ui_name" ) ) ) { + info->Set( "ui_name", va( "%s_", info->GetString( "ui_name" ) ) ); + modifiedInfo = true; + } + + // don't allow dupe nicknames + for ( i = 0; i < maxViewer; i++ ) { + if ( i == clientNum ) { + continue; + } + + if ( !viewers[ i ].active ) { + continue; + } + + if ( !idStr::Icmp( info->GetString( "ui_name" ), viewers[ i ].info.GetString( "ui_name" ) ) ) { + info->Set( "ui_name", va( "%s_", info->GetString( "ui_name" ) ) ); + modifiedInfo = true; + i = -1; // rescan + continue; + } + } + + if ( modifiedInfo ) { + newInfo = *info; + return &newInfo; + } + + return NULL; +} + +/* +=========== +idGameLocal::UpdateRepeaterInfo +============ +*/ + +void idGameLocal::UpdateRepeaterInfo( bool transmit ) { + if ( !isRepeater ) { + return; + } + + repeaterInfo = serverInfo; + repeaterInfo.Set( "si_tv", "1" ); + + // fudge a few cvars so old browsers display correctly + repeaterInfo.Set( "si_usePass", ri_useViewerPass.GetBool() ? "1" : "0" ); + + const char *ri_name = cvarSystem->GetCVarString( "ri_name" ); + if ( ri_name[0] ) { + if ( !serverInfo.GetString( "ri_origName" ) [0] ) { + repeaterInfo.Set( "ri_origName", serverInfo.GetString( "si_name" ) ); + } + repeaterInfo.Set( "si_name", ri_name ); + } + + const char *si_serverURL = cvarSystem->GetCVarString( "si_serverURL" ); + if ( si_serverURL[0] ) { + if ( !serverInfo.GetString( "ri_origServerURL" ) [0] ) { + repeaterInfo.Set( "ri_origServerURL", serverInfo.GetString( "si_serverURL" ) ); + } + repeaterInfo.Set( "si_serverURL", si_serverURL ); + } + + repeaterInfo.Copy( *cvarSystem->MoveCVarsToDict( CVAR_REPEATERINFO ) ); + + networkSystem->RepeaterSetInfo( repeaterInfo ); + + if ( transmit ) { + // Let our clients know the server info changed + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.repeaterInfo, NULL ); + networkSystem->RepeaterSendReliableMessage( -1, outMsg ); + } +} + +/* +=========== +idGameLocal::SetServerInfo +============ +*/ +void idGameLocal::SetServerInfo( const idDict &_serverInfo ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + bool timeLimitChanged = false; + +// RAVEN BEGIN +// mekberg: clear announcer and reschedule time announcements + if ( ( isClient || isListenServer ) && mpGame.GetGameState( ) && mpGame.GetGameState( )->GetMPGameState( ) == GAMEON && + serverInfo.GetInt( "si_timelimit" ) != _serverInfo.GetInt( "si_timelimit" ) ) { + timeLimitChanged = true; + } + + serverInfo = _serverInfo; + + if ( timeLimitChanged ) { + mpGame.ClearAnnouncerSounds( ); + mpGame.ScheduleTimeAnnouncements( ); + } +// RAVEN END + + UpdateRepeaterInfo( true ); + + if ( isServer ) { + // Let our clients know the server info changed + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg, true ); + } else if ( isClient ) { + networkSystem->ClientSetServerInfo( gameLocal.serverInfo ); + isTVClient = ( gameLocal.serverInfo.GetInt( "si_tv" ) == 1 ) ? true : false; + } +} + +/* +=================== +idGameLocal::LoadMap + +Initializes all map variables common to both save games and spawned games. +=================== +*/ +void idGameLocal::LoadMap( const char *mapName, int randseed ) { + int i; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_PARSER); +// RAVEN END + bool sameMap = (mapFile && idStr::Icmp(mapFile->GetName(), mapName) == 0); + + networkSystem->SetLoadingText( mapName ); + + InitAsyncNetwork(); + + // these can changed based upon sp / mp + mHz = common->GetUserCmdHz(); + msec = common->GetUserCmdMSec(); + + if ( !sameMap || ( mapFile && mapFile->NeedsReload() ) ) { + // load the .map file + if ( mapFile ) { + delete mapFile; + } + mapFile = new idMapFile; + if ( !mapFile->Parse( idStr( mapName ) + ".map" ) ) { + delete mapFile; + mapFile = NULL; + Error( "Couldn't load %s", mapName ); + } +// RAVEN BEGIN +// rjohnson: added resolve for handling func_groups and other aspects. Before, radiant would do this processing on a map destroying the original data + mapFile->Resolve(); +// RAVEN END + } + mapFileName = mapFile->GetName(); + + assert(!idStr::Cmp(mapFileName, mapFile->GetName())); + +// RAVEN BEGIN +// rjohnson: entity usage stats + mapFileNameStripped = mapFileName; + mapFileNameStripped.StripFileExtension(); + mapFileNameStripped.StripPath(); + + const char* entityFilter; + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + mapFileNameStripped += " "; + mapFileNameStripped += entityFilter; + } +// RAVEN END + + // load the collision map + networkSystem->SetLoadingText( common->GetLocalizedString( "#str_107668" ) ); + collisionModelManager->LoadMap( mapFile, false ); + + numClients = 0; + + // initialize all entities for this game + memset( entities, 0, sizeof( entities ) ); + usercmds = NULL; + memset( spawnIds, -1, sizeof( spawnIds ) ); + spawnCount = INITIAL_SPAWN_COUNT; + + spawnedEntities.Clear(); + activeEntities.Clear(); + numEntitiesToDeactivate = 0; + sortTeamMasters = false; + sortPushers = false; + lastGUIEnt = NULL; + lastGUI = 0; + +// RAVEN BEGIN +// bdube: client entities + clientSpawnCount = INITIAL_SPAWN_COUNT; + clientSpawnedEntities.Clear ( ); + memset ( clientSpawnIds, -1, sizeof(clientSpawnIds ) ); + memset ( clientEntities, 0, sizeof(clientEntities) ); + firstFreeClientIndex = 0; +// RAVEN END + + globalMaterial = NULL; + + memset( globalShaderParms, 0, sizeof( globalShaderParms ) ); + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + num_entities = MAX_CLIENTS; + firstFreeIndex = MAX_CLIENTS; + + // reset the random number generator. + random.SetSeed( isMultiplayer ? randseed : 0 ); + + camera = NULL; + world = NULL; + testmodel = NULL; +// RAVEN BEGIN +// bdube: not using id effects +// testFx = NULL; +// RAVEN END + +// RAVEN BEGIN +// bdube: merged + lastAIAlertEntity = NULL; + lastAIAlertEntityTime = 0; + lastAIAlertActor = NULL; + lastAIAlertActorTime = 0; +// RAVEN END + + previousTime = 0; + time = 0; + framenum = 0; + sessionCommand = ""; + nextGibTime = 0; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + unFreezeTime = 0; + isFrozen = 0; +// RITUAL END + + vacuumAreaNum = -1; // if an info_vacuum is spawned, it will set this + +// RAVEN BEGIN +// abahr + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + if ( !editEntities ) { + editEntities = new idEditEntities; + } + + if( gameLocal.isMultiplayer ) { + gravity.Set( 0, 0, -g_mp_gravity.GetFloat() ); + } else { + gravity.Set( 0, 0, -g_gravity.GetFloat() ); + } + + spawnArgs.Clear(); + +// RAVEN BEGIN +// nmckenzie: + //make sure we clear all reachabilities we marked as blocked! + aiManager.UnMarkAllReachBlocked(); + aiManager.Clear(); +// RAVEN END + + skipCinematic = false; + inCinematic = false; + cinematicSkipTime = 0; + cinematicStopTime = 0; + cinematicMaxSkipTime = 0; + +// RAVEN BEGIN +// ddynerman: main world instance + PACIFIER_UPDATE; + AddInstance( 0, true ); + assert( instances.Num() == 1 && instances[ 0 ]->GetInstanceID() == 0 ); +// RAVEN END + pvs.Init(); +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + // Experimental use of game's PVS for reducing amount of stuff streamed. Will + // do this a bit more cleanly if I decide to keep this. + extern idPVS* pvsForStreaming; + pvsForStreaming=&pvs; +#endif +// RAVEN END + + playerPVS.i = -1; + playerConnectedAreas.i = -1; + + // load navigation system for all the different monster sizes + for( i = 0; i < aasNames.Num(); i++ ) { + aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() ); + } + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + AI_MoveInitialize(); +// RAVEN END + + FindEntityDef( "preCacheExtras", false ); + + if ( !sameMap ) { + mapFile->RemovePrimitiveData(); + } + +// RAVEN BEGIN +// ddynerman: ambient light list + ambientLights.Clear(); +// RAVEN END +} + +/* +=================== +idGameLocal::LocalMapRestart +=================== +*/ +void idGameLocal::LocalMapRestart( int instance ) { + int i, latchSpawnCount; + + Printf( "----------- Game Map Restart (%s) ------------\n", instance == -1 ? "all instances" : va( "instance %d", instance ) ); + + // client always respawns everything, so make sure it picks up the right map entities + if( instance == -1 || isClient ) { + memset( isMapEntity, 0, sizeof(bool) * MAX_GENTITIES ); + } else { + assert( instance >= 0 && instance < instances.Num() ); + + for( int i = 0; i < instances[ instance ]->GetNumMapEntities(); i++ ) { + if ( instances[ instance ]->GetMapEntityNumber( i ) >= 0 && instances[ instance ]->GetMapEntityNumber( i ) < MAX_GENTITIES ) { + isMapEntity[ instances[ instance ]->GetMapEntityNumber( i ) ] = false; + } + } + } + + gamestate = GAMESTATE_SHUTDOWN; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->PrepareForRestart(); + } + } + + eventQueue.Shutdown(); + + MapClear( false, instance ); + + // clear the sound system + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + + // clear icons + iconManager->Shutdown(); + + // the spawnCount is reset to zero temporarily to spawn the map entities with the same spawnId + // if we don't do that, network clients are confused and don't show any map entities + latchSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + + gamestate = GAMESTATE_STARTUP; + + program.Restart(); + + InitScriptForMap(); + + MapPopulate( instance ); + + // once the map is populated, set the spawnCount back to where it was so we don't risk any collision + // (note that if there are no players in the game, we could just leave it at it's current value) + spawnCount = latchSpawnCount; + + // setup the client entities again + for ( i = 0; i < MAX_CLIENTS; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ i ] && entities[ i ]->IsType( idPlayer::GetClassType() ) && (isClient || instance == -1 || entities[ i ]->GetInstance() == instance ) ) { +// RAVEN END + static_cast< idPlayer * >( entities[ i ] )->Restart(); + } + } + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); +} + +/* +=================== +idGameLocal::MapRestart +=================== +*/ +void idGameLocal::MapRestart( int instance ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idDict newInfo; + int i; + const idKeyValue *keyval, *keyval2; + + if ( isClient ) { + SetGameType(); + LocalMapRestart( instance ); + } else { + + if ( mpGame.PickMap( "", true ) ) { + common->Warning( "map %s and gametype %s are incompatible, aborting map change", si_map.GetString(), si_gameType.GetString() ); + if ( (++incompatibleMaps) >= 50 ) { + common->Warning( "too many incompatible maps for gametype %s; setting gametype to DM.", si_gameType.GetString() ); + si_gameType.SetString( "DM" ); + } + return; + } + + incompatibleMaps = 0; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + // this has to be after the cvars are moved to the dict + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + break; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && + ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) ) ) { + break; + } +//RAVEN BEGIN + //asalmon: need to restart if the game type has changed but the map has not cause someone could be connecting + if( keyval->GetValue().Icmp( keyval2->GetValue() ) && (!keyval->GetKey().Icmp("si_gametype"))) + { + break; + } +//RAVEN END + + // some calculations about time are done from the frame, and + // that changes when si_fps changes. full restart resets to 0. + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && + !keyval->GetKey().Icmp( "si_fps" ) ) { + break; + } + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + + SetGameType(); + + mpGame.isBuyingAllowedRightNow = false; + + if ( i != newInfo.GetNumKeyVals() ) { + gameLocal.sessionCommand = "nextMap"; + } else { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 1, 1 ); + outMsg.WriteDeltaDict( serverInfo, NULL ); + networkSystem->ServerSendReliableMessage( -1, outMsg, true ); + + if ( isRepeater ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 1, 1 ); + outMsg.WriteDeltaDict( repeaterInfo, NULL ); + networkSystem->RepeaterSendReliableMessage( -1, outMsg ); + } + + LocalMapRestart( instance ); + mpGame.MapRestart(); + } + } +} + +/* +=================== +idGameLocal::VerifyServerSettings_f +=================== +*/ +void idGameLocal::VerifyServerSettings_f( const idCmdArgs &args ) { + gameLocal.mpGame.PickMap( si_gameType.GetString() ); +} + +/* +=================== +idGameLocal::MapRestart_f +=================== +*/ +void idGameLocal::MapRestart_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running - use spawnServer\n" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + return; + } + + int instance = -1; + if( args.Argc() > 1 ) { + instance = atoi( args.Args( 1 ) ); + if( instance < 0 || instance >= gameLocal.GetNumInstances() ) { + common->Warning( "idGameLocal::MapRestart_f() - Invalid instance '%d' specified\n", instance ); + return; + } + gameLocal.LocalMapRestart( instance ); + return; + } + + gameLocal.MapRestart( instance ); +} + +/* +=================== +idGameLocal::NextMap +=================== +*/ +bool idGameLocal::NextMap( void ) { + const function_t *func; + idThread *thread; + idDict newInfo; + const idKeyValue *keyval, *keyval2; + int i; + const char *mapCycleList, *currentMap; + +//RAVEN BEGIN +//asalmon: pick another map on Xenon +#ifdef _XENON + //Live()->PickMap(); + return true; +#endif +//RAVEN END + +// RAVEN BEGIN +// rjohnson: traditional map cycle +// si_mapCycle "mp/q4dm4;mp/q4dm5;mp/q4dm6" + mapCycleList = si_mapCycle.GetString(); + if ( mapCycleList && strlen( mapCycleList ) ) { + idLexer src; + idToken token, firstFound; + int numMaps = 0; + bool foundMap = false; + + src.FreeSource(); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( mapCycleList, strlen( mapCycleList ), "idGameLocal::NextMap" ); + if ( src.IsLoaded() ) { + + currentMap = si_map.GetString(); + while( src.ReadToken( &token ) ) { + if ( token == ";" ) { + continue; + } + numMaps++; + + if ( numMaps == 1 ) { + // guarantee that we use a map no matter what ( or when we wrap ) + firstFound = token; + } + if ( foundMap ) { + firstFound = token; + break; + } + + if ( idStr::Icmp( token, currentMap ) == 0 ) { + foundMap = true; + } + } + + if ( firstFound != "" ) { + si_map.SetString( firstFound ); + return true; + } + } + + return false; + } +// RAVEN END + + if ( !g_mapCycle.GetString()[0] ) { + Printf( common->GetLocalizedString( "#str_104294" ) ); + return false; + } + if ( fileSystem->ReadFile( g_mapCycle.GetString(), NULL, NULL ) < 0 ) { + if ( fileSystem->ReadFile( va( "%s.scriptcfg", g_mapCycle.GetString() ), NULL, NULL ) < 0 ) { + Printf( "map cycle script '%s': not found\n", g_mapCycle.GetString() ); + return false; + } else { + g_mapCycle.SetString( va( "%s.scriptcfg", g_mapCycle.GetString() ) ); + } + } + + Printf( "map cycle script: '%s'\n", g_mapCycle.GetString() ); + func = program.FindFunction( "mapcycle::cycle" ); + if ( !func ) { + program.CompileFile( g_mapCycle.GetString(), false ); + func = program.FindFunction( "mapcycle::cycle" ); + } + if ( !func ) { + Printf( "Couldn't find mapcycle::cycle\n" ); + return false; + } + thread = new idThread( func ); + thread->Start(); + delete thread; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + for ( i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 || keyval->GetValue().Icmp( keyval2->GetValue() ) ) { + break; + } + } + return ( i != newInfo.GetNumKeyVals() ); +} + +/* +=================== +idGameLocal::NextMap_f +=================== +*/ +void idGameLocal::NextMap_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + common->Printf( "server is not running\n" ); + return; + } + + gameLocal.NextMap( ); + // next map was either voted for or triggered by a server command - always restart + gameLocal.MapRestart( ); +} + +/* +=============== +idGameLocal::GetStartingIndexForInstance +=============== +*/ +int idGameLocal::GetStartingIndexForInstance( int instanceID ) { + if ( isServer ) { + assert( instancesEntityIndexWatermarks.Num() >= instanceID ); + if ( instanceID == 0 ) { + return MAX_CLIENTS; + } else { + // the high watermark of the previous instance is the starting index of the next one + return instancesEntityIndexWatermarks[ instanceID - 1 ]; + } + } else { + assert( instanceID == 0 ); + return clientInstanceFirstFreeIndex; + } +} + +/* +=============== +idGameLocal::ServerSetEntityIndexWatermark +keep track of the entity layout at the server - specially when there are multiple instances ( tourney ) +=============== +*/ +void idGameLocal::ServerSetEntityIndexWatermark( int instanceID ) { + if ( isClient ) { + return; + } + instancesEntityIndexWatermarks.AssureSize( instanceID + 1, MAX_CLIENTS ); + // make sure there is no drift. if a value was already set it has to match + // otherwise that means the server is repopulating with different indexes, and that would likely lead to net corruption + assert( instancesEntityIndexWatermarks[ instanceID ] == MAX_CLIENTS || instancesEntityIndexWatermarks[ instanceID ] == firstFreeIndex ); + instancesEntityIndexWatermarks[ instanceID ] = firstFreeIndex; +} + +/* +=============== +idGameLocal::ServerSetMinSpawnIndex +=============== +*/ +void idGameLocal::ServerSetMinSpawnIndex( void ) { + if ( isClient ) { + return; + } + // setup minSpawnIndex with enough headroom so gameplay entities don't cause bad offsets + // only needed on server, clients are completely slaved up to server entity layout + if ( !idStr::Icmp( serverInfo.GetString( "si_gameType" ), "Tourney" ) ) { + minSpawnIndex = MAX_CLIENTS + GetNumMapEntities() * MAX_INSTANCES; + } +} + +/* +=================== +idGameLocal::MapPopulate +=================== +*/ +void idGameLocal::MapPopulate( int instance ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + if ( isMultiplayer ) { + cvarSystem->SetCVarBool( "r_skipSpecular", false ); + } + + minSpawnIndex = MAX_CLIENTS; + + // parse the key/value pairs and spawn entities +// RAVEN BEGIN +// ddynerman: instance code + // reload the instances + if ( instance == -1 ) { + int i; + firstFreeIndex = MAX_CLIENTS; + for ( i = 0; i < instances.Num(); i++ ) { + if ( instances[ i ] ) { + instances[ i ]->Restart(); + + ServerSetEntityIndexWatermark( i ); + } + } + } else { + assert( instance >= 0 && instance < instances.Num() ); + instances[ instance ]->Restart(); + } +// RAVEN END + + ServerSetMinSpawnIndex(); + + // mark location entities in all connected areas + SpreadLocations(); + + // RAVEN BEGIN + // ddynerman: prepare the list of spawn spots + InitializeSpawns(); + // RAVEN END + + // execute pending events before the very first game frame + // this makes sure the map script main() function is called + // before the physics are run so entities can bind correctly + Printf( "------------ Processing events --------------\n" ); + idEvent::ServiceEvents(); +} + +/* +=================== +idGameLocal::InitFromNewMap +=================== +*/ +void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randseed ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + this->isServer = isServer; + this->isClient = isClient; +// RAVEN BEGIN +// ddynerman: listen server + this->isListenServer = isServer && !cvarSystem->GetCVarBool( "net_serverDedicated" ); +// RAVEN END + this->isMultiplayer = isServer || isClient; + + if ( this->isClient && gameLocal.serverInfo.GetInt( "si_tv" ) == 1 ) { + this->isTVClient = true; + } + + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 3 ) { + networkSystem->HTTPEnable( this->isServer || this->isRepeater ); + } + + if ( !this->isMultiplayer ) + gameLocal.Error( "This mod is for multiplayer only" ); + + PACIFIER_UPDATE; + +//RAVEN BEGIN +//asalmon: stats for single player + if (!this->isMultiplayer) { +#ifdef _MPBETA + return; +#else + statManager->EndGame(); +#ifdef _XENON + Live()->DeleteSPSession(true); +#endif + statManager->Shutdown(); + statManager->Init(); + statManager->BeginGame(); + statManager->ClientConnect(0); +#ifdef _XENON + Live()->CreateSPSession(); +#endif +#endif // _MPBETA + } +//RAVEN END + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "-------------- Game Map Init ----------------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->BeginLevelLoad(); +#endif + + SetGameType(); + + LoadMap( mapName, randseed ); + + InitScriptForMap(); + + MapPopulate(); + + mpGame.Reset(); + + mpGame.Precache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + animationLib->EndLevelLoad(); +#endif +// RAVEN END + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "---------------------------------------------\n" ); +} + +/* +================= +idGameLocal::InitFromSaveGame +================= +*/ +bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + int num; + idEntity *ent; + idDict si; + + if ( mapFileName.Length() ) { + MapShutdown(); + } + + Printf( "---------- Game Map Init SaveGame -----------\n" ); + + gamestate = GAMESTATE_STARTUP; + + gameRenderWorld = renderWorld; + + idRestoreGame savegame( saveGameFile ); + + savegame.ReadBuildNumber(); + + // Create the list of all objects in the game + savegame.CreateObjects(); + + // Load the idProgram, also checking to make sure scripting hasn't changed since the savegame + if ( program.Restore( &savegame ) == false ) { + + // Abort the load process, and let the session know so that it can restart the level + // with the player persistent data. + savegame.DeleteObjects(); + program.Restart(); + + return false; + } + + // load the map needed for this savegame + LoadMap( mapName, 0 ); + + savegame.ReadInt( i ); + g_skill.SetInteger( i ); + + // precache any media specified in the map + for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *mapEnt = mapFile->GetEntity( i ); + + if ( !InhibitEntitySpawn( mapEnt->epairs ) ) { + CacheDictionaryMedia( &mapEnt->epairs ); + const char *classname = mapEnt->epairs.GetString( "classname" ); + if ( classname != '\0' ) { + FindEntityDef( classname, false ); + } + } + } + + savegame.ReadDict( &si ); + SetServerInfo( si ); + + savegame.ReadInt( numClients ); + for( i = 0; i < numClients; i++ ) { +// RAVEN BEGIN +// mekberg: don't read in userinfo. Grab from cvars +// savegame.ReadDict( &userInfo[ i ] ); +// RAVEN END +// savegame.ReadUsercmd( usercmds[ i ] ); + savegame.ReadDict( &persistentPlayerInfo[ i ] ); + } + + for( i = 0; i < MAX_GENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( entities[ i ] ) ); + savegame.ReadInt( spawnIds[ i ] ); + + // restore the entityNumber + if ( entities[ i ] != NULL ) { + entities[ i ]->entityNumber = i; + } + } + + // Precache the player +// RAVEN BEGIN +// bdube: changed so we actually cache stuff + FindEntityDef( idPlayer::GetSpawnClassname( ) ); + +// abahr: saving clientEntities + for( i = 0; i < MAX_CENTITIES; i++ ) { + savegame.ReadObject( reinterpret_cast( clientEntities[ i ] ) ); + savegame.ReadInt( clientSpawnIds[ i ] ); + + // restore the entityNumber + if ( clientEntities[ i ] != NULL ) { + clientEntities[ i ]->entityNumber = i; + } + } +// RAVEN END + + savegame.ReadInt( firstFreeIndex ); + savegame.ReadInt( num_entities ); + + // enityHash is restored by idEntity::Restore setting the entity name. + + savegame.ReadObject( reinterpret_cast( world ) ); + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->spawnNode.AddToEnd( spawnedEntities ); + } + } + +// RAVEN BEGIN +// abahr: save scriptObject proxies + savegame.ReadInt( num ); + scriptObjectProxies.SetNum( num ); + for( i = 0; i < num; ++i ) { + scriptObjectProxies[i].Restore( &savegame ); + } +// abahr: save client entity stuff + rvClientEntity* clientEnt = NULL; + savegame.ReadInt( num ); + for( i = 0; i < num; ++i ) { + savegame.ReadObject( reinterpret_cast( clientEnt ) ); + assert( clientEnt ); + if ( clientEnt ) { + clientEnt->spawnNode.AddToEnd( clientSpawnedEntities ); + } + } +// RAVEN END + + savegame.ReadInt( num ); + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( ent ) ); + assert( ent ); + if ( ent ) { + ent->activeNode.AddToEnd( activeEntities ); + } + } + + savegame.ReadInt( numEntitiesToDeactivate ); + savegame.ReadBool( sortPushers ); + savegame.ReadBool( sortTeamMasters ); + savegame.ReadDict( &persistentLevelInfo ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.ReadFloat( globalShaderParms[ i ] ); + } + + savegame.ReadInt( i ); + random.SetSeed( i ); + + savegame.ReadObject( reinterpret_cast( frameCommandThread ) ); + + // clip + // push + // pvs + + // testmodel = "" + // testFx = "" + + savegame.ReadString( sessionCommand ); +// RAVEN BEGIN +// nmckenzie: Let the AI system load itself too. + aiManager.Restore( &savegame ); +// RAVEN END + + // FIXME: save smoke particles + + savegame.ReadInt( cinematicSkipTime ); + savegame.ReadInt( cinematicStopTime ); + savegame.ReadInt( cinematicMaxSkipTime ); + savegame.ReadBool( inCinematic ); + savegame.ReadBool( skipCinematic ); + + savegame.ReadBool( isMultiplayer ); + savegame.ReadInt( (int &)gameType ); + + savegame.ReadInt( framenum ); + savegame.ReadInt( previousTime ); + savegame.ReadInt( time ); + + savegame.ReadInt( vacuumAreaNum ); + + savegame.ReadInt( entityDefBits ); + savegame.ReadBool( isServer ); + savegame.ReadBool( isClient ); +// RAVEN BEGIN + savegame.ReadBool( isListenServer ); +// RAVEN END + + savegame.ReadInt( localClientNum ); + + // snapshotEntities is used for multiplayer only + + savegame.ReadInt( realClientTime ); + savegame.ReadBool( isNewFrame ); + + savegame.ReadBool( mapCycleLoaded ); + savegame.ReadInt( spawnCount ); + + savegame.ReadInt( num ); + if ( num ) { + if ( num != gameRenderWorld->NumAreas() ) { + savegame.Error( "idGameLocal::InitFromSaveGame: number of areas in map differs from save game." ); + } + + locationEntities = new idLocationEntity *[ num ]; + for( i = 0; i < num; i++ ) { + savegame.ReadObject( reinterpret_cast( locationEntities[ i ] ) ); + } + } + + savegame.ReadObject( reinterpret_cast( camera ) ); + + savegame.ReadMaterial( globalMaterial ); + +// RAVEN BEGIN +// bdube: added + lastAIAlertEntity.Restore( &savegame ); + savegame.ReadInt( lastAIAlertEntityTime ); + lastAIAlertActor.Restore( &savegame ); + savegame.ReadInt( lastAIAlertActorTime ); +// RAVEN END + + savegame.ReadDict( &spawnArgs ); + + savegame.ReadInt( playerPVS.i ); + savegame.ReadInt( (int &)playerPVS.h ); + savegame.ReadInt( playerConnectedAreas.i ); + savegame.ReadInt( (int &)playerConnectedAreas.h ); + + savegame.ReadVec3( gravity ); + + // gamestate is restored after restoring everything else + + savegame.ReadBool( influenceActive ); + savegame.ReadInt( nextGibTime ); + + // spawnSpots + // initialSpots + // currentInitialSpot + // newInfo + // makingBuild + // shakeSounds + + // Read out pending events + idEvent::Restore( &savegame ); + + savegame.RestoreObjects(); + + mpGame.Reset(); + + mpGame.Precache(); + + // free up any unused animations +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->FlushUnusedAnims(); +// RAVEN END + + gamestate = GAMESTATE_ACTIVE; + + Printf( "--------------------------------------\n" ); + + return true; +} + +/* +=========== +idGameLocal::MapClear +=========== +*/ +// RAVEN BEGIN +// ddynerman: multiple instances +void idGameLocal::MapClear( bool clearClients, int instance ) { +// RAVEN END + int i; + +// RAVEN BEGIN +// bdube: delete client entities first since they reference real entities + for( i = 0; i < MAX_CENTITIES; i++ ) { + // on the server we need to keep around client entities bound to entities in our instance2 + if( gameLocal.isServer && gameLocal.GetLocalPlayer() && instance != -1 && + instance != gameLocal.GetLocalPlayer()->GetInstance() && + clientEntities[ i ] && clientEntities[ i ]->GetBindMaster() && + clientEntities[ i ]->GetBindMaster()->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + delete clientEntities[ i ]; + clientEntities[ i ] = NULL; + clientSpawnIds[ i ] = -1; + } +// RAVEN END + + for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) { + if( instance >= 0 && entities[ i ] && entities[ i ]->GetInstance() != instance ) { + continue; + } + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); +// RAVEN BEGIN +// see FIXME in idRestoreGame::Error + entities[ i ] = NULL; +// RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + spawnedEntities.Clear(); + } +// RAVEN END + + if ( !clearClients ) { + // add back the hashes of the clients/stuff in other instances + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); +// RAVEN BEGIN +// rjohnson: reset spawnedEntities during clear to ensure no left over pieces that get remapped to a new id ( causing bad snapshot reading ) + if ( instance == -1 ) { + entities[ i ]->spawnNode.AddToEnd( spawnedEntities ); + } +// RAVEN END + } + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; +// abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + +// RAVEN BEGIN +// ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); +// RAVEN END + + // set the free index back at MAX_CLIENTS for registering entities again + // the deletion of map entities causes the index to go down, but it may not have been left exactly at 32 + // under such conditions, the map populate that will follow may be offset + firstFreeIndex = MAX_CLIENTS; +} + +// RAVEN BEGIN +// ddynerman: instance-specific clear +void idGameLocal::InstanceClear( void ) { + // note: clears all ents EXCEPT those in the instance + int i; + + for( i = 0; i < MAX_CENTITIES; i++ ) { + delete clientEntities[ i ]; + assert( !clientEntities[ i ] ); + clientSpawnIds[ i ] = -1; + } + + for( i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + if( i == ENTITYNUM_CLIENT || i == ENTITYNUM_WORLD || i == ENTITYNUM_NONE ) { + continue; + } + + if( entities[ i ] && entities[ i ]->fl.persistAcrossInstances ) { + //common->DPrintf( "Instance %d: persistant: excluding ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + continue; + } + + //if( entities[ i ] ) { + //Printf( "Instance %d: CLEARING ent from clear: %s (%s)\n", instance, entities[ i ]->name.c_str(), entities[ i ]->GetClassname() ); + //} + + delete entities[ i ]; + // ~idEntity is in charge of setting the pointer to NULL + // it will also clear pending events for this entity + assert( !entities[ i ] ); + // RAVEN BEGIN + // see FIXME in idRestoreGame::Error + entities[ i ] = NULL; + // RAVEN END + spawnIds[ i ] = -1; + } + + entityHash.Clear( 1024, MAX_GENTITIES ); + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[ i ] ) { + continue; + } + entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); + } + +// RAVEN BEGIN +// jscott: clear out portal skies + portalSky = NULL; + // abahr: + gravityInfo.Clear(); + scriptObjectProxies.Clear(); +// RAVEN END + + delete frameCommandThread; + frameCommandThread = NULL; + + if ( editEntities ) { + delete editEntities; + editEntities = NULL; + } + + delete[] locationEntities; + locationEntities = NULL; + + // RAVEN BEGIN + // ddynerman: mp clear + if( gameLocal.isMultiplayer ) { + ClearForwardSpawns(); + for( i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[ i ].Clear(); + } + mpGame.ClearMap(); + } + ambientLights.Clear(); + + nextLagoCheck = 0; +} +// RAVEN END + +/* +=========== +idGameLocal::MapShutdown +============ +*/ +void idGameLocal::MapShutdown( void ) { + Printf( "------------ Game Map Shutdown --------------\n" ); + + gamestate = GAMESTATE_SHUTDOWN; + + if ( soundSystem ) { + soundSystem->ResetListener(); + } + +// RAVEN BEGIN +// rjohnson: new blur special effect + renderSystem->ShutdownSpecialEffects(); +// RAVEN END + + // clear out camera if we're in a cinematic + if ( inCinematic ) { + camera = NULL; + inCinematic = false; + } + +// RAVEN BEGIN +// jscott: cleanup playbacks + gameEdit->ShutdownPlaybacks(); +// RAVEN END + + MapClear( true ); + + instancesEntityIndexWatermarks.Clear(); + clientInstanceFirstFreeIndex = MAX_CLIENTS; + +// RAVEN BEGIN +// jscott: make sure any spurious events are killed + idEvent::ClearEventList(); + + // reset the script to the state it was before the map was started + program.Restart(); + +// bdube: game debug + gameDebug.Shutdown( ); + gameLogLocal.Shutdown( ); +// RAVEN END + + iconManager->Shutdown(); + + pvs.Shutdown(); + +// RAVEN BEGIN +// ddynerman: MP multiple instances + ShutdownInstances(); +// mwhitlock: Dynamic memory consolidation + clip.Clear(); +// RAVEN END + idClipModel::ClearTraceModelCache(); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + idForce::ClearForceList(); +#endif +// RAVEN END + + ShutdownAsyncNetwork(); + + mapFileName.Clear(); + + gameRenderWorld = NULL; + + gamestate = GAMESTATE_NOMAP; + + camera = NULL; + portalSky = NULL; + + Printf( "---------------------------------------------\n" ); +} + +/* +=================== +idGameLocal::DumpOggSounds +=================== +*/ +void idGameLocal::DumpOggSounds( void ) { + int i, j, k, size, totalSize; + idFile *file; + idStrList oggSounds, weaponSounds; + const idSoundShader *soundShader; + const soundShaderParms_t *parms; + idStr soundName; + + for ( i = 0; i < declManager->GetNumDecls( DECL_SOUND ); i++ ) { + soundShader = static_cast(declManager->DeclByIndex( DECL_SOUND, i, false )); + parms = soundShader->GetParms(); + + if ( soundShader->EverReferenced() && soundShader->GetState() != DS_DEFAULTED ) { + + const_cast(soundShader)->EnsureNotPurged(); + + for ( j = 0; j < soundShader->GetNumSounds(); j++ ) { + soundName = soundShader->GetSound( j ); + soundName.BackSlashesToSlashes(); + + // don't OGG sounds that cause a shake because that would + // cause continuous seeking on the OGG file which is expensive + if ( parms->shakes != 0.0f ) { + shakeSounds.AddUnique( soundName ); + continue; + } + + // if not voice over or combat chatter + if ( soundName.Find( "/vo/", false ) == -1 && + soundName.Find( "/combat_chatter/", false ) == -1 && + soundName.Find( "/bfgcarnage/", false ) == -1 && + soundName.Find( "/enpro/", false ) == - 1 && + soundName.Find( "/soulcube/energize_01.wav", false ) == -1 ) { + // don't OGG weapon sounds + if ( soundName.Find( "weapon", false ) != -1 || + soundName.Find( "gun", false ) != -1 || + soundName.Find( "bullet", false ) != -1 || + soundName.Find( "bfg", false ) != -1 || + soundName.Find( "plasma", false ) != -1 ) { + weaponSounds.AddUnique( soundName ); + continue; + } + } + + for ( k = 0; k < shakeSounds.Num(); k++ ) { + if ( shakeSounds[k].IcmpPath( soundName ) == 0 ) { + break; + } + } + if ( k < shakeSounds.Num() ) { + continue; + } + + oggSounds.AddUnique( soundName ); + } + } + } + + file = fileSystem->OpenFileWrite( "makeogg.bat", "fs_savepath" ); + if ( file == NULL ) { + common->Warning( "Couldn't open makeogg.bat" ); + return; + } + + // list all the shake sounds + totalSize = 0; + for ( i = 0; i < shakeSounds.Num(); i++ ) { + size = fileSystem->ReadFile( shakeSounds[i], NULL, NULL ); + totalSize += size; + shakeSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", shakeSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in shake sounds\n\n\n", totalSize >> 10 ); + + // list all the weapon sounds + totalSize = 0; + for ( i = 0; i < weaponSounds.Num(); i++ ) { + size = fileSystem->ReadFile( weaponSounds[i], NULL, NULL ); + totalSize += size; + weaponSounds[i].Replace( "/", "\\" ); + file->Printf( "echo \"%s\" (%d kB)\n", weaponSounds[i].c_str(), size >> 10 ); + } + file->Printf( "echo %d kB in weapon sounds\n\n\n", totalSize >> 10 ); + + // list commands to convert all other sounds to ogg + totalSize = 0; + for ( i = 0; i < oggSounds.Num(); i++ ) { +// RAVEN BEGIN +// rjohnson: changed path to raven's directories +// jnewquist: Use filesystem search path to get absolute file path + idStr tempFile; + idFile *f = fileSystem->OpenFileRead( oggSounds[i] ); + if ( !f ) { + continue; + } + size = f->Length(); + totalSize += size; + tempFile = f->GetFullPath(); + fileSystem->CloseFile(f); + f = NULL; + tempFile.Replace( "/", "\\" ); + +// jnewquist: prevent alterations to files relative to cdpath + const char *cdPath = cvarSystem->GetCVarString("fs_cdpath"); + const int cdPathLen = idStr::Length(cdPath); + if ( cdPathLen > 0 && idStr::Icmpn(cdPath, tempFile, cdPathLen) == 0 ) { + file->Printf( "rem Ignored file from CD path: %s\n", tempFile.c_str() ); + continue; + } + + file->Printf( "echo %d / %d\n", i, oggSounds.Num() ); + file->Printf( "k:\\utility\\oggenc -q 0 \"%s\"\n", tempFile.c_str() ); + file->Printf( "del \"%s\"\n", tempFile.c_str() ); +// RAVEN END + } + file->Printf( "\n\necho %d kB in OGG sounds\n\n\n", totalSize >> 10 ); + + fileSystem->CloseFile( file ); + + shakeSounds.Clear(); +} + +/* +=================== +idGameLocal::GetShakeSounds +=================== +*/ +void idGameLocal::GetShakeSounds( const idDict *dict ) { + const idSoundShader *soundShader; + const char *soundShaderName; + idStr soundName; + + soundShaderName = dict->GetString( "s_shader" ); + if ( soundShaderName != '\0' && dict->GetFloat( "s_shakes" ) != 0.0f ) { + soundShader = declManager->FindSound( soundShaderName ); + + for ( int i = 0; i < soundShader->GetNumSounds(); i++ ) { + soundName = soundShader->GetSound( i ); + soundName.BackSlashesToSlashes(); + + shakeSounds.AddUnique( soundName ); + } + } +} + +/* +=================== +idGameLocal::CacheDictionaryMedia + +This is called after parsing an EntityDef and for each entity spawnArgs before +merging the entitydef. It could be done post-merge, but that would +avoid the fast pre-cache check associated with each entityDef +=================== +*/ +void idGameLocal::CacheDictionaryMedia( const idDict *dict ) { + idDict spawnerArgs; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( dict == NULL ) { +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild") ) { + DumpOggSounds(); + } +#endif + return; + } + +#ifndef _CONSOLE + if ( cvarSystem->GetCVarBool( "com_makingBuild" ) ) { + GetShakeSounds( dict ); + } +#endif + + int numVals = dict->GetNumKeyVals(); + + for ( int i = 0; i < numVals; ++i ) { + const idKeyValue *kv = dict->GetKeyVal( i ); + + #define MATCH(s) \ + (!kv->GetKey().Icmpn( s, strlen(s) )) + /**/ + + if ( !kv || !kv->GetValue().Length() ) { + continue; + } + + if ( MATCH( "model" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MODEL); + declManager->MediaPrint( "Precaching model %s\n", kv->GetValue().c_str() ); + // precache model/animations + if ( declManager->FindType( DECL_MODELDEF, kv->GetValue(), false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( kv->GetValue() ); + // precache .cm files only + collisionModelManager->PreCacheModel( GetMapName(), kv->GetValue() ); + } + } else if ( MATCH( "s_shader" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "snd_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_SOUND); + declManager->FindType( DECL_SOUND, kv->GetValue() ); + } else if ( MATCH( "gui_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_GUI); + if ( !idStr::Icmp( kv->GetKey(), "gui_noninteractive" ) + || !idStr::Icmpn( kv->GetKey(), "gui_parm", 8 ) + || !idStr::Icmp( kv->GetKey(), "gui_inventory" ) ) { + // unfortunate flag names, they aren't actually a gui + } else { + declManager->MediaPrint( "Precaching gui %s\n", kv->GetValue().c_str() ); + uiManager->FindGui( kv->GetValue().c_str(), true ); + } + } else if ( MATCH( "texture" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "mtr_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "screenShot" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "inv_icon" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->FindType( DECL_MATERIAL, kv->GetValue() ); + } else if ( MATCH( "teleport" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + int teleportType = atoi( kv->GetValue() ); + const char *p = ( teleportType ) ? va( "effects/teleporter%i.fx", teleportType ) : "effects/teleporter.fx"; + declManager->FindType( DECL_EFFECT, p ); + } else if ( MATCH( "fx_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_EFFECT); + declManager->MediaPrint( "Precaching fx %s\n", kv->GetValue().c_str() ); + declManager->FindEffect( kv->GetValue() ); + } else if ( MATCH( "skin" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + declManager->MediaPrint( "Precaching skin %s\n", kv->GetValue().c_str() ); + declManager->FindType( DECL_SKIN, kv->GetValue() ); + } else if ( MATCH( "def_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + FindEntityDef( kv->GetValue().c_str(), false ); + } else if ( MATCH( "playback_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching playback %s\n", kv->GetValue().c_str() ); + declManager->FindPlayback( kv->GetValue() ); + } else if ( MATCH( "lipsync_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_ANIM); + declManager->MediaPrint( "Precaching lipsync %s\n", kv->GetValue().c_str() ); + declManager->FindLipSync( kv->GetValue() ); + declManager->FindSound ( kv->GetValue() ); + } else if ( MATCH( "icon " ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_MATERIAL); + idLexer src ( LEXFL_ALLOWPATHNAMES ); + idToken token; + idToken token2; + src.LoadMemory( kv->GetValue(), kv->GetValue().Length(), "icon" ); + + src.ReadToken ( &token ); + if ( src.CheckTokenString ( "," ) ) { + int x, y, w, h; + src.ReadToken ( &token2 ) ; + x = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + y = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + w = token2.GetIntValue ( ); + src.ExpectTokenString ( "," ); + + src.ReadToken ( &token2 ) ; + h = token2.GetIntValue ( ); + + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token, x, y, w, h ); + } else { + uiManager->RegisterIcon ( kv->GetKey ( ).c_str() + 5, token ); + } + } else if ( MATCH( "spawn_" ) ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + MEM_SCOPED_TAG(tag,MA_DECL); + spawnerArgs.Set ( kv->GetKey ( ).c_str() + 6, kv->GetValue ( ) ); + } + + #undef MATCH + } + + if ( spawnerArgs.GetNumKeyVals() ) { + CacheDictionaryMedia ( &spawnerArgs ); + } +// RAVEN END +} + +/* +=========== +idGameLocal::InitScriptForMap +============ +*/ +void idGameLocal::InitScriptForMap( void ) { + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_DEFAULT); +// RAVEN END + // create a thread to run frame commands on + frameCommandThread = new idThread(); + frameCommandThread->ManualDelete(); + frameCommandThread->SetThreadName( "frameCommands" ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_SCRIPT); + + // run the main game script function (not the level specific main) + const function_t *func = program.FindFunction( SCRIPT_DEFAULTFUNC ); + + +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG_SET(tag,MA_DEFAULT); + + if ( func != NULL ) { + idThread *thread = new idThread( func ); + if ( thread->Start() ) { + // thread has finished executing, so delete it + delete thread; + } + } + +// RAVEN END + +} + +/* +=========== +idGameLocal::SpawnPlayer +============ +*/ +void idGameLocal::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + idEntity *ent; + idDict args; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_ENTITY); +// RAVEN END + + // they can connect + common->DPrintf( "SpawnPlayer: %i\n", clientNum ); + + args.SetInt( "spawn_entnum", clientNum ); + args.Set( "name", va( "player%d", clientNum + 1 ) ); +// RAVEN BEGIN +// bdube: changed marine class + args.Set( "classname", idPlayer::GetSpawnClassname() ); +// RAVEN END + + // This takes a really long time. + PACIFIER_UPDATE; + if ( !SpawnEntityDef( args, &ent ) || !entities[ clientNum ] ) { + Error( "Failed to spawn player as '%s'", args.GetString( "classname" ) ); + } + + // make sure it's a compatible class + if ( !ent->IsType( idPlayer::GetClassType() ) ) { + Error( "'%s' spawned the player as a '%s'. Player spawnclass must be a subclass of idPlayer.", args.GetString( "classname" ), ent->GetClassname() ); + } + + PACIFIER_UPDATE; + + if ( clientNum != ENTITYNUM_NONE ) { + if ( clientNum >= numClients ) { + numClients = clientNum + 1; + } + } + + mpGame.SpawnPlayer( clientNum ); +} + +/* +================ +idGameLocal::GetClientByNum +================ +*/ +idPlayer *idGameLocal::GetClientByNum( int current ) const { + if ( current == MAX_CLIENTS ) { + return NULL; + } + if ( current == ENTITYNUM_NONE ) { + return static_cast( entities[ current ] ); + } + if ( current < 0 || current >= numClients ) { + // that's a bit nasty but I suppose it has it's use + current = 0; + } + if ( entities[current] ) { + return static_cast( entities[ current ] ); + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByName +================ +*/ +idPlayer *idGameLocal::GetClientByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END +// RAVEN BEGIN +// bdube: escape codes + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { +// RAVEN END + return static_cast( ent ); + } + } + } + return NULL; +} + +/* +================ +idGameLocal::GetClientByCmdArgs +================ +*/ +idPlayer *idGameLocal::GetClientByCmdArgs( const idCmdArgs &args ) const { + idPlayer *player; + idStr client = args.Argv( 1 ); + if ( !client.Length() ) { + return NULL; + } + // we don't allow numeric ui_name so this can't go wrong + if ( client.IsNumeric() ) { + player = GetClientByNum( atoi( client.c_str() ) ); + } else { + player = GetClientByName( client.c_str() ); + } + if ( !player ) { + common->Printf( "Player '%s' not found\n", client.c_str() ); + } + return player; +} + +/* +================ +idGameLocal::GetClientNumByName +================ +*/ +int idGameLocal::GetClientNumByName( const char *name ) const { + int i; + idEntity *ent; + for ( i = 0 ; i < numClients ; i++ ) { + ent = entities[ i ]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( idStr::IcmpNoEscape( name, userInfo[ i ].GetString( "ui_name" ) ) == 0 ) { + return i; + } + } + } + return -1; +} + +/* +================ +idGameLocal::GetNextClientNum +================ +*/ +int idGameLocal::GetNextClientNum( int _current ) const { + int i, current; + + current = 0; + for ( i = 0; i < numClients; i++) { + current = ( _current + i + 1 ) % numClients; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entities[ current ] && entities[ current ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return current; + } + } + + return current; +} + +/* +================ +idGameLocal::GetLocalPlayer + +Nothing in the game tic should EVER make a decision based on what the +local client number is, it shouldn't even be aware that there is a +draw phase even happening. + +localClientNum == MAX_CLIENTS when playing server demos +don't send back the wrong entity obviously +================ +*/ +idPlayer *idGameLocal::GetLocalPlayer() const { + if ( localClientNum == MAX_CLIENTS ) { + return static_cast( entities[ ENTITYNUM_NONE ] ); + } + + if ( localClientNum < 0 || localClientNum >= MAX_CLIENTS ) { + return NULL; + } + + if ( !entities[ localClientNum ] || !entities[ localClientNum ]->IsType( idPlayer::GetClassType() ) ) { + // not fully in game yet + return NULL; + } + + return static_cast( entities[ localClientNum ] ); +} + +/* +================ +idGameLocal::SetupClientPVS +for client spectating others, get the pvs of spectated +================ +*/ +pvsHandle_t idGameLocal::GetClientPVS( idPlayer *player, pvsType_t type ) { + if ( player->GetPrivateCameraView() ) { + return pvs.SetupCurrentPVS( player->GetPrivateCameraView()->GetPVSAreas(), player->GetPrivateCameraView()->GetNumPVSAreas() ); + } else if ( camera ) { + return pvs.SetupCurrentPVS( camera->GetPVSAreas(), camera->GetNumPVSAreas() ); + } else { + if ( player->spectating && player->spectator != player->entityNumber && entities[ player->spectator ] ) { + player = static_cast( entities[ player->spectator ] ); + } + return pvs.SetupCurrentPVS( player->GetPVSAreas(), player->GetNumPVSAreas() ); + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +================ +idGameLocal::GetSpawnCount +================ +*/ +int idGameLocal::GetSpawnCount ( void ) const { + return spawnCount; +} + +/* +================ +idGameLocal::SetupPortalSkyPVS +================ +*/ +bool idGameLocal::SetupPortalSkyPVS( idPlayer *player ) { + + int i, count, numAreas; + const int *areaNums; + bool *visibleAreas; + + if( !portalSky ) { + + return( false ); + } + + // Allocate room for the area flags + numAreas = gameRenderWorld->NumAreas(); + visibleAreas = ( bool * )_alloca( numAreas ); + memset( visibleAreas, 0, numAreas ); + + // Grab the areas the player can see.... + count = player->GetNumPVSAreas(); + areaNums = player->GetPVSAreas(); + for( i = 0; i < count; i++ ) { + + // Work out the referenced areas + gameRenderWorld->FindVisibleAreas( player->GetPhysics()->GetOrigin(), areaNums[i], visibleAreas ); + } + + // Do any of the visible areas have a skybox? + for( i = 0; i < numAreas; i++ ) { + + if( !visibleAreas[i] ) { + + continue; + } + + if( gameRenderWorld->HasSkybox( i ) ) { + + break; + } + } + + // .. if any one has a skybox component, then merge in the portal sky + return ( i != numAreas ); +} +// RAVEN END + +/* +=============== +idGameLocal::UpdateClientsPVS +=============== +*/ +void idGameLocal::UpdateClientsPVS( void ) { + int i; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[ i ] ) { + continue; + } + assert( clientsPVS[ i ].i == -1 ); + clientsPVS[ i ] = GetClientPVS( static_cast< idPlayer * >( entities[ i ] ), PVS_NORMAL ); + } +} + +/* +================ +idGameLocal::SetupPlayerPVS +================ +*/ +void idGameLocal::SetupPlayerPVS( void ) { + int i = 0; + idPlayer * player = NULL; + pvsHandle_t otherPVS, newPVS; + + UpdateClientsPVS( ); + + playerPVS.i = -1; + for ( i = 0; i < numClients; i++ ) { + if ( !entities[i] ) { + return; + } + assert( entities[i]->IsType( idPlayer::GetClassType() ) ); + + player = static_cast( entities[ i ] ); + + if ( playerPVS.i == -1 ) { + playerPVS = clientsPVS[ i ]; + freePlayerPVS = false; // don't try to free it as long as we stick to the client PVS + } else { + otherPVS = clientsPVS[ i ]; + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS = newPVS; + freePlayerPVS = true; // that merged one will need to be freed + } + +// RAVEN BEGIN +// jscott: for portal skies + portalSkyVisible = SetupPortalSkyPVS( player ); + if ( portalSkyVisible ) { + + otherPVS = pvs.SetupCurrentPVS( portalSky->GetPVSAreas(), portalSky->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + + if ( freePlayerPVS ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + pvs.FreeCurrentPVS( otherPVS ); + playerPVS = newPVS; + freePlayerPVS = true; + } +// RAVEN END + + if ( playerConnectedAreas.i == -1 ) { + playerConnectedAreas = GetClientPVS( player, PVS_CONNECTED_AREAS ); + } else { + otherPVS = GetClientPVS( player, PVS_CONNECTED_AREAS ); + newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); + pvs.FreeCurrentPVS( playerConnectedAreas ); + pvs.FreeCurrentPVS( otherPVS ); + playerConnectedAreas = newPVS; + } + } +} + +/* +================ +idGameLocal::FreePlayerPVS +================ +*/ +void idGameLocal::FreePlayerPVS( void ) { + int i; + + // only clear playerPVS if it's a different handle than the one in clientsPVS + if ( freePlayerPVS && playerPVS.i != -1 ) { + pvs.FreeCurrentPVS( playerPVS ); + freePlayerPVS = false; + } + playerPVS.i = -1; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( clientsPVS[ i ].i >= 0 ) { + pvs.FreeCurrentPVS( clientsPVS[ i ] ); + clientsPVS[i].i = -1; + } + } + if ( playerConnectedAreas.i != -1 ) { + pvs.FreeCurrentPVS( playerConnectedAreas ); + playerConnectedAreas.i = -1; + } +} + +/* +================ +idGameLocal::InPlayerPVS + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerPVS( idEntity *ent ) const { + if ( playerPVS.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerPVS, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + +/* +================ +idGameLocal::InPlayerConnectedArea + + should only be called during entity thinking and event handling +================ +*/ +bool idGameLocal::InPlayerConnectedArea( idEntity *ent ) const { + if ( playerConnectedAreas.i == -1 ) { + return false; + } + return pvs.InCurrentPVS( playerConnectedAreas, ent->GetPVSAreas(), ent->GetNumPVSAreas() ); +} + + +/* +================ +idGameLocal::UpdateGravity +================ +*/ +void idGameLocal::UpdateGravity( void ) { + idEntity *ent; + + idCVar* gravityCVar = NULL; + + if( gameLocal.isMultiplayer ) { + gravityCVar = &g_mp_gravity; + } else { + gravityCVar = &g_gravity; + } + + if ( gravityCVar->IsModified() ) { + if ( gravityCVar->GetFloat() == 0.0f ) { + gravityCVar->SetFloat( 1.0f ); + } + gravity.Set( 0, 0, -gravityCVar->GetFloat() ); + + // update all physics objects + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + idPhysics *phys = ent->GetPhysics(); + if ( phys ) { + phys->SetGravity( gravity ); + } +// RAVEN BEGIN +// ddynerman: jump pads + } else if ( ent->IsType( rvJumpPad::GetClassType() ) ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + } +// RAVEN END + } + gravityCVar->ClearModified(); + } +} + +/* +================ +idGameLocal::GetGravity +================ +*/ +const idVec3 &idGameLocal::GetGravity( void ) const { + return gravity; +} + +/* +================ +idGameLocal::SortActiveEntityList + + Sorts the active entity list such that pushing entities come first, + actors come next and physics team slaves appear after their master. +================ +*/ +void idGameLocal::SortActiveEntityList( void ) { + idEntity *ent, *next_ent, *master, *part; + + // if the active entity list needs to be reordered to place physics team masters at the front + if ( sortTeamMasters ) { + // Sort bind masters first + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + for ( part = ent->GetBindMaster ( ); part; part = part->GetBindMaster ( ) ) { + // Ensure we dont rerun the whole active entity list if our cached next_ent is one + // of the entities we are moving + if ( next_ent == part ) { + next_ent = next_ent->activeNode.Next(); + part = ent->GetBindMaster ( ); + continue; + } + part->activeNode.Remove(); + part->activeNode.AddToFront( activeEntities ); + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( master && master == ent ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + // if the active entity list needs to be reordered to place pushers at the front + if ( sortPushers ) { + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an actor on the team + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( part->GetPhysics()->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + break; + } + } + // if there is an actor on the team + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + + for ( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + master = ent->GetTeamMaster(); + if ( !master || master == ent ) { + // check if there is an entity on the team using parametric physics + for ( part = ent; part != NULL; part = part->GetNextTeamEntity() ) { + if ( part->GetPhysics()->IsType( idPhysics_Parametric::GetClassType() ) ) { + break; + } + if ( part->GetPhysics()->IsType( rvPhysics_Spline::GetClassType() ) ) { + break; + } + } + // if there is an entity on the team using parametric physics + if ( part ) { + ent->activeNode.Remove(); + ent->activeNode.AddToFront( activeEntities ); + } + } + } + } + + sortTeamMasters = false; + sortPushers = false; +} + +/* +================ +idGameLocal::MenuFrame +Called each session frame when a map is not running (e.g. usually in the main menu) +================ +*/ +void idGameLocal::MenuFrame( void ) { } + +/* +================ +idGameLocal::RunFrame +================ +*/ +// RAVEN BEGIN +gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ) { + idEntity * ent; + int num; + float ms; + idTimer timer_think, timer_events, timer_singlethink; + idTimer timer_misc, timer_misc2; + gameReturn_t ret; + idPlayer *player; + const renderView_t *view; + + editors = activeEditors; + isLastPredictFrame = lastCatchupFrame; + + assert( !isClient ); + + player = GetLocalPlayer(); + + do { + + previousTime = time; + + // sanity - game frame is counted on both sides of the DLL fence + // but I've seen cases where it would be offset and we need to make sure that doesn't happen + if ( serverGameFrame != framenum ) { + common->Warning( "core and game frame counters are out of sync: %d/%d", serverGameFrame, framenum ); + if ( serverGameFrame > framenum ) { + int delta = serverGameFrame - framenum; + time += delta * GetMSec(); + framenum = serverGameFrame; + } if ( serverGameFrame < framenum ) { + // don't do anything, let the core catchup to us + memset( &ret, 0, sizeof( ret ) ); + return ret; + } + } + + // update the game time + framenum++; + // bdube: use GetMSec access rather than USERCMD_TIME + time += GetMSec(); + + realClientTime = time; + { +TIME_THIS_SCOPE("idGameLocal::RunFrame - gameDebug.BeginFrame()"); + // bdube: added advanced debug support + gameDebug.BeginFrame( ); + gameLogLocal.BeginFrame( time ); + } + +#ifdef GAME_DLL + // allow changing SIMD usage on the fly + if ( com_forceGenericSIMD.IsModified() ) { + idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() ); + } +#endif + + // make sure the random number counter is used each frame so random events + // are influenced by the player's actions + random.RandomInt(); + + if ( player ) { + // update the renderview so that any gui videos play from the right frame + view = player->GetRenderView(); + if ( view ) { + gameRenderWorld->SetRenderView( view ); + } + } + + // If modview is running then let it think + common->ModViewThink( ); + + // rjohnson: added option for guis to always think + common->RunAlwaysThinkGUIs( time ); + + // nmckenzie: Let AI System stuff update itself. + if ( !isMultiplayer ) { +#ifndef _MPBETA + aiManager.RunFrame(); +#endif // !_MPBETA + } + + timer_misc.Start(); + + // set the user commands for this frame + usercmds = clientCmds; + + // create a merged pvs for all players + // do this before we process events, which may rely on PVS info + SetupPlayerPVS(); + + // process events on the server + ServerProcessEntityNetworkEventQueue(); + + // update our gravity vector if needed. + UpdateGravity(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->StartFrame(); + } + + // sort the active entity list + SortActiveEntityList(); + + timer_think.Clear(); + timer_think.Start(); + + // jscott: for timing and effect handling + timer_misc2.Start(); + timer_misc.Stop(); + + // let entities think + if ( g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + idPlayer *player; + idVec3 origin; + + player = GetLocalPlayer(); + if ( player ) { + origin = player->GetPhysics()->GetOrigin(); + } + + idBounds viewTextBounds( origin ); + viewTextBounds.ExpandSelf( 128.0f ); + + num = 0; + + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && inCinematic && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + timer_singlethink.Clear(); + timer_singlethink.Start(); + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + timer_singlethink.Stop(); + ms = timer_singlethink.Milliseconds(); + if ( ms >= g_timeentities.GetFloat() ) { + // rjohnson: will now draw entity info for long thinkers + Printf( "%d: entity '%s' [%s]: %.1f ms\n", time, ent->name.c_str(), ent->GetPhysics()->GetOrigin().ToString(), ms ); + + if ( ms >= g_timeentities.GetFloat() * 3.0f ) + { + ent->mLastLongThinkColor = colorRed; + } + else + { + ent->mLastLongThinkColor[0] = 1.0f; + ent->mLastLongThinkColor[1] = 2.0f - (( ms - g_timeentities.GetFloat()) / g_timeentities.GetFloat() ); + ent->mLastLongThinkColor[2] = 0.0f; + ent->mLastLongThinkColor[3] = 1.0f; + } + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + ent->mLastLongThinkTime = time + 2000; + } + else if ( ent->mLastLongThinkTime > time ) + { + ent->DrawDebugEntityInfo( 0, &viewTextBounds, &ent->mLastLongThinkColor ); + } + num++; + } + } else { + if ( inCinematic ) { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + if ( g_cinematic.GetBool() && !ent->cinematic ) { + ent->GetPhysics()->UpdateTime( time ); + continue; + } + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } else { + num = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + // ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = ent; + ent->Think(); + currentThinkingEntity = NULL; + num++; + } + } + } + + // remove any entities that have stopped thinking + if ( numEntitiesToDeactivate ) { + idEntity *next_ent; + int c = 0; + for( ent = activeEntities.Next(); ent != NULL; ent = next_ent ) { + next_ent = ent->activeNode.Next(); + if ( !ent->thinkFlags ) { + ent->activeNode.Remove(); + c++; + } + } + //assert( numEntitiesToDeactivate == c ); + numEntitiesToDeactivate = 0; + } + + timer_think.Stop(); + timer_events.Clear(); + timer_events.Start(); + + if ( isLastPredictFrame ) { + // bdube: client entities + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } + + // service any pending events + idEvent::ServiceEvents(); + + // nrausch: player could have been deleted in an event + player = GetLocalPlayer(); + + timer_events.Stop(); + + if ( isLastPredictFrame ) { + // jscott: effect system uses gravity and the player PVS + bse->EndFrame(); + } + + // do multiplayer related stuff + if ( isMultiplayer ) { + mpGame.Run(); + } + + // free the player pvs + FreePlayerPVS(); + + // display how long it took to calculate the current game frame + if ( g_frametime.GetBool() ) { + Printf( "game %d: all:%.1f th:%.1f ev:%.1f %d ents \n", + time, timer_think.Milliseconds() + timer_events.Milliseconds(), + timer_think.Milliseconds(), timer_events.Milliseconds(), num ); + } + + // build the return value + ret.consistencyHash = 0; + ret.sessionCommand[0] = 0; + + // see if a target_sessionCommand has forced a changelevel + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + break; + } + + // make sure we don't loop forever when skipping a cinematic + if ( skipCinematic && ( time > cinematicMaxSkipTime ) ) { + Warning( "Exceeded maximum cinematic skip length. Cinematic may be looping infinitely." ); + skipCinematic = false; + break; + } + + // jscott: additional timings + timer_misc2.Stop(); + + if ( g_frametime.GetInteger() > 1 ) { + gameLocal.Printf( "misc:%.1f misc2:%.1f\n", timer_misc.Milliseconds(), timer_misc2.Milliseconds() ); + } + // bdube: let gameDebug know that its not in a game frame anymore + gameDebug.EndFrame( ); + gameLogLocal.EndFrame( ); + + } while( ( inCinematic || ( time < cinematicStopTime ) ) && skipCinematic ); + + ret.syncNextGameFrame = skipCinematic; + if ( skipCinematic ) { + soundSystem->EndCinematic(); + soundSystem->SetMute( false ); + skipCinematic = false; + } + + // show any debug info for this frame + RunDebugInfo(); + D_DrawDebugLines(); + + g_simpleItems.ClearModified(); + return ret; +} +// RAVEN END + +/* +================ +idGameLocal::RepeaterFrame +================ +*/ +void idGameLocal::RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int serverGameFrame ) { + int i; + + // passing serverGameFrame is strictly a sanity check + // the adjustment in case of problem in RunFrame should cascade to here and never be a long term problem + static int FrameDelta = 0; + if ( serverGameFrame != framenum ) { + // don't want to spam the warning. don't have a good repro case to fix this but still want to track the error (seen it happen) + int delta = serverGameFrame - framenum; + if ( delta != FrameDelta ) { + if ( framenum != 0 ) { // to skip printing the initial messages + common->Warning( "repeater frame and game frame out of sync: %d/%d", serverGameFrame, framenum ); + } + FrameDelta = delta; + } + } else { + if ( FrameDelta != 0 ) { + common->Warning( "repeater in sync with game frame at %d", serverGameFrame ); + } + FrameDelta = 0; + } + + ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) ); + + for ( i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].active ) { + continue; + } + + if ( viewers[ i ].nopvs ) { + continue; + } + + viewers[ i ].origin = clientOrigins[ i ]; + + if ( viewers[ i ].origin.followClient >= 0 && entities[ viewers[ i ].origin.followClient ] && entities[ viewers[ i ].origin.followClient ]->IsType( idPlayer::GetClassType() ) ) { + // if the viewer is following someone, use their origin + idPlayer *player = static_cast< idPlayer * >( entities[ viewers[ i ].origin.followClient ] ); + viewers[ i ].origin.origin = player->GetEyePosition(); + } + + viewers[ i ].pvsArea = pvs.GetPVSArea( viewers[ i ].origin.origin ); + } +} + +/* +====================================================================== + + Game view drawing + +====================================================================== +*/ + +/* +==================== +idGameLocal::CalcFov + +Calculates the horizontal and vertical field of view based on a horizontal field of view and custom aspect ratio +==================== +*/ +void idGameLocal::CalcFov( float base_fov, float &fov_x, float &fov_y ) const { + float x; + float y; + float ratio_x; + float ratio_y; + + if ( !sys->FPU_StackIsEmpty() ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: FPU stack not empty" ); + } + +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( g_fixedHorizFOV.GetBool() ) { + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + ratio_x = 4.0f; + ratio_y = 3.0f; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + x = ratio_x / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( ratio_y, x ); + fov_y = y * 360.0f / idMath::PI; + fov_x = base_fov; + return; + } +// RAVEN END + + // first, calculate the vertical fov based on a 640x480 view + x = 640.0f / idMath::Tan( base_fov / 360.0f * idMath::PI ); + y = idMath::ATan( 480.0f, x ); + fov_y = y * 360.0f / idMath::PI; + + // FIXME: somehow, this is happening occasionally + assert( fov_y > 0 ); + if ( fov_y <= 0 ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } + + int aspectChoice = cvarSystem->GetCVarInteger( "r_aspectRatio" ); + switch( aspectChoice ) { + default : + case 0 : + // 4:3 + fov_x = base_fov; + return; + break; + + case 1 : + // 16:9 + ratio_x = 16.0f; + ratio_y = 9.0f; + break; + + case 2 : + // 16:10 + ratio_x = 16.0f; + ratio_y = 10.0f; + break; + } + + y = ratio_y / idMath::Tan( fov_y / 360.0f * idMath::PI ); + fov_x = idMath::ATan( ratio_x, y ) * 360.0f / idMath::PI; + + if ( fov_x < base_fov ) { + fov_x = base_fov; + x = ratio_x / idMath::Tan( fov_x / 360.0f * idMath::PI ); + fov_y = idMath::ATan( ratio_y, x ) * 360.0f / idMath::PI; + } + + // FIXME: somehow, this is happening occasionally + assert( ( fov_x > 0 ) && ( fov_y > 0 ) ); + if ( ( fov_y <= 0 ) || ( fov_x <= 0 ) ) { + Printf( sys->FPU_GetState() ); + Error( "idGameLocal::CalcFov: bad result" ); + } +} + +void ClearClipProfile( void ); +void DisplayClipProfile( void ); + +/* +================ +idGameLocal::Draw + +makes rendering and sound system calls +================ +*/ +bool idGameLocal::Draw( int clientNum ) { +// DisplayClipProfile( ); +// ClearClipProfile( ); + + if ( isMultiplayer ) { + return mpGame.Draw( clientNum ); + } + + idPlayer *player = static_cast(entities[ clientNum ]); + + if ( !player ) { + return false; + } + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming. +#if defined(_XENON) + renderView_t *view = player->GetRenderView(); + // nrausch: view doesn't necessarily exist yet + if ( !view ) { + player->CalculateRenderView(); + view = player->GetRenderView(); + } +#endif +// RAVEN END + // render the scene + player->playerView.RenderPlayerView( player->hud ); + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud( ); +// RAVEN END + + return true; +} + +/* +================ +idGameLocal::HandleESC +================ +*/ +escReply_t idGameLocal::HandleESC( idUserInterface **gui ) { + +//RAVEN BEGIN +//asalmon: xbox dedicated server needs to bring up a special menu +#ifdef _XBOX + if( cvarSystem->GetCVarBool( "net_serverDedicated" )) + { + return ESC_MAIN; + } +#endif +//RAVEN END + + if ( isMultiplayer ) { + *gui = StartMenu(); + // we may set the gui back to NULL to hide it + return ESC_GUI; + } + idPlayer *player = GetLocalPlayer(); + if ( player ) { + if ( player->HandleESC() ) { + return ESC_IGNORE; + } else { + return ESC_MAIN; + } + } + return ESC_MAIN; +} + +/* +================ +idGameLocal::UpdatePlayerPostMainMenu +================ +*/ +void idGameLocal::UpdatePlayerPostMainMenu() { + idPlayer* player = GetLocalPlayer(); + + //dedicated server? + if( !player) { + return; + } + + //crosshair may have changed + player->UpdateHudWeapon(); +} + +/* +================ +idGameLocal::StartMenu +================ +*/ +idUserInterface* idGameLocal::StartMenu( void ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.StartMenu(); +} + +/* +================ +idGameLocal::GetClientStats +================ +*/ +void idGameLocal::GetClientStats( int clientNum, char *data, const int len ) { + mpGame.PlayerStats( clientNum, data, len ); +} + +/* +================ +idGameLocal::SwitchTeam +================ +*/ +void idGameLocal::SwitchTeam( int clientNum, int team ) { + + idPlayer * player; + player = clientNum >= 0 ? static_cast( gameLocal.entities[ clientNum ] ) : NULL; + + if ( !player ) { + return; + } + + int oldTeam = player->team; + + // Put in spectator mode + if ( team == -1 ) { + static_cast< idPlayer * >( entities[ clientNum ] )->Spectate( true ); + } + // Switch to a team + else { + mpGame.SwitchToTeam ( clientNum, oldTeam, team ); + } +} + +/* +================ +idGameLocal::HandleGuiCommands +================ +*/ +const char* idGameLocal::HandleGuiCommands( const char *menuCommand ) { + if ( !isMultiplayer ) { + return NULL; + } + return mpGame.HandleGuiCommands( menuCommand ); +} + +/* +================ +idGameLocal::HandleMainMenuCommands +================ +*/ +void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { + if ( !idStr::Icmp( menuCommand, "initCreateServerSettings" ) ) { + int guiValue = 0; + + switch ( mpGame.GameTypeToVote( si_gameType.GetString() ) ) { + case idMultiplayerGame::VOTE_GAMETYPE_DM: + guiValue = 0; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TOURNEY: + guiValue = 1; + break; + case idMultiplayerGame::VOTE_GAMETYPE_TDM: + guiValue = 2; + break; + case idMultiplayerGame::VOTE_GAMETYPE_CTF: + guiValue = 3; + break; + case idMultiplayerGame::VOTE_GAMETYPE_ARENA_CTF: + guiValue = 4; + break; + case idMultiplayerGame::VOTE_GAMETYPE_DEADZONE: + guiValue = 5; + break; + } + + gui->SetStateInt( "currentGametype", guiValue ); + } else if ( !idStr::Icmp( menuCommand, "filterByNextMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = 0; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + ++filterMod; + if ( filterMod < modList.Num() ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "filterByPrevMod" ) ) { + BuildModList(); + + if ( modList.Num() > 0 && (filterMod < 0 || filterMod >= modList.Num()) ) { + filterMod = modList.Num() - 1; + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + --filterMod; + if ( filterMod >= 0 ) { + networkSystem->UseSortFunction( filterByMod, true ); + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } else { + filterMod = -1; + networkSystem->UseSortFunction( filterByMod, false ); + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + } + } else if ( !idStr::Icmp( menuCommand, "updateFilterByMod" ) ) { + if ( filterMod < 0 || filterMod >= modList.Num() ) { + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } else { + gui->SetStateString( "filterMod", modList[ filterMod ].c_str() ); + } + } else if ( !idStr::Icmp( menuCommand, "server_clearSort" ) ) { + filterMod = -1; + gui->SetStateString( "filterMod", common->GetLocalizedString( "#str_123008" ) ); + } + + return; +} + +/* +================ +idGameLocal::GetLevelMap + + should only be used for in-game level editing +================ +*/ +idMapFile *idGameLocal::GetLevelMap( void ) { +// RAVEN BEGIN +// rhummer: Added the HasBeenResolved check, if resolve has been run then it doesn't have func_groups. +// Which we probably don't want, so force the map to be reloaded. + if ( mapFile && mapFile->HasPrimitiveData() && !mapFile->HasBeenResloved() ) { +// RAVEN END + return mapFile; + } + if ( !mapFileName.Length() ) { + return NULL; + } + + if ( mapFile ) { + delete mapFile; + } + + mapFile = new idMapFile; + if ( !mapFile->Parse( mapFileName ) ) { + delete mapFile; + mapFile = NULL; + } + + return mapFile; +} + +/* +================ +idGameLocal::GetMapName +================ +*/ +const char *idGameLocal::GetMapName( void ) const { + return mapFileName.c_str(); +} + +/* +================ +idGameLocal::CallFrameCommand +================ +*/ +void idGameLocal::CallFrameCommand( idEntity* ent, const char* frameCommand ) { + const function_t *func = program.FindFunction( frameCommand ); + if ( !func ) { + Warning( "Script function '%s' not found.", frameCommand ); + return; + } + CallFrameCommand ( ent, func ); +} + +void idGameLocal::CallFrameCommand( idEntity *ent, const function_t *frameCommand ) { + frameCommandThread->CallFunction( ent, frameCommand, true ); + frameCommandThread->Execute(); +} + +/* +================ +idGameLocal::CallObjectFrameCommand +================ +*/ +void idGameLocal::CallObjectFrameCommand( idEntity *ent, const char *frameCommand ) { + const function_t *func; + + func = ent->scriptObject.GetFunction( frameCommand ); + if ( !func ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idTestModel::GetClassType() ) ) { +// RAVEN END + Error( "Unknown function '%s' called for frame command on entity '%s'", frameCommand, ent->name.c_str() ); + } + } else { + frameCommandThread->CallFunction( ent, func, true ); + frameCommandThread->Execute(); + } +} + +/* +================ +idGameLocal::ShowTargets +================ +*/ +void idGameLocal::ShowTargets( void ) { + idMat3 axis = GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + const idVec3 &viewPos = GetLocalPlayer()->GetPhysics()->GetOrigin(); + idBounds viewTextBounds( viewPos ); + idBounds viewBounds( viewPos ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + idBounds totalBounds; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + totalBounds = ent->GetPhysics()->GetAbsBounds(); + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + totalBounds.AddBounds( target->GetPhysics()->GetAbsBounds() ); + } + } + + if ( !viewBounds.IntersectsBounds( totalBounds ) ) { + continue; + } + + float dist; + idVec3 dir = totalBounds.GetCenter() - viewPos; + dir.NormalizeFast(); + totalBounds.RayIntersection( viewPos, dir, dist ); + float frac = ( 512.0f - dist ) / 512.0f; + if ( frac < 0.0f ) { + continue; + } + + gameRenderWorld->DebugBounds( ( ent->IsHidden() ? colorLtGrey : colorOrange ) * frac, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + idVec3 center = ent->GetPhysics()->GetAbsBounds().GetCenter(); + gameRenderWorld->DrawText( ent->name.c_str(), center - up, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), center, 0.1f, colorWhite * frac, axis, 1 ); + gameRenderWorld->DrawText( va( "#%d", ent->entityNumber ), center + up, 0.1f, colorWhite * frac, axis, 1 ); + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow * frac, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen * frac, box, target->GetPhysics()->GetOrigin() ); + } + } + } +} + +/* +================ +idGameLocal::RunDebugInfo +================ +*/ +void idGameLocal::RunDebugInfo( void ) { + idEntity *ent; + idPlayer *player; + + player = GetLocalPlayer(); + if ( !player ) { + return; + } + + const idVec3 &origin = player->GetPhysics()->GetOrigin(); + + if ( g_showEntityInfo.GetBool() ) { + idBounds viewTextBounds( origin ); + idBounds viewBounds( origin ); + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // don't draw the worldspawn + if ( ent == world ) { + continue; + } + +// RAVEN BEGIN +// rjohnson: moved entity info out of idGameLocal into its own function + ent->DrawDebugEntityInfo(&viewBounds, &viewTextBounds); +// RAVEN END + } + } + + // debug tool to draw bounding boxes around active entities + if ( g_showActiveEntities.GetBool() ) { + for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { + idBounds b = ent->GetPhysics()->GetBounds(); + if ( b.GetVolume() <= 0 ) { + b[0][0] = b[0][1] = b[0][2] = -8; + b[1][0] = b[1][1] = b[1][2] = 8; + } + if ( ent->fl.isDormant ) { + gameRenderWorld->DebugBounds( colorYellow, b, ent->GetPhysics()->GetOrigin() ); + } else { + gameRenderWorld->DebugBounds( colorGreen, b, ent->GetPhysics()->GetOrigin() ); + } + } + } + +// RAVEN BEGIN +// bdube: client entities + if ( cl_showEntityInfo.GetBool ( ) ) { + rvClientEntity* cent; + for( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->DrawDebugInfo ( ); + } + } +// RAVEN END + + if ( g_showTargets.GetBool() ) { + ShowTargets(); + } + + if ( g_showTriggers.GetBool() ) { + idTrigger::DrawDebugInfo(); + } + + if ( ai_showCombatNodes.GetBool() ) { +// idCombatNode::DrawDebugInfo(); + } + + if ( ai_showPaths.GetBool() ) { + idPathCorner::DrawDebugInfo(); + } + + if ( g_editEntityMode.GetBool() ) { + if ( gameLocal.isMultiplayer ) { + g_editEntityMode.SetInteger(0); + Printf( "Not allowed in multiplayer.\n" ); + } else { + editEntities->DisplayEntities(); + } + } + + if ( g_showCollisionWorld.GetBool() ) { + // use g_maxShowDistance value instead of 128.0f + collisionModelManager->DrawModel( clip[0]->GetWorldCollisionModel(), vec3_origin, mat3_identity, origin, mat3_identity, g_maxShowDistance.GetFloat() ); + } + + if ( g_showCollisionModels.GetBool() ) { + if( g_showCollisionModels.GetInteger() == 2 ) { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player, &idPlayer::GetClassType() ); + } else { + clip[ 0 ]->DrawClipModels( player->GetEyePosition(), g_maxShowDistance.GetFloat(), pm_thirdPerson.GetBool() ? NULL : player ); + } + + } + + if ( g_showCollisionTraces.GetBool() ) { + clip[ 0 ]->PrintStatistics(); + } + + if ( g_showPVS.GetInteger() ) { + pvs.DrawPVS( origin, ( g_showPVS.GetInteger() == 2 ) ? PVS_ALL_PORTALS_OPEN : PVS_NORMAL ); + } + +// RAVEN BEGIN +// rjohnson: added debug hud + if ( gameDebug.IsHudActive ( DBGHUD_PHYSICS ) ) { + clip[ 0 ]->DebugHudStatistics(); + } + + clip[ 0 ]->ClearStatistics(); +// RAVEN END + + if ( aas_test.GetInteger() >= 0 ) { + idAAS *aas = GetAAS( aas_test.GetInteger() ); + if ( aas ) { + aas->Test( origin ); + if ( ai_testPredictPath.GetBool() ) { + idVec3 velocity; + predictedPath_t path; + + velocity.x = idMath::Cos( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.y = idMath::Sin( DEG2RAD( player->viewAngles.yaw ) ) * 100.0f; + velocity.z = 0.0f; + idAI::PredictPath( player, aas, origin, velocity, 1000, 100, SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA, path ); + } + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin ); + } + } + } + if ( aas_showProblemAreas.GetInteger() == 3 ) { + for( int i=0; iIsValid() ) { + continue; + } + + if ( aas->GetSettings()->debugDraw ) { + aas->ShowAreas( origin, true ); + } + } + } +// RAVEN END + } + + if ( ai_showObstacleAvoidance.GetInteger() == 2 ) { + idAAS *aas = GetAAS( 0 ); + if ( aas ) { + idVec3 seekPos; + obstaclePath_t path; + + seekPos = player->GetPhysics()->GetOrigin() + player->viewAxis[0] * 200.0f; + idAI::FindPathAroundObstacles( player->GetPhysics(), aas, NULL, player->GetPhysics()->GetOrigin(), seekPos, path ); + } + } + + // collision map debug output + collisionModelManager->DebugOutput( player->GetEyePosition(), mat3_identity ); + +// RAVEN BEGIN +// jscott: for debugging playbacks + if( g_showPlayback.GetInteger() ) { + gameEdit->DrawPlaybackDebugInfo(); + } +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if ( g_showClipSectors.GetBool() ) { + clip[ 0 ]->DrawClipSectors(); + } + + if ( g_showAreaClipSectors.GetFloat() ) { + clip[ 0 ]->DrawAreaClipSectors( g_showAreaClipSectors.GetFloat() ); + } +// RAVEN END +} + +/* +================== +idGameLocal::NumAAS +================== +*/ +int idGameLocal::NumAAS( void ) const { + return aasList.Num(); +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( int num ) const { + if ( ( num >= 0 ) && ( num < aasList.Num() ) ) { + if ( aasList[ num ] && aasList[ num ]->GetSettings() ) { + return aasList[ num ]; + } + } + return NULL; +} + +/* +================== +idGameLocal::GetAAS +================== +*/ +idAAS *idGameLocal::GetAAS( const char *name ) const { + int i; + + for ( i = 0; i < aasNames.Num(); i++ ) { + if ( aasNames[ i ] == name ) { + if ( !aasList[ i ]->GetSettings() ) { + return NULL; + } else { + return aasList[ i ]; + } + } + } + return NULL; +} + +/* +================== +idGameLocal::SetAASAreaState +================== +*/ +void idGameLocal::SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->SetAreaState( bounds, areaContents, closed ); + } +} + +/* +================== +idGameLocal::AddAASObstacle +================== +*/ +aasHandle_t idGameLocal::AddAASObstacle( const idBounds &bounds ) { + int i; + aasHandle_t obstacle; + aasHandle_t check; + + if ( !aasList.Num() ) { + return -1; + } + + obstacle = aasList[ 0 ]->AddObstacle( bounds ); + for( i = 1; i < aasList.Num(); i++ ) { + check = aasList[ i ]->AddObstacle( bounds ); + assert( check == obstacle ); + } + + return obstacle; +} + +/* +================== +idGameLocal::RemoveAASObstacle +================== +*/ +void idGameLocal::RemoveAASObstacle( const aasHandle_t handle ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveObstacle( handle ); + } +} + +/* +================== +idGameLocal::RemoveAllAASObstacles +================== +*/ +void idGameLocal::RemoveAllAASObstacles( void ) { + int i; + + for( i = 0; i < aasList.Num(); i++ ) { + aasList[ i ]->RemoveAllObstacles(); + } +} + +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. +/* +================== +idGameLocal::GetEntityMemoryUsage + +Compute combined total memory footprint of server and client entity storage. +================== +*/ + +size_t idGameLocal::GetEntityMemoryUsage ( void ) const { + + // Server ents. + size_t serverEntitiesSize = 0; + for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + serverEntitiesSize += sizeof( idEntity ); + } + + // Client ents. + size_t clientEntitiesSize = 0; + for( rvClientEntity *ent = gameLocal.clientSpawnedEntities.Next() ; ent != NULL; ent = ent->spawnNode.Next() ) { + clientEntitiesSize += ent->Size(); + } + + return serverEntitiesSize + clientEntitiesSize; +} +// RAVEN END + +/* +================== +idGameLocal::CheatsOk +================== +*/ +bool idGameLocal::CheatsOk( bool requirePlayer ) { + idPlayer *player; + + if ( isMultiplayer && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { + Printf( "Not allowed in multiplayer.\n" ); + return false; + } + + if ( !developer.GetBool() ) { + return false; + } + + player = GetLocalPlayer(); + if ( !requirePlayer || ( player && ( player->health > 0 ) ) ) { + return true; + } + + Printf( "You must be alive to use this command.\n" ); + + return false; +} + +/* +=================== +idGameLocal::RegisterEntity +=================== +*/ +void idGameLocal::RegisterEntity( idEntity *ent ) { + int spawn_entnum; + + if ( spawnCount >= ( 1 << ( 32 - GENTITYNUM_BITS ) ) ) { + Error( "idGameLocal::RegisterEntity: spawn count overflow" ); + } + + firstFreeIndex = Max( minSpawnIndex, firstFreeIndex ); + + if ( !spawnArgs.GetInt( "spawn_entnum", "0", spawn_entnum ) ) { + while ( entities[firstFreeIndex] && firstFreeIndex < ENTITYNUM_MAX_NORMAL ) { + firstFreeIndex++; + } + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } + spawn_entnum = firstFreeIndex++; + } else { + assert( spawn_entnum < MAX_CLIENTS || spawn_entnum >= minSpawnIndex ); + } + + entities[ spawn_entnum ] = ent; + spawnIds[ spawn_entnum ] = spawnCount++; + ent->entityNumber = spawn_entnum; + ent->spawnNode.AddToEnd( spawnedEntities ); + ent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( spawn_entnum >= num_entities ) { + num_entities++; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=================== +idGameLocal::UnregisterEntity +=================== +*/ +void idGameLocal::UnregisterEntity( idEntity *ent ) { + assert( ent ); + + if ( editEntities ) { + editEntities->RemoveSelectedEntity( ent ); + } + + if ( ( ent->entityNumber != ENTITYNUM_NONE || ent == entities[ ENTITYNUM_NONE ] ) && ( entities[ ent->entityNumber ] == ent ) ) { + ent->spawnNode.Remove(); + entities[ ent->entityNumber ] = NULL; + spawnIds[ ent->entityNumber ] = -1; + if ( ent->entityNumber >= MAX_CLIENTS && ent->entityNumber < firstFreeIndex ) { + firstFreeIndex = ent->entityNumber; + } + ent->entityNumber = ENTITYNUM_NONE; + } + +// RAVEN BEGIN +// bdube: keep track of last time an entity was registered + entityRegisterTime = time; +// RAVEN END +} + +/* +=============== +idGameLocal::SkipEntityIndex +=============== +*/ +void idGameLocal::SkipEntityIndex( void ) { + assert( entities[ firstFreeIndex ] == NULL ); + firstFreeIndex++; + if ( firstFreeIndex >= ENTITYNUM_MAX_NORMAL ) { + Error( "no free entities" ); + } +} + +/* +================ +idGameLocal::SpawnEntityType +================ +*/ +idEntity *idGameLocal::SpawnEntityType( const idTypeInfo &classdef, const idDict *args, bool bIsClientReadSnapshot ) { + idClass *obj; + +#ifdef _DEBUG + if ( isClient ) { + assert( bIsClientReadSnapshot ); + } +#endif + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !classdef.IsType( idEntity::GetClassType() ) ) { +// RAVEN END + Error( "Attempted to spawn non-entity class '%s'", classdef.classname ); + } + + try { + if ( args ) { + spawnArgs = *args; + } else { + spawnArgs.Clear(); + } + obj = classdef.CreateInstance(); + obj->CallSpawn(); + } + + catch( idAllocError & ) { + obj = NULL; + } + spawnArgs.Clear(); + + return static_cast(obj); +} + +/* +=================== +idGameLocal::SpawnClientEntityDef + +Finds the spawn function for the client entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnClientEntityDef( const idDict &args, rvClientEntity **cent, bool setDefaults, const char* spawn ) { + const char *classname; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + if ( cent ) { + *cent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ){ + //not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { + error = va( " on '%s'", name ); + } + + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { + // RAVEN BEGIN + // jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } + // RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + + // check if we should spawn a class object + if( spawn == NULL ) { + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + } + + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + + if ( cent && obj->IsType( rvClientEntity::GetClassType() ) ) { + *cent = static_cast(obj); + } + + return true; + } + + Warning( "%s doesn't include a spawnfunc%s.", classname, error.c_str() ); + return false; +} + +/* +=================== +idGameLocal::SpawnEntityDef + +Finds the spawn function for the entity and calls it, +returning false if not found +=================== +*/ +bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDefaults ) { + const char *classname; + const char *spawn; + idTypeInfo *cls; + idClass *obj; + idStr error; + const char *name; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( ent ) { + *ent = NULL; + } + + spawnArgs = args; + + if ( spawnArgs.GetBool( "nospawn" ) ) + {//not meant to actually spawn, just there for some compiling process + return false; + } + + if ( spawnArgs.GetString( "name", "", &name ) ) { +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + error = va( " on '%s'", name ); +// RAVEN END + } + spawnArgs.GetString( "classname", NULL, &classname ); + + const idDeclEntityDef *def = FindEntityDef( classname, false ); + if ( !def ) { +// RAVEN BEGIN +// jscott: a NULL classname would crash Warning() + if( classname ) { + Warning( "Unknown classname '%s'%s.", classname, error.c_str() ); + } +// RAVEN END + return false; + } + + spawnArgs.SetDefaults( &def->dict ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + if ( g_keepEntityStats.GetBool() ) { + if ( idStr::Icmp( classname, "func_spawner" ) == 0 || + idStr::Icmp( classname, "func_spawner_enemy" ) == 0 ) { + // special case for spawners + for( int i = 1; ; i++ ) { + char tempSpawn[128]; + const char *tempClassname; + + sprintf( tempSpawn, "def_spawn_type%d", i ); + tempClassname = spawnArgs.GetString( tempSpawn, NULL ); + if ( tempClassname ) { + const idDeclEntityDef *tempDef = FindEntityDef( tempClassname, false ); + + if ( tempDef ) { + idDict tempArgs = tempDef->dict; + + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } else { + break; + } + } + } + else if ( def->dict.GetBool( "report_stats" ) ) { + idDict tempArgs = spawnArgs; + tempArgs.Set( "mapFileName", mapFileNameStripped ); + entityUsageList.Insert( tempArgs ); + } + } +// RAVEN END + + // check if we should spawn a class object + spawnArgs.GetString( "spawnclass", NULL, &spawn ); + if ( spawn ) { + + cls = idClass::GetClass( spawn ); + if ( !cls ) { + Warning( "Could not spawn '%s'. Class '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + + obj = cls->CreateInstance(); + if ( !obj ) { + Warning( "Could not spawn '%s'. Instance could not be created%s.", classname, error.c_str() ); + return false; + } + + obj->CallSpawn(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && obj->IsType( idEntity::GetClassType() ) ) { +// RAVEN END + *ent = static_cast(obj); + } + + return true; + } + + // check if we should call a script function to spawn + spawnArgs.GetString( "spawnfunc", NULL, &spawn ); + if ( spawn ) { + const function_t *func = program.FindFunction( spawn ); + if ( !func ) { + Warning( "Could not spawn '%s'. Script function '%s' not found%s.", classname, spawn, error.c_str() ); + return false; + } + idThread *thread = new idThread( func ); + thread->DelayedStart( 0 ); + + return true; + } + + Warning( "%s doesn't include a spawnfunc or spawnclass%s.", classname, error.c_str() ); + return false; +} + +// abahr: +idEntity* idGameLocal::SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idDict finalArgs; + const idDict* entityDict = NULL; + idEntity* entity = NULL; + + TIME_THIS_SCOPE( __FUNCLINE__); + + if( !entityDefName ) { + return NULL; + } + + entityDict = FindEntityDefDict( entityDefName, false ); + if( !entityDict ) { + return NULL; + } + + if( !additionalArgs ) { + SpawnEntityDef( *entityDict, &entity ); + } else { + finalArgs.Copy( *entityDict ); + finalArgs.Copy( *additionalArgs ); + SpawnEntityDef( finalArgs, &entity ); + } + + return entity; +} +// RAVEN END + +/* +================ +idGameLocal::FindEntityDef +================ +*/ +const idDeclEntityDef *idGameLocal::FindEntityDef( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDecl *decl = NULL; + if ( isMultiplayer ) { + decl = declManager->FindType( DECL_ENTITYDEF, va( "%s_mp", name ), false ); + } + if ( !decl ) { + decl = declManager->FindType( DECL_ENTITYDEF, name, makeDefault ); + } + return static_cast( decl ); +} + +/* +================ +idGameLocal::FindEntityDefDict +================ +*/ +const idDict *idGameLocal::FindEntityDefDict( const char *name, bool makeDefault ) const { + TIME_THIS_SCOPE( __FUNCLINE__); + const idDeclEntityDef *decl = FindEntityDef( name, makeDefault ); + return decl ? &decl->dict : NULL; +} + +/* +================ +idGameLocal::InhibitEntitySpawn +================ +*/ +bool idGameLocal::InhibitEntitySpawn( idDict &spawnArgs ) { + + bool result = false; + + if ( isMultiplayer ) { + spawnArgs.GetBool( "not_multiplayer", "0", result ); + } else if ( g_skill.GetInteger() == 0 ) { + spawnArgs.GetBool( "not_easy", "0", result ); + } else if ( g_skill.GetInteger() == 1 ) { + spawnArgs.GetBool( "not_medium", "0", result ); + } else { + spawnArgs.GetBool( "not_hard", "0", result ); + } + + const char *name; +#ifndef ID_DEMO_BUILD + if ( g_skill.GetInteger() == 3 ) { + name = spawnArgs.GetString( "classname" ); + if ( idStr::Icmp( name, "item_medkit" ) == 0 || idStr::Icmp( name, "item_medkit_small" ) == 0 ) { + result = true; + } + } +#endif + +// RAVEN BEGIN +// bdube: suppress ents that don't match the entity filter + const char* entityFilter; + if ( serverInfo.GetString( "si_entityFilter", "", &entityFilter ) && *entityFilter ) { + if ( spawnArgs.MatchPrefix ( "filter_" ) && !spawnArgs.GetBool ( va("filter_%s", entityFilter) ) ) { + return true; + } + } +// RAVEN END + +// RITUAL BEGIN +// squirrel: suppress ents that aren't supported in Buying modes (if that's the mode we're in) + if ( mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( spawnArgs.GetBool( "disableSpawnInBuying", "0" ) ) { + return true; + } + + /// Don't spawn weapons or armor vests or ammo in Buying modes + idStr classname = spawnArgs.GetString( "classname" ); + if( idStr::FindText( classname, "weapon_" ) == 0 || + idStr::FindText( classname, "item_armor_small" ) == 0 || + idStr::FindText( classname, "ammo_" ) == 0 || + idStr::FindText( classname, "item_armor_large" ) == 0 ) + { + return true; + } + } +// RITUAL END + + // suppress deadzone triggers if we're not running DZ + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "DeadZone" ) != 0 ) { + if ( idStr::Icmp( spawnArgs.GetString( "classname" ), "trigger_controlzone" ) == 0 ) { + return true; + } + } + + return result; +} + +/* +================ +idGameLocal::SetSkill +================ +*/ +void idGameLocal::SetSkill( int value ) { + int skill_level; + + if ( value < 0 ) { + skill_level = 0; + } else if ( value > 3 ) { + skill_level = 3; + } else { + skill_level = value; + } + + g_skill.SetInteger( skill_level ); +} + +/* +============== +idGameLocal::GameState + +Used to allow entities to know if they're being spawned during the initial spawn. +============== +*/ +gameState_t idGameLocal::GameState( void ) const { + return gamestate; +} + +/* +============== +idGameLocal::SpawnMapEntities + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple game instances +void idGameLocal::SpawnMapEntities( int instance, unsigned short* entityNumIn, unsigned short* entityNumOut, int* startSpawnCount ) { +// RAVEN END + int i; + int num; + int inhibit; + int latchedSpawnCount = -1; + idMapEntity *mapEnt; + int numEntities; + idDict args; + idDict items; + + Printf( "Spawning entities\n" ); + + TIME_THIS_SCOPE( __FUNCLINE__); + + if ( mapFile == NULL ) { + Printf("No mapfile present\n"); + return; + } + + SetSkill( g_skill.GetInteger() ); + + numEntities = mapFile->GetNumEntities(); + if ( numEntities == 0 ) { + Error( "...no entities" ); + } + +// RAVEN BEGIN +// ddynerman: on the server, perform world-initialization when spawning instance 0 only and while starting up +// on the client, perform world-init when spawning while starting up, as client main instance may not be ID 0 +// always spawn world-init in single-player + num = inhibit = 0; + + // the worldspawn is a special that performs any global setup + // needed by a level + if( ( gameLocal.isServer && instance == 0 && gamestate == GAMESTATE_STARTUP ) || (gameLocal.isClient && gamestate == GAMESTATE_STARTUP) || !gameLocal.isMultiplayer ) { + mapEnt = mapFile->GetEntity( 0 ); + args = mapEnt->epairs; + args.SetInt( "spawn_entnum", ENTITYNUM_WORLD ); + + // on the client, spawnCount won't always start at INITIAL_SPAWN_COUNT (see rvInstance::Populate()) + // make sure the world and physics ent get the right spawn id + if( gameLocal.isClient && spawnCount != INITIAL_SPAWN_COUNT ) { + latchedSpawnCount = spawnCount; + spawnCount = INITIAL_SPAWN_COUNT; + } + +// abahr: precache stuff on worldSpawn like player def and other misc things + CacheDictionaryMedia( &args ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityDef( args ) || !entities[ ENTITYNUM_WORLD ] || !entities[ ENTITYNUM_WORLD ]->IsType( idWorldspawn::GetClassType() ) ) { + Error( "Problem spawning world entity" ); + } + num++; + +// bdube: dummy entity for client entities with physics + args.Clear ( ); + args.SetInt( "spawn_entnum", ENTITYNUM_CLIENT ); +// jnewquist: Use accessor for static class type + if ( !SpawnEntityType( rvClientPhysics::GetClassType(), &args, true ) || !entities[ ENTITYNUM_CLIENT ] ) { + Error( "Problem spawning client physics entity" ); + } + + if( gameLocal.isClient && latchedSpawnCount != -1 ) { + spawnCount = latchedSpawnCount; + } + + isMapEntity[ ENTITYNUM_CLIENT ] = true; + isMapEntity[ ENTITYNUM_WORLD ] = true; +// RAVEN END + } + + // capture spawn count of start of map entities (after we've spawned in the physics ents) + if ( startSpawnCount ) { + (*startSpawnCount) = spawnCount; + } + + for ( i = 1 ; i < numEntities ; i++ ) { + + mapEnt = mapFile->GetEntity( i ); + args = mapEnt->epairs; + +// RAVEN BEGIN +// ddynerman: merge the dicts ahead of SpawnEntityDef() so we can inhibit using merged info + const idDeclEntityDef* entityDef = FindEntityDef( args.GetString( "classname" ), false ); + + if( entityDef == NULL ) { + gameLocal.Error( "idGameLocal::SpawnMapEntities() - Unknown entity classname '%s'\n", args.GetString( "classname" ) ); + return; + } + args.SetDefaults( &(entityDef->dict) ); +// RAVEN END + + if ( !InhibitEntitySpawn( args ) ) { + + if( args.GetBool( "inv_item" ) ) { + if( !items.GetBool( args.GetString( "inv_icon" ) ) ) { + networkSystem->AddLoadingIcon( args.GetString( "inv_icon" ) ); + networkSystem->SetLoadingText( common->GetLocalizedString( args.GetString( "inv_name" ) ) ); + items.SetBool( args.GetString( "inv_icon" ), true ); + } + } + + // precache any media specified in the map entity + CacheDictionaryMedia( &args ); +// RAVEN BEGIN + if ( instance != 0 ) { +// ddynerman: allow this function to be called multiple-times to respawn map entities in other instances + args.SetInt( "instance", instance ); + args.Set( "name", va( "%s_instance%d", args.GetString( "name" ), instance ) ); + //gameLocal.Printf( "Instance %d: Spawning %s (class: %s)\n", instance, args.GetString( "name" ), args.GetString( "classname" ) ); + + // need a better way to do this - map entities that target other map entities need + // to target the ones in the correct instance. + idStr target; + if( args.GetString( "target", "", target ) ) { + args.Set( "target", va( "%s_instance%d", target.c_str(), instance ) ); + } + + // and also udpate binds on a per-instance bases + idStr bind; + if( args.GetString( "bind", "", bind ) ) { + args.Set( "bind", va( "%s_instance%d", bind.c_str(), instance ) ); + } + + // and also door triggers + idStr triggerOnOpen; + if( args.GetString( "triggerOnOpen", "", triggerOnOpen ) ) { + args.Set( "triggerOnOpen", va( "%s_instance%d", triggerOnOpen.c_str(), instance ) ); + } + + idStr triggerOpened; + if( args.GetString( "triggerOpened", "", triggerOpened ) ) { + args.Set( "triggerOpened", va( "%s_instance%d", triggerOpened.c_str(), instance ) ); + } + } + + idEntity* ent = NULL; + SpawnEntityDef( args, &ent ); + //common->Printf( "pop: spawn map ent %d at %d ( %s )\n", i, ent->entityNumber, args.GetString( "name" ) ); + + if ( ent && entityNumOut ) { + entityNumOut[ i ] = ent->entityNumber; + } + + if ( gameLocal.GetLocalPlayer() && ent && gameLocal.isServer && instance != gameLocal.GetLocalPlayer()->GetInstance() ) { + // if we're spawning entities not in our instance, tell them not to draw + ent->BecomeInactive( TH_UPDATEVISUALS ); + } + + if ( ent ) { + isMapEntity[ ent->entityNumber ] = true; + } +// RAVEN END + num++; + } else { + // keep counting and leave an empty slot in the entity list for inhibited entities + // this so we maintain the same layout as the server and don't change it across restarts with different inhibit schemes + //common->Printf( "pop: skip map ent %d at index %d ( %s )\n", i, firstFreeIndex, args.GetString( "name" ) ); + SkipEntityIndex(); + inhibit++; + } + } + + Printf( "...%i entities spawned, %i inhibited\n\n", num, inhibit ); +} + +/* +================ +idGameLocal::AddEntityToHash +================ +*/ +void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) { + if ( FindEntity( name ) ) { + Error( "Multiple entities named '%s'", name ); + } + entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber ); +} + +/* +================ +idGameLocal::RemoveEntityFromHash +================ +*/ +bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) { + entityHash.Remove( hash, i ); + return true; + } + } + return false; +} + +/* +================ +idGameLocal::GetTargets +================ +*/ +int idGameLocal::GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const { + int i, num, refLength; + const idKeyValue *arg; + idEntity *ent; + + list.Clear(); + + refLength = strlen( ref ); + num = args.GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + + arg = args.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( ref, refLength ) == 0 ) { + + ent = FindEntity( arg->GetValue() ); + if ( ent ) { + idEntityPtr &entityPtr = list.Alloc(); + entityPtr = ent; + } + } + } + + return list.Num(); +} + +/* +============= +idGameLocal::GetTraceEntity + +returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. +============= +*/ +idEntity *idGameLocal::GetTraceEntity( const trace_t &trace ) const { + idEntity *master; + + if ( !entities[ trace.c.entityNum ] ) { + return NULL; + } + master = entities[ trace.c.entityNum ]->GetBindMaster(); + if ( master ) { + return master; + } + return entities[ trace.c.entityNum ]; +} + +/* +============= +idGameLocal::ArgCompletion_EntityName + +Argument completion for entity names +============= +*/ +void idGameLocal::ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::ArgCompletion_AIName + +Argument completion for idAI entity names +============= +*/ +void idGameLocal::ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i; + + for( i = 0; i < gameLocal.num_entities; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idAI::GetClassType() ) ) { + callback( va( "%s %s", args.Argv( 0 ), gameLocal.entities[ i ]->name.c_str() ) ); + } + } +} + +/* +============= +idGameLocal::FindEntity + +Returns the entity whose name matches the specified string. +============= +*/ +idEntity *idGameLocal::FindEntity( const char *name ) const { + int hash, i; + + hash = entityHash.GenerateKey( name, true ); + for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { + if ( entities[i] && entities[i]->name.Icmp( name ) == 0 ) { + return entities[i]; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindEntityUsingDef + +Searches all active entities for the next one using the specified entityDef. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. +============= +*/ +idEntity *idGameLocal::FindEntityUsingDef( idEntity *from, const char *match ) const { + idEntity *ent; + + if ( !from ) { + ent = spawnedEntities.Next(); + } else { + ent = from->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + assert( ent ); + if ( idStr::Icmp( ent->GetEntityDefName(), match ) == 0 ) { + return ent; + } + } + + return NULL; +} + +/* +============= +idGameLocal::FindTraceEntity + +Searches all active entities for the closest ( to start ) match that intersects +the line start,end +============= +*/ +idEntity *idGameLocal::FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const { + idEntity *ent; + idEntity *bestEnt; + float scale; + float bestScale; + idBounds b; + + bestEnt = NULL; + bestScale = 1.0f; + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( c ) && ent != skip ) { + b = ent->GetPhysics()->GetAbsBounds().Expand( 16 ); + if ( b.RayIntersection( start, end-start, scale ) ) { + if ( scale >= 0.0f && scale < bestScale ) { + bestEnt = ent; + bestScale = scale; + } + } + } + } + + return bestEnt; +} + +/* +================ +idGameLocal::EntitiesWithinRadius +================ +*/ +int idGameLocal::EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const { + idEntity *ent; + idBounds bo( org ); + int entCount = 0; + + bo.ExpandSelf( radius ); + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetAbsBounds().IntersectsBounds( bo ) ) { + entityList[entCount++] = ent; + } + } + + return entCount; +} + +/* +================= +idGameLocal::KillBox + +Kills all entities that would touch the proposed new positioning of ent. The ent itself will not being killed. +Checks if player entities are in the teleporter, and marks them to die at teleport exit instead of immediately. +If catch_teleport, this only marks teleport players for death on exit +================= +*/ +void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + idPhysics *phys; + + phys = ent->GetPhysics(); + if ( !phys->GetNumClipModels() ) { + return; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = ClipModelsTouchingBounds( ent, phys->GetAbsBounds(), phys->GetClipMask(), clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == ent ) || !hit->fl.takedamage ) { + continue; + } + + if ( !phys->ClipContents( cm ) ) { + continue; + } + + // nail it +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( hit->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( hit )->IsInTeleport() ) { +// RAVEN END + static_cast< idPlayer * >( hit )->TeleportDeath( ent->entityNumber ); + } else if ( !catch_teleport ) { + hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + } + + if ( !gameLocal.isMultiplayer ) { + // let the mapper know about it + Warning( "'%s' telefragged '%s'", ent->name.c_str(), hit->name.c_str() ); + } + } +} + +/* +================ +idGameLocal::RequirementMet +================ +*/ +bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) { + if ( requires.Length() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + idPlayer *player = static_cast(activator); + idDict *item = player->FindInventoryItem( requires ); + if ( item ) { + if ( removeItem ) { + player->RemoveInventoryItem( item ); + } + return true; + } else { + return false; + } + } + } + + return true; +} + + +/* +================ +idGameLocal::IsWeaponsStayOn +================ +*/ +bool idGameLocal::IsWeaponsStayOn( void ) { + /// Override weapons stay when buying is active + if( isMultiplayer && mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + return false; + } + + return serverInfo.GetBool( "si_weaponStay" ); +} + + +/* +============ +idGameLocal::AlertAI +============ +*/ +void idGameLocal::AlertAI( idEntity *ent ) { +// RAVEN BEGIN +// bdube: merged + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // alert them for the next frame + lastAIAlertActorTime = time + GetMSec(); + lastAIAlertActor = static_cast( ent ); + } else { + lastAIAlertEntityTime = time + GetMSec(); + lastAIAlertEntity = ent; + } + } else { + lastAIAlertEntityTime = 0; + lastAIAlertActorTime = 0; + lastAIAlertEntity = NULL; + lastAIAlertActor = NULL; + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: alert entity returns an entity, alert actor a an actor +/* +============ +idGameLocal::GetAlertEntity +============ +*/ +idEntity *idGameLocal::GetAlertEntity( void ) { + if ( lastAIAlertEntityTime >= time ) { + return lastAIAlertEntity.GetEntity(); + } + + return NULL; +} + +/* +============ +idGameLocal::GetAlertActor +============ +*/ +idActor *idGameLocal::GetAlertActor( void ) { + if ( lastAIAlertActorTime >= time ) { + return lastAIAlertActor.GetEntity(); + } + + return NULL; +} +// RAVEN END + +/* +============ +SortClipModelsByEntity +============ +*/ +static int SortClipModelsByEntity( const void* left, const void* right ) { + idEntity* leftEntity = left ? (*((const idClipModel**)left))->GetEntity() : NULL; + idEntity* rightEntity = right ? (*((const idClipModel**)right))->GetEntity() : NULL; + + int entityNumLeft = (leftEntity) ? leftEntity->entityNumber : 0; + int entityNumRight = (rightEntity) ? rightEntity->entityNumber : 0; + + return entityNumLeft - entityNumRight; +} + +// RAVEN BEGIN +/* +============ +idGameLocal::RadiusDamage +Returns the number of actors damaged +============ +*/ +// abahr: changed to work with deathPush +void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower, int* hitCount ) { + float dist, damageScale, attackerDamageScale, attackerPushScale; + idEntity* ent = NULL; + idEntity* lastEnt = NULL; + idClipModel* clipModel = NULL; + idClipModel* clipModelList[ MAX_GENTITIES ]; + int numListedClipModels; + modelTrace_t result; + idVec3 v, damagePoint, dir; + int i, damage, radius, push; + + const idDict *damageDef = FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damageDef->GetInt( "radius", "50", radius ); + damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); + damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); + if( gameLocal.isMultiplayer ) { + damageDef->GetFloat( "attackerPushScale", "2", attackerPushScale ); + } else { + damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + } + + if ( radius < 1 ) { + radius = 1; + } + + +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, idBounds(origin).Expand(radius), MASK_ALL, clipModelList, MAX_GENTITIES ); + if( numListedClipModels > 0 ) { + //Sort list by unique entities for easier searching + qsort( clipModelList, numListedClipModels, sizeof(clipModelList[0]), SortClipModelsByEntity ); + } + + if ( inflictor ) { + inflictor = inflictor->GetDamageEntity ( ); + } + if ( attacker ) { + attacker = attacker->GetDamageEntity ( ); + } + if ( ignoreDamage ) { + ignoreDamage = ignoreDamage->GetDamageEntity ( ); + } + + for( int c = 0; c < numListedClipModels; ++c ) { + clipModel = clipModelList[ c ]; + assert( clipModel ); + + ent = clipModel->GetEntity(); + + // Skip all entitys that arent primary damage entities + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } + + // Dont damage inflictor or the ignore entity + if( ent == inflictor || ent == ignoreDamage ) { + continue; + } + + idBounds absBounds = clipModel->GetAbsBounds(); + + // find the distance from the edge of the bounding box + for ( i = 0; i < 3; i++ ) { + if ( origin[ i ] < absBounds[0][ i ] ) { + v[ i ] = absBounds[0][ i ] - origin[ i ]; + } else if ( origin[ i ] > absBounds[1][ i ] ) { + v[ i ] = origin[ i ] - absBounds[1][ i ]; + } else { + v[ i ] = 0; + } + } + + dist = v.Length(); + if ( dist >= radius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, absBounds.GetCenter()) ) { + continue; + } + + RadiusPushClipModel ( inflictor, origin, push, clipModel ); + + // Only damage unique entities. This works because we have a sorted list + if( lastEnt == ent ) { + continue; + } + + lastEnt = ent; + + if ( ent->CanDamage( origin, damagePoint, ignoreDamage ) ) { + // push the center of mass higher than the origin so players + // get knocked into the air more + if( gameLocal.isMultiplayer ) { + // fudge the direction in MP to account for player height difference and origin shift + // 31.875 = origin is 23.875 units lower in Q4 than Q3 + player is 8 units taller in Q4 + dir = ( ent->GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 31.875f ) ) - origin; + } else { + dir = ent->GetPhysics()->GetOrigin() - origin; + } + + dir[2] += 24; + + // get the damage scale + damageScale = dmgPower * ( 1.0f - dist / radius ); + + if ( ent == attacker ) { + damageScale *= attackerDamageScale; + } + + dir.Normalize(); + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(ent->GetPhysics()->GetClipModel()->GetId()) ); + + // for stats, count the first + if( attacker && attacker->IsType( idPlayer::GetClassType() ) && inflictor && inflictor->IsType( idProjectile::GetClassType() ) && ent->IsType( idPlayer::GetClassType() ) && hitCount ) { + // with splash damage projectiles, one projectile fire can damage multiple people. If anyone is hit, + // the shot counts as a hit but only report accuracy on the first one to avoid accuracies > 100% + statManager->WeaponHit( (const idActor*)attacker, ent, ((idProjectile*)inflictor)->methodOfDeath, (*hitCount) == 0 ); + (*hitCount)++; + } + } + } +} +// RAVEN END + +/* +============== +idGameLocal::RadiusPush +============== +*/ +void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) { + int i, numListedClipModels; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 dir; + idBounds bounds; + modelTrace_t result; + idEntity *ent; + float scale; + +// RAVEN BEGIN +// abahr: need to use gravity instead of assuming z is up + dir = -GetCurrentGravity( const_cast(inflictor) ).ToNormal(); +// RAVEN END + + bounds = idBounds( origin ).Expand( radius ); + + // get all clip models touching the bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = ClipModelsTouchingBounds( inflictor, bounds, -1, clipModelList, MAX_GENTITIES ); + +// bdube: getdamageentity + if ( inflictor ) { + inflictor = ((idEntity*)inflictor)->GetDamageEntity ( ); + } + if ( ignore ) { + ignore = ((idEntity*)ignore)->GetDamageEntity ( ); + } +// RAVEN END + + // apply impact to all the clip models through their associated physics objects + for ( i = 0; i < numListedClipModels; i++ ) { + + clipModel = clipModelList[i]; + + // never push render models + if ( clipModel->IsRenderModel() ) { + continue; + } + + ent = clipModel->GetEntity(); +// RAVEN BEGIN +// bdube: damage entity + if ( !ent || ent != ent->GetDamageEntity ( ) ) { + continue; + } +// RAVEN END + + // never push projectiles +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + continue; + } + + // players use "knockback" in idPlayer::Damage +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idPlayer::GetClassType() ) && !quake ) { +// RAVEN END + continue; + } + + // don't push the ignore entity + if ( ent == ignore ) { + continue; + } + + if ( gameRenderWorld->FastWorldTrace( result, origin, clipModel->GetOrigin() ) ) { + continue; + } + + // scale the push for the inflictor + if ( ent == inflictor ) { + scale = inflictorScale; + } else { + scale = 1.0f; + } + + if ( quake ) { + clipModel->GetEntity()->ApplyImpulse( world, clipModel->GetId(), clipModel->GetOrigin(), scale * push * dir ); + } else { +// RAVEN BEGIN +// bdube: inflictor + RadiusPushClipModel( (idEntity*)inflictor, origin, scale * push, clipModel ); +// RAVEN END + } + } +} + +/* +============== +idGameLocal::RadiusPushClipModel +============== +*/ +// RAVEN BEGIN +// bdube: inflictor +void idGameLocal::RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ) { +// RAVEN END + int i, j; + float dot, dist, area; + const idTraceModel *trm; + const traceModelPoly_t *poly; + idFixedWinding w; + idVec3 v, localOrigin, center, impulse; + + trm = clipModel->GetTraceModel(); + if ( !trm || 1 ) { + impulse = clipModel->GetAbsBounds().GetCenter() - origin; + impulse.Normalize(); +// RAVEN BEGIN +// abahr: removed because z isn't always up + //impulse.z += 1.0f; + //impulse = idVec3( 0.0, 0.0, 1.0 ); + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), clipModel->GetOrigin(), push * impulse, true ); +// RAVEN END + return; + } + + localOrigin = ( origin - clipModel->GetOrigin() ) * clipModel->GetAxis().Transpose(); + for ( i = 0; i < trm->numPolys; i++ ) { + poly = &trm->polys[i]; + + center.Zero(); + for ( j = 0; j < poly->numEdges; j++ ) { + v = trm->verts[ trm->edges[ abs(poly->edges[j]) ].v[ INTSIGNBITSET( poly->edges[j] ) ] ]; + center += v; + v -= localOrigin; + v.NormalizeFast(); // project point on a unit sphere + w.AddPoint( v ); + } + center /= poly->numEdges; + v = center - localOrigin; + dist = v.NormalizeFast(); + dot = v * poly->normal; + if ( dot > 0.0f ) { + continue; + } + area = w.GetArea(); + // impulse in polygon normal direction + impulse = poly->normal * clipModel->GetAxis(); + // always push up for nicer effect + impulse.z -= 1.0f; + // scale impulse based on visible surface area and polygon angle + impulse *= push * ( dot * area * ( 1.0f / ( 4.0f * idMath::PI ) ) ); + // scale away distance for nicer effect + impulse *= ( dist * 2.0f ); + // impulse is applied to the center of the polygon + center = clipModel->GetOrigin() + center * clipModel->GetAxis(); + +// RAVEN BEGIN +// bdube: inflictor + clipModel->GetEntity()->ApplyImpulse( inflictor, clipModel->GetId(), center, impulse ); +// RAVEN END + } +} + +/* +=============== +idGameLocal::ProjectDecal +=============== +*/ +void idGameLocal::ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle ) { + float s, c; + idMat3 axis, axistemp; + idFixedWinding winding; + idVec3 windingOrigin, projectionOrigin; + + static idVec3 decalWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !g_decals.GetBool() ) { + return; + } + +// RAVEN BEGIN +// rjohnson: no decals on dedicated server + if ( isMultiplayer && !isClient && !isListenServer ) { + // no decals on dedicated server + return; + } +// RAVEN END + + // randomly rotate the decal winding + idMath::SinCos16( ( angle ) ? angle : random.RandomFloat() * idMath::TWO_PI, s, c ); + + // winding orientation + axis[2] = dir; + axis[2].Normalize(); + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + windingOrigin = origin + depth * axis[2]; + if ( parallel ) { + projectionOrigin = origin - depth * axis[2]; + } else { + projectionOrigin = origin; + } + + size *= 0.5f; + + winding.Clear(); + winding += idVec5( windingOrigin + ( axis * decalWinding[0] ) * size, idVec2( 1, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[1] ) * size, idVec2( 0, 1 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[2] ) * size, idVec2( 0, 0 ) ); + winding += idVec5( windingOrigin + ( axis * decalWinding[3] ) * size, idVec2( 1, 0 ) ); + gameRenderWorld->ProjectDecalOntoWorld( winding, projectionOrigin, parallel, depth * 0.5f, declManager->FindMaterial( material ), time ); +} + +/* +============== +idGameLocal::BloodSplat +============== +*/ +// RAVEN BEGIN +// ddynerman: multiple collision models +void idGameLocal::BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dirArg, float size, const char *material ) { + + float halfSize = size * 0.5f; + idVec3 verts[] = { idVec3( 0.0f, +halfSize, +halfSize ), + idVec3( 0.0f, +halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, -halfSize ), + idVec3( 0.0f, -halfSize, +halfSize ) }; + idVec3 dir = dirArg; + idTraceModel trm; + idClipModel mdl; + trace_t results; + +// RAVEN BEGIN +// mekberg: changed from g_bloodEffects to g_decals + if ( !g_decals.GetBool() ) { + return; + } +// RAVEN END + + size = halfSize + random.RandomFloat() * halfSize; + trm.SetupPolygon( verts, 4 ); + mdl.LoadModel( trm, NULL ); + + // I don't want dir to be axis aligned, as it is more likely to streak them (because most architecture is axis aligned + dir.Set( dirArg.x*.1f, dirArg.y*.1f, -1 ); + dir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Translation( ent, results, origin, origin + dir * 72.0f, &mdl, mat3_identity, CONTENTS_SOLID, NULL ); +// RAVEN END + ProjectDecal( results.endpos, dir, 2.0f * size, true, size, material ); +} + +/* +============= +idGameLocal::SetCamera +============= +*/ +void idGameLocal::SetCamera( idCamera *cam ) { + int i; + idEntity *ent; + idAI *ai; + + // this should fix going into a cinematic when dead.. rare but happens + idPlayer *client = GetLocalPlayer(); + if ( client->health <= 0 || client->pfl.dead ) { + return; + } + + camera = cam; + if ( camera ) { +// RAVEN BEGIN +// bdube: tool support + inCinematic = false; + if( !( gameLocal.editors & ( EDITOR_MODVIEW | EDITOR_PLAYBACKS ) ) ) { + inCinematic = true; + } +// RAVEN END + + if ( skipCinematic && camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return; + } + + if ( time > cinematicStopTime ) { + cinematicSkipTime = time + CINEMATIC_SKIP_DELAY; + } + + // set r_znear so that transitioning into/out of the player's head doesn't clip through the view + cvarSystem->SetCVarFloat( "r_znear", 1.0f ); + + // hide all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + client = static_cast< idPlayer* >( entities[ i ] ); + client->EnterCinematic(); + } + } + + if ( !cam->spawnArgs.GetBool( "ignore_enemies" ) ) { + // kill any active monsters that are enemies of the player + for ( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->cinematic || ent->fl.isDormant ) { + // only kill entities that aren't needed for cinematics and aren't dormant + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idAI::GetClassType() ) ) { +// RAVEN END + ai = static_cast( ent ); + if ( !ai->GetEnemy() || !ai->IsActive() ) { + // no enemy, or inactive, so probably safe to ignore + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + } else if ( ent->spawnArgs.GetBool( "cinematic_remove" ) ) { + // remove anything marked to be removed during cinematics + } else { + // ignore everything else + continue; + } + + // remove it + DPrintf( "removing '%s' for cinematic\n", ent->GetName() ); + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + } else { + inCinematic = false; + cinematicStopTime = time + msec; + + // restore r_znear + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + + // show all the player models + for( i = 0; i < numClients; i++ ) { + if ( entities[ i ] ) { + idPlayer *client = static_cast< idPlayer* >( entities[ i ] ); + client->ExitCinematic(); + } + } + } +} + +// RAVEN BEGIN +// jscott: for portal skies +/* +============= +idGameLocal::GetPortalSky +============= +*/ +idCamera *idGameLocal::GetPortalSky( void ) const +{ + if( !portalSkyVisible ) { + + return( NULL ); + } + return( portalSky ); +} +/* +============= +idGameLocal::SetPortalSky +============= +*/ +void idGameLocal::SetPortalSky( idCamera *cam ) +{ + portalSky = cam; +} +// RAVEN END + +/* +============= +idGameLocal::GetCamera +============= +*/ +idCamera *idGameLocal::GetCamera( void ) const { + return camera; +} + +/* +============= +idGameLocal::SkipCinematic +============= +*/ +bool idGameLocal::SkipCinematic( void ) { + if ( camera ) { + if ( camera->spawnArgs.GetBool( "disconnect" ) ) { + camera->spawnArgs.SetBool( "disconnect", false ); + cvarSystem->SetCVarFloat( "r_znear", 3.0f ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + skipCinematic = false; + return false; + } + + if ( camera->spawnArgs.GetBool( "instantSkip" ) ) { + camera->Stop(); + return false; + } + } + + soundSystem->SetMute( true ); + if ( !skipCinematic ) { + skipCinematic = true; + cinematicMaxSkipTime = gameLocal.time + SEC2MS( g_cinematicMaxSkipTime.GetFloat() ); + } + + return true; +} + +// RAVEN BEGIN +/* +====================== +idGameLocal::StartViewEffect + +For passing in an effect triggered view effect +====================== +*/ +void idGameLocal::StartViewEffect( int type, float time, float scale ) +{ + idPlayer *player; + idPlayerView *view; + + player = GetLocalPlayer(); + if( player ) + { + view = &player->playerView; + + switch( type ) + { + case VIEWEFFECT_DOUBLEVISION: + view->SetDoubleVisionParms( time, scale ); + break; + + case VIEWEFFECT_SHAKE: + if( !gameLocal.isMultiplayer ) { + view->SetShakeParms( time, scale ); + } + break; + + case VIEWEFFECT_TUNNEL: + view->SetTunnelParms( time, scale ); + break; + + default: + gameLocal.Warning( "Invalid view effect" ); + break; + } + } +} + +/* +====================== +idGameLocal::GetPlayerView +====================== +*/ +void idGameLocal::GetPlayerView( idVec3 &origin, idMat3 &axis ) +{ + idPlayer *player; + renderView_t *view; + + player = GetLocalPlayer(); + if( player ) + { + view = player->GetRenderView(); + if ( !view ) { + player->CalculateRenderView(); + view = player->GetRenderView(); + assert( view ); + } + origin = view->vieworg; + axis = view->viewaxis; + } + else + { + origin = vec3_origin; + axis = mat3_identity; + } +} + +/* +====================== +idGameLocal::Translation + +small portion of physics required for the effects system +====================== +*/ +void idGameLocal::Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ) { + if( !trm ) { + clip[0]->Translation( trace, source, dest, NULL, mat3_identity, clipMask, NULL, NULL ); + } else { + idClipModel cm; + + cm.LoadModel( *trm, NULL ); + clip[0]->Translation( trace, source, dest, &cm, mat3_identity, clipMask, NULL, NULL ); + } +} + +/* +====================== +idGameLocal::SpawnClientMoveable +====================== +*/ +void idGameLocal::SpawnClientMoveable( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ) { + // find the debris def + const idDict* args = gameLocal.FindEntityDefDict( name, false ); + if ( !args ) { + return; + } + + // Ensure client moveables never last forever + if ( lifetime <= 0 ) { + lifetime = SEC2MS(args->GetFloat( "duration", "5" )); + } + int burn_time = idMath::ClampInt( 0, lifetime, SEC2MS(args->GetFloat( "burn_time", "2.5" )) ); + + // Spawn the debris + + rvClientMoveable* cent = NULL; + // force the args to spawn a rvClientMoveable + SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + return; + } + + cent->SetOrigin( origin ); + cent->SetAxis( axis ); + + cent->GetPhysics()->SetLinearVelocity( velocity ); + cent->GetPhysics()->SetAngularVelocity( angular_velocity ); + + if ( !burn_time ) { + //just disappear + cent->PostEventMS( &EV_Remove, lifetime ); + } else { + cent->PostEventMS( &CL_FadeOut, lifetime-burn_time, burn_time ); + } +} + +/* +====================== +idGameLocal::DebugSet +====================== +*/ +void idGameLocal::DebugSetString( const char* name, const char* value ) { + gameDebug.SetString( name, value ); +} +void idGameLocal::DebugSetFloat( const char* name, float value ) { + gameDebug.SetFloat( name, value ); +} +void idGameLocal::DebugSetInt( const char* name, int value ) { + gameDebug.SetInt( name, value ); +} + +/* +====================== +idGameLocal::DebugGetStat +====================== +*/ +const char* idGameLocal::DebugGetStatString ( const char* name ) { + return gameDebug.GetStatString( name ); +} + +int idGameLocal::DebugGetStatInt ( const char* name ) { + return gameDebug.GetStatInt( name ); +} + +float idGameLocal::DebugGetStatFloat ( const char* name ) { + return gameDebug.GetStatFloat( name ); +} + +/* +====================== +idGameLocal::IsDebugHudActive +====================== +*/ +bool idGameLocal::IsDebugHudActive ( void ) const { + return gameDebug.IsHudActive( DBGHUD_ANY ); +} + + +// rjohnson: added player info support for note taking system +/* +====================== +idGameLocal::GetPlayerInfo +====================== +*/ +bool idGameLocal::GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum, idAngles *deltaViewAngles, int reqClientNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if( reqClientNum != -1 ) { + idPlayer* reqClient = GetClientByNum( reqClientNum ); + if( reqClient && player ) { + if( reqClient->GetInstance() != player->GetInstance() ) { + return false; + } + } + } + + if ( !player ) { + return false; + } + + player->GetViewPos( origin, axis ); + origin = player->GetPhysics()->GetOrigin(); + + if ( deltaViewAngles ) { + *deltaViewAngles = player->GetDeltaViewAngles(); + } + + return true; +}; + +/* +====================== +idGameLocal::SetCurrentPlayerInfo +====================== +*/ +void idGameLocal::SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum ) { + idPlayer *player; + + if ( PlayerNum == -1 ) { + player = GetLocalPlayer(); + } else { + player = GetClientByNum( PlayerNum ); + } + + if ( !player ) { + return; + } + + player->Teleport( origin, axis.ToAngles(), NULL ); +// RAVEN BEGIN +// ddynerman: save the current thinking entity for instance-dependent + currentThinkingEntity = player; + player->CalculateFirstPersonView(); + player->CalculateRenderView(); + currentThinkingEntity = NULL; +// RAVEN END + + return; +}; + +bool idGameLocal::PlayerChatDisabled( int clientNum ) { + if( clientNum < 0 || clientNum >= MAX_CLIENTS || !entities[ clientNum ] ) { + return false; + } + + return !( ((idPlayer*)entities[ clientNum ])->isChatting || ((idPlayer*)entities[ clientNum ])->pfl.dead ); +} + +void idGameLocal::SetViewComments( const char *text ) { + idPlayer *player; + + player = GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( text ) { + player->hud->SetStateString( "viewcomments", text ); + player->hud->HandleNamedEvent( "showViewComments" ); + } + else { + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); + } +} + +/* +=================== +idGameLocal::GetNumGravityAreas +=================== +*/ +int idGameLocal::GetNumGravityAreas() const { + return gravityInfo.Num(); +} + +/* +=================== +idGameLocal::GetGravityInfo +=================== +*/ +const rvGravityArea* idGameLocal::GetGravityInfo( int index ) const { + return gravityInfo[ index ]; +} + +/* +=================== +idGameLocal::SetGravityArea +=================== +*/ +void idGameLocal::SetGravityInfo( int index, rvGravityArea* info ) { + gravityInfo[ index ] = info; +} + +/* +=================== +idGameLocal::AddUniqueGravityInfo +=================== +*/ +void idGameLocal::AddUniqueGravityInfo( rvGravityArea* info ) { + gravityInfo.AddUnique( info ); +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( const idVec3& origin ) const { + int numGravityAreas = GetNumGravityAreas(); + if( !numGravityAreas ) { + return -1; + } + + int areaNum = gameRenderWorld->PointInArea( origin ); + + for( int ix = 0; ix < numGravityAreas; ++ix ) { + if( !gameRenderWorld->AreasAreConnected(GetGravityInfo(ix)->GetArea(), areaNum, PS_BLOCK_GRAVITY) ) { + continue; + } + + return ix; + } + + return -1; +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( idEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( idEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const { + int index = GetCurrentGravityInfoIndex( origin ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(origin, axis, MASK_SOLID, NULL) : GetGravity(); +} + +/* +=================== +idGameLocal::InGravityArea +=================== +*/ +bool idGameLocal::InGravityArea( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity ) >= 0; +} + +/* +=================== +idGameLocal::GetCurrentGravityInfoIndex +=================== +*/ +int idGameLocal::GetCurrentGravityInfoIndex( rvClientEntity* entity ) const { + return GetCurrentGravityInfoIndex( entity->GetPhysics()->GetOrigin() ); +} + +/* +=================== +idGameLocal::GetCurrentGravity +=================== +*/ +const idVec3 idGameLocal::GetCurrentGravity( rvClientEntity* entity ) const { + int index = GetCurrentGravityInfoIndex( entity ); + return (index >= 0) ? gravityInfo[ index ]->GetGravity(entity) : GetGravity(); +} + +/* +=================== +idGameLocal::ReferenceScriptObjectProxy +=================== +*/ +idEntity* idGameLocal::ReferenceScriptObjectProxy( const char* scriptObjectName ) { + idEntity* proxy = NULL; + idEntityPtr safeProxy; + idDict args; + idScriptObject* object = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + proxy = scriptObjectProxies[ ix ].GetEntity(); + assert( proxy ); + + object = &proxy->scriptObject; + if( !object->data ) { + object->SetType( scriptObjectName ); + proxy->ConstructScriptObject(); + return proxy; + } + } + + args.Set( "classname", "func_static" ); + args.Set( "scriptobject", scriptObjectName ); + args.SetBool( "noclipmodel", true ); + bool spawned = SpawnEntityDef(args, &proxy); + if ( !spawned ) { + assert( 0 ); + } + safeProxy = proxy; + scriptObjectProxies.AddUnique( safeProxy ); + return proxy; +} + +/* +=================== +idGameLocal::ReleaseScriptObjectProxy +=================== +*/ +void idGameLocal::ReleaseScriptObjectProxy( const char* proxyName ) { + idScriptObject* object = NULL; + idEntity* entity = NULL; + + for( int ix = 0; ix < scriptObjectProxies.Num(); ++ix ) { + entity = scriptObjectProxies[ ix ].GetEntity(); + if( entity && !idStr::Icmp(entity->GetName(), proxyName) ) { + object = &entity->scriptObject; + if( !object ) { + continue; + } + + entity->DeconstructScriptObject(); + object->Free(); + } + } +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +void idGameLocal::ListEntityStats( const idCmdArgs &args ) { + int i, j; + idStr currentMap; + idList uniqueMapNames; + + + for( i = 1; i < args.Argc(); i++ ) { + if ( idStr::Icmp( args.Argv( i ), "clear" ) == 0 ) { + entityUsageList.Clear(); + common->Printf("Entity stats cleared.\n"); + return; + } + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + entityUsageList[ i ].SetInt( "reported_stat", false ); + } + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) ) { + continue; + } + + entityUsageList[ i ].GetString( "mapFileName", "none", mapFileName ); + if ( currentMap != mapFileName ) + { + if ( i ) { + common->Printf( "\n" ); + } + common->Printf( "================ %s ================\n", mapFileName.c_str() ); + currentMap = mapFileName; + uniqueMapNames.Insert( mapFileName ); + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + if ( checkMapFileName != mapFileName ) { + break; + } + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 1 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + common->Printf( "\n" ); + common->Printf( "\n" ); + common->Printf( "================ OVERALL ================\n" ); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr mapFileName, className; + int count; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 2 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + count = 0; + + for( j = i; j < entityUsageList.Num(); j++ ) { + idStr checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 2 ); + count++; + } + } + + common->Printf("%d\t%s\n", count, className.c_str() ); + } + + idFile *FH = fileSystem->OpenFileWrite( "EntityStats.csv" ); + if ( FH ) { + int size = sizeof( int ) * uniqueMapNames.Num(); + int *count = ( int * )_alloca( size ); + + FH->Printf("\"Definition\""); + for( i = 0; i < uniqueMapNames.Num(); i++ ) { + FH->Printf( ",\"%s\"", uniqueMapNames[ i ].c_str() ); + } + FH->Printf(",Total\n"); + + for( i = 0; i < entityUsageList.Num(); i++ ) { + idStr className; + int total; + + if ( entityUsageList[ i ].GetInt( "reported_stat" ) == 3 ) { + continue; + } + + entityUsageList[ i ].GetString( "classname", "none", className ); + + memset( count, 0, size ); + for( j = i; j < entityUsageList.Num(); j++ ) + { + idStr checkMapFileName, checkClassName; + + entityUsageList[ j ].GetString( "classname", "none", checkClassName ); + + if ( checkClassName == className ) { + entityUsageList[ j ].SetInt( "reported_stat", 3 ); + entityUsageList[ j ].GetString( "mapFileName", "none", checkMapFileName ); + + int loc = uniqueMapNames.FindIndex( checkMapFileName ); + if ( loc >= 0 ) { + count[ loc ]++; + } + } + } + + total = 0; + FH->Printf( "\"%s\"", className.c_str() ); + for( j = 0; j < uniqueMapNames.Num(); j++ ) { + FH->Printf( ",%d", count[ j ] ); + total += count[ j ]; + } + FH->Printf( ",%d\n", total ); + } + + fileSystem->CloseFile( FH ); + } +} +// RAVEN END + +/* +====================== +idGameLocal::SpreadLocations + +Now that everything has been spawned, associate areas with location entities +====================== +*/ +void idGameLocal::SpreadLocations() { + idEntity *ent; + +// RAVEN BEGIN + if( !gameRenderWorld ) { + common->Error( "GameRenderWorld is NULL!" ); + } +// RAVEN END + + // allocate the area table + int numAreas = gameRenderWorld->NumAreas(); + locationEntities = new idLocationEntity *[ numAreas ]; + memset( locationEntities, 0, numAreas * sizeof( *locationEntities ) ); + + // for each location entity, make pointers from every area it touches + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLocationEntity::GetClassType() ) ) { +// RAVEN END + continue; + } + idVec3 point = ent->spawnArgs.GetVector( "origin" ); + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Printf( "SpreadLocations: location '%s' is not in a valid area\n", ent->spawnArgs.GetString( "name" ) ); + continue; + } + if ( areaNum >= numAreas ) { + Error( "idGameLocal::SpreadLocations: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning( "location entity '%s' overlaps '%s'", ent->spawnArgs.GetString( "name" ), + locationEntities[areaNum]->spawnArgs.GetString( "name" ) ); + continue; + } + locationEntities[areaNum] = static_cast(ent); + + // spread to all other connected areas + for ( int i = 0 ; i < numAreas ; i++ ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(ent); + } + } + } +} + +/* +=================== +idGameLocal::AddLocation +=================== +*/ +idLocationEntity* idGameLocal::AddLocation( const idVec3& point, const char* name ) { + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + Warning ( "idGameLocal::AddLocation: cannot add location entity '%s' at '%g %g %g'\n", name, point.x, point.y, point.z ); + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::AddLocation: areaNum >= gameRenderWorld->NumAreas()" ); + } + if ( locationEntities[areaNum] ) { + Warning ( "idGameLocal::AddLocation: location '%s' already exists at '%g %g %g'\n", locationEntities[areaNum]->GetName(), point.x, point.y, point.z ); + return NULL; + } + + // Spawn the new location entity + idDict args; + args.Set ( "location", name ); + locationEntities[areaNum] = static_cast(SpawnEntityType ( idLocationEntity::GetClassType(), &args )); + + // spread to all other connected areas + for ( int i = gameRenderWorld->NumAreas() - 1 ; i >= 0 ; i-- ) { + if ( i == areaNum ) { + continue; + } + if ( gameRenderWorld->AreasAreConnected( areaNum, i, PS_BLOCK_LOCATION ) ) { + locationEntities[i] = static_cast(locationEntities[areaNum]); + } + } + + return locationEntities[areaNum]; +} + +/* +=================== +idGameLocal::LocationForPoint + +The player checks the location each frame to update the HUD text display +May return NULL +=================== +*/ +idLocationEntity *idGameLocal::LocationForPoint( const idVec3 &point ) { + if ( !locationEntities ) { + // before SpreadLocations() has been called + return NULL; + } + + int areaNum = gameRenderWorld->PointInArea( point ); + if ( areaNum < 0 ) { + return NULL; + } + if ( areaNum >= gameRenderWorld->NumAreas() ) { + Error( "idGameLocal::LocationForPoint: areaNum >= gameRenderWorld->NumAreas()" ); + } + + return locationEntities[ areaNum ]; +} + +/* +============ +idGameLocal::SetPortalState +============ +*/ +void idGameLocal::SetPortalState( qhandle_t portal, int blockingBits ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTAL ); + outMsg.WriteLong( portal ); + outMsg.WriteBits( blockingBits, NUM_RENDER_PORTAL_BITS ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + gameRenderWorld->SetPortalState( portal, blockingBits ); +} + +/* +============ +idGameLocal::sortSpawnPoints +============ +*/ +int idGameLocal::sortSpawnPoints( const void *ptr1, const void *ptr2 ) { + const spawnSpot_t *spot1 = static_cast( ptr1 ); + const spawnSpot_t *spot2 = static_cast( ptr2 ); + float diff; + + diff = spot1->dist - spot2->dist; + if ( diff < 0.0f ) { + return 1; + } else if ( diff > 0.0f ) { + return -1; + } else { + return 0; + } +} + +// RAVEN BEGIN +// ddynerman: new gametype specific spawn code +// TODO this should be moved to idMultiplayerGame +/* +=========== +idGameLocal::InitializeSpawns +randomize the order of the initial spawns +prepare for a sequence of initial player spawns +============ +*/ +void idGameLocal::InitializeSpawns( void ) { + idEntity* spot = NULL; + + // initialize the spawns for clients as well, need them for free fly demo replays + if ( !isMultiplayer ) { + return; + } + + spawnSpots.Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamSpawnSpots[i].Clear(); + } + + spot = FindEntityUsingDef( NULL, "info_player_team" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + spot = FindEntityUsingDef( NULL, "info_player_deathmatch" ); + while( spot ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { +// RAVEN END + spawnSpots.Append ( static_cast(spot) ); + } + spot = FindEntityUsingDef( spot, "info_player_deathmatch" ); + } + + while( spot ) { + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if( spot->IsType ( idPlayerStart::GetClassType() ) ) { + // RAVEN END + if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "strogg") ) { + teamSpawnSpots[TEAM_STROGG].Append( static_cast(spot) ); + } else if( !idStr::Icmp(spot->spawnArgs.GetString("team"), "marine") ) { + teamSpawnSpots[TEAM_MARINE].Append( static_cast(spot) ); + } + + // spawnSpots contains info_player_team as well as info_player_deathmatch + spawnSpots.Append ( static_cast(spot) ); + + } + + spot = FindEntityUsingDef( spot, "info_player_team" ); + } + + if( IsFlagGameType() && ( teamSpawnSpots[ TEAM_STROGG ].Num() == 0 || teamSpawnSpots[ TEAM_MARINE ].Num() == 0 ) ) { + Error( "InitializeSpawns() - Map must have at least one Marine and one Strogg spawn for CTF gametype."); + } + + if( spawnSpots.Num() == 0 ) { + Error( "InitializeSpawns() - Map must have a spawn spot." ); + } + + common->Printf( "%d general spawns\n", spawnSpots.Num() ); + common->Printf( "%d team spawns (%d strogg/%d marine)\n", teamSpawnSpots[TEAM_STROGG].Num() + teamSpawnSpots[TEAM_MARINE].Num(), + teamSpawnSpots[TEAM_STROGG].Num(), teamSpawnSpots[TEAM_MARINE].Num()); +} + +/* +=========== +idGameLocal::UpdateForwardSpawn +ddynerman: Updates forward spawn lists +=========== +*/ +void idGameLocal::UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ) { + teamForwardSpawnSpots[ team ].Append( point ); +} + +/* +=========== +idGameLocal::ClearForwardSpawn +ddynerman: Clears forward spawn lists +=========== +*/ +void idGameLocal::ClearForwardSpawns( void ) { + for( int i = 0; i < TEAM_MAX; i++ ) { + teamForwardSpawnSpots[ i ].Clear(); + } +} + +/* +=========== +idGameLocal::SpotWouldTelefrag +=========== +*/ +bool idGameLocal::SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ) { + idPlayer* playerList[ MAX_CLIENTS ]; + idBounds bound = player->GetPhysics()->GetBounds(); + + bound.TranslateSelf( spawn->GetPhysics()->GetOrigin() ); + int numEntities = PlayersTouchingBounds( player, bound, CONTENTS_BODY, playerList, MAX_CLIENTS ); + + return !( numEntities == 0 ); +} + +/* +=========== +idGameLocal::SelectSpawnSpot +ddynerman: Selects a spawn spot randomly from spots furthest from the player + This is taken from q3 +=========== +*/ +idEntity* idGameLocal::SelectSpawnPoint( idPlayer* player ) { + if( !isMultiplayer ) { + idEntity* ent = FindEntityUsingDef( NULL, "info_player_start" ); + if ( !ent ) { + Error( "No info_player_start on map.\n" ); + } + return ent; + } + + if ( player == NULL ) { + return NULL; + } + + // Give spectators any old random spot + if ( player->team < 0 || player->team >= TEAM_MAX || player->spectating ) { + common->DPrintf("Returning a random spot\n"); + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; + } + + idList weightedSpawns; + idList* spawnArray = NULL; + + // Pick which spawns to use based on gametype +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_TOURNEY ) { + spawnArray = &spawnSpots; + } + else if( IsFlagGameType() || gameLocal.gameType == GAME_DEADZONE ) { + if( teamForwardSpawnSpots[ player->team ].Num() ) { + spawnArray = &teamForwardSpawnSpots[ player->team ]; + } else { + spawnArray = &teamSpawnSpots[ player->team ]; + } + } +// RITUAL END + + if ( spawnArray == NULL ) { + Error( "SelectSpawnPoint() - invalid spawn list." ); + return NULL; + } + + idVec3 refPos; + if ( player->lastKiller != NULL && !player->lastKiller->spectating && player->lastKiller->GetInstance() == player->GetInstance() ) { + refPos = player->lastKiller->GetPhysics()->GetOrigin(); + } else { + refPos = player->GetPhysics()->GetOrigin(); + } + + for ( int i = 0; i < spawnArray->Num(); i++ ) { + idPlayerStart* spot = (*spawnArray)[i]; + + if ( spot->GetInstance() != player->GetInstance() || SpotWouldTelefrag( player, spot ) ) { + continue; + } + + idVec3 pos = spot->GetPhysics()->GetOrigin(); + float dist = ( pos - refPos ).LengthSqr(); + + spawnSpot_t newSpot; + + newSpot.dist = dist; + newSpot.ent = (*spawnArray)[ i ]; + weightedSpawns.Append( newSpot ); + } + + if ( weightedSpawns.Num() == 0 ) { + // no spawns avaialable, spawn randomly + common->DPrintf("no spawns avaialable, spawn randomly\n"); + return (*spawnArray)[ random.RandomInt( spawnArray->Num() ) ]; + } + + qsort( ( void * )weightedSpawns.Ptr(), weightedSpawns.Num(), sizeof( spawnSpot_t ), ( int (*)(const void *, const void *) )sortSpawnPoints ); + + int rnd = rvRandom::flrand( 0.0, 1.0 ) * (weightedSpawns.Num() / 2); + return weightedSpawns[ rnd ].ent; +} +/* +================ +idGameLocal::UpdateServerInfoFlags +================ +*/ +// RAVEN BEGIN +// ddynerman: new gametype strings +void idGameLocal::SetGameType( void ) { + gameType = GAME_SP; + + if ( idStr::Icmp( serverInfo.GetString( "si_gameType" ), "singleplayer" ) ) { + mpGame.SetGameType(); + } +} +// RAVEN END + +/* +================ +idGameLocal::SetGlobalMaterial +================ +*/ +void idGameLocal::SetGlobalMaterial( const idMaterial *mat ) { + globalMaterial = mat; +} + +/* +================ +idGameLocal::GetGlobalMaterial +================ +*/ +const idMaterial *idGameLocal::GetGlobalMaterial() { + return globalMaterial; +} + +/* +================ +idGameLocal::GetSpawnId +================ +*/ +int idGameLocal::GetSpawnId( const idEntity* ent ) const { + return ( gameLocal.spawnIds[ ent->entityNumber ] << GENTITYNUM_BITS ) | ent->entityNumber; +} + +/* +================ +idGameLocal::ThrottleUserInfo +================ +*/ +void idGameLocal::ThrottleUserInfo( void ) { + mpGame.ThrottleUserInfo(); +} + +/* +=========== +idGameLocal::ValidateServerSettings +============ +*/ +bool idGameLocal::ValidateServerSettings( const char* map, const char* gametype ) { + // PickMap uses si_map directly + // PickMap returns wether we would have to change the maps, which means settings are invalid + assert( !idStr::Icmp( si_map.GetString(), map ) ); + if ( mpGame.PickMap( gametype, true ) ) { + common->Printf( "map '%s' and gametype '%s' are not compatible\n", map, gametype ); + return false; + } + return true; +} + +/* +=========== +idGameLocal::NeedRestart +============ +*/ +bool idGameLocal::NeedRestart() { + + idDict newInfo; + const idKeyValue *keyval, *keyval2; + + newInfo = *cvarSystem->MoveCVarsToDict( CVAR_SERVERINFO ); + + for ( int i = 0; i < newInfo.GetNumKeyVals(); i++ ) { + keyval = newInfo.GetKeyVal( i ); + keyval2 = serverInfo.FindKey( keyval->GetKey() ); + if ( !keyval2 ) { + return true; + } + // a select set of si_ changes will cause a full restart of the server + if ( keyval->GetValue().Icmp( keyval2->GetValue() ) && ( !keyval->GetKey().Icmp( "si_pure" ) || !keyval->GetKey().Icmp( "si_map" ) || !keyval->GetKey().Icmp( "si_fps" ) ) ) { + return true; + } + } + return false; +} + +// RAVEN BEGIN +// jshepard: update player hud to alert to end of level +/* +=================== +idGameLocal::UpdateEndLevel +=================== +*/ +void idGameLocal::UpdateEndLevel() { + idPlayer * player = GetLocalPlayer(); + + if( player && player->GetHud() ) { + player->GetHud()->HandleNamedEvent( "showExit" ); + } +} + + +// bdube: added +/* +================ +idGameLocal::GetEffect + +Get the handle of the effect with the given name +================ +*/ +const idDecl *idGameLocal::GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType ) { + const char *effectFile = NULL; + + float chance = args.GetFloat ( idStr("effectchance ") + effectName, "1" ); + if ( random.RandomFloat ( ) > chance ) { + return NULL; + } + + // we should ALWAYS be playing sounds from the def. + // hardcoded sounds MUST be avoided at all times because they won't get precached. + assert( !idStr::Icmpn( effectName, "fx_", 3 ) ); + + if ( materialType ) { + idStr temp; + const char* result = NULL; + + temp = effectName; + temp += "_"; + + temp += materialType->GetName(); + + // See if the given material effect is specified + idStr testMP = temp; + testMP += "_mp"; + + result = args.GetString( testMP ); + + if ( !result || !*result ) { + result = args.GetString( temp ); + } + if ( result && *result) { + return( ( const idDecl * )declManager->FindEffect( result ) ); + } + } + + // grab the non material effect name + idStr alternateEffect = effectName; + alternateEffect += "_mp"; + + idStr classname = args.GetString( "classname" ); + if ( !alternateEffect.Icmp( "fx_fly_mp" ) ) { + if ( + ( !g_nailTrail.GetBool() && !classname.Icmpn( "projectile_nail", 15 ) ) || + ( !g_rocketTrail.GetBool() && !classname.Icmpn( "projectile_rocket", 17 ) ) || + ( !g_napalmTrail.GetBool() && !classname.Icmpn( "projectile_napalm", 17 ) ) ) + { + alternateEffect += "_low"; + } else if ( !g_grenadeTrail.GetBool() && !classname.Icmpn( "projectile_grenade", 18 ) ) + { + return NULL; + } + } else if ( !alternateEffect.Icmp( "fx_path_mp" ) ) + { + if ( !g_railTrail.GetBool() && !classname.Icmpn( "hitscan_railgun", 15 ) ) + { + alternateEffect += "_low"; + } + } + + effectFile = args.GetString( alternateEffect ); + + if ( !effectFile || !*effectFile ) { + effectFile = args.GetString( effectName ); + } + + if ( !effectFile || !*effectFile ) { + return NULL; + } + + return( ( const idDecl * )declManager->FindEffect( effectFile ) ); +} + +/* +================ +idGameLocal::PlayEffect + +Plays an effect at the given origin using the given direction +================ +*/ +rvClientEffect* idGameLocal::PlayEffect( + const idDecl *effect, + const idVec3& origin, + const idMat3& axis, + bool loop, + const idVec3& endOrigin, + bool broadcast, + bool predictedBit, + effectCategory_t category, + const idVec4& effectTint ) { + + if ( !effect ) { + return NULL; + } + + if ( !gameLocal.isNewFrame ) { + return NULL; + } + + if ( isServer && broadcast ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idCQuat quat; + + quat = axis.ToCQuat(); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EFFECT ); + idGameLocal::WriteDecl( msg, effect ); + msg.WriteFloat( origin.x ); + msg.WriteFloat( origin.y ); + msg.WriteFloat( origin.z ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteFloat( endOrigin.x ); + msg.WriteFloat( endOrigin.y ); + msg.WriteFloat( endOrigin.z ); + msg.WriteByte( category ); + msg.WriteBits( loop, 1 ); + msg.WriteBits( predictedBit, 1 ); + + // send to everyone who has start or end in it's PVS + SendUnreliableMessagePVS( msg, currentThinkingEntity, pvs.GetPVSArea( origin ), pvs.GetPVSArea( endOrigin ) ); + } + + if ( isServer && localClientNum < 0 ) { + // no effects on dedicated server + return NULL; + } + + if ( bse->Filtered( effect->GetName(), category ) ) { + // Effect filtered out + return NULL; + } +// RAVEN END + + if ( gameLocal.isListenServer && currentThinkingEntity && gameLocal.GetLocalPlayer() ) { + if ( currentThinkingEntity->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + return NULL; + } + } + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + rvClientEffect* clientEffect = new rvClientEffect( effect ); + RV_POP_HEAP(); + + if( !clientEffect ) { + common->Warning( "Failed to create effect \'%s\'\n", effect->GetName() ); + return NULL; + } + + if( clientEffect->entityNumber == -1 ) { + common->Warning( "Failed to spawn effect \'%s\'\n", effect->GetName() ); + delete clientEffect; + return NULL; + } + + clientEffect->SetOrigin( origin ); + clientEffect->SetAxis( axis ); + clientEffect->SetGravity( GetCurrentGravity( origin, axis ) ); + if ( !clientEffect->Play( gameLocal.time, loop, endOrigin ) ) { + delete clientEffect; + return NULL; + } + + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_RED ] = effectTint[ 0 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_GREEN ] = effectTint[ 1 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_BLUE ] = effectTint[ 2 ]; + clientEffect->GetRenderEffect()->shaderParms[ SHADERPARM_ALPHA ] = effectTint[ 3 ]; + + return clientEffect; +} + +void idGameLocal::CheckPlayerWhizzBy( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ) +{ + //FIXME: make this client-side? Work in MP? + if ( gameLocal.isMultiplayer ) { + return; + } + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + if ( player->IsHidden() ) { + return; + } + if ( player == attacker ) { + return; + } + if ( player == hitEnt ) { + return; + } + idVec3 center = player->firstPersonViewOrigin; + idVec3 diff = end-center; + if ( diff.Length() < 64.0f ) { + //hit too close - didn't actually pass by, will hear impact sound instead + return; + } + idVec3 closestPoint = player->firstPersonViewOrigin; + if ( closestPoint.ProjectToLineSeg( start, end ) ) { + //on line seg + diff = closestPoint-center; + if ( diff.Length() < 48.0f ) { + //close enough to hear whizz-by + idVec3 dir = end-start; + dir.Normalize(); + idVec3 fxStart = closestPoint+dir*-32.0f; + idVec3 fxEnd = closestPoint+dir*32.0f; + player->PlayEffect( "fx_whizby", fxStart, player->firstPersonViewAxis, false, fxEnd ); + } + } +} + +/* +================ +idGameLocal::HitScan + +Run a hitscan trace from the given origin and direction +================ +*/ +idEntity* idGameLocal::HitScan( + const idDict& hitscanDict, + const idVec3& origOrigin, + const idVec3& origDir, + const idVec3& origFxOrigin, + idEntity* owner, + bool noFX, + float damageScale, +// twhitaker: added additionalIgnore parameter + idEntity* additionalIgnore, + int areas[ 2 ] + ) { + + idVec3 dir; + idVec3 origin; + idVec3 fxOrigin; + idVec3 fxDir; + idVec3 impulse; + idVec4 hitscanTint( 1.0f, 1.0f, 1.0f, 1.0f ); + int reflect; + float tracerChance; + idEntity* ignore; + float penetrate; + + if ( areas ) { + areas[ 0 ] = pvs.GetPVSArea( origFxOrigin ); + areas[ 1 ] = -1; + } + + ignore = owner; + penetrate = hitscanDict.GetFloat( "penetrate" ); + + if( hitscanDict.GetBool( "hitscanTint" ) && owner->IsType( idPlayer::GetClassType() ) ) { + hitscanTint = ((idPlayer*)owner)->GetHitscanTint(); + } + + // twhitaker: additionalIgnore parameter + if ( !additionalIgnore ) { + additionalIgnore = ignore; + } + + origin = origOrigin; + fxOrigin = origFxOrigin; + dir = origDir; + tracerChance = ((g_perfTest_weaponNoFX.GetBool())?0:hitscanDict.GetFloat( "tracerchance", "0" )); + + // Apply player powerups + if ( owner && owner->IsType( idPlayer::GetClassType() ) ) { + damageScale *= static_cast(owner)->PowerUpModifier(PMOD_PROJECTILE_DAMAGE); + } + + // Run reflections + for ( reflect = hitscanDict.GetFloat( "reflect", "0" ); reflect >= 0; reflect-- ) { + idVec3 start; + idVec3 end; + idEntity* ent; + idEntity* actualHitEnt; + trace_t tr; + int contents; + int collisionArea; + idVec3 collisionPoint; + bool tracer; + + // Calculate the end point of the trace + start = origin; + if ( g_perfTest_hitscanShort.GetBool() ) { + end = start + (dir.ToMat3() * idVec3(idMath::ClampFloat(0,2048,hitscanDict.GetFloat ( "range", "2048" )),0,0)); + } else { + end = start + (dir.ToMat3() * idVec3(hitscanDict.GetFloat ( "range", "40000" ),0,0)); + } + if ( g_perfTest_hitscanBBox.GetBool() ) { + contents = MASK_SHOT_BOUNDINGBOX|CONTENTS_PROJECTILE; + } else { + contents = MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE; + } + + // Loop the traces to handle cases where something can be shot through + while ( 1 ) { + // Trace to see if we hit any entities +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( hitscanDict.GetFloat( "trace_size", "0" ) > 0.0f ) + { + float range = hitscanDict.GetFloat ( "range", "1024" ); + if ( range > 4096.0f ) + { + assert( !(range > 4096.0f) ); + Warning( "idGameLocal::HitScan: hitscan def (%s) with trace_size must have max range of 4096!", hitscanDict.GetString( "classname" ) ); + range = idMath::ClampFloat( 0.0f, 4096.0f, range ); + } + end = start + (dir * range); + + idBounds traceBounds; + traceBounds.Zero(); + traceBounds.ExpandSelf( hitscanDict.GetFloat( "trace_size", "0" ) ); + // twhitaker: additionalIgnore parameter + TraceBounds( owner, tr, start, end, traceBounds, contents, additionalIgnore ); + } + else + { + // twhitaker: additionalIgnore parameter + TracePoint( owner, tr, start, end, contents, additionalIgnore ); + } + //gameRenderWorld->DebugArrow( colorRed, start, end, 10, 5000 ); +// RAVEN END + + // If the hitscan hit a no impact surface we can just return out + //assert( tr.c.material ); + if ( tr.fraction >= 1.0f || (tr.c.material && tr.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { + PlayEffect( hitscanDict, "fx_path", fxOrigin, dir.ToMat3(), false, tr.endpos, false, false, EC_IGNORE, hitscanTint ); + if ( random.RandomFloat( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, dir.ToMat3(), false, tr.endpos ); + tracer = true; + } else { + tracer = false; + } + + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.endpos ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + + return NULL; + } + + // computing the collisionArea from the collisionPoint fails sometimes + if ( areas ) { + collisionArea = pvs.GetPVSArea( tr.c.point ); + if ( collisionArea != areas[0] ) { + areas[1] = collisionArea; + } + } + collisionPoint = tr.c.point - ( tr.c.normal * tr.c.point - tr.c.dist ) * tr.c.normal; + ent = entities[ tr.c.entityNum ]; + actualHitEnt = NULL; + start = collisionPoint; + + // Keep tracing if we hit water + if ( (ent->GetPhysics()->GetContents() & CONTENTS_WATER) || (tr.c.material && (tr.c.material->GetContentFlags() & CONTENTS_WATER)) ) { + // Apply force to the water entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -(hitscanDict.GetFloat( "push", "5000" )) * tr.c.normal ); + // Continue on excluding water + contents &= (~CONTENTS_WATER); + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3(), false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + continue; + // Reflect off a bounce target? + } else if ( (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) && !hitscanDict.GetBool ( "noBounce" ) ) { + reflect++; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->fl.takedamage && ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster( ); + } + + if ( !gameLocal.isClient ) { + + // Apply force to the entity that was hit + ent->ApplyImpulse( owner, tr.c.id, tr.c.point, -tr.c.normal, &hitscanDict ); + + // Handle damage to the entity + if ( ent->fl.takedamage && !(( tr.c.material != NULL ) && ( tr.c.material->GetSurfaceFlags() & SURF_NODAMAGE )) ) { + const char* damage; + + damage = NULL; + + // RAVEN BEGIN + // jdischler: code from the other project..to ensure that if an attached head is hit, the body will use the head joint + // otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(tr.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == tr.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } + // RAVEN END + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + + if ( damage && damage[0] ) { + // RAVEN BEGIN + // ddynerman: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, ((idPlayer*)owner)->GetCurrentWeapon() ); + } + // RAVEN END + ent->Damage( owner, owner, dir, damage, damageScale, hitJoint ); + } + + // Let the entity add its own damage effect + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect ( tr, dir, damage, owner ); + } + } else { + if ( actualHitEnt + && actualHitEnt != ent + && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + const char* damage = NULL; + // Inflict damage + if ( tr.c.materialType ) { + damage = hitscanDict.GetString( va("def_damage_%s", tr.c.materialType->GetName()) ); + } + if ( !damage || !*damage ) { + damage = hitscanDict.GetString ( "def_damage" ); + } + if ( damage && damage[0] ) { + actualHitEnt->Damage( owner, owner, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( tr.c.id ) ); + } + } + if ( !g_perfTest_weaponNoFX.GetBool() ) { + ent->AddDamageEffect( tr, dir, hitscanDict.GetString ( "def_damage" ), owner ); + } + } + } + + + // Pass through actors + if ( ent->IsType ( idActor::GetClassType() ) && penetrate > 0.0f ) { + start = collisionPoint; + additionalIgnore = ent; + damageScale *= penetrate; + continue; + } + break; + } + + // Path effect + fxDir = collisionPoint - fxOrigin; + fxDir.Normalize( ); + PlayEffect( hitscanDict, "fx_path", fxOrigin, fxDir.ToMat3(), false, collisionPoint, false, false, EC_IGNORE, hitscanTint ); + if ( !ent->fl.takedamage && random.RandomFloat ( ) < tracerChance ) { + PlayEffect( hitscanDict, "fx_tracer", fxOrigin, fxDir.ToMat3(), false, collisionPoint ); + tracer = true; + } else { + tracer = false; + } + + if ( !reflect ) { + //on initial trace only + if ( hitscanDict.GetBool( "doWhizz" ) ) { + //play whizz-by sound if trace is close to player's head + CheckPlayerWhizzBy( origin, collisionPoint, ent, owner ); + } + } + + // Play a different effect when reflecting + if ( !reflect || ent->fl.takedamage ) { + idMat3 axis; + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType ( idActor::GetClassType() ) ) { + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + } else { + axis = tr.c.normal.ToMat3(); + } + + if ( !g_perfTest_weaponNoFX.GetBool() ) { + if ( ent->CanPlayImpactEffect( owner, ent ) ) { + if ( ent->IsType( idMover::GetClassType( ) ) ) { + ent->PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } else { + gameLocal.PlayEffect( GetEffect( hitscanDict, "fx_impact", tr.c.materialType ), collisionPoint, axis, false, vec3_origin, false, false, EC_IMPACT, hitscanTint ); + } + } + } + + // End of reflection + return ent; + } else { + PlayEffect( GetEffect( hitscanDict, "fx_reflect", tr.c.materialType ), collisionPoint, tr.c.normal.ToMat3() ); + } + + // Calc new diretion based on bounce + origin = start; + fxOrigin = start; + dir = ( dir - ( 2.0f * DotProduct( dir, tr.c.normal ) * tr.c.normal ) ); + dir.Normalize( ); + + // Increase damage scale on reflect + damageScale += hitscanDict.GetFloat( "reflect_powerup", "0" ); + } + + assert( false ); + + return NULL; +} + +/* +=================== +idGameLocal::RegisterClientEntity +=================== +*/ +void idGameLocal::RegisterClientEntity( rvClientEntity *cent ) { + int entityNumber; + + assert ( cent ); + + if ( clientSpawnCount >= ( 1 << ( 32 - CENTITYNUM_BITS ) ) ) { +// Error( "idGameLocal::RegisterClientEntity: spawn count overflow" ); + clientSpawnCount = INITIAL_SPAWN_COUNT; + } + + // Find a free entity index to use + while( clientEntities[firstFreeClientIndex] && firstFreeClientIndex < MAX_CENTITIES ) { + firstFreeClientIndex++; + } + + if ( firstFreeClientIndex >= MAX_CENTITIES ) { + cent->PostEventMS ( &EV_Remove, 0 ); + Warning( "idGameLocal::RegisterClientEntity: no free client entities" ); + return; + } + + entityNumber = firstFreeClientIndex++; + + // Add the client entity to the lists + clientEntities[ entityNumber ] = cent; + clientSpawnIds[ entityNumber ] = clientSpawnCount++; + cent->entityNumber = entityNumber; + cent->spawnNode.AddToEnd( clientSpawnedEntities ); + cent->spawnArgs.TransferKeyValues( spawnArgs ); + + if ( entityNumber >= num_clientEntities ) { + num_clientEntities++; + } +} + +/* +=================== +idGameLocal::UnregisterClientEntity +=================== +*/ +void idGameLocal::UnregisterClientEntity( rvClientEntity* cent ) { + assert( cent ); + + // No entity number then it failed to register + if ( cent->entityNumber == -1 ) { + return; + } + + cent->spawnNode.Remove ( ); + cent->bindNode.Remove ( ); + + if ( clientEntities [ cent->entityNumber ] == cent ) { + clientEntities [ cent->entityNumber ] = NULL; + clientSpawnIds[ cent->entityNumber ] = -1; + if ( cent->entityNumber < firstFreeClientIndex ) { + firstFreeClientIndex = cent->entityNumber; + } + cent->entityNumber = -1; + } +} + +/* +=================== +idGameLocal::Translation +=================== +*/ +bool idGameLocal::Translation( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if ( clipWorld ) { + return clipWorld->Translation( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } + + return false; +} + +/* +=================== +idGameLocal::Rotation +=================== +*/ +bool idGameLocal::Rotation( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::Motion +=================== +*/ +bool idGameLocal::Motion( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Motion( results, start, end, rotation, mdl, trmAxis, contentMask, passEntity ); + } + + return false; +} + + +/* +=================== +idGameLocal::Contacts +=================== +*/ +int idGameLocal::Contacts( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contacts( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, passEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::Contents +=================== +*/ +int idGameLocal::Contents( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->Contents( start, mdl, trmAxis, contentMask, passEntity, touchedEntity ); + } + + return 0; +} + +/* +=================== +idGameLocal::TracePoint +=================== +*/ +bool idGameLocal::TracePoint( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TracePoint( results, start, end, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TraceBounds +=================== +*/ +bool idGameLocal::TraceBounds( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->TraceBounds( results, start, end, bounds, contentMask, passEntity ); + } + + return false; +} + +/* +=================== +idGameLocal::TranslationModel +=================== +*/ +void idGameLocal::TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationModel( results, start, end, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::RotationModel +=================== +*/ +void idGameLocal::RotationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->RotationModel( results, start, rotation, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } +} + +/* +=================== +idGameLocal::ContactsModel +=================== +*/ +int idGameLocal::ContactsModel( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContactsModel( contacts, maxContacts, start, dir, depth, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::ContentsModel +=================== +*/ +int idGameLocal::ContentsModel( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ContentsModel( start, mdl, trmAxis, contentMask, model, modelOrigin, modelAxis ); + } + + return 0; +} + +/* +=================== +idGameLocal::TranslationEntities +=================== +*/ +void idGameLocal::TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { + idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + clipWorld->TranslationEntities( results, start, end, mdl, trmAxis, contentMask, passEntity, passEntity2 ); + } +} + +/* +=================== +idGameLocal::GetModelContactFeature +=================== +*/ +bool idGameLocal::GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->GetModelContactFeature( contact, clipModel, winding ); + } + + return false; +} + +/* +=================== +idGameLocal::EntitiesTouchingBounds +=================== +*/ +int idGameLocal::EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->EntitiesTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::ClipModelsTouchingBounds +=================== +*/ +int idGameLocal::ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->ClipModelsTouchingBounds( bounds, contentMask, clipModelList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::PlayersTouchingBounds +=================== +*/ +int idGameLocal::PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if( clipWorld ) { + return clipWorld->PlayersTouchingBounds( bounds, contentMask, entityList, maxCount ); + } + + return 0; +} + +/* +=================== +idGameLocal::GetWorldBounds +=================== +*/ +const idBounds& idGameLocal::GetWorldBounds( const idEntity* ent ) const { + const idClip* clipWorld = GetEntityClipWorld( ent ); + + if ( clipWorld ) { + return clipWorld->GetWorldBounds(); + } + + return clip[ 0 ]->GetWorldBounds(); +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::GetEntityClipWorld +=================== +*/ +const idClip* idGameLocal::GetEntityClipWorld( const idEntity* ent ) const { + if( ent == NULL ) { + return clip[ 0 ]; + } + + if( ent->GetClipWorld() < 0 || ent->GetClipWorld() >= clip.Num() ) { + Warning( "idGameLocal::GetEntityClipWorld() - invalid clip world %d on entity %s (valid range: 0 - %d)\n", ent->GetClipWorld(), ent->GetClassname(), clip.Num() - 1 ); + return NULL; + } + return clip[ ent->GetClipWorld() ]; +} + +/* +=================== +idGameLocal::AddClipWorld +=================== +*/ +int idGameLocal::AddClipWorld( int id ) { + if( id >= clip.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = clip.Num(); i <= id; i++ ) { + clip.Append( NULL ); + } + } + + if( clip[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); +// RAVEN END + clip[ id ] = new idClip(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + clip[ id ]->Init(); + } + return id; +} + +/* +=================== +idGameLocal::RemoveClipWorld +=================== +*/ +void idGameLocal::RemoveClipWorld( int id ) { + assert( id >= 0 && id < clip.Num() ); + + clip[ id ]->Shutdown(); + delete clip[ id ]; + clip[ id ] = NULL; +} + +/* +=================== +idGameLocal::ShutdownInstances +=================== +*/ +void idGameLocal::ShutdownInstances( void ) { + if( gamestate == GAMESTATE_UNINITIALIZED ) { + return; + } + + instances.DeleteContents( true ); + + // free the trace model used for the defaultClipModel + idClip::FreeDefaultClipModel(); +} + +/* +=================== +idGameLocal::AddInstance +=================== +*/ +int idGameLocal::AddInstance( int id, bool deferPopulate ) { + if ( id == -1 ) { + id = instances.Num(); + } + + if ( id >= instances.Num() ) { + // if we want an index higher in the list, fill the intermediate indices with empties + for( int i = instances.Num(); i <= id; i++ ) { + instances.Append( NULL ); + } + } + + if ( instances[ id ] == NULL ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + instances[ id ] = new rvInstance( id, deferPopulate ); + RV_POP_HEAP(); +// RAVEN END + + if ( !deferPopulate ) { + // keep track of the high watermark + ServerSetEntityIndexWatermark( id ); + } + + common->DPrintf( "idGameLocal::AddInstance(): Adding instance %d\n", instances[ id ]->GetInstanceID() ); + } else { + common->DPrintf( "idGameLocal::AddInstance(): Instance %d already exists\n", instances[ id ]->GetInstanceID() ); + } + + // keep the min spawn index correctly set + ServerSetMinSpawnIndex(); + + return instances[ id ]->GetInstanceID(); +} + +/* +=================== +idGameLocal::RemoveInstance +=================== +*/ +void idGameLocal::RemoveInstance( int id ) { + delete instances[ id ]; + instances[ id ] = NULL; +} + +/* +=================== +idGameLocal::GetPlayerName +Returns the specified player name, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerName( int clientNum, char* name ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( name, gameLocal.GetUserInfo( clientNum )->GetString( "ui_name" ), 64 ); + name[ 63 ] = 0; +} + +/* +=================== +idGameLocal::GetPlayerClan +Returns the specified player clan, max of 64 chars +=================== +*/ +void idGameLocal::GetPlayerClan( int clientNum, char* clan ) { + if( !gameLocal.entities[ clientNum ] ) { + return; + } + + strncpy( clan, gameLocal.GetUserInfo( clientNum )->GetString( "ui_clan" ), 64 ); + clan[ 63 ] = 0; +} + +/* +=================== +idGameLocal::SetFriend +=================== +*/ +void idGameLocal::SetFriend( int clientNum, bool isFriend ) { + if( !gameLocal.GetLocalPlayer() ) { + Warning( "idGameLocal::SetFriend() - SetFriend() called with NULL local player\n" ); + return; + } + + gameLocal.GetLocalPlayer()->SetFriend( clientNum, isFriend ); +} + +/* +=================== +idGameLocal::GetLongGametypeName +=================== +*/ +const char* idGameLocal::GetLongGametypeName( const char* gametype ) { + return mpGame.GetLongGametypeName( gametype ); +} + +void idGameLocal::Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ) { + int instance = 0; + + if ( args.Argc() > 1 ) { + instance = atoi( args.Argv( 1 ) ); + } + + if( gameLocal.instances[ instance ] ) { + gameLocal.instances[ instance ]->PrintMapNumbers(); + } +} + +void idGameLocal::Cmd_PrintSpawnIds_f( const idCmdArgs& args ) { + for( int i = 0; i < MAX_GENTITIES; i++ ) { + if( gameLocal.entities[ i ] ) { + gameLocal.Printf( "Spawn id %d: %d\n", i, gameLocal.spawnIds[ i ] ); + } + } +} + +/* +=============== +idGameLocal::IsTeamPowerups +=============== +*/ +bool idGameLocal::IsTeamPowerups( void ) { + if ( !serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + return false; + } + if ( !IsTeamGameType() ) { + return false; + } + return ( gameType != GAME_ARENA_CTF ); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +=================== +idGameLocal::FlushBeforelevelLoad +=================== +*/ +void idGameLocal::FlushBeforelevelLoad( void ) +{ + TIME_THIS_SCOPE( __FUNCLINE__); + +#ifndef _XENON + MapShutdown(); +#else + mpGame.Clear(); +#endif + for(int i = 0; i < aasNames.Num(); i++) + { + aasList[i]->Shutdown(); + } +} +#endif +// RAVEN END + +// dluetscher: moved the overloaded new/delete to sys_local.cpp and Game_local.cpp (from Heap.h) +// so that the tools.dll will link. +#if !defined(_XBOX) && (defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT)) + +#undef new +#undef delete +#undef Mem_Alloc +#undef Mem_Free + +#ifdef ID_DEBUG_MEMORY +void *operator new( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new[]( size_t s, int t1, int t2, char *fileName, int lineNumber ) { + return Mem_Alloc( s, fileName, lineNumber, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p, int t1, int t2, char *fileName, int lineNumber ) { + Mem_Free( p, fileName, lineNumber ); +} + +void *operator new( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p, "", 0 ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, "", 0, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p, "", 0 ); +} + +#else // #ifdef ID_DEBUG_MEMORY + +void *operator new( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete( void *p ) { + Mem_Free( p ); +} + +void *operator new[]( size_t s ) { + return Mem_Alloc( s, MemScopedTag_GetTopTag() ); +} + +void operator delete[]( void *p ) { + Mem_Free( p ); +} +#endif // #else #ifdef ID_DEBUG_MEMORY +#endif // #if defined(ID_REDIRECT_NEWDELETE) || defined(_RV_MEM_SYS_SUPPORT) +// RAVEN END diff --git a/src/mpgame/Game_local.h b/src/mpgame/Game_local.h new file mode 100644 index 0000000..7c9d076 --- /dev/null +++ b/src/mpgame/Game_local.h @@ -0,0 +1,1520 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_LOCAL_H__ +#define __GAME_LOCAL_H__ + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR +inline void *operator new( size_t s ) { return Memory::Allocate(s); } +inline void operator delete( void *p ) { Memory::Free(p); } +inline void *operator new[]( size_t s ) { return Memory::Allocate(s); } +inline void operator delete[]( void *p ) { Memory::Free(p); } +#endif +// RAVEN END + +/* +=============================================================================== + + Local implementation of the public game interface. + +=============================================================================== +*/ + + +// if set to 1 the server sends the client PVS with snapshots and the client compares against what it sees +#ifndef ASYNC_WRITE_PVS + #define ASYNC_WRITE_PVS 0 +#endif + +extern idRenderWorld * gameRenderWorld; + +// classes used by idGameLocal +class idEntity; +class idActor; +class idPlayer; +class idCamera; +class idWorldspawn; +class idTestModel; +class idAAS; +class idAI; +// RAVEN BEGIN +// bdube: not using id effects +//class idSmokeParticles; +//class idEntityFx; +// bdube: client side entities +class rvInstance; +class rvClientEntity; +class rvClientModel; +class rvCTFAssaultPlayerStart; +class idPlayerStart; +// RAVEN END +class idTypeInfo; +class idProgram; +class idThread; +class idEditEntities; +class idLocationEntity; + +// RAVEN BEGIN +// dluetscher: reduced max clients for memory usage +#ifdef _XENON +#define MAX_CLIENTS 16 +#else +// RAVEN END +#define MAX_CLIENTS 32 +#endif + +#define GENTITYNUM_BITS 12 +#define MAX_GENTITIES (1<PostEventMS(&EV_Remove, 0); (p) = NULL; } +// RAVEN END + +//============================================================================ + +void gameError( const char *fmt, ... ); + +//============================================================================ + +typedef enum { + CHANNEL_DEST_NONE, + CHANNEL_DEST_RELIABLE_SERVER, + CHANNEL_DEST_RELIABLE_REPEATER, + + CHANNEL_DEST_COUNT +} channelDestType_t; + +class idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const = 0; + virtual void Send( const idBitMsg &msg ) const = 0; +}; + +class idNullMessageSender : public idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const { return CHANNEL_DEST_NONE; } + virtual void Send( const idBitMsg &msg ) const { return; }; +}; + +class idServerReliableMessageSender : public idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const { return CHANNEL_DEST_RELIABLE_SERVER; } + virtual void Send( const idBitMsg &msg ) const { + if ( excludeClient != -1 ) { + networkSystem->ServerSendReliableMessageExcluding( excludeClient, msg, inhibitRepeater ); + } else { + networkSystem->ServerSendReliableMessage( clientNum, msg, inhibitRepeater ); + } + }; + const idMessageSender &To( int _clientNum, bool _inhibitRepeater = false ) { + clientNum = _clientNum; + excludeClient = -1; + inhibitRepeater = _inhibitRepeater; + return *this; + } + const idMessageSender &NotTo( int _excludeClient, bool _inhibitRepeater = false ) { + clientNum = -1; + excludeClient = _excludeClient; + inhibitRepeater = _inhibitRepeater; + return *this; + } + +private: + int clientNum; + int excludeClient; + bool inhibitRepeater; +}; + +class idRepeaterReliableMessageSender : public idMessageSender { +public: + virtual channelDestType_t GetChannelType( void ) const { return CHANNEL_DEST_RELIABLE_REPEATER; } + virtual void Send( const idBitMsg &msg ) const { + if ( excludeClient != -1 ) { + networkSystem->RepeaterSendReliableMessageExcluding( excludeClient, msg, inhibitHeader, repeaterClient ); + } else { + networkSystem->RepeaterSendReliableMessage( repeaterClient, msg, inhibitHeader, clientNum ); + } + }; + const idMessageSender &To( int _clientNum, int asClient = -1, bool _inhibitHeader = false ) { + repeaterClient = _clientNum; + clientNum = asClient; + excludeClient = -1; + inhibitHeader = _inhibitHeader; + return *this; + } + const idMessageSender &ExcludingTo( int _excludeClient, int _clientNum, bool _inhibitHeader = false ) { + repeaterClient = _clientNum; + clientNum = -1; + excludeClient = _excludeClient; + inhibitHeader = _inhibitHeader; + return *this; + } + +private: + int repeaterClient; // viewer number on repeater + int clientNum; // clientNum of real player to address exclusively to + int excludeClient; // clientNum of real player to exclude + bool inhibitHeader; +}; + +extern idNullMessageSender nullSender; +extern idServerReliableMessageSender serverReliableSender; +extern idRepeaterReliableMessageSender repeaterReliableSender; + +//============================================================================ + +#include "gamesys/Event.h" +// RAVEN BEGIN +// bdube: added +#include "gamesys/State.h" +// RAVEN END +#include "gamesys/Class.h" +#include "gamesys/SysCvar.h" +#include "gamesys/SysCmds.h" +#include "gamesys/SaveGame.h" +#include "gamesys/DebugGraph.h" + +#include "script/Script_Program.h" + +#include "anim/Anim.h" + +#include "ai/AAS.h" + +#include "physics/Clip.h" +#include "physics/Push.h" + +#include "Pvs.h" + +#include "Lagometer.h" + +//============================================================================ + +const int MAX_GAME_MESSAGE_SIZE = 8192; +const int MAX_ENTITY_STATE_SIZE = 512; +const int ENTITY_PVS_SIZE = ((MAX_GENTITIES+31)>>5); +// RAVEN BEGIN +// abahr: changed to NUM_PORTAL_ATTRIBUTES to take into account gravity +const int NUM_RENDER_PORTAL_BITS = NUM_PORTAL_ATTRIBUTES; +// RAVEN END + +typedef struct entityState_s { + int entityNumber; + idBitMsg state; + byte stateBuf[MAX_ENTITY_STATE_SIZE]; + struct entityState_s * next; +} entityState_t; + +typedef struct snapshot_s { + int sequence; + entityState_t * firstEntityState; + int pvs[ENTITY_PVS_SIZE]; + struct snapshot_s * next; +} snapshot_t; + +const int MAX_EVENT_PARAM_SIZE = 128; + +typedef struct entityNetEvent_s { + int spawnId; + int event; + int time; + int paramsSize; + byte paramsBuf[MAX_EVENT_PARAM_SIZE]; + struct entityNetEvent_s *next; + struct entityNetEvent_s *prev; +} entityNetEvent_t; + +enum { + GAME_RELIABLE_MESSAGE_SPAWN_PLAYER, + GAME_RELIABLE_MESSAGE_DELETE_ENT, + GAME_RELIABLE_MESSAGE_CHAT, + GAME_RELIABLE_MESSAGE_TCHAT, + GAME_RELIABLE_MESSAGE_DB, + GAME_RELIABLE_MESSAGE_KILL, + GAME_RELIABLE_MESSAGE_DROPWEAPON, + GAME_RELIABLE_MESSAGE_RESTART, + GAME_RELIABLE_MESSAGE_SERVERINFO, + GAME_RELIABLE_MESSAGE_CALLVOTE, + GAME_RELIABLE_MESSAGE_CASTVOTE, + GAME_RELIABLE_MESSAGE_STARTVOTE, + GAME_RELIABLE_MESSAGE_UPDATEVOTE, + GAME_RELIABLE_MESSAGE_PORTALSTATES, + GAME_RELIABLE_MESSAGE_PORTAL, + GAME_RELIABLE_MESSAGE_VCHAT, + GAME_RELIABLE_MESSAGE_STARTSTATE, + GAME_RELIABLE_MESSAGE_MENU, + GAME_RELIABLE_MESSAGE_EVENT, + GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND, + GAME_RELIABLE_MESSAGE_DEATH, + GAME_RELIABLE_MESSAGE_GAMESTATE, + GAME_RELIABLE_MESSAGE_STAT, + GAME_RELIABLE_MESSAGE_ALL_STATS, + GAME_RELIABLE_MESSAGE_INGAMEAWARD, + GAME_RELIABLE_MESSAGE_SET_INSTANCE, + GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING, + GAME_RELIABLE_MESSAGE_SERVER_ADMIN, + GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE, + GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE, + GAME_RELIABLE_MESSAGE_GETADMINBANLIST, + GAME_RELIABLE_MESSAGE_PRINT, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST, + GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST, + GAME_RELIABLE_MESSAGE_GETVOTEMAPS, + // unreliable message sent over the reliable channel + // used as a debug solution when making sure some missing things are not due to packet drop + GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE, + // server acknowledges events from client, in response to specific GAME_RELIABLE_MESSAGE_EVENT situations + GAME_RELIABLE_MESSAGE_EVENT_ACK, +}; + +enum { + GAME_UNRELIABLE_MESSAGE_EVENT, + GAME_UNRELIABLE_MESSAGE_EFFECT, + GAME_UNRELIABLE_MESSAGE_HITSCAN, + GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER +}; + +enum { + GAME_UNRELIABLE_RECORD_CLIENTNUM, + GAME_UNRELIABLE_RECORD_AREAS, + + GAME_UNRELIABLE_RECORD_COUNT +}; + +typedef enum { + GAMESTATE_UNINITIALIZED, // prior to Init being called + GAMESTATE_NOMAP, // no map loaded + GAMESTATE_STARTUP, // inside InitFromNewMap(). spawning map entities. + GAMESTATE_RESTART, // spawning map entities from an instance restart, but not fully restarting + GAMESTATE_ACTIVE, // normal gameplay + GAMESTATE_SHUTDOWN // inside MapShutdown(). clearing memory. +} gameState_t; + +typedef struct { + idPlayerStart *ent; + int dist; +} spawnSpot_t; + +typedef struct { + bool connected; + bool active; + bool priv; + bool nopvs; + userOrigin_t origin; + int pvsArea; + idDict info; +} viewer_t; + +//============================================================================ +class idEventQueue { +public: + typedef enum { + OUTOFORDER_IGNORE, + OUTOFORDER_DROP, + OUTOFORDER_SORT + } outOfOrderBehaviour_t; + + idEventQueue() : start( NULL ), end( NULL ) {} + + entityNetEvent_t * Alloc(); + void Free( entityNetEvent_t *event ); + void Shutdown(); + + void Init(); + void Enqueue( entityNetEvent_t* event, outOfOrderBehaviour_t oooBehaviour ); + entityNetEvent_t * Dequeue( void ); + entityNetEvent_t * RemoveLast( void ); + + entityNetEvent_t * Start( void ) { return start; } + +private: + entityNetEvent_t * start; + entityNetEvent_t * end; +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc eventAllocator; +// RAVEN END +}; + +//============================================================================ + +template< class type > +class idEntityPtr { +public: + idEntityPtr(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + idEntityPtr & operator=( type *ent ); + + // synchronize entity pointers over the network + int GetSpawnId( void ) const { return spawnId; } + bool SetSpawnId( int id ); + bool UpdateSpawnId( void ); + + bool IsValid( void ) const; + type * GetEntity( void ) const; + int GetEntityNum( void ) const; + +// RAVEN BEGIN +// bdube: overloaded operators + idEntityPtr( type* ent ) { *this = ent; } + idEntityPtr& operator=( idEntityPtr& ent ) { *this = ent.GetEntity(); return *this; } + type * operator->( void ) const; + operator type *( void ) const; +// RAVEN END + +private: + int spawnId; +}; + +// RAVEN BEGIN +// abahr: forward declaration +class rvGravityArea; +// RAVEN END + +//============================================================================ +// ddynerman: moved MultiplayerGame.h down here, so it can use more stuff in Game_local (idEntityPtr) +#include "MultiplayerGame.h" + +//============================================================================ + +class idGameLocal : public idGame { +public: + idDict serverInfo; // all the tunable parameters, like numclients, etc + idDict repeaterInfo; // serverInfo + repeater specific stuff + int numClients; // pulled from serverInfo and verified + idDict userInfo[MAX_CLIENTS]; // client specific settings + const usercmd_t *usercmds; // client input commands + idDict persistentPlayerInfo[MAX_CLIENTS]; + idEntity * entities[MAX_GENTITIES];// index to entities + int spawnIds[MAX_GENTITIES];// for use in idEntityPtr + int firstFreeIndex; // first free index in the entities array + int minSpawnIndex; // when spawning multiple instances, so nothing pollutes in between the instances + int num_entities; // current number <= MAX_GENTITIES + idHashIndex entityHash; // hash table to quickly find entities by name + idWorldspawn * world; // world entity + idLinkList spawnedEntities; // all spawned entities + idLinkList activeEntities; // all thinking entities (idEntity::thinkFlags != 0) + int numEntitiesToDeactivate;// number of entities that became inactive in current frame + bool sortPushers; // true if active lists needs to be reordered to place pushers at the front + bool sortTeamMasters; // true if active lists needs to be reordered to place physics team masters before their slaves + idDict persistentLevelInfo; // contains args that are kept around between levels + +// RAVEN BEGIN +// bdube: client entities + rvClientEntity * clientEntities[MAX_CENTITIES]; // index to client entities + int clientSpawnIds[MAX_CENTITIES]; // for use in idClientEntityPtr + idLinkList clientSpawnedEntities; // all client side entities + int num_clientEntities; // current number of client entities + int firstFreeClientIndex; // first free index in the client entities array + + int entityRegisterTime; +// RAVEN END + + int maxViewers; // currently allocated + int maxViewer; // highest numbered active viewer + 1 + + viewer_t (*viewers); + + // can be used to automatically effect every material in the world that references globalParms + float globalShaderParms[ MAX_GLOBAL_SHADER_PARMS ]; + + idRandom random; // random number generator used throughout the game + + idProgram program; // currently loaded script and data space + idThread * frameCommandThread; + + idPush push; // geometric pushing + idPVS pvs; // potential visible set + pvsHandle_t clientsPVS[MAX_CLIENTS];// PVS of multiplayer clients updated every frame + + idTestModel * testmodel; // for development testing of models +// RAVEN BEGIN +// bdube: not using id effects +// idEntityFx * testFx; // for development testing of fx +// RAVEN END + + // only set when an end level is activated, which will take over camera positioning + // and draw end-level guis, then + + idStr sessionCommand; // a target_sessionCommand can set this to return something to the session + + idMultiplayerGame mpGame; // handles rules for standard dm + + idEditEntities * editEntities; // in game editing + + int cinematicSkipTime; // don't allow skipping cinemetics until this time has passed so player doesn't skip out accidently from a firefight + int cinematicStopTime; // cinematics have several camera changes, so keep track of when we stop them so that we don't reset cinematicSkipTime unnecessarily + int cinematicMaxSkipTime; // time to end cinematic when skipping. there's a possibility of an infinite loop if the map isn't set up right. + bool inCinematic; // game is playing cinematic (player controls frozen) + bool skipCinematic; + + // are kept up to date with changes to serverInfo + int framenum; + int previousTime; // time in msec of last frame + int time; // in msec + int msec; // time since last update in milliseconds + int mHz; // hertz + + int vacuumAreaNum; // -1 if level doesn't have any outside areas + +// RAVEN BEGIN +// abahr: + idList gravityInfo; // area num for each gravity zone + idList< idEntityPtr > scriptObjectProxies; +// RAVEN END + + gameType_t gameType; + bool isMultiplayer; // set if the game is run in multiplayer mode + bool isServer; // set if the game is run for a dedicated or listen server + bool isClient; // set if the game is run for a client + // discriminates between the RunFrame path and the ClientPrediction path + // NOTE: on a listen server, isClient is false + bool isRepeater; // set if the game is repeating + bool isListenServer; + bool isTVClient; + int localClientNum; // number of the local client. MP: -1 on a dedicated, MAX_CLIENTS when playing a server demo + idLinkList snapshotEntities; // entities from the last snapshot + int realClientTime; // real client time + bool isNewFrame; // true if this is a new game frame, not a rerun due to prediction + int entityDefBits; // bits required to store an entity def number + int clientAckSequence; // holds frame number updated through GAME_RELIABLE_MESSAGE_EVENT_ACK server->client messages + + static const char * sufaceTypeNames[ MAX_SURFACE_TYPES ]; // text names for surface types + + idEntityPtr lastGUIEnt; // last entity with a GUI, used by Cmd_NextGUI_f + int lastGUI; // last GUI on the lastGUIEnt + + int editors; // Mirrored editors flags from common determine which editors are running + bool isLastPredictFrame; // on an MP server or in SP game this means 'last catchup frame' rather than predict + +// RAVEN BEGIN +// rjohnson: entity usage stats + idStr mapFileNameStripped; // name of the map, empty string if no map loaded, with path and extension removed. If entity filter, that is appended + idList entityUsageList; +// ddynerman: the entity currently thinking, used to play effects/etc only in the appropriate instance + idEntity* currentThinkingEntity; + + const static int INITIAL_SPAWN_COUNT = 1; + +// RAVEN END + + int filterMod; + idList modList; + + int nextLagoCheck; + + // ---------------------- Public idGame Interface ------------------- + + idGameLocal(); + +// RAVEN BEGIN +// jsinger: attempt to eliminate cross-DLL allocation issues +#ifdef RV_UNIFIED_ALLOCATOR + virtual void Init( void *(*allocator)( size_t size ), void (*deallocator)( void *ptr ), size_t (*msize)( void *ptr ) ); +#else + virtual void Init( void ); +#endif +// RAVEN END + virtual void Shutdown( void ); + virtual void SetLocalClient( int clientNum ); + virtual void ThrottleUserInfo( void ); + virtual const idDict * SetUserInfo( int clientNum, const idDict &userInfo, bool isClient ); + virtual const idDict * GetUserInfo( int clientNum ); + virtual bool IsClientActive( int clientNum ); + virtual void SetServerInfo( const idDict &serverInfo ); + + virtual const idDict * RepeaterSetUserInfo( int clientNum, const idDict &userInfo ); + + virtual const idDict & GetPersistentPlayerInfo( int clientNum ); + virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ); + virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, bool isServer, bool isClient, int randSeed ); + virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idFile *saveGameFile ); +// RAVEN BEGIN +// mekberg: added saveTypes + virtual void SaveGame( idFile *saveGameFile, saveType_t saveType = ST_REGULAR ); +// RAVEN END + virtual void MapShutdown( void ); + virtual void CacheDictionaryMedia( const idDict *dict ); + virtual void SpawnPlayer( int clientNum ); + virtual gameReturn_t RunFrame( const usercmd_t *clientCmds, int activeEditors, bool lastCatchupFrame, int serverGameFrame ); + virtual void MenuFrame( void ); + virtual void RepeaterFrame( const userOrigin_t *clientOrigins, bool lastCatchupFrame, int serverGameFrame ); + virtual bool Draw( int clientNum ); + virtual escReply_t HandleESC( idUserInterface **gui ); + virtual idUserInterface *StartMenu( void ); + virtual const char * HandleGuiCommands( const char *menuCommand ); + virtual void HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ); + virtual allowReply_t ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ); + virtual void ServerClientConnect( int clientNum, const char *guid ); + virtual void ServerClientBegin( int clientNum ); + virtual void ServerClientDisconnect( int clientNum ); + virtual void ServerWriteInitialReliableMessages( int clientNum ); + virtual allowReply_t RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] ); + virtual void RepeaterClientConnect( int clientNum ); + virtual void RepeaterClientBegin( int clientNum ); + virtual void RepeaterClientDisconnect( int clientNum ); + virtual void RepeaterWriteInitialReliableMessages( int clientNum ); + virtual void WriteSnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], idMsgQueue &unreliable, int sequence, idBitMsg &msg, int transmitEntity, int transmitEntity2, int instance, bool doPVS, const idBounds &pvs_bounds, int lastSnapshotFrame ); + virtual void ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ); + virtual bool ServerApplySnapshot( int clientNum, int sequence ); + virtual void ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual bool RepeaterApplySnapshot( int clientNum, int sequence ); + virtual void RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual void ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ); + virtual bool ClientApplySnapshot( int clientNum, int sequence ); + virtual void ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ); + virtual gameReturn_t ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame = true, ClientStats_t *cs = NULL ); +// RAVEN BEGIN +// ddynerman: client game frame + virtual void ClientRun( void ); + virtual void ClientEndFrame( void ); + +// jshepard: rcon password check + virtual void ProcessRconReturn( bool success ); + virtual void ResetRconGuiStatus( void ); +// RAVEN END + + virtual void GetClientStats( int clientNum, char *data, const int len ); + virtual void SwitchTeam( int clientNum, int team ); + + virtual bool DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ); + + virtual bool HTTPRequest( const char *IP, const char *file, bool isGamePak ); + +// RAVEN BEGIN +// bdube: client hitscan + virtual void ClientHitScan( const idBitMsg &msg ); +// jscott: for effects system + virtual void StartViewEffect( int type, float time, float scale ); + virtual void GetPlayerView( idVec3 &origin, idMat3 &axis ); + virtual void Translation( trace_t &trace, idVec3 &source, idVec3 &dest, idTraceModel *trm, int clipMask ); + virtual void SpawnClientMoveable ( const char* name, int lifetime, const idVec3& origin, const idMat3& axis, const idVec3& velocity, const idVec3& angular_velocity ); +// bdube: added debug methods + virtual void DebugSetString ( const char* name, const char* value ); + virtual void DebugSetFloat ( const char* name, float value ); + virtual void DebugSetInt ( const char* name, int value ); + virtual const char* DebugGetStatString ( const char* name ); + virtual int DebugGetStatInt ( const char* name ); + virtual float DebugGetStatFloat ( const char* name ); + virtual bool IsDebugHudActive ( void ) const; +// rjohnson: for new note taking mechanism + virtual bool GetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1, idAngles *deltaViewAngles = NULL, int reqClientNum = -1 ); + virtual void SetPlayerInfo( idVec3 &origin, idMat3 &axis, int PlayerNum = -1 ); + virtual bool PlayerChatDisabled( int clientNum ); + virtual void SetViewComments( const char *text = 0 ); +// ddynerman: utility functions + virtual void GetPlayerName( int clientNum, char* name ); + virtual void GetPlayerClan( int clientNum, char* clan ); + virtual void SetFriend( int clientNum, bool isFriend ); + static void Cmd_PrintMapEntityNumbers_f( const idCmdArgs& args ); + static void Cmd_PrintSpawnIds_f( const idCmdArgs& args ); +// abahr: + virtual int GetNumGravityAreas() const; + virtual const rvGravityArea* GetGravityInfo( int index ) const; + virtual void SetGravityInfo( int index, rvGravityArea* info ); + virtual void AddUniqueGravityInfo( rvGravityArea* info ); + + virtual int GetCurrentGravityInfoIndex( const idVec3& origin ) const; + virtual bool InGravityArea( idEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( idEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( idEntity* entity ) const; + + virtual const idVec3 GetCurrentGravity( const idVec3& origin, const idMat3& axis ) const; + + virtual bool InGravityArea( rvClientEntity* entity ) const; + virtual int GetCurrentGravityInfoIndex( rvClientEntity* entity ) const; + virtual const idVec3 GetCurrentGravity( rvClientEntity* entity ) const; + virtual idEntity* ReferenceScriptObjectProxy( const char* scriptObjectName ); + virtual void ReleaseScriptObjectProxy( const char* proxyName ); + +// rjohnson: entity usage stats + virtual void ListEntityStats( const idCmdArgs &args ); +// RAVEN END + + virtual void SetDemoState( demoState_t state, bool serverDemo, bool timeDemo ); + virtual void SetRepeaterState( bool isRepeater, bool serverIsRepeater ); + virtual void WriteNetworkInfo( idFile* file, int clientNum ); + virtual void ReadNetworkInfo( int gameTime, idFile* file, int clientNum ); + virtual bool ValidateDemoProtocol( int minor_ref, int minor ); + + virtual void ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ); + virtual void ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ); + + virtual void RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ); + virtual void RepeaterEndSnapshots( void ); + virtual void ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ); + + virtual int GetDemoFollowClient( void ) { return IsServerDemoPlaying() ? followPlayer : -1; } + + virtual void GetBotInput( int clientNum, usercmd_t &userCmd ) { Error( "Bot input requested\n" ); }; + + virtual const char * GetLoadingGui( const char *mapDeclName ) { return NULL; } + virtual void SetupLoadingGui( idUserInterface *gui ) {} + + // ---------------------- Public idGameLocal Interface ------------------- + + void Printf( const char *fmt, ... ) const; + void DPrintf( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + void DWarning( const char *fmt, ... ) const; + void Error( const char *fmt, ... ) const; + + // Initializes all map variables common to both save games and spawned games + void LoadMap( const char *mapName, int randseed ); + + void LocalMapRestart( int instance = -1 ); + void MapRestart( int instance = -1 ); + static void VerifyServerSettings_f( const idCmdArgs &args ); + static void MapRestart_f( const idCmdArgs &args ); + bool NextMap( void ); // returns wether serverinfo settings have been modified + static void NextMap_f( const idCmdArgs &args ); + + idMapFile * GetLevelMap( void ); + const char * GetMapName( void ) const; + + int NumAAS( void ) const; + idAAS * GetAAS( int num ) const; + idAAS * GetAAS( const char *name ) const; +// RAVEN BEGIN +// jscott: added accessor for memory tracking + int GetNumAAS( void ) const { return( aasList.Num() ); } +// RAVEN END + void SetAASAreaState( const idBounds &bounds, const int areaContents, bool closed ); + aasHandle_t AddAASObstacle( const idBounds &bounds ); + void RemoveAASObstacle( const aasHandle_t handle ); + void RemoveAllAASObstacles( void ); +// RAVEN BEGIN +// mwhitlock: added entity memory usage stuff. + size_t GetEntityMemoryUsage ( void ) const; +// RAVEN END + bool CheatsOk( bool requirePlayer = true ); + void SetSkill( int value ); + gameState_t GameState( void ) const; + void SetGameState( gameState_t newState ) { gamestate = newState; } + idEntity * SpawnEntityType( const idTypeInfo &classdef, const idDict *args = NULL, bool bIsClientReadSnapshot = false ); + bool SpawnEntityDef( const idDict &args, idEntity **ent = NULL, bool setDefaults = true ); + bool SpawnClientEntityDef( const idDict &args, rvClientEntity **ent = NULL, bool setDefaults = true, const char* spawn = NULL ); +// abahr: + idEntity* SpawnEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + template< class type > + type* SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs = NULL ); + int GetPreviousTime() const { return previousTime; } +// RAVEN END + int GetSpawnId( const idEntity *ent ) const; + + const idDeclEntityDef * FindEntityDef( const char *name, bool makeDefault = true ) const; + const idDict * FindEntityDefDict( const char *name, bool makeDefault = true ) const; + + void RegisterEntity( idEntity *ent ); + void UnregisterEntity( idEntity *ent ); + // used to skip one when registering entities, leaving an empty entity in the array + void SkipEntityIndex( void ); + + bool RequirementMet( idEntity *activator, const idStr &requires, int removeItem ); + +// RITUAL BEGIN +// squirrel: accessor for si_weaponStay checks + bool IsWeaponsStayOn( void ); +// RITUAL END + +// RAVEN BEGIN +// bdube: client entities + void RegisterClientEntity( rvClientEntity *cent ); + void UnregisterClientEntity( rvClientEntity *cent ); +// RAVEN END + + void AlertAI( idEntity *ent ); +// RAVEN BEGIN +// bdube: added get alert actor + idActor * GetAlertActor( void ); + idEntity * GetAlertEntity( void ); +// RAVEN END + + + bool InPlayerPVS( idEntity *ent ) const; + bool InPlayerConnectedArea( idEntity *ent ) const; + + void SetCamera( idCamera *cam ); + idCamera * GetCamera( void ) const; + bool SkipCinematic( void ); + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *GetPortalSky( void ) const; + void SetPortalSky( idCamera *cam ); +// RAVEN END + + void CalcFov( float base_fov, float &fov_x, float &fov_y ) const; + + void AddEntityToHash( const char *name, idEntity *ent ); + bool RemoveEntityFromHash( const char *name, idEntity *ent ); + int GetTargets( const idDict &args, idList< idEntityPtr > &list, const char *ref ) const; + + // returns the master entity of a trace. for example, if the trace entity is the player's head, it will return the player. + idEntity * GetTraceEntity( const trace_t &trace ) const; + + static void ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ); + idEntity * FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const; + static void ArgCompletion_AIName( const idCmdArgs &args, void(*callback)( const char *s ) ); + +//RAVEN BEGIN +// bgeisler: added, I don't want to have to do this work myself every single time I have an entityNumber + idEntity * FindEntity( int entityNumber ) { return ((entityNumber >= 0 && entityNumber < MAX_GENTITIES) ? entities[entityNumber] : NULL); } +//RAVEN BEGIN + + idEntity * FindEntity( const char *name ) const; + idEntity * FindEntityUsingDef( idEntity *from, const char *match ) const; + int EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const; + + void KillBox( idEntity *ent, bool catch_teleport = false ); + void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); +// RAVEN BEGIN +// ddynerman: return number of people damaged + void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f, int* hitCount = NULL ); +// bdube: inflictor + void RadiusPushClipModel( idEntity* inflictor, const idVec3 &origin, const float push, const idClipModel *clipModel ); +// RAVEN END + + void ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle = 0 ); +// RAVEN BEGIN +// ddynerman: multiple collision worlds + void BloodSplat( const idEntity* ent, const idVec3 &origin, const idVec3 &dir, float size, const char *material ); +// RAVEN END + + void CallFrameCommand( idEntity *ent, const function_t *frameCommand ); +// RAVEN BEGIN +// bdube: added script object frame commands + void CallFrameCommand( idScriptObject* obj, const function_t* frameCommand ); + void CallFrameCommand( idEntity* ent, const char* frameCommand ); +// RAVEN END + + void CallObjectFrameCommand( idEntity *ent, const char *frameCommand ); + + const idVec3 & GetGravity( void ) const; + + // added the following to assist licensees with merge issues + int GetFrameNum() const { return framenum; } + int GetTime() const { return time; } + int GetMSec() const { return msec; } + int GetMHz() const { return mHz; } + + int GetNextClientNum( int current ) const; + idPlayer * GetClientByNum( int current ) const; + idPlayer * GetClientByName( const char *name ) const; + idPlayer * GetClientByCmdArgs( const idCmdArgs &args ) const; + int GetClientNumByName( const char *name ) const; + + idPlayer * GetLocalPlayer() const; + +// RAVEN BEGIN +// jshepard: update player data after main menu close + void UpdatePlayerPostMainMenu(); + +// bdube: added + int GetSpawnCount ( void ) const; + void SetSpawnCount ( int newSpawnCount ) { spawnCount = newSpawnCount; } +// ddynerman: team type + bool IsTeamGame ( void ) const; +// RAVEN END + + void SpreadLocations(); + idLocationEntity * LocationForPoint( const idVec3 &point ); // May return NULL +// RAVEN BEGIN +// bdube: added + idLocationEntity* AddLocation ( const idVec3& point, const char* name ); +// ddynerman: new gametype specific spawn code + bool SpotWouldTelefrag( idPlayer* player, idPlayerStart* spawn ); + idEntity* SelectSpawnPoint( idPlayer* player ); + void UpdateForwardSpawns( rvCTFAssaultPlayerStart* point, int team ); + void ClearForwardSpawns( void ); +// RAVEN END + + void SetPortalState( qhandle_t portal, int blockingBits ); + void ServerSendChatMessage( int to, const char *name, const char *text, const char *parm = "" ); + + void SetGlobalMaterial( const idMaterial *mat ); + const idMaterial * GetGlobalMaterial(); + + void SetGibTime( int _time ) { nextGibTime = _time; } + int GetGibTime() { return nextGibTime; } +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + void SetUnFreezeTime( int _time ) { unFreezeTime = _time; }; + int GetUnFreezeTime() { return unFreezeTime; }; + void SetIsFrozen( bool _isFrozen ) { isFrozen = _isFrozen; }; + bool GetIsFrozen() { return isFrozen; }; +// RITUAL END + bool NeedRestart(); + +// RAVEN BEGIN +// jshepard: update end of level on player hud + void UpdateEndLevel(); +// MCG: added whizz-by sound + void CheckPlayerWhizzBy ( idVec3 start, idVec3 end, idEntity* hitEnt, idEntity *attacker ); +// bdube: added hitscan +// twhitaker: added additionalIgnore parameter + idEntity* HitScan ( const idDict& hitscanDef, const idVec3& origin, const idVec3& dir, const idVec3& fxOrigin, idEntity* owner = NULL, bool noFX = false, float damageScale = 1.0f, idEntity * additionalIgnore = NULL, int *areas = NULL ); +// bdube: added effect calls + virtual rvClientEffect* PlayEffect ( const idDecl *effect, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictedBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + rvClientEffect* PlayEffect ( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false, bool predictedBit = false, effectCategory_t category = EC_IGNORE, const idVec4& effectTint = vec4_one ); + const idDecl *GetEffect ( const idDict& args, const char* effectName, const rvDeclMatType* materialType = NULL ); + + void UpdateRepeaterInfo( bool transmit = false ); + + idList ambientLights; // lights that cast ambient + +// ddynerman: multiple collision world - game collision wrapper functions to +// use the correct idClip +// --------------------------------------------------------------- +// These are wrapper functions around idClip collision detection +// functions. They expose the collision detection engine to the +// game code, but do collision world determination in one spot. +// 'ent' refers to the entity we want collision information about + bool Translation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + bool Rotation ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contents ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); + // special case translations versus the rest of the world + bool TracePoint ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ); + bool TraceBounds ( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ); + // clip versus a specific model + void TranslationModel( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel ( const idEntity* ent, trace_t &results, const idVec3 &start, const idRotation &rotation, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel ( const idEntity* ent, contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel ( const idEntity* ent, const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + // clip versus all entities but not the world + void TranslationEntities( const idEntity* ent, trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); + // get a contact feature + bool GetModelContactFeature( const idEntity* ent, const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idEntity* ent, const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + int PlayersTouchingBounds ( const idEntity* ent, const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; + const idBounds & GetWorldBounds( const idEntity* ent ) const; + + void Link( idClipModel* clip, idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); + + idClip* GetEntityClipWorld( const idEntity* ent ); + const idClip* GetEntityClipWorld( const idEntity* ent ) const; + int GetNumMapEntities( void ) const; + + int AddClipWorld( int id ); + void RemoveClipWorld( int id ); + int AddInstance( int id = -1, bool deferPopulate = false ); + void RemoveInstance( int id ); + rvInstance* GetInstance( int id ); + int GetNumInstances( void ); +// ddynerman: multiple game instances + void SpawnMapEntities( int instance = 0, unsigned short* entityNumIn = NULL, unsigned short* entityNumOut = NULL, int* startSpawnCount = NULL ); + void InstanceClear( void ); +// ddynerman: utility function + virtual const char* GetLongGametypeName( const char* gametype ); + virtual void ReceiveRemoteConsoleOutput( const char* output ); + + bool IsFlagGameType( void ) { return ( gameType == GAME_CTF || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_ARENA_1F_CTF ); } + bool IsTeamGameType( void ) { return ( gameType == GAME_TDM || gameType == GAME_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ); } + bool IsTeamPowerups( void ); + + // twhitaker: needed this for difficulty settings + float GetDifficultyModifier( void ) { const static float difficulty[] = { -0.3f, 0.0f, 0.4f, 0.8f }; return difficulty[ idMath::ClampInt( 0, 3, g_skill.GetInteger() ) ]; } + + bool IsMultiplayer( void ) { return isMultiplayer; } + +// mekberg: added + bool InCinematic( void ) { return inCinematic; } + + // mekberg: so ban list can be populated outside of multiplayer game + void PopulateBanList( idUserInterface* hud ); +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushBeforelevelLoad( void ); +#endif +// RAVEN END + + void ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ); + void ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ); + + void SendUnreliableMessage( const idBitMsg &msg, const int clientNum ); + // note: listen server client is always excluded + void SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1 = -1, int area2 = -1 ); + + void RepeaterAppendUnreliableMessage( int icl, const idBitMsg &msg, const idBitMsg *header = NULL ); + void RepeaterUnreliableMessage( const idBitMsg &msg, const int clientNum, const idBitMsg *header = NULL ); + void RepeaterUnreliableMessagePVS( const idBitMsg &msg, const int *areas, int numAreas, const idBitMsg *header = NULL ); + + demoState_t GetDemoState( void ) const { return demoState; } + bool IsServerDemoPlaying( void ) const { return (demoState == DEMO_PLAYING && serverDemo) || serverIsRepeater; } + bool IsServerDemoRecording( void ) const { return demoState == DEMO_RECORDING && serverDemo; } + bool IsRepeaterDemoPlaying( void ) const { return (demoState == DEMO_PLAYING && !serverDemo) && serverIsRepeater; } + bool IsTimeDemo( void ) const { return timeDemo; } + + /* + do not synchronize implicit decls over the network + implicit decls are created when loading sounds and materials that don't have an explicit entry in the def files + clients and server may load those in different orders in a same map, or even join in with different orders because of different map histories before this game + in D3 we maintain remap tables, but it's much better to have tighter multiplayer assets so we have no need at all + still, you want to catch when bad things happen, so indexes should ALL be read and written through these functions + */ + static void WriteDecl( idBitMsg &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsg &msg, declType_t type ); + static void WriteDecl( idBitMsgDelta &msg, const idDecl *decl ); + static const idDecl* ReadDecl( const idBitMsgDelta &msg, declType_t type ); + + idPlayerStart *RandomSpawn( void ); + + int GetStartingIndexForInstance( int instanceID ); + void ClientSetStartingIndex( int i ) { clientInstanceFirstFreeIndex = i; } + void ServerSetMinSpawnIndex( void ); + void ServerSetEntityIndexWatermark( int instanceID ); + + idLagometer lagometer; +private: +// RAVEN BEGIN +// ddynerman: multiple instance for MP + idList clip; // collision detection + idList instances; +// RAVEN END + + // keep watermarks on the high entity index + // server transmits this to clients so they use the right entity layout + idList instancesEntityIndexWatermarks; + int clientInstanceFirstFreeIndex; + + idStr mapFileName; // name of the map, empty string if no map loaded + idMapFile * mapFile; // will be NULL during the game unless in-game editing is used + bool mapCycleLoaded; + int incompatibleMaps; + + int spawnCount; + bool isMapEntity[ MAX_GENTITIES ]; // it's handy to know which entities are part of the map +// RAVEN BEGIN +// bdube: client entities + int clientSpawnCount; +// RAVEN END + + idLocationEntity ** locationEntities; // for location names, etc + + idCamera * camera; + const idMaterial * globalMaterial; // for overriding everything + +// RAVEN BEGIN +// jscott: for portal skies + idCamera *portalSky; + bool portalSkyVisible; +// RAVEN END + + idList aasList; // area system + idStrList aasNames; + +// RAVEN BEGIN +// bdube: GetAlertActor + idEntityPtr lastAIAlertActor; + int lastAIAlertActorTime; + idEntityPtr lastAIAlertEntity; + int lastAIAlertEntityTime; +// RAVEN END + + idDict spawnArgs; // spawn args used during entity spawning FIXME: shouldn't be necessary anymore +// RAVEN BEGIN +// nmckenzie: + const idDeclEntityDef * spawnOverrides; +// RAVEN END + + pvsHandle_t playerPVS; // merged pvs of all players + bool freePlayerPVS; // tracks if playerPVS needs to be released + pvsHandle_t playerConnectedAreas; // all areas connected to any player area + + idVec3 gravity; // global gravity vector + gameState_t gamestate; // keeps track of whether we're spawning, shutting down, or normal gameplay + bool influenceActive; // true when a phantasm is happening + int nextGibTime; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int unFreezeTime; // time at which players unfreeze and the match begins + bool isFrozen; // true if the match is frozen (for buying, etc.) +// RITUAL END + + entityState_t * clientEntityStates[MAX_CLIENTS+1][MAX_GENTITIES]; // MAX_CLIENTS slot is for server demo recordings + int clientPVS[MAX_CLIENTS+1][ENTITY_PVS_SIZE]; + snapshot_t * clientSnapshots[MAX_CLIENTS+1]; + + idList privateViewerIds, nopvsViewerIds; + + entityState_t * (*viewerEntityStates)[MAX_GENTITIES]; // MAX_CLIENTS slot is for server demo recordings + int (*viewerPVS)[ENTITY_PVS_SIZE]; + snapshot_t * (*viewerSnapshots); + + idMsgQueue (*viewerUnreliableMessages); + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc + idBlockAlloc entityStateAllocator; + idBlockAlloc snapshotAllocator; +// RAVEN END + + idEventQueue eventQueue; + + idList spawnSpots; +// RAVEN BEGIN +// ddynerman: two lists to hold team spawn points for team based games + idList teamSpawnSpots[TEAM_MAX]; + idList teamForwardSpawnSpots[TEAM_MAX]; // forward spawn positions, used in CTF +// RAVEN END + + idDict newInfo; + + idStrList shakeSounds; + + idMsgQueue unreliableMessages[ MAX_CLIENTS+1 ]; // MAX_CLIENTS slot for server demo recording + + demoState_t demoState; + bool serverDemo; + bool timeDemo; + bool serverIsRepeater; + + // demo interaction usercmds + usercmd_t usercmd, oldUsercmd; + int followPlayer; // free fly or spectate follow through local interaction during server demo replays + + int demo_protocol; // keep track of the protocol of the demo we're replaying + +private: + + void Clear( void ); + // returns true if the entity shouldn't be spawned at all in this game type or difficulty level + bool InhibitEntitySpawn( idDict &spawnArgs ); + // spawn entities from the map file + // commons used by init, shutdown, and restart + void MapPopulate( int instance = -1 ); + void MapClear( bool clearClients, int instance = -1 ); + + bool SetupPortalSkyPVS( idPlayer *player ); +// RAVEN END + void SetupPlayerPVS( void ); + void FreePlayerPVS( void ); + void UpdateGravity( void ); + void SortActiveEntityList( void ); + void ShowTargets( void ); + void RunDebugInfo( void ); + + void InitScriptForMap( void ); + void InitConsoleCommands( void ); + void ShutdownConsoleCommands( void ); + + void InitAsyncNetwork( void ); + void ShutdownAsyncNetwork( void ); + void InitLocalClient( int clientNum ); + void FreeSnapshotsOlderThanSequence( snapshot_t *&clientSnapshot, int sequence ); + void FreeSnapshotsOlderThanSequence( int clientNum, int sequence ); + bool ApplySnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], int sequence ); + bool ApplySnapshot( int clientNum, int sequence ); + void WriteGameStateToSnapshot( idBitMsgDelta &msg ) const; + void ReadGameStateFromSnapshot( const idBitMsgDelta &msg ); + void NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) id_attribute((format(printf,3,4))); + void ServerProcessEntityNetworkEventQueue( void ); + void ClientProcessEntityNetworkEventQueue( void ); + void ClientShowSnapshot( int clientNum ) const; + void SetGameType( void ); +// RAVEN BEGIN +// ddynerman: gametype specific spawn code + void InitializeSpawns( void ); + idList WeightSpawnSpots( idPlayer* player ); +// RAVEN END + static int sortSpawnPoints( const void *ptr1, const void *ptr2 ); + + void DumpOggSounds( void ); + void GetShakeSounds( const idDict *dict ); + bool ValidateServerSettings( const char *map, const char *gametype ); + + void Tokenize( idStrList &out, const char *in ); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void ShutdownInstances( void ); +// shouchard: ban list support (lives in game_network.cpp) + + void ClientReadUnreliableMessages( const idBitMsg &msg ); + void ProcessUnreliableMessage( const idBitMsg &msg ); + + void UpdateClientsPVS( void ); + + bool IsDemoReplayInAreas( const int areas[2], int numAreas ); + + void ReallocViewers( int newMaxViewers ); + + void BuildModList( void ); + +public: + void LoadBanList(); + void SaveBanList(); + void FlushBanList(); + bool IsPlayerBanned( const char *name ); + bool IsGuidBanned( const char *guid ); + void AddGuidToBanList( const char *guid ); + void RemoveGuidFromBanList( const char *guid ); + virtual void RegisterClientGuid( int clientNum, const char *guid ); + + int GetBanListCount(); + const mpBanInfo_t* GetBanListEntry( int entry ); // returns client name + const char* GetGuidByClientNum( int clientNum ); // returns GUID + int GetClientNumByGuid( const char* ); // returns clientNum + +// mekberg: get and send ban list + void ServerSendBanList( int clientNum ); + +// jscott: made public + pvsHandle_t GetClientPVS( idPlayer *player, pvsType_t type ); + + int GetCurrentDemoProtocol( void ) { return demo_protocol; } + +private: + char clientGuids[ MAX_CLIENTS ][ CLIENT_GUID_LENGTH ]; + idList banList; + bool banListLoaded; + bool banListChanged; +// RAVEN END +}; + +//============================================================================ + +extern idGameLocal gameLocal; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer to prevent it from allocating memory +// before the unified allocator is initialized +extern idAnimManager *animationLib; +// RAVEN END + +//============================================================================ + +ID_INLINE void idGameLocal::WriteDecl( idBitMsg &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsg &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +ID_INLINE void idGameLocal::WriteDecl( idBitMsgDelta &msg, const idDecl *decl ) { + assert( decl ); + if ( decl->IsImplicit() ) { + gameLocal.Error( "WriteDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( decl->GetType() ), decl->GetName(), decl->Index() ); + } + msg.WriteLong( decl->Index() ); +} + +ID_INLINE const idDecl* idGameLocal::ReadDecl( const idBitMsgDelta &msg, declType_t type ) { + int index = msg.ReadLong(); + const idDecl *decl = declManager->DeclByIndex( type, index ); + if ( !decl ) { + gameLocal.Error( "ReadDecl: NULL %s decl at index %d", declManager->GetDeclNameFromType( type ), index ); + } + if ( decl->IsImplicit() ) { + gameLocal.Error( "ReadDecl: %s decl %s ( index %d ) is implicit", declManager->GetDeclNameFromType( type ), decl->GetName(), decl->Index() ); + } + return decl; +} + +//============================================================================ + +class idGameError : public idException { +public: + idGameError( const char *text ) : idException( text ) {} +}; + +//============================================================================ + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER) +#define MASK_OPAQUE (CONTENTS_OPAQUE|CONTENTS_SIGHTCLIP) +#define MASK_SHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL) +#define MASK_SHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY) +#define MASK_LARGESHOT_RENDERMODEL (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_LARGESHOTCLIP) +#define MASK_LARGESHOT_BOUNDINGBOX (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_LARGESHOTCLIP) +#define MASK_DMGSOLID (CONTENTS_SOLID|CONTENTS_LARGESHOTCLIP) + +// RAVEN BEGIN +// creed: added monster clip +#define MASK_MONSTERCLIP (CONTENTS_SOLID|CONTENTS_MONSTERCLIP) +// RAVEN END + +const float DEFAULT_GRAVITY = 1066.0f; +const float DEFAULT_GRAVITY_MP = 800.0f; + +#define DEFAULT_GRAVITY_STRING "1066" +#define DEFAULT_MP_GRAVITY_STRING "800" +const idVec3 DEFAULT_GRAVITY_VEC3( 0, 0, -DEFAULT_GRAVITY ); + +const int CINEMATIC_SKIP_DELAY = SEC2MS( 2.0f ); + +//============================================================================ + +#include "physics/Force.h" +#include "physics/Force_Constant.h" +#include "physics/Force_Drag.h" +#include "physics/Force_Field.h" +#include "physics/Force_Spring.h" +#include "physics/Physics.h" +#include "physics/Physics_Static.h" +#include "physics/Physics_StaticMulti.h" +#include "physics/Physics_Base.h" +#include "physics/Physics_Actor.h" +#include "physics/Physics_Monster.h" +#include "physics/Physics_Player.h" +#include "physics/Physics_Parametric.h" +#include "physics/Physics_RigidBody.h" +#include "physics/Physics_AF.h" +#include "physics/Physics_Particle.h" +#include "physics/Physics_VehicleMonster.h" + +#include "vehicle/VehicleController.h" + +#include "Entity.h" +#include "Game_Debug.h" +#include "IconManager.h" +#include "GameEdit.h" +#include "AF.h" +#include "IK.h" +#include "AFEntity.h" +#include "Misc.h" +#include "Actor.h" + +// client entities +#include "client/ClientEntity.h" +#include "client/ClientEffect.h" +#include "client/ClientMoveable.h" +#include "client/ClientModel.h" +#include "client/ClientAFEntity.h" + +#include "Weapon.h" + +#include "script/ScriptFuncUtility.h" + +#include "Light.h" +#include "WorldSpawn.h" +#include "Item.h" +#include "PlayerView.h" +// TTimo: moved AI.h up, can't do template instanciation on forward declared-classes +#include "ai/AI.h" +#include "Player.h" +#include "Mover.h" +// RAVEN BEGIN +// abahr: +#include "vehicle/VehicleParts.h" +#include "vehicle/Vehicle.h" +#include "SplineMover.h" +#include "TramGate.h" +#include "vehicle/VehicleDriver.h" +// RAVEN END +#include "Camera.h" +#include "Moveable.h" +#include "Target.h" +#include "Trigger.h" +#include "Sound.h" +#include "SecurityCamera.h" +#include "BrittleFracture.h" + +// RAVEN BEGIN +// nmckenzie: Reduce dependencies. +#include "mp/CTF.h" +#include "mp/stats/StatManager.h" +#include "mp/Tourney.h" +#include "Instance.h" +// RAVEN END +#include "anim/Anim_Testmodel.h" + +// RAVEN BEGIN +// jscott: for lip syncing +#include "LipSync.h" +// RAVEN END + +#include "script/Script_Compiler.h" +#include "script/Script_Interpreter.h" +#include "script/Script_Thread.h" + +#ifdef _XENON +#define PACIFIER_UPDATE session->PacifierUpdate() +#else +#define PACIFIER_UPDATE +#endif + +ID_INLINE rvClientEffect* idGameLocal::PlayEffect( const idDict& args, const char* effectName, const idVec3& origin, const idMat3& axis, bool loop, const idVec3& endOrigin, bool broadcast, bool predictBit, effectCategory_t category, const idVec4& effectTint ) { + return PlayEffect( GetEffect( args, effectName ), origin, axis, loop, endOrigin, broadcast, predictBit, category, effectTint ); +} + +ID_INLINE bool idGameLocal::IsTeamGame( void ) const { + return ( isMultiplayer && ( gameType == GAME_CTF || gameType == GAME_TDM || gameType == GAME_1F_CTF || gameType == GAME_ARENA_CTF || gameType == GAME_DEADZONE ) ); +} + +ID_INLINE int idGameLocal::GetNumMapEntities( void ) const { + if ( mapFile == NULL ) { + return -1; + } else { + return mapFile->GetNumEntities(); + } +} + +ID_INLINE rvInstance* idGameLocal::GetInstance( int id ) { + return instances[ id ]; +} + +ID_INLINE int idGameLocal::GetNumInstances( void ) { + return instances.Num(); +} + +ID_INLINE void idGameLocal::ReceiveRemoteConsoleOutput( const char* output ) { + if( isMultiplayer ) { + mpGame.ReceiveRemoteConsoleOutput( output ); + } +} + +template< class type > +type* idGameLocal::SpawnSafeEntityDef( const char* entityDefName, const idDict* additionalArgs ) { + idEntity* entity = SpawnEntityDef( entityDefName, additionalArgs ); + if( !entity ) { + return NULL; + } + + if( !entity->IsType(type::GetClassType()) ) { + entity->PostEventMS( &EV_Remove, 0 ); + return NULL; + } + + return static_cast( entity ); +} + +template< class type > +ID_INLINE idEntityPtr::idEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE void idEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void idEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +template< class type > +ID_INLINE idEntityPtr &idEntityPtr::operator=( type *ent ) { + if ( ent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.spawnIds[ent->entityNumber] << GENTITYNUM_BITS ) | ent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool idEntityPtr::SetSpawnId( int id ) { + if ( ( id >> GENTITYNUM_BITS ) == gameLocal.spawnIds[ id & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool idEntityPtr::IsValid( void ) const { + return ( gameLocal.spawnIds[ spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] == ( spawnId >> GENTITYNUM_BITS ) ); +} + +template< class type > +ID_INLINE type *idEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.spawnIds[ entityNum ] == ( spawnId >> GENTITYNUM_BITS ) ) ) { + return static_cast( gameLocal.entities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int idEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ); +} + +// RAVEN BEGIN +// bdube: overloaded operator +template< class type > +ID_INLINE type * idEntityPtr::operator->( void ) const { + return GetEntity ( ); +} + +template< class type > +ID_INLINE idEntityPtr::operator type * ( void ) const { + return GetEntity(); +} +// RAVEN END + +#include "../idlib/containers/ListGame.h" + +#endif /* !__GAME_LOCAL_H__ */ diff --git a/src/mpgame/Game_network.cpp b/src/mpgame/Game_network.cpp new file mode 100644 index 0000000..4a428ee --- /dev/null +++ b/src/mpgame/Game_network.cpp @@ -0,0 +1,4002 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +// RAVEN BEGIN +// bdube: client entities +#include "client/ClientEffect.h" +// shouchard: ban list support +#define BANLIST_FILENAME "banlist.txt" +// RAVEN END + +/* +=============================================================================== + + Client running game code: + - entity events don't work and should not be issued + - entities should never be spawned outside idGameLocal::ClientReadSnapshot + +=============================================================================== +*/ + +// adds tags to the network protocol to detect when things go bad ( internal consistency ) +// NOTE: this changes the network protocol +#ifndef ASYNC_WRITE_TAGS + #define ASYNC_WRITE_TAGS 1 +#endif + +idCVar net_clientShowSnapshot( "net_clientShowSnapshot", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar net_clientShowSnapshotRadius( "net_clientShowSnapshotRadius", "128", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar net_clientMaxPrediction( "net_clientMaxPrediction", "1000", CVAR_SYSTEM | CVAR_INTEGER | CVAR_NOCHEAT, "maximum number of milliseconds a client can predict ahead of server." ); +idCVar net_clientLagOMeter( "net_clientLagOMeter", "0", CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT | PC_CVAR_ARCHIVE, "draw prediction graph" ); +idCVar net_clientLagOMeterResolution( "net_clientLagOMeterResolution", "16", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT | PC_CVAR_ARCHIVE, "resolution for predict ahead graph (upper part of lagometer)", 1, 100 ); + +// RAVEN BEGIN +// ddynerman: performance profiling +int net_entsInSnapshot; +int net_snapshotSize; + +extern const int ASYNC_PLAYER_FRAG_BITS; +// RAVEN END + +// message senders +idNullMessageSender nullSender; +idServerReliableMessageSender serverReliableSender; +idRepeaterReliableMessageSender repeaterReliableSender; + +/* +================ +idGameLocal::InitAsyncNetwork +================ +*/ +void idGameLocal::InitAsyncNetwork( void ) { + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + memset( viewerEntityStates, 0, sizeof( *viewerEntityStates ) * maxViewers ); + memset( viewerPVS, 0, sizeof( *viewerPVS ) * maxViewers ); + memset( viewerSnapshots, 0, sizeof( *viewerSnapshots ) * maxViewers ); + memset( viewerUnreliableMessages, 0, sizeof (*viewerUnreliableMessages) * maxViewers ); + + eventQueue.Init(); + + // we used to have some special case -1 value, but we don't anymore so this is always >= 0 now + entityDefBits = idMath::BitsForInteger( declManager->GetNumDecls( DECL_ENTITYDEF ) ) + 1; + localClientNum = 0; // on a listen server SetLocalUser will set this right + realClientTime = 0; + isNewFrame = true; + + nextLagoCheck = 0; +} + +/* +================ +idGameLocal::ShutdownAsyncNetwork +================ +*/ +void idGameLocal::ShutdownAsyncNetwork( void ) { + entityStateAllocator.Shutdown(); + snapshotAllocator.Shutdown(); + eventQueue.Shutdown(); + memset( clientEntityStates, 0, sizeof( clientEntityStates ) ); + memset( clientPVS, 0, sizeof( clientPVS ) ); + memset( clientSnapshots, 0, sizeof( clientSnapshots ) ); + + memset( viewerEntityStates, 0, sizeof( *viewerEntityStates ) * maxViewers ); + memset( viewerPVS, 0, sizeof( *viewerPVS ) * maxViewers ); + memset( viewerSnapshots, 0, sizeof( *viewerSnapshots ) * maxViewers ); + memset( viewerUnreliableMessages, 0, sizeof (*viewerUnreliableMessages) * maxViewers ); +} + +/* +================ +idGameLocal::InitLocalClient +================ +*/ +void idGameLocal::InitLocalClient( int clientNum ) { + isServer = false; + isClient = true; + localClientNum = clientNum; + + if ( clientNum == MAX_CLIENTS && !entities[ ENTITYNUM_NONE ] ) { + SpawnPlayer( ENTITYNUM_NONE ); + idPlayer *player = gameLocal.GetLocalPlayer(); + assert( player ); + player->SpawnFromSpawnSpot(); + } +} + +/* +================ +idGameLocal::ServerAllowClient +clientId is the ID of the connecting client - can later be mapped to a clientNum by calling networkSystem->ServerGetClientNum( clientId ) +================ +*/ +allowReply_t idGameLocal::ServerAllowClient( int clientId, int numClients, const char *IP, const char *guid, const char *password, const char *privatePassword, char reason[ MAX_STRING_CHARS ] ) { + reason[0] = '\0'; + +// RAVEN BEGIN +// shouchard: ban support + if ( IsGuidBanned( guid ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107239" ); + return ALLOW_NO; + } +// RAVEN END + + if ( serverInfo.GetInt( "si_pure" ) && !mpGame.IsPureReady() ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107139" ); + return ALLOW_NOTYET; + } + + if ( !serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107140" ); + return ALLOW_NOTYET; + } + + // completely full + if ( numClients >= serverInfo.GetInt( "si_maxPlayers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + + // check private clients + if( serverInfo.GetInt( "si_privatePlayers" ) > 0 ) { + // just in case somehow we have a stale private clientId that matches a new client + mpGame.RemovePrivatePlayer( clientId ); + + const char *privatePass = cvarSystem->GetCVarString( "g_privatePassword" ); + if( privatePass[ 0 ] == '\0' ) { + common->Warning( "idGameLocal::ServerAllowClient() - si_privatePlayers > 0 with no g_privatePassword" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_privatePlayers is set but g_privatePassword is empty" ); + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + + int numPrivateClients = cvarSystem->GetCVarInteger( "si_numPrivatePlayers" ); + + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, serverInfo.GetInt( "si_privatePlayers" ), numPrivateClients ); + + if ( !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + mpGame.AddPrivatePlayer( clientId ); + } else if( (numClients - numPrivateClients) >= (serverInfo.GetInt( "si_maxPlayers" ) - serverInfo.GetInt( "si_privatePlayers" )) ) { + // if the number of public clients is greater than or equal to the number of public slots, require a private slot + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + } + + + if ( !cvarSystem->GetCVarBool( "si_usePass" ) ) { + return ALLOW_YES; + } + + const char *pass = cvarSystem->GetCVarString( "g_password" ); + if ( pass[ 0 ] == '\0' ) { + common->Warning( "si_usePass is set but g_password is empty" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say si_usePass is set but g_password is empty" ); + // avoids silent misconfigured state + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + if ( !idStr::Cmp( pass, password ) ) { + return ALLOW_YES; + } + + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting client %s from IP %s: invalid password\n", guid, IP ); + return ALLOW_BADPASS; +} + +/* +================ +idGameLocal::ReallocViewers +Allocate/Reallocate space for viewers +================ +*/ +template static ID_INLINE void ReallocateZero( T *&var, int oldSize, int newSize ) { + T *new_var = newSize ? new T[ newSize ] : NULL; + SIMDProcessor->Memcpy( new_var, var, sizeof(*new_var) * Min( newSize, oldSize ) ); + delete[] var; + var = new_var; + if ( newSize > oldSize ) { + SIMDProcessor->Memset( var + oldSize, 0, sizeof(*var) * (newSize - oldSize) ); + } +} + +static ID_INLINE void ReallocateZeroClearInfo( viewer_t *&var, int oldSize, int newSize ) { + int minBound = Min( newSize, oldSize ), i; + viewer_t *new_var = newSize ? new viewer_t[ newSize ] : NULL; + for (i = 0; i < minBound; i++ ) { + new_var[ i ] = var[ i ]; + } + for (i = 0; i < oldSize; i++ ) { + var[ i ].info.Clear(); + } + delete[] var; + var = new_var; + for (i = oldSize; i < newSize; i++) { + var[i].connected = var[i].active = var[i].priv = var[i].nopvs = false; + var[i].pvsArea = -1; + } +} + +void idGameLocal::ReallocViewers( int newMaxViewers ) { + int i; + + if ( newMaxViewers == maxViewers ) { + return; + } + + if ( newMaxViewers > 0 ) { // zinx: during shutdown we clear everything, but there are still connected viewers. + for ( i = newMaxViewers; i < maxViewers; i++ ) { + if ( viewers[ i ].connected ) { + // this should never happen + Error( "Too many active viewers for ri_maxViewers change" ); + } + } + assert( maxViewer <= newMaxViewers ); + } else { + maxViewer = 0; + } + + ReallocateZeroClearInfo( viewers, maxViewers, newMaxViewers ); + ReallocateZero( viewerEntityStates, maxViewers, newMaxViewers ); + ReallocateZero( viewerPVS, maxViewers, newMaxViewers ); + ReallocateZero( viewerSnapshots, maxViewers, newMaxViewers ); + ReallocateZero( viewerUnreliableMessages, maxViewers, newMaxViewers ); + + maxViewers = newMaxViewers; + + UpdateRepeaterInfo(); +} + +/* +================ +idGameLocal::RepeaterAllowClient +clientId is the ID of the connecting client - can later be mapped to a clientNum by calling networkSystem->ServerGetClientNum( clientId ) +================ +*/ +allowReply_t idGameLocal::RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[ MAX_STRING_CHARS ] ) { + reason[0] = '\0'; + + if ( IsGuidBanned( guid ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107239" ); + return ALLOW_NO; + } + + if ( serverInfo.GetInt( "si_pure" ) != 0 && !mpGame.IsPureReady() ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107139" ); + return ALLOW_NOTYET; + } + + if ( !cvarSystem->GetCVarInteger( "ri_maxViewers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_123000" ); + return ALLOW_NOTYET; + } + + // completely full + if ( numClients >= cvarSystem->GetCVarInteger( "ri_maxViewers" ) ) { + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_123000" ); + return ALLOW_NOTYET; + } + + // check private clients + // just in case somehow we have a stale private clientId that matches a new client + privateViewerIds.Remove( clientId ); + nopvsViewerIds.Remove( clientId ); + + const char *privatePass = cvarSystem->GetCVarString( "g_privateViewerPassword" ); + if ( privatePass[ 0 ] == '\0' && ri_privateViewers.GetInteger() > 0 ) { + common->Warning( "idGameLocal::RepeaterAllowClient() - ri_privateViewers > 0 with no g_privateViewerPassword" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say ri_privateViewers is set but g_privateViewerPassword is empty" ); + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + int numPrivateClients = privateViewerIds.Num(); + + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, ri_privateViewers.GetInteger(), numPrivateClients ); + + if ( repeater ) { + const char *nopvsPass = cvarSystem->GetCVarString( "g_repeaterPassword" ); + + if ( nopvsPass[ 0 ] != '\0' ) { + if ( idStr::Cmp( nopvsPass, privatePassword ) ) { + // password is set, and they don't match. + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting viewer %s from IP %s: invalid password for repeater\n", guid, IP ); + return ALLOW_BADPASS; + } + + // once this client spawns in, they'll be marked private + privateViewerIds.Append( clientId ); + } else { + if ( privatePass[ 0 ] != '\0' && !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + privateViewerIds.Append( clientId ); + } + } + + // once this client spawns in, they'll be marked as a broadcaster + nopvsViewerIds.Append( clientId ); + } else if ( privatePass[ 0 ] != '\0' && !idStr::Cmp( privatePass, privatePassword ) ) { + // once this client spawns in, they'll be marked private + privateViewerIds.Append( clientId ); + } else if( (numClients - numPrivateClients) >= ( cvarSystem->GetCVarInteger( "ri_maxViewers" ) - ri_privateViewers.GetInteger() ) ) { + // if the number of public clients is greater than or equal to the number of public slots, require a private slot + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107141" ); + return ALLOW_NOTYET; + } + + if ( !ri_useViewerPass.GetBool() ) { + return ALLOW_YES; + } + + const char *pass = cvarSystem->GetCVarString( "g_viewerPassword" ); + if ( pass[ 0 ] == '\0' ) { + common->Warning( "ri_useViewerPass is set but g_viewerPassword is empty" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "say ri_useViewerPass is set but g_viewerPassword is empty" ); + // avoids silent misconfigured state + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107142" ); + return ALLOW_NOTYET; + } + + if ( !idStr::Cmp( pass, password ) ) { + return ALLOW_YES; + } + + idStr::snPrintf( reason, MAX_STRING_CHARS, "#str_107143" ); + Printf( "Rejecting viewer %s from IP %s: invalid password\n", guid, IP ); + return ALLOW_BADPASS; +} + +/* +================ +idGameLocal::ServerClientConnect +================ +*/ +void idGameLocal::ServerClientConnect( int clientNum, const char *guid ) { + // make sure no parasite entity is left + if ( entities[ clientNum ] ) { + common->DPrintf( "ServerClientConnect: remove old player entity\n" ); + delete entities[ clientNum ]; + } + unreliableMessages[ clientNum ].Init( 0 ); + userInfo[ clientNum ].Clear(); + mpGame.ServerClientConnect( clientNum ); + Printf( "client %d connected.\n", clientNum ); +} + +/* +================ +idGameLocal::RepeaterClientConnect +================ +*/ +void idGameLocal::RepeaterClientConnect( int clientNum ) { + // ensure that we have enough slots for this client + assert( cvarSystem->GetCVarInteger( "ri_maxViewers" ) > clientNum ); + ReallocViewers( cvarSystem->GetCVarInteger( "ri_maxViewers" ) ); + + viewers[ clientNum ].connected = true; + viewers[ clientNum ].active = false; + + if ( maxViewer <= clientNum ) { + maxViewer = clientNum + 1; + } + + // mark them as private/nopvs + for ( int i = 0; i < privateViewerIds.Num(); i++ ) { + int num = networkSystem->RepeaterGetClientNum( privateViewerIds[ i ] ); + + // check for timed out clientids + if ( num < 0 || (num == clientNum && viewers[ clientNum ].priv) ) { + privateViewerIds.RemoveIndex( i ); + i--; + continue; + } + + if ( num == clientNum ) { + viewers[ clientNum ].priv = true; + continue; + } + } + + for ( int i = 0; i < nopvsViewerIds.Num(); i++ ) { + int num = networkSystem->RepeaterGetClientNum( nopvsViewerIds[ i ] ); + + // check for timed out clientids + if ( num < 0 || (num == clientNum && viewers[ clientNum ].nopvs) ) { + nopvsViewerIds.RemoveIndex( i ); + i--; + continue; + } + + if ( num == clientNum ) { + viewers[ clientNum ].nopvs = true; + continue; + } + } + + Printf( "viewer %d connected%s.\n", clientNum, viewers[ clientNum ].nopvs ? " (repeater)" : "" ); + + // count up viewer slots so players know when servers are full + int numClients = 0; + for ( int i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].connected ) { + continue; + } + + ++numClients; + } + + int numPrivateClients = privateViewerIds.Num(); + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, ri_privateViewers.GetInteger(), numPrivateClients ); + + ri_numViewers.SetInteger( numClients ); + ri_numPrivateViewers.SetInteger( numPrivateClients ); + + UpdateRepeaterInfo(); +} + +/* +================ +idGameLocal::ServerClientBegin +================ +*/ +void idGameLocal::ServerClientBegin( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn the player + SpawnPlayer( clientNum ); + if ( clientNum == localClientNum ) { + mpGame.EnterGame( clientNum ); + } + + // ddynerman: connect time + ((idPlayer*)entities[ clientNum ])->SetConnectTime( time ); + + // send message to spawn the player at the clients + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteLong( spawnIds[ clientNum ] ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + if( gameType != GAME_TOURNEY ) { + ((idPlayer*)entities[ clientNum ])->JoinInstance( 0 ); + } else { + // instance 0 might be empty in Tourney + ((idPlayer*)entities[ clientNum ])->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } +//RAVEN BEGIN +//asalmon: This client has finish loading and will be spawned mark them as ready. +#ifdef _XENON + Live()->ClientReady(clientNum); +#endif +//RAVEN END +} + +/* +================ +idGameLocal::RepeaterClientBegin +================ +*/ +void idGameLocal::RepeaterClientBegin( int clientNum ) { + viewers[ clientNum ].active = true; +} + +/* +================ +idGameLocal::ServerClientDisconnect +clientNum == MAX_CLIENTS for cleanup of server demo recording data +================ +*/ +void idGameLocal::ServerClientDisconnect( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( clientNum < MAX_CLIENTS ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DELETE_ENT ); + outMsg.WriteBits( ( spawnIds[ clientNum ] << GENTITYNUM_BITS ) | clientNum, 32 ); // see GetSpawnId + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + // free snapshots stored for this client + FreeSnapshotsOlderThanSequence( clientNum, 0x7FFFFFFF ); + + // free entity states stored for this client + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( clientEntityStates[ clientNum ][ i ] ) { + entityStateAllocator.Free( clientEntityStates[ clientNum ][ i ] ); + clientEntityStates[ clientNum ][ i ] = NULL; + } + } + + // clear the client PVS + memset( clientPVS[ clientNum ], 0, sizeof( clientPVS[ clientNum ] ) ); + + if ( clientNum == MAX_CLIENTS ) { + return; + } + + // only drop MP clients if we're in multiplayer and the server isn't going down + if ( gameLocal.isMultiplayer && !(gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) ) { + // idMultiplayerGame::DisconnectClient will do the delete in MP + mpGame.DisconnectClient( clientNum ); + } else { + // delete the player entity + delete entities[ clientNum ]; + } + + +} + +/* +================ +idGameLocal::RepeaterClientDisconnect +================ +*/ +void idGameLocal::RepeaterClientDisconnect( int clientNum ) { + int i; + + if ( clientNum >= maxViewers ) { + // this can happen during shutdown, because repeater clients are + // discarded when the (shared) entity states/snapshots of the other + // clients are freed + return; + } + + // free snapshots stored for this client + FreeSnapshotsOlderThanSequence( viewerSnapshots[ clientNum ], 0x7FFFFFFF ); + + // free entity states stored for this client + for ( i = 0; i < MAX_GENTITIES; i++ ) { + if ( viewerEntityStates[ clientNum ][ i ] ) { + entityStateAllocator.Free( viewerEntityStates[ clientNum ][ i ] ); + viewerEntityStates[ clientNum ][ i ] = NULL; + } + } + + // clear the client PVS + memset( viewerPVS[ clientNum ], 0, sizeof( viewerPVS[ clientNum ] ) ); + + viewerUnreliableMessages[ clientNum ].Init( 0 ); + + viewers[ clientNum ].connected = false; + viewers[ clientNum ].active = false; + viewers[ clientNum ].priv = false; + viewers[ clientNum ].nopvs = false; + + if ( maxViewer <= (clientNum+1) ) { + // find the new highest client + for ( maxViewer = clientNum; maxViewer >= 0 && !viewers[ maxViewer ].connected; maxViewer-- ) ; + maxViewer++; + } + + // remove them from private/nopvs lists + for ( int i = 0; i < privateViewerIds.Num(); i++ ) { + int num = networkSystem->RepeaterGetClientNum( privateViewerIds[ i ] ); + + // check for timed out clientids + if ( num < 0 ) { + nopvsViewerIds.Remove( privateViewerIds[ i ] ); + privateViewerIds.RemoveIndex( i ); + i--; + continue; + } + + if ( num == clientNum ) { + nopvsViewerIds.Remove( privateViewerIds[ i ] ); + privateViewerIds.RemoveIndex( i ); + i--; + continue; + } + } + + // count up viewer slots so players know when servers are full + int numClients = 0; + for ( int i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].connected ) { + continue; + } + + ++numClients; + } + + int numPrivateClients = privateViewerIds.Num(); + // private clients that take up public slots are considered public clients + numPrivateClients = idMath::ClampInt( 0, ri_privateViewers.GetInteger(), numPrivateClients ); + + ri_numViewers.SetInteger( numClients ); + ri_numPrivateViewers.SetInteger( numPrivateClients ); + + UpdateRepeaterInfo(); +} + +/* +================ +idGameLocal::ServerWriteInitialReliableMessages + + Send reliable messages to initialize the client game up to a certain initial state. +================ +*/ +void idGameLocal::ServerWriteInitialReliableMessages( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn players + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[i] == NULL || i == clientNum ) { + continue; + } + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( i ); + outMsg.WriteLong( spawnIds[ i ] ); + networkSystem->ServerSendReliableMessage( clientNum, outMsg, true ); + } + + // update portals for opened doors + int numPortals = gameRenderWorld->NumPortals(); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTALSTATES ); + outMsg.WriteLong( numPortals ); + for ( i = 0; i < numPortals; i++ ) { + outMsg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); + } + networkSystem->ServerSendReliableMessage( clientNum, outMsg, true ); + + mpGame.ServerWriteInitialReliableMessages( serverReliableSender.To( clientNum, true ), clientNum ); +} + +/* +================ +idGameLocal::RepeaterWriteInitialReliableMessages + + Send reliable messages to initialize the client game up to a certain initial state. +================ +*/ +void idGameLocal::RepeaterWriteInitialReliableMessages( int clientNum ) { + int i; + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + // spawn players + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( entities[i] == NULL ) { + continue; + } + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SPAWN_PLAYER ); + outMsg.WriteByte( i ); + outMsg.WriteLong( spawnIds[ i ] ); + networkSystem->RepeaterSendReliableMessage( clientNum, outMsg ); + } + + // update portals for opened doors + int numPortals = gameRenderWorld->NumPortals(); + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PORTALSTATES ); + outMsg.WriteLong( numPortals ); + for ( i = 0; i < numPortals; i++ ) { + outMsg.WriteBits( gameRenderWorld->GetPortalState( (qhandle_t) (i+1) ) , NUM_RENDER_PORTAL_BITS ); + } + networkSystem->RepeaterSendReliableMessage( clientNum, outMsg ); + + mpGame.ServerWriteInitialReliableMessages( repeaterReliableSender.To( clientNum ), ENTITYNUM_NONE ); +} + +/* +================ +idGameLocal::FreeSnapshotsOlderThanSequence +================ +*/ +void idGameLocal::FreeSnapshotsOlderThanSequence( snapshot_t *&clientSnapshot, int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + for ( lastSnapshot = NULL, snapshot = clientSnapshot; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence < sequence ) { + for ( state = snapshot->firstEntityState; state; state = snapshot->firstEntityState ) { + snapshot->firstEntityState = snapshot->firstEntityState->next; + entityStateAllocator.Free( state ); + } + if ( lastSnapshot ) { + lastSnapshot->next = snapshot->next; + } else { + clientSnapshot = snapshot->next; + } + snapshotAllocator.Free( snapshot ); + } else { + lastSnapshot = snapshot; + } + } +} + +/* +================ +idGameLocal::FreeSnapshotsOlderThanSequence +================ +*/ +void idGameLocal::FreeSnapshotsOlderThanSequence( int clientNum, int sequence ) { + FreeSnapshotsOlderThanSequence( clientSnapshots[ clientNum ], sequence ); +} + +/* +================ +idGameLocal::ApplySnapshot +================ +*/ +bool idGameLocal::ApplySnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], int sequence ) { + snapshot_t *snapshot, *lastSnapshot, *nextSnapshot; + entityState_t *state; + + FreeSnapshotsOlderThanSequence( clientSnapshot, sequence ); + + for ( lastSnapshot = NULL, snapshot = clientSnapshot; snapshot; snapshot = nextSnapshot ) { + nextSnapshot = snapshot->next; + if ( snapshot->sequence == sequence ) { + for ( state = snapshot->firstEntityState; state; state = state->next ) { + if ( entityStates[state->entityNumber] ) { + entityStateAllocator.Free( entityStates[state->entityNumber] ); + } + entityStates[state->entityNumber] = state; + } + // ~512 bytes + memcpy( PVS, snapshot->pvs, sizeof( snapshot->pvs ) ); + if ( lastSnapshot ) { + lastSnapshot->next = nextSnapshot; + } else { + clientSnapshot = nextSnapshot; + } + snapshotAllocator.Free( snapshot ); + return true; + } else { + lastSnapshot = snapshot; + } + } + + return false; +} + +/* +================ +idGameLocal::ApplySnapshot +================ +*/ +bool idGameLocal::ApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientSnapshots[ clientNum ], clientEntityStates[ clientNum ], clientPVS[ clientNum ], sequence ); +} + +/* +================ +idGameLocal::WriteGameStateToSnapshot +================ +*/ +void idGameLocal::WriteGameStateToSnapshot( idBitMsgDelta &msg ) const { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + msg.WriteFloat( globalShaderParms[i] ); + } + + mpGame.WriteToSnapshot( msg ); +} + +/* +================ +idGameLocal::ReadGameStateFromSnapshot +================ +*/ +void idGameLocal::ReadGameStateFromSnapshot( const idBitMsgDelta &msg ) { + int i; + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + globalShaderParms[i] = msg.ReadFloat(); + } + + mpGame.ReadFromSnapshot( msg ); +} + +/* +================ +idGameLocal::WriteSnapshot +Write a snapshot of the current game state for the given client. +================ +*/ +void idGameLocal::WriteSnapshot( snapshot_t *&clientSnapshot, entityState_t *entityStates[MAX_GENTITIES], int PVS[ENTITY_PVS_SIZE], idMsgQueue &unreliable, int sequence, idBitMsg &msg, int transmitEntity, int transmitEntity2, int instance, bool doPVS, const idBounds &pvs_bounds, int lastSnapshotFrame ) { + int i, msgSize, msgWriteBit; + idEntity *ent; + pvsHandle_t pvsHandle = { 0, 0 }; // shut warning + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + // free too old snapshots + // ( that's a security, normal acking from server keeps a smaller backlog of snaps ) + FreeSnapshotsOlderThanSequence( clientSnapshot, sequence - 64 ); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = sequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshot; + clientSnapshot = snapshot; + memset( snapshot->pvs, 0, sizeof( snapshot->pvs ) ); + + if ( doPVS ) { + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( pvs_bounds, sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + } + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( random.RandomInt() ); + msg.WriteLong( tagRandom.GetSeed() ); +#endif + + // write unreliable messages + unreliable.WriteTo( msg ); // NOTE: No flush + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + + // create the snapshot + for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // local fake client + if ( ent->entityNumber == ENTITYNUM_NONE ) { + continue; + } + + // if the entity is not in the player PVS + if ( doPVS && !ent->PhysicsTeamInPVS( pvsHandle ) && ent->entityNumber != transmitEntity && ent->entityNumber != transmitEntity2 ) { + continue; + } + + if ( ent->GetInstance() != instance ) { + continue; + } + + // if the entity is a map entity, mark it in PVS + if ( isMapEntity[ ent->entityNumber ] ) { + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + } + + // if that entity is not marked for network synchronization + if ( !ent->fl.networkSync ) { + continue; + } + + // add the entity to the snapshot PVS + snapshot->pvs[ ent->entityNumber >> 5 ] |= 1 << ( ent->entityNumber & 31 ); + + // save the write state to which we can revert when the entity didn't change at all + msg.SaveWriteState( msgSize, msgWriteBit ); + + // write the entity to the snapshot + msg.WriteBits( ent->entityNumber, GENTITYNUM_BITS ); + + base = entityStates[ent->entityNumber]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ent->entityNumber; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + deltaMsg.WriteBits( spawnIds[ ent->entityNumber ], 32 - GENTITYNUM_BITS ); + assert( ent->entityDefNumber > 0 ); + deltaMsg.WriteBits( ent->entityDefNumber, entityDefBits ); + + // write the class specific data to the snapshot + ent->WriteToSnapshot( deltaMsg ); + + // if this is a player and a transmit of the player state has been requested, write the player state + if ( ent->entityNumber < MAX_CLIENTS ) { + if ( transmitEntity < 0 || transmitEntity == ent->entityNumber || transmitEntity2 == ent->entityNumber ) { + assert( ent->IsType( idPlayer::GetClassType() ) ); + deltaMsg.WriteBits( 1, 1 ); + static_cast< idPlayer * >( ent )->WritePlayerStateToSnapshot( lastSnapshotFrame, deltaMsg ); + } else { + deltaMsg.WriteBits( 0, 1 ); + } + } + + if ( !deltaMsg.HasChanged() ) { + msg.RestoreWriteState( msgSize, msgWriteBit ); + entityStateAllocator.Free( newBase ); + } else { + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + +#if ASYNC_WRITE_TAGS + msg.WriteLong( tagRandom.RandomInt() ); +#endif + } + } + + msg.WriteBits( ENTITYNUM_NONE, GENTITYNUM_BITS ); + + // write the PVS to the snapshot +#if ASYNC_WRITE_PVS + if ( doPVS ) { + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + if ( i < numSourceAreas ) { + msg.WriteLong( sourceAreas[ i ] ); + } else { + msg.WriteLong( 0 ); + } + } + gameLocal.pvs.WritePVS( pvsHandle, msg ); + } +#endif + // always write the entity pvs, even when doPVS isn't set + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + msg.WriteDeltaLong( PVS[i], snapshot->pvs[i] ); + } + + if ( doPVS ) { + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + } + + // write the game state to the snapshot + base = entityStates[ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitWriting( base ? &base->state : NULL, &newBase->state, &msg ); + + WriteGameStateToSnapshot( deltaMsg ); +} + +/* +================ +idGameLocal::ServerWriteSnapshot +Write a snapshot of the current game state for the given client. +================ +*/ +void idGameLocal::ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, int lastSnapshotFrame ) { + + int i; + idPlayer *player, *spectated = NULL; + + player = static_cast( entities[ clientNum ] ); + assert( player ); + + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + WriteSnapshot( + clientSnapshots[ clientNum ], + clientEntityStates[ clientNum ], + clientPVS[ clientNum ], + unreliableMessages[ clientNum ], + sequence, + msg, + spectated->entityNumber, + clientNum, + player->GetInstance(), + true, + spectated->GetPlayerPhysics()->GetAbsBounds(), + lastSnapshotFrame + ); + + unreliableMessages[ clientNum ].Init( 0 ); // Flush + + // copy the client PVS string +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + const int numDwords = ( numPVSClients + 31 ) >> 5; + for ( i = 0; i < numDwords; i++ ) { + clientInPVS[i] = clientSnapshots[ clientNum ]->pvs[i]; + } +// RAVEN END +} + +/* +=============== +idGameLocal::ServerWriteServerDemoSnapshot +=============== +*/ +void idGameLocal::ServerWriteServerDemoSnapshot( int sequence, idBitMsg &msg, int lastSnapshotFrame ) { + bool ret = ApplySnapshot( clientSnapshots[ MAX_CLIENTS ], clientEntityStates[ MAX_CLIENTS ], clientPVS[ MAX_CLIENTS ], sequence - 1 ); + ret = ret; // shut the warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + idBounds dummy_bounds; + + WriteSnapshot( + clientSnapshots[ MAX_CLIENTS ], + clientEntityStates[ MAX_CLIENTS ], + clientPVS[ MAX_CLIENTS ], + unreliableMessages[ MAX_CLIENTS ], + sequence, + msg, + -1, + -1, + 0, + false, + dummy_bounds, + lastSnapshotFrame + ); + + unreliableMessages[ MAX_CLIENTS ].Init( 0 ); // Flush +} + +/* +=============== +idGameLocal::RepeaterWriteSnapshot +=============== +*/ +void idGameLocal::RepeaterWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, dword *clientInPVS, int numPVSClients, const userOrigin_t &pvs_origin, int lastSnapshotFrame ) { + int i; + idBounds bounds; + + // if the client is spectating a valid player + if ( pvs_origin.followClient >= 0 && pvs_origin.followClient < MAX_CLIENTS && entities[ pvs_origin.followClient ] && entities[ pvs_origin.followClient ]->IsType( idPlayer::GetClassType() ) ) { + idPlayer *spectated = static_cast< idPlayer * >( entities[ pvs_origin.followClient ] ); + bounds = spectated->GetPlayerPhysics()->GetAbsBounds(); + } else { + // zinx - FIXME - bounds, not point + bounds = idBounds( pvs_origin.origin ); + } + + WriteSnapshot( + viewerSnapshots[ clientNum ], + viewerEntityStates[ clientNum ], + viewerPVS[ clientNum ], + viewerUnreliableMessages[ clientNum ], + sequence, + msg, + viewers[ clientNum ].nopvs ? -1 : (pvs_origin.followClient >= 0 ? pvs_origin.followClient : ENTITYNUM_NONE ), + ENTITYNUM_NONE, + 0, + viewers[ clientNum ].nopvs ? false : true, + bounds, + lastSnapshotFrame + ); + + viewerUnreliableMessages[ clientNum ].Init( 0 ); // Flush + + // copy the client PVS string +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy +// jnewquist: Use dword array to match pvs array so we don't have endianness problems. + const int numDwords = ( numPVSClients + 31 ) >> 5; + for ( i = 0; i < numDwords; i++ ) { + clientInPVS[i] = viewerSnapshots[ clientNum ]->pvs[i]; + } +// RAVEN END +} + +/* +=============== +idGameLocal::RepeaterEndSnapshots +=============== +*/ +void idGameLocal::RepeaterEndSnapshots( void ) { +} + +/* +================ +idGameLocal::ServerApplySnapshot +================ +*/ +bool idGameLocal::ServerApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::RepeaterApplySnapshot +================ +*/ +bool idGameLocal::RepeaterApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( viewerSnapshots[ clientNum ], viewerEntityStates[ clientNum ], viewerPVS[ clientNum ], sequence ); +} + +/* +================ +idGameLocal::NetworkEventWarning +================ +*/ +void idGameLocal::NetworkEventWarning( const entityNetEvent_t *event, const char *fmt, ... ) { + char buf[1024]; + int length = 0; + va_list argptr; + + int entityNum = event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ); + int id = event->spawnId >> GENTITYNUM_BITS; + + length += idStr::snPrintf( buf+length, sizeof(buf)-1-length, "event %d for entity %d %d: ", event->event, entityNum, id ); + va_start( argptr, fmt ); + length = idStr::vsnPrintf( buf+length, sizeof(buf)-1-length, fmt, argptr ); + va_end( argptr ); + idStr::Append( buf, sizeof(buf), "\n" ); + + common->DWarning( buf ); +} + +/* +================ +idGameLocal::ServerProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ServerProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while ( eventQueue.Start() ) { + event = eventQueue.Start(); + + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if ( !entPtr.SetSpawnId( event->spawnId ) ) { + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ServerReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +/* +================ +idGameLocal::ServerSendChatMessage +================ +*/ +void idGameLocal::ServerSendChatMessage( int to, const char *name, const char *text, const char *parm ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( parm ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + + if ( to == -1 || to == localClientNum ) { + idStr temp = va( "%s%s", common->GetLocalizedString( text ), parm ); + mpGame.AddChatLine( "%s^0: %s", name, temp.c_str() ); + } +} + +/* +================ +idGameLocal::ServerProcessReliableMessage +================ +*/ +void idGameLocal::ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + + id = msg.ReadByte(); + switch( id ) { + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + char name[128]; + char text[128]; + char parm[128]; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + // This parameter is ignored - it is only used when going to client from server + msg.ReadString( parm, sizeof( parm ) ); + + mpGame.ProcessChatMessage( clientNum, id == GAME_RELIABLE_MESSAGE_TCHAT, name, text, NULL ); + break; + } + case GAME_RELIABLE_MESSAGE_VCHAT: { + int index = msg.ReadLong(); + bool team = msg.ReadBits( 1 ) != 0; + mpGame.ProcessVoiceChat( clientNum, team, index ); + break; + } + case GAME_RELIABLE_MESSAGE_KILL: { + mpGame.WantKilled( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_DROPWEAPON: { + mpGame.DropWeapon( clientNum ); + break; + } + case GAME_RELIABLE_MESSAGE_CALLVOTE: { + mpGame.ServerCallVote( clientNum, msg ); + break; + } + case GAME_RELIABLE_MESSAGE_CASTVOTE: { + bool vote = ( msg.ReadByte() != 0 ); + mpGame.CastVote( clientNum, vote ); + break; + } + case GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE: { + mpGame.ServerCallPackedVote( clientNum, msg ); + break; + } +#if 0 + // uncomment this if you want to track when players are in a menu + case GAME_RELIABLE_MESSAGE_MENU: { + bool menuUp = ( msg.ReadBits( 1 ) != 0 ); + break; + } +#endif + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_DROP ); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + +// RAVEN BEGIN +// jscott: voice comms + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST: { + mpGame.ReceiveAndForwardVoiceData( clientNum, msg, id - GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT ); + break; + } +// ddynerman: stats + case GAME_RELIABLE_MESSAGE_STAT: { + int client = msg.ReadByte(); + statManager->SendStat( clientNum, client ); + break; + } +// shouchard: voice chat + case GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING: { + int clientDest = msg.ReadByte(); + bool mute = ( 0 != msg.ReadByte() ); + mpGame.ServerHandleVoiceMuting( clientNum, clientDest, mute ); + break; + } +// shouchard: server admin + case GAME_RELIABLE_MESSAGE_SERVER_ADMIN: { + int commandType = msg.ReadByte(); + int clientNum = msg.ReadByte(); + if ( SERVER_ADMIN_REMOVE_BAN == commandType ) { + mpGame.HandleServerAdminRemoveBan( "" ); + } else if ( SERVER_ADMIN_KICK == commandType ) { + mpGame.HandleServerAdminKickPlayer( clientNum ); + } else if ( SERVER_ADMIN_FORCE_SWITCH == commandType ) { + mpGame.HandleServerAdminForceTeamSwitch( clientNum ); + } else { + Warning( "Server admin packet with bad type %d", commandType ); + } + break; + } +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + ServerSendBanList( clientNum ); + break; + } +// RAVEN END + + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + mpGame.SendMapList( clientNum ); + break; + } + + default: { + Warning( "Unknown client->server reliable message: %d", id ); + break; + } + } +} + +/* +================ +idGameLocal::RepeaterProcessReliableMessage +================ +*/ +void idGameLocal::RepeaterProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + + id = msg.ReadByte(); + switch( id ) { + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + if ( !g_noTVChat.GetBool() ) + { + char name[128], text[128], parm[128]; + idStr suffixed_name, mangled_text; + + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + msg.ReadString( parm, sizeof( parm ) ); + + suffixed_name = va( "^0%s^c777 (viewer)", name ); + mangled_text = va( "^c777%s", text ); + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( suffixed_name.c_str() ); + outMsg.WriteString( mangled_text.c_str() ); + outMsg.WriteString( "" ); + networkSystem->RepeaterSendReliableMessage( -1, outMsg ); + } + break; + } + case GAME_RELIABLE_MESSAGE_VCHAT: { + break; + } + case GAME_RELIABLE_MESSAGE_KILL: { + break; + } + case GAME_RELIABLE_MESSAGE_DROPWEAPON: { + break; + } + case GAME_RELIABLE_MESSAGE_CALLVOTE: { + break; + } + case GAME_RELIABLE_MESSAGE_CASTVOTE: { + break; + } +// RAVEN BEGIN +// shouchard: multivalue votes + case GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE: { + break; + } +// RAVEN END +#if 0 + // uncomment this if you want to track when players are in a menu + case GAME_RELIABLE_MESSAGE_MENU: { + bool menuUp = ( msg.ReadBits( 1 ) != 0 ); + break; + } +#endif + case GAME_RELIABLE_MESSAGE_EVENT: { + break; + } + +// RAVEN BEGIN +// jscott: voice comms + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_TEST: + case GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT_ECHO_TEST: { + break; + } +// ddynerman: stats + case GAME_RELIABLE_MESSAGE_STAT: { + break; + } +// shouchard: voice chat + case GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING: { + break; + } +// shouchard: server admin + case GAME_RELIABLE_MESSAGE_SERVER_ADMIN: { + break; + } +// mekberg: get ban list for server + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + break; + } +// RAVEN END + + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + break; + } + + default: { + Warning( "Unknown client->server reliable message: %d", id ); + break; + } + } +} + +/* +================ +idGameLocal::ClientShowSnapshot +================ +*/ +void idGameLocal::ClientShowSnapshot( int clientNum ) const { + int baseBits; + idEntity *ent; + idPlayer *player; + idMat3 viewAxis; + idBounds viewBounds; + entityState_t *base; + + if ( !net_clientShowSnapshot.GetInteger() ) { + return; + } + + player = static_cast( entities[clientNum] ); + if ( !player ) { + return; + } + + viewAxis = player->viewAngles.ToMat3(); + viewBounds = player->GetPhysics()->GetAbsBounds().Expand( net_clientShowSnapshotRadius.GetFloat() ); + + for( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { + + if ( net_clientShowSnapshot.GetInteger() == 1 && ent->snapshotBits == 0 ) { + continue; + } + + const idBounds &entBounds = ent->GetPhysics()->GetAbsBounds(); + + if ( !entBounds.IntersectsBounds( viewBounds ) ) { + continue; + } + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( base ) { + baseBits = base->state.GetNumBitsWritten(); + } else { + baseBits = 0; + } + + if ( net_clientShowSnapshot.GetInteger() == 2 && baseBits == 0 ) { + continue; + } + + gameRenderWorld->DebugBounds( colorGreen, entBounds ); + gameRenderWorld->DrawText( va( "%d: %s (%d,%d bytes of %d,%d)\n", ent->entityNumber, + ent->name.c_str(), ent->snapshotBits >> 3, ent->snapshotBits & 7, baseBits >> 3, baseBits & 7 ), + entBounds.GetCenter(), 0.1f, colorWhite, viewAxis, 1 ); + } +} + + +/* +================ +idGameLocal::ReadSnapshot +================ +*/ +void idGameLocal::ClientReadSnapshot( int clientNum, int snapshotSequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) { + int i, entityDefNumber, numBitsRead; + idEntity *ent; + idPlayer *player, *spectated; + pvsHandle_t pvsHandle; + idDict args; + idBitMsgDelta deltaMsg; + snapshot_t *snapshot; + entityState_t *base, *newBase; + int spawnId; + int numSourceAreas, sourceAreas[ idEntity::MAX_PVS_AREAS ]; + + const idDeclEntityDef *decl; + + if ( net_clientLagOMeter.GetBool() && renderSystem && !(GetDemoState() == DEMO_PLAYING && ( IsServerDemoPlaying() || IsTimeDemo() )) ) { + lagometer.Update( aheadOfServer, dupeUsercmds ); + } + + InitLocalClient( clientNum ); + + gameRenderWorld->DebugClear( time ); + + // update the game time + framenum = gameFrame; + time = gameTime; + previousTime = time - GetMSec(); + + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + // allocate new snapshot + snapshot = snapshotAllocator.Alloc(); + snapshot->sequence = snapshotSequence; + snapshot->firstEntityState = NULL; + snapshot->next = clientSnapshots[clientNum]; + clientSnapshots[clientNum] = snapshot; + +#if ASYNC_WRITE_TAGS + idRandom tagRandom; + tagRandom.SetSeed( msg.ReadLong() ); +#endif + + ClientReadUnreliableMessages( msg ); + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + Error( "error after read unreliable" ); + } +#endif + + // read all entities from the snapshot + for ( i = msg.ReadBits( GENTITYNUM_BITS ); i != ENTITYNUM_NONE; i = msg.ReadBits( GENTITYNUM_BITS ) ) { + base = clientEntityStates[clientNum][i]; + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = i; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + + numBitsRead = msg.GetNumBitsRead(); + + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + ent = entities[i]; + + // if there is no entity or an entity of the wrong type + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + if ( i < MAX_CLIENTS && ent ) { + // SPAWN_PLAYER should be taking care of spawning the entity with the right spawnId + common->Warning( "ClientReadSnapshot: recycling client entity %d\n", i ); + } + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if ( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + assert( entityDefNumber >= 0 ); + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString( "spawnclass" ) ); + } + + if ( i < MAX_CLIENTS && i >= numClients ) { + numClients = i + 1; + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + +// RAVEN BEGIN +// bdube: stale network entities + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // read the player state from player entities + if ( ent->entityNumber < MAX_CLIENTS ) { + if ( deltaMsg.ReadBits( 1 ) == 1 ) { + assert( ent->IsType( idPlayer::GetClassType() ) ); + static_cast< idPlayer * >( ent )->ReadPlayerStateFromSnapshot( deltaMsg ); + } + } + + // once we read new snapshot data, unstale the ent + if( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + ent->snapshotBits = msg.GetNumBitsRead() - numBitsRead; + +#if ASYNC_WRITE_TAGS + if ( msg.ReadLong() != tagRandom.RandomInt() ) { + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "writeGameState" ); + assert( entityDefNumber >= 0 ); + assert( entityDefNumber < declManager->GetNumDecls( DECL_ENTITYDEF ) ); + const char * classname = declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false )->GetName(); + Error( "write to and read from snapshot out of sync for classname '%s'\n", classname ); + } +#endif + } + + if ( clientNum < MAX_CLIENTS ) { + player = static_cast( entities[ clientNum ] ); + } else { + player = gameLocal.GetLocalPlayer(); + } + + if ( player->spectating && player->spectator != clientNum && entities[ player->spectator ] ) { + spectated = static_cast< idPlayer * >( entities[ player->spectator ] ); + } else { + spectated = player; + } + + if ( spectated ) { + // get PVS for this player + // don't use PVSAreas for networking - PVSAreas depends on animations (and md5 bounds), which are not synchronized + numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + } else { + gameLocal.Printf( "Client hasn't spawned self yet.\n" ); + numSourceAreas = gameRenderWorld->BoundsInAreas( idBounds( vec3_origin ), sourceAreas, idEntity::MAX_PVS_AREAS ); + pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + } + + // read the PVS from the snapshot +#if ASYNC_WRITE_PVS +// zinx - FIXME - this isn't written for server demos or for non-PVS'd repeaters, so we shouldn't read it. + int serverPVS[idEntity::MAX_PVS_AREAS]; + i = numSourceAreas; + while ( i < idEntity::MAX_PVS_AREAS ) { + sourceAreas[ i++ ] = 0; + } + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + serverPVS[ i ] = msg.ReadLong(); + } + if ( memcmp( sourceAreas, serverPVS, idEntity::MAX_PVS_AREAS * sizeof( int ) ) ) { + common->Warning( "client PVS areas != server PVS areas, sequence 0x%x", snapshotSequence ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", sourceAreas[ i ] ); + } + common->DPrintf( "\n" ); + for ( i = 0; i < idEntity::MAX_PVS_AREAS; i++ ) { + common->DPrintf( "%3d ", serverPVS[ i ] ); + } + common->DPrintf( "\n" ); + } + gameLocal.pvs.ReadPVS( pvsHandle, msg ); +#endif + for ( i = 0; i < ENTITY_PVS_SIZE; i++ ) { + snapshot->pvs[i] = msg.ReadDeltaLong( clientPVS[clientNum][i] ); + } +// RAVEN BEGIN +// ddynerman: performance profiling + net_entsInSnapshot += snapshotEntities.Num(); + net_snapshotSize += msg.GetSize(); +// RAVEN END + // add entities in the PVS that haven't changed since the last applied snapshot + idEntity *nextSpawnedEnt; + for( ent = spawnedEntities.Next(); ent != NULL; ent = nextSpawnedEnt ) { + nextSpawnedEnt = ent->spawnNode.Next(); + + // local fake client + if ( ent->entityNumber == ENTITYNUM_NONE ) { + continue; + } + + // if the entity is already in the snapshot + if ( ent->snapshotSequence == snapshotSequence ) { + continue; + } + + // if the entity is not in the snapshot PVS + if ( !( snapshot->pvs[ent->entityNumber >> 5] & ( 1 << ( ent->entityNumber & 31 ) ) ) ) { + + if ( !ent->fl.networkSync ) { + // don't do stale / unstale on entities that are not marked network sync + continue; + } + + if ( ent->PhysicsTeamInPVS( pvsHandle ) ) { + if ( ent->entityNumber >= MAX_CLIENTS && isMapEntity[ ent->entityNumber ] ) { + // server says it's not in PVS, client says it's in PVS + // if that happens on map entities, most likely something is wrong + // I can see that moving pieces along several PVS could be a legit situation though + // this is a band aid, which means something is not done right elsewhere + if ( net_warnStale.GetInteger() > 1 || ( net_warnStale.GetInteger() == 1 && !ent->fl.networkStale ) ) { + common->Warning( "client thinks map entity 0x%x (%s) is stale, sequence 0x%x", ent->entityNumber, ent->name.c_str(), snapshotSequence ); + } + } +// RAVEN BEGIN +// bdube: hide while not in snapshot + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + + } else { + if ( !ent->fl.networkStale ) { + if ( ent->ClientStale() ) { + delete ent; + ent = NULL; + } else { + ent->fl.networkStale = true; + } + } + } +// RAVEN END + + continue; + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + ent->snapshotSequence = snapshotSequence; + ent->snapshotBits = 0; + +// RAVEN BEGIN +// bdube: hide while not in snapshot + // Ensure the clipmodel is relinked when transitioning from state + if ( ent->fl.networkStale ) { + ent->GetPhysics()->LinkClip(); + } +// RAVEN END + + base = clientEntityStates[clientNum][ent->entityNumber]; + if ( !base ) { + // entity has probably fl.networkSync set to false + // non netsynced map entities go in and out of PVS, and may need stale/unstale calls + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + continue; + } + + if ( !ent->fl.networkSync ) { + // this is not supposed to happen + // it did however, when restarting a map with a different inhibit of entities caused entity numbers to be laid differently + // an idLight would occupy the entity number of an idItem for instance, and although it's not network-synced ( static level light ), + // the presence of a base would cause the system to think that it is and corrupt things + // we changed the map population so the entity numbers are kept the same no matter how things are inhibited + // this code is left as a fall-through fixup / sanity type of thing + // if this still happens, it's likely "client thinks map entity is stale" is happening as well, and we're still at risk of corruption + Warning( "ClientReadSnapshot: entity %d of type %s is not networkSync and has a snapshot base", ent->entityNumber, ent->GetType()->classname ); + entityStateAllocator.Free( clientEntityStates[clientNum][ent->entityNumber] ); + clientEntityStates[clientNum][ent->entityNumber] = NULL; + continue; + } + + base->state.BeginReading(); + + deltaMsg.InitReading( &base->state, NULL, (const idBitMsg *)NULL ); + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + // read the class specific data from the base state + ent->ReadFromSnapshot( deltaMsg ); + + // after snapshot read, notify client of unstale + if ( ent->fl.networkStale ) { + ent->ClientUnstale(); + ent->fl.networkStale = false; + } + } + +// RAVEN BEGIN +// ddynerman: add the ambient lights to the snapshot entities + for( int i = 0; i < ambientLights.Num(); i++ ) { + ambientLights[ i ]->snapshotNode.AddToEnd( snapshotEntities ); + ambientLights[ i ]->fl.networkStale = false; + } +// RAVEN END + + // free the PVS + pvs.FreeCurrentPVS( pvsHandle ); + + // read the game state from the snapshot + base = clientEntityStates[clientNum][ENTITYNUM_NONE]; // ENTITYNUM_NONE is used for the game state + if ( base ) { + base->state.BeginReading(); + } + newBase = entityStateAllocator.Alloc(); + newBase->entityNumber = ENTITYNUM_NONE; + newBase->next = snapshot->firstEntityState; + snapshot->firstEntityState = newBase; + newBase->state.Init( newBase->stateBuf, sizeof( newBase->stateBuf ) ); + newBase->state.BeginWriting(); + deltaMsg.InitReading( base ? &base->state : NULL, &newBase->state, &msg ); + + ReadGameStateFromSnapshot( deltaMsg ); + + // visualize the snapshot + if ( clientNum < MAX_CLIENTS ) { + ClientShowSnapshot( clientNum ); + } else { + // FIXME + } + + // process entity events + ClientProcessEntityNetworkEventQueue(); + +} + +/* +=============== +idGameLocal::ClientReadServerDemoSnapshot +=============== +*/ +void idGameLocal::ClientReadServerDemoSnapshot( int sequence, const int gameFrame, const int gameTime, const idBitMsg &msg ) { + bool ret = ClientApplySnapshot( MAX_CLIENTS, sequence - 1 ); + ret = ret; // shut the warning + assert( ret || sequence == 1 ); // past the first snapshot of the server demo stream, there's always exactly one to clear + + ClientReadSnapshot( MAX_CLIENTS, sequence, gameFrame, gameTime, 0, 0, msg ); +} + +/* +=============== +idGameLocal::ClientReadRepeaterSnapshot +=============== +*/ +void idGameLocal::ClientReadRepeaterSnapshot( int sequence, const int gameFrame, const int gameTime, const int aheadOfServer, const idBitMsg &msg ) { + ClientReadSnapshot( MAX_CLIENTS, sequence, gameFrame, gameTime, 0, aheadOfServer, msg ); +} + +/* +================ +idGameLocal::ClientApplySnapshot +================ +*/ +bool idGameLocal::ClientApplySnapshot( int clientNum, int sequence ) { + return ApplySnapshot( clientNum, sequence ); +} + +/* +================ +idGameLocal::ClientProcessEntityNetworkEventQueue +================ +*/ +void idGameLocal::ClientProcessEntityNetworkEventQueue( void ) { + idEntity *ent; + entityNetEvent_t *event; + idBitMsg eventMsg; + + while ( eventQueue.Start() ) { + event = eventQueue.Start(); + + // only process forward, in order + if ( event->time > time ) { + break; + } + + idEntityPtr< idEntity > entPtr; + + if ( !entPtr.SetSpawnId( event->spawnId ) ) { + if( !gameLocal.entities[ event->spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ) ] ) { + // if new entity exists in this position, silently ignore + NetworkEventWarning( event, "Entity does not exist any longer, or has not been spawned yet." ); + } + } else { + ent = entPtr.GetEntity(); + assert( ent ); + + eventMsg.Init( event->paramsBuf, sizeof( event->paramsBuf ) ); + eventMsg.SetSize( event->paramsSize ); + eventMsg.BeginReading(); + if ( !ent->ClientReceiveEvent( event->event, event->time, eventMsg ) ) { + NetworkEventWarning( event, "unknown event" ); + } + } + +#ifdef _DEBUG + entityNetEvent_t* freedEvent = eventQueue.Dequeue(); + assert( freedEvent == event ); +#else + eventQueue.Dequeue(); +#endif + eventQueue.Free( event ); + } +} + +// RAVEN BEGIN +// bdube: client side hitscan + +/* +================ +idGameLocal::ClientHitScan +================ +*/ +void idGameLocal::ClientHitScan( const idBitMsg &msg ) { + int hitscanDefIndex; + idVec3 muzzleOrigin; + idVec3 dir; + idVec3 fxOrigin; + const idDeclEntityDef *decl; + int num_hitscans; + int i; + idEntity *owner; + + assert( isClient ); + + hitscanDefIndex = msg.ReadLong(); + decl = static_cast< const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, hitscanDefIndex ) ); + if ( !decl ) { + common->Warning( "idGameLocal::ClientHitScan: entity def index %d not found\n", hitscanDefIndex ); + return; + } + num_hitscans = decl->dict.GetInt( "hitscans", "1" ); + + owner = entities[ msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ) ]; + + muzzleOrigin[0] = msg.ReadFloat(); + muzzleOrigin[1] = msg.ReadFloat(); + muzzleOrigin[2] = msg.ReadFloat(); + fxOrigin[0] = msg.ReadFloat(); + fxOrigin[1] = msg.ReadFloat(); + fxOrigin[2] = msg.ReadFloat(); + + // one direction sent per hitscan + for( i = 0; i < num_hitscans; i++ ) { + dir = msg.ReadDir( 24 ); + gameLocal.HitScan( decl->dict, muzzleOrigin, dir, fxOrigin, owner ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientProcessReliableMessage +================ +*/ +void idGameLocal::ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) { + int id; + idDict backupSI; + int toClient = -1, excludeClient = -1; + bool noEffect = false; + + InitLocalClient( clientNum ); + + if ( IsServerDemoPlaying() ) { + int record_type = msg.ReadByte(); + assert( record_type < DEMO_RECORD_COUNT ); + // if you need to do some special filtering: + switch ( record_type ) { + case DEMO_RECORD_CLIENTNUM: { + toClient = msg.ReadChar(); + if ( toClient >= 0 && toClient < MAX_CLIENTS ) { + // reliable was targetted + if ( toClient != followPlayer ) { + // we're free flying or following someone else + if ( !isRepeater ) { + return; + } else { + noEffect = true; + } + } + } else { + toClient = -1; + } + break; + } + case DEMO_RECORD_EXCLUDE: { + excludeClient = msg.ReadChar(); // may be -1 + if ( excludeClient == followPlayer ) { + if ( !isRepeater ) { + return; + } else { + noEffect = true; + } + } + toClient = -1; + break; + } + } + } + + id = msg.ReadByte(); + + if ( isRepeater ) { + bool inhibitRepeater = false; + + switch( id ) { + case GAME_RELIABLE_MESSAGE_SPAWN_PLAYER: { + if ( toClient != -1 ) { + inhibitRepeater = true; + } + break; + } + + case GAME_RELIABLE_MESSAGE_PORTALSTATES: { + inhibitRepeater = true; + break; + } + + case GAME_RELIABLE_MESSAGE_STARTSTATE: { + if ( toClient != -1 ) { + inhibitRepeater = true; + } + break; + } + + case GAME_RELIABLE_MESSAGE_GAMESTATE: { + if ( toClient != -1 ) { + inhibitRepeater = true; + } + break; + } + + case GAME_RELIABLE_MESSAGE_SET_INSTANCE: { + inhibitRepeater = true; + break; + } + + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + inhibitRepeater = true; + break; + } + + case GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE: { + inhibitRepeater = true; + break; + } + + default: + break; + } + + if ( !inhibitRepeater ) { + // pass it along to the connected viewers + if ( excludeClient == -1 ) { + networkSystem->RepeaterSendReliableMessage( -1, msg, IsServerDemoPlaying(), toClient ); + } else { + networkSystem->RepeaterSendReliableMessageExcluding( excludeClient, msg, IsServerDemoPlaying() ); + } + } + + if ( noEffect ) { + return; + } + } + + switch( id ) { + case GAME_RELIABLE_MESSAGE_SPAWN_PLAYER: { + int client = msg.ReadByte(); + int spawnId = msg.ReadLong(); + if ( !entities[ client ] ) { + SpawnPlayer( client ); + entities[ client ]->FreeModelDef(); + } + // fix up the spawnId to match what the server says + // otherwise there is going to be a bogus delete/new of the client entity in the first ClientReadFromSnapshot + spawnIds[ client ] = spawnId; + break; + } + case GAME_RELIABLE_MESSAGE_DELETE_ENT: { + int spawnId = msg.ReadBits( 32 ); + idEntityPtr< idEntity > entPtr; + if( !entPtr.SetSpawnId( spawnId ) ) { + break; + } + if( entPtr.GetEntity() && entPtr.GetEntity()->entityNumber < MAX_CLIENTS ) { + delete entPtr.GetEntity(); + gameLocal.mpGame.UpdatePlayerRanks(); + } else { + delete entPtr.GetEntity(); + } + break; + } + case GAME_RELIABLE_MESSAGE_CHAT: + case GAME_RELIABLE_MESSAGE_TCHAT: { + char name[128]; + char text[128]; + char parm[128]; + msg.ReadString( name, sizeof( name ) ); + msg.ReadString( text, sizeof( text ) ); + msg.ReadString( parm, sizeof( parm ) ); + idStr temp = va( "%s^0: %s%s\n", name, common->GetLocalizedString( text ), parm ); + mpGame.PrintChatLine( temp.c_str(), ( id == GAME_RELIABLE_MESSAGE_TCHAT && gameLocal.IsTeamGame() ) ); + break; + } + case GAME_RELIABLE_MESSAGE_DB: { + msg_evt_t msg_evt = (msg_evt_t)msg.ReadByte(); + int parm1, parm2; + parm1 = msg.ReadByte( ); + parm2 = msg.ReadByte( ); + mpGame.PrintMessageEvent( -1, msg_evt, parm1, parm2 ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT: { + entityNetEvent_t *event; + + // allocate new event + event = eventQueue.Alloc(); + + event->spawnId = msg.ReadBits( 32 ); + event->event = msg.ReadByte(); + event->time = msg.ReadLong(); + + eventQueue.Enqueue( event, idEventQueue::OUTOFORDER_IGNORE ); + + event->paramsSize = msg.ReadBits( idMath::BitsForInteger( MAX_EVENT_PARAM_SIZE ) ); + if ( event->paramsSize ) { + if ( event->paramsSize > MAX_EVENT_PARAM_SIZE ) { + NetworkEventWarning( event, "invalid param size" ); + return; + } + msg.ReadByteAlign(); + msg.ReadData( event->paramsBuf, event->paramsSize ); + } + break; + } + case GAME_RELIABLE_MESSAGE_SERVERINFO: { + idDict info; + msg.ReadDeltaDict( info, NULL ); + SetServerInfo( info ); + break; + } + case GAME_RELIABLE_MESSAGE_RESTART: { + MapRestart(); + break; + } + case GAME_RELIABLE_MESSAGE_STARTVOTE: { + char voteString[ MAX_STRING_CHARS ]; + int clientNum = msg.ReadByte( ); + msg.ReadString( voteString, sizeof( voteString ) ); + mpGame.ClientStartVote( clientNum, voteString ); + break; + } + case GAME_RELIABLE_MESSAGE_PRINT: { + char str[ MAX_PRINT_LEN ] = { '\0' }; + msg.ReadString( str, MAX_PRINT_LEN ); + mpGame.PrintMessage( -1, str ); + break; + } + case GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE: { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + int clientNum = msg.ReadByte(); + voteData.m_fieldFlags = msg.ReadShort(); + char mapName[256]; + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + msg.ReadString( mapName, sizeof( mapName ) ); + voteData.m_map = mapName; + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + } + mpGame.ClientStartPackedVote( clientNum, voteData ); + break; + } + case GAME_RELIABLE_MESSAGE_UPDATEVOTE: { + int result = msg.ReadByte( ); + int yesCount = msg.ReadByte( ); + int noCount = msg.ReadByte( ); + int multiVote = msg.ReadByte( ); + voteStruct_t voteData; + char mapNameBuffer[256]; + memset( &voteData, 0, sizeof( voteData ) ); + if ( multiVote ) { + voteData.m_fieldFlags = msg.ReadShort(); + voteData.m_kick = msg.ReadByte(); + msg.ReadString( mapNameBuffer, sizeof( mapNameBuffer ) ); + voteData.m_map = mapNameBuffer; + voteData.m_gameType = msg.ReadByte(); + voteData.m_timeLimit = msg.ReadByte(); + voteData.m_fragLimit = msg.ReadShort(); + voteData.m_tourneyLimit = msg.ReadShort(); + voteData.m_captureLimit = msg.ReadShort(); + voteData.m_buying = msg.ReadByte(); + voteData.m_teamBalance = msg.ReadByte(); + voteData.m_controlTime = msg.ReadShort(); + } + mpGame.ClientUpdateVote( (idMultiplayerGame::vote_result_t)result, yesCount, noCount, voteData ); + break; + } + case GAME_RELIABLE_MESSAGE_PORTALSTATES: { + int numPortals = msg.ReadLong(); + assert( numPortals == gameRenderWorld->NumPortals() ); + for ( int i = 0; i < numPortals; i++ ) { + gameRenderWorld->SetPortalState( (qhandle_t) (i+1), msg.ReadBits( NUM_RENDER_PORTAL_BITS ) ); + } + break; + } + case GAME_RELIABLE_MESSAGE_PORTAL: { + qhandle_t portal = msg.ReadLong(); + int blockingBits = msg.ReadBits( NUM_RENDER_PORTAL_BITS ); + assert( portal > 0 && portal <= gameRenderWorld->NumPortals() ); + gameRenderWorld->SetPortalState( portal, blockingBits ); + break; + } + case GAME_RELIABLE_MESSAGE_STARTSTATE: { + mpGame.ClientReadStartState( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND: + mpGame.PlayGlobalItemAcquireSound( msg.ReadBits ( gameLocal.entityDefBits ) ); + break; + case GAME_RELIABLE_MESSAGE_DEATH: { + int attackerEntityNumber = msg.ReadByte( ); + int attackerScore = -1; + if( attackerEntityNumber >= 0 && attackerEntityNumber < MAX_CLIENTS ) { + attackerScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + int victimEntityNumber = msg.ReadByte( ); + int victimScore = -1; + if( victimEntityNumber >= 0 && victimEntityNumber < MAX_CLIENTS ) { + victimScore = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + } + + idPlayer* attacker = (attackerEntityNumber != 255 ? static_cast(gameLocal.entities[ attackerEntityNumber ]) : NULL); + idPlayer* victim = (victimEntityNumber != 255 ? static_cast(gameLocal.entities[ victimEntityNumber ]) : NULL); + int methodOfDeath = msg.ReadByte( ); + bool quadKill = msg.ReadBits( 1 ) != 0; + + mpGame.ReceiveDeathMessage( attacker, attackerScore, victim, victimScore, methodOfDeath, quadKill ); + break; + } + case GAME_RELIABLE_MESSAGE_GAMESTATE: { + mpGame.GetGameState()->ReceiveState( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_STAT: { + statManager->ReceiveStat( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_ALL_STATS: { + statManager->ReceiveAllStats( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_SET_INSTANCE: { + if ( !IsServerDemoPlaying() ) { + mpGame.ClientSetInstance( msg ); + } + break; + } + case GAME_RELIABLE_MESSAGE_INGAMEAWARD: { + statManager->ReceiveInGameAward( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_GETADMINBANLIST: { + mpBanInfo_t banInfo; + char name[MAX_STRING_CHARS]; + char guid[MAX_STRING_CHARS]; + + FlushBanList( ); + while ( msg.ReadString( name, MAX_STRING_CHARS ) && msg.ReadString( guid, MAX_STRING_CHARS ) ) { + banInfo.name = name; + strncpy( banInfo.guid, guid, CLIENT_GUID_LENGTH ); + banList.Append( banInfo ); + } + + break; + } + case GAME_RELIABLE_MESSAGE_GETVOTEMAPS: { + mpGame.ReadMapList( msg ); + break; + } + case GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE: { + idBitMsg unreliable; + unreliable.Init( msg.GetReadData(), msg.GetRemainingData() ); + unreliable.SetSize( msg.GetRemainingData() ); + unreliable.BeginReading(); + ProcessUnreliableMessage( unreliable ); + break; + } + case GAME_RELIABLE_MESSAGE_EVENT_ACK: { + clientAckSequence = msg.ReadLong(); + // make sure the local client is waiting for a ACK + // if he's not, this may indicate a problem + idPlayer *p = GetLocalPlayer(); + if ( net_clientPredictWeaponSwitch.GetBool() && p != NULL && !p->IsWaitingForPredictAck() ) { + common->Warning( "frame %d: got EVENT_ACK seq %d while not expecting one", gameLocal.framenum, clientAckSequence ); + } + break; + } + default: { + Error( "Unknown server->client reliable message: %d", id ); + break; + } + } +} + +// RAVEN BEGIN +/* +================ +idGameLocal::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientRun( void ) { + if( isMultiplayer ) { + mpGame.ClientRun(); + } +} + +/* +================ +idGameLocal::ClientEndFrame +Called once each client render frame (after all ClientPrediction frames have been run) +================ +*/ +void idGameLocal::ClientEndFrame( void ) { + if( isMultiplayer ) { + mpGame.ClientEndFrame(); + } +} + +/* +================ +idGameLocal::ProcessRconReturn +================ +*/ +void idGameLocal::ProcessRconReturn( bool success ) { + if( isMultiplayer ) { + mpGame.ProcessRconReturn( success ); + } +} + +/* +================ +idGameLocal::ResetGuiRconStatus +================ +*/ +void idGameLocal::ResetRconGuiStatus( void ) { + if( isMultiplayer ) { + mpGame.ResetRconGuiStatus( ); + } +} + +// RAVEN END + +/* +================ +idGameLocal::ClientPrediction +server demos: clientNum == MAX_CLIENTS +================ +*/ +gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clientCmds, bool lastPredictFrame, ClientStats_t *cs ) { + idEntity *ent; + idPlayer *player; // may be NULL when predicting for a server demo + gameReturn_t ret; + + ret.sessionCommand[ 0 ] = '\0'; + + if ( g_showDebugHud.GetInteger() && net_entsInSnapshot && net_snapshotSize) { + gameDebug.SetInt( "snap_ents", net_entsInSnapshot ); + gameDebug.SetInt( "snap_size", net_snapshotSize ); + + net_entsInSnapshot = 0; + net_snapshotSize = 0; + } + + if ( clientNum == localClientNum ) { + gameDebug.BeginFrame(); + gameLog->BeginFrame( time ); + } + + isLastPredictFrame = lastPredictFrame; + + InitLocalClient( clientNum ); + + // update the game time + framenum++; + previousTime = time; + time += GetMSec(); + + // update the real client time and the new frame flag + if ( time > realClientTime ) { + realClientTime = time; + isNewFrame = true; + } else { + isNewFrame = false; + } + + if ( clientNum == MAX_CLIENTS ) { + player = gameLocal.GetLocalPlayer(); + assert( player ); + } else { + player = static_cast( entities[clientNum] ); + } + + // check for local client lag + if ( player ) { + if ( networkSystem->ClientGetTimeSinceLastPacket() >= net_clientMaxPrediction.GetInteger() ) { + player->isLagged = true; + } else { + player->isLagged = false; + } + } + + if ( cs ) { + cs->isLastPredictFrame = isLastPredictFrame; + cs->isLagged = player ? player->isLagged : false; + cs->isNewFrame = isNewFrame; + } + + // set the user commands for this frame + usercmds = clientCmds; + + // run prediction on all entities from the last snapshot + for ( ent = snapshotEntities.Next(); ent != NULL; ent = ent->snapshotNode.Next() ) { + // don't force TH_PHYSICS on, only call ClientPredictionThink if thinkFlags != 0 + // it's better to synchronize TH_PHYSICS on specific entities when needed ( movers may be trouble ) + if ( ent->thinkFlags != 0 ) { + ent->ClientPredictionThink(); + } + } + + // run client entities + if ( isNewFrame ) { + // rjohnson: only run the entire logic when it is a new frame + rvClientEntity* cent; + for ( cent = clientSpawnedEntities.Next(); cent != NULL; cent = cent->spawnNode.Next() ) { + cent->Think(); + } + } + + // freeView + if ( clientNum == MAX_CLIENTS && player && isNewFrame && !isRepeater) { + assert( player->IsFakeClient() ); + + player->ClientPredictionThink(); + player->UpdateVisuals(); + + assert( player->spectating ); + + if ( player->spectator != player->entityNumber ) { + followPlayer = player->spectator; + } else { + followPlayer = -1; + } + } + + // service any pending events + idEvent::ServiceEvents(); + + // show any debug info for this frame + if ( isNewFrame ) { + RunDebugInfo(); + D_DrawDebugLines(); + } + + if ( sessionCommand.Length() ) { + strncpy( ret.sessionCommand, sessionCommand, sizeof( ret.sessionCommand ) ); + sessionCommand = ""; + } + +// RAVEN BEGIN +// ddynerman: client logging/debugging + if ( clientNum == localClientNum ) { + gameDebug.EndFrame(); + gameLog->EndFrame(); + } +// RAVEN END + + g_simpleItems.ClearModified(); + return ret; +} + +/* +=============== +idGameLocal::Tokenize +=============== +*/ +void idGameLocal::Tokenize( idStrList &out, const char *in ) { + char buf[ MAX_STRING_CHARS ]; + char *token, *next; + + idStr::Copynz( buf, in, MAX_STRING_CHARS ); + token = buf; + next = strchr( token, ';' ); + while ( token ) { + if ( next ) { + *next = '\0'; + } + idStr::ToLower( token ); + out.Append( token ); + if ( next ) { + token = next + 1; + next = strchr( token, ';' ); + } else { + token = NULL; + } + } +} + +/* +=============== +idGameLocal::DownloadRequest +=============== +*/ +bool idGameLocal::DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) { + if ( !cvarSystem->GetCVarInteger( "net_serverDownload" ) ) { + return false; + } + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 1 ) { + // 1: single URL redirect + if ( !strlen( cvarSystem->GetCVarString( "si_serverURL" ) ) ) { + common->Warning( "si_serverURL not set" ); + return false; + } + idStr::snPrintf( urls, MAX_STRING_CHARS, "1;%s", cvarSystem->GetCVarString( "si_serverURL" ) ); + return true; + } else { + // 2: table of pak URLs + // 3: table of pak URLs with built-in http server + // first token is the game pak if requested, empty if not requested by the client + // there may be empty tokens for paks the server couldn't pinpoint - the order matters + idStr reply = "2;"; + idStrList dlTable, pakList; + bool matchAll = false; + int i, j; + + if ( !idStr::Icmp( cvarSystem->GetCVarString( "net_serverDlTable" ), "*" ) ) { + matchAll = true; + } else { + Tokenize( dlTable, cvarSystem->GetCVarString( "net_serverDlTable" ) ); + } + Tokenize( pakList, paks ); + + for ( i = 0; i < pakList.Num(); i++ ) { + if ( i > 0 ) { + reply += ";"; + } + if ( pakList[ i ][ 0 ] == '\0' ) { + if ( i == 0 ) { + // pak 0 will always miss when client doesn't ask for game bin + common->DPrintf( "no game pak request\n" ); + } else { + common->DPrintf( "no pak %d\n", i ); + } + continue; + } + + idStr url = cvarSystem->GetCVarString( "net_serverDlBaseURL" ); + + if ( !url.Length() ) { + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 2 ) { + common->Warning( "net_serverDownload == 2 and net_serverDlBaseURL not set" ); + } else { + url = cvarSystem->GetCVarString( "net_httpServerBaseURL" ); + } + } + + if ( matchAll ) { + url.AppendPath( pakList[i] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } else { + for ( j = 0; j < dlTable.Num(); j++ ) { + if ( !pakList[ i ].Icmp( dlTable[ j ] ) ) { + break; + } + } + if ( j == dlTable.Num() ) { + common->Printf( "download for %s: pak not matched: %s\n", IP, pakList[ i ].c_str() ); + } else { + url.AppendPath( dlTable[ j ] ); + reply += url; + common->Printf( "download for %s: %s\n", IP, url.c_str() ); + } + } + } + + idStr::Copynz( urls, reply, MAX_STRING_CHARS ); + return true; + } +} + +/* +=============== +idGameLocal::HTTPRequest +=============== +*/ +bool idGameLocal::HTTPRequest( const char *IP, const char *file, bool isGamePak ) { + idStrList dlTable; + int i; + + if ( !idStr::Icmp( cvarSystem->GetCVarString( "net_serverDlTable" ), "*" ) ) { + return true; + } + + Tokenize( dlTable, cvarSystem->GetCVarString( "net_serverDlTable" ) ); + while ( *file == '/' ) ++file; // net_serverDlTable doesn't include the initial / + + for ( i = 0; i < dlTable.Num(); i++ ) { + if ( !dlTable[ i ].Icmp( file ) ) { + return true; + } + } + + return false; +} + +/* +=============== +idEventQueue::Alloc +=============== +*/ +entityNetEvent_t* idEventQueue::Alloc() { + entityNetEvent_t* event = eventAllocator.Alloc(); + event->prev = NULL; + event->next = NULL; + return event; +} + +/* +=============== +idEventQueue::Free +=============== +*/ +void idEventQueue::Free( entityNetEvent_t *event ) { + // should only be called on an unlinked event! + assert( !event->next && !event->prev ); + eventAllocator.Free( event ); +} + +/* +=============== +idEventQueue::Shutdown +=============== +*/ +void idEventQueue::Shutdown() { + eventAllocator.Shutdown(); + Init(); +} + +/* +=============== +idEventQueue::Init +=============== +*/ +void idEventQueue::Init( void ) { + start = NULL; + end = NULL; +} + +/* +=============== +idEventQueue::Dequeue +=============== +*/ +entityNetEvent_t* idEventQueue::Dequeue( void ) { + entityNetEvent_t* event = start; + if ( !event ) { + return NULL; + } + + start = start->next; + + if ( !start ) { + end = NULL; + } else { + start->prev = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::RemoveLast +=============== +*/ +entityNetEvent_t* idEventQueue::RemoveLast( void ) { + entityNetEvent_t *event = end; + if ( !event ) { + return NULL; + } + + end = event->prev; + + if ( !end ) { + start = NULL; + } else { + end->next = NULL; + } + + event->next = NULL; + event->prev = NULL; + + return event; +} + +/* +=============== +idEventQueue::Enqueue +=============== +*/ +void idEventQueue::Enqueue( entityNetEvent_t *event, outOfOrderBehaviour_t behaviour ) { + if ( behaviour == OUTOFORDER_IGNORE ) { + // warn if an event comes in with a time before the last one in queue + // as that would cause the one we're adding to get delayed processing + if ( end && end->time > event->time ) { + common->Warning( "new event with id %d (time %d) is earlier than head event with id %d (time %d) - is OUTOFORDER_SORT needed?", event->event, event->time, end->event, end->time ); + } + } + + if ( behaviour == OUTOFORDER_DROP ) { + // go backwards through the queue and determine if there are + // any out-of-order events + while ( end && end->time > event->time ) { + entityNetEvent_t *outOfOrder = RemoveLast(); + common->Warning( "new event with id %d (time %d) caused removal of event with id %d (time %d), game time = %d", event->event, event->time, outOfOrder->event, outOfOrder->time, gameLocal.time ); + Free( outOfOrder ); + } + } else if ( behaviour == OUTOFORDER_SORT && end != NULL ) { + entityNetEvent_t *cur = end; + // iterate until we find a time < the new event's + while ( cur && cur->time > event->time ) { + cur = cur->prev; + } + if ( cur == end ) { + end = event; + } + if ( !cur ) { + // add to start + event->next = start; + event->prev = NULL; + start->prev = event; // start cannot be NULL because end is already != NULL + start = event; + } else { + // insert + event->prev = cur; + event->next = cur->next; + if ( cur->next != NULL ) { // insert possibly at the end so have to check that + cur->next->prev = event; + } + cur->next = event; + } + return; + } + + // add the new event + event->next = NULL; + event->prev = NULL; + + if ( end ) { + end->next = event; + event->prev = end; + } else { + start = event; + } + end = event; +} + +// RAVEN BEGIN +// shouchard: ban list stuff here + +/* +================ +idGameLocal::LoadBanList +================ +*/ +void idGameLocal::LoadBanList() { + + // open file + idStr token; + idFile *banFile = fileSystem->OpenFileRead( BANLIST_FILENAME ); + mpBanInfo_t banInfo; + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::LoadBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + // parse file (read three consecutive strings per banInfo (real complex ;) ) ) + while ( banFile->ReadString( token ) > 0 ) { + // name + banInfo.name = token; + + // guid + if ( banFile->ReadString( token ) > 0 && token.Length() >= 11 ) { + idStr::Copynz( banInfo.guid, token.c_str(), CLIENT_GUID_LENGTH ); + + banList.Append( banInfo ); + continue; + } + + gameLocal.Warning( "idGameLocal::LoadBanList: Potential curruption of banlist file (%s).", BANLIST_FILENAME ); + } + fileSystem->CloseFile( banFile ); + + banListLoaded = true; + banListChanged = false; +} + +/* +================ +idGameLocal::SaveBanList +================ +*/ +void idGameLocal::SaveBanList() { + if ( !banListChanged ) { + return; + } + + // open file + idFile *banFile = fileSystem->OpenFileWrite( BANLIST_FILENAME ); + if ( NULL == banFile ) { + common->DPrintf( "idGameLocal::SaveBanList: unable to open ban list file!\n" ); // fixme: need something better here + return; + } + + for ( int i = 0; i < banList.Num(); i++ ) { + const mpBanInfo_t& banInfo = banList[ i ]; + char temp[ 16 ] = { 0, }; + banFile->WriteString( va( "%s", banInfo.name.c_str() ) ); + idStr::Copynz( temp, banInfo.guid, CLIENT_GUID_LENGTH ); + banFile->WriteString( temp ); +// idStr::Copynz( temp, (const char*)banInfo.ip, 15 ); +// banFile->WriteString( "255.255.255.255" ); + } + fileSystem->CloseFile( banFile ); + banListChanged = false; +} + +/* +================ +idGameLocal::FlushBanList +================ +*/ +void idGameLocal::FlushBanList() { + banList.Clear(); + banListLoaded = false; + banListChanged = false; +} + +/* +================ +idGameLocal::IsPlayerBanned +================ +*/ +bool idGameLocal::IsPlayerBanned( const char *name ) { + assert( name ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( name, banList[ i ].name ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::IsGuidBanned +================ +*/ +bool idGameLocal::IsGuidBanned( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we found one return true + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + return true; + } + } + + return false; +} + +/* +================ +idGameLocal::AddGuidToBanList +================ +*/ +void idGameLocal::AddGuidToBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + mpBanInfo_t banInfo; + char name[ 512 ]; // TODO: clean this up + gameLocal.GetPlayerName( gameLocal.GetClientNumByGuid( guid ), name ); + banInfo.name = name; + idStr::Copynz( banInfo.guid, guid, CLIENT_GUID_LENGTH ); +// SIMDProcessor->Memset( banInfo.ip, 0xFF, 15 ); + banList.Append( banInfo ); + banListChanged = true; +} + +/* +================ +idGameLocal::RemoveGuidFromBanList +================ +*/ +void idGameLocal::RemoveGuidFromBanList( const char *guid ) { + assert( guid ); + + if ( !banListLoaded ) { + LoadBanList(); + } + + // check vs. each line in the list, if we find a match remove it. + for ( int i = 0; i < banList.Num(); i++ ) { + if ( 0 == idStr::Icmp( guid, banList[ i ].guid ) ) { + banList.RemoveIndex( i ); + banListChanged = true; + return; + } + } +} + +/* +================ +idGameLocal::RegisterClientGuid +================ +*/ +void idGameLocal::RegisterClientGuid( int clientNum, const char *guid ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + assert( guid ); + memset( clientGuids[ clientNum ], 0, CLIENT_GUID_LENGTH ); // just in case + idStr::Copynz( clientGuids[ clientNum ], guid, CLIENT_GUID_LENGTH ); +} + +/* +================ +idGameLocal::GetBanListCount +================ +*/ +int idGameLocal::GetBanListCount() { + if ( !banListLoaded ) { + LoadBanList(); + } + + return banList.Num(); +} + +/* +================ +idGameLocal::GetBanListEntry +================ +*/ +const mpBanInfo_t* idGameLocal::GetBanListEntry( int entry ) { + if ( !banListLoaded ) { + LoadBanList(); + } + + if ( entry < 0 || entry >= banList.Num() ) { + return NULL; + } + + return &banList[ entry ]; +} + +/* +================ +idGameLocal::GetGuidByClientNum +================ +*/ +const char *idGameLocal::GetGuidByClientNum( int clientNum ) { + assert( clientNum >= 0 && clientNum < numClients ); + + return clientGuids[ clientNum ]; +} + +/* +================ +idGameLocal::GetClientNumByGuid +================ +*/ +int idGameLocal::GetClientNumByGuid( const char * guid ) { + assert( guid ); + + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( !idStr::Icmp( networkSystem->GetClientGUID( i ), guid ) ) { + return i; + } + } + return -1; +} + +// mekberg: send ban list to client +/* +================ +idGameLocal::ServerSendBanList +================ +*/ +void idGameLocal::ServerSendBanList( int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + + if ( !banListLoaded ) { + LoadBanList(); + } + + int i; + int c = banList.Num(); + for ( i = 0; i < c; i++ ) { + outMsg.WriteString( banList[ i ].name.c_str() ); + outMsg.WriteString( banList[ i ].guid, CLIENT_GUID_LENGTH ); + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); +} + +// mekberg: so we can populate ban list outside of multiplayer game +/* +=================== +idGameLocal::PopulateBanList +=================== +*/ +void idGameLocal::PopulateBanList( idUserInterface* hud ) { + if ( !hud ) { + return; + } + + int bans = GetBanListCount(); + for ( int i = 0; i < bans; i++ ) { + const mpBanInfo_t * banInfo = GetBanListEntry( i ); + hud->SetStateString( va( "sa_banList_item_%d", i ), va( "%d: %s\t%s", i+1, banInfo->name.c_str(), banInfo->guid ) ); + } + hud->DeleteStateVar( va( "sa_banList_item_%d", bans ) ); + hud->SetStateString( "sa_banList_sel_0", "-1" ); + // used to trigger a redraw, was slow, and doesn't seem to do anything so took it out. fixes #13675 + hud->StateChanged( gameLocal.time, false ); +} +// RAVEN END + +/* +================ +idGameLocal::ServerSendInstanceReliableMessageExcluding +Works like networkSystem->ServerSendReliableMessageExcluding, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessageExcluding( const idEntity* owner, int excludeClient, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if ( owner == NULL ) { + assert( false ); // does not happen in q4mp, previous revisions of the code would have crashed + networkSystem->ServerSendReliableMessageExcluding( excludeClient, msg ); // NOTE: will also transmit through repeater + return; + } + + for ( i = 0; i < numClients; i++ ) { + if ( i == excludeClient ) { + continue; + } + + if ( entities[ i ] == NULL ) { + continue; + } + + if ( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg, true ); + } + + if ( owner->GetInstance() == 0 ) { + // record this message into the demo client if server demo is active + // the message will also go in the server demo data through the above individual client ServerSendReliableMessage, + // but will be ignored on replay unless you are following that particular client + networkSystem->ServerSendReliableMessage( MAX_CLIENTS, msg, true ); + + networkSystem->RepeaterSendReliableMessageExcluding( excludeClient, msg ); + } +} + +/* +================ +idGameLocal::ServerSendInstanceReliableMessage +Works like networkSystem->ServerSendReliableMessage, but only sends to entities in the owner's instance +================ +*/ +void idGameLocal::ServerSendInstanceReliableMessage( const idEntity* owner, int clientNum, const idBitMsg& msg ) { + int i; + assert( isServer ); + + if ( owner == NULL ) { + networkSystem->ServerSendReliableMessage( clientNum, msg ); // NOTE: will also transmit through repeater + return; + } + + if ( clientNum == -1 ) { + for ( i = 0; i < numClients; i++ ) { + if ( entities[ i ] == NULL ) { + continue; + } + + if ( entities[ i ]->GetInstance() != owner->GetInstance() ) { + continue; + } + + networkSystem->ServerSendReliableMessage( i, msg, true ); + } + + if ( owner->GetInstance() == 0 ) { + // see ServerSendInstanceReliableMessageExcluding + networkSystem->ServerSendReliableMessage( MAX_CLIENTS, msg, true ); + networkSystem->RepeaterSendReliableMessage( -1, msg ); + } + } else { + assert( false ); // not used in q4mp + if ( entities[ clientNum ] && entities[ clientNum ]->GetInstance() == owner->GetInstance() ) { + networkSystem->ServerSendReliableMessage( clientNum, msg, true ); + + if ( owner->GetInstance() == 0 ) { + for ( i = 0; i < maxViewer; i++ ) { + if ( !viewers[ i ].active ) { + continue; + } + + if ( viewers[ i ].origin.followClient != clientNum && !viewers[ i ].nopvs ) { + continue; + } + + networkSystem->RepeaterSendReliableMessage( clientNum, msg, false, i ); + } + } + } + } +} + +/* +=============== +idGameLocal::RepeaterAppendUnreliableMessage +=============== +*/ +void idGameLocal::RepeaterAppendUnreliableMessage( int icl, const idBitMsg &msg, const idBitMsg *header ) { + assert( isRepeater ); + + // send the normally unreliable message as a reliable message + if ( ( viewers[ icl ].nopvs && ( cvarSystem->GetCVarInteger( "g_repeaterReliableOnly" ) >= 1 ) ) || ( cvarSystem->GetCVarInteger( "g_repeaterReliableOnly" ) >= 2 ) ) { + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + idBitMsg reliable; + + reliable.Init( msgBuf, sizeof( msgBuf ) ); + reliable.WriteByte( GAME_RELIABLE_MESSAGE_UNRELIABLE_MESSAGE ); + if ( header ) { + reliable.WriteData( header->GetData(), header->GetSize() ); + } + reliable.WriteData( msg.GetData(), msg.GetSize() ); + + networkSystem->RepeaterSendReliableMessage( icl, reliable ); + return; + } + + // send the message + if ( header ) { + viewerUnreliableMessages[ icl ].AddConcat( header->GetData(), header->GetSize(), msg.GetData(), msg.GetSize(), false ); + } else { + viewerUnreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } +} + +/* +=============== +idGameLocal::RepeaterUnreliableMessage +for spectating support, we have to loop through the clients and emit to the spectator client too +note that a clientNum == -1 means send to everyone +header and msg are concatenated in case a header is needed. +=============== +*/ +void idGameLocal::RepeaterUnreliableMessage( const idBitMsg &msg, const int clientNum, const idBitMsg *header ) { + if ( !isRepeater ) { + return; + } + + int icl; + + for ( icl = 0; icl < maxViewer; icl++ ) { + if ( !viewers[ icl ].active ) { + continue; + } + + if ( clientNum != -1 && viewers[ icl ].origin.followClient != clientNum && !viewers[ icl ].nopvs ) { + // not global, not to the followed client + + // not following anyone + if ( viewers[ icl ].origin.followClient == -1 ) { + continue; + } + + // the followed player doesn't exist + if ( !entities[ viewers[ icl ].origin.followClient ] ) { + continue; + } + + // the followed player isn't a player + if ( !entities[ viewers[ icl ].origin.followClient ]->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + // following a valid player + + idPlayer *player = static_cast< idPlayer * >( entities[ viewers[ icl ].origin.followClient ] ); + if ( !player->spectating || viewers[ icl ].origin.followClient != player->spectator ) { + // .. but not the right player + continue; + } + + // fallthrough + } + + RepeaterAppendUnreliableMessage( icl, msg, header ); + } +} + +/* +=============== +idGameLocal::SendUnreliableMessage +for spectating support, we have to loop through the clients and emit to the spectator client too +note that a clientNum == -1 means send to everyone +=============== +*/ +void idGameLocal::SendUnreliableMessage( const idBitMsg &msg, const int clientNum ) { + int icl; + idPlayer *player; + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // not to local client + // note that if local is spectated he will still get it + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( icl != clientNum ) { + player = static_cast< idPlayer * >( entities[ icl ] ); + // drop all clients except the ones that follow the client we emit to + if ( !player->spectating || player->spectator != clientNum ) { + continue; + } + } + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING || isRepeater ) { + // record the type and destination for remap on readback + idBitMsg dest; + byte msgBuf[ 16 ]; + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_CLIENTNUM ); + dest.WriteChar( clientNum ); + + if ( demoState == DEMO_RECORDING ) { + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } + + RepeaterUnreliableMessage( msg, clientNum, &dest ); + } +} + +/* +=============== +idGameLocal::RepeaterUnreliableMessagePVS +Forwards an unreliable message to viewers based on PVS. +Concats header and msg in case a header needs to be added. +=============== +*/ +void idGameLocal::RepeaterUnreliableMessagePVS( const idBitMsg &msg, const int *areas, int numAreas, const idBitMsg *header ) { + if ( !isRepeater ) { + return; + } + + int i, icl; + pvsHandle_t pvsHandle[ 2 ]; + assert( numAreas <= 2 ); + + for ( i = 0; i < numAreas; i++ ) { + pvsHandle[ i ] = pvs.SetupCurrentPVS( areas[ i ], PVS_NORMAL ); + } + + for ( icl = 0; icl < maxViewer; icl ++ ) { + if ( !viewers[ icl ].active ) { + continue; + } + + // if no areas are given, this is a global emit; also always emit for repeaters + if ( numAreas && !viewers[ icl ].nopvs ) { + // only send if pvs says this client can see it + for ( i = 0; i < numAreas; i++ ) { + if ( pvs.InCurrentPVS( pvsHandle[ i ], viewers[ icl ].pvsArea ) ) { + RepeaterAppendUnreliableMessage( icl, msg, header ); + break; + } + } + } else { + // global, so always send it + RepeaterAppendUnreliableMessage( icl, msg, header ); + } + } + + for ( i = 0; i < numAreas; i++ ) { + pvs.FreeCurrentPVS( pvsHandle[ i ] ); + } +} + +/* +=============== +idGameLocal::SendUnreliableMessagePVS +instanceEnt to NULL for no instance checks +excludeClient to -1 for no exclusions +=============== +*/ +void idGameLocal::SendUnreliableMessagePVS( const idBitMsg &msg, const idEntity *instanceEnt, int area1, int area2 ) { + int icl; + int matchInstance = instanceEnt ? instanceEnt->GetInstance() : -1; + idPlayer *player; + int areas[ 2 ]; + int numEvAreas; + + numEvAreas = 0; + if ( area1 != -1 ) { + areas[ 0 ] = area1; + numEvAreas++; + } + if ( area2 != -1 ) { + areas[ numEvAreas ] = area2; + numEvAreas++; + } + + for ( icl = 0; icl < numClients; icl++ ) { + if ( icl == localClientNum ) { + // local client is always excluded + continue; + } + if ( !entities[ icl ] ) { + continue; + } + if ( matchInstance >= 0 && entities[ icl ]->GetInstance() != matchInstance ) { + continue; + } + if ( clientsPVS[ icl ].i < 0 ) { + // clients for which we don't have PVS info won't get anything + continue; + } + player = static_cast< idPlayer * >( entities[ icl ] ); + + // if no areas are given, this is a global emit + if ( numEvAreas ) { + // ony send if pvs says this client can see it + if ( !pvs.InCurrentPVS( clientsPVS[ icl ], areas, numEvAreas ) ) { + continue; + } + } + + unreliableMessages[ icl ].Add( msg.GetData(), msg.GetSize(), false ); + } + + if ( demoState == DEMO_RECORDING || isRepeater ) { + // record the target areas to the message + idBitMsg dest; + byte msgBuf[ 16 ]; + + // Tourney games: only record from instance 0 + if ( !instanceEnt || instanceEnt->GetInstance() == 0 ) { + + dest.Init( msgBuf, sizeof( msgBuf ) ); + dest.WriteByte( GAME_UNRELIABLE_RECORD_AREAS ); + dest.WriteLong( area1 ); + dest.WriteLong( area2 ); + + if ( demoState == DEMO_RECORDING ) { + unreliableMessages[ MAX_CLIENTS ].AddConcat( dest.GetData(), dest.GetSize(), msg.GetData(), msg.GetSize(), false ); + } + + RepeaterUnreliableMessagePVS( msg, areas, numEvAreas, &dest ); + } + } +} + +/* +=============== +idGameLocal::ClientReadUnreliableMessages +=============== +*/ +void idGameLocal::ClientReadUnreliableMessages( const idBitMsg &_msg ) { + idMsgQueue localQueue; + int size; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + idBitMsg msg; + + localQueue.ReadFrom( _msg ); + + msg.Init( msgBuf, sizeof( msgBuf ) ); + while ( localQueue.Get( msg.GetData(), msg.GetMaxSize(), size, false ) ) { + msg.SetSize( size ); + msg.BeginReading(); + ProcessUnreliableMessage( msg ); + msg.BeginWriting(); + } +} + +/* +=============== +idGameLocal::DemoReplayInAreas +checks if our current demo replay view ( server demo ) matches the areas given +=============== +*/ +bool idGameLocal::IsDemoReplayInAreas( const int areas[2], int numAreas ) { + idVec3 view; + pvsHandle_t handle; + + bool ret; + + assert( IsServerDemoPlaying() ); + + if ( !numAreas ) { + return true; + } + + if ( followPlayer == -1 ) { + view = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + } else { + view = entities[ followPlayer ]->GetPhysics()->GetOrigin(); + } + + // could probably factorize this, at least for processing all unreliable messages, maybe at a higher level of the loop? + handle = pvs.SetupCurrentPVS( view ); + ret = pvs.InCurrentPVS( handle, areas, numAreas ); + pvs.FreeCurrentPVS( handle ); + return ret; +} + +/* +=============== +idGameLocal::ProcessUnreliableMessage +=============== +*/ +void idGameLocal::ProcessUnreliableMessage( const idBitMsg &msg ) { + if ( IsServerDemoPlaying() ) { + int record_type = msg.ReadByte(); + assert( record_type < GAME_UNRELIABLE_RECORD_COUNT ); + switch ( record_type ) { + case GAME_UNRELIABLE_RECORD_CLIENTNUM: { + int client = msg.ReadChar(); + + RepeaterUnreliableMessage( msg, client ); + + if ( client != -1 ) { + // unreliable was targetted + if ( followPlayer != client ) { + // either free flying, or following someone else + return; + } + } + break; + } + case GAME_UNRELIABLE_RECORD_AREAS: { + int areas[ 2 ]; + int numEvAreas = 0; + + areas[ numEvAreas ] = msg.ReadLong(); + if ( areas[ numEvAreas ] != -1 ) ++numEvAreas; + areas[ numEvAreas ] = msg.ReadLong(); + if ( areas[ numEvAreas ] != -1 ) ++numEvAreas; + + RepeaterUnreliableMessagePVS( msg, areas, numEvAreas ); + + if ( !IsDemoReplayInAreas( areas, numEvAreas ) ) { + return; + } + break; + } + } + } + + int type = msg.ReadByte(); + switch ( type ) { + case GAME_UNRELIABLE_MESSAGE_EVENT: { + idEntityPtr p; + int spawnId = msg.ReadBits( 32 ); + p.SetSpawnId( spawnId ); + + if ( p.GetEntity() ) { + p.GetEntity()->ClientReceiveEvent( msg.ReadByte(), time, msg ); + } else { + Warning( "ProcessUnreliableMessage: no local entity 0x%x for event %d", spawnId & ( ( 1 << GENTITYNUM_BITS ) - 1 ), msg.ReadByte() ); + } + break; + } + case GAME_UNRELIABLE_MESSAGE_EFFECT: { + idCQuat quat; + idVec3 origin, origin2; + rvClientEffect* effect; + effectCategory_t category; + const idDecl *decl; + + decl = idGameLocal::ReadDecl( msg, DECL_EFFECT ); + + origin.x = msg.ReadFloat( ); + origin.y = msg.ReadFloat( ); + origin.z = msg.ReadFloat( ); + + quat.x = msg.ReadFloat( ); + quat.y = msg.ReadFloat( ); + quat.z = msg.ReadFloat( ); + + origin2.x = msg.ReadFloat( ); + origin2.y = msg.ReadFloat( ); + origin2.z = msg.ReadFloat( ); + + category = ( effectCategory_t )msg.ReadByte(); + + bool loop = msg.ReadBits( 1 ) != 0; + bool predictBit = msg.ReadBits( 1 ) != 0; + + if ( bse->CanPlayRateLimited( category ) && ( !predictBit || !g_predictedProjectiles.GetBool() ) ) { + effect = new rvClientEffect( decl ); + effect->SetOrigin( origin ); + effect->SetAxis( quat.ToMat3() ); + effect->Play( time, loop, origin2 ); + } + + break; + } + case GAME_UNRELIABLE_MESSAGE_HITSCAN: { + ClientHitScan( msg ); + break; + } +#ifdef _USE_VOICECHAT + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + mpGame.ReceiveAndPlayVoiceData( msg ); + break; + } +#else + case GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER: { + break; + } +#endif + default: { + Error( "idGameLocal::ProcessUnreliableMessage() - Unknown unreliable message '%d'\n", type ); + } + } +} + +/* +=============== +idGameLocal::WriteNetworkInfo +=============== +*/ +void idGameLocal::WriteNetworkInfo( idFile* file, int clientNum ) { + int i, j; + snapshot_t *snapshot; + entityState_t *entityState; + + if ( !IsServerDemoRecording() ) { + + // save the current states + for ( i = 0; i < MAX_GENTITIES; i++ ) { + entityState = clientEntityStates[clientNum][i]; + file->WriteBool( !!entityState ); + if ( entityState ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + } + + // save the PVS states + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->WriteInt( clientPVS[i][j] ); + } + } + + } + + // players ( including local client ) + j = 0; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + j++; + } + file->WriteInt( j ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( !entities[i] ) { + continue; + } + file->WriteInt( i ); + file->WriteInt( spawnIds[ i ] ); + } + + if ( !IsServerDemoRecording() ) { + + // write number of snapshots so on readback we know how many to allocate + i = 0; + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + i++; + } + file->WriteInt( i ); + + for ( snapshot = clientSnapshots[ clientNum ]; snapshot; snapshot = snapshot->next ) { + file->WriteInt( snapshot->sequence ); + + // write number of entity states in the snapshot + i = 0; + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + i++; + } + file->WriteInt( i ); + + for ( entityState = snapshot->firstEntityState; entityState; entityState = entityState->next ) { + file->WriteInt( entityState->entityNumber ); + file->WriteInt( entityState->state.GetSize() ); + file->Write( entityState->state.GetData(), entityState->state.GetSize() ); + } + + file->Write( snapshot->pvs, sizeof( snapshot->pvs ) ); + } + + } + + // write the 'initial reliables' data + mpGame.WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idGameLocal::ReadNetworkInfo +=============== +*/ +void idGameLocal::ReadNetworkInfo( int gameTime, idFile* file, int clientNum ) { + int i, j, num, numStates, stateSize; + snapshot_t *snapshot, **lastSnap; + entityState_t *entityState, **lastState; + + assert( clientNum == MAX_CLIENTS || !IsServerDemoPlaying() ); + InitLocalClient( clientNum ); + + time = gameTime; + previousTime = gameTime; + + // force new frame + realClientTime = 0; + isNewFrame = true; + + // clear the snapshot entity list + snapshotEntities.Clear(); + + if ( !IsServerDemoPlaying() || IsRepeaterDemoPlaying() ) { + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + bool isValid; + + file->ReadBool( isValid ); + if ( isValid ) { + clientEntityStates[clientNum][i] = entityStateAllocator.Alloc(); + entityState = clientEntityStates[clientNum][i]; + entityState->next = NULL; + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + } else { + clientEntityStates[clientNum][i] = NULL; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < ENTITY_PVS_SIZE; j++ ) { + file->ReadInt( clientPVS[i][j] ); + } + } + + } + + // spawn player entities. ( numClients is not a count but the watermark of client indexes ) + file->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + int icl, spawnId; + file->ReadInt( icl ); + file->ReadInt( spawnId ); + SpawnPlayer( icl ); + spawnIds[ icl ] = spawnId; + numClients = icl + 1; + } + + if ( !IsServerDemoPlaying() || IsRepeaterDemoPlaying() ) { + + file->ReadInt( num ); + lastSnap = &clientSnapshots[ localClientNum ]; + for ( i = 0; i < num; i++ ) { + snapshot = snapshotAllocator.Alloc(); + snapshot->firstEntityState = NULL; + snapshot->next = NULL; + file->ReadInt( snapshot->sequence ); + + file->ReadInt( numStates ); + lastState = &snapshot->firstEntityState; + for ( j = 0; j < numStates; j++ ) { + entityState = entityStateAllocator.Alloc(); + file->ReadInt( entityState->entityNumber ); + file->ReadInt( stateSize ); + entityState->state.Init( entityState->stateBuf, sizeof( entityState->stateBuf ) ); + entityState->state.SetSize( stateSize ); + file->Read( entityState->state.GetData(), stateSize ); + entityState->next = NULL; + assert( !(*lastState ) ); + *lastState = entityState; + lastState = &entityState->next; + } + + file->Read( snapshot->pvs, sizeof( snapshot->pvs ) ); + + assert( !(*lastSnap) ); + *lastSnap = snapshot; + lastSnap = &snapshot->next; + } + + // spawn entities + for ( i = 0; i < ENTITYNUM_NONE; i++ ) { + int spawnId, entityDefNumber; + idBitMsgDelta deltaMsg; + idDict args; + entityState_t *base = clientEntityStates[clientNum][i]; + idEntity *ent = entities[i]; + const idDeclEntityDef *decl; + + if ( !base ) { + continue; + } + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + + spawnId = deltaMsg.ReadBits( 32 - GENTITYNUM_BITS ); + entityDefNumber = deltaMsg.ReadBits( entityDefBits ); + + if ( !ent || ent->entityDefNumber != entityDefNumber || spawnId != spawnIds[ i ] ) { + + delete ent; + + spawnCount = spawnId; + + args.Clear(); + args.SetInt( "spawn_entnum", i ); + args.Set( "name", va( "entity%d", i ) ); + + // assume any items spawned from a server-snapshot are in our instance + if( gameLocal.GetLocalPlayer() ) { + args.SetInt( "instance", gameLocal.GetLocalPlayer()->GetInstance() ); + } + + assert( entityDefNumber >= 0 ); + if ( entityDefNumber >= declManager->GetNumDecls( DECL_ENTITYDEF ) ) { + Error( "server has %d entityDefs instead of %d", entityDefNumber, declManager->GetNumDecls( DECL_ENTITYDEF ) ); + } + decl = static_cast< const idDeclEntityDef * >( declManager->DeclByIndex( DECL_ENTITYDEF, entityDefNumber, false ) ); + assert( decl && decl->GetType() == DECL_ENTITYDEF ); + args.Set( "classname", decl->GetName() ); + if ( !SpawnEntityDef( args, &ent ) || !entities[i] ) { + Error( "Failed to spawn entity with classname '%s' of type '%s'", decl->GetName(), decl->dict.GetString("spawnclass") ); + } + } + + // add the entity to the snapshot list + ent->snapshotNode.AddToEnd( snapshotEntities ); + + // read the class specific data from the snapshot + ent->ReadFromSnapshot( deltaMsg ); + + // read the player state from player entities + if ( ent->entityNumber < MAX_CLIENTS ) { + if ( deltaMsg.ReadBits( 1 ) == 1 ) { + assert( ent->IsType( idPlayer::GetClassType() ) ); + static_cast< idPlayer * >( ent )->ReadPlayerStateFromSnapshot( deltaMsg ); + } + } + + // this is useful. for instance on idPlayer, resets stuff so powerups actually appear + ent->ClientUnstale(); + } + + { + // specific state read for game and player state + idBitMsgDelta deltaMsg; + entityState_t *base = clientEntityStates[clientNum][ENTITYNUM_NONE]; + + // it's possible to have a recording start right at CS_INGAME and not have a base for reading this yet + if ( base ) { + base->state.BeginReading(); + deltaMsg.InitReading( &base->state, NULL, NULL ); + ReadGameStateFromSnapshot( deltaMsg ); + } + } + + // set self spectating state according to userinfo settings + if ( !IsRepeaterDemoPlaying() ) { + GetLocalPlayer()->Spectate( idStr::Icmp( userInfo[ clientNum ].GetString( "ui_spectate" ), "Spectate" ) == 0 ); + } + + } + + // read the 'initial reliables' data + mpGame.ReadNetworkInfo( file, clientNum ); +} + +/* +============ +idGameLocal::SetDemoState +============ +*/ +void idGameLocal::SetDemoState( demoState_t state, bool _serverDemo, bool _timeDemo ) { + if ( demoState == DEMO_RECORDING && state == DEMO_NONE && serverDemo ) { + ServerClientDisconnect( MAX_CLIENTS ); + } + demoState = state; + serverDemo = _serverDemo; + timeDemo = _timeDemo; +} + +/* +============ +idGameLocal::SetRepeaterState +============ +*/ +void idGameLocal::SetRepeaterState( bool isRepeater, bool serverIsRepeater ) { + this->isRepeater = isRepeater; + this->serverIsRepeater = serverIsRepeater; + + if ( cvarSystem->GetCVarInteger( "net_serverDownload" ) == 3 ) { + networkSystem->HTTPEnable( this->isServer || this->isRepeater ); + } + + ReallocViewers( isRepeater ? cvarSystem->GetCVarInteger( "ri_maxViewers" ) : 0 ); +} + +/* +=============== +idGameLocal::ValidateDemoProtocol +=============== +*/ +bool idGameLocal::ValidateDemoProtocol( int minor_ref, int minor ) { +#if 0 + // 1.1 beta : 67 + // 1.1 final: 68 + // 1.2 : 69 + // 1.3 : 71 + + // let 1.3 play 1.2 demos - keep a careful eye on snapshotting changes + demo_protocol = minor; + return ( minor_ref == minor || ( minor_ref == 71 && minor == 69 ) ); +#else + demo_protocol = minor; + return ( minor_ref == minor ); +#endif +} + +/* +=============== +idGameLocal::RandomSpawn +=============== +*/ +idPlayerStart *idGameLocal::RandomSpawn( void ) { + return spawnSpots[ random.RandomInt( spawnSpots.Num() ) ]; +} diff --git a/src/mpgame/Healing_Station.cpp b/src/mpgame/Healing_Station.cpp new file mode 100644 index 0000000..427dd1f --- /dev/null +++ b/src/mpgame/Healing_Station.cpp @@ -0,0 +1,233 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Healing_Station.h" + +CLASS_DECLARATION( idAnimatedEntity, rvHealingStation ) +END_CLASS + +/* +================ +rvHealingStation::Think +================ +*/ +void rvHealingStation::Think ( void ) { + // TODO: I'm guessing this is bad, but I wanted to get this in so that people could start + // placing it. The entity decided to stop thinking and I didn't have time to debug it. + BecomeActive( TH_ALL ); + + stateThread.Execute(); + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + if ( healthDispensed > 0 ) { + CreateFrame( float( healthDispensed ) / maxHealth ); + } + Present(); + } +} + +/* +================ +rvHealingStation::Spawn +================ +*/ +void rvHealingStation::Spawn ( void ) { + entityToHeal = 0; + nextHealTime = 0; + healFrequency = spawnArgs.GetInt( "heal_frequency", "24" ); + healAmount = spawnArgs.GetInt( "heal_amount", "1" ); + + healthDispensed = 0; + soundStartTime = 0; + soundLength = 0; + maxHealth = spawnArgs.GetInt( "max_health", "100" ); + + dispenseAnim = GetAnimator()->GetAnim( spawnArgs.GetString( "dispense_anim", "dispense" ) ); + + CreateFrame( 0 ); + + stateThread.SetOwner( this ); + stateThread.SetName( GetName() ); + GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, GetAnimator()->GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 4 ); +} + +/* +================ +rvHealingStation::Save +================ +*/ +void rvHealingStation::Save ( idSaveGame *savefile ) const { + stateThread.Save( savefile ); + entityToHeal.Save ( savefile ); + savefile->WriteInt( nextHealTime ); + savefile->WriteInt( healFrequency ); + savefile->WriteInt( healAmount ); + savefile->WriteInt( healthDispensed ); + savefile->WriteInt( maxHealth ); + savefile->WriteInt( dispenseAnim ); + savefile->WriteInt( soundStartTime ); + savefile->WriteInt( soundLength ); +} + +/* +================ +rvHealingStation::Restore +================ +*/ +void rvHealingStation::Restore ( idRestoreGame *savefile ) { + stateThread.Restore( savefile, this ); + entityToHeal.Restore ( savefile ); + savefile->ReadInt( nextHealTime ); + savefile->ReadInt( healFrequency ); + savefile->ReadInt( healAmount ); + savefile->ReadInt( healthDispensed ); + savefile->ReadInt( maxHealth ); + savefile->ReadInt( dispenseAnim ); + savefile->ReadInt( soundStartTime ); + savefile->ReadInt( soundLength ); +} + +/* +================ +rvHealingStation::BeginHealing +================ +*/ +void rvHealingStation::BeginHealing ( idEntity *toHeal ) { + entityToHeal = toHeal; + stateThread.SetState( "Healing" ); +} + +/* +================ +rvHealingStation::EndHealing +================ +*/ +void rvHealingStation::EndHealing ( void ) { + entityToHeal = NULL; +} + +/* +================ +rvHealingStation::CreateFrame +================ +*/ +void rvHealingStation::CreateFrame ( float station_health ) { + // Update the GUI + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->SetStateFloat( "station_health", 1.0f - station_health ); + renderEntity.gui[ 0 ]->StateChanged( gameLocal.time, true ); + } + + // Update the Animation + int numFrames = GetAnimator()->GetAnim( dispenseAnim )->NumFrames(); + float lerp = numFrames * station_health; + int frame = lerp; + lerp = lerp - frame; + frameBlend_t frameBlend = { 0, frame, frame + 1, 1.0f - lerp, lerp }; + GetAnimator()->SetFrame( ANIMCHANNEL_ALL, dispenseAnim, frameBlend ); +} + +/* +================ +rvHealingStation::IsPlaying +================ +*/ +bool rvHealingStation::IsPlaying ( void ) { + idSoundEmitter* emitter = soundSystem->EmitterForIndex ( SOUNDWORLD_GAME, GetSoundEmitter ( ) ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvHealingStation ) + STATE ( "Healing", rvHealingStation::State_Healing ) +END_CLASS_STATES + +/* +================ +rvHealingStation::State_Healing +================ +*/ +stateResult_t rvHealingStation::State_Healing ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_DISPENSE, + }; + + if ( entityToHeal.IsValid() ) { + idPlayer* player = static_cast( entityToHeal.GetEntity( ) ); + const int entityMaxHealth = player->inventory.maxHealth; + + if ( healthDispensed < maxHealth && // and we have health to dispense... + entityToHeal->health < entityMaxHealth && // and the entity needs health. + entityToHeal->health > 0 ) // and he's still alive. + { + switch ( parms.stage ) { + case STAGE_INIT: + soundStartTime = gameLocal.time; + StartSound( "snd_start", SND_CHANNEL_ANY, 0, false, &soundLength ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time > soundStartTime + soundLength ) { + soundStartTime = 0; + soundLength = 0; + return SRESULT_STAGE ( STAGE_DISPENSE ); + } + return SRESULT_WAIT; + + case STAGE_DISPENSE: + if ( gameLocal.time > nextHealTime ) { // If it's time to heal... + int healthGiven = Min( maxHealth - healthDispensed, Min( healAmount, entityMaxHealth - entityToHeal->health ) ); + entityToHeal->health += healthGiven; + healthDispensed += healthGiven; + nextHealTime = gameLocal.time + healFrequency; + } + if ( !IsPlaying ( ) ) { + StartSound( "snd_loop", SND_CHANNEL_ANY, 0, false, NULL ); + } + return SRESULT_WAIT; + } + } + } + + StopSound ( SND_CHANNEL_ANY, 0 ); + StartSound ( "snd_stop", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_DONE; +} diff --git a/src/mpgame/Healing_Station.h b/src/mpgame/Healing_Station.h new file mode 100644 index 0000000..4acab43 --- /dev/null +++ b/src/mpgame/Healing_Station.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + + rvHealingStation + +=============================================================================== +*/ +class rvHealingStation : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvHealingStation ); + + virtual void Think ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BeginHealing ( idEntity *toHeal ); + void EndHealing ( void ); + +protected: + + void CreateFrame ( float station_health ); + + stateResult_t State_Healing ( const stateParms_t& parms ); + + rvStateThread stateThread; + idEntityPtr entityToHeal; + int nextHealTime; + int healFrequency; + int healAmount; + int healthDispensed; + int maxHealth; + int dispenseAnim; + int soundStartTime; + int soundLength; + +private: + + bool IsPlaying ( void ); + + CLASS_STATES_PROTOTYPE ( rvHealingStation ); +}; + diff --git a/src/mpgame/IK.cpp b/src/mpgame/IK.cpp new file mode 100644 index 0000000..34d8431 --- /dev/null +++ b/src/mpgame/IK.cpp @@ -0,0 +1,1085 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idIK + +=============================================================================== +*/ + +/* +================ +idIK::idIK +================ +*/ +idIK::idIK( void ) { + ik_activate = false; + initialized = false; + self = NULL; + animator = NULL; + modifiedAnim = 0; + modelOffset.Zero(); +} + +/* +================ +idIK::~idIK +================ +*/ +idIK::~idIK( void ) { +} + +/* +================ +idIK::Save +================ +*/ +void idIK::Save( idSaveGame *savefile ) const { + savefile->WriteBool( initialized ); + savefile->WriteBool( ik_activate ); + savefile->WriteObject( self ); + savefile->WriteString( animator != NULL && animator->GetAnim( modifiedAnim ) ? animator->GetAnim( modifiedAnim )->Name() : "" ); + //savefile->WriteInt( modifiedAnim ); // Recomputed during restore, do not save + savefile->WriteVec3( modelOffset ); +} + +/* +================ +idIK::Restore +================ +*/ +void idIK::Restore( idRestoreGame *savefile ) { + idStr anim; + + savefile->ReadBool( initialized ); + savefile->ReadBool( ik_activate ); + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadString( anim ); + //savefile->ReadInt( modifiedAnim ); // This is defined below + savefile->ReadVec3( modelOffset ); + + if ( self ) { + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Restore: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + } + } else { + animator = NULL; + modifiedAnim = 0; + } +} + +/* +================ +idIK::IsInitialized +================ +*/ +bool idIK::IsInitialized( void ) const { + return initialized && ik_enable.GetBool(); +} + +/* +================ +idIK::Init +================ +*/ +bool idIK::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + idRenderModel *model; + + if ( self == NULL ) { + return false; + } + + this->self = self; + + animator = self->GetAnimator(); + if ( animator == NULL || animator->ModelDef() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + if ( animator->ModelDef()->ModelHandle() == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) uses default model.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + model = animator->ModelHandle(); + if ( model == NULL ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no model set.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + modifiedAnim = animator->GetAnim( anim ); + if ( modifiedAnim == 0 ) { + gameLocal.Warning( "idIK::Init: IK for entity '%s' at (%s) has no modified animation.", + self->name.c_str(), self->GetPhysics()->GetOrigin().ToString(0) ); + return false; + } + + this->modelOffset = modelOffset; + + return true; +} + +/* +================ +idIK::Evaluate +================ +*/ +void idIK::Evaluate( void ) { +} + +/* +================ +idIK::ClearJointMods +================ +*/ +void idIK::ClearJointMods( void ) { + ik_activate = false; +} + +/* +================ +idIK::SolveTwoBones +================ +*/ +bool idIK::SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ) { + float length, lengthSqr, lengthInv, x, y; + idVec3 vec0, vec1; + + vec0 = endPos - startPos; + lengthSqr = vec0.LengthSqr(); + lengthInv = idMath::InvSqrt( lengthSqr ); + length = lengthInv * lengthSqr; + + // if the start and end position are too far out or too close to each other + if ( length > len0 + len1 || length < idMath::Fabs( len0 - len1 ) ) { + jointPos = startPos + 0.5f * vec0; + return false; + } + + vec0 *= lengthInv; + vec1 = dir - vec0 * dir * vec0; + vec1.Normalize(); + + x = ( length * length + len0 * len0 - len1 * len1 ) * ( 0.5f * lengthInv ); + y = idMath::Sqrt( len0 * len0 - x * x ); + + jointPos = startPos + x * vec0 + y * vec1; + + return true; +} + +/* +================ +idIK::GetBoneAxis +================ +*/ +float idIK::GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ) { + float length; + axis[0] = endPos - startPos; + length = axis[0].Normalize(); + axis[1] = dir - axis[0] * dir * axis[0]; + axis[1].Normalize(); + axis[2].Cross( axis[1], axis[0] ); + return length; +} + + +/* +=============================================================================== + + idIK_Walk + +=============================================================================== +*/ + +/* +================ +idIK_Walk::idIK_Walk +================ +*/ +idIK_Walk::idIK_Walk() { + int i; + + initialized = false; + footModel = NULL; + numLegs = 0; + enabledLegs = 0; + for ( i = 0; i < MAX_LEGS; i++ ) { + footJoints[i] = INVALID_JOINT; + ankleJoints[i] = INVALID_JOINT; + kneeJoints[i] = INVALID_JOINT; + hipJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + hipForward[i].Zero(); + kneeForward[i].Zero(); + upperLegLength[i] = 0.0f; + lowerLegLength[i] = 0.0f; + upperLegToHipJoint[i].Identity(); + lowerLegToKneeJoint[i].Identity(); + oldAnkleHeights[i] = 0.0f; + } + waistJoint = INVALID_JOINT; + + smoothing = 0.75f; + waistSmoothing = 0.5f; + footShift = 0.0f; + waistShift = 0.0f; + minWaistFloorDist = 0.0f; + minWaistAnkleDist = 0.0f; + footUpTrace = 32.0f; + footDownTrace = 32.0f; + tiltWaist = false; + usePivot = false; + + pivotFoot = -1; + pivotYaw = 0.0f; + pivotPos.Zero(); + + oldHeightsValid = false; + oldWaistHeight = 0.0f; + waistOffset.Zero(); +} + +/* +================ +idIK_Walk::~idIK_Walk +================ +*/ +idIK_Walk::~idIK_Walk() { + if ( footModel ) { + delete footModel; + } +} + +/* +================ +idIK_Walk::Save +================ +*/ +void idIK_Walk::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteClipModel( footModel ); + + savefile->WriteInt( numLegs ); + savefile->WriteInt( enabledLegs ); + savefile->Write( footJoints, sizeof( footJoints ) ); + savefile->Write( ankleJoints, sizeof( ankleJoints ) ); + savefile->Write( kneeJoints, sizeof( kneeJoints ) ); + savefile->Write( hipJoints, sizeof( hipJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + savefile->Write( &waistJoint, sizeof( waistJoint ) ); + + savefile->Write( hipForward, sizeof( hipForward ) ); + savefile->Write( kneeForward, sizeof( kneeForward ) ); + + savefile->Write( upperLegLength, sizeof( upperLegLength ) ); + savefile->Write( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Write( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Write( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->WriteFloat( smoothing ); + savefile->WriteFloat( waistSmoothing ); + savefile->WriteFloat( footShift ); + savefile->WriteFloat( waistShift ); + savefile->WriteFloat( minWaistFloorDist ); + savefile->WriteFloat( minWaistAnkleDist ); + savefile->WriteFloat( footUpTrace ); + savefile->WriteFloat( footDownTrace ); + savefile->WriteBool( tiltWaist ); + savefile->WriteBool( usePivot ); + + savefile->WriteInt( pivotFoot ); + savefile->WriteFloat( pivotYaw ); + savefile->WriteVec3( pivotPos ); + savefile->WriteBool( oldHeightsValid ); + savefile->WriteFloat( oldWaistHeight ); + savefile->Write( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->WriteVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Restore +================ +*/ +void idIK_Walk::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadClipModel( footModel ); + + savefile->ReadInt( numLegs ); + savefile->ReadInt( enabledLegs ); + savefile->Read( footJoints, sizeof( footJoints ) ); + savefile->Read( ankleJoints, sizeof( ankleJoints ) ); + savefile->Read( kneeJoints, sizeof( kneeJoints ) ); + savefile->Read( hipJoints, sizeof( hipJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + savefile->Read( &waistJoint, sizeof( waistJoint ) ); + + savefile->Read( hipForward, sizeof( hipForward ) ); + savefile->Read( kneeForward, sizeof( kneeForward ) ); + + savefile->Read( upperLegLength, sizeof( upperLegLength ) ); + savefile->Read( lowerLegLength, sizeof( lowerLegLength ) ); + + savefile->Read( upperLegToHipJoint, sizeof( upperLegToHipJoint ) ); + savefile->Read( lowerLegToKneeJoint, sizeof( lowerLegToKneeJoint ) ); + + savefile->ReadFloat( smoothing ); + savefile->ReadFloat( waistSmoothing ); + savefile->ReadFloat( footShift ); + savefile->ReadFloat( waistShift ); + savefile->ReadFloat( minWaistFloorDist ); + savefile->ReadFloat( minWaistAnkleDist ); + savefile->ReadFloat( footUpTrace ); + savefile->ReadFloat( footDownTrace ); + savefile->ReadBool( tiltWaist ); + savefile->ReadBool( usePivot ); + + savefile->ReadInt( pivotFoot ); + savefile->ReadFloat( pivotYaw ); + savefile->ReadVec3( pivotPos ); + savefile->ReadBool( oldHeightsValid ); + savefile->ReadFloat( oldWaistHeight ); + savefile->Read( oldAnkleHeights, sizeof( oldAnkleHeights ) ); + savefile->ReadVec3( waistOffset ); +} + +/* +================ +idIK_Walk::Init +================ +*/ +bool idIK_Walk::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + float footSize; + idVec3 verts[4]; + idTraceModel trm; + const char *jointName; + idVec3 dir, ankleOrigin, kneeOrigin, hipOrigin, dirOrigin; + idMat3 axis, ankleAxis, kneeAxis, hipAxis; + + static idVec3 footWinding[4] = { + idVec3( 1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, 1.0f, 0.0f ), + idVec3( -1.0f, -1.0f, 0.0f ), + idVec3( 1.0f, -1.0f, 0.0f ) + }; + + if ( !self ) { + return false; + } + + numLegs = Min( self->spawnArgs.GetInt( "ik_numLegs", "0" ), MAX_LEGS ); + if ( numLegs == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledLegs = 0; + + // get all the joints + for ( i = 0; i < numLegs; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_foot%d", i+1 ) ); + footJoints[i] = animator->GetJointHandle( jointName ); + if ( footJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid foot joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_ankle%d", i+1 ) ); + ankleJoints[i] = animator->GetJointHandle( jointName ); + if ( ankleJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid ankle joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_knee%d", i+1 ) ); + kneeJoints[i] = animator->GetJointHandle( jointName ); + if ( kneeJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid knee joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_hip%d", i+1 ) ); + hipJoints[i] = animator->GetJointHandle( jointName ); + if ( hipJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid hip joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_dir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledLegs |= 1 << i; + } + + jointName = self->spawnArgs.GetString( "ik_waist" ); + waistJoint = animator->GetJointHandle( jointName ); + if ( waistJoint == INVALID_JOINT ) { + gameLocal.Error( "idIK_Walk::Init: invalid waist joint '%s'\n", jointName ); + } + + // get the leg bone lengths and rotation matrices + for ( i = 0; i < numLegs; i++ ) { + oldAnkleHeights[i] = 0.0f; + + ankleAxis = joints[ ankleJoints[ i ] ].ToMat3(); + ankleOrigin = joints[ ankleJoints[ i ] ].ToVec3(); + + kneeAxis = joints[ kneeJoints[ i ] ].ToMat3(); + kneeOrigin = joints[ kneeJoints[ i ] ].ToVec3(); + + hipAxis = joints[ hipJoints[ i ] ].ToMat3(); + hipOrigin = joints[ hipJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - kneeOrigin; + } else { + dir.Set( 1.0f, 0.0f, 0.0f ); + } + + hipForward[i] = dir * hipAxis.Transpose(); + kneeForward[i] = dir * kneeAxis.Transpose(); + + // conversion from upper leg bone axis to hip joint axis + upperLegLength[i] = GetBoneAxis( hipOrigin, kneeOrigin, dir, axis ); + upperLegToHipJoint[i] = hipAxis * axis.Transpose(); + + // conversion from lower leg bone axis to knee joint axis + lowerLegLength[i] = GetBoneAxis( kneeOrigin, ankleOrigin, dir, axis ); + lowerLegToKneeJoint[i] = kneeAxis * axis.Transpose(); + } + + smoothing = self->spawnArgs.GetFloat( "ik_smoothing", "0.75" ); + waistSmoothing = self->spawnArgs.GetFloat( "ik_waistSmoothing", "0.75" ); + footShift = self->spawnArgs.GetFloat( "ik_footShift", "0" ); + waistShift = self->spawnArgs.GetFloat( "ik_waistShift", "0" ); + minWaistFloorDist = self->spawnArgs.GetFloat( "ik_minWaistFloorDist", "0" ); + minWaistAnkleDist = self->spawnArgs.GetFloat( "ik_minWaistAnkleDist", "0" ); + footUpTrace = self->spawnArgs.GetFloat( "ik_footUpTrace", "32" ); + footDownTrace = self->spawnArgs.GetFloat( "ik_footDownTrace", "32" ); + tiltWaist = self->spawnArgs.GetBool( "ik_tiltWaist", "0" ); + usePivot = self->spawnArgs.GetBool( "ik_usePivot", "0" ); + + // setup a clip model for the feet + footSize = self->spawnArgs.GetFloat( "ik_footSize", "4" ) * 0.5f; + if ( footSize > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + verts[i] = footWinding[i] * footSize; + } + trm.SetupPolygon( verts, 4 ); + footModel = new idClipModel( trm ); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Walk::Evaluate +================ +*/ +void idIK_Walk::Evaluate( void ) { + int i, newPivotFoot = 0; + float modelHeight, jointHeight, lowestHeight, floorHeights[MAX_LEGS]; + float shift, smallestShift, newHeight, step, newPivotYaw, height, largestAnkleHeight; + idVec3 modelOrigin, normal, hipDir, kneeDir, start, end, jointOrigins[MAX_LEGS]; + idVec3 footOrigin, ankleOrigin, kneeOrigin, hipOrigin, waistOrigin; + idMat3 modelAxis, waistAxis, axis; + idMat3 hipAxis[MAX_LEGS], kneeAxis[MAX_LEGS], ankleAxis[MAX_LEGS]; + trace_t results; + + if ( !self || !gameLocal.isNewFrame ) { + return; + } + + // if no IK enabled on any legs + if ( !enabledLegs ) { + return; + } + + normal = - self->GetPhysics()->GetGravityNormal(); + modelOrigin = self->GetPhysics()->GetOrigin(); + modelAxis = self->GetRenderEntity()->axis; + modelHeight = modelOrigin * normal; + + modelOrigin += modelOffset * modelAxis; + + // create frame without joint mods + animator->CreateFrame( gameLocal.time, false ); + + // get the joint positions for the feet + lowestHeight = idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + animator->GetJointTransform( footJoints[i], gameLocal.time, footOrigin, axis ); + jointOrigins[i] = modelOrigin + footOrigin * modelAxis; + jointHeight = jointOrigins[i] * normal; + if ( jointHeight < lowestHeight ) { + lowestHeight = jointHeight; + newPivotFoot = i; + } + } + + if ( usePivot ) { + + newPivotYaw = modelAxis[0].ToYaw(); + + // change pivot foot + if ( newPivotFoot != pivotFoot || idMath::Fabs( idMath::AngleNormalize180( newPivotYaw - pivotYaw ) ) > 30.0f ) { + pivotFoot = newPivotFoot; + pivotYaw = newPivotYaw; + animator->GetJointTransform( footJoints[pivotFoot], gameLocal.time, footOrigin, axis ); + pivotPos = modelOrigin + footOrigin * modelAxis; + } + + // keep pivot foot in place + jointOrigins[pivotFoot] = pivotPos; + } + + // get the floor heights for the feet + for ( i = 0; i < numLegs; i++ ) { + + if ( !( enabledLegs & ( 1 << i ) ) ) { + continue; + } + + start = jointOrigins[i] + normal * footUpTrace; + end = jointOrigins[i] - normal * footDownTrace; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, mat3_identity, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + floorHeights[i] = results.endpos * normal; + + if ( ik_debug.GetBool() && footModel ) { + idFixedWinding w; + for ( int j = 0; j < footModel->GetTraceModel()->numVerts; j++ ) { + w += footModel->GetTraceModel()->verts[j]; + } + gameRenderWorld->DebugWinding( colorRed, w, results.endpos, results.endAxis ); + } + } + + const idPhysics *phys = self->GetPhysics(); + + // test whether or not the character standing on the ground + bool onGround = phys->HasGroundContacts(); + + // test whether or not the character is standing on a plat + bool onPlat = false; + for ( i = 0; i < phys->GetNumContacts(); i++ ) { + idEntity *ent = gameLocal.entities[ phys->GetContact( i ).entityNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != NULL && ent->IsType( idPlat::GetClassType() ) ) { +// RAVEN END + onPlat = true; + break; + } + } + + // adjust heights of the ankles + smallestShift = idMath::INFINITY; + largestAnkleHeight = -idMath::INFINITY; + for ( i = 0; i < numLegs; i++ ) { + + if ( onGround && ( enabledLegs & ( 1 << i ) ) ) { + shift = floorHeights[i] - modelHeight + footShift; + } else { + shift = 0.0f; + } + + if ( shift < smallestShift ) { + smallestShift = shift; + } + + animator->GetJointTransform( ankleJoints[i], gameLocal.time, ankleOrigin, ankleAxis[i] ); + jointOrigins[i] = modelOrigin + ankleOrigin * modelAxis; + + height = jointOrigins[i] * normal; + + if ( oldHeightsValid && !onPlat ) { + step = height + shift - oldAnkleHeights[i]; + shift -= smoothing * step; + } + + newHeight = height + shift; + if ( newHeight > largestAnkleHeight ) { + largestAnkleHeight = newHeight; + } + + oldAnkleHeights[i] = newHeight; + + jointOrigins[i] += shift * normal; + } + + animator->GetJointTransform( waistJoint, gameLocal.time, waistOrigin, waistAxis ); + waistOrigin = modelOrigin + waistOrigin * modelAxis; + + // adjust position of the waist + waistOffset = ( smallestShift + waistShift ) * normal; + + // if the waist should be at least a certain distance above the floor + if ( minWaistFloorDist > 0.0f && waistOffset * normal < 0.0f ) { + start = waistOrigin; + end = waistOrigin + waistOffset - normal * minWaistFloorDist; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, results, start, end, footModel, modelAxis, CONTENTS_SOLID|CONTENTS_IKCLIP, self ); +// RAVEN END + height = ( waistOrigin + waistOffset - results.endpos ) * normal; + if ( height < minWaistFloorDist ) { + waistOffset += ( minWaistFloorDist - height ) * normal; + } + } + + // if the waist should be at least a certain distance above the ankles + if ( minWaistAnkleDist > 0.0f ) { + height = ( waistOrigin + waistOffset ) * normal; + if ( height - largestAnkleHeight < minWaistAnkleDist ) { + waistOffset += ( minWaistAnkleDist - ( height - largestAnkleHeight ) ) * normal; + } + } + + if ( oldHeightsValid ) { + // smoothly adjust height of waist + newHeight = ( waistOrigin + waistOffset ) * normal; + step = newHeight - oldWaistHeight; + waistOffset -= waistSmoothing * step * normal; + } + + // save height of waist for smoothing + oldWaistHeight = ( waistOrigin + waistOffset ) * normal; + + if ( !oldHeightsValid ) { + oldHeightsValid = true; + return; + } + + // solve IK + for ( i = 0; i < numLegs; i++ ) { + + // get the position of the hip in world space + animator->GetJointTransform( hipJoints[i], gameLocal.time, hipOrigin, axis ); + hipOrigin = modelOrigin + waistOffset + hipOrigin * modelAxis; + hipDir = hipForward[i] * axis * modelAxis; + + // get the IK bend direction + animator->GetJointTransform( kneeJoints[i], gameLocal.time, kneeOrigin, axis ); + kneeDir = kneeForward[i] * axis * modelAxis; + + // solve IK and calculate knee position + SolveTwoBones( hipOrigin, jointOrigins[i], kneeDir, upperLegLength[i], lowerLegLength[i], kneeOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, hipOrigin, kneeOrigin ); + gameRenderWorld->DebugLine( colorRed, kneeOrigin, jointOrigins[i] ); + gameRenderWorld->DebugLine( colorYellow, kneeOrigin, kneeOrigin + hipDir ); + gameRenderWorld->DebugLine( colorGreen, kneeOrigin, kneeOrigin + kneeDir ); + } + + // get the axis for the hip joint + GetBoneAxis( hipOrigin, kneeOrigin, hipDir, axis ); + hipAxis[i] = upperLegToHipJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the knee joint + GetBoneAxis( kneeOrigin, jointOrigins[i], kneeDir, axis ); + kneeAxis[i] = lowerLegToKneeJoint[i] * ( axis * modelAxis.Transpose() ); + } + + // set the joint mods + animator->SetJointAxis( waistJoint, JOINTMOD_WORLD_OVERRIDE, waistAxis ); + animator->SetJointPos( waistJoint, JOINTMOD_WORLD_OVERRIDE, ( waistOrigin + waistOffset - modelOrigin ) * modelAxis.Transpose() ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_WORLD_OVERRIDE, hipAxis[i] ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_WORLD_OVERRIDE, kneeAxis[i] ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_WORLD_OVERRIDE, ankleAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Walk::ClearJointMods +================ +*/ +void idIK_Walk::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + animator->SetJointAxis( waistJoint, JOINTMOD_NONE, mat3_identity ); + animator->SetJointPos( waistJoint, JOINTMOD_NONE, vec3_origin ); + for ( i = 0; i < numLegs; i++ ) { + animator->SetJointAxis( hipJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( kneeJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( ankleJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} + +/* +================ +idIK_Walk::EnableAll +================ +*/ +void idIK_Walk::EnableAll( void ) { + enabledLegs = ( 1 << numLegs ) - 1; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::DisableAll +================ +*/ +void idIK_Walk::DisableAll( void ) { + enabledLegs = 0; + oldHeightsValid = false; +} + +/* +================ +idIK_Walk::EnableLeg +================ +*/ +void idIK_Walk::EnableLeg( int num ) { + enabledLegs |= 1 << num; +} + +/* +================ +idIK_Walk::DisableLeg +================ +*/ +void idIK_Walk::DisableLeg( int num ) { + enabledLegs &= ~( 1 << num ); +} + + +/* +=============================================================================== + + idIK_Reach + +=============================================================================== +*/ + +/* +================ +idIK_Reach::idIK_Reach +================ +*/ +idIK_Reach::idIK_Reach() { + int i; + + initialized = false; + numArms = 0; + enabledArms = 0; + for ( i = 0; i < MAX_ARMS; i++ ) { + handJoints[i] = INVALID_JOINT; + elbowJoints[i] = INVALID_JOINT; + shoulderJoints[i] = INVALID_JOINT; + dirJoints[i] = INVALID_JOINT; + shoulderForward[i].Zero(); + elbowForward[i].Zero(); + upperArmLength[i] = 0.0f; + lowerArmLength[i] = 0.0f; + upperArmToShoulderJoint[i].Identity(); + lowerArmToElbowJoint[i].Identity(); + } +} + +/* +================ +idIK_Reach::~idIK_Reach +================ +*/ +idIK_Reach::~idIK_Reach() { +} + +/* +================ +idIK_Reach::Save +================ +*/ +void idIK_Reach::Save( idSaveGame *savefile ) const { + idIK::Save( savefile ); + + savefile->WriteInt( numArms ); + savefile->WriteInt( enabledArms ); + savefile->Write( handJoints, sizeof( handJoints ) ); + savefile->Write( elbowJoints, sizeof( elbowJoints ) ); + savefile->Write( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Write( dirJoints, sizeof( dirJoints ) ); + + savefile->Write( shoulderForward, sizeof( shoulderForward ) ); + savefile->Write( elbowForward, sizeof( elbowForward ) ); + + savefile->Write( upperArmLength, sizeof( upperArmLength ) ); + savefile->Write( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Write( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Write( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Restore +================ +*/ +void idIK_Reach::Restore( idRestoreGame *savefile ) { + idIK::Restore( savefile ); + + savefile->ReadInt( numArms ); + savefile->ReadInt( enabledArms ); + savefile->Read( handJoints, sizeof( handJoints ) ); + savefile->Read( elbowJoints, sizeof( elbowJoints ) ); + savefile->Read( shoulderJoints, sizeof( shoulderJoints ) ); + savefile->Read( dirJoints, sizeof( dirJoints ) ); + + savefile->Read( shoulderForward, sizeof( shoulderForward ) ); + savefile->Read( elbowForward, sizeof( elbowForward ) ); + + savefile->Read( upperArmLength, sizeof( upperArmLength ) ); + savefile->Read( lowerArmLength, sizeof( lowerArmLength ) ); + + savefile->Read( upperArmToShoulderJoint, sizeof( upperArmToShoulderJoint ) ); + savefile->Read( lowerArmToElbowJoint, sizeof( lowerArmToElbowJoint ) ); +} + +/* +================ +idIK_Reach::Init +================ +*/ +bool idIK_Reach::Init( idEntity *self, const char *anim, const idVec3 &modelOffset ) { + int i; + const char *jointName; + idTraceModel trm; + idVec3 dir, handOrigin, elbowOrigin, shoulderOrigin, dirOrigin; + idMat3 axis, handAxis, elbowAxis, shoulderAxis; + + if ( !self ) { + return false; + } + + numArms = Min( self->spawnArgs.GetInt( "ik_numArms", "0" ), MAX_ARMS ); + if ( numArms == 0 ) { + return true; + } + + if ( !idIK::Init( self, anim, modelOffset ) ) { + return false; + } + + int numJoints = animator->NumJoints(); + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( joints[0] ) ); + + // create the animation frame used to setup the IK + gameEdit->ANIM_CreateAnimFrame( animator->ModelHandle(), animator->GetAnim( modifiedAnim )->MD5Anim( 0 ), numJoints, joints, 1, animator->ModelDef()->GetVisualOffset() + modelOffset, animator->RemoveOrigin() ); + + enabledArms = 0; + + // get all the joints + for ( i = 0; i < numArms; i++ ) { + + jointName = self->spawnArgs.GetString( va( "ik_hand%d", i+1 ) ); + handJoints[i] = animator->GetJointHandle( jointName ); + if ( handJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid hand joint '%s'", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbow%d", i+1 ) ); + elbowJoints[i] = animator->GetJointHandle( jointName ); + if ( elbowJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid elbow joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_shoulder%d", i+1 ) ); + shoulderJoints[i] = animator->GetJointHandle( jointName ); + if ( shoulderJoints[i] == INVALID_JOINT ) { + gameLocal.Error( "idIK_Reach::Init: invalid shoulder joint '%s'\n", jointName ); + } + + jointName = self->spawnArgs.GetString( va( "ik_elbowDir%d", i+1 ) ); + dirJoints[i] = animator->GetJointHandle( jointName ); + + enabledArms |= 1 << i; + } + + // get the arm bone lengths and rotation matrices + for ( i = 0; i < numArms; i++ ) { + + handAxis = joints[ handJoints[ i ] ].ToMat3(); + handOrigin = joints[ handJoints[ i ] ].ToVec3(); + + elbowAxis = joints[ elbowJoints[ i ] ].ToMat3(); + elbowOrigin = joints[ elbowJoints[ i ] ].ToVec3(); + + shoulderAxis = joints[ shoulderJoints[ i ] ].ToMat3(); + shoulderOrigin = joints[ shoulderJoints[ i ] ].ToVec3(); + + // get the IK direction + if ( dirJoints[i] != INVALID_JOINT ) { + dirOrigin = joints[ dirJoints[ i ] ].ToVec3(); + dir = dirOrigin - elbowOrigin; + } else { + dir.Set( -1.0f, 0.0f, 0.0f ); + } + + shoulderForward[i] = dir * shoulderAxis.Transpose(); + elbowForward[i] = dir * elbowAxis.Transpose(); + + // conversion from upper arm bone axis to should joint axis + upperArmLength[i] = GetBoneAxis( shoulderOrigin, elbowOrigin, dir, axis ); + upperArmToShoulderJoint[i] = shoulderAxis * axis.Transpose(); + + // conversion from lower arm bone axis to elbow joint axis + lowerArmLength[i] = GetBoneAxis( elbowOrigin, handOrigin, dir, axis ); + lowerArmToElbowJoint[i] = elbowAxis * axis.Transpose(); + } + + initialized = true; + + return true; +} + +/* +================ +idIK_Reach::Evaluate +================ +*/ +void idIK_Reach::Evaluate( void ) { + int i; + idVec3 modelOrigin, shoulderOrigin, elbowOrigin, handOrigin, shoulderDir, elbowDir; + idMat3 modelAxis, axis; + idMat3 shoulderAxis[MAX_ARMS], elbowAxis[MAX_ARMS]; + trace_t trace; + + modelOrigin = self->GetRenderEntity()->origin; + modelAxis = self->GetRenderEntity()->axis; + + // solve IK + for ( i = 0; i < numArms; i++ ) { + + // get the position of the shoulder in world space + animator->GetJointTransform( shoulderJoints[i], gameLocal.time, shoulderOrigin, axis ); + shoulderOrigin = modelOrigin + shoulderOrigin * modelAxis; + shoulderDir = shoulderForward[i] * axis * modelAxis; + + // get the position of the hand in world space + animator->GetJointTransform( handJoints[i], gameLocal.time, handOrigin, axis ); + handOrigin = modelOrigin + handOrigin * modelAxis; + + // get first collision going from shoulder to hand +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( self, trace, shoulderOrigin, handOrigin, CONTENTS_SOLID, self ); +// RAVEN END + handOrigin = trace.endpos; + + // get the IK bend direction + animator->GetJointTransform( elbowJoints[i], gameLocal.time, elbowOrigin, axis ); + elbowDir = elbowForward[i] * axis * modelAxis; + + // solve IK and calculate elbow position + SolveTwoBones( shoulderOrigin, handOrigin, elbowDir, upperArmLength[i], lowerArmLength[i], elbowOrigin ); + + if ( ik_debug.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, shoulderOrigin, elbowOrigin ); + gameRenderWorld->DebugLine( colorRed, elbowOrigin, handOrigin ); + gameRenderWorld->DebugLine( colorYellow, elbowOrigin, elbowOrigin + elbowDir ); + gameRenderWorld->DebugLine( colorGreen, elbowOrigin, elbowOrigin + shoulderDir ); + } + + // get the axis for the shoulder joint + GetBoneAxis( shoulderOrigin, elbowOrigin, shoulderDir, axis ); + shoulderAxis[i] = upperArmToShoulderJoint[i] * ( axis * modelAxis.Transpose() ); + + // get the axis for the elbow joint + GetBoneAxis( elbowOrigin, handOrigin, elbowDir, axis ); + elbowAxis[i] = lowerArmToElbowJoint[i] * ( axis * modelAxis.Transpose() ); + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_WORLD_OVERRIDE, shoulderAxis[i] ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_WORLD_OVERRIDE, elbowAxis[i] ); + } + + ik_activate = true; +} + +/* +================ +idIK_Reach::ClearJointMods +================ +*/ +void idIK_Reach::ClearJointMods( void ) { + int i; + + if ( !self || !ik_activate ) { + return; + } + + for ( i = 0; i < numArms; i++ ) { + animator->SetJointAxis( shoulderJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( elbowJoints[i], JOINTMOD_NONE, mat3_identity ); + animator->SetJointAxis( handJoints[i], JOINTMOD_NONE, mat3_identity ); + } + + ik_activate = false; +} diff --git a/src/mpgame/IK.h b/src/mpgame/IK.h new file mode 100644 index 0000000..a117f7c --- /dev/null +++ b/src/mpgame/IK.h @@ -0,0 +1,178 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_IK_H__ +#define __GAME_IK_H__ + +/* +=============================================================================== + + IK base class with a simple fast two bone solver. + +=============================================================================== +*/ + +#define IK_ANIM "ik_pose" + +class idIK { +public: + idIK( void ); + virtual ~idIK( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsInitialized( void ) const; + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + bool SolveTwoBones( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, float len0, float len1, idVec3 &jointPos ); + float GetBoneAxis( const idVec3 &startPos, const idVec3 &endPos, const idVec3 &dir, idMat3 &axis ); + +protected: + bool initialized; + bool ik_activate; + idEntity * self; // entity using the animated model + idAnimator * animator; // animator on entity + int modifiedAnim; // animation modified by the IK + idVec3 modelOffset; +}; + + +/* +=============================================================================== + + IK controller for a walking character with an arbitrary number of legs. + +=============================================================================== +*/ + +class idIK_Walk : public idIK { +public: + + idIK_Walk( void ); + virtual ~idIK_Walk( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + + void EnableAll( void ); + void DisableAll( void ); + void EnableLeg( int num ); + void DisableLeg( int num ); + +private: + static const int MAX_LEGS = 8; + + idClipModel * footModel; + + int numLegs; + int enabledLegs; + jointHandle_t footJoints[MAX_LEGS]; + jointHandle_t ankleJoints[MAX_LEGS]; + jointHandle_t kneeJoints[MAX_LEGS]; + jointHandle_t hipJoints[MAX_LEGS]; + jointHandle_t dirJoints[MAX_LEGS]; + jointHandle_t waistJoint; + + idVec3 hipForward[MAX_LEGS]; + idVec3 kneeForward[MAX_LEGS]; + + float upperLegLength[MAX_LEGS]; + float lowerLegLength[MAX_LEGS]; + + idMat3 upperLegToHipJoint[MAX_LEGS]; + idMat3 lowerLegToKneeJoint[MAX_LEGS]; + + float smoothing; + float waistSmoothing; + float footShift; + float waistShift; + float minWaistFloorDist; + float minWaistAnkleDist; + float footUpTrace; + float footDownTrace; + bool tiltWaist; + bool usePivot; + + // state + int pivotFoot; + float pivotYaw; + idVec3 pivotPos; + bool oldHeightsValid; + float oldWaistHeight; + float oldAnkleHeights[MAX_LEGS]; + idVec3 waistOffset; +}; + + +/* +=============================================================================== + + IK controller for reaching a position with an arm or leg. + +=============================================================================== +*/ + +class idIK_Reach : public idIK { +public: + + idIK_Reach( void ); + virtual ~idIK_Reach( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Init( idEntity *self, const char *anim, const idVec3 &modelOffset ); + virtual void Evaluate( void ); + virtual void ClearJointMods( void ); + +private: + + static const int MAX_ARMS = 2; + + int numArms; + int enabledArms; + jointHandle_t handJoints[MAX_ARMS]; + jointHandle_t elbowJoints[MAX_ARMS]; + jointHandle_t shoulderJoints[MAX_ARMS]; + jointHandle_t dirJoints[MAX_ARMS]; + + idVec3 shoulderForward[MAX_ARMS]; + idVec3 elbowForward[MAX_ARMS]; + + float upperArmLength[MAX_ARMS]; + float lowerArmLength[MAX_ARMS]; + + idMat3 upperArmToShoulderJoint[MAX_ARMS]; + idMat3 lowerArmToElbowJoint[MAX_ARMS]; +}; + +#endif /* !__GAME_IK_H__ */ diff --git a/src/mpgame/Icon.cpp b/src/mpgame/Icon.cpp new file mode 100644 index 0000000..d4562f3 --- /dev/null +++ b/src/mpgame/Icon.cpp @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Icon.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Icon.h" + +/* +=============== +rvIcon::rvIcon +=============== +*/ +rvIcon::rvIcon() { + iconHandle = -1; +} + +/* +=============== +rvIcon::~rvIcon +=============== +*/ +rvIcon::~rvIcon() { + FreeIcon(); +} + +/* +=============== +rvIcon::FreeIcon +=============== +*/ +void rvIcon::FreeIcon( void ) { + if ( iconHandle != - 1 ) { + gameRenderWorld->FreeEntityDef( iconHandle ); + iconHandle = -1; + } +} + +/* +=============== +rvIcon::CreateIcon +=============== +*/ +qhandle_t rvIcon::CreateIcon( const char *mtr, int suppressViewID ) { + FreeIcon(); + + memset( &renderEnt, 0, sizeof( renderEnt ) ); + renderEnt.origin = vec3_origin; + renderEnt.axis = mat3_identity; + renderEnt.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = 16.0f; + renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = 16.0f; + renderEnt.hModel = renderModelManager->FindModel( "_sprite" ); + renderEnt.callback = NULL; + renderEnt.numJoints = 0; + renderEnt.joints = NULL; + renderEnt.customSkin = 0; + renderEnt.noShadow = true; + renderEnt.noSelfShadow = true; + renderEnt.customShader = declManager->FindMaterial( mtr ); + renderEnt.referenceShader = 0; + renderEnt.bounds = renderEnt.hModel->Bounds( &renderEnt ); + renderEnt.suppressSurfaceInViewID = suppressViewID; + + iconHandle = gameRenderWorld->AddEntityDef( &renderEnt ); + + return iconHandle; +} + +/* +=============== +rvIcon::UpdateIcon +=============== +*/ +void rvIcon::UpdateIcon( const idVec3 &origin, const idMat3 &axis ) { + assert( iconHandle >= 0 ); + + renderEnt.origin = origin; + renderEnt.axis = axis; + gameRenderWorld->UpdateEntityDef( iconHandle, &renderEnt ); +} + +int rvIcon::GetWidth( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_WIDTH ]; +} + +int rvIcon::GetHeight( void ) const { + return renderEnt.shaderParms[ SHADERPARM_SPRITE_HEIGHT ]; +} diff --git a/src/mpgame/Icon.h b/src/mpgame/Icon.h new file mode 100644 index 0000000..ef4b260 --- /dev/null +++ b/src/mpgame/Icon.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Icon.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICON_H__ +#define __ICON_H__ + +class rvIcon { +public: + rvIcon(); + ~rvIcon(); + void UpdateIcon( const idVec3 &origin, const idMat3 &axis ); + qhandle_t CreateIcon( const char *mtr, int suppressViewID = 0 ); + void FreeIcon( void ); + qhandle_t GetHandle( void ) const; + + int GetHeight( void ) const; + int GetWidth( void ) const; +private: + void Draw( jointHandle_t joint ); + void Draw( const idVec3 &origin ); + + renderEntity_t renderEnt; + qhandle_t iconHandle; +}; + +ID_INLINE qhandle_t rvIcon::GetHandle( void ) const { + return iconHandle; +} + +#endif /* !_ICON_H_ */ + diff --git a/src/mpgame/IconManager.cpp b/src/mpgame/IconManager.cpp new file mode 100644 index 0000000..ce0671e --- /dev/null +++ b/src/mpgame/IconManager.cpp @@ -0,0 +1,229 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// IconManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "IconManager.h" + +rvIconManager iconManagerLocal; +rvIconManager* iconManager = &iconManagerLocal; + +/* +=============================================================================== + + rvIconManager + +=============================================================================== +*/ + +void rvIconManager::AddIcon( int clientNum, const char* iconName ) { + assert( gameLocal.GetLocalPlayer() ); + + idPlayer* player = gameLocal.GetLocalPlayer(); + + icons[ clientNum ].Append( rvPair(new rvIcon(), gameLocal.time + ICON_STAY_TIME) ); + icons[ clientNum ][ icons[ clientNum ].Num() - 1 ].First()->CreateIcon( player->spawnArgs.GetString( iconName ), (clientNum == gameLocal.localClientNum ? gameLocal.localClientNum + 1 : 0) ); +} + +void rvIconManager::UpdateIcons( void ) { + if( gameLocal.GetLocalPlayer() == NULL || !gameLocal.GetLocalPlayer()->GetRenderView() ) { + return; + } + + // draw team icons + if( gameLocal.IsTeamGame() ) { + UpdateTeamIcons(); + } + + // draw chat icons + UpdateChatIcons(); + + // remove old icons and icons not in our snapshot + // ** if you want to have permanent icons, you'll have to add support + // ** for the icons to re-appear when the player comes back in your snapshot (like team icons and chat icons) + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( gameLocal.time > icons[ i ][ j ].Second() || (gameLocal.entities[ i ] && gameLocal.entities[ i ]->fl.networkStale) ) { + rvIcon* oldIcon = icons[ i ][ j ].First(); + oldIcon->FreeIcon(); + icons[ i ].RemoveIndex( j-- ); + delete oldIcon; + } + } + } + + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + // draw extra icons + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + if( player->IsHidden() || !icons[ i ].Num() ) { + continue; + } + + // distribute the icons appropriately + int maxHeight = 0; + int totalWidth = 0; + + for( int j = 0; j < icons[ i ].Num(); j++ ) { + if( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + totalWidth += icons[ i ][ j ].First()->GetWidth(); + } + + idVec3 centerIconPosition = player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + + + if( teamIcons[ player->entityNumber ].GetHandle() >= 0 ) { + centerIconPosition[ 2 ] += teamIcons[ player->entityNumber ].GetHeight(); + } + + int incrementalWidth = 0; + for( int j = 0; j < icons[ i ].Num(); j++ ) { + idVec3 iconPosition = centerIconPosition; + iconPosition += ( (-totalWidth / 2) + incrementalWidth + (icons[ i ][ j ].First()->GetWidth() / 2) ) * localPlayer->GetRenderView()->viewaxis[ 1 ]; + incrementalWidth += icons[ i ][ j ].First()->GetWidth(); + icons[ i ][ j ].First()->UpdateIcon( player->GetPhysics()->GetOrigin() + iconPosition, localPlayer->GetRenderView()->viewaxis ); + } + } + } +} + +void rvIconManager::UpdateTeamIcons( void ) { + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + return; + } + int localTeam = localPlayer->team; + bool spectating = localPlayer->spectating; + + if( localPlayer->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ localPlayer->spectator ]; + if( spec ) { + localTeam = spec->team; + localPlayer = spec; + } else { + localTeam = -1; + } + } + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.localClientNum == i ) { + continue; + } + + //if entity i is a player, manage his icon. + if( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale && !spectating ) { + idPlayer* player = static_cast(gameLocal.entities[ i ]); + + //if the player is alive and not hidden, show his icon. + if( player->team == localTeam && !player->IsHidden() && !player->pfl.dead && gameLocal.mpGame.IsInGame( i ) ) { + if( teamIcons[ i ].GetHandle() < 0 ) { + teamIcons[ i ].CreateIcon( player->spawnArgs.GetString( player->team ? "mtr_team_icon_strogg" : "mtr_team_icon_marine" ), (player == localPlayer ? localPlayer->entityNumber + 1 : 0) ); + } + teamIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + player->spawnArgs.GetVector( (player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ), localPlayer->GetRenderView()->viewaxis ); + //else, the player is hidden, dead, or otherwise not needing an icon-- free it. + } else { + if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } + //if entity i is not a player, free icon i from the map. + } else if( teamIcons[ i ].GetHandle() >= 0 ) { + teamIcons[ i ].FreeIcon(); + } + } +} + +void rvIconManager::UpdateChatIcons( void ) { + + int localInst = gameLocal.GetLocalPlayer()->GetInstance(); + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + if ( gameLocal.localClientNum == i ) { + continue; + } + + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) && !gameLocal.entities[ i ]->fl.networkStale ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[ i ] ); + + if ( player->isChatting && + !player->IsHidden() && + !( ( idPhysics_Player* )player->GetPhysics() )->IsDead() && + gameLocal.mpGame.IsInGame( i ) + && (localInst == player->GetInstance())) { + if ( chatIcons[ i ].GetHandle() < 0 ) { + chatIcons[ i ].CreateIcon( player->spawnArgs.GetString( "mtr_icon_chatting" ), ( player == gameLocal.GetLocalPlayer() ? player->entityNumber + 1 : 0) ); + } + int maxHeight = 0; + for ( int j = 0; j < icons[ i ].Num(); j++ ) { + if ( icons[ i ][ j ].First()->GetHeight() > maxHeight ) { + maxHeight = icons[ i ][ j ].First()->GetHeight(); + } + } + if ( teamIcons[ i ].GetHandle() >= 0 && teamIcons[ i ].GetHeight() > maxHeight ) { + maxHeight = teamIcons[ i ].GetHeight(); + } + idVec3 centerIconPosition = player->spawnArgs.GetVector( ( player->team ? "team_icon_height_strogg" : "team_icon_height_marine") ); + centerIconPosition[ 2 ] += maxHeight; + chatIcons[ i ].UpdateIcon( player->GetPhysics()->GetOrigin() + centerIconPosition, gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis ); + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } else if ( chatIcons[ i ].GetHandle() >= 0 ) { + chatIcons[ i ].FreeIcon(); + } + } +} + +/* +=============== +rvIconManager::Shutdown +=============== +*/ +void rvIconManager::Shutdown( void ) { + int i, j; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + for ( j = 0; j < icons[ i ].Num(); j++ ) { + icons[ i ][ j ].First()->FreeIcon(); + } + icons[ i ].Clear(); + teamIcons[ i ].FreeIcon(); + chatIcons[ i ].FreeIcon(); + } +} diff --git a/src/mpgame/IconManager.h b/src/mpgame/IconManager.h new file mode 100644 index 0000000..61ebd9b --- /dev/null +++ b/src/mpgame/IconManager.h @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// IconManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __ICONMANAGER_H__ +#define __ICONMANAGER_H__ + +#include "Icon.h" + +const int ICON_STAY_TIME = 2000; + +class rvIconManager { +public: + void AddIcon( int clientNum, const char* iconName ); + void UpdateIcons( void ); + void UpdateTeamIcons( void ); + void UpdateChatIcons( void ); + void Shutdown( void ); + +private: + idList > icons[ MAX_CLIENTS ]; + rvIcon teamIcons[ MAX_CLIENTS ]; + rvIcon chatIcons[ MAX_CLIENTS ]; +}; + +extern rvIconManager* iconManager; + +#endif diff --git a/src/mpgame/Instance.cpp b/src/mpgame/Instance.cpp new file mode 100644 index 0000000..102e0b6 --- /dev/null +++ b/src/mpgame/Instance.cpp @@ -0,0 +1,225 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Instance.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Instance.h" + +rvInstance::rvInstance( int id, bool deferPopulate ) { + instanceID = id; + spawnInstanceID = id; + + gameLocal.AddClipWorld( id ); + + mapEntityNumbers = NULL; + numMapEntities = 0; + + initialSpawnCount = idGameLocal::INITIAL_SPAWN_COUNT; + + if ( !deferPopulate ) { + Populate(); + } +} + +rvInstance::~rvInstance() { + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + } + gameLocal.RemoveClipWorld( instanceID ); +} + +void rvInstance::Populate( int serverChecksum ) { + gameState_t currentState = gameLocal.GameState(); + + // disable the minSpawnIndex lock out + int latchMinSpawnIndex = gameLocal.minSpawnIndex; + gameLocal.minSpawnIndex = MAX_CLIENTS; + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( GAMESTATE_RESTART ); + } + + if ( gameLocal.isServer ) { + // When populating on a server, record the entity numbers + numMapEntities = gameLocal.GetNumMapEntities(); + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + if ( mapEntityNumbers ) { + delete[] mapEntityNumbers; + } + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + // read the index we should start populating at as transmitted by the server + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + //common->Printf( "pos: get starting index for instance %d sets firstFreeIndex to %d\n", instanceID, gameLocal.firstFreeIndex ); + + // remember the spawnCount ahead of time, so that the client can accurately reconstruct its spawnIds + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers, &initialSpawnCount ); + + // only build the message in MP + if ( gameLocal.isMultiplayer ) { + BuildInstanceMessage(); + + // force joins of anyone in our instance so they get potentially new map entitynumbers + for( int i = 0; i < MAX_CLIENTS; i++ ) { + PACIFIER_UPDATE; + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player && player->GetInstance() == instanceID ) { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg, true ); + } + } + } + } else { + // have the client produce a log of the entity layout so we can match it with the server's + // this is also going to be used to issue the EV_FindTargets below + if ( mapEntityNumbers ) { + delete []mapEntityNumbers; + } + numMapEntities = gameLocal.GetNumMapEntities(); + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); + mapEntityNumbers = new unsigned short[ numMapEntities ]; + RV_POP_HEAP(); + memset( mapEntityNumbers, -1, sizeof( unsigned short ) * numMapEntities ); + + gameLocal.firstFreeIndex = gameLocal.GetStartingIndexForInstance( instanceID ); + + gameLocal.SetSpawnCount( initialSpawnCount ); // that was transmitted through the instance msg + gameLocal.SpawnMapEntities( spawnInstanceID, NULL, mapEntityNumbers ); + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + if ( serverChecksum != 0 && checksum != serverChecksum ) { + common->Error( "client side map populate checksum ( 0x%x ) doesn't match server's ( 0x%x )", checksum, serverChecksum ); + } + } + + + for ( int i = 0; i < numMapEntities; i++ ) { + if ( mapEntityNumbers[ i ] < 0 || mapEntityNumbers[ i ] >= MAX_GENTITIES ) { + continue; + } + + if ( (i % 100) == 0 ) { + PACIFIER_UPDATE; + } + + idEntity* ent = gameLocal.entities[ mapEntityNumbers[ i ] ]; + + if ( ent ) { + ent->PostEventMS( &EV_FindTargets, 0 ); + ent->PostEventMS( &EV_PostSpawn, 0 ); + } + } + + if ( currentState != GAMESTATE_STARTUP ) { + gameLocal.SetGameState( currentState ); + } + + // re-enable the min spawn index + assert( latchMinSpawnIndex == MAX_CLIENTS || gameLocal.firstFreeIndex <= latchMinSpawnIndex ); + gameLocal.minSpawnIndex = latchMinSpawnIndex; +} + +void rvInstance::PopulateFromMessage( const idBitMsg& msg ) { + initialSpawnCount = msg.ReadShort(); + + delete[] mapEntityNumbers; + mapEntityNumbers = NULL; + + int populateIndex = msg.ReadLong(); + gameLocal.ClientSetStartingIndex( populateIndex ); + //common->Printf( "pos: set firstFreeIndex to %d\n", populateIndex ); + int checksum = msg.ReadLong(); + Populate( checksum ); +} + +void rvInstance::Restart( void ) { + if ( gameLocal.isMultiplayer ) { + Populate(); + } else { + gameLocal.SpawnMapEntities(); + } +} + +void rvInstance::BuildInstanceMessage( void ) { + // Build the client join instance msg + mapEntityMsg.BeginWriting(); + mapEntityMsg.Init( mapEntityMsgBuf, sizeof( byte ) * MAX_GAME_MESSAGE_SIZE ); + mapEntityMsg.WriteByte( GAME_RELIABLE_MESSAGE_SET_INSTANCE ); + + mapEntityMsg.WriteByte( instanceID ); + mapEntityMsg.WriteShort( initialSpawnCount ); + // we need to send that down for tourney so the index will match + mapEntityMsg.WriteLong( gameLocal.GetStartingIndexForInstance( instanceID ) ); + + LittleRevBytes( mapEntityNumbers, sizeof(unsigned short ), numMapEntities ); //DAJ + int checksum = MD5_BlockChecksum( mapEntityNumbers, sizeof( unsigned short ) * numMapEntities ); + //common->Printf( "pop: server checksum: 0x%x\n", checksum ); + mapEntityMsg.WriteLong( checksum ); +} + +void rvInstance::JoinInstance( idPlayer* player ) { + assert( player && gameLocal.isServer ); + + // Transmit the instance information to the new client + if( gameLocal.isListenServer && player == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ServerSetInstance( instanceID ); + } else { + networkSystem->ServerSendReliableMessage( player->entityNumber, mapEntityMsg, true ); + } +} + +void rvInstance::PrintMapNumbers( void ) { + gameLocal.Printf( "Instance: %d\n", instanceID ); + gameLocal.Printf( "Num Map Entities: %d\n", numMapEntities ); + + for( int i = 0; i < numMapEntities; i++ ) { + gameLocal.Printf( "%d\n", mapEntityNumbers[ i ] ); + } +} + +/* +================ +rvInstance::SetSpawnInstanceID +Sets the spawn instance ID for this instance. On the client, only instance 0 is ever +used, but it spawns in map entities for other instances. spawnInstanceID is used to +spawn map entities with the correct instance number on the client. +================ +*/ +void rvInstance::SetSpawnInstanceID( int newInstance ) { + assert( gameLocal.isClient ); + + spawnInstanceID = newInstance; +} diff --git a/src/mpgame/Instance.h b/src/mpgame/Instance.h new file mode 100644 index 0000000..2a115ee --- /dev/null +++ b/src/mpgame/Instance.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Instance.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __INSTANCE_H__ +#define __INSTANCE_H__ + +#include "Game_local.h" + +class rvInstance { +public: + rvInstance( int id, bool deferPopulate = false ); + ~rvInstance(); + + void Populate( int serverChecksum = 0 ); + void PopulateFromMessage( const idBitMsg& msg ); + void Restart( void ); + + void JoinInstance( idPlayer* player ); + int GetInstanceID( void ); + + void SetSpawnInstanceID( int newInstance ); + + void PrintMapNumbers( void ); + int GetNumMapEntities( void ) { return numMapEntities; } + unsigned short GetMapEntityNumber( int i ) { return mapEntityNumbers[ i ]; } + +private: + void BuildInstanceMessage( void ); + + int instanceID; + int spawnInstanceID; + unsigned short* mapEntityNumbers; + int numMapEntities; + int initialSpawnCount; + + idBitMsg mapEntityMsg; + byte mapEntityMsgBuf[ MAX_GAME_MESSAGE_SIZE ]; +}; + +ID_INLINE int rvInstance::GetInstanceID( void ) { + return instanceID; +} + +#endif diff --git a/src/mpgame/Item.cpp b/src/mpgame/Item.cpp new file mode 100644 index 0000000..abf86e9 --- /dev/null +++ b/src/mpgame/Item.cpp @@ -0,0 +1,2410 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/* +=============================================================================== + + idItem + +=============================================================================== +*/ + +const idEventDef EV_DropToFloor( "" ); +const idEventDef EV_RespawnItem( "respawn" ); +const idEventDef EV_RespawnFx( "" ); +const idEventDef EV_GetPlayerPos( "" ); +const idEventDef EV_HideObjective( "", "e" ); +const idEventDef EV_CamShot( "" ); + +// RAVEN BEGIN +// abahr: +const idEventDef EV_SetGravity( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idItem ) + EVENT( EV_DropToFloor, idItem::Event_DropToFloor ) + EVENT( EV_Touch, idItem::Event_Touch ) + EVENT( EV_Activate, idItem::Event_Trigger ) + EVENT( EV_RespawnItem, idItem::Event_Respawn ) + EVENT( EV_RespawnFx, idItem::Event_RespawnFx ) +// RAVEN BEGIN +// abahr + EVENT( EV_SetGravity, idItem::Event_SetGravity ) +// RAVEN END + +END_CLASS + + +/* +================ +idItem::idItem +================ +*/ +idItem::idItem() { + spin = false; + inView = false; + skin = NULL; + pickupSkin = NULL; + inViewTime = 0; + lastCycle = 0; + lastRenderViewTime = -1; + itemShellHandle = -1; + shellMaterial = NULL; + orgOrigin.Zero(); + canPickUp = true; + fl.networkSync = true; + trigger = NULL; + syncPhysics = false; + srvReady = -1; + clReady = -1; + effectIdle = NULL; + itemPVSArea = 0; + effectIdle = NULL; + simpleItem = false; + pickedUp = false; +} + +/* +================ +idItem::~idItem +================ +*/ +idItem::~idItem() { + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + } + if ( trigger ) { + delete trigger; + } + + SetPhysics( NULL ); +} + +/* +================ +idItem::Save +================ +*/ +void idItem::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteBool( spin ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( pickupSkin ); + + savefile->WriteVec3( orgOrigin ); + + savefile->WriteBool( pulse ); + savefile->WriteBool( canPickUp ); + + savefile->WriteStaticObject( physicsObj ); + +// savefile->WriteInt(itemShellHandle); // cnicholson: Set at end of Restore, do not save + savefile->WriteMaterial( shellMaterial ); + + savefile->WriteBool( inView ); + savefile->WriteInt( inViewTime ); + savefile->WriteInt( lastCycle ); + savefile->WriteInt( lastRenderViewTime ); +} + +/* +================ +idItem::Restore +================ +*/ +void idItem::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadBool( spin ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( pickupSkin ); + + savefile->ReadVec3( orgOrigin ); + + savefile->ReadBool( pulse ); + savefile->ReadBool( canPickUp ); + + savefile->ReadStaticObject ( physicsObj ); + +// savefile->ReadInt(itemShellHandle); // cnicholson: Set at end of function, do not restore + savefile->ReadMaterial( shellMaterial ); + + savefile->ReadBool( inView ); + savefile->ReadInt( inViewTime ); + savefile->ReadInt( lastCycle ); + savefile->ReadInt( lastRenderViewTime ); + + RestorePhysics( &physicsObj ); + + physicsObj.SetSelf( this ); + + itemShellHandle = -1; +} + +/* +================ +idItem::UpdateRenderEntity +================ +*/ +bool idItem::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) const { + if( simpleItem ) { + return false; + } + + if ( lastRenderViewTime == renderView->time ) { + return false; + } + + lastRenderViewTime = renderView->time; + + // check for glow highlighting if near the center of the view + idVec3 dir = renderEntity->origin - renderView->vieworg; + dir.Normalize(); + float d = dir * renderView->viewaxis[0]; + + // two second pulse cycle + float cycle = ( renderView->time - inViewTime ) / 2000.0f; + + if ( d > 0.94f ) { + if ( !inView ) { + inView = true; + if ( cycle > lastCycle ) { + // restart at the beginning + inViewTime = renderView->time; + cycle = 0.0f; + } + } + } else { + if ( inView ) { + inView = false; + lastCycle = ceil( cycle ); + } + } + + // fade down after the last pulse finishes + if ( !inView && cycle > lastCycle ) { + renderEntity->shaderParms[4] = 0.0f; + } else { + // pulse up in 1/4 second + cycle -= (int)cycle; + if ( cycle < 0.1f ) { + renderEntity->shaderParms[4] = cycle * 10.0f; + } else if ( cycle < 0.2f ) { + renderEntity->shaderParms[4] = 1.0f; + } else if ( cycle < 0.3f ) { + renderEntity->shaderParms[4] = 1.0f - ( cycle - 0.2f ) * 10.0f; + } else { + // stay off between pulses + renderEntity->shaderParms[4] = 0.0f; + } + } + + // update every single time this is in view + return true; +} + +/* +================ +idItem::UpdateTrigger +================ +*/ +void idItem::UpdateTrigger ( void ) { + // update trigger position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END +} + +/* +================ +idItem::ModelCallback +================ +*/ +bool idItem::ModelCallback( renderEntity_t *renderEntity, const renderView_t *renderView ) { + const idItem *ent; + + // this may be triggered by a model trace or other non-view related source + if ( !renderView ) { + return false; + } + + ent = static_cast(gameLocal.entities[ renderEntity->entityNum ]); + if ( !ent ) { + gameLocal.Error( "idItem::ModelCallback: callback with NULL game entity" ); + } + + return ent->UpdateRenderEntity( renderEntity, renderView ); +} + + +/* +================ +idItem::GetPhysicsToVisualTransform +================ +*/ +bool idItem::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if( simpleItem ) { + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetRenderView() ) { + if( gameLocal.GetLocalPlayer()->spectating ) { + idPlayer* spec = (idPlayer*)gameLocal.entities[ gameLocal.GetLocalPlayer()->spectator ]; + if( spec && spec->GetRenderView() ) { + axis = spec->GetRenderView()->viewaxis; + } + } else { + axis = gameLocal.GetLocalPlayer()->GetRenderView()->viewaxis; + } + } else { + // dedicated server for instance + axis = mat3_identity; + } + origin = idVec3( 0.0f, 0.0f, simpleItemScale / 2.0f ); + return true; + } + + if( !spin || (gameLocal.isServer && !gameLocal.isListenServer) ) { + return false; + } + + idAngles ang; + ang.pitch = ang.roll = 0.0f; + ang.yaw = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; + axis = ang.ToMat3() * GetPhysics()->GetAxis(); + + float scale = 0.005f; + float offset = entityNumber * 0.685145f; // rjohnson: just a random number here to shift the cos curve + + origin.Zero(); + + origin += GetPhysics()->GetAxis()[2] * (4.0f + idMath::Cos( ( ( gameLocal.time + 1000 ) * scale ) + offset ) * 4.0f); + + return true; +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idItem::Collide +================ +*/ +bool idItem::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_Remove, 0 ); + } + return false; +} +// RAVEN END + +/* +================ +idItem::Think +================ +*/ +void idItem::Think( void ) { + if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + UpdateTrigger(); + } + + if ( gameLocal.IsMultiplayer() && g_skipItemShadowsMP.GetBool() ) { + renderEntity.suppressShadowInViewID = gameLocal.localClientNum + 1; + } else { + renderEntity.suppressShadowInViewID = 0; + } + + if( !(simpleItem && pickedUp) ) { + UpdateVisuals(); + Present(); + } +} + +/* +================ +idItem::Present +================ +*/ +void idItem::Present( void ) { + idEntity::Present(); + + if ( !fl.hidden && pulse ) { + // also add a highlight shell model + renderEntity_t shell; + + shell = renderEntity; + + // we will mess with shader parms when the item is in view + // to give the "item pulse" effect + shell.callback = idItem::ModelCallback; + shell.entityNum = entityNumber; + shell.customShader = shellMaterial; + if ( itemShellHandle == -1 ) { + itemShellHandle = gameRenderWorld->AddEntityDef( &shell ); + } else { + gameRenderWorld->UpdateEntityDef( itemShellHandle, &shell ); + } + } +} + +/* +================ +idItem::InstanceJoin +================ +*/ +void idItem::InstanceJoin( void ) { + idEntity::InstanceJoin(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::InstanceLeave +================ +*/ +void idItem::InstanceLeave( void ) { + idEntity::InstanceLeave(); + + StopEffect( "fx_idle", true ); +} + +/* +================ +idItem::Spawn +================ +*/ +void idItem::Spawn( void ) { + idStr giveTo; + idEntity * ent; + idVec3 vSize; + idBounds bounds(vec3_origin); + + // check for triggerbounds, which allows for non-square triggers (useful for, say, a CTF flag) + if ( spawnArgs.GetVector( "triggerbounds", "16 16 16", vSize )) { + bounds.AddPoint(idVec3( vSize.x*0.5f, vSize.y*0.5f, 0.0f)); + bounds.AddPoint(idVec3(-vSize.x*0.5f, -vSize.y*0.5f, vSize.z)); + } + else { + // create a square trigger for item pickup + float tsize; + spawnArgs.GetFloat( "triggersize", "16.0", tsize ); + bounds.ExpandSelf( tsize ); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds )); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + + physicsObj.SetSelf ( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_SOLID ); + physicsObj.SetFriction( 0.0f, 0.0f, 6.0f ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_off" ) ) { + trigger->SetContents( 0 ); + Hide(); + } else { + trigger->SetContents( CONTENTS_TRIGGER ); + } + + giveTo = spawnArgs.GetString( "owner" ); + if ( giveTo.Length() ) { + ent = gameLocal.FindEntity( giveTo ); + if ( !ent ) { + gameLocal.Error( "Item couldn't find owner '%s'", giveTo.c_str() ); + } + PostEventMS( &EV_Touch, 0, ent, NULL ); + } + + if ( spawnArgs.GetBool( "spin" ) || gameLocal.isMultiplayer ) { + spin = true; + BecomeActive( TH_THINK ); + } + + // pulse ( and therefore itemShellHandle ) was taken out and shot. do not sync + //pulse = !spawnArgs.GetBool( "nopulse" ); + pulse = false; + orgOrigin = GetPhysics()->GetOrigin(); + + canPickUp = !( spawnArgs.GetBool( "triggerFirst" ) || spawnArgs.GetBool( "no_touch" ) ); + + inViewTime = -1000; + lastCycle = -1; + itemShellHandle = -1; +// RAVEN BEGIN +// abahr: move texture to def file for precaching + shellMaterial = declManager->FindMaterial( spawnArgs.GetString("mtr_highlight", "_default") ); + PostEventMS( &EV_SetGravity, 0 ); +// RAVEN END + if ( spawnArgs.GetString( "skin", NULL ) ) { + skin = declManager->FindSkin( spawnArgs.GetString( "skin" ), false ); + if( skin ) { + SetSkin( skin ); + srvReady = 1; + } + } else { + skin = NULL; + } + + if ( spawnArgs.GetString( "skin_pickup", NULL ) ) { + pickupSkin = declManager->FindSkin( spawnArgs.GetString( "skin_pickup" ), false ); + } else { + pickupSkin = NULL; + } + + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( srvReady == -1 ) { + srvReady = IsHidden() ? 0 : 1; + } + +// RAVEN BEGIN +// mekberg: added for removing pickups in mp in pits + if ( gameLocal.isMultiplayer ) { + trigger->SetContents( trigger->GetContents() | CONTENTS_ITEMCLIP ); + } +// RAVEN END + + if( gameLocal.isMultiplayer ) { + itemPVSArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + } else { + itemPVSArea = 0; + } + + simpleItemScale = spawnArgs.GetFloat( "simple_icon_scale", "20.0" ); + + simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !IsType( rvItemCTFFlag::GetClassType() ); + if( simpleItem ) { + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.axis = mat3_identity; + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_SPRITE_WIDTH ] = simpleItemScale; + renderEntity.shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = simpleItemScale; + renderEntity.hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + renderEntity.customSkin = 0; + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customShader = declManager->FindMaterial( spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity.referenceShader = 0; + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + SetAxis( mat3_identity ); + } else { + if ( spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + pickedUp = false; +} + +/* +================ +idItem::Event_SetGravity +================ +*/ +void idItem::Event_SetGravity() { + // If the item isnt a dropped item then see if it should settle itself + // to the floor or not + if ( !spawnArgs.GetBool( "dropped" ) ) { + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + PostEventMS( &EV_DropToFloor, 0 ); + } + } +} +// RAVEN END + +/* +================ +idItem::GetAttributes +================ +*/ +void idItem::GetAttributes( idDict &attributes ) { + int i; + const idKeyValue *arg; + + for( i = 0; i < spawnArgs.GetNumKeyVals(); i++ ) { + arg = spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Left( 4 ) == "inv_" ) { + attributes.Set( arg->GetKey().Right( arg->GetKey().Length() - 4 ), arg->GetValue() ); + } + } +} + +/* +================ +idItem::GiveToPlayer +================ +*/ +bool idItem::GiveToPlayer( idPlayer *player ) { + if ( player == NULL ) { + return false; + } + + if ( spawnArgs.GetBool( "inv_carry" ) ) { + return player->GiveInventoryItem( &spawnArgs ); + } + + // Handle the special ammo pickup that gives ammo for the weapon the player currently has + if ( spawnArgs.GetBool( "item_currentWeaponAmmo" ) ) { + const char *ammoName = player->weapon->GetAmmoNameForIndex(player->weapon->GetAmmoType()); + if ( player->weapon->TotalAmmoCount() != player->weapon->maxAmmo && player->weapon->AmmoRequired() ) { + player->GiveItem(ammoName); + player->GiveInventoryItem( &spawnArgs ); + return true; + } + return false; + } + + return player->GiveItem( this ); +} + +/* +=============== +idItem::SendPickupMsg +=============== +*/ +void idItem::SendPickupMsg( int clientNum ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_PICKUP ); + msg.WriteByte( clientNum ); + + // send as unreliable to client picking up the item, so it can play HUD things + gameLocal.SendUnreliableMessagePVS( msg, this, itemPVSArea ); +} + +/* +================ +idItem::Pickup +================ +*/ +bool idItem::Pickup( idPlayer *player ) { + //dropped weapon? + bool dropped = spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && !dropped && spawnArgs.FindKey( "weaponclass" ) + && gameLocal.IsWeaponsStayOn() && gameLocal.time > player->lastPickupTime + 1000 ) { + + idDict attr; + GetAttributes( attr ); + const idKeyValue* arg = attr.FindKey( "weapon" ); + + if ( arg ) { + if ( !player->inventory.Give( player, player->spawnArgs, arg->GetKey(), arg->GetValue(), NULL, false, dropped, true ) ) { + StartSound( "snd_noacquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + } + + // only predict noacquire on client + if ( gameLocal.isClient ) { + return false; + } + + int givenToPlayer = spawnArgs.GetInt( "givenToPlayer", "-1" ); + if ( player == NULL || ( givenToPlayer != -1 && givenToPlayer != player->entityNumber ) ) { + // idPlayer::GiveItem spawns an idItem for pickup, which appears at the origin before being picked + // and could sometimes be picked by someone else, particularly in buy mode when there is a play spawn sitting on the origin ;-) + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + player->lastPickupTime = gameLocal.time; + + //if a placed item and si_weaponStay is on and we're a weapon, don't remove and respawn + if ( gameLocal.IsMultiplayer() ) { + if ( !dropped ) { + if ( spawnArgs.FindKey( "weaponclass" ) ) { + if ( gameLocal.IsWeaponsStayOn() ) { + return true; + } + } + } + } + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model, or switch to the pickup skin + if ( pickupSkin ) { + SetSkin( pickupSkin ); + srvReady = 0; + } else { + Hide(); + BecomeInactive( TH_THINK ); + } + + pickedUp = true; + + // allow SetSkin or Hide() to get called regardless of simpleitem mode + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + // remove the highlight shell + if ( itemShellHandle != -1 ) { + gameRenderWorld->FreeEntityDef( itemShellHandle ); + itemShellHandle = -1; + } + + // asalmon: Added option for a differnt respawn rate based on gametype. + float respawn = spawnArgs.GetFloat(va("respawn_%s",gameLocal.serverInfo.GetString( "si_gameType" )), "-1.0"); + if( respawn == -1.0f ) { + respawn = spawnArgs.GetFloat( "respawn", "5.0" ); + } + + bool no_respawn = spawnArgs.GetBool( "no_respawn" ); + + if ( !gameLocal.isMultiplayer ) { + respawn = 0.0f; + } else if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( givenToPlayer != -1 ) { + respawn = 0.0f; + } + } + + if ( respawn && !dropped && !no_respawn ) { + const char *sfx = spawnArgs.GetString( "fx_Respawn" ); + if ( sfx && *sfx ) { + PostEventSec( &EV_RespawnFx, respawn - 0.5f ); + } + PostEventSec( &EV_RespawnItem, respawn ); + } else if ( !spawnArgs.GetBool( "inv_objective" ) && !no_respawn ) { + // give some time for the pickup sound to play + // FIXME: Play on the owner + if ( !spawnArgs.GetBool( "inv_carry" ) ) { + PostEventMS( &EV_Remove, 5000 ); + } + } + + trigger->SetContents( 0 ); + + StopEffect( "fx_idle" ); + + return true; +} + +/* +================ +idItem::Hide +================ +*/ +void idItem::Hide( void ) { + srvReady = 0; + idEntity::Hide( ); + trigger->SetContents( 0 ); +} + +/* +================ +idItem::Show +================ +*/ +void idItem::Show( void ) { + srvReady = 1; + idEntity::Show( ); + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +================ +idItem::ClientStale +================ +*/ +bool idItem::ClientStale( void ) { + idEntity::ClientStale(); + + StopEffect( "fx_idle" ); + return false; +} + +/* +================ +idItem::ClientUnstale +================ +*/ +void idItem::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + UpdateModelTransform(); + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::ClientPredictionThink +================ +*/ +void idItem::ClientPredictionThink( void ) { + // only think forward because the state is not synced through snapshots + if ( !gameLocal.isNewFrame ) { + return; + } + Think(); +} + +/* +================ +idItem::WriteFromSnapshot +================ +*/ +void idItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + assert( srvReady != -1 ); + msg.WriteBits( ( srvReady == 1 ), 1 ); +} + +/* +================ +idItem::ReadFromSnapshot +================ +*/ +void idItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + int newReady = ( msg.ReadBits( 1 ) != 0 ); + idVec3 resetOrigin( 0, 0, 0 ); + // client spawns the ent with ready == -1 so the state set happens at least once + if ( newReady != clReady ) { + if ( newReady ) { + // g_simpleItems might force a hide even with a pickup skin + if ( pickupSkin ) { + SetSkin( skin ); + srvReady = 1; + } else { + SetSkin( skin ); + Show(); + } + + if( simpleItem ) { + UpdateVisuals(); + } + pickedUp = false; + + if ( effectIdle.GetEntity( ) ) { + UpdateModelTransform(); + effectIdle->SetOrigin( resetOrigin ); + } else if ( spawnArgs.GetString( "fx_idle" ) && !simpleItem ) { + UpdateModelTransform(); + effectIdle = PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } + } else { + if ( pickupSkin ) { + SetSkin( pickupSkin ); + srvReady = 0; + } else { + Hide(); + } + + if( simpleItem ) { + FreeModelDef(); + UpdateVisuals(); + } + + pickedUp = true; + + StopEffect( "fx_idle" ); + effectIdle = NULL; + } + } + clReady = newReady; +} + +/* +================ +idItem::Event_Pickup +================ +*/ +void idItem::Event_Pickup( int clientNum ) { + idPlayer *player; + + assert( gameLocal.isClient ); + + // play pickup sound + if ( !spawnArgs.GetBool( "globalAcquireSound" ) ) { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + if( clientNum == gameLocal.localClientNum ) { + player = (idPlayer*)gameLocal.entities[ clientNum ]; + player->lastPickupTime = gameLocal.time; + if ( player ) { + player->GiveItem( this ); + } + } +} + +/* +================ +idItem::ClientReceiveEvent +================ +*/ +bool idItem::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_PICKUP: { + int clientNum = msg.ReadByte(); + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + Event_Pickup( clientNum ); + } + return true; + } + case EVENT_RESPAWNFX: { + Event_RespawnFx(); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +================ +idItem::Event_DropToFloor +================ +*/ +void idItem::Event_DropToFloor( void ) { + // don't drop the floor if bound to another entity + if ( GetBindMaster() != NULL && GetBindMaster() != this ) { + return; + } + + physicsObj.DropToFloor( ); +} + +/* +================ +idItem::Event_Touch +================ +*/ +void idItem::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( !canPickUp ) { + return; + } + + Pickup( static_cast(other) ); +} + +/* +================ +idItem::Event_Trigger +================ +*/ +void idItem::Event_Trigger( idEntity *activator ) { + if ( !canPickUp && spawnArgs.GetBool( "triggerFirst" ) ) { + canPickUp = true; + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + Pickup( static_cast( activator ) ); + } +} + +/* +================ +idItem::Event_Respawn +================ +*/ +void idItem::Event_Respawn( void ) { + + // with simple items, re-show the item, but still set srvReady to true to let clients + // know how to deal + if ( pickupSkin ) { + srvReady = true; + if( !simpleItem ) { + SetSkin( skin ); + } + } + + if( !pickupSkin ) { + BecomeActive( TH_THINK ); + Show(); + } + + if( simpleItem ) { + Show(); + } + + pickedUp = false; + + inViewTime = -1000; + lastCycle = -1; + trigger->SetContents ( CONTENTS_TRIGGER ); + SetOrigin( orgOrigin ); + StartSound( "snd_respawn", SND_CHANNEL_ITEM, 0, false, NULL ); + PostEventMS( &EV_SetGravity, 0 ); + CancelEvents( &EV_RespawnItem ); // don't double respawn + + if ( !simpleItem && spawnArgs.GetString( "fx_idle" ) ) { + UpdateModelTransform(); + PlayEffect( "fx_idle", renderEntity.origin, renderEntity.axis, true ); + } +} + +/* +================ +idItem::Event_RespawnFx +================ +*/ +void idItem::Event_RespawnFx( void ) { + idBitMsg msg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_RESPAWNFX ); + // send unreliable PVS-sensitive + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ) ); + } + + if( !simpleItem ) { + gameLocal.PlayEffect( spawnArgs, "fx_respawn", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else { + // zinx - HACK - hardcoded sound to avoid new defs for 1.4. Please ensure it remains precached. + // FIXME - put the sound in the defs + if ( !idStr::Icmp( spawnArgs.GetString( "fx_respawn" ), "effects/mp/itemrespawn.fx" ) ) { + const idSoundShader *shader = declManager->FindSound( "mp_item_respawn" ); + StartSoundShader( shader, SND_CHANNEL_ITEM, 0, false, NULL ); + } + } +} + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +/* +=============== +idItemPowerup +=============== +*/ + +CLASS_DECLARATION( idItem, idItemPowerup ) +END_CLASS + +/* +================ +idItemPowerup::idItemPowerup +================ +*/ +idItemPowerup::idItemPowerup() { + time = 0; + type = 0; + droppedTime = 0; + unique = false; +} + +/* +================ +idItemPowerup::Save +================ +*/ +void idItemPowerup::Save( idSaveGame *savefile ) const { + savefile->WriteInt( time ); + savefile->WriteInt( type ); + savefile->WriteInt( droppedTime ); // cnicholson: Added unsaved var +} + +/* +================ +idItemPowerup::Restore +================ +*/ +void idItemPowerup::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( time ); + savefile->ReadInt( type ); + savefile->ReadInt( droppedTime ); // cnicholson: Added unrestored var +} + +/* +================ +idItemPowerup::Spawn +================ +*/ +void idItemPowerup::Spawn( void ) { + time = SEC2MS( spawnArgs.GetInt( "time", "30" ) ); + // SEC2MS screws up when we want -1 time (no expiration) + if( spawnArgs.GetInt( "time" ) == -1 ) { + time = -1; + } + + type = spawnArgs.GetInt( "type", "0" ); + + // If the powerup was dropped then make it dissapear using its remaining time. + if ( spawnArgs.GetBool( "dropped" ) && time != -1 ) { + droppedTime = gameLocal.time + time; + PostEventMS( &EV_Remove, time ); + } + + // unique powerpus won't respawn while a player has them + unique = spawnArgs.GetBool( "unique", "0" ); + if( unique ) { + spawnArgs.SetBool( "no_respawn", true ); + } + + if ( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else { + team = -1; + } +} + +/* +================ +idItemPowerup::GiveToPlayer +================ +*/ +bool idItemPowerup::GiveToPlayer( idPlayer *player ) { + if ( player == NULL || player->spectating ) { + return false; + } + + // only one arena CTF powerup at a time + if ( type >= POWERUP_AMMOREGEN && type <= POWERUP_SCOUT ) { + if ( ( player->inventory.powerups & ARENA_POWERUP_MASK ) != 0 ) { + return false; + } + } + + // in flavours of arena CTF (or are idItemPowerups only used in Arena? or even, are idItemPowerups MP only?), + // ensure that items with a team can only be picked up by members of that team + if ( gameLocal.IsMultiplayer() && gameLocal.IsTeamGame() && team >= 0 ) { + if( team != player->team ) { + return false; + } + } + + if ( droppedTime > 0 ) { + player->GivePowerUp( type, droppedTime - gameLocal.time ); + } else { + player->GivePowerUp( type, time ); + } + + // also call idItem::GiveToPlayer so any inv_* keywords get applied + idItem::GiveToPlayer( player ); + + return true; +} + +/* +================ +idItemPowerup::Think +================ +*/ +void idItemPowerup::Think( void ) { + int i; + + // idItem::Think() only needs to be called if we're spawned in + if( !IsHidden() || (pickupSkin && GetSkin() != pickupSkin ) ) { + // only get here if spawned in + idItem::Think(); + return; + } + + if( !unique ) { + // non-unique despawned powerups don't need to think + return; + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL ) { + continue; + } + + // only spawn back in if noone on your team has the powerup + if( p->PowerUpActive( type ) && p->team == team ) { + break; + } + } + + if( i >= gameLocal.numClients ) { + PostEventMS( &EV_RespawnItem, 0 ); + } +} + +/* +================ +idItemPowerup::Pickup +================ +*/ +bool idItemPowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + + if( gameLocal.isClient ) { + // no client-side powerup prediction + return false; + } + + pickup = idItem::Pickup( player ); + if( unique ) { + BecomeActive( TH_THINK ); + } + + return pickup; +} + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idObjective ) + EVENT( EV_Activate, idObjective::Event_Trigger ) + EVENT( EV_HideObjective, idObjective::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjective::Event_GetPlayerPos ) + EVENT( EV_CamShot, idObjective::Event_CamShot ) +END_CLASS + +/* +================ +idObjective::idObjective +================ +*/ +idObjective::idObjective() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjective::Save +================ +*/ +void idObjective::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjective::Restore +================ +*/ +void idObjective::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +// RAVEN BEGIN +// jnewquist: Don't do this on Xenon, we want prebuilt textures +#ifndef _XENON + PostEventMS( &EV_CamShot, 250 ); +#endif +// RAVEN END +} + +/* +================ +idObjective::Spawn +================ +*/ +void idObjective::Spawn( void ) { + Hide(); +// RAVEN BEGIN +// jnewquist: Only post a camshot event if the spawn args request it +#ifndef _XENON + const char *camName; + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + common->Warning( "SpawnArg camShot on %s is not recommended.", spawnArgs.GetString( "name" ) ); + PostEventMS( &EV_CamShot, 250 ); + } +#endif +// RAVEN END +} + +/* +================ +idObjective::Event_Screenshot +================ +*/ +void idObjective::Event_CamShot( ) { + const char *camName; +// RAVEN BEGIN +// bdube: changed screenshot location + idStr shotName = "gfx/objectives/"; + shotName += spawnArgs.GetString( "screenshot" ); + shotName.SetFileExtension( ".tga" ); + // RAVEN END + if ( spawnArgs.GetString( "camShot", "", &camName ) ) { + idEntity *ent = gameLocal.FindEntity( camName ); + if ( ent && ent->cameraTarget ) { + const renderView_t *view = ent->cameraTarget->GetRenderView(); + renderView_t fullView = *view; + fullView.width = SCREEN_WIDTH; + fullView.height = SCREEN_HEIGHT; + + // draw a view to a texture + renderSystem->CropRenderSize( 256, 256, true ); + gameRenderWorld->RenderScene( &fullView ); + // TTimo: I don't think this jives with SMP code, but I grepped through the final maps and didn't see any using it + renderSystem->CaptureRenderToFile( shotName ); + renderSystem->UnCrop(); + } + } +} + +/* +================ +idObjective::Event_Trigger +================ +*/ +void idObjective::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so when in a vehicle we update the vehicle hud +// twhitaker: changed player->hud to player->GetObjectiveHud(), to resolve all issues + if ( player && player->GetObjectiveHud() ) { +// bdube: changed screenshot location + idStr shotName = spawnArgs.GetString( "screenshot" ); + + player->GetObjectiveHud()->SetStateString( "screenshot", shotName ); + player->GetObjectiveHud()->SetStateString( "objective", "1" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->GiveObjective( spawnArgs.GetString( "objectivetitle" ), spawnArgs.GetString( "objectivetext" ), shotName ); + + // a tad slow but keeps from having to update all objectives in all maps with a name ptr + for( int i = 0; i < gameLocal.num_entities; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idObjectiveComplete::GetClassType() ) ) { +// RAVEN END + if ( idStr::Icmp( spawnArgs.GetString( "objectivetitle" ), gameLocal.entities[ i ]->spawnArgs.GetString( "objectivetitle" ) ) == 0 ){ + gameLocal.entities[ i ]->spawnArgs.SetBool( "objEnabled", true ); + break; + } + } + } + + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjective::Event_GetPlayerPos +================ +*/ +void idObjective::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjective::Event_HideObjective +================ +*/ +void idObjective::Event_HideObjective(idEntity *e) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +CLASS_DECLARATION( idItem, idMoveableItem ) + EVENT( EV_Gib, idMoveableItem::Event_Gib ) +END_CLASS + +/* +================ +idMoveableItem::idMoveableItem +================ +*/ +idMoveableItem::idMoveableItem() { +} + +/* +================ +idMoveableItem::~idMoveableItem +================ +*/ +idMoveableItem::~idMoveableItem() { + // If this entity has been allocated, but not spawned, the physics object will + // not have a self pointer, and will not unregister itself from the entity. + SetPhysics( NULL ); +} + +/* +================ +idMoveableItem::Save +================ +*/ +void idMoveableItem::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); +} + +/* +================ +idMoveableItem::Restore +================ +*/ +void idMoveableItem::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); +} + +/* +================ +idMoveableItem::Spawn +================ +*/ +void idMoveableItem::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + idStr clipModelName; + idBounds bounds; + + // if the model should be shrinked + if ( spawnArgs.GetBool( "clipshrink" ) ) { + trm.Shrink( CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + + // setup the physics + physicsObj.SetSelf( this ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // check if a clip model is set + spawnArgs.GetString( "itemclipmodel", "", clipModelName ); + if ( clipModelName[0] ) { + if ( collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + physicsObj.SetClipModel( new idClipModel( trm ), density ); + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + } else { + // fallback + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), density ); + } + +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + +// RAVEN BEGIN +// mekberg: added + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } +// RAVEN END +} + +/* +================ +idMoveableItem::Think +================ +*/ +void idMoveableItem::Think( void ) { + RunPhysics(); + UpdateTrigger( ); + Present(); +} + +/* +================ +idMoveableItem::DropItem +================ +*/ +idEntity *idMoveableItem::DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ) { + idDict args; + idEntity *item; + + args.Set( "classname", classname ); + args.Set( "dropped", "1" ); + + // we sometimes drop idMoveables here, so set 'nodrop' to 1 so that it doesn't get put on the floor + args.Set( "nodrop", "1" ); + + if ( activateDelay ) { + args.SetBool( "triggerFirst", true ); + } + + gameLocal.SpawnEntityDef( args, &item ); + if ( item ) { + // set item position + item->GetPhysics()->SetOrigin( origin ); + item->GetPhysics()->SetAxis( axis ); + item->GetPhysics()->SetLinearVelocity( velocity ); + item->UpdateVisuals(); + if ( activateDelay ) { + item->PostEventMS( &EV_Activate, activateDelay, item ); + } + if ( !removeDelay ) { + removeDelay = 5 * 60 * 1000; + } + // always remove a dropped item after 5 minutes in case it dropped to an unreachable location + item->PostEventMS( &EV_Remove, removeDelay ); + } + + return item; +} + +/* +================ +idMoveableItem::DropItems + + The entity should have the following key/value pairs set: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + "skin_drop" "skin name" + To drop multiple items the following key/value pairs can be used: + "def_dropItem" "item def" + "dropItemJoint" "joint name" + "dropItemRotation" "pitch yaw roll" + "dropItemOffset" "x y z" + where is an aribtrary string. +================ +*/ +void idMoveableItem::DropItems( idAnimatedEntity *ent, const char *type, idList *list ) { + const idKeyValue *kv; + const char *skinName, *c, *jointName; + idStr key, key2; + idVec3 origin; + idMat3 axis; + idAngles angles; + const idDeclSkin *skin; + jointHandle_t joint; + idEntity *item; + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), NULL ); + while ( kv ) { + + c = kv->GetKey().c_str() + kv->GetKey().Length(); + if ( idStr::Icmp( c - 5, "Joint" ) != 0 && idStr::Icmp( c - 8, "Rotation" ) != 0 ) { + + key = kv->GetKey().c_str() + 4; + key2 = key; + key += "Joint"; + key2 += "Offset"; + jointName = ent->spawnArgs.GetString( key ); + joint = ent->GetAnimator()->GetJointHandle( jointName ); + if ( !ent->GetJointWorldTransform( joint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", key.c_str(), jointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + if ( g_dropItemRotation.GetString()[0] ) { + angles.Zero(); + sscanf( g_dropItemRotation.GetString(), "%f %f %f", &angles.pitch, &angles.yaw, &angles.roll ); + } else { + key = kv->GetKey().c_str() + 4; + key += "Rotation"; + ent->spawnArgs.GetAngles( key, "0 0 0", angles ); + } + axis *= angles.ToMat3() * axis; + + origin += ent->spawnArgs.GetVector( key2, "0 0 0" ); + + item = DropItem( kv->GetValue(), origin, axis, vec3_origin, 0, 0 ); + if ( list && item ) { + list->Append( item ); + } + } + + kv = ent->spawnArgs.MatchPrefix( va( "def_drop%sItem", type ), kv ); + } + + // change the skin to hide all items + skinName = ent->spawnArgs.GetString( va( "skin_drop%s", type ) ); + if ( skinName[0] ) { + skin = declManager->FindSkin( skinName ); + ent->SetSkin( skin ); + } +} + +/* +====================== +idMoveableItem::WriteToSnapshot +====================== +*/ +void idMoveableItem::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +====================== +idMoveableItem::ReadFromSnapshot +====================== +*/ +void idMoveableItem::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +============ +idMoveableItem::Gib +============ +*/ +void idMoveableItem::Gib( const idVec3 &dir, const char *damageDefName ) { + // remove the entity + PostEventMS( &EV_Remove, 0 ); +} + +/* +============ +idMoveableItem::Event_Gib +============ +*/ +void idMoveableItem::Event_Gib( const char *damageDefName ) { + Gib( idVec3( 0, 0, 1 ), damageDefName ); +} + +/* +=============================================================================== + + idItemRemover + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idItemRemover ) + EVENT( EV_Activate, idItemRemover::Event_Trigger ) +END_CLASS + +/* +================ +idItemRemover::Spawn +================ +*/ +void idItemRemover::Spawn( void ) { +} + +/* +================ +idItemRemover::RemoveItem +================ +*/ +void idItemRemover::RemoveItem( idPlayer *player ) { + const char *remove; + + remove = spawnArgs.GetString( "remove" ); + player->RemoveInventoryItem( remove ); +} + +/* +================ +idItemRemover::Event_Trigger +================ +*/ +void idItemRemover::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + RemoveItem( static_cast(activator) ); + } +} + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +CLASS_DECLARATION( idItemRemover, idObjectiveComplete ) + EVENT( EV_Activate, idObjectiveComplete::Event_Trigger ) + EVENT( EV_HideObjective, idObjectiveComplete::Event_HideObjective ) + EVENT( EV_GetPlayerPos, idObjectiveComplete::Event_GetPlayerPos ) +END_CLASS + +/* +================ +idObjectiveComplete::idObjectiveComplete +================ +*/ +idObjectiveComplete::idObjectiveComplete() { + playerPos.Zero(); + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = 0; +// RAVEN END +} + +/* +================ +idObjectiveComplete::Save +================ +*/ +void idObjectiveComplete::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Restore +================ +*/ +void idObjectiveComplete::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idObjectiveComplete::Spawn +================ +*/ +void idObjectiveComplete::Spawn( void ) { + spawnArgs.SetBool( "objEnabled", false ); + Hide(); +} + +/* +================ +idObjectiveComplete::Event_Trigger +================ +*/ +void idObjectiveComplete::Event_Trigger( idEntity *activator ) { + if ( !spawnArgs.GetBool( "objEnabled" ) ) { + return; + } + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + RemoveItem( player ); + + if ( spawnArgs.GetString( "inv_objective", NULL ) ) { +// RAVEN BEGIN +// abahr: changed player->hud to player->GetHud so if in a vehicle we update that hud +// twhitaker: moved objective system to it's own hud, to solve vehicle hud issues. + if ( player->GetObjectiveHud() ) { + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); +// RAVEN END + player->CompleteObjective( spawnArgs.GetString( "objectivetitle" ) ); + PostEventMS( &EV_GetPlayerPos, 2000 ); +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + triggerTime = gameLocal.time; +// RAVEN END + } + } + } +} + +/* +================ +idObjectiveComplete::Event_GetPlayerPos +================ +*/ +void idObjectiveComplete::Event_GetPlayerPos() { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_HideObjective, 100, player ); + } +} + +/* +================ +idObjectiveComplete::Event_HideObjective +================ +*/ +void idObjectiveComplete::Event_HideObjective( idEntity *e ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->GetObjectiveHud() ) { + idVec3 v = player->GetPhysics()->GetOrigin(); + v -= playerPos; +// RAVEN BEGIN +// mekberg: hide time done internally now + if ( v.Length() > 64.0f || gameLocal.time > triggerTime + 5000 ) { +// RAVEN END + player->HideObjective ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + PostEventMS( &EV_HideObjective, 100, player ); + } + } +} + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ +CLASS_DECLARATION( idItemRemover, rvObjectiveFailed ) + EVENT( EV_Activate, rvObjectiveFailed::Event_Trigger ) +END_CLASS + +rvObjectiveFailed::rvObjectiveFailed ( void ) { +} + +/* +================ +rvObjectiveFailed::Event_Trigger +================ +*/ +void rvObjectiveFailed::Event_Trigger( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->GetObjectiveHud() ) { + return; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + return; + } + + player->GetObjectiveHud()->SetStateString( "objective", "2" ); + player->GetObjectiveHud()->SetStateString( "objectivetext", common->GetLocalizedString( spawnArgs.GetString( "objectivetext" ) ) ); + player->GetObjectiveHud()->SetStateString( "objectivetitle", common->GetLocalizedString( spawnArgs.GetString( "objectivetitle" ) ) ); + player->FailObjective( spawnArgs.GetString( "objectivetitle" ) ); +} + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +const idEventDef EV_ResetFlag ( "" ); +const idEventDef EV_LinkTrigger( "" ); +CLASS_DECLARATION( idItem, rvItemCTFFlag ) + EVENT( EV_ResetFlag, rvItemCTFFlag::Event_ResetFlag ) + EVENT( EV_LinkTrigger, rvItemCTFFlag::Event_LinkTrigger ) +END_CLASS + +/* +================ +rvItemCTFFlag::rvItemCTFFlag +================ +*/ +rvItemCTFFlag::rvItemCTFFlag() { +} + + +/* +================ +rvItemCTFFlag::Spawn +================ +*/ +void rvItemCTFFlag::Spawn () { +/* rjohnson: I fixed the crash so we don't need to remove them... + //don't spawn outside of CTF games + if( (gameLocal.gameType != GAME_CTF) && (gameLocal.gameType != GAME_1F_CTF) && + (gameLocal.gameType != GAME_ARENA_CTF) && (gameLocal.gameType != GAME_ARENA_1F_CTF) ){ + //don't spawn! + gameLocal.Error("Flag spawn on in non-CTF gametype."); + PostEventMS ( &EV_Remove, 0 ); + return; + } +*/ + spawnArgs.GetBool ( "dropped", "0", dropped ); + spawnArgs.GetInt ( "team", "0", team ); + + bool reset = false; + spawnArgs.GetBool ( "reset", "0", reset ); + + switch ( team ) { + case TEAM_MARINE: { + powerup = POWERUP_CTF_MARINEFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_MARINE ); + break; + } + case TEAM_STROGG: { + powerup = POWERUP_CTF_STROGGFLAG; + gameLocal.mpGame.SetFlagEntity( this, TEAM_STROGG ); + break; + } + case TEAM_MAX: { + powerup = POWERUP_CTF_ONEFLAG; + break; + } + default: + gameLocal.Warning ( "Unknown ctf flag team '%d' on entity '%s'", team, name.c_str() ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + if ( dropped ) { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_DROPPED ); + } + if ( reset ) { + //PostEventSec( &EV_ResetFlag, 1 ); + } else { + PostEventSec( &EV_ResetFlag, 30 ); + } + + // Let powerups settle for a frame before allowing them to be picked up - we need to + // make sure we hit the dropped state for VO, etc to work properly + trigger->SetContents( 0 ); + PostEventSec( &EV_LinkTrigger, 0 ); + } else { + if ( !gameLocal.isClient ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + } + + GetPhysics( )->SetClipMask( GetPhysics( )->GetClipMask( ) | CONTENTS_ITEMCLIP ); + + spin = false; +} + +/* +================ +rvItemCTFFlag::Event_LinkTrigger +================ +*/ +void rvItemCTFFlag::Event_LinkTrigger( void ) { + trigger->SetContents( CONTENTS_TRIGGER | CONTENTS_ITEMCLIP ); +} + +/* +================ +rvItemCTFFlag::GiveToPlayer +================ +*/ +bool rvItemCTFFlag::GiveToPlayer( idPlayer* player ) { + if ( !gameLocal.IsMultiplayer() ) { + return false; + } + + if ( player->spectating || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && (gameLocal.mpGame.GetGameState())->GetMPGameState() != SUDDENDEATH && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return false; + } + + int teamPowerup; + int enemyPowerup; + + bool canPickup = ( team == player->team ); + + switch ( player->team ) { + default: + case TEAM_MARINE: + teamPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + break; + + case TEAM_STROGG: + teamPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + break; + } + + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + // in one flag CTF, we score touching the enemy's flag + canPickup = ( team == gameLocal.mpGame.OpposingTeam( player->team ) ); + enemyPowerup = POWERUP_CTF_ONEFLAG; + } + + // If the player runs over their own flag the only thing they + // can do is score or return it. + + // when the player touches the one flag, he always gets it + if ( canPickup ) { + // If the flag was dropped, return it + if ( dropped ) { + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + statManager->FlagReturned( player ); + ResetFlag ( teamPowerup ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagReturned", 0 ) ); +// RITUAL END + } else if ( player->PowerUpActive ( enemyPowerup ) ) { + // If they have the enemy flag then they score + if ( !gameLocal.mpGame.CanCapture ( player->team ) ) { + return false; + } + + ResetFlag( enemyPowerup ); + + gameLocal.mpGame.FlagCaptured( player ); + } + return false; + } + + // only pickup one flag in arena CTF + if( ( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) && team != TEAM_MAX ) { + return false; + } + + player->GivePowerUp( enemyPowerup, -1 ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + player->GiveCash( (float)gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagStolen", 0 ) ); +// RITUAL END + return true; +} + +/* +================ +rvItemCTFFlag::Pickup +================ +*/ +bool rvItemCTFFlag::Pickup( idPlayer *player ) { + if( gameLocal.isClient ) { + // no client-side CTF flag prediction + return false; + } + + if ( !GiveToPlayer( player ) ) { + return false; + } + + // ServerSendEvent( EVENT_PICKUP, NULL, false, -1 ); + if ( gameLocal.isServer ) { + SendPickupMsg( player->entityNumber ); + } + + // Check for global acquire sounds in multiplayer + if ( gameLocal.isMultiplayer && spawnArgs.GetBool( "globalAcquireSound" ) ) { + gameLocal.mpGame.PlayGlobalItemAcquireSound( entityDefNumber ); + } else { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } + + // trigger our targets + ActivateTargets( player ); + + // clear our contents so the object isn't picked up twice + GetPhysics()->SetContents( 0 ); + + // hide the model + Hide(); + + gameLocal.mpGame.SetFlagEntity( NULL, team ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 1 ); + + if( gameLocal.gameType == GAME_CTF || gameLocal.gameType == GAME_ARENA_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_TAKEN ); + } else if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, (player->team == TEAM_MARINE ? FS_TAKEN_MARINE : FS_TAKEN_STROGG) ); + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagCarrier( team, player->entityNumber ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + PostEventMS( &EV_Remove, 0 ); + } + + BecomeInactive( TH_THINK ); + + return true; +} + +/* +================ +rvItemCTFFlag::ResetFlag +================ +*/ +void rvItemCTFFlag::ResetFlag( int powerup ) { + idEntity* ent; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + // Make sure no players have the flag anymore +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType ( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->ClearPowerup ( powerup ); + continue; + } + + // If its not a CTF flag item then skip it + if ( !ent->IsType( rvItemCTFFlag::Type ) ) { + continue; + } + + // Make sure its the right type first + rvItemCTFFlag* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->dropped ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + flag->PostEventMS( &EV_RespawnItem, 0 ); + gameLocal.mpGame.SetFlagEntity( flag, flag->team ); + } + } + + if ( !gameLocal.isClient ) { + int team = -1; + if ( powerup == POWERUP_CTF_MARINEFLAG ) { + team = TEAM_MARINE; + } else if ( powerup == POWERUP_CTF_STROGGFLAG ) { + team = TEAM_STROGG; + } else if ( powerup == POWERUP_CTF_ONEFLAG ) { + team = TEAM_MAX; + } + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetFlagState( team, FS_AT_BASE ); + } + +} + +/* +================ +rvItemCTFFlag::Event_ResetFlag +================ +*/ +void rvItemCTFFlag::Event_ResetFlag( void ) { + ResetFlag( powerup ); +} + +void rvItemCTFFlag::Think( void ) { + idItem::Think(); +} + +/* +================ +rvItemCTFFlag::Collide +================ +*/ +bool rvItemCTFFlag::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + + if ( collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + ResetFlag( powerup ); + } + return false; +} +// RAVEN END + + + + +const idEventDef EV_ResetSpawn ( "" ); +CLASS_DECLARATION( idItemPowerup, riDeadZonePowerup ) + EVENT( EV_ResetSpawn, riDeadZonePowerup::Event_ResetSpawn ) +END_CLASS + +/* +================ +riDeadZonePowerup::idItemPowerup +================ +*/ +riDeadZonePowerup::riDeadZonePowerup() { +} + +/* +================ +riDeadZonePowerup::Save +================ +*/ +void riDeadZonePowerup::Save( idSaveGame *savefile ) const { +} + +/* +================ +riDeadZonePowerup::Restore +================ +*/ +void riDeadZonePowerup::Restore( idRestoreGame *savefile ) { +} + + +/* +================ +riDeadZonePowerup::Show +================ +*/ +void riDeadZonePowerup::Show() +{ + idItem::Show(); +} + + +/* +================ +riDeadZonePowerup::Spawn +================ +*/ +void riDeadZonePowerup::Spawn( void ) { + powerup = POWERUP_DEADZONE; + + time = SEC2MS( gameLocal.serverInfo.GetInt("si_deadZonePowerupTime") ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + time = SEC2MS( spawnArgs.GetInt( "time", "10" ) ); + if ( time > SEC2MS(10) ) + time = SEC2MS(10); + + Show(); + CancelEvents(&EV_Remove); + PostEventSec( &EV_ResetSpawn, MS2SEC(time) ); + } + else + Hide(); +} + + +/* +================ +riDeadZonePowerup::Pickup +================ +*/ +bool riDeadZonePowerup::Pickup( idPlayer* player ) { + // regular pickup routine, but unique items need to think to know when to respawn + bool pickup; + if ( player->PowerUpActive(POWERUP_DEADZONE) ) + return false; + + pickup = idItemPowerup::Pickup( player ); + + if ( spawnArgs.GetBool( "dropped" ) ) { + // Cancel the respawn so the powerup doesn't get removed + // from the player that just picked it up. + PostEventMS( &EV_Remove, 0 ); + CancelEvents( &EV_ResetSpawn ); + } + + return pickup; +} + +/* +================ +riDeadZonePowerup::ResetSpawn +================ +*/ +void riDeadZonePowerup::ResetSpawn( int powerup ) { + int count = 1; + idEntity* ent; + riDeadZonePowerup* spawnSpot = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::Type ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != powerup ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && flag == this ) { + flag->PostEventMS( &EV_Remove, 0 ); + } else { + if ( !flag->IsVisible() ) { + if ( !(rand()%count) ) { + spawnSpot = flag; + if ( flag->spawnArgs.GetBool("dropped", "0") ) + gameLocal.DPrintf("WARNING: Trying to spawn a powerup at a DROPPED location!"); + } + count++; + } + } + } + + if ( spawnSpot ) { + spawnSpot->Show(); + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + } + else { + gameLocal.DPrintf("WARNING: Failed to find a valid spawn spot!"); + } +} + +/* +================ +riDeadZonePowerup::Collide +================ +*/ +bool riDeadZonePowerup::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity* lol = gameLocal.entities[ collision.c.entityNum ]; + if ( gameLocal.isMultiplayer && collision.c.contents & CONTENTS_ITEMCLIP && lol && !lol->IsType( idItem::GetClassType() ) ) { + PostEventMS( &EV_ResetSpawn, 0 ); // Just respawn it. + } + return false; +} + +/* +================ +riDeadZonePowerup::Event_ResetFlag +================ +*/ +void riDeadZonePowerup::Event_ResetSpawn( void ) { + ResetSpawn( POWERUP_DEADZONE ); +} diff --git a/src/mpgame/Item.h b/src/mpgame/Item.h new file mode 100644 index 0000000..941cf40 --- /dev/null +++ b/src/mpgame/Item.h @@ -0,0 +1,387 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_ITEM_H__ +#define __GAME_ITEM_H__ + +extern const int ARENA_POWERUP_MASK; +extern const idEventDef EV_ResetFlag; +extern const idEventDef EV_RespawnItem; +extern const idEventDef EV_SetGravity; + +/* +=============================================================================== + + Items the player can pick up or use. + +=============================================================================== +*/ + +class idItem : public idEntity { +public: + CLASS_PROTOTYPE( idItem ); + + idItem(); + virtual ~idItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void GetAttributes( idDict &attributes ); + virtual bool GiveToPlayer( idPlayer *player ); + virtual bool Pickup( idPlayer *player ); + virtual void Think( void ); + virtual void Present(); + virtual void InstanceJoin( void ); + virtual void InstanceLeave( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// mekberg: added + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); +// RAVEN END + + enum { + EVENT_PICKUP = idEntity::EVENT_MAXEVENTS, + EVENT_RESPAWNFX, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + int IsVisible() { return srvReady; } + + rvClientEntityPtr effectIdle; + bool simpleItem; + float simpleItemScale; + bool pickedUp; + const idDeclSkin* pickupSkin; + void Event_DropToFloor ( void ); +protected: + + void UpdateTrigger( void ); + void SendPickupMsg( int clientNum ); + + idClipModel * trigger; + bool spin; + // only a small subset of idItem need their physics object synced + bool syncPhysics; + + bool pulse; + bool canPickUp; + const idDeclSkin* skin; + +private: + idVec3 orgOrigin; + + rvPhysics_Particle physicsObj; + + // for item pulse effect + int itemShellHandle; + const idMaterial * shellMaterial; + + // used to update the item pulse effect + mutable bool inView; + mutable int inViewTime; + mutable int lastCycle; + mutable int lastRenderViewTime; + + // synced through snapshots to indicate show/hide or pickupSkin state + // -1 on a client means undef, 0 not ready, 1 ready +public: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int srvReady; +private: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp is trying to access this directly + int clReady; + + int itemPVSArea; + + bool UpdateRenderEntity ( renderEntity_s *renderEntity, const renderView_t *renderView ) const; + static bool ModelCallback ( renderEntity_s *renderEntity, const renderView_t *renderView ); + + + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_Trigger ( idEntity *activator ); + void Event_Respawn ( void ); + void Event_RespawnFx ( void ); + void Event_Pickup ( int clientNum ); + +// RAVEN BEGIN +// abahr + void Event_SetGravity(); +// RAVEN END +}; + +/* +=============================================================================== + + idItemPowerup + +=============================================================================== +*/ + +class idItemPowerup : public idItem { +public: + CLASS_PROTOTYPE( idItemPowerup ); + + idItemPowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + virtual bool GiveToPlayer( idPlayer *player ); + virtual void Think( void ); + virtual bool Pickup( idPlayer *player ); + +protected: + + int time; + int type; + int droppedTime; + int team; + bool unique; +}; + +/* +=============================================================================== + + riDeadZonePowerup + +=============================================================================== +*/ + +class riDeadZonePowerup : public idItemPowerup { +public: + CLASS_PROTOTYPE( riDeadZonePowerup ); + + riDeadZonePowerup(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool Pickup( idPlayer *player ); + + void Spawn(); + void Show(); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + int powerup; + +protected: + void ResetSpawn( int powerup ); + +private: + void Event_ResetSpawn( void ); +}; + +/* +=============================================================================== + + rvItemCTFFlag + +=============================================================================== +*/ + +class rvItemCTFFlag : public idItem { +public: + CLASS_PROTOTYPE( rvItemCTFFlag ); + + rvItemCTFFlag(); + + void Spawn(); + virtual bool GiveToPlayer ( idPlayer* player ); + virtual bool Pickup( idPlayer *player ); + + static void ResetFlag( int type ); + virtual void Think( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + +private: + int team; + int powerup; + bool dropped; + + void Event_ResetFlag( void ); + void Event_LinkTrigger( void ); +}; + +/* +=============================================================================== + + idObjective + +=============================================================================== +*/ + +class idObjective : public idItem { +public: + CLASS_PROTOTYPE( idObjective ); + + idObjective(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); + void Event_CamShot(); +}; + +/* +=============================================================================== + + idMoveableItem + +=============================================================================== +*/ + +class idMoveableItem : public idItem { +public: + CLASS_PROTOTYPE( idMoveableItem ); + + idMoveableItem(); + virtual ~idMoveableItem(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual void Think( void ); + + static void DropItems( idAnimatedEntity *ent, const char *type, idList *list ); + static idEntity* DropItem( const char *classname, const idVec3 &origin, const idMat3 &axis, const idVec3 &velocity, int activateDelay, int removeDelay ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + idPhysics_RigidBody physicsObj; + + void Gib( const idVec3 &dir, const char *damageDefName ); + void Event_Gib( const char *damageDefName ); +}; + +/* +=============================================================================== + + Item removers. + +=============================================================================== +*/ + +class idItemRemover : public idEntity { +public: + CLASS_PROTOTYPE( idItemRemover ); + + void Spawn(); + void RemoveItem( idPlayer *player ); + +private: + void Event_Trigger( idEntity *activator ); +}; + +/* +=============================================================================== + + idObjectiveComplete + +=============================================================================== +*/ + +class idObjectiveComplete : public idItemRemover { +public: + CLASS_PROTOTYPE( idObjectiveComplete ); + + idObjectiveComplete(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn(); + +private: + idVec3 playerPos; + +// RAVEN BEGIN +// mekberg: store triggered time for timed removal. + int triggerTime; +// RAVEN END + + void Event_Trigger( idEntity *activator ); + void Event_HideObjective( idEntity *e ); + void Event_GetPlayerPos(); +}; + +/* +=============================================================================== + + rvObjectiveFailed + +=============================================================================== +*/ + +class rvObjectiveFailed : public idItemRemover { +public: + CLASS_PROTOTYPE( rvObjectiveFailed ); + + rvObjectiveFailed (); +private: + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_ITEM_H__ */ + + +// RAVEN END diff --git a/src/mpgame/Lagometer.cpp b/src/mpgame/Lagometer.cpp new file mode 100644 index 0000000..b65b310 --- /dev/null +++ b/src/mpgame/Lagometer.cpp @@ -0,0 +1,83 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#define LAGO_TOP 380 +#define LAGO_LEFT 10 +#define LAGO_WIDTH 64 +#define LAGO_HEIGHT 44 + + +idLagometer::idLagometer() +{ + memset(aheadOfServer, 0, sizeof(int) * LAGOMETER_HISTORY); + memset(dupeUserCmds, 0, sizeof(byte) * LAGOMETER_HISTORY); + + frameIndex = 0; +} + +void idLagometer::Draw() +{ + //renderSystem->DrawStretchPic(x, y, 1, h, 0, 0, 0, 0, lagometerBack); + + const idVec4 colorYellow(1.0f, 1.0f, 0.0f, 1.0f); + const idVec4 colorGreen(0.0f, 1.0f, 0.0f, 1.0f); + const idVec4 colorRed(1.0f, 0.0f, 0.0f, 1.0f); + const idVec4 colorWhite(1.0f, 1.0f, 1.0f, 1.0f); + + const idMaterial *whiteMaterial = declManager->FindMaterial("_white"); + + for (int x=0; x 0) + { + renderSystem->SetColor(colorGreen); + renderSystem->DrawStretchPic(LAGO_LEFT + x, LAGO_TOP + 10 - aheadOfServer[i], 1, aheadOfServer[i], 0, 0, 0, 0, whiteMaterial); + } + else if (aheadOfServer[i] < 0) + { + renderSystem->SetColor(colorYellow); + renderSystem->DrawStretchPic(LAGO_LEFT + x, LAGO_TOP + 10, 1, -aheadOfServer[i], 0, 0, 0, 0, whiteMaterial); + } + + renderSystem->SetColor(colorYellow); + renderSystem->DrawStretchPic(LAGO_LEFT + x, LAGO_TOP + LAGO_HEIGHT - dupeUserCmds[i], 1, dupeUserCmds[i], 0, 0, 0, 0, whiteMaterial); + } +} + +void idLagometer::Update(int ahead, int dupe) +{ + frameIndex = (frameIndex + 1) % LAGOMETER_HISTORY; + + aheadOfServer[frameIndex] = 2 * idMath::ClampInt(-10, 5, + floorf( (float)ahead / net_clientLagOMeterResolution.GetInteger() )); + + dupeUserCmds[frameIndex] = 2 * Min( 6, dupe ); +} \ No newline at end of file diff --git a/src/mpgame/Lagometer.h b/src/mpgame/Lagometer.h new file mode 100644 index 0000000..23f44e6 --- /dev/null +++ b/src/mpgame/Lagometer.h @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LAGOMETER_H__ +#define __LAGOMETER_H__ + +#define LAGOMETER_HISTORY 64 + +class idLagometer +{ +public: + idLagometer(); + + void Update(int ahead, int dupe); + void Draw(); +protected: + int aheadOfServer[LAGOMETER_HISTORY]; + byte dupeUserCmds[LAGOMETER_HISTORY]; + + int frameIndex; +}; + + +#endif diff --git a/src/mpgame/Light.cpp b/src/mpgame/Light.cpp new file mode 100644 index 0000000..d0754b7 --- /dev/null +++ b/src/mpgame/Light.cpp @@ -0,0 +1,1509 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + idLight + +=============================================================================== +*/ + +const idEventDef EV_Light_SetShader( "setShader", "s" ); +const idEventDef EV_Light_GetLightParm( "getLightParm", "d", 'f' ); +const idEventDef EV_Light_SetLightParm( "setLightParm", "df" ); +const idEventDef EV_Light_SetLightParms( "setLightParms", "ffff" ); +const idEventDef EV_Light_SetRadiusXYZ( "setRadiusXYZ", "fff" ); +const idEventDef EV_Light_SetRadius( "setRadius", "f" ); +const idEventDef EV_Light_On( "On", NULL ); +const idEventDef EV_Light_Off( "Off", NULL ); +const idEventDef EV_Light_FadeOut( "fadeOutLight", "f" ); +const idEventDef EV_Light_FadeIn( "fadeInLight", "f" ); + +// RAVEN BEGIN +// bdube: added +const idEventDef EV_Light_SetLightGUI ( "setLightGUI", "s" ); +// jscott: added for modview +const idEventDef EV_Light_SetCurrentLightLevel( "setCurrentLightLevel", "d" ); +const idEventDef EV_Light_SetMaxLightLevel( "setMaxLightLevel", "d" ); +// kfuller: 8/11/03 +const idEventDef EV_Light_IsOn( "isOn", NULL, 'f' ); +const idEventDef EV_Light_Break( "break", "ef" ); +// kfuller: lights that blink to life +const idEventDef EV_Light_DoneBlinking( "doneBlinking", NULL ); +// kfuller: lights that blink off +const idEventDef EV_Light_DoneBlinkingOff( "doneBlinkingOff", NULL ); +// abahr: +const idEventDef EV_Light_Timer( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idLight ) + EVENT( EV_Light_SetShader, idLight::Event_SetShader ) + EVENT( EV_Light_GetLightParm, idLight::Event_GetLightParm ) + EVENT( EV_Light_SetLightParm, idLight::Event_SetLightParm ) + EVENT( EV_Light_SetLightParms, idLight::Event_SetLightParms ) + EVENT( EV_Light_SetRadiusXYZ, idLight::Event_SetRadiusXYZ ) + EVENT( EV_Light_SetRadius, idLight::Event_SetRadius ) + EVENT( EV_Hide, idLight::Event_Hide ) + EVENT( EV_Show, idLight::Event_Show ) + EVENT( EV_Light_On, idLight::Event_On ) + EVENT( EV_Light_Off, idLight::Event_Off ) + EVENT( EV_Activate, idLight::Event_ToggleOnOff ) + EVENT( EV_PostSpawn, idLight::Event_SetSoundHandles ) + EVENT( EV_Light_FadeOut, idLight::Event_FadeOut ) + EVENT( EV_Light_FadeIn, idLight::Event_FadeIn ) + +// RAVEN BEGIN +// bdube: added + EVENT( EV_Light_SetLightGUI, idLight::Event_SetLightGUI ) + EVENT( EV_Light_SetCurrentLightLevel, idLight::Event_SetCurrentLightLevel ) + EVENT( EV_Light_SetMaxLightLevel, idLight::Event_SetMaxLightLevel ) +// kfuller: 8/11/03 + EVENT( EV_Light_IsOn, idLight::Event_IsOn ) + EVENT( EV_Light_Break, idLight::Event_Break ) +// kfuller: lights that blink to life + EVENT( EV_Light_DoneBlinking, idLight::Event_DoneBlinking ) +// kfuller: lights that blink off + EVENT( EV_Light_DoneBlinkingOff, idLight::Event_DoneBlinkingOff ) + EVENT( EV_Earthquake, idLight::Event_EarthQuake ) +// abahr: + EVENT( EV_Light_Timer, idLight::Event_Timer ) +// RAVEN END +END_CLASS + + +/* +================ +idGameEdit::ParseSpawnArgsToRenderLight + +parse the light parameters +this is the canonical renderLight parm parsing, +which should be used by dmap and the editor +================ +*/ +bool idGameEdit::ParseSpawnArgsToRenderLight( const idDict *args, renderLight_t *renderLight ) { + bool gotTarget, gotUp, gotRight; + const char *texture; + idVec3 color; + bool rv = true; + + memset( renderLight, 0, sizeof( *renderLight ) ); + + if (!args->GetVector("light_origin", "", renderLight->origin)) { + args->GetVector( "origin", "", renderLight->origin ); + } + + gotTarget = args->GetVector( "light_target", "", renderLight->target ); + gotUp = args->GetVector( "light_up", "", renderLight->up ); + gotRight = args->GetVector( "light_right", "", renderLight->right ); + args->GetVector( "light_start", "0 0 0", renderLight->start ); + if ( !args->GetVector( "light_end", "", renderLight->end ) ) { + renderLight->end = renderLight->target; + } + + // we should have all of the target/right/up or none of them + if ( ( gotTarget || gotUp || gotRight ) != ( gotTarget && gotUp && gotRight ) ) { + gameLocal.Warning( "Light at (%f,%f,%f) has bad target info\n", + renderLight->origin[0], renderLight->origin[1], renderLight->origin[2] ); + + return false; + } + + if ( !gotTarget ) { + renderLight->pointLight = true; + + // allow an optional relative center of light and shadow offset + args->GetVector( "light_center", "0 0 0", renderLight->lightCenter ); + +// RAVEN BEGIN +// bdube: default light radius changed to 320 + // create a point light + if (!args->GetVector( "light_radius", "320 320 320", renderLight->lightRadius ) ) { + float radius; + + args->GetFloat( "light", "320", radius ); +// RAVEN END + renderLight->lightRadius[0] = renderLight->lightRadius[1] = renderLight->lightRadius[2] = radius; + } + + if ( renderLight->lightRadius[0] == 0 || + renderLight->lightRadius[1] == 0 || + renderLight->lightRadius[2] == 0 ) { + gameLocal.Warning( "PointLight at ( %d, %d, %d ) has at least one radius component of 0!", + ( int )renderLight->origin[0], ( int )renderLight->origin[1], ( int )renderLight->origin[2] ); + rv = false; + } + } + + // get the rotation matrix in either full form, or single angle form + idAngles angles; + idMat3 mat; + if ( !args->GetMatrix( "light_rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + if ( !args->GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", mat ) ) { + args->GetFloat( "angle", "0", angles[ 1 ] ); + angles[ 0 ] = 0; + angles[ 1 ] = idMath::AngleNormalize360( angles[ 1 ] ); + angles[ 2 ] = 0; + mat = angles.ToMat3(); + } + } + + // fix degenerate identity matrices + mat[0].FixDegenerateNormal(); + mat[1].FixDegenerateNormal(); + mat[2].FixDegenerateNormal(); + + renderLight->axis = mat; + + // check for other attributes + args->GetVector( "_color", "1 1 1", color ); + renderLight->shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight->shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight->shaderParms[ SHADERPARM_BLUE ] = color[2]; + args->GetFloat( "shaderParm3", "1", renderLight->shaderParms[ SHADERPARM_TIMESCALE ] ); + if ( !args->GetFloat( "shaderParm4", "0", renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] ) ) { + // offset the start time of the shader to sync it to the game time + renderLight->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + + args->GetFloat( "shaderParm5", "0", renderLight->shaderParms[5] ); + args->GetFloat( "shaderParm6", "0", renderLight->shaderParms[6] ); + args->GetFloat( "shaderParm7", "0", renderLight->shaderParms[ SHADERPARM_MODE ] ); + args->GetBool( "noshadows", "0", renderLight->noShadows ); + +// RAVEN BEGIN +// dluetscher: added a min light detail level setting that describes when this light is visible + args->GetFloat( "detailLevel", "10", renderLight->detailLevel ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: dynamic shadows + args->GetBool( "noDynamicShadows", "0", renderLight->noDynamicShadows ); +// RAVEN END + args->GetBool( "nospecular", "0", renderLight->noSpecular ); + args->GetBool( "parallel", "0", renderLight->parallel ); + + args->GetString( "texture", "lights/squarelight1", &texture ); + // allow this to be NULL + renderLight->shader = declManager->FindMaterial( texture, false ); + + return rv; +} + +/* +================ +idLight::UpdateChangeableSpawnArgs +================ +*/ +void idLight::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + source->Print(); + } + FreeSoundEmitter( true ); + gameEdit->ParseSpawnArgsToRefSound( source ? source : &spawnArgs, &refSound ); + if ( refSound.shader && !refSound.waitfortrigger ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + } + + gameEdit->ParseSpawnArgsToRenderLight( source ? source : &spawnArgs, &renderLight ); + + UpdateVisuals(); +} + +/* +================ +idLight::idLight +================ +*/ +idLight::idLight() { + memset( &renderLight, 0, sizeof( renderLight ) ); +// RAVEN BEGIN +// dluetscher: added a default detail level to each render light + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + localLightOrigin = vec3_zero; + localLightAxis = mat3_identity; + lightDefHandle = -1; + levels = 0; + currentLevel = 0; + baseColor = vec3_zero; + breakOnTrigger = false; + count = 0; + triggercount = 0; + lightParent = NULL; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + soundWasPlaying = false; + +// RAVEN BEGIN +// bdube: light gui + lightGUI = NULL; + random = 0.0f; + wait = 0.0f; +// RAVEN END +} + +/* +================ +idLight::~idLight +================ +*/ +idLight::~idLight() { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + } +} + +/* +================ +idLight::Save + +archives object for save game file +================ +*/ +void idLight::Save( idSaveGame *savefile ) const { + savefile->WriteRenderLight( renderLight ); + + savefile->WriteBool( renderLight.prelightModel != NULL ); + + savefile->WriteVec3( localLightOrigin ); + savefile->WriteMat3( localLightAxis ); + //qhandle_t lightDefHandle; // cnicholson: This wasn't here from id, so I didnt add it either. + + savefile->WriteString( brokenModel ); + savefile->WriteInt( levels ); + savefile->WriteInt( currentLevel ); + + savefile->WriteVec3( baseColor ); + savefile->WriteBool( breakOnTrigger ); + savefile->WriteInt( count ); + savefile->WriteInt( triggercount ); + savefile->WriteObject( lightParent ); + + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + + lightGUI.Save( savefile ); // cnicholson: added unsaved var + + savefile->WriteBool( soundWasPlaying ); +} + +/* +================ +idLight::Restore + +unarchives object from save game file +================ +*/ +void idLight::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// jscott: constants can be read from the spawnargs + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// RAVEN END + + bool hadPrelightModel; + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadBool( hadPrelightModel ); + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + if ( ( renderLight.prelightModel == NULL ) && hadPrelightModel ) { + assert( 0 ); + if ( developer.GetBool() ) { + // we really want to know if this happens + gameLocal.Error( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } else { + // but let it slide after release + gameLocal.Warning( "idLight::Restore: prelightModel '_prelight_%s' not found", name.c_str() ); + } + } + + savefile->ReadVec3( localLightOrigin ); + savefile->ReadMat3( localLightAxis ); + + savefile->ReadString( brokenModel ); + savefile->ReadInt( levels ); + savefile->ReadInt( currentLevel ); + + savefile->ReadVec3( baseColor ); + savefile->ReadBool( breakOnTrigger ); + savefile->ReadInt( count ); + savefile->ReadInt( triggercount ); + savefile->ReadObject( reinterpret_cast( lightParent ) ); + + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +// RAVEN BEGIN +// bdube: light gui + lightGUI.Restore ( savefile ); +// RAVEN END + + savefile->ReadBool( soundWasPlaying ); + + lightDefHandle = -1; + + SetLightLevel(); +} + +/* +================ +idLight::Spawn +================ +*/ +void idLight::Spawn( void ) { + bool start_off; + bool needBroken; + const char *demonic_shader; + + // do the parsing the same way dmap and the editor do + if ( !gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &renderLight ) ) { + gameLocal.Warning( "Removing invalid light named: %s", GetName() ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + // we need the origin and axis relative to the physics origin/axis + localLightOrigin = ( renderLight.origin - GetPhysics()->GetOrigin() ) * GetPhysics()->GetAxis().Transpose(); + localLightAxis = renderLight.axis * GetPhysics()->GetAxis().Transpose(); + + // set the base color from the shader parms + baseColor.Set( renderLight.shaderParms[ SHADERPARM_RED ], renderLight.shaderParms[ SHADERPARM_GREEN ], renderLight.shaderParms[ SHADERPARM_BLUE ] ); + + // set the number of light levels + spawnArgs.GetInt( "levels", "1", levels ); + currentLevel = levels; + if ( levels <= 0 ) { + gameLocal.Error( "Invalid light level set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + // make sure the demonic shader is cached + if ( spawnArgs.GetString( "mat_demonic", NULL, &demonic_shader ) ) { + declManager->FindType( DECL_MATERIAL, demonic_shader ); + } + + // game specific functionality, not mirrored in + // editor or dmap light parsing + + // also put the light texture on the model, so light flares + // can get the current intensity of the light + renderEntity.referenceShader = renderLight.shader; + + lightDefHandle = -1; // no static version yet + + // see if an optimized shadow volume exists + // the renderer will ignore this value after a light has been moved, + // but there may still be a chance to get it wrong if the game moves + // a light before the first present, and doesn't clear the prelight + renderLight.prelightModel = 0; + if ( name[ 0 ] ) { + // this will return 0 if not found + renderLight.prelightModel = renderModelManager->CheckModel( va( "_prelight_%s", name.c_str() ) ); + } + + spawnArgs.GetBool( "start_off", "0", start_off ); + if ( start_off ) { + Off(); + } + + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + spawnArgs.GetBool( "break", "0", breakOnTrigger ); + spawnArgs.GetInt( "count", "1", count ); + + triggercount = 0; + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // if we have a health make light breakable + if ( health ) { + idStr model = spawnArgs.GetString( "model" ); // get the visual model + if ( !model.Length() ) { + gameLocal.Error( "Breakable light without a model set on entity #%d(%s)", entityNumber, name.c_str() ); + } + + fl.takedamage = true; + + // see if we need to create a broken model name + needBroken = true; + if ( model.Length() && !brokenModel.Length() ) { + int pos; + + needBroken = false; + + pos = model.Find( "." ); + if ( pos < 0 ) { + pos = model.Length(); + } + if ( pos > 0 ) { + model.Left( pos, brokenModel ); + } + brokenModel += "_broken"; + if ( pos > 0 ) { + brokenModel += &model[ pos ]; + } + } + + // make sure the model gets cached + if ( !renderModelManager->CheckModel( brokenModel ) ) { + if ( needBroken ) { + gameLocal.Error( "Model '%s' not found for entity %d(%s)", brokenModel.c_str(), entityNumber, name.c_str() ); + } else { + brokenModel = ""; + } + } + + GetPhysics()->SetContents( spawnArgs.GetBool( "nonsolid" ) ? 0 : CONTENTS_SOLID ); + } + + PostEventMS( &EV_PostSpawn, 0 ); + +// RAVEN BEGIN +// bdube: light guis + const char* lightGUI; + if ( spawnArgs.GetString ( "light_gui", "", &lightGUI ) ) { + PostEventMS( &EV_Light_SetLightGUI, 0, lightGUI ); + } + +// abahr: + wait = spawnArgs.GetFloat( "wait" ); + random = spawnArgs.GetFloat( "random" ); +// AReis: Minor light optimization stuff. + spawnArgs.GetBool( "globalLight", "0", renderLight.globalLight ); +// RAVEN END + + UpdateVisuals(); + +// RAVEN BEGIN +// ddynerman: ambient lights added clientside + if( renderLight.shader && renderLight.shader->IsAmbientLight() ) { + if ( !gameLocal.ambientLights.Find( static_cast(this) ) ) { + gameLocal.ambientLights.Append( static_cast(this) ); + } + fl.networkSync = false; // don't transmit ambient lights + } +// RAVEN END +} + +/* +================ +idLight::SetLightLevel +================ +*/ +void idLight::SetLightLevel( void ) { + idVec3 color; + float intensity; + + intensity = ( float )currentLevel / ( float )levels; + color = baseColor * intensity; + renderLight.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[ 1 ]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + renderEntity.shaderParms[ SHADERPARM_RED ] = color[ 0 ]; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= color[ 1 ]; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = color[ 2 ]; + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec3 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; +} + +/* +================ +idLight::GetColor +================ +*/ +void idLight::GetColor( idVec4 &out ) const { + out[ 0 ] = renderLight.shaderParms[ SHADERPARM_RED ]; + out[ 1 ] = renderLight.shaderParms[ SHADERPARM_GREEN ]; + out[ 2 ] = renderLight.shaderParms[ SHADERPARM_BLUE ]; + out[ 3 ] = renderLight.shaderParms[ SHADERPARM_ALPHA ]; +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( float red, float green, float blue ) { + baseColor.Set( red, green, blue ); + SetLightLevel(); +} + +/* +================ +idLight::SetColor +================ +*/ +void idLight::SetColor( const idVec4 &color ) { + baseColor = color.ToVec3(); + renderLight.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = color[ 3 ]; + SetLightLevel(); +} + +/* +================ +idLight::SetShader +================ +*/ +void idLight::SetShader( const char *shadername ) { + // allow this to be NULL + renderLight.shader = declManager->FindMaterial( shadername, false ); + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParm +================ +*/ +void idLight::SetLightParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + renderLight.shaderParms[ parmnum ] = value; + PresentLightDefChange(); +} + +/* +================ +idLight::SetLightParms +================ +*/ +void idLight::SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + renderLight.shaderParms[ SHADERPARM_RED ] = parm0; + renderLight.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderLight.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderLight.shaderParms[ SHADERPARM_ALPHA ] = parm3; + renderEntity.shaderParms[ SHADERPARM_RED ] = parm0; + renderEntity.shaderParms[ SHADERPARM_GREEN ] = parm1; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = parm2; + renderEntity.shaderParms[ SHADERPARM_ALPHA ] = parm3; + baseColor.Set( parm0, parm1, parm2 ); + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::SetRadiusXYZ +================ +*/ +void idLight::SetRadiusXYZ( float x, float y, float z ) { + renderLight.lightRadius[0] = x; + renderLight.lightRadius[1] = y; + renderLight.lightRadius[2] = z; + PresentLightDefChange(); +} + +/* +================ +idLight::SetRadius +================ +*/ +void idLight::SetRadius( float radius ) { + renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = radius; + PresentLightDefChange(); +} + +/* +================ +idLight::On +================ +*/ +void idLight::On( void ) { + currentLevel = levels; + // offset the start time of the shader to sync it to the game time + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + +// RAVEN BEGIN + idStr blinkOnSound; + if (spawnArgs.GetString("snd_blinkOn", "", blinkOnSound)) + { + refSound.shader = declManager->FindSound(blinkOnSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + PostEventMS(&EV_Light_DoneBlinking, howLongInMS); + soundWasPlaying = false; + idStr blinkOnTexture; + if (spawnArgs.GetString( "mtr_blinkOn", "", blinkOnTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOnTexture, false ); + UpdateVisuals(); + Present(); + } + } + else +// RAVEN END + + if ( ( soundWasPlaying || refSound.waitfortrigger ) && refSound.shader ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Off +================ +*/ +void idLight::Off( void ) { +// RAVEN BEGIN +// kfuller: lights can flicker off + idStr blinkOffSound; + if (spawnArgs.GetString("snd_blinkOff", "", blinkOffSound)) + { + refSound.shader = declManager->FindSound(blinkOffSound); + int howLongInMS = StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL );//*1000; + PostEventMS(&EV_Light_DoneBlinkingOff, howLongInMS); + soundWasPlaying = false; + idStr blinkOffTexture; + if (spawnArgs.GetString( "mtr_blinkOff", "", blinkOffTexture )) + { + renderLight.shader = declManager->FindMaterial( blinkOffTexture, false ); + UpdateVisuals(); + Present(); + } + } + else + { + currentLevel = 0; + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { + StopSound( SND_CHANNEL_ANY, false ); + soundWasPlaying = true; + } + } +// RAVEN END + SetLightLevel(); + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idLight::Fade +================ +*/ +void idLight::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idLight::FadeOut +================ +*/ +void idLight::FadeOut( float time ) { + Fade( colorBlack, time ); +} + +/* +================ +idLight::FadeIn +================ +*/ +void idLight::FadeIn( float time ) { + idVec3 color; + idVec4 color4; + + currentLevel = levels; + spawnArgs.GetVector( "_color", "1 1 1", color ); + color4.Set( color.x, color.y, color.z, 1.0f ); + Fade( color4, time ); +} + +/* +================ +idLight::Killed +================ +*/ +void idLight::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + BecomeBroken( attacker ); +} + +/* +================ +idLight::BecomeBroken +================ +*/ +void idLight::BecomeBroken( idEntity *activator ) { + const char *damageDefName; + + fl.takedamage = false; + + if ( brokenModel.Length() ) { + SetModel( brokenModel ); + + if ( !spawnArgs.GetBool( "nonsolid" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel( brokenModel.c_str() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + } else if ( spawnArgs.GetBool( "hideModelOnBreak" ) ) { + SetModel( "" ); + GetPhysics()->SetContents( 0 ); + } + + if ( gameLocal.isServer ) { + + ServerSendInstanceEvent( EVENT_BECOMEBROKEN, NULL, true, -1 ); + + if ( spawnArgs.GetString( "def_damage", "", &damageDefName ) ) { + idVec3 origin = renderEntity.origin + renderEntity.bounds.GetCenter() * renderEntity.axis; + gameLocal.RadiusDamage( origin, activator, activator, this, this, damageDefName ); + } + + } + + ActivateTargets( activator ); + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + // set the state parm + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + renderLight.shaderParms[ SHADERPARM_MODE ] = 1; + + // if the light has a sound, either start the alternate (broken) sound, or stop the sound + const char *parm = spawnArgs.GetString( "snd_broken" ); + if ( refSound.shader || ( parm && *parm ) ) { + StopSound( SND_CHANNEL_ANY, false ); + const idSoundShader *alternate = refSound.shader ? refSound.shader->GetAltSound() : declManager->FindSound( parm ); + if ( alternate ) { + // start it with no diversity, so the leadin break sound plays + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound( alternate, SND_CHANNEL_ANY, 0.0, 0 ); + } + } + } + + parm = spawnArgs.GetString( "mtr_broken" ); + if ( parm && *parm ) { + SetShader( parm ); + } + + UpdateVisuals(); +} + +/* +================ +idLight::PresentLightDefChange +================ +*/ +void idLight::PresentLightDefChange( void ) { + // let the renderer apply it to the world + if ( ( lightDefHandle != -1 ) ) { + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } +} + +/* +================ +idLight::PresentModelDefChange +================ +*/ +void idLight::PresentModelDefChange( void ) { + + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} + +/* +================ +idLight::Present +================ +*/ +void idLight::Present( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_RENDER ); +// RAVEN END + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + + // add the model + idEntity::Present(); + + // current transformation + renderLight.axis = localLightAxis * GetPhysics()->GetAxis(); + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * localLightOrigin; + + // reference the sound for shader synced effects + if ( lightParent ) { +// RAVEN BEGIN + renderLight.referenceSoundHandle = lightParent->GetSoundEmitter(); + renderEntity.referenceSoundHandle = lightParent->GetSoundEmitter(); + } + else { + renderLight.referenceSoundHandle = refSound.referenceSoundHandle; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; +// RAVEN END + } + + // update the renderLight and renderEntity to render the light and flare + PresentLightDefChange(); + PresentModelDefChange(); +} + +/* +================ +idLight::Think +================ +*/ +void idLight::Think( void ) { + idVec4 color; + + if ( thinkFlags & TH_THINK ) { + if ( fadeEnd > 0 ) { + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + +// RAVEN BEGIN +// bdube: gui controlled lights + if ( lightGUI ) { + SetColor ( lightGUI->GetRenderEntity()->gui[0]->GetLightColor ( ) ); + } +// RAVEN END + } + + RunPhysics(); + Present(); +} + +/* +================ +idLight::GetPhysicsToSoundTransform +================ +*/ +bool idLight::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = localLightOrigin + renderLight.lightCenter; + axis = localLightAxis * GetPhysics()->GetAxis(); + return true; +} + +/* +================ +idLight::FreeLightDef +================ +*/ +void idLight::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================ +idLight::SaveState +================ +*/ +void idLight::SaveState( idDict *args ) { + int i, c = spawnArgs.GetNumKeyVals(); + for ( i = 0; i < c; i++ ) { + const idKeyValue *pv = spawnArgs.GetKeyVal(i); + if ( pv->GetKey().Find( "editor_", false ) >= 0 || pv->GetKey().Find( "parse_", false ) >= 0 ) { + continue; + } + args->Set( pv->GetKey(), pv->GetValue() ); + } +} + +/* +=============== +idLight::ShowEditingDialog +=============== +*/ +void idLight::ShowEditingDialog( void ) { + if ( g_editEntityMode.GetInteger() == 1 ) { + common->InitTool( EDITOR_LIGHT, &spawnArgs ); + } else { + common->InitTool( EDITOR_SOUND, &spawnArgs ); + } +} + +/* +================ +idLight::Event_SetShader +================ +*/ +void idLight::Event_SetShader( const char *shadername ) { + SetShader( shadername ); +} + +/* +================ +idLight::Event_GetLightParm +================ +*/ +void idLight::Event_GetLightParm( int parmnum ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_ENTITY_SHADER_PARMS ) ) { + gameLocal.Error( "shader parm index (%d) out of range", parmnum ); + } + + idThread::ReturnFloat( renderLight.shaderParms[ parmnum ] ); +} + +/* +================ +idLight::Event_SetLightParm +================ +*/ +void idLight::Event_SetLightParm( int parmnum, float value ) { + SetLightParm( parmnum, value ); +} + +/* +================ +idLight::Event_SetLightParms +================ +*/ +void idLight::Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ) { + SetLightParms( parm0, parm1, parm2, parm3 ); +} + +/* +================ +idLight::Event_SetRadiusXYZ +================ +*/ +void idLight::Event_SetRadiusXYZ( float x, float y, float z ) { + SetRadiusXYZ( x, y, z ); +} + +/* +================ +idLight::Event_SetRadius +================ +*/ +void idLight::Event_SetRadius( float radius ) { + SetRadius( radius ); +} + +/* +================ +idLight::Event_Hide +================ +*/ +void idLight::Event_Hide( void ) { + Hide(); + PresentModelDefChange(); + Off(); +} + +/* +================ +idLight::Event_Show +================ +*/ +void idLight::Event_Show( void ) { + Show(); + PresentModelDefChange(); + On(); +} + +/* +================ +idLight::Event_On +================ +*/ +void idLight::Event_On( void ) { + On(); +} + +/* +================ +idLight::Event_Off +================ +*/ +void idLight::Event_Off( void ) { + Off(); +} + +/* +================ +idLight::Event_ToggleOnOff +================ +*/ +void idLight::Event_ToggleOnOff( idEntity *activator ) { +// RAVEN BEGIN +// abahr: + if( wait > 0 ) { + if( EventIsPosted(&EV_Light_Timer) ) { + CancelEvents( &EV_Light_Timer ); + } else { + ProcessEvent( &EV_Light_Timer ); + } + } else { +// RAVEN END + triggercount++; + if ( triggercount < count ) { + return; + } + + // reset trigger count + triggercount = 0; + + if ( breakOnTrigger ) { + BecomeBroken( activator ); + breakOnTrigger = false; + return; + } + + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } +// RAVEN BEGIN +// abahr: + } +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSound::Event_Timer +================ +*/ +void idLight::Event_Timer( void ) { +// FIXME: think about putting this logic in helper function so we don't have cut and pasted code + if ( !currentLevel ) { + On(); + } + else { + currentLevel--; + if ( !currentLevel ) { + Off(); + } + else { + SetLightLevel(); + } + } + + PostEventSec( &EV_Light_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idLight::Event_SetSoundHandles + + set the same sound def handle on all targeted lights +================ +*/ +void idLight::Event_SetSoundHandles( void ) { + int i; + idEntity *targetEnt; + + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idLight::Type ) ) { + idLight *light = static_cast(targetEnt); + light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->renderEntity.referenceSoundHandle = renderEntity.referenceSoundHandle; + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } +// RAVEN BEGIN +// rjohnson: func_static's can now have their color parms affected by lights + else if ( targetEnt && targetEnt->IsType( idStaticEntity::GetClassType() ) ) { + targetEnt->GetRenderEntity()->referenceShader = renderLight.shader; + targetEnt->GetRenderEntity()->referenceSoundHandle = renderEntity.referenceSoundHandle; + } +// RAVEN END + } +} + +/* +================ +idLight::Event_FadeOut +================ +*/ +void idLight::Event_FadeOut( float time ) { + FadeOut( time ); +} + +/* +================ +idLight::Event_FadeIn +================ +*/ +void idLight::Event_FadeIn( float time ) { + FadeIn( time ); +} + +/* +================ +idLight::ClientPredictionThink +================ +*/ +void idLight::ClientPredictionThink( void ) { + Think(); +} + +/* +================ +idLight::WriteToSnapshot +================ +*/ +void idLight::WriteToSnapshot( idBitMsgDelta &msg ) const { + + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + + msg.WriteByte( currentLevel ); + msg.WriteLong( PackColor( baseColor ) ); + // msg.WriteBits( lightParent.GetEntityNum(), GENTITYNUM_BITS ); + +/* // only helps prediction + msg.WriteLong( PackColor( fadeFrom ) ); + msg.WriteLong( PackColor( fadeTo ) ); + msg.WriteLong( fadeStart ); + msg.WriteLong( fadeEnd ); +*/ + + // FIXME: send renderLight.shader + msg.WriteFloat( renderLight.lightRadius[0], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[1], 5, 10 ); + msg.WriteFloat( renderLight.lightRadius[2], 5, 10 ); + + msg.WriteLong( PackColor( idVec4( renderLight.shaderParms[SHADERPARM_RED], + renderLight.shaderParms[SHADERPARM_GREEN], + renderLight.shaderParms[SHADERPARM_BLUE], + renderLight.shaderParms[SHADERPARM_ALPHA] ) ) ); + + msg.WriteFloat( renderLight.shaderParms[SHADERPARM_TIMESCALE], 5, 10 ); + msg.WriteLong( renderLight.shaderParms[SHADERPARM_TIMEOFFSET] ); + //msg.WriteByte( renderLight.shaderParms[SHADERPARM_DIVERSITY] ); + msg.WriteShort( renderLight.shaderParms[SHADERPARM_MODE] ); + + WriteColorToSnapshot( msg ); +} + +/* +================ +idLight::ReadFromSnapshot +================ +*/ +void idLight::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idVec4 shaderColor; + int oldCurrentLevel = currentLevel; + idVec3 oldBaseColor = baseColor; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + currentLevel = msg.ReadByte(); + if ( currentLevel != oldCurrentLevel ) { + // need to call On/Off for flickering lights to start/stop the sound + // while doing it this way rather than through events, the flickering is out of sync between clients + // but at least there is no question about saving the event and having them happening globally in the world + if ( currentLevel ) { + On(); + } else { + Off(); + } + } + UnpackColor( msg.ReadLong(), baseColor ); + // lightParentEntityNum = msg.ReadBits( GENTITYNUM_BITS ); + +/* // only helps prediction + UnpackColor( msg.ReadLong(), fadeFrom ); + UnpackColor( msg.ReadLong(), fadeTo ); + fadeStart = msg.ReadLong(); + fadeEnd = msg.ReadLong(); +*/ + + // FIXME: read renderLight.shader + renderLight.lightRadius[0] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[1] = msg.ReadFloat( 5, 10 ); + renderLight.lightRadius[2] = msg.ReadFloat( 5, 10 ); + + UnpackColor( msg.ReadLong(), shaderColor ); + renderLight.shaderParms[SHADERPARM_RED] = shaderColor[0]; + renderLight.shaderParms[SHADERPARM_GREEN] = shaderColor[1]; + renderLight.shaderParms[SHADERPARM_BLUE] = shaderColor[2]; + renderLight.shaderParms[SHADERPARM_ALPHA] = shaderColor[3]; + + renderLight.shaderParms[SHADERPARM_TIMESCALE] = msg.ReadFloat( 5, 10 ); + renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadLong(); + //renderLight.shaderParms[SHADERPARM_DIVERSITY] = msg.ReadFloat(); + renderLight.shaderParms[SHADERPARM_MODE] = msg.ReadShort(); + + ReadColorFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( ( currentLevel != oldCurrentLevel ) || ( baseColor != oldBaseColor ) ) { + SetLightLevel(); + } else { + PresentLightDefChange(); + PresentModelDefChange(); + } + } +} + +/* +================ +idLight::ClientReceiveEvent +================ +*/ +bool idLight::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + + switch( event ) { + case EVENT_BECOMEBROKEN: { + BecomeBroken( NULL ); + return true; + } + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +// RAVEN BEGIN +// kfuller: 8/11/03 +void idLight::Event_IsOn() +{ + // not entirely sure this is the best way to check for offness + if (currentLevel == 0) + { + idThread::ReturnFloat( false ); + } + else + { + idThread::ReturnFloat( true ); + } +} + +void idLight::Event_Break(idEntity *activator, float turnOff) +{ + BecomeBroken(activator); + if (turnOff) + { + Off(); + } +} + +void idLight::Event_DoneBlinking() +{ + // switch to a new (possibly non-blinking) shader for the light as well as a new looping sound + idStr blinkedOn; + if (spawnArgs.GetString( "mtr_doneBlinking", "", blinkedOn )) + { + renderLight.shader = declManager->FindMaterial( blinkedOn, false ); + UpdateVisuals(); + Present(); + } + idStr doneBlinkingSound; + if (spawnArgs.GetBool("doneBlinkingNoSound")) + { + StopSound( SCHANNEL_ANY, false ); + } + else if (spawnArgs.GetString("snd_doneBlinking", "", doneBlinkingSound)) + { + StopSound( SCHANNEL_ANY, false ); + if (doneBlinkingSound.Icmp("none")) + { + refSound.shader = declManager->FindSound(doneBlinkingSound); + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); + soundWasPlaying = false; + } + } +} + +void idLight::Event_DoneBlinkingOff() +{ + // switch light and sound off + currentLevel = 0; + SetLightLevel(); +// RAVEN BEGIN + // kill any sound it was making + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying ( ) ) { +// RAVEN END + StopSound( SCHANNEL_ANY, false ); + soundWasPlaying = true; + } +} + +// kfuller: want fx entities to be able to respond to earthquakes +void idLight::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + + if (requiresLOS) + { + bool inPVS = gameLocal.InPlayerPVS( this ); + + // for lights, a line-of-sight check doesn't make as much sense, so if the quake requires an LOS check + //we'll actually perform a PVS check + if (!inPVS) + { + return; + } + } + // do something with this light + if (spawnArgs.GetBool("quakeBreak")) + { + spawnArgs.SetBool("quakeBreak", false); + BecomeBroken(gameLocal.entities[ENTITYNUM_WORLD]); + return; + } + + float offTime = spawnArgs.GetFloat("quakeOffTime", "1.0"); + + Off(); + PostEventMS(&EV_Light_On, offTime*1000.0f); +} + +/* +================ +idLight::Event_SetLightGUI +================ +*/ +void idLight::Event_SetLightGUI ( const char* gui ) { + lightGUI = gameLocal.FindEntity( gui ); + if ( lightGUI && lightGUI->GetRenderEntity() && lightGUI->GetRenderEntity()->gui[0] ) { + BecomeActive( TH_THINK ); + } else { + lightGUI = NULL; + } +} + +/* +================ +idLight::Event_SetCurrentLightLevel +================ +*/ +void idLight::Event_SetCurrentLightLevel( int in ) { + currentLevel = in; + SetLightLevel(); +} + +/* +================ +idLight::Event_SetMaxLightLevel +================ +*/ +void idLight::Event_SetMaxLightLevel( int in ) { + levels = in; + SetLightLevel(); +} + +// RAVEN END diff --git a/src/mpgame/Light.h b/src/mpgame/Light.h new file mode 100644 index 0000000..6a3f974 --- /dev/null +++ b/src/mpgame/Light.h @@ -0,0 +1,170 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_LIGHT_H__ +#define __GAME_LIGHT_H__ + +/* +=============================================================================== + + Generic light. + +=============================================================================== +*/ + +extern const idEventDef EV_Light_GetLightParm; +extern const idEventDef EV_Light_SetLightParm; +extern const idEventDef EV_Light_SetLightParms; + +class idLight : public idEntity { +public: + CLASS_PROTOTYPE( idLight ); + + idLight(); + ~idLight(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + virtual void Think( void ); + virtual void FreeLightDef( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + void Present( void ); + + void SaveState( idDict *args ); + virtual void SetColor( float red, float green, float blue ); + virtual void SetColor( const idVec4 &color ); + virtual void GetColor( idVec3 &out ) const; + virtual void GetColor( idVec4 &out ) const; + const idVec3 & GetBaseColor( void ) const { return baseColor; } + void SetShader( const char *shadername ); + void SetLightParm( int parmnum, float value ); + void SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void SetRadiusXYZ( float x, float y, float z ); + void SetRadius( float radius ); + void On( void ); + void Off( void ); + void Fade( const idVec4 &to, float fadeTime ); + void FadeOut( float time ); + void FadeIn( float time ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void BecomeBroken( idEntity *activator ); + qhandle_t GetLightDefHandle( void ) const { return lightDefHandle; } + void SetLightParent( idEntity *lparent ) { lightParent = lparent; } + void SetLightLevel( void ); + +// RAVEN BEGIN +// jshepard: other entities (speakers) need access to the refSound of a light object + void SetRefSound( int rSound ) { refSound.referenceSoundHandle = rSound;} +// ddynerman: sometimes the game needs to know if this light is ambient + bool IsAmbient( void ) { return renderLight.shader->IsAmbientLight(); } +// RAVEN END + virtual void ShowEditingDialog( void ); + + enum { + EVENT_BECOMEBROKEN = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + renderLight_t renderLight; // light presented to the renderer + idVec3 localLightOrigin; // light origin relative to the physics origin + idMat3 localLightAxis; // light axis relative to physics axis + qhandle_t lightDefHandle; // handle to renderer light def + idStr brokenModel; + int levels; + int currentLevel; + idVec3 baseColor; + bool breakOnTrigger; + int count; + int triggercount; + idEntity * lightParent; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; +// RAVEN BEGIN +// bdube: light gui + idEntityPtr lightGUI; +// abahr: + float wait; + float random; +// RAVEN END + +private: + bool soundWasPlaying; + + void PresentLightDefChange( void ); + void PresentModelDefChange( void ); + +// RAVEN BEGIN +// jscott: added events for light level +private: + void Event_SetCurrentLightLevel ( int in ); + void Event_SetMaxLightLevel ( int in ); + void Event_IsOn( void ); + void Event_Break( idEntity *activator, float turnOff ); + void Event_DoneBlinking( void ); + void Event_DoneBlinkingOff( void ); + void Event_EarthQuake( float requiresLOS ); + void Event_Timer( void ); +// RAVEN END + +private: + void Event_SetShader( const char *shadername ); + void Event_GetLightParm( int parmnum ); + void Event_SetLightParm( int parmnum, float value ); + void Event_SetLightParms( float parm0, float parm1, float parm2, float parm3 ); + void Event_SetRadiusXYZ( float x, float y, float z ); + void Event_SetRadius( float radius ); + void Event_Hide( void ); + void Event_Show( void ); + void Event_On( void ); + void Event_Off( void ); + void Event_ToggleOnOff( idEntity *activator ); + void Event_SetSoundHandles( void ); + void Event_FadeOut( float time ); + void Event_FadeIn( float time ); +// RAVEN BEGIN +// bdube: set light gui + void Event_SetLightGUI( const char* gui ); +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: externed events +extern const idEventDef EV_Light_SetCurrentLightLevel; +extern const idEventDef EV_Light_SetMaxLightLevel; +extern const idEventDef EV_Light_SetRadius; +// RAVEN END + +#endif /* !__GAME_LIGHT_H__ */ diff --git a/src/mpgame/LipSync.cpp b/src/mpgame/LipSync.cpp new file mode 100644 index 0000000..f9eb065 --- /dev/null +++ b/src/mpgame/LipSync.cpp @@ -0,0 +1,491 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// 1 normal +// 2 scared +// 3 surprised +// 4 panicked +// 5 angry +// 6 suspicious with rt eyelid raised +// 7 suspicious with lft eyelid raised +// 8 curious +// 9 tired +// 10 happy + +idCVar fas_debug( "fas_debug", "0", CVAR_INTEGER, "debug info for facial animation system" ); +idCVar fas_threshhold0( "fas_threshhold0", "60", CVAR_INTEGER, "intensity required to use frame set 0" ); +idCVar fas_threshhold1( "fas_threshhold1", "30", CVAR_INTEGER, "intensity required to use frame set 1" ); +idCVar fas_blendBias( "fas_blendBias", "1.5", 0, "multiplier to the per phoneme blend time" ); +idCVar fas_intensityBias( "fas_intensityBias", "0", CVAR_INTEGER, "bias applied to the intensity of the phoneme when trying to extract the viseme" ); +idCVar fas_timeOffset( "fas_timeOffset", "50", CVAR_INTEGER, "ms offset to the viseme frame" ); + +idStr phonemeFile; +idHashTable *visemeTable100; +idHashTable *visemeTable66; +idHashTable *visemeTable33; + +/* +================ +rvViseme::Init +================ +*/ +void rvViseme::Init( idStr &phon, int f, int bt ) +{ + phoneme = phon; + frame = f; + blendTime = bt; +} + +/* +================ +FAS_LoadPhonemes + +Load in the the file that cross references phonemes with visemes. +================ +*/ +bool FAS_LoadPhonemes( const char *visemes ) +{ + idStr visemeFile; + rvViseme viseme; + idLexer lexer; + idToken token; + idStr phoneme; + int frame, blendTime, intensity; + + phonemeFile = visemes; + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + + common->Printf( "Loading viseme file: %s\n", visemes ); + + visemeFile = "lipsync/"; + visemeFile += visemes; + visemeFile += ".viseme"; + + lexer.SetFlags( DECL_LEXER_FLAGS ); + lexer.LoadFile( visemeFile ); + + if( !lexer.ExpectTokenString( "visemes" ) ) + { + return( false ); + } + + if( !lexer.ExpectTokenString( "{" ) ) + { + return( false ); + } + + while( true ) + { + if( !lexer.ReadToken( &token ) ) + { + return( false ); + } + + if( token == "}" ) + { + break; + } + + phoneme = token; + lexer.ExpectTokenString( "," ); + frame = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + blendTime = lexer.ParseInt(); + lexer.ExpectTokenString( "," ); + intensity = lexer.ParseInt(); + + viseme.Init( phoneme, frame, blendTime ); + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Set( phoneme, viseme ); + } + else if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Set( phoneme, viseme ); + } + else + { + visemeTable33->Set( phoneme, viseme ); + } + } + + return( true ); +} + +/* +================ +rvLipSyncData +================ +*/ +rvLipSyncData::rvLipSyncData( const rvDeclLipSync *ls, int time ) +{ + const char *lsd = ls->GetLipSyncData(); + + mLexer.SetFlags( LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWNUMBERNAMES ); + mLexer.LoadMemory( lsd, idStr::Length( lsd ), ls->GetName() ); + mFlags = 0; + mFrame = 0; + mBlendTime = 0; + mEmotion = "idle"; + mNextTokenTime = time; +} + +void rvLipSyncData::SetFrame( int frame ) +{ + mLastFrame = mFrame; + mFrame = frame; + mVisemeStartTime = mNextTokenTime; +} + +float rvLipSyncData::GetFrontLerp( void ) +{ + float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( float )( gameLocal.GetTime() + fas_timeOffset.GetInteger() - mVisemeStartTime ) / ( float )mBlendTime ); + return( lerp ); +} + +/* +================ +FAS_StartVisemeExtraction + +Use a lexer to extract the phoneme data. This is a pretty slow but safe way. +There shouldn't be much in the way of multiple lip syncs going on, and if there are, it should be in a non performance critical cinematic. +================ +*/ +rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ) +{ + rvLipSyncData *lsd; + + lsd = new rvLipSyncData( ls, time ); + + return( lsd ); +} + +/* +================ +FAS_EndVisemeExtraction + +Delete the workspace. FAS could use a void pointer if it wanted +================ +*/ +void FAS_EndVisemeExtraction( rvLipSyncData *lsd ) +{ + delete lsd; +} + +/* +================ +FAS_ExtractViseme + +Extract the correct viseme frame from the lexer +================ +*/ +void FAS_ExtractViseme( rvLipSyncData *lsd, int time ) +{ + idToken token; + rvViseme *viseme; + idStr phoneme, duration; + int index, intensity; + + // Make sure not to return any garbage + lsd->ClearFlags(); + + // Grab all the visemes, phrases and emotions until we are current + while( lsd->Ready( time ) ) + { + if( !lsd->ReadToken( &token ) ) + { + lsd->SetFlags( FAS_ENDED ); + return; + } + + if( token == "<" ) + { + // Extract phrase + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse phrase from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetLastPhrase( token ); + lsd->ExpectTokenString( ">" ); + + lsd->SetFlags( FAS_NEW_PHRASE ); + } + else if( token == "{" ) + { + // Extract emotion + if( !lsd->ReadToken( &token ) ) + { + common->Printf( "Failed to parse emotion from phoneme string\n" ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetEmotion( token ); + lsd->ExpectTokenString( "}" ); + + lsd->SetFlags( FAS_NEW_EMOTION ); + } + else + { + // Extract phoneme data + index = 0; + phoneme = idStr( token[index] ); + if( isupper( phoneme[0] ) ) + { + index++; + phoneme += token[index]; + } + + // Extract duration + index++; + + duration = idStr( token[index] ); + index++; + if( isdigit( token[index] ) ) + { + duration += token[index]; + index++; + } + + // Extract intensity + intensity = ( token[index] - 'a' ) * 4; + intensity += fas_intensityBias.GetInteger(); + + // Extract the viseme data for the selected viseme + viseme = NULL; + + if( intensity > fas_threshhold0.GetInteger() ) + { + visemeTable100->Get( phoneme, &viseme ); + } + if( intensity > fas_threshhold1.GetInteger() ) + { + visemeTable66->Get( phoneme, &viseme ); + } + else + { + visemeTable33->Get( phoneme, &viseme ); + } + + if( !viseme ) + { + common->Printf( "FAS: Failed to find phoneme %s intensity %d", phoneme.c_str(), intensity ); + lsd->SetFlags( FAS_ENDED ); + return; + } + + lsd->SetFrame( viseme->GetFrame() ); + lsd->SetNextTokenTime( atol( duration ) * 10 ); + lsd->SetBlendTime( int( viseme->GetBlendTime() * fas_blendBias.GetFloat() ) ); + lsd->SetFlags( FAS_NEW_VISEME ); + } + } +} + +/* +================ +FAS_Reload_f +================ +*/ +void FAS_Reload_f( const idCmdArgs &args ) +{ + // mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + + FAS_LoadPhonemes( phonemeFile.c_str() ); + + fileSystem->SetIsFileLoadingAllowed( false ); +} + +/* +================ +FAS_Init +================ +*/ +bool FAS_Init( const char *visemes ) +{ + // jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG( tag, MA_ANIM ); + + cmdSystem->AddCommand( "reloadFAS", FAS_Reload_f, CMD_FL_SYSTEM, "reloads the viseme data" ); + return( FAS_LoadPhonemes( visemes ) ); +} + +/* +================ +FAS_Shutdown +================ +*/ +void FAS_Shutdown( void ) +{ + phonemeFile.Clear(); + if ( visemeTable100 ) { + visemeTable100->Clear(); + visemeTable66->Clear(); + visemeTable33->Clear(); + } + + cmdSystem->RemoveCommand( "reloadFAS" ); +} + +/* +================ +idAFAttachment::EndLipSyncing +================ +*/ +void idAFAttachment::EndLipSyncing( void ) +{ + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0.0f }; + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + animator.CycleAnim( ANIMCHANNEL_HEAD, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, animator.GetAnim( "emotion_idle" ), gameLocal.time, 200 ); + + FAS_EndVisemeExtraction( lipSyncData ); + lipSyncData = NULL; +} + +/* +================ +idAFAttachment::StartLipSyncing +================ +*/ +int idAFAttachment::StartLipSyncing( const char *speechDecl ) +{ + int length; + + length = 0; + + // Clean up any spurious data + EndLipSyncing(); + + // Start a new lipsync if there is one + if( speechDecl[0] ) + { + const rvDeclLipSync *lipSync; + int emotion; + idStr anim; + + lipSync = declManager->FindLipSync( speechDecl ); + lipSyncData = FAS_StartVisemeExtraction( lipSync, gameLocal.GetTime() ); + + // Output debug info + if( lipSync->GetDescription() && fas_debug.GetInteger() ) + { + gameLocal.Printf( "Name: %s\n", speechDecl ); + gameLocal.Printf( "Sub: %s\n", lipSync->GetDescription().c_str() ); + gameLocal.Printf( "Lip: %s\n", lipSync->GetLipSyncData() ); + } + + // Start the associated sound + refSound.diversity = 0.0f; + renderEntity.referenceSoundHandle = refSound.referenceSoundHandle; + StartSoundShader( declManager->FindSound( lipSync->GetName() ), SND_CHANNEL_VOICE, refSound.parms.soundShaderFlags | SSF_IS_VO, false, &length ); + + // Start the default emotion + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + } + + return( length ); +} + +/* +================ +idAFAttachment::HandleLipSync +================ +*/ +void idAFAttachment::HandleLipSync( void ) +{ + idStr anim; + int emotion; + + if( !lipSyncData ) + { + return; + } + + FAS_ExtractViseme( lipSyncData, gameLocal.GetTime() + fas_timeOffset.GetInteger() ); + if( lipSyncData->HasEnded() ) + { + EndLipSyncing(); + return; + } + + // If frame non zero - blend to it as a new viseme + if( lipSyncData->GetFrame() || lipSyncData->GetLastFrame() ) + { + frameBlend_t frameBlend; + + frameBlend.cycleCount = 0; + frameBlend.frame1 = idMath::ClampInt( 0, 120, lipSyncData->GetLastFrame() - 1 ); + frameBlend.frame2 = idMath::ClampInt( 0, 120, lipSyncData->GetFrame() - 1 ); + frameBlend.frontlerp = lipSyncData->GetFrontLerp(); + frameBlend.backlerp = 1.0f - frameBlend.frontlerp; + + animator.SetFrame( ANIMCHANNEL_TORSO, lipSyncAnim, frameBlend ); + + if( fas_debug.GetInteger() > 1 ) + { + common->Printf( "Blending: %d (%2f) -> %d (%2f)\n", frameBlend.frame1, frameBlend.frontlerp, frameBlend.frame2, frameBlend.backlerp ); + } + } + + // If an embedded emotion command, play it. + if( lipSyncData->HasNewEmotion() ) + { + anim = "emotion_"; + anim += lipSyncData->GetEmotion(); + emotion = animator.GetAnim( anim ); + animator.CycleAnim( ANIMCHANNEL_HEAD, emotion, gameLocal.time, 200 ); + animator.CycleAnim( ANIMCHANNEL_EYELIDS, emotion, gameLocal.time, 200 ); + + if( fas_debug.GetInteger() ) + { + common->Printf( "Emotion: %s\n", lipSyncData->GetEmotion().c_str() ); + } + } + + // If a new phrase, display in debug mode + if( fas_debug.GetInteger() && lipSyncData->HasNewPhrase() ) + { + common->Printf( "Phrase: %s(%i)\n", lipSyncData->GetLastPhrase().c_str(), lipSyncData->GetFrame() ); + } +} + +// end diff --git a/src/mpgame/LipSync.h b/src/mpgame/LipSync.h new file mode 100644 index 0000000..0ac903b --- /dev/null +++ b/src/mpgame/LipSync.h @@ -0,0 +1,111 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _LIPSYNC_H_INC_ +#define _LIPSYNC_H_INC_ + +// Possible TBDs +// Auto head swapping from simple to lip sync head +// Return time of sound from the speak call for the script to wait +// Set emotion directly without encoding it into the string + +class rvViseme +{ +public: + rvViseme( void ) {} + ~rvViseme( void ) { phoneme.Clear(); } + + void Init( idStr &phon, int f, int bt ); + + int GetFrame( void ) const { return( frame ); } + int GetBlendTime( void ) const { return( blendTime ); } + +private: + idStr phoneme; + int frame; + int blendTime; +}; + +#define FAS_NEW_VISEME BIT( 0 ) +#define FAS_NEW_PHRASE BIT( 1 ) +#define FAS_NEW_EMOTION BIT( 2 ) +#define FAS_ENDED BIT( 3 ) + +class rvLipSyncData +{ +public: + rvLipSyncData( const rvDeclLipSync *ls, int time ); + ~rvLipSyncData( void ) {} + + bool Ready( int time ) { return( time >= mNextTokenTime ); } + + int ReadToken( idToken *token ) { return( mLexer.ReadToken( token ) ); } + int ExpectTokenString( const char *str ) { return( mLexer.ExpectTokenString( str ) ); } + void SetNextTokenTime( int time ) { mNextTokenTime += time; } + + void ClearFlags( void ) { mFlags = 0; } + void SetFlags( int flags ) { mFlags |= flags; } + bool HasNewPhoneme( void ) const { return( !!( mFlags & FAS_NEW_VISEME ) ); } + bool HasNewPhrase( void ) const { return( !!( mFlags & FAS_NEW_PHRASE ) ); } + bool HasNewEmotion( void ) const { return( !!( mFlags & FAS_NEW_EMOTION ) ); } + bool HasEnded( void ) const { return( !!( mFlags & FAS_ENDED ) ); } + + void SetFrame( int frame ); + int GetFrame( void ) const { return( mFrame ); } + int GetLastFrame( void ) { return( mLastFrame ); } + + void SetBlendTime( int bt ) { mBlendTime = bt; } + int GetBlendTime( void ) const { return( mBlendTime ); } + + float GetFrontLerp( void ); + + void SetEmotion( idStr &str ) { mEmotion = str; } + const idStr &GetEmotion( void ) const { return( mEmotion ); } + + void SetLastPhrase( idStr &str ) { mLastPhrase = str; } + const idStr &GetLastPhrase( void ) const { return( mLastPhrase ); } + +private: + int mNextTokenTime; + int mFlags; + int mFrame; + int mLastFrame; + int mVisemeStartTime; + int mBlendTime; + idStr mEmotion; + idStr mLastPhrase; + idLexer mLexer; +}; + +bool FAS_Init( const char *visemes ); +void FAS_Shutdown( void ); + +void FAS_ExtractViseme( class rvLipSyncData *lsd, int time ); +class rvLipSyncData *FAS_StartVisemeExtraction( const rvDeclLipSync *ls, int time ); +void FAS_EndVisemeExtraction( class rvLipSyncData *lsd ); + +extern idCVar fas_debug; +extern idCVar fas_timeOffset; + +#endif // _LIPSYNC_H_INC_ diff --git a/src/mpgame/Misc.cpp b/src/mpgame/Misc.cpp new file mode 100644 index 0000000..661bf93 --- /dev/null +++ b/src/mpgame/Misc.cpp @@ -0,0 +1,4407 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Various utility objects and functions. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +// RAVEN BEGIN +// nmckenzie: added ai +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif +#include "ai/AI.h" +// RAVEN END + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpawnableEntity ) +END_CLASS + +/* +====================== +idSpawnableEntity::Spawn +====================== +*/ +void idSpawnableEntity::Spawn() { + // this just holds dict information +} + + +/* +=============================================================================== + + idPlayerStart + +=============================================================================== +*/ + +const idEventDef EV_TeleportStage( "", "e" ); + +CLASS_DECLARATION( idEntity, idPlayerStart ) + EVENT( EV_Activate, idPlayerStart::Event_Teleport ) + EVENT( EV_TeleportStage, idPlayerStart::Event_TeleportStage ) +END_CLASS + +/* +=============== +idPlayerStart::idPlayerStart +================ +*/ +idPlayerStart::idPlayerStart( void ) { + teleportStage = 0; +} + +/* +=============== +idPlayerStart::Spawn +================ +*/ +void idPlayerStart::Spawn( void ) { + teleportStage = 0; +} + +/* +================ +idPlayerStart::Save +================ +*/ +void idPlayerStart::Save( idSaveGame *savefile ) const { + savefile->WriteInt( teleportStage ); +} + +/* +================ +idPlayerStart::Restore +================ +*/ +void idPlayerStart::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( teleportStage ); +} + +/* +================ +idPlayerStart::ClientReceiveEvent +================ +*/ +bool idPlayerStart::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int entityNumber; + + switch( event ) { + case EVENT_TELEPORTPLAYER: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idPlayer *player = static_cast( gameLocal.entities[entityNumber] ); + float x = msg.ReadFloat(); + float y = msg.ReadFloat(); + float z = msg.ReadFloat(); + + idVec3 prevOrigin( x, y, z ); + if ( player != NULL && player->IsType( idPlayer::GetClassType() ) ) { + Event_TeleportEntity( player, false, prevOrigin ); + } + return true; + } + case EVENT_TELEPORTITEM: { + entityNumber = msg.ReadBits( GENTITYNUM_BITS ); + idEntity* activator = gameLocal.entities[ entityNumber ]; + + if ( activator != NULL ) { + Event_TeleportEntity( activator, false ); + } + return true; + } + + default: { + return idEntity::ClientReceiveEvent( event, time, msg ); + } + } +//unreachable +// return false; +} + +/* +=============== +idPlayerStart::Event_TeleportStage + +FIXME: add functionality to fx system ( could be done with player scripting too ) +================ +*/ +void idPlayerStart::Event_TeleportStage( idPlayer *player ) { + float teleportDelay = spawnArgs.GetFloat( "teleportDelay" ); + switch ( teleportStage ) { + case 0: + player->playerView.Flash( colorWhite, 125 ); + player->SetInfluenceLevel( INFLUENCE_LEVEL3 ); + player->SetInfluenceView( spawnArgs.GetString( "mtr_teleportFx" ), NULL, 0.0f, NULL ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -20.0f, teleportDelay ); + teleportStage++; + PostEventSec( &EV_TeleportStage, teleportDelay, player ); + break; + case 1: + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0.25f ); + teleportStage++; + PostEventSec( &EV_TeleportStage, 0.25f, player ); + break; + case 2: + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + TeleportPlayer( player ); + player->StopSound( SND_CHANNEL_BODY2, false ); + player->SetInfluenceLevel( INFLUENCE_NONE ); + teleportStage = 0; + break; + default: + break; + } +} + +/* +=============== +idPlayerStart::TeleportPlayer +================ +*/ +void idPlayerStart::TeleportPlayer( idPlayer *player ) { + float pushVel = spawnArgs.GetFloat( "push", "50000" ); + float f = spawnArgs.GetFloat( "visualEffect", "0" ); + const char *viewName = spawnArgs.GetString( "visualView", "" ); + idEntity *ent = viewName ? gameLocal.FindEntity( viewName ) : NULL; + + if ( f && ent && !player->spectating ) { + // place in private camera view for some time + // the entity needs to teleport to where the camera view is to have the PVS right + player->Teleport( ent->GetPhysics()->GetOrigin(), ang_zero, this ); + + player->SetPrivateCameraView( static_cast(ent) ); + // the player entity knows where to spawn from the previous Teleport call + if ( !gameLocal.isClient ) { + player->PostEventSec( &EV_Player_ExitTeleporter, f ); + } + } else { + // direct to exit, Teleport will take care of the killbox + player->Teleport( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles(), NULL ); + + // multiplayer hijacked this entity, so only push the player in multiplayer + if ( gameLocal.isMultiplayer ) { + // push + idVec3 impulse( pushVel, 0, 0 ); + impulse *= GetPhysics( )->GetAxis ( ); + player->ApplyImpulse( gameLocal.world, 0, player->GetPhysics( )->GetOrigin( ), impulse ); + } + } +} + +/* +=============== +idPlayerStart::Teleport +For non-players +================ +*/ +void idPlayerStart::Teleport( idEntity* other ) { + other->SetOrigin( GetPhysics()->GetOrigin() ); + idVec3 vel = other->GetPhysics()->GetLinearVelocity(); + vel *= GetPhysics()->GetAxis(); + other->GetPhysics()->SetLinearVelocity( vel ); +} + +void idPlayerStart::Event_TeleportEntity( idEntity* activator, bool predicted, idVec3& prevOrigin ) { + idPlayer *player; + + if ( activator->IsType( idPlayer::GetClassType() ) ) { + player = static_cast( activator ); + } else { + player = NULL; + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( activator->entityNumber, GENTITYNUM_BITS ); + ServerSendInstanceEvent( EVENT_TELEPORTITEM, &msg, false, -1 ); + } + + idVec3 oldOrigin = activator->GetPhysics()->GetOrigin(); + Teleport( activator ); + + if( gameLocal.isMultiplayer ) { + if( gameLocal.isServer ) { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", activator->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else if( predicted ) { + // only predict teleport enter + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } else { + gameLocal.PlayEffect( activator->spawnArgs, "fx_teleport", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + + if ( player ) { + if ( spawnArgs.GetBool( "visualFx" ) ) { + + teleportStage = 0; + Event_TeleportStage( player ); + + } else { + idVec3 oldOrigin; + + if ( gameLocal.isServer || prevOrigin == vec3_zero ) { + oldOrigin = activator->GetPhysics()->GetOrigin(); + } else { + oldOrigin = prevOrigin; + } + + if ( gameLocal.isServer && !player->spectating ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( player->entityNumber, GENTITYNUM_BITS ); + msg.WriteFloat( oldOrigin.x ); + msg.WriteFloat( oldOrigin.y ); + msg.WriteFloat( oldOrigin.z ); + ServerSendInstanceEvent( EVENT_TELEPORTPLAYER, &msg, false, -1 ); + } + + TeleportPlayer( player ); + + if ( gameLocal.isMultiplayer ) { + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport_enter", oldOrigin, idVec3(0,0,1).ToMat3(), false, vec3_origin ); + gameLocal.PlayEffect( player->spawnArgs, "fx_teleport", player->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + } + } + } + } + + +/* +=============== +idPlayerStart::Event_TeleportPlayer +================ +*/ +void idPlayerStart::Event_Teleport( idEntity *activator ) { + Event_TeleportEntity( activator, true ); +} + +/* +=============================================================================== + + idActivator + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idActivator ) + EVENT( EV_Activate, idActivator::Event_Activate ) +END_CLASS + +/* +=============== +idActivator::Save +================ +*/ +void idActivator::Save( idSaveGame *savefile ) const { + savefile->WriteBool( stay_on ); +} + +/* +=============== +idActivator::Restore +================ +*/ +void idActivator::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( stay_on ); + + if ( stay_on ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Spawn +================ +*/ +void idActivator::Spawn( void ) { + bool start_off; + + spawnArgs.GetBool( "stay_on", "0", stay_on ); + spawnArgs.GetBool( "start_off", "0", start_off ); + +// RAVEN BEGIN +// bdube: optional clip model on activators + const char *temp; + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + GetPhysics()->SetClipModel( new idClipModel(temp), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } else { + GetPhysics()->SetClipBox( idBounds( vec3_origin ).Expand( 4 ), 1.0f ); + } + + GetPhysics()->SetContents( 0 ); +// RAVEN END + + if ( !start_off ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idActivator::Think +================ +*/ +void idActivator::Think( void ) { + RunPhysics(); + if ( thinkFlags & TH_THINK ) { + if ( TouchTriggers() ) { + if ( !stay_on ) { + BecomeInactive( TH_THINK ); + } + } + } + Present(); +} + +/* +=============== +idActivator::Activate +================ +*/ +void idActivator::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + + +/* +=============================================================================== + +idPathCorner + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPathCorner ) +// EVENT( AI_RandomPath, idPathCorner::Event_RandomPath ) +END_CLASS + +/* +===================== +idPathCorner::Spawn +===================== +*/ +void idPathCorner::Spawn( void ) { +} + +/* +===================== +idPathCorner::DrawDebugInfo +===================== +*/ +void idPathCorner::DrawDebugInfo( void ) { + idEntity *ent; + idBounds bnds( idVec3( -4.0, -4.0f, -8.0f ), idVec3( 4.0, 4.0f, 64.0f ) ); + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( !ent->IsType( idPathCorner::Type ) ) { + continue; + } + + idVec3 org = ent->GetPhysics()->GetOrigin(); + gameRenderWorld->DebugBounds( colorRed, bnds, org, 0 ); + } +} + +/* +============ +idPathCorner::RandomPath +============ +*/ +idPathCorner *idPathCorner::RandomPath( const idEntity *source, const idEntity *ignore ) { + int i; + int num; + int which; + idEntity *ent; + idPathCorner *path[ MAX_GENTITIES ]; + + num = 0; + for( i = 0; i < source->targets.Num(); i++ ) { + ent = source->targets[ i ].GetEntity(); + if ( ent && ( ent != ignore ) && ent->IsType( idPathCorner::Type ) ) { + path[ num++ ] = static_cast( ent ); + if ( num >= MAX_GENTITIES ) { + break; + } + } + } + + if ( !num ) { + return NULL; + } + + which = gameLocal.random.RandomInt( num ); + return path[ which ]; +} + +/* +===================== +idPathCorner::Event_RandomPath +===================== +*/ +void idPathCorner::Event_RandomPath( void ) { + idPathCorner *path; + + path = RandomPath( this, NULL ); + idThread::ReturnEntity( path ); +} + +/* +=============================================================================== + + idDamagable + +=============================================================================== +*/ + +const idEventDef EV_RestoreDamagable( "" ); +// RAVEN BEGIN +// kfuller: spawn other things +const idEventDef EV_SpawnForcefield( "" ); +const idEventDef EV_SpawnTriggerHurt( "" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idDamagable ) + EVENT( EV_Activate, idDamagable::Event_BecomeBroken ) + EVENT( EV_RestoreDamagable, idDamagable::Event_RestoreDamagable ) +END_CLASS + +/* +================ +idDamagable::idDamagable +================ +*/ +idDamagable::idDamagable( void ) { + count = 0; + nextTriggerTime = 0; + invincibleTime = 0; +} + +/* +================ +idDamagable::Save +================ +*/ +void idDamagable::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( invincibleTime ); + + savefile->WriteInt( stage ); + savefile->WriteInt( stageNext ); + + // cnicholson: Don't save the stageDict, its setup in the restore + + savefile->WriteInt( stageEndTime ); + savefile->WriteInt( stageEndHealth ); + savefile->WriteInt( stageEndSpeed ); + savefile->WriteBool( stageEndOnGround ); + savefile->WriteBool( activateStageOnTrigger ); + + savefile->WriteInt( count ); + savefile->WriteInt( nextTriggerTime ); +} + +/* +================ +idDamagable::Restore +================ +*/ +void idDamagable::Restore( idRestoreGame *savefile ) { + + const char* stageName; + + savefile->ReadInt( invincibleTime ); + + savefile->ReadInt( stage ); + savefile->ReadInt( stageNext ); + savefile->ReadInt( stageEndTime ); + savefile->ReadInt( stageEndHealth ); + savefile->ReadInt( stageEndSpeed ); + savefile->ReadBool( stageEndOnGround ); + savefile->ReadBool( activateStageOnTrigger ); + + savefile->ReadInt( count ); + savefile->ReadInt( nextTriggerTime ); + + + // Get the stage name, if there is none then there are no more stages + stageDict = NULL; + if ( spawnArgs.GetString ( va( "def_stage%d", stage ), "", &stageName ) && stageName ) { + + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + } +} + +/* +================ +idDamagable::Spawn +================ +*/ +void idDamagable::Spawn( void ) { + idStr broken; + bool keepContents; + +// RAVEN BEGIN +// abahr: stage stuff + stage = 0; + stageNext = 1; + stageDict = NULL; + stageEndTime = 0; + stageEndHealth = 9999; + stageEndSpeed = 0; +//jshepard: + stageEndOnGround = false; +// RAVEN END + + health = spawnArgs.GetInt( "health", "5" ); + +// RAVEN BEGIN +// abahr: stage stuff + stageEndHealth = health - 1; + activateStageOnTrigger = spawnArgs.GetBool("activateOnTrigger"); +// RAVEN END + + spawnArgs.GetInt( "count", "1", count ); + invincibleTime = gameLocal.GetTime() + SEC2MS( spawnArgs.GetFloat( "invincibleTime", "0" ) ); + nextTriggerTime = 0; + + // make sure the model gets cached + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() && !renderModelManager->CheckModel( broken ) ) { + gameLocal.Error( "idDamagable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), broken.c_str() ); + } + +// RAVEN BEGIN +// bdube: turn take damage off if it has no health + fl.takedamage = health > 0 ? true : false; +// RAVEN END + + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idDamagable::BecomeBroken +================ +*/ +void idDamagable::BecomeBroken( idEntity *activator ) { + float forceState; + int numStates; + int cycle; + float wait; + + if ( gameLocal.time < nextTriggerTime ) { + return; + } + + spawnArgs.GetFloat( "wait", "0.1", wait ); + nextTriggerTime = gameLocal.time + SEC2MS( wait ); + if ( count > 0 ) { + count--; + if ( !count ) { + fl.takedamage = false; + } else { + health = spawnArgs.GetInt( "health", "5" ); + } + } + + idStr broken; + + spawnArgs.GetString( "broken", "", broken ); + if ( broken.Length() ) { + SetModel( broken ); + } + + // offset the start time of the shader to sync it to the gameLocal time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + spawnArgs.GetInt( "numstates", "1", numStates ); + spawnArgs.GetInt( "cycle", "0", cycle ); + spawnArgs.GetFloat( "forcestate", "0", forceState ); + + // set the state parm + if ( cycle ) { + renderEntity.shaderParms[ SHADERPARM_MODE ]++; + if ( renderEntity.shaderParms[ SHADERPARM_MODE ] > numStates ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = 0; + } + } else if ( forceState ) { + renderEntity.shaderParms[ SHADERPARM_MODE ] = forceState; + } else { + renderEntity.shaderParms[ SHADERPARM_MODE ] = gameLocal.random.RandomInt( numStates ) + 1; + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + ActivateTargets( activator ); + + if ( spawnArgs.GetBool( "hideWhenBroken" ) ) { + Hide(); + PostEventMS( &EV_RestoreDamagable, nextTriggerTime - gameLocal.time ); + BecomeActive( TH_THINK ); + } +} + +/* +============ +idMoveable::Damage +============ +*/ +// RAVEN BEGIN +// abahr +void idDamagable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + + if ( invincibleTime > gameLocal.GetTime() ) { + return; + } + + // If there is a damage filter we need to check to see if the damage def meets the requirement + const char* damageFilter; + if ( spawnArgs.GetString ( "damage_filter", "", &damageFilter ) && *damageFilter ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + // If the filter isnt matched then ignore it + if ( !damageDef->GetBool ( va("filter_%s", damageFilter ) ) ) { + return; + } + } + if ( spawnArgs.GetBool( "nosplash", "0" ) ) { + //ignore splash damage + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + //if it has radius, then it's splash damage and ignore it + if ( damageDef->GetFloat ( "radius", "0" ) ) { + return; + } + } + + idEntity::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Force a stage update so impact effects will be correct + UpdateStage(); +} + +/* +================ +idDamagable::Killed +================ +*/ +void idDamagable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( gameLocal.time < nextTriggerTime ) { + health += damage; + return; + } + + BecomeBroken( attacker ); +} + +/* +================ +idDamagable::UpdateStage +================ +*/ +void idDamagable::UpdateStage ( void ) { + + // If there is a stage to go to then see if its time to do that + if ( stage != stageNext ) { + int oldstage; + + // Check to see if the stage is complete + oldstage = stage; + while ( health < stageEndHealth || (stageEndTime != 0 && gameLocal.time > stageEndTime ) || (stageEndOnGround && GetPhysics()->HasGroundContacts()) || activateStageOnTrigger ) { + stage = stageNext; + + //this only needs to happen once + activateStageOnTrigger = false; + + // Get the stage name, if there is none then there are no more stages + const char* stageName; + if ( !spawnArgs.GetString ( va("def_stage%d", stage ), "", &stageName ) || !stageName ) { + stage = 0; + break; + } + + // Get the stage dictionary + stageDict = gameLocal.FindEntityDefDict ( stageName, false ); + if ( !stageDict ) { + gameLocal.Warning ( "could not find stage '%s' for moveable '%s'", stageName, name.c_str() ); + stage = 0; + break; + } + + // Get the end conditions of the stage + stageEndHealth = stageDict->GetInt ( "end_health", "-9999" ); + + stageEndOnGround = stageDict->GetBool( "end_onGround" ); + + // Timed? + stageEndTime = SEC2MS ( GetStageFloat ( "end_time" ) ); + if ( stageEndTime > 0 ) { + stageEndTime += gameLocal.time; + } + + // Set the next stage to move to when end conditions met + stageNext = stage + 1; + + // Execute the new stage + ExecuteStage ( ); + } + } +} + +/* +================ +idDamagable::ExecuteStage +================ +*/ +void idDamagable::ExecuteStage ( void ) { + const idKeyValue* kv; + bool remove; + + // Remove the entity this frame? + remove = stageDict->GetBool ( "remove" ); + + // Targets? + if ( stageDict->GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } + + // Unbind targets + if ( stageDict->GetBool( "unbindTargets" ) ) { + UnbindTargets( this ); + } + + // Stop current looping effects + StopAllEffects ( ); + + // Play all effects (if they start with "fx_loop" then also loop them + for ( kv = stageDict->MatchPrefix ( "fx_" ); kv; kv = stageDict->MatchPrefix("fx_",kv) ) { + if ( !kv->GetKey().Icmpn ( "fx_loop", 7 ) ) { + if ( !remove ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, kv->GetKey() ), + renderEntity.origin + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() * renderEntity.axis, + true ); + } + } else { + gameLocal.PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_explode" ), + GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str() ) ) * GetPhysics()->GetAxis(), + GetStageVector ( va("dir_%s", kv->GetKey().c_str() ), "1 0 0" ).ToMat3() ); + } + } + + // Kick off debris + for ( kv = stageDict->MatchPrefix ( "def_debris" ); kv; kv = stageDict->MatchPrefix("def_debris",kv) ) { + const idDict* args = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !args ) { + continue; + } + + rvClientMoveable* cent = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *args, (rvClientEntity**)(¢), false, "rvClientMoveable" ); + + if( !cent ) { + continue; + } + cent->SetOrigin ( GetPhysics()->GetOrigin() + GetStageVector ( va("offset_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis() ); + cent->SetAxis ( GetPhysics()->GetAxis ( ) ); + + idVec3 vel; + vel = GetStageVector ( va("vel_%s", kv->GetKey().c_str()) ) * GetPhysics()->GetAxis(); + vel += GetPhysics()->GetLinearVelocity ( ); + cent->GetPhysics()->SetLinearVelocity ( vel ); + cent->PostEventMS ( &CL_FadeOut, 2500, 2500 ); + } + + // Apply force to all objects nearby + float fPush; + stageDict->GetFloat("radiusPush","0",fPush); + if (fPush > 0) { + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 128, fPush, this, this, 1.0f, true ); + } + + // Remove the entity now? + if ( remove ) { + Hide ( ); + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Switch model? + const char* model; + if ( stageDict->GetString ( "model", "", &model ) && *model ) { + SetModel( model ); + } + + // Skin? + const char* skin; + if ( stageDict->GetString ( "skin", "", &skin ) && *skin ) { + renderEntity.customSkin = declManager->FindSkin ( skin, false ); + } + + // Velocities + idVec3 vel; + vel = GetStageVector ( "vel_world" ); + vel += (GetStageVector ( "vel_local" ) * GetPhysics()->GetAxis() ); + vel += GetPhysics()->GetLinearVelocity ( ); + GetPhysics()->SetLinearVelocity ( vel ); + + // Enable thinking to ensure stages are run + BecomeActive ( TH_THINK ); +} + +/* +================ +idDamagable::GetStageVector +================ +*/ +idVec3 idDamagable::GetStageVector ( const char* key, const char* defaultString ) const { + idVec3 mins; + if ( stageDict->GetVector ( va("%s_min", key ), "", mins ) ) { + idVec3 maxs; + stageDict->GetVector ( va("%s_max", key), mins.ToString(), maxs ); + return mins + (maxs - mins) * gameLocal.random.RandomFloat ( ); + } + + return stageDict->GetVector ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageFloat +================ +*/ +float idDamagable::GetStageFloat ( const char* key, const char* defaultString ) const { + float minValue; + if ( stageDict->GetFloat ( va("%s_min", key ), "", minValue ) ) { + float maxValue; + stageDict->GetFloat ( va("%s_max", key), va("%g",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetFloat ( key, defaultString ); +} + +/* +================ +idDamagable::GetStageInt +================ +*/ +int idDamagable::GetStageInt ( const char* key, const char* defaultString ) const { + int minValue; + if ( stageDict->GetInt ( va("%s_min", key ), "", minValue ) ) { + int maxValue; + stageDict->GetInt ( va("%s_max", key), va("%d",minValue), maxValue ); + return minValue + (maxValue - minValue) * gameLocal.random.CRandomFloat ( ); + } + + return stageDict->GetInt ( key, defaultString ); +} + +/* +================ +idDamagable::Event_BecomeBroken +================ +*/ +void idDamagable::Event_BecomeBroken( idEntity *activator ) { + BecomeBroken( activator ); + +// RAVEN BEGIN +// bdube: start stages + UpdateStage ( ); +// RAVEN END +} + +/* +================ +idDamagable::Event_RestoreDamagable +================ +*/ +void idDamagable::Event_RestoreDamagable( void ) { + health = spawnArgs.GetInt( "health", "5" ); + Show(); +} + + +/* +=============================================================================== + + idExplodable + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idExplodable ) + EVENT( EV_Activate, idExplodable::Event_Explode ) +END_CLASS + +/* +================ +idExplodable::Spawn +================ +*/ +void idExplodable::Spawn( void ) { + Hide(); +} + +/* +================ +idExplodable::Event_Explode +================ +*/ +void idExplodable::Event_Explode( idEntity *activator ) { + const char *temp; + + if ( spawnArgs.GetString( "def_damage", "damage_explosion", &temp ) ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), activator, activator, this, this, temp ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + // Show() calls UpdateVisuals, so we don't need to call it ourselves after setting the shaderParms + renderEntity.shaderParms[SHADERPARM_RED] = 1.0f; + renderEntity.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEntity.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = 0.0f; + Show(); + + PostEventMS( &EV_Remove, 2000 ); + + ActivateTargets( activator ); +} + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idSpring ) + EVENT( EV_PostSpawn, idSpring::Event_LinkSpring ) +END_CLASS + +/* +================ +idSpring::Think +================ +*/ +void idSpring::Think( void ) { + idVec3 start, end, origin; + idMat3 axis; + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + // evaluate force + spring.Evaluate( gameLocal.time ); + + start = p1; + if ( ent1 && ent1->GetPhysics() ) { + axis = ent1->GetPhysics()->GetAxis(); + origin = ent1->GetPhysics()->GetOrigin(); + start = origin + start * axis; + } + + end = p2; + if ( ent2 && ent2->GetPhysics() ) { + axis = ent2->GetPhysics()->GetAxis(); + origin = ent2->GetPhysics()->GetOrigin(); + end = origin + p2 * axis; + } + + gameRenderWorld->DebugLine( idVec4(1, 1, 0, 1), start, end, 0, true ); + } + + Present(); +} + +/* +================ +idSpring::Event_LinkSpring +================ +*/ +void idSpring::Event_LinkSpring( void ) { + idStr name1, name2; + + spawnArgs.GetString( "ent1", "", name1 ); + spawnArgs.GetString( "ent2", "", name2 ); + + if ( name1.Length() ) { + ent1 = gameLocal.FindEntity( name1 ); + if ( !ent1 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find first entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name1.c_str() ); + } + } + else { + ent1 = gameLocal.entities[ENTITYNUM_WORLD]; + } + + if ( name2.Length() ) { + ent2 = gameLocal.FindEntity( name2 ); + if ( !ent2 ) { + gameLocal.Error( "idSpring '%s' at (%s): cannot find second entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name2.c_str() ); + } + } + else { + ent2 = gameLocal.entities[ENTITYNUM_WORLD]; + } + spring.SetPosition( ent1->GetPhysics(), id1, p1, ent2->GetPhysics(), id2, p2 ); + BecomeActive( TH_THINK ); +} + +/* +================ +idSpring::Spawn +================ +*/ +void idSpring::Spawn( void ) { + float Kstretch, damping, restLength; + + spawnArgs.GetInt( "id1", "0", id1 ); + spawnArgs.GetInt( "id2", "0", id2 ); + spawnArgs.GetVector( "point1", "0 0 0", p1 ); + spawnArgs.GetVector( "point2", "0 0 0", p2 ); + spawnArgs.GetFloat( "constant", "100.0f", Kstretch ); + spawnArgs.GetFloat( "damping", "10.0f", damping ); + spawnArgs.GetFloat( "restlength", "0.0f", restLength ); + + spring.InitSpring( Kstretch, 0.0f, damping, restLength ); + + ent1 = ent2 = NULL; + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idSpring::Save +================ +*/ +void idSpring::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( id1 ); + savefile->WriteInt ( id2 ); + savefile->WriteVec3 ( p1 ); + savefile->WriteVec3 ( p2 ); + spring.Save ( savefile ); +} + +/* +================ +idSpring::Restore +================ +*/ +void idSpring::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( id1 ); + savefile->ReadInt ( id2 ); + savefile->ReadVec3 ( p1 ); + savefile->ReadVec3 ( p2 ); + spring.Restore ( savefile ); + Event_LinkSpring ( ); +} + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const idEventDef EV_Toggle( "Toggle", NULL ); + +CLASS_DECLARATION( idEntity, idForceField ) + EVENT( EV_Activate, idForceField::Event_Activate ) + EVENT( EV_Toggle, idForceField::Event_Toggle ) + EVENT( EV_FindTargets, idForceField::Event_FindTargets ) +END_CLASS + +/* +=============== +idForceField::Toggle +================ +*/ +void idForceField::Toggle( void ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idForceField::Think +================ +*/ +void idForceField::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + } + Present(); +} + +/* +================ +idForceField::Save +================ +*/ +void idForceField::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( forceField ); +} + +/* +================ +idForceField::Restore +================ +*/ +void idForceField::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( forceField ); +} + +/* +================ +idForceField::Spawn +================ +*/ +void idForceField::Spawn( void ) { + idVec3 uniform; + float explosion, implosion, randomTorque; + + if ( spawnArgs.GetVector( "uniform", "0 0 0", uniform ) ) { + forceField.Uniform( uniform ); + } else if ( spawnArgs.GetFloat( "explosion", "0", explosion ) ) { + forceField.Explosion( explosion ); + } else if ( spawnArgs.GetFloat( "implosion", "0", implosion ) ) { + forceField.Implosion( implosion ); + } + + if ( spawnArgs.GetFloat( "randomTorque", "0", randomTorque ) ) { + forceField.RandomTorque( randomTorque ); + } + + if ( spawnArgs.GetBool( "applyForce", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_FORCE ); + } else if ( spawnArgs.GetBool( "applyImpulse", "0" ) ) { + forceField.SetApplyType( FORCEFIELD_APPLY_IMPULSE ); + } else { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + } + + forceField.SetPlayerOnly( spawnArgs.GetBool( "playerOnly", "0" ) ); + forceField.SetMonsterOnly( spawnArgs.GetBool( "monsterOnly", "0" ) ); + + // set the collision model on the force field +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + forceField.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + forceField.SetOwner( this ); + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idForceField::Event_Toggle +================ +*/ +void idForceField::Event_Toggle( void ) { + Toggle(); +} + +/* +================ +idForceField::Event_Activate +================ +*/ +void idForceField::Event_Activate( idEntity *activator ) { + float wait; + + Toggle(); + if ( spawnArgs.GetFloat( "wait", "0.01", wait ) ) { + PostEventSec( &EV_Toggle, wait ); + } +} + +/* +================ +idForceField::Event_FindTargets +================ +*/ +void idForceField::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + forceField.Uniform( targets[0].GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ); + } +} + +// RAVEN BEGIN +// bdube: jump pads + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +const int JUMPPAD_EFFECT_DELAY = 100; + +CLASS_DECLARATION( idForceField, rvJumpPad ) + EVENT( EV_FindTargets, rvJumpPad::Event_FindTargets ) +END_CLASS + +/* +================ +rvJumpPad::rvJumpPad +================ +*/ +rvJumpPad::rvJumpPad ( void ) { + lastEffectTime = -1; +} + +/* +================ +rvJumpPad::Think +================ +*/ +void rvJumpPad::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // evaluate force + forceField.Evaluate( gameLocal.time ); + + // If force has been applied to an entity and jump pad effect hasnt been played for a bit + // then play it now. + if ( forceField.GetLastApplyTime ( ) - lastEffectTime > JUMPPAD_EFFECT_DELAY ) { + // start locally + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + + // send through unreliable + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_JUMPFX ); + gameLocal.SendUnreliableMessagePVS( msg, this, gameLocal.pvs.GetPVSArea( renderEntity.origin ) ); + } + + lastEffectTime = forceField.GetLastApplyTime( ); + } + } + Present(); +} + +/* +================ +rvJumpPad::Spawn +================ +*/ +void rvJumpPad::Spawn( void ) { + forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY ); + forceField.SetOwner( this ); +} + +/* +================ +rvJumpPad::Event_FindTargets +================ +*/ +void rvJumpPad::Event_FindTargets( void ) { + FindTargets(); + RemoveNullTargets(); + if ( targets.Num() ) { + idEntity* ent; + ent = targets[0].GetEntity(); + assert( ent ); + + idVec3 vert; + idVec3 diff; + idVec3 vel; + idVec3 localGravity( gameLocal.GetCurrentGravity(this) ); + idVec3 localGravityNormal( localGravity.ToNormal() ); + float time; + float dist; + + // Find the distance along the gravity vector between the jump pad and its target + diff = (ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()); + vert = (diff * localGravityNormal) * localGravityNormal; + + // Determine how long it would take to cover the distance along the gravity vector + time = idMath::Sqrt( vert.Length() / (0.5f * localGravity.Length()) ); + if ( !time ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // The final velocity is the direction between the jump pad and its target using + // the travel time to determine the forward vector and adding in an inverse gravity vector. + vel = diff - vert; + dist = vel.Normalize(); + + vel = vel * (dist / time); + vel += (localGravity * -time); + + forceField.Uniform( vel ); + + // calculate a coordinate axis where the vector to the jumppad target is forward + // use this to play the fx_jump fx + diff.Normalize(); + effectAxis = diff.ToMat3(); + } +} + +/* +=============== +rvJumpPad::ClientReceiveEvent +=============== +*/ +bool rvJumpPad::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch ( event ) { + case EVENT_JUMPFX: { + if ( spawnArgs.GetString( "fx_jump" )[ 0 ] ) { + PlayEffect( "fx_jump", renderEntity.origin, effectAxis, false, vec3_origin, false ); + } + StartSound( "snd_jump", SND_CHANNEL_ITEM, 0, false, NULL ); + return true; + } + default: + return idEntity::ClientReceiveEvent( event, time, msg ); + } +} + +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +const idEventDef EV_Animated_Start( "" ); +const idEventDef EV_LaunchMissiles( "launchMissiles", "ssssdf" ); +const idEventDef EV_LaunchMissilesUpdate( "", "dddd" ); +const idEventDef EV_AnimDone( "", "d" ); +const idEventDef EV_StartRagdoll( "startRagdoll" ); + +// RAVEN BEGIN +// bdube: script object +const idEventDef EV_SetAnimState ( "setAnimState", "sd" ); +// RAVEN END + +CLASS_DECLARATION( idAFEntity_Gibbable, idAnimated ) + EVENT( EV_Activate, idAnimated::Event_Activate ) + EVENT( EV_Animated_Start, idAnimated::Event_Start ) + EVENT( EV_StartRagdoll, idAnimated::Event_StartRagdoll ) + EVENT( EV_AnimDone, idAnimated::Event_AnimDone ) + EVENT( EV_Footstep, idAnimated::Event_Footstep ) + EVENT( EV_FootstepLeft, idAnimated::Event_Footstep ) + EVENT( EV_FootstepRight, idAnimated::Event_Footstep ) + EVENT( EV_LaunchMissiles, idAnimated::Event_LaunchMissiles ) + EVENT( EV_LaunchMissilesUpdate, idAnimated::Event_LaunchMissilesUpdate ) + +// RAVEN BEGIN +// bdube: script object + EVENT( EV_SetAnimState, idAnimated::Event_SetAnimState ) + EVENT( AI_PlayAnim, idAnimated::Event_PlayAnim ) + EVENT( AI_PlayCycle, idAnimated::Event_PlayCycle ) + EVENT( AI_AnimDone, idAnimated::Event_AnimDone2 ) +// RAVEN END + +END_CLASS + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::idAnimated() { + int i; + + anim = 0; + blendFrames = 0; + soundJoint = INVALID_JOINT; + activated = false; + combatModel = NULL; + activator = NULL; + current_anim_index = 0; + num_anims = 0; +// RAVEN BEGIN +// bdube: script control + scriptThread = NULL; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + animDoneTime[i] = 0; + } +// RAVEN END +} + +/* +=============== +idAnimated::idAnimated +================ +*/ +idAnimated::~idAnimated() { + delete combatModel; + combatModel = NULL; + +// RAVEN BEGIN +// bdube: kill script object + delete scriptThread; +// RAVEN END +} + +/* +=============== +idAnimated::Save +================ +*/ +void idAnimated::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( num_anims ); + savefile->WriteInt( current_anim_index ); + savefile->WriteInt( anim ); + savefile->WriteInt( blendFrames ); + savefile->WriteJoint( soundJoint ); + activator.Save( savefile ); + savefile->WriteBool( activated ); + +// RAVEN BEGIN + savefile->WriteObject( scriptThread ); + savefile->WriteString( state ); + savefile->WriteString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Restore +================ +*/ +void idAnimated::Restore( idRestoreGame *savefile ) { + int i; + + savefile->ReadInt( num_anims ); + savefile->ReadInt( current_anim_index ); + savefile->ReadInt( anim ); + savefile->ReadInt( blendFrames ); + savefile->ReadJoint( soundJoint ); + activator.Restore( savefile ); + savefile->ReadBool( activated ); + +// RAVEN BEGIN + savefile->ReadObject( reinterpret_cast( scriptThread ) ); + savefile->ReadString( state ); + savefile->ReadString( idealState ); + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } +// RAVEN END +} + +/* +=============== +idAnimated::Spawn +================ +*/ +void idAnimated::Spawn( void ) { + idStr animname; + int anim2; + float wait; + const char *joint; + + joint = spawnArgs.GetString( "sound_bone", "origin" ); + soundJoint = animator.GetJointHandle( joint ); + if ( soundJoint == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), joint ); + } + + LoadAF( NULL ); + + // allow bullets to collide with a combat model + if ( spawnArgs.GetBool( "combatModel", "0" ) ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + combatModel = new idClipModel( modelDefHandle ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + } + + // allow the entity to take damage + if ( spawnArgs.GetBool( "takeDamage", "0" ) ) { + fl.takedamage = true; + } + + blendFrames = 0; + +// RAVEN BEGIN +// bdube: script control + // setup script object + const char* scriptObjectName; + if ( spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + // init the script object's data + scriptObject.ClearObject(); + + // call script object's constructor + const function_t *constructor; + constructor = scriptObject.GetConstructor(); + if ( constructor ) { + // start a thread that will initialize after Spawn is done being called +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + scriptThread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + scriptThread->ManualDelete(); + scriptThread->ManualControl(); + scriptThread->SetThreadName( name.c_str() ); + scriptThread->CallFunction( this, constructor, true ); + scriptThread->Execute ( ); + } + + fl.takedamage = true; + + return; + } +// RAVEN END + + current_anim_index = 0; + spawnArgs.GetInt( "num_anims", "0", num_anims ); + + blendFrames = spawnArgs.GetInt( "blend_in" ); + + animname = spawnArgs.GetString( num_anims ? "anim1" : "anim" ); + if ( !animname.Length() ) { + anim = 0; + } else { + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + } + + if ( spawnArgs.GetBool( "hide" ) ) { + Hide(); + + if ( !num_anims ) { + blendFrames = 0; + } + } else if ( spawnArgs.GetString( "start_anim", "", animname ) ) { + anim2 = animator.GetAnim( animname ); + if ( !anim2 ) { + gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() ); + } + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, 0 ); + } else if ( anim ) { + // init joints to the first frame of the animation +// RAVEN BEGIN + frameBlend_t frameBlend = { 0, 0, 0, 1.0f, 0 }; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); +// RAVEN END + + if ( !num_anims ) { + blendFrames = 0; + } + } + + spawnArgs.GetFloat( "wait", "-1", wait ); + + if ( wait >= 0 ) { + PostEventSec( &EV_Activate, wait, this ); + } +} + +/* +=============== +idAnimated::LoadAF +=============== +*/ +bool idAnimated::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "ragdoll"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + af.SetAnimator( GetAnimator() ); + return af.Load( this, fileName ); +} + +/* +=============== +idAnimated::GetPhysicsToSoundTransform +=============== +*/ +bool idAnimated::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis ); + axis = renderEntity.axis; + return true; +} + +/* +================ +idAnimated::StartRagdoll +================ +*/ +bool idAnimated::StartRagdoll( void ) { + // if no AF loaded + if ( !af.IsLoaded() ) { + return false; + } + + // if the AF is already active + if ( af.IsActive() ) { + return true; + } + + // disable any collision model used + GetPhysics()->DisableClip(); + + // start using the AF + af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) ); + + return true; +} + +/* +===================== +idAnimated::PlayNextAnim +===================== +*/ +void idAnimated::PlayNextAnim( void ) { + const char *animname; + int len; + int cycle; + + if ( current_anim_index >= num_anims ) { + Hide(); + if ( spawnArgs.GetBool( "remove" ) ) { + PostEventMS( &EV_Remove, 0 ); + } else { + current_anim_index = 0; + } + return; + } + + Show(); + current_anim_index++; + + spawnArgs.GetString( va( "anim%d", current_anim_index ), NULL, &animname ); + if ( !animname ) { + anim = 0; + animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, FRAME2MS( blendFrames ) ); + return; + } + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing anim '%s' on %s", animname, name.c_str() ); + return; + } + + if ( g_debugCinematic.GetBool() ) { + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animname ); + } + + spawnArgs.GetInt( "cycle", "1", cycle ); + if ( ( current_anim_index == num_anims ) && spawnArgs.GetBool( "loop_last_anim" ) ) { + cycle = -1; + } + + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, current_anim_index ); + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_StartRagdoll +================ +*/ +void idAnimated::Event_StartRagdoll( void ) { + StartRagdoll(); +} + +/* +=============== +idAnimated::Event_AnimDone +================ +*/ +void idAnimated::Event_AnimDone( int animindex ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' end anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + + if ( ( animindex >= num_anims ) && spawnArgs.GetBool( "remove" ) ) { + Hide(); + PostEventMS( &EV_Remove, 0 ); + } else if ( spawnArgs.GetBool( "auto_advance" ) ) { + PlayNextAnim(); + } else { + activated = false; + } + + ActivateTargets( activator.GetEntity() ); +} + +/* +=============== +idAnimated::Event_Activate +================ +*/ +void idAnimated::Event_Activate( idEntity *_activator ) { +// RAVEN BEGIN +// bdube: script object support + if ( scriptThread ) { + CallHandler ( "onActivate" ); + return; + } +// RAVEN END + + if ( num_anims ) { + PlayNextAnim(); + activator = _activator; + return; + } + + if ( activated ) { + // already activated + return; + } + + activated = true; + activator = _activator; + ProcessEvent( &EV_Animated_Start ); +} + +/* +=============== +idAnimated::Event_Start +================ +*/ +void idAnimated::Event_Start( void ) { + int cycle; + int len; + + Show(); + + if ( num_anims ) { + PlayNextAnim(); + return; + } + + if ( anim ) { + if ( g_debugCinematic.GetBool() ) { + const idAnim *animPtr = animator.GetAnim( anim ); + gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" ); + } + spawnArgs.GetInt( "cycle", "1", cycle ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle ); + + len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength(); + if ( len >= 0 ) { + PostEventMS( &EV_AnimDone, len, 1 ); + } + } + + // offset the start time of the shader to sync it to the game time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + animator.ForceUpdate(); + UpdateAnimation(); + UpdateVisuals(); + Present(); +} + +/* +=============== +idAnimated::Event_Footstep +=============== +*/ +void idAnimated::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +===================== +idAnimated::Event_LaunchMissilesUpdate +===================== +*/ +void idAnimated::Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ) { + idVec3 launchPos; + idVec3 targetPos; + idMat3 axis; + idVec3 dir; + idEntity * ent; + idProjectile * projectile; + const idDict * projectileDef; + const char * projectilename; + + projectilename = spawnArgs.GetString( "projectilename" ); + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): 'launchMissiles' called with unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + StartSound( "snd_missile", SND_CHANNEL_WEAPON, 0, false, NULL ); + + animator.GetJointTransform( ( jointHandle_t )launchjoint, gameLocal.time, launchPos, axis ); + launchPos = renderEntity.origin + launchPos * renderEntity.axis; + +// RAVEN BEGIN +// bdube: with no target bone it will just shoot out the direction of the bones orientation + if ( targetjoint != INVALID_JOINT ) { + animator.GetJointTransform( ( jointHandle_t )targetjoint, gameLocal.time, targetPos, axis ); + targetPos = renderEntity.origin + targetPos * renderEntity.axis; + dir = targetPos - launchPos; + } else { + axis *= renderEntity.axis; + dir = axis[0]; + } +// RAVEN END + + dir.Normalize(); + + gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "idAnimated '%s' at (%s): in 'launchMissiles' call '%s' is not an idProjectile", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + } + projectile = ( idProjectile * )ent; + projectile->Create( this, launchPos, dir ); + projectile->Launch( launchPos, dir, vec3_origin ); + + if ( numshots > 0 ) { + PostEventMS( &EV_LaunchMissilesUpdate, FRAME2MS( framedelay ), launchjoint, targetjoint, numshots - 1, framedelay ); + } +} + +/* +===================== +idAnimated::Event_LaunchMissiles +===================== +*/ +void idAnimated::Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ) { + const idDict * projectileDef; + jointHandle_t launch; + jointHandle_t target; + + projectileDef = gameLocal.FindEntityDefDict( projectilename, false ); + if ( !projectileDef ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename ); + return; + } + + launch = animator.GetJointHandle( launchjoint ); + if ( launch == INVALID_JOINT ) { + gameLocal.Warning( "idAnimated '%s' at (%s): unknown launch joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), launchjoint ); + gameLocal.Error( "Unknown joint '%s'", launchjoint ); + } + + target = animator.GetJointHandle( targetjoint ); +// RAVEN BEGIN +// bdube: invalid is ok now, it means shoot out the direction of the bone +// if ( target == INVALID_JOINT ) { +// gameLocal.Warning( "idAnimated '%s' at (%s): unknown target joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), targetjoint ); +// } +// RAVEN END + + spawnArgs.Set( "projectilename", projectilename ); + spawnArgs.Set( "missilesound", sound ); + + CancelEvents( &EV_LaunchMissilesUpdate ); +// RAVEN BEGIN +// nmckenzie: Do this now. No delays. No event loops or updates. Just a straightforward DO THIS. Tacky. + if ( numshots == 1 && framedelay == 0 ){ + Event_LaunchMissilesUpdate ( launch, target, 1, 0 ); + } + else{ + ProcessEvent( &EV_LaunchMissilesUpdate, launch, target, numshots - 1, framedelay ); + } +// RAVEN END +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idAnimated::ShouldConstructScriptObjectAtSpawn +===================== +*/ +bool idAnimated::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Think ( void ) { + UpdateScript ( ); + idAFEntity_Gibbable::Think ( ); +} + +/* +===================== +idAnimated::Think +===================== +*/ +void idAnimated::Damage ( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) { + if ( !scriptThread ) { + return; + } + + CallHandler ( "onDamage" ); +} + +/* +===================== +idAnimated::Event_PlayAnim +===================== +*/ +void idAnimated::Event_PlayAnim( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + idThread::ReturnFloat( false ); + } else { + animator.PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + idThread::ReturnFloat( MS2SEC( animDoneTime[channel] - gameLocal.time ) ); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_PlayCycle +=============== +*/ +void idAnimated::Event_PlayCycle( int channel, const char *animname ) { + int anim; + + anim = animator.GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + animator.Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + animator.CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = animator.CurrentAnim( channel )->GetEndTime(); + } + blendFrames = 0; +} + +/* +=============== +idAnimated::Event_AnimDone2 +=============== +*/ +void idAnimated::Event_AnimDone2( int channel, int blend ) { + if ( animDoneTime[channel] - FRAME2MS( blend ) <= gameLocal.time ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idAnimated::Event_SetAnimState +=============== +*/ +void idAnimated::Event_SetAnimState ( const char* statename, int blend ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + idealState = statename; + blendFrames = blend; + scriptThread->DoneProcessing(); +} + +/* +================ +idAnimated::UpdateScript +================ +*/ +void idAnimated::UpdateScript( void ) { + int count; + + if ( !scriptThread || !gameLocal.isNewFrame ) { + return; + } + + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + + // If no state has been set then dont execute nothing + if ( !state.Length ( ) || scriptThread->IsWaiting() ) { + return; + } + + // update script state + count = 10; + while( ( scriptThread->Execute() || idealState.Length() ) && count-- ) { + if ( idealState.Length() ) { + SetState( idealState, blendFrames ); + } + } +} + +/* +===================== +idAnimated::CallHandler +===================== +*/ +void idAnimated::CallHandler ( const char* handler ) { + const function_t *func; + func = scriptObject.GetFunction( handler ); + if ( !func ) { + return; + } + + scriptThread->CallFunction( this, func, false ); + scriptThread->Execute ( ); +} + +/* +===================== +idAnimated::SetState +===================== +*/ +void idAnimated::SetState( const char *statename, int frames ) { + const function_t *func; + + func = scriptObject.GetFunction( statename ); + if ( !func ) { + gameLocal.Error( "Can't find function '%s' in object '%s'", statename, scriptObject.GetTypeName() ); + } + + scriptThread->CallFunction( this, func, true ); + state = statename; + blendFrames = frames; + idealState = ""; +} + +// RAVEN END + +/* +=============================================================================== + + idStaticEntity + + Some static entities may be optimized into inline geometry by dmap + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idStaticEntity ) + EVENT( EV_Activate, idStaticEntity::Event_Activate ) +END_CLASS + +/* +=============== +idStaticEntity::idStaticEntity +=============== +*/ +idStaticEntity::idStaticEntity( void ) { + spawnTime = 0; + active = false; + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + runGui = false; +} + +/* +=============== +idStaticEntity::Save +=============== +*/ +void idStaticEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnTime ); + savefile->WriteBool( active ); + savefile->WriteVec4( fadeFrom ); + savefile->WriteVec4( fadeTo ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); + savefile->WriteBool( runGui ); +} + +/* +=============== +idStaticEntity::Restore +=============== +*/ +void idStaticEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnTime ); + savefile->ReadBool( active ); + savefile->ReadVec4( fadeFrom ); + savefile->ReadVec4( fadeTo ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); + savefile->ReadBool( runGui ); +} + +/* +=============== +idStaticEntity::Spawn +=============== +*/ +void idStaticEntity::Spawn( void ) { + bool solid; + bool hidden; + bool keepContents; + +// RAVEN BEGIN +// rjohnson: inline models can be solid, since the entities hang around anyway (don't know why it was done that way) + solid = spawnArgs.GetBool( "solid" ); + + // an inline static model will not do anything at all + if ( spawnArgs.GetBool( "inline" ) || gameLocal.world->spawnArgs.GetBool( "inlineAllStatics" ) ) { + Hide(); + if ( solid ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } + return; + } +// RAVEN END + + hidden = spawnArgs.GetBool( "hide" ); + keepContents = spawnArgs.GetBool( "KeepContents" ); + + if ( keepContents ) { + // do nothing, keep the contents from the model + } else if ( solid && !hidden ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } else { + GetPhysics()->SetContents( 0 ); + } + + spawnTime = gameLocal.time; + active = false; + + idStr model = spawnArgs.GetString( "model" ); + // FIXME: temp also catch obsolete .ips extension + if ( model.Find( ".ips" ) >= 0 || model.Find( ".prt" ) >= 0 ) { + // we want the parametric particles out of sync with each other + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = gameLocal.random.RandomInt( 32767 ); + } + + fadeFrom.Set( 1, 1, 1, 1 ); + fadeTo.Set( 1, 1, 1, 1 ); + fadeStart = 0; + fadeEnd = 0; + + // NOTE: this should be used very rarely because it is expensive + runGui = spawnArgs.GetBool( "runGui" ); + if ( runGui ) { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idStaticEntity::ShowEditingDialog +================ +*/ +void idStaticEntity::ShowEditingDialog( void ) { +// RAVEN BEGIN +// bdube: not using +// common->InitTool( EDITOR_PARTICLE, &spawnArgs ); +// RAVEN END +} +/* +================ +idStaticEntity::Think +================ +*/ +void idStaticEntity::Think( void ) { + idEntity::Think(); + if ( thinkFlags & TH_THINK ) { + if ( runGui && renderEntity.gui[0] ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->objectiveSystemOpen ) { + for( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } + } + } + } + if ( fadeEnd > 0 ) { + idVec4 color; + if ( gameLocal.time < fadeEnd ) { + color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) ); + } else { + color = fadeTo; + fadeEnd = 0; + BecomeInactive( TH_THINK ); + } + SetColor( color ); + } + } +} + +/* +================ +idStaticEntity::Fade +================ +*/ +void idStaticEntity::Fade( const idVec4 &to, float fadeTime ) { + GetColor( fadeFrom ); + fadeTo = to; + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( fadeTime ); + BecomeActive( TH_THINK ); +} + +/* +================ +idStaticEntity::Hide +================ +*/ +void idStaticEntity::Hide( void ) { + idEntity::Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idStaticEntity::Show +================ +*/ +void idStaticEntity::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid" ) ) { + GetPhysics()->SetContents( CONTENTS_SOLID ); + } +} + +/* +================ +idStaticEntity::Event_Activate +================ +*/ +void idStaticEntity::Event_Activate( idEntity *activator ) { + idStr activateGui; + + spawnTime = gameLocal.time; + active = !active; + + const idKeyValue *kv = spawnArgs.FindKey( "hide" ); + if ( kv ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } + } + + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( spawnTime ); + renderEntity.shaderParms[5] = active; + // this change should be a good thing, it will automatically turn on + // lights etc.. when triggered so that does not have to be specifically done + // with trigger parms.. it MIGHT break things so need to keep an eye on it + renderEntity.shaderParms[ SHADERPARM_MODE ] = ( renderEntity.shaderParms[ SHADERPARM_MODE ] ) ? 0.0f : 1.0f; + BecomeActive( TH_UPDATEVISUALS ); +} + +/* +================ +idStaticEntity::WriteToSnapshot +================ +*/ +void idStaticEntity::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + WriteGUIToSnapshot( msg ); + msg.WriteBits( IsHidden()?1:0, 1 ); +} + +/* +================ +idStaticEntity::ReadFromSnapshot +================ +*/ +void idStaticEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) { + bool hidden; + + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + hidden = msg.ReadBits( 1 ) == 1; + if ( hidden != IsHidden() ) { + if ( hidden ) { + Hide(); + } else { + Show(); + } + } + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + + +CLASS_DECLARATION( idStaticEntity, idFuncEmitter ) +EVENT( EV_Activate, idFuncEmitter::Event_Activate ) +END_CLASS + +/* +=============== +idFuncEmitter::idFuncEmitter +=============== +*/ +idFuncEmitter::idFuncEmitter( void ) { + hidden = false; +} + +/* +=============== +idFuncEmitter::Spawn +=============== +*/ +void idFuncEmitter::Spawn( void ) { + if ( spawnArgs.GetBool( "start_off" ) ) { + hidden = true; + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( 1 ); + UpdateVisuals(); + } else { + hidden = false; + } +} + +/* +=============== +idFuncEmitter::Save +=============== +*/ +void idFuncEmitter::Save( idSaveGame *savefile ) const { + savefile->WriteBool( hidden ); +} + +/* +=============== +idFuncEmitter::Restore +=============== +*/ +void idFuncEmitter::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( hidden ); +} + +/* +================ +idFuncEmitter::Event_Activate +================ +*/ +void idFuncEmitter::Event_Activate( idEntity *activator ) { + if ( hidden || spawnArgs.GetBool( "cycleTrigger" ) ) { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0; + renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); + hidden = false; + } else { + renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + hidden = true; + } + UpdateVisuals(); +} + +/* +================ +idFuncEmitter::WriteToSnapshot +================ +*/ +void idFuncEmitter::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( hidden ? 1 : 0, 1 ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] ); + msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] ); +} + +/* +================ +idFuncEmitter::ReadFromSnapshot +================ +*/ +void idFuncEmitter::ReadFromSnapshot( const idBitMsgDelta &msg ) { + hidden = msg.ReadBits( 1 ) != 0; + renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat(); + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + + +const idEventDef EV_Splat( "" ); +CLASS_DECLARATION( idFuncEmitter, idFuncSplat ) +EVENT( EV_Activate, idFuncSplat::Event_Activate ) +EVENT( EV_Splat, idFuncSplat::Event_Splat ) +END_CLASS + +/* +=============== +idFuncSplat::idFuncSplat +=============== +*/ +idFuncSplat::idFuncSplat( void ) { +} + +/* +=============== +idFuncSplat::Spawn +=============== +*/ +void idFuncSplat::Spawn( void ) { +} + +/* +================ +idFuncSplat::Event_Splat +================ +*/ +void idFuncSplat::Event_Splat( void ) { + const char *splat = NULL; + int count = spawnArgs.GetInt( "splatCount", "1" ); + for ( int i = 0; i < count; i++ ) { + splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random ); + if ( splat && *splat ) { + float size = spawnArgs.GetFloat( "splatSize", "128" ); + float dist = spawnArgs.GetFloat( "splatDistance", "128" ); + float angle = spawnArgs.GetFloat( "splatAngle", "0" ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[2], dist, true, size, splat, angle ); + } + } + StartSound( "snd_splat", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +idFuncSplat::Event_Activate +================ +*/ +void idFuncSplat::Event_Activate( idEntity *activator ) { + idFuncEmitter::Event_Activate( activator ); + PostEventSec( &EV_Splat, spawnArgs.GetFloat( "splatDelay", "0.25" ) ); + StartSound( "snd_spurt", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +=============================================================================== + + idTextEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTextEntity ) +END_CLASS + +/* +================ +idTextEntity::Spawn +================ +*/ +void idTextEntity::Spawn( void ) { + // these are cached as the are used each frame + text = spawnArgs.GetString( "text" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + bool force = spawnArgs.GetBool( "force" ); + if ( developer.GetBool() || force ) { + BecomeActive(TH_THINK); + } +} + +/* +================ +idTextEntity::Save +================ +*/ +void idTextEntity::Save( idSaveGame *savefile ) const { + savefile->WriteString( text ); + savefile->WriteBool( playerOriented ); +} + +/* +================ +idTextEntity::Restore +================ +*/ +void idTextEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadString( text ); + savefile->ReadBool( playerOriented ); +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::Think( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +================ +idTextEntity::Think +================ +*/ +void idTextEntity::ClientPredictionThink( void ) { + if ( thinkFlags & TH_THINK ) { + gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 ); + for ( int i = 0; i < targets.Num(); i++ ) { + if ( targets[i].GetEntity() ) { + gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + + + +/* +=============================================================================== + + idVacuumSeperatorEntity + + Can be triggered to let vacuum through a portal (blown out window) + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumSeparatorEntity ) + EVENT( EV_Activate, idVacuumSeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +idVacuumSeparatorEntity::idVacuumSeparatorEntity +================ +*/ +idVacuumSeparatorEntity::idVacuumSeparatorEntity( void ) { + portal = 0; +} + +/* +================ +idVacuumSeparatorEntity::Save +================ +*/ +void idVacuumSeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +idVacuumSeparatorEntity::Restore +================ +*/ +void idVacuumSeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +idVacuumSeparatorEntity::Spawn +================ +*/ +void idVacuumSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "VacuumSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_AIR | PS_BLOCK_LOCATION ); +} + +/* +================ +idVacuumSeparatorEntity::Event_Activate +================ +*/ +void idVacuumSeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + gameLocal.SetPortalState( portal, PS_BLOCK_NONE ); +} + + +/* +=============================================================================== + +idLocationSeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationSeparatorEntity ) +END_CLASS + +/* +================ +idLocationSeparatorEntity::Spawn +================ +*/ +void idLocationSeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + qhandle_t portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "LocationSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + } + gameLocal.SetPortalState( portal, PS_BLOCK_LOCATION ); +} + + +/* +=============================================================================== + + idVacuumEntity + + Levels should only have a single vacuum entity. + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idVacuumEntity ) +END_CLASS + +/* +================ +idVacuumEntity::Spawn +================ +*/ +void idVacuumEntity::Spawn() { + if ( gameLocal.vacuumAreaNum != -1 ) { + gameLocal.Warning( "idVacuumEntity::Spawn: multiple idVacuumEntity in level" ); + return; + } + + idVec3 org = spawnArgs.GetVector( "origin" ); + + gameLocal.vacuumAreaNum = gameRenderWorld->PointInArea( org ); +} + +// RAVEN BEGIN +// abahr: +/* +=============================================================================== + + rvGravitySeparatorEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvGravitySeparatorEntity ) + EVENT( EV_Activate, rvGravitySeparatorEntity::Event_Activate ) +END_CLASS + + +/* +================ +rvGravitySeparatorEntity::idVacuumSeparatorEntity +================ +*/ +rvGravitySeparatorEntity::rvGravitySeparatorEntity( void ) { + portal = 0; +} + +/* +================ +rvGravitySeparatorEntity::Save +================ +*/ +void rvGravitySeparatorEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) ); +} + +/* +================ +rvGravitySeparatorEntity::Restore +================ +*/ +void rvGravitySeparatorEntity::Restore( idRestoreGame *savefile ) { + int state; + + savefile->ReadInt( (int &)portal ); + savefile->ReadInt( state ); + + gameLocal.SetPortalState( portal, state ); +} + +/* +================ +rvGravitySeparatorEntity::Spawn +================ +*/ +void rvGravitySeparatorEntity::Spawn() { + idBounds b; + + b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 ); + portal = gameRenderWorld->FindPortal( b ); + if ( !portal ) { + gameLocal.Warning( "GravitySeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) ); + return; + } + gameLocal.SetPortalState( portal, gameRenderWorld->GetPortalState(portal) | PS_BLOCK_GRAVITY ); +} + +/* +================ +rvGravitySeparatorEntity::Event_Activate +================ +*/ +void rvGravitySeparatorEntity::Event_Activate( idEntity *activator ) { + if ( !portal ) { + return; + } + + int portalState = gameRenderWorld->GetPortalState( portal ); + gameLocal.SetPortalState( portal, (portalState & PS_BLOCK_GRAVITY) > 0 ? (portalState & ~PS_BLOCK_GRAVITY) : portalState | PS_BLOCK_GRAVITY ); +} + +/* +=============================================================================== + + rvGravityEntity + +=============================================================================== +*/ +ABSTRACT_DECLARATION( idEntity, rvGravityArea ) +END_CLASS + +/* +================ +rvGravityArea::Spawn +================ +*/ +void rvGravityArea::Spawn() { + area = gameRenderWorld->PointInArea( spawnArgs.GetVector("origin") ); + gameLocal.AddUniqueGravityInfo( this ); +} + +/* +================ +rvGravityArea::Save +================ +*/ +void rvGravityArea::Save( idSaveGame *savefile ) const { + savefile->WriteInt( area ); +} + +/* +================ +rvGravityArea::Restore +================ +*/ +void rvGravityArea::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( area ); +} + +/* +================ +rvGravityArea::IsEqualTo +================ +*/ +bool rvGravityArea::IsEqualTo( const rvGravityArea* area ) const { + assert( area ); + return !idStr::Icmp( GetName(), area->GetName() ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea* area ) const { + return IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator== +================ +*/ +bool rvGravityArea::operator==( const rvGravityArea& area ) const { + return IsEqualTo( &area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea* area ) const { + return !IsEqualTo( area ); +} + +/* +================ +rvGravityArea::operator!= +================ +*/ +bool rvGravityArea::operator!=( const rvGravityArea& area ) const { + return !IsEqualTo( &area ); +} + +/* +=============================================================================== + + rvGravityEntity_Static + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_Static ) +END_CLASS + +/* +================ +rvGravityArea_Static::Spawn +================ +*/ +void rvGravityArea_Static::Spawn() { + gravity = spawnArgs.GetVector( "gravity" );// * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_Static::Save +================ +*/ +void rvGravityArea_Static::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( gravity ); +} + +/* +================ +rvGravityArea_Static::Restore +================ +*/ +void rvGravityArea_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( gravity ); +} + +/* +=============================================================================== + + rvGravityArea_SurfaceNormal + +=============================================================================== +*/ +CLASS_DECLARATION( rvGravityArea, rvGravityArea_SurfaceNormal ) +END_CLASS + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { + trace_t results; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if( !gameLocal.TracePoint( this, results, origin, origin + -axis[2] * 50.0f, clipMask, passEntity ) ) { +// RAVEN END + return gameLocal.GetGravity(); + } + + return -results.c.normal * gameLocal.GetGravity().Length(); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const rvClientEntity* ent ) const { + return GetGravity( ent->GetPhysics() ); +} + +/* +================ +rvGravityArea_SurfaceNormal::GetGravity +================ +*/ +const idVec3 rvGravityArea_SurfaceNormal::GetGravity( const idPhysics* physics ) const { + return GetGravity( physics->GetOrigin(), physics->GetAxis(), physics->GetClipMask(), physics->GetSelf() ); +} +// RAVEN END + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLocationEntity ) +END_CLASS + +/* +====================== +idLocationEntity::Spawn +====================== +*/ +void idLocationEntity::Spawn() { + idStr realName; + + // this just holds dict information + + // if "location" not already set, use the entity name. + if ( !spawnArgs.GetString( "location", "", realName ) ) { + spawnArgs.Set( "location", name ); + } +} + +/* +====================== +idLocationEntity::GetLocation +====================== +*/ +const char *idLocationEntity::GetLocation( void ) const { + return spawnArgs.GetString( "location" ); +} + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idBeam ) + EVENT( EV_PostSpawn, idBeam::Event_MatchTarget ) + EVENT( EV_Activate, idBeam::Event_Activate ) +END_CLASS + +/* +=============== +idBeam::idBeam +=============== +*/ +idBeam::idBeam() { + target = NULL; + master = NULL; +} + +/* +=============== +idBeam::Save +=============== +*/ +void idBeam::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + master.Save( savefile ); +} + +/* +=============== +idBeam::Restore +=============== +*/ +void idBeam::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + master.Restore( savefile ); +} + +/* +=============== +idBeam::Spawn +=============== +*/ +void idBeam::Spawn( void ) { + float width; + + if ( spawnArgs.GetFloat( "width", "0", width ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = width; + } + + SetModel( "_BEAM" ); + Hide(); + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +idBeam::Think +================ +*/ +void idBeam::Think( void ) { + idBeam *masterEnt; + + if ( !IsHidden() && !target.GetEntity() ) { + // hide if our target is removed + Hide(); + } + + RunPhysics(); + + masterEnt = master.GetEntity(); + if ( masterEnt ) { + const idVec3 &origin = GetPhysics()->GetOrigin(); + masterEnt->SetBeamTarget( origin ); + } + Present(); +} + +/* +================ +idBeam::SetMaster +================ +*/ +void idBeam::SetMaster( idBeam *masterbeam ) { + master = masterbeam; +} +/* +================ +idBeam::SetBeamTarget +================ +*/ +void idBeam::SetBeamTarget( const idVec3 &origin ) { + if ( ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] != origin.x ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] != origin.y ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] != origin.z ) ) { + renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = origin.x; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = origin.y; + renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = origin.z; + UpdateVisuals(); + } +} + +/* +================ +idBeam::Show +================ +*/ +void idBeam::Show( void ) { + idBeam *targetEnt; + + idEntity::Show(); + + targetEnt = target.GetEntity(); + if ( targetEnt ) { + const idVec3 &origin = targetEnt->GetPhysics()->GetOrigin(); + SetBeamTarget( origin ); + } +} + +/* +================ +idBeam::Event_MatchTarget +================ +*/ +void idBeam::Event_MatchTarget( void ) { + int i; + idEntity *targetEnt; + idBeam *targetBeam; + + if ( !targets.Num() ) { + return; + } + + targetBeam = NULL; + for( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); + if ( targetEnt && targetEnt->IsType( idBeam::Type ) ) { + targetBeam = static_cast( targetEnt ); + break; + } + } + + if ( !targetBeam ) { + gameLocal.Error( "Could not find valid beam target for '%s'", name.c_str() ); + } + + target = targetBeam; + targetBeam->SetMaster( this ); + if ( !spawnArgs.GetBool( "start_off" ) ) { + Show(); + } +} + +/* +================ +idBeam::Event_Activate +================ +*/ +void idBeam::Event_Activate( idEntity *activator ) { + if ( IsHidden() ) { + Show(); + } else { + Hide(); + } +} + +/* +================ +idBeam::WriteToSnapshot +================ +*/ +void idBeam::WriteToSnapshot( idBitMsgDelta &msg ) const { + GetPhysics()->WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + WriteColorToSnapshot( msg ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_X] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] ); + msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] ); +} + +/* +================ +idBeam::ReadFromSnapshot +================ +*/ +void idBeam::ReadFromSnapshot( const idBitMsgDelta &msg ) { + GetPhysics()->ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + ReadColorFromSnapshot( msg ); + renderEntity.shaderParms[SHADERPARM_BEAM_END_X] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] = msg.ReadFloat(); + renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] = msg.ReadFloat(); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idLiquid ) + EVENT( EV_Touch, idLiquid::Event_Touch ) +END_CLASS + +/* +================ +idLiquid::Save +================ +*/ +void idLiquid::Save( idSaveGame *savefile ) const { + // Nothing to save + // cnicholson: It says nothing to save here, no mention of *model +} + +/* +================ +idLiquid::Restore +================ +*/ +void idLiquid::Restore( idRestoreGame *savefile ) { + //FIXME: NO! + Spawn(); +} + +/* +================ +idLiquid::Spawn +================ +*/ +void idLiquid::Spawn() { +/* + model = dynamic_cast( renderEntity.hModel ); + if ( !model ) { + gameLocal.Error( "Entity '%s' must have liquid model", name.c_str() ); + } + model->Reset(); + GetPhysics()->SetContents( CONTENTS_TRIGGER ); +*/ +} + +/* +================ +idLiquid::Event_Touch +================ +*/ +void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) { + // FIXME: for QuakeCon +/* + idVec3 pos; + + pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f ); +*/ +} + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idShaking ) + EVENT( EV_Activate, idShaking::Event_Activate ) +END_CLASS + +/* +=============== +idShaking::idShaking +=============== +*/ +idShaking::idShaking() { + active = false; +} + + +idShaking::~idShaking() { + SetPhysics( NULL ); +} + +/* +=============== +idShaking::Save +=============== +*/ +void idShaking::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); + savefile->WriteStaticObject( physicsObj ); +} + +/* +=============== +idShaking::Restore +=============== +*/ +void idShaking::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +=============== +idShaking::Spawn +=============== +*/ +void idShaking::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + SetPhysics( &physicsObj ); + + active = false; + if ( !spawnArgs.GetBool( "start_off" ) ) { + BeginShaking(); + } +} + +/* +================ +idShaking::BeginShaking +================ +*/ +void idShaking::BeginShaking( void ) { + int phase; + idAngles shake; + int period; + + active = true; + phase = gameLocal.random.RandomInt( 1000 ); + shake = spawnArgs.GetAngles( "shake", "0.5 0.5 0.5" ); + period = spawnArgs.GetFloat( "period", "0.05" ) * 1000; + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase, period * 0.25f, GetPhysics()->GetAxis().ToAngles(), shake, ang_zero ); +} + +/* +================ +idShaking::Event_Activate +================ +*/ +void idShaking::Event_Activate( idEntity *activator ) { + if ( !active ) { + BeginShaking(); + } else { + active = false; + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, physicsObj.GetAxis().ToAngles(), ang_zero, ang_zero ); + } +} + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ +idCVar g_earthquake( "g_earthquake", "1", 0, "controls earthquake effect" ); + +CLASS_DECLARATION( idEntity, idEarthQuake ) + EVENT( EV_Activate, idEarthQuake::Event_Activate ) +END_CLASS + +/* +=============== +idEarthQuake::idEarthQuake +=============== +*/ +idEarthQuake::idEarthQuake() { + wait = 0.0f; + random = 0.0f; + nextTriggerTime = 0; + shakeStopTime = 0; + triggered = false; + playerOriented = false; + disabled = false; + shakeTime = 0.0f; +} + +/* +=============== +idEarthQuake::Save +=============== +*/ +void idEarthQuake::Save( idSaveGame *savefile ) const { + savefile->WriteInt( nextTriggerTime ); + savefile->WriteInt( shakeStopTime ); + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteBool( triggered ); + savefile->WriteBool( playerOriented ); + savefile->WriteBool( disabled ); + savefile->WriteFloat( shakeTime ); +} + +/* +=============== +idEarthQuake::Restore +=============== +*/ +void idEarthQuake::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( nextTriggerTime ); + savefile->ReadInt( shakeStopTime ); + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadBool( triggered ); + savefile->ReadBool( playerOriented ); + savefile->ReadBool( disabled ); + savefile->ReadFloat( shakeTime ); + + if ( shakeStopTime > gameLocal.time ) { + BecomeActive( TH_THINK ); + } +} + +/* +=============== +idEarthQuake::Spawn +=============== +*/ +void idEarthQuake::Spawn( void ) { + nextTriggerTime = 0; + shakeStopTime = 0; + wait = spawnArgs.GetFloat( "wait", "15" ); + random = spawnArgs.GetFloat( "random", "5" ); + triggered = spawnArgs.GetBool( "triggered" ); + playerOriented = spawnArgs.GetBool( "playerOriented" ); + disabled = false; + shakeTime = spawnArgs.GetFloat( "shakeTime", "0" ); + + if ( !triggered ){ + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "wait" ), this ); + } + BecomeInactive( TH_THINK ); +} + +/* +================ +idEarthQuake::Event_Activate +================ +*/ +void idEarthQuake::Event_Activate( idEntity *activator ) { + + if ( nextTriggerTime > gameLocal.time ) { + return; + } + + if ( disabled && activator == this ) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) { + return; + } + + nextTriggerTime = 0; + + if ( !triggered && activator != this ){ + // if we are not triggered ( i.e. random ), disable or enable + disabled ^= 1; + if (disabled) { + return; + } else { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } + } + + ActivateTargets( activator ); + + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_quake" ) ); + if ( playerOriented ) { + player->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } else { + StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off + AffectNearbyEntities(spawnArgs.GetFloat("affectEntities", "0")); +// RAVEN END + + if ( shakeTime > 0.0f ) { + shakeStopTime = gameLocal.time + SEC2MS( shakeTime ); + BecomeActive( TH_THINK ); + } + + if ( wait > 0.0f ) { + if ( !triggered ) { + PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this ); + } else { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } + } else if ( shakeTime == 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round, earthquake, etc goes off +void idEarthQuake::AffectNearbyEntities(float affectRadius) +{ + if( !g_earthquake.GetBool() ) + { + return; + } + + if (affectRadius == 0) + { + return; + } + idVec3 affectOrigin(GetPhysics()->GetOrigin()); + + if (playerOriented) + { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player == NULL ) + { + return; + } + affectOrigin = player->GetPhysics()->GetOrigin(); + } + + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities = 0; + idEntity *curEnt = NULL; + bool requiresLOS = spawnArgs.GetBool("requiresLOS", "false"); + + // get all entities touching the bounds + numListedEntities = gameLocal.EntitiesWithinRadius(affectOrigin, affectRadius, entityList, MAX_GENTITIES ); + for (int i = 0; i < numListedEntities; i++) + { + // only affect certain entities. first check if they have the "quakeChance" key/value, then send them an earthquake event + curEnt = entityList[i]; + if (curEnt && curEnt->spawnArgs.GetFloat("quakeChance")) + { + curEnt->PostEventMS(&EV_Earthquake, 0, (float)requiresLOS); + } + } +} +// RAVEN END + +/* +=============== +idEarthQuake::Think +================ +*/ +void idEarthQuake::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( gameLocal.time > shakeStopTime ) { + BecomeInactive( TH_THINK ); + if ( wait <= 0.0f ) { + PostEventMS( &EV_Remove, 0 ); + } + return; + } + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, gameLocal.GetLocalPlayer()->firstPersonViewOrigin ); + gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 256, 1500 * shakeVolume, this, this, 1.0f, true ); + } + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncPortal ) + EVENT( EV_Activate, idFuncPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncPortal::idFuncPortal +=============== +*/ +idFuncPortal::idFuncPortal() { + portal = 0; + state = false; +} + +/* +=============== +idFuncPortal::Save +=============== +*/ +void idFuncPortal::Save( idSaveGame *savefile ) const { + savefile->WriteInt( (int)portal ); + savefile->WriteBool( state ); +} + +/* +=============== +idFuncPortal::Restore +=============== +*/ +void idFuncPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)portal ); + savefile->ReadBool( state ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); +} + +/* +=============== +idFuncPortal::Spawn +=============== +*/ +void idFuncPortal::Spawn( void ) { + portal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds().Expand( 32.0f ) ); + if ( portal > 0 ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +================ +idFuncPortal::Event_Activate +================ +*/ +void idFuncPortal::Event_Activate( idEntity *activator ) { + if ( portal > 0 ) { + state = !state; + gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE ); + } +} + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASPortal ) + EVENT( EV_Activate, idFuncAASPortal::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASPortal::idFuncAASPortal +=============== +*/ +idFuncAASPortal::idFuncAASPortal() { + state = false; +} + +/* +=============== +idFuncAASPortal::Save +=============== +*/ +void idFuncAASPortal::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASPortal::Restore +=============== +*/ +void idFuncAASPortal::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============== +idFuncAASPortal::Spawn +=============== +*/ +void idFuncAASPortal::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +================ +idFuncAASPortal::Event_Activate +================ +*/ +void idFuncAASPortal::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state ); +} + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idFuncAASObstacle ) + EVENT( EV_Activate, idFuncAASObstacle::Event_Activate ) +END_CLASS + +/* +=============== +idFuncAASObstacle::idFuncAASObstacle +=============== +*/ +idFuncAASObstacle::idFuncAASObstacle() { + state = false; +} + +/* +=============== +idFuncAASObstacle::Save +=============== +*/ +void idFuncAASObstacle::Save( idSaveGame *savefile ) const { + savefile->WriteBool( state ); +} + +/* +=============== +idFuncAASObstacle::Restore +=============== +*/ +void idFuncAASObstacle::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( state ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============== +idFuncAASObstacle::Spawn +=============== +*/ +void idFuncAASObstacle::Spawn( void ) { + state = spawnArgs.GetBool( "start_on" ); + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::Event_Activate +================ +*/ +void idFuncAASObstacle::Event_Activate( idEntity *activator ) { + state ^= 1; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +================ +idFuncAASObstacle::SetState +================ +*/ +void idFuncAASObstacle::SetState ( bool _state ) { + state = _state; + gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state ); +} + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +const idEventDef EV_ResetRadioHud( "", "e" ); +idEntityPtr idFuncRadioChatter::lastRadioChatter = 0; + +CLASS_DECLARATION( idEntity, idFuncRadioChatter ) +EVENT( EV_Activate, idFuncRadioChatter::Event_Activate ) +EVENT( EV_ResetRadioHud, idFuncRadioChatter::Event_ResetRadioHud ) +EVENT( EV_IsActive, idFuncRadioChatter::Event_IsActive ) +END_CLASS + +/* +=============== +idFuncRadioChatter::idFuncRadioChatter +=============== +*/ +idFuncRadioChatter::idFuncRadioChatter() { + time = 0.0f; + isActive = false; +} + +/* +=============== +idFuncRadioChatter::Save +=============== +*/ +void idFuncRadioChatter::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( time ); + savefile->WriteBool( isActive ); // cnicholson: Added unsaved var + lastRadioChatter.Save ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Restore +=============== +*/ +void idFuncRadioChatter::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( time ); + savefile->ReadBool( isActive ); // cnicholson: Added unrestored var + lastRadioChatter.Restore ( savefile ); +} + +/* +=============== +idFuncRadioChatter::Spawn +=============== +*/ +void idFuncRadioChatter::Spawn( void ) { + time = spawnArgs.GetFloat( "time", "5.0" ); +} + +/* +================ +idFuncRadioChatter::Event_Activate +================ +*/ +void idFuncRadioChatter::Event_Activate( idEntity *activator ) { + idPlayer *player; + const char *sound; + const idSoundShader *shader; + int length; + bool isDirectedAtPlayer = false; + + lastRadioChatter = this; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( activator && activator->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + player = static_cast( activator ); + } else { + player = gameLocal.GetLocalPlayer(); + } + +// RAVEN BEGIN +// bdube: replaced named event with method + player->StartRadioChatter(); +// twhitaker: for isActive() script event + isActive = true; +// RAVEN END + + sound = spawnArgs.GetString( "snd_radiochatter", "" ); + if ( sound && *sound ) { + shader = declManager->FindSound( sound ); + if ( shader && shader->IsVO_ForPlayer()){ + isDirectedAtPlayer = true; + } + player->StartSoundShader( shader, SND_CHANNEL_RADIO, SSF_GLOBAL, false, &length ); + time = MS2SEC( length + 150 ); + } + + if ( player && player->GetHud() ) { + player->GetHud()->SetStateBool( "rhinochatter", isDirectedAtPlayer ); + } + + // we still put the hud up because this is used with no sound on + // certain frame commands when the chatter is triggered + PostEventSec( &EV_ResetRadioHud, time, player ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_ResetRadioHud( idEntity *activator ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + idPlayer *player = ( activator->IsType( idPlayer::GetClassType() ) ) ? static_cast( activator ) : gameLocal.GetLocalPlayer(); +// RAVEN END +// RAVEN BEGIN +// bdube: replaced named event with method + player->StopRadioChatter(); +// twhitaker: for isActive() script event + isActive = false; +// RAVEN END + ActivateTargets( activator ); +} + +/* +================ +idFuncRadioChatter::Event_ResetRadioHud +================ +*/ +void idFuncRadioChatter::Event_IsActive( void ) { + idThread::ReturnFloat( static_cast< float >( isActive ) ); +} + +/* +================ +idFuncRadioChatter::RepeatLast +================ +*/ +void idFuncRadioChatter::RepeatLast( void ) { + if ( lastRadioChatter ) { + lastRadioChatter->Event_Activate( gameLocal.GetLocalPlayer() ); + } +} + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPhantomObjects ) + EVENT( EV_Activate, idPhantomObjects::Event_Activate ) +END_CLASS + +/* +=============== +idPhantomObjects::idPhantomObjects +=============== +*/ +idPhantomObjects::idPhantomObjects() { + target = NULL; + end_time = 0; + throw_time = 0.0f; + shake_time = 0.0f; + shake_ang.Zero(); + speed = 0.0f; + min_wait = 0; + max_wait = 0; + fl.neverDormant = false; +} + +/* +=============== +idPhantomObjects::Save +=============== +*/ +void idPhantomObjects::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( end_time ); + savefile->WriteFloat( throw_time ); + savefile->WriteFloat( shake_time ); + savefile->WriteVec3( shake_ang ); + savefile->WriteFloat( speed ); + savefile->WriteInt( min_wait ); + savefile->WriteInt( max_wait ); + target.Save( savefile ); + savefile->WriteInt( targetTime.Num() ); + for( i = 0; i < targetTime.Num(); i++ ) { + savefile->WriteInt( targetTime[ i ] ); + } + + for( i = 0; i < lastTargetPos.Num(); i++ ) { + savefile->WriteVec3( lastTargetPos[ i ] ); + } +} + +/* +=============== +idPhantomObjects::Restore +=============== +*/ +void idPhantomObjects::Restore( idRestoreGame *savefile ) { + int num; + int i; + + savefile->ReadInt( end_time ); + savefile->ReadFloat( throw_time ); + savefile->ReadFloat( shake_time ); + savefile->ReadVec3( shake_ang ); + savefile->ReadFloat( speed ); + savefile->ReadInt( min_wait ); + savefile->ReadInt( max_wait ); + target.Restore( savefile ); + + savefile->ReadInt( num ); + targetTime.SetGranularity( 1 ); + targetTime.SetNum( num ); + lastTargetPos.SetGranularity( 1 ); + lastTargetPos.SetNum( num ); + + for( i = 0; i < num; i++ ) { + savefile->ReadInt( targetTime[ i ] ); + } + + if ( savefile->GetBuildNumber() == INITIAL_RELEASE_BUILD_NUMBER ) { + // these weren't saved out in the first release + for( i = 0; i < num; i++ ) { + lastTargetPos[ i ].Zero(); + } + } else { + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( lastTargetPos[ i ] ); + } + } +} + +/* +=============== +idPhantomObjects::Spawn +=============== +*/ +void idPhantomObjects::Spawn( void ) { + throw_time = spawnArgs.GetFloat( "time", "5" ); + speed = spawnArgs.GetFloat( "speed", "1200" ); + shake_time = spawnArgs.GetFloat( "shake_time", "1" ); + throw_time -= shake_time; + if ( throw_time < 0.0f ) { + throw_time = 0.0f; + } + min_wait = SEC2MS( spawnArgs.GetFloat( "min_wait", "1" ) ); + max_wait = SEC2MS( spawnArgs.GetFloat( "max_wait", "3" ) ); + + shake_ang = spawnArgs.GetVector( "shake_ang", "65 65 65" ); + Hide(); + GetPhysics()->SetContents( 0 ); +} + +/* +================ +idPhantomObjects::Event_Activate +================ +*/ +void idPhantomObjects::Event_Activate( idEntity *activator ) { + int i; + float time; + float frac; + float scale; + + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + return; + } + + RemoveNullTargets(); + if ( !targets.Num() ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !activator || !activator->IsType( idActor::GetClassType() ) ) { +// RAVEN END + target = gameLocal.GetLocalPlayer(); + } else { + target = static_cast( activator ); + } + + end_time = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "end_time", "0" ) ); + + targetTime.SetNum( targets.Num() ); + lastTargetPos.SetNum( targets.Num() ); + + const idVec3 &toPos = target.GetEntity()->GetEyePosition(); + + // calculate the relative times of all the objects + time = 0.0f; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = SEC2MS( time ); + lastTargetPos[ i ] = toPos; + + frac = 1.0f - ( float )i / ( float )targetTime.Num(); + time += ( gameLocal.random.RandomFloat() + 1.0f ) * 0.5f * frac + 0.1f; + } + + // scale up the times to fit within throw_time + scale = throw_time / time; + for( i = 0; i < targetTime.Num(); i++ ) { + targetTime[ i ] = gameLocal.time + SEC2MS( shake_time )+ targetTime[ i ] * scale; + } + + BecomeActive( TH_THINK ); +} + +/* +=============== +idPhantomObjects::Think +================ +*/ +void idPhantomObjects::Think( void ) { + int i; + int num; + float time; + idVec3 vel; + idVec3 ang; + idEntity *ent; + idActor *targetEnt; + idPhysics *entPhys; + trace_t tr; + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + if ( !( thinkFlags & TH_THINK ) ) { + BecomeInactive( thinkFlags & ~TH_THINK ); + return; + } + + targetEnt = target.GetEntity(); + if ( !targetEnt || ( targetEnt->health <= 0 ) || ( end_time && ( gameLocal.time > end_time ) ) || gameLocal.inCinematic ) { + BecomeInactive( TH_THINK ); + } + + const idVec3 &toPos = targetEnt->GetEyePosition(); + + num = 0; + for ( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } + + if ( ent->fl.hidden ) { + // don't throw hidden objects + continue; + } + + if ( !targetTime[ i ] ) { + // already threw this object + continue; + } + + num++; + + time = MS2SEC( targetTime[ i ] - gameLocal.time ); + if ( time > shake_time ) { + continue; + } + + entPhys = ent->GetPhysics(); + const idVec3 &entOrg = entPhys->GetOrigin(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, entOrg, toPos, MASK_OPAQUE, ent ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == targetEnt ) ) { + lastTargetPos[ i ] = toPos; + } + + if ( time < 0.0f ) { + idAI::PredictTrajectory( entPhys->GetOrigin(), lastTargetPos[ i ], speed, entPhys->GetGravity(), + entPhys->GetClipModel(), entPhys->GetClipMask(), 256.0f, ent, targetEnt, ai_debugTrajectory.GetBool() ? 1 : 0, vel ); + vel *= speed; + entPhys->SetLinearVelocity( vel ); + if ( !end_time ) { + targetTime[ i ] = 0; + } else { + targetTime[ i ] = gameLocal.time + gameLocal.random.RandomInt( max_wait - min_wait ) + min_wait; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + idMoveable *ment = static_cast( ent ); + ment->EnableDamage( true, 2.5f ); + } + } else { + // this is not the right way to set the angular velocity, but the effect is nice, so I'm keeping it. :) + ang.Set( gameLocal.random.CRandomFloat() * shake_ang.x, gameLocal.random.CRandomFloat() * shake_ang.y, gameLocal.random.CRandomFloat() * shake_ang.z ); + ang *= ( 1.0f - time / shake_time ); + entPhys->SetAngularVelocity( ang ); + } + } + + if ( !num ) { + BecomeInactive( TH_THINK ); + } +} + +// RAVEN BEGIN +// bdube: jump points + +/* +=============================================================================== + +rvDebugJumpPoint + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvDebugJumpPoint ) +END_CLASS + +/* +===================== +rvDebugJumpPoint::Spawn +===================== +*/ +void rvDebugJumpPoint::Spawn() { + // Add the jump point + gameDebug.JumpAdd ( name, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles ( ) ); + + // Dont need the entity any more + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvFuncSaveGame ) + EVENT( EV_Activate, rvFuncSaveGame::Event_Activate ) +END_CLASS + +void rvFuncSaveGame::Spawn( void ) { + +} + +void rvFuncSaveGame::Event_Activate( idEntity *activator ) { + // TODO: Xenon - request the player to save the game. + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "saveGame checkPoint" ); +} diff --git a/src/mpgame/Misc.h b/src/mpgame/Misc.h new file mode 100644 index 0000000..b754ee5 --- /dev/null +++ b/src/mpgame/Misc.h @@ -0,0 +1,997 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_MISC_H__ +#define __GAME_MISC_H__ + +/* +=============================================================================== + +idSpawnableEntity + +A simple, spawnable entity with a model and no functionable ability of it's own. +For example, it can be used as a placeholder during development, for marking +locations on maps for script, or for simple placed models without any behavior +that can be bound to other entities. Should not be subclassed. +=============================================================================== +*/ + +class idSpawnableEntity : public idEntity { +public: + CLASS_PROTOTYPE( idSpawnableEntity ); + + void Spawn( void ); + +private: +}; + +/* +=============================================================================== + + Potential spawning position for players. + The first time a player enters the game, they will be at an 'initial' spot. + Targets will be fired when someone spawns in on them. + + When triggered, will cause player to be teleported to spawn spot. + +=============================================================================== +*/ + +class idPlayerStart : public idEntity { +public: + CLASS_PROTOTYPE( idPlayerStart ); + + enum { + EVENT_TELEPORTPLAYER = idEntity::EVENT_MAXEVENTS, + EVENT_TELEPORTITEM, + EVENT_MAXEVENTS + }; + + idPlayerStart( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + +private: + int teleportStage; + + void Event_TeleportEntity( idEntity *activator, bool predicted, idVec3& prevOrigin = vec3_origin ); + void Event_Teleport( idEntity *activator ); + void Teleport( idEntity* other ); + void Event_TeleportStage( idPlayer *player ); + void Event_ResetCamera( idPlayer *player ); + void TeleportPlayer( idPlayer *player ); +}; + + +/* +=============================================================================== + + Non-displayed entity used to activate triggers when it touches them. + Bind to a mover to have the mover activate a trigger as it moves. + When target by triggers, activating the trigger will toggle the + activator on and off. Check "start_off" to have it spawn disabled. + +=============================================================================== +*/ + +class idActivator : public idEntity { +public: + CLASS_PROTOTYPE( idActivator ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + bool stay_on; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + Path entities for monsters to follow. + +=============================================================================== +*/ +class idPathCorner : public idEntity { +public: + CLASS_PROTOTYPE( idPathCorner ); + + void Spawn( void ); + + static void DrawDebugInfo( void ); + + static idPathCorner *RandomPath( const idEntity *source, const idEntity *ignore ); + +private: + void Event_RandomPath( void ); +}; + +// RAVEN BEGIN +// bdube: jump points +/* +=============================================================================== + + Debug Jump Point + +=============================================================================== +*/ + +class rvDebugJumpPoint : public idEntity { +public: + + CLASS_PROTOTYPE( rvDebugJumpPoint ); + + void Spawn(); +}; + +/* +=============================================================================== + + Object that fires targets and changes shader parms when damaged. + +=============================================================================== +*/ + +class idDamagable : public idEntity { +public: + CLASS_PROTOTYPE( idDamagable ); + + idDamagable( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +// RAVEN BEGIN +// abahr: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + int invincibleTime; +// RAVEN BEGIN +// abahr: changed to protected +protected: + int stage; + int stageNext; + const idDict* stageDict; + int stageEndTime; + int stageEndHealth; + int stageEndSpeed; +//jshepard: used to end a stage if a moveable is on the ground (for falling objects) + bool stageEndOnGround; +//jshepard: we want to activate certain objects when triggered-- falling blocks yes, barrels no. + bool activateStageOnTrigger; + + virtual void ExecuteStage ( void ); + void UpdateStage ( void ); + idVec3 GetStageVector ( const char* key, const char* defaultString = "" ) const; + float GetStageFloat ( const char* key, const char* defaultString = "" ) const; + int GetStageInt ( const char* key, const char* defaultString = "" ) const; +// RAVEN END + + int count; + int nextTriggerTime; + + void BecomeBroken( idEntity *activator ); + void Event_BecomeBroken( idEntity *activator ); + void Event_RestoreDamagable( void ); +}; + +/* +=============================================================================== + + Hidden object that explodes when activated + +=============================================================================== +*/ + +class idExplodable : public idEntity { +public: + CLASS_PROTOTYPE( idExplodable ); + + void Spawn( void ); + +private: + void Event_Explode( idEntity *activator ); +}; + + +/* +=============================================================================== + + idSpring + +=============================================================================== +*/ + +class idSpring : public idEntity { +public: + CLASS_PROTOTYPE( idSpring ); + + void Spawn( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + idEntityPtr ent1; + idEntityPtr ent2; + int id1; + int id2; + idVec3 p1; + idVec3 p2; + idForce_Spring spring; + + void Event_LinkSpring( void ); +}; + + +/* +=============================================================================== + + idForceField + +=============================================================================== +*/ + +class idForceField : public idEntity { +public: + CLASS_PROTOTYPE( idForceField ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: idDamagable may want to change some things on the fly + void SetExplosion(float force) { forceField.Explosion(force); } +// RAVEN END + + +// RAVEN BEGIN +// bdube: made force field protected +protected: + + idForce_Field forceField; + +private: +// RAVEN END + void Toggle( void ); + + void Event_Activate( idEntity *activator ); + void Event_Toggle( void ); + void Event_FindTargets( void ); +}; + +// RAVEN BEGIN +// bdube: jump pads +/* +=============================================================================== + + rvJumpPad + +=============================================================================== +*/ + +class rvJumpPad : public idForceField { +public: + CLASS_PROTOTYPE( rvJumpPad ); + + rvJumpPad ( void ); + + void Spawn( void ); + void Think( void ); + +private: + + int lastEffectTime; + + void Event_FindTargets( void ); + + enum { + EVENT_JUMPFX = idEntity::EVENT_MAXEVENTS, + EVENT_MAXEVENTS + }; + bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + idMat3 effectAxis; +}; +// RAVEN END + +/* +=============================================================================== + + idAnimated + +=============================================================================== +*/ + +class idAnimated : public idAFEntity_Gibbable { +public: + CLASS_PROTOTYPE( idAnimated ); + + idAnimated(); + ~idAnimated(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + virtual bool LoadAF( const char* keyname ); + bool StartRagdoll( void ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + +// RAVEN BEGIN +// bdube: script + void Think ( void ); + + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; +// RAVEN END + +private: + int num_anims; + int current_anim_index; + int anim; + int blendFrames; + jointHandle_t soundJoint; + idEntityPtr activator; + bool activated; + +// RAVEN BEGIN +// bdube: script variables + // script control + idThread * scriptThread; + idStr state; + idStr idealState; + int animDoneTime[ANIM_NumAnimChannels]; + + // Script state management + void UpdateScript( void ); + void SetState( const char *statename, int blend ); + void CallHandler ( const char* handler ); +// RAVEN END + + void PlayNextAnim( void ); + + void Event_Activate( idEntity *activator ); + void Event_Start( void ); + void Event_StartRagdoll( void ); + void Event_AnimDone( int animIndex ); + void Event_Footstep( void ); + void Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ); + void Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetAnimState( const char* state, int blendframes ); + void Event_PlayAnim( int channel, const char *animname ); + void Event_PlayCycle( int channel, const char *animname ); + void Event_AnimDone2( int channel, int blendFrames ); +// RAVEN END +}; + +/* +=============================================================================== + + idStaticEntity + +=============================================================================== +*/ + +class idStaticEntity : public idEntity { +public: + CLASS_PROTOTYPE( idStaticEntity ); + + idStaticEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void ShowEditingDialog( void ); + virtual void Hide( void ); + virtual void Show( void ); + void Fade( const idVec4 &to, float fadeTime ); + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_Activate( idEntity *activator ); + + int spawnTime; + bool active; + idVec4 fadeFrom; + idVec4 fadeTo; + int fadeStart; + int fadeEnd; + bool runGui; +}; + + +/* +=============================================================================== + +idFuncEmitter + +=============================================================================== +*/ + +class idFuncEmitter : public idStaticEntity { +public: + CLASS_PROTOTYPE( idFuncEmitter ); + + idFuncEmitter( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + void Event_Activate( idEntity *activator ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + bool hidden; + +}; + + +// RAVEN BEGIN +// bdube: not using +#if 0 +// RAVEN END + +/* +=============================================================================== + +idFuncSmoke + +=============================================================================== +*/ + +class idFuncSmoke : public idEntity { +public: + CLASS_PROTOTYPE( idFuncSmoke ); + + idFuncSmoke(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + void Event_Activate( idEntity *activator ); + +private: + int smokeTime; + const idDeclParticle * smoke; + bool restart; +}; + +// RAVEN BEGIN +// bdube: not using +#endif +// RAVEN END + +/* +=============================================================================== + +idFuncSplat + +=============================================================================== +*/ + +class idFuncSplat : public idFuncEmitter { +public: + CLASS_PROTOTYPE( idFuncSplat ); + + idFuncSplat( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Splat(); +}; + + +/* +=============================================================================== + +idTextEntity + +=============================================================================== +*/ + +class idTextEntity : public idEntity { +public: + CLASS_PROTOTYPE( idTextEntity ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void ClientPredictionThink( void ); + +private: + idStr text; + bool playerOriented; +}; + + +/* +=============================================================================== + +idLocationEntity + +=============================================================================== +*/ + +class idLocationEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationEntity ); + + void Spawn( void ); + + const char * GetLocation( void ) const; + +private: +}; + +class idLocationSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idLocationSeparatorEntity ); + + void Spawn( void ); + +private: +}; + +class idVacuumSeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumSeparatorEntity ); + + idVacuumSeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class idVacuumEntity : public idEntity { +public: + CLASS_PROTOTYPE( idVacuumEntity ); + + void Spawn( void ); + +private: +}; + +// RAVEN BEGIN +// abahr +class rvGravitySeparatorEntity : public idEntity { +public: + CLASS_PROTOTYPE( rvGravitySeparatorEntity ); + + rvGravitySeparatorEntity( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Event_Activate( idEntity *activator ); + +private: + qhandle_t portal; +}; + +class rvGravityArea : public idEntity { +public: + ABSTRACT_PROTOTYPE( rvGravityArea ); + + void Spawn( void ); + + virtual int GetArea() const { return area; } + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const = 0; + virtual const idVec3 GetGravity( const idEntity* ent ) const = 0; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const = 0; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + bool IsEqualTo( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea* area ) const; + bool operator==( const rvGravityArea& area ) const; + bool operator!=( const rvGravityArea* area ) const; + bool operator!=( const rvGravityArea& area ) const; + +protected: + int area; +}; + +class rvGravityArea_Static : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_Static ); + + void Spawn( void ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const { return gravity; } + virtual const idVec3 GetGravity( const idEntity* ent ) const { return gravity; } + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const { return gravity; } + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + idVec3 gravity; +}; + +class rvGravityArea_SurfaceNormal : public rvGravityArea { +public: + CLASS_PROTOTYPE( rvGravityArea_SurfaceNormal ); + + virtual const idVec3 GetGravity( const idVec3& origin, const idMat3& axis, int clipMask, idEntity* passEntity ) const; + virtual const idVec3 GetGravity( const idEntity* ent ) const; + virtual const idVec3 GetGravity( const rvClientEntity* ent ) const; + +protected: + virtual const idVec3 GetGravity( const idPhysics* physics ) const; +}; +// RAVEN END + +/* +=============================================================================== + + idBeam + +=============================================================================== +*/ + +class idBeam : public idEntity { +public: + CLASS_PROTOTYPE( idBeam ); + + idBeam(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + void SetMaster( idBeam *masterbeam ); + void SetBeamTarget( const idVec3 &origin ); + + virtual void Show( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + void Event_MatchTarget( void ); + void Event_Activate( idEntity *activator ); + + idEntityPtr target; + idEntityPtr master; +}; + + +/* +=============================================================================== + + idLiquid + +=============================================================================== +*/ + +class idRenderModelLiquid; + +class idLiquid : public idEntity { +public: + CLASS_PROTOTYPE( idLiquid ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + void Event_Touch( idEntity *other, trace_t *trace ); + + + idRenderModelLiquid *model; +}; + + +/* +=============================================================================== + + idShaking + +=============================================================================== +*/ + +class idShaking : public idEntity { +public: + CLASS_PROTOTYPE( idShaking ); + + idShaking(); + ~idShaking(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idPhysics_Parametric physicsObj; + bool active; + + void BeginShaking( void ); + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idEarthQuake + +=============================================================================== +*/ + +class idEarthQuake : public idEntity { +public: + CLASS_PROTOTYPE( idEarthQuake ); + + idEarthQuake(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +// RAVEN BEGIN +// kfuller: look for fx entities and the like that may want to be triggered when a mortar round (aka earthquake) goes off +protected: + void AffectNearbyEntities(float affectRadius); +// RAVEN END + +private: + int nextTriggerTime; + int shakeStopTime; + float wait; + float random; + bool triggered; + bool playerOriented; + bool disabled; + float shakeTime; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + + idFuncPortal + +=============================================================================== +*/ + +class idFuncPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncPortal ); + + idFuncPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + qhandle_t portal; + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASPortal + +=============================================================================== +*/ + +class idFuncAASPortal : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASPortal ); + + idFuncAASPortal(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + + idFuncAASObstacle + +=============================================================================== +*/ + +class idFuncAASObstacle : public idEntity { +public: + CLASS_PROTOTYPE( idFuncAASObstacle ); + + idFuncAASObstacle(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetState ( bool _state ); + +private: + bool state; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idFuncRadioChatter + +=============================================================================== +*/ + +class idFuncRadioChatter : public idEntity { +public: + CLASS_PROTOTYPE( idFuncRadioChatter ); + + idFuncRadioChatter(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + static void RepeatLast ( void ); + +private: + static idEntityPtr lastRadioChatter; + float time; + bool isActive; + void Event_Activate( idEntity *activator ); + void Event_ResetRadioHud( idEntity *activator ); + void Event_IsActive( void ); +}; + + +/* +=============================================================================== + + idPhantomObjects + +=============================================================================== +*/ + +class idPhantomObjects : public idEntity { +public: + CLASS_PROTOTYPE( idPhantomObjects ); + + idPhantomObjects(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_Throw( void ); + void Event_ShakeObject( idEntity *object, int starttime ); + + int end_time; + float throw_time; + float shake_time; + idVec3 shake_ang; + float speed; + int min_wait; + int max_wait; + idEntityPtrtarget; + idList targetTime; + idList lastTargetPos; +}; + + +/* +=============================================================================== + +rvFuncSaveGame + +=============================================================================== +*/ + +class rvFuncSaveGame : public idEntity { +public: + CLASS_PROTOTYPE( rvFuncSaveGame ); + + void Spawn( void ); + + void Event_Activate ( idEntity *activator ); + +private: +}; + + +#endif /* !__GAME_MISC_H__ */ diff --git a/src/mpgame/Moveable.cpp b/src/mpgame/Moveable.cpp new file mode 100644 index 0000000..954e799 --- /dev/null +++ b/src/mpgame/Moveable.cpp @@ -0,0 +1,1371 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if !defined(__GAME_PROJECTILE_H__) + #include "Projectile.h" +#endif + +/* +=============================================================================== + + idMoveable + +=============================================================================== +*/ + +const idEventDef EV_BecomeNonSolid( "becomeNonSolid" ); +const idEventDef EV_SetOwnerFromSpawnArgs( "" ); +const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' ); +const idEventDef EV_CanDamage( "canDamage", "f" ); +const idEventDef EV_SetHealth( "setHealth", "f" ); +const idEventDef EV_RadiusDamage( "", "es" ); + +CLASS_DECLARATION( idDamagable, idMoveable ) + EVENT( EV_Activate, idMoveable::Event_Activate ) + EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid ) + EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs ) + EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest ) + EVENT( EV_CanDamage, idMoveable::Event_CanDamage ) + EVENT( EV_SetHealth, idMoveable::Event_SetHealth ) + EVENT( EV_RadiusDamage, idMoveable::Event_RadiusDamage ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; +static const int BOUNCE_SOUND_DELAY_MIN = 500.0f; +static const int BOUNCE_SOUND_DELAY_MAX = 1200.0f; + +/* +================ +idMoveable::idMoveable +================ +*/ +idMoveable::idMoveable( void ) { + minDamageVelocity = 100.0f; + maxDamageVelocity = 200.0f; + nextCollideFxTime = 0; + initialSpline = NULL; + initialSplineDir = vec3_zero; + unbindOnDeath = false; + allowStep = false; + canDamage = false; + + lastAttacker = NULL; +} + +/* +================ +idMoveable::~idMoveable +================ +*/ +idMoveable::~idMoveable( void ) { + delete initialSpline; + initialSpline = NULL; + SetPhysics( NULL ); +} + +/* +================ +idMoveable::Spawn +================ +*/ +void idMoveable::Spawn( void ) { + idTraceModel trm; + float density, friction, bouncyness; + int clipShrink; + idStr clipModelName; + bool setClipModel = false; + idBounds bounds; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName[0] ) { + idVec3 size; + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on moveable '%s'", bounds[0].ToString(), bounds[1].ToString(), name.c_str() ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on moveable '%s'", size.ToString(), name.c_str() ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + } + + if ( setClipModel ) { + trm.SetupBox( bounds ); + } else { + if ( !clipModelName[0] ) { + clipModelName = spawnArgs.GetString ( "model" ); // use the visual model + } + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + } + + // if the model should be shrinked + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); + spawnArgs.GetFloat( "friction", "0.05", friction ); + friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); + spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); + bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + unbindOnDeath = spawnArgs.GetBool( "unbindondeath" ); + + nextCollideFxTime = 0; + + damage = spawnArgs.GetString( "def_damage", "" ); + canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true; + health = spawnArgs.GetInt( "health", "0" ); + spawnArgs.GetString( "broken", "", brokenModel ); + + if ( health ) { + if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) { + gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), brokenModel.c_str() ); + } + } + + fl.takedamage = (health > 0 ); + + // setup the physics + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM( this ); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( trm, GetRenderModelMaterial() ), density ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( bouncyness ); + physicsObj.SetFriction( 0.6f, 0.6f, friction ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "nodrop" ) ) { + physicsObj.PutToRest(); + } else { + physicsObj.DropToFloor(); + } + + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + physicsObj.DisableImpact(); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + BecomeNonSolid(); + } + + allowStep = spawnArgs.GetBool( "allowStep", "1" ); + +// RAVEN BEGIN +// cdr: Obstacle Avoidance + fl.isAIObstacle = !physicsObj.IsPushable(); +// RAVEN END + + PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 ); +} + +/* +================ +idMoveable::Save +================ +*/ +void idMoveable::Save( idSaveGame *savefile ) const { + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteString( brokenModel ); + savefile->WriteString( damage ); + savefile->WriteInt( nextCollideFxTime ); + savefile->WriteFloat( minDamageVelocity ); + savefile->WriteFloat( maxDamageVelocity ); + + savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 ); + savefile->WriteVec3( initialSplineDir ); + + savefile->WriteBool( unbindOnDeath ); + savefile->WriteBool( allowStep ); + savefile->WriteBool( canDamage ); + + lastAttacker.Save(savefile); // cnicholson: Added unsaved var +} + +/* +================ +idMoveable::Restore +================ +*/ +void idMoveable::Restore( idRestoreGame *savefile ) { + int initialSplineTime; + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadString( brokenModel ); + savefile->ReadString( damage ); + savefile->ReadInt( nextCollideFxTime ); + savefile->ReadFloat( minDamageVelocity ); + savefile->ReadFloat( maxDamageVelocity ); + + savefile->ReadInt( initialSplineTime ); + if ( initialSplineTime != -1 ) { + InitInitialSpline( initialSplineTime ); + } else { + initialSpline = NULL; + } + + savefile->ReadVec3( initialSplineDir ); + savefile->ReadBool( unbindOnDeath ); + savefile->ReadBool( allowStep ); + savefile->ReadBool( canDamage ); // cnicholson: Added unrestored var + + lastAttacker.Restore(savefile); // cnicholson: Added unrestored var + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMoveable::Hide +================ +*/ +void idMoveable::Hide( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMoveable::Show +================ +*/ +void idMoveable::Show( void ) { +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Show(); + if ( !spawnArgs.GetBool( "nonsolid" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } +} + +/* +================= +idMoveable::Collide +================= +*/ +bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { + float len, f; + idVec3 dir; + idEntity *ent; + + dir = velocity; + len = dir.NormalizeFast(); + + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( gameLocal.time > nextCollideFxTime ) { + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_collide",collision.c.materialType), collision.c.point, collision.c.normal.ToMat3() ); +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( len <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } +// RAVEN END + SetSoundVolume( f ); + StartSound( "snd_bounce", SND_CHANNEL_BODY, 0, false, NULL ); + + nextCollideFxTime = gameLocal.time + BOUNCE_SOUND_DELAY_MIN + gameLocal.random.RandomInt(BOUNCE_SOUND_DELAY_MAX - BOUNCE_SOUND_DELAY_MIN); + } + } + + if ( canDamage && damage.Length() ) { + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent && len > minDamageVelocity ) { +// RAVEN BEGIN +// jscott: fixed negative sqrt call + if( len > maxDamageVelocity ) { + f = 1.0f; + } else if( len <= minDamageVelocity ) { + f = 0.0f; + } else { + f = idMath::Sqrt( len - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) ); + } +// RAVEN END + ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT ); + } + } + + return false; +} + +/* +============ +idMoveable::Damage +============ +*/ +void idMoveable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + idDamagable::Damage ( inflictor, attacker, dir, damageDefName, damageScale, location ); + + // Cache the attacker to ensure credit for a kill is given to the entity that caused the damage + lastAttacker = inflictor; +// jshepard: +// gameLocal.Warning("idMoveable Damaged! Health is %d", health); + +} + +/* +============ +idMoveable::Killed +============ +*/ +void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + +// jshepard: I am dead! +// gameLocal.Warning("idMoveable Killed! Health is %d", health); + +/* + // No more taking damage + fl.takedamage = false; + + // Should the moveable launch around when killed? + int launchTime; + launchTime = SEC2MS ( spawnArgs.GetFloat ( "launch_time", "0" ); + if ( launchTime > 0 ) { + launchOffset = spawnArgs.GetVector ( "launch_offset" ); + launchDir = spawnArgs.GetVector ( " + } + + spawnArgs.GetFloat ( "explode_lapse", + PostEventSec ( &EV_Remove, explodeLapse ); + + // Two part explosion? + explode_impulse + explode_lapse + + if ( unbindOnDeath ) { + Unbind(); + } + + if ( brokenModel != "" ) { + SetModel( brokenModel ); + } + + if ( explode ) { + if ( brokenModel == "" ) { + Hide(); + physicsObj.PutToRest(); + GetPhysics()->SetContents( 0 ); + PostEventMS( &EV_Remove, 0 ); + } + + const char *splash = spawnArgs.GetString( "def_splash_damage", "" ); + if ( splash && *splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), inflictor, inflictor, this, this, splash ); + } + + StartSound( "snd_explode", SND_CHANNEL_ANY ); + + StopAllEffects ( ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets( this ); + + fl.takedamage = false; +*/ +} + + +/* +================ +idMoveable::ExecuteStage +================ +*/ +void idMoveable::ExecuteStage ( void ) { + // Splash damage? + const char *splash; + if ( stageDict->GetString( "def_splash_damage", "", &splash ) && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, lastAttacker.GetEntity()?lastAttacker.GetEntity():this, splash ); + } + + idDamagable::ExecuteStage(); +} + +/* +================ +idMoveable::AddDamageEffect +================ +*/ +void idMoveable::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + + // Play an impact effect during this stage? + if ( stageDict ) { + PlayEffect ( gameLocal.GetEffect ( *stageDict, "fx_impact" ), + collision.c.point, collision.c.normal.ToMat3(), + true, vec3_origin, true ); + } +} + +/* +================ +idMoveable::AllowStep +================ +*/ +bool idMoveable::AllowStep( void ) const { + return allowStep; +} + +/* +================ +idMoveable::BecomeNonSolid +================ +*/ +void idMoveable::BecomeNonSolid( void ) { + // set CONTENTS_RENDERMODEL so bullets still collide with the moveable + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); +} + +/* +================ +idMoveable::EnableDamage +================ +*/ +void idMoveable::EnableDamage( bool enable, float duration ) { + canDamage = enable; + if ( duration ) { + PostEventSec( &EV_CanDamage, duration, ( !enable ) ? 0.0f : 1.0f ); + } +} + +/* +================ +idMoveable::InitInitialSpline +================ +*/ +void idMoveable::InitInitialSpline( int startTime ) { + int initialSplineTime; + + initialSpline = GetSpline(); + initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" ); + + if ( initialSpline != NULL ) { + initialSpline->MakeUniform( initialSplineTime ); + initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) ); + initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime ); + initialSplineDir *= physicsObj.GetAxis().Transpose(); + initialSplineDir.Normalize(); + BecomeActive( TH_THINK ); + } +} + +/* +================ +idMoveable::FollowInitialSplinePath +================ +*/ +bool idMoveable::FollowInitialSplinePath( void ) { + if ( initialSpline != NULL ) { + if ( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) ) { + idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time ); + idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * gameLocal.GetMHz(); + physicsObj.SetLinearVelocity( linearVelocity ); + + idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time ); + idVec3 dir = initialSplineDir * physicsObj.GetAxis(); + idVec3 angularVelocity = dir.Cross( splineDir ); + angularVelocity.Normalize(); + angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * gameLocal.GetMHz(); + physicsObj.SetAngularVelocity( angularVelocity ); + return true; + } else { + delete initialSpline; + initialSpline = NULL; + } + } + return false; +} + +/* +================ +idMoveable::Think +================ +*/ +void idMoveable::Think( void ) { + if ( thinkFlags & TH_THINK ) { + // Move to the next stage? + UpdateStage ( ); + + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } +// RAVEN BEGIN +// abahr: changed parent scope + idDamagable::Think(); +} + +/* +================ +idMoveable::GetRenderModelMaterial +================ +*/ +const idMaterial *idMoveable::GetRenderModelMaterial( void ) const { + if ( renderEntity.customShader ) { + return renderEntity.customShader; + } + if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) { + return renderEntity.hModel->Surface( 0 )->shader; + } + return NULL; +} + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idMoveable::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idMoveable::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idMoveable::Event_BecomeNonSolid +================ +*/ +void idMoveable::Event_BecomeNonSolid( void ) { + BecomeNonSolid(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idMoveable::Event_Activate( idEntity *activator ) { + float delay; + idVec3 init_velocity, init_avelocity; + + Show(); + + if ( !spawnArgs.GetInt( "notPushable" ) ) { + physicsObj.EnableImpact(); + } + + physicsObj.Activate(); + + spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity ); + spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity ); + + delay = spawnArgs.GetFloat( "init_velocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetLinearVelocity( init_velocity ); + } else { + PostEventSec( &EV_SetLinearVelocity, delay, init_velocity ); + } + + delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" ); + if ( delay == 0.0f ) { + physicsObj.SetAngularVelocity( init_avelocity ); + } else { + PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity ); + } + + InitInitialSpline( gameLocal.time ); + +// RAVEN BEGIN +// jshepard: we should update it's stage on activation, specifically for falling blocks. + UpdateStage(); +// RAVEN END + +} + +/* +================ +idMoveable::Event_SetOwnerFromSpawnArgs +================ +*/ +void idMoveable::Event_SetOwnerFromSpawnArgs( void ) { + idStr owner; + + if ( spawnArgs.GetString( "owner", "", owner ) ) { + ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) ); + } +} + +/* +================ +idMoveable::Event_IsAtRest +================ +*/ +void idMoveable::Event_IsAtRest( void ) { + idThread::ReturnInt( physicsObj.IsAtRest() ); +} + +/* +================ +idMoveable::Event_CanDamage +================ +*/ +void idMoveable::Event_CanDamage( float enable ) { + canDamage = ( enable != 0.0f ); +} + +/* +================ +idMoveable::Event_SetHealth +================ +*/ +void idMoveable::Event_SetHealth( float newHealth ) { + health = newHealth; +} + +/* +================ +idMoveable::Event_RadiusDamage +================ +*/ +void idMoveable::Event_RadiusDamage( idEntity *attacker, const char* splash ) { + gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash ); +} + +/* +=============================================================================== + + idBarrel + +=============================================================================== +*/ + +CLASS_DECLARATION( idMoveable, idBarrel ) +END_CLASS + +/* +================ +idBarrel::idBarrel +================ +*/ +idBarrel::idBarrel() { + radius = 1.0f; + barrelAxis = 0; + lastOrigin.Zero(); + lastAxis.Identity(); + additionalRotation = 0.0f; + additionalAxis.Identity(); + fl.networkSync = true; +} + +/* +================ +idBarrel::Save +================ +*/ +void idBarrel::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( radius ); + savefile->WriteInt( barrelAxis ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteMat3( lastAxis ); + savefile->WriteFloat( additionalRotation ); + savefile->WriteMat3( additionalAxis ); +} + +/* +================ +idBarrel::Restore +================ +*/ +void idBarrel::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( radius ); + savefile->ReadInt( barrelAxis ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadMat3( lastAxis ); + savefile->ReadFloat( additionalRotation ); + savefile->ReadMat3( additionalAxis ); +} + +/* +================ +idBarrel::BarrelThink +================ +*/ +void idBarrel::BarrelThink( void ) { + bool wasAtRest, onGround; + float movedDistance, rotatedDistance, angle; + idVec3 curOrigin, gravityNormal, dir; + idMat3 curAxis, axis; + + wasAtRest = IsAtRest(); + + // Progress to the next stage? + UpdateStage ( ); + + // run physics + RunPhysics(); + + // only need to give the visual model an additional rotation if the physics were run + if ( !wasAtRest ) { + + // current physics state + onGround = GetPhysics()->HasGroundContacts(); + curOrigin = GetPhysics()->GetOrigin(); + curAxis = GetPhysics()->GetAxis(); + + // if the barrel is on the ground + if ( onGround ) { + gravityNormal = GetPhysics()->GetGravityNormal(); + + dir = curOrigin - lastOrigin; + dir -= gravityNormal * dir * gravityNormal; + movedDistance = dir.LengthSqr(); + + // if the barrel moved and the barrel is not aligned with the gravity direction + if ( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f ) { + + // barrel movement since last think frame orthogonal to the barrel axis + movedDistance = idMath::Sqrt( movedDistance ); + dir *= 1.0f / movedDistance; + movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance; + + // get rotation about barrel axis since last think frame + angle = lastAxis[(barrelAxis+1)%3] * curAxis[(barrelAxis+1)%3]; + angle = idMath::ACos( angle ); + // distance along cylinder hull + rotatedDistance = angle * radius; + + // if the barrel moved further than it rotated about it's axis + if ( movedDistance > rotatedDistance ) { + + // additional rotation of the visual model to make it look + // like the barrel rolls instead of slides + angle = 180.0f * (movedDistance - rotatedDistance) / (radius * idMath::PI); + if ( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f ) { + additionalRotation += angle; + } else { + additionalRotation -= angle; + } + dir = vec3_origin; + dir[barrelAxis] = 1.0f; + additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3(); + } + } + } + + // save state for next think + lastOrigin = curOrigin; + lastAxis = curAxis; + } + + Present(); +} + +/* +================ +idBarrel::Think +================ +*/ +void idBarrel::Think( void ) { + if ( thinkFlags & TH_THINK ) { + if ( !FollowInitialSplinePath() && stage == -1 ) { + BecomeInactive( TH_THINK ); + } + } + + BarrelThink(); +} + +/* +================ +idBarrel::GetPhysicsToVisualTransform +================ +*/ +bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin = vec3_origin; + axis = additionalAxis; + return true; +} + +/* +================ +idBarrel::Spawn +================ +*/ +void idBarrel::Spawn( void ) { + const idBounds &bounds = GetPhysics()->GetBounds(); + + // radius of the barrel cylinder + radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f; + + // always a vertical barrel with cylinder axis parallel to the z-axis + barrelAxis = 2; + + lastOrigin = GetPhysics()->GetOrigin(); + lastAxis = GetPhysics()->GetAxis(); + + additionalRotation = 0.0f; + additionalAxis.Identity(); +} + +/* +================ +idBarrel::ClientPredictionThink +================ +*/ +void idBarrel::ClientPredictionThink( void ) { + Think(); +} + + +/* +=============================================================================== + +idExplodingBarrel + +=============================================================================== +*/ +const idEventDef EV_Respawn( "" ); +const idEventDef EV_TriggerTargets( "" ); + +CLASS_DECLARATION( idBarrel, idExplodingBarrel ) + EVENT( EV_Activate, idExplodingBarrel::Event_Activate ) + EVENT( EV_Respawn, idExplodingBarrel::Event_Respawn ) + EVENT( EV_Explode, idExplodingBarrel::Event_Explode ) + EVENT( EV_TriggerTargets, idExplodingBarrel::Event_TriggerTargets ) +END_CLASS + +/* +================ +idExplodingBarrel::idExplodingBarrel +================ +*/ +idExplodingBarrel::idExplodingBarrel() { + spawnOrigin.Zero(); + spawnAxis.Zero(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + ipsTime = 0; + lightTime = 0; + time = 0.0f; + explodeFinishTime = 0; +} + +/* +================ +idExplodingBarrel::~idExplodingBarrel +================ +*/ +idExplodingBarrel::~idExplodingBarrel() { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + if ( lightHandle >= 0 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + } +} + +/* +================ +idExplodingBarrel::Save +================ +*/ +void idExplodingBarrel::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( state ); + savefile->WriteVec3( spawnOrigin ); + savefile->WriteMat3( spawnAxis ); + savefile->WriteInt( ipsHandle ); + savefile->WriteInt( lightHandle ); + savefile->WriteRenderEntity( ipsEntity ); + savefile->WriteRenderLight( light ); + savefile->WriteInt( ipsTime ); + savefile->WriteInt( lightTime ); + savefile->WriteFloat( time ); + savefile->WriteInt( explodeFinishTime ); +} + +/* +================ +idExplodingBarrel::Restore +================ +*/ +void idExplodingBarrel::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( (int &)state ); + savefile->ReadVec3( spawnOrigin ); + savefile->ReadMat3( spawnAxis ); + savefile->ReadInt( (int &)ipsHandle ); + savefile->ReadInt( (int &)lightHandle ); +// RAVEN BEGIN + savefile->ReadRenderEntity( ipsEntity, &spawnArgs ); +// RAVEN END + savefile->ReadRenderLight( light ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &light ); + } + savefile->ReadInt( ipsTime ); + savefile->ReadInt( lightTime ); + savefile->ReadFloat( time ); + savefile->ReadInt( explodeFinishTime ); + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } + + if ( ipsHandle != -1 ) { + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::Spawn +================ +*/ +void idExplodingBarrel::Spawn( void ) { + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = true; + spawnOrigin = GetPhysics()->GetOrigin(); + spawnAxis = GetPhysics()->GetAxis(); + state = NORMAL; + ipsHandle = -1; + lightHandle = -1; + lightTime = 0; + ipsTime = 0; + time = spawnArgs.GetFloat( "time" ); + memset( &ipsEntity, 0, sizeof( ipsEntity ) ); + memset( &light, 0, sizeof( light ) ); + explodeFinishTime = 0; + + // precache decls + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { + declManager->FindType( DECL_ENTITYDEF, splash, false, false ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Think( void ) { + idBarrel::BarrelThink(); + + // MP: EXPLODED means no effect on client when updating the state + if ( !gameLocal.isClient && state == EXPLODING ) { + if ( gameLocal.time > explodeFinishTime ) { + state = EXPLODED; + } + } + + if ( lightHandle >= 0 ){ + if ( state == BURNING ) { + // ramp the color up over 250 ms + float pct = (gameLocal.time - lightTime) / 250.f; + if ( pct > 1.0f ) { + pct = 1.0f; + } + light.origin = physicsObj.GetAbsBounds().GetCenter(); + light.axis = mat3_identity; + light.shaderParms[ SHADERPARM_RED ] = pct; + light.shaderParms[ SHADERPARM_GREEN ] = pct; + light.shaderParms[ SHADERPARM_BLUE ] = pct; + light.shaderParms[ SHADERPARM_ALPHA ] = pct; + gameRenderWorld->UpdateLightDef( lightHandle, &light ); + } else { + if ( gameLocal.time - lightTime > 250 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } + return; + } + } + + if ( !gameLocal.isClient && state != BURNING && state != EXPLODING ) { + BecomeInactive( TH_THINK ); + return; + } + + if ( ipsHandle >= 0 ){ + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + gameRenderWorld->UpdateEntityDef( ipsHandle, &ipsEntity ); + } +} + +/* +================ +idExplodingBarrel::AddIPS +================ +*/ +void idExplodingBarrel::AddIPS( const char *name, bool burn ) { + if ( name && *name ) { + if ( ipsHandle >= 0 ){ + gameRenderWorld->FreeEntityDef( ipsHandle ); + } + memset( &ipsEntity, 0, sizeof ( ipsEntity ) ); + const idDeclModelDef *modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name ) ); + if ( modelDef ) { + ipsEntity.origin = physicsObj.GetAbsBounds().GetCenter(); + ipsEntity.axis = mat3_identity; + ipsEntity.hModel = modelDef->ModelHandle(); + float rgb = ( burn ) ? 0.0f : 1.0f; + ipsEntity.shaderParms[ SHADERPARM_RED ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_GREEN ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_BLUE ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb; + ipsEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + ipsEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 ); + if ( !ipsEntity.hModel ) { + ipsEntity.hModel = renderModelManager->FindModel( name ); + } + ipsHandle = gameRenderWorld->AddEntityDef( &ipsEntity ); + if ( burn ) { + BecomeActive( TH_THINK ); + } + ipsTime = gameLocal.time; + } + } +} + +/* +================ +idExplodingBarrel::AddLight +================ +*/ +void idExplodingBarrel::AddLight( const char *name, bool burn ) { + if ( lightHandle >= 0 ){ + gameRenderWorld->FreeLightDef( lightHandle ); + } + memset( &light, 0, sizeof ( light ) ); + light.axis = mat3_identity; + light.lightRadius.x = spawnArgs.GetFloat( "light_radius" ); + light.lightRadius.y = light.lightRadius.z = light.lightRadius.x; + light.origin = physicsObj.GetOrigin(); + light.origin.z += 128; + light.pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: made sure that barrels are set to no shadows + light.noShadows = true; +// RAVEN END + light.shader = declManager->FindMaterial( name ); + light.shaderParms[ SHADERPARM_RED ] = 2.0f; + light.shaderParms[ SHADERPARM_GREEN ] = 2.0f; + light.shaderParms[ SHADERPARM_BLUE ] = 2.0f; + light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f; + lightHandle = gameRenderWorld->AddLightDef( &light ); + lightTime = gameLocal.time; + BecomeActive( TH_THINK ); +} + +/* +================ +idExplodingBarrel::ExplodingEffects +================ +*/ +void idExplodingBarrel::ExplodingEffects( void ) { + const char *temp; + + StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL ); + + temp = spawnArgs.GetString( "model_damage", "" ); + if ( temp && *temp ) { + SetModel( temp ); + Show(); + } + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + temp = spawnArgs.GetString( "mtr_lightexplode", "" ); + if ( temp && *temp ) { + AddLight( temp, false ); + } +*/ + StopEffect ( "fx_burn" ); + gameLocal.PlayEffect ( gameLocal.GetEffect(spawnArgs, "fx_explode"), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3(), false, vec3_origin, true ); + + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); +} + +/* +================ +idExplodingBarrel::Killed +================ +*/ +void idExplodingBarrel::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + if ( IsHidden() || state == EXPLODED || state == EXPLODING || state == BURNING ) { + return; + } + + float f = spawnArgs.GetFloat( "burn" ); + if ( f > 0.0f && state == NORMAL ) { + state = BURNING; + PostEventSec( &EV_Explode, f ); + StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( gameLocal.GetEffect(spawnArgs,"fx_burn"), + vec3_origin, (-GetPhysics()->GetGravityNormal()).ToMat3(), true, vec3_origin, true ); + return; + } else { + state = EXPLODING; + spawnArgs.GetInt( "explode_lapse", "1000", explodeFinishTime ); + explodeFinishTime += gameLocal.time; + } + + // do this before applying radius damage so the ent can trace to any damagable ents nearby + Hide(); + physicsObj.SetContents( 0 ); + + const char *splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" ); + if ( splash && *splash ) { +// gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, inflictor, this, this, splash ); + PostEventMS( &EV_RadiusDamage, 0, this, splash); + } + + ExplodingEffects( ); + +// RAVEN BEGIN +// bdube: replaced with playing an effect +/* + //FIXME: need to precache all the debris stuff here and in the projectiles + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" ); + // bool first = true; + while ( kv ) { + const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( debris_args ) { + idEntity *ent; + idVec3 dir; + idDebris *debris; + //if ( first ) { + dir = physicsObj.GetAxis()[1]; + // first = false; + //} else { + dir.x += gameLocal.random.CRandomFloat() * 4.0f; + dir.y += gameLocal.random.CRandomFloat() * 4.0f; + //dir.z = gameLocal.random.RandomFloat() * 8.0f; + //} + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris_args, &ent, false ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f; + debris->UpdateVisuals(); + + } + kv = spawnArgs.MatchPrefix( "def_debris", kv ); + } +*/ +// RAVEN END + + physicsObj.PutToRest(); + CancelEvents( &EV_Explode ); + CancelEvents( &EV_Activate ); + + f = spawnArgs.GetFloat( "respawn" ); + if ( f > 0.0f ) { + PostEventSec( &EV_Respawn, f ); + } else { + PostEventMS( &EV_Remove, 5000 ); + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } +} + +/* +================ +idExplodingBarrel::Think +================ +*/ +void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) { + PostEventMS( &EV_Explode, 400 ); + } else { + idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + } +} + + +/* +================ +idExplodingBarrel::Event_TriggerTargets +================ +*/ +void idExplodingBarrel::Event_TriggerTargets() { + ActivateTargets( this ); +} + +/* +================ +idExplodingBarrel::Event_Explode +================ +*/ +void idExplodingBarrel::Event_Explode() { + if ( state == NORMAL || state == BURNING ) { + state = BURNEXPIRED; + Killed( NULL, NULL, 0, vec3_zero, 0 ); + } +} + +/* +================ +idExplodingBarrel::Event_Respawn +================ +*/ +void idExplodingBarrel::Event_Respawn() { + const char *temp = spawnArgs.GetString( "model" ); + if ( temp && *temp ) { + SetModel( temp ); + } + health = spawnArgs.GetInt( "health", "5" ); + fl.takedamage = (health > 0); + physicsObj.SetOrigin( spawnOrigin ); + physicsObj.SetAxis( spawnAxis ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.DropToFloor(); + state = NORMAL; + Show(); + UpdateVisuals(); +} + +/* +================ +idMoveable::Event_Activate +================ +*/ +void idExplodingBarrel::Event_Activate( idEntity *activator ) { + Killed( activator, activator, 0, vec3_origin, 0 ); +} + + + +/* +================ +idMoveable::WriteToSnapshot +================ +*/ +void idExplodingBarrel::WriteToSnapshot( idBitMsgDelta &msg ) const { + idMoveable::WriteToSnapshot( msg ); + msg.WriteBits( IsHidden(), 1 ); + msg.WriteBits( state, 3 ); +} + +/* +================ +idMoveable::ReadFromSnapshot +================ +*/ +void idExplodingBarrel::ReadFromSnapshot( const idBitMsgDelta &msg ) { + explode_state_t newState; + + idMoveable::ReadFromSnapshot( msg ); + if ( msg.ReadBits( 1 ) ) { + Hide(); + } else { + Show(); + } + newState = (explode_state_t)msg.ReadBits( 3 ); + if ( newState != state ) { + state = newState; + if ( state == EXPLODING ) { + ExplodingEffects( ); + } + } +} + +// RAVEN END diff --git a/src/mpgame/Moveable.h b/src/mpgame/Moveable.h new file mode 100644 index 0000000..5c6e744 --- /dev/null +++ b/src/mpgame/Moveable.h @@ -0,0 +1,203 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 06/02/2004 + +#ifndef __GAME_MOVEABLE_H__ +#define __GAME_MOVEABLE_H__ + +/* +=============================================================================== + + Entity using rigid body physics. + +=============================================================================== +*/ + +extern const idEventDef EV_BecomeNonSolid; +extern const idEventDef EV_IsAtRest; + +class idMoveable : public idDamagable { +public: + CLASS_PROTOTYPE( idMoveable ); + + idMoveable( void ); + ~idMoveable( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + bool AllowStep( void ) const; + void EnableDamage( bool enable, float duration ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + idPhysics_RigidBody physicsObj; // physics object + idStr brokenModel; // model set when health drops down to or below zero + idStr damage; // if > 0 apply damage to hit entities + int nextCollideFxTime; // next time it is ok to spawn collision fx + float minDamageVelocity; // minimum velocity before moveable applies damage + float maxDamageVelocity; // velocity at which the maximum damage is applied + idCurve_Spline *initialSpline; // initial spline path the moveable follows + idVec3 initialSplineDir; // initial relative direction along the spline path + bool unbindOnDeath; // unbind from master when health drops down to or below zero + bool allowStep; // allow monsters to step on the object + bool canDamage; // only apply damage when this is set + + idEntityPtr lastAttacker; + + virtual void ExecuteStage ( void ); + + const idMaterial * GetRenderModelMaterial( void ) const; + void BecomeNonSolid( void ); + void InitInitialSpline( int startTime ); + bool FollowInitialSplinePath( void ); + + void Event_Activate( idEntity *activator ); + void Event_BecomeNonSolid( void ); + void Event_SetOwnerFromSpawnArgs( void ); + void Event_IsAtRest( void ); + void Event_CanDamage ( float enable ); + void Event_SetHealth ( float newHealth ); + void Event_RadiusDamage( idEntity *attacker, const char* splash ); +}; + + +/* +=============================================================================== + + A barrel using rigid body physics. The barrel has special handling of + the view model orientation to make it look like it rolls instead of slides. + +=============================================================================== +*/ + +class idBarrel : public idMoveable { + +public: + CLASS_PROTOTYPE( idBarrel ); + idBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void BarrelThink( void ); + virtual void Think( void ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual void ClientPredictionThink( void ); + +private: + float radius; // radius of barrel + int barrelAxis; // one of the coordinate axes the barrel cylinder is parallel to + idVec3 lastOrigin; // origin of the barrel the last think frame + idMat3 lastAxis; // axis of the barrel the last think frame + float additionalRotation; // additional rotation of the barrel about it's axis + idMat3 additionalAxis; // additional rotation axis +}; + + +/* +=============================================================================== + + A barrel using rigid body physics and special handling of the view model + orientation to make it look like it rolls instead of slides. The barrel + can burn and explode when damaged. + +=============================================================================== +*/ + +class idExplodingBarrel : public idBarrel { +public: + CLASS_PROTOTYPE( idExplodingBarrel ); + + idExplodingBarrel(); + ~idExplodingBarrel(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + typedef enum { + NORMAL = 0, + BURNING, + BURNEXPIRED, + EXPLODING, + EXPLODED + } explode_state_t; + explode_state_t state; + + idVec3 spawnOrigin; + idMat3 spawnAxis; + qhandle_t ipsHandle; + qhandle_t lightHandle; + renderEntity_t ipsEntity; + renderLight_t light; + int ipsTime; + int lightTime; + float time; + + int explodeFinishTime; + + void AddIPS( const char *name, bool burn ); + void AddLight( const char *name , bool burn ); + void ExplodingEffects( void ); + + void Event_Activate( idEntity *activator ); + void Event_Respawn(); + void Event_Explode(); + void Event_TriggerTargets(); +}; + +#endif /* !__GAME_MOVEABLE_H__ */ + +// RAVEN END diff --git a/src/mpgame/Mover.cpp b/src/mpgame/Mover.cpp new file mode 100644 index 0000000..286c9b7 --- /dev/null +++ b/src/mpgame/Mover.cpp @@ -0,0 +1,5997 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +// a mover will update any gui entities in it's target list with +// a key/val pair of "mover" "state" from below.. guis can represent +// realtime info like this +// binary only +static const char *guiBinaryMoverStates[] = { + "1", // pos 1 + "2", // pos 2 + "3", // moving 1 to 2 + "4" // moving 2 to 1 +}; + + +/* +=============================================================================== + +idMover + +=============================================================================== +*/ + +const idEventDef EV_FindGuiTargets( "", NULL ); +const idEventDef EV_TeamBlocked( "", "ee" ); +const idEventDef EV_PartBlocked( "", "e" ); +const idEventDef EV_ReachedPos( "", NULL ); +const idEventDef EV_ReachedAng( "", NULL ); +// RAVEN BEGIN +const idEventDef EV_PostRestoreExt( "", "ddddd" ); +// RAVEN END +const idEventDef EV_StopMoving( "stopMoving", NULL ); +const idEventDef EV_StopRotating( "stopRotating", NULL ); +const idEventDef EV_Speed( "speed", "f" ); +const idEventDef EV_Time( "time", "f" ); +const idEventDef EV_AccelTime( "accelTime", "f" ); +const idEventDef EV_DecelTime( "decelTime", "f" ); +const idEventDef EV_MoveTo( "moveTo", "e" ); +const idEventDef EV_MoveToPos( "moveToPos", "v" ); +const idEventDef EV_Move( "move", "ff" ); +const idEventDef EV_MoveAccelerateTo( "accelTo", "ff" ); +const idEventDef EV_MoveDecelerateTo( "decelTo", "ff" ); +const idEventDef EV_RotateDownTo( "rotateDownTo", "df" ); +const idEventDef EV_RotateUpTo( "rotateUpTo", "df" ); +const idEventDef EV_RotateTo( "rotateTo", "v" ); +const idEventDef EV_Rotate( "rotate", "v" ); +const idEventDef EV_RotateOnce( "rotateOnce", "v" ); +const idEventDef EV_Bob( "bob", "ffv" ); +const idEventDef EV_Sway( "sway", "ffv" ); +const idEventDef EV_Mover_OpenPortal( "openPortal" ); +const idEventDef EV_Mover_ClosePortal( "closePortal" ); +const idEventDef EV_AccelSound( "accelSound", "s" ); +const idEventDef EV_DecelSound( "decelSound", "s" ); +// RAVEN BEGIN +// cnicholson: added stop sound support +const idEventDef EV_StoppedSound( "stoppedSound", "s" ); +// RAVEN END +const idEventDef EV_MoveSound( "moveSound", "s" ); +const idEventDef EV_Mover_InitGuiTargets( "", NULL ); +const idEventDef EV_EnableSplineAngles( "enableSplineAngles", NULL ); +const idEventDef EV_DisableSplineAngles( "disableSplineAngles", NULL ); +const idEventDef EV_RemoveInitialSplineAngles( "removeInitialSplineAngles", NULL ); +const idEventDef EV_StartSpline( "startSpline", "e" ); +const idEventDef EV_StopSpline( "stopSpline", NULL ); +const idEventDef EV_IsMoving( "isMoving", NULL, 'd' ); +const idEventDef EV_IsRotating( "isRotating", NULL, 'd' ); +// RAVEN BEGIN +// abahr: +const idEventDef EV_GetSplineEntity( "getSplineEntity", NULL, 'E' ); +const idEventDef EV_MoveAlongVector( "moveAlongVector", "v" ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idMover ) + EVENT( EV_FindGuiTargets, idMover::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover::Event_SetCallback ) + EVENT( EV_TeamBlocked, idMover::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover::Event_PartBlocked ) + EVENT( EV_ReachedPos, idMover::Event_UpdateMove ) + EVENT( EV_ReachedAng, idMover::Event_UpdateRotation ) +// RAVEN BEGIN + EVENT( EV_PostRestoreExt, idMover::Event_PostRestoreExt ) +// RAVEN END + EVENT( EV_StopMoving, idMover::Event_StopMoving ) + EVENT( EV_StopRotating, idMover::Event_StopRotating ) + EVENT( EV_Speed, idMover::Event_SetMoveSpeed ) + EVENT( EV_Time, idMover::Event_SetMoveTime ) + EVENT( EV_AccelTime, idMover::Event_SetAccellerationTime ) + EVENT( EV_DecelTime, idMover::Event_SetDecelerationTime ) + EVENT( EV_MoveTo, idMover::Event_MoveTo ) + EVENT( EV_MoveToPos, idMover::Event_MoveToPos ) + EVENT( EV_Move, idMover::Event_MoveDir ) + EVENT( EV_MoveAccelerateTo, idMover::Event_MoveAccelerateTo ) + EVENT( EV_MoveDecelerateTo, idMover::Event_MoveDecelerateTo ) + EVENT( EV_RotateDownTo, idMover::Event_RotateDownTo ) + EVENT( EV_RotateUpTo, idMover::Event_RotateUpTo ) + EVENT( EV_RotateTo, idMover::Event_RotateTo ) + EVENT( EV_Rotate, idMover::Event_Rotate ) + EVENT( EV_RotateOnce, idMover::Event_RotateOnce ) + EVENT( EV_Bob, idMover::Event_Bob ) + EVENT( EV_Sway, idMover::Event_Sway ) + EVENT( EV_Mover_OpenPortal, idMover::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover::Event_ClosePortal ) + EVENT( EV_AccelSound, idMover::Event_SetAccelSound ) + EVENT( EV_DecelSound, idMover::Event_SetDecelSound ) +// RAVEN BEGIN +// cnicholson: added stop sound support + EVENT( EV_StoppedSound, idMover::Event_SetStoppedSound ) +// RAVEN END + EVENT( EV_MoveSound, idMover::Event_SetMoveSound ) + EVENT( EV_Mover_InitGuiTargets, idMover::Event_InitGuiTargets ) + EVENT( EV_EnableSplineAngles, idMover::Event_EnableSplineAngles ) + EVENT( EV_DisableSplineAngles, idMover::Event_DisableSplineAngles ) + EVENT( EV_RemoveInitialSplineAngles, idMover::Event_RemoveInitialSplineAngles ) + EVENT( EV_StartSpline, idMover::Event_StartSpline ) + EVENT( EV_StopSpline, idMover::Event_StopSpline ) + EVENT( EV_Activate, idMover::Event_Activate ) + EVENT( EV_IsMoving, idMover::Event_IsMoving ) + EVENT( EV_IsRotating, idMover::Event_IsRotating ) +// RAVEN BEGIN +// abahr: + EVENT( EV_GetSplineEntity, idMover::Event_GetSplineEntity ) + EVENT( EV_MoveAlongVector, idMover::Event_MoveAlongVector ) +// RAVEN END +END_CLASS + +/* +================ +idMover::idMover +================ +*/ +idMover::idMover( void ) { + memset( &move, 0, sizeof( move ) ); + memset( &rot, 0, sizeof( rot ) ); + move_thread = 0; + rotate_thread = 0; + dest_angles.Zero(); + angle_delta.Zero(); + dest_position.Zero(); + move_delta.Zero(); + move_speed = 0.0f; + move_time = 0; + deceltime = 0; + acceltime = 0; + stopRotation = false; + useSplineAngles = true; + attenuate = false; + useIdleSound = false; + lastCommand = MOVER_NONE; + damage = 0.0f; + areaPortal = 0; + maxAttenuation = 0.0f; + attenuationScale = 0.0f; + lastOrigin.Zero( ); + lastTime = 0; + splineStartTime = 0; + + fl.networkSync = true; +} + + +idMover::~idMover( void ) { + SetPhysics( NULL ); +} + + + +/* +================ +idMover::Save +================ +*/ +void idMover::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteStaticObject( physicsObj ); + + savefile->Write( &move, sizeof( move ) ); + savefile->Write( &rot, sizeof( rot ) ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( rotate_thread ); + + savefile->WriteAngles( dest_angles ); + savefile->WriteAngles( angle_delta ); + savefile->WriteVec3( dest_position ); + savefile->WriteVec3( move_delta ); + + savefile->WriteFloat( move_speed ); + savefile->WriteInt( move_time ); + savefile->WriteInt( deceltime ); + savefile->WriteInt( acceltime ); + savefile->WriteBool( stopRotation ); + savefile->WriteBool( useSplineAngles ); + savefile->WriteInt( lastCommand ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal > 0 ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } + + if ( splineEnt.GetEntity() ) { + idCurve_Spline *spline = physicsObj.GetSpline(); + if (spline) { + + savefile->WriteBool( true ); + splineEnt.Save( savefile ); + savefile->WriteInt( spline->GetTime( 0 ) ); + savefile->WriteInt( spline->GetTime( spline->GetNumValues() - 1 ) - spline->GetTime( 0 ) ); + savefile->WriteInt( physicsObj.GetSplineAcceleration() ); + savefile->WriteInt( physicsObj.GetSplineDeceleration() ); + savefile->WriteInt( (int)physicsObj.UsingSplineAngles() ); + } else { + savefile->WriteBool( false ); + } + + } else { + savefile->WriteBool( false ); + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->WriteBool( attenuate ); + savefile->WriteFloat( maxAttenuation ); + savefile->WriteFloat( attenuationScale ); + savefile->WriteVec3( lastOrigin ); + savefile->WriteInt( lastTime ); + savefile->WriteBool( useIdleSound ); + splineStateThread.Save( savefile ); + savefile->WriteInt( splineStartTime ); +// RAVEN END +} + +/* +================ +idMover::Restore +================ +*/ +void idMover::Restore( idRestoreGame *savefile ) { + int i, num; + bool hasSpline = false; + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->Read( &move, sizeof( move ) ); + savefile->Read( &rot, sizeof( rot ) ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( rotate_thread ); + + savefile->ReadAngles( dest_angles ); + savefile->ReadAngles( angle_delta ); + savefile->ReadVec3( dest_position ); + savefile->ReadVec3( move_delta ); + + savefile->ReadFloat( move_speed ); + savefile->ReadInt( move_time ); + savefile->ReadInt( deceltime ); + savefile->ReadInt( acceltime ); + savefile->ReadBool( stopRotation ); + savefile->ReadBool( useSplineAngles ); + savefile->ReadInt( (int &)lastCommand ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal > 0 ) { + int portalState = 0; + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } + + savefile->ReadBool( hasSpline ); + if ( hasSpline ) { + int starttime; + int totaltime; + int accel; + int decel; + int useAngles; + + splineEnt.Restore( savefile ); + savefile->ReadInt( starttime ); + savefile->ReadInt( totaltime ); + savefile->ReadInt( accel ); + savefile->ReadInt( decel ); + savefile->ReadInt( useAngles ); + +// RAVEN BEGIN + PostEventMS( &EV_PostRestoreExt, 0, starttime, totaltime, accel, decel, useAngles ); +// RAVEN END + } + +// RAVEN BEGIN +// mekberg: added for attenuation and idle sound + savefile->ReadBool( attenuate ); + savefile->ReadFloat( maxAttenuation ); + savefile->ReadFloat( attenuationScale ); + savefile->ReadVec3( lastOrigin ); + savefile->ReadInt( lastTime ); + savefile->ReadBool( useIdleSound ); + splineStateThread.Restore( savefile, this ); + savefile->ReadInt( splineStartTime ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +/* +================ +idMover::Event_PostRestoreExt +================ +*/ +// RAVEN BEGIN +void idMover::Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ) { +// RAVEN END + idCurve_Spline *spline; + + idEntity *splineEntity = splineEnt.GetEntity(); + if ( !splineEntity ) { + // We should never get this event if splineEnt is invalid + common->Warning( "Invalid spline entity during restore\n" ); + return; + } + + spline = splineEntity->GetSpline(); + + spline->MakeUniform( total ); + spline->ShiftTime( start - spline->GetTime( 0 ) ); + + physicsObj.SetSpline( spline, accel, decel, useSplineAng ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); +} + +/* +================ +idMover::Spawn +================ +*/ +void idMover::Spawn( void ) { + move_thread = 0; + rotate_thread = 0; + stopRotation = false; + lastCommand = MOVER_NONE; + + acceltime = 1000.0f * spawnArgs.GetFloat( "accel_time", "0" ); + deceltime = 1000.0f * spawnArgs.GetFloat( "decel_time", "0" ); + move_time = 1000.0f * spawnArgs.GetFloat( "move_time", "1" ); // safe default value + move_speed = spawnArgs.GetFloat( "move_speed", "0" ); + + spawnArgs.GetFloat( "damage" , "0", damage ); + + dest_position = GetPhysics()->GetOrigin(); + dest_angles = GetPhysics()->GetAxis().ToAngles(); + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !renderEntity.hModel || !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } + + health = spawnArgs.GetInt( "health" ); + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + if( spawnArgs.GetBool("removeGimbleLock") ) { + physicsObj.SetAxisOffset( spawnArgs.GetMatrix("rotation", mat3_identity.ToString()) ); + physicsObj.SetAxis( mat3_identity ); + UpdateVisuals(); + } + +// mekberg: attenuation + attenuate = spawnArgs.GetBool( "attenuate" ); + if( attenuate ) { + maxAttenuation = spawnArgs.GetFloat( "maxAttenuation", "3" ); + attenuationScale = spawnArgs.GetFloat( "attenuationScale", "100" ); + + // Check for bad value/prevent divide by zero + if ( attenuationScale == 0.0f || attenuationScale < 0.0f ) { + attenuationScale = 100; + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } else { + maxAttenuation = 0.0f; + attenuationScale = 1.0f; + } + + if ( !idStr::Icmp( spawnArgs.GetString( "snd_idle", "" ), "" ) ) { + useIdleSound = false; + } else { + StartSound( "snd_idle", SND_CHANNEL_BODY, 0, false, NULL ); + useIdleSound = true; + } + + splineStateThread.SetName( "SplineStateThread" ); + splineStateThread.SetOwner( this ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_moverCrush", false, false ); +// RAVEN END +} + +// RAVEN BEGIN +// mekberg: added +/* +================ +idMover::Think +================ +*/ +void idMover::Think( void ) { + idVec3 deltaPosition; + float deltaTime; + float speed; + float attenuation; + + if ( physicsObj.GetSpline( ) ) { + splineStateThread.Execute( ); + } + + if ( attenuate ) { + deltaPosition = physicsObj.GetOrigin( ) - lastOrigin; + deltaTime = gameLocal.time - lastTime; + + if ( !deltaTime ) { + deltaTime = 1; + } + + speed = deltaPosition.Length( ) * ( 1000.0f / float( deltaTime ) ); + + if ( speed >= VECTOR_EPSILON ) { + soundShaderParms_t parms = refSound.parms; + + attenuation = 0.8f + 0.2f * ( speed / attenuationScale ); + parms.frequencyShift = idMath::ClampFloat( 0.0f, maxAttenuation, attenuation ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( SND_CHANNEL_BODY, &parms ); + } + } + + lastOrigin = physicsObj.GetOrigin( ); + lastTime = gameLocal.time; + } + + idEntity::Think( ); +} + +// abahr +/* +================ +idMover::GetPhysicsToVisualTransform +================ +*/ +bool idMover::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + origin.Zero(); + axis = physicsObj.GetAxisOffset(); + return physicsObj.UseAxisOffset(); +} + +/* +================ +idMover::MoveAlongVector +================ +*/ +void idMover::MoveAlongVector( const idVec3& vec ) { + idAngles ang; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.GetLocalAngles( ang ); + dest_position = org + vec * ang.ToMat3(); + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAlongVector +================ +*/ +void idMover::Event_MoveAlongVector( const idVec3& vec ) { + MoveAlongVector( vec ); +} +// RAVEN END + +/* +================ +idMover::Hide +================ +*/ +void idMover::Hide( void ) { + idEntity::Hide(); + physicsObj.SetContents( 0 ); +} + +/* +================ +idMover::Show +================ +*/ +void idMover::Show( void ) { + idEntity::Show(); + if ( spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( CONTENTS_SOLID ); + } + SetPhysics( &physicsObj ); +} + +/* +============ +idMover::Killed +============ +*/ +void idMover::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + fl.takedamage = false; + ActivateTargets( this ); +} + + +/* +================ +idMover::Event_SetCallback +================ +*/ +void idMover::Event_SetCallback( void ) { + if ( ( lastCommand == MOVER_ROTATING ) && !rotate_thread ) { + lastCommand = MOVER_NONE; + rotate_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else if ( ( lastCommand == MOVER_MOVING || lastCommand == MOVER_SPLINE ) && !move_thread ) { + lastCommand = MOVER_NONE; + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idMover::VectorForDir +================ +*/ +void idMover::VectorForDir( float angle, idVec3 &vec ) { + idAngles ang; + + switch( ( int )angle ) { + case DIR_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 90; + vec = ang.ToForward(); + break; + + case DIR_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw -= 90; + vec = ang.ToForward(); + break; + + case DIR_FORWARD : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + vec = ang.ToForward(); + break; + + case DIR_BACK : + physicsObj.GetLocalAngles( ang ); + ang.pitch = 0; + ang.roll = 0; + ang.yaw += 180; + vec = ang.ToForward(); + break; + + case DIR_REL_UP : + vec.Set( 0, 0, 1 ); + break; + + case DIR_REL_DOWN : + vec.Set( 0, 0, -1 ); + break; + + case DIR_REL_LEFT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + vec *= -1; + break; + + case DIR_REL_RIGHT : + physicsObj.GetLocalAngles( ang ); + ang.ToVectors( NULL, &vec ); + break; + + case DIR_REL_FORWARD : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward(); + break; + + case DIR_REL_BACK : + physicsObj.GetLocalAngles( ang ); + vec = ang.ToForward() * -1; + break; + + default: + ang.Set( 0, angle, 0 ); + vec = GetWorldVector( ang.ToForward() ); + break; + } +} + +/* +================ +idMover::FindGuiTargets +================ +*/ +void idMover::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover::SetGuiState( const char *key, const char *val ) const { + gameLocal.Printf( "Setting %s to %s\n", key, val ); + for( int i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover::SetGuiStates +================ +*/ +void idMover::SetGuiStates( const char *state ) { + int i; + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( renderEntity.gui[ i ] ) { + renderEntity.gui[ i ]->SetStateString( "movestate", state ); + renderEntity.gui[ i ]->StateChanged( gameLocal.time, true ); + } + } +} + +/* +================ +idMover::Event_InitGuiTargets +================ +*/ +void idMover::Event_InitGuiTargets( void ) { + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); +} + +/*********************************************************************** + + Translation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopMoving +================ +*/ +void idMover::Event_StopMoving( void ) { + physicsObj.GetLocalOrigin( dest_position ); + DoneMoving(); +} + +/* +================ +idMover::DoneMoving +================ +*/ +void idMover::DoneMoving( void ) { + + if ( lastCommand != MOVER_SPLINE ) { + // set our final position so that we get rid of any numerical inaccuracy + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + } + + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added sig reached + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + +// RAVEN BEGIN +// mekberg: for idle sound + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } +} + +/* +================ +idMover::UpdateMoveSound +================ +*/ +void idMover::UpdateMoveSound( moveStage_t stage ) { +// RAVEN BEGIN +// mekberg: Idle sound plays instead of snd_move. Don't stop idle sounds. + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case LINEAR_STAGE: { + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + break; + } + case DECELERATION_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + // cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + } +// RAVEN END +} + +/* +================ +idMover::Event_UpdateMove +================ +*/ +void idMover::Event_UpdateMove( void ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + + UpdateMoveSound( move.stage ); + + switch( move.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, move.dir, vec3_origin ); + if ( move.movetime > 0 ) { + move.stage = LINEAR_STAGE; + } else if ( move.deceleration > 0 ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, move.movetime, org, move.dir, vec3_origin ); + if ( move.deceleration ) { + move.stage = DECELERATION_STAGE; + } else { + move.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, move.dir, vec3_origin ); + move.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' move done\n", gameLocal.time, name.c_str() ); + } + DoneMoving(); + break; + } + } +} + +/* +================ +idMover::BeginMove +================ +*/ +void idMover::BeginMove( idThread *thread ) { + moveStage_t stage; + idVec3 org; + float dist; + float acceldist; + int totalacceltime; + int at; + int dt; + + lastCommand = MOVER_MOVING; + move_thread = 0; + + physicsObj.GetLocalOrigin( org ); + + move_delta = dest_position - org; + if ( move_delta.Compare( vec3_zero ) ) { + DoneMoving(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + + // if we're moving at a specific speed, we need to calculate the move time + if ( move_speed ) { + dist = move_delta.Length(); + + totalacceltime = acceltime + deceltime; + + // calculate the distance we'll move during acceleration and deceleration + acceldist = totalacceltime * 0.5f * 0.001f * move_speed; + if ( acceldist >= dist ) { + // going too slow for this distance to move at a constant speed + move_time = totalacceltime; + } else { + // calculate move time taking acceleration into account + move_time = totalacceltime + 1000.0f * ( dist - acceldist ) / move_speed; + } + } + + // scale time up to a whole physics frames + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + move_delta = move_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + move.stage = stage; + move.acceleration = at; + move.movetime = move_time - at - dt; + move.deceleration = dt; + move.dir = move_delta; + + ProcessEvent( &EV_ReachedPos ); +} + +/*********************************************************************** + + Rotation control functions + +***********************************************************************/ + +/* +================ +idMover::Event_StopRotating +================ +*/ +void idMover::Event_StopRotating( void ) { + physicsObj.GetLocalAngles( dest_angles ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + DoneRotating(); +} + +/* +================ +idMover::DoneRotating +================ +*/ +void idMover::DoneRotating( void ) { + lastCommand = MOVER_NONE; + +// RAVEN BEGIN +// kfuller: added reached signal + Signal(SIG_REACHED); +// RAVEN END + + idThread::ObjectMoveDone( rotate_thread, this ); + rotate_thread = 0; + + StopSound( SND_CHANNEL_BODY, false ); +} + +/* +================ +idMover::UpdateRotationSound +================ +*/ +void idMover::UpdateRotationSound( moveStage_t stage ) { + switch( stage ) { + case ACCELERATION_STAGE: { + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case LINEAR_STAGE: { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + break; + } + case DECELERATION_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + break; + } + case FINISHED_STAGE: { + StopSound( SND_CHANNEL_BODY, false ); +// RAVEN BEGIN +// cnicholson: added stop sound support + StartSound( "snd_stopped", SND_CHANNEL_BODY, 0, false, NULL ); +// RAVEN END + break; + } + } +} + +/* +================ +idMover::Event_UpdateRotation +================ +*/ +void idMover::Event_UpdateRotation( void ) { + idAngles ang; + + physicsObj.GetLocalAngles( ang ); + + UpdateRotationSound( rot.stage ); + + switch( rot.stage ) { + case ACCELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, rot.acceleration, ang, rot.rot, ang_zero ); + if ( rot.movetime > 0 ) { + rot.stage = LINEAR_STAGE; + } else if ( rot.deceleration > 0 ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case LINEAR_STAGE: { + if ( !stopRotation && !rot.deceleration ) { + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } else { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, rot.movetime, ang, rot.rot, ang_zero ); + } + + if ( rot.deceleration ) { + rot.stage = DECELERATION_STAGE; + } else { + rot.stage = FINISHED_STAGE; + } + break; + } + case DECELERATION_STAGE: { + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, rot.deceleration, ang, rot.rot, ang_zero ); + rot.stage = FINISHED_STAGE; + break; + } + case FINISHED_STAGE: { + lastCommand = MOVER_NONE; + if ( stopRotation ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + } else if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_ACCELLINEAR ) { + // keep our angular velocity constant + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, rot.rot, ang_zero ); + } + + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' rotation done\n", gameLocal.time, name.c_str() ); + } + + DoneRotating(); + break; + } + } +} + +/* +================ +idMover::BeginRotation +================ +*/ +void idMover::BeginRotation( idThread *thread, bool stopwhendone ) { + moveStage_t stage; + idAngles ang; + int at; + int dt; + + lastCommand = MOVER_ROTATING; + rotate_thread = 0; + + // rotation always uses move_time so that if a move was started before the rotation, + // the rotation will take the same amount of time as the move. If no move has been + // started and no time is set, the rotation takes 1 second. + if ( !move_time ) { + move_time = 1; + } + + physicsObj.GetLocalAngles( ang ); + angle_delta = dest_angles - ang; + if ( angle_delta == ang_zero ) { + // set our final angles so that we get rid of any numerical inaccuracy + dest_angles.Normalize360(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_angles, ang_zero, ang_zero ); + stopRotation = false; + DoneRotating(); + return; + } + + // scale times up to whole physics frames + at = idPhysics::SnapTimeToPhysicsFrame( acceltime ); + move_time += at - acceltime; + acceltime = at; + dt = idPhysics::SnapTimeToPhysicsFrame( deceltime ); + move_time += dt - deceltime; + deceltime = dt; + move_time = idPhysics::SnapTimeToPhysicsFrame( move_time ); + + if ( acceltime ) { + stage = ACCELERATION_STAGE; + } else if ( move_time <= deceltime ) { + stage = DECELERATION_STAGE; + } else { + stage = LINEAR_STAGE; + } + + at = acceltime; + dt = deceltime; + + if ( at + dt > move_time ) { + // there's no real correct way to handle this, so we just scale + // the times to fit into the move time in the same proportions + at = idPhysics::SnapTimeToPhysicsFrame( at * move_time / ( at + dt ) ); + dt = move_time - at; + } + + angle_delta = angle_delta * ( 1000.0f / ( (float) move_time - ( at + dt ) * 0.5f ) ); + + stopRotation = stopwhendone || ( dt != 0 ); + + rot.stage = stage; + rot.acceleration = at; + rot.movetime = move_time - at - dt; + rot.deceleration = dt; + rot.rot = angle_delta; + + ProcessEvent( &EV_ReachedAng ); +} + + +/*********************************************************************** + + Script callable routines + +***********************************************************************/ + +/* +=============== +idMover::Event_TeamBlocked +=============== +*/ +void idMover::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' stopped due to team member '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockedEntity->name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +=============== +idMover::Event_PartBlocked +=============== +*/ +void idMover::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if ( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockingEntity->name.c_str() ); + } +} + +/* +================ +idMover::Event_SetMoveSpeed +================ +*/ +void idMover::Event_SetMoveSpeed( float speed ) { + if ( speed <= 0 ) { + gameLocal.Error( "Cannot set speed less than or equal to 0." ); + } + + move_speed = speed; + move_time = 0; // move_time is calculated for each move when move_speed is non-0 +} + +/* +================ +idMover::Event_SetMoveTime +================ +*/ +void idMover::Event_SetMoveTime( float time ) { + if ( time <= 0 ) { + gameLocal.Error( "Cannot set time less than or equal to 0." ); + } + + move_speed = 0; + move_time = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetAccellerationTime +================ +*/ +void idMover::Event_SetAccellerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set acceleration time less than 0." ); + } + + acceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_SetDecelerationTime +================ +*/ +void idMover::Event_SetDecelerationTime( float time ) { + if ( time < 0 ) { + gameLocal.Error( "Cannot set deceleration time less than 0." ); + } + + deceltime = SEC2MS( time ); +} + +/* +================ +idMover::Event_MoveTo +================ +*/ +void idMover::Event_MoveTo( idEntity *ent ) { + if ( !ent ) { + gameLocal.Warning( "Entity not found" ); +// RAVEN BEGIN +// abahr: added return so the NULL ptr doesn't get used + return; +// RAVEN END + } + + dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::MoveToPos +================ +*/ +void idMover::MoveToPos( const idVec3 &pos ) { + dest_position = GetLocalCoordinates( pos ); + BeginMove( NULL ); +} + +/* +================ +idMover::Event_MoveToPos +================ +*/ +void idMover::Event_MoveToPos( idVec3 &pos ) { + MoveToPos( pos ); +} + +/* +================ +idMover::Event_MoveDir +================ +*/ +void idMover::Event_MoveDir( float angle, float distance ) { + idVec3 dir; + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + VectorForDir( angle, dir ); + dest_position = org + dir * distance; + + BeginMove( idThread::CurrentThread() ); +} + +/* +================ +idMover::Event_MoveAccelerateTo +================ +*/ +void idMover::Event_MoveAccelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int at; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: cannot set acceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveAccelerateTo: not moving." ); + } + + // if already moving faster than the desired speed + if ( v >= speed ) { + return; + } + + at = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = ACCELERATION_STAGE; + move.acceleration = at; + move.movetime = 0; + move.deceleration = 0; + + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_ACCELLINEAR, gameLocal.time, move.acceleration, org, dir * ( speed - v ), dir * v ); +} + +/* +================ +idMover::Event_MoveDecelerateTo +================ +*/ +void idMover::Event_MoveDecelerateTo( float speed, float time ) { + float v; + idVec3 org, dir; + int dt; + + if ( time < 0 ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: cannot set deceleration time less than 0." ); + } + + dir = physicsObj.GetLinearVelocity(); + v = dir.Normalize(); + + // if not moving already + if ( v == 0.0f ) { + gameLocal.Error( "idMover::Event_MoveDecelerateTo: not moving." ); + } + + // if already moving slower than the desired speed + if ( v <= speed ) { + return; + } + + dt = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + lastCommand = MOVER_MOVING; + + physicsObj.GetLocalOrigin( org ); + + move.stage = DECELERATION_STAGE; + move.acceleration = 0; + move.movetime = 0; + move.deceleration = dt; + + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_DECELLINEAR, gameLocal.time, move.deceleration, org, dir * ( v - speed ), dir * speed ); +} + +/* +================ +idMover::Event_RotateDownTo +================ +*/ +void idMover::Event_RotateDownTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] > ang[ axis ] ) { + dest_angles[ axis ] -= 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateUpTo +================ +*/ +void idMover::Event_RotateUpTo( int axis, float angle ) { + idAngles ang; + + if ( ( axis < 0 ) || ( axis > 2 ) ) { + gameLocal.Error( "Invalid axis" ); + } + + physicsObj.GetLocalAngles( ang ); + + dest_angles[ axis ] = angle; + if ( dest_angles[ axis ] < ang[ axis ] ) { + dest_angles[ axis ] += 360; + } + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_RotateTo +================ +*/ +void idMover::Event_RotateTo( idAngles &angles ) { + dest_angles = angles; + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Rotate +================ +*/ +void idMover::Event_Rotate( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles * ( move_time - ( acceltime + deceltime ) / 2 ) * 0.001f; + + BeginRotation( idThread::CurrentThread(), false ); +} + +/* +================ +idMover::Event_RotateOnce +================ +*/ +void idMover::Event_RotateOnce( idAngles &angles ) { + idAngles ang; + + if ( rotate_thread ) { + DoneRotating(); + } + + physicsObj.GetLocalAngles( ang ); + dest_angles = ang + angles; + + BeginRotation( idThread::CurrentThread(), true ); +} + +/* +================ +idMover::Event_Bob +================ +*/ +void idMover::Event_Bob( float speed, float phase, idVec3 &depth ) { + idVec3 org; + + physicsObj.GetLocalOrigin( org ); + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), speed * 1000 * phase, speed * 500, org, depth * 2.0f, vec3_origin ); +} + +/* +================ +idMover::Event_Sway +================ +*/ +void idMover::Event_Sway( float speed, float phase, idAngles &depth ) { + idAngles ang, angSpeed; + float duration; + + physicsObj.GetLocalAngles( ang ); + assert ( speed > 0.0f ); + duration = idMath::Sqrt( depth[0] * depth[0] + depth[1] * depth[1] + depth[2] * depth[2] ) / speed; + angSpeed = depth / ( duration * idMath::SQRT_1OVER2 ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), duration * 1000.0f * phase, duration * 1000.0f, ang, angSpeed, ang_zero ); +} + +/* +================ +idMover::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover::Event_OpenPortal( void ) { + if ( areaPortal ) { + SetPortalState( true ); + } +} + +/* +================ +idMover::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover::Event_ClosePortal( void ) { + if ( areaPortal ) { + SetPortalState( false ); + } +} + +/* +================ +idMover::Event_SetAccelSound +================ +*/ +void idMover::Event_SetAccelSound( const char *sound ) { +// refSound.SetSound( "accel", sound ); +} + +/* +================ +idMover::Event_SetDecelSound +================ +*/ +void idMover::Event_SetDecelSound( const char *sound ) { +// refSound.SetSound( "decel", sound ); +} + +// RAVEN BEGIN +// cnicholson: added stop sound support +/* +================ +idMover::Event_SetStoppedSound +================ +*/ +void idMover::Event_SetStoppedSound( const char *sound ) { +// refSound.SetSound( "stopped", sound ); +} +// RAVEN END + +/* +================ +idMover::Event_SetMoveSound +================ +*/ +void idMover::Event_SetMoveSound( const char *sound ) { +// refSound.SetSound( "move", sound ); +} + +/* +================ +idMover::Event_EnableSplineAngles +================ +*/ +void idMover::Event_EnableSplineAngles( void ) { + useSplineAngles = true; +} + +/* +================ +idMover::Event_DisableSplineAngles +================ +*/ +void idMover::Event_DisableSplineAngles( void ) { + useSplineAngles = false; +} + +/* +================ +idMover::Event_RemoveInitialSplineAngles +================ +*/ +void idMover::Event_RemoveInitialSplineAngles( void ) { + idCurve_Spline *spline; + idAngles ang; + + spline = physicsObj.GetSpline(); + if ( !spline ) { + return; + } + ang = spline->GetCurrentFirstDerivative( 0 ).ToAngles(); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, -ang, ang_zero, ang_zero ); +} + +/* +================ +idMover::Event_StartSpline +================ +*/ +void idMover::Event_StartSpline( idEntity *splineEntity ) { + idCurve_Spline *spline; + + if ( !splineEntity ) { + return; + } + + // Needed for savegames + splineEnt = splineEntity; + + spline = splineEntity->GetSpline(); + if ( !spline ) { + return; + } + + lastCommand = MOVER_SPLINE; + move_thread = 0; + +// RAVEN BEGIN +// bdube: movement speed + // Use movement speed? + if ( idMath::Fabs(move_speed) >= VECTOR_EPSILON ) { + // Set a fixed time to determine the length from + spline->MakeUniform( 1000 ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + + // Calculate the move time from the speed + move.movetime = SEC2MS( spline->GetLengthForTime(spline->GetTime(spline->GetNumValues() - 1)) / move_speed ); + move_time = move.movetime; + + spline->SetConstantSpeed( move.movetime ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } else { + spline->MakeUniform( move_time ); + spline->ShiftTime( gameLocal.GetTime() - spline->GetTime( 0 ) ); + } +// RAVEN END + + if ( acceltime + deceltime > move_time ) { + acceltime = move_time / 2; + deceltime = move_time - acceltime; + } + + move.stage = FINISHED_STAGE; + move.acceleration = acceltime; + move.movetime = move_time; + move.deceleration = deceltime; + + physicsObj.SetSpline( spline, move.acceleration, move.deceleration, useSplineAngles ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, dest_position, vec3_origin, vec3_origin ); + +// RAVEN BEGIN +// mekberg: let the splines use a state thread instead of linear extrapolation + if ( acceltime ) { + splineStateThread.SetState( "Accel" ); + } else { + splineStateThread.SetState( "Linear" ); + } + + splineStartTime = gameLocal.time; +// RAVEN END +} + +/* +================ +idMover::Event_StopSpline +================ +*/ +void idMover::Event_StopSpline( void ) { + physicsObj.SetSpline( NULL, 0, 0, useSplineAngles ); + splineEnt = NULL; +} + +/* +================ +idMover::Event_Activate +================ +*/ +void idMover::Event_Activate( idEntity *activator ) { + Show(); + Event_StartSpline( this ); +} + +/* +================ +idMover::Event_IsMoving +================ +*/ +void idMover::Event_IsMoving( void ) { + if ( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::Event_IsRotating +================ +*/ +void idMover::Event_IsRotating( void ) { + if ( physicsObj.GetAngularExtrapolationType() == EXTRAPOLATION_NONE ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idMover::WriteToSnapshot +================ +*/ +void idMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteBits( ( ( thinkFlags & TH_PHYSICS ) != 0 ), 1 ); + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( move.stage, 3 ); + msg.WriteBits( rot.stage, 3 ); + WriteBindToSnapshot( msg ); + WriteGUIToSnapshot( msg ); +} + +/* +================ +idMover::ReadFromSnapshot +================ +*/ +void idMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moveStage_t oldMoveStage = move.stage; + moveStage_t oldRotStage = rot.stage; + + // sync down the TH_PHYSICS flag for movers, now that we skip ClientPredictionThink when thinkFlags == 0 and no longer force TH_PHYSICS on + // movers still have prediction issues though, they predict a stop and clear TH_PHYSICS too early + bool physics_on = ( msg.ReadBits( 1 ) != 0 ); + if ( physics_on ) { + thinkFlags |= TH_PHYSICS; + } else { + thinkFlags &= ~TH_PHYSICS; + } + + physicsObj.ReadFromSnapshot( msg ); + move.stage = (moveStage_t) msg.ReadBits( 3 ); + rot.stage = (moveStage_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + ReadGUIFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( move.stage != oldMoveStage ) { + UpdateMoveSound( oldMoveStage ); + } + if ( rot.stage != oldRotStage ) { + UpdateRotationSound( oldRotStage ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover::SetPortalState +================ +*/ +void idMover::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +// RAVEN BEGIN +// abahr: +void idMover::Event_GetSplineEntity() { + idThread::ReturnEntity( splineEnt.GetEntity() ); +} + +CLASS_STATES_DECLARATION( idMover ) + STATE( "Accel", idMover::State_Accel ) + STATE( "Linear", idMover::State_Linear ) + STATE( "Decel", idMover::State_Decel ) +END_CLASS_STATES + +// mekberg: spline states +/* +================ +idMover::State_Accel +================ +*/ +stateResult_t idMover::State_Accel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_accel", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + acceltime ) { + splineStateThread.SetState( "Linear" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Linear +================ +*/ +stateResult_t idMover::State_Linear( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StartSound( "snd_move", SND_CHANNEL_BODY, 0, false, NULL ); + } + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time - deceltime ) { + if ( deceltime ) { + splineStateThread.SetState( "Decel" ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idMover::State_Decel +================ +*/ +stateResult_t idMover::State_Decel( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + if ( !useIdleSound ) { + StopSound( SND_CHANNEL_BODY, false ); + } + StartSound( "snd_decel", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( gameLocal.time >= splineStartTime + move_time ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +// RAVEN END + +/* +=============================================================================== + + idSplinePath, holds a spline path to be used by an idMover + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger +const idEventDef EV_IsActive( "isActive", "", 'd' ); +// RAVEN END + +CLASS_DECLARATION( idEntity, idSplinePath ) +// RAVEN BEGIN +// abahr: so we can toggle activated state via script or trigger + EVENT( EV_Activate, idSplinePath::Event_Toggle ) + EVENT( EV_IsActive, idSplinePath::Event_IsActive ) +// RAVEN END +END_CLASS + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::idSplinePath() { + sampledTimes = NULL; + numSamples = 0; +} + +/* +================ +idSplinePath::idSplinePath +================ +*/ +idSplinePath::~idSplinePath() { + if ( sampledTimes ) { + delete [] sampledTimes; + sampledTimes = NULL; + } +} + +/* +================ +idSplinePath::Spawn +================ +*/ +void idSplinePath::Spawn( void ) { +// RAVEN BEGIN +// abahr: + SetActive( spawnArgs.GetBool("start_active", "1") ); + SampleSpline ( ); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: +/* +================ +idSplinePath::FindTargets +================ +*/ +void idSplinePath::FindTargets() { + idEntity::FindTargets(); + + gameLocal.GetTargets( spawnArgs, backwardPathTargets, "target_reverse" ); + + // This alows us to seperate forward targets from backward targets + for( int ix = backwardPathTargets.Num() - 1; ix >= 0; --ix ) { + targets.Remove( backwardPathTargets[ix] ); + } +} + +/* +================ +idSplinePath::SortTargets +================ +*/ +int rvSortByActiveState( const void* a, const void* b ) { + idEntityPtr splineA; + idEntityPtr splineB; + + splineA = *(idEntityPtr*)a; + splineB = *(idEntityPtr*)b; + + return splineB->IsActive() - splineA->IsActive(); +} +int idSplinePath::SortTargets( idList< idEntityPtr >& list ) { + int numActive = 0; + idSplinePath* target = NULL; + + RemoveNullTargets(); + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByActiveState ); + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + target = static_cast( list[ix].GetEntity() ); + if( target->IsActive() ) { + numActive++; + } + } + + return numActive; +} + +int idSplinePath::SortTargets() { + return SortTargets( targets ); +} + +int idSplinePath::SortBackwardsTargets() { + return SortTargets( backwardPathTargets ); +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( idList< idEntityPtr >& list ) { + int i; + + for( i = list.Num() - 1; i >= 0; i-- ) { + if ( !list[ i ].GetEntity() ) { + list.RemoveIndex( i ); + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const { + idEntity *ent; + int i, j; + + for( i = 0; i < list.Num(); i++ ) { + ent = list[ i ].GetEntity(); + if ( !ent ) { + continue; + } + if ( ent->RespondsTo( EV_Activate ) || ent->HasSignal( SIG_TRIGGER ) ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, activator ); + } + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->Trigger( gameLocal.time ); + } + } + } +} + +/* +================ +idSplinePath::RemoveNullTargets +================ +*/ +void idSplinePath::RemoveNullTargets( void ) { + RemoveNullTargets( targets ); + RemoveNullTargets( backwardPathTargets ); +} + +/* +============================== +idSplinePath::ActivateTargets + +"activator" should be set to the entity that initiated the firing. +============================== +*/ +void idSplinePath::ActivateTargets( idEntity *activator ) const { + ActivateTargets( activator, targets ); + ActivateTargets( activator, backwardPathTargets ); +} + +/* +================ +idSplinePath::Save +================ +*/ +void idSplinePath::Save( idSaveGame *savefile ) const { + savefile->WriteBool( active ); +} + +/* +================ +idSplinePath::Restore +================ +*/ +void idSplinePath::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( active ); +} + +/* +================ +idSplinePath::Event_IsActive +================ +*/ +void idSplinePath::Event_IsActive() { + idThread::ReturnInt( IsActive() ); +} + +/* +================ +idSplinePath::SampleSpline +================ +*/ +void idSplinePath::SampleSpline ( void ) { + int i; + float splineLength; + idCurve_Spline* tempSpline = GetSpline ( ); + splineLength = tempSpline->GetLengthForTime( tempSpline->GetTime(tempSpline->GetNumValues() - 1) ); + + if ( splineLength > SPLINE_SAMPLE_RATE ) { + numSamples = int( splineLength / SPLINE_SAMPLE_RATE ); + sampledTimes = new float[ numSamples ]; + float stepSize = splineLength / ( numSamples - 1 ); + + for ( i = 0; i < numSamples; i++ ) { + float time = float( i * stepSize ); + if ( time >= splineLength ) { + time = splineLength; + } + sampledTimes[ i ] = tempSpline->GetTimeForLength ( time, 0.01f ); + } + } + SAFE_DELETE_PTR( tempSpline ); +} + +/* +================ +idSplinePath::GetSampledTime +================ +*/ +float idSplinePath::GetSampledTime ( float distance ) const { + if ( sampledTimes && distance >= 0.0f ) { + int lowIndex, highIndex; + float lerp = distance / SPLINE_SAMPLE_RATE; + int actualLowIndex = int( lerp ); + lowIndex = idMath::ClampInt ( 0, numSamples - 2, actualLowIndex ); + highIndex = lowIndex + 1; + lerp = lerp - idMath::Floor ( lerp ); + return ( actualLowIndex != lowIndex ) ? sampledTimes[ highIndex ] : idMath::Lerp( sampledTimes[ lowIndex ], sampledTimes[ highIndex ], lerp ); + } + return -1.0f; +} + +// RAVEN END + + +/* +=============================================================================== + +idElevator + +=============================================================================== +*/ +const idEventDef EV_PostArrival( "postArrival", NULL ); +const idEventDef EV_GotoFloor( "gotoFloor", "d" ); +const idEventDef EV_UpdateFloorInfo ( "updateFloorInfo", NULL ); + +CLASS_DECLARATION( idMover, idElevator ) + EVENT( EV_Activate, idElevator::Event_Activate ) + EVENT( EV_TeamBlocked, idElevator::Event_TeamBlocked ) + EVENT( EV_PostArrival, idElevator::Event_PostFloorArrival ) + EVENT( EV_GotoFloor, idElevator::Event_GotoFloor ) + EVENT( EV_Touch, idElevator::Event_Touch ) + EVENT( EV_UpdateFloorInfo, idElevator::Event_UpdateFloorInfo ) +END_CLASS + +/* +================ +idElevator::idElevator +================ +*/ +idElevator::idElevator( void ) { + state = INIT; + floorInfo.Clear(); + currentFloor = 0; + pendingFloor = 0; + lastFloor = 0; + controlsDisabled = false; + lastTouchTime = 0; + returnFloor = 0; + returnTime = 0; +} + +/* +================ +idElevator::Save +================ +*/ +void idElevator::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( (int)state ); + + savefile->WriteInt( floorInfo.Num() ); + for ( i = 0; i < floorInfo.Num(); i++ ) { + savefile->WriteVec3( floorInfo[ i ].pos ); + savefile->WriteString( floorInfo[ i ].door ); + savefile->WriteInt( floorInfo[ i ].floor ); + } + + savefile->WriteInt( currentFloor ); + savefile->WriteInt( pendingFloor ); + savefile->WriteInt( lastFloor ); + savefile->WriteBool( controlsDisabled ); +// savefile->WriteBool( waitingForPlayerFollowers ); + savefile->WriteFloat( returnTime ); + savefile->WriteInt( returnFloor ); + savefile->WriteInt( lastTouchTime ); +} + +/* +================ +idElevator::Restore +================ +*/ +void idElevator::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( (int &)state ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + floorInfo_s floor; + + savefile->ReadVec3( floor.pos ); + savefile->ReadString( floor.door ); + savefile->ReadInt( floor.floor ); + + floorInfo.Append( floor ); + } + + savefile->ReadInt( currentFloor ); + savefile->ReadInt( pendingFloor ); + savefile->ReadInt( lastFloor ); + savefile->ReadBool( controlsDisabled ); +// savefile->ReadBool( waitingForPlayerFollowers ); + savefile->ReadFloat( returnTime ); + savefile->ReadInt( returnFloor ); + savefile->ReadInt( lastTouchTime ); +} + +/* +================ +idElevator::Spawn +================ +*/ +void idElevator::Spawn( void ) { + lastFloor = 0; + currentFloor = 0; + pendingFloor = spawnArgs.GetInt( "floor", "1" ); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1]); + + returnTime = spawnArgs.GetFloat( "returnTime" ); + returnFloor = spawnArgs.GetInt( "returnFloor" ); + + UpdateFloorInfo ( ); + + lastTouchTime = 0; + state = INIT; + BecomeActive( TH_THINK | TH_PHYSICS ); + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); + controlsDisabled = false; +// waitingForPlayerFollowers = false; +} + +/* +============== +idElevator::UpdateFloorInfo +=============== +*/ +void idElevator::UpdateFloorInfo ( void ) { + int len1; + idStr str; + + floorInfo.Clear ( ); + + len1 = strlen( "floorPos_" ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "floorPos_", NULL ); + while( kv ) { + str = kv->GetKey().Right( kv->GetKey().Length() - len1 ); + floorInfo_s fi; + fi.floor = atoi( str ); + fi.door = spawnArgs.GetString( va( "floorDoor_%i", fi.floor ) ); + fi.pos = spawnArgs.GetVector( kv->GetKey() ); + floorInfo.Append( fi ); + kv = spawnArgs.MatchPrefix( "floorPos_", kv ); + } +} + +/* +============== +idElevator::Event_UpdateFloorInfo +=============== +*/ +void idElevator::Event_UpdateFloorInfo ( void ) { + UpdateFloorInfo ( ); +} + +/* +============== +idElevator::Event_Touch +=============== +*/ +void idElevator::Event_Touch( idEntity *other, trace_t *trace ) { + + if ( gameLocal.time < lastTouchTime + 2000 ) { + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + lastTouchTime = gameLocal.time; + + if ( thinkFlags & TH_PHYSICS ) { + return; + } + + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, 0.25f, triggerFloor ); + } +} + +/* +================ +idElevator::Think +================ +*/ +void idElevator::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idDoor *doorent = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( state == INIT ) { + state = IDLE; + if ( doorent ) { + doorent->BindTeam( this ); + doorent->spawnArgs.Set( "snd_open", "" ); + doorent->spawnArgs.Set( "snd_close", "" ); + doorent->spawnArgs.Set( "snd_opened", "" ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + idDoor *door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->SetCompanion( doorent ); + } + } + + Event_GotoFloor( pendingFloor ); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + +// RAVEN BEGIN +// bdube: provide floor information to status guis + if ( floorInfo.Num ( ) > 0 ) { + int j; + // Guis on the elevator are considered status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI && renderEntity.gui[j]; j++ ) { + InitStatusGui ( renderEntity.gui[j] ); + } + + // Initialize all the status guis of the elevator + const idKeyValue* kv; + for ( kv = spawnArgs.MatchPrefix( "statusGui" ); kv; kv = spawnArgs.MatchPrefix( "statusGui", kv ) ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( !ent || !ent->GetRenderEntity() ) { + continue; + } + for ( j = 0; j < MAX_RENDERENTITY_GUI && ent->GetRenderEntity()->gui[j]; j++ ) { + InitStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + } +// RAVEN END + + } else if ( state == WAITING_ON_DOORS ) { + if ( doorent ) { + state = doorent->IsOpen() ? WAITING_ON_DOORS : IDLE; + } else { + state = IDLE; + } + if ( state == IDLE ) { + lastFloor = currentFloor; + currentFloor = pendingFloor; + floorInfo_s *fi = GetFloorInfo( currentFloor ); + if ( fi ) { + MoveToPos( fi->pos ); + } + } + } + RunPhysics(); + Present(); +} + +/* +================ +idElevator::Event_Activate +================ +*/ +void idElevator::Event_Activate( idEntity *activator ) { + int triggerFloor = spawnArgs.GetInt( "triggerFloor" ); + if ( spawnArgs.GetBool( "trigger" ) && triggerFloor != currentFloor ) { + Event_GotoFloor( triggerFloor ); + } +} + +/* +================ +idElevator::Event_TeamBlocked +================ +*/ +void idElevator::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + if ( blockedEntity == this ) { + Event_GotoFloor( lastFloor ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( blockedEntity && blockedEntity->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + // open the inner doors if one is blocked + idDoor *blocked = static_cast( blockedEntity ); + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && blocked->GetMoveMaster() == door->GetMoveMaster() ) { + door->SetBlocked(true); + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } + } +} + + +/* +=============== +idElevator::HandleSingleGuiCommand +=============== +*/ +bool idElevator::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( controlsDisabled ) { + return false; + } + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "changefloor" ) == 0 ) { + if ( src->ReadToken( &token ) ) { +// RAVEN BEGIN +// bdube: up and down floor commands + int newFloor; + if (!token.Cmp("up")) { + newFloor = currentFloor + 1; + } else if (!token.Cmp("down")) { + newFloor = currentFloor - 1; + } else { + newFloor = atoi( token ); + } +// RAVEN END + + if ( newFloor == currentFloor ) { + // open currentFloor and interior doors + OpenInnerDoor(); + OpenFloorDoor( currentFloor ); + } else { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door && door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, newFloor ); + } else { + ProcessEvent( &EV_GotoFloor, newFloor ); + } + } + return true; + } + } + + src->UnreadToken( &token ); + return false; +} + +/* +================ +idElevator::OpenFloorDoor +================ +*/ +void idElevator::OpenFloorDoor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( fi->door ); + if ( door ) { + door->Open(); + } + } +} + +/* +================ +idElevator::OpenInnerDoor +================ +*/ +void idElevator::OpenInnerDoor( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Open(); + } +} + +/* +================ +idElevator::GetFloorInfo +================ +*/ +floorInfo_s *idElevator::GetFloorInfo( int floor ) { + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == floor ) { + return &floorInfo[i]; + } + } + return NULL; +} + +/* +================ +idElevator::Event_GotoFloor +================ +*/ +void idElevator::Event_GotoFloor( int floor ) { + floorInfo_s *fi = GetFloorInfo( floor ); + if ( fi ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + if ( door->IsBlocked() || door->IsOpen() ) { + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + /* + if ( !gameLocal.isMultiplayer ) { + //FIXME: make sure player is my activator? + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( !player->GetGroundEntity() ) { + //player in air + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + if ( player->GetGroundElevator() == this ) { + idActor* actor = NULL; + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + if ( !actor->IsHidden() && actor->health > 0 && actor->IsType( idAI::GetClassType() ) ) { + if ( ((idAI*)(actor))->leader == player && !((idAI*)(actor))->move.fl.disabled && !((idAI*)(actor))->aifl.scripted ) { + if ( actor->GetGroundElevator( this ) != this ) { + waitingForPlayerFollowers = true; + //follower of player is not standing on me, don't move! + PostEventSec( &EV_GotoFloor, 0.5f, floor ); + return; + } + } + } + } + } else if ( waitingForPlayerFollowers ) { + //player got off, cancel + waitingForPlayerFollowers = false; + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + UpdateStatusGuis ( ); + return; + } + } + } + waitingForPlayerFollowers = false; + */ + DisableAllDoors(); + CloseAllDoors(); + state = WAITING_ON_DOORS; + pendingFloor = floor; + } +} + +/* +================ +idElevator::BeginMove +================ +*/ +void idElevator::BeginMove( idThread *thread ) { + controlsDisabled = true; + CloseAllDoors(); + DisableAllDoors(); + SetGuiStates( ( pendingFloor == 1 ) ? guiBinaryMoverStates[3] : guiBinaryMoverStates[2] ); + +// RAVEN BEGIN +// bdube: replaced with function + SetAASAreaState ( true ); + + idMover::BeginMove( thread ); + + UpdateStatusGuis ( ); +// RAVEN END +} + +/* +================ +idElevator::GetDoor +================ +*/ +idDoor *idElevator::GetDoor( const char *name ) { + idEntity *ent; + idEntity *master; + idDoor *doorEnt; + + doorEnt = NULL; + if ( name && *name ) { + ent = gameLocal.FindEntity( name ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( ent ); + master = doorEnt->GetMoveMaster(); + if ( master != doorEnt ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( master->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + doorEnt = static_cast( master ); + } else { + doorEnt = NULL; + } + } + } + } + + return doorEnt; +} + +/* +================ +idElevator::Event_PostFloorArrival +================ +*/ +void idElevator::Event_PostFloorArrival() { + OpenFloorDoor( currentFloor ); + OpenInnerDoor(); + SetGuiStates( ( currentFloor == 1 ) ? guiBinaryMoverStates[0] : guiBinaryMoverStates[1] ); + controlsDisabled = false; + if ( returnTime > 0.0f && returnFloor != currentFloor ) { + PostEventSec( &EV_GotoFloor, returnTime, returnFloor ); + } +} + +/* +================ +idElevator::DoneMoving +================ +*/ +void idElevator::DoneMoving( void ) { + idMover::DoneMoving(); + EnableProperDoors(); +// RAVEN BEGIN +// bdube: factored into a function + UpdateStatusGuis ( ); +// RAVEN END + if ( spawnArgs.GetInt( "pauseOnFloor", "-1" ) == currentFloor ) { + PostEventSec( &EV_PostArrival, spawnArgs.GetFloat( "pauseTime" ) ); + } else { + Event_PostFloorArrival(); + } + +// RAVEN BEGIN +// kfuller: we want an elevator to fire its targets when it reaches a floor + SetAASAreaState( false ); + + // Floor targets + if ( lastFloor != 0 ) { + const char* floorTarget = spawnArgs.GetString ( va("floorTarget_%d", currentFloor ) ); + if ( floorTarget && *floorTarget ) { + idEntity* ent = gameLocal.FindEntity ( floorTarget ); + if ( ent ) { + ent->ProcessEvent ( &EV_Activate, this ); + } + } + } + + if (spawnArgs.GetInt("fireTargetsAtFloor") == currentFloor) { + ActivateTargets(gameLocal.entities[ENTITYNUM_WORLD]); + spawnArgs.SetInt("fireTargetsAtFloor", -1); + } +// RAVEN END +} + +/* +================ +idElevator::CloseAllDoors +================ +*/ +void idElevator::CloseAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Close(); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Close(); + } + } +} + +/* +================ +idElevator::DisableAllDoors +================ +*/ +void idElevator::DisableAllDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( false ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( false ); + } + } +} + +/* +================ +idElevator::EnableProperDoors +================ +*/ +void idElevator::EnableProperDoors( void ) { + idDoor *door = GetDoor( spawnArgs.GetString( "innerdoor" ) ); + if ( door ) { + door->Enable( true ); + } + for ( int i = 0; i < floorInfo.Num(); i++ ) { + if ( floorInfo[i].floor == currentFloor ) { + door = GetDoor( floorInfo[i].door ); + if ( door ) { + door->Enable( true ); + break; + } + } + } +} + +// RAVEN BEGIN +// bdube: more advanced status gui control + +/* +================ +idElevator::SetAASAreaState +================ +*/ +void idElevator::SetAASAreaState ( bool enable ) { + idEntity* ents[16]; + int numEnts; + numEnts = gameLocal.EntitiesTouchingBounds ( this, physicsObj.GetAbsBounds(), CONTENTS_AAS_OBSTACLE, ents, 16 ); + for ( numEnts--; numEnts >= 0; numEnts -- ) { + idFuncAASObstacle* obstacle = dynamic_cast(ents[numEnts]); + if ( obstacle ) { + obstacle->SetState ( enable ); + } + } +} + +/* +================ +idElevator::InitStatusGui +================ +*/ +void idElevator::InitStatusGui ( idUserInterface* gui ) { + int floor; + int topFloor; + int bottomFloor; + + topFloor = -1; + bottomFloor = 9999; + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + topFloor = Max( floorInfo[floor].floor, topFloor ); + bottomFloor = Min( floorInfo[floor].floor, bottomFloor ); + } + + gui->SetStateInt ( "topFloor", topFloor ); + gui->SetStateInt ( "bottomFloor", bottomFloor ); + + for ( floor = 0; floor < floorInfo.Num(); floor ++ ) { + idStr keySrc; + idStr keyDest; + gui->SetStateInt ( va("floorNumber_%d", floor ), floorInfo[floor].floor ); + keySrc = va("floorName_%d", floorInfo[floor].floor ); + keyDest = va("floorName_%d", floor ); + gui->SetStateString ( keyDest, spawnArgs.GetString ( keySrc, va("%d", floorInfo[floor].floor ) ) ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGui +================ +*/ +void idElevator::UpdateStatusGui ( idUserInterface* gui ) { + idStr floorName; + floorName = va("floorName_%d", currentFloor ); + gui->SetStateInt ( "floor", (physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE) ? currentFloor : lastFloor ); + gui->SetStateInt ( "floorNext", currentFloor ); + gui->SetStateString( "floorName", spawnArgs.GetString ( floorName, va("%d", currentFloor ) ) ); + gui->StateChanged( gameLocal.time, true ); + + // mekberg: trigger all status guis if we moved the elevator from another gui. + if ( lastFloor && !( physicsObj.GetLinearExtrapolationType() == EXTRAPOLATION_NONE ) ) { + gui->HandleNamedEvent( "triggerGui" ); + } + + gui->HandleNamedEvent ( "updateFloor" ); +} + +/* +================ +idElevator::UpdateStatusGuis +================ +*/ +void idElevator::UpdateStatusGuis ( void ) { + int j; + + // Treat the guis on the elevator as status guis + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( renderEntity.gui[ j ] ) { + UpdateStatusGui ( renderEntity.gui[ j ] ); + } + } + + // All entities linked as status guis should get updated + const idKeyValue *kv = spawnArgs.MatchPrefix( "statusGui" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + UpdateStatusGui ( ent->GetRenderEntity()->gui[j] ); + } + } + ent->UpdateVisuals(); + } + kv = spawnArgs.MatchPrefix( "statusGui", kv ); + } +} +// RAVEN END + +/* +=============================================================================== + +idMover_Binary + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" + +=============================================================================== +*/ + +const idEventDef EV_Mover_ReturnToPos1( "", NULL ); +const idEventDef EV_Mover_MatchTeam( "", "dd" ); +const idEventDef EV_Mover_Enable( "enable", NULL ); +const idEventDef EV_Mover_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idMover_Binary ) + EVENT( EV_FindGuiTargets, idMover_Binary::Event_FindGuiTargets ) + EVENT( EV_Thread_SetCallback, idMover_Binary::Event_SetCallback ) + EVENT( EV_Mover_ReturnToPos1, idMover_Binary::Event_ReturnToPos1 ) + EVENT( EV_Activate, idMover_Binary::Event_Use_BinaryMover ) + EVENT( EV_ReachedPos, idMover_Binary::Event_Reached_BinaryMover ) + EVENT( EV_Mover_MatchTeam, idMover_Binary::Event_MatchActivateTeam ) + EVENT( EV_Mover_Enable, idMover_Binary::Event_Enable ) + EVENT( EV_Mover_Disable, idMover_Binary::Event_Disable ) + EVENT( EV_Mover_OpenPortal, idMover_Binary::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idMover_Binary::Event_ClosePortal ) + EVENT( EV_Mover_InitGuiTargets, idMover_Binary::Event_InitGuiTargets ) +END_CLASS + +/* +================ +idMover_Binary::idMover_Binary() +================ +*/ +idMover_Binary::idMover_Binary() { + pos1.Zero(); + pos2.Zero(); + moverState = MOVER_POS1; + moveMaster = NULL; + activateChain = NULL; + soundPos1 = 0; + sound1to2 = 0; + sound2to1 = 0; + soundPos2 = 0; + soundLoop = 0; + wait = 0.0f; + damage = 0.0f; + duration = 0; + accelTime = 0; + decelTime = 0; + activatedBy = this; + stateStartTime = 0; + team.Clear(); + enabled = false; + deferedOpen = false; + move_thread = 0; + updateStatus = 0; + areaPortal = 0; + blocked = false; + fl.networkSync = true; +} + +/* +================ +idMover_Binary::~idMover_Binary +================ +*/ +idMover_Binary::~idMover_Binary() { + idMover_Binary *mover; + + // if this is the mover master + if ( this == moveMaster ) { + // make the next mover in the chain the move master + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + mover->moveMaster = this->activateChain; + } + } + else { + // remove mover from the activate chain + for ( mover = moveMaster; mover; mover = mover->activateChain ) { + if ( mover->activateChain == this ) { + mover->activateChain = this->activateChain; + break; + } + } + } + + SetPhysics( NULL ); +} + +/* +================ +idMover_Binary::Save +================ +*/ +void idMover_Binary::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteVec3( pos1 ); + savefile->WriteVec3( pos2 ); + savefile->WriteInt( (moverState_t)moverState ); + + savefile->WriteObject( moveMaster ); + savefile->WriteObject( activateChain ); + + savefile->WriteInt( soundPos1 ); + savefile->WriteInt( sound1to2 ); + savefile->WriteInt( sound2to1 ); + savefile->WriteInt( soundPos2 ); + savefile->WriteInt( soundLoop ); + + savefile->WriteFloat( wait ); + savefile->WriteFloat( damage ); + + savefile->WriteInt( duration ); + savefile->WriteInt( accelTime ); + savefile->WriteInt( decelTime ); + + activatedBy.Save( savefile ); + + savefile->WriteInt( stateStartTime ); + savefile->WriteString( team ); + savefile->WriteBool( enabled ); + savefile->WriteBool( deferedOpen ); + + savefile->WriteInt( move_thread ); + savefile->WriteInt( updateStatus ); + + savefile->WriteInt( buddies.Num() ); + for ( i = 0; i < buddies.Num(); i++ ) { + savefile->WriteString( buddies[ i ] ); + } + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( areaPortal ); + if ( areaPortal ) { + savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) ); + } + savefile->WriteBool( blocked ); + + savefile->WriteInt( guiTargets.Num() ); + for( i = 0; i < guiTargets.Num(); i++ ) { + guiTargets[ i ].Save( savefile ); + } +} + +/* +================ +idMover_Binary::Restore +================ +*/ +void idMover_Binary::Restore( idRestoreGame *savefile ) { + int i, num, portalState; + idStr temp; + + savefile->ReadVec3( pos1 ); + savefile->ReadVec3( pos2 ); + savefile->ReadInt( (int &)moverState ); + + savefile->ReadObject( reinterpret_cast( moveMaster ) ); + savefile->ReadObject( reinterpret_cast( activateChain ) ); + + savefile->ReadInt( soundPos1 ); + savefile->ReadInt( sound1to2 ); + savefile->ReadInt( sound2to1 ); + savefile->ReadInt( soundPos2 ); + savefile->ReadInt( soundLoop ); + + savefile->ReadFloat( wait ); + savefile->ReadFloat( damage ); + + savefile->ReadInt( duration ); + savefile->ReadInt( accelTime ); + savefile->ReadInt( decelTime ); + + activatedBy.Restore( savefile ); + + savefile->ReadInt( stateStartTime ); + + savefile->ReadString( team ); + savefile->ReadBool( enabled ); + savefile->ReadBool( deferedOpen ); + + savefile->ReadInt( move_thread ); + savefile->ReadInt( updateStatus ); + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( temp ); + buddies.Append( temp ); + } + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( areaPortal ); + if ( areaPortal ) { + savefile->ReadInt( portalState ); + gameLocal.SetPortalState( areaPortal, portalState ); + } + savefile->ReadBool( blocked ); + + guiTargets.Clear(); + savefile->ReadInt( num ); + guiTargets.SetNum( num ); + for( i = 0; i < num; i++ ) { + guiTargets[ i ].Restore( savefile ); + } +} + +/* +================ +idMover_Binary::Spawn + +Base class for all movers. + +"wait" wait before returning (3 default, -1 = never return) +"speed" movement speed +================ +*/ +void idMover_Binary::Spawn( void ) { + idEntity *ent; + const char *temp; + + move_thread = 0; + enabled = true; + areaPortal = 0; + + activateChain = NULL; + + spawnArgs.GetFloat( "wait", "0", wait ); + + spawnArgs.GetInt( "updateStatus", "0", updateStatus ); + + const idKeyValue *kv = spawnArgs.MatchPrefix( "buddy", NULL ); + while( kv ) { + buddies.Append( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "buddy", kv ); + } + + spawnArgs.GetString( "team", "", &temp ); + team = temp; + + if ( !team.Length() ) { + ent = this; + } else { + // find the first entity spawned on this team (which could be us) + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idMover_Binary::Type ) && !idStr::Icmp( static_cast(ent)->team.c_str(), temp ) ) { + break; + } + } + if ( !ent ) { + ent = this; + } + } + moveMaster = static_cast(ent); + + // create a physics team for the binary mover parts + if ( ent != this ) { + JoinTeam( ent ); + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( moveMaster != this ) { + JoinActivateTeam( moveMaster ); + } + + idBounds soundOrigin; + idMover_Binary *slave; + + soundOrigin.Clear(); + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + soundOrigin += slave->GetPhysics()->GetAbsBounds(); + } + moveMaster->refSound.origin = soundOrigin.GetCenter(); + + if ( spawnArgs.MatchPrefix( "guiTarget" ) ) { + if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { + PostEventMS( &EV_FindGuiTargets, 0 ); + } else { + // not during spawn, so it's ok to get the targets + FindGuiTargets(); + } + } +} + +/* +=============== +idMover_Binary::GetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void idMover_Binary::GetMovedir( float angle, idVec3 &movedir ) { + if ( angle == -1 ) { + movedir.Set( 0, 0, 1 ); + } else if ( angle == -2 ) { + movedir.Set( 0, 0, -1 ); + } else { + movedir = idAngles( 0, angle, 0 ).ToForward(); + } +} + +/* +================ +idMover_Binary::Event_SetCallback +================ +*/ +void idMover_Binary::Event_SetCallback( void ) { + if ( ( moverState == MOVER_1TO2 ) || ( moverState == MOVER_2TO1 ) ) { + move_thread = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +=============== +idMover_Binary::UpdateMoverSound +=============== +*/ +void idMover_Binary::UpdateMoverSound( moverState_t state ) { + if ( moveMaster == this ) { + switch( state ) { + case MOVER_POS1: + break; + case MOVER_POS2: + break; + case MOVER_1TO2: + StartSound( "snd_open", SND_CHANNEL_ANY, 0, false, NULL ); + break; + case MOVER_2TO1: + StartSound( "snd_close", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } + } +} + +/* +=============== +idMover_Binary::SetMoverState +=============== +*/ +void idMover_Binary::SetMoverState( moverState_t newstate, int time ) { + idVec3 delta; + + moverState = newstate; + move_thread = 0; + + UpdateMoverSound( newstate ); + + stateStartTime = time; + switch( moverState ) { + case MOVER_POS1: { + Signal( SIG_MOVER_POS1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos1, vec3_origin, vec3_origin ); + break; + } + case MOVER_POS2: { + Signal( SIG_MOVER_POS2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, time, 0, pos2, vec3_origin, vec3_origin ); + break; + } + case MOVER_1TO2: { + Signal( SIG_MOVER_1TO2 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos1, ( pos2 - pos1 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos1, pos2 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + case MOVER_2TO1: { + Signal( SIG_MOVER_2TO1 ); + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, time, duration, pos2, ( pos1 - pos2 ) * 1000.0f / duration, vec3_origin ); + if ( accelTime != 0 || decelTime != 0 ) { + physicsObj.SetLinearInterpolation( time, accelTime, decelTime, duration, pos2, pos1 ); + } else { + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, pos1, pos2 ); + } + break; + } + } +} + +/* +================ +idMover_Binary::MatchActivateTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void idMover_Binary::MatchActivateTeam( moverState_t newstate, int time ) { + idMover_Binary *slave; + deferedOpen = false; + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->SetMoverState( newstate, time ); + } +} + +/* +================ +idMover_Binary::Enable +================ +*/ +void idMover_Binary::Enable( bool b ) { + enabled = b; +} + +/* +================ +idMover_Binary::Event_MatchActivateTeam +================ +*/ +void idMover_Binary::Event_MatchActivateTeam( moverState_t newstate, int time ) { + MatchActivateTeam( newstate, time ); +} + +/* +================ +idMover_Binary::BindTeam + +All entities in a mover team will be bound +================ +*/ +void idMover_Binary::BindTeam( idEntity *bindTo ) { + idMover_Binary *slave; + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->Bind( bindTo, true ); + } +} + +/* +================ +idMover_Binary::JoinActivateTeam + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::JoinActivateTeam( idMover_Binary *master ) { + this->activateChain = master->activateChain; + master->activateChain = this; +} + +/* +================ +idMover_Binary::Event_Enable + +Set all entities in a mover team to be enabled +================ +*/ +void idMover_Binary::Event_Enable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_Disable + +Set all entities in a mover team to be disabled +================ +*/ +void idMover_Binary::Event_Disable( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->Enable( false ); + } +} + +/* +================ +idMover_Binary::Event_OpenPortal + +Sets the portal associtated with this mover to be open +================ +*/ +void idMover_Binary::Event_OpenPortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( slave->areaPortal ) { + slave->SetPortalState( true ); + } + } +} + +/* +================ +idMover_Binary::Event_ClosePortal + +Sets the portal associtated with this mover to be closed +================ +*/ +void idMover_Binary::Event_ClosePortal( void ) { + idMover_Binary *slave; + + for ( slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + if ( !slave->IsHidden() ) { + if ( slave->areaPortal ) { + slave->SetPortalState( false ); + } + } + } +} + +/* +================ +idMover_Binary::Event_ReturnToPos1 +================ +*/ +void idMover_Binary::Event_ReturnToPos1( void ) { + MatchActivateTeam( MOVER_2TO1, gameLocal.time ); +} + +/* +================ +idMover_Binary::Event_Reached_BinaryMover +================ +*/ +void idMover_Binary::Event_Reached_BinaryMover( void ) { + + if ( moverState == MOVER_1TO2 ) { + // reached pos2 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + if ( moveMaster == this ) { + StartSound( "snd_opened", SND_CHANNEL_ANY, 0, false, NULL ); + } + + SetMoverState( MOVER_POS2, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS2] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 1 ); +// RAVEN END + + if ( enabled && wait >= 0 && !spawnArgs.GetBool( "toggle" ) ) { + // return to pos1 after a delay + PostEventSec( &EV_Mover_ReturnToPos1, wait ); + } + + // fire targets + ActivateTargets( moveMaster->GetActivator() ); + + SetBlocked ( false ); + } else if ( moverState == MOVER_2TO1 ) { + // reached pos1 + idThread::ObjectMoveDone( move_thread, this ); + move_thread = 0; + + SetMoverState( MOVER_POS1, gameLocal.time ); + + SetGuiStates( guiBinaryMoverStates[MOVER_POS1] ); + +// RAVEN BEGIN +// jdischler: this wasn't actually doing anything, anyway +// UpdateBuddies( 0 ); +// RAVEN END + + // close areaportals + if ( moveMaster == this ) { +// RAVEN BEGIN +// kfuller: added "snd_closed" + StartSound( "snd_closed", SND_CHANNEL_ANY, 0, false, NULL ); +// RAVEN END + ProcessEvent( &EV_Mover_ClosePortal ); + } + + if ( enabled && wait >= 0 && spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, wait, this ); + } + + SetBlocked ( false ); + } else { + gameLocal.Error( "Event_Reached_BinaryMover: bad moverState" ); + } +} + +/* +================ +idMover_Binary::GotoPosition1 +================ +*/ +void idMover_Binary::GotoPosition1( void ) { + idMover_Binary *slave; + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition1(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + if ( ( moverState == MOVER_POS1 ) || ( moverState == MOVER_2TO1 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS2 ) { + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + } + if ( !spawnArgs.GetBool( "toggle" ) ) { + ProcessEvent( &EV_Mover_ReturnToPos1 ); + } + + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_2TO1, physicsObj.GetTime() - partial ); + // if already at at position 1 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::GotoPosition2 +================ +*/ +void idMover_Binary::GotoPosition2( void ) { + int partial; + + // only the master should control this + if ( moveMaster != this ) { + moveMaster->GotoPosition2(); + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + + if ( ( moverState == MOVER_POS2 ) || ( moverState == MOVER_1TO2 ) ) { + // already there, or on the way + return; + } + + if ( moverState == MOVER_POS1 ) { + MatchActivateTeam( MOVER_1TO2, gameLocal.time ); + + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + return; + } + + + // only partway up before reversing + if ( moverState == MOVER_2TO1 ) { + // use the physics times because this might be executed during the physics simulation + partial = physicsObj.GetLinearEndTime() - physicsObj.GetTime(); + assert( partial >= 0 ); + if ( partial < 0 ) { + partial = 0; + } + MatchActivateTeam( MOVER_1TO2, physicsObj.GetTime() - partial ); + // if already at at position 2 (partial == duration) execute the reached event + if ( partial >= duration ) { + Event_Reached_BinaryMover(); + } + } +} + +/* +================ +idMover_Binary::UpdateBuddies +================ +*/ +void idMover_Binary::UpdateBuddies( int val ) +{ +// RAVEN BEGIN +// jdischler: was using update status but that was never getting set anyway. +// Additionally, shaderparm_mode was never getting set on the mover itself, which creates +// extra work for the designers. + int c = buddies.Num(); + for ( int i = 0; i < c; i++ ) { + idEntity *buddy = gameLocal.FindEntity( buddies[i] ); + if ( buddy ) { + buddy->SetShaderParm( SHADERPARM_MODE, val ); + buddy->UpdateVisuals(); + } + } + // Update the mover itself, too. + SetShaderParm( SHADERPARM_MODE, val ); + UpdateVisuals(); +// RAVEN END +} + +/* +================ +idMover_Binary::SetGuiStates +================ +*/ +void idMover_Binary::SetGuiStates( const char *state ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", state ); + } + + idMover_Binary *mb = activateChain; + while( mb ) { + if ( mb->guiTargets.Num() ) { + mb->SetGuiState( "movestate", state ); + } + mb = mb->activateChain; + } +} + +/* +================ +idMover_Binary::Use_BinaryMover +================ +*/ +void idMover_Binary::Use_BinaryMover( idEntity *activator ) { + // only the master should be used + if ( moveMaster != this ) { + moveMaster->Use_BinaryMover( activator ); + return; + } + + if ( !enabled ) { + return; + } + + activatedBy = activator; + + if ( moverState == MOVER_POS1 && !deferedOpen) { + + float openWait = spawnArgs.GetFloat( "openWait", "0" ); + deferedOpen = true; + PostEventMS( &EV_Mover_MatchTeam, SEC2MS(openWait), MOVER_1TO2, SEC2MS(openWait) + gameLocal.time ); + + // TODO: might want to delay these as well if openWait is present? + SetGuiStates( guiBinaryMoverStates[MOVER_1TO2] ); + // open areaportal + ProcessEvent( &EV_Mover_OpenPortal ); + + // any things we should trigger when the door is first asked to open? + // NOTE: with openWait, it's possible (and desirable as per aweldon's request) + // that this will be called before the door actually opens. This is specifically + // used by the rotate/lock doors...the triggering below starts another entity rotating + // and onWait is used to offset the actual open so the rotating piece has time to work... + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOnOpen" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOnOpen", kv ); + } + + return; + } + + // if all the way up, just delay before coming down + if ( moverState == MOVER_POS2 ) { + idMover_Binary *slave; + + if ( wait == -1 ) { + return; + } + + SetGuiStates( guiBinaryMoverStates[MOVER_2TO1] ); + + for ( slave = this; slave != NULL; slave = slave->activateChain ) { + slave->CancelEvents( &EV_Mover_ReturnToPos1 ); + slave->PostEventSec( &EV_Mover_ReturnToPos1, spawnArgs.GetBool( "toggle" ) ? 0 : wait ); + } + return; + } + + // only partway down before reversing + if ( moverState == MOVER_2TO1 ) { + GotoPosition2(); + return; + } + + // only partway up before reversing + if ( moverState == MOVER_1TO2 ) { + GotoPosition1(); + return; + } +} + +/* +================ +idMover_Binary::Event_Use_BinaryMover +================ +*/ +void idMover_Binary::Event_Use_BinaryMover( idEntity *activator ) { + Use_BinaryMover( activator ); +} + +/* +================ +idMover_Binary::PreBind +================ +*/ +void idMover_Binary::PreBind( void ) { + pos1 = GetWorldCoordinates( pos1 ); + pos2 = GetWorldCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::PostBind +================ +*/ +void idMover_Binary::PostBind( void ) { + pos1 = GetLocalCoordinates( pos1 ); + pos2 = GetLocalCoordinates( pos2 ); +} + +/* +================ +idMover_Binary::FindGuiTargets +================ +*/ +void idMover_Binary::FindGuiTargets( void ) { + gameLocal.GetTargets( spawnArgs, guiTargets, "guiTarget" ); +} + +/* +============================== +idMover_Binary::SetGuiState + +key/val will be set to any renderEntity->gui's on the list +============================== +*/ +void idMover_Binary::SetGuiState( const char *key, const char *val ) const { + int i; + + for( i = 0; i < guiTargets.Num(); i++ ) { + idEntity *ent = guiTargets[ i ].GetEntity(); + if ( ent ) { + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time, true ); + } + } + ent->UpdateVisuals(); + } + } +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_FindGuiTargets( void ) { + FindGuiTargets(); +} + +/* +================ +idMover_Binary::Event_InitGuiTargets +================ +*/ +void idMover_Binary::Event_InitGuiTargets( void ) { + if ( guiTargets.Num() ) { + SetGuiState( "movestate", guiBinaryMoverStates[MOVER_POS1] ); + } +} + +/* +================ +idMover_Binary::InitSpeed + +pos1, pos2, and speed are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ) { + idVec3 move; + float distance; + float speed; + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + speed = mspeed ? mspeed : 100; + + // calculate time to reach second position from speed + move = pos2 - pos1; + distance = move.Length(); + duration = idPhysics::SnapTimeToPhysicsFrame( distance * 1000 / speed ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::InitTime + +pos1, pos2, and time are passed in so the movement delta can be calculated +================ +*/ +void idMover_Binary::InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ) { + + pos1 = mpos1; + pos2 = mpos2; + + accelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( maccelTime ) ); + decelTime = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mdecelTime ) ); + + duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( mtime ) ); + if ( duration <= 0 ) { + duration = 1; + } + + moverState = MOVER_POS1; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, pos1, vec3_origin, vec3_origin ); + physicsObj.SetLinearInterpolation( 0, 0, 0, 0, vec3_origin, vec3_origin ); + SetOrigin( pos1 ); + + PostEventMS( &EV_Mover_InitGuiTargets, 0 ); +} + +/* +================ +idMover_Binary::SetBlocked +================ +*/ +void idMover_Binary::SetBlocked( bool b ) { + for ( idMover_Binary *slave = moveMaster; slave != NULL; slave = slave->activateChain ) { + slave->blocked = b; + if ( b ) { + const idKeyValue *kv = slave->spawnArgs.MatchPrefix( "triggerBlocked" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = slave->spawnArgs.MatchPrefix( "triggerBlocked", kv ); + } + } + } +} + +/* +================ +idMover_Binary::IsBlocked +================ +*/ +bool idMover_Binary::IsBlocked( void ) { + return blocked; +} + +/* +================ +idMover_Binary::GetActivator +================ +*/ +idEntity *idMover_Binary::GetActivator( void ) const { + return activatedBy.GetEntity(); +} + +/* +================ +idMover_Binary::WriteToSnapshot +================ +*/ +void idMover_Binary::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + msg.WriteBits( moverState, 3 ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Binary::ReadFromSnapshot +================ +*/ +void idMover_Binary::ReadFromSnapshot( const idBitMsgDelta &msg ) { + moverState_t oldMoverState = moverState; + + physicsObj.ReadFromSnapshot( msg ); + moverState = (moverState_t) msg.ReadBits( 3 ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + if ( moverState != oldMoverState ) { + UpdateMoverSound( moverState ); + } + UpdateVisuals(); + } +} + +/* +================ +idMover_Binary::SetPortalState +================ +*/ +void idMover_Binary::SetPortalState( bool open ) { + assert( areaPortal ); + gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); +} + +/* +=============================================================================== + +idDoor + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +const idEventDef EV_Door_StartOpen( "", NULL ); +const idEventDef EV_Door_SpawnDoorTrigger( "", NULL ); +const idEventDef EV_Door_SpawnSoundTrigger( "", NULL ); +const idEventDef EV_Door_Open( "open", NULL ); +const idEventDef EV_Door_Close( "close", NULL ); +const idEventDef EV_Door_Lock( "lock", "d" ); +const idEventDef EV_Door_IsOpen( "isOpen", NULL, 'f' ); +const idEventDef EV_Door_IsLocked( "isLocked", NULL, 'f' ); + +CLASS_DECLARATION( idMover_Binary, idDoor ) + EVENT( EV_TeamBlocked, idDoor::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idDoor::Event_PartBlocked ) + EVENT( EV_Touch, idDoor::Event_Touch ) + EVENT( EV_Activate, idDoor::Event_Activate ) + EVENT( EV_Door_StartOpen, idDoor::Event_StartOpen ) + EVENT( EV_Door_SpawnDoorTrigger, idDoor::Event_SpawnDoorTrigger ) + EVENT( EV_Door_SpawnSoundTrigger, idDoor::Event_SpawnSoundTrigger ) + EVENT( EV_Door_Open, idDoor::Event_Open ) + EVENT( EV_Door_Close, idDoor::Event_Close ) + EVENT( EV_Door_Lock, idDoor::Event_Lock ) + EVENT( EV_Door_IsOpen, idDoor::Event_IsOpen ) + EVENT( EV_Door_IsLocked, idDoor::Event_Locked ) + EVENT( EV_ReachedPos, idDoor::Event_Reached_BinaryMover ) + EVENT( EV_SpectatorTouch, idDoor::Event_SpectatorTouch ) + EVENT( EV_Mover_OpenPortal, idDoor::Event_OpenPortal ) + EVENT( EV_Mover_ClosePortal, idDoor::Event_ClosePortal ) +// RAVEN BEGIN +// abahr: + EVENT( EV_Mover_ReturnToPos1, idDoor::Event_ReturnToPos1 ) +// RAVEN END +END_CLASS + +/* +================ +idDoor::idDoor +================ +*/ +idDoor::idDoor( void ) { + triggersize = 1.0f; + crusher = false; + noTouch = false; + aas_area_closed = false; + buddyStr.Clear(); + trigger = NULL; + sndTrigger = NULL; + nextSndTriggerTime = 0; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); + requires.Clear(); + removeItem = 0; + syncLock.Clear(); + companionDoor = NULL; + normalAxisIndex = 0; +} + +/* +================ +idDoor::~idDoor +================ +*/ +idDoor::~idDoor( void ) { + if ( trigger ) { + delete trigger; + } + if ( sndTrigger ) { + delete sndTrigger; + } +} + +/* +================ +idDoor::Save +================ +*/ +void idDoor::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( triggersize ); + savefile->WriteBool( crusher ); + savefile->WriteBool( noTouch ); + savefile->WriteBool( aas_area_closed ); + savefile->WriteString( buddyStr ); + + savefile->WriteClipModel( trigger ); + savefile->WriteClipModel( sndTrigger ); + savefile->WriteInt( nextSndTriggerTime ); + + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); + + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteString( syncLock ); + savefile->WriteInt( normalAxisIndex ); + + savefile->WriteObject( companionDoor ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Save( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Restore +================ +*/ +void idDoor::Restore( idRestoreGame *savefile ) { + + savefile->ReadFloat( triggersize ); + savefile->ReadBool( crusher ); + savefile->ReadBool( noTouch ); + savefile->ReadBool( aas_area_closed ); + SetAASAreaState( aas_area_closed ); + savefile->ReadString( buddyStr ); + + savefile->ReadClipModel( trigger ); + savefile->ReadClipModel( sndTrigger ); + savefile->ReadInt( nextSndTriggerTime ); + + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); + + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadString( syncLock ); + savefile->ReadInt( normalAxisIndex ); + + savefile->ReadObject( reinterpret_cast( companionDoor ) ); + +// RAVEN BEGIN +// abahr: + doorFrameController.Restore( savefile ); +// RAVEN END +} + +/* +================ +idDoor::Spawn +================ +*/ +void idDoor::Spawn( void ) { + idVec3 abs_movedir; + float distance; + idVec3 size; + idVec3 movedir; + float dir; + float lip; + bool start_open; + float time; + float speed; + + // get the direction to move + if ( !spawnArgs.GetFloat( "movedir", "0", dir ) ) { + // no movedir, so angle defines movement direction and not orientation, + // a la oldschool Quake + SetAngles( ang_zero ); + spawnArgs.GetFloat( "angle", "0", dir ); + } + GetMovedir( dir, movedir ); + + // default speed of 400 + spawnArgs.GetFloat( "speed", "400", speed ); + + // default wait of 2 seconds + spawnArgs.GetFloat( "wait", "3", wait ); + + // default lip of 8 units + spawnArgs.GetFloat( "lip", "8", lip ); + + // by default no damage + spawnArgs.GetFloat( "damage", "0", damage ); + + // trigger size + spawnArgs.GetFloat( "triggersize", "120", triggersize ); + + spawnArgs.GetBool( "crusher", "0", crusher ); + spawnArgs.GetBool( "start_open", "0", start_open ); + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // expects syncLock to be a door that must be closed before this door will open + spawnArgs.GetString( "syncLock", "", syncLock ); + + spawnArgs.GetString( "buddy", "", buddyStr ); + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + + // ever separate piece of a door is considered solid when other team mates push entities + fl.solidForTeam = true; + + // first position at start + pos1 = GetPhysics()->GetOrigin(); + + // calculate second position + abs_movedir[0] = idMath::Fabs( movedir[ 0 ] ); + abs_movedir[1] = idMath::Fabs( movedir[ 1 ] ); + abs_movedir[2] = idMath::Fabs( movedir[ 2 ] ); + size = GetPhysics()->GetAbsBounds()[1] - GetPhysics()->GetAbsBounds()[0]; + distance = ( abs_movedir * size ) - lip; + pos2 = pos1 + distance * movedir; + + // if "start_open", reverse position 1 and 2 + if ( start_open ) { + // post it after EV_SpawnBind + PostEventMS( &EV_Door_StartOpen, 1 ); + } + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } + + if ( moveMaster == this ) { + if ( health ) { + fl.takedamage = true; + } + if ( noTouch || health ) { + // non touch/shoot doors + PostEventMS( &EV_Mover_MatchTeam, 0, moverState, gameLocal.time ); + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } else { + // spawn trigger + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + } + + // see if we are on an areaportal + areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() ); + if ( !start_open ) { + // start closed + ProcessEvent( &EV_Mover_ClosePortal ); + } + + int locked = spawnArgs.GetInt( "locked" ); + if ( locked ) { + // make sure all members of the team get locked + PostEventMS( &EV_Door_Lock, 0, locked ); + } + + if ( spawnArgs.GetBool( "continuous" ) ) { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "delay" ), this ); + } + + // sounds have a habit of stuttering when portals close, so make them unoccluded + refSound.parms.soundShaderFlags |= SSF_NO_OCCLUSION; + + companionDoor = NULL; + + enabled = true; + blocked = false; + +// RAVEN BEGIN +// bdube: added + // Instruct ai to avoid standing in doors + if ( !spawnArgs.GetBool ( "noavoid" ) ) { + aiManager.AddAvoid ( GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetBounds().GetRadius(), -1 ); + } +// RAVEN END +} + +/* +================ +idDoor::Think +================ +*/ +void idDoor::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + if ( sndTrigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idDoor::PreBind +================ +*/ +void idDoor::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idDoor::PostBind +================ +*/ +void idDoor::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ? trigger : sndTrigger ); +} + +/* +================ +idDoor::SetAASAreaState +================ +*/ +void idDoor::SetAASAreaState( bool closed ) { + aas_area_closed = closed; + gameLocal.SetAASAreaState( physicsObj.GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL|AREACONTENTS_OBSTACLE, closed ); +} + +/* +================ +idDoor::Hide +================ +*/ +void idDoor::Hide( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Hide(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Hide(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Disable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Disable(); + } + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + slave->GetPhysics()->GetClipModel()->Disable(); + slave->idMover_Binary::Hide(); + } + } +} + +/* +================ +idDoor::Show +================ +*/ +void idDoor::Show( void ) { + idMover_Binary *slave; + idMover_Binary *master; + idDoor *slaveDoor; + idDoor *companion; + + master = GetMoveMaster(); + if ( this != master ) { + master->Show(); + } else { + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + companion = slaveDoor->companionDoor; + if ( companion && ( companion != master ) && ( companion->GetMoveMaster() != master ) ) { + companion->Show(); + } + if ( slaveDoor->trigger ) { + slaveDoor->trigger->Enable(); + } + if ( slaveDoor->sndTrigger ) { + slaveDoor->sndTrigger->Enable(); + } + if ( slaveDoor->areaPortal && ( slaveDoor->moverState == MOVER_POS1 ) ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + slave->GetPhysics()->GetClipModel()->Enable(); + slave->idMover_Binary::Show(); + } + } +} + +/* +================ +idDoor::GetLocalTriggerPosition +================ +*/ +void idDoor::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +================ +idDoor::Use +================ +*/ +void idDoor::Use( idEntity *other, idEntity *activator ) { + if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) { + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + ActivateTargets( activator ); + Use_BinaryMover( activator ); + } +} + +/* +================ +idDoor::Open +================ +*/ +void idDoor::Open( void ) { + GotoPosition2(); +} + +/* +================ +idDoor::Close +================ +*/ +void idDoor::Close( void ) { + GotoPosition1(); +} + +/* +================ +idDoor::Lock +================ +*/ +void idDoor::Lock( int f ) { + idMover_Binary *other; + + // lock all the doors on the team + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + idDoor *door = static_cast( other ); + if ( other == moveMaster ) { + if ( door->sndTrigger == NULL ) { + // in this case the sound trigger never got spawned + const char *sndtemp = door->spawnArgs.GetString( "snd_locked" ); + if ( sndtemp && *sndtemp ) { + door->PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + } + if ( !f && ( door->spawnArgs.GetInt( "locked" ) != 0 ) ) { + door->StartSound( "snd_unlocked", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + door->spawnArgs.SetInt( "locked", f ); +// RAVEN BEGIN +// jdischler + // locking/unlocking doors should update the shaderparm7 of any buddies + door->UpdateBuddies( !f ); +// RAVEN END + + if ( ( f == 0 ) || ( !IsHidden() && ( door->moverState == MOVER_POS1 ) ) ) { + door->SetAASAreaState( f != 0 ); + } + } + } + + if ( f ) { + Close(); + } +} + +/* +================ +idDoor::IsLocked +================ +*/ +int idDoor::IsLocked( void ) { + return spawnArgs.GetInt( "locked" ); +} + +/* +================ +idDoor::IsOpen +================ +*/ +bool idDoor::IsOpen( void ) { + return ( moverState != MOVER_POS1 ); +} + +/* +================ +idDoor::IsNoTouch +================ +*/ +bool idDoor::IsNoTouch( void ) { + return noTouch; +} + +/* +====================== +idDoor::CalcTriggerBounds + +Calcs bounds for a trigger. +====================== +*/ +void idDoor::CalcTriggerBounds( float size, idBounds &bounds ) { + idMover_Binary *other; + int i; + int best; + + // find the bounds of everything on the team + bounds = GetPhysics()->GetAbsBounds(); + + if ( health > 0 ) { + fl.takedamage = true; + } + for( other = activateChain; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + // find the bounds of everything on the team + bounds.AddBounds( other->GetPhysics()->GetAbsBounds() ); + + // set all of the slaves as shootable + other->fl.takedamage = true; + } + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) { + best = i; + } + } + normalAxisIndex = best; + bounds[0][ best ] -= size; + bounds[1][ best ] += size; + bounds[0] -= GetPhysics()->GetOrigin(); + bounds[1] -= GetPhysics()->GetOrigin(); +} + +// RAVEN BEGIN +// abahr: +/* +============================== +idDoor::SetDoorFrameController +============================== +*/ +void idDoor::SetDoorFrameController( idEntity* controller ) { + doorFrameController = controller; +} + +/* +============================== +idDoor::ActivateTargets + +If we have a frame controller we activate its targets +============================== +*/ +void idDoor::ActivateTargets( idEntity *activator ) const { + if ( doorFrameController.IsValid() && static_cast< const idMover_Binary *>( GetMoveMaster() ) == this && moverState == MOVER_POS1 ) { + doorFrameController->ActivateTargets( activator ); + } + + idMover_Binary::ActivateTargets( activator ); +} +// RAVEN END + +/* +====================== +idDoor::Event_StartOpen + +if "start_open", reverse position 1 and 2 +====================== +*/ +void idDoor::Event_StartOpen( void ) { + float time; + float speed; + + // if "start_open", reverse position 1 and 2 + pos1 = pos2; + pos2 = GetPhysics()->GetOrigin(); + + spawnArgs.GetFloat( "speed", "400", speed ); + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, 0, 0 ); + } else { + InitSpeed( pos1, pos2, speed, 0, 0 ); + } +} + +/* +====================== +idDoor::Event_SpawnDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them. +====================== +*/ +void idDoor::Event_SpawnDoorTrigger( void ) { + idBounds bounds; + idMover_Binary *other; + bool toggle; + + if ( trigger ) { + // already have a trigger, so don't spawn a new one. + return; + } + + // check if any of the doors are marked as toggled + toggle = false; + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) && other->spawnArgs.GetBool( "toggle" ) ) { + toggle = true; + break; + } + } + + if ( toggle ) { + // mark them all as toggled + for( other = moveMaster; other != NULL; other = other->GetActivateChain() ) { + if ( other->IsType( idDoor::Type ) ) { + other->spawnArgs.Set( "toggle", "1" ); + } + } + // don't spawn trigger + return; + } + + const char *sndtemp = spawnArgs.GetString( "snd_locked" ); + if ( spawnArgs.GetInt( "locked" ) && sndtemp && *sndtemp ) { + PostEventMS( &EV_Door_SpawnSoundTrigger, 0 ); + } + + CalcTriggerBounds( triggersize, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( trigger ); + + MatchActivateTeam( moverState, gameLocal.time ); +} + +/* +====================== +idDoor::Event_SpawnSoundTrigger + +Spawn a sound trigger to activate locked sound if it exists. +====================== +*/ +void idDoor::Event_SpawnSoundTrigger( void ) { + idBounds bounds; + + if ( sndTrigger ) { + return; + } + + CalcTriggerBounds( triggersize * 0.5f, bounds ); + + // create a trigger clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + sndTrigger = new idClipModel( idTraceModel( bounds ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + sndTrigger->Link( this, 254, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + sndTrigger->SetContents( CONTENTS_TRIGGER ); + + GetLocalTriggerPosition( sndTrigger ); +} + +/* +================ +idDoor::Event_Reached_BinaryMover +================ +*/ +void idDoor::Event_Reached_BinaryMover( void ) { + if ( moverState == MOVER_2TO1 ) { + SetBlocked( false ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerClosed" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerClosed", kv ); + } + } else if ( moverState == MOVER_1TO2 ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "triggerOpened" ); + while( kv ) { + idEntity *ent = gameLocal.FindEntity( kv->GetValue() ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 0, moveMaster->GetActivator() ); + } + kv = spawnArgs.MatchPrefix( "triggerOpened", kv ); + } + } + idMover_Binary::Event_Reached_BinaryMover(); +} + +/* +================ +idDoor::Blocked_Door +================ +*/ +void idDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } + SetBlocked( true ); + + if ( crusher ) { + return; // crushers don't reverse + } + + // reverse direction + Use_BinaryMover( moveMaster->GetActivator() ); + + if ( companionDoor ) { + companionDoor->ProcessEvent( &EV_TeamBlocked, blockedEntity, blockingEntity ); + } +} + +/* +=============== +idDoor::SetCompanion +=============== +*/ +void idDoor::SetCompanion( idDoor *door ) { + companionDoor = door; +} + +/* +=============== +idDoor::Event_PartBlocked +=============== +*/ +void idDoor::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idDoor::Event_ReturnToPos1 +================ +*/ +void idDoor::Event_ReturnToPos1( void ) { + idMover_Binary::Event_ReturnToPos1(); + + if( doorFrameController.IsValid() ) { + doorFrameController->ProcessEvent( &EV_CloseGate ); + } +} +// RAVEN END + +/* +================ +idDoor::Event_Touch +================ +*/ +void idDoor::Event_Touch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate; + idVec3 planeaxis1, planeaxis2, normal; + idBounds bounds; + + if ( !enabled ) { + return; + } + + if ( trigger && trace->c.id == trigger->GetId() ) { + if ( !IsNoTouch() && !IsLocked() && GetMoverState() != MOVER_1TO2 ) { +// RAVEN BEGIN +// abahr: allowing animated door frames + if( doorFrameController.IsValid() && doorFrameController != other ) { + doorFrameController->ProcessEvent( &EV_Touch, other, trace ); + } else { + Use( this, other ); + } +// RAVEN END + } + } else if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( other && other->IsType( idPlayer::GetClassType() ) && IsLocked() && gameLocal.time > nextSndTriggerTime ) { +// RAVEN END + StartSound( "snd_locked", SND_CHANNEL_ANY, 0, false, NULL ); + nextSndTriggerTime = gameLocal.time + 10000; + } + } +} + +/* +================ +idDoor::Event_SpectatorTouch +================ +*/ +void idDoor::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { + idVec3 contact, translate, normal; + idBounds bounds; + idPlayer *p; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( other && other->IsType( idPlayer::GetClassType() ) && static_cast< idPlayer * >( other )->spectating ); +// RAVEN END + + p = static_cast< idPlayer * >( other ); + // avoid flicker when stopping right at clip box boundaries + if ( p->lastSpectateTeleport > gameLocal.time - 1000 ) { + return; + } + if ( trigger && !IsOpen() ) { + // teleport to the other side, center to the middle of the trigger brush + bounds = trigger->GetAbsBounds(); + contact = trace->endpos - bounds.GetCenter(); + translate = bounds.GetCenter(); + normal.Zero(); + normal[ normalAxisIndex ] = 1.0f; + if ( normal * contact > 0 ) { + translate[ normalAxisIndex ] += ( bounds[ 0 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } else { + translate[ normalAxisIndex ] += ( bounds[ 1 ][ normalAxisIndex ] - translate[ normalAxisIndex ] ) * 0.5f; + } + p->SetOrigin( translate ); + p->lastSpectateTeleport = gameLocal.time; + } +} + +/* +================ +idDoor::Event_Activate +================ +*/ +void idDoor::Event_Activate( idEntity *activator ) { + int old_lock; + + if ( spawnArgs.GetInt( "locked" ) ) { + if ( !trigger ) { + PostEventMS( &EV_Door_SpawnDoorTrigger, 0 ); + } + UpdateBuddies( 1 ); + + old_lock = spawnArgs.GetInt( "locked" ); + Lock( 0 ); + if ( old_lock == 2 ) { + return; + } + } + + if ( syncLock.Length() ) { + idEntity *sync = gameLocal.FindEntity( syncLock ); + if ( sync && sync->IsType( idDoor::Type ) ) { + if ( static_cast( sync )->IsOpen() ) { + return; + } + } + } + +// RAVEN BEGIN +// abahr: + if( doorFrameController.IsValid() && doorFrameController != activator ) { + doorFrameController->ProcessEvent( &EV_Activate, activator ); + } else { + ActivateTargets( activator ); + + renderEntity.shaderParms[ SHADERPARM_MODE ] = 1; + UpdateVisuals(); + + Use_BinaryMover( activator ); + } +//RAVEN END +} + +/* +================ +idDoor::Event_Open +================ +*/ +void idDoor::Event_Open( void ) { + Open(); +} + +/* +================ +idDoor::Event_Close +================ +*/ +void idDoor::Event_Close( void ) { + Close(); +} + +/* +================ +idDoor::Event_Lock +================ +*/ +void idDoor::Event_Lock( int f ) { + Lock( f ); +} + +/* +================ +idDoor::Event_IsOpen +================ +*/ +void idDoor::Event_IsOpen( void ) { + bool state; + + state = IsOpen(); + idThread::ReturnFloat( state ); +} + +/* +================ +idDoor::Event_Locked +================ +*/ +void idDoor::Event_Locked( void ) { + idThread::ReturnFloat( spawnArgs.GetInt("locked") ); +} + +/* +================ +idDoor::Event_OpenPortal + +Sets the portal associtated with this door to be open +================ +*/ +void idDoor::Event_OpenPortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( true ); + } + slaveDoor->SetAASAreaState( false ); + } + } +} + +/* +================ +idDoor::Event_ClosePortal + +Sets the portal associtated with this door to be closed +================ +*/ +void idDoor::Event_ClosePortal( void ) { + idMover_Binary *slave; + idDoor *slaveDoor; + + for ( slave = this; slave != NULL; slave = slave->GetActivateChain() ) { + if ( !slave->IsHidden() ) { + if ( slave->IsType( idDoor::Type ) ) { + slaveDoor = static_cast( slave ); + if ( slaveDoor->areaPortal ) { + slaveDoor->SetPortalState( false ); + } + slaveDoor->SetAASAreaState( IsLocked() || IsNoTouch() ); + } + } + } +} + + +/* +=============================================================================== + +idPlat + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Binary, idPlat ) + EVENT( EV_Touch, idPlat::Event_Touch ) + EVENT( EV_TeamBlocked, idPlat::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idPlat::Event_PartBlocked ) +END_CLASS + +/* +=============== +idPlat::idPlat +=============== +*/ +idPlat::idPlat( void ) { + trigger = NULL; + localTriggerOrigin.Zero(); + localTriggerAxis.Identity(); +} + +/* +=============== +idPlat::~idPlat +=============== +*/ +idPlat::~idPlat( void ) { + if ( trigger ) { + delete trigger; + } +} + +/* +=============== +idPlat::Save +=============== +*/ +void idPlat::Save( idSaveGame *savefile ) const { + savefile->WriteClipModel( trigger ); + savefile->WriteVec3( localTriggerOrigin ); + savefile->WriteMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Restore +=============== +*/ +void idPlat::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( trigger ); + savefile->ReadVec3( localTriggerOrigin ); + savefile->ReadMat3( localTriggerAxis ); +} + +/* +=============== +idPlat::Spawn +=============== +*/ +void idPlat::Spawn( void ) { + float lip; + float height; + float time; + float speed; + float accel; + float decel; + bool noTouch; + + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetFloat( "damage", "0", damage ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetFloat( "lip", "8", lip ); + spawnArgs.GetFloat( "accel_time", "0.25", accel ); + spawnArgs.GetFloat( "decel_time", "0.25", decel ); + + // create second position + if ( !spawnArgs.GetFloat( "height", "0", height ) ) { + height = ( GetPhysics()->GetBounds()[1][2] - GetPhysics()->GetBounds()[0][2] ) - lip; + } + + spawnArgs.GetBool( "no_touch", "0", noTouch ); + + // pos1 is the rest (bottom) position, pos2 is the top + pos2 = GetPhysics()->GetOrigin(); + pos1 = pos2; + pos1[2] -= height; + + if ( spawnArgs.GetFloat( "time", "1", time ) ) { + InitTime( pos1, pos2, time, accel, decel ); + } else { + InitSpeed( pos1, pos2, speed, accel, decel ); + } + + SetMoverState( MOVER_POS1, gameLocal.time ); + UpdateVisuals(); + + // spawn the trigger if one hasn't been custom made + if ( !noTouch ) { + // spawn trigger + SpawnPlatTrigger( pos1 ); + } +} + +/* +================ +idPlat::Think +================ +*/ +void idPlat::Think( void ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + idMover_Binary::Think(); + + if ( thinkFlags & TH_PHYSICS ) { + // update trigger position + if ( GetMasterPosition( masterOrigin, masterAxis ) ) { + if ( trigger ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, masterOrigin + localTriggerOrigin * masterAxis, localTriggerAxis * masterAxis ); +// RAVEN END + } + } + } +} + +/* +================ +idPlat::PreBind +================ +*/ +void idPlat::PreBind( void ) { + idMover_Binary::PreBind(); +} + +/* +================ +idPlat::PostBind +================ +*/ +void idPlat::PostBind( void ) { + idMover_Binary::PostBind(); + GetLocalTriggerPosition( trigger ); +} + +/* +================ +idPlat::GetLocalTriggerPosition +================ +*/ +void idPlat::GetLocalTriggerPosition( const idClipModel *trigger ) { + idVec3 origin; + idMat3 axis; + + if ( !trigger ) { + return; + } + + GetMasterPosition( origin, axis ); + localTriggerOrigin = ( trigger->GetOrigin() - origin ) * axis.Transpose(); + localTriggerAxis = trigger->GetAxis() * axis.Transpose(); +} + +/* +============== +idPlat::SpawnPlatTrigger +=============== +*/ +void idPlat::SpawnPlatTrigger( idVec3 &pos ) { + idBounds bounds; + idVec3 tmin; + idVec3 tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + + bounds = GetPhysics()->GetBounds(); + + tmin[0] = bounds[0][0] + 33; + tmin[1] = bounds[0][1] + 33; + tmin[2] = bounds[0][2]; + + tmax[0] = bounds[1][0] - 33; + tmax[1] = bounds[1][1] - 33; + tmax[2] = bounds[1][2] + 8; + + if ( tmax[0] <= tmin[0] ) { + tmin[0] = ( bounds[0][0] + bounds[1][0] ) * 0.5f; + tmax[0] = tmin[0] + 1; + } + if ( tmax[1] <= tmin[1] ) { + tmin[1] = ( bounds[0][1] + bounds[1][1] ) * 0.5f; + tmax[1] = tmin[1] + 1; + } +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + trigger = new idClipModel( idTraceModel( idBounds( tmin, tmax ) ) ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 255, GetPhysics()->GetOrigin(), mat3_identity ); +// RAVEN END + trigger->SetContents( CONTENTS_TRIGGER ); +} + +/* +============== +idPlat::Event_Touch +=============== +*/ +void idPlat::Event_Touch( idEntity *other, trace_t *trace ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !other->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } + + if ( ( GetMoverState() == MOVER_POS1 ) && trigger && ( trace->c.id == trigger->GetId() ) && ( other->health > 0 ) ) { + Use_BinaryMover( other ); + } +} + +/* +================ +idPlat::Event_TeamBlocked +================ +*/ +void idPlat::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + // reverse direction + Use_BinaryMover( activatedBy.GetEntity() ); + + Use_BinaryMover( this ); + +} + +/* +=============== +idPlat::Event_PartBlocked +=============== +*/ +void idPlat::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + + +/* +=============================================================================== + +idMover_Periodic + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idMover_Periodic ) + EVENT( EV_TeamBlocked, idMover_Periodic::Event_TeamBlocked ) + EVENT( EV_PartBlocked, idMover_Periodic::Event_PartBlocked ) +END_CLASS + +/* +=============== +idMover_Periodic::idMover_Periodic +=============== +*/ +idMover_Periodic::idMover_Periodic( void ) { + damage = 0.0f; + fl.neverDormant = false; +} + +idMover_Periodic::~idMover_Periodic( void ) { + SetPhysics( NULL ); +} + +/* +=============== +idMover_Periodic::Spawn +=============== +*/ +void idMover_Periodic::Spawn( void ) { + spawnArgs.GetFloat( "damage", "0", damage ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + GetPhysics()->SetContents( 0 ); + } +} + +/* +=============== +idMover_Periodic::Save +=============== +*/ +void idMover_Periodic::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( damage ); +} + +/* +=============== +idMover_Periodic::Restore +=============== +*/ +void idMover_Periodic::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + savefile->ReadFloat( damage ); + + RestorePhysics( &physicsObj ); +} + +/* +================ +idMover_Periodic::Think +================ +*/ +void idMover_Periodic::Think( void ) { + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + RunPhysics(); + Present(); +} + +/* +=============== +idMover_Periodic::Event_TeamBlocked +=============== +*/ +void idMover_Periodic::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) { + if ( !blockingEntity->fl.takedamage ) { + if ( blockingEntity->IsType( idAI::GetClassType() ) ) { + //burning out already + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) + || (blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() )) ) { + //moveable + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else { + if ( blockingEntity->IsType( idAI::GetClassType() ) && blockingEntity->health <= 0 ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } else if ( blockingEntity->IsType( idMoveable::GetClassType() ) || blockingEntity->IsType( idMoveableItem::GetClassType() ) ) { + //damagable movable? + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else if ( blockingEntity->IsType( idAFEntity_Base::GetClassType() ) && !blockingEntity->IsType( idActor::GetClassType() ) ) { + if ( blockingEntity->spawnArgs.GetBool( "gib" ) ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", 20, INVALID_JOINT ); + return; + } else { + blockingEntity->ProcessEvent( &EV_Remove ); + return; + } + } + } +} + +/* +=============== +idMover_Periodic::Event_PartBlocked +=============== +*/ +void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) { + if ( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } +} + +/* +================ +idMover_Periodic::WriteToSnapshot +================ +*/ +void idMover_Periodic::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); +} + +/* +================ +idMover_Periodic::ReadFromSnapshot +================ +*/ +void idMover_Periodic::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + + +/* +=============================================================================== + +idRotater + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRotater ) + EVENT( EV_Activate, idRotater::Event_Activate ) +END_CLASS + +/* +=============== +idRotater::idRotater +=============== +*/ +idRotater::idRotater( void ) { + activatedBy = this; +} + +/* +=============== +idRotater::Spawn +=============== +*/ +void idRotater::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, gameLocal.time, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, GetPhysics()->GetAxis().ToAngles(), ang_zero, ang_zero ); + SetPhysics( &physicsObj ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + ProcessEvent( &EV_Activate, this ); + } +} + +/* +=============== +idRotater::Save +=============== +*/ +void idRotater::Save( idSaveGame *savefile ) const { + activatedBy.Save( savefile ); +} + +/* +=============== +idRotater::Restore +=============== +*/ +void idRotater::Restore( idRestoreGame *savefile ) { + activatedBy.Restore( savefile ); +} + +/* +=============== +idRotater::Event_Activate +=============== +*/ +void idRotater::Event_Activate( idEntity *activator ) { + float speed; + bool x_axis; + bool y_axis; + idAngles delta; + + activatedBy = activator; + + delta.Zero(); + + if ( !spawnArgs.GetBool( "rotate" ) ) { + spawnArgs.Set( "rotate", "1" ); + spawnArgs.GetFloat( "speed", "100", speed ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of rotation + if ( x_axis ) { + delta[2] = speed; + } else if ( y_axis ) { + delta[0] = speed; + } else { + delta[1] = speed; + } + } else { + spawnArgs.Set( "rotate", "0" ); + } + + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idBobber ) +END_CLASS + +/* +=============== +idBobber::idBobber +=============== +*/ +idBobber::idBobber( void ) { +} + +/* +=============== +idBobber::Spawn +=============== +*/ +void idBobber::Spawn( void ) { + float speed; + float height; + float phase; + bool x_axis; + bool y_axis; + idVec3 delta; + + spawnArgs.GetFloat( "speed", "4", speed ); + spawnArgs.GetFloat( "height", "32", height ); + spawnArgs.GetFloat( "phase", "0", phase ); + spawnArgs.GetBool( "x_axis", "0", x_axis ); + spawnArgs.GetBool( "y_axis", "0", y_axis ); + + // set the axis of bobbing + delta = vec3_origin; + if ( x_axis ) { + delta[ 0 ] = height; + } else if ( y_axis ) { + delta[ 1 ] = height; + } else { + delta[ 2 ] = height; + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, speed * 500, GetPhysics()->GetOrigin(), delta * 2.0f, vec3_origin ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idPendulum + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idPendulum ) +END_CLASS + +/* +=============== +idPendulum::idPendulum +=============== +*/ +idPendulum::idPendulum( void ) { +} + +/* +=============== +idPendulum::Spawn +=============== +*/ +void idPendulum::Spawn( void ) { + float speed; + float freq; + float length; + float phase; + + spawnArgs.GetFloat( "speed", "30", speed ); + spawnArgs.GetFloat( "phase", "0", phase ); + + if ( spawnArgs.GetFloat( "freq", "", freq ) ) { + if ( freq <= 0.0f ) { + gameLocal.Error( "Invalid frequency on entity '%s'", GetName() ); + } + } else { + // find pendulum length + length = idMath::Fabs( GetPhysics()->GetBounds()[0][2] ); + if ( length < 8 ) { + length = 8; + } + + if( gameLocal.isMultiplayer ) { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_mp_gravity.GetFloat() / ( 3 * length ) ); + } else { + freq = 1 / ( idMath::TWO_PI ) * idMath::Sqrt( g_gravity.GetFloat() / ( 3 * length ) ); + } + } + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase * 1000, 500/freq, GetPhysics()->GetAxis().ToAngles(), idAngles( 0, 0, speed * 2.0f ), ang_zero ); + SetPhysics( &physicsObj ); +} + + +/* +=============================================================================== + +idBobber + +=============================================================================== +*/ + +CLASS_DECLARATION( idMover_Periodic, idRiser ) +EVENT( EV_Activate, idRiser::Event_Activate ) +END_CLASS + +/* +=============== +idRiser::idRiser +=============== +*/ +idRiser::idRiser( void ) { +} + +/* +=============== +idRiser::Spawn +=============== +*/ +void idRiser::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + physicsObj.SetClipMask( MASK_SOLID ); + if ( !spawnArgs.GetBool( "solid", "1" ) ) { + physicsObj.SetContents( 0 ); + } + if ( !spawnArgs.GetBool( "nopush" ) ) { + physicsObj.SetPusher( 0 ); + } + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, GetPhysics()->GetOrigin(), vec3_origin, vec3_origin ); + SetPhysics( &physicsObj ); +} + +/* +================ +idRiser::Event_Activate +================ +*/ +void idRiser::Event_Activate( idEntity *activator ) { + + if ( !IsHidden() && spawnArgs.GetBool("hide") ) { + Hide(); + } else { + Show(); + float time; + float height; + idVec3 delta; + + spawnArgs.GetFloat( "time", "4", time ); + spawnArgs.GetFloat( "height", "32", height ); + + delta = vec3_origin; + delta[ 2 ] = height; + + physicsObj.SetLinearExtrapolation( EXTRAPOLATION_LINEAR, gameLocal.time, time * 1000, physicsObj.GetOrigin(), delta, vec3_origin ); + } +} + + +/* +=============================================================================== + +rvConveyor + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvConveyor ) + EVENT( EV_FindTargets, rvConveyor::Event_FindTargets ) +END_CLASS + +/* +================ +rvConveyor::rvConveyor +================ +*/ +rvConveyor::rvConveyor ( void ) { +} + +/* +================ +rvConveyor::Spawn +================ +*/ +void rvConveyor::Spawn ( void ) { + spawnArgs.GetFloat ( "speed", "100", moveSpeed ); + + float angle; + if ( spawnArgs.GetFloat ( "moveAngle", "0", angle ) ) { + moveDir = idAngles ( 0, angle, 0 ).ToMat3()[0]; + } else { + moveDir = GetPhysics()->GetAxis()[0]; + } + + GetPhysics()->SetContents ( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID ); + GetPhysics()->EnableClip ( ); + + BecomeActive ( TH_THINK|TH_PHYSICS ); +} + +/* +================ +rvConveyor::Think +================ +*/ +void rvConveyor::Think ( void ) { + trace_t pushResults; + idVec3 newOrigin; + idMat3 newAxis; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin ( ); + oldAxis = GetPhysics()->GetAxis ( ); + + newOrigin = GetPhysics()->GetOrigin() + moveDir * moveSpeed * MS2SEC ( gameLocal.GetMSec() ); + newAxis = oldAxis; + gameLocal.push.ClipPush( pushResults, this, 0, + GetPhysics()->GetOrigin(), oldAxis, newOrigin, newAxis ); + + GetPhysics()->SetOrigin ( oldOrigin ); + GetPhysics()->SetAxis ( oldAxis ); + + idEntity::Think(); +} + +/* +================ +rvConveyor::Save +================ +*/ +void rvConveyor::Save( idSaveGame *savefile ) const +{ + savefile->WriteVec3( moveDir ); + savefile->WriteFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Restore +================ +*/ +void rvConveyor::Restore( idRestoreGame *savefile ) +{ + savefile->ReadVec3( moveDir ); + savefile->ReadFloat( moveSpeed ); +} + +/* +================ +rvConveyor::Event_FindTargets +================ +*/ +void rvConveyor::Event_FindTargets ( void ) { + FindTargets ( ); + + moveDir = GetPhysics()->GetAxis ( )[0]; + if ( !targets.Num ( ) || !targets[0] ) { + return; + } + + idEntity* path; + idEntity* next; + path = targets[0]; + path->FindTargets ( ); + next = path->targets.Num() ? path->targets[0].GetEntity() : NULL; + if ( !next ) { + return; + } + + moveDir = next->GetPhysics()->GetOrigin() - path->GetPhysics()->GetOrigin(); + moveDir.Normalize ( ); + + path->PostEventMS ( &EV_Remove, 0 ); +} + + +CLASS_DECLARATION( idMover, rvPusher ) +END_CLASS + +/* +================ +rvPusher::rvPusher +================ +*/ +rvPusher::rvPusher( void ) { + parent = 0; +} + +/* +================ +rvPusher::~rvPusher +================ +*/ +rvPusher::~rvPusher( void ) { +} + +/* +================ +rvPusher::Spawn +================ +*/ +void rvPusher::Spawn( void ) { + parent = 0; +} + +/* +================ +rvPusher::Think +================ +*/ +void rvPusher::Think( void ) { + + // Total hack, but it gets the job done + BecomeActive( TH_ALL ); + + if ( parent ) { + idAnimator *parentAnimator = parent->GetAnimator(); + if ( parentAnimator ) { + + idStr jointName; + if ( spawnArgs.GetString( "attachedBone", "", jointName ) ) { + bindJointHandle = parentAnimator->GetJointHandle( jointName ); + + trace_t pushResults; + idVec3 oldOrigin; + idMat3 oldAxis; + + oldOrigin = GetPhysics()->GetOrigin(); + oldAxis = GetPhysics()->GetAxis(); + + parentAnimator->CreateFrame( gameLocal.time, true ); + parentAnimator->ServiceAnims( gameLocal.previousTime, gameLocal.time ); + parentAnimator->GetJointTransform( bindJointHandle, gameLocal.time, pusherOrigin, pusherAxis ); + pusherAxis *= parent->GetRenderEntity()->axis; + pusherOrigin = parent->GetRenderEntity()->origin + pusherOrigin * parent->GetRenderEntity()->axis; + MoveToPos( pusherOrigin ); + gameLocal.push.ClipTranslationalPush(pushResults, this, 0, pusherOrigin, pusherOrigin - oldOrigin ); + GetPhysics()->SetOrigin ( pusherOrigin ); + } + + } + } else { + idStr bindEntName; + parent = 0; + if ( spawnArgs.GetString( "attachedEntity", "", bindEntName ) ) { + parent = gameLocal.FindEntity( bindEntName ); + } + } + + idMover::Think(); +} + + + +// RAVEN END diff --git a/src/mpgame/Mover.h b/src/mpgame/Mover.h new file mode 100644 index 0000000..09a28ec --- /dev/null +++ b/src/mpgame/Mover.h @@ -0,0 +1,730 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_MOVER_H__ +#define __GAME_MOVER_H__ + +extern const idEventDef EV_TeamBlocked; +extern const idEventDef EV_PartBlocked; +extern const idEventDef EV_ReachedPos; +extern const idEventDef EV_ReachedAng; + +// RAVEN BEGIN +// bdube: more externs +extern const idEventDef EV_Door_Lock; +extern const idEventDef EV_Door_IsLocked; +// abahr: +extern const idEventDef EV_GetSplineEntity; +extern const idEventDef EV_MoveAlongVector; +extern const idEventDef EV_Door_Open; +extern const idEventDef EV_Door_Close; +extern const idEventDef EV_Door_IsOpen; +extern const idEventDef EV_Move; +extern const idEventDef EV_RotateOnce; +// twhitaker: +extern const idEventDef EV_Speed; +extern const idEventDef EV_IsActive; +extern const idEventDef EV_IsMoving; + +// mekberg: spline sampling +#define SPLINE_SAMPLE_RATE 60.0f +// RAVEN END + +/* +=============================================================================== + + General movers. + +=============================================================================== +*/ + +class idMover : public idEntity { +public: + CLASS_PROTOTYPE( idMover ); + + idMover( void ); + ~idMover( void ); + + void Spawn( void ); + +// RAVEN BEGIN +// mekberg: added + void Think( void ); +// RAVEN END + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual void Hide( void ); + virtual void Show( void ); + + void SetPortalState( bool open ); + +// RAVEN BEGIN +// mekberg: sounds for splines + void UpdateSplineStage ( void ); +// RAVEN END + +protected: + typedef enum { + ACCELERATION_STAGE, + LINEAR_STAGE, + DECELERATION_STAGE, + FINISHED_STAGE + } moveStage_t; + + typedef enum { + MOVER_NONE, + MOVER_ROTATING, + MOVER_MOVING, + MOVER_SPLINE + } moverCommand_t; + + // + // mover directions. make sure to change script/doom_defs.script if you add any, or change their order + // + typedef enum { + DIR_UP = -1, + DIR_DOWN = -2, + DIR_LEFT = -3, + DIR_RIGHT = -4, + DIR_FORWARD = -5, + DIR_BACK = -6, + DIR_REL_UP = -7, + DIR_REL_DOWN = -8, + DIR_REL_LEFT = -9, + DIR_REL_RIGHT = -10, + DIR_REL_FORWARD = -11, + DIR_REL_BACK = -12 + } moverDir_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idVec3 dir; + } moveState_t; + + typedef struct { + moveStage_t stage; + int acceleration; + int movetime; + int deceleration; + idAngles rot; + } rotationState_t; + + idPhysics_Parametric physicsObj; + + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_PartBlocked( idEntity *blockingEntity ); + + void MoveToPos( const idVec3 &pos); + void UpdateMoveSound( moveStage_t stage ); + void UpdateRotationSound( moveStage_t stage ); + void SetGuiStates( const char *state ); + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + virtual void DoneMoving( void ); + virtual void DoneRotating( void ); + virtual void BeginMove( idThread *thread = NULL ); + virtual void BeginRotation( idThread *thread, bool stopwhendone ); + moveState_t move; + + rvStateThread splineStateThread; + float damage; + +private: + rotationState_t rot; + + int move_thread; + int rotate_thread; + idAngles dest_angles; + idAngles angle_delta; + idVec3 dest_position; + idVec3 move_delta; + float move_speed; + int move_time; + int deceltime; + int acceltime; + bool stopRotation; + bool useSplineAngles; + bool attenuate; + bool useIdleSound; + idEntityPtr splineEnt; + moverCommand_t lastCommand; + + qhandle_t areaPortal; // 0 = no portal + +// mekberg: attenution and sound additions + float maxAttenuation; + float attenuationScale; + idVec3 lastOrigin; + int lastTime; + int splineStartTime; +// RAVEN END + + idList< idEntityPtr > guiTargets; + +// RAVEN BEGIN +// abahr: + bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void MoveAlongVector( const idVec3& vec ); +// RAVEN END + + void VectorForDir( float dir, idVec3 &vec ); + idCurve_Spline *GetSpline( idEntity *splineEntity ) const; + + void Event_SetCallback( void ); + void Event_TeamBlocked( idEntity *blockedPart, idEntity *blockingEntity ); + void Event_StopMoving( void ); + void Event_StopRotating( void ); + void Event_UpdateMove( void ); + void Event_UpdateRotation( void ); + void Event_SetMoveSpeed( float speed ); + void Event_SetMoveTime( float time ); + void Event_SetDecelerationTime( float time ); + void Event_SetAccellerationTime( float time ); + void Event_MoveTo( idEntity *ent ); + void Event_MoveToPos( idVec3 &pos ); + void Event_MoveDir( float angle, float distance ); + void Event_MoveAccelerateTo( float speed, float time ); + void Event_MoveDecelerateTo( float speed, float time ); + void Event_RotateDownTo( int axis, float angle ); + void Event_RotateUpTo( int axis, float angle ); + void Event_RotateTo( idAngles &angles ); + void Event_Rotate( idAngles &angles ); + void Event_RotateOnce( idAngles &angles ); + void Event_Bob( float speed, float phase, idVec3 &depth ); + void Event_Sway( float speed, float phase, idAngles &depth ); + void Event_SetAccelSound( const char *sound ); + void Event_SetDecelSound( const char *sound ); +// RAVEN BEGIN +// cnicholson: added stop sound support + void Event_SetStoppedSound( const char *sound ); +// RAVEN END + void Event_SetMoveSound( const char *sound ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + void Event_EnableSplineAngles( void ); + void Event_DisableSplineAngles( void ); + void Event_RemoveInitialSplineAngles( void ); + void Event_StartSpline( idEntity *splineEntity ); + void Event_StopSpline( void ); + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN + void Event_PostRestoreExt( int start, int total, int accel, int decel, bool useSplineAng ); +// RAVEN END + void Event_IsMoving( void ); + void Event_IsRotating( void ); +// RAVEN BEGIN +// abahr: + void Event_GetSplineEntity(); + void Event_MoveAlongVector( const idVec3& vec ); + +// mekberg: spline states + CLASS_STATES_PROTOTYPE ( idMover ); + + stateResult_t State_Accel( const stateParms_t& parms ); + stateResult_t State_Linear( const stateParms_t& parms ); + stateResult_t State_Decel( const stateParms_t& parms ); +// RAVEN END +}; + +class idSplinePath : public idEntity { +public: + CLASS_PROTOTYPE( idSplinePath ); + + idSplinePath(); + ~idSplinePath(); + + void Spawn( void ); + +// RAVEN BEGIN +// abahr: so we can ignore these if needed + void Toggle() { SetActive(!active); } + void Activate() { SetActive(true); } + void Deactivate() { SetActive(false); } + + bool IsActive() const { return active; } + + int SortTargets(); + int SortBackwardsTargets(); + int SortTargets( idList< idEntityPtr >& list ); + void RemoveNullTargets( void ); + void ActivateTargets( idEntity *activator ) const; + + void RemoveNullTargets( idList< idEntityPtr >& list ); + void ActivateTargets( idEntity *activator, const idList< idEntityPtr >& list ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idList< idEntityPtr > backwardPathTargets; + +// mekberg: spline sampling + float GetSampledTime ( float distance ) const; + +protected: + void SetActive( bool activate ) { active = activate; } + virtual void FindTargets(); + + void Event_Toggle( idEntity* activator ) { Toggle(); } + void Event_IsActive(); + +// mekberg: spline sampling + void SampleSpline ( void ); + +protected: + bool active; + +// mekberg: sample splines. + float* sampledTimes; + float sampledSpeed; + int numSamples; +// RAVEN END +}; + + +struct floorInfo_s { + idVec3 pos; + idStr door; + int floor; +}; + +class idElevator : public idMover { +public: + CLASS_PROTOTYPE( idElevator ); + + idElevator( void ); + + void Spawn(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual bool HandleSingleGuiCommand ( idEntity *entityGui, idLexer *src ); + floorInfo_s * GetFloorInfo ( int floor ); + +protected: + virtual void DoneMoving( void ); + virtual void BeginMove( idThread *thread = NULL ); + void SpawnTrigger( const idVec3 &pos ); + void GetLocalTriggerPosition(); + void Event_Touch( idEntity *other, trace_t *trace ); + +// RAVEN BEGIN +// bdube: added + void SetAASAreaState ( bool enable ); + void InitStatusGui ( idUserInterface* gui ); + void UpdateStatusGui ( idUserInterface* gui ); + void UpdateStatusGuis ( void ); + void UpdateFloorInfo ( void ); +// RAVEN END + +private: + typedef enum { + INIT, + IDLE, + WAITING_ON_DOORS + } elevatorState_t; + + elevatorState_t state; + idList floorInfo; + int currentFloor; + int pendingFloor; + int lastFloor; + bool controlsDisabled; +// bool waitingForPlayerFollowers; + float returnTime; + int returnFloor; + int lastTouchTime; + + class idDoor * GetDoor( const char *name ); + void Think( void ); + void OpenInnerDoor( void ); + void OpenFloorDoor( int floor ); + void CloseAllDoors( void ); + void DisableAllDoors( void ); + void EnableProperDoors( void ); + + void Event_TeamBlocked ( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_Activate ( idEntity *activator ); + void Event_PostFloorArrival ( void ); + void Event_GotoFloor ( int floor ); + void Event_UpdateFloorInfo ( void ); +}; + + +/* +=============================================================================== + + Binary movers. + +=============================================================================== +*/ + +typedef enum { + MOVER_POS1, + MOVER_POS2, + MOVER_1TO2, + MOVER_2TO1 +} moverState_t; + +class idMover_Binary : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Binary ); + + idMover_Binary(); + ~idMover_Binary(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void PreBind( void ); + virtual void PostBind( void ); + + void Enable( bool b ); + void InitSpeed( idVec3 &mpos1, idVec3 &mpos2, float mspeed, float maccelTime, float mdecelTime ); + void InitTime( idVec3 &mpos1, idVec3 &mpos2, float mtime, float maccelTime, float mdecelTime ); + void GotoPosition1( void ); + void GotoPosition2( void ); + void Use_BinaryMover( idEntity *activator ); + void SetGuiStates( const char *state ); + void UpdateBuddies( int val ); + idMover_Binary * GetActivateChain( void ) const { return activateChain; } + idMover_Binary * GetMoveMaster( void ) const { return moveMaster; } + void BindTeam( idEntity *bindTo ); + void SetBlocked( bool b ); + bool IsBlocked( void ); + idEntity * GetActivator( void ) const; + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void SetPortalState( bool open ); + +protected: + idVec3 pos1; + idVec3 pos2; + moverState_t moverState; + idMover_Binary * moveMaster; + idMover_Binary * activateChain; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + float wait; + float damage; + int duration; + int accelTime; + int decelTime; + idEntityPtr activatedBy; + int stateStartTime; + idStr team; + bool enabled; + bool deferedOpen; + int move_thread; + int updateStatus; // 1 = lock behaviour, 2 = open close status + idStrList buddies; + idPhysics_Parametric physicsObj; + qhandle_t areaPortal; // 0 = no portal + bool blocked; + idList< idEntityPtr > guiTargets; + + void MatchActivateTeam( moverState_t newstate, int time ); + void JoinActivateTeam( idMover_Binary *master ); + + void UpdateMoverSound( moverState_t state ); + void SetMoverState( moverState_t newstate, int time ); + moverState_t GetMoverState( void ) const { return moverState; } + void FindGuiTargets( void ); + void SetGuiState( const char *key, const char *val ) const; + + void Event_SetCallback( void ); + void Event_ReturnToPos1( void ); + void Event_Use_BinaryMover( idEntity *activator ); + void Event_Reached_BinaryMover( void ); + void Event_MatchActivateTeam( moverState_t newstate, int time ); + void Event_Enable( void ); + void Event_Disable( void ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + void Event_FindGuiTargets( void ); + void Event_InitGuiTargets( void ); + + static void GetMovedir( float dir, idVec3 &movedir ); +}; + +class idDoor : public idMover_Binary { +public: + CLASS_PROTOTYPE( idDoor ); + + idDoor( void ); + ~idDoor( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + virtual void Hide( void ); + virtual void Show( void ); + + bool IsOpen( void ); + bool IsNoTouch( void ); + int IsLocked( void ); + void Lock( int f ); + void Use( idEntity *other, idEntity *activator ); + void Close( void ); + void Open( void ); + void SetCompanion( idDoor *door ); + +// RAVEN BEGIN +// abahr: + bool IsClosed( void ) const { return moverState == MOVER_POS1; } + void SetDoorFrameController( idEntity* controller ); + virtual void ActivateTargets( idEntity *activator ) const; +// RAVEN END + +private: + float triggersize; + bool crusher; + bool noTouch; + bool aas_area_closed; + idStr buddyStr; + idClipModel * trigger; + idClipModel * sndTrigger; + int nextSndTriggerTime; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + idStr requires; + int removeItem; + idStr syncLock; + int normalAxisIndex; // door faces X or Y for spectator teleports + idDoor * companionDoor; + +// RAVEN BEGIN +// abahr: so we can route calls through a tramGate + idEntityPtr doorFrameController; +// RAVEN END + + void SetAASAreaState( bool closed ); + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void CalcTriggerBounds( float size, idBounds &bounds ); + + void Event_Reached_BinaryMover( void ); + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Activate( idEntity *activator ); + void Event_StartOpen( void ); + void Event_SpawnDoorTrigger( void ); + void Event_SpawnSoundTrigger( void ); + void Event_Close( void ); + void Event_Open( void ); + void Event_Lock( int f ); + void Event_IsOpen( void ); + void Event_Locked( void ); + void Event_SpectatorTouch( idEntity *other, trace_t *trace ); + void Event_OpenPortal( void ); + void Event_ClosePortal( void ); + +// RAVEN BEGIN +// abahr: + void Event_ReturnToPos1( void ); +// RAVEN END +}; + +class idPlat : public idMover_Binary { +public: + CLASS_PROTOTYPE( idPlat ); + + idPlat( void ); + ~idPlat( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void PreBind( void ); + virtual void PostBind( void ); + +private: + idClipModel * trigger; + idVec3 localTriggerOrigin; + idMat3 localTriggerAxis; + + void GetLocalTriggerPosition( const idClipModel *trigger ); + void SpawnPlatTrigger( idVec3 &pos ); + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Special periodic movers. + +=============================================================================== +*/ + +class idMover_Periodic : public idEntity { +public: + CLASS_PROTOTYPE( idMover_Periodic ); + + idMover_Periodic( void ); + ~idMover_Periodic( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idPhysics_Parametric physicsObj; + float damage; + + void Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ); + void Event_PartBlocked( idEntity *blockingEntity ); +}; + +class idRotater : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRotater ); + + idRotater( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idEntityPtr activatedBy; + + void Event_Activate( idEntity *activator ); +}; + +class idBobber : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idBobber ); + + idBobber( void ); + + void Spawn( void ); + +private: +}; + +class idPendulum : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idPendulum ); + + idPendulum( void ); + + void Spawn( void ); + +private: + +}; + +class idRiser : public idMover_Periodic { +public: + CLASS_PROTOTYPE( idRiser ); + + idRiser( void ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: conveyor belts +class rvConveyor : public idEntity { +public: + CLASS_PROTOTYPE( rvConveyor ); + + rvConveyor ( void ); + + void Spawn( void ); + void Think ( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +private: + + idVec3 moveDir; + float moveSpeed; + + void Event_FindTargets ( void ); +}; + +// nrausch: +class rvPusher : public idMover { +public: + CLASS_PROTOTYPE( rvPusher ); + + rvPusher( void ); + ~rvPusher( void ); + + virtual void Spawn( void ); + virtual void Think ( void ); + +private: + jointHandle_t bindJointHandle; + idEntity *parent; + idVec3 pusherOrigin; + idMat3 pusherAxis; +}; + +// RAVEN END + +#endif /* !__GAME_MOVER_H__ */ diff --git a/src/mpgame/MultiplayerGame.cpp b/src/mpgame/MultiplayerGame.cpp new file mode 100644 index 0000000..71a7e02 --- /dev/null +++ b/src/mpgame/MultiplayerGame.cpp @@ -0,0 +1,9299 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// ddynerman: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" ); + +const char *idMultiplayerGame::MPGuis[] = { +// RAVEN BEGIN +// bdube: use regular hud for now + "guis/hud.gui", +// RAVEN END + "guis/mpmain.gui", + "guis/mpmsgmode.gui", + "guis/netmenu.gui", + "guis/mphud.gui", + NULL +}; + +const char *idMultiplayerGame::ThrottleVars[] = { + "ui_spectate", + "ui_ready", + "ui_team", + NULL +}; + +const char *idMultiplayerGame::ThrottleVarsInEnglish[] = { + "#str_106738", + "#str_106737", + "#str_101991", + NULL +}; + +const int idMultiplayerGame::ThrottleDelay[] = { + 8, + 5, + 5 +}; + +const char* idMultiplayerGame::teamNames[ TEAM_MAX ] = { + "Marine", + "Strogg" +}; + +idCVar gui_ui_name( "gui_ui_name", "", CVAR_GAME | CVAR_NOCHEAT, "copy-over cvar for ui_name" ); + +/* +================ +ComparePlayerByScore +================ +*/ +int ComparePlayersByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +CompareTeamByScore +================ +*/ +int CompareTeamsByScore( const void* left, const void* right ) { + return ((const rvPair*)right)->Second() - + ((const rvPair*)left)->Second(); +} + +/* +================ +idMultiplayerGame::idMultiplayerGame +================ +*/ +idMultiplayerGame::idMultiplayerGame() { +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = NULL; +// RITUAL END + scoreBoard = NULL; + statSummary = NULL; + mainGui = NULL; + mapList = NULL; + msgmodeGui = NULL; + defaultWinner = -1; + deadZonePowerupCount = -1; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + + memset( lights, 0, sizeof( lights ) ); + memset( lightHandles, -1, sizeof( lightHandles ) ); + + Clear(); + + for( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + flagEntities[ i ] = NULL; + teamDeadZoneScore[i] = 0; + } + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + announcerSoundQueue.Clear(); + announcerPlayTime = 0; + + gameState = NULL; + currentSoundOverride = false; + + rankTextPlayer = NULL; + + privatePlayers = 0; + + lastAnnouncerSound = AS_NUM_SOUNDS; +} + +/* +================ +idMultiplayerGame::Shutdown +================ +*/ +void idMultiplayerGame::Shutdown( void ) { + + Clear(); + statManager->Shutdown(); + + if( gameState ) { + delete gameState; + } + gameState = NULL; +} + +/* +================ +idMultiplayerGame::Reset +================ +*/ +void idMultiplayerGame::Reset() { + Clear(); + assert( !scoreBoard && !mainGui && !mapList ); + + mpBuyingManager.Reset(); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + buyMenu = uiManager->FindGui( "guis/buymenu.gui", true, false, true ); + buyMenu->SetStateString( "field_credits", "$0.00"); + buyMenu->SetStateBool( "gameDraw", true ); +// RITUAL END + PACIFIER_UPDATE; + scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true ); + +#ifdef _XENON + statSummary = scoreBoard; +#else + statSummary = uiManager->FindGui( "guis/summary.gui", true, false, true ); + statSummary->SetStateBool( "gameDraw", true ); +#endif + + PACIFIER_UPDATE; + + mainGui = uiManager->FindGui( "guis/mpmain.gui", true, false, true ); + mapList = uiManager->AllocListGUI( ); + mapList->Config( mainGui, "mapList" ); + + // set this GUI so that our Draw function is still called when it becomes the active/fullscreen GUI + mainGui->SetStateBool( "gameDraw", true ); + mainGui->SetKeyBindingNames(); + mainGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + +// SetMenuSkin(); + + PACIFIER_UPDATE; + msgmodeGui = uiManager->FindGui( "guis/mpmsgmode.gui", true, false, true ); + msgmodeGui->SetStateBool( "gameDraw", true ); + + memset ( lights, 0, sizeof( lights ) ); + memset ( lightHandles, -1, sizeof( lightHandles ) ); + + renderLight_t *light; + const char *shader; + + light = &lights[ MPLIGHT_CTF_MARINE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 142.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 190.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 84.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_MARINE; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_CTF_STROGG ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 153.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_QUAD ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 0.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 128.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_HASTE ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 225.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + light = &lights[ MPLIGHT_REGEN ]; + shader = "lights/mpCTFLight"; + if ( shader && *shader ) { + light->axis.Identity(); + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = 64.0f; + light->shaderParms[ SHADERPARM_RED ] = 255.0f / 255.0f; + light->shaderParms[ SHADERPARM_GREEN ] = 0.0f; + light->shaderParms[ SHADERPARM_BLUE ] = 0.0f; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; + light->pointLight = true; + light->noShadows = true; + light->noDynamicShadows = true; + light->lightId = -MPLIGHT_CTF_STROGG; + light->allowLightInViewID = 0; + } + + PACIFIER_UPDATE; + ClearGuis(); + +//asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = 0; + + iconManager->Shutdown(); + + // update serverinfo + UpdatePrivatePlayerCount(); + + lastReadyToggleTime = -1; + + cvarSystem->SetCVarBool( "s_voiceChatTest", false ); +} + +/* +================ +idMultiplayerGame::ServerClientConnect +================ +*/ +void idMultiplayerGame::ServerClientConnect( int clientNum ) { + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + statManager->ClientConnect( clientNum ); +} + +/* +================ +idMultiplayerGame::SpawnPlayer +================ +*/ +void idMultiplayerGame::SpawnPlayer( int clientNum ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); + + if ( !p->IsFakeClient() ) { + bool ingame = playerState[ clientNum ].ingame; + // keep ingame to true if needed, that should only happen for local player + + memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) ); + if ( !gameLocal.isClient ) { + p->spawnedTime = gameLocal.time; + //if ( gameLocal.IsTeamGame() ) { + // SwitchToTeam( clientNum, -1, p->team ); + //} + playerState[ clientNum ].ingame = ingame; + } + } + + if ( p->IsLocalClient() && gameLocal.GetLocalPlayer() ) { + tourneyGUI.SetupTourneyGUI( gameLocal.GetLocalPlayer()->mphud, scoreBoard ); + } + + lastVOAnnounce = 0; +} + +/* +================ +idMultiplayerGame::Clear +================ +*/ +void idMultiplayerGame::Clear() { + + int i; + + pingUpdateTime = 0; + vote = VOTE_NONE; + voteTimeOut = 0; + voteExecTime = 0; + matchStartedTime = 0; + memset( &playerState, 0 , sizeof( playerState ) ); + currentMenu = 0; + bCurrentMenuMsg = false; + nextMenu = 0; + pureReady = false; + scoreBoard = NULL; + buyMenu = NULL; + isBuyingAllowedRightNow = false; + statSummary = NULL; + mainGui = NULL; + msgmodeGui = NULL; + if ( mapList ) { + uiManager->FreeListGUI( mapList ); + mapList = NULL; + } + memset( &switchThrottle, 0, sizeof( switchThrottle ) ); + voiceChatThrottle = 0; + + voteValue.Clear(); + voteString.Clear(); + + prevAnnouncerSnd = -1; + + localisedGametype.Clear(); + + for( i = 0; i < MAX_CLIENTS; i++ ) { + kickVoteMapNames[ i ].Clear(); + } + + voteMapDecls.Clear(); + voteMapsWaiting = 0; + + for ( i = 0; i < MPLIGHT_MAX; i ++ ) { + FreeLight( i ); + } + + chatHistory.Clear(); + rconHistory.Clear(); + + memset( rankedTeams, 0, sizeof( rvPair ) * TEAM_MAX ); + + if( gameState ) { + gameState->Clear(); + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + rankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + unrankedPlayers.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); + assaultPoints.SetAllocatorHeap(rvGetSysHeap(RV_HEAP_ID_MULTIPLE_FRAME)); +#endif +// RAVEN END + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + assaultPoints.Clear(); + + ClearAnnouncerSounds(); + + rankTextPlayer = NULL; + + for ( i = 0; i < TEAM_MAX; i++ ) { + flagEntities[ i ] = NULL; + } +} + +/* +================ +idMultiplayerGame::ClearMap +================ +*/ +void idMultiplayerGame::ClearMap( void ) { + assaultPoints.Clear(); + ClearAnnouncerSounds(); + announcerPlayTime = 0; + powerupCount = 0; + marineScoreBarPulseAmount = 0.0f; + stroggScoreBarPulseAmount = 0.0f; + prevAnnouncerSnd = -1; + + for( int i = 0; i < TEAM_MAX; i++ ) + for( int j = 0; j < MAX_TEAM_POWERUPS; j++ ) { + teamPowerups[i][j].powerup = 0; + teamPowerups[i][j].time = 0; + teamPowerups[i][j].endTime = 0; + teamPowerups[i][j].update = false; + } + + // Dead Zone uses teamFragCount as the "player score" + // so we need to clear it at the beginning of every round. + if ( gameLocal.gameType == GAME_DEADZONE ) { + for ( int i = 0; i < MAX_CLIENTS; i++ ) { + playerState[i].teamFragCount = 0; + playerState[i].deadZoneScore = 0; + } + } +} + +/* +================ +idMultiplayerGame::ClearGuis +================ +*/ +void idMultiplayerGame::ClearGuis() { + int i; + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "player%i",i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_tscore", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_tdm_score", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_wins", i+1 ), "" ); + scoreBoard->SetStateString( va( "player%i_status", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + scoreBoard->SetStateInt( "rank_self", 0 ); + + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->hud ) { + continue; + } + player->hud->SetStateString( va( "player%i",i+1 ), "" ); + player->hud->SetStateString( va( "player%i_score", i+1 ), "" ); + player->hud->SetStateString( va( "player%i_ready", i+1 ), "" ); + scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 ); + player->hud->SetStateInt( "rank_self", 0 ); + + player->hud->SetStateInt( "team", TEAM_MARINE ); + player->hud->HandleNamedEvent( "flagReturn" ); + player->hud->SetStateInt( "team", TEAM_STROGG ); + player->hud->HandleNamedEvent( "flagReturn" ); + } + + ClearVote(); +} + +/* +================ +idMultiplayerGame::GetPlayerRank +Returns the player rank (0 best), returning the best rank in the case of a tie +================ +*/ +int idMultiplayerGame::GetPlayerRank( idPlayer* player, bool& isTied ) { + int initialRank = -1; + int rank = -1; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rank = i; + initialRank = rank; + } + } + + if( rank == -1 ) { + return rank; + } + + if( rank > 0 ) { + if( rankedPlayers[ rank - 1 ].Second() == rankedPlayers[ rank ].Second() ) { + rank = rankedPlayers[ rank - 1 ].First()->GetRank(); + } else { + rank = rankedPlayers[ rank - 1 ].First()->GetRank() + 1; + } + } + + // check for tie + isTied = false; + + for( int i = rank - 1; i <= rank + 1; i++ ) { + if( i < 0 || i >= rankedPlayers.Num() || rankedPlayers[ i ].First() == player ) { + continue; + } + + if( rankedPlayers[ i ].Second() == rankedPlayers[ initialRank ].Second() ) { + isTied = true; + break; + } + } + + return rank; +} + +/* +================ +idMultiplayerGame::UpdatePlayerRanks +================ +*/ +void idMultiplayerGame::UpdatePlayerRanks( playerRankMode_t rankMode ) { + idEntity* ent = NULL; + + if( rankMode == PRM_AUTO ) { + if( gameLocal.IsTeamGame() ) { + rankMode = PRM_TEAM_SCORE_PLUS_SCORE; + } else if ( gameLocal.gameType == GAME_TOURNEY ) { + rankMode = PRM_WINS; + } else { + rankMode = PRM_SCORE; + } + } + + rankedPlayers.Clear(); + unrankedPlayers.Clear(); + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer* player = (idPlayer*)ent; + + if ( !CanPlay( player ) ) { + unrankedPlayers.Append( player ); + } else { + int rankingValue = 0; + switch( rankMode ) { + case PRM_SCORE: { + rankingValue = GetScore( player ); + break; + } + case PRM_TEAM_SCORE: { + rankingValue = GetTeamScore( player ); + break; + } + case PRM_TEAM_SCORE_PLUS_SCORE: { + rankingValue = GetScore( player ) + GetTeamScore( player ); + break; + } + case PRM_WINS: { + rankingValue = GetWins( player ); + break; + } + default: { + gameLocal.Error( "idMultiplayerGame::UpdatePlayerRanks() - Bad ranking mode '%d'\n", rankMode ); + } + } + rankedPlayers.Append( rvPair(player, rankingValue ) ); + } + } + + qsort( rankedPlayers.Ptr(), rankedPlayers.Num(), rankedPlayers.TypeSize(), ComparePlayersByScore ); + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + bool tied; + rankedPlayers[ i ].First()->SetRank( GetPlayerRank( rankedPlayers[ i ].First(), tied ) ); + } + + for( int i = 0; i < unrankedPlayers.Num(); i++ ) { + unrankedPlayers[ i ]->SetRank( -1 ); + } +} + +/* +================ +idMultiplayerGame::UpdateTeamRanks +================ +*/ +void idMultiplayerGame::UpdateTeamRanks( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + rankedTeams[ i ] = rvPair( i, teamScore[ i ] ); + } + + qsort( rankedTeams, TEAM_MAX, sizeof( rvPair ), CompareTeamsByScore ); +} + +/* +================ +idMultiplayerGame::UpdateRankColor +================ +*/ +void idMultiplayerGame::UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ) { + for ( int j = 1; j < 4; j++ ) { + gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] ); + } +} + +/* +================ +idMultiplayerGame::CanCapture + +Determines if the given flag can be captured in the given gamestate +================ +*/ +bool idMultiplayerGame::CanCapture( int team ) { + // no AP's in one flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + return true; + } else if( gameLocal.gameType != GAME_CTF && gameLocal.gameType != GAME_ARENA_CTF ) { + return false; // no flag caps in none-CTF games + } + + if ( !assaultPoints.Num() ) { + return true; + } + + // since other logic ensures AP's are captured in order, we just need to check the last AP before the enemy flag + if ( team == TEAM_STROGG ) { + // AP 0 is always next to the marine flag + return ((rvCTFGameState*)gameState)->GetAPOwner( 0 ) == TEAM_STROGG; + } + if ( team == TEAM_MARINE ) { + // the last AP is always the one next to the strogg flag + return ((rvCTFGameState*)gameState)->GetAPOwner( assaultPoints.Num() - 1 ) == TEAM_MARINE; + } + + return false; +} + +void idMultiplayerGame::FlagCaptured( idPlayer *player ) { + if( !gameLocal.isClient ) { + AddTeamScore( player->team, 1 ); + AddPlayerTeamScore( player, 5 ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + float teamCashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_flagCapture", 0 ); + GiveCashToTeam( player->team, teamCashAward ); + + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_flagCapture", 0 ); + player->GiveCash( cashAward ); + } +// RITUAL END + + gameLocal.ClearForwardSpawns(); + + for( int i = 0; i < assaultPoints.Num(); i++ ) { + assaultPoints[ i ]->Reset(); + ((rvCTFGameState*)gameState)->SetAPOwner( i, AS_NEUTRAL ); + } + + statManager->FlagCaptured( player, OpposingTeam( player->team ) ); + player->SetEmote( PE_CHEER ); + } +} + +/* +================ +idMultiplayerGame::SendDeathMessage +================ +*/ +void idMultiplayerGame::SendDeathMessage( idPlayer* attacker, idPlayer* victim, int methodOfDeath, bool quadKill ) { + if( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DEATH ); + if( attacker ) { + outMsg.WriteByte( attacker->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ attacker->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + if( victim ) { + outMsg.WriteByte( victim->entityNumber ); + outMsg.WriteBits( idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ victim->entityNumber ].fragCount ), ASYNC_PLAYER_FRAG_BITS ); + } else { + outMsg.WriteByte( 255 ); + } + + outMsg.WriteByte( methodOfDeath ); + outMsg.WriteBits( quadKill, 1 ); + + gameLocal.ServerSendInstanceReliableMessage( victim, -1, outMsg ); + + if( gameLocal.isListenServer && gameLocal.GetLocalPlayer() && victim && gameLocal.GetLocalPlayer()->GetInstance() == victim->GetInstance() ) + { + // This is for listen servers, which won't get to ClientProcessReliableMessage + ReceiveDeathMessage( attacker, attacker ? playerState[ attacker->entityNumber ].fragCount : -1, victim, victim ? playerState[ victim->entityNumber ].fragCount : -1, methodOfDeath, quadKill ); + } + } +} + +/* +================ +idMultiplayerGame::ReceiveDeathMessage +================ +*/ +void idMultiplayerGame::ReceiveDeathMessage( idPlayer *attacker, int attackerScore, idPlayer *victim, int victimScore, int methodOfDeath, bool quadKill ) { + idUserInterface *hud = gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->hud : NULL; + +// RITUAL BEGIN +// squirrel: force buy menu open when you die + //if( gameLocal.IsMultiplayer() && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && victim == gameLocal.GetLocalPlayer() ) + //{ + // OpenLocalBuyMenu(); + //} +// RITUAL END + + const char* icon = ""; + + // if methodOfDeath is in range [0, MAX_WEAPONS - 1] it refers to a specific weapon. MAX_WEAPONS refers to + // a generic or unknown death (i.e. "Killer killed victim") and values above MAX_WEAPONS + 1 refer + // to other non-weapon deaths (i.e. telefrags) + + // setup to either use weapon icons for a weapon death, or generic death icons + if ( methodOfDeath < MAX_WEAPONS ) { + icon = va( "w%02d", methodOfDeath ); + } else { + icon = va( "dm%d", methodOfDeath - MAX_WEAPONS ); + } + + char* message = NULL; + + if ( gameLocal.IsTeamGame() ) { + idStr attackerStr( ( attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "" ) ); + idStr victimStr( ( victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "" ) ); + + attackerStr.RemoveEscapes(); + victimStr.RemoveEscapes(); + + message = va ( "%s%s ^r%s^i%s %s%s", (attacker ? (attacker->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + attackerStr.c_str(), + quadKill ? "^iqad" : "", + icon, + (victim ? (victim->team ? S_COLOR_STROGG : S_COLOR_MARINE) : ""), + victimStr.c_str() ); + } else { + message = va ( "%s ^r%s^i%s %s", (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : ""), + quadKill ? "^iqad" : "", + icon, + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "") ); + } + + if( hud ) { + hud->SetStateString ( "deathinfo", message ); + hud->HandleNamedEvent ( "addDeathLine" ); + } + + // echo to console + gameLocal.Printf( gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->spawnArgs.GetString( va( "%s_text", icon ), "%s killed %s" ) : "%s killed %s", + (victim ? gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) : "world"), + (attacker ? gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) : "world") ); + gameLocal.Printf( "\n" ); + + // display message on hud + if( attacker && victim && (gameLocal.GetLocalPlayer() == attacker || gameLocal.GetLocalPlayer() == victim) && attacker != victim && methodOfDeath < MAX_WEAPONS ) { + if( gameLocal.GetLocalPlayer() == attacker ) { +// RAVEN BEGIN +// rhummer: Added lang entries for "You fragged %s" and "You were fragged by %s" + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107295" ), gameLocal.userInfo[ victim->entityNumber ].GetString( "ui_name" ) ) ); + } else { + (gameLocal.GetLocalPlayer())->GUIFragNotice( va( common->GetLocalizedString( "#str_107296" ), gameLocal.userInfo[ attacker->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + + if( gameLocal.gameType == GAME_DM ) { + // print rank text next time after we update scores + + // stash the scores on the client so we can print accurate rank info + if( gameLocal.isClient ) { + if( victim ) { + playerState[ victim->entityNumber ].fragCount = victimScore; + } + + if( attacker ) { + playerState[ attacker->entityNumber ].fragCount = attackerScore; + } + } + + if( victim && (gameLocal.GetLocalPlayer() == victim || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == victim->entityNumber)) ) { + rankTextPlayer = victim; + } + + if( attacker && (gameLocal.GetLocalPlayer() == attacker || (gameLocal.GetLocalPlayer()->spectating && gameLocal.GetLocalPlayer()->spectator == attacker->entityNumber)) ) { + rankTextPlayer = attacker; + } + } + } +} + + +// ddynerman: Gametype specific scoreboard +/* +================ +idMultiplayerGame::UpdateScoreboard +================ +*/ +void idMultiplayerGame::UpdateScoreboard( idUserInterface *scoreBoard ) { + scoreBoard->SetStateInt( "gametype", gameLocal.gameType ); + + //statManager->UpdateInGameHud( scoreBoard, true ); + + if( gameLocal.IsTeamGame() ) { + UpdateTeamScoreboard( scoreBoard ); + } else { + UpdateDMScoreboard( scoreBoard ); + } + + return; +} + +/* +================ +idMultiplayerGame::UpdateDMScoreboard +================ +*/ +void idMultiplayerGame::UpdateDMScoreboard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + int i; + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if ( !player ) { + return; + } + + scoreBoard->SetStateString( "scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + if( gameLocal.gameType == GAME_DM ) { + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == player ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + } else if( gameLocal.gameType == GAME_TOURNEY ) { + // loop through twice listing players who are playing, then players who have been eliminated + int listIndex = 0; + + + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), false ); + listIndex++; + } + + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( rankedPlayer->GetTourneyStatus() != PTS_ELIMINATED ) { + continue; + } + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( "scores_sel_0", listIndex ); + } + + scoreBoard->SetStateString ( + va("scores_item_%i", listIndex), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%s\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].ping, // ping + rankedPlayer->GetTextTourneyStatus() ) ); // tourney status + + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", listIndex ), true ); + listIndex++; + } + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == player ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%s\t", + ( player->spectator && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + playerState[ unrankedPlayers[ i ]->entityNumber ].ping, // ping + "" ) ); + } else { + scoreBoard->SetStateString( va( "spectator_scores_item_%i", i ), "" ); + } + } + + for( i = listIndex; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString( va( "scores_item_%i", i ), "" ); + scoreBoard->SetStateBool( va( "scores_item_%i_greyed", i ), false ); + } + } + + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, rankedPlayers.Num() + unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); + + scoreBoard->SetStateString( "position_text", GetPlayerRankText( player ) ); + // shouchard: added map name + // mekberg: localized string + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) == 1 ) { + // stupid english plurals + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107712" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } else { + scoreBoard->SetStateString( "tourney_frag_count", va( common->GetLocalizedString( "#str_107715" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "tourney_count", va( common->GetLocalizedString( "#str_107713" ), ((rvTourneyGameState*)gameState)->GetTourneyCount(), gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) ); + if( player ) { + inNonTimedState |= ((rvTourneyGameState*)gameState)->GetArena( player->GetArena() ).GetState() == AS_SUDDEN_DEATH; + } + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || inNonTimedState ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTeamScoreboard +================ +*/ + +// only output 16 clients onto the scoreboard +#define SCOREBOARD_MAX_CLIENTS 16 + +void idMultiplayerGame::UpdateTeamScoreboard( idUserInterface *scoreBoard ) { + idStr gameinfo; + int numTeamEntries[ TEAM_MAX ]; + idPlayer* player = gameLocal.GetLocalPlayer(); + + // bdube: mechanism for testing the scoreboard (populates it with fake names, pings, etc) + if ( g_testScoreboard.GetInteger() > 0 ) { + UpdateTestScoreboard ( scoreBoard ); + return; + } + + if ( !player ) { + return; + } + + SIMDProcessor->Memset( numTeamEntries, 0, sizeof( int ) * TEAM_MAX ); + + scoreBoard->SetStateString( "team_0_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "team_1_scores_sel_0", "-1" ); + scoreBoard->SetStateString( "spectator_scores_sel_0", "-1" ); + bool useReady = (gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP); + + for ( int i = 0; i < SCOREBOARD_MAX_CLIENTS; i++ ) { + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if ( rankedPlayer == player ) { + // highlight who we are + scoreBoard->SetStateInt( va("team_%i_scores_sel_0", rankedPlayer->team ), numTeamEntries[ rankedPlayer->team ] ); + } + +// RAVEN BEGIN +// mekberg: redid this + if ( gameLocal.gameType == GAME_TDM ) + { + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } + //else if ( gameLocal.gameType == GAME_DEADZONE ) + //{ + // // mekberg: made this check slightly more sane. + // const char* flagString = ""; + // if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + // flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + // } else if ( gameLocal.gameType == GAME_ARENA_CTF && player && rankedPlayer->team == player->team ) { + // flagString = rankedPlayer->GetArenaPowerupString( ); + // } + // scoreBoard->SetStateString ( + // va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + // va("%s\t%s\t%s\t%s\t%s\t%s\t%.01f\t%i\t%i\t%i\t", + // ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + // ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + // ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + // flagString, // shouchard: twhitaker: updated steve's original flag system + // rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + // rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + // rankedScore * 0.1f, // score + // playerState[ rankedPlayer->entityNumber ].fragCount, // kills + // GetPlayerTime( rankedPlayer ), // time + // playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + // numTeamEntries[ rankedPlayer->team ]++; + //} + else + { + // mekberg: made this check slightly more sane. + const char* flagString = ""; + if ( rankedPlayer->PowerUpActive( rankedPlayer->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + flagString = ( rankedPlayer->team ? I_FLAG_MARINE : I_FLAG_STROGG ); + } else if ( gameLocal.gameType == GAME_ARENA_CTF && player && rankedPlayer->team == player->team ) { + flagString = rankedPlayer->GetArenaPowerupString( ); + } + scoreBoard->SetStateString ( + va("team_%i_scores_item_%i", rankedPlayer->team, numTeamEntries[ rankedPlayer->team ]), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t%i\t", + ( useReady ? (rankedPlayer->IsReady() ? I_READY : I_NOT_READY) : "" ), // ready icon + ( player->IsPlayerMuted( rankedPlayer ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( rankedPlayer ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + flagString, // shouchard: twhitaker: updated steve's original flag system + rankedPlayer->GetUserInfo()->GetString( "ui_name" ), // name + rankedPlayer->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + playerState[ rankedPlayer->entityNumber ].fragCount, // kills + GetPlayerTime( rankedPlayer ), // time + playerState[ rankedPlayer->entityNumber ].ping ) ); // ping + numTeamEntries[ rankedPlayer->team ]++; + } +// RAVEN END + } + + if( i < unrankedPlayers.Num() ) { + if ( unrankedPlayers[ i ] == player ) { + // highlight who we are + scoreBoard->SetStateInt( "spectator_scores_sel_0", i ); + } + +// RAVEN BEGIN +// mekberg: redid this + scoreBoard->SetStateString ( + va("spectator_scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%i\t%i\t", + ( player->spectating && player->IsPlayerMuted( unrankedPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( player->IsFriend( unrankedPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ), // name + unrankedPlayers[ i ]->GetUserInfo()->GetString( "ui_clan" ), // clan + "", // score + GetPlayerTime( unrankedPlayers[ i ] ), // time + playerState[ unrankedPlayers[ i ]->entityNumber ].ping ) ); // ping // ping +// RAVEN END + + } else { + scoreBoard->SetStateString ( va("spectator_scores_item_%i", i), "" ); + } + } + + // clear unused space + for( int k = 0; k < TEAM_MAX; k++ ) { + for( int i = numTeamEntries[ k ]; i < MAX_CLIENTS; i++ ) { + scoreBoard->SetStateString ( va("team_%i_scores_item_%i", k, i), "" ); + } + } + + scoreBoard->SetStateInt ( "playerteam", player ? player->team : TEAM_NONE ); + + scoreBoard->SetStateInt ( "strogg_score", teamScore[ TEAM_STROGG ] ); + scoreBoard->SetStateInt ( "marine_score", teamScore[ TEAM_MARINE ] ); + scoreBoard->SetStateInt ( "num_strogg_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] ) ); + scoreBoard->SetStateInt ( "num_marine_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] ) ); + scoreBoard->SetStateInt ( "num_total_players", idMath::ClampInt( 0, 16, numTeamEntries[ TEAM_STROGG ] + numTeamEntries[ TEAM_MARINE ] + unrankedPlayers.Num() ) ); + scoreBoard->SetStateInt ( "num_spec_players", idMath::ClampInt( 0, 16, unrankedPlayers.Num() ) ); + + idStr serverAddress = networkSystem->GetServerAddress(); + + scoreBoard->SetStateString( "servername", gameLocal.serverInfo.GetString( "si_name" ) ); +// RAVEN BEGIN +// shouchard: added map name +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + scoreBoard->SetStateString( "servermap", mapName ); +// RAVEN END + scoreBoard->SetStateString( "serverip", serverAddress.c_str() ); + scoreBoard->SetStateString( "servergametype", GetLongGametypeName( gameLocal.serverInfo.GetString( "si_gameType" ) ) ); + scoreBoard->SetStateString( "servertimelimit", va( "%s: %d", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + if ( gameLocal.IsFlagGameType() ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ); + } else if ( gameLocal.gameType == GAME_DEADZONE ) { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_122008" ), gameLocal.serverInfo.GetInt( "si_controlTime" ) ) ); + } else { + scoreBoard->SetStateString( "serverlimit", va( "%s: %d", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ); + } + + scoreBoard->SetStateString( "timeleft", GameTime() ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + mpGameState_t state = gameState->GetMPGameState(); + scoreBoard->SetStateBool( "infinity", ( !timeLimit && state != COUNTDOWN ) || state == WARMUP || state == GAMEREVIEW || state == SUDDENDEATH ); + + scoreBoard->StateChanged( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::BuildSummaryListString +Returns a summary string for the specified player +================ +*/ +const char* idMultiplayerGame::BuildSummaryListString( idPlayer* player, int rankedScore ) { + // track top 3 accuracies + rvPlayerStat* stat = statManager->GetPlayerStat( player->entityNumber ); + idList > bestAccuracies; + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + // only consider weapons we fired more than a few shots + if( stat->weaponShots[ j ] <= 10 ) { + continue; + } + + float accuracy = (float)stat->weaponHits[ j ] / (float)stat->weaponShots[ j ]; + bestAccuracies.Append( rvPair( j, accuracy ) ); + } + + bestAccuracies.Sort( rvPair::rvPairSecondCompareDirect ); + + // hold upto 3 top weapons at 5 chars each + idStr weaponString; + for( int j = 0; j < 3; j++ ) { + if( j >= bestAccuracies.Num() ) { + continue; + } + + weaponString += va( "^iw%02d", bestAccuracies[ j ].First() ); + } + + return va("%d. %s\t%s\t%d\t%s\t", + player->GetRank() + 1, + player->GetUserInfo()->GetString( "ui_name" ), // name + player->GetUserInfo()->GetString( "ui_clan" ), // clan + rankedScore, // score + weaponString.c_str() ); +} + +/* +================ +idMultiplayerGame::UpdateSummaryBoard +Shows top 10 players if local player is in top 10, otherwise shows top 9 and localplayer +================ +*/ +void idMultiplayerGame::UpdateSummaryBoard( idUserInterface *scoreBoard ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + int playerIndex = -1; + + // update our ranks in case we call this the same frame it happens + UpdatePlayerRanks(); + + // highlight top 3 players + idVec4 blueHighlight = idStr::ColorForIndex( C_COLOR_BLUE ); + idVec4 redHighlight = idStr::ColorForIndex( C_COLOR_RED ); + idVec4 yellowHighlight = idStr::ColorForIndex( C_COLOR_YELLOW ); + blueHighlight[ 3 ] = 0.15f; + redHighlight[ 3 ] = 0.15f; + yellowHighlight[ 3 ] = 0.15f; + + if( gameLocal.IsTeamGame() ) { + scoreBoard->HandleNamedEvent( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? "marine_wins" : "strogg_wins" ); + // summary is top 5 players on each team + int lastHighIndices[ TEAM_MAX ]; + memset( lastHighIndices, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < 5; i++ ) { + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_marine_names", i ), "" ); + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_strogg_names", i ), "" ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + for( int j = 0; j < 5; j++ ) { + idPlayer* rankedPlayer = NULL; + int rankedScore = 0; + int k; + for( k = lastHighIndices[ i ]; k < rankedPlayers.Num(); k++ ) { + if( rankedPlayers[ k ].First()->team == i ) { + rankedPlayer = rankedPlayers[ k ].First(); + rankedScore = rankedPlayers[ k ].Second(); + break; + } + } + + // no more teammates + if( k >= rankedPlayers.Num() ) { + break; + } + + if( j == 4 && playerIndex == -1 && player->team == i ) { + int z; + for( z = 0; z < rankedPlayers.Num(); z++ ) { + if( rankedPlayers[ z ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ z ].Second(); + break; + } + } + } + + if ( rankedPlayer == player ) { + // highlight who we are + playerIndex = j; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", i == TEAM_MARINE ? "summary_marine_names" : "summary_strogg_names", j ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + lastHighIndices[ i ] = k + 1; + } + } + + if( playerIndex > 0 ) { + if( player->team == TEAM_MARINE ) { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", playerIndex ); + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + } + } else { + scoreBoard->SetStateInt( "summary_marine_names_sel_0", -1 ); + scoreBoard->SetStateInt( "summary_strogg_names_sel_0", -1 ); + } + } else { + for ( int i = 0; i < 10; i++ ) { + + // mekberg: delete old highlights + scoreBoard->DeleteStateVar( va( "summary_names_item_%d_highlight", i ) ); + + if( i < rankedPlayers.Num() ) { + // ranked player + idPlayer* rankedPlayer = rankedPlayers[ i ].First(); + int rankedScore = rankedPlayers[ i ].Second(); + + if( i == 9 && playerIndex == -1 ) { + // if the player is ranked, substitute them in + int i; + for( i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() == player ) { + rankedPlayer = player; + rankedScore = rankedPlayers[ i ].Second(); + break; + } + } + } + + if ( rankedPlayer == player ) { + // highlight who we are + playerIndex = i; + } + + scoreBoard->SetStateString ( va( "%s_item_%i", "summary_names", i ), BuildSummaryListString( rankedPlayer, rankedScore ) ); + + if( rankedPlayer->GetRank() == 0 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), blueHighlight ); + } else if( rankedPlayer->GetRank() == 1 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), redHighlight ); + } else if( rankedPlayer->GetRank() == 2 ) { + scoreBoard->SetStateVec4( va( "summary_names_item_%d_highlight", i ), yellowHighlight ); + } + } else { + scoreBoard->SetStateString ( va("summary_names_item_%i", i), "" ); + } + } + + // highlight who we are (only if not ranked in the top 3) + if( player->GetRank() >= 0 && player->GetRank() < 3 ) { + scoreBoard->SetStateInt( "summary_names_sel_0", -1 ); + } else { + scoreBoard->SetStateInt( "summary_names_sel_0", playerIndex ); + } + } + + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->Redraw( gameLocal.time ); +} + +/* +================ +idMultiplayerGame::UpdateTestScoreboard +================ +*/ +void idMultiplayerGame::UpdateTestScoreboard ( idUserInterface *scoreBoard ) { + int i; + + gameLocal.random.SetSeed ( g_testScoreboard.GetInteger ( ) ); + + if( gameLocal.IsTeamGame() ) { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_0_scores_item_%i", i), "" ); + i++; + } + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + name = va("%s\t%i\t%i", name.c_str(), + gameLocal.random.RandomInt ( 50 ), + gameLocal.random.RandomInt ( 10 )); + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), name ); + } + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("team_1_scores_item_%i", i), "" ); + i++; + } + + scoreBoard->SetStateInt ( "strogg_score", gameLocal.random.RandomInt ( 10 ) ); + scoreBoard->SetStateInt ( "marine_score", gameLocal.random.RandomInt ( 10 ) ); + } else { + for ( i = 0; i < MAX_CLIENTS && i < g_testScoreboard.GetInteger ( ); i ++ ) { + idStr name = va("Player %d", i + 1 ); + + scoreBoard->SetStateString ( + va("scores_item_%i", i), + va("%s\t%s\t%s\t%s\t%s\t%s\t%i\t%i\t%i\t", + ( gameLocal.random.RandomInt() % 2 ? I_VOICE_DISABLED : I_VOICE_ENABLED ), // mute icon + ( gameLocal.random.RandomInt() % 2 ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), // friend icon + "", // shouchard: flag + name.c_str(), // name + "Clan", // clan + "", // team score (unused in DM) + gameLocal.random.RandomInt ( 50 ), // score + gameLocal.random.RandomInt ( 10 ), // time + gameLocal.random.RandomInt ( 300 ) + 20 ) ); + + + } + // clear remaining lines (empty slots) + while ( i < MAX_CLIENTS ) { + scoreBoard->SetStateString ( va("scores_item_%i", i), "" ); + i++; + } + } + + scoreBoard->SetStateInt ( "num_marine_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_strogg_players", g_testScoreboard.GetInteger() ); + scoreBoard->SetStateInt ( "num_players", g_testScoreboard.GetInteger() ); + + scoreBoard->SetStateInt( "rank_self", 2 ); + scoreBoard->SetStateInt ( "playercount", g_testScoreboard.GetInteger ( ) ); + + scoreBoard->StateChanged ( gameLocal.time ); + scoreBoard->SetStateString( "gameinfo", va( "Game Type:%s Frag Limit:%i Time Limit:%i", gameLocal.serverInfo.GetString( "si_gameType" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) ); + scoreBoard->Redraw( gameLocal.time ); +} +// RAVEN END + +/* +================ +idMultiplayerGame::GameTime +================ +*/ +const char *idMultiplayerGame::GameTime( void ) { + static char buff[32]; + int m, s, t, ms; + + bool inCountdown = false; + + ms = 0; + if( gameState->GetMPGameState() == COUNTDOWN ) { + inCountdown = true; + ms = gameState->GetNextMPGameStateTime() - gameLocal.realClientTime; + } else if( gameLocal.GetLocalPlayer() && gameLocal.gameType == GAME_TOURNEY && ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetState() == AS_WARMUP ) { + inCountdown = true; + ms = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetNextStateTime() - gameLocal.realClientTime; + } + if ( inCountdown ) { + s = ms / 1000 + 1; + if ( ms <= 0 ) { + // in tourney mode use a different string since warmups happen before each round + // (not really before the overall game) + idStr::snPrintf( buff, sizeof( buff ), "%s --", ( gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY ) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ) ); + } else { + idStr::snPrintf( buff, sizeof( buff ), "%s %i", (gameState->GetMPGameState() == COUNTDOWN && gameLocal.gameType == GAME_TOURNEY) ? common->GetLocalizedString( "#str_107721" ) : common->GetLocalizedString( "#str_107706" ), s ); + } + } else { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int startTime = matchStartedTime; + if( gameLocal.gameType == GAME_TOURNEY ) { + if( gameLocal.GetLocalPlayer() ) { + startTime = ((rvTourneyGameState*)gameState)->GetArena( gameLocal.GetLocalPlayer()->GetArena() ).GetMatchStartTime(); + } + } + if ( timeLimit ) { + ms = ( timeLimit * 60000 ) - ( gameLocal.time - startTime ); + } else { + ms = gameLocal.time - startTime; + } + if ( ms < 0 ) { + ms = 0; + } + + s = ms / 1000; + m = s / 60; + s -= m * 60; + t = s / 10; + s -= t * 10; + + sprintf( buff, "%i:%i%i", m, t, s ); + } + return &buff[0]; +} + +/* +================ +idMultiplayerGame::NumActualClients +================ +*/ +int idMultiplayerGame::NumActualClients( bool countSpectators, int *teamcounts ) { + idPlayer *p; + int c = 0; + + if ( teamcounts ) { + teamcounts[ 0 ] = teamcounts[ 1 ] = 0; + } + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + p = static_cast< idPlayer * >( ent ); + if ( countSpectators || CanPlay( p ) ) { + c++; + } + if ( teamcounts && CanPlay( p ) ) { + teamcounts[ p->team ]++; + } + } + return c; +} + +/* +================ +idMultiplayerGame::EnoughClientsToPlay +================ +*/ +bool idMultiplayerGame::EnoughClientsToPlay() { + int team[ 2 ]; + int clients = NumActualClients( false, &team[ 0 ] ); + if ( gameLocal.IsTeamGame() ) { + return clients >= 2 && team[ 0 ] && team[ 1 ]; + } else { + return clients >= 2; + } +} + +/* +================ +idMultiplayerGame::AllPlayersReady +================ +*/ +bool idMultiplayerGame::AllPlayersReady( idStr* reason ) { + int i, minClients, numClients; + idEntity *ent; + idPlayer *p; + int team[ 2 ]; + bool notReady; + + notReady = false; + + minClients = Max( 2, gameLocal.serverInfo.GetInt( "si_minPlayers" ) ); + numClients = NumActualClients( false, &team[ 0 ] ); + if ( numClients < minClients ) { + if( reason ) { + // stupid english plurals + if( minClients == 2 ) { + *reason = common->GetLocalizedString( "#str_107674" ); + } else { + *reason = va( common->GetLocalizedString( "#str_107732" ), minClients - numClients ); + } + + } + + return false; + } + + if ( gameLocal.IsTeamGame() ) { + if ( !team[ 0 ] || !team[ 1 ] ) { + if( reason ) { + *reason = common->GetLocalizedString( "#str_107675" ); + } + + return false; + } + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + p = static_cast< idPlayer * >( ent ); + + if ( CanPlay( p ) && !p->IsReady() ) { + notReady = true; + } + team[ p->team ]++; + } + + if( notReady ) { + if( reason ) { + if( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() ) { + // Tourney has a different hud layout, so needs a different "you are (not)ready" string + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110018" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107711" ), common->KeysFromBinding( "_impulse17" ) ); + } + } else if( gameLocal.GetLocalPlayer() ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + *reason = va( common->GetLocalizedString( "#str_110017" ), common->KeysFromBinding( "_impulse17" ) ); + } else { + *reason = va( common->GetLocalizedString( "#str_107710" ), common->KeysFromBinding( "_impulse17" ) ); + } + } + } + return false; + } + + return true; +} + +/* +================ +idMultiplayerGame::FragLimitHit +return the winning player (team player) +if there is no FragLeader(), the game is tied and we return NULL +================ +*/ +idPlayer *idMultiplayerGame::FragLimitHit() { + int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + idPlayer *leader = NULL; + + if ( fragLimit <= 0 ) { + return NULL; // fraglimit disabled + } + + leader = FragLeader(); + if ( !leader ) { + return NULL; + } + + if ( playerState[ leader->entityNumber ].fragCount >= fragLimit ) { + return leader; + } + + return NULL; +} + +/* +================ +idMultiplayerGame::TimeLimitHit +================ +*/ +bool idMultiplayerGame::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartedTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +================ +idMultiplayerGame::FragLeader +return the current winner +NULL if even +relies on UpdatePlayerRanks() being called earlier in frame to sort players +================ +*/ +idPlayer* idMultiplayerGame::FragLeader( void ) { + if( rankedPlayers.Num() < 2 ) { + return NULL; + } + + // mark leaders + int i; + int high = GetScore( rankedPlayers[ 0 ].First() ); + idPlayer* p; + for ( i = 0; i < rankedPlayers.Num(); i++ ) { + p = rankedPlayers[ i ].First(); + if ( !p ) { + continue; + } + p->SetLeader( false ); + + if ( !CanPlay( p ) ) { + continue; + } + if ( gameLocal.gameType == GAME_TOURNEY ) { + continue; + } + if ( p->spectating ) { + continue; + } + + if ( GetScore( p ) >= high ) { + p->SetLeader( true ); + } + } + + if( gameLocal.IsTeamGame() ) { + // in a team game, find the first player not on the leader's team, and make sure they aren't tied + int i = 0; + while( i < rankedPlayers.Num() && rankedPlayers[ i ].First()->team == rankedPlayers[ 0 ].First()->team ) { + i++; + } + if( i < rankedPlayers.Num() ) { + if( GetScore( rankedPlayers[ i ].First()->entityNumber ) == GetScore( rankedPlayers[ 0 ].First()->entityNumber ) ) { + return NULL; + } + } + } else if( GetScore( rankedPlayers[ 0 ].First()->entityNumber ) == GetScore( rankedPlayers[ 1 ].First()->entityNumber ) ) { + return NULL; + } + + return rankedPlayers[ 0 ].First(); +} + +/* +================ +idMultiplayerGame::PlayerDeath +================ +*/ +void idMultiplayerGame::PlayerDeath( idPlayer *dead, idPlayer *killer, int methodOfDeath ) { + // don't do PrintMessageEvent + assert( !gameLocal.isClient ); + + if ( killer ) { + if ( gameLocal.IsTeamGame() ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + + // in flag games, we subtract suicides from team-score rather than player score, which is the true + // kill count + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( killer == dead ? dead : killer, -1 ); + } else { + AddPlayerScore( killer == dead ? dead : killer, -1 ); + } + + } else { + // mark a kill + AddPlayerScore( killer, 1 ); + } + + // additional CTF points + if( gameLocal.IsFlagGameType() ) { + if( dead->PowerUpActive( killer->team ? POWERUP_CTF_STROGGFLAG : POWERUP_CTF_MARINEFLAG ) ) { + AddPlayerTeamScore( killer, 2 ); + } + } + if( gameLocal.gameType == GAME_TDM ) { + if ( killer == dead || killer->team == dead->team ) { + // suicide or teamkill + AddTeamScore( killer->team, -1 ); + } else { + AddTeamScore( killer->team, 1 ); + } + } + } else { + // in tourney mode, we don't award points while in the waiting arena + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameState)->GetArena( killer->GetArena() ).GetState() != AS_WARMUP ) { + AddPlayerScore( killer, ( killer == dead ) ? -1 : 1 ); + } + + // in tourney mode, frags track performance over the entire level load, team score keeps track of + // individual rounds + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( killer, ( killer == dead ) ? -1 : 1 ); + } + } + } else { + // e.g. an environmental death + + // flag gametypes subtract points from teamscore, not playerscore + if( gameLocal.IsFlagGameType() ) { + AddPlayerTeamScore( dead, -1 ); + } else { + AddPlayerScore( dead, -1 ); + } + + if( gameLocal.gameType == GAME_TOURNEY ) { + AddPlayerTeamScore( dead, -1 ); + } + if( gameLocal.gameType == GAME_TDM ) { + AddTeamScore( dead->team, -1 ); + } + } + + SendDeathMessage( killer, dead, methodOfDeath, killer ? killer->PowerUpActive( POWERUP_QUADDAMAGE ) : false ); + + statManager->Kill( dead, killer, methodOfDeath ); + +// RAVEN BEGIN +// shouchard: hack for CTF drop messages for listen servers + if ( dead == gameLocal.GetLocalPlayer() && + dead->PowerUpActive( dead->team ? POWERUP_CTF_MARINEFLAG : POWERUP_CTF_STROGGFLAG ) ) { + if ( dead->mphud ) { + dead->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + dead->mphud->HandleNamedEvent( "main_notice" ); + } + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::PlayerStats +================ +*/ +void idMultiplayerGame::PlayerStats( int clientNum, char *data, const int len ) { + + idEntity *ent; + int team; + + *data = 0; + + // make sure we don't exceed the client list + if ( clientNum < 0 || clientNum > gameLocal.numClients ) { + return; + } + + // find which team this player is on + ent = gameLocal.entities[ clientNum ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + team = static_cast< idPlayer * >(ent)->team; + } else { + return; + } + + idStr::snPrintf( data, len, "team=%d score=%ld tks=%ld", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount ); +} + +/* +================ +idMultiplayerGame::PlayerVote +================ +*/ +void idMultiplayerGame::PlayerVote( int clientNum, playerVote_t vote ) { + playerState[ clientNum ].vote = vote; +} + +/* +================ +idMultiplayerGame::ExecuteVote +the votes are checked for validity/relevance before they are started +we assume that they are still legit when reaching here +================ +*/ +void idMultiplayerGame::ExecuteVote( void ) { + bool needRestart; + ClearVote(); + switch ( vote ) { + case VOTE_RESTART: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_TIMELIMIT: + si_timeLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_FRAGLIMIT: + si_fragLimit.SetInteger( atoi( voteValue ) ); + needRestart = gameLocal.NeedRestart(); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + if ( needRestart ) { + gameLocal.sessionCommand = "nextMap"; + } + break; + case VOTE_GAMETYPE: + cvarSystem->SetCVarString( "si_gametype", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_KICK: + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %s", voteValue.c_str() ) ); + break; + case VOTE_MAP: + cvarSystem->SetCVarString( "si_map", voteValue ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; + case VOTE_BUYING: + cvarSystem->SetCVarString( "si_isBuyingEnabled", voteValue ); + //cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n"); + break; +// RAVEN BEGIN +// shouchard: added capture limit + case VOTE_CAPTURELIMIT: + si_captureLimit.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + // todo: round limit here (if we add it) + case VOTE_AUTOBALANCE: + si_autobalance.SetInteger( atoi( voteValue ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + break; + case VOTE_MULTIFIELD: + ExecutePackedVote(); + break; +// RAVEN END + case VOTE_CONTROLTIME: + si_controlTime.SetInteger( atoi( voteValue ) ); + gameLocal.sessionCommand = "nextMap"; + break; + case VOTE_NEXTMAP: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverNextMap\n" ); + break; + } +} + +/* +================ +idMultiplayerGame::CheckVote +================ +*/ +void idMultiplayerGame::CheckVote( void ) { + int numVoters, i; + + if ( vote == VOTE_NONE ) { + return; + } + + if ( voteExecTime ) { + if ( gameLocal.time > voteExecTime ) { + voteExecTime = 0; + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + ExecuteVote(); + vote = VOTE_NONE; + } + return; + } + + // count voting players + numVoters = 0; + + for ( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + if ( playerState[ i ].vote != PLAYER_VOTE_NONE ) { + numVoters++; + } + } + if ( !numVoters ) { + // abort + vote = VOTE_NONE; + ClientUpdateVote( VOTE_ABORTED, yesVotes, noVotes, currentVoteData ); + return; + } + if ( float(yesVotes) / numVoters > 0.5f ) { + ClientUpdateVote( VOTE_PASSED, yesVotes, noVotes, currentVoteData ); + voteExecTime = gameLocal.time + 2000; + return; + } + if ( gameLocal.time > voteTimeOut || float(noVotes) / numVoters >= 0.5f ) { + ClientUpdateVote( VOTE_FAILED, yesVotes, noVotes, currentVoteData ); + vote = VOTE_NONE; + return; + } +} + +// RAVEN BEGIN +// shouchard: multifield voting here + +/* +================ +idMultiplayerGame::ClientCallPackedVote + +The assumption is that the zero changes case has been handled above. +================ +*/ +void idMultiplayerGame::ClientCallPackedVote( const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( 0 != voteData.m_fieldFlags ); + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLPACKEDVOTE ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ServerCallPackedVote +================ +*/ +void idMultiplayerGame::ServerCallPackedVote( int clientNum, const idBitMsg &msg ) { + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + assert( -1 != clientNum ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + // this is set to false if an invalid parameter is asked for-- time limit of -1, or frag limit of "jeff" or whatever. + // if it's a multivote, it may still be valid, but this value is only checked if there are no vote parameters changed. + bool validVote = true; + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + + // flags (short) + voteData.m_fieldFlags = msg.ReadShort(); + + // clear any unallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if ( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // kick + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + voteData.m_kick = msg.ReadByte(); + if ( voteData.m_kick == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_KICK ); + } + } + + // map (string) + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + char buffer[128]; + msg.ReadString( buffer, sizeof( buffer ) ); + voteData.m_map = buffer; + if ( 0 == idStr::Icmp( buffer, si_map.GetString() ) ) { + //gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map is the same as current map." ); + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + } + + // because of addon pk4's clients may submit votes for maps the server doesn't have - audit here + const idDict *mapDict = fileSystem->GetMapDecl( voteData.m_map.c_str() ); + if( !mapDict ) { + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + gameLocal.ServerSendChatMessage( clientNum, "server", "Selected map does not exist on the server" ); + } + } + + // gametype + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + voteData.m_gameType = msg.ReadByte(); + const char *voteString = VoteGameTypeToString( voteData.m_gameType ); + if ( !idStr::Icmp( voteString, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_GAMETYPE ); + } + + if ( voteData.m_fieldFlags & VOTEFLAG_MAP ) { + const idDict *mapDict = fileSystem->GetMapDecl( voteData.m_map.c_str() ); + if ( !mapDict || !mapDict->GetInt( voteString ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "gametype incompatible with map" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_GAMETYPE ); + } + } + } else { + if ( voteData.m_fieldFlags & VOTEFLAG_MAP ) { + const idDict *mapDict = fileSystem->GetMapDecl( voteData.m_map.c_str() ); + if ( !mapDict || !mapDict->GetInt( si_gameType.GetString() ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "map incompatible with gametype" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_MAP ); + } + } + } + + // timelimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + voteData.m_timeLimit = msg.ReadByte(); + if ( voteData.m_timeLimit < si_timeLimit.GetMinValue() || voteData.m_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %d\n", clientNum, voteData.m_timeLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + if ( voteData.m_timeLimit == si_timeLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TIMELIMIT ); + } + } + + // tourneylimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + voteData.m_tourneyLimit = msg.ReadShort(); + if ( voteData.m_tourneyLimit < si_tourneyLimit.GetMinValue() || voteData.m_tourneyLimit > si_tourneyLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104261" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + if ( voteData.m_tourneyLimit == si_tourneyLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104260" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TOURNEYLIMIT ); + } + } + + // capture limit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + voteData.m_captureLimit = msg.ReadShort(); + if ( voteData.m_captureLimit < si_captureLimit.GetMinValue() || voteData.m_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: caplimit value out of range for vote: %d\n", clientNum, voteData.m_captureLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + if ( voteData.m_captureLimit == si_captureLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CAPTURELIMIT ); + } + } + + // fraglimit + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + voteData.m_fragLimit = msg.ReadShort(); + if ( voteData.m_fragLimit < si_fragLimit.GetMinValue() || voteData.m_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %d\n", clientNum, voteData.m_fragLimit ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + if ( voteData.m_fragLimit == si_fragLimit.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_FRAGLIMIT ); + } + } + + // spectators +/* if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_SPECTATORS ) ) { + voteData.m_spectators = msg.ReadByte(); + if ( voteData.m_spectators == si_spectators.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104421" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_SPECTATORS ); + } + } */ + + // buying + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + voteData.m_buying = msg.ReadShort(); + if ( voteData.m_buying == si_isBuyingEnabled.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + validVote = false; + voteData.m_buying &= ( ~VOTEFLAG_BUYING ); + } + } + + // autobalance teams + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + voteData.m_teamBalance = msg.ReadByte(); + if ( voteData.m_teamBalance == si_autobalance.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_TEAMBALANCE ); + } + } + + // control time + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + voteData.m_controlTime = msg.ReadShort(); + if ( voteData.m_controlTime == si_controlTime.GetInteger() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + validVote = false; + voteData.m_fieldFlags &= ( ~VOTEFLAG_CONTROLTIME ); + } + } + + // check for no changes at all + if ( 0 == voteData.m_fieldFlags ) { + // If the vote was called empty, announce there were no valid changes. Otherwise, say nothing, there's already been a warning message. + if( validVote ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104400" ); + } + return; + } + + ServerStartPackedVote( clientNum, voteData ); + ClientStartPackedVote( clientNum, voteData ); +} + +/* +================ +idMultiplayerGame::ClientStartPackedVote +================ +*/ +void idMultiplayerGame::ClientStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + idUserInterface * mpHud = gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->mphud : NULL; + + assert( 0 != voteData.m_fieldFlags ); + + if ( !gameLocal.isListenServer && !gameLocal.isClient ) { + return; + } + + // "%s has called a vote!" + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + + // display the vote called text on the hud and play an announcer sound + if ( mpHud ) { + mpHud->SetStateInt( "voteNotice", 1 ); + } + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + currentVoteData = voteData; + + // push data to the interface + if ( mpHud && mainGui ) { + int voteLineCount = 1; + int menuVoteLineCount = 0; + bool kickActive = false; + bool maxWindows = false; + idStr yesKey = common->KeysFromBinding("_impulse28"); + + mainGui->SetStateInt( "vote_going", 1 ); + + //dynamic vote yes/no box + mpHud->SetStateString( "voteNoticeText", va( common->GetLocalizedString( "#str_107242" ), yesKey.c_str(), common->KeysFromBinding("_impulse29") )); + + // kick should always be the highest one + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + // mpGui here, not mpHud + //mpHud->SetStateString( "vote_data0", va( common->GetLocalizedString( "#str_104422" ), player->GetName() ); + kickActive = true; + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104422" ), gameLocal.userInfo[ currentVoteData.m_kick ].GetString( "ui_name" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_104423" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122011" ), currentVoteData.m_buying ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104427" ), currentVoteData.m_teamBalance ? common->GetLocalizedString( "#str_104341" ) : common->GetLocalizedString( "#str_104342" ) ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_122009" ), currentVoteData.m_controlTime ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + common->GetLocalizedString( "#str_110010" ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + + const char *mapName = currentVoteData.m_map.c_str(); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104429" ), mapName ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = common->GetLocalizedString( "#str_110011" ); + switch( currentVoteData.m_gameType ) { + case VOTE_GAMETYPE_TOURNEY: + gameTypeString = common->GetLocalizedString( "#str_110012" ); + break; + case VOTE_GAMETYPE_TDM: + gameTypeString = common->GetLocalizedString( "#str_110013" ); + break; + case VOTE_GAMETYPE_CTF: + gameTypeString = common->GetLocalizedString( "#str_110014" ); + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameTypeString = common->GetLocalizedString( "#str_110015" ); + break; + case VOTE_GAMETYPE_DEADZONE: + gameTypeString = "DeadZone"; + break; + case VOTE_GAMETYPE_DM: + default: + gameTypeString = common->GetLocalizedString( "#str_110011" ); + break; + } + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104430" ), gameTypeString ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104431" ), currentVoteData.m_timeLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104432" ), currentVoteData.m_tourneyLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104433" ), currentVoteData.m_captureLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + mpHud->SetStateString( va( "voteInfo_%d", voteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + mainGui->SetStateString( va( "voteData_item_%d", menuVoteLineCount ), + va( common->GetLocalizedString( "#str_104434" ), currentVoteData.m_fragLimit ) ); + + voteLineCount++; + menuVoteLineCount++; + if( voteLineCount == 7) { + voteLineCount = 6; + maxWindows = true; + } + } + + //jshep: max of 7 windows and the 7th is always "..." + if( maxWindows ) { + mpHud->SetStateString( "voteInfo_7", "..." ); + } + + mainGui->DeleteStateVar( va( "voteData_item_%d", menuVoteLineCount ) ); + mainGui->SetStateInt( "vote_going", 1 ); + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), yesVotes, noVotes ) ); + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerStartPackedVote +================ +*/ +void idMultiplayerGame::ServerStartPackedVote( int clientNum, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + assert( vote == VOTE_NONE ); + + if ( !gameLocal.isServer ) { + return; + } + + // #13705: clients passing a vote during server restart could abuse the voting system into passing the vote right away after the new map loads + if ( !playerState[ clientNum ].ingame ) { + common->Printf( "ignore vote called by client %d: not in game\n", clientNum ); + return; + } + + // setup + yesVotes = 1; + noVotes = 0; + vote = VOTE_MULTIFIELD; + currentVoteData = voteData; + voteTimeOut = gameLocal.time + 30000; // 30 seconds? might need to be longer because it requires fiddling with the GUI + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTPACKEDVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteShort( voteData.m_fieldFlags ); + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + outMsg.WriteString( voteData.m_map.c_str() ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + if ( 0 != ( voteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + outMsg.WriteShort( idMath::ClampShort( voteData.m_controlTime ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); +} + +/* +================ +idMultiplayerGame::ExecutePackedVote +================ +*/ +void idMultiplayerGame::ExecutePackedVote( void ) { + assert( VOTE_MULTIFIELD == vote ); + + if ( 0 == currentVoteData.m_fieldFlags ) { + return; + } + + bool needRestart = false; + bool needNextMap = false; + bool needRescanSI = false; + + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_RESTART ) ) { + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_BUYING ) ) { + si_isBuyingEnabled.SetInteger( currentVoteData.m_buying ); + needRescanSI = true; + needRestart = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TEAMBALANCE ) ) { + si_autobalance.SetInteger( currentVoteData.m_teamBalance ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CONTROLTIME ) ) { + si_controlTime.SetInteger( currentVoteData.m_controlTime ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_SHUFFLE ) ) { + ShuffleTeams(); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_KICK ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d", currentVoteData.m_kick ) ); + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_MAP ) ) { + si_map.SetString( currentVoteData.m_map.c_str() ); + needNextMap = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_GAMETYPE ) ) { + const char *gameTypeString = VoteGameTypeToString( currentVoteData.m_gameType ); + //jshepard: Currently the DM gametypes can be played on any map. The other gametypes require specially configured maps. + //if further gametypes are added that can be played on any map, don't set the "runPickMap" flag. + bool runPickMap = (idStr::Cmp( gameTypeString, "DM" ) != 0) ? true : false; + + si_gameType.SetString( gameTypeString ); + //jshepard: run a pick map here in case the packed vote is trying to pick the wrong map type. + //PickMap returns true if the map has changed (requiring a nextMap call) + if( runPickMap ) { + if( PickMap( gameTypeString ) ) { + needNextMap = true; + } else { + needRestart = true; + } + + const idDict *mapDict = fileSystem->GetMapDecl( si_map.GetString() ); + if ( !mapDict || !mapDict->GetInt( gameTypeString ) ) { + gameLocal.Warning( "server voted to gametype with no maps; resetting gametype to DM." ); + si_gameType.SetString( "DM" ); + needNextMap = false; + needRestart = true; + } + } else { + needRestart = true; + } + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TIMELIMIT ) ) { + si_timeLimit.SetInteger( currentVoteData.m_timeLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_TOURNEYLIMIT ) ) { + si_tourneyLimit.SetInteger( currentVoteData.m_tourneyLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_CAPTURELIMIT ) ) { + si_captureLimit.SetInteger( currentVoteData.m_captureLimit ); + needRescanSI = true; + } + if ( 0 != ( currentVoteData.m_fieldFlags & VOTEFLAG_FRAGLIMIT ) ) { + si_fragLimit.SetInteger( currentVoteData.m_fragLimit ); + needRescanSI = true; + } + + if ( needRescanSI ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + if ( needNextMap ) { + gameLocal.sessionCommand = "nextMap"; + } + else if ( needRestart || gameLocal.NeedRestart() ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } +} + +// RAVEN END + +/* +================ +idMultiplayerGame::SendMapList +================ +*/ +void idMultiplayerGame::SendMapList( int clientNum ) { + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + int i; + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETVOTEMAPS ); + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + + const char *mapName = dict->GetString( "path" ); + assert( mapName[ 0 ] != '\0' ); + outMsg.WriteString( mapName ); + } + outMsg.WriteString( "" ); + + if ( gameLocal.localClientNum == clientNum ) { + outMsg.BeginReading(); + outMsg.ReadByte(); + ReadMapList( outMsg ); + } else { + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + } +} + +/* +================ +idMultiplayerGame::ReadMapList +================ +*/ +void idMultiplayerGame::ReadMapList( const idBitMsg &msg ) { + int numMaps = fileSystem->GetNumMaps(); + const idDict *dict; + char path[ MAX_STRING_CHARS ]; + int i; + + voteMapDecls.Clear(); + + while ( msg.ReadString( path, MAX_STRING_CHARS ) > 0 ) { + // find the local decl for the path + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( i ); + + if ( !idStr::Icmp( path, dict->GetString( "path" ) ) ) { + break; + } + } + if ( i >= numMaps ) { + // ignore maps we don't already have + continue; + } + + voteMapDecls.Append( i ); + } + + // update any map requests that triggered this + int flags = voteMapsWaiting; + + voteMapsWaiting = 0; + + if ( flags & VOTEMAPS_WAITING_MAPLIST ) { + SetVoteMapList(); + } + + if ( flags & VOTEMAPS_WAITING_SAMAPLIST ) { + SetSAMapList(); + } + + if ( flags & VOTEMAPS_WAITING_LISTMAPS ) { + ListMaps(); + } +} + +/* +================ +idMultiplayerGame::RequestVoteMaps +================ +*/ +bool idMultiplayerGame::RequestVoteMaps( int flags ) { + if ( voteMapDecls.Num() > 0 ) { + return true; + } + + if ( gameLocal.isServer || !gameLocal.isClient ) { + int i; + int numMaps = fileSystem->GetNumMaps(); + + voteMapDecls.Clear(); + for (i = 0; i < numMaps; i++) { + voteMapDecls.Append( i ); + } + return true; + } + + if ( voteMapsWaiting ) { + return false; + } + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETVOTEMAPS ); + networkSystem->ClientSendReliableMessage( outMsg ); + + voteMapsWaiting |= flags; + + return false; +} + +/* +================ +idMultiplayerGame::ListMaps +================ +*/ +void idMultiplayerGame::ListMaps( void ) { + if ( !RequestVoteMaps( VOTEMAPS_WAITING_LISTMAPS ) ) { + gameLocal.Printf( "Requesting map list...\n" ); + return; + } + + int i; + int numMaps = voteMapDecls.Num(); + + for (i = 0; i < numMaps; i++) { + const idDict *dict = fileSystem->GetMapDecl( voteMapDecls[ i ] ); + gameLocal.Printf( "%s", dict->GetBool( "DM" ) ? "DM " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "Team DM" ) ? "TDM " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "CTF" ) ? "CTF " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "Arena CTF" ) ? "ACTF " : " " ); + gameLocal.Printf( "%s", dict->GetBool( "Tourney" ) ? "Trn " : " " ); + gameLocal.Printf( "%-20s %s\n", dict->GetString( "path" ), common->GetLocalizedString( dict->GetString( "name" ) ) ); + } +} + +/* +================ +idMultiplayerGame::SetMapList +================ +*/ +void idMultiplayerGame::SetMapList( const char *listName, const char *mapName, int gameTypeInt ) { + int numMaps = voteMapDecls.Num(); + const idDict *dict; + int numMapsAdded = 0; + int i; + + if ( !RequestVoteMaps( !idStr::Cmp( listName, "mapList" ) ? VOTEMAPS_WAITING_MAPLIST : VOTEMAPS_WAITING_SAMAPLIST ) ) { + return; + } + + const char *gameType = VoteGameTypeToString( gameTypeInt ); + + idStr originalMapName = gameLocal.serverInfo.GetString( "si_map" ); + originalMapName.StripFileExtension(); + + bool foundOriginalMap = false; + int originalMapIndex = -1; + + for ( i = 0; i < numMaps; i++ ) { + dict = fileSystem->GetMapDecl( voteMapDecls[ i ] ); + + bool mapOk = false; + //if the gametype is DM, check for any of these types... + if( !(strcmp( gameType, "DM")) || !(strcmp( gameType, "Team DM")) ) { + if ( dict && ( + dict->GetBool( "DM" ) || + dict->GetBool( "Team DM" ) || + dict->GetBool( "CTF" ) || + dict->GetBool( "Tourney" ) || + dict->GetBool( "Arena CTF" )) + ) { + mapOk = true; + } + //but if not, match the gametype. + } else if ( dict && dict->GetBool( gameType ) ) { + mapOk = true; + } + if( mapOk ) { + const char *mapName = dict->GetString( "name" ); + if ( '\0' == mapName[ 0 ] ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + + if ( idStr::Icmp(dict->GetString( "path" ), originalMapName) == 0 ) { + foundOriginalMap = true; + originalMapIndex = numMapsAdded; + } + + mainGui->SetStateString( va( "%s_item_%d", listName, numMapsAdded), mapName ); + mainGui->SetStateInt( va( "%s_item_%d_id", listName, numMapsAdded), voteMapDecls[ i ] ); + + numMapsAdded++; + } + } + + mainGui->DeleteStateVar( va( "%s_item_%d", listName, numMapsAdded ) ); + + if ( !foundOriginalMap ) { + mainGui->SetStateInt( va( "%s_sel_0", listName ), 0 ); + mainGui->SetStateString( mapName, mainGui->GetStateString( va( "%s_item_0", listName ) ) ); + } else { + mainGui->SetStateInt( va( "%s_sel_0", listName ), originalMapIndex ); + mainGui->SetStateString( mapName, mainGui->GetStateString( va( "%s_item_%d", listName, originalMapIndex ) ) ); + } +} + +/* +================ +idMultiplayerGame::SetVoteMapList +================ +*/ +void idMultiplayerGame::SetVoteMapList( void ) { + SetMapList( "mapList", "mapName", mainGui->GetStateInt( "currentGametype" ) ); +} + +/* +================ +idMultiplayerGame::SetSAMapList +================ +*/ +void idMultiplayerGame::SetSAMapList( void ) { + SetMapList( "sa_mapList", "sa_mapName", mainGui->GetStateInt( "adminCurrentGametype" ) ); +} + +/* +================ +idMultiplayerGame::ClientEndFrame +Called once each render frame (client) after all idGameLocal::ClientPredictionThink() calls +================ +*/ +void idMultiplayerGame::ClientEndFrame( void ) { + iconManager->UpdateIcons(); +} + +/* +================ +idMultiplayerGame::CommonRun +Called once each render frame (client)/once each game frame (server) +================ +*/ +void idMultiplayerGame::CommonRun( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + // twhitaker r282 + // TTimo: sure is a nasty way to do it + if ( gameLocal.isServer && ( gameLocal.serverInfo.GetInt( "net_serverDedicated" ) != cvarSystem->GetCVarInteger( "net_serverDedicated" ) ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "spawnServer\n" ); + } + + if ( player && player->mphud ) { + // update icons + if ( gameLocal.isServer ) { + iconManager->UpdateIcons(); + } + +#ifdef _USE_VOICECHAT + float micLevel; + bool sending, testing; + + // jscott: enable the voice recording + testing = cvarSystem->GetCVarBool( "s_voiceChatTest" ); + sending = soundSystem->EnableRecording( !!( player->usercmd.buttons & BUTTON_VOICECHAT ), testing, micLevel ); + + if( mainGui ) { + mainGui->SetStateFloat( "s_micLevel", micLevel ); + mainGui->SetStateFloat( "s_micInputLevel", cvarSystem->GetCVarFloat( "s_micInputLevel" ) ); + } + +// RAVEN BEGIN +// shouchard: let the UI know about voicechat states + if ( !testing && sending ) { + player->mphud->HandleNamedEvent( "show_transmit_self" ); + } else { + player->mphud->HandleNamedEvent( "hide_transmit_self" ); + } + + if( player->GetUserInfo() && player->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) { + int maxChannels = soundSystem->GetNumVoiceChannels(); + int clientNum = -1; + for (int channels = 0; channels < maxChannels; channels++ ) { + clientNum = soundSystem->GetCommClientNum( channels ); + if ( -1 != clientNum ) { + break; + } + } + + // Sanity check for network errors + assert( clientNum > -2 && clientNum < MAX_CLIENTS ); + + if ( clientNum > -1 && clientNum < MAX_CLIENTS ) { + idPlayer *from = ( idPlayer * )gameLocal.entities[clientNum]; + if( from ) { + player->mphud->SetStateString( "audio_name", from->GetUserInfo()->GetString( "ui_name" ) ); + player->mphud->HandleNamedEvent( "show_transmit" ); + } + } else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } + } + else { + player->mphud->HandleNamedEvent( "hide_transmit" ); + } +#endif // _USE_VOICECHAT +// RAVEN END + } +#ifdef _USE_VOICECHAT + // jscott: Send any new voice data + XmitVoiceData(); +#endif + + int oldRank = -1; + int oldLeadingTeam = -1; + bool wasTied = false; + int oldHighScore = idMath::INT_MIN; + + if( player && rankedPlayers.Num() ) { + if( gameLocal.gameType == GAME_DM ) { + oldRank = GetPlayerRank( player, wasTied ); + oldHighScore = rankedPlayers[ 0 ].Second(); + } else if( gameLocal.IsTeamGame() ) { + oldLeadingTeam = rankedTeams[ 0 ].First(); + wasTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + oldHighScore = rankedTeams[ 0 ].Second(); + } + } + + UpdatePlayerRanks(); + if ( gameLocal.IsTeamGame() ) { + UpdateTeamRanks(); + } + + if ( player && rankedPlayers.Num() && gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType == GAME_DM ) { + // leader message + bool isTied = false; + int newRank = GetPlayerRank( player, isTied ); + + if ( newRank == 0 ) { + if( ( oldRank != 0 || wasTied ) && !isTied ) { + // we've gained first place or the person we were tied with dropped out of first place + ScheduleAnnouncerSound( AS_DM_YOU_HAVE_TAKEN_LEAD, gameLocal.time ); + } else if( oldRank != 0 || (!wasTied && isTied) ) { + // we tied first place or we were in first and someone else tied + ScheduleAnnouncerSound( AS_DM_YOU_TIED_LEAD, gameLocal.time ); + } + } else if ( oldRank == 0 ) { + // we lost first place + ScheduleAnnouncerSound( AS_DM_YOU_LOST_LEAD, gameLocal.time ); + } + } else if ( gameLocal.IsTeamGame() ) { + int leadingTeam = rankedTeams[ 0 ].First(); + bool isTied = ( rankedTeams[ 0 ].Second() == rankedTeams[ 1 ].Second() ); + + if ( !wasTied && isTied ) { + if ( gameLocal.gameType != GAME_DEADZONE ) + ScheduleAnnouncerSound( AS_TEAM_TEAMS_TIED, gameLocal.time ); + } else if ( (leadingTeam != oldLeadingTeam && !isTied) || ( wasTied && !isTied ) ) { + ScheduleAnnouncerSound( leadingTeam ? AS_TEAM_STROGG_LEAD : AS_TEAM_MARINES_LEAD, gameLocal.time ); + } + + if ( gameLocal.gameType == GAME_TDM && oldHighScore != teamScore[ rankedTeams[ 0 ].First() ] && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( teamScore[ rankedTeams[ 0 ].First() ] == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + } + + if( ( gameLocal.gameType == GAME_DM ) && rankedPlayers[ 0 ].Second() != oldHighScore && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 ) { + // fraglimit warning + if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 3 ) { + ScheduleAnnouncerSound( AS_GENERAL_THREE_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 2 ) { + ScheduleAnnouncerSound( AS_GENERAL_TWO_FRAGS, gameLocal.time ); + } else if( rankedPlayers[ 0 ].Second() == gameLocal.serverInfo.GetInt( "si_fragLimit" ) - 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_FRAG, gameLocal.time ); + } + } + + } + + if ( rankTextPlayer ) { + bool tied = false; + int rank = GetPlayerRank( rankTextPlayer, tied ); + (gameLocal.GetLocalPlayer())->GUIMainNotice( GetPlayerRankText( rank, tied, playerState[ rankTextPlayer->entityNumber ].fragCount ) ); + rankTextPlayer = NULL; + } + + PlayAnnouncerSounds(); + + + // asalmon: Need to refresh stats periodically if the player is looking at stats + if ( currentStatClient != -1 ) { + rvPlayerStat* clientStat = statManager->GetPlayerStat( currentStatClient ); + if ( ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) { + statManager->SelectStatWindow(currentStatClient, currentStatTeam); + } + } + + bool updateModels = false; + if( g_forceModel.IsModified() && !gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceModel.ClearModified(); + } + + if( g_forceMarineModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceMarineModel.ClearModified(); + } + + if( g_forceStroggModel.IsModified() && gameLocal.IsTeamGame() ) { + updateModels = true; + g_forceStroggModel.ClearModified(); + } + + if( updateModels ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + player->UpdateModelSetup(); + } + } + } + + // do this here rather than in idItem::Think() because clients don't run Think on ents outside their snap + if( g_simpleItems.IsModified() ) { + + for( int i = 0; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( !ent || !ent->IsType( idItem::GetClassType() ) || ent->IsType( rvItemCTFFlag::GetClassType() ) ) { + continue; + } + + idItem* item = (idItem*)ent; + + item->FreeModelDef(); + + renderEntity_t* renderEntity = item->GetRenderEntity(); + memset( renderEntity, 0, sizeof( renderEntity ) ); + + item->simpleItem = g_simpleItems.GetBool() && gameLocal.isMultiplayer && !item->IsType( rvItemCTFFlag::GetClassType() ); + + if( item->simpleItem ) { + renderEntity->shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_GREEN ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ] = item->simpleItemScale; + renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] = item->simpleItemScale; + renderEntity->hModel = renderModelManager->FindModel( "_sprite" ); + renderEntity->callback = NULL; + renderEntity->numJoints = 0; + renderEntity->joints = NULL; + renderEntity->customSkin = 0; + renderEntity->noShadow = true; + renderEntity->noSelfShadow = true; + renderEntity->customShader = declManager->FindMaterial( item->spawnArgs.GetString( "mtr_simple_icon" ) ); + + renderEntity->referenceShader = 0; + renderEntity->bounds = renderEntity->hModel->Bounds( renderEntity ); + renderEntity->axis = mat3_identity; + + item->StopEffect( "fx_idle", true ); + item->effectIdle = NULL; + item->SetAxis( mat3_identity ); + if( item->pickedUp ) { + item->FreeModelDef(); + item->UpdateVisuals(); + } + } else { + gameEdit->ParseSpawnArgsToRenderEntity( &item->spawnArgs, renderEntity ); + item->SetAxis( renderEntity->axis ); + + if ( item->spawnArgs.GetString( "fx_idle" ) ) { + item->UpdateModelTransform(); + item->effectIdle = item->PlayEffect( "fx_idle", renderEntity->origin, renderEntity->axis, true ); + } + + if( item->pickedUp && item->pickupSkin ) { + item->SetSkin( item->pickupSkin ); + } + } + if ( !item->spawnArgs.GetBool( "dropped" ) ) { + if ( item->spawnArgs.GetBool( "nodrop" ) ) { + item->GetPhysics()->PutToRest(); + } else { + item->Event_DropToFloor(); + } + } + } + + g_simpleItems.ClearModified(); + } + + if (hud_showSpeed.IsModified()) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && player->hud) { + player->hud->HandleNamedEvent( hud_showSpeed.GetBool() ? "showSpeed" : "hideSpeed" ); + } + hud_showSpeed.ClearModified(); + } +} + +/* +================ +idMultiplayerGame::ClientRun +Called once each client render frame (before any ClientPrediction frames have been run) +================ +*/ +void idMultiplayerGame::ClientRun( void ) { + if ( gameLocal.isRepeater ) { + assert( !gameLocal.isServer ); + pureReady = true; + } + + CommonRun(); +} + + +/* +================ +idMultiplayerGame::ReportZoneControllingPlayer +================ +*/ +void idMultiplayerGame::ReportZoneControllingPlayer( idPlayer* player ) +{ + assert( gameLocal.gameType == GAME_DEADZONE ); + + if ( !player ) + return; + + playerState[player->entityNumber].deadZoneScore += gameLocal.GetMSec(); + playerState[player->entityNumber].teamFragCount = playerState[player->entityNumber].deadZoneScore / 1000; + + float cashPerSecondForDeadZoneControl = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_deadZoneControlPerSecond", 0 ); +// player->GiveCash( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); + player->buyMenuCash += ( cashPerSecondForDeadZoneControl * 0.001f * (float) gameLocal.GetMSec() ); +} + + +/* +================ +idMultiplayerGame::ReportZoneController +================ +*/ +void idMultiplayerGame::ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger) +{ + assert( gameLocal.gameType == GAME_DEADZONE ); + assert( gameState->IsType( riDZGameState::GetClassType() ) ); + + riDZGameState *dzGameState = static_cast( gameState ); + + powerupCount = pCount; + + idTrigger_Multi* zTrigger = 0; + if ( zoneTrigger && zoneTrigger->IsType( idTrigger_Multi::GetClassType() ) ) { + zTrigger = static_cast( zoneTrigger ); + } + + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() != GAMEON && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ) + { + // We're not playing right now. However, make sure all the clients are updated to know + // that the zone is neutral. + dzGameState->SetDZState(TEAM_MARINE, DZ_NONE); + dzGameState->SetDZState(TEAM_STROGG, DZ_NONE); + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + return; + } + + if ( IsValidTeam(team) ) { + const int t = gameLocal.serverInfo.GetInt( "si_controlTime" ); + teamDeadZoneScore[team] += gameLocal.GetMSec() * powerupCount; + teamScore[team] = (int)((float)teamDeadZoneScore[team] / 1000.0f); + + // We have a winner! + if ( teamDeadZoneScore[team] > t*1000 ) { + // Set the shaders and lights back to neutral. + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(idVec3(0.75f, 0.75f, 0.75f)); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = 2; + targetEnt->SetShaderParm(7, 2.0f); + } + } + + OnDeadZoneTeamVictory( team ); + + return; + } + } + + // Someone took control of a zone, report this to the + if ( situation == DZ_MARINES_TAKEN || situation == DZ_STROGG_TAKEN || situation == DZ_MARINE_TO_STROGG || + situation == DZ_STROGG_TO_MARINE || situation == DZ_MARINE_REGAIN || situation == DZ_STROGG_REGAIN ) { + dzGameState->SetDZState(TEAM_MARINE, DZ_NONE); // Clear hacked deadlock + dzGameState->SetDZState(team, DZ_TAKEN); + } + + const int NOCHANGE = -2; + const int DEADLOCK = 3; + int controlSit = NOCHANGE; + switch ( situation ) { + case DZ_NONE : + controlSit = NOCHANGE; + break; + case DZ_MARINES_TAKEN : + controlSit = TEAM_MARINE; + break; + case DZ_MARINES_LOST : + controlSit = TEAM_NONE; + dzGameState->SetDZState(TEAM_MARINE, DZ_LOST); + break; + case DZ_STROGG_TAKEN : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_LOST : + controlSit = TEAM_NONE; + dzGameState->SetDZState(TEAM_STROGG, DZ_LOST); + break; + case DZ_MARINE_TO_STROGG : + controlSit = TEAM_STROGG; + break; + case DZ_STROGG_TO_MARINE : + controlSit = TEAM_MARINE; + break; + case DZ_MARINE_DEADLOCK : + controlSit = DEADLOCK; + dzGameState->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_STROGG_DEADLOCK : + controlSit = DEADLOCK; + dzGameState->SetDZState(TEAM_MARINE, DZ_DEADLOCK); + break; + case DZ_MARINE_REGAIN : + controlSit = TEAM_MARINE; + break; + case DZ_STROGG_REGAIN : + controlSit = TEAM_STROGG; + break; + } + + if ( zTrigger && controlSit == NOCHANGE && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + // There's been no change in status, but keep these variables updated on the client + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = (int)targetEnt->GetRenderEntity()->shaderParms[7]; + } + } + + if ( controlSit == NOCHANGE || !zTrigger ) + return; // We're done. + + idVec3 colorVec; + int parmNum = 2; + if ( controlSit == TEAM_NONE ) { + colorVec = idVec3(0.75f, 0.75f, 0.75f); + parmNum = 2; + } + else if ( controlSit == TEAM_MARINE ) { + colorVec = idVec3(0.0f, 1.0f, 0.0f); + parmNum = 0; + } + else if ( controlSit == TEAM_STROGG ) { + colorVec = idVec3(1.0f, 0.5f, 0.0f); + parmNum = 1; + } + else if ( controlSit == DEADLOCK ) { + colorVec = idVec3(1.0f, 0.0f, 0.0f); + parmNum = 3; + } + + if ( zTrigger->spawnArgs.MatchPrefix( "colorTarget" ) ) { + const idKeyValue *arg; + int refLength = strlen( "colorTarget" ); + int num = zTrigger->spawnArgs.GetNumKeyVals(); + for( int i = 0; i < num; i++ ) { + arg = zTrigger->spawnArgs.GetKeyVal( i ); + if ( arg->GetKey().Icmpn( "colorTarget", refLength ) == 0 ) { + idStr targetStr = arg->GetValue(); + idEntity* targetEnt = gameLocal.FindEntity(targetStr); + if ( targetEnt ) { + targetEnt->SetColor(colorVec); + } + } + } + } + + if ( zTrigger && zTrigger->spawnArgs.MatchPrefix( "entityAffect" ) ) { + idEntity* targetEnt = gameLocal.FindEntity(zTrigger->spawnArgs.GetString("entityAffect", "")); + if ( targetEnt ) { + dzGameState->dzTriggerEnt = targetEnt->entityNumber; + dzGameState->dzShaderParm = parmNum; + targetEnt->SetShaderParm(7, (float)parmNum); + } + } +} + + + +bool idMultiplayerGame::IsValidTeam(int team) +{ + if ( team == TEAM_MARINE || team == TEAM_STROGG ) + return true; + + return false; +} + + +void idMultiplayerGame::OnDeadZoneTeamVictory( int winningTeam ) +{ + OnBuyModeTeamVictory( winningTeam ); + + gameState->NewState( GAMEREVIEW ); +} + +void idMultiplayerGame::OnBuyModeTeamVictory( int winningTeam ) + { + if( !IsBuyingAllowedInTheCurrentGameMode() ) + return; + + float teamCashForWin = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeWin", 0 ); + float teamCashForTie = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeTie", 0 ); + float teamCashForLoss = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "teamCashAward_gameModeLoss", 0 ); + + if( winningTeam == TEAM_NONE ) + { + GiveCashToTeam( TEAM_MARINE, teamCashForTie ); + GiveCashToTeam( TEAM_STROGG, teamCashForTie ); + } + else + { + int losingTeam = 1 - winningTeam; + GiveCashToTeam( winningTeam, teamCashForWin ); + GiveCashToTeam( losingTeam, teamCashForLoss ); + } + } + +/* +================ +idMultiplayerGame::Run +================ +*/ +void idMultiplayerGame::Run( void ) { + pureReady = true; + + assert( gameLocal.isMultiplayer && gameLocal.isServer && gameState ); + + CommonRun(); + + CheckVote(); + + CheckRespawns(); + + CheckSpecialLights( ); + +//RITUAL BEGIN + UpdateTeamPowerups(); +//RITUAL END + gameState->Run(); + + gameState->SendState( serverReliableSender.To( -1 ) ); + + // don't update the ping every frame to save bandwidth + if ( gameLocal.time > pingUpdateTime ) { + for ( int i = 0; i < gameLocal.numClients; i++ ) { + playerState[i].ping = networkSystem->ServerGetClientPing( i ); + } + pingUpdateTime = gameLocal.time + 1000; + } + + +} + +/* +================ +idMultiplayerGame::UpdateMainGui +================ +*/ +void idMultiplayerGame::UpdateMainGui( void ) { + int i; + mainGui->SetStateInt( "readyon", gameState->GetMPGameState() == WARMUP ? 1 : 0 ); + mainGui->SetStateInt( "readyoff", gameState->GetMPGameState() != WARMUP ? 1 : 0 ); + idStr strReady = cvarSystem->GetCVarString( "ui_ready" ); + if ( strReady.Icmp( "ready") == 0 ){ + strReady = common->GetLocalizedString( "#str_104248" ); + } else { + strReady = common->GetLocalizedString( "#str_104247" ); + } + mainGui->SetStateString( "ui_ready", strReady ); + mainGui->SetStateInt( "num_spec_players", unrankedPlayers.Num() ); + + mainGui->SetStateInt( "gametype", gameLocal.gameType ); + mainGui->SetStateBool( "s_useOpenAL", cvarSystem->GetCVarBool( "s_useOpenAL" ) ); + mainGui->SetStateBool( "s_loadOpenALFailed", cvarSystem->GetCVarBool( "s_loadOpenALFailed" ) ); + + idVec4 hitscanTint; + idStr hitScanValue = cvarSystem->GetCVarString( "ui_hitscanTint" ); + sscanf( hitScanValue.c_str(), "%f %f %f %f", &hitscanTint.x, &hitscanTint.y, &hitscanTint.z, &hitscanTint.w ); + mainGui->SetStateFloat( "ui_hitscanTint", hitscanTint.x ); + + // RAVEN BEGIN +// bdube: capture the flag + if ( gameLocal.IsTeamGame() ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + mainGui->SetStateInt( "teamon", 1 ); + mainGui->SetStateInt( "teamoff", 0 ); + } else { + mainGui->SetStateInt( "teamon", 0 ); + mainGui->SetStateInt( "teamoff", 1 ); + } +// RAVEN END +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + mainGui->SetStateInt( "teamon", (gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + mainGui->SetStateInt( "teamoff", !(gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE) ? 1 : 0 ); + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { +// RITUAL END + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p ) { + mainGui->SetStateInt( "team", p->team ); + } + } + // setup vote + mainGui->SetStateInt( "voteon", ( vote != VOTE_NONE && !voted ) ? 1 : 0 ); + mainGui->SetStateInt( "voteoff", ( vote != VOTE_NONE && !voted ) ? 0 : 1 ); + // send the current serverinfo values + for ( i = 0; i < gameLocal.serverInfo.GetNumKeyVals(); i++ ) { + const idKeyValue *keyval = gameLocal.serverInfo.GetKeyVal( i ); + mainGui->SetStateString( keyval->GetKey(), keyval->GetValue() ); + } + mainGui->StateChanged( gameLocal.time ); +#if defined( __linux__ ) + // replacing the oh-so-useful s_reverse with sound backend prompt + mainGui->SetStateString( "driver_prompt", "1" ); +#else + mainGui->SetStateString( "driver_prompt", "0" ); +#endif + +//RAVEN BEGIN +// cnicholson: Add Custom Crosshair update + mainGui->SetStateString( "g_crosshairCustom", cvarSystem->GetCVarBool( "g_crosshairCustom" ) ? "1" : "0" ); +//RAVEN END + +// RAVEN BEGIN +// cnicholson: We need to setup the custom crosshair so it shows up the first time the player enters the MP settings menu. +// This block checks the current crosshair, and compares it against the list of crosshairs in player.def (mtr_crosshair*) under the +// player_marine_mp section. If it finds a match, it assigns the crosshair, otherwise, the first found crosshair is used. +#ifndef _XENON + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *defCH = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + +#ifndef _XENON + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { // Loop through all crosshairs listed in the def + if ( kv->GetValue() == currentCrosshair.c_str() ) { // Until a match is found + break; + } + kv = defCH->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = defCH->dict.MatchPrefix("mtr_crosshair", NULL ); // If no natches are found, use the first one. + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + + +//asalmon: Set up a state var for match type of Xbox 360 +#ifdef _XENON + mainGui->SetStateBool("CustomHost", Live()->IsCustomHost()); + mainGui->SetStateInt("MatchType", Live()->GetMatchtype()); + mainGui->SetStateString("si_gametype", gameLocal.serverInfo.GetString("si_gametype")); + const char *damage; + if (gameLocal.serverInfo.GetBool("si_teamdamage")){ + damage = "Yes" ; + } + else { + damage = "No"; + } + mainGui->SetStateString("si_teamdamage", damage); + const char *shuffle; + if (gameLocal.serverInfo.GetBool("si_shuffleMaps")){ + shuffle = "Yes" ; + } + else { + shuffle = "No"; + } + mainGui->SetStateString("si_shuffleMaps", shuffle); + mainGui->SetStateString("si_fraglimit", gameLocal.serverInfo.GetString("si_fraglimit")); + mainGui->SetStateString("si_capturelimit", gameLocal.serverInfo.GetString("si_capturelimit")); + mainGui->SetStateString("si_timelimit", gameLocal.serverInfo.GetString("si_timelimit")); + +// mekberg: send spectating to the mainGui. + if ( gameLocal.GetLocalPlayer( ) ) { + mainGui->SetStateBool( "spectating", gameLocal.GetLocalPlayer( )->spectating ); + if( gameLocal.gameType == GAME_TOURNEY ) { + if ( gameLocal.GetLocalPlayer()->GetUserInfo() ) { + // additionally in tourney, indicate whether the player is voluntarily spectating + mainGui->SetStateBool( "tourneyspectating", !idStr::Icmp( gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_spectate" ), "Spectate" ) ); + } else { + mainGui->SetStateBool( "tourneyspectating", 1 ); + } + } + } else { + mainGui->SetStateBool( "spectating", false ); + } +#endif +// RAVEN END + +} + +/* +================ +idMultiplayerGame::SetupBuyMenuItems +================ +*/ +void idMultiplayerGame::SetupBuyMenuItems() +{ + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) + return; + + buyMenu->SetStateInt( "buyStatus_shotgun", player->ItemBuyStatus( "weapon_shotgun" ) ); + buyMenu->SetStateInt( "buyStatus_hyperblaster", player->ItemBuyStatus( "weapon_hyperblaster" ) ); + buyMenu->SetStateInt( "buyStatus_grenadelauncher", player->ItemBuyStatus( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "buyStatus_nailgun", player->ItemBuyStatus( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "buyStatus_rocketlauncher", player->ItemBuyStatus( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "buyStatus_railgun", player->ItemBuyStatus( "weapon_railgun" ) ); + buyMenu->SetStateInt( "buyStatus_lightninggun", player->ItemBuyStatus( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "buyStatus_dmg", player->ItemBuyStatus( "weapon_dmg" ) ); + buyMenu->SetStateInt( "buyStatus_napalmgun", player->ItemBuyStatus( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "buyStatus_lightarmor", player->ItemBuyStatus( "item_armor_small" ) ); + buyMenu->SetStateInt( "buyStatus_heavyarmor", player->ItemBuyStatus( "item_armor_large" ) ); + buyMenu->SetStateInt( "buyStatus_ammorefill", player->ItemBuyStatus( "ammorefill" ) ); + + buyMenu->SetStateInt( "buyStatus_special0", player->ItemBuyStatus( "ammo_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special1", player->ItemBuyStatus( "health_regen" ) ); + buyMenu->SetStateInt( "buyStatus_special2", player->ItemBuyStatus( "damage_boost" ) ); + + buyMenu->SetStateInt( "playerTeam", player->team ); + + if ( player->weapon ) + buyMenu->SetStateString( "ammoIcon", player->weapon->spawnArgs.GetString ( "inv_icon" ) ); + + buyMenu->SetStateInt( "player_weapon", player->GetCurrentWeapon() ); +} + +/* +================ +idMultiplayerGame::StartMenu +================ +*/ +idUserInterface* idMultiplayerGame::StartMenu( void ) { + if ( mainGui == NULL ) { + return NULL; + } + //if we're the server, allow access to the admin tab right away. Otherwise, make sure we don't have it. + if( gameLocal.isServer ) { + mainGui->SetStateInt( "password_valid", 1 ); + } else { + mainGui->SetStateInt( "password_valid", 0 ); + } + + int i, j; + + if ( currentMenu ) { + currentMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + } else { + if ( nextMenu >= 2 ) { + currentMenu = nextMenu; + } else { + // for default and explicit + currentMenu = 1; + } + cvarSystem->SetCVarBool( "ui_chat", true ); + } + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = true; + } + + nextMenu = 0; + if ( currentMenu == 1 ) { + UpdateMainGui(); + + // UpdateMainGui sets most things, but it doesn't set these because + // it'd be pointless and/or harmful to set them every frame (for various reasons) + // Currenty the gui doesn't update properly if they change anyway, so we'll leave it like this. + + // player kick data + for ( i = 0; i < 16; i++ ) { + kickVoteMapNames[ i ].Clear(); + kickVoteMap[ i ] = -1; + } + + idStr kickList; + j = 0; + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + if ( kickList.Length() ) { + kickList += ";"; + } + kickList += va( "\"%d - %s\"", i, gameLocal.userInfo[ i ].GetString( "ui_name" ) ); + kickVoteMap[ j ] = i; +// RAVEN BEGIN +// shouchard: names for kick vote map + kickVoteMapNames[ j ] = gameLocal.userInfo[ i ].GetString( "ui_name" ); +// RAVEN END + j++; + } + } + mainGui->SetStateString( "kickChoices", kickList ); + + mainGui->SetStateString( "chattext", "" ); + mainGui->Activate( true, gameLocal.time ); + + idPlayer *localP = gameLocal.GetLocalPlayer(); +#ifndef _XENON + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp", false ); +#else + const idDeclEntityDef *def = gameLocal.FindEntityDef( "player_marine_mp_ui", false ); +#endif + idStr buildValues, buildNames; + + int numModels = declManager->GetNumDecls( DECL_PLAYER_MODEL ); + for( int i = 0; i < numModels; i++ ) { + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->DeclByIndex( DECL_PLAYER_MODEL, i, false ); + + if( !playerModel ) { + continue; + } + + const char *resultValue = playerModel->GetName(); + + if ( !resultValue || !resultValue[0] ) { + continue; + } + + const char *team = playerModel->team.c_str(); + + if ( gameLocal.IsTeamGame() ) { + if ( team && localP && localP->team >= 0 && localP->team < TEAM_MAX && idStr::Icmp( teamNames[ localP->team ], team ) == 0 ) { + } else { + // doesn't match, so skip + continue; + } + } + + if ( i ) { + buildValues += ";"; + buildNames += ";"; + } + buildValues += resultValue; + + const char *resultName = common->GetLocalizedString( playerModel->description.c_str() ); + + if ( !resultName || !resultName[0] ) { + buildNames += resultValue; + } else { + buildNames += resultName; + } + } + + mainGui->SetStateString( "model_values", buildValues.c_str() ); + mainGui->SetStateString( "model_names", buildNames.c_str() ); + mainGui->SetStateBool( "player_model_updated", true ); + + const char *model; + if ( localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if( *model == '\0' ) { + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + if( *model == '\0' ) { + model = def->dict.GetString( "def_default_model" ); + } + } + + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + mainGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + mainGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + mainGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + mainGui->SetStateBool( "need_update", true ); + } + + if( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetUserInfo() ) { + cvarSystem->SetCVarString( "gui_ui_name", gameLocal.GetLocalPlayer()->GetUserInfo()->GetString( "ui_name" ) ); + } else { + cvarSystem->SetCVarString( "gui_ui_name", cvarSystem->GetCVarString( "ui_name" ) ); + } + + if ( gameLocal.isTVClient ) { + mainGui->SetStateBool( "is_tv_client", true ); + } else { + mainGui->SetStateBool( "is_tv_client", false ); + } + + return mainGui; + } else if ( currentMenu == 2 ) { + // the setup is done in MessageMode + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; + } + msgmodeGui->Activate( true, gameLocal.time ); + cvarSystem->SetCVarBool( "ui_chat", true ); + return msgmodeGui; + } else if ( currentMenu == 3 ) { + statSummary->Activate( true, gameLocal.time ); + statManager->SetupStatWindow( statSummary ); + UpdateScoreboard( statSummary ); + UpdateSummaryBoard( statSummary ); + statSummary->SetStateFloat( "ready", 0 ); + statSummary->StateChanged( gameLocal.time ); + + // Moved the announcer sound here. This way we can be sure the client has updated team score information by this point. + // #13576 #13544 causing double sounds because it's getting triggered twice at endgame ( from GameStateChanged and from ReceiveAllStats ) + // the move to here was for fixing some problem when running at the previous location ( GameStateChanged ) + // there are too many codepaths leading to various orders of ReceiveAllStats and GameStateChanged + // various attempts to flag the right call that should trigger the sound failed, so just using a timeout now + if ( gameLocal.time - lastVOAnnounce > 1000 ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() ) { + int winningTeam = GetScoreForTeam( TEAM_MARINE ) > GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + if( player->team == winningTeam ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } else if ( gameLocal.gameType != GAME_TOURNEY ) { + if( player->GetRank() == 0 ) { + ScheduleAnnouncerSound( AS_GENERAL_YOU_WIN, gameLocal.time ); + } else { + ScheduleAnnouncerSound( AS_GENERAL_YOU_LOSE, gameLocal.time ); + } + } + lastVOAnnounce = gameLocal.time; + } + + return statSummary; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + //if( mpClientGameState.gameState.currentState == COUNTDOWN ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->SetStateInt( "price_shotgun", player->GetItemCost("weapon_shotgun") ); + buyMenu->SetStateInt( "price_hyperblaster", player->GetItemCost("weapon_hyperblaster") ); + buyMenu->SetStateInt( "price_grenadelauncher", player->GetItemCost( "weapon_grenadelauncher" ) ); + buyMenu->SetStateInt( "price_nailgun", player->GetItemCost( "weapon_nailgun" ) ); + buyMenu->SetStateInt( "price_rocketlauncher", player->GetItemCost( "weapon_rocketlauncher" ) ); + buyMenu->SetStateInt( "price_railgun", player->GetItemCost( "weapon_railgun" ) ); + buyMenu->SetStateInt( "price_lightninggun", player->GetItemCost( "weapon_lightninggun" ) ); + // buyMenu->SetStateInt( "price_dmg", player->GetItemCost( "weapon_dmg" ) ); + buyMenu->SetStateInt( "price_napalmgun", player->GetItemCost( "weapon_napalmgun" ) ); + + buyMenu->SetStateInt( "price_lightarmor", player->GetItemCost( "item_armor_small" ) ); + buyMenu->SetStateInt( "price_heavyarmor", player->GetItemCost( "item_armor_large" ) ); + buyMenu->SetStateInt( "price_ammorefill", player->GetItemCost( "ammorefill" ) ); + + buyMenu->SetStateInt( "price_special0", player->GetItemCost( "ammo_regen" ) ); + buyMenu->SetStateInt( "price_special1", player->GetItemCost( "health_regen" ) ); + buyMenu->SetStateInt( "price_special2", player->GetItemCost( "damage_boost" ) ); + SetupBuyMenuItems(); + buyMenu->Activate(true, gameLocal.time); + return buyMenu; + //} +// RITUAL END + } + + return NULL; +} + +/* +================ +idMultiplayerGame::DisableMenu +================ +*/ +void idMultiplayerGame::DisableMenu( void ) { + if ( currentMenu == 1 ) { + mainGui->Activate( false, gameLocal.time ); + } else if ( currentMenu == 2 ) { + msgmodeGui->Activate( false, gameLocal.time ); + } else if( currentMenu == 3 ) { + statSummary->Activate( false, gameLocal.time ); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + buyMenu->Activate( false, gameLocal.time ); +// RITUAL END + } + + // copy over name from temp cvar + if( currentMenu == 1 && idStr::Cmp( cvarSystem->GetCVarString( "gui_ui_name" ), cvarSystem->GetCVarString( "ui_name" ) ) ) { + cvarSystem->SetCVarString( "ui_name", cvarSystem->GetCVarString( "gui_ui_name" ) ); + } + + currentMenu = 0; + nextMenu = 0; + cvarSystem->SetCVarBool( "ui_chat", false ); + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->disableHud = false; +//RAVEN BEGIN +//asalmon: make the scoreboard on Xenon close +#ifdef _XENON + gameLocal.GetLocalPlayer()->scoreBoardOpen = false; +#endif +//RAVEN END + if( gameLocal.GetLocalPlayer()->mphud) { + gameLocal.GetLocalPlayer()->mphud->Activate( true, gameLocal.time ); + } + } + + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = -1; + currentStatTeam = -1; +} + +/* +================ +idMultiplayerGame::SetMapShot +================ +*/ +void idMultiplayerGame::SetMapShot( void ) { +#ifdef _XENON + // Should not be used + assert( 0 ); +#else + char screenshot[ MAX_STRING_CHARS ]; + int mapNum = mapList->GetSelection( NULL, 0 ); + const idDict *dict = NULL; + if ( mapNum >= 0 ) { + dict = fileSystem->GetMapDecl( mapNum ); + } + fileSystem->FindMapScreenshot( dict ? dict->GetString( "path" ) : "", screenshot, MAX_STRING_CHARS ); + mainGui->SetStateString( "current_levelshot", screenshot ); +// RAVEN BEGIN +// cnicholson: Need to sort the material screenshot so it doesn't overlap other things + const idMaterial *mat = declManager->FindMaterial( screenshot ); + mat->SetSort( SS_GUI ); +// RAVEN END +#endif +} + +/* +================ +LocalServerRedirect +Dummy local redirect for gui rcon functionality on a local server +================ +*/ +void LocalServerRedirect( const char* string ) { + gameLocal.mpGame.ReceiveRemoteConsoleOutput( string ); +} + +/* +================ +idMultiplayerGame::HandleGuiCommands +================ +*/ +const char* idMultiplayerGame::HandleGuiCommands( const char *_menuCommand ) { + idUserInterface *currentGui; +// RAVEN BEGIN +// shouchard: removed the code that deals with these variables + //const char *voteValue; + //int vote_clientNum; +// RAVEN END + int icmd; + idCmdArgs args; + + + + if ( !_menuCommand[ 0 ] ) { + common->Printf( "idMultiplayerGame::HandleGuiCommands: empty command\n" ); + return "continue"; + } + +#ifdef _XENON + if ( currentMenu == 0 && (session->GetActiveGUI() != scoreBoard) ) { +#else + if ( currentMenu == 0 ) { +#endif + return NULL; // this will tell session to not send us events/commands anymore + } + + if ( currentMenu == 1 ) { + currentGui = mainGui; + } +#ifdef _XENON + else if (session->GetActiveGUI() != scoreBoard) { + currentGui = msgmodeGui; + } else { + currentGui = scoreBoard; + } +#else + else if( currentMenu == 2 ) { + currentGui = msgmodeGui; +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if ( currentMenu == 4 ) { + currentGui = buyMenu; +// jmartel: make sure var is initialized (compiler complained) + } else if( currentMenu == 3 ) { + currentGui = statSummary; + } else { + gameLocal.Warning( "idMultiplayerGame::HandleGuiCommands() - Unknown current menu '%d'\n", currentMenu ); + currentGui = mainGui; + } +// RITUAL END +#endif + + + args.TokenizeString( _menuCommand, false ); + + for( icmd = 0; icmd < args.Argc(); ) { + const char *cmd = args.Argv( icmd++ ); + + if ( !idStr::Icmp( cmd, ";" ) ) { + continue; + } else if ( !idStr::Icmp( cmd, "inGameMenu" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + currentGui->SetStateString( "chat", chatHistory.c_str() ); + + currentGui->SetStateInt( "player_team", gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->team : TEAM_NONE ); + + // mekberg: added + UpdateMPSettingsModel ( currentGui ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) ) { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107699" ) ); + } else { + currentGui->SetStateString( "toggleTourneyButton", common->GetLocalizedString( "#str_107700" ) ); + } + } + + currentGui->SetStateBool( "useReady", gameLocal.serverInfo.GetBool( "si_useReady", "0" ) && gameState->GetMPGameState() == WARMUP ); + if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104247" ) ); + } else if( gameLocal.serverInfo.GetBool( "si_useReady" ) && gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->IsReady() && gameState->GetMPGameState() == WARMUP ) { + currentGui->SetStateString( "readyStatus", common->GetLocalizedString( "#str_104248" ) ); + } else { + currentGui->SetStateString( "readyStatus", "" ); + } + + currentGui->SetStateBool( "si_allowVoting", gameLocal.serverInfo.GetBool( "si_allowVoting" ) ); + currentGui->SetStateBool( "si_allowVoice", gameLocal.serverInfo.GetBool( "si_voiceChat" ) ); + + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), false ); + } else { + currentGui->SetStateBool( va( "allowvote_%d", i + 1 ), true ); + } + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "video" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + if ( idStr::Icmp( vcmd, "low" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 0 ); + } else if ( idStr::Icmp( vcmd, "medium" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 1 ); + } else if ( idStr::Icmp( vcmd, "high" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 2 ); + } else if ( idStr::Icmp( vcmd, "ultra" ) == 0 ) { + cvarSystem->SetCVarInteger( "com_machineSpec", 3 ); + } else if ( idStr::Icmp( vcmd, "recommended" ) == 0 ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" ); + } + +// RAVEN BEGIN +// mekberg: set the r_mode. + cvarSystem->SetCVarInteger( "r_aspectRatio", 0 ); + currentGui->SetStateInt( "r_aspectRatio", 0 ); + currentGui->HandleNamedEvent( "forceAspect0" ); + currentGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) ); + currentGui->StateChanged( gameLocal.realClientTime ); + cvarSystem->SetCVarInteger( "r_mode", common->GetRModeForMachineSpec ( cvarSystem->GetCVarInteger( "com_machineSpec" ) ) ); + common->SetDesiredMachineSpec( cvarSystem->GetCVarInteger( "com_machineSpec" ) ); +// RAVEN END + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "execMachineSpec" ); + if ( idStr::Icmp( vcmd, "restart" ) == 0) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart\n" ); + } + + continue; + } else if ( !idStr::Icmp( cmd, "join" ) ) { + if ( args.Argc() - icmd >= 1 ) { + JoinTeam( args.Argv( icmd++ ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "quit" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "disconnect" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); + return NULL; + } else if ( !idStr::Icmp( cmd, "close" ) ) { + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "spectate" ) ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "admin" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + idStr input( currentGui->State().GetString( "admin_console_input" ) ); + input.StripTrailing( "\n" ); + //jshepard: check to see if this is a server before using rcon! + if( gameLocal.isServer ) { + char redirectBuffer[ RCON_HISTORY_SIZE ]; + common->BeginRedirect( (char *)redirectBuffer, sizeof( redirectBuffer ), LocalServerRedirect ); + + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + ReceiveRemoteConsoleOutput( input.c_str() ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "%s\n", input.c_str() ) ); + } + + common->EndRedirect(); + } else { + if( !igArg.Icmp( "tab" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon tabComplete \"%s\"\n", input.c_str() ) ); + } else if( !igArg.Icmp( "command" ) ) { + currentGui->SetStateString( "admin_console_input", "" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "rcon \"%s\"\n", input.c_str() ) ); + ReceiveRemoteConsoleOutput( input.c_str() ); + } + } + } + continue; + } else if ( !idStr::Icmp( cmd, "chatmessage" ) ) { + int mode = currentGui->State().GetInt( "messagemode" ); + idStr text = currentGui->GetStateString( "chattext" ); +// RAVEN BEGIN +// bdube: dont send chat message if there was no text specified + if ( !text.IsEmpty() ) { + text.Replace( "&", "&" ); + text.Replace( "\\", "&bsl;" ); + if ( mode ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "sayTeam \"%s\"", text.c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text.c_str() ) ); + } + } +// RAVEN BEGIN + currentGui->SetStateString( "chattext", "" ); + if ( currentMenu == 1 || currentMenu == 3 ) { + return "continue"; + } else { + DisableMenu(); + return NULL; + } + } else if ( !idStr::Icmp( cmd, "toggleReady" ) ) { + ToggleReady( ); + DisableMenu( ); + return NULL; + } else if ( !idStr::Icmp( cmd, "play" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr snd = args.Argv( icmd++ ); + int channel = 1; + if ( snd.Length() == 1 ) { + channel = atoi( snd ); + snd = args.Argv( icmd++ ); + } + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, snd, channel ); + } + continue; + } else if ( !idStr::Icmp( cmd, "callVote" ) ) { +// RAVEN BEGIN +// shouchard: new functionality to match the new interface + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + // kick + int uiKickSelection = mainGui->State().GetInt( "playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + voteData.m_kick = kickVoteMap[ uiKickSelection ]; + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + // restart + if ( 0 != mainGui->State().GetInt( "vote_val2_sel" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } + + if ( 0 != mainGui->State().GetBool( "si_shuffleteams" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_SHUFFLE; + } + // map + int uiMapSelection = mainGui->State().GetInt( "mapList_sel_0" ); + if ( -1 != uiMapSelection ) { +// rjohnson: code commented out below would get the text friendly name of the map and not the file name + int mapNum = mainGui->State().GetInt( va( "mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + voteData.m_map = dict->GetString( "path" ); + voteData.m_fieldFlags |= VOTEFLAG_MAP; + } +// const char *mapName = mainGui->State().GetString( va( "mapList_item_%d", uiMapSelection ) ); +// if ( NULL != mapName && '\0' != mapName[0] ) { +// if ( mapFileName[ 0 ] ) { +// voteData.m_map = va( "mp/%s", mapName ); +// voteData.m_fieldFlags |= VOTEFLAG_MAP; +// } + } + // gametype + // todo: need a function for switching between gametype strings and values + int uiGameTypeInt = mainGui->GetStateInt( "currentGametype" ); + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gametype" ); + int serverGameTypeInt = GameTypeToVote( currentGameTypeString ); + + if ( uiGameTypeInt != serverGameTypeInt ) { + voteData.m_gameType = uiGameTypeInt; + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + } + // time limit + int uiTimeLimit = mainGui->GetStateInt( "timeLimit" ); + if ( uiTimeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + voteData.m_timeLimit = uiTimeLimit; + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + } + // autobalance + int uiBalanceTeams = mainGui->GetStateInt( "vote_val6_sel" ); + if ( uiBalanceTeams != gameLocal.serverInfo.GetInt( "si_autobalance" ) ) { + voteData.m_teamBalance = uiBalanceTeams; + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + } + // allow spectators + /* int uiAllowSpectators = mainGui->GetStateInt( "vote_val7_sel" ); + if ( uiAllowSpectators != gameLocal.serverInfo.GetInt( "si_spectators" ) ) { + voteData.m_spectators = uiAllowSpectators; + voteData.m_fieldFlags |= VOTEFLAG_SPECTATORS; + } */ + // minimum players + int uiBuying = mainGui->GetStateInt( "buying" ); + if ( uiBuying != gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + voteData.m_buying = uiBuying; + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + } + // roundlimit (tourney only) + int uiTourneyLimit = mainGui->GetStateInt( "tourneylimit" ); + if ( uiTourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + voteData.m_tourneyLimit = uiTourneyLimit; + voteData.m_fieldFlags |= VOTEFLAG_TOURNEYLIMIT; + } + // capturelimit (ctf only) + int uiCaptureLimit = mainGui->GetStateInt( "capturelimit" ); + if ( uiCaptureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + voteData.m_captureLimit = uiCaptureLimit; + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + } + // controltime (deadzone only) + int uiControlTime = mainGui->GetStateInt( "controlTime" ); + if ( uiControlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + voteData.m_controlTime = uiControlTime; + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + } + // fraglimit (DM & TDM only) + int uiFragLimit = mainGui->GetStateInt( "fraglimit" ); + if ( uiFragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + voteData.m_fragLimit = uiFragLimit; + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + } + DisableMenu(); + + // clear any disallowed votes + int disallowedVotes = gameLocal.serverInfo.GetInt( "si_voteFlags" ); + for( int i = 0; i < NUM_VOTES; i++ ) { + if( disallowedVotes & (1 << i) ) { + voteData.m_fieldFlags &= ~(1 << i); + } + } + + // this means we haven't changed anything + if ( 0 == voteData.m_fieldFlags ) { + //AddChatLine( common->GetLocalizedString( "#str_104400" ) ); + } else { + ClientCallPackedVote( voteData ); + } + /* + // sjh: original doom code here + vote_flags_t voteIndex = (vote_flags_t)mainGui->State().GetInt( "voteIndex" ); + if ( voteIndex == VOTE_MAP ) { + int mapNum = mapList->GetSelection( NULL, 0 ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + if ( dict ) { + ClientCallVote( VOTE_MAP, dict->GetString( "path" ) ); + } + } + } else { + voteValue = mainGui->State().GetString( "str_voteValue" ); + if ( voteIndex == VOTE_KICK ) { + vote_clientNum = kickVoteMap[ atoi( voteValue ) ]; + ClientCallVote( voteIndex, va( "%d", vote_clientNum ) ); + } else { + ClientCallVote( voteIndex, voteValue ); + } + } + */ + return NULL; + } else if ( !idStr::Icmp( cmd, "voteYes" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteNo" ) ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "click_playerList" ) ) { + // push data into the name field + int sel = mainGui->GetStateInt( "playerList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "playerKick", "" ); + } else { + mainGui->SetStateString( "playerKick", kickVoteMapNames[ sel ] ); + } + continue; + } else if ( !idStr::Icmp( cmd, "click_voteMapList" ) ) { + int sel = mainGui->GetStateInt( "mapList_sel_0" ); + if ( -1 == sel ) { + mainGui->SetStateString( "mapName", "" ); + } else { + mainGui->SetStateString( "mapName", mainGui->GetStateString( va( "mapList_item_%d", sel ) ) ); + } + continue; + } else if ( !idStr::Icmp( cmd, "setVoteMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + SetVoteMapList(); +#endif + continue; + } else if ( !idStr::Icmp( cmd, "setVoteData" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + // push data into the vote_ cvars so the UI can start at where we currently are + int players; + for ( players=0; playersSetStateString( va( "playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "playerList_item_%d", players ) ); + } + mainGui->SetStateString( "playerList_sel_0", "-1" ); + mainGui->SetStateString( "playerKick", "" ); + mainGui->SetStateInt( "vote_val2_sel", 0 ); + + +// RAVEN BEGIN +// mekberg: get localized string. + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + mainGui->SetStateString( "mapName", mapName ); +// RAVEN END + + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "currentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "vote_val6_sel", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "si_shuffleteams", 0 ); + mainGui->StateChanged( gameLocal.time ); + mainGui->HandleNamedEvent( "gametypeChange" ); +#endif + + SetVoteMapList(); + continue; + } else if ( !idStr::Icmp( cmd, "populateServerInfo" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + mainGui->SetStateString( "serverInfoList_item_0", va( "%s:\t%s", common->GetLocalizedString( "#str_107725" ), gameLocal.serverInfo.GetString( "si_name" ) ) ); + idStr serverAddress = networkSystem->GetServerAddress( ); + mainGui->SetStateString( "serverInfoList_item_1", va( "%s:\t%s", common->GetLocalizedString( "#str_107726" ), serverAddress.c_str() ) ); + mainGui->SetStateString( "serverInfoList_item_2", va( "%s:\t%s", common->GetLocalizedString( "#str_107727" ), LocalizeGametype() ) ); + + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } +// rhummer localized "map name" + mainGui->SetStateString( "serverInfoList_item_3", va( "%s\t%s", common->GetLocalizedString( "#str_107730" ), mapName ) ); + const char *gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107661" ), gameLocal.serverInfo.GetString( "si_captureLimit" ) ) ); + } + else if ( 0 == idStr::Icmp( gameType, "DM" ) || 0 == idStr::Icmp( gameType, "Team DM" ) ) { + mainGui->SetStateString( "serverInfoList_item_4", va( "%s:\t%s", common->GetLocalizedString( "#str_107660" ), gameLocal.serverInfo.GetString( "si_fragLimit" ) ) ); + } + mainGui->SetStateString( "serverInfoList_item_5", va( "%s:\t%s", common->GetLocalizedString( "#str_107659" ), gameLocal.serverInfo.GetString( "si_timeLimit" ) ) ); + mainGui->SetStateString( "serverInfoList_item_6", va( "%s:\t%s", common->GetLocalizedString( "#str_107662" ), gameLocal.serverInfo.GetString( "si_pure" ) ) ); + mainGui->SetStateString( "serverInfoList_item_7", va( "%s:\t%s", common->GetLocalizedString( "#str_107663" ), gameLocal.serverInfo.GetString( "si_maxPlayers" ) ) ); + mainGui->SetStateString( "serverInfoList_item_8", va( "%s:\t%s", common->GetLocalizedString( "#str_107664" ), gameLocal.serverInfo.GetString( "si_teamDamage" ) ) ); + mainGui->SetStateString( "serverInfoList_item_9", va( "%s:\t%s", common->GetLocalizedString( "#str_104254" ), gameLocal.serverInfo.GetString( "si_spectators" ) ) ); +#endif + continue; + // handler for the server admin tab (normal stuff) + } else if ( !idStr::Icmp( cmd, "checkAdminPass" )) { + //password has been added, so call the rcon verifypassword command + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon verifyRconPass" ); + continue; + + } else if ( !idStr::Icmp( cmd, "initServerAdmin" ) ) { + mainGui->SetStateInt( "admin_server_val1_sel", 0 ); // restart defaults to off + // maplist handled in initServerAdminMaplist; this needs to be called first + // to properly set the gametype since we read it back to show an appropriate list + const char *currentGameTypeString = gameLocal.serverInfo.GetString( "si_gameType" ); + int uiGameTypeInt = GameTypeToVote( currentGameTypeString ); + mainGui->SetStateInt( "admincurrentGametype", uiGameTypeInt ); + mainGui->SetStateInt( "sa_timelimit", gameLocal.serverInfo.GetInt( "si_timeLimit" ) ); + mainGui->SetStateInt( "sa_tourneylimit", gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ); + mainGui->SetStateInt( "sa_capturelimit", gameLocal.serverInfo.GetInt( "si_captureLimit" ) ); + mainGui->SetStateInt( "sa_controlTime", gameLocal.serverInfo.GetInt( "si_controlTime" ) ); + mainGui->SetStateInt( "sa_autobalance", gameLocal.serverInfo.GetInt( "si_autobalance" ) ); + mainGui->SetStateInt( "sa_buying", gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ); + mainGui->SetStateInt( "sa_fraglimit", gameLocal.serverInfo.GetInt( "si_fraglimit" ) ); + mainGui->SetStateInt( "sa_shuffleteams", 0 ); +// mekberg: get the ban list if not server + if ( !gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GETADMINBANLIST ) ; + networkSystem->ClientSendReliableMessage( outMsg ); + } + + mainGui->StateChanged( gameLocal.time ); + + continue; + // handler for populating the map list; called both on open and on change gametype so it'll show the right maps + } else if ( !idStr::Icmp( cmd, "initServerAdminMapList" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + SetSAMapList(); +#endif + continue; + // handler for updating the current map in the name + } else if ( !idStr::Icmp( cmd, "serverAdminUpdateMap" ) ) { + int mapSelection = mainGui->GetStateInt( "sa_mapList_sel_0" ); + if ( -1 == mapSelection ) { + + const idDict *mapDict = fileSystem->GetMapDecl( gameLocal.serverInfo.GetString( "si_map" ) ); + if ( mapDict ) { + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( mapDict->GetString( "name" )) ); + } else { + mainGui->SetStateString( "sa_mapName", gameLocal.serverInfo.GetString( "si_map" ) ); + } + + } else { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", mapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + mainGui->SetStateString( "sa_mapName", common->GetLocalizedString( dict->GetString( "name" )) ); + } + } + continue; + // handler for initializing the player list on the admin player tab + } else if ( !idStr::Icmp( cmd, "initServerAdminPlayer" ) ) { + int players; + for ( players=0; playersSetStateString( va( "sa_playerList_item_%d", players ), kickVoteMapNames[players] ); + } + if ( players < MAX_CLIENTS ) { + mainGui->DeleteStateVar( va( "sa_playerList_item_%d", players ) ); + //common->Printf( "DELETING at slot %d\n", players ); + } + mainGui->SetStateString( "sa_playerList_sel_0", "-1" ); + continue; + // handler for actually changing something on the server admin tab + } else if ( !idStr::Icmp( cmd, "handleServerAdmin" ) ) { + // read in a bunch of data, pack it into the appropriate structure + serverAdminData_t data; + memset( &data, 0, sizeof( data ) ); + data.restartMap = 0 != mainGui->GetStateInt( "admin_server_val1_sel" ); + // map list here + int uiMapSelection = mainGui->State().GetInt( "sa_mapList_sel_0" ); + if (-1 != uiMapSelection ) { + int mapNum = mainGui->State().GetInt( va( "sa_mapList_item_%d_id", uiMapSelection ) ); + if ( mapNum >= 0 ) { + const idDict *dict = fileSystem->GetMapDecl( mapNum ); + data.mapName = common->GetLocalizedString( dict->GetString( "path" )); + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + } else { + data.mapName = gameLocal.serverInfo.GetString( "si_map" ); + } + + switch ( mainGui->GetStateInt( "admincurrentGametype" ) ) { + case VOTE_GAMETYPE_DM: + data.gameType = GAME_DM; + break; + case VOTE_GAMETYPE_TOURNEY: + data.gameType = GAME_TOURNEY; + break; + case VOTE_GAMETYPE_TDM: + data.gameType = GAME_TDM; + break; + case VOTE_GAMETYPE_CTF: + data.gameType = GAME_CTF; + break; + case VOTE_GAMETYPE_ARENA_CTF: + data.gameType = GAME_ARENA_CTF; + break; + case VOTE_GAMETYPE_DEADZONE: + data.gameType = GAME_DEADZONE; + } + data.captureLimit = mainGui->GetStateInt( "sa_captureLimit" ); + data.fragLimit = mainGui->GetStateInt( "sa_fragLimit" ); + data.tourneyLimit = mainGui->GetStateInt( "sa_tourneylimit" ); + data.timeLimit = mainGui->GetStateInt( "sa_timeLimit" ); + data.buying = mainGui->GetStateInt( "sa_buying" ); + data.autoBalance = 0 != mainGui->GetStateInt( "sa_autobalance" ); + data.buying = 0 != mainGui->GetStateInt( "sa_buying" ); + data.controlTime = mainGui->GetStateInt( "sa_controlTime" ); + data.shuffleTeams = 0 != mainGui->GetStateInt( "sa_shuffleteams" ); + + // make the call to change the server data + if ( gameLocal.mpGame.HandleServerAdminCommands( data ) ) { + DisableMenu(); + return NULL; + } + continue; + // handler for the kick button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminKick" ) ) { + int uiKickSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiKickSelection ) { + HandleServerAdminKickPlayer( kickVoteMap[ uiKickSelection ] ); + DisableMenu(); + return NULL; + } + //common->Printf( "HANDLE SERVER ADMIN KICK!\n" ); + continue; + // handler for the ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiBanSelection ) { + HandleServerAdminBanPlayer( kickVoteMap[ uiBanSelection ] ); + DisableMenu(); + mainGui->DeleteStateVar( va( "sa_banList_item_%d", 0 ) ); + mainGui->SetStateString( "sa_banList_sel_0", "-1" ); + return NULL; + } + continue; + // handler for the remove ban button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminRemoveBan" ) ) { + //common->Printf( "HANDLE SERVER ADMIN REMOVE BAN!\n" ); + int uiBanSelection = mainGui->State().GetInt( "sa_banList_sel_0" ); + if ( -1 != uiBanSelection ) { + idStr guid = &mainGui->GetStateString( va( "sa_banList_item_%d", uiBanSelection ) )[ 4 ]; + guid = guid.ReplaceChar( '\t', '\0' ); + guid = &guid.c_str()[ strlen( guid.c_str() ) + 1 ]; + HandleServerAdminRemoveBan( guid.c_str() ); + DisableMenu(); + return NULL; + } + continue; + // handler for the switch teams button on the player tab of the server admin gui + } else if ( !idStr::Icmp( cmd, "handleServerAdminSwitchTeams" ) ) { + if ( gameLocal.IsTeamGame() ) { + int uiSwitchSelection = mainGui->State().GetInt( "sa_playerList_sel_0" ); + if ( -1 != uiSwitchSelection ) { + HandleServerAdminForceTeamSwitch( kickVoteMap[ uiSwitchSelection ] ); + DisableMenu(); + return NULL; + } + } + continue; + // handler for the show ban list button of the server admin gui + } else if ( !idStr::Icmp( cmd, "populateBanList" ) ) { + gameLocal.PopulateBanList( mainGui ); + continue; +// RAVEN END + } else if ( !idStr::Icmp( cmd, "voteyes" ) ) { + CastVote( gameLocal.localClientNum, true ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "voteno" ) ) { + CastVote( gameLocal.localClientNum, false ); + DisableMenu(); + return NULL; + } else if ( !idStr::Icmp( cmd, "bind" ) ) { + if ( args.Argc() - icmd >= 2 ) { + idStr key = args.Argv( icmd++ ); + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "bindunbindtwo \"%s\" \"%s\"", key.c_str(), bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "clearbind" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr bind = args.Argv( icmd++ ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "unbind \"%s\"", bind.c_str() ) ); + mainGui->SetKeyBindingNames(); + } + continue; + } else if ( !idStr::Icmp( cmd, "MAPScan" ) ) { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" ); + if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) { + gametype = "DM"; + } + + int i, num; + idStr si_map = gameLocal.serverInfo.GetString("si_map"); + const idDict *dict; + + mapList->Clear(); + mapList->SetSelection( -1 ); + num = fileSystem->GetNumMaps(); + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if ( dict ) { + // any MP gametype supported + bool isMP = false; + int igt = GAME_SP + 1; + while ( si_gameTypeArgs[ igt ] ) { + if ( dict->GetBool( si_gameTypeArgs[ igt ] ) ) { + isMP = true; + break; + } + igt++; + } + if ( isMP ) { + const char *mapName = dict->GetString( "name" ); + if ( mapName[0] == '\0' ) { + mapName = dict->GetString( "path" ); + } + mapName = common->GetLocalizedString( mapName ); + mapList->Add( i, mapName ); + if ( !si_map.Icmp( dict->GetString( "path" ) ) ) { + mapList->SetSelection( mapList->Num() - 1 ); + } + } + } + } + // set the current level shot + SetMapShot( ); +#endif + return "continue"; + } else if ( !idStr::Icmp( cmd, "click_maplist" ) ) { + SetMapShot( ); + return "continue"; + } else if ( !idStr::Icmp( cmd, "sm_select_player" ) ) { + idStr vcmd; + if ( args.Argc() - icmd >= 1 ) { + vcmd = args.Argv( icmd++ ); + } + + int index = atoi( vcmd.c_str() ); + if( index > 0 && index < MAX_CLIENTS && statSummary && currentMenu == 3 ) { + statManager->UpdateEndGameHud( statSummary, index - 1 ); + } + return "continue"; + } else if ( !idStr::Icmp( cmd, "update_model" ) ) { + UpdateMPSettingsModel( currentGui ); + continue; + } else if( !idStr::Icmp( cmd, "ingameStats" ) ) { + if ( args.Argc() - icmd >= 1 ) { + idStr igArg = args.Argv( icmd++ ); + if( !igArg.Icmp( "init" ) ) { + // setup the player list + statManager->SetupStatWindow( currentGui ); + } else if( !igArg.Icmp( "spectator" ) ) { + int currentSel = currentGui->State().GetInt( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MAX ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MAX; + } else if( !igArg.Icmp( "dm" ) ) { + int currentSel = currentGui->State().GetInt( "dm_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, 0 ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = 0; + } else if( !igArg.Icmp( "strogg" ) ) { + int currentSel = currentGui->State().GetInt( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_STROGG ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_STROGG; + } else if( !igArg.Icmp( "marine" ) ) { + int currentSel = currentGui->State().GetInt( "team_1_names_sel_0", "-1" ); + currentGui->SetStateString( "spec_names_sel_0", "-1" ); + currentGui->SetStateString( "team_2_names_sel_0", "-1" ); + currentGui->SetStateString( "dm_names_sel_0", "-1" ); + + statManager->SelectStatWindow( currentSel, TEAM_MARINE ); + // asalmon: Need to refresh stats periodically if the player is looking at stats + currentStatClient = currentSel; + currentStatTeam = TEAM_MARINE; + } + } + continue; + } else if( !idStr::Icmp( cmd, "mainMenu" ) ) { + DisableMenu(); + static idStr menuCmd; + menuCmd.Clear(); // cnicholson: In order to avoid repeated eventnames from screwing up the menu system, clear it. + menuCmd.Append( "main" ); + const char* eventName = ""; + if( args.Argc() - icmd >= 1 ) { + eventName = args.Argv( icmd++ ); + menuCmd.Append( " " ); + menuCmd.Append( eventName ); + } + return menuCmd.c_str(); + } +// RAVEN BEGIN +// cnicholson: The menu calls this prior to entering multiplayer settings. What it does is to check the current crosshair, and compare it +// agasint the list of crosshairs in player.def under the player_marine_mp section. If it finds a match, it assigns the +// crosshair to the next one in the list. If there isn't one, or if its the end of the list, the first found crosshair is used. + else if ( !idStr::Icmp( cmd, "chooseCrosshair" ) ) { +#ifndef _XENON + +#ifndef _XENON + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp", false, true ) ); +#else + bool insideLevelLoad = declManager->GetInsideLoad(); + if ( !insideLevelLoad ) { + declManager->SetInsideLoad( true ); + } + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, false ) ); + declManager->SetInsideLoad( insideLevelLoad ); +#endif + + idStr currentCrosshair = cvarSystem->GetCVarString("g_crosshairCustomFile"); + + const idKeyValue* kv = def->dict.MatchPrefix("mtr_crosshair", NULL); + + while ( kv ) { + if ( kv->GetValue() == currentCrosshair.c_str() ) { + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + break; + } + kv = def->dict.MatchPrefix("mtr_crosshair", kv ); + } + + if ( !kv ){ + kv = def->dict.MatchPrefix("mtr_crosshair", NULL ); + } + + idStr newCrosshair(kv->GetValue()); + + mainGui->SetStateString ( "crossImage", newCrosshair.c_str()); + const idMaterial *material = declManager->FindMaterial( newCrosshair.c_str() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + cvarSystem->SetCVarString("g_crosshairCustomFile", newCrosshair.c_str()); +#endif + } +// RAVEN END + else if( !idStr::Icmp( cmd, "friend" ) ) { + // we friend/unfriend from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + if( ( client < 0 || client >= MAX_CLIENTS ) || !gameLocal.GetLocalPlayer() ) { + continue; + } + + // un-mark this client as a friend + if( gameLocal.GetLocalPlayer() ) { + if( gameLocal.GetLocalPlayer()->IsFriend( client ) ) { + networkSystem->RemoveFriend( client ); + } else { + networkSystem->AddFriend( client ); + } + } + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + continue; + } else if( !idStr::Icmp( cmd, "mute" ) ) { + // we mute/unmute from the stat window, so use that to get selection info + int selectionTeam = -1; + int selectionIndex = -1; + + // get the selected client num, as well as the selectionIndex/Team from the stat window + int client = statManager->GetSelectedClientNum( &selectionIndex, &selectionTeam ); + + + if ( gameLocal.GetLocalPlayer() ) { + ClientVoiceMute( client, !gameLocal.GetLocalPlayer()->IsPlayerMuted( client ) ); + } + + // refresh with new info + statManager->SetupStatWindow( currentGui ); + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + + continue; + } +//RAVEN BEGIN +//asalmon: pass through some commands that need to be handled in the main menu handle function + else if(strstr( cmd, "LiveInviteAccept" ) == cmd){ +#ifdef _XENON + Live()->SetInvite(); +#endif + } + else if ((strstr( cmd, "FilterMPMapList" ) == cmd) + || (strstr( cmd, "AddMapLive" ) == cmd) + || (strstr( cmd, "RemoveMapLive" ) == cmd) + ) { + static idStr menuCmd; + menuCmd.Clear(); + menuCmd.Append( cmd ); + return menuCmd.c_str(); + } else if( !idStr::Icmp( cmd, "toggleTourney" ) ) { + if( gameLocal.gameType == GAME_TOURNEY ) { + ToggleSpectate(); + DisableMenu( ); + return NULL; + } + continue; + } +//RAVEN END + common->Printf( "idMultiplayerGame::HandleGuiCommands: '%s' unknown\n", cmd ); + + } + return "continue"; +} + +/* +=============== +idMultiplayerGame::SetShaderParms +=============== +*/ +void idMultiplayerGame::SetShaderParms( renderView_t *view ) { + if ( gameLocal.IsFlagGameType() ) { + view->shaderParms[ 1 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_MARINE ) != FS_AT_BASE ); + view->shaderParms[ 2 ] = ( ((rvCTFGameState*)GetGameState())->GetFlagState( TEAM_STROGG ) != FS_AT_BASE ); + } +} + +/* +================ +idMultiplayerGame::Draw +server demo: clientNum == MAX_CLIENTS +================ +*/ +bool idMultiplayerGame::Draw( int clientNum ) { + idPlayer *player, *viewPlayer; + idUserInterface *hud = NULL; + + if ( clientNum == MAX_CLIENTS ) { +// assert( gameLocal.GetDemoState() == DEMO_PLAYING ); + clientNum = ENTITYNUM_NONE; + } + + player = viewPlayer = static_cast( gameLocal.entities[ clientNum ] ); + + if ( player == NULL ) { + return false; + } + + if ( player->spectating ) { + viewPlayer = static_cast( gameLocal.entities[ player->spectator ] ); + if ( viewPlayer == NULL ) { + return false; + } + } + + if ( !viewPlayer->GetRenderView() ) { + return false; + } + + SetShaderParms( viewPlayer->GetRenderView() ); + + // use the hud of the local player + if ( !hud ) { + hud = player->hud; + } + viewPlayer->playerView.RenderPlayerView( hud ); + + // allow force scoreboard to overwrite a fullscreen menu + if ( currentMenu ) { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( !bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 1, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = true; + } +#endif + if ( player->wantSpectate ) { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104249" ) ); + } else { + mainGui->SetStateString( "spectext", common->GetLocalizedString( "#str_104250" ) ); + } + // if we died, isChatting is cleared, so re-set our chatting cvar + if ( gameLocal.GetLocalPlayer() && !gameLocal.GetLocalPlayer()->IsFakeClient() && !gameLocal.GetLocalPlayer()->isChatting && !gameLocal.GetLocalPlayer()->pfl.dead ) { + cvarSystem->SetCVarBool( "ui_chat", true ); + cvarSystem->SetModifiedFlags( CVAR_USERINFO ); // force update + } + if ( currentMenu == 1 ) { + UpdateMainGui(); + mainGui->Redraw( gameLocal.time ); + } else if( currentMenu == 2 ) { + msgmodeGui->Redraw( gameLocal.time ); + } else if( currentMenu == 3 ) { + DrawStatSummary(); +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + } else if( currentMenu == 4 ) { + SetupBuyMenuItems(); + player->UpdateHudStats( buyMenu ); + buyMenu->HandleNamedEvent( "update_buymenu" ); + idPlayer* player = gameLocal.GetLocalPlayer(); + buyMenu->SetStateString( "field_credits", va("%i", (int)player->buyMenuCash) ); + buyMenu->Redraw(gameLocal.time); +// RITUAL END + } + } else { +#if 0 + // uncomment this if you want to track when players are in a menu + if ( bCurrentMenuMsg ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); + + bCurrentMenuMsg = false; + } +#endif + DrawScoreBoard( player ); + } + +// RAVEN BEGIN +// bdube: debugging HUD + gameDebug.DrawHud(); +// RAVEN END + return true; +} + +/* +================ +idMultiplayerGame::UpdateHud +================ +*/ +void idMultiplayerGame::UpdateHud( idUserInterface* _mphud ) { + idPlayer *localPlayer; + + if ( !_mphud ) { + return; + } + + // server demos don't have a true local player, but need one for hud updates + localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.IsServerDemoPlaying() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + +//RAVEN BEGIN +//asalmon: Turn on/off the lag icon so that clients know that they are losing connection + if ( networkSystem->ClientGetTimeSinceLastPacket() > 0 && ( networkSystem->ClientGetTimeSinceLastPacket() > cvarSystem->GetCVarInteger("net_clientServerTimeout")*500 ) ) { + _mphud->SetStateBool("IsLagged", true); + } + else{ + _mphud->SetStateBool("IsLagged", false); + } +//RAVEN END + + _mphud->SetStateInt( "marine_score", teamScore[ TEAM_MARINE ] ); + _mphud->SetStateInt( "strogg_score", teamScore[ TEAM_STROGG ] ); + + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + + // Always show GameTime() for WARMUP and COUNTDOWN. + mpGameState_t state = gameState->GetMPGameState(); + _mphud->SetStateString( "timeleft", GameTime() ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + /// Set "credits" gui element + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + int cash = 0; + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + assert( gameLocal.IsServerDemoPlaying() ); + assert( gameLocal.GetDemoFollowClient() >= 0 ); + assert( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] && gameLocal.entities[ gameLocal.GetDemoFollowClient() ]->IsType( idPlayer::GetClassType() ) ); + localPlayer = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + + idPlayer* specPlayer = NULL; + if ( localPlayer->spectating ) + specPlayer = gameLocal.GetClientByNum( localPlayer->spectator ); + + if ( specPlayer ) + cash = (int)specPlayer->buyMenuCash; + else + cash = (int)localPlayer->buyMenuCash; + + if( localPlayer->CanBuy() ) { + _mphud->SetStateString("credits", va("%s %d %s", common->GetLocalizedString( "#str_122015" ), + cash, common->GetLocalizedString( "#str_122016" ))); + } + else { + _mphud->SetStateString("credits", va("%s %d", common->GetLocalizedString( "#str_122015" ), cash)); + } + } + else + { + _mphud->SetStateString("credits", ""); + } +// RITUAL END + + + bool inNonTimedState = (state == SUDDENDEATH) || (state == WARMUP) || (state == GAMEREVIEW); + bool inCountdownState = (state == COUNTDOWN); + if( gameLocal.gameType == GAME_TOURNEY ) { + inNonTimedState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_SUDDEN_DEATH); + inCountdownState |= (((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ).GetState() == AS_WARMUP); + } + _mphud->SetStateBool( "infinity", ( !timeLimit && !inCountdownState ) || inNonTimedState ); + + if( gameLocal.gameType == GAME_DM ) { + if( rankedPlayers.Num() ) { + _mphud->SetStateString( "player1_name", rankedPlayers[ 0 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player1_score", va( "%d", GetScore( rankedPlayers[ 0 ].First() ) ) ); + _mphud->SetStateString( "player1_rank", "1." ); + + // if we're in the lead or spectating, show the person in 2nd + if( ( (rankedPlayers[ 0 ].First() == localPlayer) || (localPlayer->spectating) ) && rankedPlayers.Num() > 1 ) { + _mphud->SetStateString( "player2_name", rankedPlayers[ 1 ].First()->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( rankedPlayers[ 1 ].First() ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", rankedPlayers[ 1 ].First()->GetRank() + 1 ) ); + } else if( rankedPlayers[ 0 ].First() != localPlayer && !localPlayer->spectating ) { + // otherwise, show our score + _mphud->SetStateString( "player2_name", localPlayer->GetUserInfo()->GetString( "ui_name" ) ); + _mphud->SetStateString( "player2_score", va( "%d", GetScore( localPlayer ) ) ); + _mphud->SetStateString( "player2_rank", va( "%d.", localPlayer->GetRank() + 1 ) ); + } else { + // no person to place in 2nd + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } else { + _mphud->SetStateString( "player1_name", "" ); + _mphud->SetStateString( "player1_score", "" ); + _mphud->SetStateString( "player1_rank", "" ); + + _mphud->SetStateString( "player2_name", "" ); + _mphud->SetStateString( "player2_score", "" ); + _mphud->SetStateString( "player2_rank", "" ); + } + } + + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if( gameLocal.gameType == GAME_DEADZONE ) { + + static int lastMarineScore = teamScore[ TEAM_MARINE ]; + static int lastStroggScore = teamScore[ TEAM_STROGG ]; + int marineScore = teamScore[ TEAM_MARINE ]; + int stroggScore = teamScore[ TEAM_STROGG ]; + const float asymptoticAverageWeight = 0.95f; + + /// Check if Marines have scored since last frame + if( marineScore != lastMarineScore ) + { + /// Pulse the bar's color + marineScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_MARINE ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + marineScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Check if Strogg have scored since last frame + if( stroggScore != lastStroggScore ) + { + /// Pulse the bar's color + stroggScoreBarPulseAmount = 1.0f; + + // Play the pulse sound + idStr pulseSnd = "snd_dzpulse_happy"; + if ( localPlayer->team != TEAM_STROGG ) + pulseSnd = "snd_dzpulse_unhappy"; + + localPlayer->StartSound( pulseSnd, SND_CHANNEL_ANY, 0, false, NULL ); + } + else + { + /// Asymptotic-average back to the normal color + stroggScoreBarPulseAmount *= asymptoticAverageWeight; + } + + /// Set "gameStatus" gui element + _mphud->SetStateString("gameStatus", "" ); + + _mphud->SetStateFloat( "marine_pulse_amount", marineScoreBarPulseAmount ); + _mphud->SetStateFloat( "strogg_pulse_amount", stroggScoreBarPulseAmount ); + + lastMarineScore = teamScore[ TEAM_MARINE ]; + lastStroggScore = teamScore[ TEAM_STROGG ]; + } + // RITUAL END + + if( gameLocal.gameType == GAME_TOURNEY && localPlayer->GetArena() == MAX_ARENAS ) { + int numWaitingArenaPlayers = 0; + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( rankedPlayers[ i ].First() && rankedPlayers[ i ].First()->GetArena() == MAX_ARENAS ) { + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers++ ), rankedPlayers[ i ].First()->GetUserInfo()->GetString( "ui_name" ) ); + } + } + _mphud->SetStateString( va( "waitRoom_item_%d", numWaitingArenaPlayers ), "" ); + _mphud->SetStateBool( "waitroom", true ); + _mphud->SetStateInt( "num_waitroom_players", numWaitingArenaPlayers ); + } else { + _mphud->SetStateBool( "waitroom", false ); + } + + idStr spectateText0; + idStr spectateText1; + idStr spectateText2; + + if( gameLocal.gameType == GAME_TOURNEY ) { + // line 1 - why we aren't playing + if( localPlayer->wantSpectate ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + } else { + rvTourneyArena& currentArena = ((rvTourneyGameState*)gameState)->GetArena( localPlayer->GetArena() ); + if( gameState->GetMPGameState() == WARMUP ) { + // grab the reason we aren't playing yet + AllPlayersReady( &spectateText0 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText0 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ELIMINATED ) { + spectateText0 = common->GetLocalizedString( "#str_107687" ); + } else if( gameState->GetMPGameState() != GAMEREVIEW && localPlayer->GetTourneyStatus() == PTS_ADVANCED ) { + spectateText0 = common->GetLocalizedString( "#str_107688" ); + } else if( ((rvTourneyGameState*)gameState)->HasBye( localPlayer ) ) { + spectateText0 = common->GetLocalizedString( "#str_107709" ); + } else if( currentArena.IsPlaying( localPlayer ) ) { + spectateText0 = va( "%s %d; %s", common->GetLocalizedString( "#str_107716" ), localPlayer->GetArena() + 1, ((rvTourneyGameState*)gameState)->GetRoundDescription() ); + } else if( localPlayer->spectating ) { + // this should only happen if the player was spectating at start of round, but then decides + // to join the tourney + spectateText0 = common->GetLocalizedString( "#str_107684" ); + } + } + + // line 2 - will or wont be seeded, how to cycle + // line 3 - how to enter waiting room + if( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN ) { + if( localPlayer->wantSpectate ) { + spectateText1 = common->GetLocalizedString( "#str_107685" ); + spectateText2 = common->GetLocalizedString( "#str_107695" ); + } else { + spectateText1 = common->GetLocalizedString( "#str_107684" ); + spectateText2 = common->GetLocalizedString( "#str_107694" ); + } + } else if( localPlayer->spectating ) { + if( localPlayer->GetArena() == MAX_ARENAS ) { + spectateText1 = common->GetLocalizedString( "#str_107686" ); + } else { + spectateText1 = va( common->GetLocalizedString( "#str_107670" ), common->KeysFromBinding( "_impulse14" ), common->KeysFromBinding( "_impulse15" ) ); + } + } + } else { + // non-tourney spectate text + if( localPlayer->spectating ) { + if( localPlayer->spectator != localPlayer->entityNumber ) { + spectateText0 = va( common->GetLocalizedString( "#str_107672" ), gameLocal.GetClientByNum( localPlayer->spectator )->GetUserInfo()->GetString( "ui_name" ) ); + } else if( localPlayer->spectating ) { + spectateText0 = common->GetLocalizedString( "#str_107673" ); + } + + // spectating instructions + if( localPlayer->spectator != localPlayer->entityNumber ) { + //cycle & exit follow + spectateText1 = va( common->GetLocalizedString( "#str_107698" ), common->KeysFromBinding( "_attack" ), common->KeysFromBinding( "_moveup" ) ); + } else { + //start follow + spectateText1 = va( common->GetLocalizedString( "#str_108024" ), common->KeysFromBinding( "_attack" ) ); + } + + } + + if( gameState->GetMPGameState() == WARMUP ) { + AllPlayersReady( &spectateText1 ); + } else if( gameState->GetMPGameState() == COUNTDOWN ) { + spectateText1 = va( common->GetLocalizedString( "#str_107671" ), Max( ((gameState->GetNextMPGameStateTime() - gameLocal.time) / 1000) + 1, 0 ) ); + } + } + + _mphud->SetStateString( "spectatetext0", spectateText0 ); + _mphud->SetStateString( "spectatetext1", spectateText1 ); + _mphud->SetStateString( "spectatetext2", spectateText2 ); + + if( gameLocal.gameType == GAME_TOURNEY ) { + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + _mphud->StateChanged( gameLocal.time ); + + statManager->UpdateInGameHud( _mphud, ( localPlayer->usercmd.buttons & BUTTON_INGAMESTATS ) != 0 ); + + //update awards + if ( gameLocal.isClient || gameLocal.isListenServer) { + statManager->CheckAwardQueue(); + } +} + +/* +================ +idMultiplayerGame::DrawScoreBoard +================ +*/ +void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) { + if ( player->scoreBoardOpen ) { + if ( !playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( true, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = true; + player->disableHud = true; + } + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameState)->UpdateTourneyBrackets(); + } + UpdateScoreboard( scoreBoard ); + } else { + if ( playerState[ player->entityNumber ].scoreBoardUp ) { + scoreBoard->Activate( false, gameLocal.time ); + playerState[ player->entityNumber ].scoreBoardUp = false; + player->disableHud = false; + } + } +} + +/* +=============== +idMultiplayerGame::AddChatLine +=============== +*/ +void idMultiplayerGame::AddChatLine( const char *fmt, ... ) +{ + idStr s; + va_list argptr; + va_start( argptr, fmt ); + vsprintf( s, fmt, argptr ); + va_end( argptr ); + PrintChatLine( s, false ); +} + +void idMultiplayerGame::PrintChatLine( const char *message, const bool teamChat ) { + idStr text = message; + text.StripTrailingOnce("\n"); + gameLocal.Printf( "%s\n", text.c_str() ); + + wrapInfo_t wrapInfo; + idStr wrap1; + idStr wrap2; + + idUserInterface *mpHud = gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->mphud : NULL; + if ( mpHud ) { + wrap1 = text; + wrap2 = text; + do { + memset( &wrapInfo, -1, sizeof ( wrapInfo_t ) ); + mpHud->GetMaxTextIndex( "history1", wrap1.c_str( ), wrapInfo ); + + // If we have a whitespace near the end. Otherwise the user could enter a giant word. + if ( wrapInfo.lastWhitespace != -1 && float( wrapInfo.lastWhitespace ) / float( wrapInfo.maxIndex ) > .75 ) { + wrap2 = wrap1.Left( wrapInfo.lastWhitespace++ ); + + // Just text wrap, no word wrap. + } else if ( wrapInfo.maxIndex != -1 ) { + wrap2 = wrap1.Left( wrapInfo.maxIndex ); + + // We fit in less than a line. + } else { + wrap2 = wrap1; + } + + // Recalc the base string. + wrap1 = wrap2.GetLastColorCode() + wrap1.Right( wrap1.Length( ) - wrap2.Length( ) ); + + // Push to gui. + mpHud->SetStateString( "chattext", wrap2.c_str( ) ); + mpHud->HandleNamedEvent( "addchatline" ); + } while ( wrapInfo.maxIndex != -1 ); + } + + if( chatHistory.Length() + text.Length() > CHAT_HISTORY_SIZE ) { + int removeLength = chatHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + chatHistory.Empty(); + } else { + while( (chatHistory.Length() - removeLength) + text.Length() > CHAT_HISTORY_SIZE ) { + removeLength = chatHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + chatHistory.Empty(); + break; + } + } + } + chatHistory = chatHistory.Right( chatHistory.Length() - removeLength ); + } + + chatHistory.Append( text ); + chatHistory.Append( '\n' ); + + if( mainGui ) { + mainGui->SetStateString( "chat", chatHistory.c_str() ); + } + if( statSummary ) { + statSummary->SetStateString( "chat", chatHistory.c_str() ); + } + + // play chat sound + if( gameLocal.GetLocalPlayer() ) { + if ( teamChat ) { + gameLocal.GetLocalPlayer()->StartSound( "snd_teamchat", SND_CHANNEL_ANY, 0, false, NULL ); + } else { + gameLocal.GetLocalPlayer()->StartSound( "snd_chat", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +void idMultiplayerGame::DrawStatSummary( void ) { + if ( !statSummary->GetStateFloat( "ready" ) ) { + statSummary->SetStateFloat( "ready", 1 ); + statSummary->HandleNamedEvent( "chatFocus" ); + statSummary->StateChanged( gameLocal.time ); + } + statSummary->Redraw( gameLocal.time ); +} + +void idMultiplayerGame::ShowStatSummary( void ) { + if ( !gameLocal.GetLocalPlayer() ) { + assert( false ); + return; + } + DisableMenu( ); + nextMenu = 3; + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.GetLocalPlayer()->GUIMainNotice( "" ); + gameLocal.GetLocalPlayer()->GUIFragNotice( "" ); +} + +/* +================ +idMultiplayerGame::WriteToSnapshot +================ +*/ +void idMultiplayerGame::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + int value; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + + assert( MAX_CLIENTS % 8 == 0 ); +// RITUAL BEGIN - DeadZone Messages + msg.WriteBits(isBuyingAllowedRightNow, 1); + msg.WriteShort(powerupCount); + msg.WriteFloat( marineScoreBarPulseAmount ); + msg.WriteFloat( stroggScoreBarPulseAmount ); +// RITUAL END + + +// RAVEN BEGIN +// ddynerman: CTF scoring +// FIXME - not in the snapshot + for ( i = 0; i < TEAM_MAX; i++ ) { + msg.WriteShort( teamScore[i] ); + msg.WriteLong( teamDeadZoneScore[i] ); + } +// RAVEN END + + // write ingame bits first, then we only sync down for ingame clients + // do a single write, this doesn't change often it's best to deltify in a single shot + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ingame[ i / 8 ] |= 1 << ( i % 8 ); + } else { + ingame[ i / 8 ] &= ~( 1 << ( i % 8 ) ); + } + } + msg.WriteData( ingame, MAX_CLIENTS / 8 ); + + // those rarely change as well and will deltify away nicely + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + // clamp all values to min/max possible value that we can send over + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount ); + msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS ); + msg.WriteLong( playerState[i].deadZoneScore ); + value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins ); + msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS ); + // only transmit instance info in tourney + if( gameLocal.gameType == GAME_TOURNEY ) { + if( !ent ) { + msg.WriteBits( 0, 1 ); + } else { + msg.WriteBits( 1, 1 ); + value = idMath::ClampInt( 0, MAX_INSTANCES, ent->GetInstance() ); + msg.WriteBits( value, ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( ((idPlayer*)ent)->GetTourneyStatus(), ASYNC_PLAYER_TOURNEY_STATUS_BITS ); + } + } + } + } + + // those change all the time, keep them in a single pack + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping ); + msg.WriteBits( value, ASYNC_PLAYER_PING_BITS ); + } + } +} + +/* +================ +idMultiplayerGame::ReadFromSnapshot +================ +*/ +void idMultiplayerGame::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, newInstance; + byte ingame[ MAX_CLIENTS / 8 ]; + idEntity* ent; + + isBuyingAllowedRightNow = msg.ReadBits(1); + powerupCount = msg.ReadShort(); + // TTimo: NOTE: sounds excessive to be transmitting floats for that + marineScoreBarPulseAmount = msg.ReadFloat(); + stroggScoreBarPulseAmount = msg.ReadFloat(); + + // CTF/TDM scoring + for( i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = msg.ReadShort( ); + teamDeadZoneScore[ i ] = msg.ReadLong( ); + } + + msg.ReadData( ingame, MAX_CLIENTS / 8 ); + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( ingame[ i / 8 ] & ( 1 << ( i % 8 ) ) ) { + playerState[i].ingame = true; + } else { + playerState[i].ingame = false; + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + ent = gameLocal.entities[ i ]; + playerState[ i ].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[ i ].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS ); + playerState[ i ].deadZoneScore = msg.ReadLong(); + playerState[ i ].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS ); + if( gameLocal.gameType == GAME_TOURNEY ) { + if( msg.ReadBits( 1 ) ) { + newInstance = msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ); + if( newInstance != ent->GetInstance() ) { + ent->SetInstance( newInstance ); + if( gameLocal.GetLocalPlayer() && i != gameLocal.localClientNum ) { + if( ent->GetInstance() == gameLocal.GetLocalPlayer()->GetInstance() ) { + ((idPlayer*)ent)->ClientInstanceJoin(); + } else { + ((idPlayer*)ent)->ClientInstanceLeave(); + } + } + } + ((idPlayer*)ent)->SetTourneyStatus( (playerTourneyStatus_t)msg.ReadBits( ASYNC_PLAYER_TOURNEY_STATUS_BITS ) ); + } + } + } + } + + for ( i = 0; i < MAX_CLIENTS; i++ ) { + if ( playerState[i].ingame ) { + playerState[ i ].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS ); + } + } +} + +// RAVEN BEGIN +// bdube: global item sounds +/* +================ +idMultiplayerGame::PlayGlobalItemAcquireSound +================ +*/ +void idMultiplayerGame::PlayGlobalItemAcquireSound( int defIndex ) { + const idDeclEntityDef* def; + def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, defIndex, false ) ); + if ( !def ) { + gameLocal.Warning ( "NET: invalid entity def index (%d) for global item acquire sound", defIndex ); + return; + } + + if( !gameLocal.GetLocalPlayer() || !gameLocal.currentThinkingEntity || gameLocal.GetLocalPlayer()->GetInstance() == gameLocal.currentThinkingEntity->GetInstance() ) { + soundSystem->PlayShaderDirectly ( SOUNDWORLD_GAME, def->dict.GetString ( "snd_acquire" ) ); + } + + if ( gameLocal.isServer ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ITEMACQUIRESOUND ); + outMsg.WriteBits( defIndex, gameLocal.entityDefBits ); + gameLocal.ServerSendInstanceReliableMessage( gameLocal.currentThinkingEntity, -1, outMsg ); + } +} +// RAVEN END + +/* +================ +idMultiplayerGame::PrintMessageEvent +================ +*/ +void idMultiplayerGame::PrintMessageEvent( int to, msg_evt_t evt, int parm1, int parm2 ) { + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( to == -1 || ( p && to == p->entityNumber ) ) { + switch ( evt ) { + case MSG_SUICIDE: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104293" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_KILLED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104292" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_KILLEDTEAM: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104291" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_TELEFRAGGED: + assert( parm1 >= 0 && parm2 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104290" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); + break; + case MSG_DIED: + assert( parm1 >= 0 ); + AddChatLine( common->GetLocalizedString( "#str_104289" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_VOTE: + AddChatLine( common->GetLocalizedString( "#str_104288" ) ); + break; + case MSG_SUDDENDEATH: + AddChatLine( common->GetLocalizedString( "#str_104287" ) ); + break; + case MSG_FORCEREADY: + AddChatLine( common->GetLocalizedString( "#str_104286" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ parm1 ] && gameLocal.entities[ parm1 ]->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + static_cast< idPlayer * >( gameLocal.entities[ parm1 ] )->forcedReady = true; + } + break; + case MSG_JOINEDSPEC: + AddChatLine( common->GetLocalizedString( "#str_104285" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + break; + case MSG_TIMELIMIT: + AddChatLine( common->GetLocalizedString( "#str_104284" ) ); + break; + case MSG_FRAGLIMIT: + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + if ( gameLocal.gameType == GAME_TDM || gameLocal.gameType == GAME_DEADZONE ) { + // RITUAL END + // RAVEN BEGIN + // rhummer: localized "Strogg" and "Marine" + AddChatLine( common->GetLocalizedString( "#str_107665" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + } else { + AddChatLine( common->GetLocalizedString( "#str_104281" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) ); + } + break; + case MSG_CAPTURELIMIT: + // RAVEN BEGIN + // rhummer: localized "%s team hit the capture limit." and "Strogg and "Marine" + AddChatLine( common->GetLocalizedString( "#str_108027" ), parm1 ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ); + // RAVEN END + break; + case MSG_HOLYSHIT: + AddChatLine( common->GetLocalizedString( "#str_106732" ) ); + break; + default: + gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt ); + return; + } + } + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DB ); + outMsg.WriteByte( evt ); + outMsg.WriteByte( parm1 ); + outMsg.WriteByte( parm2 ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::PrintMessage +================ +*/ +void idMultiplayerGame::PrintMessage( int to, const char* msg ) { + if( idStr::Length( msg ) >= MAX_PRINT_LEN ) { + common->Warning( "idMultiplayerGame::PrintMessage() - Not transmitting message of length %d", idStr::Length( msg ) ); + return; + } + + AddChatLine( msg ); + + if ( !gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[1024]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_PRINT ); + outMsg.WriteString( msg ); + networkSystem->ServerSendReliableMessage( to, outMsg ); + } +} + +/* +================ +idMultiplayerGame::CheckSpawns +================ +*/ +void idMultiplayerGame::CheckRespawns( idPlayer *spectator ) { + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast(ent); + + // once we hit sudden death, nobody respawns till game has ended + // no respawns in tourney mode, the tourney manager manually handles spawns + if ( (WantRespawn( p ) || p == spectator) ) { + if ( gameState->GetMPGameState() == SUDDENDEATH && gameLocal.gameType != GAME_TOURNEY ) { + // respawn rules while sudden death are different + // sudden death may trigger while a player is dead, so there are still cases where we need to respawn + // don't do any respawns while we are in end game delay though + if ( gameLocal.IsTeamGame() || p->IsLeader() ) { + //everyone respawns in team games, only fragleaders respawn in DM + p->ServerSpectate( false ); + } else {//if ( !p->IsLeader() ) { + // sudden death is rolling, this player is not a leader, have him spectate + p->ServerSpectate( true ); + CheckAbortGame(); + } + } else { + if ( gameState->GetMPGameState() == WARMUP || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEON ) { + if ( gameLocal.gameType != GAME_TOURNEY ) { + // wait for team to be set before spawning in + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( false ); + } + + } else { + if( p->GetArena() >= 0 && p->GetArena() < MAX_ARENAS ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameState)->GetArena( p->GetArena() ); + if( ( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) && ( p == arena.GetPlayers()[ 0 ] || p == arena.GetPlayers()[ 1 ] ) ) { + // only allow respawn if the arena we're in is active + // and we're one of the assigned players (we're not just spectating it) + p->ServerSpectate( false ); + } + } else { + // always allow respawn in the waiting room + assert( p->GetArena() == MAX_ARENAS ); + p->ServerSpectate( false ); + } + } + } + } + } else if ( p->wantSpectate && !p->spectating ) { + playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets + p->ServerSpectate( true ); + CheckAbortGame(); + } + } +} + +void idMultiplayerGame::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 && gameRenderWorld ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + +void idMultiplayerGame::UpdateLight ( int lightID, idPlayer *player ) { + lights[ lightID ].origin = player->GetPhysics()->GetOrigin() + idVec3( 0, 0, 20 ); + + if ( lightHandles[ lightID ] == -1 ) { + lightHandles[ lightID ] = gameRenderWorld->AddLightDef ( &lights[ lightID ] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[ lightID ], &lights[ lightID ] ); + } +} + +void idMultiplayerGame::CheckSpecialLights( void ) { + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idPlayer *marineFlagCarrier = NULL; + idPlayer *stroggFlagCarrier = NULL; + idPlayer *quadDamageCarrier = NULL; + idPlayer *regenerationCarrier = NULL; + idPlayer *hasteCarrier = NULL; + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + + idPlayer *p = static_cast( ent ); + + if( gameLocal.GetLocalPlayer() && p->GetInstance() != gameLocal.GetLocalPlayer()->GetInstance() ) { + continue; + } + + if ( p->PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + marineFlagCarrier = p; + } + else if ( p->PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + stroggFlagCarrier = p; + } + else if( p->PowerUpActive( POWERUP_QUADDAMAGE ) || p->PowerUpActive( POWERUP_TEAM_DAMAGE_MOD )) { + quadDamageCarrier = p; + } + else if( p->PowerUpActive( POWERUP_REGENERATION ) ) { + regenerationCarrier = p; + } + else if( p->PowerUpActive( POWERUP_HASTE ) ) { + hasteCarrier = p; + } + } + + if ( marineFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_MARINE, marineFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_MARINE ); + } + + if ( stroggFlagCarrier ) { + UpdateLight( MPLIGHT_CTF_STROGG, stroggFlagCarrier ); + } else { + FreeLight( MPLIGHT_CTF_STROGG ); + } + + if ( quadDamageCarrier ) { + UpdateLight( MPLIGHT_QUAD, quadDamageCarrier ); + } else { + FreeLight( MPLIGHT_QUAD ); + } + + if ( regenerationCarrier ) { + UpdateLight( MPLIGHT_REGEN, regenerationCarrier ); + } else { + FreeLight( MPLIGHT_REGEN ); + } + + if ( hasteCarrier ) { + UpdateLight( MPLIGHT_HASTE, hasteCarrier ); + } else { + FreeLight( MPLIGHT_HASTE ); + } +} + +/* +================ +idMultiplayerGame::ForceReady +================ +*/ +void idMultiplayerGame::ForceReady( ) { + + for( int i = 0 ; i < gameLocal.numClients ; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + continue; + } + idPlayer *p = static_cast( ent ); + if ( !p->IsReady() ) { + PrintMessageEvent( -1, MSG_FORCEREADY, i ); + p->forcedReady = true; + } + } +} + +/* +================ +idMultiplayerGame::ForceReady_f +================ +*/ +void idMultiplayerGame::ForceReady_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer || gameLocal.isClient ) { + gameLocal.Printf( "forceReady: multiplayer server only\n" ); + return; + } + gameLocal.mpGame.ForceReady(); +} + +/* +================ +idMultiplayerGame::DropWeapon +================ +*/ +void idMultiplayerGame::DropWeapon( int clientNum ) { + assert( !gameLocal.isClient ); + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + return; + } +// RAVEN BEGIN +// bdube: removed parameter + static_cast< idPlayer* >( ent )->DropWeapon( ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::DropWeapon_f +================ +*/ +void idMultiplayerGame::DropWeapon_f( const idCmdArgs &args ) { + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "clientDropWeapon: only valid in multiplayer\n" ); + return; + } + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DROPWEAPON ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::MessageMode_f +================ +*/ +void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) { + gameLocal.mpGame.MessageMode( args ); +} + +/* +================ +idMultiplayerGame::MessageMode +================ +*/ +void idMultiplayerGame::MessageMode( const idCmdArgs &args ) { + const char *mode; + int imode; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientMessageMode: only valid in multiplayer\n" ); + return; + } + if ( !mainGui ) { + common->Printf( "no local client\n" ); + return; + } + mode = args.Argv( 1 ); + if ( !mode[ 0 ] || !gameLocal.IsTeamGame() ) { + imode = 0; + } else { + imode = atoi( mode ); + } + msgmodeGui->SetStateString( "messagemode", imode ? "1" : "0" ); + msgmodeGui->SetStateString( "chattext", "" ); + nextMenu = 2; + // let the session know that we want our ingame main menu opened + gameLocal.sessionCommand = "game_startmenu"; +} + +/* +================ +idMultiplayerGame::Vote_f +================ +*/ +void idMultiplayerGame::Vote_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// shouchard: implemented for testing + if ( args.Argc() < 2 ) { + common->Printf( common->GetLocalizedString( "#str_104418" ) ); + return; + } + + const char *szArg1 = args.Argv(1); + bool voteValue = false; + if ( 0 == idStr::Icmp( szArg1, "yes" ) ) { + voteValue = true; + } + + gameLocal.mpGame.CastVote( gameLocal.localClientNum, voteValue ); +// RAVEN END +} + +/* +================ +idMultiplayerGame::CallVote_f +moved this over the use the packed voting +still only does one vote though, can easily be extended to do more +================ +*/ +void idMultiplayerGame::CallVote_f( const idCmdArgs &args ) { + const char *szArg1 = args.Argv(1); + const char *szArg2 = args.Argv(2); + if ( '\0' == *szArg1 ) { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + voteStruct_t voteData; + memset( &voteData, 0, sizeof( voteData ) ); + + if ( 0 == idStr::Icmp( szArg1, "restart" ) ) { + voteData.m_fieldFlags |= VOTEFLAG_RESTART; + } else if ( 0 == idStr::Icmp( szArg1, "timelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104406" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_TIMELIMIT; + voteData.m_timeLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "fraglimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104407" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_FRAGLIMIT; + voteData.m_fragLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "gametype" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104408" ) ); + common->Printf( common->GetLocalizedString( "#str_104409" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_GAMETYPE; + voteData.m_gameType = gameLocal.mpGame.GameTypeToVote( szArg2 ); + } + else if ( 0 == idStr::Icmp( szArg1, "kick" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104412" ) ); + return; + } + voteData.m_kick = gameLocal.mpGame.GetClientNumFromPlayerName( szArg2 ); + if ( voteData.m_kick >= 0 ) { + voteData.m_fieldFlags |= VOTEFLAG_KICK; + } + } else if ( 0 == idStr::Icmp( szArg1, "map" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104413" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_MAP; + voteData.m_map = szArg2; + } else if ( 0 == idStr::Icmp( szArg1, "buying" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122012" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_BUYING; + voteData.m_buying = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "capturelimit" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104415" ) ); + return; + } + voteData.m_fieldFlags |= VOTEFLAG_CAPTURELIMIT; + voteData.m_captureLimit = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "autobalance" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_104416" ) ); + } + voteData.m_fieldFlags |= VOTEFLAG_TEAMBALANCE; + voteData.m_teamBalance = atoi( szArg2 ); + } else if ( 0 == idStr::Icmp( szArg1, "controlTime" ) ) { + if ( '\0' == *szArg2 ) { + common->Printf( common->GetLocalizedString( "#str_122002" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + } + voteData.m_fieldFlags |= VOTEFLAG_CONTROLTIME; + voteData.m_controlTime = atoi(szArg2 ); + } else { + common->Printf( common->GetLocalizedString( "#str_104404" ) ); + common->Printf( common->GetLocalizedString( "#str_104405" ) ); + return; + } + + if ( voteData.m_fieldFlags != 0 ) { + gameLocal.mpGame.ClientCallPackedVote( voteData ); + } +} + +// RAVEN BEGIN +// shouchard: added voice mute and unmute console commands; sans XBOX to not step on their live voice stuff +#ifndef _XBOX +/* +================ +idMultiplayerGame::VoiceMute_f +================ +*/ +void idMultiplayerGame::VoiceMute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoicemute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), true ); +} + +/* +================ +idMultiplayerGame::VoiceUnmute_f +================ +*/ +void idMultiplayerGame::VoiceUnmute_f( const idCmdArgs &args ) { + if ( args.Argc() < 2 ) { + common->Printf( "USAGE: clientvoiceunmute \n" ); + return; + } + gameLocal.mpGame.ClientVoiceMute( gameLocal.mpGame.GetClientNumFromPlayerName( args.Argv( 1 ) ), false ); +} + +// RAVEN END +#endif // _XBOX + +// RAVEN BEGIN +/* +================ +idMultiplayerGame::ForceTeamChange_f +================ +*/ +void idMultiplayerGame::ForceTeamChange_f( const idCmdArgs &args) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Forces player to change teams. Usage: ForceTeamChange \n" ); + return; + } + + idStr clientId; + int clientNum; + + clientId = args.Argv( 1 ); + if ( !clientId.IsNumeric() ) { + common->Printf( "usage: ForceTeamChange \n" ); + return; + } + + clientNum = atoi( clientId ); + + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + } + +} + + +/* +================ +idMultiplayerGame::RemoveClientFromBanList_f +================ +*/ +void idMultiplayerGame::RemoveClientFromBanList_f( const idCmdArgs& args ) { + + if( !gameLocal.isMultiplayer ) { + common->Printf( "[MP ONLY] Remove player from banlist. Usage: RemoveClientFromBanList \n" ); + return; + } + + idStr clientId; + clientId = args.Argv( 1 ); + int clientNum; + + if ( !clientId.IsNumeric() ) { + common->Printf( "Usage: RemoveClientFromBanList \n" ); + return; + } + + clientNum = atoi( clientId ); + + const char *clientGuid = networkSystem->GetClientGUID( clientNum ); // gameLocal.GetGuidByClientNum( clientNum ); + + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + // remove from the ban list + gameLocal.RemoveGuidFromBanList( clientGuid ); + } + +} + +/* +================ +idMultiplayerGame::ProcessRconReturn +================ +*/ +void idMultiplayerGame::ProcessRconReturn( bool success ) { + + if( success ) { + mainGui->HandleNamedEvent("adminPasswordSuccess"); + } else { + mainGui->HandleNamedEvent("adminPasswordFail"); + } + + +} + + +// RAVEN END + +/* +================ +idMultiplayerGame::ServerStartVote +================ +*/ +void idMultiplayerGame::ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *value ) { + int i; + + assert( vote == VOTE_NONE ); + + // setup + yesVotes = 1; + noVotes = 0; + vote = voteIndex; + voteValue = value; + voteTimeOut = gameLocal.time + 20000; + // mark players allowed to vote - only current ingame players, players joining during vote will be ignored + for ( i = 0; i < gameLocal.numClients; i++ ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT; + } else { + playerState[i].vote = PLAYER_VOTE_NONE; + } + } +} + +/* +================ +idMultiplayerGame::ClientStartVote +================ +*/ +void idMultiplayerGame::ClientStartVote( int clientNum, const char *_voteString ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTVOTE ); + outMsg.WriteByte( clientNum ); + outMsg.WriteString( _voteString ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } + + voteString = _voteString; + AddChatLine( va( common->GetLocalizedString( "#str_104279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); +// RAVEN BEGIN +// shouchard: better info when a vote called in the chat buffer + AddChatLine( voteString ); // TODO: will push this into a UI field later +// shouchard: display the vote called text on the hud + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->mphud ) { + gameLocal.GetLocalPlayer()->mphud->SetStateInt( "voteNotice", 1 ); + } +// RAVEN END + ScheduleAnnouncerSound( AS_GENERAL_VOTE_NOW, gameLocal.time ); + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } else { + voted = false; + } + if ( gameLocal.isClient ) { + // the the vote value to something so the vote line is displayed + vote = VOTE_RESTART; + yesVotes = 1; + noVotes = 0; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ClientUpdateVote +================ +*/ +void idMultiplayerGame::ClientUpdateVote( vote_result_t status, int yesCount, int noCount, const voteStruct_t &voteData ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + const char * localizedString = 0; + idPlayer* player = gameLocal.GetLocalPlayer( ); + + if ( !gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_UPDATEVOTE ); + outMsg.WriteByte( status ); + outMsg.WriteByte( yesCount ); + outMsg.WriteByte( noCount ); +// RAVEN BEGIN +// shouchard: multifield vote support + if ( VOTE_MULTIFIELD != vote ) { + outMsg.WriteByte( 0 ); + } else { + outMsg.WriteByte( 1 ); + outMsg.WriteShort( voteData.m_fieldFlags ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_kick ) ); + outMsg.WriteString( voteData.m_map.c_str() ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_gameType ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_timeLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_fragLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_tourneyLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_captureLimit ) ); + outMsg.WriteShort( idMath::ClampShort( voteData.m_buying ) ); + outMsg.WriteByte( idMath::ClampChar( voteData.m_teamBalance ) ); + } + networkSystem->ServerSendReliableMessage( -1, outMsg ); + } else { + currentVoteData = voteData; + } +// RAVEN END + + if ( vote == VOTE_NONE ) { + // clients coming in late don't get the vote start and are not allowed to vote + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + } + return; + } + + switch ( status ) { + case VOTE_FAILED: + localizedString = common->GetLocalizedString( "#str_104278" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_FAILED, gameLocal.time ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_PASSED: + localizedString = common->GetLocalizedString( "#str_104277" ); + AddChatLine( localizedString ); + ScheduleAnnouncerSound( AS_GENERAL_VOTE_PASSED, gameLocal.time ); + break; + case VOTE_RESET: + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_ABORTED: + localizedString = common->GetLocalizedString( "#str_104276" ); + AddChatLine( localizedString ); + if ( gameLocal.isClient ) { + vote = VOTE_NONE; + } + break; + case VOTE_UPDATE: + if ( player && player->mphud && voted ) { + player->mphud->SetStateString( "voteNoticeText", va("^:%s\n%s: %d %s: %d", + common->GetLocalizedString( "#str_107724" ), + common->GetLocalizedString( "#str_107703" ), + yesCount, + common->GetLocalizedString( "#str_107704" ), + noCount ) ); + } + + if ( mainGui ) { + mainGui->SetStateInt( "playerVoted", voted ); + } + break; + default: + break; + } + + if ( gameLocal.isClient ) { + yesVotes = yesCount; + noVotes = noCount; + } + +// RAVEN BEGIN +// shouchard: remove vote notification + if ( VOTE_FAILED == status || VOTE_PASSED == status || VOTE_RESET == status ) { + ClearVote(); + } + + if ( mainGui ) { + mainGui->SetStateString( "voteCount", va( common->GetLocalizedString( "#str_104435" ), (int)yesVotes, (int)noVotes ) ); + } +// RAVEN END +} + +/* +================ +idMultiplayerGame::ClientCallVote +================ +*/ +void idMultiplayerGame::ClientCallVote( vote_flags_t voteIndex, const char *voteValue ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // send + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLVOTE ); + outMsg.WriteByte( voteIndex ); + outMsg.WriteString( voteValue ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::CastVote +================ +*/ +void idMultiplayerGame::CastVote( int clientNum, bool castVote ) { + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + if ( clientNum == gameLocal.localClientNum ) { + voted = true; + } + + if ( gameLocal.isClient ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CASTVOTE ); + outMsg.WriteByte( castVote ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // sanity + if ( vote == VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104275" ); + common->DPrintf( "client %d: cast vote while no vote in progress\n", clientNum ); + return; + } + if ( playerState[ clientNum ].vote != PLAYER_VOTE_WAIT ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104274" ); + common->DPrintf( "client %d: cast vote - vote %d != PLAYER_VOTE_WAIT\n", clientNum, playerState[ clientNum ].vote ); + return; + } + + if ( castVote ) { + playerState[ clientNum ].vote = PLAYER_VOTE_YES; + yesVotes++; + } else { + playerState[ clientNum ].vote = PLAYER_VOTE_NO; + noVotes++; + } + + ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes, currentVoteData ); +} + +/* +================ +idMultiplayerGame::ServerCallVote +================ +*/ +void idMultiplayerGame::ServerCallVote( int clientNum, const idBitMsg &msg ) { + vote_flags_t voteIndex; + int vote_timeLimit, vote_fragLimit, vote_clientNum, vote_gameTypeIndex, vote_buying; //, vote_kickIndex; +// RAVEN BEGIN +// shouchard: added capture limit and autobalance + int vote_captureLimit; + bool vote_autobalance; +// RAVEN END + int vote_controlTime; + char value[ MAX_STRING_CHARS ]; + + assert( clientNum != -1 ); + assert( !gameLocal.isClient ); + + if( !gameLocal.serverInfo.GetBool( "si_allowVoting" ) ) { + return; + } + + voteIndex = (vote_flags_t)msg.ReadByte( ); + msg.ReadString( value, sizeof( value ) ); + + // sanity checks - setup the vote + if ( vote != VOTE_NONE ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104273" ); + common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum ); + return; + } + switch ( voteIndex ) { + case VOTE_RESTART: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104271" ) ); + break; + } + case VOTE_NEXTMAP: { + ServerStartVote( clientNum, voteIndex, "" ); + ClientStartVote( clientNum, common->GetLocalizedString( "#str_104272" ) ); + break; + } + case VOTE_TIMELIMIT: { + vote_timeLimit = strtol( value, NULL, 10 ); + if ( vote_timeLimit == gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104270" ); + common->DPrintf( "client %d: already at the voted Time Limit\n", clientNum ); + return; + } + if ( vote_timeLimit < si_timeLimit.GetMinValue() || vote_timeLimit > si_timeLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104269" ); + common->DPrintf( "client %d: timelimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104268" ), vote_timeLimit ) ); + break; + } + case VOTE_FRAGLIMIT: { + vote_fragLimit = strtol( value, NULL, 10 ); + if ( vote_fragLimit == gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104267" ); + common->DPrintf( "client %d: already at the voted Frag Limit\n", clientNum ); + return; + } + if ( vote_fragLimit < si_fragLimit.GetMinValue() || vote_fragLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104266" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104303" ), common->GetLocalizedString( "#str_104265" ), vote_fragLimit ) ); + break; + } + case VOTE_GAMETYPE: { +// RAVEN BEGIN +// shouchard: removed magic numbers & added CTF type + vote_gameTypeIndex = strtol( value, NULL, 10 ); + assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex < VOTE_GAMETYPE_COUNT ); + idStr::Copynz( value, VoteGameTypeToString( vote_gameTypeIndex ), sizeof( value ) ); + if ( !idStr::Icmp( value, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104259" ); + common->DPrintf( "client %d: already at the voted Game Type\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104258" ), value ) ); + break; + } + case VOTE_KICK: { + vote_clientNum = strtol( value, NULL, 10 ); + if ( vote_clientNum == gameLocal.localClientNum ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104257" ); + common->DPrintf( "client %d: called kick for the server host\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, va( "%d", vote_clientNum ) ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104302" ), vote_clientNum, gameLocal.userInfo[ vote_clientNum ].GetString( "ui_name" ) ) ); + break; + } + case VOTE_MAP: { +#ifdef _XENON + // Xenon should not get here + assert( 0 ); +#else + if ( idStr::FindText( gameLocal.serverInfo.GetString( "si_map" ), value ) != -1 ) { + + // mekberg: localized string + const char* mapName = si_map.GetString(); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + mapName = common->GetLocalizedString( mapDict->GetString( "name", mapName ) ); + } + gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLocalizedString( "#str_104295" ), mapName ) ); + common->DPrintf( "client %d: already running the voted map: %s\n", clientNum, value ); + return; + } + int num = fileSystem->GetNumMaps(); + int i; + const idDict *dict = NULL; + bool haveMap = false; + for ( i = 0; i < num; i++ ) { + dict = fileSystem->GetMapDecl( i ); + if( !dict ) { + gameLocal.Warning( "idMultiplayerGame::ServerCallVote() - bad map decl index on vote\n" ); + break; + } + if ( dict && !idStr::Icmp( dict->GetString( "path" ), value ) ) { + haveMap = true; + break; + } + } + if ( !haveMap ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104296", value ); + common->Printf( "client %d: map not found: %s\n", clientNum, value ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_104256" ), dict ? dict->GetString( "name" ) : value ) ); +#endif + break; + } + case VOTE_BUYING: { + vote_buying = strtol( value, NULL, 10 ); + if ( vote_buying == gameLocal.serverInfo.GetInt( "si_isBuyingEnabled" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122013" ); + common->DPrintf( "client %d: already at the voted buying mode\n", clientNum ); + return; + } + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, va( common->GetLocalizedString( "#str_122014" ), vote_buying ) ); + break; + } +// RAVEN BEGIN +// shouchard: added capture limit, round limit, and autobalance + case VOTE_CAPTURELIMIT: { + vote_captureLimit = strtol( value, NULL, 10 ); + if ( vote_captureLimit == gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104401" ); + common->DPrintf( "client %d: already at the voted Capture Limit\n", clientNum ); + return; + } + if ( vote_captureLimit < si_captureLimit.GetMinValue() || vote_captureLimit > si_fragLimit.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104402" ); + common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_captureLimit" ); + break; + } + // round limit is for tourneys + case VOTE_ROUNDLIMIT: { + // need a CVar or something to change here + break; + } + case VOTE_AUTOBALANCE: { + vote_autobalance = (0 != strtol( value, NULL, 10 ) ); + if ( vote_autobalance == gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104403" ); + common->DPrintf( "client %d: already at the voted balance teams\n", clientNum ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_autobalance" ); + break; + } +// RAVEN END + case VOTE_CONTROLTIME: { + vote_controlTime = strtol( value, NULL, 10 ); + if ( vote_controlTime == gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122017" ); + common->DPrintf( "client %d: already at the voted Control Time\n", clientNum ); + return; + } + if ( vote_controlTime < si_controlTime.GetMinValue() || vote_controlTime > si_controlTime.GetMaxValue() ) { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_122018" ); + common->DPrintf( "client %d: controlTime value out of range for vote: %s\n", clientNum, value ); + return; + } + + ServerStartVote( clientNum, voteIndex, value ); + ClientStartVote( clientNum, "si_controlTime" ); + break; + } + default: { + gameLocal.ServerSendChatMessage( clientNum, "server", "#str_104297", va( "%d", ( int )voteIndex ) ); + common->DPrintf( "client %d: unknown vote index %d\n", clientNum, voteIndex ); + } + } +} + + +/* +================ +idMultiplayerGame::DisconnectClient +================ +*/ +void idMultiplayerGame::DisconnectClient( int clientNum ) { + // gameLocal.entities[ clientNum ] could be null if server is shutting down + if( gameLocal.entities[ clientNum ] ) { + // only kill non-spectators + if( !((idPlayer*)gameLocal.entities[ clientNum ])->spectating ) { + static_cast( gameLocal.entities[ clientNum ] )->Kill( true, true ); + } + statManager->ClientDisconnect( clientNum ); + } + + delete gameLocal.entities[ clientNum ]; + + UpdatePlayerRanks(); + CheckAbortGame(); + + privatePlayers &= ~( 1 << clientNum ); + + // update serverinfo + UpdatePrivatePlayerCount(); +} + +/* +================ +idMultiplayerGame::CheckAbortGame +================ +*/ +void idMultiplayerGame::CheckAbortGame( void ) { + // only checks for aborts -> game review below + if ( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != GAMEON && gameState->GetMPGameState() != SUDDENDEATH ) { + return; + } + + // in tourney, if we don't have enough clients to play we need to cycle back to + // warmup to re-seed + if( gameLocal.gameType == GAME_TOURNEY ) { + if ( !EnoughClientsToPlay() ) { + gameState->NewState( WARMUP ); + } + } else { + if ( !EnoughClientsToPlay() && TimeLimitHit() ) { + gameState->NewState( GAMEREVIEW ); + } + } +} + +/* +================ +idMultiplayerGame::WantKilled +================ +*/ +void idMultiplayerGame::WantKilled( int clientNum ) { + idEntity *ent = gameLocal.entities[ clientNum ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + static_cast( ent )->Kill( false, false ); + } +} + +/* +================ +idMultiplayerGame::ClearVote +================ +*/ +void idMultiplayerGame::ClearVote( int clientNum ) { + int start = 0; + int end = MAX_CLIENTS; + + if( clientNum != -1 ) { + start = clientNum; + end = clientNum + 1; + } + + for ( int i = start; i < end; i++ ) { + idPlayer *player = static_cast( gameLocal.entities[ i ] ); + if ( !player || !player->mphud ) { + continue; + } + + player->mphud->SetStateInt( "voteNotice", 0 ); + player->mphud->SetStateString( "voteInfo_1", "" ); + player->mphud->SetStateString( "voteInfo_2", "" ); + player->mphud->SetStateString( "voteInfo_3", "" ); + player->mphud->SetStateString( "voteInfo_4", "" ); + player->mphud->SetStateString( "voteInfo_5", "" ); + player->mphud->SetStateString( "voteInfo_6", "" ); + player->mphud->SetStateString( "voteInfo_7", "" ); + player->mphud->StateChanged( gameLocal.time ); + } + // clear the local demo player's vote too + if ( clientNum == -1 && gameLocal.IsServerDemoPlaying() ) do { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !player->mphud ) { + continue; + } + + player->mphud->SetStateInt( "voteNotice", 0 ); + player->mphud->SetStateString( "voteInfo_1", "" ); + player->mphud->SetStateString( "voteInfo_2", "" ); + player->mphud->SetStateString( "voteInfo_3", "" ); + player->mphud->SetStateString( "voteInfo_4", "" ); + player->mphud->SetStateString( "voteInfo_5", "" ); + player->mphud->SetStateString( "voteInfo_6", "" ); + player->mphud->SetStateString( "voteInfo_7", "" ); + player->mphud->StateChanged( gameLocal.time ); + } while(0); + if ( mainGui ) { + mainGui->SetStateInt( "vote_going", 0 ); + mainGui->StateChanged( gameLocal.time ); + } +} +/* +================ +idMultiplayerGame::MapRestart +================ +*/ +void idMultiplayerGame::MapRestart( void ) { + int clientNum; + // jshepard: clean up votes + ClearVote(); + + ClearAnnouncerSounds(); + + assert( !gameLocal.isClient ); + if ( gameLocal.GameState() != GAMESTATE_SHUTDOWN && gameState->GetMPGameState() != WARMUP ) { + gameState->NewState( WARMUP ); + // force an immediate state detection/update, otherwise if we update our state this + // same frame we'll miss transitions + gameState->SendState( serverReliableSender.To( -1 ) ); + + gameState->SetNextMPGameState( INACTIVE ); + gameState->SetNextMPGameStateTime( 0 ); + + } + + // mekberg: moved this before the updateUI just in case these values weren't reset. + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } + + // mekberg: Re-wrote this loop to always updateUI. Previously the player would be + // on a team but the UI wouldn't know about it + // shouchard: balance teams extended to CTF + for ( clientNum = 0; clientNum < gameLocal.numClients; clientNum++ ) { + // jnewquist: Use accessor for static class type + if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) { + // mekberg: clear wins only on map restart + idPlayer *player = static_cast( gameLocal.entities[ clientNum ] ); + SetPlayerWin( player, 0 ); + + /*if( clientNum == gameLocal.localClientNum ) { + if ( player->alreadyDidTeamAnnouncerSound ) { + player->alreadyDidTeamAnnouncerSound = false; + } else { + if ( gameLocal.IsTeamGame() ) { + player->alreadyDidTeamAnnouncerSound = true; + if( player->team == TEAM_STROGG ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( player->team == TEAM_MARINE ) { + ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + }*/ + // let the player rejoin the team through normal channels + //player->ServerSpectate( true ); + //player->team = -1; + //player->latchedTeam = -1; + + // shouchard: BalanceTDM->BalanceTeam + //if ( gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // player->BalanceTeam(); + //} + + // core is in charge of syncing down userinfo changes + // it will also call back game through SetUserInfo with the current info for update + /*cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) );*/ + } + } +} + +/* +================ +idMultiplayerGame::SwitchToTeam +================ +*/ +void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) { + assert( gameLocal.IsTeamGame() ); + + assert( oldteam != newteam ); + assert( !gameLocal.isClient ); + + if ( !gameLocal.isClient && newteam >= 0 ) { + // clients might not have userinfo of joining client at this point, so + // send down the player's name + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( !p->wantSpectate ) { + PrintMessage( -1, va( common->GetLocalizedString( "#str_104280" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ), newteam ? common->GetLocalizedString( "#str_108025" ) : common->GetLocalizedString( "#str_108026" ) ) ); + } + } + + if ( oldteam != -1 ) { + // kill and respawn + idPlayer *p = static_cast( gameLocal.entities[ clientNum ] ); + if ( p->IsInTeleport() ) { + p->ServerSendInstanceEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false, -1 ); + p->SetPrivateCameraView( NULL ); + } +//RITUAL BEGIN + p->inventory.carryOverWeapons = 0; + p->ResetCash(); +//RITUAL END + p->Kill( true, true ); + CheckAbortGame(); + } +} + +/* +================ +idMultiplayerGame::JoinTeam +================ +*/ +void idMultiplayerGame::JoinTeam( const char* team ) { + if( !idStr::Icmp( team, "auto" ) ) { + int teamCount[ TEAM_MAX ]; + idEntity *ent; + + memset( teamCount, 0, sizeof( int ) * TEAM_MAX ); + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ ((idPlayer*)ent)->team ]++; + } + } + } + + int minCount = idMath::INT_MAX; + int minCountTeam = -1; + for( int i = 0; i < TEAM_MAX; i++ ) { + if( teamCount[ i ] < minCount ) { + minCount = teamCount[ i ]; + minCountTeam = i; + } + } + + if( minCountTeam >= 0 && minCountTeam < TEAM_MAX ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ minCountTeam ] ); + } else { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ gameLocal.random.RandomInt( TEAM_MAX - 1 ) ] ); + } + } else if( !idStr::Icmp( team, "spectator" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + if( !idStr::Icmp( team, teamNames[ i ] ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + cvarSystem->SetCVarString( "ui_team", teamNames[ i ] ); + break; + } + } + if( i >= TEAM_MAX ) { + gameLocal.Warning( "idMultiplayerGame::JoinTeam() - unknown team '%s'\n", team ); + } + } +} + +/* +================ +idMultiplayerGame::ProcessChatMessage +================ +*/ +void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + const char *suffix = NULL; + int send_to; // 0 - all, 1 - specs, 2 - team + int i; + idEntity *ent; + idPlayer *p; + idStr suffixed_name; + idStr prefixed_text; + + assert( !gameLocal.isClient ); + + if ( clientNum >= 0 ) { + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating && ( p->wantSpectate || gameLocal.gameType == GAME_TOURNEY ) ) { + suffix = "spectating"; + if ( team || ( !g_spectatorChat.GetBool() && ( gameState->GetMPGameState() == GAMEON || gameState->GetMPGameState() == SUDDENDEATH ) ) ) { + // to specs + send_to = 1; + } else { + // to all + send_to = 0; + } + } else if ( team ) { + suffix = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, p->team ? "Strogg^0" : "Marine^0" ); + // to team + send_to = 2; + } else { + if( gameLocal.gameType == GAME_TOURNEY ) { + suffix = va( "Arena %d", (p->GetArena() + 1) ); + } + // to all + send_to = 0; + } + } else { + p = NULL; + send_to = 0; + } + // put the message together + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( ( send_to == 2 ) ? GAME_RELIABLE_MESSAGE_TCHAT : GAME_RELIABLE_MESSAGE_CHAT ); + + if ( suffix ) { + suffixed_name = va( "^0%s^0 (%s)", name, suffix ); + } else { + suffixed_name = va( "^0%s^0", name ); + } + if( p && send_to == 2 ) { + prefixed_text = va( "%s%s", p->team ? S_COLOR_STROGG : S_COLOR_MARINE, common->GetLocalizedString( text ) ); + } else { + prefixed_text = common->GetLocalizedString( text ); + } + + if( suffixed_name.Length() + prefixed_text.Length() >= 240 ) { + gameLocal.Warning( "idMultiplayerGame::ProcessChatMessage() - Chat line too long\n" ); + return; + } + + outMsg.WriteString( suffixed_name ); + outMsg.WriteString( prefixed_text ); + outMsg.WriteString( "" ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *to = static_cast< idPlayer * >( ent ); + switch( send_to ) { + case 0: + if ( !p || !to->IsPlayerMuted( p ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 1: + if ( !p || ( to->spectating && !to->IsPlayerMuted( p ) ) ) { + if ( i == gameLocal.localClientNum ) { + AddChatLine( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + break; + + case 2: + if ( !p || ( to->team == p->team && !to->IsPlayerMuted( p ) ) ) { + if ( !to->spectating ) { + if ( i == gameLocal.localClientNum ) { + PrintChatLine( va( "%s^0: %s\n", suffixed_name.c_str(), prefixed_text.c_str() ), true ); + } else { + networkSystem->ServerSendReliableMessage( i, outMsg ); + } + } + } + break; + } + } +} + +/* +================ +idMultiplayerGame::Precache +================ +*/ +void idMultiplayerGame::Precache( void ) { + int i; + + if ( !gameLocal.isMultiplayer ) { + return; + } + gameLocal.FindEntityDef( "player_marine", false ); + + // MP game sounds + for ( i = 0; i < AS_NUM_SOUNDS; i++ ) { + declManager->FindSound( announcerSoundDefs[ i ], false ); + } + + // MP guis. just make sure we hit all of them + i = 0; + while ( MPGuis[ i ] ) { + uiManager->FindGui( MPGuis[ i ], true ); + i++; + } +} + +/* +================ +idMultiplayerGame::ToggleSpectate +================ +*/ +void idMultiplayerGame::ToggleSpectate( void ) { + bool spectating; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + spectating = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( spectating ) { + // always allow toggling to play + cvarSystem->SetCVarString( "ui_spectate", "Play" ); + } else { + // only allow toggling to spectate if spectators are enabled. + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + cvarSystem->SetCVarString( "ui_spectate", "Spectate" ); + } else { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_106747" ) ); + } + } +} + +/* +================ +idMultiplayerGame::ToggleReady +================ +*/ +void idMultiplayerGame::ToggleReady( void ) { + bool ready; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + if ( lastReadyToggleTime == -1 ) { + lastReadyToggleTime = gameLocal.time; + } else { + int currentTime = gameLocal.time; + if ( currentTime - lastReadyToggleTime < 500 ) { + return; + } else { + lastReadyToggleTime = currentTime; + } + } + + ready = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready ) { + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + } else { + cvarSystem->SetCVarString( "ui_ready", "Ready" ); + } +} + +/* +================ +idMultiplayerGame::ToggleTeam +================ +*/ +void idMultiplayerGame::ToggleTeam( void ) { + bool team; + assert( gameLocal.isClient || gameLocal.localClientNum == 0 ); + + // RAVEN BEGIN + // ddynerman: new multiplayer teams + team = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_team" ), "Marine" ) == 0 ); + if ( team ) { + cvarSystem->SetCVarString( "ui_team", "Strogg" ); + } else { + cvarSystem->SetCVarString( "ui_team", "Marine" ); + } + // RAVEN END +} + +/* +================ +idMultiplayerGame::ToggleUserInfo +================ +*/ +void idMultiplayerGame::ThrottleUserInfo( void ) { + int i; + + if ( gameLocal.localClientNum == MAX_CLIENTS ) { + // repeater; UserInfo doesn't get changed in-game anyway. + return; + } + + assert( gameLocal.localClientNum >= 0 ); + + i = 0; + while ( ThrottleVars[ i ] ) { + if ( idStr::Icmp( gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ), + cvarSystem->GetCVarString( ThrottleVars[ i ] ) ) ) { + if ( gameLocal.realClientTime < switchThrottle[ i ] ) { + AddChatLine( common->GetLocalizedString( "#str_104299" ), common->GetLocalizedString( ThrottleVarsInEnglish[ i ] ), ( switchThrottle[ i ] - gameLocal.time ) / 1000 + 1 ); + cvarSystem->SetCVarString( ThrottleVars[ i ], gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ) ); + } else { + switchThrottle[ i ] = gameLocal.time + ThrottleDelay[ i ] * 1000; + } + } + i++; + } +} + +/* +================ +idMultiplayerGame::CanPlay +================ +*/ +bool idMultiplayerGame::CanPlay( idPlayer *p ) { + return !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::EnterGame +================ +*/ +void idMultiplayerGame::EnterGame( int clientNum ) { + assert( !gameLocal.isClient ); + + if ( !playerState[ clientNum ].ingame ) { + playerState[ clientNum ].ingame = true; + if ( gameLocal.isMultiplayer ) { + // can't use PrintMessageEvent as clients don't know the nickname yet + //gameLocal.ServerSendChatMessage( -1, common->GetLocalizedString( "#str_102047" ), va( common->GetLocalizedString( "#str_107177" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) ); + } + + // mark them as private and update si_numPrivatePlayers + for( int i = 0; i < privateClientIds.Num(); i++ ) { + int num = networkSystem->ServerGetClientNum( privateClientIds[ i ] ); + + // check for timed out clientids + if( num < 0 ) { + privateClientIds.RemoveIndex( i ); + i--; + continue; + } + + if( num == clientNum ) { + privatePlayers |= (1 << clientNum); + } + } + + // update serverinfo + UpdatePrivatePlayerCount(); + } +} + +/* +================ +idMultiplayerGame::WantRespawn +================ +*/ +bool idMultiplayerGame::WantRespawn( idPlayer *p ) { + return p->forceRespawn && !p->wantSpectate && playerState[ p->entityNumber ].ingame; +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, false ); +} + +/* +================ +idMultiplayerGame::UpdateMPSettingsModel +================ +*/ +void idMultiplayerGame::UpdateMPSettingsModel( idUserInterface* currentGui ) { + if ( !currentGui ) { + return; + } + + const char *model; + idPlayer *localP = gameLocal.GetLocalPlayer(); + if ( gameLocal.IsTeamGame() && localP && localP->team >= 0 && localP->team < TEAM_MAX ) { + model = cvarSystem->GetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ) ); + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( va( "def_default_model_%s", teamNames[ localP->team ] ) ); + cvarSystem->SetCVarString( va( "ui_model_%s", teamNames[ localP->team ] ), model ); + } + } else { + model = cvarSystem->GetCVarString( "ui_model" ); + + if ( idStr::Cmp( model, "" ) == 0 ) { + const idDeclEntityDef *def = static_cast( declManager->FindType( DECL_ENTITYDEF, "player_marine_mp_ui", false, true ) ); + model = def->dict.GetString( "def_default_model" ); + cvarSystem->SetCVarString( "ui_model", model ); + } + } + const rvDeclPlayerModel* playerModel = (const rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, model, false ); + if ( playerModel ) { + currentGui->SetStateString( "player_model_name", playerModel->model.c_str() ); + currentGui->SetStateString( "player_head_model_name", playerModel->uiHead.c_str() ); + currentGui->SetStateString( "player_skin_name", playerModel->skin.c_str() ); + if( playerModel->uiHead.Length() ) { + const idDeclEntityDef* head = (const idDeclEntityDef*)declManager->FindType( DECL_ENTITYDEF, playerModel->uiHead.c_str(), false ); + if( head && head->dict.GetString( "skin" ) ) { + mainGui->SetStateString( "player_head_skin_name", head->dict.GetString( "skin" ) ); + } + } + currentGui->SetStateBool( "need_update", true ); + } +} + +/* +================ +idMultiplayerGame::VoiceChatTeam +================ +*/ +void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs &args ) { + gameLocal.mpGame.VoiceChat( args, true ); +} + +/* +================ +idMultiplayerGame::VoiceChat +================ +*/ +void idMultiplayerGame::VoiceChat( const idCmdArgs &args, bool team ) { + idBitMsg outMsg; + byte msgBuf[128]; + const char *voc; + const idDict *spawnArgs; + const idKeyValue *keyval; + int index; + + if ( !gameLocal.isMultiplayer ) { + common->Printf( "clientVoiceChat: only valid in multiplayer\n" ); + return; + } + if ( args.Argc() != 2 ) { + common->Printf( "clientVoiceChat: bad args\n" ); + return; + } + // throttle + if ( gameLocal.realClientTime < voiceChatThrottle ) { + return; + } + + voc = args.Argv( 1 ); + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + index = 0; + while ( keyval ) { + if ( !keyval->GetValue().Icmp( voc ) ) { + break; + } + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index++; + } + if ( !keyval ) { + common->Printf( "Voice command not found: %s\n", voc ); + return; + } + voiceChatThrottle = gameLocal.realClientTime + 1000; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VCHAT ); + outMsg.WriteLong( index ); + outMsg.WriteBits( team ? 1 : 0, 1 ); + networkSystem->ClientSendReliableMessage( outMsg ); +} + +/* +================ +idMultiplayerGame::ProcessVoiceChat +================ +*/ +void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index ) { + const idDict *spawnArgs; + const idKeyValue *keyval; + idStr name; + idStr snd_key; + idStr text_key; + idPlayer *p; + + p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !( p && p->IsType( idPlayer::GetClassType() ) ) ) { +// RAVEN END + return; + } + + if ( p->spectating ) { + return; + } + + // lookup the sound def + spawnArgs = gameLocal.FindEntityDefDict( "player_marine", false ); + keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL ); + while ( index > 0 && keyval ) { + keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval ); + index--; + } + if ( !keyval ) { + common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index ); + return; + } + snd_key = keyval->GetKey(); + name = gameLocal.userInfo[ clientNum ].GetString( "ui_name" ); + sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() ); + if ( team || gameState->GetMPGameState() == COUNTDOWN || gameState->GetMPGameState() == GAMEREVIEW ) { + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) ); + } else { + p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL ); + ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL ); + } +} + +// RAVEN BEGIN +// shouchard: added commands to mute/unmute voice chat +/* +================ +idMultiplayerGame::ClientVoiceMute +================ +*/ +void idMultiplayerGame::ClientVoiceMute( int muteClient, bool mute ) { + // clients/listen server only + assert( gameLocal.isListenServer || gameLocal.isClient ); + + if ( NULL == gameLocal.GetLocalPlayer() ) { + return; + } + + if ( muteClient == -1 || !gameLocal.mpGame.IsInGame( muteClient ) ) { + gameLocal.Warning( "idMultiplayerGame::ClientVoiceMute() - Invalid client '%d' specified", muteClient ); + return; + } + + // do the mute/unmute + gameLocal.GetLocalPlayer()->MutePlayer( muteClient, mute ); + + // tell the server + if( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[128]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VOICECHAT_MUTING ); + outMsg.WriteByte( muteClient ); + outMsg.WriteByte( mute ? 1 : 0 ); // 1 for mute, 0 for unmute + networkSystem->ClientSendReliableMessage( outMsg ); + } + + // display some niceties + common->Printf( "Player %s's has been %s.\n", gameLocal.GetUserInfo( muteClient )->GetString( "ui_name" ), mute ? "muted" : "unmuted" ); +} + +/* +================ +idMultiplayerGame::GetClientNumFromPlayerName +================ +*/ +int idMultiplayerGame::GetClientNumFromPlayerName( const char *playerName ) { + if ( NULL == playerName || '\0' == *playerName ) { + return -1; + } + + int clientNum = -1; + + for ( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::GetClassType() ) ) { + if ( 0 == idStr::Icmp( gameLocal.userInfo[ i ].GetString( "ui_name" ), playerName ) ) { + clientNum = i; + break; + } + } + } + + if ( -1 == clientNum ) { + common->Warning( "idMultiplayerGame::GetClientNumFromPlayerName(): unknown player '%s'", playerName ); + } + + return clientNum; +} + +/* +================ +idMultiplayerGame::ServerHandleVoiceMuting +================ +*/ +void idMultiplayerGame::ServerHandleVoiceMuting( int clientSrc, int clientDest, bool mute ) { + assert( !gameLocal.isClient ); + + idPlayer *playerSrc = gameLocal.GetClientByNum( clientSrc ); + idPlayer *playerDest = gameLocal.GetClientByNum( clientDest ); + + if ( NULL == playerSrc ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientSrc ); + return; + } + + if ( NULL == playerDest ) { + common->DPrintf( "idMultiplayerGame::ServerHandleVoiceMuting: couldn't map client %d to a player\n", clientDest ); + return; + } + + if ( mute ) { + playerSrc->MutePlayer( playerDest, true ); + common->DPrintf( "DEBUG: client %s muted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } else { + playerSrc->MutePlayer( playerDest, false ); + common->DPrintf( "DEBUG: client %s unmuted to client %s\n", + gameLocal.userInfo[ clientDest ].GetString( "ui_name" ), + gameLocal.userInfo[ clientSrc ].GetString( "ui_name" ) ); + } +} + + +/* +================ +idMultiplayerGame::ClearAnnouncerSounds + +This method deletes unplayed announcer sounds at the end of a game round. +This fixes a bug where the round time warnings were being played from +previous rounds. +================ +*/ +void idMultiplayerGame::ClearAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + snd->announcerSoundNode.Remove ( ); + delete snd; + } + + announcerPlayTime = 0; +} + +/* +================ +idMultiplayerGame::HandleServerAdminBanPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminBanPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + if ( gameLocal.isListenServer && clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban the host!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i ban", clientNum ) ); + } else { + if ( clientNum == gameLocal.localClientNum ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminBanPlayer: Cannot ban yourserlf!\n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i ban", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminRemoveBan +================ +*/ +void idMultiplayerGame::HandleServerAdminRemoveBan( const char * clientGuid ) { + if ( NULL == clientGuid || !clientGuid[ 0 ]) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminRemoveBan: bad guid!\n" ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + gameLocal.RemoveGuidFromBanList( clientGuid ); + } else { + int clientNum = gameLocal.GetClientNumByGuid( clientGuid ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon removeClientFromBanList %d", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminKickPlayer +================ +*/ +void idMultiplayerGame::HandleServerAdminKickPlayer( int clientNum ) { + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminKickPlayer: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %i", clientNum ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon kick %i", clientNum ) ); + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminForceTeamSwitch +================ +*/ +void idMultiplayerGame::HandleServerAdminForceTeamSwitch( int clientNum ) { + if ( !gameLocal.IsTeamGame() ) { + return; + } + + if ( clientNum < 0 || clientNum >= gameLocal.numClients ) { + common->DPrintf( "idMultiplayerGame::HandleServerAdminForceTeamSwitch: bad client num %d\n", clientNum ); + return; + } + + if ( gameLocal.isServer || gameLocal.isListenServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "forceTeamChange %d\n", clientNum)); + +/* if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::GetClassType() ) ) + { + idPlayer *player = static_cast< idPlayer *>( gameLocal.entities[ clientNum ] ); + player->GetUserInfo()->Set( "ui_team", player->team ? "Marine" : "Strogg" ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) ); + }*/ + } else { +/* idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVER_ADMIN ); + outMsg.WriteByte( SERVER_ADMIN_FORCE_SWITCH ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); */ + + //jshepard: need to be able to do this via rcon + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon forceTeamChange %d\n", clientNum)); + + } +} + +/* +================ +idMultiplayerGame::HandleServerAdminCommands +================ +*/ +bool idMultiplayerGame::HandleServerAdminCommands( serverAdminData_t &data ) { + bool restartNeeded = false; + bool nextMapNeeded = false; + bool anyChanges = false; + bool runPickMap = false; + int nGameType = 0; + idStr currentMap = si_map.GetString( ); + + const char *szGameType = gameLocal.serverInfo.GetString( "si_gametype" ); + if ( 0 == idStr::Icmp( szGameType, "DM" ) ) { + nGameType = GAME_DM; + } else if ( 0 == idStr::Icmp( szGameType, "Team DM" ) ) { + nGameType = GAME_TDM; + } else if ( 0 == idStr::Icmp( szGameType, "CTF" ) ) { + nGameType = GAME_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "Tourney" ) ) { + nGameType = GAME_TOURNEY; + } else if ( 0 == idStr::Icmp( szGameType, "Arena CTF" ) ) { + nGameType = GAME_ARENA_CTF; + } else if ( 0 == idStr::Icmp( szGameType, "DeadZone" ) ) { + nGameType = GAME_DEADZONE; + } else { + nGameType = GAME_SP; + } + if ( nGameType != data.gameType ) { + + switch ( data.gameType ) { + case GAME_TDM: szGameType = "Team DM"; runPickMap = true; break; + case GAME_TOURNEY: szGameType = "Tourney"; runPickMap = true; break; + case GAME_CTF: szGameType = "CTF"; runPickMap = true; break; + case GAME_ARENA_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + + // mekberg: hack, if we had 1f ctf the gui index wouldn't be off =( + case GAME_1F_CTF: szGameType = "Arena CTF"; runPickMap = true; break; + case GAME_DEADZONE: szGameType = "DeadZone"; runPickMap = true; break; + default: + case GAME_DM: szGameType = "DM"; break; + } + + //we're going to reset the map here, so make sure to kill the active vote. + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + + si_gameType.SetString( szGameType ); + if( runPickMap && gameLocal.isServer ) { + //set the selected map to the admin data value, then make sure it can run the selected gametype. + si_map.SetString( data.mapName.c_str() ); + if( PickMap( szGameType ) || idStr::Icmp( si_map.GetString( ), currentMap.c_str( ) ) ) { + nextMapNeeded = true; + restartNeeded = false; + data.mapName = idStr( si_map.GetString() ); + data.restartMap = true; + } + } + } + + if ( gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) != data.buying ) + restartNeeded = true; + + // Rcon these cvars if this isn't the server. We can trust the input from the gui that the + // gametype and map always match. + if ( !gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_autoBalance %d", data.autoBalance ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_isBuyingEnabled %d", data.buying ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_captureLimit %d", data.captureLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_controlTime %d", data.controlTime ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_fragLimit %d", data.fragLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_gameType %s", szGameType ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_map %s", data.mapName.c_str() ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_tourneyLimit %d", data.tourneyLimit ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_minPlayers %d", data.minPlayers ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon si_timeLimit %d", data.timeLimit ) ); + if( runPickMap ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "rcon verifyServerSettings" ) ); + } + + if( data.shuffleTeams ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon shuffleTeams" ); + } + + if( restartNeeded || data.restartMap || nextMapNeeded || idStr::Icmp( gameLocal.serverInfo.GetString( "si_map" ), data.mapName.c_str() ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon serverMapRestart" ); + } + else + { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rcon rescanSI" ); + } + + return true; + } + + if ( data.restartMap ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + restartNeeded = true; + anyChanges = true; + } + + if ( data.shuffleTeams ) { + ShuffleTeams(); + anyChanges = true; + } + + //this section won't be encountered if the gametype was changed. But that's ok. + if ( data.mapName.c_str() && idStr::Icmp( data.mapName.c_str(), si_map.GetString() ) ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + si_map.SetString(data.mapName.c_str()); + cvarSystem->SetCVarString( "si_map", data.mapName.c_str() ); + nextMapNeeded = true; + anyChanges = true; + } + + if ( data.captureLimit != gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) { + si_captureLimit.SetInteger( data.captureLimit ); + anyChanges = true; + } + if ( data.fragLimit != gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + si_fragLimit.SetInteger( data.fragLimit ); + anyChanges = true; + } + if ( data.tourneyLimit != gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + si_tourneyLimit.SetInteger( data.tourneyLimit ); + anyChanges = true; + } + if ( data.timeLimit != gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) { + si_timeLimit.SetInteger( data.timeLimit ); + anyChanges = true; + } + if ( data.buying != gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ) ) { + si_isBuyingEnabled.SetInteger( data.buying ); + anyChanges = true; + restartNeeded = true; + } + if ( data.autoBalance != gameLocal.serverInfo.GetBool( "si_autobalance" ) ) { + si_autobalance.SetBool( data.autoBalance ); + anyChanges = true; + } + if ( data.controlTime != gameLocal.serverInfo.GetInt( "si_controlTime" ) ) { + si_controlTime.SetInteger( data.controlTime ); + anyChanges = true; + } + + if ( gameLocal.NeedRestart() || restartNeeded || nextMapNeeded ) { + ClientUpdateVote( VOTE_RESET, 0, 0, currentVoteData ); + vote = VOTE_NONE; + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart" ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); + } + + return anyChanges; +} + + +// RAVEN END + +/* +=============== +idMultiplayerGame::WriteStartState +=============== +*/ + void idMultiplayerGame::WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ) { + int i; + idEntity *ent; + + // send the start time + msg.WriteLong( matchStartedTime ); + // send the powerup states and the spectate states + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( withLocalClient || i != clientNum ) && ent && ent->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + msg.WriteShort( i ); + msg.WriteShort( static_cast< idPlayer * >( ent )->inventory.powerups ); + msg.WriteBits( ent->GetInstance(), ASYNC_PLAYER_INSTANCE_BITS ); + msg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 ); + } + } + msg.WriteShort( MAX_CLIENTS ); +} + +/* +================ +idMultiplayerGame::ServerWriteInitialReliableMessages +================ +*/ +void idMultiplayerGame::ServerWriteInitialReliableMessages( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.BeginWriting(); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTSTATE ); + WriteStartState( clientNum, outMsg, false ); + sender.Send( outMsg ); + + // we send SI in connectResponse messages, but it may have been modified already + outMsg.BeginWriting( ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO ); + if ( sender.GetChannelType() == CHANNEL_DEST_RELIABLE_REPEATER ) { + assert( gameLocal.isRepeater ); + outMsg.WriteDeltaDict( gameLocal.repeaterInfo, NULL ); + } else { + outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL ); + } + sender.Send( outMsg ); + + gameState->SendInitialState( sender, clientNum ); +} + +/* +================ +idMultiplayerGame::ClientReadStartState +================ +*/ +void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) { + int i, client, powerup; + + assert( gameLocal.isClient ); + + // read the state in preparation for reading snapshot updates + matchStartedTime = msg.ReadLong( ); + while ( ( client = msg.ReadShort() ) != MAX_CLIENTS ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::GetClassType() ) ); +// RAVEN END + powerup = msg.ReadShort(); + + int instance = ( msg.ReadBits( ASYNC_PLAYER_INSTANCE_BITS ) ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->SetInstance( instance ); + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + static_cast< idPlayer * >( gameLocal.entities[ client ] )->Spectate( spectate ); + + // set powerups after we get instance information for this client + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( powerup & ( 1 << i ) ) { + static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0 ); + } + } + } +} + +const char* idMultiplayerGame::announcerSoundDefs[ AS_NUM_SOUNDS ] = { + // General announcements + "announce_general_one", // AS_GENERAL_ONE + "announce_general_two", // AS_GENERAL_TWO + "announce_general_three", // AS_GENERAL_THREE + "announce_general_you_win", // AS_GENERAL_YOU_WIN + "announce_general_you_lose", // AS_GENERAL_YOU_LOSE + "announce_general_fight", // AS_GENERAL_FIGHT + "announce_general_sudden_death", // AS_GENERAL_SUDDEN_DEATH + "announce_general_vote_failed", // AS_GENERAL_VOTE_FAILED + "announce_general_vote_passed", // AS_GENERAL_VOTE_PASSED + "announce_general_vote_now", // AS_GENERAL_VOTE_NOW + "announce_general_one_frag", // AS_GENERAL_ONE_FRAG + "announce_general_two_frags", // AS_GENERAL_TWO_FRAGS + "announce_general_three_frags", // AS_GENERAL_THREE_FRAGS + "announce_general_one_minute", // AS_GENERAL_ONE_MINUTE + "announce_general_five_minute", // AS_GENERAL_FIVE_MINUTE + "announce_general_prepare_to_fight", // AS_GENERAL_PREPARE_TO_FIGHT + "announce_general_quad_damage", // AS_GENERAL_QUAD_DAMAGE + "announce_general_regeneration", // AS_GENERAL_REGENERATION + "announce_general_haste", // AS_GENERAL_HASTE + "announce_general_invisibility", // AS_GENERAL_INVISIBILITY + // DM announcements + "announce_dm_you_tied_lead", // AS_DM_YOU_TIED_LEAD + "announce_dm_you_have_taken_lead", // AS_DM_YOU_HAVE_TAKEN_LEAD + "announce_dm_you_lost_lead", // AS_DM_YOU_LOST_LEAD + // Team announcements + "announce_team_enemy_score", // AS_TEAM_ENEMY_SCORES + "announce_team_you_score", // AS_TEAM_YOU_SCORE + "announce_team_teams_tied", // AS_TEAM_TEAMS_TIED + "announce_team_strogg_lead", // AS_TEAM_STROGG_LEAD + "announce_team_marines_lead", // AS_TEAM_MARINES_LEAD + "announce_team_join_marine", // AS_TEAM_JOIN_MARINE + "announce_team_join_strogg", // AS_TEAM_JOIN_STROGG + // CTF announcements + "announce_ctf_you_have_flag", // AS_CTF_YOU_HAVE_FLAG + "announce_ctf_your_team_has_flag", // AS_CTF_YOUR_TEAM_HAS_FLAG + "announce_ctf_enemy_has_flag", // AS_CTF_ENEMY_HAS_FLAG + "announce_ctf_your_team_drops_flag", // AS_CTF_YOUR_TEAM_DROPS_FLAG + "announce_ctf_enemy_drops_flag", // AS_CTF_ENEMY_DROPS_FLAG + "announce_ctf_your_flag_returned", // AS_CTF_YOUR_FLAG_RETURNED + "announce_ctf_enemy_returns_flag", // AS_CTF_ENEMY_RETURNS_FLAG + // Tourney announcements + "announce_tourney_advance", // AS_TOURNEY_ADVANCE + "announce_tourney_join_arena_one", // AS_TOURNEY_JOIN_ARENA_ONE + "announce_tourney_join_arena_two", // AS_TOURNEY_JOIN_ARENA_TWO + "announce_tourney_join_arena_three", // AS_TOURNEY_JOIN_ARENA_THREE + "announce_tourney_join_arena_four", // AS_TOURNEY_JOIN_ARENA_FOUR + "announce_tourney_join_arena_five", // AS_TOURNEY_JOIN_ARENA_FIVE + "announce_tourney_join_arena_six", // AS_TOURNEY_JOIN_ARENA_SIX + "announce_tourney_join_arena_seven", // AS_TOURNEY_JOIN_ARENA_SEVEN + "announce_tourney_join_arena_eight", // AS_TOURNEY_JOIN_ARENA_EIGHT + "announce_tourney_join_arena_waiting", // AS_TOURNEY_JOIN_ARENA_WAITING + "announce_tourney_done", // AS_TOURNEY_DONE + "announce_tourney_start", // AS_TOURNEY_START + "announce_tourney_eliminated", // AS_TOURNEY_ELIMINATED + "announce_tourney_won", // AS_TOURNEY_WON + "announce_tourney_prelims", // AS_TOURNEY_PRELIMS + "announce_tourney_quarter_finals", // AS_TOURNEY_QUARTER_FINALS + "announce_tourney_semi_finals", // AS_TOURNEY_SEMI_FINALS + "announce_tourney_final_match", // AS_TOURNEY_FINAL_MATCH + "sound/vo/mp/9_99_320_10", // AS_GENERAL_TEAM_AMMOREGEN + "sound/vo/mp/9_99_360_6" // AS_GENERAL_TEAM_DOUBLER +}; + +void idMultiplayerGame::ScheduleAnnouncerSound( announcerSound_t sound, float time, int instance, bool allowOverride ) { + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + if ( time < gameLocal.time ) { + return; + } + + if ( sound >= AS_NUM_SOUNDS ) { + return; + } + + announcerSoundNode_t* newSound = new announcerSoundNode_t; + newSound->soundShader = sound; + newSound->time = time; + newSound->announcerSoundNode.SetOwner( newSound ); + newSound->instance = instance; + newSound->allowOverride = allowOverride; + + announcerSoundNode_t* snd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = snd->announcerSoundNode.Next() ) { + if ( snd->time > newSound->time ) { + newSound->announcerSoundNode.InsertBefore( snd->announcerSoundNode ); + break; + } + } + if ( snd == NULL ) { + newSound->announcerSoundNode.AddToEnd( announcerSoundQueue ); + } +} + +void idMultiplayerGame::RemoveAnnouncerSound( int type ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + if ( snd->soundShader == type ) { + snd->announcerSoundNode.Remove( ); + delete snd; + break; + } + } + + // if a sound is currently playing, stop it + if( gameLocal.GetLocalPlayer() && lastAnnouncerSound == type ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + } +} + +void idMultiplayerGame::RemoveAnnouncerSoundRange( int startType, int endType ) { + // clean out any preexisting announcer sounds + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + for( int i = startType; i <= endType; i++ ) { + if ( snd->soundShader == i ) { + snd->announcerSoundNode.Remove( ); + delete snd; + } + } + } + + // if a sound is currently playing, stop it + if ( gameLocal.GetLocalPlayer() ) { + for( int i = startType; i <= endType; i++ ) { + if( lastAnnouncerSound == i ) { + gameLocal.GetLocalPlayer()->StopSound( SND_CHANNEL_MP_ANNOUNCER, false ); + lastAnnouncerSound = AS_NUM_SOUNDS; + break; + } + } + } +} + + +void idMultiplayerGame::ScheduleTimeAnnouncements( void ) { + if( !gameLocal.GetLocalPlayer() || !gameState ) { + // too early + return; + } + + // clean out any preexisting announcer sounds + RemoveAnnouncerSound( AS_GENERAL_ONE_MINUTE ); + RemoveAnnouncerSound( AS_GENERAL_FIVE_MINUTE ); + + if( gameState->GetMPGameState() != COUNTDOWN && gameState->GetMPGameState() != WARMUP ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + int endGameTime = 0; + + if( gameLocal.gameType == GAME_TOURNEY ) { + int arena = gameLocal.GetLocalPlayer()->GetArena(); + if( !((rvTourneyGameState*)gameState)->GetArena( arena ).IsPlaying() ) { + return; // arena is not active + } + // per-arena timelimits + endGameTime = ((rvTourneyGameState*)gameState)->GetArena( arena ).GetMatchStartTime() + ( timeLimit * 60000 ); + } else { + endGameTime = matchStartedTime + ( timeLimit * 60000 ); + } + + if( timeLimit > 5 ) { + ScheduleAnnouncerSound( AS_GENERAL_FIVE_MINUTE, endGameTime - (5 * 60000) ); + } + if( timeLimit > 1 ) { + ScheduleAnnouncerSound( AS_GENERAL_ONE_MINUTE, endGameTime - (60000) ); + } + } +} + +void idMultiplayerGame::PlayAnnouncerSounds( void ) { + announcerSoundNode_t* snd = NULL; + announcerSoundNode_t* nextSnd = NULL; + + if( !gameLocal.GetLocalPlayer() ) { + return; + } + + // if we're done playing the last sound reset override status + if( announcerPlayTime <= gameLocal.time ) { + currentSoundOverride = false; + } + + if ( announcerPlayTime > gameLocal.time && !currentSoundOverride ) { + return; + } + + // in tourney only play sounds scheduled for your current arena + if ( gameLocal.gameType == GAME_TOURNEY ) { + // go through and find the first sound to play in our arena, delete any sounds + // for other arenas we see along the way. + for ( snd = announcerSoundQueue.Next(); snd != NULL; snd = nextSnd ) { + nextSnd = snd->announcerSoundNode.Next(); + + if( snd->time > gameLocal.time ) { + return; + } + + if( snd->instance == -1 || snd->soundShader == AS_GENERAL_VOTE_NOW || snd->soundShader == AS_GENERAL_VOTE_PASSED || snd->soundShader == AS_GENERAL_VOTE_FAILED ) { + // all-instance sound + break; + } + + if( snd->instance == gameLocal.GetLocalPlayer()->GetInstance() ) { + if( snd->allowOverride && nextSnd && nextSnd->time <= gameLocal.time ) { + // this sound is OK with being over-ridden, + // and the next sound is ready to play, so go ahead and look at the next sound + snd->announcerSoundNode.Remove ( ); + delete snd; + + continue; + } else { + break; + } + } + + snd->announcerSoundNode.Remove ( ); + delete snd; + } + } else { + snd = announcerSoundQueue.Next(); + if( snd && snd->time > gameLocal.time ) { + return; + } + } + + // play the sound locally + if ( snd && snd->soundShader < AS_NUM_SOUNDS ) { + int length = 0; + + //don't play timelimit countdown announcements if game is already over + mpGameState_t state = gameState->GetMPGameState(); + if ( state == GAMEREVIEW //game is over, in scoreboard + && ( snd->soundShader == AS_GENERAL_ONE_MINUTE + || snd->soundShader == AS_GENERAL_FIVE_MINUTE ) ) { + //ignore scheduled time limit warnings that haven't executed yet + snd->announcerSoundNode.Remove(); + delete snd; + } else { + snd->announcerSoundNode.Remove(); + + gameLocal.GetLocalPlayer()->StartSoundShader( declManager->FindSound( announcerSoundDefs[ snd->soundShader ], false ), SND_CHANNEL_MP_ANNOUNCER, 0, false, &length ); + currentSoundOverride = snd->allowOverride; + lastAnnouncerSound = snd->soundShader; + + delete snd; + } + + // if sounds remain to be played, check again + announcerPlayTime = gameLocal.time + length; + } +} + +void idMultiplayerGame::ClearTeamScores ( void ) { + for ( int i = 0; i < TEAM_MAX; i++ ) { + teamScore[ i ] = 0; + teamDeadZoneScore[i] = 0; + } +} + +void idMultiplayerGame::AddTeamScore ( int team, int amount ) { + if ( team < 0 || team >= TEAM_MAX ) { + return; + } + + teamScore[ team ] += amount; +} + +void idMultiplayerGame::AddPlayerScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount += amount; + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].fragCount ); +} + +void idMultiplayerGame::AddPlayerTeamScore( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount += amount; + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ player->entityNumber ].teamFragCount ); +} + +void idMultiplayerGame::AddPlayerWin( idPlayer* player, int amount ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::AddPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins += amount; + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ player->entityNumber ].wins ); +} + +void idMultiplayerGame::SetPlayerScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].fragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); + +} + +void idMultiplayerGame::SetPlayerTeamScore( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerTeamScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].teamFragCount = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, value ); +} + +void idMultiplayerGame::SetPlayerDeadZoneScore( idPlayer* player, float value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerDeadZoneScore() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].deadZoneScore = value; +} + +void idMultiplayerGame::SetPlayerWin( idPlayer* player, int value ) { + if( player == NULL ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - NULL player specified" ); + return; + } + + if( player->entityNumber < 0 || player->entityNumber >= MAX_CLIENTS ) { + gameLocal.Warning( "idMultiplayerGame::SetPlayerWin() - Bad player entityNumber '%d'\n", player->entityNumber ); + return; + } + + playerState[ player->entityNumber ].wins = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, value ); +} + +rvCTF_AssaultPoint* idMultiplayerGame::NextAP( int team ) { + for( int i = 0; i < assaultPoints.Num(); i++ ) { + if( assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]->GetOwner() == team ) { + continue; + } + return assaultPoints[ (team ? (assaultPoints.Num() - 1 - i) : i) ]; + } + return NULL; +} + +void idMultiplayerGame::ClientSetInstance( const idBitMsg& msg ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + int instance = msg.ReadByte(); + + if ( !player ) { + gameLocal.Warning( "idMultiplayerGame::ClientSetInstance - NULL local player" ); + return; + } + + gameLocal.GetInstance( 0 )->SetSpawnInstanceID( instance ); + // on the client, we delete all entities, + // the server will send over new ones + gameLocal.InstanceClear(); + // set the starting offset for repopulation back to matching what the server will have + // this should be covered by setting indexes when populating the instances as well, but it doesn't hurt + gameLocal.firstFreeIndex = MAX_CLIENTS; + + player->SetArena( instance ); + player->SetInstance( instance ); + + // spawn the instance entities + gameLocal.GetInstance( 0 )->PopulateFromMessage( msg ); + + // players in other instances might have been hidden, update them + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p ) { + if( p->GetInstance() == instance ) { + p->ClientInstanceJoin(); + } else { + p->ClientInstanceLeave(); + } + } + } +} + +void idMultiplayerGame::ServerSetInstance( int instance ) { + for( int i = MAX_CLIENTS; i < MAX_GENTITIES; i++ ) { + idEntity* ent = gameLocal.entities[ i ]; + if( ent ) { + if( ent->GetInstance() != instance ) { + ent->InstanceLeave(); + } else { + ent->InstanceJoin(); + } + } + } +} + +const char* idMultiplayerGame::GetLongGametypeName( const char* gametype ) { + if( !idStr::Icmp( gametype, "Tourney" ) ) { + return common->GetLocalizedString( "#str_107676" ); + } else if( !idStr::Icmp( gametype, "Team DM" ) ) { + return common->GetLocalizedString( "#str_107677" ); + } else if( !idStr::Icmp( gametype, "CTF" ) ) { + return common->GetLocalizedString( "#str_107678" ); + } else if( !idStr::Icmp( gametype, "DM" ) ) { + return common->GetLocalizedString( "#str_107679" ); + } else if( !idStr::Icmp( gametype, "One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107680" ); + } else if( !idStr::Icmp( gametype, "Arena CTF" ) ) { + return common->GetLocalizedString( "#str_107681" ); + } else if( !idStr::Icmp( gametype, "Arena One Flag CTF" ) ) { + return common->GetLocalizedString( "#str_107682" ); + // RITUAL BEGIN + // squirrel: added DeadZone multiplayer mode + } else if( !idStr::Icmp( gametype, "DeadZone" ) ) { + return common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + // RITUAL END + } + + return ""; +} + +/* +================ +idMultiplayerGame::VoteGameTypeToString +================ +*/ +const char *idMultiplayerGame::VoteGameTypeToString( int gameTypeInt ) { + const char *gameType = NULL; + switch ( gameTypeInt ) { + default: + case VOTE_GAMETYPE_DM: + gameType = "DM"; + break; + case VOTE_GAMETYPE_TOURNEY: + gameType = "Tourney"; + break; + case VOTE_GAMETYPE_TDM: + gameType = "Team DM"; + break; + case VOTE_GAMETYPE_CTF: + gameType = "CTF"; + break; + case VOTE_GAMETYPE_ARENA_CTF: + gameType = "Arena CTF"; + break; + case VOTE_GAMETYPE_DEADZONE: + gameType = "DeadZone"; + break; + } + return gameType; +} + +int idMultiplayerGame::GameTypeToVote( const char *gameType ) { + if ( 0 == idStr::Icmp( gameType, "DM" ) ) { + return VOTE_GAMETYPE_DM; + } else if ( 0 == idStr::Icmp( gameType, "Tourney" ) ) { + return VOTE_GAMETYPE_TOURNEY; + } else if ( 0 == idStr::Icmp( gameType, "Team DM" ) ) { + return VOTE_GAMETYPE_TDM; + } else if ( 0 == idStr::Icmp( gameType, "CTF" ) ) { + return VOTE_GAMETYPE_CTF; + } else if ( 0 == idStr::Icmp( gameType, "Arena CTF" ) ) { + return VOTE_GAMETYPE_ARENA_CTF; + } else if ( 0 == idStr::Icmp( gameType, "DeadZone" ) ) { + return VOTE_GAMETYPE_DEADZONE; + } + + return VOTE_GAMETYPE_DM; +} + +float idMultiplayerGame::GetPlayerDeadZoneScore( idPlayer* player ) { + return playerState[ player->entityNumber ].deadZoneScore; +} + +int idMultiplayerGame::GetPlayerTime( idPlayer* player ) { + return ( gameLocal.time - player->GetConnectTime() ) / 60000; +} + +int idMultiplayerGame::GetTeamScore( idPlayer* player ) { + return GetTeamScore( player->entityNumber ); +} + +int idMultiplayerGame::GetScore( idPlayer* player ) { + return GetScore( player->entityNumber ); +} + +int idMultiplayerGame::GetWins( idPlayer* player ) { + return GetWins( player->entityNumber ); +} + +void idMultiplayerGame::EnableDamage( bool enable ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + player->fl.takedamage = enable; + } +} + +void idMultiplayerGame::ReceiveRemoteConsoleOutput( const char* output ) { + if( mainGui ) { + idStr newOutput( output ); + + if( rconHistory.Length() + newOutput.Length() > RCON_HISTORY_SIZE ) { + int removeLength = rconHistory.Find( '\n' ); + if( removeLength == -1 ) { + // nuke the whole string + rconHistory.Empty(); + } else { + while( (rconHistory.Length() - removeLength) + newOutput.Length() > RCON_HISTORY_SIZE ) { + removeLength = rconHistory.Find( '\n', removeLength + 1 ); + if( removeLength == -1 ) { + rconHistory.Empty(); + break; + } + } + } + rconHistory = rconHistory.Right( rconHistory.Length() - removeLength ); + } + + + int consoleInputStart = newOutput.Find( "Console Input: " ); + if( consoleInputStart != -1 ) { + idStr consoleInput = newOutput.Right( newOutput.Length() - consoleInputStart - 15 ); + newOutput = newOutput.Left( consoleInputStart ); + newOutput.StripTrailing( "\n" ); + consoleInput.StripTrailing( "\n" ); + mainGui->SetStateString( "admin_console_input", consoleInput.c_str() ); + } + + if( newOutput.Length() ) { + rconHistory.Append( newOutput ); + rconHistory.Append( '\n' ); + } + + mainGui->SetStateString( "admin_console_history", rconHistory.c_str() ); + } +} + +/* +=============== +idMultiplayerGame::ShuffleTeams +=============== +*/ +void idMultiplayerGame::ShuffleTeams( void ) { + // turn off autobalance if its on + bool autoBalance = gameLocal.serverInfo.GetBool( "si_autoBalance" ); + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", false ); + } + + int loosingTeam = teamScore[ TEAM_MARINE ] < teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG; + int winningTeam = loosingTeam == TEAM_MARINE ? TEAM_STROGG : TEAM_MARINE; + + for( int i = 0; i < rankedPlayers.Num(); i++ ) { + if( !(i % 2) ) { + // switch even players to losing team + if( rankedPlayers[ i ].First()->team != loosingTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ loosingTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } else { + if( rankedPlayers[ i ].First()->team != winningTeam ) { + rankedPlayers[ i ].First()->GetUserInfo()->Set( "ui_team", teamNames[ winningTeam ] ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", rankedPlayers[ i ].First()->entityNumber ) ); + } + } + } + + if( autoBalance ) { + gameLocal.serverInfo.SetBool( "si_autoBalance", true ); + } +} + + +rvGameState* idMultiplayerGame::GetGameState( void ) { + return gameState; +} + +void idMultiplayerGame::SetGameType( void ) { + if ( gameState != NULL ) { + delete gameState; + gameState = NULL; + } + + if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) == 0 ) ) { + gameLocal.gameType = GAME_DM; + gameState = new rvDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Tourney" ) == 0 ) ) { + gameLocal.gameType = GAME_TOURNEY; + gameState = new rvTourneyGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) == 0 ) ) { + gameLocal.gameType = GAME_TDM; + gameState = new rvTeamDMGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Arena One Flag CTF" ) == 0 ) ) { + gameLocal.gameType = GAME_ARENA_1F_CTF; + gameState = new rvCTFGameState(); + } else if ( ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DeadZone" ) == 0 ) ) { + gameLocal.gameType = GAME_DEADZONE; + gameState = new riDZGameState; + } else { + gameLocal.Error( "idMultiplayerGame::SetGameType() - Unknown gametype '%s'\n", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } + + // force entity filter to gametype name in multiplayer + if ( gameLocal.gameType != GAME_SP ) { + gameLocal.serverInfo.Set( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + // also set as a CVar for when serverinfo is rescanned + cvarSystem->SetCVarString( "si_entityFilter", gameLocal.serverInfo.GetString( "si_gameType" ) ); + } +} + +//asalmon: need to access total frags for a team and total score for a team +int idMultiplayerGame::GetTeamsTotalFrags( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + if(rankedPlayers[ j ].First()->team == i) + { + total += GetScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +int idMultiplayerGame::GetTeamsTotalScore( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + int total = 0; + for(int j=0; j < GetNumRankedPlayers(); j++) + { + idPlayer foo; + + if(rankedPlayers[ j ].First()->team == i) + { + total += GetTeamScore(rankedPlayers[ j ].First()->entityNumber); + } + } + + return total; + +} + +/* +=============== +idMultiplayerGame::PickMap +=============== +*/ +bool idMultiplayerGame::PickMap( idStr gameType, bool checkOnly ) { + + idStrList maps; + int miss = 0; + const idDict *mapDict; + int index = 0; + int btype; + const char* mapName; + + mapName = si_map.GetString(); + + // if we didn't set up a gametype, grab the current game type. + if ( gameType.IsEmpty() ) { + gameType = si_gameType.GetString(); + } + + // if we're playing a map of this gametype, don't change. + mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + btype = mapDict->GetInt( gameType ); + if ( btype ) { + // ( not sure what the gloubi boulga is about re-setting si_map two ways after reading it at the start of the function already ) + cvarSystem->SetCVarString( "si_map", mapName ); + si_map.SetString( mapName ); + return false; + } + } + + if ( checkOnly ) { + // always allow switching to DM mode, whatever the settings on the map ( DM should always be possible ) + if ( !idStr::Icmp( si_gameType.GetString(), "DM" ) ) { + return false; + } + // don't actually change anything, indicate we would + return true; + } + + int i; + idFileList *files; + idStrList fileList; + + int count = 0; + + files = fileSystem->ListFiles( "maps/mp", ".map" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + fileList.AddUnique( va( "mp/%s", files->GetList()[i].c_str() ) ); + } + fileSystem->FreeFileList( files ); + + files = fileSystem->ListFiles( "maps/mp", ".mapc" ); + for ( i = 0; i < files->GetList().Num(); i++, count++ ) { + idStr fixedExtension(files->GetList()[i]); + fixedExtension.SetFileExtension("map"); + fileList.AddUnique( va( "mp/%s", fixedExtension.c_str() ) ); + } + + fileList.Sort(); + + idStr name; + idStr cycle; + + //Populate the map list + for ( i = 0; i < fileList.Num(); i++) { + //Add only MP maps. + if(!idStr::FindText(fileList[i].c_str(), "mp/")) + { + maps.AddUnique(fileList[i].c_str()); + } + } + maps.Sort(); + + if(maps.Num() > 0) + { + while(miss < 100) + { + index = gameLocal.random.RandomInt( maps.Num() ); + mapName = maps[index].c_str(); + + mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) { + btype = mapDict->GetInt( gameType ); + if(btype) + { + cvarSystem->SetCVarString("si_map",mapName); + si_map.SetString( mapName ); + return true; + + } + } + miss++; + + } + + } + + //something is wrong and there are no maps for this game type. This should never happen. + gameLocal.Error( "No maps found for game type: %s.\n", gameType.c_str() ); + return false; +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( int rank, bool tied, int score ) { + char* placeString; + + if( rank == 0 ) { + //"1st^0 place with" + placeString = va( "%s%s %d", S_COLOR_BLUE, common->GetLocalizedString( "#str_107689" ), score ); + } else if( rank == 1 ) { + //"2nd^0 place with" + placeString = va( "%s%s %d", S_COLOR_RED, common->GetLocalizedString( "#str_107690" ), score ); + } else if( rank == 2 ) { + //"3rd^0 place with" + placeString = va( "%s%s %d", S_COLOR_YELLOW, common->GetLocalizedString( "#str_107691" ), score ); + } else { + //"th^0 place with" + placeString = va( "%d%s %d", rank + 1, common->GetLocalizedString( "#str_107692" ), score ); + } + + if( tied ) { + //Tied for + return va( "%s %s", common->GetLocalizedString( "#str_107693" ), placeString ); + } else { + return placeString; + } +} + +/* +=============== +idMultiplayerGame::GetPlayerRankText +=============== +*/ +char* idMultiplayerGame::GetPlayerRankText( idPlayer* player ) { + if( player == NULL ) { + return ""; + } + + bool tied = false; + int rank = GetPlayerRank( player, tied ); + return GetPlayerRankText( rank, tied, GetScore( player ) ); +} + +/* +=============== +idMultiplayerGame::WriteNetworkInfo +=============== +*/ +void idMultiplayerGame::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteStartState( clientNum, msg, true ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); + + gameState->WriteNetworkInfo( file, clientNum ); +} + +/* +=============== +idMultiplayerGame::ReadNetworkInfo +=============== +*/ +void idMultiplayerGame::ReadNetworkInfo( idFile* file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ClientReadStartState( msg ); + + gameState->ReadNetworkInfo( file, clientNum ); +} + +void idMultiplayerGame::AddPrivatePlayer( int clientId ) { + privateClientIds.Append( clientId ); +} + +void idMultiplayerGame::RemovePrivatePlayer( int clientId ) { + for( int i = 0; i < privateClientIds.Num(); i++ ) { + if( clientId == privateClientIds[ i ] ) { + privateClientIds.RemoveIndex( i ); + i--; + } + } +} + +void idMultiplayerGame::UpdatePrivatePlayerCount( void ) { + if ( !gameLocal.isServer ) { + return; + } + + int numPrivatePlayers = 0; + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( privatePlayers & (1 << i) ) { + if( gameLocal.entities[ i ] ) { + numPrivatePlayers++; + } else { + privatePlayers &= ~( 1 << i ); + } + } + } + + cvarSystem->SetCVarInteger( "si_numPrivatePlayers", numPrivatePlayers ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" " " __FILE__ " " __LINESTR__ ); +} + +void idMultiplayerGame::SetFlagEntity( idEntity* ent, int team ) { + assert( ( team == TEAM_STROGG || team == TEAM_MARINE ) ); + + flagEntities[ team ] = ent; +} + +idEntity* idMultiplayerGame::GetFlagEntity( int team ) { + assert( team >= 0 && team < TEAM_MAX ); + + return flagEntities[ team ]; +} + + +// +void idMultiplayerGame::CheckTeamBalance_f( const idCmdArgs &args ) { + + if ( args.Argc() < 5 ) { + return; + } + + idPlayer *localPlayer = gameLocal.GetLocalPlayer(); + + const char *team = args.Argv(1); + const char *yesEvent = args.Argv(2); + const char *noEvent = args.Argv(3); + const char *sameTeamEvent = args.Argv(4); + + if ( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) || !gameLocal.IsTeamGame() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + return; + } + + int teamCount[2]; + teamCount[0] = teamCount[1] = 0; + + for ( int i = 0; i < gameLocal.numClients; ++i ) { + idEntity *ent = gameLocal.entities[i]; + + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != localPlayer ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + if ( idStr::Icmp( team, "marine" ) == 0 ) { + if ( localPlayer->team == TEAM_MARINE && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_MARINE] > teamCount[TEAM_STROGG] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } else { + + if ( localPlayer->team == TEAM_STROGG && !localPlayer->spectating ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", sameTeamEvent) ); + } else { + if ( teamCount[TEAM_STROGG] > teamCount[TEAM_MARINE] ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", noEvent) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va("GuiEvent %s", yesEvent) ); + } + } + } +} + +/* +================ +idMultiplayerGame::LocalizeGametype + +dupe of rvServerScanGUI::LocalizeGametype +================ +*/ +const char *idMultiplayerGame::LocalizeGametype( void ) { + + const char *gameType; + + gameType = gameLocal.serverInfo.GetString( "si_gametype" ); + localisedGametype = gameType; + + if( !idStr::Icmp( gameType, "DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110011" ); + } + if( !idStr::Icmp( gameType, "Tourney" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110012" ); + } + if( !idStr::Icmp( gameType, "Team DM" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110013" ); + } + if( !idStr::Icmp( gameType, "CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110014" ); + } + if( !idStr::Icmp( gameType, "Arena CTF" ) ) { + localisedGametype = common->GetLocalizedString( "#str_110015" ); + } + if( !idStr::Icmp( gameType, "DeadZone" ) ) { + localisedGametype = common->GetLocalizedString( "#str_122001" ); // Squirrel@Ritual - Localized for 1.2 Patch + } + + return( localisedGametype.c_str() ); +} + +int idMultiplayerGame::VerifyTeamSwitch( int wantTeam, idPlayer *player ) { + idEntity* ent; + int teamCount[ TEAM_MAX ]; + int balanceTeam = -1; + + if( !gameLocal.serverInfo.GetBool( "si_autoBalance" ) ) { + return wantTeam; + } + + teamCount[ TEAM_MARINE ] = teamCount[ TEAM_STROGG ] = 0; + + for( int i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating && ent != player ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ TEAM_MARINE ] > teamCount[ TEAM_STROGG ] ) { + balanceTeam = TEAM_STROGG; + } else if ( teamCount[ TEAM_STROGG ] > teamCount[ TEAM_MARINE ] ) { + balanceTeam = TEAM_MARINE; + } + + return (balanceTeam == -1) ? wantTeam : balanceTeam; +} + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================ +idMultiplayerGame::NumberOfPlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfPlayersOnTeam( int team ) +{ + int teamPlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + teamPlayerCount ++; + } + } + } + + return teamPlayerCount; +} + + +/* +================ +idMultiplayerGame::NumberOfAlivePlayersOnTeam +================ +*/ +int idMultiplayerGame::NumberOfAlivePlayersOnTeam( int team ) +{ + int teamAlivePlayerCount = 0; + + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team && entPlayer->allowedToRespawn ) + { + teamAlivePlayerCount ++; + } + } + } + + return teamAlivePlayerCount; +} + + +// RITUAL END + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +/* +================ +idMultiplayerGame::OpenLocalBuyMenu +================ +*/ +void idMultiplayerGame::OpenLocalBuyMenu( void ) +{ + // Buy menu work in progress + //if ( gameLocal.mpGame.GetCurrentMenu() == 4 ) + //{ + // return; + //} + + if ( currentMenu == 4 ) + return; // Already open + + gameLocal.sessionCommand = "game_startmenu"; + gameLocal.mpGame.nextMenu = 4; +} + +/* +================ +idMultiplayerGame::RedrawLocalBuyMenu +================ +*/ +void idMultiplayerGame::RedrawLocalBuyMenu( void ) +{ + if ( !buyMenu ) + return; + + SetupBuyMenuItems(); + buyMenu->HandleNamedEvent( "update_buymenu" ); +} + + +/* +================ +idMultiplayerGame::GiveCashToTeam +================ +*/ +void idMultiplayerGame::GiveCashToTeam( int team, float cashAmount ) +{ + for ( int i = 0; i < gameLocal.numClients; i++ ) + { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* entPlayer = static_cast< idPlayer * >( ent ); + if( entPlayer->team == team ) + { + entPlayer->GiveCash( cashAmount ); + } + } + } + +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedInTheCurrentGameMode( void ) { + if ( !gameLocal.isMultiplayer ) { + return false; + } + + if ( gameLocal.gameType != GAME_TOURNEY ) { + return gameLocal.serverInfo.GetBool( "si_isBuyingEnabled" ); + } + + return false; +} + + +/* +================ +idMultiplayerGame::IsBuyingAllowedRightNow +================ +*/ +bool idMultiplayerGame::IsBuyingAllowedRightNow( void ) +{ + return ( IsBuyingAllowedInTheCurrentGameMode() && isBuyingAllowedRightNow ); +} + + +void idMultiplayerGame::AddTeamPowerup(int powerup, int time, int team) +{ + int i; + for ( i=0; i. + +=========================================================================== +*/ + +// RAVEN BEGIN +// ddynerman: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __MULTIPLAYERGAME_H__ +#define __MULTIPLAYERGAME_H__ + +/* +=============================================================================== + + Quake IV multiplayer + +=============================================================================== +*/ + +#include "mp/Buying.h" +class idPlayer; +class rvCTF_AssaultPoint; +class rvItemCTFFlag; +typedef enum { + GAME_SP, + GAME_DM, + GAME_TOURNEY, + GAME_TDM, + // bdube: added ctf + GAME_CTF, + // ddynerman: new gametypes + GAME_1F_CTF, + GAME_ARENA_CTF, + GAME_ARENA_1F_CTF, // is not used, but leaving it in the list so I don't offset GAME_DEADZONE + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + GAME_DEADZONE, + NUM_GAME_TYPES, +// RITUAL END +} gameType_t; + + +// ddynerman: teams +typedef enum { + TEAM_NONE = -1, + TEAM_MARINE, + TEAM_STROGG, + TEAM_MAX, +} team_t; + +// shouchard: server admin command types +typedef enum { + SERVER_ADMIN_KICK, + SERVER_ADMIN_BAN, + SERVER_ADMIN_REMOVE_BAN, + SERVER_ADMIN_FORCE_SWITCH, +} serverAdmin_t; + +// shouchard: vote struct for packing up interface values to be handled later +// note that we have two mechanisms for dealing with vote data that +// should be consolidated: this one that handles the interface and +// multi-field votes and the one that handles console commands and +// single line votes. +struct voteStruct_t { + int m_fieldFlags; // flags for which fields are valid + int m_kick; // id of the player + idStr m_map; // name of the map + int m_gameType; // game type enum + int m_timeLimit; + int m_fragLimit; + int m_tourneyLimit; + int m_captureLimit; + int m_buying; + int m_teamBalance; + int m_controlTime; + // restart is a flag only + // nextmap is a flag only but we don't technically support it (but doom had it so it's here) +}; + +typedef enum { + VOTEFLAG_RESTART = 0x0001, + VOTEFLAG_BUYING = 0x0002, + VOTEFLAG_TEAMBALANCE = 0x0004, + VOTEFLAG_SHUFFLE = 0x0008, + VOTEFLAG_KICK = 0x0010, + VOTEFLAG_MAP = 0x0020, + VOTEFLAG_GAMETYPE = 0x0040, + VOTEFLAG_TIMELIMIT = 0x0080, + VOTEFLAG_TOURNEYLIMIT = 0x0100, + VOTEFLAG_CAPTURELIMIT = 0x0200, + VOTEFLAG_FRAGLIMIT = 0x0400, + VOTEFLAG_CONTROLTIME = 0x0800, +} voteFlag_t; + +#define NUM_VOTES 11 +#define MAX_PRINT_LEN 128 + +// more compact than a chat line +typedef enum { + MSG_SUICIDE = 0, + MSG_KILLED, + MSG_KILLEDTEAM, + MSG_DIED, + MSG_VOTE, + MSG_VOTEPASSED, + MSG_VOTEFAILED, + MSG_SUDDENDEATH, + MSG_FORCEREADY, + MSG_JOINEDSPEC, + MSG_TIMELIMIT, + MSG_FRAGLIMIT, + MSG_CAPTURELIMIT, + MSG_TELEFRAGGED, + MSG_JOINTEAM, + MSG_HOLYSHIT, + MSG_COUNT +} msg_evt_t; + +typedef enum { + PLAYER_VOTE_NONE, + PLAYER_VOTE_NO, + PLAYER_VOTE_YES, + PLAYER_VOTE_WAIT // mark a player allowed to vote +} playerVote_t; + +typedef enum { + PRM_AUTO, + PRM_SCORE, + PRM_TEAM_SCORE, + PRM_TEAM_SCORE_PLUS_SCORE, + PRM_WINS +} playerRankMode_t; + +enum announcerSound_t { + // General announcements + AS_GENERAL_ONE, + AS_GENERAL_TWO, + AS_GENERAL_THREE, + AS_GENERAL_YOU_WIN, + AS_GENERAL_YOU_LOSE, + AS_GENERAL_FIGHT, + AS_GENERAL_SUDDEN_DEATH, + AS_GENERAL_VOTE_FAILED, + AS_GENERAL_VOTE_PASSED, + AS_GENERAL_VOTE_NOW, + AS_GENERAL_ONE_FRAG, + AS_GENERAL_TWO_FRAGS, + AS_GENERAL_THREE_FRAGS, + AS_GENERAL_ONE_MINUTE, + AS_GENERAL_FIVE_MINUTE, + AS_GENERAL_PREPARE_TO_FIGHT, + AS_GENERAL_QUAD_DAMAGE, + AS_GENERAL_REGENERATION, + AS_GENERAL_HASTE, + AS_GENERAL_INVISIBILITY, + // DM announcements + AS_DM_YOU_TIED_LEAD, + AS_DM_YOU_HAVE_TAKEN_LEAD, + AS_DM_YOU_LOST_LEAD, + // Team announcements + AS_TEAM_ENEMY_SCORES, + AS_TEAM_YOU_SCORE, + AS_TEAM_TEAMS_TIED, + AS_TEAM_STROGG_LEAD, + AS_TEAM_MARINES_LEAD, + AS_TEAM_JOIN_MARINE, + AS_TEAM_JOIN_STROGG, + // CTF announcements + AS_CTF_YOU_HAVE_FLAG, + AS_CTF_YOUR_TEAM_HAS_FLAG, + AS_CTF_ENEMY_HAS_FLAG, + AS_CTF_YOUR_TEAM_DROPS_FLAG, + AS_CTF_ENEMY_DROPS_FLAG, + AS_CTF_YOUR_FLAG_RETURNED, + AS_CTF_ENEMY_RETURNS_FLAG, + // Tourney announcements + AS_TOURNEY_ADVANCE, + AS_TOURNEY_JOIN_ARENA_ONE, + AS_TOURNEY_JOIN_ARENA_TWO, + AS_TOURNEY_JOIN_ARENA_THREE, + AS_TOURNEY_JOIN_ARENA_FOUR, + AS_TOURNEY_JOIN_ARENA_FIVE, + AS_TOURNEY_JOIN_ARENA_SIX, + AS_TOURNEY_JOIN_ARENA_SEVEN, + AS_TOURNEY_JOIN_ARENA_EIGHT, + AS_TOURNEY_JOIN_ARENA_WAITING, + AS_TOURNEY_DONE, + AS_TOURNEY_START, + AS_TOURNEY_ELIMINATED, + AS_TOURNEY_WON, + AS_TOURNEY_PRELIMS, + AS_TOURNEY_QUARTER_FINALS, + AS_TOURNEY_SEMI_FINALS, + AS_TOURNEY_FINAL_MATCH, + AS_GENERAL_TEAM_AMMOREGEN, + AS_GENERAL_TEAM_DOUBLER, + AS_NUM_SOUNDS +}; + +const int VOTEMAPS_WAITING_MAPLIST = (1<<0); +const int VOTEMAPS_WAITING_SAMAPLIST = (1<<1); +const int VOTEMAPS_WAITING_LISTMAPS = (1<<2); + +typedef struct mpPlayerState_s { + int ping; // player ping + int fragCount; // kills + int teamFragCount; // teamplay awards + int deadZoneScore; // Score in dead zone + int wins; + playerVote_t vote; // player's vote + bool scoreBoardUp; // toggle based on player scoreboard button, used to activate de-activate the scoreboard gui + bool ingame; +} mpPlayerState_t; + +const int MAX_INSTANCES = 8; + +const int NUM_CHAT_NOTIFY = 5; +const int CHAT_FADE_TIME = 400; +const int FRAGLIMIT_DELAY = 2000; +const int CAPTURELIMIT_DELAY = 750; + +const int MP_PLAYER_MINFRAGS = -100; +const int MP_PLAYER_MAXFRAGS = 999; +const int MP_PLAYER_MAXWINS = 100; +const int MP_PLAYER_MAXPING = 999; + +const int MP_PLAYER_MAXKILLS = 999; +const int MP_PLAYER_MAXDEATHS = 999; + +const int MAX_AP = 5; + +const int CHAT_HISTORY_SIZE = 2048; +const int RCON_HISTORY_SIZE = 4096; + +const int KILL_NOTIFICATION_LEN = 256; +//RAVEN BEGIN +//asalmon: update stats for Xenon +#ifdef _XENON +const int XENON_STAT_UPDATE_INTERVAL = 1000; +#endif + +const int ASYNC_PLAYER_FRAG_BITS = -idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS ); // player can have negative frags +const int ASYNC_PLAYER_WINS_BITS = idMath::BitsForInteger( MP_PLAYER_MAXWINS ); +const int ASYNC_PLAYER_PING_BITS = idMath::BitsForInteger( MP_PLAYER_MAXPING ); +const int ASYNC_PLAYER_INSTANCE_BITS = idMath::BitsForInteger( MAX_INSTANCES ); +const int ASYNC_PLAYER_DEATH_BITS = idMath::BitsForInteger( MP_PLAYER_MAXDEATHS ); +const int ASYNC_PLAYER_KILL_BITS = idMath::BitsForInteger( MP_PLAYER_MAXKILLS ); +//RAVEN END +//RITUAL BEGIN +const int MAX_TEAM_POWERUPS = 5; +//RITUAL END +// ddynerman: game state +#include "mp/GameState.h" + +typedef struct mpChatLine_s { + idStr line; + short fade; // starts high and decreases, line is removed once reached 0 +} mpChatLine_t; + +typedef struct mpBanInfo_s { + idStr name; + char guid[ CLIENT_GUID_LENGTH ]; +// unsigned char ip[ 15 ]; +} mpBanInfo_t; + +class idPhysics_Player; + +class idMultiplayerGame { + + // rvGameState manages our state + friend class rvGameState; + +public: + + idMultiplayerGame(); + + void Shutdown( void ); + + // resets everything and prepares for a match + void Reset( void ); + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + // Made this public so that level heap can be emptied. + void Clear( void ); +// RAVEN END + + // setup local data for a new player + void SpawnPlayer( int clientNum ); + + // Run the MP Game + void Run( void ); + + // Run the local client + void ClientRun( void ); + void ClientEndFrame( void ); + + // Run common code (client & server) + void CommonRun( void ); + + // draws mp hud, scoredboard, etc.. + bool Draw( int clientNum ); + + + // updates a player vote + void PlayerVote( int clientNum, playerVote_t vote ); + + // updates frag counts and potentially ends the match in sudden death + void PlayerDeath( idPlayer *dead, idPlayer *killer, int methodOfDeath ); + + void AddChatLine( const char *fmt, ... ) id_attribute((format(printf,2,3))); + void PrintChatLine( const char *message, const bool teamChat ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + void OnBuyModeTeamVictory( int winningTeam ); +// squirrel: added DeadZone multiplayer mode + void OnDeadZoneTeamVictory( int winningTeam ); +// RITUAL END + + void UpdateMainGui( void ); +// RAVEN BEGIN +// bdube: global pickup sounds (powerups, etc) + // Global item acquire sounds + void PlayGlobalItemAcquireSound ( int entityDefIndex ); + + bool CanTalk( idPlayer *from, idPlayer *to, bool echo ); + void ReceiveAndForwardVoiceData( int clientNum, const idBitMsg &inMsg, int messageType ); + +#ifdef _USE_VOICECHAT +// jscott: game side voice comms + void XmitVoiceData( void ); + void ReceiveAndPlayVoiceData( const idBitMsg &inMsg ); +#endif + +// jshepard: selects a map at random that will run with the current game type + bool PickMap( idStr gameType, bool checkOnly = false ); + void ClearVote( int clientNum = -1 ); + void ResetRconGuiStatus( void ); + +// RAVEN END + + idUserInterface* StartMenu( void ); + + const char* HandleGuiCommands( const char *menuCommand ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + void ShuffleTeams( void ); + void SetGameType( void ); + void SetMatchStartedTime( int time ) { matchStartedTime = time; } + + rvGameState* GetGameState( void ); + + + void PrintMessageEvent( int to, msg_evt_t evt, int parm1 = -1, int parm2 = -1 ); + void PrintMessage( int to, const char* message ); + + void DisconnectClient( int clientNum ); + static void ForceReady_f( const idCmdArgs &args ); + static void DropWeapon_f( const idCmdArgs &args ); + static void MessageMode_f( const idCmdArgs &args ); + static void VoiceChat_f( const idCmdArgs &args ); + static void VoiceChatTeam_f( const idCmdArgs &args ); + + +// RAVEN BEGIN +// shouchard: added console commands to mute/unmute voice chat + static void VoiceMute_f( const idCmdArgs &args ); + static void VoiceUnmute_f( const idCmdArgs &args ); + +// jshepard: command wrappers + static void ForceTeamChange_f( const idCmdArgs& args ); + static void RemoveClientFromBanList_f( const idCmdArgs& args ); + +// autobalance helper for the guis + static void CheckTeamBalance_f( const idCmdArgs &args ); + +// activates the admin console when a rcon password challenge returns. + void ProcessRconReturn( bool success ); +// RAVEN END + + typedef enum { + VOTE_RESTART = 0, + VOTE_TIMELIMIT, + VOTE_FRAGLIMIT, + VOTE_GAMETYPE, + VOTE_KICK, + VOTE_MAP, + VOTE_BUYING, + VOTE_NEXTMAP, +// RAVEN BEGIN +// shouchard: added capturelimit, round limit, and autobalance to vote flags + VOTE_CAPTURELIMIT, + VOTE_ROUNDLIMIT, + VOTE_AUTOBALANCE, + VOTE_MULTIFIELD, // all the "packed" vote functions +// RAVEN END + VOTE_CONTROLTIME, + VOTE_COUNT, + VOTE_NONE + } vote_flags_t; + + typedef enum { + VOTE_UPDATE, + VOTE_FAILED, + VOTE_PASSED, // passed, but no reset yet + VOTE_ABORTED, + VOTE_RESET // tell clients to reset vote state + } vote_result_t; + +// RAVEN BEGIN +// shouchard: added enum to remove magic numbers + typedef enum { + VOTE_GAMETYPE_DM = 0, + VOTE_GAMETYPE_TOURNEY, + VOTE_GAMETYPE_TDM, + VOTE_GAMETYPE_CTF, + VOTE_GAMETYPE_ARENA_CTF, +//RITUAL BEGIN +// + VOTE_GAMETYPE_DEADZONE, +//RITUAL END + VOTE_GAMETYPE_COUNT + } vote_gametype_t; +// RAVEN END + + void SendMapList( int clientNum ); + void ReadMapList( const idBitMsg &msg ); + + static void Vote_f( const idCmdArgs &args ); + static void CallVote_f( const idCmdArgs &args ); + void ClientCallVote( vote_flags_t voteIndex, const char *voteValue ); + void ServerCallVote( int clientNum, const idBitMsg &msg ); + void ClientStartVote( int clientNum, const char *voteString ); + void ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *voteValue ); +// RAVEN BEGIN +// shouchard: multiline vote support + void ClientUpdateVote( vote_result_t result, int yesCount, int noCount, const voteStruct_t &voteData ); +// RAVEN END + void CastVote( int clientNum, bool vote ); + void ExecuteVote( void ); +// RAVEN BEGIN +// shouchard: multiline vote handlers + void ClientCallPackedVote( const voteStruct_t &voteData ); + void ServerCallPackedVote( int clientNum, const idBitMsg &msg ); + void ClientStartPackedVote( int clientNum, const voteStruct_t &voteData ); + void ServerStartPackedVote( int clientNum, const voteStruct_t &voteData ); + void ExecutePackedVote( void ); + const char * LocalizeGametype( void ); +// RAVEN END + + void WantKilled( int clientNum ); + int NumActualClients( bool countSpectators, int *teamcount = NULL ); + void DropWeapon( int clientNum ); + void MapRestart( void ); + void JoinTeam( const char* team ); + // called by idPlayer whenever it detects a team change (init or switch) + void SwitchToTeam( int clientNum, int oldteam, int newteam ); + + bool IsPureReady( void ) const; + void ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ); + void ProcessVoiceChat( int clientNum, bool team, int index ); +// RAVEN BEGIN +// shouchard: added commands to mute/unmute voice chat + void ClientVoiceMute( int clientNum, bool mute ); + int GetClientNumFromPlayerName( const char *playerName ); + void ServerHandleVoiceMuting( int clientSrc, int clientDest, bool mute ); +// shouchard: fixing a bug in multiplayer where round timer sounds (5 minute +// warning, etc.) don't go away at the end of the round. + void ClearAnnouncerSounds( void ); +// shouchard: server admin stuff + typedef struct + { + bool restartMap; + idStr mapName; + int gameType; + int captureLimit; + int fragLimit; + int tourneyLimit; + int timeLimit; + int minPlayers; + int controlTime; + bool buying; + bool autoBalance; + bool shuffleTeams; + } serverAdminData_t; + + void HandleServerAdminBanPlayer( int clientNum ); + void HandleServerAdminRemoveBan( const char * info ); + void HandleServerAdminKickPlayer( int clientNum ); + void HandleServerAdminForceTeamSwitch( int clientNum ); + bool HandleServerAdminCommands( serverAdminData_t &data ); +// RAVEN END + +// RITUAL BEGIN + typedef struct mpTeamPowerups_s { + int powerup; + int time; + bool update; + int endTime; + } mpTeamPowerups_t; + + mpTeamPowerups_t teamPowerups[TEAM_MAX][MAX_TEAM_POWERUPS]; + + void AddTeamPowerup(int powerup, int time, int team); + void UpdateTeamPowerups(); + void SetUpdateForTeamPowerups(int team); +// RITUAL END + + void Precache( void ); + + // throttle UI switch rates + void ThrottleUserInfo( void ); + void ToggleSpectate( void ); + void ToggleReady( void ); + void ToggleTeam( void ); + + void ClearFrags( int clientNum ); + + void EnterGame( int clientNum ); + bool CanPlay( idPlayer *p ); + bool IsInGame( int clientNum ); + bool WantRespawn( idPlayer *p ); + + void ServerWriteInitialReliableMessages( const idMessageSender &sender, int clientNum ); + void ClientReadStartState( const idBitMsg &msg ); + + void ServerClientConnect( int clientNum ); + + void PlayerStats( int clientNum, char *data, const int len ); + + void AddTeamScore ( int team, int amount ); + void AddPlayerScore( idPlayer* player, int amount ); + void AddPlayerTeamScore( idPlayer* player, int amount ); + void AddPlayerWin( idPlayer* player, int amount ); + void SetPlayerTeamScore( idPlayer* player, int value ); + void SetPlayerDeadZoneScore( idPlayer* player, float value ); + void SetPlayerScore( idPlayer* player, int value ); + void SetPlayerWin( idPlayer* player, int value ); + void SetHudOverlay( idUserInterface* overlay, int duration ); + + void ClearMap ( void ); + + void EnableDamage( bool enable = true ); + + idPlayer* GetRankedPlayer( int i ); + int GetRankedPlayerScore( int i ); + int GetNumRankedPlayers( void ); + + idPlayer* GetUnrankedPlayer( int i ); + int GetNumUnrankedPlayers( void ); + + int GetScore( int i ); + int GetScore( idPlayer* player ); + + int GetTeamScore( int i ); + int GetTeamScore( idPlayer* player ); + + int GetWins( int i ); + int GetWins( idPlayer* player ); + + // asalmon: Get the score for a team. + int GetScoreForTeam( int i ); + int GetTeamsTotalFrags( int i ); + int GetTeamsTotalScore( int i ); + idUserInterface *GetMainGUI() {return mainGui;} + + float GetPlayerDeadZoneScore(idPlayer* player); + + int TeamLeader( void ); + + int GetPlayerTime( idPlayer* player ); + + const char* GetLongGametypeName( const char* gametype ); + const char* VoteGameTypeToString( int gameTypeInt ); + int GameTypeToVote( const char *gameType ); + + void ReceiveRemoteConsoleOutput( const char* output ); + + void ClientSetInstance( const idBitMsg& msg ); + void ServerSetInstance( int instance ); + + void AddPrivatePlayer( int clientId ); + void RemovePrivatePlayer( int clientId ); + +//RAVEN BEGIN +//asalmon: Xenon scoreboard update +#ifdef _XENON + void UpdateXenonScoreboard( idUserInterface *scoreBoard ); + int lastScoreUpdate; +// mekberg: for selecting local player + void SelectLocalPlayer( idUserInterface *scoreBoard ); +#endif +//RAVEN END + int VerifyTeamSwitch( int wantTeam, idPlayer *player ); + + void RemoveAnnouncerSound( int type ); + void RemoveAnnouncerSoundRange( int startType, int endType ); + void ScheduleAnnouncerSound ( announcerSound_t sound, float time, int instance = -1, bool allowOverride = false ); + void ScheduleTimeAnnouncements( void ); +// RAVEN END + + void SendDeathMessage( idPlayer *attacker, idPlayer *victim, int methodOfDeath, bool quadKill ); + void ReceiveDeathMessage( idPlayer *attacker, int attackerScore, idPlayer *victim, int victimScore, int methodOfDeath, bool quadKill ); + + rvCTF_AssaultPoint* NextAP( int team ); + int OpposingTeam( int team ); + + idList > assaultPoints; + + // Buying Manager - authority for buying system game balance constants (awards, + // costs, etc.) + riBuyingManager mpBuyingManager; + + idUserInterface* statSummary; // stat summary + rvTourneyGUI tourneyGUI; + + void ShowStatSummary( void ); + bool CanCapture( int team ); + void FlagCaptured( idPlayer *player ); + + void UpdatePlayerRanks( playerRankMode_t rankMode = PRM_AUTO ); + void UpdateTeamRanks( void ); + void UpdateHud( idUserInterface* _mphud ); + idPlayer * FragLimitHit( void ); + idPlayer * FragLeader( void ); + bool TimeLimitHit( void ); + int GetCurrentMenu( void ) { return currentMenu; } + + void SetFlagEntity( idEntity* ent, int team ); + idEntity* GetFlagEntity( int team ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile* file, int clientNum ); + + void SetShaderParms( renderView_t *view ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + int NumberOfPlayersOnTeam( int team ); + int NumberOfAlivePlayersOnTeam( int team ); + void ReportZoneControllingPlayer( idPlayer* player ); + void ReportZoneController(int team, int pCount, int situation, idEntity* zoneTrigger = 0); + bool IsValidTeam(int team); + void ControlZoneStateChanged( int team ); + + void ListMaps( void ); + + int powerupCount; + int prevAnnouncerSnd; + int defaultWinner; + int deadZonePowerupCount; + dzState_t dzState[ TEAM_MAX ]; + float marineScoreBarPulseAmount; + float stroggScoreBarPulseAmount; +// RITUAL END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + bool isBuyingAllowedRightNow; + + void OpenLocalBuyMenu( void ); + void RedrawLocalBuyMenu( void ); + void GiveCashToTeam( int team, float cashAmount ); + bool IsBuyingAllowedInTheCurrentGameMode( void ); + bool IsBuyingAllowedRightNow( void ); +// RITUAL END + static const char* teamNames[ TEAM_MAX ]; + +private: + static const char *MPGuis[]; + static const char *ThrottleVars[]; + static const char *ThrottleVarsInEnglish[]; + static const int ThrottleDelay[]; + + char killNotificationMsg[ KILL_NOTIFICATION_LEN ]; + + int pingUpdateTime; // time to update ping + + mpPlayerState_t playerState[ MAX_CLIENTS ]; + + // game state + rvGameState* gameState; + + // vote vars + vote_flags_t vote; // active vote or VOTE_NONE + int voteTimeOut; // when the current vote expires + int voteExecTime; // delay between vote passed msg and execute + int yesVotes; // counter for yes votes + int noVotes; // and for no votes + idStr voteValue; // the data voted upon ( server ) + idStr voteString; // the vote string ( client ) + bool voted; // hide vote box ( client ) + int kickVoteMap[ MAX_CLIENTS ]; +// RAVEN BEGIN +// shouchard: names for kickVoteMap + idStr kickVoteMapNames[ MAX_CLIENTS ]; + voteStruct_t currentVoteData; // used for multi-field votes +// RAVEN END + + idStr localisedGametype; + + idList voteMapDecls; + int voteMapsWaiting; + + // time related + int matchStartedTime; // time current match started + + // guis +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + //int sqRoundNumber; // round number in DeadZone; match expires when this equals "sq_numRoundsPerMatch" (cvar) +// squirrel: Mode-agnostic buymenus + idUserInterface *buyMenu; // buy menu +// RITUAL END + idUserInterface *scoreBoard; // scoreboard + idUserInterface *mainGui; // ready / nick / votes etc. + idListGUI *mapList; + idUserInterface *msgmodeGui; // message mode + int currentMenu; // 0 - none, 1 - mainGui, 2 - msgmodeGui + int nextMenu; // if 0, will do mainGui + bool bCurrentMenuMsg; // send menu state updates to server + + + enum { + MPLIGHT_CTF_MARINE, + MPLIGHT_CTF_STROGG, + MPLIGHT_QUAD, + MPLIGHT_HASTE, + MPLIGHT_REGEN, + MPLIGHT_MAX + }; + + int lightHandles[ MPLIGHT_MAX ]; + renderLight_t lights[ MPLIGHT_MAX ]; + + // chat buffer + idStr chatHistory; + + // rcon buffer + idStr rconHistory; + +//RAVEN BEGIN +//asalmon: Need to refresh stats periodically if the player is looking at stats + int currentStatClient; + int currentStatTeam; +//RAVEN END + +public: + // current player rankings + idList > rankedPlayers; + idList unrankedPlayers; + + rvPair rankedTeams[ TEAM_MAX ]; + +private: + + int lastVOAnnounce; + + int lastReadyToggleTime; + bool pureReady; // defaults to false, set to true once server game is running with pure checksums + bool currentSoundOverride; + int switchThrottle[ 3 ]; + int voiceChatThrottle; + + void SetupBuyMenuItems(); + + idList privateClientIds; + int privatePlayers; + + // player who's rank info we're displaying + idEntityPtr rankTextPlayer; + + idEntityPtr flagEntities[ TEAM_MAX ]; + idEntityPtr flagCarriers[ TEAM_MAX ]; + + // updates the passed gui with current score information + void UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ); + + // bdube: test scoreboard + void UpdateTestScoreboard( idUserInterface *scoreBoard ); + + // ddynerman: gametype specific scoreboard + void UpdateScoreboard( idUserInterface *scoreBoard ); + + void UpdateDMScoreboard( idUserInterface *scoreBoard ); + void UpdateTeamScoreboard( idUserInterface *scoreBoard ); + void UpdateSummaryBoard( idUserInterface *scoreBoard ); + + int GetPlayerRank( idPlayer* player, bool& isTied ); + char* GetPlayerRankText( idPlayer* player ); + char* GetPlayerRankText( int rank, bool tied, int score ); + + const char* BuildSummaryListString( idPlayer* player, int rankedScore ); + + void UpdatePrivatePlayerCount( void ); + + typedef struct announcerSoundNode_s { + announcerSound_t soundShader; + float time; + idLinkList announcerSoundNode; + int instance; + bool allowOverride; + } announcerSoundNode_t; + + idLinkList announcerSoundQueue; + announcerSound_t lastAnnouncerSound; + + static const char* announcerSoundDefs[ AS_NUM_SOUNDS ]; + + float announcerPlayTime; + + void PlayAnnouncerSounds ( void ); + + int teamScore[ TEAM_MAX ]; + int teamDeadZoneScore[ TEAM_MAX]; + void ClearTeamScores ( void ); + + void UpdateLeader( idPlayer* oldLeader ); + + void ClearGuis( void ); + void DrawScoreBoard( idPlayer *player ); + void CheckVote( void ); + bool AllPlayersReady( idStr* reason = NULL ); + + const char * GameTime( void ); + + bool EnoughClientsToPlay( void ); + void DrawStatSummary( void ); + // go through the clients, and see if they want to be respawned, and if the game allows it + // called during normal gameplay for death -> respawn cycles + // and for a spectator who want back in the game (see param) + void CheckRespawns( idPlayer *spectator = NULL ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID, idPlayer *player ); + void CheckSpecialLights( void ); + void ForceReady(); + // when clients disconnect or join spectate during game, check if we need to end the game + void CheckAbortGame( void ); + void MessageMode( const idCmdArgs &args ); + void DisableMenu( void ); + void SetMapShot( void ); + // scores in TDM + void VoiceChat( const idCmdArgs &args, bool team ); + +// RAVEN BEGIN +// mekberg: added + void UpdateMPSettingsModel( idUserInterface* currentGui ); +// RAVEN END + + void WriteStartState( int clientNum, idBitMsg &msg, bool withLocalClient ); + + bool RequestVoteMaps( int flags ); + + void SetMapList( const char *listName, const char *mapName, int gameTypeInt ); + void SetVoteMapList( void ); + void SetSAMapList( void ); +}; + +ID_INLINE bool idMultiplayerGame::IsPureReady( void ) const { + return pureReady; +} + +ID_INLINE void idMultiplayerGame::ClearFrags( int clientNum ) { + playerState[ clientNum ].fragCount = 0; +} + +ID_INLINE bool idMultiplayerGame::IsInGame( int clientNum ) { + return playerState[ clientNum ].ingame; +} + +ID_INLINE int idMultiplayerGame::OpposingTeam( int team ) { + return (team == TEAM_STROGG ? TEAM_MARINE : TEAM_STROGG); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetRankedPlayer( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].First(); + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetRankedPlayerScore( int i ) { + if( i >= 0 && i < rankedPlayers.Num() ) { + return rankedPlayers[ i ].Second(); + } else { + return 0; + } +} + +ID_INLINE int idMultiplayerGame::GetNumUnrankedPlayers( void ) { + return unrankedPlayers.Num(); +} + +ID_INLINE idPlayer* idMultiplayerGame::GetUnrankedPlayer( int i ) { + if( i >= 0 && i < unrankedPlayers.Num() ) { + return unrankedPlayers[ i ]; + } else { + return NULL; + } +} + +ID_INLINE int idMultiplayerGame::GetNumRankedPlayers( void ) { + return rankedPlayers.Num(); +} + +ID_INLINE int idMultiplayerGame::GetTeamScore( int i ) { + return playerState[ i ].teamFragCount; +} + +ID_INLINE int idMultiplayerGame::GetScore( int i ) { + return playerState[ i ].fragCount; +} + +ID_INLINE int idMultiplayerGame::GetWins( int i ) { + return playerState[ i ].wins; +} + +ID_INLINE void idMultiplayerGame::ResetRconGuiStatus( void ) { + if( mainGui) { + mainGui->SetStateInt( "password_valid", 0 ); + } +} + +// asalmon: needed access team scores for rich presence +ID_INLINE int idMultiplayerGame::GetScoreForTeam( int i ) { + if( i < 0 || i > TEAM_MAX ) { + return 0; + } + return teamScore[ i ]; +} + +ID_INLINE int idMultiplayerGame::TeamLeader( void ) { + if( teamScore[ TEAM_MARINE ] == teamScore[ TEAM_STROGG ] ) { + return -1; + } else { + return ( teamScore[ TEAM_MARINE ] > teamScore[ TEAM_STROGG ] ? TEAM_MARINE : TEAM_STROGG ); + } +} + +int ComparePlayersByScore( const void* left, const void* right ); +int CompareTeamsByScore( const void* left, const void* right ); + +#endif /* !__MULTIPLAYERGAME_H__ */ + +// RAVEN END diff --git a/src/mpgame/Playback.cpp b/src/mpgame/Playback.cpp new file mode 100644 index 0000000..920649b --- /dev/null +++ b/src/mpgame/Playback.cpp @@ -0,0 +1,441 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI.h" + +#define RECORD_STATE_TRACE_LEN 2048.0f + +class rvGamePlayback +{ +public: + rvGamePlayback( void ); + ~rvGamePlayback( void ); + + void RecordData( const usercmd_t &cmd, idEntity *source ); +private: + int mStartTime; + byte mOldFlags; + idStr mName; + idClipModel *mClipModel; + rvDeclPlayback *mPlayback; +}; + +idCVar g_recordPlayback( "g_recordPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "record the player movement in a playback" ); +idCVar g_playPlayback( "g_playPlayback", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "plays the current playback in a camera path" ); + +rvGamePlayback *gamePlayback = NULL; +rvCameraPlayback *playbackCamera = NULL; + +rvGamePlayback::rvGamePlayback( void ) +{ + idStr newName; + const idVec3 trace_mins( -1.0f, -1.0f, -1.0f ); + const idVec3 trace_maxs( 1.0f, 1.0f, 1.0f ); + const idBounds trace_bounds( trace_mins, trace_maxs ); + idTraceModel traceModel( trace_bounds ); + + mStartTime = gameLocal.time; + mOldFlags = 0; + mClipModel = new idClipModel( traceModel ); + + if( !g_currentPlayback.GetInteger() ) + { + newName = declManager->GetNewName( DECL_PLAYBACK, "playbacks/untitled" ); + mPlayback = ( rvDeclPlayback * )declManager->CreateNewDecl( DECL_PLAYBACK, newName, newName + ".playback" ); + mPlayback->ReplaceSourceFileText(); + mPlayback->Invalidate(); + + g_currentPlayback.SetInteger( mPlayback->Index() ); + } + else + { + mPlayback = ( rvDeclPlayback * )declManager->PlaybackByIndex( g_currentPlayback.GetInteger() ); + } + + declManager->StartPlaybackRecord( mPlayback ); + common->Printf( "Starting playback record to %s type %d\n", mPlayback->GetName(), g_recordPlayback.GetInteger() ); +} + +rvGamePlayback::~rvGamePlayback( void ) +{ + declManager->FinishPlayback( mPlayback ); + delete mClipModel; + + common->Printf( "Stopping playback play/record\n" ); +} + +void rvGamePlayback::RecordData( const usercmd_t &cmd, idEntity *source ) +{ + idPlayer *player; + trace_t trace; + idMat3 axis; + idVec3 start, end; + rvDeclPlaybackData info; + + info.Init(); + + switch( g_recordPlayback.GetInteger() ) + { + case 1: + info.SetPosition( source->GetPhysics()->GetOrigin() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + break; + + case 2: + gameLocal.GetPlayerView( start, axis ); + + end = start + axis[0] * RECORD_STATE_TRACE_LEN; + + gameLocal.Translation( gameLocal.GetLocalPlayer(), trace, start, end, mClipModel, mat3_identity, CONTENTS_SOLID | CONTENTS_RENDERMODEL, source ); + + if( trace.fraction != 1.0f ) + { + info.SetPosition( trace.endpos ); + info.SetAngles( trace.c.normal.ToAngles() ); + } + break; + + case 3: + assert( source->IsType( idPlayer::GetClassType() ) ); + if( source->IsType( idPlayer::GetClassType() ) ) + { + player = static_cast( source ); + info.SetPosition( player->GetEyePosition() ); + info.SetAngles( source->GetPhysics()->GetAxis().ToAngles() ); + } + break; + } + + // Record buttons + info.SetButtons( cmd.buttons ); + + // Record impulses + if( ( cmd.flags & UCF_IMPULSE_SEQUENCE ) != ( mOldFlags & UCF_IMPULSE_SEQUENCE ) ) + { + info.SetImpulse( cmd.impulse ); + } + else + { + info.SetImpulse( 0 ); + } + mOldFlags = cmd.flags; + + declManager->SetPlaybackData( mPlayback, gameLocal.time - mStartTime, -1, &info ); +} + +// ================================================================================================ + +void idGameEdit::DrawPlaybackDebugInfo( void ) +{ + int duration, time; + rvDeclPlaybackData pbd, pbdOld; + const rvDeclPlayback *pb; + + pb = declManager->PlaybackByIndex( g_currentPlayback.GetInteger(), true ); + if( pb ) + { + duration = SEC2MS( pb->GetDuration() ); + pbd.Init(); + pbdOld.Init(); + + declManager->GetPlaybackData( pb, -1, 0, 0, &pbdOld ); + for( time = gameLocal.GetMSec(); time < duration; time += gameLocal.GetMSec() * g_showPlayback.GetInteger() ) + { + declManager->GetPlaybackData( pb, -1, time, time, &pbd ); + gameRenderWorld->DebugArrow( colorGreen, pbdOld.GetPosition(), pbd.GetPosition(), 2 ); + pbdOld = pbd; + } + + gameRenderWorld->DebugBounds( colorRed, pb->GetBounds(), pb->GetOrigin() ); + } +} + +void idGameEdit::RecordPlayback( const usercmd_t &cmd, idEntity *source ) +{ + // Not recording - so instantly exit + if( !g_recordPlayback.GetInteger() && !gamePlayback ) + { + return; + } + + if( !gamePlayback ) + { + gamePlayback = new rvGamePlayback(); + } + + if( g_recordPlayback.GetInteger() ) + { + gamePlayback->RecordData( cmd, source ); + } + else + { + delete gamePlayback; + gamePlayback = NULL; + } +} + +// ================================================================================================ + +bool idGameEdit::PlayPlayback( void ) +{ + // Not playing - so instantly exit + if( !g_playPlayback.GetInteger() && !playbackCamera ) + { + return( false ); + } + + if( !playbackCamera ) + { + playbackCamera = static_cast( gameLocal.SpawnEntityType( rvCameraPlayback::GetClassType() ) ); + SetCamera( playbackCamera ); + + common->Printf( "Starting playback play\n" ); + } + + if( g_currentPlayback.IsModified() ) + { + // Spawn is a misnomer - it should be init with new data + playbackCamera->Spawn(); + g_currentPlayback.ClearModified(); + } + + if( !g_playPlayback.GetInteger() ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } + + return( true ); +} + +// ================================================================================================ + +void idGameEdit::ShutdownPlaybacks( void ) +{ + g_recordPlayback.SetInteger( 0 ); + g_playPlayback.SetInteger( 0 ); + + if( gamePlayback ) + { + delete gamePlayback; + gamePlayback = NULL; + } + + if( playbackCamera ) + { + playbackCamera->PostEventMS( &EV_Remove, 0 ); + playbackCamera = NULL; + SetCamera( NULL ); + } +} + +// ================================================================================================ + +/* +============ +rvPlaybackDriver::Start + +Start a new playback automatically blending with the old playback (if any) over numFrames +============ +*/ +bool rvPlaybackDriver::Start( const char *playback, idEntity *owner, int flags, int numFrames ) +{ + idVec3 startPos; + + if( !idStr::Length( playback ) ) + { + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + return( true ); + } + + const rvDeclPlayback *pb = declManager->FindPlayback( playback ); + + if( g_showPlayback.GetInteger() ) + { + common->Printf( "Starting playback: %s\n", pb->GetName() ); + } + + mOldPlaybackDecl = mPlaybackDecl; + mOldFlags = mFlags; + mOldStartTime = mStartTime; + mOldOffset = mOffset; + + mPlaybackDecl = pb; + mFlags = flags; + mStartTime = gameLocal.time; + mOffset.Zero(); + + if( flags & PBFL_RELATIVE_POSITION ) + { + mOffset = owner->GetPhysics()->GetOrigin() - pb->GetOrigin(); + } + + mTransitionTime = numFrames * gameLocal.GetMSec(); + return( true ); +} + +/* +============ +PlaybackCallback + +Called whenever a button up or down, or an impulse event is found while getting the playback data +============ +*/ +void PlaybackCallback( int type, float time, const void *data ) +{ + const rvDeclPlaybackData *pbd = ( const rvDeclPlaybackData * )data; + idEntity *ent = pbd->GetEntity(); + + ent->PostEventSec( &EV_PlaybackCallback, time, type, pbd->GetChanged(), pbd->GetImpulse() ); +} + +/* +============ +rvPlaybackDriver::UpdateFrame + +Blend two playbacks together +============ +*/ +bool rvPlaybackDriver::UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ) +{ + rvDeclPlaybackData pbd, oldPbd; + float blend, invBlend; + idStr ret; + bool expired, oldExpired; + + // Get the current playback position + pbd.Init(); + pbd.SetCallback( ent, PlaybackCallback ); + expired = declManager->GetPlaybackData( mPlaybackDecl, mFlags, gameLocal.time - mStartTime, mLastTime - mStartTime, &pbd ); + pbd.SetPosition( pbd.GetPosition() + mOffset ); + + // Get the playback data we are merging from + oldPbd.Init(); + oldExpired = declManager->GetPlaybackData( mOldPlaybackDecl, mOldFlags, gameLocal.time - mOldStartTime, gameLocal.time - mOldStartTime, &oldPbd ); + oldPbd.SetPosition( oldPbd.GetPosition() + mOldOffset ); + + mLastTime = gameLocal.time; + + if( g_showPlayback.GetInteger() && mPlaybackDecl ) + { + common->Printf( "Running playback: %s at %.1f\n", mPlaybackDecl->GetName(), MS2SEC( gameLocal.time - mStartTime ) ); + } + + // Fully merged - so delete the old one + if( gameLocal.time > mStartTime + mTransitionTime ) + { + oldExpired = true; + } + + // Interpolate the result + if( expired && oldExpired ) + { + out.Init(); + mPlaybackDecl = NULL; + mOldPlaybackDecl = NULL; + } + else if( !expired && oldExpired ) + { + out = pbd; + mOldPlaybackDecl = NULL; + } + else if( expired && !oldExpired ) + { + out = oldPbd; + mPlaybackDecl = NULL; + } + else + { + // Linear zero to one + blend = idMath::ClampFloat( 0.0f, 1.0f, ( gameLocal.time - mStartTime ) / ( float )mTransitionTime ); + + // Sinusoidal + blend = idMath::Sin( blend * idMath::HALF_PI ); + invBlend = 1.0f - blend; + + out.SetPosition( blend * pbd.GetPosition() + invBlend * oldPbd.GetPosition() ); + out.SetAngles( blend * pbd.GetAngles() + invBlend * oldPbd.GetAngles() ); + out.SetButtons( pbd.GetButtons() ); + out.SetImpulse( pbd.GetImpulse() ); + } + + return( expired ); +} + +// cnicholson: Begin Added save/restore functionality +/* +============ +rvPlaybackDriver::Save + +Save all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( mLastTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mTransitionTime ); // cnicholson: Added unsaved var + + savefile->WriteInt( mStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->WriteVec3( mOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt( mOldStartTime ); // cnicholson: Added unsaved var + savefile->WriteInt( mOldFlags ); // cnicholson: Added unsaved var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->WriteVec3( mOldOffset ); // cnicholson: Added unsaved var +} + +/* +============ +rvPlaybackDriver::Restore + +Restore all member vars for save/load games +============ +*/ +void rvPlaybackDriver::Restore( idRestoreGame *savefile ) { + + savefile->ReadInt( mLastTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mTransitionTime ); // cnicholson: Added unrestored var + + savefile->ReadInt( mStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mPlaybackDecl; + savefile->ReadVec3( mOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt( mOldStartTime ); // cnicholson: Added unrestored var + savefile->ReadInt( mOldFlags ); // cnicholson: Added unrestored var + // TOSAVE: const rvDeclPlayback *mOldPlaybackDecl; + savefile->ReadVec3( mOldOffset ); // cnicholson: Added unrestored var +} +// cnicholson: End Added save/restore functionality + +// end + diff --git a/src/mpgame/Player.cpp b/src/mpgame/Player.cpp new file mode 100644 index 0000000..16429de --- /dev/null +++ b/src/mpgame/Player.cpp @@ -0,0 +1,14180 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "Weapon.h" +#include "Projectile.h" +#include "vehicle/Vehicle.h" +#include "client/ClientModel.h" +#include "ai/AAS_tactical.h" +#include "Healing_Station.h" +#include "ai/AI_Medic.h" + +// RAVEN BEGIN +// nrausch: support for turning the weapon change ui on and off +#ifdef _XENON +#include "../ui/Window.h" + +// nrausch: support for direct button input +#include "../sys/xenon/xen_input.h" +#endif +// RAVEN END + +idCVar net_predictionErrorDecay( "net_predictionErrorDecay", "112", CVAR_FLOAT | CVAR_GAME | CVAR_NOCHEAT, "time in milliseconds it takes to fade away prediction errors", 0.0f, 200.0f ); +idCVar net_showPredictionError( "net_showPredictionError", "-1", CVAR_INTEGER | CVAR_GAME | CVAR_NOCHEAT, "show prediction errors for the given client", -1, MAX_CLIENTS ); + + +/* +=============================================================================== + + Player control. + This object handles all player movement and world interaction. + +=============================================================================== +*/ + +#ifdef _XENON +bool g_ObjectiveSystemOpen = false; +#endif + +// distance between ladder rungs (actually is half that distance, but this sounds better) +const int LADDER_RUNG_DISTANCE = 32; + +// amount of health per dose from the health station +const int HEALTH_PER_DOSE = 10; + +// time before a weapon dropped to the floor disappears +const int WEAPON_DROP_TIME = 20 * 1000; + +// time before a next or prev weapon switch happens +const int WEAPON_SWITCH_DELAY = 150; + +const float PLAYER_ITEM_DROP_SPEED = 100.0f; + +// how many units to raise spectator above default view height so it's in the head of someone +const int SPECTATE_RAISE = 25; + +const int HEALTH_PULSE = 1000; // Regen rate and heal leak rate (for health > 100) +const int ARMOR_PULSE = 1000; // armor ticking down due to being higher than maxarmor +const int AMMO_REGEN_PULSE = 1000; // ammo regen in Arena CTF +const int POWERUP_BLINKS = 5; // Number of times the powerup wear off sound plays +const int POWERUP_BLINK_TIME = 1000; // Time between powerup wear off sounds +const float MIN_BOB_SPEED = 5.0f; // minimum speed to bob and play run/walk animations at +const int MAX_RESPAWN_TIME = 10000; +const int RAGDOLL_DEATH_TIME = 3000; +#ifdef _XENON + const int RAGDOLL_DEATH_TIME_XEN_SP = 1000; + const int MAX_RESPAWN_TIME_XEN_SP = 3000; +#endif +const int STEPUP_TIME = 200; +const int MAX_INVENTORY_ITEMS = 20; + +const int ARENA_POWERUP_MASK = ( 1 << POWERUP_AMMOREGEN ) | ( 1 << POWERUP_GUARD ) | ( 1 << POWERUP_DOUBLER ) | ( 1 << POWERUP_SCOUT ); + +//const idEventDef EV_Player_HideDatabaseEntry ( "", NULL ); +const idEventDef EV_Player_ZoomIn ( "" ); +const idEventDef EV_Player_ZoomOut ( "" ); + +const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' ); +const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' ); +const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' ); +const idEventDef EV_Player_SetViewAngles( "setViewAngles", "v" ); +const idEventDef EV_Player_StopFxFov( "stopFxFov" ); +const idEventDef EV_Player_EnableWeapon( "enableWeapon" ); +const idEventDef EV_Player_DisableWeapon( "disableWeapon" ); +const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' ); +const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' ); +const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" ); +const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' ); +const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" ); +const idEventDef EV_Player_HideTip( "hideTip" ); +const idEventDef EV_Player_LevelTrigger( "levelTrigger" ); +const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" ); +const idEventDef EV_Player_GetViewPos("getViewPos", NULL, 'v'); +const idEventDef EV_Player_FinishHearingLoss ( "", "f" ); +const idEventDef EV_Player_GetAmmoData( "getAmmoData", "s", 'v'); +const idEventDef EV_Player_RefillAmmo( "refillAmmo" ); +const idEventDef EV_Player_SetExtraProjPassEntity( "setExtraProjPassEntity", "E" ); +const idEventDef EV_Player_SetArmor( "setArmor", "f" ); +const idEventDef EV_Player_DamageEffect( "damageEffect", "sE" ); +const idEventDef EV_Player_AllowFallDamage( "allowFallDamage", "d" ); + +// mekberg: allow enabling/disabling of objectives +const idEventDef EV_Player_EnableObjectives( "enableObjectives" ); +const idEventDef EV_Player_DisableObjectives( "disableObjectives" ); + +// mekberg: don't suppress showing of new objectives anymore +const idEventDef EV_Player_AllowNewObjectives( "" ); + +// RAVEN END + +CLASS_DECLARATION( idActor, idPlayer ) +// EVENT( EV_Player_HideDatabaseEntry, idPlayer::Event_HideDatabaseEntry ) + EVENT( EV_Player_ZoomIn, idPlayer::Event_ZoomIn ) + EVENT( EV_Player_ZoomOut, idPlayer::Event_ZoomOut ) + EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons ) + EVENT( EV_Player_GetMove, idPlayer::Event_GetMove ) + EVENT( EV_Player_GetViewAngles, idPlayer::Event_GetViewAngles ) + EVENT( EV_Player_SetViewAngles, idPlayer::Event_SetViewAngles ) + EVENT( EV_Player_StopFxFov, idPlayer::Event_StopFxFov ) + EVENT( EV_Player_EnableWeapon, idPlayer::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, idPlayer::Event_DisableWeapon ) + EVENT( EV_Player_GetCurrentWeapon, idPlayer::Event_GetCurrentWeapon ) + EVENT( EV_Player_GetPreviousWeapon, idPlayer::Event_GetPreviousWeapon ) + EVENT( EV_Player_SelectWeapon, idPlayer::Event_SelectWeapon ) + EVENT( EV_Player_GetWeaponEntity, idPlayer::Event_GetWeaponEntity ) + EVENT( EV_Player_ExitTeleporter, idPlayer::Event_ExitTeleporter ) + EVENT( EV_Player_HideTip, idPlayer::Event_HideTip ) + EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger ) + EVENT( EV_Player_GetViewPos, idPlayer::Event_GetViewPos ) + EVENT( EV_Player_FinishHearingLoss, idPlayer::Event_FinishHearingLoss ) + EVENT( EV_Player_GetAmmoData, idPlayer::Event_GetAmmoData ) + EVENT( EV_Player_RefillAmmo, idPlayer::Event_RefillAmmo ) + EVENT( EV_Player_AllowFallDamage, idPlayer::Event_AllowFallDamage ) + + +// mekberg: allow enabling/disabling of objectives + EVENT ( EV_Player_EnableObjectives, idPlayer::Event_EnableObjectives ) + EVENT ( EV_Player_DisableObjectives, idPlayer::Event_DisableObjectives ) + +// mekberg: don't suppress showing of new objectives anymore + EVENT ( EV_Player_AllowNewObjectives, idPlayer::Event_AllowNewObjectives ) +// RAVEN END + + EVENT( AI_EnableTarget, idPlayer::Event_EnableTarget ) + EVENT( AI_DisableTarget, idPlayer::Event_DisableTarget ) + + EVENT( EV_ApplyImpulse, idPlayer::Event_ApplyImpulse ) + +// RAVEN BEGIN +// mekberg: sethealth on player. + EVENT( AI_SetHealth, idPlayer::Event_SetHealth ) +//MCG: setArmor + EVENT( EV_Player_SetArmor, idPlayer::Event_SetArmor ) +// RAVEN END; + EVENT( EV_Player_SetExtraProjPassEntity,idPlayer::Event_SetExtraProjPassEntity ) +//MCG: direct damage + EVENT( EV_Player_DamageEffect, idPlayer::Event_DamageEffect ) +END_CLASS + +// RAVEN BEGIN +// asalmon: Xenon weapon combo system +#ifdef _XENON +nextWeaponCombo_t weaponComboChart[12] = { + // up, down, left, right + {1,3,2,4}, // 0: empty slot (select none) + {10,0,1,1}, + {2,2,5,0}, + {0,7,3,3}, + {4,4,0,9}, + {5,5,6,2}, + {6,6,6,5}, + {3,7,7,7}, + {8,8,9,8}, + {9,9,4,8}, + {10,1,10,10}, + {0,0,0,0} +}; +#endif +// RAVEN END + +const idVec4 marineHitscanTint( 0.69f, 1.0f, 0.4f, 1.0f ); +const idVec4 stroggHitscanTint( 1.0f, 0.5f, 0.0f, 1.0f ); +const idVec4 defaultHitscanTint( 0.4f, 1.0f, 0.4f, 1.0f ); + +/* +============== +idInventory::Clear +============== +*/ +void idInventory::Clear( void ) { + maxHealth = 0; + weapons = 0; + carryOverWeapons = 0; + powerups = 0; + armor = 0; + maxarmor = 0; + secretAreasDiscovered = 0; + + memset( ammo, 0, sizeof( ammo ) ); + + ClearPowerUps(); + + memset( weaponMods, 0, sizeof(weaponMods) ); + + // set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level + memset( clip, -1, sizeof( clip ) ); + + items.DeleteContents( true ); + pdas.Clear(); + videos.Clear(); + + levelTriggers.Clear(); + + nextItemPickup = 0; + nextItemNum = 1; + onePickupTime = 0; + objectiveNames.Clear(); + + ammoPredictTime = 0; + lastGiveTime = 0; + + memset( ammoRegenStep, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoIndices, -1, sizeof( int ) * MAX_WEAPONS ); + memset( startingAmmo, -1, sizeof( int ) * MAX_WEAPONS ); + memset( ammoRegenTime, -1, sizeof( int ) * MAX_WEAPONS ); +} + +/* +============== +idInventory::GivePowerUp +============== +*/ +void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) { + powerups |= 1 << powerup; + powerupEndTime[ powerup ] = msec == -1 ? -1 : (gameLocal.time + msec); +} + +/* +============== +idInventory::ClearPowerUps +============== +*/ +void idInventory::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + powerupEndTime[ i ] = 0; + } + powerups = 0; +} + +/* +============== +idInventory::GetPersistantData +============== +*/ +void idInventory::GetPersistantData( idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + const idKeyValue *kv; + const char *name; + + // armor + dict.SetInt( "armor", armor ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex( i ); + if ( name ) { + dict.SetInt( name, ammo[ i ] ); + } + } + + // items + num = 0; + for( i = 0; i < items.Num(); i++ ) { + item = items[ i ]; + + // copy all keys with "inv_" + kv = item->MatchPrefix( "inv_" ); + if ( kv ) { + while( kv ) { + sprintf( key, "item_%i %s", num, kv->GetKey().c_str() ); + dict.Set( key, kv->GetValue() ); + kv = item->MatchPrefix( "inv_", kv ); + } + num++; + } + } + dict.SetInt( "items", num ); + + // weapons + dict.SetInt( "weapon_bits", weapons ); + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + dict.SetInt( va( "weapon_mods_%i", i ), weaponMods[ i ] ); + } + + dict.SetInt( "levelTriggers", levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + sprintf( key, "levelTrigger_Level_%i", i ); + dict.Set( key, levelTriggers[i].levelName ); + sprintf( key, "levelTrigger_Trigger_%i", i ); + dict.Set( key, levelTriggers[i].triggerName ); + } +} + +/* +============== +idInventory::RestoreInventory +============== +*/ +void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { + int i; + int num; + idDict *item; + idStr key; + idStr itemname; + const idKeyValue *kv; + const char *name; + + //We might not need to clear it out. + //Clear(); + + // health/armor + maxHealth = dict.GetInt( "maxhealth", "100" ); + armor = dict.GetInt( "armor", "50" ); + maxarmor = dict.GetInt( "maxarmor", "100" ); + + // ammo + for( i = 0; i < MAX_AMMOTYPES; i++ ) { + name = rvWeapon::GetAmmoNameForIndex ( i ); + if ( name ) { + ammo[ i ] = dict.GetInt( name ); + } + } + + // items + num = dict.GetInt( "items" ); + items.SetNum( num ); + for( i = 0; i < num; i++ ) { + item = new idDict(); + items[ i ] = item; + sprintf( itemname, "item_%i ", i ); + kv = dict.MatchPrefix( itemname ); + while( kv ) { + key = kv->GetKey(); + key.Strip( itemname ); + item->Set( key, kv->GetValue() ); + kv = dict.MatchPrefix( itemname, kv ); + } + } + + // weapons are stored as a number for persistant data, but as strings in the entityDef + weapons = dict.GetInt( "weapon_bits", "0" ); + +// RAVEN BEGIN +// mekberg: removed nightmare weapon check. + Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false ); +// RAVEN END + + // weapon mods + for ( i = 0; i < MAX_WEAPONS; i++ ) { + weaponMods[ i ] = dict.GetInt( va( "weapon_mods_%i", i ) ); + } + // forcefully invalidate the weapon + owner->GiveWeaponMods( 0 ); + + num = dict.GetInt( "levelTriggers" ); + for ( i = 0; i < num; i++ ) { + sprintf( itemname, "levelTrigger_Level_%i", i ); + idLevelTriggerInfo lti; + lti.levelName = dict.GetString( itemname ); + sprintf( itemname, "levelTrigger_Trigger_%i", i ); + lti.triggerName = dict.GetString( itemname ); + levelTriggers.Append( lti ); + } + +} + +/* +============== +idInventory::Save +============== +*/ +void idInventory::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( maxHealth ); + savefile->WriteInt( weapons ); + savefile->WriteInt( powerups ); + savefile->WriteInt( armor ); + savefile->WriteInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->WriteInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->WriteInt( clip[ i ] ); + savefile->WriteInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->WriteInt( powerupEndTime[ i ] ); + } + + savefile->WriteInt( ammoPredictTime ); + savefile->WriteInt( lastGiveTime ); + + // Save Items + savefile->WriteInt( items.Num() ); + for( i = 0; i < items.Num(); i++ ) { + savefile->WriteDict( items[ i ] ); + } + + // TOSAVE: idStrList pdas; + // TOSAVE: idStrList pdaSecurity; + // TOSAVE: idStrList videos; + + // Save level triggers + savefile->WriteInt( levelTriggers.Num() ); + for ( i = 0; i < levelTriggers.Num(); i++ ) { + savefile->WriteString( levelTriggers[i].levelName ); + savefile->WriteString( levelTriggers[i].triggerName ); + } + + savefile->WriteInt( nextItemPickup ); + savefile->WriteInt( nextItemNum ); + savefile->WriteInt( onePickupTime ); + + // Save pick up item names + savefile->WriteInt( pickupItemNames.Num() ); + for( i = 0; i < pickupItemNames.Num(); i++ ) { + savefile->WriteString( pickupItemNames[ i ].name ); + savefile->WriteString( pickupItemNames[ i ].icon ); + } + + // Save objectives + savefile->WriteInt( objectiveNames.Num() ); + for( i = 0; i < objectiveNames.Num(); i++ ) { + savefile->WriteString( objectiveNames[i].screenshot ); + savefile->WriteString( objectiveNames[i].text ); + savefile->WriteString( objectiveNames[i].title ); + } +/* + // Save database + savefile->WriteInt ( database.Num() ); + for ( i = 0; i < database.Num(); i ++ ) { + savefile->WriteString ( database[i].title ); + savefile->WriteString ( database[i].text ); + savefile->WriteString ( database[i].image ); + savefile->WriteString ( database[i].filter ); + } +*/ + savefile->WriteInt( secretAreasDiscovered ); + + savefile->WriteSyncId(); +} + +/* +============== +idInventory::Restore +============== +*/ +void idInventory::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadInt( maxHealth ); + savefile->ReadInt( weapons ); + savefile->ReadInt( powerups ); + savefile->ReadInt( armor ); + savefile->ReadInt( maxarmor ); + + for( i = 0; i < MAX_AMMO; i++ ) { + savefile->ReadInt( ammo[ i ] ); + } + + for( i = 0; i < MAX_WEAPONS; i++ ) { + savefile->ReadInt( clip[ i ] ); + savefile->ReadInt( weaponMods[i] ); + } + + for( i = 0; i < POWERUP_MAX; i++ ) { + savefile->ReadInt( powerupEndTime[ i ] ); + } + + savefile->ReadInt( ammoPredictTime ); + savefile->ReadInt( lastGiveTime ); + + // Load Items + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idDict *itemdict = new idDict; + + savefile->ReadDict( itemdict ); + items.Append( itemdict ); + } + + // TORESTORE: idStrList pdas; + // TORESTORE: idStrList pdaSecurity; + // TORESTORE: idStrList videos; + + // Load level triggers + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idLevelTriggerInfo lti; + savefile->ReadString( lti.levelName ); + savefile->ReadString( lti.triggerName ); + levelTriggers.Append( lti ); + } + + savefile->ReadInt( nextItemPickup ); + savefile->ReadInt( nextItemNum ); + savefile->ReadInt( onePickupTime ); + + // Load pickup items + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + idItemInfo itemInfo; + savefile->ReadString( itemInfo.name ); + savefile->ReadString( itemInfo.icon ); + pickupItemNames.Append( itemInfo ); + } + + // Load objectives + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + idObjectiveInfo obj; + savefile->ReadString( obj.screenshot ); + savefile->ReadString( obj.text ); + savefile->ReadString( obj.title ); + objectiveNames.Append( obj ); + } +/* + // Load database + savefile->ReadInt ( num ); + for ( i = 0; i < num; i++ ) { + rvDatabaseEntry entry; + savefile->ReadString ( entry.title ); + savefile->ReadString ( entry.text ); + savefile->ReadString ( entry.image ); + savefile->ReadString ( entry.filter ); + database.Append ( entry ); + } +*/ + savefile->ReadInt( secretAreasDiscovered ); + + savefile->ReadSyncId( "idInventory::Restore" ); +} + +/* +============== +idInventory::AmmoIndexForAmmoClass +============== +*/ +int idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const { + return rvWeapon::GetAmmoIndexForName( ammo_classname ); +} + +/* +============== +idInventory::MaxAmmoForAmmoClass +============== +*/ +int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const { + return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" ); +} + +/* +============== +idInventory::AmmoIndexForWeaponClass +============== +*/ +int idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + if ( ammoRequired ) { + *ammoRequired = decl->dict.GetInt( "ammoRequired" ); + } + return AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); +} + +/* +============== +idInventory::AmmoClassForWeaponClass +============== +*/ +const char * idInventory::AmmoClassForWeaponClass( const char *weapon_classname ) { + const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); + if ( !decl ) { + gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); + } + + return decl->dict.GetString( "ammoType" ); +} + +// RAVEN BEGIN +// mekberg: if the player can pick up ammo at this time +/* +============== +idInventory::DetermineAmmoAvailability +============== +*/ +bool idInventory::DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ) { + const idDeclEntityDef *weaponDecl = NULL; + const idDict* weaponDict = NULL; + const char* mod = NULL; + const idDict* modDict = NULL; + int weaponIndex = -1; + int clipSize = 0; + int modClipSize = 0; + int difference = 0; + idStr realAmmoName( ammoName ); + + // Early out + if ( ammo[ ammoIndex ] == ammoMax ) { + return false; + } + + // Make sure the clip info is updated. + if ( owner->weapon ) { + clip[ owner->GetCurrentWeapon( ) ] = owner->weapon->AmmoInClip( ); + } + + if ( !idStr::Icmpn( ammoName, "start_ammo_", 11 ) ) { + realAmmoName.StripLeading( "start_" ); + } + + // Find the entityDef for the weapon that uses this ammo. + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + + weaponDecl = owner->GetWeaponDef( i ); + + if ( !weaponDecl ) { + continue; + } + + if ( !idStr::Icmp ( weaponDecl->dict.GetString( "ammoType" ), realAmmoName.c_str() ) ) { + weaponDict = &( weaponDecl->dict ); + weaponIndex = i; + break; + } + } + + // If we didn't find one. + if ( weaponIndex == -1 ) { + // If we are picking up ammo and we aren't currently full. + if ( ammoAmount && ammo[ ammoIndex ] != ammoMax ) { + ammo[ ammoIndex ] += ammoAmount; + if ( ammo[ ammoIndex ] > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } + return true; + } + return false; + } + + clipSize = weaponDict->GetInt( "clipSize", "0" ); + + // Find the weaponmods for this weapon and see if we have any clipsize mods. + for ( int m = 0; m < MAX_WEAPONMODS; m ++ ) { + if ( ! ( weaponMods[ weaponIndex ] & ( 1 << m ) ) ) { + continue; + } + + mod = weaponDict->GetString ( va ( "def_mod%d" , m + 1 ) ); + if ( !mod || !*mod ) { + break; + } + + modDict = gameLocal.FindEntityDefDict ( mod, false ); + modClipSize = modDict->GetInt( "clipSize", "0" ); + + if ( modClipSize > clipSize ) { + clipSize = modClipSize; + } + } + + // Don't bother with these checks if we don't have a clipsize + if ( clipSize ) { + difference = ( ammoMax - clipSize ) - ( ammo[ ammoIndex ] - clip[ weaponIndex ] ); + + if ( difference ) { + if ( ammoAmount > difference ) { + ammo[ ammoIndex ] += difference; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; + } else { + return false; + } + } else if ( ( ammo[ ammoIndex ] + ammoAmount ) > ammoMax ) { + ammo[ ammoIndex ] = ammoMax; + } else { + ammo[ ammoIndex ] += ammoAmount; + } + return true; +} +// RAVEN END + + +/* +============== +idInventory::AmmoIndexForWeaponIndex +============== +*/ +int idInventory::AmmoIndexForWeaponIndex( int weaponIndex ) { + if( ammoIndices[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoIndexForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + ammoIndices[ weaponIndex ] = AmmoIndexForWeaponClass( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ) ); + } + + return ammoIndices[ weaponIndex ]; +} + +/* +============== +idInventory::StartingAmmoForWeaponIndex +============== +*/ +int idInventory::StartingAmmoForWeaponIndex( int weaponIndex ) { + if( startingAmmo[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::StartingAmmoForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + const idKeyValue* kv = weaponDict->MatchPrefix( "inv_start_ammo" ); + if( kv == NULL ) { + startingAmmo[ weaponIndex ] = 1; + } else { + startingAmmo[ weaponIndex ] = atoi( kv->GetValue() ); + kv = weaponDict->MatchPrefix( "inv_start_ammo", kv ); + if( kv != NULL ) { + gameLocal.Error( "idInventory::StartingAmmoForWeaponIndex() - Weapon dict for player's def_weapon%d has multiple inv_start_ammo entries\n", weaponIndex ); + return -1; + } + } + } + + return startingAmmo[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenStepForWeaponIndex +============== +*/ +int idInventory::AmmoRegenStepForWeaponIndex( int weaponIndex ) { + if( ammoRegenStep[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenStepForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenStepForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenStep[ weaponIndex ] = weaponDict->GetInt( "ammoRegenStep", "1" ); + } + + return ammoRegenStep[ weaponIndex ]; +} + +/* +============== +idInventory::AmmoRegenTimeForAmmoIndex +============== +*/ +int idInventory::AmmoRegenTimeForWeaponIndex( int weaponIndex ) { + if ( ammoRegenTime[ weaponIndex ] == -1 ) { + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + if( !playerDict ) { + gameLocal.Error( "idInventory::AmmoRegenTimeForWeaponIndex() - Can't find player def\n" ); + return -1; + } + + const idDict* weaponDict = gameLocal.FindEntityDefDict( playerDict->GetString( va( "def_weapon%d", weaponIndex ) ), false ); + if( !weaponDict ) { + gameLocal.Warning( "idInventory::AmmoRegenTimeForWeaponIndex() - Unknown weapon '%d'\n", weaponIndex ); + return -1; + } + + ammoRegenTime[ weaponIndex ] = weaponDict->GetInt( "ammoRegenTime", "1" ); + } + + return ammoRegenTime[ weaponIndex ]; +} + + +/* +============== +idInventory::Give +If checkOnly is true, check only for possibility of adding to inventory, don't actually add +============== +*/ +bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped, bool checkOnly ) { + int i; + const char *pos; + const char *end; + int len; + idStr weaponString; + int max; + int amount; + + if ( owner->IsFakeClient() ) { + return false; + } + + if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { + i = AmmoIndexForAmmoClass( statname ); + max = MaxAmmoForAmmoClass( owner, statname ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( ammo[ i ] >= max ) { + return false; + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmpn( statname, "start_ammo_", 11 ) ) { + // starting ammo gives only if current ammo is below it + idStr ammoname( statname ); + ammoname.StripLeading( "start_" ); + i = AmmoIndexForAmmoClass( ammoname.c_str() ); + max = MaxAmmoForAmmoClass( owner, ammoname.c_str() ); + amount = atoi( value ); + +// RAVEN BEGIN +// mekberg: check max ammo vs clipsize when picking up ammo + if ( !gameLocal.IsMultiplayer ( ) ) { + return DetermineAmmoAvailability ( owner, statname, i, amount, max ); + } else if ( amount ) { + if ( ammo[ i ] >= amount ) { + amount = 1; + } else { + amount = amount - ammo[ i ]; + } + } + + if ( amount && !checkOnly ) { + ammo[ i ] += amount; + if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { + ammo[ i ] = max; + } + } +// RAVEN END + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( armor >= maxarmor * 2 ) { + return false; + } + } else if ( !idStr::Icmp( statname, "health" ) ) { + if ( owner->health >= maxHealth ) { + return false; + } + } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) { + i = owner->SlotForWeapon ( statname + 7 ); + if ( i != -1 && !checkOnly ) { + // set, don't add. not going over the clip size limit. + clip[ i ] = atoi( value ); + } + } else if ( !idStr::Icmp( statname, "quad" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_QUADDAMAGE, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "regen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_REGENERATION, SEC2MS( atof( value ) ) ); + } else if ( !idStr::Icmp( statname, "haste" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_HASTE, SEC2MS( atof( value ) ) ); + } else if( !idStr::Icmp( statname, "ammoregen" ) && !checkOnly ) { + GivePowerUp( owner, POWERUP_AMMOREGEN, -1 ); + } else if ( !idStr::Icmp( statname, "weapon" ) ) { + bool tookWeapon = false; + for( pos = value; pos != NULL; pos = end ) { + end = strchr( pos, ',' ); + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponName( pos, 0, len ); + + // find the number of the matching weapon names + i = owner->SlotForWeapon ( weaponName ); + if ( i == -1 ) { + gameLocal.Warning( "Unknown weapon '%s'", weaponName.c_str() ); + return false; + } + + if ( gameLocal.isMultiplayer + && ( weapons & ( 1 << i ) ) ) { + //already have this weapon + if ( !dropped ) { + //a placed weapon item + if ( gameLocal.IsWeaponsStayOn() ) { + //don't pick up weapons at all if you already have them... + continue; + } + } + // don't pickup "no ammo" weapon types twice + // not for singleplayer.. there is only one case in the game where you can get a no ammo + // weapon when you might already have it, in that case it is more consistent to pick it up + // cache the media for this weapon + const idDict* dict; + dict = &owner->GetWeaponDef ( i )->dict; + if ( dict && !dict->GetInt( "ammoRequired" ) ) { + continue; + } + } + + if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) ) { + if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) { + if ( ( owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) + || ( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) ) + && idealWeapon && !checkOnly ) + { + // client prediction should not get here + assert( !gameLocal.isClient ); + *idealWeapon = i; + } + if ( owner->hud && updateHud && lastGiveTime + 1000 < gameLocal.time && !checkOnly ) { + owner->hud->SetStateInt( "newWeapon", i ); + owner->hud->HandleNamedEvent( "newWeapon" ); + lastGiveTime = gameLocal.time; + } + if ( !checkOnly ) { + weapons |= ( 1 << i ); + } + tookWeapon = true; + } + } + } + return tookWeapon; + } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) { + // ignore these as they're handled elsewhere + return false; + } else { + // unknown item + gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname ); + return false; + } + + return true; +} + +/* +=============== +idInventoy::Drop +=============== +*/ +void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) { + // remove the weapon bit + // also remove the ammo associated with the weapon as we pushed it in the item + assert( weapon_index != -1 || weapon_classname ); + if ( weapon_index == -1 ) { + for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) { + if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) { + break; + } + } + + if ( weapon_index >= MAX_WEAPONS ) { + gameLocal.Error( "Unknown weapon '%s'", weapon_classname ); + } + } else if ( !weapon_classname ) { + weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ); + } + weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) ); + int ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL ); + if ( ammo_i ) { + clip[ weapon_index ] = -1; + ammo[ ammo_i ] = 0; + } + + weaponMods[weapon_index] = 0; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( int index, int amount ) { + if ( ( index == 0 ) || !amount ) { + // always allow weapons that don't use ammo to fire + return -1; + } + + // check if we have infinite ammo + if ( ammo[ index ] < 0 ) { + return -1; + } + + // return how many shots we can fire + return ammo[ index ] / amount; +} + +/* +=============== +idInventory::HasAmmo +=============== +*/ +int idInventory::HasAmmo( const char *weapon_classname ) { + int ammoRequired; + int index; + index = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); + return HasAmmo( index, ammoRequired ); +} + +/* +=============== +idInventory::UseAmmo +=============== +*/ +bool idInventory::UseAmmo( int index, int amount ) { + if ( !HasAmmo( index, amount ) ) { + return false; + } + + // take an ammo away if not infinite + if ( ammo[ index ] >= 0 ) { + ammo[ index ] -= amount; + ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + } + + return true; +} + +/* +============== +idPlayer::idPlayer +============== +*/ +idPlayer::idPlayer() { + memset( &usercmd, 0, sizeof( usercmd ) ); + + alreadyDidTeamAnnouncerSound = false; + + noclip = false; + godmode = false; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + spawnAnglesSet = false; + spawnAngles = ang_zero; + viewAngles = ang_zero; + deltaViewAngles = ang_zero; + cmdAngles = ang_zero; + + demoViewAngleTime = 0; + demoViewAngles = ang_zero; + + oldButtons = 0; + buttonMask = 0; + oldFlags = 0; + + lastHitFrame = 0; + lastHitArmor = false; + lastSavingThrowTime = 0; + + weapon = NULL; + + hud = NULL; + mphud = NULL; + objectiveSystem = NULL; + objectiveSystemOpen = false; + showNewObjectives = false; +#ifdef _XENON + g_ObjectiveSystemOpen = false; +#endif + objectiveButtonReleased = false; + cinematicHud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + lastDmgTime = 0; + deathClearContentsTime = 0; + nextHealthPulse = 0; + + scoreBoardOpen = false; + forceScoreBoard = false; + forceScoreBoardTime = 0; + forceRespawn = false; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// squirrel: Mode-agnostic buymenus + inBuyZone = false; + inBuyZonePrev = false; +// RITUAL END + spectating = false; + spectator = 0; + forcedReady = false; + wantSpectate = false; + + minRespawnTime = 0; + maxRespawnTime = 0; + + firstPersonViewOrigin = vec3_zero; + firstPersonViewAxis = mat3_identity; + + hipJoint = INVALID_JOINT; + chestJoint = INVALID_JOINT; + headJoint = INVALID_JOINT; + neckLeanJoint = INVALID_JOINT; + + bobFoot = 0; + bobFrac = 0.0f; + bobfracsin = 0.0f; + bobCycle = 0; + xyspeed = 0.0f; + stepUpTime = 0; + stepUpDelta = 0.0f; + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + viewBobAngles = ang_zero; + viewBob = vec3_zero; + landChange = 0; + landTime = 0; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + carryOverCurrentWeapon = -1; +// RITUAL END + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = true; + oldInventoryWeapons = 0; + +// RAVEN BEGIN +// mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; +// RAVEN END + + skin = NULL; + weaponViewSkin = NULL; + headSkin = NULL; + powerUpSkin = NULL; + + numProjectilesFired = 0; + numProjectileHits = 0; + + airless = false; + airTics = 0; + lastAirDamage = 0; + + gibDeath = false; + gibsLaunched = false; + gibDir = vec3_zero; + + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + privateCameraView = NULL; + + memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) ); + memset( loggedAccel, 0, sizeof( loggedAccel ) ); + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + + cursor = NULL; + talkCursor = 0; + + oldMouseX = 0; + oldMouseY = 0; + + lastDamageDef = 0; + lastDamageDir = vec3_zero; + lastDamageLocation = 0; + + predictedFrame = 0; + predictedOrigin = vec3_zero; + predictedAngles = ang_zero; + predictedUpdated = false; + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + predictionErrorTime = 0; + + fl.networkSync = true; + + latchedTeam = -1; + hudTeam = -1; + doingDeathSkin = false; + weaponGone = false; + useInitialSpawns = false; + lastSpectateTeleport = 0; + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + teleportKiller = -1; + lastKiller = NULL; + + respawning = false; + ready = false; + leader = false; + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + weaponCatchup = false; + lastSnapshotSequence = 0; + + aimClientNum = -1; + + spawnedTime = 0; + + isTelefragged = false; + isLagged = false; + isChatting = false; + + intentDir.Zero(); + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + ResetCash(); +// RITUAL END + + zoomFov.Init ( 0, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + + memset ( cachedWeaponDefs, 0, sizeof(cachedWeaponDefs) ); + memset ( cachedPowerupDefs, 0, sizeof(cachedPowerupDefs) ); + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time; + + weaponChangeIconsUp = false; + + reloadModel = false; + modelDecl = NULL; + + disableHud = false; + + mutedPlayers = 0; + friendPlayers = 0; + connectTime = 0; + rank = -1; + arena = 0; + + memset( nextAmmoRegenPulse, 0, sizeof( int ) * MAX_AMMO ); + + spectator = 0; + + quadOverlay = NULL; + hasteOverlay = NULL; + regenerationOverlay = NULL; + invisibilityOverlay = NULL; + powerUpOverlay = NULL; + + tourneyStatus = PTS_UNKNOWN; + + vsMsgState = false; + + deathSkinTime = 0; + + lastPickupTime = 0; + + int i; + for ( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + voiceDest[i] = -1; + voiceDestTimes[i] = 0; + } + + itemCosts = NULL; + + teamHealthRegen = NULL; + teamHealthRegenPending = false; + teamAmmoRegen = NULL; + teamAmmoRegenPending = false; + teamDoubler = NULL; + teamDoublerPending = false; + + leanVelocity = vec3_zero; + leanMaxSpeed = 0.0f; + leanBlendRatio = 0.0f; + leanMaxLateralAngle = 0.0f; + leanMaxForwardAngle = 0.0f; + leanHeadRatio = 0.0f; + leanHipRatio = 0.0f; + + ignoreOnGroundUntilFrame = 0; + prevOnGround = true; + clientIdealWeaponPredictFrame = -1; + serverReceiveEvent = false; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +void idPlayer::SetShowHud( bool showHud ) { + disableHud = !showHud; +} + +/* +============== +idPlayer::SetShowHud +============== +*/ +bool idPlayer::GetShowHud( void ) { + return !disableHud; +} + +/* +============== +idPlayer::SetWeapon +============== +*/ +void idPlayer::SetWeapon( int weaponIndex ) { + if ( weapon && weaponIndex == currentWeapon ) { + return; + } + + // Clear the weapon entity + delete weapon; + weapon = NULL; + + previousWeapon = currentWeapon; + currentWeapon = weaponIndex; + weaponGone = false; + + if ( weaponIndex < 0 ) { + weaponGone = true; + return; + } + + animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + + idTypeInfo* typeInfo; + weaponDef = GetWeaponDef( currentWeapon ); + if ( !weaponDef ) { + gameLocal.Error( "Weapon definition not found for weapon %d", currentWeapon ) ; + } + typeInfo = idClass::GetClass( weaponDef->dict.GetString( "weaponclass", "rvWeapon" ) ); + if ( !typeInfo || !typeInfo->IsType( rvWeapon::GetClassType() ) ) { + gameLocal.Error( "Invalid weapon class '%s' specified for weapon '%s'", animPrefix.c_str(), weaponDef->dict.GetString ( "weaponclass", "rvWeapon" ) ); + } + weapon = static_cast( typeInfo->CreateInstance() ); + weapon->Init( this, weaponDef, currentWeapon, isStrogg ); + weapon->CallSpawn( ); + + // Reset the zoom fov on weapon change + if ( zoomed ) { + zoomFov.Init ( gameLocal.time, 100, CalcFov(true), DefaultFov() ); + zoomed = false; + } + + UpdateHudWeapon(); + + // Remove the "weapon_" from the anim prefect for the player world anims + animPrefix.Strip( "weapon_" ); + + // Make sure weapon is hidden + if ( !weaponEnabled ) { + Event_DisableWeapon(); + } +} + +/* +============== +idPlayer::SetupWeaponEntity +============== +*/ +void idPlayer::SetupWeaponEntity( void ) { + int w; + const char *weap; + const idDeclEntityDef *decl; + idEntity *spawn; + + // don't setup weapons for spectators + if ( gameLocal.isClient || (weaponViewModel && weaponWorldModel) || spectating ) { + return; + } + + idDict args; + + if ( !weaponViewModel ) { + // setup the view model +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_viewweapon", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_viewweapon" ); + } + args.Set( "name", va( "%s_weapon", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponViewModel" ); + } + weaponViewModel = static_cast(spawn); + weaponViewModel->SetName( va("%s_weapon", name.c_str() ) ); + weaponViewModel->SetInstance( instance ); + } + + + if ( !weaponWorldModel ) { + // setup the world model + decl = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "player_animatedentity", false, false ) ); + if ( !decl ) { + gameLocal.Error( "entityDef not found: player_animatedentity" ); + } + args.Set( "name", va( "%s_weapon_world", name.c_str() ) ); + args.SetInt( "instance", instance ); + args.Set( "classname", decl->GetName() ); + spawn = NULL; + gameLocal.SpawnEntityDef( args, &spawn ); + if ( !spawn ) { + gameLocal.Error( "idPlayer::SetupWeaponEntity: failed to spawn weaponWorldModel" ); + } + weaponWorldModel = static_cast(spawn); + weaponWorldModel->fl.networkSync = true; + weaponWorldModel->SetName ( va("%s_weapon_world", name.c_str() ) ); + weaponWorldModel->SetInstance( instance ); + } + + currentWeapon = -1; + + weaponWorldModel->fl.persistAcrossInstances = true; + weaponViewModel->fl.persistAcrossInstances = true; + + for( w = 0; w < MAX_WEAPONS; w++ ) { + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( weap && *weap ) { + rvWeapon::CacheWeapon( weap ); + } + } +} + +/* +============== +idPlayer::Init +============== +*/ +void idPlayer::Init( void ) { + const char *value; + const idDict *userInfo = GetUserInfo(); + + Hide(); + + noclip = false; + godmode = false; + godmodeDamage = 0; + undying = g_forceUndying.GetBool() ? !gameLocal.isMultiplayer : false; + + oldButtons = 0; + oldFlags = 0; + + currentWeapon = -1; + idealWeapon = -1; + previousWeapon = -1; + weaponSwitchTime = 0; + weaponEnabled = true; + showWeaponViewModel = userInfo ? userInfo->GetBool( "ui_showGun" ) : cvarSystem->GetCVarBool( "ui_showGun" ); + oldInventoryWeapons = 0; + + lastHitFrame = 0; + lastHitArmor = false; + + lastDmgTime = 0; + + bobCycle = 0; + bobFrac = 0.0f; + landChange = 0; + landTime = 0; + zoomFov.Init( 0, 0, 0, 0 ); + centerView.Init( 0, 0, 0, 0 ); + fxFov = false; + + influenceFov = 0; + influenceActive = 0; + influenceRadius = 0.0f; + influenceEntity = NULL; + influenceMaterial = NULL; + influenceSkin = NULL; + + currentLoggedAccel = 0; + + focusTime = 0; + focusUI = NULL; + focusEnt = NULL; + focusType = FOCUS_NONE; + focusBrackets = NULL; + focusBracketsTime = 0; + + talkingNPC = NULL; + talkCursor = 0; + + lightningEffects = 0; + lightningNextTime = 0; + + modelName = idStr(); + + // Remove any hearing loss that may be set up from the last map + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, 0 ); + + // remove any damage effects + playerView.ClearEffects(); + + // damage values + fl.takedamage = true; + ClearPain(); + + // restore persistent data + RestorePersistantInfo(); + + bobCycle = 0; + + SetupWeaponEntity( ); + currentWeapon = -1; + previousWeapon = -1; + + flashlightOn = false; + + idealLegsYaw = 0.0f; + legsYaw = 0.0f; + legsForward = true; + oldViewYaw = 0.0f; + + leanVelocity = vec3_zero; + + ignoreOnGroundUntilFrame = 0; + prevOnGround = true; + + vehicleCameraDist = 0.0f; + + SetPMCVars(); + + // air always initialized to maximum too + airTics = pm_airTics.GetFloat(); + airless = false; + + gibDeath = false; + gibsLaunched = false; + gibDir.Zero(); + + // set the gravity + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); + + // start out standing + SetEyeHeight( pm_normalviewheight.GetFloat() ); + + stepUpTime = 0; + stepUpDelta = 0.0f; + viewBobAngles.Zero(); + viewBob.Zero(); + + if ( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + value = spawnArgs.GetString( va( "model_%s", team ? "strogg" : "marine" ), NULL ); + } else { + value = spawnArgs.GetString( "model" ); + } + + if ( gameLocal.isMultiplayer ) { + UpdateModelSetup( true ); + } else { + if ( value && ( *value != 0 ) ) { + SetModel( value ); + } + // check head + if( idStr::Icmp( head ? head->spawnArgs.GetString( "classname", "" ) : "", spawnArgs.GetString( "def_head", "" ) ) ) { + SetupHead(); + } + } + + if ( cursor ) { + cursor->SetStateInt( "talkcursor", 0 ); + cursor->HandleNamedEvent( "showCrossCombat" ); + } + + if ( !gameLocal.isMultiplayer ) { + if ( g_testDeath.GetBool() && skin ) { + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) { + skin = declManager->FindSkin( value ); + SetSkin( skin ); + renderEntity.shaderParms[6] = 0.0f; + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + if( clientHead->GetModelDefHandle() > 0) { + gameRenderWorld->RemoveDecals( clientHead->GetModelDefHandle() ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + value = spawnArgs.GetString( "joint_hips", "" ); + hipJoint = animator.GetJointHandle( value ); + if ( hipJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_hips' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_chest", "" ); + chestJoint = animator.GetJointHandle( value ); + if ( chestJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_chest' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_head", "" ); + headJoint = animator.GetJointHandle( value ); + if ( headJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_head' on '%s'", value, name.c_str() ); + } + + value = spawnArgs.GetString( "joint_neckLean", "neckcontrol" ); + neckLeanJoint = animator.GetJointHandle( value ); + if ( neckLeanJoint == INVALID_JOINT ) { + gameLocal.Error( "Joint '%s' not found for 'joint_neck' on '%s'", value, name.c_str() ); + } + + // read player lean properties + spawnArgs.GetFloat( "lean_maxSpeed", "500", leanMaxSpeed ); + spawnArgs.GetFloat( "lean_blendRatio", "0.9", leanBlendRatio ); + spawnArgs.GetFloat( "lean_maxLateralAngle", "30", leanMaxLateralAngle ); + spawnArgs.GetFloat( "lean_maxForwardAngle", "50", leanMaxForwardAngle ); + spawnArgs.GetFloat( "lean_headRatio", "-0.5", leanHeadRatio ); + spawnArgs.GetFloat( "lean_hipRatio", "0.5", leanHipRatio ); + + // initialize the script variables + memset( &pfl, 0, sizeof( pfl ) ); + pfl.onGround = true; + pfl.noFallingDamage = false; + + // Start in idle + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + forceScoreBoard = false; + forceScoreBoardTime = 0; + forcedReady = false; + + privateCameraView = NULL; + + lastSpectateChange = 0; + lastArenaChange = 0; + lastTeleFX = -9999; + + hiddenWeapon = false; + tipUp = false; + objectiveUp = false; + teleportEntity = NULL; + lastKiller = NULL; + teleportKiller = -1; + leader = false; + + SetPrivateCameraView( NULL ); + + lastSnapshotSequence = 0; + + if ( !gameLocal.isMultiplayer ) { + // in MP we set isStrogg in UpdateModelSetup() + isStrogg = spawnArgs.GetBool ( "strogg", "0" ); + } + + + aimClientNum = -1; + if ( mphud ) { + mphud->HandleNamedEvent( "aim_fade" ); + } + + isChatting = false; + + SetInitialHud(); + + emote = PE_NONE; + + powerupEffectTime = 0; + powerupEffect = NULL; + powerupEffectType = 0; + hasteEffect = NULL; + flagEffect = NULL; + arenaEffect = NULL; + + quadOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_quaddamage_overlay" ), false ); + hasteOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_haste_overlay" ), false ); + regenerationOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_regeneration_overlay" ), false ); + invisibilityOverlay = declManager->FindMaterial( spawnArgs.GetString( "mtr_invisibility_overlay" ), false ); + powerUpOverlay = NULL; + + if ( gameLocal.isMultiplayer && IsLocalClient() ) { + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP) && gameLocal.mpGame.GetGameState()->GetMPGameState() != SUDDENDEATH ){ + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_WARMUP && ((rvTourneyGameState*)(gameLocal.mpGame.GetGameState()))->GetArena( arena ).GetState() != AS_SUDDEN_DEATH ) { + // don't clear notices while in warmup modes or sudden death + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + if ( (gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP) && vsMsgState ) { + GUIMainNotice( "" ); + GUIFragNotice( "" ); + } + } + + deathSkinTime = 0; + deathStateHitch = false; + + lastPickupTime = 0; + + if ( teamHealthRegenPending ) { + assert( teamHealthRegen == NULL ); + teamHealthRegenPending = false; + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamAmmoRegenPending ) { + assert( teamAmmoRegen == NULL ); + teamAmmoRegenPending = false; + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + if ( teamDoublerPending ) { + assert( teamDoubler == NULL ); + teamDoublerPending = false; + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } + + clientIdealWeaponPredictFrame = -1; + serverReceiveEvent = false; +} + +/* +=============== +idPlayer::ProjectHeadOverlay +=============== +*/ +void idPlayer::ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ) { + + if( clientHead ) { + clientHead.GetEntity()->ProjectOverlay( point, dir, size, decal ); + } +} + +/* +=============== +idPlayer::GetCursorGUI +=============== +*/ +idUserInterface* idPlayer::GetCursorGUI( void ) { + idStr temp; + + assert( !gameLocal.isMultiplayer || IsLocalClient() ); + if ( cursor ) { + return cursor; + } + if ( spawnArgs.GetString( "cursor", "", temp ) ) { + cursor = uiManager->FindGui( temp, true, gameLocal.isMultiplayer, gameLocal.isMultiplayer ); + } + return cursor; +} + +/* +============== +idPlayer::Spawn + +Prepare any resources used by the player. +============== +*/ +void idPlayer::Spawn( void ) { + idStr temp; + idBounds bounds; + + if ( entityNumber >= MAX_CLIENTS && !IsFakeClient() ) { + gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." ); + } + + // allow thinking during cinematics + cinematic = true; + + if ( gameLocal.isMultiplayer ) { + // always start in spectating state waiting to be spawned in + // do this before SetClipModel to get the right bounding box + spectating = true; + } + + // set our collision model + physicsObj.SetSelf( this ); + SetClipModel( ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + SetPhysics( &physicsObj ); + InitAASLocation(); + + skin = renderEntity.customSkin; + + // only the local player needs guis + // for server netdemos that have no local player, we use demo_* guis in idGameLocal + if ( !gameLocal.isMultiplayer || IsLocalClient() ) { + + // load HUD + hud = NULL; + mphud = NULL; + + overlayHud = NULL; + overlayHudTime = 0; + + objectiveSystem = NULL; + + if ( spawnArgs.GetString( "hud", "", temp ) ) { + hud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No hud for player." ); + } + + if ( gameLocal.isMultiplayer ) { + if ( spawnArgs.GetString( "mphud", "", temp ) ) { + mphud = uiManager->FindGui( temp, true, false, true ); + } else { + gameLocal.Warning( "idPlayer::Spawn() - No MP hud overlay while in MP."); + } + } + + if ( hud ) { + hud->Activate( true, gameLocal.time ); + } + + if ( mphud ) { + mphud->Activate( true, gameLocal.time ); + } + + // load cursor + GetCursorGUI(); + if ( cursor ) { + cursor->Activate( true, gameLocal.time ); + } + + // Load + + if ( spawnArgs.GetString ( "cinematicHud", "", temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + + if ( !gameLocal.isMultiplayer ) { + objectiveSystem = uiManager->FindGui( spawnArgs.GetString( "wristcomm", "guis/wristcomm.gui" ), true, false, true ); + objectiveSystemOpen = false; +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + + // clear votes + // if we want to display current votes that were started before a player was connected + // but are still being voted on, this should check the current vote and update the gui appropriately + gameLocal.mpGame.ClearVote( entityNumber ); + } + + // load the armor sound feedback + declManager->FindSound( "player_sounds_hitArmor" ); + + animator.RemoveOriginOffset( true ); + + // initialize user info related settings + // on server, we wait for the userinfo broadcast, as this controls when the player is initially spawned in game + // ocassionally, a race condition may mark a client in-game before he is spawned, if this is the case, parse the userinfo here + if ( (gameLocal.isClient || IsLocalClient() ) || (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber ) ) ) { + UserInfoChanged(); + } + + // create combat collision hull for exact collision detection + SetCombatModel(); + + // init the damage effects + playerView.SetPlayerEntity( this ); + + // supress model in non-player views, but allow it in mirrors and remote views + renderEntity.suppressSurfaceInViewID = entityNumber+1; + + // don't project shadow on self or weapon + renderEntity.noSelfShadow = true; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + clientHead->GetRenderEntity()->noSelfShadow = true; + } + } else { + idAFAttachment *headEnt = head.GetEntity(); + if ( headEnt ) { + headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1; + headEnt->GetRenderEntity()->noSelfShadow = true; + } + } + + if ( gameLocal.isMultiplayer ) { + Init(); + Hide(); // properly hidden if starting as a spectator + + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + if( inOtherInstance ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } + + if ( !gameLocal.isClient ) { + // set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + spectator = entityNumber; + forceRespawn = true; + assert( spectating ); + } + } else { + SetupWeaponEntity( ); + SpawnFromSpawnSpot( ); + } + + // trigger playtesting item gives, if we didn't get here from a previous level + // the devmap key will be set on the first devmap, but cleared on any level + // transitions + if ( !gameLocal.isMultiplayer && gameLocal.serverInfo.FindKey( "devmap" ) ) { + // fire a trigger with the name "devmap" + idEntity *ent = gameLocal.FindEntity( "devmap" ); + if ( ent ) { + ent->ActivateTargets( this ); + } + } + + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + hiddenWeapon = true; + if ( weapon ) { + weapon->LowerWeapon(); + } + idealWeapon = SlotForWeapon( "weapon_blaster" ); + Event_DisableWeapon(); + } else { + hiddenWeapon = false; + } + + if ( hud ) { + UpdateHudWeapon( ); + hud->StateChanged( gameLocal.time ); + } + + tipUp = false; + objectiveUp = false; + + aiManager.AddTeammate( this ); + + if ( inventory.levelTriggers.Num() ) { + PostEventMS( &EV_Player_LevelTrigger, 0 ); + } + + // ddynerman: defaults for these values are the single player fall deltas + fatalFallDelta = spawnArgs.GetFloat("fatal_fall_delta", "65"); + hardFallDelta = spawnArgs.GetFloat("hard_fall_delta", "45"); + softFallDelta = spawnArgs.GetFloat("soft_fall_delta", "30"); + noFallDelta = spawnArgs.GetFloat("no_fall_delta", "7"); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); + + gibSkin = declManager->FindSkin( spawnArgs.GetString( "skin_gibskin" ) ); + + // Skil levels + dynamicProtectionScale = 1.0f; + if ( !gameLocal.isMultiplayer ) { + if ( g_skill.GetInteger() < 2 ) { + if ( health < 25 ) { + health = 25; + } + } else { + //g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f ); + } + } + + // Powerup joints? + if ( spawnArgs.GetString ( "powerup_effect_joints", "", temp ) ) { + animator.GetJointList ( temp, powerupEffectJoints ); + } + + // RAVEN BEGIN + // mekberg: allow disabling of objectives during non-cinematic time periods + objectivesEnabled = true; + + // mekberg: new objectives are suppressed until this event is processed + PostEventMS( &EV_Player_AllowNewObjectives, 5000 ); + tourneyStatus = PTS_UNKNOWN; + + predictionOriginError = vec3_zero; + predictionAnglesError = ang_zero; + + // zero out view angles when we spawn ourselves in MP - the server will send down + // the correct ones (only zero if our input is still zero'd) + if( gameLocal.isClient && IsLocalClient() && usercmd.angles[ 0 ] == 0 && usercmd.angles[ 1 ] == 0 && usercmd.angles[ 2 ] == 0 ) { + deltaViewAngles = ang_zero; + } +//RITUAL BEGIN + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = 0; +//RITUAL END + + itemCosts = static_cast< const idDeclEntityDef * >( declManager->FindType( DECL_ENTITYDEF, "ItemCostConstants", false ) ); +} + +/* +============== +idPlayer::~idPlayer() + +Release any resources used by the player. +============== +*/ +idPlayer::~idPlayer() { + if( gameLocal.mpGame.GetGameState() ) { + gameLocal.mpGame.GetGameState()->ClientDisconnect( this ); + } + + delete weaponViewModel; + delete weaponWorldModel; + delete weapon; + delete aasSensor; + + SetPhysics( NULL ); +} + +/* +=========== +idPlayer::Save +=========== +*/ +void idPlayer::Save( idSaveGame *savefile ) const { + assert( !IsFakeClient() ); + + int i; + + savefile->WriteUsercmd( usercmd ); + + playerView.Save( savefile ); + + savefile->WriteBool( noclip ); + savefile->WriteBool( godmode ); + savefile->WriteInt ( godmodeDamage ); + savefile->WriteBool( undying ); + + // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame + savefile->WriteAngles( spawnAngles ); + savefile->WriteAngles( viewAngles ); + savefile->WriteAngles( cmdAngles ); + + savefile->WriteInt( buttonMask ); + savefile->WriteInt( oldButtons ); + savefile->WriteInt( oldFlags ); + + savefile->WriteInt( 0 ); + savefile->WriteInt( 0 ); + savefile->WriteInt( lastSavingThrowTime ); + + // idBoolFields don't need to be saved, just re-linked in Restore + savefile->Write( &pfl, sizeof( pfl ) ); + + inventory.Save( savefile ); + + //weapon->Save( savefile ); // Don't save this + + weaponViewModel.Save( savefile ); + weaponWorldModel.Save ( savefile ); + // weaponDef restore = weaponDef = GetWeaponDef ( currentWeapon ); + + savefile->WriteUserInterface( hud, false ); +// savefile->WriteUserInterface( mphud, false ); // Don't save MP stuff + savefile->WriteUserInterface( objectiveSystem, false ); + savefile->WriteUserInterface( cinematicHud, false ); + savefile->WriteBool( objectiveSystemOpen ); + savefile->WriteBool( disableHud ); + + savefile->WriteInt( lastDmgTime ); + savefile->WriteInt( deathClearContentsTime ); + savefile->WriteBool( doingDeathSkin ); + savefile->WriteInt( nextHealthPulse ); + savefile->WriteInt( nextArmorPulse ); + savefile->WriteBool( hiddenWeapon ); + +// savefile->WriteInt( spectator ); // Don't save MP stuff + +// savefile->WriteBool( scoreBoardOpen ); // Don't save MP stuff +// savefile->WriteBool( tourneyBracketsOpen ); // Don't save MP stuff +// savefile->WriteBool( forceScoreBoard ); // Don't save MP stuff +// savefile->WriteBool( forceRespawn ); // Don't save MP stuff + +// savefile->WriteBool( spectating ); // Don't save MP stuff +// savefile->WriteBool( lastHitToggle ); // Don't save MP stuff +// savefile->WriteBool( forcedReady ); // Don't save MP stuff +// savefile->WriteBool( wantSpectate ); // Don't save MP stuff + +// savefile->WriteBool( weaponGone ); // Don't save MP stuff +// savefile->WriteBool( useInitialSpawns ); // Don't save MP stuff +// savefile->WriteBool( isLagged ); // Don't save MP stuff +// savefile->WriteBool( isChatting ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateTeleport ); // Don't save MP stuff +// savefile->WriteInt( latchedTeam ); // Don't save MP stuff +// savefile->WriteInt( spawnedTime ); // Don't save MP stuff + +// teleportEntity.Save( savefile ); // Don't save MP stuff +// savefile->WriteInt( teleportKiller ); // Don't save MP stuff + + savefile->WriteInt( minRespawnTime ); + savefile->WriteInt( maxRespawnTime ); + + savefile->WriteVec3( firstPersonViewOrigin ); + savefile->WriteMat3( firstPersonViewAxis ); + + // don't bother saving dragEntity or aasSensor since it's a dev tool + savefile->WriteVec3( intentDir ); + + savefile->WriteFloat ( vehicleCameraDist ); + + savefile->WriteJoint( hipJoint ); + savefile->WriteJoint( chestJoint ); + savefile->WriteJoint( headJoint ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( aasLocation.Num() ); + for( i = 0; i < aasLocation.Num(); i++ ) { + savefile->WriteInt( aasLocation[ i ].areaNum ); + savefile->WriteVec3( aasLocation[ i ].pos ); + } + + savefile->WriteString( modelName ); // cnicholson: Added unsaved var + // TOSAVE: const idDict* modelDict + + savefile->WriteInt( bobFoot ); + savefile->WriteFloat( bobFrac ); + savefile->WriteFloat( bobfracsin ); + savefile->WriteInt( bobCycle ); + savefile->WriteFloat( xyspeed ); + savefile->WriteInt( stepUpTime ); + savefile->WriteFloat( stepUpDelta ); + savefile->WriteFloat( idealLegsYaw ); + savefile->WriteFloat( legsYaw ); + savefile->WriteBool( legsForward ); + savefile->WriteFloat( oldViewYaw ); + savefile->WriteAngles( viewBobAngles ); + savefile->WriteVec3( viewBob ); + savefile->WriteInt( landChange ); + savefile->WriteInt( landTime ); + + savefile->WriteFloat( fatalFallDelta ); + savefile->WriteFloat( hardFallDelta ); + savefile->WriteFloat( softFallDelta ); + savefile->WriteFloat( noFallDelta ); + + savefile->WriteInt( currentWeapon ); + savefile->WriteInt( idealWeapon ); + savefile->WriteInt( previousWeapon ); + savefile->WriteInt( weaponSwitchTime ); + savefile->WriteBool( weaponEnabled ); + + savefile->WriteBool ( flashlightOn); + savefile->WriteBool ( zoomed ); + + savefile->WriteBool( reloadModel ); + + savefile->WriteSkin( skin ); + savefile->WriteSkin( powerUpSkin ); + savefile->WriteSkin( gibSkin ); + + savefile->WriteInt( numProjectilesFired ); + savefile->WriteInt( numProjectileHits ); + + savefile->WriteBool( airless ); + savefile->WriteInt( airTics ); + savefile->WriteInt( lastAirDamage ); + + savefile->WriteBool( gibDeath ); + savefile->WriteBool( gibsLaunched ); + savefile->WriteVec3( gibDir ); + + savefile->WriteBool( isStrogg ); + + savefile->WriteInterpolate( zoomFov ); + savefile->WriteInterpolate( centerView ); + savefile->WriteBool( fxFov ); + + savefile->WriteFloat( influenceFov ); + savefile->WriteInt( influenceActive ); + savefile->WriteObject( influenceEntity ); + savefile->WriteMaterial( influenceMaterial ); + savefile->WriteFloat( influenceRadius ); + savefile->WriteSkin( influenceSkin ); + + savefile->WriteObject( privateCameraView ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->WriteAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->WriteInt( loggedAccel[ i ].time ); + savefile->WriteVec3( loggedAccel[ i ].dir ); + } + savefile->WriteInt( currentLoggedAccel ); + + savefile->WriteUserInterface( focusUI, false ); + savefile->WriteInt( focusTime ); + savefile->WriteInt ( focusType ); + focusEnt.Save ( savefile ); + savefile->WriteUserInterface( focusBrackets, false ); + savefile->WriteInt( focusBracketsTime ); + + talkingNPC.Save( savefile ); + + extraProjPassEntity.Save( savefile ); + + savefile->WriteInt( talkCursor ); + savefile->WriteUserInterface( cursor, false ); + + savefile->WriteUserInterface( overlayHud, false ); + savefile->WriteInt ( overlayHudTime ); + + savefile->WriteBool( targetFriendly ); + + savefile->WriteInt( oldMouseX ); + savefile->WriteInt( oldMouseY ); + + savefile->WriteBool( tipUp ); + savefile->WriteBool( objectiveUp ); + + savefile->WriteFloat( dynamicProtectionScale ); + savefile->WriteInt( lastDamageDef ); + savefile->WriteVec3( lastDamageDir ); + savefile->WriteInt( lastDamageLocation ); + + savefile->WriteInt( predictedFrame ); + savefile->WriteVec3( predictedOrigin ); + savefile->WriteAngles( predictedAngles ); + savefile->WriteBool( predictedUpdated ); + savefile->WriteVec3( predictionOriginError ); + savefile->WriteAngles( predictionAnglesError ); + savefile->WriteInt( predictionErrorTime ); + +// savefile->WriteBool( ready ); // Don't save MP stuff +// savefile->WriteBool( respawning ); // Don't save MP stuff +// savefile->WriteBool( leader ); // Don't save MP stuff +// savefile->WriteBool( weaponCatchup ); // Don't save MP stuff +// savefile->WriteBool( isTelefragged ); // Don't save MP stuff + +// savefile->WriteInt( lastSpectateChange ); // Don't save MP stuff +// savefile->WriteInt( lastTeleFX ); // Don't save MP stuff +// savefile->WriteInt( lastSnapshotSequence ); // Don't save MP stuff + +// savefile->WriteInt( aimClientNum ); // Don't save MP stuff + +// lastImpulsePlayer->Save( savefile ); // Don't save MP stuff + +// savefile->WriteInt( arena ); // Don't save MP stuff + +// savefile->WriteInt( connectTime ); // Don't save MP stuff +// savefile->WriteInt( mutedPlayers ); // Don't save MP stuff +// savefile->WriteInt( friendPlayers ); // Don't save MP stuff + +// savefile->WriteInt( rank ); // Don't save MP stuff + + savefile->WriteInt( lastImpulseTime ); + bossEnemy.Save( savefile ); // cnicholson: Added unsaved var + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->WriteBool( weaponChangeIconsUp ); // cnicholson: Added unsaved var + + // mekberg: added + savefile->WriteBool( showNewObjectives ); + savefile->WriteBool( objectivesEnabled ); + + savefile->WriteBool( flagCanFire ); + + // TOSAVE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TOSAVE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + +#ifndef _XENON + if ( hud ) { + hud->SetStateString( "message", common->GetLocalizedString( "#str_102916" ) ); + hud->HandleNamedEvent( "Message" ); + } +#endif +} + +/* +=========== +idPlayer::Restore +=========== +*/ +void idPlayer::Restore( idRestoreGame *savefile ) { + assert( !IsFakeClient() ); + + int i; + int num; + + savefile->ReadUsercmd( usercmd ); + + playerView.Restore( savefile ); + + savefile->ReadBool( noclip ); + savefile->ReadBool( godmode ); + savefile->ReadInt ( godmodeDamage ); + savefile->ReadBool( undying ); + + savefile->ReadAngles( spawnAngles ); + savefile->ReadAngles( viewAngles ); + savefile->ReadAngles( cmdAngles ); + + memset( usercmd.angles, 0, sizeof( usercmd.angles ) ); + SetViewAngles( viewAngles ); + spawnAnglesSet = true; + + savefile->ReadInt( buttonMask ); + savefile->ReadInt( oldButtons ); + savefile->ReadInt( oldFlags ); + + usercmd.flags = 0; + oldFlags = 0; + + int foo; + savefile->ReadInt( foo ); + savefile->ReadInt( foo ); + savefile->ReadInt( lastSavingThrowTime ); + + savefile->Read( &pfl, sizeof( pfl ) ); + + inventory.Restore( savefile ); + + assert( !weapon ); + + weaponViewModel.Restore( savefile ); + weaponWorldModel.Restore( savefile ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + assert( !mphud ); // Don't save MP stuff + savefile->ReadUserInterface( objectiveSystem, &spawnArgs ); + savefile->ReadUserInterface( cinematicHud, &spawnArgs ); + savefile->ReadBool( objectiveSystemOpen ); + +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + + objectiveButtonReleased = false; + savefile->ReadBool( disableHud ); // cnicholson: Added unrestored var + + savefile->ReadInt( lastDmgTime ); + savefile->ReadInt( deathClearContentsTime ); + savefile->ReadBool( doingDeathSkin ); + savefile->ReadInt( nextHealthPulse ); + savefile->ReadInt( nextArmorPulse ); + savefile->ReadBool( hiddenWeapon ); + + assert( !spectator ); // Don't save MP stuff + + assert( !scoreBoardOpen ); // Don't save MP stuff + assert( !forceScoreBoard ); // Don't save MP stuff + assert( !forceRespawn ); // Don't save MP stuff + + assert( !spectating ); // Don't save MP stuff + assert( !forcedReady ); // Don't save MP stuff + assert( !wantSpectate ); // Don't save MP stuff + + assert( !weaponGone ); // Don't save MP stuff + assert( !useInitialSpawns ); // Don't save MP stuff + assert( !isLagged ); // Don't save MP stuff + assert( !isChatting ); // Don't save MP stuff + + assert( !lastSpectateTeleport ); // Don't save MP stuff + assert( latchedTeam == -1 ); // Don't save MP stuff + assert( !spawnedTime ); // Don't save MP stuff + + assert( !teleportEntity ); // Don't save MP stuff + assert( teleportKiller == -1 ); // Don't save MP stuff + + savefile->ReadInt( minRespawnTime ); + savefile->ReadInt( maxRespawnTime ); + + savefile->ReadVec3( firstPersonViewOrigin ); + savefile->ReadMat3( firstPersonViewAxis ); + + // don't bother restoring dragEntity since it's a dev tool + dragEntity.Clear(); + savefile->ReadVec3( intentDir ); + + savefile->ReadFloat ( vehicleCameraDist ); + + savefile->ReadJoint( hipJoint ); + savefile->ReadJoint( chestJoint ); + savefile->ReadJoint( headJoint ); + + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadInt( num ); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( aasLocation[ i ].areaNum ); + savefile->ReadVec3( aasLocation[ i ].pos ); + } + + savefile->ReadString( modelName ); // cnicholson: Added unrestored var + // TORESTORE: const idDict* modelDict + + savefile->ReadInt( bobFoot ); + savefile->ReadFloat( bobFrac ); + savefile->ReadFloat( bobfracsin ); + savefile->ReadInt( bobCycle ); + savefile->ReadFloat( xyspeed ); + savefile->ReadInt( stepUpTime ); + savefile->ReadFloat( stepUpDelta ); + savefile->ReadFloat( idealLegsYaw ); + savefile->ReadFloat( legsYaw ); + savefile->ReadBool( legsForward ); + savefile->ReadFloat( oldViewYaw ); + savefile->ReadAngles( viewBobAngles ); + savefile->ReadVec3( viewBob ); + savefile->ReadInt( landChange ); + savefile->ReadInt( landTime ); + + savefile->ReadFloat( fatalFallDelta ); + savefile->ReadFloat( hardFallDelta ); + savefile->ReadFloat( softFallDelta ); + savefile->ReadFloat( noFallDelta ); + + savefile->ReadInt( currentWeapon ); + savefile->ReadInt( idealWeapon ); + savefile->ReadInt( previousWeapon ); + savefile->ReadInt( weaponSwitchTime ); + savefile->ReadBool( weaponEnabled ); + + savefile->ReadBool ( flashlightOn ); + savefile->ReadBool ( zoomed ); + + savefile->ReadBool ( reloadModel ); + + savefile->ReadSkin( skin ); + savefile->ReadSkin( powerUpSkin ); + savefile->ReadSkin( gibSkin ); + + savefile->ReadInt( numProjectilesFired ); + savefile->ReadInt( numProjectileHits ); + + savefile->ReadBool( airless ); + savefile->ReadInt( airTics ); + savefile->ReadInt( lastAirDamage ); + + savefile->ReadBool( gibDeath ); + savefile->ReadBool( gibsLaunched ); + savefile->ReadVec3( gibDir ); + + savefile->ReadBool( isStrogg ); + + savefile->ReadInterpolate( zoomFov ); + savefile->ReadInterpolate( centerView ); + savefile->ReadBool( fxFov ); + + savefile->ReadFloat( influenceFov ); + savefile->ReadInt( influenceActive ); + savefile->ReadObject( reinterpret_cast( influenceEntity ) ); + savefile->ReadMaterial( influenceMaterial ); + savefile->ReadFloat( influenceRadius ); + savefile->ReadSkin( influenceSkin ); + + savefile->ReadObject( reinterpret_cast( privateCameraView ) ); + + for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) { + savefile->ReadAngles( loggedViewAngles[ i ] ); + } + for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) { + savefile->ReadInt( loggedAccel[ i ].time ); + savefile->ReadVec3( loggedAccel[ i ].dir ); + } + savefile->ReadInt( currentLoggedAccel ); + + savefile->ReadUserInterface( focusUI, &spawnArgs ), + savefile->ReadInt( focusTime ); + savefile->ReadInt( (int&)focusType ); + focusEnt.Restore ( savefile ); + savefile->ReadUserInterface( focusBrackets, &spawnArgs ); + savefile->ReadInt( focusBracketsTime ); + + talkingNPC.Restore( savefile ); + + extraProjPassEntity.Restore( savefile ); + + savefile->ReadInt( talkCursor ); + savefile->ReadUserInterface( cursor, &spawnArgs ); + + savefile->ReadUserInterface( overlayHud, &spawnArgs ); // cnicholson: Added unrestored var + savefile->ReadInt ( overlayHudTime ); // cnicholson: Added unrestored var + + savefile->ReadBool( targetFriendly ); + + savefile->ReadInt( oldMouseX ); + savefile->ReadInt( oldMouseY ); + + savefile->ReadBool( tipUp ); + savefile->ReadBool( objectiveUp ); + + savefile->ReadFloat( dynamicProtectionScale ); // cnicholson: Added unrestored var + savefile->ReadInt( lastDamageDef ); + savefile->ReadVec3( lastDamageDir ); + savefile->ReadInt( lastDamageLocation ); + + savefile->ReadInt( predictedFrame ); + savefile->ReadVec3( predictedOrigin ); + savefile->ReadAngles( predictedAngles ); + savefile->ReadBool( predictedUpdated ); + savefile->ReadVec3( predictionOriginError ); + savefile->ReadAngles( predictionAnglesError ); + savefile->ReadInt( predictionErrorTime ); + + assert( !ready ); // Don't save MP stuff + assert( !respawning ); // Don't save MP stuff + assert( !leader ); // Don't save MP stuff + assert( !weaponCatchup ); // Don't save MP stuff + assert( !isTelefragged ); // Don't save MP stuff + + assert( !lastSpectateChange ); // Don't save MP stuff + assert( lastTeleFX == -9999 ); // Don't save MP stuff + assert( !lastSnapshotSequence ); // Don't save MP stuff + + assert( aimClientNum == -1 ); // Don't save MP stuff + + assert( !lastImpulsePlayer ); // Don't save MP stuff + + assert( !arena ); // Don't save MP stuff + + assert( !connectTime ); // Don't save MP stuff + assert( !mutedPlayers ); // Don't save MP stuff + assert( !friendPlayers ); // Don't save MP stuff + + assert( rank == -1 ); // Don't save MP stuff + + savefile->ReadInt( lastImpulseTime ); + bossEnemy.Restore( savefile ); // cnicholson: Added unrestored var + + // TORESTORE: const idDeclEntityDef* cachedWeaponDefs [ MAX_WEAPONS ]; // cnicholson: Save these? + // TORESTORE: const idDeclEntityDef* cachedPowerupDefs [ POWERUP_MAX ]; + + savefile->ReadBool( weaponChangeIconsUp ); // cnicholson: Added unrestored var + +// mekberg: added + savefile->ReadBool( showNewObjectives ); + savefile->ReadBool( objectivesEnabled ); + + savefile->ReadBool( flagCanFire ); + + // set the pm_ cvars + const idKeyValue *kv; + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + + // Loading a game on easy mode ensures you alwasy have 20% health when you load + i = spawnArgs.GetInt ( va("minRestoreHealth%d", g_skill.GetInteger ( ) ) ); + if ( health < i ) { + health = i; + } + + //if there's hearing loss, make sure we post a finishing event + if( pfl.hearingLoss ) { + Event_FinishHearingLoss( 3.0f ); + } else { + Event_FinishHearingLoss( 0.05f ); + } + // create combat collision hull for exact collision detection + SetCombatModel(); + +// RAVEN BEGIN +// mekberg: Grab from user info. + showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" ); + + // precache decls + declManager->FindType( DECL_ENTITYDEF, "damage_fatalfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_hardfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_softfall", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_noair", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_suicide", false, false ); + declManager->FindType( DECL_ENTITYDEF, "damage_telefrag", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock", false, false ); + declManager->FindType( DECL_ENTITYDEF, "dmg_shellshock_nohl", false, false ); +// RAVEN END +} + +/* +=============== +idPlayer::PrepareForRestart +================ +*/ +void idPlayer::PrepareForRestart( void ) { + ClearPowerUps(); + Spectate( true ); + + forceRespawn = true; +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + allowedToRespawn = true; +// RITUAL END + + // we will be restarting program, clear the client entities from program-related things first + ShutdownThreads(); + + // the sound world is going to be cleared, don't keep references to emitters + FreeSoundEmitter( false ); +} + +/* +=============== +idPlayer::Restart +================ +*/ +void idPlayer::Restart( void ) { + idActor::Restart(); + + // client needs to setup the animation script object again + if ( gameLocal.isClient ) { + // clear the existing model to force a reload + Init(); + if ( !spectating ) { + Show(); + } + } else { + // choose a random spot and prepare the point of view in case player is left spectating + assert( spectating ); + SpawnFromSpawnSpot(); + } + + lastKiller = NULL; + useInitialSpawns = true; +} + +/* +=============== +idPlayer::ServerSpectate +================ +*/ +void idPlayer::ServerSpectate( bool spectate ) { + assert( !gameLocal.isClient ); + + if ( spectating != spectate ) { + // if we select spectating on the client + // mekberg: drop and clear powerups from the player. + DropPowerups(); + ClearPowerUps(); + Spectate( spectate ); + gameLocal.mpGame.GetGameState()->Spectate( this ); + if ( spectate ) { + SetSpectateOrigin( ); + } + } + if ( !spectate ) { + SpawnFromSpawnSpot( ); + } +} + +/* +=========== +idPlayer::SelectSpawnPoint + +Find a spawn point marked, otherwise use normal spawn selection. +============ +*/ +bool idPlayer::SelectSpawnPoint( idVec3 &origin, idAngles &angles ) { + idEntity *spot; + idStr skin; + + spot = gameLocal.SelectSpawnPoint( this ); + + // no spot, try again next frame + if( !spot ) { + forceRespawn = true; + return false; + } + + // set the player skin from the spawn location + if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) { + spawnArgs.Set( "spawn_skin", skin ); + } + + if ( spot->spawnArgs.GetString( "spawn_model", NULL ) ) { + spawnArgs.Set( "model", spot->spawnArgs.GetString( "spawn_model", NULL ) ); + } + + if ( spot->spawnArgs.GetString( "def_head", NULL ) ) { + spawnArgs.Set( "def_head", spot->spawnArgs.GetString( "def_head", NULL ) ); + } + + // activate the spawn locations targets + spot->PostEventMS( &EV_ActivateTargets, 0, this ); + + origin = spot->GetPhysics()->GetOrigin(); + origin[2] += 4.0f + CM_BOX_EPSILON; // move up to make sure the player is at least an epsilon above the floor + angles = spot->GetPhysics()->GetAxis().ToAngles(); + + return true; +} + +/* +=========== +idPlayer::SpawnFromSpawnSpot + +Chooses a spawn location and spawns the player +============ +*/ +void idPlayer::SpawnFromSpawnSpot( void ) { + idVec3 spawn_origin; + idAngles spawn_angles; + + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + forceRespawn = true; + return; + } + SpawnToPoint( spawn_origin, spawn_angles ); +} + +/* +=========== +idPlayer::SpawnToPoint + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState + +when called here with spectating set to true, just place yourself and init +============ +*/ +void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) { + idVec3 spec_origin; + + assert( !gameLocal.isClient || IsFakeClient() ); + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // Record previous weapons for later restoration + inventory.carryOverWeapons &= ~CARRYOVER_WEAPONS_MASK; + inventory.carryOverWeapons |= inventory.weapons; + } +// RITUAL END + + respawning = true; + + Init(); + + // Force players to use bounding boxes when in multiplayer + if ( gameLocal.isMultiplayer ) { + use_combat_bbox = true; + + // Make sure the combat model is unlinked + if ( combatModel ) { + combatModel->Unlink( ); + } + } + + // Any health over max health will tick down + if ( health > inventory.maxHealth ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } + + fl.noknockback = false; + // stop any ragdolls being used + StopRagdoll(); + // set back the player physics + SetPhysics( &physicsObj ); + physicsObj.SetClipModelAxis(); + physicsObj.EnableClip(); + if ( !spectating ) { + SetCombatContents( true ); + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + // setup our initial view + if ( !spectating ) { + SetOrigin( spawn_origin ); +// RAVEN BEGIN +// abahr: taking into account gravity + SetAxis( spawn_angles.ToMat3() ); +// RAVEN END + } else { + spec_origin = spawn_origin; + spec_origin[ 2 ] += pm_normalheight.GetFloat(); + spec_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spec_origin ); + } + + // if this is the first spawn of the map, we don't have a usercmd yet, + // so the delta angles won't be correct. This will be fixed on the first think. + viewAngles = ang_zero; + SetDeltaViewAngles( ang_zero ); + SetViewAngles( spawn_angles ); + spawnAngles = spawn_angles; + spawnAnglesSet = false; + + legsForward = true; + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + leanVelocity = vec3_zero; + + if ( spectating ) { + Hide(); + } else { + Show(); + } + + if ( gameLocal.isMultiplayer ) { + if ( !spectating ) { + // we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof' + if ( lastTeleFX < gameLocal.time - 1000 ) { + // currentThinkingEntity not set here (called out of Run()) + idEntity* thinker = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + gameLocal.PlayEffect( spawnArgs, "fx_spawn", renderEntity.origin, idVec3(0,0,1).ToMat3(), false, vec3_origin, true ); + lastTeleFX = gameLocal.time; + gameLocal.currentThinkingEntity = thinker; + } + } +//RAVEN BEGIN +//asalmon: Clear the respwan message + if ( mphud ) { + mphud->SetStateInt( "respawnNotice", 0 ); + } +//RAVEN END + pfl.teleport = true; + } else { + pfl.teleport = false; + } + + // kill anything at the new position + if ( !spectating ) { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it +// RAVEN BEGIN +// abahr: this is killing the tram car when spawning in. Ooooops! + if( gameLocal.isMultiplayer ) { + gameLocal.KillBox( this ); + } +// RAVEN END + } + + // don't allow full run speed for a bit + physicsObj.SetKnockBack( 100 ); + + // set our respawn time and buttons so that if we're killed we don't respawn immediately + minRespawnTime = gameLocal.time; + maxRespawnTime = gameLocal.time; + if ( !spectating ) { + forceRespawn = false; + } + + privateCameraView = NULL; + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + if( gameLocal.isMultiplayer && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + // restore previous weapons + inventory.weapons |= inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + for( int weaponIndex = 0; weaponIndex < MAX_WEAPONS; weaponIndex++ ) + { + if( inventory.weapons & ( 1 << weaponIndex ) ) + { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( weaponIndex ); + inventory.ammo[ ammoIndex ] = inventory.StartingAmmoForWeaponIndex( weaponIndex ); + } + } + + /// Restore armor purchased while dead + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_LIGHT; + GiveItem( "item_armor_small" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_ARMOR_HEAVY; + GiveItem( "item_armor_large" ); + } + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + { + inventory.carryOverWeapons &= ~CARRYOVER_FLAG_AMMO; + GiveItem( "ammorefill" ); + } + + // Reactivate team powerups + gameLocal.mpGame.SetUpdateForTeamPowerups(team); + UpdateTeamPowerups(); + } +// RITUAL END + + BecomeActive( TH_THINK ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + Think(); + + respawning = false; + isTelefragged = false; + isLagged = false; + isChatting = false; + + lastImpulsePlayer = NULL; + lastImpulseTime = 0; +} + +/* +=============== +idPlayer::SavePersistantInfo + +Saves any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::SavePersistantInfo( void ) { + if ( IsFakeClient() ) { + return; + } + + idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber]; + + playerInfo.Clear(); + inventory.GetPersistantData( playerInfo ); + playerInfo.SetInt( "health", health ); + playerInfo.SetInt( "current_weapon", currentWeapon ); +} + +/* +=============== +idPlayer::RestorePersistantInfo + +Restores any inventory and player stats when changing levels. +=============== +*/ +void idPlayer::RestorePersistantInfo( void ) { + if ( IsFakeClient() ) { + return; + } + + if ( gameLocal.isMultiplayer ) { + gameLocal.persistentPlayerInfo[entityNumber].Clear(); + } + + spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] ); + + inventory.RestoreInventory( this, spawnArgs ); + health = spawnArgs.GetInt( "health", "100" ); + if ( !gameLocal.isClient ) { + idealWeapon = spawnArgs.GetInt( "current_weapon", "0" ); + } +} + +/* +================ +idPlayer::GetUserInfo +================ +*/ +idDict *idPlayer::GetUserInfo( void ) { + if ( IsFakeClient() ) { + return NULL; + } + return &gameLocal.userInfo[ entityNumber ]; +} + +/* +============== +idPlayer::UpdateModelSetup +Updates the player's model setup. Model setups are read from the player def, and contain +information about the player's model, the player's head model, a skin, and a team for the +composite model. +============== +*/ +void idPlayer::UpdateModelSetup( bool forceReload ) { + const idDict* dict; + const char* uiKeyName = NULL; + const char* defaultModel = NULL; + const char* newModelName = NULL; + + if( !gameLocal.isMultiplayer || spectating ) { + return; + } + + if( gameLocal.IsTeamGame() ) { + defaultModel = spawnArgs.GetString( va( "def_default_model_%s", idMultiplayerGame::teamNames[ team ] ), NULL ); + + if( g_forceMarineModel.GetString()[ 0 ] && team == TEAM_MARINE ) { + newModelName = g_forceMarineModel.GetString(); + } else if( g_forceStroggModel.GetString()[ 0 ] && team == TEAM_STROGG ) { + newModelName = g_forceStroggModel.GetString(); + } else { + uiKeyName = va( "ui_model_%s", idMultiplayerGame::teamNames[ team ] ); + newModelName = GetUserInfo() ? GetUserInfo()->GetString( uiKeyName ) : ""; + } + } else { + defaultModel = spawnArgs.GetString( "def_default_model" ); + + if( g_forceModel.GetString()[ 0 ] ) { + newModelName = g_forceModel.GetString(); + } else { + uiKeyName = "ui_model"; + newModelName = GetUserInfo() ? GetUserInfo()->GetString( uiKeyName ) : ""; + } + } + + if( !idStr::Icmp( newModelName, "" ) ) { + newModelName = defaultModel; + } + + // model hasn't changed + if( !modelName.Icmp( newModelName ) && !forceReload ) { + return; + } + + rvDeclPlayerModel* model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + + // validate that the model they've selected is OK for this team game + if( gameLocal.IsTeamGame() && model ) { + if( idStr::Icmp( model->team, idMultiplayerGame::teamNames[ team ] ) ) { + gameLocal.Warning( "idPlayer::UpdateModelSetup() - Player %d (%s) set to model %s which is restricted to team %s (Player team: %s)\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?", newModelName, model->team.c_str(), idMultiplayerGame::teamNames[ team ] ); + if( uiKeyName ) { + cvarSystem->SetCVarBool( uiKeyName, "" ); + } + + dict = NULL; + } + } + + // check to see if the user-specified ui_model/ui_model_strogg/ui_model_marine is valid + if( !model ) { + newModelName = defaultModel; + + model = (rvDeclPlayerModel*)declManager->FindType( DECL_PLAYER_MODEL, newModelName, false ); + if( !model ) { + gameLocal.Error( "idPlayer::UpdateModelSetup() - Can't find default model (%s)\n", defaultModel ); + } else { + // If it's not valid, set the cvar to the default model + if( uiKeyName && GetUserInfo() ) { + GetUserInfo()->Set( uiKeyName, defaultModel ); + + if( IsLocalClient() ) { + cvarSystem->SetCVarString( uiKeyName, defaultModel ); + } + + if( gameLocal.isServer ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, va( "updateUI %d\n", entityNumber ) ); + return; + } + } + + } + } + + modelName = newModelName; + modelDecl = model; + + reloadModel = true; + + // if we have a strogg model, set the strogg flag here + isStrogg = false; + if( modelDecl && !modelDecl->team.Icmp( "strogg" ) ) { + isStrogg = true; + } + + if (gameLocal.isClient || gameLocal.isListenServer) + UpdateSounds(modelDecl); +} + +void idPlayer::UpdateSounds(const rvDeclPlayerModel *modelDecl) +{ + // reset to the player.def sounds just in case we're switching from a model + // which defines sounds back to one that doesnt + const idDict* playerDict = gameLocal.FindEntityDefDict( "player_marine", false ); + const idKeyValue *kv = playerDict->MatchPrefix( "snd_", NULL ); + while (kv) + { + spawnArgs.Set(kv->GetKey().c_str(), kv->GetValue().c_str()); + kv = playerDict->MatchPrefix( "snd_", kv ); + } + + spawnArgs.Copy(modelDecl->sounds); +} + +/* +============== +idPlayer::BalanceTeam +============== +*/ +bool idPlayer::BalanceTeam( void ) { + assert( !IsFakeClient() ); + + int i, balanceTeam, teamCount[2]; + idEntity *ent; + + teamCount[ 0 ] = teamCount[ 1 ] = 0; + for( i = 0; i < gameLocal.numClients; i++ ) { + ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::Type ) && gameLocal.mpGame.IsInGame( i ) ) { + if ( !static_cast< idPlayer * >( ent )->spectating ) { + teamCount[ static_cast< idPlayer * >( ent )->team ]++; + } + } + } + + balanceTeam = -1; + if ( teamCount[ 0 ] < teamCount[ 1 ] ) { + balanceTeam = 0; + } else if ( teamCount[ 0 ] > teamCount[ 1 ] ) { + balanceTeam = 1; + } + + if ( balanceTeam != -1 && team != balanceTeam && teamCount[ balanceTeam ]+1 != teamCount[ !balanceTeam ] ) { + common->DPrintf( "team balance: forcing player %d to %s team\n", entityNumber, balanceTeam ? "strogg" : "marine" ); + team = balanceTeam; + GetUserInfo()->Set( "ui_team", team ? "Strogg" : "Marine" ); + return true; + } + return false; +} + +void HSVtoRGB( float &r, float &g, float &b, float h, float s, float v ) { + int i; + float f, p, q, t; + + h = idMath::ClampFloat( 0.0f, 360.0f, h ); + s = idMath::ClampFloat( 0.0f, 1.0f, s ); + v = idMath::ClampFloat( 0.75f, 1.0f, v ); + + if( s == 0 ) { + // achromatic (grey) + r = g = b = v; + return; + } + + h /= 60; // sector 0 to 5 + i = floor( h ); + f = h - i; // factorial part of h + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch( i ) { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: // case 5: + r = v; + g = p; + b = q; + break; + } +} + +/* +============== +idPlayer::UserInfoChanged +============== +*/ +bool idPlayer::UserInfoChanged( void ) { + idDict *userInfo; + bool modifiedInfo; + bool spec; + bool newready; + + if ( IsFakeClient() ) { + showWeaponViewModel = cvarSystem->GetCVarBool( "ui_showGun" ); + wantSpectate = true; + spectator = this->entityNumber; + return false; + } + + userInfo = GetUserInfo(); + showWeaponViewModel = userInfo->GetBool( "ui_showGun" ); + + if ( !gameLocal.isMultiplayer ) { + handicap = 1.0f; + return false; + } + + modifiedInfo = false; + + // update/apply handicap + handicap = userInfo->GetInt( "ui_handicap", "100" ) / 100.0f; + if ( handicap <= 0.0f || handicap >= 1.0f ) { + handicap = 1.0f; + } + + if( PowerUpActive( POWERUP_GUARD ) ) { + inventory.maxHealth = 200; + inventory.maxarmor = 200; + } else { + inventory.maxHealth = spawnArgs.GetInt( "maxhealth", "100" ); + inventory.maxarmor = spawnArgs.GetInt( "maxarmor", "100" ); + } + + spec = ( idStr::Icmp( userInfo->GetString( "ui_spectate" ), "Spectate" ) == 0 ); + if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) { + // never let spectators go back to game while sudden death is on + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == SUDDENDEATH && !spec && wantSpectate == true ) { + userInfo->Set( "ui_spectate", "Spectate" ); + modifiedInfo |= true; + } else { + if ( spec != wantSpectate && !spec ) { + // returning from spectate, set forceRespawn so we don't get stuck in spectate forever + forceRespawn = true; + } + wantSpectate = spec; + } + } else { + if ( spec ) { + userInfo->Set( "ui_spectate", "Play" ); + modifiedInfo |= true; + } else if ( spectating ) { + // allow player to leaving spectator mode if they were in it when it was disallowed + forceRespawn = true; + } + wantSpectate = false; + } + + if ( gameLocal.serverInfo.GetBool( "si_useReady" ) ) { + newready = ( idStr::Icmp( userInfo->GetString( "ui_ready" ), "Ready" ) == 0 ); + if ( ready != newready && gameLocal.mpGame.GetGameState()->GetMPGameState() == WARMUP && !wantSpectate ) { + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( "#str_107180" ), userInfo->GetString( "ui_name" ), newready ? common->GetLocalizedString( "#str_104300" ) : common->GetLocalizedString( "#str_104301" ) ); + } + ready = newready; + } + + int newTeam = ( idStr::Icmp( userInfo->GetString( "ui_team" ), "Strogg" ) == 0 ); + + if( hud && gameLocal.IsTeamGame() ) { + hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + } else if( hud ) { + hud->HandleNamedEvent( "setTeam_marine" ); + } + + if ( gameLocal.IsTeamGame() && newTeam != latchedTeam ) { + team = newTeam; + + if ( gameLocal.isServer ) { + int verifyTeam = gameLocal.mpGame.VerifyTeamSwitch( newTeam, this ); + if( verifyTeam != newTeam ) { + if( verifyTeam == TEAM_MARINE || verifyTeam == TEAM_STROGG ) { + userInfo->Set( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + if( IsLocalClient() ) { + cvarSystem->SetCVarString( "ui_team", gameLocal.mpGame.teamNames[ verifyTeam ] ); + } + modifiedInfo = true; + team = verifyTeam; + } + } + } + + // if still OK to change + if( team != latchedTeam ) { + if( gameLocal.isServer ) { + gameLocal.mpGame.SwitchToTeam( entityNumber, latchedTeam, team ); + } + + SetInitialHud(); + if( mphud ) { + mphud->SetStateInt( "playerteam", team ); + mphud->HandleNamedEvent( "TeamChange" ); + } + + if( IsLocalClient() ) { + alreadyDidTeamAnnouncerSound = true; + // the client might set its team to a value before the server corrects for team balance + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_MARINE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_TEAM_JOIN_STROGG ); + + if ( !wantSpectate ) { + if( team == TEAM_STROGG ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time + 500 ); + } else if( team == TEAM_MARINE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time + 500 ); + } + } + } + + // ATVI DevTrack #13224 - update on each team change + iconManager->UpdateTeamIcons(); + + latchedTeam = team; + } + } + + //if ( !gameLocal.isClient && gameLocal.serverInfo.GetBool( "si_autoBalance" ) && gameLocal.IsTeamGame() ) { + // bool teamsBalanced = BalanceTeam(); + // modifiedInfo |= teamsBalanced; + //} + + UpdateModelSetup(); + + if( (gameLocal.isServer && gameLocal.mpGame.IsInGame( entityNumber )) || (gameLocal.isClient && IsLocalClient() ) ) { + isChatting = userInfo->GetBool( "ui_chat", "0" ); + if ( isChatting && pfl.dead ) { + // if dead, always force chat icon off. + isChatting = false; + userInfo->SetBool( "ui_chat", false ); + modifiedInfo |= true; + } + } + + // grab hitscan tint + hitscanTint = userInfo->GetVec4( "ui_hitscanTint", "81 1 1 1" ); + HSVtoRGB( hitscanTint.x, hitscanTint.y, hitscanTint.z, hitscanTint.x, hitscanTint.y, hitscanTint.z ); + // force alpha to 1 + hitscanTint.w = 1.0f; + + return modifiedInfo; +} + +/* +=============== +idPlayer::UpdateHudAmmo +=============== +*/ +void idPlayer::UpdateHudAmmo( idUserInterface *_hud ) { + int inclip; + int ammoamount; + + assert( weapon ); + assert( _hud ); + + inclip = weapon->AmmoInClip(); + ammoamount = weapon->AmmoAvailable(); + + if ( ammoamount < 0 ) { + // show infinite ammo + _hud->SetStateString( "player_ammo", "-1" ); + _hud->SetStateString( "player_totalammo", "-1" ); + _hud->SetStateFloat ( "player_ammopct", 1.0f ); + } else if ( weapon->ClipSize ( ) && !gameLocal.isMultiplayer ) { + _hud->SetStateInt ( "player_clip_size", weapon->ClipSize() ); + _hud->SetStateFloat ( "player_ammopct", (float)inclip / (float)weapon->ClipSize ( ) ); + if ( weapon->ClipSize ( )==1) { + _hud->SetStateInt ( "player_totalammo", ammoamount ); + } + else { + _hud->SetStateInt ( "player_totalammo", ammoamount - inclip ); + } + _hud->SetStateInt ( "player_ammo", inclip ); + } else { + _hud->SetStateFloat ( "player_ammopct", (float)ammoamount / (float)weapon->maxAmmo ); + _hud->SetStateInt ( "player_totalammo", ammoamount ); + _hud->SetStateInt ( "player_ammo", -1 ); + } + + _hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) ); +} + +/* +=============== +idPlayer::UpdateHudStats +=============== +*/ +void idPlayer::UpdateHudStats( idUserInterface *_hud ) { + int temp; + + assert ( _hud ); + + temp = _hud->State().GetInt ( "player_health", "-1" ); + if ( temp != health ) { + _hud->SetStateInt ( "player_healthDelta", temp == -1 ? 0 : (temp - health) ); + _hud->SetStateInt ( "player_health", health < -100 ? -100 : health ); + _hud->SetStateFloat ( "player_healthpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)health / (float)inventory.maxHealth ) ); + _hud->HandleNamedEvent ( "updateHealth" ); + } + + temp = _hud->State().GetInt ( "player_armor", "-1" ); + if ( temp != inventory.armor ) { + _hud->SetStateInt ( "player_armorDelta", temp == -1 ? 0 : (temp - inventory.armor) ); + _hud->SetStateInt ( "player_armor", inventory.armor ); + _hud->SetStateFloat ( "player_armorpct", idMath::ClampFloat ( 0.0f, 1.0f, (float)inventory.armor / (float)inventory.maxarmor ) ); + _hud->HandleNamedEvent ( "updateArmor" ); + } + + // Boss bar + if ( _hud->State().GetInt ( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt ( "boss_health", -1 ); + _hud->HandleNamedEvent ( "hideBossBar" ); + _hud->HandleNamedEvent ( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt ( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent ( "updateBossBar" ); + } + } + + // god mode information + _hud->SetStateString( "player_god", va( "%i", (godmode && g_showGodDamage.GetBool()) ) ); + _hud->SetStateString( "player_god_damage", va( "%i", godmodeDamage ) ); + + // Update the hit direction + idVec3 localDir; + viewAxis.ProjectVector( lastDamageDir, localDir ); + _hud->SetStateFloat( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + + //_hud->HandleNamedEvent( "updateArmorHealthAir" ); + + if ( weapon ) { + UpdateHudAmmo( _hud ); + } + + UpdateHudPowerUps( _hud ); + + if ( hud_showSpeed.GetBool() ) { + idVec3 velocity = physicsObj.GetLinearVelocity(); + velocity[2] = 0; + _hud->SetStateString("player_speed", va("%d ups", (int)velocity.Length())); + } + + if (hud_showInput.GetBool()) + DrawInput(); + + _hud->StateChanged( gameLocal.time ); +} + +void idPlayer::DrawInput() +{ + const idVec4 highlightColor(1.0f, 0.0f, 0.0f, 0.8f); + sscanf( hud_inputColor.GetString(), "%f %f %f", &highlightColor.x, &highlightColor.y, &highlightColor.z ); + + const idVec4 normalColor(1.0f, 1.0f, 1.0f, 0.8f); + + idVec2 inputPos(500,90); + sscanf( hud_inputPosition.GetString(), "%f %f", &inputPos.x, &inputPos.y ); + + const idMaterial *centerMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_center"); + renderSystem->SetColor( normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 10, inputPos.y + 10, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, centerMaterial ); + + const idMaterial *forwardMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_forward"); + renderSystem->SetColor( (usercmd.forwardmove == 127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 10, inputPos.y, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, forwardMaterial ); + + const idMaterial *backMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_back"); + renderSystem->SetColor( (usercmd.forwardmove == -127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 10, inputPos.y + 20, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, backMaterial ); + + const idMaterial *leftMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_moveleft"); + renderSystem->SetColor( (usercmd.rightmove == -127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x, inputPos.y + 10, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, leftMaterial ); + + const idMaterial *rightMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_moveright"); + renderSystem->SetColor( (usercmd.rightmove == 127) ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 20, inputPos.y + 10, 10.0f, 10.0f, 0.0, 0.0, 1.0f, 1.0f, rightMaterial ); + + const idMaterial *upMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_moveup"); + renderSystem->SetColor( physicsObj.IsJumping() ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 36, inputPos.y, 14.0f, 14.0f, 0.0, 0.0, 1.0f, 1.0f, upMaterial ); + + const idMaterial *downMaterial = declManager->FindMaterial("gfx/guis/showinput/hud_showinput_movedown"); + renderSystem->SetColor( physicsObj.IsCrouching() ? highlightColor : normalColor ); + renderSystem->DrawStretchPic(inputPos.x + 36, inputPos.y + 17, 14.0f, 14.0f, 0.0, 0.0, 1.0f, 1.0f, downMaterial ); +} + +/* +=============== +idPlayer::UpdateHudWeapon +=============== +*/ +void idPlayer::UpdateHudWeapon( int displayWeapon ) { + + if ( ( displayWeapon < -1 ) || ( displayWeapon >= MAX_WEAPONS ) ) { + common->DPrintf( "displayweapon was out of range" ); + return; + } + + int index = 0; + int idealIndex = 0; + idUserInterface * hud = idPlayer::hud; + idUserInterface * mphud = idPlayer::mphud; + idUserInterface * cursor = idPlayer::cursor; + + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && p->spectating && p->spectator == entityNumber ) { + hud = p->hud; + mphud = p->mphud; + cursor = p->cursor; + } + + if ( !hud || !weapon ) { + return; + } + + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + const char *weapnum = va( "weapon%d", i ); + int weapstate = 0; + if ( ( inventory.weapons & ( 1 << i ) ) && spawnArgs.GetBool( va( "weapon%d_cycle", i ) ) ) { + hud->SetStateBool( weapnum, true ); + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + weapstate++; + + if ( idealWeapon == i ) { + idealIndex = index; + } + + const char *weaponIcon = GetWeaponDef ( i )->dict.GetString ( "inv_icon" ); + + hud->SetStateInt ( va( "weapon%d_index", i ), index++ ); + hud->SetStateString ( va( "weapon%d_icon", i ), weaponIcon ); + hud->SetStateInt ( va( "weapon%d_ammo", i ), inventory.ammo[ inventory.AmmoIndexForWeaponClass( weap ) ] ); + } + } else { + hud->SetStateBool( weapnum, false ); + } + } + + hud->SetStateInt( "weaponcount", index ); + + const idMaterial *material = declManager->FindMaterial( weapon->GetIcon() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + material = declManager->FindMaterial( weapon->spawnArgs.GetString( "inv_icon" ) ); + + hud->SetStateString( "weapicon", weapon->GetIcon() ); + hud->SetStateString( "ammoIcon", weapon->spawnArgs.GetString( "inv_icon" ) ); + hud->SetStateInt( "player_weapon", currentWeapon ); + hud->SetStateInt( "player_lastweapon", previousWeapon ); + int hud_idealWeapon = ( displayWeapon != -1 ) ? displayWeapon : idealWeapon; + hud->SetStateInt( "player_idealWeapon", hud_idealWeapon ); + // unused in q4mp hud.gui + //hud->SetStateInt( "player_idealIndex", idealIndex ); + + // Weapon name for weapon selection + const idDeclEntityDef* w = GetWeaponDef( ( displayWeapon != -1 ) ? displayWeapon : idealWeapon ); + if ( w ) { + idStr langToken = w->dict.GetString( "inv_name" ); + hud->SetStateString( "weaponname", common->GetLocalizedString( langToken ) ); + } + + UpdateHudAmmo( hud ); + + if ( cursor ) { + weapon->UpdateCrosshairGUI( cursor ); + + // mekberg: force a redraw so ON_INIT gets called and doesn't stomp all over + // the color values we set in weaponChange. + cursor->Redraw( gameLocal.time ); + cursor->HandleNamedEvent( "weaponChange" ); + } + + hud->HandleNamedEvent( "weaponChange" ); + hud->StateChanged( gameLocal.time ); + weaponChangeIconsUp = true; +} + +void idPlayer::UpdateHudPowerUps( idUserInterface *_hud ) { + assert( _hud ); + + int i, index; + + _hud->HandleNamedEvent( "clearPowerups" ); + + for ( i = 0, index = 0; i < POWERUP_MAX; i++ ) { + // Do we have this powerup? + if ( !(inventory.powerups & ( 1 << i ) ) ) { + continue; + } + + if ( inventory.powerupEndTime[i] > gameLocal.time || inventory.powerupEndTime[i] == -1 ) { + // If there is still time remaining on the powerup then update the hud + // for flags, set the powerup_flag_* variables, which give us a special pulsing flag display + if( i == POWERUP_CTF_MARINEFLAG || i == POWERUP_CTF_STROGGFLAG || i == POWERUP_CTF_ONEFLAG ) { + _hud->SetStateInt( "powerup_flag_visible", 1 ); + } else { + _hud->SetStateString ( va("powerup%d_icon", index ), GetPowerupDef(i)->dict.GetString ( "inv_icon" ) ); + _hud->SetStateString ( va("powerup%d_time", index ), inventory.powerupEndTime[i] == -1 ? "" : va( "%d" , (int)MS2SEC(inventory.powerupEndTime[i] - gameLocal.time) + 1 ) ); + _hud->SetStateInt ( va( "powerup%d_visible", index ), 1 ); + index++; + } + + continue; + } + } + +} + +/* +=============== +idPlayer::StartRadioChatter +=============== +*/ +void idPlayer::StartRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterUp" ); + } + if ( vehicleController.IsDriving ( ) ) { + vehicleController.StartRadioChatter ( ); + } +} + +/* +=============== +idPlayer::StopRadioChatter +=============== +*/ +void idPlayer::StopRadioChatter ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + if ( vehicleController.IsDriving( ) ) { + vehicleController.StopRadioChatter( ); + } +} + +/* +=============== +idPlayer::DrawShadow +=============== +*/ +void idPlayer::DrawShadow( renderEntity_t *headRenderEnt ) { + if ( gameLocal.isMultiplayer && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all player shadows for the local client + renderEntity.suppressShadowInViewID = gameLocal.localClientNum+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = gameLocal.localClientNum+1; + } + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all player shadows + renderEntity.suppressShadowInViewID = 0; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = 0; + } + } else { + // Only show player shadows for other clients + renderEntity.suppressShadowInViewID = entityNumber+1; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInViewID = entityNumber+1; + } + } +} + +/* +=============== +idPlayer::DrawHUD +=============== +*/ +void idPlayer::DrawHUD( idUserInterface *_hud ) { + idUserInterface * cursor = idPlayer::cursor; + + if ( gameLocal.GetLocalPlayer() ) { + if ( team != gameLocal.GetLocalPlayer()->hudTeam && _hud ) { + _hud->HandleNamedEvent( (team ? "setTeam_strogg" : "setTeam_marine") ); + gameLocal.GetLocalPlayer()->hudTeam = team; + } + } + + // if updating the hud of a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && p != this && p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + } + + if ( disableHud || influenceActive != INFLUENCE_NONE || privateCameraView || !_hud || !g_showHud.GetBool() ) { + return; + } + + if ( objectiveSystemOpen ) { + if ( !GuiActive() ) { + // showing weapon zoom gui when objectives are open because that's the way I'z told to make it werkz + if ( weapon && weapon->GetZoomGui() && zoomed ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } + } + return; + } + + _hud->SetStateBool( "mp", true ); + + // Draw the cinematic hud when in a cinematic + if ( gameLocal.GetCamera() ) { + if ( cinematicHud && !(gameLocal.editors & EDITOR_MODVIEW) ) { + cinematicHud->Redraw( gameLocal.time ); + } + return; + } + + // Let the vehicle draw the hud instead + if ( vehicleController.IsDriving( ) ) { + if ( !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + vehicleController.DrawHUD( ); + if ( cursor && health > 0 ) { + // mekberg: adjustable crosshair size. + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); + cursor->SetStateBool( "vehiclecursor", true ); + + vehicleController.UpdateCursorGUI( cursor ); + cursor->Redraw( gameLocal.time ); + } + } + _hud = GetHud(); + // Boss bar + if ( _hud && _hud->State().GetInt( "boss_health", "-1" ) != (bossEnemy ? bossEnemy->health : -1) ) { + if ( !bossEnemy || bossEnemy->health <= 0 ) { + bossEnemy = NULL; + _hud->SetStateInt( "boss_health", -1 ); + _hud->HandleNamedEvent( "hideBossBar" ); + _hud->HandleNamedEvent( "hideBossShieldBar" ); // grrr, for boss buddy..but maybe other bosses will have shields? + } else { + _hud->SetStateInt( "boss_health", bossEnemy->health ); + _hud->HandleNamedEvent( "updateBossBar" ); + } + } + return; + } + + if ( cursor ) { + cursor->SetStateBool( "vehiclecursor", false ); + } + + // FIXME: this is temp to allow the sound meter to show up in the hud + // it should be commented out before shipping but the code can remain + // for mod developers to enable for the same functionality + _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) ); + + // don't draw main hud in spectator (only mphud) + if ( !spectating && !gameDebug.IsHudActive( DBGHUD_ANY ) ) { + // weapon targeting crosshair + if ( !GuiActive() ) { + if ( weapon && weapon->GetZoomGui() && zoomed ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } + if ( cursor && health > 0 ) { + // Pass the current weapon to the cursor gui for custom crosshairs + int crossSize = cvarSystem->GetCVarInteger( "g_crosshairSize" ); + crossSize = crossSize - crossSize % 8; + cvarSystem->SetCVarInteger( "g_crosshairSize", crossSize ); + cursor->SetStateInt( "g_crosshairSize", crossSize ); + cursor->Redraw( gameLocal.time ); + } + } + + UpdateHudStats( _hud ); + + if ( focusBrackets ) { + // If 2d_calc is still true then the gui didnt render so we can abandon it + if ( focusBrackets->State().GetBool( "2d_calc" ) ) { + focusBrackets->SetStateBool( "2d_calc", false ); + focusBrackets = NULL; + focusBracketsTime = 0; + _hud->HandleNamedEvent( "hideBrackets" ); + } else { + _hud->SetStateString( "bracket_left", focusBrackets->State().GetString( "2d_min_x" ) ); + _hud->SetStateString( "bracket_top", focusBrackets->State().GetString( "2d_min_y" ) ); + _hud->SetStateFloat( "bracket_width", focusBrackets->State().GetFloat( "2d_max_x" ) - focusBrackets->State().GetFloat( "2d_min_x" ) ); + _hud->SetStateFloat( "bracket_height", focusBrackets->State().GetFloat( "2d_max_y" ) - focusBrackets->State().GetFloat( "2d_min_y" ) ); + // TODO: Find a way to get bracket text from gui to hud + } + } + _hud->Redraw( gameLocal.realClientTime ); + } + + if ( gameLocal.isMultiplayer ) { + idUserInterface* _mphud = mphud; + if ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer() != this ) { + // if we're spectating someone else, use our local hud + _mphud = gameLocal.GetLocalPlayer()->mphud; + } + if ( _mphud ) { + gameLocal.mpGame.UpdateHud( _mphud ); + _mphud->Redraw( gameLocal.time ); + } + + if ( overlayHud && overlayHudTime > gameLocal.time && overlayHudTime != 0 ) { + overlayHud->Redraw( gameLocal.time ); + } else { + overlayHud = NULL; + overlayHudTime = 0; + } + } +} + +/* +=============== +idPlayer::EnterCinematic +=============== +*/ +void idPlayer::EnterCinematic( void ) { + Hide(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(true); +// RAVEN END + + if ( hud ) { + hud->HandleNamedEvent( "radioChatterDown" ); + } + + cinematicHud = NULL; + + // See if camera has custom cinematic gui + if ( gameLocal.GetCamera ( ) ) { + const char* guiCinematic; + guiCinematic = gameLocal.GetCamera()->spawnArgs.GetString ( "guiCinematic", "" ); + if ( *guiCinematic ) { + cinematicHud = uiManager->FindGui( guiCinematic, true, false, true ); + } + } + + // Load default cinematic gui? + if ( !cinematicHud ) { + const char* temp; + if ( spawnArgs.GetString ( "cinematicHud", "", &temp ) ) { + cinematicHud = uiManager->FindGui( temp, true, false, true ); + } + } + + // Have the cinematic hud start + if ( cinematicHud ) { + cinematicHud->Activate ( true, gameLocal.time ); + cinematicHud->HandleNamedEvent ( "cinematicStart" ); +// RAVEN BEGIN +// jnewquist: Option to adjust vertical fov instead of horizontal for non 4:3 modes + if ( cvarSystem->GetCVarInteger( "r_aspectRatio" ) != 0 ) { + cinematicHud->HandleNamedEvent ( "hideLetterbox" ); + } +// RAVEN END + } + + physicsObj.SetLinearVelocity( vec3_origin ); + + if ( weaponEnabled && weapon ) { + //this preSave kills all effects and sounds that we don't need lingering around. + weapon->PreSave(); + weapon->EnterCinematic(); + } + + // Reset state flags + memset( &pfl, 0, sizeof(pfl) ); + pfl.onGround = true; + pfl.dead = (health <= 0); +} + +/* +=============== +idPlayer::ExitCinematic +=============== +*/ +void idPlayer::ExitCinematic( void ) { + Show(); + +// RAVEN BEGIN +// jnewquist: Cinematics are letterboxed, this auto-fixes on widescreens + g_fixedHorizFOV.SetBool(false); +// RAVEN END + + if ( weaponEnabled && weapon ) { + //and this will restore them! + weapon->PostSave(); + weapon->ExitCinematic(); + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + + UpdateState(); +} + +/* +=============== +idPlayer::SkipCinematic +=============== +*/ +bool idPlayer::SkipCinematic( void ) { + StartSound( "snd_skipcinematic", SND_CHANNEL_ANY, 0, false, NULL ); + return gameLocal.SkipCinematic(); +} + +/* +===================== +idPlayer::UpdateConditions +===================== +*/ +void idPlayer::UpdateConditions( void ) { + idVec3 velocity; + float fallspeed; + float forwardspeed; + float sidespeed; + + // minus the push velocity to avoid playing the walking animation and sounds when riding a mover + velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity(); + fallspeed = velocity * physicsObj.GetGravityNormal(); + + if ( influenceActive ) { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } else if ( gameLocal.time - lastDmgTime < 500 ) { + forwardspeed = velocity * viewAxis[ 0 ]; + sidespeed = velocity * viewAxis[ 1 ]; + pfl.forward = pfl.onGround && ( forwardspeed > 20.01f ); + pfl.backward = pfl.onGround && ( forwardspeed < -20.01f ); + pfl.strafeLeft = pfl.onGround && ( sidespeed > 20.01f ); + pfl.strafeRight = pfl.onGround && ( sidespeed < -20.01f ); + } else if ( xyspeed > MIN_BOB_SPEED ) { + pfl.forward = pfl.onGround && ( usercmd.forwardmove > 0 ); + pfl.backward = pfl.onGround && ( usercmd.forwardmove < 0 ); + pfl.strafeLeft = pfl.onGround && ( usercmd.rightmove < 0 ); + pfl.strafeRight = pfl.onGround && ( usercmd.rightmove > 0 ); + } else { + pfl.forward = false; + pfl.backward = false; + pfl.strafeLeft = false; + pfl.strafeRight = false; + } + + pfl.run = 1; + pfl.dead = ( health <= 0 ); +} + +/* +================== +idPlayer::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) { + // force a blink + blink_time = 0; + + // play the fire animation + pfl.weaponFired = true; + + // Bias the intent direction more heavily due to firing + BiasIntentDir( viewAxis[0]*100.0f, 1.0f ); + + // update view feedback + playerView.WeaponFireFeedback( weaponDef ); +} + +/* +=============== +idPlayer::StopFiring +=============== +*/ +void idPlayer::StopFiring( void ) { + pfl.attackHeld = false; + pfl.weaponFired = false; + pfl.reload = false; + if ( weapon ) { + weapon->EndAttack(); + } +} + +/* +=============== +idPlayer::FireWeapon +=============== +*/ +void idPlayer::FireWeapon( void ) { + idMat3 axis; + idVec3 muzzle; + + if ( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) { + return; + } + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ); + return; + } + + if ( !hiddenWeapon && weapon->IsReady() ) { + // cheap hack so in MP the LG isn't allowed to fire in the short lapse while it goes from Fire -> Idle before changing to another weapon + // this gimps the weapon a lil bit but is consistent with the visual feedback clients are getting since 1.0 + bool noFireWhileSwitching = false; + noFireWhileSwitching = ( gameLocal.isMultiplayer && idealWeapon != currentWeapon && weapon->NoFireWhileSwitching() ); + if ( !noFireWhileSwitching ) { + if ( weapon->AmmoInClip() || weapon->AmmoAvailable() ) { + pfl.attackHeld = true; + weapon->BeginAttack(); + } else { + pfl.attackHeld = false; + pfl.weaponFired = false; + StopFiring(); + NextBestWeapon(); + } + } else { + StopFiring(); + } + } + // If reloading when fire is hit cancel the reload + else if ( weapon->IsReloading() ) { + weapon->CancelReload(); + } + + if ( hud && weaponChangeIconsUp ) { + hud->HandleNamedEvent( "weaponFire" ); + // nrausch: objectiveSystem does not necessarily exist (in mp it doesn't) + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "weaponFire" ); + } + weaponChangeIconsUp = false; + } +} + +/* +=============== +idPlayer::CacheWeapons +=============== +*/ +void idPlayer::CacheWeapons( void ) { + idStr weap; + int w; + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + for( w = 0; w < MAX_WEAPONS; w++ ) { + if ( inventory.weapons & ( 1 << w ) ) { + if ( !GetWeaponDef ( w ) ) { + inventory.weapons &= ~( 1 << w ); + } else { + rvWeapon::CacheWeapon( spawnArgs.GetString( va( "def_weapon%d", w ) ) ); + } + } + } +} + +/* +=============== +idPlayer::Give +=============== +*/ +bool idPlayer::Give( const char *statname, const char *value, bool dropped ) { + int amount; + + if ( pfl.dead ) { + return false; + } + + if ( IsInVehicle ( ) ) { + vehicleController.Give ( statname, value ); + } + + int boundaryHealth = inventory.maxHealth; + int boundaryArmor = inventory.maxarmor; + if( PowerUpActive( POWERUP_GUARD ) ) { + boundaryHealth = inventory.maxHealth / 2; + boundaryArmor = inventory.maxarmor / 2; + } + if( PowerUpActive( POWERUP_SCOUT ) ) { + boundaryArmor = 0; + } + if ( gameLocal.isMultiplayer ) { + //In MP, you can get twice your max from pickups + boundaryArmor *= 2; + } + + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= boundaryHealth ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth ) { + health = boundaryHealth; + } + } + } else if ( !idStr::Icmp( statname, "bonushealth" ) ) { + // allow health over max health + if ( health >= boundaryHealth * 2 ) { + return false; + } + amount = atoi( value ); + if ( amount ) { + health += amount; + if ( health > boundaryHealth * 2 ) { + health = boundaryHealth * 2; + } + } + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( inventory.armor >= boundaryArmor ) { + return false; + } + amount = atoi( value ); + + inventory.armor += amount; + if ( inventory.armor > boundaryArmor ) { + inventory.armor = boundaryArmor; + } + nextArmorPulse = gameLocal.time + ARMOR_PULSE; + } else if ( !idStr::Icmp( statname, "air" ) ) { + if ( airTics >= pm_airTics.GetInteger() ) { + return false; + } + airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger(); + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } else if ( !idStr::Icmp ( statname, "weaponmod" ) ) { + if( !idStr::Icmp( value, "all" ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + GiveWeaponMods( i, 0xFFFFFFFF ); + } + } + } else { + const char* pos = value; + + while( pos != NULL ) { + const char* end = strchr( pos, ',' ); + int len; + if ( end ) { + len = end - pos; + end++; + } else { + len = strlen( pos ); + } + + idStr weaponMod ( pos, 0, len ); + GiveWeaponMod ( weaponMod ); + + pos = end; + } + } + } else { + return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true, dropped ); + } + return true; +} + +/* +=============== +idPlayer::GiveItem + +Returns false if the item shouldn't be picked up +=============== +*/ +bool idPlayer::GiveItem( idItem *item ) { + int i; + const idKeyValue *arg; + idDict attr; + bool gave; + + bool dropped = item->spawnArgs.GetBool( "dropped" ); + + if ( gameLocal.isMultiplayer && spectating ) { + return false; + } + + item->GetAttributes( attr ); + + if( gameLocal.isServer || !gameLocal.isMultiplayer ) { + gave = false; + bool skipWeaponKey = false; + bool skipRestOfKeys = false; + if ( gameLocal.IsMultiplayer() ) { + dropped = item->spawnArgs.GetBool( "dropped" ); + if ( item->spawnArgs.FindKey( "weaponclass" ) ) { + //this is really fucking lame, but + //this is the only way we know we're trying + //to pick up a weapon before we blindly start + //processesing the attribute arguments in + //whatever order they're in below. We need + //to not process any at all if we're not allowed + //to pick up the weapon in the first place! + arg = attr.FindKey( "weapon" ); + if ( arg ) { + skipWeaponKey = true; + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } else if ( !dropped//not a dropped weapon + && gameLocal.IsWeaponsStayOn() ) { + //if failed to give weapon, don't give anything else with the weapon + skipRestOfKeys = true; + } + } + } + } + if ( !skipRestOfKeys ) { + for( i = 0; i < attr.GetNumKeyVals(); i++ ) { + arg = attr.GetKeyVal( i ); + if ( skipWeaponKey && arg->GetKey() == "weapon" ) { + //already processed this above + continue; + } + if ( Give( arg->GetKey(), arg->GetValue(), dropped ) ) { + gave = true; + } + } + } + + // hack - powerups call into this code to let them give stuff based on inv_ keywords + // for powerups that don't have any ammo/etc to give to the player, we still want to + // display the inv_name on the hud + // since idItemPowerup::GiveToPlayer() handles whether or not a player gets a powerup, + // we can override gave here for powerups + if ( !gave && !item->IsType( idItemPowerup::GetClassType() ) ) { + return false; + } + } else { + gave = true; + } + + arg = item->spawnArgs.MatchPrefix( "inv_ammo_", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "ammoPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_health", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL ); + if ( arg && hud ) { + // We need to update the weapon hud manually, but not + // the armor/ammo/health because they are updated every + // frame no matter what + if ( gameLocal.isMultiplayer ) { + UpdateHudWeapon(); + } else { + //so weapon mods highlight the correct weapon when received + int weapon = SlotForWeapon( arg->GetValue() ); + UpdateHudWeapon( weapon ); + } + hud->HandleNamedEvent( "weaponPulse" ); + } + arg = item->spawnArgs.MatchPrefix( "inv_armor", NULL ); + if ( arg && hud ) { + hud->HandleNamedEvent( "armorPulse" ); + } + +// GiveDatabaseEntry ( &item->spawnArgs ); + + // Show the item pickup on the hud + if ( hud ) { + idStr langToken = item->spawnArgs.GetString( "inv_name" ); + hud->SetStateString ( "itemtext", common->GetLocalizedString( langToken ) ); + hud->SetStateString ( "itemicon", item->spawnArgs.GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } +//RITUAL BEGIN + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + gameLocal.mpGame.RedrawLocalBuyMenu(); +//RITUAL END + + return gave; +} + +/* +=============== +idPlayer::PowerUpModifier +=============== +*/ +float idPlayer::PowerUpModifier( int type ) { + float mod = 1.0f; + + if ( PowerUpActive( POWERUP_QUADDAMAGE ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 3.0f; + break; + } + case PMOD_PROJECTILE_DEATHPUSH: { + mod *= 2.0f; + break; + } + } + } + + if ( PowerUpActive( POWERUP_HASTE ) ) { + switch ( type ) { + case PMOD_SPEED: + mod *= 1.3f; + break; + + case PMOD_FIRERATE: + mod *= 0.7f; + break; + } + } + + // Arena CTF powerups + if( PowerUpActive( POWERUP_AMMOREGEN ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= 0.7f; + break; + } + } + } + + if( PowerUpActive( POWERUP_DOUBLER ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 2.0f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 2.0f; + break; + } + } + } + +//RITUAL BEGIN + if( PowerUpActive( POWERUP_TEAM_DAMAGE_MOD ) ) { + switch( type ) { + case PMOD_PROJECTILE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_MELEE_DAMAGE: { + mod *= 1.75f; + break; + } + case PMOD_FIRERATE: { + mod *= 0.80f; + break; + } + } + } +//RITUAL END + if( PowerUpActive( POWERUP_SCOUT ) ) { + switch( type ) { + case PMOD_FIRERATE: { + mod *= (2.0f / 3.0f); + break; + } + case PMOD_SPEED: { + mod *= 1.5f; + break; + } + } + } + + return mod; +} + +/* +=============== +idPlayer::PowerUpActive +=============== +*/ +bool idPlayer::PowerUpActive( int powerup ) const { + return ( inventory.powerups & ( 1 << powerup ) ) != 0; +} + +/* +=============== +idPlayer::StartPowerUpEffect +=============== +*/ +void idPlayer::StartPowerUpEffect( int powerup ) { + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_marine_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_marine_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + AddClientModel( "mp_ctf_flag_pole" ); + AddClientModel( "mp_ctf_strogg_flag_world" ); + flagEffect = PlayEffect( "fx_ctf_strogg_flag_world", animator.GetJointHandle( spawnArgs.GetString( "flagEffectJoint" ) ), spawnArgs.GetVector( "flagEffectOrigin" ), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_CTF_ONEFLAG: { + AddClientModel( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + PlayEffect( "fx_deadzone", animator.GetJointHandle( "origin" ), true ); + break; + } + case POWERUP_QUADDAMAGE: { + powerUpOverlay = quadOverlay; + + StopEffect( "fx_regeneration" ); + PlayEffect( "fx_quaddamage", animator.GetJointHandle( "chest" ), true ); + StartSound( "snd_quaddamage_idle", SND_CHANNEL_POWERUP_IDLE, 0, false, NULL ); + + // Spawn quad effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_quaddamage_crawl" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_QUADDAMAGE; + + break; + } + + case POWERUP_REGENERATION: { + + // when buy mode is enabled, we use the guard effect for team powerup regen ( more readable than everyone going red ) + if ( gameLocal.IsTeamPowerups() ) { + // don't setup the powerup on dead bodies, it will float up where the body is invisible and the orientation will be messed up + if ( teamHealthRegen == NULL ) { + if ( health <= 0 ) { + // we can't start it now, it will be floating where the hidden dead body is + teamHealthRegenPending = true; + } else { + teamHealthRegen = PlayEffect( "fx_guard", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + powerUpOverlay = regenerationOverlay; + + StopEffect( "fx_quaddamage" ); + PlayEffect( "fx_regeneration", animator.GetJointHandle( "chest" ), true ); + + // Spawn regen effect + powerupEffect = gameLocal.GetEffect( spawnArgs, "fx_regeneration" ); + powerupEffectTime = gameLocal.time; + powerupEffectType = POWERUP_REGENERATION; + } + + break; + } + + case POWERUP_HASTE: { + powerUpOverlay = hasteOverlay; + + hasteEffect = PlayEffect( "fx_haste", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), true ); + break; + } + + case POWERUP_INVISIBILITY: { + powerUpOverlay = invisibilityOverlay; + + powerUpSkin = declManager->FindSkin( spawnArgs.GetString( "skin_invisibility" ), false ); + break; + } + + case POWERUP_GUARD: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_guard", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_SCOUT: { + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_scout", physicsObj.GetOrigin(), physicsObj.GetAxis(), true ); + break; + } + + case POWERUP_AMMOREGEN: { + if ( gameLocal.IsTeamPowerups() ) { + if ( teamAmmoRegen == NULL ) { + if ( health <= 0 ) { + teamAmmoRegenPending = true; + } else { + teamAmmoRegen = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + } + } else { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_ammoregen", renderEntity.origin, renderEntity.axis, true ); + } + break; + } + + case POWERUP_TEAM_DAMAGE_MOD: { + assert( gameLocal.IsTeamPowerups() ); + if ( teamDoubler == NULL ) { + if ( health <= 0 ) { + teamDoublerPending = true; + } else { + teamDoubler = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + } + } + break; + } + + case POWERUP_DOUBLER: { + assert( health > 0 ); + if ( arenaEffect != NULL ) { + // don't accumulate. clear whatever was there + arenaEffect->Stop( true ); + } + arenaEffect = PlayEffect( "fx_doubler", renderEntity.origin, renderEntity.axis, true ); + break; + } + } +} + +/* +=============== +idPlayer::StopPowerUpEffect +=============== +*/ +void idPlayer::StopPowerUpEffect( int powerup ) { + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if ( !( + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) + ) ) { + powerUpOverlay = NULL; + } + + // only quad has a hum, clear it even when other powerups might mean leaving the overlay on + if ( ( inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) == 0 ) { + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + switch( powerup ) { + case POWERUP_QUADDAMAGE: { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_quaddamage" ); + break; + } + case POWERUP_REGENERATION: { + if ( gameLocal.IsTeamPowerups() ) { + teamHealthRegenPending = false; + StopEffect( "fx_guard" ); + } else { + powerupEffect = NULL; + powerupEffectTime = 0; + powerupEffectType = 0; + + StopEffect( "fx_regeneration" ); + } + break; + } + case POWERUP_HASTE: { + StopEffect( "fx_haste" ); + break; + } + case POWERUP_INVISIBILITY: { + powerUpSkin = NULL; + break; + } + case POWERUP_CTF_STROGGFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + break; + } + case POWERUP_CTF_MARINEFLAG: { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + break; + } + case POWERUP_CTF_ONEFLAG: { + RemoveClientModel ( "mp_ctf_one_flag" ); + break; + } + case POWERUP_DEADZONE: { + StopEffect( "fx_deadzone" ); + break; + } + case POWERUP_SCOUT: { + StopEffect( "fx_scout" ); + break; + } + case POWERUP_GUARD: { + StopEffect( "fx_guard" ); + break; + } + case POWERUP_TEAM_DAMAGE_MOD: + teamDoublerPending = false; + // fallthrough + case POWERUP_DOUBLER: { + StopEffect( "fx_doubler" ); + break; + } + case POWERUP_AMMOREGEN: { + teamAmmoRegenPending = false; + StopEffect( "fx_ammoregen" ); + break; + } + } +} + +/* +=============== +idPlayer::GivePowerUp +passiveEffectsOnly - GivePowerup() is used to restore effects on stale players coming +back into snapshot. We don't want to announce powerups in this case +(just re-start effects) +=============== +*/ +bool idPlayer::GivePowerUp( int powerup, int time, bool team ) { + if ( powerup < 0 || powerup >= POWERUP_MAX ) { + gameLocal.Warning( "Player given power up %i\n which is out of range", powerup ); + return false; + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( powerup ); + msg.WriteBits( 1, 1 ); + // team flag only needed for POWERUP_AMMOREGEN + msg.WriteBits( team, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.GivePowerUp( this, powerup, time ); + + // only start client effects in the same instance + // play all stuff in instance 0 for server netdemo - atm other instances are not recorded + bool playClientEffects = ( ( gameLocal.IsServerDemoPlaying() && instance == 0 ) || + ( gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) ); + + switch( powerup ) { + case POWERUP_CTF_MARINEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_STROGGFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_CTF_ONEFLAG: { + // shouchard: added notice for picking up the flag + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + mphud->HandleNamedEvent( "main_notice" ); + } + } + UpdateTeamPowerups(); + break; + } + + case POWERUP_QUADDAMAGE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_QUAD_DAMAGE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + + case POWERUP_REGENERATION: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + + // Have to test for this because buying the team regeneration powerup will cause + // this to get hit multiple times as the server distributes the powerups to the clients. + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_REGENERATION, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_HASTE: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_HASTE, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_INVISIBILITY: { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_INVISIBILITY, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + break; + } + case POWERUP_GUARD: { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + inventory.maxHealth = 200; + inventory.maxarmor = 200; + + break; + } + case POWERUP_SCOUT: { + inventory.armor = 0; + + break; + } + case POWERUP_AMMOREGEN: { + if ( team && gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_AMMOREGEN, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } + case POWERUP_TEAM_DAMAGE_MOD: { + if ( gameLocal.GetLocalPlayer() == this ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TEAM_DOUBLER, gameLocal.time, gameLocal.gameType == GAME_TOURNEY ? GetInstance() : -1 ); + } + break; + } +//RITUAL BEGIN + case POWERUP_DEADZONE: { + if ( playClientEffects && this == gameLocal.GetLocalPlayer() ) { + if ( mphud ) { + mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_122000" ) ); // Squirrel@Ritual - Localized for 1.2 Patch + mphud->HandleNamedEvent( "main_notice" ); + } + } + break; + } +//RITUAL END + } + + // only start effects if in our instances and snapshot + if ( playClientEffects && !fl.networkStale ) { + StartPowerUpEffect( powerup ); + } + + return true; +} + +/* +============== +idPlayer::ClearPowerup +============== +*/ +void idPlayer::ClearPowerup( int i ) { + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteShort( i ); + msg.WriteBits( 0, 1 ); + ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); + } + + inventory.powerups &= ~( 1 << i ); + inventory.powerupEndTime[ i ] = 0; + + //if the player doesn't have quad, regen, haste or invisibility remaining on him, remove the power up overlay. + if ( !( + (inventory.powerups & ( 1 << POWERUP_TEAM_DAMAGE_MOD ) ) || + (inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) || + (inventory.powerups & ( 1 << POWERUP_REGENERATION ) ) || + (inventory.powerups & ( 1 << POWERUP_HASTE ) ) || + (inventory.powerups & ( 1 << POWERUP_INVISIBILITY ) ) || + (inventory.powerups & ( 1 << POWERUP_DEADZONE ) ) + ) ) { + powerUpOverlay = NULL; + } + + // only quad has a hum, clear it even when other powerups might mean leaving the overlay on + if ( ( inventory.powerups & ( 1 << POWERUP_QUADDAMAGE ) ) == 0 ) { + StopSound( SND_CHANNEL_POWERUP_IDLE, false ); + } + + StopPowerUpEffect( i ); +} + +/* +============== +idPlayer::GetArenaPowerupString +============== +*/ +const char* idPlayer::GetArenaPowerupString ( void ) { + if ( PowerUpActive( POWERUP_SCOUT ) ) { + return "^isct"; + } else if ( PowerUpActive( POWERUP_GUARD ) ) { + return "^igrd"; + } else if ( PowerUpActive( POWERUP_DOUBLER ) ) { + return "^idbl"; + } else if ( PowerUpActive( POWERUP_AMMOREGEN ) ) { + return "^irgn"; + } else { + return "^ixxx"; + } +} + +/* +============== +idPlayer::UpdatePowerUps +============== +*/ +void idPlayer::UpdatePowerUps( void ) { + int i; + int wearoff = -1; + bool playWearoffSound = false; + + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && ( p->spectating && p->spectator == entityNumber || !p->spectating && p->entityNumber == entityNumber ) ) { + playWearoffSound = true; + } + + for ( i = 0; i < POWERUP_MAX; i++ ) { + // Do we have this powerup? + if ( !(inventory.powerups & ( 1 << i ) ) ) { + continue; + } + + if ( inventory.powerupEndTime[i] > gameLocal.time || inventory.powerupEndTime[i] == -1 ) { + // If there is still time remaining on the powerup then update the hud + if ( playWearoffSound ) { + // Play the wearoff sound for the powerup that is closest to wearing off + if ( ( wearoff == -1 || inventory.powerupEndTime[i] < inventory.powerupEndTime[wearoff] ) && inventory.powerupEndTime[i] != -1 ) { + wearoff = i; + } + } + continue; + } else if ( inventory.powerupEndTime[ i ] != -1 && gameLocal.isServer ) { + // This particular powerup needs to respawn in a special way. + if ( i == POWERUP_DEADZONE ) { + gameLocal.mpGame.GetGameState()->SpawnDeadZonePowerup(); + } + // Powerup time has run out so take it away from the player + ClearPowerup( i ); + } + } + + // PLay wear off sound? + if ( gameLocal.isNewFrame && wearoff != -1 ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) < POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + if ( (inventory.powerupEndTime[wearoff] - gameLocal.time) / POWERUP_BLINK_TIME != ( inventory.powerupEndTime[wearoff] - gameLocal.previousTime ) / POWERUP_BLINK_TIME ) { + StartSound ( "snd_powerup_wearoff", SND_CHANNEL_POWERUP, 0, false, NULL ); + } + } + } + + // Reneration regnerates faster when less than maxHealth and can regenerate up to maxHealth * 2 + if ( gameLocal.time > nextHealthPulse ) { +// RITUAL BEGIN +// squirrel: health regen only applies if you have positive health + if( health > 0 ) { + if ( PowerUpActive ( POWERUP_REGENERATION ) || PowerUpActive ( POWERUP_GUARD ) ) { + int healthBoundary = inventory.maxHealth; // health will regen faster under this value, slower above + int healthTic = 15; + + if( PowerUpActive ( POWERUP_GUARD ) ) { + // guard max health == 200, so set the boundary back to 100 + healthBoundary = inventory.maxHealth / 2; + if( PowerUpActive (POWERUP_REGENERATION) ) { + healthTic = 30; + } + } + + if ( health < healthBoundary ) { + // only actually give health on the server + if( gameLocal.isServer ) { + health += healthTic; + if ( health > (healthBoundary * 1.1f) ) { + health = healthBoundary * 1.1f; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } else if ( health < (healthBoundary * 2) ) { + if( gameLocal.isServer ) { + health += healthTic / 3; + if ( health > (healthBoundary * 2) ) { + health = healthBoundary * 2; + } + } + StartSound ( "snd_powerup_regen", SND_CHANNEL_POWERUP, 0, false, NULL ); + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + } + // Health above max technically isnt a powerup but functions as one so handle it here + } else if ( health > inventory.maxHealth && gameLocal.isServer ) { + nextHealthPulse = gameLocal.time + HEALTH_PULSE; + health--; + } + } +// RITUAL END + } + + // Regenerate ammo + if( gameLocal.isServer && PowerUpActive( POWERUP_AMMOREGEN ) ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( inventory.weapons & ( 1 << i ) ) { + int ammoIndex = inventory.AmmoIndexForWeaponIndex( i ); + int max = inventory.StartingAmmoForWeaponIndex( i ); + + // only regen ammo if lower than starting + if( gameLocal.time > nextAmmoRegenPulse[ ammoIndex ] && inventory.ammo[ ammoIndex ] < max ) { + int step = inventory.AmmoRegenStepForWeaponIndex( i ); + int time = inventory.AmmoRegenTimeForWeaponIndex( i ); + + if( inventory.ammo[ ammoIndex ] < max ) { + inventory.ammo[ ammoIndex ] += step; + } + if( inventory.ammo[ ammoIndex ] >= max ) { + inventory.ammo[ ammoIndex ] = max; + } + + nextAmmoRegenPulse[ ammoIndex ] = gameLocal.time + time; + } + } + } + } + + // Tick armor down if greater than max armor + if ( !gameLocal.isClient && gameLocal.time > nextArmorPulse ) { + if ( inventory.armor > inventory.maxarmor ) { + nextArmorPulse += ARMOR_PULSE; + inventory.armor--; + } + } + + // Assign the powerup skin as long as we are alive + if ( health > 0 ) { + if ( powerUpSkin ) { + renderEntity.customSkin = powerUpSkin; + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + + if( weaponWorldModel ) { + weaponWorldModel->SetSkin( powerUpSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( powerUpSkin ); + } + } else { + renderEntity.customSkin = skin; + + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + + if( weaponViewModel ) { + weaponViewModel->SetSkin( weaponViewSkin ); + } + } + + if( weaponViewModel ) { + weaponViewModel->SetOverlayShader( powerUpOverlay ); + } + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + if( weaponWorldModel ) { + weaponWorldModel->GetRenderEntity()->overlayShader = powerUpOverlay; + } + + renderEntity.overlayShader = powerUpOverlay; + } else { + renderEntity.overlayShader = NULL; + powerUpOverlay = NULL; + + if( clientHead ) { + clientHead->GetRenderEntity()->overlayShader = NULL; + } + + if ( renderEntity.customSkin != gibSkin ) { + if ( influenceSkin ) { + renderEntity.customSkin = influenceSkin; + } else { + renderEntity.customSkin = skin; + } + } + } + + // Spawn quad effect + if( PowerUpActive( powerupEffectType ) && powerupEffect && gameLocal.time >= powerupEffectTime ) { + rvClientCrawlEffect* effect = new rvClientCrawlEffect( powerupEffect, this, 100, &powerupEffectJoints ); + effect->Play ( gameLocal.time, false ); + effect->GetRenderEffect()->suppressSurfaceInViewID = entityNumber+1; + powerupEffectTime = gameLocal.time + 400; + } + + // Attenuate haste effect + if ( hasteEffect ) { + hasteEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if ( flagEffect ) { + flagEffect->Attenuate( idMath::ClampFloat( 0.0f, 1.0f, physicsObj.GetLinearVelocity().LengthSqr() / Square(100.0f) ) ); + } + + if( arenaEffect ) { + arenaEffect->SetOrigin( vec3_zero ); + } +} + +/* +=============== +idPlayer::ClearPowerUps +=============== +*/ +void idPlayer::ClearPowerUps( void ) { + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( PowerUpActive( i ) ) { + ClearPowerup( i ); + } + } + + inventory.ClearPowerUps(); +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int mods ) { + inventory.weaponMods[currentWeapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +=============== +idPlayer::GiveWeaponMods +=============== +*/ +bool idPlayer::GiveWeaponMods( int weapon, int mods ) { + inventory.weaponMods[weapon] |= mods; + currentWeapon = -1; + + return true; +} + +/* +============== +idPlayer::GiveWeaponMod +============== +*/ +void idPlayer::GiveWeaponMod ( const char* weaponmod ) { + const idDict* modDict; + const idDict* weaponDict; + const char* weaponClass; + int m; + int weaponIndex; + + // Grab the weapon mod dictionary + modDict = gameLocal.FindEntityDefDict ( weaponmod, false ); + if ( !modDict ) { + gameLocal.Warning ( "Invalid weapon modification def specified '%s'", weaponmod ); + return; + } + + // Get the weapon it modifies + weaponClass = modDict->GetString ( "weapon" ); + weaponDict = gameLocal.FindEntityDefDict ( weaponClass, false ); + if ( !weaponDict ) { + gameLocal.Warning ( "Invalid weapon classname '%s' specified on weapon modification '%s'", weaponClass, weaponmod ); + return; + } + + weaponIndex = SlotForWeapon ( weaponClass ); + + // Find the index of the weapon mod + for ( m = 0; m < MAX_WEAPONMODS; m ++ ) { + const char* mod; + mod = weaponDict->GetString ( va("def_mod%d",m+1) ); + if ( !mod || !*mod ) { + break; + } + + if ( !idStr::Icmp ( weaponmod, mod ) ) { + if ( !(inventory.weaponMods[weaponIndex] & (1<GetLocalizedString( item->GetString( "inv_name" ) ); + hud->SetStateString ( "itemtext", itemName ); + hud->SetStateString ( "itemicon", item->GetString( "inv_icon" ) ); + hud->HandleNamedEvent ( "itemPickup" ); + } + + return true; +} + +/* +============== +idPlayer::UpdateObjectiveInfo +============== + */ +void idPlayer::UpdateObjectiveInfo( void ) { + if ( objectiveSystem == NULL ) { + return; + } + objectiveSystem->SetStateString( "objective1", "" ); + objectiveSystem->SetStateString( "objective2", "" ); + objectiveSystem->SetStateString( "objective3", "" ); + +// RAVEN BEGIN +// mekberg: swap objective positions to allow for stack-like appearance. + int objectiveCount = inventory.objectiveNames.Num(); + for ( int i = 0; i < inventory.objectiveNames.Num(); i++, objectiveCount-- ) { + objectiveSystem->SetStateString( va( "objective%i", objectiveCount ), "1" ); + objectiveSystem->SetStateString( va( "objectivetitle%i", objectiveCount ), inventory.objectiveNames[i].title.c_str() ); + objectiveSystem->SetStateString( va( "objectivetext%i", objectiveCount), inventory.objectiveNames[i].text.c_str() ); + objectiveSystem->SetStateInt( va( "objectiveLength%i", objectiveCount), inventory.objectiveNames[i].text.Length() ); + objectiveSystem->SetStateString( va( "objectiveshot%i", objectiveCount), inventory.objectiveNames[i].screenshot.c_str() ); + } + objectiveSystem->SetStateBool( "noObjective", !objectiveCount ); +// RAVEN END + + objectiveSystem->StateChanged( gameLocal.time ); +} + +/* +=============== +idPlayer::GiveObjective +=============== +*/ +void idPlayer::GiveObjective( const char *title, const char *text, const char *screenshot ) { + idObjectiveInfo info; +// RAVEN BEGIN + info.title = common->GetLocalizedString( title ); + info.text = common->GetLocalizedString( text ); +// RAVEN END + info.screenshot = screenshot; + inventory.objectiveNames.Append( info ); + if ( showNewObjectives ) { + ShowObjective( "newObjective" ); + } + if ( objectiveSystem ) { + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } + } +} + +/* +=============== +idPlayer::CompleteObjective +=============== +*/ +void idPlayer::CompleteObjective( const char *title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); +// RAVEN END + int c = inventory.objectiveNames.Num(); + for ( int i = 0; i < c; i++ ) { + if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) { + inventory.objectiveNames.RemoveIndex( i ); + break; + } + } + ShowObjective( "newObjectiveComplete" ); + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "newObjectiveComplete" ); + } + + if ( objectiveSystemOpen ) { + objectiveSystemOpen = false; + ToggleObjectives ( ); +#ifdef _XENON + g_ObjectiveSystemOpen = objectiveSystemOpen; +#endif + } +} + +/* +=============== +idPlayer::FailObjective +=============== +*/ +void idPlayer::FailObjective ( const char* title ) { +// RAVEN BEGIN + title = common->GetLocalizedString( title ); + +// mekberg: prevent save games if objective failed. + gameLocal.sessionCommand = "objectiveFailed "; + gameLocal.sessionCommand += title; +// RAVEN END + HideObjective ( ); + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "objectiveFailed" ); + } + if( IsInVehicle() ) { + vehicleController.GetVehicle()->EjectAllDrivers(); + } + fl.takedamage = true; + pfl.objectiveFailed = true; +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME_XEN_SP; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME_XEN_SP; +#else + playerView.Fade( colorBlack, 12000 ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; +#endif +} + +/* +=============== +idPlayer::FindInventoryItem +=============== +*/ +idDict *idPlayer::FindInventoryItem( const char *name ) { + for ( int i = 0; i < inventory.items.Num(); i++ ) { + const char *iname = inventory.items[i]->GetString( "inv_name" ); + if ( iname && *iname ) { + if ( idStr::Icmp( name, iname ) == 0 ) { + return inventory.items[i]; + } + } + } + return NULL; +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( const char *name ) { + idDict *item = FindInventoryItem(name); + if ( item ) { + RemoveInventoryItem( item ); + } +} + +/* +=============== +idPlayer::RemoveInventoryItem +=============== +*/ +void idPlayer::RemoveInventoryItem( idDict *item ) { + inventory.items.Remove( item ); + delete item; +} + +/* +=============== +idPlayer::GiveItem +=============== +*/ +void idPlayer::GiveItem( const char *itemname ) { + idDict args; + + args.Set( "classname", itemname ); + args.Set( "owner", name.c_str() ); + args.Set( "givenToPlayer", va( "%d", entityNumber ) ); + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + // check if this is a weapon + if( !idStr::Icmpn( itemname, "weapon_", 7 ) ) { + int weaponIndex = SlotForWeapon( itemname ); + if( weaponIndex >= 0 && weaponIndex < MAX_WEAPONS ) + { + int weaponIndexBit = ( 1 << weaponIndex ); + inventory.weapons |= weaponIndexBit; + inventory.carryOverWeapons |= weaponIndexBit; + carryOverCurrentWeapon = weaponIndex; + } + } + + // if the player is dead, credit him with this armor or ammo purchase + if ( health <= 0 ) { + if( !idStr::Icmp( itemname, "item_armor_small" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_LIGHT; + } else if( !idStr::Icmp( itemname, "item_armor_large" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_ARMOR_HEAVY; + } else if( !idStr::Icmp( itemname, "ammorefill" ) ) { + inventory.carryOverWeapons |= CARRYOVER_FLAG_AMMO; + } + } else { + if ( !idStr::Icmp( itemname, "ammorefill" ) ) { + int i; + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + int a = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( rvWeapon::GetAmmoNameForIndex( i ), 0 ); + inventory.ammo[i] += a; + if ( inventory.ammo[i] > inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) { + inventory.ammo[i] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ); + } + } + } + } + } + + // spawn the item if the player is alive + if ( health > 0 && idStr::Icmp( itemname, "ammorefill" ) ) { + gameLocal.SpawnEntityDef( args ); + } + +} + +/* +================== +idPlayer::SlotForWeapon +================== +*/ +int idPlayer::SlotForWeapon( const char *weaponName ) { + int i; + + for( i = 0; i < MAX_WEAPONS; i++ ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + return i; + } + } + + // not found + return -1; +} + +/* +=============== +idPlayer::Reload +=============== +*/ +void idPlayer::Reload( void ) { + if ( gameLocal.isClient || spectating || gameLocal.inCinematic || influenceActive || !weapon ) { + return; + } + + weapon->Reload(); +} + +#ifdef _XENON +/* +=============== +idPlayer::ScheduleWeaponSwitch +=============== +*/ +void idPlayer::ScheduleWeaponSwitch(int weapon) +{ + CancelEvents(&EV_Player_SelectWeapon); + hud->SetStateInt("player_selectedWeapon", weapon-1); + hud->HandleNamedEvent( "weaponSelect" ); + + // nrausch: support for turning the weapon change ui on and off + idWindow *win = FindWindowByName( "p_weapswitch", hud->GetDesktop() ); + if ( win ) { + win->SetVisible( false ); + } + + if ( weapon > 0 ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", weapon-1 ) ); + PostEventSec(&EV_Player_SelectWeapon, 0.25f, weap); + } +} +#endif + +/* +=============== +idPlayer::ShowCrosshair +=============== +*/ +void idPlayer::ShowCrosshair( void ) { + if ( !weaponEnabled ) { + return; + } + + if ( cursor ) { + cursor->HandleNamedEvent( "showCrossCombat" ); + } + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::HideCrosshair +=============== +*/ +void idPlayer::HideCrosshair( void ) { + if ( cursor ) { + cursor->HandleNamedEvent( "crossHide" ); + } +} + +/* +=============== +idPlayer::LastWeapon +=============== +*/ +void idPlayer::LastWeapon( void ) { + // Dont bother if previousWeapon is invalid or the player is spectating + if ( spectating || previousWeapon < 0 ) { + return; + } + + // Do we have the weapon still? + if ( !(inventory.weapons & ( 1 << previousWeapon ) ) ) { + return; + } + + idealWeapon = previousWeapon; +} + +/* +=============== +idPlayer::NextBestWeapon +=============== +*/ +void idPlayer::NextBestWeapon( void ) { + const char *weap; + int w = MAX_WEAPONS; + + if ( gameLocal.isClient || !weaponEnabled ) { + return; + } + + while ( w > 0 ) { + w--; + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap ) ) ) { + continue; + } + if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) { + continue; + } + break; + } + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); +} + +/* +=============== +idPlayer::NextWeapon +=============== +*/ +void idPlayer::NextWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + if ( gameLocal.isClient ) { + if ( !net_clientPredictWeaponSwitch.GetBool() ) { + return; + } + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + } else { + // if we are processing a GAME_RELIABLE_MESSAGE_EVENT, then the impulse has been predicted on the client, + // who now expects a ACK back to know when it's ok to read snapshot data again + // if the weapon change below fails (weapon not present), we still send back a ACK just in case the client decided it had a weapon + CheckAckReply(); + } + + w = idealWeapon; + while ( 1 ) { + w++; + if ( w >= MAX_WEAPONS ) { + w = 0; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( w != idealWeapon ) { + + if ( gameLocal.isClient && entityNumber == gameLocal.localClientNum ) { + // only if we are actually predicting a weapon change because if the server doesn't reply + // we'll be stuck with a bad weapon + clientIdealWeaponPredictFrame = gameLocal.framenum; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +idPlayer::PrevWeapon +=============== +*/ +void idPlayer::PrevWeapon( void ) { + const char *weap; + int w; + + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + return; + } + + // check if we have any weapons + if ( !inventory.weapons ) { + return; + } + + if ( gameLocal.isClient ) { + if ( !net_clientPredictWeaponSwitch.GetBool() ) { + return; + } + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + } else { + // if we are processing a GAME_RELIABLE_MESSAGE_EVENT, then the impulse has been predicted on the client, + // who now expects a ACK back to know when it's ok to read snapshot data again + CheckAckReply(); + } + + w = idealWeapon; + while( 1 ) { + w--; + if ( w < 0 ) { + w = MAX_WEAPONS - 1; + } + weap = spawnArgs.GetString( va( "def_weapon%d", w ) ); + if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) { + continue; + } + if ( !weap[ 0 ] ) { + continue; + } + if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) { + continue; + } + if ( inventory.HasAmmo( weap ) ) { + break; + } + } + + if ( w != idealWeapon ) { + + if ( gameLocal.isClient && entityNumber == gameLocal.localClientNum ) { + // only if we are actually predicting a weapon change because if the server doesn't reply + // we'll be stuck with a bad weapon + clientIdealWeaponPredictFrame = gameLocal.framenum; + } + + idealWeapon = w; + weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY; + UpdateHudWeapon(); + } +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( const char *weapon_name ) { + Event_SelectWeapon( weapon_name ); +} + +/* +=============== +idPlayer::SelectWeapon +=============== +*/ +void idPlayer::SelectWeapon( int num, bool force ) { + const char *weap; + + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) { + return; + } + + if ( gameLocal.isClient ) { + if ( !net_clientPredictWeaponSwitch.GetBool() ) { + return; + } + if ( entityNumber != gameLocal.localClientNum ) { + return; + } + } else { + // if we are processing a GAME_RELIABLE_MESSAGE_EVENT, then the impulse has been predicted on the client, + // who now expects a ACK back to know when it's ok to read snapshot data again + // if the weapon change below fails (weapon not present), we still send back a ACK just in case the client decided it had a weapon + CheckAckReply(); + } + + weap = spawnArgs.GetString( va( "def_weapon%d", num ) ); + if ( !weap[ 0 ] ) { + gameLocal.Warning( "Invalid weapon def_weapon%d\n", num ); + return; + } + + // cycle in-between weapons + // if a weapon_def has a "def_weapon_swap" keyvalue pointing to another + // weapon, hitting that impulse twice will cycle to the target swap. + if ( num == currentWeapon ) { + const idDict* weapDict = gameLocal.FindEntityDefDict( weap, false ); + + if ( weapDict == NULL ) { + gameLocal.Warning( "Invalid weapon entity %s\n", weap ); + return; + } + + const char* destWeapon = weapDict->GetString( "def_weapon_swap", NULL ); + + if ( destWeapon != NULL ) { + int swapNum = SlotForWeapon( destWeapon ); + if( swapNum == -1 ) { + gameLocal.Warning( "Swap weapon for %s (%s) is invalid", weap, destWeapon ); + } else { + num = swapNum; + } + } + } + + if ( force || ( inventory.weapons & ( 1 << num ) ) ) { + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) { + return; + } + + if ( gameLocal.isClient && entityNumber == gameLocal.localClientNum ) { + // only if we are actually predicting a weapon change because if the server doesn't reply + // we'll be stuck with a bad weapon + clientIdealWeaponPredictFrame = gameLocal.framenum; + } + + if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) { + assert( false ); // I'm assuming this does not happen in MP + weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) ); + if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) { + return; + } + idealWeapon = previousWeapon; + } else { + idealWeapon = num; + } + UpdateHudWeapon(); + } +} + +/* +================= +idPlayer::DropItem +================= +*/ +idEntity* idPlayer::DropItem( const char* itemClass, const idDict& customArgs, const idVec3& velocity ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.SetFloat ( "angle", 360.0f * gameLocal.random.RandomFloat ( ) ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + // If a velocity was given then just use that, otherwise randomly throw it around + if ( velocity != vec3_origin ) { + ent->GetPhysics()->SetLinearVelocity ( velocity ); + } else { + idVec3 vel; + float ang; + ang = idMath::TWO_PI * gameLocal.random.RandomFloat(); + vel[0] = PLAYER_ITEM_DROP_SPEED * idMath::Cos ( ang ); + vel[1] = PLAYER_ITEM_DROP_SPEED * idMath::Sin ( ang ); + vel[2] = PLAYER_ITEM_DROP_SPEED * 2; + ent->GetPhysics()->SetLinearVelocity ( vel ); + } + return ent; +} + +/* +================= +idPlayer::DropPowerups +================= +*/ +void idPlayer::DropPowerups( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + // These powerups aren't dropped + if ( i >= POWERUP_TEAM_AMMO_REGEN && i <= POWERUP_TEAM_DAMAGE_MOD ) + continue; + + // Don't drop this either with buying enabled. + if ( i == POWERUP_REGENERATION && gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + continue; + + /// Don't drop arena rune powerups in non-Arena modes + if( gameLocal.gameType != GAME_ARENA_CTF ) + { + if( i == POWERUP_AMMOREGEN || + i == POWERUP_GUARD || + i == POWERUP_DOUBLER || + i == POWERUP_SCOUT ) + { + continue; + } + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + if( def->dict.GetBool( "nodrop" ) ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + args.SetInt( "instance", GetInstance() ); + item = DropItem ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +idPlayer::ResetFlag +================= +*/ +idEntity* idPlayer::ResetFlag ( const char* itemClass, const idDict& customArgs ) const { + idDict args; + idEntity* ent; + args.Set( "classname", itemClass ); + args.Set( "origin", GetPhysics()->GetAbsBounds().GetCenter().ToString ( ) ); + args.Set( "dropped", "1" ); + args.Set( "reset", "1" ); + args.Copy ( customArgs ); + gameLocal.SpawnEntityDef ( args, &ent ); + if ( !ent ) { + return NULL; + } + + return ent; +} + +/* +================= +idPlayer::RespawnFlags +================= +*/ +void idPlayer::RespawnFlags ( void ) { + int i; + idEntity* item; + + assert( !gameLocal.isClient ); + + for ( i = POWERUP_CTF_MARINEFLAG; i < POWERUP_CTF_ONEFLAG; i++ ) { + if ( !(inventory.powerups & ( 1 << i )) ) { + continue; + } + + const idDeclEntityDef* def; + def = GetPowerupDef ( i ); + if ( !def ) { + continue; + } + + idDict args; + args.SetFloat ( "time", inventory.powerupEndTime[i] == -1 ? -1 : MS2SEC(inventory.powerupEndTime[i]-gameLocal.time) ); + item = ResetFlag ( def->dict.GetString ( "classname" ), args ); + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop powerup '%s'", entityNumber, def->dict.GetString ( "classname" ) ); + return; + } + } +} + +/* +================= +DropWeapon +================= +*/ +void idPlayer::DropWeapon( void ) { + idEntity* item; + idDict args; + const char* itemClass; + + assert( !gameLocal.isClient ); + + if( !gameLocal.isMultiplayer ) { + return; + } + +// RITUAL BEGIN +// squirrel: don't drop weapons in Buying modes unless "always drop" is on + if( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() && !gameLocal.serverInfo.GetBool( "si_dropWeaponsInBuyingModes" ) ) { + return; + } +// RITUAL END + + if ( spectating || weaponGone || !weapon ) { + return; + } + + // Make sure the weapon is droppable + itemClass = weapon->spawnArgs.GetString ( "def_dropItem" ); + if ( !itemClass || !*itemClass ) { + return; + } + + // If still alive then the weapon is being thrown so start it a bit in front of the player + + // copy over the instance + args.SetInt( "instance", GetInstance() ); + + if ( health > 0 ) { + idVec3 forward; + idVec3 up; + viewAngles.ToVectors( &forward, NULL, &up ); + args.SetBool( "triggerFirst", true ); + item = DropItem ( itemClass, args, 250.0f*forward + 150.0f*up ); + } else { + item = DropItem ( itemClass, args ); + } + + // Drop the weapon + if ( !item ) { + gameLocal.Warning ( "Player %d failed to drop weapon '%s'", entityNumber, weapon->spawnArgs.GetString ( "def_dropItem" ) ); + return; + } + + // Since this weapon was dropped, replace any starting ammo values with real ammo values + const idKeyValue* keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_" ); + idDict newArgs; + while( keyval ) { + newArgs.Set( va( "inv_ammo_%s", keyval->GetKey().Right( keyval->GetKey().Length() - 15 ).c_str() ), keyval->GetValue().c_str() ); + item->spawnArgs.Set( keyval->GetKey(), "" ); + keyval = item->spawnArgs.MatchPrefix( "inv_start_ammo_", keyval ); + } + + item->spawnArgs.SetDefaults( &newArgs ); + + // Set the appropriate mods on the dropped item + int i; + int mods; + idStr out; + mods = weapon->GetMods ( ); + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + if ( mods & (1<spawnArgs.GetString ( va("def_mod%d", i+1) ); + } + } + if ( out.Length() ) { + item->spawnArgs.Set ( "inv_weaponmod", out ); + } + + // Make sure the weapon removes itself over time. + item->PostEventMS ( &EV_Remove, WEAPON_DROP_TIME ); + + // Delay aquire since the weapon is being thrown + if ( health > 0 ) { + item->PostEventMS ( &EV_Activate, 500, item ); + inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 ); + NextWeapon(); + } +} + +/* +=============== +idPlayer::ActiveGui +=============== +*/ +idUserInterface *idPlayer::ActiveGui( void ) { +#ifdef _XENON + if ( objectiveSystemOpen ) { + return 0; + } +#endif + return focusUI; +} + +/* +=============== +idPlayer::Weapon_Combat +=============== +*/ +void idPlayer::Weapon_Combat( void ) { + + if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) { + return; + } + + if ( weapon ) { + weapon->RaiseWeapon(); + + if ( weapon->IsReloading() ) { + if ( !pfl.reload ) { + pfl.reload = true; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Reload", 4 ); + UpdateState(); + } + } else { + pfl.reload = false; + } + } + + if ( idealWeapon != currentWeapon ) { + if ( !weapon || weaponCatchup ) { + assert( gameLocal.isClient ); + weaponGone = false; + SetWeapon( idealWeapon ); + + weapon->NetCatchup(); + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + } else { + if ( weapon->IsReady() || weapon->IsReloading() ) { + weapon->PutAway(); + } + + if ( weapon->IsHolstered() && weaponViewModel ) { + assert( idealWeapon >= 0 ); + assert( idealWeapon < MAX_WEAPONS ); + + SetWeapon( idealWeapon ); + + weapon->Raise(); + } + } + } else { + weaponGone = false; + if ( weapon->IsHolstered() ) { + if ( !weapon->AmmoAvailable() ) { + // weapons can switch automatically if they have no more ammo + NextBestWeapon(); + } else { + weapon->Raise(); + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 3 ); + } + } + } + + weaponCatchup = false; + + // check for attack + pfl.weaponFired = false; + if ( !influenceActive ) { + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) { + FireWeapon(); + } else if ( oldButtons & BUTTON_ATTACK ) { + pfl.attackHeld = false; + weapon->EndAttack(); + } + } + + if ( gameLocal.isMultiplayer && spectating ) { + UpdateHudWeapon(); + } + + // update our ammo clip in our inventory + if ( gameLocal.GetLocalPlayer() == this && ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) { + inventory.clip[ currentWeapon ] = weapon->AmmoInClip(); + if ( hud && ( currentWeapon == idealWeapon ) ) { + UpdateHudAmmo( hud ); + } + } +} + +/* +=============== +idPlayer::Weapon_Vehicle +=============== +*/ +void idPlayer::Weapon_Vehicle( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + ProcessEvent ( &AI_EnterVehicle, focusEnt.GetEntity() ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_Usable +=============== +*/ +void idPlayer::Weapon_Usable( void ) { + StopFiring(); + weapon->LowerWeapon(); + + if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + focusEnt->ProcessEvent ( &EV_Activate, this ); + + ClearFocus ( ); + } +} + +/* +=============== +idPlayer::Weapon_NPC +=============== +*/ +void idPlayer::Weapon_NPC( void ) { + + flagCanFire = false; + + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + + if ( currentWeapon ) { + StopFiring(); + } + + if ( !focusEnt || focusEnt->health <= 0 ) { + ClearFocus ( ); + return; + } + + if ( talkCursor && ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { + buttonMask |= BUTTON_ATTACK; + if ( !talkingNPC ) { + idAI *focusAI = static_cast(focusEnt.GetEntity()); + if ( focusAI ) { + focusAI->TalkTo( this ); + talkingNPC = focusAI; + } + } + } else if ( currentWeapon == SlotForWeapon ( "weapon_blaster" ) ) { + Weapon_Combat(); + } +} + + +/* +=============== +idPlayer::LowerWeapon +=============== +*/ +void idPlayer::LowerWeapon( void ) { + if ( weapon && !weapon->IsHidden() ) { + weapon->LowerWeapon(); + } +} + +/* +=============== +idPlayer::RaiseWeapon +=============== +*/ +void idPlayer::RaiseWeapon( void ) { + if ( weapon && weapon->IsHidden() ) { + weapon->RaiseWeapon(); + } +} + +/* +=============== +idPlayer::WeaponLoweringCallback +=============== +*/ +void idPlayer::WeaponLoweringCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LowerWeapon", 3 ); + UpdateState(); +} + +/* +=============== +idPlayer::WeaponRisingCallback +=============== +*/ +void idPlayer::WeaponRisingCallback( void ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_RaiseWeapon", 2 ); + UpdateState(); +} + +/* +=============== +idPlayer::Weapon_GUI +=============== +*/ +void idPlayer::Weapon_GUI( void ) { + + flagCanFire = false; + + if ( !objectiveSystemOpen ) { + if ( idealWeapon != currentWeapon ) { + Weapon_Combat(); + } + StopFiring(); + weapon->LowerWeapon(); + } + + // disable click prediction for the GUIs. handy to check the state sync does the right thing + if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) { + return; + } + + if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) { + sysEvent_t ev; + const char *command = NULL; + bool updateVisuals = false; + + idUserInterface *ui = ActiveGui(); + if ( ui ) { + ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 ); + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + } + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +} + +/* +=============== +idPlayer::UpdateWeapon +=============== +*/ +void idPlayer::UpdateWeapon( void ) { + if ( health <= 0 ) { + return; + } + + assert( !spectating ); + + // clients need to wait till the weapon and it's world model entity + // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity ) + if ( gameLocal.isClient && (!weaponViewModel || !weaponWorldModel) ) { + return; + } + + // always make sure the weapon is correctly setup before accessing it + if ( !weapon ) { + if ( idealWeapon != -1 ) { + SetWeapon( idealWeapon ); + weaponCatchup = false; + assert( weapon ); + } else { + return; + } + } + + + if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) { + HideTip(); + } + + // Make sure the weapon is in a settled state before preventing thinking due + // to drag entity. This way things like hitting reload, zoom, etc, wont crash + if ( g_dragEntity.GetInteger() ) { + StopFiring(); + flagCanFire = false; + if ( weapon ) { + weapon->LowerWeapon(); + } + dragEntity.Update( this ); + return; + } else if ( focusType == FOCUS_CHARACTER) { + flagCanFire = false; + Weapon_NPC(); + } else if ( focusType == FOCUS_VEHICLE ) { + flagCanFire = false; + Weapon_Vehicle(); + } else if ( focusType == FOCUS_USABLE || focusType == FOCUS_USABLE_VEHICLE ) { + flagCanFire = false; + Weapon_Usable(); + } else if ( ActiveGui() ) { + flagCanFire = false; + Weapon_GUI(); + } else if ( !hiddenWeapon ) { /* no pda yet || ( ( weapon_pda >= 0 ) && ( idealWeapon == weapon_pda ) ) ) { */ + flagCanFire = true; + Weapon_Combat(); + } + + // Range finder for debugging + if ( g_showRange.GetBool ( ) ) { + idVec3 start; + idVec3 end; + trace_t tr; + + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 50000.0f; + gameLocal.TracePoint( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); + + idVec3 forward; + idVec3 right; + idVec3 up; + viewAngles.ToVectors ( &forward, &right, &up ); + gameRenderWorld->DrawText( va( "%d qu", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d m", ( int )( tr.endpos - start ).Length() ), start + forward * 100.0f + right * 25.0f - up * 6.0f, .2f, colorCyan, viewAxis ); + gameRenderWorld->DrawText( va( "%d 2d", ( int )DistanceTo2d( tr.endpos ) ), start + forward * 100.0f + right * 25.0f - up * 12.0f, .2f, colorCyan, viewAxis ); + } + + if ( weapon ) { + if ( hiddenWeapon ) { + weapon->LowerWeapon(); + } + + // update weapon state, particles, dlights, etc + weaponViewModel->PresentWeapon( showWeaponViewModel ); + } +} + +/* +=============== +idPlayer::SpectateFreeFly +=============== +*/ +void idPlayer::SpectateFreeFly( bool force ) { + idPlayer *player; + idVec3 newOrig; + idVec3 spawn_origin; + idAngles spawn_angles; + + player = gameLocal.GetClientByNum( spectator ); + if ( force || gameLocal.time > lastSpectateChange ) { + spectator = entityNumber; + if ( player && player != this && !player->spectating && !player->IsInTeleport() ) { + newOrig = player->GetPhysics()->GetOrigin(); + if ( player->physicsObj.IsCrouching() ) { + newOrig[ 2 ] += pm_crouchviewheight.GetFloat(); + } else { + newOrig[ 2 ] += pm_normalviewheight.GetFloat(); + } + newOrig[ 2 ] += SPECTATE_RAISE; + idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + idVec3 start = player->GetPhysics()->GetOrigin(); + start[2] += pm_spectatebbox.GetFloat() * 0.5f; + trace_t t; + // assuming spectate bbox is inside stand or crouch box +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( player, t, start, newOrig, b, MASK_PLAYERSOLID, player ); +// RAVEN END + newOrig.Lerp( start, newOrig, t.fraction ); + SetOrigin( newOrig ); + idAngles angle = player->viewAngles; + angle[ 2 ] = 0; + SetViewAngles( angle ); + } else { + if( !SelectSpawnPoint( spawn_origin, spawn_angles ) ) { + return; + } + spawn_origin[ 2 ] += pm_normalviewheight.GetFloat(); + spawn_origin[ 2 ] += SPECTATE_RAISE; + SetOrigin( spawn_origin ); + SetViewAngles( spawn_angles ); + } + lastSpectateChange = gameLocal.time + 500; + } +} + +/* +=============== +idPlayer::SpectateCycle +=============== +*/ +void idPlayer::SpectateCycle( void ) { + idPlayer *player; + + if ( gameLocal.time > lastSpectateChange ) { + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + if ( !player ) { + SpectateFreeFly( true ); + return; + } + + // ignore other spectators + int latchedSpectator = spectator; + while ( player->spectating ) { + spectator = gameLocal.GetNextClientNum( spectator ); + player = gameLocal.GetClientByNum( spectator ); + if ( spectator == latchedSpectator ) { + break; + } + } + lastSpectateChange = gameLocal.time + 500; + + if ( !player || player->spectating ) { + SpectateFreeFly( true ); + return; + } + + if ( player ) { + player->UpdateHudWeapon( player->currentWeapon ); + } + } +} + +/* +=============== +idPlayer::UpdateSpectating +=============== +*/ +void idPlayer::UpdateSpectating( void ) { + assert( spectating ); + assert( !gameLocal.isClient || IsFakeClient() ); + assert( IsHidden() ); + idPlayer *player; + if ( !gameLocal.isMultiplayer ) { + return; + } + player = gameLocal.GetClientByNum( spectator ); + if ( !player || ( player->spectating && player != this ) ) { + SpectateFreeFly( true ); + } else if ( usercmd.upmove > 0 && player && player != this ) { + // following someone and hit jump? release. + SpectateFreeFly( false ); + } else if ( usercmd.buttons & BUTTON_ATTACK && gameLocal.gameType != GAME_TOURNEY ) { + // tourney mode uses seperate cycling + SpectateCycle(); + } +} + +/* +=============== +idPlayer::HandleSingleGuiCommand +=============== +*/ +bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { + idToken token; + + if ( !src->ReadToken( &token ) ) { + return false; + } + + if ( token == ";" ) { + return false; + } + + if ( token.Icmp( "addhealth" ) == 0 ) { + if ( entityGui && health < 100 ) { + int _health = entityGui->spawnArgs.GetInt( "gui_parm1" ); + int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health; + _health -= amt; + entityGui->spawnArgs.SetInt( "gui_parm1", _health ); + if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) { + entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health ); + } + health += amt; + if ( health > 100 ) { + health = 100; + } + } + return true; + } + + if ( token.Icmp( "ready" ) == 0 ) { + PerformImpulse( IMPULSE_17 ); + return true; + } + + if ( token.Icmp( "heal" ) == 0 && + entityGui->IsType( rvHealingStation::GetClassType() ) && + src->ReadToken( &token ) ) + { + rvHealingStation * station = static_cast< rvHealingStation * >( entityGui ); + + if ( token.Icmp( "begin" ) == 0 ) { + station->BeginHealing( this ); + } else if ( token.Icmp( "end" ) == 0 ) { + station->EndHealing( ); + } else { + return false; + } + return true; + } + + src->UnreadToken( &token ); + return false; +} + +/* +============== +idPlayer::Collide +============== +*/ +bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity *other; + other = gameLocal.entities[ collision.c.entityNum ]; + + // allow client-side prediction of item collisions for simple client effects + if ( gameLocal.isClient && !other->IsType( idItem::GetClassType() ) ) { + return false; + } + + + if ( other ) { + other->Signal( SIG_TOUCH ); + if ( !spectating ) { + if ( other->RespondsTo( EV_Touch ) ) { + other->ProcessEvent( &EV_Touch, this, &collision ); + } + } else { + if ( other->RespondsTo( EV_SpectatorTouch ) ) { + other->ProcessEvent( &EV_SpectatorTouch, this, &collision ); + } + } + } + return false; +} + +/* +================ +idPlayer::UpdateLocation + +Searches nearby locations +================ +*/ + void idPlayer::UpdateLocation( void ) { + if ( hud ) { + idLocationEntity *locationEntity = gameLocal.LocationForPoint( GetEyePosition() ); + if ( locationEntity ) { + hud->SetStateString( "location", locationEntity->GetLocation() ); + } else { +// RAVEN BEGIN +// rjohnson: temp fix until id corrects slow downs created from constant string lookup +// hud->SetStateString( "location", common->GetLocalizedString( "#str_102911" ) ); + hud->SetStateString( "location", "Unidentified" ); +// RAVEN END + } + } +} + +/* +================ +idPlayer::UpdateFocus + +Searches nearby entities for interactive guis, possibly making one of them +the focus and sending it a mouse move event +================ +*/ +void idPlayer::UpdateFocus( void ) { + + // These only need to be updated at the last tic + if ( !gameLocal.isLastPredictFrame ) { + return; + } + + idClipModel* clipModelList[ MAX_GENTITIES ]; + idClipModel* clip; + int listedClipModels; + idEntity* ent; + idUserInterface* oldBrackets; + int oldTalkCursor; + int i; + int j; + idVec3 start; + idVec3 end; + trace_t renderTrace, bboxTrace, allTrace; + guiPoint_t pt; + +// RAVEN BEGIN + // mekberg: removed check to see if attack was held. +// RAVEN END + + // No focus during cinimatics + if ( gameLocal.inCinematic ) { + return; + } + + // Focus has a limited time, make sure it hasnt expired + if ( focusTime && gameLocal.time > focusTime ) { + ClearFocus ( ); + } + + if ( spectating ) { + return; + } + + if ( g_perfTest_noPlayerFocus.GetBool() ) { + return; + } + +#ifndef _XENON + cvarSystem->SetCVarInteger( "pm_isZoomed", zoomed ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + +#ifdef _XENON + if ( cursor ) { + if ( weapon ) { + cursor->SetStateInt( "autoaim", weapon->AllowAutoAim() ? 1 : 0 ); + } else { + cursor->SetStateInt( "autoaim", 0 ); + } + } +#endif + + // Kill the focus brackets when their time has elapsed + oldBrackets = focusBrackets; + if ( focusBracketsTime && gameLocal.time > focusBracketsTime ) { + focusBrackets = NULL; + } + + oldTalkCursor = talkCursor; + talkCursor = 0; + start = GetEyePosition(); + end = start + viewAngles.ToForward() * 768.0f; + + // player identification -> names to the hud + if ( gameLocal.isMultiplayer && IsLocalClient() ) { + trace_t trace; + idVec3 end = start + viewAngles.ToForward() * 768.0f; + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + // no aim text if player is invisible + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) && ( !((idPlayer*)gameLocal.entities[ trace.c.entityNum ])->PowerUpActive( POWERUP_INVISIBILITY ) ) ) { + char* teammateHealth = ""; + idPlayer* p = static_cast(gameLocal.entities[ trace.c.entityNum ]); + + if( trace.c.entityNum != aimClientNum ) { + if( mphud ) { + mphud->SetStateString( "aim_text", va( "%s\n%s", gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_name" ), gameLocal.userInfo[ trace.c.entityNum ].GetString( "ui_clan" ) ) ); + if( gameLocal.IsTeamGame() ) { + mphud->SetStateInt( "aim_player_team", p->team ); + + if( p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + // when looking at a friendly, color the crosshair + if( cursor ) { + if( p->team == team ) { + cursor->HandleNamedEvent( "targetFriendly" ); + } else { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + + + mphud->HandleNamedEvent( "aim_text" ); + aimClientNum = trace.c.entityNum; + } + } else { + // update health + if( gameLocal.IsTeamGame() && p->team == team ) { + teammateHealth = va( "^iteh %d / ^itea %d", p->health, p->inventory.armor ); + } + + mphud->SetStateString( "aim_teammate_health", teammateHealth ); + } + } else { + if( mphud && aimClientNum != -1 ) { + mphud->HandleNamedEvent( "aim_fade" ); + aimClientNum = -1; + } + if( cursor ) { + cursor->HandleNamedEvent( "clearTarget" ); + } + } + } + +#ifdef _XENON + + bboxTrace.fraction = -1; + + bestEnemy = NULL; + if ( gameLocal.isMultiplayer ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + return; + } + + idEntity *ent = NULL; + idActor *actor = NULL; + bool inside; + bool showDebug = cvarSystem->GetCVarBool("pm_showAimAssist"); + float bDist = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + float dist; + idFrustum aimArea; + float dNear, dFar, size; + renderView_t *rv = GetRenderView(); + float fovY, fovX; + // field of view + gameLocal.CalcFov( CalcFov( true ), fovX, fovY ); + + + dNear = cvarSystem->GetCVarFloat( "r_znear" ); + dFar = cvarSystem->GetCVarInteger("pm_AimAssistDistance"); + +#ifndef _FINAL + if ( cvarSystem->GetCVarInteger("pm_AimAssistTest") != 0 ) { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * pm_AimAssistFOV.GetFloat()/100.0; + } else +#endif + { + size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * weapon->GetAutoAimFOV()/100.0; + } + + aimArea.SetOrigin( GetEyePosition() ); + aimArea.SetAxis( viewAngles.ToMat3() ); + aimArea.SetSize( dNear, dFar, size, size ); + + + if ( showDebug ) { + gameRenderWorld->DebugFrustum(colorRed, aimArea, false, 20); + } + + idLinkList *entities = &gameLocal.snapshotEntities; + if ( gameLocal.isServer ) { + entities = &gameLocal.spawnedEntities; + } + ent = entities->Next(); + while ( ent != NULL ) { + + bool isOk = true; + + if ( !ent->IsType(idPlayer::GetClassType()) ) { + isOk = false; + } else if ( gameLocal.IsTeamGame() && ((idPlayer*)ent)->team == team ) { + isOk = false; + } else if ( ((idPlayer*)ent)->instance != instance ) { + isOk = false; + } else if ( ((idPlayer*)ent)->spectating ) { + isOk = false; + } else if ( !idStr::Icmp(name.c_str(),ent->name.c_str()) ) { + isOk = false; + } else if ( ent->health <= 0 ) { + isOk = false; + } + + if ( !isOk ) { + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + continue; + } + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + if ( showDebug ) { + gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + } + inside = aimArea.IntersectsBounds(bounds); + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + + if ( gameLocal.isServer ) { + ent = ent->spawnNode.Next(); + } else { + ent = ent->snapshotNode.Next(); + } + } + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && IsLocalClient() ) ) { + if ( bestEnemy ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + trace_t trace; + + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + return; + } + +#endif + + idBounds bounds( start ); + bounds.AddPoint( end ); + + listedClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); + + // dluetscher: added optimization to eliminate redundant traces + renderTrace.fraction = -1; + bboxTrace.fraction = -1; + allTrace.fraction = -1; + + // no pretense at sorting here, just assume that there will only be one active + // gui within range along the trace + bool wasTargetFriendly = targetFriendly; + targetFriendly = false; + for ( i = 0; i < listedClipModels; i++ ) { + clip = clipModelList[ i ]; + ent = clip->GetEntity(); +//RITUAL BEGIN +//singlis: if ent is null, continue; + if(ent == NULL || ent->IsHidden()) { + continue; + } +//RITUAL END + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + // SP only + // basically what was happening was that heads, which are an idAFAttachment, were being used for the focusLength + // calculations, but that generates a different focus length than the body would (when you scan the crosshair back + // and forth between the head and body). This ends up looking like a bug when you are right at the threshold where + // the body will display the name and rank, but doesn't when you pitch up to aim at the head. Hence, using the body + // for this special case. + if ( !gameLocal.isMultiplayer && ent->IsType( idAFAttachment::GetClassType() )) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType()) ) { + focusLength = (body->GetPhysics()->GetOrigin() - start).LengthFast() - body->GetPhysics()->GetBounds().GetRadius(); + } + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + isFriendly = (static_cast( ent )->team == team); + } + + //change crosshair color if over a friendly + if ( !gameLocal.isMultiplayer + && focusType == FOCUS_NONE + && !g_crosshairCharInfoFar.GetBool() ) { + if ( focusLength < 512 ) { + bool newTargetFriendly = false; + if ( isAI && isFriendly ) { + newTargetFriendly = true; + } else if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->team == team ) ) { + newTargetFriendly = true; + } + } + if ( newTargetFriendly ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + targetFriendly = true; + if( cursor && !wasTargetFriendly ) { + cursor->HandleNamedEvent( "showCrossBuddy" ); + } + } + } + } + } + +// RAVEN BEGIN + +#ifdef _XENON + if ( doAimAssist ) { + if ( isAI && !isFriendly && (ent->health > 0) ) { + + if ( idStr::Icmp( name.c_str(),ent->name.c_str() ) != 0 ) { + + const idBounds &bounds = ent->GetPhysics()->GetAbsBounds(); + //if ( showDebug ) { + //gameRenderWorld->DebugBounds(colorGreen, bounds, vec3_origin, 20); + //} + bool inside = aimArea.IntersectsBounds(bounds); + + if ( inside ) { + dist = bounds.ShortestDistance(GetEyePosition()); + if ( bDist > dist ) { + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum != ent->entityNumber ) ) { + idVec3 v = end - start; + if ( ((v.Length() * bboxTrace.fraction) + 5.0f) >= dist ) { + // close enough to the bbox + bDist = dist; + bestEnemy = (idActor *)ent; + } + } else { + // Didn't hit anything, so this must be in view + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } + } +#endif + +// mekberg: allowFocus removed + if ( focusLength < (g_crosshairCharInfoFar.GetBool()?256.0f:80.0f) ) { +// RAVEN END + if ( ent->IsType( idAFAttachment::GetClassType() ) ) { + idEntity *body = static_cast( ent )->GetBody(); + if ( body && body->IsType( idAI::GetClassType() ) && ( static_cast( body )->GetTalkState() >= TALK_OK ) ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, body, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + + if ( isAI ) { + if ( static_cast( ent )->GetTalkState() >= TALK_OK ) { + + // dluetscher: added optimization to eliminate redundant traces + if ( renderTrace.fraction == -1 ) { + gameLocal.TracePoint( this, renderTrace, start, end, MASK_SHOT_RENDERMODEL, this ); + } + if ( ( renderTrace.fraction < 1.0f ) && ( renderTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_CHARACTER, FOCUS_TIME, ent, NULL ); + if ( focusLength < 80.0f ) { + talkCursor = 1; + } + break; + } + } + continue; + } + } + + if ( focusLength < 80.0f ) { + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + rvVehicle* vehicle = static_cast(ent); + // dluetscher: added optimization to eliminate redundant traces + if ( bboxTrace.fraction == -1 ) { + gameLocal.TracePoint( this, bboxTrace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + } + if ( ( bboxTrace.fraction < 1.0f ) && ( bboxTrace.c.entityNum == ent->entityNumber ) && ((end - start).Length() * bboxTrace.fraction < vehicle->FocusLength()) ) { + //jshepard: locked or unusable vehicles + if ( !vehicle->IsLocked() && vehicle->HasOpenPositions() ) { + SetFocus ( FOCUS_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + + //jshepard: unusable vehicle + if( ent->spawnArgs.GetBool( "unusableVehicle", "0" ) ) { + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + SetFocus ( FOCUS_LOCKED_VEHICLE, FOCUS_TIME, ent, NULL ); + break; + } + } + // Usable entities are last + if ( ent->fl.usable ) { + //jshepard: fake vehicles + if ( allTrace.fraction == -1.f ) { + gameLocal.TracePoint( this, allTrace, start, end, MASK_ALL, this ); + } + if ( ( allTrace.fraction < 1.0f ) && ( allTrace.c.entityNum == ent->entityNumber ) ) { + if( ent->spawnArgs.GetBool("crosshair_vehicle")) { + SetFocus ( FOCUS_USABLE_VEHICLE, FOCUS_TIME, ent, NULL ); + } else { + SetFocus ( FOCUS_USABLE, FOCUS_TIME, ent, NULL ); + } + break; + } + } + } + + if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) { + continue; + } + + if ( ent->spawnArgs.GetBool( "inv_item" ) ) { + // don't allow guis on pickup items focus + continue; + } + + pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end ); + if ( pt.x != -1 ) { + idUserInterface* ui = NULL; + renderEntity_t* focusGUIrenderEntity; + + focusGUIrenderEntity = ent->GetRenderEntity(); + if ( !focusGUIrenderEntity ) { + continue; + } + + if ( pt.guiId >= 1 && pt.guiId <= MAX_RENDERENTITY_GUI ) { + ui = focusGUIrenderEntity->gui[ pt.guiId-1 ]; + } + + if ( ui == NULL || !ui->IsInteractive ( ) ) { + continue; + } + + // All focused guis get brackets + focusBrackets = ui; + + // Any GUI that is too far away will just get bracket focus so the player can still shoot + // but still see which guis are interractive + if ( focusLength > 300.0f ) { + ClearFocus ( ); + break; + } else if ( focusLength > 80.0f ) { + ClearFocus ( ); + focusType = FOCUS_BRACKETS; +#ifdef _XENON + // Hide the "press whatever" text + hud->SetStateInt( "GUIIsNotUsingDPad", 3 ); + hud->SetStateInt( "hideInteractive", 1 ); +#endif + break; + } + + // If this is the first time this gui was activated then set up some things + if ( focusEnt != ent ) { + const idKeyValue* kv; + + // new activation + // going to see if we have anything in inventory a gui might be interested in + // need to enumerate inventory items + ui->SetStateInt( "inv_count", inventory.items.Num() ); + for ( j = 0; j < inventory.items.Num(); j++ ) { + idDict *item = inventory.items[ j ]; + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + + ui->SetStateString( va( "inv_name_%i", j), iname ); + ui->SetStateString( va( "inv_icon_%i", j), iicon ); + ui->SetStateString( va( "inv_text_%i", j), itext ); + kv = item->MatchPrefix("inv_id", NULL); + if ( kv ) { + ui->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + ui->SetStateInt( iname, 1 ); + } + + for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) { + const char *p = inventory.pdaSecurity[ j ]; + + if ( p && *p ) { + ui->SetStateInt( p, 1 ); + } + } + + ui->SetStateString( "player_health", va("%i", health ) ); + ui->SetStateString( "player_armor", va( "%i%%", inventory.armor ) ); + + kv = ent->spawnArgs.MatchPrefix( "gui_", NULL ); + while ( kv ) { + ui->SetStateString( kv->GetKey(), common->GetLocalizedString( kv->GetValue() ) ); + kv = ent->spawnArgs.MatchPrefix( "gui_", kv ); + } + } + + // clamp the mouse to the corner + const char* command; + sysEvent_t ev; + ev = sys->GenerateMouseMoveEvent( -2000, -2000 ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + + // move to an absolute position + ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT ); + command = ui->HandleEvent( &ev, gameLocal.time ); + HandleGuiCommands( ent, command ); + +#ifdef _XENON + int usepad = 0; + if ( focusUI ) { + idWindow *dwin = ui->GetDesktop(); + if ( dwin ) { + idWinVar *wv = dwin->GetWinVarByName("dpadGUI"); + if ( wv ) { + usepad = atoi(wv->c_str()); + } + } + } + hud->SetStateInt( "GUIIsNotUsingDPad", (usepad) ? 0 : 1 ); + hud->SetStateInt( "hideInteractive", 0 ); +#endif + + SetFocus ( FOCUS_GUI, FOCUS_GUI_TIME, ent, ui ); + break; + } + } + +#ifdef _XENON + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && IsLocalClient() ) ) { + + if ( !weapon || !weapon->AllowAutoAim() ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } else { + + if ( bestEnemy && doAimAssist ) { + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 1); + } + trace_t trace; + idVec3 altEnd = bestEnemy->GetPhysics()->GetOrigin(); + altEnd[2]+=35.0f; // origin is always below the monster's feet for whatever reason, so aim at this + gameLocal.TracePoint( this, trace, start, altEnd, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == bestEnemy->entityNumber ) ) { + int slow = pm_AimAssistSlow.GetInteger(); + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : slow ); + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + } + } else { + usercmdGen->SetSlowJoystick( zoomed ? pm_zoomedSlow.GetInteger() : 100 ); + if ( cursor ) { + cursor->SetStateInt("enemyFocus", 0); + } + } + } + } +#endif + + if ( !gameLocal.isMultiplayer || (gameLocal.isMultiplayer && IsLocalClient() ) ) { + if ( wasTargetFriendly && !targetFriendly && focusType == FOCUS_NONE ) { + if ( cursor ) { + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); + } + } + } + + // Update the focus brackets within the hud + if ( focusBrackets && hud ) { + if (focusType == FOCUS_BRACKETS || focusType == FOCUS_GUI ) { + if ( !oldBrackets || focusBracketsTime ) { + hud->HandleNamedEvent ( "showBrackets" ); + } + focusBracketsTime = 0; + } else { + if ( focusBracketsTime == 0 ) { + hud->HandleNamedEvent ( "fadeBrackets" ); + focusBracketsTime = gameLocal.time + 2000; + } + } + focusBrackets->SetStateBool ( "2d_calc", true ); + } + + if ( cursor && ( oldTalkCursor != talkCursor ) ) { + cursor->SetStateInt( "talkcursor", talkCursor ); + } + +} + +/* +================ +idPlayer::ClearFocus +================ +*/ +void idPlayer::ClearFocus ( void ) { + SetFocus ( FOCUS_NONE, 0, NULL, NULL ); +} + +void idPlayer::UpdateFocusCharacter( idEntity* newEnt ) { + if ( !cursor ) { + return; + } + // Handle character interaction + cursor->SetStateString( "npc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_name", "Joe" )) ); + cursor->SetStateString( "npcdesc", common->GetLocalizedString(newEnt->spawnArgs.GetString( "npc_description", "" )) ); + if ( newEnt->IsType( rvAIMedic::GetClassType() ) ) { + if ( ((rvAIMedic*)newEnt)->isTech ) { + cursor->SetStateInt( "npc_medictech", 2 ); + } else { + cursor->SetStateInt( "npc_medictech", 1 ); + } + } else { + cursor->SetStateInt( "npc_medictech", 0 ); + } +} +/* +================ +idPlayer::SetFocus +================ +*/ +void idPlayer::SetFocus ( playerFocus_t newType, int _focusTime, idEntity* newEnt, idUserInterface* newUI ) { + const char* command; + + // Handle transitions from one user interface to another or to none + if ( newUI != focusUI ) { + if ( focusUI ) { + command = focusUI->Activate( false, gameLocal.time ); + HandleGuiCommands( focusEnt, command ); + StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL ); + } + if ( newUI ) { + command = newUI->Activate( true, gameLocal.time ); + HandleGuiCommands( newEnt, command ); + StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL ); + + // Hide the weapon when a gui is being used + /* + if ( weapon ) { + weapon->Hide(); + } + */ + }/* + else if ( weapon ) { + // Show the weapon since it was hidden when a gui first got focus + weapon->Show(); + } + */ + } + + //jshepard: the medic/tech crosshair won't update unless handleNamedEvent is called. I moved this outside + //of the switch below because the focus type is the same when moving directly from marine to marine, but the + //medic/tech status may change. + if ( newType == FOCUS_CHARACTER ) { + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + cursor->HandleNamedEvent ( "showCrossTalk" ); + } + } + // Show the appropriate cursor for the current focus type + if ( cursor && ( focusType != newType ) ) { + switch ( newType ) { + case FOCUS_VEHICLE: + case FOCUS_USABLE_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicle" ); + break; + case FOCUS_LOCKED_VEHICLE: + cursor->HandleNamedEvent ( "showCrossVehicleLocked" ); + break; + case FOCUS_USABLE: + cursor->HandleNamedEvent ( "showCrossUsable" ); + break; + case FOCUS_GUI: + cursor->HandleNamedEvent ( "showCrossGui" ); + break; + case FOCUS_CHARACTER: + if ( newEnt != focusEnt ) { + UpdateFocusCharacter( newEnt ); + } + cursor->HandleNamedEvent ( "showCrossTalk" ); + break; + default: + // Make sure the weapon is shown in the default state +// RAVEN BEGIN +// abahr: don't do this if weapons are disabled + cursor->HandleNamedEvent ( WeaponIsEnabled() ? "showCrossCombat" : "crossHide" ); +// RAVEN END + break; + } + } + + + focusType = newType; + focusEnt = newEnt; + focusUI = newUI; + + if ( focusType == FOCUS_NONE ) { + focusTime = 0; + } else { + focusTime = gameLocal.time + _focusTime; + } +} + +/* +================= +idPlayer::CrashLand + +Check for hard landings that generate sound events +================= +*/ +void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { + idVec3 origin, velocity; + idVec3 gravityVector, gravityNormal; + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + waterLevel_t waterLevel; + bool noDamage; + + pfl.softLanding = false; + pfl.hardLanding = false; + + // if the player is not on the ground + if ( !physicsObj.HasGroundContacts() ) { + return; + } + + gravityNormal = physicsObj.GetGravityNormal(); + + // if the player wasn't going down + if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) { + return; + } + + waterLevel = physicsObj.GetWaterLevel(); + + // never take falling damage if completely underwater + if ( waterLevel == WATERLEVEL_HEAD ) { + return; + } + + // no falling damage if touching a nodamage surface + noDamage = false; + for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) { + const contactInfo_t &contact = physicsObj.GetContact( i ); + if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) { + noDamage = true; + break; + } + } + + //jshepard: no falling damage if falling damage is disabled + if( pfl.noFallingDamage ) { + return; + } + + origin = GetPhysics()->GetOrigin(); + gravityVector = physicsObj.GetGravity(); + + // calculate the exact velocity on landing + dist = ( origin - oldOrigin ) * -gravityNormal; + vel = oldVelocity * -gravityNormal; + acc = -gravityVector.Length(); + + a = acc / 2.0f; + b = vel; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.0001; + + // reduce falling damage if there is standing water + if ( waterLevel == WATERLEVEL_WAIST ) { + delta *= 0.25f; + } + if ( waterLevel == WATERLEVEL_FEET ) { + delta *= 0.5f; + } + + if ( delta < 1.0f ) { + return; + } + + // Some bug in the player movement code causes double fall damage on the 2nd frame in certain situations, + // so hack around this. Basically, if the next frame's velocity is going to be great enough to cause damage, + // don't do the damage this frame. This feels a lot safer than messing with things that could break player + // movement in worse ways. + if ( gameLocal.isMultiplayer ) { + + float vel2 = GetPhysics()->GetLinearVelocity() * -gravityNormal; + + a = acc / 2.0f; + b = vel2; + c = -dist; + + den = b * b - 4.0f * a * c; + if ( den < 0 ) { + return; + } + t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a ); + + float delta2 = vel2 + t * acc; + delta2 = delta2 * delta2 * 0.0001; + + if ( delta2 > softFallDelta ) { + return; + } + } + + + // ddynerman: moved height delta selection to player def + if ( delta > fatalFallDelta && fatalFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -32; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 ); + } + } else if ( delta > hardFallDelta && hardFallDelta > 0.0f ) { + pfl.hardLanding = true; + landChange = -24; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 ); + } + } else if ( delta > softFallDelta && softFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -16; + landTime = gameLocal.time; + if ( !noDamage ) { + pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 ); + } + } else if ( delta > noFallDelta && noFallDelta > 0.0f ) { + pfl.softLanding = true; + landChange = -8; + landTime = gameLocal.time; + } + + // ddynerman: sometimes the actual landing animation is pre-empted by another animation (i.e. sliding, moving forward) + // so we play the landing sound here instead of relying on the anim + if( pfl.hardLanding ) { + StartSound ( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL ); + StartSound ( "snd_land_hard_pain", SND_CHANNEL_ANY, 0, false, NULL ); + } else if ( pfl.softLanding ) { + // todo - 2 different landing sounds for variety? + StartSound ( "snd_land_soft", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +/* +=============== +idPlayer::BobCycle +=============== +*/ +void idPlayer::BobCycle( const idVec3 &pushVelocity ) { + float bobmove; + int old, deltaTime; + idVec3 vel, gravityDir, velocity; + idMat3 viewaxis; + float bob; + float delta; + float speed; + float f; + + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + velocity = physicsObj.GetLinearVelocity() - pushVelocity; + + if ( noclip ) { + velocity.Zero ( ); + } + + gravityDir = physicsObj.GetGravityNormal(); + vel = velocity - ( velocity * gravityDir ) * gravityDir; + xyspeed = vel.LengthFast(); + + if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) { + // airborne + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) { + // start at beginning of cycle again + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else { + if ( physicsObj.IsCrouching() ) { + bobmove = pm_crouchbob.GetFloat(); + // ducked characters never play footsteps + } else { + // vary the bobbing based on the speed of the player + bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac; + } + + // check for footstep / splash sounds + old = bobCycle; + bobCycle = (int)( old + bobmove * gameLocal.GetMSec() ) & 255; + bobFoot = ( bobCycle & 128 ) >> 7; + bobfracsin = idMath::Fabs( idMath::Sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); + } + + // calculate angles for view bobbing + viewBobAngles.Zero(); + + // no view bob at all in MP while zoomed in + if ( IsZoomed() ) { + bobCycle = 0; + bobFoot = 0; + bobfracsin = 0; + } else { + viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + + // add angles based on velocity + delta = velocity * viewaxis[0]; + viewBobAngles.pitch += delta * pm_runpitch.GetFloat(); + + delta = velocity * viewaxis[1]; + viewBobAngles.roll -= delta * pm_runroll.GetFloat(); + + // add angles based on bob + // make sure the bob is visible even at low speeds + speed = xyspeed > 200 ? xyspeed : 200; + + delta = bobfracsin * pm_bobpitch.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching + } + viewBobAngles.pitch += delta; + delta = bobfracsin * pm_bobroll.GetFloat() * speed; + if ( physicsObj.IsCrouching() ) { + delta *= 3; // crouching accentuates roll + } + if ( bobFoot & 1 ) { + delta = -delta; + } + viewBobAngles.roll += delta; + } + // calculate position for view bobbing + viewBob.Zero(); + + if ( physicsObj.HasSteppedUp() ) { + + // check for stepping up before a previous step is completed + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp(); + } else { + stepUpDelta = physicsObj.GetStepUp(); + } + if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) { + stepUpDelta = 2.0f * pm_stepsize.GetFloat(); + } + stepUpTime = gameLocal.time; + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + + // if the player stepped up recently + deltaTime = gameLocal.time - stepUpTime; + if ( deltaTime < STEPUP_TIME ) { + viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME ); + } + + // add bob height after any movement smoothing + bob = bobfracsin * xyspeed * pm_bobup.GetFloat(); + if ( bob > 6 ) { + bob = 6; + } +// RAVEN BEGIN +// abahr: added gravity + viewBob += bob * -gravityDir; +// RAVEN END + + // add fall height + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + viewBob -= gravity * ( landChange * f ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + viewBob -= gravity * ( landChange * f ); + } +} + +/* +================ +idPlayer::UpdateDeltaViewAngles +================ +*/ +void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) { + // set the delta angle + idAngles delta; + for( int i = 0; i < 3; i++ ) { + delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] ); + } + SetDeltaViewAngles( delta ); +} + +/* +================ +idPlayer::SetViewAngles +================ +*/ +void idPlayer::SetViewAngles( const idAngles &angles ) { + UpdateDeltaViewAngles(angles); + viewAngles = angles; +} + +/* +================ +idPlayer::UpdateViewAngles +================ +*/ +void idPlayer::UpdateViewAngles( void ) { + int i; + idAngles delta; + + if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 ) ) { + // no view changes at all, but we still want to update the deltas or else when + // we get out of this mode, our view will snap to a kind of random angle + UpdateDeltaViewAngles( viewAngles ); + return; + } + + // if dead + if ( health <= 0 ) { + if ( pm_thirdPersonDeath.GetBool() ) { + viewAngles.roll = 0.0f; + viewAngles.pitch = 30.0f; + } else { + viewAngles.roll = 40.0f; + viewAngles.pitch = -15.0f; + } + return; + } + + for ( i = 0; i < 3; i++ ) { + cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] ); + if ( influenceActive == INFLUENCE_LEVEL3 ) { + viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) ); + } else { + viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i] ) + deltaViewAngles[i] ); + } + } + if ( !centerView.IsDone( gameLocal.time ) ) { + viewAngles.pitch = centerView.GetCurrentValue( gameLocal.time ); + } + + // clamp the pitch + if ( noclip ) { + if ( viewAngles.pitch > 89.0f ) { + // don't let the player look down more than 89 degrees while noclipping + viewAngles.pitch = 89.0f; + } else if ( viewAngles.pitch < -89.0f ) { + // don't let the player look up more than 89 degrees while noclipping + viewAngles.pitch = -89.0f; + } + } else { + if ( viewAngles.pitch > pm_maxviewpitch.GetFloat() ) { + // don't let the player look down enough to see the shadow of his (non-existant) feet + viewAngles.pitch = pm_maxviewpitch.GetFloat(); + } else if ( viewAngles.pitch < pm_minviewpitch.GetFloat() ) { + // don't let the player look up more than 89 degrees + viewAngles.pitch = pm_minviewpitch.GetFloat(); + } + } + + UpdateDeltaViewAngles( viewAngles ); + + // orient the model towards the direction we're looking + SetAngles( idAngles( 0, viewAngles.yaw, 0 ) ); + + // save in the log for analyzing weapon angle offsets + loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles; +} + +/* +============== +idPlayer::UpdateAir +============== +*/ +void idPlayer::UpdateAir( void ) { + + if ( health <= 0 ) { + return; + } + + // see if the player is connected to the info_vacuum + bool newAirless = false; + + if ( gameLocal.vacuumAreaNum != -1 ) { + int num = GetNumPVSAreas(); + if ( num > 0 ) { + int areaNum; + + // if the player box spans multiple areas, get the area from the origin point instead, + // otherwise a rotating player box may poke into an outside area + if ( num == 1 ) { + const int *pvsAreas = GetPVSAreas(); + areaNum = pvsAreas[0]; + } else { + areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() ); + } + newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR ); + } + } + + if ( newAirless ) { + if ( !airless ) { + StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL ); + if ( hud ) { + hud->HandleNamedEvent( "noAir" ); + } + } + airTics--; + if ( airTics < 0 ) { + airTics = 0; + // check for damage + const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false ); + int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f ); + if ( gameLocal.time > lastAirDamage + dmgTiming ) { + Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 ); + lastAirDamage = gameLocal.time; + } + } + + } else { + if ( airless ) { + StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + if ( hud ) { + hud->HandleNamedEvent( "Air" ); + } + } + airTics+=2; // regain twice as fast as lose + if ( airTics > pm_airTics.GetInteger() ) { + airTics = pm_airTics.GetInteger(); + } + } + + airless = newAirless; + + if ( hud ) { + hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() ); + } +} + +// RAVEN BEGIN +// abahr +/* +============== +idPlayer::UpdateGravity +============== +*/ +void idPlayer::UpdateGravity( void ) { + GetPhysics()->SetGravity( gameLocal.GetCurrentGravity(this) ); +} +// RAVEN END + +/* +============== +idPlayer::ToggleObjectives +============== +*/ +void idPlayer::ToggleObjectives ( void ) { +// RAVEN BEGIN +// mekberg: allow disabling of objectives. + if ( objectiveSystem == NULL || !objectivesEnabled ) { + return; + } +// RAVEN END + + if ( !objectiveSystemOpen ) { + int j, c = inventory.items.Num(); + objectiveSystem->SetStateInt( "inv_count", c ); + for ( j = 0; j < c; j++ ) { + idDict *item = inventory.items[j]; + if ( !item->GetBool( "inv_pda" ) ) { + const char *iname = item->GetString( "inv_name" ); + iname = common->GetLocalizedString( iname ); + + const char *iicon = item->GetString( "inv_icon" ); + const char *itext = item->GetString( "inv_text" ); + objectiveSystem->SetStateString( va( "inv_name_%i", j ), iname ); + objectiveSystem->SetStateString( va( "inv_icon_%i", j ), iicon ); + objectiveSystem->SetStateString( va( "inv_text_%i", j ), itext ); + const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL ); + if ( kv ) { + objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() ); + } + } + } + + for ( j = 0; j < MAX_WEAPONS; j++ ) { + const char *weapnum = va( "def_weapon%d", j ); + int weapstate = 0; + if ( inventory.weapons & ( 1 << j ) ) { + const char *weap = spawnArgs.GetString( weapnum ); + if ( weap && *weap ) { + weapstate++; + } + } + objectiveSystem->SetStateInt( weapnum, weapstate ); + } + + UpdateObjectiveInfo(); + objectiveSystem->Activate( true, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommShow" ); + } else { + objectiveSystem->Activate( false, gameLocal.time ); + objectiveSystem->HandleNamedEvent( "wristcommHide" ); + } + objectiveSystemOpen ^= 1; +} + +/* +============== +idPlayer::ToggleScoreboard +============== +*/ +void idPlayer::ToggleScoreboard( void ) { + scoreBoardOpen ^= 1; +} + +/* +============== +idPlayer::Spectate +============== +*/ +void idPlayer::Spectate( bool spectate, bool force ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + // track invisible player bug + // all hiding and showing should be performed through Spectate calls + // except for the private camera view, which is used for teleports + // ok for players in other instances to be hidden + assert( force || ( teleportEntity.GetEntity() != NULL ) || ( IsHidden() || !spectating ) || ( IsHidden() && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ); + + if ( spectating == spectate && !force ) { + return; + } + + bool inOtherInstance = gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance; + + spectating = spectate; + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + if ( gameLocal.isServer ) { + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteBits( spectating, 1 ); + ServerSendEvent( EVENT_SPECTATE, &msg, false, -1 ); + } + + // on the client, we'll get spectate messages about clients in other instances - always assume they are spectating + if ( spectating || inOtherInstance ) { + // join the spectators + delete clientHead; + clientHead = NULL; + + ClearPowerUps(); + spectator = this->entityNumber; + Init(); + StopRagdoll(); + SetPhysics( &physicsObj ); + common->DPrintf( "idPlayer::Spectate() - Disabling clip for %d '%s' - spectate: %d force: %d\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?", spectate, force ); + physicsObj.DisableClip(); + Hide(); + Event_DisableWeapon(); + + // remove the weapon + delete weapon; + weapon = NULL; + if ( !gameLocal.isClient ) { + delete weaponViewModel; + weaponViewModel = NULL; + delete weaponWorldModel; + weaponWorldModel = NULL; + } + } else { + // put everything back together again + UpdateModelSetup( true ); + currentWeapon = -1; // to make sure the def will be loaded if necessary + Show(); + Event_EnableWeapon(); + } + + SetClipModel( inOtherInstance ); + + if ( inOtherInstance ) { + // Normally idPlayer::Move() gets called to set the contents to 0, but we don't call + // move on players not in our snap, so we need to set it manually here. + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + physicsObj.SetClipMask( MASK_DEADSOLID ); + } +} + +/* +============== +idPlayer::SetClipModel +============== +*/ +void idPlayer::SetClipModel( bool forceSpectatorBBox ) { + idBounds bounds; + + common->DPrintf( "idPlayer::SetClipModel() - Called on %d '%s' forceSpectatorBBox = %d spectate = %d instance = %d local instance = %d\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "", forceSpectatorBBox, spectating, instance, gameLocal.GetLocalPlayer() ? gameLocal.GetLocalPlayer()->GetInstance() : -1 ); + if ( spectating || forceSpectatorBBox ) { + bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f ); + } else { + bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 ); + bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() ); + } + // the origin of the clip model needs to be set before calling SetClipModel + // otherwise our physics object's current origin value gets reset to 0 + idClipModel *newClip; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + + // < 0: don't use alternate alignment, > 0: use alternate alignment (faces aligned with axes for even-sided cylinders) + // 0: use AABB; 1: use 8-sided cylinder; 3+: use custom number of sides for cylinder + int sides = pm_usecylinder.GetInteger(); + bool alt_align = (sides > 0); + sides = idMath::Abs( sides ); + + if ( sides >= 1 ) { + if ( sides < 3 ) { + sides = 8; + } + idTraceModel trm( bounds, sides, alt_align ); +// trm.findWalkSurfaces = true; + newClip = new idClipModel( trm, NULL ); + } else { + idTraceModel trm( bounds ); +// trm.findWalkSurfaces = true; + newClip = new idClipModel( trm, NULL ); + } + + newClip->Translate( physicsObj.PlayerGetOrigin() ); + physicsObj.SetClipModel( newClip, 1.0f ); +} + +/* +============== +idPlayer::EnterVehicle +============== +*/ +bool idPlayer::EnterVehicle( idEntity* vehicle ) { + if ( !idActor::EnterVehicle ( vehicle ) ) { + return false; + } + +// RAVEN BEGIN +// jshepard: safety first + if( weapon) { + weapon->Hide(); + } + +// abahr: + //HideCrosshair(); +// RAVEN END + + return true; +} + +/* +============== +idPlayer::ExitVehicle +============== +*/ +bool idPlayer::ExitVehicle ( bool force ) { + if ( !idActor::ExitVehicle ( force ) ) { + return false; + } + + SetViewAngles( viewAxis[0].ToAngles() ); + +// RAVEN BEGIN +// jshepard: had this crash on me more than once :( + if(weapon) { + weapon->Show(); + } +// abahr: Would like to call something more specific + ShowCrosshair(); +// RAVEN END + + return true; +} + + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + +/* +============== +GetItemCost +============== +*/ +int idPlayer::GetItemCost( const char* itemName ) { + if ( !itemCosts ) { + assert( false ); + return 99999; + } + return itemCosts->dict.GetInt( itemName, "99999" ); +} + +/* +============== +GetItemBuyImpulse +============== +*/ +int GetItemBuyImpulse( const char* itemName ) +{ + struct ItemBuyImpulse + { + const char* itemName; + int itemBuyImpulse; + }; + + ItemBuyImpulse itemBuyImpulseTable[] = + { + { "weapon_shotgun", IMPULSE_100, }, + { "weapon_machinegun", IMPULSE_101, }, + { "weapon_hyperblaster", IMPULSE_102, }, + { "weapon_grenadelauncher", IMPULSE_103, }, + { "weapon_nailgun", IMPULSE_104, }, + { "weapon_rocketlauncher", IMPULSE_105, }, + { "weapon_railgun", IMPULSE_106, }, + { "weapon_lightninggun", IMPULSE_107, }, + // IMPULSE_108 - Unused + { "weapon_napalmgun", IMPULSE_109, }, + // { "weapon_dmg", IMPULSE_110, }, + // IMPULSE_111 - Unused + // IMPULSE_112 - Unused + // IMPULSE_113 - Unused + // IMPULSE_114 - Unused + // IMPULSE_115 - Unused + // IMPULSE_116 - Unused + // IMPULSE_117 - Unused + { "item_armor_small", IMPULSE_118, }, + { "item_armor_large", IMPULSE_119, }, + { "ammorefill", IMPULSE_120, }, + // IMPULSE_121 - Unused + // IMPULSE_122 - Unused + { "ammo_regen", IMPULSE_123, }, + { "health_regen", IMPULSE_124, }, + { "damage_boost", IMPULSE_125, }, + // IMPULSE_126 - Unused + // IMPULSE_127 - Unused + }; + const int itemBuyImpulseTableSize = sizeof(itemBuyImpulseTable) / sizeof(itemBuyImpulseTable[0]); + + for( int i = 0; i < itemBuyImpulseTableSize; ++ i ) + { + if( !stricmp( itemBuyImpulseTable[ i ].itemName, itemName ) ) + { + return itemBuyImpulseTable[ i ].itemBuyImpulse; + } + } + + return 0; +} + + +bool idPlayer::CanBuyItem( const char* itemName ) +{ + itemBuyStatus_t buyStatus = ItemBuyStatus( itemName ); + return( buyStatus == IBS_CAN_BUY ); +} + + +itemBuyStatus_t idPlayer::ItemBuyStatus( const char* itemName ) +{ + idStr itemNameStr = itemName; + if ( itemNameStr == "notimplemented" ) + { + return IBS_NOT_ALLOWED; + } + else if( !idStr::Cmpn( itemName, "wpmod_", 6 ) ) + { + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_small" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_LIGHT ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "item_armor_large" ) + { + if( inventory.armor >= 190 ) + return IBS_ALREADY_HAVE; + + if( inventory.carryOverWeapons & CARRYOVER_FLAG_ARMOR_HEAVY ) + return IBS_ALREADY_HAVE; + + if( PowerUpActive( POWERUP_SCOUT ) ) + return IBS_NOT_ALLOWED; + } + else if( itemNameStr == "ammorefill" ) + { + if( inventory.carryOverWeapons & CARRYOVER_FLAG_AMMO ) + return IBS_ALREADY_HAVE; + + // If we are full of ammo for all weapons, you can't buy the ammo refill anymore. + bool fullAmmo = true; + for ( int i = 0 ; i < MAX_AMMOTYPES; i++ ) + { + if ( inventory.ammo[i] != inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex(i) ) ) + fullAmmo = false; + } + if ( fullAmmo ) + return IBS_NOT_ALLOWED; + } + else if ( itemNameStr == "fc_armor_regen" ) + { + return IBS_NOT_ALLOWED; + } + + if ( gameLocal.gameType == GAME_DM || gameLocal.gameType == GAME_TOURNEY || gameLocal.gameType == GAME_ARENA_CTF || gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if ( itemNameStr == "ammo_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "health_regen" ) + return IBS_NOT_ALLOWED; + if ( itemNameStr == "damage_boost" ) + return IBS_NOT_ALLOWED; + } + + if ( CanSelectWeapon(itemName) != -1 ) + return IBS_ALREADY_HAVE; + + int cost = GetItemCost(itemName); + if ( cost > (int)buyMenuCash ) + { + return IBS_CANNOT_AFFORD; + } + + return IBS_CAN_BUY; +} + +/* +============== +idPlayer::UpdateTeamPowerups +============== +*/ +void idPlayer::UpdateTeamPowerups( bool isBuying ) { + for ( int i=0; iIsType( idPlayer::Type ) ) + continue; + + idPlayer* player = static_cast< idPlayer * >( ent ); + + // Not my teammate + if ( player->team != team ) + continue; + + // when a teammate respawns while a powerup is active, we don't want to go through other players which already have the powerup + // otherwise they'll get multiple VO announces + // ignore this when setting up powerups after a buying impulse, it only means someone is buying before expiration + if ( !isBuying && ( ( player->inventory.powerups & ( 1 << powerup ) ) != 0 ) ) { + continue; + } + + player->GivePowerUp( powerup, time, true ); + } + + gameLocal.mpGame.teamPowerups[team][i].update = false; + } + } +} + + +/* +============== +idPlayer::AttemptToBuyTeamPowerup +============== +*/ +bool idPlayer::AttemptToBuyTeamPowerup( const char* itemName ) +{ + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_AMMOREGEN, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "health_regen" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_REGENERATION, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + else if ( itemNameStr == "damage_boost" ) { + gameLocal.mpGame.AddTeamPowerup(POWERUP_TEAM_DAMAGE_MOD, SEC2MS(30), team); + UpdateTeamPowerups( true ); + return true; + } + + return false; +} + +/* +============== +idPlayer::AttemptToBuyItem +============== +*/ +bool idPlayer::AttemptToBuyItem( const char* itemName ) +{ + assert( !IsFakeClient() ); + + if ( gameLocal.isClient ) { + return false; + } + + if( !itemName ) { + return false; + } + + int itemCost = GetItemCost( itemName ); + + /// Check if the player is allowed to buy this item + if( !CanBuyItem( itemName ) ) + { + return false; + } + + const char* playerName = GetUserInfo()->GetString( "ui_name" ); + common->DPrintf( "Player %s about to buy item %s; player has %d (%g) credits, cost is %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + buyMenuCash -= (float)itemCost; + + common->DPrintf( "Player %s just bought item %s; player now has %d (%g) credits, cost was %d\n", playerName, itemName, (int)buyMenuCash, buyMenuCash, itemCost ); + + + // Team-based effects + idStr itemNameStr = itemName; + + if ( itemNameStr == "ammo_regen" || itemNameStr == "health_regen" || itemNameStr == "damage_boost" ) { + return AttemptToBuyTeamPowerup(itemName); + } + + GiveStuffToPlayer( this, itemName, NULL ); + gameLocal.mpGame.RedrawLocalBuyMenu(); + return true; +} + +bool idPlayer::CanBuy( void ) { + bool ret = gameLocal.mpGame.IsBuyingAllowedRightNow(); + if ( !ret ) { + return false; + } + return !spectating; +} + + +void idPlayer::GenerateImpulseForBuyAttempt( const char* itemName ) { + if ( !CanBuy() ) + return; + + int itemBuyImpulse = GetItemBuyImpulse( itemName ); + PerformImpulse( itemBuyImpulse ); +} +// RITUAL END + + +/* +============== +idPlayer::PerformImpulse +============== +*/ +void idPlayer::PerformImpulse( int impulse ) { + + if ( gameLocal.isClient ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( IsLocalClient() ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteBits( impulse, IMPULSE_NUMBER_OF_BITS ); + ClientSendEvent( EVENT_IMPULSE, &msg ); + } + + if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 ) { + SelectWeapon( impulse, false ); + return; + } + + switch( impulse ) { + case IMPULSE_13: { + Reload(); + break; + } + case IMPULSE_14: { + NextWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCycleNext( this ); + } + break; + } + case IMPULSE_15: { + PrevWeapon(); + if( gameLocal.isServer && spectating && gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->SpectateCyclePrev( this ); + } + break; + } + case IMPULSE_17: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleReady( ); + } + break; + } + case IMPULSE_18: { + centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); + break; + } + case IMPULSE_19: { +/* + // when we're not in single player, IMPULSE_19 is used for showScores + // otherwise it does IMPULSE_12 (PDA) + if ( !gameLocal.isMultiplayer ) { + if ( !objectiveSystemOpen ) { + if ( weapon ) { + weapon->Hide (); + } + } + ToggleMap(); + } +*/ + break; + } + case IMPULSE_20: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleTeam( ); + } + break; + } + case IMPULSE_21: { + if( gameLocal.isServer && gameLocal.gameType == GAME_TOURNEY ) { + // only allow a client to join the waiting arena if they are not currently assigned to an arena + + // removed waiting arena functionality for now + /*rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( this != arena.GetPlayers()[ 0 ] && this != arena.GetPlayers()[ 1 ] ) { + if( instance == MAX_ARENAS && !spectating ) { + ServerSpectate( true ); + JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( 0 ) ); + } else if( spectating ) { + JoinInstance( MAX_ARENAS ); + ServerSpectate( false ); + } + }*/ + } + break; + } + case IMPULSE_22: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.ToggleSpectate( ); + } + break; + } + + case IMPULSE_28: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, true ); + } + break; + } + case IMPULSE_29: { + if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) { + gameLocal.mpGame.CastVote( gameLocal.localClientNum, false ); + } + break; + } + case IMPULSE_40: { + idFuncRadioChatter::RepeatLast(); + break; + } + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus + case IMPULSE_100: AttemptToBuyItem( "weapon_shotgun" ); break; + case IMPULSE_101: AttemptToBuyItem( "weapon_machinegun" ); break; + case IMPULSE_102: AttemptToBuyItem( "weapon_hyperblaster" ); break; + case IMPULSE_103: AttemptToBuyItem( "weapon_grenadelauncher" ); break; + case IMPULSE_104: AttemptToBuyItem( "weapon_nailgun" ); break; + case IMPULSE_105: AttemptToBuyItem( "weapon_rocketlauncher" ); break; + case IMPULSE_106: AttemptToBuyItem( "weapon_railgun" ); break; + case IMPULSE_107: AttemptToBuyItem( "weapon_lightninggun" ); break; + case IMPULSE_108: break; // Unused + case IMPULSE_109: AttemptToBuyItem( "weapon_napalmgun" ); break; + case IMPULSE_110: /* AttemptToBuyItem( "weapon_dmg" );*/ break; + case IMPULSE_111: break; // Unused + case IMPULSE_112: break; // Unused + case IMPULSE_113: break; // Unused + case IMPULSE_114: break; // Unused + case IMPULSE_115: break; // Unused + case IMPULSE_116: break; // Unused + case IMPULSE_117: break; // Unused + case IMPULSE_118: AttemptToBuyItem( "item_armor_small" ); break; + case IMPULSE_119: AttemptToBuyItem( "item_armor_large" ); break; + case IMPULSE_120: AttemptToBuyItem( "ammorefill" ); break; + case IMPULSE_121: break; // Unused + case IMPULSE_122: break; // Unused + case IMPULSE_123: AttemptToBuyItem( "ammo_regen" ); break; + case IMPULSE_124: AttemptToBuyItem( "health_regen" ); break; + case IMPULSE_125: AttemptToBuyItem( "damage_boost" ); break; + case IMPULSE_126: break; // Unused + case IMPULSE_127: break; // Unused +// RITUAL END + + case IMPULSE_50: { + ToggleFlashlight ( ); + break; + } + + case IMPULSE_51: { + LastWeapon(); + break; + } + } + +//RAVEN BEGIN +//asalmon: route d-pad input to the active gui. +#ifdef _XBOX + if (ui && ev.evValue != 0 && !objectiveSystemOpen ) { + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusEnt && ui == focusUI ) { + focusEnt->UpdateVisuals(); + } + + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusEnt ) { + HandleGuiCommands( focusEnt, command ); + } else { + HandleGuiCommands( this, command ); + } + } +#endif +//RAVEN END +} + +/* +============== +idPlayer::HandleESC +============== +*/ +bool idPlayer::HandleESC( void ) { + +// jdischler: Straight from the top, cinematic skipping on xenon is OFFICIALLY OUT. Too many problems with it and not enough time to properly address them. +#ifndef _XENON + if ( gameLocal.inCinematic ) { + return SkipCinematic(); + } +#endif + return false; +} + +/* +============== +idPlayer::HandleObjectiveInput +============== +*/ +void idPlayer::HandleObjectiveInput() { +#ifdef _XENON + if ( gameLocal.inCinematic ) { + return; + } + if ( !objectiveButtonReleased ) { + if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 ) { + objectiveButtonReleased = true; + } + } else { + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 ) { + ToggleObjectives ( ); + g_ObjectiveSystemOpen = objectiveSystemOpen; + objectiveButtonReleased = false; + } + } +#else + if ( ( usercmd.buttons & BUTTON_SCORES ) != 0 && !objectiveSystemOpen && !gameLocal.inCinematic ) { + ToggleObjectives ( ); + } else if ( ( usercmd.buttons & BUTTON_SCORES ) == 0 && objectiveSystemOpen ) { + ToggleObjectives ( ); + } else if ( objectiveSystemOpen && gameLocal.inCinematic ) { + ToggleObjectives ( ); + } +#endif +} + +/* +============== +idPlayer::EvaluateControls +============== +*/ +void idPlayer::EvaluateControls( void ) { + // check for respawning + if ( pfl.dead || pfl.objectiveFailed ) { +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode + if( allowedToRespawn ) { + if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) { + forceRespawn = true; + } else if ( gameLocal.time > maxRespawnTime ) { + forceRespawn = true; + } + } + else + { + Spectate(true); + } +// RITUAL END + } + + // in MP, idMultiplayerGame decides spawns + if ( forceRespawn && !gameLocal.isMultiplayer && !g_testDeath.GetBool() ) { + // in single player, we let the session handle restarting the level or loading a game + gameLocal.sessionCommand = "died"; + } + + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + assert( false ); // unused in multiplayer? + PerformImpulse( usercmd.impulse ); + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + oldFlags = usercmd.flags; + + AdjustSpeed(); + + // update the viewangles + UpdateViewAngles(); +} + +/* +============== +idPlayer::AdjustSpeed +============== +*/ +void idPlayer::AdjustSpeed( void ) { + float speed; + + if ( spectating ) { + speed = pm_spectatespeed.GetFloat(); + bobFrac = 0.0f; + } else if ( noclip ) { + speed = pm_noclipspeed.GetFloat(); + bobFrac = 0.0f; + } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) { + bobFrac = 1.0f; + speed = pm_speed.GetFloat(); + } else { + speed = pm_walkspeed.GetFloat(); + bobFrac = 0.0f; + } + + speed *= PowerUpModifier(PMOD_SPEED); + + if ( influenceActive == INFLUENCE_LEVEL3 ) { + speed *= 0.33f; + } + + physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() ); +} + +/* +============== +idPlayer::AdjustBodyAngles +============== +*/ +void idPlayer::AdjustBodyAngles( void ) { + idMat3 lookAxis; + idMat3 legsAxis; + bool blend; + float diff; + float upBlend; + float forwardBlend; + float downBlend; + + if ( health < 0 ) { + return; + } + + blend = true; + + if ( !physicsObj.HasGroundContacts() ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else if ( usercmd.forwardmove < 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = false; + } else if ( usercmd.forwardmove > 0 ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() ); + legsForward = true; + } else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) { + if ( !legsForward ) { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() ); + } else { + idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() ); + } + } else if ( usercmd.rightmove != 0 ) { + idealLegsYaw = 0.0f; + legsForward = true; + } else { + legsForward = true; + diff = idMath::Fabs( idealLegsYaw - legsYaw ); + idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw ); + if ( diff < 0.1f ) { + legsYaw = idealLegsYaw; + blend = false; + } + } + + if ( !physicsObj.IsCrouching() ) { + legsForward = true; + } + + oldViewYaw = viewAngles.yaw; + + pfl.turnLeft = false; + pfl.turnRight = false; + if ( idealLegsYaw < -45.0f ) { + idealLegsYaw = 0; + pfl.turnRight = true; + blend = true; + } else if ( idealLegsYaw > 45.0f ) { + idealLegsYaw = 0; + pfl.turnLeft = true; + blend = true; + } + + if ( blend ) { + legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f; + } + legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3(); + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis ); + + // ============================================================================================== + // leaning - orient parts of the body towards the movement direction + // we control the chest, neck and hip orientations + + float leanGunCorrection = 0.0f; + if ( g_playerLean.GetFloat() != 0.0f ) { + + idVec3 velocity = physicsObj.GetLinearVelocity(); + velocity.z = 0.0f; + + // clamp and scale max speed to unit velocity + float horizSpeed = velocity.Normalize(); + if ( horizSpeed > leanMaxSpeed ) { + horizSpeed = leanMaxSpeed; + } + velocity *= horizSpeed / leanMaxSpeed; + // now express this velocity in the view's axis base + velocity *= viewAxis.Transpose(); + + // blend + leanVelocity = velocity * leanBlendRatio + leanVelocity * ( 1.0f - leanBlendRatio ); + + // build the rotation + float t = fabs( leanVelocity.x ) + fabs( leanVelocity.y ); + if ( t > 1e-3f ) { + float maxAngle = ( leanMaxForwardAngle * fabs( leanVelocity.x ) + leanMaxLateralAngle * fabs( leanVelocity.y ) ) / t; + maxAngle *= g_playerLean.GetFloat(); + + idVec3 rotate; + rotate.Cross( leanVelocity, idVec3( 0.0f, 0.0f, 1.0f ) ); + float chestAngle = maxAngle * rotate.Normalize(); // doing the blend on the vectors, but idRotation wants a normalized rotation + idRotation chestRotate( vec3_origin, rotate, chestAngle ); + animator.SetJointAxis( chestJoint, JOINTMOD_WORLD, chestRotate.ToMat3() ); + + // extract a rotation angle in the forward plane, this is used to compensate the gun later + idVec3 p( 1.0f, 0.0f, 0.0f ); + chestRotate.RotatePoint( p ); + leanGunCorrection = RAD2DEG( idMath::ATan( p.z, p.x ) ); + + float headAngle = chestAngle * leanHeadRatio; + idRotation neckRotate( vec3_origin, rotate, headAngle ); + animator.SetJointAxis( neckLeanJoint, JOINTMOD_WORLD, neckRotate.ToMat3() ); + + float hipAngle = chestAngle * leanHipRatio; + idRotation hipRotate( vec3_origin, rotate, hipAngle ); + animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, hipRotate.ToMat3() ); + } else { + leanVelocity = vec3_origin; + leanGunCorrection = 0.0f; + } + } + + // ============================================================================================== + // calculate the blending between down, straight, and up. account for the chest lean + + float leanFrac = idMath::ClampFloat( -1.0f, 1.0f, ( viewAngles.pitch + leanGunCorrection ) / 90.0f ); + if ( leanFrac > 0.0f ) { + downBlend = leanFrac; + forwardBlend = 1.0f - leanFrac; + upBlend = 0.0f; + } else { + downBlend = 0.0f; + forwardBlend = 1.0f + leanFrac; + upBlend = -leanFrac; + } + + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend ); + + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend ); +} + +/* +============== +idPlayer::InitAASLocation +============== +*/ +void idPlayer::InitAASLocation( void ) { + int i; + int num; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + GetFloorPos( 64.0f, origin ); + + num = gameLocal.NumAAS(); + aasLocation.SetGranularity( 1 ); + aasLocation.SetNum( num ); + for( i = 0; i < aasLocation.Num(); i++ ) { + aasLocation[ i ].areaNum = 0; + aasLocation[ i ].pos = origin; + aas = gameLocal.GetAAS( i ); + if ( aas && aas->GetSettings() ) { + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + } + } +} + +/* +============== +idPlayer::SetAASLocation +============== +*/ +void idPlayer::SetAASLocation( void ) { + int i; + int areaNum; + idVec3 size; + idBounds bounds; + idAAS *aas; + idVec3 origin; + + if ( !GetFloorPos( 64.0f, origin ) ) { + return; + } + + for( i = 0; i < aasLocation.Num(); i++ ) { + aas = gameLocal.GetAAS( i ); + if ( !aas ) { + continue; + } + + size = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK ); + if ( areaNum ) { + aasLocation[ i ].pos = origin; + aasLocation[ i ].areaNum = areaNum; + } + } +} + +/* +============== +idPlayer::GetAASLocation +============== +*/ +void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const { + int i; + + if ( aas != NULL ) { + for( i = 0; i < aasLocation.Num(); i++ ) { + if ( aas == gameLocal.GetAAS( i ) ) { + areaNum = aasLocation[ i ].areaNum; + pos = aasLocation[ i ].pos; + return; + } + } + } + + areaNum = 0; + pos = physicsObj.GetOrigin(); +} + +/* +============== +idPlayer::Move +============== +*/ +void idPlayer::Move( void ) { + float newEyeOffset; + idVec3 oldOrigin; + idVec3 oldVelocity; + idVec3 pushVelocity; + + // save old origin and velocity for crashlanding + oldOrigin = physicsObj.GetOrigin(); + oldVelocity = physicsObj.GetLinearVelocity(); + pushVelocity = physicsObj.GetPushedLinearVelocity(); + + // set physics variables + physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() ); + physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() ); + + if ( noclip ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_NOCLIP ); + } else if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetMovementType( PM_SPECTATOR ); + } else if ( health <= 0 ) { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + physicsObj.SetMovementType( PM_DEAD ); + } else if ( gameLocal.inCinematic || pfl.objectiveFailed || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_FREEZE ); + } else { + physicsObj.SetContents( CONTENTS_BODY | (use_combat_bbox?CONTENTS_SOLID:0) ); + physicsObj.SetMovementType( PM_NORMAL ); + } + + if ( spectating || ( gameLocal.isClient && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() != instance ) ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else if ( health <= 0 ) { + physicsObj.SetClipMask( MASK_DEADSOLID ); + } else { + physicsObj.SetClipMask( MASK_PLAYERSOLID ); + } + + physicsObj.SetDebugLevel( g_debugMove.GetBool() ); + physicsObj.SetPlayerInput( usercmd, viewAngles ); + + // FIXME: physics gets disabled somehow + BecomeActive( TH_PHYSICS ); + + // If the player is dead then only run physics on new + // frames since articulated figures are not synchronized over the network + if ( health <= 0 ) { + if ( gameLocal.isNewFrame ) { + DeathPush(); + RunPhysics(); + } + } else { + RunPhysics(); + } + + // update our last valid AAS location for the AI + if ( !gameLocal.isMultiplayer ) { + SetAASLocation(); + } + + if ( spectating ) { + newEyeOffset = 0.0f; + } else if ( health <= 0 ) { + newEyeOffset = pm_deadviewheight.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { + newEyeOffset = pm_crouchviewheight.GetFloat(); + } else if ( IsInVehicle ( ) ) { + newEyeOffset = 0.0f; + } else { + newEyeOffset = pm_normalviewheight.GetFloat(); + } + + if ( EyeHeight() != newEyeOffset ) { + if ( spectating ) { + SetEyeHeight( newEyeOffset ); + } else { + // smooth out duck height changes + SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) ); + } + } + + if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) { + pfl.crouch = false; + pfl.onGround = ( influenceActive == INFLUENCE_LEVEL2 ); + pfl.onLadder = false; + pfl.jump = false; + } else { + pfl.crouch = physicsObj.IsCrouching(); + pfl.onGround = physicsObj.OnGround(); + pfl.onLadder = physicsObj.OnLadder(); + pfl.jump = physicsObj.HasJumped(); + + // check if we're standing on top of a monster and give a push if we are + idEntity *groundEnt = physicsObj.GetGroundEntity(); + if ( groundEnt && groundEnt->IsType( idAI::GetClassType() ) ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() < 0.1f ) { + vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2(); + vel.ToVec2().NormalizeFast(); + vel.ToVec2() *= pm_speed.GetFloat(); + } else { + // give em a push in the direction they're going + vel *= 1.1f; + } + physicsObj.SetLinearVelocity( vel ); + } + } + + if ( pfl.jump ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[2] = 200; + acc->dir[0] = acc->dir[1] = 0; + + // PMF_JUMP can get stuck when dead, which causes some bad spamming + // a cleaner fix would be to make sure PMF_JUMP gets cleared, but this close to release, that will do + // (the AF becomes the active physics object, so physics don't run on idPlayer_Physics and that flag never gets cleared) + if ( health > 0 ) { + // don't use the sound broadcasting facility in StartSound, we only need unreliable, and we need to filter for local client prediction + if ( gameLocal.isServer || IsLocalClient() ) { + StartSound( "snd_jump", (s_channelType)FC_SOUND, 0, false, NULL ); + } + + if ( gameLocal.isServer ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_EVENT ); + msg.WriteBits( gameLocal.GetSpawnId( this ), 32 ); + msg.WriteByte( EVENT_JUMP ); + gameLocal.SendUnreliableMessagePVS( msg, this, gameRenderWorld->PointInArea( physicsObj.GetOrigin() ) ); + } + } + } + + if ( pfl.onLadder ) { + int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE; + int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE; + + if ( old_rung != new_rung ) { + StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + UpdateIntentDir( ); + + BobCycle( pushVelocity ); + + if( !noclip ) { + CrashLand( oldOrigin, oldVelocity ); + } +} + +/* +================== +idPlayer::BiasIntentDir + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayer::BiasIntentDir( idVec3 newIntentDir, float prevBias ) { + if ( !newIntentDir.Compare( vec3_origin ) ) { + if ( intentDir.Compare( vec3_origin ) ) { + //initialize it + intentDir = newIntentDir; + } else { + intentDir = ((intentDir*prevBias)+newIntentDir)/(prevBias+1.0f); + float iDirLen = idMath::ClampFloat(0.0f,1024.0f,intentDir.Normalize()); + intentDir *= iDirLen; + } + } +} + +/* +============== +idPlayer::UpdateIntentDir +============== +*/ +void idPlayer::UpdateIntentDir( void ) { + idVec3 newIntentDir; + idVec3 viewDir = viewAxis[0]; + viewDir.z = 0; + viewDir.Normalize(); + float prevBias = 199.0f; + if ( intentDir.Compare( vec3_origin ) ) { + newIntentDir = viewDir*50.0f; + } else { + newIntentDir = viewDir*intentDir.Length(); + if ( flashlightOn ) { + // bias them more heavily to looking where I'm looking + prevBias = 19.0f; + } + } + if ( pfl.onGround ) { + idVec3 moveDir = physicsObj.GetLinearVelocity(); + if ( moveDir.x || moveDir.y ) { + // moving, too + moveDir.z = 0; + newIntentDir = moveDir; + prevBias = 39.0f; + } + } + BiasIntentDir( newIntentDir, prevBias ); + if ( ai_debugSquad.GetBool() ) { + idVec4 color = colorCyan; + if ( pfl.weaponFired ) { + color = colorRed; + } + gameRenderWorld->DebugArrow( color, GetPhysics()->GetOrigin(), GetPhysics()->GetOrigin() + intentDir*4.0f, 8, 0 ); + } +} + +/* +============== +idPlayer::UpdateHud +============== +*/ +void idPlayer::UpdateHud( void ) { + if ( !hud ) { + return; + } + + if ( !IsLocalClient() ) { + return; + } + + // FIXME: this is here for level ammo balancing to see pct of hits + hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() ); + if ( numProjectilesFired ) { + hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) ); + } else { + hud->SetStateString( "projectilepct", "Hit % 0.0" ); + } + + if ( isLagged && gameLocal.isMultiplayer && IsLocalClient() ) { + hud->SetStateString( "hudLag", "1" ); + } else { + hud->SetStateString( "hudLag", "0" ); + } +} + +/* +============== +idPlayer::UpdateDeathSkin +============== +*/ +void idPlayer::UpdateDeathSkin( bool state_hitch ) { + if ( !( gameLocal.isMultiplayer || g_testDeath.GetBool() ) ) { + return; + } + if ( health <= 0 ) { + if ( !doingDeathSkin && !deathSkinTime ) { + deathSkinTime = gameLocal.time + 1000; + deathStateHitch = state_hitch; + } + + // wait a bit before switching off the content + if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) { + SetCombatContents( false ); + deathClearContentsTime = 0; + } + } else { + renderEntity.noShadow = false; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; + } + } + UpdateVisuals(); + doingDeathSkin = false; + deathClearContentsTime = 0; + } +} + +/* +=============== +idPlayer::LoadDeferredModel +=============== +*/ +void idPlayer::LoadDeferredModel( void ) { + if( !modelDecl ) { + gameLocal.Warning( "idPlayer::LoadDeferredModel() - reloadModel without vaid modelDict\n" ); + return; + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + UpdateState(); + + if( weapon ) { + weapon->NetCatchup(); + } + + if( !modelDecl->skin.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( modelDecl->skin.c_str(), false ); + } + + SetModel( modelDecl->model ); + + if( !modelDecl->head.Length() ) { + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + } else { + SetupHead( modelDecl->head.c_str(), modelDecl->headOffset ); + } + + if( clientHead && health <= 0 ) { + // update death shader for new head + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ]; + clientHead.GetEntity()->GetRenderEntity()->noShadow = renderEntity.noShadow; + } + + if( powerUpSkin != NULL ) { + SetSkin( powerUpSkin ); + if( clientHead ) { + clientHead->SetSkin( powerUpSkin ); + } + } else { + SetSkin( skin ); + if( clientHead ) { + clientHead->SetSkin( headSkin ); + } + } +} + +/* +============== +idPlayer::Think + +Called every tic for each player +============== +*/ +void idPlayer::Think( void ) { + renderEntity_t *headRenderEnt; + + if ( talkingNPC ) { + if ( !talkingNPC.IsValid() ) { + talkingNPC = NULL; + } else { + idAI *talkingNPCAI = (idAI*)(talkingNPC.GetEntity()); + if ( !talkingNPCAI ) { + //wtf? + talkingNPC = NULL; + } else if ( talkingNPCAI->talkTarget != this || !talkingNPCAI->IsSpeaking() || DistanceTo( talkingNPCAI ) > 256.0f ) { + //forget about them, okay to talk to someone else now + talkingNPC = NULL; + } + } + } + + if ( !gameLocal.usercmds ) { + return; + } + +#ifdef _XENON + // change the crosshair if it's modified + if ( cursor && weapon && g_crosshairColor.IsModified() ) { + weapon->UpdateCrosshairGUI( cursor ); + cursor->HandleNamedEvent( "weaponChange" ); + g_crosshairColor.ClearModified(); + } +#endif + + // Dont do any thinking if we are in modview + if ( gameLocal.editors & EDITOR_MODVIEW || gameEdit->PlayPlayback() ) { + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + FreeModelDef(); + + if ( weapon ) { + weapon->GetWorldModel()->FreeModelDef(); + } + + if ( head.GetEntity() ) { + head->FreeModelDef(); + } + + if ( clientHead ) { + clientHead->FreeEntityDef(); + } + + return; + } + + if( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + gameEdit->RecordPlayback( usercmd, this ); + + // latch button actions + oldButtons = usercmd.buttons; + + // grab out usercmd + usercmd_t oldCmd = usercmd; + usercmd = gameLocal.usercmds[ IsFakeClient() ? MAX_CLIENTS : entityNumber ]; + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + HandleObjectiveInput(); + if ( objectiveSystemOpen ) { + HandleCheats(); + } else { + ClearCheatState(); + } + + aasSensor->Update(); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + // we need to let the camera think inside of this routine + CalculateRenderView(); + return; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // if this is the very first frame of the map, set the delta view angles + // based on the usercmd angles + if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) { + spawnAnglesSet = true; + SetViewAngles( spawnAngles ); + oldFlags = usercmd.flags; + } + + if ( gameLocal.inCinematic || influenceActive +#ifdef _XENON + || objectiveSystemOpen +#endif + ) { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + if( gameLocal.GetIsFrozen() && gameLocal.gameType == GAME_DEADZONE ) + { + usercmd.forwardmove = 0; + usercmd.rightmove = 0; + usercmd.upmove = 0; + } + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent ( &EV_Player_ZoomIn ); + } else { + ProcessEvent ( &EV_Player_ZoomOut ); + } + + if ( vehicleController.IsDriving( ) ) { +#ifdef _XENON + usercmdGen->SetSlowJoystick( zoom ? pm_zoomedSlow.GetInteger() : 100 ); +#else + cvarSystem->SetCVarInteger( "pm_isZoomed", zoom ? pm_zoomedSlow.GetInteger() : 0 ); +#endif + } + + } + + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + thinkFlags |= TH_PHYSICS; + RunPhysics(); + + if ( health > 0 ) { + TouchTriggers(); + } + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + LinkCombat(); + } else { + UpdateModel(); + } + + // I don't want to have to add this but if you are in a locked vehicle and you fail your objective, you won't be + // ejected from the vehicle (and I don't think we'd want that even though we do have the option of forcing it..) + // and since you are still in the vehicle, EvaluateControls (which covers the logic below for player usercmds) + // will never get called. + if ( pfl.objectiveFailed ) + { + if ( ( gameLocal.time > minRespawnTime && (usercmd.buttons & BUTTON_ATTACK)) || + gameLocal.time > maxRespawnTime ) + { + gameLocal.sessionCommand = "died"; + } + } + + return; + } + + // log movement changes for weapon bobbing effects + if ( usercmd.forwardmove != oldCmd.forwardmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove; + acc->dir[1] = acc->dir[2] = 0; + } + + if ( usercmd.rightmove != oldCmd.rightmove ) { + loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)]; + currentLoggedAccel++; + acc->time = gameLocal.time; + acc->dir[1] = usercmd.rightmove - oldCmd.rightmove; + acc->dir[0] = acc->dir[2] = 0; + } + + // freelook centering + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) { + centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 ); + } + + // if we have an active gui, we will unrotate the view angles as + // we turn the mouse movements into gui events + idUserInterface *gui = ActiveGui(); + if ( gui && gui != focusUI ) { + RouteGuiMouse( gui ); + } + + // set the push velocity on the weapon before running the physics + if ( weapon ) { + weapon->SetPushVelocity( physicsObj.GetPushedLinearVelocity() ); + } + + EvaluateControls(); + + +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END + + Move(); + + if ( !g_stopTime.GetBool() ) { + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers(); + } else if ( spectating && !noclip ) { + TouchTriggers( &idTrigger_Multi::GetClassType() ); + } + + // not done on clients for various reasons. don't do it on server and save the sound channel for other things + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && dynamicProtectionScale < 1.0f && gameLocal.time - lastDmgTime > 500 ) { + if ( dynamicProtectionScale < 1.0f ) { + dynamicProtectionScale += 0.05f; + } + if ( dynamicProtectionScale > 1.0f ) { + dynamicProtectionScale = 1.0f; + } + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + UpdateLocation(); + + // update player script + UpdateState(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + } + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeroson / camera view + CalculateRenderView(); + + if ( spectating ) { + UpdateSpectating(); + } else if ( health > 0 && !gameLocal.inCinematic ) { + UpdateWeapon(); + } + + UpdateAir(); + + UpdateHud(); + + UpdatePowerUps(); + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + if( entityNumber == gameLocal.localClientNum ) { + renderEntity.suppressLOD = 1; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + } else { + renderEntity.suppressLOD = 0; + if( headRenderEnt ) { + headRenderEnt->suppressLOD = 0; + } + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: get first person flashlight into this + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + } + + if ( !g_stopTime.GetBool() ) { + UpdateAnimation(); + + Present(); + + LinkCombat(); + } + + if ( !( thinkFlags & TH_THINK ) ) { + common->DPrintf( "player %d not thinking?\n", entityNumber ); + } + + if ( g_showEnemies.GetBool() ) { + idActor *ent; + int num = 0; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + common->DPrintf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() ); + gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() ); + num++; + } + common->DPrintf( "%d: enemies\n", num ); + } + + if ( !inBuyZonePrev ) + inBuyZone = false; + + inBuyZonePrev = false; +} + +/* +================= +idPlayer::RouteGuiMouse +================= +*/ +void idPlayer::RouteGuiMouse( idUserInterface *gui ) { + sysEvent_t ev; + const char *command; + + if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) { + ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY ); + command = gui->HandleEvent( &ev, gameLocal.time ); + oldMouseX = usercmd.mx; + oldMouseY = usercmd.my; + } +} + +bool idPlayer::CanZoom( void ) +{ + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + return weapon && weapon->CanZoom(); + } + + return weapon && weapon->CanZoom() && !weapon->IsReloading ( ); +} + +/* +================== +idPlayer::LookAtKiller +================== +*/ +void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) { + idVec3 dir; + + if ( attacker && attacker != this ) { + dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else if ( inflictor && inflictor != this ) { + dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + } else { + dir = viewAxis[ 0 ]; + } + + idAngles ang( 0, dir.ToYaw(), 0 ); + SetViewAngles( ang ); +} + +/* +============== +idPlayer::Kill +============== +*/ +void idPlayer::Kill( bool delayRespawn, bool nodamage ) { + if ( spectating ) { + SpectateFreeFly( false ); + } else if ( health > 0 ) { + godmode = false; + if ( !g_forceUndying.GetBool() ) { + undying = false; + } + if ( nodamage ) { + ServerSpectate( true ); + forceRespawn = true; + } else { + Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + if ( delayRespawn ) { + forceRespawn = false; + int delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + } + } +} + +/* +================== +idPlayer::Killed +================== +*/ +void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + float delay; + + assert( !gameLocal.isClient ); + + // stop taking knockback once dead + fl.noknockback = true; + if ( health < -999 ) { + health = -999; + } + + if ( pfl.dead ) { + pfl.pain = true; + return; + } + +// squirrel: Mode-agnostic buymenus + if ( gameLocal.isMultiplayer ) { + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) { + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP ) { + /// Remove the player's armor + inventory.armor = 0; + + /// Preserve this player's weapons at the state of his death, to be restored on respawn + carryOverCurrentWeapon = currentWeapon; + inventory.carryOverWeapons = inventory.weapons; + + if ( attacker ) { + idPlayer* killer = NULL; + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + if ( killer == this ) { + // Killed by self + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingSelf", 0 ); + killer->GiveCash( cashAward ); + } + else if ( gameLocal.IsTeamGame() && killer->team == team ) { + // Killed by teammate + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingTeammate", 0 ); + killer->GiveCash( cashAward ); + } else { + // Killed by enemy + float cashAward = (float) gameLocal.mpGame.mpBuyingManager.GetOpponentKillCashAward(); + killer->GiveCash( cashAward ); + } + } + } + } + } + } +// RITUAL END + + bool noDrop = false; + if ( inflictor + && inflictor->IsType( idTrigger_Hurt::GetClassType() ) + && inflictor->spawnArgs.GetBool( "nodrop" ) ) { + //don't drop weapon or items here, flag auto-returns. + noDrop = true; + } + + if ( !g_testDeath.GetBool() && !gameLocal.isMultiplayer ) { +#ifdef _XENON + playerView.Fade( colorBlack, MAX_RESPAWN_TIME_XEN_SP ); +#else + playerView.Fade( colorBlack, 12000 ); +#endif + } + + + pfl.dead = true; + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + + animator.ClearAllJoints(); + + if ( StartRagdoll() ) { + pm_modelView.SetInteger( 0 ); + minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME; + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } else { + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + delay = spawnArgs.GetFloat( "respawn_delay" ); + minRespawnTime = gameLocal.time + SEC2MS( delay ); + maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME; + } + + physicsObj.SetMovementType( PM_DEAD ); + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + StopSound( SND_CHANNEL_BODY2, false ); + + fl.takedamage = true; // can still be gibbed + + if ( weapon ) { // cnicholson: Fix for crash if player dies while in vehicle + weapon->OwnerDied(); // get rid of weapon + if ( !noDrop ) { + DropWeapon( ); // drop the weapon as an item + } + delete weapon; + } + + weapon = NULL; + currentWeapon = -1; + + if ( !g_testDeath.GetBool() ) { + LookAtKiller( inflictor, attacker ); + } + + if ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) { + idPlayer *killer = NULL; + int methodOfDeath = MAX_WEAPONS + isTelefragged; + + if ( attacker->IsType( idPlayer::Type ) ) { + killer = static_cast(attacker); + + lastKiller = killer; + if ( gameLocal.IsTeamGame() && killer->team == team ) { + // don't worry about team killers + lastKiller = NULL; + } + if ( killer == this ) { + // don't worry about yourself + lastKiller = NULL; + } + + if ( health < -20 || killer->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + gibDeath = true; + gibDir = dir; + gibsLaunched = false; + if( gameLocal.isMultiplayer && gameLocal.isListenServer && gameLocal.GetLocalPlayer() && gameLocal.GetLocalPlayer()->GetInstance() == instance ) { + ClientGib( dir ); + } + } + + if ( !isTelefragged ) { + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + methodOfDeath = static_cast(inflictor)->methodOfDeath; + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + } + } + + if ( methodOfDeath == -1 ) { + methodOfDeath = MAX_WEAPONS + isTelefragged; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if ( attacker->IsType( idWorldspawn::GetClassType() ) ) { +// RAVEN END + if ( lastImpulseTime > gameLocal.time && lastImpulsePlayer ) { + killer = lastImpulsePlayer; + } + } + + gameLocal.mpGame.PlayerDeath( this, killer, methodOfDeath ); + } else { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP ); + } + + if ( gameLocal.isMultiplayer && gameLocal.IsFlagGameType() ) { + if ( PowerUpActive( POWERUP_CTF_MARINEFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_marine_flag_world" ); + StopEffect( "fx_ctf_marine_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_STROGGFLAG ) ) { + RemoveClientModel( "mp_ctf_flag_pole" ); + RemoveClientModel( "mp_ctf_strogg_flag_world" ); + StopEffect( "fx_ctf_strogg_flag_world" ); + } else if ( PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + RemoveClientModel( "mp_ctf_one_flag" ); + } + + if( PowerUpActive( POWERUP_CTF_STROGGFLAG ) || PowerUpActive( POWERUP_CTF_MARINEFLAG ) || PowerUpActive( POWERUP_CTF_ONEFLAG ) ) { + idPlayer* killer = (idPlayer*)attacker; + if ( killer != NULL && killer->IsType( idPlayer::GetClassType() ) && killer != this ) { + if ( killer->team != team ) { + // killing the flag carrier gives killer double cash + killer->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerCashAward_killingOpponentFlagCarrier", 0 ) ); + } + } + statManager->FlagDropped( this, attacker ); + } + } + + DropPowerups(); + + ClearPowerUps(); + + UpdateVisuals(); + + // AI sometimes needs to respond to having killed someone. + // Note: Would it be better to make this a virtual funciton of... something? + aiManager.RemoveTeammate( this ); + + isChatting = false; +} + +/* +================= +CalcDamagePoints + +Calculates how many health and armor points will be inflicted, but +doesn't actually do anything with them. This is used to tell when an attack +would have killed the player, possibly allowing a "saving throw" +================= +*/ +void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ) { + int damage; + int armorSave; + float pDmgScale; + + damageDef->GetInt( "damage", "20", damage ); + damage = GetDamageForLocation( damage, location ); + + // optional different damage in team games + if ( gameLocal.isMultiplayer && gameLocal.IsTeamGame() && damageDef->GetInt( "damage_team" ) ) { + damage = damageDef->GetInt( "damage_team" ); + } + + idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast(attacker) : NULL; + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + switch ( g_skill.GetInteger() ) { + case 0: + damage = ceil(0.80f*(float)damage); + break; + case 2: + damage *= 1.7f; + break; + case 3: + damage *= 3.5f; + break; + default: + //damage *= 1.1f; reverted to 1.0 for default damage... as per Biessman's request. + break; + } + } + } + + damage = ceil(damageScale*(float)damage); + + pDmgScale = damageDef->GetFloat( "playerScale", "1" ); + damage = ceil(pDmgScale*(float)damage); + + // check for completely getting out of the damage + if ( !damageDef->GetBool( "noGod" ) ) { + // check for godmode + if ( godmode ) { + godmodeDamage += damage; + damage = 0; + } + } + + // reduce damage by handicap, except to self + if( player && player != this ) { + damage = ceil(player->handicap*(float)damage); + } + + // save some from armor + if ( !damageDef->GetBool( "noArmor" ) ) { + float armor_protection; + + armor_protection = ( gameLocal.isMultiplayer ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat(); + armorSave = ceil( damage * armor_protection ); + if ( armorSave >= inventory.armor ) { + armorSave = inventory.armor; + } + + if ( !damage ) { + armorSave = 0; + } else if ( armorSave >= damage ) { + armorSave = damage - 1; + damage = 1; + } else { + damage -= armorSave; + } + } else { + armorSave = 0; + } + + // check for team damage + if ( gameLocal.IsTeamGame() + && !gameLocal.serverInfo.GetBool( "si_teamDamage" ) + && !damageDef->GetBool( "noTeam" ) + && player + && player != this // you get self damage no matter what + && player->team == team ) { + damage = 0; + } + + *health = damage; + *armor = armorSave; +} + +/* +============ +Damage + +this entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: this=monster, inflictor=rocket, attacker=player + +dir direction of the attack for knockback in global space + +damageDef an idDict with all the options for damage effects + +inflictor, attacker, dir, and point can be NULL for environmental effects +============ +*/ +void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, int location ) { + idVec3 kick; + int damage; + int armorSave; + int knockback; + idVec3 damage_from; + float attackerPushScale; + + float modifiedDamageScale = damageScale; + + if ( !gameLocal.isMultiplayer ) { + if ( inflictor != gameLocal.world ) { + modifiedDamageScale *= ( 1.0f + gameLocal.GetDifficultyModifier() ); + } + } + + if ( forwardDamageEnt.IsValid() ) { + forwardDamageEnt->Damage( inflictor, attacker, dir, damageDefName, modifiedDamageScale, location ); + return; + } + + // damage is only processed on server + if ( gameLocal.isClient ) { + return; + } + + if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) { + // If in vehicle let it know that something is trying to hurt the invisible player + if ( IsInVehicle ( ) ) { + const idDict *damageDict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDict ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + // If the damage def is marked as a hazard then issue a warning to the vehicle + if ( damageDict->GetBool ( "hazard", "0" ) ) { + vehicleController.GetVehicle()->IssueHazardWarning ( ); + } + } + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + // MCG: player doesn't take friendly fire damage, except from self! + if ( !gameLocal.isMultiplayer && attacker != this ) { + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == team ) { + return; + } + } + + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->dict.GetBool( "ignore_player" ) ) { + return; + } + + if ( damageDef->dict.GetBool( "lightning_damage_effect" ) ) { + lightningEffects = 0; + lightningNextTime = gameLocal.GetTime(); + } + + // We pass in damageScale, because this function calculates a modified damageScale + // based on g_skill, and we don't want to compensate for skill level twice. + CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave ); + + // + // determine knockback + // + damageDef->dict.GetInt( "knockback", "0", knockback ); + if( gameLocal.isMultiplayer && gameLocal.IsTeamGame() ) { + damageDef->dict.GetInt( "knockback_team", va( "%d", knockback ), knockback ); + } + + knockback *= damageScale; + + if ( knockback != 0 && !fl.noknockback ) { + if ( !gameLocal.isMultiplayer && attacker == this ) { + //In SP, no knockback from your own stuff + knockback = 0; + } else { + if ( attacker != this ) { + attackerPushScale = 1.0f; + } else { + // since default attackerDamageScale is 0.5, default attackerPushScale should be 2 + damageDef->dict.GetFloat( "attackerPushScale", "2", attackerPushScale ); + } + + kick = dir; + + kick.Normalize(); + kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f; + + physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick ); + + // set the timer so that the player can't cancel out the movement immediately + physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) ); + } + } + + if ( damageDef->dict.GetBool( "burn" ) ) { + StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL ); + } else if ( damageDef->dict.GetBool( "no_air" ) ) { + if ( !armorSave && health > 0 ) { + StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL ); + } + } + + // give feedback on the player view and audibly when armor is helping + inventory.armor -= armorSave; + + if ( g_debugDamage.GetInteger() ) { + gameLocal.Printf( "client:%i health:%i damage:%i armor:%i\n", + entityNumber, health, damage, armorSave ); + } + + // move the world direction vector to local coordinates + ClientDamageEffects( damageDef->dict, dir, damage ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + + if( gameLocal.isMultiplayer ) { + idEntity* attacker = NULL; + + int methodOfDeath = -1; + if ( inflictor->IsType( idProjectile::GetClassType() ) ) { + methodOfDeath = static_cast(inflictor)->methodOfDeath; + attacker = static_cast(inflictor)->GetOwner(); + } else if ( inflictor->IsType( idPlayer::Type ) ) { + // hitscan weapon + methodOfDeath = static_cast(inflictor)->GetCurrentWeapon(); + attacker = inflictor; + } + + statManager->Damage( attacker, this, methodOfDeath, damage ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->dict.GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->dict.GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->dict.GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->dict.GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->dict.GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->dict.GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + // do the damage + if ( damage > 0 ) { + if ( !gameLocal.isMultiplayer ) { + if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) { + if ( gameLocal.time > lastDmgTime + 500 && dynamicProtectionScale > 0.25f ) { + dynamicProtectionScale -= 0.05f; + } + } + + if ( dynamicProtectionScale > 0.0f ) { + damage *= dynamicProtectionScale; + } + } + + if ( damage < 1 ) { + damage = 1; + } + + int oldHealth = health; + health -= damage; + + GAMELOG_ADD ( va("player%d_damage_taken", entityNumber ), damage ); + GAMELOG_ADD ( va("player%d_damage_%s", entityNumber, damageDefName), damage ); + + // Check undying mode + if ( !damageDef->dict.GetBool( "noGod" ) ) { + if ( undying ) { + if ( health < 1 ) { + health = 1; + } + } + } + + if ( health <= 0 ) { + + if ( health < -999 ) { + health = -999; + } + + isTelefragged = damageDef->dict.GetBool( "telefrag" ); + + lastDmgTime = gameLocal.time; + + Killed( inflictor, attacker, damage, dir, location ); + + if ( oldHealth > 0 ) { + float pushScale = 1.0f; + if ( inflictor && inflictor->IsType ( idPlayer::Type ) ) { + pushScale = static_cast(inflictor)->PowerUpModifier ( PMOD_PROJECTILE_DEATHPUSH ); + } + InitDeathPush ( dir, location, &damageDef->dict, pushScale ); + } + } else { + // force a blink + blink_time = 0; + + // let the anim script know we took damage + pfl.pain = Pain( inflictor, attacker, damage, dir, location ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + } + } else { + // don't accumulate impulses + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + } + + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = location; +} + +/* +===================== +idPlayer::CanPlayImpactEffect +===================== +*/ +bool idPlayer::CanPlayImpactEffect( idEntity* attacker, idEntity* target ) +{ + if ( !gameLocal.isMultiplayer && attacker->IsType( idAI::GetClassType()) && target->IsType( idPlayer::GetClassType())) + { + // don't display impact effects when marines on our team shoot us... + idPlayer *player = static_cast( target ); + idAI *ai = static_cast( attacker ); + if ( player->team == ai->team ) + { + return false; + } + } + + return idAFEntity_Base::CanPlayImpactEffect( attacker, target ); +} + +/* +===================== +idPlayer::AddDamageEffect +===================== +*/ +void idPlayer::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if( gameLocal.isMultiplayer ) { + if( ! cvarSystem->GetCVarBool("si_teamDamage") && inflictor && inflictor->IsType( idPlayer::GetClassType() ) && gameLocal.IsTeamGame() && ((idPlayer*)inflictor)->team == team ) { + return; + } + } + + idActor::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +=========== +idPlayer::Teleport +============ +*/ +void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) { + idVec3 org; + + if ( weapon ) { + weapon->LowerWeapon(); + } + + if ( !spectating ) { + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + } else { + SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) + idVec3( 0, 0, SPECTATE_RAISE ) ); + } + if ( !gameLocal.isMultiplayer && GetFloorPos( 16.0f, org ) ) { + SetOrigin( org ); + } + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + GetPhysics()->SetLinearVelocity( vec3_origin ); + SetViewAngles( angles ); + + legsYaw = 0.0f; + idealLegsYaw = 0.0f; + oldViewYaw = viewAngles.yaw; + leanVelocity = vec3_zero; + + if ( gameLocal.isMultiplayer ) { + playerView.Flash( colorWhite, 140 ); + } + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + UpdateVisuals(); + + teleportEntity = destination; + + if ( !gameLocal.isClient && !noclip && !spectating ) { + if ( gameLocal.isMultiplayer ) { + // kill anything at the new position or mark for kill depending on immediate or delayed teleport + gameLocal.KillBox( this, destination != NULL ); + } else { + // kill anything at the new position + gameLocal.KillBox( this, true ); + } + } +} + +/* +==================== +idPlayer::SetPrivateCameraView +==================== +*/ +void idPlayer::SetPrivateCameraView( idCamera *camView ) { + privateCameraView = camView; + if ( camView ) { + StopFiring(); + Hide(); + } else { + if ( !spectating ) { + Show(); + } + } +} + +/* +==================== +idPlayer::DefaultFov + +Returns the base FOV +==================== +*/ +float idPlayer::DefaultFov( void ) const { + float fov; + + fov = g_fov.GetFloat(); + if ( gameLocal.isMultiplayer ) { + if ( fov < 90.0f ) { + return 90.0f; + } else if ( fov > 175.0f ) { + return 175.0f; + } + } + + return fov; +} + +/* +==================== +idPlayer::CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +float idPlayer::CalcFov( bool honorZoom ) { + float fov; + + if ( fxFov ) { + return DefaultFov() + 10.0f + idMath::Cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f; + } + + if ( influenceFov ) { + return influenceFov; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } else { + if ( zoomFov.IsDone( gameLocal.time ) ) { + fov = ( honorZoom && zoomed && weapon ) ? weapon->GetZoomFov() : DefaultFov(); + } else { + fov = zoomFov.GetCurrentValue( gameLocal.time ); + } + } + + // bound normal viewsize + if ( fov < 1 ) { + fov = 1; + } else if ( fov > 179 ) { + fov = 179; + } + + return fov; +} + +/* +============== +idPlayer::GunTurningOffset + +generate a rotational offset for the gun based on the view angle +history in loggedViewAngles +============== +*/ +idAngles idPlayer::GunTurningOffset( void ) { + idAngles a; + + a.Zero(); + + if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) { + return a; + } + + idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles av, base; + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale, weaponAngleOffsetMax; + + weapon->GetAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax ); + + av = current; + + // calcualte this so the wrap arounds work properly + for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) { + idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ]; + + idAngles delta = a2 - current; + + if ( delta[1] > 180 ) { + delta[1] -= 360; + } else if ( delta[1] < -180 ) { + delta[1] += 360; + } + + av += delta * ( 1.0f / weaponAngleOffsetAverages ); + } + + a = ( av - current ) * weaponAngleOffsetScale; + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( a[i] < -weaponAngleOffsetMax ) { + a[i] = -weaponAngleOffsetMax; + } else if ( a[i] > weaponAngleOffsetMax ) { + a[i] = weaponAngleOffsetMax; + } + } + + return a; +} + +/* +============== +idPlayer::GunAcceleratingOffset + +generate a positional offset for the gun based on the movement +history in loggedAccelerations +============== +*/ +idVec3 idPlayer::GunAcceleratingOffset( void ) { + idVec3 ofs; + float weaponOffsetTime; + float weaponOffsetScale; + + ofs.Zero(); + + weapon->GetTimeOffsets( &weaponOffsetTime, &weaponOffsetScale ); + + int stop = currentLoggedAccel - NUM_LOGGED_ACCELS; + if ( stop < 0 ) { + stop = 0; + } + for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) { + loggedAccel_t *acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)]; + + float f; + float t = gameLocal.time - acc->time; + if ( t >= weaponOffsetTime ) { + break; // remainder are too old to care about + } + + f = t / weaponOffsetTime; + f = ( idMath::Cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f; + ofs += f * weaponOffsetScale * acc->dir; + } + + return ofs; +} + +/* +============== +idPlayer::CalculateViewWeaponPos + +Calculate the bobbing position of the view weapon +============== +*/ +void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) { + float scale; + float fracsin; + idAngles angles; + int delta; + + // CalculateRenderView must have been called first + const idVec3 &viewOrigin = firstPersonViewOrigin; + const idMat3 &viewAxis = firstPersonViewAxis; + + // the constant was -0.2 in quake3, but -.1 seems closest to the same visual effect in quake4 + float fovOffset = 0; + float curfov = g_fov.GetFloat(); + if ( g_weaponFovEffect.GetBool() && curfov > 90.0f ) + fovOffset = -0.1f * ( curfov - 90.0f ); + + // these cvars are just for hand tweaking before moving a value to the weapon def + idVec3 gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() + fovOffset ); + + const idPlayer* player = gameLocal.GetLocalPlayer(); + if (player && (this == player || player->spectating && player->spectator == this->entityNumber) ) { + gunpos += weapon->GetViewModelOffset(); + } else { + gunpos = weapon->GetViewModelOffset(); + } + + + // as the player changes direction, the gun will take a small lag + idVec3 gunOfs = GunAcceleratingOffset(); + origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis; + + // on odd legs, invert some angles + if ( noclip || 1 ) { + scale = 0; + } else if ( bobCycle & 128 ) { + scale = -xyspeed; + } else { + scale = xyspeed; + } + + // gun angles from bobbing + angles.roll = scale * bobfracsin * 0.005f + g_gun_roll.GetFloat(); + angles.yaw = scale * bobfracsin * 0.01f + g_gun_yaw.GetFloat(); + angles.pitch = xyspeed * bobfracsin * 0.005f + g_gun_pitch.GetFloat(); + + angles += weapon->GetViewModelAngles(); + + // gun angles from turning + if ( gameLocal.isMultiplayer ) { + idAngles offset = GunTurningOffset(); + offset *= g_mpWeaponAngleScale.GetFloat(); + angles += offset; + } else { + angles += GunTurningOffset(); + } + + idVec3 gravity = physicsObj.GetGravityNormal(); + +// RAVEN BEGIN +// abahr: when looking down, really large deflections cause back of weapons to show + float landChangeFrac = idMath::Lerp( 0.25f, 0.05f, viewAngles.ToForward() * gravity); +// RAVEN ABAHR + + // drop the weapon when landing after a jump / fall + delta = gameLocal.time - landTime; + if ( delta < LAND_DEFLECT_TIME ) { +// RAVEN BEGIN +// abahr: changed to use landChangeFrac + origin -= gravity * ( landChange*landChangeFrac * delta / LAND_DEFLECT_TIME ); + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin -= gravity * ( landChange*landChangeFrac * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME ); +// RAVEN END + } + + // speed sensitive idle drift + if ( !noclip ) { + scale = xyspeed * 0.5f + 40.0f; + fracsin = scale * idMath::Sin( MS2SEC( gameLocal.time ) ) * 0.01f; + angles.roll += fracsin; + angles.yaw += fracsin; + angles.pitch += fracsin; + } + + axis = angles.ToMat3() * viewAxis; +} + +/* +=============== +idPlayer::OffsetThirdPersonVehicleView +=============== +*/ +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world +void idPlayer::OffsetThirdPersonVehicleView( bool clip ) { +// RAVEN END + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + idVec3 origin; + idAngles angles, angles2; + idEntity* vehicle; + + assert ( IsInVehicle ( ) ); + + vehicle = vehicleController.GetVehicle(); + + origin = vehicle->GetRenderEntity()->origin; + angles = vehicle->GetRenderEntity()->axis.ToAngles(); + + angles.yaw += pm_thirdPersonAngle.GetFloat(); + angles.pitch += 25.0f; + +// angles.pitch += viewAngles.pitch; +// angles.yaw += viewAngles.yaw; + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * 8.0f; +// RAVEN END + + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + float speed = vehicle->GetPhysics()->GetLinearVelocity() * + vehicle->GetPhysics()->GetAxis()[0]; + + speed = idMath::Fabs( speed ); + speed *= pm_vehicleCameraSpeedScale.GetFloat(); + if( speed > pm_vehicleCameraScaleMax.GetFloat() ) + { + speed = pm_vehicleCameraScaleMax.GetFloat(); + } + + vehicleCameraDist += ( MS2SEC( gameLocal.GetMSec() ) * ( ( pm_vehicleCameraMinDist.GetFloat() + speed ) - vehicleCameraDist ) ); + + view -= vehicleCameraDist * renderView->viewaxis[ 0 ]; + +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + if ( clip ) { + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + const idVec3 clip_mins( -4.0f, -4.0f, -4.0f ); + const idVec3 clip_maxs( 4.0f, 4.0f, 4.0f ); + const idBounds clip_bounds( clip_mins, clip_maxs ); + idClipModel clipBounds( clip_bounds );// We clip when using a tram gun in the tram car +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + if ( trace.fraction != 1.0 ) + { + view = trace.endpos; +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32; + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, origin, view, &clipBounds, vehicle->GetPhysics()->GetAxis(), MASK_SOLID, vehicle, vehicle->GetBindMaster() ); + view = trace.endpos; + } + } +// RAVEN END + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) + { + focusDist = 1; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::OffsetThirdPersonView +=============== +*/ +void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) { + idVec3 view; + idVec3 focusAngles; + trace_t trace; + idVec3 focusPoint; + float focusDist; + float forwardScale, sideScale; + idVec3 origin; + idAngles angles; + idMat3 axis; + idBounds bounds; + + angles = viewAngles; + GetViewPos( origin, axis ); + + if ( angle ) { + angles.pitch = 0.0f; + } + + if ( angles.pitch > 45.0f ) { + angles.pitch = 45.0f; // don't go too far overhead + } + + focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE; + focusPoint.z += height; + view = origin; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * (8.0f + height); +// RAVEN END + + angles.pitch *= 0.5f; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale ); + view -= range * forwardScale * renderView->viewaxis[ 0 ]; + view += range * sideScale * renderView->viewaxis[ 1 ]; + + if ( clip ) { + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + if ( trace.fraction != 1.0f ) { + view = trace.endpos; +// RAVEN BEGIN +// abahr: taking into account gravity + view += physicsObj.GetGravityAxis()[2] * ( 1.0f - trace.fraction ) * 32.0f; +// RAVEN END + + // try another trace to this position, because a tunnel may have the ceiling + // close enough that this is poking out +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TraceBounds( this, trace, origin, view, bounds, MASK_SOLID, this ); +// RAVEN END + view = trace.endpos; + } + } + + + // select pitch to look at focus point from vieword + focusPoint -= view; + focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1.0f ) { + focusDist = 1.0f; // should never happen + } + + angles.pitch = - RAD2DEG( idMath::ATan( focusPoint.z, focusDist ) ); + angles.yaw -= angle; + + renderView->vieworg = view; + renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis(); + renderView->viewID = 0; +} + +/* +=============== +idPlayer::GetEyePosition +=============== +*/ +idVec3 idPlayer::GetEyePosition( void ) const { + idVec3 org; + + if ( WantSmoothing() ) { + org = predictedOrigin; + } else { + org = GetPhysics()->GetOrigin(); + } + + return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z ); +} + +/* +=============== +idPlayer::GetViewPos +=============== +*/ +void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const { + idAngles angles; + + // if dead, fix the angle and don't add any kick + if ( health <= 0 ) { + angles.yaw = viewAngles.yaw; + angles.roll = 40; + angles.pitch = -15; + axis = angles.ToMat3(); + origin = GetEyePosition(); + } else if ( IsInVehicle ( ) ) { + vehicleController.GetEyePosition ( origin, axis ); + + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + + origin += shakeOffset; + axis = (shakeAngleOffset + playerView.AngleOffset()).ToMat3() * axis; + } else { + idVec3 shakeOffset; + idAngles shakeAngleOffset; + idBounds relBounds(idVec3(0, 0, 0), idVec3(0, 0, 0)); + + playerView.ShakeOffsets( shakeOffset, shakeAngleOffset, relBounds ); + origin = GetEyePosition() + viewBob + shakeOffset; + angles = viewAngles + viewBobAngles + shakeAngleOffset + playerView.AngleOffset(); + + axis = angles.ToMat3() * physicsObj.GetGravityAxis(); + + // adjust the origin based on the camera nodal distance (eye distance from neck) + origin += physicsObj.GetGravityNormal() * g_viewNodalZ.GetFloat(); + origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat(); + } +} + +/* +=============== +idPlayer::CalculateFirstPersonView +=============== +*/ +void idPlayer::CalculateFirstPersonView( void ) { + if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) { + // Displays the view from the point of view of the "camera" joint in the player model + + idMat3 axis; + idVec3 origin; + idAngles ang; + + ang = viewBobAngles + playerView.AngleOffset(); + ang.yaw += viewAxis[ 0 ].ToYaw(); + + jointHandle_t joint = animator.GetJointHandle( "camera" ); + animator.GetJointTransform( joint, gameLocal.time, origin, axis ); + firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob; + firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis(); + } else { + // offset for local bobbing and kicks + GetViewPos( firstPersonViewOrigin, firstPersonViewAxis ); + } +} + +/* +================== +idPlayer::GetRenderView + +Returns the renderView that was calculated for this tic +================== +*/ +renderView_t *idPlayer::GetRenderView( void ) { + return renderView; +} + +/* +================== +idPlayer::SmoothenRenderView + +various situations where the view angles need smoothing: + +demo replay: + On a slow client with low fps multiple game frames are run in quick succession + inbetween rendered frames. As a result the usercmds are not recorded at fixed + time intervals but in small bursts. This routine interpolates the view angles + based on the real time at which the usercmds were recorded to make a demo + recorded on a slow client play back smoothly on a fast client. + +spectate follow? +================== +*/ +void idPlayer::SmoothenRenderView( bool firstPerson ) { + int d1, d2; + idAngles angles, anglesDelta, newAngles; + + if ( gameLocal.IsServerDemoPlaying() ) { + + d1 = usercmd.gameTime - demoViewAngleTime; + if ( d1 < 0 ) { + return; + } + d2 = usercmd.realTime - demoViewAngleTime; + if ( d2 <= 0 ) { + return; + } + if ( d1 >= d2 ) { + return; + } + + angles = renderView->viewaxis.ToAngles(); + + anglesDelta = angles - demoViewAngles; + anglesDelta.Normalize180(); + newAngles = demoViewAngles + ( (float) d1 / d2 ) * anglesDelta; + renderView->viewaxis = newAngles.ToMat3(); + + if ( usercmd.gameTime + gameLocal.msec > usercmd.realTime ) { + demoViewAngleTime = usercmd.realTime; + demoViewAngles = angles; + } + + if ( firstPerson ) { + // make sure the view weapon moves smoothly + firstPersonViewAxis = renderView->viewaxis; + } + } +} + +/* +================== +idPlayer::CalculateRenderView + +create the renderView for the current tic +================== +*/ +void idPlayer::CalculateRenderView( void ) { + int i; + float range; + + if ( !renderView ) { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM_AUTO(p0,this); +// RAVEN END + renderView = new renderView_t; + } + memset( renderView, 0, sizeof( *renderView ) ); + + // copy global shader parms + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ]; + } + renderView->globalMaterial = gameLocal.GetGlobalMaterial(); + renderView->time = gameLocal.time; + + // calculate size of 3D view + renderView->x = 0; + renderView->y = 0; + renderView->width = SCREEN_WIDTH; + renderView->height = SCREEN_HEIGHT; + renderView->viewID = 0; + + // check if we should be drawing from a camera's POV + if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) { + // get origin, axis, and fov + if ( privateCameraView ) { + privateCameraView->GetViewParms( renderView ); + } else { + gameLocal.GetCamera()->GetViewParms( renderView ); + } + } else { + bool cameraIsSet = false; + + // First try out any camera views that can possibly fail. + if( !cameraIsSet ){ + if ( g_stopTime.GetBool() ) { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + + if ( !pm_thirdPerson.GetBool() ) { + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } else if ( pm_thirdPerson.GetBool() && IsInVehicle ( ) ) { +// RAVEN BEGIN +// jnewquist: option to avoid clipping against world + OffsetThirdPersonVehicleView( pm_thirdPersonClip.GetBool() ); +// RAVEN END + SmoothenRenderView( false ); + } else if ( pm_thirdPerson.GetBool() ) { + OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() ); + SmoothenRenderView( false ); + } else if ( pm_thirdPersonDeath.GetBool() ) { + range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f; + OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false ); + SmoothenRenderView( false ); + } else { + renderView->vieworg = firstPersonViewOrigin; + renderView->viewaxis = firstPersonViewAxis; + SmoothenRenderView( true ); + // set the viewID to the clientNum + 1, so we can suppress the right player bodies and + // allow the right player view weapons + renderView->viewID = entityNumber + 1; + } + } + + // field of view + gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y ); + + } + + if ( renderView->fov_y == 0 ) { + common->Error( "renderView->fov_y == 0" ); + } + + if ( g_showviewpos.GetBool() ) { + gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() ); + } +} + +/* +================== +idPlayer::Event_EnableTarget +================== +*/ +void idPlayer::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +================== +idPlayer::Event_DisableTarget +================== +*/ +void idPlayer::Event_DisableTarget ( void ) { + fl.notarget = true; +} + +/* +================== +idPlayer::Event_GetViewPos +================== +*/ +void idPlayer::Event_GetViewPos( void ) { + idVec3 viewOrigin; + idMat3 viewAxis; + + GetViewPos(viewOrigin, viewAxis); + idThread::ReturnVector( viewOrigin ); +} + +/* +================== +idPlayer::Event_FinishHearingLoss +================== +*/ +void idPlayer::Event_FinishHearingLoss ( float fadeTime ) { + if ( fadeTime <= 0.0f ) { + StopSound ( SND_CHANNEL_DEMONIC, false ); + pfl.hearingLoss = false; + } else { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, 0.0f ); + } +} + +// RAVEN BEGIN +// twhitaker: added the event +/* +============= +idPlayer::Event_ApplyImpulse +============= +*/ +void idPlayer::Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ) { + GetPhysics()->ApplyImpulse( 0, point, impulse ); +} + +// mekberg: added Event_EnableObjectives +/* +============= +idPlayer::Event_EnableObjectives +============= +*/ +void idPlayer::Event_EnableObjectives ( void ) { + objectivesEnabled = true; +} + +// mekberg: added Event_DisableObjectives +/* +============= +idPlayer::Event_DisableObjectives +============= +*/ +void idPlayer::Event_DisableObjectives ( void ) { + // if it's open, it should be closed + if (objectiveSystemOpen ) { + ToggleObjectives(); + } + objectivesEnabled = false; +} + +// mekberg: added Event_AllowNewObjectives +void idPlayer::Event_AllowNewObjectives ( void ) { + showNewObjectives = true; +} + +// mekberg: added sethealth +/* +============= +idPlayer::Event_SetHealth +============= +*/ +void idPlayer::Event_SetHealth( float newHealth ) { + health = idMath::ClampInt( 1 , inventory.maxHealth, newHealth ); +} +/* +============= +idPlayer::Event_SetArmor +============= +*/ +void idPlayer::Event_SetArmor( float newArmor ) { + inventory.armor = idMath::ClampInt( 0 , inventory.maxarmor, newArmor ); +} + +/* +============= +idPlayer::Event_SetExtraProjPassEntity +============= +*/ +void idPlayer::Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ) { + extraProjPassEntity = _extraProjPassEntity; +} +// RAVEN END + +/* +============= +idPlayer::AddProjectilesFired +============= +*/ +void idPlayer::AddProjectilesFired( int count ) { + numProjectilesFired += count; +} + +/* +============= +idPlayer::AddProjectileHites +============= +*/ +void idPlayer::AddProjectileHits( int count ) { + numProjectileHits += count; +} + +/* +============= +idPlayer::TriggerHitSound +============= +*/ +void idPlayer::TriggerHitSound( bool armor ) { + if ( gameLocal.framenum <= lastHitFrame ) { + return; + } + + lastHitFrame = gameLocal.framenum; + lastHitArmor = armor; + + idUserInterface *cursor = idPlayer::cursor; + bool spectated = false; + + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( p && p->spectating && p->spectator == entityNumber ) { + cursor = p->GetCursorGUI(); + spectated = true; + } + + if ( cursor ) { + cursor->HandleNamedEvent( "weaponHit" ); + } + + // spectated so we get blips for a client we're following + // localClientNum check so listen server plays only for local player + if ( spectated || IsLocalClient() ) { + const char* sound = NULL; + + if ( armor ) { + spawnArgs.GetString( "snd_armorHit", "", &sound ); + } + if ( sound == NULL ) { + spawnArgs.GetString( "snd_weaponHit", "", &sound ); + } + if ( sound != NULL ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, sound ); + } + + if ( aimClientNum != -1 ) { + if ( mphud ) { + mphud->HandleNamedEvent( "aim_hit" ); + } + } + + } +} + +/* +============= +idPlayer::SetInfluenceLevel +============= +*/ +void idPlayer::SetInfluenceLevel( int level ) { + if ( level != influenceActive ) { + if ( level ) { + for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + // remove all projectiles + ent->PostEventMS( &EV_Remove, 0 ); + } + } + if ( weaponEnabled && weapon ) { + weapon->EnterCinematic(); + } + } else { + physicsObj.SetLinearVelocity( vec3_origin ); + if ( weaponEnabled && weapon ) { + weapon->ExitCinematic(); + } + } + influenceActive = level; + } +} + +/* +============= +idPlayer::SetInfluenceView +============= +*/ +void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) { + influenceMaterial = NULL; + influenceEntity = NULL; + influenceSkin = NULL; + if ( mtr && *mtr ) { + influenceMaterial = declManager->FindMaterial( mtr ); + } + if ( skinname && *skinname ) { + influenceSkin = declManager->FindSkin( skinname ); + if ( head.GetEntity() ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + } + UpdateVisuals(); + } + influenceRadius = radius; + if ( radius > 0.0f ) { + influenceEntity = ent; + } +} + +/* +============= +idPlayer::SetInfluenceFov +============= +*/ +void idPlayer::SetInfluenceFov( float fov ) { + influenceFov = fov; +} + +/* +================ +idPlayer::OnLadder +================ +*/ +bool idPlayer::OnLadder( void ) const { + return physicsObj.OnLadder(); +} + +/* +================== +idPlayer::Event_GetButtons +================== +*/ +void idPlayer::Event_GetButtons( void ) { + idThread::ReturnInt( usercmd.buttons ); +} + +/* +================== +idPlayer::Event_GetMove +================== +*/ +void idPlayer::Event_GetMove( void ) { + idVec3 move( usercmd.forwardmove, usercmd.rightmove, usercmd.upmove ); + idThread::ReturnVector( move ); +} + +/* +================ +idPlayer::Event_GetViewAngles +================ +*/ +void idPlayer::Event_GetViewAngles( void ) { + idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) ); +} + +/* +================ +idPlayer::Event_SetViewAngles +================ +*/ +void idPlayer::Event_SetViewAngles( const idVec3 & vec ) { + idAngles ang; + ang.Set( vec.z, vec.y, vec.x ); + SetViewAngles( ang ); +} + +/* +================== +idPlayer::Event_StopFxFov +================== +*/ +void idPlayer::Event_StopFxFov( void ) { + fxFov = false; +} + +/* +================== +idPlayer::StartFxFov +================== +*/ +void idPlayer::StartFxFov( float duration ) { + fxFov = true; + PostEventSec( &EV_Player_StopFxFov, duration ); +} + +/* +================== +idPlayer::Event_EnableWeapon +================== +*/ +void idPlayer::Event_EnableWeapon( void ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", 0 ); + Give( "weapon", spawnArgs.GetString( va( "def_weapon%d", 0 ) ) ); + hiddenWeapon = false; + weaponEnabled = true; + if ( weapon ) { + weapon->ExitCinematic(); + } + ShowCrosshair(); +} + +/* +================== +idPlayer::Event_DisableWeapon +================== +*/ +void idPlayer::Event_DisableWeapon( void ) { + hiddenWeapon = true; + weaponEnabled = false; + if ( weapon ) { + weapon->EnterCinematic(); + } + HideCrosshair(); +} + +/* +================== +idPlayer::Event_GetCurrentWeapon +================== +*/ +void idPlayer::Event_GetCurrentWeapon( void ) { + if ( currentWeapon >= 0 ) { + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ) ); + } else { + idThread::ReturnString( "" ); + } +} + +/* +================== +idPlayer::Event_GetPreviousWeapon +================== +*/ +void idPlayer::Event_GetPreviousWeapon( void ) { + if ( previousWeapon >= 0 ) { + int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon; + idThread::ReturnString( spawnArgs.GetString( va( "def_weapon%d", pw) ) ); + } else { + idThread::ReturnString( "def_weapon0" ); + } +} + +/* +================== +idPlayer::Event_SelectWeapon +================== +*/ +void idPlayer::Event_SelectWeapon( const char *weaponName ) { + int i; + int weaponNum; + + if ( gameLocal.isClient ) { + gameLocal.Warning( "Cannot switch weapons from script in multiplayer" ); + return; + } + + weaponNum = -1; + for( i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + + if ( !inventory.HasAmmo( weap ) ) { + return; + } + weaponNum = i; + break; + } + } + } + + if ( weaponNum < 0 ) { + gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName ); + return; + } + + hiddenWeapon = false; + idealWeapon = weaponNum; + + UpdateHudWeapon(); +} + +/* +================== +idPlayer::Event_GetAmmoData +================== +*/ +void idPlayer::Event_GetAmmoData( const char *ammoClass ) { + + idVec3 weaponAmmo; + + //ammo vector is this: current ammo count, max ammo count, and % + weaponAmmo.x = inventory.ammo[ inventory.AmmoIndexForAmmoClass( ammoClass) ]; + weaponAmmo.y = inventory.MaxAmmoForAmmoClass( this, ammoClass ); + + if( weaponAmmo.y == 0) + weaponAmmo.z = 0; + else + weaponAmmo.z = (float)(weaponAmmo.x / weaponAmmo.y); + + idThread::ReturnVector( weaponAmmo); +} + +/* +================== +idPlayer::Event_RefillAmmo +================== +*/ +void idPlayer::Event_RefillAmmo( void ) { + int a; + for ( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( weap && *weap ) { + a = inventory.AmmoIndexForWeaponIndex( i ); + inventory.ammo[ a ] = inventory.MaxAmmoForAmmoClass( this, rvWeapon::GetAmmoNameForIndex( a ) ); + } + } + } +} + +/* +================== +idPlayer::Event_AllowFallDamage +================== +*/ +void idPlayer::Event_AllowFallDamage( int toggle ) { + if( toggle ) { + pfl.noFallingDamage = false; + } else { + pfl.noFallingDamage = true; + } + +} +/* +================== +idPlayer::Event_GetWeaponEntity +================== +*/ +void idPlayer::Event_GetWeaponEntity( void ) { + idThread::ReturnEntity( weaponViewModel ); +} + +/* +================== +idPlayer::Event_HideDatabaseEntry +================== +*/ +void idPlayer::Event_HideDatabaseEntry ( void ) { + if ( hud ) { + hud->HandleNamedEvent( "closeDatabaseEntry" ); + } +} + +/* +================== +idPlayer::Event_ZoomIn +================== +*/ +void idPlayer::Event_ZoomIn ( void ) { + float currentFov; + float t; + + if ( zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming in while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else if ( weapon && this->weaponEnabled ) { + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t *= weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, weapon->GetZoomFov() ); + + zoomed = true; + if ( weapon->GetZoomGui() ) { + weapon->GetZoomGui()->HandleNamedEvent ( "zoomIn" ); + weaponViewModel->StartSound ( "snd_zoomin", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::Event_ZoomOut +================== +*/ +void idPlayer::Event_ZoomOut ( void ) { + float t; + float currentFov; + + if ( !zoomed ) { + return; + } + + if ( vehicleController.IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + rvVehiclePosition * position = vehicle ? vehicle->GetPosition( vehicleController.GetPosition() ) : 0; + rvVehicleWeapon * weapon = position ? position->GetActiveWeapon() : 0; + + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound ( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } else { + if( !weapon ) { + // this should only happen in fringe cases - zooming out while dead, etc + zoomFov.Init( gameLocal.time, 0, DefaultFov(), DefaultFov() ); + zoomed = false; + return; + } + + currentFov = CalcFov ( true ); + t = currentFov - weapon->GetZoomFov(); + t /= (DefaultFov() - weapon->GetZoomFov()); + t = (1.0f - t) * weapon->GetZoomTime(); + + zoomFov.Init( gameLocal.time, SEC2MS(t), currentFov, DefaultFov() ); + zoomed = false; + if ( weapon->GetZoomGui() ) { + weaponViewModel->StartSound( "snd_zoomout", SND_CHANNEL_ANY, 0, false, NULL ); + } + } +} + +/* +================== +idPlayer::TeleportDeath +================== +*/ +void idPlayer::TeleportDeath( int killer ) { + teleportKiller = killer; +} + +/* +================== +idPlayer::Event_ExitTeleporter +================== +*/ +void idPlayer::Event_ExitTeleporter( void ) { + idEntity *exitEnt; + float pushVel; + + // verify and setup + exitEnt = teleportEntity; + if ( !exitEnt ) { + common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber ); + return; + } + + pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" ); + + if ( gameLocal.isServer && !spectating ) { + ServerSendInstanceEvent( EVENT_EXIT_TELEPORTER, NULL, false, -1 ); + } + + SetPrivateCameraView( NULL ); + // setup origin and push according to the exit target + SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() ); + physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel ); + physicsObj.ClearPushedVelocity( ); + // teleport fx + playerView.Flash( colorWhite, 120 ); + + // clear the ik heights so model doesn't appear in the wrong place + walkIK.EnableAll(); + + UpdateVisuals(); + + gameLocal.PlayEffect( spawnArgs, "fx_teleport", GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3(), false, vec3_origin ); + + StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( teleportKiller != -1 ) { + // we got killed while being teleported + Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + teleportKiller = -1; + } else { + // kill anything that would have waited at teleport exit + gameLocal.KillBox( this ); + } + teleportEntity = NULL; +} + +/* +=============== +idPlayer::Event_DamageOverTimeEffect +=============== +*/ +void idPlayer::Event_DamageOverTimeEffect( int endTime, int interval, const char *damageDefName ) { + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) { + rvClientCrawlEffect* effect; + + // mwhitlock: Dynamic memory consolidation + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_MULTIPLE_FRAME); + effect = new rvClientCrawlEffect( gameLocal.GetEffect ( damageDef->dict, "fx_dot" ), GetWeaponViewModel(), interval ); + RV_POP_HEAP(); + + effect->Play ( gameLocal.time, false ); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) { + //post it again + PostEventMS( &EV_DamageOverTimeEffect, interval, endTime, interval, damageDefName ); + } + } +} + +/* +=============== +idPlayer::LocalClientPredictionThink +=============== +*/ +void idPlayer::LocalClientPredictionThink( void ) { + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ IsFakeClient() ? MAX_CLIENTS : entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + + if ( IsInVehicle( ) ) { + vehicleController.SetInput( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } + + AdjustSpeed(); + + UpdateViewAngles(); + +/* +// RAVEN BEGIN +// abahr + if( !noclip && !spectating ) { + UpdateGravity(); + } +// RAVEN END +*/ + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + Move(); + + // predict collisions with items + if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) { + TouchTriggers( &idItem::GetClassType() ); + } else if ( spectating && !noclip ) { + // predict teleports for specs + TouchTriggers( &idTrigger_Multi::GetClassType() ); + } + } + + // update GUIs, Items, and character interactions + UpdateFocus(); + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + if ( IsFakeClient() && spectating ) { + UpdateSpectating(); + } + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + UpdateHud(); + + if ( gameLocal.isNewFrame ) { + UpdatePowerUps(); + } + + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + + headRenderEnt->suppressSurfaceInViewID = entityNumber + 1; + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +=============== +idPlayer::NonLocalClientPredictionThink +=============== +*/ +#define LIMITED_PREDICTION 1 + +void idPlayer::NonLocalClientPredictionThink( void ) { + assert( !IsFakeClient() ); + + renderEntity_t *headRenderEnt; + + oldFlags = usercmd.flags; + oldButtons = usercmd.buttons; + + usercmd = gameLocal.usercmds[ entityNumber ]; + + buttonMask &= usercmd.buttons; + usercmd.buttons &= ~buttonMask; + + //jshepard: added this to make sure clients can see other clients and the host switching weapons + if ( idealWeapon != currentWeapon ) { + usercmd.buttons &= ~BUTTON_ATTACK; + } + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + if ( gameLocal.isNewFrame ) { + if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) { + PerformImpulse( usercmd.impulse ); + } + } + + if ( forceScoreBoard && forceScoreBoardTime && gameLocal.time > forceScoreBoardTime ) { + forceScoreBoardTime = 0; + forceScoreBoard = false; + } + scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard ); + + // zooming + bool zoom = (usercmd.buttons & BUTTON_ZOOM) && CanZoom(); + if ( zoom != zoomed ) { + if ( zoom ) { + ProcessEvent( &EV_Player_ZoomIn ); + } else { + ProcessEvent( &EV_Player_ZoomOut ); + } + } + +#if !LIMITED_PREDICTION + if ( IsInVehicle ( ) ) { + vehicleController.SetInput ( usercmd, viewAngles ); + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + // this may use firstPersonView, or a thirdPeoson / camera view + CalculateRenderView(); + + UpdateLocation(); + + if ( !fl.hidden ) { + UpdateAnimation(); + Present(); + } + + return; + } +#endif + + AdjustSpeed(); + + UpdateViewAngles(); + + if ( !isLagged ) { + // don't allow client to move when lagged + predictedUpdated = false; + // NOTE: only running on new frames causes prediction errors even when the input does not change! + if ( gameLocal.isNewFrame ) { + Move(); + } else { + PredictionErrorDecay(); + } + } + +#if defined( _XENON ) || !LIMITED_PREDICTION + // update GUIs, Items, and character interactions + UpdateFocus(); +#endif + + // service animations + if ( !spectating && !af.IsActive() ) { + UpdateConditions(); + UpdateAnimState(); + CheckBlink(); + } + + // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think + pfl.pain = false; + + if ( !af.IsActive() ) { + AdjustBodyAngles(); + } + + // calculate the exact bobbed view position, which is used to + // position the view weapon, among other things + CalculateFirstPersonView(); + + if ( !gameLocal.inCinematic && weaponViewModel && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) { + UpdateWeapon(); + } + + if ( gameLocal.isLastPredictFrame ) { + // this may use firstPersonView, or a thirdPerson / camera view + CalculateRenderView(); + + UpdateHud(); + UpdatePowerUps(); + } + +//#if !LIMITED_PREDICTION + UpdateDeathSkin( false ); + + UpdateDeathShader( deathStateHitch ); +//#endif + + if( gameLocal.isMultiplayer ) { + if ( clientHead.GetEntity() ) { + headRenderEnt = clientHead.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } else { + if ( head.GetEntity() ) { + headRenderEnt = head.GetEntity()->GetRenderEntity(); + } else { + headRenderEnt = NULL; + } + } + + if ( headRenderEnt ) { + // in MP, powerup skin overrides influence + if ( powerUpSkin ) { + headRenderEnt->customSkin = powerUpSkin; + } else if ( influenceSkin ) { + headRenderEnt->customSkin = influenceSkin; + } else { + headRenderEnt->customSkin = headSkin; + } + } + + // always show your own shadow + renderEntity.suppressLOD = 1; + if ( headRenderEnt ) { + headRenderEnt->suppressLOD = 1; + } + + DrawShadow( headRenderEnt ); + + // never cast shadows from our first-person muzzle flashes + // FIXME: flashlight too + renderEntity.suppressShadowInLightID = rvWeapon::WPLIGHT_MUZZLEFLASH * 100 + entityNumber; + if ( headRenderEnt ) { + headRenderEnt->suppressShadowInLightID = renderEntity.suppressShadowInLightID; + } + + if ( !gameLocal.inCinematic ) { + UpdateAnimation(); + } + + Present(); + + LinkCombat(); +} + +/* +================ +idPlayer::ClientPredictionThink +================ +*/ +void idPlayer::ClientPredictionThink( void ) { + // common code for both the local & non local clients + if ( reloadModel ) { + LoadDeferredModel(); + reloadModel = false; + } + + if ( entityNumber == gameLocal.GetDemoFollowClient() ) { + LocalClientPredictionThink(); + return; + } + + if ( IsLocalClient() ) { + LocalClientPredictionThink(); + return; + } + + assert( gameLocal.localClientNum >= 0 ); + idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum ); + if ( p && p->spectating && p->spectator == entityNumber ) { + LocalClientPredictionThink(); + return; + } + + NonLocalClientPredictionThink(); +} + +/* +================ +idPlayer::GetMasterPosition +================ +*/ +bool idPlayer::GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const { + if( !IsInVehicle() ) { + return idActor::GetMasterPosition( masterOrigin, masterAxis ); + } + + vehicleController.GetDriverPosition( masterOrigin, masterAxis ); + return true; +} + +/* +=============== +idPlayer::PredictionErrorDecay +=============== +*/ +void idPlayer::PredictionErrorDecay( void ) { + if ( predictedUpdated ) { + return; + } + + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + return; + } + + if ( gameLocal.framenum >= predictedFrame ) { + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + + if ( gameLocal.framenum == predictedFrame ) { + + predictionOriginError = predictedOrigin - renderOrigin; + predictionAnglesError = predictedAngles - viewAngles; + predictionAnglesError.Normalize180(); + predictionErrorTime = gameLocal.time; + + // skip doing error decay if the error is too high to be due to misprediction + // this would take care of stray cases where we fail to mark that no decaying should happen + if ( predictionOriginError.Length() > 64.0f ) { + if ( net_showPredictionError.GetInteger() == entityNumber ) { + renderSystem->DebugGraph( 1.0f, 0.0f, 1.0f, colorRed ); + } + idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis(); + idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis; + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + predictedFrame = gameLocal.framenum; + return; + } + + if ( net_showPredictionError.GetInteger() == entityNumber ) { + renderSystem->DebugGraph( predictionOriginError.Length(), 0.0f, 100.0f, colorGreen ); + renderSystem->DebugGraph( predictionAnglesError.Length(), 0.0f, 180.0f, colorBlue ); + } + } + + int t = gameLocal.time - predictionErrorTime; + float f = ( net_predictionErrorDecay.GetFloat() - t ) / net_predictionErrorDecay.GetFloat(); + if ( f > 0.0f && f < 1.0f ) { + predictedOrigin = renderOrigin + f * predictionOriginError; + predictedAngles = viewAngles + f * predictionAnglesError; + predictedAngles.Normalize180(); + } else { + predictedOrigin = renderOrigin; + predictedAngles = viewAngles; + } + + predictedFrame = gameLocal.framenum; + + } + + viewAngles = predictedAngles; + // adjust them now so they are right for the bound objects ( head and weapon ) + AdjustBodyAngles(); + + predictedUpdated = true; +} + +/* +=============== +idPlayer::WantSmoothing +=============== +*/ +bool idPlayer::WantSmoothing( void ) const { + if ( !gameLocal.isClient ) { + return false; + } + if ( net_predictionErrorDecay.GetFloat() <= 0.0f ) { + return false; + } + return true; +} + +/* +================ +idPlayer::GetPhysicsToVisualTransform +================ +*/ +bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + + if ( vehicleController.IsDriving() ) { + vehicleController.GetDriverPosition( origin, axis ); + origin.Zero(); + return true; + } + + PredictionErrorDecay(); + + // smoothen the rendered origin and angles of other clients + if ( gameLocal.framenum >= predictedFrame && WantSmoothing() ) { + + axis = idAngles( 0.0f, predictedAngles.yaw, 0.0f ).ToMat3(); + origin = ( predictedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose(); + + } else { + + axis = viewAxis; + origin = modelOffset; + + } + + return true; +} + +/* +================ +idPlayer::GetPhysicsToSoundTransform +================ +*/ +bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + idCamera *camera; + + if ( privateCameraView ) { + camera = privateCameraView; + } else { + camera = gameLocal.GetCamera(); + } + + if ( camera ) { + renderView_t view; + + memset( &view, 0, sizeof( view ) ); + camera->GetViewParms( &view ); + origin = view.vieworg; + axis = view.viewaxis; + return true; + } else { + return idActor::GetPhysicsToSoundTransform( origin, axis ); + } +} + +/* +================ +idPlayer::WriteToSnapshot +================ +*/ +void idPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const { + assert( !IsFakeClient() ); + + physicsObj.WriteToSnapshot( msg ); + WriteBindToSnapshot( msg ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] ); + msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] ); + msg.WriteShort( health ); + msg.WriteByte( inventory.armor ); + msg.WriteBits( lastDamageDef, gameLocal.entityDefBits ); + msg.WriteDir( lastDamageDir, 9 ); + msg.WriteShort( lastDamageLocation ); + msg.WriteBits( idealWeapon, -idMath::BitsForInteger( MAX_WEAPONS ) ); + msg.WriteBits( inventory.weapons, MAX_WEAPONS ); + msg.WriteBits( weaponViewModel.GetSpawnId(), 32 ); + msg.WriteBits( weaponWorldModel.GetSpawnId(), 32 ); + msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteBits( weaponGone, 1 ); + msg.WriteBits( isLagged, 1 ); + msg.WriteBits( isChatting, 1 ); + msg.WriteLong( connectTime ); + msg.WriteByte( lastKiller ? lastKiller->entityNumber : 255 ); + + if ( weapon ) { + msg.WriteBits( 1, 1 ); + weapon->WriteToSnapshot( msg ); + } else { + msg.WriteBits( 0, 1 ); + } + msg.WriteBits( inBuyZone, 1 ); + msg.WriteLong( (int)buyMenuCash ); +} + +/* +================ +idPlayer::ReadFromSnapshot +================ +*/ +void idPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) { + assert( !IsFakeClient() ); + + int i, oldHealth, newIdealWeapon, weaponSpawnId, weaponWorldSpawnId; + bool stateHitch; + int lastKillerEntity; + + if ( snapshotSequence - lastSnapshotSequence > 1 ) { + stateHitch = true; + } else { + stateHitch = false; + } + lastSnapshotSequence = snapshotSequence; + + oldHealth = health; + + physicsObj.ReadFromSnapshot( msg ); + ReadBindFromSnapshot( msg ); + deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f ); + deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f ); + health = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + lastDamageDef = msg.ReadBits( gameLocal.entityDefBits ); + lastDamageDir = msg.ReadDir( 9 ); + lastDamageLocation = msg.ReadShort(); + newIdealWeapon = msg.ReadBits( -idMath::BitsForInteger( MAX_WEAPONS ) ); + inventory.weapons = msg.ReadBits( MAX_WEAPONS ); + weaponSpawnId = msg.ReadBits( 32 ); + weaponWorldSpawnId = msg.ReadBits( 32 ); + int latchedSpectator = spectator; + spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) ); + if ( spectating && latchedSpectator != spectator ) { + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + + if ( this == gameLocal.GetLocalPlayer() ) { + // this is where the client updates their spectated player + if ( gameLocal.gameType == GAME_TOURNEY ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( GetArena() ); + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (spectator != arena.GetPlayers()[ 0 ]->entityNumber && spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_BRACKET ); + } else if( spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_ONE ); + } else if( spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } + + if ( gameLocal.entities[ spectator ] ) { + idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ spectator ] ); + p->UpdateHudWeapon( p->currentWeapon ); + if ( p->weapon ) { + p->weapon->SpectatorCycle(); + } + } + } + } + weaponGone = msg.ReadBits( 1 ) != 0; + isLagged = msg.ReadBits( 1 ) != 0; + isChatting = msg.ReadBits( 1 ) != 0; + connectTime = msg.ReadLong(); + lastKillerEntity = msg.ReadByte(); + if( lastKillerEntity >= 0 && lastKillerEntity < MAX_CLIENTS) { + lastKiller = static_cast(gameLocal.entities[ lastKillerEntity ]); + } else { + lastKiller = NULL; + } + + bool weaponChange = ( idealWeapon != newIdealWeapon ); + if ( gameLocal.clientAckSequence < clientIdealWeaponPredictFrame ) { + // we have predicted a weapon change, and sent a reliable message to the server about it + // but it has not been processed yet, so ignore what may be contradictory information here + weaponChange = false; + } else { + clientIdealWeaponPredictFrame = -1; + } + + if ( weaponChange ) { + if ( stateHitch ) { + weaponCatchup = true; + } + idealWeapon = newIdealWeapon; + StopFiring(); + UpdateHudWeapon(); + usercmd.buttons &= (~BUTTON_ATTACK); + clientIdealWeaponPredictFrame = -1; + } + + // Attach the world and view entities + if ( weaponWorldModel.GetSpawnId() != weaponWorldSpawnId || weaponViewModel.GetSpawnId() != weaponSpawnId ) { + SetWeapon( -1 ); + + if ( weaponWorldModel.SetSpawnId( weaponWorldSpawnId ) && weaponViewModel.SetSpawnId( weaponSpawnId ) ) { + weaponCatchup = true; + } + } + + // rjohnson: instance persistance information + if ( weaponWorldModel.IsValid() ) { + weaponWorldModel->fl.persistAcrossInstances = true; + weaponWorldModel->SetInstance( GetInstance() ); + } + if ( weaponViewModel.IsValid() ) { + weaponViewModel->fl.persistAcrossInstances = true; + weaponViewModel->SetInstance( GetInstance() ); + } + + // Clear the weapon for clients entering PVS + if ( fl.networkStale && currentWeapon != idealWeapon && entityNumber != gameLocal.localClientNum && weaponWorldModel.IsValid() && weaponViewModel.IsValid() ) { + // We've probably already mispredicted a few frames without the client. + // Clearing the weapon here (during snapshot reading) prevents + // the wrong weapon from getting ClientUnstale() + // Weapon_Combat will set the proper weapon. + SetWeapon( -1 ); + weaponCatchup = true; + } + + // If we have a weapon then update it from the snapshot, otherwise + // we just skip whatever it would have read if it were there + if ( msg.ReadBits( 1 ) ) { + if ( weapon ) { + weapon->ReadFromSnapshot( msg ); + } else { + rvWeapon::SkipFromSnapshot( msg ); + } + } + inBuyZone = msg.ReadBits( 1 ) != 0; + int cash = msg.ReadLong(); + if ( cash != (int)buyMenuCash ) { + buyMenuCash = (float)cash; + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + // no msg reading below this + + // if not a local client assume the client has all ammo types + // don't do this for server demos because they have the player states included + if ( !gameLocal.isRepeater && (entityNumber != gameLocal.GetDemoFollowClient()) && !IsLocalClient() && !IsSpectatedClient() ) { + for( i = 0; i < MAX_AMMO; i++ ) { + inventory.ammo[ i ] = -1; + } + } + + if ( oldHealth > 0 && health <= 0 ) { + if ( stateHitch ) { + // so we just hide and don't show a death skin + UpdateDeathSkin( true ); + } + // die + pfl.dead = true; + ClearPowerUps(); + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Dead", 4 ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Dead", 4 ); + animator.ClearAllJoints(); + StartRagdoll(); + physicsObj.SetMovementType( PM_DEAD ); + + if ( !stateHitch ) { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + const idDeclEntityDef* def = static_cast(declManager->DeclByIndex ( DECL_ENTITYDEF, lastDamageDef )); + if ( def ) { + // TODO: get attackers push scale? + InitDeathPush ( lastDamageDir, lastDamageLocation, &def->dict, 1.0f ); + ClientDamageEffects ( def->dict, lastDamageDir, ( oldHealth - health ) * 4 ); + } + + // gib them here + if ( health < -20 || ( lastKiller && lastKiller->PowerUpActive( POWERUP_QUADDAMAGE )) ) { + ClientGib( lastDamageDir ); + } + + if ( weapon ) { + weapon->OwnerDied(); + + // Get rid of the weapon now + delete weapon; + weapon = NULL; + currentWeapon = -1; + } + } else if ( oldHealth <= 0 && health > 0 ) { + // respawn + //common->DPrintf( "idPlayer::ReadFromSnapshot() - Player respawn detected for %d '%s' - re-enabling clip\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "" ); + + // this is the first time we've seen the player since we heard he died - he may have picked up + // some powerups since he actually spawned in, so restore those + int latchPowerup = inventory.powerups; + Init(); + inventory.powerups = latchPowerup; + StopRagdoll(); + SetPhysics( &physicsObj ); + physicsObj.EnableClip(); + SetCombatContents( true ); + } else if ( oldHealth - health > 2 && health > 0 ) { + if ( stateHitch ) { + lastDmgTime = gameLocal.time; + } else { + // damage feedback + const idDeclEntityDef *def = static_cast( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) ); + if ( def ) { + ClientDamageEffects ( def->dict, lastDamageDir, oldHealth - health ); + pfl.pain = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation ); + lastDmgTime = gameLocal.time; + } else { + common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef ); + } + } + } + + if ( oldHealth > 0 && health > 0 && IsHidden() && !spectating ) { + // ensure the client is shown after the initial spawn frame + Show(); + } + + if ( msg.HasChanged() ) { + UpdateVisuals(); + } +} + +/* +================ +idPlayer::WritePlayerStateToSnapshot +================ +*/ +void idPlayer::WritePlayerStateToSnapshot( int lastSnapshotFrame, idBitMsgDelta &msg ) const { + int i; + + // write out a flag if a hit sound was triggered since last snap + msg.WriteBits( lastHitFrame > lastSnapshotFrame ? 1 : 0, 1 ); + msg.WriteBits( lastHitArmor, 1 ); + + msg.WriteDeltaByte( 0, bobCycle ); + msg.WriteDeltaLong( 0, stepUpTime ); + msg.WriteDeltaFloat( 0.0f, stepUpDelta ); + + msg.WriteShort( inventory.weapons ); + msg.WriteByte( inventory.armor ); + msg.WriteShort( inventory.powerups ); + + for( i = 0; i < MAX_AMMO; i++ ) { + // send a value of -1 as the max positive value as we have ASYNC_PLAYER_INV_AMMO_BITS>0 + if ( inventory.ammo[i] == -1 ) { + msg.WriteBits( ( 1 << ASYNC_PLAYER_INV_AMMO_BITS ) - 1, ASYNC_PLAYER_INV_AMMO_BITS ); + } else { + msg.WriteBits( inventory.ammo[i], ASYNC_PLAYER_INV_AMMO_BITS ); + } + } + + for ( i = 0; i < POWERUP_MAX; i ++ ) { + msg.WriteLong( inventory.powerupEndTime[ i ] ); + } +} + +/* +================ +idPlayer::ReadPlayerStateFromSnapshot +================ +*/ +void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) { + int i, ammo; + + bool trigger = ( msg.ReadBits( 1 ) != 0 ); + bool armor = ( msg.ReadBits( 1 ) != 0 ); + if ( trigger ) { + TriggerHitSound( armor ); + } + + bobCycle = msg.ReadDeltaByte( 0 ); + stepUpTime = msg.ReadDeltaLong( 0 ); + stepUpDelta = msg.ReadDeltaFloat( 0.0f ); + + inventory.weapons = msg.ReadShort(); + inventory.armor = msg.ReadByte(); + inventory.powerups = msg.ReadShort(); + + for( i = 0; i < MAX_AMMO; i++ ) { + ammo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS ); + if ( gameLocal.time >= inventory.ammoPredictTime ) { + if ( ammo == ( 1 << ASYNC_PLAYER_INV_AMMO_BITS ) - 1 ) { + inventory.ammo[ i ] = -1; + } else { + inventory.ammo[ i ] = ammo; + } + } + } + + int powerup_max = POWERUP_MAX; + for ( i = 0; i < powerup_max; i ++ ) { + inventory.powerupEndTime[ i ] = msg.ReadLong(); + } + while ( i < POWERUP_MAX ) { + inventory.powerupEndTime[ i ] = 0; + i++; + } + + if ( gameLocal.IsMultiplayer() ) { + if ( (inventory.weapons&~oldInventoryWeapons) ) { + //added a weapon from inventory, bring up bar + UpdateHudWeapon(); + } + oldInventoryWeapons = inventory.weapons; + } +} + +/* +================ +idPlayer::ServerReceiveEvent +================ +*/ +bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) { + + if ( idEntity::ServerReceiveEvent( event, time, msg ) ) { + return true; + } + + // client->server events + switch ( event ) { + case EVENT_IMPULSE: { + int impulse = msg.ReadBits( IMPULSE_NUMBER_OF_BITS ); + serverReceiveEvent = true; // marking so we know if ACKs are needed + PerformImpulse( impulse ); + serverReceiveEvent = false; + return true; + } + case EVENT_EMOTE: { + // forward the emote on to all clients except the one that sent it to us + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, entityNumber ); + + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + default: { + return false; + } + } +} + +/* +=============== +idPlayer::CheckAckReply +=============== +*/ +void idPlayer::CheckAckReply( void ) const { + assert( !gameLocal.isClient ); + if ( serverReceiveEvent ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_EVENT_ACK ); + outMsg.WriteLong( gameLocal.framenum ); + networkSystem->ServerSendReliableMessage( entityNumber, outMsg ); + } +} + +/* +================ +idPlayer::ClientReceiveEvent +================ +*/ +bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + int powerup; + bool start; + + switch ( event ) { + case EVENT_EXIT_TELEPORTER: + Event_ExitTeleporter(); + return true; + case EVENT_ABORT_TELEPORTER: + SetPrivateCameraView( NULL ); + return true; + case EVENT_POWERUP: { + powerup = msg.ReadShort(); + start = ( msg.ReadBits( 1 ) != 0 ); + if ( start ) { + bool team = ( msg.ReadBits( 1 ) != 0 ); + GivePowerUp( powerup, 0, team ); + } else { + ClearPowerup( powerup ); + } + + return true; + } + case EVENT_SPECTATE: { + bool spectate = ( msg.ReadBits( 1 ) != 0 ); + // force to spectator if we got this event about a client in a different + // instance + Spectate( spectate ); + + // spectate might re-link clip for stale players, so re-call ClientStale if we're stale + if ( fl.networkStale ) { + ClientStale(); + } + return true; + } + case EVENT_ADD_DAMAGE_EFFECT: { + if ( spectating ) { + // if we're spectating, ignore + // happens if the event and the spectate change are written on the server during the same frame (fraglimit) + return true; + } + return idActor::ClientReceiveEvent( event, time, msg ); + } + case EVENT_EMOTE: { + // Set the emote locally + SetEmote( (playerEmote_t)msg.ReadByte() ); + + return true; + } + case EVENT_JUMP: { + // jumps for the local client are predicted + if ( IsLocalClient() ) { + return true; + } + StartSound( "snd_jump", (s_channelType)FC_SOUND, 0, false, NULL ); + return true; + } + default: { + return idActor::ClientReceiveEvent( event, time, msg ); + } + } +} + +/* +================ +idPlayer::Hide +================ +*/ +void idPlayer::Hide( void ) { + idActor::Hide(); + + if ( weapon ) { + weapon->HideWorldModel( ); + } +} + +/* +================ +idPlayer::Show +================ +*/ +void idPlayer::Show( void ) { + idActor::Show(); + + if ( weapon ) { + weapon->ShowWorldModel( ); + } +} + +/* +=============== +idPlayer::ShowTip +=============== +*/ +void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) { + if ( tipUp ) { + return; + } + hud->SetStateString( "tip", tip ); + hud->SetStateString( "tiptitle", title ); + hud->HandleNamedEvent( "tipWindowUp" ); + if ( autoHide ) { + PostEventSec( &EV_Player_HideTip, 5.0f ); + } + tipUp = true; +} + +/* +=============== +idPlayer::HideTip +=============== +*/ +void idPlayer::HideTip( void ) { + hud->HandleNamedEvent( "tipWindowDown" ); + tipUp = false; +} + +/* +=============== +idPlayer::Event_HideTip +=============== +*/ +void idPlayer::Event_HideTip( void ) { + HideTip(); +} + +/* +=============== +idPlayer::ShowObjective +=============== +*/ +void idPlayer::ShowObjective( const char *obj ) { + objectiveSystem->HandleNamedEvent( obj ); + objectiveUp = true; +} + + +/* +=============== +idPlayer::HideObjective +=============== +*/ +void idPlayer::HideObjective( void ) { + objectiveSystem->HandleNamedEvent( "closeObjective" ); + objectiveUp = false; +} + +/* +=============== +idPlayer::SetSpectateOrigin +=============== +*/ +void idPlayer::SetSpectateOrigin( void ) { + idVec3 neworig; + + neworig = GetPhysics()->GetOrigin(); + neworig[ 2 ] += EyeHeight(); + neworig[ 2 ] += 25; + SetOrigin( neworig ); +} + +/* +=============== +idPlayer::RemoveWeapon +=============== +*/ +void idPlayer::RemoveWeapon( const char *weap ) { + if ( weap && *weap ) { + inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 ); + } +} + +/* +=============== +idPlayer::CanShowWeaponViewmodel +=============== +*/ +bool idPlayer::CanShowWeaponViewmodel( void ) const { + return showWeaponViewModel; +} + +/* +=============== +idPlayer::SetLevelTrigger +=============== +*/ +void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) { + if ( levelName && *levelName && triggerName && *triggerName ) { + idLevelTriggerInfo lti; + lti.levelName = levelName; + lti.triggerName = triggerName; + inventory.levelTriggers.Append( lti ); + } +} + + +/* +=============== +idPlayer::Event_LevelTrigger +=============== +*/ +void idPlayer::Event_LevelTrigger( void ) { + idStr mapName = gameLocal.GetMapName(); + mapName.StripPath(); + mapName.StripFileExtension(); + for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) { + if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){ + idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName ); + if ( ent ) { + ent->PostEventMS( &EV_Activate, 1, this ); + } + } + } +} + +/* +================ +idPlayer::ToggleFlashlight +================ +*/ +void idPlayer::ToggleFlashlight ( void ) { + // Dead people can use flashlights +// RAVEN BEGIN +// mekberg: check to see if the weapon is enabled. + if ( health <= 0 || !weaponEnabled ) { + return; + } +// RAVEN END + + int flashlightWeapon = currentWeapon; + if ( !spawnArgs.GetBool( va( "weapon%d_flashlight", flashlightWeapon ) ) ) { + // TODO: find the first flashlight weapon that has ammo starting at the bottom + for( flashlightWeapon = MAX_WEAPONS - 1; flashlightWeapon >= 0; flashlightWeapon-- ) { + if ( inventory.weapons & ( 1 << flashlightWeapon ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", flashlightWeapon ) ); + int ammo = inventory.ammo[inventory.AmmoIndexForWeaponClass ( weap ) ]; + + if ( !ammo ) { + continue; + } + + if ( spawnArgs.GetBool ( va ( "weapon%d_flashlight", flashlightWeapon ) ) ) { + break; + } + } + } + + // Couldnt find flashlight + if ( flashlightWeapon < 0 ) { + return; + } + } + + // If the current weapon isnt the flashlight then always force the flashlight on + if ( flashlightWeapon != idealWeapon ) { + flashlightOn = true; + idealWeapon = flashlightWeapon; + // Inform the weapon to toggle the flashlight, this will eventually cause the players + // Flashlight method to be called + } else if ( weapon ) { + weapon->Flashlight ( ); + } +} + +/* +================ +idPlayer::Flashlight +================ +*/ +void idPlayer::Flashlight ( bool on ) { + flashlightOn = on; +} + +/* +================ +idPlayer::DamageFeedback +================ +*/ +void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + + assert( !gameLocal.isClient ); + + //rvTramCars weren't built on the idActor inheritance hierarchy but need to be treated like one when shot. + //TODO: Maybe add a key to entity flags that will allow them to be shot as actors even if they aren't actors? + if ( !victim || ( !victim->IsType( idActor::GetClassType() ) && !victim->IsType( rvTramCar::GetClassType() ) ) || victim->health <= 0 ) { + return; + } + + bool armorHit = false; + + if ( gameLocal.isMultiplayer && victim->IsType( idPlayer::GetClassType() ) ) { + if ( this == victim ) { + // no feedback for self hits + return; + } + idPlayer *p = static_cast< idPlayer * >( victim ); + if ( gameLocal.IsTeamGame() && p->team == team ) { + // no feedback for team hits + return; + } + if ( p->inventory.armor > 0 ) { + armorHit = true; + } + } + + TriggerHitSound( armorHit ); +} + +/* +============== +idPlayer::GetWeaponDef +============== +*/ +const idDeclEntityDef* idPlayer::GetWeaponDef ( int weaponIndex ) { + if ( cachedWeaponDefs[weaponIndex] ) { + return cachedWeaponDefs[weaponIndex]; + } + + idStr weapon; + weapon = spawnArgs.GetString ( va("def_weapon%d", weaponIndex ) ); + if ( !weapon.Length() ) { + return NULL; + } + + cachedWeaponDefs[weaponIndex] = gameLocal.FindEntityDef ( weapon, false ); + if ( !cachedWeaponDefs[weaponIndex] ) { + gameLocal.Error( "Could not find weapon definition '%s'", weapon.c_str() ); + } + + return cachedWeaponDefs[weaponIndex]; +} + +/* +============== +idPlayer::GetPowerupDef + +Returns the powerup dictionary for the given powerup index. The dictionary is cached to ensure a +speedy retrieval after the first call. +============== +*/ +const idDeclEntityDef* idPlayer::GetPowerupDef ( int powerupIndex ) { + const idDict* types; + int i; + int num; + + if ( cachedPowerupDefs[powerupIndex] ) { + return cachedPowerupDefs[powerupIndex]; + } + + types = gameLocal.FindEntityDefDict( "powerup_types", false ); + if ( !types ) { + gameLocal.Error( "Could not find entity definition for 'powerup_types'" ); + } + + num = types->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + const idKeyValue* kv; + kv = types->GetKeyVal( i ); + if ( atoi(kv->GetValue()) == powerupIndex ) { + cachedPowerupDefs[powerupIndex] = gameLocal.FindEntityDef ( kv->GetKey(), false ); + if ( !cachedPowerupDefs[powerupIndex] ) { + gameLocal.Error( "Could not find powerup definition '%s'", kv->GetKey().c_str() ); + } + return cachedPowerupDefs[powerupIndex]; + } + } + + gameLocal.Error( "Could not find powerup definition '%d'", powerupIndex ); + + return NULL; +} + +/* +============== +idPlayer::discoverSecretArea + +Announces a secret area and increases the secret area tally +============== +*/ +void idPlayer::DiscoverSecretArea(const char* _description) { + + //increment the secret area tally + inventory.secretAreasDiscovered++; +} + +/* +============== +idPlayer::StartBossBattle + +Starts a boss battle with the given entity. During a boss battle the health of the boss +will be displayed on the HUD +============== +*/ +void idPlayer::StartBossBattle ( idEntity* enemy ) { + bossEnemy = enemy; + idUserInterface *hud_ = GetHud(); + if ( hud_ ) { + hud_->SetStateInt ( "boss_maxhealth", enemy->health ); + hud_->HandleNamedEvent ( "showBossBar" ); + } +} + +/* +===================== +idPlayer::SetInitialHud +===================== +*/ +void idPlayer::SetInitialHud ( void ) { + if ( !mphud || !gameLocal.isMultiplayer || gameLocal.GetLocalPlayer() != this ) { + return; + } + + mphud->SetStateInt( "gametype", gameLocal.gameType ); + + if( hud ) { + hud->SetStateInt( "gametype", gameLocal.gameType ); + } + + mphud->HandleNamedEvent( "InitHud" ); + mphud->HandleNamedEvent( "TeamChange" ); + + if( gameLocal.IsFlagGameType() ) { + mphud->SetStateFloat( "ap", gameLocal.mpGame.assaultPoints.Num() ); + + for( int i = 0; i < TEAM_MAX; i++ ) { + mphud->SetStateInt( "team", i ); + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_DROPPED ) { + mphud->HandleNamedEvent( "flagDrop" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN ) { + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_MARINE ) { + mphud->SetStateInt( "team", TEAM_MARINE ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_TAKEN_STROGG ) { + mphud->SetStateInt( "team", TEAM_STROGG ); + mphud->HandleNamedEvent( "flagTaken" ); + } else if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( i ) == FS_AT_BASE ) { + mphud->SetStateInt( "team", i ); + mphud->HandleNamedEvent( "flagReturn" ); + } + } + + for( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + mphud->SetStateFloat( "apindex", i ); + //mphud->SetStateInt( "apteam", ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetAPOwner( i ) ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "APCaptured" ); + } + } + + mphud->StateChanged ( gameLocal.time ); +} + +void idPlayer::RemoveClientModel ( const char *entityDefName ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + if ( !idStr::Icmp ( ( static_cast ( cent ) )->GetClassname(), entityDefName ) ) { + cent->Unbind ( ); + delete cent; + } + } + } +} + +rvClientEntityPtr idPlayer::AddClientModel ( const char* entityDefName, const char* shaderName ) { + rvClientEntityPtr ptr; + ptr = NULL; + + if ( entityDefName == NULL ) { + return ptr; + } + + const idDict* entityDef = gameLocal.FindEntityDefDict ( entityDefName, false ); + + if ( entityDef == NULL ) { + return ptr; + } + + rvClientModel *newModel = NULL; + + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientModel" ); + + if( newModel == NULL ) { + return ptr; + } + idMat3 rotation; + rotation = entityDef->GetAngles( "angles" ).ToMat3(); + newModel->SetAxis( rotation ); + + newModel->SetOrigin( entityDef->GetVector( "origin" ) * rotation ); + + newModel->Bind ( this, animator.GetJointHandle( entityDef->GetString ( "joint" ) ) ); + + newModel->SetCustomShader ( shaderName ); + newModel->GetRenderEntity()->suppressSurfaceInViewID = entityNumber + 1; + newModel->GetRenderEntity()->noSelfShadow = true; + newModel->GetRenderEntity()->noShadow = true; + + ptr = newModel; + + return ptr; +} + +void idPlayer::RemoveClientModels ( void ) { + rvClientEntity* cent; + rvClientEntity* next; + + for( cent = clientEntities.Next(); cent != NULL; cent = next ) { + next = cent->bindNode.Next(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( cent->IsType ( rvClientModel::GetClassType() ) ) { +// RAVEN END + cent->Unbind ( ); + delete cent; + } + } +} + +/* +===================== +idPlayer::ClientGib() +ddynerman: Spawns client side gibs around this player +===================== +*/ +void idPlayer::ClientGib( const idVec3& dir ) { + + if( !spawnArgs.GetBool( "gib" ) ) { + return; + } + + int i; + idVec3 entityCenter, velocity; + idList list; + + + // hide the player + SetSkin( gibSkin ); + + //and the head + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead->UnlinkCombat(); + delete clientHead; + } + } else { + if ( head.GetEntity() ) { + head.GetEntity()->Hide(); + } + } + + // blow out the gibs in the given direction away from the center of the entity + + + // spawn gib client models + rvClientMoveable::SpawnClientMoveables( this, "clientgib", &list ); + + entityCenter = GetPhysics()->GetAbsBounds().GetCenter(); + for ( i = 0; i < list.Num(); i++ ) { + list[i]->GetPhysics()->SetContents( CONTENTS_CORPSE ); + // we don't want collision on gibs + //list[i]->GetPhysics()->SetClipMask( CONTENTS_SOLID ); + velocity = list[i]->GetPhysics()->GetAbsBounds().GetCenter() - entityCenter; + velocity.NormalizeFast(); + velocity += ( i & 1 ) ? dir : -dir; + + list[i]->GetPhysics()->ApplyImpulse( 0, list[i]->GetPhysics()->GetOrigin(), velocity * ( 25000.0f + ( gameLocal.random.RandomFloat() * 50000.0f)) ); +// list[i]->GetPhysics()->SetLinearVelocity( velocity * ( 25.0f + ( gameLocal.random.RandomFloat() * 300.0f))); + list[i]->GetPhysics()->SetAngularVelocity( velocity * ( -250.0f + ( gameLocal.random.RandomFloat() * 500.0f))); + + list[i]->GetRenderEntity()->noShadow = true; + list[i]->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + + list[i]->PostEventMS( &CL_FadeOut, SEC2MS( 4.0f ), SEC2MS( 2.0f ) ); + } + + + //play gib fx + gameLocal.PlayEffect( spawnArgs, "fx_gib", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + // gibs are PVS agnostic. If we gib a player outside of our PVS, set the oldHealth + // to below 0 so when this player re-appears in our snap we respawn him + if( gameLocal.isClient && gameLocal.GetLocalPlayer() && health > 0 ) { + health = -100; + } +} + +/* +===================== +idPlayer::CanDamage +===================== +*/ +bool idPlayer::CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ) { + if( gameLocal.isMultiplayer && health <= 0 ) { + return false; + } + + return idActor::CanDamage( origin, damagePoint, ignoreEnt ); +} + +/* +===================== +idPlayer::ClientDamageEffects + +===================== +*/ +void idPlayer::ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ) { + idVec3 from; + idVec3 localDir; + float fadeDB; + + // Only necessary on clients + if ( gameLocal.isMultiplayer && !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + from = dir; + from.Normalize(); + viewAxis.ProjectVector( from, localDir ); + + if ( damage ) { +// RAVEN BEGIN +// jnewquist: Controller rumble + idPlayer *p = gameLocal.GetLocalPlayer(); + + if ( p && ( p == this || ( p->spectating && p->spectator == entityNumber ) ) ) { + playerView.DamageImpulse( localDir, &damageDef, damage ); + } +// RAVEN END + } + + // Visual effects + if ( health > 0 && damage ) { + // Let the hud know about the hit + if ( hud ) { + hud->SetStateFloat ( "hitdir", localDir.ToAngles()[YAW] + 180.0f ); + hud->HandleNamedEvent ( "playerHit" ); + } + } + + // Sound effects + if ( damageDef.GetFloat ( "hl_volumeDB", "-40", fadeDB ) ) { + float fadeTime; + + fadeTime = 0.0f; + if ( !pfl.hearingLoss ) { + const char* fade; + + fadeTime = damageDef.GetFloat ( "hl_fadeOutTime", ".25" ); + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); + + pfl.hearingLoss = true; + + // sound overlayed? + if ( damageDef.GetString ( "snd_hl", "", &fade ) && *fade ) { + StartSoundShader ( declManager->FindSound ( fade ), SND_CHANNEL_DEMONIC, 0, false, NULL ); + } + } + + fadeTime += damageDef.GetFloat ( "hl_time", "1" ); + + CancelEvents ( &EV_Player_FinishHearingLoss ); + PostEventSec ( &EV_Player_FinishHearingLoss, fadeTime, damageDef.GetFloat ( "hl_fadeInTime", ".25" ) ); + } +} + +/* +===================== +idPlayer::GetDebugInfo +===================== +*/ +void idPlayer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idActor::GetDebugInfo ( proc, userData ); + proc ( "idPlayer", "inventory.armor", va("%d", inventory.armor ), userData ); + proc ( "idPlayer", "inventory.weapons", va("%d", inventory.weapons ), userData ); + proc ( "idPlayer", "inventory.powerups", va("%d", inventory.powerups ), userData ); +} + + + +// RAVEN END + +/* +===================== +idPlayer::ApplyImpulse +===================== +*/ +void idPlayer::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if( !ent) { + gameLocal.Warning( "idPlayer::ApplyImpulse called with null entity as instigator."); + return; + } + + lastImpulsePlayer = NULL; + lastImpulseTime = gameLocal.time + 1000; + + if( ent->IsType( idPlayer::Type ) && ent != this ) { + lastImpulsePlayer = static_cast(ent); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + } else if( ent->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + idEntity* owner = static_cast(ent)->GetOwner(); + if( owner && owner->IsType( idPlayer::Type ) && owner != this ) { + lastImpulsePlayer = static_cast(owner); + } + } + + idAFEntity_Base::ApplyImpulse( ent, id, point, impulse, splash ); +} + +/* +===================== +idPlayer::SetupHead +===================== +*/ +void idPlayer::SetupHead( const char* headModel, idVec3 headOffset ) { + if( gameLocal.isMultiplayer ) { + // player's don't use idActor's real head entities - uses clientEntities instead + if( clientHead.GetEntity() ) { + delete clientHead.GetEntity(); + clientHead = NULL; + } + + + if( spectating || (gameLocal.GetLocalPlayer() && instance != gameLocal.GetLocalPlayer()->GetInstance()) ) { + return; + } + + const idDict* headDict = gameLocal.FindEntityDefDict( headModel, false ); + if ( !headDict ) { + return; + } + + rvClientAFAttachment* headEnt = clientHead.GetEntity(); + gameLocal.SpawnClientEntityDef( *headDict, (rvClientEntity**)&headEnt, false ); + if( headEnt ) { + idStr jointName = spawnArgs.GetString( "joint_head" ); + jointHandle_t joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + return; + } + + headEnt->SetBody ( this, headDict->GetString ( "model" ), joint ); + + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->Bind( this, joint, true ); + headEnt->InitCopyJoints(); + + // Spawn might have parsed a skin from the spawnargs, save it for future use here + headSkin = headEnt->GetRenderEntity()->customSkin; + clientHead = headEnt; + } + } else { + idActor::SetupHead( headModel, headOffset ); + + if ( head ) { + head->fl.persistAcrossInstances = true; + } + } +} + +/* +===================== +idPlayer::GUIMainNotice +===================== +*/ +void idPlayer::GUIMainNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "main_notice_text", message ); + mphud->SetStateBool( "main_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "main_notice" ); +} + +/* +===================== +idPlayer::GUIFragNotice +===================== +*/ +void idPlayer::GUIFragNotice( const char* message, bool persist ) { + if( !gameLocal.isMultiplayer || !mphud ) { + return; + } + + mphud->SetStateString( "frag_notice_text", message ); + mphud->SetStateBool( "frag_notice_persist", persist ); + mphud->StateChanged( gameLocal.time ); + mphud->HandleNamedEvent( "frag_notice" ); +} + +/* +===================== +idPlayer::SetHudOverlay +===================== +*/ +void idPlayer::SetHudOverlay( idUserInterface* overlay, int duration ) { + overlayHud = overlay; + overlayHudTime = gameLocal.time + duration; +} + +// RAVEN BEGIN +// mekberg: wrap saveMessages +/* +===================== +idPlayer::SaveMessage +===================== +*/ +void idPlayer::SaveMessage( void ) { +#ifndef _XENON + if ( GetHud( ) ) { + GetHud()->HandleNamedEvent( "saveMessage" ); + } + + if ( objectiveSystem ) { + objectiveSystem->HandleNamedEvent( "saveMessage" ); + } +#endif +} + +// mekberg: set pm_ cvars +/* +===================== +idPlayer::SetPMCVars +===================== +*/ +void idPlayer::SetPMCVars( void ) { + const idKeyValue *kv; + + if ( !gameLocal.isMultiplayer || gameLocal.isServer ) { + kv = spawnArgs.MatchPrefix( "pm_", NULL ); + while( kv ) { + cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "pm_", kv ); + } + } +} +// RAVEN END + +/* +===================== +idPlayer::GetSpawnClassname +===================== +*/ +const char* idPlayer::GetSpawnClassname ( void ) { + idEntity* world; + const char* entityFilter; + + // Test player def + if ( *g_testPlayer.GetString() ) { + return g_testPlayer.GetString ( ); + } + + // Multiplayer + if ( gameLocal.isMultiplayer ) { + return "player_marine_mp"; + } + + // See if the world spawn specifies a player + world = gameLocal.entities[ENTITYNUM_WORLD]; + assert( world ); + + gameLocal.serverInfo.GetString( "si_entityFilter", "", &entityFilter ); + if ( entityFilter && *entityFilter ) { + return world->spawnArgs.GetString( va("player_%s", entityFilter ), world->spawnArgs.GetString( "player", "player_marine" ) ); + } + + return world->spawnArgs.GetString( "player", "player_marine" ); +} + +/* +=============== +idPlayer::SetInstance +=============== +*/ +void idPlayer::SetInstance( int newInstance ) { + common->DPrintf( "idPlayer::SetInstance() - Setting instance for '%s' to %d\n", name.c_str(), newInstance ); + idEntity::SetInstance( newInstance ); + + if( head.GetEntity() ) { + head.GetEntity()->SetInstance( newInstance ); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetInstance( newInstance ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetInstance( newInstance ); + } + } + + if( weaponWorldModel ) { + weaponWorldModel->SetInstance( newInstance ); + } + + if( weaponViewModel ) { + weaponViewModel->SetInstance( newInstance ); + } + + // reschedule time announcements if needed + if( this == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleTimeAnnouncements(); + + if( gameLocal.isServer ) { + // remove/add heads on server + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if( player ) { + if( player->instance != newInstance ) { + if( player->clientHead.GetEntity() ) { + delete player->clientHead; + player->clientHead = NULL; + } + } else { + player->UpdateModelSetup( true ); + } + } + } + } + } +} + +/* +=============== +idPlayer::JoinInstance +=============== +*/ +void idPlayer::JoinInstance( int newInstance ) { + assert( gameLocal.isServer ); + if( instance == newInstance ) { + return; + } + + if( newInstance < 0 || newInstance >= MAX_ARENAS ) { + gameLocal.Warning( "idPlayer::JoinInstance() - Invalid instance %d specified\n", newInstance ); + } + + if( gameLocal.GetNumInstances() <= newInstance || gameLocal.GetInstance( newInstance ) == NULL ) { + // don't populate instance until player gets linked into the right one + gameLocal.AddInstance( newInstance ); + + if( this == gameLocal.GetLocalPlayer() ) { + // ensure InstanceLeave() gets called on newly spawned entities before + // InstanceJoin() gets called. + gameLocal.mpGame.ServerSetInstance( instance ); + } + } + + SetArena( newInstance ); + SetInstance( newInstance ); + + gameLocal.GetInstance( newInstance )->JoinInstance( this ); +} + +/* +=============== +idPlayer::SetEmote +=============== +*/ +void idPlayer::SetEmote( playerEmote_t newEmote ) { + emote = newEmote; + + // if we're the ones generating the emote, pass it along + if( IsLocalClient() ) { + idBitMsg msg; + byte msgBuf[MAX_EVENT_PARAM_SIZE]; + + assert( entityNumber == gameLocal.localClientNum ); + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( emote ); + if( gameLocal.isServer ) { + ServerSendInstanceEvent( EVENT_EMOTE, &msg, false, -1 ); + } else { + ClientSendEvent( EVENT_EMOTE, &msg ); + } + } +} + +/* +=============== +idPlayer::GetGroundElevator +=============== +*/ +idEntity* idPlayer::GetGroundElevator( idEntity* testElevator ) const { + idEntity* groundEnt = GetGroundEntity(); + if ( !groundEnt ) { + return NULL; + } + while ( groundEnt->GetBindMaster() ) { + groundEnt = groundEnt->GetBindMaster(); + } + + if ( !groundEnt->IsType( idElevator::GetClassType() ) ) { + return NULL; + } + //NOTE: for player, don't care if all the way on, or not + return groundEnt; +} + +/* +=================== +idPlayer::IsCrouching +=================== +*/ +bool idPlayer::IsCrouching( void ) const { + return physicsObj.IsCrouching(); +} + +/* +=============== +idPlayer::SetArena +=============== +*/ +void idPlayer::SetArena( int newArena ) { + if( arena == newArena ) { + return; + } + + arena = newArena; + if( gameLocal.GetLocalPlayer() == this && gameLocal.gameType == GAME_TOURNEY ) { + if( arena >= 0 && arena <= MAX_ARENAS ) { + if( arena < MAX_ARENAS ) { +// RAVEN BEGIN +// rhummer: localized these strings. + GUIMainNotice( va( common->GetLocalizedString( "#str_107270" ), newArena + 1 ) ); + } else { + GUIMainNotice( common->GetLocalizedString( "#str_107271" ) ); + } +// RAVEN END + + if( gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( newArena, TGH_BRACKET ); + } + gameLocal.mpGame.RemoveAnnouncerSoundRange( AS_TOURNEY_JOIN_ARENA_ONE, AS_TOURNEY_JOIN_ARENA_EIGHT ); + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)(AS_TOURNEY_JOIN_ARENA_ONE + arena), gameLocal.time ); + } + } +} + +/* +=============== +idPlayer::Event_DamageEffect +=============== +*/ +void idPlayer::Event_DamageEffect( const char *damageDefName, idEntity* _damageFromEnt ) +{ + const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false ); + if ( damageDef ) + { + idVec3 dir = (_damageFromEnt!=NULL)?(GetEyePosition()-_damageFromEnt->GetEyePosition()):viewAxis[2]; + dir.Normalize(); + int damage = 1; + ClientDamageEffects( damageDef->dict, dir, damage ); + if ( !g_testDeath.GetBool() ) { + lastDmgTime = gameLocal.time; + } + lastDamageDir = dir; + lastDamageDir.Normalize(); + lastDamageDef = damageDef->Index(); + lastDamageLocation = 0; + } +} + +/* +=============== +idPlayer::UpdateDeathShader +=============== +*/ +void idPlayer::UpdateDeathShader ( bool state_hitch ) { + if ( !doingDeathSkin && gameLocal.time > deathSkinTime && deathSkinTime ) { + deathSkinTime = 0; + + deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" ); + doingDeathSkin = true; + if ( state_hitch ) { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } else { + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + if( gameLocal.isMultiplayer ) { + if( clientHead ) { + clientHead.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + clientHead.GetEntity()->GetRenderEntity()->noShadow = true; + } + } else { + if( head ) { + head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + head.GetEntity()->GetRenderEntity()->noShadow = true; + } + } + } + renderEntity.noShadow = true; + UpdateVisuals(); + } +} + +#if 0 +/* +=============== +idPlayer::Event_InitWeapon +=============== +*/ +void idPlayer::InitWeapon( void ) { + currentWeapon = -1; + SetWeapon( idealWeapon ); +} +#endif + +/* +=============== +idPlayer::GetHitscanTint +=============== +*/ +const idVec4& idPlayer::GetHitscanTint( void ) { + assert( !IsFakeClient() ); + + if( gameLocal.IsTeamGame() ) { + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 2 ) { + if( team == TEAM_MARINE ) { + return marineHitscanTint; + } else if( team == TEAM_STROGG ) { + return stroggHitscanTint; + } else { + gameLocal.Error( "idPlayer::GetHitscanTint() - Unknown team '%d' on player %d '%s'\n", team, entityNumber, GetUserInfo()->GetString( "ui_name" ) ); + } + } else { + return defaultHitscanTint; + } + } + + if( gameLocal.serverInfo.GetInt( "si_allowHitscanTint" ) >= 1 ) { + return hitscanTint; + } + + return defaultHitscanTint; +} + +/* +=============== +idPlayer::IsReady +=============== +*/ +bool idPlayer::IsReady( void ) { + return !gameLocal.serverInfo.GetBool( "si_useReady" ) || ready || forcedReady; +} + +/* +=============== +idPlayer::ForceScoreboard +=============== +*/ +void idPlayer::ForceScoreboard( bool force, int time ) { + forceScoreBoard = force; + forceScoreBoardTime = time; +} + +/* +=============== +idPlayer::GetTextTourneyStatus +=============== +*/ +const char* idPlayer::GetTextTourneyStatus( void ) { + if( tourneyStatus == PTS_ADVANCED ) { + return common->GetLocalizedString( "#str_107740" ); + } else if( tourneyStatus == PTS_ELIMINATED ) { + return common->GetLocalizedString( "#str_107729" ); + } else if( tourneyStatus == PTS_PLAYING ) { + return common->GetLocalizedString( "#str_107728" ); + } else if( tourneyStatus == PTS_UNKNOWN ) { + return common->GetLocalizedString( "#str_107739" ); + } + return "UNKNOWN TOURNEY STATUS"; +} + +/* +=============== +idPlayer::ClientInstanceJoin +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceJoin( void ) { + assert( gameLocal.isClient ); + + common->DPrintf( "idPlayer::ClientInstanceJoin() - client %d ('%s') is being shown\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?" ); + + // restore client + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientInstanceLeave +Players know about all other players, even in other instances +We need to hide/show them on the client as we switch to/from instances +=============== +*/ +void idPlayer::ClientInstanceLeave( void ) { + assert( gameLocal.isClient ); + common->DPrintf( "idPlayer::ClientInstanceLeave() - client %d ('%s') is being hidden\n", entityNumber, GetUserInfo() ? GetUserInfo()->GetString( "ui_name" ) : "?" ); + + // force client to spectate + Spectate( spectating, true ); +} + +/* +=============== +idPlayer::ClientStale +=============== +*/ +bool idPlayer::ClientStale( void ) { + idEntity::ClientStale(); + + // remove all powerup effects + for( int i = 0; i < POWERUP_MAX; i++ ) { + if( inventory.powerups & ( 1 << i ) ) { + StopPowerUpEffect( i ); + } + } + + + if( clientHead ) { + delete clientHead; + clientHead = NULL; + } + + Hide(); + + // never delete client + return false; +} + +/* +=============== +idPlayer::ClientUnstale +=============== +*/ +void idPlayer::ClientUnstale( void ) { + idEntity::ClientUnstale(); + + // force render ent to position + renderEntity.axis = physicsObj.GetAxis(); + renderEntity.origin = physicsObj.GetOrigin(); + + // don't do any smoothing with this snapshot + predictedFrame = gameLocal.framenum; + // the powerup effects ( rvClientEntity ) will do some bindings, which in turn will call GetPosition + // which uses the predictedOrigin .. which won't be updated till we Think() so just don't leave the predictedOrigin to the old position + predictedOrigin = renderEntity.origin; + + // restart powerup effects on clients that are coming back into our snapshot + int i; + for ( i = 0; i < POWERUP_MAX; i++ ) { + if ( inventory.powerups & (1 << i) ) { + StartPowerUpEffect( i ); + } + } + + UpdateModelSetup( true ); + + if ( weapon ) { + weapon->ClientUnstale(); + } +} + +/* +=============== +idPlayer::AllowedVoiceDest +=============== +*/ +bool idPlayer::AllowedVoiceDest( int from ) { + + int i, free; + + free = -1; + for( i = 0; i < MAX_CONCURRENT_VOICES; i++ ) { + + if( voiceDest[i] == from ) { + voiceDestTimes[i] = gameLocal.time; + return true; + } + + if( voiceDestTimes[i] + 200 < gameLocal.time ) { + free = i; + } + } + + if( free > -1 ) { + voiceDest[free] = from; + voiceDestTimes[i] = gameLocal.time; + return true; + } + + return false; +} + +// RITUAL BEGIN +void idPlayer::ClampCash( float minCash, float maxCash ) +{ + if( buyMenuCash < minCash ) + buyMenuCash = minCash; + + if( buyMenuCash > maxCash ) + buyMenuCash = maxCash; +} + +void idPlayer::GiveCash( float cashDeltaAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + float oldCash = buyMenuCash; + buyMenuCash += cashDeltaAmount; + ClampCash( minCash, maxCash ); + + if( (int)buyMenuCash != (int)oldCash ) + { + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if( (int)buyMenuCash > (int)oldCash ) + { + // Play the "get cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } + else if( (int)buyMenuCash < (int)oldCash ) + { + // Play the "lose cash" sound +// gameLocal.GetLocalPlayer()->StartSound( "snd_buying_givecash", SND_CHANNEL_ANY, 0, false, NULL ); + } +} + +void idPlayer::SetCash( float newCashAmount ) +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + + buyMenuCash = newCashAmount; + ClampCash( minCash, maxCash ); +} + +void idPlayer::ResetCash() +{ + //int minCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMinCash", 0 ); + //int maxCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerMaxCash", 0 ); + //buyMenuCash = gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( "playerStartingCash", 0 ); + + float minCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMinCredits"); + float maxCash = (float) gameLocal.serverInfo.GetInt("si_buyModeMaxCredits"); + buyMenuCash = (float) gameLocal.serverInfo.GetInt("si_buyModeStartingCredits"); + ClampCash( minCash, maxCash ); +} + +/** + * Checks to see if the player can accept this item in their inventory + * + * weaponName Name of the weapon. + */ +int idPlayer::CanSelectWeapon(const char* weaponName) +{ + int weaponNum = -1; + if(weaponName == NULL) + return weaponNum; + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if ( inventory.weapons & ( 1 << i ) ) { + const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); + if ( !idStr::Cmp( weap, weaponName ) ) { + weaponNum = i; + break; + } + } + } + + return weaponNum; +} + +// RITUAL END + +bool idPlayer::IsSpectatedClient( void ) const { + idPlayer *localPlayer = gameLocal.GetLocalPlayer(); + if ( !localPlayer ) { + return false; + } + if ( localPlayer->spectating && localPlayer->spectator == entityNumber ) { + return true; + } + return false; +} diff --git a/src/mpgame/Player.h b/src/mpgame/Player.h new file mode 100644 index 0000000..6e4b9e7 --- /dev/null +++ b/src/mpgame/Player.h @@ -0,0 +1,1345 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYER_H__ +#define __GAME_PLAYER_H__ + +/* +=============================================================================== + + Player entity. + +=============================================================================== +*/ + +extern const idEventDef EV_Player_GetButtons; +extern const idEventDef EV_Player_GetMove; +extern const idEventDef EV_Player_GetViewAngles; +extern const idEventDef EV_Player_SetViewAngles; +extern const idEventDef EV_Player_EnableWeapon; +extern const idEventDef EV_Player_DisableWeapon; +extern const idEventDef EV_Player_ExitTeleporter; +extern const idEventDef EV_Player_SelectWeapon; +extern const idEventDef EV_Player_Freeze; +extern const idEventDef EV_SpectatorTouch; +extern const idEventDef EV_Player_SetArmor; +extern const idEventDef EV_Player_SetExtraProjPassEntity; +extern const idEventDef EV_Player_DamageEffect; + +const float THIRD_PERSON_FOCUS_DISTANCE = 512.0f; +const int LAND_DEFLECT_TIME = 150; +const int LAND_RETURN_TIME = 300; +const int FOCUS_TIME = 200; +const int FOCUS_GUI_TIME = 300; +const int FOCUS_USABLE_TIME = 100; + +const int MAX_WEAPONS = 16; +const int MAX_AMMO = 16; +const int CARRYOVER_FLAG_AMMO = 0x40000000; +const int CARRYOVER_FLAG_ARMOR_LIGHT = 0x20000000; +const int CARRYOVER_FLAG_ARMOR_HEAVY = 0x10000000; +const int CARRYOVER_WEAPONS_MASK = 0x0FFFFFFF; +const int CARRYOVER_FLAGS_MASK = 0xF0000000; + +const int MAX_SKILL_LEVELS = 4; + +const int ZERO_VOLUME = -40; // volume at zero +const int DMG_VOLUME = 5; // volume when taking damage +const int DEATH_VOLUME = 15; // volume at death + +const int SAVING_THROW_TIME = 5000; // maximum one "saving throw" every five seconds + +// grep max_ammo_* in the multiplayer settings, all less than 512 (2^9) +const int ASYNC_PLAYER_INV_AMMO_BITS = 9; +const int ASYNC_PLAYER_INV_CLIP_BITS = -7; // -7 bits to cover the range [-1, 60] +// NOTE: protocol 69 used 6 bits, but that's only used for client -> server traffic, so doesn't affect backwards protocol replay compat +const int IMPULSE_NUMBER_OF_BITS = 8; // allows for 2< items; + idStrList pdas; + idStrList pdaSecurity; + idStrList videos; + + idList levelTriggers; + + idInventory() { Clear(); } + ~idInventory() { Clear(); } + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Clear( void ); + void GivePowerUp( idPlayer* player, int powerup, int msec ); + void ClearPowerUps( void ); + void GetPersistantData( idDict &dict ); + void RestoreInventory( idPlayer *owner, const idDict &dict ); + bool Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud, bool dropped = false, bool checkOnly = false ); + void Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ); + int AmmoIndexForAmmoClass( const char *ammo_classname ) const; + int MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const; + int AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired = NULL ); + const char * AmmoClassForWeaponClass( const char *weapon_classname); + +// RAVEN BEGIN +// mekberg: if the player can pick up the ammo at this time + bool DetermineAmmoAvailability( idPlayer* owner, const char *ammoName, int ammoIndex, int ammoAmount, int ammoMax ); +// RAVEN END + + int AmmoIndexForWeaponIndex( int weaponIndex ); + int StartingAmmoForWeaponIndex( int weaponIndex ); + int AmmoRegenStepForWeaponIndex( int weaponIndex ); + int AmmoRegenTimeForWeaponIndex( int weaponIndex ); + + int HasAmmo( int index, int amount ); + bool UseAmmo( int index, int amount ); + int HasAmmo( const char *weapon_classname ); // looks up the ammo information for the weapon class first + + int nextItemPickup; + int nextItemNum; + int onePickupTime; + idList pickupItemNames; + idList objectiveNames; +// idList database; + + int secretAreasDiscovered; +}; + +class idPlayer : public idActor { +public: + + enum { + EVENT_IMPULSE = idEntity::EVENT_MAXEVENTS, + EVENT_EXIT_TELEPORTER, + EVENT_ABORT_TELEPORTER, + EVENT_POWERUP, + EVENT_SPECTATE, + EVENT_EMOTE, + EVENT_JUMP, + EVENT_MAXEVENTS + }; + + friend class idThread; + + usercmd_t usercmd; + + class idPlayerView playerView; // handles damage kicks and effects + + bool alreadyDidTeamAnnouncerSound; + bool noclip; + bool godmode; + int godmodeDamage; + bool undying; + + bool spawnAnglesSet; // on first usercmd, we must set deltaAngles + idAngles spawnAngles; + idAngles viewAngles; // player view angles + idAngles cmdAngles; // player cmd angles + + int buttonMask; + int oldButtons; + int oldFlags; + + int lastSavingThrowTime; // for the "free miss" effect + + struct playerFlags_s { + bool forward :1; + bool backward :1; + bool strafeLeft :1; + bool strafeRight :1; + bool attackHeld :1; + bool weaponFired :1; + bool jump :1; + bool crouch :1; + bool onGround :1; + bool onLadder :1; + bool dead :1; + bool run :1; + bool pain :1; + bool hardLanding :1; + bool softLanding :1; + bool reload :1; + bool teleport :1; + bool turnLeft :1; + bool turnRight :1; + bool hearingLoss :1; + bool objectiveFailed :1; + bool noFallingDamage :1; + } pfl; + + // inventory + idInventory inventory; + + rvWeapon* weapon; + idEntityPtr weaponViewModel; + idEntityPtr weaponWorldModel; + const idDeclEntityDef* weaponDef; + + + idUserInterface * hud; // Common hud + idUserInterface * mphud; // hud overlay containing MP elements + + idUserInterface * objectiveSystem; + idUserInterface * cinematicHud; + bool objectiveSystemOpen; + bool objectiveButtonReleased; + bool disableHud; + bool showNewObjectives; + + int lastDmgTime; + int deathClearContentsTime; + bool doingDeathSkin; + int nextHealthPulse; // time when health will tick down + int nextAmmoRegenPulse[ MAX_AMMO ]; // time when ammo will regenerate + int nextArmorPulse; // time when armor will tick down + bool hiddenWeapon; // if the weapon is hidden ( in noWeapons maps ) + + // mp stuff + int spectator; + + bool scoreBoardOpen; + bool forceScoreBoard; + bool forceRespawn; + int forceScoreBoardTime; + bool allowedToRespawn; + bool inBuyZone; + bool inBuyZonePrev; + bool spectating; + int lastHitFrame; // game frame of the last hit sound feedback + bool lastHitArmor; + bool forcedReady; + int lastArenaChange; + + bool wantSpectate; // from userInfo + + bool weaponGone; // force stop firing + bool useInitialSpawns; // toggled by a map restart to be active for the first game spawn + bool isLagged; // replicated from server, true if packets haven't been received from client. + bool isChatting; // replicated from server, true if the player is chatting. + + int lastSpectateTeleport; + int latchedTeam; // need to track when team gets changed + int spawnedTime; // when client first enters the game + int hudTeam; + + idEntityPtr teleportEntity; // while being teleported, this is set to the entity we'll use for exit + int teleportKiller; // entity number of an entity killing us at teleporter exit + + idEntityPtr lastKiller; + + // timers + int minRespawnTime; // can respawn when time > this, force after g_forcerespawn + int maxRespawnTime; // force respawn after this time + + // the first person view values are always calculated, even + // if a third person view is used + idVec3 firstPersonViewOrigin; + idMat3 firstPersonViewAxis; + + idDragEntity dragEntity; + idVec3 intentDir; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr extraProjPassEntity; + + bool vsMsgState; + + int lastPickupTime; + + float buyMenuCash; + + float handicap; // multiplier for damage/health + +public: + CLASS_PROTOTYPE( idPlayer ); + + idPlayer(); + virtual ~idPlayer(); + + void Spawn( void ); + void Think( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + static const char* GetSpawnClassname ( void ); + + virtual void Hide( void ); + virtual void Show( void ); + + void Init( void ); + void PrepareForRestart( void ); + virtual void Restart( void ); + void SetWeapon ( int weapon ); + void SetupWeaponEntity( void ); + bool SelectSpawnPoint( idVec3 &origin, idAngles &angles ); + void SpawnFromSpawnSpot( void ); + void SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ); + void SetClipModel( bool forceSpectatorBBox = false ); // spectator mode uses a different bbox size + + void SavePersistantInfo( void ); + void RestorePersistantInfo( void ); + void SetLevelTrigger( const char *levelName, const char *triggerName ); + + bool UserInfoChanged( void ); + idDict * GetUserInfo( void ); + bool BalanceTeam( void ); + void CacheWeapons( void ); + + bool HandleESC( void ); + void EnterCinematic( void ); + void ExitCinematic( void ); + bool SkipCinematic( void ); + + void UpdateConditions( void ); + void SetViewAngles( const idAngles &angles ); + + void BiasIntentDir ( idVec3 newIntentDir, float prevBias = 199.0f ); + + // delta view angles to allow movers to rotate the view of the player + void UpdateDeltaViewAngles( const idAngles &angles ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + + virtual void GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const; + virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); + void CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, + const float damageScale, const int location, int *health, int *armor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void ProjectHeadOverlay( const idVec3 &point, const idVec3 &dir, float size, const char *decal ); + // use exitEntityNum to specify a teleport with private camera view and delayed exit + virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint, idEntity *ignoreEnt ); + + void Kill( bool delayRespawn, bool nodamage ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + renderView_t * GetRenderView( void ); + void SmoothenRenderView( bool firstPerson ); + void CalculateRenderView( void ); // called every tic by player code + void CalculateFirstPersonView( void ); + + void DrawShadow( renderEntity_t *headRenderEnt ); + void DrawHUD( idUserInterface *hud ); + void DrawInput(); + + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void WeaponFireFeedback( const idDict *weaponDef ); + + float DefaultFov( void ) const; + float CalcFov( bool honorZoom ); + void CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ); + void GetViewPos( idVec3 &origin, idMat3 &axis ) const; + void OffsetThirdPersonView( float angle, float range, float height, bool clip ); + void OffsetThirdPersonVehicleView( bool clip ); + bool OffsetThirdPersonTargetView( void ); + + bool Give( const char *statname, const char *value, bool dropped = false ); + bool GiveItem( idItem *item ); + void GiveItem( const char *name ); + + // Inventory + bool GiveInventoryItem( idDict *item ); + void RemoveInventoryItem( idDict *item ); + bool GiveInventoryItem( const char *name ); + void RemoveInventoryItem( const char *name ); + idDict * FindInventoryItem( const char *name ); + + // Wrist computer + void GiveObjective ( const char *title, const char *text, const char *screenshot ); + void CompleteObjective ( const char *title ); + void FailObjective ( const char *title ); + void GiveDatabaseEntry ( const idDict* dbEntry, bool hudPopup = true ); + bool IsObjectiveUp ( void ) const { return objectiveUp; } + idUserInterface * GetObjectiveHud ( void ) { return objectiveSystem; } + + // Secret Areas + void DiscoverSecretArea ( const char *description); + + void StartBossBattle ( idEntity* ent ); + + // Powerups + bool GivePowerUp ( int powerup, int time, bool team = false ); + void ClearPowerUps ( void ); + + void StartPowerUpEffect ( int powerup ); + void StopPowerUpEffect ( int powerup ); + + bool PowerUpActive ( int powerup ) const; + float PowerUpModifier ( int type ); + void ClearPowerup ( int i ); + const char* GetArenaPowerupString ( void ); + + // Helper methods to retrieving dictionaries + const idDeclEntityDef* GetWeaponDef ( int weaponIndex ); + const idDeclEntityDef* GetPowerupDef ( int powerupIndex ); + + // Weapons + bool GiveWeaponMods ( int mods ); + bool GiveWeaponMods ( int weapon, int mods ); + void GiveWeaponMod ( const char* weaponmod ); + + int SlotForWeapon ( const char *weaponName ); + + idEntity* DropItem ( const char* itemClass, const idDict& customArgs, const idVec3& velocity = vec3_origin ) const; + void DropPowerups ( void ); + idEntity* ResetFlag ( const char* itemClass, const idDict& customArgs ) const; + void RespawnFlags ( void ); + void DropWeapon ( void ); + + bool WeaponIsEnabled ( void ) const { return weaponEnabled; } + void ShowCrosshair ( void ); + void HideCrosshair ( void ); + + void Reload ( void ); + void NextWeapon ( void ); + void NextBestWeapon ( void ); + void PrevWeapon ( void ); + void LastWeapon ( void ); + void SelectWeapon ( int num, bool force ); + void SelectWeapon ( const char * ); + void AddProjectilesFired ( int count ); + void AddProjectileHits ( int count ); + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void WeaponLoweringCallback ( void ); + void WeaponRisingCallback ( void ); + void RemoveWeapon ( const char *weap ); + void Flashlight ( bool on ); + void ToggleFlashlight ( void ); + bool CanShowWeaponViewmodel ( void ) const; + + void TriggerHitSound ( bool armor ); + + virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); + bool GuiActive( void ) { return focusType == FOCUS_GUI; } + + void GenerateImpulseForBuyAttempt( const char* itemName ); + bool AttemptToBuyItem( const char* itemName ); + bool AttemptToBuyTeamPowerup( const char* itemName ); + void UpdateTeamPowerups( bool isBuying = false ); + bool CanBuy( void ); + int CanSelectWeapon ( const char* weaponName ); + int GetItemCost(const char* itemName); + void PerformImpulse( int impulse ); + void Spectate( bool spectate, bool force = false ); + void ToggleObjectives ( void ); + void ToggleScoreboard( void ); + void RouteGuiMouse( idUserInterface *gui ); + void UpdateHud( void ); + idUserInterface* GetHud(); + const idUserInterface* GetHud() const; + void SetInfluenceFov( float fov ); + void SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ); + void SetInfluenceLevel( int level ); + int GetInfluenceLevel( void ) { return influenceActive; }; + void SetPrivateCameraView( idCamera *camView ); + idCamera * GetPrivateCameraView( void ) const { return privateCameraView; } + void StartFxFov( float duration ); + void UpdateHudWeapon( int displayWeapon = -1 ); +#ifdef _XENON + void ResetHUDWeaponSwitch( void ); +#endif + void UpdateHudStats( idUserInterface *hud ); + void UpdateHudAmmo( idUserInterface *hud ); + void UpdateHudPowerUps( idUserInterface *hud ); + void ShowTip( const char *title, const char *tip, bool autoHide ); + void HideTip( void ); + bool IsTipVisible( void ) { return tipUp; }; + void ShowObjective( const char *obj ); + void HideObjective( void ); + idVec3 GetEyePosition( void ) const; + + void LocalClientPredictionThink( void ); + void NonLocalClientPredictionThink( void ); + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + void WritePlayerStateToSnapshot( int lastSnapshotFrame, idBitMsgDelta &msg ) const; + void ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + virtual void ClientUnstale( void ); + + virtual bool ServerReceiveEvent( int event, int time, const idBitMsg &msg ); + + virtual bool GetMasterPosition( idVec3 &masterOrigin, idMat3 &masterAxis ) const; + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); + + bool IsBeingTalkedTo ( void ); + bool IsReady ( void ); + bool IsRespawning ( void ); + bool IsInTeleport ( void ); + bool IsZoomed ( void ); + bool IsFlashlightOn ( void ); + virtual bool IsCrouching ( void ) const; + + // voice com muting + bool IsPlayerMuted ( idPlayer* player ) const; + bool IsPlayerMuted ( int clientNum ) const; + void MutePlayer ( idPlayer* player, bool mute ); + void MutePlayer ( int clientNum, bool mute ); + + + // buddy list + void SetFriend ( idPlayer* player, bool isFriend ); + void SetFriend ( int clientNum, bool isFriend ); + bool IsFriend ( idPlayer* player ) const; + bool IsFriend ( int clientNum ) const; + + // time joined server + int GetConnectTime ( void ) const; + void SetConnectTime ( int time ); + + // emotes + void SetEmote ( playerEmote_t emote ); + + // rankings + int GetRank ( void ) const; + void SetRank ( int newRank ); + + // arenas for tourney mode + int GetArena ( void ) const; + void SetArena ( int newArena ); + + idEntity *GetInfluenceEntity( void ) { return influenceEntity; }; + const idMaterial *GetInfluenceMaterial( void ) { return influenceMaterial; }; + float GetInfluenceRadius( void ) { return influenceRadius; }; + + // server side work for in/out of spectate. takes care of spawning it into the world as well + void ServerSpectate( bool spectate ); + + // for very specific usage. != GetPhysics() + idPhysics *GetPlayerPhysics( void ); + void TeleportDeath( int killer ); + void SetLeader( bool lead ); + bool IsLeader( void ); + + void UpdateModelSetup( bool forceReload = false ); + void UpdateSounds(const rvDeclPlayerModel *modelDecl); + + bool OnLadder( void ) const; + + rvViewWeapon* GetWeaponViewModel ( void ) const; + idAnimatedEntity* GetWeaponWorldModel ( void ) const; + int GetCurrentWeapon ( void ) const; + + bool IsGibbed ( void ) const; + const idVec3& GetGibDir ( void ) const; + + void SetInitialHud ( void ); + + void RemoveClientModel ( const char* entityDefName ); + void RemoveClientModels ( void ); + + rvClientEntityPtr AddClientModel ( const char* entityDefName, const char* shaderName = NULL ); + + void ClientGib ( const idVec3& dir ); + void ClientDamageEffects ( const idDict& damageDef, const idVec3& dir, int damage ); + + + void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + + void GUIMainNotice( const char* message, bool persist = false ); + void GUIFragNotice( const char* message, bool persist = false ); + + virtual bool EnterVehicle ( idEntity* vehicle ); + virtual bool ExitVehicle ( bool force = false ); + + virtual void SetClipWorld( int newCW ); + virtual int GetClipWorld( void ) const; + + virtual idEntity* GetGroundElevator( idEntity* testElevator=NULL ) const; + + int GetWeaponIndex( const char* weaponName ) const; + + virtual void SetInstance( int newInstance ); + void JoinInstance( int newInstance ); + + void ClientInstanceJoin( void ); + void ClientInstanceLeave( void ); + + void SetHudOverlay( idUserInterface* overlay, int duration ); + + void SetShowHud( bool showHud ); + bool GetShowHud( void ); + + + // mekberg: wrap saveMessages + void SaveMessage( void ); + + // mekberg: set pm_ cvars + void SetPMCVars( void ); + + void SetTourneyStatus( playerTourneyStatus_t newStatus ) { tourneyStatus = newStatus; } + playerTourneyStatus_t GetTourneyStatus( void ) { return tourneyStatus; } + const char* GetTextTourneyStatus( void ); + + const idVec4& GetHitscanTint( void ); + + void ForceScoreboard( bool force, int time ); + + // call only on the local player + idUserInterface* GetCursorGUI( void ); + + bool CanFire( void ) const; + + bool AllowedVoiceDest( int from ); + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer + itemBuyStatus_t ItemBuyStatus( const char* itemName ); + bool CanBuyItem( const char* itemName ); + void GiveCash( float cashDeltaAmount ); + void ClampCash( float minCash, float maxCash ); + void SetCash( float newCashAmount ); + void ResetCash(); +// RITUAL END + + bool IsFakeClient( void ) const { return entityNumber == ENTITYNUM_NONE; } + bool IsLocalClient( void ) const { return entityNumber == gameLocal.localClientNum || IsFakeClient(); } + bool IsSpectatedClient( void ) const; + bool IsWaitingForPredictAck( void ) const; + +protected: + void SetupHead( const char* modelKeyName = "", idVec3 headOffset = idVec3(0, 0, 0) ); + +private: + float vehicleCameraDist; + + jointHandle_t hipJoint; + jointHandle_t chestJoint; + jointHandle_t neckLeanJoint; + + idPhysics_Player physicsObj; // player physics + + idList aasLocation; // for AI tracking the player + + idStr modelName; // current model name + const rvDeclPlayerModel* modelDecl; + + int bobFoot; + float bobFrac; + float bobfracsin; + int bobCycle; // for view bobbing and footstep generation + float xyspeed; + int stepUpTime; + float stepUpDelta; + float idealLegsYaw; + float legsYaw; + bool legsForward; + float oldViewYaw; + idAngles viewBobAngles; + idVec3 viewBob; + int landChange; + int landTime; + + // ddynerman: we read fall deltas from spawnargs, cache them to save some lookups + float fatalFallDelta; + float hardFallDelta; + float softFallDelta; + float noFallDelta; + + int carryOverCurrentWeapon; + int currentWeapon; + int idealWeapon; + int clientIdealWeaponPredictFrame; // for weapon switch prediction + int previousWeapon; + int weaponSwitchTime; + bool weaponEnabled; + bool showWeaponViewModel; + + rvClientEntityPtr clientHead; + bool objectivesEnabled; + bool flagCanFire; + + bool flashlightOn; + bool zoomed; + + bool reloadModel; + + const idDeclSkin * skin; + const idDeclSkin * weaponViewSkin; + const idDeclSkin * headSkin; + + const idDeclSkin * powerUpSkin; // active powerup skin + const idDeclSkin * gibSkin; + + const idMaterial* quadOverlay; + const idMaterial* hasteOverlay; + const idMaterial* regenerationOverlay; + const idMaterial* invisibilityOverlay; + const idMaterial* powerUpOverlay; + + int numProjectilesFired; // number of projectiles fired + int numProjectileHits; // number of hits on mobs + + bool airless; + int airTics; // set to pm_airTics at start, drops in vacuum + int lastAirDamage; + + bool gibDeath; + bool gibsLaunched; + idVec3 gibDir; + + playerTourneyStatus_t tourneyStatus; + bool isStrogg; + + idInterpolate zoomFov; + idInterpolate centerView; + bool fxFov; + + float influenceFov; + int influenceActive; // level of influence.. 1 == no gun or hud .. 2 == 1 + no movement + idEntity * influenceEntity; + const idMaterial * influenceMaterial; + float influenceRadius; + const idDeclSkin * influenceSkin; + + idCamera * privateCameraView; + + static const int NUM_LOGGED_VIEW_ANGLES = 64; // for weapon turning angle offsets + idAngles loggedViewAngles[NUM_LOGGED_VIEW_ANGLES]; // [gameLocal.framenum&(LOGGED_VIEW_ANGLES-1)] + static const int NUM_LOGGED_ACCELS = 16; // for weapon turning angle offsets + loggedAccel_t loggedAccel[NUM_LOGGED_ACCELS]; // [currentLoggedAccel & (NUM_LOGGED_ACCELS-1)] + int currentLoggedAccel; + + int demoViewAngleTime; + idAngles demoViewAngles; + + // if there is a focusGUIent, the attack button will be changed into mouse clicks + idUserInterface * focusUI; + int focusTime; + playerFocus_t focusType; + idEntityPtr focusEnt; + idUserInterface * focusBrackets; + int focusBracketsTime; + + bool targetFriendly; + + idEntityPtr talkingNPC; // NPC who's currently talking to us + int talkCursor; // show the state of the focusCharacter (0 == can't talk/dead, 1 == ready to talk, 2 == busy talking) + idUserInterface * cursor; + + idUserInterface * overlayHud; // a temporary hud overlay + int overlayHudTime; + + // full screen guis track mouse movements directly + int oldMouseX; + int oldMouseY; + + bool tipUp; + bool objectiveUp; + + float dynamicProtectionScale; // value to scale damage by due to dynamic protection + int lastDamageDef; + idVec3 lastDamageDir; + int lastDamageLocation; + + int predictedFrame; + idVec3 predictedOrigin; + idAngles predictedAngles; + bool predictedUpdated; + idVec3 predictionOriginError; + idAngles predictionAnglesError; + int predictionErrorTime; + + // mp + bool ready; // from userInfo + bool respawning; // set to true while in SpawnToPoint for telefrag checks + bool leader; // for sudden death situations + bool weaponCatchup; // raise up the weapon silently ( state catchups ) + bool isTelefragged; // proper obituaries + + int lastSpectateChange; + int lastTeleFX; + unsigned int lastSnapshotSequence; // track state hitches on clients + + int aimClientNum; // player num in aim + + idPlayer* lastImpulsePlayer; // the last player who gave me an impulse, may be null + + int arena; // current arena for tourney gameplay + + int connectTime; + int mutedPlayers; // bitfield set to which clients this player wants muted + int friendPlayers; // bitfield set to which clients this player has marked as friends + + int voiceDest[MAX_CONCURRENT_VOICES]; + int voiceDestTimes[MAX_CONCURRENT_VOICES]; + + int rank; + + int deathSkinTime; + bool deathStateHitch; + + playerEmote_t emote; + + int powerupEffectTime; + const idDecl *powerupEffect; + int powerupEffectType; + idList powerupEffectJoints; + rvClientEffectPtr hasteEffect; + rvClientEffectPtr flagEffect; + rvClientEffectPtr arenaEffect; + + rvClientEffectPtr teamHealthRegen; + bool teamHealthRegenPending; + rvClientEffectPtr teamAmmoRegen; + bool teamAmmoRegenPending; + rvClientEffectPtr teamDoubler; + bool teamDoublerPending; + + idVec4 hitscanTint; + // end mp + + int lastImpulseTime; // time of last impulse + idEntityPtr bossEnemy; + + const idDeclEntityDef* cachedWeaponDefs[ MAX_WEAPONS ]; + const idDeclEntityDef* cachedPowerupDefs[ POWERUP_MAX ]; + + bool weaponChangeIconsUp; + + int oldInventoryWeapons; + + const idDeclEntityDef* itemCosts; + + idVec3 leanVelocity; + + // hack to avoid jittery legs + int ignoreOnGroundUntilFrame; + bool prevOnGround; + + // lean props + float leanMaxSpeed; + float leanBlendRatio; + float leanMaxLateralAngle; + float leanMaxForwardAngle; + float leanHeadRatio; + float leanHipRatio; + + // flags when server is processing entity events, for acknowledge replies + bool serverReceiveEvent; + + bool WantSmoothing( void ) const; + void PredictionErrorDecay( void ); + + bool CanZoom(void); + + void LookAtKiller( idEntity *inflictor, idEntity *attacker ); + + void StopFiring( void ); + void FireWeapon( void ); + void Weapon_Combat( void ); + void Weapon_NPC( void ); + void Weapon_GUI( void ); + void Weapon_Vehicle ( void ); + void Weapon_Usable ( void ); + void SpectateFreeFly( bool force ); // ignore the timeout to force when followed spec is no longer valid + void SpectateCycle( void ); + idAngles GunTurningOffset( void ); + idVec3 GunAcceleratingOffset( void ); + + void CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ); + void BobCycle( const idVec3 &pushVelocity ); + void EvaluateControls( void ); + void AdjustSpeed( void ); + void AdjustBodyAngles( void ); + void Move( void ); + void SetSpectateOrigin( void ); + + void InitAASLocation( void ); + void SetAASLocation( void ); + + idUserInterface * ActiveGui( void ); + + void UpdateWeapon ( void ); + void UpdateSpectating ( void ); + void UpdateAir ( void ); + void UpdateGravity ( void ); + void HandleObjectiveInput ( void ); + void HandleCheats ( void ); + void ClearCheatState ( void ); + void UpdateViewAngles ( void ); + void UpdatePowerUps ( void ); + void UpdateDeathSkin ( bool state_hitch ); + void UpdateFocus ( void ); + void UpdateLocation ( void ); + void UpdateObjectiveInfo ( void ); + void UpdateIntentDir ( void ); + + void LoadDeferredModel ( void ); + + void ClearFocus ( void ); + void UpdateFocusCharacter ( idEntity* newEnt ); + void SetFocus ( playerFocus_t type, int focusTime, idEntity* ent, idUserInterface* ui ); + + void UpdateDeathShader ( bool state_hitch ); + +// void InitWeapon ( void ); + + bool IsLegsIdle ( bool crouching ) const; + + void CheckAckReply ( void ) const; + + void Event_GetButtons ( void ); + void Event_GetMove ( void ); + void Event_GetViewAngles ( void ); + void Event_SetViewAngles ( const idVec3 &vec ); + void Event_StopFxFov ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_GetCurrentWeapon ( void ); + void Event_GetPreviousWeapon ( void ); + void Event_SelectWeapon ( const char *weaponName ); + void Event_GetWeaponEntity ( void ); + void Event_ExitTeleporter ( void ); + void Event_HideTip ( void ); + void Event_LevelTrigger ( void ); + void Event_GetViewPos ( void ); + void Event_TeleportPlayer ( idVec3 &newPos, idVec3 &newAngles ); + void Event_Freeze ( float f ); + void Event_HideDatabaseEntry ( void ); + void Event_ZoomIn ( void ); + void Event_ZoomOut ( void ); + void Event_FinishHearingLoss ( float fadeTime ); + void Event_GetAmmoData ( const char *ammoClass ); + void Event_RefillAmmo ( void ); + void Event_AllowFallDamage ( int toggle ); + + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + virtual void Event_DamageOverTimeEffect ( int endTime, int interval, const char *damageDefName ); + + // RAVEN BEGIN + // twhitaker: added Event_ApplyImpulse + void Event_ApplyImpulse ( idEntity* ent, idVec3 &point, idVec3 &impulse ); + + // mekberg: added sethealth + void Event_SetHealth ( float newHealth ); + void Event_SetArmor ( float newArmor ); + + void Event_SetExtraProjPassEntity( idEntity* _extraProjPassEntity ); + void Event_DamageEffect ( const char *damageDefName, idEntity* _damageFromEnt ); + + // mekberg: allow enabling/disabling of objectives + void Event_EnableObjectives ( void ); + void Event_DisableObjectives ( void ); + + // mekberg: don't supress showing new objectives anymore + void Event_AllowNewObjectives ( void ); + + stateResult_t State_Wait_Alive ( const stateParms_t& parms ); + stateResult_t State_Wait_ReloadAnim ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_IdleThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + stateResult_t State_Torso_RaiseWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_LowerWeapon ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire ( const stateParms_t& parms ); + stateResult_t State_Torso_Fire_Windup ( const stateParms_t& parms ); + + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_Dead ( const stateParms_t& parms ); + stateResult_t State_Torso_Emote ( const stateParms_t& parms ); + + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Run_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Left ( const stateParms_t& parms ); + stateResult_t State_Legs_Walk_Right ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Uncrouch ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Forward ( const stateParms_t& parms ); + stateResult_t State_Legs_Crouch_Backward ( const stateParms_t& parms ); + stateResult_t State_Legs_Jump ( const stateParms_t& parms ); + stateResult_t State_Legs_Fall ( const stateParms_t& parms ); + stateResult_t State_Legs_Land ( const stateParms_t& parms ); + stateResult_t State_Legs_Dead ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( idPlayer ); +}; + +ID_INLINE bool idPlayer::IsBeingTalkedTo( void ) { + return talkingNPC!=NULL; +} + +ID_INLINE bool idPlayer::IsRespawning( void ) { + return respawning; +} + +ID_INLINE idPhysics* idPlayer::GetPlayerPhysics( void ) { + return &physicsObj; +} + +ID_INLINE bool idPlayer::IsInTeleport( void ) { + return ( teleportEntity.GetEntity() != NULL ); +} + +ID_INLINE void idPlayer::SetLeader( bool lead ) { + leader = lead; +} + +ID_INLINE bool idPlayer::IsLeader( void ) { + return leader; +} + +ID_INLINE bool idPlayer::IsZoomed( void ) { + return zoomed; +} + +ID_INLINE bool idPlayer::IsFlashlightOn( void ) { + return flashlightOn; +} + +ID_INLINE rvViewWeapon* idPlayer::GetWeaponViewModel( void ) const { + return weaponViewModel; +} + +ID_INLINE idAnimatedEntity* idPlayer::GetWeaponWorldModel( void ) const { + return weaponWorldModel; +} + +ID_INLINE int idPlayer::GetCurrentWeapon( void ) const { + return currentWeapon; +} + +ID_INLINE bool idPlayer::IsGibbed( void ) const { + return gibDeath; +} + +ID_INLINE const idVec3& idPlayer::GetGibDir( void ) const { + return gibDir; +} + +ID_INLINE int idPlayer::GetClipWorld( void ) const { + return clipWorld; +} + +ID_INLINE void idPlayer::SetClipWorld( int newCW ) { + idEntity::SetClipWorld( newCW ); + + if( head.GetEntity() ) { + head.GetEntity()->SetClipWorld( newCW ); + + head.GetEntity()->UnlinkCombat(); + head.GetEntity()->LinkCombat(); + } + + if( weapon ) { + if( weapon->GetViewModel() ) { + weapon->GetViewModel()->SetClipWorld( newCW ); + } + + if( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetClipWorld( newCW ); + } + } + + UnlinkCombat(); + LinkCombat(); +} + +ID_INLINE int idPlayer::GetWeaponIndex( const char* weaponName ) const { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + if( !idStr::Icmp( spawnArgs.GetString( va( "def_weapon%d", i ), "" ), weaponName ) ) { + return i; + } + } + + return 0; +} + +ID_INLINE idUserInterface* idPlayer::GetHud() { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE const idUserInterface* idPlayer::GetHud() const { + return vehicleController.IsDriving() ? vehicleController.GetHud() : hud; +} + +ID_INLINE bool idPlayer::IsPlayerMuted( idPlayer* player ) const { + return !!( mutedPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsPlayerMuted( int clientNum ) const { + return !!( mutedPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::MutePlayer( idPlayer* player, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << player->entityNumber ); + } else { + mutedPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::MutePlayer( int clientNum, bool mute ) { + if( mute ) { + mutedPlayers |= ( 1 << clientNum ); + } else { + mutedPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE bool idPlayer::IsFriend( idPlayer* player ) const { + return !!( friendPlayers & ( 1 << player->entityNumber ) ); +} + +ID_INLINE bool idPlayer::IsFriend( int clientNum ) const { + return !!( friendPlayers & ( 1 << clientNum ) ); +} + +ID_INLINE void idPlayer::SetFriend( idPlayer* player, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << player->entityNumber ); + } else { + friendPlayers &= ~( 1 << player->entityNumber ); + } +} + +ID_INLINE void idPlayer::SetFriend( int clientNum, bool isFriend ) { + if( isFriend ) { + friendPlayers |= ( 1 << clientNum ); + } else { + friendPlayers &= ~( 1 << clientNum ); + } +} + +ID_INLINE int idPlayer::GetConnectTime( void ) const { + return connectTime; +} + +ID_INLINE void idPlayer::SetConnectTime( int time ) { + connectTime = time; +} + +ID_INLINE int idPlayer::GetRank( void ) const { + return rank; +} + +ID_INLINE void idPlayer::SetRank( int newRank ) { + rank = newRank; +} + +ID_INLINE int idPlayer::GetArena( void ) const { + return arena; +} + +ID_INLINE bool idPlayer::CanFire( void ) const { + return flagCanFire; +} + +ID_INLINE bool idPlayer::IsWaitingForPredictAck( void ) const { + return ( clientIdealWeaponPredictFrame != -1 ); +} + +#endif /* !__GAME_PLAYER_H__ */ + +// RAVEN END diff --git a/src/mpgame/PlayerView.cpp b/src/mpgame/PlayerView.cpp new file mode 100644 index 0000000..54092fb --- /dev/null +++ b/src/mpgame/PlayerView.cpp @@ -0,0 +1,805 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#if defined(_XENON) && (defined(_PROFILE) || defined(_DEBUG)) +#include "../sys/xenon/ProfilingSupport.h" +#endif + +const int IMPULSE_DELAY = 150; +/* +============== +idPlayerView::idPlayerView +============== +*/ +idPlayerView::idPlayerView() { + memset( screenBlobs, 0, sizeof( screenBlobs ) ); + memset( &view, 0, sizeof( view ) ); + player = NULL; + dvMaterial = NULL; + dvMaterialBlend = NULL; + tunnelMaterial = NULL; + armorMaterial = NULL; + bloodSprayMaterial = NULL; + bfgVision = false; + dvFinishTime = 0; + tvFinishTime = 0; + tvStartTime = 0; + kickFinishTime = 0; + kickAngles.Zero(); + lastDamageTime = 0.0f; + fadeTime = 0; + fadeRate = 0.0; + fadeFromColor.Zero(); + fadeToColor.Zero(); + fadeColor.Zero(); + + ClearEffects(); + + dvScale = 1.0f; + shakeScale = 1.0f; + tvScale = 1.0f; +} + +/* +============== +idPlayerView::Save +============== +*/ +void idPlayerView::Save( idSaveGame *savefile ) const { + int i; + const screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->WriteMaterial( blob->material ); + savefile->WriteFloat( blob->x ); + savefile->WriteFloat( blob->y ); + savefile->WriteFloat( blob->w ); + savefile->WriteFloat( blob->h ); + savefile->WriteFloat( blob->s1 ); + savefile->WriteFloat( blob->t1 ); + savefile->WriteFloat( blob->s2 ); + savefile->WriteFloat( blob->t2 ); + savefile->WriteInt( blob->finishTime ); + savefile->WriteInt( blob->startFadeTime ); + savefile->WriteFloat( blob->driftAmount ); + } + + savefile->WriteInt( dvFinishTime ); + savefile->WriteMaterial( dvMaterial ); + + savefile->WriteMaterial( dvMaterialBlend ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( dvScale ); + + savefile->WriteInt( kickFinishTime ); + savefile->WriteAngles( kickAngles ); + + savefile->WriteBool( bfgVision ); + + savefile->WriteMaterial( tunnelMaterial ); + savefile->WriteMaterial( armorMaterial ); + + savefile->WriteMaterial( bloodSprayMaterial ); + + savefile->WriteFloat( lastDamageTime ); + + savefile->WriteFloat( shakeFinishTime ); + savefile->WriteFloat( shakeScale ); + savefile->WriteFloat( tvScale ); + savefile->WriteInt( tvFinishTime ); + savefile->WriteInt( tvStartTime ); + + savefile->WriteVec4( fadeColor ); + savefile->WriteVec4( fadeToColor ); + savefile->WriteVec4( fadeFromColor ); + savefile->WriteFloat( fadeRate ); + savefile->WriteInt( fadeTime ); + + savefile->WriteObject( player ); + savefile->WriteRenderView( view ); +} + +/* +============== +idPlayerView::Restore +============== +*/ +void idPlayerView::Restore( idRestoreGame *savefile ) { + int i; + screenBlob_t *blob; + + blob = &screenBlobs[ 0 ]; + for( i = 0; i < MAX_SCREEN_BLOBS; i++, blob++ ) { + savefile->ReadMaterial( blob->material ); + savefile->ReadFloat( blob->x ); + savefile->ReadFloat( blob->y ); + savefile->ReadFloat( blob->w ); + savefile->ReadFloat( blob->h ); + savefile->ReadFloat( blob->s1 ); + savefile->ReadFloat( blob->t1 ); + savefile->ReadFloat( blob->s2 ); + savefile->ReadFloat( blob->t2 ); + savefile->ReadInt( blob->finishTime ); + savefile->ReadInt( blob->startFadeTime ); + savefile->ReadFloat( blob->driftAmount ); + } + + savefile->ReadInt( dvFinishTime ); + savefile->ReadMaterial( dvMaterial ); + + savefile->ReadMaterial( dvMaterialBlend ); // cnicholson: Added unrestored var + + savefile->ReadFloat( dvScale ); + + savefile->ReadInt( kickFinishTime ); + savefile->ReadAngles( kickAngles ); + + savefile->ReadBool( bfgVision ); + + savefile->ReadMaterial( tunnelMaterial ); + savefile->ReadMaterial( armorMaterial ); + + savefile->ReadMaterial( bloodSprayMaterial ); + + savefile->ReadFloat( lastDamageTime ); + + savefile->ReadFloat( shakeFinishTime ); + savefile->ReadFloat( shakeScale ); + savefile->ReadFloat( tvScale ); + savefile->ReadInt( tvFinishTime ); + savefile->ReadInt ( tvStartTime ); + + savefile->ReadVec4( fadeColor ); + savefile->ReadVec4( fadeToColor ); + savefile->ReadVec4( fadeFromColor ); + savefile->ReadFloat( fadeRate ); + savefile->ReadInt( fadeTime ); + + savefile->ReadObject( reinterpret_cast( player ) ); + savefile->ReadRenderView( view ); +} + +/* +============== +idPlayerView::SetPlayerEntity +============== +*/ +void idPlayerView::SetPlayerEntity( idPlayer *playerEnt ) { + player = playerEnt; + + const idDict* dict = NULL; + if( !playerEnt ) { + return; + } + + dict = gameLocal.FindEntityDefDict( playerEnt->spawnArgs.GetString("def_playerView"), false ); + + if( dict ) { + dvMaterial = declManager->FindMaterial( dict->GetString("mtr_doubleVision") ); + dvMaterialBlend = declManager->FindMaterial( dict->GetString("mtr_doubleVisionBlend") ); + tunnelMaterial = declManager->FindMaterial( dict->GetString("mtr_tunnel") ); + armorMaterial = declManager->FindMaterial( dict->GetString("mtr_armourEffect") ); + bloodSprayMaterial = declManager->FindMaterial( dict->GetString("mtr_bloodspray") ); + } +} + +/* +============== +idPlayerView::ClearEffects +============== +*/ +void idPlayerView::ClearEffects() { + lastDamageTime = MS2SEC( gameLocal.time - 99999 ); + + dvFinishTime = ( gameLocal.time - 99999 ); + tvFinishTime = ( gameLocal.time - 99999 ); + tvStartTime = ( gameLocal.time - 99999 ); + kickFinishTime = ( gameLocal.time - 99999 ); + shakeFinishTime = gameLocal.time; + + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlobs[i].finishTime = gameLocal.time; + } + + fadeTime = 0; + bfgVision = false; +} + +/* +============== +idPlayerView::GetScreenBlob +============== +*/ +screenBlob_t *idPlayerView::GetScreenBlob() { + screenBlob_t *oldest = &screenBlobs[0]; + + for ( int i = 1 ; i < MAX_SCREEN_BLOBS ; i++ ) { + if ( screenBlobs[i].finishTime < oldest->finishTime ) { + oldest = &screenBlobs[i]; + } + } + return oldest; +} + +/* +============== +idPlayerView::DamageImpulse + +LocalKickDir is the direction of force in the player's coordinate system, +which will determine the head kick direction +============== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ) { + // + // double vision effect + // + float tvTime = damageDef->GetFloat( "tv_time" ); + if ( tvTime ) { + tvStartTime = gameLocal.time; + tvFinishTime = gameLocal.time + tvTime; + damageDef->GetFloat ( "tv_scale", "1", tvScale ); + } + + if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.time ) { + // keep shotgun from obliterating the view + return; + } + + // + // double vision effect + // + float dvTime = damageDef->GetFloat( "dv_time" ); + if ( dvTime ) { + dvFinishTime = gameLocal.time + (g_dvTime.GetFloat() * dvTime); + damageDef->GetFloat ( "dv_scale", "1", dvScale ); + } + + // + // head angle kick + // + const float modifierScale = 0.25f; + const float inverseModifier = ( 1.0f - modifierScale ); + + float modifier = idMath::ClampFloat( 0.0f, inverseModifier, damage / 100.0f * inverseModifier ) + modifierScale; + float kickTime = damageDef->GetFloat( "kick_time" ); + + if ( kickTime ) { + kickFinishTime = gameLocal.time + g_kickTime.GetFloat() * kickTime; + + // forward / back kick will pitch view + kickAngles[0] = localKickDir[0]; + + // side kick will yaw view + kickAngles[1] = localKickDir[1]*0.5f; + + // up / down kick will pitch view + kickAngles[0] += localKickDir[2]; + + // roll will come from side + kickAngles[2] = localKickDir[1]; + + float kickAmplitude = damageDef->GetFloat( "kick_amplitude" ); + if ( kickAmplitude ) { + kickAngles *= kickAmplitude; + } + + if ( modifier < kickAmplitude ) { + modifier = kickAmplitude; + } + } + else { + kickTime = 500; + } + + // + // screen blob + // + float blobTime = damageDef->GetFloat( "blob_time" ); + if ( blobTime ) { + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + blobTime * g_blobTime.GetFloat(); + + const char *materialName = damageDef->GetString( "mtr_blob" ); + blob->material = declManager->FindMaterial( materialName ); + blob->x = damageDef->GetFloat( "blob_x" ); + blob->x += ( gameLocal.random.RandomInt()&63 ) - 32; + blob->y = damageDef->GetFloat( "blob_y" ); + blob->y += ( gameLocal.random.RandomInt()&63 ) - 32; + + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = damageDef->GetFloat( "blob_width" ) * g_blobSize.GetFloat() * scale; + blob->h = damageDef->GetFloat( "blob_height" ) * g_blobSize.GetFloat() * scale; + blob->s1 = 0; + blob->t1 = 0; + blob->s2 = 1; + blob->t2 = 1; + } + + // + // save lastDamageTime for tunnel vision accentuation + // + lastDamageTime = MS2SEC( gameLocal.time ); +} +// RAVEN END + +/* +================== +idPlayerView::AddBloodSpray + +If we need a more generic way to add blobs then we can do that +but having it localized here lets the material be pre-looked up etc. +================== +*/ +void idPlayerView::AddBloodSpray( float duration ) { +/* + if ( duration <= 0 || bloodSprayMaterial == NULL || g_skipViewEffects.GetBool() ) { + return; + } + // visit this for chainsaw + screenBlob_t *blob = GetScreenBlob(); + blob->startFadeTime = gameLocal.time; + blob->finishTime = gameLocal.time + ( duration * 1000 ); + blob->material = bloodSprayMaterial; + blob->x = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->y = ( gameLocal.random.RandomInt() & 63 ) - 32; + blob->driftAmount = 0.5f + gameLocal.random.CRandomFloat() * 0.5; + float scale = ( 256 + ( ( gameLocal.random.RandomInt()&63 ) - 32 ) ) / 256.0f; + blob->w = 600 * g_blobSize.GetFloat() * scale; + blob->h = 480 * g_blobSize.GetFloat() * scale; + float s1 = 0.0f; + float t1 = 0.0f; + float s2 = 1.0f; + float t2 = 1.0f; + if ( blob->driftAmount < 0.6 ) { + s1 = 1.0f; + s2 = 0.0f; + } else if ( blob->driftAmount < 0.75 ) { + t1 = 1.0f; + t2 = 0.0f; + } else if ( blob->driftAmount < 0.85 ) { + s1 = 1.0f; + s2 = 0.0f; + t1 = 1.0f; + t2 = 0.0f; + } + blob->s1 = s1; + blob->t1 = t1; + blob->s2 = s2; + blob->t2 = t2; +*/ +} + +/* +================== +idPlayerView::WeaponFireFeedback + +Called when a weapon fires, generates head twitches, etc +================== +*/ +void idPlayerView::WeaponFireFeedback( const idDict *weaponDef ) { + int recoilTime; + + recoilTime = weaponDef->GetInt( "recoilTime" ); + // don't shorten a damage kick in progress + if ( recoilTime && recoilTime > (kickFinishTime - gameLocal.time) ) { + idAngles angles; + weaponDef->GetAngles( "recoilAngles", "5 0 0", angles ); + kickAngles = angles; + int finish = gameLocal.time + g_kickTime.GetFloat() * recoilTime; + kickFinishTime = finish; + } + +} + +/* +=================== +idPlayerView::CalculateShake +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +float idPlayerView::CalculateShake( idAngles &shakeAngleOffset ) const { + idVec3 origin, matrix; + + float shakeVolume = soundSystem->CurrentShakeAmplitudeForPosition( SOUNDWORLD_GAME, gameLocal.time, player->firstPersonViewOrigin ); + // + // shakeVolume should somehow be molded into an angle here + // it should be thought of as being in the range 0.0 -> 1.0, although + // since CurrentShakeAmplitudeForPosition() returns all the shake sounds + // the player can hear, it can go over 1.0 too. + // + shakeAngleOffset[0] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[1] = gameLocal.random.CRandomFloat() * shakeVolume; + shakeAngleOffset[2] = gameLocal.random.CRandomFloat() * shakeVolume; + + return shakeVolume; +} +// RAVEN END + +/* +=================== +idPlayerView::AngleOffset + + kickVector, a world space direction that the attack should +=================== +*/ +idAngles idPlayerView::AngleOffset() const { + idAngles ang; + + ang.Zero(); + + if ( gameLocal.time < kickFinishTime ) { + float offset = kickFinishTime - gameLocal.time; + + ang = kickAngles * offset * offset * g_kickAmplitude.GetFloat(); + + for ( int i = 0 ; i < 3 ; i++ ) { + if ( ang[i] > 70.0f ) { + ang[i] = 70.0f; + } else if ( ang[i] < -70.0f ) { + ang[i] = -70.0f; + } + } + } + return ang; +} + +/* +=================== +idPlayerView::ShakeOffsets +=================== +*/ +// RAVEN BEGIN +// jnewquist: Controller rumble +void idPlayerView::ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const { + float shakeVolume = 0.0f; + shakeOffset.Zero(); + shakeAngleOffset.Zero(); + + if( gameLocal.isMultiplayer ) { + return; + } + + shakeVolume = CalculateShake( shakeAngleOffset ); + + if( gameLocal.time < shakeFinishTime ) { + float offset = ( shakeFinishTime - gameLocal.time ) * shakeScale * 0.001f; + + shakeOffset[0] = idMath::ClampFloat( bounds[0][0] - 1.0f, bounds[1][0] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[1] = idMath::ClampFloat( bounds[0][1] - 1.0f, bounds[1][1] + 1.0f, rvRandom::flrand( -offset, offset ) ); + shakeOffset[2] = idMath::ClampFloat( bounds[0][2] - 1.0f, bounds[1][2] + 1.0f, rvRandom::flrand( -offset, offset ) ); + + shakeAngleOffset[0] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[1] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + shakeAngleOffset[2] = idMath::ClampFloat( -70.0f, 70.0f, rvRandom::flrand( -offset, offset ) ); + } +} +// RAVEN END + +/* +================== +idPlayerView::SingleView +================== +*/ +void idPlayerView::SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags ) { + // normal rendering + if ( !view ) { + return; + } + + if ( !( RF_GUI_ONLY & renderFlags ) ) { + // jscott: portal sky rendering with KRABS + idCamera *portalSky = gameLocal.GetPortalSky(); + if ( portalSky ) { + renderView_t portalSkyView = *view; + portalSky->GetViewParms( &portalSkyView ); + gameRenderWorld->RenderScene( &portalSkyView, ( renderFlags & ( ~RF_PRIMARY_VIEW ) ) | RF_DEFER_COMMAND_SUBMIT | RF_PORTAL_SKY ); + } + gameRenderWorld->RenderScene( view, renderFlags | RF_PENUMBRA_MAP ); + } + + if ( RF_NO_GUI & renderFlags ) { + return; + } + + // draw screen blobs + if ( !pm_thirdPerson.GetBool() && !g_skipViewEffects.GetBool() ) { + for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { + screenBlob_t *blob = &screenBlobs[i]; + if ( blob->finishTime <= gameLocal.time ) { + continue; + } + + blob->y += blob->driftAmount; + + float fade = (float)( blob->finishTime - gameLocal.time ) / ( blob->finishTime - blob->startFadeTime ); + if ( fade > 1.0f ) { + fade = 1.0f; + } + if ( fade ) { + renderSystem->SetColor4( 1,1,1,fade ); + renderSystem->DrawStretchPic( blob->x, blob->y, blob->w, blob->h,blob->s1, blob->t1, blob->s2, blob->t2, blob->material ); + } + } + + // Render tunnel vision + if ( gameLocal.time < tvFinishTime ) { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, tvScale * ((float)(tvFinishTime - gameLocal.time) / (float)(tvFinishTime - tvStartTime)) ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } + + player->DrawHUD( hud ); + + +/* + // tunnel vision + float health = 0.0f; + if ( g_testHealthVision.GetFloat() != 0.0f ) { + health = g_testHealthVision.GetFloat(); + } else { + health = player->health; + } + float alpha = health / 100.0f; + if ( alpha < 0.0f ) { + alpha = 0.0f; + } + if ( alpha > 1.0f ) { + alpha = 1.0f; + } + + if ( alpha < 1.0f ) { + renderSystem->SetColor4( ( player->health <= 0.0f ) ? MS2SEC( gameLocal.time ) : lastDamageTime, 1.0f, 1.0f, ( player->health <= 0.0f ) ? 0.0f : alpha ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, tunnelMaterial ); + } +*/ + + + + // Render the object system + // RAVEN BEGIN + // twhitaker: always draw objective system + if ( player->objectiveSystem ) { + player->objectiveSystem->Redraw( gameLocal.time ); + } + // RAVEN END + } + + // test a single material drawn over everything + if ( g_testPostProcess.GetString()[0] ) { + const idMaterial *mtr = declManager->FindMaterial( g_testPostProcess.GetString(), false ); + if ( !mtr ) { + common->Printf( "Material not found.\n" ); + g_testPostProcess.SetString( "" ); + } else { + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, 1.0f ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, mtr ); + } + } +} + +/* +=================== +idPlayerView::DoubleVision +=================== +*/ +void idPlayerView::DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ) { + + if ( !g_doubleVision.GetBool() ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } + + float scale = offset * g_dvAmplitude.GetFloat() * dvScale; + if( scale < 0.0f ) { + return; + } + + if ( scale > 0.5f ) { + scale = 0.5f; + } + float shift = scale * idMath::Sin( idMath::Sqrt ( offset ) * g_dvFrequency.GetFloat() ); + shift = fabs( shift ); + + // if double vision, render to a texture + renderSystem->CropRenderSize( 512, 256, true ); + SingleView( hud, view, RF_NO_GUI ); + renderSystem->CaptureRenderToImage( "_scratch" ); + renderSystem->UnCrop(); + + // carry red tint if in berserk mode + idVec4 color(1, 1, 1, 1); + + renderSystem->SetColor4( color.x, color.y, color.z, 1.0f ); +// RAVEN BEGIN +// jnewquist: Call DrawStretchCopy, which will flip the texcoords for D3D + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, shift, 1, 1, 0, dvMaterial ); + renderSystem->DrawStretchCopy( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 1, 1-shift, 0, dvMaterialBlend ); +// RAVEN END +} + +/* +================= +idPlayerView::Flash + +flashes the player view with the given color +================= +*/ +void idPlayerView::Flash(idVec4 color, int time ) { + Fade(idVec4(0, 0, 0, 0), time); + fadeFromColor = colorWhite; +} + +/* +================= +idPlayerView::Fade + +used for level transition fades +assumes: color.w is 0 or 1 +================= +*/ +void idPlayerView::Fade( idVec4 color, int time ) { + + if ( !fadeTime ) { + fadeFromColor.Set( 0.0f, 0.0f, 0.0f, 1.0f - color[ 3 ] ); + } else { + fadeFromColor = fadeColor; + } + fadeToColor = color; + + if ( time <= 0 ) { + fadeRate = 0; + time = 0; + fadeColor = fadeToColor; + } else { + fadeRate = 1.0f / ( float )time; + } + + if ( gameLocal.realClientTime == 0 && time == 0 ) { + fadeTime = 1; + } else { + fadeTime = gameLocal.realClientTime + time; + } +} + +/* +================= +idPlayerView::ScreenFade +================= +*/ +void idPlayerView::ScreenFade() { + int msec; + float t; + + if ( !fadeTime ) { + return; + } + + msec = fadeTime - gameLocal.realClientTime; + + if ( msec <= 0 ) { + fadeColor = fadeToColor; + if ( fadeColor[ 3 ] == 0.0f ) { + fadeTime = 0; + } + } else { + t = ( float )msec * fadeRate; + fadeColor = fadeFromColor * t + fadeToColor * ( 1.0f - t ); + } + + if ( fadeColor[ 3 ] != 0.0f ) { + renderSystem->SetColor4( fadeColor[ 0 ], fadeColor[ 1 ], fadeColor[ 2 ], fadeColor[ 3 ] ); + renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, declManager->FindMaterial( "_white" ) ); + } +} + +/* +=================== +idPlayerView::InfluenceVision +=================== +*/ +void idPlayerView::InfluenceVision( idUserInterface *hud, const renderView_t *view ) { + + float distance = 0.0f; + float pct = 1.0f; + if ( player->GetInfluenceEntity() ) { + distance = ( player->GetInfluenceEntity()->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).Length(); + if ( player->GetInfluenceRadius() != 0.0f && distance < player->GetInfluenceRadius() ) { + pct = distance / player->GetInfluenceRadius(); + pct = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, pct ); + } + } + if ( player->GetInfluenceMaterial() ) { + SingleView( hud, view ); + renderSystem->SetColor4( 1.0f, 1.0f, 1.0f, pct ); + renderSystem->DrawStretchPic( 0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f, 1.0f, player->GetInfluenceMaterial() ); + } else if ( player->GetInfluenceEntity() == NULL ) { + SingleView( hud, view, RF_NO_GUI ); + return; + } else { + int offset = 25 + idMath::Sin ( gameLocal.time ); + DoubleVision( hud, view, pct * offset ); + } +} + +/* +=================== +idPlayerView::RenderPlayerView +=================== +*/ +void idPlayerView::RenderPlayerView( idUserInterface *hud ) { + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( !view ) { + return; + } + + bool guiRendered = false; + + // place the sound origin for the player + soundSystem->PlaceListener( view->vieworg, view->viewaxis, player->entityNumber + 1, gameLocal.time, "Undefined" ); + + if ( g_skipViewEffects.GetBool() ) { + SingleView( hud, view ); + } else { + if ( player->GetInfluenceMaterial() || player->GetInfluenceEntity() ) { + InfluenceVision( hud, view ); + guiRendered = true; + } else if ( g_doubleVision.GetBool() && gameLocal.time < dvFinishTime ) { + DoubleVision( hud, view, dvFinishTime - gameLocal.time ); + guiRendered = false; + } else { + SingleView( hud, view, RF_NO_GUI | RF_PRIMARY_VIEW ); + } + + // Now draw GUI's. + if ( !guiRendered ) { + SingleView( hud, view, RF_GUI_ONLY ); + } + + ScreenFade(); + } + + if ( net_clientLagOMeter.GetBool() && gameLocal.isClient && !( gameLocal.GetDemoState() == DEMO_PLAYING && ( gameLocal.IsServerDemoPlaying() || gameLocal.IsTimeDemo() ) ) ) { + gameLocal.lagometer.Draw(); + } +} + +// RAVEN END diff --git a/src/mpgame/PlayerView.h b/src/mpgame/PlayerView.h new file mode 100644 index 0000000..d5f0b7d --- /dev/null +++ b/src/mpgame/PlayerView.h @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 07/07/2004 + +#ifndef __GAME_PLAYERVIEW_H__ +#define __GAME_PLAYERVIEW_H__ + +/* +=============================================================================== + + Player view. + +=============================================================================== +*/ + +// screenBlob_t are for the on-screen damage claw marks, etc +typedef struct { + const idMaterial * material; + float x, y, w, h; + float s1, t1, s2, t2; + int finishTime; + int startFadeTime; + float driftAmount; +} screenBlob_t; + +#define MAX_SCREEN_BLOBS 8 + +class idPlayerView { +public: + idPlayerView(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPlayerEntity( class idPlayer *playerEnt ); + + void ClearEffects( void ); + +// RAVEN BEGIN +// jnewquist: Controller rumble + void DamageImpulse( idVec3 localKickDir, const idDict *damageDef, int damage ); +// RAVEN END + + void WeaponFireFeedback( const idDict *weaponDef ); + + idAngles AngleOffset( void ) const; // returns the current kick angle + +// RAVEN BEGIN +// jnewquist: Controller rumble + float CalculateShake( idAngles &shakeAngleOffset ) const; +// RAVEN END + +// RAVEN BEGIN +// jscott: for screen shake + void ShakeOffsets( idVec3 &shakeOffset, idAngles &shakeAngleOffset, const idBounds bounds ) const; +// RAVEN END + + // adds little entities to the renderer for local blood blobs, etc + + // this may involve rendering to a texture and displaying + // that with a warp model or in double vision mode + void RenderPlayerView( idUserInterface *hud ); + + void Fade( idVec4 color, int time ); + + void Flash( idVec4 color, int time ); + + void AddBloodSpray( float duration ); + + // temp for view testing + void EnableBFGVision( bool b ) { bfgVision = b; }; + +// RAVEN BEGIN +// jscott: accessors required for the fx system + void SetDoubleVisionParms( float time, float scale ) { dvFinishTime = SEC2MS( time ); dvScale = scale; } + void SetShakeParms( float time, float scale ) { shakeFinishTime = SEC2MS( time ); shakeScale = scale; } + void SetTunnelParms( float time, float scale ) { tvStartTime = gameLocal.time; tvFinishTime = tvStartTime + time; tvScale = 1.0f / scale; } +// RAVEN END + +private: +// RAVEN BEGIN +// AReis: Modified SingleView() signature to include renderFlags variable. + void SingleView( idUserInterface *hud, const renderView_t *view, int renderFlags = RF_NORMAL ); +// RAVEN END + void DoubleVision( idUserInterface *hud, const renderView_t *view, int offset ); + void BerserkVision( idUserInterface *hud, const renderView_t *view ); + void InfluenceVision( idUserInterface *hud, const renderView_t *view ); + void ScreenFade(); + + screenBlob_t * GetScreenBlob(); + + screenBlob_t screenBlobs[MAX_SCREEN_BLOBS]; + + int dvFinishTime; // double vision will be stopped at this time + const idMaterial * dvMaterial; // material to take the double vision screen shot +// RAVEN BEGIN +// jscott: to make double vision work with alpha components + const idMaterial * dvMaterialBlend; +// jscott: for effects + float dvScale; +// RAVEN END + + int kickFinishTime; // view kick will be stopped at this time + idAngles kickAngles; + + bool bfgVision; // + + const idMaterial * tunnelMaterial; // health tunnel vision + const idMaterial * armorMaterial; // armor damage view effect +// RAVEN BEGIN +// bdube: not using these +// const idMaterial * berserkMaterial; // berserk effect +// const idMaterial * irGogglesMaterial; // ir effect + const idMaterial * bloodSprayMaterial; // blood spray +// const idMaterial * bfgMaterial; // when targeted with BFG +// RAVEN END + + float lastDamageTime; // accentuate the tunnel effect for a while + +// RAVEN BEGIN +// jscott: for effects + float shakeFinishTime; + float shakeScale; + float tvScale; + int tvFinishTime; + int tvStartTime; +// RAVEN END + + idVec4 fadeColor; // fade color + idVec4 fadeToColor; // color to fade to + idVec4 fadeFromColor; // color to fade from + float fadeRate; // fade rate + int fadeTime; // fade time + + idPlayer * player; + renderView_t view; +}; + +#endif /* !__GAME_PLAYERVIEW_H__ */ + +// RAVEN END diff --git a/src/mpgame/Player_Cheats.cpp b/src/mpgame/Player_Cheats.cpp new file mode 100644 index 0000000..9ea2163 --- /dev/null +++ b/src/mpgame/Player_Cheats.cpp @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +============== +idPlayer::HandleCheats +============== +*/ +void idPlayer::HandleCheats() { +} + + +void idPlayer::ClearCheatState() { +} diff --git a/src/mpgame/Player_States.cpp b/src/mpgame/Player_States.cpp new file mode 100644 index 0000000..2afb1b8 --- /dev/null +++ b/src/mpgame/Player_States.cpp @@ -0,0 +1,965 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +CLASS_STATES_DECLARATION ( idPlayer ) + + // Wait States + STATE ( "Wait_Alive", idPlayer::State_Wait_Alive ) + STATE ( "Wait_ReloadAnim", idPlayer::State_Wait_ReloadAnim ) + + // Torso States + STATE ( "Torso_Idle", idPlayer::State_Torso_Idle ) + STATE ( "Torso_IdleThink", idPlayer::State_Torso_IdleThink ) + STATE ( "Torso_Teleport", idPlayer::State_Torso_Teleport ) + STATE ( "Torso_RaiseWeapon", idPlayer::State_Torso_RaiseWeapon ) + STATE ( "Torso_LowerWeapon", idPlayer::State_Torso_LowerWeapon ) + STATE ( "Torso_Fire", idPlayer::State_Torso_Fire ) + STATE ( "Torso_Fire_Windup", idPlayer::State_Torso_Fire_Windup ) + STATE ( "Torso_Reload", idPlayer::State_Torso_Reload ) + STATE ( "Torso_Pain", idPlayer::State_Torso_Pain ) + STATE ( "Torso_Dead", idPlayer::State_Torso_Dead ) + STATE ( "Torso_Emote", idPlayer::State_Torso_Emote ) + + // Leg States + STATE ( "Legs_Idle", idPlayer::State_Legs_Idle ) + STATE ( "Legs_Crouch", idPlayer::State_Legs_Crouch ) + STATE ( "Legs_Uncrouch", idPlayer::State_Legs_Uncrouch ) + STATE ( "Legs_Run_Forward", idPlayer::State_Legs_Run_Forward ) + STATE ( "Legs_Run_Backward", idPlayer::State_Legs_Run_Backward ) + STATE ( "Legs_Run_Left", idPlayer::State_Legs_Run_Left ) + STATE ( "Legs_Run_Right", idPlayer::State_Legs_Run_Right ) + STATE ( "Legs_Walk_Forward", idPlayer::State_Legs_Walk_Forward ) + STATE ( "Legs_Walk_Backward", idPlayer::State_Legs_Walk_Backward ) + STATE ( "Legs_Walk_Left", idPlayer::State_Legs_Walk_Left ) + STATE ( "Legs_Walk_Right", idPlayer::State_Legs_Walk_Right ) + STATE ( "Legs_Crouch_Idle", idPlayer::State_Legs_Crouch_Idle ) + STATE ( "Legs_Crouch_Forward", idPlayer::State_Legs_Crouch_Forward ) + STATE ( "Legs_Crouch_Backward", idPlayer::State_Legs_Crouch_Backward ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Jump", idPlayer::State_Legs_Jump ) + STATE ( "Legs_Fall", idPlayer::State_Legs_Fall ) + STATE ( "Legs_Land", idPlayer::State_Legs_Land ) + STATE ( "Legs_Dead", idPlayer::State_Legs_Dead ) + +END_CLASS_STATES + +/* +================ +idPlayer::State_Torso_Idle +================ +*/ +stateResult_t idPlayer::State_Torso_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT == State_Torso_IdleThink ( parms ) ) { + PlayCycle ( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_IdleThink", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_IdleThink +================ +*/ +stateResult_t idPlayer::State_Torso_IdleThink ( const stateParms_t& parms ) { + if ( pfl.teleport ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Teleport", 0 ); + return SRESULT_DONE; + } + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 0 ); + return SRESULT_DONE; + } + + if( pfl.attackHeld && weapon && weapon->wfl.hasWindupAnim) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_Windup", 0 ); + return SRESULT_DONE; + } + + if ( pfl.attackHeld && HasAnim ( ANIMCHANNEL_TORSO, "startfire" ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire_StartFire", 2 ); + return SRESULT_DONE; + } + if ( pfl.pain ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Pain", 0 ); + return SRESULT_DONE; + } + if ( emote != PE_NONE ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Emote", 5 ); + return SRESULT_DONE; + } + + + return SRESULT_WAIT; +} + +/* +================ +idPlayer::State_Torso_Teleport +================ +*/ +stateResult_t idPlayer::State_Torso_Teleport ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + pfl.teleport = false; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Torso_RaiseWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_RaiseWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "raise", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_LowerWeapon +================ +*/ +stateResult_t idPlayer::State_Torso_LowerWeapon ( const stateParms_t& parms ) { + PlayAnim( ANIMCHANNEL_TORSO, "lower", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 3 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire +================ +*/ +stateResult_t idPlayer::State_Torso_Fire ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_FIRE_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_FIRE_WAIT); + + // Wait for the firing animation to be finished + case TORSO_FIRE_WAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", parms.blendFrames ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Keep the gun aimed but dont shoot + case TORSO_FIRE_AIM: + PlayAnim ( ANIMCHANNEL_TORSO, "aim", 3 ); + return SRESULT_STAGE(TORSO_FIRE_AIMWAIT); + + // Keep the gun aimed as long as the attack button is held and nothing is firing + case TORSO_FIRE_AIMWAIT: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Fire", 3 ); + return SRESULT_DONE; + } else if ( !pfl.attackHeld ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Fire_Windup +================ +*/ +stateResult_t idPlayer::State_Torso_Fire_Windup ( const stateParms_t& parms ) { + enum { + TORSO_FIRE_INIT, + TORSO_WINDUP_WAIT, + TORSO_FIRE_LOOP, + TORSO_FIRE_WAIT, + TORSO_WINDDOWN_START, + TORSO_WINDDOWN_WAIT, + TORSO_FIRE_AIM, + TORSO_FIRE_AIMWAIT + }; + + switch ( parms.stage ) { + // Start the firing sequence + case TORSO_FIRE_INIT: + //jshepard: HACK for now we're blending here, but we need to support charge up anims here + PlayAnim ( ANIMCHANNEL_TORSO, "fire", 4 ); + pfl.weaponFired = false; + return SRESULT_STAGE(TORSO_WINDUP_WAIT); + + // wait for the windup anim to end, or the attackHeld to be false. + case TORSO_WINDUP_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + if( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames )) { + return SRESULT_STAGE( TORSO_FIRE_LOOP ); + } + return SRESULT_WAIT; + + // play the firing loop + case TORSO_FIRE_LOOP: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + + // loop the fire anim + case TORSO_FIRE_WAIT: + if( !pfl.attackHeld ) { + return SRESULT_STAGE( TORSO_WINDDOWN_START ); + } + //loop the attack anim + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fire", parms.blendFrames ); + return SRESULT_STAGE( TORSO_FIRE_WAIT ); + } + return SRESULT_WAIT; + + //wind down + case TORSO_WINDDOWN_START: + //jshepard: HACK just blend back into idle for now, we could support winddown anims here. + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + pfl.weaponFired = false; + return SRESULT_DONE; + + } + return SRESULT_DONE; +} +/* +================ +idPlayer::State_Torso_Reload +================ +*/ +stateResult_t idPlayer::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + TORSO_RELOAD_START, + TORSO_RELOAD_STARTWAIT, + TORSO_RELOAD_LOOP, + TORSO_RELOAD_LOOPWAIT, + TORSO_RELOAD_WAIT, + TORSO_RELOAD_END + }; + switch ( parms.stage ) { + // Start the reload by either playing the reload animation or the reload_start animation + case TORSO_RELOAD_START: + if ( HasAnim ( ANIMCHANNEL_TORSO, "reload_start" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "reload_start", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_STARTWAIT); + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for the reload_start animation to finish and transition to reload_loop + case TORSO_RELOAD_STARTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // Play a single reload from the reload loop + case TORSO_RELOAD_LOOP: + if ( !pfl.reload ) { + return SRESULT_STAGE(TORSO_RELOAD_END); + } + PlayAnim ( ANIMCHANNEL_TORSO, "reload_loop", 0 ); + return SRESULT_STAGE(TORSO_RELOAD_LOOPWAIT); + + // Wait for the looping reload to finish and either start a new one or end the reload + case TORSO_RELOAD_LOOPWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE(TORSO_RELOAD_LOOP); + } else if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + // End the reload + case TORSO_RELOAD_END: + if ( pfl.weaponFired ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + + PlayAnim( ANIMCHANNEL_TORSO, "reload_end", 3 ); + return SRESULT_STAGE(TORSO_RELOAD_WAIT); + + // Wait for reload to finish (called by both reload_end and reload) + case TORSO_RELOAD_WAIT: + if ( pfl.weaponFired || AnimDone ( ANIMCHANNEL_TORSO, 3 ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 3 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Pain +================ +*/ +stateResult_t idPlayer::State_Torso_Pain ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, painAnim.Length()?painAnim:"pain", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Dead +================ +*/ +stateResult_t idPlayer::State_Torso_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Torso_Emote +================ +*/ +stateResult_t idPlayer::State_Torso_Emote ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if( emote == PE_GRAB_A ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_a", parms.blendFrames ); + } else if( emote == PE_GRAB_B ) { + PlayAnim ( ANIMCHANNEL_TORSO, "grab_b", parms.blendFrames ); + } else if( emote == PE_SALUTE ) { + PlayAnim ( ANIMCHANNEL_TORSO, "salute", parms.blendFrames ); + } else if( emote == PE_CHEER ) { + PlayAnim ( ANIMCHANNEL_TORSO, "cheer", parms.blendFrames ); + } else if( emote == PE_TAUNT ) { + PlayAnim ( ANIMCHANNEL_TORSO, "taunt", parms.blendFrames ); + } + + emote = PE_NONE; + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idPlayer::IsLegsIdle +================ +*/ +bool idPlayer::IsLegsIdle( bool crouching ) const { + return ( ( pfl.crouch == crouching ) && pfl.onGround && ( pfl.forward == pfl.backward ) && ( pfl.strafeLeft == pfl.strafeRight ) ); +} + +/* +================ +idPlayer::State_Legs_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.crouch ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Crouch", parms.blendFrames ); + return SRESULT_DONE; + } + IdleAnim( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + // #34 - have a delay while idle before going to something else if a ground flag change happens + // this makes the legs animation stay in idle state when the ground flag value flip-flops fast + if ( prevOnGround != pfl.onGround ) { + // when explicitely jumping, don't impose any delay + bool explicitJump = ( !pfl.onGround && usercmd.upmove > 0 ); + if ( !explicitJump ) { + ignoreOnGroundUntilFrame = gameLocal.framenum + 5; + } + prevOnGround = pfl.onGround; + } + if ( gameLocal.framenum <= ignoreOnGroundUntilFrame ) { + return SRESULT_WAIT; + } + + // If now crouching go back to idle so we can transition to crouch + if ( pfl.crouch ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Crouch", 4 ); + return SRESULT_DONE; + } else if ( pfl.jump ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Jump", 4 ); + return SRESULT_DONE; + } else if ( !pfl.onGround ) { + PostAnimState( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + return SRESULT_DONE; + } else if ( pfl.forward && !pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + if ( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeLeft && !pfl.strafeRight ) { + if ( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + } + + return SRESULT_DONE; + } else if ( pfl.strafeRight && !pfl.strafeLeft ) { + if ( usercmd.buttons & BUTTON_RUN ) { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch_Idle +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", parms.blendFrames ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "crouch", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.crouch || pfl.jump ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Uncrouch", 4 ); + return SRESULT_DONE; + } else if ( (pfl.forward && !pfl.backward) || (pfl.strafeLeft != pfl.strafeRight) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Forward", parms.blendFrames ); + return SRESULT_DONE; + } else if ( pfl.backward && !pfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Crouch +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_down", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( true ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Uncrouch +================ +*/ +stateResult_t idPlayer::State_Legs_Uncrouch ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_LEGS, "crouch_up", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Run_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Run_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Run_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( usercmd.buttons & BUTTON_RUN ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "walk_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Walk_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Forward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.backward && pfl.forward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_forward", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Forward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Backward ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && !pfl.forward && pfl.backward ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_backwards", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Backward", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Left +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Left ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeLeft && !pfl.strafeRight ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_left", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Left", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Walk_Right +================ +*/ +stateResult_t idPlayer::State_Legs_Walk_Right ( const stateParms_t& parms ) { + if ( !pfl.jump && pfl.onGround && !pfl.crouch && (pfl.forward == pfl.backward) && pfl.strafeRight && !pfl.strafeLeft ) { + if( !(usercmd.buttons & BUTTON_RUN) ) { + return SRESULT_WAIT; + } else { + PlayCycle( ANIMCHANNEL_LEGS, "run_strafe_right", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Run_Right", parms.blendFrames ); + return SRESULT_DONE; + } + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Forward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Forward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && ((!pfl.backward && pfl.forward) || (pfl.strafeLeft != pfl.strafeRight)) ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Crouch_Backward +================ +*/ +stateResult_t idPlayer::State_Legs_Crouch_Backward ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayCycle( ANIMCHANNEL_LEGS, "crouch_walk_backward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !pfl.jump && pfl.onGround && pfl.crouch && !pfl.forward && pfl.backward ) { + return SRESULT_WAIT; + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Jump +================ +*/ +stateResult_t idPlayer::State_Legs_Jump ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // prevent infinite recursion + pfl.jump = false; + if ( pfl.run ) { + PlayAnim ( ANIMCHANNEL_LEGS, "run_jump", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "jump", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", 4 ); + } else { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Fall", 4 ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Fall +================ +*/ +stateResult_t idPlayer::State_Legs_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + PlayCycle ( ANIMCHANNEL_LEGS, "fall", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + case STAGE_WAIT: + if ( pfl.onGround ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Land", 2 ); + return SRESULT_DONE; + } + if ( pfl.crouch ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Crouch", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Legs_Land +================ +*/ +stateResult_t idPlayer::State_Legs_Land ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( IsLegsIdle ( false ) && ( pfl.hardLanding || pfl.softLanding ) ) { + if ( pfl.hardLanding ) { + PlayAnim ( ANIMCHANNEL_LEGS, "hard_land", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, "soft_land", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + + case STAGE_WAIT: + if ( !IsLegsIdle ( false ) || AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idPlayer::State_Legs_Dead +================ +*/ +stateResult_t idPlayer::State_Legs_Dead ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Alive", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_Alive + +Waits until the player is alive again. +================ +*/ +stateResult_t idPlayer::State_Wait_Alive ( const stateParms_t& parms ) { + if ( pfl.dead ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idPlayer::State_Wait_ReloadAnim +================ +*/ +stateResult_t idPlayer::State_Wait_ReloadAnim ( const stateParms_t& parms ) { + // The gun firing can cancel any of the relod animations + if ( pfl.weaponFired ) { + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // wait for the animation to finish + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} diff --git a/src/mpgame/Projectile.cpp b/src/mpgame/Projectile.cpp new file mode 100644 index 0000000..11cd421 --- /dev/null +++ b/src/mpgame/Projectile.cpp @@ -0,0 +1,2308 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" +#include "Projectile.h" +#include "spawner.h" + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; + +const idEventDef EV_Explode( "", NULL ); +const idEventDef EV_Fizzle( "", NULL ); +const idEventDef EV_RadiusDamage( "", "E" ); +const idEventDef EV_ResidualDamage ( "", "E" ); +const idEventDef EV_EmitDamage ( "", "E" ); + +CLASS_DECLARATION( idEntity, idProjectile ) + EVENT( EV_Explode, idProjectile::Event_Explode ) + EVENT( EV_Fizzle, idProjectile::Event_Fizzle ) + EVENT( EV_Touch, idProjectile::Event_Touch ) + EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) + EVENT( EV_ResidualDamage, idProjectile::Event_ResidualDamage ) + EVENT( EV_EmitDamage, idProjectile::Event_EmitDamage ) +END_CLASS + +/* +================ +idProjectile::idProjectile +================ +*/ +idProjectile::idProjectile( void ) { + methodOfDeath = -1; + owner = NULL; + memset( &projectileFlags, 0, sizeof( projectileFlags ) ); + damagePower = 1.0f; + + memset( &renderLight, 0, sizeof( renderLight ) ); + + lightDefHandle = -1; + lightOffset.Zero(); + lightStartTime = 0; + lightEndTime = 0; + lightColor.Zero(); + + visualAngles.Zero(); + angularVelocity.Zero(); + speed.Init( 0.0f, 0.0f, 0.0f, 0.0f ); + updateVelocity = false; + + rotation.Init( 0, 0.0f, mat3_identity.ToQuat(), mat3_identity.ToQuat() ); + + flyEffect = NULL; + flyEffectAttenuateSpeed = 0.0f; + bounceCount = 0; + hitCount = 0; + state = SPAWNED; + + fl.networkSync = true; + + prePredictTime = 0; + + syncPhysics = false; + + launchTime = 0; + launchOrig = vec3_origin; + launchDir = vec3_origin; + launchSpeed = 0.0f; + + playedDamageEffect = false; + + predictedProjectiles = false; +} + +/* +================ +idProjectile::Spawn +================ +*/ +void idProjectile::Spawn( void ) { + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( 0 ); + physicsObj.PutToRest(); + SetPhysics( &physicsObj ); + prePredictTime = spawnArgs.GetInt( "predictTime", "0" ); + syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); + + if ( gameLocal.isClient ) { + Hide(); + } + + predictedProjectiles = g_predictedProjectiles.GetBool(); +} + +/* +================ +idProjectile::Save +================ +*/ +void idProjectile::Save( idSaveGame *savefile ) const { + + savefile->WriteInt( methodOfDeath ); // cnicholson: Added unsaved var + owner.Save( savefile ); + savefile->Write( &projectileFlags, sizeof( projectileFlags ) ); + savefile->WriteFloat( damagePower ); + + savefile->WriteRenderLight( renderLight ); + + savefile->WriteInt( ( int )lightDefHandle ); + savefile->WriteVec3( lightOffset ); + savefile->WriteInt( lightStartTime ); + savefile->WriteInt( lightEndTime ); + savefile->WriteVec3( lightColor ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteAngles( visualAngles ); // cnicholson: added unsaved var + savefile->WriteAngles( angularVelocity ); // cnicholson: moved var + + // Save speed + savefile->WriteBool ( updateVelocity ); + savefile->WriteFloat( speed.GetStartTime() ); + savefile->WriteFloat( speed.GetDuration() ); + savefile->WriteFloat( speed.GetStartValue() ); + savefile->WriteFloat( speed.GetEndValue() ); + + // rotation; this is a class, so it doesnt get saved here + + flyEffect.Save( savefile ); // cnicholson: added unsaved var + savefile->WriteFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->WriteInt ( bounceCount ); + savefile->WriteInt ( hitCount ); + + savefile->WriteInt( (int)state ); +} + +/* +================ +idProjectile::Restore +================ +*/ +void idProjectile::Restore( idRestoreGame *savefile ) { + float fset; + idVec3 temp; + + savefile->ReadInt( methodOfDeath ); // cnicholson: Added unrestored var + owner.Restore( savefile ); + savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); + savefile->ReadFloat( damagePower ); + + savefile->ReadRenderLight( renderLight ); + + savefile->ReadInt( (int &)lightDefHandle ); + if ( lightDefHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + savefile->ReadVec3( lightOffset ); + savefile->ReadInt( lightStartTime ); + savefile->ReadInt( lightEndTime ); + savefile->ReadVec3( lightColor ); + + // Restore the physics + savefile->ReadStaticObject( physicsObj ); + RestorePhysics ( &physicsObj ); + + savefile->ReadAngles( visualAngles ); // cnicholson: added unrestored var + savefile->ReadAngles( angularVelocity ); // cnicholson: moved var + + // Restore speed + savefile->ReadBool ( updateVelocity ); + savefile->ReadFloat( fset ); + speed.SetStartTime( fset ); + savefile->ReadFloat( fset ); + speed.SetDuration( fset ); + savefile->ReadFloat( fset ); + speed.SetStartValue( fset ); + savefile->ReadFloat( fset ); + speed.SetEndValue( fset ); + + // rotation? + + flyEffect.Restore( savefile ); // cnicholson: added unrestored var + savefile->ReadFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var + savefile->ReadInt ( bounceCount ); + savefile->ReadInt ( hitCount ); + + savefile->ReadInt( (int &)state ); +} + +/* +================ +idProjectile::GetOwner +================ +*/ +idEntity *idProjectile::GetOwner( void ) const { + return owner.GetEntity(); +} + +/* +================ +idProjectile::SetSpeed +================ +*/ +void idProjectile::SetSpeed( float s, int accelTime ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + speed.Init( gameLocal.time, accelTime, speed.GetCurrentValue(gameLocal.time), s ); + + if ( accelTime > 0 ) { + updateVelocity = true; + } else { + updateVelocity = false; + } + + // Update the velocity to match the direction we are facing and include any accelerations + physicsObj.SetLinearVelocity( speed.GetCurrentValue( gameLocal.time ) * vel ); +} + +/* +================ +idProjectile::Create +================ +*/ +void idProjectile::Create( idEntity* _owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore, idEntity* extraPassEntity ) { + idDict args; + idStr shaderName; + idVec3 light_color; + idVec3 light_offset; + idVec3 tmp; + idMat3 axis; + + Unbind(); + + axis = dir.ToMat3(); + + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( axis ); + + physicsObj.GetClipModel()->SetOwner( ignore ? ignore : _owner ); + physicsObj.extraPassEntity = extraPassEntity; + + owner = _owner; + + memset( &renderLight, 0, sizeof( renderLight ) ); + shaderName = spawnArgs.GetString( "mtr_light_shader" ); + if ( *shaderName ) { + renderLight.shader = declManager->FindMaterial( shaderName, false ); + renderLight.pointLight = true; + renderLight.lightRadius[0] = + renderLight.lightRadius[1] = + renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); + spawnArgs.GetVector( "light_color", "1 1 1", light_color ); + renderLight.shaderParms[0] = light_color[0]; + renderLight.shaderParms[1] = light_color[1]; + renderLight.shaderParms[2] = light_color[2]; + renderLight.shaderParms[3] = 1.0f; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: set the projectile lights to be no shadows + renderLight.noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + } + + spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); + + lightStartTime = 0; + lightEndTime = 0; + + damagePower = 1.0f; + + UpdateVisuals(); + + state = CREATED; +} + +/* +================= +idProjectile::~idProjectile +================= +*/ +idProjectile::~idProjectile() { + StopSound( SND_CHANNEL_ANY, false ); + FreeLightDef(); + SetPhysics( NULL ); +} + +/* +================= +idProjectile::FreeLightDef +================= +*/ +void idProjectile::FreeLightDef( void ) { + if ( lightDefHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightDefHandle ); + lightDefHandle = -1; + } +} + +/* +================= +idProjectile::Launch +================= +*/ +void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float dmgPower ) { + float fuse; + idVec3 velocity; + float linear_friction; + float angular_friction; + float contact_friction; + float bounce; + float mass; + float gravity; + float temp, temp2; + idVec3 gravVec; + idVec3 tmp; + int contents; + int clipMask; + + // allow characters to throw projectiles during cinematics, but not the player + if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + cinematic = owner.GetEntity()->cinematic; + } else { + cinematic = false; + } + + // Set the damage + damagePower = dmgPower; + + if ( !spawnArgs.GetFloat( "speed", "0", temp ) ) { + spawnArgs.GetVector( "velocity", "0 0 0", tmp ); + temp = tmp[0]; + } else { + float speedRandom; + if ( !spawnArgs.GetFloat( "speedRandom", "0", speedRandom ) ) { + temp += gameLocal.random.CRandomFloat()*speedRandom; + } + } + if ( !spawnArgs.GetFloat( "speed_end", "0", temp2 ) ) { + temp2 = temp; + } + float speedDuration; + speedDuration = SEC2MS( spawnArgs.GetFloat( "speed_duration", "0" ) ); + speed.Init( gameLocal.time, speedDuration, temp, temp2 ); + if ( speedDuration > 0 && temp != temp2 ) { + // only support constant velocity projectiles in MP + // ( we also assume that no MP projectiles use speedRandom ) + assert( !gameLocal.isServer ); + updateVelocity = true; + } + launchSpeed = temp; + + spawnArgs.GetAngles( "angular_velocity", "0 0 0", angularVelocity ); + + linear_friction = spawnArgs.GetFloat( "linear_friction" ); + angular_friction = spawnArgs.GetFloat( "angular_friction" ); + contact_friction = spawnArgs.GetFloat( "contact_friction" ); + bounce = spawnArgs.GetFloat( "bounce" ); + mass = spawnArgs.GetFloat( "mass" ); + gravity = spawnArgs.GetFloat( "gravity" ); + fuse = spawnArgs.GetFloat( "fuse" ) + ( spawnArgs.GetFloat( "fuse_random", "0" ) * gameLocal.random.RandomFloat() ); + bounceCount = spawnArgs.GetInt( "bounce_count", "-1" ); + + //spawn impact entity information + impactEntity = spawnArgs.GetString("def_impactEntity",""); + numImpactEntities = spawnArgs.GetInt("numImpactEntities","0"); + ieMinPitch = spawnArgs.GetInt("ieMinPitch","0"); + ieMaxPitch = spawnArgs.GetInt("ieMaxPitch","0"); + ieSlicePercentage = spawnArgs.GetFloat("ieSlicePercentage","0.0"); + + projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); + projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); + projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); + projectileFlags.detonate_on_bounce = spawnArgs.GetBool( "detonate_on_bounce" ); + + lightStartTime = 0; + lightEndTime = 0; + + impactedEntity = 0; + + if ( health ) { + fl.takedamage = true; + } + +// RAVEN BEGIN +// abahr: + gravVec = ( idMath::Fabs(gravity) > VECTOR_EPSILON ) ? gameLocal.GetCurrentGravity(this) * gravity : vec3_zero; +// RAVEN END + + Unbind(); + + contents = 0; + clipMask = spawnArgs.GetBool( "clipmask_rendermodel", "1" ) ? MASK_SHOT_RENDERMODEL : MASK_SHOT_BOUNDINGBOX; + + // all projectiles are projectileclip + clipMask |= CONTENTS_PROJECTILECLIP; + + if ( spawnArgs.GetBool( "clipmask_largeshot", "1" ) ) { + clipMask |= CONTENTS_LARGESHOTCLIP; + } + + if ( spawnArgs.GetBool( "clipmask_moveable", "0" ) ) { + clipMask |= CONTENTS_MOVEABLECLIP; + } + if ( spawnArgs.GetBool( "clipmask_monsterclip", "0" ) ) { + clipMask |= CONTENTS_MONSTERCLIP; + } + + if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) { + contents |= CONTENTS_TRIGGER; + } + + if ( !spawnArgs.GetBool( "no_contents", "1" ) ) { + contents |= CONTENTS_PROJECTILE; + } + + clipMask |= CONTENTS_PROJECTILE; + + // don't do tracers on client, we don't know origin and direction + if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) { + SetModel( spawnArgs.GetString( "model_tracer" ) ); + projectileFlags.isTracer = true; + } + + physicsObj.SetMass( mass ); + physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); + physicsObj.SetBouncyness( bounce, !projectileFlags.detonate_on_bounce ); + physicsObj.SetGravity( gravVec ); + physicsObj.SetContents( contents ); + physicsObj.SetClipMask( clipMask | CONTENTS_WATER ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + physicsObj.SetOrigin( start ); + physicsObj.SetAxis( dir.ToMat3() ); + + if ( !gameLocal.isClient ) { + if ( fuse <= 0 ) { + // run physics for 1 second + RunPhysics(); + PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); + } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Explode, fuse ); + } else { + fuse -= timeSinceFire; + if ( fuse < 0.0f ) { + fuse = 0.0f; + } + PostEventSec( &EV_Fizzle, fuse ); + } + } + + idQuat q( dir.ToMat3().ToQuat() ); + rotation.Init( gameLocal.GetTime(), 0.0f, q, q ); + + if ( projectileFlags.isTracer ) { + StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); + } else { + StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); + } + + // used for the plasma bolts but may have other uses as well + if ( projectileFlags.randomShaderSpin ) { + float f = gameLocal.random.RandomFloat(); + f *= 0.5f; + renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f; + } + + UpdateVisuals(); + + // Make sure these come after update visuals so the origin and axis are correct + PlayEffect( "fx_launch", renderEntity.origin, renderEntity.axis ); + + flyEffect = PlayEffect( "fx_fly", renderEntity.origin, renderEntity.axis, true ); + flyEffectAttenuateSpeed = spawnArgs.GetFloat( "flyEffectAttenuateSpeed", "0" ); + + state = LAUNCHED; + + hitCount = 0; + + predictTime = prePredictTime; + + if ( spawnArgs.GetFloat( "delay_emit_damage" ) > 0.0f ) { + PostEventSec( &EV_EmitDamage, spawnArgs.GetFloat( "wait_emit_damage", "0" ), this ); + } + + if ( gameLocal.isServer ) { + // store launch information for networking + launchTime = gameLocal.time; + launchOrig = physicsObj.GetOrigin(); + launchDir = dir; + } else { + if ( predictedProjectiles ) { + physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); + } + } + + if ( g_perfTest_noProjectiles.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idProjectile::Think +================ +*/ +void idProjectile::Think( void ) { + // run physics + if ( thinkFlags & TH_PHYSICS ) { + + // Update the velocity to match the changing speed + if ( updateVelocity ) { + idVec3 vel; + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize ( ); + physicsObj.SetLinearVelocity ( speed.GetCurrentValue ( gameLocal.time ) * vel ); + if ( speed.IsDone ( gameLocal.time ) ) { + updateVelocity = false; + } + } + + RunPhysics(); + + // If we werent at rest and are now then start the atrest fuse + if ( physicsObj.IsAtRest( ) ) { + float fuse = spawnArgs.GetFloat( "fuse_atrest" ); + if ( fuse > 0.0f ) { + if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { + CancelEvents( &EV_Explode ); + PostEventSec( &EV_Explode, fuse ); + } else { + CancelEvents( &EV_Fizzle ); + PostEventSec( &EV_Fizzle, fuse ); + } + } + } + + // Stop the trail effect if the physics flag was removed + if ( flyEffect && flyEffectAttenuateSpeed > 0.0f ) { + if ( physicsObj.IsAtRest( ) ) { + flyEffect->Stop( ); + flyEffect = NULL; + } else { + float speed; + speed = idMath::ClampFloat( 0, flyEffectAttenuateSpeed, physicsObj.GetLinearVelocity ( ).LengthFast ( ) ); + flyEffect->Attenuate( speed / flyEffectAttenuateSpeed ); + } + } + + UpdateVisualAngles(); + } + + Present(); + + // add the light + if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) { + renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset; + renderLight.axis = GetPhysics()->GetAxis(); + if ( ( lightDefHandle != -1 ) ) { + if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) { + idVec3 color( 0, 0, 0 ); + if ( gameLocal.time < lightEndTime ) { + float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime ); + color.Lerp( lightColor, color, frac ); + } + renderLight.shaderParms[SHADERPARM_RED] = color.x; + renderLight.shaderParms[SHADERPARM_GREEN] = color.y; + renderLight.shaderParms[SHADERPARM_BLUE] = color.z; + } + gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); + } else { + lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } +} + +/* +================= +idProjectile::UpdateVisualAngles +================= +*/ +void idProjectile::UpdateVisualAngles() { + idVec3 linearVelocity( GetPhysics()->GetLinearVelocity() ); + + if( angularVelocity.Compare(ang_zero, VECTOR_EPSILON) ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), linearVelocity.ToNormal().ToMat3().ToQuat() ); + return; + } + + if( physicsObj.GetNumContacts() ) { + return; + } + + if( !rotation.IsDone(gameLocal.GetTime()) ) { + return; + } + + if( linearVelocity.Length() <= BOUNCE_SOUND_MIN_VELOCITY ) { + return; + } + + visualAngles += angularVelocity; + idQuat q = visualAngles.ToQuat() * linearVelocity.ToNormal().ToMat3().ToQuat(); + rotation.Init( gameLocal.GetTime(), gameLocal.GetMSec(), rotation.GetCurrentValue(gameLocal.GetTime()), q ); +} + +/* +================= +idProjectile::Collide +================= +*/ +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { + bool dummy = false; + return Collide( collision, velocity, dummy ); +} + +bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { + idEntity* ent; + idEntity* actualHitEnt = NULL; + idEntity* ignore; + const char* damageDefName; + idVec3 dir; + bool canDamage; + + hitTeleporter = false; + + if ( state == EXPLODED || state == FIZZLED || ( state == IMPACTED && predictedProjectiles ) ) { + return true; + } + + if ( IsHidden() && predictedProjectiles ) { + // teleported, ignore impacts + return false; + } + + // allow projectiles to hit triggers (teleports) + // predict this on a client + if ( collision.c.contents & CONTENTS_TRIGGER ) { + idEntity* trigger = gameLocal.entities[ collision.c.entityNum ]; + + if ( trigger ) { + if ( trigger->RespondsTo( EV_Touch ) || trigger->HasSignal( SIG_TOUCH ) ) { + + hitTeleporter = true; + + trace_t trace; + + trace.endpos = physicsObj.GetOrigin(); + trace.endAxis = physicsObj.GetAxis(); + + trace.c.contents = collision.c.contents; + trace.c.entityNum = collision.c.entityNum; + if( trigger->GetPhysics()->GetClipModel() ) { + trace.c.id = trigger->GetPhysics()->GetClipModel()->GetId(); + } else { + trace.c.id = 0; + } + + // hack to play the effect on clients when mispredicted (Hide() ensures it can't play twice for the same teleport) + int wasNewFrame = gameLocal.isNewFrame; + if ( gameLocal.isClient && predictedProjectiles ) { + gameLocal.isNewFrame = true; + } + + trigger->Signal( SIG_TOUCH ); + trigger->ProcessEvent( &EV_Touch, this, &trace ); + + if ( gameLocal.isClient && predictedProjectiles ) { + Hide(); + gameLocal.isNewFrame = wasNewFrame; + } + } + } + + // when we hit a trigger, align our velocity to the trigger's coordinate plane + if( gameLocal.isServer ) { + idVec3 up( 0.0f, 0.0f, 1.0f ); + idVec3 right = collision.c.normal.Cross( up ); + idMat3 mat( collision.c.normal, right, up ); + + physicsObj.SetLinearVelocity( -1.0f * (physicsObj.GetLinearVelocity() * mat.Transpose()) ); + physicsObj.SetLinearVelocity( idVec3( physicsObj.GetLinearVelocity()[ 0 ], -1.0 *physicsObj.GetLinearVelocity()[ 1 ], -1.0 * physicsObj.GetLinearVelocity()[ 2 ] ) ); + + // update the projectile's launchdir and launch origin + // this will propagate the change to the clients for prediction + // re-launch the projectile + + idVec3 newDir = physicsObj.GetLinearVelocity(); + newDir.Normalize(); + launchTime = gameLocal.time; + launchDir = newDir; + physicsObj.SetOrigin( physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 32.0f ) ); + physicsObj.SetAxis( newDir.ToMat3() ); + launchOrig = physicsObj.GetOrigin(); + } + + if( !(collision.c.contents & CONTENTS_SOLID) || hitTeleporter ) { + return false; + } + } + + // network clients: heuristic to skip predicting collisions + if ( gameLocal.isClient ) { + if ( predictedProjectiles ) { + if ( syncPhysics ) { + return false; + } + } else { + if ( spawnArgs.GetBool( "no_impact_prediction" ) || syncPhysics ) { + return false; + } + } + } + + // remove projectile when a 'noimpact' surface is hit + if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + PostEventMS( &EV_Remove, 0 ); + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + return true; + } + + // get the entity the projectile collided with + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent == owner.GetEntity() ) { + return true; + } + + // just get rid of the projectile when it hits a player in noclip + if ( ent->IsType( idPlayer::GetClassType() ) && static_cast( ent )->noclip ) { + PostEventMS( &EV_Remove, 0 ); + common->DPrintf( "Projectile collision no impact\n" ); + return true; + } + + // If the hit entity is bound to an actor use the actor instead + if ( ent->GetTeamMaster() && ent->GetTeamMaster()->IsType ( idActor::GetClassType() ) ) { + actualHitEnt = ent; + ent = ent->GetTeamMaster(); + } + + // Can the projectile damage? + canDamage = ent->fl.takedamage && !(( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE )); + + // direction of projectile + dir = velocity; + dir.Normalize(); + + // projectiles can apply an additional impulse next to the rigid body physics impulse +// RAVEN BEGIN +// abahr: added call to SkipDamageImpulse changed where push comes from + damageDefName = NULL; + if ( collision.c.materialType ) { + damageDefName = spawnArgs.GetString( va("def_damage_%s", collision.c.materialType->GetName()) ); + } + if ( !damageDefName || !*damageDefName ) { + damageDefName = spawnArgs.GetString ( "def_damage" ); + } + + if( damageDefName && damageDefName[0] ) { + const idDict* dict = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( dict ) { + ent->ApplyImpulse( this, collision.c.id, collision.endpos, dir, dict ); + } + } +// RAVEN END + + //Spawn any impact entities if necessary. + SpawnImpactEntities(collision, velocity); + + //Apply any impact force if the necessary + //ApplyImpactForce(ent, collision, dir); + + // MP: projectiles open doors + if ( gameLocal.isMultiplayer && ent->IsType( idDoor::GetClassType() ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { + ent->ProcessEvent( &EV_Activate , this ); + } + + // If the projectile hits water then we need to let the projectile keep going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + if ( !physicsObj.IsInWater( ) ) { + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + } + // Pass through water + return false; + } else if ( canDamage && ent->IsType( idActor::GetClassType() ) ) { + if ( !projectileFlags.detonate_on_actor ) { + return false; + } + } else { + bool bounce = false; + + // Determine if the projectile should bounce + bounce = !physicsObj.IsInWater() && !projectileFlags.detonate_on_world && !canDamage; + bounce = bounce && (bounceCount == -1 || bounceCount > 0); + //assert(collision.c.material); + if ( !bounce && collision.c.material && (collision.c.material->GetSurfaceFlags() & SURF_BOUNCE) ) { + bounce = !projectileFlags.detonate_on_bounce; + } + + if ( bounce ) { + if ( bounceCount != -1 ) { + bounceCount--; + } + + StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ); + + float len = velocity.Length(); + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + if ( ent->IsType ( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } else { + gameLocal.PlayEffect( + gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), + collision.c.point, collision.c.normal.ToMat3(), + false, vec3_origin, true ); + } + } else { + // FIXME: clean up + idMat3 axis( rotation.GetCurrentValue(gameLocal.GetTime()).ToMat3() ); + axis[0].ProjectOntoPlane( collision.c.normal ); + axis[0].Normalize(); + axis[2] = collision.c.normal; + axis[1] = axis[2].Cross( axis[0] ).ToNormal(); + + rotation.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("settle_duration")), rotation.GetCurrentValue(gameLocal.GetTime()), axis.ToQuat() ); + } + if ( actualHitEnt + && actualHitEnt != ent + && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) + {//bleh... + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); + actualHitEnt->Damage( this, owner, dir, damageDefName, damagePower, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + } + } + return false; + } + } + + SetOrigin( collision.endpos ); +// SetAxis( collision.endAxis ); + + // unlink the clip model because we no longer need it + GetPhysics()->UnlinkClip(); + + ignore = NULL; + +// RAVEN BEGIN +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + bool willPlayDamageEffect = true; + + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { + // if the projectile hit an ai + if ( ent->IsType( idAI::GetClassType() ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + player->AddProjectileHits( 1 ); + +// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. + idAI * ai_ent = static_cast(ent); + if( ai_ent->team == player->team) { + willPlayDamageEffect = false; + } + } + } + +// RAVEN END + + // if the hit entity takes damage + if ( canDamage ) { + + if ( damageDefName[0] != '\0' ) { + idVec3 dir = velocity; + dir.Normalize(); +// RAVEN BEGIN +// jdischler: code from the 'other' project..to ensure that if an attached head is hit, the body will use the head joint +// otherwise damage zones for head attachments no-worky + int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(collision.c.id); + if ( ent->IsType(idActor::GetClassType()) ) + { + idActor* entActor = static_cast(ent); + if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) + { + idAFAttachment* headEnt = static_cast(entActor->GetHead()); + if ( headEnt && headEnt->entityNumber == collision.c.entityNum ) + {//hit ent's head, get the proper joint for the head + hitJoint = entActor->GetAnimator()->GetJointHandle("head"); + } + } + } +// RAVEN END + ent->Damage( this, owner, dir, damageDefName, damagePower, hitJoint ); + + if( owner && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) ) { + statManager->WeaponHit( (const idActor*)(owner.GetEntity()), ent, methodOfDeath, hitCount == 0 ); + hitCount++; + } + } + } + + ignore = ent; + + if ( predictedProjectiles ) { + if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) { + ent->AddDamageEffect( collision, velocity, damageDefName, owner ); + } + + // hack to play the effect on clients when mispredicted (state/playedDamageEffect ensure it can't happen twice) + int wasNewFrame = gameLocal.isNewFrame; + if ( gameLocal.isClient ) { + gameLocal.isNewFrame = true; + } + + // if the the player is the attacker and the victim is teammate, don't play any effects. + if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect && ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) ) { + DefaultDamageEffect( collision, velocity, damageDefName ); + playedDamageEffect = true; + } + + Explode( &collision, false, ignore ); + + gameLocal.isNewFrame = wasNewFrame; + } else { + ent->AddDamageEffect ( collision, velocity, damageDefName, owner ); + + // if the the player is the attacker and the victim is teammate, don't play any effects. + if ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) { + DefaultDamageEffect( collision, velocity, damageDefName ); + } + + // don't predict explosions on clients + if ( gameLocal.isClient ) { + return true; + } + + Explode( &collision, false, ignore ); + } + + return true; +} + +void idProjectile::SpawnImpactEntities(const trace_t& collision, const idVec3 velocity) +{ + if( impactEntity.Length() == 0 || numImpactEntities == 0 ) + return; + + const idDict* impactEntityDict = gameLocal.FindEntityDefDict(impactEntity); + if(impactEntityDict == NULL) + return; + + idVec3 tempDirection; + idVec3 direction; + direction.Zero(); + + idVec3 up = collision.c.normal; + + //Calculate the axes for that are oriented to the impact point. + idMat3 impactAxes; + + idVec3 right = velocity.Cross(up); + idVec3 forward = up.Cross(right); + + right.Normalize(); + forward.Normalize(); + impactAxes[0] = forward; + impactAxes[1] = right; + impactAxes[2] = up; + + //Calculate the reflection vector by calculating the forward component and up component of the projectile direction + //idVec3 reflectionVelocity = (forward * (velocity*0.33f * forward));// - (up * (velocity * up)); + idVec3 reflectionVelocity = up * 0.01f; + + //The algorithm below will launch entities at a random pitch and somewhat random yaw. + //The yaw is calculated by dividing 360 by the number of entities to spawn. This creates + //a distribution slice. Then using the slice percentage, this will determine how much of the + //slice to use. + //This creates a random,but somewhat even coverage of the circle. + + //Calculate the slice size and pick a random start position. + int sliceSize = 360 / numImpactEntities; + int startPosition = rvRandom::irand(0, 360); + + //Move the origin away from the collision point. This prevents the projectiles + //from colliding with the surface. + idVec3 origin = collision.endpos; + origin += 10.0f * collision.c.normal; + for(int i = 0;i < numImpactEntities; i++) + { + idProjectile* spawnProjectile = NULL; + gameLocal.SpawnEntityDef(*impactEntityDict,(idEntity**)&spawnProjectile); + if(spawnProjectile != NULL) + { + int pitch = rvRandom::irand(ieMinPitch, ieMaxPitch); + int sliceMiddle = (i * sliceSize) + startPosition; + int sliceSloppiness = (sliceSize * ieSlicePercentage) / 2; + + int yaw = rvRandom::irand(sliceMiddle - sliceSloppiness, sliceMiddle + sliceSloppiness); + yaw = yaw % 360; + + float cosPitch = idMath::Cos(DEG2RAD(pitch)); + tempDirection.x = cosPitch * idMath::Cos(DEG2RAD(yaw)); + tempDirection.y = cosPitch * idMath::Sin(DEG2RAD(yaw)); + tempDirection.z = idMath::Sin(DEG2RAD(pitch)); + + spawnProjectile->SetOwner(owner); + + //Now orient the direction to the surface world orientation. + direction = impactAxes * tempDirection; + spawnProjectile->Launch(origin, direction, reflectionVelocity); + } + } +} + +/* +================= +idProjectile::DefaultDamageEffect +================= +*/ +void idProjectile::DefaultDamageEffect( const trace_t &tr, const idVec3 &velocity, const char *damageDefName ) { + idEntity* ent; + idMat3 axis; + ent = gameLocal.entities[ tr.c.entityNum ]; + + // Make sure we want to play effects + if ( (!*spawnArgs.GetString( "def_splash_damage" ) || spawnArgs.GetBool( "bloodyImpactEffect" )) + && owner.GetEntity( ) && !ent->CanPlayImpactEffect( owner, ent ) ) { + return; + } + + // Effect axis when hitting actors is along the direction of impact because actor models are + // very detailed. + if ( ent->IsType( idActor::GetClassType() ) || ent->IsType( idAFAttachment::GetClassType() ) ) { + idVec3 dir; + dir = velocity; + dir.Normalize ( ); + axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); + + // Play an actor specific impact effect? + const idDecl *actorImpactEffect = gameLocal.GetEffect( spawnArgs, "fx_impact_actor", tr.c.materialType ); + if ( actorImpactEffect ) { + gameLocal.PlayEffect( actorImpactEffect, tr.c.point, axis, false, vec3_origin, true, true ); + return; + } + } else { + axis = tr.c.normal.ToMat3(); + } + + // Play an impact effect on the entity that got hit + if ( ent->IsType( idMover::GetClassType ( ) ) ) { + ent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true ); + } else { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true ); + } +} + +/* +================ +idProjectile::Killed +================ +*/ +void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( spawnArgs.GetBool( "detonate_on_death" ) ) { + Explode( NULL, true ); + physicsObj.ClearContacts(); + physicsObj.PutToRest(); + } else { + Fizzle(); + } +} + +/* +================ +idProjectile::Fizzle +================ +*/ +void idProjectile::Fizzle( void ) { + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + if ( predictedProjectiles ) { + if ( state == IMPACTED ) { + return; + } + } else { + if ( gameLocal.isClient ) { + return; + } + } + + StopSound( SND_CHANNEL_BODY, false ); + StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); + + gameLocal.PlayEffect( spawnArgs, "fx_fuse", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + physicsObj.PutToRest(); + + // No more fly effects + StopEffect( "fx_fly" ); + if( flyEffect) { + //flyEffect->Event_Remove(); + } + + Hide(); + FreeLightDef(); + + state = FIZZLED; + + int removeTime = spawnArgs.GetInt( "remove_time", "1500" ); + + if ( predictedProjectiles ) { + // ensure the projectile makes it in to at least one snapshot + int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" ); if ( removeTime < snapshotTime ) + if ( removeTime < snapshotTime ) { + removeTime = snapshotTime; + } + } + + CancelEvents( &EV_Fizzle ); + PostEventMS( &EV_Remove, removeTime ); +} + +/* +================ +idProjectile::Event_RadiusDamage +================ +*/ +void idProjectile::Event_RadiusDamage( idEntity *ignore ) { + const char *splash_damage = spawnArgs.GetString( "def_splash_damage" ); + if ( splash_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, splash_damage, damagePower, &hitCount ); + } +} + +/* +================ +idProjectile::Event_ResidualDamage +================ +*/ +void idProjectile::Event_ResidualDamage ( idEntity* ignore ) { + const char *residual_damage = spawnArgs.GetString( "def_residual_damage" ); + if ( residual_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, residual_damage, damagePower, &hitCount ); + } + + // Keep the loop going + PostEventSec ( &EV_ResidualDamage, spawnArgs.GetFloat ( "delay_residual" ), ignore ); +} + +void idProjectile::Event_EmitDamage ( idEntity* ignore ) { + const char *emit_damage = spawnArgs.GetString( "def_emit_damage" ); + if ( emit_damage[0] != '\0' ) { + gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, emit_damage, damagePower, &hitCount ); + } + + // Keep the loop going + PostEventSec ( &EV_EmitDamage, spawnArgs.GetFloat ( "delay_emit_damage" ), ignore ); +} + +/* +================ +idProjectile::Explode +================ +*/ +void idProjectile::Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore, const char *sndExplode ) { + idVec3 normal, endpos; + int removeTime; + + if ( state == EXPLODED || state == FIZZLED ) { + return; + } + + if ( predictedProjectiles && state == IMPACTED ) { + return; + } + + CancelEvents( &EV_EmitDamage ); + + if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { + GetPhysics()->SetAxis( normal.ToMat3() ); + } else { + normal = collision ? collision->c.normal : idVec3( 0, 0, 1 ); + } + endpos = ( collision ) ? collision->endpos : GetPhysics()->GetOrigin(); + + removeTime = spawnArgs.GetInt( "remove_time", "1500" ); + + // play sound + StopSound( SND_CHANNEL_BODY, false ); + StartSound( sndExplode, SND_CHANNEL_BODY, 0, false, NULL ); + + idVec3 fxDir; + if ( physicsObj.GetGravityNormal( ) != vec3_zero ) { + fxDir = -physicsObj.GetGravityNormal( ); + } else { + fxDir = -physicsObj.GetLinearVelocity( ); + fxDir.Normalize( ); + } + + if ( predictedProjectiles ) { + if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) { + PlayDetonateEffect( endpos, fxDir.ToMat3() ); + } + } else if ( showExplodeFX ) { + PlayDetonateEffect( endpos, fxDir.ToMat3() ); + } + + // Stop the fly effect without destroying particles to ensure the trail within can persist. + StopEffect( "fx_fly" ); + + // Stop the remaining particles + StopAllEffects( ); + + Hide(); + FreeLightDef(); + + GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + 8.0f * normal ); + + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.PutToRest(); + + if ( predictedProjectiles ) { + state = showExplodeFX ? EXPLODED : IMPACTED; + } else { + state = EXPLODED; + } + + if ( gameLocal.isClient ) { + return; + } + + // alert the ai + gameLocal.AlertAI( owner.GetEntity() ); + + // bind the projectile to the impact entity if necesary + if ( collision && gameLocal.entities[collision->c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { + Bind( gameLocal.entities[collision->c.entityNum], true ); + } + + if ( predictedProjectiles ) { + removeTime = spawnArgs.GetInt( "detonate_remove_time", "0" ); + + // ensure the projectile makes it in to at least one snapshot + int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" ); + if ( removeTime < snapshotTime ) { + removeTime = snapshotTime; + } + } else { + removeTime = 0; + } + + // splash damage + float delay = spawnArgs.GetFloat( "delay_splash" ); + if ( delay ) { + if ( removeTime < delay * 1000 ) { + removeTime = ( delay + 0.10 ) * 1000; + } + PostEventSec( &EV_RadiusDamage, delay, ignore ); + } else { + Event_RadiusDamage( ignore ); + } + + // Residual damage (damage over time) + delay = SEC2MS ( spawnArgs.GetFloat ( "delay_residual" ) ); + if ( delay > 0.0f ) { + PostEventMS ( &EV_ResidualDamage, delay, ignore ); + + // Keep the projectile around until the residual damage is done + delay = SEC2MS ( spawnArgs.GetFloat ( "residual_time" ) ); + if ( removeTime < delay ) { + removeTime = delay; + } + } + + CancelEvents( &EV_Explode ); + PostEventMS( &EV_Remove, removeTime ); +} + +/* +================ +idProjectile::GetVelocity +================ +*/ +idVec3 idProjectile::GetVelocity( const idDict *projectile ) { + idVec3 velocity; + + velocity.Zero ( ); + if ( projectile && !projectile->GetFloat ( "speed", "0", velocity.x ) ) { + projectile->GetVector( "velocity", "0 0 0", velocity ); + } + return velocity; +} + +/* +================ +idProjectile::GetGravity +================ +*/ +idVec3 idProjectile::GetGravity( const idDict *projectile ) { + if ( projectile ) { + return gameLocal.GetGravity ( ) * projectile->GetFloat( "gravity" ); + } + return vec3_origin; +} + +/* +================ +idProjectile::PlayPainEffect +================ +*/ +void idProjectile::PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& dir ) { + static int damageTable[] = { 100, 50, 25, 10, 0 }; + int index; + + // Normalize the damage value to the damage table + for ( index = 0; damage < damageTable[index] && damageTable[index]; index ++ ); + + // loop until we find a pain effect, trying lower damage numbers if needed + for ( ; damageTable[index]; index ++ ) { + // Try the pain effect for the current damage value and if it plays then + // we are done + if ( ent->PlayEffect( gameLocal.GetEffect( spawnArgs, va( "fx_pain%d", damageTable[index] ), materialType ), origin, dir.ToMat3() ) ) { + return; + } + } + + // Play the default pain effect + ent->PlayEffect( gameLocal.GetEffect ( spawnArgs, "fx_pain", materialType ), origin, dir.ToMat3() ); +} + +/* +================ +idProjectile::PlayDetonateEffect +================ +*/ +void idProjectile::PlayDetonateEffect( const idVec3& origin, const idMat3& axis, bool forceImpact ) { + if( physicsObj.HasGroundContacts() || ( forceImpact && predictedProjectiles ) ) { + if ( spawnArgs.GetBool( "detonateTestGroundMaterial" ) ) { + trace_t tr; + idVec3 down; + down = GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal()*8.0f; + gameLocal.Translation( this, tr, GetPhysics()->GetOrigin(), down, GetPhysics()->GetClipModel(), GetPhysics()->GetClipModel()->GetAxis(), GetPhysics()->GetClipMask(), this ); + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), origin, axis, false, vec3_origin, true, true ); + } else { + gameLocal.PlayEffect( spawnArgs, "fx_impact", origin, axis, false, vec3_origin, true, true ); + } + return; + } + + gameLocal.PlayEffect( spawnArgs, "fx_detonate", origin, axis, false, vec3_origin, true, true ); +} + +/* +================ +idProjectile::Event_Explode +================ +*/ +void idProjectile::Event_Explode( void ) { + // events are processed outside of the think loop, so set the current thinking ent appropriately + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Explode( NULL, true ); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Fizzle +================ +*/ +void idProjectile::Event_Fizzle( void ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + Fizzle(); + gameLocal.currentThinkingEntity = think; +} + +/* +================ +idProjectile::Event_Touch +================ +*/ +void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { + if ( IsHidden() ) { + return; + } + + if ( other != owner.GetEntity() ) { + idEntity* think = gameLocal.currentThinkingEntity; + gameLocal.currentThinkingEntity = this; + + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + + bool playDefaultDamageEffect = predictedProjectiles ? !playedDamageEffect : true; + if ( playDefaultDamageEffect ) { + DefaultDamageEffect( collision, collision.c.normal, NULL ); + playedDamageEffect = true; + } + Explode( NULL, !predictedProjectiles ); + + gameLocal.currentThinkingEntity = think; + } +} + +/* +================ +idProjectile::ClientPredictionThink +================ +*/ +void idProjectile::ClientPredictionThink( void ) { + if ( !renderEntity.hModel && clientEntities.IsListEmpty() ) { + return; + } + if ( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + Think(); +} + +/* +================ +idProjectile::WriteToSnapshot +================ +*/ +void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { + if ( syncPhysics ) { + physicsObj.WriteToSnapshot( msg ); + } + + msg.WriteBits( state, 3 ); + if ( state >= LAUNCHED ) { + // feed the client with start position, direction and time. let the client do everything else + // this won't change during projectile life and be completely deltified away + msg.WriteLong( launchTime ); + msg.WriteFloat( launchOrig[ 0 ] ); + msg.WriteFloat( launchOrig[ 1 ] ); + msg.WriteFloat( launchOrig[ 2 ] ); + msg.WriteDir( launchDir, 24 ); + + int ownerNum = (GetOwner() && GetOwner()->entityNumber < MAX_CLIENTS) ? GetOwner()->entityNumber : MAX_CLIENTS; + msg.WriteBits( ownerNum, idMath::BitsForInteger(MAX_CLIENTS) ); + } +} + +/* +================ +idProjectile::ReadFromSnapshot +================ +*/ +void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { + projectileState_t newState; + idEntity *ownerEnt = NULL; + int newLaunchTime; + + if ( syncPhysics ) { + physicsObj.ReadFromSnapshot( msg ); + } + + newState = (projectileState_t) msg.ReadBits( 3 ); + if ( newState >= LAUNCHED ) { + newLaunchTime = msg.ReadLong(); + launchOrig[ 0 ] = msg.ReadFloat(); + launchOrig[ 1 ] = msg.ReadFloat(); + launchOrig[ 2 ] = msg.ReadFloat(); + launchDir = msg.ReadDir( 24 ); + + int ownerNum = msg.ReadBits( idMath::BitsForInteger(MAX_CLIENTS) ); + if ( ownerNum < MAX_CLIENTS && gameLocal.entities[ ownerNum ] && gameLocal.entities[ ownerNum ]->IsType( idPlayer::GetClassType() ) ) { + ownerEnt = gameLocal.entities[ ownerNum ]; + } + + if ( predictedProjectiles && launchTime != newLaunchTime ) { + launchTime = newLaunchTime; + + if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); + } + + if ( state == LAUNCHED && newState == LAUNCHED ) { + Show(); + } + + UpdateVisuals(); + } + + if ( !predictedProjectiles ) { + launchTime = newLaunchTime; + } + } + + // we always create and launch at the same time + // state on a client can be SPAWNED, LAUNCHED, EXPLODED + // expect never to get a CREATED projectile, they should launch right away + if ( predictedProjectiles ) { + if ( msg.HasChanged() ) { + if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); + } + } + UpdateVisuals(); + + assert( state != CREATED && newState != CREATED ); + + if ( newState != state ) { + if ( state < LAUNCHED && newState >= LAUNCHED ) { + Create( ownerEnt, launchOrig, launchDir ); + Launch( launchOrig, launchDir, vec3_origin ); + Show(); + } + switch ( newState ) { + case FIZZLED: + Fizzle(); + break; + case EXPLODED: + case IMPACTED: + Explode( NULL, state == EXPLODED ); + break; + } + } + } else { + assert( state != CREATED && state != FIZZLED && newState != CREATED ); + + if ( newState != state ) { + switch ( newState ) { + case LAUNCHED: + Create( NULL, launchOrig, launchDir ); + Launch( launchOrig, launchDir, vec3_origin ); + Show(); + break; + case FIZZLED: + case EXPLODED: + if ( state != EXPLODED ) { + StopSound( SND_CHANNEL_BODY, false ); + StopAllEffects(); + Hide(); + FreeLightDef(); + state = EXPLODED; + } + break; + } + } + + if ( !syncPhysics && state == LAUNCHED ) { + idMat3 axis = launchDir.ToMat3(); + idVec3 origin( launchOrig ); + origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; + physicsObj.SetAxis( axis ); + physicsObj.SetOrigin( origin ); + physicsObj.SetLinearVelocity( launchSpeed * launchDir ); + } + UpdateVisuals(); + } +} + +/* +=============== +idProjectile::ClientStale +=============== +*/ +bool idProjectile::ClientStale( void ) { + // delete stale projectile ents. if they pop back in pvs, they will be re-spawned ( rare case anyway ) + StopAllEffects(); + return true; +} + +/* +================ +idProjectile::GetPhysicsToVisualTransform +================ +*/ +bool idProjectile::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + axis = rotation.GetCurrentValue( gameLocal.GetTime() ).ToMat3() * GetPhysics()->GetAxis().Transpose(); + + origin.Zero(); + return true; +} + +/* +=============================================================================== + + idGuidedProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, idGuidedProjectile ) +END_CLASS + +/* +================ +idGuidedProjectile::idGuidedProjectile( void ) +================ +*/ +idGuidedProjectile::idGuidedProjectile( void ) { + guideType = GUIDE_NONE; + turn_max.Init ( gameLocal.time, 0, 0.0f, 0.0f ); +} + +/* +================= +idGuidedProjectile::~idGuidedProjectile +================= +*/ +idGuidedProjectile::~idGuidedProjectile() { +} + +/* +================ +idGuidedProjectile::Save +================ +*/ +void idGuidedProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( guideType ); + guideEnt.Save( savefile ); + savefile->WriteVec3 ( guideDir ); + savefile->WriteVec3 ( guidePos ); + savefile->WriteJoint ( guideJoint ); + savefile->WriteFloat( guideMinDist ); // cnicholson: Added unsaved var + + savefile->WriteInt ( driftTime ); + savefile->WriteInt ( driftRate ); + savefile->WriteFloat ( driftRange ); + savefile->WriteFloat ( driftRadius ); + savefile->WriteFloat ( driftDiversity ); // cnicholson: Added unsaved var + savefile->WriteFloat ( driftAngle ); + savefile->WriteFloat ( driftAngleStep ); + savefile->WriteFloat ( driftProjectRange ); + + savefile->WriteFloat( turn_max.GetStartTime() ); + savefile->WriteFloat( turn_max.GetDuration() ); + savefile->WriteFloat( turn_max.GetStartValue() ); + savefile->WriteFloat( turn_max.GetEndValue() ); + + savefile->WriteInt ( launchTime ); + savefile->WriteInt ( guideDelay ); + savefile->WriteInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::Restore +================ +*/ +void idGuidedProjectile::Restore( idRestoreGame *savefile ) { + float set; + savefile->ReadInt ( guideType ); + guideEnt.Restore( savefile ); + savefile->ReadVec3 ( guideDir ); + savefile->ReadVec3 ( guidePos ); + savefile->ReadJoint ( guideJoint ); + savefile->ReadFloat( guideMinDist ); // cnicholson: Added unrestored var + + savefile->ReadInt ( driftTime ); + savefile->ReadInt ( driftRate ); + savefile->ReadFloat ( driftRange ); + savefile->ReadFloat ( driftRadius ); + savefile->ReadFloat ( driftDiversity ); // cnicholson: Added unrestored var + savefile->ReadFloat ( driftAngle ); + savefile->ReadFloat ( driftAngleStep ); + savefile->ReadFloat ( driftProjectRange ); + + savefile->ReadFloat( set ); + turn_max.SetStartTime( set ); + savefile->ReadFloat( set ); + turn_max.SetDuration( set ); + savefile->ReadFloat( set ); + turn_max.SetStartValue( set ); + savefile->ReadFloat( set ); + turn_max.SetEndValue( set ); + + savefile->ReadInt ( launchTime ); + savefile->ReadInt ( guideDelay ); + savefile->ReadInt ( driftDelay ); +} + +/* +================ +idGuidedProjectile::GetGuideDir +================ +*/ +bool idGuidedProjectile::GetGuideDir ( idVec3 &outDir, float& outDist ) { + // Dont start guiding immeidately? + if ( gameLocal.GetTime() - launchTime < guideDelay ) { + return false; + } + + switch ( guideType ) { + case GUIDE_ENTITY: + // If the guide entity is gone or dead then cancel the guide + if ( !guideEnt.GetEntity ( ) || (guideEnt->fl.takedamage && guideEnt->health <= 0 ) ) { + CancelGuide ( ); + return false; + } + // Use eye position for actors and center of bounds for everything else + if ( guideJoint != INVALID_JOINT ) { + idMat3 jointAxis; + guideEnt->GetAnimator()->GetJointTransform( guideJoint, gameLocal.GetTime(), outDir, jointAxis ); + outDir = guideEnt->GetRenderEntity()->origin + (outDir*guideEnt->GetRenderEntity()->axis); + if ( !guidePos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * guideEnt->GetRenderEntity()->axis; + outDir += jointAxis[0]*guidePos[0]; + outDir += jointAxis[1]*guidePos[1]; + outDir += jointAxis[2]*guidePos[2]; + } + } else { + outDir = guideEnt->GetPhysics()->GetAbsBounds().GetCenter(); + if ( guideEnt->IsType( idActor::GetClassType() ) ) { + outDir += static_cast(guideEnt.GetEntity())->GetEyePosition(); + outDir *= 0.5f; + } + } + outDir -= physicsObj.GetOrigin(); + break; + + case GUIDE_POS: + outDir = guidePos - physicsObj.GetOrigin(); + break; + + case GUIDE_DIR: + // Project our current position on to the desired direction + outDir = guidePos + guideDir * ((physicsObj.GetOrigin() - guidePos) * guideDir); + + // Seek towards a point forward along our desired direction + outDir = (outDir + guideDir * (guideMinDist * 1.10f)) - physicsObj.GetOrigin(); + break; + + default: + return false; + } + + // Add drifting + if ( driftRate && gameLocal.GetTime() - launchTime > driftDelay ) { + idMat3 axis; + + outDist = outDir.NormalizeFast(); + axis = outDir.ToMat3(); + + if ( gameLocal.time > driftTime ) { + driftRadius = driftRange + gameLocal.random.RandomFloat ( ) * driftRange * driftDiversity; + driftTime = gameLocal.time + driftRate; + } else { + driftAngle += driftAngleStep * MS2SEC ( gameLocal.msec ); + idMath::AngleNormalize360 ( driftAngle ); + } + + float angle; + angle = DEG2RAD ( driftAngle ); + outDir = physicsObj.GetOrigin ( ) + outDir * Min( outDist, driftProjectRange ); + outDir += axis[2] * (driftRadius * idMath::Sin ( angle )); + outDir += axis[1] * (driftRadius * idMath::Cos ( angle )); + + outDir -= physicsObj.GetOrigin(); + } + + outDist = outDir.Normalize ( ); + + return true; +} + +/* +================ +idGuidedProjectile::Think +================ +*/ +void idGuidedProjectile::Think( void ) { + + if ( state == LAUNCHED ) { + idVec3 dir; + idVec3 vel; + float angle; + float maxangle; + idMat3 axis; + float dist; + + // crank up to normal speed ? + if ( guideDelay && gameLocal.GetTime() - launchTime >= guideDelay ) { + float newSpeed = spawnArgs.GetFloat( "speed" ); + float newSpeed2; + if ( !spawnArgs.GetFloat ( "speed_end", "0", newSpeed2 ) ) { + newSpeed2 = newSpeed; + } + float newSpeedDuration; + newSpeedDuration = SEC2MS( spawnArgs.GetFloat ( "speed_duration", "0" ) ); + speed.Init ( gameLocal.time, newSpeedDuration, newSpeed, newSpeed2 ); + guideDelay = 0; + } + + if ( !GetGuideDir( dir, dist ) ) { + idProjectile::Think(); + return; + } + + // Direction of travel + vel = physicsObj.GetLinearVelocity(); + vel.Normalize(); + + // Calculate the angle between the current projectile direction and where we want to go + angle = RAD2DEG( idMath::ACos( dir * vel ) ); + + // Make sure the angle doesnt cross our max turn radius + maxangle = turn_max.GetCurrentValue( gameLocal.time ); + if ( angle < -maxangle ) { + angle = -maxangle; + } else if ( angle > maxangle ) { + angle = maxangle; + } + + // Debug information + if ( g_debugWeapon.GetBool ( ) ) { + gameRenderWorld->DebugArrow( colorCyan, physicsObj.GetOrigin(), physicsObj.GetOrigin() + vel * 50.0f, 10.0f ); + gameRenderWorld->DebugArrow( colorMagenta, physicsObj.GetOrigin(), physicsObj.GetOrigin() + dir * 50.0f, 10.0f ); + } + + // Calculate the new axis by rotating the current forward vector around the cross of the forward + // vector and the direction vector. + vel = vel * idRotation( vec3_origin, dir.Cross ( vel ), angle ); + physicsObj.SetLinearVelocity( vel * GetSpeed ( ) ); + + // If within the minium distance to the target anything over a 45 degree change will cancel the guide + if ( guideMinDist != 0.0f && dist < guideMinDist ) { + // Stop guiding if we have passed our target + vel = physicsObj.GetLinearVelocity ( ); + vel.Normalize( ); + if ( vel * dir < 0.7f ) { + guideType = GUIDE_NONE; + } + } + + idProjectile::Think(); + } else { + idProjectile::Think(); + } +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + launchTime = gameLocal.GetTime(); + + if ( owner.GetEntity() ) { + if ( owner.GetEntity()->IsType( idAI::GetClassType() ) ) { + GuideTo ( static_cast( owner.GetEntity() )->GetEnemy() ); + } + } + + guideMinDist = spawnArgs.GetFloat ( "min_dist", "128" ); + guideDelay = SEC2MS(spawnArgs.GetFloat ( "delayGuide" ) + ( gameLocal.random.RandomFloat ( ) * spawnArgs.GetFloat ( "delayGuide_random")) ); + + if ( guideDelay ) { + float delaySpeed; + if ( spawnArgs.GetFloat( "delaySpeed", "0", delaySpeed ) ) { + float delaySpeed2; + if ( !spawnArgs.GetFloat ( "delaySpeed_end", "0", delaySpeed2 ) ) { + delaySpeed2 = delaySpeed; + } + float delaySpeedDuration; + delaySpeedDuration = SEC2MS( spawnArgs.GetFloat ( "delaySpeed_duration", "0" ) ); + speed.Init( gameLocal.time, delaySpeedDuration, delaySpeed, delaySpeed2 ); + physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); + } + } + + driftTime = 0; + driftRate = SEC2MS ( spawnArgs.GetFloat ( "driftRate", "0" ) ); + driftRange = spawnArgs.GetFloat ( "driftRange" ); + driftDiversity = spawnArgs.GetFloat ( "driftDiversity", ".5" ); + driftAngle = gameLocal.random.RandomFloat ( ) * 360.0f; + driftAngleStep = spawnArgs.GetFloat ( "driftRotate" ); + driftAngleStep += gameLocal.random.CRandomFloat ( ) * (driftAngleStep * driftDiversity) * (gameLocal.random.RandomFloat()<0.5f?-1.0f:1.0f); + driftDelay = SEC2MS(spawnArgs.GetFloat ( "driftDelay" )); + + driftProjectRange = spawnArgs.GetFloat ( "driftProjectRange", "128" ); + + // Turn rate can be ramped up over time + turn_max.Init ( gameLocal.time, + SEC2MS(spawnArgs.GetFloat ( "turn_accel", "0" )), + 0, + spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.GetMHz() ); + + UpdateVisuals(); +} + +/* +================= +idGuidedProjectile::Launch +================= +*/ +void idGuidedProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + idProjectile::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + rvDriftingProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvDriftingProjectile ) +END_CLASS + +/* +================ +rvDriftingProjectile::rvDriftingProjectile( void ) +================ +*/ +rvDriftingProjectile::rvDriftingProjectile( void ) { +} + +/* +================= +rvDriftingProjectile::~rvDriftingProjectile +================= +*/ +rvDriftingProjectile::~rvDriftingProjectile ( void ) { +} + +/* +================ +rvDriftingProjectile::Save +================ +*/ +void rvDriftingProjectile::Save( idSaveGame *savefile ) const { + savefile->WriteVec3 ( startDir ); + savefile->WriteVec3 ( startOrigin ); + savefile->WriteMat3 ( startAxis ); + savefile->WriteFloat ( startSpeed ); + savefile->WriteFloat ( driftOffsetMax ); + savefile->WriteFloat ( driftSpeedMax ); + savefile->WriteFloat ( driftTime ); + + savefile->WriteInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->WriteInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Restore +================ +*/ +void rvDriftingProjectile::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3 ( startDir ); + savefile->ReadVec3 ( startOrigin ); + savefile->ReadMat3 ( startAxis ); + savefile->ReadFloat ( startSpeed ); + savefile->ReadFloat ( driftOffsetMax ); + savefile->ReadFloat ( driftSpeedMax ); + savefile->ReadFloat ( driftTime ); + + savefile->ReadInterpolate( driftSpeed ); + for( int ix = 0; ix < 2; ++ix ) { + savefile->ReadInterpolate( driftOffset[ix] ); + } +} + +/* +================ +rvDriftingProjectile::Think +================ +*/ +void rvDriftingProjectile::Think( void ) { + idVec3 diff; + idVec3 origin; + idVec3 oldOrigin; + idVec3 dir; + float dist; + + oldOrigin = GetPhysics()->GetOrigin ( ); + + diff = oldOrigin - startOrigin; + dist = diff.Length ( ); + origin = startOrigin + startDir * dist; + + if ( driftSpeed.IsDone ( gameLocal.time ) ) { + driftSpeed.Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftSpeed.GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftSpeedMax * (driftSpeed.GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[0].IsDone ( gameLocal.time ) ) { + driftOffset[0].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, + driftOffset[0].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[0].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + if ( driftOffset[1].IsDone ( gameLocal.time ) ) { + driftOffset[1].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat()*driftTime, + driftOffset[1].GetCurrentValue ( gameLocal.time ), + gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[1].GetCurrentValue ( gameLocal.time )<0?1:-1) ); + } + + origin += startAxis[1] * driftOffset[0].GetCurrentValue ( gameLocal.time ); + origin += startAxis[2] * driftOffset[1].GetCurrentValue ( gameLocal.time ); + + GetPhysics ( )->SetOrigin ( origin ); + GetPhysics ( )->SetLinearVelocity ( startDir * (startSpeed + driftSpeed.GetCurrentValue ( gameLocal.time )) ); + + idProjectile::Think(); + + // Now orient the projectile using the old origin + dir = GetPhysics()->GetOrigin() - oldOrigin; + dir.Normalize ( ); + GetPhysics ( )->SetAxis ( dir.ToMat3 ( ) ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower) { + startDir = dir; + startOrigin = start; + startAxis = dir.ToMat3(); + + driftOffsetMax = spawnArgs.GetFloat ( "driftOffset", "50" ); + driftSpeedMax = spawnArgs.GetFloat ( "driftSpeed", "50" ); + driftTime = SEC2MS ( spawnArgs.GetFloat ( "driftTime", ".5" ) ); + + idProjectile::Launch ( start, dir, pushVelocity, timeSinceFire, dmgPower ); + + startSpeed = GetPhysics()->GetLinearVelocity().Length ( ); +} + +/* +================= +rvDriftingProjectile::Launch +================= +*/ +void rvDriftingProjectile::UpdateVisualAngles ( void ) { + rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), GetPhysics()->GetAxis().ToQuat() ); +} + +/* +=============================================================================== + + rvSpawnerProjectile + +=============================================================================== +*/ + +CLASS_DECLARATION( idProjectile, rvSpawnerProjectile ) + EVENT( EV_PostSpawn, rvSpawnerProjectile::Event_PostSpawn ) +END_CLASS + +/* +================ +rvSpawnerProjectile::rvSpawnerProjectile( void ) +================ +*/ +rvSpawnerProjectile::rvSpawnerProjectile( void ) { + spawnState = STATE_NONE; +} + +/* +================= +rvSpawnerProjectile::~rvSpawnerProjectile +================= +*/ +rvSpawnerProjectile::~rvSpawnerProjectile ( void ) { + if ( spawnState == STATE_ADDED && spawner ) { + spawner->RemoveSpawnPoint ( this ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::Spawn ( void ) { + if ( *spawnArgs.GetString ( "spawner" ) ) { + PostEventMS ( &EV_PostSpawn, 0 ); + } +} + +/* +================= +rvSpawnerProjectile::SetSpawner +================= +*/ +void rvSpawnerProjectile::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +================= +rvSpawnerProjectile::Think +================= +*/ +void rvSpawnerProjectile::Think ( void ) { + idProjectile::Think ( ); + + if ( physicsObj.IsAtRest ( ) ) { + if ( spawnState == STATE_NONE && spawner ) { + spawner->AddSpawnPoint ( this ); + spawnState = STATE_ADDED; + } + } +} + +/* +================= +rvSpawnerProjectile::Event_PostSpawn +================= +*/ +void rvSpawnerProjectile::Event_PostSpawn ( void ) { + const char* temp; + temp = spawnArgs.GetString ( "spawner" ); + if ( temp && *temp ) { + idEntity* ent; + ent = gameLocal.FindEntity ( temp ); + if ( !ent ) { + gameLocal.Warning ( "spawner entity ('%s') not found for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else if ( !ent->IsType ( rvSpawner::GetClassType() ) ) { + gameLocal.Warning ( "spawner entity ('%s') is not of type rvSpawner for rvSpawnerProjectile '%s'", temp, GetName ( ) ); + } else { + SetSpawner ( static_cast(ent) ); + } + } +} + + +/* +=============================================================================== + + rvMIRVProjectile + +=============================================================================== +*/ +idEventDef EV_LaunchWarheads( "launchWarheads" ); + +CLASS_DECLARATION( idProjectile, rvMIRVProjectile ) + EVENT( EV_LaunchWarheads, rvMIRVProjectile::Event_LaunchWarheads ) +END_CLASS + +/* +================ +rvMIRVProjectile::rvMIRVProjectile( void ) +================ +*/ +rvMIRVProjectile::rvMIRVProjectile( void ) { + +} + +/* +================= +rvMIRVProjectile::~rvMIRVProjectile +================= +*/ +rvMIRVProjectile::~rvMIRVProjectile ( void ) { + +} + +/* +================ +void rvMIRVProjectile::Spawn( void ) +================ +*/ +void rvMIRVProjectile::Spawn( void ) { + + float launchDelay = spawnArgs.GetFloat("warhead_fuse", "0"); + //post event for warhead launch + PostEventSec( &EV_LaunchWarheads, launchDelay ); +} + +/* +================ +void rvMIRVProjectile::Event_LaunchWarheads( void ) +================ +*/ +void rvMIRVProjectile::Event_LaunchWarheads( void ) { + + const char* warhead; + int count; + + warhead = spawnArgs.GetString("def_warhead",""); + count = spawnArgs.GetFloat("warhead_count","0"); + + if( warhead && warhead[0] ) { + + //start launching! + float angle = (360.0f / count); + idMat3 normalMat = this->GetPhysics()->GetAxis( ); + idVec3 normal = normalMat[0]; + idVec3 axis = normalMat[1]; + + float t; + idMat3 axisMat; + idProjectile * warheadEntity; + + //hey how 'bout that. + normal.Normalize(); + + for( t = 0; t< count; t++) { + + //rotate axis around normal by angle degrees. + axisMat = axis.ToMat3(); + axisMat.RotateArbitrary( normal, angle * t); + warheadEntity = static_cast(gameLocal.SpawnEntityDef( warhead)); + warheadEntity->Create( this->GetOwner(), this->GetPhysics()->GetOrigin(), axisMat[0], this ); + warheadEntity->Launch( this->GetPhysics()->GetOrigin(), axisMat[0], axisMat[0] ); + + } + //foom! + gameLocal.PlayEffect( spawnArgs, "fx_impact", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + PostEventSec( &EV_Explode, 0 ); + } + + + +} +// RAVEN END diff --git a/src/mpgame/Projectile.h b/src/mpgame/Projectile.h new file mode 100644 index 0000000..9d2f78d --- /dev/null +++ b/src/mpgame/Projectile.h @@ -0,0 +1,387 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __GAME_PROJECTILE_H__ +#define __GAME_PROJECTILE_H__ + +/* +=============================================================================== + + idProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_Explode; + +class idProjectile : public idEntity { +public : + CLASS_PROTOTYPE( idProjectile ); + + idProjectile(); + virtual ~idProjectile(); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Create( idEntity *owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore = NULL, idEntity* extraPassEntity = NULL ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + virtual void FreeLightDef( void ); + +//RITUAL BEGIN + void SetOwner(idEntity* ent) { owner = ent; } +// RITUAL END + + idEntity * GetOwner( void ) const; + + virtual void Think( void ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ); + virtual void Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore = NULL, const char *sndExplode = "snd_explode" ); + void Fizzle( void ); + + static idVec3 GetVelocity( const idDict *projectile ); + static idVec3 GetGravity( const idDict *projectile ); + + void SetSpeed ( float s, int accelTime = 0 ); + float GetSpeed ( void ) const; + + virtual void UpdateVisualAngles(); + + // information about what kind of projectile we are, used for death messages + int methodOfDeath; + + virtual void ClientPredictionThink( void ); + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual bool ClientStale( void ); + +protected: + void SpawnImpactEntities(const trace_t& collision, const idVec3 projectileDirection); + + + idEntityPtr owner; + + struct projectileFlags_s { + bool detonate_on_world : 1; + bool detonate_on_actor : 1; + bool detonate_on_bounce : 1; // Detonate if hit a bounce surface + bool randomShaderSpin : 1; + bool isTracer : 1; + } projectileFlags; + + float damagePower; + + renderLight_t renderLight; + qhandle_t lightDefHandle; // handle to renderer light def + idVec3 lightOffset; + int lightStartTime; + int lightEndTime; + idVec3 lightColor; + + idEntity* impactedEntity; + + rvPhysics_Particle physicsObj; + idAngles visualAngles; + idAngles angularVelocity; + idInterpolate speed; + bool updateVelocity; + + rvSphericalInterpolate rotation; + + rvClientEffectPtr flyEffect; + float flyEffectAttenuateSpeed; + + int bounceCount; + bool sticky; + + idStr impactEntity; + int numImpactEntities; + int ieMinPitch; + int ieMaxPitch; + float ieSlicePercentage; + + bool predictedProjectiles; + +// RAVEN BEGIN +// ddynerman: hit count for stats + int hitCount; +// ddynerman: pre-prediction ( rocket jumping ) + int prePredictTime; +// RAVEN END + typedef enum { + SPAWNED = 0, + CREATED = 1, + LAUNCHED = 2, + FIZZLED = 3, + EXPLODED = 4, + IMPACTED = 5, + } projectileState_t; + + projectileState_t state; + + void PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& direction ); + virtual void PlayDetonateEffect ( const idVec3& origin, const idMat3& axis, bool forceImpact = false ); + +private: + void DefaultDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); + + void Event_Explode ( void ); + void Event_Fizzle ( void ); + void Event_RadiusDamage ( idEntity *ignore ); + void Event_ResidualDamage ( idEntity *ignore ); + void Event_EmitDamage ( idEntity *ignore ); + void Event_Touch ( idEntity *other, trace_t *trace ); + + bool syncPhysics; + + // cheap linear client side projectiles + // transmitted in snapshot + int launchTime; + idVec3 launchOrig; + idVec3 launchDir; + // set from def file in :Launch on both client and server + float launchSpeed; + bool playedDamageEffect; +}; + +ID_INLINE float idProjectile::GetSpeed ( void ) const { + return speed.GetCurrentValue( gameLocal.time ); +} + +/* +=============================================================================== + +idGuidedProjectile + +=============================================================================== +*/ + +extern const idEventDef EV_UpdateGuideTarget; +extern const idEventDef EV_GuideToEntity; +extern const idEventDef EV_GuideToPos; + +class idGuidedProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( idGuidedProjectile ); + + idGuidedProjectile( void ); + ~idGuidedProjectile( void ); + + enum { + GUIDE_NONE, + GUIDE_ENTITY, + GUIDE_POS, + GUIDE_DIR, + GUIDE_MAX + }; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + virtual void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + + void GuideTo ( const idVec3& post, const idVec3& dir ); + void GuideTo ( const idVec3& pos ); + void GuideTo ( idEntity* ent, jointHandle_t guideJoint=INVALID_JOINT, const idVec3 &offset=vec3_origin ); + void CancelGuide ( void ); + + int GetGuideType ( void ) const; + + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int guideType; + idEntityPtr guideEnt; + idVec3 guideDir; + idVec3 guidePos; + jointHandle_t guideJoint; + float guideMinDist; + + int driftTime; + int driftRate; + float driftRange; + float driftRadius; + float driftDiversity; + float driftAngle; + float driftAngleStep; + float driftProjectRange; + + virtual bool GetGuideDir ( idVec3 &outDir, float& outDist ); + +private: + + idInterpolate turn_max; + int launchTime; + int guideDelay; + int driftDelay; +}; + +ID_INLINE int idGuidedProjectile::GetGuideType ( void ) const { + return guideType; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos, const idVec3& dir ) { + guideType = GUIDE_DIR; + guidePos = pos; + guideDir = dir; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( const idVec3& pos ) { + guideType = GUIDE_POS; + guidePos = pos; +} + +ID_INLINE void idGuidedProjectile::GuideTo ( idEntity* ent, jointHandle_t joint, const idVec3 &offset ) { + guideType = GUIDE_ENTITY; + guideEnt = ent; + guideJoint = joint; + guidePos = offset; + + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( this ); + } +} + +ID_INLINE void idGuidedProjectile::CancelGuide ( void ) { + guideType = GUIDE_NONE; + + // twhitaker: TEMP + if ( guideEnt.IsValid() ) { + guideEnt->GuidedProjectileIncoming( NULL ); + } + // +} + +/* +=============================================================================== + +rvDriftingProjectile + +=============================================================================== +*/ + +class rvDriftingProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDriftingProjectile ); + + rvDriftingProjectile ( void ); + ~rvDriftingProjectile ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + virtual void Launch ( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float dmgPower = 1.0f ); + +protected: + + virtual void UpdateVisualAngles ( void ); + + idVec3 startDir; + idVec3 startOrigin; + idMat3 startAxis; + float startSpeed; + + idInterpolateAccelDecelLinear driftOffset[2]; + idInterpolateAccelDecelLinear driftSpeed; + float driftOffsetMax; + float driftSpeedMax; + float driftTime; +}; + +/* +=============================================================================== + +rvSpawnerProjectile + +=============================================================================== +*/ + +class rvSpawner; +class rvSpawnerProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvSpawnerProjectile ); + + rvSpawnerProjectile ( void ); + ~rvSpawnerProjectile ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + void SetSpawner ( rvSpawner* spawner ); + +protected: + + idEntityPtr spawner; + + enum { + STATE_NONE, + STATE_ADDED, + } spawnState; + +private: + + void Event_PostSpawn ( void ); +}; + +/* +=============================================================================== + +rvMIRVProjectile + +=============================================================================== +*/ + +class rvMIRVProjectile : public idProjectile { + CLASS_PROTOTYPE( rvMIRVProjectile ); + + rvMIRVProjectile ( void ); + ~rvMIRVProjectile ( void ); + + + void Spawn ( void ); + +private: + + void Event_LaunchWarheads ( void ); +}; + +#endif /* !__GAME_PROJECTILE_H__ */ + +// RAVEN END diff --git a/src/mpgame/Pvs.cpp b/src/mpgame/Pvs.cpp new file mode 100644 index 0000000..64753e6 --- /dev/null +++ b/src/mpgame/Pvs.cpp @@ -0,0 +1,1462 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#define MAX_BOUNDS_AREAS 16 + + +typedef struct pvsPassage_s { + byte * canSee; // bit set for all portals that can be seen through this passage +} pvsPassage_t; + + +typedef struct pvsPortal_s { + int areaNum; // area this portal leads to + idWinding * w; // winding goes counter clockwise seen from the area this portal is part of + idBounds bounds; // winding bounds + idPlane plane; // winding plane, normal points towards the area this portal leads to + pvsPassage_t * passages; // passages to portals in the area this portal leads to + bool done; // true if pvs is calculated for this portal + byte * vis; // PVS for this portal + byte * mightSee; // used during construction +} pvsPortal_t; + + +typedef struct pvsArea_s { + int numPortals; // number of portals in this area + idBounds bounds; // bounds of the whole area + pvsPortal_t ** portals; // array with pointers to the portals of this area +} pvsArea_t; + + +typedef struct pvsStack_s { + struct pvsStack_s * next; // next stack entry + byte * mightSee; // bit set for all portals that might be visible through this passage/portal stack +} pvsStack_t; + + +/* +================ +idPVS::idPVS +================ +*/ +idPVS::idPVS( void ) { + int i; + + numAreas = 0; + numPortals = 0; + + connectedAreas = NULL; + areaQueue = NULL; + areaPVS = NULL; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = NULL; + } + + pvsAreas = NULL; + pvsPortals = NULL; +} + +/* +================ +idPVS::~idPVS +================ +*/ +idPVS::~idPVS( void ) { + Shutdown(); +} + +/* +================ +idPVS::GetPortalCount +================ +*/ +int idPVS::GetPortalCount( void ) const { + int i, na, np; + + na = gameRenderWorld->NumAreas(); + np = 0; + for ( i = 0; i < na; i++ ) { + np += gameRenderWorld->NumPortalsInArea( i ); + } + return np; +} + +/* +================ +idPVS::CreatePVSData +================ +*/ +void idPVS::CreatePVSData( void ) { + int i, j, n, cp; + exitPortal_t portal; + pvsArea_t *area; + pvsPortal_t *p, **portalPtrs; + + if ( !numPortals ) { + return; + } + + pvsPortals = new pvsPortal_t[numPortals]; + pvsAreas = new pvsArea_t[numAreas]; + memset( pvsAreas, 0, numAreas * sizeof( *pvsAreas ) ); + + cp = 0; + portalPtrs = new pvsPortal_t*[numPortals]; + + for ( i = 0; i < numAreas; i++ ) { + + area = &pvsAreas[i]; + area->bounds.Clear(); + area->portals = portalPtrs + cp; + + n = gameRenderWorld->NumPortalsInArea( i ); + + for ( j = 0; j < n; j++ ) { + + portal = gameRenderWorld->GetPortal( i, j ); + + p = &pvsPortals[cp++]; + // the winding goes counter clockwise seen from this area + p->w = portal.w->Copy(); + p->areaNum = portal.areas[1]; // area[1] is always the area the portal leads to + + p->vis = new byte[portalVisBytes]; + memset( p->vis, 0, portalVisBytes ); + p->mightSee = new byte[portalVisBytes]; + memset( p->mightSee, 0, portalVisBytes ); + p->w->GetBounds( p->bounds ); + p->w->GetPlane( p->plane ); + // plane normal points to outside the area + p->plane = -p->plane; + // no PVS calculated for this portal yet + p->done = false; + + area->portals[area->numPortals] = p; + area->numPortals++; + + area->bounds += p->bounds; + } + } +} + +/* +================ +idPVS::DestroyPVSData +================ +*/ +void idPVS::DestroyPVSData( void ) { + int i; + + if ( !pvsAreas ) { + return; + } + + // delete portal pointer array + delete[] pvsAreas[0].portals; + + // delete all areas + delete[] pvsAreas; + pvsAreas = NULL; + + // delete portal data + for ( i = 0; i < numPortals; i++ ) { + delete[] pvsPortals[i].vis; + delete[] pvsPortals[i].mightSee; + delete pvsPortals[i].w; + } + + // delete portals + delete[] pvsPortals; + pvsPortals = NULL; +} + +/* +================ +idPVS::FloodFrontPortalPVS_r +================ +*/ +void idPVS::FloodFrontPortalPVS_r( pvsPortal_t *portal, int areaNum ) const { + int i, n; + pvsArea_t *area; + pvsPortal_t *p; + + area = &pvsAreas[ areaNum ]; + + for ( i = 0; i < area->numPortals; i++ ) { + p = area->portals[i]; + n = p - pvsPortals; + // don't flood through if this portal is not at the front + if ( !( portal->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + continue; + } + // don't flood through if already visited this portal + if ( portal->vis[ n>>3 ] & (1 << (n&7)) ) { + continue; + } + // this portal might be visible + portal->vis[ n>>3 ] |= (1 << (n&7)); + // flood through the portal + FloodFrontPortalPVS_r( portal, p->areaNum ); + } +} + +/* +================ +idPVS::FrontPortalPVS +================ +*/ +void idPVS::FrontPortalPVS( void ) const { + int i, j, k, n, p, side1, side2, areaSide; + pvsPortal_t *p1, *p2; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + + for ( j = 0; j < numAreas; j++ ) { + + area = &pvsAreas[j]; + + areaSide = side1 = area->bounds.PlaneSide( p1->plane ); + + // if the whole area is at the back side of the portal + if ( areaSide == PLANESIDE_BACK ) { + continue; + } + + for ( p = 0; p < area->numPortals; p++ ) { + + p2 = area->portals[p]; + + // if we the whole area is not at the front we need to check + if ( areaSide != PLANESIDE_FRONT ) { + // if the second portal is completely at the back side of the first portal + side1 = p2->bounds.PlaneSide( p1->plane ); + if ( side1 == PLANESIDE_BACK ) { + continue; + } + } + + // if the first portal is completely at the front of the second portal + side2 = p1->bounds.PlaneSide( p2->plane ); + if ( side2 == PLANESIDE_FRONT ) { + continue; + } + + // if the second portal is not completely at the front of the first portal + if ( side1 != PLANESIDE_FRONT ) { + // more accurate check + for ( k = 0; k < p2->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the front side + if ( p1->plane.Side( (*p2->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_FRONT ) { + break; + } + } + if ( k >= p2->w->GetNumPoints() ) { + continue; // second portal is at the back of the first portal + } + } + + // if the first portal is not completely at the back side of the second portal + if ( side2 != PLANESIDE_BACK ) { + // more accurate check + for ( k = 0; k < p1->w->GetNumPoints(); k++ ) { + // if more than an epsilon at the back side + if ( p2->plane.Side( (*p1->w)[k].ToVec3(), ON_EPSILON ) == PLANESIDE_BACK ) { + break; + } + } + if ( k >= p1->w->GetNumPoints() ) { + continue; // first portal is at the front of the second portal + } + } + + // the portal might be visible at the front + n = p2 - pvsPortals; + p1->mightSee[ n >> 3 ] |= 1 << (n&7); + } + } + } + + // flood the front portal pvs for all portals + for ( i = 0; i < numPortals; i++ ) { + p1 = &pvsPortals[i]; + FloodFrontPortalPVS_r( p1, p1->areaNum ); + } +} + +/* +=============== +idPVS::FloodPassagePVS_r +=============== +*/ +pvsStack_t *idPVS::FloodPassagePVS_r( pvsPortal_t *source, const pvsPortal_t *portal, pvsStack_t *prevStack ) const { + int i, j, n, m; + pvsPortal_t *p; + pvsArea_t *area; + pvsStack_t *stack; + pvsPassage_t *passage; + long *sourceVis, *passageVis, *portalVis, *mightSee, *prevMightSee, more; + + area = &pvsAreas[portal->areaNum]; + + stack = prevStack->next; + // if no next stack entry allocated + if ( !stack ) { + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + prevStack->next = stack; + } + + // check all portals for flooding into other areas + for ( i = 0; i < area->numPortals; i++ ) { + + passage = &portal->passages[i]; + + // if this passage is completely empty + if ( !passage->canSee ) { + continue; + } + + p = area->portals[i]; + n = p - pvsPortals; + + // if this portal cannot be seen through our current portal/passage stack + if ( !( prevStack->mightSee[n >> 3] & (1 << (n & 7)) ) ) { + continue; + } + + // mark the portal as visible + source->vis[n >> 3] |= (1 << (n & 7)); + + // get pointers to vis data + prevMightSee = reinterpret_cast(prevStack->mightSee); + passageVis = reinterpret_cast(passage->canSee); + sourceVis = reinterpret_cast(source->vis); + mightSee = reinterpret_cast(stack->mightSee); + + more = 0; + // use the portal PVS if it has been calculated + if ( p->done ) { + portalVis = reinterpret_cast(p->vis); + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++ & *portalVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + else { + // the p->mightSee is implicitely stored in the passageVis + for ( j = 0; j < portalVisLongs; j++ ) { + // get new PVS which is decreased by going through this passage + m = *prevMightSee++ & *passageVis++; + // check if anything might be visible through this passage that wasn't yet visible + more |= (m & ~(*sourceVis++)); + // store new PVS + *mightSee++ = m; + } + } + + // if nothing more can be seen + if ( !more ) { + continue; + } + + // go through the portal + stack->next = FloodPassagePVS_r( source, p, stack ); + } + + return stack; +} + +/* +=============== +idPVS::PassagePVS +=============== +*/ +void idPVS::PassagePVS( void ) const { + int i; + pvsPortal_t *source; + pvsStack_t *stack, *s; + + // create the passages + CreatePassages(); + + // allocate first stack entry + stack = reinterpret_cast(new byte[sizeof(pvsStack_t) + portalVisBytes]); + stack->mightSee = (reinterpret_cast(stack)) + sizeof(pvsStack_t); + stack->next = NULL; + + // calculate portal PVS by flooding through the passages + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + memset( source->vis, 0, portalVisBytes ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( stack->mightSee, source->mightSee, portalVisBytes ); +// RAVEN END + FloodPassagePVS_r( source, source, stack ); + source->done = true; + } + + // free the allocated stack + for ( s = stack; s; s = stack ) { + stack = stack->next; + delete[] s; + } + + // destroy the passages + DestroyPassages(); +} + +/* +=============== +idPVS::AddPassageBoundaries +=============== +*/ +void idPVS::AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const { + int i, j, k, l; + idVec3 v1, v2, normal; + float d, dist; + bool flipTest, front; + idPlane plane; + + + // check all combinations + for ( i = 0; i < source.GetNumPoints(); i++ ) { + + l = (i + 1) % source.GetNumPoints(); + v1 = source[l].ToVec3() - source[i].ToVec3(); + + // find a vertex of pass that makes a plane that puts all of the + // vertices of pass on the front side and all of the vertices of + // source on the back side + for ( j = 0; j < pass.GetNumPoints(); j++ ) { + + v2 = pass[j].ToVec3() - source[i].ToVec3(); + + normal = v1.Cross( v2 ); + if ( normal.Normalize() < 0.01f ) { + continue; + } + dist = normal * pass[j].ToVec3(); + + // + // find out which side of the generated seperating plane has the + // source portal + // + flipTest = false; + for ( k = 0; k < source.GetNumPoints(); k++ ) { + if ( k == i || k == l ) { + continue; + } + d = source[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + // source is on the negative side, so we want all + // pass and target on the positive side + flipTest = false; + break; + } + else if ( d > ON_EPSILON ) { + // source is on the positive side, so we want all + // pass and target on the negative side + flipTest = true; + break; + } + } + if ( k == source.GetNumPoints() ) { + continue; // planar with source portal + } + + // flip the normal if the source portal is backwards + if (flipTest) { + normal = -normal; + dist = -dist; + } + + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + front = false; + for ( k = 0; k < pass.GetNumPoints(); k++ ) { + if ( k == j ) { + continue; + } + d = pass[k].ToVec3() * normal - dist; + if ( d < -ON_EPSILON ) { + break; + } + else if ( d > ON_EPSILON ) { + front = true; + } + } + if ( k < pass.GetNumPoints() ) { + continue; // points on negative side, not a seperating plane + } + if ( !front ) { + continue; // planar with seperating plane + } + + // flip the normal if we want the back side + if ( flipClip ) { + plane.SetNormal( -normal ); + plane.SetDist( -dist ); + } + else { + plane.SetNormal( normal ); + plane.SetDist( dist ); + } + + // check if the plane is already a passage boundary + for ( k = 0; k < numBounds; k++ ) { + if ( plane.Compare( bounds[k], 0.001f, 0.01f ) ) { + break; + } + } + if ( k < numBounds ) { + break; + } + + if ( numBounds >= maxBounds ) { + gameLocal.Warning( "max passage boundaries." ); + break; + } + bounds[numBounds] = plane; + numBounds++; + break; + } + } +} + +/* +================ +idPVS::CreatePassages +================ +*/ +#define MAX_PASSAGE_BOUNDS 128 + +void idPVS::CreatePassages( void ) const { + int i, j, l, n, numBounds, front, passageMemory, byteNum, bitNum; + int sides[MAX_PASSAGE_BOUNDS]; + idPlane passageBounds[MAX_PASSAGE_BOUNDS]; + pvsPortal_t *source, *target, *p; + pvsArea_t *area; + pvsPassage_t *passage; + idFixedWinding winding; + byte canSee, mightSee, bit; + + passageMemory = 0; + for ( i = 0; i < numPortals; i++ ) { + source = &pvsPortals[i]; + area = &pvsAreas[source->areaNum]; + + source->passages = new pvsPassage_t[area->numPortals]; + + for ( j = 0; j < area->numPortals; j++ ) { + target = area->portals[j]; + n = target - pvsPortals; + + passage = &source->passages[j]; + + // if the source portal cannot see this portal + if ( !( source->mightSee[ n>>3 ] & (1 << (n&7)) ) ) { + // not all portals in the area have to be visible because areas are not necesarily convex + // also no passage has to be created for the portal which is the opposite of the source + passage->canSee = NULL; + continue; + } + + passage->canSee = new byte[portalVisBytes]; + passageMemory += portalVisBytes; + + // boundary plane normals point inwards + numBounds = 0; + AddPassageBoundaries( *(source->w), *(target->w), false, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + AddPassageBoundaries( *(target->w), *(source->w), true, passageBounds, numBounds, MAX_PASSAGE_BOUNDS ); + + // get all portals visible through this passage + for ( byteNum = 0; byteNum < portalVisBytes; byteNum++) { + + canSee = 0; + mightSee = source->mightSee[byteNum] & target->mightSee[byteNum]; + + // go through eight portals at a time to speed things up + for ( bitNum = 0; bitNum < 8; bitNum++ ) { + + bit = 1 << bitNum; + + if ( !( mightSee & bit ) ) { + continue; + } + + p = &pvsPortals[(byteNum << 3) + bitNum]; + + if ( p->areaNum == source->areaNum ) { + continue; + } + + for ( front = 0, l = 0; l < numBounds; l++ ) { + sides[l] = p->bounds.PlaneSide( passageBounds[l] ); + // if completely at the back of the passage bounding plane + if ( sides[l] == PLANESIDE_BACK ) { + break; + } + // if completely at the front + if ( sides[l] == PLANESIDE_FRONT ) { + front++; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + + // if not at the front of all bounding planes and thus not completely inside the passage + if ( front != numBounds ) { + + winding = *p->w; + + for ( l = 0; l < numBounds; l++ ) { + // only clip if the winding possibly crosses this plane + if ( sides[l] != PLANESIDE_CROSS ) { + continue; + } + // clip away the part at the back of the bounding plane + winding.ClipInPlace( passageBounds[l] ); + // if completely clipped away + if ( !winding.GetNumPoints() ) { + break; + } + } + // if completely outside the passage + if ( l < numBounds ) { + continue; + } + } + + canSee |= bit; + } + + // store results of all eight portals + passage->canSee[byteNum] = canSee; + } + + // can always see the target portal + passage->canSee[n >> 3] |= (1 << (n&7)); + } + } + if ( passageMemory < 1024 ) { + gameLocal.Printf( "%5d bytes passage memory used to build PVS\n", passageMemory ); + } + else { + gameLocal.Printf( "%5d KB passage memory used to build PVS\n", passageMemory>>10 ); + } +} + +/* +================ +idPVS::DestroyPassages +================ +*/ +void idPVS::DestroyPassages( void ) const { + int i, j; + pvsPortal_t *p; + pvsArea_t *area; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; + area = &pvsAreas[p->areaNum]; + for ( j = 0; j < area->numPortals; j++ ) { + if ( p->passages[j].canSee ) { + delete[] p->passages[j].canSee; + } + } + delete[] p->passages; + } +} + +/* +================ +idPVS::CopyPortalPVSToMightSee +================ +*/ +void idPVS::CopyPortalPVSToMightSee( void ) const { + int i; + pvsPortal_t *p; + + for ( i = 0; i < numPortals; i++ ) { + p = &pvsPortals[i]; +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( p->mightSee, p->vis, portalVisBytes ); +// RAVEN BEGIN + } +} + +/* +================ +idPVS::AreaPVSFromPortalPVS +================ +*/ +int idPVS::AreaPVSFromPortalPVS( void ) const { + int i, j, k, areaNum, totalVisibleAreas; + long *p1, *p2; + byte *pvs, *portalPVS; + pvsArea_t *area; + + totalVisibleAreas = 0; + + if ( !numPortals ) { + return totalVisibleAreas; + } + + memset( areaPVS, 0, numAreas * areaVisBytes ); + + for ( i = 0; i < numAreas; i++ ) { + area = &pvsAreas[i]; + pvs = areaPVS + i * areaVisBytes; + + // the area is visible to itself + pvs[ i >> 3 ] |= 1 << (i & 7); + + if ( !area->numPortals ) { + continue; + } + + // store the PVS of all portals in this area at the first portal + for ( j = 1; j < area->numPortals; j++ ) { + p1 = reinterpret_cast(area->portals[0]->vis); + p2 = reinterpret_cast(area->portals[j]->vis); + for ( k = 0; k < portalVisLongs; k++ ) { + *p1++ |= *p2++; + } + } + + // the portals of this area are always visible + for ( j = 0; j < area->numPortals; j++ ) { + k = area->portals[j] - pvsPortals; + area->portals[0]->vis[ k >> 3 ] |= 1 << (k & 7); + } + + // set all areas to visible that can be seen from the portals of this area + portalPVS = area->portals[0]->vis; + for ( j = 0; j < numPortals; j++ ) { + // if this portal is visible + if ( portalPVS[j>>3] & (1 << (j&7)) ) { + areaNum = pvsPortals[j].areaNum; + pvs[ areaNum >> 3 ] |= 1 << (areaNum & 7); + } + } + + // count the number of visible areas + for ( j = 0; j < numAreas; j++ ) { + if ( pvs[j>>3] & (1 << (j&7)) ) { + totalVisibleAreas++; + } + } + } + return totalVisibleAreas; +} + +/* +================ +idPVS::Init +================ +*/ +void idPVS::Init( void ) { + int totalVisibleAreas; + + Shutdown(); + + numAreas = gameRenderWorld->NumAreas(); + if ( numAreas <= 0 ) { + return; + } + + connectedAreas = new bool[numAreas]; + areaQueue = new int[numAreas]; + + areaVisBytes = ( ((numAreas+31)&~31) >> 3); + areaVisLongs = areaVisBytes/sizeof(long); + areaPVS = new byte[numAreas * areaVisBytes]; + memset( areaPVS, 0xFF, numAreas * areaVisBytes ); + + numPortals = GetPortalCount(); + + portalVisBytes = ( ((numPortals+31)&~31) >> 3); + portalVisLongs = portalVisBytes/sizeof(long); + + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { + currentPVS[i].handle.i = -1; + currentPVS[i].handle.h = 0; + currentPVS[i].pvs = new byte[areaVisBytes]; + memset( currentPVS[i].pvs, 0, areaVisBytes ); + } + + idTimer timer; + timer.Start(); + + CreatePVSData(); + + FrontPortalPVS(); + + CopyPortalPVSToMightSee(); + + PassagePVS(); + + totalVisibleAreas = AreaPVSFromPortalPVS(); + + DestroyPVSData(); + + timer.Stop(); + +#if 0 + // bkreimeier20060123 - per MrE's recommendation, used this in Wolf + // if the PVS is not relexive for (i,j), we assume floating point errors + // and assume a false positive - remove either from either + for ( int i=0; i>3] & (1<<(j&7))); + const bool i_in_j = (pvs_j[i>>3] & (1<<(i&7))); + if ( i_in_j!=j_in_i ) { + pvs_i[j>>3] &= ~(1 << (j&7)); + pvs_j[i>>3] &= ~(1 << (i&7)); + } + } + } +#endif + + gameLocal.Printf( "%5.0f msec to calculate PVS\n", timer.Milliseconds() ); + gameLocal.Printf( "%5d areas\n", numAreas ); + gameLocal.Printf( "%5d portals\n", numPortals ); + +// RAVEN BEGIN +// rjohnson: fix for div by 0 + if( numAreas ) { + gameLocal.Printf( "%5d areas visible on average\n", totalVisibleAreas / numAreas ); + } +// RAVEN END + + if ( numAreas * areaVisBytes < 1024 ) { + gameLocal.Printf( "%5d bytes PVS data\n", numAreas * areaVisBytes ); + } + else { + gameLocal.Printf( "%5d KB PVS data\n", (numAreas * areaVisBytes) >> 10 ); + } +} + +/* +================ +idPVS::Shutdown +================ +*/ +void idPVS::Shutdown( void ) { + if ( connectedAreas ) { + delete[] connectedAreas; + connectedAreas = NULL; + } + if ( areaQueue ) { + delete[] areaQueue; + areaQueue = NULL; + } + if ( areaPVS ) { + delete[] areaPVS; + areaPVS = NULL; + } + if ( currentPVS ) { + for ( int i = 0; i < MAX_CURRENT_PVS; i++ ) { +// RAVEN BEGIN +// jsinger: modified to check to make sure the pointers have a value before attempting to delete +// them. This prevents a call through the unified allocator before it has been initialized + if(currentPVS[i].pvs) + { + delete[] currentPVS[i].pvs; + } +// RAVEN END + currentPVS[i].pvs = NULL; + } + } +} + +/* +================ +idPVS::GetConnectedAreas + + assumes the 'areas' array is initialized to false +================ +*/ +void idPVS::GetConnectedAreas( int srcArea, bool *areas ) const { + int curArea, nextArea; + int queueStart, queueEnd; + int i, n; + exitPortal_t portal; + + queueStart = -1; + queueEnd = 0; + areas[srcArea] = true; + + for ( curArea = srcArea; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + n = gameRenderWorld->NumPortalsInArea( curArea ); + + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( curArea, i ); + + if ( portal.blockingBits & PS_BLOCK_VIEW ) { + continue; + } + + // area[1] is always the area the portal leads to + nextArea = portal.areas[1]; + + // if already visited this area + if ( areas[nextArea] ) { + continue; + } + + // add area to queue + areaQueue[queueEnd++] = nextArea; + areas[nextArea] = true; + } + } +} + +/* +================ +idPVS::GetPVSArea +================ +*/ +int idPVS::GetPVSArea( const idVec3 &point ) const { + return gameRenderWorld->PointInArea( point ); +} + +/* +================ +idPVS::GetPVSAreas +================ +*/ +int idPVS::GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const { + return gameRenderWorld->BoundsInAreas( bounds, areas, maxAreas ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idVec3 &source, const pvsType_t type ) const { + int sourceArea; + + sourceArea = gameRenderWorld->PointInArea( source ); + + return SetupCurrentPVS( sourceArea, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const idBounds &source, const pvsType_t type ) const { + int numSourceAreas, sourceAreas[MAX_BOUNDS_AREAS]; + + numSourceAreas = gameRenderWorld->BoundsInAreas( source, sourceAreas, MAX_BOUNDS_AREAS ); + + return SetupCurrentPVS( sourceAreas, numSourceAreas, type ); +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int sourceArea, const pvsType_t type ) const { + int i; + pvsHandle_t handle; + + handle = AllocCurrentPVS( *reinterpret_cast(&sourceArea) ); + + if ( sourceArea < 0 || sourceArea >= numAreas ) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceArea * areaVisBytes, areaVisBytes ); +// RAVEN END + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + GetConnectedAreas( sourceArea, connectedAreas ); + + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::SetupCurrentPVS +================ +*/ +pvsHandle_t idPVS::SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type ) const { + int i, j; + unsigned int h; + long *vis, *pvs; + pvsHandle_t handle; + + h = 0; + for ( i = 0; i < numSourceAreas; i++ ) { + h ^= *reinterpret_cast(&sourceAreas[i]); + } + handle = AllocCurrentPVS( h ); + + if ( !numSourceAreas || sourceAreas[0] < 0 || sourceAreas[0] >= numAreas) { + memset( currentPVS[handle.i].pvs, 0, areaVisBytes ); + return handle; + } + + if ( type != PVS_CONNECTED_AREAS ) { + // merge PVS of all areas the source is in +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( currentPVS[handle.i].pvs, areaPVS + sourceAreas[0] * areaVisBytes, areaVisBytes ); +// RAVEN END + for ( i = 1; i < numSourceAreas; i++ ) { + + assert( sourceAreas[i] >= 0 && sourceAreas[i] < numAreas ); + + vis = reinterpret_cast(areaPVS + sourceAreas[i] * areaVisBytes); + pvs = reinterpret_cast(currentPVS[handle.i].pvs); + for ( j = 0; j < areaVisLongs; j++ ) { + *pvs++ |= *vis++; + } + } + } else { + memset( currentPVS[handle.i].pvs, -1, areaVisBytes ); + } + + if ( type == PVS_ALL_PORTALS_OPEN ) { + return handle; + } + + memset( connectedAreas, 0, numAreas * sizeof( *connectedAreas ) ); + + // get all areas connected to any of the source areas + for ( i = 0; i < numSourceAreas; i++ ) { + if ( !connectedAreas[sourceAreas[i]] ) { + GetConnectedAreas( sourceAreas[i], connectedAreas ); + } + } + + // remove unconnected areas from the PVS + for ( i = 0; i < numAreas; i++ ) { + if ( !connectedAreas[i] ) { + currentPVS[handle.i].pvs[i>>3] &= ~(1 << (i&7)); + } + } + + return handle; +} + +/* +================ +idPVS::MergeCurrentPVS +================ +*/ +pvsHandle_t idPVS::MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const { + int i; + long *pvs1Ptr, *pvs2Ptr, *ptr; + pvsHandle_t handle; + + if ( pvs1.i < 0 || pvs1.i >= MAX_CURRENT_PVS || pvs1.h != currentPVS[pvs1.i].handle.h || + pvs2.i < 0 || pvs2.i >= MAX_CURRENT_PVS || pvs2.h != currentPVS[pvs2.i].handle.h ) { + gameLocal.Error( "idPVS::MergeCurrentPVS: invalid handle" ); + } + + handle = AllocCurrentPVS( pvs1.h ^ pvs2.h ); + + ptr = reinterpret_cast(currentPVS[handle.i].pvs); + pvs1Ptr = reinterpret_cast(currentPVS[pvs1.i].pvs); + pvs2Ptr = reinterpret_cast(currentPVS[pvs2.i].pvs); + + for ( i = 0; i < areaVisLongs; i++ ) { + *ptr++ = *pvs1Ptr++ | *pvs2Ptr++; + } + + return handle; +} + +/* +================ +idPVS::AllocCurrentPVS +================ +*/ +pvsHandle_t idPVS::AllocCurrentPVS( unsigned int h ) const { + int i; + pvsHandle_t handle; + + for ( i = 0; i < MAX_CURRENT_PVS; i++ ) { + if ( currentPVS[i].handle.i == -1 ) { + currentPVS[i].handle.i = i; + currentPVS[i].handle.h = h; + return currentPVS[i].handle; + } + } + + gameLocal.Error( "idPVS::AllocCurrentPVS: no free PVS left" ); + + handle.i = -1; + handle.h = 0; + return handle; +} + +/* +================ +idPVS::FreeCurrentPVS +================ +*/ +void idPVS::FreeCurrentPVS( pvsHandle_t handle ) const { + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::FreeCurrentPVS: invalid handle" ); + } + currentPVS[handle.i].handle.i = -1; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const { + int targetArea; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + targetArea = gameRenderWorld->PointInArea( target ); + + if ( targetArea == -1 ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const { + int i, numTargetAreas, targetAreas[MAX_BOUNDS_AREAS]; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + numTargetAreas = gameRenderWorld->BoundsInAreas( target, targetAreas, MAX_BOUNDS_AREAS ); + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const { + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + if ( targetArea < 0 || targetArea >= numAreas ) { + return false; + } + + return ( ( currentPVS[handle.i].pvs[targetArea>>3] & (1 << (targetArea&7)) ) != 0 ); +} + +/* +================ +idPVS::InCurrentPVS +================ +*/ +bool idPVS::InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const { + int i; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::InCurrentPVS: invalid handle" ); + } + + for ( i = 0; i < numTargetAreas; i++ ) { + if ( targetAreas[i] < 0 || targetAreas[i] >= numAreas ) { + continue; + } + if ( currentPVS[handle.i].pvs[targetAreas[i]>>3] & (1 << (targetAreas[i]&7)) ) { + return true; + } + } + return false; +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idVec3 &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawPVS( const idBounds &source, const pvsType_t type ) const { + int i, j, k, numPoints, n, num, areas[MAX_BOUNDS_AREAS]; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + pvsHandle_t handle; + + num = gameRenderWorld->BoundsInAreas( source, areas, MAX_BOUNDS_AREAS ); + + if ( !num ) { + return; + } + + handle = SetupCurrentPVS( source, type ); + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + for ( i = 0; i < num; i++ ) { + if ( j == areas[i] ) { + break; + } + } + if ( i < num ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } + + FreeCurrentPVS( handle ); +} + +/* +================ +idPVS::DrawPVS +================ +*/ +void idPVS::DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const { + int i, j, k, numPoints, n, sourceArea; + exitPortal_t portal; + idPlane plane; + idVec3 offset; + idVec4 *color; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || + handle.h != currentPVS[handle.i].handle.h ) { + gameLocal.Error( "idPVS::DrawCurrentPVS: invalid handle" ); + } + + sourceArea = gameRenderWorld->PointInArea( source ); + + if ( sourceArea == -1 ) { + return; + } + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( j == sourceArea ) { + color = &colorRed; + } + else { + color = &colorCyan; + } + + n = gameRenderWorld->NumPortalsInArea( j ); + + // draw all the portals of the area + for ( i = 0; i < n; i++ ) { + portal = gameRenderWorld->GetPortal( j, i ); + + numPoints = portal.w->GetNumPoints(); + + portal.w->GetPlane( plane ); + offset = plane.Normal() * 4.0f; + for ( k = 0; k < numPoints; k++ ) { + gameRenderWorld->DebugLine( *color, (*portal.w)[k].ToVec3() + offset, (*portal.w)[(k+1)%numPoints].ToVec3() + offset ); + } + } + } +} + +#if ASYNC_WRITE_PVS + +/* +=================== +idPVS::WritePVS +=================== +*/ +void idPVS::WritePVS( const pvsHandle_t handle, idBitMsg &msg ) { + msg.WriteData( currentPVS[ handle.i ].pvs, areaVisBytes ); +} + +/* +=================== +idPVS::ReadPVS +=================== +*/ +void idPVS::ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ) { + byte l_pvs[ 256 ]; + int i; + + assert( areaVisBytes <= 256 ); + msg.ReadData( l_pvs, areaVisBytes ); + if ( memcmp( l_pvs, currentPVS[ handle.i ].pvs, areaVisBytes ) ) { + common->Printf( "PVS not matching ( %d areaVisBytes ) - server then client:\n", areaVisBytes ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", l_pvs[ i ] ); + } + common->Printf( "\n" ); + for ( i = 0; i < areaVisBytes; i++ ) { + common->Printf( "%x ", currentPVS[ handle.i ].pvs[ i ] ); + } + common->Printf( "\n" ); + } +} + +#endif + diff --git a/src/mpgame/Pvs.h b/src/mpgame/Pvs.h new file mode 100644 index 0000000..55a4c9d --- /dev/null +++ b/src/mpgame/Pvs.h @@ -0,0 +1,127 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_PVS_H__ +#define __GAME_PVS_H__ + +/* +=================================================================================== + + PVS + + Note: mirrors and other special view portals are not taken into account + +=================================================================================== +*/ + + +typedef struct pvsHandle_s { + int i; // index to current pvs + unsigned int h; // handle for current pvs +} pvsHandle_t; + +typedef struct pvsCurrent_s { + pvsHandle_t handle; // current pvs handle + byte * pvs; // current pvs bit string +} pvsCurrent_t; + +// must be a power of 2 +// base was 8, MP now stores client PVS from frame to frame ( MAX_CLIENTS ) +#define MAX_CURRENT_PVS 64 + +typedef enum { + PVS_NORMAL = 0, // PVS through portals taking portal states into account + PVS_ALL_PORTALS_OPEN = 1, // PVS through portals assuming all portals are open + PVS_CONNECTED_AREAS = 2 // PVS considering all topologically connected areas visible +} pvsType_t; + + +class idPVS { +public: + idPVS( void ); + ~idPVS( void ); + // setup for the current map + void Init( void ); + void Shutdown( void ); + // get the area(s) the source is in + int GetPVSArea( const idVec3 &point ) const; // returns the area number + int GetPVSAreas( const idBounds &bounds, int *areas, int maxAreas ) const; // returns number of areas + // setup current PVS for the source + pvsHandle_t SetupCurrentPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int sourceArea, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t SetupCurrentPVS( const int *sourceAreas, const int numSourceAreas, const pvsType_t type = PVS_NORMAL ) const; + pvsHandle_t MergeCurrentPVS( pvsHandle_t pvs1, pvsHandle_t pvs2 ) const; + void FreeCurrentPVS( pvsHandle_t handle ) const; + // returns true if the target is within the current PVS + bool InCurrentPVS( const pvsHandle_t handle, const idVec3 &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const idBounds &target ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int targetArea ) const; + bool InCurrentPVS( const pvsHandle_t handle, const int *targetAreas, int numTargetAreas ) const; + // draw all portals that are within the PVS of the source + void DrawPVS( const idVec3 &source, const pvsType_t type = PVS_NORMAL ) const; + void DrawPVS( const idBounds &source, const pvsType_t type = PVS_NORMAL ) const; + // visualize the PVS the handle points to + void DrawCurrentPVS( const pvsHandle_t handle, const idVec3 &source ) const; + +#if ASYNC_WRITE_PVS + void WritePVS( const pvsHandle_t handle, idBitMsg &msg ); + void ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ); +#endif + +private: + int numAreas; + int numPortals; + bool * connectedAreas; + int * areaQueue; + byte * areaPVS; + + // current PVS for a specific source possibly taking portal states (open/closed) into account + mutable pvsCurrent_t currentPVS[MAX_CURRENT_PVS]; + // used to create PVS + int portalVisBytes; + int portalVisLongs; + int areaVisBytes; + int areaVisLongs; + struct pvsPortal_s *pvsPortals; + struct pvsArea_s * pvsAreas; + +private: + int GetPortalCount( void ) const; + void CreatePVSData( void ); + void DestroyPVSData( void ); + void CopyPortalPVSToMightSee( void ) const; + void FloodFrontPortalPVS_r( struct pvsPortal_s *portal, int areaNum ) const; + void FrontPortalPVS( void ) const; + struct pvsStack_s * FloodPassagePVS_r( struct pvsPortal_s *source, const struct pvsPortal_s *portal, struct pvsStack_s *prevStack ) const; + void PassagePVS( void ) const; + void AddPassageBoundaries( const idWinding &source, const idWinding &pass, bool flipClip, idPlane *bounds, int &numBounds, int maxBounds ) const; + void CreatePassages( void ) const; + void DestroyPassages( void ) const; + int AreaPVSFromPortalPVS( void ) const; + void GetConnectedAreas( int srcArea, bool *connectedAreas ) const; + pvsHandle_t AllocCurrentPVS( unsigned int h ) const; +}; + +#endif /* !__GAME_PVS_H__ */ diff --git a/src/mpgame/SecurityCamera.cpp b/src/mpgame/SecurityCamera.cpp new file mode 100644 index 0000000..2f30941 --- /dev/null +++ b/src/mpgame/SecurityCamera.cpp @@ -0,0 +1,602 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + + SecurityCamera.cpp + + Security camera that triggers targets when player is in view + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + + +/*********************************************************************** + + idSecurityCamera + +***********************************************************************/ + +const idEventDef EV_SecurityCam_ReverseSweep( "" ); +const idEventDef EV_SecurityCam_ContinueSweep( "" ); +const idEventDef EV_SecurityCam_Pause( "" ); +const idEventDef EV_SecurityCam_Alert( "" ); +const idEventDef EV_SecurityCam_AddLight( "" ); + +CLASS_DECLARATION( idEntity, idSecurityCamera ) + EVENT( EV_SecurityCam_ReverseSweep, idSecurityCamera::Event_ReverseSweep ) + EVENT( EV_SecurityCam_ContinueSweep, idSecurityCamera::Event_ContinueSweep ) + EVENT( EV_SecurityCam_Pause, idSecurityCamera::Event_Pause ) + EVENT( EV_SecurityCam_Alert, idSecurityCamera::Event_Alert ) + EVENT( EV_SecurityCam_AddLight, idSecurityCamera::Event_AddLight ) +END_CLASS + + +idSecurityCamera::~idSecurityCamera() { + SetPhysics( NULL ); +} + +/* +================ +idSecurityCamera::Save +================ +*/ +void idSecurityCamera::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( angle ); + savefile->WriteFloat( sweepAngle ); + savefile->WriteInt( modelAxis ); + savefile->WriteBool( flipAxis ); + savefile->WriteFloat( scanDist ); + savefile->WriteFloat( scanFov ); + + savefile->WriteFloat( sweepStart ); + savefile->WriteFloat( sweepEnd ); + savefile->WriteBool( negativeSweep ); + savefile->WriteBool( sweeping ); + savefile->WriteInt( alertMode ); + savefile->WriteFloat( stopSweeping ); + savefile->WriteFloat( scanFovCos ); + + savefile->WriteVec3( viewOffset ); + + savefile->WriteInt( pvsArea ); + savefile->WriteStaticObject( physicsObj ); + savefile->Write( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Restore +================ +*/ +void idSecurityCamera::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( angle ); + savefile->ReadFloat( sweepAngle ); + savefile->ReadInt( modelAxis ); + savefile->ReadBool( flipAxis ); + savefile->ReadFloat( scanDist ); + savefile->ReadFloat( scanFov ); + + savefile->ReadFloat( sweepStart ); + savefile->ReadFloat( sweepEnd ); + savefile->ReadBool( negativeSweep ); + savefile->ReadBool( sweeping ); + savefile->ReadInt( alertMode ); + savefile->ReadFloat( stopSweeping ); + savefile->ReadFloat( scanFovCos ); + + savefile->ReadVec3( viewOffset ); + + savefile->ReadInt( pvsArea ); + savefile->ReadStaticObject( physicsObj ); + savefile->Read( &trm, sizeof( trm ) ); +} + +/* +================ +idSecurityCamera::Spawn +================ +*/ +void idSecurityCamera::Spawn( void ) { + idStr str; + + sweepAngle = spawnArgs.GetFloat( "sweepAngle", "90" ); + health = spawnArgs.GetInt( "health", "100" ); + scanFov = spawnArgs.GetFloat( "scanFov", "90" ); + scanDist = spawnArgs.GetFloat( "scanDist", "200" ); + flipAxis = spawnArgs.GetBool( "flipAxis" ); + + modelAxis = spawnArgs.GetInt( "modelAxis" ); + if ( modelAxis < 0 || modelAxis > 2 ) { + modelAxis = 0; + } + + spawnArgs.GetVector( "viewOffset", "0 0 0", viewOffset ); + + if ( spawnArgs.GetBool( "spotLight" ) ) { + PostEventMS( &EV_SecurityCam_AddLight, 0 ); + } + + negativeSweep = ( sweepAngle < 0 ) ? true : false; + sweepAngle = abs( sweepAngle ); + + scanFovCos = idMath::Cos( scanFov * idMath::PI / 360.0f ); + + angle = GetPhysics()->GetAxis().ToAngles().yaw; + StartSweep(); + SetAlertMode( SCANNING ); + BecomeActive( TH_THINK ); + + if ( health ) { + fl.takedamage = true; + } + + pvsArea = gameLocal.pvs.GetPVSArea( GetPhysics()->GetOrigin() ); + // if no target specified use ourself + str = spawnArgs.GetString( "cameraTarget" ); + if ( str.Length() == 0 ) { + spawnArgs.Set( "cameraTarget", spawnArgs.GetString( "name" ) ); + } + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", str ); + if ( !str[0] ) { + str = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), str, trm ) ) { + gameLocal.Error( "idSecurityCamera '%s': cannot load collision model %s", name.c_str(), str.c_str() ); + return; + } + + GetPhysics()->SetContents( CONTENTS_SOLID ); + GetPhysics()->SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + // setup the physics + UpdateChangeableSpawnArgs( NULL ); +} + +/* +================ +idSecurityCamera::Event_AddLight +================ +*/ +void idSecurityCamera::Event_AddLight( void ) { + idDict args; + idVec3 right, up, target, temp; + idVec3 dir; + float radius; + idVec3 lightOffset; + idLight *spotLight; + + dir = GetAxis(); + dir.NormalVectors( right, up ); + target = GetPhysics()->GetOrigin() + dir * scanDist; + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + up = dir + up * radius; + up.Normalize(); + up = GetPhysics()->GetOrigin() + up * scanDist; + up -= target; + + right = dir + right * radius; + right.Normalize(); + right = GetPhysics()->GetOrigin() + right * scanDist; + right -= target; + + spawnArgs.GetVector( "lightOffset", "0 0 0", lightOffset ); + + args.Set( "origin", ( GetPhysics()->GetOrigin() + lightOffset ).ToString() ); + args.Set( "light_target", target.ToString() ); + args.Set( "light_right", right.ToString() ); + args.Set( "light_up", up.ToString() ); + args.SetFloat( "angle", GetPhysics()->GetAxis()[0].ToYaw() ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + spotLight = static_cast( gameLocal.SpawnEntityType( idLight::GetClassType(), &args ) ); +// RAVEN END + spotLight->Bind( this, true ); + spotLight->UpdateVisuals(); +} + +/* +================ +idSecurityCamera::DrawFov +================ +*/ +void idSecurityCamera::DrawFov( void ) { + int i; + float radius, a, s, c, halfRadius; + idVec3 right, up; + idVec4 color(1, 0, 0, 1), color2(0, 0, 1, 1); + idVec3 lastPoint, point, lastHalfPoint, halfPoint, center; + + idVec3 dir = GetAxis(); + dir.NormalVectors( right, up ); + + radius = idMath::Tan( scanFov * idMath::PI / 360.0f ); + halfRadius = radius * 0.5f; + lastPoint = dir + up * radius; + lastPoint.Normalize(); + lastPoint = GetPhysics()->GetOrigin() + lastPoint * scanDist; + lastHalfPoint = dir + up * halfRadius; + lastHalfPoint.Normalize(); + lastHalfPoint = GetPhysics()->GetOrigin() + lastHalfPoint * scanDist; + center = GetPhysics()->GetOrigin() + dir * scanDist; + for ( i = 1; i < 12; i++ ) { + a = idMath::TWO_PI * i / 12.0f; + idMath::SinCos( a, s, c ); + point = dir + right * s * radius + up * c * radius; + point.Normalize(); + point = GetPhysics()->GetOrigin() + point * scanDist; + gameRenderWorld->DebugLine( color, lastPoint, point ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), point ); + lastPoint = point; + + halfPoint = dir + right * s * halfRadius + up * c * halfRadius; + halfPoint.Normalize(); + halfPoint = GetPhysics()->GetOrigin() + halfPoint * scanDist; + gameRenderWorld->DebugLine( color2, point, halfPoint ); + gameRenderWorld->DebugLine( color2, lastHalfPoint, halfPoint ); + lastHalfPoint = halfPoint; + + gameRenderWorld->DebugLine( color2, halfPoint, center ); + } +} + +/* +================ +idSecurityCamera::GetRenderView +================ +*/ +renderView_t *idSecurityCamera::GetRenderView() { + renderView_t *rv = idEntity::GetRenderView(); + rv->fov_x = scanFov; + rv->fov_y = scanFov; + rv->viewaxis = GetAxis().ToAngles().ToMat3(); + rv->vieworg = GetPhysics()->GetOrigin() + viewOffset; + return rv; +} + +/* +================ +idSecurityCamera::CanSeePlayer +================ +*/ +bool idSecurityCamera::CanSeePlayer( void ) { + int i; + float dist; + idPlayer *ent; + trace_t tr; + idVec3 dir; + pvsHandle_t handle; + + handle = gameLocal.pvs.SetupCurrentPVS( pvsArea ); + + for ( i = 0; i < gameLocal.numClients; i++ ) { + ent = static_cast(gameLocal.entities[ i ]); + + if ( !ent || ( ent->fl.notarget ) ) { + continue; + } + + // if there is no way we can see this player + if ( !gameLocal.pvs.InCurrentPVS( handle, ent->GetPVSAreas(), ent->GetNumPVSAreas() ) ) { + continue; + } + + dir = ent->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dist = dir.Normalize(); + + if ( dist > scanDist ) { + continue; + } + + if ( dir * GetAxis() < scanFovCos ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, GetPhysics()->GetOrigin(), ent->GetEyePosition(), MASK_OPAQUE, this ); +// RAVEN END + if ( tr.fraction == 1.0 || ( gameLocal.GetTraceEntity( tr ) == ent ) ) { + gameLocal.pvs.FreeCurrentPVS( handle ); + return true; + } + } + + gameLocal.pvs.FreeCurrentPVS( handle ); + + return false; +} + +/* +================ +idSecurityCamera::SetAlertMode +================ +*/ +void idSecurityCamera::SetAlertMode( int alert ) { + if (alert >= SCANNING && alert <= ACTIVATED) { + alertMode = alert; + } + renderEntity.shaderParms[ SHADERPARM_MODE ] = alertMode; + UpdateVisuals(); +} + +/* +================ +idSecurityCamera::Think +================ +*/ +void idSecurityCamera::Think( void ) { + float pct; + float travel; + + if ( thinkFlags & TH_THINK ) { + if ( g_showEntityInfo.GetBool() ) { + DrawFov(); + } + + if (health <= 0) { + BecomeInactive( TH_THINK ); + return; + } + } + + // run physics + RunPhysics(); + + if ( thinkFlags & TH_THINK ) { + if (CanSeePlayer()) { + if (alertMode == SCANNING) { + float sightTime; + + SetAlertMode(ALERT); + stopSweeping = gameLocal.time; + if (sweeping) { + CancelEvents( &EV_SecurityCam_Pause ); + } else { + CancelEvents( &EV_SecurityCam_ReverseSweep ); + } + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_sight", SND_CHANNEL_BODY, 0, false, NULL ); + + sightTime = spawnArgs.GetFloat( "sightTime", "5" ); + PostEventSec(&EV_SecurityCam_Alert, sightTime); + } + } else { + if (alertMode == ALERT) { + float sightResume; + + SetAlertMode(LOSINGINTEREST); + CancelEvents( &EV_SecurityCam_Alert ); + + sightResume = spawnArgs.GetFloat( "sightResume", "1.5" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, sightResume ); + } + + if ( sweeping ) { + idAngles a = GetPhysics()->GetAxis().ToAngles(); + + pct = ( gameLocal.time - sweepStart ) / ( sweepEnd - sweepStart ); + travel = pct * sweepAngle; + if ( negativeSweep ) { + a.yaw = angle + travel; + } else { + a.yaw = angle - travel; + } + + SetAngles( a ); + } + } + } + Present(); +} + +/* +================ +idSecurityCamera::GetAxis +================ +*/ +const idVec3 idSecurityCamera::GetAxis( void ) const { + return (flipAxis) ? -GetPhysics()->GetAxis()[modelAxis] : GetPhysics()->GetAxis()[modelAxis]; +}; + +/* +================ +idSecurityCamera::SweepSpeed +================ +*/ +float idSecurityCamera::SweepSpeed( void ) const { + return spawnArgs.GetFloat( "sweepSpeed", "5" ); +} + +/* +================ +idSecurityCamera::StartSweep +================ +*/ +void idSecurityCamera::StartSweep( void ) { + int speed; + + sweeping = true; + sweepStart = gameLocal.time; + speed = SEC2MS( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed ); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idSecurityCamera::Event_ContinueSweep +================ +*/ +void idSecurityCamera::Event_ContinueSweep( void ) { + float pct = (stopSweeping - sweepStart) / (sweepEnd - sweepStart); + float f = gameLocal.time - (sweepEnd - sweepStart) * pct; + int speed; + + sweepStart = f; + speed = MS2SEC( SweepSpeed() ); + sweepEnd = sweepStart + speed; + PostEventMS( &EV_SecurityCam_Pause, speed * (1.0 - pct)); + StartSound( "snd_moving", SND_CHANNEL_BODY, 0, false, NULL ); + SetAlertMode(SCANNING); + sweeping = true; +} + +/* +================ +idSecurityCamera::Event_Alert +================ +*/ +void idSecurityCamera::Event_Alert( void ) { + float wait; + + SetAlertMode(ACTIVATED); + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_activate", SND_CHANNEL_BODY, 0, false, NULL ); + ActivateTargets(this); + CancelEvents( &EV_SecurityCam_ContinueSweep ); + + wait = spawnArgs.GetFloat( "wait", "20" ); + PostEventSec( &EV_SecurityCam_ContinueSweep, wait ); +} + +/* +================ +idSecurityCamera::Event_ReverseSweep +================ +*/ +void idSecurityCamera::Event_ReverseSweep( void ) { + angle = GetPhysics()->GetAxis().ToAngles().yaw; + negativeSweep = !negativeSweep; + StartSweep(); +} + +/* +================ +idSecurityCamera::Event_Pause +================ +*/ +void idSecurityCamera::Event_Pause( void ) { + float sweepWait; + + sweepWait = spawnArgs.GetFloat( "sweepWait", "0.5" ); + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); + StartSound( "snd_stop", SND_CHANNEL_BODY, 0, false, NULL ); + PostEventSec( &EV_SecurityCam_ReverseSweep, sweepWait ); +} + +/* +============ +idSecurityCamera::Killed +============ +*/ +void idSecurityCamera::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + sweeping = false; + StopSound( SND_CHANNEL_ANY, false ); +// RAVEN BEGIN +// bdube: replaced fx call with raven call + gameLocal.PlayEffect ( spawnArgs, "fx_destroyed", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +/* + const char *fx = spawnArgs.GetString( "fx_destroyed" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( trm ), 0.02f ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetBouncyness( 0.2f ); + physicsObj.SetFriction( 0.6f, 0.6f, 0.2f ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + SetPhysics( &physicsObj ); + physicsObj.DropToFloor(); +} + + +/* +============ +idSecurityCamera::Pain +============ +*/ +bool idSecurityCamera::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { +// RAVEN BEGIN +// bdube: replaced fx call with raven call + PlayEffect ( "fx_damage", renderEntity.origin, renderEntity.axis ); +/* + const char *fx = spawnArgs.GetString( "fx_damage" ); + if ( fx[0] != '\0' ) { + idEntityFx::StartFx( fx, NULL, NULL, this, true ); + } +*/ +// RAVEN END + return true; +} + + +/* +================ +idSecurityCamera::Present + +Present is called to allow entities to generate refEntities, lights, etc for the renderer. +================ +*/ +void idSecurityCamera::Present( void ) { + // don't present to the renderer if the entity hasn't changed + if ( !( thinkFlags & TH_UPDATEVISUALS ) ) { + return; + } + BecomeInactive( TH_UPDATEVISUALS ); + + // camera target for remote render views + if ( cameraTarget ) { + renderEntity.remoteRenderView = cameraTarget->GetRenderView(); + } + + // if set to invisible, skip + if ( !renderEntity.hModel || IsHidden() ) { + return; + } + + // add to refresh list + if ( modelDefHandle == -1 ) { + modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity ); + } +} diff --git a/src/mpgame/SecurityCamera.h b/src/mpgame/SecurityCamera.h new file mode 100644 index 0000000..94136a7 --- /dev/null +++ b/src/mpgame/SecurityCamera.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_SECURITYCAMERA_H__ +#define __GAME_SECURITYCAMERA_H__ + +/* +=================================================================================== + + Security camera + +=================================================================================== +*/ + + +class idSecurityCamera : public idEntity { +public: + CLASS_PROTOTYPE( idSecurityCamera ); + + ~idSecurityCamera( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Think( void ); + + virtual renderView_t * GetRenderView(); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Present( void ); + +private: + + enum { SCANNING, LOSINGINTEREST, ALERT, ACTIVATED }; + + float angle; + float sweepAngle; + int modelAxis; + bool flipAxis; + float scanDist; + float scanFov; + + float sweepStart; + float sweepEnd; + bool negativeSweep; + bool sweeping; + int alertMode; + float stopSweeping; + float scanFovCos; + + idVec3 viewOffset; + + int pvsArea; + idPhysics_RigidBody physicsObj; + idTraceModel trm; + + void StartSweep( void ); + bool CanSeePlayer( void ); + void SetAlertMode( int status ); + void DrawFov( void ); + const idVec3 GetAxis( void ) const; + float SweepSpeed( void ) const; + + void Event_ReverseSweep( void ); + void Event_ContinueSweep( void ); + void Event_Pause( void ); + void Event_Alert( void ); + void Event_AddLight( void ); +}; + +#endif /* !__GAME_SECURITYCAMERA_H__ */ diff --git a/src/mpgame/Sound.cpp b/src/mpgame/Sound.cpp new file mode 100644 index 0000000..f0a0352 --- /dev/null +++ b/src/mpgame/Sound.cpp @@ -0,0 +1,394 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +=============================================================================== + + SOUND + +=============================================================================== +*/ + +const idEventDef EV_Speaker_On( "On", NULL ); +const idEventDef EV_Speaker_Off( "Off", NULL ); +const idEventDef EV_Speaker_Timer( "", NULL ); + +CLASS_DECLARATION( idEntity, idSound ) + EVENT( EV_Activate, idSound::Event_Trigger ) + EVENT( EV_Speaker_On, idSound::Event_On ) + EVENT( EV_Speaker_Off, idSound::Event_Off ) + EVENT( EV_Speaker_Timer, idSound::Event_Timer ) +END_CLASS + + +/* +================ +idSound::idSound +================ +*/ +idSound::idSound( void ) { + lastSoundVol = 0.0f; + soundVol = 0.0f; + shakeTranslate.Zero(); + shakeRotate.Zero(); + random = 0.0f; + wait = 0.0f; + timerOn = false; + playingUntilTime = 0; +} + +/* +================ +idSound::Save +================ +*/ +void idSound::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( lastSoundVol ); + savefile->WriteFloat( soundVol ); + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( timerOn ); + savefile->WriteVec3( shakeTranslate ); + savefile->WriteAngles( shakeRotate ); + savefile->WriteInt( playingUntilTime ); +} + +/* +================ +idSound::Restore +================ +*/ +void idSound::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( lastSoundVol ); + savefile->ReadFloat( soundVol ); + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( timerOn ); + savefile->ReadVec3( shakeTranslate ); + savefile->ReadAngles( shakeRotate ); + savefile->ReadInt( playingUntilTime ); +} + +/* +================ +idSound::Spawn +================ +*/ +void idSound::Spawn( void ) { + spawnArgs.GetVector( "move", "0 0 0", shakeTranslate ); + spawnArgs.GetAngles( "rotate", "0 0 0", shakeRotate ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + soundVol = 0.0f; + lastSoundVol = 0.0f; + + if ( ( shakeRotate != ang_zero ) || ( shakeTranslate != vec3_zero ) ) { + BecomeActive( TH_THINK ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } else { + timerOn = false; + } + +} + +/* +================ +idSound::Event_Trigger + +this will toggle the idle idSound on and off +================ +*/ +void idSound::Event_Trigger( idEntity *activator ) { + if ( wait > 0.0f ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } else { + timerOn = true; + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + } else { + if ( gameLocal.isMultiplayer ) { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && ( gameLocal.time < playingUntilTime ) ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } else { +// RAVEN BEGIN + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter && emitter->CurrentlyPlaying() ) { +// RAVEN END + DoSound( false ); + } else { + DoSound( true ); + } + } + } +} + +/* +================ +idSound::Event_Timer +================ +*/ +void idSound::Event_Timer( void ) { + DoSound( true ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +} + +/* +================ +idSound::Think +================ +*/ +void idSound::Think( void ) { + idAngles ang; + + // run physics + RunPhysics(); + + // clear out our update visuals think flag since we never call Present + BecomeInactive( TH_UPDATEVISUALS ); +} + +/* +=============== +idSound::UpdateChangableSpawnArgs +=============== +*/ +void idSound::UpdateChangeableSpawnArgs( const idDict *source ) { + + idEntity::UpdateChangeableSpawnArgs( source ); + + if ( source ) { + FreeSoundEmitter( true ); + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + + spawnArgs.Copy( *source ); + gameEdit->ParseSpawnArgsToRefSound( &spawnArgs, &refSound ); + + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToSoundTransform( origin, axis ) ) { + refSound.origin = GetPhysics()->GetOrigin() + origin * axis; + } else { + refSound.origin = GetPhysics()->GetOrigin(); + } + + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "wait", "0", wait ); + + if ( ( wait > 0.0f ) && ( random >= wait ) ) { + random = wait - 0.001; + gameLocal.Warning( "speaker '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( !refSound.waitfortrigger && ( wait > 0.0f ) ) { + timerOn = true; + DoSound( false ); + CancelEvents( &EV_Speaker_Timer ); + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); +// RAVEN BEGIN + } else if ( !refSound.waitfortrigger ) { + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + // start it if it isn't already playing, and we aren't waitForTrigger + DoSound( true ); + timerOn = false; + } + } + } +} + +/* +=============== +idSound::SetSound +=============== +*/ +void idSound::SetSound( const char *sound, int channel ) { + const idSoundShader *shader = declManager->FindSound( sound ); + if ( shader != refSound.shader ) { + FreeSoundEmitter( true ); + } + gameEdit->ParseSpawnArgsToRefSound(&spawnArgs, &refSound); + refSound.shader = shader; +// RAVEN BEGIN + // start it if it isn't already playing, and we aren't waitForTrigger + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !refSound.waitfortrigger && !( emitter && emitter->CurrentlyPlaying() ) ) { +// RAVEN END + DoSound( true ); + } + +} + +/* +================ +idSound::DoSound +================ +*/ +void idSound::DoSound( bool play ) { + if ( play ) { + StartSoundShader( refSound.shader, SND_CHANNEL_ANY, refSound.parms.soundShaderFlags, true, &playingUntilTime ); + playingUntilTime += gameLocal.time; + } else { + StopSound( SND_CHANNEL_ANY, true ); + playingUntilTime = 0; + } +} + +/* +================ +idSound::Event_On +================ +*/ +void idSound::Event_On( void ) { + if ( wait > 0.0f ) { + timerOn = true; + PostEventSec( &EV_Speaker_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } + DoSound( true ); +} + +/* +================ +idSound::Event_Off +================ +*/ +void idSound::Event_Off( void ) { + if ( timerOn ) { + timerOn = false; + CancelEvents( &EV_Speaker_Timer ); + } + DoSound( false ); +} + +// RAVEN BEGIN +// abahr: so we only set the referenceSounds on our targets once +/* +================ +idSound::FindTargets +================ +*/ +void idSound::FindTargets() { + idEntity::FindTargets(); + + if( !targets.Num() ) {// I don't think we ever get in here unless we have targets + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + // if we have targets lets get an emitter + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + SetTargetSoundHandles(); +} +// RAVEN END + +/* +=============== +idSound::ShowEditingDialog +=============== +*/ +void idSound::ShowEditingDialog( void ) { + common->InitTool( EDITOR_SOUND, &spawnArgs ); +} + +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and have those lights use this speaker's refSound +/* +=============== +idSound::SetSoundHandles +=============== +*/ +void idSound::SetTargetSoundHandles( void ) { +//this code is mostly boosted from a similar function in light.cpp + int i; + idEntity *targetEnt; + + //is this check really necessary? We are a speaker after all... + if ( !soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ) ) { + return; + } + + for ( i = 0; i < targets.Num(); i++ ) { + targetEnt = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( targetEnt && targetEnt->IsType( idLight::GetClassType() ) ) { +// RAVEN END + idLight *light = static_cast(targetEnt); + + //no need to make this speaker a lightparent.... + //light->lightParent = this; + + // explicitly delete any sounds on the entity + light->FreeSoundEmitter( true ); + + // manually set the refSound to this light's refSound + light->SetRefSound(refSound.referenceSoundHandle); + + // update the renderEntity to the renderer + light->UpdateVisuals(); + } + } +} + +// abahr: +/* +================ +idSound::GetPhysicsToSoundTransform +================ +*/ +bool idSound::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) { + origin = shakeTranslate.Random( spawnArgs.GetVector("move_random_delta"), gameLocal.random ); + axis = shakeRotate.Random( spawnArgs.GetVector("shake_random_delta"), gameLocal.random ).ToMat3(); + return true; +} +// RAVEN END diff --git a/src/mpgame/Sound.h b/src/mpgame/Sound.h new file mode 100644 index 0000000..cbab583 --- /dev/null +++ b/src/mpgame/Sound.h @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_SOUND_H__ +#define __GAME_SOUND_H__ + +/* +=============================================================================== + + Generic sound emitter. + +=============================================================================== +*/ + +class idSound : public idEntity { +public: + CLASS_PROTOTYPE( idSound ); + + idSound( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void UpdateChangeableSpawnArgs( const idDict *source ); + + void Spawn( void ); + + void ToggleOnOff( idEntity *other, idEntity *activator ); + void Think( void ); + void SetSound( const char *sound, int channel = SND_CHANNEL_ANY ); + + virtual void ShowEditingDialog( void ); + +private: + float lastSoundVol; + float soundVol; + float random; + float wait; + bool timerOn; + idVec3 shakeTranslate; + idAngles shakeRotate; + int playingUntilTime; + + void Event_Trigger( idEntity *activator ); + void Event_Timer( void ); + void Event_On( void ); + void Event_Off( void ); + + void DoSound( bool play ); +// RAVEN BEGIN +// jshepard: Allow speakers to target lights and tie them to the speaker's ref sound + void SetTargetSoundHandles( void ); +// abahr + virtual void FindTargets(); + bool GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ); +// RAVEN END + +}; + +#endif /* !__GAME_SOUND_H__ */ diff --git a/src/mpgame/SplineMover.cpp b/src/mpgame/SplineMover.cpp new file mode 100644 index 0000000..33b19b5 --- /dev/null +++ b/src/mpgame/SplineMover.cpp @@ -0,0 +1,3313 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" + +const idEventDef EV_OnAcceleration( "" ); +const idEventDef EV_OnDeceleration( "" ); +const idEventDef EV_OnCruising( "" ); + +const idEventDef EV_OnStartMoving( "" ); +const idEventDef EV_OnStopMoving( "" ); + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Spline ) + EVENT( EV_PostRestore, rvPhysics_Spline::Event_PostRestore ) +END_CLASS + +void splinePState_t::ApplyAccelerationDelta( float timeStepSec ) { + speed = SignZero(idealSpeed) * Min( idMath::Fabs(idealSpeed), idMath::Fabs(speed) + acceleration * timeStepSec ); +} + +void splinePState_t::ApplyDecelerationDelta( float timeStepSec ) { + speed = SignZero(speed) * Max( idMath::Fabs(idealSpeed), idMath::Fabs(speed) - deceleration * timeStepSec ); +} + +void splinePState_t::UpdateDist( float timeStepSec ) { + dist += speed * timeStepSec; +} + +bool splinePState_t::ShouldAccelerate() const { + if( Sign(idealSpeed) == Sign(speed) ) { + return idMath::Fabs(speed) < idMath::Fabs(idealSpeed); + } else if( !Sign(speed) ) { + return true; + } + + return false; +} + +bool splinePState_t::ShouldDecelerate() const { + if( Sign(speed) == Sign(idealSpeed) ) { + return idMath::Fabs(speed) > idMath::Fabs(idealSpeed); + } else if( !Sign(idealSpeed) ) { + return true; + } + + return false; +} + +void splinePState_t::Clear() { + origin.Zero(); + localOrigin.Zero(); + axis.Identity(); + localAxis.Identity(); + speed = 0.0f; + idealSpeed = 0.0f; + dist = 0.0f; + acceleration = 0.0f; + deceleration = 0.0f; +} + +void splinePState_t::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteDeltaVec3( vec3_zero, origin ); + msg.WriteDeltaVec3( vec3_zero, localOrigin ); + msg.WriteDeltaMat3( mat3_identity, axis ); + msg.WriteDeltaMat3( mat3_identity, localAxis ); + msg.WriteDeltaFloat( 0.0f, speed ); + msg.WriteDeltaFloat( 0.0f, idealSpeed ); + msg.WriteDeltaFloat( 0.0f, dist ); + msg.WriteDeltaFloat( 0.0f, acceleration ); + msg.WriteDeltaFloat( 0.0f, deceleration ); +} + +void splinePState_t::ReadFromSnapshot( const idBitMsgDelta &msg ) { + origin = msg.ReadDeltaVec3( vec3_zero ); + localOrigin = msg.ReadDeltaVec3( vec3_zero ); + axis = msg.ReadDeltaMat3( mat3_identity ); + localAxis = msg.ReadDeltaMat3( mat3_identity ); + speed = msg.ReadDeltaFloat( 0.0f ); + idealSpeed = msg.ReadDeltaFloat( 0.0f ); + dist = msg.ReadDeltaFloat( 0.0f ); + acceleration = msg.ReadDeltaFloat( 0.0f ); + deceleration = msg.ReadDeltaFloat( 0.0f ); +} + +void splinePState_t::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( origin ); + savefile->WriteVec3( localOrigin ); + savefile->WriteMat3( axis ); + savefile->WriteMat3( localAxis ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( dist ); + savefile->WriteFloat( acceleration ); + savefile->WriteFloat( deceleration ); +} + +void splinePState_t::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( origin ); + savefile->ReadVec3( localOrigin ); + savefile->ReadMat3( axis ); + savefile->ReadMat3( localAxis ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( dist ); + savefile->ReadFloat( acceleration ); + savefile->ReadFloat( deceleration ); +} + +splinePState_t& splinePState_t::Assign( const splinePState_t* state ) { + SIMDProcessor->Memcpy( this, state, sizeof(splinePState_t) ); + return *this; +} + +splinePState_t& splinePState_t::operator=( const splinePState_t& state ) { + return Assign( &state ); +} + +splinePState_t& splinePState_t::operator=( const splinePState_t* state ) { + return Assign( state ); +} + +/* +================ +rvPhysics_Spline::rvPhysics_Spline +================ +*/ +rvPhysics_Spline::rvPhysics_Spline( void ) { + accelDecelStateThread.SetName( "AccelDecel" ); + accelDecelStateThread.SetOwner( this ); + accelDecelStateThread.SetState( "Cruising" ); + + clipModel = NULL; + + spline = NULL; + SetSplineEntity( NULL ); + + memset( &pushResults, 0, sizeof(trace_t) ); + pushResults.fraction = 1.0f; + + current.Clear(); + SaveState(); +} + +/* +================ +rvPhysics_Spline::~rvPhysics_Spline +================ +*/ +rvPhysics_Spline::~rvPhysics_Spline( void ) { + SAFE_DELETE_PTR( clipModel ); + + SAFE_DELETE_PTR( spline ); +} + +/* +================ +rvPhysics_Spline::Save +================ +*/ +void rvPhysics_Spline::Save( idSaveGame *savefile ) const { + + current.Save( savefile ); + saved.Save( savefile ); + + savefile->WriteFloat( splineLength ); + // This spline was retored as NULL, so there's no reason to save it. + //savefile->WriteInt(spline != NULL ? spline->GetTime( 0 ) : -1 ); // cnicholson: Added unsaved var + splineEntity.Save( savefile ); + + savefile->WriteTrace( pushResults ); + + savefile->WriteClipModel( clipModel ); + + accelDecelStateThread.Save( savefile ); +} + +/* +================ +rvPhysics_Spline::Restore +================ +*/ +void rvPhysics_Spline::Event_PostRestore( void ) { + + if( splineEntity.IsValid() ) { + spline = splineEntity->GetSpline(); + } +} + +void rvPhysics_Spline::Restore( idRestoreGame *savefile ) { + + current.Restore( savefile ); + saved.Restore( savefile ); + + savefile->ReadFloat( splineLength ); + SAFE_DELETE_PTR( spline ); + splineEntity.Restore( savefile ); + + savefile->ReadTrace( pushResults ); + + savefile->ReadClipModel( clipModel ); + + accelDecelStateThread.Restore( savefile, this ); +} + +/* +================ +rvPhysics_Spline::SetClipModel +================ +*/ +void rvPhysics_Spline::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; + + LinkClip(); +} + +/* +================ +rvPhysics_Spline::GetClipModel +================ +*/ +idClipModel *rvPhysics_Spline::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Spline::SetContents +================ +*/ +void rvPhysics_Spline::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Spline::GetContents +================ +*/ +int rvPhysics_Spline::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Spline::GetBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Spline::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Spline::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Spline::SetSpline +================ +*/ +void rvPhysics_Spline::SetSpline( idCurve_Spline* spline ) { + SAFE_DELETE_PTR( this->spline ); + + //Keep any left over dist from last spline to minimize hitches + if( GetSpeed() >= 0.0f ) { + current.dist = Max( 0.0f, current.dist - splineLength ); + } + + if( !spline ) { + splineLength = 0.0f; + return; + } + + this->spline = spline; + + splineLength = spline->GetLengthForTime( spline->GetTime(spline->GetNumValues() - 1) ); + if( GetSpeed() < 0.0f ) { + current.dist = splineLength - current.dist; + } + + Activate(); +} + +/* +================ +rvPhysics_Spline::SetSplineEntity +================ +*/ +void rvPhysics_Spline::SetSplineEntity( idSplinePath* spline ) { + splineEntity = spline; + SetSpline( (spline) ? spline->GetSpline() : NULL ); +} + +/* +================ +rvPhysics_Spline::ComputeDecelFromSpline +================ +*/ +float rvPhysics_Spline::ComputeDecelFromSpline() const { + // FIXME: merge this in better. It seems very special case + float numerator = GetSpeed() * GetSpeed(); + float denomonator = 2.0f * ((GetSpeed() >= 0.0f) ? (splineLength - current.dist) : current.dist); + + assert( denomonator > VECTOR_EPSILON ); + + return numerator / denomonator; +} + +/* +================ +rvPhysics_Spline::SetLinearAcceleration +================ +*/ +void rvPhysics_Spline::SetLinearAcceleration( const float accel ) { + current.acceleration = accel; +} + +/* +================ +rvPhysics_Spline::SetLinearDeceleration +================ +*/ +void rvPhysics_Spline::SetLinearDeceleration( const float decel ) { + current.deceleration = decel; +} + +/* +================ +rvPhysics_Spline::SetSpeed +================ +*/ +void rvPhysics_Spline::SetSpeed( float speed ) { + if( IsAtRest() || StoppedMoving() ) { + current.dist = (speed < 0.0f) ? splineLength - current.dist : current.dist; + } + + current.idealSpeed = speed; + Activate(); +} + +/* +================ +rvPhysics_Spline::GetSpeed +================ +*/ +float rvPhysics_Spline::GetSpeed() const { + return current.speed; +} + +/* +================ +rvPhysics_Spline::Evaluate +================ +*/ +bool rvPhysics_Spline::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + splinePState_t previous = current; + + if( HasValidSpline() ) { + if( StoppedMoving() ) { + Rest(); + return false; + } + + accelDecelStateThread.Execute(); + + // FIXME: clean this up + if( IsAtBeginningOfSpline() || IsAtEndOfSpline() ) { + current = previous; + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + + if( gameLocal.program.GetReturnedBool() ) { + current.speed = 0.0f; + return false; + } else { + return true; + } + } + + float currentTime = splineEntity->GetSampledTime ( current.dist ); + if ( currentTime == -1.0f ) { + currentTime = spline->GetTimeForLength( Min(current.dist, splineLength), 0.01f ); + } + + current.axis = spline->GetCurrentFirstDerivative(currentTime).ToAngles().Normalize360().ToMat3(); + current.origin = spline->GetCurrentValue( currentTime ); + current.localOrigin = current.origin; + current.localAxis = current.axis; + } else if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAxis * masterAxis; + current.origin = masterOrigin + current.localOrigin * masterAxis; + } else { + Rest(); + return false; + } + + gameLocal.push.ClipPush( pushResults, self, 0, previous.origin, previous.axis, current.origin, current.axis ); + if( pushResults.fraction < 1.0f ) { + current = previous; + LinkClip(); + current.speed = 0.0f; + return false; + } + + LinkClip(); + + if( StoppedMoving() && !self->IsBound() ) { + Rest(); + self->ProcessEvent( &EV_DoneMoving ); + return !gameLocal.program.GetReturnedBool(); + } + + return true; +} + +/* +================ +rvPhysics_Spline::Activate +================ +*/ +void rvPhysics_Spline::Activate( void ) { + assert( self ); + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::Rest +================ +*/ +void rvPhysics_Spline::Rest( void ) { + assert( self ); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtRest +================ +*/ +bool rvPhysics_Spline::IsAtRest( void ) const { + assert( self ); + return !self->IsActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Spline::IsAtEndOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtEndOfSpline( void ) const { + return current.dist >= splineLength; +} + +/* +================ +rvPhysics_Spline::IsAtBeginningOfSpline +================ +*/ +bool rvPhysics_Spline::IsAtBeginningOfSpline( void ) const { + return current.dist <= 0.0f; +} + +/* +================ +rvPhysics_Spline::IsPushable +================ +*/ +bool rvPhysics_Spline::IsPushable( void ) const { + return !HasValidSpline() && idPhysics_Base::IsPushable(); +} + +/* +================ +rvPhysics_Spline::StartingToMove +================ +*/ +bool rvPhysics_Spline::StartingToMove( void ) const { + float firstDeltaSpeed = current.acceleration * MS2SEC(gameLocal.GetMSec()); + return idMath::Fabs(current.idealSpeed) > VECTOR_EPSILON && idMath::Fabs(current.speed) <= firstDeltaSpeed; +} + +/* +================ +rvPhysics_Spline::StoppedMoving +================ +*/ +bool rvPhysics_Spline::StoppedMoving( void ) const { + return idMath::Fabs(current.idealSpeed) < VECTOR_EPSILON && idMath::Fabs(current.speed) < VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::HasValidSpline +================ +*/ +bool rvPhysics_Spline::HasValidSpline() const { + return spline && splineLength > VECTOR_EPSILON; +} + +/* +================ +rvPhysics_Spline::SaveState +================ +*/ +void rvPhysics_Spline::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Spline::RestoreState +================ +*/ +void rvPhysics_Spline::RestoreState( void ) { + current = saved; + + LinkClip(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Spline::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + + LinkClip(); + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Spline::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( self->IsBound() ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } + else { + current.axis = newAxis; + } + + LinkClip(); + Activate(); +} + +/* +================ +rvPhysics_Spline::Translate +================ +*/ +void rvPhysics_Spline::Translate( const idVec3 &translation, int id ) { + SetOrigin( GetLocalOrigin() + translation ); +} + +/* +================ +rvPhysics_Spline::Rotate +================ +*/ +void rvPhysics_Spline::Rotate( const idRotation &rotation, int id ) { + SetAxis( GetLocalAxis() * rotation.ToMat3() ); + SetOrigin( GetLocalOrigin() * rotation ); +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetOrigin( int id ) { + return current.origin; +} + +/* +================ +rvPhysics_Spline::GetAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetAxis( int id ) { + return current.axis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +const idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) const { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +const idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) const { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::GetLocalOrigin +================ +*/ +idVec3 &rvPhysics_Spline::GetLocalOrigin( int id ) { + return current.localOrigin; +} + +/* +================ +rvPhysics_Spline::GetLocalAxis +================ +*/ +idMat3 &rvPhysics_Spline::GetLocalAxis( int id ) { + return current.localAxis; +} + +/* +================ +rvPhysics_Spline::SetMaster +================ +*/ +void rvPhysics_Spline::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if( master ) { + if( self->IsBound() ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( GetOrigin() - masterOrigin ) * masterAxis.Transpose(); + current.localAxis = GetAxis() * masterAxis.Transpose(); + } + } +} + +/* +================ +rvPhysics_Spline::ClipTranslation +================ +*/ +void rvPhysics_Spline::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, GetOrigin(), GetOrigin() + translation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipRotation +================ +*/ +void rvPhysics_Spline::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, GetOrigin(), rotation, + clipModel, GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Spline::ClipContents +================ +*/ +int rvPhysics_Spline::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return gameLocal.ContentsModel( self, GetOrigin(), clipModel, GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, GetOrigin(), clipModel, GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::DisableClip +================ +*/ +void rvPhysics_Spline::DisableClip( void ) { + if( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +rvPhysics_Spline::EnableClip +================ +*/ +void rvPhysics_Spline::EnableClip( void ) { + if( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +rvPhysics_Spline::UnlinkClip +================ +*/ +void rvPhysics_Spline::UnlinkClip( void ) { + if( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +rvPhysics_Spline::LinkClip +================ +*/ +void rvPhysics_Spline::LinkClip( void ) { + if( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), GetOrigin(), GetAxis() ); +// RAVEN END + } +} + +/* +================ +rvPhysics_Spline::GetBlockingInfo +================ +*/ +const trace_t* rvPhysics_Spline::GetBlockingInfo( void ) const { + return (pushResults.fraction < 1.0f) ? &pushResults : NULL; +} + +/* +================ +rvPhysics_Spline::GetBlockingEntity +================ +*/ +idEntity* rvPhysics_Spline::GetBlockingEntity( void ) const { + return (pushResults.fraction < 1.0f) ? gameLocal.entities[ pushResults.c.entityNum ] : NULL; +} + +/* +================ +rvPhysics_Spline::WriteToSnapshot +================ +*/ +void rvPhysics_Spline::WriteToSnapshot( idBitMsgDelta &msg ) const { + current.WriteToSnapshot( msg ); +} + +/* +================ +rvPhysics_Spline::ReadFromSnapshot +================ +*/ +void rvPhysics_Spline::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.ReadFromSnapshot( msg ); + + LinkClip(); +} + +CLASS_STATES_DECLARATION( rvPhysics_Spline ) + STATE( "Accelerating", rvPhysics_Spline::State_Accelerating ) + STATE( "Decelerating", rvPhysics_Spline::State_Decelerating ) + STATE( "Cruising", rvPhysics_Spline::State_Cruising ) +END_CLASS_STATES + +/* +================ +rvPhysics_Spline::State_Accelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Accelerating( const stateParms_t& parms ) { + stateResult_t returnResult = SRESULT_WAIT; + + if( !current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( current.ShouldDecelerate() ? "Decelerating" : "Cruising" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + if( StartingToMove() ) { + self->ProcessEvent( &EV_OnStartMoving ); + } + self->ProcessEvent( &EV_OnAcceleration ); + + returnResult = SRESULT_STAGE( parms.stage + 1 ); + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyAccelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + return returnResult; +} + +/* +================ +rvPhysics_Spline::State_Decelerating +================ +*/ +stateResult_t rvPhysics_Spline::State_Decelerating( const stateParms_t& parms ) { + if( !current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( current.ShouldAccelerate() ? "Accelerating" : "Cruising" ); + return SRESULT_DONE; + } + + float timeStepSec = MS2SEC( gameLocal.GetMSec() ); + current.ApplyDecelerationDelta( timeStepSec ); + current.UpdateDist( timeStepSec ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnDeceleration ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( StoppedMoving() ) { + self->ProcessEvent( &EV_OnStopMoving ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvPhysics_Spline::State_Cruising +================ +*/ +stateResult_t rvPhysics_Spline::State_Cruising( const stateParms_t& parms ) { + if( current.ShouldAccelerate() ) { + accelDecelStateThread.SetState( "Accelerating" ); + return SRESULT_DONE; + } else if( current.ShouldDecelerate() ) { + accelDecelStateThread.SetState( "Decelerating" ); + return SRESULT_DONE; + } + + current.UpdateDist( MS2SEC(gameLocal.GetMSec()) ); + + if( !parms.stage ) { + self->ProcessEvent( &EV_OnCruising ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + return SRESULT_WAIT; +} + + + +const idEventDef EV_SetSpline( "setSpline", "E" ); + +const idEventDef EV_SetAccel( "setAccel", "f" ); +const idEventDef EV_SetDecel( "setDecel", "f" ); + +const idEventDef EV_SetSpeed( "setSpeed", "f" ); +const idEventDef EV_GetSpeed( "getSpeed", "", 'f' ); + +const idEventDef EV_TramCar_SetIdealSpeed( "setIdealSpeed", "f" ); +const idEventDef EV_TramCar_GetIdealSpeed( "getIdealSpeed", "", 'f' ); + +const idEventDef EV_TramCar_ApplySpeedScale( "applySpeedScale", "f" ); + +const idEventDef EV_GetCurrentTrackInfo( "getCurrentTrackInfo", "", 's' ); +const idEventDef EV_GetTrackInfo( "getTrackInfo", "e", 's' ); + +const idEventDef EV_DoneMoving( "", "", 'd' ); +const idEventDef EV_StartSoundPeriodic( "", "sddd" ); + +idLinkList rvSplineMover::splineMovers; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvSplineMover ) + EVENT( EV_PostSpawn, rvSplineMover::Event_PostSpawn ) + EVENT( EV_Activate, rvSplineMover::Event_Activate ) + EVENT( EV_SetSpline, rvSplineMover::Event_SetSpline ) + EVENT( EV_SetAccel, rvSplineMover::Event_SetAcceleration ) + EVENT( EV_SetDecel, rvSplineMover::Event_SetDeceleration ) + EVENT( EV_SetSpeed, rvSplineMover::Event_SetSpeed ) + EVENT( EV_GetSpeed, rvSplineMover::Event_GetSpeed ) + EVENT( EV_Thread_SetCallback, rvSplineMover::Event_SetCallBack ) + EVENT( EV_DoneMoving, rvSplineMover::Event_DoneMoving ) + EVENT( EV_GetSplineEntity, rvSplineMover::Event_GetSpline ) + EVENT( EV_GetCurrentTrackInfo, rvSplineMover::Event_GetCurrentTrackInfo ) + EVENT( EV_GetTrackInfo, rvSplineMover::Event_GetTrackInfo ) + EVENT( EV_TramCar_SetIdealSpeed, rvSplineMover::Event_SetIdealSpeed ) + EVENT( EV_TramCar_GetIdealSpeed, rvSplineMover::Event_GetIdealSpeed ) + EVENT( EV_TramCar_ApplySpeedScale, rvSplineMover::Event_ApplySpeedScale ) + EVENT( EV_OnAcceleration, rvSplineMover::Event_OnAcceleration ) + EVENT( EV_OnDeceleration, rvSplineMover::Event_OnDeceleration ) + EVENT( EV_OnCruising, rvSplineMover::Event_OnCruising ) + EVENT( EV_OnStartMoving, rvSplineMover::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvSplineMover::Event_OnStopMoving ) + EVENT( EV_StartSoundPeriodic, rvSplineMover::Event_StartSoundPeriodic ) + EVENT( EV_PartBlocked, rvSplineMover::Event_PartBlocked ) +END_CLASS + +/* +================ +rvSplineMover::Spawn +================ +*/ +void rvSplineMover::Spawn() { + waitThreadId = -1; + + physicsObj.SetSelf( this ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + physicsObj.SetContents( spawnArgs.GetBool("solid", "1") ? CONTENTS_SOLID : 0 ); + physicsObj.SetClipMask( spawnArgs.GetBool("solidClip") ? CONTENTS_SOLID : 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat("accel", "50") ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat("decel", "50") ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + AddSelfToGlobalList(); + + // This is needed so we get sorted correctly + BecomeInactive( TH_PHYSICS ); + BecomeActive( TH_PHYSICS ); + + PlayAnim( ANIMCHANNEL_ALL, "idle", 0 ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvSplineMover::~rvSplineMover +================ +*/ +rvSplineMover::~rvSplineMover() { + RemoveSelfFromGlobalList(); + SetPhysics( NULL ); +} + +/* +================ +rvSplineMover::SetSpeed +================ +*/ +void rvSplineMover::SetSpeed( float newSpeed ) { + physicsObj.SetSpeed( newSpeed ); +} + +/* +================ +rvSplineMover::GetSpeed +================ +*/ +float rvSplineMover::GetSpeed() const { + return physicsObj.GetSpeed(); +} + +/* +================ +rvSplineMover::SetIdealSpeed +================ +*/ +void rvSplineMover::SetIdealSpeed( float newIdealSpeed ) { + idealSpeed = newIdealSpeed; + SetSpeed( newIdealSpeed ); +} + +/* +================ +rvSplineMover::GetIdealSpeed +================ +*/ +float rvSplineMover::GetIdealSpeed() const { + return idealSpeed; +} + +/* +================ +rvSplineMover::SetSpline +================ +*/ +void rvSplineMover::SetSpline( idSplinePath* spline ) { + physicsObj.SetSplineEntity( spline ); + CheckSplineForOverrides( physicsObj.GetSpline(), &spline->spawnArgs ); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +const idSplinePath* rvSplineMover::GetSpline() const { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::GetSpline +================ +*/ +idSplinePath* rvSplineMover::GetSpline() { + return physicsObj.GetSplineEntity(); +} + +/* +================ +rvSplineMover::SetAcceleration +================ +*/ +void rvSplineMover::SetAcceleration( float accel ) { + physicsObj.SetLinearAcceleration( accel ); +} + +/* +================ +rvSplineMover::SetDeceleration +================ +*/ +void rvSplineMover::SetDeceleration( float decel ) { + physicsObj.SetLinearDeceleration( decel ); +} + +/* +================ +rvSplineMover::CheckSplineForOverrides +================ +*/ +void rvSplineMover::CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ) { + if( !spline || !args ) { + return; + } + + int endSpline = args->GetInt( "end_spline" ); + if( endSpline && Sign(endSpline) == Sign(GetSpeed()) ) { + physicsObj.SetLinearDeceleration( physicsObj.ComputeDecelFromSpline() ); + SetIdealSpeed( 0.0f ); + } +} + +/* +================ +rvSplineMover::RestoreFromOverrides +================ +*/ +void rvSplineMover::RestoreFromOverrides( const idDict* args ) { + if( !args ) { + return; + } + + physicsObj.SetLinearDeceleration( args->GetFloat("decel") ); +} + +/* +================ +rvSplineMover::PlayAnim +================ +*/ +int rvSplineMover::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return 0; + } + + GetAnimator()->PlayAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim( channel )->Length(); +} + +/* +================ +rvSplineMover::CycleAnim +================ +*/ +void rvSplineMover::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animIndex = GetAnimator()->GetAnim( animName ); + if( !animIndex ) { + return; + } + + GetAnimator()->CycleAnim( channel, animIndex, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvSplineMover::ClearChannel +================ +*/ +void rvSplineMover::ClearChannel( int channel, int clearFrame ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(clearFrame) ); +} + +/* +================ +rvSplineMover::PreBind +================ +*/ +void rvSplineMover::PreBind() { + idAnimatedEntity::PreBind(); + + SetSpline( NULL ); +} + +/* +================ +rvSplineMover::Save +================ +*/ +void rvSplineMover::Save( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteInt( waitThreadId ); +} + +/* +================ +rvSplineMover::Restore +================ +*/ +void rvSplineMover::Restore( idRestoreGame *savefile ) { + savefile->ReadStaticObject( physicsObj ); + RestorePhysics( &physicsObj ); + + savefile->ReadFloat( idealSpeed ); + savefile->ReadInt( waitThreadId ); + + AddSelfToGlobalList(); +} + +/* +================ +rvSplineMover::WriteToSnapshot +================ +*/ +void rvSplineMover::WriteToSnapshot( idBitMsgDelta &msg ) const { + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvSplineMover::ReadFromSnapshot +================ +*/ +void rvSplineMover::ReadFromSnapshot( const idBitMsgDelta &msg ) { + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvSplineMover::AddSelfToGlobalList +================ +*/ +void rvSplineMover::AddSelfToGlobalList() { + splineMoverNode.SetOwner( this ); + + if( !InGlobalList() ) { + splineMoverNode.AddToEnd( splineMovers ); + } +} + +/* +================ +rvSplineMover::RemoveSelfFromGlobalList +================ +*/ +void rvSplineMover::RemoveSelfFromGlobalList() { + splineMoverNode.Remove(); +} + +/* +================ +rvSplineMover::InGlobalList +================ +*/ +bool rvSplineMover::InGlobalList() const { + return splineMoverNode.InList(); +} + +/* +================ +rvSplineMover::WhosVisible +================ +*/ +bool rvSplineMover::WhosVisible( const idFrustum& frustum, idList& list ) const { + list.Clear(); + + if( !frustum.IsValid() ) { + return false; + } + + for( rvSplineMover* node = splineMovers.Next(); node; node = node->splineMoverNode.Next() ) { + if( node == this ) { + continue; + } + + if( frustum.IntersectsBounds(node->GetPhysics()->GetAbsBounds()) ) { + list.AddUnique( node ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvSplineMover::GetTrackInfo +================ +*/ +idStr rvSplineMover::GetTrackInfo( const idSplinePath* track ) const { + if( !track ) { + return idStr( "" ); + } + + idStr info( track->GetName() ); + return info.Mid( info.Last('_') - 1, 1 ); +} + +/* +================ +rvSplineMover::ConvertToMover +================ +*/ +rvSplineMover* rvSplineMover::ConvertToMover( idEntity* mover ) const { + return mover && mover->IsType(rvSplineMover::Type) ? static_cast(mover) : NULL; +} + +/* +================ +rvSplineMover::ConvertToSplinePath +================ +*/ +idSplinePath* rvSplineMover::ConvertToSplinePath( idEntity* spline ) const { + return (spline && spline->IsType(idSplinePath::GetClassType())) ? static_cast(spline) : NULL; +} + +/* +================ +rvSplineMover::PreDoneMoving +================ +*/ +void rvSplineMover::PreDoneMoving() { + if( waitThreadId >= 0 ) { + idThread::ObjectMoveDone( waitThreadId, this ); + waitThreadId = -1; + } + + RestoreFromOverrides( &spawnArgs ); +} + +/* +================ +rvSplineMover::PostDoneMoving +================ +*/ +void rvSplineMover::PostDoneMoving() { + CallScriptEvents( physicsObj.GetSplineEntity(), "call_doneMoving", this ); +} + +/* +============== +rvSplineMover::CallScriptEvents +============== +*/ +// FIXME: very similier code is in the spawner...if possible try and make one function for both to call +void rvSplineMover::CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ) { + if( !spline || !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spline->spawnArgs.MatchPrefix(prefixKey); kv; kv = spline->spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( spline, 0 ); + func.InsertEntity( parm, 1 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +================ +rvSplineMover::Event_PostSpawn +================ +*/ +void rvSplineMover::Event_PostSpawn() { + idEntityPtr target; + for( int ix = targets.Num() - 1; ix >= 0; --ix ) { + target = targets[ix]; + + if( target.IsValid() && target->IsType(idSplinePath::GetClassType()) ) { + SetSpline( static_cast(target.GetEntity()) ); + break; + } + } + + SetIdealSpeed( spawnArgs.GetBool("waitForTrigger") ? 0.0f : spawnArgs.GetFloat("speed", "50") ); +} + +/* +=============== +rvSplineMover::Event_PartBlocked +=============== +*/ +void rvSplineMover::Event_PartBlocked( idEntity *blockingEntity ) { + assert( blockingEntity ); + + float damage = spawnArgs.GetFloat( "damage" ); + if( damage > 0.0f ) { + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + } + if( g_debugMover.GetBool() ) { + gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.GetTime(), GetName(), blockingEntity->GetName() ); + } +} + +/* +================ +rvSplineMover::Event_SetSpline +================ +*/ +void rvSplineMover::Event_SetSpline( idEntity* spline ) { + SetSpline( ConvertToSplinePath(spline) ); +} + +/* +================ +rvSplineMover::Event_GetSpline +================ +*/ +void rvSplineMover::Event_GetSpline() { + idThread::ReturnEntity( GetSpline() ); +} + +/* +================ +rvSplineMover::Event_SetAcceleration +================ +*/ +void rvSplineMover::Event_SetAcceleration( float accel ) { + SetAcceleration( accel ); +} + +/* +================ +rvSplineMover::Event_SetDeceleration +================ +*/ +void rvSplineMover::Event_SetDeceleration( float decel ) { + SetDeceleration( decel ); +} + +/* +================ +rvSplineMover::Event_SetSpeed +================ +*/ +void rvSplineMover::Event_SetSpeed( float speed ) { + SetIdealSpeed( speed ); + SetSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetSpeed +================ +*/ +void rvSplineMover::Event_GetSpeed() { + idThread::ReturnFloat( GetSpeed() ); +} + +/* +================ +rvSplineMover::Event_SetIdealSpeed +================ +*/ +void rvSplineMover::Event_SetIdealSpeed( float speed ) { + SetIdealSpeed( speed ); +} + +/* +================ +rvSplineMover::Event_GetIdealSpeed +================ +*/ +void rvSplineMover::Event_GetIdealSpeed() { + idThread::ReturnFloat( GetIdealSpeed() ); +} + +/* +================ +rvSplineMover::Event_ApplySpeedScale +================ +*/ +void rvSplineMover::Event_ApplySpeedScale( float scale ) { + SetIdealSpeed( spawnArgs.GetFloat("speed", "50") * scale ); +} + +/* +================ +rvSplineMover::Event_SetCallBack +================ +*/ +void rvSplineMover::Event_SetCallBack() { + if( waitThreadId >= 0 ) { + idThread::ReturnInt( false ); + } + + waitThreadId = idThread::CurrentThreadNum(); + idThread::ReturnInt( true ); +} + +/* +================ +rvSplineMover::Event_DoneMoving +================ +*/ +void rvSplineMover::Event_DoneMoving() { + PreDoneMoving(); + PostDoneMoving(); + + idThread::ReturnInt( !physicsObj.HasValidSpline() ); +} + +/* +================ +rvSplineMover::Event_GetCurrentTrackInfo +================ +*/ +void rvSplineMover::Event_GetCurrentTrackInfo() { + Event_GetTrackInfo( physicsObj.GetSplineEntity() ); +} + +/* +================ +rvSplineMover::Event_GetTrackInfo +================ +*/ +void rvSplineMover::Event_GetTrackInfo( idEntity* track ) { + idThread::ReturnString( GetTrackInfo(ConvertToSplinePath(track)) ); +} + +/* +================ +rvSplineMover::Event_Activate +================ +*/ +void rvSplineMover::Event_Activate( idEntity* activator ) { + // This is for my special case in tram1b + + //if( physicsObj.StoppedMoving() ) { + // SetIdealSpeed( spawnArgs.GetFloat("speed", "50") ); + //} +} + +/* +================ +rvSplineMover::Event_OnAcceleration +================ +*/ +void rvSplineMover::Event_OnAcceleration() { + StartSound( "snd_accel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnDeceleration +================ +*/ +void rvSplineMover::Event_OnDeceleration() { + StartSound( "snd_decel", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvSplineMover::Event_OnCruising +================ +*/ +void rvSplineMover::Event_OnCruising() { + idVec2 range( spawnArgs.GetVec2("noisePeriodRange") * idMath::M_SEC2MS ); + if( !EventIsPosted(&EV_StartSoundPeriodic) && range.Length() > VECTOR_EPSILON ) { + ProcessEvent( &EV_StartSoundPeriodic, "snd_noise", (int)SND_CHANNEL_ANY, (int)range[0], (int)range[1] ); + } +} + +/* +================ +rvSplineMover::Event_OnStopMoving +================ +*/ +void rvSplineMover::Event_OnStopMoving() { + StopSound( SND_CHANNEL_ANY, false ); + CancelEvents( &EV_StartSoundPeriodic ); +} + +/* +================ +rvSplineMover::Event_OnStartMoving +================ +*/ +void rvSplineMover::Event_OnStartMoving() { +} + +/* +================ +rvSplineMover::Event_StartSoundPeriodic +================ +*/ +void rvSplineMover::Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ) { + CancelEvents( &EV_StartSoundPeriodic ); + + if( physicsObj.StoppedMoving() ) { + return; + } + + int length; + StartSound( sndKey, channel, 0, false, &length ); + + PostEventMS( &EV_StartSoundPeriodic, Max(rvRandom::irand(minDelay, maxDelay), length), sndKey, (int)channel, minDelay, maxDelay ); +} + + +const idEventDef EV_TramCar_RadiusDamage( "", "vs" ); +const idEventDef EV_TramCar_SetIdealTrack( "setIdealTrack", "s" ); +const idEventDef EV_TramCar_DriverSpeak( "driverSpeak", "s", 'e' ); +const idEventDef EV_TramCar_GetDriver( "getDriver", "", 'E' ); + +const idEventDef EV_TramCar_OpenDoors( "openDoors" ); +const idEventDef EV_TramCar_CloseDoors( "closeDoors" ); + +//======================================================= +// +// rvTramCar +// +//======================================================= +CLASS_DECLARATION( rvSplineMover, rvTramCar ) + EVENT( EV_TramCar_DriverSpeak, rvTramCar::Event_DriverSpeak ) + EVENT( EV_TramCar_GetDriver, rvTramCar::Event_GetDriver ) + EVENT( EV_Activate, rvTramCar::Event_Activate ) + EVENT( EV_TramCar_RadiusDamage, rvTramCar::Event_RadiusDamage ) + EVENT( EV_TramCar_SetIdealTrack, rvTramCar::Event_SetIdealTrack ) + EVENT( EV_OnStartMoving, rvTramCar::Event_OnStartMoving ) + EVENT( EV_OnStopMoving, rvTramCar::Event_OnStopMoving ) + EVENT( EV_TramCar_OpenDoors, rvTramCar::Event_OpenDoors ) + EVENT( EV_TramCar_CloseDoors, rvTramCar::Event_CloseDoors ) + EVENT( AI_SetHealth, rvTramCar::Event_SetHealth ) +END_CLASS + +/* +================ +rvTramCar::Spawn +================ +*/ +void rvTramCar::Spawn() { + RegisterStateThread( idealTrackStateThread, "IdealTrack" ); + RegisterStateThread( speedSoundEffectsStateThread, "SpeedSoundEffects" ); + + numTracksOnMap = gameLocal.world->spawnArgs.GetInt( "numTramCarTracks", "1" ); + + idStr track; + if ( numTracksOnMap == 1 ) { + track += ConvertToTrackLetter( numTracksOnMap - 1 ); + } else { + track = spawnArgs.RandomPrefix( "idealTrack", gameLocal.random, "0" ); + } + Event_SetIdealTrack( track.c_str() ); + idealTrackStateThread.SetState( GetIdealTrack() < 0 ? "RandomTrack" : "AssignedTrack" ); + + SpawnDriver( "def_driver" ); + SpawnWeapons( "def_weapon" ); + SpawnOccupants( "def_occupant" ); + SpawnDoors(); + + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; + const char* frustumKey = spawnArgs.GetString( "collisionFov" ); + if( frustumKey[0] ) { + sscanf( frustumKey, "%f %f %f %f", &dNear, &dFar, &dLeft, &dUp ); + collisionFov.SetSize( dNear, dFar, dLeft, dUp ); + } + + fl.takedamage = health > 0; + + BecomeActive( TH_THINK ); + + SetSpline( NULL ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +rvTramCar::~rvTramCar() { + SAFE_REMOVE( driver ); + occupants.RemoveContents( true ); + weapons.RemoveContents( true ); + + SAFE_REMOVE( leftDoor ); + SAFE_REMOVE( rightDoor ); +} + +/* +================ +rvTramCar::SpawnDriver +================ +*/ +void rvTramCar::SpawnDriver( const char* driverKey ) { + driver = (idAI*)SpawnPart( spawnArgs.GetString(driverKey), "def_occupant" ); +} + +/* +================ +rvTramCar::SpawnWeapons +================ +*/ +void rvTramCar::SpawnWeapons( const char* partKey ) { + idEntityPtr weapon; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + weapon = (rvVehicle*)SpawnPart( kv->GetValue().c_str(), partKey ); + weapon->IdleAnim( ANIMCHANNEL_LEGS, "idle", 0 ); + //if( weapon->GetAnimator()->GetAnim("toFire") && weapon->GetAnimator()->GetAnim("toIdle") ) { + weapons.AddUnique( weapon ); + //} + } +} + +/* +================ +rvTramCar::SpawnOccupants +================ +*/ +void rvTramCar::SpawnOccupants( const char* partKey ) { + idEntityPtr occupant; + + for( const idKeyValue* kv = spawnArgs.MatchPrefix(partKey); kv; kv = spawnArgs.MatchPrefix(partKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + occupant = (idAI*)SpawnPart( kv->GetValue().c_str(), partKey ); + occupants.AddUnique( occupant ); + } +} + +/* +================ +rvTramCar::SpawnPart +================ +*/ +idEntity* rvTramCar::SpawnPart( const char* partDefName, const char* subPartDefName ) { + idEntity* part = NULL; + idDict entityDef; + const idDict* info = gameLocal.FindEntityDefDict( partDefName, false ); + if( !info ) { + return NULL; + } + + const idDict* def = gameLocal.FindEntityDefDict( info->GetString(subPartDefName), false ); + if( !def ) { + return NULL; + } + + entityDef.Copy( *def ); + + entityDef.SetBool( "trigger", spawnArgs.GetBool("waitForTrigger") ); + entityDef.SetVector( "origin", GetPhysics()->GetOrigin() + info->GetVector("spawn_offset") * GetPhysics()->GetAxis() ); + entityDef.SetFloat( "angle", info->GetInt("spawn_facing_offset") + spawnArgs.GetFloat("angle") ); + entityDef.Set( "bind", GetName() ); + gameLocal.SpawnEntityDef( entityDef, &part ); + + return part; +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +void rvTramCar::SpawnDoors() { + leftDoor = SpawnDoor( "clipModel_doorLeft" ); + rightDoor = SpawnDoor( "clipModel_doorRight" ); +} + +/* +================ +rvTramCar::SpawnDoors +================ +*/ +idMover* rvTramCar::SpawnDoor( const char* key ) { + idDict args; + const idDict* dict = NULL; + idMover* outer = NULL; + idMover* inner = NULL; + + dict = gameLocal.FindEntityDefDict( spawnArgs.GetString(key), false ); + if( !dict ) { + return NULL; + } + + args.Set( "open_rotation", dict->GetString("open_rotation") ); + args.Set( "close_rotation", dict->GetString("close_rotation") ); + + args.Set( "open_dir", dict->GetString("open_dir") ); + args.Set( "close_dir", dict->GetString("close_dir") ); + + args.Set( "distExt", dict->GetString("distExt") ); + + args.SetVector( "origin", GetPhysics()->GetOrigin() + dict->GetVector("offset") * GetPhysics()->GetAxis() ); + args.SetMatrix( "rotation", GetPhysics()->GetAxis() ); + args.Set( "clipModel", dict->GetString("clipModel") ); + args.Set( "bind", GetName() ); + outer = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + assert( outer ); + + args.Set( "clipModel", dict->GetString("clipModelExt") ); + args.Set( "bind", outer->GetName() ); + inner = gameLocal.SpawnSafeEntityDef( "func_mover", &args ); + + return inner; +} + +/* +================ +rvTramCar::Think +================ +*/ +void rvTramCar::Think() { + RunPhysics(); + + MidThink(); + + Present(); +} + +/* +================ +rvTramCar::MidThink +================ +*/ +void rvTramCar::MidThink() { + if( !physicsObj.StoppedMoving() ) { + LookAround(); + TouchTriggers(); + speedSoundEffectsStateThread.Execute(); + } +} + +/* +================ +rvTramCar::RegisterStateThread +================ +*/ +void rvTramCar::RegisterStateThread( rvStateThread& stateThread, const char* name ) { + stateThread.SetName( name ); + stateThread.SetOwner( this ); +} + +/* +================ +rvTramCar::SetIdealTrack +================ +*/ +void rvTramCar::SetIdealTrack( int track ) { + idealTrack = track; +} + +/* +================ +rvTramCar::GetIdealTrack +================ +*/ +int rvTramCar::GetIdealTrack() const { + return idealTrack; +} + +/* +================ +rvTramCar::AddDamageEffect +================ +*/ +void rvTramCar::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + rvSplineMover::AddDamageEffect( collision, velocity, damageDefName, inflictor ); + + if( GetDamageScale() >= 0.5f && rvRandom::flrand() <= spawnArgs.GetFloat("damageEffectFreq", "0.25") ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + idVec3 v = (center - collision.endpos).ToNormal(); + float dot = v * collision.c.normal; + PlayEffect( (dot > 0.0f) ? "fx_damage_internal" : "fx_damage_external", collision.endpos, collision.c.normal.ToMat3(2) ); + } +} + +/* +================ +rvTramCar::Damage +================ +*/ +void rvTramCar::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + if( attacker && GetPhysics()->GetAbsBounds().Contains(attacker->GetPhysics()->GetAbsBounds()) ) { + return; + } + + if( attacker && g_debugVehicle.GetInteger() == 3 ) { + gameLocal.Printf( "Was damaged by %s. Current damage scale: %f\n", attacker->GetName(), GetDamageScale() ); + } + + rvSplineMover::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvTramCar::Killed +================ +*/ +void rvTramCar::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idVec3 center = GetPhysics()->GetAbsBounds().GetCenter(); + + fl.takedamage = false; + + StopSound( SND_CHANNEL_ANY, false ); + + //Calling gameLocal's version because we don't want to get bound and hidden + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_explode" ), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + + PostEventMS( &EV_TramCar_RadiusDamage, 0, center, spawnArgs.GetString("def_damage_explode") ); + + //TODO: think about giving rigid body physics and have it fall from track + Hide(); + PostEventMS( &EV_Remove, 0 ); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetDamageScale() const { + return 1.0f - GetHealthScale(); +} + +/* +================ +rvTramCar::GetDamageScale +================ +*/ +float rvTramCar::GetHealthScale() const { + float spawnHealth = spawnArgs.GetFloat( "health" ); + return ( idMath::Fabs(spawnHealth) <= VECTOR_EPSILON ) ? 1.0f : (health / spawnHealth); +} + +/* +================ +rvTramCar::Save +================ +*/ +void rvTramCar::Save( idSaveGame *savefile ) const { + savefile->WriteInt( idealTrack ); +// savefile->WriteString( idealTrackTag.c_str() ); // cnicholson: FIXME: This has a comment of HACK in the .h file... Not sure how to write a idStr yet + + savefile->WriteFrustum( collisionFov ); + + idealTrackStateThread.Save( savefile ); + speedSoundEffectsStateThread.Save( savefile ); + + driver.Save( savefile ); + + savefile->WriteInt( occupants.Num() ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Save( savefile ); + } + + savefile->WriteInt( weapons.Num() ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Save( savefile ); + } + + savefile->WriteInt( numTracksOnMap ); + + leftDoor.Save ( savefile ); + rightDoor.Save ( savefile ); +} + +/* +================ +rvTramCar::Restore +================ +*/ +void rvTramCar::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( idealTrack ); + + savefile->ReadFrustum( collisionFov ); + + idealTrackStateThread.Restore( savefile, this ); + speedSoundEffectsStateThread.Restore( savefile, this ); + + driver.Restore( savefile ); + + int num = 0; + savefile->ReadInt( num ); + occupants.SetNum( num ); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix].Restore( savefile ); + } + + savefile->ReadInt( num ); + weapons.SetNum( num ); + for( int ix = weapons.Num() - 1; ix >= 0; --ix ) { + weapons[ix].Restore( savefile ); + } + + savefile->ReadInt( numTracksOnMap ); + + leftDoor.Restore ( savefile ); + rightDoor.Restore ( savefile ); +} + +/* +================ +rvTramCar::WriteToSnapshot +================ +*/ +void rvTramCar::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar::ReadFromSnapshot +================ +*/ +void rvTramCar::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar::HeadTowardsIdealTrack +================ +*/ +void rvTramCar::HeadTowardsIdealTrack() { + idSplinePath* ideal = FindSplineToIdealTrack( GetSpline() ); + if( !ideal ) { + ideal = GetRandomSpline(GetSpline()); + } + + SetSpline( ideal ); +} + +/* +================ +rvTramCar::FindSplineToTrack +================ +*/ +enum { + LOOK_LEFT = -1, + LOOK_RIGHT = 1 +}; +idSplinePath* rvTramCar::FindSplineToTrack( idSplinePath* spline, const idStr& track ) const { + idSplinePath* target = NULL; + idEntity* ent = NULL; + idStr trackInfo; + idList list; + + if( !spline ) { + return NULL; + } + + for( int ix = SortSplineTargets(spline) - 1; ix >= 0; --ix ) { + ent = GetSplineTarget( spline, ix ); + target = static_cast( ent ); + assert( target->IsActive() ); + + trackInfo = GetTrackInfo( target ); + if( -1 >= trackInfo.Find(track) ) { + continue; + } + + // HACK: I hate switch statements + switch( ConvertToTrackNumber(trackInfo) - GetCurrentTrack() ) { + case LOOK_LEFT: { + if( !LookLeft(list) ) { + return target; + } + break; + } + + case LOOK_RIGHT: { + if( !LookRight(list) ) { + return target; + } + break; + } + + default: { + return target; + } + } + // HACK + } + + return NULL; +} + +/* +================ +rvTramCar::SortSplineTargets +================ +*/ +int rvTramCar::SortSplineTargets( idSplinePath* spline ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->SortTargets() : spline->SortBackwardsTargets(); +} + +/* +================ +rvTramCar::GetSplineTarget +================ +*/ +idEntity* rvTramCar::GetSplineTarget( idSplinePath* spline, int index ) const { + assert( spline ); + return (SignZero(GetSpeed()) >= 0) ? spline->targets[index].GetEntity() : spline->backwardPathTargets[index].GetEntity(); +} + +/* +================ +rvTramCar::FindSplineToIdealTrack +================ +*/ +idSplinePath* rvTramCar::FindSplineToIdealTrack( idSplinePath* spline ) const { + // HACK + int trackDelta = idMath::ClampInt( -1, 1, GetIdealTrack() - GetCurrentTrack() ); + idStr trackLetter( ConvertToTrackLetter(GetCurrentTrack() + trackDelta) ); + idSplinePath* s = NULL; + + if( idealTrackTag.Length() && !trackDelta ) {// On ideal track + s = FindSplineToTrack( spline, idealTrackTag + trackLetter ); + } + if( !s ) { + s = FindSplineToTrack( spline, trackLetter ); + } + return s; +} + +/* +================ +rvTramCar::GetRandomSpline +================ +*/ +idSplinePath* rvTramCar::GetRandomSpline( idSplinePath* spline ) const { + if( !spline ) { + return NULL; + } + + int numActiveTargets = SortSplineTargets( spline ); + if( !numActiveTargets ) { + return NULL; + } + + idEntity* target = GetSplineTarget( spline, rvRandom::irand(0, numActiveTargets - 1) ); + return (target && target->IsType(idSplinePath::GetClassType())) ? static_cast(target) : NULL; +} + +/* +================ +rvTramCar::GetCurrentTrack +================ +*/ +int rvTramCar::GetCurrentTrack() const { + return ConvertToTrackNumber( GetTrackInfo(GetSpline()) ); +} + +/* +================ +rvTramCar::UpdateChannel +================ +*/ +void rvTramCar::UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->ModifySound( channel, &parms ); + } +} + +/* +================ +rvTramCar::AttenuateTrackChannel +================ +*/ +void rvTramCar::AttenuateTrackChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.frequencyShift = attenuation;//idMath::MidPointLerp( 0.0f, 1.0f, 1.1f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY, parms ); +} + +/* +================ +rvTramCar::AttenuateTramCarChannel +================ +*/ +void rvTramCar::AttenuateTramCarChannel( float attenuation ) { + soundShaderParms_t parms = refSound.parms; + + parms.volume = (attenuation + 1.0f) * 0.5f; + parms.frequencyShift = Max( 0.4f, attenuation ); + + UpdateChannel( SND_CHANNEL_BODY2, parms ); +} + +/* +================ +rvTramCar::LookForward +================ +*/ +bool rvTramCar::LookForward( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( GetPhysics()->GetAxis() ); + + Look( collisionFov, list ); + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( !OnSameTrackAs(list[ix]) ) { + list.Remove( list[ix] ); + } + } + + return list.Num() > 0; +} + +/* +================ +rvTramCar::LookLeft +================ +*/ +bool rvTramCar::LookLeft( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRight +================ +*/ +bool rvTramCar::LookRight( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -60.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookLeftForTrackChange +================ +*/ +bool rvTramCar::LookLeftForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, 45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::LookRightForTrackChange +================ +*/ +bool rvTramCar::LookRightForTrackChange( idList& list ) const { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0f, -45.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + return Look( collisionFov, list ); +} + +/* +================ +rvTramCar::Look +================ +*/ +int rvSortByDist( const void* left, const void* right ) { + rvSplineMover* leftMover = *(rvSplineMover**)left; + rvSplineMover* rightMover = *(rvSplineMover**)right; + + return rightMover->spawnArgs.GetFloat("distAway") - leftMover->spawnArgs.GetFloat("distAway"); +} +bool rvTramCar::Look( const idFrustum& fov, idList& list ) const { + bool result = WhosVisible( fov, list ); + + if( g_debugVehicle.GetInteger() == 3 ) { + gameRenderWorld->DebugFrustum( colorRed, fov ); + } + + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + list[ix]->spawnArgs.SetFloat( "distAway", (list[ix]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).Length() ); + } + + qsort( list.Ptr(), list.Num(), list.TypeSize(), rvSortByDist ); + + // Do we need to get rid of these keyvalues? + + return result; +} + +/* +================ +rvTramCar::OnSameTrackAs +================ +*/ +bool rvTramCar::OnSameTrackAs( const rvSplineMover* tram ) const { + return tram && !GetTrackInfo(GetSpline()).Icmp( GetTrackInfo(tram->GetSpline()) ); +} + +/* +================ +rvTramCar::SameIdealTrackAs +================ +*/ +bool rvTramCar::SameIdealTrackAs( const rvSplineMover* tram ) const { + if( !tram ) { + return false; + } + return GetIdealTrack() == static_cast(tram)->GetIdealTrack(); +} + +/* +================ +rvTramCar::LookAround +================ +*/ +void rvTramCar::LookAround() { + idList moverList; + + if( !AdjustSpeed(moverList) ) { + SetSpeed( GetIdealSpeed() ); + } +} + +/* +================ +rvTramCar::AdjustSpeed +================ +*/ +bool rvTramCar::AdjustSpeed( idList& moverList ) { + if( LookForward(moverList) ) { + if( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugLine( colorRed, GetPhysics()->GetOrigin(), moverList[0]->GetPhysics()->GetOrigin() ); + } + + //Slow down and try to get onto other track if allowed + SetSpeed( moverList[0]->GetSpeed() * rvRandom::flrand(0.5f, 0.75f) ); + + // Is this safe if we are on a specified track + SetIdealTrack( (GetIdealTrack() + rvRandom::irand(1, 2)) % numTracksOnMap ); + return true; + } + + return false; +} + +/* +================ +rvTramCar::DriverSpeak +================ +*/ +idEntity* rvTramCar::DriverSpeak( const char* speechDecl, bool random ) { + if( !driver.IsValid() || driver->IsSpeaking() ) { + return NULL; + } + + driver->Speak( speechDecl, random ); + return driver; +} + +/* +================ +rvTramCar::OccupantSpeak +================ +*/ +idEntity* rvTramCar::OccupantSpeak( const char *speechDecl, bool random ) { + idEntityPtr occupant; + + if( !occupants.Num() ) { + return NULL; + } + + occupant = occupants[ rvRandom::irand(0, occupants.Num() - 1) ]; + if( !occupant.IsValid() || occupant->IsSpeaking() ) { + return NULL; + } + + occupant->Speak( speechDecl, random ); + return occupant; +} + +/* +================ +rvTramCar::PostDoneMoving +================ +*/ +void rvTramCar::PostDoneMoving() { + rvSplineMover::PostDoneMoving(); + + HeadTowardsIdealTrack(); +} + +/* +================ +rvTramCar::DeployRamp +================ +*/ +void rvTramCar::DeployRamp() { + OperateRamp( "open" ); +} + +/* +================ +rvTramCar::RetractRamp +================ +*/ +void rvTramCar::RetractRamp() { + OperateRamp( "close" ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation ) { + if( !operation || !operation[0] ) { + return; + } + + PlayAnim( ANIMCHANNEL_ALL, operation, 0 ); + + OperateRamp( operation, leftDoor ); + OperateRamp( operation, rightDoor ); +} + +/* +================ +rvTramCar::OperateRamp +================ +*/ +void rvTramCar::OperateRamp( const char* operation, idMover* door ) { + if( !operation || !operation[0] ) { + return; + } + + idAngles ang; + idVec3 vec; + if( !door ) { + return; + } + + if( !door->IsBound() ) { + return; + } + + ang = door->spawnArgs.GetAngles( va("%s%s", operation, "_rotation") ); + vec.Set( ang[0], ang[1], ang[2] ); + door->GetBindMaster()->ProcessEvent( &EV_RotateOnce, vec ); + + vec = door->spawnArgs.GetVector(va("%s%s", operation, "_dir")).ToNormal() * door->spawnArgs.GetFloat("distExt"); + door->PostEventSec( &EV_MoveAlongVector, 0.5f, vec ); +} + +/* +================ +rvTramCar::Event_OnStopMoving +================ +*/ +void rvTramCar::Event_OnStopMoving() { + rvSplineMover::Event_OnStopMoving(); + + speedSoundEffectsStateThread.SetState( "IdleSpeed" ); +} + +/* +================ +rvTramCar::Event_OnStartMoving +================ +*/ +void rvTramCar::Event_OnStartMoving() { + rvSplineMover::Event_OnStartMoving(); + + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); +} + +/* +================ +rvTramCar::Event_DriverSpeak +================ +*/ +void rvTramCar::Event_DriverSpeak( const char* voKey ) { + idThread::ReturnEntity( DriverSpeak(voKey) ); +} + +/* +================ +rvTramCar::Event_GetDriver +================ +*/ +void rvTramCar::Event_GetDriver() { + idThread::ReturnEntity( driver ); +} + +/* +================ +rvTramCar::Event_Activate +================ +*/ +void rvTramCar::Event_Activate( idEntity* activator ) { + rvSplineMover::Event_Activate( activator ); + + // If being activated by a spawner we need to attach to it + if( activator->IsType(rvSpawner::GetClassType()) ) { + static_cast(activator)->Attach( this ); + } else { + if( driver.IsValid() ) { + driver->ProcessEvent( &EV_Activate, activator ); + } + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->ProcessEvent( &EV_Activate, activator ); + } + } +} + +/* +================ +rvTramCar::Event_RadiusDamage +================ +*/ +void rvTramCar::Event_RadiusDamage( const idVec3& origin, const char* damageDefName ) { + gameLocal.RadiusDamage( origin, this, this, this, this, damageDefName ); +} + +/* +================ +rvTramCar::Event_SetIdealTrack +================ +*/ +void rvTramCar::Event_SetIdealTrack( const char* track ) { + idStr ideal = track; + + if( ideal.Length() > 1 ) { + idealTrackTag = ideal.Left( ideal.Length() - 1 ); + } + + idealTrackStateThread.SetState( "AssignedTrack" ); + SetIdealTrack( ConvertToTrackNumber(ideal.Right(1)) ); +} + +/* +================ +rvTramCar::Event_OpenDoors +================ +*/ +void rvTramCar::Event_OpenDoors() { + DeployRamp(); +} + +/* +================ +rvTramCar::Event_CloseDoors +================ +*/ +void rvTramCar::Event_CloseDoors() { + RetractRamp(); +} + +/* +================ +rvTramCar::Event_SetHealth +================ +*/ +void rvTramCar::Event_SetHealth ( float health ) { + this->health = health; +} + +CLASS_STATES_DECLARATION( rvTramCar ) + STATE( "IdleSpeed", rvTramCar::State_Idle ) + STATE( "NormalSpeed", rvTramCar::State_NormalSpeed ) + STATE( "ExcessiveSpeed", rvTramCar::State_ExcessiveSpeed ) + STATE( "RandomTrack", rvTramCar::State_RandomTrack ) + STATE( "AssignedTrack", rvTramCar::State_AssignedTrack ) +END_CLASS_STATES + +/* +================ +rvTramCar::State_Idle +================ +*/ +stateResult_t rvTramCar::State_Idle( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_idle_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_idle_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_NormalSpeed +================ +*/ +stateResult_t rvTramCar::State_NormalSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_normal_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_normal_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = 0.8f + 0.2f * ( GetSpeed() / GetNormalSpeed() ); + + if( speedScale >= 1.0f ) { + speedSoundEffectsStateThread.SetState( "ExcessiveSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_ExcessiveSpeed +================ +*/ +stateResult_t rvTramCar::State_ExcessiveSpeed( const stateParms_t& parms ) { + if( !parms.stage ) { + StartSound( "snd_speed_excessive_track", SND_CHANNEL_BODY, 0, false, NULL ); + StartSound( "snd_speed_excessive_tram", SND_CHANNEL_BODY2, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + float speedScale = GetSpeed() / GetNormalSpeed(); + + if( speedScale < 1.0f ) { + speedSoundEffectsStateThread.SetState( "NormalSpeed" ); + return SRESULT_DONE; + } + + AttenuateTrackChannel( speedScale ); + AttenuateTramCarChannel( speedScale ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_RandomTrack +================ +*/ +stateResult_t rvTramCar::State_RandomTrack( const stateParms_t& parms ) { + SetIdealTrack( rvRandom::irand(0, numTracksOnMap - 1) ); + return SRESULT_WAIT; +} + +/* +================ +rvTramCar::State_AssignedTrack +================ +*/ +stateResult_t rvTramCar::State_AssignedTrack( const stateParms_t& parms ) { + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +const idEventDef EV_TramCar_UseMountedGun( "useMountedGun", "e" ); +const idEventDef EV_TramCar_SetPlayerDamageEnt( "setPlayerDamageEnt", "f" ); + +CLASS_DECLARATION( rvTramCar, rvTramCar_Marine ) + EVENT( EV_TramCar_UseMountedGun, rvTramCar_Marine::Event_UseMountedGun ) + EVENT( EV_TramCar_SetPlayerDamageEnt, rvTramCar_Marine::Event_SetPlayerDamageEntity ) +END_CLASS + +/* +================ +rvTramCar_Marine::Spawn +================ +*/ +void rvTramCar_Marine::Spawn() { + RegisterStateThread( playerOccupationStateThread, "PlayerOccupation" ); + playerOccupationStateThread.SetState( "NotOccupied" ); + + RegisterStateThread( playerUsingMountedGunStateThread, "MountedGunInUse" ); + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); + lastHeal = 0; + healDelay = spawnArgs.GetInt( "heal_delay", "15" ); + healAmount = spawnArgs.GetInt( "heal_amount", "2" ); +} + +/* +================ +rvTramCar_Marine::MidThink +================ +*/ +void rvTramCar_Marine::MidThink() { + rvTramCar::MidThink(); + + if ( healDelay > 0 && lastHeal + healDelay < gameLocal.time && health < maxHealth ) { + health += healAmount; + + if ( health > maxHealth ) { + health = maxHealth; + } + } + + playerOccupationStateThread.Execute(); +} + +/* +================ +rvTramCar_Marine::ActivateTramHud +================ +*/ +void rvTramCar_Marine::ActivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "enterTram" ); +} + +/* +================ +rvTramCar_Marine::DeactivateTramHud +================ +*/ +void rvTramCar_Marine::DeactivateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->HandleNamedEvent( "leaveTram" ); +} + +/* +================ +rvTramCar_Marine::UpdateTramHud +================ +*/ +void rvTramCar_Marine::UpdateTramHud( idPlayer* player ) { + if( !player ) { + return; + } + + idUserInterface* hud = player->GetHud(); + if( !hud ) { + return; + } + + hud->SetStateFloat( "tram_healthpct", GetHealthScale() ); +} + +/* +============ +rvTramCar_Marine::Damage +============ +*/ +void rvTramCar_Marine::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { + rvTramCar::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if( attacker == gameLocal.GetLocalPlayer() ) { + return; + } + + if( fl.takedamage && GetDamageScale() < 0.35f ) { + return; + } + + if( rvRandom::flrand() > spawnArgs.GetFloat("damageWarningFreq", "0.2") ) { + return; + } + + DriverSpeak( "lipsync_damageWarning", true ); +} + +/* +================ +rvTramCar_Marine::Killed +================ +*/ +void rvTramCar_Marine::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + playerOccupationStateThread.Execute(); + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && EntityIsInside(player) ) { + player->SetDamageEntity( NULL );// This should fix the damage from tram car propogation issue + player->ExitVehicle( true );// This should fix the issue that was causing the player to get removed + } + + rvTramCar::Killed( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvTramCar_Marine::Save +================ +*/ +void rvTramCar_Marine::Save( idSaveGame *savefile ) const { + savefile->WriteInt( visibleEnemies.Num() ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Save( savefile ); + } + + playerOccupationStateThread.Save( savefile ); + playerUsingMountedGunStateThread.Save( savefile ); + + // no need to save this since it's set in the restore + //savefile->WriteInt( maxHealth ); // cnicholson: added unsaved var + savefile->WriteInt( lastHeal ); + savefile->WriteInt( healDelay ); + savefile->WriteInt( healAmount ); +} + +/* +================ +rvTramCar_Marine::Restore +================ +*/ +void rvTramCar_Marine::Restore( idRestoreGame *savefile ) { + int num = 0; + savefile->ReadInt( num ); + visibleEnemies.SetNum( num ); + for( int ix = visibleEnemies.Num() - 1; ix >= 0; --ix ) { + visibleEnemies[ix].Restore( savefile ); + } + + playerOccupationStateThread.Restore( savefile, this ); + playerUsingMountedGunStateThread.Restore( savefile, this ); + + savefile->ReadInt( lastHeal ); + savefile->ReadInt( healDelay ); + savefile->ReadInt( healAmount ); + + maxHealth = spawnArgs.GetInt( "health", "0" ); +} + +/* +================ +rvTramCar_Marine::LookAround +================ +*/ +void rvTramCar_Marine::LookAround() { + idList moverList; + + rvTramCar::LookAround(); + + if( !driver.IsValid() || driver->IsSpeaking() ) { + return; + } + + if( LookOverLeftShoulder(moverList)) { + DriverSpeak( "lipsync_warningLeft", true ); + driver->ScriptedAnim( "point_left", 0, false, true ); + } else if( LookOverRightShoulder(moverList)) { + DriverSpeak( "lipsync_warningRight", true ); + driver->ScriptedAnim( "point_right", 0, false, true ); + } +} + + +/* +================ +rvTramCar_Marine::LookOverLeftShoulder +================ +*/ +bool rvTramCar_Marine::LookOverLeftShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, -120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::LookOverRightShoulder +================ +*/ +bool rvTramCar_Marine::LookOverRightShoulder( idList& list ) { + collisionFov.SetOrigin( GetPhysics()->GetOrigin() ); + collisionFov.SetAxis( idAngles(0.0, 120.0f, 0.0f).ToMat3() * GetPhysics()->GetAxis() ); + + bool newOneFound = false; + Look( collisionFov, list ); + + // now determine if we've spotted a new enemy + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if ( !visibleEnemies.Find( list[ix] )) { + newOneFound = true; + visibleEnemies.AddUnique( list[ix] ); + } + } + + return newOneFound; +} + +/* +================ +rvTramCar_Marine::EntityIsInside +================ +*/ +bool rvTramCar_Marine::EntityIsInside( const idEntity* entity ) const { + assert( entity ); + return GetPhysics()->GetAbsBounds().ContainsPoint( entity->GetPhysics()->GetAbsBounds().GetCenter() ); +} + +/* +================ +rvTramCar_Marine::DeployRamp +================ +*/ +void rvTramCar_Marine::DeployRamp() { + rvTramCar::DeployRamp(); + + if( weapons.Num() ) { + weapons[0]->Unlock(); + weapons[0]->EjectAllDrivers( true ); + } +} + +/* +================ +rvTramCar_Marine::RetractRamp +================ +*/ +void rvTramCar_Marine::RetractRamp() { + rvTramCar::RetractRamp(); + + UseMountedGun( gameLocal.GetLocalPlayer() ); +} + +/* +================ +rvTramCar_Marine::UseMountedGun +================ +*/ +void rvTramCar_Marine::UseMountedGun( idPlayer* player ) { + if( playerOccupationStateThread.CurrentStateIs("Occupied") && EntityIsInside(player) && weapons.Num() ) { + player->EnterVehicle( weapons[0] ); + weapons[0]->Lock(); + } +} + +/* +================ +rvTramCar_Marine::Event_UseMountedGun +================ +*/ +void rvTramCar_Marine::Event_UseMountedGun( idEntity* ent ) { + if( !ent || !ent->IsType(idPlayer::GetClassType()) ) { + return; + } + + UseMountedGun( static_cast(ent) ); +} + +/* +================ +rvTramCar_Marine::Event_SetPlayerDamageEntity +================ +*/ +void rvTramCar_Marine::Event_SetPlayerDamageEntity(float f) { + + gameLocal.GetLocalPlayer()->SetDamageEntity( (f? this : NULL) ); +} + +CLASS_STATES_DECLARATION( rvTramCar_Marine ) + STATE( "Occupied", rvTramCar_Marine::State_Occupied ) + STATE( "NotOccupied", rvTramCar_Marine::State_NotOccupied ) + STATE( "UsingMountedGun", rvTramCar_Marine::State_UsingMountedGun ) + STATE( "NotUsingMountedGun",rvTramCar_Marine::State_NotUsingMountedGun ) +END_CLASS_STATES + + +/* +================ +rvTramCar_Marine::State_Occupied +================ +*/ +stateResult_t rvTramCar_Marine::State_Occupied( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_DisableWeapon ); + ActivateTramHud( player ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( !PlayerIsInside() ) { + playerOccupationStateThread.SetState( "NotOccupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( NULL ); + fl.takedamage = false; + return SRESULT_DONE_WAIT; + } + + playerUsingMountedGunStateThread.Execute(); + + UpdateTramHud( player ); + + return SRESULT_WAIT; +} + + + +/* +================ +rvTramCar_Marine::State_NotOccupied +================ +*/ +stateResult_t rvTramCar_Marine::State_NotOccupied( const stateParms_t& parms ) { + if( !parms.stage ) { + //player->ProcessEvent( &EV_Player_EnableWeapon ); + DeactivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( PlayerIsInside() ) { + playerOccupationStateThread.SetState( "Occupied" ); + //gameLocal.GetLocalPlayer()->SetDamageEntity( this ); + fl.takedamage = true; + return SRESULT_DONE_WAIT; + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_UsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_UsingMountedGun( const stateParms_t& parms ) { + if( !parms.stage ) { + ActivateTramHud( gameLocal.GetLocalPlayer() ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && !player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "NotUsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +/* +================ +rvTramCar_Marine::State_NotUsingMountedGun +================ +*/ +stateResult_t rvTramCar_Marine::State_NotUsingMountedGun( const stateParms_t& parms ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if( player && player->IsInVehicle() ) { + playerUsingMountedGunStateThread.SetState( "UsingMountedGun" ); + } + + return SRESULT_WAIT; +} + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +CLASS_DECLARATION( rvTramCar, rvTramCar_Strogg ) + EVENT( EV_PostSpawn, rvTramCar_Strogg::Event_PostSpawn ) +END_CLASS + +/* +================ +rvTramCar_Strogg::Spawn +================ +*/ +void rvTramCar_Strogg::Spawn() { + SetTarget( NULL ); + + RegisterStateThread( targetSearchStateThread, "targetSearch" ); + targetSearchStateThread.SetState( "LookingForTarget" ); +} + +/* +================ +rvTramCar_Strogg::SetTarget +================ +*/ +void rvTramCar_Strogg::SetTarget( idEntity* newTarget ) { + target = ConvertToMover( newTarget ); +} + +/* +================ +rvTramCar_Strogg::GetTarget +================ +*/ +const rvSplineMover* rvTramCar_Strogg::GetTarget() const { + return target.GetEntity(); +} + +/* +================ +rvTramCar_Strogg::Save +================ +*/ +void rvTramCar_Strogg::Save( idSaveGame *savefile ) const { + target.Save( savefile ); + targetSearchStateThread.Save( savefile ); +} + +/* +================ +rvTramCar_Strogg::Restore +================ +*/ +void rvTramCar_Strogg::Restore( idRestoreGame *savefile ) { + target.Restore( savefile ); + targetSearchStateThread.Restore( savefile, this ); +} + +/* +================ +rvTramCar_Strogg::WriteToSnapshot +================ +*/ +void rvTramCar_Strogg::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvTramCar_Strogg::ReadFromSnapshot +================ +*/ +void rvTramCar_Strogg::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvTramCar_Strogg::Event_PostSpawn +================ +*/ +void rvTramCar_Strogg::Event_PostSpawn() { + idEntity* enemy = gameLocal.FindEntity( spawnArgs.GetString("enemy") ); + + SetTarget( enemy ); + + occupants.RemoveNull(); + for( int ix = occupants.Num() - 1; ix >= 0; --ix ) { + occupants[ix]->SetEnemy( enemy ); + } + + rvTramCar::Event_PostSpawn(); +} + +/* +================ +rvTramCar_Strogg::TargetIsToLeft +================ +*/ +bool rvTramCar_Strogg::TargetIsToLeft() { + idList list; + + if( LookLeft(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::TargetIsToRight +================ +*/ +bool rvTramCar_Strogg::TargetIsToRight() { + idList list; + + if( LookRight(list) ) { + for( int ix = list.Num() - 1; ix >= 0; --ix ) { + if( list[ix] == target ) { + return true; + } + } + } + + return false; +} + +/* +================ +rvTramCar_Strogg::LookAround +================ +*/ +void rvTramCar_Strogg::LookAround() { + targetSearchStateThread.Execute(); +} + +CLASS_STATES_DECLARATION( rvTramCar_Strogg ) + STATE( "LookingForTarget", rvTramCar_Strogg::State_LookingForTarget ) + STATE( "TargetInSight", rvTramCar_Strogg::State_TargetInSight ) +END_CLASS_STATES + +/* +================ +rvTramCar_Strogg::State_LookingForTarget +================ +*/ +stateResult_t rvTramCar_Strogg::State_LookingForTarget( const stateParms_t& parms ) { + static const int MSEC_DELAY = 100; + idList list; + + if( !parms.stage ) {// Go back to random if we are supposed to be random + //idealTrackStateThread.SetState( "RandomTrack" ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + // Could optimise based on what track + if( TargetIsToLeft() || TargetIsToRight() ) { + targetSearchStateThread.SetState( "TargetInSight" ); + return SRESULT_DONE; + } + + SetSpeed( GetIdealSpeed() ); + return SRESULT_DELAY( MSEC_DELAY ); +} + +/* +================ +rvTramCar_Strogg::State_TargetInSight +================ +*/ +stateResult_t rvTramCar_Strogg::State_TargetInSight( const stateParms_t& parms ) { + static const int MSEC_DELAY = 3000; + + if( !target.IsValid() || (!TargetIsToLeft() && !TargetIsToRight()) ) { + targetSearchStateThread.SetState( "LookingForTarget" ); + return SRESULT_DONE; + } + + if( !parms.stage ) { + SetIdealTrack( GetCurrentTrack() ); + SetSpeed( target->GetSpeed() * 0.5f ); + idealTrackStateThread.SetState( "AssignedTrack" ); + StartSound( "snd_horn", SND_CHANNEL_ANY, 0, false, NULL ); + return SRESULT_STAGE( parms.stage + 1 ); + } + + idList list; + if( AdjustSpeed(list) ) { + return SRESULT_DELAY( MSEC_DELAY ); + } + + float dot = GetPhysics()->GetAxis()[0] * (target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToNormal(); + float deltaSpeed = target->GetIdealSpeed() * rvRandom::flrand(0.0f, 0.1f) * SignZero(dot); + SetSpeed( target->GetIdealSpeed() + deltaSpeed ); + + return SRESULT_DELAY( MSEC_DELAY ); +} diff --git a/src/mpgame/SplineMover.h b/src/mpgame/SplineMover.h new file mode 100644 index 0000000..653e8a3 --- /dev/null +++ b/src/mpgame/SplineMover.h @@ -0,0 +1,518 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RV_SPLINE_MOVER_H +#define __RV_SPLINE_MOVER_H + +extern const idEventDef EV_SetSpline; + +struct splinePState_t { + idVec3 origin; + idVec3 localOrigin; + idMat3 axis; + idMat3 localAxis; + + float speed; + float idealSpeed; + float dist; + + float acceleration; + float deceleration; + + bool ShouldAccelerate() const; + bool ShouldDecelerate() const; + + void ApplyAccelerationDelta( float timeStepSec ); + + void ApplyDecelerationDelta( float timeStepSec ); + + void UpdateDist( float timeStepSec ); + void Clear(); + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + splinePState_t& Assign( const splinePState_t* state ); + splinePState_t& operator=( const splinePState_t& state ); + splinePState_t& operator=( const splinePState_t* state ); +}; + +//======================================================= +// +// rvPhysics_Spline +// +//======================================================= +class rvPhysics_Spline : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Spline ); + + rvPhysics_Spline( void ); + virtual ~rvPhysics_Spline( void ); + + void Save( idSaveGame *savefile ) const; + void Event_PostRestore( void ); + void Restore( idRestoreGame *savefile ); + + void SetSpline( idCurve_Spline* spline ); + const idCurve_Spline* GetSpline() const { return spline; } + idCurve_Spline* GetSpline() { return spline; } + + void SetSplineEntity( idSplinePath* spline ); + const idSplinePath* GetSplineEntity() const { return splineEntity; } + idSplinePath* GetSplineEntity() { return splineEntity; } + + void SetLinearAcceleration( const float accel ); + void SetLinearDeceleration( const float decel ); + + void SetSpeed( float speed ); + float GetSpeed( void ) const; + + virtual bool StartingToMove( void ) const; + virtual bool StoppedMoving( void ) const; + + float ComputeDecelFromSpline( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + bool EvaluateSpline( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + bool EvaluateMaster( idVec3& newOrigin, idMat3& newAxis, const splinePState_t& previous ); + + void Activate( void ); + void Rest( void ); + + bool IsAtRest( void ) const; + bool IsAtEndOfSpline( void ) const; + bool IsAtBeginningOfSpline( void ) const; + + virtual bool IsPushable( void ) const; + + bool HasValidSpline( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + idVec3 & GetOrigin( int id = 0 ); + idMat3 & GetAxis( int id = 0 ); + + void SetMaster( idEntity *master, const bool orientated ); + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + virtual const trace_t* GetBlockingInfo( void ) const; + virtual idEntity* GetBlockingEntity( void ) const; + +public: + stateResult_t State_Accelerating( const stateParms_t& parms ); + stateResult_t State_Decelerating( const stateParms_t& parms ); + stateResult_t State_Cruising( const stateParms_t& parms ); + +protected: + const idVec3 & GetLocalOrigin( int id = 0 ) const; + const idMat3 & GetLocalAxis( int id = 0 ) const; + + idVec3 & GetLocalOrigin( int id = 0 ); + idMat3 & GetLocalAxis( int id = 0 ); + +protected: + splinePState_t current; + splinePState_t saved; + + float splineLength; + idCurve_Spline* spline; + idEntityPtr splineEntity; + + trace_t pushResults; + + idClipModel* clipModel; + + rvStateThread accelDecelStateThread; + + CLASS_STATES_PROTOTYPE( rvPhysics_Spline ); +}; + +extern const idEventDef EV_DoneMoving; + +//======================================================= +// +// rvSplineMover +// +//======================================================= +class rvSplineMover : public idAnimatedEntity { + CLASS_PROTOTYPE( rvSplineMover ); + +public: + void Spawn(); + virtual ~rvSplineMover(); + + virtual void SetSpeed( float newSpeed ); + virtual float GetSpeed() const; + virtual void SetIdealSpeed( float newIdealSpeed ); + virtual float GetIdealSpeed() const; + + virtual void SetSpline( idSplinePath* spline ); + virtual const idSplinePath* GetSpline() const; + virtual idSplinePath* GetSpline(); + + virtual void SetAcceleration( float accel ); + virtual void SetDeceleration( float decel ); + + virtual void CheckSplineForOverrides( const idCurve_Spline* spline, const idDict* args ); + virtual void RestoreFromOverrides( const idDict* args ); + + int PlayAnim( int channel, const char* animName, int blendFrames ); + void CycleAnim( int channel, const char* animName, int blendFrames ); + void ClearChannel( int channel, int clearFrame ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected:// TramCar utility functions + void AddSelfToGlobalList(); + void RemoveSelfFromGlobalList(); + bool InGlobalList() const; + bool WhosVisible( const idFrustum& frustum, idList& list ) const; + +protected: + virtual idStr GetTrackInfo( const idSplinePath* track ) const; + rvSplineMover* ConvertToMover( idEntity* mover ) const; + idSplinePath* ConvertToSplinePath( idEntity* spline ) const; + + void CallScriptEvents( const idSplinePath* spline, const char* prefixKey, idEntity* parm ); + virtual void PreBind(); + + virtual void PreDoneMoving(); + virtual void PostDoneMoving(); + +protected: + void Event_PostSpawn(); + + void Event_SetSpline( idEntity* spline ); + void Event_GetSpline(); + + void Event_SetAcceleration( float accel ); + void Event_SetDeceleration( float decel ); + + void Event_OnAcceleration(); + void Event_OnDeceleration(); + void Event_OnCruising(); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_SetSpeed( float speed ); + void Event_GetSpeed(); + void Event_SetIdealSpeed( float speed ); + void Event_GetIdealSpeed(); + void Event_ApplySpeedScale( float scale ); + + void Event_SetCallBack(); + void Event_DoneMoving(); + + void Event_GetCurrentTrackInfo(); + void Event_GetTrackInfo( idEntity* track ); + + void Event_Activate( idEntity* activator ); + + void Event_StartSoundPeriodic( const char* sndKey, const s_channelType channel, int minDelay, int maxDelay ); + void Event_PartBlocked( idEntity *blockingEntity ); + +protected: + rvPhysics_Spline physicsObj; + + float idealSpeed; + + int waitThreadId; + +private: + idLinkList splineMoverNode; + static idLinkList splineMovers; +}; + +//======================================================= +// +// rvTramCar +// +//======================================================= +class rvTramCar : public rvSplineMover { + CLASS_PROTOTYPE( rvTramCar ); + +public: + void Spawn(); + virtual ~rvTramCar(); + + virtual void Think(); + virtual void MidThink(); + + virtual float GetNormalSpeed() const { return spawnArgs.GetFloat("normalSpeed"); } + + virtual void SetIdealTrack( int track ); + virtual int GetIdealTrack() const; + + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual float GetDamageScale() const; + virtual float GetHealthScale() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_Idle( const stateParms_t& parms ); + stateResult_t State_NormalSpeed( const stateParms_t& parms ); + stateResult_t State_ExcessiveSpeed( const stateParms_t& parms ); + + stateResult_t State_RandomTrack( const stateParms_t& parms ); + stateResult_t State_AssignedTrack( const stateParms_t& parms ); + +protected: + void SpawnDriver( const char* driverKey ); + void SpawnWeapons( const char* partKey ); + void SpawnOccupants( const char* partKey ); + idEntity* SpawnPart( const char* partDefName, const char* subPartDefName ); + void SpawnDoors(); + idMover* SpawnDoor( const char* key ); + + int SortSplineTargets( idSplinePath* spline ) const; + idEntity* GetSplineTarget( idSplinePath* spline, int index ) const; + + void HeadTowardsIdealTrack(); + idSplinePath* FindSplineToTrack( idSplinePath* spline, const idStr& track ) const; + idSplinePath* FindSplineToIdealTrack( idSplinePath* spline ) const; + idSplinePath* GetRandomSpline( idSplinePath* spline ) const; + + char ConvertToTrackLetter( int trackNum ) const { return trackNum + 'A'; } + int ConvertToTrackNumber( char trackLetter ) const { return trackLetter - 'A'; } + int ConvertToTrackNumber( const idStr& trackInfo ) const { return ConvertToTrackNumber(idStr::ToUpper(trackInfo.Right(1)[0])); } + int GetCurrentTrack() const; + + virtual void UpdateChannel( const s_channelType channel, const soundShaderParms_t& parms ); + virtual void AttenuateTrackChannel( float attenuation ); + virtual void AttenuateTramCarChannel( float attenuation ); + + virtual void RegisterStateThread( rvStateThread& stateThread, const char* name ); + + bool LookForward( idList& list ) const; + bool LookLeft( idList& list ) const; + bool LookRight( idList& list ) const; + + bool LookLeftForTrackChange( idList& list ) const; + bool LookRightForTrackChange( idList& list ) const; + + bool Look( const idFrustum& fov, idList& list ) const; + + virtual void LookAround(); + virtual bool AdjustSpeed( idList& moverList ); + + virtual bool OnSameTrackAs( const rvSplineMover* tram ) const; + virtual bool SameIdealTrackAs( const rvSplineMover* tram ) const; + + virtual idEntity* DriverSpeak( const char *speechDecl, bool random = false ); + virtual idEntity* OccupantSpeak( const char *speechDecl, bool random = false ); + + virtual void PostDoneMoving(); + + virtual void DeployRamp(); + virtual void RetractRamp(); + void OperateRamp( const char* operation ); + void OperateRamp( const char* operation, idMover* door ); + +protected: + void Event_DriverSpeak( const char* voKey ); + void Event_GetDriver(); + void Event_Activate( idEntity* activator ); + void Event_RadiusDamage( const idVec3& origin, const char* damageDefName ); + void Event_SetIdealTrack( const char* track ); + + void Event_OnStopMoving(); + void Event_OnStartMoving(); + + void Event_OpenDoors(); + void Event_CloseDoors(); + + void Event_SetHealth( float health ); + +protected: + int idealTrack; + idStr idealTrackTag;// HACK + + mutable idFrustum collisionFov; + + rvStateThread idealTrackStateThread; + rvStateThread speedSoundEffectsStateThread; + + idEntityPtr driver; + idList< idEntityPtr > occupants; + idList< idEntityPtr > weapons; + + int numTracksOnMap; + + idEntityPtr leftDoor; + idEntityPtr rightDoor; + + CLASS_STATES_PROTOTYPE( rvTramCar ); +}; + +//======================================================= +// +// rvTramCar_Marine +// +//======================================================= +class rvTramCar_Marine : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Marine ); + +public: + void Spawn(); + + virtual void MidThink(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: + stateResult_t State_Occupied( const stateParms_t& parms ); + stateResult_t State_NotOccupied( const stateParms_t& parms ); + + stateResult_t State_UsingMountedGun( const stateParms_t& parms ); + stateResult_t State_NotUsingMountedGun( const stateParms_t& parms ); + +protected: + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual void LookAround(); + + bool LookOverLeftShoulder( idList& list ); + bool LookOverRightShoulder( idList& list ); + + bool EntityIsInside( const idEntity* entity ) const; + bool PlayerIsInside() const { return EntityIsInside(gameLocal.GetLocalPlayer()); } + + void ActivateTramHud( idPlayer* player ); + void DeactivateTramHud( idPlayer* player ); + void UpdateTramHud( idPlayer* player ); + + virtual void DeployRamp(); + virtual void RetractRamp(); + + void UseMountedGun( idPlayer* player ); + + void Event_UseMountedGun( idEntity* ent ); + void Event_SetPlayerDamageEntity(float f); + +protected: + idList< idEntityPtr > visibleEnemies; + + rvStateThread playerOccupationStateThread; + rvStateThread playerUsingMountedGunStateThread; + + int maxHealth; + int lastHeal; + int healDelay; + int healAmount; + + CLASS_STATES_PROTOTYPE( rvTramCar_Marine ); +}; + +//======================================================= +// +// rvTramCar_Strogg +// +//======================================================= +class rvTramCar_Strogg : public rvTramCar { + CLASS_PROTOTYPE( rvTramCar_Strogg ); + +public: + void Spawn(); + + virtual void SetTarget( idEntity* newTarget ); + virtual const rvSplineMover* GetTarget() const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +public: + stateResult_t State_LookingForTarget( const stateParms_t& parms ); + stateResult_t State_TargetInSight( const stateParms_t& parms ); + +protected: + bool TargetIsToLeft(); + bool TargetIsToRight(); + + virtual void LookAround(); + +protected: + void Event_PostSpawn(); + +protected: + idEntityPtr target; + + rvStateThread targetSearchStateThread; + + CLASS_STATES_PROTOTYPE( rvTramCar_Strogg ); +}; + +#endif diff --git a/src/mpgame/Target.cpp b/src/mpgame/Target.cpp new file mode 100644 index 0000000..78cf35a --- /dev/null +++ b/src/mpgame/Target.cpp @@ -0,0 +1,2345 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Invisible entities that affect other entities or the world when activated. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "Projectile.h" + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idTarget ) +END_CLASS + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Remove ) + EVENT( EV_Activate, idTarget_Remove::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Remove::Event_Activate +================ +*/ +void idTarget_Remove::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Show ) + EVENT( EV_Activate, idTarget_Show::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Show::Event_Activate +================ +*/ +void idTarget_Show::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Show(); + } + } + + // delete our self when done + PostEventMS( &EV_Remove, 0 ); +} + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Damage ) + EVENT( EV_Activate, idTarget_Damage::Event_Activate ) +END_CLASS + +/* +================ +idTarget_Damage::Event_Activate +================ +*/ +void idTarget_Damage::Event_Activate( idEntity *activator ) { + int i; + const char *damage; + idEntity * ent; + + damage = spawnArgs.GetString( "def_damage", "damage_generic" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->Damage( this, this, vec3_origin, damage, 1.0f, INVALID_JOINT ); + } + } +} + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SessionCommand ) + EVENT( EV_Activate, idTarget_SessionCommand::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SessionCommand::Event_Activate +================ +*/ +void idTarget_SessionCommand::Event_Activate( idEntity *activator ) { + gameLocal.sessionCommand = spawnArgs.GetString( "command" ); +} + + +/* +=============================================================================== + +idTarget_EndLevel + +Just a modified form of idTarget_SessionCommand +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EndLevel ) + EVENT( EV_Activate, idTarget_EndLevel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EndLevel::Event_Activate +================ +*/ +void idTarget_EndLevel::Event_Activate( idEntity *activator ) { + idStr nextMap; + +#ifdef ID_DEMO_BUILD + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endofDemo"; + return; + } +#else + if ( spawnArgs.GetBool( "endOfGame" ) ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + gameLocal.sessionCommand = "endOfGame"; + return; + } +#endif + if ( !spawnArgs.GetString( "nextMap", "", nextMap ) ) { + gameLocal.Printf( "idTarget_SessionCommand::Event_Activate: no nextMap key\n" ); + return; + } + + if ( spawnArgs.GetInt( "devmap", "0" ) ) { + gameLocal.sessionCommand = "devmap "; // only for special demos + } else { + gameLocal.sessionCommand = "map "; + } + + gameLocal.sessionCommand += nextMap; + +// RAVEN BEGIN +// jscott: additional info for multiple maps + const char* entityFilter; + if( spawnArgs.GetString( "entityFilter", "", &entityFilter ) && *entityFilter ) { + gameLocal.sessionCommand += " "; + gameLocal.sessionCommand += entityFilter; + } +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_WaitForButton ) + EVENT( EV_Activate, idTarget_WaitForButton::Event_Activate ) +END_CLASS + +/* +================ +idTarget_WaitForButton::Event_Activate +================ +*/ +void idTarget_WaitForButton::Event_Activate( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTarget_WaitForButton::Think +================ +*/ +void idTarget_WaitForButton::Think( void ) { + idPlayer *player; + + if ( thinkFlags & TH_THINK ) { + player = gameLocal.GetLocalPlayer(); + if ( player && ( !player->oldButtons & BUTTON_ATTACK ) && ( player->usercmd.buttons & BUTTON_ATTACK ) ) { + player->usercmd.buttons &= ~BUTTON_ATTACK; + BecomeInactive( TH_THINK ); + ActivateTargets( player ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetGlobalShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetGlobalShaderTime ) +EVENT( EV_Activate, idTarget_SetGlobalShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetGlobalShaderTime::Event_Activate +================ +*/ +void idTarget_SetGlobalShaderTime::Event_Activate( idEntity *activator ) { + int parm = spawnArgs.GetInt( "globalParm" ); + float time = -MS2SEC( gameLocal.time ); + if ( parm >= 0 && parm < MAX_GLOBAL_SHADER_PARMS ) { + gameLocal.globalShaderParms[parm] = time; + } +} + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderParm ) + EVENT( EV_Activate, idTarget_SetShaderParm::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderParm::Event_Activate +================ +*/ +void idTarget_SetShaderParm::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float value; + idVec3 color; + int parmnum; + + // set the color on the targets + if ( spawnArgs.GetVector( "_color", "1 1 1", color ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color[ 0 ], color[ 1 ], color[ 2 ] ); + } + } + } + + // set any shader parms on the targets + for( parmnum = 0; parmnum < MAX_ENTITY_SHADER_PARMS; parmnum++ ) { + if ( spawnArgs.GetFloat( va( "shaderParm%d", parmnum ), "0", value ) ) { + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( parmnum, value ); + } + } + if (spawnArgs.GetBool("toggle") && (value == 0 || value == 1)) { + int val = value; + val ^= 1; + value = val; + spawnArgs.SetFloat(va("shaderParm%d", parmnum), value); + } + } + } +} + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetShaderTime ) + EVENT( EV_Activate, idTarget_SetShaderTime::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetShaderTime::Event_Activate +================ +*/ +void idTarget_SetShaderTime::Event_Activate( idEntity *activator ) { + int i; + idEntity * ent; + float time; + + time = -MS2SEC( gameLocal.time ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetShaderParm( SHADERPARM_TIMEOFFSET, time ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + static_cast(ent)->SetLightParm( SHADERPARM_TIMEOFFSET, time ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_FadeEntity ) + EVENT( EV_Activate, idTarget_FadeEntity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_FadeEntity::idTarget_FadeEntity +================ +*/ +idTarget_FadeEntity::idTarget_FadeEntity( void ) { + fadeFrom.Zero(); + fadeStart = 0; + fadeEnd = 0; +} + +/* +================ +idTarget_FadeEntity::Save +================ +*/ +void idTarget_FadeEntity::Save( idSaveGame *savefile ) const { + savefile->WriteVec4( fadeFrom ); + savefile->WriteInt( fadeStart ); + savefile->WriteInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Restore +================ +*/ +void idTarget_FadeEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadVec4( fadeFrom ); + savefile->ReadInt( fadeStart ); + savefile->ReadInt( fadeEnd ); +} + +/* +================ +idTarget_FadeEntity::Event_Activate +================ +*/ +void idTarget_FadeEntity::Event_Activate( idEntity *activator ) { + idEntity *ent; + int i; + + if ( !targets.Num() ) { + return; + } + + // always allow during cinematics + cinematic = true; + BecomeActive( TH_THINK ); + + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->GetColor( fadeFrom ); + break; + } + } + + fadeStart = gameLocal.time; + fadeEnd = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "fadetime" ) ); +} + +/* +================ +idTarget_FadeEntity::Think +================ +*/ +void idTarget_FadeEntity::Think( void ) { + int i; + idEntity *ent; + idVec4 color; + idVec4 fadeTo; + float frac; + + if ( thinkFlags & TH_THINK ) { + GetColor( fadeTo ); + if ( gameLocal.time >= fadeEnd ) { + color = fadeTo; + BecomeInactive( TH_THINK ); + } else { + frac = ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ); + color.Lerp( fadeFrom, fadeTo, frac ); + } + + // set the color on the targets + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetColor( color ); + } + } + } else { + BecomeInactive( TH_ALL ); + } +} + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeIn ) + EVENT( EV_Activate, idTarget_LightFadeIn::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeIn::Event_Activate +================ +*/ +void idTarget_LightFadeIn::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeIn( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LightFadeOut ) + EVENT( EV_Activate, idTarget_LightFadeOut::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LightFadeOut::Event_Activate +================ +*/ +void idTarget_LightFadeOut::Event_Activate( idEntity *activator ) { + idEntity *ent; + idLight *light; + int i; + float time; + + if ( !targets.Num() ) { + return; + } + + time = spawnArgs.GetFloat( "fadetime" ); + ent = this; + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( !ent ) { + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + light = static_cast( ent ); + light->FadeOut( time ); + } else { + gameLocal.Printf( "'%s' targets non-light '%s'", name.c_str(), ent->GetName() ); + } + } +} + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_Give ) + EVENT( EV_Activate, idTarget_Give::Event_Activate ) +// RAVEN BEGIN +// abahr: + EVENT( EV_PostSpawn, idTarget_Give::Event_PostSpawn ) +// RAVEN END +END_CLASS + +/* +================ +idTarget_Give::Spawn +================ +*/ +void idTarget_Give::Spawn( void ) { +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + if ( spawnArgs.GetBool( "onSpawn" ) ) { + PostEventMS( &EV_PostSpawn, 50 ); + +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +#endif + } +// RAVEN END + + // precache decls + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + declManager->FindType( DECL_ENTITYDEF, kv->GetValue(), false, false ); + kv = spawnArgs.MatchPrefix( "item", kv ); + } +} + +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs +/* +================ +idTarget_Give::Event_PostSpawn +================ +*/ +void idTarget_Give::Event_PostSpawn() { + ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); +} +// RAVEN END + +/* +================ +idTarget_Give::Event_Activate +================ +*/ +void idTarget_Give::Event_Activate( idEntity *activator ) { + + if ( spawnArgs.GetBool( "development" ) && developer.GetInteger() == 0 ) { + return; + } + + static int giveNum = 0; + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + const idKeyValue *kv = spawnArgs.MatchPrefix( "item", NULL ); + while ( kv ) { + const idDict *dict = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( dict ) { + idDict d2; + d2.Copy( *dict ); + d2.Set( "name", va( "givenitem_%i", giveNum++ ) ); + idEntity *ent = NULL; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( gameLocal.SpawnEntityDef( d2, &ent ) && ent && ent->IsType( idItem::GetClassType() ) ) { +// RAVEN END + idItem *item = static_cast(ent); + item->GiveToPlayer( gameLocal.GetLocalPlayer() ); + item->PostEventMS ( &EV_Remove, 0 ); + + // rules are that if we are given a weapon by a character, we are supposed to switch to it regardless of + // whether auto-switch is on or not. + if ( !gameLocal.isMultiplayer && !player->GetUserInfo()->GetBool( "ui_autoSwitch" ) && !spawnArgs.GetBool( "onSpawn" )) { + const idKeyValue *kv = ent->spawnArgs.FindKey( "weaponclass" ); + if ( kv ) { + // does player already have this weapon selected? + if ( player->weapon && idStr::Icmp(player->weapon->GetClassname(), kv->GetValue())) { + kv = ent->spawnArgs.FindKey( "inv_weapon" ); + if ( kv ) { + // nope, so attempt to switch to this weapon + player->SelectWeapon(kv->GetValue()); + } + } + } + } + } + } + kv = spawnArgs.MatchPrefix( "item", kv ); + } + } +} + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveEmail ) +EVENT( EV_Activate, idTarget_GiveEmail::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Spawn +================ +*/ +void idTarget_GiveEmail::Spawn( void ) { +} + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveEmail::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using email +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + const idDeclPDA *pda = player->GetPDA(); + if ( pda ) { + player->GiveEmail( spawnArgs.GetString( "email" ) ); + } else { + player->ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_PDANeeded" ), true ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetModel ) + EVENT( EV_Activate, idTarget_SetModel::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetModel::Spawn +================ +*/ +void idTarget_SetModel::Spawn( void ) { + const char *model; + + model = spawnArgs.GetString( "newmodel" ); + if ( declManager->FindType( DECL_MODELDEF, model, false ) == NULL ) { + // precache the render model + renderModelManager->FindModel( model ); + // precache .cm files only + collisionModelManager->PreCacheModel( gameLocal.GetMapName(), model ); + } +} + +/* +================ +idTarget_SetModel::Event_Activate +================ +*/ +void idTarget_SetModel::Event_Activate( idEntity *activator ) { + for( int i = 0; i < targets.Num(); i++ ) { + idEntity *ent = targets[ i ].GetEntity(); + if ( ent ) { + ent->SetModel( spawnArgs.GetString( "newmodel" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +const idEventDef EV_RestoreInfluence( "" ); +const idEventDef EV_GatherEntities( "" ); +const idEventDef EV_Flash( "", "fd" ); +const idEventDef EV_ClearFlash( "", "f" ); + +CLASS_DECLARATION( idTarget, idTarget_SetInfluence ) + EVENT( EV_Activate, idTarget_SetInfluence::Event_Activate ) + EVENT( EV_RestoreInfluence, idTarget_SetInfluence::Event_RestoreInfluence ) + EVENT( EV_GatherEntities, idTarget_SetInfluence::Event_GatherEntities ) + EVENT( EV_Flash, idTarget_SetInfluence::Event_Flash ) + EVENT( EV_ClearFlash, idTarget_SetInfluence::Event_ClearFlash ) +END_CLASS + +/* +================ +idTarget_SetInfluence::idTarget_SetInfluence +================ +*/ +idTarget_SetInfluence::idTarget_SetInfluence( void ) { + flashIn = 0.0f; + flashOut = 0.0f; + delay = 0.0f; + switchToCamera = NULL; + soundFaded = false; + restoreOnTrigger = false; +} + +/* +================ +idTarget_SetInfluence::Save +================ +*/ +void idTarget_SetInfluence::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( lightList.Num() ); + for( i = 0; i < lightList.Num(); i++ ) { + savefile->WriteInt( lightList[ i ] ); + } + + savefile->WriteInt( guiList.Num() ); + for( i = 0; i < guiList.Num(); i++ ) { + savefile->WriteInt( guiList[ i ] ); + } + + savefile->WriteInt( soundList.Num() ); + for( i = 0; i < soundList.Num(); i++ ) { + savefile->WriteInt( soundList[ i ] ); + } + + savefile->WriteInt( genericList.Num() ); + for( i = 0; i < genericList.Num(); i++ ) { + savefile->WriteInt( genericList[ i ] ); + } + + savefile->WriteFloat( flashIn ); + savefile->WriteFloat( flashOut ); + + savefile->WriteFloat( delay ); + + savefile->WriteString( flashInSound ); + savefile->WriteString( flashOutSound ); + + savefile->WriteObject( switchToCamera ); + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); + + savefile->WriteBool( soundFaded ); + savefile->WriteBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Restore +================ +*/ +void idTarget_SetInfluence::Restore( idRestoreGame *savefile ) { + int i, num; + int itemNum; + float set; + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + lightList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + guiList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + soundList.Append( itemNum ); + } + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadInt( itemNum ); + genericList.Append( itemNum ); + } + + savefile->ReadFloat( flashIn ); + savefile->ReadFloat( flashOut ); + + savefile->ReadFloat( delay ); + + savefile->ReadString( flashInSound ); + savefile->ReadString( flashOutSound ); + + savefile->ReadObject( reinterpret_cast( switchToCamera ) ); + + savefile->ReadFloat( set ); + fovSetting.SetStartTime( set ); + savefile->ReadFloat( set ); + fovSetting.SetDuration( set ); + savefile->ReadFloat( set ); + fovSetting.SetStartValue( set ); + savefile->ReadFloat( set ); + fovSetting.SetEndValue( set ); + + savefile->ReadBool( soundFaded ); + savefile->ReadBool( restoreOnTrigger ); +} + +/* +================ +idTarget_SetInfluence::Spawn +================ +*/ +void idTarget_SetInfluence::Spawn() { + PostEventMS( &EV_GatherEntities, 0 ); + flashIn = spawnArgs.GetFloat( "flashIn", "0" ); + flashOut = spawnArgs.GetFloat( "flashOut", "0" ); + flashInSound = spawnArgs.GetString( "snd_flashin" ); + flashOutSound = spawnArgs.GetString( "snd_flashout" ); + delay = spawnArgs.GetFloat( "delay" ); + soundFaded = false; + restoreOnTrigger = false; + + // always allow during cinematics + cinematic = true; +} + +/* +================ +idTarget_SetInfluence::Event_Flash +================ +*/ +void idTarget_SetInfluence::Event_Flash( float flash, int out ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( idVec4( 1, 1, 1, 1 ), flash ); + const idSoundShader *shader = NULL; + if ( !out && flashInSound.Length() ){ + shader = declManager->FindSound( flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } else if ( out && ( flashOutSound.Length() || flashInSound.Length() ) ) { + shader = declManager->FindSound( flashOutSound.Length() ? flashOutSound : flashInSound ); + player->StartSoundShader( shader, SND_CHANNEL_VOICE, 0, false, NULL ); + } + PostEventSec( &EV_ClearFlash, flash, flash ); +} + + +/* +================ +idTarget_SetInfluence::Event_ClearFlash +================ +*/ +void idTarget_SetInfluence::Event_ClearFlash( float flash ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->playerView.Fade( vec4_zero , flash ); +} +/* +================ +idTarget_SetInfluence::Event_GatherEntities +================ +*/ +void idTarget_SetInfluence::Event_GatherEntities() { + int i, listedEntities; + idEntity *entityList[ MAX_GENTITIES ]; + + bool lights = spawnArgs.GetBool( "effect_lights" ); + bool sounds = spawnArgs.GetBool( "effect_sounds" ); + bool guis = spawnArgs.GetBool( "effect_guis" ); + bool models = spawnArgs.GetBool( "effect_models" ); + bool vision = spawnArgs.GetBool( "effect_vision" ); + bool targetsOnly = spawnArgs.GetBool( "targetsOnly" ); + + lightList.Clear(); + guiList.Clear(); + soundList.Clear(); + + if ( spawnArgs.GetBool( "effect_all" ) ) { + lights = sounds = guis = models = vision = true; + } + + if ( targetsOnly ) { + listedEntities = targets.Num(); + for ( i = 0; i < listedEntities; i++ ) { + entityList[i] = targets[i].GetEntity(); + } + } else { + float radius = spawnArgs.GetFloat( "radius" ); + listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES ); + } + + for( i = 0; i < listedEntities; i++ ) { + idEntity *ent = entityList[ i ]; + if ( ent ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( lights && ent->IsType( idLight::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + lightList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( sounds && ent->IsType( idSound::GetClassType() ) && ent->spawnArgs.FindKey( "snd_demonic" ) ) { +// RAVEN END + soundList.Append( ent->entityNumber ); + continue; + } + if ( guis && ent->GetRenderEntity() && ent->GetRenderEntity()->gui[ 0 ] && ent->spawnArgs.FindKey( "gui_demonic" ) ) { + guiList.Append( ent->entityNumber ); + continue; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idStaticEntity::GetClassType() ) && ent->spawnArgs.FindKey( "color_demonic" ) ) { +// RAVEN END + genericList.Append( ent->entityNumber ); + continue; + } + } + } + idStr temp; + temp = spawnArgs.GetString( "switchToView" ); + switchToCamera = ( temp.Length() ) ? gameLocal.FindEntity( temp ) : NULL; + +} + +/* +================ +idTarget_SetInfluence::Event_Activate +================ +*/ +void idTarget_SetInfluence::Event_Activate( idEntity *activator ) { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + const char *parm; + const char *skin; + bool update; + idVec3 color; + idVec4 colorTo; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + + if ( spawnArgs.GetBool( "triggerActivate" ) ) { + if ( restoreOnTrigger ) { + ProcessEvent( &EV_RestoreInfluence ); + restoreOnTrigger = false; + return; + } + restoreOnTrigger = true; + } + + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + + if ( delay > 0.0f ) { + PostEventSec( &EV_Activate, delay, activator ); + delay = 0.0f; + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + return; + } else if ( fadeTime && !soundFaded ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, -40.0f, fadeTime ); + soundFaded = true; + } + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( activator ); + } + + if ( flashIn ) { + PostEventSec( &EV_Flash, 0.0f, flashIn, 0 ); + } + + parm = spawnArgs.GetString( "snd_influence" ); + if ( parm && *parm ) { + PostEventSec( &EV_StartSoundShader, flashIn, parm, SND_CHANNEL_ANY ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventSec( &EV_Activate, flashIn + 0.05f, this ); + } + +// RAVEN BEGIN +// mekberg: allow for initial fov and both fovs. + int fov = spawnArgs.GetInt( "fov" ); + int fovInitial = spawnArgs.GetInt( "fov_initial" ); + if ( fov && fovInitial) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, fov ); + BecomeActive( TH_THINK ); + } else if ( fov ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), player->DefaultFov(), fov ); + BecomeActive( TH_THINK ); + } else if ( fovInitial ) { + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "fovTime" ) ), fovInitial, player->DefaultFov() ); + BecomeActive( TH_THINK ); + } +// RAVEN END + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + color = generic->spawnArgs.GetVector( "color_demonic" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + parm = light->spawnArgs.GetString( "mat_demonic" ); + if ( parm && *parm ) { + light->SetShader( parm ); + } + + color = light->spawnArgs.GetVector( "_color" ); + color = light->spawnArgs.GetVector( "color_demonic", color.ToString() ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + parm = sound->spawnArgs.GetString( "snd_demonic" ); + if ( parm && *parm ) { + if ( sound->spawnArgs.GetBool( "overlayDemonic" ) ) { + sound->StartSound( "snd_demonic", SND_CHANNEL_DEMONIC, 0, false, NULL ); + } else { + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( parm ); + } + } + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || ent->GetRenderEntity() == NULL ) { + continue; + } + update = false; + for ( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] && ent->spawnArgs.FindKey( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ) ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui_demonic" : va( "gui_demonic%d", j+1 ) ), true ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + + } + + player->SetInfluenceLevel( spawnArgs.GetInt( "influenceLevel" ) ); + + int snapAngle = spawnArgs.GetInt( "snapAngle" ); + if ( snapAngle ) { + idAngles ang( 0, snapAngle, 0 ); + player->SetViewAngles( ang ); + player->SetAngles( ang ); + } + + if ( spawnArgs.GetBool( "effect_vision" ) ) { + parm = spawnArgs.GetString( "mtrVision" ); + skin = spawnArgs.GetString( "skinVision" ); + player->SetInfluenceView( parm, skin, spawnArgs.GetInt( "visionRadius" ), this ); + } + + parm = spawnArgs.GetString( "mtrWorld" ); + if ( parm && *parm ) { + gameLocal.SetGlobalMaterial( declManager->FindMaterial( parm ) ); + } + + if ( !restoreOnTrigger ) { + PostEventMS( &EV_RestoreInfluence, SEC2MS( spawnArgs.GetFloat( "time" ) ) ); + } +} + +/* +================ +idTarget_SetInfluence::Think +================ +*/ +void idTarget_SetInfluence::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + if ( !spawnArgs.GetBool( "leaveFOV" ) ) { + player->SetInfluenceFov( 0 ); + } + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +================ +idTarget_SetInfluence::Event_RestoreInfluence +================ +*/ +void idTarget_SetInfluence::Event_RestoreInfluence() { + int i, j; + idEntity *ent; + idLight *light; + idSound *sound; + idStaticEntity *generic; + bool update; + idVec3 color; + idVec4 colorTo; + + if ( flashOut ) { + PostEventSec( &EV_Flash, 0.0f, flashOut, 1 ); + } + + if ( switchToCamera ) { + switchToCamera->PostEventMS( &EV_Activate, 0.0f, this ); + } + + for ( i = 0; i < genericList.Num(); i++ ) { + ent = gameLocal.entities[genericList[i]]; + if ( ent == NULL ) { + continue; + } + generic = static_cast( ent ); + colorTo.Set( 1.0f, 1.0f, 1.0f, 1.0f ); + generic->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < lightList.Num(); i++ ) { + ent = gameLocal.entities[lightList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + light = static_cast(ent); + if ( !light->spawnArgs.GetBool( "leave_demonic_mat" ) ) { + const char *texture = light->spawnArgs.GetString( "texture", "lights/squarelight1" ); + light->SetShader( texture ); + } + color = light->spawnArgs.GetVector( "_color" ); + colorTo.Set( color.x, color.y, color.z, 1.0f ); + light->Fade( colorTo, spawnArgs.GetFloat( "fade_time", "0.25" ) ); + } + + for ( i = 0; i < soundList.Num(); i++ ) { + ent = gameLocal.entities[soundList[i]]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent == NULL || !ent->IsType( idSound::GetClassType() ) ) { +// RAVEN END + continue; + } + sound = static_cast(ent); + sound->StopSound( SND_CHANNEL_ANY, false ); + sound->SetSound( sound->spawnArgs.GetString( "s_shader" ) ); + } + + for ( i = 0; i < guiList.Num(); i++ ) { + ent = gameLocal.entities[guiList[i]]; + if ( ent == NULL || GetRenderEntity() == NULL ) { + continue; + } + update = false; + for( j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + ent->GetRenderEntity()->gui[ j ] = uiManager->FindGui( ent->spawnArgs.GetString( j == 0 ? "gui" : va( "gui%d", j+1 ) ) ); + update = true; + } + } + if ( update ) { + ent->UpdateVisuals(); + ent->Present(); + } + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceLevel( 0 ); + player->SetInfluenceView( NULL, NULL, 0.0f, NULL ); + player->SetInfluenceFov( 0 ); + gameLocal.SetGlobalMaterial( NULL ); + float fadeTime = spawnArgs.GetFloat( "fadeWorldSounds" ); + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, 0.0f, fadeTime / 2.0f ); + } + +} + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetKeyVal ) + EVENT( EV_Activate, idTarget_SetKeyVal::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetKeyVal::Event_Activate +================ +*/ +void idTarget_SetKeyVal::Event_Activate( idEntity *activator ) { + int i; + idStr key, val; + idEntity *ent; + const idKeyValue *kv; + int n; + + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent ) { + kv = spawnArgs.MatchPrefix("keyval"); + while ( kv ) { + n = kv->GetValue().Find( ";" ); + if ( n > 0 ) { + key = kv->GetValue().Left( n ); + val = kv->GetValue().Right( kv->GetValue().Length() - n - 1 ); + ent->spawnArgs.Set( key, val ); + for ( int j = 0; j < MAX_RENDERENTITY_GUI; j++ ) { + if ( ent->GetRenderEntity()->gui[ j ] ) { + if ( idStr::Icmpn( key, "gui_", 4 ) == 0 ) { + ent->GetRenderEntity()->gui[ j ]->SetStateString( key, val ); + ent->GetRenderEntity()->gui[ j ]->StateChanged( gameLocal.time ); + } + } + } + } + kv = spawnArgs.MatchPrefix( "keyval", kv ); + } + ent->UpdateChangeableSpawnArgs( NULL ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetFov ) + EVENT( EV_Activate, idTarget_SetFov::Event_Activate ) +END_CLASS + + +/* +================ +idTarget_SetFov::Save +================ +*/ +void idTarget_SetFov::Save( idSaveGame *savefile ) const { + + savefile->WriteFloat( fovSetting.GetStartTime() ); + savefile->WriteFloat( fovSetting.GetDuration() ); + savefile->WriteFloat( fovSetting.GetStartValue() ); + savefile->WriteFloat( fovSetting.GetEndValue() ); +} + +/* +================ +idTarget_SetFov::Restore +================ +*/ +void idTarget_SetFov::Restore( idRestoreGame *savefile ) { + float setting; + + savefile->ReadFloat( setting ); + fovSetting.SetStartTime( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetDuration( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetStartValue( setting ); + savefile->ReadFloat( setting ); + fovSetting.SetEndValue( setting ); + + fovSetting.GetCurrentValue( gameLocal.time ); +} + +/* +================ +idTarget_SetFov::Event_Activate +================ +*/ +void idTarget_SetFov::Event_Activate( idEntity *activator ) { + // always allow during cinematics + cinematic = true; + + idPlayer *player = gameLocal.GetLocalPlayer(); + fovSetting.Init( gameLocal.time, SEC2MS( spawnArgs.GetFloat( "time" ) ), player ? player->DefaultFov() : g_fov.GetFloat(), spawnArgs.GetFloat( "fov" ) ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTarget_SetFov::Think +================ +*/ +void idTarget_SetFov::Think( void ) { + if ( thinkFlags & TH_THINK ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + player->SetInfluenceFov( fovSetting.GetCurrentValue( gameLocal.time ) ); + if ( fovSetting.IsDone( gameLocal.time ) ) { + player->SetInfluenceFov( 0.0f ); + BecomeInactive( TH_THINK ); + } + } else { + BecomeInactive( TH_ALL ); + } +} + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_SetPrimaryObjective ) + EVENT( EV_Activate, idTarget_SetPrimaryObjective::Event_Activate ) +END_CLASS + +/* +================ +idTarget_SetPrimaryObjective::Event_Activate +================ +*/ +void idTarget_SetPrimaryObjective::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && player->objectiveSystem ) { + player->objectiveSystem->SetStateString( "missionobjective", spawnArgs.GetString( "text", common->GetLocalizedString( "#str_104253" ) ) ); + } +} + +// RAVEN BEGIN +// bdube: added database entry trigger +// twhitaker: removed database entry trigger +/* +=============================================================================== + +rvTarget_AddDatabaseEntry + +=============================================================================== +*/ +/* +CLASS_DECLARATION( idTarget, rvTarget_AddDatabaseEntry ) + EVENT( EV_Activate, rvTarget_AddDatabaseEntry::Event_Activate ) +END_CLASS +*/ +/* +================ +rvTarget_AddDatabaseEntry::Event_Activate +================ +*/ +/*void rvTarget_AddDatabaseEntry::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveDatabaseEntry ( gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_db", "" ) ) ); + } +} +*/ +// jshepard: secret area trigger +/* +=============================================================================== + +rvTarget_SecretArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_SecretArea ) + EVENT( EV_Activate, rvTarget_SecretArea::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_SecretArea::Event_Activate +================ +*/ +void rvTarget_SecretArea::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->DiscoverSecretArea( spawnArgs.GetString ( "description" )); + } +} + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +const idEventDef EV_SetShieldPercent( "setShieldPercent", "f" ); +const idEventDef EV_SetBossMaxHealth( "setMaxBossHealth", "f" ); +const idEventDef EV_AllowShieldBar( "allowShieldBar", "f" ); +const idEventDef EV_AllowShieldWarningBar( "allowShieldWarnBar", "f" ); + + +CLASS_DECLARATION( idTarget, rvTarget_BossBattle ) + EVENT( EV_Activate, rvTarget_BossBattle::Event_Activate ) + EVENT( EV_SetShieldPercent, rvTarget_BossBattle::Event_SetShieldPercent ) + EVENT( EV_SetBossMaxHealth, rvTarget_BossBattle::Event_SetBossMaxHealth ) + EVENT( EV_AllowShieldBar, rvTarget_BossBattle::Event_AllowShieldBar ) + EVENT( EV_AllowShieldWarningBar, rvTarget_BossBattle::Event_AllowShieldWarningBar ) +END_CLASS + +/* +================ +rvTarget_BossBattle::Event_Activate +================ +*/ +void rvTarget_BossBattle::Event_Activate( idEntity *activator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + idEntity* enemy = gameLocal.FindEntity ( spawnArgs.GetString ( "target" ) ); + if ( player && enemy ) { + player->StartBossBattle ( enemy ); + } + + StartSound ( "snd_activate", SND_CHANNEL_ANY, 0, false, NULL ); +} + + +/* +================ +rvTarget_BossBattle::Event_AllowShieldBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldBar" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldBar" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_AllowShieldWarningBar +================ +*/ +void rvTarget_BossBattle::Event_AllowShieldWarningBar( float activate ) +{ + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + if( activate ) { + hud->HandleNamedEvent( "showBossShieldWarn" ); + hud->HandleNamedEvent( "updateBossShield" ); + } else { + hud->HandleNamedEvent( "hideBossShieldWarn" ); + } + } +} + +/* +================ +rvTarget_BossBattle::Event_SetShieldPercent +================ +*/ +void rvTarget_BossBattle::Event_SetShieldPercent( float percent ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } +} + +/* +================ +rvTarget_BossBattle::Event_SetMaxBossHealth +================ +*/ +void rvTarget_BossBattle::Event_SetBossMaxHealth( float f ) { + + idUserInterface *hud = gameLocal.GetLocalPlayer()->GetHud(); + if ( hud ) + { + + hud->SetStateInt ( "boss_maxhealth",f ); + } +} + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +const idEventDef EV_LaunchProjectile( "launchProjectile", "e" ); +CLASS_DECLARATION( idTarget, rvTarget_LaunchProjectile ) + EVENT( EV_Activate, rvTarget_LaunchProjectile::Event_Activate ) + EVENT( EV_LaunchProjectile, rvTarget_LaunchProjectile::Event_LaunchProjectile ) +END_CLASS + +/* +================ +rvTarget_LaunchProjectile::Spawn +================ +*/ +void rvTarget_LaunchProjectile::Spawn( void ) { + if ( spawnArgs.GetBool( "start_on" ) ) { + PostEventMS( &EV_Activate, 0, this ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_LaunchProjectile +================ +*/ +void rvTarget_LaunchProjectile::Event_LaunchProjectile( idEntity *activator ) { + const idDict* projectileDict; + idEntity* ent; + + projectileDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ), false ); + if ( !projectileDict ) { + gameLocal.Error ( "Could not find entityDef '%s' for launch projectile target '%s'", spawnArgs.GetString ( "def_projectile" ), GetName() ); + } + + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + gameLocal.Error( "Could not spawn entityDef '%s'", projectileDict->GetString( "classname" ) ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idProjectile", ent->GetClassname() ); + } + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + idProjectile* proj = static_cast(ent); + + proj->Create( this, GetPhysics()->GetOrigin(), dir, this ); + proj->Launch( GetPhysics()->GetOrigin(), dir, GetPhysics()->GetLinearVelocity(), 0.0f, 1.0f ); + + + if ( targets.Num() && proj->IsType ( idGuidedProjectile::GetClassType ( ) ) ) { + static_cast(proj)->GuideTo ( targets[0] ); + } + + if ( spawnArgs.GetFloat( "loop_interval" ) ) + { + PostEventSec( &EV_Activate, spawnArgs.GetFloat( "loop_interval" ), activator ); + } +} + +/* +================ +rvTarget_LaunchProjectile::Event_Activate +================ +*/ +void rvTarget_LaunchProjectile::Event_Activate( idEntity *activator ) { + + idVec3 dir; + if ( targets.Num ( ) && targets[0] ) { + dir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); + dir.Normalize(); + } else { + dir = GetPhysics()->GetAxis()[0]; + } + + if ( spawnArgs.GetString( "fx_launch", NULL ) ) + { + gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_launch" ), GetPhysics()->GetOrigin(), dir.ToMat3() ); + } + + if ( spawnArgs.GetFloat( "delay" ) ) + { + PostEventSec( &EV_LaunchProjectile, spawnArgs.GetFloat( "delay" ), activator ); + return; + } + ProcessEvent( &EV_LaunchProjectile, activator ); +} + +/* +=============================================================================== + +rvTarget_ExitArea + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_ExitAreaAlert ) + EVENT( EV_Activate, rvTarget_ExitAreaAlert::Event_Activate ) +END_CLASS + +void rvTarget_ExitAreaAlert::Event_Activate( idEntity *activator ) { + gameLocal.UpdateEndLevel(); +} + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ + +int CompareAmmoData( const void* ammo1, const void* ammo2) { + if ((( ammodata_t *)ammo2)->percentFull > (( ammodata_t *)ammo1)->percentFull ) { + return -1; + } else { + return 1; + } +} + +CLASS_DECLARATION( idTarget, rvTarget_AmmoStash ) + EVENT( EV_Activate, rvTarget_AmmoStash::Event_Activate ) +END_CLASS + +void rvTarget_AmmoStash::Event_Activate( idEntity *activator ) { + + const idKeyValue* kv; + idPlayer* player; + int typeCount; + idEntity* entAmmo; + idItem* item; + idDict args; + + + player = gameLocal.GetLocalPlayer(); + + //set up the array + memset( AmmoArray, 0, sizeof( ammodata_t) * AMMO_ARRAY_SIZE); + for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + AmmoArray[ t ].percentFull = 2.0f; + } + + // we only check for certain types of ammo. + kv = spawnArgs.MatchPrefix ( "def_ammo", NULL ); + if ( kv ) { + kv->GetValue(); + for ( typeCount = 0; typeCount < AMMO_ARRAY_SIZE && kv; kv = spawnArgs.MatchPrefix ( "def_ammo", kv ) ) { + + AmmoArray[ typeCount ].ammoName = kv->GetValue(); + AmmoArray[ typeCount ].ammoIndex = player->inventory.AmmoIndexForAmmoClass( AmmoArray[ typeCount ].ammoName.c_str() ); + + //check and see how much ammo this weapon can hold... + AmmoArray[ typeCount ].ammoMax = player->inventory.MaxAmmoForAmmoClass( player, AmmoArray[ typeCount ].ammoName.c_str() ); + + //and the current amount + AmmoArray[ typeCount ].ammoCount = player->inventory.HasAmmo( AmmoArray[ typeCount ].ammoIndex, 1); + + if( AmmoArray[ typeCount ].ammoMax > 0) { + AmmoArray[ typeCount ].percentFull = float( AmmoArray[ typeCount ].ammoCount / AmmoArray[ typeCount ].ammoMax ); + } else { + AmmoArray[ typeCount ].percentFull = 2.0f; + } + + //increment + typeCount++; + } + + } else { + gameLocal.Warning("Bad ammo data on rvTarget_AmmoStash '%s'", this->GetName()); + return; + } + + //TEMP: print out the ammo counts + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + //sort the types of ammo by need. The most-needed ammo will be placed. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + + int i; + //run through our targets until we have no more targets. + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + //drop the most needed ammo at ent's location. + args.Set ( "origin", ent->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", ent->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", AmmoArray[ 0 ].ammoName ); + + gameLocal.SpawnEntityDef ( args, &entAmmo ); + + //now assume that the item was picked up. + item = static_cast< idItem* >(entAmmo); + kv = entAmmo->spawnArgs.MatchPrefix ( "inv_ammo", NULL ); + + if( kv ) { + + //add the ammo as if the player picked it up + AmmoArray[ 0 ].ammoCount += atoi(kv->GetValue().c_str()); + AmmoArray[ 0 ].percentFull = (float)AmmoArray[ 0 ].ammoCount / (float)AmmoArray[ 0 ].ammoMax; + + //resort the ammo. + qsort( ( void * )AmmoArray, AMMO_ARRAY_SIZE ,sizeof( ammodata_t), CompareAmmoData ); + + + //TEMP: print out the ammo counts + //gameLocal.Printf("--------------------------------\nresorting\n--------------------------------\n" ); + //for( int t= 0; t < AMMO_ARRAY_SIZE; t++) { + // gameLocal.Printf("Ammo %d is %s and %f full\n", t, AmmoArray[ t ].ammoName.c_str(), AmmoArray[ t].percentFull); + //} + } else { + gameLocal.Warning("Bad ammo data on target_ammostash '%s'", this->GetName()); + return; + } + } + +} + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_TetherAI ) + EVENT( EV_Activate, rvTarget_TetherAI::Event_Activate ) +END_CLASS + +/* +================ +rvTarget_TetherAI::Event_Activate +================ +*/ +void rvTarget_TetherAI::Event_Activate( idEntity *activator ) { + int i; + if ( activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + } + } +} +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, rvTarget_Nailable ) +END_CLASS +void rvTarget_Nailable::Spawn() { + + //InitDefaultPhysics( origin, axis ); + +// int contents = GetPhysics()->GetContents(); +// contents &= CONTENTS_BODY; +// contents &= CONTENTS_SOLID; + GetPhysics()->SetContents( MASK_SHOT_BOUNDINGBOX ); + +} + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LockDoor ) + EVENT( EV_Activate, idTarget_LockDoor::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LockDoor::Event_Activate +================ +*/ +void idTarget_LockDoor::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + int lock; + + lock = spawnArgs.GetInt( "locked", "1" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent && ent->IsType( idDoor::GetClassType() ) ) { +// RAVEN END + if ( static_cast( ent )->IsLocked() ) { + static_cast( ent )->Lock( 0 ); + } else { + static_cast( ent )->Lock( lock ); + } + } + } +} + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_CallObjectFunction ) + EVENT( EV_Activate, idTarget_CallObjectFunction::Event_Activate ) +END_CLASS + +/* +================ +idTarget_CallObjectFunction::Event_Activate +================ +*/ +void idTarget_CallObjectFunction::Event_Activate( idEntity *activator ) { + int i; + idEntity *ent; + const function_t *func; + const char *funcName; + idThread *thread; + + funcName = spawnArgs.GetString( "call" ); + for( i = 0; i < targets.Num(); i++ ) { + ent = targets[ i ].GetEntity(); + if ( ent && ent->scriptObject.HasObject() ) { + func = ent->scriptObject.GetFunction( funcName ); + if ( !func ) { + gameLocal.Error( "Function '%s' not found on entity '%s' for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( func->type->NumParameters() != 1 ) { + gameLocal.Error( "Function '%s' on entity '%s' has the wrong number of parameters for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + if ( !ent->scriptObject.GetTypeDef()->Inherits( func->type->GetParmType( 0 ) ) ) { + gameLocal.Error( "Function '%s' on entity '%s' is the wrong type for function call from '%s'", funcName, ent->name.c_str(), name.c_str() ); + } + // create a thread and call the function +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + thread = new idThread(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + thread->CallFunction( ent, func, true ); + thread->Start(); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableLevelWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableLevelWeapons ) + EVENT( EV_Activate, idTarget_EnableLevelWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableLevelWeapons::Event_Activate +================ +*/ +void idTarget_EnableLevelWeapons::Event_Activate( idEntity *activator ) { + int i; + const char *weap; + + gameLocal.world->spawnArgs.SetBool( "no_Weapons", spawnArgs.GetBool( "disable" ) ); + + if ( spawnArgs.GetBool( "disable" ) ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_DisableWeapon ); + } + } + } else { + weap = spawnArgs.GetString( "weapon" ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->ProcessEvent( &EV_Player_EnableWeapon ); + if ( weap && weap[ 0 ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, weap ); + } + } + } + } +} + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +const idEventDef EV_TipOff( "" ); +extern const idEventDef EV_GetPlayerPos( "" ); + +CLASS_DECLARATION( idTarget, idTarget_Tip ) + EVENT( EV_Activate, idTarget_Tip::Event_Activate ) + EVENT( EV_TipOff, idTarget_Tip::Event_TipOff ) + EVENT( EV_GetPlayerPos, idTarget_Tip::Event_GetPlayerPos ) +END_CLASS + + +/* +================ +idTarget_Tip::idTarget_Tip +================ +*/ +idTarget_Tip::idTarget_Tip( void ) { + playerPos.Zero(); +} + +/* +================ +idTarget_Tip::Spawn +================ +*/ +void idTarget_Tip::Spawn( void ) { +} + +/* +================ +idTarget_Tip::Save +================ +*/ +void idTarget_Tip::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Restore +================ +*/ +void idTarget_Tip::Restore( idRestoreGame *savefile ) { + savefile->ReadVec3( playerPos ); +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_GetPlayerPos( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + playerPos = player->GetPhysics()->GetOrigin(); + PostEventMS( &EV_TipOff, 100 ); + } +} + +/* +================ +idTarget_Tip::Event_Activate +================ +*/ +void idTarget_Tip::Event_Activate( idEntity *activator ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( player->IsTipVisible() ) { + PostEventSec( &EV_Activate, 5.1f, activator ); + return; + } + player->ShowTip( spawnArgs.GetString( "text_title" ), spawnArgs.GetString( "text_tip" ), false ); + PostEventMS( &EV_GetPlayerPos, 2000 ); + } +} + +/* +================ +idTarget_Tip::Event_TipOff +================ +*/ +void idTarget_Tip::Event_TipOff( void ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + idVec3 v = player->GetPhysics()->GetOrigin() - playerPos; + if ( v.Length() > 96.0f ) { + player->HideTip(); + } else { + PostEventMS( &EV_TipOff, 100 ); + } + } +} + + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_GiveSecurity ) +EVENT( EV_Activate, idTarget_GiveSecurity::Event_Activate ) +END_CLASS + +/* +================ +idTarget_GiveEmail::Event_Activate +================ +*/ +void idTarget_GiveSecurity::Event_Activate( idEntity *activator ) { +// RAVEN BEGIN +// bdube: not using security +/* + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player ) { + player->GiveSecurity( spawnArgs.GetString( "text_security" ) ); + } +*/ +// RAVEN END +} + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_RemoveWeapons ) +EVENT( EV_Activate, idTarget_RemoveWeapons::Event_Activate ) +END_CLASS + +/* +================ +idTarget_RemoveWeapons::Event_Activate +================ +*/ +void idTarget_RemoveWeapons::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + const idKeyValue *kv = spawnArgs.MatchPrefix( "weapon", NULL ); + while ( kv ) { + player->RemoveWeapon( kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "weapon", kv ); + } +// RAVEN BEGIN +// bdube: default to initial weapon + player->SelectWeapon( 0, true ); +// RAVEN END + } + } +} + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_LevelTrigger ) +EVENT( EV_Activate, idTarget_LevelTrigger::Event_Activate ) +END_CLASS + +/* +================ +idTarget_LevelTrigger::Event_Activate +================ +*/ +void idTarget_LevelTrigger::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + player->SetLevelTrigger( spawnArgs.GetString( "levelName" ), spawnArgs.GetString( "triggerName" ) ); + } + } +} + + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ + +CLASS_DECLARATION( idTarget, idTarget_EnableStamina ) +EVENT( EV_Activate, idTarget_EnableStamina::Event_Activate ) +END_CLASS + +/* +================ +idTarget_EnableStamina::Event_Activate +================ +*/ +void idTarget_EnableStamina::Event_Activate( idEntity *activator ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + idPlayer *player = static_cast< idPlayer* >( gameLocal.entities[i] ); + if ( spawnArgs.GetBool( "enable" ) ) { + pm_stamina.SetFloat( player->spawnArgs.GetFloat( "pm_stamina" ) ); + } else { + pm_stamina.SetFloat( 0.0f ); + } + } + } +} + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ + +const idEventDef EV_RestoreVolume( "" ); +CLASS_DECLARATION( idTarget, idTarget_FadeSoundClass ) +EVENT( EV_Activate, idTarget_FadeSoundClass::Event_Activate ) +EVENT( EV_RestoreVolume, idTarget_FadeSoundClass::Event_RestoreVolume ) +END_CLASS + +/* +================ +idTarget_FadeSoundClass::Event_Activate +================ +*/ +void idTarget_FadeSoundClass::Event_Activate( idEntity *activator ) { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); + float fadeDuration = spawnArgs.GetFloat( "fadeDuration" ); + int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // start any sound fading now + if ( fadeTime ) { + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, fadeClass, spawnArgs.GetBool( "fadeIn" ) ? fadeDB : 0.0f - fadeDB, fadeTime ); + if ( fadeDuration ) { + PostEventSec( &EV_RestoreVolume, fadeDuration ); + } + } +} + +/* +================ +idTarget_FadeSoundClass::Event_RestoreVolume +================ +*/ +void idTarget_FadeSoundClass::Event_RestoreVolume() { + float fadeTime = spawnArgs.GetFloat( "fadeTime" ); + float fadeDB = spawnArgs.GetFloat( "fadeDB" ); +// int fadeClass = spawnArgs.GetInt( "fadeClass" ); + // restore volume + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, 0, fadeDB, fadeTime ); +} + diff --git a/src/mpgame/Target.h b/src/mpgame/Target.h new file mode 100644 index 0000000..d2181ec --- /dev/null +++ b/src/mpgame/Target.h @@ -0,0 +1,727 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_TARGET_H__ +#define __GAME_TARGET_H__ + +//Used to compare two ammoData structs and see who has more. +int CompareAmmoData( const void* ammo1, const void* ammo2); + + +/* +=============================================================================== + +idTarget + +=============================================================================== +*/ + +class idTarget : public idEntity { +public: + CLASS_PROTOTYPE( idTarget ); +}; + + +/* +=============================================================================== + +idTarget_Remove + +=============================================================================== +*/ + +class idTarget_Remove : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Remove ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Show + +=============================================================================== +*/ + +class idTarget_Show : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Show ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Damage + +=============================================================================== +*/ + +class idTarget_Damage : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Damage ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SessionCommand + +=============================================================================== +*/ + +class idTarget_SessionCommand : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SessionCommand ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_EndLevel + +=============================================================================== +*/ + +class idTarget_EndLevel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EndLevel ); + +private: + void Event_Activate( idEntity *activator ); + +}; + + +/* +=============================================================================== + +idTarget_WaitForButton + +=============================================================================== +*/ + +class idTarget_WaitForButton : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_WaitForButton ); + + void Think( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetGlobalShaderTime + +=============================================================================== +*/ + +class idTarget_SetGlobalShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetGlobalShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderParm + +=============================================================================== +*/ + +class idTarget_SetShaderParm : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderParm ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetShaderTime + +=============================================================================== +*/ + +class idTarget_SetShaderTime : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetShaderTime ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeEntity + +=============================================================================== +*/ + +class idTarget_FadeEntity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeEntity ); + + idTarget_FadeEntity( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idVec4 fadeFrom; + int fadeStart; + int fadeEnd; + + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeIn + +=============================================================================== +*/ + +class idTarget_LightFadeIn : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeIn ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_LightFadeOut + +=============================================================================== +*/ + +class idTarget_LightFadeOut : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LightFadeOut ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_Give + +=============================================================================== +*/ + +class idTarget_Give : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Give ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +// RAVEN BEGIN +// abahr: fixing issue with EV_Activate not taking NULL ptrs + void Event_PostSpawn(); +// RAVEN END +}; + + +/* +=============================================================================== + +idTarget_GiveEmail + +=============================================================================== +*/ + +class idTarget_GiveEmail : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveEmail ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_SetModel + +=============================================================================== +*/ + +class idTarget_SetModel : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetModel ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetInfluence + +=============================================================================== +*/ + +class idTarget_SetInfluence : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetInfluence ); + + idTarget_SetInfluence( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreInfluence(); + void Event_GatherEntities(); + void Event_Flash( float flash, int out ); + void Event_ClearFlash( float flash ); + void Think( void ); + + idList lightList; + idList guiList; + idList soundList; + idList genericList; + float flashIn; + float flashOut; + float delay; + idStr flashInSound; + idStr flashOutSound; + idEntity * switchToCamera; + idInterpolatefovSetting; + bool soundFaded; + bool restoreOnTrigger; +}; + + +/* +=============================================================================== + +idTarget_SetKeyVal + +=============================================================================== +*/ + +class idTarget_SetKeyVal : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetKeyVal ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetFov + +=============================================================================== +*/ + +class idTarget_SetFov : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetFov ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Think( void ); + +private: + idInterpolate fovSetting; + + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_SetPrimaryObjective + +=============================================================================== +*/ + +class idTarget_SetPrimaryObjective : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_SetPrimaryObjective ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// RAVEN BEGIN +// bdube: added player database +/* +=============================================================================== + +idTarget_AddDatabaseEntry + +=============================================================================== +*/ + +class rvTarget_AddDatabaseEntry : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AddDatabaseEntry ); + +private: + void Event_Activate( idEntity *activator ); +}; + +// jshepard: secret area discovery + +/* +=============================================================================== + +idTarget_SecretArea + +=============================================================================== +*/ + +class rvTarget_SecretArea : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_SecretArea ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_BossBattle + +=============================================================================== +*/ + +class rvTarget_BossBattle : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_BossBattle ); + +private: + void Event_Activate( idEntity *activator ); + void Event_SetShieldPercent( float percent ); + void Event_SetBossMaxHealth( float f ); + void Event_AllowShieldBar( float activate ); + void Event_AllowShieldWarningBar( float activate ); + +}; + +/* +=============================================================================== + +rvTarget_TetherAI + +=============================================================================== +*/ + +class rvTarget_TetherAI : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_TetherAI ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +rvTarget_Nailable + +=============================================================================== +*/ + +class rvTarget_Nailable : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_Nailable ); + +private: + void Spawn( void ); +}; + + +// RAVEN END + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_LockDoor: public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LockDoor ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_CallObjectFunction + +=============================================================================== +*/ + +class idTarget_CallObjectFunction : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_CallObjectFunction ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LockDoor + +=============================================================================== +*/ + +class idTarget_EnableLevelWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableLevelWeapons ); + +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_Tip + +=============================================================================== +*/ + +class idTarget_Tip : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_Tip ); + + idTarget_Tip( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +private: + idVec3 playerPos; + + void Event_Activate( idEntity *activator ); + void Event_TipOff( void ); + void Event_GetPlayerPos( void ); +}; + +/* +=============================================================================== + +idTarget_GiveSecurity + +=============================================================================== +*/ +class idTarget_GiveSecurity : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_GiveSecurity ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_RemoveWeapons + +=============================================================================== +*/ +class idTarget_RemoveWeapons : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_RemoveWeapons ); +private: + void Event_Activate( idEntity *activator ); +}; + + +/* +=============================================================================== + +idTarget_LevelTrigger + +=============================================================================== +*/ +class idTarget_LevelTrigger : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_LevelTrigger ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_EnableStamina + +=============================================================================== +*/ +class idTarget_EnableStamina : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_EnableStamina ); +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +idTarget_FadeSoundClass + +=============================================================================== +*/ +class idTarget_FadeSoundClass : public idTarget { +public: + CLASS_PROTOTYPE( idTarget_FadeSoundClass ); +private: + void Event_Activate( idEntity *activator ); + void Event_RestoreVolume(); +}; + + +/* +=============================================================================== + +rvTarget_LaunchProjectile + +=============================================================================== +*/ + +class rvTarget_LaunchProjectile : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_LaunchProjectile ); + + void Spawn( void ); + +private: + void Event_Activate( idEntity *activator ); + void Event_LaunchProjectile( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_ExitAreaAlert + +=============================================================================== +*/ + +class rvTarget_ExitAreaAlert : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_ExitAreaAlert ); + +private: + void Event_Activate( idEntity *activator ); +}; + +/* +=============================================================================== + +rvTarget_AmmoStash + +=============================================================================== +*/ +typedef struct ammodata_s { + + int ammoIndex; + idStr ammoName; + int ammoCount; + int ammoMax; + float percentFull; + +} ammodata_t; + +#define AMMO_ARRAY_SIZE 10 + +class rvTarget_AmmoStash : public idTarget { +public: + CLASS_PROTOTYPE( rvTarget_AmmoStash ); + + //used to detect which weapons need ammo. The values stored are 0 to 1, with -1 meaning don't check this out. + ammodata_t AmmoArray[ AMMO_ARRAY_SIZE]; + +private: + void Event_Activate( idEntity *activator ); +}; +#endif /* !__GAME_TARGET_H__ */ diff --git a/src/mpgame/TramGate.cpp b/src/mpgame/TramGate.cpp new file mode 100644 index 0000000..7a49fc6 --- /dev/null +++ b/src/mpgame/TramGate.cpp @@ -0,0 +1,335 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +const idEventDef EV_OpenGate( "" ); +const idEventDef EV_CloseGate( "" ); + +//======================================================= +// +// rvTramGate +// +//======================================================= +CLASS_DECLARATION( idAnimatedEntity, rvTramGate ) + EVENT( EV_Touch, rvTramGate::Event_Touch ) + EVENT( EV_Activate, rvTramGate::Event_Activate ) + EVENT( EV_OpenGate, rvTramGate::Event_OpenGate ) + EVENT( EV_CloseGate, rvTramGate::Event_CloseGate ) + EVENT( EV_Door_Lock, rvTramGate::Event_Lock ) + EVENT( EV_Door_IsOpen, rvTramGate::Event_IsOpen ) + EVENT( EV_Door_IsLocked, rvTramGate::Event_IsLocked ) +END_CLASS + +/* +================ +rvTramGate::Spawn +================ +*/ +void rvTramGate::Spawn() { + SpawnDoors(); + + AdjustFrameRate(); +} + +/* +================ +rvTramGate::~rvTramGate +================ +*/ +rvTramGate::~rvTramGate() { + doorList.RemoveContents( true ); +} + +/* +================ +rvTramGate::SpawnDoors +================ +*/ +void rvTramGate::SpawnDoors() { + idDict args; + idVec3 dir = spawnArgs.GetAngles("doorsAxisOffset").ToMat3() * GetPhysics()->GetAxis()[1]; + + args.Set( "team", GetName() ); + args.SetMatrix( "rotation", dir.ToMat3() ); + args.SetVector( "origin", GetPhysics()->GetOrigin() ); + + int len = strlen("door_"); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("door"); kv; kv = spawnArgs.MatchPrefix("door", kv) ) { + args.Set( kv->GetKey().Right(kv->GetKey().Length() - len), kv->GetValue() ); + } + + args.SetFloat( "movedir", (-dir).ToYaw() ); + idDoor* door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door1"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + args.SetFloat( "movedir", dir.ToYaw() ); + door = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_door2"), &args ); + if( door ) { + doorList.Alloc() = door; + door->SetDoorFrameController( this ); + } + + //assert( GetDoorMaster() == door->GetMoveMaster() ); +} + +/* +================ +rvTramGate::AdjustFrameRate +================ +*/ +void rvTramGate::AdjustFrameRate() { + GetAnimator()->SetPlaybackRate( "open", spawnArgs.GetFloat("openFrameRateScale") ); + GetAnimator()->SetPlaybackRate( "close", spawnArgs.GetFloat("closeFrameRateScale") ); +} + +/* +================ +rvTramGate::OpenGate +================ +*/ +void rvTramGate::OpenGate() { + PlayAnim( ANIMCHANNEL_ALL, "open" ); +} + +/* +================ +rvTramGate::CloseGate +================ +*/ +void rvTramGate::CloseGate() { + PlayAnim( ANIMCHANNEL_ALL, "close" ); +} + +/* +================ +rvTramGate::Save +================ +*/ +void rvTramGate::Save( idSaveGame *savefile ) const { + savefile->WriteInt( doorList.Num() ); + for( int i = 0; i < doorList.Num(); i++ ) { + doorList[i].Save( savefile ); + } +} + +/* +================ +rvTramGate::Restore +================ +*/ +void rvTramGate::Restore( idRestoreGame *savefile ) { + int num = 0; + idEntityPtr temp; + savefile->ReadInt( num ); + for( int i = 0; i < num; i++ ) { + temp.Restore( savefile ); + doorList.Append( temp ); + } +} + +/* +================ +rvTramGate::PlayAnim +================ +*/ +int rvTramGate::PlayAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return 0; + } + + GetAnimator()->PlayAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); + return GetAnimator()->CurrentAnim(channel)->PlayLength(); +} + +/* +================ +rvTramGate::CycleAnim +================ +*/ +void rvTramGate::CycleAnim( int channel, const char* animName, int blendFrames ) { + int animHandle = GetAnimator()->GetAnim( animName ); + + if( !animHandle ) { + ClearAllAnims( blendFrames ); + return; + } + + GetAnimator()->CycleAnim( channel, animHandle, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAllAnims( int blendFrames ) { + GetAnimator()->ClearAllAnims( gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::ClearAnim( int channel, int blendFrames ) { + GetAnimator()->Clear( channel, gameLocal.GetTime(), FRAME2MS(blendFrames) ); +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +bool rvTramGate::AnimIsPlaying( int channel, int blendFrames ) { + return GetAnimator()->CurrentAnim(channel)->GetEndTime() - FRAME2MS(blendFrames) >= gameLocal.GetTime(); +} + +/* +================ +rvTramGate::IsOpen +================ +*/ +bool rvTramGate::IsOpen() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsOpen() : false; +} + +/* +================ +rvTramGate::IsClosed +================ +*/ +bool rvTramGate::IsClosed() const { + return (IsDoorMasterValid()) ? GetDoorMaster()->IsClosed() : false; +} + +/* +================ +rvTramGate::GetDoorMaster +================ +*/ +idDoor* rvTramGate::GetDoorMaster() const { + return doorList.Num() ? doorList[0] : NULL; +} + +/* +================ +rvTramGate::IsDoorMasterValid +================ +*/ +bool rvTramGate::IsDoorMasterValid() const { + return GetDoorMaster() != NULL; +} + +/* +================ +rvTramGate::Event_Touch +================ +*/ +void rvTramGate::Event_Touch( idEntity* other, trace_t* trace ) { + if( !IsDoorMasterValid() ) { + return; + } + + if( IsClosed() ) { + OpenGate(); + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } else if( IsOpen() ) { + GetDoorMaster()->ProcessEvent( &EV_Touch, this, trace ); + } +} + +/* +================ +rvTramGate::Event_Activate +================ +*/ +void rvTramGate::Event_Activate( idEntity* activator ) { + if( !IsDoorMasterValid() ) { + return; + } + + // FIXME: may need some better logic than this. + const char* animName = (IsClosed()) ? "open" : (IsOpen()) ? "close" : NULL; + if( animName ) { + PlayAnim( ANIMCHANNEL_ALL, animName ); + GetDoorMaster()->ProcessEvent( &EV_Activate, this ); + } +} + +/* +================ +rvTramGate::Event_OpenGate +================ +*/ +void rvTramGate::Event_OpenGate() { + OpenGate(); +} + +/* +================ +rvTramGate::Event_CloseGate +================ +*/ +void rvTramGate::Event_CloseGate() { + CloseGate(); +} + +/* +================ +rvTramGate::Event_IsOpen +================ +*/ +void rvTramGate::Event_IsOpen( void ) { + idThread::ReturnInt( IsOpen() ); +} + +/* +================ +rvTramGate::Event_IsLocked +================ +*/ +void rvTramGate::Event_IsLocked( void ) { + idThread::ReturnInt( (IsDoorMasterValid()) ? GetDoorMaster()->IsLocked() : 0 ); +} + +/* +================ +rvTramGate::Event_Lock +================ +*/ +void rvTramGate::Event_Lock( int f ) { + if( IsDoorMasterValid() ) { + GetDoorMaster()->Lock( f ); + } +} diff --git a/src/mpgame/TramGate.h b/src/mpgame/TramGate.h new file mode 100644 index 0000000..b9cab2a --- /dev/null +++ b/src/mpgame/TramGate.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RV_TRAM_GATE_H +#define __RV_TRAM_GATE_H + +extern const idEventDef EV_OpenGate; +extern const idEventDef EV_CloseGate; + +class rvTramGate : public idAnimatedEntity { + CLASS_PROTOTYPE( rvTramGate ); + +public: + void Spawn(); + virtual ~rvTramGate(); + + void OpenGate(); + void CloseGate(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +protected: + void SpawnDoors(); + void AdjustFrameRate(); + + int PlayAnim( int channel, const char* animName, int blendFrames = 0 ); + void CycleAnim( int channel, const char* animName, int blendFrames = 0 ); + void ClearAllAnims( int blendFrames = 0 ); + void ClearAnim( int channel, int blendFrames = 0 ); + bool AnimIsPlaying( int channel, int blendFrames = 0 ); + + bool IsOpen() const; + bool IsClosed() const; + + idDoor* GetDoorMaster() const; + bool IsDoorMasterValid() const; + +protected: + void Event_Touch( idEntity* other, trace_t* trace ); + void Event_Activate( idEntity* activator ); + + void Event_OpenGate(); + void Event_CloseGate(); + + void Event_IsOpen( void ); + void Event_IsLocked( void ); + void Event_Lock( int f ); + +protected: + idList< idEntityPtr > doorList; +}; + +#endif diff --git a/src/mpgame/Trigger.cpp b/src/mpgame/Trigger.cpp new file mode 100644 index 0000000..6ea0d53 --- /dev/null +++ b/src/mpgame/Trigger.cpp @@ -0,0 +1,1639 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "ai/AI_Manager.h" + +/* +=============================================================================== + + idTrigger + +=============================================================================== +*/ + +const idEventDef EV_Enable( "enable", NULL ); +const idEventDef EV_Disable( "disable", NULL ); + +CLASS_DECLARATION( idEntity, idTrigger ) + EVENT( EV_Enable, idTrigger::Event_Enable ) + EVENT( EV_Disable, idTrigger::Event_Disable ) +END_CLASS + +/* +================ +idTrigger::DrawDebugInfo +================ +*/ +void idTrigger::DrawDebugInfo( void ) { + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + idBounds viewTextBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds viewBounds( gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() ); + idBounds box( idVec3( -4.0f, -4.0f, -4.0f ), idVec3( 4.0f, 4.0f, 4.0f ) ); + idEntity *ent; + idEntity *target; + int i; + bool show; + const function_t *func; + + viewTextBounds.ExpandSelf( 128.0f ); + viewBounds.ExpandSelf( 512.0f ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->GetPhysics()->GetContents() & ( CONTENTS_TRIGGER | CONTENTS_FLASHLIGHT_TRIGGER ) ) { + show = viewBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ); + if ( !show ) { + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target && viewBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + show = true; + break; + } + } + } + + if ( !show ) { + continue; + } + + gameRenderWorld->DebugBounds( colorOrange, ent->GetPhysics()->GetAbsBounds() ); + if ( viewTextBounds.IntersectsBounds( ent->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( ent->name.c_str(), ent->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + gameRenderWorld->DrawText( ent->GetEntityDefName(), ent->GetPhysics()->GetAbsBounds().GetCenter() + up, 0.1f, colorWhite, axis, 1 ); + if ( ent->IsType( idTrigger::Type ) ) { + func = static_cast( ent )->GetScriptFunction(); + } else { + func = NULL; + } + + if ( func ) { + gameRenderWorld->DrawText( va( "call script '%s'", func->Name() ), ent->GetPhysics()->GetAbsBounds().GetCenter() - up, 0.1f, colorWhite, axis, 1 ); + } + } + + for( i = 0; i < ent->targets.Num(); i++ ) { + target = ent->targets[ i ].GetEntity(); + if ( target ) { + gameRenderWorld->DebugArrow( colorYellow, ent->GetPhysics()->GetAbsBounds().GetCenter(), target->GetPhysics()->GetOrigin(), 10, 0 ); + gameRenderWorld->DebugBounds( colorGreen, box, target->GetPhysics()->GetOrigin() ); + if ( viewTextBounds.IntersectsBounds( target->GetPhysics()->GetAbsBounds() ) ) { + gameRenderWorld->DrawText( target->name.c_str(), target->GetPhysics()->GetAbsBounds().GetCenter(), 0.1f, colorWhite, axis, 1 ); + } + } + } + } + } +} + +/* +================ +idTrigger::Enable +================ +*/ +void idTrigger::Enable( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + GetPhysics()->EnableClip(); +} + +/* +================ +idTrigger::Disable +================ +*/ +void idTrigger::Disable( void ) { + // we may be relinked if we're bound to another object, so clear the contents as well + GetPhysics()->SetContents( 0 ); + GetPhysics()->DisableClip(); +} + +/* +================ +idTrigger::CallScript +================ +*/ +void idTrigger::CallScript( idEntity* scriptEntity ) { +// RAVEN BEGIN +// abahr + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].InsertEntity( scriptEntity, 0 );//We could pass both the activator and self if wanted + scriptFunctions[ix].CallFunc( &spawnArgs ); + scriptFunctions[ix].RemoveIndex( 0 ); + } +// RAVEN END + +} + +/* +================ +idTrigger::GetScriptFunction +================ +*/ +const function_t *idTrigger::GetScriptFunction( void ) const { +// RAVEN BEGIN +// abahr: + return (scriptFunctions.Num()) ? scriptFunctions[0].GetFunc() : NULL; +// RAVEN END +} + +/* +================ +idTrigger::Save +================ +*/ +void idTrigger::Save( idSaveGame *savefile ) const { +// RAVEN BEGIN +// abahr + savefile->WriteInt( scriptFunctions.Num() ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Save( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Restore +================ +*/ +void idTrigger::Restore( idRestoreGame *savefile ) { +// RAVEN BEGIN +// abahr + int numScripts = 0; + savefile->ReadInt( numScripts ); + scriptFunctions.SetNum( numScripts ); + for( int ix = scriptFunctions.Num() - 1; ix >= 0; --ix ) { + scriptFunctions[ix].Restore( savefile ); + } +// RAVEN END +} + +/* +================ +idTrigger::Event_Enable +================ +*/ +void idTrigger::Event_Enable( void ) { + Enable(); +} + +/* +================ +idTrigger::Event_Disable +================ +*/ +void idTrigger::Event_Disable( void ) { + Disable(); +} + +/* +================ +idTrigger::idTrigger +================ +*/ +idTrigger::idTrigger() { +// RAVEN BEGIN +// abahr: scriptFunction init's itself + //scriptFunction = NULL; +// RAVEN END +} + +/* +================ +idTrigger::Spawn +================ +*/ +void idTrigger::Spawn( void ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + +// RAVEN BEGIN +// abahr: + scriptFunctions.SetGranularity( 1 ); + for( const idKeyValue* kv = spawnArgs.MatchPrefix("call"); kv; kv = spawnArgs.MatchPrefix("call", kv) ) { + if( !kv->GetValue() ) { + continue; + } + + rvScriptFuncUtility& utility = scriptFunctions.Alloc(); + if( !utility.Init(kv->GetValue()) ) { + gameLocal.Warning( "Trigger '%s' at (%s) trying to call an unknown function.", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + } +// RAVEN END +} + + +/* +=============================================================================== + + idTrigger_Multi + +=============================================================================== +*/ + +// RAVEN BEGIN +// abahr: changed to 'E' to allow NULL entities +const idEventDef EV_TriggerAction( "", "E" ); +// RAVEN END + +CLASS_DECLARATION( idTrigger, idTrigger_Multi ) + EVENT( EV_FindTargets, idTrigger_Multi::Event_FindTargets ) + + EVENT( EV_Touch, idTrigger_Multi::Event_Touch ) + EVENT( EV_SpectatorTouch, idTrigger_Multi::Event_SpectatorTouch ) + EVENT( EV_Activate, idTrigger_Multi::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Multi::Event_TriggerAction ) + +// RAVEN BEGIN +// kfuller: respond to earthquakes + EVENT( EV_Earthquake, idTrigger_Multi::Event_EarthQuake ) +// RAVEN END +END_CLASS + + +/* +================ +idTrigger_Multi::idTrigger_Multi +================ +*/ +idTrigger_Multi::idTrigger_Multi( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + removeItem = 0; + touchClient = false; + touchOther = false; + touchVehicle = false; + touchSpec = false; + triggerFirst = false; + triggerWithSelf = false; + buyZoneTrigger = 0; + controlZoneTrigger = 0; + prevZoneController = TEAM_NONE; +} + +/* +================ +idTrigger_Multi::Save +================ +*/ +void idTrigger_Multi::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteString( requires ); + savefile->WriteInt( removeItem ); + savefile->WriteBool( touchClient ); + savefile->WriteBool( touchOther ); + savefile->WriteBool( touchVehicle ); + savefile->WriteBool( triggerFirst ); + savefile->WriteBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::Restore +================ +*/ +void idTrigger_Multi::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadString( requires ); + savefile->ReadInt( removeItem ); + savefile->ReadBool( touchClient ); + savefile->ReadBool( touchOther ); + savefile->ReadBool( touchVehicle ); + savefile->ReadBool( triggerFirst ); + savefile->ReadBool( triggerWithSelf ); +} + +/* +================ +idTrigger_Multi::IsTeleporter + +Returns true if there is a single target, which is a teleporter +================ +*/ +bool idTrigger_Multi::IsTeleporter( void ) const { + if ( targets.Num() != 1 ) { + return false; + } + + return targets[ 0 ].GetEntity()->IsType( idPlayerStart::GetClassType() ); +} + +/* +================ +idTrigger_Multi::Spawn + +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"call" : Script function to call when triggered +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +================ +*/ +void idTrigger_Multi::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetString( "requires", "", requires ); + spawnArgs.GetInt( "removeItem", "0", removeItem ); + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + spawnArgs.GetBool( "triggerWithSelf", "0", triggerWithSelf ); + spawnArgs.GetInt( "buyZone", "0", buyZoneTrigger); + spawnArgs.GetInt( "controlZone", "0", controlZoneTrigger); + + if ( buyZoneTrigger == -1 ) + gameLocal.Warning( "trigger_buyzone '%s' at (%s) has no buyZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + if ( controlZoneTrigger == -1 ) + gameLocal.Warning( "trigger_controlzone '%s' at (%s) has no controlZone key set!", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + + + if ( spawnArgs.GetBool( "onlyVehicle" ) ) { + touchVehicle = true; + } else if ( spawnArgs.GetBool( "anyTouch" ) ) { + touchClient = true; + touchOther = true; + } else if ( spawnArgs.GetBool( "noTouch" ) ) { + touchClient = false; + touchOther = false; + } else if ( spawnArgs.GetBool( "noClient" ) ) { + touchClient = false; + touchOther = true; + } else { + touchClient = true; + touchOther = false; + } + + nextTriggerTime = 0; + + if ( spawnArgs.GetBool( "flashlight_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER ); + } else if ( spawnArgs.GetBool( "projectile_trigger" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_PROJECTILE ); + } else { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } + + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Multi::FindTargets +================ +*/ +void idTrigger_Multi::Event_FindTargets( void ) { + FindTargets(); + touchSpec = !spawnArgs.GetBool( "noSpec", IsTeleporter() ? "0" : "1" ); +} + +/* +================ +idTrigger_Multi::CheckFacing +================ +*/ +bool idTrigger_Multi::CheckFacing( idEntity *activator ) { + if ( spawnArgs.GetBool( "facing" ) ) { + if ( !activator->IsType( idPlayer::GetClassType() ) ) { + return true; + } + idPlayer *player = static_cast< idPlayer* >( activator ); + + // Unfortunately, the angle key rotates the trigger entity also. So I've added + // an angleFacing key which is used instead when present, otherwise the code defaults + // to the behaviour present prior to this change + idVec3 tFacing = GetPhysics()->GetAxis()[0]; + if ( spawnArgs.FindKey( "angleFacing" )) { + idAngles angs(0,spawnArgs.GetFloat( "angleFacing", "0" ),0); + tFacing = angs.ToForward(); + } + float dot = player->viewAngles.ToForward() * tFacing; + + float angle = RAD2DEG( idMath::ACos( dot ) ); + if ( angle > spawnArgs.GetFloat( "angleLimit", "30" ) ) { + return false; + } + } + return true; +} + + +/* +================ +idTrigger_Multi::TriggerAction +================ +*/ +void idTrigger_Multi::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// jdischler: added for Aweldon. The trigger, when activated, will call the listed func with all attached targets, then return. + if ( spawnArgs.GetBool( "_callWithTargets", "0" )) + { + idEntity *ent; + for( int i = 0; i < targets.Num(); i++ ) + { + ent = targets[ i ].GetEntity(); + if ( !ent ) + { + continue; + } + CallScript( ent ); + } + return; + } +// RAVEN END + ActivateTargets( triggerWithSelf ? this : activator ); + CallScript( triggerWithSelf ? this : activator ); + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_Multi::Event_TriggerAction +================ +*/ +void idTrigger_Multi::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_Multi::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_Multi::Event_Trigger( idEntity *activator ) { +// RAVEN BEGIN +// bdube: moved trigger first + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( activator ) ) { + return; + } +// RAVEN END + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + + +void idTrigger_Multi::HandleControlZoneTrigger() +{ + // This only does something in multiplayer with gameType == DeadZone + if ( !gameLocal.isMultiplayer || gameLocal.gameType != GAME_DEADZONE ) + return; + + const int TEAM_DEADLOCK = 2; + + int pCount = 0; + int count = 0, controllingTeam = TEAM_NONE; + count = playersInTrigger.Num(); + + for ( int i = 0; iPowerUpActive( POWERUP_DEADZONE ) ) + continue; + + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + { + pCount++; + } + + int team = playersInTrigger[i]->team; + + if ( i == 0 ) + controllingTeam = playersInTrigger[i]->team; + + // Assign the controlling team based on the first player + // for zones that accept both. + if ( team != controllingTeam ) + { + controllingTeam = TEAM_DEADLOCK; + pCount = 0; + } + } + + if ( controllingTeam != controlZoneTrigger-1 && controlZoneTrigger != 3 ) + { + controllingTeam = TEAM_NONE; + pCount = 0; + } + + int situation = DZ_NONE; + if ( controllingTeam != prevZoneController ) + { + if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_NONE ) + situation = DZ_MARINES_TAKEN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_NONE ) + situation = DZ_STROGG_TAKEN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINES_LOST; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_LOST; + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_TO_MARINE; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_TO_STROGG; + + // DEADLOCK + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_MARINE ) + situation = DZ_MARINE_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_STROGG ) + situation = DZ_STROGG_DEADLOCK; + else if ( controllingTeam == TEAM_DEADLOCK && prevZoneController == TEAM_NONE ) + situation = DZ_MARINE_DEADLOCK; // Unlikely case, just use this. + else if ( controllingTeam == TEAM_MARINE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINE_REGAIN; + else if ( controllingTeam == TEAM_STROGG && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_STROGG_REGAIN; + else if ( controllingTeam == TEAM_NONE && prevZoneController == TEAM_DEADLOCK ) + situation = DZ_MARINES_LOST; // Unlikely case, just use this. + } + + /// Report individual credits + for( int i = 0; i < count; i++ ) + { + idPlayer* player = playersInTrigger[i]; + + // No token? Ignore em! + if ( spawnArgs.GetBool("requiresDeadZonePowerup", "1") && !player->PowerUpActive( POWERUP_DEADZONE ) ) + continue; + + int team = player->team; + if( team == controllingTeam ) + { + gameLocal.mpGame.ReportZoneControllingPlayer( player ); + } + } + + /// Report zone control to multiplayer game manager + gameLocal.mpGame.ReportZoneController(controllingTeam, pCount, situation, this); + + playersInTrigger.Clear(); + prevZoneController = controllingTeam; +} + + +/* +================ +idTrigger_Multi::Think +================ +*/ +void idTrigger_Multi::Think() +{ + // Control zone handling + if ( controlZoneTrigger > 0 ) + HandleControlZoneTrigger(); +} + +/* +================ +idTrigger_Multi::Event_Touch +================ +*/ +void idTrigger_Multi::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + +// RAVEN BEGIN +// jdischler: vehicle only trigger + if ( touchVehicle ) { + if ( !other->IsType(rvVehicle::GetClassType()) ) { + return; + } + } else { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool player = other->IsType( idPlayer::GetClassType() ); +// RAVEN END + if ( player ) { + if ( !touchClient ) { + return; + } + if ( static_cast< idPlayer * >( other )->spectating ) { + Event_SpectatorTouch( other, trace ); + return; + } + + // Buy zone handling + if ( buyZoneTrigger /*&& gameLocal.mpGame.mpGameState.gameState.currentState != 1*/ ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( buyZoneTrigger-1 == p->team || buyZoneTrigger == 3) + { + p->inBuyZone = true; + p->inBuyZonePrev = true; + } + } + + // Control zone handling + if ( controlZoneTrigger > 0 ) { + idPlayer *p = static_cast< idPlayer * >( other ); + if ( p->PowerUpActive(POWERUP_DEADZONE) || !spawnArgs.GetBool("requiresDeadZonePowerup", "1") ) + playersInTrigger.Append(p); + } + + } else if ( !touchOther ) { + return; + } + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + + // see if this trigger requires an item + if ( !gameLocal.RequirementMet( other, requires, removeItem ) ) { + return; + } + + if ( !CheckFacing( other ) ) { + return; + } + + if ( spawnArgs.GetBool( "toggleTriggerFirst" ) ) { + triggerFirst = true; + } + +// RAVEN BEGIN +// rjohnson: added block + if ( developer.GetBool() && *spawnArgs.GetString ( "message" ) ) { + gameLocal.DPrintf ( "Trigger: %s\n", spawnArgs.GetString ( "message" ) ); + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +================ +idTrigger_Multi::Event_Touch +================ +*/ +void idTrigger_Multi::Event_SpectatorTouch( idEntity *other, trace_t *trace ) { + idPlayer *player; + + if ( !touchSpec ) { + return; + } + + if ( !other->IsType( idPlayer::GetClassType() ) ) { + return; + } + + player = static_cast< idPlayer* >( other ); + if ( !player->spectating ) { + return; + } + + if ( player->lastSpectateTeleport > (gameLocal.time - 1000) ) { + // can't retrigger until the wait is over + return; + } + + if ( !CheckFacing( other ) ) { + return; + } + + player->lastSpectateTeleport = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + player->lastSpectateTeleport += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +// RAVEN BEGIN +// kfuller: +void idTrigger_Multi::Event_EarthQuake(float requiresLOS) +{ + // does this entity even care about earthquakes? + float quakeChance = 0; + + if (!spawnArgs.GetFloat("quakeChance", "0", quakeChance)) + { + return; + } + if (rvRandom::flrand(0, 1.0f) > quakeChance) + { + // failed its activation roll + return; + } + if (requiresLOS) + { + // if the player doesn't have line of sight to this fx, don't do anything + trace_t trace; + idPlayer *player = gameLocal.GetLocalPlayer(); + idVec3 viewOrigin; + idMat3 viewAxis; + + player->GetViewPos(viewOrigin, viewAxis); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, trace, viewOrigin, GetPhysics()->GetOrigin(), MASK_OPAQUE, player ); +// RAVEN END + if (trace.fraction < 1.0f) + { + // something blocked LOS + return; + } + } + // activate this effect now + TriggerAction(gameLocal.entities[ENTITYNUM_WORLD]); +} + +// RAVEN END + +/* +=============================================================================== + + idTrigger_EntityName + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_EntityName ) + EVENT( EV_Touch, idTrigger_EntityName::Event_Touch ) + EVENT( EV_Activate, idTrigger_EntityName::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_EntityName::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_EntityName::idTrigger_EntityName +================ +*/ +idTrigger_EntityName::idTrigger_EntityName( void ) { + wait = 0.0f; + random = 0.0f; + delay = 0.0f; + random_delay = 0.0f; + nextTriggerTime = 0; + triggerFirst = false; +} + +/* +================ +idTrigger_EntityName::Save +================ +*/ +void idTrigger_EntityName::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( wait ); + savefile->WriteFloat( random ); + savefile->WriteFloat( delay ); + savefile->WriteFloat( random_delay ); + savefile->WriteInt( nextTriggerTime ); + savefile->WriteBool( triggerFirst ); + savefile->WriteString( entityName ); +} + +/* +================ +idTrigger_EntityName::Restore +================ +*/ +void idTrigger_EntityName::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( wait ); + savefile->ReadFloat( random ); + savefile->ReadFloat( delay ); + savefile->ReadFloat( random_delay ); + savefile->ReadInt( nextTriggerTime ); + savefile->ReadBool( triggerFirst ); + savefile->ReadString( entityName ); +} + +/* +================ +idTrigger_EntityName::Spawn +================ +*/ +void idTrigger_EntityName::Spawn( void ) { + spawnArgs.GetFloat( "wait", "0.5", wait ); + spawnArgs.GetFloat( "random", "0", random ); + spawnArgs.GetFloat( "delay", "0", delay ); + spawnArgs.GetFloat( "random_delay", "0", random_delay ); + + if ( random && ( random >= wait ) && ( wait >= 0 ) ) { + random = wait - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( random_delay && ( random_delay >= delay ) && ( delay >= 0 ) ) { + random_delay = delay - 1; + gameLocal.Warning( "idTrigger_EntityName '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + spawnArgs.GetBool( "triggerFirst", "0", triggerFirst ); + + entityName = spawnArgs.GetString( "entityname" ); + if ( !entityName.Length() ) { + gameLocal.Error( "idTrigger_EntityName '%s' at (%s) doesn't have 'entityname' key specified", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + nextTriggerTime = 0; + + if ( !spawnArgs.GetBool( "noTouch" ) ) { + GetPhysics()->SetContents( CONTENTS_TRIGGER ); + } +} + +/* +================ +idTrigger_EntityName::TriggerAction +================ +*/ +void idTrigger_EntityName::TriggerAction( idEntity *activator ) { +// RAVEN BEGIN +// abahr: want same functionality as trigger_multi. Need to move this code into these two function calls + idEntity* scriptEntity = spawnArgs.GetBool("triggerWithSelf") ? this : activator; + ActivateTargets( scriptEntity ); + CallScript( scriptEntity ); +// RAVEN END + + if ( wait >= 0 ) { + nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); + } else { + // we can't just remove (this) here, because this is a touch function + // called while looping through area links... + nextTriggerTime = gameLocal.time + 1; + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +idTrigger_EntityName::Event_TriggerAction +================ +*/ +void idTrigger_EntityName::Event_TriggerAction( idEntity *activator ) { + TriggerAction( activator ); +} + +/* +================ +idTrigger_EntityName::Event_Trigger + +the trigger was just activated +activated should be the entity that originated the activation sequence (ie. the original target) +activator should be set to the activator so it can be held through a delay +so wait for the delay time before firing +================ +*/ +void idTrigger_EntityName::Event_Trigger( idEntity *activator ) { + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !activator ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && activator->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && activator->name != entityName ) { + return; + } +// RAVEN END + + if ( triggerFirst ) { + triggerFirst = false; + return; + } + + // don't allow it to trigger twice in a single frame + nextTriggerTime = gameLocal.time + 1; + + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, activator ); + } else { + TriggerAction( activator ); + } +} + +/* +================ +idTrigger_EntityName::Event_Touch +================ +*/ +void idTrigger_EntityName::Event_Touch( idEntity *other, trace_t *trace ) { + if( triggerFirst ) { + return; + } + + if ( nextTriggerTime > gameLocal.time ) { + // can't retrigger until the wait is over + return; + } + +// RAVEN BEGIN +// abahr: so we can exclude an entity by name + if( !other ) { + return; + } + + if( spawnArgs.GetBool("excludeEntityName") && other->name == entityName ) { + return; + } + + if( !spawnArgs.GetBool("excludeEntityName") && other->name != entityName ) { + return; + } +// RAVEN END + + nextTriggerTime = gameLocal.time + 1; + if ( delay > 0 ) { + // don't allow it to trigger again until our delay has passed + nextTriggerTime += SEC2MS( delay + random_delay * gameLocal.random.CRandomFloat() ); + PostEventSec( &EV_TriggerAction, delay, other ); + } else { + TriggerAction( other ); + } +} + +/* +=============================================================================== + + idTrigger_Timer + +=============================================================================== +*/ + +const idEventDef EV_Timer( "", NULL ); + +CLASS_DECLARATION( idTrigger, idTrigger_Timer ) + EVENT( EV_Timer, idTrigger_Timer::Event_Timer ) + EVENT( EV_Activate, idTrigger_Timer::Event_Use ) +END_CLASS + +/* +================ +idTrigger_Timer::idTrigger_Timer +================ +*/ +idTrigger_Timer::idTrigger_Timer( void ) { + random = 0.0f; + wait = 0.0f; + on = false; + delay = 0.0f; +} + +/* +================ +idTrigger_Timer::Save +================ +*/ +void idTrigger_Timer::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( random ); + savefile->WriteFloat( wait ); + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteString( onName ); + savefile->WriteString( offName ); +} + +/* +================ +idTrigger_Timer::Restore +================ +*/ +void idTrigger_Timer::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( random ); + savefile->ReadFloat( wait ); + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadString( onName ); + savefile->ReadString( offName ); +} + +/* +================ +idTrigger_Timer::Spawn + +Repeatedly fires its targets. +Can be turned on or off by using. +================ +*/ +void idTrigger_Timer::Spawn( void ) { + spawnArgs.GetFloat( "random", "1", random ); + spawnArgs.GetFloat( "wait", "1", wait ); + spawnArgs.GetBool( "start_on", "0", on ); + spawnArgs.GetFloat( "delay", "0", delay ); + onName = spawnArgs.GetString( "onName" ); + offName = spawnArgs.GetString( "offName" ); + + if ( random >= wait && wait >= 0 ) { + random = wait - 0.001; + gameLocal.Warning( "idTrigger_Timer '%s' at (%s) has random >= wait", name.c_str(), GetPhysics()->GetOrigin().ToString(0) ); + } + + if ( on ) { + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Enable +================ +*/ +void idTrigger_Timer::Enable( void ) { + // if off, turn it on + if ( !on ) { + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +================ +idTrigger_Timer::Disable +================ +*/ +void idTrigger_Timer::Disable( void ) { + // if on, turn it off + if ( on ) { + on = false; + CancelEvents( &EV_Timer ); + } +} + +/* +================ +idTrigger_Timer::Event_Timer +================ +*/ +void idTrigger_Timer::Event_Timer( void ) { + ActivateTargets( this ); + + // set time before next firing + if ( wait >= 0.0f ) { + PostEventSec( &EV_Timer, wait + gameLocal.random.CRandomFloat() * random ); + } +} + +/* +================ +idTrigger_Timer::Event_Use +================ +*/ +void idTrigger_Timer::Event_Use( idEntity *activator ) { + // if on, turn it off + if ( on ) { + if ( offName.Length() && offName.Icmp( activator->GetName() ) ) { + return; + } + on = false; + CancelEvents( &EV_Timer ); + } else { + // turn it on + if ( onName.Length() && onName.Icmp( activator->GetName() ) ) { + return; + } + on = true; + PostEventSec( &EV_Timer, delay ); + } +} + +/* +=============================================================================== + + idTrigger_Count + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Count ) + EVENT( EV_Activate, idTrigger_Count::Event_Trigger ) + EVENT( EV_TriggerAction, idTrigger_Count::Event_TriggerAction ) +END_CLASS + +/* +================ +idTrigger_Count::idTrigger_Count +================ +*/ +idTrigger_Count::idTrigger_Count( void ) { + goal = 0; + count = 0; + delay = 0.0f; +} + +/* +================ +idTrigger_Count::Save +================ +*/ +void idTrigger_Count::Save( idSaveGame *savefile ) const { + savefile->WriteInt( goal ); + savefile->WriteInt( count ); + savefile->WriteFloat( delay ); +} + +/* +================ +idTrigger_Count::Restore +================ +*/ +void idTrigger_Count::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( goal ); + savefile->ReadInt( count ); + savefile->ReadFloat( delay ); +} + +/* +================ +idTrigger_Count::Spawn +================ +*/ +void idTrigger_Count::Spawn( void ) { + spawnArgs.GetInt( "count", "1", goal ); + spawnArgs.GetFloat( "delay", "0", delay ); + count = 0; +} + +/* +================ +idTrigger_Count::Event_Trigger +================ +*/ +void idTrigger_Count::Event_Trigger( idEntity *activator ) { + // goal of -1 means trigger has been exhausted + if (goal >= 0) { + count++; + if ( count >= goal ) { + if (spawnArgs.GetBool("repeat")) { + count = 0; + } else { + goal = -1; + } + PostEventSec( &EV_TriggerAction, delay, activator ); + } + } +} + +/* +================ +idTrigger_Count::Event_TriggerAction +================ +*/ +void idTrigger_Count::Event_TriggerAction( idEntity *activator ) { + ActivateTargets( activator ); + CallScript( activator ); + if ( goal == -1 ) { + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +=============================================================================== + + idTrigger_Hurt + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Hurt ) + EVENT( EV_Touch, idTrigger_Hurt::Event_Touch ) + EVENT( EV_Activate, idTrigger_Hurt::Event_Toggle ) +END_CLASS + +/* +================ +idTrigger_Hurt::idTrigger_Hurt +================ +*/ +idTrigger_Hurt::idTrigger_Hurt( void ) { + on = false; + delay = 0.0f; + nextTime = 0; +} + +/* +================ +idTrigger_Hurt::Save +================ +*/ +void idTrigger_Hurt::Save( idSaveGame *savefile ) const { + savefile->WriteBool( on ); + savefile->WriteFloat( delay ); + savefile->WriteInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->WriteBool ( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Restore +================ +*/ +void idTrigger_Hurt::Restore( idRestoreGame *savefile ) { + savefile->ReadBool( on ); + savefile->ReadFloat( delay ); + savefile->ReadInt( nextTime ); +// RAVEN BEGIN +// bdube: playeronly flag + savefile->ReadBool( playerOnly ); +// RAVEN END +} + +/* +================ +idTrigger_Hurt::Spawn + + Damages activator + Can be turned on or off by using. +================ +*/ +void idTrigger_Hurt::Spawn( void ) { + spawnArgs.GetBool( "on", "1", on ); + spawnArgs.GetFloat( "delay", "1.0", delay ); + +// RAVEN BEGIN +// kfuller: playeronly flag + spawnArgs.GetBool( "playerOnly", "0", playerOnly ); +// RAVEN END + + nextTime = gameLocal.time; + Enable(); +} + +/* +================ +idTrigger_Hurt::Event_Touch +================ +*/ +void idTrigger_Hurt::Event_Touch( idEntity *other, trace_t *trace ) { + const char *damage; + +// RAVEN BEGIN +// kfuller: playeronly flag +// jnewquist: Use accessor for static class type + if ( playerOnly && !other->IsType( idPlayer::GetClassType() ) ) { + return; + } +// RAVEN END + + if ( on && other && gameLocal.time >= nextTime ) { + damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); + other->Damage( this, NULL, vec3_origin, damage, 1.0f, INVALID_JOINT ); + + ActivateTargets( other ); + CallScript( other ); + + nextTime = gameLocal.time + SEC2MS( delay ); + } +} + +/* +================ +idTrigger_Hurt::Event_Toggle +================ +*/ +void idTrigger_Hurt::Event_Toggle( idEntity *activator ) { + on = !on; +} + + +/* +=============================================================================== + + idTrigger_Fade + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Fade ) + EVENT( EV_Activate, idTrigger_Fade::Event_Trigger ) +END_CLASS + +/* +================ +idTrigger_Fade::Event_Trigger +================ +*/ +void idTrigger_Fade::Event_Trigger( idEntity *activator ) { + idVec4 fadeColor; + int fadeTime; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor = spawnArgs.GetVec4( "fadeColor", "0, 0, 0, 1" ); + fadeTime = SEC2MS( spawnArgs.GetFloat( "fadeTime", "0.5" ) ); + player->playerView.Fade( fadeColor, fadeTime ); + PostEventMS( &EV_ActivateTargets, fadeTime, activator ); + } +} + +/* +=============================================================================== + + idTrigger_Touch + +=============================================================================== +*/ + +CLASS_DECLARATION( idTrigger, idTrigger_Touch ) + EVENT( EV_Activate, idTrigger_Touch::Event_Trigger ) +END_CLASS + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::idTrigger_Touch( void ) { + clipModel = NULL; +} + + +/* +================ +idTrigger_Touch::idTrigger_Touch +================ +*/ +idTrigger_Touch::~idTrigger_Touch( ) { + if ( clipModel ) { + delete clipModel; + clipModel = 0; + } +} + +/* +================ +idTrigger_Touch::Spawn +================ +*/ +void idTrigger_Touch::Spawn( void ) { + // get the clip model +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// RAVEN END + clipModel = new idClipModel( GetPhysics()->GetClipModel() ); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + // remove the collision model from the physics object + GetPhysics()->SetClipModel( NULL, 1.0f ); + + if ( spawnArgs.GetBool( "start_on" ) ) { + BecomeActive( TH_THINK ); + } + filterTeam = -1; + idStr filterTeamStr = spawnArgs.GetString( "filterTeam" ); + if ( filterTeamStr.Size() ) + { + if ( !idStr::Icmp( "marine", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_MARINE; + } + else if ( !idStr::Icmp( "strogg", filterTeamStr.c_str() ) ) + { + filterTeam = AITEAM_STROGG; + } + } +} + +/* +================ +idTrigger_Touch::Save +================ +*/ +void idTrigger_Touch::Save( idSaveGame *savefile ) { + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::Restore +================ +*/ +void idTrigger_Touch::Restore( idRestoreGame *savefile ) { + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( filterTeam ); +} + +/* +================ +idTrigger_Touch::TouchEntities +================ +*/ +void idTrigger_Touch::TouchEntities( void ) { + int numClipModels, i; + idBounds bounds; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + +// RAVEN BEGIN +// abahr: now scriptFunction list + if ( clipModel == NULL || !scriptFunctions.Num() ) { +// RAVEN END + return; + } + + bounds.FromTransformedBounds( clipModel->GetBounds(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ); +// RAVEN BEGIN +// MCG: filterTeam + if ( filterTeam != -1 ) + { + idActor* actor; + // Iterate through the filter team + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)filterTeam ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden actors and actors that can't be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + if ( !bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + continue; + } + cm = actor->GetPhysics()->GetClipModel(); + if ( !cm || !cm->IsTraceModel() ) { + continue; + } + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), GetBindMaster()!=NULL?GetPhysics()->GetOrigin():clipModel->GetOrigin(), GetBindMaster()!=NULL?GetPhysics()->GetAxis():clipModel->GetAxis() ) ) { + continue; + } + ActivateTargets( (idEntity*)actor ); + + CallScript( (idEntity*)actor ); + } + return; + } +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( this, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( this, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + ActivateTargets( entity ); + +// RAVEN BEGIN +// abahr: changed to be compatible with new script function utility + CallScript( entity ); +// RAVEN END + } +} + +/* +================ +idTrigger_Touch::Think +================ +*/ +void idTrigger_Touch::Think( void ) { + if ( thinkFlags & TH_THINK ) { + TouchEntities(); + } + idEntity::Think(); +} + +/* +================ +idTrigger_Touch::Event_Trigger +================ +*/ +void idTrigger_Touch::Event_Trigger( idEntity *activator ) { + if ( thinkFlags & TH_THINK ) { + BecomeInactive( TH_THINK ); + } else { + BecomeActive( TH_THINK ); + } +} + +/* +================ +idTrigger_Touch::Enable +================ +*/ +void idTrigger_Touch::Enable( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +idTrigger_Touch::Disable +================ +*/ +void idTrigger_Touch::Disable( void ) { + BecomeInactive( TH_THINK ); +} diff --git a/src/mpgame/Trigger.h b/src/mpgame/Trigger.h new file mode 100644 index 0000000..f1dffdc --- /dev/null +++ b/src/mpgame/Trigger.h @@ -0,0 +1,316 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_TRIGGER_H__ +#define __GAME_TRIGGER_H__ + +extern const idEventDef EV_Enable; +extern const idEventDef EV_Disable; + +/* +=============================================================================== + + Trigger base. + +=============================================================================== +*/ + +class idTrigger : public idEntity { +public: + CLASS_PROTOTYPE( idTrigger ); + + static void DrawDebugInfo( void ); + + idTrigger(); + void Spawn( void ); + + const function_t * GetScriptFunction( void ) const; + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + +protected: +// RAVEN BEGIN +// abahr: removed const from function + void CallScript( idEntity* scriptEntity ); +// RAVEN END + + void Event_Enable( void ); + void Event_Disable( void ); + +// RAVEN BEGIN +// abahr: changed to allow parms to be passed + idList scriptFunctions; + //const function_t * scriptFunction; +// RAVEN END +}; + + +/* +=============================================================================== + + Trigger which can be activated multiple times. + +=============================================================================== +*/ + +class idTrigger_Multi : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Multi ); + + idTrigger_Multi( void ); + + void Spawn( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + virtual void Think( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + idStr requires; + int removeItem; + bool touchClient; + bool touchOther; + bool touchVehicle; + bool touchSpec; + bool triggerFirst; + bool triggerWithSelf; + int buyZoneTrigger; + int controlZoneTrigger; + int prevZoneController; + + idList playersInTrigger; + + bool IsTeleporter( void ) const; + bool CheckFacing( idEntity *activator ); + void HandleControlZoneTrigger(); + + void Event_FindTargets( void ); + +// RAVEN BEGIN +// kfuller: want trigger_relays entities to be able to respond to earthquakes + void Event_EarthQuake (float requiresLOS); +// RAVEN END + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_SpectatorTouch( idEntity *other, trace_t *trace ); +}; + + +/* +=============================================================================== + + Trigger which can only be activated by an entity with a specific name. + +=============================================================================== +*/ + +class idTrigger_EntityName : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_EntityName ); + + idTrigger_EntityName( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + float wait; + float random; + float delay; + float random_delay; + int nextTriggerTime; + bool triggerFirst; + idStr entityName; + + void TriggerAction( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); + void Event_Trigger( idEntity *activator ); + void Event_Touch( idEntity *other, trace_t *trace ); +}; + +/* +=============================================================================== + + Trigger which repeatedly fires targets. + +=============================================================================== +*/ + +class idTrigger_Timer : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Timer ); + + idTrigger_Timer( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual void Enable( void ); + virtual void Disable( void ); + +private: + float random; + float wait; + bool on; + float delay; + idStr onName; + idStr offName; + + void Event_Timer( void ); + void Event_Use( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fires targets after being activated a specific number of times. + +=============================================================================== +*/ + +class idTrigger_Count : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Count ); + + idTrigger_Count( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + int goal; + int count; + float delay; + + void Event_Trigger( idEntity *activator ); + void Event_TriggerAction( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which hurts touching entities. + +=============================================================================== +*/ + +class idTrigger_Hurt : public idTrigger { +public: + CLASS_PROTOTYPE( idTrigger_Hurt ); + + idTrigger_Hurt( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + +private: + bool on; + float delay; + int nextTime; + +// RAVEN BEGIN +// kfuller: added playeronly + bool playerOnly; +// RAVEN END + + void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Toggle( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which fades the player view. + +=============================================================================== +*/ + +class idTrigger_Fade : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Fade ); + +private: + void Event_Trigger( idEntity *activator ); +}; + + +/* +=============================================================================== + + Trigger which continuously tests whether other entities are touching it. + +=============================================================================== +*/ + +class idTrigger_Touch : public idTrigger { +public: + + CLASS_PROTOTYPE( idTrigger_Touch ); + + idTrigger_Touch( void ); + ~idTrigger_Touch( ); + + void Spawn( void ); + virtual void Think( void ); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + virtual void Enable( void ); + virtual void Disable( void ); + + void TouchEntities( void ); + +private: + idClipModel * clipModel; + int filterTeam; + + void Event_Trigger( idEntity *activator ); +}; + +#endif /* !__GAME_TRIGGER_H__ */ diff --git a/src/mpgame/Weapon.cpp b/src/mpgame/Weapon.cpp new file mode 100644 index 0000000..51beb2a --- /dev/null +++ b/src/mpgame/Weapon.cpp @@ -0,0 +1,3370 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +#include "Weapon.h" +#include "Projectile.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "client/ClientEffect.h" +//#include "../renderer/tr_local.h" + +/*********************************************************************** + + rvViewWeapon + +***********************************************************************/ + +// class def +CLASS_DECLARATION( idAnimatedEntity, rvViewWeapon ) + EVENT( EV_CallFunction, rvViewWeapon::Event_CallFunction ) +END_CLASS + +/*********************************************************************** + + init + +***********************************************************************/ + +/* +================ +rvViewWeapon::rvViewWeapon() +================ +*/ +rvViewWeapon::rvViewWeapon() { + modelDefHandle = -1; + weapon = NULL; + + Clear(); + + fl.networkSync = true; +} + +/* +================ +rvViewWeapon::~rvViewWeapon() +================ +*/ +rvViewWeapon::~rvViewWeapon() { + Clear(); +} + +/* +================ +rvViewWeapon::Spawn +================ +*/ +void rvViewWeapon::Spawn( void ) { + GetPhysics()->SetContents( 0 ); + GetPhysics()->SetClipMask( 0 ); + GetPhysics()->SetClipModel( NULL, 1.0f ); +} + +/* +================ +rvViewWeapon::Save +================ +*/ +void rvViewWeapon::Save( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( pendingGUIEvents.Num() ); + for ( i = 0; i < pendingGUIEvents.Num(); i ++ ) { + savefile->WriteString ( pendingGUIEvents[i] ); + } + + // TOSAVE: const idDeclSkin * saveSkin; + // TOSAVE: const idDeclSkin * invisSkin; + // TOSAVE: const idDeclSkin * saveWorldSkin; + // TOSAVE: const idDeclSkin * worldInvisSkin; + // TOSAVE: const idDeclSkin * saveHandsSkin; + // TOSAVE: const idDeclSkin * handsSkin; + + // TOSAVE: friend rvWeapon; + // TOSAVE: rvWeapon* weapon; +} + +/* +================ +rvViewWeapon::Restore +================ +*/ +void rvViewWeapon::Restore( idRestoreGame *savefile ) { + int i; + int num; + savefile->ReadInt ( num ); + pendingGUIEvents.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadString ( pendingGUIEvents[i] ); + } +} + +/* +=============== +rvViewWeapon::ClientPredictionThink +=============== +*/ +void rvViewWeapon::ClientPredictionThink( void ) { + UpdateAnimation(); +} + +/*********************************************************************** + + Weapon definition management + +***********************************************************************/ + +/* +================ +rvViewWeapon::Clear +================ +*/ +void rvViewWeapon::Clear( void ) { + DeconstructScriptObject(); + scriptObject.Free(); + + StopAllEffects( ); + + // TTimo - the weapon doesn't get a proper Event_DisableWeapon sometimes, so the sound sticks + // typically, client side instance join in tourney mode just wipes all ents + StopSound( SND_CHANNEL_ANY, false ); + + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.entityNum = entityNumber; + + renderEntity.noShadow = true; + renderEntity.noSelfShadow = true; + renderEntity.customSkin = NULL; + + // set default shader parms + renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_GREEN ]= 1.0f; + renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; + renderEntity.shaderParms[3] = 1.0f; + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = 0.0f; + renderEntity.shaderParms[5] = 0.0f; + renderEntity.shaderParms[6] = 0.0f; + renderEntity.shaderParms[7] = 0.0f; + + memset( &refSound, 0, sizeof( refSound_t ) ); + refSound.referenceSoundHandle = -1; + + // setting diversity to 0 results in no random sound. -1 indicates random. + refSound.diversity = -1.0f; + + if ( weapon && weapon->GetOwner ( ) ) { + // don't spatialize the weapon sounds + refSound.listenerId = weapon->GetOwner( )->GetListenerId(); + } + + animator.ClearAllAnims( gameLocal.time, 0 ); + + FreeModelDef(); +} + +/* +===================== +rvViewWeapon::GetDebugInfo +===================== +*/ +void rvViewWeapon::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAnimatedEntity::GetDebugInfo( proc, userData ); + weapon->GetDebugInfo( proc, userData ); +} + +/*********************************************************************** + + GUIs + +***********************************************************************/ + +/* +================ +rvViewWeapon::PostGUIEvent +================ +*/ +void rvViewWeapon::PostGUIEvent( const char* event ) { + pendingGUIEvents.Append ( event ); +} + +/*********************************************************************** + + Model and muzzleflash + +***********************************************************************/ + +/* +================ +rvViewWeapon::SetPowerUpSkin +================ +*/ +void rvViewWeapon::SetPowerUpSkin( const char *name ) { +/* FIXME + saveSkin = renderEntity.customSkin; + renderEntity.customSkin = invisSkin; + if ( worldModel.GetEntity() ) { + saveWorldSkin = worldModel.GetEntity()->GetSkin(); + worldModel.GetEntity()->SetSkin( worldInvisSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::UpdateSkin +================ +*/ +void rvViewWeapon::UpdateSkin( void ) { +/* FIXME + renderEntity.customSkin = saveSkin; + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( saveWorldSkin ); + } +*/ +} + +/* +================ +rvViewWeapon::SetModel +================ +*/ +void rvViewWeapon::SetModel( const char *modelname, int mods ) { + assert( modelname ); + + if ( modelDefHandle >= 0 ) { + gameRenderWorld->RemoveDecals( modelDefHandle ); + } + + renderEntity.hModel = animator.SetModel( modelname ); + if ( renderEntity.hModel ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + } else { + renderEntity.customSkin = NULL; + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + } + + // hide the model until an animation is played + Hide(); +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvViewWeapon::Think +================ +*/ +void rvViewWeapon::Think( void ) { + // do nothing because the present is called from the player through PresentWeapon +} + +/* +===================== +rvViewWeapon::ConvertLocalToWorldTransform +===================== +*/ +void rvViewWeapon::ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ) { + if( !weapon ) { + idAnimatedEntity::ConvertLocalToWorldTransform( offset, axis ); + return; + } + + offset = GetPhysics()->GetOrigin() + offset * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + axis *= GetPhysics()->GetAxis(); +} + +/* +================ +rvViewWeapon::UpdateModelTransform +================ +*/ +void rvViewWeapon::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if( !weapon ) { + idAnimatedEntity::UpdateModelTransform(); + return; + } + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis; + } else { + renderEntity.axis = weapon->ForeshortenAxis( GetPhysics()->GetAxis() ); + renderEntity.origin = GetPhysics()->GetOrigin(); + } +} + +/* +================ +rvViewWeapon::PresentWeapon +================ +*/ +void rvViewWeapon::PresentWeapon( bool showViewModel ) { + // Dont do anything with the weapon while its stale + if ( fl.networkStale ) { + return; + } + +// RAVEN BEGIN +// rjohnson: cinematics should never be done from the player's perspective, so don't think the weapon ( and their sounds! ) + if ( gameLocal.inCinematic ) { + return; + } +// RAVEN END + + // only show the surface in player view + renderEntity.allowSurfaceInViewID = weapon->GetOwner()->entityNumber + 1; + + // crunch the depth range so it never pokes into walls this breaks the machine gun gui + renderEntity.weaponDepthHackInViewID = weapon->GetOwner()->entityNumber + 1; + + weapon->Think(); + + // present the model + if ( showViewModel && !(weapon->wsfl.zoom && weapon->GetZoomGui() ) ) { + Present(); + } else { + FreeModelDef(); + } + + UpdateSound(); +} + +/* +================ +rvViewWeapon::WriteToSnapshot +================ +*/ +void rvViewWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +rvViewWeapon::ReadFromSnapshot +================ +*/ +void rvViewWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} + +/* +================ +rvViewWeapon::ClientStale +================ +*/ +bool rvViewWeapon::ClientStale ( void ) { + StopSound( SND_CHANNEL_ANY, false ); + + if ( weapon ) { + weapon->ClientStale( ); + } + + idEntity::ClientStale( ); + + return false; +} + +/* +================ +rvViewWeapon::ClientReceiveEvent +================ +*/ +bool rvViewWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + if ( idEntity::ClientReceiveEvent( event, time, msg ) ) { + return true; + } + if ( weapon ) { + return weapon->ClientReceiveEvent ( event, time, msg ); + } + return false; +} + +/*********************************************************************** + + Script events + +***********************************************************************/ + +/* +===================== +rvViewWeapon::Event_CallFunction +===================== +*/ +void rvViewWeapon::Event_CallFunction( const char *funcname ) { + if ( weapon ) { + stateParms_t parms = {0}; + if ( weapon->ProcessState ( funcname, parms ) == SRESULT_ERROR ) { + gameLocal.Error ( "Unknown function '%s' on entity '%s'", funcname, GetName() ); + } + } +} + +/* +================ +rvViewWeapon::SetSkin +================ +*/ +void rvViewWeapon::SetSkin( const char *skinname ) { + const idDeclSkin *skinDecl; + + if ( !skinname || !skinname[ 0 ] ) { + skinDecl = NULL; + } else { + skinDecl = declManager->FindSkin( skinname ); + } + + renderEntity.customSkin = skinDecl; + UpdateVisuals(); + + // Set the skin on the world model as well + if ( weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skinDecl ); + } +} + +void rvViewWeapon::SetSkin( const idDeclSkin* skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); + + if( weapon && weapon->GetWorldModel() ) { + weapon->GetWorldModel()->SetSkin( skin ); + } +} + +/* +================ +rvViewWeapon::GetPosition +================ +*/ +void rvViewWeapon::GetPosition( idVec3& origin, idMat3& axis ) const { + origin = GetPhysics()->GetOrigin(); + axis = GetPhysics()->GetAxis(); +} + +void rvViewWeapon::SetOverlayShader( const idMaterial* material ) { + renderEntity.overlayShader = material; +} + +/*********************************************************************** + + rvWeapon + +***********************************************************************/ + +CLASS_DECLARATION( idClass, rvWeapon ) +END_CLASS + +/* +================ +rvWeapon::rvWeapon +================ +*/ +rvWeapon::rvWeapon ( void ) { + viewModel = NULL; + worldModel = NULL; + weaponDef = NULL; + +#ifdef _XENON + aimAssistFOV = 10.0f; +#endif + + memset ( &animDoneTime, 0, sizeof(animDoneTime) ); + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + + hitscanAttackDef = -1; + + forceGUIReload = false; +} + +/* +================ +rvWeapon::~rvWeapon +================ +*/ +rvWeapon::~rvWeapon( void ) { + int i; + + // Free all current light defs + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + FreeLight ( i ); + } + + // Disassociate with the view model + if ( viewModel ) { + StopSound( SND_CHANNEL_ANY, false ); + viewModel->weapon = NULL; + } +} + +/* +================ +rvWeapon::Init +================ +*/ +void rvWeapon::Init( idPlayer* _owner, const idDeclEntityDef* def, int _weaponIndex, bool _isStrogg ) { + int i; + + viewModel = _owner->GetWeaponViewModel( ); + worldModel = _owner->GetWeaponWorldModel( ); + weaponDef = def; + owner = _owner; + scriptObject = &viewModel->scriptObject; + weaponIndex = _weaponIndex; + mods = owner->inventory.weaponMods[ weaponIndex ]; + isStrogg = _isStrogg; + + spawnArgs = weaponDef->dict; + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif + + // Apply the mod dictionaries + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict; + if ( !(mods & (1<weapon = this; +} + +/* +================ +rvWeapon::FindViewModelPositionStyle +================ +*/ +void rvWeapon::FindViewModelPositionStyle( idVec3& viewOffset, idAngles& viewAngles ) const { + int viewStyle = g_gunViewStyle.GetInteger(); + const char* styleDefName = spawnArgs.GetString( va("def_viewStyle%d", viewStyle) ); + const idDict* styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + if( !styleDef ) { + styleDefName = spawnArgs.GetString( "def_viewStyle" ); + styleDef = gameLocal.FindEntityDefDict( styleDefName, false ); + } + assert( styleDef ); + + viewAngles = styleDef->GetAngles( "viewangles" ); + viewOffset = styleDef->GetVector( "viewoffset" ); +} + +/* +================ +rvWeapon::Spawn +================ +*/ +void rvWeapon::Spawn ( void ) { + + memset ( &wsfl, 0, sizeof(wsfl) ); + memset ( &wfl, 0, sizeof(wfl) ); + +// RAVEN BEGIN +// nrausch: +#if defined(_XENON) + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +// RAVEN END + + // Initialize variables + projectileEnt = NULL; + kick_endtime = 0; + hideStart = 0.0f; + hideEnd = 0.0f; + hideOffset = 0.0f; + status = WP_HOLSTERED; + lastAttack = 0; + clipPredictTime = 0; + + muzzleAxis.Identity(); + muzzleOrigin.Zero(); + pushVelocity.Zero(); + playerViewAxis.Identity(); + playerViewOrigin.Zero(); + viewModelAxis.Identity(); + viewModelOrigin.Zero(); + + // View + viewModelForeshorten = spawnArgs.GetFloat ( "foreshorten", "1" ); + + FindViewModelPositionStyle( viewModelOffset, viewModelAngles ); + + // Offsets + weaponAngleOffsetAverages = spawnArgs.GetInt( "weaponAngleOffsetAverages", "10" ); + weaponAngleOffsetScale = spawnArgs.GetFloat( "weaponAngleOffsetScale", "0.25" ); + weaponAngleOffsetMax = spawnArgs.GetFloat( "weaponAngleOffsetMax", "10" ); + weaponOffsetTime = spawnArgs.GetFloat( "weaponOffsetTime", "400" ); + weaponOffsetScale = spawnArgs.GetFloat( "weaponOffsetScale", "0.005" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate" ) ); + altFireRate = SEC2MS ( spawnArgs.GetFloat ( "altFireRate" ) ); + if( altFireRate == 0 ) { + altFireRate = fireRate; + } + spread = (gameLocal.IsMultiplayer()&&spawnArgs.FindKey("spread_mp"))?spawnArgs.GetFloat ( "spread_mp" ):spawnArgs.GetFloat ( "spread" ); + nextAttackTime = 0; + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); + wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); + + // Attack related values + muzzle_kick_time = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_time" ) ); + muzzle_kick_maxtime = SEC2MS( spawnArgs.GetFloat( "muzzle_kick_maxtime" ) ); + muzzle_kick_angles = spawnArgs.GetAngles( "muzzle_kick_angles" ); + muzzle_kick_offset = spawnArgs.GetVector( "muzzle_kick_offset" ); + + // General weapon properties + wfl.silent_fire = spawnArgs.GetBool( "silent_fire" ); + wfl.hasWindupAnim = spawnArgs.GetBool( "has_windup", "0" ); + icon = spawnArgs.GetString( "mtr_icon" ); + hideTime = SEC2MS( weaponDef->dict.GetFloat( "hide_time", "0.3" ) ); + hideDistance = weaponDef->dict.GetFloat( "hide_distance", "-15" ); + hideStartTime = gameLocal.time - hideTime; + muzzleOffset = weaponDef->dict.GetFloat ( "muzzleOffset", "14" ); + + // Ammo + clipSize = spawnArgs.GetInt( "clipSize" ); + ammoRequired = spawnArgs.GetInt( "ammoRequired" ); + lowAmmo = spawnArgs.GetInt( "lowAmmo" ); + ammoType = GetAmmoIndexForName( spawnArgs.GetString( "ammoType" ) ); + maxAmmo = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ); + + if ( ( ammoType < 0 ) || ( ammoType >= MAX_AMMO ) ) { + gameLocal.Warning( "Unknown ammotype for class '%s'", this->GetClassname ( ) ); + } + + // If the weapon has a clip, then fill it up + ammoClip = owner->inventory.clip[weaponIndex]; + if ( ( ammoClip < 0 ) || ( ammoClip > clipSize ) ) { + // first time using this weapon so have it fully loaded to start + ammoClip = clipSize; + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + } + + // Complex initializations Initialize + InitDefs( ); + InitWorldModel( ); + InitViewModel( ); + + // Requires the view model so must be done after it + InitLights( ); + + viewModel->PostGUIEvent( "weapon_init" ); + viewModel->PostGUIEvent( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent( "weapon_noammo" ); + } + + stateThread.SetName( va("%s_%s_%s", owner->GetName(), viewModel->GetName ( ), spawnArgs.GetString("classname") ) ); + stateThread.SetOwner( this ); + + forceGUIReload = true; +} + +/* +================ +rvWeapon::InitViewModel +================ +*/ +void rvWeapon::InitViewModel( void ) { + const char* guiName; + const char* temp; + int i; + const idKeyValue* kv; + + // Reset view model to clean state + viewModel->Clear ( ); + // Make sure the sound handle is initted + viewModel->refSound.referenceSoundHandle = -1; + + // Intialize the weapon guis + if ( spawnArgs.GetString ( "gui", "", &guiName ) ) { + int g = 0; + do { + viewModel->GetRenderEntity()->gui[g++] = uiManager->FindGui ( guiName, true, false, true ); + guiName = spawnArgs.GetString ( va("gui%d", g + 1 ) ); + } while ( *guiName && viewModel->GetRenderEntity()->gui[g-1] ); + } + + // Set the view models spawn args + viewModel->spawnArgs = weaponDef->dict; + + // Set the model for the view model + if ( isStrogg ) { + temp = spawnArgs.GetString ( "model_view_strogg", spawnArgs.GetString ( "model_view" ) ); + } else { + temp = spawnArgs.GetString ( "model_view" ); + } + viewModel->SetModel( temp, mods ); + + // Hide surfaces + for ( kv = spawnArgs.MatchPrefix ( "hidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + + // Show and Hide the mods + for ( i = 0; i < MAX_WEAPONMODS; i ++ ) { + const idDict* modDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_mod%d", i+1) ), false ); + if ( !modDict ) { + continue; + } + + // Hide any show surfaces for mods that arent on + if ( !(mods & (1<MatchPrefix ( "mod_showsurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_showsurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); // NOTE: HIDING them because we don't have this mod yet + } + } else { + for ( kv = modDict->MatchPrefix ( "mod_hidesurface", NULL ); + kv; + kv = modDict->MatchPrefix ( "mod_hidesurface", kv ) ) { + viewModel->ProcessEvent ( &EV_HideSurface, kv->GetValue() ); + } + } + } + + // find some joints in the model for locating effects + viewAnimator = viewModel->GetAnimator ( ); + barrelJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_barrel", "barrel" ) ); + flashJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flash", "flash" ) ); + ejectJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + guiLightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_guiLight", "guiLight" ) ); + flashlightJointView = viewAnimator->GetJointHandle( spawnArgs.GetString ( "joint_view_flashlight", "flashlight" ) ); + + // Eject offset + spawnArgs.GetVector ( "ejectOffset", "0 0 0", ejectOffset ); + + // Setup a skin for the view model + if ( spawnArgs.GetString ( "skin", "", &temp ) ) { + viewModel->GetRenderEntity()->customSkin = declManager->FindSkin ( temp ); + } + + // make sure we have the correct skin + viewModel->UpdateSkin(); +} + +/* +================ +rvWeapon::InitLights +================ +*/ +void rvWeapon::InitLights ( void ) { + const char* shader; + idVec4 color; + renderLight_t* light; + + memset ( lights, 0, sizeof(lights) ); + memset ( lightHandles, -1, sizeof(lightHandles) ); + + // setup gui light + light = &lights[WPLIGHT_GUI]; + shader = spawnArgs.GetString( "mtr_guiLightShader", "" ); + if ( shader && *shader && viewModel->GetRenderEntity()->gui[0] ) { + light->shader = declManager->FindMaterial( shader, false ); + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = spawnArgs.GetFloat("glightRadius", "3" ); + color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light->shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + light->shaderParms[ SHADERPARM_ALPHA ] = 1.0f; + light->pointLight = true; +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->lightId = WPLIGHT_GUI * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + spawnArgs.GetVector ( "glightOffset", "0 0 0", guiLightOffset ); + } + + // Muzzle flash + light = &lights[WPLIGHT_MUZZLEFLASH]; + shader = spawnArgs.GetString( "mtr_flashShader", "muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = true; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector ( "flashTarget" ); + light->up = spawnArgs.GetVector ( "flashUp" ); + light->right = spawnArgs.GetVector ( "flashRight" ); + light->end = light->target; + } + light->lightId = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + light->allowLightInViewID = owner->entityNumber+1; + muzzleFlashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) ); + muzzleFlashEnd = 0; + spawnArgs.GetVector ( "flashViewOffset", "0 0 0", muzzleFlashViewOffset ); + } + + // the world muzzle flash is the same, just positioned differently + lights[WPLIGHT_MUZZLEFLASH_WORLD] = lights[WPLIGHT_MUZZLEFLASH]; + light = &lights[WPLIGHT_MUZZLEFLASH_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_MUZZLEFLASH_WORLD * 100 + owner->entityNumber; + + // flashlight + light = &lights[WPLIGHT_FLASHLIGHT]; + shader = spawnArgs.GetString( "mtr_flashlightShader", "lights/muzzleflash" ); + if ( shader && *shader ) { + light->shader = declManager->FindMaterial( shader, false ); + spawnArgs.GetVec4( "flashlightColor", "0 0 0 0", color ); + light->shaderParms[ SHADERPARM_RED ] = color[0]; + light->shaderParms[ SHADERPARM_GREEN ] = color[1]; + light->shaderParms[ SHADERPARM_BLUE ] = color[2]; + light->shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + light->lightRadius[0] = light->lightRadius[1] = light->lightRadius[2] = + (float)spawnArgs.GetInt( "flashlightRadius" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + light->detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// dluetscher: changed lights to no shadow for performance reasons + light->noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; +// RAVEN END + light->pointLight = spawnArgs.GetBool( "flashlightPointLight", "1" ); + if ( !light->pointLight ) { + light->target = spawnArgs.GetVector( "flashlightTarget" ); + light->up = spawnArgs.GetVector( "flashlightUp" ); + light->right = spawnArgs.GetVector( "flashlightRight" ); + light->end = light->target; + } + + light->allowLightInViewID = owner->entityNumber+1; + light->lightId = WPLIGHT_FLASHLIGHT * 100 + owner->entityNumber; + spawnArgs.GetVector ( "flashlightViewOffset", "0 0 0", flashlightViewOffset ); + } + + // the world muzzle flashlight is the same, just positioned differently + lights[WPLIGHT_FLASHLIGHT_WORLD] = lights[WPLIGHT_FLASHLIGHT]; + light = &lights[WPLIGHT_FLASHLIGHT_WORLD]; + light->suppressLightInViewID = owner->entityNumber+1; + light->allowLightInViewID = 0; + light->lightId = WPLIGHT_FLASHLIGHT_WORLD * 100 + owner->entityNumber; +} + +/* +================ +rvWeapon::InitDefs +================ +*/ +void rvWeapon::InitDefs( void ) { + const char* name; + const idDeclEntityDef* def; + const char* spawnclass; + idTypeInfo* cls; + + // get the projectile + attackDict.Clear(); + + // Projectile + if ( spawnArgs.GetString( "def_projectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_hitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackDict = def->dict; + hitscanAttackDef = def->Index(); + } + wfl.attackHitscan = true; + } + + // Alternate projectile + attackAltDict.Clear (); + if ( spawnArgs.GetString( "def_altprojectile", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown alt projectile '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + spawnclass = def->dict.GetString( "spawnclass" ); + cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::GetClassType() ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' for alt projectile '%s' (used by weapon '%s')", spawnclass, name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + } + } else if ( spawnArgs.GetString( "def_althitscan", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown hitscan '%s' for weapon '%s'", name, weaponDef->GetName ( ) ); + } else { + attackAltDict = def->dict; + } + wfl.attackAltHitscan = true; + } + + // get the melee damage def + meleeDistance = spawnArgs.GetFloat( "melee_distance" ); + if ( spawnArgs.GetString( "def_melee", "", &name ) && *name ) { + meleeDef = gameLocal.FindEntityDef( name, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown melee '%s' for weapon '%s'", name, weaponDef->GetName() ); + } + } else { + meleeDef = NULL; + } + + // get the brass def + brassDict.Clear(); + if ( spawnArgs.GetString( "def_ejectBrass", "", &name ) && *name ) { + def = gameLocal.FindEntityDef( name, false ); + if ( !def ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon '%s'", name, weaponDef->GetName() ); + } else { + brassDict = def->dict; + // force any brass to spawn as client moveable + brassDict.Set( "spawnclass", "rvClientMoveable" ); + } + } +} + +/* +================ +rvWeapon::Think +================ +*/ +void rvWeapon::Think ( void ) { + + // Cache the player origin and axis + playerViewOrigin = owner->firstPersonViewOrigin; + playerViewAxis = owner->firstPersonViewAxis; + + // calculate weapon position based on player movement bobbing + owner->CalculateViewWeaponPos( viewModelOrigin, viewModelAxis ); + + // hide offset is for dropping the gun when approaching a GUI or NPC + // This is simpler to manage than doing the weapon put-away animation + if ( gameLocal.time - hideStartTime < hideTime ) { + float frac = ( float )( gameLocal.time - hideStartTime ) / ( float )hideTime; + if ( hideStart < hideEnd ) { + frac = 1.0f - frac; + frac = 1.0f - frac * frac; + } else { + frac = frac * frac; + } + hideOffset = hideStart + ( hideEnd - hideStart ) * frac; + } else { + hideOffset = hideEnd; + } + viewModelOrigin += hideOffset * viewModelAxis[ 2 ]; + + // kick up based on repeat firing + MuzzleRise( viewModelOrigin, viewModelAxis ); + + if ( viewModel ) { + // set the physics position and orientation + viewModel->GetPhysics()->SetOrigin( viewModelOrigin ); + viewModel->GetPhysics()->SetAxis( viewModelAxis ); + viewModel->UpdateVisuals(); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + + // Update the zoom variable before updating the script + wsfl.zoom = owner->IsZoomed( ); + + // Only update the state loop on new frames + if ( gameLocal.isNewFrame ) { + stateThread.Execute( ); + } + + if ( viewModel ) { + viewModel->UpdateAnimation( ); + } + + // Clear reload and flashlight flags + wsfl.reload = false; + wsfl.flashlight = false; + + // deal with the third-person visible world model + // don't show shadows of the world model in first person + if ( worldModel && worldModel->GetRenderEntity() ) { + // always show your own weapon + if( owner->entityNumber == gameLocal.localClientNum ) { + worldModel->GetRenderEntity()->suppressLOD = 1; + } else { + worldModel->GetRenderEntity()->suppressLOD = 0; + } + + if ( gameLocal.IsMultiplayer() && g_skipPlayerShadowsMP.GetBool() ) { + // Disable all weapon shadows for the local client + worldModel->GetRenderEntity()->suppressShadowInViewID = gameLocal.localClientNum+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } else if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool() ) { + // Show all weapon shadows + worldModel->GetRenderEntity()->suppressShadowInViewID = 0; + } else { + // Only show weapon shadows for other clients + worldModel->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber+1; + worldModel->GetRenderEntity()->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } + + UpdateGUI(); + + // Update lights + UpdateFlashlight ( ); + UpdateMuzzleFlash ( ); + + // update the gui light + renderLight_t& light = lights[WPLIGHT_GUI]; + if ( light.lightRadius[0] && guiLightJointView != INVALID_JOINT ) { + if ( viewModel ) { + idVec4 color = viewModel->GetRenderEntity()->gui[0]->GetLightColor ( ); + light.shaderParms[ SHADERPARM_RED ] = color[0] * color[3]; + light.shaderParms[ SHADERPARM_GREEN ] = color[1] * color[3]; + light.shaderParms[ SHADERPARM_BLUE ] = color[2] * color[3]; + GetGlobalJointTransform( true, guiLightJointView, light.origin, light.axis, guiLightOffset ); + UpdateLight ( WPLIGHT_GUI ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + } + + // Alert Monsters if the flashlight is one or a muzzle flash is active? + if ( !gameLocal.isMultiplayer ) { + if ( !owner->fl.notarget && (lightHandles[WPLIGHT_MUZZLEFLASH] != -1 || lightHandles[WPLIGHT_FLASHLIGHT] != -1 ) ) { + AlertMonsters ( ); + } + } +} + +/* +================ +rvWeapon::InitWorldModel +================ +*/ +void rvWeapon::InitWorldModel( void ) { + idEntity *ent; + + ent = worldModel; + if ( !ent ) { + gameLocal.Warning ( "InitWorldModel failed due to missing entity" ); + return; + } + + const char *model = spawnArgs.GetString( "model_world" ); + const char *attach = spawnArgs.GetString( "joint_attach" ); + + if ( model[0] && attach[0] ) { + ent->Show(); + ent->SetModel( model ); + ent->GetPhysics()->SetContents( 0 ); + ent->GetPhysics()->SetClipModel( NULL, 1.0f ); + ent->BindToJoint( owner, attach, true ); + ent->GetPhysics()->SetOrigin( vec3_origin ); + ent->GetPhysics()->SetAxis( mat3_identity ); + + // supress model in player views, but allow it in mirrors and remote views + renderEntity_t *worldModelRenderEntity = ent->GetRenderEntity(); + if ( worldModelRenderEntity ) { + worldModelRenderEntity->suppressSurfaceInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber+1; + worldModelRenderEntity->suppressShadowInLightID = WPLIGHT_MUZZLEFLASH * 100 + owner->entityNumber; + } + } else { + ent->SetModel( "" ); + ent->Hide(); + } + + // the renderEntity is reused, so the relevant fields (except this one) appear to be correctly reinitialized + worldModel->GetRenderEntity()->suppressSurfaceMask = 0; + + // Cache the world joints + worldAnimator = ent->GetAnimator ( ); + flashJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flash", "flash" ) ); + flashlightJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_flashlight", "flashlight" ) ); + ejectJointWorld = worldAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_world_eject", "eject" ) ); +} + +/* +================ +rvWeapon::SetState +================ +*/ +void rvWeapon::SetState( const char *statename, int blendFrames ) { + stateThread.SetState( statename, blendFrames ); +} + +/* +================ +rvWeapon::PostState +================ +*/ +void rvWeapon::PostState( const char* statename, int blendFrames ) { + stateThread.PostState( statename, blendFrames ); +} + +/* +===================== +rvWeapon::ExecuteState +===================== +*/ +void rvWeapon::ExecuteState ( const char* statename ) { + SetState ( statename, 0 ); + stateThread.Execute ( ); +} + +/* +================ +rvWeapon::UpdateLight +================ +*/ +void rvWeapon::UpdateLight ( int lightID ) { + if ( lightHandles[lightID] == -1 ) { + lightHandles[lightID] = gameRenderWorld->AddLightDef ( &lights[lightID] ); + } else { + gameRenderWorld->UpdateLightDef( lightHandles[lightID], &lights[lightID] ); + } +} + +/* +================ +rvWeapon::FreeLight +================ +*/ +void rvWeapon::FreeLight ( int lightID ) { + if ( lightHandles[lightID] != -1 ) { + gameRenderWorld->FreeLightDef( lightHandles[lightID] ); + lightHandles[lightID] = -1; + } +} + + +/*********************************************************************** + + Networking + +***********************************************************************/ + +/* +================ +rvWeapon::WriteToSnapshot +================ +*/ +void rvWeapon::WriteToSnapshot( idBitMsgDelta &msg ) const { + // this can probably be reduced a bit, there's no clip/reload in MP + // it seems that's used to drive some of the weapon model firing animations though (such as RL) + msg.WriteBits( ammoClip, ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ReadFromSnapshot +================ +*/ +void rvWeapon::ReadFromSnapshot( const idBitMsgDelta &msg ) { + ammoClip = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::SkipFromSnapshot +================ +*/ +void rvWeapon::SkipFromSnapshot ( const idBitMsgDelta &msg ) { + msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS ); +} + +/* +================ +rvWeapon::ClientStale +================ +*/ +void rvWeapon::ClientStale( void ) { +} + +/* +================ +rvWeapon::ClientReceiveEvent +================ +*/ +bool rvWeapon::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { + switch( event ) { + case EVENT_RELOAD: { + if ( gameLocal.time - time < 1000 ) { + wsfl.netReload = true; + wsfl.netEndReload = false; + } + return true; + } + case EVENT_ENDRELOAD: { + wsfl.netEndReload = true; + return true; + } + case EVENT_CHANGESKIN: { +/* + // FIXME: use idGameLocal::ReadDecl + int index = msg.ReadLong(); + renderEntity.customSkin = ( index != -1 ) ? static_cast( declManager->DeclByIndex( DECL_SKIN, index ) ) : NULL; + UpdateVisuals(); + if ( worldModel.GetEntity() ) { + worldModel.GetEntity()->SetSkin( renderEntity.customSkin ); + } + */ + return true; + } + } + return false; +} + +/*********************************************************************** + + Save / Load + +***********************************************************************/ + +/* +================ +rvWeapon::Save +================ +*/ +void rvWeapon::Save ( idSaveGame *savefile ) const { + int i; + + // Flags + savefile->Write ( &wsfl, sizeof( wsfl ) ); + savefile->Write ( &wfl, sizeof( wfl ) ); + + // Write all cached joints + savefile->WriteJoint ( barrelJointView ); + savefile->WriteJoint ( flashJointView ); + savefile->WriteJoint ( ejectJointView ); + savefile->WriteJoint ( guiLightJointView ); + savefile->WriteJoint ( flashlightJointView ); + + savefile->WriteJoint ( flashJointWorld ); + savefile->WriteJoint ( ejectJointWorld ); + savefile->WriteJoint ( flashlightJointWorld ); + + savefile->WriteInt ( status ); + savefile->WriteInt ( lastAttack ); + + // Hide / Show + savefile->WriteInt ( hideTime ); + savefile->WriteFloat ( hideDistance ); + savefile->WriteInt ( hideStartTime ); + savefile->WriteFloat ( hideStart ); + savefile->WriteFloat ( hideEnd ); + savefile->WriteFloat ( hideOffset ); + + // Write attack related values + savefile->WriteVec3 ( pushVelocity ); + savefile->WriteInt ( kick_endtime ); + savefile->WriteInt ( muzzle_kick_time ); + savefile->WriteInt ( muzzle_kick_maxtime ); + savefile->WriteAngles ( muzzle_kick_angles ); + savefile->WriteVec3 ( muzzle_kick_offset ); + savefile->WriteVec3 ( muzzleOrigin ); + savefile->WriteMat3 ( muzzleAxis ); + savefile->WriteFloat ( muzzleOffset ); + projectileEnt.Save ( savefile ); + savefile->WriteVec3 ( ejectOffset ); // cnicholson: Added unsaved var + + savefile->WriteInt ( fireRate ); + savefile->WriteFloat ( spread ); + // savefile->WriteInt ( nextAttackTime ); // cnicholson: This is set to 0 in restore, so don't save it + + // cnicholson: These 3 idDicts are setup during restore, no need to save them. + // TOSAVE: idDict attackAltDict; + // TOSAVE: idDict attackDict; + // TOSAVE: idDict brassDict; + + // Defs + // TOSAVE: const idDeclEntityDef * meleeDef; // cnicholson: This is setup in restore, so don't save it + savefile->WriteFloat ( meleeDistance ); + + // Zoom + savefile->WriteInt ( zoomFov ); + savefile->WriteUserInterface ( zoomGui, true ); + savefile->WriteFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->WriteInt( lightHandles[i] ); + savefile->WriteRenderLight( lights[i] ); + } + savefile->WriteVec3 ( guiLightOffset ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashViewOffset ); + savefile->WriteVec3 ( flashlightViewOffset ); + savefile->WriteBool ( flashlightOn ); // cnicholson: Added unsaved var + savefile->WriteVec3 ( flashlightViewOffset ); // cnicholson: Added unsaved var + + // Write ammo values + savefile->WriteInt ( ammoType ); + savefile->WriteInt ( ammoRequired ); + savefile->WriteInt ( clipSize ); + savefile->WriteInt ( ammoClip ); + savefile->WriteInt ( lowAmmo ); + savefile->WriteInt ( maxAmmo ); + + // multiplayer + savefile->WriteInt ( clipPredictTime ); // TOSAVE: Save MP value? + + // View + savefile->WriteVec3 ( playerViewOrigin ); + savefile->WriteMat3 ( playerViewAxis ); + + savefile->WriteVec3 ( viewModelOrigin ); + savefile->WriteMat3 ( viewModelAxis ); + savefile->WriteAngles ( viewModelAngles ); + savefile->WriteVec3 ( viewModelOffset ); // cnicholson: Added unsaved var + + // Offsets + savefile->WriteInt ( weaponAngleOffsetAverages ); + savefile->WriteFloat ( weaponAngleOffsetScale ); + savefile->WriteFloat ( weaponAngleOffsetMax ); + savefile->WriteFloat ( weaponOffsetTime ); + savefile->WriteFloat ( weaponOffsetScale ); + + savefile->WriteString ( icon ); + savefile->WriteBool ( isStrogg ); + + // TOSAVE: idDict spawnArgs; + + // TOSAVE: idEntityPtr viewModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* viewAnimator; + // TOSAVE: idEntityPtr worldModel; // cnicholson: Setup in restore, no need to save + // TOSAVE: idAnimator* worldAnimator; + // TOSAVE: const idDeclEntityDef* weaponDef; + // TOSAVE: idScriptObject* scriptObject; + savefile->WriteObject ( owner ); + savefile->WriteInt ( weaponIndex ); // cnicholson: Added unsaved var + savefile->WriteInt ( mods ); // cnicholson: Added unsaved var + + savefile->WriteFloat ( viewModelForeshorten ); + + stateThread.Save( savefile ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->WriteInt( animDoneTime[i] ); + } + + savefile->WriteInt ( methodOfDeath ); // cnicholson: Added unsaved var +} + +/* +================ +rvWeapon::Restore +================ +*/ +void rvWeapon::Restore ( idRestoreGame *savefile ) { + int i; + const idDeclEntityDef* def; + + // General + savefile->Read ( &wsfl, sizeof( wsfl ) ); + savefile->Read ( &wfl, sizeof( wfl ) ); + + // Read cached joints + savefile->ReadJoint ( barrelJointView ); + savefile->ReadJoint ( flashJointView ); + savefile->ReadJoint ( ejectJointView ); + savefile->ReadJoint ( guiLightJointView ); + savefile->ReadJoint ( flashlightJointView ); + + savefile->ReadJoint ( flashJointWorld ); + savefile->ReadJoint ( ejectJointWorld ); + savefile->ReadJoint ( flashlightJointWorld ); + + savefile->ReadInt ( (int&)status ); + savefile->ReadInt ( lastAttack ); + + // Hide / Show + savefile->ReadInt ( hideTime ); + savefile->ReadFloat ( hideDistance ); + savefile->ReadInt ( hideStartTime ); + savefile->ReadFloat ( hideStart ); + savefile->ReadFloat ( hideEnd ); + savefile->ReadFloat ( hideOffset ); + + // Read attack related values + savefile->ReadVec3 ( pushVelocity ); + savefile->ReadInt ( kick_endtime ); + savefile->ReadInt ( muzzle_kick_time ); + savefile->ReadInt ( muzzle_kick_maxtime ); + savefile->ReadAngles ( muzzle_kick_angles ); + savefile->ReadVec3 ( muzzle_kick_offset ); + savefile->ReadVec3 ( muzzleOrigin ); + savefile->ReadMat3 ( muzzleAxis ); + savefile->ReadFloat ( muzzleOffset ); + projectileEnt.Restore ( savefile ); + savefile->ReadVec3 ( ejectOffset ); // cnicholson: Added unrestored var + + savefile->ReadInt ( fireRate ); + savefile->ReadFloat ( spread ); + nextAttackTime = 0; + + // Attack Alt Def + attackAltDict.Clear( ); + wfl.attackAltHitscan = false; + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_altprojectile" ), false ); + if ( def ) { + attackAltDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_althitscan" ), false ); + if ( def ) { + attackAltDict = def->dict; + wfl.attackAltHitscan = true; + } + } + + // Attack def + attackDict.Clear( ); + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_projectile" ), false ); + wfl.attackHitscan = false; + if ( def ) { + attackDict = def->dict; + } else { + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_hitscan" ), false ); + if ( def ) { + attackDict = def->dict; + wfl.attackHitscan = true; + } + } + + // Brass Def + def = gameLocal.FindEntityDef( spawnArgs.GetString( "def_ejectBrass" ), false ); + if ( def ) { + brassDict = def->dict; + } else { + brassDict.Clear(); + } + + // Melee Def + meleeDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_melee" ), false ); + savefile->ReadFloat( meleeDistance ); + + // Zoom + savefile->ReadInt ( zoomFov ); + savefile->ReadUserInterface ( zoomGui, &spawnArgs ); + savefile->ReadFloat ( zoomTime ); + + // Lights + for ( i = 0; i < WPLIGHT_MAX; i ++ ) { + savefile->ReadInt ( lightHandles[i] ); + savefile->ReadRenderLight( lights[i] ); + if ( lightHandles[i] != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandles[i] = gameRenderWorld->AddLightDef ( &lights[i] ); + } + } + savefile->ReadVec3 ( guiLightOffset ); + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashViewOffset ); + savefile->ReadVec3 ( flashlightViewOffset ); + savefile->ReadBool ( flashlightOn ); // cnicholson: Added unrestored var + savefile->ReadVec3 ( flashlightViewOffset ); // cnicholson: Added unrestored var + + // Read the ammo values + savefile->ReadInt ( (int&)ammoType ); + savefile->ReadInt ( ammoRequired ); + savefile->ReadInt ( clipSize ); + savefile->ReadInt ( ammoClip ); + savefile->ReadInt ( lowAmmo ); + savefile->ReadInt ( maxAmmo ); + + // multiplayer + savefile->ReadInt ( clipPredictTime ); // TORESTORE: Restore MP value? + + // View + savefile->ReadVec3 ( playerViewOrigin ); + savefile->ReadMat3 ( playerViewAxis ); + + savefile->ReadVec3 ( viewModelOrigin ); + savefile->ReadMat3 ( viewModelAxis ); + savefile->ReadAngles ( viewModelAngles ); + savefile->ReadVec3 ( viewModelOffset ); // cnicholson: Added unrestored var + + // Offsets + savefile->ReadInt ( weaponAngleOffsetAverages ); + savefile->ReadFloat ( weaponAngleOffsetScale ); + savefile->ReadFloat ( weaponAngleOffsetMax ); + savefile->ReadFloat ( weaponOffsetTime ); + savefile->ReadFloat ( weaponOffsetScale ); + + savefile->ReadString ( icon ); + savefile->ReadBool ( isStrogg ); + + // TORESTORE: idDict spawnArgs; + + // TORESTORE: idAnimator* viewAnimator; + // TORESTORE: idAnimator* worldAnimator; + // TORESTORE: const idDeclEntityDef* weaponDef; + // TORESTORE: idScriptObject* scriptObject; + + // Entities + savefile->ReadObject( reinterpret_cast( owner ) ); + viewModel = owner->GetWeaponViewModel ( ); + worldModel = owner->GetWeaponWorldModel ( ); + + savefile->ReadInt ( weaponIndex ); // cnicholson: Added unrestored var + savefile->ReadInt ( mods ); // cnicholson: Added unrestored var + + savefile->ReadFloat ( viewModelForeshorten ); + + stateThread.Restore( savefile, this ); + + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + savefile->ReadInt( animDoneTime[i] ); + } + + savefile->ReadInt ( methodOfDeath ); // cnicholson: Added unrestored var + +#ifdef _XENON + aimAssistFOV = spawnArgs.GetFloat( "aimAssistFOV", "10.0f" ); +#endif +} + +/*********************************************************************** + + State control/player interface + +***********************************************************************/ + +/* +================ +rvWeapon::Hide +================ +*/ +void rvWeapon::Hide( void ) { + muzzleFlashEnd = 0; + + if ( viewModel ) { + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide ( ); + } + + // Stop flashlight and gui lights + FreeLight ( WPLIGHT_GUI ); + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::Show +================ +*/ +void rvWeapon::Show ( void ) { + if ( viewModel ) { + viewModel->Show(); + } + if ( worldModel ) { + worldModel->Show(); + } +} + +/* +================ +rvWeapon::IsHidden +================ +*/ +bool rvWeapon::IsHidden( void ) const { + return !viewModel || viewModel->IsHidden(); +} + +/* +================ +rvWeapon::HideWorldModel +================ +*/ +void rvWeapon::HideWorldModel ( void ) { + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::ShowWorldModel +================ +*/ +void rvWeapon::ShowWorldModel ( void ) { + if ( worldModel ) { + worldModel->Show(); + } +} + + +/* +================ +rvWeapon::LowerWeapon +================ +*/ +void rvWeapon::LowerWeapon( void ) { + if ( !wfl.hide ) { + hideStart = 0.0f; + hideEnd = hideDistance; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = true; + } +} + +/* +================ +rvWeapon::RaiseWeapon +================ +*/ +void rvWeapon::RaiseWeapon( void ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->Show(); + + if ( forceGUIReload ) { + forceGUIReload = false; + int ammo = AmmoInClip(); + for ( int g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + idUserInterface* gui = viewModel->GetRenderEntity()->gui[g]; + if ( gui ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + } + + if ( wfl.hide ) { + hideStart = hideDistance; + hideEnd = 0.0f; + if ( gameLocal.time - hideStartTime < hideTime ) { + hideStartTime = gameLocal.time - ( hideTime - ( gameLocal.time - hideStartTime ) ); + } else { + hideStartTime = gameLocal.time; + } + wfl.hide = false; + } +} + +/* +================ +rvWeapon::PutAway +================ +*/ +void rvWeapon::PutAway( void ) { + wfl.hasBloodSplat = false; + wsfl.lowerWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_lower" ); +} + +/* +================ +rvWeapon::Raise +================ +*/ +void rvWeapon::Raise( void ) { + wsfl.raiseWeapon = true; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->PostGUIEvent ( "weapon_raise" ); +} + +/* +================ +rvWeapon::Flashlight +================ +*/ +void rvWeapon::Flashlight ( void ) { + wsfl.flashlight = true; +} + +/* +================ +rvWeapon::SetPushVelocity +================ +*/ +void rvWeapon::SetPushVelocity( const idVec3& _pushVelocity ) { + pushVelocity = _pushVelocity; +} + +/* +================ +rvWeapon::Reload +NOTE: this is only for impulse-triggered reload, auto reload is scripted +================ +*/ +void rvWeapon::Reload( void ) { + if ( clipSize ) { + wsfl.reload = true; + } +} + +/* +================ +rvWeapon::CancelReload +================ +*/ +void rvWeapon::CancelReload( void ) { + wsfl.attack = true; +} + +/* +================ +rvWeapon::AutoReload +================ +*/ +bool rvWeapon::AutoReload ( void ) { + assert( owner ); + + // on a network client, never predict reloads of other clients. wait for the server + if ( gameLocal.isClient ) { + return false; + } + return gameLocal.userInfo[ owner->entityNumber ].GetBool( "ui_autoReload" ); +} + +/* +================ +rvWeapon::NetReload +================ +*/ +void rvWeapon::NetReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_RELOAD, NULL, false, -1 ); + } +} + +/* +=============== +rvWeapon::NetEndReload +=============== +*/ +void rvWeapon::NetEndReload ( void ) { + assert( owner ); + if ( gameLocal.isServer ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + viewModel->ServerSendEvent( EVENT_ENDRELOAD, NULL, false, -1 ); + } +} + +/* +================ +rvWeapon::SetStatus +================ +*/ +void rvWeapon::SetStatus ( weaponStatus_t _status ) { + status = _status; + switch ( status ) { + case WP_READY: + wsfl.raiseWeapon = false; + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_ready" ); + break; + case WP_OUTOFAMMO: + wsfl.raiseWeapon = false; + break; + case WP_RELOAD: + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + break; + } + viewModel->PostGUIEvent ( "weapon_reload" ); + break; + case WP_HOLSTERED: + case WP_RISING: + wsfl.lowerWeapon = false; + owner->WeaponRisingCallback(); + break; + case WP_LOWERING: + wsfl.raiseWeapon = false; + owner->WeaponLoweringCallback(); + break; + } +} + +/* +================ +rvWeapon::OwnerDied +================ +*/ +void rvWeapon::OwnerDied( void ) { + + CleanupWeapon(); + + ExecuteState( "OwnerDied" ); + + if ( viewModel ) { + viewModel->StopSound( SCHANNEL_ANY, false ); + viewModel->StopAllEffects( ); + viewModel->Hide(); + } + if ( worldModel ) { + worldModel->Hide(); + } +} + +/* +================ +rvWeapon::BeginAttack +================ +*/ +void rvWeapon::BeginAttack( void ) { + wsfl.attack = true; + + if ( status != WP_OUTOFAMMO ) { + lastAttack = gameLocal.time; + } +} + +/* +================ +rvWeapon::EndAttack +================ +*/ +void rvWeapon::EndAttack( void ) { + wsfl.attack = false; +} + +/* +================ +rvWeapon::isReady +================ +*/ +bool rvWeapon::IsReady( void ) const { + return !wfl.hide && ! ( gameLocal.time - hideStartTime < hideTime ) && ( viewModel && !viewModel->IsHidden()) && ( ( status == WP_READY ) || ( status == WP_OUTOFAMMO ) ); +} + +/* +================ +rvWeapon::IsReloading +================ +*/ +bool rvWeapon::IsReloading( void ) const { + return ( status == WP_RELOAD ); +} + +/* +================ +rvWeapon::IsHolstered +================ +*/ +bool rvWeapon::IsHolstered( void ) const { + return ( status == WP_HOLSTERED ); +} + +/* +================ +rvWeapon::ShowCrosshair +================ +*/ +bool rvWeapon::ShowCrosshair( void ) const { + if ( owner->IsZoomed ( ) && zoomGui && wfl.zoomHideCrosshair ) { + return false; + } + return !( status == WP_HOLSTERED ); +} + +/* +===================== +rvWeapon::CanDrop +===================== +*/ +bool rvWeapon::CanDrop( void ) const { + const char *classname = spawnArgs.GetString( "def_dropItem" ); + if ( !classname[ 0 ] ) { + return false; + } + return true; +} + +/* +===================== +rvViewWeapon::CanZoom +===================== +*/ +bool rvWeapon::CanZoom( void ) const { +#ifdef _XENON + // apparently a xenon specific bug in medlabs. + return zoomFov != -1 && !IsHidden(); +#else + return zoomFov != -1; +#endif +} + +/*********************************************************************** + + Visual presentation + +***********************************************************************/ + +/* +================ +rvWeapon::MuzzleRise +================ +*/ +void rvWeapon::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + int time; + float amount; + idAngles ang; + idVec3 offset; + + time = kick_endtime - gameLocal.time; + if ( time <= 0 ) { + return; + } + + if ( muzzle_kick_maxtime <= 0 ) { + return; + } + + if ( time > muzzle_kick_maxtime ) { + time = muzzle_kick_maxtime; + } + + amount = ( float )time / ( float )muzzle_kick_maxtime; + ang = muzzle_kick_angles * amount; + offset = muzzle_kick_offset * amount; + + origin = origin - axis * offset; + axis = ang.ToMat3() * axis; +} + +/* +================ +rvWeapon::UpdateFlashPosition +================ +*/ +void rvWeapon::UpdateMuzzleFlash ( void ) { + // remove the muzzle flash light when it's done + if ( gameLocal.time >= muzzleFlashEnd || !gameLocal.GetLocalPlayer() || !owner || gameLocal.GetLocalPlayer()->GetInstance() != owner->GetInstance() ) { + FreeLight ( WPLIGHT_MUZZLEFLASH ); + FreeLight ( WPLIGHT_MUZZLEFLASH_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + light.origin = playerViewOrigin + (playerViewAxis * muzzleFlashViewOffset); + light.axis = playerViewAxis; + + // put the world muzzle flash on the end of the joint, no matter what + GetGlobalJointTransform( false, flashJointWorld, lightWorld.origin, lightWorld.axis ); + + UpdateLight ( WPLIGHT_MUZZLEFLASH ); + UpdateLight ( WPLIGHT_MUZZLEFLASH_WORLD ); +} + +/* +================ +rvWeapon::UpdateFlashlight +================ +*/ +void rvWeapon::UpdateFlashlight ( void ) { + // Turn flashlight off? + if (! owner->IsFlashlightOn ( ) ) { + FreeLight ( WPLIGHT_FLASHLIGHT ); + FreeLight ( WPLIGHT_FLASHLIGHT_WORLD ); + return; + } + + renderLight_t& light = lights[WPLIGHT_FLASHLIGHT]; + renderLight_t& lightWorld = lights[WPLIGHT_FLASHLIGHT_WORLD]; + trace_t tr; + + // the flash has an explicit joint for locating it + GetGlobalJointTransform( true, flashlightJointView, light.origin, light.axis, flashlightViewOffset ); + + // if the desired point is inside or very close to a wall, back it up until it is clear + gameLocal.TracePoint( owner, tr, light.origin - playerViewAxis[0] * 8.0f, light.origin, MASK_SHOT_BOUNDINGBOX, owner ); + + // be at least 8 units away from a solid + light.origin = tr.endpos - (tr.fraction < 1.0f ? (playerViewAxis[0] * 8) : vec3_origin); + + // put the world muzzle flash on the end of the joint, no matter what + if ( flashlightJointWorld != INVALID_JOINT ) { + GetGlobalJointTransform( false, flashlightJointWorld, lightWorld.origin, lightWorld.axis ); + } else { + lightWorld.origin = playerViewOrigin + playerViewAxis[0] * 20.0f; + lightWorld.axis = playerViewAxis; + } + + UpdateLight ( WPLIGHT_FLASHLIGHT ); + UpdateLight ( WPLIGHT_FLASHLIGHT_WORLD ); +} + +/* +================ +rvWeapon::MuzzleFlash +================ +*/ +void rvWeapon::MuzzleFlash ( void ) { + renderLight_t& light = lights[WPLIGHT_MUZZLEFLASH]; + renderLight_t& lightWorld = lights[WPLIGHT_MUZZLEFLASH_WORLD]; + + if ( !g_muzzleFlash.GetBool() || flashJointView == INVALID_JOINT || !light.lightRadius[0] ) { + return; + } + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( viewModel ) { + // these will be different each fire + light.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + light.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + light.noShadows = true; + + lightWorld.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + lightWorld.shaderParms[ SHADERPARM_DIVERSITY ] = viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + lightWorld.noShadows = true; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } + UpdateMuzzleFlash ( ); +} + + +/* +================ +rvWeapon::UpdateGUI +================ +*/ +void rvWeapon::UpdateGUI( void ) { + idUserInterface* gui; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + gui = viewModel->GetRenderEntity()->gui[0]; + if ( !gui || status == WP_HOLSTERED ) { + return; + } + int g; +for ( g = 0; g < MAX_RENDERENTITY_GUI && viewModel->GetRenderEntity()->gui[g]; g ++ ) { + gui = viewModel->GetRenderEntity()->gui[g]; + + if ( gameLocal.localClientNum != owner->entityNumber ) { + // if updating the hud for a followed client + idPlayer *p = gameLocal.GetLocalPlayer(); + if ( !p ) { + return; + } + if ( !p->spectating || p->spectator != owner->entityNumber ) { + return; + } + } + + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + // show remaining ammo + if ( gui->State().GetInt ( "player_cachedammo", "-1") != ammo ) { + gui->SetStateInt ( "player_ammo", ammo ); + + if ( ClipSize ( ) ) { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)ClipSize() ); + gui->SetStateInt ( "player_clip_size", ClipSize() ); + } else { + gui->SetStateFloat ( "player_ammopct", (float)ammo / (float)maxAmmo ); + gui->SetStateInt ( "player_clip_size", maxAmmo ); + } + gui->SetStateInt ( "player_cachedammo", ammo ); + gui->HandleNamedEvent ( "weapon_ammo" ); + } + } + +// viewModel->GetRenderEntity()->gui[g]->SetStateInt ( "player_clip_size", ClipSize() ); +} + for ( int i = 0; i < viewModel->pendingGUIEvents.Num(); i ++ ) { + gui->HandleNamedEvent( viewModel->pendingGUIEvents[i] ); + } + viewModel->pendingGUIEvents.Clear(); +} + +/* +================ +rvWeapon::UpdateCrosshairGUI +================ +*/ +void rvWeapon::UpdateCrosshairGUI( idUserInterface* gui ) const { +// RAVEN BEGIN +// cnicholson: Added support for universal crosshair + + // COMMENTED OUT until Custom crosshair GUI is implemented. + if ( g_crosshairCustom.GetBool() ) { // If there's a custom crosshair, use it. + gui->SetStateString( "crossImage", g_crosshairCustomFile.GetString()); + + const idMaterial *material = declManager->FindMaterial( g_crosshairCustomFile.GetString() ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } else { + gui->SetStateString( "crossImage", spawnArgs.GetString( "mtr_crosshair" ) ); + + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + } + +// Original Block + //gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); +// RAVEN END + gui->SetStateString( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt( "crossOffsetX", spawnArgs.GetInt( "crosshairOffsetX", "0" ) ); + gui->SetStateInt( "crossOffsetY", spawnArgs.GetInt( "crosshairOffsetY", "0" ) ); + gui->StateChanged( gameLocal.time ); +} + +/* +================ +rvWeapon::ForeshortenAxis +================ +*/ +idMat3 rvWeapon::ForeshortenAxis( const idMat3& axis ) const { + return idMat3( axis[0] * viewModelForeshorten, axis[1], axis[2] ); +} + +/* +================ +rvWeapon::GetAngleOffsets +================ +*/ +void rvWeapon::GetAngleOffsets ( int *average, float *scale, float *max ) { + *average = weaponAngleOffsetAverages; + *scale = weaponAngleOffsetScale; + *max = weaponAngleOffsetMax; +} + +/* +================ +rvWeapon::GetTimeOffsets +================ +*/ +void rvWeapon::GetTimeOffsets ( float *time, float *scale ) { + *time = weaponOffsetTime; + *scale = weaponOffsetScale; +} + +/* +================ +rvWeapon::GetGlobalJointTransform + +This returns the offset and axis of a weapon bone in world space, suitable for attaching models or lights +================ +*/ +bool rvWeapon::GetGlobalJointTransform ( bool view, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset ) { + if ( view) { + // view model + if ( viewModel && viewAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = origin * ForeshortenAxis(viewModelAxis) + viewModelOrigin; + axis = axis * viewModelAxis; + return true; + } + } else { + // world model + if ( worldModel && worldAnimator->GetJointTransform( jointHandle, gameLocal.time, origin, axis ) ) { + origin = offset * axis + origin; + origin = worldModel->GetPhysics()->GetOrigin() + origin * worldModel->GetPhysics()->GetAxis(); + axis = axis * worldModel->GetPhysics()->GetAxis(); + return true; + } + } + origin = viewModelOrigin + offset * viewModelAxis; + axis = viewModelAxis; + return false; +} + +/*********************************************************************** + + Ammo + +***********************************************************************/ + +/* +================ +rvWeapon::GetAmmoIndexForName +================ +*/ +int rvWeapon::GetAmmoIndexForName( const char *ammoname ) { + int num; + const idDict *ammoDict; + + assert( ammoname ); + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + if ( !ammoname[ 0 ] ) { + return 0; + } + + if ( !ammoDict->GetInt( ammoname, "-1", num ) ) { + gameLocal.Error( "Unknown ammo type '%s'", ammoname ); + } + + if ( ( num < 0 ) || ( num >= MAX_AMMOTYPES ) ) { + gameLocal.Error( "Ammo type '%s' value out of range. Maximum ammo types is %d.\n", ammoname, MAX_AMMOTYPES ); + } + + return num; +} + +/* +================ +rvWeapon::GetAmmoNameForNum +================ +*/ +const char* rvWeapon::GetAmmoNameForIndex( int index ) { + int i; + int num; + const idDict *ammoDict; + const idKeyValue *kv; + char text[ 32 ]; + + ammoDict = gameLocal.FindEntityDefDict( "ammo_types", false ); + if ( !ammoDict ) { + gameLocal.Error( "Could not find entity definition for 'ammo_types'\n" ); + } + + sprintf( text, "%d", index ); + + num = ammoDict->GetNumKeyVals(); + for( i = 0; i < num; i++ ) { + kv = ammoDict->GetKeyVal( i ); + if ( kv->GetValue() == text ) { + return kv->GetKey(); + } + } + + return NULL; +} + +/* +================ +rvWeapon::TotalAmmoCount +================ +*/ +int rvWeapon::TotalAmmoCount ( void ) const { + return owner->inventory.HasAmmo( ammoType, 1 ); +} + +/* +================ +rvWeapon::AmmoAvailable +================ +*/ +int rvWeapon::AmmoAvailable( void ) const { + if ( owner ) { + return owner->inventory.HasAmmo( ammoType, ammoRequired ); + } else { + return 0; + } +} + +/* +================ +rvWeapon::AmmoInClip +================ +*/ +int rvWeapon::AmmoInClip( void ) const { + if ( !clipSize ) { + return AmmoAvailable(); + } + return ammoClip; +} + +/* +================ +rvWeapon::ResetAmmoClip +================ +*/ +void rvWeapon::ResetAmmoClip( void ) { + ammoClip = -1; +} + +/* +================ +rvWeapon::GetAmmoType +================ +*/ +int rvWeapon::GetAmmoType( void ) const { + return ammoType; +} + +/* +================ +rvWeapon::ClipSize +================ +*/ +int rvWeapon::ClipSize( void ) const { + return clipSize; +} + +/* +================ +rvWeapon::LowAmmo +================ +*/ +int rvWeapon::LowAmmo() const { + return lowAmmo; +} + +/* +================ +rvWeapon::AmmoRequired +================ +*/ +int rvWeapon::AmmoRequired( void ) const { + return ammoRequired; +} + +/* +================ +rvWeapon::SetClip +================ +*/ +void rvWeapon::SetClip ( int amount ) { + ammoClip = amount; + if ( amount < 0 ) { + ammoClip = 0; + } else if ( amount > clipSize ) { + ammoClip = clipSize; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/* +================ +rvWeapon::UseAmmo +================ +*/ +void rvWeapon::UseAmmo( int amount ) { + owner->inventory.UseAmmo( ammoType, amount * ammoRequired ); + if ( clipSize && ammoRequired ) { + ammoClip -= ( amount * ammoRequired ); + if ( ammoClip < 0 ) { + ammoClip = 0; + } + } +} + +/* +================ +rvWeapon::AddToClip +================ +*/ +void rvWeapon::AddToClip ( int amount ) { + int ammoAvail; + + if ( gameLocal.isClient ) { + return; + } + + ammoClip += amount; + if ( ammoClip > clipSize ) { + ammoClip = clipSize; + } + + ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( ammoAvail > 0 && ammoClip > ammoAvail ) { + ammoClip = ammoAvail; + } + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } +} + +/*********************************************************************** + + Attack + +***********************************************************************/ + + +/* +================ +rvWeapon::Attack +================ +*/ +void rvWeapon::Attack( bool altAttack, int num_attacks, float spread, float fuseOffset, float power ) { + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + if ( viewModel->IsHidden() ) { + return; + } + + // avoid all ammo considerations on an MP client + if ( !gameLocal.isClient ) { + // check if we're out of ammo or the clip is empty + int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + if ( !ammoAvail || ( ( clipSize != 0 ) && ( ammoClip <= 0 ) ) ) { + return; + } + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + if ( clipSize && ammoRequired ) { + clipPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + ammoClip -= 1; + } + + // wake up nearby monsters + if ( !wfl.silent_fire ) { + gameLocal.AlertAI( owner ); + } + } + + // set the shader parm to the time of last projectile firing, + // which the gun material shaders can reference for single shot barrel glows, etc + viewModel->SetShaderParm ( SHADERPARM_DIVERSITY, gameLocal.random.CRandomFloat() ); + viewModel->SetShaderParm ( SHADERPARM_TIMEOFFSET, -MS2SEC( gameLocal.realClientTime ) ); + + if ( worldModel.GetEntity() ) { + worldModel->SetShaderParm( SHADERPARM_DIVERSITY, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ] ); + worldModel->SetShaderParm( SHADERPARM_TIMEOFFSET, viewModel->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] ); + } + + // calculate the muzzle position + if ( barrelJointView != INVALID_JOINT && spawnArgs.GetBool( "launchFromBarrel" ) ) { + // there is an explicit joint for the muzzle + GetGlobalJointTransform( true, barrelJointView, muzzleOrigin, muzzleAxis ); + } else { + // go straight out of the view + muzzleOrigin = playerViewOrigin; + muzzleAxis = playerViewAxis; + muzzleOrigin += playerViewAxis[0] * muzzleOffset; + } + + // add some to the kick time, incrementally moving repeat firing weapons back + if ( kick_endtime < gameLocal.realClientTime ) { + kick_endtime = gameLocal.realClientTime; + } + kick_endtime += muzzle_kick_time; + if ( kick_endtime > gameLocal.realClientTime + muzzle_kick_maxtime ) { + kick_endtime = gameLocal.realClientTime + muzzle_kick_maxtime; + } + + // add the muzzleflash + MuzzleFlash(); + + // quad damage overlays a sound + if ( owner->PowerUpActive( POWERUP_QUADDAMAGE ) ) { + viewModel->StartSound( "snd_quaddamage", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + // Muzzle flash effect + bool muzzleTint = spawnArgs.GetBool( "muzzleTint" ); + viewModel->PlayEffect( "fx_muzzleflash", flashJointView, false, vec3_origin, false, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict, "fx_muzzleflash_world" ), flashJointWorld, vec3_origin, mat3_identity, false, vec3_origin, false, false, EC_IGNORE, muzzleTint ? owner->GetHitscanTint() : vec4_one ); + } + + owner->WeaponFireFeedback( &weaponDef->dict ); + + // Inform the gui of the ammo change + viewModel->PostGUIEvent ( "weapon_ammo" ); + if ( ammoClip == 0 && AmmoAvailable() == 0 ) { + viewModel->PostGUIEvent ( "weapon_noammo" ); + } + + // The attack is either a hitscan or a launched projectile, do that now. + if ( !gameLocal.isClient ) { + idDict& dict = altAttack ? attackAltDict : attackDict; + power *= owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ); + if ( altAttack ? wfl.attackAltHitscan : wfl.attackHitscan ) { + Hitscan( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, power ); + } else { + LaunchProjectiles( dict, muzzleOrigin, muzzleAxis, num_attacks, spread, fuseOffset, power ); + } + //asalmon: changed to keep stats even in single player + statManager->WeaponFired( owner, weaponIndex, num_attacks ); + + } +} + +/* +================ +rvWeapon::LaunchProjectiles +================ +*/ +void rvWeapon::LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ) { + idProjectile* proj; + idEntity* ent; + int i; + float spreadRad; + idVec3 dir; + idBounds ownerBounds; + + if ( gameLocal.isClient ) { + return; + } + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack ( owner, muzzleOrigin, muzzleAxis[0] ); + } + + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + spreadRad = DEG2RAD( spread ); + + idVec3 dirOffset; + idVec3 startOffset; + + spawnArgs.GetVector( "dirOffset", "0 0 0", dirOffset ); + spawnArgs.GetVector( "startOffset", "0 0 0", startOffset ); + + for( i = 0; i < num_projectiles; i++ ) { + float ang; + float spin; + idVec3 dir; + idBounds projBounds; + idVec3 muzzle_pos; + + // Calculate a random launch direction based on the spread + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir += dirOffset; + dir.Normalize(); + + // If a projectile entity has already been created then use that one, otherwise + // spawn a new one based on the given dictionary + if ( projectileEnt ) { + ent = projectileEnt; + ent->Show(); + ent->Unbind(); + projectileEnt = NULL; + } else { + dict.SetInt( "instance", owner->GetInstance() ); + gameLocal.SpawnEntityDef( dict, &ent, false ); + } + + // Make sure it spawned + if ( !ent ) { + gameLocal.Error( "failed to spawn projectile for weapon '%s'", weaponDef->GetName ( ) ); + } + + assert ( ent->IsType( idProjectile::GetClassType() ) ); + + // Create the projectile + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin + startOffset, dir, NULL, owner->extraProjPassEntity ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + idVec3 start; + float distance; + trace_t tr; +//RAVEN BEGIN +//asalmon: xbox must use muzzle Axis for aim assistance +#ifdef _XBOX + muzzle_pos = muzzleOrigin + muzzleAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, muzzleAxis[0], distance ) ) { + start = muzzle_pos + distance * muzzleAxis[0]; + } +#else + muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } +#endif +//RAVEN END + else { + start = ownerBounds.GetCenter(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( owner, tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + muzzle_pos = tr.endpos; + } + + // Launch the actual projectile + proj->Launch( muzzle_pos + startOffset, dir, pushVelocity, fuseOffset, power ); + + // Increment the projectile launch count and let the derived classes + // mess with it if they want. + OnLaunchProjectile ( proj ); + } +} + +/* +================ +rvWeapon::OnLaunchProjectile +================ +*/ +void rvWeapon::OnLaunchProjectile ( idProjectile* proj ) { + owner->AddProjectilesFired( 1 ); + if ( proj ) { + proj->methodOfDeath = owner->GetCurrentWeapon(); + } +} + +/* +================ +rvWeapon::Hitscan +================ +*/ +void rvWeapon::Hitscan( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ) { + idVec3 fxOrigin; + idMat3 fxAxis; + int i; + float ang; + float spin; + idVec3 dir; + int areas[ 2 ]; + + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + aiManager.ReactToPlayerAttack( owner, muzzleOrigin, muzzleAxis[0] ); + } + + GetGlobalJointTransform( true, flashJointView, fxOrigin, fxAxis, dict.GetVector( "fxOriginOffset" ) ); + + if ( gameLocal.isServer ) { + + assert( hitscanAttackDef >= 0 ); + assert( owner && owner->entityNumber < MAX_CLIENTS ); + int ownerId = owner ? owner->entityNumber : 0; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + msg.WriteByte( GAME_UNRELIABLE_MESSAGE_HITSCAN ); + msg.WriteLong( hitscanAttackDef ); + msg.WriteBits( ownerId, idMath::BitsForInteger( MAX_CLIENTS ) ); + msg.WriteFloat( muzzleOrigin[0] ); + msg.WriteFloat( muzzleOrigin[1] ); + msg.WriteFloat( muzzleOrigin[2] ); + msg.WriteFloat( fxOrigin[0] ); + msg.WriteFloat( fxOrigin[1] ); + msg.WriteFloat( fxOrigin[2] ); + } + + float spreadRad = DEG2RAD( spread ); + idVec3 end; + for( i = 0; i < num_hitscans; i++ ) { + if( weaponDef->dict.GetBool( "machinegunSpreadStyle" ) ) { + float r = gameLocal.random.RandomFloat() * idMath::PI * 2.0f; + float u = idMath::Sin( r ) * gameLocal.random.CRandomFloat() * spread * 16; + r = idMath::Cos( r ) * gameLocal.random.CRandomFloat() * spread * 16; +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else if( weaponDef->dict.GetBool( "shotgunSpreadStyle" ) ) { + int radius; + float angle; + + // this may look slightly odd, but ensures with an odd number of pellets we get + // two complete circles, and the outer one gets the extra hit + int circleHitscans = num_hitscans - (num_hitscans / 2); + if (i < circleHitscans) + { + radius = spread * 14; + angle = i * (idMath::TWO_PI / circleHitscans); + } + else + { + radius = spread * 6; + angle = (i - circleHitscans) * (idMath::TWO_PI / num_hitscans) * 2; + } + + float r = radius * (idMath::Cos(angle) + (gameLocal.random.CRandomFloat() * 0.2f)); + float u = radius * (idMath::Sin(angle) + (gameLocal.random.CRandomFloat() * 0.2f)); + +#ifdef _XBOX + end = muzzleOrigin + ( ( 8192 * 16 ) * muzzleAxis[ 0 ] ); + end += ( r * muzzleAxis[ 1 ] ); + end += ( u * muzzleAxis[ 2 ] ); +#else + end = muzzleOrigin + ( ( 8192 * 16 ) * playerViewAxis[ 0 ] ); + end += ( r * playerViewAxis[ 1 ] ); + end += ( u * playerViewAxis[ 2 ] ); +#endif + dir = end - muzzleOrigin; + } else { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + //RAVEN BEGIN + //asalmon: xbox must use the muzzleAxis so the aim can be adjusted for aim assistance +#ifdef _XBOX + dir = muzzleAxis[ 0 ] + muzzleAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - muzzleAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#else + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); +#endif + //RAVEN END + } + dir.Normalize(); + + gameLocal.HitScan( dict, muzzleOrigin, dir, fxOrigin, owner, false, 1.0f, NULL, areas ); + + if ( gameLocal.isServer ) { + msg.WriteDir( dir, 24 ); + if ( i == num_hitscans - 1 ) { + // NOTE: we emit to the areas of the last hitscan + // there is a remote possibility that multiple hitscans for shotgun would cover more than 2 areas, + // so in some rare case a client might miss it + gameLocal.SendUnreliableMessagePVS( msg, owner, areas[0], areas[1] ); + } + } + } +} + +/* +================ +rvWeapon::AlertMonsters +================ +*/ +void rvWeapon::AlertMonsters( void ) { + trace_t tr; + idEntity *ent; + idVec3 end; + renderLight_t& muzzleFlash = lights[WPLIGHT_MUZZLEFLASH]; + + end = muzzleFlash.origin + muzzleFlash.axis * muzzleFlash.target; + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } + + // jitter the trace to try to catch cases where a trace down the center doesn't hit the monster + end += muzzleFlash.axis * muzzleFlash.right * idMath::Sin16( MS2SEC( gameLocal.time ) * 31.34f ); + end += muzzleFlash.axis * muzzleFlash.up * idMath::Sin16( MS2SEC( gameLocal.time ) * 12.17f ); + gameLocal.TracePoint( owner, tr, muzzleFlash.origin, end, CONTENTS_OPAQUE | MASK_SHOT_RENDERMODEL | CONTENTS_FLASHLIGHT_TRIGGER, owner ); + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine( colorYellow, muzzleFlash.origin, end, 0 ); + gameRenderWorld->DebugArrow( colorGreen, muzzleFlash.origin, tr.endpos, 2, 0 ); + } + + if ( tr.fraction < 1.0f ) { + ent = gameLocal.GetTraceEntity( tr ); + if ( ent->IsType( idAI::GetClassType() ) ) { + static_cast( ent )->TouchedByFlashlight( owner ); + } else if ( ent->IsType( idTrigger::GetClassType() ) ) { + ent->Signal( SIG_TOUCH ); + ent->ProcessEvent( &EV_Touch, owner, &tr ); + } + } +} + +/* +================ +rvWeapon::EjectBrass +================ +*/ +void rvWeapon::EjectBrass ( void ) { + if ( g_brassTime.GetFloat() <= 0.0f || !owner->CanShowWeaponViewmodel() ) { + return; + } + + if ( g_perfTest_weaponNoFX.GetBool() ) { + return; + } + + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( ejectJointView == INVALID_JOINT || !brassDict.GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !GetGlobalJointTransform( true, ejectJointView, origin, axis ) ) { + return; + } + + // Spawn the client side moveable for the brass + rvClientMoveable* cent = NULL; + + gameLocal.SpawnClientEntityDef( brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOwner( GetOwner() ); + cent->SetOrigin ( origin + playerViewAxis * ejectOffset ); + cent->SetAxis ( playerViewAxis ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = GetOwner()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 2.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict.GetVector("linear_velocity").Random( brassDict.GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( GetOwner()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict.GetAngles("angular_velocity").Random( brassDict.GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/* +================ +rvWeapon::BloodSplat +================ +*/ +bool rvWeapon::BloodSplat( float size ) { + float s, c; + idMat3 localAxis, axistemp; + idVec3 localOrigin, normal; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + + if ( wfl.hasBloodSplat ) { + return true; + } + + wfl.hasBloodSplat = true; + + if ( viewModel->modelDefHandle < 0 ) { + return false; + } + + if ( !GetGlobalJointTransform( true, ejectJointView, localOrigin, localAxis ) ) { + return false; + } + + localOrigin[0] += gameLocal.random.RandomFloat() * -10.0f; + localOrigin[1] += gameLocal.random.RandomFloat() * 1.0f; + localOrigin[2] += gameLocal.random.RandomFloat() * -2.0f; + + normal = idVec3( gameLocal.random.CRandomFloat(), -gameLocal.random.RandomFloat(), -1 ); + normal.Normalize(); + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + localAxis[2] = -normal; + localAxis[2].NormalVectors( axistemp[0], axistemp[1] ); + localAxis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + localAxis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + localAxis[0] *= 1.0f / size; + localAxis[1] *= 1.0f / size; + + idPlane localPlane[2]; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -(localOrigin * localAxis[0]) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -(localOrigin * localAxis[1]) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( "textures/decals/duffysplatgun" ); + + gameRenderWorld->ProjectOverlay( viewModel->modelDefHandle, localPlane, mtr ); + + return true; +} + +/* +================ +rvWeapon::EnterCinematic +================ +*/ +void rvWeapon::EnterCinematic( void ) { + if( viewModel ) { + viewModel->StopSound( SND_CHANNEL_ANY, false ); + } + ExecuteState( "EnterCinematic" ); + + memset( &wsfl, 0, sizeof(wsfl) ); + + wfl.disabled = true; + + LowerWeapon(); +} + +/* +================ +rvWeapon::ExitCinematic +================ +*/ +void rvWeapon::ExitCinematic( void ) { + wfl.disabled = false; + ExecuteState ( "ExitCinematic" ); + RaiseWeapon(); +} + +/* +================ +rvWeapon::NetCatchup +================ +*/ +void rvWeapon::NetCatchup( void ) { + ExecuteState ( "NetCatchup" ); +} + +/* +=============== +rvWeapon::PlayAnim +=============== +*/ +void rvWeapon::PlayAnim( int channel, const char *animname, int blendFrames ) { + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + int anim; + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->PlayAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::PlayCycle +=============== +*/ +void rvWeapon::PlayCycle( int channel, const char *animname, int blendFrames ) { + int anim; + + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return; + } + + anim = viewAnimator->GetAnim( animname ); + if ( !anim ) { + gameLocal.Warning( "missing '%s' animation on '%s' (%s)", animname, viewModel->GetName(), viewModel->GetEntityDefName() ); + viewAnimator->Clear( channel, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = 0; + } else { + viewModel->Show(); + viewAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + animDoneTime[channel] = viewAnimator->CurrentAnim( channel )->GetEndTime(); + + // Play the animation on the world model as well + if ( worldAnimator ) { + anim = worldAnimator->GetAnim( animname ); + if ( anim ) { + worldAnimator->CycleAnim( channel, anim, gameLocal.time, FRAME2MS( blendFrames ) ); + } + } + } +} + +/* +=============== +rvWeapon::AnimDone +=============== +*/ +bool rvWeapon::AnimDone( int channel, int blendFrames ) { + if ( animDoneTime[channel] - FRAME2MS( blendFrames ) <= gameLocal.time ) { + return true; + } + return false; +} + +/* +=============== +rvWeapon::StartSound +=============== +*/ +bool rvWeapon::StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + if ( !viewModel ) { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return false; + } + return viewModel->StartSound( soundName, channel, soundShaderFlags, broadcast, length ); +} + +/* +=============== +rvWeapon::StopSound +=============== +*/ +void rvWeapon::StopSound( const s_channelType channel, bool broadcast ) { + if ( viewModel ) { + viewModel->StopSound( channel, broadcast ); + } else { + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + } +} + +/* +=============== +rvWeapon::PlayEffect +=============== +*/ +rvClientEffect* rvWeapon::PlayEffect( const char* effectName, jointHandle_t joint, bool loop, const idVec3& endOrigin, bool broadcast ) { + if ( viewModel ) { + return viewModel->PlayEffect( effectName, joint, loop, endOrigin, broadcast ); + } + + common->Warning( "NULL viewmodel %s\n", __FUNCTION__ ); + return 0; +} + +/* +================ +rvWeapon::CacheWeapon +================ +*/ +void rvWeapon::CacheWeapon( const char *weaponName ) { + const idDeclEntityDef *weaponDef; + const char *brassDefName; + const char *clipModelName; + idTraceModel trm; + + weaponDef = gameLocal.FindEntityDef( weaponName, false ); + if ( !weaponDef ) { + return; + } + + // precache the brass collision model + brassDefName = weaponDef->dict.GetString( "def_ejectBrass" ); + if ( brassDefName[0] ) { + const idDeclEntityDef *brassDef = gameLocal.FindEntityDef( brassDefName, false ); + if ( brassDef ) { + brassDef->dict.GetString( "clipmodel", "", &clipModelName ); + if ( idStr::Icmp( clipModelName, SIMPLE_TRI_NAME ) == 0 ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + if ( !clipModelName[0] ) { + clipModelName = brassDef->dict.GetString( "model" ); // use the visual model + } + // load the trace model + collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ); + } + } + } + + const idKeyValue* kv; + + kv = weaponDef->dict.MatchPrefix( "gui", NULL ); + while( kv ) { + if ( kv->GetValue().Length() ) { + uiManager->FindGui( kv->GetValue().c_str(), true, false, true ); + } + kv = weaponDef->dict.MatchPrefix( "gui", kv ); + } +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeapon ) + STATE ( "Raise", rvWeapon::State_Raise ) + STATE ( "Lower", rvWeapon::State_Lower ) + STATE ( "ExitCinematic", rvWeapon::State_ExitCinematic ) + STATE ( "NetCatchup", rvWeapon::State_NetCatchup ) + STATE ( "EjectBrass", rvWeapon::Frame_EjectBrass ) +END_CLASS_STATES + +/* +================ +rvWeapon::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeapon::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeapon::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeapon::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_NetCatchup ( const stateParms_t& parms ) { + SetState ( "idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::State_ExitCinematic +===================== +*/ +stateResult_t rvWeapon::State_ExitCinematic ( const stateParms_t& parms ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::Frame_EjectBrass +===================== +*/ +stateResult_t rvWeapon::Frame_EjectBrass( const stateParms_t& parms ) { + EjectBrass(); + return SRESULT_DONE; +} + +/* +===================== +rvWeapon::GetDebugInfo +===================== +*/ +void rvWeapon::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + idClass::GetDebugInfo ( proc, userData ); + proc ( "rvWeapon", "state", stateThread.GetState()?stateThread.GetState()->state->name : "", userData ); +} diff --git a/src/mpgame/Weapon.h b/src/mpgame/Weapon.h new file mode 100644 index 0000000..d8e0e46 --- /dev/null +++ b/src/mpgame/Weapon.h @@ -0,0 +1,516 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 9/30/2004 + +#ifndef __GAME_WEAPON_H__ +#define __GAME_WEAPON_H__ + +/* +=============================================================================== + + Player Weapon + +=============================================================================== +*/ + +typedef enum { + WP_READY, + WP_OUTOFAMMO, + WP_RELOAD, + WP_HOLSTERED, + WP_RISING, + WP_LOWERING, + WP_FLASHLIGHT, +} weaponStatus_t; + +static const int MAX_WEAPONMODS = 4; +static const int MAX_AMMOTYPES = 16; + +class idPlayer; +class idItem; +class idAnimatedEntity; +class idProjectile; +class rvWeapon; + +class rvViewWeapon : public idAnimatedEntity { +public: + + CLASS_PROTOTYPE( rvViewWeapon ); + + rvViewWeapon( void ); + virtual ~rvViewWeapon( void ); + + // Init + void Spawn ( void ); + + // save games + void Save ( idSaveGame *savefile ) const; // archives object for save game file + void Restore ( idRestoreGame *savefile ); // unarchives object from save game file + + + // Weapon definition management + void Clear ( void ); + + // GUIs + void PostGUIEvent ( const char* event ); + + virtual void SetModel ( const char *modelname, int mods = 0 ); + void SetPowerUpSkin ( const char *name ); + void UpdateSkin ( void ); + + // State control/player interface + void Think ( void ); + + // Visual presentation + void PresentWeapon ( bool showViewModel ); + + // Networking + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientPredictionThink ( void ); + virtual bool ClientStale ( void ); + + virtual void ConvertLocalToWorldTransform( idVec3 &offset, idMat3 &axis ); + virtual void UpdateModelTransform ( void ); + + // Debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + void SetSkin ( const char *skinname ); + void SetSkin ( const idDeclSkin* skin ); + + void SetOverlayShader ( const idMaterial* material ); + + virtual void GetPosition ( idVec3& origin, idMat3& axis ) const; + +private: + + idStrList pendingGUIEvents; + + // effects + const idDeclSkin * saveSkin; + const idDeclSkin * invisSkin; + const idDeclSkin * saveWorldSkin; + const idDeclSkin * worldInvisSkin; + const idDeclSkin * saveHandsSkin; + const idDeclSkin * handsSkin; + + void Event_CallFunction ( const char* function ); + + friend class rvWeapon; + rvWeapon* weapon; +}; + +class rvWeapon : public idClass { +public: + + CLASS_PROTOTYPE( rvWeapon ); + + rvWeapon( void ); + virtual ~rvWeapon( void ); + + enum { + WPLIGHT_MUZZLEFLASH, + WPLIGHT_MUZZLEFLASH_WORLD, + WPLIGHT_FLASHLIGHT, + WPLIGHT_FLASHLIGHT_WORLD, + WPLIGHT_GUI, + WPLIGHT_MAX + }; + + enum { + EVENT_RELOAD = idEntity::EVENT_MAXEVENTS, + EVENT_ENDRELOAD, + EVENT_CHANGESKIN, + EVENT_MAXEVENTS + }; + + void Init ( idPlayer* _owner, const idDeclEntityDef* def, int weaponIndex, bool isStrogg = false ); + + // Virtual overrides + void Spawn ( void ); + virtual void Think ( void ); + virtual void CleanupWeapon ( void ) {} + virtual void WriteToSnapshot ( idBitMsgDelta &msg ) const; + virtual void ReadFromSnapshot ( const idBitMsgDelta &msg ); + virtual bool ClientReceiveEvent ( int event, int time, const idBitMsg &msg ); + virtual void ClientStale ( void ); + virtual void ClientUnstale ( void ) { } + virtual void Attack ( bool altFire, int num_attacks, float spread, float fuseOffset, float power ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + virtual void SpectatorCycle ( void ) { } + virtual bool NoFireWhileSwitching ( void ) const { return false; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void PreSave ( void ); + virtual void PostSave ( void ); + + + // Visual presentation + bool BloodSplat ( float size ); + void MuzzleFlash ( void ); + void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + float GetMuzzleFlashLightParm ( int parm ); + void SetMuzzleFlashLightParm ( int parm, float value ); + void GetAngleOffsets ( int *average, float *scale, float *max ); + void GetTimeOffsets ( float *time, float *scale ); + bool GetGlobalJointTransform ( bool viewModel, const jointHandle_t jointHandle, idVec3 &origin, idMat3 &axis, const idVec3& offset = vec3_origin ); + + // State control/player interface + void LowerWeapon ( void ); + void RaiseWeapon ( void ); + void Raise ( void ); + void PutAway ( void ); + void Hide ( void ); + void Show ( void ); + void HideWorldModel ( void ); + void ShowWorldModel ( void ); + void SetFlashlight ( bool on = true ); + void Flashlight ( void ); + void SetPushVelocity ( const idVec3 &pushVelocity ); + void Reload ( void ); + void OwnerDied ( void ); + void BeginAttack ( void ); + void EndAttack ( void ); + bool IsReady ( void ) const; + bool IsReloading ( void ) const; + bool IsHolstered ( void ) const; + bool ShowCrosshair ( void ) const; + bool CanDrop ( void ) const; + bool CanZoom ( void ) const; + void CancelReload ( void ); + void SetStatus ( weaponStatus_t status ); + bool AutoReload ( void ); + bool IsHidden ( void ) const; + void EjectBrass ( void ); + + // Network helpers + void NetReload ( void ); + void NetEndReload ( void ); + void NetCatchup ( void ); + + // Ammo + static int GetAmmoIndexForName ( const char *ammoname ); + static const char* GetAmmoNameForIndex ( int index ); + int GetAmmoType ( void ) const; + int AmmoAvailable ( void ) const; + int AmmoInClip ( void ) const; + void ResetAmmoClip ( void ); + int ClipSize ( void ) const; + int LowAmmo ( void ) const; + int AmmoRequired ( void ) const; + void AddToClip ( int amount ); + void UseAmmo ( int amount ); + void SetClip ( int amount ); + int TotalAmmoCount ( void ) const; + + // Attack + bool PerformAttack ( idVec3& muzzleOrigin, idMat3& muzzleAxis, float dmgPower ); + void LaunchProjectiles ( idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_projectiles, float spread, float fuseOffset, float power ); + void Hitscan ( const idDict& dict, const idVec3& muzzleOrigin, const idMat3& muzzleAxis, int num_hitscans, float spread, float power ); + void AlertMonsters ( void ); + + // Mods + int GetMods ( void ) const; + + // Zoom + idUserInterface* GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + int GetZoomFov ( void ) const; + + rvViewWeapon* GetViewModel ( void ) const; + idAnimatedEntity* GetWorldModel ( void ) const; + idPlayer* GetOwner ( void ) const; + const char * GetIcon ( void ) const; + renderLight_t& GetLight ( int light ); + const idAngles& GetViewModelAngles ( void ) const; + const idVec3& GetViewModelOffset ( void ) const; + + static void CacheWeapon ( const char *weaponName ); + static void SkipFromSnapshot ( const idBitMsgDelta &msg ); + + void EnterCinematic ( void ); + void ExitCinematic ( void ); + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetState ( const char *statename, int blendFrames ); + void PostState ( const char *statename, int blendFrames ); + void ExecuteState ( const char *statename ); + + void PlayAnim ( int channel, const char *animname, int blendFrames ); + void PlayCycle ( int channel, const char *animname, int blendFrames ); + bool AnimDone ( int channel, int blendFrames ); + bool StartSound ( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + void StopSound ( const s_channelType channel, bool broadcast ); + rvClientEffect* PlayEffect ( const char* effectName, jointHandle_t joint, bool loop = false, const idVec3& endOrigin = vec3_origin, bool broadcast = false ); + + void FindViewModelPositionStyle ( idVec3& viewOffset, idAngles& viewAngles ) const; + +public: + + void InitLights ( void ); + void InitWorldModel ( void ); + void InitViewModel ( void ); + void InitDefs ( void ); + + void FreeLight ( int lightID ); + void UpdateLight ( int lightID ); + + void UpdateMuzzleFlash ( void ); + void UpdateFlashlight ( void ); + + void UpdateGUI ( void ); + void UpdateCrosshairGUI ( idUserInterface* gui ) const; + + idMat3 ForeshortenAxis ( const idMat3& axis ) const; + + // Script state management + struct weaponStateFlags_s { + bool attack :1; + bool reload :1; + bool netReload :1; + bool netEndReload :1; + bool raiseWeapon :1; + bool lowerWeapon :1; + bool flashlight :1; + bool zoom :1; + } wsfl; + + // Generic flags + struct weaponFlags_s { + bool attackAltHitscan :1; + bool attackHitscan :1; + bool hide :1; + bool disabled :1; + bool hasBloodSplat :1; + bool silent_fire :1; + bool zoomHideCrosshair :1; + bool flashlightOn :1; + bool hasWindupAnim :1; + } wfl; + + // joints from models + jointHandle_t barrelJointView; + jointHandle_t flashJointView; + jointHandle_t ejectJointView; + jointHandle_t guiLightJointView; + jointHandle_t flashlightJointView; + + jointHandle_t flashJointWorld; + jointHandle_t ejectJointWorld; + jointHandle_t flashlightJointWorld; + + weaponStatus_t status; + int lastAttack; + + // hiding weapon + int hideTime; + float hideDistance; + int hideStartTime; + float hideStart; + float hideEnd; + float hideOffset; + + // Attack + idVec3 pushVelocity; + int kick_endtime; + int muzzle_kick_time; + int muzzle_kick_maxtime; + idAngles muzzle_kick_angles; + idVec3 muzzle_kick_offset; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + float muzzleOffset; + idEntityPtr projectileEnt; + idVec3 ejectOffset; + + int fireRate; + int altFireRate; + float spread; + int nextAttackTime; + + // we maintain local copies of the projectile and brass dictionaries so they + // do not have to be copied across the DLL boundary when entities are spawned + idDict attackAltDict; + idDict attackDict; + idDict brassDict; + + // Melee + const idDeclEntityDef * meleeDef; + float meleeDistance; + + // zoom + int zoomFov; // variable zoom fov per weapon (-1 is no zoom) + idUserInterface* zoomGui; // whether or not to overlay a zoom scope + float zoomTime; // time it takes to zoom in + + // lights + renderLight_t lights[WPLIGHT_MAX]; + int lightHandles[WPLIGHT_MAX]; + idVec3 guiLightOffset; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashViewOffset; + bool flashlightOn; + idVec3 flashlightViewOffset; + + // ammo management + int ammoType; + int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + int clipSize; // 0 means no reload + int ammoClip; + int lowAmmo; // if ammo in clip hits this threshold, snd_ + int maxAmmo; + + // multiplayer + int clipPredictTime; + + // these are the player render view parms, which include bobbing + idVec3 playerViewOrigin; + idMat3 playerViewAxis; + + + // View Model + idVec3 viewModelOrigin; + idMat3 viewModelAxis; + idAngles viewModelAngles; + idVec3 viewModelOffset; + + // weighting for viewmodel offsets + int weaponAngleOffsetAverages; + float weaponAngleOffsetScale; + float weaponAngleOffsetMax; + float weaponOffsetTime; + float weaponOffsetScale; + + // General + idStr icon; + bool isStrogg; + + bool forceGUIReload; + +public: + + idDict spawnArgs; + +protected: + + idEntityPtr viewModel; + idAnimator* viewAnimator; + idEntityPtr worldModel; + idAnimator* worldAnimator; + const idDeclEntityDef* weaponDef; + idScriptObject* scriptObject; + idPlayer * owner; + int weaponIndex; + int mods; + + float viewModelForeshorten; + + rvStateThread stateThread; + int animDoneTime[ANIM_NumAnimChannels]; + +private: + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_ExitCinematic ( const stateParms_t& parms ); + stateResult_t State_NetCatchup ( const stateParms_t& parms ); + + stateResult_t Frame_EjectBrass ( const stateParms_t& parms ); + + // store weapon index information for death messages + int methodOfDeath; + + // multiplayer hitscans + int hitscanAttackDef; + + CLASS_STATES_PROTOTYPE ( rvWeapon ); +}; + +ID_INLINE rvViewWeapon* rvWeapon::GetViewModel ( void ) const { + return viewModel.GetEntity(); +} + +ID_INLINE idAnimatedEntity* rvWeapon::GetWorldModel ( void ) const { + return worldModel; +} + +ID_INLINE idPlayer* rvWeapon::GetOwner ( void ) const { + return owner; +} + +ID_INLINE const char* rvWeapon::GetIcon ( void ) const { + return icon; +} + +ID_INLINE renderLight_t& rvWeapon::GetLight ( int light ) { + assert ( light < WPLIGHT_MAX ); + return lights[light]; +} + +ID_INLINE const idAngles& rvWeapon::GetViewModelAngles( void ) const { + return viewModelAngles; +} + +ID_INLINE const idVec3& rvWeapon::GetViewModelOffset ( void ) const { + return viewModelOffset; +} + +ID_INLINE int rvWeapon::GetZoomFov ( void ) const { + return zoomFov; +} + +ID_INLINE idUserInterface* rvWeapon::GetZoomGui ( void ) const { + return zoomGui; +} + +ID_INLINE float rvWeapon::GetZoomTime ( void ) const { + return zoomTime; +} + +ID_INLINE int rvWeapon::GetMods ( void ) const { + return mods; +} + +ID_INLINE void rvWeapon::PreSave ( void ) { +} + +ID_INLINE void rvWeapon::PostSave ( void ) { +} + + +#endif /* !__GAME_WEAPON_H__ */ + +// RAVEN END diff --git a/src/mpgame/WorldSpawn.cpp b/src/mpgame/WorldSpawn.cpp new file mode 100644 index 0000000..7b877ba --- /dev/null +++ b/src/mpgame/WorldSpawn.cpp @@ -0,0 +1,131 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +game_worldspawn.cpp + +Worldspawn class. Each map has one worldspawn which handles global spawnargs. + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" + +/* +================ +idWorldspawn + +Every map should have exactly one worldspawn. +================ +*/ +CLASS_DECLARATION( idEntity, idWorldspawn ) + EVENT( EV_Remove, idWorldspawn::Event_Remove ) + EVENT( EV_SafeRemove, idWorldspawn::Event_Remove ) +END_CLASS + +/* +================ +idWorldspawn::Spawn +================ +*/ +void idWorldspawn::Spawn( void ) { + idStr scriptname; + idThread *thread; + const function_t *func; + const idKeyValue *kv; + + assert( gameLocal.world == NULL ); + gameLocal.world = this; + + // load script + scriptname = gameLocal.GetMapName(); + scriptname.SetFileExtension( ".script" ); + if ( fileSystem->ReadFile( scriptname, NULL, NULL ) > 0 ) { + gameLocal.program.CompileFile( scriptname ); + + // call the main function by default + func = gameLocal.program.FindFunction( "main" ); + if ( func != NULL ) { + thread = new idThread( func ); + thread->DelayedStart( 0 ); + } + } + + // call any functions specified in worldspawn + kv = spawnArgs.MatchPrefix( "call" ); + while( kv != NULL ) { + func = gameLocal.program.FindFunction( kv->GetValue() ); + if ( func == NULL ) { + gameLocal.Error( "Function '%s' not found in script for '%s' key on worldspawn", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + + thread = new idThread( func ); + thread->DelayedStart( 0 ); + kv = spawnArgs.MatchPrefix( "call", kv ); + } +} + +/* +================= +idWorldspawn::Save +================= +*/ +void idWorldspawn::Save( idRestoreGame *savefile ) { +} + +/* +================= +idWorldspawn::Restore +================= +*/ +void idWorldspawn::Restore( idRestoreGame *savefile ) { + assert( gameLocal.world == this ); + +// RAVEN BEGIN +// bdube: gravity change + g_gravity.SetFloat( spawnArgs.GetFloat( "gravity", va( "%f", DEFAULT_GRAVITY) ) ); +// RAVEN END +} + +/* +================ +idWorldspawn::~idWorldspawn +================ +*/ +idWorldspawn::~idWorldspawn() { + if ( gameLocal.world == this ) { + gameLocal.world = NULL; + } +} + +/* +================ +idWorldspawn::Event_Remove +================ +*/ +void idWorldspawn::Event_Remove( void ) { + gameLocal.Error( "Tried to remove world" ); +} diff --git a/src/mpgame/WorldSpawn.h b/src/mpgame/WorldSpawn.h new file mode 100644 index 0000000..1b2bfd7 --- /dev/null +++ b/src/mpgame/WorldSpawn.h @@ -0,0 +1,51 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAME_WORLDSPAWN_H__ +#define __GAME_WORLDSPAWN_H__ + +/* +=============================================================================== + + World entity. + +=============================================================================== +*/ + +class idWorldspawn : public idEntity { +public: + CLASS_PROTOTYPE( idWorldspawn ); + + ~idWorldspawn(); + + void Spawn( void ); + + void Save( idRestoreGame *savefile ); + void Restore( idRestoreGame *savefile ); + +private: + void Event_Remove( void ); +}; + +#endif /* !__GAME_WORLDSPAWN_H__ */ diff --git a/src/mpgame/ai/AAS.cpp b/src/mpgame/ai/AAS.cpp new file mode 100644 index 0000000..05a3531 --- /dev/null +++ b/src/mpgame/ai/AAS.cpp @@ -0,0 +1,486 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +/* +============ +idAAS::Alloc +============ +*/ +idAAS *idAAS::Alloc( void ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_AAS); +// RAVEN END + return new idAASLocal; +} + +/* +============ +idAAS::idAAS +============ +*/ +idAAS::~idAAS( void ) { +} + +/* +============ +idAASLocal::idAASLocal +============ +*/ +idAASLocal::idAASLocal( void ) { + file = NULL; +} + +/* +============ +idAASLocal::~idAASLocal +============ +*/ +idAASLocal::~idAASLocal( void ) { + Shutdown(); +} + +/* +============ +idAASLocal::Init +============ +*/ +bool idAASLocal::Init( const idStr &mapName, unsigned int mapFileCRC ) { + if ( file && mapName.Icmp( file->GetName() ) == 0 && mapFileCRC == file->GetCRC() ) { + gameLocal.Printf( "Keeping %s\n", file->GetName() ); + RemoveAllObstacles(); + } + else { + Shutdown(); + + file = AASFileManager->LoadAAS( mapName, mapFileCRC ); + if ( !file ) { + common->DWarning( "Couldn't load AAS file: '%s'", mapName.c_str() ); + return false; + } +// RAVEN BEGIN +// rhummer: Check if this is a dummy file, since it really has no valid data dump it. + else if ( file->IsDummyFile( mapFileCRC ) ) { + AASFileManager->FreeAAS( file ); + file = NULL; + return false; + } +// RAVEN END + SetupRouting(); + } + return true; +} + +/* +============ +idAASLocal::Shutdown +============ +*/ +void idAASLocal::Shutdown( void ) { + if ( file ) { + ShutdownRouting(); + RemoveAllObstacles(); + AASFileManager->FreeAAS( file ); + file = NULL; + } +} + +/* +============ +idAASLocal::Stats +============ +*/ +void idAASLocal::Stats( void ) const { + if ( !file ) { + return; + } + common->Printf( "[%s]\n", file->GetName() ); + file->PrintInfo(); + RoutingStats(); +} + +// RAVEN BEGIN +// jscott: added +/* +============ +idAASLocal::StatsSummary +============ +*/ +size_t idAASLocal::StatsSummary( void ) const { + + int size; + + if( !file ) { + + return( 0 ); + } + + size = ( numAreaTravelTimes * sizeof( unsigned short ) ) + + ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) + + ( portalCacheIndexSize * sizeof( idRoutingCache * ) ); + + return( file->GetMemorySize() + size ); +} +// RAVEN END + +/* +============ +idAASLocal::GetSettings +============ +*/ +const idAASSettings *idAASLocal::GetSettings( void ) const { + if ( !file ) { + return NULL; + } + return &file->GetSettings(); +} + +/* +============ +idAASLocal::PointAreaNum +============ +*/ +int idAASLocal::PointAreaNum( const idVec3 &origin ) const { + if ( !file ) { + return 0; + } + return file->PointAreaNum( origin ); +} + +/* +============ +idAASLocal::PointReachableAreaNum +============ +*/ +int idAASLocal::PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->PointReachableAreaNum( origin, searchBounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::BoundsReachableAreaNum +============ +*/ +int idAASLocal::BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const { + if ( !file ) { + return 0; + } + + return file->BoundsReachableAreaNum( bounds, areaFlags, TFL_INVALID ); +} + +/* +============ +idAASLocal::PushPointIntoAreaNum +============ +*/ +void idAASLocal::PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const { + if ( !file ) { + return; + } + file->PushPointIntoAreaNum( areaNum, origin ); +} + +/* +============ +idAASLocal::AreaCenter +============ +*/ +idVec3 idAASLocal::AreaCenter( int areaNum ) const { + if ( !file ) { + return vec3_origin; + } + return file->GetArea( areaNum ).center; +} + +// RAVEN BEGIN +// bdube: added +/* +============ +idAASLocal::AreaRadius +============ +*/ +float idAASLocal::AreaRadius( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).bounds.GetRadius(); +} +// mcg: added +/* +============ +idAASLocal::AreaBounds +============ +*/ +idBounds & idAASLocal::AreaBounds( int areaNum ) const { + return file->GetArea( areaNum ).bounds; +} +/* +============ +idAASLocal::AreaCeiling +============ +*/ +float idAASLocal::AreaCeiling( int areaNum ) const { + return file->GetArea( areaNum ).ceiling; +} +// RAVEN END + +/* +============ +idAASLocal::AreaFlags +============ +*/ +int idAASLocal::AreaFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).flags; +} + +/* +============ +idAASLocal::AreaTravelFlags +============ +*/ +int idAASLocal::AreaTravelFlags( int areaNum ) const { + if ( !file ) { + return 0; + } + return file->GetArea( areaNum ).travelFlags; +} + +/* +============ +idAASLocal::Trace +============ +*/ +bool idAASLocal::Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const { + if ( !file ) { + trace.fraction = 0.0f; + trace.lastAreaNum = 0; + trace.numAreas = 0; + return true; + } + return file->Trace( trace, start, end ); +} + +/* +============ +idAASLocal::GetPlane +============ +*/ +const idPlane &idAASLocal::GetPlane( int planeNum ) const { + if ( !file ) { + static idPlane dummy; + return dummy; + } + return file->GetPlane( planeNum ); +} + +/* +============ +idAASLocal::GetEdgeVertexNumbers +============ +*/ +void idAASLocal::GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const { + if ( !file ) { + verts[0] = verts[1] = 0; + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + verts[0] = v[INTSIGNBITSET(edgeNum)]; + verts[1] = v[INTSIGNBITNOTSET(edgeNum)]; +} + +/* +============ +idAASLocal::GetEdge +============ +*/ +void idAASLocal::GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const { + if ( !file ) { + start.Zero(); + end.Zero(); + return; + } + const int *v = file->GetEdge( abs(edgeNum) ).vertexNum; + start = file->GetVertex( v[INTSIGNBITSET(edgeNum)] ); + end = file->GetVertex( v[INTSIGNBITNOTSET(edgeNum)] ); +} + + +/* +=============================================================================== + + idAASCallback + +=============================================================================== +*/ + +/* +============ +idAASCallback::~idAASCallback +============ +*/ +idAASCallback::~idAASCallback ( void ) { +} + +/* +============ +idAASCallback::Test +============ +*/ +idAASCallback::testResult_t idAASCallback::Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ) { + // Get AAS file + idAASFile* file = ((idAAS&)*aas).GetFile ( ); + if ( !file ) { + return TEST_BADAREA; + } + + // Get area for edges + aasArea_t& area = file->GetArea ( areaNum ); + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorYellow, area.center, area.center + idVec3(0,0,80.0f), 10000 ); + } + + // Make sure the area itself is valid + if ( !TestArea ( aas, areaNum, area ) ) { + return TEST_BADAREA; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 && point ) { + gameRenderWorld->DebugLine ( colorMagenta, *point, *point + idVec3(0,0,64.0f), 10000 ); + } + + // Test the original origin first + if ( point && TestPointDistance ( origin, *point, minDistance, maxDistance) && TestPoint ( aas, *point ) ) { + goal.areaNum = areaNum; + goal.origin = *point; + return TEST_OK; + } + + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorCyan, area.center, area.center + idVec3(0,0,64.0f), 10000 ); + } + + // Test the center of the area + if ( TestPointDistance ( origin, area.center, minDistance, maxDistance) && TestPoint ( aas, area.center, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = area.center; + return TEST_OK; + } + + // For each face test all available edges + int f; + int e; + for ( f = 0; f < area.numFaces; f ++ ) { + aasFace_t& face = file->GetFace ( abs ( file->GetFaceIndex (area.firstFace + f ) ) ); + + // for each edge test a point between the center of the edge and the center + for ( e = 0; e < face.numEdges; e ++ ) { + idVec3 edgeCenter = file->EdgeCenter ( abs( file->GetEdgeIndex( face.firstEdge + e ) ) ); + idVec3 dir = area.center - edgeCenter; + float dist; + for ( dist = dir.Normalize() - 64.0f; dist > 0.0f; dist -= 64.0f ) { + idVec3 testPoint = edgeCenter + dir * dist; + if ( ai_debugTactical.GetInteger ( ) > 1 ) { + gameRenderWorld->DebugLine ( colorPurple, testPoint, testPoint + idVec3(0,0,64.0f), 10000 ); + } + + if ( TestPointDistance ( origin, testPoint, minDistance, maxDistance) && TestPoint ( aas, testPoint, area.ceiling ) ) { + goal.areaNum = areaNum; + goal.origin = testPoint; + return TEST_OK; + } + } + } + } + + return TEST_BADPOINT; +} + +/* +============ +idAASCallback::Init +============ +*/ +bool idAASCallback::TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ) { + float dist = (origin - point).LengthFast ( ); + if ( minDistance > 0.0f && dist < minDistance ) { + return false; + } + if ( maxDistance > 0.0f && dist > maxDistance ) { + return false; + } + return true; +} + +/* +============ +idAASCallback::Init +============ +*/ +void idAASCallback::Init ( void ) { +} + +/* +============ +idAASCallback::Finish +============ +*/ +void idAASCallback::Finish ( void ) { +} + +/* +============ +idAASCallback::TestArea +============ +*/ +bool idAASCallback::TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ) { + return true; +} + +/* +============ +idAASCallback::TestPoint +============ +*/ +bool idAASCallback::TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow ) { + return true; +} + +// RAVEN END diff --git a/src/mpgame/ai/AAS.h b/src/mpgame/ai/AAS.h new file mode 100644 index 0000000..de6da32 --- /dev/null +++ b/src/mpgame/ai/AAS.h @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AAS_H__ +#define __AAS_H__ + +/* +=============================================================================== + + Area Awareness System + +=============================================================================== +*/ + +enum { + PATHTYPE_WALK, + PATHTYPE_WALKOFFLEDGE, + PATHTYPE_BARRIERJUMP, + PATHTYPE_JUMP +}; + +typedef struct aasPath_s { + int type; // path type + idVec3 moveGoal; // point the AI should move towards + int moveAreaNum; // number of the area the AI should move towards + idVec3 secondaryGoal; // secondary move goal for complex navigation + const idReachability * reachability; // reachability used for navigation +} aasPath_t; + + +typedef struct aasGoal_s { + int areaNum; // area the goal is in + idVec3 origin; // position of goal +} aasGoal_t; + + +typedef struct aasObstacle_s { + idBounds absBounds; // absolute bounds of obstacle + idBounds expAbsBounds; // expanded absolute bounds of obstacle +} aasObstacle_t; + +class idAASCallback { +public: + virtual ~idAASCallback ( void ); + + enum testResult_t { + TEST_OK, + TEST_BADAREA, + TEST_BADPOINT + }; + + virtual void Init ( void ); + virtual void Finish ( void ); + + testResult_t Test ( class idAAS *aas, int areaNum, const idVec3& origin, float minDistance, float maxDistance, const idVec3* point, aasGoal_t& goal ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + bool TestPointDistance ( const idVec3& origin, const idVec3& point, float minDistance, float maxDistance ); +}; + +typedef int aasHandle_t; + +class idAAS { +public: + static idAAS * Alloc( void ); + virtual ~idAAS( void ) = 0; + // Initialize for the given map. + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ) = 0; +// RAVEN BEGIN +// jscott: added + // Prints out the memory used by this AAS + virtual size_t StatsSummary( void ) const = 0; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void Shutdown( void ) = 0; +#endif +// RAVEN END + // Print AAS stats. + virtual void Stats( void ) const = 0; + // Test from the given origin. + virtual void Test( const idVec3 &origin ) = 0; + // Get the AAS settings. + virtual const idAASSettings *GetSettings( void ) const = 0; + // Returns the number of the area the origin is in. + virtual int PointAreaNum( const idVec3 &origin ) const = 0; + // Returns the number of the nearest reachable area for the given point. + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &bounds, const int areaFlags ) const = 0; + // Returns the number of the first reachable area in or touching the bounds. + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const = 0; + // Push the point into the area. + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const = 0; + // Returns a reachable point inside the given area. + virtual idVec3 AreaCenter( int areaNum ) const = 0; +// RAVEN BEGIN +// bdube: added + // Returns a reachable point inside the given area. + virtual float AreaRadius( int areaNum ) const = 0; + virtual idBounds & AreaBounds( int areaNum ) const = 0; + virtual float AreaCeiling( int areaNum ) const = 0; +// RAVEN END + // Returns the area flags. + virtual int AreaFlags( int areaNum ) const = 0; + // Returns the travel flags for traveling through the area. + virtual int AreaTravelFlags( int areaNum ) const = 0; + // Trace through the areas and report the first collision. + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + // Get a plane for a trace. + virtual const idPlane & GetPlane( int planeNum ) const = 0; + // Get wall edges. + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const = 0; + // Sort the wall edges to create continuous sequences of walls. + virtual void SortWallEdges( int *edges, int numEdges ) const = 0; + // Get the vertex numbers for an edge. + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const = 0; + // Get an edge. + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const = 0; + // Find all areas within or touching the bounds with the given contents and disable/enable them for routing. + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) = 0; + // Add an obstacle to the routing system. + virtual aasHandle_t AddObstacle( const idBounds &bounds ) = 0; + // Remove an obstacle from the routing system. + virtual void RemoveObstacle( const aasHandle_t handle ) = 0; + // Remove all obstacles from the routing system. + virtual void RemoveAllObstacles( void ) = 0; + // Returns the travel time towards the goal area in 100th of a second. + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const = 0; + // Get the travel time and first reachability to be used towards the goal, returns true if there is a path. + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const = 0; + // Creates a walk path towards the goal. + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can walk along a straight line from the origin to the goal origin. + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Creates a fly path towards the goal. + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const = 0; + // Returns true if one can fly along a straight line from the origin to the goal origin. + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const = 0; + // Show the walk path from the origin towards the area. + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Show the fly path from the origin towards the area. + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const = 0; + // Find the nearest goal which satisfies the callback. + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const = 0; + +// RAVEN BEGIN +// CDR : Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) = 0; +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ) = 0; + +// rjohnson: added more debug drawing + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const = 0; + virtual bool IsValid( void ) const = 0; +// RAVEN END +}; + +#endif /* !__AAS_H__ */ diff --git a/src/mpgame/ai/AAS_Find.cpp b/src/mpgame/ai/AAS_Find.cpp new file mode 100644 index 0000000..f586abc --- /dev/null +++ b/src/mpgame/ai/AAS_Find.cpp @@ -0,0 +1,364 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + +AAS_Find.cpp + +This file has all aas search classes. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + rvAASFindGoalForHide + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForHide::rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::rvAASFindGoalForHide( const idVec3 &hideFromPos ) { + int numPVSAreas; + idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) ); + + // setup PVS + numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS ); + hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForHide::~rvAASFindGoalForHide +============ +*/ +rvAASFindGoalForHide::~rvAASFindGoalForHide() { + gameLocal.pvs.FreeCurrentPVS( hidePVS ); +} + +/* +rvAASFindGoalForHide +rvAASFindHide::TestArea +============ +*/ +bool rvAASFindGoalForHide::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ).TranslateSelf(idVec3(0,0,1)), PVSAreas, idEntity::MAX_PVS_AREAS ); + if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) { + return true; + } + + return false; +} + +/* +=============================================================================== + + rvAASFindGoalOutOfRange + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange +============ +*/ +rvAASFindGoalOutOfRange::rvAASFindGoalOutOfRange( idAI* _owner ) { + owner = _owner; +} + +/* +============ +rvAASFindAreaOutOfRange::TestArea +============ +*/ +bool rvAASFindGoalOutOfRange::TestPoint ( idAAS* aas, const idVec3& pos, const float zAllow ) { + return aiManager.ValidateDestination ( owner, pos ); +} + +/* +=============================================================================== + +rvAASFindGoalForAttack + +Find a position to move to that allows the ai to at their target + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForAttack::rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::rvAASFindGoalForAttack( idAI* _owner ) { + owner = _owner; + cachedIndex = 0; +} + +/* +============ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack +============ +*/ +rvAASFindGoalForAttack::~rvAASFindGoalForAttack ( void ) { +} + +/* +============ +rvAASFindGoalForAttack::Init +============ +*/ +void rvAASFindGoalForAttack::Init ( void ) { + // setup PVS + int numPVSAreas; + numPVSAreas = gameLocal.pvs.GetPVSAreas( owner->enemy.ent->GetPhysics()->GetAbsBounds(), PVSAreas, idEntity::MAX_PVS_AREAS ); + targetPVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas ); + + cachedGoals.SetGranularity ( 1024 ); +} + +/* +============ +rvAASFindGoalForAttack::Finish +============ +*/ +void rvAASFindGoalForAttack::Finish ( void ) { + gameLocal.pvs.FreeCurrentPVS( targetPVS ); +} + +/* +============ +rvAASFindGoalForAttack::TestArea +============ +*/ +bool rvAASFindGoalForAttack::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + int numPVSAreas; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + cachedAreaNum = areaNum; + + // If the whole area is out of range then skip it + float range; + range = area.bounds.ShortestDistance ( owner->enemy.lastKnownPosition ); + if ( range > owner->combat.attackRange[1] ) { + return false; + } + + // Out of pvs? + numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( area.center ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS ); + return gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ); +} + +/* +============ +rvAASFindGoalForAttack::TestPoint +============ +*/ +bool rvAASFindGoalForAttack::TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow ) { + float dist; + + idVec3 localPoint = point; + float bestZ = owner->enemy.ent->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + + // Out of attack range? + dist = (localPoint - owner->enemy.ent->GetPhysics()->GetOrigin ( )).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // If tethered make sure the point is within the tether range + if ( owner->tether ) { + idVec3 localPoint = point; + float bestZ = owner->tether.GetEntity()->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !owner->tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + } + + aasGoal_t& goal = cachedGoals.Alloc ( ); + goal.areaNum = cachedAreaNum; + goal.origin = localPoint; + + return false; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedGoal +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoal ( int index ) { + const aasGoal_t& goal = cachedGoals[index]; + + // Out of attack range? + float dist = (goal.origin - owner->enemy.ent->GetPhysics()->GetOrigin ( ) ).LengthFast ( ); + if ( dist < owner->combat.attackRange[0] || dist > owner->combat.attackRange[1] ) { + return false; + } + + // Someone already there? + if ( !aiManager.ValidateDestination ( owner, goal.origin, true ) ) { + return false; + } + + // Can we see the enemy from this position? + if ( !owner->CanSeeFrom ( goal.origin - owner->GetPhysics()->GetGravityNormal ( ) * owner->combat.visStandHeight, owner->GetEnemy(), false ) ) { + return false; + } + + return true; +} + +/* +============ +rvAASFindGoalForAttack::TestCachedPoints +============ +*/ +bool rvAASFindGoalForAttack::TestCachedGoals ( int count, aasGoal_t& goal ) { + int i; + + goal.areaNum = 0; + + // Test as many points as we are allowed to test + for ( i = 0; i < count && cachedIndex < cachedGoals.Num(); cachedIndex ++, i ++ ) { + // Retest simple checks + if ( TestCachedGoal( cachedIndex ) ) { + goal = cachedGoals[cachedIndex]; + return true; + } + } + + return !(cachedIndex >= cachedGoals.Num()); +} + +/* +=============================================================================== + +rvAASFindGoalForTether + +Find a goal to move to that is within the given tether. + +=============================================================================== +*/ + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::rvAASFindGoalForTether( idAI* _owner, rvAITether* _tether ) { + owner = _owner; + tether = _tether; +} + +/* +============ +rvAASFindGoalForTether::rvAASFindGoalForTether +============ +*/ +rvAASFindGoalForTether::~rvAASFindGoalForTether( void ) { +} + +/* +============ +rvAASFindGoalForTether::TestArea +============ +*/ +bool rvAASFindGoalForTether::TestArea( class idAAS *aas, int areaNum, const aasArea_t& area ) { + // Test super class first + if ( !idAASCallback::TestArea ( aas, areaNum, area ) ) { + return false; + } + + // Make sure the area bounds is remotely valid for the tether + idBounds tempBounds = area.bounds; + tempBounds[1].z = area.ceiling; + if ( !tether->ValidateBounds ( tempBounds ) ) { + return false; + } + return true; +} + +/* +============ +rvAASFindGoalForTether::TestPoint +============ +*/ +bool rvAASFindGoalForTether::TestPoint ( class idAAS* aas, const idVec3& point, const float zAllow ) { + if ( !tether ) { + return false; + } + + idVec3 localPoint = point; + float bestZ = tether->GetPhysics()->GetOrigin().z; + if ( bestZ > localPoint.z ) { + if ( bestZ > zAllow ) { + localPoint.z = zAllow; + } else { + localPoint.z = bestZ; + } + } + if ( !tether->ValidateDestination ( owner, localPoint ) ) { + return false; + } + + if ( !aiManager.ValidateDestination ( owner, localPoint ) ) { + return false; + } + + return true; +} diff --git a/src/mpgame/ai/AAS_Find.h b/src/mpgame/ai/AAS_Find.h new file mode 100644 index 0000000..c4dbb44 --- /dev/null +++ b/src/mpgame/ai/AAS_Find.h @@ -0,0 +1,136 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AAS_Find.h + +================ +*/ + +#ifndef __AAS_FIND__ +#define __AAS_FIND__ + +class idAI; +class rvAIHelper; + +/* +=============================================================================== + rvAASFindHide +=============================================================================== +*/ + +class rvAASFindGoalForHide : public idAASCallback { +public: + rvAASFindGoalForHide ( const idVec3 &hideFromPos ); + ~rvAASFindGoalForHide ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + +private: + + pvsHandle_t hidePVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; +}; + +/* +=============================================================================== + rvAASFindAreaOutOfRange +=============================================================================== +*/ + +class rvAASFindGoalOutOfRange : public idAASCallback { +public: + + rvAASFindGoalOutOfRange ( idAI* _owner ); + +protected: + + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + +private: + + idAI* owner; +}; + +/* +=============================================================================== + rvAASFindAttackPosition +=============================================================================== +*/ + +class rvAASFindGoalForAttack : public idAASCallback { +public: + rvAASFindGoalForAttack ( idAI *self ); + ~rvAASFindGoalForAttack ( void ); + + + bool TestCachedGoals ( int count, aasGoal_t& goal ); + + virtual void Init ( void ); + virtual void Finish ( void ); + +private: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS *aas, const idVec3& point, const float zAllow=0.0f ); + + bool TestCachedGoal ( int index ); + + idAI* owner; + + pvsHandle_t targetPVS; + int PVSAreas[ idEntity::MAX_PVS_AREAS ]; + + idList cachedGoals; + int cachedIndex; + int cachedAreaNum; +}; + +/* +=============================================================================== + rvAASFindGoalForTether +=============================================================================== +*/ + +class rvAASFindGoalForTether : public idAASCallback { +public: + rvAASFindGoalForTether ( idAI* owner, rvAITether* helper ); + ~rvAASFindGoalForTether ( void ); + +protected: + + virtual bool TestArea ( class idAAS *aas, int areaNum, const aasArea_t& area ); + virtual bool TestPoint ( class idAAS* aas, const idVec3& pos, const float zAllow=0.0f ); + +private: + + idAI* owner; + rvAITether* tether; +}; + +#endif // __AAS_FIND__ diff --git a/src/mpgame/ai/AAS_debug.cpp b/src/mpgame/ai/AAS_debug.cpp new file mode 100644 index 0000000..cdb7880 --- /dev/null +++ b/src/mpgame/ai/AAS_debug.cpp @@ -0,0 +1,932 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for cvars and debug drawing +#include "AI.h" +#include "AAS_Find.h" + + +/* +============ +idAASLocal::DrawCone +============ +*/ +void idAASLocal::DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const { + int i; + idMat3 axis; + idVec3 center, top, p, lastp; + + axis[2] = dir; + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + + center = origin + dir; + top = center + dir * (3.0f * radius); + lastp = center + radius * axis[1]; + + for ( i = 20; i <= 360; i += 20 ) { + p = center + idMath::Sin( DEG2RAD(i) ) * radius * axis[0] + idMath::Cos( DEG2RAD(i) ) * radius * axis[1]; + gameRenderWorld->DebugLine( color, lastp, p, 0 ); + gameRenderWorld->DebugLine( color, p, top, 0 ); + lastp = p; + } +} + +/* +============ +idAASLocal::DrawReachability +============ +*/ +void idAASLocal::DrawReachability( const idReachability *reach ) const { + gameRenderWorld->DebugArrow( colorCyan, reach->start, reach->end, 2 ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", reach->edgeNum ), ( reach->start + reach->end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis ); + } + + switch( reach->travelType ) { + case TFL_WALK: { +// const idReachability_Walk *walk = static_cast(reach); + break; + } + default: { + break; + } + } +} + +/* +============ +idAASLocal::DrawEdge +============ +*/ +void idAASLocal::DrawEdge( int edgeNum, bool arrow ) const { + const aasEdge_t *edge; + idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &colorRed; + if ( arrow ) { + gameRenderWorld->DebugArrow( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ), 1 ); + } else { + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); + } + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText( va( "%d", edgeNum ), ( file->GetVertex( edge->vertexNum[0] ) + file->GetVertex( edge->vertexNum[1] ) ) * 0.5f + idVec3(0,0,4), 0.1f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } +} + +/* +============ +idAASLocal::DrawFace +============ +*/ +void idAASLocal::DrawFace( int faceNum, bool side ) const { + int i, j, numEdges, firstEdge; + const aasFace_t *face; + idVec3 mid, end; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + if ( !numEdges ) + {//wtf? A face with no edges?! + return; + } + + mid = vec3_origin; + for ( i = 0; i < numEdges; i++ ) { + DrawEdge( abs( file->GetEdgeIndex( firstEdge + i ) ), ( face->flags & FACE_FLOOR ) != 0 ); + j = file->GetEdgeIndex( firstEdge + i ); + mid += file->GetVertex( file->GetEdge( abs( j ) ).vertexNum[ j < 0 ] ); + } + + mid /= numEdges; + if ( side ) { + end = mid - 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } else { + end = mid + 5.0f * file->GetPlane( file->GetFace( faceNum ).planeNum ).Normal(); + } + gameRenderWorld->DebugArrow( colorGreen, mid, end, 1 ); +} + +/* +============ +idAASLocal::DrawAreaBounds +============ +*/ +void idAASLocal::DrawAreaBounds( int areaNum ) const { + const aasArea_t *area; + if ( !file ) { + return; + } + area = &file->GetArea( areaNum ); + + idVec3 points[8]; + bool drawn[8][8]; + memset( drawn, false, sizeof( drawn ) ); + + area->bounds.ToPoints( points ); + for ( int p1 = 0; p1 < 8; p1++ ) { + for ( int p2 = 0; p2 < 8; p2++ ) { + if ( !drawn[p2][p1] ) { + if ( (points[p1].x == points[p2].x && (points[p1].y == points[p2].y||points[p1].z == points[p2].z)) + || (points[p1].y == points[p2].y && (points[p1].x == points[p2].x||points[p1].z == points[p2].z)) + || (points[p1].z == points[p2].z && (points[p1].x == points[p2].x||points[p1].y == points[p2].y)) ) { + //an edge + gameRenderWorld->DebugLine( colorRed, points[p1], points[p2] ); + drawn[p1][p2] = true; + } + } + } + } +} + +/* +============ +idAASLocal::DrawArea +============ +*/ +void idAASLocal::DrawArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idReachability *reach; + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawFace( abs( file->GetFaceIndex( firstFace + i ) ), file->GetFaceIndex( firstFace + i ) < 0 ); + } + + if (aas_showRevReach.GetInteger()) + { + for ( reach = area->rev_reach; reach; reach = reach->rev_next ) { + DrawReachability( reach ); + } + } + else + { + for ( reach = area->reach; reach; reach = reach->next ) { + DrawReachability( reach ); + } + } +} + +/* +============ +idAASLocal::DefaultSearchBounds +============ +*/ +const idBounds &idAASLocal::DefaultSearchBounds( void ) const { + return file->GetSettings().boundingBoxes[0]; +} + +/* +============ +idAASLocal::ShowArea +============ +*/ +void idAASLocal::ShowArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + const aasArea_t *area; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + org = origin; + PushPointIntoAreaNum( areaNum, org ); + + if ( aas_goalArea.GetInteger() ) { + int travelTime; + idReachability *reach; + + RouteToGoalArea( areaNum, org, aas_goalArea.GetInteger(), TFL_WALK|TFL_AIR, travelTime, &reach ); + gameLocal.Printf( "\rtt = %4d", travelTime ); + if ( reach ) { + gameLocal.Printf( " to area %4d", reach->toAreaNum ); + DrawArea( reach->toAreaNum ); + } + } + + if ( areaNum != lastAreaNum ) { + area = &file->GetArea( areaNum ); + gameLocal.Printf( "area %d: ", areaNum ); + if ( area->flags & AREA_LEDGE ) { + gameLocal.Printf( "AREA_LEDGE " ); + } + if ( area->flags & AREA_REACHABLE_WALK ) { + gameLocal.Printf( "AREA_REACHABLE_WALK " ); + } + if ( area->flags & AREA_REACHABLE_FLY ) { + gameLocal.Printf( "AREA_REACHABLE_FLY " ); + } + if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) { + gameLocal.Printf( "AREACONTENTS_CLUSTERPORTAL " ); + } + if ( area->contents & AREACONTENTS_OBSTACLE ) { + gameLocal.Printf( "AREACONTENTS_OBSTACLE " ); + } + gameLocal.Printf( "\n" ); + lastAreaNum = areaNum; + } + + if ( org != origin ) { + idBounds bnds = file->GetSettings().boundingBoxes[ 0 ]; + bnds[ 1 ].z = bnds[ 0 ].z; + gameRenderWorld->DebugBounds( colorYellow, bnds, org ); + } + + DrawArea( areaNum ); +} + +// RAVEN BEGIN +// rjohnson: added more debug drawing +/* +============ +idAASLocal::DrawSimpleEdge +============ +*/ +void idAASLocal::DrawSimpleEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + color = &file->GetSettings().debugColor; + + gameRenderWorld->DebugLine( *color, file->GetVertex( edge->vertexNum[0] ), file->GetVertex( edge->vertexNum[1] ) ); +} + +/* +============ +idAASLocal::DrawSimpleFace +============ +*/ +const int MAX_AAS_WALL_EDGES = 256; + +// RAVEN BEGIN +// cdr: added visited check +void idAASLocal::DrawSimpleFace( int faceNum, bool visited ) const { +// RAVEN END + int i, numEdges, firstEdge, edgeNum; + const aasFace_t *face; + idVec3 sides[MAX_AAS_WALL_EDGES]; + const aasEdge_t *edge, *nextEdge; + idVec4 color2; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + DrawSimpleEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } + + if ( numEdges >= 2 && numEdges <= MAX_AAS_WALL_EDGES ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + edgeNum = abs( file->GetEdgeIndex( firstEdge + 1 ) ); + nextEdge = &file->GetEdge( abs( edgeNum ) ); + + // need to find the first common edge so that we go form the polygon in the right direction + if ( file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[0] ) || + file->GetVertex( edge->vertexNum[0] ) == file->GetVertex( nextEdge->vertexNum[1] ) ) { + sides[ 0 ] = file->GetVertex( edge->vertexNum[0] ); + } else { + sides[ 0 ] = file->GetVertex( edge->vertexNum[1] ); + } + + for ( i = 1; i < numEdges; i++ ) { + edgeNum = abs( file->GetEdgeIndex( firstEdge + i ) ); + edge = &file->GetEdge( abs( edgeNum ) ); + + if ( sides[ i-1 ] == file->GetVertex( edge->vertexNum[0] ) ) { + sides[ i ] = file->GetVertex( edge->vertexNum[1] ); + } else { + sides[ i ] = file->GetVertex( edge->vertexNum[0] ); + } + } + + color2 = file->GetSettings().debugColor; + color2[3] = 0.20f; + + // RAVEN BEGIN + // cdr: added visited check + if (!visited) + { + color2[3] = 0.05f; + } + // RAVEN END + idWinding winding( sides, numEdges ); + gameRenderWorld->DebugPolygon( color2, winding, 0, true ); + } +} + + +/* +============ +idAASLocal::DrawSimpleArea +============ +*/ +void idAASLocal::DrawSimpleArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + + if ( aas_showAreaBounds.GetBool() ) { + if ( (area->flags&AREA_FLOOR) ) { + idVec3 areaTop = area->center; + areaTop.z = area->ceiling; + gameRenderWorld->DebugArrow( colorCyan, area->center, areaTop, 1 ); + gameRenderWorld->DrawText( va( "%4.2f", floor(area->ceiling-area->center.z) ), ( area->center + areaTop ) * 0.5f, 0.1f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } else {//air area + DrawAreaBounds( areaNum ); + } + } + + numFaces = area->numFaces; + firstFace = area->firstFace; + + for ( i = 0; i < numFaces; i++ ) { + DrawSimpleFace( abs( file->GetFaceIndex( firstFace + i )), true ); + } +} + +/* +============ +idAASLocal::IsValid +============ +*/ +bool idAASLocal::IsValid( void ) const { + if ( !file ) { + return false; + } + + return true; +} + +/* +============ +idAASLocal::ShowAreas +============ +*/ +void idAASLocal::ShowAreas( const idVec3 &origin, bool ShowProblemAreas ) const { + int i, areaNum; + idPlayer *player; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + idReachability *reach; + int travelFlags = (TFL_WALK); + const idBounds bounds = idBounds( origin ).Expand( 256.0f ); + + if ( !file ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = 0; + queueEnd = 1; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + area = &file->GetArea( curArea ); + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + + if ( ShowProblemAreas ) { + for ( i = 0; i < queueEnd; i++ ) { + ShowProblemArea( areaQueue[ i ] ); + } + } else { + for ( i = 0; i < queueEnd; i++ ) { + DrawSimpleArea( areaQueue[ i ] ); + } + } +} + +/* +============ +idAASLocal::ShowProblemEdge +============ +*/ +void idAASLocal::ShowProblemEdge( int edgeNum ) const { + const aasEdge_t *edge; + const idVec4 *color; + idVec4 color2; + idTraceModel trm; + idClipModel mdl; + idVec3 sides[4]; + trace_t results; + idVec3 forward, forwardNormal, left, down, start, end; + float hullSize, hullHeight; + + if ( !file ) { + return; + } + + edge = &file->GetEdge( edgeNum ); + + start = (idVec3)file->GetVertex( edge->vertexNum[0] ); + end = (idVec3)file->GetVertex( edge->vertexNum[1] ); + forward = end - start ; + forward.Normalize(); + forwardNormal = forward; + forward.NormalVectors( left, down ); + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ) - 1.0f; // assumption that x and y are the same size + hullHeight = ( file->GetSettings().boundingBoxes[0][1][2] - file->GetSettings().boundingBoxes[0][0][2] ); + + left *= hullSize; + forward *= hullSize; + + sides[0] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[1] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][0][2] + 1.0f ); + sides[2] = left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + sides[3] = -left + idVec3( 0.0f, 0.0f, file->GetSettings().boundingBoxes[0][1][2] - 1.0f ); + trm.SetupPolygon( sides, 4 ); + mdl.LoadModel( trm, NULL ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( gameLocal.GetLocalPlayer(), results, start, end, &mdl, mat3_identity, MASK_MONSTERSOLID, gameLocal.GetLocalPlayer() ); +// RAVEN END + + if ( results.fraction != 1.0f ) { + color = &file->GetSettings().debugColor; + gameRenderWorld->DebugLine( *color, start - left, end - left ); + gameRenderWorld->DebugLine( *color, start + left, end + left ); + + color = &colorYellow; + gameRenderWorld->DebugLine( *color, start, end ); + + color2 = colorOrange; + color2[3] = 0.25f; + sides[0] += results.endpos; + sides[1] += results.endpos; + sides[2] += results.endpos; + sides[3] += results.endpos; + idWinding winding( sides, 4 ); + gameRenderWorld->DebugPolygon( color2, winding ); + } +} + +/* +============ +idAASLocal::ShowProblemFace +============ +*/ +void idAASLocal::ShowProblemFace( int faceNum ) const { + int i, numEdges, firstEdge; + const aasFace_t *face; + + if ( !file ) { + return; + } + + face = &file->GetFace( faceNum ); + numEdges = face->numEdges; + firstEdge = face->firstEdge; + + for ( i = 0; i < numEdges; i++ ) { + ShowProblemEdge( abs( file->GetEdgeIndex( firstEdge + i ) ) ); + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( int areaNum ) const { + int i, numFaces, firstFace; + const aasArea_t *area; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + idBounds bounds; + float hullSize; + + if ( !file ) { + return; + } + + area = &file->GetArea( areaNum ); + numFaces = area->numFaces; + firstFace = area->firstFace; + + hullSize = ( ( file->GetSettings().boundingBoxes[0][1][0] - file->GetSettings().boundingBoxes[0][0][0] ) / 2.0f ); // assumption that x and y are the same size + + bounds = area->bounds; + bounds.Expand( hullSize ); +// RAVEN BEGIN +// ddynerman: multiple clip world + numListedEntities = gameLocal.EntitiesTouchingBounds( gameLocal.GetLocalPlayer(), bounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( entityList[ i ]->IsType( idMoveable::GetClassType() ) || + entityList[ i ]->IsType( idMover::GetClassType() ) || + entityList[ i ]->IsType( idAI::GetClassType() ) ) { +// RAVEN END + entityList[ i ]->GetPhysics()->DisableClip(); + continue; + } + + entityList[ i ] = NULL; + } + + for ( i = 0; i < numFaces; i++ ) { + ShowProblemFace( abs( file->GetFaceIndex( firstFace + i ) ) ); + } + + for( i = 0; i < numListedEntities; i++) { + if ( entityList[ i ] ) { + entityList[ i ]->GetPhysics()->EnableClip(); + } + } +} + +/* +============ +idAASLocal::ShowProblemArea +============ +*/ +void idAASLocal::ShowProblemArea( const idVec3 &origin ) const { + static int lastAreaNum; + int areaNum; + idVec3 org; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + + ShowProblemArea( areaNum ); +} + +// RAVEN END + +/* +============ +idAASLocal::ShowWalkPath +============ +*/ +void idAASLocal::ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_WALK ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorGreen, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( WalkPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowFlyPath +============ +*/ +void idAASLocal::ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + int i, areaNum, curAreaNum, travelTime; + idReachability *reach; + idVec3 org, areaCenter; + aasPath_t path; + + if ( !file ) { + return; + } + + org = origin; + areaNum = PointReachableAreaNum( org, DefaultSearchBounds(), AREA_REACHABLE_FLY ); + PushPointIntoAreaNum( areaNum, org ); + curAreaNum = areaNum; + + for ( i = 0; i < 100; i++ ) { + + if ( !RouteToGoalArea( curAreaNum, org, goalAreaNum, TFL_WALK|TFL_FLY|TFL_AIR, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + break; + } + + gameRenderWorld->DebugArrow( colorPurple, org, reach->start, 2 ); + DrawReachability( reach ); + + if ( reach->toAreaNum == goalAreaNum ) { + break; + } + + curAreaNum = reach->toAreaNum; + org = reach->end; + } + + if ( FlyPathToGoal( path, areaNum, origin, goalAreaNum, goalOrigin, TFL_WALK|TFL_FLY|TFL_AIR ) ) { + gameRenderWorld->DebugArrow( colorBlue, origin, path.moveGoal, 2 ); + } +} + +/* +============ +idAASLocal::ShowWallEdges +============ +*/ +void idAASLocal::ShowWallEdges( const idVec3 &origin ) const { + int i, areaNum, numEdges, edges[1024]; + idVec3 start, end; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + numEdges = GetWallEdges( areaNum, idBounds( origin ).Expand( 256.0f ), TFL_WALK, edges, 1024 ); + for ( i = 0; i < numEdges; i++ ) { + GetEdge( edges[i], start, end ); + gameRenderWorld->DebugLine( colorRed, start, end ); + gameRenderWorld->DrawText( va( "%d", edges[i] ), ( start + end ) * 0.5f, 0.1f, colorWhite, player->viewAxis ); + } +} + +/* +============ +idAASLocal::ShowHideArea +============ +*/ +void idAASLocal::ShowHideArea( const idVec3 &origin, int targetAreaNum ) const { + int areaNum, numObstacles; + idVec3 target; + aasGoal_t goal; + aasObstacle_t obstacles[10]; + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + target = AreaCenter( targetAreaNum ); + + // consider the target an obstacle + obstacles[0].absBounds = idBounds( target ).Expand( 16 ); + numObstacles = 1; + + DrawCone( target, idVec3(0,0,1), 16.0f, colorYellow ); + + rvAASFindGoalForHide findHide ( target ); + if ( FindNearestGoal( goal, areaNum, origin, target, TFL_WALK|TFL_AIR, 0.0f, 0.0f, obstacles, numObstacles, findHide ) ) { + DrawArea( goal.areaNum ); + ShowWalkPath( origin, goal.areaNum, goal.origin ); + DrawCone( goal.origin, idVec3(0,0,1), 16.0f, colorWhite ); + } +} + +/* +============ +idAASLocal::PullPlayer +============ +*/ +bool idAASLocal::PullPlayer( const idVec3 &origin, int toAreaNum ) const { + int areaNum; + idVec3 areaCenter, dir, vel; + idAngles delta; + aasPath_t path; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return true; + } + + idPhysics *physics = player->GetPhysics(); + if ( !physics ) { + return true; + } + + if ( !toAreaNum ) { + return false; + } + + areaNum = PointReachableAreaNum( origin, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + areaCenter = AreaCenter( toAreaNum ); + if ( player->GetPhysics()->GetAbsBounds().Expand( 8 ).ContainsPoint( areaCenter ) ) { + return false; + } + if ( WalkPathToGoal( path, areaNum, origin, toAreaNum, areaCenter, TFL_WALK|TFL_AIR ) ) { + dir = path.moveGoal - origin; + dir[2] *= 0.5f; + dir.Normalize(); + delta = dir.ToAngles() - player->cmdAngles - player->GetDeltaViewAngles(); + delta.Normalize180(); + player->SetDeltaViewAngles( player->GetDeltaViewAngles() + delta * 0.1f ); + dir[2] = 0.0f; + dir.Normalize(); + dir *= 100.0f; + vel = physics->GetLinearVelocity(); + dir[2] = vel[2]; + physics->SetLinearVelocity( dir ); + return true; + } + else { + return false; + } +} + +/* +============ +idAASLocal::RandomPullPlayer +============ +*/ +void idAASLocal::RandomPullPlayer( const idVec3 &origin ) const { + int rnd, i, n; + + if ( !PullPlayer( origin, aas_pullPlayer.GetInteger() ) ) { + + rnd = gameLocal.random.RandomFloat() * file->GetNumAreas(); + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + n = (rnd + i) % file->GetNumAreas(); + if ( file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) { + aas_pullPlayer.SetInteger( n ); + } + } + } else { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + } +} + +/* +============ +idAASLocal::ShowPushIntoArea +============ +*/ +void idAASLocal::ShowPushIntoArea( const idVec3 &origin ) const { + int areaNum; + idVec3 target; + + target = origin; + areaNum = PointReachableAreaNum( target, DefaultSearchBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + PushPointIntoAreaNum( areaNum, target ); + gameRenderWorld->DebugArrow( colorGreen, origin, target, 1 ); +} + +/* +============ +idAASLocal::Test +============ +*/ +void idAASLocal::Test( const idVec3 &origin ) { + + if ( !file ) { + return; + } + + if ( aas_randomPullPlayer.GetBool() ) { + RandomPullPlayer( origin ); + } + if ( ( aas_pullPlayer.GetInteger() > 0 ) && ( aas_pullPlayer.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_pullPlayer.GetInteger(), AreaCenter( aas_pullPlayer.GetInteger() ) ); + PullPlayer( origin, aas_pullPlayer.GetInteger() ); + } + if ( ( aas_showPath.GetInteger() > 0 ) && ( aas_showPath.GetInteger() < file->GetNumAreas() ) ) { + ShowWalkPath( origin, aas_showPath.GetInteger(), AreaCenter( aas_showPath.GetInteger() ) ); + } + if ( ( aas_showFlyPath.GetInteger() > 0 ) && ( aas_showFlyPath.GetInteger() < file->GetNumAreas() ) ) { + ShowFlyPath( origin, aas_showFlyPath.GetInteger(), AreaCenter( aas_showFlyPath.GetInteger() ) ); + } + if ( ( aas_showHideArea.GetInteger() > 0 ) && ( aas_showHideArea.GetInteger() < file->GetNumAreas() ) ) { + ShowHideArea( origin, aas_showHideArea.GetInteger() ); + } +// RAVEN BEGIN +// rjohnson: added more debug drawing + if ( aas_showAreas.GetInteger() == 1 ) { + ShowArea( origin ); + } + else if ( aas_showAreas.GetInteger() == 2 ) { + ShowAreas( origin ); + } + if ( aas_showProblemAreas.GetInteger() == 1 ) { + ShowProblemArea( origin ); + } + else if ( aas_showProblemAreas.GetInteger() == 2 ) { + ShowAreas( origin, true ); + } +// RAVEN END + if ( aas_showWallEdges.GetBool() ) { + ShowWallEdges( origin ); + } + if ( aas_showPushIntoArea.GetBool() ) { + ShowPushIntoArea( origin ); + } +} diff --git a/src/mpgame/ai/AAS_local.h b/src/mpgame/ai/AAS_local.h new file mode 100644 index 0000000..8c7f92d --- /dev/null +++ b/src/mpgame/ai/AAS_local.h @@ -0,0 +1,216 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AAS_LOCAL_H__ +#define __AAS_LOCAL_H__ + +#include "AAS.h" +#include "../Pvs.h" + + +class idRoutingCache { + friend class idAASLocal; + +public: + idRoutingCache( int size ); + ~idRoutingCache( void ); + + int Size( void ) const; + +private: + int type; // portal or area cache + int size; // size of cache + int cluster; // cluster of the cache + int areaNum; // area of the cache + int travelFlags; // combinations of the travel flags + idRoutingCache * next; // next in list + idRoutingCache * prev; // previous in list + idRoutingCache * time_next; // next in time based list + idRoutingCache * time_prev; // previous in time based list + unsigned short startTravelTime; // travel time to start with + unsigned char * reachabilities; // reachabilities used for routing + unsigned short * travelTimes; // travel time for every area +}; + + +class idRoutingUpdate { + friend class idAASLocal; + +private: + int cluster; // cluster number of this update + int areaNum; // area number of this update + unsigned short tmpTravelTime; // temporary travel time + unsigned short * areaTravelTimes; // travel times within the area + idVec3 start; // start point into area + idRoutingUpdate * next; // next in list + idRoutingUpdate * prev; // prev in list + bool isInList; // true if the update is in the list +}; + + +class idRoutingObstacle { + friend class idAASLocal; + idRoutingObstacle( void ) { } + +private: + idBounds bounds; // obstacle bounds + idList areas; // areas the bounds are in +}; + +class idAASLocal : public idAAS { +public: + idAASLocal( void ); + virtual ~idAASLocal( void ); + virtual bool Init( const idStr &mapName, unsigned int mapFileCRC ); + virtual void Shutdown( void ); +// RAVEN BEGIN +// jscott: added summary flag + virtual size_t StatsSummary( void ) const; +// RAVEN END + virtual void Stats( void ) const; + virtual void Test( const idVec3 &origin ); + virtual const idAASSettings *GetSettings( void ) const; + virtual int PointAreaNum( const idVec3 &origin ) const; + virtual int PointReachableAreaNum( const idVec3 &origin, const idBounds &searchBounds, const int areaFlags ) const; + virtual int BoundsReachableAreaNum( const idBounds &bounds, const int areaFlags ) const; + virtual void PushPointIntoAreaNum( int areaNum, idVec3 &origin ) const; + virtual idVec3 AreaCenter( int areaNum ) const; +// RAVEN BEGIN +// bdube: added + virtual float AreaRadius( int areaNum ) const; + virtual idBounds & AreaBounds( int areaNum ) const; + virtual float AreaCeiling( int areaNum ) const; +// RAVEN END + virtual int AreaFlags( int areaNum ) const; + virtual int AreaTravelFlags( int areaNum ) const; + virtual bool Trace( aasTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + virtual const idPlane & GetPlane( int planeNum ) const; + virtual int GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const; + virtual void SortWallEdges( int *edges, int numEdges ) const; + virtual void GetEdgeVertexNumbers( int edgeNum, int verts[2] ) const; + virtual void GetEdge( int edgeNum, idVec3 &start, idVec3 &end ) const; + virtual bool SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ); + virtual aasHandle_t AddObstacle( const idBounds &bounds ); + virtual void RemoveObstacle( const aasHandle_t handle ); + virtual void RemoveAllObstacles( void ); + virtual int TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const; + virtual bool RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const; + virtual bool WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual bool FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const; + virtual bool FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const; + virtual void ShowWalkPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual void ShowFlyPath( const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + virtual bool FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const; +// RAVEN BEGIN +// creed: Added Area Wall Extraction For AASTactical + virtual idAASFile* GetFile( void ) {return file;} +// cdr: Alternate Routes Bug + virtual void SetReachabilityState( idReachability* reach, bool enable ); +// rjohnson: added more debug drawing + virtual bool IsValid( void ) const; + virtual void ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const; +// RAVEN END + + +private: + idAASFile * file; + idStr name; + +private: // routing data + idRoutingCache *** areaCacheIndex; // for each area in each cluster the travel times to all other areas in the cluster + int areaCacheIndexSize; // number of area cache entries + idRoutingCache ** portalCacheIndex; // for each area in the world the travel times from each portal + int portalCacheIndexSize; // number of portal cache entries + idRoutingUpdate * areaUpdate; // memory used to update the area routing cache + idRoutingUpdate * portalUpdate; // memory used to update the portal routing cache + unsigned short * goalAreaTravelTimes; // travel times to goal areas + unsigned short * areaTravelTimes; // travel times through the areas + int numAreaTravelTimes; // number of area travel times + mutable idRoutingCache * cacheListStart; // start of list with cache sorted from oldest to newest + mutable idRoutingCache * cacheListEnd; // end of list with cache sorted from oldest to newest + mutable int totalCacheMemory; // total cache memory used + idList obstacleList; // list with obstacles + +private: // routing + bool SetupRouting( void ); + void ShutdownRouting( void ); + unsigned short AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const; + void CalculateAreaTravelTimes( void ); + void DeleteAreaTravelTimes( void ); + void SetupRoutingCache( void ); + void DeleteClusterCache( int clusterNum ); + void DeletePortalCache( void ); + void ShutdownRoutingCache( void ); + void RoutingStats( void ) const; + void LinkCache( idRoutingCache *cache ) const; + void UnlinkCache( idRoutingCache *cache ) const; + void DeleteOldestCache( void ) const; + idReachability * GetAreaReachability( int areaNum, int reachabilityNum ) const; + int ClusterAreaNum( int clusterNum, int areaNum ) const; + void UpdateAreaRoutingCache( idRoutingCache *areaCache ) const; + idRoutingCache * GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void UpdatePortalRoutingCache( idRoutingCache *portalCache ) const; + idRoutingCache * GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const; + void RemoveRoutingCacheUsingArea( int areaNum ); + void DisableArea( int areaNum ); + void EnableArea( int areaNum ); + bool SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ); + void GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const; + void SetObstacleState( const idRoutingObstacle *obstacle, bool enable ); + +private: // pathing + bool EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const; + bool FloorEdgeSplitPoint( idVec3 &split, int areaNum, const idPlane &splitPlane, const idPlane &frontPlane, bool closest ) const; + idVec3 SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + idVec3 SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const; + +private: // debug + const idBounds & DefaultSearchBounds( void ) const; + void DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const; + void DrawAreaBounds( int areaNum ) const; + void DrawArea( int areaNum ) const; + void DrawFace( int faceNum, bool side ) const; + void DrawEdge( int edgeNum, bool arrow ) const; + void DrawReachability( const idReachability *reach ) const; + void ShowArea( const idVec3 &origin ) const; + void ShowWallEdges( const idVec3 &origin ) const; + void ShowHideArea( const idVec3 &origin, int targerAreaNum ) const; + bool PullPlayer( const idVec3 &origin, int toAreaNum ) const; + void RandomPullPlayer( const idVec3 &origin ) const; + void ShowPushIntoArea( const idVec3 &origin ) const; + +// RAVEN BEGIN +// rjohnson: added more debug drawing + void DrawSimpleEdge( int edgeNum ) const; + void DrawSimpleFace( int faceNum, bool visited ) const; + void DrawSimpleArea( int areaNum ) const; + void ShowProblemEdge( int edgeNum ) const; + void ShowProblemFace( int faceNum ) const; + void ShowProblemArea( int areaNum ) const; + void ShowProblemArea( const idVec3 &origin ) const; +// RAVEN END +}; + +#endif /* !__AAS_LOCAL_H__ */ diff --git a/src/mpgame/ai/AAS_pathing.cpp b/src/mpgame/ai/AAS_pathing.cpp new file mode 100644 index 0000000..efbe667 --- /dev/null +++ b/src/mpgame/ai/AAS_pathing.cpp @@ -0,0 +1,757 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +// RAVEN BEGIN +#include "../Game_local.h" +// RAVEN END +#include "AAS_local.h" + +#define SUBSAMPLE_WALK_PATH 1 +#define SUBSAMPLE_FLY_PATH 0 + +const int maxWalkPathIterations = 10; +const float maxWalkPathDistance = 500.0f; +const float walkPathSampleDistance = 8.0f; + +const int maxFlyPathIterations = 10; +const float maxFlyPathDistance = 500.0f; +const float flyPathSampleDistance = 8.0f; + + +/* +============ +idAASLocal::EdgeSplitPoint + + calculates split point of the edge with the plane + returns true if the split point is between the edge vertices +============ +*/ +bool idAASLocal::EdgeSplitPoint( idVec3 &split, int edgeNum, const idPlane &plane ) const { + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + edge = &file->GetEdge( edgeNum ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * plane.Normal() - plane.Dist(); + d2 = v2 * plane.Normal() - plane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + return false; + } + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + return true; +} + +// RAVEN BEGIN +// rjohnson: optimized function +/* +============ +idAASLocal::FloorEdgeSplitPoint + + calculates either the closest or furthest point on the floor of the area which also lies on the pathPlane + the point has to be on the front side of the frontPlane to be valid +============ +*/ +bool idAASLocal::FloorEdgeSplitPoint( idVec3 &bestSplit, int areaNum, const idPlane &pathPlane, const idPlane &frontPlane, bool closest ) const { + int i, j, faceNum, edgeNum; + const aasArea_t *area; + const aasFace_t *face; + idVec3 split; + float dist, bestDist; + const aasEdge_t *edge; + idVec3 v1, v2; + float d1, d2; + + area = &file->GetArea( areaNum ); + if ( closest ) { + bestDist = maxWalkPathDistance; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist >= -0.1f && dist < bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist < maxWalkPathDistance ); + + } else { + bestDist = -0.1f; + + for ( i = area->numFaces-1; i >= 0; i-- ) { + faceNum = file->GetFaceIndex( area->firstFace + i ); + face = &file->GetFace( abs(faceNum) ); + + if ( !(face->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = face->numEdges-1; j >= 0; j-- ) { + edgeNum = file->GetEdgeIndex( face->firstEdge + j ); + + edge = &file->GetEdge( abs( edgeNum ) ); + v1 = file->GetVertex( edge->vertexNum[0] ); + v2 = file->GetVertex( edge->vertexNum[1] ); + d1 = v1 * pathPlane.Normal() - pathPlane.Dist(); + d2 = v2 * pathPlane.Normal() - pathPlane.Dist(); + + //if ( (d1 < CM_CLIP_EPSILON && d2 < CM_CLIP_EPSILON) || (d1 > -CM_CLIP_EPSILON && d2 > -CM_CLIP_EPSILON) ) { + if ( FLOATSIGNBITSET( d1 ) == FLOATSIGNBITSET( d2 ) ) { + continue; + } + + split = v1 + (d1 / (d1 - d2)) * (v2 - v1); + dist = frontPlane.Distance( split ); + if ( dist > bestDist ) { + bestDist = dist; + bestSplit = split; + } + } + } + + return ( bestDist > -0.1f ); + } +} +// RAVEN END + +/* +============ +idAASLocal::WalkPathValid + + returns true if one can walk in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::WalkPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + int curAreaNum, lastAreaNum, lastAreas[4], lastAreaIndex; + idPlane pathPlane, frontPlane, farPlane; + idReachability *reach; + const aasArea_t *area; + idVec3 p, dir; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + pathPlane.SetNormal( (goalOrigin - origin).Cross( file->GetSettings().gravityDir ) ); + pathPlane.Normalize(); + pathPlane.FitThroughPoint( origin ); + + frontPlane.SetNormal( goalOrigin - origin ); + frontPlane.Normalize(); + frontPlane.FitThroughPoint( origin ); + + farPlane.SetNormal( frontPlane.Normal() ); + farPlane.FitThroughPoint( goalOrigin ); + + curAreaNum = areaNum; + lastAreaNum = curAreaNum; + + while ( 1 ) { + + // find the furthest floor face split point on the path + if ( !FloorEdgeSplitPoint( endPos, curAreaNum, pathPlane, frontPlane, false ) ) { + endPos = origin; + } + + // if we found a point near or further than the goal we're done + if ( farPlane.Distance( endPos ) > -0.5f ) { + break; + } + + // if we reached the goal area we're done + if ( curAreaNum == goalAreaNum ) { + break; + } + + frontPlane.SetDist( frontPlane.Normal() * endPos ); + + area = &file->GetArea( curAreaNum ); + + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType != TFL_WALK ) { + continue; + } + + // if the reachability goes back to a previous area + if ( reach->toAreaNum == lastAreas[0] || reach->toAreaNum == lastAreas[1] || + reach->toAreaNum == lastAreas[2] || reach->toAreaNum == lastAreas[3] ) { + continue; + } + + // if undesired travel flags are required to travel through the area + if ( file->GetArea( reach->toAreaNum ).travelFlags & ~travelFlags ) { + continue; + } + + // don't optimize through an area near a ledge + if ( file->GetArea( reach->toAreaNum ).flags & AREA_LEDGE ) { + continue; + } + + // find the closest floor face split point on the path + if ( !FloorEdgeSplitPoint( p, reach->toAreaNum, pathPlane, frontPlane, true ) ) { + continue; + } + + // direction parallel to gravity + dir = ( file->GetSettings().gravityDir * endPos * file->GetSettings().gravityDir ) - + ( file->GetSettings().gravityDir * p * file->GetSettings().gravityDir ); + if ( dir.LengthSqr() > Square( file->GetSettings().maxStepHeight ) ) { + continue; + } + + // direction orthogonal to gravity + dir = endPos - p - dir; + if ( dir.LengthSqr() > Square( 0.2f ) ) { + continue; + } + + break; + } + + if ( !reach ) { + return false; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + } + + endAreaNum = curAreaNum; + + return true; +} + +/* +============ +idAASLocal::SubSampleWalkPath +============ +*/ +idVec3 idAASLocal::SubSampleWalkPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / walkPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxWalkPathDistance ) ) { + return point; + } + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::WalkPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::WalkPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxWalkPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.reachability = reach; + // RAVEN END + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + // only optimize a limited distance ahead + if ( (reach->start - origin).LengthSqr() > Square( maxWalkPathDistance ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( reach->travelType != TFL_WALK ) { + break; + } + + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::WalkPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_WALK_PATH + path.moveGoal = SubSampleWalkPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::WalkPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + switch( reach->travelType ) { + case TFL_WALKOFFLEDGE: + path.type = PATHTYPE_WALKOFFLEDGE; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_BARRIERJUMP: + path.type |= PATHTYPE_BARRIERJUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + case TFL_JUMP: + path.type |= PATHTYPE_JUMP; + path.secondaryGoal = reach->end; + path.reachability = reach; + break; + default: + break; + } + + return true; +} + +/* +============ +idAASLocal::FlyPathValid + + returns true if one can fly in a straight line between origin and goalOrigin +============ +*/ +bool idAASLocal::FlyPathValid( int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags, idVec3 &endPos, int &endAreaNum ) const { + aasTrace_t trace; + + if ( file == NULL ) { + endPos = goalOrigin; + endAreaNum = 0; + return true; + } + + file->Trace( trace, origin, goalOrigin ); + + endPos = trace.endpos; + endAreaNum = trace.lastAreaNum; + + if ( trace.fraction >= 1.0f ) { + return true; + } + + return false; +} + +/* +============ +idAASLocal::SubSampleFlyPath +============ +*/ +idVec3 idAASLocal::SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const { + int i, numSamples, curAreaNum; + idVec3 dir, point, nextPoint, endPos; + + dir = end - start; + numSamples = (int) (dir.Length() / flyPathSampleDistance) + 1; + + point = start; + for ( i = 1; i < numSamples; i++ ) { + nextPoint = start + dir * ((float) i / numSamples); + if ( (point - nextPoint).LengthSqr() > Square( maxFlyPathDistance ) ) { + return point; + } + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, nextPoint, travelFlags, endPos, curAreaNum ) ) { + return point; + } + point = nextPoint; + endAreaNum = curAreaNum; + } + return point; +} + +/* +============ +idAASLocal::FlyPathToGoal + + FIXME: don't stop optimizing on first failure ? +============ +*/ +bool idAASLocal::FlyPathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin, int travelFlags ) const { + int i, travelTime, curAreaNum, lastAreas[4], lastAreaIndex, endAreaNum; + idReachability *reach = NULL; + idVec3 endPos; + + path.type = PATHTYPE_WALK; + path.moveGoal = origin; + path.moveAreaNum = areaNum; + path.secondaryGoal = origin; + path.reachability = NULL; + + if ( file == NULL || areaNum == goalAreaNum ) { + path.moveGoal = goalOrigin; + return true; + } + + lastAreas[0] = lastAreas[1] = lastAreas[2] = lastAreas[3] = areaNum; + lastAreaIndex = 0; + + curAreaNum = areaNum; + + for ( i = 0; i < maxFlyPathIterations; i++ ) { + + if ( !idAASLocal::RouteToGoalArea( curAreaNum, path.moveGoal, goalAreaNum, travelFlags, travelTime, &reach ) ) { + break; + } + + if ( !reach ) { + return false; + } + + // no need to check through the first area + if ( areaNum != curAreaNum ) { + if ( (reach->start - origin).LengthSqr() > Square( maxFlyPathDistance ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->start, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, reach->start, travelFlags, path.moveAreaNum ); +#endif + return true; + } + } + + path.moveGoal = reach->start; + path.moveAreaNum = curAreaNum; + + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, reach->end, travelFlags, endPos, endAreaNum ) ) { + return true; + } + + path.moveGoal = reach->end; + path.moveAreaNum = reach->toAreaNum; + + if ( reach->toAreaNum == goalAreaNum ) { + if ( !idAASLocal::FlyPathValid( areaNum, origin, 0, goalOrigin, travelFlags, endPos, endAreaNum ) ) { +#if SUBSAMPLE_FLY_PATH + path.moveGoal = SubSampleFlyPath( areaNum, origin, path.moveGoal, goalOrigin, travelFlags, path.moveAreaNum ); +#endif + return true; + } + path.moveGoal = goalOrigin; + path.moveAreaNum = goalAreaNum; + return true; + } + + lastAreas[lastAreaIndex] = curAreaNum; + lastAreaIndex = ( lastAreaIndex + 1 ) & 3; + + curAreaNum = reach->toAreaNum; + + if ( curAreaNum == lastAreas[0] || curAreaNum == lastAreas[1] || + curAreaNum == lastAreas[2] || curAreaNum == lastAreas[3] ) { + common->Warning( "idAASLocal::FlyPathToGoal: local routing minimum going from area %d to area %d", areaNum, goalAreaNum ); + break; + } + } + + if ( !reach ) { + return false; + } + + return true; +} + +typedef struct wallEdge_s { + int edgeNum; + int verts[2]; + struct wallEdge_s * next; +} wallEdge_t; + +/* +============ +idAASLocal::SortWallEdges +============ +*/ +void idAASLocal::SortWallEdges( int *edges, int numEdges ) const { + int i, j, k, numSequences; + wallEdge_t **sequenceFirst, **sequenceLast, *wallEdges, *wallEdge; + + wallEdges = (wallEdge_t *) _alloca16( numEdges * sizeof( wallEdge_t ) ); + sequenceFirst = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + sequenceLast = (wallEdge_t **)_alloca16( numEdges * sizeof( wallEdge_t * ) ); + + for ( i = 0; i < numEdges; i++ ) { + wallEdges[i].edgeNum = edges[i]; + GetEdgeVertexNumbers( edges[i], wallEdges[i].verts ); + wallEdges[i].next = NULL; + sequenceFirst[i] = &wallEdges[i]; + sequenceLast[i] = &wallEdges[i]; + } + numSequences = numEdges; + + for ( i = 0; i < numSequences; i++ ) { + for ( j = i+1; j < numSequences; j++ ) { + if ( sequenceFirst[i]->verts[0] == sequenceLast[j]->verts[1] ) { + sequenceLast[j]->next = sequenceFirst[i]; + sequenceFirst[i] = sequenceFirst[j]; + break; + } + if ( sequenceLast[i]->verts[1] == sequenceFirst[j]->verts[0] ) { + sequenceLast[i]->next = sequenceFirst[j]; + break; + } + } + if ( j < numSequences ) { + numSequences--; + for ( k = j; k < numSequences; k++ ) { + sequenceFirst[k] = sequenceFirst[k+1]; + sequenceLast[k] = sequenceLast[k+1]; + } + i = -1; + } + } + + k = 0; + for ( i = 0; i < numSequences; i++ ) { + for ( wallEdge = sequenceFirst[i]; wallEdge; wallEdge = wallEdge->next ) { + edges[k++] = wallEdge->edgeNum; + } + } +} + +/* +============ +idAASLocal::GetWallEdges +============ +*/ +int idAASLocal::GetWallEdges( int areaNum, const idBounds &bounds, int travelFlags, int *edges, int maxEdges ) const { + int i, j, k, l, face1Num, face2Num, edge1Num, edge2Num, numEdges, absEdge1Num; + int *areaQueue, curArea, queueStart, queueEnd; + byte *areasVisited; + const aasArea_t *area; + const aasFace_t *face1, *face2; + idReachability *reach; + + if ( !file ) { + return 0; + } + + numEdges = 0; + + areasVisited = (byte *) _alloca16( file->GetNumAreas() ); + memset( areasVisited, 0, file->GetNumAreas() * sizeof( byte ) ); + areaQueue = (int *) _alloca16( file->GetNumAreas() * sizeof( int ) ); + + queueStart = -1; + queueEnd = 0; + areaQueue[0] = areaNum; + areasVisited[areaNum] = true; + + for ( curArea = areaNum; queueStart < queueEnd; curArea = areaQueue[++queueStart] ) { + + area = &file->GetArea( curArea ); + + for ( i = 0; i < area->numFaces; i++ ) { + face1Num = file->GetFaceIndex( area->firstFace + i ); + face1 = &file->GetFace( abs(face1Num) ); + + if ( !(face1->flags & FACE_FLOOR ) ) { + continue; + } + + for ( j = 0; j < face1->numEdges; j++ ) { + edge1Num = file->GetEdgeIndex( face1->firstEdge + j ); + absEdge1Num = abs( edge1Num ); + + // test if the edge is shared by another floor face of this area + for ( k = 0; k < area->numFaces; k++ ) { + if ( k == i ) { + continue; + } + face2Num = file->GetFaceIndex( area->firstFace + k ); + face2 = &file->GetFace( abs(face2Num) ); + + if ( !(face2->flags & FACE_FLOOR ) ) { + continue; + } + + for ( l = 0; l < face2->numEdges; l++ ) { + edge2Num = abs( file->GetEdgeIndex( face2->firstEdge + l ) ); + if ( edge2Num == absEdge1Num ) { + break; + } + } + if ( l < face2->numEdges ) { + break; + } + } + if ( k < area->numFaces ) { + continue; + } + + // test if the edge is used by a reachability + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + if ( reach->edgeNum == absEdge1Num ) { + break; + } + } + } + if ( reach ) { + continue; + } + + // test if the edge is already in the list + for ( k = 0; k < numEdges; k++ ) { + if ( edge1Num == edges[k] ) { + break; + } + } + if ( k < numEdges ) { + continue; + } + + // add the edge to the list + edges[numEdges++] = edge1Num; + if ( numEdges >= maxEdges ) { + return numEdges; + } + } + } + + // add new areas to the queue + for ( reach = area->reach; reach; reach = reach->next ) { + if ( reach->travelType & travelFlags ) { + // if the area the reachability leads to hasn't been visited yet and the area bounds touch the search bounds + if ( !areasVisited[reach->toAreaNum] && bounds.IntersectsBounds( file->GetArea( reach->toAreaNum ).bounds ) ) { + areaQueue[queueEnd++] = reach->toAreaNum; + areasVisited[reach->toAreaNum] = true; + } + } + } + } + return numEdges; +} diff --git a/src/mpgame/ai/AAS_routing.cpp b/src/mpgame/ai/AAS_routing.cpp new file mode 100644 index 0000000..50e1892 --- /dev/null +++ b/src/mpgame/ai/AAS_routing.cpp @@ -0,0 +1,1447 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "AAS_local.h" +#include "../Game_local.h" // for print and error + +#define CACHETYPE_AREA 1 +#define CACHETYPE_PORTAL 2 + +#define MAX_ROUTING_CACHE_MEMORY (2*1024*1024) + +#define LEDGE_TRAVELTIME_PANALTY 250 + +/* +============ +idRoutingCache::idRoutingCache +============ +*/ +idRoutingCache::idRoutingCache( int size ) { + areaNum = 0; + cluster = 0; + next = prev = NULL; + time_next = time_prev = NULL; + travelFlags = 0; + startTravelTime = 0; + type = 0; + this->size = size; + reachabilities = new byte[size]; + memset( reachabilities, 0, size * sizeof( reachabilities[0] ) ); + travelTimes = new unsigned short[size]; + memset( travelTimes, 0, size * sizeof( travelTimes[0] ) ); +} + +/* +============ +idRoutingCache::~idRoutingCache +============ +*/ +idRoutingCache::~idRoutingCache( void ) { + delete [] reachabilities; + delete [] travelTimes; +} + +/* +============ +idRoutingCache::Size +============ +*/ +int idRoutingCache::Size( void ) const { + return sizeof( idRoutingCache ) + size * sizeof( reachabilities[0] ) + size * sizeof( travelTimes[0] ); +} + +/* +============ +idAASLocal::AreaTravelTime +============ +*/ +unsigned short idAASLocal::AreaTravelTime( int areaNum, const idVec3 &start, const idVec3 &end ) const { + float dist; + + dist = ( end - start ).Length(); + + if ( file->GetArea( areaNum ).travelFlags & TFL_CROUCH ) { + dist *= 100.0f / 100.0f; + } else if ( file->GetArea( areaNum ).travelFlags & TFL_WATER ) { + dist *= 100.0f / 150.0f; + } else { + dist *= 100.0f / 300.0f; + } + if ( dist < 1.0f ) { + return 1; + } + return (unsigned short) idMath::FtoiFast( dist ); +} + +/* +============ +idAASLocal::CalculateAreaTravelTimes +============ +*/ +void idAASLocal::CalculateAreaTravelTimes(void) { + int n, i, j, numReach, numRevReach, t, maxt; + byte *bytePtr; + idReachability *reach, *rev_reach; + + // get total memory for all area travel times + numAreaTravelTimes = 0; + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + numReach = 0; + for ( reach = file->GetArea( n ).reach; reach; reach = reach->next ) { + numReach++; + } + + numRevReach = 0; + for ( rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + numRevReach++; + } + numAreaTravelTimes += numReach * numRevReach; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaTravelTimes = (unsigned short *) Mem_Alloc( numAreaTravelTimes * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + bytePtr = (byte *) areaTravelTimes; + + for ( n = 0; n < file->GetNumAreas(); n++ ) { + + if ( !(file->GetArea( n ).flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) { + continue; + } + + // for each reachability that starts in this area calculate the travel time + // towards all the reachabilities that lead towards this area + for ( maxt = i = 0, reach = file->GetArea( n ).reach; reach; reach = reach->next, i++ ) { + assert( i < MAX_REACH_PER_AREA ); + if ( i >= MAX_REACH_PER_AREA ) { +// RAVEN BEGIN +// bdube: better error message + gameLocal.Error( "i >= MAX_REACH_PER_AREA on area %d at %g,%g,%g", n, file->GetArea( n ).center.x, file->GetArea( n ).center.y, file->GetArea( n ).center.z ); +// RAVEN END + } + reach->number = i; + reach->disableCount = 0; + reach->areaTravelTimes = (unsigned short *) bytePtr; + for ( j = 0, rev_reach = file->GetArea( n ).rev_reach; rev_reach; rev_reach = rev_reach->rev_next, j++ ) { + t = AreaTravelTime( n, reach->start, rev_reach->end ); + reach->areaTravelTimes[j] = t; + if ( t > maxt ) { + maxt = t; + } + } + bytePtr += j * sizeof( unsigned short ); + } + + // if this area is a portal + if ( file->GetArea( n ).cluster < 0 ) { + // set the maximum travel time through this portal + file->SetPortalMaxTravelTime( -file->GetArea( n ).cluster, maxt ); + } + } + + assert( ( (unsigned int) bytePtr - (unsigned int) areaTravelTimes ) <= numAreaTravelTimes * sizeof( unsigned short ) ); +} + +/* +============ +idAASLocal::DeleteAreaTravelTimes +============ +*/ +void idAASLocal::DeleteAreaTravelTimes( void ) { + Mem_Free( areaTravelTimes ); + areaTravelTimes = NULL; + numAreaTravelTimes = 0; +} + +/* +============ +idAASLocal::SetupRoutingCache +============ +*/ +void idAASLocal::SetupRoutingCache( void ) { + int i; + byte *bytePtr; + + areaCacheIndexSize = 0; + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndexSize += file->GetCluster( i ).numReachableAreas; + } +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + areaCacheIndex = (idRoutingCache ***) Mem_ClearedAlloc( file->GetNumClusters() * sizeof( idRoutingCache ** ) + + areaCacheIndexSize * sizeof( idRoutingCache *), MA_AAS ); +//RAVEN END + bytePtr = ((byte *)areaCacheIndex) + file->GetNumClusters() * sizeof( idRoutingCache ** ); + for ( i = 0; i < file->GetNumClusters(); i++ ) { + areaCacheIndex[i] = ( idRoutingCache ** ) bytePtr; + bytePtr += file->GetCluster( i ).numReachableAreas * sizeof( idRoutingCache * ); + } + + portalCacheIndexSize = file->GetNumAreas(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + portalCacheIndex = (idRoutingCache **) Mem_ClearedAlloc( portalCacheIndexSize * sizeof( idRoutingCache * ), MA_AAS ); + + areaUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( idRoutingUpdate ),MA_AAS ); + portalUpdate = (idRoutingUpdate *) Mem_ClearedAlloc( (file->GetNumPortals()+1) * sizeof( idRoutingUpdate ),MA_AAS ); + + goalAreaTravelTimes = (unsigned short *) Mem_ClearedAlloc( file->GetNumAreas() * sizeof( unsigned short ),MA_AAS ); +//RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::DeleteClusterCache +============ +*/ +void idAASLocal::DeleteClusterCache( int clusterNum ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !areaCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetCluster( clusterNum ).numReachableAreas; i++ ) { + for ( cache = areaCacheIndex[clusterNum][i]; cache; cache = areaCacheIndex[clusterNum][i] ) { + areaCacheIndex[clusterNum][i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::DeletePortalCache +============ +*/ +void idAASLocal::DeletePortalCache( void ) { + int i; + idRoutingCache *cache; + +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( !portalCacheIndex ) { + return; + } +// RAVEN END + + for ( i = 0; i < file->GetNumAreas(); i++ ) { + for ( cache = portalCacheIndex[i]; cache; cache = portalCacheIndex[i] ) { + portalCacheIndex[i] = cache->next; + UnlinkCache( cache ); + delete cache; + } + } +} + +/* +============ +idAASLocal::ShutdownRoutingCache +============ +*/ +void idAASLocal::ShutdownRoutingCache( void ) { + int i; + + for ( i = 0; i < file->GetNumClusters(); i++ ) { + DeleteClusterCache( i ); + } + + DeletePortalCache(); + + Mem_Free( areaCacheIndex ); + areaCacheIndex = NULL; + areaCacheIndexSize = 0; + Mem_Free( portalCacheIndex ); + portalCacheIndex = NULL; + portalCacheIndexSize = 0; +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + if ( areaUpdate ) { + Mem_Free( areaUpdate ); + areaUpdate = NULL; + } + if ( portalUpdate ) { + Mem_Free( portalUpdate ); + portalUpdate = NULL; + } + if ( goalAreaTravelTimes ) { + Mem_Free( goalAreaTravelTimes ); + goalAreaTravelTimes = NULL; + } +// RAVEN END + + cacheListStart = cacheListEnd = NULL; + totalCacheMemory = 0; +} + +/* +============ +idAASLocal::SetupRouting +============ +*/ +bool idAASLocal::SetupRouting( void ) { +// RAVEN BEGIN +// rjohnson: init these variables for proper shutdown during init of aas + areaCacheIndex = NULL; + portalCacheIndex = NULL; + areaUpdate = NULL; + portalUpdate = NULL; + goalAreaTravelTimes = NULL; +// RAVEN END + + CalculateAreaTravelTimes(); + SetupRoutingCache(); + return true; +} + +/* +============ +idAASLocal::ShutdownRouting +============ +*/ +void idAASLocal::ShutdownRouting( void ) { + DeleteAreaTravelTimes(); + ShutdownRoutingCache(); +} + +/* +============ +idAASLocal::RoutingStats +============ +*/ +void idAASLocal::RoutingStats( void ) const { + idRoutingCache *cache; + int numAreaCache, numPortalCache; + int totalAreaCacheMemory, totalPortalCacheMemory; + + numAreaCache = numPortalCache = 0; + totalAreaCacheMemory = totalPortalCacheMemory = 0; + for ( cache = cacheListStart; cache; cache = cache->time_next ) { + if ( cache->type == CACHETYPE_AREA ) { + numAreaCache++; + totalAreaCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } else { + numPortalCache++; + totalPortalCacheMemory += sizeof( idRoutingCache ) + cache->size * (sizeof( unsigned short ) + sizeof( byte )); + } + } + + gameLocal.Printf( "%6d area cache (%d KB)\n", numAreaCache, totalAreaCacheMemory >> 10 ); + gameLocal.Printf( "%6d portal cache (%d KB)\n", numPortalCache, totalPortalCacheMemory >> 10 ); + gameLocal.Printf( "%6d total cache (%d KB)\n", numAreaCache + numPortalCache, totalCacheMemory >> 10 ); + gameLocal.Printf( "%6d area travel times (%d KB)\n", numAreaTravelTimes, ( numAreaTravelTimes * sizeof( unsigned short ) ) >> 10 ); + gameLocal.Printf( "%6d area cache entries (%d KB)\n", areaCacheIndexSize, ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); + gameLocal.Printf( "%6d portal cache entries (%d KB)\n", portalCacheIndexSize, ( portalCacheIndexSize * sizeof( idRoutingCache * ) ) >> 10 ); +} + +/* +============ +idAASLocal::RemoveRoutingCacheUsingArea +============ +*/ +void idAASLocal::RemoveRoutingCacheUsingArea( int areaNum ) { + int clusterNum; + + clusterNum = file->GetArea( areaNum ).cluster; + if ( clusterNum > 0 ) { + // remove all the cache in the cluster the area is in + DeleteClusterCache( clusterNum ); + } + else { + // if this is a portal remove all cache in both the front and back cluster + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[0] ); + DeleteClusterCache( file->GetPortal( -clusterNum ).clusters[1] ); + } + DeletePortalCache(); +} + +/* +============ +idAASLocal::DisableArea +============ +*/ +void idAASLocal::DisableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) { + return; + } + + file->SetAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::EnableArea +============ +*/ +void idAASLocal::EnableArea( int areaNum ) { + assert( areaNum > 0 && areaNum < file->GetNumAreas() ); + + if ( !( file->GetArea( areaNum ).travelFlags & TFL_INVALID ) ) { + return; + } + + file->RemoveAreaTravelFlag( areaNum, TFL_INVALID ); + + RemoveRoutingCacheUsingArea( areaNum ); +} + +/* +============ +idAASLocal::SetAreaState_r +============ +*/ +bool idAASLocal::SetAreaState_r( int nodeNum, const idBounds &bounds, const int areaContents, bool disabled ) { + int res; + const aasNode_t *node; + bool foundClusterPortal = false; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + // if this area is a cluster portal + if ( file->GetArea( -nodeNum ).contents & areaContents ) { + if ( disabled ) { + DisableArea( -nodeNum ); + } else { + EnableArea( -nodeNum ); + } + foundClusterPortal |= true; + } + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + foundClusterPortal |= SetAreaState_r( node->children[1], bounds, areaContents, disabled ); + nodeNum = node->children[0]; + } + } + + return foundClusterPortal; +} + +/* +============ +idAASLocal::SetAreaState +============ +*/ +bool idAASLocal::SetAreaState( const idBounds &bounds, const int areaContents, bool disabled ) { + idBounds expBounds; + + if ( !file ) { + return false; + } + + expBounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + expBounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + + // find all areas within or touching the bounds with the given contents and disable/enable them for routing + return SetAreaState_r( 1, expBounds, areaContents, disabled ); +} + +/* +============ +idAASLocal::GetBoundsAreas_r +============ +*/ +void idAASLocal::GetBoundsAreas_r( int nodeNum, const idBounds &bounds, idList &areas ) const { + int res; + const aasNode_t *node; + + while( nodeNum != 0 ) { + if ( nodeNum < 0 ) { + areas.Append( -nodeNum ); + break; + } + node = &file->GetNode( nodeNum ); + res = bounds.PlaneSide( file->GetPlane( node->planeNum ) ); + if ( res == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( res == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + GetBoundsAreas_r( node->children[1], bounds, areas ); + nodeNum = node->children[0]; + } + } +} + +/* +============ +idAASLocal::SetObstacleState +============ +*/ +void idAASLocal::SetObstacleState( const idRoutingObstacle *obstacle, bool enable ) { + int i; + const aasArea_t *area; + idReachability *reach, *rev_reach; + bool inside; + + for ( i = 0; i < obstacle->areas.Num(); i++ ) { + + RemoveRoutingCacheUsingArea( obstacle->areas[i] ); + + area = &file->GetArea( obstacle->areas[i] ); + + for ( rev_reach = area->rev_reach; rev_reach; rev_reach = rev_reach->rev_next ) { + + if ( rev_reach->travelType & TFL_INVALID ) { + continue; + } + + inside = false; + + if ( obstacle->bounds.ContainsPoint( rev_reach->end ) ) { + inside = true; + } + else { + for ( reach = area->reach; reach; reach = reach->next ) { + if ( obstacle->bounds.LineIntersection( rev_reach->end, reach->start ) ) { + inside = true; + break; + } + } + } + + if ( inside ) { + if ( enable ) { + rev_reach->disableCount--; + if ( rev_reach->disableCount <= 0 ) { + rev_reach->travelType &= ~TFL_INVALID; + rev_reach->disableCount = 0; + } + } + else { + rev_reach->travelType |= TFL_INVALID; + rev_reach->disableCount++; + } + } + } + } +} + +// RAVEN BEGIN +// cdr: Alternate Routes Bug + +/* +============ +idAASLocal::SetReachabilityState +============ +*/ +void idAASLocal::SetReachabilityState( idReachability* reach, bool enable ) { + if ( enable && reach->travelType&TFL_INVALID) { + reach->disableCount--; + if ( reach->disableCount <= 0 ) { + reach->travelType &= ~TFL_INVALID; + reach->disableCount = 0; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } + } else if (!enable && !(reach->travelType&TFL_INVALID)) { + reach->travelType |= TFL_INVALID; + reach->disableCount++; + RemoveRoutingCacheUsingArea( reach->fromAreaNum ); + } +} +// RAVEN END + + +/* +============ +idAASLocal::AddObstacle +============ +*/ +aasHandle_t idAASLocal::AddObstacle( const idBounds &bounds ) { + idRoutingObstacle *obstacle; + + if ( !file ) { + return -1; + } + + obstacle = new idRoutingObstacle; + obstacle->bounds[0] = bounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacle->bounds[1] = bounds[1] - file->GetSettings().boundingBoxes[0][0]; + GetBoundsAreas_r( 1, obstacle->bounds, obstacle->areas ); + SetObstacleState( obstacle, true ); + + obstacleList.Append( obstacle ); + return obstacleList.Num() - 1; +} + +/* +============ +idAASLocal::RemoveObstacle +============ +*/ +void idAASLocal::RemoveObstacle( const aasHandle_t handle ) { + if ( !file ) { + return; + } + if ( ( handle >= 0 ) && ( handle < obstacleList.Num() ) ) { + SetObstacleState( obstacleList[handle], false ); + + delete obstacleList[handle]; + obstacleList.RemoveIndex( handle ); + } +} + +/* +============ +idAASLocal::RemoveAllObstacles +============ +*/ +void idAASLocal::RemoveAllObstacles( void ) { + int i; + + if ( !file ) { + return; + } + + for ( i = 0; i < obstacleList.Num(); i++ ) { + SetObstacleState( obstacleList[i], false ); + delete obstacleList[i]; + } + obstacleList.Clear(); +} + +/* +============ +idAASLocal::LinkCache + + link the cache in the cache list sorted from oldest to newest cache +============ +*/ +void idAASLocal::LinkCache( idRoutingCache *cache ) const { + + // if the cache is already linked + if ( cache->time_next || cache->time_prev || cacheListStart == cache ) { + UnlinkCache( cache ); + } + + totalCacheMemory += cache->Size(); + + // add cache to the end of the list + cache->time_next = NULL; + cache->time_prev = cacheListEnd; + if ( cacheListEnd ) { + cacheListEnd->time_next = cache; + } + cacheListEnd = cache; + if ( !cacheListStart ) { + cacheListStart = cache; + } +} + +/* +============ +idAASLocal::UnlinkCache +============ +*/ +void idAASLocal::UnlinkCache( idRoutingCache *cache ) const { + + totalCacheMemory -= cache->Size(); + + // unlink the cache + if ( cache->time_next ) { + cache->time_next->time_prev = cache->time_prev; + } else { + cacheListEnd = cache->time_prev; + } + if ( cache->time_prev ) { + cache->time_prev->time_next = cache->time_next; + } else { + cacheListStart = cache->time_next; + } + cache->time_next = cache->time_prev = NULL; +} + +/* +============ +idAASLocal::DeleteOldestCache +============ +*/ +void idAASLocal::DeleteOldestCache( void ) const { + idRoutingCache *cache; + + assert( cacheListStart ); + + // unlink the oldest cache + cache = cacheListStart; + UnlinkCache( cache ); + + // unlink the oldest cache from the area or portal cache index + if ( cache->next ) { + cache->next->prev = cache->prev; + } + if ( cache->prev ) { + cache->prev->next = cache->next; + } + else if ( cache->type == CACHETYPE_AREA ) { + areaCacheIndex[cache->cluster][ClusterAreaNum( cache->cluster, cache->areaNum )] = cache->next; + } + else if ( cache->type == CACHETYPE_PORTAL ) { + portalCacheIndex[cache->areaNum] = cache->next; + } + + delete cache; +} + +/* +============ +idAASLocal::GetAreaReachability +============ +*/ +idReachability *idAASLocal::GetAreaReachability( int areaNum, int reachabilityNum ) const { + idReachability *reach; + + for ( reach = file->GetArea( areaNum ).reach; reach; reach = reach->next ) { + if ( --reachabilityNum < 0 ) { + return reach; + } + } + return NULL; +} + +/* +============ +idAASLocal::ClusterAreaNum +============ +*/ +ID_INLINE int idAASLocal::ClusterAreaNum( int clusterNum, int areaNum ) const { + int side, areaCluster; + + areaCluster = file->GetArea( areaNum ).cluster; + if ( areaCluster > 0 ) { + return file->GetArea( areaNum ).clusterAreaNum; + } + else { + side = file->GetPortal( -areaCluster ).clusters[0] != clusterNum; + return file->GetPortal( -areaCluster ).clusterAreaNum[side]; + } +} + +/* +============ +idAASLocal::UpdateAreaRoutingCache +============ +*/ +void idAASLocal::UpdateAreaRoutingCache( idRoutingCache *areaCache ) const { + int i, nextAreaNum, cluster, badTravelFlags, clusterAreaNum, numReachableAreas; + unsigned short t, startAreaTravelTimes[MAX_REACH_PER_AREA]; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + + // number of reachability areas within this cluster + numReachableAreas = file->GetCluster( areaCache->cluster ).numReachableAreas; + + // number of the start area within the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, areaCache->areaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + return; + } + + areaCache->travelTimes[clusterAreaNum] = areaCache->startTravelTime; + badTravelFlags = ~areaCache->travelFlags; + memset( startAreaTravelTimes, 0, sizeof( startAreaTravelTimes ) ); + + // initialize first update + curUpdate = &areaUpdate[clusterAreaNum]; + curUpdate->areaNum = areaCache->areaNum; + curUpdate->areaTravelTimes = startAreaTravelTimes; + curUpdate->tmpTravelTime = areaCache->startTravelTime; + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the list + while( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).rev_reach; reach; reach = reach->rev_next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->fromAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + // get the cluster number of the area + cluster = nextArea->cluster; + // don't leave the cluster, however do flood into cluster portals + if ( cluster > 0 && cluster != areaCache->cluster ) { + continue; + } + + // get the number of the area in the cluster + clusterAreaNum = ClusterAreaNum( areaCache->cluster, nextAreaNum ); + if ( clusterAreaNum >= numReachableAreas ) { + continue; // should never happen + } + + assert( clusterAreaNum < areaCache->size ); + + // time already travelled plus the traveltime through the current area + // plus the travel time of the reachability towards the next area + t = curUpdate->tmpTravelTime + curUpdate->areaTravelTimes[i] + reach->travelTime; + + if ( !areaCache->travelTimes[clusterAreaNum] || t < areaCache->travelTimes[clusterAreaNum] ) { + + areaCache->travelTimes[clusterAreaNum] = t; + areaCache->reachabilities[clusterAreaNum] = reach->number; // reversed reachability used to get into this area + nextUpdate = &areaUpdate[clusterAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->areaTravelTimes = reach->areaTravelTimes; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetAreaRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetAreaRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + int clusterAreaNum; + idRoutingCache *cache, *clusterCache; + + // number of the area in the cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // pointer to the cache for the area in the cluster + clusterCache = areaCacheIndex[clusterNum][clusterAreaNum]; + // check if cache without undesired travel flags already exists + for ( cache = clusterCache; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetCluster( clusterNum ).numReachableAreas ); + cache->type = CACHETYPE_AREA; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = clusterCache; + if ( clusterCache ) { + clusterCache->prev = cache; + } + areaCacheIndex[clusterNum][clusterAreaNum] = cache; + UpdateAreaRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::UpdatePortalRoutingCache +============ +*/ +void idAASLocal::UpdatePortalRoutingCache( idRoutingCache *portalCache ) const { + int i, portalNum, clusterAreaNum; + unsigned short t; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *cache; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + + curUpdate = &portalUpdate[ file->GetNumPortals() ]; + curUpdate->cluster = portalCache->cluster; + curUpdate->areaNum = portalCache->areaNum; + curUpdate->tmpTravelTime = portalCache->startTravelTime; + + //put the area to start with in the current read list + curUpdate->next = NULL; + curUpdate->prev = NULL; + updateListStart = curUpdate; + updateListEnd = curUpdate; + + // while there are updates in the current list + while( updateListStart ) { + + curUpdate = updateListStart; + // remove the current update from the list + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + // current update is removed from the list + curUpdate->isInList = false; + + cluster = &file->GetCluster( curUpdate->cluster ); + cache = GetAreaRoutingCache( curUpdate->cluster, curUpdate->areaNum, portalCache->travelFlags ); + + // take all portals of the cluster + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + assert( portalNum < portalCache->size ); + portal = &file->GetPortal( portalNum ); + + clusterAreaNum = ClusterAreaNum( curUpdate->cluster, portal->areaNum ); + if ( clusterAreaNum >= cluster->numReachableAreas ) { + continue; + } + + t = cache->travelTimes[clusterAreaNum]; + if ( t == 0 ) { + continue; + } + t += curUpdate->tmpTravelTime; + + if ( !portalCache->travelTimes[portalNum] || t < portalCache->travelTimes[portalNum] ) { + + portalCache->travelTimes[portalNum] = t; + portalCache->reachabilities[portalNum] = cache->reachabilities[clusterAreaNum]; + nextUpdate = &portalUpdate[portalNum]; + if ( portal->clusters[0] == curUpdate->cluster ) { + nextUpdate->cluster = portal->clusters[1]; + } + else { + nextUpdate->cluster = portal->clusters[0]; + } + nextUpdate->areaNum = portal->areaNum; + // add travel time through the actual portal area for the next update + nextUpdate->tmpTravelTime = t + portal->maxAreaTravelTime; + + if ( !nextUpdate->isInList ) { + + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } + else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + } +} + +/* +============ +idAASLocal::GetPortalRoutingCache +============ +*/ +idRoutingCache *idAASLocal::GetPortalRoutingCache( int clusterNum, int areaNum, int travelFlags ) const { + idRoutingCache *cache; + + // check if cache without undesired travel flags already exists + for ( cache = portalCacheIndex[areaNum]; cache; cache = cache->next ) { + if ( cache->travelFlags == travelFlags ) { + break; + } + } + // if no cache found + if ( !cache ) { + cache = new idRoutingCache( file->GetNumPortals() ); + cache->type = CACHETYPE_PORTAL; + cache->cluster = clusterNum; + cache->areaNum = areaNum; + cache->startTravelTime = 1; + cache->travelFlags = travelFlags; + cache->prev = NULL; + cache->next = portalCacheIndex[areaNum]; + if ( portalCacheIndex[areaNum] ) { + portalCacheIndex[areaNum]->prev = cache; + } + portalCacheIndex[areaNum] = cache; + UpdatePortalRoutingCache( cache ); + } + LinkCache( cache ); + return cache; +} + +/* +============ +idAASLocal::RouteToGoalArea +============ +*/ +bool idAASLocal::RouteToGoalArea( int areaNum, const idVec3 origin, int goalAreaNum, int travelFlags, int &travelTime, idReachability **reach ) const { + int clusterNum, goalClusterNum, portalNum, i, clusterAreaNum; + unsigned short int t, bestTime; + const aasPortal_t *portal; + const aasCluster_t *cluster; + idRoutingCache *areaCache, *portalCache, *clusterCache; + idReachability *bestReach, *r, *nextr; + + travelTime = 0; + *reach = NULL; + + if ( !file ) { + return false; + } + + if ( areaNum == goalAreaNum ) { + return true; + } + + if ( areaNum <= 0 || areaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: areaNum %d out of range\n", areaNum ); +// RAVEN END + return false; + } + if ( goalAreaNum <= 0 || goalAreaNum >= file->GetNumAreas() ) { +// RAVEN BEGIN +// bgeisler: +// gameLocal.Printf( "RouteToGoalArea: goalAreaNum %d out of range\n", goalAreaNum ); +// RAVEN END + return false; + } + + while( totalCacheMemory > MAX_ROUTING_CACHE_MEMORY ) { + DeleteOldestCache(); + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the source area is a cluster portal, read directly from the portal cache + if ( clusterNum < 0 ) { + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + *reach = GetAreaReachability( areaNum, portalCache->reachabilities[-clusterNum] ); + travelTime = portalCache->travelTimes[-clusterNum] + AreaTravelTime( areaNum, origin, (*reach)->start ); + return true; + } + + bestTime = 0; + bestReach = NULL; + + // check if the goal area is a portal of the source area cluster + if ( goalClusterNum < 0 ) { + portal = &file->GetPortal( -goalClusterNum ); + if ( portal->clusters[0] == clusterNum || portal->clusters[1] == clusterNum) { + goalClusterNum = clusterNum; + } + } + + // if both areas are in the same cluster + if ( clusterNum > 0 && goalClusterNum > 0 && clusterNum == goalClusterNum ) { + clusterCache = GetAreaRoutingCache( clusterNum, goalAreaNum, travelFlags ); + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + if ( clusterCache->travelTimes[clusterAreaNum] ) { + bestReach = GetAreaReachability( areaNum, clusterCache->reachabilities[clusterAreaNum] ); + bestTime = clusterCache->travelTimes[clusterAreaNum] + AreaTravelTime( areaNum, origin, bestReach->start ); + } + else { + clusterCache = NULL; + } + } + else { + clusterCache = NULL; + } + + clusterNum = file->GetArea( areaNum ).cluster; + goalClusterNum = file->GetArea( goalAreaNum ).cluster; + + // if the goal area is a portal + if ( goalClusterNum < 0 ) { + // just assume the goal area is part of the front cluster + portal = &file->GetPortal( -goalClusterNum ); + goalClusterNum = portal->clusters[0]; + } + // get the portal routing cache + portalCache = GetPortalRoutingCache( goalClusterNum, goalAreaNum, travelFlags ); + + // the cluster the area is in + cluster = &file->GetCluster( clusterNum ); + // current area inside the current cluster + clusterAreaNum = ClusterAreaNum( clusterNum, areaNum ); + // if the area is not a reachable area + if ( clusterAreaNum >= cluster->numReachableAreas) { + return false; + } + + // find the portal of the source area cluster leading towards the goal area + for ( i = 0; i < cluster->numPortals; i++ ) { + portalNum = file->GetPortalIndex( cluster->firstPortal + i ); + + // if the goal area isn't reachable from the portal + if ( !portalCache->travelTimes[portalNum] ) { + continue; + } + + portal = &file->GetPortal( portalNum ); + // get the cache of the portal area + areaCache = GetAreaRoutingCache( clusterNum, portal->areaNum, travelFlags ); + // if the portal is not reachable from this area + if ( !areaCache->travelTimes[clusterAreaNum] ) { + continue; + } + + r = GetAreaReachability( areaNum, areaCache->reachabilities[clusterAreaNum] ); + + if ( clusterCache ) { + // if the next reachability from the portal leads back into the cluster + nextr = GetAreaReachability( portal->areaNum, portalCache->reachabilities[portalNum] ); + if ( file->GetArea( nextr->toAreaNum ).cluster < 0 || file->GetArea( nextr->toAreaNum ).cluster == clusterNum ) { + continue; + } + } + + // the total travel time is the travel time from the portal area to the goal area + // plus the travel time from the source area towards the portal area + t = portalCache->travelTimes[portalNum] + areaCache->travelTimes[clusterAreaNum]; + // NOTE: Should add the exact travel time through the portal area. + // However we add the largest travel time through the portal area. + // We cannot directly calculate the exact travel time through the portal area + // because the reachability used to travel into the portal area is not known. + t += portal->maxAreaTravelTime; + + // if the time is better than the one already found + if ( !bestTime || t < bestTime ) { + bestReach = r; + bestTime = t; + } + } + + if ( !bestReach ) { + return false; + } + + *reach = bestReach; + travelTime = bestTime; + + return true; +} + +/* +============ +idAASLocal::TravelTimeToGoalArea +============ +*/ +int idAASLocal::TravelTimeToGoalArea( int areaNum, const idVec3 &origin, int goalAreaNum, int travelFlags ) const { + int travelTime; + idReachability *reach; + + if ( !file ) { + return 0; + } + + if ( !RouteToGoalArea( areaNum, origin, goalAreaNum, travelFlags, travelTime, &reach ) ) { + return 0; + } + return travelTime; +} + +/* +============ +idAASLocal::FindNearestGoal +============ +*/ +bool idAASLocal::FindNearestGoal( aasGoal_t &goal, int areaNum, const idVec3 origin, const idVec3 &target, int travelFlags, float minDistance, float maxDistance, aasObstacle_t *obstacles, int numObstacles, idAASCallback &callback ) const { + int i, j, k, badTravelFlags, nextAreaNum; + aasGoal_t bestGoal; + unsigned short t, bestTravelTime; + idRoutingUpdate *updateListStart, *updateListEnd, *curUpdate, *nextUpdate; + idReachability *reach; + const aasArea_t *nextArea; + idVec3 v1, v2, p; + float targetDist, dist; + + if ( file == NULL || areaNum <= 0 ) { + goal.areaNum = areaNum; + goal.origin = origin; + return false; + } + + // setup obstacles + for ( k = 0; k < numObstacles; k++ ) { + obstacles[k].expAbsBounds[0] = obstacles[k].absBounds[0] - file->GetSettings().boundingBoxes[0][1]; + obstacles[k].expAbsBounds[1] = obstacles[k].absBounds[1] - file->GetSettings().boundingBoxes[0][0]; + } + + badTravelFlags = ~travelFlags; + SIMDProcessor->Memset( goalAreaTravelTimes, 0, file->GetNumAreas() * sizeof( unsigned short ) ); + + targetDist = (target - origin).Length(); + + // initialize first update + curUpdate = &areaUpdate[areaNum]; + curUpdate->areaNum = areaNum; + curUpdate->tmpTravelTime = 0; + curUpdate->start = origin; + curUpdate->next = NULL; + curUpdate->prev = NULL; + + callback.Init ( ); + + // if the first area is valid goal, just return the origin + curUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, areaNum, origin, minDistance, maxDistance, &origin, goal ); + if ( curUpdate->cluster == idAASCallback::TEST_OK ) { + callback.Finish ( ); + return true; + } + + updateListStart = curUpdate; + updateListEnd = curUpdate; + + bestTravelTime = 0; + bestGoal.areaNum = 0; + + // while there are updates in the list + while ( updateListStart ) { + + curUpdate = updateListStart; + if ( curUpdate->next ) { + curUpdate->next->prev = NULL; + } + else { + updateListEnd = NULL; + } + updateListStart = curUpdate->next; + + curUpdate->isInList = false; + + // if we already found a closer location + if ( bestTravelTime && curUpdate->tmpTravelTime >= bestTravelTime ) { + continue; + } + + for ( i = 0, reach = file->GetArea( curUpdate->areaNum ).reach; reach; reach = reach->next, i++ ) { + + // if the reachability uses an undesired travel type + if ( reach->travelType & badTravelFlags ) { + continue; + } + + // next area the reversed reachability leads to + nextAreaNum = reach->toAreaNum; + nextArea = &file->GetArea( nextAreaNum ); + + // if traveling through the next area requires an undesired travel flag + if ( nextArea->travelFlags & badTravelFlags ) { + continue; + } + + t = curUpdate->tmpTravelTime + + AreaTravelTime( curUpdate->areaNum, curUpdate->start, reach->start ) + + reach->travelTime; + + // project target origin onto movement vector through the area + v1 = reach->end - curUpdate->start; + v1.Normalize(); + v2 = target - curUpdate->start; + p = curUpdate->start + (v2 * v1) * v1; + + // get the point on the path closest to the target + for ( j = 0; j < 3; j++ ) { + if ( (p[j] > curUpdate->start[j] + 0.1f && p[j] > reach->end[j] + 0.1f) || + (p[j] < curUpdate->start[j] - 0.1f && p[j] < reach->end[j] - 0.1f) ) { + break; + } + } + if ( j >= 3 ) { + dist = (target - p).Length(); + } else { + dist = (target - reach->end).Length(); + } + + // avoid moving closer to the target + if ( dist < targetDist ) { + t += ( targetDist - dist ) * 10; + } + + // if we already found a closer location + if ( bestTravelTime && t >= bestTravelTime ) { + continue; + } + + // if this is not the best path towards the next area + if ( goalAreaTravelTimes[nextAreaNum] && t >= goalAreaTravelTimes[nextAreaNum] ) { + continue; + } + + // path may not go through any obstacles + for ( k = 0; k < numObstacles; k++ ) { + // If the start of the movement vector is inside the expanded bounds then we are already too + // close to the obstacle, so use its unexpanded bounds instead. + if ( obstacles[k].expAbsBounds.ContainsPoint ( curUpdate->start ) ) { + if ( obstacles[k].absBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + // if the movement vector intersects the expanded obstacle bounds + } else if ( obstacles[k].expAbsBounds.LineIntersection( curUpdate->start, reach->end ) ) { + break; + } + } + if ( k < numObstacles ) { + continue; + } + + goalAreaTravelTimes[nextAreaNum] = t; + nextUpdate = &areaUpdate[nextAreaNum]; + nextUpdate->areaNum = nextAreaNum; + nextUpdate->tmpTravelTime = t; + nextUpdate->start = reach->end; + + // if we are not allowed to fly + if ( badTravelFlags & TFL_FLY ) { + // avoid areas near ledges + if ( file->GetArea( nextAreaNum ).flags & AREA_LEDGE ) { + nextUpdate->tmpTravelTime += LEDGE_TRAVELTIME_PANALTY; + } + } + + // If outside of max distance skip this area + idVec3 point = origin; + float areaDist; + file->PushPointIntoAreaNum ( nextAreaNum, point ); + areaDist = (origin-point).LengthFast(); + if ( maxDistance > 0.0f && areaDist > maxDistance ) { + curUpdate->cluster = idAASCallback::TEST_BADAREA; + continue; + } + + // don't put goal near a ledge + if ( !( nextArea->flags & AREA_LEDGE ) ) { + + // add travel time through the area + t += AreaTravelTime( reach->toAreaNum, reach->end, nextArea->center ); + + if ( !bestTravelTime || t < bestTravelTime ) { + // if the area is not visible to the target + nextUpdate->cluster = (int)callback.Test ( (idAASLocal*)this, reach->toAreaNum, origin, minDistance, maxDistance, NULL, bestGoal ); + switch ( nextUpdate->cluster ) { + case idAASCallback::TEST_OK: + bestTravelTime = t; + break; + case idAASCallback::TEST_BADAREA: + if ( curUpdate->cluster != idAASCallback::TEST_BADAREA ) { + continue; + } + break; + } + } + } + + if ( !nextUpdate->isInList ) { + nextUpdate->next = NULL; + nextUpdate->prev = updateListEnd; + if ( updateListEnd ) { + updateListEnd->next = nextUpdate; + } else { + updateListStart = nextUpdate; + } + updateListEnd = nextUpdate; + nextUpdate->isInList = true; + } + } + } + + callback.Finish ( ); + + if ( bestGoal.areaNum ) { + goal = bestGoal; + return true; + } + + return false; +} diff --git a/src/mpgame/ai/AAS_tactical.cpp b/src/mpgame/ai/AAS_tactical.cpp new file mode 100644 index 0000000..e36670a --- /dev/null +++ b/src/mpgame/ai/AAS_tactical.cpp @@ -0,0 +1,1789 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/////////////////////////////////////////////////////////////////////////////////// +// rvAAS_tactical.cpp +// +// AAST Tactical Search Parameters Documentation +// --------------------------------------------- +// There are 10 tests at your disposal when creating search functions, 3 +// sets of 3 and one special case test. Each of these 10 tests can have +// hard min and max limits, as well as a "soft" weight which will determine how +// highly the feature is ranked against other features. Let's examine a +// diagram of what these 10 tests are: +// +// [F] ######### Key: +// | ######### [F] = Focus Test Set (enemy, or forward projection) +// / ######### [O] = Owner Test Set (actor who is doing the search) +// [P] ######### [P] = Path Test Set (line between owner and focus) +// | ######### <-> = Advance Test (In front / behind owner) +// / x ##### x = Feature (feature being tested) +// <---[O]---> ##### ### = Walls +// #################### +// #################### +// #################### +// +// Each Test Set Has The Following: +// - Distance RANGE=[ 0, 1] WEIGHT=(-1=Close, 1=Far) +// Distance is computed from the origin of the subject to the origin of the +// feature being tested (x in the above diagram). Distance will be a common +// test to use on all three sets, with all mannor of clamped ranges and +// sort values. Most common though will be to sort close to the owner so +// as to minimize how long it will take for the AI to get to the spot. +// +// - FacingDot RANGE=[-1, 1] WEIGHT=(-1=Behind, 1=In Front) +// FacingDot is computed with the dotproduct of the subject's facing and +// the feature's normal. This too will be a common test to compare with +// all three subjects. With it you can prefer points in front or behind +// things. +// +// - DirectionDot RANGE=[-1, 1] WEIGHT=(-1=Toward, 1=Away) +// DirectionDot is computed by subtracing the origins of the feature and +// the subject, normalizing and taking the dotproduct of the result with +// the feature's normal. Usually, you will want a negative weight on this +// test to comare how close the feature is pointing at the subject (usually +// tested against Focus) +// +// Lastly, there is a special "additional" test for "advance": +// - Advance RANGE=[-1, 1] WEIGHT=(-1=Backward, 1=Forward) +// Advance is computed using the owner direction dot with the path facing. +// This test will tell you how much the feature is between the owner and +// the focus, reguardless of what direction either is facing at the time. +// As a result, it approximates "advancing". +// +/////////////////////////////////////////////////////////////////////////////////// +#include "../../idlib/precompiled.h" +#pragma hdrstop + + +/////////////////////////////////////////////////////////////////////////////////// +// Includes +/////////////////////////////////////////////////////////////////////////////////// +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_local.h" +#include "AAS_tactical.h" + +/////////////////////////////////////////////////////////////////////////////////// +// Global::CONTANER SIZES AND OTHER LIMITS +/////////////////////////////////////////////////////////////////////////////////// +const int MAX_FEATURE_LIST = 20; +const int MAX_AREAS_TOUCHED = 450; + +/////////////////////////////////////////////////////////////////////////////////// +// Global::FEATURE TESTING DISTANCES +/////////////////////////////////////////////////////////////////////////////////// +const float MAX_DISTANCE = 650.0f; +const float MIN_DISTANCE = 72.0f; +const float MIN_NEAR_DISTANCE = 112.0f; +const float FROM_ENEMY_PROJECT = 350.0f; +const float TEST_TEAMMATE_DIST = 150.0f; + + + + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Normal +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Normal() +{ + static idVec3 n(0,0,0); + n[0] = ((float)(normalx) / 127.0f) - 1.0f; + n[1] = ((float)(normaly) / 127.0f) - 1.0f; + return n; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::Origin() +/////////////////////////////////////////////////////////////////////////////// +idVec3& aasFeature_s::Origin() +{ + static idVec3 o; + o.Set((float)x, (float)y, (float)z); + return o; +} + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_s::GetLookPos() +/////////////////////////////////////////////////////////////////////////////// +int aasFeature_s::GetLookPos( idVec3& lookPos, const idVec3& aimAtOrigin, const float leanDistance ) +{ + static idVec3 up(0.0f,0.0f,1.0f); + static idVec3 direction; + static idVec3 right; + static float rightDot; + static float distance; + + lookPos = Origin(); + lookPos[2] += height - leanDistance; + direction = aimAtOrigin - lookPos; + distance = direction.NormalizeFast(); + right = Normal().Cross(up); + rightDot = right * direction; + + + // Check For Optimal Conditions + //------------------------------ + if (flags&FEATURE_LOOK_OVER && fabsf(rightDot)<0.2f) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT && rightDot>0.0f) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT && rightDot<0.0f) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + + // So Nothing Matches Perfectly, Let's Try Fallback Cases In This Order + //---------------------------------------------------------------------- + if (flags&FEATURE_LOOK_OVER) + { + lookPos[2] += leanDistance*2.0f; // CDR_TODO: Hard coded numbers make me sad + return FEATURE_LOOK_OVER; + } + + if (flags&FEATURE_LOOK_RIGHT) + { + lookPos += right * leanDistance; + return FEATURE_LOOK_RIGHT; + } + + if (flags&FEATURE_LOOK_LEFT) + { + lookPos -= right * leanDistance; + return FEATURE_LOOK_LEFT; + } + + // This Is Odd, There Must Be No Look Flags On This Feature At All + //----------------------------------------------------------------- + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvSortReach +// +// This structure is used by the search heap below to sort areas by actual +// distance from the start point to do a distance based BFS instead of a +// least links based BFS. +/////////////////////////////////////////////////////////////////////////////// +struct rvSortReach +{ + int mAreaNum; + idReachability* mReach; + float mDistance; + + bool operator<(const rvSortReach& r) const + { + return (mDistance>r.mDistance); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTest +// +// A test is a mechanism to weight values and to provide min and max bounds. +/////////////////////////////////////////////////////////////////////////////// +struct rvTest +{ + float mMin; + float mMax; + float mWeight; + float mValue; + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteFloat(mMin); + savefile->WriteFloat(mMax); + savefile->WriteFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadFloat(mMin); + savefile->ReadFloat(mMax); + savefile->ReadFloat(mWeight); + } + + /////////////////////////////////////////////////////////////////////////// + // Reset - Sets the values to defaults + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mMax = 1.0f; + mMin = -1.0f; + mWeight = 0.0f; + mValue = 0.0f; + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Simple compute weight + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return mValue * mWeight; + } + + /////////////////////////////////////////////////////////////////////////// + // Test - Records the value out of the range and returns true if succeeded + /////////////////////////////////////////////////////////////////////////// + bool Test(float Value, float MaxScale=1.0f) + { + if (mMin>-1.0f || mMax<1.0f || mWeight!=0.0f) + { + mValue = Value; + if (MaxScale!=1.0f) + { + mValue /= MaxScale; + } + mValue = (mValue-mMin) / (mMax-mMin); // Now Scale It [0.0, 1.0] Of Min And Max + return (mValue>=0.0f && mValue<=1.0f); // If Not In [0.0, 1.0], Test Fails + } + return true; // Test Is Not Active + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Returns the abs of the weight, and adds to negatives + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + if (mWeight<0.0f) + { + negatives += mWeight; + } + return fabsf(mWeight); + } + + void DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction); + void DrawDebugInfo(const idVec4 color, const idVec3& origin); +}; + +/////////////////////////////////////////////////////////////////////////////// +// rvTestSet +// +// Test sets are designed to be used by the rvAASTacticalSensorLocal class to compute +// various vectors against a given feature. +/////////////////////////////////////////////////////////////////////////////// +struct rvTestSet +{ + // Parameters + //------------ + bool mProjectOrigin; // If True, Origin Is Cast Out Along mFacing Vector + idVec3 mOrigin; // Position Of The Test Subject + idVec3 mFacing; // Orientation Of The Test Subject + + // Computed During Test() + //------------------------ + rvTest mDistance; // Resulting Distance To Feature + rvTest mFacingDot; // Resulting Facing Dot Product To Feature + rvTest mDirectionDot; // Resulting Direction Dot Product To Feature + idVec3 mDirection; // Resulting Direction To Feature + + /////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////// + rvTestSet( void ) + : mOrigin(0,0,0), mFacing(0,0,0), mProjectOrigin(false) + { + } + + /////////////////////////////////////////////////////////////////////////// + // Save + /////////////////////////////////////////////////////////////////////////// + void Save(idSaveGame *savefile) + { + savefile->WriteBool(mProjectOrigin); + savefile->WriteVec3(mOrigin); + savefile->WriteVec3(mFacing); + mDistance.Save(savefile); + mFacingDot.Save(savefile); + mDirectionDot.Save(savefile); + } + + /////////////////////////////////////////////////////////////////////////// + // Restore + /////////////////////////////////////////////////////////////////////////// + void Restore(idRestoreGame *savefile) + { + savefile->ReadBool(mProjectOrigin); + savefile->ReadVec3(mOrigin); + savefile->ReadVec3(mFacing); + mDistance.Restore(savefile); + mFacingDot.Restore(savefile); + mDirectionDot.Restore(savefile); + } + + + /////////////////////////////////////////////////////////////////////////// + // Reset - Restets all sub tests + /////////////////////////////////////////////////////////////////////////// + void Reset() + { + mProjectOrigin = false; + mDirectionDot.Reset(); + mFacingDot.Reset(); + mDistance.Reset(); +// bdube: Had to comment this out becaue it would break the test function which checks for non defaults +// mDistance.mMin = 0.0f; // special case (default would be -1.0f because all others are dot products) + } + + /////////////////////////////////////////////////////////////////////////// + // Weight - Adds up the weights of the sub tests + /////////////////////////////////////////////////////////////////////////// + float Weight() + { + return (mDistance.Weight() + mFacingDot.Weight() + mDirectionDot.Weight()); + } + + /////////////////////////////////////////////////////////////////////////// + // WeightRange - Computes weight range of all sub tests + /////////////////////////////////////////////////////////////////////////// + float WeightRange(float& negatives) + { + return (mDistance.WeightRange(negatives) + mFacingDot.WeightRange(negatives) + mDirectionDot.WeightRange(negatives)); + } + + /////////////////////////////////////////////////////////////////////////// + // Test - This is the actuall feature test + /////////////////////////////////////////////////////////////////////////// + bool Test(aasFeature_t* f, float distance=0.0f) + { + // First Compute The Direction + //----------------------------- + mDirection = f->Origin() - mOrigin; + + // If Project Origin Is True, Then Move Origin Along Facing Vector + //----------------------------------------------------------------- + if (mProjectOrigin) + { + mDirection.ProjectOntoVector(mFacing); + mOrigin += mDirection; + mDirection = f->Origin() - mOrigin; + } + + // If No Override On Distance, Compute It By Normalizing The Direction + //--------------------------------------------------------------------- + if (!distance) + { + distance = mDirection.Normalize(); + } + else + { + mDirection.Normalize(); + } + + + // THE DISTANCE TEST + //------------------- + if (!mDistance.Test(distance, MAX_DISTANCE)) + { + return false; + } + + // THE FACING TEST + //----------------- + if (!mFacingDot.Test(f->Normal()*mFacing)) + { + return false; + } + + // THE DIRECTION TEST + //-------------------- + if (!mDirectionDot.Test(f->Normal()*mDirection)) + { + return false; + } + return true; + } + + /////////////////////////////////////////////////////////////////////////// + // SetupOriginAndFacing - Called For Each Test Set + /////////////////////////////////////////////////////////////////////////// + void SetupOriginAndFacing(const idEntity* ent, const idVec3* originOverride=0, const idVec3* facingOverride=0) + { + if (!ent) + { + mOrigin = (originOverride)?(*originOverride):(vec3_origin); + mFacing = (facingOverride)?(*facingOverride):(vec3_origin); + } + else + { + const idActor* entActor = dynamic_cast(ent); // bleh. Base entity class should properly return origin, angles, and forward vector + + mOrigin = (originOverride)?(*originOverride):(ent->GetPhysics()->GetOrigin()); + mFacing = (facingOverride)?(*facingOverride):(entActor?entActor->viewAxis[0]:ent->GetPhysics()->GetAxis(0)[0]); + } + + mFacing[2] = 0; + mFacing.Normalize(); + } + + /////////////////////////////////////////////////////////////////////////// + // ProjectOriginForward - Used By Several Setup Options To Project Origin + // Along Facing Direction Some + /////////////////////////////////////////////////////////////////////////// + void ProjectOriginForward(float distance, float xyRange=0.0f, bool randomFacing=0.0f) + { + mOrigin += (mFacing * distance); + if (xyRange) + { + mOrigin[0] += rvRandom::flrand(-xyRange, xyRange); + mOrigin[1] += rvRandom::flrand(-xyRange, xyRange); + } + if (randomFacing) + { + mFacing[0] = rvRandom::flrand(-1.0f, 1.0f); + mFacing[1] = rvRandom::flrand(-1.0f, 1.0f); + } + mFacing[2] = 0.0f; + mFacing.Normalize(); + } + + void DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin); +}; + + + + + +/////////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +// +// This is the local implimentation of the rvAASTacticalSensor interface. It +// contains all the various local data and functions needed to execute a +// search of the tactical data in AAS. +/////////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensorLocal : rvAASTacticalSensor +{ + // Owner + /////////////////////////////////////////////////////////////////////////////// + idActor* mOwner; // Owner Of The Sensor + idAI* mOwnerAI; // Owner As Already Cast To AI Type + + // Search Parameters + /////////////////////////////////////////////////////////////////////////////// + idStr mSearchName; // Current Search Name + int mFlagsMatchAny; // Features must match AT LEAST ONE of these flags + int mFlagsMatchAll; // Features must match ALL of these flags + int mFlagsMatchNone; // Features must match NONE of these flags + int mFeaturesSearchMax; // Maximum number of features to extract from the grid + int mFeaturesFinalMax; // After sorting, prune list down to this size + rvTestSet mFromOwner; // Test Set For Owner Relation + rvTestSet mFromEnemy; // Test Set For Focus Relation + rvTestSet mFromTether; // Test Set for Tether Relation + rvTestSet mFromPath; // Test Set For Path Relation + rvTest mAdvance; // Single Test For Advance / Retreat + rvTest mAssignment; // Single Test For Assignment Direction Dot Product + rvTest mLeanNormal; // Single Test For Lean Normal Biasing + idVec3 mAssignmentDirection; // Used By Assignment Test + bool mAssignmentValid; // Turns On And Off The Assignment Test + idEntityPtr mEnemyOverride; // Overrides Enemy Pointer To Any Entity + + // Search & Update Results + /////////////////////////////////////////////////////////////////////////////// + idList mFeatures; // The list of all features found in the most recent search + idVec3 mReservedOrigin; // Origin of feature that is currently reserved + aasFeature_t* mReserved; // Which feature is currently reserved + aasFeature_t* mNear; // Which feature is closest + aasFeature_t* mLook; // Which feature to look down + int mLookStartTime; + float mLookStopDist; + + + + + + // Local API + /////////////////////////////////////////////////////////////////////////////// + rvAASTacticalSensorLocal(); + ~rvAASTacticalSensorLocal(); + void Update(); + void Save(idSaveGame *savefile); + void Restore(idRestoreGame *savefile); + void Clear(); + void DrawDebugInfo(); + + // Search + /////////////////////////////////////////////////////////////////////////////// + void Search(); + void SearchReset(idEntity* enemyOverride=0, float ownerRangeMin=0.0f, float ownerRangeMax=1.0f); + void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f); + void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f); + void SearchHide(idEntity* from=0); + void SearchFlank(); + void SearchAdvance(); + void SearchRetreat(); + void SearchAmbush(); + void SearchDebug(); + float SearchComputeWeightRange(float& rangeNegative); + float SearchComputeWeight(); + + // Feature Testing + /////////////////////////////////////////////////////////////////////////////// + void TestSetupCurrentValues(); + bool TestValid(aasFeature_t* f, float walkDistanceToFeature); + bool TestValidWithCurrentState(aasFeature_t* f=0); + + // Feature Reservation + /////////////////////////////////////////////////////////////////////////////// + void Reserve(aasFeature_t* f); + + // Access To Results + /////////////////////////////////////////////////////////////////////////////// + int FeatureCount() {return mFeatures.Num();} + aasFeature_t* Feature(int i) {return mFeatures[i];} + aasFeature_t* Near() const {return mNear;} + aasFeature_t* Look() const {return mLook;} + aasFeature_t* Reserved() const {return mReserved;} + const idVec3& ReservedOrigin() const {return mReservedOrigin;} +}; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Objects +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal* mSensor; +float mDebugRadius; + +/////////////////////////////////////////////////////////////////////////////// +// Global::Typedefines +/////////////////////////////////////////////////////////////////////////////// +typedef idEntityPtr TEntPtr; +typedef aasFeature_t* TFeaturePtr; + + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor::CREATE_SENSOR +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensor* rvAASTacticalSensor::CREATE_SENSOR(idActor* owner) +{ + rvAASTacticalSensorLocal* nSensor = new rvAASTacticalSensorLocal(); + nSensor->mOwner = owner; + nSensor->mOwnerAI = dynamic_cast(owner); + return nSensor; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::rvAASTacticalSensorLocal() +{ + mOwner = 0; + mOwnerAI = 0; + Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Destructor +/////////////////////////////////////////////////////////////////////////////// +rvAASTacticalSensorLocal::~rvAASTacticalSensorLocal() +{ + Reserve(0); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Clear +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Clear() +{ + mReserved = 0; + mNear = 0; + mLook = 0; + mSearchName = ""; + mFeatures.Clear(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Save +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Save(idSaveGame *savefile) +{ + savefile->WriteString(mSearchName); + savefile->WriteInt(mFlagsMatchAny); + savefile->WriteInt(mFlagsMatchAll); + savefile->WriteInt(mFlagsMatchNone); + savefile->WriteInt(mFeaturesSearchMax); + savefile->WriteInt(mFeaturesFinalMax); + mFromOwner.Save(savefile); + mFromEnemy.Save(savefile); + mFromTether.Save(savefile); + mFromPath.Save(savefile); + mAdvance.Save(savefile); + mAssignment.Save(savefile); + mLeanNormal.Save(savefile); + savefile->WriteVec3(mAssignmentDirection); + savefile->WriteBool(mAssignmentValid); + mEnemyOverride.Save(savefile); + +// cnicholson: NOTE: The following 4 vars are set to 0 / cleared in the restore, so don't save them. + // NOSAVE: idList mFeatures; + // NOSAVE: savefile->WriteVec3(mFeatures); + // NOSAVE: aasFeature_t* mReserved; + // NOSAVE: aasFeature_t* mNear; + // NOSAVE: aasFeature_t* mLook; + savefile->WriteInt(mLookStartTime); // cnicholson: Added unsaved var + savefile->WriteFloat(mLookStopDist);// cnicholson: Added unsaved var +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Restore +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Restore(idRestoreGame *savefile) +{ + // Clear Old Data + //---------------- + mFeatures.Clear(); + mNear = 0; + mLook = 0; + mReserved = 0; + + // Read The Save File Search Parameters + //-------------------------------------- + savefile->ReadString(mSearchName); + savefile->ReadInt(mFlagsMatchAny); + savefile->ReadInt(mFlagsMatchAll); + savefile->ReadInt(mFlagsMatchNone); + savefile->ReadInt(mFeaturesSearchMax); + savefile->ReadInt(mFeaturesFinalMax); + mFromOwner.Restore(savefile); + mFromEnemy.Restore(savefile); + mFromTether.Restore(savefile); + mFromPath.Restore(savefile); + mAdvance.Restore(savefile); + mAssignment.Restore(savefile); + mLeanNormal.Restore(savefile); + savefile->ReadVec3(mAssignmentDirection); + savefile->ReadBool(mAssignmentValid); + mEnemyOverride.Restore(savefile); +// Search(); + + savefile->ReadInt(mLookStartTime); // cnicholson: Added unrestored var + savefile->ReadFloat(mLookStopDist);// cnicholson: Added unrestored var +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Update +// +// If called regularly, this function will handle drawing debug information +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Update() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden()) + { + return; + } + + idAASFile* file = aas->GetFile(); + + idVec3 velocityFwd = mOwner->GetPhysics()->GetLinearVelocity(); + const idVec3& ownerOrigin = mOwner->GetPhysics()->GetOrigin(); + int ownerAreaNum = mOwnerAI ? mOwnerAI->PointReachableAreaNum ( ownerOrigin ) : aas->PointReachableAreaNum(ownerOrigin, mOwner->GetPhysics()->GetBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aasFeature_t* feature = 0; + aasArea_t& area = file->GetArea(ownerAreaNum); + idActor* teammate = NULL; + float featureDistance = 0.0f; + float featureDotLeft = 0.0f; + float featureDotDirection = 0.0f; + float teammateDistance = 0.0f; + float closestDistance = 0.0f; + idVec3 velocityLeft; + idVec3 velocityDown; + idVec3 featureDirection; + idVec3 teammateDirection; + + + + // Update And Possibly Clear The Near Feature + //-------------------------------------------- + if (mNear) + { + closestDistance = mNear->Origin().Dist(ownerOrigin); + if (closestDistance>MIN_NEAR_DISTANCE) + { + mNear = 0; + } + } + + + // Search For Features In This Area That Are Close To Owner Origin + //----------------------------------------------------------------- + if (area.numFeatures) + { + for (int areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + if (feature!=mNear) + { + featureDirection = feature->Origin(); + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + if (featureDistance1.0f) + { + // Compute Velocity Vectors + //-------------------------- + velocityFwd.NormalizeFast(); + velocityFwd.NormalVectors(velocityLeft, velocityDown); + + + // Check If We Should Clear The Look Feature + //------------------------------------------- + if (mLook) + { + const idVec3& featureOrigin = mLook->Origin(); + const idVec3& featureNormal = mLook->Normal(); + + // Too Far? + //---------- + if (featureOrigin.Dist(ownerOrigin)>mLookStopDist) + { + mLook = 0; + } + + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + mLook = 0; + break; + } + } + teammate = NULL; + } + + // And, If We Are Moving, Check The Near Feature To See If It Qualifies As A Look Feature + //---------------------------------------------------------------------------------------- + if (mNear && mNear!=mLook && (gameLocal.GetTime() - mLookStartTime)>3000) + { + const idVec3& featureOrigin = mNear->Origin(); + const idVec3& featureNormal = mNear->Normal(); + + // Compute Feature Direction + //--------------------------- + featureDirection = featureOrigin; + featureDirection -= ownerOrigin; + featureDistance = featureDirection.NormalizeFast(); + + // Must Be Behind Me (I've Alreay Walked Past It) + //------------------------------------------------ + if (featureDistance>16.0f && featureDirection*velocityFwd<0.0f) + { + // Must Be Facing Away From Me + //----------------------------- + featureDotDirection = featureNormal*featureDirection; + if (featureDotDirection>-0.8f) + { + // Must Be Roughly Perpendicular To My Velocity (Within 45 Degrees) + //------------------------------------------------------------------ + featureDotLeft = featureNormal*velocityLeft; + if (fabsf(featureDotLeft)>0.5f) + { + // If On Left Of Me, Must Have A Right Lean, And Converse + //-------------------------------------------------------- + if ((featureDotLeft>0.0f && mNear->flags&FEATURE_LOOK_RIGHT) || + (featureDotLeft<0.0f && mNear->flags&FEATURE_LOOK_LEFT)) + { + // Check All Team Mates To See If This Look Points At Them + //--------------------------------------------------------- + for (teammate = aiManager.GetAllyTeam ( (aiTeam_t)mOwner->team ); teammate; teammate = teammate->teamNode.Next()) + { + if (teammate->fl.hidden || teammate == mOwner || teammate->health <= 0) + { + continue; + } + + teammateDirection = featureOrigin - teammate->GetPhysics()->GetOrigin(); + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance0.85f) + { + break; + } + } + + if (!teammate) + { + mLook = mNear; + mLookStartTime = gameLocal.GetTime(); + mLookStopDist = rvRandom::flrand(48.0f, 80.0f); + } + } + } + } + } + } + } + else if (!mOwnerAI || !mOwnerAI->move.fl.moving) + { + mLook = 0; + } + + // Draw Any Debug Info + //--------------------- + DrawDebugInfo(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Reserve +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Reserve(aasFeature_t* f) +{ + if (f!=mReserved && mOwner) + { + mReserved = f; + + if ( f ) + { + mReservedOrigin = f->Origin ( ); + } + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// FEATURE TESTING +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// TestSetupCurrentValuesFor +// +// This function sets up the test sets to have new +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::TestSetupCurrentValues() +{ + // Owner Test Set + //---------------- + mFromOwner.SetupOriginAndFacing(mOwner); + + + // Enemy Test Set + //---------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + if (mOwnerAI && mEnemyOverride) + { + mFromEnemy.SetupOriginAndFacing(mEnemyOverride, &mOwnerAI->LastKnownPosition(mEnemyOverride), 0); + } + else if (mOwnerAI && mOwnerAI->GetEnemy()) + { + mFromEnemy.SetupOriginAndFacing(mOwnerAI->GetEnemy(), &mOwnerAI->LastKnownPosition(mOwnerAI->GetEnemy()), 0); + } + + // Tether Test Set + //---------------- + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.SetupOriginAndFacing(mOwnerAI->GetTether ( )); + } + + // Path Test Set + //---------------- + mFromPath.mProjectOrigin = true; + mFromPath.mOrigin = mFromOwner.mOrigin; + mFromPath.mFacing = mFromEnemy.mOrigin - mFromOwner.mOrigin; + mFromPath.mFacing[2] = 0; + mFromPath.mFacing.Normalize(); + + // Advance Test Set + //------------------ + // NOTHING TO DO HERE FOR NOW... +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValidReserved +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValidWithCurrentState(aasFeature_t* f) +{ + // If Trying To Hide From An Enemy That No Longer Exists, Any Feature is Invalid + //------------------------------------------------------------------------------- + if (mEnemyOverride.GetSpawnId() && !mEnemyOverride.IsValid()) + { + return false; + } + + // Reset The Test Parameters With Current Origins (Cuz Things May Have Moved) + //---------------------------------------------------------------------------- + TestSetupCurrentValues(); + + // And Run The Test That The Original Search Ran + //----------------------------------------------- + if (!f) + { + return TestValid(mReserved, 0.0f); + } + return TestValid(f, 0.0f); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::TestValid +// +// This is THE function that tests if a given feature matches the current +// search parameters. An optional walk distance may be passed into the function +// to replace the from owner straight line distance. +/////////////////////////////////////////////////////////////////////////////// +bool rvAASTacticalSensorLocal::TestValid(aasFeature_t* f, float walkDistanceToFeature) +{ + static idVec3 LeanNormal; + static idVec3 Up(0.0f,0.0f,1.0f); + static float LeanNormalDot; + + + // Is There A Feature At All + //--------------------------- + if (!f) + { + return false; + } + + // Does It Match The Flags? + //-------------------------- + if (!(f->flags&mFlagsMatchAny) || + ((f->flags&mFlagsMatchAll)!=mFlagsMatchAll) || + (f->flags&mFlagsMatchNone)) + { + return false; + } + + // Does It Pass The Tests? + //------------------------- + if (!mFromOwner.Test(f, walkDistanceToFeature) || + !mFromEnemy.Test(f) || + !mFromTether.Test(f) || + !mFromPath.Test(f) || + !mAdvance.Test(mFromOwner.mDirection*mFromPath.mFacing) || + !mAssignment.Test(mFromOwner.mDirection*mAssignmentDirection)) + { + return false; + } + + // Make sure this cover point is vaild for the current tether + //------------------------------------------------------------ + if ( mOwnerAI && mOwnerAI->IsTethered() && !mOwnerAI->GetTether()->ValidateDestination ( mOwnerAI, f->Origin() ) ) + { + return false; + } + + // Does It Pass The Lean Normal Test? + //------------------------------------ + if (mOwnerAI && (mEnemyOverride||mOwnerAI->GetEnemy())) + {//we have an enemy position to test against + mLeanNormal.mValue = 0.0f; + if (!(f->flags&FEATURE_LOOK_OVER) && ((f->flags&FEATURE_LOOK_RIGHT) || (f->flags&FEATURE_LOOK_LEFT))) + { + LeanNormal = f->Normal().Cross(Up); // Start With Left + LeanNormalDot = LeanNormal * mFromEnemy.mDirection; + + if (!(f->flags&FEATURE_LOOK_LEFT) || ((f->flags&FEATURE_LOOK_RIGHT) && LeanNormalDot<0.0f)) + { + LeanNormalDot *= -1.0f; // Use The Right Normal + } + + if (!mLeanNormal.Test(LeanNormalDot)) + { + return false; + } + } + } + + + // Is Anyone Else Going There? + //----------------------------- + if (mOwnerAI && !aiManager.ValidateDestination(mOwnerAI, f->Origin())) + { + return false; + } + + // Everything Passed, This Feature Is Good To Go + //----------------------------------------------- + return true; +} + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SEARCH PARAMETERS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchReset +// +// Initialize Default Flags, Feature Counts, And Population Points. This +// function must be called before first in any search function, because +// the search functions rely on this standard set of parameters and then +// build upon them. +/////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchReset(idEntity* enemyOverride, float ownerRangeMin, float ownerRangeMax) +{ + if (!mOwner) + { + return; + } + + // Setup Base Search Parameters + //------------------------------ + mFlagsMatchAll = 0; // Must Have All Of These + mFlagsMatchAny = (FEATURE_LOOK_LEFT|FEATURE_LOOK_RIGHT|FEATURE_LOOK_OVER); // Must Have At Least One Of These + mFlagsMatchNone = (FEATURE_PINCH|FEATURE_VANTAGE); // Don't Want Any Of These + mFeaturesSearchMax = 100; + mFeaturesFinalMax = 20; + mAssignmentValid = false; // TODO: Turn This Back On + mAssignmentDirection = vec3_zero; + mEnemyOverride = enemyOverride; + + + // Lean Normal Test + //------------------ + mLeanNormal.Reset(); + mLeanNormal.mMin =-0.2f; // Must Lean Toward Enemy + + + // Owner Test Set Default Values + //------------------------------- + mFromOwner.Reset(); + mFromOwner.mDistance.mMin = ownerRangeMin; + mFromOwner.mDistance.mMax = ownerRangeMax; + mFromOwner.mDistance.mWeight =-1.0f; // Prefer Close To Owner + + // Enemy Test Set Default Values + //------------------------------- + //NOTE!!! This does NOT clear any old info about your enemy, so if any tests + // use this info ASSUMING you have an enemy, your test will be totally + // wrong!!! + mFromEnemy.Reset(); + if ( mOwnerAI && mOwnerAI->enemy.ent ) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; // must be at least 100 units from enemy + mFromEnemy.mDistance.mMax = 2.0f; // don't care how far the distance is to the enemy, let it go over max (up to 1600) + mFromEnemy.mDirectionDot.mMax =-0.7f; // Must Face Within 45 Degrees Of enemy + mFromEnemy.mDirectionDot.mWeight =-0.3f; // Prefer To Face Toward Enemy + if (mOwnerAI && mOwnerAI->enemy.ent ) + { + // Cap Min And Max Distances To Attack Range, and Aware Range + //------------------------------------------------------------ + mFromEnemy.mDistance.mMax = mOwnerAI->combat.attackRange[1] / MAX_DISTANCE; + mFromEnemy.mDistance.mMin = mOwnerAI->combat.attackRange[0] / MAX_DISTANCE; + + if (mFromEnemy.mDistance.mMin < (mOwnerAI->combat.awareRange / MAX_DISTANCE)) + { + mFromEnemy.mDistance.mMin = mOwnerAI->combat.awareRange / MAX_DISTANCE; + } + + // If haven't seen enemy in a while, allow you to go right to his last known spot + //-------------------------------------------------------------------------------- + if ( mOwnerAI->enemy.lastVisibleTime && (gameLocal.GetTime() - mOwnerAI->enemy.lastVisibleTime)>mOwnerAI->combat.maxLostVisTime/2.0f) + { + mFromEnemy.mDistance.mMin = 0.0f; + } + } + } + + + // Tether test set + //------------------------------- + mFromTether.Reset(); + if (mOwnerAI && mOwnerAI->IsTethered ( ) ) + { + mFromTether.mFacingDot.mMin = 0.5f; + mFromTether.mFacingDot.mWeight = 0.3f; + + // Disable the owner distance test + mFromOwner.Reset(); + } + + // Other Test Sets + //----------------- + mFromPath.Reset(); + mAdvance.Reset(); + mAssignment.Reset(); + + + // Default Test Values + //--------------------- + TestSetupCurrentValues(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchDebug +// +// TO RUN THIS FUNCTION, TYPE "extract_tactical" ON THE CONSOLE. +// +// Feel free to modify this function to test whatever search or other +// operation you need. The owner will be the player. +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchDebug() +{ + SearchCover(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRadius +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRadius(const idVec3& origin, float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Radius"; + mFromEnemy.Reset(); // Don't Care About Enemy At All + if ( origin != vec3_origin ) + { + mFromOwner.mOrigin = origin; // Override the owner origin + } + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchCover +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchCover(float rangeMin, float rangeMax) +{ + SearchReset(0, rangeMin, rangeMax); + mSearchName = "Cover"; + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchHide +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchHide(idEntity* from) +{ + SearchReset(from); + mSearchName = "Hide"; + mFlagsMatchNone |= FEATURE_LOOK_OVER; // Want Full Height Walls Here + mFromEnemy.mDirectionDot.mMax = -0.8f; // Must Almost Exactly At The Enemy + mFromOwner.mDistance.mMax = 2.0f; // Go as far as you need to - ignore tethering for hide + mFromOwner.mDistance.mMin = 0.4f; // Get A Good Distance Away + mFromOwner.mDirectionDot.Reset(); // Ignore any direction dot with the leader + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchFlank +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchFlank() +{ + SearchReset(); + mSearchName = "Flank"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMin = -0.2f; // Must Be Behind Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAdvance +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAdvance() +{ + SearchReset(); + mSearchName = "Advance"; + mFromOwner.mDistance.mMin = 0.15f; // Make Sure To Move Some + mAdvance.mMin = 0.3f; // Forward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchRetreat +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchRetreat() +{ + SearchReset(); + mSearchName = "Retreat"; + mFromOwner.mDistance.mMin = 0.1f; // Make Sure To Move Some + mAdvance.mMax = -0.3f; // Backward! + Search(); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::SearchAmbush +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::SearchAmbush() +{ + SearchReset(); + mSearchName = "Ambush"; + mFromOwner.mDistance.mMin = 0.35f; // Must Be A Good Distance From Where We Are + mFromEnemy.mFacingDot.mMax = 0.2f; // Must Be In Front Of Enemy + mAdvance.mMin = -0.5f; // In Front Of Owner + mAdvance.mMax = 0.8f; // But Not Directly Along Path + Search(); +} + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// THE SEARCH +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////// +// SearchComputeWeightRange +// +// We compute the weight range so that we can make a better scale factor +// between 0.0 and 1.0 later on. It's not critical that all the weights +// factor exactly 0.0 to 1.0, but it is nice to see how "good" a feature +// matches the given search parameters +/////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeightRange(float& rangeNegative) +{ + return (mFromOwner.WeightRange(rangeNegative) + + mFromEnemy.WeightRange(rangeNegative) + + mFromTether.WeightRange(rangeNegative) + + mFromPath.WeightRange(rangeNegative) + + mAdvance.WeightRange(rangeNegative) + + mLeanNormal.WeightRange(rangeNegative) + + mAssignment.WeightRange(rangeNegative)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Weight +// +// Add up the computed weight of all tests. +/////////////////////////////////////////////////////////////////////////////// +float rvAASTacticalSensorLocal::SearchComputeWeight() +{ + return (mFromOwner.Weight() + + mFromEnemy.Weight() + + mFromTether.Weight() + + mFromPath.Weight() + + mAdvance.Weight() + + mLeanNormal.Weight() + + mAssignment.Weight()); +} + +/////////////////////////////////////////////////////////////////////////////// +// SortFeature function (used by Search() below) +/////////////////////////////////////////////////////////////////////////////// +ID_INLINE int rvSortFeature( const TFeaturePtr *a, const TFeaturePtr *b ) +{ + if ((*a)->weight > (*b)->weight) + { + return -1; + } + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::Search +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::Search() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!mOwner || !aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures()) + { + return; + } + + + static idAASFile* file; + static int areaNum; + static int areaNumOwner; + static rvBits<32000> areaVisit; + static int areaVisitCount; + static int travelTime; + static int areaFeatureNum; + static int featureNum; + static aasFeature_t* featurePtr; + static idVec3 featureOrigin; + static idReachability* reach; + static idList searchHeap; + static rvSortReach sortReach; + static float walkDistanceToFeature; + static float weight; + static float weightRangeNegative; + static float weightRangeTotal; + static idVec3 from; + static idVec3 endPos; + static int endAreaNum; + + + + +//----------------------------------------------------------------------------- +// SETUP +//----------------------------------------------------------------------------- + file = aas->GetFile(); + weightRangeNegative = 0.0f; + weightRangeTotal = SearchComputeWeightRange(weightRangeNegative); + + mFeatures.Clear(); + searchHeap.Clear(); + areaVisit.clear(); + if (!mAssignmentValid) + { + mAssignment.Reset(); // Never Worry About Squad Assignments If No Leader Is Active + } + + + + +//----------------------------------------------------------------------------- +// PHASE I - POPULATE AREA QUEUE +//----------------------------------------------------------------------------- + if ( mOwnerAI ) + { + areaNumOwner = mOwnerAI->PointReachableAreaNum ( mFromOwner.mOrigin ); + } + else + { + areaNumOwner = aas->PointReachableAreaNum(mFromOwner.mOrigin, mOwner->GetPhysics()->GetBounds(), AREA_REACHABLE_WALK); + } + + from = mFromOwner.mOrigin; + areaNum = areaNumOwner; + sortReach.mAreaNum = areaNumOwner; + sortReach.mDistance = 0.0f; + sortReach.mReach = 0; + searchHeap.Append(sortReach); + + + + +//----------------------------------------------------------------------------- +// PHASE II - BREADTH FIRST SEARCH NEIGHBORING AREAS FOR FEATURES THAT TEST OK +//----------------------------------------------------------------------------- + areaVisitCount = 0; + while (searchHeap.Num() && areaVisitCountGetArea(sr.mAreaNum); + if (area.numFeatures) + { + for (areaFeatureNum=0; areaFeatureNumGetFeature(file->GetFeatureIndex(area.firstFeature+areaFeatureNum))); + featureOrigin = featurePtr->Origin(); + + // If Walk Path Is Valid, Allow From Owner Test To Use Computed Straight Line Distance + //------------------------------------------------------------------------------------- + if (aas->WalkPathValid(areaNumOwner, mFromOwner.mOrigin, sr.mAreaNum, featureOrigin, TFL_WALK, endPos, endAreaNum)) + { + walkDistanceToFeature = 0.0f; // Allows Test to use straight line distance computed + } + + // If It Is Not Possible To Straight Line Walk To A Feature, Use The Enter Point And Distance Of The Area (Which Is A Rough Appx) + //-------------------------------------------------------------------------------------------------------------------------------- + else + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(featureOrigin)):(mFromOwner.mOrigin.Dist(featureOrigin))); + } + + // Test The Feature To See If It's Valid + //--------------------------------------- + if (!TestValid(featurePtr, walkDistanceToFeature)) + { + continue; + } + + // Compute The Weight + //-------------------- + if (weightRangeTotal>0.0f) + { + weight = SearchComputeWeight(); // Compute Weight Sum + weight -= weightRangeNegative; // Bring it into a positive range + weight /= weightRangeTotal; // Scale down to 0.0 - 1.0 + + assert(weight>0.0f && weight<255.0f); + featurePtr->weight = (char)(weight*255); + } + else + { + featurePtr->weight = (unsigned char)(128); // No Sorting + } + + + // Append The Feature To The List + //-------------------------------- + mFeatures.Append(featurePtr); + } + } + + + // Add Neighboring Areas To Search + //--------------------------------- + for (reach=area.reach; reach; reach=reach->next) + { + if ((reach->travelType&TFL_WALK)) + { + walkDistanceToFeature = sr.mDistance + ((sr.mReach)?(sr.mReach->end.Dist(reach->end)):(mFromOwner.mOrigin.Dist(reach->end))); + + if (walkDistanceToFeaturetoAreaNum; + sortReach.mReach = reach; + searchHeap.HeapAdd(sortReach); + } + } + } + } + + + + +//----------------------------------------------------------------------------- +// PHASE III - SORT AND CLIP THE FEATURE LIST +//----------------------------------------------------------------------------- + mFeatures.Sort(rvSortFeature); + + // Now, Clip The Sorted List To The Max Size + //------------------------------------------- + if (mFeatures.Num()>MAX_FEATURE_LIST) + { + mFeatures.Resize(MAX_FEATURE_LIST); + } + + // Now Reset Any Parameters Which Were Only "Temporary" During The Search, and Do Not Invalidate The Point Later + //---------------------------------------------------------------------------------------------------------------- + mFromOwner.mDistance.mMin = 0.0f; // Allow Getting Close Again + + + // Print Search Results + //---------------------- + if (ai_showTacticalFeatures.GetInteger()==3) + { + common->Printf( "[%10d] Search%s Found %d Features For %s\n", gameLocal.GetTime(), mSearchName.c_str(), mFeatures.Num(), mOwner->GetName() ); + } +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DEBUG GRAPHICS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// aasFeature_t::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void aasFeature_t::DrawDebugInfo( int index ) +{ + static idVec3 Height; + static idVec3 Orig; + static idVec3 Norm; + static idVec3 Text; + static idVec4 color; + static idVec3 Left; + + int lifetime = 0; + + color = colorWhite; + + if (flags & FEATURE_COVER) + { + color = colorGreen; + } + + Orig = Origin(); + Orig[2] += 1.0f; + + Height = Orig; + Height[2] += height; + + Norm = Orig; + Norm += Normal() * mDebugRadius; + Left = Normal().Cross(idVec3(0,0,-1)) * mDebugRadius; + + gameRenderWorld->DebugLine( color, Orig, Height, lifetime ); + gameRenderWorld->DebugLine( color, Orig, Norm, lifetime ); + + if (index>=0) + { + Text = (Origin() + Height) * 0.5f; + gameRenderWorld->DrawText( va( "%d", index ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + + Text[2] += 15.0f; + gameRenderWorld->DrawText( va( "%d", (int)weight ), Text, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, lifetime ); + } + + // Left Corner Is On The Ground + //------------------------------ + if (flags & FEATURE_CORNER_LEFT) + { + gameRenderWorld->DebugLine( color, Orig, Orig + Left, lifetime ); + } + + // Otherwise Windows Are At The Given Height + //------------------------------------------- + else if (flags & FEATURE_LOOK_LEFT) + { + gameRenderWorld->DebugLine( color, Height, Height + Left, lifetime ); + } + + if (flags & FEATURE_CORNER_RIGHT) + { + gameRenderWorld->DebugLine( color, Orig, Orig - Left, lifetime ); + } + else if (flags & FEATURE_LOOK_RIGHT) + { + gameRenderWorld->DebugLine( color, Height, Height - Left, lifetime ); + } + + if (flags & FEATURE_LOOK_OVER) + { + gameRenderWorld->DebugLine( color, Height, Height + (Normal()*mDebugRadius), lifetime ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin, const idVec3& direction) +{ + gameRenderWorld->DebugFOV(color, origin, direction, mMax, 20.0f, mMin, 10.0f, 20.0f); +} + +/////////////////////////////////////////////////////////////////////////// +// rvTest::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTest::DrawDebugInfo(const idVec4 color, const idVec3& origin) +{ + static idVec3 up(0.0f,0.0f,-1.0f); + if (mMax<1.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMax * MAX_DISTANCE), 25); + } + if (mMin>0.0f) + { + gameRenderWorld->DebugCircle(color, origin, up, (mMin * MAX_DISTANCE), 25); + } +} +/////////////////////////////////////////////////////////////////////////// +// rvTestSet::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////// +void rvTestSet::DrawDebugInfo(const idVec4& color, const idVec3& nonProjectedOrigin) +{ + static int lifetime = 0; + static idVec3 origin; + + origin = mOrigin; + if (mProjectOrigin) + { + origin = nonProjectedOrigin; + } + + gameRenderWorld->DebugArrow( color, origin, origin+mFacing * 25.0f, 8, lifetime ); + mFacingDot.DrawDebugInfo(color, origin, mFacing); + mDistance.DrawDebugInfo(color, origin); +} + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensorLocal::DrawDebugInfo +/////////////////////////////////////////////////////////////////////////////// +void rvAASTacticalSensorLocal::DrawDebugInfo() +{ + idAAS* aas = (mOwnerAI)?(mOwnerAI->aas):(gameLocal.GetAAS(0)); + if (!aas || !aas->GetFile() || !aas->GetFile()->GetNumFeatures() || !mOwner || mOwner->IsHidden() || (ai_showTacticalFeatures.GetInteger()<2 && !mOwner->DebugFilter(ai_showTacticalFeatures) && !mOwner->DebugFilter(ai_debugTactical))) + { + return; + } + + + static idVec3 pos; + bool reservedDrawn = false; + bool nearDrawn = false; + bool lookDrawn = false; + + mDebugRadius = aas->GetSettings()->boundingBoxes[0][1][0]; + + + // Draw Parameters + //----------------- + if (ai_showTacticalFeatures.GetInteger()==1) + { + if (!mSearchName.IsEmpty()) + { + pos = mFromOwner.mOrigin + mFromEnemy.mOrigin; + pos *= 0.5f; + pos[2] += 25.0f; + gameRenderWorld->DrawText(mSearchName.c_str(), pos, 0.5f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis, 1, 0 ); + pos[2] -= 25.0f; + + // Draw Tests + //------------ + mFromEnemy.DrawDebugInfo(colorYellow, pos); + mFromTether.DrawDebugInfo(colorOrange, pos); + mFromOwner.DrawDebugInfo(colorMagenta, pos); + mFromPath.DrawDebugInfo(colorCyan, pos); + mAssignment.DrawDebugInfo(colorPink, pos); + mAdvance.DrawDebugInfo(colorPurple, mFromOwner.mOrigin, mFromPath.mFacing); + mLeanNormal.DrawDebugInfo(colorPurple, pos); + } + + + // Draw Features + //--------------- + for (int i=0; iDrawDebugInfo(i); + if (mFeatures[i]==mReserved) + { + reservedDrawn = true; + } + if (mFeatures[i]==mNear) + { + nearDrawn = true; + } + if (mFeatures[i]==mLook) + { + lookDrawn = true; + } + } + } + + // Draw All Neighboring Features If Player & CVar==2 + //--------------------------------------------------- + if (mOwner==gameLocal.GetLocalPlayer() && ai_showTacticalFeatures.GetInteger()>=2) + { + idAASFile* file = aas->GetFile(); + const idVec3& playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + for (int i=0; iGetNumFeatures(); i++) + { + if (file->GetFeature(i).Origin().Dist(playerOrigin)<600.0f) + { + file->GetFeature(i).DrawDebugInfo(); + } + } + } + + + // Always Draw Reserved + //---------------------- + if (mReserved) + { + if (!reservedDrawn) + { + mReserved->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorBlue, mOwner->GetPhysics()->GetOrigin(), mReserved->Origin(), 8); + } + + // If Near Is Valid, Draw It + //--------------------------- + if (mNear && (!mReserved || mNear!=mReserved)) + { + if (!nearDrawn) + { + mNear->DrawDebugInfo(); + } + gameRenderWorld->DebugArrow(colorOrange, mOwner->GetPhysics()->GetOrigin(), mNear->Origin(), 8); + } + + // If Look Is True, Then Draw That + //--------------------------------- + if (mLook && (!mOwnerAI || mOwnerAI->InLookAtCoverMode())) + { + idVec3 n = mLook->Normal(); + n *= 64.0f; + gameRenderWorld->DebugArrow(colorYellow, mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32), mOwner->GetPhysics()->GetOrigin() + idVec3(0,0,32) + n, 8); + } +} + + + + +// RAVENEND - CDR + + + + + diff --git a/src/mpgame/ai/AAS_tactical.h b/src/mpgame/ai/AAS_tactical.h new file mode 100644 index 0000000..3daf2ac --- /dev/null +++ b/src/mpgame/ai/AAS_tactical.h @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/////////////////////////////////////////////////////////////////////////////// +// AAS_tactical +// +// This file is the interface to an AAS Tactical Extractor, which can search +// out from a given start point and report a variety of tactically important +// objectives, including corners, walls, and pinch points. +// +// By seeing the AAS graph as a qualitative and simplified spatial +// representation of the game world. This representation is amply capapble of +// rendering higher level tactical data efficiently in real time. +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef __AAS_TACTICAL_H__ +#define __AAS_TACTICAL_H__ + + +/////////////////////////////////////////////////////////////////////////////// +// rvAASTacticalSensor +// +// The sensor structure is the public interface to the internals of AAS +// tactical features. +/////////////////////////////////////////////////////////////////////////////// +struct rvAASTacticalSensor +{ + // Regular Update Function + /////////////////////////////////////////////////////////////////////// + virtual void Update() = 0; + virtual void Save(idSaveGame *savefile) = 0; + virtual void Restore(idRestoreGame *savefile) = 0; + virtual void Clear() = 0; + + // Search + /////////////////////////////////////////////////////////////////////// + virtual void SearchRadius(const idVec3& origin=vec3_origin, float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchCover(float rangeMin=0.0f, float rangeMax=1.0f) = 0; + virtual void SearchHide(idEntity* from=0) = 0; + virtual void SearchFlank() = 0; + virtual void SearchAdvance() = 0; + virtual void SearchRetreat() = 0; + virtual void SearchAmbush() = 0; + virtual void SearchDebug() = 0; + + // Feature Testing + /////////////////////////////////////////////////////////////////////// + virtual bool TestValid(aasFeature_t* f, float walkDistanceToFeature) = 0; + virtual bool TestValidWithCurrentState(aasFeature_t* f=0) = 0; + + // Feature Reservation + /////////////////////////////////////////////////////////////////////// + virtual void Reserve(aasFeature_t* f) = 0; + + // Access To Results + /////////////////////////////////////////////////////////////////////// + virtual int FeatureCount() = 0; + virtual aasFeature_t* Feature(int i) = 0; + virtual aasFeature_t* Near() const = 0; + virtual aasFeature_t* Look() const = 0; + virtual aasFeature_t* Reserved() const = 0; + virtual const idVec3& ReservedOrigin() const = 0; + + + + // STATIC SYSTEM FUNCTIONS + /////////////////////////////////////////////////////////////////////// + static rvAASTacticalSensor* CREATE_SENSOR(idActor* owner); +}; + + +#endif /* !__AAS_TACTICAL_H__ */ diff --git a/src/mpgame/ai/AI.cpp b/src/mpgame/ai/AI.cpp new file mode 100644 index 0000000..a53a88f --- /dev/null +++ b/src/mpgame/ai/AI.cpp @@ -0,0 +1,5175 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "../Projectile.h" +#include "../spawner.h" +#include "AI_Tactical.h" + +const char* aiTalkMessageString [ ] = { + "None", + "primary", + "secondary", + "loop" +}; + +static const float AI_SIGHTDELAYSCALE = 5000.0f; // Full sight delay at 5 seconds or more of not seeing enemy + + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +/* +===================== +idAI::idAI +===================== +*/ +idAI::idAI ( void ) { + projectile_height_to_distance_ratio = 1.0f; + + aas = NULL; + aasSensor = NULL; + aasFind = NULL; + + lastHitCheckResult = false; + lastHitCheckTime = 0; + lastAttackTime = 0; + projectile = NULL; + projectileClipModel = NULL; + chatterTime = 0; + talkState = TALK_NEVER; + talkTarget = NULL; + talkMessage = TALKMSG_NONE; + talkBusyCount = 0; + + enemy.ent = NULL; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + + fl.neverDormant = false; // AI's can go dormant + + allowEyeFocus = true; + disablePain = false; + allowJointMod = true; + focusEntity = NULL; + focusTime = 0; + alignHeadTime = 0; + forceAlignHeadTime = 0; + + orientationJoint = INVALID_JOINT; + + eyeVerticalOffset = 0.0f; + eyeHorizontalOffset = 0.0f; + headFocusRate = 0.0f; + eyeFocusRate = 0.0f; + focusAlignTime = 0; + focusRange = 0.0f; + focusType = AIFOCUS_NONE; + + memset ( &combat.fl, 0, sizeof(combat.fl) ); + combat.max_chasing_turn = 0; + combat.shotAtTime = 0; + combat.shotAtAngle = 0.0f; + combat.meleeRange = 0.0f; + combat.tacticalPainTaken = 0; + combat.tacticalFlinches = 0; + combat.investigateTime = 0; + combat.aggressiveScale = 1.0f; + + passive.animFidgetPrefix.Clear ( ); + passive.animIdlePrefix.Clear ( ); + passive.animTalkPrefix.Clear ( ); + passive.idleAnim.Clear(); + passive.prefix.Clear(); + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + memset ( &passive.fl, 0, sizeof(passive.fl) ); + + pain.lastTakenTime = 0; + pain.takenThisFrame = 0; + pain.loopEndTime = 0; + + enemy.range = 0; + enemy.range2d = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + enemy.lastKnownPosition.Zero ( ); + enemy.lastVisibleEyePosition.Zero ( ); + enemy.lastVisibleChestPosition.Zero ( ); + enemy.lastVisibleFromEyePosition.Zero ( ); + enemy.checkTime = 0; + enemy.changeTime = 0; + enemy.lastVisibleChangeTime = 0; + enemy.lastVisibleTime = 0; + memset ( &enemy.fl, 0, sizeof(enemy.fl) ); + + currentFocusPos.Zero(); + eyeAng.Zero(); + lookAng.Zero(); + destLookAng.Zero(); + lookMin.Zero(); + lookMax.Zero(); + + eyeMin.Zero(); + eyeMax.Zero(); + + helperCurrent = NULL; + helperIdeal = NULL; + + speakTime = 0; + + actionAnimNum = 0; + actionSkipTime = 0; + actionTime = 0; +} + +/* +===================== +idAI::~idAI +===================== +*/ +idAI::~idAI() { + // Make sure we arent stuck in the simple think list + simpleThinkNode.Remove ( ); + + delete aasFind; + delete aasSensor; + delete projectileClipModel; + DeconstructScriptObject(); + scriptObject.Free(); + aiManager.RemoveTeammate ( this ); + SetPhysics( NULL ); +} + +/* +===================== +idAI::Save +===================== +*/ +void idAI::Save( idSaveGame *savefile ) const { + int i; + +// cnicholson: These 3 vars are intentionally not saved, as noted in the restore + // NOSAVE: idLinkList simpleThinkNode; + // NOSAVE: idAAS* aas; + // NOSAVE: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + // Movement + move.Save( savefile ); + savedMove.Save( savefile ); + + savefile->WriteStaticObject( physicsObj ); + savefile->WriteBool( GetPhysics() == static_cast(&physicsObj) ); + + savefile->WriteBool( lastHitCheckResult ); + savefile->WriteInt( lastHitCheckTime ); + savefile->WriteInt( lastAttackTime ); + savefile->WriteFloat( projectile_height_to_distance_ratio ); + + savefile->WriteInt( attackAnimInfo.Num() ); + for( i = 0; i < attackAnimInfo.Num(); i++ ) { + savefile->WriteVec3( attackAnimInfo[ i ].attackOffset ); + savefile->WriteVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TOSAVE: mutable idClipModel* projectileClipModel; + projectile.Save ( savefile ); + + // Talking + savefile->WriteInt( chatterTime ); +// savefile->WriteInt( chatterRateCombat ); // NOSAVE: +// savefile->WriteInt( chatterRateIdle ); // NOSAVE: + savefile->WriteInt( talkState ); + talkTarget.Save( savefile ); + savefile->WriteInt( talkMessage ); + savefile->WriteInt( talkBusyCount ); + savefile->WriteInt ( speakTime ); + + // Focus + lookTarget.Save ( savefile ); + savefile->WriteInt( focusType ); + focusEntity.Save ( savefile ); + savefile->WriteFloat ( focusRange ); + savefile->WriteInt ( focusAlignTime ); + savefile->WriteInt ( focusTime ); + savefile->WriteVec3( currentFocusPos ); + + // Looking + savefile->WriteBool( allowJointMod ); + savefile->WriteInt( alignHeadTime ); + savefile->WriteInt( forceAlignHeadTime ); + savefile->WriteAngles( eyeAng ); + savefile->WriteAngles( lookAng ); + savefile->WriteAngles( destLookAng ); + savefile->WriteAngles( lookMin ); + savefile->WriteAngles( lookMax ); + + savefile->WriteInt( lookJoints.Num() ); + for( i = 0; i < lookJoints.Num(); i++ ) { + savefile->WriteJoint( lookJoints[ i ] ); + savefile->WriteAngles( lookJointAngles[ i ] ); + } + + savefile->WriteFloat( eyeVerticalOffset ); + savefile->WriteFloat( eyeHorizontalOffset ); + savefile->WriteFloat( headFocusRate ); + savefile->WriteFloat( eyeFocusRate ); + + // Joint Controllers + savefile->WriteAngles( eyeMin ); + savefile->WriteAngles( eyeMax ); + savefile->WriteJoint( orientationJoint ); + + pusher.Save( savefile ); // cnicholson: Added unsaved var + scriptedActionEnt.Save( savefile ); // cnicholson: Added unsaved var + + savefile->Write( &aifl, sizeof( aifl ) ); + + // Misc + savefile->WriteInt ( actionAnimNum ); + savefile->WriteInt ( actionTime ); + savefile->WriteInt ( actionSkipTime ); + savefile->WriteInt ( flagOverrides ); + + // Combat variables + savefile->Write( &combat.fl, sizeof( combat.fl ) ); + savefile->WriteFloat ( combat.max_chasing_turn ); + savefile->WriteFloat ( combat.shotAtTime ); + savefile->WriteFloat ( combat.shotAtAngle ); + savefile->WriteVec2 ( combat.hideRange ); + savefile->WriteVec2 ( combat.attackRange ); + savefile->WriteInt ( combat.attackSightDelay ); + savefile->WriteFloat ( combat.meleeRange ); + savefile->WriteFloat ( combat.aggressiveRange ); + savefile->WriteFloat ( combat.aggressiveScale ); + savefile->WriteInt ( combat.investigateTime ); + savefile->WriteFloat ( combat.visStandHeight ); + savefile->WriteFloat ( combat.visCrouchHeight ); + savefile->WriteFloat ( combat.visRange ); + savefile->WriteFloat ( combat.earRange ); + savefile->WriteFloat ( combat.awareRange ); + savefile->WriteInt ( combat.tacticalMaskAvailable ); + savefile->WriteInt ( (int&)combat.tacticalCurrent ); + savefile->WriteInt ( combat.tacticalUpdateTime ); + savefile->WriteInt ( combat.tacticalPainTaken ); + savefile->WriteInt ( combat.tacticalPainThreshold ); + savefile->WriteInt ( combat.tacticalFlinches ); + savefile->WriteInt ( combat.maxLostVisTime ); + savefile->WriteFloat ( combat.threatBase ); + savefile->WriteFloat ( combat.threatCurrent ); + savefile->WriteInt ( combat.coverValidTime ); + savefile->WriteInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->WriteString ( passive.prefix ); + savefile->WriteString ( passive.animFidgetPrefix ); + savefile->WriteString ( passive.animIdlePrefix); + savefile->WriteString ( passive.animTalkPrefix ); + savefile->WriteString ( passive.idleAnim ); + savefile->WriteInt ( passive.idleAnimChangeTime ); + savefile->WriteInt ( passive.fidgetTime ); + savefile->WriteInt ( passive.talkTime ); + savefile->Write ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Save ( savefile ); + savefile->Write ( &enemy.fl, sizeof(enemy.fl) ); + savefile->WriteInt ( enemy.lastVisibleChangeTime ); + savefile->WriteVec3 ( enemy.lastKnownPosition ); + savefile->WriteVec3 ( enemy.smoothedLinearVelocity ); + savefile->WriteVec3 ( enemy.smoothedPushedVelocity ); + savefile->WriteVec3 ( enemy.lastVisibleEyePosition ); + savefile->WriteVec3 ( enemy.lastVisibleChestPosition ); + savefile->WriteVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->WriteInt ( enemy.lastVisibleTime ); + savefile->WriteFloat ( enemy.range ); + savefile->WriteFloat ( enemy.range2d ); + savefile->WriteInt ( enemy.changeTime ); + savefile->WriteInt ( enemy.checkTime ); + + // Pain variables + savefile->WriteFloat ( pain.threshold ); + savefile->WriteFloat ( pain.takenThisFrame ); + savefile->WriteInt ( pain.lastTakenTime ); + savefile->WriteInt ( pain.loopEndTime ); + savefile->WriteString ( pain.loopType ); + + // Functions + funcs.first_sight.Save ( savefile ); + funcs.sight.Save ( savefile ); + funcs.pain.Save ( savefile ); + funcs.damage.Save ( savefile ); + funcs.death.Save ( savefile ); + funcs.attack.Save ( savefile ); + funcs.init.Save ( savefile ); + funcs.onclick.Save ( savefile ); + funcs.launch_projectile.Save ( savefile ); + funcs.footstep.Save ( savefile ); + + mPlayback.Save( savefile ); // cnicholson: Added save functionality + mLookPlayback.Save( savefile ); // cnicholson: Added save functionality + + // Tactical Sensor + aasSensor->Save( savefile ); + + // Helpers + tether.Save ( savefile ); + helperCurrent.Save ( savefile ); + helperIdeal.Save ( savefile ); + leader.Save ( savefile ); + spawner.Save ( savefile ); + + // Action timers + actionTimerRangedAttack.Save ( savefile ); + actionTimerEvade.Save ( savefile ); + actionTimerSpecialAttack.Save ( savefile ); + actionTimerPain.Save ( savefile ); + + // Actions + actionEvadeLeft.Save ( savefile ); + actionEvadeRight.Save ( savefile ); + actionRangedAttack.Save ( savefile ); + actionMeleeAttack.Save ( savefile ); + actionLeapAttack.Save ( savefile ); + actionJumpBack.Save ( savefile ); +} + +/* +===================== +idAI::Restore +===================== +*/ +void idAI::Restore( idRestoreGame *savefile ) { + bool restorePhysics; + int i; + int num; + idBounds bounds; + + InitNonPersistentSpawnArgs ( ); + + // INTENTIONALLY NOT SAVED: idLinkList simpleThinkNode; + // INTENTIONALLY NOT SAVED: idAAS* aas; + // INTENTIONALLY NOT SAVED: idAASCallback* aasFind; + // NOTE That some AAS stuff is done at end of ::Restore + + move.Restore( savefile ); + savedMove.Restore( savefile ); + + savefile->ReadStaticObject( physicsObj ); + savefile->ReadBool( restorePhysics ); + if ( restorePhysics ) { + RestorePhysics( &physicsObj ); + } + + savefile->ReadBool( lastHitCheckResult ); + savefile->ReadInt( lastHitCheckTime ); + savefile->ReadInt( lastAttackTime ); + savefile->ReadFloat( projectile_height_to_distance_ratio ); + + savefile->ReadInt( num ); + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadVec3( attackAnimInfo[ i ].attackOffset ); + savefile->ReadVec3( attackAnimInfo[ i ].eyeOffset ); + } + + // TORESTORE: mutable idClipModel* projectileClipModel; + projectile.Restore( savefile ); + + // Talking + savefile->ReadInt( chatterTime ); +// savefile->ReadInt( chatterRateCombat ); // Don't save +// savefile->ReadInt( chatterRateIdle ); // Don't save + savefile->ReadInt( i ); + talkState = static_cast( i ); + talkTarget.Restore( savefile ); + savefile->ReadInt( i ); + talkMessage = static_cast( i ); + savefile->ReadInt( talkBusyCount ); + savefile->ReadInt ( speakTime ); + + // Focus + lookTarget.Restore ( savefile ); + savefile->ReadInt( i ); + focusType = static_cast( i ); + focusEntity.Restore ( savefile ); + savefile->ReadFloat ( focusRange ); + savefile->ReadInt ( focusAlignTime ); + savefile->ReadInt ( focusTime ); + savefile->ReadVec3( currentFocusPos ); + + // Looking + savefile->ReadBool( allowJointMod ); + savefile->ReadInt( alignHeadTime ); + savefile->ReadInt( forceAlignHeadTime ); + savefile->ReadAngles( eyeAng ); + savefile->ReadAngles( lookAng ); + savefile->ReadAngles( destLookAng ); + savefile->ReadAngles( lookMin ); + savefile->ReadAngles( lookMax ); + + savefile->ReadInt( num ); + lookJoints.SetGranularity( 1 ); + lookJoints.SetNum( num ); + lookJointAngles.SetGranularity( 1 ); + lookJointAngles.SetNum( num ); + for( i = 0; i < num; i++ ) { + savefile->ReadJoint( lookJoints[ i ] ); + savefile->ReadAngles( lookJointAngles[ i ] ); + } + + savefile->ReadFloat( eyeVerticalOffset ); + savefile->ReadFloat( eyeHorizontalOffset ); + savefile->ReadFloat( headFocusRate ); + savefile->ReadFloat( eyeFocusRate ); + + // Joint Controllers + savefile->ReadAngles( eyeMin ); + savefile->ReadAngles( eyeMax ); + savefile->ReadJoint( orientationJoint ); + + pusher.Restore( savefile ); // cnicholson: Added unrestored var + scriptedActionEnt.Restore ( savefile ); // cnicholson: Added unrestored var + + savefile->Read( &aifl, sizeof( aifl ) ); + + // Misc + savefile->ReadInt ( actionAnimNum ); + savefile->ReadInt ( actionTime ); + savefile->ReadInt ( actionSkipTime ); + savefile->ReadInt ( flagOverrides ); + + // Combat variables + savefile->Read ( &combat.fl, sizeof( combat.fl ) ); + savefile->ReadFloat ( combat.max_chasing_turn ); + savefile->ReadFloat ( combat.shotAtTime ); + savefile->ReadFloat ( combat.shotAtAngle ); + savefile->ReadVec2 ( combat.hideRange ); + savefile->ReadVec2 ( combat.attackRange ); + savefile->ReadInt ( combat.attackSightDelay ); + savefile->ReadFloat ( combat.meleeRange ); + savefile->ReadFloat ( combat.aggressiveRange ); + savefile->ReadFloat ( combat.aggressiveScale ); + savefile->ReadInt ( combat.investigateTime ); + savefile->ReadFloat ( combat.visStandHeight ); + savefile->ReadFloat ( combat.visCrouchHeight ); + savefile->ReadFloat ( combat.visRange ); + savefile->ReadFloat ( combat.earRange ); + savefile->ReadFloat ( combat.awareRange ); + savefile->ReadInt ( combat.tacticalMaskAvailable ); + savefile->ReadInt ( (int&)combat.tacticalCurrent ); + savefile->ReadInt ( combat.tacticalUpdateTime ); + savefile->ReadInt ( combat.tacticalPainTaken ); + savefile->ReadInt ( combat.tacticalPainThreshold ); + savefile->ReadInt ( combat.tacticalFlinches ); + savefile->ReadInt ( combat.maxLostVisTime ); + savefile->ReadFloat ( combat.threatBase ); + savefile->ReadFloat ( combat.threatCurrent ); + savefile->ReadInt ( combat.coverValidTime ); + savefile->ReadInt ( combat.maxInvalidCoverTime ); + + // Passive state variables + savefile->ReadString ( passive.prefix ); + savefile->ReadString ( passive.animFidgetPrefix ); + savefile->ReadString ( passive.animIdlePrefix); + savefile->ReadString ( passive.animTalkPrefix ); + savefile->ReadString ( passive.idleAnim ); + savefile->ReadInt ( passive.idleAnimChangeTime ); + savefile->ReadInt ( passive.fidgetTime ); + savefile->ReadInt ( passive.talkTime ); + savefile->Read ( &passive.fl, sizeof(passive.fl) ); + + // Enemy + enemy.ent.Restore ( savefile ); + savefile->Read ( &enemy.fl, sizeof(enemy.fl) ); + savefile->ReadInt ( enemy.lastVisibleChangeTime ); + savefile->ReadVec3 ( enemy.lastKnownPosition ); + savefile->ReadVec3 ( enemy.smoothedLinearVelocity ); + savefile->ReadVec3 ( enemy.smoothedPushedVelocity ); + savefile->ReadVec3 ( enemy.lastVisibleEyePosition ); + savefile->ReadVec3 ( enemy.lastVisibleChestPosition ); + savefile->ReadVec3 ( enemy.lastVisibleFromEyePosition ); + savefile->ReadInt ( enemy.lastVisibleTime ); + savefile->ReadFloat ( enemy.range ); + savefile->ReadFloat ( enemy.range2d ); + savefile->ReadInt ( enemy.changeTime ); + savefile->ReadInt ( enemy.checkTime ); + + // Pain variables + savefile->ReadFloat ( pain.threshold ); + savefile->ReadFloat ( pain.takenThisFrame ); + savefile->ReadInt ( pain.lastTakenTime ); + savefile->ReadInt ( pain.loopEndTime ); + savefile->ReadString ( pain.loopType ); + + // Functions + funcs.first_sight.Restore ( savefile ); + funcs.sight.Restore ( savefile ); + funcs.pain.Restore ( savefile ); + funcs.damage.Restore ( savefile ); + funcs.death.Restore ( savefile ); + funcs.attack.Restore ( savefile ); + funcs.init.Restore ( savefile ); + funcs.onclick.Restore ( savefile ); + funcs.launch_projectile.Restore ( savefile ); + funcs.footstep.Restore ( savefile ); + + mPlayback.Restore( savefile ); // cnicholson: Added restore functionality + mLookPlayback.Restore( savefile ); // cnicholson: Added restore functionality + + // Tactical Sensor + aasSensor->Restore( savefile ); + + // Helpers + tether.Restore ( savefile ); + helperCurrent.Restore( savefile ); + helperIdeal.Restore ( savefile ); + leader.Restore ( savefile ); + spawner.Restore ( savefile ); + + // Action timers + actionTimerRangedAttack.Restore ( savefile ); + actionTimerEvade.Restore ( savefile ); + actionTimerSpecialAttack.Restore ( savefile ); + actionTimerPain.Restore ( savefile ); + + // Actions + actionEvadeLeft.Restore ( savefile ); + actionEvadeRight.Restore ( savefile ); + actionRangedAttack.Restore ( savefile ); + actionMeleeAttack.Restore ( savefile ); + actionLeapAttack.Restore ( savefile ); + actionJumpBack.Restore ( savefile ); + + // Set the AAS if the character has the correct gravity vector + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + if ( gravity == gameLocal.GetGravity() ) { + SetAAS(); + } + + // create combat collision hull for exact collision detection, do initial set un-hidden + SetCombatModel(); + LinkCombat(); +} + +/* +===================== +idAI::InitNonPersistentSpawnArgs +===================== +*/ +void idAI::InitNonPersistentSpawnArgs ( void ) { + aasSensor = rvAASTacticalSensor::CREATE_SENSOR(this); + aasFind = NULL; + + simpleThinkNode.Remove ( ); + simpleThinkNode.SetOwner ( this ); + + chatterRateIdle = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_idle", "0" ) ); + chatterRateCombat = SEC2MS ( spawnArgs.GetFloat ( "chatter_rate_combat", "0" ) ); + chatterTime = 0; + + combat.tacticalMaskUpdate = 0; + + enemy.smoothVelocityRate = spawnArgs.GetFloat ( "smoothVelocityRate", "0.1" ); +} + +/* +===================== +idAI::Spawn +===================== +*/ +void idAI::Spawn( void ) { + const char* jointname; + const idKeyValue* kv; + idStr jointName; + idAngles jointScale; + jointHandle_t joint; + idVec3 local_dir; + + // Are all monsters disabled? + if ( !g_monsters.GetBool() ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + // Initialize the non saved spawn args + InitNonPersistentSpawnArgs ( ); + + spawnArgs.GetInt( "team", "1", team ); + spawnArgs.GetInt( "rank", "0", rank ); + + animPrefix = spawnArgs.GetString ( "animPrefix", "" ); + + // Standard flags + fl.notarget = spawnArgs.GetBool ( "notarget", "0" ); + fl.quickBurn = false; + + // AI flags + flagOverrides = 0; + memset ( &aifl, 0, sizeof(aifl) ); + aifl.ignoreFlashlight = spawnArgs.GetBool ( "ignore_flashlight", "1" ); + aifl.lookAtPlayer = spawnArgs.GetBool ( "lookAtPlayer", "0" ); + aifl.disableLook = spawnArgs.GetBool ( "noLook", "0" ); + aifl.undying = spawnArgs.GetBool ( "undying", "0" ); + aifl.killerGuard = spawnArgs.GetBool ( "killer_guard", "0" ); + aifl.scriptedEndWithIdle = true; + + // Setup Move Data + move.Spawn( spawnArgs ); + + pain.threshold = spawnArgs.GetInt ( "painThreshold", "0" ); + pain.takenThisFrame = 0; + + // Initialize combat variables + combat.fl.aware = spawnArgs.GetBool ( "ambush", "0" ); + combat.fl.tetherNoBreak = spawnArgs.GetBool ( "tetherNoBreak", "0" ); + combat.fl.noChatter = spawnArgs.GetBool ( "noCombatChatter" ); + combat.hideRange = spawnArgs.GetVec2 ( "hideRange", "150 750" ); + combat.attackRange = spawnArgs.GetVec2 ( "attackRange", "0 1000" ); + combat.attackSightDelay = SEC2MS ( spawnArgs.GetFloat ( "attackSightDelay", "1" ) ); + combat.visRange = spawnArgs.GetFloat( "visRange", "2048" ); + combat.visStandHeight = spawnArgs.GetFloat( "visStandHeight", "68" ); + combat.visCrouchHeight = spawnArgs.GetFloat( "visCrouchHeight", "48" ); + combat.earRange = spawnArgs.GetFloat( "earRange", "2048" ); + combat.awareRange = spawnArgs.GetFloat( "awareRange", "150" ); + combat.aggressiveRange = spawnArgs.GetFloat( "aggressiveRange", "0" ); + combat.maxLostVisTime = SEC2MS ( spawnArgs.GetFloat ( "maxLostVisTime", "10" ) ); + combat.tacticalPainThreshold = spawnArgs.GetInt ( "tactical_painThreshold", va("%d", health / 4) ); + combat.coverValidTime = 0; + combat.maxInvalidCoverTime = SEC2MS ( spawnArgs.GetFloat ( "maxInvalidCoverTime", "1" ) ); + combat.threatBase = spawnArgs.GetFloat ( "threatBase", "1" ); + combat.threatCurrent = combat.threatBase; + + SetPassivePrefix ( spawnArgs.GetString ( "passivePrefix" ) ); + + disablePain = spawnArgs.GetBool ( "nopain", "0" ); + + spawnArgs.GetFloat( "melee_range", "64", combat.meleeRange ); + spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio ); + + //melee superhero -- take far reduced damage from melee. + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) && spawnArgs.GetBool( "meleeSuperhero", "1") ) { + aifl.meleeSuperhero = true; + } else { + aifl.meleeSuperhero = false; + } + + //announce rate + announceRate = spawnArgs.GetFloat( "announceRate" ); + if( 0.0f == announceRate ) { + announceRate = AISPEAK_CHANCE; + } + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + + animator.RemoveOriginOffset( true ); + + // create combat collision hull for exact collision detection + SetCombatModel(); + + lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" ); + lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" ); + + lookJoints.SetGranularity( 1 ); + lookJointAngles.SetGranularity( 1 ); + kv = spawnArgs.MatchPrefix( "look_joint", NULL ); + while( kv ) { + jointName = kv->GetKey(); + jointName.StripLeadingOnce( "look_joint " ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() ); + } else { + jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" ); + jointScale.roll = 0.0f; + + // if no scale on any component, then don't bother adding it. this may be done to + // zero out rotation from an inherited entitydef. + if ( jointScale != ang_zero ) { + lookJoints.Append( joint ); + lookJointAngles.Append( jointScale ); + } + } + kv = spawnArgs.MatchPrefix( "look_joint", kv ); + } + + // calculate joint positions on attack frames so we can do proper "can hit" tests + CalculateAttackOffsets(); + + eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" ); + eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" ); + eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" ); + eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" ); + headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.06" ); + eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" ); + focusRange = spawnArgs.GetFloat( "focus_range", "0" ); + focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) ); + + jointname = spawnArgs.GetString( "joint_orientation" ); + if ( *jointname ) { + orientationJoint = animator.GetJointHandle( jointname ); + if ( orientationJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + jointname = spawnArgs.GetString( "joint_flytilt" ); + if ( *jointname ) { + move.flyTiltJoint = animator.GetJointHandle( jointname ); + if ( move.flyTiltJoint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() ); + } + } + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY ); + } else { + if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + } + + // move up to make sure the monster is at least an epsilon above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat(); + physicsObj.SetGravity( gravity ); + + SetPhysics( &physicsObj ); + + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + lookAng.Zero ( ); + lookAng.yaw = move.current_yaw; + + + SetAAS(); + + projectile = NULL; + projectileClipModel = NULL; + + if ( spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "trigger_anim" ) ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + Hide(); + } else { + // play a looping ambient sound if we have one + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); + } + + if ( health <= 0 ) { + gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() ); + health = 1; + } + + BecomeActive( TH_THINK ); + + if ( move.fl.allowPushMovables ) { + af.SetupPose( this, gameLocal.time ); + af.GetPhysics()->EnableClip(); + } + + // init the move variables + StopMove( MOVE_STATUS_DONE ); + + // Initialize any scripts + idStr prefix = "script_"; + for ( kv = spawnArgs.MatchPrefix ( prefix.c_str(), NULL ); + kv; + kv = spawnArgs.MatchPrefix ( prefix.c_str(), kv ) ) { + SetScript ( kv->GetKey().c_str() + prefix.Length(), kv->GetValue() ); + } + + // Initialize actions + actionEvadeLeft.Init ( spawnArgs, "action_evadeLeft", NULL, 0 ); + actionEvadeRight.Init ( spawnArgs, "action_evadeRight", NULL, 0 ); + actionRangedAttack.Init ( spawnArgs, "action_rangedAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK|AIACTIONF_MELEE ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", NULL, AIACTIONF_ATTACK ); + actionJumpBack.Init ( spawnArgs, "action_jumpBack", NULL, 0 ); + + // Global action timers + actionTimerRangedAttack.Init ( spawnArgs, "actionTimer_RangedAttack" ); + actionTimerEvade.Init ( spawnArgs, "actionTimer_Evade" ); + actionTimerSpecialAttack.Init ( spawnArgs, "actionTimer_SpecialAttack" ); + actionTimerPain.Init ( spawnArgs, "actionTimer_pain" ); + + // Available tactical options + combat.tacticalUpdateTime = 0; + combat.tacticalCurrent = AITACTICAL_NONE; + combat.tacticalMaskUpdate = 0; + combat.tacticalMaskAvailable = AITACTICAL_TURRET_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_MOVE_TETHER_BIT; + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_cover", "0" ) ? AITACTICAL_COVER_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_ranged", "0" ) ? AITACTICAL_RANGED_BITS : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_hide", "0" ) ? AITACTICAL_HIDE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_rush", "0" ) ? AITACTICAL_MELEE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "tactical_passive", "0" ) ? AITACTICAL_PASSIVE_BIT : 0); + combat.tacticalMaskAvailable |= (spawnArgs.GetBool ( "allowPlayerPush", "0" ) ? AITACTICAL_MOVE_PLAYERPUSH_BIT : 0); + + // Talking? + const char* npc; + if ( spawnArgs.GetString( "npc_name", NULL, &npc ) != NULL && *npc ) { + if ( spawnArgs.GetBool ( "follows", "0" ) ) { + SetTalkState ( TALK_FOLLOW ); + } else { + switch ( spawnArgs.GetInt( "talks" ) ) + { + case 2: + SetTalkState ( TALK_WAIT ); + break; + case 1: + SetTalkState ( TALK_OK ); + break; + case 0: + default: + SetTalkState ( TALK_BUSY ); + break; + } + } + } else { + SetTalkState ( TALK_NEVER ); + } + + // Passive or aggressive ai? + if ( spawnArgs.GetBool ( "passive" ) ) { + Event_BecomePassive ( true ); + + if ( spawnArgs.GetInt ( "passive" ) > 1 ) { + aifl.disableLook = true; + } + } + + // Print out a warning about any AI that is spawned unhidden since they will be all thinking + if( gameLocal.GameState ( ) == GAMESTATE_STARTUP && !spawnArgs.GetInt( "hide" ) && !spawnArgs.GetInt( "trigger_anim" ) && !spawnArgs.GetInt( "trigger_cover" ) && !spawnArgs.GetInt( "trigger_move" ) ){ + gameLocal.Warning( "Unhidden AI placed in map (will be constantly active): %s (%s)", name.c_str(), GetPhysics()->GetOrigin().ToString() ); + } + + Begin ( ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + PostEventMS( &EV_PostSpawn, 0 ); + // RAVEN END +} + +/* +=================== +idAI::Begin +=================== +*/ +void idAI::Begin ( void ) { + const char* temp; + bool animWalk; + bool animRun; + + // Look for the lack of a run or walk animation and force the opposite + animWalk = HasAnim ( ANIMCHANNEL_LEGS, "walk" ); + animRun = HasAnim ( ANIMCHANNEL_LEGS, "run" ); + if ( !animWalk && animRun ) { + move.fl.noWalk = true; + } else if ( !animRun && animWalk ) { + move.fl.noRun = true; + } + + // If a trigger anim or hide is specified then wait until activated before waking up + if ( spawnArgs.GetString ( "trigger_anim", "", &temp ) || spawnArgs.GetBool ( "hide" ) ) { + Hide(); + PostState ( "Wait_Activated" ); + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + // Wake up + } else { + PostState ( "State_WakeUp", SEC2MS(spawnArgs.GetFloat ( "wait" ) ) ); + } +} + +/* +=================== +idAI::WakeUp +=================== +*/ +void idAI::WakeUp ( void ) { + const char* temp; + + // Already awake? + if ( aifl.awake ) { + return; + } + + // Find the closest helper + UpdateHelper ( ); + + aifl.awake = true; + + // If the monster is flying then start them with the flying movement type + if ( spawnArgs.GetBool ( "static" ) ) { + SetMoveType ( MOVETYPE_STATIC ); + } else if ( spawnArgs.GetBool ( "flying" ) ) { + SetMoveType ( MOVETYPE_FLY ); + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += move.fly_offset; + } else { + SetMoveType ( MOVETYPE_ANIM ); + } + + // Wake up any linked entities + WakeUpTargets ( ); + + // Default enemy? + if ( !combat.fl.ignoreEnemies ) { + if ( spawnArgs.GetString ( "enemy", "", &temp ) && *temp ) { + SetEnemy ( gameLocal.FindEntity ( temp ) ); + } else if ( spawnArgs.GetBool ( "forceEnemy", "0" ) ) { + SetEnemy ( FindEnemy ( false, true ) ); + } + } + + // Default leader? + if ( spawnArgs.GetString ( "leader", "", &temp ) && *temp ) { + SetLeader ( gameLocal.FindEntity ( temp ) ); + } + + // If hidden and face enemy is specified we should orient ourselves toward our enemy immediately + if ( enemy.ent && IsHidden ( ) && spawnArgs.GetBool ( "faceEnemy" ) ) { + TurnToward ( enemy.ent->GetPhysics()->GetOrigin() ); + move.current_yaw = move.ideal_yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + Show ( ); + + aiManager.AddTeammate ( this ); + + // External script functions + ExecScriptFunction( funcs.init, this ); + + OnWakeUp ( ); +} + +/* +=================== +idAI::List_f +=================== +*/ +void idAI::List_f( const idCmdArgs &args ) { + int e; + int i; + idEntity* check; + int count; + idDict countsFixed; + idDict countsSpawned; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" ); + gameLocal.Printf( "------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + if ( !check ) { + continue; + } + + if ( check->IsType ( idAI::Type ) ) { + idAI* checkAI = static_cast(check); + + // Skip spawned AI + if ( checkAI->spawner ) { + continue; + } + countsFixed.SetInt ( check->GetEntityDefName(), countsFixed.GetInt ( check->GetEntityDefName(), "0" ) + 1 ); + + gameLocal.Printf( "%4i: %-20s %-20s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), checkAI->move.fl.allowAnimMove ); + count++; + } else if ( check->IsType ( rvSpawner::GetClassType() ) ) { + const idKeyValue* kv; + rvSpawner* checkSpawner = static_cast(check); + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + for ( i = 0; i < checkSpawner->GetNumSpawnPoints(); i ++ ) { + check = checkSpawner->GetSpawnPoint ( i ); + if ( !check ) { + continue; + } + for ( kv = check->spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = check->spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + countsSpawned.SetInt ( kv->GetValue(), countsSpawned.GetInt ( kv->GetValue(), "0" ) + 1 ); + } + } + } + } + + // Combine the two lists + for ( e = 0; e < countsSpawned.GetNumKeyVals(); e ++ ) { + const char* keyName = countsSpawned.GetKeyVal ( e )->GetKey(); + countsFixed.Set ( keyName, va("(%s) %3d", + countsSpawned.GetKeyVal ( e )->GetValue().c_str(), + countsFixed.GetInt ( keyName, "0" ) + atoi(countsSpawned.GetKeyVal ( e )->GetValue()) ) ); + } + + // Print out total counts + if ( countsFixed.GetNumKeyVals() ) { + gameLocal.Printf( "------------------------------------------------\n" ); + for ( e = 0; e < countsFixed.GetNumKeyVals(); e ++ ) { + const idKeyValue* kv = countsFixed.GetKeyVal ( e ); + gameLocal.Printf( "%10s: %-20s\n", kv->GetValue().c_str(), kv->GetKey().c_str() ); + } + } + + gameLocal.Printf( "------------------------------------------------\n" ); + gameLocal.Printf( "...%d monsters (%d unique types)\n", count, countsFixed.GetNumKeyVals() ); +} + +/* +================ +idAI::DormantBegin + +called when entity becomes dormant +================ +*/ +void idAI::DormantBegin( void ) { + // since dormant happens on a timer, we wont get to update particles to + // hidden through the think loop, but we need to hide them though. + if ( enemyNode.InList() ) { + // remove ourselves from the enemy's enemylist + enemyNode.Remove(); + } + idActor::DormantBegin(); +} + +/* +================ +idAI::DormantEnd + +called when entity wakes from being dormant +================ +*/ +void idAI::DormantEnd( void ) { + if ( enemy.ent && !enemyNode.InList() ) { + // let our enemy know we're back on the trail + idActor *enemyEnt = dynamic_cast< idActor *>( enemy.ent.GetEntity() ); + if( enemyEnt ){ + enemyNode.AddToEnd( enemyEnt->enemyList ); + } + } + idActor::DormantEnd(); +} + +/* +===================== +idAI::ValidateCover + +Validate the reserved cover feature with current conditions. Also quickly tests if +cover does not face enemy at all, which triggers an instant update (ignoring maxCoverInvalidTime) +===================== +*/ +bool idAI::ValidateCover( ) { + if ( !InCoverMode( ) ) { + return false; + } + + if ( aasSensor->TestValidWithCurrentState() && (!enemy.ent || enemy.range > combat.awareRange ) ) { + combat.coverValidTime = gameLocal.time; + } else if ( combat.coverValidTime && !IsCoverValid ( ) ) { + combat.coverValidTime = 0; + OnCoverInvalidated(); + } + + return IsCoverValid(); +} + +/* +===================== +idAI::DoDormantTests +===================== +*/ +bool idAI::DoDormantTests ( void ) { + // If in a scripted move that we should never go dormant in + if ( aifl.scripted && aifl.scriptedNeverDormant ) { + return false; + } + // If following a player never go dormant + if ( leader && leader->IsType ( idPlayer::GetClassType ( ) ) ) { + return false; + } + // AI should no longer go dormant when outside of tether + if ( IsTethered () && !IsWithinTether ( ) ) { + return false; + } + return idActor::DoDormantTests ( ); +} + +/* +===================== +idAI::Think +===================== +*/ +void idAI::Think( void ) { + + // if we are completely closed off from the player, don't do anything at all + if ( CheckDormant() ) { + return; + } + + // Simple think this frame? + aifl.simpleThink = aiManager.IsSimpleThink ( this ); + + aiManager.thinkCount++; + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Start ( ); + } + + if ( thinkFlags & TH_THINK ) { + // clear out the enemy when he dies or is hidden + idEntity* enemyEnt = enemy.ent; + idActor* enemyAct = dynamic_cast( enemyEnt ); + + // Clear our enemy if necessary + if ( enemyEnt ) { + if (enemyAct && enemyAct->IsInVehicle()) { + SetEnemy(enemyAct->GetVehicleController().GetVehicle()); // always get angry at the enemy's vehicle first, not the enemy himself + } else { + bool enemyDead = (enemyEnt->fl.takedamage && enemyEnt->health <= 0); + if ( enemyDead || enemyEnt->fl.notarget || enemyEnt->IsHidden() || (enemyAct && enemyAct->team == team)) { + ClearEnemy ( enemyDead ); + } + } + } + + // Action time is stopped when the torso is not idle + if ( !aifl.action ) { + actionTime += gameLocal.msec; + } + + ValidateCover(); + + move.current_yaw += deltaViewAngles.yaw; + move.ideal_yaw = idMath::AngleNormalize180( move.ideal_yaw + deltaViewAngles.yaw ); + deltaViewAngles.Zero(); + + if( move.moveType != MOVETYPE_PLAYBACK ){ + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + } + + if ( !move.fl.allowHiddenMove && IsHidden() ) { + // hidden monsters + UpdateStates (); + } else if( !ai_freeze.GetBool() ) { + Prethink(); + + // clear the ik before we do anything else so the skeleton doesn't get updated twice + walkIK.ClearJointMods(); + + // update enemy position if not dead + if ( !aifl.dead ) { + UpdateEnemy ( ); + } + + // update state machine + UpdateStates(); + + // run all movement commands + Move(); + + // if not dead, chatter and blink + if( move.moveType != MOVETYPE_DEAD ){ + UpdateChatter(); + CheckBlink(); + } + + Postthink(); + } else { + DrawTactical ( ); + } + + // clear pain flag so that we recieve any damage between now and the next time we run the script + aifl.pain = false; + aifl.damage = false; + aifl.pushed = false; + pusher = NULL; + } else if ( thinkFlags & TH_PHYSICS ) { + RunPhysics(); + } + + if ( move.fl.allowPushMovables ) { + PushWithAF(); + } + + if ( fl.hidden && move.fl.allowHiddenMove ) { + // UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + } + + aasSensor->Update(); + + UpdateAnimation(); + Present(); + LinkCombat(); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerThink.Stop ( ); + } +} + +/* +============ +idAI::UpdateFocus +============ +*/ +void idAI::UpdateFocus ( const idMat3& orientationAxis ) { + // Alwasy look at enemy + if ( !allowJointMod || !allowEyeFocus ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( IsBehindCover() && !aifl.action && !IsSpeaking() ) { + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( !aifl.scripted && (IsEnemyRecentlyVisible(2.0f) || gameLocal.GetTime()-lastAttackTime<1000) ) { + //not scripted and: have an enemy OR we shot at an enemy recently (for when we kill an enemy in the middle of a volley) + SetFocus ( AIFOCUS_ENEMY, 1000 ); + } else if ( move.fl.moving && InCoverMode ( ) && DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_COVER, 1000 ); + } else if ( InLookAtCoverMode() ) { + SetFocus ( AIFOCUS_COVERLOOK, 1000 ); + } else if ( talkTarget && gameLocal.time < passive.talkTime ) { + SetFocus ( AIFOCUS_TALK, 100 ); + } else if ( lookTarget ) { + SetFocus ( AIFOCUS_TARGET, 100 ); + } else if ( !IsBehindCover() && tether && DistanceTo ( move.moveDest ) < move.walkRange * 2.0f ) { + SetFocus ( AIFOCUS_TETHER, 100 ); + } else if ( IsTethered() && (move.fl.moving || IsBehindCover()) ) { + //tethered and at cover or moving - don't look at leader or player + SetFocus ( AIFOCUS_NONE, 0 ); + } else if ( helperCurrent && helperCurrent->IsCombat ( ) ) { + SetFocus ( AIFOCUS_HELPER, 100 ); + } else if ( !aifl.scripted && !move.fl.moving ) { + idEntity* lookat = NULL; + aiFocus_t newfocus = AIFOCUS_NONE; + + if ( leader ) { + idVec3 dir2Leader = leader->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin(); + if ( (viewAxis[0] * dir2Leader) > 0.0f ) { + //leader is in front of me + newfocus = AIFOCUS_LEADER; + lookat = leader; + } else if ( focusType == AIFOCUS_LEADER ) { + //stop looking at him! + SetFocus ( AIFOCUS_NONE, 0 ); + } + } else if ( aifl.lookAtPlayer && !move.fl.moving ) { + newfocus = AIFOCUS_PLAYER; + lookat = gameLocal.GetLocalPlayer(); + } + + if ( newfocus != AIFOCUS_NONE && lookat ) { + if ( DistanceTo ( lookat ) < focusRange * (move.fl.moving?2.0f:1.0f) && CheckFOV ( lookat->GetPhysics()->GetOrigin() ) ) { + SetFocus ( newfocus, 1000 ); + } + } + } + + // Make sure cover look wasnt invalidated + if ( focusType == AIFOCUS_COVERLOOK && !aasSensor->Look() ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_COVER && !aasSensor->Reserved ( ) ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_HELPER && !helperCurrent ) { + focusType = AIFOCUS_NONE; + } else if ( focusType == AIFOCUS_TETHER && !tether ) { + focusType = AIFOCUS_NONE; + } + + // Update focus type to none when the focus time runs out + if ( focusType != AIFOCUS_NONE ) { + if ( gameLocal.time > focusTime ) { + focusType = AIFOCUS_NONE; + } + } + + // Calculate the focus position + if ( focusType == AIFOCUS_NONE ) { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } else if ( focusType == AIFOCUS_COVER ) { + currentFocusPos = GetEyePosition() + aasSensor->Reserved()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_COVERLOOK ) { + currentFocusPos = GetEyePosition() + aasSensor->Look()->Normal() * 64.0f; + } else if ( focusType == AIFOCUS_ENEMY ) { + currentFocusPos = enemy.lastVisibleEyePosition; + } else if ( focusType == AIFOCUS_HELPER ) { + currentFocusPos = helperCurrent->GetPhysics()->GetOrigin() + helperCurrent->GetDirection ( this ); + } else if ( focusType == AIFOCUS_TETHER ) { + currentFocusPos = GetEyePosition() + tether->GetPhysics()->GetAxis()[0] * 64.0f; + } else { + idEntity* focusEnt = NULL; + switch ( focusType ) { + case AIFOCUS_LEADER: focusEnt = leader; break; + case AIFOCUS_PLAYER: focusEnt = gameLocal.GetLocalPlayer(); break; + case AIFOCUS_TALK: focusEnt = talkTarget; break; + case AIFOCUS_TARGET: focusEnt = lookTarget; break; + } + if ( focusEnt ) { + if ( focusEnt->IsType ( idActor::GetClassType ( ) ) ) { + currentFocusPos = static_cast(focusEnt)->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal(); + } else { + currentFocusPos = focusEnt->GetPhysics()->GetOrigin(); + } + } else { + currentFocusPos = GetEyePosition() + orientationAxis[ 0 ] * 64.0f; + } + } + + //draw it so we can see what they think they're looking at! + if ( DebugFilter(ai_debugMove) ) { // Cyan & Blue = currentFocusPos + if ( focusType != AIFOCUS_NONE ) { + gameRenderWorld->DebugArrow( colorCyan, GetEyePosition(), currentFocusPos, 0 ); + } else { + gameRenderWorld->DebugArrow( colorBlue, GetEyePosition(), currentFocusPos, 0 ); + } + } +} + +/* +===================== +idAI::UpdateStates +===================== +*/ +void idAI::UpdateStates ( void ) { + MEM_SCOPED_TAG(tag,MA_DEFAULT); + + // Continue updating tactical state if for some reason we dont have one + if ( !aifl.dead && !aifl.scripted && !aifl.action && stateThread.IsIdle ( ) && aifl.scriptedEndWithIdle ) { + UpdateTactical ( 0 ); + } else { + UpdateState(); + } + + // clear the hit enemy flag so we catch the next time we hit someone + aifl.hitEnemy = false; + + if ( move.fl.allowHiddenMove || !IsHidden() ) { + // update the animstate if we're not hidden + UpdateAnimState(); + } +} + +/* +===================== +idAI::OnStateChange +===================== +*/ +void idAI::OnStateChange ( int channel ) { +} + +/* +===================== +idAI::OverrideFlag +===================== +*/ +void idAI::OverrideFlag ( aiFlagOverride_t flag, bool value ) { + bool oldValue; + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: oldValue = disablePain; break; + case AIFLAGOVERRIDE_DAMAGE: oldValue = fl.takedamage; break; + case AIFLAGOVERRIDE_NOTURN: oldValue = move.fl.noTurn; break; + case AIFLAGOVERRIDE_NOGRAVITY: oldValue = move.fl.noGravity; break; + default: + return; + } + + if ( oldValue == value ) { + return; + } + + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + + if ( (flagOverrides & flagOverride) && !!(flagOverrides & flagOverrideValue) == value ) { + flagOverrides &= ~flagOverride; + } else { + if ( oldValue ) { + flagOverrides |= flagOverrideValue; + } else { + flagOverrides &= ~flagOverrideValue; + } + flagOverrides |= flagOverride; + } + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/* +===================== +idAI::RestoreFlag +===================== +*/ +void idAI::RestoreFlag ( aiFlagOverride_t flag ) { + int flagOverride = 1 << (flag * 2); + int flagOverrideValue = 1 << ((flag * 2) + 1); + bool value; + + if ( !(flagOverrides&flagOverride) ) { + return; + } + + value = !!(flagOverrides & flagOverrideValue); + + switch ( flag ) { + case AIFLAGOVERRIDE_DISABLEPAIN: disablePain = value; break; + case AIFLAGOVERRIDE_DAMAGE: fl.takedamage = value; break; + case AIFLAGOVERRIDE_NOTURN: move.fl.noTurn = value; break; + case AIFLAGOVERRIDE_NOGRAVITY: move.fl.noGravity = value; break; + } +} + +/*********************************************************************** + + Damage + +***********************************************************************/ + +/* +===================== +idAI::ReactionTo +===================== +*/ +int idAI::ReactionTo( const idEntity *ent ) { + + if ( ent->fl.hidden ) { + // ignore hidden entities + return ATTACK_IGNORE; + } + + if ( !ent->IsType( idActor::GetClassType() ) ) { + return ATTACK_IGNORE; + } + + if( combat.fl.ignoreEnemies ){ + return ATTACK_IGNORE; + } + + const idActor *actor = static_cast( ent ); + if ( actor->IsType( idPlayer::GetClassType() ) && static_cast(actor)->noclip ) { + // ignore players in noclip mode + return ATTACK_IGNORE; + } + + // actors on different teams will always fight each other + if ( actor->team != team ) { + if ( actor->fl.notarget ) { + // don't attack on sight when attacker is notargeted + return ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE ; */ + } + return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE; /* | ATTACK_ON_ACTIVATE; */ + } + + //FIXME: temporarily disabled monsters of same rank fighting because it's happening too much. + // monsters will fight when attacked by lower or equal ranked monsters. rank 0 never fights back. + //if ( rank && ( actor->rank <= rank ) ) { + if ( rank && ( actor->rank < rank ) ) { + return ATTACK_ON_DAMAGE; + } + + // don't fight back + return ATTACK_IGNORE; +} + +/* +===================== +idAI::AdjustHealthByDamage +===================== +*/ +void idAI::AdjustHealthByDamage ( int damage ) { + if ( aifl.undying ) { + return; + } + idActor::AdjustHealthByDamage ( damage ); + + if ( g_perfTest_aiUndying.GetBool() && health <= 0 ) { + //so we still take pain! + health = 1; + } +} + +/* +===================== +idAI::Pain +===================== +*/ +bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + aifl.pain = idActor::Pain( inflictor, attacker, damage, dir, location ); + aifl.damage = true; + + // force a blink + blink_time = 0; + + // No special handling if friendly fire + // jshepard: friendly fire will cause pain. Players will only be able to pain buddy marines + // via splash damage. + +/* + if ( attacker && attacker->IsType ( idActor::GetClassType ( ) ) ) { + if ( static_cast( attacker )->team == team ) { + return aifl.pain; + } + } +*/ + + // ignore damage from self + if ( attacker != this ) { + // React to taking pain + ReactToPain ( attacker, damage ); + + pain.takenThisFrame += damage; + pain.lastTakenTime = gameLocal.time; + combat.tacticalPainTaken += damage; + + // If taken too much pain where we then skip the current destination + if ( combat.tacticalPainThreshold && combat.tacticalPainTaken > combat.tacticalPainThreshold ) { + if (team==AITEAM_STROGG && + (combat.tacticalMaskAvailable&AITACTICAL_COVER_BITS) && + IsType(rvAITactical::GetClassType()) && + ai_allowTacticalRush.GetBool() && + spawnArgs.GetBool("rushOnPain", "1")) { + + // clear any tether + tether = NULL; + + // change ranged distances + combat.attackRange[0] = 0.0f; + combat.attackRange[1] = 100.0f; // make them get really close + + // remove cover behavior + combat.tacticalMaskAvailable &= ~AITACTICAL_COVER_BITS; + + // add ranged behavior + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BITS; + } + ForceTacticalUpdate ( ); + return true; + } + + // If looping pain and a new paintype comes in we can stop the loop + if ( pain.loopEndTime && pain.loopType.Icmp ( painType ) ) { + pain.loopEndTime = 0; + pain.loopType = painType; + } + + ExecScriptFunction( funcs.damage ); + } + + // If we were hit by our enemy and our enemy isnt visible then + // force the enemy visiblity to be updated as if we saw him momentarily + if ( enemy.ent == attacker && !enemy.fl.visible ) { + UpdateEnemyPosition ( true ); + } + + return aifl.pain; +} + +/* +===================== +idAI::Killed +===================== +*/ +void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idAngles ang; + const char* modelDeath; + const idKeyValue* kv; + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + GetDamageGroup( location ) ); + } + + if ( aifl.dead ) { + aifl.pain = true; + aifl.damage = true; + return; + } + + aifl.dead = true; + + // turn off my flashlight, if I had one + ProcessEvent( &AI_Flashlight, false ); + + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + // Hide surfaces on death + for ( kv = spawnArgs.MatchPrefix ( "deathhidesurface", NULL ); + kv; + kv = spawnArgs.MatchPrefix ( "deathhidesurface", kv ) ) { + HideSurface ( kv->GetValue() ); + } + + // stop all voice sounds + StopSpeaking( true ); + + SetMoveType ( MOVETYPE_DEAD ); + + move.fl.noGravity = false; + move.fl.allowPushMovables = false; + aifl.scripted = false; + + physicsObj.UseFlyMove( false ); + physicsObj.ForceDeltaMove( false ); + + // end our looping ambient sound + StopSound( SND_CHANNEL_AMBIENT, false ); + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + + aiManager.AnnounceKill ( this, attacker, inflictor ); + aiManager.AnnounceDeath ( this, attacker ); + } + + if ( attacker && attacker->IsType( idActor::GetClassType() ) ) { + gameLocal.AlertAI( ( idActor * )attacker ); + } + + // activate targets + ActivateTargets( this ); + + RemoveAttachments(); + RemoveProjectile(); + StopMove( MOVE_STATUS_DONE ); + + OnDeath(); + CheckDeathObjectives(); + + ClearEnemy(); + + // make monster nonsolid + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + + Unbind(); + if ( g_perfTest_aiNoRagdoll.GetBool() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else if( fl.quickBurn ){ + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + + } else { + if ( StartRagdoll() ) { + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + } + + if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) { + // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here + if ( spawnArgs.MatchPrefix( "lipsync_death" ) ) { + Speak( "lipsync_death", true ); + } else { + StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL ); + } + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + SetModel( modelDeath ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.PutToRest(); + physicsObj.DisableImpact(); + } + } + + SetState ( "State_Killed" ); + + kv = spawnArgs.MatchPrefix( "def_drops", NULL ); + while( kv ) { + idDict args; + idEntity *tEnt; + if( kv->GetValue() != "" ){ + args.Set( "classname", kv->GetValue() ); + args.Set( "origin", physicsObj.GetAbsBounds().GetCenter().ToString() ); + // Let items know that they are of the dropped variety + args.Set( "dropped", "1" ); + if (gameLocal.SpawnEntityDef( args, &tEnt )) { + if ( tEnt && tEnt->GetPhysics()) { //tEnt *should* be valid, but hey... + // magic/arbitrary number to give it some spin. Some constants used to ensure guns rarely fall standing up + tEnt->GetPhysics()->SetAngularVelocity( idVec3( (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f, + (gameLocal.random.RandomFloat() * 10.0f) + 20.0f)); + } + } + } + kv = spawnArgs.MatchPrefix( "def_drops", kv ); + } +} + +/*********************************************************************** + + Targeting/Combat + +***********************************************************************/ + +/* +===================== +idAI::Activate + +Notifies the script that a monster has been activated by a trigger or flashlight +===================== +*/ +void idAI::Activate( idEntity *activator ) { + idPlayer *player; + + // Set our tether? + if ( activator && activator->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(activator) ); + } + + if ( aifl.dead ) { + // ignore it when they're dead + return; + } + + // make sure he's not dormant + dormantStart = 0; + + aifl.activated = true; + if ( !activator || !activator->IsType( idPlayer::GetClassType() ) ) { + player = gameLocal.GetLocalPlayer(); + } else { + player = static_cast( activator ); + } + + if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) { + SetEnemy( player ); + } + + // If being activated by a spawner we need to attach to it + if ( activator && activator->IsType ( rvSpawner::GetClassType() ) ) { + rvSpawner* spawn = static_cast( activator ); + spawn->Attach ( this ); + SetSpawner ( spawn ); + } +} + +/* +===================== +idAI::TalkTo +===================== +*/ +void idAI::TalkTo( idActor *actor ) { + + // jshepard: the dead do not speak. + if ( aifl.dead ) + return; + + ExecScriptFunction( funcs.onclick ); + + // Cant talk when already talking + if ( aifl.action || IsSpeaking ( ) || !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_TALK ) ) { + return; + } + + + switch ( GetTalkState ( ) ) { + case TALK_OK: + if ( talkMessage >= TALKMSG_LOOP ) { + //MCG: requested change: stop responding after one loop + return; + } + // at least one second between talking to people + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_TALK, 2000 ); + + talkTarget = actor; + + // Move to the next talk message + talkMessage = (talkMessage_t)((int)talkMessage + 1); + + // Loop until we find a valid talk message + while ( 1 ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Try the lipsync for the specific passive prefix first + if ( Speak ( va("lipsync_%stalk_%s", passive.prefix.c_str(), postfix.c_str() ) ) ) { + break; + } + // Try the generic lipsync for the current talk message + if ( Speak ( va("lipsync_talk_%s", postfix.c_str() ) ) ) { + break; + } + // If the first loop failed then there is no other options + if ( talkMessage == TALKMSG_LOOP ) { + return; + } else if ( talkMessage >= TALKMSG_LOOP ) { + talkMessage = TALKMSG_LOOP; + } else { + talkMessage = (talkMessage_t)((int)talkMessage + 1); + } + } + + // Start talking anim if we have one + if ( IsSpeaking ( ) ) { + passive.talkTime = speakTime; + } + + break; + + case TALK_FOLLOW: + if ( actor == leader ) { + leader = NULL; + Speak ( "lipsync_stopfollow", true ); + } else { + leader = actor; + Speak ( "lipsync_follow", true ); + } + break; + + case TALK_BUSY: + if ( talkBusyCount > 3 ) + {//only say a max of 4 lines + return; + } + talkBusyCount++; + //try to say this variant - if we can't, stop forever + if ( !Speak ( va("lipsync_busy_%d",talkBusyCount) ) ) + {//nevermore + talkBusyCount = 999; + } + break; + + case TALK_WAIT: + //do nothing + break; + } +} + +/* +===================== +idAI::GetTalkState +===================== +*/ +talkState_t idAI::GetTalkState( void ) const { + if ( ( talkState != TALK_NEVER ) && aifl.dead ) { + return TALK_DEAD; + } + if ( IsHidden() ) { + return TALK_NEVER; + } + if ( (talkState == TALK_OK || talkState == TALK_FOLLOW) && aifl.scripted ) { + return TALK_BUSY; + } + return talkState; +} + +/* +===================== +idAI::TouchedByFlashlight +===================== +*/ +void idAI::TouchedByFlashlight( idActor *flashlight_owner ) { + if ( RespondToFlashlight() ) { + Activate( flashlight_owner ); + } +} + +/* +===================== +idAI::ClearEnemy +===================== +*/ +void idAI::ClearEnemy( bool dead ) { + // Dont bother if we dont have an enemy + if ( !enemy.ent ) { + return; + } + + if ( move.moveCommand == MOVE_TO_ENEMY ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + } else if ( !aifl.scripted + && move.moveCommand == MOVE_TO_COVER + && !move.fl.done + && aasSensor->Reserved() + && !IsBehindCover() ) { + //clear cover and stop move + aasSensor->Reserve( NULL ); + combat.coverValidTime = 0; + OnCoverInvalidated(); + ForceTacticalUpdate(); + StopMove( MOVE_STATUS_DONE ); + } + + enemyNode.Remove(); + enemy.ent = NULL; + enemy.fl.dead = dead; + combat.fl.seenEnemyDirectly = false; + + UpdateHelper ( ); +} + +/* +===================== +idAI::UpdateEnemyPosition +===================== +*/ +void idAI::UpdateEnemyPosition ( bool forceUpdate ) { + if( !enemy.ent || (!enemy.fl.visible && !forceUpdate) ) { + return; + } + + idActor* enemyActor = dynamic_cast(enemy.ent.GetEntity()); + idEntity* enemyEnt = static_cast(enemy.ent.GetEntity()); + + enemy.lastVisibleFromEyePosition = GetEyePosition ( ); + + // Update the enemy origin if it isnt locked + if ( !enemy.fl.lockOrigin ) { + enemy.lastKnownPosition = enemyEnt->GetPhysics()->GetOrigin(); + + if ( enemyActor ) { + enemy.lastVisibleEyePosition = enemyActor->GetEyePosition(); + enemy.lastVisibleChestPosition = enemyActor->GetChestPosition(); + } else { + enemy.lastVisibleEyePosition = enemy.ent->GetPhysics()->GetOrigin ( ); + enemy.lastVisibleChestPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + } + } +} + +/* +===================== +idAI::UpdateEnemy +===================== +*/ +void idAI::UpdateEnemy ( void ) { + predictedPath_t predictedPath; + + // If we lost our enemy then clear it out to be sure + if( !enemy.ent ) { + return; + } + + // Rest to not being in fov + enemy.fl.inFov = false; + + // Bail out if we arent queued to do a complex think + if ( aifl.simpleThink ) { + // Keep the fov flag up to date + if ( IsEnemyVisible ( ) ) { + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + } + } else { + bool oldVisible; + + // Cache current enemy visiblity so we can see if it changes + oldVisible = IsEnemyVisible ( ); + + // See if enemy still visible + UpdateEnemyVisibility ( ); + + // If our enemy isnt visible but is within aware range then we know where he is + if ( !IsEnemyVisible ( ) && DistanceTo ( enemy.ent ) < combat.awareRange ) { + UpdateEnemyPosition( true ); + } else { +/* + // check if we heard any sounds in the last frame + if ( enemyEnt == gameLocal.GetAlertActor() ) { + float dist = ( enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin() ).LengthSqr(); + if ( dist < Square( combat.earRange ) ) { + SetEnemyPosition(); + } + } +*/ + } + + // Update fov + enemy.fl.inFov = CheckFOV( enemy.lastKnownPosition ); + + // Set enemy.visibleTime to the time when the visibility flag changed + if ( oldVisible != IsEnemyVisible ( ) ) { + // Just caught sight of the enemy after loosing him, delay the attack actions a bit + if ( !oldVisible && combat.attackSightDelay ) { + float delay; + if ( enemy.lastVisibleChangeTime ) { + // The longer the enemy has not been visible, the more we should delay + delay = idMath::ClampFloat ( 0.0f, 1.0f, (gameLocal.time - enemy.lastVisibleChangeTime) / AI_SIGHTDELAYSCALE ); + } else { + // The enemy has never been visible so delay the full amount + delay = 1.0f; + } + // Add to the ranged attack timer providing its not already running a timer + if ( actionTimerRangedAttack.IsDone ( actionTime) ) { + actionTimerRangedAttack.Clear ( actionTime ); + actionTimerRangedAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + // Add to the special attack timer providing its not already running a timer + if ( actionTimerSpecialAttack.IsDone ( actionTime ) ) { + actionTimerSpecialAttack.Clear ( actionTime ); + actionTimerSpecialAttack.Add ( combat.attackSightDelay * delay, 0.5f ); + } + } + + enemy.lastVisibleChangeTime = gameLocal.time; + } + + // Handler for visibility change + if ( oldVisible != IsEnemyVisible ( ) ) { + OnEnemyVisiblityChange ( oldVisible ); + } + } + + // Adjust smoothed linear and pushed velocities for enemies + if ( enemy.fl.visible ) { + enemy.smoothedLinearVelocity += ((enemy.ent->GetPhysics()->GetLinearVelocity ( ) - enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedLinearVelocity) * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity += ((enemy.ent->GetPhysics()->GetPushedLinearVelocity ( ) - enemy.smoothedPushedVelocity) * enemy.smoothVelocityRate ); + } else { + enemy.smoothedLinearVelocity -= (enemy.smoothedLinearVelocity * enemy.smoothVelocityRate ); + enemy.smoothedPushedVelocity -= (enemy.smoothedPushedVelocity * enemy.smoothVelocityRate ); + } + + // Update enemy range + enemy.range = DistanceTo ( enemy.lastKnownPosition ); + enemy.range2d = DistanceTo2d ( enemy.lastKnownPosition ); + + // Calulcate the aggression scale + // Skill level 0: (1.0) + // Skill level 1: (1.0 - 1.25) + // Skill level 2: (1.0 - 1.5) + // Skill level 3: (1.0 - 1.75) + if ( combat.aggressiveRange > 0.0f ) { + combat.aggressiveScale = (g_skill.GetFloat() / MAX_SKILL_LEVELS); + combat.aggressiveScale *= 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, enemy.range / combat.aggressiveRange ); + combat.aggressiveScale += 1.0f; + } +} + +/* +===================== +idAI::LastKnownPosition +===================== +*/ +const idVec3& idAI::LastKnownPosition ( const idEntity *ent ) { + return (ent==enemy.ent)?(enemy.lastKnownPosition):(ent->GetPhysics()->GetOrigin()); +} + +/* +===================== +idAI::UpdateEnemyVisibility + +Update whether or not the AI can see its enemy or not and determine how. +===================== +*/ +void idAI::UpdateEnemyVisibility ( void ) { + enemy.fl.visible = false; + + // No enemy so nothing to see + if ( !enemy.ent ) { + return; + } + + // Update enemy visibility flag + enemy.fl.visible = CanSeeFrom ( GetEyePosition ( ), enemy.ent, false ); + + // IF the enemy isnt visible and not forcing an update we can just early out + if ( !enemy.fl.visible ) { + return; + } + + // If we are seeing our enemy for the first time after changing enemies, call him out + if ( enemy.lastVisibleTime < enemy.changeTime ) { + AnnounceNewEnemy( ); + } + + combat.fl.seenEnemyDirectly = true; + enemy.lastVisibleTime = gameLocal.time; + + // Update our known locations of the enemy since we just saw him + UpdateEnemyPosition ( ); +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +void idAI::SetSpawner ( rvSpawner* _spawner ) { + spawner = _spawner; +} + +/* +===================== +idAI::SetSpawner +===================== +*/ +rvSpawner* idAI::GetSpawner ( void ) { + return spawner; +} + +/* +===================== +idAI::SetEnemy +===================== +*/ +bool idAI::SetEnemy( idEntity *newEnemy ) { + idEntity* oldEnemy; + + // Look for obvious early out + if ( enemy.ent == newEnemy ) { + return true; + } + + // Cant set enemy if dead + if ( aifl.dead ) { + ClearEnemy ( false ); + return false; + } + + // Cant set enemy if the enemy is dead or has no target set + if ( newEnemy ) { + if ( newEnemy->health <= 0 || newEnemy->fl.notarget ) { + return false; + } + } + + oldEnemy = enemy.ent; + enemy.fl.dead = false; + enemy.fl.lockOrigin = false; + + if ( !newEnemy ) { + ClearEnemy ( false ); + } else { + // Set our current enemy + enemy.ent = newEnemy; + + // If the enemy is an actor then add to the actors enemy list + if( newEnemy->IsType( idActor::GetClassType() ) ){ + idActor* enemyActor = static_cast( newEnemy ); + enemyNode.AddToEnd( enemyActor->enemyList ); + } + } + + // Allow custom handling of enemy change + if ( enemy.ent != oldEnemy ) { + OnEnemyChange ( oldEnemy ); + } + + return true; +} + + +/* +=================== +idAI::CalculateAttackOffsets + +calculate joint positions on attack frames so we can do proper "can hit" tests +=================== +*/ +void idAI::CalculateAttackOffsets ( void ) { + const idDeclModelDef* modelDef; + int num; + int i; + int frame; + const frameCommand_t* command; + idMat3 axis; + const idAnim* anim; + jointHandle_t joint; + + modelDef = animator.ModelDef(); + if ( !modelDef ) { + return; + } + num = modelDef->NumAnims(); + + // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim + animator.RemoveOriginOffset( false ); + + // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for + // launch offsets so that anim number can be used without subtracting 1. + attackAnimInfo.SetGranularity( 1 ); + attackAnimInfo.SetNum( num + 1 ); + attackAnimInfo[ 0 ].attackOffset.Zero(); + attackAnimInfo[ 0 ].eyeOffset.Zero(); + + for( i = 1; i <= num; i++ ) { + attackAnimInfo[ i ].attackOffset.Zero(); + attackAnimInfo[ i ].eyeOffset.Zero(); + anim = modelDef->GetAnim( i ); + if ( !anim ) { + continue; + } + + frame = anim->FindFrameForFrameCommand( FC_AI_ATTACK, &command ); + if ( frame >= 0 ) { + joint = animator.GetJointHandle( command->joint->c_str() ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Invalid joint '%s' on 'ai_attack' frame command on frame %d of model '%s'", command->joint->c_str(), frame, modelDef->GetName() ); + } + GetJointTransformForAnim( joint, i, FRAME2MS( frame ), attackAnimInfo[ i ].attackOffset, axis ); + + if ( chestOffsetJoint!= INVALID_JOINT ) { + GetJointTransformForAnim( chestOffsetJoint, i, FRAME2MS( frame ), attackAnimInfo[ i ].eyeOffset, axis ); + } + } + } + + animator.RemoveOriginOffset( true ); +} + +/* +===================== +idAI::CreateProjectileClipModel +===================== +*/ +void idAI::CreateProjectileClipModel( void ) const { + if ( projectileClipModel == NULL ) { + idBounds projectileBounds( vec3_origin ); + projectileBounds.ExpandSelf( 2.0f ); // projectileRadius ); + projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) ); + } +} + +/* +===================== +idAI::GetPredictedAimDirOffset +===================== +*/ +void idAI::GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const { + float a; + float b; + float c; + float d; + float t; + idVec3 r; + + // Make sure there is something to predict + if ( targetVelocity.LengthSqr ( ) == 0.0f ) { + offset.Zero ( ); + return; + } + + // Solve for (t): magnitude(targetVelocity * t + target - source) = projectileSpeed * t + + r = (target-source); + a = (targetVelocity * targetVelocity) - Square(projectileSpeed); + b = (targetVelocity * 2.0f) * r; + c = r*r; + d = b*b - 4*a*c; + t = 0.0f; + + if ( d >= 0.0f ) { + float t1; + float t2; + float denom; + d = idMath::Sqrt(d); + denom = 1.0f / (2.0f * a); + t1 = (-b + d) * denom; + t2 = (-b - d) * denom; + if ( t1 < 0.0f && t2 < 0.0f ) { + t = 0.0f; + } else if ( t1 < 0.0f ) { + t = t2; + } else if ( t2 < 0.0f ) { + t = t1; + } else { + t = Min(t1,t2); + } + } + + offset = targetVelocity * t; +} + +/* +===================== +idAI::GetAimDir +===================== +*/ +bool idAI::GetAimDir( + const idVec3& firePos, + const idEntity* target, + const idDict* projectileDef, + idEntity* ignore, + idVec3& aimDir, + float aimOffset, + float predict + ) const +{ + idVec3 targetPos1; + idVec3 targetPos2; + idVec3 targetLinearVel; + idVec3 targetPushedVel; + idVec3 delta; + float max_height; + bool result; + idEntity* targetEnt = const_cast(target); + + // if no aimAtEnt or projectile set + if ( !targetEnt ) { + aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + return false; + } + + float projectileSpeed = idProjectile::GetVelocity ( projectileDef ).LengthFast ( ); + idVec3 projectileGravity = idProjectile::GetGravity ( projectileDef ); + + if ( projectileClipModel == NULL ) { + CreateProjectileClipModel(); + } + + if ( targetEnt == enemy.ent ) { + targetPos2 = enemy.lastVisibleEyePosition; + targetPos1 = enemy.lastVisibleChestPosition; + targetPushedVel = enemy.smoothedPushedVelocity; + targetLinearVel = enemy.smoothedLinearVelocity; + } else if ( targetEnt->IsType( idActor::GetClassType() ) ) { + targetPos2 = static_cast(targetEnt)->GetEyePosition ( ); + targetPos1 = static_cast(targetEnt)->GetChestPosition ( ); + targetPushedVel = target->GetPhysics()->GetPushedLinearVelocity ( ); + targetLinearVel = (target->GetPhysics()->GetLinearVelocity ( ) - targetPushedVel); + } else { + targetPos1 = targetEnt->GetPhysics()->GetAbsBounds().GetCenter(); + targetPos2 = targetPos1; + targetPushedVel.Zero ( ); + targetLinearVel.Zero ( ); + } + + // Target prediction + if ( projectileSpeed == 0.0f ) { + // Hitscan prediction actually causes the hitscan to miss unless it is predicted. + delta = (targetLinearVel * (-1.0f + predict)); + } else { + // Projectile prediction must figure out how far to lead the shot to hit the target + GetPredictedAimDirOffset ( firePos, targetPos1, projectileSpeed, (targetLinearVel*predict)+targetPushedVel, delta ); + } + targetPos1 += delta; + targetPos2 += delta; + + result = false; + + // try aiming for chest + delta = targetPos1 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos1 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + if ( result || !targetEnt->IsType( idActor::GetClassType() ) ) { + return result; + } + } + + // try aiming for head + delta = targetPos2 - firePos; + max_height = (delta.NormalizeFast ( ) + aimOffset) * projectile_height_to_distance_ratio; + if ( max_height > 0.0f ) { + result = PredictTrajectory( firePos, targetPos2 + delta * aimOffset, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, targetEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); + } + + return result; +} + +/* +===================== +idAI::CreateProjectile +===================== +*/ +idProjectile *idAI::CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ) { + idEntity* ent; + const char* clsname; + + if ( !projectile.GetEntity() ) { + gameLocal.SpawnEntityDef( *projectileDict, &ent, false ); + if ( !ent ) { + clsname = projectileDict->GetString( "classname" ); + gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); + } + + if ( !ent->IsType( idProjectile::GetClassType() ) ) { + clsname = ent->GetClassname(); + gameLocal.Error( "'%s' is not an idProjectile", clsname ); + } + projectile = (idProjectile*)ent; + } + + projectile.GetEntity()->Create( this, pos, dir ); + + return projectile.GetEntity(); +} + +/* +===================== +idAI::RemoveProjectile +===================== +*/ +void idAI::RemoveProjectile( void ) { + if ( projectile.GetEntity() ) { + projectile.GetEntity()->PostEventMS( &EV_Remove, 0 ); + projectile = NULL; + } +} + +/* +===================== +idAI::Attack +===================== +*/ +bool idAI::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + // Get the attack dictionary + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", attackName ) ), false ); + if ( !attackDict ) { + gameLocal.Error ( "could not find attack entityDef 'def_attack_%s (%s)' on AI entity %s", attackName, spawnArgs.GetString ( va("def_attack_%s", attackName ) ), GetName ( ) ); + } + + // Melee Attack? + if ( spawnArgs.GetBool ( va("attack_%s_melee", attackName ), "0" ) ) { + return AttackMelee ( attackName, attackDict ); + } + + // Ranged attack (hitscan or projectile)? + return ( AttackRanged ( attackName, attackDict, joint, target, pushVelocity ) != NULL ); +} + +/* +===================== +idAI::AttackRanged +===================== +*/ +idProjectile* idAI::AttackRanged ( + const char* attackName, + const idDict* attackDict, + jointHandle_t joint, + idEntity* target, + const idVec3& pushVelocity + ) +{ + float attack_accuracy; + float attack_cone; + float attack_spread; + int attack_count; + bool attack_hitscan; + float attack_predict; + float attack_pullback; + int i; + idVec3 muzzleOrigin; + idMat3 muzzleAxis; + idVec3 dir; + idAngles ang; + idMat3 axis; + idProjectile* lastProjectile; + + lastProjectile = NULL; + + // Generic attack properties + attack_accuracy = spawnArgs.GetFloat ( va("attack_%s_accuracy", attackName ), "7" ); + attack_cone = spawnArgs.GetFloat ( va("attack_%s_cone", attackName ), "75" ); + attack_spread = DEG2RAD ( spawnArgs.GetFloat ( va("attack_%s_spread", attackName ), "0" ) ); + attack_count = spawnArgs.GetInt ( va("attack_%s_count", attackName ), "1" ); + attack_hitscan = spawnArgs.GetBool ( va("attack_%s_hitscan", attackName ), "0" ); + attack_predict = spawnArgs.GetFloat ( va("attack_%s_predict", attackName ), "0" ); + attack_pullback = spawnArgs.GetFloat ( va("attack_%s_pullback", attackName ), "0" ); + + // Get the muzzle origin and axis from the given launch joint + GetMuzzle( joint, muzzleOrigin, muzzleAxis ); + if ( attack_pullback ) + { + muzzleOrigin -= muzzleAxis[0]*attack_pullback; + } + + // set aiming direction + bool calcAim = true; + if ( GetEnemy() && GetEnemy() == target && GetEnemy() == gameLocal.GetLocalPlayer() && spawnArgs.GetBool( va("attack_%s_missFirstShot",attackName) ) ) { + //purposely miss + if ( gameLocal.random.RandomFloat() < 0.5f ) { + //actually, hit anyway... + } else { + idVec3 targetPos = enemy.lastVisibleEyePosition; + idVec3 eFacing; + idVec3 left, up; + + up.Set( 0, 0, 1 ); + left = viewAxis[0].Cross(up); + + if ( GetEnemy()->IsType( idActor::GetClassType() ) ) + { + eFacing = ((idActor*)GetEnemy())->viewAxis[0]; + } + else + { + eFacing = GetEnemy()->GetPhysics()->GetAxis()[0]; + } + if ( left*eFacing > 0 ) + { + targetPos += left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + else + { + targetPos -= left * ((gameLocal.random.RandomFloat()*8.0f) + 8.0f); + } + dir = targetPos-muzzleOrigin; + dir.Normalize(); + attack_accuracy = 0; + + calcAim = false; + //don't miss next time + spawnArgs.SetBool( va("attack_%s_missFirstShot",attackName), false ); + } + } + if ( calcAim ) { + if ( target && !spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) { + GetAimDir( muzzleOrigin, target, attackDict, this, dir, + spawnArgs.GetFloat ( va("attack_%s_aimoffset", attackName )), + attack_predict ); + } else { + dir = muzzleAxis[0]; + if ( spawnArgs.GetBool ( va("attack_%s_lockToJoint",attackName), "0" ) ) + { + if ( spawnArgs.GetBool( va("attack_%s_traceToNextJoint", attackName ), "0" ) ) + { + jointHandle_t endJoint = animator.GetFirstChild( joint ); + if ( endJoint != INVALID_JOINT && endJoint != joint ) + { + idVec3 endJointPos; + idMat3 blah; + GetJointWorldTransform( endJoint, gameLocal.GetTime(), endJointPos, blah ); + dir = endJointPos-muzzleOrigin; + //ARGHH: need to be able to set range!!! + //const_cast(attackDict)->SetFloat( "range", dir.Normalize() );//NOTE: yes, I intentionally normalize here as well as use the result for length... + dir.Normalize(); + } + } + } + } + } + + // random accuracy + ang = dir.ToAngles(); + ang.pitch += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + ang.yaw += gameLocal.random.CRandomFloat ( ) * attack_accuracy; + + // Lock attacks to a cone? + if ( attack_cone ) { + float diff; + // clamp the attack direction to be within monster's attack cone so he doesn't do + // things like throw the missile backwards if you're behind him + diff = idMath::AngleDelta( ang.yaw, move.current_yaw ); + if ( diff > attack_cone ) { + ang.yaw = move.current_yaw + attack_cone; + } else if ( diff < -attack_cone ) { + ang.yaw = move.current_yaw - attack_cone; + } + } + + ang.Normalize360 ( ); + axis = ang.ToMat3(); + + for( i = 0; i < attack_count; i++ ) { + float angle; + float spin; + + // spread the projectiles out + angle = idMath::Sin( attack_spread * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( attack_hitscan ) { + gameLocal.HitScan( *attackDict, muzzleOrigin, dir, muzzleOrigin, this, false, combat.aggressiveScale ); + } else { + // launch the projectile + if ( !projectile.GetEntity() ) { + CreateProjectile( attackDict, muzzleOrigin, dir ); + } + lastProjectile = projectile.GetEntity(); + lastProjectile->Launch( muzzleOrigin, dir, pushVelocity, 0.0f, combat.aggressiveScale ); + + // Let the script manage projectiles if need be + ExecScriptFunction ( funcs.launch_projectile, lastProjectile ); + + projectile = NULL; + } + } + + lastAttackTime = gameLocal.time; + + // If shooting at another ai entity then kick off an attack reaction + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) ) { + static_cast(enemy.ent.GetEntity())->ReactToShotAt ( this, muzzleOrigin, axis[0] ); + } + + return lastProjectile; +} + +/*s +================ +idAI::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( ( victim == this ) && inflictor->IsType( idProjectile::GetClassType() ) ) { + // monsters only get half damage from their own projectiles + damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage + } else if ( victim == enemy.ent ) { + aifl.hitEnemy = true; + } +} + +/* +===================== +idAI::DirectDamage + +Causes direct damage to an entity + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) { + const idDict *meleeDef; + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() ); + } + + if ( !ent->fl.takedamage ) { + const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" )); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + return; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + +// ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ); + ent->Damage( this, this, globalKickDir, meleeDefName, damageScale, NULL ); +} + +/* +===================== +idAI::TestMelee +===================== +*/ +bool idAI::TestMelee( void ) const { + trace_t trace; + idEntity* enemyEnt = enemy.ent; + + if ( !enemyEnt || !combat.meleeRange ) { + return false; + } + + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); + idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( DebugFilter(ai_debugMove) ) { //YELLOW = Test Melee Bounds + gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec ); + } + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + return false; + } + + idVec3 start = GetEyePosition(); + idVec3 end = enemyEnt->GetEyePosition(); + + gameLocal.TracePoint( this, trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); + if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { + return true; + } + + return false; +} + +/* +===================== +idAI::AttackMelee + +jointname allows the endpoint to be exactly specified in the model, +as for the commando tentacle. If not specified, it will be set to +the facing direction + combat.meleeRange. + +kickDir is specified in the monster's coordinate system, and gives the direction +that the view kick and knockback should go +===================== +*/ +bool idAI::AttackMelee ( const char *attackName, const idDict* meleeDict ) { + idEntity* enemyEnt = enemy.ent; + const char* p; + const idSoundShader* shader; + + if ( !enemyEnt ) { + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // check for the "saving throw" automatic melee miss on lethal blow + // stupid place for this. + bool forceMiss = false; + if ( enemyEnt->IsType( idPlayer::GetClassType() ) && g_skill.GetInteger() < 2 ) { + int damage, armor; + idPlayer *player = static_cast( enemyEnt ); + player->CalcDamagePoints( this, this, meleeDict, 1.0f, INVALID_JOINT, &damage, &armor ); + + if ( enemyEnt->health <= damage ) { + int t = gameLocal.time - player->lastSavingThrowTime; + if ( t > SAVING_THROW_TIME ) { + player->lastSavingThrowTime = gameLocal.time; + t = 0; + } + if ( t < 1000 ) { + forceMiss = true; + } + } + } + + // make sure the trace can actually hit the enemy + if ( forceMiss || !TestMelee( ) ) { + // missed + p = meleeDict->GetString( "snd_miss" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + return false; + } + + // + // do the damage + // + p = meleeDict->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDict->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; + + // This allows some AI to be soft against melee-- most marines are selfMeleeDamageScale of 3, which means they take 3X from melee attacks! + float damageScale = spawnArgs.GetFloat( "damageScale", "1" ) * enemyEnt->spawnArgs.GetFloat ( "selfMeleeDamageScale", "1" ); + + //if attacker is a melee superhero, damageScale is way increased + idAI* enemyAI = static_cast(enemyEnt); + if( aifl.meleeSuperhero) { + if( damageScale >=1 ) { + damageScale *= 6; + } else { + damageScale = 6; + } + } + + //if the defender is a melee superhero, damageScale is way decreased + if( enemyAI->aifl.meleeSuperhero) { + damageScale = 0.5f; + } + + int location = INVALID_JOINT; + if ( enemyEnt->IsType ( idAI::Type ) ) { + location = static_cast(enemyEnt)->chestOffsetJoint; + } + enemyEnt->Damage( this, this, globalKickDir, meleeDict->GetString ( "classname" ), damageScale, location ); + + if ( meleeDict->GetString( "fx_impact", NULL ) ) { + if ( enemyEnt == gameLocal.GetLocalPlayer() ) { + idPlayer *ePlayer = static_cast(enemyEnt); + if ( ePlayer ) { + idVec3 dir = ePlayer->firstPersonViewOrigin-GetEyePosition(); + dir.Normalize(); + idVec3 org = ePlayer->firstPersonViewOrigin + (dir * -((gameLocal.random.RandomFloat()*8.0f)+8.0f) ); + idAngles ang = ePlayer->firstPersonViewAxis.ToAngles() * -1; + idMat3 axis = ang.ToMat3(); + gameLocal.PlayEffect( *meleeDict, "fx_impact", org, viewAxis ); + } + } + } + + lastAttackTime = gameLocal.time; + + return true; +} + +/* +================ +idAI::PushWithAF +================ +*/ +void idAI::PushWithAF( void ) { + int i, j; + afTouch_t touchList[ MAX_GENTITIES ]; + idEntity *pushed_ents[ MAX_GENTITIES ]; + idEntity *ent; + idVec3 vel; + int num_pushed; + + num_pushed = 0; + af.ChangePose( this, gameLocal.time ); + int num = af.EntitiesTouchingAF( touchList ); + for( i = 0; i < num; i++ ) { + if ( touchList[ i ].touchedEnt->IsType( idProjectile::GetClassType() ) ) { + // skip projectiles + continue; + } + + // make sure we havent pushed this entity already. this avoids causing double damage + for( j = 0; j < num_pushed; j++ ) { + if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) { + break; + } + } + if ( j >= num_pushed ) { + ent = touchList[ i ].touchedEnt; + pushed_ents[num_pushed++] = ent; + vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin(); + vel.Normalize(); + ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() ); + } + } +} + +/*********************************************************************** + + Misc + +***********************************************************************/ + +/* +================ +idAI::GetMuzzle +================ +*/ +void idAI::GetMuzzle( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ) { + if ( joint == INVALID_JOINT ) { + muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14; + muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f; + } else { + GetJointWorldTransform( joint, gameLocal.time, muzzle, axis ); + //MCG + //pull the muzzle back inside the bounds if possible + //FIXME: this is nasty, we should just be able to check for starting in solid and register that as a hit! But... + /* + float scale = 0.0f; + if ( physicsObj.GetBounds().RayIntersection( muzzle, -axis[0], scale ) ) + { + if ( scale != 0.0f ) + {//not already inside + muzzle += scale * -axis[0]; + } + } + else + {//just pull it back a little anyway? + idVec3 xyOfs = muzzle-physicsObj.GetOrigin(); + xyOfs.z = 0; + muzzle += xyOfs.Length() * -axis[0]; + } + */ + } +} + +/* +================ +idAI::Hide +================ +*/ +void idAI::Hide( void ) { + idActor::Hide(); + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); + StopSound( SND_CHANNEL_AMBIENT, false ); + + enemy.fl.inFov = false; + enemy.fl.visible = false; + + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::Show +================ +*/ +void idAI::Show( void ) { + idActor::Show(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } + + physicsObj.GetClipModel()->Link(); + + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); + StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL ); +} + +/* +================ +idAI::CanPlayChatterSounds + +Used for playing chatter sounds on monsters. +================ +*/ +bool idAI::CanPlayChatterSounds( void ) const { + if ( aifl.dead ) { + return false; + } + + if ( IsHidden() ) { + return false; + } + + if ( enemy.ent ) { + return true; + } + + if ( spawnArgs.GetBool( "no_idle_chatter" ) ) { + return false; + } + + return true; +} + +/* +===================== +idAI::UpdateChatter +===================== +*/ +void idAI::UpdateChatter ( void ) { + int chatterRate; + const char* chatter; + + // No chatter? + if ( !chatterRateIdle && !chatterRateCombat ) { + return; + } + + // check if it's time to play a chat sound + if ( IsHidden() || !aifl.awake || aifl.dead || ( chatterTime > gameLocal.time ) ) { + return; + } + + // Skip first chatter + if ( enemy.ent ) { + chatter = "lipsync_chatter_combat"; + chatterRate = chatterRateCombat; + } else { + chatter = "lipsync_chatter_idle"; + chatterRate = chatterRateIdle; + } + + // Can chatter ? + if ( !chatterRate ) { + return; + } + + // Start chattering, but not if he's already speaking. And he might already be speaking because he was scripted to do so. + if ( chatterTime > 0 && !IsSpeaking() ) { + Speak ( chatter, true ); + } + + // set the next chat time + chatterTime = gameLocal.time + chatterRate + (gameLocal.random.RandomFloat() * chatterRate * 0.5f) - (chatterRate * 0.25f); +} + +/* +===================== +idAI::HeardSound +===================== +*/ +idEntity *idAI::HeardSound( int ignore_team ){ + // check if we heard any sounds in the last frame + idActor *actor = gameLocal.GetAlertActor(); + if ( actor && ( !ignore_team || ( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) && gameLocal.InPlayerPVS( this ) ) { + idVec3 pos = actor->GetPhysics()->GetOrigin(); + idVec3 org = physicsObj.GetOrigin(); + float dist = ( pos - org ).LengthSqr(); + + if ( dist < Square( combat.earRange ) ) { + //really close? + if ( dist < Square( combat.earRange/4.0f ) ) { + return actor; + //possible LOS + } else if ( dist < Square( combat.visRange * 2.0f ) && CanSee( actor, false ) ) { + return actor; + } else if ( combat.fl.aware ) { + //FIXME: or, maybe find cover/hide/ambush spot and wait to ambush them? + //don't have an enemy and not tethered to a position + } else if ( !GetEnemy() && !tether ) { + //go into search mode + WanderAround(); + move.fl.noRun = false; + move.fl.idealRunning = true; + //undid this: was causing them to not be able to do fine nav. + //move.fl.noWalk = true; + } + } + } + + return NULL; +} + +/* +============ +idAI::SetLeader +============ +*/ +void idAI::SetLeader ( idEntity *newLeader ) { + idEntity* oldLeader = leader; + + if( !newLeader ){ + leader = NULL; + } else if ( !newLeader->IsType( idActor::GetClassType() ) ) { + gameLocal.Error( "'%s' is not an idActor (player or ai controlled character)", newLeader->name.c_str() ); + } else { + leader = static_cast( newLeader ); + } + + if ( oldLeader != leader ) { + OnLeaderChange ( oldLeader ); + } +} + + + +/*********************************************************************** + + Head & torso aiming + +***********************************************************************/ + +/* +================ +idAI::UpdateAnimationControllers +================ +*/ +bool idAI::UpdateAnimationControllers( void ) { + idVec3 left; + idVec3 dir; + idVec3 orientationJointPos; + idVec3 localDir; + idAngles newLookAng; + idMat3 mat; + idMat3 axis; + idMat3 orientationJointAxis; + idVec3 pos; + int i; + idAngles jointAng; + float orientationJointYaw; + float currentHeadFocusRate = ( combat.fl.aware ? headFocusRate : headFocusRate * 0.5f ); + + MEM_SCOPED_TAG(tag,MA_ANIM); + + if ( aifl.dead ) { + return idActor::UpdateAnimationControllers(); + } + + if ( orientationJoint == INVALID_JOINT ) { + orientationJointAxis = viewAxis; + orientationJointPos = physicsObj.GetOrigin(); + orientationJointYaw = move.current_yaw; + } else { + GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis ); + orientationJointYaw = orientationJointAxis[ 2 ].ToYaw(); + orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3(); + } + + // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions. + // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which + // are already up to date because of getting the joints in this function) and then sets their positions, which + // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled, + // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no + // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number + // in order to see how many times an entity transforms the joints per frame. + idActor::UpdateAnimationControllers(); + + // Update the focus position + UpdateFocus( orientationJointAxis ); + + //MCG NOTE: don't know why Dube added this extra check for the torsoAnim (5/09/05), but it was causing popping, so I took it out... :/ + //bool canLook = ( !torsoAnim.AnimDone(0) || !torsoAnim.GetAnimator()->CurrentAnim(ANIMCHANNEL_TORSO)->IsDone(gameLocal.GetTime()) || torsoAnim.Disabled() ) && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook; + //bool canLook = (!torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook); + + bool canLook = (!animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look && !aifl.disableLook); + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + currentFocusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 64.0f; + } + + // Determine the new look yaw + dir = currentFocusPos - orientationJointPos; + newLookAng.yaw = dir.ToYaw( ); + + // Determine the new look pitch + dir = currentFocusPos - GetEyePosition(); + dir.NormalizeFast(); + orientationJointAxis.ProjectVector( dir, localDir ); + newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ); + newLookAng.roll = 0.0f; + + if ( !canLook ) { + //actually, do the looking, but bring it back forward... + newLookAng.yaw = orientationJointAxis[0].ToYaw(); + newLookAng.pitch = 0; + } + // Add the angle change using the head focus rate and convert the look angles to + // local angles so they can be properly clamped. + float f = lookAng.yaw; + if ( lookMin.yaw <= -360.0f && lookMax.yaw >= 360.0f ) + { + lookAng.yaw = f - orientationJointYaw; + float diff; + diff = ( idMath::AngleNormalize360(newLookAng.yaw) - idMath::AngleNormalize360(f) ); + diff = idMath::AngleNormalize180( diff ); + lookAng.yaw += diff * currentHeadFocusRate; + //lookAng.yaw += ( newLookAng.yaw - f ) * currentHeadFocusRate; + } + else + { + lookAng.yaw = idMath::AngleNormalize180 ( f - orientationJointYaw ); + lookAng.yaw += (idMath::AngleNormalize180 ( newLookAng.yaw - f ) * currentHeadFocusRate); + } + lookAng.pitch += ( idMath::AngleNormalize180( newLookAng.pitch - lookAng.pitch ) * currentHeadFocusRate ); + + // Clamp the look angles + lookAng.Clamp( lookMin, lookMax ); + + // Calcuate the eye angles + f = eyeAng.yaw; + eyeAng.yaw = idMath::AngleNormalize180( f - orientationJointYaw ); + eyeAng.yaw += (idMath::AngleNormalize180( newLookAng.yaw - f ) * eyeFocusRate); + eyeAng.pitch += (idMath::AngleNormalize180( newLookAng.pitch - eyeAng.pitch ) * eyeFocusRate); + + // Clamp eye angles relative to the look angles + jointAng = eyeAng - lookAng; + jointAng.Normalize180( ); + jointAng.Clamp( eyeMin, eyeMax ); + eyeAng = lookAng + jointAng; + eyeAng.Normalize180( ); + + if ( canLook ) { + // Apply the look angles to the look joints + if ( animator.GetAnimFlags( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimNum()).ai_look_head_only ) { + if ( neckJoint != INVALID_JOINT || headJoint != INVALID_JOINT ) { + float jScale = 1.0f; + if ( neckJoint != INVALID_JOINT && headJoint != INVALID_JOINT ) { + jScale = 0.5f; + } + jointAng.pitch = lookAng.pitch * jScale; + jointAng.yaw = lookAng.yaw * jScale; + if ( neckJoint != INVALID_JOINT ) { + animator.SetJointAxis( neckJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + if ( headJoint != INVALID_JOINT ) { + animator.SetJointAxis( headJoint, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + //what if we have a previous joint mod on the rest of the lookJoints + //from an anim that *wasn't* ai_look_head_only...? + //just clear them? Or move them towards 0? + //FIXME: move them towards zero... + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + } + } else { + //blend back + //yes, this is framerate dependant and inefficient and wrong, but... eliminates pops + jointAng.roll = 0.0f; + jointMod_t *curJointMod; + idAngles curJointAngleMod; + for( i = 0; i < lookJoints.Num(); i++ ) { + if ( lookJoints[i] != neckJoint + && lookJoints[i] != headJoint ) { + //blend back + curJointMod = animator.FindExistingJointMod( lookJoints[ i ], NULL ); + if ( curJointMod ) + { + curJointAngleMod = curJointMod->mat.ToAngles(); + curJointAngleMod *= 0.75f; + if ( fabs(curJointAngleMod.pitch) < 1.0f + && fabs(curJointAngleMod.yaw) < 1.0f + && fabs(curJointAngleMod.roll) < 1.0f ) + { + //snap back now! + animator.ClearJoint ( lookJoints[i] ); + } + else + { + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, curJointAngleMod.ToMat3() ); + } + } + } + } + } + } else { + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } else { + //if ( animator.GetAnimFlags(animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimNum()).ai_no_look || aifl.disableLook ) { + if ( newLookAng.Compare( lookAng ) ) { + //snap back now! + for( i = 0; i < lookJoints.Num(); i++ ) { + animator.ClearJoint ( lookJoints[i] ); + } + } else { + //PCJ back to neutral + jointAng.roll = 0.0f; + for( i = 0; i < lookJoints.Num(); i++ ) { + jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch; + jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw; + animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() ); + } + } + } + + // Convert the look angles back to world angles. This is done to prevent dramatic orietation changes + // from changing the look direction abruptly (unless of course the minimums are not met) + lookAng.yaw = idMath::AngleNormalize180( lookAng.yaw + orientationJointYaw ); + eyeAng.yaw = idMath::AngleNormalize180( eyeAng.yaw + orientationJointYaw ); + + if ( move.moveType == MOVETYPE_FLY || move.fl.flyTurning ) { + // lean into turns + AdjustFlyingAngles(); + } + + // Orient the eyes towards their target + if ( leftEyeJoint != INVALID_JOINT && rightEyeJoint != INVALID_JOINT ) { + if ( head ) { + idAnimator *headAnimator = head->GetAnimator(); + + if ( focusType != AIFOCUS_NONE && allowEyeFocus && canLook ) { + //tweak these since it's looking at the wrong spot and not calculating from each eye and I don't have time to bother rewriting all of this properly + eyeAng.yaw -= 0.5f; + eyeAng.pitch += 3.25f; + idMat3 eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + eyeAng.yaw += 3.0f; + eyeAxis = ( eyeAng ).ToMat3() * head->GetPhysics()->GetAxis().Transpose ( ); + + headAnimator->SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + + if ( ai_debugEyeFocus.GetBool() ) { + idVec3 eyeOrigin; + idMat3 axis; + + head->GetJointWorldTransform ( rightEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + + head->GetJointWorldTransform ( leftEyeJoint, gameLocal.time, eyeOrigin, axis ); + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, ( eyeOrigin + (32.0f*axis[0])), 1, 0 ); + } + } else { + headAnimator->ClearJoint( leftEyeJoint ); + headAnimator->ClearJoint( rightEyeJoint ); + } + } else { + if ( allowEyeFocus && focusType != AIFOCUS_NONE && !torsoAnim.GetAnimFlags().ai_no_look && !aifl.disableLook ) { + idMat3 eyeAxis = ( eyeAng ).ToMat3() * GetPhysics()->GetAxis().Transpose ( ); + animator.SetJointAxis ( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + animator.SetJointAxis ( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyeAxis ); + } else { + animator.ClearJoint( leftEyeJoint ); + animator.ClearJoint( rightEyeJoint ); + } + } + } + + return true; +} + +void idAI::OnTouch( idEntity *other, trace_t *trace ) { + // if we dont have an enemy or had one for at least a second that is a potential enemy the set the enemy + if ( other->IsType( idActor::GetClassType() ) + && !other->fl.notarget + && ( ReactionTo( other )&ATTACK_ON_SIGHT) + && (!enemy.ent || gameLocal.time - enemy.changeTime > 1000 ) ) { + SetEnemy( other ); + } + + if ( !enemy.ent && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { + Activate( other ); + } + pusher = other; + aifl.pushed = true; + + // If pushed by the player update tactical + if ( pusher && pusher->IsType ( idPlayer::GetClassType() ) && (combat.tacticalMaskAvailable & AITACTICAL_MOVE_PLAYERPUSH_BIT) ) { + ForceTacticalUpdate ( ); + } +} + +idProjectile* idAI::AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ) { + idVec3 start; + trace_t tr; + idBounds projBounds; + const idClipModel* projClip; + idMat3 axis; + float distance; + idProjectile* result; + + if ( !projectileDict ) { + gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); + return NULL; + } + + axis = ang.ToMat3(); + if ( !projectile.GetEntity() ) { + CreateProjectile( projectileDict, org, axis[ 0 ] ); + } + + // make sure the projectile starts inside the monster bounding box + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); + projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); + + // check if the owner bounds is bigger than the projectile bounds + if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && + ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && + ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { + if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { + start = org + distance * viewAxis[ 0 ]; + } else { + start = ownerBounds.GetCenter(); + } + } else { + // projectile bounds bigger than the owner bounds, so just start it from the center + start = ownerBounds.GetCenter(); + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); +// RAVEN END + + // launch the projectile + projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin ); + result = projectile; + projectile = NULL; + + lastAttackTime = gameLocal.time; + + return result; +} + +void idAI::RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { + jointHandle_t joint; + idVec3 org; + idMat3 axis; + + if ( !jointname || !jointname[ 0 ] ) { + org = physicsObj.GetOrigin(); + } else { + joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + GetJointWorldTransform( joint, gameLocal.time, org, axis ); + } + + gameLocal.RadiusDamage( org, this, this, this, this, damageDefName ); +} + +/* +===================== +idAI::CanBecomeSolid + +returns true if the AI entity could become solid at its current position +===================== +*/ +bool idAI::CanBecomeSolid ( void ) { + int i; + int num; + idEntity * hit; + idClipModel* cm; + idClipModel* clipModels[ MAX_GENTITIES ]; + + // Determine what we are currently touching +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.ClipModelsTouchingBounds( this, physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + + if ( physicsObj.ClipContents( cm ) ) { + return false; + } + } + + return true; +} + +/* +===================== +idAI::BecomeSolid +===================== +*/ +void idAI::BecomeSolid( void ) { + physicsObj.EnableClip(); + if ( spawnArgs.GetBool( "big_monster" ) ) { + physicsObj.SetContents( 0 ); + } else if ( use_combat_bbox ) { + physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID ); + } else { + physicsObj.SetContents( CONTENTS_BODY ); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + physicsObj.GetClipModel()->Link(); +// RAVEN END + fl.takedamage = !spawnArgs.GetBool( "noDamage" ); +} + +/* +===================== +idAI::BecomeNonSolid +===================== +*/ +void idAI::BecomeNonSolid( void ) { + fl.takedamage = false; + physicsObj.SetContents( 0 ); + physicsObj.GetClipModel()->Unlink(); +} + +const char *idAI::ChooseAnim( int channel, const char *animname ) { + int anim; + + anim = GetAnim( channel, animname ); + if ( anim ) { + if ( channel == ANIMCHANNEL_HEAD ) { + if ( head.GetEntity() ) { + return head.GetEntity()->GetAnimator()->AnimFullName( anim ); + } + } else { + return animator.AnimFullName( anim ); + } + } + + return ""; +} + +/* +============ +idAI::ExecScriptFunction +============ +*/ +void idAI::ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm ) { + if( parm ) { + func.InsertEntity( parm, 0 ); + } else { + func.InsertEntity( this, 0 ); + } + + func.CallFunc( &spawnArgs ); + + if( parm ) { + func.RemoveIndex( 0 ); + } +} + +/* +============ +idAI::Prethink +============ +*/ +void idAI::Prethink ( void ) { + // Update our helper if we are moving + if ( move.fl.moving ) { + UpdateHelper ( ); + } + + if ( leader ) { + idEntity* groundEnt = leader->GetGroundEntity ( ); + if ( !(tether && !aifl.tetherMover) && groundEnt ) { + if ( groundEnt->IsType ( idMover::GetClassType ( ) ) ) { + idEntity* ent; + idEntity* next; + for( ent = groundEnt->GetNextTeamEntity(); ent != NULL; ent = next ) { + next = ent->GetNextTeamEntity(); + if ( ent->GetBindMaster() == groundEnt && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + SetTether ( static_cast(ent) ); + aifl.tetherMover = true; + break; + } + } + } else { + SetTether ( NULL ); + } + } + } else if ( tether && aifl.tetherMover ) { + SetTether ( NULL ); + } +} + +/* +============ +idAI::Postthink +============ +*/ +void idAI::Postthink( void ){ + if ( !aifl.simpleThink ) { + pain.takenThisFrame = 0; + } + + // Draw debug tactical information + DrawTactical ( ); + + if( vehicleController.IsDriving() ){ // Generate some sort of command? + usercmd_t usercmd; + + // Note! usercmd angles stuff is in deltas, not in absolute values. + + memset( &usercmd, 0, sizeof( usercmd ) ); + + idVec3 toEnemy; + + if( enemy.ent ){ + toEnemy = enemy.ent->GetPhysics()->GetOrigin(); + toEnemy -= GetPhysics()->GetOrigin(); + toEnemy.Normalize(); + + idAngles enemyAng; + + enemyAng = toEnemy.ToAngles(); + + usercmd.angles[PITCH] = ANGLE2SHORT( enemyAng.pitch ); + usercmd.angles[YAW] = ANGLE2SHORT( enemyAng.yaw ); + + usercmd.buttons = BUTTON_ATTACK; + + vehicleController.SetInput ( usercmd, enemyAng ); + } + } + + // Keep our threat value up to date + UpdateThreat ( ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::OnDeath( void ){ + if( vehicleController.IsDriving() ){ + usercmd_t usercmd; + + memset( &usercmd, 0, sizeof( usercmd ) ); + usercmd.buttons = BUTTON_ATTACK; + usercmd.upmove = 300.0f; // This will cause the character to eject. + + vehicleController.SetInput( usercmd, idAngles( 0, 0, 0 ) ); + + // Fixme! Is this safe to do immediately? + vehicleController.Eject(); + } + + aiManager.RemoveTeammate ( this ); + + ExecScriptFunction( funcs.death ); + +/* DONT DROP ANYTHING FOR NOW + float rVal = gameLocal.random.RandomInt( 100 ); + + if( spawnArgs.GetFloat( "no_drops" ) >= 1.0 ){ + spawnArgs.Set( "def_dropsItem1", "" ); + }else{ + // Fixme! Better guys should drop better stuffs! Make drops related to guy type? Do something cooler here? + if( rVal < 25 ){ // Half of guys drop nothing? + spawnArgs.Set( "def_dropsItem1", "" ); + }else if( rVal < 50 ){ + spawnArgs.Set( "def_dropsItem1", "item_health_small" ); + } + } +*/ +} + +/* +============ +idAI::OnWakeUp +============ +*/ +void idAI::OnWakeUp ( void ) { +} + +/* +============ +idAI::OnUpdatePlayback +============ +*/ +void idAI::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + return; +} + +/* +============ +idAI::OnLeaderChange +============ +*/ +void idAI::OnLeaderChange ( idEntity* oldLeader ) { + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnEnemyChange +============ +*/ +void idAI::OnEnemyChange ( idEntity* oldEnemy ) { + // Make sure we update our tactical state immediately + ForceTacticalUpdate ( ); + + // see if we should announce the enemy + if ( enemy.ent ) { + combat.fl.aware = true; + combat.investigateTime = 0; + + enemy.changeTime = gameLocal.time; + + enemy.lastKnownPosition = enemy.ent->GetPhysics()->GetOrigin ( ); + + UpdateEnemyVisibility ( ); + UpdateEnemyPosition ( true ); + UpdateEnemy ( ); + } else { + enemy.range = 0; + enemy.range2d = 0; + enemy.changeTime = 0; + enemy.smoothedLinearVelocity.Zero ( ); + enemy.smoothedPushedVelocity.Zero ( ); + } + + enemy.fl.visible = false; +} + +/* +============ +idAI::OnTacticalChange +============ +*/ +void idAI::OnTacticalChange ( aiTactical_t oldTactical ) { + // if acutally moving to a new tactial location announce it + if ( move.fl.moving ) { + AnnounceTactical( combat.tacticalCurrent ); + } +} + +/* +============ +idAI::OnFriendlyFire +============ +*/ +void idAI::OnFriendlyFire ( idActor* attacker ) { + AnnounceFriendlyFire( static_cast(attacker) ); +} + +/* +============ +idAI::OnStartMoving +============ +*/ +void idAI::OnStartMoving ( void ) { + aifl.simpleThink = false; + combat.fl.crouchViewClear = false; +} + +/* +============ +idAI::OnStopMoving +============ +*/ +void idAI::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { +} + +/* +============ +idAI::OnStartAction +============ +*/ +void idAI::OnStartAction ( void ) { +} + +/* +============ +idAI::OnStopAction +============ +*/ +void idAI::OnStopAction ( void ) { +} + +/* +============ +idAI::OnEnemyVisiblityChange +============ +*/ +void idAI::OnEnemyVisiblityChange ( bool oldVisible ) { +} + +/* +============ +idAI::OnSetKey +============ +*/ +void idAI::OnSetKey ( const char* key, const char* value ) { + if ( !idStr::Icmp ( key, "noCombatChatter" ) ) { + combat.fl.noChatter = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "allowPlayerPush" ) ) { + combat.tacticalMaskAvailable &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + if ( spawnArgs.GetBool ( key ) ) { + combat.tacticalMaskAvailable |= AITACTICAL_MOVE_PLAYERPUSH_BIT; + } + } else if ( !idStr::Icmp ( key, "noLook" ) ) { + aifl.disableLook = spawnArgs.GetBool ( key ); + } else if ( !idStr::Icmp ( key, "killer_guard" ) ) { + aifl.killerGuard = spawnArgs.GetBool ( key ); + } +} + +/* +============ +idAI::OnCoverInvalidated +============ +*/ +void idAI::OnCoverInvalidated ( void ) { + // Force a tactical update now + ForceTacticalUpdate ( ); +} + +/* +============ +idAI::OnCoverNotFacingEnemy +============ +*/ +void idAI::OnCoverNotFacingEnemy ( void ) { + // Clear attack timers so we can shoot right now + actionTimerRangedAttack.Clear ( actionTime ); + actionRangedAttack.timer.Clear( actionTime ); +} + +/* +============ +idAI::SkipCurrentDestination + +Is the AI's current destination ok enough to stay at? +============ +*/ +bool idAI::SkipCurrentDestination ( void ) const { + // can only skip current destination when we are stopped + if ( move.fl.moving ) { + return false; + } +/* + // If we are currently behind cover and that cover is no longer valid we should skip it + if ( IsBehindCover ( ) && !IsCoverValid ( ) ) { + return true; + } +*/ + return false; +} + +/* +============ +idAI::SkipImpulse +============ +*/ +bool idAI::SkipImpulse( idEntity *ent, int id ){ + bool skip = idActor::SkipImpulse( ent, id ); + + if( af.IsActive ( ) ) { + return false; + } + if( !fl.takedamage ){ + return true; + } + if( move.moveCommand == MOVE_RV_PLAYBACK ){ + return true; + } + + return skip; +} + +/* +============ +idAI::CanHitEnemy +============ +*/ +bool idAI::CanHitEnemy ( void ) { + trace_t tr; + idEntity *hit; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + idVec3 eye = GetEyePosition(); + idVec3 dir; + + // expand the ray out as far as possible so we can detect anything behind the enemy + dir = toPos - eye; + dir.Normalize(); + toPos = eye + dir * MAX_WORLD_SIZE; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return lastHitCheckResult; + } + + gameLocal.TracePoint( this, tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + hit = gameLocal.GetTraceEntity( tr ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( hit == enemyEnt ) ) { + lastHitCheckResult = true; + } else if ( ( tr.fraction < 1.0f ) && ( hit->IsType( idAI::Type ) ) && + ( static_cast( hit )->team != team ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI::CanHitEnemyFromJoint +============ +*/ + +bool idAI::CanHitEnemyFromJoint( const char *jointname ){ + trace_t tr; + idVec3 muzzle; + idMat3 axis; + + idEntity *enemyEnt = enemy.ent; + if ( !IsEnemyVisible ( ) || !enemyEnt ) { + return false; + } + + // don't check twice per frame + if ( gameLocal.time == lastHitCheckTime ) { + return lastHitCheckResult; + } + + if ( g_perfTest_aiNoVisTrace.GetBool() ) { + lastHitCheckResult = true; + return true; + } + + lastHitCheckTime = gameLocal.time; + + idVec3 toPos = enemyEnt->GetEyePosition(); + jointHandle_t joint = animator.GetJointHandle( jointname ); + if ( joint == INVALID_JOINT ) { + gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); + } + animator.GetJointTransform( joint, gameLocal.time, muzzle, axis ); + muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( this, tr, muzzle, toPos, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { + lastHitCheckResult = true; + } else { + lastHitCheckResult = false; + } + + return lastHitCheckResult; +} + +/* +============ +idAI:: +============ + +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ); + +int idAI::TestTrajectory( const idVec3 &firePos, const idVec3 &target, const char *projectileName ){ + idVec3 projVelocity; + idVec3 projGravity; + float testTime; + float zVel, height, pitch, s, c; + idVec3 dir; + float newVel; + float delta_x; + float delta_z; + + projectileDef = gameLocal.FindEntityDefDict ( projectileName ); + projVelocity = idProjectile::GetVelocity( projectileDef ); + projGravity = idProjectile::GetGravity( projectileDef ).z * GetPhysics()->GetGravity(); + pitch = DEG2RAD( gameLocal.random.RandomFloat() * 50 + 20 ); // Random pitch range between 20 and 70. Should this be customizeable? + + idMath::SinCos( pitch, s, c ); + + delta_x = idMath::Sqrt( ( target.x - firePos.x ) * ( target.x - firePos.x ) + ( target.y - firePos.y ) * ( target.y - firePos.y ) ); + delta_z = target.z - firePos.z; + newVel = ( delta_x / idMath::Cos( pitch ) ) * idMath::Sqrt( projGravity.z / ( 2.0f * ( delta_x * idMath::Tan( pitch ) - delta_z ) ) ); + testTime = delta_x / ( newVel * c ); + zVel = newVel * s; + + float a = idMath::ASin ( delta_x * GetPhysics()->GetGravity().Length() / (projVelocity.x * projVelocity.x) ); + a = a / 2; + + float r = (projVelocity.x * projVelocity.x) * idMath::Sin ( 2 * a ) / GetPhysics()->GetGravity().Length(); + if ( r < delta_x - (delta_x * 0.1) ) { + mVar.valid_lobbed_shot = 0; + return 0; + } else { + mVar.lobDir = target - firePos; + mVar.lobDir.z = 0; + mVar.lobDir.z = idMath::Tan ( a ) * mVar.lobDir.LengthFast(); + mVar.lobDir.Normalize ( ); + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = 1.0f; // newVel / projVelocity.Length(); + return 1; + } + + projGravity[2] *= -1.0f; + + dir = target - firePos; + dir.z = 0; + dir.Normalize(); + delta_x = idMath::Sqrt( 1 - ( c * c ) ); + dir.x *= delta_x; + dir.y *= delta_x; + dir.z = c; + + height = HeightForTrajectory( firePos, zVel, projGravity[2] ) - firePos.z; + if ( height > MAX_WORLD_SIZE ) { + // goes higher than we want to allow + mVar.valid_lobbed_shot = 0; + return 0; + } + + if ( idAI::TestTrajectory ( firePos, target, zVel, projGravity[2], testTime, height * 2, NULL, + MASK_SHOT_RENDERMODEL, this, enemy.GetEntity(), ai_debugTrajectory.GetBool() ? 4000 : 0 ) ) { + + if ( ai_debugTrajectory.GetBool() ) { + float t = testTime / 100.0f; + idVec3 velocity = dir * newVel; + idVec3 lastPos, pos; + lastPos = firePos; + pos = firePos; + for ( int j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, ai_debugTrajectory.GetBool() ? 4000 : 0 ); + lastPos = pos; + } + } + + mVar.lobDir = dir; + mVar.valid_lobbed_shot = gameLocal.time; + mVar.lob_vel_scale = newVel / projVelocity.Length(); + }else{ + mVar.valid_lobbed_shot = 0; + } + + return ( (int)mVar.valid_lobbed_shot != 0 ); + +} +*/ + +/* +============ +idAI:: +============ +*/ + +float idAI::GetTurnDelta( void ){ + float amount; + + if ( move.turnRate ) { + amount = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + return amount; + } else { + return 0.0f; + } +} + +/* +============ +idAI::GetIdleAnimName +============ +*/ +const char* idAI::GetIdleAnimName ( void ) { + const char* animName = NULL; + + // Start idle animation + if ( enemy.ent ) { + animName = "idle_alert"; + } + + if ( animName && HasAnim ( ANIMCHANNEL_ALL, animName ) ) { + return animName; + } + + return "idle"; +} + +/* +=============================================================================== + + idAI - Enemy Finding + +=============================================================================== +*/ + +/* +============ +idAI::FindEnemy +============ +*/ +idEntity *idAI::FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr ){ + idActor* actor; + idActor* bestEnemy; + idActor* bestEnemyBackup; + float bestThreat; + float bestThreatBackup; + float distSqr; + float enemyRangeSqr; + float awareRangeSqr; + idVec3 origin; + idVec3 delta; + pvsHandle_t pvs; + + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + bestThreat = 0.0f; + bestThreatBackup = 0.0f; + bestEnemy = NULL; + bestEnemyBackup = NULL; + awareRangeSqr = Square ( combat.awareRange ); + enemyRangeSqr = enemy.ent ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + origin = GetEyePosition ( ); + + // Iterate through the enemy team + for( actor = aiManager.GetEnemyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) { + // Skip hidden enemies and enemies that cant be targeted + if( actor->fl.notarget || actor->fl.isDormant || ( actor->IsHidden ( ) && !actor->IsInVehicle() ) ) { + continue; + } + + // Calculate the distance between ourselves and our potential enemy + delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin(); + distSqr = delta.LengthSqr(); + + // Calculate the adjusted threat for this actor + float threat = CalculateEnemyThreat ( actor ); + + // Save the highest threat enemy as a backup in case we cannot find one we can see + if ( threat > bestThreatBackup ) { + bestThreatBackup = threat; + bestEnemyBackup = actor; + } + + // If we have already found a more threatening enemy then attack that + if ( threat < bestThreat ) { + continue; + } + + // If this enemy isnt in the same pvps then use them as a backup + if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { + continue; + } + + // this is pretty specific to Quake 4, but we don't want the player to be around an enemy too long who can't see him. We need to randomly spike the + // awareRange on creatures to simulate them looking behind them, or noticing someone standing around for too long. + // Modders take note, this will prevent most "sneaking up on bad guys" action because they will likely spike their aware ranges out + // during the sneaking. + if( gameLocal.random.RandomFloat() < 0.005f ) { + awareRangeSqr *= 15; + } + + // fov doesn't matter if they're within awareRange, we "sense" them if we're alert... (or should LOS not even matter at this point?) + if ( distSqr < awareRangeSqr || CanSeeFrom ( origin, actor, (inFov && !(combat.fl.aware&&distSqrIsType ( idAI::Type ) ) { + idAI* teammateAI = static_cast(teammate); + enemy.smoothedLinearVelocity = teammateAI->enemy.smoothedLinearVelocity; + enemy.smoothedPushedVelocity = teammateAI->enemy.smoothedPushedVelocity; + enemy.lastKnownPosition = teammateAI->enemy.lastKnownPosition; + enemy.lastVisibleEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleFromEyePosition = teammateAI->enemy.lastVisibleEyePosition; + enemy.lastVisibleChestPosition = teammateAI->enemy.lastVisibleChestPosition; + enemy.lastVisibleTime = 0; + } + + return true; +} + +/* +============ +idAI::CheckForCloserEnemy +============ +*/ +bool idAI::CheckForCloserEnemy ( void ) { + idEntity* newEnemy = NULL; + float maxDistSqr; + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + // See if we happen to have heard someone this frame that we can use + newEnemy = HeardSound( true ); + if ( newEnemy && newEnemy != enemy.ent && newEnemy->IsType( idActor::GetClassType() ) ) { + //heard someone else! + float newDist = DistanceTo ( newEnemy->GetPhysics()->GetOrigin() ); + + // Are they closer than the enemy we are fighting? + if ( newDist < enemy.range ) { + //new enemy is closer than current one, take them! + SetEnemy( newEnemy ); + return true; + } + } + + if ( GetEnemy() && enemy.range ) { + maxDistSqr = Min( Square ( enemy.range ), Square ( combat.awareRange ) ); + } else { + maxDistSqr = Square ( combat.awareRange ); + } + + newEnemy = FindEnemy( false, 0, maxDistSqr ); + + if ( !newEnemy ) { + return false; + } + + SetEnemy( newEnemy ); + return true; +} + +/* +============ +idAI::CheckForReplaceEnemy + +TODO: Call CalculateThreat ( ent ) and compare to current entity +============ +*/ +bool idAI::CheckForReplaceEnemy ( idEntity* replacement ) { + bool replace; + + // If our replacement is a driver a vehicle and they are hidden we will + // want to shoot back at their vehicle not them. + idActor* actor; + actor = dynamic_cast(replacement); + if ( actor && actor->IsInVehicle ( ) && actor->IsHidden ( ) ) { + replacement = actor->GetVehicleController ( ).GetVehicle ( ); + } + + // Invalid replacement? + if ( !replacement) { + return false; + } + + // Not looking for a new enemy. + if ( combat.fl.ignoreEnemies ) { + return false; + } + + if ( replacement == enemy.ent ) { + return false; + } + + // Dont want to set our enemy to a friendly target + if ( replacement->IsType( idActor::GetClassType() ) && (static_cast(replacement))->team == team ) { + return false; + } + + // Not having an enemy will set it immediately + if ( !enemy.ent ) { + SetEnemy ( replacement ); + return true; + } + + // Dont change enemies too often when being hit + if ( gameLocal.time - enemy.changeTime < 1000 ) { + return false; + } + + replace = false; + + // If new enemy is more threatening then replace it reguardless if we can see it + if ( CalculateEnemyThreat ( replacement ) > CalculateEnemyThreat ( enemy.ent ) ) { + replace = true; + // Replace our enemy if we havent seen ours in a bit + } else if ( !IsEnemyRecentlyVisible ( 0.25f ) ) { + replace = true; + } + + // Replace enemy? + if ( replace ) { + SetEnemy ( replacement ); + } + + return replace; +} + +/* +============ +idAI::UpdateThreat +============ +*/ +void idAI::UpdateThreat ( void ) { + // Start threat at base threat level + combat.threatCurrent = combat.threatBase; + + // Adjust threat using current tactical state + switch ( combat.tacticalCurrent ) { + case AITACTICAL_HIDE: combat.threatCurrent *= 0.5f; break; + case AITACTICAL_MELEE: combat.threatCurrent *= 2.0f; break; + } + + // Signifigantly reduced threat when in undying mode + if ( aifl.undying ) { + combat.threatCurrent *= 0.25f; + } +} + +/* +============ +idAI::CalculateEnemyThreat +============ +*/ +float idAI::CalculateEnemyThreat ( idEntity* enemyEnt ) { + // Calculate the adjusted threat for this actor + float threat = 1.0f; + if ( enemyEnt->IsType ( idAI::GetClassType ( ) ) ) { + idAI* enemyAI = static_cast(enemyEnt); + threat = enemyAI->combat.threatCurrent; + + // Increase threat for enemies that are targetting us + if ( enemyAI->enemy.ent == this && enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE ) { + threat *= 2.0f; + } + } else if ( enemyEnt->IsType ( idPlayer::GetClassType ( ) ) ) { + threat = 2.0f; + } else { + threat = 1.0f; + } + + float enemyRangeSqr; + float distSqr; + + enemyRangeSqr = (enemy.ent) ? Square ( enemy.range ) : Square ( combat.attackRange[1] ); + distSqr = (physicsObj.GetOrigin ( ) - enemyEnt->GetPhysics()->GetOrigin ( )).LengthSqr ( ); + + if ( distSqr > 0 ) { + return threat * (enemyRangeSqr / distSqr); + } + + return threat; +} + +/* +============ +idAI::CheckBlink +============ +*/ +void idAI::CheckBlink ( void ) { +// if ( IsSpeaking ( ) ) { +// return; +// } + idActor::CheckBlink ( ); +} + +/* +============ +idAI::Speak +============ +*/ +bool idAI::Speak( const char *lipsync, bool random ){ + assert( idStr::Icmpn( lipsync, "lipsync_", 7 ) == 0 ); + + if ( random ) { + // If there is no lipsync then skip it + if ( spawnArgs.MatchPrefix ( lipsync ) ) { + lipsync = spawnArgs.RandomPrefix ( lipsync, gameLocal.random ); + } else { + lipsync = NULL; + } + } else { + lipsync = spawnArgs.GetString ( lipsync ); + } + + if ( !lipsync || !*lipsync ) { + return false; + } + + if ( head ) { + speakTime = head->StartLipSyncing( lipsync ); + } else { + speakTime = 0; + StartSoundShader (declManager->FindSound ( lipsync ), SND_CHANNEL_VOICE, SSF_IS_VO, false, &speakTime ); + } + + speakTime += gameLocal.time; + return true; +} + +/* +============ +idAI::StopSpeaking +============ +*/ +void idAI::StopSpeaking( bool stopAnims ){ + speakTime = 0; + StopSound( SND_CHANNEL_VOICE, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false ); + if ( stopAnims ) { + head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 ); + } + } +} + +/* +============ +idAI::CanHitEnemyFromAnim +============ +*/ +bool idAI::CanHitEnemyFromAnim( int animNum, idVec3 offset ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + trace_t tr; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin()+offset; + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + +/* + if( DebugFilter(ai_debugTactical) ) { + gameRenderWorld->DebugLine ( colorYellow, org + attackAnimInfo[ animNum ].eyeOffset * viewAxis, from, 5000 ); + gameRenderWorld->DebugLine ( colorOrange, from, enemy.lastVisibleEyePosition, 5000 ); + } +*/ + + // If the point we are shooting from is within our bounds then we are good to go, otherwise make sure its not in a wall + const idBounds &ownerBounds = physicsObj.GetAbsBounds(); + if ( !ownerBounds.ContainsPoint ( from ) ) { + trace_t tr; + if ( !g_perfTest_aiNoVisTrace.GetBool() ) { + gameLocal.TracePoint( this, tr, org + attackAnimInfo[ animNum ].eyeOffset * axis, from, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction < 1.0f ) { + return false; + } + } + } + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +idAI::ScriptedBegin +================ +*/ +bool idAI::ScriptedBegin ( bool endWithIdle, bool allowDormant ) { + if ( aifl.dead ) { + return false; + } + + // Wakeup if not awake already + WakeUp ( ); + + aifl.scriptedEndWithIdle = endWithIdle; + aifl.scripted = true; +// combat.fl.aware = false; + combat.tacticalCurrent = AITACTICAL_NONE; + + // Make sure the entity never goes dormant during a scripted event or + // the event may never end. + aifl.scriptedNeverDormant = !allowDormant; + dormantStart = 0; + +/* + // actors will ignore enemies during scripted events + ClearEnemy ( ); +*/ + + // Cancel any current movement + StopMove ( MOVE_STATUS_DONE ); + + move.fl.allowAnimMove = true; + + return true; +} + +/* +================ +idAI::ScriptedEnd +================ +*/ +void idAI::ScriptedEnd ( void ) { + dormantStart = 0; + aifl.scripted = false; +} + +/* +================ +idAI::ScriptedStop +================ +*/ +void idAI::ScriptedStop ( void ) { + if ( !aifl.scripted ) { + return; + } + aifl.scriptedEndWithIdle = true; + aifl.scripted = false; + StopMove( MOVE_STATUS_DONE ); +} + +/* +================ +idAI::ScriptedMove +================ +*/ +void idAI::ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + //disable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.UnMarkAllReachBlocked(); + //attempt the move - NOTE: this *can* fail if there's no route or AAS obstacles are in the way! + MoveToEntity ( destEnt, minDist ); + //re-enable all temporary blocked reachabilities due to teammate obstacle avoidance + aiManager.ReMarkAllReachBlocked(); + + // Move the torso to the idle state if its not already there + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Move the legs into the idle state so he will start moving + SetAnimState( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + + // Set up state loop for moving + SetState ( "State_ScriptedMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedFace +================ +*/ +void idAI::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + // Force idle while facing + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + // Start facing the entity + FaceEntity ( faceEnt ); + + SetState ( "State_ScriptedFace" ); + PostState ( "State_ScriptedStop" ); +} + +/* +================ +idAI::ScriptedAnim + +Plays an the given animation in an un-interruptable state. If looping will continue indefinately until +another operation which will stop a scripted sequence is called. When done can optionally return the character +back to their normal processing if endWithIdle is set to true. +================ +*/ +void idAI::ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle, true ) ) { + return; + } + + TurnToward ( move.current_yaw ); + + if ( loop ) { + // Loop the given animation + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + // Play the given animation + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + + SetAnimState ( ANIMCHANNEL_LEGS, "Wait_Frame" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_ScriptedAnim" ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + + SetState ( "Wait_ScriptedDone" ); + PostState ( "State_ScriptedStop" ); +} + + +/* +============ +idAI::ScriptedPlaybackAim +============ +*/ +void idAI::ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + mLookPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ); + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackAim" ); + PostState ( "State_ScriptedStop" ); +} + +/* +============ +idAI::ScriptedAction +============ +*/ +void idAI::ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + const char* actionName; + + if ( !actionEnt ) { + return; + } + + // Get the action name + actionName = actionEnt->spawnArgs.GetString ( "action" ); + if ( !*actionName ) { + gameLocal.Error ( "missing action keyword on scripted action entity '%s' for ai '%s'", + actionEnt->GetName(), + GetName() ); + return; + } + + // Start the scripted sequence + if ( !ScriptedBegin ( endWithIdle ) ) { + return; + } + + scriptedActionEnt = actionEnt; + + SetState ( "State_ScriptedStop" ); + PerformAction ( va("TorsoAction_%s", actionName ), 4, true ); +} + +/* +============ +idAI::FootStep +============ +*/ +void idAI::FootStep ( void ) { + idActor::FootStep ( ); + + ExecScriptFunction( funcs.footstep ); +} + +/* +============ +idAI::SetScript +============ +*/ +void idAI::SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "first_sight" ) ) { + funcs.first_sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "sight" ) ) { + funcs.sight.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "pain" ) ) { + funcs.pain.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "damage" ) ) { + funcs.damage.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "death" ) ) { + funcs.death.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "attack" ) ) { + funcs.attack.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "init" ) ) { + funcs.init.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "onclick" ) ) { + funcs.onclick.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "launch_projectile" ) ) { + funcs.launch_projectile.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "footstep" ) ) { + funcs.footstep.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "postHeal" ) ) { + // hax: this is a medic only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else if ( !idStr::Icmp ( scriptName, "postWeaponDestroyed" ) ) { + // hax: this is a Gladiator/Light Tank only script and I don't want to store it on every AI type.. + // I also don't want it generating the warning below in this case. + } else { + gameLocal.Warning ( "unknown script '%s' specified on entity '%s'", scriptName, name.c_str() ); + } +} + +/* +=============================================================================== + + idAI - Reactions + +=============================================================================== +*/ + +/* +============ +idAI::ReactToShotAt +============ +*/ +void idAI::ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ) { + if ( g_perfTest_aiNoDodge.GetBool() ) { + return; + } + + idVec3 foo; + idVec3 diff; + diff = GetPhysics()->GetOrigin() - origOrigin; + diff = origOrigin + diff.ProjectOntoVector ( origDir ) * origDir; + diff = diff - GetPhysics()->GetOrigin(); + diff.NormalizeFast ( ); + diff.z = 0; + + idAngles angles = diff.ToAngles ( ); + float angleDelta = idMath::AngleDelta ( angles[YAW], move.current_yaw ); + + combat.shotAtTime = gameLocal.time; + combat.shotAtAngle = angleDelta; + + // Someone is attacking us so give them a chance to be our new enemy + CheckForReplaceEnemy ( attacker ); +} + +/* +============ +idAI::ReactToPain +============ +*/ +void idAI::ReactToPain ( idEntity* attacker, int damage ) { + CheckForReplaceEnemy ( attacker ); +} + +/* +=============================================================================== + + idAI - Helpers + +=============================================================================== +*/ + +/* +============ +idAI::UpdateHelper +============ +*/ +void idAI::UpdateHelper ( void ) { + rvAIHelper* oldhelper; + + // Link ourselves to the closest helper + oldhelper = helperCurrent; + helperCurrent = aiManager.FindClosestHelper ( physicsObj.GetOrigin() ); + + // Ideal stays the same as current as long as it was the same when we started + if ( oldhelper == helperIdeal ) { + helperIdeal = helperCurrent; + } +} + +/* +============ +idAI::GetActiveHelper + +When we have an enemy our current helper becomes the active helper, when we dont have an +enemy we instead use our ideal. +============ +*/ +rvAIHelper* idAI::GetActiveHelper ( void ) { + return GetEnemy ( ) ? helperCurrent : helperIdeal; +} + +/* +=============================================================================== + + idAI - Tethers + +=============================================================================== +*/ + +/* +============ +idAI::SetTether +============ +*/ +void idAI::SetTether ( rvAITether* newTether ) { + aifl.tetherMover = false; + + // Clear our current tether? + if ( !newTether ) { + if ( tether ) { + tether = NULL; + ForceTacticalUpdate ( ); + } + } else if ( newTether->IsType ( rvAITetherClear::GetClassType ( ) ) ) { + SetTether ( NULL ); + } else { + if ( newTether && !newTether->ValidateAAS ( this ) ) { + // If you have aas error out to make them fix it + if ( aas ) { + gameLocal.Error ( "tether entity '%s' does no link into the aas for ai '%s'. (try moving it closer to the floor where the aas is)", + newTether->GetName(), GetName () ); + // If we dont have aas, just warn + } else { + gameLocal.Warning ( "tether entity '%s' does no link into the aas for ai '%s'. (there is no aas available)", + newTether->GetName(), GetName () ); + } + SetTether ( NULL ); + } else if ( newTether != tether ) { + tether = newTether; + ForceTacticalUpdate ( ); + } + } +} + +/* +============ +idAI::GetTether +============ +*/ +rvAITether* idAI::GetTether ( void ) { + return tether; +} + +/* +============ +idAI::IsTethered +============ +*/ +bool idAI::IsTethered ( void ) const { + // Need a tether entity to be tethered + if ( !tether ) { + return false; + } + // If we have an enemy and that enemy is within our tether then break it if we can + if ( enemy.ent && enemy.ent->IsType ( idAI::GetClassType() ) && tether->CanBreak ( ) ) { + if ( tether->ValidateDestination ( static_cast(enemy.ent.GetEntity()), enemy.lastKnownPosition ) ) { + return false; + } + } + return true; +} + +/* +============ +idAI::IsWithinTether +============ +*/ +bool idAI::IsWithinTether ( void ) const { + if ( !IsTethered ( ) ) { + return false; + } + if ( !tether->ValidateDestination ( (idAI*)this, physicsObj.GetOrigin ( ) ) ) { + return false; + } + return true; +} + +/* +=============================================================================== + + idAI - NonCombat + +=============================================================================== +*/ + +/* +===================== +idAI::SetTalkState +===================== +*/ +void idAI::SetTalkState ( talkState_t state ) { + // Make sure state is valid + if ( ( state < 0 ) || ( state >= NUM_TALK_STATES ) ) { + gameLocal.Error( "Invalid talk state (%d)", (int)state ); + } + + // Same state we are already in? + if ( talkState == state ) { + return; + } + + // Set new talk state + talkState = state; + +} + +/* +============ +idAI::SetPassivePrefix +============ +*/ +void idAI::SetPassivePrefix ( const char* prefix ) { + passive.prefix = prefix; + if ( passive.prefix.Length() ) { + passive.prefix += "_"; + } + + // Force an idle change + passive.idleAnimChangeTime = 0; + passive.fidgetTime = 0; + passive.talkTime = 0; + + // Get animation prefixs + passive.fl.multipleIdles = GetPassiveAnimPrefix ( "idle", passive.animIdlePrefix ); + GetPassiveAnimPrefix ( "fidget", passive.animFidgetPrefix ); + GetPassiveAnimPrefix ( "talk", passive.animTalkPrefix ); +} + +/* +============ +idAI::GetPassiveAnimPrefix +============ +*/ +bool idAI::GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ) { + const idKeyValue* key; + + // First see if we have custom idle animations for the passive prefix + key = NULL; + if ( passive.prefix.Length ( ) ) { + animPrefix = va("anim_%s%s", passive.prefix.c_str(), animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + // If there are no custom idle animations for the prefix then see if there are any custom anims at all + if ( !key ) { + animPrefix = va("anim_%s", animName ); + key = spawnArgs.MatchPrefix ( animPrefix ); + } + + if ( !key ) { + animPrefix = ""; + return false; + } + + return spawnArgs.MatchPrefix ( animPrefix, key ) ? true : false; +} + +/* +=================== +idAI::IsMeleeNeeded +=================== +*/ +bool idAI::IsMeleeNeeded( void ) { + + if( enemy.ent && enemy.ent->IsType ( idAI::Type )) { + + idAI* enemyAI = static_cast(enemy.ent.GetEntity()); + + //if our enemy is closing in on us and demands melee, we'll meet him. + if ( enemyAI->combat.tacticalCurrent == AITACTICAL_MELEE && enemy.range < combat.meleeRange ) { + return true; + } + + //other checks... + } + + return false; +} + +/* +=================== +idAI::IsCrouching +=================== +*/ +bool idAI::IsCrouching( void ) const { + return move.fl.crouching; +} + +bool idAI::CheckDeathCausesMissionFailure( void ) +{ + if ( spawnArgs.GetString( "objectivetitle_failed", NULL ) ) + { + return true; + } + if ( targets.Num() ) + { + //go through my targets and see if any are of class rvObjectiveFailed + idEntity* targEnt; + for( int i = 0; i < targets.Num(); i++ ) { + targEnt = targets[ i ].GetEntity(); + if ( !targEnt ) + { + continue; + } + if ( !targEnt->IsType( rvObjectiveFailed::GetClassType() ) ) { + continue; + } + if ( !spawnArgs.GetString( "inv_objective", NULL ) ) { + continue; + } + //yep! + return true; + } + } + return false; +} diff --git a/src/mpgame/ai/AI.h b/src/mpgame/ai/AI.h new file mode 100644 index 0000000..7761f94 --- /dev/null +++ b/src/mpgame/ai/AI.h @@ -0,0 +1,1397 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI.h + +================ +*/ + +#ifndef __AI_H__ +#define __AI_H__ + +// moved all motion related code to AI_Move.h +#ifndef __AI_MOVE_H__ + #include "AI_Move.h" +#endif +#ifndef __AAS_TACTICAL_H__ + #include "AAS_tactical.h" +#endif + +typedef enum { + AITACTICAL_NONE, + AITACTICAL_MELEE, // Rush towards the enemy and perform melee attacks when within range + AITACTICAL_MOVE_FOLLOW, // Move towards leader, stop when within range + AITACTICAL_MOVE_TETHER, // Move within tether, stop when within tether + AITACTICAL_MOVE_PLAYERPUSH, // Move away when the player pushes us (or another ai entity pushes us that was pushe by player) + AITACTICAL_COVER, // Move to cover and perform attacks from that cover position + AITACTICAL_COVER_FLANK, + AITACTICAL_COVER_ADVANCE, + AITACTICAL_COVER_RETREAT, + AITACTICAL_COVER_AMBUSH, + AITACTICAL_RANGED, // Move to position in which the enemy can be attacked from range, stop when there and attack enemy + AITACTICAL_TURRET, // Stay in current position and attack enemy + AITACTICAL_HIDE, // Move to a position where we cannot be seen by our enemy + AITACTICAL_PASSIVE, // Stay in current position with multiple idles, twitch animations, and conversations + + AITACTICAL_MAX +} aiTactical_t; + +typedef enum { + AICTRESULT_OK, + AICTRESULT_SKIP, + AICTRESULT_NOMOVE, +} aiCTResult_t; + +const int AITACTICAL_NONE_BIT = BIT(AITACTICAL_NONE); +const int AITACTICAL_MELEE_BIT = BIT(AITACTICAL_MELEE); +const int AITACTICAL_MOVE_FOLLOW_BIT = BIT(AITACTICAL_MOVE_FOLLOW); +const int AITACTICAL_MOVE_TETHER_BIT = BIT(AITACTICAL_MOVE_TETHER); +const int AITACTICAL_MOVE_PLAYERPUSH_BIT = BIT(AITACTICAL_MOVE_PLAYERPUSH); +const int AITACTICAL_COVER_BIT = BIT(AITACTICAL_COVER); +const int AITACTICAL_COVER_FLANK_BIT = BIT(AITACTICAL_COVER_FLANK); +const int AITACTICAL_COVER_ADVANCE_BIT = BIT(AITACTICAL_COVER_ADVANCE); +const int AITACTICAL_COVER_RETREAT_BIT = BIT(AITACTICAL_COVER_RETREAT); +const int AITACTICAL_COVER_AMBUSH_BIT = BIT(AITACTICAL_COVER_AMBUSH); +const int AITACTICAL_RANGED_BIT = BIT(AITACTICAL_RANGED); +const int AITACTICAL_TURRET_BIT = BIT(AITACTICAL_TURRET); +const int AITACTICAL_HIDE_BIT = BIT(AITACTICAL_HIDE); +const int AITACTICAL_PASSIVE_BIT = BIT(AITACTICAL_PASSIVE); + +const int AITACTICAL_COVER_BITS = (AITACTICAL_COVER_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); +const int AITACTICAL_RANGED_BITS = (AITACTICAL_RANGED_BIT); +const int AITACTICAL_NONMOVING_BITS = (AITACTICAL_NONE_BIT|AITACTICAL_TURRET_BIT|AITACTICAL_PASSIVE_BIT); + +typedef enum { + TALKMSG_NONE, + TALKMSG_PRIMARY, + TALKMSG_SECONDARY, + TALKMSG_LOOP, +} talkMessage_t; + +typedef enum { + AIFLAGOVERRIDE_DAMAGE, + AIFLAGOVERRIDE_DISABLEPAIN, + AIFLAGOVERRIDE_NOTURN, + AIFLAGOVERRIDE_NOGRAVITY +} aiFlagOverride_t; + +typedef enum { + TALK_NEVER, // Never talk to the player + TALK_DEAD, // Cant talk due to being dead + TALK_OK, // Can talk + TALK_FOLLOW, // Talking to will cause a follow + TALK_BUSY, // Cant talk right now, is busy + TALK_WAIT, // Wait a bit - he's probably in the middle of a conversation (this is so you can still see their names but they won't talk to you when clicked on) + NUM_TALK_STATES +} talkState_t; + +//chance that AI will make an announcement when they have the option (0 - 1.0f) +#define AISPEAK_CHANCE 0.2f + +typedef enum { + AIFOCUS_NONE, + AIFOCUS_LEADER, + AIFOCUS_TARGET, + AIFOCUS_TALK, + AIFOCUS_PLAYER, + AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_ENEMY = AIFOCUS_USE_DIRECTIONAL_MOVE, + AIFOCUS_COVER, + AIFOCUS_COVERLOOK, + AIFOCUS_HELPER, + AIFOCUS_TETHER, + AIFOCUS_MAX +} aiFocus_t; + +typedef enum { + AIMOVESPEED_DEFAULT, // Choose run/walk depending on situation + AIMOVESPEED_RUN, // Always run + AIMOVESPEED_WALK, // alwasy walk +} aiMoveSpeed_t; + +typedef struct rvAIFuncs_s { + rvScriptFuncUtility first_sight; // script to run when an enemy is first sighted + rvScriptFuncUtility sight; // script to run every time an enemy is sighted + rvScriptFuncUtility pain; // script to run when the AI takes pain + rvScriptFuncUtility damage; // script to run when the AI takes damage + rvScriptFuncUtility death; // script to run when the AI dies + rvScriptFuncUtility attack; // script to run when attacking an enemy + rvScriptFuncUtility init; // script to run on initialization + rvScriptFuncUtility onclick; // script to run when a friendly AI is clicked on + rvScriptFuncUtility launch_projectile; // script to run when a projectile is launched + rvScriptFuncUtility footstep; // script to run on a footstep +} rvAIFuncs_t; + +typedef struct rvAICombat_s{ + struct combatFlags_s { + bool ignoreEnemies :1; + bool alert :1; + bool aware :1; + bool tetherNoBreak :1; // Set to true to prevent enemies from breaking tethers + bool tetherAutoBreak :1; // Set to true to automatically break tethers when within range + bool tetherOutOfRange :1; // Set to true when we are out of range of our curren tether + bool seenEnemyDirectly :1; // Has directly seen an enemy (as opposed to having heard or been told about him) + bool noChatter :1; // No combat announcements + bool crouchViewClear :1; // Can I crouch at the position that I stopped at? + } fl; + + float max_chasing_turn; + float shotAtTime; + float shotAtAngle; + idVec2 hideRange; + idVec2 attackRange; + int attackSightDelay; + float meleeRange; + float aggressiveRange; // Range to become more aggressive + float aggressiveScale; // Scale to use when altering numbers due to aggression + int investigateTime; + + float visStandHeight; // Height to check enemy visibiliy while standing + float visCrouchHeight; // Height to check enemy visiblity while crouching + float visRange; // Maximum distance to check enemy visibility + float earRange; // Maximum distance to check hearing an enemy from + float awareRange; // Distance to become automatically aware of an enemy + + int tacticalMaskAvailable; // Currently available tactical states + int tacticalMaskUpdate; // states currently being evaluated + aiTactical_t tacticalCurrent; // Current tactical state + int tacticalUpdateTime; // Last time the tacitcal state was updated (for delaying updating) + int tacticalPainTaken; // Amount of damage taken in the current tactical state + int tacticalPainThreshold; // Threshold of pain before invalidating the current tactical state + int tacticalFlinches; // Number of flinches that have occured at the current tactical state + + float threatBase; // Base amount of threat generated by AI + float threatCurrent; // Current amount of threat generatd by AI based on current state + + int maxLostVisTime; + + int coverValidTime; + int maxInvalidCoverTime; +} rvAICombat_t; + +typedef struct rvAIPain_s { + float threshold; + float takenThisFrame; + int lastTakenTime; + int loopEndTime; + idStr loopType; +} rvAIPain_t; + +typedef struct rvAIEnemy_s { + struct flags_s { + bool lockOrigin :1; // Stop tracking enemy origin until state changes + bool dead :1; // Enemy is dead + bool inFov :1; // Enemy is currently in fov + bool sighted :1; // Enemy was sighted at least once + bool visible :1; // Enemy is visible? + } fl; + + idEntityPtr ent; + int lastVisibleChangeTime; // last time the visible state of the enemy changed + idVec3 lastVisibleFromEyePosition; // Origin used in last successfull visibility check + idVec3 lastVisibleEyePosition; // Origin of last known visible eye position + idVec3 lastVisibleChestPosition; // Origin of last known visible chest position + int lastVisibleTime; // Time we last saw and enemy + + idVec3 smoothedLinearVelocity; + idVec3 smoothedPushedVelocity; + float smoothVelocityRate; + + idVec3 lastKnownPosition; // last place the enemy was known to be (does not mean visiblity) + + float range; // range to enemy + float range2d; // 2d range to enemy + int changeTime; // Time enemy was last changed + int checkTime; // Time we last checked for a new enemy +} rvAIEnemy_t; + +typedef struct rvAIPassive_s { + struct flags_s { + bool disabled :1; // No advanced passive state + bool multipleIdles :1; // Has multiple idle animations to play + bool fidget :1; // Has fidget animations + } fl; + + idStr animIdlePrefix; + idStr animFidgetPrefix; + idStr animTalkPrefix; + //idStr talkPrefix; + + idStr prefix; + idStr idleAnim; + int idleAnimChangeTime; + int fidgetTime; + int talkTime; +} rvAIPassive_t; + +typedef struct rvAIAttackAnimInfo_s { + idVec3 attackOffset; + idVec3 eyeOffset; +} rvAIAttackAnimInfo_t; + +#define AIACTIONF_ATTACK BIT(0) +#define AIACTIONF_MELEE BIT(1) + +class rvAIActionTimer { +public: + + rvAIActionTimer ( void ); + + bool Init ( const idDict& args, const char* name ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( int currentTime ); + void Reset ( int currentTime, float diversity = 0.0f, float scale = 1.0f ); + void Add ( int _time, float diversity = 0.0f ); + + bool IsDone ( int currentTime ) const; + + int GetTime ( void ) const; + int GetRate ( void ) const; + +protected: + + int time; + int rate; +}; + +ID_INLINE bool rvAIActionTimer::IsDone ( int currentTime ) const { + return currentTime >= time; +} + +ID_INLINE int rvAIActionTimer::GetTime ( void ) const { + return time; +} + +ID_INLINE int rvAIActionTimer::GetRate ( void ) const { + return rate; +} +class rvAIAction { +public: + + rvAIAction ( void ); + + bool Init ( const idDict& args, const char* name, const char* defaultState, int flags ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum EStatus { + STATUS_UNUSED, + STATUS_OK, // action was performed + STATUS_FAIL_DISABLED, // action is current disabled + STATUS_FAIL_TIMER, // actions timer has not finished + STATUS_FAIL_EXTERNALTIMER, // external timer passed in to PerformAction has not finished + STATUS_FAIL_MINRANGE, // enemy is not within minimum range + STATUS_FAIL_MAXRANGE, // enemy is out of maximum range + STATUS_FAIL_CHANCE, // action chance check failed + STATUS_FAIL_ANIM, // bad animation + STATUS_FAIL_CONDITION, // condition given to PerformAction failed + STATUS_FAIL_NOENEMY, // enemy cant be attacked + STATUS_MAX + }; + + struct flags_s { + bool disabled :1; // action disabled? + bool noPain :1; // no pain during action + bool noTurn :1; // no turning during action + bool isAttack :1; // attack? + bool isMelee :1; // melee? + bool overrideLegs :1; // override legs on this action? + bool noSimpleThink :1; // dont use simple think logic for this action + } fl; + + + idStrList anims; + idStr state; + + rvAIActionTimer timer; + + int blendFrames; + int failRate; + + float minRange; + float maxRange; + float minRange2d; + float maxRange2d; + + float chance; + float diversity; + + EStatus status; +}; + +typedef bool (idAI::*checkAction_t)(rvAIAction*,int); + +class rvPlaybackDriver +{ +public: + rvPlaybackDriver( void ) { mPlaybackDecl = NULL; mOldPlaybackDecl = NULL; } + + bool Start( const char *playback, idEntity *owner, int flags, int numFrames ); + bool UpdateFrame( idEntity *ent, rvDeclPlaybackData &out ); + void EndFrame( void ); + bool IsActive( void ) { return( !!mPlaybackDecl || !!mOldPlaybackDecl ); } + + const char *GetDestination( void ); + +// cnicholson: Begin Added save/restore functionality + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +// cnicholson: End Added save/restore functionality + +private: + int mLastTime; + int mTransitionTime; + + int mStartTime; + int mFlags; + const rvDeclPlayback *mPlaybackDecl; + idVec3 mOffset; + + int mOldStartTime; + int mOldFlags; + const rvDeclPlayback *mOldPlaybackDecl; + idVec3 mOldOffset; +}; + +/* +=============================================================================== + + idAI + +=============================================================================== +*/ + +const float AI_TURN_SCALE = 60.0f; +const float AI_SEEK_PREDICTION = 0.3f; +const float AI_FLY_DAMPENING = 0.15f; +const float AI_HEARING_RANGE = 2048.0f; +const float AI_COVER_MINRANGE = 4.0f; +const float AI_PAIN_LOOP_DELAY = 200; +const int DEFAULT_FLY_OFFSET = 68.0f; + +#define ATTACK_IGNORE 0 +#define ATTACK_ON_DAMAGE BIT(0) +#define ATTACK_ON_ACTIVATE BIT(1) +#define ATTACK_ON_SIGHT BIT(2) + +// defined in script/ai_base.script. please keep them up to date. + +#define DI_NODIR -1 + + +// +// events +// +extern const idEventDef AI_DirectDamage; +extern const idEventDef AI_JumpFrame; +extern const idEventDef AI_EnableClip; +extern const idEventDef AI_DisableClip; +extern const idEventDef AI_EnableGravity; +extern const idEventDef AI_DisableGravity; +extern const idEventDef AI_EnablePain; +extern const idEventDef AI_DisablePain; +extern const idEventDef AI_EnableTarget; +extern const idEventDef AI_DisableTarget; +extern const idEventDef AI_EnableMovement; +extern const idEventDef AI_DisableMovement; +extern const idEventDef AI_Vagary_ChooseObjectToThrow; +extern const idEventDef AI_Speak; +extern const idEventDef AI_SpeakRandom; +extern const idEventDef AI_Attack; +extern const idEventDef AI_AttackMelee; +extern const idEventDef AI_WaitMove; +extern const idEventDef AI_EnableDamage; +extern const idEventDef AI_DisableDamage; +extern const idEventDef AI_LockEnemyOrigin; +extern const idEventDef AI_SetEnemy; +extern const idEventDef AI_ScriptedAnim; +extern const idEventDef AI_ScriptedDone; +extern const idEventDef AI_ScriptedStop; +extern const idEventDef AI_SetScript; +extern const idEventDef AI_BecomeSolid; +extern const idEventDef AI_BecomePassive; +extern const idEventDef AI_BecomeAggressive; +extern const idEventDef AI_SetHealth; +extern const idEventDef AI_TakeDamage; +extern const idEventDef AI_EnableBlink; +extern const idEventDef AI_DisableBlink; +extern const idEventDef AI_EnableAutoBlink; +extern const idEventDef AI_DisableAutoBlink; + +class idPathCorner; +class idProjectile; +class rvSpawner; +class rvAIHelper; +class rvAITether; + +class idAI : public idActor { +friend class rvAIManager; +friend class idAASFindAttackPosition; +public: + CLASS_PROTOTYPE( idAI ); + + idAI(); + ~idAI(); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Spawn ( void ); + virtual void TalkTo ( idActor *actor ); + + idEntity* GetEnemy ( void ) const; + idEntity* GetGoalEntity ( void ) const; + talkState_t GetTalkState ( void ) const; + const idVec2& GetAttackRange ( void ) const; + const idVec2& GetFollowRange ( void ) const; + int GetTravelFlags ( void ) const; + + void TouchedByFlashlight ( idActor *flashlight_owner ); + + idEntity * FindEnemy ( bool inFov, bool forceNearest, float maxDistSqr = 0.0f ); + void SetSpawner ( rvSpawner* _spawner ); + rvSpawner* GetSpawner ( void ); + + idActor* GetLeader ( void ) const; + + // Outputs a list of all monsters to the console. + static void List_f( const idCmdArgs &args ); + + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + + bool IsEnemyVisible ( void ) const; + bool InCoverMode ( void ) const; + bool InCrouchCoverMode ( void ) const; + bool LookAtCoverTall ( void ) const; + bool InLookAtCoverMode ( void ) const; + bool IsBehindCover ( void ) const; + bool IsLipSyncing ( void ) const; + bool IsSpeaking ( void ) const; + bool IsFacingEnt ( idEntity* targetEnt ); + bool IsCoverValid ( void ) const; + virtual bool IsCrouching ( void ) const; + + +public: + + idLinkList simpleThinkNode; + + // navigation + idAAS* aas; + idAASCallback* aasFind; + + // movement + idMoveState move; + idMoveState savedMove; + + // physics + idPhysics_Monster physicsObj; + + // weapon/attack vars + bool lastHitCheckResult; + int lastHitCheckTime; + int lastAttackTime; + float projectile_height_to_distance_ratio; // calculates the maximum height a projectile can be thrown + idList attackAnimInfo; + + mutable idClipModel* projectileClipModel; + idEntityPtr projectile; + + // chatter/talking + int chatterTime; + int chatterRateCombat; + int chatterRateIdle; + talkState_t talkState; + idEntityPtr talkTarget; + talkMessage_t talkMessage; + int talkBusyCount; + int speakTime; + + // Focus + idEntityPtr lookTarget; + aiFocus_t focusType; + idEntityPtr focusEntity; + float focusRange; + int focusAlignTime; + int focusTime; + idVec3 currentFocusPos; + + // Looking + bool allowJointMod; + int alignHeadTime; + int forceAlignHeadTime; + idAngles eyeAng; + idAngles lookAng; + idAngles destLookAng; + idAngles lookMin; + idAngles lookMax; + idList lookJoints; + idList lookJointAngles; + float eyeVerticalOffset; + float eyeHorizontalOffset; + float headFocusRate; + float eyeFocusRate; + + // joint controllers + idAngles eyeMin; + idAngles eyeMax; + jointHandle_t orientationJoint; + + idEntityPtr pusher; + idEntityPtr scriptedActionEnt; + + // script variables + struct aiFlags_s { + bool awake :1; // set to false until state_wakeup is called. + bool damage :1; + bool pain :1; + bool dead :1; + bool activated :1; + bool jump :1; + bool hitEnemy :1; + bool pushed :1; + bool disableAttacks :1; + bool scriptedEndWithIdle :1; + bool scriptedNeverDormant :1; // Prevent going dormant while in scripted sequence + bool scripted :1; + bool simpleThink :1; + bool ignoreFlashlight :1; + bool action :1; + bool lookAtPlayer :1; + bool disableLook :1; + bool undying :1; + bool tetherMover :1; // Currently using a dynamic tether to a mover + bool meleeSuperhero :1; + bool killerGuard :1; // Do 100 points of damage with each hit + } aifl; + + // + // ai/ai.cpp + // + void SetAAS ( void ); + virtual void DormantBegin ( void ); // called when entity becomes dormant + virtual void DormantEnd ( void ); // called when entity wakes from being dormant + virtual void Think ( void ); + void Activate ( idEntity *activator ); + virtual void Hide ( void ); + virtual void Show ( void ); + virtual void AdjustHealthByDamage ( int inDamage ); + void CalculateAttackOffsets ( void ); + + void InitNonPersistentSpawnArgs ( void ); + + /* + =============================================================================== + Speaking & Chatter + =============================================================================== + */ +public: + + bool Speak ( const char *speechDecl, bool random = false ); + void StopSpeaking ( bool stopAnims ); + virtual void CheckBlink ( void ); + +protected: + + virtual bool CanPlayChatterSounds ( void ) const; + void UpdateChatter ( void ); + + + /* + =============================================================================== + Movement + =============================================================================== + */ + + // static helper functions +public: + // Finds a path around dynamic obstacles. + static bool FindPathAroundObstacles ( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ); + // Frees any nodes used for the dynamic obstacle avoidance. + static void FreeObstacleAvoidanceNodes ( void ); + // Predicts movement, returns true if a stop event was triggered. + static bool PredictPath ( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore = NULL ); + // Return true if the trajectory of the clip model is collision free. + static bool TestTrajectory ( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ); + // Finds the best collision free trajectory for a clip model. + static bool PredictTrajectory ( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ); + + + // special flying code + void AdjustFlyingAngles ( void ); + void AddFlyBob ( idVec3 &vel ); + void AdjustFlyHeight ( idVec3 &vel, const idVec3 &goalPos ); + void FlySeekGoal ( idVec3 &vel, idVec3 &goalPos ); + void AdjustFlySpeed ( idVec3 &vel ); + void FlyTurn ( void ); + + // movement types + void Move ( void ); + virtual void DeadMove ( void ); + void AnimMove ( void ); + void SlideMove ( void ); + void PlaybackMove ( void ); + void FlyMove ( void ); + void StaticMove ( void ); + void RVMasterMove ( void ); + void SetMoveType ( moveType_t moveType ); + //twhitaker: added custom move type + virtual void CustomMove ( void ); + + // movement actions + void KickObstacles ( const idVec3 &dir, float force, idEntity *alwaysKick ); + + // steering + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + void GetAnimMoveDelta ( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ); + void CheckObstacleAvoidance ( const idVec3 &goalPos, idVec3 &newPos, idReachability* goalReach=0 ); + bool GetMovePos ( idVec3 &seekPos, idReachability** seekReach=0 ); + + + // navigation + float TravelDistance ( const idVec3 &end ) const; + float TravelDistance ( const idVec3 &start, const idVec3 &end ) const; + float TravelDistance ( idEntity* ent ) const; + float TravelDistance ( idEntity* start, idEntity* end ) const; + int PointReachableAreaNum ( const idVec3 &pos, const float boundsScale = 2.0f ) const; + bool PathToGoal ( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const; + void BlockedFailSafe ( void ); + + // turning + void Turn ( void ); + bool TurnToward ( float yaw ); + bool TurnToward ( const idVec3 &pos ); + bool TurnTowardLeader ( bool faceLeaderByDefault=false ); + bool FacingIdeal ( void ); + bool DirectionalTurnToward ( const idVec3 &pos ); + + // movement control + bool FaceEnemy ( void ); + bool FaceEntity ( idEntity *ent ); + bool SlideToPosition ( const idVec3 &pos, float time ); + bool WanderAround ( void ); + bool StepDirection ( float dir ); + bool NewWanderDir ( const idVec3 &dest ); + + + /* + =============================================================================== + Reactions + =============================================================================== + */ + void ReactToShotAt ( idEntity* attacker, const idVec3 &origOrigin, const idVec3 &origDir ); + void ReactToPain ( idEntity* attacker, int damage ); + + /* + =============================================================================== + AI helpers + =============================================================================== + */ + +public: + + void UpdateHelper ( void ); + rvAIHelper* GetActiveHelper ( void ); + + /* + =============================================================================== + Sensory Perception + =============================================================================== + */ + +public: + + const idVec3& LastKnownPosition ( const idEntity *ent ); + const idVec3& LastKnownFacing ( const idEntity *ent ); + idEntity * HeardSound ( int ignore_team ); + int ReactionTo ( const idEntity *ent ); + void SetLastVisibleEnemyTime ( int time=-1/* DEFAULT IS CURRENT TIME*/ ); + bool IsEnemyRecentlyVisible ( float maxLostVisTimeScale = 1.0f ) const; + + /* + =============================================================================== + Passive + =============================================================================== + */ + +public: + + void SetTalkState ( talkState_t state ); + void SetPassivePrefix ( const char* prefix ); + +protected: + + bool GetPassiveAnimPrefix ( const char* animName, idStr& animPrefix ); + + /* + =============================================================================== + Combat + =============================================================================== + */ + +public: + + // enemy managment + bool SetEnemy ( idEntity *newEnemy ); + void ClearEnemy ( bool dead = false ); + + void UpdateEnemy ( void ); + void UpdateEnemyPosition ( bool force = true ); + void UpdateEnemyVisibility ( void ); + + // Attack direction + bool GetAimDir ( const idVec3& source, const idEntity* aimAtEnt, const idDict* projectileDict, idEntity *ignore, idVec3 &aimDir, float aimOffset, float predict ) const; + void GetPredictedAimDirOffset ( const idVec3& source, const idVec3& target, float projectileSpeed, const idVec3& targetVelocity, idVec3& offset ) const; + + // damage + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + bool CheckDeathCausesMissionFailure ( void ); + + // attacks + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual idProjectile* AttackProjectile ( const idDict* projectileDict, const idVec3 &org, const idAngles &ang ); + virtual bool AttackMelee ( const char* attackName, const idDict* meleeDict ); + + void CreateProjectileClipModel ( void ) const; + idProjectile* CreateProjectile ( const idDict* projectileDict, const idVec3 &pos, const idVec3 &dir ); + void RemoveProjectile ( void ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + void DirectDamage ( const char *meleeDefName, idEntity *ent ); + bool TestMelee ( void ) const; + void PushWithAF ( void ); + bool IsMeleeNeeded ( void ); + + // special effects + void GetMuzzle ( jointHandle_t joint, idVec3 &muzzle, idMat3 &axis ); + void LerpLookAngles ( idAngles &curAngles, idAngles newAngles, float orientationJointYaw, float focusRate ); + virtual bool UpdateAnimationControllers ( void ); + + // AI script state management + void UpdateStates ( void ); + void UpdateFocus ( const idMat3& orientationAxis ); + void SetFocus ( aiFocus_t focus, int time ); + + // event? + virtual void FootStep ( void ); + + void CreateMissile ( jointHandle_t joint ); + void RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void BecomeSolid ( void ); + void BecomeNonSolid ( void ); + const char * ChooseAnim ( int channel, const char *animname ); + + // + // ai/ai_events.cpp + // +public: + + + virtual bool CanTakeDamage ( void ) const; + virtual bool CanTakePain ( void ) const; + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual bool CanAnnounce ( float chance ) const; + + virtual bool SkipCurrentDestination ( void ) const; + +// ----------------------------- Functions ------------------------------------ + + virtual bool DoDormantTests ( void ); + + void OverrideFlag ( aiFlagOverride_t flag, bool value ); + void RestoreFlag ( aiFlagOverride_t flag ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual const char* GetIdleAnimName ( void ); + + bool RespondToFlashlight ( void ) { return !aifl.ignoreFlashlight;} + bool ForceFaceEnemy ( void ) { return ( move.moveCommand == MOVE_TO_ENEMY ); } + + bool CanBecomeSolid ( void ); + bool CanHitEnemyFromAnim ( int animNum, idVec3 offset = vec3_origin ); + bool CanHitEnemy ( void ); + bool CanHitEnemyFromJoint ( const char *jointname ); + + float GetTurnDelta ( void ); + + int TestTrajectory ( const idVec3 &firePos, const idVec3 &target, const char *projectileName ); + bool TestAnimMove ( int animNum, idEntity *ignore = NULL, idVec3 *pMoveVec = NULL ); + void ExecScriptFunction ( rvScriptFuncUtility& func, idEntity* parm = NULL ); + void SetLeader ( idEntity *newLeader ); + + int CheckMelee ( bool disableAttack ); + bool CheckForEnemy ( bool useFov, bool force = false ); + bool CheckForCloserEnemy ( void ); + bool CheckForReplaceEnemy ( idEntity* replacement ); + bool CheckForTeammateEnemy ( void ); + + void DrawSuspicion ( void ); + float RateSuspiciousness ( idActor* shady, bool rateSound = false ); + void RateSuspicionLevel ( void ); + + void UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ); + + void LookAtEntity ( idEntity *ent, float duration ); + +// ----------------------------- Variables ------------------------------------ + + int actionAnimNum; // Index of animation to use for the upcoming action + int actionTime; // Time line for actions (time is stopped when an action is running) + int actionSkipTime; // Time to use if an action is skipped by another + + int flagOverrides; + + float announceRate; // How often (0 - 1.0f) the AI will make certain announcements. + + rvAICombat_t combat; // Members related to combat state + rvAIPassive_t passive; // Members related to passive state + rvAIEnemy_t enemy; // Members related to tracking enemies + rvAIPain_t pain; + rvAIFuncs_t funcs; + + rvPlaybackDriver mPlayback; + rvPlaybackDriver mLookPlayback; + + rvAASTacticalSensor* aasSensor; + + idEntityPtr tether; + idEntityPtr helperCurrent; + idEntityPtr helperIdeal; + idEntityPtr leader; + idEntityPtr spawner; + + bool ValidateCover ( void ); + + virtual bool UpdateRunStatus ( void ); + bool UpdateTactical ( int delay = 0 ); + void ForceTacticalUpdate ( void ); + bool UpdateTactical_r ( void ); + virtual int FilterTactical ( int availableTactical ); + void WakeUpTargets ( void ); + + virtual aiCTResult_t CheckTactical ( aiTactical_t tactical ); + + void Begin ( void ); + void WakeUp ( void ); + + virtual void Prethink ( void ); + virtual void Postthink ( void ); + + /* + =============================================================================== + Threat Management + =============================================================================== + */ + +protected: + + virtual void UpdateThreat ( void ); + virtual float CalculateEnemyThreat ( idEntity* enemy ); + + /* + =============================================================================== + Tethers + =============================================================================== + */ + +public: + + virtual bool IsTethered ( void ) const; + bool IsWithinTether ( void ) const; + rvAITether* GetTether ( void ); + virtual void SetTether ( rvAITether* newTether ); + + /* + =============================================================================== + Scripting + =============================================================================== + */ + +public: + + void ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void ScriptedStop ( void ); + + void SetScript ( const char* scriptName, const char* funcName ); + +private: + + bool ScriptedBegin ( bool endWithIdle, bool allowDormant = false ); + void ScriptedEnd ( void ); + + /* + =============================================================================== + Handlers + =============================================================================== + */ + +protected: + + virtual void OnDeath ( void ); + virtual void OnStateChange ( int channel ); + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnLeaderChange ( idEntity* oldLeader ); + virtual void OnStartMoving ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnFriendlyFire ( idActor* attacker ); + virtual void OnWakeUp ( void ); + virtual void OnTouch ( idEntity *other, trace_t *trace ); + virtual void OnCoverInvalidated ( void ); + virtual void OnCoverNotFacingEnemy ( void ); + virtual void OnEnemyVisiblityChange ( bool oldVisible ); + virtual void OnStartAction ( void ); + virtual void OnStopAction ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + /* + =============================================================================== + Movement / Turning + =============================================================================== + */ + +protected: + + bool StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ); + void StopMove ( moveStatus_t status ); + + bool MoveTo ( const idVec3 &pos, float range = 0.0f ); + bool MoveToAttack ( idEntity *ent, int attack_anim ); + bool MoveToTether ( rvAITether* tether ); + virtual bool MoveToEnemy ( void ); + bool MoveToEntity ( idEntity *ent, float range = 0.0f ); + bool MoveToCover ( float minRange, float maxRange, aiTactical_t coverType ); + bool MoveToHide ( void ); + + bool MoveOutOfRange ( idEntity *entity, float range, float minRange=0.0f ); + + void AnimTurn ( float angles, bool force ); + + bool ReachedPos ( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range = 0.0f ) const; + + /* + =============================================================================== + Debug + =============================================================================== + */ + +public: + + void DrawRoute ( void ) const; + void DrawTactical ( void ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + +public: + + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + void AnnounceNewEnemy ( void ); + void AnnounceKill ( idActor *victim ); + void AnnounceTactical ( aiTactical_t newTactical ); + void AnnounceSuppressed ( idActor *suppressor ); + void AnnounceSuppressing ( void ); + void AnnounceFlinch ( idEntity *attacker ); + void AnnounceInjured ( void ); + void AnnounceFriendlyFire ( idActor* attacker ); + void AnnounceGrenade ( void ); + void AnnounceGrenadeThrow ( void ); + + /* + =============================================================================== + Actions + =============================================================================== + */ + +protected: + + rvAIActionTimer actionTimerRangedAttack; + rvAIActionTimer actionTimerEvade; + rvAIActionTimer actionTimerSpecialAttack; + rvAIActionTimer actionTimerPain; + + rvAIAction actionEvadeLeft; + rvAIAction actionEvadeRight; + rvAIAction actionRangedAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionLeapAttack; + rvAIAction actionJumpBack; + + bool UpdateAction ( void ); + virtual bool CheckActions ( void ); + virtual bool CheckPainActions ( void ); + + bool PerformAction ( rvAIAction* action, bool (idAI::*)(rvAIAction*,int), rvAIActionTimer* timer = NULL ); + void PerformAction ( const char* stateName, int blendFrames = 0, bool noPain = false ); + + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + virtual void Event_PostSpawn ( void ); + // RAVEN END + +public: + + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + + /* + =============================================================================== + Events + =============================================================================== + */ + +private: + + // Orphaned events + void Event_ClosestReachableEnemyOfEntity ( idEntity *team_mate ); + void Event_GetReachableEntityPosition ( idEntity *ent ); + void Event_EntityInAttackCone ( idEntity *ent ); + void Event_TestAnimMoveTowardEnemy ( const char *animname ); + void Event_TestAnimMove ( const char *animname ); + void Event_TestMoveToPosition ( const idVec3 &position ); + void Event_TestMeleeAttack ( void ); + void Event_TestAnimAttack ( const char *animname ); + void Event_SaveMove ( void ); + void Event_RestoreMove ( void ); + void Event_ThrowMoveable ( void ); + void Event_ThrowAF ( void ); + void Event_PredictEnemyPos ( float time ); + void Event_FindActorsInBounds ( const idVec3 &mins, const idVec3 &maxs ); + + void Event_Activate ( idEntity *activator ); + void Event_Touch ( idEntity *other, trace_t *trace ); + void Event_LookAt ( idEntity* lookAt ); + + void Event_SetAngles ( idAngles const &ang ); + void Event_SetEnemy ( idEntity *ent ); + void Event_SetHealth ( float newHealth ); + void Event_SetTalkTarget ( idEntity *target ); + void Event_SetTalkState ( int state ); + void Event_SetLeader ( idEntity *newLeader ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetMoveSpeed ( int speed ); + void Event_SetPassivePrefix ( const char* prefix ); + + void Event_GetAngles ( void ); + void Event_GetEnemy ( void ); + void Event_GetLeader ( void ); + + void Event_Attack ( const char* attackName, const char* jointName ); + void Event_AttackMelee ( const char *meleeDefName ); + + void Event_DirectDamage ( idEntity *damageTarget, const char *damageDefName ); + void Event_RadiusDamageFromJoint ( const char *jointname, const char *damageDefName ); + void Event_CanBecomeSolid ( void ); + void Event_BecomeSolid ( void ); + void Event_BecomeNonSolid ( void ); + void Event_BecomeRagdoll ( void ); + void Event_StopRagdoll ( void ); + void Event_FaceEnemy ( void ); + void Event_FaceEntity ( idEntity *ent ); + void Event_WaitMove ( void ); + + void Event_BecomePassive ( int ignoreEnemies ); + void Event_BecomeAggressive ( void ); + + void Event_EnableDamage ( void ); + void Event_EnableClip ( void ); + void Event_EnableGravity ( void ); + void Event_EnableAFPush ( void ); + void Event_EnablePain ( void ); + void Event_DisableDamage ( void ); + void Event_DisableClip ( void ); + void Event_DisableGravity ( void ); + void Event_DisableAFPush ( void ); + void Event_DisablePain ( void ); + void Event_EnableTarget ( void ); + void Event_DisableTarget ( void ); + void Event_TakeDamage ( float takeDamage ); + void Event_SetUndying ( float setUndying ); + void Event_EnableAutoBlink ( void ); + void Event_DisableAutoBlink ( void ); + + void Event_LockEnemyOrigin ( void ); + + void Event_StopThinking ( void ); + void Event_JumpFrame ( void ); + void Event_RealKill ( void ); + void Event_Kill ( void ); + void Event_RemoveUpdateSpawner ( void ); + void Event_AllowHiddenMovement ( int enable ); + void Event_CanReachPosition ( const idVec3 &pos ); + void Event_CanReachEntity ( idEntity *ent ); + void Event_CanReachEnemy ( void ); + + void Event_IsSpeaking ( void ); + void Event_IsTethered ( void ); + void Event_IsWithinTether ( void ); + void Event_IsMoving ( void ); + + void Event_Speak ( const char *speechDecl ); + void Event_SpeakRandom ( const char *speechDecl ); + + void Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ); + void Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ); + void Event_ScriptedPlaybackAim ( const char* playback, int flags, int numFrames ); + void Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_ScriptedJumpDown ( float yaw ); + + void Event_FindEnemy ( float distSquare ); + void Event_SetKey ( const char *key, const char *value ); + + + /* + =============================================================================== + States + =============================================================================== + */ + +protected: + + // Wait states + stateResult_t State_Wait_Activated ( const stateParms_t& parms ); + stateResult_t State_Wait_ScriptedDone ( const stateParms_t& parms ); + stateResult_t State_Wait_Action ( const stateParms_t& parms ); + stateResult_t State_Wait_ActionNoPain ( const stateParms_t& parms ); + + // Global states + stateResult_t State_WakeUp ( const stateParms_t& parms ); + stateResult_t State_TriggerAnim ( const stateParms_t& parms ); + stateResult_t State_Wander ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + stateResult_t State_LightningDeath ( const stateParms_t& parms ); + stateResult_t State_Burn ( const stateParms_t& parms ); + stateResult_t State_Remove ( const stateParms_t& parms ); + + stateResult_t State_Passive ( const stateParms_t& parms ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_CombatCover ( const stateParms_t& parms ); + stateResult_t State_CombatMelee ( const stateParms_t& parms ); + stateResult_t State_CombatRanged ( const stateParms_t& parms ); + stateResult_t State_CombatTurret ( const stateParms_t& parms ); + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + + stateResult_t State_MovePlayerPush ( const stateParms_t& parms ); + stateResult_t State_MoveTether ( const stateParms_t& parms ); + stateResult_t State_MoveFollow ( const stateParms_t& parms ); + + stateResult_t State_ScriptedMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + stateResult_t State_ScriptedStop ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_ScriptedPlaybackAim ( const stateParms_t& parms ); + stateResult_t State_ScriptedJumpDown ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Torso_Sight ( const stateParms_t& parms ); + stateResult_t State_Torso_CustomCycle ( const stateParms_t& parms ); + stateResult_t State_Torso_Action ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishAction ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ScriptedAnim ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveIdle ( const stateParms_t& parms ); + stateResult_t State_Torso_PassiveFidget ( const stateParms_t& parms ); + + // Leg States + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnLeft ( const stateParms_t& parms ); + stateResult_t State_Legs_TurnRight ( const stateParms_t& parms ); + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_Legs_MoveThink ( const stateParms_t& parms ); + stateResult_t State_Legs_ChangeDirection ( const stateParms_t& parms ); + + // Head states + stateResult_t State_Head_Idle ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( idAI ); +}; + +/* +=============================================================================== + + idAI Inlines + +=============================================================================== +*/ + +ID_INLINE int DelayTime( int min, int range ) { + return min + gameLocal.random.RandomInt ( range + 1 ); +} + +ID_INLINE const idVec2& idAI::GetAttackRange ( void ) const { + return combat.attackRange; +} + +ID_INLINE const idVec2& idAI::GetFollowRange ( void ) const { + return move.followRange; +} + +ID_INLINE int idAI::GetTravelFlags ( void ) const { + return move.travelFlags; +} + +ID_INLINE bool idAI::IsEnemyVisible ( void ) const { + return enemy.ent && enemy.fl.visible; +} + +ID_INLINE bool idAI::IsEnemyRecentlyVisible( float maxLostVisTimeScale ) const { + return (enemy.ent + && combat.fl.seenEnemyDirectly + && (enemy.lastVisibleTime && gameLocal.time-enemy.lastVisibleTime < (combat.maxLostVisTime * maxLostVisTimeScale))); +} + +ID_INLINE bool idAI::LookAtCoverTall( void ) const { + return ( aasSensor->Look() + && (aasSensor->Look()->flags&FEATURE_LOOK_OVER) + && aasSensor->Look()->height > 40.0f ); +} + +ID_INLINE bool idAI::InLookAtCoverMode ( void ) const { + return (!IsBehindCover() + && !aifl.action + && move.fl.moving + && (combat.fl.alert || combat.fl.aware) + && aasSensor->Look() + && combat.tacticalCurrent != AITACTICAL_MELEE + && combat.tacticalCurrent != AITACTICAL_HIDE + && !IsEnemyRecentlyVisible(0.2f)); +} + +ID_INLINE bool idAI::InCoverMode ( void ) const { + return ( (1<Reserved ( ); +} + +ID_INLINE bool idAI::InCrouchCoverMode ( void ) const { + return ( InCoverMode() && (aasSensor->Reserved()->flags&FEATURE_LOOK_OVER) ); +} + +ID_INLINE bool idAI::IsBehindCover ( void ) const { + return ( InCoverMode() && move.fl.done && (aifl.action || DistanceTo2d ( aasSensor->ReservedOrigin() ) < AI_COVER_MINRANGE) ); +} + +ID_INLINE bool idAI::IsSpeaking ( void ) const { + return speakTime && gameLocal.time < speakTime; +} + +ID_INLINE bool idAI::IsFacingEnt ( idEntity* targetEnt ) { + return( move.moveCommand == MOVE_FACE_ENTITY && move.goalEntity == targetEnt && FacingIdeal() ); +} + +ID_INLINE bool idAI::IsCoverValid ( ) const { + return combat.coverValidTime && (gameLocal.time - combat.coverValidTime < combat.maxInvalidCoverTime); +} + +ID_INLINE idActor* idAI::GetLeader ( void ) const { + return leader; +} + +ID_INLINE idEntity* idAI::GetGoalEntity ( void ) const { + return move.goalEntity; +} + +ID_INLINE bool idAI::CanTakeDamage( void ) const { + return idActor::CanTakeDamage( ); +} + +ID_INLINE bool idAI::CanTakePain ( void ) const { + return !disablePain; +} + +ID_INLINE bool idAI::CanTurn ( void ) const { + return move.turnRate && !move.fl.noTurn && !move.fl.disabled; +} + +ID_INLINE bool idAI::CanMove ( void ) const { + return !move.fl.disabled && !move.fl.blocked && gameLocal.GetTime()>move.blockTime; +} + +ID_INLINE bool idAI::CanAnnounce ( float chance ) const { + return !aifl.dead && !IsSpeaking ( ) && !af.IsActive ( ) && !combat.fl.noChatter && ( gameLocal.random.RandomFloat() < chance ); +} + +ID_INLINE void idAI::SetFocus ( aiFocus_t focus, int time ) { + focusType = focus; + focusTime = gameLocal.time + time; +} + +ID_INLINE idEntity *idAI::GetEnemy( void ) const { + return enemy.ent; +} + +ID_INLINE void idAI::ForceTacticalUpdate ( void ) { + combat.tacticalUpdateTime = 0; + combat.tacticalMaskUpdate = 0; + delete aasFind; + aasFind = NULL; +} + +/* +=============================================================================== + + externs + +=============================================================================== +*/ + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; +extern const char* aiTalkMessageString [ ]; +extern const char* aiTacticalString [ AITACTICAL_MAX ]; +extern const char* aiMoveCommandString [ NUM_MOVE_COMMANDS ]; +extern const char* aiFocusString [ AIFOCUS_MAX ]; + +#endif /* !__AI_H__ */ + diff --git a/src/mpgame/ai/AI_Actions.cpp b/src/mpgame/ai/AI_Actions.cpp new file mode 100644 index 0000000..981f9b1 --- /dev/null +++ b/src/mpgame/ai/AI_Actions.cpp @@ -0,0 +1,615 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + + rvAIActionTimer + +=============================================================================== +*/ + +/* +================ +rvAIActionTimer::rvAIActionTimer +================ +*/ +rvAIActionTimer::rvAIActionTimer ( void ) { + time = 0; +} + +/* +================ +rvAIActionTimer::Init +================ +*/ +bool rvAIActionTimer::Init ( const idDict& args, const char* name ) { + rate = SEC2MS ( args.GetFloat ( va("%s_rate",name), "0" ) ); + return true; +} + +/* +================ +rvAIActionTimer::Save +================ +*/ +void rvAIActionTimer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( rate ); + savefile->WriteInt ( time ); +} + +/* +================ +rvAIActionTimer::Restore +================ +*/ +void rvAIActionTimer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( rate ); + savefile->ReadInt ( time ); +} + +/* +================ +rvAIActionTimer::Reset +================ +*/ +void rvAIActionTimer::Reset ( int currentTime, float diversity, float scale ) { + float _rate = rate * scale; + time = currentTime + (-gameLocal.random.RandomInt( 2.0f * _rate * diversity ) + _rate * diversity + _rate); +} + +/* +================ +rvAIActionTimer::Clear +================ +*/ +void rvAIActionTimer::Clear ( int currentTime ) { + time = currentTime; +} + +/* +================ +rvAIActionTimer::Add +================ +*/ +void rvAIActionTimer::Add ( int _time, float diversity ) { + time += (-gameLocal.random.RandomInt( 2.0f * _time * diversity ) + _time * diversity + _time); +} + +/* +=============================================================================== + + rvAIAction + +=============================================================================== +*/ + +/* +================ +rvAIAction::rvAIAction +================ +*/ +rvAIAction::rvAIAction ( void ) { + memset ( &fl, 0, sizeof(fl) ); + status = STATUS_UNUSED; +} + +/* +================ +rvAIAction::Init +================ +*/ +bool rvAIAction::Init ( const idDict& args, const char* name, const char* defaultState, int _flags ) { + const idKeyValue* kv; + + if ( _flags & AIACTIONF_ATTACK ) { + fl.isAttack = true; + } + if ( _flags & AIACTIONF_MELEE ) { + fl.isMelee = true; + } + + // Initialize timer + timer.Init ( args, name ); + + // Is this action enabled? + fl.disabled = !args.GetBool ( va("%s",name), "0" ); + fl.noPain = args.GetBool ( va("%s_nopain",name), "0" ); + fl.noTurn = args.GetBool ( va("%s_noturn",name), "1" ); + fl.overrideLegs = args.GetBool ( va("%s_overrideLegs",name), "1" ); + fl.noSimpleThink = args.GetBool ( va("%s_nosimplethink",name), "0" ); + + blendFrames = args.GetInt ( va("%s_blendFrames",name), "4" ); + failRate = SEC2MS ( args.GetInt ( va("%s_failRate",name), ".1" ) ); + + minRange = args.GetInt ( va("%s_minRange",name), "0" ); + maxRange = args.GetInt ( va("%s_maxRange",name), (_flags & AIACTIONF_ATTACK ) ? "-1" : "0" ); + minRange2d = args.GetInt ( va("%s_minRange2d",name), "0" ); + maxRange2d = args.GetInt ( va("%s_maxRange2d",name), "0" ); + + chance = args.GetFloat ( va("%s_chance",name), "1" ); + diversity = args.GetFloat ( va("%s_diversity", name ), ".5" ); + + // action state + state = args.GetString ( va("%s_state",name), (!defaultState||!*defaultState) ? "Torso_Action" : defaultState ); + + // allow for multiple animations + const char* prefix = va("%s_anim",name); + for ( kv = args.MatchPrefix ( prefix, NULL ); kv; kv = args.MatchPrefix ( prefix, kv ) ) { + if ( kv->GetValue ( ).Length ( ) ) { + anims.Append ( kv->GetValue ( ) ); + } + } + + return true; +} + +/* +================ +rvAIAction::Save +================ +*/ +void rvAIAction::Save ( idSaveGame *savefile ) const { + int i; + + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteInt ( anims.Num ( ) ); + for ( i = 0; i < anims.Num(); i ++ ) { + savefile->WriteString ( anims[i] ); + } + savefile->WriteString ( state ); + + timer.Save ( savefile ); + + savefile->WriteInt ( blendFrames ); + savefile->WriteInt ( failRate ); + + savefile->WriteFloat ( minRange ); + savefile->WriteFloat ( maxRange ); + savefile->WriteFloat ( minRange2d ); + savefile->WriteFloat ( maxRange2d ); + + savefile->WriteFloat ( chance ); + savefile->WriteFloat ( diversity ); + + savefile->WriteInt ( (int)status ); +} + +/* +================ +rvAIAction::Restore +================ +*/ +void rvAIAction::Restore ( idRestoreGame *savefile ) { + int num; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadInt ( num ); + anims.Clear ( ); + anims.SetNum ( num ); + for ( num--; num >= 0; num -- ) { + savefile->ReadString ( anims[num] ); + } + savefile->ReadString ( state ); + + timer.Restore ( savefile ); + + savefile->ReadInt ( blendFrames ); + savefile->ReadInt ( failRate ); + + savefile->ReadFloat ( minRange ); + savefile->ReadFloat ( maxRange ); + savefile->ReadFloat ( minRange2d ); + savefile->ReadFloat ( maxRange2d ); + + savefile->ReadFloat ( chance ); + savefile->ReadFloat ( diversity ); + + savefile->ReadInt ( (int&)status ); +} + +/* +=============================================================================== + + Actions + +=============================================================================== +*/ + +/* +================ +idAI::CheckAction_EvadeLeft +================ +*/ +bool idAI::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + // TODO: dont evade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_EvadeRight +================ +*/ +bool idAI::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_JumpBack +================ +*/ +bool idAI::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage ) { + return false; + } + // TODO: enemy must be in front to jump backwards + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_RangedAttack +================ +*/ +bool idAI::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_MeleeAttack +================ +*/ +bool idAI::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::CheckAction_LeapAttack +================ +*/ +bool idAI::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( enemy.range > 64.0f && !TestAnimMove ( animNum, enemy.ent ) ) { + return false; + } + // Must be looking right at the enemy to leap + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 4 ) ) { + return false; + } + return true; +} + +/* +================ +idAI::UpdateAction +================ +*/ +bool idAI::UpdateAction ( void ) { + // Update action MUST be called from the main state loop + assert ( stateThread.IsExecuting ( ) ); + + // If an action is already running then dont let another start + if ( aifl.action ) { + return false; + } + + return CheckActions ( ); +} + +/* +================ +idAI::CheckPainActions +================ +*/ +bool idAI::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + return false; + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +idAI::CheckActions +================ +*/ +bool idAI::CheckActions ( void ) { + + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // Actions are limited at a cover position to shooting and leaning + if ( IsBehindCover ( ) ) { + // Test ranged attack first + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +idAI::PerformAction +================ +*/ +void idAI::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + if ( legsAnim.Disabled() ) { + //MCG: Hmmm... I hope this doesn't break anything, but if an action happens *right* + // at the end of a trigger_anim, then the legs will be enabled (by the SetAnimState + // on the torso) with no state! The actor will then be stuck in place until + // something actually sets the legsAnim state... so let's check for disabled and + // set a default state right here...? + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + } + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + + // Main state will wait until action is finished before continuing + if ( noPain ) { + InterruptState ( "Wait_ActionNoPain" ); + } else { + InterruptState ( "Wait_Action" ); + } + + OnStartAction ( ); +} + +bool idAI::PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + // If we arent ignoring simple think then dont perform this action on a simple think frame + if ( !action->fl.noSimpleThink && aifl.simpleThink ) { + return false; + } + + // Is the action disabled? + if ( action->fl.disabled ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + + // Action timers still running? + if ( !action->timer.IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_TIMER; + return false; + } + + if ( timer && !timer->IsDone ( actionTime ) ) { + action->status = rvAIAction::STATUS_FAIL_EXTERNALTIMER; + return false; + } + + // Special code for attacks + if ( action->fl.isAttack ) { + // Attacks disabled? + if ( ai_disableAttacks.GetBool() ) { + action->status = rvAIAction::STATUS_FAIL_DISABLED; + return false; + } + // No attack actions if we have no enemy or our enemy cant be hurt + if ( !enemy.ent || enemy.ent->health <= 0 ) { + action->status = rvAIAction::STATUS_FAIL_NOENEMY; + return false; + } + } + + // Min Range check + if ( action->minRange ) { + if ( !enemy.ent || !enemy.range || enemy.range < action->minRange ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + if ( action->minRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d < action->minRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MINRANGE; + return false; + } + } + + // Max Range check + if ( action->maxRange != 0 ) { + float maxrange = action->maxRange == -1 ? combat.attackRange[1] : action->maxRange; + if ( !enemy.ent || !enemy.range || enemy.range > maxrange ) { + if ( action->fl.isMelee && GetEnemy() ) { + //FIXME: make work with gravity vector + idVec3 org = physicsObj.GetOrigin(); + const idBounds &myBounds = physicsObj.GetBounds(); + idBounds bounds; + + // expand the bounds out by our melee range + bounds[0][0] = -combat.meleeRange; + bounds[0][1] = -combat.meleeRange; + bounds[0][2] = myBounds[0][2] - 4.0f; + bounds[1][0] = combat.meleeRange; + bounds[1][1] = combat.meleeRange; + bounds[1][2] = myBounds[1][2] + 4.0f; + bounds.TranslateSelf( org ); + + idVec3 enemyOrg = GetEnemy()->GetPhysics()->GetOrigin(); + idBounds enemyBounds = GetEnemy()->GetPhysics()->GetBounds(); + enemyBounds.TranslateSelf( enemyOrg ); + + if ( !bounds.IntersectsBounds( enemyBounds ) ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } else { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + } + if ( action->maxRange2d ) { + if ( !enemy.ent || !enemy.range2d || enemy.range2d > action->maxRange2d ) { + action->status = rvAIAction::STATUS_FAIL_MAXRANGE; + return false; + } + } + + int animNum; + if ( action->anims.Num ( ) ) { + // Pick a random animation from the list + animNum = GetAnim ( ANIMCHANNEL_TORSO, action->anims[gameLocal.random.RandomInt(action->anims.Num())] ); + if ( !animNum ) { + action->status = rvAIAction::STATUS_FAIL_ANIM; + return false; + } + } else { + animNum = -1; + } + + // Random chance? + if ( action->chance < 1.0f && gameLocal.random.RandomFloat ( ) > action->chance ) { + action->status = rvAIAction::STATUS_FAIL_CHANCE; + action->timer.Clear ( actionTime ); + action->timer.Add ( 100 ); + return false; + } + + // Check the condition + if ( condition && !(this->*(condition)) ( action, animNum ) ) { + action->status = rvAIAction::STATUS_FAIL_CONDITION; + action->timer.Clear ( actionTime ); + action->timer.Add ( action->failRate ); + return false; + } + + // Disallow turning during action? + if ( action->fl.noTurn ) { + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + } + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + // Override legs for this state? + if ( action->fl.overrideLegs ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + + // When attacking scale the time by the aggression scale + float scale; + if ( action->fl.isAttack ) { + scale = 2.0f - combat.aggressiveScale; + } else { + scale = 1.0f; + } + + // Restart the action timer using the length of the animation being played + action->timer.Reset ( actionTime, action->diversity, scale ); + + // If the action gets interrupted it will be skipped, if it is then move the action timers forward + // by half of its normal delay to allow it to be performed again quicker than usual. This also allows + // other actions that may be still pending to be performed and thus cause less of a pause after taking pain. + actionSkipTime = (action->timer.GetTime ( ) + actionTime) / 2; + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + + action->status = rvAIAction::STATUS_OK; + + actionAnimNum = animNum; + + return true; +} diff --git a/src/mpgame/ai/AI_Announcements.cpp b/src/mpgame/ai/AI_Announcements.cpp new file mode 100644 index 0000000..24baca4 --- /dev/null +++ b/src/mpgame/ai/AI_Announcements.cpp @@ -0,0 +1,578 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Announcements.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +/* +===================== +idAI::AnnounceNewEnemy + +Announce that we are going to shooting at a different enemy +===================== +*/ +void idAI::AnnounceNewEnemy( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + if ( !enemy.ent || !enemy.ent->IsType( idActor::GetClassType() ) ) { + return; + } + + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY ) ) { + return; + } + + // Check to see if we should announce the new enemy as a sniper + // jshepard: Disabled by request + /* + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER ) && enemy.ent->spawnArgs.GetBool ( "sniper" ) ) { + // Should we announce the sniper as a high up sniper? + if ( (enemy.lastKnownPosition - physicsObj.GetOrigin()) * physicsObj.GetGravityNormal() >= 250.0f ) { + if ( Speak ( "lipsync_high_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + return; + } + + // Just announce the sniper + if ( Speak ( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + */ + + idActor* newEnemyAct = static_cast(enemy.ent.GetEntity()); + bool result = false; + + //first see if the new enemy is behind our buddy closest to the enemy + idActor* teammate = aiManager.NearestTeammateToPoint( this, newEnemyAct->GetPhysics()->GetOrigin(), false, 200.0f ); + if ( teammate ) { + if ( aiManager.ActorIsBehindActor( newEnemyAct, teammate ) ) { + result = Speak ( "lipsync_enemy_back", true ); + } + } + + // If we havent spoken yet then find a teammat near us that we can tell about our enemy and + // determine the best announcement to make based on their location + if ( !result ) { + teammate = aiManager.NearestTeammateToPoint( this, GetPhysics()->GetOrigin(), false, 300 ); + if ( teammate ) { + if ( newEnemyAct->GetPhysics()->GetOrigin().z-GetPhysics()->GetOrigin().z >= 250.0f ) { + result = Speak ( "lipsync_enemy_high", true ); + } + + // IF we still havent spoken and we're talking to the player, give him directional info + if ( !result && teammate->IsType( idPlayer::GetClassType() ) ) { + idPlayer* teamPlayer = static_cast(teammate); + idVec3 dir = newEnemyAct->GetPhysics()->GetOrigin()-teamPlayer->GetPhysics()->GetOrigin(); + dir.z = 0; + dir.NormalizeFast(); + idVec3 fwd = teamPlayer->viewAxis[0]; + fwd.z = 0.0f; + fwd.NormalizeFast(); + idVec3 lt = teamPlayer->viewAxis[1]; + lt.z = 0.0f; + lt.NormalizeFast(); + if ( fabs( dir * fwd ) < 0.4f ) { + // more to the side than the front + if ( dir * lt > 0 ) { + result = Speak ( "lipsync_enemy_left", true ); + } else { + result = Speak ( "lipsync_enemy_right", true ); + } + } + } + + // If we still havent spoken, just announce the enemy + if ( !result ) { + //FIXME: check right & left? + //FIXME: check for surrounded? + result = Speak ( "lipsync_enemy_default", true ); + } + } + } + + // If we spoke then set the timer + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_NEWENEMY, 2000 ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announce that we have killed our enemy +===================== +*/ +void idAI::AnnounceKill( idActor* victim ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // If already speaking or one the same team, dont announce the kill + if ( victim->team == team ) { + return; + } + //jshepard: "Watch It" sounds have been cut. Replaced with Victory. + + // Generic annoucement of enemy's death + Speak( "lipsync_victory", true ); + + + return; + +/* + // If the victim is targetting the player and was close to the player when he died + // then announce to the player to be careful + if ( victim->IsType( idAI::Type ) ) { + idAI* vicAI = static_cast(victim); + if ( vicAI && vicAI->GetEnemy() && vicAI->GetEnemy()->IsType ( idPlayer::GetClassType() ) ) { + idPlayer* vicEnemyPlayer = static_cast(vicAI->GetEnemy()); + if ( vicEnemyPlayer->team == team ) { + idVec3 diff = vicAI->GetPhysics()->GetOrigin() - vicEnemyPlayer->GetPhysics()->GetOrigin(); + if ( !vicEnemyPlayer->CheckFOV ( vicAI->GetPhysics()->GetOrigin() ) || diff.LengthSqr() < 300.0f * 300.0f ) { + Speak( "lipsync_watchit", true ); + return; + } + } + } + } + + // Chance that we say "watch it" to the closest teammate to him if the guy that died was facing the teammate + // when he died (ie, was a possible threat) + idActor* teammate = aiManager.NearestTeammateToPoint( this, victim->GetPhysics()->GetOrigin(), false, 300.0f ); + if ( teammate && victim->CheckFOV(teammate->GetPhysics()->GetOrigin()) ) { + if ( gameLocal.random.RandomInt(2) < 1 ) { + Speak( "lipsync_watchit", true ); + return; + } + } +*/ + +} + +/* +===================== +idAI::AnnounceTactical + +Announce the changing of tactical status +===================== +*/ +void idAI::AnnounceTactical( aiTactical_t newTactical ) { + bool result = false; + + // If already speaking dont bother + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL ) ) { + return; + } + + switch ( newTactical ) { + case AITACTICAL_MELEE: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_rush", true ); + } + break; +/* + case AITACTICAL_COVER: + if ( gameLocal.random.RandomFloat() < 0.2f ) { + result = Speak( "lipsync_cover", true ); + } + break; + case AITACTICAL_COVER_FLANK: + result = Speak( "lipsync_flank", true ); + break; + case AITACTICAL_COVER_ADVANCE: + result = Speak( "lipsync_moveup", true ); + break; + case AITACTICAL_COVER_RETREAT: + result = Speak( "lipsync_fallback", true ); + break; + case AITACTICAL_COVER_AMBUSH: + break; + case AITACTICAL_RANGED: + break; + case AITACTICAL_TURRET: + break; + case AITACTICAL_HIDE: + result = Speak( "lipsync_cover", true ); + break; +*/ + } + + if ( result ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_TACTICAL, 2000 ); + } +} + +/* +===================== +idAI::AnnounceSuppressed + +Announce that someone is using supressing fire on us +===================== +*/ +void idAI::AnnounceSuppressed( idActor *suppressor ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if we are already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED ) ) { + return; + } + + //FIXME: check crossfire? + if ( Speak( "lipsync_supressed", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSED, 3000 ); + } +*/ + +} + +/* +===================== +idAI::AnnounceSuppressing + +Announce that we are about to use supressing fire +===================== +*/ +void idAI::AnnounceSuppressing( void ) { + + //jshepard: Suppressed and suppressing removed by request + return; +/* + // Dont bother if already speaking + if ( !CanAnnounce ( ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING ) ) { + return; + } + + //Make the guy being shot at know this + if ( enemy.ent->IsType( idAI::Type ) ) { + idAI* enemyAI = dynamic_cast(enemy.ent.GetEntity()); + if ( enemyAI ) { + enemyAI->AnnounceSuppressed( this ); + } + } + + if ( Speak( "lipsync_supressing", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SUPPRESSING, 5000 ); + } +*/ +} + +/* +===================== +idAI::AnnounceFlinch + +Announce that an attack just missed us +===================== +*/ +void idAI::AnnounceFlinch( idEntity *attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + idActor* attackActor = dynamic_cast(attacker); + + // Friendly fire? + if ( attackActor && attackActor->team == team ) { + if ( gameLocal.random.RandomFloat() < 0.2f ) { + AnnounceFriendlyFire( attackActor ); + } + } +//jshepard: sniper announcement removed by request +/* + else if ( attackActor->spawnArgs.GetBool ( "sniper" ) ) { + //TEMP: static debounce timer + static int lastPlayed2 = 0; + if ( gameLocal.time - lastPlayed2 < 10000 ) { + return; + } + lastPlayed2 = gameLocal.time; + Speak( "lipsync_sniper", true ); + } else { +*/ + else { + if ( gameLocal.random.RandomFloat() < 0.4f ) { + return; + } + //TEMP: static debounce timer + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + Speak( "lipsync_closeone", true ); + } +} + +/* +===================== +idAI::AnnounceInjured + +Announce that we have been injured +===================== +*/ +void idAI::AnnounceInjured( void ) { + if ( !CanAnnounce ( 1.0f ) ) { + return; + } + + Speak( "lipsync_needhelp", true ); +} + +/* +===================== +idAI::AnnounceFriendlyFire + +Announce that someone on our own team is shooting us +===================== +*/ +void idAI::AnnounceFriendlyFire( idActor* attacker ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + // Early outs + if ( health <= 0 || attacker == this ) { + return; + } + + // Don't react to ff from other buddy AI + if ( !attacker->IsType( idPlayer::GetClassType() ) ) { + return; + } + + // Make sure nobody on this team has announced a tactical change recently + if ( !aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE ) ) { + return; + } + + // Must be on same team for friendly fire + if ( team != attacker->team ) { + return; + } + + // Must be close enough to hear it + if ( DistanceTo ( attacker ) > 300.0f ) { + return; + } + + //FIXME: escalate? + if ( Speak( "lipsync_checkfire", true ) ) { + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, 1000 ); + } +} + +/* +===================== +idAI::AnnounceGrenade +===================== +*/ +void idAI::AnnounceGrenade( void ) { + if ( !CanAnnounce ( 1 ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 5000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_grenade", true ); +} + +/* +===================== +idAI::AnnounceGrenadeThrow + +Announce that we are throwing a grenade +===================== +*/ +void idAI::AnnounceGrenadeThrow( void ) { + if ( !CanAnnounce ( announceRate ) ) { + return; + } + + static int lastPlayed = 0; + if ( gameLocal.time - lastPlayed < 1000 ) { + return; + } + lastPlayed = gameLocal.time; + //FIXME: escalate? + Speak( "lipsync_throw_grenade", true ); +} + + +/* +===================== +rvAIManager::AnnounceDeath + +Announce through an ally of the victem that they have died +===================== +*/ +void rvAIManager::AnnounceDeath( idAI* victim, idEntity* attacker ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire kill? + //MCG NOTE: This isn't even possible anymore... + if ( attacker->IsType ( idPlayer::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + teammate = NearestTeammateToPoint( static_cast(attacker), attacker->GetPhysics()->GetOrigin(), true, 500.0f ); + teammateAI = dynamic_cast(teammate); + + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + teammateAI->Speak( "lipsync_traitor", true ); + return; + } + } + + teammate = NearestTeammateToPoint( victim, victim->GetPhysics()->GetOrigin(), true, 1000.0f ); + teammateAI = dynamic_cast(teammate); + + //jshepard: double check to make sure we don't call out our own death! + if( teammateAI == victim ) { + //MCG: note - NearestTeammateToPoint should *never* allow this, should never happen + assert(0); + return; + } + + // Early out if we dont have a teammate or our teammate cant talk + if ( !attacker || !teammateAI || !teammateAI->CanAnnounce ( teammateAI->announceRate ) ) { + return; + } + + // Announce sniper? + // jshepard: sniper announcement removed by request +/* + if ( attacker->spawnArgs.GetBool ( "sniper" ) ) { + if ( !aiManager.CheckTeamTimer( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER ) ) { + if ( teammateAI->Speak( "lipsync_sniper", true ) ) { + aiManager.SetTeamTimer ( teammateAI->team, AITEAMTIMER_ANNOUNCE_SNIPER, 10000 ); + return; + } + } + } +*/ + // Annoucne specific death or just a generic death + const char* shortName; + if ( !victim->spawnArgs.GetString ( "npc_shortname", "", &shortName ) || !*shortName || + !teammateAI->Speak ( va("lipsync_%s_killed", shortName ), true ) ) { + teammateAI->Speak( "lipsync_mandown", true ); + } +} + +/* +===================== +idAI::AnnounceKill + +Announces an ai being killed +===================== +*/ +void rvAIManager::AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ) { + idActor* teammate; + idAI* teammateAI; + + // Friendly fire deaths are handled elsewhere + if ( attacker->IsType ( idActor::GetClassType() ) && static_cast(attacker)->team == victim->team ) { + return; + } + + // If it was an AI guy that did the killing then just let him announce it + if ( attacker->IsType( idAI::Type ) ) { + //announce the kill + static_cast(attacker)->AnnounceKill( victim ); + } else if ( attacker->IsType( idPlayer::GetClassType() ) ) { + idPlayer* attackerPlayer = static_cast(attacker); + + // If the guy who died is an AI guy who was targetting a buddy nearby, have him say "thanks!" + // jshepard: these are cut unless we can get some tighter "thanks" quotes +/* if ( victim->IsType( idAI::Type ) ) { + idAI* victimAI = static_cast(victim); + //if the victim's enemy is a teammate of mine, make the teammate say "thanks!" + if ( victimAI && victimAI->GetEnemy() ) { + idAI* victimEnemyAI = dynamic_cast(victimAI->GetEnemy()); + + // See if the enemy of the guy who died is a teammate and wants to say thanks + if ( victimEnemyAI && victimEnemyAI->CanAnnounce ( ) && victimEnemyAI->team == attackerPlayer->team ) { + float distSqr = (victimAI->GetPhysics()->GetOrigin() - victimEnemyAI->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr < Square ( 300.0f ) ) { + //teammate was fighting him or close to him + victimEnemyAI->Speak( "lipsync_thanks", true ); + return; + } + } + } + } */ + + // Grab a nearby teammate of the player and say "nice shot!" + teammate = NearestTeammateToPoint( attackerPlayer, attacker->GetPhysics()->GetOrigin(), true, 500.0f, true ); + teammateAI = dynamic_cast(teammate); + if ( teammateAI && teammateAI->CanAnnounce( teammateAI->announceRate ) ) { + idProjectile* proj = dynamic_cast(inflictor); + //killed them with a grenade? + if ( proj && proj->spawnArgs.GetBool( "thrown" ) ) { + teammateAI->Speak( "lipsync_nicetoss", true ); + // Or just shot them? + } else { + teammateAI->Speak( "lipsync_niceshot", true ); + } + return; + } + } +} + diff --git a/src/mpgame/ai/AI_Debug.cpp b/src/mpgame/ai/AI_Debug.cpp new file mode 100644 index 0000000..2a89325 --- /dev/null +++ b/src/mpgame/ai/AI_Debug.cpp @@ -0,0 +1,354 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Debug.cpp + +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" + +const char *aiMoveCommandString[ NUM_MOVE_COMMANDS ] = { + "MOVE_NONE", + "MOVE_FACE_ENEMY", + "MOVE_FACE_ENTITY", + "MOVE_TO_ENEMY", + "MOVE_TO_ENTITY", + "MOVE_TO_ATTACK", + "MOVE_TO_HELPER", + "MOVE_TO_TETHER", + "MOVE_TO_COVER", + "MOVE_TO_HIDE", + "MOVE_TO_POSITION", + "MOVE_OUT_OF_RANGE", + "MOVE_SLIDE_TO_POSITION", + "MOVE_WANDER" +}; + +const char* aiMoveStatusString[ NUM_MOVE_STATUS ] = { + "MOVE_STATUS_DONE", + "MOVE_STATUS_MOVING", + "MOVE_STATUS_WAITING", + "MOVE_STATUS_DEST_NOT_FOUND", + "MOVE_STATUS_DEST_UNREACHABLE", + "MOVE_STATUS_BLOCKED_BY_WALL", + "MOVE_STATUS_BLOCKED_BY_OBJECT", + "MOVE_STATUS_BLOCKED_BY_ENEMY", + "MOVE_STATUS_BLOCKED_BY_MONSTER", + "MOVE_STATUS_BLOCKED_BY_PLAYER", + "MOVE_STATUS_DISABLED" +}; + +const char* aiMoveDirectionString [ MOVEDIR_MAX ] = { + "MOVEDIR_FORWARD", + "MOVEDIR_BACKWARD", + "MOVEDIR_LEFT", + "MOVEDIR_RIGHT" +}; + +const char* aiTacticalString [ AITACTICAL_MAX ] = { + "AITACTICAL_NONE", + "AITACTICAL_MELEE", + "AITACTICAL_MOVE_FOLLOW", + "AITACTICAL_MOVE_TETHER", + "AITACTICAL_MOVE_PLAYERPUSH", + "AITACTICAL_COVER", + "AITACTICAL_COVER_FLANK", + "AITACTICAL_COVER_ADVANCE", + "AITACTICAL_COVER_RETREAT", + "AITACTICAL_COVER_AMBUSH", + "AITACTICAL_RANGED", + "AITACTICAL_TURRET", + "AITACTICAL_HIDE", + "AITACTICAL_PASSIVE", +}; + +const char* aiFocusString [ AIFOCUS_MAX ] = { + "AIFOCUS_NONE", + "AIFOCUS_LEADER", + "AIFOCUS_TARGET", + "AIFOCUS_TALK", + "AIFOCUS_PLAYER", + "AIFOCUS_ENEMY", + "AIFOCUS_COVER", + "AIFOCUS_COVERLOOK", + "AIFOCUS_HELPER", + "AIFOCUS_TETHER", +}; + +const char* aiActionStatusString [ rvAIAction::STATUS_MAX ] = { + "Unused", + "OK", + "Disabled", + "Failed: timer", + "Failed: external timer", + "Failed: within min range", + "Failed: out of max range", + "Failed: random chance", + "Failed: bad animation", + "Failed: condition", + "Failed: no enemy", +}; + +/* +===================== +idAI::GetDebugInfo +===================== +*/ +void idAI::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idActor::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "aifl.damage", aifl.damage ? "true" : "false", userData ); + proc ( "idAI", "aifl.undying", aifl.undying ? "true" : "false", userData ); + proc ( "idAI", "aifl.pain", aifl.pain ? "true" : "false", userData ); + proc ( "idAI", "aifl.dead", aifl.dead ? "true" : "false", userData ); + proc ( "idAI", "aifl.activated", aifl.activated ? "true" : "false", userData ); + proc ( "idAI", "aifl.jump", aifl.jump ? "true" : "false", userData ); + proc ( "idAI", "aifl.hitEnemy", aifl.hitEnemy ? "true" : "false", userData ); + proc ( "idAI", "aifl.pushed", aifl.pushed ? "true" : "false", userData ); + proc ( "idAI", "aifl.lookAtPlayer", aifl.lookAtPlayer ? "true" : "false", userData ); + proc ( "idAI", "aifl.disableAttacks", aifl.disableAttacks ? "true" : "false", userData ); + proc ( "idAI", "aifl.simpleThink", aifl.simpleThink ? "true" : "false", userData ); + proc ( "idAI", "aifl.action", aifl.action ? "true" : "false", userData ); + proc ( "idAI", "aifl.scripted", aifl.scripted? "true" : "false", userData ); + + proc ( "idAI", "leader", leader.GetEntity() ? leader.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "move.followRangeMin", va("%g",move.followRange[0]), userData ); + proc ( "idAI", "move.followRangeMax", va("%g",move.followRange[1]), userData ); + proc ( "idAI", "combat.fl.aware", combat.fl.aware ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.ignoreEnemies", combat.fl.ignoreEnemies ? "true" : "false", userData ); + + proc ( "idAI", "enemy", enemy.ent ? enemy.ent->GetName() : "", userData ); + proc ( "idAI", "enemy.fl.inFov", enemy.fl.inFov ? "true" : "false", userData ); + proc ( "idAI", "enemy.fl.visible", enemy.fl.visible ? "true" : "false", userData ); + proc ( "idAI", "combat.fl.seenEnemyDirectly",combat.fl.seenEnemyDirectly ? "true" : "false", userData ); + proc ( "idAI", "enemy.lastVisibleTime", va("%d",enemy.lastVisibleTime), userData ); + proc ( "idAI", "combat.maxLostVisTime", va("%d",combat.maxLostVisTime), userData ); + proc ( "idAI", "enemy.range", va("%g",enemy.range), userData ); + proc ( "idAI", "enemy.ranged2d", va("%g",enemy.range2d), userData ); + proc ( "idAI", "lastAttackTime", va("%d",lastAttackTime), userData ); + + proc ( "idAI", "move.fl.done", move.fl.done ? "true" : "false", userData ); + proc ( "idAI", "move.fl.disabled", move.fl.disabled ? "true" : "false", userData ); + proc ( "idAI", "move.fl.onGround", move.fl.onGround ? "true" : "false", userData ); + proc ( "idAI", "move.fl.blocked", move.fl.blocked ? "true" : "false", userData ); + proc ( "idAI", "move.fl.obstacleInPath", move.fl.obstacleInPath ? "true" : "false", userData ); + proc ( "idAI", "move.fl.goalUnreachable", move.fl.goalUnreachable ? "true" : "false", userData ); + proc ( "idAI", "move.fl.moving", move.fl.moving ? "true" : "false", userData ); + proc ( "idAI", "move.command", aiMoveCommandString[move.moveCommand], userData ); + proc ( "idAI", "move.status", aiMoveStatusString[move.moveStatus], userData ); + proc ( "idAI", "move.fl.allowDirectional", move.fl.allowDirectional ? "true" : "false", userData ); + proc ( "idAI", "move.direction_ideal", aiMoveDirectionString[move.idealDirection ], userData ); + proc ( "idAI", "move.direction_current", aiMoveDirectionString[move.currentDirection ], userData ); + proc ( "idAI", "move.yaw_ideal", va("%d",(int)move.ideal_yaw), userData ); + proc ( "idAI", "move.yaw_current", va("%d",(int)move.current_yaw), userData ); + proc ( "idAI", "move.fly_roll", va("%g", move.fly_roll ), userData ); + proc ( "idAI", "move.fly_pitch", va("%g", move.fly_pitch ), userData ); + + proc ( "idAI", "tether", tether!=NULL?tether->GetName():"", userData ); + proc ( "idAI", "IsTethered()", IsTethered()?"true":"false", userData ); + + proc ( "idAI", "lookTarget", lookTarget.GetEntity() ? lookTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "talkTarget", talkTarget.GetEntity() ? talkTarget.GetEntity()->GetName() : "", userData ); + proc ( "idAI", "focusType", aiFocusString[focusType], userData ); + proc ( "idAI", "look.yaw", va("%g", lookAng.yaw ), userData ); + proc ( "idAI", "look.pitch", va("%g", lookAng.pitch ), userData ); + + proc ( "idAI", "combat.attackRangeMin", va("%g",combat.attackRange[0]), userData ); + proc ( "idAI", "combat.attackRangeMax", va("%g",combat.attackRange[1]), userData ); + proc ( "idAI", "combat.tacticalCurrent", aiTacticalString[combat.tacticalCurrent], userData ); + + proc ( "idAI", "action_rangedAttack", aiActionStatusString[actionRangedAttack.status], userData ); + proc ( "idAI", "action_meleeAttack", aiActionStatusString[actionMeleeAttack.status], userData ); + proc ( "idAI", "action_leapAttack", aiActionStatusString[actionLeapAttack.status], userData ); + proc ( "idAI", "action_jumpBack", aiActionStatusString[actionJumpBack.status], userData ); + proc ( "idAI", "action_evadeLeft", aiActionStatusString[actionEvadeLeft.status], userData ); + proc ( "idAI", "action_evadeRight", aiActionStatusString[actionEvadeRight.status], userData ); + + proc ( "idAI", "npc_name", spawnArgs.FindKey("npc_name")?common->GetLocalizedString(spawnArgs.GetString( "npc_name", "")):"", userData ); +} + +/* +===================== +idAI::DrawRoute +===================== +*/ +void idAI::DrawRoute( void ) const { + if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY ) { + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } else { + aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest ); + } + } +} + +/* +===================== +idAI::DrawTactical + +Draw the debug tactical information +===================== +*/ +void idAI::DrawTactical ( void ) { + if ( !DebugFilter(ai_debugTactical) ) { + return; + } + + // Colors For Lines In This File + //------------------------------- + // Majenta = Move Dest + // Green / Red = Enemy (On Player Side, On Enemy Side) + // Pink = Tether Radius + // Grey = FOV + + // Colors From AAST Draw Debug Info + //---------------------------------- + // Blue = Reserved Feature + // Orange = Near Feature + // Yellow = Look Feature + + + // Get Origin + //------------ + idVec3 origin = GetPhysics()->GetOrigin(); + origin += (GetPhysics()->GetGravityNormal() * 5.0f); + + // Draw FOV (Must be close to player and on enemy team) + //------------------------------------------------------ + if (team && DistanceTo(gameLocal.GetLocalPlayer())<500.0f) { + if (!combat.fl.aware) { + gameRenderWorld->DebugFOV(colorLtGrey, origin, viewAxis[0], fovDot, 300.0f, fovCloseDot, fovCloseRange, 0.3f, 10); + } else if (!combat.fl.seenEnemyDirectly) { + gameRenderWorld->DebugFOV(colorMdGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, 0.3f, 10); + } else { + float alpha = (1.0f - ((float)(gameLocal.GetTime() - enemy.lastVisibleTime) / (float)combat.maxLostVisTime)) * 0.35f; + gameRenderWorld->DebugFOV(colorDkGrey, origin, viewAxis[0], fovDot, 300.0f, -1.0f, combat.awareRange, Max(alpha, 0.1f), 10); + } + } + + // Move Over Head + //---------------- + origin -= GetPhysics()->GetGravityNormal() * (GetPhysics()->GetBounds()[ 1 ].z - GetPhysics()->GetBounds()[ 0 ].z); + + // Draw Enemy Related Info + //------------------------- + if ( enemy.ent ) { + // Draw Lost Time + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > combat.maxLostVisTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime && (gameLocal.GetTime() - enemy.lastVisibleTime) > ( combat.maxLostVisTime/2 ) ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } else if ( enemy.lastVisibleTime ) { + gameRenderWorld->DrawText ( va("losttime: %d", (gameLocal.GetTime() - enemy.lastVisibleTime)), origin, 0.12f, colorGreen, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Enemy Chest position for enemy lines + idVec3 enemyEyePosition; + idVec3 offset; + if ( enemy.ent->IsType ( idActor::GetClassType() ) ){ + enemyEyePosition = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + enemyEyePosition = enemy.ent->GetPhysics()->GetOrigin(); + } + + offset = idVec3(0,0,team*2.0f); + + gameRenderWorld->DebugLine ( aiTeamColor[team], GetEyePosition() + offset, enemy.lastVisibleFromEyePosition + offset, 4.0f ); + if ( enemyEyePosition != enemy.lastVisibleEyePosition ) { + gameRenderWorld->DebugLine ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemy.lastVisibleEyePosition + offset, 4.0f ); + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } else { + gameRenderWorld->DebugArrow ( aiTeamColor[team], enemy.lastVisibleFromEyePosition + offset, enemyEyePosition + offset, 4.0f ); + } + } + + // Ivalid Cover Timer + //-------------------- + if ( IsBehindCover() && combat.coverValidTime && combat.coverValidTime < gameLocal.time) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("invalid cover time: %d", (gameLocal.GetTime() - combat.coverValidTime)), origin, 0.12f, colorRed, gameLocal.GetLocalPlayer()->viewAxis ); + } + + + // Vis Crouch + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visCrouchHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Vis Stand + gameRenderWorld->DebugArrow ( colorBrown, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight, + GetPhysics()->GetOrigin ( ) - GetPhysics()->GetGravityNormal() * combat.visStandHeight + viewAxis[0] * 16.0f, 10.0f ); + + // Aggression + if ( combat.aggressiveScale != 1.0f ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("aggressive: %g", combat.aggressiveScale), origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw anim prefix + //----------------------- + if ( animPrefix.Length ( ) ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( va("anim: %s", animPrefix.c_str()), origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw focus type + //----------------------- + if ( focusType != AIFOCUS_NONE ) { + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiFocusString[focusType], origin, 0.12f, colorCyan, gameLocal.GetLocalPlayer()->viewAxis ); + } + + // Draw Tactical Current + //----------------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText ( aiTacticalString[combat.tacticalCurrent], origin, 0.12f, colorYellow, gameLocal.GetLocalPlayer()->viewAxis ); + + // Draw My Name + //-------------- + origin -= (GetPhysics()->GetGravityNormal() * 5.0f); + gameRenderWorld->DrawText(name, origin, 0.12f, colorWhite, gameLocal.GetLocalPlayer()->viewAxis); + + // Draw the tethered radius + if ( IsTethered ( ) ) { + tether->DebugDraw ( ); + } + + // Draw Move Destination + if ( !move.fl.done ) { + gameRenderWorld->DebugArrow ( colorMagenta, GetPhysics()->GetOrigin(), move.moveDest, 5 ); + } +} diff --git a/src/mpgame/ai/AI_Manager.cpp b/src/mpgame/ai/AI_Manager.cpp new file mode 100644 index 0000000..9861139 --- /dev/null +++ b/src/mpgame/ai/AI_Manager.cpp @@ -0,0 +1,790 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" + +idVec4 aiTeamColor[AITEAM_NUM] = { idVec4 ( 0.0f, 1.0f, 0.0f, 1.0f ), + idVec4 ( 1.0f, 0.0f, 0.0f, 1.0f ) }; + +rvAIManager aiManager; + +/* +================ +rvAIManager::rvAIManager +================ +*/ +rvAIManager::rvAIManager ( void ) { + Clear(); +} + +/* +================ +rvAIManager::IsActive +================ +*/ +bool rvAIManager::IsActive( void ){ + if( gameLocal.isMultiplayer ){ + return false; + } + + return true; +} + +/* +================ +rvAIManager::RunFrame +================ +*/ +void rvAIManager::RunFrame ( void ){ + if ( !IsActive() ) { + return; + } + + // Pop the top simple think off the list + if ( !simpleThink.IsListEmpty() ) { + simpleThink.Next()->simpleThinkNode.Remove ( ); + } + + // Display current ai speeds + if ( ai_speeds.GetBool ( ) && thinkCount > 0 ) { + gameLocal.Printf ( "ai:%6i n:%2i s:%2i all:%5.2f t:%5.2f e:%5.2f m:%5.f\n", + gameLocal.framenum, thinkCount, simpleThinkCount, + timerThink.Milliseconds(), + timerTactical.Milliseconds(), + timerFindEnemy.Milliseconds(), + timerMove.Milliseconds() ); + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + int i; + int j; + // look for reaches that are no longer blocked + for ( i=0; ib.time) { + break; + } + + idEntityPtr blockent = b.blockers[j]; + if (!blockent.IsValid() || blockent->DistanceTo(b.positions[j])>10.0f) { + break; + } + if (blockent->IsType(idAI::GetClassType())) { + idAI* blockentAI = static_cast(blockent.GetEntity()); + if (blockentAI->move.moveDest.Dist2XY(b.positions[j])>100.0f) { + break; + } + } + } + + // if any one of the blockers moved or no longer exists, re-enable this reach + if ( !b.blockers.Num() || jSetReachabilityState(b.reach, true); + blockedReaches.RemoveIndex(i); + break; + } + + // DEBUG GRAPHICS + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, b.reach->start, b.reach->start + idVec3(0,0,40.0f), gameLocal.msec ); + for ( j=0; jDebugArrow( colorRed, b.blockers[j]->GetPhysics()->GetOrigin(), b.reach->start, 8, gameLocal.msec ); + } + } + } + } + // RAVEN END + + timerThink.Clear ( ); + timerTactical.Clear ( ); + timerFindEnemy.Clear ( ); + timerMove.Clear ( ); + + gameDebug.SetStatInt( "ai_thinkCount", thinkCount ); + + thinkCount = 0; + simpleThinkCount = 0; + + // Draw any debugging information + DebugDraw ( ); +} + +/* +================ +rvAIManager::Clear +================ +*/ +void rvAIManager::Clear( void ) { + thinkCount = 0; + simpleThinkCount = 0; + timerThink.Clear ( ); + timerFindEnemy.Clear ( ); + timerTactical.Clear ( ); + timerMove.Clear ( ); + + blockedReaches.Clear ( ); + helpers.Clear ( ); + simpleThink.Clear ( ); + avoids.Clear ( ); + + memset ( &teamTimers, 0, sizeof(teamTimers) ); +} + +/* +================ +rvAIManager::UnMarkAllReachBlocked +================ +*/ +void rvAIManager::UnMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, true); + } +} + +/* +================ +rvAIManager::ReMarkAllReachBlocked +================ +*/ +void rvAIManager::ReMarkAllReachBlocked( void ) +{ + for ( int i=0; iSetReachabilityState(blockedReaches[i].reach, false); + } +} + +/* +================ +rvAIManager::MarkReachBlocked +================ +*/ +void rvAIManager::MarkReachBlocked(idAAS* aas, idReachability* reach, const idList& blockers) { + + // only if not already blocked + if (!(reach->travelType&TFL_INVALID)) { + aiBlocked_t blocked; + blocked.aas = aas; + blocked.reach = reach; + blocked.time = gameLocal.GetTime() + 5000.0f; + + for (int i=0; iGetPhysics() && (blockers[i]->GetPhysics()->IsAtRest() || blockers[i]->GetPhysics()->GetLinearVelocity().LengthSqr()<2500.0f)) { + blocked.blockers.Append(blockers[i]); + blocked.positions.Append(blockers[i]->GetPhysics()->GetOrigin()); + } + } + if (blocked.blockers.Num()) { + aas->SetReachabilityState(reach, false); + blockedReaches.Append(blocked); + } + } + +} + +/* +================ +rvAIManager::ReactToPlayerAttack +================ +*/ +void rvAIManager::ReactToPlayerAttack ( idPlayer* player, const idVec3 &origin, const idVec3 &dir ){ + idActor* actor; + float expandSize; + + // Check all enemies and see if they need to react + for ( actor = GetEnemyTeam ( (aiTeam_t)player->team ); actor; actor = actor->teamNode.Next() ) { + // Skip non ai entities + if ( !actor->IsType ( idAI::Type ) ) { + continue; + } + + idAI *curAI = static_cast(actor); + + // See if it will pass through an expanded bounding box + expandSize = curAI->spawnArgs.GetFloat( "shotAtReactionRange", "16" ); + if ( !curAI->GetPhysics()->GetAbsBounds ( ).Expand(expandSize).LineIntersection ( origin, origin + dir * curAI->combat.visRange ) ) { + continue; + } + + curAI->ReactToShotAt ( player, origin, dir ); + } +} + +/* +================ +rvAIManager::Save +================ +*/ +void rvAIManager::Save( idSaveGame *savefile ) const { + int i; + int j; + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->WriteInt( teams[i].Num() ); + for( actor = teams[i].Next(); actor != NULL; actor = actor->teamNode.Next() ) { + savefile->WriteObject( actor ); + } + } + + // Write out team timers + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->WriteInt ( teamTimers[i][j] ); + } + } + + // Write out team timers + savefile->WriteInt ( avoids.Num ( ) ); + for ( i = 0; i < avoids.Num ( ); i ++ ) { + savefile->WriteVec3 ( avoids[i].origin ); + savefile->WriteFloat ( avoids[i].radius ); + savefile->WriteInt ( avoids[i].team ); + } +} + +/* +================ +rvAIManager::Restore +================ +*/ +void rvAIManager::Restore( idRestoreGame *savefile ){ + int i; + int j; + + Clear ( ); + + // Write out team list + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + savefile->ReadInt( j ); + for ( ; j > 0; j -- ) { + savefile->ReadObject ( reinterpret_cast( actor ) ); + if ( actor ) { + actor->teamNode.AddToEnd ( teams[i] ); + } + } + } + + // Read team timers + memset ( teamTimers, 0, sizeof(teamTimers) ); + for ( i = 0; i < AITEAM_NUM; i ++ ) { + for ( j = 0; j < AITEAMTIMER_MAX; j ++ ) { + savefile->ReadInt ( teamTimers[i][j] ); + } + } + + // Read in team timers + savefile->ReadInt ( j ); + avoids.SetNum ( j ); + for ( i = 0; i < j; i ++ ) { + savefile->ReadVec3 ( avoids[i].origin ); + savefile->ReadFloat ( avoids[i].radius ); + savefile->ReadInt ( avoids[i].team ); + } +} + +/* +===================== +rvAIManager::AddTeammate +===================== +*/ +void rvAIManager::AddTeammate ( idActor* actor ) { + // If its already in a team least then ignore the call. + // NOTE: You have to call removeteammate before addteammate to switch the + // actor from one team to another + if ( actor->teamNode.InList ( ) ) { + return; + } + actor->teamNode.AddToEnd ( teams[actor->team] ); +} + +/* +===================== +rvAIManager::RemoveTeammate +===================== +*/ +void rvAIManager::RemoveTeammate ( idActor* actor ) { + actor->teamNode.Remove ( ); +} + +/* +===================== +rvAIManager::IsSimpleThink + +Determines whether or not the given AI entity should be simple thinking this frame +===================== +*/ +bool rvAIManager::IsSimpleThink ( idAI* ai ) { + + // no simple think if simple thinking is disabled or we are the head node + if ( ai_disableSimpleThink.GetBool() ) { + return false; + } + + // Add to simple think list + if ( !ai->simpleThinkNode.InList ( ) ) { + ai->simpleThinkNode.AddToEnd ( simpleThink ); + } + + // If head of list then its a complex think + if ( ai->simpleThinkNode.Prev() == NULL ) { + return false; + } + + return true; +} + +/* +================ +rvAIManager::GetEnemyTeam +================ +*/ +idActor* rvAIManager::GetEnemyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_STROGG].Next(); + case AITEAM_STROGG: + return teams[AITEAM_MARINE].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::GetAllyTeam +================ +*/ +idActor* rvAIManager::GetAllyTeam ( aiTeam_t team ) { + switch ( team ) { + case AITEAM_MARINE: + return teams[AITEAM_MARINE].Next(); + case AITEAM_STROGG: + return teams[AITEAM_STROGG].Next(); + } + return NULL; +} + +/* +================ +rvAIManager::ValidateDestination + +Validate whether or not the destinations is a destination +================ +*/ +bool rvAIManager::ValidateDestination ( idAI* ai, const idVec3& dest, bool skipCurrent, idActor* skipActor ) const { + int i; + idBounds bounds; + idAI* ignore; + + ignore = (!skipCurrent && ai && !ai->SkipCurrentDestination ( )) ? ai : NULL; + + bounds = ai->GetPhysics()->GetBounds ( ); + bounds.TranslateSelf ( dest ); + bounds.ExpandSelf ( 16.0f ); + + // All teams and all actors on those teams + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + // Ignored? + if ( actor == ignore || actor == skipActor || actor->IsHidden ( ) ) { + continue; + } + + // If the actor is AI that is moving we should check their destination rather than where they are now + if ( actor->IsType ( idAI::Type ) ) { + idAI* aiactor = static_cast(actor); + if ( aiactor->move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( bounds.IntersectsBounds ( aiactor->GetPhysics()->GetBounds().Translate ( aiactor->move.moveDest ) ) ) { + return false; + } + continue; + } + } + + // Does the destination overlap this actor? + if ( bounds.IntersectsBounds ( actor->GetPhysics()->GetAbsBounds ( ) ) ) { + return false; + } + } + } + + // Destinations inside an avoid area are invalid + for ( i = avoids.Num() - 1; i >= 0; i -- ) { + const aiAvoid_t& avoid = avoids[i]; + // Skip all avoids that arent meant for this team + if ( avoid.team != -1 && ai->team != avoid.team ) { + continue; + } + // Skip if within range of the avoid + if ( (avoid.origin - dest).LengthSqr ( ) < Square ( avoid.radius ) ) { + if ( ai_debugMove.GetBool() || ai_debugTactical.GetBool() ) { + gameRenderWorld->DebugCircle( colorRed, avoid.origin, idVec3(0,0,1), avoid.radius, 25 ); + } + return false; + } + } + + return true; +} + +/* +================ +rvAIManager::NearestTeammateToPoint + +Returns the teammate closest to the given point with the given parameters +================ +*/ +idActor* rvAIManager::NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer, float maxRange, bool checkFOV, bool checkLOS ) { + idActor* actor = NULL; + idActor* closestActor = NULL; + float distSqr; + float bestDistSqr; + + closestActor = 0x0; + bestDistSqr = Square ( maxRange ); + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Player? + if ( nonPlayer && actor->IsType( idPlayer::GetClassType() ) ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (point - actor->GetPhysics()->GetOrigin()).LengthSqr(); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV && !actor->CheckFOV( point ) ) { + continue; + } + //actor has clear LOS to point? + if ( checkLOS && !actor->CanSeeFrom ( from->GetEyePosition ( ), point, false ) ) { + continue; + } + // New best actor + bestDistSqr = distSqr; + closestActor = actor; + } + return closestActor; +} + +/* +================ +rvAIManager::NearestTeammateEnemy + +Returns the closest enemy of an ally. +================ +*/ +idEntity* rvAIManager::NearestTeammateEnemy( idActor* from, float maxRange, bool checkFOV, bool checkLOS, idActor** closestAllyWithEnemy ) { + idActor* actor; + idAI* allyAI; + idEntity* allyEnemy; + idEntity* closestAllyEnemy; + float distSqr; + float bestDistSqr; + + bestDistSqr = Square ( maxRange ); + closestAllyEnemy = NULL; + + // Optionally return the ally whos enemy it is + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = NULL; + } + + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)from->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Self? + if ( actor == from ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + + allyAI = dynamic_cast(actor); + if ( !allyAI ) { + //player? + allyEnemy = actor->EnemyWithMostHealth(); + if ( !allyEnemy ) { + continue; + } + if ( allyEnemy->health <= 0 ) { + continue; + } + } else { + allyEnemy = allyAI->GetEnemy(); + //has enemy? + if ( !allyEnemy ) { + continue; + } + //still alive? + if ( allyAI->enemy.fl.dead ) { + continue; + } + } + + //Calc Range and check to see if closer before doing any complicated checks + distSqr = (actor->GetPhysics()->GetOrigin() - from->GetPhysics()->GetOrigin()).LengthSqr ( ); + if ( distSqr >= bestDistSqr ) { + continue; + } + + //point in actor's in FOV? + if ( checkFOV ) { + if ( !from->CheckFOV( actor->GetPhysics()->GetOrigin() ) ) { + continue; + } + } + //actor has clear LOS to point? + if ( checkLOS ) { + if ( !from->CanSee( actor, false ) ) { + continue; + } + } + + bestDistSqr = distSqr; + closestAllyEnemy = allyEnemy; + + if ( closestAllyWithEnemy ) { + *closestAllyWithEnemy = actor; + } + } + return closestAllyEnemy; +} + +/* +================ +rvAIManager::LocalTeamHasEnemies + +Returns true if nearby members of my team have any enemies or if any nearby enemies have enemies that are nearby members of my team +================ +*/ +bool rvAIManager::LocalTeamHasEnemies ( idAI* self, float maxBuddyRange, float maxEnemyRange, bool checkPVS ) { + idActor* actor = NULL; + pvsHandle_t pvs; + + if ( !self ) { + return false; + } + + // Iterate through all teammates + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)self->team ); actor; actor = actor->teamNode.Next() ) { + //Hidden? + if ( actor->fl.hidden ) { + continue; + } + //Dead? + if ( actor->health <= 0 ) { + continue; + } + if ( actor->IsType( idAI::GetClassType() ) ) { + //Has an enemy? + if ( !((idAI*)actor)->GetEnemy() ) { + continue; + } + //Has an enemy + if ( checkPVS ) { + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( pvs.i > 0 + && pvs.i < MAX_CURRENT_PVS + && !gameLocal.pvs.InCurrentPVS( pvs, ((idAI*)actor)->GetEnemy()->GetPVSAreas(), ((idAI*)actor)->GetEnemy()->GetNumPVSAreas() ) ) { + gameLocal.pvs.FreeCurrentPVS( pvs ); + continue; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + + } + //close enough? + if ( actor != self && self->DistanceTo( actor->GetPhysics()->GetOrigin() ) > maxBuddyRange ) { + continue; + } + //Anyone mad at him? + if ( !actor->ClosestEnemyToPoint( actor->GetPhysics()->GetOrigin(), maxEnemyRange, true, checkPVS ) ) { + continue; + } + return true; + } + return false; +} + +/* +================ +rvAIManager::ActorIsBehindActor + +Returns true if the given 'ambuser' is behind the given 'victim' +================ +*/ +bool rvAIManager::ActorIsBehindActor( idActor* ambusher, idActor* victim ) { + idVec3 dir2Ambusher = ambusher->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin(); + float dist = dir2Ambusher.Normalize(); + if ( DotProduct(dir2Ambusher, victim->viewAxis[0] ) < 0 && dist < 200.0f ) { + return true; + } + return false; +} + +/* +=============================================================================== + + rvAIManager - Helpers + +=============================================================================== +*/ + +/* +===================== +rvAIManager::RegisterHelper +===================== +*/ +void rvAIManager::RegisterHelper ( rvAIHelper* helper ) { + helper->helperNode.AddToEnd ( helpers ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::UnregisterHelper +===================== +*/ +void rvAIManager::UnregisterHelper ( rvAIHelper* helper ) { + helper->helperNode.Remove ( ); + UpdateHelpers ( ); +} + +/* +===================== +rvAIManager::FindClosestHelper +===================== +*/ +rvAIHelper* rvAIManager::FindClosestHelper ( const idVec3& origin ) { + rvAIHelper* helper; + rvAIHelper* bestHelper; + float bestDist; + float dist; + + bestDist = idMath::INFINITY; + bestHelper = NULL; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + dist = (origin - helper->GetPhysics()->GetOrigin()).LengthFast ( ); + if ( dist < bestDist ) { + bestDist = dist; + bestHelper = helper; + } + } + return bestHelper; +} + +/* +================ +rvAIManager::UpdateHelpers +================ +*/ +void rvAIManager::UpdateHelpers ( void ) { + int i; + + for ( i = 0; i < AITEAM_NUM; i ++ ) { + idActor* actor; + for ( actor = teams[i].Next(); actor; actor = actor->teamNode.Next() ) { + if ( actor->IsHidden ( ) || !actor->IsType ( idAI::Type ) ) { + continue; + } + static_cast(actor)->UpdateHelper ( ); + } + } +} + +/* +=============================================================================== + + rvAIManager - Debugging + +=============================================================================== +*/ + +/* +================ +rvAIManager::DebugDraw +================ +*/ +void rvAIManager::DebugDraw ( void ) { + // Draw helpers? + if ( ai_debugHelpers.GetBool ( ) ) { + DebugDrawHelpers ( ); + + int i; + for ( i = 0; i < avoids.Num(); i ++ ) { + const aiAvoid_t& avoid = avoids[i]; + gameRenderWorld->DebugCircle ( colorOrange, avoid.origin, idVec3(0,0,1), avoid.radius, 10, 0 ); + } + } + + +} + +/* +================ +rvAIManager::DebugDrawHelpers +================ +*/ +void rvAIManager::DebugDrawHelpers ( void ) { + rvAIHelper* helper; + for ( helper = helpers.Next(); helper; helper = helper->helperNode.Next( ) ) { + helper->DrawDebugEntityInfo ( ); + } +} diff --git a/src/mpgame/ai/AI_Manager.h b/src/mpgame/ai/AI_Manager.h new file mode 100644 index 0000000..28fb41b --- /dev/null +++ b/src/mpgame/ai/AI_Manager.h @@ -0,0 +1,260 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AI_MANAGER_H__ +#define __AI_MANAGER_H__ + +typedef enum { + AITEAM_MARINE, + AITEAM_STROGG, + AITEAM_NUM +} aiTeam_t; + +typedef enum { + AITEAMTIMER_ANNOUNCE_TACTICAL, // Tactical change + AITEAMTIMER_ANNOUNCE_SUPPRESSING, + AITEAMTIMER_ANNOUNCE_SUPPRESSED, + AITEAMTIMER_ANNOUNCE_FRIENDLYFIRE, // Shot by a teammate + AITEAMTIMER_ANNOUNCE_ENEMYSTEATH, + AITEAMTIMER_ANNOUNCE_NEWENEMY, // New enemy was aquired + AITEAMTIMER_ANNOUNCE_SNIPER, // Sniper sighted + AITEAMTIMER_ANNOUNCE_CANIHELPYOU, // Player standing in front of a friendly too long + AITEAMTIMER_ANNOUNCE_SIGHT, // First time seeing an enemy + AITEAMTIMER_ACTION_RELAX, // Play relax animation + AITEAMTIMER_ACTION_PEEK, // Play peek animation + AITEAMTIMER_ACTION_TALK, // Able to talk to another person yet? + AITEAMTIMER_MAX +} aiTeamTimer_t; + +extern idVec4 aiTeamColor[AITEAM_NUM]; + +/* +===================== +blockedReach_t +===================== +*/ +// cdr: Alternate Routes Bug +typedef struct aiBlocked_s { + idAAS* aas; + idReachability* reach; + int time; + idList< idEntityPtr > blockers; + idList< idVec3 > positions; +} aiBlocked_t; + +/* +===================== +aiAvoid_t +===================== +*/ +typedef struct aiAvoid_s { + idVec3 origin; + float radius; + int team; +} aiAvoid_t; + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +class rvAIHelper : public idEntity { +public: + CLASS_PROTOTYPE( rvAIHelper ); + + rvAIHelper ( void ); + + idLinkList helperNode; + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + + idVec3 GetDirection ( const idAI* ent ) const; + +protected: + + virtual void OnActivate ( bool active ); + +private: + + void Event_Activate ( idEntity *activator ); +}; + +/* +=============================================================================== + +rvAIManager + +=============================================================================== +*/ + +class rvAIManager { +public: + + rvAIManager ( void ); + ~rvAIManager ( void ) {} + + /* + =============================================================================== + General + =============================================================================== + */ + + void RunFrame ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Clear ( void ); + + bool IsActive ( void ); + bool IsSimpleThink ( idAI* ai ); + + /* + =============================================================================== + Navigation + =============================================================================== + */ + + void UnMarkAllReachBlocked ( void ); + void ReMarkAllReachBlocked ( void ); + void MarkReachBlocked ( idAAS* aas, idReachability* reach, const idList& blockers); + bool ValidateDestination ( idAI* ignore, const idVec3& dest, bool skipCurrent = false, idActor* skipActor = NULL ) const; + void AddAvoid ( const idVec3& origin, float range, int team ); + + /* + =============================================================================== + Helpers + =============================================================================== + */ + + void RegisterHelper ( rvAIHelper* helper ); + void UnregisterHelper ( rvAIHelper* helper ); + rvAIHelper* FindClosestHelper ( const idVec3& origin ); + + /* + =============================================================================== + Team Management + =============================================================================== + */ + + void AddTeammate ( idActor* ent ); + void RemoveTeammate ( idActor* ent ); + + idActor* GetAllyTeam ( aiTeam_t team ); + idActor* GetEnemyTeam ( aiTeam_t team ); + + idActor* NearestTeammateToPoint ( idActor* from, idVec3 point, bool nonPlayer = false, float maxRange = 1000.0f, bool checkFOV = false, bool checkLOS = false ); + idEntity* NearestTeammateEnemy ( idActor* from, float maxRange=1000.0f, bool checkFOV = false, bool checkLOS = false, idActor** ally = NULL ); + bool LocalTeamHasEnemies ( idAI* self, float maxBuddyRange=640.0f, float maxEnemyRange=1024.0f, bool checkPVS=false ); + bool ActorIsBehindActor ( idActor* ambusher, idActor* victim ); + + /* + =============================================================================== + Team Timers + =============================================================================== + */ + + bool CheckTeamTimer ( int team, aiTeamTimer_t timer ); + void ClearTeamTimer ( int team, aiTeamTimer_t timer ); + void SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ); + + /* + =============================================================================== + Announcements + =============================================================================== + */ + + void AnnounceKill ( idAI* victim, idEntity* attacker, idEntity* inflictor ); + void AnnounceDeath ( idAI* victim, idEntity* attacker ); + + /* + =============================================================================== + Reactions + =============================================================================== + */ + + void ReactToPlayerAttack ( idPlayer* player, const idVec3 &origOrigin, const idVec3 &origDir ); + + /* + =============================================================================== + Debugging + =============================================================================== + */ + + idTimer timerFindEnemy; + idTimer timerTactical; + idTimer timerMove; + idTimer timerThink; + + int thinkCount; + int simpleThinkCount; + +protected: + + void UpdateHelpers ( void ); + + void DebugDraw ( void ); + void DebugDrawHelpers ( void ); + + idList blockedReaches; + idLinkList simpleThink; + idLinkList helpers; + + idLinkList teams[AITEAM_NUM]; + int teamTimers[AITEAM_NUM][AITEAMTIMER_MAX]; + + idList avoids; +}; + +ID_INLINE bool rvAIManager::CheckTeamTimer ( int team, aiTeamTimer_t timer ) { + return gameLocal.time >= teamTimers[team][timer]; +} + +ID_INLINE void rvAIManager::ClearTeamTimer ( int team, aiTeamTimer_t timer ) { + teamTimers[team][timer] = 0; +} + +ID_INLINE void rvAIManager::SetTeamTimer ( int team, aiTeamTimer_t timer, int delay ) { + teamTimers[team][timer] = gameLocal.time + delay; +} + +ID_INLINE void rvAIManager::AddAvoid ( const idVec3& origin, float radius, int team ) { + if ( !IsActive ( ) ) { + return; + } + aiAvoid_t& a = avoids.Alloc ( ); + a.origin = origin; + a.radius = radius; + a.team = team; +} + +extern rvAIManager aiManager; + +#endif // __AI_MANAGER_H__ diff --git a/src/mpgame/ai/AI_Medic.cpp b/src/mpgame/ai/AI_Medic.cpp new file mode 100644 index 0000000..5dfd861 --- /dev/null +++ b/src/mpgame/ai/AI_Medic.cpp @@ -0,0 +1,860 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Medic.h" + + +const idEventDef AI_DisableHeal ( "disableHeal" ); +const idEventDef AI_EnableHeal ( "enableHeal" ); +const idEventDef AI_TakePatient ( "takePatient", "e" ); + +CLASS_DECLARATION( rvAITactical, rvAIMedic ) + EVENT( AI_DisableHeal, rvAIMedic::Event_DisableHeal ) + EVENT( AI_EnableHeal, rvAIMedic::Event_EnableHeal ) + EVENT( AI_TakePatient, rvAIMedic::TakePatient ) + EVENT( AI_EnableMovement, rvAIMedic::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvAIMedic::Event_DisableMovement ) +END_CLASS + +/* +================ +rvAIMedic::rvAIMedic +================ +*/ +rvAIMedic::rvAIMedic ( void ) { + patient = NULL; + isTech = false; + noAutoHeal = false; + stationary = false; + silent = false; + healObeyTether = false; + healing = false; + lastPatientCheckTime = 0; + emergencyOverride = false; + //healedAmount = 0; + healDisabled = false; + wasAware = false; + wasIgnoreEnemies = false; + healDebounceTime = 0; +} + +void rvAIMedic::InitSpawnArgsVariables( void ) +{ + //NOTE: these shouldn't change from spawn values - maybe they don't need to be variables and saved/loaded? + isTech = spawnArgs.GetBool( "tech" ); + noAutoHeal = spawnArgs.GetBool( "noAutoHeal" ); + healAmt = 200;//spawnArgs.GetInt( "healAmt", "25" ); + healObeyTether = spawnArgs.GetBool( "healObeyTether" ); + patientRange = spawnArgs.GetFloat( "patientRange", "640" ); + buddyRange = spawnArgs.GetFloat( "buddyRange", "640" ); + enemyRange = spawnArgs.GetFloat( "enemyRange", "1024" ); + healDebounceInterval = 0;//SEC2MS( spawnArgs.GetFloat( "healWait", "0" ) ); + + /* + // JTD: check for player being strogg, since the limits will be different in that case.. + idStr str = gameLocal.world->spawnArgs.GetString ( "player", "player_marine" ); + str.Strip( "player_" ); + // ...we'll end up with something like minMarineHeal...or minStroggHeal + */ + minHealValue = 35;//spawnArgs.GetInt( va( "min%sHeal", str.c_str()), "50" ); + maxHealValue = 0;//75;//spawnArgs.GetInt( va( "max%sHeal", str.c_str()), "75" ); +} +/* +================ +rvAIMedic::Spawn +================ +*/ +void rvAIMedic::Spawn ( void ) { + InitSpawnArgsVariables(); + + stationary = spawnArgs.GetBool( "stationary" ); + silent = spawnArgs.GetBool( "silent", "0"); + if ( spawnArgs.GetBool( "disableHeal" ) ) { + Event_DisableHeal(); + } + if ( spawnArgs.GetBool( "enableHeal" ) ) { + Event_EnableHeal(); + } + const char *func; + if ( spawnArgs.GetString( "script_postHeal", "", &func ) ) + { + mPostHealScript.Init( func ); + } +} + +/* +================ +rvAIMedic::Show +================ +*/ +void rvAIMedic::Show( void ) { + rvAITactical::Show(); + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +/* +================ +rvAIMedic::Save +================ +*/ +void rvAIMedic::Save( idSaveGame *savefile ) const { + patient.Save( savefile ); + savefile->WriteBool( healing ); + savefile->WriteInt( lastPatientCheckTime ); + savefile->WriteBool( emergencyOverride ); + savefile->WriteBool( healDisabled ); + savefile->WriteBool( wasAware ); + savefile->WriteBool( wasIgnoreEnemies ); + //savefile->WriteInt( healedAmount ); + savefile->WriteInt( healDebounceTime ); + savefile->WriteBool( stationary ); + savefile->WriteBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->WriteInt( curHealValue ); + savefile->WriteInt( maxPatientValue ); + mPostHealScript.Save( savefile ); +} + +/* +================ +rvAIMedic::Restore +================ +*/ +void rvAIMedic::Restore( idRestoreGame *savefile ) { + patient.Restore( savefile ); + savefile->ReadBool( healing ); + savefile->ReadInt( lastPatientCheckTime ); + savefile->ReadBool( emergencyOverride ); + savefile->ReadBool( healDisabled ); + savefile->ReadBool( wasAware ); + savefile->ReadBool( wasIgnoreEnemies ); + //savefile->ReadInt( healedAmount ); + savefile->ReadInt( healDebounceTime ); + savefile->ReadBool( stationary ); + savefile->ReadBool( silent ); + + //NOTE: every time these are used, they are set first, no point in saving/loading them! + savefile->ReadInt( curHealValue ); + savefile->ReadInt( maxPatientValue ); + mPostHealScript.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvAIMedic::Event_EnableHeal +===================== +*/ +void rvAIMedic::Event_EnableHeal( void ) { + healDisabled = false; +} + +/* +===================== +rvAIMedic::Event_DisableHeal +===================== +*/ +void rvAIMedic::Event_DisableHeal( void ) { + healDisabled = true; + /* + if ( patient ) { + //drop them right now - NOTE: should be done before any scripting? + DropPatient(); + } + */ +} + +/* +================== +rvAIMedic::Event_EnableMovement +================== +*/ +void rvAIMedic::Event_EnableMovement( void ) { + stationary = false; +} + +/* +================== +rvAIMedic::Event_DisableMovement +================== +*/ +void rvAIMedic::Event_DisableMovement ( void ) { + stationary = true; +} + +/* +===================== +rvAIMedic::TalkTo +===================== +*/ +void rvAIMedic::TalkTo( idActor *actor ) { + if ( actor->IsType( idPlayer::GetClassType() ) ) + { + idPlayer* player = dynamic_cast(actor); + if ( player ) + { + emergencyOverride = true; + if ( AvailableToTakePatient() && CheckTakePatient( player ) ) + { + return; + } + emergencyOverride = false; + } + } + rvAITactical::TalkTo( actor ); + + if ( !aifl.action && !aifl.scripted && !IsSpeaking() && !IsHidden() && !silent) { + if ( GetEnemy() || patient ) { + Speak( "lipsync_heal_busy_", true ); + } else { + //Never got proper VO for this, disabling it... :/ + //Speak( "lipsync_heal_noheal_", true ); + } + } +} + +/* +================ +rvAIMedic::GetDebugInfo +================ +*/ +void rvAIMedic::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + rvAITactical::GetDebugInfo ( proc, userData ); + + proc ( "rvAIMedic", "aifl.scripted", aifl.scripted?"true":"false", userData ); + proc ( "rvAIMedic", "move.fl.disabled", move.fl.disabled?"true":"false", userData ); + proc ( "rvAIMedic", "IsSpeaking", IsSpeaking()?"true":"false", userData ); + proc ( "rvAIMedic", "healDisabled", healDisabled?"true":"false", userData ); + proc ( "rvAIMedic", "noAutoHeal ", noAutoHeal ?"true":"false", userData ); + proc ( "rvAIMedic", "stationary", stationary?"true":"false", userData ); + proc ( "rvAIMedic", "silent", silent?"true":"false", userData ); + proc ( "rvAIMedic", "healObeyTether", healObeyTether?"true":"false", userData ); + proc ( "rvAIMedic", "patient", patient==NULL?"":patient->GetName(), userData ); + proc ( "rvAIMedic", "wasAware", wasAware?"true":"false", userData ); + proc ( "rvAIMedic", "wasIgnoreEnemies", wasIgnoreEnemies?"true":"false", userData ); + proc ( "rvAIMedic", "lastPatientCheckTime",va("%d",lastPatientCheckTime), userData ); + proc ( "rvAIMedic", "healDebounceTime", va("%d",healDebounceTime), userData ); + proc ( "rvAIMedic", "healing", healing?"true":"false", userData ); + proc ( "rvAIMedic", "emergencyOverride",healing?"true":"false", userData ); + //proc ( "rvAIMedic", "healedAmount", va("%d",healedAmount), userData ); + proc ( "rvAIMedic", "minHealValue", va("%d",minHealValue), userData ); + proc ( "rvAIMedic", "maxHealValue", va("%d",maxHealValue), userData ); + proc ( "rvAIMedic", "healAmt", va("%d",healAmt), userData ); + proc ( "rvAIMedic", "patientRange", va("%f",patientRange), userData ); + proc ( "rvAIMedic", "buddyRange", va("%f",buddyRange), userData ); + proc ( "rvAIMedic", "enemyRange", va("%f",enemyRange), userData ); + proc ( "rvAIMedic", "tech", isTech?"true":"false", userData ); +} + +/* +============ +rvAIMedic::IsTethered +============ +*/ +bool rvAIMedic::IsTethered ( void ) const { + if ( rvAITactical::IsTethered() ) { + if ( !healObeyTether && patient ) { + return false; + } + return true; + } + return false; +} + +void rvAIMedic::TakePatient( idPlayer* pPatient ) +{ + patient = pPatient; + if ( emergencyOverride ) + { + ClearEnemy(); + } + + wasAware = combat.fl.aware; + wasIgnoreEnemies = combat.fl.ignoreEnemies; + ProcessEvent( &AI_BecomePassive, true ); + combat.fl.ignoreEnemies = true; + ClearEnemy(); + lookTarget = patient; + healing = false; + //healedAmount = 0; + + if ( pPatient && DistanceTo( pPatient ) > 250.0f && !silent ) + { + //have enough time to say this before we get there...? + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_call_player_tech_", true ); + } else { + Speak( "lipsync_call_player_", true ); + } + } + + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + SetState( "State_Medic" ); + PostState( "State_Combat" ); + + //just in case + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } +} + +void rvAIMedic::DropPatient( void ) +{ + /* + if ( !spawnArgs.GetBool( "ignoreEnemies" ) ) + { + combat.fl.ignoreEnemies = false; + } + */ + /* + if ( !aifl.scripted ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_DEFAULT ); + } + */ + healing = false; + if ( !aifl.scripted ) { + ProcessEvent( &AI_BecomeAggressive ); + combat.fl.aware = wasAware; + combat.fl.ignoreEnemies = wasIgnoreEnemies; + lookTarget = NULL; + } else if ( lookTarget == patient ) { + //if scripted, clear the looktarget only if it's the patient? This could be wrong, though.... + lookTarget = NULL; + } + + patient = NULL; + + if ( healDebounceInterval ) { + healDebounceTime = gameLocal.GetTime() + healDebounceInterval; + } + + ForceTacticalUpdate(); + if ( !aifl.scripted ) { + UpdateTactical ( 0 ); + } + //FIXME: what if they stay in this state? + HideAttachment( spawnArgs.GetString("def_attach") ); +} + +void rvAIMedic::SetHealValues( idPlayer* player ) +{ + if ( !player ) + { + return; + } + if ( isTech ) + { + curHealValue = player->inventory.armor; + maxPatientValue = player->inventory.maxarmor; + } + else + { + curHealValue = player->health; + maxPatientValue = player->inventory.maxHealth; + } +} + +bool rvAIMedic::CheckTakePatient( idPlayer* player ) +{ + if ( !player ) + { + return false; + } + if ( player->IsHidden() ) + { + return false; + } + if ( player->health <= 0 ) + { + return false; + } + SetHealValues( player ); + + if ( curHealValue < maxPatientValue ) + {//they are hurt + if ( curHealValue <= minHealValue || (gameLocal.GetTime() >= healDebounceTime && emergencyOverride && (!maxHealValue || curHealValue < maxHealValue)) ) + {//patient needs healing or he requested a heal and it's been long enough and he's below the max heal level (if there is one) + if ( DistanceTo( player ) < patientRange ) + {//close enough + if ( (!move.fl.disabled && !stationary) || DistanceTo( player ) <= combat.meleeRange ) + {//either I am allowed to move or player is close enough that I don't have to + //if ( !tether || tether->ValidateDestination( this, player->GetPhysics()->GetOrigin() ) ) + {//not tethered or patient is in our tether + if ( emergencyOverride || CanSee( player, false ) ) + {//can see the patient - OR: just check PVS? + TakePatient( player ); + return true; + } + } + } + } + } + } + + return false; +} + +bool rvAIMedic::SituationAllowsPatient( void ) +{ + if ( !aifl.scripted && !IsHidden() && !aifl.dead ) + {//not scripted right now + if ( combat.fl.ignoreEnemies ) { + return true; + } + bool enemyInPVS = false; + bool enemyInRange = false; + if ( GetEnemy() ) + {//sorry, we have to bail on you! + enemyInRange = (DistanceTo( GetEnemy() ) < enemyRange); + if ( enemyInRange ) { + pvsHandle_t pvs; + // Setup our local variables used in the search + pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + // If this enemy isnt in the same pvps then use them as a backup + if ( gameLocal.pvs.InCurrentPVS( pvs, GetEnemy()->GetPVSAreas(), GetEnemy()->GetNumPVSAreas() ) ) { + enemyInPVS = true; + emergencyOverride = false; + } + gameLocal.pvs.FreeCurrentPVS( pvs ); + } + } + if ( emergencyOverride ) + {//don't care how crazy it is, go for it! + return true; + } + if ( (!GetEnemy() || !enemyInPVS || !enemyInRange ) && gameLocal.GetTime() - enemy.changeTime > 5000 ) + {//haven't had an enemy for 5 seconds + if ( !aiManager.LocalTeamHasEnemies( this, buddyRange, enemyRange, true ) ) + {//local buddies don't have enemies + //NOTE: which buddies and enemies are local can change as we head to our patient! + return true; + } + } + } + return false; +} + +bool rvAIMedic::AvailableToTakePatient( void ) +{ + if ( !healDisabled ) + {//not forced to disabled + if ( !aifl.action ) + {//not in the middle of an action + if ( !patient ) + {//don't already have a patient + if ( !IsSpeaking() ) + {//not talking + return SituationAllowsPatient(); + } + } + } + } + return false; +} + +/* +================ +rvAIMedic::Think +================ +*/ +void rvAIMedic::Think ( void ) { + rvAITactical::Think ( ); + +// while( entMedic.getKey("alive") == "true" && entMedic.getKey("healer") == "1") +//??? + if ( !noAutoHeal ) + { + if ( gameLocal.GetTime() - lastPatientCheckTime > 1000 ) + { + lastPatientCheckTime = gameLocal.GetTime(); + if ( !patient ) + { + emergencyOverride = false; + } + if ( AvailableToTakePatient() ) + { + idPlayer* player = gameLocal.GetLocalPlayer(); + + if ( CheckTakePatient( player ) ) + { + return; + } + //otherwise, check team? + /* + idActor* actor; + for( actor = aiManager.GetAllyTeam ( (aiTeam_t)team ); actor; actor = actor->teamNode.Next() ) + { + if ( CheckTakePatient( actor ) ) + { + return; + } + } + */ + } + } + } +} + +/* +===================== +rvAIMedic::OnStateThreadClear +===================== +*/ +void rvAIMedic::OnStateThreadClear( const char *statename, int flags ) { + if ( idStr::Icmp( statename, "State_Medic" ) ) { + if ( patient ) { + //BAH! Someone changed our state on us! + if ( lookTarget == patient ) { + lookTarget = NULL; + } + patient = NULL; + healing = false; + } + } +} + +/* +============ +rvAIMedic::OnStartMoving +============ +*/ +void rvAIMedic::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( patient ) + {//we were trying to heal! + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//being told to leave the patient + //abort the heal, for now + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + } +} + +/* +===================== +rvAIMedic::Pain +===================== +*/ +bool rvAIMedic::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool retVal = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( retVal && patient ) { + //if get hit while trying to heal, protect ourselves + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + StopMove ( MOVE_STATUS_DONE ); + } + DropPatient(); + if ( !aifl.scripted ) { + //only do this if you're not already scripted? + ExecScriptFunction( mPostHealScript ); + } + } + return retVal; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAIMedic ) + STATE ( "State_Medic", rvAIMedic::State_Medic ) +END_CLASS_STATES + +/* +================ +idAI::State_Medic + +Rush towards the patient to melee range and heal until they're okay +================ +*/ +stateResult_t rvAIMedic::State_Medic ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the patient, speak & start anim + STAGE_PRE_HEAL_ANIM_WAIT,// Wait for pre heal anim to finish, then start the normal heal anim + STAGE_HEAL_START_WAIT,// Wait for start anim to finish + STAGE_HEAL, // Keep healing until no longer in melee range or they're fully healed + STAGE_WAIT_FINISH // Finish anim + }; + if ( !patient || patient->health <= 0 || !SituationAllowsPatient() ) + {//patient dead or situation is bad + //NOTE: if patient still alive and situation is just bad, maybe we shouldn't break out altogether, maybe pause and resume? + StopMove ( MOVE_STATUS_DONE ); + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + + if ( !move.fl.done ) + { + if ( move.moveCommand != MOVE_TO_ENTITY + || move.goalEntity != patient ) + {//something stomped our move, give it up, for now... :/ + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + } + + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) + { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee(patient,false) ) + {//wtf, we're not there yet, try again! + if ( !stationary && !move.fl.disabled ) { + MoveToEntity( patient, 42 ); + } + } + else + {//we're there! + if ( !healing ) + { + SetHealValues( patient ); + if ( !IsSpeaking() && speakTime < (gameLocal.GetTime() - 2000) && !silent ) + {//didn't speak in last couple seconds + //jshepard: tech/medic dependent speech + if( isTech ) { + Speak( "lipsync_heal_start_tech_", true ); + } else { + Speak( "lipsync_heal_start_", true ); + } + } + } + StopMove ( MOVE_STATUS_DONE ); + healing = true; + // check for preHeal anim key and if it's present, play it first. + const char *preHealAnim; + if ( spawnArgs.GetString( "anim_preHeal", "", &preHealAnim )) + { + PlayAnim( ANIMCHANNEL_TORSO, preHealAnim, 4 ); + return SRESULT_STAGE ( STAGE_PRE_HEAL_ANIM_WAIT ); + } + else // otherwise just use the regular anim to start healing + { + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + } + } + if ( !stationary && move.range != 42.0f ) + { + //MCG: if you ever get this assert, please call me over so I can debug it! + assert(move.range==42.0f); + move.range = 42.0f;//shouldn't have to do this, but sometimes something is overriding the range to 8! VERY VERY BAD... :_( + } + /* + else if ( !CheckTacticalMove ( AITACTICAL_MEDIC ) && CanSee(patient,false) ) + {//we're here, just stop + Speak( "lipsync_medic_arrive", true ); + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + */ + + // Perform actions on the way to the patient + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + } + + return SRESULT_WAIT; + + // intermediate state which may or may not exist...depends on the presence of pre heal anim key on this entity + case STAGE_PRE_HEAL_ANIM_WAIT: + const char *preHealAnim; + spawnArgs.GetString( "anim_preHeal", "", &preHealAnim ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), preHealAnim )) + {//finished or interrupted + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_start", 4 ); + return SRESULT_STAGE ( STAGE_HEAL_START_WAIT ); + } + return SRESULT_WAIT; + + + case STAGE_HEAL_START_WAIT: + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_start" ) ) + {//finished or interrupted +// PlayCycle( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player", 4 ); + //show the tool, just in case the anim was interrupted + ShowAttachment( spawnArgs.GetString("def_attach") ); + return SRESULT_STAGE ( STAGE_HEAL ); + } + return SRESULT_WAIT; + + case STAGE_HEAL: + { + SetHealValues( patient ); + /* + if ( curHealValue >= maxPatientValue + || (curHealValue > minHealValue && healedAmount >= healAmt) ) + {//patient fully healed (or we've used up our allotment), we're done here + Speak( "lipsync_heal_end_", true ); + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + */ + + // If we are out of melee range or lost sight of our patient then start moving again + /* + if ( !stationary && !move.fl.disabled ) { + if ( DistanceTo( patient ) > combat.meleeRange || !CanSee( patient, false ) ) { + MoveToEntity( patient, 42 ); + Speak( "lipsync_heal_move_", true ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return SRESULT_STAGE ( STAGE_MOVE ); + } + } + */ + + /* + combat.fl.ignoreEnemies = true; + */ + /* + if ( patient->pfl.crouch && postureIdeal != AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_CROUCH ); + } + else if ( !patient->pfl.crouch && postureIdeal == AIPOSTURE_CROUCH ) + { + ProcessEvent( &AI_ForcePosture, AIPOSTURE_STAND ); + } + */ + + // Always face patient when in melee range + TurnToward ( patient->GetPhysics()->GetOrigin ( ) ); + + // Perform actions while standing still + /* + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update enemy, not tactical state + if ( !emergencyOverride ) + { + combat.tacticalUpdateTime = gameLocal.GetTime(); + if ( UpdateTactical( 100000 ) ) { + DropPatient(); + return SRESULT_DONE_WAIT; + } + } + */ + + //jshepard: tech/medic dependent speech + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player" ) ) { + if ( !isTech ) { + patient->health = patient->health+healAmt>maxPatientValue?maxPatientValue:patient->health+healAmt; + if( !silent) { + Speak( "lipsync_heal_end_", true ); + } + } else { + patient->inventory.armor = patient->inventory.armor+healAmt>maxPatientValue?maxPatientValue:patient->inventory.armor+healAmt;; + if( !silent) { + Speak( "lipsync_heal_end_tech_", true ); + } + } + PlayAnim( ANIMCHANNEL_TORSO, "medic_treating_player_end", 4 ); + return SRESULT_STAGE ( STAGE_WAIT_FINISH ); + } + if ( gameLocal.random.RandomFloat() > 0.5f ) + { + if ( !isTech ) + { + if ( patient->health < maxPatientValue ) { + patient->health++; + } + } + else + { + if ( patient->inventory.armor < maxPatientValue ) { + patient->inventory.armor++; + } + } + } + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 ) || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "medic_treating_player_end" ) ) + {//finished or interrupted + //turn off the tool, just in case we were interrupted + DropPatient(); + ExecScriptFunction( mPostHealScript ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/AI_Medic.h b/src/mpgame/ai/AI_Medic.h new file mode 100644 index 0000000..657bf64 --- /dev/null +++ b/src/mpgame/ai/AI_Medic.h @@ -0,0 +1,116 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Medic.h + +================ +*/ +#include "AI_Tactical.h" + +#ifndef __AI_MEDIC__ +#define __AI_MEDIC__ + +class rvAIMedic : public rvAITactical { +public: + + CLASS_PROTOTYPE( rvAIMedic ); + + rvAIMedic ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Show ( void ); + + virtual void TalkTo ( idActor *actor ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool IsTethered ( void ) const; + + virtual void OnStateThreadClear ( const char *statename, int flags ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + bool isTech; + +protected: + virtual void OnStartMoving ( void ); + +private: + + idEntityPtr patient; + bool healing; + int lastPatientCheckTime; + bool emergencyOverride; + + bool noAutoHeal; + bool stationary; + bool silent; + bool healObeyTether; + int healAmt; + float patientRange; + float buddyRange; + float enemyRange; + + int curHealValue; + int maxHealValue; + int minHealValue; + int healedAmount; + int maxPatientValue; + + int healDebounceInterval; + int healDebounceTime; + + bool healDisabled; + bool wasAware; + bool wasIgnoreEnemies; + + void SetHealValues ( idPlayer* player ); + + void TakePatient ( idPlayer* pPatient ); + void DropPatient ( void ); + bool CheckTakePatient ( idPlayer* actor ); + bool SituationAllowsPatient ( void ); + bool AvailableToTakePatient ( void ); + + stateResult_t State_Medic ( const stateParms_t& parms ); + + void Event_EnableHeal ( void ); + void Event_DisableHeal ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + + rvScriptFuncUtility mPostHealScript; // script to run after completing a heal + + CLASS_STATES_PROTOTYPE ( rvAIMedic ); +}; + +#endif /* !__AI_MEDIC__ */ diff --git a/src/mpgame/ai/AI_Move.cpp b/src/mpgame/ai/AI_Move.cpp new file mode 100644 index 0000000..556adbe --- /dev/null +++ b/src/mpgame/ai/AI_Move.cpp @@ -0,0 +1,4856 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + +AI_Move.cpp + +This file has all movement related functions. It and its sister H file were +split from AI.h and AI.cpp in order to prevent merge conflicts and to make +further changes to the system possible. + +=============================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + + idMoveState + +=============================================================================== +*/ + +/* +===================== +idMoveState::idMoveState +===================== +*/ +idMoveState::idMoveState() { + moveType = MOVETYPE_ANIM; + moveCommand = MOVE_NONE; + moveStatus = MOVE_STATUS_DONE; + moveDest.Zero(); + moveDir.Set( 1.0f, 0.0f, 0.0f ); + goalEntity = NULL; + goalEntityOrigin.Zero(); + toAreaNum = 0; + startTime = 0; + duration = 0; + speed = 0.0f; + range = 0.0f; + wanderYaw = 0; + nextWanderTime = 0; + blockTime = 0; + obstacle = NULL; + lastMoveOrigin = vec3_origin; + lastMoveTime = 0; + anim = 0; + travelFlags = TFL_WALK|TFL_AIR; + kickForce = 2048.0f; + fl.ignoreObstacles = false; + fl.allowAnimMove = false; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + blockedRadius = 0.0f; + blockedMoveTime = 750; + blockedAttackTime = 750; + turnRate = 360.0f; + turnVel = 0.0f; + anim_turn_yaw = 0.0f; + anim_turn_amount = 0.0f; + anim_turn_angles = 0.0f; + fly_offset = 0; + fly_seek_scale = 1.0f; + fly_roll_scale = 0.0f; + fly_roll_max = 0.0f; + fly_roll = 0.0f; + fly_pitch_scale = 0.0f; + fly_pitch_max = 0.0f; + fly_pitch = 0.0f; + fly_speed = 0.0f; + fly_bob_strength = 0.0f; + fly_bob_vert = 0.0f; + fly_bob_horz = 0.0f; + currentDirection = MOVEDIR_FORWARD; + idealDirection = MOVEDIR_FORWARD; + current_yaw = 0.0f; + ideal_yaw = 0.0f; + flyTiltJoint = INVALID_JOINT; + addVelocity.Zero(); +} + + + +/* +===================== +idMoveState::Spawn +===================== +*/ +void idMoveState::Spawn( idDict &spawnArgs ) { + memset ( &fl, 0, sizeof(fl) ); + fl.allowAnimMove = true; + fl.allowPrevAnimMove = false; + fl.allowHiddenMove = false; + fl.noRun = spawnArgs.GetBool ( "forceWalk", "0" ); + fl.noWalk = spawnArgs.GetBool ( "forceRun", "0" ); + fl.noTurn = spawnArgs.GetBool ( "noTurn", "0" ); + fl.noGravity = spawnArgs.GetBool ( "animate_z", "0" ); + fl.noRangedInterrupt = spawnArgs.GetBool ( "noRangedInterrupt", "0" ); + + fl.flyTurning = spawnArgs.GetBool ( "flyTurning", "0" ); + fl.allowDirectional = spawnArgs.GetBool ( "directionalMovement", "0" ); + fl.allowPushMovables = spawnArgs.GetBool( "af_push_moveables", "0" ); + fl.ignoreObstacles = spawnArgs.GetBool( "ignore_obstacles", "0" ); + fl.disabled = spawnArgs.GetBool ( "noMove", "0" ); + walkRange = spawnArgs.GetFloat ( "walkRange", "100" ); + walkTurn = spawnArgs.GetFloat ( "walkTurn", "45" ); + followRange = spawnArgs.GetVec2 ( "followRange", "70 250" ); + searchRange = spawnArgs.GetVec2 ( "searchRange", "0 1024" ); + attackPositionRange = spawnArgs.GetFloat ( "attackPositionRange", "0" ); + turnDelta = spawnArgs.GetFloat ( "turnDelta", "10" ); + blockTime = 0; + + spawnArgs.GetInt( "fly_offset", "100", fly_offset ); + spawnArgs.GetFloat( "fly_speed", "100", fly_speed ); + spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength ); + spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz ); + spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert ); + spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale ); + spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale ); + spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max ); + spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale ); + spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max ); + + spawnArgs.GetFloat( "turn_rate", "360", turnRate ); + spawnArgs.GetFloat( "kick_force", "4096", kickForce ); + spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius ); + spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime ); + spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime ); +} + + +/* +===================== +idMoveState::Save +===================== +*/ +void idMoveState::Save( idSaveGame *savefile ) const { + int i; + + savefile->Write ( &fl, sizeof(fl) ); + + savefile->WriteInt( (int)moveType ); + savefile->WriteInt( (int)moveCommand ); + savefile->WriteInt( (int)moveStatus ); + savefile->WriteVec3( moveDest ); + savefile->WriteVec3( moveDir ); + + savefile->WriteInt( toAreaNum ); + savefile->WriteInt( startTime ); + savefile->WriteInt( duration ); + savefile->WriteFloat( speed ); + savefile->WriteFloat( range ); + savefile->WriteFloat( wanderYaw ); + savefile->WriteInt( nextWanderTime ); + savefile->WriteInt( blockTime ); + obstacle.Save( savefile ); + savefile->WriteVec3( lastMoveOrigin ); + savefile->WriteInt( lastMoveTime ); + savefile->WriteInt( anim ); + + savefile->WriteInt( travelFlags ); + + savefile->WriteFloat ( kickForce ); + savefile->WriteFloat ( blockedRadius ); + savefile->WriteInt ( blockedMoveTime ); + savefile->WriteInt ( blockedAttackTime ); + + savefile->WriteFloat( ideal_yaw ); + savefile->WriteFloat( current_yaw ); + savefile->WriteFloat( turnRate ); + savefile->WriteFloat( turnVel ); + savefile->WriteFloat( anim_turn_yaw ); + savefile->WriteFloat( anim_turn_amount ); + savefile->WriteFloat( anim_turn_angles ); + + savefile->WriteJoint( flyTiltJoint ); + savefile->WriteFloat( fly_speed ); + savefile->WriteFloat( fly_bob_strength ); + savefile->WriteFloat( fly_bob_vert ); + savefile->WriteFloat( fly_bob_horz ); + savefile->WriteInt( fly_offset ); + savefile->WriteFloat( fly_seek_scale ); + savefile->WriteFloat( fly_roll_scale ); + savefile->WriteFloat( fly_roll_max ); + savefile->WriteFloat( fly_roll ); + savefile->WriteFloat( fly_pitch_scale ); + savefile->WriteFloat( fly_pitch_max ); + savefile->WriteFloat( fly_pitch ); + + savefile->WriteInt ( (int) currentDirection ); + savefile->WriteInt ( (int) idealDirection ); + savefile->WriteFloat( walkRange ); + savefile->WriteFloat( walkTurn ); + savefile->WriteVec2 ( followRange ); + savefile->WriteVec2 ( searchRange ); + savefile->WriteFloat( attackPositionRange ); + savefile->WriteFloat( turnDelta ); + + savefile->WriteVec3 ( goalPos ); + savefile->WriteInt ( goalArea ); + goalEntity.Save( savefile ); + savefile->WriteVec3( goalEntityOrigin ); + + savefile->WriteVec3( myPos ); // cnicholson: Added unsaved var + savefile->WriteInt( myArea ); // cnicholson: Added unsaved var + + savefile->WriteVec3 ( seekPos ); + + savefile->WriteInt( (int) MAX_PATH_LEN ); // cnicholson: Added unsaved vars + for (i=0; i< MAX_PATH_LEN; ++i) { + // TOSAVE: idReachability* reach; + savefile->WriteVec3( path[i].seekPos ); + } + + savefile->WriteInt( pathLen ); // cnicholson: Added unsaved var + savefile->WriteInt( pathArea ); // cnicholson: Added unsaved var + savefile->WriteInt( pathTime ); // cnicholson: Added unsaved var + + savefile->WriteVec3( addVelocity ); +} + +/* +===================== +idMoveState::Restore +===================== +*/ +void idMoveState::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->Read ( &fl, sizeof(fl) ); + + savefile->ReadInt( (int&)moveType ); + savefile->ReadInt( (int&)moveCommand ); + savefile->ReadInt( (int&)moveStatus ); + savefile->ReadVec3( moveDest ); + savefile->ReadVec3( moveDir ); + + savefile->ReadInt( toAreaNum ); + savefile->ReadInt( startTime ); + savefile->ReadInt( duration ); + savefile->ReadFloat( speed ); + savefile->ReadFloat( range ); + savefile->ReadFloat( wanderYaw ); + savefile->ReadInt( nextWanderTime ); + savefile->ReadInt( blockTime ); + obstacle.Restore ( savefile ); + savefile->ReadVec3( lastMoveOrigin ); + savefile->ReadInt( lastMoveTime ); + savefile->ReadInt( anim ); + + savefile->ReadInt( travelFlags ); + + savefile->ReadFloat ( kickForce ); + savefile->ReadFloat ( blockedRadius ); + savefile->ReadInt ( blockedMoveTime ); + savefile->ReadInt ( blockedAttackTime ); + + savefile->ReadFloat( ideal_yaw ); + savefile->ReadFloat( current_yaw ); + savefile->ReadFloat( turnRate ); + savefile->ReadFloat( turnVel ); + savefile->ReadFloat( anim_turn_yaw ); + savefile->ReadFloat( anim_turn_amount ); + savefile->ReadFloat( anim_turn_angles ); + + savefile->ReadJoint( flyTiltJoint ); + savefile->ReadFloat( fly_speed ); + savefile->ReadFloat( fly_bob_strength ); + savefile->ReadFloat( fly_bob_vert ); + savefile->ReadFloat( fly_bob_horz ); + savefile->ReadInt( fly_offset ); + savefile->ReadFloat( fly_seek_scale ); + savefile->ReadFloat( fly_roll_scale ); + savefile->ReadFloat( fly_roll_max ); + savefile->ReadFloat( fly_roll ); + savefile->ReadFloat( fly_pitch_scale ); + savefile->ReadFloat( fly_pitch_max ); + savefile->ReadFloat( fly_pitch ); + + savefile->ReadInt ( (int&) currentDirection ); + savefile->ReadInt ( (int&) idealDirection ); + savefile->ReadFloat( walkRange ); + savefile->ReadFloat( walkTurn ); + savefile->ReadVec2 ( followRange ); + savefile->ReadVec2 ( searchRange ); + savefile->ReadFloat( attackPositionRange ); + savefile->ReadFloat( turnDelta ); + + savefile->ReadVec3 ( goalPos ); + savefile->ReadInt ( goalArea ); + goalEntity.Restore ( savefile ); + savefile->ReadVec3( goalEntityOrigin ); + + savefile->ReadVec3( myPos ); // cnicholson: Added unrestored var + savefile->ReadInt( myArea ); // cnicholson: Added unrestored var + + savefile->ReadVec3 ( seekPos ); + + savefile->ReadInt( num ); + for (i=0; i< num; ++i) { + // TOSAVE: idReachability* reach; + savefile->ReadVec3( path[i].seekPos ); + } + + savefile->ReadInt( pathLen ); // cnicholson: Added unrestored var + savefile->ReadInt( pathArea ); // cnicholson: Added unrestored var + savefile->ReadInt( pathTime ); // cnicholson: Added unrestored var + + savefile->ReadVec3( addVelocity ); +} + + +/* +============ +idAI::SetMoveType +============ +*/ +void idAI::SetMoveType ( moveType_t moveType ) { + if ( move.moveType == moveType ) { + return; + } + + StopSound ( SND_CHANNEL_HEART, false ); + + switch ( moveType ) { + case MOVETYPE_STATIC: + move.travelFlags = 0; + break; + + case MOVETYPE_FLY: + move.travelFlags = TFL_FLY|TFL_WALK|TFL_AIR; + StartSound ( "snd_fly", SND_CHANNEL_HEART, 0, false, NULL ); + break; + + case MOVETYPE_ANIM: + move.travelFlags = TFL_WALK|TFL_AIR; + break; + + case MOVETYPE_CUSTOM: + move.travelFlags = TFL_WALK|TFL_AIR|TFL_WALKOFFLEDGE; + break; + } + + move.moveType = moveType; +} + +/* +===================== +idAI::Event_SaveMove +===================== +*/ +void idAI::Event_SaveMove( void ) { + savedMove = move; +} + +/* +===================== +idAI::Event_RestoreMove +===================== +*/ +void idAI::Event_RestoreMove( void ) { + idVec3 dest; + + switch( savedMove.moveCommand ) { + case MOVE_NONE : + StopMove( savedMove.moveStatus ); + break; + + case MOVE_FACE_ENEMY : + FaceEnemy(); + break; + + case MOVE_FACE_ENTITY : + FaceEntity( savedMove.goalEntity.GetEntity() ); + break; + + case MOVE_TO_ENEMY : + MoveToEnemy(); + break; + + case MOVE_TO_ENTITY : + MoveToEntity( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_OUT_OF_RANGE : + MoveOutOfRange( savedMove.goalEntity.GetEntity(), savedMove.range ); + break; + + case MOVE_TO_ATTACK: + MoveToAttack ( savedMove.goalEntity.GetEntity(), savedMove.anim ); + break; + + case MOVE_TO_COVER : + MoveToCover ( combat.attackRange[0], combat.attackRange[1], AITACTICAL_COVER ); + break; + + case MOVE_TO_POSITION : + MoveTo ( savedMove.moveDest, savedMove.range ); + break; + + case MOVE_SLIDE_TO_POSITION : + SlideToPosition( savedMove.moveDest, savedMove.duration ); + break; + + case MOVE_WANDER : + WanderAround(); + break; + } + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, dest ); + } +} + +/* +============ +idAI::KickObstacles +============ +*/ +void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) { + int i, numListedClipModels; + idBounds clipBounds; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + int clipmask; + idVec3 org; + idVec3 forceVec; + idVec3 delta; + idVec2 perpendicular; + + org = physicsObj.GetOrigin(); + + // find all possible obstacles + clipBounds = physicsObj.GetAbsBounds(); + clipBounds.TranslateSelf( dir * 32.0f ); + clipBounds.ExpandSelf( 8.0f ); + clipBounds.AddPoint( org ); + clipmask = physicsObj.GetClipMask(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( this, clipBounds, clipmask, clipModelList, MAX_GENTITIES ); +// RAVEN END + for ( i = 0; i < numListedClipModels; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + if ( obEnt == alwaysKick ) { + // we'll kick this one outside the loop + continue; + } + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if (( obEnt->IsType( idMoveable::GetClassType() ) || obEnt->IsType( idAFAttachment::GetClassType() )) && obEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + delta = obEnt->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * obEnt->GetPhysics()->GetMass(); + obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec ); + } + } + + if ( alwaysKick ) { + delta = alwaysKick->GetPhysics()->GetOrigin() - org; + delta.NormalizeFast(); + perpendicular.x = -delta.y; + perpendicular.y = delta.x; + delta.z += 0.5f; + delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f; + forceVec = delta * force * alwaysKick->GetPhysics()->GetMass(); + alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec ); + } + +} + +/* +============ +ValidForBounds +============ +*/ +bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) { + int i; + + for ( i = 0; i < 3; i++ ) { + if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) { + return false; + } + if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) { + return false; + } + } + return true; +} + +/* +===================== +idAI::SetAAS +===================== +*/ +void idAI::SetAAS( void ) { + idStr use_aas; + + spawnArgs.GetString( "use_aas", NULL, use_aas ); + if ( !use_aas || !use_aas[0] ) { + //don't intend to use AAS at all? + //no warning - lack of AAS intentional + aas = NULL; + return; + } + aas = gameLocal.GetAAS( use_aas ); + if ( aas ) { + const idAASSettings *settings = aas->GetSettings(); + if ( settings ) { + if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) { + gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() ); + } + float height = settings->maxStepHeight; + physicsObj.SetMaxStepHeight( height ); + return; + } else { + aas = NULL; + } + } + gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() ); +} + +/* +===================== +idAI::ReachedPos +===================== +*/ +bool idAI::ReachedPos( const idVec3 &pos, const aiMoveCommand_t moveCommand, float range ) const { + // When moving towards the enemy just see if our bounding box touches the desination + if ( moveCommand == MOVE_TO_ENEMY ) { + if ( !enemy.ent || physicsObj.GetAbsBounds().IntersectsBounds( enemy.ent->GetPhysics()->GetAbsBounds().Expand( range ) ) ) { + return true; + } + return false; + } + + // Dont add vertical bias when using fly move + if ( move.moveType == MOVETYPE_FLY ) { + float offset; + if ( moveCommand == MOVE_TO_ENTITY ) { + offset = 0.0f; + } else { + offset = move.fly_offset; + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-range), idVec3(range,range,range+offset) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + + if ( moveCommand == MOVE_TO_TETHER ) + {//if you're not actually in the tether, we don't want to stop early (for compliance with ai_trigger condition_tether) + if ( !IsWithinTether() ) + { + return false; + } + } + + // Excluded z height when determining reached + if ( move.toAreaNum > 0 ) { + if ( PointReachableAreaNum( physicsObj.GetOrigin() ) == move.toAreaNum ) { + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-4096.0f), idVec3(range,range,4096.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); + } + } + + idBounds bnds; + bnds = idBounds ( idVec3(-range,-range,-16.0f), idVec3(range,range,64.0f) ); + bnds.TranslateSelf( physicsObj.GetOrigin() ); + return bnds.ContainsPoint( pos ); +} + +/* +===================== +idAI::PointReachableAreaNum +===================== +*/ +int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const { + int areaNum; + idVec3 size; + idBounds bounds; + + if ( !aas ) { + return 0; + } + + size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale; + bounds[0] = -size; + size.z = 32.0f; + bounds[1] = size; + + if ( move.moveType == MOVETYPE_FLY ) { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY ); + } else { + areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK ); + } + + return areaNum; +} + +/* +===================== +idAI::PathToGoal +===================== +*/ +bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const { + idVec3 org; + idVec3 goal; + + if ( !aas ) { + return false; + } + + org = origin; + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !areaNum ) { + return false; + } + + goal = goalOrigin; + aas->PushPointIntoAreaNum( goalAreaNum, goal ); + if ( !goalAreaNum ) { + return false; + } + //push goal back up if flying to a point way above an area + if ( move.moveType == MOVETYPE_FLY ) { + if ( areaNum == goalAreaNum ) { + if ( goalOrigin.z > goal.z ) { + float areaTop = aas->AreaCeiling( goalAreaNum ) - GetPhysics()->GetBounds()[1].z; + if ( goalOrigin.z > areaTop ) { + //crap, cap it... + goal.z = areaTop; + } else { + goal.z = goalOrigin.z; + } + } + } + } + + + if ( move.moveType == MOVETYPE_FLY ) { + return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } else { + return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, move.travelFlags ); + } +} + +/* +===================== +idAI::TravelDistance + +Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance. + +This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you +are from the goal, so try to break the goals up into shorter distances. +===================== +*/ +float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const { + int fromArea; + int toArea; + float dist; + idVec2 delta; + aasPath_t path; + + if ( !aas ) { + // no aas, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + fromArea = PointReachableAreaNum( start ); + toArea = PointReachableAreaNum( end ); + + if ( !fromArea || !toArea ) { + // can't seem to get there + return -1; + } + + if ( fromArea == toArea ) { + // same area, so just take the straight line distance + delta = end.ToVec2() - start.ToVec2(); + dist = delta.LengthFast(); + + if ( DebugFilter(ai_debugMove) ) { // Blue = Travel Distance + gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false ); + gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() ); + } + + return dist; + } + + idReachability *reach; + int travelTime; + if ( !aas->RouteToGoalArea( fromArea, start, toArea, move.travelFlags, travelTime, &reach ) ) { + return -1; + } + + if ( DebugFilter(ai_debugMove) ) { // Travel Distance, Fly Path & Walk Path + if ( move.moveType == MOVETYPE_FLY ) { + aas->ShowFlyPath( start, toArea, end ); + } else { + aas->ShowWalkPath( start, toArea, end ); + } + } + + return travelTime; +} + +float idAI::TravelDistance ( idEntity *ent ) const { + return TravelDistance ( physicsObj.GetOrigin(), ent->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( idEntity* start, idEntity* end ) const { + assert( start ); + assert( end ); + return TravelDistance( start->GetPhysics()->GetOrigin(), end->GetPhysics()->GetOrigin() ); +} + +float idAI::TravelDistance( const idVec3 &pos ) const { + return TravelDistance( physicsObj.GetOrigin(), pos ); +} + +/* +============ +idAI::ScriptedPlaybackMove +============ +*/ +void idAI::ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + // Start the scripted sequence + if ( !ScriptedBegin ( false ) ) { + return; + } + + // Start the playback + if ( !mPlayback.Start( spawnArgs.GetString ( playback ), this, flags, numFrames ) ) { + ScriptedEnd ( ); + return; + } + + move.goalEntity = NULL; + move.moveCommand = MOVE_RV_PLAYBACK; + move.moveType = MOVETYPE_PLAYBACK; + move.fl.done = false; + + // Wait till its done and mark it finished + SetState ( "State_ScriptedPlaybackMove" ); + PostState ( "State_ScriptedStop" ); +} + +/* +===================== +idAI::FaceEnemy + +Continually face the enemy's last known position. MoveDone is always true in this case. +===================== +*/ +bool idAI::FaceEnemy( void ) { + idEntity *enemyEnt = enemy.ent; + if ( !enemyEnt ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + TurnToward( enemy.lastKnownPosition ); + move.goalEntity = enemyEnt; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENEMY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::FaceEntity + +Continually face the entity position. MoveDone will never be true in this case. +===================== +*/ +bool idAI::FaceEntity( idEntity *ent ) { + if ( !ent ) { + StopMove( MOVE_STATUS_DEST_NOT_FOUND ); + return false; + } + + idVec3 entityOrg = ent->GetPhysics()->GetOrigin(); + TurnToward( entityOrg ); + move.goalEntity = ent; + move.moveDest = physicsObj.GetOrigin(); + move.moveCommand = MOVE_FACE_ENTITY; + move.moveStatus = MOVE_STATUS_WAITING; + move.startTime = gameLocal.time; + move.speed = 0.0f; + + move.fl.done = false; + move.fl.moving = false; + move.fl.goalUnreachable = false; + aifl.simpleThink = false; + + return true; +} + +/* +===================== +idAI::StartMove + +Initialize a new movement by setting up the movement structure +===================== +*/ +bool idAI::StartMove ( aiMoveCommand_t command, const idVec3& goalOrigin, int goalArea, idEntity* goalEntity, aasFeature_t* feature, float range ) { + // If we are already there then we are done + if ( ReachedPos( goalOrigin, command ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + move.lastMoveOrigin = physicsObj.GetOrigin ( ); + + move.seekPos = move.goalPos = move.moveDest = goalOrigin; + move.toAreaNum = goalArea; + move.goalEntity = goalEntity; + move.moveCommand = command; + move.moveStatus = MOVE_STATUS_MOVING; + move.speed = move.fly_speed; + move.startTime = gameLocal.time; + move.range = range; + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = true; + + aasSensor->Reserve ( feature ); + + OnStartMoving ( ); + + return true; +} + +/* +===================== +idAI::StopMove +===================== +*/ +void idAI::StopMove( moveStatus_t status ) { + aiMoveCommand_t oldCommand = move.moveCommand; + float saveZ = 0.0f; + + move.fl.done = true; + move.fl.moving = false; + move.fl.goalUnreachable = false; + move.fl.obstacleInPath = false; + move.fl.blocked = false; + + if ( move.moveType == MOVETYPE_FLY ) { + if ( move.moveCommand == MOVE_TO_ENEMY + || move.moveCommand == MOVE_TO_ATTACK ) { + saveZ = (move.moveDest.z-physicsObj.GetOrigin().z); + } + } + move.moveCommand = MOVE_NONE; + move.moveStatus = status; + move.toAreaNum = 0; + move.goalEntity = NULL; + move.moveDest = physicsObj.GetOrigin(); + move.moveDest.z += saveZ; + move.startTime = gameLocal.time; + move.duration = 0; + move.range = 0.0f; + move.speed = 0.0f; + move.anim = 0; + + move.moveDir.Zero(); + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + + // Callback for handling stopping + if ( oldCommand != MOVE_NONE ) { + OnStopMoving ( oldCommand ); + } +} + +/* +===================== +idAI::MoveToTether +===================== +*/ +bool idAI::MoveToTether ( rvAITether* tether ) { + aasGoal_t goal; + + // find a goal using the currently active tether + if ( !aas || !tether ) { + return false; + } + + if ( !tether->FindGoal ( this, goal ) ) { + //This is extremely bad - if 2 guys both try to get to the center of a tether, they get hosed. + return MoveTo ( tether->GetPhysics()->GetOrigin ( ), tether->GetOriginReachedRange() ); + } + + if ( move.moveType == MOVETYPE_FLY ) { + //Hmm... we shouldn't have to clamp as the area wouldn't have been valid unless the tether was in its bounds and height, but...? + //float areaTop = aas->AreaCeiling( goal.areaNum ); + goal.origin.z = tether->GetPhysics()->GetOrigin().z; + } + + return StartMove ( MOVE_TO_TETHER, goal.origin, goal.areaNum, NULL, NULL, AI_TETHER_MINRANGE ); +} + +/* +===================== +idAI::MoveToAttack +===================== +*/ +bool idAI::MoveToAttack ( idEntity *ent, int attack_anim ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( ent ); + + // if we havent started a find yet or the current find is something other than a attack find then start a new one + if ( !aasFind || !dynamic_cast(aasFind) ) { + // Allocate the new aas find + delete aasFind; + aasFind = new rvAASFindGoalForAttack ( this ); + + // Find a goal that gives us a viable attack position on our enemy + aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, IsTethered()?0:move.searchRange[0], move.searchRange[1], &obstacle, 1, *aasFind ); + } + + assert ( aasFind ); + + // Test some more points with the existing find + rvAASFindGoalForAttack* aasFindAttack = static_cast(aasFind); + if ( !aasFindAttack->TestCachedGoals ( aifl.simpleThink ? 1 : 4, goal ) ) { + delete aasFind; + aasFind = NULL; + return false; + } + + // Havent found a goal yet but exhaused our trace count + if ( !goal.areaNum ) { + return false; + } + + // Dont need the find anymore + delete aasFind; + aasFind = NULL; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(goal.areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( pos.z > areaTop ) { + goal.origin.z = areaTop; + } else if ( pos.z > goal.origin.z+1.0f ) { + goal.origin.z = pos.z; + } else if ( move.fly_offset > 0 ) { + if ( (goal.origin.z+move.fly_offset) > areaTop ) { + goal.origin.z = areaTop; + } else { + goal.origin.z += move.fly_offset; + } + } + } + } + + return StartMove ( MOVE_TO_ATTACK, goal.origin, goal.areaNum, ent, NULL, move.attackPositionRange?move.attackPositionRange:8.0f ); +} + +/* +===================== +idAI::MoveToEnemy +===================== +*/ +bool idAI::MoveToEnemy( void ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !enemy.ent ) { + return false; + } + +// pos = LastKnownPosition ( enemy.ent ); + pos = enemy.ent->GetPhysics()->GetOrigin ( ); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent && move.goalEntityOrigin == pos ) { + return true; + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENEMY, 8.0f ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = pos; + + if ( move.moveType == MOVETYPE_FLY ) { + //float up above the ground? + if ( aas && aas->GetFile() ) { + //we have AAS + float areaTop = aas->AreaCeiling(areaNum)-GetPhysics()->GetBounds()[1][2]; + if ( move.fly_offset > 0 ) { + if ( (pos.z+move.fly_offset) > areaTop ) { + pos.z = areaTop; + move.goalEntityOrigin.z = areaTop; + } else { + pos.z += move.fly_offset; + move.goalEntityOrigin.z += move.fly_offset; + } + } + } + } + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + move.moveDest = pos; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENEMY, pos, areaNum, enemy.ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveToEntity +===================== +*/ +bool idAI::MoveToEntity( idEntity *ent, float range ) { + int areaNum; + aasPath_t path; + idVec3 pos; + + if ( !ent ) { + return false; + } + + // Where do we want to go? + pos = ent->GetPhysics()->GetOrigin(); + + // If we are already moving to the entity and its position hasnt changed then we are done + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent && move.goalEntityOrigin == pos ) { + return true; + } + + // If we arent flying we should move to a position on the floor + if ( move.moveType != MOVETYPE_FLY ) { + ent->GetFloorPos( 64.0f, pos ); + } + + // Early out if we are already there. + if ( ReachedPos( pos, MOVE_TO_ENTITY, range ) ) { + StopMove( MOVE_STATUS_DONE ); + return true; + } + + // See if its posible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + return false; + } + } + + move.goalEntityOrigin = ent->GetPhysics()->GetOrigin(); + + // If we are already moving towards the given enemy then we have updated enough + if ( move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == ent ) { + move.moveDest = pos; + move.range = range <= 0.0f ? 8.0f : range; + move.toAreaNum = areaNum; + return true; + } + + return StartMove ( MOVE_TO_ENTITY, pos, areaNum, ent, NULL, range <= 0.0f ? 8.0f : range ); +} + +/* +===================== +idAI::MoveOutOfRange +===================== +*/ +bool idAI::MoveOutOfRange( idEntity *ent, float range, float minRange ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + int obstacles; + + if ( !aas || !ent ) { + return false; + } + + const idVec3 &org = physicsObj.GetOrigin(); + + // consider the entity the monster is getting close to as an obstacle + if ( ent != this ) { + obstacles = 1; + obstacle.absBounds = ent->GetPhysics()->GetAbsBounds(); + } else { + obstacles = 0; + } + + pos = LastKnownPosition ( ent ); + + // Find a goal out of range of where we are + rvAASFindGoalOutOfRange findGoal( this ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, minRange, range, &obstacle, obstacles, findGoal ) ) { + return false; + } + + return StartMove ( MOVE_OUT_OF_RANGE, goal.origin, goal.areaNum, ent, NULL, 8.0f ); +} + +/* +===================== +idAI::MoveTo +===================== +*/ +bool idAI::MoveTo ( const idVec3 &pos, float range ) { + idVec3 org; + int areaNum; + aasPath_t path; + + if ( !aas ) { + return false; + } + + org = pos; + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // Can we get to where we want to go? + aas->PushPointIntoAreaNum( areaNum, org ); + if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), PointReachableAreaNum( physicsObj.GetOrigin() ), org ) ) { + return false; + } + + // Start moving + return StartMove ( MOVE_TO_POSITION, org, areaNum, NULL, NULL, range ); +} + +/* +===================== +idAI::MoveToCover +===================== +*/ +bool idAI::MoveToCover( float minRange, float maxRange, aiTactical_t coverType ) { + idVec3 org; + int areaNum; + aasPath_t path; + aasFeature_t* feature = 0; + idVec3 featureOrigin; + + if ( !aas ) { + return false; + } + + // Look for nearby cover + switch ( coverType ) { + case AITACTICAL_HIDE: aasSensor->SearchHide(); break; + case AITACTICAL_COVER_FLANK: aasSensor->SearchFlank(); break; + case AITACTICAL_COVER_ADVANCE: aasSensor->SearchAdvance(); break; + case AITACTICAL_COVER_RETREAT: aasSensor->SearchRetreat(); break; + case AITACTICAL_COVER_AMBUSH: aasSensor->SearchAmbush(); break; + case AITACTICAL_COVER: + default: + aasSensor->SearchCover(); + break; + } + + if ( !aasSensor->FeatureCount ( ) ) { + return false; + } + + feature = aasSensor->Feature ( 0 ); + featureOrigin = feature->Origin ( ); + org = featureOrigin; + areaNum = 0; + + // Find the aas area our cover point is in + areaNum = PointReachableAreaNum( org ); + if ( !areaNum ) { + return false; + } + + // See if there is a path to our goal or not + aas->PushPointIntoAreaNum( areaNum, org ); +/* + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, org ) ) { + return false; + } +*/ + + combat.coverValidTime = gameLocal.time; + + // Start the move + return StartMove ( MOVE_TO_COVER, org, areaNum, enemy.ent, feature, AI_COVER_MINRANGE / 2.0f ); +} + +/* +===================== +idAI::MoveToHide +===================== +*/ +bool idAI::MoveToHide ( void ) { + aasObstacle_t obstacle; + aasGoal_t goal; + idBounds bounds; + idVec3 pos; + + // Need an enemy to hide from + if ( !aas || !enemy.ent ) { + return false; + } + + const idVec3& org = physicsObj.GetOrigin(); + obstacle.absBounds = enemy.ent->GetPhysics()->GetAbsBounds(); + pos = LastKnownPosition ( enemy.ent ); + + // Search aas for a suitable goal + rvAASFindGoalForHide findGoal( pos ); + if ( !aas->FindNearestGoal( goal, PointReachableAreaNum( org ), org, pos, move.travelFlags, 0.0f, 0.0f, &obstacle, 1, findGoal ) ) { + return false; + } + + // Start the movement + return StartMove ( MOVE_TO_HIDE, goal.origin, goal.areaNum, enemy.ent, NULL, 0.0f ); +} + +/* +===================== +idAI::SlideToPosition +===================== +*/ +bool idAI::SlideToPosition( const idVec3 &pos, float time ) { + StopMove( MOVE_STATUS_DONE ); + + move.moveDest = pos; + move.goalEntity = NULL; + move.moveCommand = MOVE_SLIDE_TO_POSITION; + move.moveStatus = MOVE_STATUS_MOVING; + move.startTime = gameLocal.time; + move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) ); + + move.fl.done = false; + move.fl.goalUnreachable = false; + move.fl.moving = false; + aifl.simpleThink = false; + + move.fl.allowAnimMove = false; + + if ( move.duration > 0 ) { + move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration ); + if ( move.moveType != MOVETYPE_FLY ) { + move.moveDir.z = 0.0f; + } + move.speed = move.moveDir.LengthFast(); + } + + return true; +} + +/* +===================== +idAI::WanderAround +===================== +*/ +bool idAI::WanderAround( void ) { + idVec3 dest; + + StopMove( MOVE_STATUS_DONE ); + + dest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + if ( !NewWanderDir( dest ) ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + return false; + } + + return StartMove ( MOVE_WANDER, dest, 0, NULL, NULL, 0.0f ); +} + +/* +================ +idAI::StepDirection +================ +*/ +bool idAI::StepDirection( float dir ) { + predictedPath_t path; + idVec3 org; + + move.wanderYaw = dir; + move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward(); + + org = physicsObj.GetOrigin(); + + idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) { + // don't report being blocked if we ran into our goal entity + return true; + } + + if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) { + float z; + + move.moveDir = path.endVelocity * 1.0f / 48.0f; + + // trace down to the floor and see if we can go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 floorPos = path.endPos; + idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = -1.0f; + return true; + } + + // trace up to see if we can go over something and go forward + idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path ); + + idVec3 ceilingPos = path.endPos; + + for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) { + idVec3 start; + if ( z <= ceilingPos.z ) { + start.x = org.x; + start.y = org.y; + start.z = z; + } else { + start = ceilingPos; + } + idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path ); + if ( !path.endEvent ) { + move.moveDir.z = 1.0f; + return true; + } + } + return false; + } + + return ( path.endEvent == 0 ); +} + +/* +================ +idAI::NewWanderDir +================ +*/ +bool idAI::NewWanderDir( const idVec3 &dest ) { + float deltax, deltay; + float d[ 3 ]; + float tdir, olddir, turnaround; + + move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 ); + + olddir = idMath::AngleNormalize360( ( int )( move.current_yaw / 45 ) * 45 ); + turnaround = idMath::AngleNormalize360( olddir - 180 ); + + idVec3 org = physicsObj.GetOrigin(); + deltax = dest.x - org.x; + deltay = dest.y - org.y; + if ( deltax > 10 ) { + d[ 1 ]= 0; + } else if ( deltax < -10 ) { + d[ 1 ] = 180; + } else { + d[ 1 ] = DI_NODIR; + } + + if ( deltay < -10 ) { + d[ 2 ] = 270; + } else if ( deltay > 10 ) { + d[ 2 ] = 90; + } else { + d[ 2 ] = DI_NODIR; + } + + // try direct route + if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) { + if ( d[ 1 ] == 0 ) { + tdir = d[ 2 ] == 90 ? 45 : 315; + } else { + tdir = d[ 2 ] == 90 ? 135 : 215; + } + + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + + // try other directions + if ( ( gameLocal.random.RandomInt() & 1 ) || abs( deltay ) > abs( deltax ) ) { + tdir = d[ 1 ]; + d[ 1 ] = d[ 2 ]; + d[ 2 ] = tdir; + } + + if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) { + return true; + } + + if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) { + return true; + } + + // there is no direct path to the player, so pick another direction + if ( olddir != DI_NODIR && StepDirection( olddir ) ) { + return true; + } + + // randomly determine direction of search + if ( gameLocal.random.RandomInt() & 1 ) { + for( tdir = 0; tdir <= 315; tdir += 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } else { + for ( tdir = 315; tdir >= 0; tdir -= 45 ) { + if ( tdir != turnaround && StepDirection( tdir ) ) { + return true; + } + } + } + + if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) { + return true; + } + + // can't move + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; +} + +/* +===================== +idAI::GetMovePos +===================== +*/ +bool idAI::GetMovePos( idVec3 &seekPos, idReachability** seekReach ) { + int areaNum; + aasPath_t path; + bool result; + idVec3 org; + + org = physicsObj.GetOrigin(); + seekPos = org; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = 0; + } + // RAVEN END + + switch( move.moveCommand ) { + case MOVE_NONE : + seekPos = move.moveDest; + return false; + + case MOVE_FACE_ENEMY : + case MOVE_FACE_ENTITY : + seekPos = move.moveDest; + return false; + + case MOVE_TO_POSITION_DIRECT : + seekPos = move.moveDest; + if ( ReachedPos( move.moveDest, move.moveCommand ) ) { + StopMove( MOVE_STATUS_DONE ); + } + return false; + + case MOVE_SLIDE_TO_POSITION : + seekPos = org; + return false; + + case MOVE_TO_ENTITY: + MoveToEntity( move.goalEntity.GetEntity(), move.range ); + break; + + case MOVE_TO_ENEMY: + if ( !MoveToEnemy() && combat.tacticalCurrent == AITACTICAL_MELEE ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + return false; + } + break; + } + + if ( move.moveType == MOVETYPE_FLY && move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + //flying + if ( DistanceTo( move.moveDest ) < 1024.0f ) { + //less than huge translation dist, which is actually 4096, but, just to be safe... + trace_t moveTrace; + gameLocal.Translation( this, moveTrace, org, move.moveDest, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this, move.goalEntity.GetEntity() ); + if ( moveTrace.fraction >= 1.0f ) { + //can head straight for it, so do it. + seekPos = move.moveDest; + return false; + } + } + } + + move.moveStatus = MOVE_STATUS_MOVING; + result = false; + + if ( move.moveCommand == MOVE_WANDER ) { + move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f; + } else { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + seekPos = org; + return false; + } + } + + if ( aas && move.toAreaNum ) { + areaNum = PointReachableAreaNum( org ); + if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) { + seekPos = path.moveGoal; + if ( aas->GetFile() ) { + //we have AAS + if ( move.fly_offset > 0 ) { + if ( (seekPos-move.moveDest).LengthSqr() > 10.0f ) { + //not heading to final dest, that already has offset in it + float areaTop = aas->AreaCeiling(path.moveAreaNum)-GetPhysics()->GetBounds()[1][2]; + if ( (seekPos.z+move.fly_offset) > areaTop ) { + seekPos.z = areaTop; + } else { + seekPos.z += move.fly_offset; + } + } + } + } + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + if (seekReach) { + (*seekReach) = (idReachability*)(path.reachability); + } + // RAVEN END + + result = true; + move.nextWanderTime = 0; + } else { + move.fl.goalUnreachable = true; + } + } + + + if ( !result ) { + // wander around + if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) { + result = NewWanderDir( move.moveDest ); + if ( !result ) { + StopMove( MOVE_STATUS_DEST_UNREACHABLE ); + move.fl.goalUnreachable = true; + seekPos = org; + + return false; + } + } else { + result = true; + } + + seekPos = org + move.moveDir * 2048.0f; + + } else { + move.fl.goalUnreachable = false; + } + + if ( DebugFilter(ai_debugMove) ) { // YELLOW = seekPos + gameRenderWorld->DebugLine( colorYellow, physicsObj.GetOrigin(), seekPos ); + } + + return result; +} + + + +/* +===================== +idAI::BlockedFailSafe +===================== +*/ +void idAI::BlockedFailSafe( void ) { +/* move.fl.blocked = false; + + if ( !ai_blockedFailSafe.GetBool() || move.blockedRadius < 0.0f ) { + return; + } + if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL || + ( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( move.blockedRadius ) ) { + move.lastMoveOrigin = physicsObj.GetOrigin(); + move.lastMoveTime = gameLocal.time; + } + if ( move.lastMoveTime < gameLocal.time - move.blockedMoveTime ) { + if ( lastAttackTime < gameLocal.time - move.blockedAttackTime ) { + move.fl.blocked = true; + move.lastMoveTime = gameLocal.time; + } + }*/ +} + +/*********************************************************************** + + turning + +***********************************************************************/ + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( float yaw ) { + move.ideal_yaw = idMath::AngleNormalize180( yaw ); + bool result = FacingIdeal(); + return result; +} + +/* +===================== +idAI::TurnToward +===================== +*/ +bool idAI::TurnToward( const idVec3 &pos ) { + idVec3 dir; + idVec3 local_dir; + float lengthSqr; + + dir = pos - physicsObj.GetOrigin(); + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + lengthSqr = local_dir.LengthSqr(); + if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.ent == NULL ) ) { + move.ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); + } + + return FacingIdeal(); +} + +/* +===================== +idAI::TurnTowardLeader +===================== +*/ +bool idAI::TurnTowardLeader( bool faceLeaderByDefault ) { + if ( !leader.GetEntity() ) { + return false; + } + //see if there's a wall in the direction the player's looking + trace_t tr; + + idVec3 leaderLookDest = leader->GetPhysics()->GetOrigin() + (( (leader.GetEntity() && leader.GetEntity()->IsType( idPlayer::GetClassType() )) ? ((idPlayer*)leader.GetEntity())->intentDir:leader->viewAxis[0])*300.0f); + idVec3 myLookDir = leaderLookDest-GetPhysics()->GetOrigin(); + myLookDir.Normalize(); + idVec3 start = GetPhysics()->GetOrigin(); + start.z += EyeHeight(); + idVec3 end = start + (myLookDir*128.0f); + end.z = start.z; + idVec3 currentLookDir = viewAxis[0]; + currentLookDir.Normalize(); + + if ( !GetEnemy() && (!leader->IsType(idPlayer::GetClassType()) || !((idPlayer*)leader.GetEntity())->IsFlashlightOn()) ) { + //Not in combat and leader isn't looking around with flashlight + if ( myLookDir*currentLookDir > 0.666f ) { + //new dir isn't different enough from current dir for me to care + return true; + } + } + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + idEntity* traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //wall there - NOTE: okay to look at doors + if ( faceLeaderByDefault//want to face leader by default + || leader->viewAxis[0].ToYaw() == move.ideal_yaw ) {//a wall must have moved in front of us? + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + //just keep looking in the last valid dir + if ( FacingIdeal() ) { + //make sure it's still valid + idVec3 end = start + (viewAxis[0]*128.0f); + end.z = start.z; + gameLocal.TracePoint( this, tr, start, end, MASK_OPAQUE, this ); + traceEnt = gameLocal.entities[ tr.c.entityNum ]; + if ( tr.fraction < 1.0f + && (tr.fraction<0.5f||!traceEnt||!traceEnt->IsType(idDoor::GetClassType())) ) { + //a wall right in front of us - NOTE: okay to look at doors + //face the leader + return TurnToward( leader->GetPhysics()->GetOrigin() ); + } + } + return true; + } + //opening, face leader's dir + return TurnToward( myLookDir.ToYaw() ); +} + +/* +============ +idAI::DirectionalTurnToward + +Turn toward the given point using directional movement +============ +*/ +bool idAI::DirectionalTurnToward ( const idVec3 &pos ) { + static float moveDirOffset [ MOVEDIR_MAX ] = { + 0.0f, 180.0f, -90.0f, 90.0f + }; + + // Issue standard TurnToward if we are not currently eligible for directional movement + if ( combat.tacticalCurrent != AITACTICAL_MOVE_PLAYERPUSH ) { + //always move directionally when getting out of the player's way + if( !combat.fl.aware || focusType < AIFOCUS_USE_DIRECTIONAL_MOVE || !move.fl.moving || move.moveCommand == MOVE_TO_ENEMY || !move.fl.allowDirectional ) { + move.idealDirection = MOVEDIR_FORWARD; + return TurnToward ( pos ); + } + } + + // Turn towards our desination + float moveYaw; + TurnToward ( pos ); + moveYaw = move.ideal_yaw; + + // Turn towards the goal entity and determine the angle difference + TurnToward ( currentFocusPos ); + + // Check for a direction change only when we can no longer see + // where we need to look and where we are looking is greater than 3/4ths the maximum look + if ( (pos - GetPhysics()->GetOrigin()).LengthFast ( ) > 8.0f ) + if ( !FacingIdeal ( ) ) + if ( fabs ( idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ) >= fabs(lookMax[YAW]) || + fabs ( idMath::AngleNormalize180 ( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) - move.current_yaw ) ) >= 80.0f ) { + + float diffYaw; + diffYaw = idMath::AngleNormalize180 ( move.ideal_yaw - moveYaw ); + + if ( diffYaw > -45.0f && diffYaw < 45.0f ) { + move.idealDirection = MOVEDIR_FORWARD; + } else if ( diffYaw < -135.0f || diffYaw > 135.0f ) { + move.idealDirection = MOVEDIR_BACKWARD; + } else if ( diffYaw < 0.0f ) { + move.idealDirection = MOVEDIR_LEFT; + } else { + move.idealDirection = MOVEDIR_RIGHT; + } + } + + return TurnToward( idMath::AngleNormalize180 ( moveDirOffset[move.idealDirection] + moveYaw ) ); +} + +/* +===================== +idAI::Turn +===================== +*/ +void idAI::Turn( void ) { + float diff; + float diff2; + float turnAmount; + animFlags_t animflags; + + // If cant turn or turning is disabled just bail + if ( !CanTurn ( ) ) { + return; + } + + // check if the animator has marked this anim as non-turning + if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) { + animflags = legsAnim.GetAnimFlags(); + } else { + animflags = torsoAnim.GetAnimFlags(); + } + if ( animflags.ai_no_turn ) { + return; + } + + if ( move.anim_turn_angles && animflags.anim_turn ) { + idMat3 rotateAxis; + + // set the blend between no turn and full turn + float frac = move.anim_turn_amount / move.anim_turn_angles; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac ); + + // get the total rotation from the start of the anim + animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis ); + move.current_yaw = idMath::AngleNormalize180( move.anim_turn_yaw + rotateAxis[ 0 ].ToYaw() ); + } else { + diff = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( move.currentDirection != move.idealDirection ) { + move.turnVel += (AI_TURN_SCALE * 2.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.running ) { + move.turnVel += (AI_TURN_SCALE * 3.0f)* diff * MS2SEC( gameLocal.msec ); + } else if ( move.fl.moving ) { + move.turnVel += (AI_TURN_SCALE * 1.5f)* diff * MS2SEC( gameLocal.msec ); + } else { + move.turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec ); + } + + if ( move.turnVel > move.turnRate ) { + move.turnVel = move.turnRate; + } else if ( move.turnVel < -move.turnRate ) { + move.turnVel = -move.turnRate; + } + turnAmount = move.turnVel * MS2SEC( gameLocal.msec ); + if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) { + move.turnVel = diff / MS2SEC( gameLocal.msec ); + turnAmount = diff; + } + move.current_yaw = idMath::AngleNormalize180( move.current_yaw + turnAmount ); + diff2 = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + + if ( idMath::Fabs( diff2 ) < 0.1f ) { + move.current_yaw = move.ideal_yaw; + } + } + + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); + +// if ( DebugFilter(ai_debugMove) ) { // RED = ideal_yaw, GREEN = current_yaw, YELLOW = current+velocity +// const idVec3 &org = physicsObj.GetOrigin(); +// gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, move.ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, move.current_yaw, 0 ).ToForward() * 48, gameLocal.msec ); +// gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, move.current_yaw + move.turnVel, 0 ).ToForward() * 32, gameLocal.msec ); +// if ( move.anim_turn_angles && animflags.anim_turn ) { +// gameRenderWorld->DebugLine( colorOrange, org, org + idAngles( 0, move.anim_turn_yaw, 0 ).ToForward() * 32, gameLocal.msec ); +// } +// } +} + +/* +===================== +idAI::FacingIdeal +===================== +*/ +bool idAI::FacingIdeal( void ) { + float diff; + + if ( !move.turnRate ) { + return true; + } + + diff = idMath::AngleDelta ( move.current_yaw, move.ideal_yaw ); + if ( idMath::Fabs( diff ) < 0.01f ) { + // force it to be exact + move.current_yaw = move.ideal_yaw; + return true; + } + + return false; +} + +/* +================ +idAI::AnimTurn +================ +*/ +void idAI::AnimTurn ( float angles, bool force ) { + move.turnVel = 0.0f; + move.anim_turn_angles = angles; + if ( angles ) { + move.anim_turn_yaw = move.current_yaw; + + if ( force ) { + move.anim_turn_amount = angles; + } else { + move.anim_turn_amount = idMath::Fabs( idMath::AngleNormalize180( move.current_yaw - move.ideal_yaw ) ); + if ( move.anim_turn_amount > move.anim_turn_angles ) { + move.anim_turn_amount = move.anim_turn_angles; + } + } + } else { + move.anim_turn_amount = 0.0f; + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, 0.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f ); + animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, 0.0f ); + } +} + +/*********************************************************************** + + Movement Helper Functions + +***********************************************************************/ + +/* +================ +idAI::ApplyImpulse +================ +*/ +void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + // FIXME: Jim take a look at this and see if this is a reasonable thing to do + // instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 ) + // and we don't want him taking physics impulses as it can knock him off the path + if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) { + idActor::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +===================== +idAI::GetAnimMoveDelta +===================== +*/ +void idAI::GetAnimMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) { + idVec3 oldModelOrigin; + idVec3 modelOrigin; + + animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta ); + delta = axis * delta; + + if ( modelOffset != vec3_zero ) { + // the pivot of the monster's model is around its origin, and not around the bounding + // box's origin, so we have to compensate for this when the model is offset so that + // the monster still appears to rotate around it's origin. + oldModelOrigin = modelOffset * oldaxis; + modelOrigin = modelOffset * axis; + delta += oldModelOrigin - modelOrigin; + } + + delta *= physicsObj.GetGravityAxis(); +} + + + +/* +===================== +TestTeammateCollisions +===================== +*/ + +void TestTeammateCollisions(idAI* owner) { + idActor* teammate; + idVec3 teammateDirection; + idVec3 teammateVelocity; + float teammateDirectionDot; + float teammateDistance; + float teammateSpeed; + const idVec3& myOrigin = owner->GetPhysics()->GetOrigin(); + const idBounds& myBounds = owner->GetPhysics()->GetBounds(); + idVec3 myVelocity = owner->GetPhysics()->GetLinearVelocity(); + float mySpeed = myVelocity.NormalizeFast(); + + // Don't Bother, We're Blocked Or Not Moving + //--------------------------------------------- + if (owner->move.blockTime>gameLocal.GetTime() || !owner->move.fl.moving || mySpeed<1.0f) { + return; + } + + + for (teammate = aiManager.GetAllyTeam((aiTeam_t)owner->team); teammate; teammate = teammate->teamNode.Next()) { + if (teammate->fl.hidden || teammate == owner || teammate->health <= 0) { + continue; + } + + // If On Same Floor + //------------------ + teammateDirection = teammate->GetPhysics()->GetOrigin() - myOrigin; + if (fabsf(teammateDirection[2])>myBounds.Size().z) { + continue; + } + + // If Close Enough + //----------------- + teammateDistance = teammateDirection.NormalizeFast(); + if (teammateDistance>128.0f) { + continue; + } + + // Completely Ignore Dudes Directly Behind Me + //-------------------------------------------- + teammateDirectionDot = teammateDirection*myVelocity; + if (teammateDirectionDot<-0.5f) { + continue; + } + + // Switch To Walk If I'm Heading For A Teammate + //---------------------------------------------- + if (teammateDirectionDot>0.85f) { + owner->move.fl.obstacleInPath = true; // make him slow to a walk + + if ( owner->DebugFilter(ai_debugMove) ) { // WHITE = Walk Teammate Near + gameRenderWorld->DebugArrow( colorWhite, myOrigin, teammate->GetPhysics()->GetOrigin(), 4, 250 ); + } + } + + if (teammateDistance<48.0f) { + + teammateVelocity = teammate->GetPhysics()->GetLinearVelocity(); + teammateSpeed = teammateVelocity.NormalizeFast(); + + if (teammateSpeed>50.0f) { + + // If I'm Following Him, And I'm RIGHT Behind Him, Stop And Let Him Go A Bit Farther Ahead + //------------------------------------------------------------------------------------------ + if (teammateDirectionDot>0.85f && teammateVelocity*myVelocity>0.4f) { + owner->move.blockTime = gameLocal.time + 1000; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1000); + } + + } + + // Stop moving if my leader or a guy with a higher entity number is headed straight for me + //----------------------------------------------------------------------------------------- + else if (teammate->entityNumberentityNumber && teammateDirection*teammateVelocity<0.5f) { + owner->move.blockTime = gameLocal.time + 1500; // Set Blocktime Timer + owner->move.fl.blocked = true; + + if ( owner->DebugFilter(ai_debugMove) ) { // RED = Stop Teammate Near + gameRenderWorld->DebugArrow( colorRed, myOrigin, teammate->GetPhysics()->GetOrigin(), 8, 1500); + } + } + } + } + } +} + + + + + + +/* +===================== +idAI::CheckObstacleAvoidance +===================== +*/ +void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &seekPos, idReachability* goalReach ) { + + move.fl.blocked = false; // Makes Character Stop Moving + move.fl.obstacleInPath = false; // Makes Character Walk + move.obstacle = NULL; + seekPos = goalPos; + + if (move.fl.ignoreObstacles) { + return; + } + if ( g_perfTest_aiNoObstacleAvoid.GetBool() ) { + return; + } + + // Test For Path Around Obstacles + //-------------------------------- + obstaclePath_t path; + move.fl.blocked = !FindPathAroundObstacles( &physicsObj, aas, move.moveCommand == MOVE_TO_ENEMY ? enemy.ent : NULL, physicsObj.GetOrigin(), goalPos, path ); + move.fl.obstacleInPath = (path.firstObstacle || path.seekPosObstacle || path.startPosObstacle); + move.obstacle = (path.firstObstacle)?(path.firstObstacle):(path.seekPosObstacle); + seekPos = path.seekPos; + + // Don't Worry About Obstacles Out Of Walk Range + //----------------------------------------------- + if (move.obstacle && DistanceTo(move.obstacle)>155.0f) { + move.fl.blocked = false; + move.fl.obstacleInPath = false; + move.obstacle = 0; + seekPos = goalPos; + } + + // cdr: Alternate Routes Bug + // If An Obstacle Remains, And The Seek Pos Is Fairly Farr Off Of The Straight Line Path, Then Mark The Reach As Blocked + //----------------------------------------------------------------------------------------------------------------------- + if (move.fl.obstacleInPath && goalReach && !(goalReach->travelType&TFL_INVALID) && seekPos.Dist2XY(goalPos)>100.0f) { + float scale; + float dist; + + dist = seekPos.DistToLineSeg(physicsObj.GetOrigin(), goalPos, scale); + if (scale<0.95f && dist>50.0f) { + aiManager.MarkReachBlocked(aas, goalReach, path.allObstacles); + } + } + + TestTeammateCollisions(this); + + + + if ( DebugFilter(ai_showObstacleAvoidance) ) { + gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec ); + gameRenderWorld->DebugLine( !move.fl.blocked ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec ); + } +} + +/* +============ +idAI::TestAnimMove +============ +*/ +bool idAI::TestAnimMove ( int animNum, idEntity *ignore, idVec3 *pMoveVec ) { + const idAnim* anim; + predictedPath_t path; + idVec3 moveVec; + + anim = GetAnimator()->GetAnim ( animNum ); + assert ( anim ); + +// moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + moveVec = anim->TotalMovementDelta() * idAngles( 0.0f, move.current_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec / MS2SEC( anim->Length() ), anim->Length(), 200, SE_BLOCKED | SE_ENTER_LEDGE_AREA, path, ignore ); + + if ( DebugFilter(ai_debugMove) ) { // TestAnimMove + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, anim->Length() ); + } + if ( pMoveVec ) { + *pMoveVec = moveVec; + } + + return ( path.endEvent == 0 ); +} + +/* +===================== +Seek +===================== +*/ +idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) { + idVec3 predictedPos; + idVec3 goalDelta; + idVec3 seekVel; + + // predict our position + predictedPos = org + vel * prediction; + goalDelta = goal - predictedPos; +// goalDelta = goal - org; + seekVel = goalDelta * MS2SEC( gameLocal.msec ); + + return seekVel; +} + +/*********************************************************************** + + Movement + +***********************************************************************/ + +/* +===================== +idAI::DeadMove +===================== +*/ +void idAI::DeadMove( void ) { + idVec3 delta; + monsterMoveResult_t moveResult; + + DeathPush ( ); + + idVec3 org = physicsObj.GetOrigin(); + + GetAnimMoveDelta( viewAxis, viewAxis, delta ); + physicsObj.SetDelta( delta ); + + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + move.fl.onGround = physicsObj.OnGround(); +} + +/* +===================== +idAI::AdjustFlyingAngles +===================== +*/ +void idAI::AdjustFlyingAngles( void ) { + idVec3 vel; + float speed; + float roll; + float pitch; + + vel = physicsObj.GetLinearVelocity(); + + speed = vel.Length(); + if ( speed < 5.0f ) { + roll = 0.0f; + pitch = 0.0f; + } else { + roll = vel * viewAxis[ 1 ] * -move.fly_roll_scale / move.fly_speed; + if ( roll > move.fly_roll_max ) { + roll = move.fly_roll_max; + } else if ( roll < -move.fly_roll_max ) { + roll = -move.fly_roll_max; + } + + pitch = vel * viewAxis[ 0 ] * -move.fly_pitch_scale / move.fly_speed; + if ( pitch > move.fly_pitch_max ) { + pitch = move.fly_pitch_max; + } else if ( pitch < -move.fly_pitch_max ) { + pitch = -move.fly_pitch_max; + } + } + + move.fly_roll = move.fly_roll * 0.95f + roll * 0.05f; + move.fly_pitch = move.fly_pitch * 0.95f + pitch * 0.05f; + + if ( move.flyTiltJoint != INVALID_JOINT ) { + animator.SetJointAxis( move.flyTiltJoint, JOINTMOD_WORLD, idAngles( move.fly_pitch, 0.0f, move.fly_roll ).ToMat3() ); + } else { + viewAxis = idAngles( move.fly_pitch, move.current_yaw, move.fly_roll ).ToMat3(); + } +} + +/* +===================== +idAI::AddFlyBob +===================== +*/ +void idAI::AddFlyBob( idVec3 &vel ) { + idVec3 fly_bob_add; + float t; + + if ( move.fly_bob_strength ) { + t = MS2SEC( gameLocal.time + entityNumber * 497 ); + fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * move.fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * move.fly_bob_vert ) ) * move.fly_bob_strength; + vel += fly_bob_add * MS2SEC( gameLocal.msec ); + if ( DebugFilter(ai_debugMove) ) { // FlyBob + const idVec3 &origin = physicsObj.GetOrigin(); + gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 ); + } + } +} + +/* +===================== +idAI::AdjustFlyHeight +===================== +*/ +void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) { + const idVec3 &origin = physicsObj.GetOrigin(); + predictedPath_t path; + idVec3 end; + idVec3 dest; + trace_t trace; + bool goLower; + + // make sure we're not flying too high to get through doors + // FIXME: with move to enemy this thinks we're going to hit + // the enemy and then says we need to go lower... but that's not quite right... + // if we're about to hit our enemy, then maintaining our height should be desirable..? + goLower = false; + if ( origin.z > goalPos.z ) { + dest = goalPos; + dest.z = origin.z + 128.0f; + idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path, move.goalEntity.GetEntity() ); + if ( path.endPos.z < origin.z ) { + + //Hmm, should we make sure the path.endPos is high enough off the ground? + if ( move.fly_offset && (move.moveCommand == MOVE_TO_ENEMY || move.moveCommand == MOVE_TO_ATTACK) ) { + int pathArea = PointReachableAreaNum( path.endPos ); + if ( aas && (aas->AreaFlags( pathArea )&AREA_FLOOR) ) { + path.endPos.z = aas->AreaBounds( pathArea )[0][2]; + float areaTop = aas->AreaCeiling( pathArea ) - GetPhysics()->GetBounds()[1].z; + if ( path.endPos.z + move.fly_offset > areaTop ) { + path.endPos.z = areaTop; + } else { + path.endPos.z += move.fly_offset; + } + } + } + + idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION ); + vel.z += addVel.z; + goLower = true; + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Height + gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + if ( !goLower ) { + // make sure we don't fly too low + end = origin; + if ( move.moveCommand == MOVE_TO_ENEMY ) { + end.z = enemy.lastKnownPosition.z + move.fly_offset; + } else if ( move.moveCommand == MOVE_TO_ENTITY ) { + end.z = goalPos.z; + } else { + end.z = goalPos.z;// + move.fly_offset; + } + +// RAVEN BEGIN +// ddynerman: multiple collision world + idVec3 cappedEnd = (end-origin); + if ( cappedEnd.LengthFast() > 1024.0f ) { + //don't do translation predictions over 1024 + cappedEnd.Normalize(); + cappedEnd *= 1024.0f; + } + cappedEnd += origin; + gameLocal.Translation( this, trace, origin, cappedEnd, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this ); +// RAVEN END + vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION ); + } +} + +/* +===================== +idAI::FlySeekGoal +===================== +*/ +void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) { + idVec3 seekVel; + + // seek the goal position + seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION ); + seekVel *= move.fly_seek_scale; + //seekVel.Normalize(); + //vel = seekVel*move.speed; + vel += seekVel; +} + +/* +===================== +idAI::AdjustFlySpeed +===================== +*/ +void idAI::AdjustFlySpeed( idVec3 &vel ) { + float goalSpeed; + + // Slow down movespeed when we close to goal (this is similar to how AnimMove ai will + // switch to walking when they are close, it allows for more fine control of movement) + if ( move.walkRange > 0.0f ) { + float distSqr; + distSqr = (physicsObj.GetOrigin ( ) - move.moveDest).LengthSqr ( ); + goalSpeed = move.speed * idMath::ClampFloat ( 0.1f, 1.0f, distSqr / Square ( move.walkRange ) ); + } else { + goalSpeed = move.speed; + } + + // apply dampening as long as we arent within the walk range +// if ( goalSpeed != move.speed) { + float speed; + + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + + // gradually speed up/slow down to desired speed + speed = vel.Normalize(); + speed += ( goalSpeed - speed ) * MS2SEC( gameLocal.msec ); + if ( speed < 0.0f ) { + speed = 0.0f; + } else if (goalSpeed && ( speed > goalSpeed ) ) { + speed = goalSpeed; + } + + vel *= speed; +// } else { +// vel.Normalize ( ); +// vel *= goalSpeed; +// } +} + +/* +===================== +idAI::FlyTurn +===================== +*/ +void idAI::FlyTurn( void ) { + if ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( focusType != AIFOCUS_NONE && move.fl.allowDirectional ) { + DirectionalTurnToward ( currentFocusPos ); + } else if ( move.speed > 0.0f ) { + const idVec3 &vel = physicsObj.GetLinearVelocity(); + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + + Turn(); +} + +/* +===================== +idAI::FlyMove +===================== +*/ +void idAI::FlyMove( void ) { + idVec3 oldorigin; + idVec3 newDest; + + move.fl.blocked = false; + if ( ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) && ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), aiMoveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, move.fly_speed ); + } + + // Dont move when movement is disabled + if ( !CanMove() || legsAnim.Disabled ( ) ) { + // Still allow turning though + FlyTurn ( ); + + if ( (aifl.action || aifl.scripted ) && legsAnim.Disabled () && move.fl.allowAnimMove ) { + idMat3 oldaxis = viewAxis; + idVec3 delta; + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + physicsObj.UseFlyMove( false ); + if ( spawnArgs.GetBool( "alwaysBob" ) ) { + AddFlyBob( delta ); + } + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( true ); + + RunPhysics(); + } else if ( spawnArgs.GetBool( "alwaysBob" ) ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + AddFlyBob( vel ); + physicsObj.SetLinearVelocity( vel ); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + } + + UpdateAnimationControllers ( ); + return; + } + + if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) { + idVec3 vel = physicsObj.GetLinearVelocity(); + + if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + move.seekPos.x = newDest.x; + move.seekPos.y = newDest.y; + } + + if ( move.speed ) { + FlySeekGoal( vel, move.seekPos ); + } + + // add in bobbing + AddFlyBob( vel ); + + if ( ( move.moveCommand != MOVE_TO_POSITION ) ) { + AdjustFlyHeight( vel, move.seekPos ); + } + + AdjustFlySpeed( vel ); + + vel += move.addVelocity; + move.addVelocity.Zero(); + + physicsObj.SetLinearVelocity( vel ); + } + + // turn + FlyTurn(); + + // run the physics for this frame + oldorigin = physicsObj.GetOrigin(); + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + RunPhysics(); + + monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else if ( moveResult == MM_BLOCKED ) { + move.blockTime = gameLocal.time + 500; + move.fl.blocked = true; + } + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Fly Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 ); + gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorBlue, org, move.seekPos, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition ( ), GetEyePosition ( ) + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +============ +idAI::UpdatePlayback +============ +*/ +void idAI::UpdatePlayback ( idVec3 &goalPos, idVec3 &delta, idVec3 &oldorigin, idMat3 &oldaxis ) { + rvDeclPlaybackData pbd; + bool atDest; + + // New playback stuff + if( !mPlayback.IsActive() ) { + return; + } + + atDest = mPlayback.UpdateFrame( this, pbd ); + + goalPos = pbd.GetPosition(); + SetOrigin( goalPos ); + viewAxis = pbd.GetAngles().ToMat3(); + + // Keep the yaw updated + idVec3 local_dir; + physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir ); + move.current_yaw = local_dir.ToYaw(); + move.ideal_yaw = idMath::AngleNormalize180( move.current_yaw ); + + OnUpdatePlayback ( pbd ); +} + +/* +============ +idAI:: +============ +*/ + +void idAI::PlaybackMove( void ){ + idVec3 goalPos; + idVec3 delta; + idVec3 goalDelta; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + move.obstacle = NULL; + + goalPos = oldorigin; + + UpdatePlayback( goalPos, delta, oldorigin, oldaxis ); + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + physicsObj.UseFlyMove( true ); + physicsObj.UseVelocityMove( false ); + physicsObj.SetLinearVelocity( vec3_zero ); + physicsObj.SetDelta( vec3_zero ); + physicsObj.ForceDeltaMove( true ); + RunPhysics(); + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } else { + move.fl.blocked = true; + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // Playback Move + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::StaticMove +===================== +*/ +void idAI::StaticMove( void ) { + idEntity* enemyEnt = enemy.ent; + + if ( aifl.dead ) { + return; + } + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemyEnt ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else if ( move.moveCommand != MOVE_NONE ) { + TurnToward( move.moveDest ); + } + Turn(); + + physicsObj.ForceDeltaMove( true ); // disable gravity + RunPhysics(); + + move.fl.onGround = false; + + if ( DebugFilter(ai_debugMove) ) { // Static Move + const idVec3 &org = physicsObj.GetOrigin(); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + } +} + +/* +===================== +idAI::SlideMove +===================== +*/ +void idAI::SlideMove( void ) { + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + move.fl.blocked = false; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.seekPos = move.moveDest; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.seekPos = move.moveDest; + } else if ( GetMovePos( move.seekPos ) ) { + CheckObstacleAvoidance( move.seekPos, newDest ); + TurnToward( newDest ); + move.seekPos = newDest; + } + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.seekPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + } else { + move.seekPos = move.moveDest; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } + + if ( move.moveCommand == MOVE_TO_POSITION ) { + goalDelta = move.moveDest - oldorigin; + goalDist = goalDelta.LengthFast(); + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + idVec3 vel = physicsObj.GetLinearVelocity(); + float z = vel.z; + idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION; + + // seek the goal position + goalDelta = move.seekPos - predictedPos; + vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec ); + vel += goalDelta * MS2SEC( gameLocal.msec ); + + // cap our speed + vel.Truncate( move.fly_speed ); + vel.z = z; + physicsObj.SetLinearVelocity( vel ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); + + if ( ( move.moveCommand == MOVE_FACE_ENEMY || ForceFaceEnemy() ) && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + } else { + if ( vel.ToVec2().LengthSqr() > 0.1f ) { + TurnToward( vel.ToYaw() ); + } + } + Turn(); + + if ( DebugFilter(ai_debugMove) ) { // Slide Move + gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); + } + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + idVec3 org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // SlideMove + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugLine( colorYellow, GetEyePosition(), GetEyePosition() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true ); + DrawRoute(); + } +} + +/* +===================== +idAI::AnimMove +===================== +*/ +void idAI::AnimMove( void ) { + + if ( ai_useRVMasterMove.GetBool ( ) ) { + RVMasterMove(); + return; + } + + idVec3 delta; + idVec3 goalDelta; + float goalDist; + monsterMoveResult_t moveResult; + idVec3 newDest; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idReachability* goalReach; + // RAVEN END + + idVec3 oldorigin = physicsObj.GetOrigin(); + idMat3 oldaxis = viewAxis; + + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){ + move.lastMoveOrigin.Zero(); + //move.lastMoveTime = gameLocal.time; + } + + move.obstacle = NULL; + if ( move.moveCommand == MOVE_FACE_ENEMY && enemy.ent ) { + TurnToward( enemy.lastKnownPosition ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) { + TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() ); + move.goalPos = oldorigin; + move.seekPos = oldorigin; + } else if ( move.moveCommand >= NUM_NONMOVING_COMMANDS ) { + if ( ReachedPos( move.moveDest, move.moveCommand, move.range ) ) { + StopMove( MOVE_STATUS_DONE ); + } else { + move.moveStatus = MOVE_STATUS_MOVING; + + // Otherwise, Update The Seek Pos + if ( !aifl.simpleThink && GetMovePos( move.goalPos, &goalReach ) ) { + if ( move.moveCommand != MOVE_WANDER ) { + CheckObstacleAvoidance( move.goalPos, move.seekPos, goalReach ); + } else { + move.seekPos = move.goalPos; + } + DirectionalTurnToward ( move.seekPos ); + } + } + } + + Turn(); + + goalDelta = move.seekPos - oldorigin; + goalDist = goalDelta.LengthFast(); + + // FIXME: this stuff should really move to GetMovePos (Steering) + if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) { + if ( gameLocal.time < move.startTime + move.duration ) { + move.goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); + delta = move.goalPos - oldorigin; + delta.z = 0.0f; + } else { + delta = move.moveDest - oldorigin; + delta.z = 0.0f; + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + StopMove( MOVE_STATUS_DONE ); + } + } else if ( move.fl.allowAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + } else if ( move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( oldaxis, viewAxis, delta ); + float speed = delta.LengthFast(); + delta = goalDelta; + delta.Normalize(); + delta *= speed; + } else { + delta.Zero(); + } + + if ( move.moveCommand > NUM_NONMOVING_COMMANDS ) { + //actually *trying* to move to a goal + if ( goalDist < delta.LengthFast() ) { + delta = goalDelta; + } + } + + physicsObj.SetDelta( delta ); + physicsObj.ForceDeltaMove( move.fl.noGravity ); + + RunPhysics(); + + + moveResult = physicsObj.GetMoveResult(); + idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockEnt && blockEnt->IsType( idMoveable::GetClassType() ) && blockEnt->GetPhysics()->IsPushable() ) { +// RAVEN END + KickObstacles( viewAxis[ 0 ], move.kickForce, blockEnt ); + } + + BlockedFailSafe(); + + move.fl.onGround = physicsObj.OnGround(); + + const idVec3& org = physicsObj.GetOrigin(); + if ( oldorigin != org ) { + TouchTriggers(); + } + + if ( DebugFilter(ai_debugMove) ) { // AnimMove : GREEN / RED Bounds & Move Dest + gameRenderWorld->DebugLine( colorCyan, oldorigin, org, 5000 ); + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), org, gameLocal.msec ); + if (!ReachedPos( move.moveDest, move.moveCommand, move.range )) { + gameRenderWorld->DebugBounds( (team==0)?(colorGreen):(colorRed), physicsObj.GetBounds(), move.moveDest, gameLocal.msec ); + gameRenderWorld->DebugArrow( (team==0)?(colorGreen):(colorRed), org, move.moveDest, 4, gameLocal.msec ); + } + DrawRoute(); + } +} + +/* +===================== +idAI::CustomMove +===================== +*/ +void idAI::CustomMove( void ) { + // derived class must implement this +} + + + +/* +====================================================================================== + NEW MOVE CODE BEGIN +====================================================================================== +*/ + +struct rvObstacle; + +const float REACHED_RADIUS = 4.0f; +const float REACHED_RADIUS_SQUARE = REACHED_RADIUS*REACHED_RADIUS; + + + + + + +/* +===================== +LineIntersection2D +===================== +*/ +bool LineIntersection2D(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& contactPoint) { + + // Test If Parallel + //------------------ + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)<0.01f) { + return false; + } + + // Test CD Edge + //-------------- + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f) { + return false; + } + if (s>1.0f) { + return false; + } + + // Test AB Edge + //-------------- + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r<0.0f) { + return false; + } + if (r>1.0f) { + return false; + } + + contactPoint = A + r*(B-A); + return true; +} + + +/* +===================== +rvWindingBox + +Clockwise winding with a simple line intersection test + +[1]---->[2] + ^ | + | verts | + | V +[0]<----[3] + +===================== +*/ +struct rvWindingBox { + idVec3 verts[4]; + aasArea_t* areas[4]; + rvObstacle* obstacles[4]; // CDR_TODO: Should be a list of obstacles for each vertex + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + for (int i=0; i<4; i++) { + verts[i] = vec3_zero; + areas[i] = NULL; + obstacles[i] = NULL; + } + } + + /* + ===================== + FromBounds + ===================== + */ + void FromBounds(const idBounds& b) { + verts[0].x = b[0].x; + verts[0].y = b[0].y; + verts[0].z = b[0].z; + + verts[1].x = b[0].x; + verts[1].y = b[1].y; + verts[1].z = b[0].z; + + verts[2].x = b[1].x; + verts[2].y = b[1].y; + verts[2].z = b[0].z; + + verts[3].x = b[1].x; + verts[3].y = b[0].y; + verts[3].z = b[0].z; + } + + /* + ===================== + LineIntersection + ===================== + */ + bool LineIntersection(const idVec3& start, const idVec3& end, idVec3& contactPoint, int& v1, int& v2) const { + for (int i=0; i<4; i++) { + v1 = i; + v2 = (i<3)?(i+1):(0); + + if (start.IsLeftOf(verts[v1], verts[v2]) && LineIntersection2D(start, end, verts[v1], verts[v2], contactPoint)) { + return true; + } + } + return false; + } + + /* + ===================== + PointInside + ===================== + */ + bool PointInside(const idVec3& point) const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + if (point.IsLeftOf(vert1, vert2)) { + return false; + } + } + return true; + } + + /* + ===================== + DrawDebugGraphics + ===================== + */ + bool DrawDebugGraphics() const { + for (int i=0; i<4; i++) { + const idVec3& vert1 = verts[i]; + const idVec3& vert2 = verts[(i<3)?(i+1):(0)]; + + gameRenderWorld->DebugLine(colorYellow, vert1, vert2, gameLocal.msec); + if (!areas[i] || obstacles[i]) { + gameRenderWorld->DebugLine(colorRed, vert1, vert1+idVec3(0.0f,0.0f,16.0f), gameLocal.msec); + } else if (areas[i]) { + // gameRenderWorld->DebugLine(colorYellow, vert1, areas[i]->center, gameLocal.msec); + } + } + return true; + } +}; + + + + +/* +===================== +rvMarker + +A marker represents an obstacle within +an area. Any single obstacle can have +any number of markers in any number of +areas that it touches +===================== +*/ +struct rvMarker { + rvObstacle* obstacle; + aasArea_t* area; + rvMarker* prev; + rvMarker* next; +}; +rvPool markerPool; + +/* +===================== +rvObstacle +===================== +*/ +struct rvObstacle { + static idAASFile* searchFile; + static idBounds searchBounds; + + entityPointer_t entity; + idVec3 origin; + idVec3 originFuture; + int lastTimeMoved; + bool pendingUpdate; + + rvWindingBox windings[3]; + idList markers; + idList areas; + + /* + ============ + Initialize + ============ + */ + void Initialize(idEntity* ent) { + entity = ent; + origin = vec3_zero; + originFuture = vec3_zero; + lastTimeMoved = 0; + pendingUpdate = true; + + for (int i=0; i<3; i++) { + windings[i].Initialize(); + } + markers.Clear(); + areas.Clear(); + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + if ((gameLocal.time - lastTimeMoved) < 1000) { + gameRenderWorld->DebugArrow(colorBrown, origin, originFuture, 3, gameLocal.msec); + } + + windings[0].DrawDebugGraphics(); + } + + /* + ============ + RemoveMarkers + ============ + */ + void RemoveMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==this); + + if (marker->area->firstMarker==marker) { + marker->area->firstMarker = marker->next; + } + if (marker->prev) { + marker->prev->next = marker->next; + } + if (marker->next) { + marker->next->prev = marker->prev; + } + + marker->obstacle = NULL; + + markerPool.free(marker); + } + markers.Clear(); + } + /* + ============ + SearchAreas_r + ============ + */ + void SearchAreas_r( int nodeNum ) { + int side; + const aasNode_t *node; + + while( nodeNum != 0 ) { + + // Negative nodeNum signifies a Leaf Area + if ( nodeNum < 0 ) { + if ( searchFile->GetArea(-nodeNum).flags&AREA_REACHABLE_WALK ) { + areas.AddUnique( &searchFile->GetArea(-nodeNum) ); + } + break; + } + node = &searchFile->GetNode( nodeNum ); + side = searchBounds.PlaneSide( searchFile->GetPlane( node->planeNum ) ); + if ( side == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else if ( side == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else { + SearchAreas_r( node->children[1] ); + nodeNum = node->children[0]; + } + } + } + + /* + ============ + PointInsideArea + ============ + */ + bool PointInsideArea( const idVec3& point, aasArea_t* area ) { + int i, faceNum; + + for ( i = 0; i < area->numFaces; i++ ) { + faceNum = searchFile->GetFaceIndex(area->firstFace + i); + + const aasFace_t& face = searchFile->GetFace(abs( faceNum )); + if (!(face.flags & FACE_FLOOR)) { + const idPlane& plane = searchFile->GetPlane(face.planeNum ^ INTSIGNBITSET( faceNum )); + if (plane.Side(point) == PLANESIDE_BACK) { + return false; + } + } + } + return true; + } + + /* + ============ + AddMarkers + ============ + */ + void AddMarkers() { + static rvMarker* marker; + + for (int i=0; iobstacle==NULL); + + // Setup The New Marker + //---------------------- + marker->obstacle = this; + marker->area = areas[i]; + marker->next = NULL; + marker->prev = NULL; + + + // Fixup Any Existing Linked List (First Marker) + //----------------------------------------------- + if (marker->area->firstMarker) { + marker->area->firstMarker->prev = marker; + marker->next = marker->area->firstMarker; + } + + // Add This Marker At THe Area's Head + //------------------------------------ + marker->area->firstMarker = marker; + + markers.Append(marker); + } + } + + + + /* + ============ + Update + ============ + */ + bool Update() { + static rvMarker* marker; + static rvObstacle* obstacle; + static aasArea_t* area; + static idVec3 expand; + static float speed; + static float distance; + static idVec3 direction; + static int aasFileNum, t, v, a; + static idList touched; + static rvWindingBox* myWinding; + + + pendingUpdate = false; + idEntity* ent = entity.GetEntity(); + if (!ent || ent->health<=0 || ent->fl.hidden || !ent->GetPhysics()) { + RemoveMarkers(); + return false; // Means This Obstacle Structure Should Be Retired + } + + + // Only Update If We've Moved Far Enough + //--------------------------------------- + idPhysics* physics = ent->GetPhysics(); + if (origin.Dist2XY(physics->GetOrigin())<100.0f) { // CDR_TODO: Scale This By Distance To Player, Other Obstacles Near... + return true; + } + + // Get The Obstacle's Seek Direction & Speed + //------------------------------------------- + if (ent->IsType(idAI::GetClassType())) { + speed = (((idAI*)ent)->move.fl.done)?(0.0f):(physics->GetLinearVelocity().LengthFast()) * 2.5f; + direction = (((idAI*)ent)->move.seekPos - physics->GetOrigin()); + distance = direction.NormalizeFast(); + + // Cap The Projection To The Seek Position + if (speed > distance) { + speed = distance; + } + } else { + direction = physics->GetLinearVelocity(); + speed = direction.NormalizeFast() * 2.5f; + } + + origin = physics->GetOrigin(); + originFuture = origin + (direction*speed); // 2.5 seconds into the future predicion + lastTimeMoved = gameLocal.time; + + + // Remove Old Markers + //-------------------- + RemoveMarkers(); + + + // Get The Entity's Bounds And Areas That These Bounds Cover + //----------------------------------------------------------- + for (aasFileNum=0; aasFileNumGetFile(); + searchBounds = physics->GetAbsBounds(); + expand = searchFile->GetSettings().boundingBoxes[0].Size(); + expand[0] *= 0.5f; + expand[1] *= 0.5f; + expand[2] = 0; + expand[0] += REACHED_RADIUS; + expand[1] += REACHED_RADIUS; + + searchBounds.ExpandSelf(expand); + SearchAreas_r(1); + + // Setup The Winding From The Bounds + //----------------------------------- + myWinding->FromBounds(searchBounds); + + // Setup Each Vertex On The Winding + //---------------------------------- + for (v=0; v<4; v++) { + const idVec3& myVertex = myWinding->verts[v]; + + myWinding->areas[v] = NULL; + for (a=0; aareas[v] = area; + + + // Search This Area For All Obstacles That May Contain This Vertex + //----------------------------------------------------------------- + obstacle = NULL; + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle->windings[aasFileNum].PointInside(myVertex)) { + obstacle = marker->obstacle; + break; + } + } + + // Update The Obstacle On The Winding + //------------------------------------ + if (myWinding->obstacles[v] && myWinding->obstacles[v]!=obstacle) { + touched.AddUnique(myWinding->obstacles[v]); + } + if (obstacle) { + touched.AddUnique(obstacle); + } + + myWinding->obstacles[v] = obstacle; + break; + } + } + } + + // Touched Obstacles Need To Test Their Verts Against This Winding + //----------------------------------------------------------------- + for (t=0; tPointInside(obstacle->windings[aasFileNum].verts[v])) { + obstacle->windings[aasFileNum].obstacles[v] = this; + } else if (obstacle->windings[aasFileNum].obstacles[v]==this) { + obstacle->windings[aasFileNum].obstacles[v] = NULL; + } + } + } + + // AddMarkers In Each Found Area + //------------------------------- + AddMarkers(); + } + + return true; + } + + /* + ===================== + VertexValid + ===================== + */ + static bool VertexValid(const rvWindingBox& bounds, int v, const aasArea_t* inArea, const idEntity* ignore) { + return (bounds.areas[v] && (bounds.obstacles[v]==NULL || bounds.obstacles[v]->entity.GetEntity()==ignore)); + } + + /* + ============ + MovedRecently + ============ + */ + bool MovedRecently(int time=800) { + if ((gameLocal.time - lastTimeMoved) < time) { + return true; + } + // idEntity* ent = entity.GetEntity(); + // if (ent && ent->IsType(idAI::GetClassType())) { + // return !((idAI*)ent)->move.fl.done; + // } + return false; + } +}; +rvIndexPool obstaclePool; + +idAASFile* rvObstacle::searchFile; +idBounds rvObstacle::searchBounds; + + + + + + +/* +===================== +rvObstacleFinder +===================== +*/ +struct rvObstacleFinder { + idList obstaclesPendingUpdate; + int obstaclesUpdateTime; + + + struct traceResult_t { + rvObstacle* obstacle; + idVec3 endPoint; + float distance; + int v1; + int v2; + bool v1Valid; + bool v2Valid; + }; + traceResult_t contact; + + + + + /* + ============ + Initialize + ============ + */ + void Initialize() { + markerPool.clear(); + obstaclePool.clear(); + + obstaclesPendingUpdate.Clear(); + obstaclesUpdateTime = 0; + memset(&contact, 0, sizeof(contact)); + + // Clear All The Marker Pointers In Any Areas + //-------------------------------------------- + for (int i=0; iGetFile(); + for (int a=0; aGetNumAreas(); a++) { + file->GetArea(a).firstMarker = NULL; + } + } + } + } + + /* + ============ + DrawDebugGraphics + ============ + */ + void DrawDebugGraphics() { + static int nextDrawTime=0; + static rvObstacle* obstacle; + if (nextDrawTime>=gameLocal.time) { + return; + } + nextDrawTime = gameLocal.time; + + for (int i=0; iDrawDebugGraphics(); + } + } + } + + /* + ============ + UpdateObstacles + ============ + */ + void UpdateObstacles() { + static rvObstacle* obstacle; + static float entityNum; + + if (obstaclesUpdateTime < gameLocal.time && obstaclesPendingUpdate.Num()) { + obstaclesUpdateTime = gameLocal.time + 50; + + // CDR_TODO: Priority Queue Of Updates? Track Last Update Time Perhaps? + while (obstaclesPendingUpdate.Num()) { + obstacle = obstaclesPendingUpdate.StackTop(); + obstaclesPendingUpdate.StackPop(); + + if (!obstacle->Update()) { + obstaclePool.free(obstacle->entity.GetEntityNum()); + } + } + } + } + + /* + ============ + MarkEntityForUpdate + ============ + */ + void MarkEntityForUpdate(idEntity* ent) { + + // Ignore Non Physics Entities + //----------------------------- + if (!ent || !ent->GetPhysics()) { + return; + } + + // Ignore Obstacles That Are Already Pending + //-------------------------------------------- + if (obstaclePool.valid(ent->entityNumber) && obstaclePool[ent->entityNumber]->pendingUpdate) { + return; + } + + if (!obstaclePool.valid(ent->entityNumber)) { + + // If No More Obstacles Are Available, Ignore This One + //----------------------------------------------------- + if (obstaclePool.full()) { + return; + } + + obstaclePool.alloc(ent->entityNumber)->Initialize(ent); + } + + + obstaclePool[ent->entityNumber]->pendingUpdate = true; + obstaclesPendingUpdate.Append(obstaclePool[ent->entityNumber]); + } + + + /* + ============ + RecordContact + ============ + */ + void RecordContact(float maxDistance, const idVec3& start, rvObstacle* obstacle, const idVec3& point, int vert1=-1, int vert2=-1) { + static float distance; + distance = point.DistXY(start); + if ((maxDistance==0.0f || distance distance || !contact.obstacle)) { + contact.obstacle = obstacle; + contact.endPoint = point; + contact.distance = distance; + contact.v1 = vert1; + contact.v2 = vert2; + } + } + + /* + ============ + RayTrace + ============ + */ + bool RayTrace(float maxDistance, const aasArea_t* area, const idVec3& start, const idVec3& stop, int aasNum, const idEntity* ignore1, const idEntity* ignore2=NULL, const idEntity* ignore3=NULL) { + static rvMarker* marker; + static idVec3 p; + static int v1; + static int v2; + static rvObstacle* ignoreA; + static rvObstacle* ignoreB; + static rvObstacle* ignoreC; + static idEntity* ent; + static idVec3 startBack; + static idVec3 direction; + static bool pulledBack; + + ignoreA = (ignore1 && obstaclePool.valid(ignore1->entityNumber))?(obstaclePool[ignore1->entityNumber]):(NULL); + ignoreB = (ignore2 && obstaclePool.valid(ignore2->entityNumber))?(obstaclePool[ignore2->entityNumber]):(NULL); + ignoreC = (ignore3 && obstaclePool.valid(ignore3->entityNumber))?(obstaclePool[ignore3->entityNumber]):(NULL); + + contact.obstacle = NULL; + pulledBack = false; + + + for (marker=area->firstMarker; marker; marker=marker->next) { + if (marker->obstacle==NULL || marker->obstacle==ignoreA || marker->obstacle==ignoreB || marker->obstacle==ignoreC) { + continue; + } + + // Handle Moving Obstacles Differently + //------------------------------------- + if (marker->obstacle->MovedRecently()) { + + // Ignore Moving Actors With Higher Entity Numbers + //------------------------------------------------- + ent = marker->obstacle->entity.GetEntity(); + if (ent && ignore1 && ent->entityNumber>ignore1->entityNumber && ent->IsType(idActor::GetClassType())) { + continue; + } + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Moving Obstacle's Predicted Path + //---------------------------------------------------------- + if (LineIntersection2D(startBack, stop, marker->obstacle->origin, marker->obstacle->originFuture, p)) { + RecordContact(maxDistance, startBack, marker->obstacle, p); + } + + + // Stationary Obstacles + //---------------------- + } else { + + // CDR_TODO: Special Line Intersection Test For Player Forward Aim And On Same Team + //---------------------------------------------------------------------------------- + + + // Pull The Start Back To Avoid Starting "In Solid" + //-------------------------------------------------- + if (!pulledBack) { + pulledBack = true; + direction = stop - start; + direction.Normalize(); + startBack = start - (direction*12.0f); // CDR_TODO: Use Radius + } + + // Record Contact With The Stationary Obstacle's Position + //-------------------------------------------------------- + if (marker->obstacle->windings[aasNum].LineIntersection(startBack, stop, p, v1, v2)) { + RecordContact(maxDistance, startBack, marker->obstacle, p, v1, v2); + } + } + } + + if (contact.obstacle) { + if (contact.obstacle->MovedRecently()) { + contact.v1Valid = true; + contact.v2Valid = true; + } else { + contact.v1Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v1, area, ignore1); + contact.v2Valid = rvObstacle::VertexValid(contact.obstacle->windings[aasNum], contact.v2, area, ignore1); + } + + return true; + } + return false; + } +}; +rvObstacleFinder obstacleFinder; + + + + + +/* +===================== +rvPathFinder +===================== +*/ +class rvPathFinder { +public: + /* + ===================== + visitNode + ===================== + */ + struct visitNode { + float costToGoal; + float travelCost; + bool closed; + int vertexNum; + idReachability* reach; + visitNode* from; + + float cost() { + return travelCost + costToGoal; + } + }; + + static int visitSort( const void *a, const void *b ) { + return (int)( (*((visitNode**)b))->cost() - (*((visitNode**)a))->cost() ); + } + + + + enum { + MAX_VISITED = 512, + MAX_OPEN = 255, + MAX_PENDING = 60, + }; + + idAAS* myAAS; + float myRadius; + idMoveState* myMove; + int myTeam; + const idEntity* myIgnoreEntity; + const idEntity* myIgnoreEntity2; + bool drawVisitTree; + + visitNode* next; + + visitNode* open[MAX_OPEN]; + int openCount; + bool openListUpdate; + + visitNode* pending[MAX_PENDING]; + visitNode* pendingBest; + int pendingCount; + + visitNode visited[MAX_VISITED]; + int visitedCount; + idHashIndex visitedIndexReach; + idHashIndex visitedIndexVert; + + + + + + /* + ===================== + Initialize + ===================== + */ + void Initialize() { + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + next = NULL; + } + + + /* + ===================== + Close + ===================== + */ + void Close(visitNode* node) { + node->closed = true; + } + + /* + ===================== + DrawVisitTree + ===================== + */ + void DrawVisitTree() { + for (int i=0; imyPos); + const idVec3& stop = (GetSeekPosition(&visited[i])); + const idVec4& color = (visited[i].closed)?(colorOrange):(colorYellow); + const int duration = (visited[i].closed)?(3000):(500); + + gameRenderWorld->DebugArrow(color, start, stop, 3, duration); + } + } + + /* + ===================== + XYLineIntersection + ===================== + */ + bool XYLineIntersection(const idVec3& A, const idVec3& B, const idVec3& C, const idVec3& D, idVec3& P) { + float q = (((B.x-A.x)*(D.y-C.y))-((B.y-A.y)*(D.x-C.x))); + if (fabsf(q)>0.01f) { + float s = (((A.y-C.y)*(B.x-A.x))-((A.x-C.x)*(B.y-A.y))) / q; + if (s<0.0f || s>1.0f) { + return false; + } + + float r = (((A.y-C.y)*(D.x-C.x))-((A.x-C.x)*(D.y-C.y))) / q; + if (r>1.0f) { + P = B; + return false; + } + if (r<0.0f) { + P = A; + return false; + } + + P = A + r*(B-A); + return true; + } + + // Lines Are Parallel, No Intersection + return false; + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(idReachability* reach, int vertexNum) { + return ((vertexNum)?(myAAS->GetFile()->GetVertex(vertexNum)):(reach->start)); + } + + /* + ===================== + GetSeekPosition + ===================== + */ + const idVec3& GetSeekPosition(visitNode* node) { + return GetSeekPosition(node->reach, node->vertexNum); + } + + /* + ===================== + Success + ===================== + */ + bool Success(visitNode* node) { + static idVec3 edgeA; + static idVec3 edgeB; + static idVec3 intersect; + static idVec3 direction; + static idVec3 smoothedPos; + static int at; + static int count; + static rvMarker* marker; + static bool isCorner; + + // Always Add The Goal Pos At The End Of The Path + //------------------------------------------------ + myMove->path[myMove->pathLen].reach = NULL; + myMove->path[myMove->pathLen].seekPos = myMove->goalPos; + myMove->pathLen ++; + + // Build The Path + //---------------- + while (node && myMove->pathLenpath[myMove->pathLen].reach = node->reach; + myMove->path[myMove->pathLen].seekPos = GetSeekPosition(node); + + myMove->pathLen ++; + node = node->from; + } + + + // Additional Path Point Modifications + //------------------------------------- + count = myMove->pathLen-1; + for (at=count; at>0; at--) { + pathSeek_t& pathPrev = myMove->path[at+1]; + pathSeek_t& pathAt = myMove->path[at]; + pathSeek_t& pathNext = myMove->path[at-1]; + + myAAS->GetEdge(pathAt.reach->edgeNum, edgeA, edgeB); + + // Smooth The Path One Pass + //-------------------------- + if (pathAt.reach->travelType==TFL_WALK) { + const idVec3& walkA = (at==count) ? (myMove->myPos) :(pathPrev.seekPos); + const idVec3& walkB = (at==0) ? (myMove->goalPos) :(pathNext.seekPos); + + isCorner = !XYLineIntersection(edgeA, edgeB, walkA, walkB, smoothedPos); + + + // If The Smoothed Position Is Not Blocked By An Obstacle + //-------------------------------------------------------- + aasArea_t* area = &myAAS->GetFile()->GetArea(pathAt.reach->toAreaNum); + for (marker=area->firstMarker; marker; marker=marker->next) { + if (!marker->obstacle || marker->obstacle->entity.GetEntity()==myIgnoreEntity || marker->obstacle->entity.GetEntity()==myIgnoreEntity2) { + continue; + } + if (marker->obstacle->windings[0/*CDR_TODO: use aasNum*/].PointInside(smoothedPos)) { + break; + } + } + if (!marker) { + pathAt.seekPos = smoothedPos; + + // Push Away From The Corner A Bit + //--------------------------------- + if (isCorner) { + smoothedPos.ProjectToLineSeg(walkA, walkB); + direction = pathAt.seekPos - smoothedPos; + direction.NormalizeFast(); + pathAt.seekPos += direction * (REACHED_RADIUS+1.0f); + } + } + } + } + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return true; + } + + /* + ===================== + Failure + ===================== + */ + bool Failure() { + + // CDR_TODO: Record Statistics Here + if (drawVisitTree) { + DrawVisitTree(); + } + return false; + } + + + /* + ===================== + ErrorCondition + ===================== + */ + bool ErrorCondition() { + assert(0); + // Stats? + return false; + } + + /* + ===================== + SelectNextVisitedNode + ===================== + */ + visitNode* SelectNextVisitedNode() { + if (pendingBest && (!openCount || open[openCount-1]->cost() > pendingBest->cost())) { + next = pendingBest; + pendingBest = NULL; + openListUpdate = true; + return next; + } + + if (openCount) { + openCount--; + return open[openCount]; + } + return NULL; + } + + /* + ===================== + Open + ===================== + */ + void Open(visitNode* node) { + node->closed = false; + + // If This Node Is Cheaper Than The Existing Best Open Node, Add It To The End Of The Open List + //---------------------------------------------------------------------------------------------- + if (openCountcost() < open[openCount-1]->cost())) { + open[openCount] = node; + openCount++; + return; + } + } + + // Otherwise, Add It To The Pending List + //--------------------------------------- + if (pendingCountcost() < pendingBest->cost())) { + pendingBest = node; + } + } + } + + /* + ===================== + UpdateOpenList + ===================== + */ + void UpdateOpenList() { + + // List Is Updated Now, So Remove The Flag + //----------------------------------------- + openListUpdate = false; + + // Add All Pending Nodes To The Open List + //---------------------------------------- + for (int i=0; iclosed) { + open[openCount] = pending[i]; + openCount ++; + } + } + pendingCount = 0; + pendingBest = NULL; + + + // Sort The Open List + //-------------------- + qsort( (void*)open, (size_t)openCount, (size_t)sizeof(visitNode*), visitSort ); + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(idReachability* reach) { + for (int i=visitedIndexReach.First(abs(reach->edgeNum)); i>=0; i=visitedIndexReach.Next(i)) { + if (visited[i].reach==reach) { + return &visited[i]; + } + } + return NULL; + } + + /* + ===================== + WasVisited + ===================== + */ + visitNode* WasVisited(int vertexNum) { + for (int i=visitedIndexVert.First(vertexNum); i>=0; i=visitedIndexVert.Next(i)) { + if (visited[i].vertexNum==vertexNum) { + return &visited[i]; + } + } + return NULL; + } + + + /* + ===================== + TravelCost + ===================== + */ + float TravelCost(idReachability* reach, int vertexNum, visitNode* from) { + static float distance; + + const idVec3& start = (from)?(GetSeekPosition(from)):(myMove->myPos); + const idVec3& stop = GetSeekPosition(reach, vertexNum); + + + // Test For Any Obstacles In The Way + //----------------------------------- + const aasArea_t* area = &myAAS->GetFile()->GetArea((from)?(from->reach->toAreaNum):(myMove->myArea)); + if (obstacleFinder.RayTrace(0.0f, area, start, stop, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + + // If It Is Not Possible To Steer Around, Then This Edge Is Completely Invalid + //----------------------------------------------------------------------------- + if (!obstacleFinder.contact.v1Valid && !obstacleFinder.contact.v2Valid) { + return 0.0f; + } + + // Completely Disable Any Points Completely Covered By An Obstacle + //----------------------------------------------------------------- + if (obstacleFinder.contact.obstacle->windings[0/*CDR_TODO: Get myAASNum*/].PointInside(stop)) { + return 0.0f; + } + distance += 128.0f; + } + + + // Compute Standard Distance + //--------------------------- + distance = start.Dist(stop); + if (from) { + distance += from->travelCost + from->reach->travelTime; + } + + return distance; + } + + + /* + ===================== + Visit + ===================== + */ + void Visit(idReachability* reach, int vertexNum, visitNode* from) { + static visitNode* visit; + static float travelCost; + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Compute Travel Cost, And Test To See If This Edge Is Blocked + //-------------------------------------------------------------- + travelCost = TravelCost(reach, vertexNum, from); + if (travelCost==0.0f) { + return; + } + + // If The Visited Version Is Already Less Costly, Then Ignore This Reach + //----------------------------------------------------------------------- + visit = (vertexNum)?(WasVisited(vertexNum)):(WasVisited(reach)); + if (visit && visit->travelCost<=travelCost) { + return; + } + + + + // Reopen Any Nodes That Were Closed + //----------------------------------- + if (visit && visit->closed) { + Open(visit); + + // Otherwise, If The Node Is Already In The Open List, Just Change The Cost And Mark The List For Resorting + //---------------------------------------------------------------------------------------------------------- + } else if (visit) { + visit->from = from; + visit->travelCost = travelCost; + openListUpdate = true; + + // Must Never Have Visited This Node Before, So Make A Whole New One + //------------------------------------------------------------------- + } else { + + const idVec3& pos = GetSeekPosition(reach, vertexNum); + + // Constant Data (Will Never Change) + //----------------------------------- + visit = &visited[visitedCount]; + visit->reach = reach; + visit->vertexNum = vertexNum; + visit->costToGoal = pos.Dist(myMove->goalPos); + + // Temporary Data + //---------------- + visit->from = from; + visit->travelCost = travelCost; + visit->closed = false; + + // Add It To The Hash Table To Be Found Later + //-------------------------------------------- + if (!vertexNum) { + visitedIndexReach.Add(abs(reach->edgeNum), visitedCount); + } else { + visitedIndexVert.Add(vertexNum, visitedCount); + } + visitedCount++; + + // Mark It As Open + //----------------- + Open(visit); + } + } + + /* + ===================== + VisitReach + + This function first visits the center of the reachability, and then if + the edge is long enough and close enough to the start or end of the + path, it visits the verts as well + ===================== + */ + void VisitReach(idReachability *reach, visitNode* from) { + static int verts[2]; + static int vertexNum; + static idVec3 start; + static idVec3 stop; + + + // If Full, Stop Visiting Anything + //--------------------------------- + if (visitedCount>=MAX_VISITED) { + return; + } + + // Ignore Any Reach To The Area We Came From + //------------------------------------------- + if (from && from->reach->fromAreaNum==reach->toAreaNum) { + return; + } + + // Ignore Any Reach That Does Not Match Our Travel Flags + //------------------------------------------------------- + if (reach->travelType&TFL_INVALID || !(reach->travelType&myMove->travelFlags)) { + return; + } + + // Visit The Center Of The Reach + //------------------------------- + Visit(reach, 0, from); + + + // If Running Low On Visit Space, Stop Adding Verts + //-------------------------------------------------- + if (visitedCount>=(int)((float)MAX_VISITED * 0.85f)) { + return; + } + + // If Edge Is Far From Start and Goal, Don't Add Verts + //----------------------------------------------------- + if (!reach->fromAreaNum!=myMove->myArea && + !reach->toAreaNum!=myMove->myArea && + !reach->fromAreaNum!=myMove->goalArea && + !reach->toAreaNum!=myMove->goalArea && + reach->start.Dist2XY(myMove->myPos)>22500.0f/*(250*250)*/ && reach->start.Dist2XY(myMove->goalPos)>22500.0f/*(250*250)*/) { + return; + } + + // If This Edge Is Small Enough, Just Skip The Verts + //--------------------------------------------------- + myAAS->GetEdge(reach->edgeNum, start, stop); + if (start.Dist2XY(stop)<6400.0f/*(80*80)*/) { + return; + } + + // Ok, So Visit The Verts Too + //---------------------------- + myAAS->GetEdgeVertexNumbers(reach->edgeNum, verts); + for (int i=0; i<2 && visitedCountGetFile()->GetArea(areaNum); + + // If Visiting From Another Node, Close That Node Now + //---------------------------------------------------- + if (from) { + + // If Already Closed + //------------------- + if (from->closed) { + if (openListUpdate) { + UpdateOpenList(); // this is kind of hacky... + } + + // Don't Bother To Look At These Reaches + //--------------------------------------- + return; + + // Otherwise, Close It + //--------------------- + } else { + Close(from); + } + } + + // Ok, Iterate All Reachabilities Within This Area + //------------------------------------------------- + for (reach=area.reach; reach && visitedCountnext) { + VisitReach(reach, from); + } + + // Finally, If Necessary, Update The Open List Now + //------------------------------------------------- + if (openListUpdate) { + UpdateOpenList(); + } + } + + + /* + ===================== + FindPath + ===================== + */ + bool FindPath(idAAS* aas, idMoveState& move, float radius, bool inDebugMode, idEntity* ignoreEntity, idEntity* ignoreEntity2) { + myAAS = aas; + myMove = &move; + myRadius = radius; + drawVisitTree = inDebugMode; + myIgnoreEntity = ignoreEntity; + myIgnoreEntity2 = ignoreEntity2; + + myMove->pathArea = myMove->goalArea; + myMove->pathTime = gameLocal.GetTime(); + myMove->pathLen = 0; + + openCount = 0; + openListUpdate = false; + pendingCount = 0; + pendingBest = NULL; + visitedCount = 0; + visitedIndexReach.Clear(); + visitedIndexVert.Clear(); + + + + if (!myMove->myArea) { + return ErrorCondition(); + } + const aasArea_t* myArea = &myAAS->GetFile()->GetArea(myMove->myArea); + const aasArea_t* goalArea = &myAAS->GetFile()->GetArea(myMove->goalArea); + + + // Special Case For Starting In The Goal Area + //-------------------------------------------- + if (myMove->myArea==myMove->goalArea) { + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, myArea, myMove->myPos, myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(NULL); + } + + // If There Is An Obstacle But We Think We Can Steer Around It, Then We've Still Succeeded + //----------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(NULL); + } + } + + + + // Start With My Area + //-------------------- + VisitArea(myMove->myArea); + + + // While Reachabilities Are Still Pending + //---------------------------------------- + while (openCount || pendingCount) { + + // Select Next Visited Node + //-------------------------- + next = SelectNextVisitedNode(); + if (!next) { + return ErrorCondition(); + } + + // If This Node Reaches Our Target Destination, We've Succeeded + //-------------------------------------------------------------- + if (next->reach->toAreaNum==myMove->goalArea) { + next->closed = true; + + // Test For Any Obstacles In The Way + //----------------------------------- + if (!obstacleFinder.RayTrace(0.0f, goalArea, GetSeekPosition(next), myMove->goalPos, 0/*CDR_TODO: Get myAASNum*/, myIgnoreEntity, myIgnoreEntity2)) { + return Success(next); + } + + // Or If There Is An Obstacle, But One Of The Verts Is Valid, Then This Is Still A Safe Course + //--------------------------------------------------------------------------------------------- + if (obstacleFinder.contact.v1Valid || obstacleFinder.contact.v2Valid) { + return Success(next); + } + + // Visit All Reaches In The Next Area + //------------------------------------- + } else { + VisitArea(next->reach->toAreaNum, next); + } + } + + return Failure(); + } +}; + +rvPathFinder pathFinder; + + + +void AI_EntityMoved(idEntity* ent) { + obstacleFinder.MarkEntityForUpdate(ent); +} +void AI_MoveInitialize() { + pathFinder.Initialize(); + obstacleFinder.Initialize(); +} + + + + + + + +/* +===================== +idAI::NewCombinedMove +===================== +*/ +void idAI::RVMasterMove( void ) { + static idVec3 mySeekDelta; + static idVec3 mySeekDirection; + static float mySeekDistance; + + static float moveDistance; + static idVec3 moveDelta; + static idEntity* moveBlockEnt; + static monsterMoveResult_t moveResult; + + + //==================================================================== + // UPDATE DATA + // A simple first step for movement is to get data from the command + // and goal arguments and compute changes if necessary. + //==================================================================== + bool seekMove = CanMove() && move.moveCommand>=NUM_NONMOVING_COMMANDS; + bool seekTurn = CanTurn() && move.moveCommand> MOVE_NONE; + + idMat3 myAxis = viewAxis; + const idVec3& myPos = physicsObj.GetOrigin(); + const idBounds& myBounds = physicsObj.GetBounds(); + float myRadius = myBounds.Size().x / 2.0f; + idVec3 myPosOld = move.myPos; // only used for debug graphics + bool myPosMoved = false; // only used for debug graphics + + const idEntity* goalEntity = move.goalEntity.GetEntity(); + const idVec3& goalPos = (goalEntity)?(LastKnownPosition(goalEntity)):(move.moveDest); + + + + + // Update My Position And Area + //----------------------------- + if (move.myArea==0 || move.myPos.Dist2XY(myPos)>20.0f) { + move.myPos = myPos; + move.myArea = PointReachableAreaNum(move.myPos); + myPosMoved = true; + } + aasArea_t* myArea = &aas->GetFile()->GetArea(move.myArea); + + // Update Goal Position And Area + //------------------------------- + if ((seekMove || seekTurn) && move.goalPos.Dist2XY(goalPos)>20.0f) { + move.goalPos = goalPos; + move.goalArea = PointReachableAreaNum(move.goalPos); + } + + // If Reached The Goal Position, Then Stop Moving + //------------------------------------------------ + if (seekMove && ReachedPos( move.goalPos, move.moveCommand, move.range )) { + StopMove( MOVE_STATUS_DONE ); + seekMove = false; + move.pathLen = 0; + } + + // Update The Obstacle Markers + //----------------------------- + obstacleFinder.UpdateObstacles(); + + + + //==================================================================== + // PATH FINDING + // If the goal area is not the same as myArea, then we need to run + // a pathfinding search to get to the goal area. This operation + // will alter the seek position + //==================================================================== + if (seekMove) { + + // If No Path Exists, Find One + //----------------------------- + if (move.pathArea!=move.goalArea || (!move.pathLen && move.pathTime < (gameLocal.time-10000))) { + pathFinder.FindPath(aas, move, myRadius, DebugFilter(ai_debugMove), this, move.goalEntity.GetEntity()); + } + + + // Set The SeekPos + //----------------- + if (move.pathLen) { + move.seekPos = move.path[move.pathLen-1].seekPos; + + // If We've Reached The Next Path Pos, Pop It Off The List And Continue + //---------------------------------------------------------------------- + if (move.seekPos.Dist2XY(move.myPos)entity; + const rvWindingBox& bounds = tr.obstacle->windings[0/*CDR_TODO: Get actual aasNumber*/]; + + // Is The Obstacle Standing On Seek Position + //------------------------------------------- + if (bounds.PointInside(move.seekPos)) { + if (move.myArea!=move.goalArea) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1150; + move.pathArea = 0; // force a refind path next update + } else { + + // Otherwise, Stop And Wait For It To Move + //----------------------------------------- + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1050; + // CDR_TODO: Issue MoveDestInvalid() Callback Here + } + } + + // Is The Obstacle About To Cross My Path? + //----------------------------------------- + else if (tr.v1==-1) { // CDR_TODO: -1 is an obtouse way to detect this + move.fl.blocked = true; + move.blockTime = gameLocal.time + 1500; + } + + // Ok, So Let's Try To Steer Around The Obstacle + //----------------------------------------------- + else { + + // If Neither Vertex Is Valid, Need To Refind Path + //------------------------------------------------- + if (!tr.v1Valid && !tr.v2Valid) { + move.fl.blocked = true; + move.blockTime = gameLocal.time + 3250; + move.pathArea = 0; // force a refind path next update + + } else { + + int vertex; + + // Otherwise, Choose The Best Valid Vertex + //----------------------------------------- + if (!tr.v2Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v1; + } else if (!tr.v1Valid || bounds.areas[tr.v2]!=myArea) { + vertex = tr.v2; + } else { + // CDRTODO: Record clockwise / counter clockwise and only test this once. + vertex = (tr.obstacle->origin.IsLeftOf(move.myPos, move.seekPos))?(tr.v2):(tr.v1); + } + + + // Get Close The Contact Point if The Choosen Vertex Is Not In This Area, Before Going Toward The Vertex + //------------------------------------------------------------------------------------------------------- + if (bounds.areas[vertex]!=myArea && move.myPos.Dist2XY(tr.endPoint)>400.0f /*20*20*/) { + move.seekPos = tr.endPoint; + } else { + move.seekPos = bounds.verts[vertex]; + } + + + // And Recompute The Seek Vectors + //-------------------------------- + mySeekDelta = move.seekPos - move.myPos; + mySeekDirection = mySeekDelta; + mySeekDistance = mySeekDirection.NormalizeFast(); + } + } + } + } + + + + + + //==================================================================== + // TURNING + // Having finialized our seek position, it is now time to turn + // toward it. + //==================================================================== + if (seekTurn) { + DirectionalTurnToward(move.seekPos); + } + Turn(); + + + + //==================================================================== + // VELOCITY + // Now we need to get the instantanious velocity vector, which will + // usually come right from the animation + //==================================================================== + if (move.moveType == MOVETYPE_ANIM) { + if ( move.fl.allowAnimMove || move.fl.allowPrevAnimMove ) { + GetAnimMoveDelta( myAxis, viewAxis, moveDelta ); + } else { + moveDelta.Zero(); + } + } else { + // CDR_TODO: Other movetypes here + } + + // If Doing Seek Move, Cap The Delta To Avoid Overshooting The Seek Position + //--------------------------------------------------------------------------- + if (seekMove && moveDelta!=vec3_zero) { + moveDistance = moveDelta.LengthFast(); + + if (mySeekDistance<0.5f) { + moveDelta = vec3_zero; + } else if (mySeekDistanceIsType( idMoveable::GetClassType() ) && moveBlockEnt->GetPhysics()->IsPushable()) { + KickObstacles( viewAxis[ 0 ], move.kickForce, moveBlockEnt ); + } + + // Touch Triggers + //---------------- + if (moveDelta!=vec3_zero ) { + if (moveResult!=MM_BLOCKED) { + TouchTriggers(); + } + } + + + + //==================================================================== + // DEBUG GRAPHICS + //==================================================================== + idVec3 origin = physicsObj.GetOrigin(); + if (DebugFilter(ai_debugMove)) { + static const idVec3 upPole(0.0f, 0.0f, 60.0f); + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + + gameRenderWorld->DebugBounds(colorMagenta, physicsObj.GetBounds(), origin, gameLocal.msec); // Bounds: MAGENTA + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + gameRenderWorld->DebugLine(colorPurple, move.goalPos, move.goalPos + upPole, gameLocal.msec); // Goal: PURPLE + + if (myPosMoved) { + gameRenderWorld->DebugLine(colorCyan, myPosOld, move.myPos, 800); // Trail: CYAN + } + + if (move.pathLen) { + for (int i=move.pathLen-1; i>=0; i--) { + gameRenderWorld->DebugLine(colorBlue, origin, move.path[i].seekPos, gameLocal.msec); // FoundPath: BLUE + origin = move.path[i].seekPos; + } + } + // PathVisited: ORANGE + // PathOpened: YELLOW + } + + if (DebugFilter(ai_showObstacleAvoidance)) { + static const idVec3 upSeek(0.0f, 0.0f, 3.0f); + const idVec3& obstaclePos = (move.obstacle.GetEntity())?(move.obstacle->GetPhysics()->GetOrigin()):(move.seekPos); + + if (!DebugFilter(ai_debugMove)) { + gameRenderWorld->DebugArrow(colorGreen, origin+upSeek, move.seekPos + upSeek, 5, gameLocal.msec); // Seek: GREEN + } + + if (move.blockTime>gameLocal.time) { + gameRenderWorld->DebugArrow(colorRed, move.myPos, obstaclePos, 3, gameLocal.msec); // Blocked Obstacle: RED + } else if (move.fl.obstacleInPath) { + gameRenderWorld->DebugArrow(colorWhite, move.myPos, obstaclePos, 3, gameLocal.msec); // Walk Obstacle: WHITE + } + // ObstacleBox: YELLOW + // VertexInvalid: RED + // FuturePosition: BROWN + obstacleFinder.DrawDebugGraphics(); + } +} + +/* +===================== +idAI::Move +===================== +*/ +void idAI::Move ( void ) { + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Start ( ); + } + + switch( move.moveType ) { + case MOVETYPE_DEAD: + DeadMove(); + break; + case MOVETYPE_FLY : + FlyMove(); + break; + case MOVETYPE_STATIC : + StaticMove(); + break; + case MOVETYPE_ANIM : + AnimMove(); + break; + case MOVETYPE_SLIDE : + SlideMove(); + break; + case MOVETYPE_PLAYBACK: + PlaybackMove(); + break; + case MOVETYPE_CUSTOM: + CustomMove(); + break; + } + + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerMove.Stop ( ); + } +} + diff --git a/src/mpgame/ai/AI_Move.h b/src/mpgame/ai/AI_Move.h new file mode 100644 index 0000000..beb9806 --- /dev/null +++ b/src/mpgame/ai/AI_Move.h @@ -0,0 +1,319 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=============================================================================== + +AI_Move.h + +This file has all movement related typedefs, enums, flags, and structures. It +and its sister CPP file were split from AI.h and AI.cpp in order to prevent +merge conflicts and to make further changes to the system possible. + +=============================================================================== +*/ + +#ifndef __AI_MOVE_H__ +#define __AI_MOVE_H__ + + +typedef idEntityPtr entityPointer_t; + +#define MAX_PATH_LEN 48 + +/* +===================== +aiMoveDir_t - used by directional movement code +===================== +*/ +typedef enum { + MOVEDIR_FORWARD, + MOVEDIR_BACKWARD, + MOVEDIR_LEFT, + MOVEDIR_RIGHT, + MOVEDIR_MAX +} aiMoveDir_t; +extern const char* aiMoveDirectionString [ MOVEDIR_MAX ]; + +/* +===================== +moveType_t - determines which movement function to call +===================== +*/ +typedef enum { + MOVETYPE_DEAD, + MOVETYPE_ANIM, + MOVETYPE_SLIDE, + MOVETYPE_FLY, + MOVETYPE_STATIC, + MOVETYPE_PLAYBACK, + //twhitaker: added custom move type + MOVETYPE_CUSTOM, + NUM_MOVETYPES +} moveType_t; + +/* +===================== +aiMoveCommand_t - tells the AI how to get there +===================== +*/ +typedef enum { + MOVE_NONE, + MOVE_FACE_ENEMY, + MOVE_FACE_ENTITY, + + // commands < NUM_NONMOVING_COMMANDS don't cause a change in position + NUM_NONMOVING_COMMANDS, + + MOVE_TO_ENEMY = NUM_NONMOVING_COMMANDS, + MOVE_TO_ENTITY, + MOVE_TO_ATTACK, + MOVE_TO_HELPER, + MOVE_TO_TETHER, + MOVE_TO_COVER, + MOVE_TO_HIDE, + MOVE_TO_POSITION, + MOVE_TO_POSITION_DIRECT, + MOVE_OUT_OF_RANGE, + MOVE_SLIDE_TO_POSITION, + MOVE_WANDER, + MOVE_RV_PLAYBACK, + NUM_MOVE_COMMANDS + +} aiMoveCommand_t; + +/* +===================== +moveStatus_t - status results from move commands + +make sure to change script/doom_defs.script if you add any, or change their order +===================== +*/ +typedef enum { + MOVE_STATUS_DONE, + MOVE_STATUS_MOVING, + MOVE_STATUS_WAITING, + MOVE_STATUS_DEST_NOT_FOUND, + MOVE_STATUS_DEST_UNREACHABLE, + MOVE_STATUS_BLOCKED_BY_WALL, + MOVE_STATUS_BLOCKED_BY_OBJECT, + MOVE_STATUS_BLOCKED_BY_ENEMY, + MOVE_STATUS_BLOCKED_BY_MONSTER, + MOVE_STATUS_BLOCKED_BY_PLAYER, + MOVE_STATUS_DISABLED, + NUM_MOVE_STATUS, +} moveStatus_t; +extern const char* aiMoveStatusString[ NUM_MOVE_STATUS ]; + +/* +===================== +stopEvent_t - used by path prediction +===================== +*/ +typedef enum { + SE_BLOCKED = BIT(0), + SE_ENTER_LEDGE_AREA = BIT(1), + SE_ENTER_OBSTACLE = BIT(2), + SE_FALL = BIT(3), + SE_LAND = BIT(4) +} stopEvent_t; + +/* +===================== +obstaclePath_s +===================== +*/ +typedef struct obstaclePath_s { + idVec3 seekPos; // seek position avoiding obstacles + idEntity * firstObstacle; // if != NULL the first obstacle along the path + idVec3 startPosOutsideObstacles; // start position outside obstacles + idEntity * startPosObstacle; // if != NULL the obstacle containing the start position + idVec3 seekPosOutsideObstacles; // seek position outside obstacles + idEntity * seekPosObstacle; // if != NULL the obstacle containing the seek position + // RAVEN BEGIN + // cdr: Alternate Routes Bug + idList allObstacles; + // RAVEN END +} obstaclePath_t; + +/* +===================== +predictedPath_s +===================== +*/ +typedef struct predictedPath_s { + idVec3 endPos; // final position + idVec3 endVelocity; // velocity at end position + idVec3 endNormal; // normal of blocking surface + int endTime; // time predicted + int endEvent; // event that stopped the prediction + const idEntity * blockingEntity; // entity that blocks the movement +} predictedPath_t; + + +/* +===================== +pathSeek_s +===================== +*/ +typedef struct pathSeek_s { + idReachability* reach; + idVec3 seekPos; +} pathSeek_t; + + + +/* +===================== +idMoveState +===================== +*/ +class idMoveState { +public: + idMoveState(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + void Spawn( idDict &spawnArgs ); + + + struct movementFlags_s { + + // current state + bool done :1; + bool moving :1; + bool crouching :1; // is currently crouching? + bool running :1; // is currently running? + bool blocked :1; + bool obstacleInPath :1; + bool goalUnreachable :1; + bool onGround :1; + bool flyTurning :1; // lean into turns when flying + + // ideal state + bool idealRunning :1; // what we want running to be + + + // various enable / disable flags + bool disabled :1; // is movement disabled? (this includes turning) + bool ignoreObstacles :1; // don't check for obsticles in path + + bool allowDirectional :1; // allows directional movement (ie. running sideways, backwards) + bool allowAnimMove :1; // allows any animation movement + bool allowPrevAnimMove :1; // allows slide move if current animmove has no motion extraction on it (smooth transitions) + bool allowHiddenMove :1; // allows character to still move around while hidden + bool allowPushMovables :1; // allows the articulated figure to push moveable objects + bool allowSlideToGoal :1; // allows the AI to excactly slide to the goal position if close enough + + bool noRun :1; // force the actor to walk + bool noWalk :1; // force the actor to run + bool noTurn :1; // can the ai turn? + bool noGravity :1; // dont use gravity + bool noRangedInterrupt :1; // dont stop halfway to attack position just because you have a clear shot + } fl; + + + moveType_t moveType; + aiMoveCommand_t moveCommand; + moveStatus_t moveStatus; + idVec3 moveDest; + idVec3 moveDir; // used for wandering and slide moves + + int toAreaNum; + int startTime; + int duration; + float speed; // only used by flying creatures + float range; + float wanderYaw; + int nextWanderTime; + int blockTime; + idEntityPtr obstacle; + idVec3 lastMoveOrigin; + int lastMoveTime; + int anim; + + int travelFlags; + + float kickForce; + float blockedRadius; + int blockedMoveTime; + int blockedAttackTime; + + // turning + float ideal_yaw; + float current_yaw; + float turnRate; + float turnVel; + float anim_turn_yaw; + float anim_turn_amount; + float anim_turn_angles; + + // flying + jointHandle_t flyTiltJoint; + float fly_speed; + float fly_bob_strength; + float fly_bob_vert; + float fly_bob_horz; + int fly_offset; // prefered offset from player's view + float fly_seek_scale; + float fly_roll_scale; + float fly_roll_max; + float fly_roll; + float fly_pitch_scale; + float fly_pitch_max; + float fly_pitch; + + aiMoveDir_t currentDirection; // Direction currently moving in + aiMoveDir_t idealDirection; // Direction we want to be moving in + float walkRange; // Distance to target before starting to walk + float walkTurn; // Turn delta threshold for walking when turning + idVec2 followRange; // Min and max range for AI following their leader + idVec2 searchRange; // Min and max range to use when searching for a new place to move to + float attackPositionRange; // Override for how close you have to get to your attackPosition before stopping + float turnDelta; // Amount to turn when turning + + idVec3 goalPos; + int goalArea; + entityPointer_t goalEntity; + idVec3 goalEntityOrigin; // move to entity uses this to avoid checking the floor position every frame + + idVec3 myPos; + int myArea; + + idVec3 seekPos; + + pathSeek_t path[MAX_PATH_LEN]; + int pathLen; + int pathArea; + int pathTime; + + idVec3 addVelocity; +}; + +// cdr: Obstacle Avoidance +void AI_EntityMoved(idEntity* ent); +void AI_MoveInitialize(); + + +#endif /* !__AI_MOVE_H__ */ diff --git a/src/mpgame/ai/AI_States.cpp b/src/mpgame/ai/AI_States.cpp new file mode 100644 index 0000000..981c462 --- /dev/null +++ b/src/mpgame/ai/AI_States.cpp @@ -0,0 +1,2236 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +static const int TACTICALUPDATE_MELEEDELAY = 500; // Rate to update tactical state when rushing +static const int TACTICALUPDATE_RANGEDDELAY = 2000; // Rate to update tactical state when in ranged combat +static const int TACTICALUPDATE_COVERDELAY = 5000; // Rate to update tactical state when in cover +static const int TACTICALUPDATE_HIDEDELAY = 5000; // Rate to update tactical state when hiding +static const int TACTICALUPDATE_FOLLOWDELAY = 500; // Rate to update tactical state when following +static const int TACTICALUPDATE_MOVETETHERDELAY = 1000; // Rate to update tactical state when moving into tether range +static const int TACTICALUPDATE_PASSIVEDELAY = 500; // Rate to update tactical state when in passive mode +static const int TACTICALUPDATE_TURRETDELAY = 250; // Rate to update tactical state when in turret mode + +static const int RANGED_ENEMYDELAY = 2000; // Time to wait to move after losing sight of an enemy +static const int COVER_ENEMYDELAY = 5000; // Stay behind cover for 5 seconds after loosing sight of an enemy + +static const float COVER_TRIGGERRADIUS = 64.0f; + +CLASS_STATES_DECLARATION ( idAI ) + // Wait States + STATE ( "Wait_Activated", idAI::State_Wait_Activated ) + STATE ( "Wait_ScriptedDone", idAI::State_Wait_ScriptedDone ) + STATE ( "Wait_Action", idAI::State_Wait_Action ) + STATE ( "Wait_ActionNoPain", idAI::State_Wait_ActionNoPain ) + + // Global States + STATE ( "State_WakeUp", idAI::State_WakeUp ) + STATE ( "State_TriggerAnim", idAI::State_TriggerAnim ) + + // Passive states + STATE ( "State_Passive", idAI::State_Passive ) + + // Combat states + STATE ( "State_Combat", idAI::State_Combat ) + STATE ( "State_CombatCover", idAI::State_CombatCover ) + STATE ( "State_CombatMelee", idAI::State_CombatMelee ) + STATE ( "State_CombatRanged", idAI::State_CombatRanged ) + STATE ( "State_CombatTurret", idAI::State_CombatTurret ) + STATE ( "State_CombatHide", idAI::State_CombatHide ) + + STATE ( "State_Wander", idAI::State_Wander ) + STATE ( "State_MoveTether", idAI::State_MoveTether ) + STATE ( "State_MoveFollow", idAI::State_MoveFollow ) + STATE ( "State_MovePlayerPush", idAI::State_MovePlayerPush ) + STATE ( "State_Killed", idAI::State_Killed ) + STATE ( "State_Dead", idAI::State_Dead ) + STATE ( "State_LightningDeath", idAI::State_LightningDeath ) + STATE ( "State_Burn", idAI::State_Burn ) + STATE ( "State_Remove", idAI::State_Remove ) + STATE ( "State_ScriptedMove", idAI::State_ScriptedMove ) + STATE ( "State_ScriptedFace", idAI::State_ScriptedFace ) + STATE ( "State_ScriptedStop", idAI::State_ScriptedStop ) + STATE ( "State_ScriptedPlaybackMove", idAI::State_ScriptedPlaybackMove ) + STATE ( "State_ScriptedPlaybackAim", idAI::State_ScriptedPlaybackAim ) + STATE ( "State_ScriptedJumpDown", idAI::State_ScriptedJumpDown ) + + // Torso States + STATE ( "Torso_Idle", idAI::State_Torso_Idle ) + STATE ( "Torso_Sight", idAI::State_Torso_Sight ) + STATE ( "Torso_CustomCycle", idAI::State_Torso_CustomCycle ) + STATE ( "Torso_Action", idAI::State_Torso_Action ) + STATE ( "Torso_FinishAction", idAI::State_Torso_FinishAction ) + STATE ( "Torso_Pain", idAI::State_Torso_Pain ) + STATE ( "Torso_ScriptedAnim", idAI::State_Torso_ScriptedAnim ) + STATE ( "Torso_PassiveIdle", idAI::State_Torso_PassiveIdle ) + STATE ( "Torso_PassiveFidget", idAI::State_Torso_PassiveFidget ) + + // Leg States + STATE ( "Legs_Idle", idAI::State_Legs_Idle ) + STATE ( "Legs_Move", idAI::State_Legs_Move ) + STATE ( "Legs_MoveThink", idAI::State_Legs_MoveThink ) + STATE ( "Legs_TurnLeft", idAI::State_Legs_TurnLeft ) + STATE ( "Legs_TurnRight", idAI::State_Legs_TurnRight ) + STATE ( "Legs_ChangeDirection", idAI::State_Legs_ChangeDirection ) + + // Head States + STATE ( "Head_Idle", idAI::State_Head_Idle ) + +END_CLASS_STATES + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +/* +================ +idAI::State_TriggerAnim +================ +*/ +stateResult_t idAI::State_TriggerAnim ( const stateParms_t& parms ) { + const char* triggerAnim; + + // If we dont have the trigger anim, just skip it + triggerAnim = spawnArgs.GetString ( "trigger_anim" ); + if ( !*triggerAnim || !HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + gameLocal.Warning ( "Missing or invalid 'trigger_anim' ('%s') specified for entity '%s'", + triggerAnim, GetName() ); + return SRESULT_DONE; + } + + Show(); + ScriptedAnim ( triggerAnim, 4, false, true ); + + // FIXME: should do this a better way + // Alwasy let trigger anims play out + fl.neverDormant = true; + + return SRESULT_DONE; +} + +/* +================ +idAI::State_WakeUp +================ +*/ +stateResult_t idAI::State_WakeUp ( const stateParms_t& parms ) { + const char* triggerAnim; + + WakeUp ( ); + + // Start immeidately into a playback? + if( spawnArgs.FindKey( "playback_intro" ) ){ + ScriptedPlaybackMove ( "playback_intro", PBFL_GET_POSITION | PBFL_GET_ANGLES_FROM_VEL, 0 ); + // Start immeidately into a scripted anim? + } else if ( spawnArgs.GetString ( "trigger_anim", "", &triggerAnim) && *triggerAnim && HasAnim ( ANIMCHANNEL_TORSO, triggerAnim ) ) { + PostState ( "State_TriggerAnim" ); + return SRESULT_DONE; + } else { + // Either wait to be triggered or wait until they have an enemy + if ( spawnArgs.GetBool ( "trigger" ) ) { + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + + PostState ( "Wait_Activated" ); + } + } + + PostState ( "State_Combat" ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Passive +================ +*/ +stateResult_t idAI::State_Passive ( const stateParms_t& parms ) { + if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + if ( UpdateTactical ( TACTICALUPDATE_PASSIVEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Combat + +The base combat state is basically the clearing house for other combat states. By calling +UpdateTactical with a timer of zero it ensures a better tactical move can be found. +================ +*/ +stateResult_t idAI::State_Combat ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + combat.tacticalCurrent = AITACTICAL_NONE; + + // Start the legs and head in the idle position + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + if ( head ) { + SetAnimState ( ANIMCHANNEL_HEAD, "Head_Idle", 4 ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Update the tactical state using all available tactical abilities and reset + // the current tactical state since this is the generic state. + combat.tacticalCurrent = AITACTICAL_NONE; + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If we are here then there isnt a single combat state available, thats not good + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatCover + +Seek out a cover point that has a vantage point on the enemy. Stay there firing at the +enemy until the cover point is invalidated. +================ +*/ +stateResult_t idAI::State_CombatCover ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_ATTACK, + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done && aasSensor->Reserved ( ) ) { + StopMove ( MOVE_STATUS_DONE ); + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we dont have a cover point anymore then just bail out + if ( !aasSensor->Reserved ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_COVERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // If we have moved off our cover point, move back + if ( DistanceTo ( aasSensor->ReservedOrigin ( ) ) > 8.0f ) { + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + } + // Dont do any cover checks until they are facing towards the wall + if ( !FacingIdeal ( ) ) { + return SRESULT_WAIT; + } + // Perform cover point actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +idAI::State_CombatMelee + +Head directly towards our enemy but allow ranged actions along the way +================ +*/ +stateResult_t idAI::State_CombatMelee ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move towards the enemy + STAGE_ATTACK, // Keep attacking until no longer in melee range + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // If we can no longer get to our enemy, give up on this and do something else! + if ( move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Attack when we have either stopped moving or are within melee range + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If we are out of melee range or lost sight of our enemy then start moving again + if ( !IsEnemyVisible ( ) ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + return SRESULT_DONE_WAIT; + } + // Always face enemy when in melee range + TurnToward ( enemy.lastKnownPosition ); + + // Perform actions while standing still + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_MELEEDELAY ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatRanged + +Move towards enemy until within firing range then continue to shoot at the enemy +================ +*/ +stateResult_t idAI::State_CombatRanged ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, // Move to an attack position + STAGE_ATTACK, // Perform actions until the enemy is out of range or not visible + }; + switch ( parms.stage ) { + case STAGE_MOVE: + // if we lost our enemy we are done here + if ( !enemy.ent ) { + ForceTacticalUpdate ( ); + if ( UpdateTactical ( 0 ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + // If done moving or within range of a visible enemy we can stop + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Stop early because we have a shot on our enemy? + if ( !move.fl.noRangedInterrupt && + !aifl.simpleThink && enemy.fl.visible + && gameLocal.time - enemy.lastVisibleChangeTime > 500 + && enemy.range >= combat.attackRange[0] + && enemy.range <= combat.attackRange[1] + && ( DistanceTo ( move.moveDest ) < DistanceTo ( move.lastMoveOrigin ) ) + && (!tether || tether->ValidateDestination ( this, physicsObj.GetOrigin( ) ) ) + && aiManager.ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + StopMove ( MOVE_STATUS_DONE ); + return SRESULT_STAGE ( STAGE_ATTACK ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + + case STAGE_ATTACK: + // If the enemy is visible but not in fov then rotate + if ( !enemy.fl.inFov && enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + // Update tactical state occasionally + if ( UpdateTactical ( TACTICALUPDATE_RANGEDDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_CombatTurret + +Stay put and shoot at the enemy when nearby +================ +*/ +stateResult_t idAI::State_CombatTurret ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) && !enemy.fl.inFov ) { + TurnToward ( enemy.lastKnownPosition ); + // Turn towards tether direction if we have no enemy + } else if ( !enemy.ent && tether ) { + TurnToward ( physicsObj.GetOrigin() + tether->GetPhysics()->GetAxis()[0] * 64.0f ); + } else if ( leader && !aifl.scripted ) { + if ( !GetEnemy() ) { + if ( combat.fl.aware && !combat.fl.ignoreEnemies ) { + //aggressive? I know, doesn't make sense, but becomePassive is different from State_Passive + TurnTowardLeader( (focusType==AIFOCUS_LEADER) ); + } else if ( focusType == AIFOCUS_LEADER && leader && leader.GetEntity() ) { + //passive + TurnToward( leader.GetEntity()->GetPhysics()->GetOrigin() ); + } + } + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + // If there are other available tactical states besides turret then try to + // go to one of them + if ( combat.tacticalMaskAvailable & ~(1<GetPhysics()->GetAxis()[0]; + TurnToward ( toPos ); + if ( !IsBehindCover() ) { + //Do a trace *once* to see if I can crouch-look in the direction of the tether at this point + trace_t tr; + idVec3 crouchEye = physicsObj.GetOrigin( ); + crouchEye.z += 32.0f; + gameLocal.TracePoint( this, tr, crouchEye, toPos, MASK_SHOT_BOUNDINGBOX, this ); + if ( tr.fraction >= 1.0f ) { + combat.fl.crouchViewClear = true; + } + } + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } + // Update tactical state occasionally to see if there is something better to do + if ( UpdateTactical ( TACTICALUPDATE_MOVETETHERDELAY ) ) { + return SRESULT_DONE_WAIT; + } + // Perform actions on the way to the enemy + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MoveFollow +================ +*/ +stateResult_t idAI::State_MoveFollow ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + // If we lost our leader we are done + if ( !leader ) { + move.fl.done = true; + // Can we just stop here? + } else if ( DistanceTo ( leader ) < (move.followRange[0]+move.followRange[1])*0.5f && aiManager.ValidateDestination ( this, physicsObj.GetOrigin( ), false, leader ) ) { + move.fl.done = true; + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + move.fl.done = false; + } + */ + } + // Are we done moving? + if ( move.fl.done ) { + StopMove ( MOVE_STATUS_DONE ); + if ( leader ) { + TurnToward ( leader->GetPhysics()->GetOrigin() ); + } + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else { + if ( UpdateTactical ( TACTICALUPDATE_FOLLOWDELAY ) ) { + return SRESULT_DONE_WAIT; + } + } + + // Perform actions on the way to the enemy + UpdateAction ( ); + + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +idAI::State_MovePlayerPush + +Move out of the way when the player is pushing us +================ +*/ +stateResult_t idAI::State_MovePlayerPush ( const stateParms_t& parms ) { + enum { + STAGE_MOVE, + STAGE_DONE, + }; + + switch ( parms.stage ) { + case STAGE_MOVE: + if ( move.fl.done ) { + StopMove(MOVE_STATUS_DONE); + ForceTacticalUpdate ( ); + return SRESULT_STAGE ( STAGE_DONE ); + } else if ( gameLocal.GetTime() - move.startTime > 2000 ) { + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( UpdateTactical ( ) ) { + return SRESULT_DONE_WAIT; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Killed +================ +*/ +stateResult_t idAI::State_Killed ( const stateParms_t& parms ) { + disablePain = true; + + //quickburning subjects skip all this jazz + if( fl.quickBurn ) { + PostState ( "State_Dead" ); + return SRESULT_DONE; + } + + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + // Make sure all animations stop + animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; +} + + +/* +================ +idAI::State_Dead +================ +*/ +stateResult_t idAI::State_Dead ( const stateParms_t& parms ) { + if ( !fl.hidden ) { + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + if( fl.quickBurn ) { + StopRagdoll(); + PostState ( "State_Burn", SEC2MS(0.05f) ); + } else if ( spawnArgs.GetString( "fx_burn_lightning", NULL ) ) { + lightningNextTime = 0; + lightningEffects = 0; + PostState ( "State_LightningDeath", SEC2MS(burnDelay) ); + } else { + PostState ( "State_Burn", SEC2MS(burnDelay) ); + } + } + float removeDelay = SEC2MS ( spawnArgs.GetFloat ( "removeDelay" ) ); + if ( removeDelay >= 0.0f ) { + PostState ( "State_Remove", removeDelay ); + } + } else { + PostState ( "State_Remove" ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_LightningDeath +================ +*/ +stateResult_t idAI::State_LightningDeath ( const stateParms_t& parms ) { + if ( gameLocal.time > lightningNextTime ) { + if ( !lightningEffects ) + { + StartSound ( "snd_burn_lightning", SND_CHANNEL_BODY, 0, false, NULL ); + } + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect ( spawnArgs, "fx_burn_lightning" ), this, 100 ); + effect->Play ( gameLocal.time, false ); + lightningNextTime = gameLocal.time + 100; + lightningEffects++; + } + if ( lightningEffects < 10 ) + { + return SRESULT_WAIT; + } + + /* + if ( spawnArgs.GetString( "fx_burn_lightning" ) ) + { + for ( int i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + if ( !gameLocal.random.RandomInt(1) ) { + PlayEffect( "fx_burn_lightning", (jointHandle_t)i ); + } + } + } + } + */ + float burnDelay = spawnArgs.GetFloat ( "burnaway" ); + if ( burnDelay > 0.0f ) { + PostState ( "State_Burn", 0 ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Burn +================ +*/ +stateResult_t idAI::State_Burn ( const stateParms_t& parms ) { + if( fl.hidden ){ + return SRESULT_DONE; + } + + + renderEntity.noShadow = true; + + // Dont let the articulated figure be shot once they start burning away + SetCombatContents ( false ); + fl.takedamage = false; + + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + idEntity *head = GetHead(); + if ( head ) { + head->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + } + + UpdateVisuals(); + + if ( spawnArgs.GetString( "fx_burn_particles", NULL ) ) + { + int i = GetAnimator()->GetJointHandle( spawnArgs.GetString("joint_chestOffset","chest") ); + if ( i != INVALID_JOINT ) + { + PlayEffect( "fx_burn_particles_chest", (jointHandle_t)i ); + } + + for ( i = GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + PlayEffect( "fx_burn_particles", (jointHandle_t)i ); + } + } + //FIXME: head, too? + //FIXME: from joint to parent joint? + //FIXME: not small joints... + } + + StartSound ( "snd_burn", SND_CHANNEL_BODY, 0, false, NULL ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Remove +================ +*/ +stateResult_t idAI::State_Remove ( const stateParms_t& parms ) { + PostEventMS( &EV_Remove, 0 ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_ScriptedAnim +================ +*/ +stateResult_t idAI::State_Torso_ScriptedAnim ( const stateParms_t& parms ) { + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + aifl.scripted = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedMove +================ +*/ +stateResult_t idAI::State_ScriptedMove ( const stateParms_t& parms ) { + if ( !aifl.scripted || move.fl.done ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedFace +================ +*/ +stateResult_t idAI::State_ScriptedFace ( const stateParms_t& parms ) { + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +idAI::State_ScriptedPlaybackMove +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + if ( mPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedPlaybackAim +================ +*/ +stateResult_t idAI::State_ScriptedPlaybackAim ( const stateParms_t& parms ) { + if ( mLookPlayback.IsActive() ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +idAI::State_ScriptedStop +================ +*/ +stateResult_t idAI::State_ScriptedStop ( const stateParms_t& parms ) { + ScriptedEnd ( ); + + // If ending in an idle animation move the legs back to idle and + // revert back to normal combat + if ( aifl.scriptedEndWithIdle ) { + ForceTacticalUpdate ( ); + UpdateTactical ( 0 ); + } + + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Torso States + +=============================================================================== +*/ + +/* +================ +idAI::State_Torso_Idle +================ +*/ +stateResult_t idAI::State_Torso_Idle ( const stateParms_t& parms ) { + // Custom passive idle? + if ( combat.tacticalCurrent == AITACTICAL_PASSIVE && passive.animIdlePrefix.Length() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the legs were disabled then get them back into idle again + if ( legsAnim.Disabled ( ) ) { + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + } + + // Start idle animation + const char* idleAnim = GetIdleAnimName (); + if ( !torsoAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_TORSO )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_TORSO, idleAnim, parms.blendFrames ); + } + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_PassiveIdle +================ +*/ +stateResult_t idAI::State_Torso_PassiveIdle ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT, + STAGE_TALK_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + // Talk animation? + if ( passive.talkTime > gameLocal.time ) { + idStr postfix; + if ( talkMessage >= TALKMSG_LOOP ) { + postfix = aiTalkMessageString[TALKMSG_LOOP]; + postfix += va("%d", (int)(talkMessage - TALKMSG_LOOP+1) ); + } else { + postfix = aiTalkMessageString[talkMessage]; + } + // Find their talk animation + animName = spawnArgs.GetString ( va("%s_%s", passive.animTalkPrefix.c_str(), postfix.c_str() ) ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.GetString ( passive.animTalkPrefix ); + } + + if ( animName.Length ( ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_TALK_WAIT ); + } + } + + // If we have a fidget animation then see if its time to play it + if ( passive.animFidgetPrefix.Length() ) { + if ( passive.fidgetTime == 0 ) { + passive.fidgetTime = gameLocal.time + SEC2MS ( spawnArgs.GetInt ( "fidget_rate", "20" ) ); + } else if ( gameLocal.time > passive.fidgetTime ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveFidget", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_PassiveIdle", 4 ); + return SRESULT_DONE; + } + } + + // Do we have a custom idle animation? + passive.idleAnim = spawnArgs.RandomPrefix ( passive.animIdlePrefix, gameLocal.random ); + passive.idleAnimChangeTime = passive.fl.multipleIdles ? SEC2MS ( spawnArgs.GetFloat ( "idle_change_rate", "10" ) ) : 0; + + // Is there a start animation for the idle? + animName = va("%s_start", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, passive.idleAnim, 0 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + // Talk animation interrupting? + if ( passive.animTalkPrefix.Length() && passive.talkTime > gameLocal.time ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If its time to play a new idle animation then do so + if ( passive.idleAnimChangeTime && gameLocal.time > passive.idleAnimChangeTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If its time to fidget then do so + if ( passive.fidgetTime && gameLocal.time > passive.fidgetTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + // Loop the idle again + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va("%s_end", passive.idleAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_STAGE ( STAGE_START ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + + case STAGE_TALK_WAIT: + if ( gameLocal.time > passive.talkTime ) { + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_PassiveFidget +================ +*/ +stateResult_t idAI::State_Torso_PassiveFidget ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_INIT: + passive.fidgetTime = 0; + animName = spawnArgs.RandomPrefix ( va("anim_%sfidget", passive.prefix.c_str() ), gameLocal.random ); + if ( !animName.Length ( ) ) { + animName = spawnArgs.RandomPrefix ( "anim_fidget", gameLocal.random ); + } + if ( !animName.Length ( ) ) { + return SRESULT_DONE; + } + if ( !PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_ScriptedJumpDown + +Face edge, walk off +================ +*/ +stateResult_t idAI::State_ScriptedJumpDown( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FACE_WAIT, + STAGE_ANIM, + STAGE_JUMP_WAIT, + STAGE_INAIR, + STAGE_WAIT_LAND, + STAGE_ANIM_WAIT, + STAGE_FINISH + }; + + switch ( parms.stage ) { + case STAGE_INIT: + { + aifl.scripted = true; + Event_SaveMove(); + StopMove(MOVE_STATUS_DONE); + //move.current_yaw = move.ideal_yaw; + move.moveCommand = MOVE_NONE; + move.fl.allowDirectional = false; + } + if ( !move.fl.onGround ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_STAGE ( STAGE_FACE_WAIT ); + break; + case STAGE_FACE_WAIT: + if ( FacingIdeal() ) + { + return SRESULT_STAGE ( STAGE_ANIM ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM: + { + DisableAnimState ( ANIMCHANNEL_LEGS ); + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + if ( HasAnim( ANIMCHANNEL_TORSO, "jumpdown_start" ) ) { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_start", 4 ); + move.fl.allowAnimMove = true; + move.fl.noGravity = true; + return SRESULT_STAGE ( STAGE_JUMP_WAIT ); + } + return SRESULT_STAGE ( STAGE_INAIR ); + } + break; + case STAGE_JUMP_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_STAGE ( STAGE_INAIR ); + } + return SRESULT_WAIT; + break; + case STAGE_INAIR: + { + idVec3 vel = viewAxis[0] * 200.0f; + vel.z = -50.0f; + physicsObj.SetLinearVelocity( vel ); + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_loop", 4 ); + move.fl.allowAnimMove = false; + move.fl.noGravity = false; + return SRESULT_STAGE ( STAGE_WAIT_LAND ); + } + break; + case STAGE_WAIT_LAND: + if ( physicsObj.OnGround() )//GetPhysics()->HasGroundContacts() ) + { + PlayAnim( ANIMCHANNEL_TORSO, "jumpdown_end", 0 ); + move.fl.allowAnimMove = true; + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + } + return SRESULT_WAIT; + break; + case STAGE_ANIM_WAIT: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 ) ) + { + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + break; + case STAGE_FINISH: + Event_RestoreMove(); + SetState( "State_Combat" ); + aifl.scripted = false; + return SRESULT_DONE; + break; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_CustomCycle +================ +*/ +stateResult_t idAI::State_Torso_CustomCycle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Sight +================ +*/ +stateResult_t idAI::State_Torso_Sight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + // Dont let everyone on the team play their sight sounds at the same time + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT ) ) { + Speak ( "lipsync_sight", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_SIGHT, 1000 ); + } + + // Execute sighted scripts + if( !enemy.fl.sighted ) { + enemy.fl.sighted = true; + ExecScriptFunction ( funcs.first_sight, this ); + } + ExecScriptFunction ( funcs.sight ); + + idStr animName = spawnArgs.GetString ( "anim_sight", spawnArgs.GetString ( "sight_anim" ) ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_DONE; + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Torso_Action +================ +*/ +stateResult_t idAI::State_Torso_Action ( const stateParms_t& parms ) { + // Invalid animation so dont bother with running the action + if ( actionAnimNum == -1 ) { + return SRESULT_DONE_WAIT; + } + + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + + // Wait till animation is finished + PostAnimState ( ANIMCHANNEL_TORSO, "Wait_TorsoAnim", parms.blendFrames ); + + return SRESULT_DONE_WAIT; +} + +/* +================ +idAI::State_Torso_FinishAction +================ +*/ +stateResult_t idAI::State_Torso_FinishAction ( const stateParms_t& parms ) { + + RestoreFlag ( AIFLAGOVERRIDE_DISABLEPAIN ); + RestoreFlag ( AIFLAGOVERRIDE_DAMAGE ); + RestoreFlag ( AIFLAGOVERRIDE_NOTURN ); + RestoreFlag ( AIFLAGOVERRIDE_NOGRAVITY ); + + enemy.fl.lockOrigin = false; + aifl.action = false; + + OnStopAction ( ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Torso_Pain +================ +*/ +stateResult_t idAI::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + + idStr animName; + + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + pain.loopEndTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "pain_maxLoopTime", "1" ) ); + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + animName = va( "%s_start", painAnim.c_str() ); + if ( HasAnim ( ANIMCHANNEL_TORSO, animName ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 1 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle ( ANIMCHANNEL_TORSO, painAnim, 1 ); + return SRESULT_STAGE ( STAGE_LOOP_WAIT ); + + case STAGE_LOOP_WAIT: + if ( gameLocal.time - pain.lastTakenTime > AI_PAIN_LOOP_DELAY ) { + return SRESULT_STAGE ( STAGE_END ); + } + if ( !pain.loopEndTime || gameLocal.time > pain.loopEndTime ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + animName = va( "%s_end", painAnim.c_str() ); + PlayAnim ( ANIMCHANNEL_TORSO, animName, 1 ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Head States + +=============================================================================== +*/ + +/* +================ +idAI::State_Head_Idle +================ +*/ +stateResult_t idAI::State_Head_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +=============================================================================== + + AI Leg States + +=============================================================================== +*/ + +/* +================ +idAI::State_Legs_Idle +================ +*/ +stateResult_t idAI::State_Legs_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: { + const char* idleAnim; + move.fl.allowAnimMove = false; + idleAnim = GetIdleAnimName ( ); + if ( !legsAnim.IsIdle () || idStr::Icmp ( idleAnim, animator.CurrentAnim ( ANIMCHANNEL_LEGS )->AnimName ( ) ) ) { + IdleAnim ( ANIMCHANNEL_LEGS, idleAnim, parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + // Dont let the legs do anything from idle until they are done blending + // the animations they have (this is to prevent pops when an action finishes + // and blends to idle then immediately the legs move to walk) + if ( animator.IsBlending ( ANIMCHANNEL_LEGS, gameLocal.time ) ) { + return SRESULT_WAIT; + } + + if ( move.fl.moving && CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +idAI::State_Legs_TurnLeft +================ +*/ +stateResult_t idAI::State_Legs_TurnLeft ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_TurnRight +================ +*/ +stateResult_t idAI::State_Legs_TurnRight ( const stateParms_t& parms ) { + // Go back to idle if we are done here + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_Move +================ +*/ +stateResult_t idAI::State_Legs_Move ( const stateParms_t& parms ) { + idStr animName; + + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // Movement direction changed? + if ( move.idealDirection != move.currentDirection ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_ChangeDirection", parms.blendFrames ); + } + + // Make sure run status is up to date when legs start moving + UpdateRunStatus ( ); + + // Run or walk? + animName = "run"; + if ( !move.fl.idealRunning && HasAnim ( ANIMCHANNEL_TORSO, "walk" ) ) { + animName = "walk"; + } + + // Append the run direction to the animation name + if ( move.idealDirection == MOVEDIR_LEFT ) { + animName = animName + "_left"; + } else if ( move.idealDirection == MOVEDIR_RIGHT ) { + animName = animName + "_right"; + } else if ( move.idealDirection == MOVEDIR_BACKWARD ) { + animName = animName + "_backwards"; + } else { +/* + // When moving to cover the walk animation is special + if ( move.moveCommand == MOVE_TO_COVER && !move.fl.idealRunning && move.fl.running ) { + animName = "run_slowdown"; + } +*/ + } + + // Update running flag with ideal state + move.fl.running = move.fl.idealRunning; + move.currentDirection = move.idealDirection; + + PlayCycle ( ANIMCHANNEL_LEGS, animName, parms.blendFrames ); + + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_MoveThink", parms.blendFrames ); + + return SRESULT_DONE; +} + +/* +================ +idAI::State_Legs_MoveThink +================ +*/ +stateResult_t idAI::State_Legs_MoveThink ( const stateParms_t& parms ) { + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + + // If not moving anymore then go back to idle + if ( !move.fl.moving || !CanMove() ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + // If the run state has changed restart the leg movement + if ( UpdateRunStatus ( ) || (move.idealDirection != move.currentDirection) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Move", 4 ); + return SRESULT_DONE; + } + + // Just wait a frame before doing anything else + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Legs_ChangeDirection +================ +*/ +stateResult_t idAI::State_Legs_ChangeDirection ( const stateParms_t& parms ) { + if ( FacingIdeal ( ) || (move.idealDirection != move.currentDirection) ) { + return SRESULT_DONE; + } + + move.fl.allowAnimMove = false; + move.fl.allowPrevAnimMove = true; + return SRESULT_WAIT; +} + +/* +=============================================================================== + + State helpers + +=============================================================================== +*/ + +/* +================ +idAI::UpdateRunStatus +================ +*/ +bool idAI::UpdateRunStatus ( void ) { + // Get new run status + if ( !move.fl.moving || !CanMove() ) { + move.fl.idealRunning = false; + } else if ( move.walkRange && !ai_useRVMasterMove.GetBool() && move.moveCommand != MOVE_TO_ENEMY && (DistanceTo ( move.moveDest ) - move.range) < move.walkRange ) { + move.fl.idealRunning = false; + } else if ( IsTethered() && tether->IsRunForced ( ) ) { + move.fl.idealRunning = true; + } else if ( IsTethered() && tether->IsWalkForced ( ) ) { + move.fl.idealRunning = false; + } else if ( move.fl.noWalk ) { + move.fl.idealRunning = true; + } else if ( move.fl.noRun ) { + move.fl.idealRunning = false; + } else if ( move.walkRange ) { + if ( move.fl.obstacleInPath ) { + move.fl.idealRunning = false; + } else if ( InLookAtCoverMode() ) { + move.fl.idealRunning = false; + } else if ( !ai_useRVMasterMove.GetBool() && move.walkTurn && !move.fl.allowDirectional && fabs(GetTurnDelta ( )) > move.walkTurn ) { + move.fl.idealRunning = false; + } else { + move.fl.idealRunning = true; + } + } else { + move.fl.idealRunning = true; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +idAI::UpdateTactical + +This method is a recursive method that will determine the best tactical state to be in +from the given available states. +================ +*/ +bool idAI::UpdateTactical ( int delay ) { + // Update tactical cannot be called while performing an action and it must be called from the main state loop + assert ( !aifl.action ); + + // No movement updating on simple think (if the update is forced do it anyway) + if ( combat.tacticalUpdateTime && aifl.simpleThink && !combat.tacticalMaskUpdate ) { + return false; + } + // Don't let tactical updates pre-empt pain actions + if ( pain.takenThisFrame ) { + return false; + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Start ( ); + } + + // Keep the enemy status up to date + if ( !combat.fl.ignoreEnemies ) { + // If we dont have an enemy or havent seen our enemy for a while just find a new one entirely + if ( gameLocal.time - enemy.checkTime > 250 ) { + CheckForEnemy ( true, true ); + } else if ( !IsEnemyRecentlyVisible ( ) ) { + CheckForEnemy ( true ); + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerFindEnemy.Stop ( ); + } + + // We have sighted an enemy so execute the sight state + if ( IsEnemyVisible() && !enemy.fl.sighted ) { + PerformAction ( "Torso_Sight", 4, true ); + return true; + } + + // continue with the last updatetactical if we still have bits left to check in our update mask + if ( !combat.tacticalMaskUpdate ) { + // Ignore the tactical delay if we are taking too much damage here. + // TODO: use skipcurrentdestination instead + if ( !combat.tacticalPainThreshold || combat.tacticalPainTaken < combat.tacticalPainThreshold ) { + // Delay tactical updating? + if ( combat.tacticalUpdateTime && move.moveStatus != MOVE_STATUS_BLOCKED_BY_ENEMY && delay && (gameLocal.time - combat.tacticalUpdateTime) < delay ) { + return false; + } + } + + // handle Auto break tethers + if ( IsTethered ( ) && tether->IsAutoBreak ( ) && IsWithinTether ( ) ) { + tether = NULL; + } + + // Filter the tactical + combat.tacticalMaskUpdate = FilterTactical ( combat.tacticalMaskAvailable ); + } else { + // Make sure all cached tactical updates are still valid + combat.tacticalMaskUpdate &= FilterTactical ( combat.tacticalMaskAvailable ); + if ( !combat.tacticalMaskUpdate ) { + return false; + } + } + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Start ( ); + } + + // Recursively look for a better tactical state + bool result = UpdateTactical_r ( ); + + // AI Speeds + if ( ai_speeds.GetBool ( ) ) { + aiManager.timerTactical.Stop ( ); + } + + return result; +} + +bool idAI::UpdateTactical_r ( void ) { + // Mapping of tactical types to combat states + static const char* tacticalState [ ] = { + "State_Combat", // AITACTICAL_NONE + "State_CombatMelee", // AITACTICAL_MELEE + "State_MoveFollow", // AITACTICAL_MOVE_FOLLOW + "State_MoveTether", // AITACTICAL_MOVE_TETHER + "State_MovePlayerPush", // AITACTICAL_MOVE_PLAYERPUSH + "State_CombatCover", // AITACTICAL_COVER + "State_CombatCover", // AITACTICAL_COVER_FLANK + "State_CombatCover", // AITACTICAL_COVER_ADVANCE + "State_CombatCover", // AITACTICAL_COVER_RETREAT + "State_CombatCover", // AITACTICAL_COVER_AMBUSH + "State_CombatRanged", // AITACTICAL_RANGED + "State_CombatTurret", // AITACTICAL_TURRET + "State_CombatHide", // AITACTICAL_HIDE + "State_Passive", // AITACTICAL_PASSIVE + }; + + if ( g_perfTest_aiStationary.GetBool() ) { + return false; + } + // Determine the new tactical state + aiTactical_t newTactical = AITACTICAL_TURRET; + + if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_PLAYERPUSH_BIT ) { + newTactical = AITACTICAL_MOVE_PLAYERPUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_FOLLOW_BIT ) { + newTactical = AITACTICAL_MOVE_FOLLOW; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_ADVANCE_BIT ) { + newTactical = AITACTICAL_COVER_ADVANCE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_FLANK_BIT ) { + newTactical = AITACTICAL_COVER_FLANK; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_RETREAT_BIT ) { + newTactical = AITACTICAL_COVER_RETREAT; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_AMBUSH_BIT ) { + newTactical = AITACTICAL_COVER_AMBUSH; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_COVER_BIT ) { + newTactical = AITACTICAL_COVER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_RANGED_BIT ) { + newTactical = AITACTICAL_RANGED; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MELEE_BIT ) { + newTactical = AITACTICAL_MELEE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_MOVE_TETHER_BIT ) { + newTactical = AITACTICAL_MOVE_TETHER; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_HIDE_BIT ) { + newTactical = AITACTICAL_HIDE; + } else if ( combat.tacticalMaskUpdate & AITACTICAL_PASSIVE_BIT ) { + newTactical = AITACTICAL_PASSIVE; + } + + // Remove the new tactical from the available in case we have to recursively call ourself + combat.tacticalMaskUpdate &= ~(1<IsType ( idPlayer::GetClassType ( ) ) ) { + //no current pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, just continue it + availableTactical = AITACTICAL_NONE_BIT; + return availableTactical; + } + //stop pushing away + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } else { + //have a pusher + if ( combat.tacticalCurrent == AITACTICAL_MOVE_PLAYERPUSH ) { + //in the middle of a push move, only allow it to continue playerpush, but can re-calc, if needbe + availableTactical = AITACTICAL_MOVE_PLAYERPUSH_BIT; + return availableTactical; + } + if ( ai_playerPushAlways.GetBool() ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + case AITACTICAL_MOVE_FOLLOW: + case AITACTICAL_MOVE_TETHER: + case AITACTICAL_MOVE_PLAYERPUSH: + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + case AITACTICAL_RANGED: + case AITACTICAL_HIDE: + //okay to be pushed by players + break; + default: + if ( !leader ) { + //no leader and not in a moving tactical state, don't be pushed by players + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + break; + } + } else if ( !leader || (move.fl.moving&&move.goalEntity!=leader) ) { + availableTactical &= ~(AITACTICAL_MOVE_PLAYERPUSH_BIT); + } + } + } + + // No tether move if not tethered + if ( !IsTethered ( ) ) { + availableTactical &= ~(AITACTICAL_MOVE_TETHER_BIT); + + // Filter out any tactical states that require a leader + if ( !leader || (enemy.ent && IsEnemyVisible() && enemy.range < move.followRange[1] * 2.0f ) ) { + availableTactical &= ~(AITACTICAL_MOVE_FOLLOW_BIT); + } else if ( leader && !enemy.ent ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + // When tethered disable actions that dont adhear to the tether. + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT|AITACTICAL_MOVE_FOLLOW_BIT|AITACTICAL_HIDE_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT); + } + + // If we dont have an enemy we can filter out pure combat states + if ( !enemy.ent ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_MELEE_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + + // If we arent aware either then we cant take cover + if ( !combat.fl.aware ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + } else { + // Dont advance or flank our enemy if we have seen them recently + if ( IsEnemyRecentlyVisible ( 0.5f ) ) { + availableTactical &= ~(AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_FLANK_BIT); + } + + // FIXME: need conditions for these cover states + availableTactical &= ~(AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_COVER_RETREAT_BIT); + } + + // Filter out all cover states if cover is disabled + if ( ai_disableCover.GetBool ( ) ) { + availableTactical &= ~(AITACTICAL_COVER_BITS); + } + + //if we need to fight in melee then fight in melee! + if( IsMeleeNeeded( ) ) { + availableTactical &= ~(AITACTICAL_RANGED_BIT|AITACTICAL_COVER_FLANK_BIT|AITACTICAL_COVER_ADVANCE_BIT|AITACTICAL_COVER_RETREAT_BIT|AITACTICAL_COVER_AMBUSH_BIT|AITACTICAL_HIDE_BIT); + } + + return availableTactical; +} + +/* +================ +idAI::CheckTactical + +Returns 'AICHECKTACTICAL_MOVE' if we should use the given tactical state and execute a movement to do so +Returns 'AICHECKTACTICAL_SKIP' if the given tactical state should be skipped +Returns 'AICHECKTACTICAL_NOMOVE' if the given tactical state should be used but no movement is required +================ +*/ +aiCTResult_t idAI::CheckTactical ( aiTactical_t tactical ) { + // Handle non movement tactical states first + if ( BIT(tactical) & AITACTICAL_NONMOVING_BITS ) { + // If we are moving + if ( move.fl.moving ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + // If we are tethered always check that first + if ( IsTethered ( ) ) { + if ( !move.fl.moving || tactical != combat.tacticalCurrent ) { + // We stopped out of tether range so try a new move to get back in + if ( !tether->ValidateDestination ( this, physicsObj.GetOrigin ( ) ) ) { + return AICTRESULT_OK; + } + } else { + // Move is out of tether range, so look for another one + if ( !tether->ValidateDestination ( this, move.moveDest ) ) { + return AICTRESULT_OK; + } + } + } + + // Anything wrong with our current destination, if so pick a new move + if ( SkipCurrentDestination ( ) ) { + return AICTRESULT_OK; + } + + switch ( tactical ) { + case AITACTICAL_MOVE_FOLLOW: + // If already moving to our leader dont move again + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENTITY && move.goalEntity == leader ) { + return AICTRESULT_NOMOVE; + } + // If not moving and within follow range we can skip the follow state + if ( !move.fl.moving ) { + if ( DistanceTo ( leader ) < move.followRange[1] ) { + //unless the leader is on an elevator we should be standing on... + /* + idEntity* leaderGroundElevator = leader->GetGroundElevator(); + if ( leaderGroundElevator && GetGroundElevator(leaderGroundElevator) != leaderGroundElevator ) { + return AICTRESULT_OK; + } + */ + return AICTRESULT_SKIP; + } + } + return AICTRESULT_OK; + + case AITACTICAL_MOVE_TETHER: + // If not moving we dont want to idle in the move state so skip it + if ( !move.fl.moving ) { + return AICTRESULT_SKIP; + } + // We are still heading towards our tether so dont reissue a move + return AICTRESULT_NOMOVE; + + case AITACTICAL_RANGED: + // Currently issuing a find + if ( dynamic_cast(aasFind) ) { + return AICTRESULT_OK; + } + + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + // If the enemy is visible then no movement is required for ranged combat + if ( !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + if ( !move.fl.moving ) { + // If not within the attack range we need to move + if ( enemy.range > combat.attackRange[1] || enemy.range < combat.attackRange[0] ) { + return AICTRESULT_OK; + } + + // If we havent seen our enemy for a while and arent moving, then get moving! + if ( enemy.lastVisibleTime && gameLocal.time - enemy.lastVisibleTime > RANGED_ENEMYDELAY ) { + return AICTRESULT_OK; + } + } else { + // Is our destination out of range? + if ( (move.moveDest - enemy.lastKnownPosition).LengthSqr ( ) > Square ( combat.attackRange[1] ) ) { + return AICTRESULT_OK; + } + } + // Our position is fine so just stay in ranged combat + return AICTRESULT_NOMOVE; + + case AITACTICAL_MELEE: + // IF we are already moving towards our enemy then we dont need a state change + if ( move.fl.moving && move.moveCommand == MOVE_TO_ENEMY && move.goalEntity == enemy.ent ) { + return AICTRESULT_NOMOVE; + } + return AICTRESULT_OK; + + case AITACTICAL_COVER: + case AITACTICAL_COVER_FLANK: + case AITACTICAL_COVER_ADVANCE: + case AITACTICAL_COVER_RETREAT: + case AITACTICAL_COVER_AMBUSH: + // If set to zero a tactical update was forced so lets force a move too + if ( !combat.tacticalUpdateTime ) { + return AICTRESULT_OK; + } + if ( enemy.ent && !IsEnemyRecentlyVisible ( ) ) { + return AICTRESULT_OK; + } + // No longer behind cover? + if ( !IsBehindCover ( ) ) { + return AICTRESULT_OK; + } + // Move if there have been too many close calls + if ( combat.tacticalFlinches && combat.tacticalFlinches > gameLocal.random.RandomInt(12) + 3 ) { + return AICTRESULT_OK; + } + // Move if cover destination isnt valid anymore + if ( !IsCoverValid() ) { + return AICTRESULT_OK; + } + return AICTRESULT_NOMOVE; + } + + return AICTRESULT_OK; +} + +/* +================ +idAI::WakeUpTargets +================ +*/ +void idAI::WakeUpTargets ( void ) { + const idKeyValue* kv; + + for ( kv = spawnArgs.MatchPrefix ( "wakeup_target" ); kv; kv = spawnArgs.MatchPrefix ( "wakeup_target", kv ) ) { + idEntity* ent; + ent = gameLocal.FindEntity ( kv->GetValue ( ) ); + if ( !ent ) { + gameLocal.Warning ( "Unknown wakeup_target '%s' on entity '%s'", kv->GetValue().c_str(), GetName() ); + } else { + ent->Signal( SIG_TRIGGER ); + ent->PostEventMS( &EV_Activate, 0, this ); + ent->TriggerGuis ( ); + } + } + + // Find all the tether entities we target + const char* target; + if ( spawnArgs.GetString ( "tether_target", "", &target ) && *target ) { + idEntity* ent; + ent = gameLocal.FindEntity ( target ); + if ( ent && ent->IsType ( rvAITether::GetClassType ( ) ) ) { + ProcessEvent ( &EV_Activate, ent ); + } + } +} + +/* +=============================================================================== + + Wait States + +=============================================================================== +*/ + +/* +================ +idAI::State_Wait_Activated + +Stop the state thread until the ai is activated +================ +*/ +stateResult_t idAI::State_Wait_Activated ( const stateParms_t& parms ) { + if ( (aifl.activated || aifl.pain ) && CanBecomeSolid ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_Action + +Stop the state thread as long as a torso action is running and allow a pain action to interrupt +================ +*/ +stateResult_t idAI::State_Wait_Action ( const stateParms_t& parms ) { + if ( CheckPainActions ( ) ) { + // Our current action is being interrupted by pain so make sure the action can be performed + // immediately after coming out of the pain. + actionTime = actionSkipTime; + return SRESULT_DONE; + } + + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ActionNoPain + +Stop the state thread as long as a torso action is running +================ +*/ +stateResult_t idAI::State_Wait_ActionNoPain ( const stateParms_t& parms ) { + if ( !aifl.action ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; +} + +/* +================ +idAI::State_Wait_ScriptedDone + +Stop the state thread as a scripted sequence is active +================ +*/ +stateResult_t idAI::State_Wait_ScriptedDone ( const stateParms_t& parms ) { + if ( aifl.scripted ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} diff --git a/src/mpgame/ai/AI_Tactical.cpp b/src/mpgame/ai/AI_Tactical.cpp new file mode 100644 index 0000000..1d4289e --- /dev/null +++ b/src/mpgame/ai/AI_Tactical.cpp @@ -0,0 +1,1078 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// +// TODO: +// - unarmed posture +// - relaxed posture +// - turning in place too much, rely more on look angles? + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AI_Tactical.h" + +const idEventDef AI_ForcePosture ( "forcePosture", "d" ); + +CLASS_DECLARATION( idAI, rvAITactical ) + EVENT( AI_ForcePosture, rvAITactical::Event_ForcePosture ) + EVENT( EV_PostSpawn, rvAITactical::Event_PostSpawn ) +END_CLASS + +static const char* aiPostureString[AIPOSTURE_MAX] = { + "stand", // AIPOSTURE_STAND, + "crouch", // AIPOSTURE_CROUCH, + "cover_left", // AIPOSTURE_STAND_COVER_LEFT, + "cover_right", // AIPOSTURE_STAND_COVER_RIGHT, + "crouch_cover", // AIPOSTURE_CROUCH_COVER + "crouch_cover_left", // AIPOSTURE_CROUCH_COVER_LEFT, + "crouch_cover_right", // AIPOSTURE_CROUCH_COVER_RIGHT, + "relaxed", // AIPOSTURE_RELAXED + "unarmed", // AIPOSTURE_UNARMED + "at_attention", // AIPOSTURE_AT_ATTENTION +}; + +/* +================ +rvAITactical::rvAITactical +================ +*/ +rvAITactical::rvAITactical ( void ) { + shots = 0; + nextWallTraceTime = 0; +} + +void rvAITactical::InitSpawnArgsVariables ( void ) +{ + // Initialize the posture info + InitPostureInfo ( ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + fireRate = SEC2MS ( spawnArgs.GetFloat ( "fireRate", "1" ) ); + + healthRegen = spawnArgs.GetInt( "healthRegen", "2" ); + healthRegenEnabled = spawnArgs.GetBool( "healthRegenEnabled", "0" ); +} + +/* +================ +rvAITactical::Spawn +================ +*/ +void rvAITactical::Spawn ( void ) { + InitSpawnArgsVariables(); + + // Force a posture? + const char* temp; + postureForce = AIPOSTURE_DEFAULT; + if ( spawnArgs.GetString ( "forcePosture", "", &temp ) && *temp ) { + for ( postureForce = AIPOSTURE_STAND; postureForce != AIPOSTURE_MAX; ((int&)postureForce)++ ) { + if ( !idStr::Icmp ( aiPostureString[postureForce], temp ) ) { + break; + } + } + if ( postureForce >= AIPOSTURE_MAX ) { + postureForce = AIPOSTURE_DEFAULT; + } + } + + UpdatePosture ( ); + postureCurrent = postureIdeal; + + OnPostureChange ( ); + + ammo = spawnArgs.GetInt ( "ammo", "-1" ); + + // Initialize custom actions + actionElbowAttack.Init ( spawnArgs, "action_elbowAttack", NULL, AIACTIONF_ATTACK ); + actionKillswitchAttack.Init ( spawnArgs, "action_killswitchAttack", NULL, AIACTIONF_ATTACK ); + + actionTimerPeek.Init ( spawnArgs, "actionTimer_peek" ); + +// playerFocusTime = 0; +// playerAnnoyTime = SEC2MS(spawnArgs.GetFloat ( "annoyed", "5" )); + + healthRegenNextTime = 0; + maxHealth = health; +} + +/* +================ +rvAITactical::Think +================ +*/ +void rvAITactical::Think ( void ) { + idAI::Think ( ); + + // If not simple thinking and not in an action, update the posture + if ( !(aifl.scripted&&move.moveCommand==MOVE_NONE) && aifl.awake && !aifl.simpleThink && !aifl.action && !aifl.dead ) { + if ( UpdatePosture ( ) ) { + PerformAction ( "Torso_SetPosture", 4, true ); + } + } + +// FIXME: disabled for now, its annoying people + /* + idPlayer* localPlayer; + localPlayer = gameLocal.GetLocalPlayer(); + + // If the player has been standing in front of the marine and looking at him for too long he should say something + if ( !aifl.dead && playerFocusTime && playerAnnoyTime + && !aifl.scripted && focusType == AIFOCUS_PLAYER && localPlayer && !IsSpeaking() + && !localPlayer->IsBeingTalkedTo() //nobody else is talking to him right now + && DistanceTo( localPlayer ) < 64.0f ) { + idVec3 diff; + + + diff = GetPhysics()->GetOrigin() - localPlayer->GetPhysics()->GetOrigin(); + diff.NormalizeFast(); + + // Is the player looking at the marine? + if ( diff * localPlayer->viewAxis[0] > 0.7f ) { + // Say something every 5 seconds + if ( gameLocal.time - playerFocusTime > playerAnnoyTime ) { + // Debounce it against other marines + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU ) ) { + Speak ( "lipsync_canihelpyou", true ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ANNOUNCE_CANIHELPYOU, 5000 ); + } + } + } else { + playerFocusTime = gameLocal.time; + } + } else { + playerFocusTime = gameLocal.time; + } + */ + + if ( health > 0 ) { + //alive + if ( healthRegenEnabled && healthRegen ) { + if ( gameLocal.GetTime() >= healthRegenNextTime ) { + health = idMath::ClampInt( 0, maxHealth, health+healthRegen ); + healthRegenNextTime = gameLocal.GetTime() + 1000; + } + } + } + + //crappy place to do this, just testing + bool clearPrefix = true; + bool facingWall = false; + if ( move.fl.moving && InCoverMode() && combat.fl.aware ) { + clearPrefix = false; + if ( DistanceTo ( aasSensor->ReservedOrigin() ) < move.walkRange * 2.0f ) { + facingWall = true; + } else if ( nextWallTraceTime < gameLocal.GetTime() ) { + //do an occasional check for solid architecture directly in front of us + nextWallTraceTime = gameLocal.GetTime() + gameLocal.random.RandomInt(750)+750; + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + if ( neckJoint != INVALID_JOINT ) { + GetJointWorldTransform ( neckJoint, gameLocal.GetTime(), start, axis ); + end = start + axis[0] * 32.0f; + } else { + start = GetEyePosition(); + start += viewAxis[0] * 8.0f;//still inside bbox + end = start + viewAxis[0] * 32.0f; + } + //trace against solid arcitecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, MASK_SOLID, this ); + if ( wallTrace.fraction < 1.0f ) { + facingWall = true; + } else { + clearPrefix = true; + } + } + } + + if ( facingWall ) { + if ( !animPrefix.Length() ) { + animPrefix = "nearcover"; + } + } else if ( clearPrefix && animPrefix == "nearcover" ) { + animPrefix = ""; + } +} + +/* +================ +rvAITactical::Save +================ +*/ +void rvAITactical::Save( idSaveGame *savefile ) const { + savefile->WriteSyncId(); + + savefile->WriteInt ( ammo ); + savefile->WriteInt ( shots ); + +// savefile->WriteInt ( playerFocusTime ); +// savefile->WriteInt ( playerAnnoyTime ); + + savefile->WriteInt ( (int&)postureIdeal ); + savefile->WriteInt ( (int&)postureCurrent ); + savefile->WriteInt ( (int&)postureForce ); + // TOSAVE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; // cnicholson: + savefile->WriteInt ( healthRegenNextTime ); + savefile->WriteInt ( maxHealth ); + savefile->WriteInt ( nextWallTraceTime ); + + + actionElbowAttack.Save ( savefile ); + actionKillswitchAttack.Save ( savefile ); + + actionTimerPeek.Save ( savefile ); +} + +/* +================ +rvAITactical::Restore +================ +*/ +void rvAITactical::Restore( idRestoreGame *savefile ) { + InitSpawnArgsVariables ( ); + savefile->ReadSyncId( "rvAITactical" ); + + savefile->ReadInt ( ammo ); + savefile->ReadInt ( shots ); + +// savefile->ReadInt ( playerFocusTime ); +// savefile->ReadInt ( playerAnnoyTime ); + + savefile->ReadInt ( (int&)postureIdeal ); + savefile->ReadInt ( (int&)postureCurrent ); + savefile->ReadInt ( (int&)postureForce ); + // TORESTORE: aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + savefile->ReadInt ( healthRegenNextTime ); + savefile->ReadInt ( maxHealth ); + savefile->ReadInt ( nextWallTraceTime ); + + actionElbowAttack.Restore ( savefile ); + actionKillswitchAttack.Restore ( savefile ); + + actionTimerPeek.Restore ( savefile ); + + UpdateAnimPrefix ( ); +} + +/* +================ +rvAITactical::CanTurn +================ +*/ +bool rvAITactical::CanTurn ( void ) const { + if ( !move.fl.moving && !postureInfo[postureCurrent].fl.canTurn ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvAITactical::CanMove +================ +*/ +bool rvAITactical::CanMove ( void ) const { + if ( !postureInfo[postureCurrent].fl.canMove ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvAITactical::CheckAction_Reload +================ +*/ +bool rvAITactical::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( ammo == 0 ) { + return true; + } + return false; +} + +/* +================ +rvAITactical::CheckActions +================ +*/ +bool rvAITactical::CheckActions ( void ) { + // Pain? + if ( CheckPainActions ( ) ) { + return true; + } + + // If we are pressed, fight-- do not break melee combat until you or the enemy is dead. + if ( IsMeleeNeeded ( )) { + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + //take no actions other than fighting + return false; + } + + // Handle any posture changes + if ( postureIdeal != postureCurrent ) { + PerformAction ( "Torso_SetPosture", 4, true ); + return true; + } + + // Reload takes precedence + if ( !move.fl.moving && postureInfo[postureCurrent].fl.canReload && ammo == 0 ) { + PerformAction ( "Torso_Reload", 4, false ); + return true; + } + + if ( IsBehindCover ( ) ) { + // If we have no enemy try peeking + if ( !IsEnemyRecentlyVisible ( ) ) { + if ( aiManager.CheckTeamTimer ( team, AITEAMTIMER_ACTION_PEEK ) ) { + if ( actionTimerPeek.IsDone ( actionTime ) ) { + actionTimerPeek.Reset ( actionTime, 0.5f ); + aiManager.SetTeamTimer ( team, AITEAMTIMER_ACTION_PEEK, 2000 ); + PerformAction ( "Torso_Cover_Peek", 4, false ); + return true; + } + } + } + + // Attacks from cover + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + // Kill switch attack from cover? + if ( postureInfo[postureCurrent].fl.canKillswitch && IsEnemyRecentlyVisible ( ) ) { + if ( PerformAction ( &actionKillswitchAttack, NULL, &actionTimerRangedAttack ) ) { + return true; + } + } + + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; + } + + // Standard attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionElbowAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } + + // Ranged attack only if there is ammo + if ( postureInfo[postureCurrent].fl.canShoot && (ammo > 0 || ammo == -1) ) { + if ( PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + + return false; +} + +/* +================ +rvAITactical::CheckRelaxed + +Returns true if the marine should currently be in a relaxed state +================ +*/ +bool rvAITactical::CheckRelaxed ( void ) const { +// if ( forceRelaxed ) { +// return true; +// } + +/* + // If we have a leader, no enemy, and havent had an enemy for over 5 seconds go to relaxed + if ( leader && !enemy.ent && !tether && gameLocal.time - enemy.changeTime > 5000 ) { + return true; + } +*/ + + // Alwasy relaxed when ignoring enemies + if ( !combat.fl.aware ) { + return true; + } + + if ( enemy.ent || focusType != AIFOCUS_PLAYER || move.fl.moving || move.fl.crouching || talkState == TALK_OK ) { + return false; + } + + if ( gameLocal.time >= focusTime ) { + return false; + } + + return true; +} + +/* +================ +rvAITactical::GetIdleAnimName +================ +*/ +const char* rvAITactical::GetIdleAnimName ( void ) { + return "idle"; +} + +/* +================ +rvAITactical::UpdateAnimPrefix +================ +*/ +void rvAITactical::UpdateAnimPrefix ( void ) { + if ( postureCurrent == AIPOSTURE_STAND ) { + animPrefix = ""; + } else { + animPrefix = aiPostureString[postureCurrent]; + } +} + +/* +================ +rvAITactical::InitPostureInfo +================ +*/ +void rvAITactical::InitPostureInfo ( void ) { + int posture; + for ( posture = AIPOSTURE_DEFAULT + 1; posture < AIPOSTURE_MAX; posture ++ ) { + aiPostureInfo_t& info = postureInfo[(aiPosture_t)posture]; + + postureCurrent = (aiPosture_t)posture; + UpdateAnimPrefix ( ); + + info.fl.canMove = HasAnim ( ANIMCHANNEL_TORSO, "run", true ); + info.fl.canPeek = HasAnim ( ANIMCHANNEL_TORSO, "peek", true ); + info.fl.canReload = HasAnim ( ANIMCHANNEL_TORSO, "reload", true ); + info.fl.canShoot = HasAnim ( ANIMCHANNEL_TORSO, "range_attack", true ); + info.fl.canKillswitch = HasAnim ( ANIMCHANNEL_TORSO, "killswitch", true ); + info.fl.canTurn = false; + } + + // FIXME: this should be based on the availablity of turn anims + postureInfo[AIPOSTURE_STAND].fl.canTurn = true; + postureInfo[AIPOSTURE_RELAXED].fl.canTurn = true; + postureInfo[AIPOSTURE_UNARMED].fl.canTurn = true; +} + +/* +================ +rvAITactical::UpdatePosture +================ +*/ +bool rvAITactical::UpdatePosture ( void ) { + // If the posture is being forced then use that until its no longer forced + if ( postureForce != AIPOSTURE_DEFAULT ) { + postureIdeal = postureForce; + // Not forcing posture, determine it from our current state + } else { + postureIdeal = AIPOSTURE_STAND; + + // Behind cover? + if ( IsBehindCover ( ) ) { + bool left; + if ( enemy.ent ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * (enemy.lastVisibleEyePosition - physicsObj.GetOrigin())) > 0.0f; + } else if ( tether ) { + left = (aasSensor->Reserved()->Normal().Cross ( physicsObj.GetGravityNormal ( ) ) * tether->GetPhysics()->GetAxis()[0] ) > 0.0f; + } else { + left = false; + } + // Should be crouching behind cover? + if ( InCrouchCoverMode ( ) ) { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_CROUCH_COVER_RIGHT; + } else { + postureIdeal = AIPOSTURE_CROUCH_COVER; + } + } else { + if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) && left ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_RIGHT) && !left ) { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } else if ( (aasSensor->Reserved()->flags & FEATURE_LOOK_LEFT) ) { + postureIdeal = AIPOSTURE_STAND_COVER_LEFT; + } else { + postureIdeal = AIPOSTURE_STAND_COVER_RIGHT; + } + } + } else if ( combat.fl.aware //aggressive + && (FacingIdeal ( ) || CheckFOV ( currentFocusPos )) //looking in desired direction + && ((leader && leader->IsCrouching()) || combat.fl.crouchViewClear) ) {//leader is crouching or we can crouch-look in this direction here + //we crouch only if leader is + postureIdeal = AIPOSTURE_CROUCH; + } else if ( CheckRelaxed ( ) ) { + postureIdeal = AIPOSTURE_RELAXED; + } + + //never crouch in melee! + if( IsMeleeNeeded() ) { + postureIdeal = AIPOSTURE_STAND; + } + } + + // Default the posture if trying to move with one that doesnt support it + if ( move.fl.moving && !postureInfo[postureIdeal].fl.canMove ) { + postureIdeal = AIPOSTURE_STAND; + // Default the posture if trying to turn and we cant in the posture we chose + } else if ( (move.moveCommand == MOVE_FACE_ENEMY || move.moveCommand == MOVE_FACE_ENTITY) && !postureInfo[postureIdeal].fl.canTurn ) { + postureIdeal = AIPOSTURE_STAND; + } + + return (postureIdeal != postureCurrent); +} + +/* +================ +rvAITactical::OnPostureChange +================ +*/ +void rvAITactical::OnPostureChange ( void ) { + UpdateAnimPrefix ( ); +} + +/* +============ +rvAITactical::OnSetKey +============ +*/ +void rvAITactical::OnSetKey ( const char* key, const char* value ) { + idAI::OnSetKey ( key, value ); + +/* + if ( !idStr::Icmp ( key, "annoyed" ) ) { + playerAnnoyTime = SEC2MS( atof ( value ) ); + } +*/ +} + +/* +================ +rvAITactical::OnStopMoving +================ +*/ +void rvAITactical::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + // Ensure the peek doesnt happen immedately every time we stop at a cover + if ( IsBehindCover ( ) ){ + actionTimerPeek.Clear ( actionTime ); + actionTimerPeek.Add ( 2000, 0.5f ); + + actionKillswitchAttack.timer.Reset ( actionTime, actionKillswitchAttack.diversity ); + + // We should be looking fairly close to the right direction, so just snap it + TurnToward ( GetPhysics()->GetOrigin() + aasSensor->Reserved()->Normal() * 64.0f ); + move.current_yaw = move.ideal_yaw; + } + + idAI::OnStopMoving ( oldMoveCommand ); +} + +/* +================ +rvAITactical::CalculateShots +================ +*/ +void rvAITactical::CalculateShots ( const char* fireAnim ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + if ( shots > ammo ) { + shots = ammo; + } + + // Update the firing animation playback rate + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } +} + +/* +================ +rvAITactical::UseAmmo +================ +*/ +void rvAITactical::UseAmmo ( int amount ) { + if ( ammo <= 0 ) { + return; + } + + shots--; + ammo-=amount; + if ( ammo < 0 ) { + ammo = 0; + shots = 0; + } +} + +/* +================ +rvAITactical::GetDebugInfo +================ +*/ +void rvAITactical::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvAITactical", "postureIdeal", aiPostureString[postureIdeal], userData ); + proc ( "rvAITactical", "postureCurrent", aiPostureString[postureCurrent], userData ); + proc ( "rvAITactical", "healthRegen", va("%d",healthRegen), userData ); + proc ( "rvAITactical", "healthRegenEnabled",healthRegenEnabled?"true":"false", userData ); + proc ( "rvAITactical", "healthRegenNextTime",va("%d",healthRegenNextTime), userData ); + proc ( "rvAITactical", "maxHealth", va("%d",maxHealth), userData ); + + proc ( "rvAITactical", "nextWallTraceTime", va("%d",nextWallTraceTime), userData ); + + + proc ( "idAI", "action_killswitchAttack", aiActionStatusString[actionKillswitchAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvAITactical ) + STATE ( "Torso_RangedAttack", rvAITactical::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvAITactical::State_Torso_MovingRangedAttack ) + + STATE ( "Torso_Cover_LeanAttack", rvAITactical::State_Torso_Cover_LeanAttack ) + STATE ( "Torso_Cover_LeanLeftAttack", rvAITactical::State_Torso_Cover_LeanLeftAttack ) + STATE ( "Torso_Cover_LeanRightAttack", rvAITactical::State_Torso_Cover_LeanRightAttack ) + STATE ( "Torso_Cover_Peek", rvAITactical::State_Torso_Cover_Peek ) + + STATE ( "Torso_Reload", rvAITactical::State_Torso_Reload ) + + STATE ( "Torso_SetPosture", rvAITactical::State_Torso_SetPosture ) + + STATE ( "Frame_Peek", rvAITactical::State_Frame_Peek ) +END_CLASS_STATES + +/* +================ +rvAITactical::State_Torso_SetPosture +================ +*/ +stateResult_t rvAITactical::State_Torso_SetPosture ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_RELAXED, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: { + idStr transAnim = va("%s_to_%s", aiPostureString[postureCurrent], aiPostureString[postureIdeal] ); + if ( !HasAnim ( ANIMCHANNEL_TORSO, transAnim ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + + if ( postureCurrent < AIPOSTURE_STAND_COVER_LEFT + || postureCurrent > AIPOSTURE_CROUCH_COVER_RIGHT + || (postureIdeal != AIPOSTURE_STAND && postureIdeal != AIPOSTURE_RELAXED && postureIdeal != AIPOSTURE_CROUCH) ) + { + // FIXME: TEMPORARY UNTIL ANIM IS FIXED TO NOT HAVE ORIGIN TRANSLATION + move.fl.allowAnimMove = false; + } else { + //no need to play cover-to-stand/relaxed transition if: + //scripted... + //or we're moving already... + //or turning away from our old cover direction... + if ( aifl.scripted + /*|| (move.fl.moving&&!move.fl.blocked) + || (fabs(move.current_yaw-move.ideal_yaw) > 30.0f && (move.moveCommand == MOVE_FACE_ENEMY||move.moveCommand == MOVE_FACE_ENTITY))*/ ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, transAnim, parms.blendFrames ); + if ( postureCurrent >= AIPOSTURE_STAND_COVER_LEFT + && postureCurrent <= AIPOSTURE_CROUCH_COVER_RIGHT + && postureIdeal == AIPOSTURE_RELAXED ) { + //we need to also play stand_to_relaxed at the end... + return SRESULT_STAGE ( STAGE_WAIT_RELAXED ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT_RELAXED: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + if ( HasAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed" ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "stand_to_relaxed", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + } + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + postureCurrent = postureIdeal; + OnPostureChange ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_RangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + CalculateShots ( "range_attack" ); + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_start", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 0 ); + UseAmmo ( 1 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov ) { + return SRESULT_STAGE ( STAGE_END ); + } else if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, "range_attack_end", true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots ( "range_attack_torso" ); + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots <= 0 || !enemy.fl.inFov ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Reload +================ +*/ +stateResult_t rvAITactical::State_Torso_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + ammo = spawnArgs.GetInt ( "ammo" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanLeftAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanRightAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RangedAttack", parms.blendFrames ); + return SRESULT_DONE; +} + +/* +================ +rvAITactical::State_Torso_Cover_LeanAttack +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_LeanAttack ( const stateParms_t& parms ) { + enum { + STAGE_OUT, + STAGE_OUTWAIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_IN, + STAGE_INWAIT, + }; + switch ( parms.stage ) { + case STAGE_OUT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // The lean out animation cannot blend with any other animations since + // it is essential that the movement delta out match the one back in. Therefore + // we force the legs and torso to be stoped before playing any animations + torsoAnim.StopAnim ( 0 ); + legsAnim.StopAnim ( 0 ); + + PlayAnim ( ANIMCHANNEL_TORSO, "lean_out", 0 ); + return SRESULT_STAGE ( STAGE_OUTWAIT ); + + case STAGE_OUTWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Random number of shots + CalculateShots ( "lean_attack" ); + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + UseAmmo ( 1 ); + PlayAnim ( ANIMCHANNEL_TORSO, "lean_attack", 0 ); + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.dead ) { + //if enemy is dead, stop shooting soon + if ( shots > 5 ) { + shots = gameLocal.random.RandomInt(6); + } + } + if ( shots > 0 ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_STAGE ( STAGE_IN ); + } + return SRESULT_WAIT; + + case STAGE_IN: + PlayAnim ( ANIMCHANNEL_TORSO, "lean_in", 0 ); + return SRESULT_STAGE ( STAGE_INWAIT ); + + case STAGE_INWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Torso_Cover_Peek +================ +*/ +stateResult_t rvAITactical::State_Torso_Cover_Peek ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + if ( !PlayAnim ( ANIMCHANNEL_TORSO, "peek", parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvAITactical::State_Frame_Peek +================ +*/ +stateResult_t rvAITactical::State_Frame_Peek ( const stateParms_t& parms ) { + CheckForEnemy ( true, true ); + return SRESULT_OK; +} + +/* +================ +rvAITactical::Event_ForcePosture +================ +*/ +void rvAITactical::Event_ForcePosture ( int posture ) { + postureForce = (aiPosture_t)posture; +} + +/* +=================== +rvAITactical::IsCrouching +=================== +*/ +bool rvAITactical::IsCrouching( void ) const { + if ( postureCurrent == AIPOSTURE_CROUCH + || postureCurrent == AIPOSTURE_CROUCH_COVER + || postureCurrent == AIPOSTURE_CROUCH_COVER_LEFT + || postureCurrent == AIPOSTURE_CROUCH_COVER_RIGHT ) { + return true; + } + return idAI::IsCrouching(); +} + +/* +================ +rvAITactical::Event_PostSpawn +================ +*/ +void rvAITactical::Event_PostSpawn( void ) { + idAI::Event_PostSpawn(); + if ( team == AITEAM_MARINE && healthRegenEnabled ) + {//regen-enabled buddy marine + if ( CheckDeathCausesMissionFailure() ) + {//who is important to a mission + if ( g_skill.GetInteger() > 2 ) + {//on impossible + health *= 1.5f; + healthRegen *= 1.5f; + } + else if ( g_skill.GetInteger() > 1 ) + {//on hard + health *= 1.2f; + healthRegen *= 1.25f; + } + } + } +} \ No newline at end of file diff --git a/src/mpgame/ai/AI_Tactical.h b/src/mpgame/ai/AI_Tactical.h new file mode 100644 index 0000000..94248f8 --- /dev/null +++ b/src/mpgame/ai/AI_Tactical.h @@ -0,0 +1,158 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Tactical.h + +================ +*/ + +#ifndef __AI_TACTICAL__ +#define __AI_TACTICAL__ + +typedef enum { + AIPOSTURE_DEFAULT = -1, + AIPOSTURE_STAND, + AIPOSTURE_CROUCH, + AIPOSTURE_STAND_COVER_LEFT, + AIPOSTURE_STAND_COVER_RIGHT, + AIPOSTURE_CROUCH_COVER, + AIPOSTURE_CROUCH_COVER_LEFT, + AIPOSTURE_CROUCH_COVER_RIGHT, + AIPOSTURE_RELAXED, + AIPOSTURE_UNARMED, + AIPOSTURE_AT_ATTENTION, + AIPOSTURE_MAX +} aiPosture_t; + +typedef struct { + struct { + bool canMove; + bool canShoot; + bool canPeek; + bool canReload; + bool canTurn; + bool canKillswitch; + } fl; + +} aiPostureInfo_t; + +class rvAITactical : public idAI { +public: + + CLASS_PROTOTYPE( rvAITactical ); + + rvAITactical ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool CheckActions ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + + virtual bool IsCrouching ( void ) const; + +protected: + + virtual const char* GetIdleAnimName ( void ); + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual void OnPostureChange ( void ); + virtual void OnSetKey ( const char* key, const char* value ); + + bool CheckRelaxed ( void ) const; + + void InitPostureInfo ( void ); + bool UpdatePosture ( void ); + void CalculateShots ( const char* fireAnim ); + void UseAmmo ( int amount ); + void SetPosture ( aiPosture_t newPosture ); + void UpdateAnimPrefix ( void ); + + int ammo; + int maxShots; + int minShots; + int shots; + float fireRate; + + int playerFocusTime; + int playerAnnoyTime; + + aiPosture_t postureIdeal; + aiPosture_t postureCurrent; + aiPosture_t postureForce; + aiPostureInfo_t postureInfo[AIPOSTURE_MAX]; + + rvAIAction actionElbowAttack; + rvAIAction actionKillswitchAttack; + + rvAIActionTimer actionTimerPeek; + +private: + + int healthRegen; + bool healthRegenEnabled; + int healthRegenNextTime; + int maxHealth; + + int nextWallTraceTime; + + // Custom actions + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + bool CheckAction_Relax ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_SetPosture ( const stateParms_t& parms ); + + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Reload ( const stateParms_t& parms ); + + stateResult_t State_Torso_Cover_LeanLeftAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanRightAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_LeanAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Cover_Peek ( const stateParms_t& parms ); + + // Frame Commands + stateResult_t State_Frame_Peek ( const stateParms_t& parms ); + + // Events + void Event_ForcePosture ( int posture ); + + virtual void Event_PostSpawn ( void ); + + CLASS_STATES_PROTOTYPE ( rvAITactical ); +}; + +extern const idEventDef AI_ForcePosture; + +#endif /* !__AI_TACTICAL__ */ diff --git a/src/mpgame/ai/AI_Util.cpp b/src/mpgame/ai/AI_Util.cpp new file mode 100644 index 0000000..7ccd46a --- /dev/null +++ b/src/mpgame/ai/AI_Util.cpp @@ -0,0 +1,1001 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "AI_Manager.h" +#include "AI_Util.h" +#include "AAS_Find.h" + +/* +=============================================================================== + +rvAIHelper + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIHelper ) + EVENT( EV_Activate, rvAIHelper::Event_Activate ) +END_CLASS + +/* +================ +rvAIHelper::rvAIHelper +================ +*/ +rvAIHelper::rvAIHelper ( void ) { + helperNode.SetOwner( this ); +} + +/* +================ +rvAIHelper::Spawn +================ +*/ +void rvAIHelper::Spawn ( void ) { + // Auto activate? + if ( spawnArgs.GetBool ( "start_on" ) ) { + PostEventMS ( &EV_Activate, 0, this ); + } +} + +/* +================ +rvAIHelper::IsCombat +================ +*/ +bool rvAIHelper::IsCombat ( void ) const { + return false; +} + + +/* +================ +rvAIHelper::OnActivate +================ +*/ +void rvAIHelper::OnActivate ( bool active ) { + if ( active ) { + ActivateTargets ( this ); + } +} + +/* +================ +rvAIHelper::Event_Activate +================ +*/ +void rvAIHelper::Event_Activate( idEntity *activator ) { + if ( !helperNode.InList ( ) ) { + aiManager.RegisterHelper ( this ); + OnActivate ( true ); + } else { + aiManager.UnregisterHelper ( this ); + OnActivate ( false ); + } +} + +/* +================ +rvAIHelper::GetDirection +================ +*/ +idVec3 rvAIHelper::GetDirection ( const idAI* ai ) const { + if ( ai->team == 0 ) { + return GetPhysics()->GetAxis()[0]; + } + return -GetPhysics()->GetAxis()[0]; +} + +/* +================ +rvAIHelper::ValidateDestination +================ +*/ +bool rvAIHelper::ValidateDestination ( const idAI* ent, const idVec3& dest ) const { + return true; +} + +/* +=============================================================================== + +rvAICombatHelper + +=============================================================================== +*/ + +class rvAICombatHelper : public rvAIHelper { +public: + CLASS_PROTOTYPE( rvAICombatHelper ); + + rvAICombatHelper ( void ); + + void Spawn ( void ); + + virtual bool IsCombat ( void ) const; + virtual bool ValidateDestination ( const idAI* ent, const idVec3& dest ) const; + +protected: + + virtual void OnActivate ( bool active ); + + idEntityPtr location; +}; + +CLASS_DECLARATION( rvAIHelper, rvAICombatHelper ) +END_CLASS + +/* +================ +rvAICombatHelper::rvAICombatHelper +================ +*/ +rvAICombatHelper::rvAICombatHelper ( void ) { +} + +/* +================ +rvAICombatHelper::Spawn +================ +*/ +void rvAICombatHelper::Spawn ( void ) { +} + +/* +================ +rvAICombatHelper::OnActivate +================ +*/ +void rvAICombatHelper::OnActivate ( bool active ) { + rvAIHelper::OnActivate ( active ); + + if ( active ) { + if ( spawnArgs.GetBool ( "tetherLocation", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() ); + } else { + location = NULL; + } + } +} + +/* +================ +rvAICombatHelper::IsCombat +================ +*/ +bool rvAICombatHelper::IsCombat ( void ) const { + return true; +} + +/* +================ +rvAICombatHelper::ValidateDestination +================ +*/ +bool rvAICombatHelper::ValidateDestination ( const idAI* ai, const idVec3& dest ) const { + // If tethering to a location then see if the location of the given points matches our tethered location + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + + // Is the destination on the wrong side of the helper? + idVec3 origin; + idVec3 dir; + + dir = GetDirection(ai); + if ( ai->enemy.ent ) { + origin = ai->enemy.lastKnownPosition; + origin -= (dir * ai->combat.attackRange[0]); + } else { + origin = GetPhysics()->GetOrigin(); + origin -= (dir * 32.0f); + } + + if ( dir * (origin-dest) < 0.0f ) { + return false; + } + + // Would this destination link us to the wrong helper? + if ( static_cast< const rvAIHelper * >( aiManager.FindClosestHelper( dest ) ) != this ) { + return false; + } + + return true; +} + +/* +=============================================================================== + + rvAIAvoid + +=============================================================================== +*/ + +class rvAIAvoid : public idEntity { +public: + CLASS_PROTOTYPE( rvAIAvoid ); + + rvAIAvoid ( void ); + + void Spawn ( void ); +}; + +CLASS_DECLARATION( idEntity, rvAIAvoid ) +END_CLASS + +/* +================ +rvAIAvoid::rvAIAvoid +================ +*/ +rvAIAvoid::rvAIAvoid ( void ) { +} + +/* +================ +rvAIAvoid::Spawn +================ +*/ +void rvAIAvoid::Spawn ( void ) { + int team = -1; + if ( !spawnArgs.GetInt ( "teamFilter", "-1", team ) ) { + //hmm, no "teamFilter" set, check "team" since many were set up like this + team = spawnArgs.GetInt ( "team", "-1" ); + } + aiManager.AddAvoid ( GetPhysics()->GetOrigin(), spawnArgs.GetFloat ( "radius", "64" ), team ); + PostEventMS ( &EV_Remove, 0 ); +} + +/* +=============================================================================== + + rvAITrigger + +=============================================================================== +*/ + +const idEventDef AI_AppendFromSpawner ( "", "ee" ); + +CLASS_DECLARATION( idEntity, rvAITrigger ) + EVENT( EV_Activate, rvAITrigger::Event_Activate ) + EVENT( AI_AppendFromSpawner, rvAITrigger::Event_AppendFromSpawner ) +END_CLASS + +/* +================ +rvAITrigger::rvAITrigger +================ +*/ +rvAITrigger::rvAITrigger( void ) { +} + +/* +================ +rvAITrigger::Spawn +================ +*/ +void rvAITrigger::Spawn ( void ) { + nextTriggerTime = 0; + wait = SEC2MS ( spawnArgs.GetFloat ( "wait", "-1" ) ); + + conditionDead = spawnArgs.GetBool ( "condition_dead", "0" ); + conditionTether = spawnArgs.GetBool ( "condition_tether", "0" ); + conditionStop = spawnArgs.GetBool ( "condition_stop", "0" ); + + percent = spawnArgs.GetFloat ( "percent", "1" ); + + // Start on by default? + nextTriggerTime = spawnArgs.GetBool ( "start_on", "0" ) ? 0 : -1; + if ( nextTriggerTime == 0 ) { + BecomeActive ( TH_THINK ); + } else { + BecomeInactive ( TH_THINK ); + } + + // If there are no conditions we are done + if ( !conditionDead && !conditionTether && !conditionStop ) { + gameLocal.Warning ( "No conditions specified on ai trigger entity '%s'", GetName ( ) ); + PostEventMS ( &EV_Remove, 0 ); + } +} + +/* +================ +rvAITrigger::Save +================ +*/ +void rvAITrigger::Save ( idSaveGame *savefile ) const { + int i; + savefile->WriteInt ( testAI.Num ( ) ); + for ( i = 0; i < testAI.Num(); i ++ ) { + testAI[i].Save ( savefile ); + } + + savefile->WriteInt ( testSpawner.Num ( ) ); + for ( i = 0; i < testSpawner.Num(); i ++ ) { + testSpawner[i].Save ( savefile ); + } + + savefile->WriteBool ( conditionDead ); + savefile->WriteBool ( conditionTether ); + savefile->WriteBool ( conditionStop ); + + savefile->WriteInt ( wait ); + savefile->WriteInt ( nextTriggerTime ); + + savefile->WriteFloat ( percent ); +} + +/* +================ +rvAITrigger::Restore +================ +*/ +void rvAITrigger::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + testAI.Clear ( ); + testAI.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testAI[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + testSpawner.Clear ( ); + testSpawner.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + testSpawner[i].Restore ( savefile ); + } + + savefile->ReadBool ( conditionDead ); + savefile->ReadBool ( conditionTether ); + savefile->ReadBool ( conditionStop ); + + savefile->ReadInt ( wait ); + savefile->ReadInt ( nextTriggerTime ); + + savefile->ReadFloat ( percent ); +} + +/* +================ +rvAITrigger::Think +================ +*/ +void rvAITrigger::Think ( void ) { + int v; + + // Only trigger so often + if ( nextTriggerTime == -1 || gameLocal.time < nextTriggerTime ) { + return; + } + + // If we have any attached spawners then our condition cannot be met + if ( testSpawner.Num() ) { + for ( v = 0; v < testSpawner.Num(); v ++ ) { + rvSpawner* spawner = testSpawner[v]; + if ( !spawner ) { + testSpawner.RemoveIndex ( v ); + v--; + continue; + } + return; + } + } + + // If we have no AI we are tracking then wait until we do + if ( !testAI.Num ( ) ) { + return; + } + + int count = 0; + for ( v = 0; v < testAI.Num(); v ++ ) { + idAI* ai = testAI[v]; + if ( !ai ) { + testAI.RemoveIndex ( v ); + v--; + continue; + } + if ( !ai->aifl.dead ) { + if ( conditionDead ) { + continue; + } + if ( conditionTether && !ai->IsWithinTether ( ) ) { + continue; + } + if ( conditionStop && ai->move.fl.moving ) { + continue; + } + } + count++; + } + + // If result is true then we should fire our trigger now + if ( count >= (int) ((float)testAI.Num()*percent) ) { + ActivateTargets ( this ); + + // If only triggering once just remove ourselves now + if ( wait < 0 ) { + nextTriggerTime = -1; + } else { + nextTriggerTime = gameLocal.time + wait; + } + } +} + +/* +================ +rvAITrigger::FindTargets +================ +*/ +void rvAITrigger::FindTargets ( void ) { + int t; + + idEntity::FindTargets ( ); + + for ( t = 0; t < targets.Num(); t ++ ) { + idEntity* ent = targets[t]; + if ( !ent ) { + continue; + } + if ( ent->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + if ( ent->IsType ( rvSpawner::GetClassType ( ) ) ) { + static_cast(ent)->AddCallback ( this, &AI_AppendFromSpawner ); + testSpawner.Append ( idEntityPtr(static_cast(ent)) ); + targets.RemoveIndex ( t ); + t--; + continue; + } + } +} + +/* +================ +rvAITrigger::Event_Activate +================ +*/ +void rvAITrigger::Event_Activate( idEntity *activator ) { + + // Add spawners and ai to the list when they come in + if ( activator && activator->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(activator)) ); + return; + } + + if ( nextTriggerTime == -1 ) { + nextTriggerTime = 0; + BecomeActive ( TH_THINK ); + } else { + nextTriggerTime = -1; + BecomeInactive ( TH_THINK ); + } +} + +/* +================ +rvAITrigger::Event_AppendFromSpawner +================ +*/ +void rvAITrigger::Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ) { + // If its an ai entity being spawned then add it to our test list + if ( spawned && spawned->IsType ( idAI::GetClassType ( ) ) ) { + testAI.Append ( idEntityPtr(static_cast(spawned)) ); + } +} + +/* +=============================================================================== + + rvAITether + +=============================================================================== +*/ +const idEventDef EV_TetherSetupLocation ( "tetherSetupLocation" ); +const idEventDef EV_TetherGetLocation ( "tetherGetLocation" ); +CLASS_DECLARATION( idEntity, rvAITether ) + EVENT( EV_Activate, rvAITether::Event_Activate ) + EVENT( EV_TetherGetLocation, rvAITether::Event_TetherGetLocation ) + EVENT( EV_TetherSetupLocation, rvAITether::Event_TetherSetupLocation ) +END_CLASS + +/* +================ +rvAITether::rvAITether +================ +*/ +rvAITether::rvAITether ( void ) { +} + +/* +================ +rvAITether::InitNonPersistentSpawnArgs +================ +*/ +void rvAITether::InitNonPersistentSpawnArgs ( void ) { + tfl.canBreak = spawnArgs.GetBool ( "allowBreak", "1" ); + tfl.autoBreak = spawnArgs.GetBool ( "autoBreak", "0" ); + tfl.forceRun = spawnArgs.GetBool ( "forceRun", "0" ); + tfl.forceWalk = spawnArgs.GetBool ( "forceWalk", "0" ); + tfl.becomeAggressive = spawnArgs.GetBool ( "becomeAggressive", "0" ); + tfl.becomePassive = spawnArgs.GetBool ( "becomePassive", "0" ); + + // Check for both being set + if ( tfl.forceRun && tfl.forceWalk ) { + gameLocal.Warning ( "both forceRun and forceWalk were specified for tether '%s', forceRun will take precedence" ); + tfl.forceWalk = false; + } + if ( tfl.becomeAggressive && tfl.becomePassive ) { + gameLocal.Warning ( "both becomePassive and becomeAggressive were specified for tether '%s', becomeAggressive will take precedence" ); + tfl.becomePassive = false; + } +} + +/* +================ +rvAITether::Spawn +================ +*/ +void rvAITether::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); + + PostEventMS( &EV_TetherSetupLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherSetupLocation +================ +*/ +void rvAITether::Event_TetherSetupLocation( void ) { + //NOTE: we now do this right after spawn so we don't stomp other tether's locations + // if we activate after them and are in the same room as them. + // Dynamically-spawned locations are very, very bad! + + // All pre-existing locations should be placed and spread by now + // Get the location entity we are attached to + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + if ( !location ) { + location = gameLocal.AddLocation ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f, "tether_location" ); + } + } else { + location = NULL; + } + PostEventMS( &EV_TetherGetLocation, 100 ); +} + +/* +================ +rvAITether::Event_TetherGetLocation +================ +*/ +void rvAITether::Event_TetherGetLocation( void ) { + //NOW: all locations should be made & spread, get our location (may not be the one + // we added, it could be be the same as another tether if it's in the same room as us) + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } else { + location = NULL; + } +} + +/* +================ +rvAITether::Save +================ +*/ +void rvAITether::Save ( idSaveGame *savefile ) const { + location.Save( savefile ); +} + +/* +================ +rvAITether::Restore +================ +*/ +void rvAITether::Restore ( idRestoreGame *savefile ) { + location.Restore( savefile ); + + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITether::ValidateAAS +================ +*/ +bool rvAITether::ValidateAAS ( idAI* ai ) { + if ( !ai->aas ) { + return false; + } + return true; +} + +/* +================ +rvAITether::ValidateDestination +================ +*/ +bool rvAITether::ValidateDestination ( idAI* ai, const idVec3& dest ) { + if ( location ) { + if ( gameLocal.LocationForPoint ( dest - ai->GetPhysics()->GetGravityNormal() * 32.0f ) != location ) { + return false; + } + } + return true; +} + +/* +================ +rvAITether::ValidateBounds +================ +*/ +bool rvAITether::ValidateBounds ( const idBounds& bounds ) { + return true; +} + +/* +================ +rvAITether::FindGoal +================ +*/ +bool rvAITether::FindGoal ( idAI* ai, aasGoal_t& goal ) { + rvAASFindGoalForTether findGoal ( ai, this ); + if ( !ai->aas->FindNearestGoal( goal, + ai->PointReachableAreaNum( ai->GetPhysics()->GetOrigin() ), + ai->GetPhysics()->GetOrigin(), + GetPhysics()->GetOrigin(), + ai->move.travelFlags, + 0.0f, 0.0f, + NULL, 0, findGoal ) ) { + return false; + } + return true; +} + + +/* +================ +rvAITether::Event_Activate +================ +*/ +void rvAITether::Event_Activate( idEntity *activator ) { + int i; + + //WELL! Turns out designers are binding tethers to movers, so we have to get our location *again* on activation... + if ( spawnArgs.GetBool ( "location", "1" ) ) { + location = gameLocal.LocationForPoint ( GetPhysics()->GetOrigin() - GetPhysics()->GetGravityNormal() * 32.0f ); + } + + if ( activator && activator->IsType ( idAI::GetClassType() ) ) { + activator->ProcessEvent ( &EV_Activate, this ); + } + + // All targetted AI will be activated with the tether AI entity + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &EV_Activate, this ); + + // Aggressive/Passive stance change? + if ( tfl.becomeAggressive ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } else if ( tfl.becomePassive ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, false ); + } + } + } +} + +/* +================ +rvAITether::DebugDraw +================ +*/ +void rvAITether::DebugDraw ( void ) { + const idBounds& bounds = GetPhysics()->GetAbsBounds(); + gameRenderWorld->DebugBounds ( colorYellow, bounds.Expand ( 8.0f ) ); + gameRenderWorld->DebugArrow ( colorWhite, bounds.GetCenter(), bounds.GetCenter() + GetPhysics()->GetAxis()[0] * 16.0f, 8.0f ); + gameRenderWorld->DrawText( name.c_str(), bounds.GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DrawText( va( "#%d", entityNumber ), bounds.GetCenter() - GetPhysics()->GetGravityNormal() * 5.0f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +} + +/* +=============================================================================== + + rvAITetherBehind + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherBehind ) +END_CLASS + +/* +================ +rvAITetherBehind::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherBehind::InitNonPersistentSpawnArgs ( void ) { + range = spawnArgs.GetFloat ( "range" ); +} + +/* +================ +rvAITetherBehind::Spawn +================ +*/ +void rvAITetherBehind::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::Restore +================ +*/ +void rvAITetherBehind::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherBehind::ValidateDestination +================ +*/ +bool rvAITetherBehind::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + + // Make sure we include the move range in the tether + idVec3 origin; + if ( range ) { + origin = GetPhysics()->GetOrigin ( ) + GetPhysics()->GetAxis()[0] * GetOriginReachedRange() - GetPhysics()->GetAxis()[0] * range; + if ( GetPhysics()->GetAxis()[0] * (origin-dest) > 0.0f ) { + return false; + } + } + + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis()[0] * GetOriginReachedRange(); + + // Are we on wrong side of tether? + return ( GetPhysics()->GetAxis()[0] * (origin-dest) ) >= 0.0f; +} + +/* +================ +rvAITetherBehind::ValidateBounds +================ +*/ +bool rvAITetherBehind::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + + idPlane plane; + int side; + plane.SetNormal ( GetPhysics()->GetAxis ( )[0] ); + plane.FitThroughPoint ( GetPhysics()->GetOrigin ( ) ); + side = bounds.PlaneSide ( plane ); + return ( side == PLANESIDE_CROSS || side == PLANESIDE_BACK ); +} + +/* +================ +rvAITetherBehind::DebugDraw +================ +*/ +void rvAITetherBehind::DebugDraw ( void ) { + idVec3 dir; + + rvAITether::DebugDraw ( ); + + dir = GetPhysics()->GetGravityNormal ( ).Cross ( GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorPink, + GetPhysics()->GetOrigin() - dir * 1024.0f, + GetPhysics()->GetOrigin() + dir * 1024.0f ); + + if ( range ) { + idVec3 origin; + origin = GetPhysics()->GetOrigin ( ) - GetPhysics()->GetAxis ( )[0] * range; + gameRenderWorld->DebugArrow ( colorPink, GetPhysics()->GetOrigin(), origin, 5.0f ); + gameRenderWorld->DebugLine ( colorPink, + origin - dir * 1024.0f, + origin + dir * 1024.0f ); + } +} + +/* +=============================================================================== + + rvAITetherRadius + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherRadius ) +END_CLASS + +/* +================ +rvAITetherRadius::InitNonPersistentSpawnArgs +================ +*/ +void rvAITetherRadius::InitNonPersistentSpawnArgs ( void ) { + float radius; + if ( !spawnArgs.GetFloat ( "tetherRadius", "0", radius ) ) { + radius = spawnArgs.GetFloat ( "radius", "128" ); + } + radiusSqr = Square ( radius ); +} + +/* +================ +rvAITetherRadius::Spawn +================ +*/ +void rvAITetherRadius::Spawn ( void ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::Restore +================ +*/ +void rvAITetherRadius::Restore ( idRestoreGame* savefile ) { + InitNonPersistentSpawnArgs ( ); +} + +/* +================ +rvAITetherRadius::ValidateDestination +================ +*/ +bool rvAITetherRadius::ValidateDestination ( idAI* ai, const idVec3& dest ) { + // Check base tether first + if ( !rvAITether::ValidateDestination ( ai, dest ) ) { + return false; + } + // Are we within tether radius? + return ((dest - GetPhysics()->GetOrigin()).LengthSqr ( ) < radiusSqr - Square ( ai->move.range ) ); +} + + +/* +================ +rvAITetherRadius::ValidateBounds +================ +*/ +bool rvAITetherRadius::ValidateBounds ( const idBounds& bounds ) { + if ( !rvAITether::ValidateBounds ( bounds ) ) { + return false; + } + return ( Square ( bounds.ShortestDistance ( GetPhysics()->GetOrigin ( ) ) ) < radiusSqr ); +} + +/* +================ +rvAITetherRadius::DebugDraw +================ +*/ +void rvAITetherRadius::DebugDraw ( void ) { + rvAITether::DebugDraw ( ); + gameRenderWorld->DebugCircle( colorPink, GetPhysics()->GetOrigin(), GetPhysics()->GetGravityNormal(), idMath::Sqrt(radiusSqr), 25 ); +} + + +/* +=============================================================================== + + rvAITetherClear + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAITether, rvAITetherClear ) +END_CLASS + + +/* +=============================================================================== + + rvAIBecomePassive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomePassive ) + EVENT( EV_Activate, rvAIBecomePassive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomePassive::Event_Activate +================ +*/ +void rvAIBecomePassive::Event_Activate( idEntity *activator ) { + int i; + bool ignoreEnemies; + + ignoreEnemies = spawnArgs.GetBool ( "ignoreEnemies", "1" ); + + // All targeted AI will become passive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomePassive, ignoreEnemies ); + } + } +} + +/* +=============================================================================== + + rvAIBecomeAggressive + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvAIBecomeAggressive ) + EVENT( EV_Activate, rvAIBecomeAggressive::Event_Activate ) +END_CLASS + +/* +================ +rvAIBecomeAggressive::Event_Activate +================ +*/ +void rvAIBecomeAggressive::Event_Activate( idEntity *activator ) { + int i; + + // All targetted AI will become aggressive + for ( i = 0; i < targets.Num(); i ++ ) { + if ( !targets[i] ) { + continue; + } + if ( targets[i]->IsType ( idAI::GetClassType() ) ) { + targets[i]->ProcessEvent ( &AI_BecomeAggressive ); + } + } +} diff --git a/src/mpgame/ai/AI_Util.h b/src/mpgame/ai/AI_Util.h new file mode 100644 index 0000000..1c95005 --- /dev/null +++ b/src/mpgame/ai/AI_Util.h @@ -0,0 +1,256 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +AI_Util.h + +================ +*/ + +#ifndef __AI_UTIL__ +#define __AI_UTIL__ + +const float AI_TETHER_MINRANGE = 8.0f; + +/* +=============================================================================== + rvAITrigger +=============================================================================== +*/ + +class rvAITrigger : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITrigger ); + + rvAITrigger ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + virtual void Think ( void ); + + virtual void FindTargets ( void ); + +protected: + + idList< idEntityPtr > testAI; + idList< idEntityPtr > testSpawner; + + bool conditionDead; + bool conditionTether; + bool conditionStop; + + int wait; + int nextTriggerTime; + + float percent; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_PostRestore ( void ); + + void Event_AppendFromSpawner ( rvSpawner* spawner, idEntity* spawned ); +}; + +/* +=============================================================================== + rvAITether +=============================================================================== +*/ + +class rvAITether : public idEntity { +public: + CLASS_PROTOTYPE ( rvAITether ); + + rvAITether ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateAAS ( idAI* ai ); + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + + virtual bool FindGoal ( idAI* ai, aasGoal_t& goal ); + virtual float GetOriginReachedRange ( void ) {return AI_TETHER_MINRANGE;} + + virtual void DebugDraw ( void ); + + bool CanBreak ( void ) const; + bool IsAutoBreak ( void ) const; + + idList areaNum; + + bool IsWalkForced ( void ) const; + bool IsRunForced ( void ) const; + +protected: + + idEntityPtr location; + + struct tetherFlags_s { + bool canBreak :1; // Temporarily break when enemy is within tether + bool autoBreak :1; // Break when the ai gets within the tether + bool forceRun :1; // Alwasy run when heading towards tether + bool forceWalk :1; // Alwasy walk when heading towards tether + bool becomeAggressive :1; // + bool becomePassive :1; + } tfl; + +private: + + void Event_Activate ( idEntity* activator ); + void Event_TetherSetupLocation ( void ); + void Event_TetherGetLocation ( void ); +}; + +ID_INLINE bool rvAITether::CanBreak ( void ) const { + return tfl.canBreak; +} + +ID_INLINE bool rvAITether::IsWalkForced ( void ) const { + return tfl.forceWalk; +} + +ID_INLINE bool rvAITether::IsRunForced ( void ) const { + return tfl.forceRun; +} + +ID_INLINE bool rvAITether::IsAutoBreak ( void ) const { + return tfl.autoBreak; +} + +/* +=============================================================================== + rvAITetherBehind +=============================================================================== +*/ + +class rvAITetherBehind : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherBehind ); + + rvAITetherBehind( void ) { range = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + +protected: + + float range; +}; + +/* +=============================================================================== + rvAITetherRadius +=============================================================================== +*/ + +class rvAITetherRadius : public rvAITether { +public: + CLASS_PROTOTYPE ( rvAITetherRadius ); + + rvAITetherRadius( void ) { radiusSqr = 0.0f; } + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const { } + void Restore ( idRestoreGame *savefile ); + void InitNonPersistentSpawnArgs ( void ); + + virtual bool ValidateDestination ( idAI* ai, const idVec3& dest ); + virtual bool ValidateBounds ( const idBounds& bounds ); + virtual void DebugDraw ( void ); + + /* + virtual float GetOriginReachedRange ( void ) + { + float rad = sqrt(radiusSqr); + float halfRad = rad/2.0f; + if ( rad < AI_TETHER_MINRANGE ) + { + return rad; + } + return (halfRad. + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "AI.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + +#include "AI_Manager.h" +#include "../vehicle/Vehicle.h" +#include "../spawner.h" + + +#define idAI_EVENT( eventname, inparms, outparm) \ + const idEventDef AI_##eventname( #eventname, inparms, outparm); \ + void idAI::Event_##eventname + + +/*********************************************************************** + + AI Events + +***********************************************************************/ + +// Get / Set +const idEventDef AI_GetLeader ( "getLeader", NULL, 'e' ); +const idEventDef AI_SetLeader ( "setLeader", "E" ); +const idEventDef AI_GetEnemy ( "getEnemy", NULL, 'e' ); +const idEventDef AI_SetEnemy ( "setEnemy", "E" ); +const idEventDef AI_SetHealth ( "setHealth", "f" ); +const idEventDef AI_SetTalkState ( "setTalkState", "d" ); +const idEventDef AI_SetScript ( "setScript", "ss" ); +const idEventDef AI_SetMoveSpeed ( "setMoveSpeed", "d" ); +const idEventDef AI_SetPassivePrefix ( "setPassivePrefix", "s" ); + +// Enable / Disable +const idEventDef AI_EnableClip ( "enableClip" ); +const idEventDef AI_DisableClip ( "disableClip" ); +const idEventDef AI_EnableGravity ( "enableGravity" ); +const idEventDef AI_DisableGravity ( "disableGravity" ); +const idEventDef AI_EnableAFPush ( "enableAFPush" ); +const idEventDef AI_DisableAFPush ( "disableAFPush" ); +const idEventDef AI_EnableDamage ( "enableDamage" ); +const idEventDef AI_DisableDamage ( "disableDamage" ); +const idEventDef AI_EnableTarget ( "enableTarget" ); +const idEventDef AI_DisableTarget ( "disableTarget" ); +const idEventDef AI_EnableMovement ( "enableMovement" ); +const idEventDef AI_DisableMovement ( "disableMovement" ); +const idEventDef AI_TakeDamage ( "takeDamage", "f" ); +const idEventDef AI_SetUndying ( "setUndying", "f" ); +const idEventDef AI_EnableAutoBlink ( "enableAutoBlink" ); +const idEventDef AI_DisableAutoBlink ( "disableAutoBlink" ); + +// Scripted Sequences +const idEventDef AI_ScriptedMove ( "scriptedMove", "efd" ); +const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" ); +const idEventDef AI_ScriptedAnim ( "scriptedAnim", "sddd" ); +const idEventDef AI_ScriptedPlaybackMove ( "scriptedPlaybackMove", "sdd" ); +const idEventDef AI_ScriptedPlaybackAim ( "scriptedPlaybackAim", "sdd" ); +const idEventDef AI_ScriptedAction ( "scriptedAction", "ed" ); +const idEventDef AI_ScriptedDone ( "scriptedDone", NULL, 'd' ); +const idEventDef AI_ScriptedStop ( "scriptedStop" ); +const idEventDef AI_ScriptedJumpDown ( "scriptedJumpDown", "f" ); + +// Misc +const idEventDef AI_LookAt ( "lookAt", "E" ); +const idEventDef AI_Attack ( "attack", "ss" ); +const idEventDef AI_LockEnemyOrigin ( "lockEnemyOrigin" ); +const idEventDef AI_StopThinking ( "stopThinking" ); +const idEventDef AI_JumpFrame ( "" ); +const idEventDef AI_RealKill ( "" ); +const idEventDef AI_Kill ( "kill" ); +const idEventDef AI_RemoveUpdateSpawner ( "removeUpdateSpawner" ); +const idEventDef AI_AllowHiddenMovement ( "allowHiddenMovement", "d" ); +const idEventDef AI_Speak ( "speak", "s" ); +const idEventDef AI_SpeakRandom ( "speakRandom", "s" ); +const idEventDef AI_IsSpeaking ( "isSpeaking", NULL, 'f' ); +const idEventDef AI_IsTethered ( "isTethered", NULL, 'f' ); +const idEventDef AI_IsWithinTether ( "isWithinTether", NULL, 'f' ); +const idEventDef AI_LaunchMissile ( "launchMissile", "vv", 'e' ); +const idEventDef AI_AttackMelee ( "attackMelee", "s", 'd' ); +const idEventDef AI_DirectDamage ( "directDamage", "es" ); +const idEventDef AI_RadiusDamageFromJoint ( "radiusDamageFromJoint", "ss" ); +const idEventDef AI_MeleeAttackToJoint ( "meleeAttackToJoint", "ss", 'd' ); +const idEventDef AI_CanBecomeSolid ( "canBecomeSolid", NULL, 'f' ); +const idEventDef AI_BecomeSolid ( "becomeSolid" ); +const idEventDef AI_BecomeRagdoll ( "becomeRagdoll", NULL, 'd' ); +const idEventDef AI_BecomePassive ( "becomePassive", "d" ); +const idEventDef AI_BecomeAggressive ( "becomeAggressive" ); +const idEventDef AI_StopRagdoll ( "stopRagdoll" ); +const idEventDef AI_FaceEnemy ( "faceEnemy" ); +const idEventDef AI_FaceEntity ( "faceEntity", "E" ); + +//jshepard +const idEventDef AI_FindEnemy ( "findEnemy", "f", 'e'); + +void idAI::Event_Activate( idEntity *activator ) { Activate( activator );} +void idAI::Event_Touch( idEntity *other, trace_t *trace ) { OnTouch( other, trace ); } +void idAI::Event_SetEnemy( idEntity *ent ) { if ( !ent ) ClearEnemy(); else SetEnemy( ent );} +void idAI::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) { DirectDamage( damageDefName, damageTarget ); } +void idAI::Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { RadiusDamageFromJoint( jointname, damageDefName ); } +void idAI::Event_CanBecomeSolid( void ) { idThread::ReturnFloat( CanBecomeSolid() ); } +void idAI::Event_BecomeSolid( void ) { BecomeSolid(); } +void idAI::Event_BecomeNonSolid( void ) { BecomeNonSolid(); } +void idAI::Event_BecomeRagdoll( void ) { idThread::ReturnInt( StartRagdoll() ); } +void idAI::Event_StopRagdoll( void ) { StopRagdoll(); SetPhysics( &physicsObj ); } +void idAI::Event_SetHealth( float newHealth ) { health = newHealth; fl.takedamage = true; if( health > 0 ) aifl.dead = false; else aifl.dead = true; } +void idAI::Event_FaceEnemy( void ) { FaceEnemy(); } +void idAI::Event_FaceEntity( idEntity *ent ) { FaceEntity( ent ); } +void idAI::Event_SetTalkState( int state ) { SetTalkState ( (talkState_t)state ); } +void idAI::Event_Speak( const char *speechDecl ) { Speak( speechDecl ); } +void idAI::Event_SpeakRandom( const char *speechDecl ) { Speak( speechDecl, true ); } +void idAI::Event_GetLeader( void ) { idThread::ReturnEntity( leader ); } +void idAI::Event_SetLeader( idEntity* ent ) { SetLeader ( ent ); } +void idAI::Event_GetEnemy( void ) { idThread::ReturnEntity( enemy.ent ); } +void idAI::Event_TakeDamage( float takeDamage ) { fl.takedamage = ( takeDamage ) ? true : false; } +void idAI::Event_SetUndying( float setUndying ) { aifl.undying = ( setUndying ) ? true : false; } + + +void idAI::Event_IsSpeaking ( void ) { idThread::ReturnFloat ( IsSpeaking ( ) ); } +void idAI::Event_IsTethered ( void ) { idThread::ReturnFloat ( IsTethered ( ) ); } +void idAI::Event_IsWithinTether ( void ) { idThread::ReturnFloat ( IsWithinTether ( ) ); } +void idAI::Event_IsMoving ( void ) { idThread::ReturnFloat ( move.fl.moving ); } + +CLASS_DECLARATION( idActor, idAI ) + EVENT( EV_Activate, idAI::Event_Activate ) + EVENT( EV_Touch, idAI::Event_Touch ) + + // Enable / Disable + EVENT( AI_EnableClip, idAI::Event_EnableClip ) + EVENT( AI_DisableClip, idAI::Event_DisableClip ) + EVENT( AI_EnableGravity, idAI::Event_EnableGravity ) + EVENT( AI_DisableGravity, idAI::Event_DisableGravity ) + EVENT( AI_EnableAFPush, idAI::Event_EnableAFPush ) + EVENT( AI_DisableAFPush, idAI::Event_DisableAFPush ) + EVENT( AI_EnableDamage, idAI::Event_EnableDamage ) + EVENT( AI_DisableDamage, idAI::Event_DisableDamage ) + EVENT( AI_EnablePain, idAI::Event_EnablePain ) + EVENT( AI_DisablePain, idAI::Event_DisablePain ) + EVENT( AI_EnableTarget, idAI::Event_EnableTarget ) + EVENT( AI_DisableTarget, idAI::Event_DisableTarget ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_SetUndying, idAI::Event_SetUndying ) + EVENT( AI_EnableAutoBlink, idAI::Event_EnableAutoBlink ) + EVENT( AI_DisableAutoBlink, idAI::Event_DisableAutoBlink ) + + // Scripted sequences + EVENT( AI_ScriptedMove, idAI::Event_ScriptedMove ) + EVENT( AI_ScriptedFace, idAI::Event_ScriptedFace ) + EVENT( AI_ScriptedAnim, idAI::Event_ScriptedAnim ) + EVENT( AI_ScriptedAction, idAI::Event_ScriptedAction ) + EVENT( AI_ScriptedPlaybackMove, idAI::Event_ScriptedPlaybackMove ) + EVENT( AI_ScriptedPlaybackAim, idAI::Event_ScriptedPlaybackAim ) + EVENT( AI_ScriptedDone, idAI::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, idAI::Event_ScriptedStop ) + EVENT( AI_ScriptedJumpDown, idAI::Event_ScriptedJumpDown ) + + // Get / Set + EVENT( AI_SetTalkState, idAI::Event_SetTalkState ) + EVENT( AI_SetLeader, idAI::Event_SetLeader ) + EVENT( AI_GetLeader, idAI::Event_GetLeader ) + EVENT( AI_SetEnemy, idAI::Event_SetEnemy ) + EVENT( AI_GetEnemy, idAI::Event_GetEnemy ) + EVENT( EV_GetAngles, idAI::Event_GetAngles ) + EVENT( EV_SetAngles, idAI::Event_SetAngles ) + EVENT( AI_SetScript, idAI::Event_SetScript ) + EVENT( AI_SetMoveSpeed, idAI::Event_SetMoveSpeed ) + EVENT( AI_SetPassivePrefix, idAI::Event_SetPassivePrefix ) + + // Misc + EVENT( AI_Attack, idAI::Event_Attack ) + EVENT( AI_AttackMelee, idAI::Event_AttackMelee ) + + EVENT( AI_LookAt, idAI::Event_LookAt ) + EVENT( AI_DirectDamage, idAI::Event_DirectDamage ) + EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint ) + EVENT( AI_CanBecomeSolid, idAI::Event_CanBecomeSolid ) + EVENT( AI_BecomeSolid, idAI::Event_BecomeSolid ) + EVENT( EV_BecomeNonSolid, idAI::Event_BecomeNonSolid ) + EVENT( AI_BecomeRagdoll, idAI::Event_BecomeRagdoll ) + EVENT( AI_BecomePassive, idAI::Event_BecomePassive ) + EVENT( AI_BecomeAggressive, idAI::Event_BecomeAggressive ) + EVENT( AI_StopRagdoll, idAI::Event_StopRagdoll ) + EVENT( AI_SetHealth, idAI::Event_SetHealth ) + EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy ) + EVENT( AI_FaceEntity, idAI::Event_FaceEntity ) + EVENT( AI_StopThinking, idAI::Event_StopThinking ) + EVENT( AI_LockEnemyOrigin, idAI::Event_LockEnemyOrigin ) + EVENT( AI_JumpFrame, idAI::Event_JumpFrame ) + EVENT( AI_RealKill, idAI::Event_RealKill ) + EVENT( AI_Kill, idAI::Event_Kill ) + EVENT( AI_RemoveUpdateSpawner, idAI::Event_RemoveUpdateSpawner ) + EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement ) + EVENT( AI_Speak, idAI::Event_Speak ) + EVENT( AI_SpeakRandom, idAI::Event_SpeakRandom ) + EVENT( AI_IsSpeaking, idAI::Event_IsSpeaking ) + EVENT( AI_IsTethered, idAI::Event_IsTethered ) + EVENT( AI_IsWithinTether, idAI::Event_IsWithinTether ) + EVENT( EV_IsMoving, idAI::Event_IsMoving ) + EVENT( AI_TakeDamage, idAI::Event_TakeDamage ) + EVENT( AI_FindEnemy, idAI::Event_FindEnemy ) + EVENT( EV_SetKey, idAI::Event_SetKey ) + // RAVEN BEGIN + // twhitaker: needed this for difficulty settings + EVENT( EV_PostSpawn, idAI::Event_PostSpawn ) + // RAVEN END +END_CLASS + +/* +===================== +idAI::Event_PredictEnemyPos +===================== +*/ +void idAI::Event_PredictEnemyPos( float time ) { + predictedPath_t path; + idEntity* enemyEnt = enemy.ent; + + // if no enemy set + if ( !enemyEnt ) { + idThread::ReturnVector( physicsObj.GetOrigin() ); + return; + } + + // predict the enemy movement + idAI::PredictPath( enemyEnt, aas, enemy.lastKnownPosition, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnVector( path.endPos ); +} + +/* +===================== +idAI::Event_TestAnimMoveTowardEnemy +===================== +*/ +void idAI::Event_TestAnimMoveTowardEnemy( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + float yaw; + idVec3 delta; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + delta = enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(); + yaw = delta.ToYaw(); + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestAnimMove +===================== +*/ +void idAI::Event_TestAnimMove( const char *animname ) { + int anim; + predictedPath_t path; + idVec3 moveVec; + int animLen; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis(); + animLen = animator.AnimLength( anim ); + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec ); + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMoveToPosition +===================== +*/ +void idAI::Event_TestMoveToPosition( const idVec3 &position ) { + predictedPath_t path; + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), position - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, gameLocal.msec ); + gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, gameLocal.msec ); + if ( path.endEvent ) { + gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, gameLocal.msec ); + } + } + + idThread::ReturnInt( path.endEvent == 0 ); +} + +/* +===================== +idAI::Event_TestMeleeAttack +===================== +*/ +void idAI::Event_TestMeleeAttack( void ) { + bool result = TestMelee(); + idThread::ReturnInt( result ); +} + +/* +===================== +idAI::Event_TestAnimAttack +===================== +*/ +void idAI::Event_TestAnimAttack( const char *animname ) { + int anim; + predictedPath_t path; + + anim = GetAnim( ANIMCHANNEL_LEGS, animname ); + if ( !anim ) { + gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() ); + idThread::ReturnInt( false ); + return; + } + + idAI::PredictPath( this, aas, physicsObj.GetOrigin(), animator.TotalMovementDelta( anim ), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); + + idThread::ReturnInt( path.blockingEntity && ( path.blockingEntity == enemy.ent ) ); +} + +/* +===================== +idAI::Event_LockEnemyOrigin +===================== +*/ +void idAI::Event_LockEnemyOrigin ( void ) { + enemy.fl.lockOrigin = true; +} + +/* +===================== +idAI::Event_StopThinking +===================== +*/ +void idAI::Event_StopThinking( void ) { + BecomeInactive( TH_THINK ); + idThread *thread = idThread::CurrentThread(); + if ( thread ) { + thread->DoneProcessing(); + } +} + +/* +===================== +idAI::Event_JumpFrame +===================== +*/ +void idAI::Event_JumpFrame( void ) { + aifl.jump = true; +} + +/* +===================== +idAI::Event_EnableClip +===================== +*/ +void idAI::Event_EnableClip( void ) { + physicsObj.SetClipMask( MASK_MONSTERSOLID ); + Event_EnableGravity ( ); +} + +/* +===================== +idAI::Event_DisableClip +===================== +*/ +void idAI::Event_DisableClip( void ) { + physicsObj.SetClipMask( 0 ); + Event_DisableGravity ( ); +} + +/* +===================== +idAI::Event_EnableGravity +===================== +*/ +void idAI::Event_EnableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, false ); +} + +/* +===================== +idAI::Event_DisableGravity +===================== +*/ +void idAI::Event_DisableGravity( void ) { + OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, true ); +} + +/* +===================== +idAI::Event_EnableAFPush +===================== +*/ +void idAI::Event_EnableAFPush( void ) { + move.fl.allowPushMovables = true; +} + +/* +===================== +idAI::Event_DisableAFPush +===================== +*/ +void idAI::Event_DisableAFPush( void ) { + move.fl.allowPushMovables = false; +} + +/* +===================== +idAI::Event_EnableDamage +===================== +*/ +void idAI::Event_EnableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, true ); +} + +/* +===================== +idAI::Event_DisableDamage +===================== +*/ +void idAI::Event_DisableDamage ( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, false ); +} + +/* +=============== +idAI::Event_DisablePain +=============== +*/ +void idAI::Event_DisablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, true ); +} + +/* +=============== +idAI::Event_EnablePain +=============== +*/ +void idAI::Event_EnablePain( void ) { + OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, false ); +} + +/* +=============== +idAI::Event_EnableTarget +=============== +*/ +void idAI::Event_EnableTarget ( void ) { + fl.notarget = false; +} + +/* +=============== +idAI::Event_DisableTarget +=============== +*/ +void idAI::Event_DisableTarget ( void ) { + fl.notarget = true; +} + + +/* +===================== +idAI::Event_EnableAutoBlink +===================== +*/ +void idAI::Event_EnableAutoBlink( void ) { + fl.allowAutoBlink = true; +} + +/* +===================== +idAI::Event_DisableAutoBlink +===================== +*/ +void idAI::Event_DisableAutoBlink( void ) { + fl.allowAutoBlink = false; +} + +/* +===================== +idAI::Event_BecomeAggressive +===================== +*/ +void idAI::Event_BecomeAggressive ( void ) { + combat.fl.ignoreEnemies = false; + combat.fl.aware = true; + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_BecomePassive +===================== +*/ +void idAI::Event_BecomePassive ( int ignoreEnemies ) { + combat.fl.ignoreEnemies = (ignoreEnemies != 0); + combat.fl.aware = false; + SetEnemy ( NULL ); + ForceTacticalUpdate ( ); +} + +/* +===================== +idAI::Event_LookAt +===================== +*/ +void idAI::Event_LookAt ( idEntity* lookAt ) { + lookTarget = lookAt; +} + +/* +===================== +idAI::LookAtEntity +===================== +*/ +void idAI::LookAtEntity( idEntity *ent, float duration ) { + if ( ent == this ) { + ent = NULL; + } + + if ( ( ent != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { + focusEntity = ent; + alignHeadTime = gameLocal.time; + forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); + blink_time = 0; + } + + focusTime = gameLocal.time + SEC2MS( duration ); +} + +/* +================ +idAI::Event_ThrowMoveable +================ +*/ +void idAI::Event_ThrowMoveable( void ) { + idEntity *ent; + idEntity *moveable = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + moveable = ent; + break; + } + } + if ( moveable ) { + moveable->Unbind(); + moveable->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_ThrowAF +================ +*/ +void idAI::Event_ThrowAF( void ) { + idEntity *ent; + idEntity *af = NULL; + + for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::GetClassType() ) ) { +// RAVEN END + af = ent; + break; + } + } + if ( af ) { + af->Unbind(); + af->PostEventMS( &EV_SetOwner, 200, NULL ); + } +} + +/* +================ +idAI::Event_SetAngles +================ +*/ +void idAI::Event_SetAngles( idAngles const &ang ) { + move.current_yaw = ang.yaw; + viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3(); +} + +/* +================ +idAI::Event_GetAngles +================ +*/ +void idAI::Event_GetAngles( void ) { + idThread::ReturnVector( idVec3( 0.0f, move.current_yaw, 0.0f ) ); +} + +/* +================ +idAI::Event_RealKill +================ +*/ +void idAI::Event_RealKill( void ) { + health = 0; + + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + + Killed( this, this, 0, vec3_zero, INVALID_JOINT ); +} + +/* +================ +idAI::Event_Kill +================ +*/ +void idAI::Event_Kill( void ) { + PostEventMS( &AI_RealKill, 0 ); +} + +/* +================ +idAI::Event_RemoveUpdateSpawner +================ +*/ +void idAI::Event_RemoveUpdateSpawner( void ) { + // Detach from any spawners + if( GetSpawner() ) { + GetSpawner()->Detach( this ); + SetSpawner( NULL ); + } + + PostEventMS( &EV_Remove, 0 ); +} +/* +===================== +idAI::Event_FindActorsInBounds +===================== +*/ +void idAI::Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ) { + idEntity * ent; + idEntity * entityList[ MAX_GENTITIES ]; + int numListedEntities; + int i; + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedEntities = gameLocal.EntitiesTouchingBounds( this, idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES ); +// RAVEN END + for( i = 0; i < numListedEntities; i++ ) { + ent = entityList[ i ]; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + idThread::ReturnEntity( ent ); + return; + } + } + + idThread::ReturnEntity( NULL ); +} + +/* +===================== +idAI::Event_ClosestReachableEnemyOfEntity +===================== +*/ +void idAI::Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ) { + idActor *actor; + idActor *ent; + idActor *bestEnt; + float bestDistSquared; + float distSquared; + idVec3 delta; + int areaNum; + int enemyAreaNum; + aasPath_t path; + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !team_mate->IsType( idActor::GetClassType() ) ) { +// RAVEN END + gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() ); + } + + actor = static_cast( team_mate ); + + const idVec3 &origin = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( origin ); + + bestDistSquared = idMath::INFINITY; + bestEnt = NULL; + for( ent = actor->enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + delta = ent->GetPhysics()->GetOrigin() - origin; + distSquared = delta.LengthSqr(); + if ( distSquared < bestDistSquared ) { + const idVec3 &enemyPos = ent->GetPhysics()->GetOrigin(); + enemyAreaNum = PointReachableAreaNum( enemyPos ); + if ( ( areaNum != 0 ) && PathToGoal( path, areaNum, origin, enemyAreaNum, enemyPos ) ) { + bestEnt = ent; + bestDistSquared = distSquared; + } + } + } + + idThread::ReturnEntity( bestEnt ); +} + +/* +===================== +idAI::Event_EntityInAttackCone +===================== +*/ +void idAI::Event_EntityInAttackCone( idEntity *ent ) { + float attack_cone; + idVec3 delta; + float yaw; + float relYaw; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + delta = ent->GetPhysics()->GetOrigin() - GetEyePosition(); + + // get our gravity normal + const idVec3 &gravityDir = GetPhysics()->GetGravityNormal(); + + // infinite vertical vision, so project it onto our orientation plane + delta -= gravityDir * ( gravityDir * delta ); + + delta.Normalize(); + yaw = delta.ToYaw(); + + attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); + relYaw = idMath::AngleNormalize180( move.ideal_yaw - yaw ); + if ( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +/* +================ +idAI::Event_CanReachPosition +================ +*/ +void idAI::Event_CanReachPosition( const idVec3 &pos ) { + aasPath_t path; + int toAreaNum; + int areaNum; + + toAreaNum = PointReachableAreaNum( pos ); + areaNum = PointReachableAreaNum( physicsObj.GetOrigin() ); + if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEntity +================ +*/ +void idAI::Event_CanReachEntity( idEntity *ent ) { + aasPath_t path; + int toAreaNum; + int areaNum; + idVec3 pos; + + if ( !ent ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + toAreaNum = PointReachableAreaNum( pos ); + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !toAreaNum || !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_CanReachEnemy +================ +*/ +void idAI::Event_CanReachEnemy( void ) { + aasPath_t path; + int toAreaNum = 0; + int areaNum; + idVec3 pos; + idEntity *enemyEnt; + + enemyEnt = enemy.ent; + if ( !enemyEnt ) { + idThread::ReturnInt( false ); + return; + } + + if ( move.moveType != MOVETYPE_FLY ) { + if( enemyEnt->IsType( idActor::GetClassType() ) ){ + idActor *enemyAct = static_cast( enemyEnt ); + if ( enemyAct->OnLadder() ) { + idThread::ReturnInt( false ); + return; + } + enemyAct->GetAASLocation( aas, pos, toAreaNum ); + } + } else { + pos = enemyEnt->GetPhysics()->GetOrigin(); + toAreaNum = PointReachableAreaNum( pos ); + } + + if ( !toAreaNum ) { + idThread::ReturnInt( false ); + return; + } + + const idVec3 &org = physicsObj.GetOrigin(); + areaNum = PointReachableAreaNum( org ); + if ( !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) { + idThread::ReturnInt( false ); + } else { + idThread::ReturnInt( true ); + } +} + +/* +================ +idAI::Event_GetReachableEntityPosition +================ +*/ +void idAI::Event_GetReachableEntityPosition( idEntity *ent ) { + int toAreaNum; + idVec3 pos; + + if ( move.moveType != MOVETYPE_FLY ) { + if ( !ent->GetFloorPos( 64.0f, pos ) ) { + idThread::ReturnInt( false ); + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) && static_cast( ent )->OnLadder() ) { +// RAVEN END + idThread::ReturnInt( false ); + return; + } + } else { + pos = ent->GetPhysics()->GetOrigin(); + } + + if ( aas ) { + toAreaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( toAreaNum, pos ); + } + + idThread::ReturnVector( pos ); +} + +/* +================ +idAI::Event_ScriptedMove +================ +*/ +void idAI::Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) { + ScriptedMove ( destEnt, minDist, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedFace +================ +*/ +void idAI::Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + ScriptedFace ( faceEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAnim +================ +*/ +void idAI::Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + ScriptedAnim ( animname, blendFrames, loop, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedAction +================ +*/ +void idAI::Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) { + ScriptedAction ( actionEnt, endWithIdle ); +} + +/* +================ +idAI::Event_ScriptedPlaybackMove +================ +*/ +void idAI::Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackMove ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedPlaybackAim +================ +*/ +void idAI::Event_ScriptedPlaybackAim( const char* playback, int flags, int numFrames ) { + ScriptedPlaybackAim ( playback, flags, numFrames ); +} + +/* +================ +idAI::Event_ScriptedDone +================ +*/ +void idAI::Event_ScriptedDone ( void ) { + idThread::ReturnFloat ( !aifl.scripted ); +} + +/* +================ +idAI::Event_ScriptedStop +================ +*/ +void idAI::Event_ScriptedStop ( void ) { + ScriptedStop ( ); +} + +/* +================ +idAI::Event_AllowHiddenMovement +================ +*/ +void idAI::Event_AllowHiddenMovement( int enable ) { + move.fl.allowHiddenMove = ( enable != 0 ); +} + +/* +================ +idAI::Event_SetScript +================ +*/ +void idAI::Event_SetScript ( const char* scriptName, const char* funcName ) { + SetScript ( scriptName, funcName ); +} + +/* +================ +idAI::Event_SetMoveSpeed +================ +*/ +void idAI::Event_SetMoveSpeed ( int speed ) { + switch ( speed ) { + case AIMOVESPEED_DEFAULT: + move.fl.noRun = false; + move.fl.noWalk = false; + break; + + case AIMOVESPEED_RUN: + move.fl.noRun = false; + move.fl.noWalk = true; + break; + + case AIMOVESPEED_WALK: + move.fl.noRun = true; + move.fl.noWalk = false; + break; + } +} + +/* +================ +idAI::Event_SetPassivePrefix +================ +*/ +void idAI::Event_SetPassivePrefix ( const char* prefix ) { + SetPassivePrefix ( prefix ); +} + +/* +================ +idAI::Event_Attack +================ +*/ +void idAI::Event_Attack ( const char* attackName, const char* jointName ) { + Attack ( attackName, animator.GetJointHandle ( jointName ), enemy.ent ); // , physicsObj.GetPushedLinearVelocity ( ) ); +} + +/* +================ +idAI::Event_AttackMelee +================ +*/ +void idAI::Event_AttackMelee( const char* meleeName ) { + const idDict* meleeDict; + meleeDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", meleeName ) ), false ); + if ( !meleeDict ) { + gameLocal.Error ( "missing meleeDef '%s' for ai entity '%s'", meleeName, GetName() ); + } + AttackMelee ( meleeName, meleeDict ); +} + +/* +================ +idAI::Event_ScriptedJumpDown +================ +*/ +void idAI::Event_ScriptedJumpDown( float yaw ) { + if ( animator.HasAnim( "jumpdown_start" ) ) + { + aifl.scripted = true; + move.ideal_yaw = yaw; + SetState( "State_ScriptedJumpDown" ); + } +} + +/* +================ +idAI::Event_FindEnemy +================ +*/ +void idAI::Event_FindEnemy( float distSqr ) { + idThread::ReturnEntity ( FindEnemy( false, 1, distSqr )); +} + +/* +================ +idAI::Event_SetKey +================ +*/ +void idAI::Event_SetKey( const char *key, const char *value ) { + spawnArgs.Set( key, value ); + + OnSetKey ( key, value ); +} + +/* +================ +idAI::Event_PostSpawn +================ +*/ +void idAI::Event_PostSpawn( void ) { + // RAVEN BEGIN + // twhitaker: difficulty levels + if ( team == TEAM_MARINE ) { + //health /= 1.0f + gameLocal.GetDifficultyModifier( ); + + //buddies are a little more healthy on hard & nightmare since the baddies deal so much more damage + switch ( g_skill.GetInteger() ) { + case 3: + health *= 1.4f; + break; + case 2: + health *= 1.2f; + break; + case 0: + health *= 1.2f; + break; + case 1: + default: + break; + } + } else { + health *= 1.0f + gameLocal.GetDifficultyModifier( ); + } + // RAVEN END +} diff --git a/src/mpgame/ai/AI_pathing.cpp b/src/mpgame/ai/AI_pathing.cpp new file mode 100644 index 0000000..ed4bf4a --- /dev/null +++ b/src/mpgame/ai/AI_pathing.cpp @@ -0,0 +1,1649 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +// nmckenzie: +#include "AI.h" +// RAVEN END + +/* +=============================================================================== + + Dynamic Obstacle Avoidance + + - assumes the AI lives inside a bounding box aligned with the gravity direction + - obstacles in proximity of the AI are gathered + - if obstacles are found the AAS walls are also considered as obstacles + - every obstacle is represented by an oriented bounding box (OBB) + - an OBB is projected onto a 2D plane orthogonal to AI's gravity direction + - the 2D windings of the projections are expanded for the AI bbox + - a path tree is build using clockwise and counter clockwise edge walks along the winding edges + - the path tree is pruned and optimized + - the shortest path is chosen for navigation + +=============================================================================== +*/ + +const float MAX_OBSTACLE_RADIUS = 256.0f; +const float PUSH_OUTSIDE_OBSTACLES = 0.5f; +const float CLIP_BOUNDS_EPSILON = 10.0f; +const int MAX_AAS_WALL_EDGES = 256; +const int MAX_OBSTACLES = 256; +const int MAX_PATH_NODES = 256; +const int MAX_OBSTACLE_PATH = 64; + +typedef struct obstacle_s { + idVec2 bounds[2]; + idWinding2D winding; + idEntity * entity; + bool fakePlayerForwardObstacle; +} obstacle_t; + +typedef struct pathNode_s { + int dir; + idVec2 pos; + idVec2 delta; + float dist; + int obstacle; + int edgeNum; + int numNodes; + struct pathNode_s * parent; + struct pathNode_s * children[2]; + struct pathNode_s * next; + void Init(); +} pathNode_t; + +void pathNode_s::Init() { + dir = 0; + pos.Zero(); + delta.Zero(); + obstacle = -1; + edgeNum = -1; + numNodes = 0; + parent = children[0] = children[1] = next = NULL; +} + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc pathNodeAllocator; +// RAVEN END + +/* +============ +LineIntersectsPath +============ +*/ +bool LineIntersectsPath( const idVec2 &start, const idVec2 &end, const pathNode_t *node ) { + float d0, d1, d2, d3; + idVec3 plane1, plane2; + + plane1 = idWinding2D::Plane2DFromPoints( start, end ); + d0 = plane1.x * node->pos.x + plane1.y * node->pos.y + plane1.z; + while( node->parent ) { + d1 = plane1.x * node->parent->pos.x + plane1.y * node->parent->pos.y + plane1.z; + if ( FLOATSIGNBITSET( d0 ) ^ FLOATSIGNBITSET( d1 ) ) { + plane2 = idWinding2D::Plane2DFromPoints( node->pos, node->parent->pos ); + d2 = plane2.x * start.x + plane2.y * start.y + plane2.z; + d3 = plane2.x * end.x + plane2.y * end.y + plane2.z; + if ( FLOATSIGNBITSET( d2 ) ^ FLOATSIGNBITSET( d3 ) ) { + return true; + } + } + d0 = d1; + node = node->parent; + } + return false; +} + +/* +============ +PointInsideObstacle +============ +*/ +int PointInsideObstacle( const obstacle_t *obstacles, const int numObstacles, const idVec2 &point, bool skipFakePlayerForwardObstacles = false ) { + int i; + + for ( i = 0; i < numObstacles; i++ ) { + if ( skipFakePlayerForwardObstacles && obstacles[i].fakePlayerForwardObstacle ) { + //don't care if we're inside of these + continue; + } + + const idVec2 *bounds = obstacles[i].bounds; + if ( point.x < bounds[0].x || point.y < bounds[0].y || point.x > bounds[1].x || point.y > bounds[1].y ) { + continue; + } + + if ( !obstacles[i].winding.PointInside( point, 0.1f ) ) { + continue; + } + + return i; + } + + return -1; +} + +/* +============ +GetPointOutsideObstacles +============ +*/ +void GetPointOutsideObstacles( const obstacle_t *obstacles, const int numObstacles, idVec2 &point, int *obstacle, int *edgeNum ) { + int i, j, k, n, bestObstacle, bestEdgeNum, queueStart, queueEnd, edgeNums[2]; + float d, bestd, scale[2]; + idVec3 plane, bestPlane; + idVec2 newPoint, dir, bestPoint; + int *queue; + bool *obstacleVisited; + idWinding2D w1, w2; + + if ( obstacle ) { + *obstacle = -1; + } + if ( edgeNum ) { + *edgeNum = -1; + } + + bestObstacle = PointInsideObstacle( obstacles, numObstacles, point ); + if ( bestObstacle == -1 ) { + return; + } + + const idWinding2D &w = obstacles[bestObstacle].winding; + bestd = idMath::INFINITY; + bestEdgeNum = 0; + for ( i = 0; i < w.GetNumPoints(); i++ ) { + plane = idWinding2D::Plane2DFromPoints( w[(i+1)%w.GetNumPoints()], w[i], true ); + d = plane.x * point.x + plane.y * point.y + plane.z; + if ( d < bestd ) { + bestd = d; + bestPlane = plane; + bestEdgeNum = i; + } + // if this is a wall always try to pop out at the first edge + if ( obstacles[bestObstacle].entity == NULL ) { + break; + } + } + + newPoint = point - ( bestd + PUSH_OUTSIDE_OBSTACLES ) * bestPlane.ToVec2(); + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + point = newPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + + queue = (int *) _alloca( numObstacles * sizeof( queue[0] ) ); + obstacleVisited = (bool *) _alloca( numObstacles * sizeof( obstacleVisited[0] ) ); + + queueStart = 0; + queueEnd = 1; + queue[0] = bestObstacle; + + memset( obstacleVisited, 0, numObstacles * sizeof( obstacleVisited[0] ) ); + obstacleVisited[bestObstacle] = true; + + bestd = idMath::INFINITY; + for ( i = queue[0]; queueStart < queueEnd; i = queue[++queueStart] ) { + w1 = obstacles[i].winding; + w1.Expand( PUSH_OUTSIDE_OBSTACLES ); + + for ( j = 0; j < numObstacles; j++ ) { + // if the obstacle has been visited already + if ( obstacleVisited[j] ) { + continue; + } + // if the bounds do not intersect + if ( obstacles[j].bounds[0].x > obstacles[i].bounds[1].x || obstacles[j].bounds[0].y > obstacles[i].bounds[1].y || + obstacles[j].bounds[1].x < obstacles[i].bounds[0].x || obstacles[j].bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + + queue[queueEnd++] = j; + obstacleVisited[j] = true; + + w2 = obstacles[j].winding; + w2.Expand( 0.2f ); + + for ( k = 0; k < w1.GetNumPoints(); k++ ) { + dir = w1[(k+1)%w1.GetNumPoints()] - w1[k]; + if ( !w2.RayIntersection( w1[k], dir, scale[0], scale[1], edgeNums ) ) { + continue; + } + for ( n = 0; n < 2; n++ ) { + newPoint = w1[k] + scale[n] * dir; + if ( PointInsideObstacle( obstacles, numObstacles, newPoint ) == -1 ) { + d = ( newPoint - point ).LengthSqr(); + if ( d < bestd ) { + bestd = d; + bestPoint = newPoint; + bestEdgeNum = edgeNums[n]; + bestObstacle = j; + } + } + } + } + } + + if ( bestd < idMath::INFINITY ) { + point = bestPoint; + if ( obstacle ) { + *obstacle = bestObstacle; + } + if ( edgeNum ) { + *edgeNum = bestEdgeNum; + } + return; + } + } + gameLocal.Warning( "GetPointOutsideObstacles: no valid point found" ); +} + +/* +============ +GetFirstBlockingObstacle +============ +*/ +bool GetFirstBlockingObstacle( const obstacle_t *obstacles, int numObstacles, int skipObstacle, const idVec2 &startPos, const idVec2 &delta, float &blockingScale, int &blockingObstacle, int &blockingEdgeNum ) { + int i, edgeNums[2]; + float dist, scale1, scale2; + idVec2 bounds[2]; + + // get bounds for the current movement delta + bounds[0] = startPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = startPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(delta.x)].x += delta.x; + bounds[FLOATSIGNBITNOTSET(delta.y)].y += delta.y; + + // test for obstacles blocking the path + blockingScale = idMath::INFINITY; + dist = delta.Length(); + for ( i = 0; i < numObstacles; i++ ) { + if ( i == skipObstacle ) { + continue; + } + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( startPos, delta, scale1, scale2, edgeNums ) ) { + if ( scale1 < blockingScale && scale1 * dist > -0.01f && scale2 * dist > 0.01f ) { + blockingScale = scale1; + blockingObstacle = i; + blockingEdgeNum = edgeNums[0]; + } + } + } + return ( blockingScale < 1.0f ); +} + +/* +============ +GetObstacles +============ +*/ +int GetObstacles( const idActor* owner, const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) { + int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum, numIterations, extrude; + int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2]; + float stepHeight, headHeight, blockingScale, min, max; + idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd; + idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal; + idVec2 obDelta; + idPhysics *obPhys; + idBox box; + idEntity *obEnt; + idClipModel *clipModel; + idClipModel *clipModelList[ MAX_GENTITIES ]; + idVec3 extrudeVec; + idPlayer* player = gameLocal.GetLocalPlayer(); + bool extrudePlayer = false; + + if ( player && owner->team == player->team ) { + if ( player->HasEnemies() + || (player->weapon && player->weapon->lastAttack > gameLocal.GetTime() - 2000) + || (owner->IsType( idAI::GetClassType() ) && ((idAI*)owner)->GetEnemy()) ) { + + extrudeVec = player->firstPersonViewAxis[0]; + extrudeVec[2] = 0; + extrudeVec.Normalize(); + idVec3 myMoveDir = (seekPos-startPos); + /* + myMoveDir.z = 0; + if ( myMoveDir * extrudeVec > 200.0f ) + {//I'm already moving out away from him in the direction he's moving... (he's following me?) + } + else + {//we are heading into his line of sight, stop + */ + extrudePlayer = true; + extrudeVec *= (player->GetPhysics()->GetBounds()[1][0]-player->GetPhysics()->GetBounds()[0][0]); + //} + //FIXME: keep moving if already inside this cone? Otherwise, we'll stop in our tracks? + } + } + + numObstacles = 0; + + seekDelta = seekPos - startPos; + expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + + physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight ); + stepHeight += aas->GetSettings()->maxStepHeight; + + // clip bounds for the obstacle search space + clipBounds[0] = clipBounds[1] = startPos; + clipBounds.AddPoint( seekPos ); + clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS ); + clipMask = physics->GetClipMask(); + + // find all obstacles touching the clip bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numListedClipModels = gameLocal.ClipModelsTouchingBounds( owner, clipBounds, clipMask, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) { + clipModel = clipModelList[i]; + obEnt = clipModel->GetEntity(); + + if ( !clipModel->IsTraceModel() ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( obEnt->IsType( idActor::GetClassType() ) ) { +// RAVEN END + obPhys = obEnt->GetPhysics(); + // ignore myself, my enemy, and dead bodies + if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) { + continue; + } + // if the actor is moving + // cdr: Alternate Routes Bug + // idVec3 v1 = obPhys->GetLinearVelocity(); + // if ( v1.LengthSqr() > Square( 10.0f ) ) { + // idVec3 v2 = physics->GetLinearVelocity(); + // if ( v2.LengthSqr() > Square( 10.0f ) ) { + // // if moving in about the same direction + // if ( v1 * v2 > 0.0f ) { + // continue; + // } + // } + // } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +// cdr: Ignore Pushable objects + } else if (obEnt->GetPhysics()->IsPushable()) { + continue; + } else if ( obEnt->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // moveables are considered obstacles + } else { + // ignore everything else + continue; + } + + // check if we can step over the object + clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max ); + if ( max < stepHeight || min > headHeight ) { + // can step over this one + continue; + } + + numIterations = 1; + if ( extrudePlayer && obEnt == player ) + {//do magic + numIterations = 3; + } + for ( extrude = 0; extrude < numIterations; extrude++ ) { + // project a box containing the obstacle onto the floor plane + box = idBox( clipModel->GetBounds(), extrude?clipModel->GetOrigin()+(extrude*extrudeVec):clipModel->GetOrigin(), clipModel->GetAxis() ); + numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts ); + + // create a 2D winding for the obstacle; + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + for ( j = 0; j < numVerts; j++ ) { + obstacle.winding.AddPoint( silVerts[j].ToVec2() ); + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( j = 0; j < numVerts; j++ ) { + silVerts[j].z = startPos.z; + } + for ( j = 0; j < numVerts; j++ ) { + gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 ); + } + } + + // expand the 2D winding for collision with a 2D box + obstacle.winding.ExpandForAxialBox( expBounds ); + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = obEnt; + if ( extrudePlayer && obEnt == player && extrude > 0 ) { + //don't care if we're inside one of these, because that would just make us stop where we are, anyway! + obstacle.fakePlayerForwardObstacle = true; + } else { + obstacle.fakePlayerForwardObstacle = false; + } + } + } + + // if there are no dynamic obstacles the path should be through valid AAS space + if ( numObstacles == 0 ) { + return 0; + } + + // if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space + if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2(), true ) == -1 ) { + if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) { + return 0; + } + } + + // create obstacles for AAS walls + if ( aas ) { + float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f; + + numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES ); + aas->SortWallEdges( wallEdges, numWallEdges ); + + lastVerts[0] = lastVerts[1] = 0; + lastEdgeNormal.Zero(); + nextVerts[0] = nextVerts[1] = 0; + for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) { + aas->GetEdge( wallEdges[i], start, end ); + aas->GetEdgeVertexNumbers( wallEdges[i], verts ); + edgeDir = end.ToVec2() - start.ToVec2(); + edgeDir.Normalize(); + edgeNormal.x = edgeDir.y; + edgeNormal.y = -edgeDir.x; + if ( i < numWallEdges-1 ) { + aas->GetEdge( wallEdges[i+1], nextStart, nextEnd ); + aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts ); + nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2(); + nextEdgeDir.Normalize(); + nextEdgeNormal.x = nextEdgeDir.y; + nextEdgeNormal.y = -nextEdgeDir.x; + } + + obstacle_t &obstacle = obstacles[numObstacles++]; + obstacle.winding.Clear(); + obstacle.winding.AddPoint( end.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() ); + obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize ); + obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize ); + if ( lastVerts[1] == verts[0] ) { + obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[1] -= edgeDir; + } + if ( verts[1] == nextVerts[0] ) { + obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize; + } else { + obstacle.winding[0] += edgeDir; + } + obstacle.winding.GetBounds( obstacle.bounds ); + obstacle.entity = NULL; + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( lastVerts, verts, sizeof( lastVerts ) ); +// RAVEN END + lastEdgeNormal = edgeNormal; + } + } + + // show obstacles + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + for ( i = 0; i < numObstacles; i++ ) { + obstacle_t &obstacle = obstacles[i]; + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + silVerts[j].ToVec2() = obstacle.winding[j]; + silVerts[j].z = startPos.z; + } + for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) { + gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 ); + } + } + } + + return numObstacles; +} + +/* +============ +FreePathTree_r +============ +*/ +void FreePathTree_r( pathNode_t *node ) { + if ( node->children[0] ) { + FreePathTree_r( node->children[0] ); + } + if ( node->children[1] ) { + FreePathTree_r( node->children[1] ); + } + pathNodeAllocator.Free( node ); +} + +/* +============ +DrawPathTree +============ +*/ +void DrawPathTree( const pathNode_t *root, const float height ) { + int i; + idVec3 start, end; + const pathNode_t *node; + + for ( node = root; node; node = node->next ) { + for ( i = 0; i < 2; i++ ) { + if ( node->children[i] ) { + start.ToVec2() = node->pos; + start.z = height; + end.ToVec2() = node->children[i]->pos; + end.z = height; + gameRenderWorld->DebugArrow( node->edgeNum == -1 ? colorYellow : i ? colorBlue : colorRed, start, end, 1 ); + break; + } + } + } +} + +/* +============ +GetPathNodeDelta +============ +*/ +bool GetPathNodeDelta( pathNode_t *node, const obstacle_t *obstacles, const idVec2 &seekPos, bool blocked ) { + int numPoints, edgeNum; + bool facing; + idVec2 seekDelta, dir; + pathNode_t *n; + + numPoints = obstacles[node->obstacle].winding.GetNumPoints(); + + // get delta along the current edge + while( 1 ) { + edgeNum = ( node->edgeNum + node->dir ) % numPoints; + node->delta = obstacles[node->obstacle].winding[edgeNum] - node->pos; + if ( node->delta.LengthSqr() > 0.01f ) { + break; + } + node->edgeNum = ( node->edgeNum + numPoints + ( 2 * node->dir - 1 ) ) % numPoints; + } + + // if not blocked + if ( !blocked ) { + + // test if the current edge faces the goal + seekDelta = seekPos - node->pos; + facing = ( ( 2 * node->dir - 1 ) * ( node->delta.x * seekDelta.y - node->delta.y * seekDelta.x ) ) >= 0.0f; + + // if the current edge faces goal and the line from the current + // position to the goal does not intersect the current path + if ( facing && !LineIntersectsPath( node->pos, seekPos, node->parent ) ) { + node->delta = seekPos - node->pos; + node->edgeNum = -1; + } + } + + // if the delta is along the obstacle edge + if ( node->edgeNum != -1 ) { + // if the edge is found going from this node to the root node + for ( n = node->parent; n; n = n->parent ) { + + if ( node->obstacle != n->obstacle || node->edgeNum != n->edgeNum ) { + continue; + } + + // test whether or not the edge segments actually overlap + if ( n->pos * node->delta > ( node->pos + node->delta ) * node->delta ) { + continue; + } + if ( node->pos * node->delta > ( n->pos + n->delta ) * node->delta ) { + continue; + } + + break; + } + if ( n ) { + return false; + } + } + return true; +} + +/* +============ +BuildPathTree +============ +*/ +pathNode_t *BuildPathTree( const obstacle_t *obstacles, int numObstacles, const idBounds &clipBounds, const idVec2 &startPos, const idVec2 &seekPos, obstaclePath_t &path ) { + int blockingEdgeNum, blockingObstacle, obstaclePoints, bestNumNodes = MAX_OBSTACLE_PATH; + float blockingScale; + pathNode_t *root, *node, *child; + + // gcc 4.0 + idQueueTemplate pathNodeQueue, treeQueue; + + root = pathNodeAllocator.Alloc(); + root->Init(); + root->pos = startPos; + + root->delta = seekPos - root->pos; + root->numNodes = 0; + pathNodeQueue.Add( root ); + + for ( node = pathNodeQueue.Get(); node && pathNodeAllocator.GetAllocCount() < MAX_PATH_NODES; node = pathNodeQueue.Get() ) { + + treeQueue.Add( node ); + + // if this path has more than twice the number of nodes than the best path so far + if ( node->numNodes > bestNumNodes * 2 ) { + continue; + } + + // don't move outside of the clip bounds + idVec2 endPos = node->pos + node->delta; + if ( endPos.x - CLIP_BOUNDS_EPSILON < clipBounds[0].x || endPos.x + CLIP_BOUNDS_EPSILON > clipBounds[1].x || + endPos.y - CLIP_BOUNDS_EPSILON < clipBounds[0].y || endPos.y + CLIP_BOUNDS_EPSILON > clipBounds[1].y ) { + continue; + } + + // if an obstacle is blocking the path + if ( GetFirstBlockingObstacle( obstacles, numObstacles, node->obstacle, node->pos, node->delta, blockingScale, blockingObstacle, blockingEdgeNum ) ) { + + if ( path.firstObstacle == NULL ) { + path.firstObstacle = obstacles[blockingObstacle].entity; + } + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(obstacles[blockingObstacle].entity); + // RAVEN END + + + node->delta *= blockingScale; + + if ( node->edgeNum == -1 ) { + node->children[0] = pathNodeAllocator.Alloc(); + node->children[0]->Init(); + node->children[1] = pathNodeAllocator.Alloc(); + node->children[1]->Init(); + node->children[0]->dir = 0; + node->children[1]->dir = 1; + node->children[0]->parent = node->children[1]->parent = node; + node->children[0]->pos = node->children[1]->pos = node->pos + node->delta; + node->children[0]->obstacle = node->children[1]->obstacle = blockingObstacle; + node->children[0]->edgeNum = node->children[1]->edgeNum = blockingEdgeNum; + node->children[0]->numNodes = node->children[1]->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( node->children[0], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[0] ); + } + if ( GetPathNodeDelta( node->children[1], obstacles, seekPos, true ) ) { + pathNodeQueue.Add( node->children[1] ); + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->obstacle = blockingObstacle; + child->edgeNum = blockingEdgeNum; + child->numNodes = node->numNodes + 1; + if ( GetPathNodeDelta( child, obstacles, seekPos, true ) ) { + pathNodeQueue.Add( child ); + } + } + } else { + node->children[node->dir] = child = pathNodeAllocator.Alloc(); + child->Init(); + child->dir = node->dir; + child->parent = node; + child->pos = node->pos + node->delta; + child->numNodes = node->numNodes + 1; + + // there is a free path towards goal + if ( node->edgeNum == -1 ) { + if ( node->numNodes < bestNumNodes ) { + bestNumNodes = node->numNodes; + } + continue; + } + + child->obstacle = node->obstacle; + obstaclePoints = obstacles[node->obstacle].winding.GetNumPoints(); + child->edgeNum = ( node->edgeNum + obstaclePoints + ( 2 * node->dir - 1 ) ) % obstaclePoints; + + if ( GetPathNodeDelta( child, obstacles, seekPos, false ) ) { + pathNodeQueue.Add( child ); + } + } + } + + return root; +} + +/* +============ +PrunePathTree +============ +*/ +void PrunePathTree( pathNode_t *root, const idVec2 &seekPos ) { + int i; + float bestDist; + pathNode_t *node, *lastNode, *n, *bestNode; + + node = root; + while( node ) { + + node->dist = ( seekPos - node->pos ).LengthSqr(); + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + + // find the node closest to the goal along this path + bestDist = idMath::INFINITY; + bestNode = node; + for ( n = node; n; n = n->parent ) { + if ( n->children[0] && n->children[1] ) { + break; + } + if ( n->dist < bestDist ) { + bestDist = n->dist; + bestNode = n; + } + } + + // free tree down from the best node + for ( i = 0; i < 2; i++ ) { + if ( bestNode->children[i] ) { + FreePathTree_r( bestNode->children[i] ); + bestNode->children[i] = NULL; + } + } + + for ( lastNode = bestNode, node = bestNode->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && ( node->children[1] != lastNode ) ) { + node = node->children[1]; + break; + } + } + } + } +} + +/* +============ +OptimizePath +============ +*/ +int OptimizePath( const pathNode_t *root, const pathNode_t *leafNode, const obstacle_t *obstacles, int numObstacles, idVec2 optimizedPath[MAX_OBSTACLE_PATH] ) { + int i, numPathPoints, edgeNums[2]; + const pathNode_t *curNode, *nextNode; + idVec2 curPos, curDelta, bounds[2]; + float scale1, scale2, curLength; + + optimizedPath[0] = root->pos; +// BDUBE: FIXME - Added this line because quite a few places in the code count on there being at least 2 items in that array + optimizedPath[1] = root->pos; + + numPathPoints = 1; + + for ( nextNode = curNode = root; curNode != leafNode; curNode = nextNode ) { + + for ( nextNode = leafNode; nextNode->parent != curNode; nextNode = nextNode->parent ) { + + // can only take shortcuts when going from one object to another + if ( nextNode->obstacle == curNode->obstacle ) { + continue; + } + + curPos = curNode->pos; + curDelta = nextNode->pos - curPos; + curLength = curDelta.Length(); + + // get bounds for the current movement delta + bounds[0] = curPos - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[1] = curPos + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON ); + bounds[FLOATSIGNBITNOTSET(curDelta.x)].x += curDelta.x; + bounds[FLOATSIGNBITNOTSET(curDelta.y)].y += curDelta.y; + + // test if the shortcut intersects with any obstacles + for ( i = 0; i < numObstacles; i++ ) { + if ( bounds[0].x > obstacles[i].bounds[1].x || bounds[0].y > obstacles[i].bounds[1].y || + bounds[1].x < obstacles[i].bounds[0].x || bounds[1].y < obstacles[i].bounds[0].y ) { + continue; + } + if ( obstacles[i].winding.RayIntersection( curPos, curDelta, scale1, scale2, edgeNums ) ) { + if ( scale1 >= 0.0f && scale1 <= 1.0f && ( i != nextNode->obstacle || scale1 * curLength < curLength - 0.5f ) ) { + break; + } + if ( scale2 >= 0.0f && scale2 <= 1.0f && ( i != nextNode->obstacle || scale2 * curLength < curLength - 0.5f ) ) { + break; + } + } + } + if ( i >= numObstacles ) { + break; + } + } + + // store the next position along the optimized path + optimizedPath[numPathPoints++] = nextNode->pos; + } + + return numPathPoints; +} + +/* +============ +PathLength +============ +*/ +float PathLength( idVec2 optimizedPath[MAX_OBSTACLE_PATH], int numPathPoints, const idVec2 &curDir ) { + int i; + float pathLength; + + // calculate the path length + pathLength = 0.0f; + for ( i = 0; i < numPathPoints-1; i++ ) { + pathLength += ( optimizedPath[i+1] - optimizedPath[i] ).LengthFast(); + } + + // add penalty if this path does not go in the current direction + if ( curDir * ( optimizedPath[1] - optimizedPath[0] ) < 0.0f ) { + pathLength += 100.0f; + } + return pathLength; +} + +/* +============ +FindOptimalPath + + Returns true if there is a path all the way to the goal. +============ +*/ +bool FindOptimalPath( const idActor* owner, const pathNode_t *root, const obstacle_t *obstacles, int numObstacles, const float height, const idVec3 &curDir, idVec3 &seekPos ) { + int i, numPathPoints, bestNumPathPoints; + const pathNode_t *node, *lastNode, *bestNode; + idVec2 optimizedPath[MAX_OBSTACLE_PATH]; + float pathLength, bestPathLength; + bool pathToGoalExists, optimizedPathCalculated; + + seekPos.Zero(); + seekPos.z = height; + + pathToGoalExists = false; + optimizedPathCalculated = false; + + bestNode = root; + bestNumPathPoints = 0; + bestPathLength = idMath::INFINITY; + + node = root; + while( node ) { + + pathToGoalExists |= ( node->dist < 0.1f ); + + if ( node->dist <= bestNode->dist ) { + + if ( idMath::Fabs( node->dist - bestNode->dist ) < 0.1f ) { + + if ( !optimizedPathCalculated ) { + bestNumPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + bestPathLength = PathLength( optimizedPath, bestNumPathPoints, curDir.ToVec2() ); + seekPos.ToVec2() = optimizedPath[1]; + } + + numPathPoints = OptimizePath( root, node, obstacles, numObstacles, optimizedPath ); + pathLength = PathLength( optimizedPath, numPathPoints, curDir.ToVec2() ); + + if ( pathLength < bestPathLength ) { + bestNode = node; + bestNumPathPoints = numPathPoints; + bestPathLength = pathLength; + seekPos.ToVec2() = optimizedPath[1]; + } + optimizedPathCalculated = true; + + } else { + + bestNode = node; + optimizedPathCalculated = false; + } + } + + if ( node->children[0] ) { + node = node->children[0]; + } else if ( node->children[1] ) { + node = node->children[1]; + } else { + for ( lastNode = node, node = node->parent; node; lastNode = node, node = node->parent ) { + if ( node->children[1] && node->children[1] != lastNode ) { + node = node->children[1]; + break; + } + } + } + } + + if ( !pathToGoalExists ) { + if ( root->children[0] ) { + seekPos.ToVec2() = root->children[0]->pos; + } else if ( root->children[1] ) { + seekPos.ToVec2() = root->children[1]->pos; + } + } else if ( !optimizedPathCalculated ) { + OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + seekPos.ToVec2() = optimizedPath[1]; + } + + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + idVec3 start, end; + start.z = end.z = height + 4.0f; + numPathPoints = OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); + for ( i = 0; i < numPathPoints-1; i++ ) { + start.ToVec2() = optimizedPath[i]; + end.ToVec2() = optimizedPath[i+1]; + gameRenderWorld->DebugArrow( colorCyan, start, end, 1 ); + } + } + + return pathToGoalExists; +} + +/* +============ +idAI::FindPathAroundObstacles + + Finds a path around dynamic obstacles using a path tree with clockwise and counter clockwise edge walks. +============ +*/ +bool idAI::FindPathAroundObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, const idVec3 &startPos, const idVec3 &seekPos, obstaclePath_t &path ) { + int numObstacles, areaNum, insideObstacle; + static obstacle_t obstacles[MAX_OBSTACLES]; + idBounds clipBounds; + idBounds bounds; + pathNode_t *root; + bool pathToGoalExists; + + path.seekPos = seekPos; + path.firstObstacle = NULL; + path.startPosOutsideObstacles = startPos; + path.startPosObstacle = NULL; + path.seekPosOutsideObstacles = seekPos; + path.seekPosObstacle = NULL; + + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.Clear(); + // RAVEN END + + if ( !aas ) { + return true; + } + idActor* owner = dynamic_cast(physics->GetSelf()); + + + bounds[1] = aas->GetSettings()->boundingBoxes[0][1]; + bounds[0] = -bounds[1]; + bounds[1].z = 32.0f; + + // get the AAS area number and a valid point inside that area + areaNum = aas->PointReachableAreaNum( path.startPosOutsideObstacles, bounds, (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ); + aas->PushPointIntoAreaNum( areaNum, path.startPosOutsideObstacles ); + + // get all the nearby obstacles + numObstacles = GetObstacles( owner, physics, aas, ignore, areaNum, path.startPosOutsideObstacles, path.seekPosOutsideObstacles, obstacles, MAX_OBSTACLES, clipBounds ); + + // get a source position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.startPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.startPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.startPosObstacle); + // RAVEN END + } + + // get a goal position outside the obstacles + GetPointOutsideObstacles( obstacles, numObstacles, path.seekPosOutsideObstacles.ToVec2(), &insideObstacle, NULL ); + if ( insideObstacle != -1 ) { + path.seekPosObstacle = obstacles[insideObstacle].entity; + // RAVEN BEGIN + // cdr: Alternate Routes Bug + path.allObstacles.AddUnique(path.seekPosObstacle); + // RAVEN END + } + + // if start and destination are pushed to the same point, we don't have a path around the obstacle + if ( ( path.seekPosOutsideObstacles.ToVec2() - path.startPosOutsideObstacles.ToVec2() ).LengthSqr() < Square( 1.0f ) ) { + if ( ( seekPos.ToVec2() - startPos.ToVec2() ).LengthSqr() > Square( 2.0f ) ) { + return false; + } + } + + // build a path tree + root = BuildPathTree( obstacles, numObstacles, clipBounds, path.startPosOutsideObstacles.ToVec2(), path.seekPosOutsideObstacles.ToVec2(), path ); + + // draw the path tree + if ( owner->DebugFilter(ai_showObstacleAvoidance) ) { + DrawPathTree( root, physics->GetOrigin().z ); + } + + // prune the tree + PrunePathTree( root, path.seekPosOutsideObstacles.ToVec2() ); + + // find the optimal path + pathToGoalExists = FindOptimalPath( owner, root, obstacles, numObstacles, physics->GetOrigin().z, physics->GetLinearVelocity(), path.seekPos ); + + // free the tree + FreePathTree_r( root ); + + return pathToGoalExists; +} + +/* +============ +idAI::FreeObstacleAvoidanceNodes +============ +*/ +void idAI::FreeObstacleAvoidanceNodes( void ) { + pathNodeAllocator.Shutdown(); +} + + +/* +=============================================================================== + + Path Prediction + + Uses the AAS to quickly and accurately predict a path for a certain + period of time based on an initial position and velocity. + +=============================================================================== +*/ + +const float OVERCLIP = 1.001f; +const int MAX_FRAME_SLIDE = 5; + +typedef struct pathTrace_s { + float fraction; + idVec3 endPos; + idVec3 normal; + const idEntity * blockingEntity; +} pathTrace_t; + +/* +============ +PathTrace + + Returns true if a stop event was triggered. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Adding ignoreent parm +bool PathTrace( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &end, int stopEvent, struct pathTrace_s &trace, predictedPath_t &path, const idEntity *ignore = NULL ) { +// RAVEN BEGIN + trace_t clipTrace; + aasTrace_t aasTrace; + + memset( &trace, 0, sizeof( trace ) ); + + if ( !aas || !aas->GetSettings() ) { +// RAVEN BEGIN +// nmckenzie: Added ignore ent +// ddynerman: multiple clip worlds + gameLocal.Translation( ent, clipTrace, start, end, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + // NOTE: could do (expensive) ledge detection here for when there is no AAS file + + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } else { + aasTrace.getOutOfSolid = true; + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + aasTrace.flags |= AREA_LEDGE; + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + aasTrace.travelFlags |= TFL_INVALID; + } + + aas->Trace( aasTrace, start, end ); + +// RAVEN BEGIN +// nmckenzie: Added ignore ent. + gameLocal.TranslationEntities( ent, clipTrace, start, aasTrace.endpos, ent->GetPhysics()->GetClipModel(), + ent->GetPhysics()->GetClipModel()->GetAxis(), MASK_MONSTERSOLID, ent, ignore ); +// RAVEN END + + if ( clipTrace.fraction >= 1.0f ) { + + trace.fraction = aasTrace.fraction; + trace.endPos = aasTrace.endpos; + trace.normal = aas->GetPlane( aasTrace.planeNum ).Normal(); + trace.blockingEntity = gameLocal.world; + + if ( aasTrace.fraction < 1.0f ) { + if ( stopEvent & SE_ENTER_LEDGE_AREA ) { + if ( aas->AreaFlags( aasTrace.blockingAreaNum ) & AREA_LEDGE ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_LEDGE_AREA; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) { // PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + if ( stopEvent & SE_ENTER_OBSTACLE ) { + if ( aas->AreaTravelFlags( aasTrace.blockingAreaNum ) & TFL_INVALID ) { + path.endPos = trace.endPos; + path.endNormal = trace.normal; + path.endEvent = SE_ENTER_OBSTACLE; + path.blockingEntity = trace.blockingEntity; + + if ( ai_debugMove.GetBool() ) {// PathTrace + gameRenderWorld->DebugLine( colorRed, start, aasTrace.endpos ); + } + return true; + } + } + } + } else { + trace.fraction = clipTrace.fraction; + trace.endPos = clipTrace.endpos; + trace.normal = clipTrace.c.normal; + trace.blockingEntity = gameLocal.entities[ clipTrace.c.entityNum ]; + } + } + + if ( trace.fraction >= 1.0f ) { + trace.blockingEntity = NULL; + } + + return false; +} + +/* +============ +idAI::PredictPath + + Can also be used when there is no AAS file available however ledges are not detected. +============ +*/ +// RAVEN BEGIN +// nmckenzie: Added ignore ent parm. +bool idAI::PredictPath( const idEntity *ent, const idAAS *aas, const idVec3 &start, const idVec3 &velocity, int totalTime, int frameTime, int stopEvent, predictedPath_t &path, const idEntity *ignore ) { +// RAVEN END + int i, j, step, numFrames, curFrameTime; + idVec3 delta, curStart, curEnd, curVelocity, lastEnd, stepUp, tmpStart; + idVec3 gravity, gravityDir, invGravityDir; + float maxStepHeight, minFloorCos; + pathTrace_t trace; + + if ( aas && aas->GetSettings() ) { + gravity = aas->GetSettings()->gravity; + gravityDir = aas->GetSettings()->gravityDir; + invGravityDir = aas->GetSettings()->invGravityDir; + maxStepHeight = aas->GetSettings()->maxStepHeight; + minFloorCos = aas->GetSettings()->minFloorCos; + } else { + gravity = DEFAULT_GRAVITY_VEC3; + gravityDir = idVec3( 0, 0, -1 ); + invGravityDir = idVec3( 0, 0, 1 ); + maxStepHeight = 14.0f; + minFloorCos = 0.7f; + } + + path.endPos = start; + path.endVelocity = velocity; + path.endNormal.Zero(); + path.endEvent = 0; + path.endTime = 0; + path.blockingEntity = NULL; + + curStart = start; + curVelocity = velocity; + + numFrames = ( totalTime + frameTime - 1 ) / frameTime; + curFrameTime = frameTime; + for ( i = 0; i < numFrames; i++ ) { + + if ( i == numFrames-1 ) { + curFrameTime = totalTime - i * curFrameTime; + } + + delta = curVelocity * curFrameTime * 0.001f; + + path.endVelocity = curVelocity; + path.endTime = i * frameTime; + + // allow sliding along a few surfaces per frame + for ( j = 0; j < MAX_FRAME_SLIDE; j++ ) { + + idVec3 lineStart = curStart; + + // allow stepping up three times per frame + for ( step = 0; step < 3; step++ ) { + + curEnd = curStart + delta; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curEnd, stopEvent, trace, path, ignore ) ) { +// RAVEN END + return true; + } + + if ( step ) { + + // step down at end point + tmpStart = trace.endPos; + curEnd = tmpStart - stepUp; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, tmpStart, curEnd, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + + // if not moved any further than without stepping up, or if not on a floor surface + if ( (lastEnd - start).LengthSqr() > (trace.endPos - start).LengthSqr() - 0.1f || + ( trace.normal * invGravityDir ) < minFloorCos ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = lastEnd; + path.endEvent = SE_BLOCKED; + + if ( ai_debugMove.GetBool() ) { + gameRenderWorld->DebugLine( colorRed, lineStart, lastEnd ); + } + + return true; + } + + curStart = lastEnd; + break; + } + } + + path.endNormal = trace.normal; + path.blockingEntity = trace.blockingEntity; + + // if the trace is not blocked or blocked by a floor surface + if ( trace.fraction >= 1.0f || ( trace.normal * invGravityDir ) > minFloorCos ) { + curStart = trace.endPos; + break; + } + + // save last result + lastEnd = trace.endPos; + + // step up + stepUp = invGravityDir * maxStepHeight; +// RAVEN BEGIN +// nmckenzie: Added ignore ent + if ( PathTrace( ent, aas, curStart, curStart + stepUp, stopEvent, trace, path, ignore ) ) { + return true; + } +// RAVEN END + stepUp *= trace.fraction; + curStart = trace.endPos; + } + + if ( ai_debugMove.GetBool() ) {//PredictPath + gameRenderWorld->DebugLine( colorRed, lineStart, curStart ); + } + + if ( trace.fraction >= 1.0f ) { + break; + } + + delta.ProjectOntoPlane( trace.normal, OVERCLIP ); + curVelocity.ProjectOntoPlane( trace.normal, OVERCLIP ); + + if ( stopEvent & SE_BLOCKED ) { + // if going backwards + if ( (curVelocity - gravityDir * curVelocity * gravityDir ) * + (velocity - gravityDir * velocity * gravityDir) < 0.0f ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + + return true; + } + } + } + + if ( j >= MAX_FRAME_SLIDE ) { + if ( stopEvent & SE_BLOCKED ) { + path.endPos = curStart; + path.endEvent = SE_BLOCKED; + return true; + } + } + + // add gravity + curVelocity += gravity * frameTime * 0.001f; + } + + path.endTime = totalTime; + path.endVelocity = curVelocity; + path.endPos = curStart; + path.endEvent = 0; + + return false; +} + + +/* +=============================================================================== + + Trajectory Prediction + + Finds the best collision free trajectory for a clip model based on an + initial position, target position and speed. + +=============================================================================== +*/ + +/* +===================== +Ballistics + + get the ideal aim pitch angle in order to hit the target + also get the time it takes for the projectile to arrive at the target +===================== +*/ +typedef struct ballistics_s { + float angle; // angle in degrees in the range [-180, 180] + float time; // time it takes before the projectile arrives +} ballistics_t; + +static int Ballistics( const idVec3 &start, const idVec3 &end, float speed, float gravity, ballistics_t bal[2] ) { + int n, i; + float x, y, a, b, c, d, sqrtd, inva, p[2]; + + x = ( end.ToVec2() - start.ToVec2() ).Length(); + y = end[2] - start[2]; + + a = 4.0f * y * y + 4.0f * x * x; + b = -4.0f * speed * speed - 4.0f * y * gravity; + c = gravity * gravity; + + d = b * b - 4.0f * a * c; + if ( d <= 0.0f || a == 0.0f ) { + return 0; + } + sqrtd = idMath::Sqrt( d ); + inva = 0.5f / a; + p[0] = ( - b + sqrtd ) * inva; + p[1] = ( - b - sqrtd ) * inva; + n = 0; + for ( i = 0; i < 2; i++ ) { + if ( p[i] <= 0.0f ) { + continue; + } + d = idMath::Sqrt( p[i] ); + bal[n].angle = idMath::ATan( 0.5f * ( 2.0f * y * p[i] - gravity ) / d, d * x ); + bal[n].time = x / ( idMath::Cos( bal[n].angle ) * speed ); + bal[n].angle = idMath::AngleNormalize180( RAD2DEG( bal[n].angle ) ); + n++; + } + + return n; +} + +/* +===================== +HeightForTrajectory + +Returns the maximum hieght of a given trajectory +===================== +*/ +// RAVEN BEGIN +// nmckenzie: Removing static for this one. +float HeightForTrajectory( const idVec3 &start, float zVel, float gravity ) { +// RAVEN END + float maxHeight, t; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + + return maxHeight; +} + +/* +===================== +idAI::TestTrajectory +===================== +*/ +bool idAI::TestTrajectory( const idVec3 &start, const idVec3 &end, float zVel, float gravity, float time, float max_height, const idClipModel *clip, int clipmask, const idEntity *ignore, const idEntity *targetEntity, int drawtime ) { + int i, numSegments; + float maxHeight, t, t2; + idVec3 points[5]; + trace_t trace; + bool result; + + t = zVel / gravity; + // maximum height of projectile + maxHeight = start.z - 0.5f * gravity * ( t * t ); + // time it takes to fall from the top to the end height + t = idMath::Sqrt( ( maxHeight - end.z ) / ( 0.5f * -gravity ) ); + + // start of parabolic + points[0] = start; + + if ( t < time ) { + numSegments = 4; + // point in the middle between top and start + t2 = ( time - t ) * 0.5f; + points[1].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // top of parabolic + t2 = time - t; + points[2].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[2].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + // point in the middel between top and end + t2 = time - t * 0.5f; + points[3].ToVec2() = start.ToVec2() + (end.ToVec2() - start.ToVec2()) * ( t2 / time ); + points[3].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } else { + numSegments = 2; + // point halfway through + t2 = time * 0.5f; + points[1].ToVec2() = start.ToVec2() + ( end.ToVec2() - start.ToVec2() ) * 0.5f; + points[1].z = start.z + t2 * zVel + 0.5f * gravity * t2 * t2; + } + + // end of parabolic + points[numSegments] = end; + + if ( drawtime ) { + for ( i = 0; i < numSegments; i++ ) { + gameRenderWorld->DebugLine( colorRed, points[i], points[i+1], drawtime ); + } + } + + // make sure projectile doesn't go higher than we want it to go + for ( i = 0; i < numSegments; i++ ) { + if ( points[i].z > max_height ) { + // goes higher than we want to allow + return false; + } + } + + result = true; + for ( i = 0; i < numSegments; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, points[i], points[i+1], clip, mat3_identity, clipmask, ignore ); +// RAVEN END + if ( trace.fraction < 1.0f ) { + if ( gameLocal.GetTraceEntity( trace ) == targetEntity ) { + result = true; + } else { + result = false; + } + break; + } + } + + if ( drawtime ) { + if ( clip ) { + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, clip->GetBounds().Expand( 1.0f ), trace.endpos, drawtime ); + } else { + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( result ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + } + + return result; +} + +/* +===================== +idAI::PredictTrajectory + + returns true if there is a collision free trajectory for the clip model + aimDir is set to the ideal aim direction in order to hit the target +===================== +*/ +bool idAI::PredictTrajectory( const idVec3 &firePos, const idVec3 &target, float projectileSpeed, const idVec3 &projGravity, const idClipModel *clip, int clipmask, float max_height, const idEntity *ignore, const idEntity *targetEntity, int drawtime, idVec3 &aimDir ) { + int n, i, j; + float zVel, a, t, pitch, s, c; + trace_t trace; + ballistics_t ballistics[2]; + idVec3 dir[2]; + idVec3 velocity; + idVec3 lastPos, pos; + + assert( targetEntity ); + + // check if the projectile starts inside the target + if ( targetEntity->GetPhysics()->GetAbsBounds().IntersectsBounds( clip->GetBounds().Translate( firePos ) ) ) { + aimDir = target - firePos; + aimDir.Normalize(); + return true; + } + + // if no velocity or the projectile is not affected by gravity + if ( projectileSpeed <= 0.0f || projGravity == vec3_origin ) { + + aimDir = target - firePos; + aimDir.Normalize(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( NULL, trace, firePos, target, clip, mat3_identity, clipmask, ignore ); +// RAVEN END + + if ( drawtime ) { + gameRenderWorld->DebugLine( colorRed, firePos, target, drawtime ); + idBounds bnds( trace.endpos ); + bnds.ExpandSelf( 1.0f ); + gameRenderWorld->DebugBounds( ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ) ? colorGreen : colorYellow, bnds, vec3_zero, drawtime ); + } + + return ( trace.fraction >= 1.0f || ( gameLocal.GetTraceEntity( trace ) == targetEntity ) ); + } + + n = Ballistics( firePos, target, projectileSpeed, projGravity[2], ballistics ); + if ( n == 0 ) { + // there is no valid trajectory + aimDir = target - firePos; + aimDir.Normalize(); + return false; + } + + // make sure the first angle is the smallest + if ( n == 2 ) { + if ( ballistics[1].angle < ballistics[0].angle ) { + a = ballistics[0].angle; ballistics[0].angle = ballistics[1].angle; ballistics[1].angle = a; + t = ballistics[0].time; ballistics[0].time = ballistics[1].time; ballistics[1].time = t; + } + } + + // test if there is a collision free trajectory + for ( i = 0; i < n; i++ ) { + pitch = DEG2RAD( ballistics[i].angle ); + idMath::SinCos( pitch, s, c ); + dir[i] = target - firePos; + dir[i].z = 0.0f; + dir[i] *= c * idMath::InvSqrt( dir[i].LengthSqr() ); + dir[i].z = s; + + zVel = projectileSpeed * dir[i].z; + + if ( ai_debugTrajectory.GetBool() ) { + t = ballistics[i].time / 100.0f; + velocity = dir[i] * projectileSpeed; + lastPos = firePos; + pos = firePos; + for ( j = 1; j < 100; j++ ) { + pos += velocity * t; + velocity += projGravity * t; +// RAVEN BEGIN +// nmckenzie: Added time parm to debugline so I can actually see it. + gameRenderWorld->DebugLine( colorCyan, lastPos, pos, drawtime ); +// RAVEN END + lastPos = pos; + } + } + + if ( TestTrajectory( firePos, target, zVel, projGravity[2], ballistics[i].time, firePos.z + max_height, clip, clipmask, ignore, targetEntity, drawtime ) ) { + aimDir = dir[i]; + return true; + } + } + + aimDir = dir[0]; + + // there is no collision free trajectory + return false; +} diff --git a/src/mpgame/ai/Monster_Berserker.cpp b/src/mpgame/ai/Monster_Berserker.cpp new file mode 100644 index 0000000..c99f9ef --- /dev/null +++ b/src/mpgame/ai/Monster_Berserker.cpp @@ -0,0 +1,488 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterBerserker : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBerserker ); + + rvMonsterBerserker ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + int FilterTactical ( int availableTactical ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + int standingMeleeNoAttackTime; + int painConsecutive; + + // Actions + rvAIAction actionPopupAttack; + rvAIAction actionChargeAttack; + + bool Berz_CanHitEnemyFromAnim ( int animNum ); + bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_ChargeAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_ChargeGroundImpact ( const stateParms_t& parms ); + stateResult_t Frame_DoBlastAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterBerserker ); +}; + +CLASS_DECLARATION( idAI, rvMonsterBerserker ) +END_CLASS + +/* +================ +rvMonsterBerserker::rvMonsterBerserker +================ +*/ +rvMonsterBerserker::rvMonsterBerserker ( ) { + painConsecutive = 0; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterBerserker::Spawn +================ +*/ +void rvMonsterBerserker::Spawn ( void ) { + actionPopupAttack.Init ( spawnArgs, "action_popupAttack", NULL, AIACTIONF_ATTACK ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", "Torso_ChargeAttack", AIACTIONF_ATTACK ); + PlayEffect( "fx_ambient_electricity", animator.GetJointHandle( "r_Lowerarm_Real" ), true ); + PlayEffect( "fx_ambient_electricity_mace", animator.GetJointHandle( "chain9" ), true ); +} + +/* +================ +rvMonsterBerserker::Save +================ +*/ +void rvMonsterBerserker::Save ( idSaveGame *savefile ) const { + actionPopupAttack.Save ( savefile ); + actionChargeAttack.Save ( savefile ); + savefile->WriteInt( painConsecutive ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::Restore +================ +*/ +void rvMonsterBerserker::Restore ( idRestoreGame *savefile ) { + actionPopupAttack.Restore ( savefile ); + actionChargeAttack.Restore ( savefile ); + savefile->ReadInt( painConsecutive ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterBerserker::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( GetEnemy() && GetEnemy()->GetPhysics()->GetOrigin().z > GetPhysics()->GetOrigin().z + 24.0f ) + {//this is a ground attack and enemy is above me, so don't even try it, stupid! + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +============ +rvMonsterBerserker::CanHitEnemyFromAnim +============ +*/ +bool rvMonsterBerserker::Berz_CanHitEnemyFromAnim( int animNum ) { + idVec3 dir; + idVec3 local_dir; + idVec3 fromPos; + idMat3 axis; + idVec3 start; + idEntity* enemyEnt; + + // Need an enemy. + if ( !enemy.ent ) { + return false; + } + + // Enemy actor pointer + enemyEnt = static_cast(enemy.ent.GetEntity()); + + // just do a ray test if close enough + if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { + return CanHitEnemy(); + } + + // calculate the world transform of the launch position + idVec3 org = physicsObj.GetOrigin(); + idVec3 from; + dir = enemy.lastVisibleChestPosition - org; + physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); + local_dir.z = 0.0f; + local_dir.ToVec2().Normalize(); + axis = local_dir.ToMat3(); + from = org + attackAnimInfo[ animNum ].attackOffset * axis; + + return CanSeeFrom ( from, enemy.lastVisibleEyePosition, true ); +} + +/* +================ +rvMonsterBerserker::CheckAction_RangedAttack +================ +*/ +bool rvMonsterBerserker::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( animNum != -1 && !Berz_CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterBerserker::CheckActions +================ +*/ +bool rvMonsterBerserker::CheckActions ( void ) { + // Pop-up attack is a forward moving melee attack that throws the enemy up in the air + if ( PerformAction ( &actionPopupAttack, (checkAction_t)&idAI::CheckAction_LeapAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + // Charge attack is where the berserker will charge up his spike and slam it in to the ground + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterBerserker::CheckAction_ChargeAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&rvMonsterBerserker::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} +/* +================ +rvMonsterBerserker::FilterTactical +================ +*/ +int rvMonsterBerserker::FilterTactical ( int availableTactical ) { + if ( move.moveCommand == MOVE_TO_ENEMY && move.moveStatus == MOVE_STATUS_DEST_UNREACHABLE ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else if ( combat.tacticalCurrent != AITACTICAL_RANGED + && combat.tacticalCurrent != AITACTICAL_MELEE + && (combat.tacticalMaskAvailable&AITACTICAL_RANGED_BIT) ) { + availableTactical |= AITACTICAL_RANGED_BIT; + } else { + availableTactical &= ~AITACTICAL_RANGED_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterBerserker::OnTacticalChange + +Enable/Disable the ranged attack based on whether the berzerker needs it +================ +*/ +void rvMonsterBerserker::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + //once you've gone into melee once, it's okay to try ranged attacks later + combat.tacticalMaskAvailable |= AITACTICAL_RANGED_BIT; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterBerserker::GetDebugInfo +===================== +*/ +void rvMonsterBerserker::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_popupAttack", aiActionStatusString[actionPopupAttack.status], userData ); + proc ( "idAI", "action_chargeAttack", aiActionStatusString[actionChargeAttack.status], userData ); +} + +/* +================ +rvMonsterBerserker::Pain +================ +*/ +bool rvMonsterBerserker::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterBerserker::CheckPainActions +================ +*/ +bool rvMonsterBerserker::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBerserker ) + STATE ( "State_Killed", rvMonsterBerserker::State_Killed ) + + STATE ( "Torso_ChargeAttack", rvMonsterBerserker::State_Torso_ChargeAttack ) + STATE ( "Torso_Pain", rvMonsterBerserker::State_Torso_Pain ) + + STATE ( "Frame_ChargeGroundImpact", rvMonsterBerserker::Frame_ChargeGroundImpact ) + STATE ( "Frame_DoBlastAttack", rvMonsterBerserker::Frame_DoBlastAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterBerserker::State_Torso_ChargeAttack +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_ChargeAttack ( const stateParms_t& parms ) { + enum { + TORSO_CHARGEATTACK_INIT, + TORSO_CHARGEATTACK_WAIT, + TORSO_CHARGEATTACK_RECOVER, + TORSO_CHARGEATTACK_RECOVERWAIT + }; + + switch ( parms.stage ) { + // Start the charge attack animation + case TORSO_CHARGEATTACK_INIT: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the ground strike + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_WAIT ); + + // Wait for charge attack animation to finish + case TORSO_CHARGEATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVER ); + } + return SRESULT_WAIT; + + // Play recover animation + case TORSO_CHARGEATTACK_RECOVER: + PlayAnim ( ANIMCHANNEL_TORSO, "ground_strike_recover", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_CHARGEATTACK_RECOVERWAIT ); + + // Wait for recover animation to finish + case TORSO_CHARGEATTACK_RECOVERWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBerserker::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterBerserker::State_Torso_Pain ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterBerserker::State_Killed +================ +*/ +stateResult_t rvMonsterBerserker::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_charge_up" ); + StopEffect ( "fx_ambient_electricity" ); + StopEffect ( "fx_ambient_electricity_mace" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterBerserker::Frame_ChargeGroundImpact +================ +*/ +stateResult_t rvMonsterBerserker::Frame_ChargeGroundImpact ( const stateParms_t& parms ) { + idVec3 start; + idVec3 end; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform ( animator.GetJointHandle ( "R_lowerArm_Real" ), gameLocal.time, start, axis ); + + end = start; + end.z -= 128; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, start, end, MASK_SHOT_BOUNDINGBOX, this ); +// RAVEN END + + gameLocal.PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_ground_impact" ), tr.endpos, idVec3(0,0,1).ToMat3() ); + + return SRESULT_OK; +} + +/* +================ +rvMonsterBerserker::Frame_DoBlastAttack +================ +*/ +stateResult_t rvMonsterBerserker::Frame_DoBlastAttack ( const stateParms_t& parms ) { + float i; + idVec3 start; + idMat3 axis; + idAngles angles ( 0.0f, move.current_yaw, 0.0f ); + const idDict* blastDict; + + blastDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_spike" ), false ); + if ( !blastDict ) { + gameLocal.Error ( "missing projectile on spike attack for AI entity '%s'", GetName ( ) ) ; + return SRESULT_OK; + } + + GetJointWorldTransform ( animator.GetJointHandle ( "end_spike" ), gameLocal.time, start, axis ); + + for( i = 0; i < 32; i++ ) { + angles.yaw += (360.0f / 32.0f); + AttackProjectile ( blastDict, start, angles ); + } + + return SRESULT_OK; +} diff --git a/src/mpgame/ai/Monster_BossBuddy.cpp b/src/mpgame/ai/Monster_BossBuddy.cpp new file mode 100644 index 0000000..90a4176 --- /dev/null +++ b/src/mpgame/ai/Monster_BossBuddy.cpp @@ -0,0 +1,685 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +//------------------------------------------------------------ +class rvMonsterBossBuddy : public idAI +//------------------------------------------------------------ +{ +public: + + CLASS_PROTOTYPE( rvMonsterBossBuddy ); + + rvMonsterBossBuddy( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + void Think ( void ); + bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + void OnWakeUp ( void ); + + // Add some dynamic externals for debugging + void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + bool CheckActions ( void ); +// void PerformAction ( const char* stateName, int blendFrames, bool noPain ); +// bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + void AdjustShieldState ( bool becomeShielded ); + void ReduceShields ( int amount ); + + int mShots; + int mShields; + int mMaxShields; + int mLastDamageTime; + int mShieldsLastFor; // read from def file, shouldn't need to be saved. + + bool mIsShielded; + bool mRequestedZoneMove; + bool mRequestedRecharge; + + //bool mCanIdle; + //bool mChaseMode; + +private: + + rvAIAction mActionRocketAttack; + rvAIAction mActionSlashMoveAttack; + rvAIAction mActionLightningAttack; + rvAIAction mActionDarkMatterAttack; + rvAIAction mActionMeleeMoveAttack; + rvAIAction mActionMeleeAttack; + + rvScriptFuncUtility mRequestRecharge; // script to run when a projectile is launched + rvScriptFuncUtility mRequestZoneMove; // script to run when he should move to the next zone + + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SlashAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + void Event_RechargeShields( float amount ); + + CLASS_STATES_PROTOTYPE( rvMonsterBossBuddy ); +}; + +#define BOSS_BUDDY_MAX_SHIELDS 8500 +//------------------------------------------------------------ +// rvMonsterBossBuddy::rvMonsterBossBuddy +//------------------------------------------------------------ +rvMonsterBossBuddy::rvMonsterBossBuddy( void ) +{ + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; + mShields = mMaxShields; + mLastDamageTime = 0; + mIsShielded = false; + mRequestedZoneMove = false; + mRequestedRecharge = false; +// mCanIdle = false; +// mChaseMode = true; +} + +void rvMonsterBossBuddy::InitSpawnArgsVariables ( void ) +{ + mShieldsLastFor = (int)(spawnArgs.GetFloat( "mShieldsLastFor", "6" ) * 1000.0f); + mMaxShields = BOSS_BUDDY_MAX_SHIELDS; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Spawn +//------------------------------------------------------------ +void rvMonsterBossBuddy::Spawn( void ) +{ + mActionRocketAttack.Init( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + mActionLightningAttack.Init( spawnArgs,"action_lightningAttack", NULL, AIACTIONF_ATTACK ); + mActionDarkMatterAttack.Init( spawnArgs,"action_dmgAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeMoveAttack.Init( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionSlashMoveAttack.Init( spawnArgs, "action_slashMoveAttack", NULL, AIACTIONF_ATTACK ); + mActionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + mShields = mMaxShields; + + const char *func; + if ( spawnArgs.GetString( "requestRecharge", "", &func ) ) + { + mRequestRecharge.Init( func ); + } + if ( spawnArgs.GetString( "requestZoneMove", "", &func ) ) + { + mRequestZoneMove.Init( func ); + } + + HideSurface( "models/monsters/bossbuddy/forcefield" ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Save +//------------------------------------------------------------ +void rvMonsterBossBuddy::Save( idSaveGame *savefile ) const +{ + savefile->WriteInt( mShots ); + savefile->WriteInt( mShields ); + savefile->WriteInt( mMaxShields ); + savefile->WriteInt( mLastDamageTime ); + savefile->WriteBool( mIsShielded ); + savefile->WriteBool( mRequestedZoneMove ); + savefile->WriteBool( mRequestedRecharge ); + + mActionRocketAttack.Save( savefile ); + mActionLightningAttack.Save( savefile ); + mActionDarkMatterAttack.Save( savefile ); + mActionMeleeMoveAttack.Save( savefile ); + mActionMeleeAttack.Save( savefile ); + mActionSlashMoveAttack.Save( savefile ); + + mRequestRecharge.Save( savefile ); + mRequestZoneMove.Save( savefile ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Restore +//------------------------------------------------------------ +void rvMonsterBossBuddy::Restore( idRestoreGame *savefile ) +{ + savefile->ReadInt( mShots ); + savefile->ReadInt( mShields ); + savefile->ReadInt( mMaxShields ); + savefile->ReadInt( mLastDamageTime ); + savefile->ReadBool( mIsShielded ); + savefile->ReadBool( mRequestedZoneMove ); + savefile->ReadBool( mRequestedRecharge ); + + mActionRocketAttack.Restore( savefile ); + mActionLightningAttack.Restore( savefile ); + mActionDarkMatterAttack.Restore( savefile ); + mActionMeleeMoveAttack.Restore( savefile ); + mActionMeleeAttack.Restore( savefile ); + mActionSlashMoveAttack.Restore( savefile ); + + mRequestRecharge.Restore( savefile ); + mRequestZoneMove.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +//------------------------------------------------------------ +// rvMonsterBerserker::GetDebugInfo +//------------------------------------------------------------ +void rvMonsterBossBuddy::GetDebugInfo( debugInfoProc_t proc, void* userData ) +{ + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_darkMatterAttack", aiActionStatusString[mActionDarkMatterAttack.status], userData ); + proc ( "idAI", "action_rocketAttack", aiActionStatusString[mActionRocketAttack.status], userData ); + proc ( "idAI", "action_meleeMoveAttack", aiActionStatusString[mActionMeleeMoveAttack.status], userData ); + proc ( "idAI", "action_lightningAttack", aiActionStatusString[mActionLightningAttack.status], userData ); +} + +//-------------------------------------------------------------- +// Custom Script Events +//-------------------------------------------------------------- +const idEventDef EV_RechargeShields( "rechargeShields", "f", 'f' ); + +CLASS_DECLARATION( idAI, rvMonsterBossBuddy ) + EVENT( EV_RechargeShields, rvMonsterBossBuddy::Event_RechargeShields ) +END_CLASS + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Event_RechargeShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::Event_RechargeShields( float amount ) +{ + mShields += (int)amount; + + if ( mShields >= mMaxShields ) + { + // charge is done + mShields = mMaxShields; + idThread::ReturnInt(0); + + // reset request states + mRequestedRecharge = false; + mRequestedZoneMove = false; + + // shield warning no longer neede for now + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("hideBossShieldWarn"); + } + else + { + // still charging + idThread::ReturnInt(1); + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::ReduceShields +//------------------------------------------------------------ +void rvMonsterBossBuddy::ReduceShields( int amount ) +{ + mShields -= amount; + + // if no mShields left... or the last time we took damage was more than 8 seconds ago + if ( mShields <= 0 || (mLastDamageTime + 8000) < gameLocal.time ) + { + //....remove the shielding + AdjustShieldState( false ); + } + + if (mShields < 1000) + { + if (!mRequestedRecharge ) + { + // entering a dangerous state! Get to the recharge station, fast! + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent("showBossShieldWarn"); + ExecScriptFunction( mRequestRecharge ); + mRequestedRecharge = true; + } + } + else if (mShields < 4000) + { + if ( !mRequestedZoneMove ) + { + // Getting low, so move him close to the next zone so he can be ready to recharge + ExecScriptFunction( mRequestZoneMove ); + mRequestedZoneMove = true; + } + } +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::AdjustShieldState +//------------------------------------------------------------ +void rvMonsterBossBuddy::AdjustShieldState( bool becomeShielded ) +{ + // only do the work for adjusting the state when it doesn't match our current state + if ( !mIsShielded && becomeShielded ) + { + // Activate Shields! + ShowSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound( "snd_enable_shields", SND_CHANNEL_ANY, 0, false, NULL ); + gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "showBossShieldBar" ); + } + else if ( mIsShielded && !becomeShielded ) + { + // Deactivate Shields! + HideSurface( "models/monsters/bossbuddy/forcefield" ); + StartSound ( "snd_disable_shields", SND_CHANNEL_ANY, 0, false, NULL ); +// gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( "hideBossShieldBar" ); + } + mIsShielded = becomeShielded; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Think() +{ + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) + { + // run simple shielding logic when we have them active + if ( mIsShielded ) + { + ReduceShields( 1 ); + + // if they are on but we haven't taken damage in x seconds, turn them off to conserve on shields + if ( (mLastDamageTime + mShieldsLastFor) < gameLocal.time ) + { + AdjustShieldState( false ); + } + } + + // update shield bar + idUserInterface *hud = gameLocal.GetLocalPlayer()->hud; + if ( hud ) + { + float percent = ((float)mShields/mMaxShields); + + hud->SetStateFloat( "boss_shield_percent", percent ); + hud->HandleNamedEvent( "updateBossShield" ); + } + } + + if ( move.obstacle.GetEntity() ) + { + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack ); + } + + idAI::Think(); +} +/* +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +void rvMonsterBossBuddy::PerformAction( const char* stateName, int blendFrames, bool noPain ) +{ + // Allow movement in actions + move.fl.allowAnimMove = true; + + if ( mChaseMode ) + { + return; + } + + // Start the action + SetAnimState( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( mCanIdle ) + { + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState( "Wait_ActionNoPain" ); + OnStartAction( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::PerformAction +//------------------------------------------------------------ +bool rvMonsterBossBuddy::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) +{ + if ( mChaseMode ) + { + return false; + } + + return idAI::PerformAction( action, condition ,timer ); +} +*/ +//------------------------------------------------------------ +// rvMonsterBossBuddy::Damage +//------------------------------------------------------------ +void rvMonsterBossBuddy::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + // get damage amount so we can decay the shields and check for ignoreShields + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) + { + gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName ); + } + + // NOTE: there is a damage def for the electrocution that is marked 'ignoreShields'.. when present on the damage def, + // we don't run shielding logic + bool directDamage = damageDef->GetBool( "ignoreShields" ); + + int loc = location; + if ( directDamage ) + { + // Lame, I know, but hack the location + loc = INVALID_JOINT; + } + else if ( attacker == this ) + { + // can't damage self + return; + } + + float scale = 1; + + // Shields will activate for a set amount of time when damage is being taken + mLastDamageTime = gameLocal.time; + + // if shields are active, we should try to 'eat' them before directing damage to the BB + if ( mIsShielded && !directDamage ) + { + // BB is resistant to any kind of splash damage when the shields are up + if ( loc <= INVALID_JOINT ) + { + // damage must have been done by splash damage + return; + } + + int damage = damageDef->GetInt( "damage" ); + ReduceShields( damage * 8 ); + + // Shielding dramatically reduces actual damage done to BB + scale = 0.1f; + } + else if ( mShields > 0 ) // not currently shielded...does he have shields to use? + { + // Yep, so turn them on + AdjustShieldState( true ); + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale * scale, loc ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::Pain +//------------------------------------------------------------ +bool rvMonsterBossBuddy::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) +{ + // immune to small damage. Is this safe to do? + if ( damage > 5 ) + { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( spawnArgs, "fx_shieldcrawl" ), this, SEC2MS(spawnArgs.GetFloat ( "shieldCrawlTime", ".2" )) ); + effect->Play( gameLocal.time, false ); + + return idAI::Pain( inflictor, attacker, damage, dir, location ); + } + return false; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CheckActions +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CheckActions( void ) +{ + // If not moving, try turning in place +/* if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180( move.ideal_yaw - move.current_yaw ); + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction( "Torso_TurnLeft90", 4, true ); + return true; + } + } +*/ + if ( PerformAction( &mActionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction( &mActionSlashMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, &actionTimerSpecialAttack )) + { + return true; + } + + if ( PerformAction( &mActionDarkMatterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction( &mActionLightningAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack )) + { + return true; + } + + return idAI::CheckActions( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::CanTurn +//------------------------------------------------------------ +bool rvMonsterBossBuddy::CanTurn( void ) const +{ +/* if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +*/ + return idAI::CanTurn ( ); +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::OnWakeUp +//------------------------------------------------------------ +void rvMonsterBossBuddy::OnWakeUp( void ) +{ + mActionDarkMatterAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + mActionRocketAttack.timer.Reset( actionTime, mActionDarkMatterAttack.diversity ); + idAI::OnWakeUp( ); +} + +//------------------------------------------------------------ +// States +//------------------------------------------------------------ + +CLASS_STATES_DECLARATION( rvMonsterBossBuddy ) + STATE( "Torso_RocketAttack", rvMonsterBossBuddy::State_Torso_RocketAttack ) + STATE( "Torso_SlashAttack", rvMonsterBossBuddy::State_Torso_SlashAttack ) + STATE( "Torso_TurnRight90", rvMonsterBossBuddy::State_Torso_TurnRight90 ) + STATE( "Torso_TurnLeft90", rvMonsterBossBuddy::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_RocketAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_RocketAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2start", parms.blendFrames ); + mShots = (gameLocal.random.RandomInt( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2loop2", 0 ); + return SRESULT_STAGE( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + if ( --mShots <= 0 || // exhausted mShots? .. or + (!IsEnemyVisible() && rvRandom::irand(0,10)>=8 ) || // ... player is no longer visible .. or + ( enemy.ent && DistanceTo(enemy.ent)<256 ) ) // ... player is so close, we prolly want to do a melee attack + { + PlayAnim( ANIMCHANNEL_TORSO, "attack_rocket2end", 0 ); + return SRESULT_STAGE( STAGE_WAITEND ); + } + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone( ANIMCHANNEL_TORSO, 4 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_SlashAttack +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_SlashAttack( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT_FIRST_SWIPE, + STAGE_WAIT_FINISH + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FIRST_SWIPE ); + + case STAGE_WAIT_FIRST_SWIPE: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + PlayAnim( ANIMCHANNEL_TORSO, "melee_move_attack", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_WAIT_FINISH: + if ( AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnRight90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnRight90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//------------------------------------------------------------ +// rvMonsterBossBuddy::State_Torso_TurnLeft90 +//------------------------------------------------------------ +stateResult_t rvMonsterBossBuddy::State_Torso_TurnLeft90( const stateParms_t& parms ) +{ + enum + { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) + { + case STAGE_INIT: + DisableAnimState( ANIMCHANNEL_LEGS ); + PlayAnim( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn( 90.0f, true ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone( ANIMCHANNEL_TORSO, 0 )) + { + AnimTurn( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_BossMakron.cpp b/src/mpgame/ai/Monster_BossMakron.cpp new file mode 100644 index 0000000..9b6c25d --- /dev/null +++ b/src/mpgame/ai/Monster_BossMakron.cpp @@ -0,0 +1,2371 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterBossMakron : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterBossMakron ); + + rvMonsterBossMakron ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + + bool CanTurn ( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void BuildActionArray ( void ); + + //void ScriptedFace ( idEntity* faceEnt, bool endWithIdle ); + +protected: + + bool CheckActions ( void ); + bool CheckTurnActions ( void ); + + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + + //The Makron behaves differently, and we don't always need him sinking back into an idle after every attack + //This version is actually overridden + void PerformAction ( const char* stateName, int blendFrames, bool noPain ); + + //This just calls the parent version + bool PerformAction ( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ); + + //This performs a patterned action + void PerformPatternedAction ( ); + + //For debug, may not need this. + void OnStopAction ( void ); + + //stops all effects + void StopAllEffects ( void ); + + //for the cannon fire + int shots; + + //determines if the Makron will enter idles between attacks + bool noIdle; + + //changes Makron from patterned, script controlled to normal AI + bool patternedMode; + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + float facingIdealYaw; + float facingTime; + float turnRate; + + //flag to indicate whether or not the Makron is in the corner of the map when he takes an action. + bool flagCornerState; + + //tallies up the number of consecutive actions the Makron has taken in the corner of the map. + //If the tally gets too high, teleport him to the map center. + int cornerCount; + + //in the state of teleporting + bool flagTeleporting; + + //for flying mode ---------------------------------------------------------------------------------------- + void BeginSeparation ( void ); + void CompleteSeparation ( void ); + + bool flagFlyingMode; + bool flagFakeDeath; + int flagUndying; + + rvClientEffectPtr effectHover; + jointHandle_t jointHoverEffect; + + //for the stomp attack ---------------------------------------------------------------------------------------- + float stompMaxRadius; + float stompWidth; + float stompSpeed; + float stompRadius; + + //for the lightning bolt sweep attacks ------------------------------------------------------------------- + void MaintainBoltSweep ( void ); + void InitBoltSweep ( idVec3 idealTarget ); + void LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ); + void StopAllBoltEffects ( void ); + + rvClientEffectPtr leftBoltEffect; + rvClientEffectPtr rightBoltEffect; + rvClientEffectPtr leftBoltImpact; + rvClientEffectPtr rightBoltImpact; + rvClientEffectPtr boltMuzzleFlash; + + idStr boltEffectName; + + idVec3 leftBoltVector; + idVec3 rightBoltVector; + idVec3 boltVectorMin; + idVec3 boltVectorMax; + idMat3 boltAimMatrix; + + bool flagSweepDone; + + //these are grabbed from the def file + float boltSweepTime; + float boltWaitTime; + + //used internally + float boltSweepStartTime; + float boltTime; + float boltNextStateTime; + + int stateBoltSweep; + + jointHandle_t jointLightningBolt; + + //end lightning bolt sweep attacks ------------------------------------------------------------------- + //changed by script event, allows for grenades to spawn baddies. + bool flagAllowSpawns; + + enum { MAKRON_ACTION_DMG, + MAKRON_ACTION_MELEE, + MAKRON_ACTION_CANNON, + MAKRON_ACTION_CANNON_SWEEP, + MAKRON_ACTION_GRENADE, + MAKRON_ACTION_LIGHTNING_1, + MAKRON_ACTION_LIGHTNING_2, + MAKRON_ACTION_STOMP, + MAKRON_ACTION_HEAL, + MAKRON_ACTION_CHARGE, + MAKRON_ACTION_KILLPLAYER, + MAKRON_ACTION_COUNT, }; + + rvAIAction actionDMGAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionCannonAttack; + rvAIAction actionCannonSweepAttack; + rvAIAction actionGrenadeAttack; + rvAIAction actionLightningPattern1Attack; + rvAIAction actionLightningPattern2Attack; + rvAIAction actionStompAttack; + rvAIAction actionHeal; + rvAIAction actionCharge; + rvAIAction actionKillPlayer; + + rvAIAction * actionArray[ MAKRON_ACTION_COUNT ]; + + int actionPatterned; + + //when the Makron is low on health, he'll use this. + rvScriptFuncUtility scriptRecharge; + rvScriptFuncUtility scriptTeleport; + + stateResult_t State_Torso_DMGAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_GrenadeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MeleeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_CannonSweepAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning1Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_Lightning2Attack ( const stateParms_t& parms ); + stateResult_t State_Torso_StompAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Recharge ( const stateParms_t& parms ); + stateResult_t State_Torso_Charge ( const stateParms_t& parms ); + stateResult_t State_Torso_KillPlayer ( const stateParms_t& parms ); + stateResult_t State_Torso_Teleport ( const stateParms_t& parms ); + + //used when killed + stateResult_t State_Killed ( const stateParms_t& parms ); + + //walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + //flying turn anims + stateResult_t State_Torso_RotateToAngle ( const stateParms_t& parms ); + + stateResult_t State_ScriptedFace ( const stateParms_t& parms ); + + //like jesus, except with robot parts + stateResult_t State_Torso_FirstDeath ( const stateParms_t& parms ); + stateResult_t State_Torso_Resurrection ( const stateParms_t& parms ); + + //These two frames activate and deactivate the second lightning sweep + stateResult_t Frame_BeginLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_EndLightningSweep2 ( const stateParms_t& parms ); + stateResult_t Frame_StompAttack ( const stateParms_t& parms ); + stateResult_t Frame_Teleport ( const stateParms_t& parms ); + + + void Event_AllowMoreSpawns ( void ); + void Event_SetNextAction ( const char* actionString ); + void Event_EnablePatternMode ( void ); + void Event_DisablePatternMode ( void ); + void Event_StompAttack ( idVec3 &origin ); + void Event_Separate ( void ); + void Event_FlyingRotate ( idVec3 &vecOrg ); + void Event_ToggleCornerState ( float f ); + + + CLASS_STATES_PROTOTYPE ( rvMonsterBossMakron ); +}; + +const idEventDef EV_AllowMoreSpawns( "allowMoreSpawns" ); +const idEventDef EV_SetNextAction( "setNextAction", "s", 'f' ); +const idEventDef EV_EnablePatternMode( "enablePatternMode" ); +const idEventDef EV_DisablePatternMode( "disablePatternMode" ); +const idEventDef EV_StompAttack( "stompAttack", "v" ); +const idEventDef EV_Separate( "separate" ); +const idEventDef EV_FlyingRotate( "flyingRotate", "v"); +const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" ); +const idEventDef EV_ToggleCornerState( "toggleCornerState", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterBossMakron ) + EVENT( EV_AllowMoreSpawns, rvMonsterBossMakron::Event_AllowMoreSpawns ) + EVENT( EV_SetNextAction, rvMonsterBossMakron::Event_SetNextAction ) + EVENT( EV_EnablePatternMode, rvMonsterBossMakron::Event_EnablePatternMode ) + EVENT( EV_DisablePatternMode, rvMonsterBossMakron::Event_DisablePatternMode ) + EVENT( EV_StompAttack, rvMonsterBossMakron::Event_StompAttack ) + EVENT( EV_Separate, rvMonsterBossMakron::Event_Separate ) + EVENT( EV_FlyingRotate, rvMonsterBossMakron::Event_FlyingRotate ) + EVENT( AI_ScriptedFace, rvMonsterBossMakron::ScriptedFace ) + EVENT( EV_ToggleCornerState, rvMonsterBossMakron::Event_ToggleCornerState ) +END_CLASS + +/* +================ +rvMonsterBossMakron::rvMonsterBossMakron +================ +*/ +rvMonsterBossMakron::rvMonsterBossMakron ( void ) { + + //set up this action array + + + +} + +/* +================ +rvMonsterBossMakron::BuildActionArray ( void ) +================ +*/ +void rvMonsterBossMakron::BuildActionArray ( void ) { + + actionArray[ MAKRON_ACTION_DMG ] = &actionDMGAttack; + actionArray[ MAKRON_ACTION_MELEE ] = &actionMeleeAttack; + actionArray[ MAKRON_ACTION_CANNON ] = &actionCannonAttack; + actionArray[ MAKRON_ACTION_CANNON_SWEEP ] = &actionCannonSweepAttack; + actionArray[ MAKRON_ACTION_GRENADE ] = &actionGrenadeAttack; + actionArray[ MAKRON_ACTION_LIGHTNING_1 ] = &actionLightningPattern1Attack; + actionArray[ MAKRON_ACTION_LIGHTNING_2 ] = &actionLightningPattern2Attack; + actionArray[ MAKRON_ACTION_STOMP ] = &actionStompAttack; + actionArray[ MAKRON_ACTION_HEAL ] = &actionHeal; + actionArray[ MAKRON_ACTION_CHARGE ] = &actionCharge; + actionArray[ MAKRON_ACTION_KILLPLAYER ] = &actionKillPlayer; + +} +/* +================ +rvMonsterBossMakron::Save +================ +*/ +void rvMonsterBossMakron::Save ( idSaveGame *savefile ) const { + + savefile->WriteInt( shots); + + savefile->WriteFloat( facingIdealYaw); + savefile->WriteFloat( facingTime); + + savefile->WriteBool( noIdle); + + savefile->WriteBool( patternedMode); + + savefile->WriteBool( flagFlyingMode); + savefile->WriteBool( flagFakeDeath); + savefile->WriteInt( flagUndying ); + + effectHover.Save( savefile ); + savefile->WriteJoint( jointHoverEffect ); // cnicholson: added unsaved var + + savefile->WriteFloat( stompRadius); + + leftBoltEffect.Save( savefile ); + rightBoltEffect.Save( savefile ); + leftBoltImpact.Save( savefile ); + rightBoltImpact.Save( savefile ); + boltMuzzleFlash.Save( savefile ); + + savefile->WriteString( boltEffectName); + + savefile->WriteVec3( leftBoltVector); + savefile->WriteVec3( rightBoltVector); + savefile->WriteVec3( boltVectorMin); + savefile->WriteVec3( boltVectorMax); + savefile->WriteMat3( boltAimMatrix); + + savefile->WriteBool( flagSweepDone); + + savefile->WriteFloat( boltSweepTime); + + savefile->WriteFloat( boltSweepStartTime); + savefile->WriteFloat( boltTime); + savefile->WriteFloat( boltNextStateTime); + + savefile->WriteInt( stateBoltSweep); + + savefile->WriteBool( flagAllowSpawns); + + savefile->WriteBool( flagCornerState ); + savefile->WriteInt( cornerCount ); + savefile->WriteBool( flagTeleporting ); + + + actionDMGAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionCannonAttack.Save( savefile ); + actionCannonSweepAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + actionLightningPattern1Attack.Save( savefile ); + actionLightningPattern2Attack.Save( savefile ); + actionStompAttack.Save( savefile ); + actionHeal.Save( savefile ); + actionCharge.Save( savefile ); + actionKillPlayer.Save( savefile ); + + savefile->WriteInt( actionPatterned); + + scriptRecharge.Save( savefile ); + scriptTeleport.Save( savefile ); + // cnicholson: No need to save actionArry, its rebuilt during restore +} + +/* +================ +rvMonsterBossMakron::Restore +================ +*/ +void rvMonsterBossMakron::Restore ( idRestoreGame *savefile ) { + + savefile->ReadInt( shots); + + savefile->ReadFloat( facingIdealYaw); + savefile->ReadFloat( facingTime); + + savefile->ReadBool( noIdle); + + savefile->ReadBool( patternedMode); + + savefile->ReadBool( flagFlyingMode); + savefile->ReadBool( flagFakeDeath); + savefile->ReadInt( flagUndying ); + + effectHover.Restore( savefile ); + savefile->ReadJoint( jointHoverEffect ); // cnicholson: added unrestoed var + + savefile->ReadFloat( stompRadius); + + leftBoltEffect.Restore( savefile ); + rightBoltEffect.Restore( savefile ); + leftBoltImpact.Restore( savefile ); + rightBoltImpact.Restore( savefile ); + boltMuzzleFlash.Restore( savefile ); + + savefile->ReadString( boltEffectName); + + savefile->ReadVec3( leftBoltVector); + savefile->ReadVec3( rightBoltVector); + savefile->ReadVec3( boltVectorMin); + savefile->ReadVec3( boltVectorMax); + savefile->ReadMat3( boltAimMatrix); + + savefile->ReadBool( flagSweepDone); + + savefile->ReadFloat( boltSweepTime); + + savefile->ReadFloat( boltSweepStartTime); + savefile->ReadFloat( boltTime); + savefile->ReadFloat( boltNextStateTime); + + savefile->ReadInt( stateBoltSweep); + + savefile->ReadBool( flagAllowSpawns); + + savefile->ReadBool( flagCornerState ); + savefile->ReadInt( cornerCount ); + savefile->ReadBool( flagTeleporting ); + + + actionDMGAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionCannonAttack.Restore( savefile ); + actionCannonSweepAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + actionLightningPattern1Attack.Restore( savefile ); + actionLightningPattern2Attack.Restore( savefile ); + actionStompAttack.Restore( savefile ); + actionHeal.Restore( savefile ); + actionCharge.Restore( savefile ); + actionKillPlayer.Restore( savefile ); + + + savefile->ReadInt( actionPatterned); + + scriptRecharge.Restore( savefile ); + scriptTeleport.Restore( savefile ); + //reload the action array, this is done in the init section but if we don't do it here our array is bunk. + BuildActionArray(); + InitSpawnArgsVariables(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + + +/* +================ +rvMonsterBossMakron::StopAllEffects +================ +*/ +void rvMonsterBossMakron::StopAllEffects ( void ) { + + StopAllBoltEffects(); + + //stop the over effect + if( effectHover ) { + effectHover.GetEntity()->Stop(); + effectHover = 0; + } + +} + +/* +================ +rvMonsterBossMakron::StopAllBoltEffects +================ +*/ +void rvMonsterBossMakron::StopAllBoltEffects ( void ) { + + if( leftBoltEffect ) { + leftBoltEffect.GetEntity()->Stop(); + leftBoltEffect = 0; + } + + if( rightBoltEffect ) { + rightBoltEffect.GetEntity()->Stop(); + rightBoltEffect = 0; + } + + if( leftBoltImpact ) { + leftBoltImpact.GetEntity()->Stop(); + leftBoltImpact = 0; + } + + if( rightBoltImpact ) { + rightBoltImpact.GetEntity()->Stop(); + rightBoltImpact = 0; + } + + if( boltMuzzleFlash ) { + boltMuzzleFlash .GetEntity()->Stop(); + boltMuzzleFlash = 0; + } + + +} + +void rvMonsterBossMakron::InitSpawnArgsVariables ( void ) { + //slick! + jointLightningBolt = animator.GetJointHandle ( spawnArgs.GetString ( "joint_lightningBolt", "claw_muzzle" ) ); + + stompMaxRadius = spawnArgs.GetFloat( "stomp_max_range", "1600"); + stompWidth = spawnArgs.GetFloat( "stomp_width", "32"); + stompSpeed = spawnArgs.GetFloat( "stomp_speed", "32"); + + boltWaitTime = spawnArgs.GetFloat( "lightingsweep_wait_time", "1"); + + turnRate = spawnArgs.GetFloat( "fly_turnRate", "180"); +} + +/* +================ +rvMonsterBossMakron::Spawn +================ +*/ +void rvMonsterBossMakron::Spawn ( void ) { + + if( spawnArgs.GetBool("passive")) { + flagFakeDeath = true; + } else { + flagFakeDeath = false; + } + + if( spawnArgs.GetBool("furniture") ) { + fl.takedamage = false; + } + + if( spawnArgs.GetBool("junior") ) { + flagUndying = 1; + } else { + flagUndying = 0; + } + + flagAllowSpawns = false; + flagFlyingMode = false; + patternedMode = false; + noIdle = false; + + flagCornerState = false; + cornerCount = 0; + flagTeleporting = false; + + //start clean + actionPatterned = -1; + + boltTime = 0; + boltSweepTime = spawnArgs.GetFloat( "lightningsweep_sweep_time", "3"); + + InitSpawnArgsVariables(); + + actionDMGAttack.Init ( spawnArgs, "action_DMGAttack", "Torso_DMGAttack", AIACTIONF_ATTACK ); + actionCannonAttack.Init ( spawnArgs, "action_cannonAttack", "Torso_CannonAttack", AIACTIONF_ATTACK ); + actionCannonSweepAttack.Init ( spawnArgs, "action_cannonsweepAttack", "Torso_CannonSweepAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", "Torso_GrenadeAttack", AIACTIONF_ATTACK ); + actionLightningPattern1Attack.Init( spawnArgs, "action_lightningPattern1Attack", "Torso_Lightning1Attack", AIACTIONF_ATTACK ); + actionLightningPattern2Attack.Init( spawnArgs, "action_lightningPattern2Attack", "Torso_Lightning2Attack", AIACTIONF_ATTACK ); + actionStompAttack.Init ( spawnArgs, "action_stompAttack", "Torso_StompAttack", AIACTIONF_ATTACK ); + actionHeal.Init( spawnArgs, "action_heal", "Torso_Recharge", AIACTIONF_ATTACK ); + actionCharge.Init( spawnArgs, "action_charge", "Torso_Charge", AIACTIONF_ATTACK ); + actionKillPlayer.Init( spawnArgs, "action_lightningPattern3Attack", "Torso_KillPlayer", AIACTIONF_ATTACK ); + + actionMeleeAttack.Init ( spawnArgs, "action_meleeAttack", "Torso_MeleeAttack", AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_recharge", "", &func ) ) + { + scriptRecharge.Init( func ); + } + if ( spawnArgs.GetString( "script_teleport", "", &func ) ) + { + scriptTeleport.Init( func ); + } + + //build the action array + BuildActionArray(); + + // pre-cache decls + gameLocal.FindEntityDefDict ( "monster_makron_legs" ); +} + +/* +================ +rvMonsterBossMakron::CheckTurnActions +================ +*/ +bool rvMonsterBossMakron::CheckTurnActions ( void ) { + + if ( !flagFakeDeath && !move.fl.moving && !move.anim_turn_angles ) { //&& gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + ////gameLocal.Printf(" turn yaw == %f \n", turnYaw); + } + return false; +} +/* +================ +rvMonsterBossMakron::CheckActions +================ +*/ +bool rvMonsterBossMakron::CheckActions ( void ) { + + if( spawnArgs.GetFloat("furniture", "0")) { + return true; + } + + //if in the middle of teleporting, do nothing + if( flagTeleporting ) { + return true; + } + + //gameLocal.Printf("Begin CheckActions \n"); + + if( CheckTurnActions() ) { + //gameLocal.Printf("---CheckActions: TurnActions was true \n"); + return true; + } + + + //If we need more baddies, do it right now regardless of range or player FOV + if ( flagAllowSpawns ) { + PerformAction ( actionGrenadeAttack.state,actionGrenadeAttack.blendFrames, actionGrenadeAttack.fl.noPain ); + //gameLocal.Printf("---CheckActions: Spawned in more fellas \n"); + return true; + } + + //melee attacks + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) || + PerformAction ( &actionStompAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + //gameLocal.Printf("---CheckActions: Melee attacks \n"); + return true; + } + + //cannon sweep is good to check if we're up close. + //if ( PerformAction ( &actionCannonSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + // return true; + //} + + //If we're in total patterned mode, then don't relay on timers or ranges to perform actions. + if ( patternedMode && !flagFakeDeath ) { + //gameLocal.Printf("**CheckActions: PerformPatternedAction called \n"); + PerformPatternedAction ( ); + return true; + } + //gameLocal.Printf("---CheckActions: FlagFakeDeath: %d \n", flagFakeDeath ); + //gameLocal.Printf("---CheckActions: PatternedMode: %d \n", patternedMode ); + + //ranged attacks + if ( PerformAction ( &actionDMGAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningPattern1Attack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionCannonAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + //gameLocal.Printf("---CheckActions: Ranged attack called \n"); + return true; + } + //gameLocal.Printf("---CheckActions: Defaulting to AI CheckActions \n"); + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterMakron::PerformPatternedAction +================ +*/ +void rvMonsterBossMakron::PerformPatternedAction( ) { + + //gameLocal.Printf("PerformPatternedAction::Begin--\n"); + + //if we don't have an action to perform, don't. + if( actionPatterned == -1) { + //gameLocal.Printf("actionPatterned = -1\n"); + //gameLocal.Printf("PerformPatternedAction::End--\n"); + return; + } + + //if we're in a corner, right now, then add to the corner tally. + if( flagCornerState ) { + cornerCount++; + } else { + cornerCount = 0; + } + + //if this is our third consecutive action in the corner, maybe we should teleport? + if( cornerCount == 3 ) { + gameLocal.Warning("Makron is stuck in a corner!"); + cornerCount = 0; + SetState( "Torso_Teleport" ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + return; + + } + + rvAIAction* action; + rvAIActionTimer* timer; + + action = actionArray[ actionPatterned]; + timer = &actionTimerRangedAttack; + + //if the attack is variable length, then do all this jive... + float scale; + scale = 1.0f; + + //gameLocal.Printf("performing the action\n"); + + // Perform the raw action + PerformAction ( action->state, action->blendFrames, action->fl.noPain ); + + //Clear this out so the next attack can be queued up. + actionPatterned = -1; + + // Restart the action timer using the length of the animation being played + // Even though we don't use the action timer for this instance, we need to keep it updated and fresh. + action->timer.Reset ( actionTime, action->diversity, scale ); + + // Restart the global action timer using the length of the animation being played + if ( timer ) { + timer->Reset ( actionTime, action->diversity, scale ); + } + +} + + +/* +================ +rvMonsterMakron::CanTurn +================ +*/ +bool rvMonsterBossMakron::CanTurn ( void ) const { + if( !flagFlyingMode ) { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; + } else { + return idAI::CanTurn ( ); + } +} + + +/* +================ +rvMonsterMakron::InitBoltSweep +================ +*/ +void rvMonsterBossMakron::InitBoltSweep ( idVec3 idealTarget ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + idVec3 fwd; + idVec3 right; + idVec3 worldup(0,0,1); + + idVec3 targetPoint; + + //init all the lightning bolt stuff + boltTime = 0; + flagSweepDone = false; + stateBoltSweep = 0; + + //get the vector from makron's gun to the target point. + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + targetPoint = idealTarget; //enemy.lastVisibleChestPosition; + + fwd = targetPoint - origin; + + fwd.Normalize(); + right = fwd.Cross( worldup); + + boltAimMatrix[0] = fwd; + boltAimMatrix[1] = right; + boltAimMatrix[2] = worldup; + + targetPoint = origin + (fwd * 2400); + + //left + targetPoint = targetPoint - (right * 1800); + boltVectorMin = targetPoint - origin; + boltVectorMin.Normalize(); + leftBoltVector = boltVectorMin; + + //right + targetPoint = targetPoint + (right * 1800 * 2); + boltVectorMax = targetPoint - origin; + boltVectorMax.Normalize(); + rightBoltVector = boltVectorMax; + +} + +/* +================ +rvMonsterMakron::MaintainBoltSweep +================ +*/ +void rvMonsterBossMakron::MaintainBoltSweep ( void ) { + enum { + STATE_START, + STATE_WAIT1, + STATE_SWEEP1, + STATE_WAIT2, + STATE_SWEEP2, + STATE_END, + }; + + //advance the state when time is up. + + switch( stateBoltSweep ) { + + //init vars + case STATE_START: + flagSweepDone = false; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + StopAllBoltEffects(); + stateBoltSweep = STATE_WAIT1; + return; + + //blast at the waiting points, vectors do not move + case STATE_WAIT1: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + + case STATE_SWEEP1: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltWaitTime); + } + + return; + case STATE_WAIT2: + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = gameLocal.time + SEC2MS( boltSweepTime); + boltTime = 0; + boltSweepStartTime = gameLocal.time; + } + return; + case STATE_SWEEP2: + //lerp the lightning bolt vectors + boltTime = gameLocal.time - boltSweepStartTime; + //min and max are reversed here. + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + rightBoltVector.Lerp( boltVectorMin, boltVectorMax, boltTime / SEC2MS(boltSweepTime)); + + //sweep + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact); + LightningSweep( rightBoltVector, rightBoltEffect, rightBoltImpact ); + + //check for state advance + if( gameLocal.time > boltNextStateTime) { + stateBoltSweep++; + boltNextStateTime = 0; + StopAllBoltEffects(); + } + return; + case STATE_END: + flagSweepDone = 1; + return; + } + +} +/* +================ +rvMonsterBossMakron::LightningSweep +================ +*/ +void rvMonsterBossMakron::LightningSweep ( idVec3 attackVector, rvClientEffectPtr& boltEffect, rvClientEffectPtr& impactEffect ) { + + idVec3 origin; + idMat3 axis; + trace_t tr; + + GetJointWorldTransform( jointLightningBolt, gameLocal.time, origin, axis); + + //trace out from origin along attackVector + attackVector.Normalize(); + gameLocal.TracePoint( this, tr, origin, origin + (attackVector * 25600), MASK_SHOT_RENDERMODEL, this); + //gameRenderWorld->DebugLine( colorRed, origin, tr.c.point, 100, true); + + if ( !boltEffect ) { + boltEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_fly" ), origin, attackVector.ToMat3(), true, tr.c.point); + } else { + boltEffect->SetOrigin ( origin ); + boltEffect->SetAxis ( attackVector.ToMat3() ); + boltEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_impact" ), tr.c.point, tr.c.normal.ToMat3(), true, tr.c.point); + } else { + impactEffect->SetOrigin ( tr.c.point ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + impactEffect->SetEndOrigin ( tr.c.point ); + } + + if ( !boltMuzzleFlash ) { + boltMuzzleFlash = gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_sweep_muzzle" ), origin, attackVector.ToMat3(), true, origin); + } else { + boltMuzzleFlash->SetOrigin ( origin ); + boltMuzzleFlash->SetAxis ( attackVector.ToMat3() ); + boltMuzzleFlash->SetEndOrigin ( origin ); + } + + //hurt anything in the way + idEntity* ent = gameLocal.entities[tr.c.entityNum]; + + if( ent) { + ent->Damage ( this, this, attackVector, spawnArgs.GetString ( "def_makron_sweep_damage" ), 1.0f, 0 ); + } + +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::PerformAction ( const char* stateName, int blendFrames, bool noPain ) { + // Allow movement in actions + move.fl.allowAnimMove = true; + + // Start the action + SetAnimState ( ANIMCHANNEL_TORSO, stateName, blendFrames ); + + // Always call finish action when the action is done, it will clear the action flag + aifl.action = true; + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + + // Go back to idle when done-- sometimes. + if ( !noIdle ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", blendFrames ); + } + + // Main state will wait until action is finished before continuing + InterruptState ( "Wait_ActionNoPain" ); + + OnStartAction ( ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +bool rvMonsterBossMakron::PerformAction( rvAIAction* action, bool (idAI::*condition)(rvAIAction*,int), rvAIActionTimer* timer ) { + return idAI::PerformAction( action, condition ,timer ); +} + +/* +================ +rvMonsterBossMakron::PerformAction +================ +*/ +void rvMonsterBossMakron::OnStopAction( void ) { + ////gameLocal.Printf("\n\nAction stopped ----------- \n"); +} + +/* +================ +rvMonsterBossMakron::Damage +================ +*/ +void rvMonsterBossMakron::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + + //Deal damage here, + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + //if the Makron has 0 or less health, make sure he's killed + ///if ( health <= 0 && !flagFlyingMode && !flagFakeDeath ) { + // Killed( this, this, 1, dir, location); + //} +} +/* +================ +rvMonsterBossMakron::Killed +================ +*/ +void rvMonsterBossMakron::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + + //if this is the undying Makron Jr, don't worry about death. Stop what we're doing, + //Call the script function and let it ride. + if( flagUndying == 1) { + flagUndying = 2; + ExecScriptFunction( funcs.death ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + PerformAction ( actionKillPlayer.state, actionKillPlayer.blendFrames, actionKillPlayer.fl.noPain ); + return; + } + + if( flagUndying == 2) { + return; + } + + //stop all effects. + StopAllEffects(); + + //if the makron isn't in flying mode, it is now. + if( flagFlyingMode ) { + //play the falling animation, then die. + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + //SetState( "Torso_Death_Fall" ); + idAI::Killed( inflictor, attacker, damage, dir, location); + return; + } + + //the Makron is in undying mode until he finishes getting up. + //gameLocal.Warning("First form defeated!"); + + health = 1; + aifl.undying = true; + fl.takedamage = false; + SetMoveType ( MOVETYPE_DEAD ); + StopMove( MOVE_STATUS_DONE ); + //gameLocal.Printf("************\n************\n************\n************\n---flagFakeDeath set to TRUE! ************\n************\n************\n************\n" ); + flagFakeDeath = true; + + + + //play the death anim + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + //Call this anyway-- hopefully the script is prepared to handle multiple Makron deaths... + ExecScriptFunction( funcs.death ); + + //make sure he goes through deadness, but we need to post FinishAction afterwards +// SetState( "Torso_FirstDeath" ); +// aifl.action = true; + SetState( "Torso_FirstDeath" ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FirstDeath", 30, 0, SFLAG_ONCLEAR ); + +} + + +/* +================ +rvMonsterBossMakron::BeginSeparation( void ) +================ +*/ +void rvMonsterBossMakron::BeginSeparation( void ) { + + //spawn in a new Makron leg, + idDict args; + idEntity* newLegs; + + //We may need to do this at a later point + //SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_makron_legs" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &newLegs ); + + //store the name of the entity in the Makron's keys so we can burn it out as well. + spawnArgs.Set( "legs_name", newLegs->GetName() ); + + +} + +/* +================ +rvMonsterBossMakron::CompleteSeparation( void ) +================ +*/ +void rvMonsterBossMakron::CompleteSeparation( void ) { + + //flying mode now + flagFlyingMode = true; + SetMoveType ( MOVETYPE_FLY ); + move.fl.noGravity = true; + animPrefix = "fly"; + + //is there a cooler way to do this? Heal over time? + //health = spawnArgs.GetFloat("health_flying", "5000" ); + + ExecScriptFunction( funcs.init); + + //makron is once again pwnable. + aifl.undying = false; + fl.takedamage = true; + flagFakeDeath = false; + patternedMode = true; + + //make sure he cleans up. +// PostAnimState ( ANIMCHANNEL_ALL, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); +// PostAnimState ( ANIMCHANNEL_LEGS, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + //gameLocal.Printf("*****\n*****\n*****\nPast 'PostAnimState' flagFakeDeath is: %d \n*****\n*****\n*****\n", flagFakeDeath); + + +// These four lines work! + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Torso_Idle", 0, 0 ); + PostState( "State_Combat" ); + + +} + +/* +================ +rvMonsterBossMakron::ScriptedFace( idEntity* faceEnt, bool endWithIdle ) +================ +*/ +/* +void rvMonsterBossMakron::ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) { + + //set the ideal yaw, the change to the facing state. + FaceEntity( faceEnt ); + + //store the ideal yaw, because somehow between here and the state we set it gets stomped. + facingIdealYaw = move.ideal_yaw; + + //become scripted + aifl.scripted = true; + + //This will get us close to facing the entity correctly. + SetState( "State_ScriptedFace", SFLAG_ONCLEAR); + +} +*/ + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterBossMakron::Event_AllowMoreSpawns +================ +*/ +// this will allow Makron to spawn more baddies. +void rvMonsterBossMakron::Event_AllowMoreSpawns( void ) { + flagAllowSpawns = true; +} + +/* +================ +rvMonsterBossMakron::Event_EnablePatternMode +================ +*/ +// When set, the Makron will now only fight via scripted patterns +void rvMonsterBossMakron::Event_EnablePatternMode( void ) { + patternedMode = true; + noIdle = true; + flagTeleporting = false; +} + +/* +================ +rvMonsterBossMakron::Event_DisablePatternMode +================ +*/ +void rvMonsterBossMakron::Event_DisablePatternMode( void ) { + patternedMode = false; + noIdle = false; +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_Separate( void ) { + + //all we need to do here is post the separation state, right? + BeginSeparation(); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Resurrection", 0, 5000, SFLAG_ONCLEAR ); + +} + +/* +================ +rvMonsterBossMakron::Event_Separate +================ +*/ +void rvMonsterBossMakron::Event_FlyingRotate( idVec3& vecOrg ) { + + //set move.ideal_yaw + TurnToward( vecOrg ); + + //copy it over + facingIdealYaw = move.ideal_yaw; + aifl.scripted = true; + + //set the state + SetState( "Torso_RotateToAngle"); + +} + +/* +================ +rvMonsterBossMakron::Event_SetNextAction +================ +*/ +void rvMonsterBossMakron::Event_SetNextAction( const char * actionString) { + + //if the next action is occupied, return false + if( actionPatterned != -1) { + idThread::ReturnFloat(0); + return; + } + + //otherwise, select the action from a list + if( !idStr::Cmp( actionString, "actionCannon")) { + actionPatterned = MAKRON_ACTION_CANNON; + } + else if( !idStr::Cmp( actionString, "actionCannonSweep")) { + actionPatterned = MAKRON_ACTION_CANNON_SWEEP; + } + else if( !idStr::Cmp( actionString, "actionDMG")) { + actionPatterned = MAKRON_ACTION_DMG; + } + else if( !idStr::Cmp( actionString, "actionDMGrenades")) { + actionPatterned = MAKRON_ACTION_GRENADE; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep1")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_1; + } + else if( !idStr::Cmp( actionString, "actionLightningSweep2")) { + actionPatterned = MAKRON_ACTION_LIGHTNING_2; + } + else if( !idStr::Cmp( actionString, "actionStomp")) { + actionPatterned = MAKRON_ACTION_STOMP; + } + else if( !idStr::Cmp( actionString, "actionHeal")) { + actionPatterned = MAKRON_ACTION_HEAL; + } + else if( !idStr::Cmp( actionString, "actionCharge")) { + actionPatterned = MAKRON_ACTION_CHARGE; + } + else if( !idStr::Cmp( actionString, "actionKillPlayer")) { + actionPatterned = MAKRON_ACTION_KILLPLAYER; + } + else if( !idStr::Cmp( actionString, "actionEndPattern")) { + actionPatterned = -1; + } + else { + gameLocal.Error(" Bad action %s passed into MonsterMakron::SetNextAction", actionString); + idThread::ReturnFloat(0); + return; + } + + idThread::ReturnFloat(1); + return; + + + +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterBossMakron ) + STATE ( "Torso_DMGAttack", rvMonsterBossMakron::State_Torso_DMGAttack ) + STATE ( "Torso_MeleeAttack", rvMonsterBossMakron::State_Torso_MeleeAttack ) + STATE ( "Torso_CannonAttack", rvMonsterBossMakron::State_Torso_CannonAttack ) + STATE ( "Torso_GrenadeAttack", rvMonsterBossMakron::State_Torso_GrenadeAttack ) + STATE ( "Torso_CannonSweepAttack", rvMonsterBossMakron::State_Torso_CannonSweepAttack ) + STATE ( "Torso_Lightning1Attack", rvMonsterBossMakron::State_Torso_Lightning1Attack ) + STATE ( "Torso_Lightning2Attack", rvMonsterBossMakron::State_Torso_Lightning2Attack ) + STATE ( "Torso_StompAttack", rvMonsterBossMakron::State_Torso_StompAttack ) + STATE ( "Torso_Recharge", rvMonsterBossMakron::State_Torso_Recharge ) + STATE ( "Torso_Charge", rvMonsterBossMakron::State_Torso_Charge ) + STATE ( "Torso_KillPlayer", rvMonsterBossMakron::State_Torso_KillPlayer ) + STATE ( "Torso_FirstDeath", rvMonsterBossMakron::State_Torso_FirstDeath ) + STATE ( "Torso_Resurrection", rvMonsterBossMakron::State_Torso_Resurrection ) + STATE ( "State_Killed", rvMonsterBossMakron::State_Killed ) + STATE ( "Torso_Teleport", rvMonsterBossMakron::State_Torso_Teleport ) + + STATE ( "Torso_TurnRight90", rvMonsterBossMakron::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterBossMakron::State_Torso_TurnLeft90 ) + + STATE ( "Torso_RotateToAngle", rvMonsterBossMakron::State_Torso_RotateToAngle ) + + STATE ( "Frame_BeginLightningSweep2", rvMonsterBossMakron::Frame_BeginLightningSweep2 ) + STATE ( "Frame_EndLightningSweep2", rvMonsterBossMakron::Frame_EndLightningSweep2 ) + STATE ( "Frame_StompAttack", rvMonsterBossMakron::Frame_StompAttack ) + STATE ( "Frame_Teleport", rvMonsterBossMakron::Frame_Teleport ) + + STATE ( "State_ScriptedFace", rvMonsterBossMakron::State_ScriptedFace ) + + +END_CLASS_STATES + + +/* +================ +rvBossMakron::State_Torso_DMGAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_DMGAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron DMG Go!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_dmg", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_Torso_Charge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Charge ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "run", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvBossMakron::State_ScriptedFace +================ +*/ + +stateResult_t rvMonsterBossMakron::State_ScriptedFace ( const stateParms_t& parms ) { + + //note this uses the Makron's version of FacingIdeal, + if( !flagFlyingMode) { + if ( !aifl.scripted || (!CheckTurnActions( ) && (!move.anim_turn_angles))) { + return SRESULT_DONE; + } + + return SRESULT_WAIT; + + } else { + + if ( !aifl.scripted || FacingIdeal() ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +} + +/* + enum { + STAGE_INIT, + STAGE_WAIT + }; + + idStr turnAnim; + float turnYaw; + + switch( parms.stage ) { + case STAGE_INIT: + + DisableAnimState ( ANIMCHANNEL_LEGS ); + //which way do we need to face? + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + turnAnim = "turn_right_90"; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + turnAnim = "turn_left_90"; + } else { + //guess we don't need to turn? We're done. + aifl.scripted = false; + return SRESULT_DONE; + } + PlayAnim ( ANIMCHANNEL_TORSO, turnAnim, 4 ); + AnimTurn ( 90.0f, true ); + return SRESULT_WAIT; + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + //back to the start to make sure we're facing the right way. + return SRESULT_STAGE ( STAGE_INIT ); + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; + +} +*/ + + +/* +================ +rvBossMakron::State_Torso_FirstDeath +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_FirstDeath ( const stateParms_t& parms ) { + + + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; +} + +/* +================ +rvBossMakron::State_Torso_Resurrection +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Resurrection ( const stateParms_t& parms ) { +/* enum { + STAGE_INIT, + STAGE_WAIT_FIRST, + STAGE_RISE, + STAGE_WAIT_SECOND + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + //force a long blend on this anim since it will be sudden + PlayAnim ( ANIMCHANNEL_TORSO, "separation_start", 30 ); + return SRESULT_STAGE ( STAGE_WAIT_FIRST); + + case STAGE_WAIT_FIRST: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_RISE); + } + return SRESULT_WAIT; + + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT_SECOND); + + case STAGE_WAIT_SECOND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } +*/ + enum { + STAGE_RISE, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_RISE: + //hide the leg surface + const idKeyValue* kv; + kv = spawnArgs.MatchPrefix ( "surface_legs" ); + HideSurface ( kv->GetValue() ); + + //start the effect + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover","thruster") ); + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "separation_rise", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + CompleteSeparation(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvBossMakron::State_Lightning2Attack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Lightning2Attack ( const stateParms_t& parms ) { + + idVec3 boltVector; + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + + //prep up for the bolt + //gameLocal.Warning("Prepping sweep 2"); + //init these values + StopAllBoltEffects(); + stateBoltSweep = 0; + flagSweepDone = false; + //set up bolt targeting at a little above the players chest-- make him duck this one. + boltVector = enemy.lastVisibleEyePosition; + boltVector.z += 8; + InitBoltSweep( boltVector ); + + //play the anim + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "claw_sweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + if( stateBoltSweep == 1) { + //fire the bolt out from the claw based on where the claw is pointing, sort of. + boltTime = gameLocal.time - boltSweepStartTime; + leftBoltVector.Lerp( boltVectorMax, boltVectorMin, boltTime / SEC2MS(boltSweepTime)); + + LightningSweep( leftBoltVector, leftBoltEffect, leftBoltImpact ); + } + else if( stateBoltSweep == 2) { + //gameLocal.Warning("Sweep 2 done."); + StopAllBoltEffects(); + stateBoltSweep = 0; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterBossMakron::State_Torso_StompAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_StompAttack( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //can't do the stomp attack while flying-- ain't got no legs!! + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shockwave_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvMonsterBossMakron::State_Torso_Teleport +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Teleport( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch( parms.stage ) { + case STAGE_INIT: + //No teleporting in flying mode. + if ( flagFlyingMode ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + //can't take damage in teleport anim, bad things happen! + aifl.undying = true; + PlayAnim ( ANIMCHANNEL_TORSO, "teleport_stomp", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + + //restore former state, which is killable unless some other effect renders Makron unkillable. + if( !flagUndying ) { + aifl.undying = false; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + + + +} + +/* +================ +rvBossMakron::State_Torso_GrenadeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_GrenadeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Grenades!"); + //fire the DMG + DisableAnimState ( ANIMCHANNEL_LEGS ); + //only allow spawns if the script tells us we can + if( !flagAllowSpawns) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_dm", parms.blendFrames ); + } + else { + flagAllowSpawns = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_grenade_spawn", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvBossMakron::State_Torso_Recharge +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_Recharge ( const stateParms_t& parms ) { + + ExecScriptFunction( scriptRecharge ); + return SRESULT_DONE; +} + +/* +================ +rvBossMakron::State_Torso_MeleeAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_MeleeAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //swing! + //gameLocal.Warning("Makron Melee Attack"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "melee_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + + +/* +================ +rvMonsterBossMakron::State_Torso_RotateToAngle +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_RotateToAngle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT_LOOP, + }; + + float facingTimeDelta; + float turnYaw; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTime = MS2SEC( gameLocal.time); + aifl.scripted = true; + return SRESULT_STAGE ( STAGE_WAIT_LOOP ); + } + aifl.scripted = false; + return SRESULT_DONE; + case STAGE_WAIT_LOOP: + turnYaw = idMath::AngleNormalize180 ( facingIdealYaw - move.current_yaw ) ; + if( turnYaw > 1.0f || turnYaw < -1.0f) { + facingTimeDelta = MS2SEC( gameLocal.time) - facingTime; + idAngles ang = GetPhysics()->GetAxis().ToAngles(); + ang.yaw += ( turnRate * facingTimeDelta ); + SetAngles( ang); + move.current_yaw = ang.yaw; + return SRESULT_STAGE( STAGE_WAIT_LOOP); + } + aifl.scripted = false; + return SRESULT_DONE; + } + + return SRESULT_ERROR; +} +/* +================ +rvMonsterBossMakron::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::State_Torso_CannonAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + //once an anim starts with the legs disabled, the rest of the anims should match that. + static bool noLegs; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( !flagFlyingMode || ( flagFlyingMode && !move.fl.moving )) { + noLegs = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + } else { + noLegs = false; + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_start", parms.blendFrames ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_start", parms.blendFrames ); + + } + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + //if we're flying, and moving, fire fast! + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire_fast", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_fire_fast", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_fire", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + //if( flagFlyingMode && move.fl.moving ) { + if( !noLegs ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + PlayAnim ( ANIMCHANNEL_LEGS, "range_cannon_end", 0 ); + } else { + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_cannon_end", 0 ); + } + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_Lightning1Attack +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_Lightning1Attack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_INIT_BOLT, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + idVec3 origin; + idVec3 targetPoint; + idMat3 axis; + trace_t tr; + + switch ( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning( "Lightningbolt!"); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 3 ) + 2) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_INIT_BOLT ); + } + return SRESULT_WAIT; + + case STAGE_INIT_BOLT: + //aim a little below the player's chest + targetPoint = enemy.lastVisibleChestPosition; + targetPoint.z -= 24; + InitBoltSweep( targetPoint ); + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //sweep the blasts back and forth + MaintainBoltSweep(); + + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + if ( flagSweepDone ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterBossMakron::State_Torso_KillPlayer +================ +*/ + +stateResult_t rvMonsterBossMakron::State_Torso_KillPlayer ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_start", parms.blendFrames ); + shots = 8; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + //keep playing the anim until the sweeping is done. + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || flagFakeDeath ) { + shots--; + if ( shots < 1) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + else { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blast_loop_killplayer", 0 ); + } + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + + +/* +================ +rvBossMakron::State_Torso_CannonSweepAttack +================ +*/ +stateResult_t rvMonsterBossMakron::State_Torso_CannonSweepAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + //gameLocal.Warning("Makron CannonSweep Go!"); + //sweep across with the cannon + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_cannonsweep", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + //if the flag is up, fire. + + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterBossMakron::State_Killed +================ +*/ +stateResult_t rvMonsterBossMakron::State_Killed ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "death_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + disablePain = true; + // At this point the Makron is killed! + // Make sure all animation stops + //StopAnimState ( ANIMCHANNEL_TORSO ); + //StopAnimState ( ANIMCHANNEL_LEGS ); + //if ( head ) { + // StopAnimState ( ANIMCHANNEL_HEAD ); + //} + + // Make sure all animations stop + //animator.ClearAllAnims ( gameLocal.time, 0 ); + + if( spawnArgs.GetBool ( "remove_on_death" ) ){ + PostState ( "State_Remove" ); + } else { + PostState ( "State_Dead" ); + } + + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_BeginLightningSweep2 ( const stateParms_t& parms ) { + + //begin the sweep with this flag + stateBoltSweep = 1; + boltSweepStartTime = gameLocal.time; + boltSweepTime = 1; + return SRESULT_OK; +} + +/* +================ +rvMonsterBossMakron::Frame_BeginLightningSweep2 +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_EndLightningSweep2 ( const stateParms_t& parms ) { + + //end the sweep with this flag + stateBoltSweep = 2; + return SRESULT_OK; + +} + +/* +================ +rvMonsterBossMakron::Frame_Teleport +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_Teleport ( const stateParms_t& parms ) { + + //hide + Hide(); + + //turn on the do-nothing teleport flag + flagTeleporting = true; + + //call some script. + ExecScriptFunction( scriptTeleport ); + return SRESULT_DONE; + +} + + +/* +================ +rvMonsterBossMakron::Frame_StompAttack +================ +*/ + +stateResult_t rvMonsterBossMakron::Frame_StompAttack ( const stateParms_t& parms ) { + + idVec3 origin; + idVec3 worldUp(0, 0, 1); + + // Eminate from Makron origin + origin = this->GetPhysics()->GetOrigin(); + + //start radius at 256; + stompRadius = spawnArgs.GetFloat("stomp_start_size", "64"); + + //stomp + gameLocal.PlayEffect ( gameLocal.GetEffect ( this->spawnArgs, "fx_stomp_wave" ), origin, worldUp.ToMat3(), false, origin); + + //bamf! + PostEventMS( &EV_StompAttack, 0, origin); + + return SRESULT_OK; + +} +/* +================ +rvMonsterBossMakron::Event_ToggleCornerState +================ +*/ +void rvMonsterBossMakron::Event_ToggleCornerState ( float f ) { + if( f == 1.0f) { + flagCornerState = true; + } else { + flagCornerState = false; + } +} + +/* +================ +rvMonsterBossMakron::Event_StompAttack +================ +*/ + +void rvMonsterBossMakron::Event_StompAttack (idVec3& origin) { + + idVec3 targetOrigin; + idVec3 worldUp; + idEntity* entities[ 1024 ]; + int count; + int i; + float stompZ; + idVec3 dir; + modelTrace_t result; + + + stompZ = origin.z; + + worldUp.x = 0; + worldUp.y = 0; + worldUp.z = 1; + + //if the radius is too big, stop. + if ( stompRadius > stompMaxRadius ) { + return; + } + + //get all enemies within radius. If they are: + // within radius, + // more than (radius - stompWidth) units away, + // Z valued within 16 of the the stomp Z + //they take stomp damage. + count = gameLocal.EntitiesWithinRadius ( origin, stompRadius, entities, 1024 ); + + //gameRenderWorld->DebugCircle( colorRed,origin,worldUp,stompRadius,24,20,false); + //gameRenderWorld->DebugCircle( colorBlue,origin,worldUp,stompRadius - stompWidth,24,20,false); + + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + + //don't stomp ourself, derp... + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Are they Z equal (about?) + targetOrigin = ent->GetPhysics()->GetOrigin(); + if( idMath::Abs( targetOrigin.z - origin.z) > 16) { + continue; + } + + // are they within the stomp width? + if( targetOrigin.Dist( origin) < ( stompRadius - stompWidth) || + targetOrigin.Dist( origin) > stompRadius ) { + continue; + } + + if( gameRenderWorld->FastWorldTrace(result, origin, ent->GetPhysics()->GetCenterMass()) ) { + continue; + } + + //ok, damage them + dir = targetOrigin - origin; + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_makron_stomp_damage" ), 1.0f, 0 ); + //gameRenderWorld->DebugArrow( colorYellow, origin, targetOrigin, 5, 1000); + + } + + //move the radius along + stompRadius += stompSpeed; + + //run it back + PostEventSec( &EV_StompAttack, stompSpeed / stompMaxRadius , origin ); + +} + + diff --git a/src/mpgame/ai/Monster_ConvoyGround.cpp b/src/mpgame/ai/Monster_ConvoyGround.cpp new file mode 100644 index 0000000..490d012 --- /dev/null +++ b/src/mpgame/ai/Monster_ConvoyGround.cpp @@ -0,0 +1,626 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +// +class rvMonsterConvoyGround : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterConvoyGround ); + + rvMonsterConvoyGround ( void ); + + void Spawn ( void ); + void InitSpawnArgsVariables ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Postthink ( void ); + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual bool CheckPainActions ( void ); + + virtual bool CanTurn ( void ) const; + virtual bool CanMove ( void ) const; + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AdjustHealthByDamage ( int inDamage ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + int shots; + int minShots; + int maxShots; + bool isOpen; + bool vehicleCollision; + float moveCurrentAnimRate; + float moveAnimRateMin; + float moveAnimRateRange; + float moveAccelRate; + bool onGround; + idVec3 oldOrigin; + idVec3 lastPainDir; + + rvAIAction actionBlasterAttack; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual const char* GetIdleAnimName ( void ); + + virtual void OnDeath ( void ); + +private: + + // General states + stateResult_t State_Fall ( const stateParms_t& parms ); + + // Legs States + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_Open ( const stateParms_t& parms ); + stateResult_t State_Torso_Close ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyGround ); +}; + +CLASS_DECLARATION( idAI, rvMonsterConvoyGround ) +END_CLASS + +/* +================ +rvMonsterConvoyGround::rvMonsterConvoyGround +================ +*/ +rvMonsterConvoyGround::rvMonsterConvoyGround ( ) { + shots = 0; + isOpen = false; + vehicleCollision = false; + moveCurrentAnimRate = 1.0f; +} + +void rvMonsterConvoyGround::InitSpawnArgsVariables ( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); + moveAccelRate = spawnArgs.GetFloat ( "moveAccelRate", ".1" ); + moveAnimRateMin = spawnArgs.GetFloat ( "moveMinAnimRate", "1" ); + moveAnimRateRange = spawnArgs.GetFloat ( "moveMaxAnimRate", "10" ) - moveAnimRateMin; +} + +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +void rvMonsterConvoyGround::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + aifl.disableLook = true; + + onGround = true; + +} + +/* +================ +rvMonsterConvoyGround::Prethink +================ +*/ +void rvMonsterConvoyGround::Postthink ( void ) { +/* FIXME + if ( onGround && !physicsObj.HasGroundContacts ( ) ) { + onGround = false; + InterruptState ( "State_Fall" ); + } +*/ + + idAI::Postthink ( ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +bool rvMonsterConvoyGround::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + lastPainDir = dir; + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterConvoyGround::Save +================ +*/ +void rvMonsterConvoyGround::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + //minShots and maxShots are set in the Restore + savefile->WriteBool ( isOpen ); + savefile->WriteBool ( vehicleCollision ); + savefile->WriteBool ( onGround ); + + savefile->WriteFloat ( moveCurrentAnimRate ); + savefile->WriteVec3 ( oldOrigin ); + savefile->WriteVec3 ( lastPainDir ); + + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterConvoyGround::Restore +================ +*/ +void rvMonsterConvoyGround::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadBool ( isOpen ); + savefile->ReadBool ( vehicleCollision ); + savefile->ReadBool ( onGround ); + + savefile->ReadFloat ( moveCurrentAnimRate ); + savefile->ReadVec3 ( oldOrigin ); + savefile->ReadVec3 ( lastPainDir ); + + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterConvoyGround::CanMove +================ +*/ +bool rvMonsterConvoyGround::CanMove ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanMove ( ); +} + +/* +================ +rvMonsterConvoyGround::CanTurn +================ +*/ +bool rvMonsterConvoyGround::CanTurn ( void ) const { + if ( isOpen ) { + return false; + } + return idAI::CanTurn ( ); +} + +/* +================ +rvMonsterConvoyGround::OnDeath +================ +*/ +void rvMonsterConvoyGround::OnDeath ( void ) { + idVec3 fxOrg; + idVec3 up; + idMat3 fxAxis; + + //center it + fxOrg = GetPhysics()->GetCenterMass(); + + //point it up + up.Set( 0, 0, 1 ); + fxAxis = up.ToMat3(); + + //if we can play it at the joint, do that + jointHandle_t axisJoint = animator.GetJointHandle ( "axis" ); + if ( axisJoint != INVALID_JOINT ) { + idMat3 junk; + animator.GetJointLocalTransform( axisJoint, gameLocal.GetTime(), fxOrg, junk ); + fxOrg = renderEntity.origin + (fxOrg*renderEntity.axis); + } + + gameLocal.PlayEffect ( spawnArgs, "fx_death", fxOrg, fxAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterConvoyGround::AdjustHealthByDamage +================ +*/ +void rvMonsterConvoyGround::AdjustHealthByDamage ( int damage ) { + if ( isOpen || vehicleCollision ) { + idAI::AdjustHealthByDamage ( damage ); + } else { + PlayEffect ( "fx_shieldHit", animator.GetJointHandle ( "axis" ) ); + } +} + +void rvMonsterConvoyGround::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + vehicleCollision = false; + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + vehicleCollision = true; + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} +/* +================ +rvMonsterConvoyGround::Spawn +================ +*/ +bool rvMonsterConvoyGround::CheckActions ( void ) { + if ( isOpen ) { + if ( move.fl.moving ) { +/* + || !CheckAction_RangedAttack( &actionBlasterAttack, -1 ) + || enemy.range > actionBlasterAttack.maxRange + || enemy.range < actionBlasterAttack.minRange + || (!move.fl.moving && (gameLocal.GetTime()-move.startTime) > 3000 ) ) { +*/ + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Close", 4, true ); + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } else { + // Open up if we have stopped and have an enemy + if ( !move.fl.moving && physicsObj.HasGroundContacts ( ) && enemy.ent && legsAnim.IsIdle ( ) && CheckTactical ( AITACTICAL_RANGED ) ) { + StartSound( "snd_prepare", SND_CHANNEL_ANY, 0, 0, 0 ); + PerformAction ( "Torso_Open", 4, true ); + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterConvoyGround::CheckPainActions +================ +*/ +bool rvMonsterConvoyGround::CheckPainActions ( void ) { + if ( isOpen ) { + return false; + } + + return idAI::CheckPainActions ( ); +} + +/* +================ +rvMonsterConvoyGround::GetIdleAnimName +================ +*/ +const char* rvMonsterConvoyGround::GetIdleAnimName ( void ) { + // Start idle animation + if ( isOpen ) { + return "idle_open"; + } + return "idle"; +} + +/* +================ +rvMonsterConvoyGround::FilterTactical +================ +*/ +int rvMonsterConvoyGround::FilterTactical ( int availableTactical ) { + return idAI::FilterTactical ( availableTactical ); +} + +/* +===================== +rvMonsterConvoyGround::GetDebugInfo +===================== +*/ +void rvMonsterConvoyGround::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + + proc ( "rvMonsterConvoyGround", "moveAnimRate", va("%g", moveCurrentAnimRate ), userData ); + proc ( "rvMonsterConvoyGround", "isOpen", isOpen ? "true" : "false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyGround ) + STATE ( "State_Fall", rvMonsterConvoyGround::State_Fall ) + STATE ( "Torso_Open", rvMonsterConvoyGround::State_Torso_Open ) + STATE ( "Torso_Close", rvMonsterConvoyGround::State_Torso_Close ) + STATE ( "Torso_BlasterAttack", rvMonsterConvoyGround::State_Torso_BlasterAttack ) + STATE ( "Torso_Pain", rvMonsterConvoyGround::State_Torso_Pain ) + + STATE ( "Legs_Move", rvMonsterConvoyGround::State_Legs_Move ) +END_CLASS_STATES + +/* +================ +rvMonsterConvoyGround::State_Fall +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact, switch to combat state + STAGE_WAITDONE, + STAGE_DONE + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StopAnimState ( ANIMCHANNEL_LEGS ); + StopAnimState ( ANIMCHANNEL_TORSO ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle", 0 ); + oldOrigin = physicsObj.GetOrigin ( ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( physicsObj.HasGroundContacts ( ) ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + + if ( (physicsObj.GetOrigin ( ) - oldOrigin).LengthSqr() > Square(128.0f) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "land", 0 ); + return SRESULT_STAGE ( STAGE_WAITDONE ); + } + return SRESULT_STAGE ( STAGE_DONE ); + + case STAGE_WAITDONE: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + onGround = true; + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Open +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Open ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "extend_legs", parms.blendFrames ); + isOpen = true; + aifl.disableLook = false; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Close +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Close ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "retract_legs", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + isOpen = false; + aifl.disableLook = true; + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Torso_Pain ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_END + }; + + switch ( parms.stage ) { + case STAGE_START: + // Force the orientation to the direction we got hit from so the animation looks correct + OverrideFlag ( AIFLAGOVERRIDE_NOTURN, true ); + TurnToward ( physicsObj.GetOrigin() - lastPainDir * 128.0f ); + move.current_yaw = move.ideal_yaw; + + // Just in case the pain anim wasnt set before we got here. + if ( !painAnim.Length ( ) ) { + painAnim = "pain"; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, painAnim, parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END ); + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyGround::State_Legs_Move +================ +*/ +stateResult_t rvMonsterConvoyGround::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_MOVE + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.allowAnimMove = true; + move.fl.allowPrevAnimMove = false; + move.fl.running = true; + move.currentDirection = MOVEDIR_FORWARD; + // TODO: Looks like current anim rate never gets reset, so they do not correctly accelerate from a stop + // unfortunately, adding this change (with a decent acceleration factor) caused them to do lots of + // not-so-good looking short moves. +// moveCurrentAnimRate = 0; + + oldOrigin = physicsObj.GetOrigin ( ); + PlayCycle ( ANIMCHANNEL_LEGS, "run", 0 ); + StartSound( "snd_move", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_MOVE ); + + case STAGE_MOVE: + + // If not moving forward just go back to idle + if ( !move.fl.moving || !CanMove() ) { + StopSound( SND_CHANNEL_BODY3, 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + + // If on the ground update the animation rate based on the normal of the ground plane + if ( !ai_debugHelpers.GetBool ( ) && physicsObj.HasGroundContacts ( ) ) { + float rate; + idVec3 dir; + + dir = (physicsObj.GetOrigin ( ) - oldOrigin); + + if ( DistanceTo ( move.moveDest ) < move.walkRange ) { + rate = moveAnimRateMin; + } else if ( dir.Normalize ( ) > 0.0f ) { + rate = idMath::ClampFloat ( -0.7f, 0.7f, physicsObj.GetGravityNormal ( ) * dir ) / 0.7f; + rate = moveAnimRateMin + moveAnimRateRange * (1.0f + rate) / 2.0f; + } else { + rate = moveAnimRateMin + moveAnimRateRange * 0.5f; + } + moveCurrentAnimRate += ((rate - moveCurrentAnimRate) * moveAccelRate); + + animator.CurrentAnim ( ANIMCHANNEL_LEGS )->SetPlaybackRate ( gameLocal.time, moveCurrentAnimRate ); + } + + oldOrigin = physicsObj.GetOrigin ( ); + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + diff --git a/src/mpgame/ai/Monster_ConvoyHover.cpp b/src/mpgame/ai/Monster_ConvoyHover.cpp new file mode 100644 index 0000000..d5df4c6 --- /dev/null +++ b/src/mpgame/ai/Monster_ConvoyHover.cpp @@ -0,0 +1,654 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// rvMonsterConvoyHover.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +class rvMonsterConvoyHover : public rvVehicleMonster { +public: + CLASS_PROTOTYPE ( rvMonsterConvoyHover ); + + rvMonsterConvoyHover ( void ); + ~rvMonsterConvoyHover ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Think ( void ); + +private: + + void Think_Random ( void ); + void Think_Pathing ( void ); + + void AttackBlaster ( void ); + void AttackBeam ( void ); + //void AttackBomb ( void ); + + float angleYaw; // actually acos( angleYaw ) from player + float minYaw; + float maxYaw; + float desiredHeight; // actually acos( desiredHeight ) from player + float minHeight; + float maxHeight; + float distance; // distance to enemy + float minDistance; + float maxDistance; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + + int shotCount; + + idPhysics_RigidBody physicsObj; + + static const int DAMPEN_ANGLE_SAMPLES = 8; + idAngles hoverDampening[ DAMPEN_ANGLE_SAMPLES ]; + + void CalcDampening ( const idAngles & cur, idAngles & out ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_BeamAttack ( const stateParms_t& parms ); + //stateResult_t State_BombAttack ( const stateParms_t& parms ); + + struct { + bool pathing:1; + bool faceEnemy:1; + bool dead:1; + } myfl; + + virtual void OnDeath ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterConvoyHover ); +}; + +CLASS_DECLARATION ( rvVehicleMonster, rvMonsterConvoyHover ) +END_CLASS + +/* +================ +rvMonsterConvoyHover::rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::rvMonsterConvoyHover ( void ) { +} + +/* +================ +rvMonsterConvoyHover::~rvMonsterConvoyHover +================ +*/ +rvMonsterConvoyHover::~rvMonsterConvoyHover ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvMonsterConvoyHover::Save +================ +*/ +void rvMonsterConvoyHover::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( angleYaw ); + savefile->WriteFloat ( desiredHeight ); + savefile->WriteFloat ( distance ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->Write ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Write( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var +} + +/* +================ +rvMonsterConvoyHover::Restore +================ +*/ +void rvMonsterConvoyHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( angleYaw ); + savefile->ReadFloat ( desiredHeight ); + savefile->ReadFloat ( distance ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + savefile->ReadInt ( shotCount ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->Read ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES ); + + savefile->Read( &myfl, sizeof(myfl) ); // cnicholson: Added unsaved var + + InitSpawnArgsVariables(); +} + +void rvMonsterConvoyHover::InitSpawnArgsVariables ( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + minYaw = spawnArgs.GetFloat( "minYaw", "0" ); + maxYaw = spawnArgs.GetFloat( "maxYaw", "360" ); + minHeight = spawnArgs.GetFloat( "minHeight", "10" ); + maxHeight = spawnArgs.GetFloat( "maxHeight", "70" ); + minDistance = spawnArgs.GetFloat( "minDistance", "100" ); + maxDistance = spawnArgs.GetFloat( "maxDistance", "500" ); + + minDistance *= minDistance; + maxDistance *= maxDistance; + + if ( minYaw == 0.0f && maxYaw == 0.0f ) { + maxYaw = 360.0f; + } + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} +/* +================ +rvMonsterConvoyHover::Spawn +================ +*/ +void rvMonsterConvoyHover::Spawn ( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( physicsObj ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP|CONTENTS_FLYCLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( vec3_origin ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); + + InitSpawnArgsVariables(); + + shotCount = 0; + + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = 0.5f * ( maxHeight - minHeight ) + minHeight; + distance = 0.5f * ( maxDistance - minDistance ) + minDistance; + + lastAttackTime = 0; + + SetState( "Idle" ); + + myfl.pathing = false; + myfl.faceEnemy = true; + myfl.dead = false; + + for ( int i = 0; i < DAMPEN_ANGLE_SAMPLES; i++ ) { + hoverDampening[ i ].Zero(); + } + + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); +} + +/* +================ +rvMonsterConvoyHover::Think +================ +*/ +void rvMonsterConvoyHover::Think ( void ) { + if ( !driver ) { + rvVehicleMonster::Think(); + return; + } + + if ( !driver->IsDriving() ) { + GetPhysics()->SetGravity( gameLocal.GetGravity() ); + } + + trace_t trace; + idVec3 dir = GetPhysics()->GetLinearVelocity(); + dir.Normalize(); + if ( gameLocal.TracePoint( this, trace, GetOrigin(), GetPhysics()->GetOrigin() + dir * 100.0f, MASK_SOLID|CONTENTS_FLYCLIP, 0 ) ) { + if ( trace.c.contents == CONTENTS_FLYCLIP ) { + float maxHeight = this->maxHeight * 2.0f; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), GetPhysics()->GetLinearVelocity() * 2.0f ); + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance + 5.0f ); + } + } + + if ( !driver->CanSee( driver->enemy.ent, false ) ) { + desiredHeight = idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f ); + distance = idMath::ClampFloat( minDistance, maxDistance, distance - 5.0f ); + } + + if ( myfl.pathing ) { + Think_Pathing( ); + } else { + Think_Random( ); + } + + rvVehicleMonster::Think(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); +// vel += idVec3( idMath::Sin( gameLocal.time + vel.x ), +// idMath::Sin( gameLocal.time + vel.y ), +// idMath::Sin( gameLocal.time + vel.z ) ); + + + idAngles hover = idAngles( idMath::ClampFloat( -45.0f, 45.0f, vel.x / 20.0f ), + 0.0f, + idMath::ClampFloat( -25.0f, 25.0f, vel.z / 20.0f ) ); + + CalcDampening( hover, hover ); + + if ( myfl.faceEnemy ) { + LookAtEntity( gameLocal.GetLocalPlayer(), 0 ); + idAngles angles = GetPhysics()->GetAxis( ).ToAngles( ); + angles.pitch *= 0.35f; + GetPhysics()->SetAxis( angles.ToMat3( ) * hover.ToMat3() ); + } else { + GetPhysics()->SetAxis( GetPhysics()->GetAxis() * hover.ToMat3() ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Random +================ +*/ +void rvMonsterConvoyHover::Think_Random ( void ) { + const idVec3 xAxis( 1.0f, 0.0f, 0.0f ); + const idVec3 yAxis( 0.0f, 1.0f, 0.0f ); + const idVec3 zAxis( 0.0f, 0.0f, 1.0f ); + const float xSpeed = 16000.0f; + const float ySpeed = 9000.0f; + const float zSpeed = 9000.0f; + + idEntity * ent = driver->enemy.ent; + if ( !ent ) { + ent = gameLocal.GetLocalPlayer(); + } + + idVec3 toEnemyYaw = ent->GetPhysics()->GetOrigin() - GetOrigin(); + toEnemyYaw.z = 0.0f; + toEnemyYaw.Normalize(); + float yaw = toEnemyYaw.ToAngles().yaw; + float height = GetOrigin().z - ent->GetPhysics()->GetOrigin().z; + float dist = GetOrigin().Dist2XY( ent->GetPhysics()->GetOrigin() ); + + // yaw + if ( idMath::Fabs( yaw - angleYaw ) < 10.0f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } else { + idVec3 impulse = yAxis * ( ( angleYaw < yaw ) ? ySpeed : -ySpeed ); + impulse *= GetPhysics()->GetAxis(); + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + + // 5% chance of choosing a new angle + if ( rvRandom::flrand() < 0.05f ) { + angleYaw = rvRandom::flrand( minYaw, maxYaw ); + } + } + + // pitch (changed to height for convenience) + if ( idMath::Fabs( height - desiredHeight ) < 5.0f ) { + desiredHeight = rvRandom::flrand( minHeight, maxHeight ); + } else if ( GetOrigin().z < ent->GetPhysics()->GetOrigin().z + desiredHeight ) { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * zSpeed ); + } else { + GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * -zSpeed ); + } + + // distance + if ( idMath::Fabs( dist - distance ) > 20.0f ) { + idVec3 impulse = xAxis * ( ( dist < distance ) ? -xSpeed : xSpeed ); + idMat3 axis = GetPhysics()->GetAxis(); + axis[ 1 ] = idVec3( 0.0f, 0.0f, 1.0f ); + axis[ 2 ] = axis[ 1 ].Cross( axis[ 0 ] ); + impulse *= axis; + GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse ); + } else { + // just choose a random distance for now + distance = rvRandom::flrand( minDistance, maxDistance ); + } +} + +/* +================ +rvMonsterConvoyHover::Think_Pathing +================ +*/ +void rvMonsterConvoyHover::Think_Pathing ( void ) { +} + +/* +================ +rvMonsterConvoyHover::AttackBlaster +================ +*/ +void rvMonsterConvoyHover::AttackBlaster ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBeam +================ +*/ +void rvMonsterConvoyHover::AttackBeam ( void ) { + idVec3 offset; + idMat3 axis; + jointHandle_t joint; + + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + PlayEffect ( "fx_muzzleflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_blaster") ); + if( proj ) { + idVec3 dir = GetVectorToEnemy(); + if ( dir.Normalize() == 0.0f ) { + dir = axis[ 0 ]; + } + proj->Create( this, offset, dir, NULL ); + proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() ); + } +} + +/* +================ +rvMonsterConvoyHover::AttackBomb +================ +* +void rvMonsterConvoyHover::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft; + + if ( joint == INVALID_JOINT ) { + return; + } + + if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) { + return; + } + + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + + PlayEffect ( "fx_bombflash", joint ); + idProjectile* proj = gameLocal.SpawnSafeEntityDef( spawnArgs.GetString("def_attack_bomb") ); + if( proj ) { + proj->Create( this, offset, axis[0], NULL ); + proj->Launch( offset, axis[0], GetPhysics()->GetPushedLinearVelocity() ); + } +} +*/ + + + +/* +================ +rvMonsterConvoyHover::OnDeath +================ +*/ +void rvMonsterConvoyHover::OnDeath ( void ) { + myfl.dead = true; + idVec3 angular = idVec3( rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ) ); + + physicsObj.SetGravity( gameLocal.GetGravity() ); + physicsObj.SetFriction( 0.0f, 0.0f, 0.0f ); + GetPhysics()->SetAngularVelocity ( angular ); + + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); +} + +/* +================ +rvMonsterConvoyHover::CalcDampening +================ +*/ +void rvMonsterConvoyHover::CalcDampening ( const idAngles & cur, idAngles & out ) { + idAngles current = cur; // just incase cur == out + out = cur; + + for ( int i = 1; i < DAMPEN_ANGLE_SAMPLES; i ++ ) { + hoverDampening[ i - 1 ] = hoverDampening[ i ]; + out += hoverDampening[ i ]; + } + hoverDampening[ DAMPEN_ANGLE_SAMPLES - 1 ] = current; + + out *= ( 1.0f / DAMPEN_ANGLE_SAMPLES ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterConvoyHover ) + STATE( "Idle", rvMonsterConvoyHover::State_Idle ) + STATE( "BlasterAttack", rvMonsterConvoyHover::State_BlasterAttack ) + STATE( "BeamAttack", rvMonsterConvoyHover::State_BeamAttack ) +// STATE( "BombAttack", rvMonsterConvoyHover::State_BombAttack ) +END_CLASS_STATES + + +/* +================ +rvMonsterConvoyHover::State_Idle +================ +*/ +stateResult_t rvMonsterConvoyHover::State_Idle ( const stateParms_t& parms ) { + if ( driver ) { + if ( gameLocal.time - lastAttackTime > rvRandom::irand( 1000, 1200 ) && CanSee( driver->enemy.ent, false ) ) { +// if ( rvRandom::irand( 0, 100 ) < 10 ) { +// distance = 0; +// PostState( "BeamAttack" ); +// } else { + PostState( "BlasterAttack" ); +// } + PostState( "Idle" ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterConvoyHover::State_BlasterAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BeamAttack +================ +*/ +stateResult_t rvMonsterConvoyHover::State_BeamAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BEAM, + STAGE_BEAMWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BEAM ); + + case STAGE_BEAM: + lastAttackTime = gameLocal.time; + AttackBeam( ); + return SRESULT_STAGE ( STAGE_BEAMWAIT ); + + case STAGE_BEAMWAIT: + if ( gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BEAM ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterConvoyHover::State_BombAttack +================ +* +stateResult_t rvMonsterConvoyHover::State_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ diff --git a/src/mpgame/ai/Monster_FailedTransfer.cpp b/src/mpgame/ai/Monster_FailedTransfer.cpp new file mode 100644 index 0000000..f9a1437 --- /dev/null +++ b/src/mpgame/ai/Monster_FailedTransfer.cpp @@ -0,0 +1,145 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "AI.h" + +class rvMonsterFailedTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFailedTransfer ); + + rvMonsterFailedTransfer ( void ); + + void Spawn ( void ); + void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + bool allowSplit; + + virtual void OnDeath ( void ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterFailedTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFailedTransfer ) +END_CLASS + +/* +================ +rvMonsterFailedTransfer::rvMonsterFailedTransfer +================ +*/ +rvMonsterFailedTransfer::rvMonsterFailedTransfer ( ) { + allowSplit = false; +} + +/* +================ +rvMonsterFailedTransfer::Spawn +================ +*/ +void rvMonsterFailedTransfer::Spawn ( void ) { + LoadAF ( "ragdoll_legs", true ); + LoadAF ( NULL, true ); +} + +/* +================ +rvMonsterFailedTransfer::Save +================ +*/ +void rvMonsterFailedTransfer::Save( idSaveGame *savefile ) const { + savefile->WriteBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::Restore +================ +*/ +void rvMonsterFailedTransfer::Restore( idRestoreGame *savefile ) { + savefile->ReadBool ( allowSplit ); +} + +/* +================ +rvMonsterFailedTransfer::OnDeath +================ +*/ +void rvMonsterFailedTransfer::OnDeath ( void ) { + idAI::OnDeath ( ); + + if ( allowSplit ) { + idEntity* torso; + idDict args; + + LoadAF ( "ragdoll_legs", true ); + + PlayEffect ( "fx_bloodyburst", animator.GetJointHandle ( "chest" ) ); + SetSkin ( declManager->FindSkin ( spawnArgs.GetString ( "skin_legs" ) ) ); + + args.Copy ( *gameLocal.FindEntityDefDict ( "monster_failed_transfer_torso" ) ); + args.SetVector ( "origin", GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal() * -50.0f ); + args.SetInt ( "angle", move.current_yaw ); + gameLocal.SpawnEntityDef ( args, &torso ); + torso->fl.takedamage = false; + PostEventMS( &AI_TakeDamage, 100, 1.0f ); + } +} + +/* +================ +rvMonsterFailedTransfer::Killed +================ +*/ +void rvMonsterFailedTransfer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( !idStr::Icmp ( GetDamageGroup( location ), "legs" ) && damage < 999 ) { + allowSplit = true; + } else { + allowSplit = false; + } + + idAI::Killed ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFailedTransfer ) +END_CLASS_STATES + diff --git a/src/mpgame/ai/Monster_Fatty.cpp b/src/mpgame/ai/Monster_Fatty.cpp new file mode 100644 index 0000000..bd831d5 --- /dev/null +++ b/src/mpgame/ai/Monster_Fatty.cpp @@ -0,0 +1,443 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +typedef struct monsterFattyChain_s { + jointHandle_t orientationJoint; + jointHandle_t attackJoint; + bool out; + idEntityPtr projectile; +} monsterFattyChain_t; + +class rvMonsterFatty : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterFatty ); + + rvMonsterFatty ( void ) {} + ~rvMonsterFatty ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool UpdateAnimationControllers ( void ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { return false; }; + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + +protected: + + rvAIAction actionWhipAttack; + + enum { + CHAIN_LEFT, + CHAIN_RIGHT, + CHAIN_MAX + }; + + monsterFattyChain_t chains[CHAIN_MAX]; + + float missHeight; + + virtual bool CheckActions ( void ); + + void PlayAttackAnim ( const idVec3& target, int blendFrames ); + + void ResetAllChains ( void ); + void ChainIn ( int chain ); + void ChainOut ( int chain ); + +private: + + // Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + + // Frame Commands + stateResult_t Frame_LeftChainOut ( const stateParms_t& parms ); + stateResult_t Frame_LeftChainIn ( const stateParms_t& parms ); + + stateResult_t Frame_RightChainOut ( const stateParms_t& parms ); + stateResult_t Frame_RightChainIn ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterFatty ); +}; + +CLASS_DECLARATION( idAI, rvMonsterFatty ) +END_CLASS + +/* +================ +rvMonsterFatty::~rvMonsterFatty +================ +*/ +rvMonsterFatty::~rvMonsterFatty ( void ) { + ResetAllChains ( ); +} + +void rvMonsterFatty::InitSpawnArgsVariables ( void ) +{ + chains[CHAIN_LEFT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChain", "chainb1" ) ); + chains[CHAIN_LEFT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_leftChainAttack", "hookb" ) ); + + chains[CHAIN_RIGHT].orientationJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChain", "chaina1" ) ); + chains[CHAIN_RIGHT].attackJoint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_rightChainAttack", "hooka" ) ); + + missHeight = spawnArgs.GetFloat ( "missHeight", "72" ); +} +/* +================ +rvMonsterFatty::Spawn +================ +*/ +void rvMonsterFatty::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + + // Cache joints + chains[CHAIN_LEFT].out = false; + chains[CHAIN_LEFT].projectile = NULL; + + chains[CHAIN_RIGHT].out = false; + chains[CHAIN_RIGHT].projectile = NULL; + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::Save +================ +*/ +void rvMonsterFatty::Save ( idSaveGame *savefile ) const { + int i; + + actionWhipAttack.Save( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->WriteBool( chains[i].out ); + chains[i].projectile.Save ( savefile ); + } +} + +/* +================ +rvMonsterFatty::Restore +================ +*/ +void rvMonsterFatty::Restore ( idRestoreGame *savefile ) { + int i; + + actionWhipAttack.Restore( savefile ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + savefile->ReadBool( chains[i].out ); + chains[i].projectile.Restore ( savefile ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterFatty::UpdateAnimationControllers +================ +*/ +bool rvMonsterFatty::UpdateAnimationControllers ( void ) { + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + if ( enemy.ent && CheckFOV ( enemy.lastKnownPosition ) ) { + int i; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( enemy.ent->IsType ( idActor::GetClassType ( ) ) ) { + target = static_cast(enemy.ent.GetEntity())->GetEyePosition ( ); + } else { + target = enemy.ent->GetPhysics()->GetOrigin ( ); + } + + if ( !IsEnemyVisible ( ) ) { + target -= enemy.ent->GetPhysics()->GetGravityNormal() * missHeight; + } + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + if ( !chains[i].out ) { + continue; + } + + animator.ClearJoint ( chains[i].orientationJoint ); + GetJointWorldTransform ( chains[i].orientationJoint, gameLocal.time, origin, axis ); + dir = target - origin; + dir.Normalize ( ); + axis.ProjectVector ( dir, localDir ); + animator.SetJointAxis ( chains[i].orientationJoint, JOINTMOD_LOCAL, localDir.ToMat3() ); + } + } + + return true; +} + +/* +================ +rvMonsterFatty::AddDamageEffect +================ +*/ +void rvMonsterFatty::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + /* + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + */ +} + +/* +================ +rvMonsterFatty::CheckAction_WhipAttack +================ +*/ +bool rvMonsterFatty::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterFatty::CheckActions +================ +*/ +bool rvMonsterFatty::CheckActions ( void ) { + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterFatty::CheckAction_WhipAttack, NULL ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterFatty::PlayAttackAnim +================ +*/ +void rvMonsterFatty::PlayAttackAnim ( const idVec3& target, int blendFrames ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -45.0f ) { + animName = "attack4b"; + } else if ( yaw < -20.0f ) { + animName = "attack3b"; + } else if ( yaw < -5.0f ) { + animName = "attack5r"; + } else if ( yaw < 5.0f ) { + animName = "attack5"; + } else if ( yaw < 20.0f ) { + animName = "attack5l"; + } else if ( yaw < 45.0f ) { + animName = "attack2b"; + } else{ + animName = "attack1b"; + } + + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); +} + +/* +================ +rvMonsterFatty::ChainOut +================ +*/ +void rvMonsterFatty::ChainOut ( int chain ) { + idEntity* ent; + idProjectile* proj; + + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_hook" ) ), &ent, false ); + proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + return; + } + + chains[chain].out = true; + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + chains[chain].projectile = proj; + ent->BindToJoint ( this, chains[chain].attackJoint, false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); +} + +/* +================ +rvMonsterFatty::ChainIn +================ +*/ +void rvMonsterFatty::ChainIn ( int chain ) { + chains[chain].out = false; + if ( chains[chain].projectile ) { + delete chains[chain].projectile; + chains[chain].projectile = NULL; + } +} + +/* +================ +rvMonsterFatty::ResetChains +================ +*/ +void rvMonsterFatty::ResetAllChains ( void ) { + int i; + + animator.ClearAllJoints ( ); + + for ( i = 0; i < CHAIN_MAX; i ++ ) { + ChainIn ( i ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterFatty ) + STATE ( "Torso_WhipAttack", rvMonsterFatty::State_Torso_WhipAttack ) + + STATE ( "Frame_LeftChainOut", rvMonsterFatty::Frame_LeftChainOut ) + STATE ( "Frame_LeftChainIn", rvMonsterFatty::Frame_LeftChainIn ) + + STATE ( "Frame_RightChainOut", rvMonsterFatty::Frame_RightChainOut ) + STATE ( "Frame_RightChainIn", rvMonsterFatty::Frame_RightChainIn ) +END_CLASS_STATES + + +/* +================ +rvMonsterFatty::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterFatty::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + PlayAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ResetAllChains ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_LeftChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_LeftChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_LEFT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainOut +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainOut ( const stateParms_t& parms ) { + ChainOut ( CHAIN_RIGHT ); + return SRESULT_OK; +} + +/* +================ +rvMonsterFatty::Frame_RightChainIn +================ +*/ +stateResult_t rvMonsterFatty::Frame_RightChainIn ( const stateParms_t& parms ) { + ChainIn ( CHAIN_RIGHT ); + return SRESULT_OK; +} diff --git a/src/mpgame/ai/Monster_Gladiator.cpp b/src/mpgame/ai/Monster_Gladiator.cpp new file mode 100644 index 0000000..8896774 --- /dev/null +++ b/src/mpgame/ai/Monster_Gladiator.cpp @@ -0,0 +1,925 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class rvMonsterGladiator : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGladiator ); + + rvMonsterGladiator ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual int GetDamageForLocation( int damage, int location ); + +// virtual void SetTether ( rvAITether* newTether ); + +protected: + + // Actions + rvAIAction actionRailgunAttack; + + // Blaster attack + int maxShots; + int minShots; + int shots; + int lastShotTime; + + // Shield + bool usingShield; + idEntityPtr shield; + int shieldStartTime; + int shieldWaitTime; + int shieldHitDelay; + //int shieldInDelay; + //int shieldFov; + int shieldHealth; + int shieldConsecutiveHits; + int shieldLastHitTime; + + int railgunHealth; + int railgunDestroyedTime; + int nextTurnTime; + + virtual bool CheckActions ( void ); + void ShowShield ( void ); + void HideShield ( int hideTime=0 ); + void DestroyRailgun ( void ); + +private: + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso states + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldStart ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldEnd ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_ShieldFire ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after railgun is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterGladiator ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGladiator ) +END_CLASS + +/* +================ +rvMonsterGladiator::rvMonsterGladiator +================ +*/ +rvMonsterGladiator::rvMonsterGladiator ( ) { + usingShield = false; +} + +void rvMonsterGladiator::InitSpawnArgsVariables ( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + shieldHitDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldHitDelay", "1" ) ); +// shieldInDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldInDelay", "3" ) ); +// shieldFov = spawnArgs.GetInt ( "shieldfov", "90" ); +} +/* +================ +rvMonsterGladiator::Spawn +================ +*/ +void rvMonsterGladiator::Spawn ( void ) { + shieldWaitTime = 0; + shieldStartTime = 0; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + + InitSpawnArgsVariables(); + + shots = 0; + lastShotTime = 0; + + railgunHealth = spawnArgs.GetInt ( "railgunHealth", "100" ); + railgunDestroyedTime = 0; + + actionRailgunAttack.Init ( spawnArgs, "action_railgunAttack", "Torso_RailgunAttack", AIACTIONF_ATTACK ); + + // Disable range attack until using shield + //actionRangedAttack.fl.disabled = true; + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterGladiator::CheckActions + +Overriden to handle taking the shield out and putting it away. Will also ensure the gladiator +stays hidden behind his shield if getting shot at. +================ +*/ +bool rvMonsterGladiator::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f || (turnYaw > 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f || (turnYaw < 0 && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + // Limited actions with shield out + if ( usingShield ) { + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + if ( move.moveCommand == MOVE_TO_ENEMY + && move.fl.moving ) + {//advancing on enemy with shield up + if ( gameLocal.GetTime() - lastShotTime > 1500 ) + {//been at least a second since the last time we fired while moving + if ( !gameLocal.random.RandomInt(2) ) + {//fire! + PerformAction ( "Torso_ShieldFire", 0, true ); + return true; + } + } + } + // Only ranged attack and melee attack are available when using shield + if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) || + PerformAction ( &actionRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + ( railgunHealth > 0 + && gameLocal.GetTime() - shieldStartTime > 2000 + && gameLocal.time - pain.lastTakenTime > 500 + && gameLocal.time - combat.shotAtTime > 300 + && gameLocal.GetTime() - shieldLastHitTime > 500 + && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) ) { + shieldWaitTime = 0; + return true; + } + + // see if it's safe to lower it? + if ( gameLocal.GetTime() - shieldStartTime > 2000 ) + {//shield's been up for at least 2 seconds + if ( !enemy.fl.visible || (gameLocal.time - combat.shotAtTime > 1000 && gameLocal.GetTime() - shieldLastHitTime > 1500) ) + { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) + { + PerformAction ( "Torso_ShieldEnd", 4, true ); + return true; + } + } + } + + return false; + } + else + {// Bring the shield out? + if ( combat.tacticalCurrent != AITACTICAL_MELEE || move.fl.done ) + {//not while rushing (NOTE: unless railgun was just destroyed?) + if ( enemy.fl.visible && enemy.fl.inFov ) + { + if ( combat.fl.aware && shieldWaitTime < gameLocal.GetTime() ) + { + if ( gameLocal.time - pain.lastTakenTime <= 1500 + || ( combat.shotAtAngle < 0 && gameLocal.time - combat.shotAtTime < 100 ) + || !gameLocal.random.RandomInt( 20 ) ) + { + if ( !gameLocal.random.RandomInt( 5 ) ) + { + PerformAction ( "Torso_ShieldStart", 4, true ); + return true; + } + } + } + } + } + if ( railgunHealth > 0 && PerformAction ( &actionRailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterGladiator::ShowShield +================ +*/ +void rvMonsterGladiator::ShowShield ( void ) { + // First time? + if ( !shield ) { + idEntity* ent; + idDict args; + const idDict *shieldDef = gameLocal.FindEntityDefDict( spawnArgs.GetString ( "def_shield" ), false ); + args.Set ( "classname", spawnArgs.GetString ( "def_shield" ) ); + if ( gameLocal.SpawnEntityDef( args, &ent ) ) { + shield = ent; + ent->GetPhysics()->SetClipMask ( 0 ); + ent->GetPhysics()->SetContents ( CONTENTS_RENDERMODEL ); + ent->GetPhysics()->GetClipModel ( )->SetOwner ( this ); + Attach ( ent ); + } + if ( !shield ) { + return; + } + if ( shieldDef && shield->IsType( idAFAttachment::GetClassType() ) ) + { + idAFAttachment* afShield = static_cast(shield.GetEntity()); + if ( afShield ) + { + jointHandle_t joint = animator.GetJointHandle( shieldDef->GetString( "joint" ) ); + afShield->SetBody ( this, shieldDef->GetString( "model" ), joint ); + } + } + } else if ( !shield || !shield->IsHidden() ) { + return; + } + + usingShield = true; + shieldWaitTime = 0; + animPrefix = "shield"; + shieldStartTime = gameLocal.time; +// actionRangedAttack.fl.disabled = false; + shieldHealth = 250; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StartSound ( "snd_shield_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + + shield->Show ( ); + + SetShaderParm ( 6, gameLocal.time + 2000 ); +} + +/* +================ +rvMonsterGladiator::HideShield +================ +*/ +void rvMonsterGladiator::HideShield ( int hideTime ) { + if ( !shield || shield->IsHidden() ) { + return; + } + + usingShield = false; + animPrefix = ""; + shieldWaitTime = gameLocal.GetTime()+hideTime; +// actionRangedAttack.fl.disabled = true; + shieldHealth = 0; + shieldConsecutiveHits = 0; + shieldLastHitTime = 0; + shield->SetShaderParm( SHADERPARM_MODE, 0 ); + + // Looping shield sound + StopSound ( SND_CHANNEL_ITEM, false ); + + shield->Hide ( ); +} + +/* +================ +rvMonsterGladiator::DestroyRailgun +================ +*/ +void rvMonsterGladiator::DestroyRailgun ( void ) { + HideSurface ( "models/monsters/gladiator/glad_railgun" ); + railgunHealth = -1; + + idVec3 origin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_railgun_explode", "gun_main_jt" ) ); + GetJointWorldTransform ( joint, gameLocal.time, origin, axis ); + gameLocal.PlayEffect ( spawnArgs, "fx_railgun_explode", origin, axis ); + PlayEffect ( "fx_railgun_burn", joint, true ); + + GetAFPhysics()->GetBody ( "b_railgun" )->SetClipMask ( 0 ); + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "pain_big"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + railgunDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + actionRailgunAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + actionRangedAttack.chance = 0.25f; + actionRangedAttack.maxRange = 400; + minShots = 5; + maxShots = 15; + + combat.tacticalMaskAvailable &= ~AITACTICAL_HIDE_BIT; + + //temporarily disable this so we can charge and get mad + //FIXME: force MELEE + actionRangedAttack.timer.Add( 6000 ); + actionTimerRangedAttack.Add( 6000 ); + actionMeleeAttack.timer.Reset( actionTime ); + + //drop any tether since we need to advance + //SetTether(NULL); + //nevermind: let scripters handle it + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +================ +rvMonsterGladiator::UpdateRunStatus +================ +*/ +bool rvMonsterGladiator::UpdateRunStatus ( void ) { + // If rushing and moving forward, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE && move.currentDirection == MOVEDIR_FORWARD ) { + move.fl.idealRunning = true; + return move.fl.running != move.fl.idealRunning; + } + + // Alwasy walk with shield out + if ( usingShield ) { + move.fl.idealRunning = false; + return move.fl.running != move.fl.idealRunning; + } + + return idAI::UpdateRunStatus ( ); +} + +/* +============ +rvMonsterGladiator::SetTether +============ +*/ +/* +void rvMonsterGladiator::SetTether ( rvAITether* newTether ) { + if ( railgunHealth <= 0 ) { + //don't allow any tethers! + idAI::SetTether(NULL); + } else { + idAI::SetTether(newTether); + } +} +*/ + +/* +================ +rvMonsterGladiator::FilterTactical +================ +*/ +int rvMonsterGladiator::FilterTactical ( int availableTactical ) { + if ( railgunHealth > 0 ) { // Only let the gladiator rush when he is really close to his enemy + if ( !enemy.range || enemy.range > combat.awareRange ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } else { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } else if ( gameLocal.GetTime() - railgunDestroyedTime < 6000 ) { + availableTactical = AITACTICAL_MELEE_BIT; + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGladiator::AddDamageEffect +================ +*/ +void rvMonsterGladiator::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( collision.c.material != NULL && (collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE ) ) { + // Delay putting shield away and shooting until the shield hasnt been hit for a while + actionRangedAttack.timer.Reset ( actionTime ); + actionRangedAttack.timer.Add ( shieldHitDelay ); + shieldStartTime = gameLocal.time; + return; + } + + return idAI::AddDamageEffect ( collision, velocity, damageDefName, inflictor ); +} + +/* +===================== +rvMonsterGladiator::GetDamageForLocation +===================== +*/ +int rvMonsterGladiator::GetDamageForLocation( int damage, int location ) { + // If the gun was hit only do damage to it + if ( idStr::Icmp ( GetDamageGroup ( location ), "gun" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( railgunHealth > 0 ){ + railgunHealth -= damage; + if ( railgunHealth <= 0 ) { + DestroyRailgun ( ); + } + } + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterGladiator::CanTurn +================ +*/ +bool rvMonsterGladiator::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterGladiator::Damage +================ +*/ +void rvMonsterGladiator::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) +{ + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef ) + { + if ( usingShield ) + {//shield up + if ( !damageDef->GetString( "filter_electricity", NULL ) ) + {//not by electricity + //If we get hit enough times with shield up, charge forward + if ( gameLocal.GetTime() - shieldLastHitTime > 1500 ) + { + shieldConsecutiveHits = 0; + } + shieldConsecutiveHits++; + shieldLastHitTime = gameLocal.GetTime(); + if ( shieldConsecutiveHits > 20 && combat.tacticalCurrent != AITACTICAL_MELEE && move.fl.done ) + {//really laying into us, move up + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } + + if ( idStr::Icmp ( GetDamageGroup ( location ), "shield" ) == 0 ) + {//Hit in shield + if ( damageDef->GetString( "filter_electricity", NULL ) ) + {//by electricity + shieldHealth -= damageDef->GetInt( "damage" ) * damageScale; + if ( shield ) + { + shield->SetShaderParm( SHADERPARM_MODE, gameLocal.GetTime() + gameLocal.random.RandomInt(1000) + 1000 ); + } + StartSound( "snd_shield_flicker", SND_CHANNEL_ANY, 0, false, NULL ); + if ( shieldHealth <= 0 ) + {//drop it + HideShield( gameLocal.random.RandomInt(3000)+2000 );//FIXME: when it does come back on, flicker back on? + painAnim = "pain_con"; + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + combat.shotAtTime = gameLocal.GetTime(); + } + return; + } + } + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + if ( aifl.pain ) + {//hurt + if ( usingShield ) + {//shield up + //move in! + combat.tacticalUpdateTime = gameLocal.GetTime(); + MoveToEnemy(); + //reset counter + shieldConsecutiveHits = 0; + } + } +} + +/* +================ +rvMonsterGladiator::Save +================ +*/ +void rvMonsterGladiator::Save( idSaveGame *savefile ) const { + actionRailgunAttack.Save ( savefile ) ; + + savefile->WriteInt ( shots ); + savefile->WriteInt ( lastShotTime ); + + savefile->WriteBool ( usingShield ); + shield.Save( savefile ); + savefile->WriteInt ( shieldStartTime ); + savefile->WriteInt ( shieldWaitTime ); + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shieldConsecutiveHits ); + savefile->WriteInt ( shieldLastHitTime ); + + savefile->WriteInt ( railgunHealth ); + savefile->WriteInt ( railgunDestroyedTime ); + savefile->WriteInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Save( savefile ); +} + +/* +================ +rvMonsterGladiator::Restore +================ +*/ +void rvMonsterGladiator::Restore( idRestoreGame *savefile ) { + actionRailgunAttack.Restore ( savefile ) ; + + savefile->ReadInt ( shots ); + savefile->ReadInt ( lastShotTime ); + savefile->ReadBool ( usingShield ); + shield.Restore( savefile ); + savefile->ReadInt ( shieldStartTime ); + savefile->ReadInt ( shieldWaitTime ); + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shieldConsecutiveHits ); + savefile->ReadInt ( shieldLastHitTime ); + + savefile->ReadInt ( railgunHealth ); + savefile->ReadInt ( railgunDestroyedTime ); + savefile->ReadInt ( nextTurnTime ); // cnicholson: added unsaved var + mPostWeaponDestroyed.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGladiator::GetDebugInfo +================ +*/ +void rvMonsterGladiator::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_RailgunAttack", aiActionStatusString[actionRailgunAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGladiator ) + STATE ( "State_Killed", rvMonsterGladiator::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterGladiator::State_Torso_BlasterAttack ) + STATE ( "Torso_RailgunAttack", rvMonsterGladiator::State_Torso_RailgunAttack ) + STATE ( "Torso_ShieldStart", rvMonsterGladiator::State_Torso_ShieldStart ) + STATE ( "Torso_ShieldEnd", rvMonsterGladiator::State_Torso_ShieldEnd ) + + STATE ( "Torso_TurnRight90", rvMonsterGladiator::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterGladiator::State_Torso_TurnLeft90 ) + STATE ( "Torso_ShieldFire", rvMonsterGladiator::State_Torso_ShieldFire ) + +END_CLASS_STATES + +/* +================ +rvMonsterGladiator::State_Killed +================ +*/ +stateResult_t rvMonsterGladiator::State_Killed ( const stateParms_t& parms ) { + HideShield ( ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldStart +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldStart ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + ShowShield ( ); + PlayAnim ( ANIMCHANNEL_TORSO, "start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + SetShaderParm ( 6, 0 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldEnd +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldEnd ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) //anim done + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "shield_end" ) ) {//anim changed + HideShield ( 2000 ); + actionRailgunAttack.timer.Reset( actionTime ); + actionMeleeAttack.timer.Reset( actionTime ); + actionRangedAttack.timer.Reset( actionTime ); + actionTimerRangedAttack.Reset( actionTime ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_start", parms.blendFrames ); + //shots = 4; + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_start" ) ) {//anim changed + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + if ( --shots <= 0 || !enemy.fl.inFov || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "blaster_loop" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGladiator::State_Torso_RailgunAttack +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_RailgunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( usingShield ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ShieldEnd", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RailgunAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "railgun_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "railgun_attack" ) ) {//anim changed + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_right" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving + || AnimDone ( ANIMCHANNEL_TORSO, 0 ) + || animator.CurrentAnim( ANIMCHANNEL_TORSO )->GetEndTime() < 0//anim somehow cycled?!!! + || idStr::Icmp( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_left" ) ) {//anim changed + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterGladiator::State_Torso_ShieldFire +================ +*/ +stateResult_t rvMonsterGladiator::State_Torso_ShieldFire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + PlayCycle( ANIMCHANNEL_TORSO, "walk_aim", 1 ); + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + Attack( "blaster", animator.GetJointHandle( "lft_wrist_jt"), GetEnemy() ); + PlayEffect( "fx_blaster_flash", animator.GetJointHandle("lft_wrist_jt") ); + lastShotTime = gameLocal.GetTime(); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( move.fl.done ) + { + return SRESULT_DONE; + } + if ( (gameLocal.GetTime()-lastShotTime) >= 250 ) { + shots--; + if ( GetEnemy() && shots > 0 ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } + PlayCycle( ANIMCHANNEL_TORSO, "walk", 1 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_Grunt.cpp b/src/mpgame/ai/Monster_Grunt.cpp new file mode 100644 index 0000000..894e2c4 --- /dev/null +++ b/src/mpgame/ai/Monster_Grunt.cpp @@ -0,0 +1,341 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGrunt : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGrunt ); + + rvMonsterGrunt ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void AdjustHealthByDamage ( int damage ); + +protected: + + rvAIAction actionMeleeMoveAttack; + rvAIAction actionChaingunAttack; + + virtual bool CheckActions ( void ); + + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + virtual void OnDeath ( void ); + +private: + + int standingMeleeNoAttackTime; + int rageThreshold; + + void RageStart ( void ); + void RageStop ( void ); + + // Torso States + stateResult_t State_Torso_Enrage ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_LeapAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGrunt ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGrunt ) +END_CLASS + +/* +================ +rvMonsterGrunt::rvMonsterGrunt +================ +*/ +rvMonsterGrunt::rvMonsterGrunt ( void ) { + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterGrunt::Spawn +================ +*/ +void rvMonsterGrunt::Spawn ( void ) { + rageThreshold = spawnArgs.GetInt ( "health_rageThreshold" ); + + // Custom actions + actionMeleeMoveAttack.Init ( spawnArgs, "action_meleeMoveAttack", NULL, AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", NULL, AIACTIONF_ATTACK ); + actionLeapAttack.Init ( spawnArgs, "action_leapAttack", "Torso_LeapAttack", AIACTIONF_ATTACK ); + + // Enraged to start? + if ( spawnArgs.GetBool ( "preinject" ) ) { + RageStart ( ); + } +} + +/* +================ +rvMonsterGrunt::Save +================ +*/ +void rvMonsterGrunt::Save ( idSaveGame *savefile ) const { + actionMeleeMoveAttack.Save( savefile ); + actionChaingunAttack.Save( savefile ); + + savefile->WriteInt( rageThreshold ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::Restore +================ +*/ +void rvMonsterGrunt::Restore ( idRestoreGame *savefile ) { + actionMeleeMoveAttack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + savefile->ReadInt( rageThreshold ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterGrunt::RageStart +================ +*/ +void rvMonsterGrunt::RageStart ( void ) { + SetShaderParm ( 6, 1 ); + + // Disable non-rage actions + actionEvadeLeft.fl.disabled = true; + actionEvadeRight.fl.disabled = true; + + // Speed up animations + animator.SetPlaybackRate ( 1.25f ); + + // Disable pain + pain.threshold = 0; + + // Start over with health when enraged + health = spawnArgs.GetInt ( "health" ); + + // No more going to rage + rageThreshold = 0; +} + +/* +================ +rvMonsterGrunt::RageStop +================ +*/ +void rvMonsterGrunt::RageStop ( void ) { + SetShaderParm ( 6, 0 ); +} + +/* +================ +rvMonsterGrunt::CheckActions +================ +*/ +bool rvMonsterGrunt::CheckActions ( void ) { + // If our health is below the rage threshold then enrage + if ( health < rageThreshold ) { + PerformAction ( "Torso_Enrage", 4, true ); + return true; + } + + // Moving melee attack? + if ( PerformAction ( &actionMeleeMoveAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + + // Default actions + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + //allow ranged attack + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + return false; +} + +/* +================ +rvMonsterGrunt::OnDeath +================ +*/ +void rvMonsterGrunt::OnDeath ( void ) { + RageStop ( ); + return idAI::OnDeath ( ); +} + +/* +================ +rvMonsterGrunt::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGrunt::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionRangedAttack.fl.disabled = true; + break; + + default: + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +===================== +rvMonsterGrunt::AdjustHealthByDamage +===================== +*/ +void rvMonsterGrunt::AdjustHealthByDamage ( int damage ) { + // Take less damage during enrage process + if ( rageThreshold && health < rageThreshold ) { + health -= (damage * 0.25f); + return; + } + return idAI::AdjustHealthByDamage ( damage ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGrunt ) + STATE ( "Torso_Enrage", rvMonsterGrunt::State_Torso_Enrage ) + STATE ( "Torso_Pain", rvMonsterGrunt::State_Torso_Pain ) + STATE ( "Torso_LeapAttack", rvMonsterGrunt::State_Torso_LeapAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGrunt::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Pain ( const stateParms_t& parms ) { + // Stop streaming pain if its time to get angry + if ( pain.loopEndTime && health < rageThreshold ) { + pain.loopEndTime = 0; + } + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterGrunt::State_Torso_Enrage +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_Enrage ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "anger", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + RageStart ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGrunt::State_Torso_LeapAttack +================ +*/ +stateResult_t rvMonsterGrunt::State_Torso_LeapAttack ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + lastAttackTime = 0; + // Play the action animation + PlayAnim ( ANIMCHANNEL_TORSO, animator.GetAnim ( actionAnimNum )->FullName ( ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + // If we missed our leap attack get angry + if ( !lastAttackTime && rageThreshold ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Enrage", parms.blendFrames ); + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_Gunner.cpp b/src/mpgame/ai/Monster_Gunner.cpp new file mode 100644 index 0000000..18bee6d --- /dev/null +++ b/src/mpgame/ai/Monster_Gunner.cpp @@ -0,0 +1,471 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterGunner : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterGunner ); + + rvMonsterGunner ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + int shots; + int shotsFired; + idStr nailgunPrefix; + int nailgunMinShots; + int nailgunMaxShots; + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + +private: + + // Actions + rvAIAction actionGrenadeAttack; + rvAIAction actionNailgunAttack; + + rvAIAction actionSideStepLeft; + rvAIAction actionSideStepRight; + rvAIActionTimer actionTimerSideStep; + + bool CheckAction_SideStepLeft ( rvAIAction* action, int animNum ); + bool CheckAction_SideStepRight ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_NailgunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterGunner ); +}; + +CLASS_DECLARATION( idAI, rvMonsterGunner ) +END_CLASS + +/* +================ +rvMonsterGunner::rvMonsterGunner +================ +*/ +rvMonsterGunner::rvMonsterGunner ( ) { + nextShootTime = 0; +} + + +void rvMonsterGunner::InitSpawnArgsVariables( void ) +{ + nailgunMinShots = spawnArgs.GetInt ( "action_nailgunAttack_minshots", "5" ); + nailgunMaxShots = spawnArgs.GetInt ( "action_nailgunAttack_maxshots", "20" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.3" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterGunner::Spawn +================ +*/ +void rvMonsterGunner::Spawn ( void ) { + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + actionNailgunAttack.Init ( spawnArgs, "action_nailgunAttack", "Torso_NailgunAttack", AIACTIONF_ATTACK ); + actionSideStepLeft.Init ( spawnArgs, "action_sideStepLeft", NULL, 0 ); + actionSideStepRight.Init ( spawnArgs, "action_sideStepRight", NULL, 0 ); + actionTimerSideStep.Init ( spawnArgs, "actionTimer_sideStep" ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterGunner::Save +================ +*/ +void rvMonsterGunner::Save ( idSaveGame *savefile ) const { + actionGrenadeAttack.Save ( savefile ); + actionNailgunAttack.Save ( savefile ); + actionSideStepLeft.Save ( savefile ); + actionSideStepRight.Save ( savefile ); + actionTimerSideStep.Save ( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + savefile->WriteString ( nailgunPrefix ); + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterGunner::Restore +================ +*/ +void rvMonsterGunner::Restore ( idRestoreGame *savefile ) { + actionGrenadeAttack.Restore ( savefile ); + actionNailgunAttack.Restore ( savefile ); + actionSideStepLeft.Restore ( savefile ); + actionSideStepRight.Restore ( savefile ); + actionTimerSideStep.Restore ( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + savefile->ReadString ( nailgunPrefix ); + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterGunner::OnStopMoving +============ +*/ +void rvMonsterGunner::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + {//hiding + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE || enemy.range <= combat.meleeRange ) + {//in melee state or in melee range + actionMeleeAttack.timer.Clear( actionTime ); + } + else if ( (!actionNailgunAttack.timer.IsDone(actionTime) || !actionTimerRangedAttack.IsDone(actionTime)) + && (!actionGrenadeAttack.timer.IsDone(actionTime) || !actionTimerSpecialAttack.IsDone(actionTime)) ) + {//no attack is ready + //Ready at least one of them + if ( gameLocal.random.RandomInt(3) ) + { + actionNailgunAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + } + else + { + actionGrenadeAttack.timer.Clear( actionTime ); + actionTimerSpecialAttack.Clear( actionTime ); + } + } + } +} + +/* +================ +rvMonsterGunner::UpdateRunStatus +================ +*/ +bool rvMonsterGunner::UpdateRunStatus ( void ) { + move.fl.idealRunning = false; + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterGunner::FilterTactical +================ +*/ +int rvMonsterGunner::FilterTactical ( int availableTactical ) { + if ( !move.fl.moving && enemy.range > combat.meleeRange ) + {//keep moving! + if ( (!actionNailgunAttack.timer.IsDone(actionTime+500) || !actionTimerRangedAttack.IsDone(actionTime+500)) + && (!actionGrenadeAttack.timer.IsDone(actionTime+500) || !actionTimerSpecialAttack.IsDone(actionTime+500)) ) + {//won't be attacking in the next 1 second + combat.tacticalUpdateTime = 0; + availableTactical |= (AITACTICAL_MELEE_BIT); + if ( !gameLocal.random.RandomInt(2) ) + { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterGunner::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterGunner::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + //walk for at least 2 seconds (default update time of 500 is too short) + combat.tacticalUpdateTime = gameLocal.GetTime() + 2000 + gameLocal.random.RandomInt(1000); + break; + } +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepLeft +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepLeft ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the left? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckAction_SideStepRight +================ +*/ +bool rvMonsterGunner::CheckAction_SideStepRight ( rvAIAction* action, int animNum ) { + if ( animNum == -1 ) { + return false; + } + idVec3 moveVec; + TestAnimMove ( animNum, NULL, &moveVec ); + //NOTE: should we care if we can't walk all the way to the right? + int attAnimNum = -1; + if ( actionNailgunAttack.anims.Num ( ) ) { + // Pick a random animation from the list + attAnimNum = GetAnim ( ANIMCHANNEL_TORSO, actionNailgunAttack.anims[gameLocal.random.RandomInt(actionNailgunAttack.anims.Num())] ); + } + if ( attAnimNum != -1 && !CanHitEnemyFromAnim( attAnimNum, moveVec ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterGunner::CheckActions +================ +*/ +bool rvMonsterGunner::CheckActions ( void ) { + // Fire a grenade? + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) || + PerformAction ( &actionNailgunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + bool action = idAI::CheckActions( ); + if ( !action ) { + //try a strafe + if ( GetEnemy() && enemy.fl.visible && gameLocal.GetTime()-lastAttackTime > actionTimerRangedAttack.GetRate()+1000 ) { + //we can see our enemy but haven't been able to shoot him in a while... + if ( PerformAction ( &actionSideStepLeft, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepLeft, &actionTimerSideStep ) + || PerformAction ( &actionSideStepRight, (checkAction_t)&rvMonsterGunner::CheckAction_SideStepRight, &actionTimerSideStep ) ) { + return true; + } + } + } + return action; +} + +/* +===================== +rvMonsterGunner::GetDebugInfo +===================== +*/ +void rvMonsterGunner::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_grenadeAttack", aiActionStatusString[actionGrenadeAttack.status], userData ); + proc ( "idAI", "action_nailgunAttack", aiActionStatusString[actionNailgunAttack.status], userData ); + proc ( "idAI", "actionSideStepLeft", aiActionStatusString[actionSideStepLeft.status], userData ); + proc ( "idAI", "actionSideStepRight", aiActionStatusString[actionSideStepRight.status], userData ); +} + +bool rvMonsterGunner::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + actionTimerRangedAttack.Clear( actionTime ); + actionNailgunAttack.timer.Clear( actionTime ); + return (idAI::Pain( inflictor, attacker, damage, dir, location )); +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterGunner ) + STATE ( "Torso_NailgunAttack", rvMonsterGunner::State_Torso_NailgunAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterGunner::State_Torso_MovingRangedAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterGunner::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "nail", attackJoint, GetEnemy() ); + PlayEffect( "fx_nail_flash", attackJoint ); + } + StartSound( "snd_nailgun_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= nailgunMinShots) || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterGunner::State_Torso_NailgunAttack +================ +*/ +stateResult_t rvMonsterGunner::State_Torso_NailgunAttack ( const stateParms_t& parms ) { + static const char* nailgunAnims [ ] = { "nailgun_short", "nailgun_long" }; + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && !actionNailgunAttack.fl.overrideLegs && FacingIdeal() && !gameLocal.random.RandomInt(1) ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + shots = (gameLocal.random.RandomInt ( nailgunMaxShots - nailgunMinShots ) + nailgunMinShots) * combat.aggressiveScale; + DisableAnimState ( ANIMCHANNEL_LEGS ); + shotsFired = 0; + nailgunPrefix = nailgunAnims[shots%2]; + if ( !CanHitEnemyFromAnim( GetAnim( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ) ) ) ) + {//this is hacky, but we really need to test the attack anim first since they're so different + //can't hit with this one, just use the other one... + nailgunPrefix = nailgunAnims[(shots+1)%2]; + } + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_start", nailgunPrefix.c_str() ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_loop", nailgunPrefix.c_str() ), 0 ); + shotsFired++; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (shotsFired >= nailgunMinShots && !enemy.fl.inFov) || aifl.damage ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("%s_end", nailgunPrefix.c_str() ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_Harvester.cpp b/src/mpgame/ai/Monster_Harvester.cpp new file mode 100644 index 0000000..875454f --- /dev/null +++ b/src/mpgame/ai/Monster_Harvester.cpp @@ -0,0 +1,1214 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ +Monster_Fatguy.cpp + +AI for the fat guy on the putra level +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +class rvMonsterHarvester : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvester ); + + rvMonsterHarvester ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + + virtual bool UpdateAnimationControllers ( void ); + bool CanTurn ( void ) const; + virtual int GetDamageForLocation ( int damage, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + enum { + WHIP_LEFT, + WHIP_CENTER, + WHIP_RIGHT, + WHIP_MAX + }; + + enum { + PART_ARM_R, + PART_ARM_L, + PART_LEG_FR, + PART_LEG_FL, + PART_LEG_BR, + PART_LEG_BL, + PART_TANK_R, + PART_TANK_L, + PARTS_MAX + }; + idStr partLocation[PARTS_MAX]; + idStr partSurf[PARTS_MAX]; + idStr partJoint[PARTS_MAX]; + int partHealth[PARTS_MAX]; + void DestroyPart ( int part ); + + rvAIAction actionWhipAttack; + rvAIAction actionSprayAttack; + rvAIAction actionRocketAttack; + rvAIAction actionGrenadeAttack; + + jointHandle_t whipJoints[WHIP_MAX]; + idEntityPtr whipProjectiles[WHIP_MAX]; + + jointHandle_t jointLeftMuzzle; + jointHandle_t jointRightMuzzle; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + const char* GetMeleeAttackAnim ( const idVec3& target ); + bool PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ); + const char* GetRangedAttackAnim ( const idVec3& target ); + bool PlayRangedAttackAnim ( const idVec3& target, int blendFrames ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + + void DropLeg ( int part ); +// Custom actions + bool CheckAction_WhipAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_MeleeAttack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RocketAttack( rvAIAction* action, int animNum ); + bool CheckAction_GrenadeAttack( rvAIAction* action, int animNum ); + + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_WhipAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ClawAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvester ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvester ) +END_CLASS + +/* +================ +rvMonsterHarvester::rvMonsterHarvester +================ +*/ +rvMonsterHarvester::rvMonsterHarvester ( void ) { +} + +void rvMonsterHarvester::InitSpawnArgsVariables( void ) +{ + whipJoints[WHIP_LEFT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_left" ) ); + whipJoints[WHIP_RIGHT] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_right" ) ); + whipJoints[WHIP_CENTER] = animator.GetJointHandle ( spawnArgs.GetString ( "joint_whip_center" ) ); + + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partLocation[part] = spawnArgs.GetString( va("part_%d_location",part), "" ); + partSurf[part] = spawnArgs.GetString( va("part_%d_surf",part), "" ); + partJoint[part] = spawnArgs.GetString( va("part_%d_joint",part), "" ); + } + + jointLeftMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_left_arm" ) ); + jointRightMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_muzzle_right_arm" ) ); +} +/* +================ +rvMonsterHarvester::Spawn +================ +*/ +void rvMonsterHarvester::Spawn ( void ) { + // Custom actions + actionWhipAttack.Init ( spawnArgs, "action_whipAttack", "Torso_WhipAttack", AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionGrenadeAttack.Init ( spawnArgs, "action_grenadeAttack", NULL, AIACTIONF_ATTACK ); + + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + whipProjectiles[i] = NULL; + } + + InitSpawnArgsVariables(); + shots = 0; + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + partHealth[part] = spawnArgs.GetInt( va("part_%d_health",part), "500" ); + } +} + +/* +================ +rvMonsterHarvester::Save +================ +*/ +void rvMonsterHarvester::Save ( idSaveGame *savefile ) const { + actionWhipAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionGrenadeAttack.Save( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->WriteObject( whipProjectiles[i] ); + } + + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->WriteInt( partHealth[part] ); + } +} + +/* +================ +rvMonsterHarvester::Restore +================ +*/ +void rvMonsterHarvester::Restore ( idRestoreGame *savefile ) { + actionWhipAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionGrenadeAttack.Restore( savefile ); + + int i; + for ( i = 0; i < WHIP_MAX; i++ ) { + savefile->ReadObject( reinterpret_cast( whipProjectiles[i] ) ); + } + + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + for ( int part = 0; part < PARTS_MAX; part++ ) + { + savefile->ReadInt( partHealth[part] ); + } + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterHarvester::UpdateAnimationControllers +================ +*/ +bool rvMonsterHarvester::UpdateAnimationControllers ( void ) { + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + idVec3 target; + + if ( !idAI::UpdateAnimationControllers ( ) ) { + return false; + } + + return true; +} + +/* +===================== +rvMonsterHarvester::SkipImpulse +===================== +*/ +bool rvMonsterHarvester::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +void rvMonsterHarvester::DropLeg( int part ) +{ + jointHandle_t joint = INVALID_JOINT; + const char* legDef = NULL; + switch ( part ) + { + case PART_LEG_FR: + joint = animator.GetJointHandle( "r_toe_front" ); + legDef = "def_leg_part1"; + break; + case PART_LEG_FL: + joint = animator.GetJointHandle( "l_toe_front" ); + legDef = "def_leg_part2"; + break; + case PART_LEG_BR: + joint = animator.GetJointHandle( "r_toe_back" ); + legDef = "def_leg_part4"; + break; + case PART_LEG_BL: + joint = animator.GetJointHandle( "l_toe_back" ); + legDef = "def_leg_part3"; + break; + } + if ( joint != INVALID_JOINT ) + { + idEntity* leg = gameLocal.SpawnEntityDef( spawnArgs.GetString( legDef ) ); + if ( leg ) + { + idVec3 jointOrg; + idMat3 jointAxis; + animator.GetJointTransform( joint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + leg->GetPhysics()->SetOrigin( jointOrg ); + leg->GetPhysics()->SetAxis( jointAxis*renderEntity.axis ); + leg->PlayEffect( "fx_trail", vec3_origin, jointAxis, true, vec3_origin, true ); + if ( leg->IsType( idDamagable::GetClassType() ) ) + {//don't be destroyed for at least 5 seconds + ((idDamagable*)leg)->invincibleTime = gameLocal.GetTime() + 5000; + } + // push it + if ( jointOrg.z > GetPhysics()->GetOrigin().z+20.0f ) + {//leg was blown off while in the air... + jointHandle_t attachJoint = animator.GetJointHandle(partJoint[part].c_str()); + if ( attachJoint != INVALID_JOINT ) + { + animator.GetJointTransform( attachJoint, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = renderEntity.origin + (jointOrg*renderEntity.axis); + + idVec3 impulse = leg->GetPhysics()->GetCenterMass() - jointOrg; + impulse.z = 0; + impulse.Normalize(); + impulse *= ((gameLocal.random.RandomFloat()*3.0f)+2.0f) * 1000;//away + impulse.z = (gameLocal.random.CRandomFloat()*500.0f)+1000.0f;//up! + leg->ApplyImpulse( this, 0, jointOrg, impulse ); + } + } + } + } +} + +void rvMonsterHarvester::DestroyPart( int part ) +{ + idStr explodeFX; + idStr trailFX; + switch ( part ) + { + case PART_ARM_R: + if ( partHealth[PART_ARM_L] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + case PART_ARM_L: + if ( partHealth[PART_ARM_R] <= 0 ) + { + actionRangedAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + //so we don't sit here and do nothing at medium range...? + actionRocketAttack.minRange = actionRangedAttack.minRange; + } + explodeFX = "fx_destroy_part_arm"; + trailFX = "fx_destroy_part_trail_arm"; + break; + //FIXME: spawn leg func_movables + case PART_LEG_FR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_frt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_FL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_flt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BR: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_brt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_LEG_BL: + DropLeg( part ); + actionMeleeAttack.fl.disabled = true; + actionSprayAttack.fl.disabled = true; + animPrefix = "dmg_blt"; + painAnim = "damaged"; + explodeFX = "fx_destroy_part_leg"; + trailFX = "fx_destroy_part_trail_leg"; + break; + case PART_TANK_R: + if ( partHealth[PART_TANK_L] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + case PART_TANK_L: + if ( partHealth[PART_TANK_R] <= 0 ) + { + actionRocketAttack.fl.disabled = true; + //so we don't sit here and do nothing at long range...? + actionRangedAttack.maxRange = actionRocketAttack.maxRange; + } + explodeFX = "fx_destroy_part_tank"; + trailFX = "fx_destroy_part_trail_tank"; + break; + } + HideSurface( partSurf[part].c_str() ); + PlayEffect( explodeFX, animator.GetJointHandle(partJoint[part].c_str()) ); + PlayEffect( trailFX, animator.GetJointHandle(partJoint[part].c_str()), true ); + //make sure it plays this pain + actionTimerPain.Reset ( actionTime ); +} + +/* +================ +rvMonsterHarvester::CanTurn +================ +*/ +bool rvMonsterHarvester::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); +} + +/* +===================== +rvMonsterHarvester::GetDamageForLocation +===================== +*/ +int rvMonsterHarvester::GetDamageForLocation( int damage, int location ) { + // If the part was hit only do damage to it + const char* dmgGroup = GetDamageGroup ( location ); + if ( dmgGroup ) + { + for ( int part = 0; part < PARTS_MAX; part++ ) + { + if ( idStr::Icmp ( dmgGroup, partLocation[part].c_str() ) == 0 ) + { + if ( partHealth[part] > 0 ) + { + partHealth[part] -= damage; + painAnim = "pain"; + if ( partHealth[part] <= 0 ) + { + if ( animPrefix.Length() + && (part == PART_LEG_FR + || part == PART_LEG_FL + || part == PART_LEG_BR + || part == PART_LEG_BL ) ) + {//just blew off a leg and already had one blown off... + DestroyPart( part ); + //we dead + health = 0; + return damage; + } + else + { + //FIXME: big pain? + DestroyPart( part ); + } + } + else + { + if ( !animPrefix.Length() ) + { + switch ( part ) + { + case PART_LEG_FR: + painAnim = "leg_pain_fr"; + break; + case PART_LEG_FL: + painAnim = "leg_pain_fl"; + break; + case PART_LEG_BR: + painAnim = "leg_pain_br"; + break; + case PART_LEG_BL: + painAnim = "leg_pain_bl"; + break; + } + } + } + + if ( pain.threshold < damage && health > 0 ) + //if ( move.anim_turn_angles == 0.0f ) + {//not in the middle of a turn + AnimTurn( 0, true ); + PerformAction ( "Torso_Pain", 2, true ); + } + } + //pain.takenThisFrame = damage; + return 0; + } + } + } + + if ( health <= spawnArgs.GetInt( "death_damage_threshold" ) + && spawnArgs.GetInt( "death_damage_threshold" ) > damage ) + {//doesn't meet the minimum damage requirements to kill us + return 0; + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterHarvester::Damage +================ +*/ +void rvMonsterHarvester::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + //don't take damage from ourselves + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHarvester::CheckAction_SprayAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !CheckFOV( GetEnemy()->GetEyePosition(), 20.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RocketAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RocketAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_GrenadeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_GrenadeAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_WhipAttack +================:: +*/ +bool rvMonsterHarvester::CheckAction_WhipAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_MeleeAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_MeleeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 90 ) ) { + return false; + } + if ( !GetMeleeAttackAnim( enemy.ent->GetEyePosition() ) ) + { + return false; + } + return true; +} + +/* +================ +rvMonsterHarvester::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvester::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( idAI::CheckAction_RangedAttack(action,animNum) && enemy.ent && GetRangedAttackAnim( enemy.ent->GetEyePosition() ) ); +} + +/* +================ +rvMonsterHarvester::CheckActions +================ +*/ +bool rvMonsterHarvester::CheckActions ( void ) { + + // such a dirty hack... I'm not sure what is actually wrong, but somehow nextTurnTime is getting to be a rediculously high number. + // I have some more significant bugs that really need to be solved, so for now, this will have to do. + if ( nextTurnTime > gameLocal.time + 500 ) { + nextTurnTime = gameLocal.time-1; + } + + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.6f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.6f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionWhipAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_WhipAttack, NULL ) ) { + return true; + } + if ( PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_SprayAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_RocketAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionGrenadeAttack, (checkAction_t)&rvMonsterHarvester::CheckAction_GrenadeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterHarvester::FilterTactical +================ +*/ +int rvMonsterHarvester::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT|AITACTICAL_RANGED_BITS|AITACTICAL_MELEE_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvester ) + STATE ( "State_Killed", rvMonsterHarvester::State_Killed ) + STATE ( "State_Dead", rvMonsterHarvester::State_Dead ) + + STATE ( "Torso_WhipAttack", rvMonsterHarvester::State_Torso_WhipAttack ) + STATE ( "Torso_ClawAttack", rvMonsterHarvester::State_Torso_ClawAttack ) + STATE ( "Torso_RangedAttack", rvMonsterHarvester::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvester::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvester::State_Torso_TurnLeft90 ) + STATE ( "Torso_SprayAttack", rvMonsterHarvester::State_Torso_SprayAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHarvester::State_Torso_RocketAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvester::State_Killed +================ +*/ +stateResult_t rvMonsterHarvester::State_Killed ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + int numLegsLost = 0; + for ( int i = PART_LEG_FR; i <= PART_LEG_BL; i++ ) { + if ( partHealth[i] <= 0 ) { + numLegsLost++; + } + } + if ( numLegsLost > 1 ) { + //dmg_death when 2 legs are blown off + PlayAnim( ANIMCHANNEL_TORSO, "dmg_death", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "death", 0 ); + } + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterHarvester::State_Dead +================ +*/ +stateResult_t rvMonsterHarvester::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterHarvester::State_Torso_WhipAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_WhipAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + idEntity* ent; + int i; + + for ( i = 0; i < WHIP_MAX; i ++ ) { + gameLocal.SpawnEntityDef( *gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_whip" ) ), &ent, false ); + idProjectile* proj = dynamic_cast(ent); + if ( !proj ) { + delete ent; + continue; + } + + proj->Create ( this, vec3_origin, idVec3(0,0,1) ); + proj->Launch ( vec3_origin, idVec3(0,0,1), vec3_origin ); + + whipProjectiles[i] = proj; + ent->BindToJoint ( this, whipJoints[i], false ); + ent->SetOrigin ( vec3_origin ); + ent->SetAxis ( mat3_identity ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + int i; + for ( i = 0; i < WHIP_MAX; i ++ ) { + delete whipProjectiles[i]; + whipProjectiles[i] = NULL; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +===================== +rvMonsterHarvester::Attack +===================== +*/ +bool rvMonsterHarvester::Attack ( const char* attackName, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + //NOTE: this stops spawning of projectile, but not muzzle flash... oh well... + if ( joint == jointLeftMuzzle && partHealth[PART_ARM_L] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + if ( joint == jointRightMuzzle && partHealth[PART_ARM_R] <= 0 ) + {//can't fire from this muzzle - arm gone + return false; + } + return idAI::Attack( attackName, joint, target, pushVelocity ); +} + +/* +================ +rvMonsterHarvester::GetMeleeAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetMeleeAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -10.0f ) { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw_rt"; + } else if ( yaw > 10.0f ) { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw_lt"; + } else{ + if ( gameLocal.random.RandomFloat() < 0.5f || partHealth[PART_LEG_FR] <= 0 ) + { + if ( partHealth[PART_LEG_FL] <= 0 ) + { + return false; + } + animName = "attack_lleg_fw"; + } + else + { + if ( partHealth[PART_LEG_FR] <= 0 ) + { + return false; + } + animName = "attack_rleg_fw"; + } + } + return animName; +} +/* +================ +rvMonsterHarvester::PlayMeleeAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayMeleeAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetMeleeAttackAnim( target ); + if ( animName ) + { + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::GetRangedAttackAnim +================ +*/ +const char* rvMonsterHarvester::GetRangedAttackAnim ( const idVec3& target ) { + idVec3 dir; + idVec3 localDir; + float yaw; + const char* animName = NULL; + + // Get the local direction vector + dir = target - GetPhysics()->GetOrigin(); + dir.Normalize ( ); + viewAxis.ProjectVector( dir, localDir ); + + // Get the yaw relative to forward + yaw = idMath::AngleNormalize180 ( localDir.ToAngles ( )[YAW] ); + + if ( yaw < -20.0f ) { + if ( partHealth[PART_ARM_R] <= 0 ) + { + return NULL; + } + animName = "fire_right"; + } else if ( yaw > 20.0f ) { + if ( partHealth[PART_ARM_L] <= 0 ) + { + return NULL; + } + animName = "fire_left"; + } else{ + animName = "fire_forward"; + } + + return animName; +} + +/* +================ +rvMonsterHarvester::PlayRangedAttackAnim +================ +*/ +bool rvMonsterHarvester::PlayRangedAttackAnim ( const idVec3& target, int blendFrames ) { + const char* animName = GetRangedAttackAnim( target ); + if ( animName ) + { + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, animName, blendFrames ); + return true; + } + return false; +} + +/* +================ +rvMonsterHarvester::State_Torso_ClawAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_ClawAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: { + if ( !enemy.ent ) { + return SRESULT_DONE; + } + + // Predict a bit + if ( !PlayMeleeAttackAnim ( enemy.ent->GetEyePosition(), parms.blendFrames ) ) + { + return SRESULT_DONE; + } + + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + } + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent || !PlayRangedAttackAnim ( enemy.ent->GetEyePosition(), 0 ) ) + { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + if ( GetEnemy() && !CheckFOV( GetEnemy()->GetEyePosition(), 270.0f ) ) + {//enemy behind me + if ( actionGrenadeAttack.timer.IsDone(gameLocal.GetTime()) ) + {//timer okay + //toss some nades at him + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("r_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("r_side_can_tip"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("l_side_can_tip") ); + Attack( "grenade", animator.GetJointHandle("l_side_can_base"), enemy.ent ); + PlayEffect( "fx_grenade_muzzleflash", animator.GetJointHandle("back_can_tip") ); + Attack( "grenade", animator.GetJointHandle("back_can_base"), enemy.ent ); + actionGrenadeAttack.timer.Clear( gameLocal.GetTime() + gameLocal.random.RandomInt(1000)+500 ); + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) + { + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvester::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHarvester::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_STAGE ( STAGE_FIRE ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "missile_fire_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "missile_fire_start", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + if ( partHealth[PART_TANK_L] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("l_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("l_hopper_muzzle_flash"), enemy.ent ); + } + if ( partHealth[PART_TANK_R] > 0 ) + { + PlayEffect( "fx_rocket_muzzleflash", animator.GetJointHandle("r_hopper_muzzle_flash") ); + Attack( "rocket", animator.GetJointHandle("r_hopper_muzzle_flash"), enemy.ent ); + } + + if ( animPrefix.Length() ) + {//don't play an anim + return SRESULT_DONE; + } + + PlayAnim ( ANIMCHANNEL_TORSO, "attack_rocket", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) || idStr::Icmp( "attack_rocket", animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName() ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_HarvesterDispersal.cpp b/src/mpgame/ai/Monster_HarvesterDispersal.cpp new file mode 100644 index 0000000..79f2a2f --- /dev/null +++ b/src/mpgame/ai/Monster_HarvesterDispersal.cpp @@ -0,0 +1,418 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ +rvMonsterHarvesterDispersal.cpp + +AI for the Harvester on dispersal +================ +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterHarvesterDispersal : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHarvesterDispersal ); + + rvMonsterHarvesterDispersal ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + rvAIAction actionSprayScream; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + int maxShots; + int minShots; + int shots; + + int nextTurnTime; + int sweepCount; + +private: + +// Custom actions + virtual bool CheckAction_SprayScream( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_RangedAttack( const stateParms_t& parms ); + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + stateResult_t State_SprayScream ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHarvesterDispersal ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHarvesterDispersal ) +END_CLASS + +/* +================ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal +================ +*/ +rvMonsterHarvesterDispersal::rvMonsterHarvesterDispersal ( void ) { +} + +void rvMonsterHarvesterDispersal::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterHarvesterDispersal::Spawn +================ +*/ +void rvMonsterHarvesterDispersal::Spawn ( void ) { + actionSprayScream.Init ( spawnArgs, "action_sprayScream", "State_SprayScream", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterHarvesterDispersal::Save +================ +*/ +void rvMonsterHarvesterDispersal::Save ( idSaveGame *savefile ) const { + actionSprayScream.Save( savefile ); + savefile->WriteInt( nextTurnTime ); + savefile->WriteInt( sweepCount ); + savefile->WriteInt ( shots ); +} + +/* +================ +rvMonsterHarvesterDispersal::Restore +================ +*/ +void rvMonsterHarvesterDispersal::Restore ( idRestoreGame *savefile ) { + actionSprayScream.Restore( savefile ); + savefile->ReadInt( nextTurnTime ); + savefile->ReadInt( sweepCount ); + savefile->ReadInt ( shots ); + + InitSpawnArgsVariables(); +} + +/* +===================== +rvMonsterHarvesterDispersal::SkipImpulse +===================== +*/ +bool rvMonsterHarvesterDispersal::SkipImpulse( idEntity* ent, int id ) { + return true; +} + +/* +================ +rvMonsterHarvesterDispersal::CanTurn +================ +*/ +bool rvMonsterHarvesterDispersal::CanTurn ( void ) const { + return false; + /* + if ( !idAI::CanTurn ( ) ) { + return false; + } + return (move.anim_turn_angles != 0.0f || move.fl.moving); + */ +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_RangedAttack +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + return ( enemy.ent && idAI::CheckAction_RangedAttack( action, animNum ) && IsEnemyRecentlyVisible( ) ); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckAction_SprayScream +================ +*/ +bool rvMonsterHarvesterDispersal::CheckAction_SprayScream ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + return (!IsEnemyRecentlyVisible()); +} + +/* +================ +rvMonsterHarvesterDispersal::CheckActions +================ +*/ +bool rvMonsterHarvesterDispersal::CheckActions ( void ) { + + // If not moving, try turning in place + /* + if ( !move.fl.moving && gameLocal.time > nextTurnTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.8f || (turnYaw > 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.8f || (turnYaw < 0 && GetEnemy() && !enemy.fl.inFov) ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + } + */ + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( idAI::CheckActions() ) { + return true; + } + + if ( PerformAction ( &actionSprayScream, (checkAction_t)&rvMonsterHarvesterDispersal::CheckAction_SprayScream ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterHarvesterDispersal::FilterTactical +================ +*/ +int rvMonsterHarvesterDispersal::FilterTactical ( int availableTactical ) { + return availableTactical & (AITACTICAL_TURRET_BIT); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHarvesterDispersal ) + STATE ( "Torso_RangedAttack", rvMonsterHarvesterDispersal::State_Torso_RangedAttack ) + STATE ( "Torso_TurnRight90", rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ) + STATE ( "State_SprayScream", rvMonsterHarvesterDispersal::State_SprayScream ) +END_CLASS_STATES + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !enemy.ent ) { + return SRESULT_DONE; + } + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + if ( !enemy.ent ) + { + return SRESULT_DONE; + } + if ( !move.fl.moving ) + { + DisableAnimState( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + shots--; + if ( GetEnemy() && !enemy.fl.inFov ) + {//just stop + } + else if ( shots > 0 || !GetEnemy() ) + { + return SRESULT_STAGE ( STAGE_ATTACK ); + } +// animator.ClearAllJoints ( ); +// leftChainOut = false; +// rightChainOut = false; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_rt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_rt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_90_lt", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 ) || !strstr( animator.CurrentAnim( ANIMCHANNEL_TORSO )->AnimName(), "turn_90_lt" ) ) { + AnimTurn ( 0, true ); + nextTurnTime = gameLocal.time + 250; + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHarvesterDispersal::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterHarvesterDispersal::State_SprayScream ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH, + STAGE_SCREAM, + STAGE_FINISH_SCREAM + }; + if ( parms.stage < STAGE_SCREAM && IsEnemyRecentlyVisible() ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + return SRESULT_DONE; + } + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + sweepCount = 0; + lookTarget = GetEnemy(); + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_start", 0 ); + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_loop", 0 ); + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( enemy.fl.inFov && sweepCount < 3 && !gameLocal.random.RandomInt(2) ) { + return SRESULT_STAGE ( STAGE_SWEEP ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, "fire_forward_spray_end", 0 ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam on + PlayAnim ( ANIMCHANNEL_TORSO, "dispersal_vent", 0 ); + return SRESULT_STAGE ( STAGE_FINISH_SCREAM ); + } + return SRESULT_WAIT; + + case STAGE_FINISH_SCREAM: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ActivateTargets( this );//toggle vent steam off + return SRESULT_STAGE ( STAGE_START ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/mpgame/ai/Monster_HeavyHoverTank.cpp b/src/mpgame/ai/Monster_HeavyHoverTank.cpp new file mode 100644 index 0000000..c817635 --- /dev/null +++ b/src/mpgame/ai/Monster_HeavyHoverTank.cpp @@ -0,0 +1,557 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +class rvMonsterHeavyHoverTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterHeavyHoverTank ); + + rvMonsterHeavyHoverTank ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + +protected: + + enum weaponState_t { + WEAPONSTATE_BLASTER, + WEAPONSTATE_ROCKET, + WEAPONSTATE_MAX + }; + + int blasterAnimIndex; + int shots; + weaponState_t weaponStateCurrent; + weaponState_t weaponStateIdeal; + + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionStrafe; + + jointHandle_t jointHoverEffect; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover; + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + +// virtual const char* GetIdleAnimName ( void ); + +private: + + float strafeSpeed; + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_ChangeWeaponState ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterHeavyHoverTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterHeavyHoverTank ) +END_CLASS + +/* +================ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank +================ +*/ +rvMonsterHeavyHoverTank::rvMonsterHeavyHoverTank ( ) { + weaponStateIdeal = WEAPONSTATE_BLASTER; + weaponStateCurrent = weaponStateIdeal; + + effectDust = NULL; + effectHover = NULL; + + blasterAnimIndex = 0; + shots = 0; + + strafeSpeed = 0; +} + +void rvMonsterHeavyHoverTank::InitSpawnArgsVariables( void ) +{ + jointHoverEffect = animator.GetJointHandle ( spawnArgs.GetString("joint_hover") ); + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "300" ); +} +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +void rvMonsterHeavyHoverTank::Spawn ( void ) { + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + + InitSpawnArgsVariables(); + + if ( jointHoverEffect != INVALID_JOINT ) { + effectHover = PlayEffect ( "fx_hover", jointHoverEffect, true ); + } +} + +/* +================ +rvMonsterHeavyHoverTank::Think +================ +*/ +void rvMonsterHeavyHoverTank::Think ( void ) { + idAI::Think ( ); + + // If thinking we should play an effect on the ground under us + if ( jointHoverEffect != INVALID_JOINT ) { + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 128 units down from the hover effect joint + GetJointWorldTransform ( jointHoverEffect, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * 128.0f, CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + } +} + +/* +================ +rvMonsterHeavyHoverTank::Save +================ +*/ +void rvMonsterHeavyHoverTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( blasterAnimIndex ); + savefile->WriteInt ( shots ); + savefile->WriteInt ( (int)weaponStateCurrent ); + savefile->WriteInt ( (int)weaponStateIdeal ); + + actionRocketAttack.Save ( savefile ); + actionBlasterAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + + effectDust.Save ( savefile ); + effectHover.Save ( savefile ); +} + +/* +================ +rvMonsterHeavyHoverTank::Restore +================ +*/ +void rvMonsterHeavyHoverTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( blasterAnimIndex ); + savefile->ReadInt ( shots ); + savefile->ReadInt ( (int&)weaponStateCurrent ); + savefile->ReadInt ( (int&)weaponStateIdeal ); + + actionRocketAttack.Restore ( savefile ); + actionBlasterAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + + effectDust.Restore ( savefile ); + effectHover.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +void rvMonsterHeavyHoverTank::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) +{ + if ( damageScale > 0.0f ) { + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( damageDef && damageDef->GetBool( "vehicle_collision" ) ) { + //push me hard! + float push = idMath::ClampFloat( 250.0f, 500.0f, damageScale*1000.0f ); + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += dir * push; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +================ +rvMonsterHeavyHoverTank::OnDeath +================ +*/ +void rvMonsterHeavyHoverTank::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + if ( effectHover ) { + effectHover->Stop ( ); + effectHover = NULL; + } + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterHeavyHoverTank::CheckAction_Strafe +================ +*/ +bool rvMonsterHeavyHoverTank::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterHeavyHoverTank::Spawn +================ +*/ +bool rvMonsterHeavyHoverTank::CheckActions ( void ) { + if ( weaponStateIdeal != weaponStateCurrent ) { + PerformAction ( "Torso_ChangeWeaponState", 4 ); + return true; + } + + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterHeavyHoverTank::CheckAction_Strafe ) ) + { + return true; + } + return false; +} + +/* +================ +rvMonsterHeavyHoverTank::OnEnemyChange +================ +*/ +void rvMonsterHeavyHoverTank::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } + + if ( enemy.ent->IsType ( rvVehicle::GetClassType() ) ) { + weaponStateIdeal = WEAPONSTATE_ROCKET; + } +} + +/* +================ +rvMonsterHeavyHoverTank::GetIdleAnimName +================ +*/ +/* +const char* rvMonsterHeavyHoverTank::GetIdleAnimName ( void ) { + if ( weaponStateCurrent == WEAPONSTATE_ROCKET ) { + return "rocket_idle"; + } + + return idAI::GetIdleAnimName ( ); +} +*/ + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterHeavyHoverTank ) + STATE ( "Torso_BlasterAttack", rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ) + STATE ( "Torso_RocketAttack", rvMonsterHeavyHoverTank::State_Torso_RocketAttack ) + STATE ( "Torso_ChangeWeaponState", rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ) + STATE ( "Torso_EvadeLeft", rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterHeavyHoverTank::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterHeavyHoverTank::State_Torso_Strafe ) +END_CLASS_STATES + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_BLASTER; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BlasterAttack", parms.blendFrames ); + return SRESULT_DONE; + } + shots = gameLocal.random.RandomInt ( 8 ) + 4; + blasterAnimIndex = (blasterAnimIndex + 1) % 2; + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_preshoot", blasterAnimIndex + 1 ), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_fire", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("blaster_%d_postshoot", blasterAnimIndex + 1 ), 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_RocketAttack +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + weaponStateIdeal = WEAPONSTATE_ROCKET; + if ( weaponStateIdeal != weaponStateCurrent ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_ChangeWeaponState", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_RocketAttack", parms.blendFrames ); + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState +================ +*/ +stateResult_t rvMonsterHeavyHoverTank::State_Torso_ChangeWeaponState ( const stateParms_t& parms ) { + static const char* stateAnims [ WEAPONSTATE_MAX ] [ WEAPONSTATE_MAX ] = { + { NULL, "blaster_to_rocket" }, // WEAPONSTATE_BLASTER + { "rocket_to_blaster", NULL }, // WEAPONSTATE_ROCKET + }; + + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + // No anim for that transition? + if ( stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ] == NULL ) { + return SRESULT_DONE; + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, stateAnims [ weaponStateCurrent ] [ weaponStateIdeal ], parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + weaponStateCurrent = weaponStateIdeal; + switch ( weaponStateCurrent ) { + case WEAPONSTATE_ROCKET: + animPrefix = "rocket"; + break; + + default: + animPrefix = ""; + break; + } + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterHeavyHoverTank::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeRight", parms.blendFrames ); + } else { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_EvadeLeft", parms.blendFrames ); + } + return SRESULT_DONE; +} diff --git a/src/mpgame/ai/Monster_IronMaiden.cpp b/src/mpgame/ai/Monster_IronMaiden.cpp new file mode 100644 index 0000000..261b7ab --- /dev/null +++ b/src/mpgame/ai/Monster_IronMaiden.cpp @@ -0,0 +1,554 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterIronMaiden : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterIronMaiden ); + + rvMonsterIronMaiden ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual int FilterTactical ( int availableTactical ); + +protected: + + int phaseTime; + jointHandle_t jointBansheeAttack; + int enemyStunTime; + + rvAIAction actionBansheeAttack; + + virtual bool CheckActions ( void ); + + void PhaseOut ( void ); + void PhaseIn ( void ); + + virtual void OnDeath ( void ); + +private: + + // Custom actions + bool CheckAction_BansheeAttack ( rvAIAction* action, int animNum ); + bool PerformAction_PhaseOut ( void ); + bool PerformAction_PhaseIn ( void ); + + // Global States + stateResult_t State_Phased ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_PhaseIn ( const stateParms_t& parms ); + stateResult_t State_Torso_PhaseOut ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BansheeAttackDone ( const stateParms_t& parms ); + + // Frame commands + stateResult_t Frame_PhaseIn ( const stateParms_t& parms ); + stateResult_t Frame_PhaseOut ( const stateParms_t& parms ); + stateResult_t Frame_BansheeAttack ( const stateParms_t& parms ); + stateResult_t Frame_EndBansheeAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterIronMaiden ); +}; + +CLASS_DECLARATION( idAI, rvMonsterIronMaiden ) +END_CLASS + +/* +================ +rvMonsterIronMaiden::rvMonsterIronMaiden +================ +*/ +rvMonsterIronMaiden::rvMonsterIronMaiden ( void ) { + enemyStunTime = 0; + phaseTime = 0; +} + +void rvMonsterIronMaiden::InitSpawnArgsVariables ( void ) { + // Cache the mouth joint + jointBansheeAttack = animator.GetJointHandle ( spawnArgs.GetString ( "joint_bansheeAttack", "mouth_effect" ) ); +} +/* +================ +rvMonsterIronMaiden::Spawn +================ +*/ +void rvMonsterIronMaiden::Spawn ( void ) { + // Custom actions + actionBansheeAttack.Init ( spawnArgs, "action_bansheeAttack", "Torso_BansheeAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + PlayEffect ( "fx_dress", animator.GetJointHandle ( spawnArgs.GetString ( "joint_laser", "cog_bone" ) ), true ); +} + +/* +================ +rvMonsterIronMaiden::Save +================ +*/ +void rvMonsterIronMaiden::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( phaseTime ); + savefile->WriteInt ( enemyStunTime ); + + actionBansheeAttack.Save ( savefile ); +} + +/* +================ +rvMonsterIronMaiden::Restore +================ +*/ +void rvMonsterIronMaiden::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( phaseTime ); + savefile->ReadInt ( enemyStunTime ); + + actionBansheeAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterIronMaiden::FilterTactical +================ +*/ +int rvMonsterIronMaiden::FilterTactical ( int availableTactical ) { + // When hidden the iron maiden only uses ranged tactical + if ( fl.hidden ) { + availableTactical &= (AITACTICAL_RANGED_BIT | AITACTICAL_TURRET_BIT); + } + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterIronMaiden::CheckAction_BansheeAttack +================ +*/ +bool rvMonsterIronMaiden::CheckAction_BansheeAttack ( rvAIAction* action, int animNum ) { + return CheckAction_RangedAttack ( action, animNum ); +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseIn +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseIn ( void ) { + if ( !phaseTime ) { + return false; + } + + // Must be out for at least 3 seconds + if ( gameLocal.time - phaseTime < 3000 ) { + return false; + } + + // Phase in after 10 seconds or our movement is done + if ( gameLocal.time - phaseTime > 10000 || move.fl.done ) { + // Make sure we arent in something + if ( CanBecomeSolid ( ) ) { + PerformAction ( "Torso_PhaseIn", 4, true ); + return true; + } + } + + return false; +} + +/* +================ +rvMonsterIronMaiden::PerformAction_PhaseOut +================ +*/ +bool rvMonsterIronMaiden::PerformAction_PhaseOut ( void ) { + // Little randomization + if ( gameLocal.random.RandomFloat ( ) > 0.5f ) { + return false; + } + if ( !enemyStunTime || gameLocal.time - enemyStunTime > 1500 ) { + return false; + } + + PerformAction ( "Torso_PhaseOut", 4, true ); + return true; +} + +/* +================ +rvMonsterIronMaiden::CheckActions +================ +*/ +bool rvMonsterIronMaiden::CheckActions ( void ) { + // When phased the only available action is phase in + if ( phaseTime ) { + if ( PerformAction_PhaseIn ( ) ) { + return true; + } + return false; + } + + if ( PerformAction ( &actionBansheeAttack, (checkAction_t)&rvMonsterIronMaiden::CheckAction_BansheeAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction_PhaseOut ( ) ) { + return true; + } + + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterIronMaiden::PhaseOut +================ +*/ +void rvMonsterIronMaiden::PhaseOut ( void ) { + if ( phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + Hide ( ); + + move.fl.allowHiddenMove = true; + + ProcessEvent ( &EV_BecomeNonSolid ); + + StopMove ( MOVE_STATUS_DONE ); + SetState ( "State_Phased" ); + + // Move away from here, to anywhere + MoveOutOfRange ( this, 500.0f, 150.0f ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); + + phaseTime = gameLocal.time; +} + +/* +================ +rvMonsterIronMaiden::PhaseIn +================ +*/ +void rvMonsterIronMaiden::PhaseIn ( void ) { + if ( !phaseTime ) { + return; + } + + rvClientEffect* effect; + effect = PlayEffect ( "fx_phase", animator.GetJointHandle("cog_bone") ); + if ( effect ) { + effect->Unbind ( ); + } + + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + ProcessEvent ( &AI_BecomeSolid ); + + Show ( ); + +// PlayEffect ( "fx_dress", animator.GetJointHandle ( "cog_bone" ), true ); + + phaseTime = 0; + // Wait for the action that started the phase in to finish, then go back to combat + SetState ( "Wait_Action" ); + PostState ( "State_Combat" ); + + SetShaderParm ( 5, MS2SEC ( gameLocal.time ) ); +} + +/* +================ +rvMonsterIronMaiden::OnDeath +================ +*/ +void rvMonsterIronMaiden::OnDeath ( void ) { + StopSound ( SND_CHANNEL_ITEM, false ); + + // Stop looping effects + StopEffect ( "fx_banshee" ); + StopEffect ( "fx_dress" ); + + idAI::OnDeath( ); +} + +/* +================ +rvMonsterIronMaiden::GetDebugInfo +================ +*/ +void rvMonsterIronMaiden::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo( proc, userData ); + + proc ( "idAI", "action_BansheeAttack", aiActionStatusString[actionBansheeAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterIronMaiden ) + STATE ( "State_Phased", rvMonsterIronMaiden::State_Phased ) + + STATE ( "Torso_PhaseOut", rvMonsterIronMaiden::State_Torso_PhaseOut ) + STATE ( "Torso_PhaseIn", rvMonsterIronMaiden::State_Torso_PhaseIn ) + STATE ( "Torso_BansheeAttack", rvMonsterIronMaiden::State_Torso_BansheeAttack ) + STATE ( "Torso_BansheeAttackDone", rvMonsterIronMaiden::State_Torso_BansheeAttackDone ) + + STATE ( "Frame_PhaseIn", rvMonsterIronMaiden::Frame_PhaseIn ) + STATE ( "Frame_PhaseOut", rvMonsterIronMaiden::Frame_PhaseOut ) + STATE ( "Frame_BansheeAttack", rvMonsterIronMaiden::Frame_BansheeAttack ) + STATE ( "Frame_EndBansheeAttack", rvMonsterIronMaiden::Frame_EndBansheeAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterIronMaiden::State_Phased +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Phased ( const stateParms_t& parms ) { + // If done moving and cant become solid here, move again + if ( move.fl.done ) { + if ( !CanBecomeSolid ( ) ) { + MoveOutOfRange ( this, 300.0f, 150.0f ); + } + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Make sure we keep facing our enemy + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + + // Make sure we are checking actions if we have no tactical move + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseIn ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + STAGE_PHASE, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_in", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_PhaseOut ( const stateParms_t& parms ) { + enum { + STAGE_ANIM, + STAGE_ANIM_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ANIM: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "phase_out", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_ANIM_WAIT ); + + case STAGE_ANIM_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttack ( const stateParms_t& parms ) { + enum { + STAGE_ATTACK, + STAGE_ATTACK_WAIT, + }; + switch ( parms.stage ) { + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "banshee", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_BansheeAttackDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE ( STAGE_ATTACK_WAIT ); + + case STAGE_ATTACK_WAIT: + if ( enemy.ent ) { + TurnToward ( enemy.lastKnownPosition ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterIronMaiden::State_Torso_BansheeAttackDone + +To ensure the movement is enabled after the banshee attack and that the effect is stopped this state +will be posted to be run after the banshe attack finishes. +================ +*/ +stateResult_t rvMonsterIronMaiden::State_Torso_BansheeAttackDone ( const stateParms_t& parms ) { + Frame_EndBansheeAttack ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterIronMaiden::Frame_PhaseIn +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseIn ( const stateParms_t& parms ) { + PhaseIn ( ); + return SRESULT_OK; +} + + +/* +================ +rvMonsterIronMaiden::Frame_PhaseOut +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_PhaseOut ( const stateParms_t& parms ) { + PhaseOut ( ); + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_BansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_BansheeAttack ( const stateParms_t& parms ) { + idVec3 origin; + idMat3 axis; + idEntity* entities[ 1024 ]; + int count; + int i; + + // Get mouth origin + GetJointWorldTransform ( jointBansheeAttack, gameLocal.time, origin, axis ); + + // Find all entities within the banshee attacks radius + count = gameLocal.EntitiesWithinRadius ( origin, actionBansheeAttack.maxRange, entities, 1024 ); + for ( i = 0; i < count; i ++ ) { + idEntity* ent = entities[i]; + if ( !ent || ent == this ) { + continue; + } + + // Must be an actor that takes damage to be affected + if ( !ent->fl.takedamage || !ent->IsType ( idActor::GetClassType() ) ) { + continue; + } + + // Has to be within fov + if ( !CheckFOV ( ent->GetEyePosition ( ) ) ) { + continue; + } + + // Do some damage + idVec3 dir; + dir = origin = ent->GetEyePosition ( ); + dir.NormalizeFast ( ); + ent->Damage ( this, this, dir, spawnArgs.GetString ( "def_banshee_damage" ), 1.0f, 0 ); + + // Cache the last time we stunned our own enemy for the phase out + if ( ent == enemy.ent ) { + enemyStunTime = gameLocal.time; + } + } + + return SRESULT_OK; +} + +/* +================ +rvMonsterIronMaiden::Frame_EndBansheeAttack +================ +*/ +stateResult_t rvMonsterIronMaiden::Frame_EndBansheeAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_banshee" ); + return SRESULT_OK; +} diff --git a/src/mpgame/ai/Monster_LightTank.cpp b/src/mpgame/ai/Monster_LightTank.cpp new file mode 100644 index 0000000..8776a62 --- /dev/null +++ b/src/mpgame/ai/Monster_LightTank.cpp @@ -0,0 +1,633 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterLightTank : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterLightTank ); + + rvMonsterLightTank ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual int GetDamageForLocation ( int damage, int location ); + virtual void DamageFeedback ( idEntity *victim, idEntity *inflictor, int &damage ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + virtual void OnTacticalChange ( aiTactical_t oldTactical ); + + virtual bool UpdateRunStatus ( void ); + + virtual int FilterTactical ( int availableTactical ); + + int flamethrowerHealth; + int chargeDebounce; + void DestroyFlamethrower ( void ); + +private: + + int standingMeleeNoAttackTime; + bool damaged; +// bool damagedMove; + int powerUpStartTime; + + rvAIAction actionFlameThrower; + rvAIAction actionPowerUp; + rvAIAction actionChargeAttack; + + bool CheckAction_PowerUp ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_ChargeAttack ( rvAIAction* action, int animNum ); + + // Global States + stateResult_t State_Killed ( const stateParms_t& parms ); + + // Torso States + stateResult_t State_Torso_FlameThrower ( const stateParms_t& parms ); + stateResult_t State_Torso_FlameThrowerThink ( const stateParms_t& parms ); + stateResult_t State_Torso_Pain ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_PowerUp ( const stateParms_t& parms ); + + rvScriptFuncUtility mPostWeaponDestroyed; // script to run after flamethrower is destroyed + + CLASS_STATES_PROTOTYPE ( rvMonsterLightTank ); +}; + +CLASS_DECLARATION( idAI, rvMonsterLightTank ) +END_CLASS + +/* +================ +rvMonsterLightTank::rvMonsterLightTank +================ +*/ +rvMonsterLightTank::rvMonsterLightTank ( void ) { + damaged = false; + standingMeleeNoAttackTime = 0; +} + +/* +================ +rvMonsterLightTank::Spawn +================ +*/ +void rvMonsterLightTank::Spawn ( void ) { +// damagedThreshold = spawnArgs.GetInt ( "health_damagedThreshold" ); + flamethrowerHealth = spawnArgs.GetInt ( "flamethrowerHealth", "160" ); + chargeDebounce = 0; + + actionFlameThrower.Init ( spawnArgs, "action_flameThrower", "Torso_FlameThrower", AIACTIONF_ATTACK ); + actionPowerUp.Init ( spawnArgs, "action_powerup", "Torso_PowerUp", 0 ); + actionChargeAttack.Init ( spawnArgs, "action_chargeAttack", NULL, AIACTIONF_ATTACK ); + + const char *func; + if ( spawnArgs.GetString( "script_postWeaponDestroyed", "", &func ) ) + { + mPostWeaponDestroyed.Init( func ); + } +} + +/* +================ +rvMonsterLightTank::Save +================ +*/ +void rvMonsterLightTank::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( flamethrowerHealth ); + savefile->WriteInt( chargeDebounce ); + + savefile->WriteBool( damaged ); +// savefile->WriteBool( damagedMove ); + savefile->WriteInt( powerUpStartTime ); + + actionFlameThrower.Save( savefile ); + actionPowerUp.Save( savefile ); + actionChargeAttack.Save( savefile ); + mPostWeaponDestroyed.Save( savefile ); + savefile->WriteInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::Restore +================ +*/ +void rvMonsterLightTank::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( flamethrowerHealth ); + savefile->ReadInt( chargeDebounce ); + + savefile->ReadBool( damaged ); +// savefile->ReadBool( damagedMove ); + savefile->ReadInt( powerUpStartTime ); + + actionFlameThrower.Restore( savefile ); + actionPowerUp.Restore( savefile ); + actionChargeAttack.Restore( savefile ); + mPostWeaponDestroyed.Restore( savefile ); + savefile->ReadInt( standingMeleeNoAttackTime ); +} + +/* +================ +rvMonsterLightTank::FilterTactical +================ +*/ +int rvMonsterLightTank::FilterTactical ( int availableTactical ) { + if ( flamethrowerHealth > 0 ) { + // Only let the light tank use ranged tactical when he is really far from his enemy + if ( !enemy.range || enemy.range < combat.attackRange[1] ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } + } + if ( chargeDebounce > gameLocal.GetTime() ) + {//don't charge again any time soon + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical( availableTactical ); +} + +/* +================ +rvMonsterLightTank::OnTacticalChange + +Enable/Disable the ranged attack based on whether the grunt needs it +================ +*/ +void rvMonsterLightTank::OnTacticalChange ( aiTactical_t oldTactical ) { + switch ( combat.tacticalCurrent ) { + case AITACTICAL_MELEE: + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + break; + + default: + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + break; + } +} + +/* +================ +rvMonsterLightTank::UpdateRunStatus +================ +*/ +bool rvMonsterLightTank::UpdateRunStatus ( void ) { + // If rushing, run + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + move.fl.idealRunning = true; + } + else + { + move.fl.idealRunning = false; + } + + return move.fl.running != move.fl.idealRunning; +} + +/* +================ +rvMonsterLightTank::DestroyFlamethrower +================ +*/ +void rvMonsterLightTank::DestroyFlamethrower ( void ) { + + StopEffect ( "fx_flame_muzzle" ); + //HideSurface ( "models/monsters/light_tank/flamethrower" ); + //GetAFPhysics()->GetBody ( "b_right_forearm" )->SetClipMask ( 0 ); + animator.CollapseJoint( animator.GetJointHandle( "r_smallShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_bigShield_nadeLauncher" ), animator.GetJointHandle( "r_elbo" ) ); + animator.CollapseJoint( animator.GetJointHandle( "r_gun_effect" ), animator.GetJointHandle( "r_elbo" ) ); + flamethrowerHealth = -1; + + + pain.takenThisFrame = pain.threshold; + pain.lastTakenTime = gameLocal.time; +// flamethrowerDestroyedTime = gameLocal.GetTime(); + + // Tweak-out the AI to be more aggressive and more likely to charge? + + PlayEffect ( "fx_destroy_arm", animator.GetJointHandle("r_elbo") ); + PlayEffect ( "fx_destroy_arm_trail", animator.GetJointHandle("r_elbo"), true ); + + DisableAnimState( ANIMCHANNEL_LEGS ); + painAnim = "damaged"; + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Pain" ); + PostAnimState( ANIMCHANNEL_TORSO, "Torso_Idle" ); + + chargeDebounce = 0; + damaged = true; + animPrefix = "damage"; + + actionFlameThrower.fl.disabled = true; + actionRangedAttack.fl.disabled = true; + + combat.attackRange[1] = 200; + combat.aggressiveRange = 400; + + spawnArgs.SetFloat( "action_meleeAttack_rate", 0.3f ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionMeleeAttack.failRate = 200; + actionMeleeAttack.chance = 1.0f; + + combat.tacticalMaskAvailable &= ~(AITACTICAL_RANGED_BITS); + + actionMeleeAttack.timer.Reset( actionTime ); + actionChargeAttack.timer.Reset( actionTime ); + + ExecScriptFunction( mPostWeaponDestroyed ); +} + +/* +===================== +rvMonsterLightTank::Pain +===================== +*/ +bool rvMonsterLightTank::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + bool didPain = idAI::Pain( inflictor, attacker, damage, dir, location ); + if ( move.fl.moving && move.fl.running ) + { + painAnim = "pain_charge"; + } + return didPain; +} + /* +===================== +rvMonsterLightTank::GetDamageForLocation +===================== +*/ +int rvMonsterLightTank::GetDamageForLocation( int damage, int location ) { + // If the flamethrower was hit only do damage to it + if( !damaged && !aifl.dead ) + { + if ( idStr::Icmp ( GetDamageGroup ( location ), "flamethrower" ) == 0 ) { +// pain.takenThisFrame = damage; + if ( flamethrowerHealth > 0 ){ + flamethrowerHealth -= damage; + if ( flamethrowerHealth <= 0 ) { + DestroyFlamethrower(); + } + } + return 0; + } + } + + return idAI::GetDamageForLocation ( damage, location ); +} + +/* +================ +rvMonsterLightTank::DamageFeedback + +callback function for when another entity recieved damage from this entity +================ +*/ +void rvMonsterLightTank::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) { + if ( !damaged ) + { + if ( victim == GetEnemy() && inflictor == this ) + { + if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//okay, get out of melee state for now + chargeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt(3000) + 3000; + } + } + } + + idAI::DamageFeedback( victim, inflictor, damage ); +} + +/* +============ +rvMonsterLightTank::OnStopMoving +============ +*/ +void rvMonsterLightTank::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionFlameThrower.timer.Clear( actionTime ); + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + {//so we don't stand there and look stupid + actionMeleeAttack.timer.Clear( actionTime ); + actionChargeAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterLightTank::CheckAction_PowerUp +================ +*/ +bool rvMonsterLightTank::CheckAction_PowerUp ( rvAIAction* action, int animNum ) +{ + if ( !damaged && combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + if ( health > 20 || gameLocal.time - pain.lastTakenTime < 500 ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeLeft( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_EvadeRight +================ +*/ +bool rvMonsterLightTank::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( damaged || combat.tacticalCurrent == AITACTICAL_MELEE ) + { + return false; + } + return idAI::CheckAction_EvadeRight( action, animNum ); +} + +/* +================ +rvMonsterLightTank::CheckAction_ChargeAttack +================ +*/ +bool rvMonsterLightTank::CheckAction_ChargeAttack ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !CheckFOV ( enemy.ent->GetPhysics()->GetOrigin(), 10 ) ) { + return false; + } + if ( damaged || idStr::Icmp( "run", animator.CurrentAnim(ANIMCHANNEL_TORSO)->AnimName() ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterLightTank::CheckActions +================ +*/ +bool rvMonsterLightTank::CheckActions ( void ) { + if ( PerformAction ( &actionFlameThrower, (checkAction_t)&idAI::CheckAction_RangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionChargeAttack, (checkAction_t)&rvMonsterLightTank::CheckAction_ChargeAttack ) ) { + return true; + } + + if ( CheckPainActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionJumpBack, (checkAction_t)&idAI::CheckAction_JumpBack, &actionTimerEvade ) || + PerformAction ( &actionLeapAttack, (checkAction_t)&idAI::CheckAction_LeapAttack ) ) { + return true; + } else if ( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack ) ) { + standingMeleeNoAttackTime = 0; + return true; + } else { + if ( actionMeleeAttack.status != rvAIAction::STATUS_FAIL_TIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_EXTERNALTIMER + && actionMeleeAttack.status != rvAIAction::STATUS_FAIL_CHANCE ) + {//melee attack fail for any reason other than timer? + if ( combat.tacticalCurrent == AITACTICAL_MELEE && !move.fl.moving ) + {//special case: we're in tactical melee and we're close enough to think we've reached the enemy, but he's just out of melee range! + //allow ranged attack + if ( !standingMeleeNoAttackTime ) + { + standingMeleeNoAttackTime = gameLocal.GetTime(); + } + else if ( standingMeleeNoAttackTime + 2500 < gameLocal.GetTime() ) + {//we've been standing still and not attacking for at least 2.5 seconds, fall back to ranged attack + actionFlameThrower.fl.disabled = false; + actionRangedAttack.fl.disabled = false; + } + } + } + if ( PerformAction ( &actionRangedAttack,(checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + } + if ( PerformAction ( &actionPowerUp, (checkAction_t)&rvMonsterLightTank::CheckAction_PowerUp ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterLightTank ) + STATE ( "State_Killed", rvMonsterLightTank::State_Killed ) + + STATE ( "Torso_FlameThrower", rvMonsterLightTank::State_Torso_FlameThrower ) + STATE ( "Torso_FlameThrowerThink", rvMonsterLightTank::State_Torso_FlameThrowerThink ) + STATE ( "Torso_Pain", rvMonsterLightTank::State_Torso_Pain ) + STATE ( "Torso_RangedAttack", rvMonsterLightTank::State_Torso_RangedAttack ) + STATE ( "Torso_PowerUp", rvMonsterLightTank::State_Torso_PowerUp ) + +END_CLASS_STATES + +/* +================ +rvMonsterLightTank::State_Killed +================ +*/ +stateResult_t rvMonsterLightTank::State_Killed ( const stateParms_t& parms ) { + StopEffect ( "fx_destroy_arm_trail" ); + StopEffect ( "fx_flame_muzzle" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrower +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrower ( const stateParms_t& parms ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Flame effect + PlayEffect ( "fx_flame_muzzle", animator.GetJointHandle ( "gun_effect" ), true ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "flamethrower", parms.blendFrames ); + + // Delay start the flame thrower think to ensure he flames for a minimum time + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FlameThrowerThink", 0, 500 ); + + return SRESULT_DONE; +} + +/* +================ +rvMonsterLightTank::State_Torso_FlameThrowerThink +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_FlameThrowerThink ( const stateParms_t& parms ) { + if ( !enemy.fl.inFov || AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + StopEffect ( "fx_flame_muzzle" ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterLightTank::State_Torso_Pain +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_Pain ( const stateParms_t& parms ) { + + StopEffect ( "fx_flame_muzzle" ); + + // Default pain animation + return idAI::State_Torso_Pain ( parms ); +} + +/* +================ +rvMonsterLightTank::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( !move.fl.moving || !FacingIdeal() ) { + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_megaattack", parms.blendFrames ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", parms.blendFrames ); + } + + return SRESULT_STAGE ( STAGE_FINISH ); + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterLightTank::State_Torso_PowerUp +================ +*/ +stateResult_t rvMonsterLightTank::State_Torso_PowerUp ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_FINISH, + }; + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + //fl.takedamage = false; + powerUpStartTime = gameLocal.GetTime(); + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_start", parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + PlayCycle( ANIMCHANNEL_TORSO, "powerup_loop", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + health++; + if ( health >= spawnArgs.GetInt( "health" ) + || gameLocal.GetTime() - powerUpStartTime > 3875 ) + {//full health or been charging up for 3 full anim loops + PlayAnim ( ANIMCHANNEL_TORSO, "powerup_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FINISH ); + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_NetworkGuardian.cpp b/src/mpgame/ai/Monster_NetworkGuardian.cpp new file mode 100644 index 0000000..8ca4640 --- /dev/null +++ b/src/mpgame/ai/Monster_NetworkGuardian.cpp @@ -0,0 +1,845 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterNetworkGuardian : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterNetworkGuardian ); + + rvMonsterNetworkGuardian ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + + int shots; + int landTime; + + int flagFlying; + + float strafeSpeed; + + //this flag allows the AI to control when it takes off and lands. + bool flagAutopilot; + + int battleStage; + + enum { + FLY_NONE = 0, + FLY_TRANSITION, + FLY_FLYING, + }; + +private: + + rvAIAction actionShotgunRocketAttack; + rvAIAction actionFlyingRangedAttack; + rvAIAction actionFlyingSweepAttack; + rvAIAction actionMeleeAttack; + rvAIAction actionBlasterSweepGround; + rvAIAction actionBlasterAttack; + rvAIAction actionMIRVAttack; + + + + stateResult_t State_Wait_Flying ( const stateParms_t& parms ); + +// stateResult_t State_Dead ( const stateParms_t& parms ); + + // walking melee attacks + + // walking ranged attacks + stateResult_t State_Torso_ShotgunRocket ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterSweepGround ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MIRVAttack ( const stateParms_t& parms ); + + //flying evades + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + //flying ranged attacks + stateResult_t State_Torso_FlyingRanged ( const stateParms_t& parms ); + stateResult_t State_Torso_FlyingSweep ( const stateParms_t& parms ); + + // flying anims + stateResult_t State_Torso_LiftOff ( const stateParms_t& parms ); + stateResult_t State_Torso_Fall ( const stateParms_t& parms ); + // walking turn anims + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + + //force the NG into walking mode. This does not play the landing anim, and will make him fall from the sky. + void Event_ForceWalkMode( void ); + + //These commands change the NG's state, but do so through states and animations. + void Event_ForceLanding( void ); + void Event_ForceTakeoff( void ); + + //toggles the NG between AI controlled flight and script controlled flight + void Event_AllowAutopilot( float f); + + //for staged combat, sets the int value of the battle stage + void Event_SetBattleStage( float f); + + CLASS_STATES_PROTOTYPE ( rvMonsterNetworkGuardian ); +}; + +const idEventDef EV_ForceWalkMode( "forceWalkMode" ); +const idEventDef EV_ForceLanding( "forceLanding" ); +const idEventDef EV_ForceTakeoff( "forceTakeoff" ); +const idEventDef EV_AllowAutopilot( "allowAutopilot", "f" ); +const idEventDef EV_SetBattleStage( "setBattleStage", "f" ); + + +CLASS_DECLARATION( idAI, rvMonsterNetworkGuardian ) + EVENT( EV_ForceWalkMode, rvMonsterNetworkGuardian::Event_ForceWalkMode ) + EVENT( EV_ForceLanding, rvMonsterNetworkGuardian::Event_ForceLanding ) + EVENT( EV_ForceTakeoff, rvMonsterNetworkGuardian::Event_ForceTakeoff ) + EVENT( EV_AllowAutopilot, rvMonsterNetworkGuardian::Event_AllowAutopilot ) + EVENT( EV_SetBattleStage, rvMonsterNetworkGuardian::Event_SetBattleStage ) + +END_CLASS + +/* +================ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian +================ +*/ +rvMonsterNetworkGuardian::rvMonsterNetworkGuardian ( ) { + shots = 0; + landTime = 0; + flagFlying = FLY_NONE; + battleStage = 1; + strafeSpeed = 0; + +} + +/* +================ +rvMonsterNetworkGuardian::Spawn +================ +*/ +void rvMonsterNetworkGuardian::Spawn ( void ) { + + disablePain = true; + flagAutopilot = false; + + strafeSpeed = spawnArgs.GetFloat( "strafeSpeed", "500" ); + + actionShotgunRocketAttack.Init ( spawnArgs, "action_ShotgunRocket", "Torso_ShotgunRocket_Attack", AIACTIONF_ATTACK ); + actionFlyingRangedAttack.Init( spawnArgs, "action_FlyingRangedAttack", "Torso_FlyingRanged_Attack", AIACTIONF_ATTACK ); + actionFlyingSweepAttack.Init( spawnArgs, "action_blasterSweepAirAttack", "Torso_FlyingSweep_Attack", AIACTIONF_ATTACK ); + actionMeleeAttack.Init( spawnArgs, "action_meleeAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterSweepGround.Init( spawnArgs, "action_blasterSweepGroundAttack", "Torso_BlasterSweepGround_Attack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init( spawnArgs, "action_blasterAttack", "Torso_Blaster_Attack", AIACTIONF_ATTACK ); + actionMIRVAttack.Init( spawnArgs, "action_MIRVAttack", "Torso_MIRV_Attack", AIACTIONF_ATTACK ); + +} + + +/* +================ +rvMonsterNetworkGuardian::Save +================ +*/ +void rvMonsterNetworkGuardian::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( shots ); + savefile->WriteInt( landTime ); + savefile->WriteInt( flagFlying ); + savefile->WriteInt( battleStage ); + savefile->WriteBool( flagAutopilot ); + savefile->WriteFloat( strafeSpeed ); + + actionShotgunRocketAttack.Save( savefile ); + actionFlyingRangedAttack.Save( savefile ); + actionFlyingSweepAttack.Save( savefile ); + actionMeleeAttack.Save( savefile ); + actionBlasterSweepGround.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionMIRVAttack.Save( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::Restore +================ +*/ +void rvMonsterNetworkGuardian::Restore ( idRestoreGame *savefile ) +{ + savefile->ReadInt( shots ); + savefile->ReadInt( landTime ); + savefile->ReadInt( flagFlying ); + savefile->ReadInt( battleStage ); + savefile->ReadBool( flagAutopilot ); + savefile->ReadFloat( strafeSpeed ); + + actionShotgunRocketAttack.Restore( savefile ); + actionFlyingRangedAttack.Restore( savefile ); + actionFlyingSweepAttack.Restore( savefile ); + actionMeleeAttack.Restore( savefile ); + actionBlasterSweepGround.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionMIRVAttack.Restore( savefile ); +} + +/* +================ +rvMonsterNetworkGuardian::CheckActions +================ +*/ +bool rvMonsterNetworkGuardian::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) + { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } + else if ( turnYaw < -lookMax[YAW] * 0.75f ) + { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + //if the flight is transitioning, do nothing + if( flagFlying == FLY_TRANSITION ) { + return false; + } + + //this is the autopilot section. + if( flagAutopilot ) { + // If he's been on the ground long enough, fly... + if ( move.moveType == MOVETYPE_ANIM && move.fl.onGround && !flagFlying && gameLocal.time > landTime ) + { + PostState ( "Wait_Flying" ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + return true; + } + else if ( move.moveType == MOVETYPE_FLY && gameLocal.time > landTime ) + { + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + + return true; + } + } + + //Normal actions here ----------- + + //if he's close enough for melee, use melee + if( flagFlying == FLY_NONE) { + if( PerformAction ( &actionMeleeAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + //check for ranged attacks on the ground + if( flagFlying == FLY_NONE ) { + + switch (battleStage) { + + case 1: + if( PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + case 2: + if( PerformAction ( &actionMIRVAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterSweepGround, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionShotgunRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + break; + default: + gameLocal.Error("Bad battleStage '%d' set for Network Guardian.", battleStage); + break; + } + + } + //airborne attack actions + if( (flagFlying == FLY_FLYING) ) { + + if( PerformAction ( &actionEvadeLeft, (checkAction_t)&idAI::CheckAction_EvadeLeft, &actionTimerEvade ) || + PerformAction ( &actionEvadeRight, (checkAction_t)&idAI::CheckAction_EvadeRight, &actionTimerEvade ) || + PerformAction ( &actionFlyingRangedAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionFlyingSweepAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ){ + return true; + } + } + + //No. + //return idAI::CheckActions ( ); + return false; +} + +/* +=============================================================================== + + Events + +=============================================================================== +*/ + +/* +================ +rvMonsterNetworkGuardian::Event_ForceWalkMode +================ +*/ +// forces NG to obey gravity and immediately switch to walking mode. +void rvMonsterNetworkGuardian::Event_ForceWalkMode( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_NONE; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceLanding +================ +*/ +// forces NG play his landing animation. He will not just fall from the sky. +void rvMonsterNetworkGuardian::Event_ForceLanding( void ) { + + SetMoveType ( MOVETYPE_ANIM ); + animPrefix = ""; + move.fl.noGravity = false; + physicsObj.UseFlyMove ( false ); + actionMeleeAttack.fl.disabled = false; + move.fl.allowDirectional = false; + flagFlying = FLY_TRANSITION; + + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Fall", 4 ); + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishAction", 0, SFLAG_ONCLEAR ); + //PostAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_ForceTakeoff +================ +*/ +// forces NG to take off and fly. +void rvMonsterNetworkGuardian::Event_ForceTakeoff( void ) { + + disablePain = true; + actionMeleeAttack.fl.disabled = true; + flagFlying = FLY_TRANSITION; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_LiftOff", 4 ); + move.fl.allowDirectional = true; + SetState ( "Wait_Flying" ); + +} + +/* +================ +rvMonsterNetworkGuardian::Event_AllowAutoPilot +================ +*/ +// toggles the AI autoPilot for deciding when to fly and land. +void rvMonsterNetworkGuardian::Event_AllowAutopilot( float f ) { + + flagAutopilot = f ? true : false; + +} + +/* +================ +rvMonsterNetworkGuardian::Event_SetBattleStage +================ +*/ +// sets the current battle stage. Each stage has different behaviors. +void rvMonsterNetworkGuardian::Event_SetBattleStage( float f ) { + + battleStage = f; + +} +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterNetworkGuardian ) + STATE ( "Wait_Flying", rvMonsterNetworkGuardian::State_Wait_Flying ) + STATE ( "State_Dead", rvMonsterNetworkGuardian::State_Dead ) + + STATE ( "Torso_ShotgunRocket_Attack", rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ) + STATE ( "Torso_BlasterSweepGround_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ) + STATE ( "Torso_Blaster_Attack", rvMonsterNetworkGuardian::State_Torso_BlasterAttack ) + STATE ( "Torso_MIRV_Attack", rvMonsterNetworkGuardian::State_Torso_MIRVAttack) + + STATE ( "Torso_EvadeLeft", rvMonsterNetworkGuardian::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterNetworkGuardian::State_Torso_EvadeRight ) + + STATE ( "Torso_FlyingRanged_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingRanged ) + STATE ( "Torso_FlyingSweep_Attack", rvMonsterNetworkGuardian::State_Torso_FlyingSweep ) + + STATE ( "Torso_LiftOff", rvMonsterNetworkGuardian::State_Torso_LiftOff ) + STATE ( "Torso_Fall", rvMonsterNetworkGuardian::State_Torso_Fall ) + + STATE ( "Torso_TurnRight90", rvMonsterNetworkGuardian::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ) + +END_CLASS_STATES + + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_left", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterNetworkGuardian::State_Torso_EvadeRight ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -strafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + PlayAnim ( ANIMCHANNEL_TORSO, "evade_right", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterNetworkGuardian::State_Torso_ShotgunRocket +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_ShotgunRocket ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "shotgunRocket_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterSweepGround ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_grd", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingSweep +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingSweep ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_spray_air", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_FlyingRanged +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_FlyingRanged ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "flyingRanged_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_MIRVAttack +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_MIRVAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + + switch( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "attack_vert", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + + } + + return SRESULT_ERROR; + +} + + +/* +================ +rvMonsterNetworkGuardian::State_Wait_Flying +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Wait_Flying ( const stateParms_t& parms ) { + if ( move.moveType == MOVETYPE_ANIM ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterNetworkGuardian::State_Dead +================ +*/ +/* +stateResult_t rvMonsterNetworkGuardian::State_Dead ( const stateParms_t& parms ) { + return SRESULT_DONE; +}*/ + +/* +================ +rvMonsterNetworkGuardian::State_Torso_LiftOff +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_LiftOff ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + move.fl.noGravity = true; + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "liftoff", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + SetMoveType ( MOVETYPE_FLY ); + landTime = gameLocal.time + DelayTime ( 5000, 10000 ); + animPrefix = "fly"; + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + SetAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + flagFlying = FLY_FLYING; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterNetworkGuardian::State_Torso_Fall +================ +*/ +stateResult_t rvMonsterNetworkGuardian::State_Torso_Fall ( const stateParms_t& parms ) { + enum { + STAGE_FALLSTART, + STAGE_FALLSTARTWAIT, + STAGE_FALLLOOPWAIT, + STAGE_FALLENDWAIT + }; + switch ( parms.stage ) { + case STAGE_FALLSTART: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_FALLSTARTWAIT ); + + case STAGE_FALLSTARTWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 4 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_loop", 0 ); + return SRESULT_STAGE ( STAGE_FALLLOOPWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLLOOPWAIT: + if ( move.fl.onGround ) { + PlayAnim ( ANIMCHANNEL_TORSO, "fly_descend_end", 0 ); + return SRESULT_STAGE ( STAGE_FALLENDWAIT ); + } + return SRESULT_WAIT; + + case STAGE_FALLENDWAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + // we've landed! determine the next fly time + landTime = gameLocal.time + DelayTime ( 10000, 15000 ); + flagFlying = FLY_NONE; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnRight90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_TurnLeft90 +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +//================ +//rvMonsterNetworkGuardian::State_Torso_BlasterAttack +//================ +stateResult_t rvMonsterNetworkGuardian::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //if flying, do not override legs + if( (flagFlying != FLY_NONE ) && move.fl.moving ) { + DisableAnimState ( ANIMCHANNEL_LEGS ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 12 ) + 8) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_loop", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "attack_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; + +} diff --git a/src/mpgame/ai/Monster_RepairBot.cpp b/src/mpgame/ai/Monster_RepairBot.cpp new file mode 100644 index 0000000..9c32c7d --- /dev/null +++ b/src/mpgame/ai/Monster_RepairBot.cpp @@ -0,0 +1,399 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../client/ClientModel.h" + +class repairBotArm_t { +public: + jointHandle_t joint; + int repairTime; + bool repairing; + rvClientEffectPtr effectRepair; + rvClientEffectPtr effectImpact; + + int periodicEndTime; + + repairBotArm_t () { + periodicEndTime = -1; + } + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvMonsterRepairBot : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterRepairBot ); + + rvMonsterRepairBot ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + int repairEndTime; + float repairEffectDist; + + repairBotArm_t armLeft; + repairBotArm_t armRight; + +private: + + void UpdateRepairs ( repairBotArm_t& arm ); + void StopRepairs ( repairBotArm_t& arm ); + + // Leg states + stateResult_t State_Legs_Move ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_Repair ( const stateParms_t& parms ); + stateResult_t State_TorsoAction_RepairDone ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterRepairBot ); +}; + +CLASS_DECLARATION( idAI, rvMonsterRepairBot ) +END_CLASS + +/* +================ +rvMonsterRepairBot::rvMonsterRepairBot +================ +*/ +rvMonsterRepairBot::rvMonsterRepairBot ( ) { +} + +void rvMonsterRepairBot::InitSpawnArgsVariables( void ) +{ + armLeft.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_left", "l_fx" ) ); + armRight.joint = animator.GetJointHandle ( spawnArgs.GetString ( "joint_arm_right", "r_fx" ) ); + repairEffectDist = spawnArgs.GetFloat( "repairEffectDist", "64" ); +} +/* +================ +rvMonsterRepairBot::Spawn +================ +*/ +void rvMonsterRepairBot::Spawn ( void ) { + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::CheckActions +================ +*/ +bool rvMonsterRepairBot::CheckActions ( void ) { + return false; +} + +/* +================ +rvMonsterRepairBot::OnDeath +================ +*/ +void rvMonsterRepairBot::OnDeath ( void ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), viewAxis ); + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterRepairBot::Save +================ +*/ +void rvMonsterRepairBot::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( repairEndTime ); + + armLeft.Save ( savefile ); + armRight.Save ( savefile ); +} + +/* +================ +rvMonsterRepairBot::Restore +================ +*/ +void rvMonsterRepairBot::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( repairEndTime ); + + armLeft.Restore ( savefile ); + armRight.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterRepairBot::Think +================ +*/ +void rvMonsterRepairBot::Think ( void ) { + idAI::Think ( ); + + // Update repair effects? (dont worry about stopping them, the state ending will do so) + UpdateRepairs ( armLeft ); + UpdateRepairs ( armRight ); +} + +/* +================ +rvMonsterRepairBot::UpdateRepairs +================ +*/ +void rvMonsterRepairBot::UpdateRepairs ( repairBotArm_t& arm ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + if ( arm.joint == INVALID_JOINT ) { + return; + } + + if ( gameLocal.time > repairEndTime ) { + StopRepairs ( arm ) ; + return; + } + + // If the repair time has been crossed we need to start/stop the repairs + if ( gameLocal.time > arm.repairTime ) { + if ( arm.repairing ) { + StopRepairs ( arm ); + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 500 ); + arm.repairing = false; + } else { + arm.repairTime = gameLocal.time + gameLocal.random.RandomInt ( 2500 ); + arm.repairing = true; + } + } + + if ( !arm.repairing ) { + return; + } + + // Left repair effect + GetJointWorldTransform ( arm.joint, gameLocal.time, origin, axis ); + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * repairEffectDist, MASK_SHOT_RENDERMODEL, this ); + + if ( tr.fraction >= 1.0f ) { + StopRepairs ( arm ); + } else { + // Start the repair effect if not already started + if ( !arm.effectRepair ) { + arm.effectRepair = PlayEffect ( "fx_repair", arm.joint, true ); + } + // If the repair effect is running then set its end origin + if ( arm.effectRepair ) { + arm.effectRepair->SetEndOrigin ( tr.endpos ); + } + // Start the impact effect + if ( !arm.effectImpact ) { + arm.effectImpact = PlayEffect ( "fx_repair_impact", tr.endpos, tr.c.normal.ToMat3(), true ); + + } else { + // Calculate the local origin and axis from the given globals + idVec3 localOrigin = (tr.endpos - renderEntity.origin) * renderEntity.axis.Transpose ( ); + idMat3 localAxis = tr.c.normal.ToMat3 ();// * renderEntity.axis.Transpose(); + arm.effectImpact->SetOrigin ( localOrigin ); + arm.effectImpact->SetAxis ( localAxis ); + } + + if( gameLocal.GetTime() > arm.periodicEndTime ) {// FIXME: Seems dumb to keep banging on this if the fx isn't defined. + gameLocal.PlayEffect( spawnArgs, "fx_repair_impact_periodic", tr.endpos, tr.c.normal.ToMat3() ); + arm.periodicEndTime = gameLocal.GetTime() + SEC2MS( rvRandom::flrand(spawnArgs.GetVec2("impact_fx_delay_range", "1 1")) ); + } + } +} + +/* +================ +rvMonsterRepairBot::StopRepairs +================ +*/ +void rvMonsterRepairBot::StopRepairs ( repairBotArm_t& arm ) { + if ( arm.effectImpact ) { + arm.effectImpact->Stop ( ); + arm.effectImpact = NULL; + } + if ( arm.effectRepair ) { + arm.effectRepair->Stop ( ); + arm.effectRepair = NULL; + } + + arm.periodicEndTime = -1; +} + +/* +================ +rvMonsterRepairBot::GetDebugInfo +================ +*/ +void rvMonsterRepairBot::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterRepairBot ) + STATE ( "Legs_Move", rvMonsterRepairBot::State_Legs_Move ) + STATE ( "TorsoAction_Repair", rvMonsterRepairBot::State_TorsoAction_Repair ) + STATE ( "TorsoAction_RepairDone", rvMonsterRepairBot::State_TorsoAction_RepairDone ) +END_CLASS_STATES + +/* +================ +rvMonsterRepairBot::State_Legs_Move +================ +*/ +stateResult_t rvMonsterRepairBot::State_Legs_Move ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_MOVE, + STAGE_MOVE_WAIT, + STAGE_STOP, + STAGE_STOP_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim ( ANIMCHANNEL_LEGS, "idle_to_run", 4 ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + return SRESULT_STAGE ( STAGE_MOVE ); + } + return SRESULT_WAIT; + + case STAGE_MOVE: + PlayCycle ( ANIMCHANNEL_LEGS, "run", 4 ); + return SRESULT_STAGE ( STAGE_MOVE_WAIT ); + + case STAGE_MOVE_WAIT: + if ( !move.fl.moving || !CanMove() ) { + return SRESULT_STAGE ( STAGE_STOP ); + } + return SRESULT_WAIT; + + case STAGE_STOP: + PlayAnim ( ANIMCHANNEL_LEGS, "run_to_idle", 4 ); + return SRESULT_STAGE ( STAGE_STOP_WAIT ); + + case STAGE_STOP_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + PostAnimState ( ANIMCHANNEL_LEGS, "Legs_Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_Repair +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_Repair ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_REPAIR, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayCycle ( ANIMCHANNEL_TORSO, "repair", 4 ); + repairEndTime = gameLocal.time + SEC2MS(scriptedActionEnt->spawnArgs.GetInt ( "duration", "5" ) ); + PostAnimState ( ANIMCHANNEL_TORSO, "TorsoAction_RepairDone", 0, 0, SFLAG_ONCLEAR ); + return SRESULT_STAGE(STAGE_REPAIR); + + case STAGE_REPAIR: + if ( gameLocal.time > repairEndTime ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterRepairBot::State_TorsoAction_RepairDone +================ +*/ +stateResult_t rvMonsterRepairBot::State_TorsoAction_RepairDone ( const stateParms_t& parms ) { + StopRepairs ( armLeft ); + StopRepairs ( armRight ); + + return SRESULT_DONE; +} + +/* +================ +repairBotArm_t::Save +================ +*/ +void repairBotArm_t::Save ( idSaveGame* savefile ) const { + savefile->WriteInt ( repairTime ); + savefile->WriteBool ( repairing ); + effectRepair.Save ( savefile ); + effectImpact.Save ( savefile ); + + savefile->WriteInt ( periodicEndTime ); +} + +/* +================ +repairBotArm_t::Restore +================ +*/ +void repairBotArm_t::Restore ( idRestoreGame* savefile ) { + savefile->ReadInt ( repairTime ); + savefile->ReadBool ( repairing ); + effectRepair.Restore ( savefile ); + effectImpact.Restore ( savefile ); + + savefile->ReadInt ( periodicEndTime ); +} diff --git a/src/mpgame/ai/Monster_Scientist.cpp b/src/mpgame/ai/Monster_Scientist.cpp new file mode 100644 index 0000000..b63e776 --- /dev/null +++ b/src/mpgame/ai/Monster_Scientist.cpp @@ -0,0 +1,99 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterScientist : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterScientist ); + + rvMonsterScientist ( void ); + + void Spawn ( void ); + + virtual void OnDeath ( void ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +private: + + CLASS_STATES_PROTOTYPE ( rvMonsterScientist ); +}; + +CLASS_DECLARATION( idAI, rvMonsterScientist ) +END_CLASS + +/* +================ +rvMonsterScientist::rvMonsterScientist +================ +*/ +rvMonsterScientist::rvMonsterScientist ( void ) { +} + +/* +================ +rvMonsterScientist::Spawn +================ +*/ +void rvMonsterScientist::Spawn ( void ) { + PlayEffect ( "fx_fly", animator.GetJointHandle ( "effects_bone" ), true ); +} + +/* +================ +rvMonsterScientist::OnDeath +================ +*/ +void rvMonsterScientist::OnDeath ( void ) { + StopEffect ( "fx_fly" ); + + idAI::OnDeath ( ); +} + +/* +================ +rvMonsterScientist::GetDebugInfo +================ +*/ +void rvMonsterScientist::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterScientist ) +END_CLASS_STATES diff --git a/src/mpgame/ai/Monster_Sentry.cpp b/src/mpgame/ai/Monster_Sentry.cpp new file mode 100644 index 0000000..6588823 --- /dev/null +++ b/src/mpgame/ai/Monster_Sentry.cpp @@ -0,0 +1,435 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSentry : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSentry ); + + rvMonsterSentry ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + // Add some dynamic externals for debugging + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + rvAIAction actionBlasterAttack; + rvAIAction actionKamakaziAttack; + rvAIAction actionCircleStrafe; + + int shots; + int minShots; + int maxShots; + int kamakaziHealth; + int strafeTime; + bool strafeRight; + + virtual bool CheckActions ( void ); + virtual int FilterTactical ( int availableTactical ); + + virtual void OnDeath ( void ); + + void Explode ( bool force = false ); + +private: + + int nextChatterTime; + + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + + // Torso States + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_KamakaziAttack ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Action_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); + stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSentry ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSentry ) +END_CLASS + +/* +================ +rvMonsterSentry::rvMonsterSentry +================ +*/ +rvMonsterSentry::rvMonsterSentry ( void ) { + strafeTime = 0; + strafeRight = false; +} + +void rvMonsterSentry::InitSpawnArgsVariables( void ) +{ + minShots = spawnArgs.GetInt ( "minShots" ); + maxShots = spawnArgs.GetInt ( "maxShots" ); +} +/* +================ +rvMonsterSentry::Spawn +================ +*/ +void rvMonsterSentry::Spawn ( void ) { + // Custom actions + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionKamakaziAttack.Init ( spawnArgs, "action_kamakaziAttack", "Torso_KamakaziAttack", AIACTIONF_ATTACK ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "State_Action_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + kamakaziHealth = spawnArgs.GetInt ( "kamakazi_Health", va("%d", health / 2) ); + nextChatterTime = 0; +} + +/* +================ +rvMonsterSentry::Save +================ +*/ +void rvMonsterSentry::Save ( idSaveGame *savefile ) const { + actionBlasterAttack.Save( savefile ); + actionKamakaziAttack.Save( savefile ); + actionCircleStrafe.Save( savefile ); + + savefile->WriteInt( shots ); + savefile->WriteInt( kamakaziHealth ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteInt ( nextChatterTime ); +} + +/* +================ +rvMonsterSentry::Restore +================ +*/ +void rvMonsterSentry::Restore ( idRestoreGame *savefile ) { + actionBlasterAttack.Restore( savefile ); + actionKamakaziAttack.Restore( savefile ); + actionCircleStrafe.Restore( savefile ); + + savefile->ReadInt( shots ); + savefile->ReadInt( kamakaziHealth ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadInt ( nextChatterTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSentry::Pain +================ +*/ +bool rvMonsterSentry::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( kamakaziHealth > 0 && health < kamakaziHealth ) { + kamakaziHealth = 0; + move.speed = move.fly_speed = spawnArgs.GetFloat ( "kamakazi_fly_speed", va("%g", move.fly_speed ) ); + move.turnRate = spawnArgs.GetFloat ( "kamakazi_turn_rate", va("%g", move.turnRate ) ); + move.fly_pitch_max = spawnArgs.GetFloat ( "kamakazi_fly_pitch_max" ); + move.fly_pitch_scale = spawnArgs.GetFloat ( "kamakazi_fly_pitch_scale" ); + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterSentry::Think +================ +*/ +void rvMonsterSentry::Think ( void ) { + // Explode whenever we hit something + if ( kamakaziHealth == 0 && physicsObj.GetSlideMoveEntity () ) { + Explode ( ); + } else { + /* + if ( nextChatterTime < gameLocal.GetTime() ) { + if ( GetEnemy() ) { + Speak( "lipsync_chatter_combat" ); + nextChatterTime = speakTime + 2000 + gameLocal.random.RandomInt( 2000 ); + } else { + Speak( "lipsync_chatter_idle" ); + nextChatterTime = speakTime + 3000 + gameLocal.random.RandomInt( 3000 ); + } + } + */ + + idAI::Think ( ); + } +} + +/* +================ +rvMonsterSentry::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterSentry::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterSentry::CheckActions +================ +*/ +bool rvMonsterSentry::CheckActions ( void ) { + if ( kamakaziHealth != 0 ) { + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterSentry::CheckAction_CircleStrafe ) ) { + return true; + } + } else { + if ( PerformAction ( &actionKamakaziAttack, (checkAction_t)&idAI::CheckAction_MeleeAttack, NULL ) ) { + return true; + } + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterSentry::FilterTactical +================ +*/ +int rvMonsterSentry::FilterTactical ( int availableTactical ) { + // Kamakazi Health will be set to 0 when its time to kamakazi + if ( !kamakaziHealth ) { + return availableTactical & (AITACTICAL_MELEE_BIT|AITACTICAL_TURRET_BIT); + // No rush when not in kamakazi mode + } else { + availableTactical &= ~(AITACTICAL_MELEE_BIT); + } + + return idAI::FilterTactical ( availableTactical ); +} + +/* +================ +rvMonsterSentry::OnDeath +================ +*/ +void rvMonsterSentry::OnDeath ( void ) { + idAI::OnDeath ( ); + + Explode ( true ); +} + +/* +================ +rvMonsterSentry::Explode +================ +*/ +void rvMonsterSentry::Explode ( bool force ) { + if ( health > 0 || force ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString ( "def_kamakazi_damage" ), 1.0f ); + + // Kill ourselves + Damage ( this, this, viewAxis[0], "damage_gib", 1.0f, 0 ); + } +} + +/* +================ +rvMonsterSentry::GetDebugInfo +================ +*/ +void rvMonsterSentry::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_kamakaziAttack", aiActionStatusString[actionKamakaziAttack.status], userData ); + proc ( "idAI", "action_circleStrafe", aiActionStatusString[actionCircleStrafe.status], userData ); + proc ( "rvMonsterSentry", "strafeRight",strafeRight?"true":"false", userData ); + proc ( "rvMonsterSentry", "strafeTime", va("%d",strafeTime), userData ); + proc ( "rvMonsterSentry", "nextChatterTime", va("%d",nextChatterTime), userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSentry ) + STATE ( "Torso_BlasterAttack", rvMonsterSentry::State_Torso_BlasterAttack ) + STATE ( "Torso_KamakaziAttack", rvMonsterSentry::State_Torso_KamakaziAttack ) + STATE ( "State_CircleStrafe", rvMonsterSentry::State_CircleStrafe ) + STATE ( "State_Action_CircleStrafe",rvMonsterSentry::State_Action_CircleStrafe ) + STATE ( "Torso_EvadeLeft", rvMonsterSentry::State_Torso_EvadeLeft ) + STATE ( "Torso_EvadeRight", rvMonsterSentry::State_Torso_EvadeRight ) +END_CLASS_STATES + +/* +================ +rvMonsterSentry::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACK, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = (gameLocal.random.RandomInt ( maxShots - minShots ) + minShots) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_ATTACK ); + + case STAGE_ATTACK: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", parms.blendFrames ); + shots--; + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_ATTACK ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSentry::State_Torso_KamakaziAttack +================ +*/ +stateResult_t rvMonsterSentry::State_Torso_KamakaziAttack ( const stateParms_t& parms ) { + Explode ( ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_CircleStrafe ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_CIRCLE + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( strafeTime ) { + //already strafing, just change dir? + strafeTime = (strafeTime0.5f); + } + return SRESULT_STAGE ( STAGE_CIRCLE ); + case STAGE_CIRCLE: + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) { + //FIXME: also stop if I bump into something + strafeTime = 0; + SetState( "State_Combat" ); + return SRESULT_DONE; + } + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (strafeRight?-200:200); + vel.Normalize(); + vel *= 200.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( enemy.lastKnownPosition ); + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t rvMonsterSentry::State_Action_CircleStrafe ( const stateParms_t& parms ) { + SetState( "State_CircleStrafe" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeLeft ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = false; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * 400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterSentry::State_Torso_EvadeRight ( const stateParms_t& parms ) { + if ( strafeTime ) { + strafeRight = true; + } else { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * -400; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + return SRESULT_DONE; +} diff --git a/src/mpgame/ai/Monster_SlimyTransfer.cpp b/src/mpgame/ai/Monster_SlimyTransfer.cpp new file mode 100644 index 0000000..107e43f --- /dev/null +++ b/src/mpgame/ai/Monster_SlimyTransfer.cpp @@ -0,0 +1,228 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterSlimyTransfer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterSlimyTransfer ); + + rvMonsterSlimyTransfer ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual bool CheckActions ( void ); + virtual void OnDeath ( void ); + + jointHandle_t jointVomitMuzzle; + + int vomitNextAttackTime; + int vomitAttackRate; + +private: + + rvAIAction actionVomitAttack; + + // Torso States + stateResult_t State_Torso_VomitAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishVomitAttack ( const stateParms_t& parms ); + + // Frame commands + stateResult_t State_Frame_StartVomit ( const stateParms_t& parms ); + stateResult_t State_Frame_StopVomit ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterSlimyTransfer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterSlimyTransfer ) +END_CLASS + +/* +================ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer +================ +*/ +rvMonsterSlimyTransfer::rvMonsterSlimyTransfer ( void ) { +} + +void rvMonsterSlimyTransfer::InitSpawnArgsVariables ( void ) { + jointVomitMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_vomitMuzzle", "puke_bone" ) ); + + vomitAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_vomit_rate", ".15" ) ); +} + +/* +================ +rvMonsterSlimyTransfer::Spawn +================ +*/ +void rvMonsterSlimyTransfer::Spawn ( void ) { + actionVomitAttack.Init ( spawnArgs, "action_vomitAttack", "Torso_VomitAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::Save +================ +*/ +void rvMonsterSlimyTransfer::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( vomitNextAttackTime ); + + actionVomitAttack.Save( savefile ); +} + +/* +================ +rvMonsterSlimyTransfer::Restore +================ +*/ +void rvMonsterSlimyTransfer::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( vomitNextAttackTime ); + + actionVomitAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterSlimyTransfer::CheckActions +================ +*/ +bool rvMonsterSlimyTransfer::CheckActions ( void ) { + if ( PerformAction ( &actionVomitAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterSlimyTransfer::OnDeath +================ +*/ +void rvMonsterSlimyTransfer::OnDeath ( void ) { + StopEffect ( "fx_vomit_muzzle" ); + idAI::OnDeath ( ); +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterSlimyTransfer ) + STATE ( "Torso_VomitAttack", rvMonsterSlimyTransfer::State_Torso_VomitAttack ) + STATE ( "Torso_FinishVomitAttack", rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ) + + STATE ( "Frame_StartVomit", rvMonsterSlimyTransfer::State_Frame_StartVomit ) + STATE ( "Frame_StopVomit", rvMonsterSlimyTransfer::State_Frame_StopVomit ) +END_CLASS_STATES + +/* +================ +rvMonsterSlimyTransfer::State_Torso_VomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_VomitAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + vomitNextAttackTime = 0; + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "vomit_attack", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishVomitAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + + if ( vomitNextAttackTime && gameLocal.time >= vomitNextAttackTime ) { + Attack ( "vomit", jointVomitMuzzle, enemy.ent ); + vomitNextAttackTime = gameLocal.time + vomitAttackRate; + } + + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Torso_FinishVomitAttack ( const stateParms_t& parms ) { + State_Frame_StopVomit ( parms ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StartVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StartVomit ( const stateParms_t& parms ) { + PlayEffect ( "fx_vomit_muzzle", jointVomitMuzzle, true ); + vomitNextAttackTime = gameLocal.time; + return SRESULT_DONE; +} + +/* +================ +rvMonsterSlimyTransfer::State_Frame_StopVomit +================ +*/ +stateResult_t rvMonsterSlimyTransfer::State_Frame_StopVomit ( const stateParms_t& parms ) { + StopEffect ( "fx_vomit_muzzle" ); + vomitNextAttackTime = 0; + return SRESULT_DONE; +} diff --git a/src/mpgame/ai/Monster_StreamProtector.cpp b/src/mpgame/ai/Monster_StreamProtector.cpp new file mode 100644 index 0000000..6ae0bd5 --- /dev/null +++ b/src/mpgame/ai/Monster_StreamProtector.cpp @@ -0,0 +1,561 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterStreamProtector : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStreamProtector ); + + rvMonsterStreamProtector ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool CanTurn ( void ) const; + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + +protected: + + virtual bool CheckPainActions ( void ); + virtual bool CheckActions ( void ); + virtual bool UpdateAnimationControllers ( void ); + + jointHandle_t jointPlasmaMuzzle; + + int attackEndTime; + int attackNextTime; + int plasmaAttackRate; + int shots; + +private: + + int painConsecutive; + + rvAIAction actionPlasmaAttack; + rvAIAction actionRocketAttack; + rvAIAction actionBlasterAttack; + rvAIAction actionHeavyBlasterAttack; + rvAIAction actionLightningActtack; + rvAIAction actionChaingunAttack; + + // Torso States + stateResult_t State_Killed ( const stateParms_t& parms ); + stateResult_t State_Dead ( const stateParms_t& parms ); + + stateResult_t State_Torso_PlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_LightningAttack ( const stateParms_t& parms ); + + stateResult_t State_Torso_TurnRight90 ( const stateParms_t& parms ); + stateResult_t State_Torso_TurnLeft90 ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStreamProtector ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStreamProtector ) +END_CLASS + +/* +================ +rvMonsterStreamProtector::rvMonsterStreamProtector +================ +*/ +rvMonsterStreamProtector::rvMonsterStreamProtector ( void ) { + painConsecutive = 0; +} + +void rvMonsterStreamProtector::InitSpawnArgsVariables ( void ) { + jointPlasmaMuzzle = animator.GetJointHandle ( spawnArgs.GetString ( "joint_plasmaMuzzle", "NM_muzzle" ) ); + + plasmaAttackRate = SEC2MS ( spawnArgs.GetFloat ( "attack_plasma_rate", ".15" ) ); +} +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +void rvMonsterStreamProtector::Spawn ( void ) { + actionPlasmaAttack.Init ( spawnArgs, "action_plasmaAttack", "Torso_PlasmaAttack", AIACTIONF_ATTACK ); + actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", NULL, AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", NULL, AIACTIONF_ATTACK ); + actionHeavyBlasterAttack.Init ( spawnArgs, "action_heavyBlasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionLightningActtack.Init ( spawnArgs, "action_lightningAttack", "Torso_LightningAttack", AIACTIONF_ATTACK ); + actionChaingunAttack.Init ( spawnArgs, "action_chaingunAttack", "Torso_ChaingunAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Save +================ +*/ +void rvMonsterStreamProtector::Save ( idSaveGame *savefile ) const { + savefile->WriteInt( attackEndTime ); + savefile->WriteInt( attackNextTime ); + savefile->WriteInt( shots ); + savefile->WriteInt( painConsecutive ); + + actionPlasmaAttack.Save( savefile ); + actionRocketAttack.Save( savefile ); + actionBlasterAttack.Save( savefile ); + actionHeavyBlasterAttack.Save ( savefile ); + actionLightningActtack.Save( savefile ); + actionChaingunAttack.Save( savefile ); +} + +/* +================ +rvMonsterStreamProtector::Restore +================ +*/ +void rvMonsterStreamProtector::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt( attackEndTime ); + savefile->ReadInt( attackNextTime ); + savefile->ReadInt( shots ); + savefile->ReadInt( painConsecutive ); + + actionPlasmaAttack.Restore( savefile ); + actionRocketAttack.Restore( savefile ); + actionBlasterAttack.Restore( savefile ); + actionHeavyBlasterAttack.Restore ( savefile ); + actionLightningActtack.Restore( savefile ); + actionChaingunAttack.Restore( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStreamProtector::Spawn +================ +*/ +bool rvMonsterStreamProtector::UpdateAnimationControllers ( void ) { + // TODO: Target enemies behind us? (doesnt need to be the same enemy that we are targetting) + + return idAI::UpdateAnimationControllers ( ); +} + +/* +================ +rvMonsterStreamProtector::CheckPainActions +================ +*/ +bool rvMonsterStreamProtector::CheckPainActions ( void ) { + if ( !pain.takenThisFrame || !actionTimerPain.IsDone ( actionTime ) ) { + return false; + } + + if ( !pain.threshold || pain.takenThisFrame < pain.threshold ) { + if ( painConsecutive < 10 ) { + return false; + } else { + painConsecutive = 0; + } + } + + PerformAction ( "Torso_Pain", 2, true ); + actionTimerPain.Reset ( actionTime ); + + return true; +} + +/* +================ +rvMonsterStreamProtector::CheckActions +================ +*/ +bool rvMonsterStreamProtector::CheckActions ( void ) { + // If not moving, try turning in place + if ( !move.fl.moving && gameLocal.time > combat.investigateTime ) { + float turnYaw = idMath::AngleNormalize180 ( move.ideal_yaw - move.current_yaw ) ; + if ( turnYaw > lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnRight90", 4, true ); + return true; + } else if ( turnYaw < -lookMax[YAW] * 0.75f ) { + PerformAction ( "Torso_TurnLeft90", 4, true ); + return true; + } + } + + if ( PerformAction ( &actionPlasmaAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionLightningActtack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionHeavyBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStreamProtector::CanTurn +================ +*/ +bool rvMonsterStreamProtector::CanTurn ( void ) const { + if ( !idAI::CanTurn ( ) ) { + return false; + } + return move.anim_turn_angles != 0.0f || move.fl.moving; +} + +/* +================ +rvMonsterStreamProtector::Pain +================ +*/ +bool rvMonsterStreamProtector::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + if ( pain.lastTakenTime > gameLocal.GetTime() - 500 ) { + painConsecutive++; + } else { + painConsecutive = 1; + } + return ( idAI::Pain( inflictor, attacker, damage, dir, location ) ); +} + +/* +================ +rvMonsterStreamProtector::Damage +================ +*/ +void rvMonsterStreamProtector::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker && attacker->IsType( rvMonsterStreamProtector::GetClassType() ) ) { + //don't take damage from ourselves or other stream protectors + return; + } + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStreamProtector ) + STATE ( "State_Killed", rvMonsterStreamProtector::State_Killed ) + STATE ( "State_Dead", rvMonsterStreamProtector::State_Dead ) + + STATE ( "Torso_PlasmaAttack", rvMonsterStreamProtector::State_Torso_PlasmaAttack ) + STATE ( "Torso_FinishPlasmaAttack", rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ) + STATE ( "Torso_BlasterAttack", rvMonsterStreamProtector::State_Torso_BlasterAttack ) + STATE ( "Torso_LightningAttack", rvMonsterStreamProtector::State_Torso_LightningAttack ) + + STATE ( "Torso_TurnRight90", rvMonsterStreamProtector::State_Torso_TurnRight90 ) + STATE ( "Torso_TurnLeft90", rvMonsterStreamProtector::State_Torso_TurnLeft90 ) +END_CLASS_STATES + +/* +================ +rvMonsterStreamProtector::State_Killed +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Killed ( const stateParms_t& parms ) { + // Make sure all animation stops + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + if ( head ) { + StopAnimState ( ANIMCHANNEL_HEAD ); + } + + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "death", parms.blendFrames ); + PostState ( "State_Dead" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Dead +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Dead ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_WAIT; + } + return idAI::State_Dead ( parms ); +} + +/* +================ +rvMonsterStreamProtector::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_start", parms.blendFrames ); + shots = (gameLocal.random.RandomInt ( 8 ) + 4) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_blaster_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_PlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_PlasmaAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_FIRE, + STAGE_INITIALFIRE_WAIT, + STAGE_FIRE_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Loop the flame animation + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_start", parms.blendFrames ); + + // Make sure we clean up some things when this state is finished (effects for one) + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_FinishPlasmaAttack", 0, 0, SFLAG_ONCLEAR ); + + return SRESULT_STAGE ( STAGE_START_WAIT ); + + case STAGE_START_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_FIRE: + attackEndTime = gameLocal.time + 500; + attackNextTime = gameLocal.time; + + // Flame effect + PlayEffect ( "fx_plasma_muzzle", jointPlasmaMuzzle, true ); + + PlayCycle ( ANIMCHANNEL_TORSO, "range_plasma_fire", 0 ); + + return SRESULT_STAGE ( STAGE_INITIALFIRE_WAIT ); + + case STAGE_INITIALFIRE_WAIT: + if ( gameLocal.time > attackEndTime ) { + attackEndTime = gameLocal.time + SEC2MS ( 1.0f + gameLocal.random.RandomFloat ( ) * 4.0f ); + return SRESULT_STAGE ( STAGE_FIRE_WAIT ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_FIRE_WAIT: + // If we have been using plasma too long or havent seen our enemy for at least half a second then + // stop now. + if ( gameLocal.time > attackEndTime || gameLocal.time - enemy.lastVisibleTime > 500 || (IsEnemyVisible() && !enemy.fl.inFov) ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_STAGE ( STAGE_END ); + } + // Launch another attack? + if ( gameLocal.time >= attackNextTime ) { + Attack ( "plasma", jointPlasmaMuzzle, enemy.ent ); + attackNextTime = gameLocal.time + plasmaAttackRate; + } + return SRESULT_WAIT; + + case STAGE_END: + // End animations + PlayAnim( ANIMCHANNEL_TORSO, "range_plasma_end", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_FinishPlasmaAttack ( const stateParms_t& parms ) { + StopEffect ( "fx_plasma_muzzle" ); + return SRESULT_DONE; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_LightningAttack +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_LightningAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + attackEndTime = gameLocal.time + 5000; + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( gameLocal.time > attackEndTime || (IsEnemyVisible() && !enemy.fl.inFov) ) { + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_end", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_lightning_fire", 0 ); + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnRight90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnRight90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_right_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStreamProtector::State_Torso_TurnLeft90 +================ +*/ +stateResult_t rvMonsterStreamProtector::State_Torso_TurnLeft90 ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "turn_left_90", parms.blendFrames ); + AnimTurn ( 90.0f, true ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( move.fl.moving || AnimDone ( ANIMCHANNEL_TORSO, 0 )) { + AnimTurn ( 0, true ); + combat.investigateTime = gameLocal.time + 250; + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_StroggFlyer.cpp b/src/mpgame/ai/Monster_StroggFlyer.cpp new file mode 100644 index 0000000..46173f9 --- /dev/null +++ b/src/mpgame/ai/Monster_StroggFlyer.cpp @@ -0,0 +1,386 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +extern const char* aiActionStatusString [ rvAIAction::STATUS_MAX ]; + +class rvMonsterStroggFlyer : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggFlyer ); + + rvMonsterStroggFlyer ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + +protected: + + virtual bool CheckActions ( void ); + + virtual void OnUpdatePlayback ( const rvDeclPlaybackData& pbd ); + virtual void OnWakeUp ( void ); + + rvAIAction actionBombAttack; + rvAIAction actionBlasterAttack; + + idVec3 velocity; + + int shotCount; + jointHandle_t jointGunRight; + jointHandle_t jointGunLeft; + + int lastAttackTime; + int attackStartTime; + + int blasterAttackDuration; + int blasterAttackRate; + int bombAttackDuration; + int bombAttackRate; + +private: + + stateResult_t State_ScriptedPlaybackMove ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); + + void AttackBlaster ( void ); + void AttackBomb ( void ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggFlyer ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggFlyer ) +END_CLASS + +/* +================ +rvMonsterStroggFlyer::rvMonsterStroggFlyer +================ +*/ +rvMonsterStroggFlyer::rvMonsterStroggFlyer ( ) { + shotCount = 0; + lastAttackTime = 0; + attackStartTime = 0; +} + +void rvMonsterStroggFlyer::InitSpawnArgsVariables( void ) +{ + jointGunRight = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) ); + jointGunLeft = animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) ); + + blasterAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) ); + blasterAttackRate = SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) ); + bombAttackDuration = SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) ); + bombAttackRate = SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) ); +} + +/* +================ +rvMonsterStroggFlyer::Spawn +================ +*/ +void rvMonsterStroggFlyer::Spawn ( void ) { + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::OnUpdatePlayback +================ +*/ +void rvMonsterStroggFlyer::OnUpdatePlayback ( const rvDeclPlaybackData& pbd ) { + byte buttons; + byte changed; + byte impulse; + + velocity = pbd.GetVelocity ( ); + + buttons = pbd.GetButtons(); + changed = pbd.GetChanged(); + impulse = pbd.GetImpulse(); + + // Shoot the blaster if the attack button was pressed + if ( (changed & buttons) & BUTTON_ATTACK ){ + AttackBlaster ( ); + } + + if ( (changed & buttons) & BUTTON_ZOOM ){ + AttackBomb ( ); + } + + switch ( impulse ) { + case 40: + aifl.disableAttacks = true; + break; + + case 41: + aifl.disableAttacks = false; + break; + + case 42: + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + break; + } +} + +/* +================ +rvMonsterStroggFlyer::OnWakeUp +================ +*/ +void rvMonsterStroggFlyer::OnWakeUp ( void ) { + jointHandle_t joint; + joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) ); + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_exhaust", joint, true ); + } + StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL ); + + return idAI::OnWakeUp ( ); +} + + +/* +================ +rvMonsterStroggFlyer::AttackBlaster +================ +*/ +void rvMonsterStroggFlyer::AttackBlaster ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + PlayEffect ( "fx_muzzleflash", joint ); + Attack ( "blaster", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::AttackBomb +================ +*/ +void rvMonsterStroggFlyer::AttackBomb ( void ) { + jointHandle_t joint; + joint = ((shotCount++)%2) ? jointGunRight : jointGunLeft; + + if ( joint != INVALID_JOINT ) { + StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( "fx_bombflash", joint ); + Attack ( "bomb", joint, enemy.ent ); + } +} + +/* +================ +rvMonsterStroggFlyer::CheckActions +================ +*/ +bool rvMonsterStroggFlyer::CheckActions ( void ) { + if ( PerformAction ( &actionBombAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterStroggFlyer::Save +================ +*/ +void rvMonsterStroggFlyer::Save( idSaveGame *savefile ) const { + actionBombAttack.Save ( savefile ) ; + actionBlasterAttack.Save ( savefile ); + + savefile->WriteVec3 ( velocity ); + + savefile->WriteInt ( shotCount ); + + savefile->WriteInt ( lastAttackTime ); + savefile->WriteInt ( attackStartTime ); +} + +/* +================ +rvMonsterStroggFlyer::Restore +================ +*/ +void rvMonsterStroggFlyer::Restore( idRestoreGame *savefile ) { + actionBombAttack.Restore ( savefile ) ; + actionBlasterAttack.Restore ( savefile ); + + savefile->ReadVec3 ( velocity ); + + savefile->ReadInt ( shotCount ); + + savefile->ReadInt ( lastAttackTime ); + savefile->ReadInt ( attackStartTime ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterStroggFlyer::GetDebugInfo +================ +*/ +void rvMonsterStroggFlyer::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "idAI", "action_blasterAttack", aiActionStatusString[actionBlasterAttack.status], userData ); + proc ( "idAI", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggFlyer ) + STATE ( "State_WakeUp", rvMonsterStroggFlyer::State_WakeUp ) + STATE ( "State_ScriptedPlaybackMove", rvMonsterStroggFlyer::State_ScriptedPlaybackMove ) + STATE ( "State_Killed", rvMonsterStroggFlyer::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterStroggFlyer::State_Torso_BlasterAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggFlyer::State_Torso_BombAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggFlyer::State_ScriptedPlaybackMove +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_ScriptedPlaybackMove ( const stateParms_t& parms ) { + // When the playback finishes cancel any running states + if ( !mPlayback.IsActive() ) { + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + // Perform actions + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterStroggFlyer::State_Killed +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Killed ( const stateParms_t& parms ) { + PlayEffect ( "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BLASTER, + STAGE_BLASTERWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BLASTER ); + + case STAGE_BLASTER: + lastAttackTime = gameLocal.time; + AttackBlaster ( ); + return SRESULT_STAGE ( STAGE_BLASTERWAIT ); + + case STAGE_BLASTERWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > blasterAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > blasterAttackRate ) { + return SRESULT_STAGE ( STAGE_BLASTER ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggFlyer::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggFlyer::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_BOMB, + STAGE_BOMBWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + attackStartTime = gameLocal.time; + return SRESULT_STAGE ( STAGE_BOMB ); + + case STAGE_BOMB: + lastAttackTime = gameLocal.time; + AttackBomb ( ); + return SRESULT_STAGE ( STAGE_BOMBWAIT ); + + case STAGE_BOMBWAIT: + if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) { + return SRESULT_DONE; + } + if ( gameLocal.time - lastAttackTime > bombAttackRate ) { + return SRESULT_STAGE ( STAGE_BOMB ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_StroggHover.cpp b/src/mpgame/ai/Monster_StroggHover.cpp new file mode 100644 index 0000000..1eeb889 --- /dev/null +++ b/src/mpgame/ai/Monster_StroggHover.cpp @@ -0,0 +1,1471 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../vehicle/Vehicle.h" + +#define MAX_MISSILE_JOINTS 4 +#define MAX_HOVER_JOINTS 4 +class rvMonsterStroggHover : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggHover ); + + rvMonsterStroggHover ( void ); + ~rvMonsterStroggHover ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void OnDeath ( void ); + virtual void DeadMove ( void ); + + virtual bool SkipImpulse ( idEntity *ent, int id ); + + virtual int FilterTactical ( int availableTactical ); + + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual void Hide( void ); + virtual void Show( void ); + + void StartHeadlight ( void ); + void StopHeadlight ( void ); + +protected: + +// rvAIAction actionRocketAttack; +// rvAIAction actionBlasterAttack; + rvAIAction actionMGunAttack; + rvAIAction actionMissileAttack; + rvAIAction actionBombAttack; + rvAIAction actionStrafe; + rvAIAction actionCircleStrafe; + + + virtual bool CheckActions ( void ); + virtual void OnEnemyChange ( idEntity* oldEnemy ); + virtual void OnStartMoving ( void ); + + virtual const char* GetIdleAnimName ( void ); + +private: + + idEntityPtr marker; + idVec3 attackPosOffset; + bool inPursuit; + int holdPosTime; + + int strafeTime; + bool strafeRight; + bool circleStrafing; + float deathPitch; + float deathRoll; + float deathPitchRate; + float deathYawRate; + float deathRollRate; + float deathSpeed; + float deathGrav; + + int markerCheckTime; + + bool MarkerPosValid ( void ); + void TryStartPursuit ( void ); + void Pursue ( void ); + void CircleStrafe ( void ); + void Evade ( bool left ); + + int mGunFireRate; + int missileFireRate; + int bombFireRate; + int nextMGunFireTime; + int nextMissileFireTime; + int nextBombFireTime; + + int mGunMinShots; + int mGunMaxShots; + int missileMinShots; + int missileMaxShots; + int bombMinShots; + int bombMaxShots; + + int shots; + + int evadeDebounce; + int evadeDebounceRate; + float evadeChance; + float evadeSpeed; + float strafeSpeed; + float circleStrafeSpeed; + + rvClientEffectPtr effectDust; + rvClientEffectPtr effectHover[MAX_HOVER_JOINTS]; + rvClientEffectPtr effectHeadlight; + + jointHandle_t jointDust; + int numHoverJoints; + jointHandle_t jointHover[MAX_HOVER_JOINTS]; + jointHandle_t jointBomb; + jointHandle_t jointMGun; + int numMissileJoints; + jointHandle_t jointMissile[MAX_MISSILE_JOINTS]; + jointHandle_t jointHeadlight; + jointHandle_t jointHeadlightControl; + + renderLight_t renderLight; + int lightHandle; +// bool lightOn; + + void DoNamedAttack ( const char* attackName, jointHandle_t joint ); + + void UpdateLightDef ( void ); + + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); + bool CheckAction_BombAttack ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + //virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + +// stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MGunAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MissileAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); +// stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); + stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); + stateResult_t State_Torso_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_CircleStrafe ( const stateParms_t& parms ); + stateResult_t State_DeathSpiral ( const stateParms_t& parms ); + stateResult_t State_Pursue ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggHover ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggHover ) +END_CLASS + +/* +================ +rvMonsterStroggHover::rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::rvMonsterStroggHover ( ) { + effectDust = NULL; + for ( int i = 0; i < MAX_HOVER_JOINTS; i++ ) { + effectHover[i] = NULL; + } + effectHeadlight = NULL; + + shots = 0; + strafeTime = 0; + strafeRight = false; + circleStrafing = false; + evadeDebounce = 0; + deathPitch = 0; + deathRoll = 0; + deathPitchRate = 0; + deathYawRate = 0; + deathRollRate = 0; + deathSpeed = 0; + deathGrav = 0; + + markerCheckTime = 0; + + marker = NULL; + attackPosOffset.Zero(); + inPursuit = false; + holdPosTime = 0; + + nextMGunFireTime = 0; + nextMissileFireTime = 0; + nextBombFireTime = 0; + + lightHandle = -1; +} + +/* +================ +rvMonsterStroggHover::~rvMonsterStroggHover +================ +*/ +rvMonsterStroggHover::~rvMonsterStroggHover ( ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +void rvMonsterStroggHover::InitSpawnArgsVariables( void ) +{ + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + jointHover[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_hover%d",i+1) ) ); + } + + jointDust = animator.GetJointHandle ( spawnArgs.GetString( "joint_dust" ) ); + + jointMGun = animator.GetJointHandle ( spawnArgs.GetString( "joint_mgun" ) ); + numMissileJoints = idMath::ClampInt(0,MAX_MISSILE_JOINTS,spawnArgs.GetInt( "num_missile_joints", "1" )); + for ( int i = 0; i < numMissileJoints; i++ ) { + jointMissile[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_missile%d",i+1) ) ); + } + jointBomb = animator.GetJointHandle ( spawnArgs.GetString( "joint_bomb" ) ); + + mGunFireRate = SEC2MS(spawnArgs.GetFloat( "mgun_fire_rate", "0.1" )); + missileFireRate = SEC2MS(spawnArgs.GetFloat( "missile_fire_rate", "0.25" )); + bombFireRate = SEC2MS(spawnArgs.GetFloat( "bomb_fire_rate", "0.5" )); + + mGunMinShots = spawnArgs.GetInt( "mgun_minShots", "20" ); + mGunMaxShots = spawnArgs.GetInt( "mgun_maxShots", "40" ); + missileMinShots = spawnArgs.GetInt( "missile_minShots", "4" ); + missileMaxShots = spawnArgs.GetInt( "missile_maxShots", "12" ); + bombMinShots = spawnArgs.GetInt( "bomb_minShots", "5" ); + bombMaxShots = spawnArgs.GetInt( "bomb_maxShots", "20" ); + + evadeDebounceRate = SEC2MS(spawnArgs.GetFloat( "evade_rate", "0" )); + evadeChance = spawnArgs.GetFloat( "evade_chance", "0.6" ); + evadeSpeed = spawnArgs.GetFloat( "evade_speed", "400" ); + strafeSpeed = spawnArgs.GetFloat( "strafe_speed", "500" ); + circleStrafeSpeed = spawnArgs.GetFloat( "circle_strafe_speed", "200" ); + + //LIGHT + jointHeadlight = animator.GetJointHandle ( spawnArgs.GetString( "joint_light" ) ); + jointHeadlightControl = animator.GetJointHandle ( spawnArgs.GetString( "joint_light_control" ) ); +} + +void rvMonsterStroggHover::Hide( void ) +{ + StopHeadlight(); + idAI::Hide(); +} + +void rvMonsterStroggHover::Show( void ) +{ + idAI::Show(); + StartHeadlight(); +} + +void rvMonsterStroggHover::StartHeadlight( void ) +{ + if ( jointHeadlight != INVALID_JOINT ) + { + lightHandle = -1; + if ( cvarSystem->GetCVarInteger( "com_machineSpec" ) > 1 && spawnArgs.GetString("mtr_light") ) { + idVec3 color; + //const char* temp; + + const idMaterial *headLightMaterial = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + if ( headLightMaterial ) + { + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "light_pointlight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + spawnArgs.GetVector( "light_color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "light_radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "light_target" ); + renderLight.up = spawnArgs.GetVector( "light_up" ); + renderLight.right = spawnArgs.GetVector( "light_right" ); + renderLight.end = spawnArgs.GetVector( "light_target" );; + } + + //lightOn = spawnArgs.GetBool( "start_on", "1" ); + + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + } + // Hide flare surface if there is one + /* + temp = spawnArgs.GetString ( "light_flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + */ + + // Sounds shader when turning light + //spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + //spawnArgs.GetString ( "snd_off", "", soundOff); + + UpdateLightDef ( ); + } +} + +void rvMonsterStroggHover::StopHeadlight( void ) +{ + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef ( lightHandle ); + lightHandle = -1; + } + memset ( &renderLight, 0, sizeof(renderLight) ); +} +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +void rvMonsterStroggHover::Spawn ( void ) { +// actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); +// actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + actionMGunAttack.Init ( spawnArgs, "action_mGunAttack", "Torso_MGunAttack", AIACTIONF_ATTACK ); + actionMissileAttack.Init ( spawnArgs, "action_missileAttack", "Torso_MissileAttack", AIACTIONF_ATTACK ); + actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); + + actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); + actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "Torso_CircleStrafe", AIACTIONF_ATTACK ); + + InitSpawnArgsVariables(); + + evadeDebounce = 0; + + numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + effectHover[i] = PlayEffect ( "fx_hover", jointHover[i], true ); + } + } + + if ( !marker ) { + marker = gameLocal.SpawnEntityDef( "target_null" ); + } + + //LIGHT + StopHeadlight(); + StartHeadlight(); + + if ( jointHeadlight != INVALID_JOINT ) + { + effectHeadlight = PlayEffect( "fx_headlight", jointHeadlight, true ); + } +} + +/* +================ +rvMonsterStroggHover::GetDebugInfo +================ +*/ +void rvMonsterStroggHover::GetDebugInfo( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterStroggHover", "action_mGunAttack", aiActionStatusString[actionMGunAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_missileAttack",aiActionStatusString[actionMissileAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); + proc ( "rvMonsterStroggHover", "action_strafe", aiActionStatusString[actionStrafe.status], userData ); + proc ( "rvMonsterStroggHover", "action_circleStrafe",aiActionStatusString[actionCircleStrafe.status], userData ); + + proc ( "rvMonsterStroggHover", "inPursuit", inPursuit?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "marker", (!inPursuit||marker==NULL)?"0 0 0":va("%f %f %f",marker->GetPhysics()->GetOrigin().x,marker->GetPhysics()->GetOrigin().y,marker->GetPhysics()->GetOrigin().z), userData ); + proc ( "rvMonsterStroggHover", "holdPosTime", va("%d",holdPosTime), userData ); + + proc ( "rvMonsterStroggHover", "circleStrafing", circleStrafing?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeRight", strafeRight?"true":"false", userData ); + proc ( "rvMonsterStroggHover", "strafeTime", va("%d",strafeTime), userData ); + + proc ( "rvMonsterStroggHover", "mGunFireRate", va("%d",mGunFireRate), userData ); + proc ( "rvMonsterStroggHover", "missileFireRate", va("%d",missileFireRate), userData ); + proc ( "rvMonsterStroggHover", "bombFireRate", va("%d",bombFireRate), userData ); + + proc ( "rvMonsterStroggHover", "mGunMinShots", va("%d",mGunMinShots), userData ); + proc ( "rvMonsterStroggHover", "mGunMaxShots", va("%d",mGunMaxShots), userData ); + proc ( "rvMonsterStroggHover", "missileMinShots", va("%d",missileMinShots), userData ); + proc ( "rvMonsterStroggHover", "missileMaxShots", va("%d",missileMaxShots), userData ); + proc ( "rvMonsterStroggHover", "bombMinShots", va("%d",bombMinShots), userData ); + proc ( "rvMonsterStroggHover", "bombMaxShots", va("%d",bombMaxShots), userData ); + proc ( "rvMonsterStroggHover", "nextMGunFireTime", va("%d",nextMGunFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextMissileFireTime",va("%d",nextMissileFireTime), userData ); + proc ( "rvMonsterStroggHover", "nextBombFireTime", va("%d",nextBombFireTime), userData ); + proc ( "rvMonsterStroggHover", "shots", va("%d",shots), userData ); + + proc ( "rvMonsterStroggHover", "evadeDebounce", va("%d",evadeDebounce), userData ); + proc ( "rvMonsterStroggHover", "evadeDebounceRate", va("%d",evadeDebounceRate), userData ); + proc ( "rvMonsterStroggHover", "evadeChance", va("%g",evadeChance), userData ); + proc ( "rvMonsterStroggHover", "evadeSpeed", va("%g",evadeSpeed), userData ); + proc ( "rvMonsterStroggHover", "strafeSpeed", va("%g",strafeSpeed), userData ); + proc ( "rvMonsterStroggHover", "circleStrafeSpeed", va("%g",circleStrafeSpeed), userData ); +} + +/* +===================== +rvMonsterStroggHover::UpdateLightDef +===================== +*/ +void rvMonsterStroggHover::UpdateLightDef ( void ) { + if ( jointHeadlight != INVALID_JOINT ) + { + idVec3 origin; + idMat3 axis; + + if ( jointHeadlightControl != INVALID_JOINT ) { + idAngles jointAng; + jointAng.Zero(); + jointAng.yaw = 10.0f * sin( ( (gameLocal.GetTime()%2000)-1000 ) / 1000.0f * idMath::PI ); + jointAng.pitch = 7.5f * sin( ( (gameLocal.GetTime()%4000)-2000 ) / 2000.0f * idMath::PI ); + + animator.SetJointAxis( jointHeadlightControl, JOINTMOD_WORLD, jointAng.ToMat3() ); + } + + GetJointWorldTransform ( jointHeadlight, gameLocal.time, origin, axis ); + + //origin += (localOffset * axis); + + // Include this part in the total bounds + // FIXME: bounds are local + //parent->AddToBounds ( worldOrigin ); + //UpdateOrigin ( ); + + if ( lightHandle != -1 ) { + renderLight.origin = origin; + renderLight.axis = axis; + + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); + } + } +} + +/* +================ +rvMonsterStroggHover::Think +================ +*/ +void rvMonsterStroggHover::Think ( void ) { + idAI::Think ( ); + + if ( !aifl.dead ) + { + // If thinking we should play an effect on the ground under us + if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { + trace_t tr; + idVec3 origin; + idMat3 axis; + + // Project the effect 80 units down from the bottom of our bbox + GetJointWorldTransform ( jointDust, gameLocal.time, origin, axis ); + + // RAVEN BEGIN + // ddynerman: multiple clip worlds + gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * (GetPhysics()->GetBounds()[0][2]+80.0f), CONTENTS_SOLID, this ); + // RAVEN END + + // Start the dust effect if not already started + if ( !effectDust ) { + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); + } + + // If the effect is playing we should update its attenuation as well as its origin and axis + if ( effectDust ) { + effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); + effectDust->SetOrigin ( tr.endpos ); + effectDust->SetAxis ( tr.c.normal.ToMat3() ); + } + + // If the hover effect is playing we can set its end origin to the ground + /* + if ( effectHover ) { + effectHover->SetEndOrigin ( tr.endpos ); + } + */ + } else if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + //Try to circle strafe or pursue + if ( circleStrafing ) + { + CircleStrafe(); + } + else if ( !inPursuit ) + { + if ( !aifl.action && move.fl.done && !aifl.scripted ) + { + if ( GetEnemy() ) + { + if ( DistanceTo( GetEnemy() ) > 2000.0f + || (GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetEnemy()->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin())) > 1000.0f ) + {//enemy is far away or moving away from us at a pretty decent speed + TryStartPursuit(); + } + } + } + } + else + { + Pursue(); + } + + //Dodge + if ( !circleStrafing ) { + if( combat.shotAtTime && gameLocal.GetTime() - combat.shotAtTime < 1000.0f ) { + if ( nextBombFireTime < gameLocal.GetTime() - 3000 ) { + if ( gameLocal.random.RandomFloat() > evadeChance ) { + //40% chance of ignoring it - makes them dodge rockets less often but bullets more often? + combat.shotAtTime = 0; + } else if ( evadeDebounce < gameLocal.GetTime() ) { + //ramps down from 400 to 100 over 1 second + float speed = evadeSpeed - ((((float)(gameLocal.GetTime()-combat.shotAtTime))/1000.0f)*(evadeSpeed-(evadeSpeed*0.25f))); + idVec3 evadeVel = viewAxis[1] * ((combat.shotAtAngle >= 0)?-1:1) * speed; + evadeVel.z *= 0.5f; + move.addVelocity += evadeVel; + move.addVelocity.Normalize(); + move.addVelocity *= speed; + /* + if ( move.moveCommand < NUM_NONMOVING_COMMANDS ) { + //just need to do it once? + combat.shotAtTime = 0; + } + */ + if ( evadeDebounceRate > 1 ) + { + evadeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt( evadeDebounceRate ) + (ceil(((float)evadeDebounceRate)/2.0f)); + } + } + } + } + } + + //If using melee rush to nav to him, stop when we're close enough to attack + if ( combat.tacticalCurrent == AITACTICAL_MELEE + && move.moveCommand == MOVE_TO_ENEMY + && !move.fl.done + && nextBombFireTime < gameLocal.GetTime() - 3000 + && enemy.fl.visible && DistanceTo( GetEnemy() ) < 2000.0f ) { + StopMove( MOVE_STATUS_DONE ); + ForceTacticalUpdate(); + } else { + //whenever we're not in the middle of something, force an update of our tactical + if ( !aifl.action ) { + if ( !aasFind ) { + if ( move.fl.done ) { + if ( !inPursuit && !circleStrafing ) { + ForceTacticalUpdate(); + } + } + } + } + } + } + + //update light +// if ( lightOn ) { + UpdateLightDef ( ); +// } +} + +/* +============ +rvMonsterStroggHover::OnStartMoving +============ +*/ +void rvMonsterStroggHover::OnStartMoving ( void ) { + idAI::OnStartMoving(); + if ( move.moveCommand == MOVE_TO_ENEMY ) { + move.range = combat.meleeRange; + } +} + +/* +================ +rvMonsterStroggHover::Save +================ +*/ +void rvMonsterStroggHover::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shots ); + savefile->WriteInt ( strafeTime ); + savefile->WriteBool ( strafeRight ); + savefile->WriteBool ( circleStrafing ); + savefile->WriteFloat ( deathPitch ); + savefile->WriteFloat ( deathRoll ); + savefile->WriteFloat ( deathPitchRate ); + savefile->WriteFloat ( deathYawRate ); + savefile->WriteFloat ( deathRollRate ); + savefile->WriteFloat ( deathSpeed ); + savefile->WriteFloat ( deathGrav ); + savefile->WriteInt ( markerCheckTime ); + + savefile->WriteVec3( attackPosOffset ); + +// actionRocketAttack.Save ( savefile ); +// actionBlasterAttack.Save ( savefile ); + actionMGunAttack.Save ( savefile ); + actionMissileAttack.Save ( savefile ); + actionBombAttack.Save ( savefile ); + actionStrafe.Save ( savefile ); + actionCircleStrafe.Save ( savefile ); + + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Save ( savefile ); + } + + effectDust.Save ( savefile ); + effectHeadlight.Save ( savefile ); + + marker.Save( savefile ); + savefile->WriteBool ( inPursuit ); + savefile->WriteInt ( holdPosTime ); + savefile->WriteInt ( nextMGunFireTime ); + savefile->WriteInt ( nextMissileFireTime ); + savefile->WriteInt ( nextBombFireTime ); + + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + + savefile->WriteInt( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Restore +================ +*/ +void rvMonsterStroggHover::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shots ); + savefile->ReadInt ( strafeTime ); + savefile->ReadBool ( strafeRight ); + savefile->ReadBool ( circleStrafing ); + savefile->ReadFloat ( deathPitch ); + savefile->ReadFloat ( deathRoll ); + savefile->ReadFloat ( deathPitchRate ); + savefile->ReadFloat ( deathYawRate ); + savefile->ReadFloat ( deathRollRate ); + savefile->ReadFloat ( deathSpeed ); + savefile->ReadFloat ( deathGrav ); + savefile->ReadInt ( markerCheckTime ); + + savefile->ReadVec3( attackPosOffset ); + +// actionRocketAttack.Restore ( savefile ); +// actionBlasterAttack.Restore ( savefile ); + actionMGunAttack.Restore ( savefile ); + actionMissileAttack.Restore ( savefile ); + actionBombAttack.Restore ( savefile ); + actionStrafe.Restore ( savefile ); + actionCircleStrafe.Restore ( savefile ); + + InitSpawnArgsVariables(); + //NOTE: if the def file changes the the number of numHoverJoints, this will be BAD... + for ( int i = 0; i < numHoverJoints; i++ ) { + effectHover[i].Restore ( savefile ); + } + + effectDust.Restore ( savefile ); + effectHeadlight.Restore ( savefile ); + + marker.Restore( savefile ); + savefile->ReadBool ( inPursuit ); + savefile->ReadInt ( holdPosTime ); + savefile->ReadInt ( nextMGunFireTime ); + savefile->ReadInt ( nextMissileFireTime ); + savefile->ReadInt ( nextBombFireTime ); + + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadInt ( evadeDebounce ); +} + +/* +================ +rvMonsterStroggHover::Collide +================ +*/ +bool rvMonsterStroggHover::Collide( const trace_t &collision, const idVec3 &velocity ) { + if ( aifl.dead ) { + StopHeadlight(); + //stop headlight + if ( effectHeadlight ) { + effectHeadlight->Stop ( ); + effectHeadlight = NULL; + } + // Stop the crash & burn effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); + SetState ( "State_Remove" ); + return false; + } + return idAI::Collide( collision, velocity ); +} + +/* +================ +rvMonsterStroggHover::Damage +================ +*/ +void rvMonsterStroggHover::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location ) { + if ( attacker == this ) { + return; + } + bool wasDead = aifl.dead; + idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + + if ( !wasDead && aifl.dead ) { + SetState( "State_DeathSpiral" ); + } +} +/* +================ +rvMonsterStroggHover::OnDeath +================ +*/ +void rvMonsterStroggHover::OnDeath ( void ) { + // Stop the dust effect + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + // Stop the hover effect + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( effectHover[i] ) { + effectHover[i]->Stop ( ); + effectHover[i] = NULL; + } + } + + idAI::OnDeath ( ); +} + +/* +===================== +rvMonsterStroggHover::DeadMove +===================== +*/ +void rvMonsterStroggHover::DeadMove( void ) { + DeathPush ( ); + physicsObj.UseVelocityMove( true ); + RunPhysics(); +} + +/* +===================== +rvMonsterStroggHover::SkipImpulse +===================== +*/ +bool rvMonsterStroggHover::SkipImpulse( idEntity* ent, int id ) { + return ((ent==this) || (move.moveCommand==MOVE_RV_PLAYBACK)); +} + +/* +================ +rvMonsterStroggHover::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( inPursuit && !holdPosTime ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + if ( evadeDebounce >= gameLocal.GetTime() ) { + return false; + } + + if ( animNum != -1 && !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { + if ( inPursuit ) { + return false; + } + + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + return true; +} + +/* +================ +rvMonsterStroggHover::CheckAction_CircleStrafe +================ +*/ +bool rvMonsterStroggHover::CheckAction_BombAttack ( rvAIAction* action, int animNum ) { + if ( !GetEnemy() || !enemy.fl.visible ) { + return false; + } + /* + if ( GetPhysics()->GetLinearVelocity().Length() < 200.0f ) { + //not moving enough + return false; + } + */ + if ( GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetPhysics()->GetOrigin()-GetEnemy()->GetPhysics()->GetOrigin()) >= 250.0f ) { + //enemy is moving toward me, drop 'em! + return true; + } + return false; +} + +/* +================ +rvMonsterStroggHover::Spawn +================ +*/ +bool rvMonsterStroggHover::CheckActions ( void ) { + if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_CircleStrafe ) ) { + return true; + } + +/* + if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { + return true; + } + + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } +*/ + if ( PerformAction ( &actionBombAttack, (checkAction_t)&rvMonsterStroggHover::CheckAction_BombAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMissileAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( PerformAction ( &actionMGunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + + if ( idAI::CheckActions ( ) ) { + return true; + } + + if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_Strafe ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterStroggHover::OnEnemyChange +================ +*/ +void rvMonsterStroggHover::OnEnemyChange ( idEntity* oldEnemy ) { + idAI::OnEnemyChange ( oldEnemy ); + + if ( !enemy.ent ) { + return; + } +} + +/* +================ +rvMonsterStroggHover::GetIdleAnimName +================ +*/ +const char* rvMonsterStroggHover::GetIdleAnimName ( void ) { + /* + if ( move.moveType == MOVETYPE_FLY ) { + return "flying_idle"; + } + */ + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterStroggHover::DoNamedAttack +================ +*/ +void rvMonsterStroggHover::DoNamedAttack ( const char* attackName, jointHandle_t joint ) { + if ( joint != INVALID_JOINT ) { + StartSound ( va("snd_%s_fire",attackName), SND_CHANNEL_ANY, 0, false, NULL ); + PlayEffect ( va("fx_%s_flash",attackName), joint ); + Attack ( attackName, joint, GetEnemy() ); + } +} + +/* +================ +rvMonsterStroggHover::FilterTactical +================ +*/ +int rvMonsterStroggHover::FilterTactical ( int availableTactical ) { + availableTactical = idAI::FilterTactical( availableTactical ); + if ( circleStrafing || inPursuit ) { + return 0; + } + if ( nextBombFireTime >= gameLocal.GetTime() ) { + availableTactical &= ~(AITACTICAL_RANGED_BITS); + } else if ( enemy.fl.visible ) { + availableTactical &= ~AITACTICAL_MELEE_BIT; + } + return availableTactical; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggHover ) +// STATE ( "Torso_BlasterAttack", rvMonsterStroggHover::State_Torso_BlasterAttack ) +// STATE ( "Torso_RocketAttack", rvMonsterStroggHover::State_Torso_RocketAttack ) + STATE ( "Torso_MGunAttack", rvMonsterStroggHover::State_Torso_MGunAttack ) + STATE ( "Torso_MissileAttack", rvMonsterStroggHover::State_Torso_MissileAttack ) + STATE ( "Torso_BombAttack", rvMonsterStroggHover::State_Torso_BombAttack ) +// STATE ( "Torso_EvadeLeft", rvMonsterStroggHover::State_Torso_EvadeLeft ) +// STATE ( "Torso_EvadeRight", rvMonsterStroggHover::State_Torso_EvadeRight ) + STATE ( "Torso_Strafe", rvMonsterStroggHover::State_Torso_Strafe ) + STATE ( "Torso_CircleStrafe", rvMonsterStroggHover::State_Torso_CircleStrafe ) + STATE ( "State_CircleStrafe", rvMonsterStroggHover::State_CircleStrafe ) + STATE ( "State_DeathSpiral", rvMonsterStroggHover::State_DeathSpiral ) + STATE ( "State_Pursue", rvMonsterStroggHover::State_Pursue ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggHover::State_Torso_MGunAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MGunAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( mGunMaxShots-mGunMinShots ) + mGunMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "mgun", jointMGun ); + nextMGunFireTime = gameLocal.GetTime() + mGunFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMGunFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_MissileAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_MissileAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( missileMaxShots-missileMinShots ) + missileMinShots; + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "missile", jointMissile[gameLocal.random.RandomInt(numMissileJoints)] ); + nextMissileFireTime = gameLocal.GetTime() + missileFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextMissileFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 || enemy.range < (actionMissileAttack.minRange*0.75f) ) { + //out of shots or enemy too close to safely keep launching rockets at + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BombAttack +================ +*/ +stateResult_t rvMonsterStroggHover::State_Torso_BombAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_LOOP, + STAGE_WAITLOOP, + }; + idVec3 vel = GetPhysics()->GetLinearVelocity(); + if ( vel.z < 150.0f ) { + vel.z += 20.0f; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + switch ( parms.stage ) { + case STAGE_INIT: + move.fly_offset = 800;//go up! + shots = gameLocal.random.RandomInt ( bombMaxShots-bombMinShots ) + bombMinShots; + if ( GetEnemy() ) { + //if I'm not above him, give me a quick boost first + float zDiff = GetPhysics()->GetOrigin().z-GetEnemy()->GetPhysics()->GetOrigin().z; + if ( zDiff < 150.0f ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel.z += 200.0f; + if ( zDiff < 0.0f ) { + //even more if I'm below him! + vel.z -= zDiff*2.0f; + } + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + } + } + if ( move.moveCommand == MOVE_TO_ATTACK ) { + StopMove( MOVE_STATUS_DONE ); + } + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + ForceTacticalUpdate(); + } + if ( move.moveCommand == MOVE_NONE + && !inPursuit && !circleStrafing ) { + MoveToEnemy(); + } + return SRESULT_STAGE ( STAGE_LOOP ); + + case STAGE_LOOP: + DoNamedAttack( "bomb", jointBomb ); + nextBombFireTime = gameLocal.GetTime() + bombFireRate; + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( nextBombFireTime <= gameLocal.GetTime() ) { + if ( --shots <= 0 ) { + move.fly_offset = spawnArgs.GetFloat("fly_offset","250"); + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggHover::State_Torso_BlasterAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + shots = gameLocal.random.RandomInt ( 8 ) + 4; + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_preshoot", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_fire", 0 ); + return SRESULT_STAGE ( STAGE_WAITLOOP ); + + case STAGE_WAITLOOP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( --shots <= 0 ) { + PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_postshoot", 0 ); + return SRESULT_STAGE ( STAGE_WAITEND ); + } + return SRESULT_STAGE ( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_WAITEND: + if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} +*/ +/* +================ +rvMonsterStroggHover::State_Torso_RocketAttack +================ +*/ +/* +stateResult_t rvMonsterStroggHover::State_Torso_RocketAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAITSTART, + STAGE_LOOP, + STAGE_WAITLOOP, + STAGE_WAITEND + }; + switch ( parms.stage ) { + case STAGE_INIT: + //DisableAnimState ( ANIMCHANNEL_LEGS ); + PlayAnim ( ANIMCHANNEL_TORSO, "rocket_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAITSTART ); + + case STAGE_WAITSTART: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + ForceTacticalUpdate(); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} +*/ + +void rvMonsterStroggHover::Evade ( bool left ) { + idVec3 vel = GetPhysics()->GetLinearVelocity(); + vel += viewAxis[1] * (left?strafeSpeed:-strafeSpeed); + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); +} + +stateResult_t rvMonsterStroggHover::State_Torso_Strafe ( const stateParms_t& parms ) { + //fixme: trace first for visibility & obstruction? + if ( gameLocal.random.RandomFloat() > 0.5f ) { + Evade( false ); + } else { + Evade( true ); + } + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_Torso_CircleStrafe ( const stateParms_t& parms ) { + circleStrafing = true; + return SRESULT_DONE; +} + +bool rvMonsterStroggHover::MarkerPosValid ( void ) +{ + //debouncer ftw + if( markerCheckTime > gameLocal.GetTime() ) { + return true; + } + + markerCheckTime = gameLocal.GetTime() + 500 + (gameLocal.random.RandomFloat() * 500); + + trace_t trace; + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetOrigin(), GetPhysics()->GetClipMask(), NULL ); + if ( !(trace.c.contents&GetPhysics()->GetClipMask()) ) + {//not in solid + gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), GetEnemy()->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, GetEnemy() ); + idActor* enemyAct = NULL; + rvVehicle* enemyVeh = NULL; + if ( GetEnemy()->IsType( rvVehicle::GetClassType() ) ) { + enemyVeh = static_cast(GetEnemy()); + } else if ( GetEnemy()->IsType( idActor::GetClassType() ) ) { + enemyAct = static_cast(GetEnemy()); + } + idEntity* hitEnt = gameLocal.entities[trace.c.entityNum]; + idActor* hitAct = NULL; + if ( hitEnt && hitEnt->IsType( idActor::GetClassType() ) ) { + hitAct = static_cast(hitEnt); + } + if ( trace.fraction >= 1.0f + || (enemyAct && enemyAct->IsInVehicle() && enemyAct->GetVehicleController().GetVehicle() == gameLocal.entities[trace.c.entityNum]) + || (enemyVeh && hitAct && hitAct->IsInVehicle() && hitAct->GetVehicleController().GetVehicle() == enemyVeh) ) + {//have a clear LOS to enemy + if ( PointReachableAreaNum( marker.GetEntity()->GetPhysics()->GetOrigin() ) ) + {//valid AAS there... + return true; + } + } + } + return false; +} + +void rvMonsterStroggHover::TryStartPursuit ( void ) +{ + if ( GetEnemy() ) + { + inPursuit = false; + if ( !marker.GetEntity() ) { + //wtf?! + assert(0); + return; + } + attackPosOffset.Set( gameLocal.random.CRandomFloat()*500.0f, gameLocal.random.CRandomFloat()*500.0f, 0.0f ); + if ( attackPosOffset.Length() < 150.0f ) + { + attackPosOffset.Normalize(); + attackPosOffset *= 150.0f; + } + attackPosOffset.z = (gameLocal.random.CRandomFloat()*30.0f)+50.0f + move.fly_offset; + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( MarkerPosValid() ) + { + if ( MoveToEntity( marker ) ) + { + inPursuit = true; + holdPosTime = 0; + SetState( "State_Pursue" ); + } + } + } +} + +void rvMonsterStroggHover::Pursue ( void ) +{ + if ( marker.GetEntity() && GetEnemy() ) + { + marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); + if ( DebugFilter(ai_debugMove) ) { + gameRenderWorld->DebugAxis( marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetAxis() ); + } + if ( MarkerPosValid() ) + { + bool breakOff = false; + if ( move.fl.done ) + {//even once get there, hold that position for a while... + if ( holdPosTime && holdPosTime > gameLocal.GetTime() ) + {//held this position long enough + breakOff = true; + } + else + { + if ( !holdPosTime ) + {//just got there, hold position for a bit + holdPosTime = gameLocal.random.RandomInt(2000)+3000 + gameLocal.GetTime(); + } + if ( !MoveToEntity( marker ) ) + { + breakOff = true; + } + } + } + if ( !breakOff ) + { + return; + } + } + } + if ( !move.fl.done ) + { + StopMove( MOVE_STATUS_DONE ); + } + inPursuit = false; +} + +stateResult_t rvMonsterStroggHover::State_Pursue ( const stateParms_t& parms ) { + if ( inPursuit ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +void rvMonsterStroggHover::CircleStrafe ( void ) +{ + if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) + { + //FIXME: also stop if I bump into something + circleStrafing = false; + strafeTime = 0; + SetState( "State_Combat" ); + return; + } + if ( !strafeTime ) + { + strafeTime = gameLocal.GetTime() + 8000; + //FIXME: try to see which side it clear? + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + } + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[1] * (strafeRight?-circleStrafeSpeed:circleStrafeSpeed); + strafeVel.z = 0.0f; + vel += strafeVel; + vel.Normalize(); + vel *= circleStrafeSpeed; + physicsObj.UseVelocityMove( true ); + GetPhysics()->SetLinearVelocity( vel ); + TurnToward( GetEnemy()->GetPhysics()->GetOrigin() ); +} + +stateResult_t rvMonsterStroggHover::State_CircleStrafe ( const stateParms_t& parms ) { + if ( circleStrafing ) { + // Perform actions along the way + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + return SRESULT_WAIT; + } + SetState( "State_Combat" ); + return SRESULT_DONE; +} + +stateResult_t rvMonsterStroggHover::State_DeathSpiral ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SPIRAL + }; + switch ( parms.stage ) { + case STAGE_INIT: + { + disablePain = true; + + // Make sure all animation and attack states stop + StopAnimState ( ANIMCHANNEL_TORSO ); + StopAnimState ( ANIMCHANNEL_LEGS ); + + // Start the crash & burn effects + for ( int i = 0; i < numHoverJoints; i++ ) { + if ( jointHover[i] != INVALID_JOINT ) { + PlayEffect ( "fx_hurt", jointHover[i], false ); + effectHover[i] = PlayEffect ( "fx_crash", jointHover[i], true ); + } + } + deathPitch = viewAxis.ToAngles()[0]; + deathRoll = viewAxis.ToAngles()[2]; + + deathPitchRate = gameLocal.random.RandomFloat()*0.3f + 0.1f; + deathYawRate = gameLocal.random.RandomFloat()*2.0f + 1.5f; + deathRollRate = gameLocal.random.RandomFloat()*3.0f + 1.0f; + deathSpeed = gameLocal.random.RandomFloat()*300.0f + 500.0f; + deathGrav = gameLocal.random.RandomFloat()*6.0f + 6.0f; + + strafeRight = (gameLocal.random.RandomFloat()>0.5f); + StopSound( SND_CHANNEL_HEART, false ); + StartSound ( "snd_crash", SND_CHANNEL_HEART, 0, false, NULL ); + } + return SRESULT_STAGE ( STAGE_SPIRAL ); + case STAGE_SPIRAL: + { + move.current_yaw += (strafeRight?-deathYawRate:deathYawRate); + deathPitch = idMath::ClampFloat( -90.0f, 90.0f, deathPitch+deathPitchRate ); + deathRoll += (strafeRight?deathRollRate:-deathRollRate); + viewAxis = idAngles( deathPitch, move.current_yaw, deathRoll ).ToMat3(); + + idVec3 vel = GetPhysics()->GetLinearVelocity(); + idVec3 strafeVel = viewAxis[0] * deathSpeed; + strafeVel.z = 0; + vel += strafeVel; + vel.Normalize(); + vel *= deathSpeed; + vel += GetPhysics()->GetGravity()/deathGrav; + + physicsObj.UseVelocityMove( true ); + + physicsObj.SetLinearVelocity( vel ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + refSound.parms.frequencyShift += 0.025f; + emitter->ModifySound ( SND_CHANNEL_HEART, &refSound.parms ); + } + } + return SRESULT_WAIT; + } + + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_StroggMarine.cpp b/src/mpgame/ai/Monster_StroggMarine.cpp new file mode 100644 index 0000000..17cf66e --- /dev/null +++ b/src/mpgame/ai/Monster_StroggMarine.cpp @@ -0,0 +1,776 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +//NOTE: actually a bit of a misnomer, as all Strogg Marine types use this class now... +class rvMonsterStroggMarine : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterStroggMarine ); + + rvMonsterStroggMarine ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); + + virtual bool CheckActions ( void ); + + int maxShots; + int minShots; + int shots; + int shotsFired; + + int fireAnimNum; + bool spraySideRight; + int sweepCount; + + bool EnemyMovingToRight ( void ); + +private: + + void CalculateShots ( void ); + + int nextShootTime; + int attackRate; + jointHandle_t attackJoint; + + // Actions + rvAIAction actionStrafe; + rvAIAction actionCrouchRangedAttack; + rvAIAction actionRollAttack; + rvAIAction actionSprayAttack; + rvAIAction actionAngry; + rvAIAction actionReload; + + virtual bool CheckAction_JumpBack ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); + virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); + bool CheckAction_Strafe ( rvAIAction* action, int animNum ); + virtual bool CheckAction_RangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ); + bool CheckAction_RollAttack ( rvAIAction* action, int animNum ); + bool CheckAction_SprayAttack ( rvAIAction* action, int animNum ); + bool CheckAction_Angry ( rvAIAction* action, int animNum ); + bool CheckAction_Reload ( rvAIAction* action, int animNum ); + + stateResult_t State_Torso_RollAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_RangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_MovingRangedAttack ( const stateParms_t& parms ); + stateResult_t State_Torso_SprayAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterStroggMarine ); +}; + +CLASS_DECLARATION( idAI, rvMonsterStroggMarine ) +END_CLASS + +/* +================ +rvMonsterStroggMarine::rvMonsterStroggMarine +================ +*/ +rvMonsterStroggMarine::rvMonsterStroggMarine ( ) { + nextShootTime = 0; +} + +void rvMonsterStroggMarine::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); + attackRate = SEC2MS( spawnArgs.GetFloat( "attackRate", "0.2" ) ); + attackJoint = animator.GetJointHandle( spawnArgs.GetString( "attackJoint", "muzzle" ) ); +} +/* +================ +rvMonsterStroggMarine::Spawn +================ +*/ +void rvMonsterStroggMarine::Spawn ( void ) { + actionStrafe.Init ( spawnArgs, "action_strafe", NULL, 0 ); + actionCrouchRangedAttack.Init ( spawnArgs, "action_crouchRangedAttack", NULL, AIACTIONF_ATTACK ); + actionRollAttack.Init ( spawnArgs, "action_rollAttack", NULL, AIACTIONF_ATTACK ); + actionSprayAttack.Init ( spawnArgs, "action_sprayAttack", "Torso_SprayAttack", AIACTIONF_ATTACK ); + actionAngry.Init ( spawnArgs, "action_angry", NULL, 0 ); + actionReload.Init ( spawnArgs, "action_reload", NULL, 0 ); + + InitSpawnArgsVariables(); + + shots = 0; + shotsFired = 0; +} + +/* +================ +rvMonsterStroggMarine::Save +================ +*/ +void rvMonsterStroggMarine::Save ( idSaveGame *savefile ) const { + actionStrafe.Save ( savefile ); + actionCrouchRangedAttack.Save( savefile ); + actionRollAttack.Save( savefile ); + actionSprayAttack.Save( savefile ); + actionAngry.Save( savefile ); + actionReload.Save( savefile ); + + savefile->WriteInt ( shots ); + savefile->WriteInt ( shotsFired ); + + savefile->WriteInt ( fireAnimNum ); + savefile->WriteBool ( spraySideRight ); + savefile->WriteInt ( sweepCount ); + + savefile->WriteInt ( nextShootTime ); +} + +/* +================ +rvMonsterStroggMarine::Restore +================ +*/ +void rvMonsterStroggMarine::Restore ( idRestoreGame *savefile ) { + actionStrafe.Restore ( savefile ); + actionCrouchRangedAttack.Restore( savefile ); + actionRollAttack.Restore( savefile ); + actionSprayAttack.Restore( savefile ); + actionAngry.Restore( savefile ); + actionReload.Restore( savefile ); + + savefile->ReadInt ( shots ); + savefile->ReadInt ( shotsFired ); + + savefile->ReadInt ( fireAnimNum ); + savefile->ReadBool ( spraySideRight ); + savefile->ReadInt ( sweepCount ); + + savefile->ReadInt ( nextShootTime ); + + InitSpawnArgsVariables(); +} + +/* +============ +rvMonsterStroggMarine::OnStopMoving +============ +*/ +void rvMonsterStroggMarine::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + //MCG - once you get to your position, attack immediately (no pause) + //FIXME: Restrict this some? Not after animmoves? Not if move was short? Only in certain tactical states? + if ( GetEnemy() ) + { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + } + else if ( combat.tacticalCurrent == AITACTICAL_MELEE ) + { + actionMeleeAttack.timer.Clear( actionTime ); + } + else + { + actionRangedAttack.timer.Clear( actionTime ); + actionTimerRangedAttack.Clear( actionTime ); + actionCrouchRangedAttack.timer.Clear( actionTime ); + actionRollAttack.timer.Clear( actionTime ); + actionSprayAttack.timer.Clear( actionTime ); + } + } +} + +/* +================ +rvMonsterStroggMarine::CheckAction_JumpBack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_JumpBack ( rvAIAction* action, int animNum ) { + // Jump back after taking damage + if ( !aifl.damage && gameLocal.time - pain.lastTakenTime > 1500 ) { + return false; + } + + // enemy must be in front to jump backwards + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + + // Can we actually move backwards? + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeLeft +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeLeft ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle >= 0 || gameLocal.time - combat.shotAtTime > 100 ) { + return false; + } + } + // TODO: dont evade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_EvadeRight +================ +*/ +bool rvMonsterStroggMarine::CheckAction_EvadeRight ( rvAIAction* action, int animNum ) { + if ( gameLocal.time - pain.lastTakenTime > 1500 ) { + if( combat.shotAtAngle < 0 || gameLocal.time - combat.shotAtTime > 100 ){ + return false; + } + } + // TODO: Dont eveade unless it was coming from directly in front of us + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Strafe +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Strafe ( rvAIAction* action, int animNum ) { + if ( !enemy.fl.visible ) { + return false; + } + + if ( !enemy.fl.inFov ) { + return false; + } + + if ( !move.fl.done ) { + return false; + } + + if ( !TestAnimMove ( animNum ) ) { + //well, at least try a new attack position + if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { + combat.tacticalUpdateTime = 0; + } + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RangedAttack ( rvAIAction* action, int animNum ) { + + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + return idAI::CheckAction_RangedAttack( action, animNum ); +} + +/* +================ +rvMonsterStroggMarine::CheckAction_CrouchRangedAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_CrouchRangedAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChanceInverse" ) + && enemy.range-action->minRange > gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the father away you are, the more likely you are to not attack + return false; + } + if ( spawnArgs.GetBool( "rangeAttackChance" ) + && enemy.range-action->minRange < gameLocal.random.RandomFloat()*(action->maxRange-action->minRange) ) { + //the closer you are, the more likely you are to not attack + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_RollAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_RollAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + if ( !TestAnimMove ( animNum ) ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_SprayAttack +================ +*/ +bool rvMonsterStroggMarine::CheckAction_SprayAttack ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) || enemy.ent->DistanceTo ( enemy.lastKnownPosition ) > 128.0f ) { + return false; + } + if ( GetEnemy()->GetPhysics()->GetLinearVelocity().Compare( vec3_origin ) ) + {//not moving + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Angry +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Angry ( rvAIAction* action, int animNum ) +{ + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckAction_Reload +================ +*/ +bool rvMonsterStroggMarine::CheckAction_Reload ( rvAIAction* action, int animNum ) { + if ( !enemy.ent || !enemy.fl.inFov || !enemy.fl.visible ) { + return false; + } + return true; +} + +/* +================ +rvMonsterStroggMarine::CheckActions +================ +*/ +bool rvMonsterStroggMarine::CheckActions ( void ) { + + if ( idAI::CheckActions ( ) ) + { + return true; + } + if ( PerformAction ( &actionCrouchRangedAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_CrouchRangedAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionRollAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_RollAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionSprayAttack, (checkAction_t)&rvMonsterStroggMarine::CheckAction_SprayAttack, &actionTimerRangedAttack ) || + PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Strafe ) || + PerformAction ( &actionAngry, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Angry ) || + PerformAction ( &actionReload, (checkAction_t)&rvMonsterStroggMarine::CheckAction_Reload ) ) { + return true; + } + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterStroggMarine ) + STATE ( "Torso_RollAttack", rvMonsterStroggMarine::State_Torso_RollAttack ) + STATE ( "Torso_RangedAttack", rvMonsterStroggMarine::State_Torso_RangedAttack ) + STATE ( "Torso_MovingRangedAttack", rvMonsterStroggMarine::State_Torso_MovingRangedAttack ) + STATE ( "Torso_SprayAttack", rvMonsterStroggMarine::State_Torso_SprayAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterStroggMarine::State_Torso_RollAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RollAttack ( const stateParms_t& parms ) { + enum { + TORSO_ROLLATTACK_ROLL, + TORSO_ROLLATTACK_FACE, + TORSO_ROLLATTACK_FIRE, + TORSO_ROLLATTACK_FINISH + }; + + TurnToward(enemy.lastKnownPosition); + + switch ( parms.stage ) { + // Start the roll attack animation + case TORSO_ROLLATTACK_ROLL: + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + // Play the roll + PlayAnim ( ANIMCHANNEL_TORSO, "dive_turn", parms.blendFrames ); + move.fl.noTurn = false; + //FaceEnemy(); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FACE ); + + // Wait for roll animation to finish + case TORSO_ROLLATTACK_FACE: + if ( AnimDone ( ANIMCHANNEL_LEGS, 6 ) ) { + return SRESULT_STAGE ( TORSO_ROLLATTACK_FIRE ); + } + return SRESULT_WAIT; + + // Play fire animation + case TORSO_ROLLATTACK_FIRE: + if ( !enemy.ent || !enemy.fl.visible ) + {//whoops! rolled out of LOS + return SRESULT_DONE; + } + if ( enemy.fl.inFov ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "shotgun_range_attack", parms.blendFrames ); + return SRESULT_STAGE ( TORSO_ROLLATTACK_FINISH ); + } + return SRESULT_WAIT; + + // Wait for fire animation to finish + case TORSO_ROLLATTACK_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvMonsterStroggMarine::CalculateShots +================ +*/ +void rvMonsterStroggMarine::CalculateShots ( void ) { + // Random number of shots ( scale by aggression range) + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + + // Update the firing animation playback rate + /* + int animNum; + animNum = GetAnim( ANIMCHANNEL_TORSO, fireAnim ); + if ( animNum != 0 ) { + const idAnim* anim = GetAnimator()->GetAnim ( animNum ); + if ( anim ) { + GetAnimator()->SetPlaybackRate ( animNum, ((float)anim->Length() * combat.aggressiveScale) / fireRate ); + } + } + */ +} + +/* +================ +rvMonsterStroggMarine::State_Torso_RangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_RangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + STAGE_END, + STAGE_END_WAIT, + }; + //TurnToward(enemy.lastKnownPosition); + switch ( parms.stage ) { + case STAGE_START: + // If moving switch to the moving ranged attack (torso only) + if ( move.fl.moving && move.fl.running && !actionRangedAttack.fl.overrideLegs && FacingIdeal() ) { + PostAnimState ( ANIMCHANNEL_TORSO, "Torso_MovingRangedAttack", parms.blendFrames ); + return SRESULT_DONE; + } + + // Full body animations + DisableAnimState ( ANIMCHANNEL_LEGS ); + + fireAnimNum = gameLocal.random.RandomInt(2)+1; + CalculateShots(); + shotsFired = 0; + + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_start", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_START_WAIT ); + } + + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_START_WAIT: + // When the pre shooting animation is done head over to shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_SHOOT ); + } + return SRESULT_WAIT; + + case STAGE_SHOOT: + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_loop", fireAnimNum), 0 ); + shots--; + shotsFired++; + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( shots <= 0 ) { + return SRESULT_STAGE ( STAGE_END ); + } + // If our enemy is no longer in our fov we can stop shooting + if ( !enemy.fl.inFov && shotsFired >= minShots ) { + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + + case STAGE_END: + // Attack lead in animation? + if ( HasAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), true ) ) { + PlayAnim ( ANIMCHANNEL_TORSO, va("range_attack%d_end", fireAnimNum), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_END_WAIT ); + } + return SRESULT_DONE; + + case STAGE_END_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::State_Torso_MovingRangedAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_MovingRangedAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_SHOOT, + STAGE_SHOOT_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + CalculateShots(); + shotsFired = 0; + return SRESULT_STAGE ( STAGE_SHOOT ); + + case STAGE_SHOOT: + shots--; + shotsFired++; + nextShootTime = gameLocal.GetTime() + attackRate; + if ( attackJoint != INVALID_JOINT ) { + Attack( "base", attackJoint, GetEnemy() ); + PlayEffect( "fx_blaster_muzzleflash", attackJoint ); + } + StartSound( "snd_weapon_fire", SND_CHANNEL_WEAPON, 0, false, 0 ); + /* + switch ( move.currentDirection ) + { + case MOVEDIR_RIGHT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_right", 0 ); + break; + case MOVEDIR_LEFT: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_left", 0 ); + break; + case MOVEDIR_BACKWARD: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso_back", 0 ); + break; + default: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack_torso", 0 ); + break; + } + */ + return SRESULT_STAGE ( STAGE_SHOOT_WAIT ); + + case STAGE_SHOOT_WAIT: + // When the shoot animation is done either play another shot animation + // or finish up with post_shooting + if ( gameLocal.GetTime() >= nextShootTime ) { + if ( shots <= 0 || (!enemy.fl.inFov && shotsFired >= minShots) || !move.fl.running || !move.fl.moving ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_SHOOT); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterStroggMarine::EnemyMovingToRight +================ +*/ +bool rvMonsterStroggMarine::EnemyMovingToRight( void ) +{ + if ( !GetEnemy() ) + { + return false; + } + //use their movement direction + idVec3 dir = GetEnemy()->GetPhysics()->GetLinearVelocity(); + //flatten + dir.z = 0; + dir.Normalize(); + + idVec3 fwd = viewAxis[0]; + idVec3 lt = viewAxis[1]; + + float dot = 0.0f; + dot = DotProduct(dir, lt); + if ( dot > 0 ) + { + return false; + } + else + { + return true; + } +} + +/* +================ +rvMonsterStroggMarine::State_Torso_SprayAttack +================ +*/ +stateResult_t rvMonsterStroggMarine::State_Torso_SprayAttack ( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_SWEEP, + STAGE_END, + STAGE_FINISH + }; + switch ( parms.stage ) { + case STAGE_START: + DisableAnimState ( ANIMCHANNEL_LEGS ); + spraySideRight = EnemyMovingToRight(); + sweepCount = 0; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_start", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_start", 0 ); + } + return SRESULT_STAGE ( STAGE_SWEEP ); + + case STAGE_SWEEP: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + sweepCount++; + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_sweep", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_sweep", 0 ); + } + return SRESULT_STAGE ( STAGE_END ); + } + return SRESULT_WAIT; + + case STAGE_END: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + bool curEnemyMovingRight = EnemyMovingToRight(); + if ( sweepCount < 3 + && (!gameLocal.random.RandomInt(2) + || (spraySideRight && !curEnemyMovingRight) + || (!spraySideRight && curEnemyMovingRight)) ) + { + spraySideRight = !spraySideRight; + return SRESULT_STAGE ( STAGE_SWEEP ); + } + else + { + if ( spraySideRight ) + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayright_end", 0 ); + } + else + { + PlayAnim ( ANIMCHANNEL_TORSO, "sprayleft_end", 0 ); + } + return SRESULT_STAGE ( STAGE_FINISH ); + } + } + return SRESULT_WAIT; + + case STAGE_FINISH: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_TeleportDropper.cpp b/src/mpgame/ai/Monster_TeleportDropper.cpp new file mode 100644 index 0000000..5fdcc56 --- /dev/null +++ b/src/mpgame/ai/Monster_TeleportDropper.cpp @@ -0,0 +1,559 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../spawner.h" +#include "../Projectile.h" +#include "AI_Manager.h" + +class rvMonsterTeleportDropper : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTeleportDropper ); + + rvMonsterTeleportDropper ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + + virtual idProjectile* AttackRanged ( const char* attackName, const idDict* projectileDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity = vec3_origin ); + virtual const char* GetIdleAnimName ( void ); + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); + + virtual bool CheckAction_LeapAttack ( rvAIAction* action, int animNum ); + +protected: + + idEntityPtr spawner; + + // Actions + rvAIAction actionDropSpawners; + + //bool dropAtGoalOnly; + +// virtual void OnStopMoving ( aiMoveCommand_t oldMoveCommand ); +// virtual bool MoveToEnemy ( void ); + + virtual int FilterTactical ( int availableTactical ); + + virtual bool CheckActions ( void ); + + virtual stateResult_t State_CombatHide ( const stateParms_t& parms ); + +private: + + bool leftSideBlocked; + bool rightSideBlocked; + jointHandle_t jointLeftFrontCannon; + jointHandle_t jointLeftRearCannon; + jointHandle_t jointRightFrontCannon; + jointHandle_t jointRightRearCannon; + + bool leapDidAttack; + + bool CheckAction_DropSpawners ( rvAIAction* action, int animNum ); + // Torso states + stateResult_t State_Torso_DropSpawners ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTeleportDropper ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTeleportDropper ) +END_CLASS + +/* +================ +rvMonsterTeleportDropper::rvMonsterTeleportDropper +================ +*/ +rvMonsterTeleportDropper::rvMonsterTeleportDropper ( void ) { + spawner = NULL; + leftSideBlocked = rightSideBlocked = false; + leapDidAttack = false; +// dropAtGoalOnly = false; +} + +void rvMonsterTeleportDropper::InitSpawnArgsVariables ( void ) +{ + jointLeftFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_front_cannon" ) ); + jointLeftRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_left_rear_cannon" ) ); + jointRightFrontCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_front_cannon" ) ); + jointRightRearCannon = animator.GetJointHandle ( spawnArgs.GetString ( "joint_right_rear_cannon" ) ); +} + +/* +================ +rvMonsterTeleportDropper::Spawn +================ +*/ +void rvMonsterTeleportDropper::Spawn ( void ) { + idEntity* ent; + idDict args; + + // Create the spawner entity + args.Clear ( ); + args.Set ( "classname", spawnArgs.GetString ( "def_spawner" ) ); + gameLocal.SpawnEntityDef ( args, &ent ); + + if ( ent ) { + spawner = static_cast(ent); + spawner->ProcessEvent ( &EV_Activate, this ); + } + + // Define actions + actionDropSpawners.Init ( spawnArgs, "action_dropSpawners", "Torso_DropSpawners", 0 ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Save +================ +*/ +void rvMonsterTeleportDropper::Save ( idSaveGame *savefile ) const { + spawner.Save( savefile ); + + actionDropSpawners.Save( savefile ); + savefile->WriteBool(leftSideBlocked); + savefile->WriteBool(rightSideBlocked); + savefile->WriteBool(leapDidAttack); +} + +/* +================ +rvMonsterTeleportDropper::Restore +================ +*/ +void rvMonsterTeleportDropper::Restore ( idRestoreGame *savefile ) { + spawner.Restore( savefile ); + + actionDropSpawners.Restore( savefile ); + savefile->ReadBool(leftSideBlocked); + savefile->ReadBool(rightSideBlocked); + savefile->ReadBool(leapDidAttack); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTeleportDropper::Think +================ +*/ +void rvMonsterTeleportDropper::Think ( void ) { + idAI::Think ( ); + if ( aifl.action && actionLeapAttack.status == rvAIAction::STATUS_OK && !leapDidAttack ) { + //in leap attack action + if ( GetEnemy() && enemy.fl.inFov && enemy.range < 64.0f ) + { + const idDict* attackDict; + attackDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_attack_leap" ), false ); + AttackMelee( "leap", attackDict ); + StartSound( "snd_leap_hit", SND_CHANNEL_BODY, 0, 0, 0 ); + leapDidAttack = true; + } + } +} + +/* +================ +rvMonsterTeleportDropper::State_CombatHide +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_CombatHide ( const stateParms_t& parms ) { + // Turn toward the enemy if visible but not in fov + if ( IsEnemyVisible ( ) ) { + if ( !move.fl.moving ) { + TurnToward ( enemy.lastKnownPosition ); + } + } + + if ( !(FilterTactical( combat.tacticalMaskAvailable )&AITACTICAL_HIDE_BIT) ) + {//shouldn't hide anymore + ForceTacticalUpdate(); + } + + if ( UpdateTactical ( 5000 ) ) { + return SRESULT_DONE_WAIT; + } + + // Perform actions + if ( UpdateAction ( ) ) { + return SRESULT_WAIT; + } + + return SRESULT_WAIT; +} + +/* +void rvMonsterTeleportDropper::OnStopMoving ( aiMoveCommand_t oldMoveCommand ) { + idAI::OnStopMoving(oldMoveCommand); + dropAtGoalOnly = false; +} + +bool rvMonsterTeleportDropper::MoveToEnemy ( void ) { + dropAtGoalOnly = false; + if ( !targets.Num() ) + { + return (idAI::MoveToEnemy()); + } + if ( !GetEnemy() ) + { + return false; + } + idEntity* goalEnt; + idEntity* bestGoal = NULL; + int areaNum = 0; + aasPath_t path; + idVec3 pos; + float dist; + float bestDist = actionDropSpawners.maxRange; + + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + if ( !aiManager.ValidateDestination(this,goalEnt->GetPhysics()->GetOrigin()) ) + { + continue; + } + dist = GetEnemy()->DistanceTo(pos); + if ( dist >= bestDist ) + { + continue; + } + // See if it's possible to get where we want to go + areaNum = 0; + if ( aas ) { + areaNum = PointReachableAreaNum( pos ); + aas->PushPointIntoAreaNum( areaNum, pos ); + + if ( !PathToGoal( path, PointReachableAreaNum( physicsObj.GetOrigin() ), physicsObj.GetOrigin(), areaNum, pos ) ) { + continue; + } + } + dist = bestDist; + bestGoal = goalEnt; + } + if ( bestGoal ) + { + if ( MoveToEntity( bestGoal, bestGoal->spawnArgs.GetFloat("range","64") ) ) + { + dropAtGoalOnly = true; + return true; + } + } + return (idAI::MoveToEnemy()); +} +*/ + +/* +================ +rvMonsterTeleportDropper::FilterTactical +================ +*/ +int rvMonsterTeleportDropper::FilterTactical ( int availableTactical ) { + //do normal filter + availableTactical = idAI::FilterTactical ( availableTactical ); + + //being tethered removes these - add them back in, we don't obey tethers! tethers are for chumps! + availableTactical |= (AITACTICAL_MELEE_BIT|AITACTICAL_HIDE_BIT); + + // Only allow hiding while the drop spawner action isnt ready + if ( !actionDropSpawners.timer.IsDone ( actionTime ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + // Hide while the spawner is still active + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + availableTactical &= AITACTICAL_HIDE_BIT; + } + + if ( leftSideBlocked && rightSideBlocked ) + {//both sides are blocked + if ( move.fl.done ) + {//not trying to move + //get out of here! + ForceTacticalUpdate(); + //try finding somewhere else to stand! + availableTactical |= AITACTICAL_RANGED_BIT; + } + } + return availableTactical; +} + +/* +================ +rvMonsterTeleportDropper::CheckAction_DropSpawners +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_DropSpawners ( rvAIAction* action, int animNum ) { + if ( !enemy.ent ) { + return false; + } + if ( spawner && (spawner->GetNumSpawnPoints ( ) || spawner->GetNumActive ( ) > 1 ) ) { + return false; + } + if ( !IsEnemyRecentlyVisible ( ) ) { + return false; + } + if ( animNum != -1 && !CanHitEnemyFromAnim( animNum ) ) { + return false; + } + //Check to see if at least one side is open + leftSideBlocked = rightSideBlocked = false; + + //trace against solid architecture only, don't care about other entities + trace_t wallTrace; + idVec3 start, end; + idMat3 axis; + start = GetPhysics()->GetCenterMass(); + //NOTE: ASSUMPTION + int mask = (MASK_SOLID|CONTENTS_LARGESHOTCLIP|CONTENTS_MOVEABLECLIP|CONTENTS_MONSTERCLIP); + //check left + end = start + (viewAxis[1] * 64.0f); + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //left side is blocked + leftSideBlocked = true; + } + //check right + end = start - (viewAxis[1] * 64.0f); + //trace against solid architecture only, don't care about other entities + gameLocal.TracePoint ( this, wallTrace, start, end, mask, this ); + if ( wallTrace.fraction < 1.0f ) { + //right side blocked + rightSideBlocked = true; + } + if ( leftSideBlocked && rightSideBlocked ) + { + return false; + } + + /* + if ( dropAtGoalOnly && !move.fl.done && move.moveCommand == MOVE_TO_ENTITY ) + {//FIXME: check ReachedPos? + return false; + } + //fuck it, just check them all + if ( targets.Num() ) + { + idEntity* goalEnt; + idVec3 pos; + float dist; + for( int i = 0; i < targets.Num(); i++ ) { + goalEnt = targets[ i ].GetEntity(); + if ( !goalEnt ) + { + continue; + } + pos = goalEnt->GetPhysics()->GetOrigin(); + dist = DistanceTo(pos); + if ( dist <= goalEnt->spawnArgs.GetFloat("range","64") ) + { + return true; + } + } + return false; + } + */ + + return true; +} + +/* +================ +rvMonsterTeleportDropper::Collide +================ +*/ + +/* +================ +rvMonsterTeleportDropper::CheckAction_LeapAttack +================ +*/ +bool rvMonsterTeleportDropper::CheckAction_LeapAttack ( rvAIAction* action, int animNum ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) + { + if ( !move.fl.done + && !move.fl.blocked ) + {//still running away + return false; + } + //done running, okay to attack if in range + } + else + { + if ( !leftSideBlocked || !rightSideBlocked ) + {//clear to shoot on sides + return false; + } + } + + if ( idAI::CheckAction_LeapAttack( action, animNum ) ) + { + leapDidAttack = false; + return true; + } + return false; +} + +/* +================ +rvMonsterTeleportDropper::CheckActions +================ +*/ +bool rvMonsterTeleportDropper::CheckActions ( void ) { + if ( PerformAction ( &actionDropSpawners, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_DropSpawners ) ) { + return true; + } + if ( CheckPainActions ( ) ) { + return true; + } + if ( PerformAction ( &actionLeapAttack, (checkAction_t)&rvMonsterTeleportDropper::CheckAction_LeapAttack ) ) { + return true; + } + + return false; +} + +/* +================ +rvMonsterTeleportDropper::AttackMissileExt +================ +*/ +idProjectile* rvMonsterTeleportDropper::AttackRanged ( const char* attackName, const idDict* attackDict, jointHandle_t joint, idEntity* target, const idVec3& pushVelocity ) { + idProjectile* proj; + + // Launch the projectile + if ( leftSideBlocked || rightSideBlocked ) + { + if ( idStr::Icmp( attackName, "dropSpawner" ) == 0 ) + { + if ( leftSideBlocked ) + { + if ( joint == jointLeftFrontCannon + || joint == jointLeftRearCannon ) + { + return NULL; + } + } + else if ( rightSideBlocked ) + { + if ( joint == jointRightFrontCannon + || joint == jointRightRearCannon ) + { + return NULL; + } + } + } + } + proj = idAI::AttackRanged ( attackName, attackDict, joint, target, pushVelocity ); + + if ( !proj ) { + return NULL; + } + + // If it was a spawner projectile set the spawer + if ( proj->IsType ( rvSpawnerProjectile::GetClassType() ) ) { + static_cast(proj)->SetSpawner ( spawner ); + } + + return proj; +} + +/* +================ +rvMonsterTeleportDropper::GetIdleAnimName +================ +*/ +const char* rvMonsterTeleportDropper::GetIdleAnimName ( void ) { + if ( combat.tacticalCurrent == AITACTICAL_HIDE ) { + return "idle_hide"; + } + return idAI::GetIdleAnimName ( ); +} + +/* +================ +rvMonsterTeleportDropper::GetDebugInfo +================ +*/ +void rvMonsterTeleportDropper::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { + // Base class first + idAI::GetDebugInfo ( proc, userData ); + + proc ( "rvMonsterTeleportDropper", "action_dropSpawners", aiActionStatusString[actionDropSpawners.status], userData ); + //proc ( "rvMonsterTeleportDropper", "dropAtGoalOnly", dropAtGoalOnly?"true":"false", userData ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTeleportDropper ) + STATE ( "State_CombatHide", rvMonsterTeleportDropper::State_CombatHide ) + STATE ( "Torso_DropSpawners", rvMonsterTeleportDropper::State_Torso_DropSpawners ) +END_CLASS_STATES + +/* +================ +rvMonsterTeleportDropper::State_Torso_DropSpawners +================ +*/ +stateResult_t rvMonsterTeleportDropper::State_Torso_DropSpawners ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_TORSO, "drop_spawners", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, parms.blendFrames ) ) { + ForceTacticalUpdate ( ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/src/mpgame/ai/Monster_Turret.cpp b/src/mpgame/ai/Monster_Turret.cpp new file mode 100644 index 0000000..2b2243f --- /dev/null +++ b/src/mpgame/ai/Monster_Turret.cpp @@ -0,0 +1,227 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurret : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurret ); + + rvMonsterTurret ( void ); + + void InitSpawnArgsVariables ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + +protected: + + virtual bool CheckActions ( void ); + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvMonsterTurret ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurret ) +END_CLASS + +/* +================ +rvMonsterTurret::rvMonsterTurret +================ +*/ +rvMonsterTurret::rvMonsterTurret ( ) { + shieldHealth = 0; +} + +void rvMonsterTurret::InitSpawnArgsVariables ( void ) { + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurret::Spawn +================ +*/ +void rvMonsterTurret::Spawn ( void ) { + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + health += shieldHealth; + + InitSpawnArgsVariables(); + shots = 0; +} + +/* +================ +rvMonsterTurret::Save +================ +*/ +void rvMonsterTurret::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ); +} + +/* +================ +rvMonsterTurret::Restore +================ +*/ +void rvMonsterTurret::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ); + + InitSpawnArgsVariables(); +} + +/* +================ +rvMonsterTurret::CheckActions +================ +*/ +bool rvMonsterTurret::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + +/* +================ +rvMonsterTurret::Pain +================ +*/ +bool rvMonsterTurret::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurret ) + STATE ( "State_Combat", rvMonsterTurret::State_Combat ) + STATE ( "State_Killed", rvMonsterTurret::State_Killed ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurret::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurret::State_Combat +================ +*/ +stateResult_t rvMonsterTurret::State_Combat ( const stateParms_t& parms ) { + // Aquire a new enemy if we dont have one + if ( !enemy.ent ) { + CheckForEnemy ( true ); + } + + FaceEnemy ( ); + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurret::State_Killed +================ +*/ +stateResult_t rvMonsterTurret::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_death" ), GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurret::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurret::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, "range_attack", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/Monster_TurretFlying.cpp b/src/mpgame/ai/Monster_TurretFlying.cpp new file mode 100644 index 0000000..3453387 --- /dev/null +++ b/src/mpgame/ai/Monster_TurretFlying.cpp @@ -0,0 +1,320 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +class rvMonsterTurretFlying : public idAI { +public: + + CLASS_PROTOTYPE( rvMonsterTurretFlying ); + + rvMonsterTurretFlying ( void ); + + void InitSpawnArgsVariables( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual bool Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + + virtual bool CheckActions ( void ); + +protected: + + stateResult_t State_Combat ( const stateParms_t& parms ); + stateResult_t State_Fall ( const stateParms_t& parms ); + stateResult_t State_Killed ( const stateParms_t& parms ); + + stateResult_t State_Torso_Idle ( const stateParms_t& parms ); + stateResult_t State_Legs_Idle ( const stateParms_t& parms ); + + stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); + + int shieldHealth; + int maxShots; + int minShots; + int shots; + +private: + + rvAIAction actionBlasterAttack; + + CLASS_STATES_PROTOTYPE ( rvMonsterTurretFlying ); +}; + +CLASS_DECLARATION( idAI, rvMonsterTurretFlying ) +END_CLASS + +/* +================ +rvMonsterTurretFlying::rvMonsterTurretFlying +================ +*/ +rvMonsterTurretFlying::rvMonsterTurretFlying ( ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); +} + +void rvMonsterTurretFlying::InitSpawnArgsVariables( void ) +{ + maxShots = spawnArgs.GetInt ( "maxShots", "1" ); + minShots = spawnArgs.GetInt ( "minShots", "1" ); +} +/* +================ +rvMonsterTurretFlying::Spawn +================ +*/ +void rvMonsterTurretFlying::Spawn ( void ) { + shieldHealth = spawnArgs.GetInt ( "shieldHealth" ); + + InitSpawnArgsVariables(); + shots = 0; + + actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); + + //don't take damage until we open up + fl.takedamage = false; +} + +/* +================ +rvMonsterTurretFlying::CheckActions +================ +*/ +bool rvMonsterTurretFlying::CheckActions ( void ) { + // Attacks + if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { + return true; + } + return idAI::CheckActions ( ); +} + + +/* +================ +rvMonsterTurretFlying::Pain +================ +*/ +bool rvMonsterTurretFlying::Pain ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + // Handle the shield effects + if ( shieldHealth > 0 ) { + shieldHealth -= damage; + if ( shieldHealth <= 0 ) { + PlayEffect ( "fx_shieldBreak", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } else { + PlayEffect ( "fx_shieldHit", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + } + } + + return idAI::Pain ( inflictor, attacker, damage, dir, location ); +} + +/* +================ +rvMonsterTurretFlying::Save +================ +*/ +void rvMonsterTurretFlying::Save( idSaveGame *savefile ) const { + savefile->WriteInt ( shieldHealth ); + savefile->WriteInt ( shots ); + actionBlasterAttack.Save ( savefile ) ; +} + +/* +================ +rvMonsterTurretFlying::Restore +================ +*/ +void rvMonsterTurretFlying::Restore( idRestoreGame *savefile ) { + savefile->ReadInt ( shieldHealth ); + savefile->ReadInt ( shots ); + actionBlasterAttack.Restore ( savefile ) ; + + InitSpawnArgsVariables(); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvMonsterTurretFlying ) + STATE ( "State_Combat", rvMonsterTurretFlying::State_Combat ) + STATE ( "State_Fall", rvMonsterTurretFlying::State_Fall ) + STATE ( "State_Killed", rvMonsterTurretFlying::State_Killed ) + + STATE ( "Torso_Idle", rvMonsterTurretFlying::State_Torso_Idle ) + STATE ( "Legs_Idle", rvMonsterTurretFlying::State_Legs_Idle ) + + STATE ( "Torso_BlasterAttack", rvMonsterTurretFlying::State_Torso_BlasterAttack ) +END_CLASS_STATES + +/* +================ +rvMonsterTurretFlying::State_Combat +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Combat ( const stateParms_t& parms ) { + // Special handling for not being on the ground + if ( !move.fl.onGround ) { + PostState ( "State_Fall", 0 ); + return SRESULT_DONE; + } + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + // try moving, if there was no movement run then just try and action instead + UpdateAction ( ); + + return SRESULT_WAIT; +} + +/* +================ +rvMonsterTurretFlying::State_Fall +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Fall ( const stateParms_t& parms ) { + enum { + STAGE_INIT, // Initialize fall stage + STAGE_WAITIMPACT, // Wait for the drop turret to hit the ground + STAGE_IMPACT, // Handle drop turret impact + STAGE_WAITOPEN, // Wait for drop turret to open up + STAGE_OPENED, // Drop turret opened and ready for combat + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopMove ( MOVE_STATUS_DONE ); + StartSound ( "snd_falling", SND_CHANNEL_VOICE, 0, false, NULL ); + PlayEffect ( "fx_droptrail", animator.GetJointHandle ( "origin" ), true ); + PlayCycle ( ANIMCHANNEL_TORSO, "idle_closed", 0 ); + return SRESULT_STAGE(STAGE_WAITIMPACT); + + case STAGE_WAITIMPACT: + if ( move.fl.onGround ) { + return SRESULT_STAGE(STAGE_IMPACT); + } + return SRESULT_WAIT; + + case STAGE_IMPACT: + StopSound ( SND_CHANNEL_VOICE, false ); + StopEffect ( "fx_droptrail" ); + PlayEffect ( "fx_landing", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + PlayAnim ( ANIMCHANNEL_TORSO, "open", 2 ); + return SRESULT_STAGE ( STAGE_WAITOPEN ); + + case STAGE_WAITOPEN: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + return SRESULT_STAGE ( STAGE_OPENED ); + } + return SRESULT_WAIT; + + case STAGE_OPENED: + // Activate shield + fl.takedamage = true; + health += shieldHealth; + PlayEffect ( "fx_shieldOpen", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + SetAnimState ( ANIMCHANNEL_TORSO, "Torso_Idle", 2 ); + PostState ( "State_Combat" ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvMonsterTurretFlying::State_Killed +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Killed ( const stateParms_t& parms ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() ); + gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, "textures/decals/genericdamage" ); + return idAI::State_Killed ( parms ); +} + +/* +================ +rvMonsterTurretFlying::State_Torso_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_Idle ( const stateParms_t& parms ) { + if ( move.fl.onGround ) { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_open", parms.blendFrames ); + } else { + IdleAnim ( ANIMCHANNEL_TORSO, "idle_closed", parms.blendFrames ); + } + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Legs_Idle +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Legs_Idle ( const stateParms_t& parms ) { + return SRESULT_DONE; +} + +/* +================ +rvMonsterTurretFlying::State_Torso_BlasterAttack +================ +*/ +stateResult_t rvMonsterTurretFlying::State_Torso_BlasterAttack ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + DisableAnimState ( ANIMCHANNEL_LEGS ); + shots = (minShots + gameLocal.random.RandomInt(maxShots-minShots+1)) * combat.aggressiveScale; + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + PlayAnim ( ANIMCHANNEL_TORSO, (shots&1)?"range_attack_top":"range_attack_bottom", 2 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 2 ) ) { + if ( --shots <= 0 ) { + return SRESULT_DONE; + } + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/ai/VehicleAI.cpp b/src/mpgame/ai/VehicleAI.cpp new file mode 100644 index 0000000..f74c975 --- /dev/null +++ b/src/mpgame/ai/VehicleAI.cpp @@ -0,0 +1,266 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// rvVehicleAI.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include + +#include "../Game_local.h" + +#ifndef __GAME_VEHICLEAI_H__ +#include "VehicleAI.h" +#endif + +CLASS_DECLARATION( idAI, rvVehicleAI ) + EVENT( VD_ChoosePathTarget, rvVehicleAI::Event_ChoosePathTarget ) +END_CLASS + +/* +================ +rvVehicleAI::rvVehicleAI +================ +*/ +rvVehicleAI::rvVehicleAI ( void ) { + flags = 0; +} + +/* +================ +rvVehicleAI::~rvVehicleAI +================ +*/ +rvVehicleAI::~rvVehicleAI ( void ) { +} + +/* +================ +rvVehicleAI::Spawn +================ +*/ +void rvVehicleAI::Spawn ( void ) { + driver = static_cast( gameLocal.SpawnEntityType( rvVehicleDriver::GetClassType() ) ); +} + +/* +================ +rvVehicleAI::Save +================ +*/ +void rvVehicleAI::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); + savefile->WriteInt ( flags ); +} + +/* +================ +rvVehicleAI::Restore +================ +*/ +void rvVehicleAI::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); + savefile->ReadInt ( flags ); +} + +/* +================ +rvVehicleAI::SetVehicle +================ +*/ +void rvVehicleAI::SetVehicle ( rvVehicleMonster * vehicle ) { + const idKeyValue * val = spawnArgs.MatchPrefix( "target", 0 ); + + if ( !driver ) { + return; + } + + driver->ProcessEvent ( &AI_EnterVehicle, vehicle ); + driver->SetPathingMode( rvVehicleDriver::VDPM_Custom, this ); + vehicle->ProcessEvent ( &EV_Door_Lock, true ); + vehicle->team = team; + driver->team = team; + + if ( val ) { + driver->ProcessEvent ( &AI_ScriptedMove, gameLocal.FindEntity( val->GetValue() ), 0.0f, 0 ); + } else { + driver->ProcessEvent ( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::Event_ChoosePathTarget +================ +*/ +void rvVehicleAI::Event_ChoosePathTarget ( idEntity * current ) { + if ( !current ) { + current = const_cast( FindClosestNode() ); + } + + if ( ( flags & VAIF_Freeze ) == 0 ) { + const idVec3 & playerOrigin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + switch ( flags & 0x03 ) { + case 1 : + idThread::ReturnEntity( MoveCloserTo( playerOrigin, current ) ); + break; + + case 2 : + idThread::ReturnEntity( MoveAwayFrom( playerOrigin, current ) ); + break; + + default: + idThread::ReturnEntity( NULL ); + } + } + + //PostEventMS( &VD_ChoosePathTarget, gameLocal.random.RandomInt( 100 ) + 100, driver->lastPathTargetInfo.node.GetEntity() ); +} + +/* +================ +rvVehicleAI::OnWakeUp +================ +*/ +void rvVehicleAI::OnWakeUp ( void ) { + SetMoveType ( MOVETYPE_CUSTOM ); +} + +/* +================ +rvVehicleAI::CustomMove +================ +*/ +void rvVehicleAI::CustomMove ( void ) { + if ( !driver->pathTargetInfo.node && !(flags & VAIF_Freeze) ) { + driver->ProcessEvent( &AI_ScriptedMove, FindClosestNode(), 0.0f, 0 ); + } +} + +/* +================ +rvVehicleAI::MoveCloserTo +================ +*/ +const idEntity * rvVehicleAI::MoveCloserTo ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float shortestDistance = FLT_MAX; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( shortestDistance > distance ) { + shortestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::MoveAwayFrom +================ +*/ +const idEntity * rvVehicleAI::MoveAwayFrom ( const idVec3 & point, idEntity * current ) { + const idEntity * best = NULL; + + if ( current && current->targets.Num() ) { + const idEntity & target = *current; + float longestDistance = FLT_MIN; + + for ( int i = target.targets.Num() - 1; i; i -- ) { + float distance = ( target.targets[ i ]->GetPhysics()->GetOrigin() - point ).LengthSqr(); + + if ( longestDistance < distance ) { + longestDistance = distance; + best = target.targets[ i ]; + } + } + } + + return best; +} + +/* +================ +rvVehicleAI::FindClosestNode +================ +*/ +const idEntity * rvVehicleAI::FindClosestNode ( void ) const { + idEntity * current = 0; + const idEntity * best = 0; + float bestDistance = FLT_MAX; + + for (;;) { + current = gameLocal.FindEntityUsingDef( current, "target_vehicle_path" ); + + if ( !current ) { + break; + } + + float dist = ( current->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist < bestDistance ) { + bestDistance = dist; + best = current; + } + } + + return best; +} + +/* +================ +rvVehicleAI::Think +================ +*/ +void rvVehicleAI::Think ( void ) { + idAI::Think(); + + // Keep the enemy status up to date + if ( !enemy.ent || enemy.fl.dead ) { + enemy.fl.dead = false; + CheckForEnemy ( true ); + } + + //HACK: always choose player as the enemy... something is broken somewhere, + // and this is a quick way to get some gameplay out of this for now. Deadlines pwn. + if ( driver && driver->IsDriving() ) { + enemy.ent = gameLocal.GetLocalPlayer(); + enemy.range = ( enemy.ent->GetPhysics()->GetOrigin() - driver->vehicleController.GetVehicle()->GetPhysics()->GetOrigin() ).Length(); + } +} + + diff --git a/src/mpgame/ai/VehicleAI.h b/src/mpgame/ai/VehicleAI.h new file mode 100644 index 0000000..505c0c8 --- /dev/null +++ b/src/mpgame/ai/VehicleAI.h @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// rvVehicleAI.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEAI_H__ +#define __GAME_VEHICLEAI_H__ + +#ifndef __AI_H__ +#include "AI.h" +#endif +#ifndef __GAME_VEHICLEMONSTER_H__ +#include "../vehicle/VehicleMonster.h" +#endif + +enum VehicleAI_Flags { + VAIF_Chase = 1, + VAIF_Avoid = 2, + VAIF_Freeze = 4, +}; + +class rvVehicleAI : public idAI { + friend class rvVehicleMonster; + +public: + CLASS_PROTOTYPE( rvVehicleAI ); + + rvVehicleAI ( void ); + ~rvVehicleAI ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void SetVehicle ( rvVehicleMonster * vehicle ); + + void Random ( void ); + void StraightToEnemy ( void ); + void ChaseEnemy ( void ); + void AvoidEnemy ( void ); + void Stop ( void ); + void Start ( void ); + + int & GetFlags ( void ) { return flags; } + + bool IsDriving ( void ) { return driver && driver->IsDriving(); } + rvVehicleDriver* GetDriver ( void ) { return driver.GetEntity(); } + const rvVehicleDriver* GetDriver ( void ) const { return driver.GetEntity(); } + +private: + virtual void OnWakeUp ( void ); + virtual void CustomMove ( void ); + + const idEntity * MoveCloserTo ( const idVec3 & point, idEntity * current ); + const idEntity * MoveAwayFrom ( const idVec3 & point, idEntity * current ); + const idEntity * FindClosestNode ( void ) const; + + void Event_ChoosePathTarget ( idEntity * current ); + + idEntityPtr driver; + int flags; +}; + +ID_INLINE void rvVehicleAI::Random ( void ) { + flags = 0; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::StraightToEnemy ( void ) { + driver->ProcessEvent( &AI_ScriptedMove, enemy.ent.GetEntity(), 0.0f, 0 ); +} + +ID_INLINE void rvVehicleAI::ChaseEnemy ( void ) { + flags = VAIF_Chase; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::AvoidEnemy ( void ) { + flags = VAIF_Avoid; + CustomMove(); +} + +ID_INLINE void rvVehicleAI::Stop ( void ) { + flags = ( flags & 0x03 ) | VAIF_Freeze; + driver->ProcessEvent( &AI_ScriptedStop ); +} + +ID_INLINE void rvVehicleAI::Start ( void ) { + flags = ( flags & 0x03 ) & ~VAIF_Freeze; + //driver->ProcessEvent( &EV_Activate, this ); + CustomMove(); +} + +#endif // __GAME_VEHICLEAI_H__ diff --git a/src/mpgame/anim/Anim.cpp b/src/mpgame/anim/Anim.cpp new file mode 100644 index 0000000..b42f220 --- /dev/null +++ b/src/mpgame/anim/Anim.cpp @@ -0,0 +1,1205 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +bool idAnimManager::forceExport = false; + +/*********************************************************************** + + idMD5Anim + +***********************************************************************/ + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::idMD5Anim() { + ref_count = 0; + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + totaldelta.Zero(); +} + +/* +==================== +idMD5Anim::idMD5Anim +==================== +*/ +idMD5Anim::~idMD5Anim() { + Free(); +} + +/* +==================== +idMD5Anim::Free +==================== +*/ +void idMD5Anim::Free( void ) { + numFrames = 0; + numJoints = 0; + frameRate = 24; + animLength = 0; + name = ""; + + totaldelta.Zero(); + + jointInfo.Clear(); + bounds.Clear(); + componentFrames.Clear(); +} + +/* +==================== +idMD5Anim::NumFrames +==================== +*/ +int idMD5Anim::NumFrames( void ) const { + return numFrames; +} + +/* +==================== +idMD5Anim::NumJoints +==================== +*/ +int idMD5Anim::NumJoints( void ) const { + return numJoints; +} + +/* +==================== +idMD5Anim::Length +==================== +*/ +int idMD5Anim::Length( void ) const { + return animLength; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const idVec3 &idMD5Anim::TotalMovementDelta( void ) const { + return totaldelta; +} + +/* +===================== +idMD5Anim::TotalMovementDelta +===================== +*/ +const char *idMD5Anim::Name( void ) const { + return name; +} + +/* +==================== +idMD5Anim::Reload +==================== +*/ +bool idMD5Anim::Reload( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + idStr filename; + + filename = name; + Free(); + + return LoadAnim( filename ); +} + +/* +==================== +idMD5Anim::Allocated +==================== +*/ +size_t idMD5Anim::Allocated( void ) const { + size_t size = bounds.Allocated() + jointInfo.Allocated() + componentFrames.Allocated() + name.Allocated(); + return size; +} + +/* +==================== +idMD5Anim::LoadAnim +==================== +*/ +bool idMD5Anim::LoadAnim( const char *filename ) { + int version; +// RAVEN BEGIN +// jsinger: done this way to minimize amount of code change + idAutoPtr lexer( LexerFactory::MakeLexer(LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT) ); + Lexer &parser(*lexer); +// RAVEN END + idToken token; + int i, j; + int num; + + if ( !parser.LoadFile( filename ) ) { + return false; + } + + Free(); + + name = filename; + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // skip the commandline + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num frames + parser.ExpectTokenString( "numFrames" ); + numFrames = parser.ParseInt(); + if ( numFrames <= 0 ) { + parser.Error( "Invalid number of frames: %d", numFrames ); + } + + // parse num joints + parser.ExpectTokenString( "numJoints" ); + numJoints = parser.ParseInt(); + if ( numJoints <= 0 ) { + parser.Error( "Invalid number of joints: %d", numJoints ); + } + + // parse frame rate + parser.ExpectTokenString( "frameRate" ); + frameRate = parser.ParseInt(); + if ( frameRate < 0 ) { + parser.Error( "Invalid frame rate: %d", frameRate ); + } + + // parse number of animated components + parser.ExpectTokenString( "numAnimatedComponents" ); + numAnimatedComponents = parser.ParseInt(); + if ( ( numAnimatedComponents < 0 ) || ( numAnimatedComponents > numJoints * 6 ) ) { + parser.Error( "Invalid number of animated components: %d", numAnimatedComponents ); + } + + // parse the hierarchy + jointInfo.SetGranularity( 1 ); + jointInfo.SetNum( numJoints ); + parser.ExpectTokenString( "hierarchy" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + parser.ReadToken( &token ); +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + jointInfo[ i ].nameIndex = animationLib->JointIndex( token ); +// RAVEN END + + // parse parent num + jointInfo[ i ].parentNum = parser.ParseInt(); + if ( jointInfo[ i ].parentNum >= i ) { + parser.Error( "Invalid parent num: %d", jointInfo[ i ].parentNum ); + } + + if ( ( i != 0 ) && ( jointInfo[ i ].parentNum < 0 ) ) { + parser.Error( "Animations may have only one root joint" ); + } + + // parse anim bits + jointInfo[ i ].animBits = parser.ParseInt(); + if ( jointInfo[ i ].animBits & ~63 ) { + parser.Error( "Invalid anim bits: %d", jointInfo[ i ].animBits ); + } + + // parse first component + jointInfo[ i ].firstComponent = parser.ParseInt(); + if ( ( numAnimatedComponents > 0 ) && ( ( jointInfo[ i ].firstComponent < 0 ) || ( jointInfo[ i ].firstComponent >= numAnimatedComponents ) ) ) { + parser.Error( "Invalid first component: %d", jointInfo[ i ].firstComponent ); + } + } + + parser.ExpectTokenString( "}" ); + + // parse bounds + parser.ExpectTokenString( "bounds" ); + parser.ExpectTokenString( "{" ); + bounds.SetGranularity( 1 ); + bounds.SetNum( numFrames ); + for( i = 0; i < numFrames; i++ ) { + parser.Parse1DMatrix( 3, bounds[ i ][ 0 ].ToFloatPtr() ); + parser.Parse1DMatrix( 3, bounds[ i ][ 1 ].ToFloatPtr() ); + } + parser.ExpectTokenString( "}" ); + + // parse base frame + baseFrame.SetGranularity( 1 ); + baseFrame.SetNum( numJoints ); + parser.ExpectTokenString( "baseframe" ); + parser.ExpectTokenString( "{" ); + for( i = 0; i < numJoints; i++ ) { + idCQuat q; + parser.Parse1DMatrix( 3, baseFrame[ i ].t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, q.ToFloatPtr() );//baseFrame[ i ].q.ToFloatPtr() ); + baseFrame[ i ].q = q.ToQuat();//.w = baseFrame[ i ].q.CalcW(); + } + parser.ExpectTokenString( "}" ); + + // parse frames + componentFrames.SetGranularity( 1 ); + componentFrames.SetNum( numAnimatedComponents * numFrames ); + + float *componentPtr = componentFrames.Ptr(); + for( i = 0; i < numFrames; i++ ) { + parser.ExpectTokenString( "frame" ); + num = parser.ParseInt(); + if ( num != i ) { + parser.Error( "Expected frame number %d", i ); + } + parser.ExpectTokenString( "{" ); + + for( j = 0; j < numAnimatedComponents; j++, componentPtr++ ) { + *componentPtr = parser.ParseFloat(); + } + + parser.ExpectTokenString( "}" ); + } + + // get total move delta + if ( !numAnimatedComponents ) { + totaldelta.Zero(); + } else { + componentPtr = &componentFrames[ jointInfo[ 0 ].firstComponent ]; + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.x; + } + totaldelta.x = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.x = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.y; + } + totaldelta.y = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + componentPtr++; + } else { + totaldelta.y = 0.0f; + } + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + for( i = 0; i < numFrames; i++ ) { + componentPtr[ numAnimatedComponents * i ] -= baseFrame[ 0 ].t.z; + } + totaldelta.z = componentPtr[ numAnimatedComponents * ( numFrames - 1 ) ]; + } else { + totaldelta.z = 0.0f; + } + } + baseFrame[ 0 ].t.Zero(); + + // we don't count last frame because it would cause a 1 frame pause at the end + animLength = ( ( numFrames - 1 ) * 1000 + frameRate - 1 ) / frameRate; + + // done + return true; +} + +/* +==================== +idMD5Anim::IncreaseRefs +==================== +*/ +void idMD5Anim::IncreaseRefs( void ) const { + ref_count++; +} + +/* +==================== +idMD5Anim::DecreaseRefs +==================== +*/ +void idMD5Anim::DecreaseRefs( void ) const { + ref_count--; +} + +/* +==================== +idMD5Anim::NumRefs +==================== +*/ +int idMD5Anim::NumRefs( void ) const { + return ref_count; +} + +/* +==================== +idMD5Anim::ConvertTimeToFrame +==================== +*/ +void idMD5Anim::ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const { + int frameTime; + int frameNum; + + if ( numFrames <= 1 ) { + frame.frame1 = 0; + frame.frame2 = 0; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + if ( time <= 0 ) { + frame.frame1 = 0; + frame.frame2 = 1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + frame.cycleCount = 0; + return; + } + + frameTime = time * frameRate; + frameNum = frameTime / 1000; + frame.cycleCount = frameNum / ( numFrames - 1 ); + + if ( ( cyclecount > 0 ) && ( frame.cycleCount >= cyclecount ) ) { + frame.cycleCount = cyclecount - 1; + frame.frame1 = numFrames - 1; + frame.frame2 = frame.frame1; + frame.backlerp = 0.0f; + frame.frontlerp = 1.0f; + return; + } + + frame.frame1 = frameNum % ( numFrames - 1 ); + frame.frame2 = frame.frame1 + 1; + if ( frame.frame2 >= numFrames ) { + frame.frame2 = 0; + } + + frame.backlerp = ( frameTime % 1000 ) * 0.001f; + frame.frontlerp = 1.0f - frame.backlerp; +} + +// RAVEN BEGIN +// jscott: added block +/* +==================== +idMD5Anim::ConvertFrameToTime + +MD5_FRAMERATE is always 24 +==================== +*/ +int idMD5Anim::ConvertFrameToTime( frameBlend_t &frame ) const { + int time; + + // Adjust the time so the lerping doesn't break the reverse calc + time = ( ( frame.frame1 % (numFrames - 1)) * 1000 ) / frameRate; + time += 500 / frameRate; + + return( time ); +} +// RAVEN END + +/* +==================== +idMD5Anim::GetOrigin +==================== +*/ +void idMD5Anim::GetOrigin( idVec3 &offset, int time, int cyclecount ) const { + frameBlend_t frame; + + offset = baseFrame[ 0 ].t; + if ( !( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) ) { + // just use the baseframe + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + + if ( frame.cycleCount ) { + offset += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetOriginRotation +==================== +*/ +void idMD5Anim::GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const { + frameBlend_t frame; + int animBits; + + animBits = jointInfo[ 0 ].animBits; + if ( !( animBits & ( ANIM_QX | ANIM_QY | ANIM_QZ ) ) ) { + // just use the baseframe + rotation = baseFrame[ 0 ].q; + return; + } + + ConvertTimeToFrame( time, cyclecount, frame ); + + const float *jointframe1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *jointframe2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( animBits & ANIM_TX ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TY ) { + jointframe1++; + jointframe2++; + } + + if ( animBits & ANIM_TZ ) { + jointframe1++; + jointframe2++; + } + + idQuat q1; + idQuat q2; + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case ANIM_QX: + q1.x = jointframe1[0]; + q2.x = jointframe2[0]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY: + q1.y = jointframe1[0]; + q2.y = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QZ: + q1.z = jointframe1[0]; + q2.z = jointframe2[0]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q1.z = baseFrame[ 0 ].q.z; + q2.z = q1.z; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + q1.x = jointframe1[0]; + q1.z = jointframe1[1]; + q2.x = jointframe2[0]; + q2.z = jointframe2[1]; + q1.y = baseFrame[ 0 ].q.y; + q2.y = q1.y; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + q1.y = jointframe1[0]; + q1.z = jointframe1[1]; + q2.y = jointframe2[0]; + q2.z = jointframe2[1]; + q1.x = baseFrame[ 0 ].q.x; + q2.x = q1.x; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + q1.x = jointframe1[0]; + q1.y = jointframe1[1]; + q1.z = jointframe1[2]; + q2.x = jointframe2[0]; + q2.y = jointframe2[1]; + q2.z = jointframe2[2]; + q1.w = q1.CalcW(); + q2.w = q2.CalcW(); + break; + } + + rotation.Slerp( q1, q2, frame.backlerp ); +} + +/* +==================== +idMD5Anim::GetBounds +==================== +*/ +void idMD5Anim::GetBounds( idBounds &bnds, int time, int cyclecount ) const { + frameBlend_t frame; + idVec3 offset; + + ConvertTimeToFrame( time, cyclecount, frame ); + + bnds = bounds[ frame.frame1 ]; + bnds.AddBounds( bounds[ frame.frame2 ] ); + + // origin position + offset = baseFrame[ 0 ].t; + if ( jointInfo[ 0 ].animBits & ( ANIM_TX | ANIM_TY | ANIM_TZ ) ) { + const float *componentPtr1 = &componentFrames[ numAnimatedComponents * frame.frame1 + jointInfo[ 0 ].firstComponent ]; + const float *componentPtr2 = &componentFrames[ numAnimatedComponents * frame.frame2 + jointInfo[ 0 ].firstComponent ]; + + if ( jointInfo[ 0 ].animBits & ANIM_TX ) { + offset.x = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TY ) { + offset.y = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + componentPtr1++; + componentPtr2++; + } + + if ( jointInfo[ 0 ].animBits & ANIM_TZ ) { + offset.z = *componentPtr1 * frame.frontlerp + *componentPtr2 * frame.backlerp; + } + } + + bnds[ 0 ] -= offset; + bnds[ 1 ] -= offset; +} + +/* +==================== +idMD5Anim::GetInterpolatedFrame +==================== +*/ +void idMD5Anim::GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const { + int i, numLerpJoints; + const float *frame1; + const float *frame2; + const float *jointframe1; + const float *jointframe2; + const jointAnimInfo_t *infoPtr; + int animBits; + idJointQuat *blendJoints; + idJointQuat *jointPtr; + idJointQuat *blendPtr; + int *lerpIndex; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return; + } +#endif + + if ( !numAnimatedComponents ) { + // just use the base frame + return; + } + + blendJoints = (idJointQuat *)_alloca16( baseFrame.Num() * sizeof( blendPtr[ 0 ] ) ); + lerpIndex = (int *)_alloca16( baseFrame.Num() * sizeof( lerpIndex[ 0 ] ) ); + numLerpJoints = 0; + + frame1 = &componentFrames[ frame.frame1 * numAnimatedComponents ]; + frame2 = &componentFrames[ frame.frame2 * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + blendPtr = &blendJoints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + lerpIndex[numLerpJoints++] = j; + + jointframe1 = frame1 + infoPtr->firstComponent; + jointframe2 = frame2 + infoPtr->firstComponent; + + switch( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + case 0: + blendPtr->t = jointPtr->t; + break; + case ANIM_TX: + jointPtr->t.x = jointframe1[0]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointPtr->t.y; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TY: + jointPtr->t.y = jointframe1[0]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.z = jointPtr->t.z; + jointframe1++; + jointframe2++; + break; + case ANIM_TZ: + jointPtr->t.z = jointframe1[0]; + blendPtr->t.z = jointframe2[0]; + blendPtr->t.x = jointPtr->t.x; + blendPtr->t.y = jointPtr->t.y; + jointframe1++; + jointframe2++; + break; + case ANIM_TX|ANIM_TY: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointPtr->t.z; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.y = jointPtr->t.y; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TY|ANIM_TZ: + jointPtr->t.y = jointframe1[0]; + jointPtr->t.z = jointframe1[1]; + blendPtr->t.y = jointframe2[0]; + blendPtr->t.z = jointframe2[1]; + blendPtr->t.x = jointPtr->t.x; + jointframe1 += 2; + jointframe2 += 2; + break; + case ANIM_TX|ANIM_TY|ANIM_TZ: + jointPtr->t.x = jointframe1[0]; + jointPtr->t.y = jointframe1[1]; + jointPtr->t.z = jointframe1[2]; + blendPtr->t.x = jointframe2[0]; + blendPtr->t.y = jointframe2[1]; + blendPtr->t.z = jointframe2[2]; + jointframe1 += 3; + jointframe2 += 3; + break; + } + + switch( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + case 0: + blendPtr->q = jointPtr->q; + break; + case ANIM_QX: + jointPtr->q.x = jointframe1[0]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointPtr->q.y; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY: + jointPtr->q.y = jointframe1[0]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QZ: + jointPtr->q.z = jointframe1[0]; + blendPtr->q.z = jointframe2[0]; + blendPtr->q.x = jointPtr->q.x; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointPtr->q.z; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.y = jointPtr->q.y; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QY|ANIM_QZ: + jointPtr->q.y = jointframe1[0]; + jointPtr->q.z = jointframe1[1]; + blendPtr->q.y = jointframe2[0]; + blendPtr->q.z = jointframe2[1]; + blendPtr->q.x = jointPtr->q.x; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + case ANIM_QX|ANIM_QY|ANIM_QZ: + jointPtr->q.x = jointframe1[0]; + jointPtr->q.y = jointframe1[1]; + jointPtr->q.z = jointframe1[2]; + blendPtr->q.x = jointframe2[0]; + blendPtr->q.y = jointframe2[1]; + blendPtr->q.z = jointframe2[2]; + jointPtr->q.w = jointPtr->q.CalcW(); + blendPtr->q.w = blendPtr->q.CalcW(); + break; + } + } + } + + SIMDProcessor->BlendJoints( joints, blendJoints, frame.backlerp, lerpIndex, numLerpJoints ); + + if ( frame.cycleCount ) { + joints[ 0 ].t += totaldelta * ( float )frame.cycleCount; + } +} + +/* +==================== +idMD5Anim::GetSingleFrame +==================== +*/ +void idMD5Anim::GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const { + int i; + const float *frame; + const float *jointframe; + int animBits; + idJointQuat *jointPtr; + const jointAnimInfo_t *infoPtr; + + // copy the baseframe + SIMDProcessor->Memcpy( joints, baseFrame.Ptr(), baseFrame.Num() * sizeof( baseFrame[ 0 ] ) ); + + if ( ( framenum == 0 ) || !numAnimatedComponents ) { + // just use the base frame + return; + } + + frame = &componentFrames[ framenum * numAnimatedComponents ]; + + for ( i = 0; i < numIndexes; i++ ) { + int j = index[i]; + jointPtr = &joints[j]; + infoPtr = &jointInfo[j]; + + animBits = infoPtr->animBits; + if ( animBits ) { + + jointframe = frame + infoPtr->firstComponent; + + if ( animBits & (ANIM_TX|ANIM_TY|ANIM_TZ) ) { + + if ( animBits & ANIM_TX ) { + jointPtr->t.x = *jointframe++; + } + + if ( animBits & ANIM_TY ) { + jointPtr->t.y = *jointframe++; + } + + if ( animBits & ANIM_TZ ) { + jointPtr->t.z = *jointframe++; + } + } + + if ( animBits & (ANIM_QX|ANIM_QY|ANIM_QZ) ) { + + if ( animBits & ANIM_QX ) { + jointPtr->q.x = *jointframe++; + } + + if ( animBits & ANIM_QY ) { + jointPtr->q.y = *jointframe++; + } + + if ( animBits & ANIM_QZ ) { + jointPtr->q.z = *jointframe; + } + + jointPtr->q.w = jointPtr->q.CalcW(); + } + } + } +} + +/* +==================== +idMD5Anim::CheckModelHierarchy +==================== +*/ +void idMD5Anim::CheckModelHierarchy( const idRenderModel *model ) const { + int i; + int jointNum; + int parent; + + if ( jointInfo.Num() != model->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Error( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), model->NumJoints(), name.c_str(), jointInfo.Num() ); +// scork: if we don't return here, we get dozens of other warnings generated by mismatching models below, one warning is sufficient... + if (common->DoingDeclValidation()) { + return; + } +// RAVEN END + } + + const idMD5Joint *modelJoints = model->GetJoints(); + for( i = 0; i < jointInfo.Num(); i++ ) { + jointNum = jointInfo[ i ].nameIndex; +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + if ( modelJoints[ i ].name != animationLib->JointName( jointNum ) ) { +// RAVEN END + gameLocal.Error( "Model '%s''s joint names don't match anim '%s''s", model->Name(), name.c_str() ); + } + if ( modelJoints[ i ].parent ) { + parent = modelJoints[ i ].parent - modelJoints; + } else { + parent = -1; + } + if ( parent != jointInfo[ i ].parentNum ) { + gameLocal.Error( "Model '%s' has different joint hierarchy than anim '%s'", model->Name(), name.c_str() ); + } + } +} + +/*********************************************************************** + + idAnimManager + +***********************************************************************/ + +/* +==================== +idAnimManager::idAnimManager +==================== +*/ +idAnimManager::idAnimManager() { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + insideLevelLoad=false; +#endif +// RAVEN END +} + +/* +==================== +idAnimManager::~idAnimManager +==================== +*/ +idAnimManager::~idAnimManager() { + Shutdown(); +} + +/* +==================== +idAnimManager::Shutdown +==================== +*/ +void idAnimManager::Shutdown( void ) { + animations.DeleteContents(); + jointnames.Clear(); + jointnamesHash.Free(); +} + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) +/* +==================== +idAnimManager::BeginLevelLoad +==================== +*/ +void idAnimManager::BeginLevelLoad( void ) +{ + insideLevelLoad = true; +} + +/* +==================== +idAnimManager::EndLevelLoad +==================== +*/ +void idAnimManager::EndLevelLoad( void ) +{ + insideLevelLoad = false; +} +#endif +// RAVEN END + +/* +==================== +idAnimManager::GetAnim +==================== +*/ +idMD5Anim *idAnimManager::GetAnim( const char *name ) { + idMD5Anim **animptrptr; + idMD5Anim *anim; + + // see if it has been asked for before + animptrptr = NULL; + if ( animations.Get( name, &animptrptr ) ) { + anim = *animptrptr; + } else { + idStr extension; + idStr filename = name; + + filename.ExtractFileExtension( extension ); + if ( extension != MD5_ANIM_EXT ) { +// RAVEN BEGIN +// nmckenzie: I'm not interested in debugging this blindly again. + gameLocal.Warning( "Animation '%s' doesn't have the correct extension for an animation file.", filename.c_str() ); +// RAVEN END + return NULL; + } + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_PUSH_SYS_HEAP_ID(insideLevelLoad?RV_HEAP_ID_LEVEL:RV_HEAP_ID_PERMANENT); +#endif +// RAVEN END + anim = new idMD5Anim(); +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + RV_POP_HEAP(); +#endif +// RAVEN END + if ( !anim->LoadAnim( filename ) ) { + gameLocal.Warning( "Couldn't load anim: '%s'", filename.c_str() ); + delete anim; + anim = NULL; + } + animations.Set( filename, anim ); + } + + return anim; +} + +/* +================ +idAnimManager::ReloadAnims +================ +*/ +void idAnimManager::ReloadAnims( void ) { + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + ( *animptr )->Reload(); + } + } +} + +/* +================ +idAnimManager::JointIndex +================ +*/ +int idAnimManager::JointIndex( const char *name ) { + int i, hash; + + hash = jointnamesHash.GenerateKey( name ); + for ( i = jointnamesHash.First( hash ); i != -1; i = jointnamesHash.Next( i ) ) { + if ( jointnames[i].Cmp( name ) == 0 ) { + return i; + } + } + + i = jointnames.Append( name ); + jointnamesHash.Add( hash, i ); + return i; +} + +/* +================ +idAnimManager::JointName +================ +*/ +const char *idAnimManager::JointName( int index ) const { + return jointnames[ index ]; +} + +/* +================ +idAnimManager::ListAnims +================ +*/ +void idAnimManager::ListAnims( void ) const { + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t s; + size_t namesize; + int num; + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + s = anim->Size(); + gameLocal.Printf( "%8d bytes : %2d refs : %s\n", s, anim->NumRefs(), anim->Name() ); + size += s; + num++; + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + gameLocal.Printf( "\n%d memory used in %d anims\n", size, num ); + gameLocal.Printf( "%d memory used in %d joint names\n", namesize, jointnames.Num() ); +} + +// RAVEN BEGIN +/* +================ +idAnimManager::PrintMemInfo +================ +*/ +void idAnimManager::PrintMemInfo( MemInfo *mi ) { + + int i; + idMD5Anim **animptr; + idMD5Anim *anim; + size_t size; + size_t namesize; + int num; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_anim.txt" ); + if( !f ) { + return; + } + + num = 0; + size = 0; + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + anim = *animptr; + size += anim->Size(); + num++; + + f->Printf( "%8d: %s\n", anim->Size(), anim->Name() ); + } + } + + namesize = jointnames.Size() + jointnamesHash.Size(); + for( i = 0; i < jointnames.Num(); i++ ) { + namesize += jointnames[ i ].Size(); + } + + mi->animsAssetsCount = num; + mi->animsAssetsTotal = namesize + size; + + f->Printf( "\nTotal anim bytes allocated: %s (%s items)\n", idStr::FormatNumber( mi->animsAssetsTotal ).c_str(), idStr::FormatNumber( mi->animsAssetsCount ).c_str() ); + fileSystem->CloseFile( f ); +} +// RAVEN END + +/* +================ +idAnimManager::FlushUnusedAnims +================ +*/ +void idAnimManager::FlushUnusedAnims( void ) { + + TIME_THIS_SCOPE( __FUNCLINE__); + + int i; + idMD5Anim **animptr; + idList removeAnims; + + for( i = 0; i < animations.Num(); i++ ) { + animptr = animations.GetIndex( i ); + if ( animptr && *animptr ) { + if ( ( *animptr )->NumRefs() <= 0 ) { + removeAnims.Append( *animptr ); + } + } + } + + for( i = 0; i < removeAnims.Num(); i++ ) { + animations.Remove( removeAnims[ i ]->Name() ); + delete removeAnims[ i ]; + } +} diff --git a/src/mpgame/anim/Anim.h b/src/mpgame/anim/Anim.h new file mode 100644 index 0000000..db0bf64 --- /dev/null +++ b/src/mpgame/anim/Anim.h @@ -0,0 +1,825 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#ifndef __ANIM_H__ +#define __ANIM_H__ + +// +// animation channels +// these can be changed by modmakers and licensees to be whatever they need. +const int ANIM_NumAnimChannels = 5; +const int ANIM_MaxAnimsPerChannel = 3; +const int ANIM_MaxSyncedAnims = 3; + +// +// animation channels. make sure to change script/doom_defs.script if you add any channels, or change their order +// +const int ANIMCHANNEL_ALL = 0; +const int ANIMCHANNEL_TORSO = 1; +const int ANIMCHANNEL_LEGS = 2; +const int ANIMCHANNEL_HEAD = 3; +const int ANIMCHANNEL_EYELIDS = 4; + +// for converting from 24 frames per second to milliseconds +ID_INLINE int FRAME2MS( int framenum ) { + return ( framenum * 1000 ) / 24; +} + +class idRenderModel; +class idAnimator; +class idAnimBlend; +class function_t; +class idEntity; +class idSaveGame; +class idRestoreGame; + +typedef struct { + int cycleCount; // how many times the anim has wrapped to the begining (0 for clamped anims) + int frame1; + int frame2; + float frontlerp; + float backlerp; +} frameBlend_t; + +typedef struct { + int nameIndex; + int parentNum; + int animBits; + int firstComponent; +} jointAnimInfo_t; + +typedef struct { + jointHandle_t num; + jointHandle_t parentNum; + int channel; +} jointInfo_t; + +// +// joint modifier modes. make sure to change script/doom_defs.script if you add any, or change their order. +// +typedef enum { + JOINTMOD_NONE, // no modification + JOINTMOD_LOCAL, // modifies the joint's position or orientation in joint local space + JOINTMOD_LOCAL_OVERRIDE, // sets the joint's position or orientation in joint local space + JOINTMOD_WORLD, // modifies joint's position or orientation in model space + JOINTMOD_WORLD_OVERRIDE, // sets the joint's position or orientation in model space + JOINTMOD_COLLAPSE +} jointModTransform_t; + +typedef struct { + jointHandle_t jointnum; + idMat3 mat; + idVec3 pos; + jointModTransform_t transform_pos; + jointModTransform_t transform_axis; + +// RAVEN BEGIN +// bdube: added more features to programmer controlled joints + idInterpolateAccelDecelLinear angularVelocity; + int lastTime; + + jointHandle_t collapseJoint; +// RAVEN END + +} jointMod_t; + +#define ANIM_TX BIT( 0 ) +#define ANIM_TY BIT( 1 ) +#define ANIM_TZ BIT( 2 ) +#define ANIM_QX BIT( 3 ) +#define ANIM_QY BIT( 4 ) +#define ANIM_QZ BIT( 5 ) + +typedef enum { + FC_SCRIPTFUNCTION, + FC_SCRIPTFUNCTIONOBJECT, + FC_EVENTFUNCTION, +// RAVEN BEGIN +// abahr: event call with parms + FC_EVENTFUNCTION_ARGS, +// RAVEN END + FC_SOUND, + FC_SOUND_VOICE, + FC_SOUND_VOICE2, + FC_SOUND_BODY, + FC_SOUND_BODY2, + FC_SOUND_BODY3, + FC_SOUND_WEAPON, + FC_SOUND_ITEM, + FC_SOUND_GLOBAL, + FC_SOUND_CHATTER, + FC_SKIN, + FC_TRIGGER, + FC_DIRECTDAMAGE, + FC_MUZZLEFLASH, + FC_FOOTSTEP, + FC_LEFTFOOT, + FC_RIGHTFOOT, + FC_ENABLE_EYE_FOCUS, + FC_DISABLE_EYE_FOCUS, + FC_FX, + FC_DISABLE_GRAVITY, + FC_ENABLE_GRAVITY, + FC_JUMP, + FC_ENABLE_CLIP, + FC_DISABLE_CLIP, + FC_ENABLE_WALK_IK, + FC_DISABLE_WALK_IK, + FC_ENABLE_LEG_IK, + FC_DISABLE_LEG_IK, + FC_RECORDDEMO, + FC_AVIGAME, + FC_GUIEVENT, + + FC_AI_ENABLE_PAIN, + FC_AI_DISABLE_PAIN, + FC_AI_ENABLE_DAMAGE, + FC_AI_DISABLE_DAMAGE, + FC_AI_LOCKENEMYORIGIN, + FC_AI_ATTACK, + FC_AI_ATTACK_MELEE, + + FC_AI_SPEAK, + FC_AI_SPEAK_RANDOM, +// MCG: for attachment managing + FC_ACT_ATTACH_HIDE, + FC_ACT_ATTACH_SHOW, + + FC_ENABLE_BLINKING, + FC_DISABLE_BLINKING, + FC_ENABLE_AUTOBLINK, + FC_DISABLE_AUTOBLINK, + + FC_COUNT + +} frameCommandType_t; + +// RAVEN BEGIN +// rjohnson: new camera frame commands +extern struct frameCommandInfo_t +{ + const char* name; + bool modview; + +} frameCommandInfo[FC_COUNT]; +// RAVEN END + +typedef struct { + int num; + int firstCommand; +} frameLookup_t; + +typedef struct { + frameCommandType_t type; + + idStr* string; + +// RAVEN BEGIN +// bdube: added joint + idStr* joint; + idStr* joint2; +// abahr: + idList* parmList; +// RAVEN END + + union { + const idSoundShader *soundShader; + const function_t *function; + const idDeclSkin *skin; + int index; +// RAVEN BEGIN +// bdube: effects + const idDecl *effect; + idStr* projectile; + idStr* melee; +// abahr: + const class idEventDef* event; +// RAVEN END + }; +} frameCommand_t; + +typedef struct { + bool prevent_idle_override : 1; + bool random_cycle_start : 1; + bool ai_no_turn : 1; + bool ai_no_look : 1; + bool ai_look_head_only : 1; + bool anim_turn : 1; + bool sync_cycle : 1; +} animFlags_t; + + +/* +============================================================================================== + + idModelExport + +============================================================================================== +*/ + +class idModelExport { +private: + void Reset( void ); + bool ParseOptions( idLexer &lex ); + int ParseExportSection( idParser &parser ); + + static bool CheckMayaInstall( void ); + static void LoadMayaDll( void ); + + bool ConvertMayaToMD5( void ); + static bool initialized; + +public: + idStr commandLine; + idStr src; + idStr dest; + bool force; + + idModelExport(); + + static void Shutdown( void ); + + int ExportDefFile( const char *filename ); + bool ExportModel( const char *model ); + bool ExportAnim( const char *anim ); + int ExportModels( const char *pathname, const char *extension ); +}; + +/* +============================================================================================== + + idMD5Anim + +============================================================================================== +*/ + +class idMD5Anim { +private: + int numFrames; + int frameRate; + int animLength; + int numJoints; + int numAnimatedComponents; + idList bounds; + idList jointInfo; + idList baseFrame; + idList componentFrames; + idStr name; + idVec3 totaldelta; + mutable int ref_count; + +public: + idMD5Anim(); + ~idMD5Anim(); + + void Free( void ); + bool Reload( void ); + size_t Allocated( void ) const; + size_t Size( void ) const { return sizeof( *this ) + Allocated(); }; + bool LoadAnim( const char *filename ); + + void IncreaseRefs( void ) const; + void DecreaseRefs( void ) const; + int NumRefs( void ) const; + + void CheckModelHierarchy( const idRenderModel *model ) const; + void GetInterpolatedFrame( const frameBlend_t &frame, idJointQuat *joints, const int *index, int numIndexes ) const; + void GetSingleFrame( int framenum, idJointQuat *joints, const int *index, int numIndexes ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumJoints( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + const char *Name( void ) const; + + void ConvertTimeToFrame( int time, int cyclecount, frameBlend_t &frame ) const; +// RAVEN BEGIN +// jscott: for modview + int ConvertFrameToTime( frameBlend_t &frame ) const; +// RAVEN END + + void GetOrigin( idVec3 &offset, int currentTime, int cyclecount ) const; + void GetOriginRotation( idQuat &rotation, int time, int cyclecount ) const; + void GetBounds( idBounds &bounds, int currentTime, int cyclecount ) const; +}; + +/* +============================================================================================== + + idAnim + +============================================================================================== +*/ + +class idAnim { +private: + const class idDeclModelDef *modelDef; + const idMD5Anim *anims[ ANIM_MaxSyncedAnims ]; + int numAnims; + idStr name; + idStr realname; + idList frameLookup; + idList frameCommands; + animFlags_t flags; + +// RAVEN BEGIN +// bdube: added anim speed + float rate; +// RAVEN END + +public: + idAnim(); + idAnim( const idDeclModelDef *modelDef, const idAnim *anim ); + ~idAnim(); + + void SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ); + const char *Name( void ) const; + const char *FullName( void ) const; + const idMD5Anim *MD5Anim( int num ) const; + const idDeclModelDef *ModelDef( void ) const; + int Length( void ) const; + int NumFrames( void ) const; + int NumAnims( void ) const; + const idVec3 &TotalMovementDelta( void ) const; + bool GetOrigin( idVec3 &offset, int animNum, int time, int cyclecount ) const; + bool GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const; + bool GetBounds( idBounds &bounds, int animNum, int time, int cyclecount ) const; +// RAVEN BEGIN +// bdube: frame command function that takes a list of frames + const char *AddFrameCommand( const class idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ); +// RAVEN END + void CallFrameCommands( idEntity *ent, int from, int to ) const; + bool HasFrameCommands( void ) const; + + // returns first frame (zero based) that command occurs. returns -1 if not found. + int FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const; + void SetAnimFlags( const animFlags_t &animflags ); + const animFlags_t &GetAnimFlags( void ) const; + +// RAVEN BEGIN +// bdube: added + void CallFrameCommandSound ( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const; + float GetPlaybackRate ( void ) const; + void SetPlaybackRate ( float rate ); +// jsinger: to support binary serialization/deserialization of idAnims +#ifdef RV_BINARYDECLS + idAnim( idDeclModelDef const *def, SerialInputStream &stream ); + void Write( SerialOutputStream &stream ) const; +#endif +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added configurable playback rate +ID_INLINE float idAnim::GetPlaybackRate ( void ) const { + return rate; +} + +ID_INLINE void idAnim::SetPlaybackRate ( float _rate ) { + rate = _rate; +} + +// RAVEN END + +/* +============================================================================================== + + idDeclModelDef + +============================================================================================== +*/ + +// RAVEN BEGIN +// jsinger; allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idDeclModelDef : public idDecl, public Serializable<'DMD '> { +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idDeclModelDef( SerialInputStream &stream ); +private: + int mNumChannels; +#else +class idDeclModelDef : public idDecl { +#endif +// RAVEN END +public: + idDeclModelDef(); + ~idDeclModelDef(); + + virtual size_t Size( void ) const; + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + +// RAVEN BEGIN +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + void Touch( void ) const; + + const idDeclSkin * GetDefaultSkin( void ) const; + const idJointQuat * GetDefaultPose( void ) const; + void SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const; + idRenderModel * ModelHandle( void ) const; + void GetJointList( const char *jointnames, idList &jointList ) const; + const jointInfo_t * FindJoint( const char *name ) const; + + int NumAnims( void ) const; + const idAnim * GetAnim( int index ) const; + int GetSpecificAnim( const char *name ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + const idDeclSkin * GetSkin( void ) const; + const char * GetModelName( void ) const; + const idList & Joints( void ) const; + const int * JointParents( void ) const; + int NumJoints( void ) const; + const jointInfo_t * GetJoint( int jointHandle ) const; + const char * GetJointName( int jointHandle ) const; + int NumJointsOnChannel( int channel ) const; + const int * GetChannelJoints( int channel ) const; + + const idVec3 & GetVisualOffset( void ) const; + +private: + void CopyDecl( const idDeclModelDef *decl ); + bool ParseAnim( idLexer &src, int numDefaultAnims ); + +private: + idVec3 offset; + idList joints; + idList jointParents; + idList channelJoints[ ANIM_NumAnimChannels ]; + idRenderModel * modelHandle; + idList anims; + const idDeclSkin * skin; +}; + +ID_INLINE const idAnim *idDeclModelDef::GetAnim( int index ) const { + if ( ( index < 1 ) || ( index > anims.Num() ) ) { + return NULL; + } + return anims[ index - 1 ]; +} + +/* +============================================================================================== + + idAnimBlend + +============================================================================================== +*/ + +class idAnimBlend { +private: + const class idDeclModelDef *modelDef; + int starttime; + int endtime; + int timeOffset; + float rate; + + int blendStartTime; + int blendDuration; + float blendStartValue; + float blendEndValue; + + float animWeights[ ANIM_MaxSyncedAnims ]; + short cycle; + short animNum; + bool allowMove; + bool allowFrameCommands; + bool useFrameBlend; + + frameBlend_t frameBlend; + + friend class idAnimator; + + void Reset( const idDeclModelDef *_modelDef ); + void CallFrameCommands( idEntity *ent, int fromtime, int totime ) const; +// RAVEN BEGIN +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( const idDeclModelDef *modelDef, int animnum, const frameBlend_t & frameBlend ); +// jshepard: added rate parameter so we can speed up/slow down animations. + void CycleAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); + void PlayAnim( const idDeclModelDef *modelDef, int animnum, int currenttime, int blendtime, float rate ); +// RAVEN END + bool BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOrigin, bool overrideBlend, bool printInfo ) const; + void BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const; + void BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const; + void BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const; + bool AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const; + +public: + idAnimBlend(); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ); + const char *AnimName( void ) const; + const char *AnimFullName( void ) const; + float GetWeight( int currenttime ) const; + float GetFinalWeight( void ) const; + void SetWeight( float newweight, int currenttime, int blendtime ); + int NumSyncedAnims( void ) const; + bool SetSyncedAnimWeight( int num, float weight ); + void Clear( int currentTime, int clearTime ); + bool IsDone( int currentTime ) const; + bool FrameHasChanged( int currentTime ) const; + int GetCycleCount( void ) const; + void SetCycleCount( int count ); + void SetPlaybackRate( int currentTime, float newRate ); + float GetPlaybackRate( void ) const; + void SetStartTime( int startTime ); + int GetStartTime( void ) const; + int GetEndTime( void ) const; + int GetFrameNumber( int currenttime ) const; + int AnimTime( int currenttime ) const; + int NumFrames( void ) const; + int Length( void ) const; + int PlayLength( void ) const; + void AllowMovement( bool allow ); + void AllowFrameCommands( bool allow ); + const idAnim *Anim( void ) const; + int AnimNum( void ) const; +}; + +ID_INLINE const idAnim *idAnimBlend::Anim( void ) const { + if ( !modelDef ) { + return NULL; + } + return modelDef->GetAnim( animNum ); +} + +/* +============================================================================================== + + idAFPoseJointMod + +============================================================================================== +*/ + +typedef enum { + AF_JOINTMOD_AXIS, + AF_JOINTMOD_ORIGIN, + AF_JOINTMOD_BOTH +} AFJointModType_t; + +class idAFPoseJointMod { +public: + idAFPoseJointMod( void ); + + AFJointModType_t mod; + idMat3 axis; + idVec3 origin; +}; + +ID_INLINE idAFPoseJointMod::idAFPoseJointMod( void ) { + mod = AF_JOINTMOD_AXIS; + axis.Identity(); + origin.Zero(); +} + +/* +============================================================================================== + + idAnimator + +============================================================================================== +*/ + +class idAnimator{ + public: + idAnimator(); + ~idAnimator(); + + size_t Allocated( void ) const; + size_t Size( void ) const; + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetEntity( idEntity *ent ); + idEntity *GetEntity( void ) const ; + void RemoveOriginOffset( bool remove ); + bool RemoveOrigin( void ) const; + + void GetJointList( const char *jointnames, idList &jointList ) const; + + int NumAnims( void ) const; + const idAnim *GetAnim( int index ) const; + int GetAnim( const char *name ) const; + bool HasAnim( const char *name ) const; + + void ServiceAnims( int fromtime, int totime ); + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation + bool IsAnimating ( int currentTime, bool IgnoreAF = false ) const; + bool IsBlending ( int channelNum, int currentTime ) const; +// RAVEN END + + void GetJoints( int *numJoints, idJointMat **jointsPtr ); + int NumJoints( void ) const; + jointHandle_t GetFirstChild( jointHandle_t jointnum ) const; + jointHandle_t GetFirstChild( const char *name ) const; + + idRenderModel *SetModel( const char *modelname ); + idRenderModel *ModelHandle( void ) const; + const idDeclModelDef *ModelDef( void ) const; + + void ForceUpdate( void ); + void ClearForceUpdate( void ); + bool CreateFrame( int animtime, bool force ); + bool FrameHasChanged( int animtime ) const; + void GetDelta( int fromtime, int totime, idVec3 &delta ) const; + bool GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const; + void GetOrigin( int currentTime, idVec3 &pos ) const; + bool GetBounds( int currentTime, idBounds &bounds ); + + idAnimBlend *CurrentAnim( int channelNum ); + void Clear( int channelNum, int currentTime, int cleartime ); + +// twhitaker & jscott: create new SetFrame that allows interpolation between arbitrary frames + void SetFrame( int channelNum, int animnum, const frameBlend_t & frameBlend ); + void CycleAnim( int channelNum, int animnum, int currenttime, int blendtime ); + void PlayAnim( int channelNum, int animnum, int currenttime, int blendTime ); + + // copies the current anim from fromChannelNum to channelNum. + // the copied anim will have frame commands disabled to avoid executing them twice. + void SyncAnimChannels( int channelNum, int fromChannelNum, int currenttime, int blendTime ); + + void SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); + void SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ); + void GetJointAxis( jointHandle_t jointnum, idMat3 &mat ); + void CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ); + void CollapseJoints ( const char* jointnames, jointHandle_t collapseJoint ); + void ClearJoint( jointHandle_t jointnum ); + void ClearAllJoints( void ); + +// RAVEN BEGIN +// bdube: more joint control functions + void AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int blendtime ); + void SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ); + idAngles GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ); + void ClearJointAngularVelocity ( jointHandle_t jointnum ); + +// jshepard: rate of playback change + void SetPlaybackRate(float multiplier); +// abahr: + void SetPlaybackRate( const char* animName, float rate ); + void SetPlaybackRate( int animHandle, float rate ); +// RAVEN END + + void InitAFPose( void ); + void SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ); + void FinishAFPose( int animnum, const idBounds &bounds, const int time ); + void SetAFPoseBlendWeight( float blendWeight ); + bool BlendAFPose( idJointQuat *blendFrame ) const; + void ClearAFPose( void ); + + void ClearAllAnims( int currentTime, int cleartime ); + + jointHandle_t GetJointHandle( const char *name ) const; + const char * GetJointName( jointHandle_t handle ) const; + int GetChannelForJoint( jointHandle_t joint ) const; + bool GetJointTransform( jointHandle_t jointHandle, int currenttime, idVec3 &offset, idMat3 &axis ); + bool GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ); + + const animFlags_t GetAnimFlags( int animnum ) const; + int NumFrames( int animnum ) const; + int NumSyncedAnims( int animnum ) const; + const char *AnimName( int animnum ) const; + const char *AnimFullName( int animnum ) const; +// RAVEN BEGIN +// rjohnson: more output for animators + const char *AnimMD5Name( int animnum, int index ) const; +// RAVEN END + int AnimLength( int animnum ) const; + const idVec3 &TotalMovementDelta( int animnum ) const; + +// RAVEN BEGIN +// nrausch: get the nearest joint to a segment - ignores joints behind the origin +// you can pass it a null jointList in order to test against all joints ( use NumJoints() for the count ) + jointHandle_t GetNearestJoint( const idVec3 &start, const idVec3 &end, int time, jointHandle_t *jointList, int cnt ); +//MCG + jointMod_t * FindExistingJointMod( jointHandle_t jointnum, int *index ); +// RAVEN END + +private: +// RAVEN BEGIN +// bdube: added methods + jointMod_t * FindJointMod ( jointHandle_t jointnum ); +// RAVEN END + + void FreeData( void ); + void PushAnims( int channel, int currentTime, int blendTime ); + +private: + const idDeclModelDef * modelDef; + idEntity * entity; + + idAnimBlend channels[ ANIM_NumAnimChannels ][ ANIM_MaxAnimsPerChannel ]; + idList jointMods; + int numJoints; + idJointMat * joints; + + mutable int lastTransformTime; // mutable because the value is updated in CreateFrame + mutable bool stoppedAnimatingUpdate; + bool removeOriginOffset; + bool forceUpdate; + + idBounds frameBounds; + + float AFPoseBlendWeight; + idList AFPoseJoints; + idList AFPoseJointMods; + idJointQuat * AFPoseJointFrame; + int AFPoseJointFrameSize; + idBounds AFPoseBounds; + int AFPoseTime; + +// RAVEN BEGIN +// jshepard: multiplier for the animation rate for all anims under this animator + float rateMultiplier; +// RAVEN END +}; + +ID_INLINE void idAnimator::SetPlaybackRate ( float _rate ) { + rateMultiplier = _rate; +} + +/* +============================================================================================== + + idAnimManager + +============================================================================================== +*/ + +class idAnimManager { +public: + idAnimManager(); + ~idAnimManager(); + + static bool forceExport; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + void BeginLevelLoad( void ); + void EndLevelLoad( void ); +#endif +// RAVEN END + void Shutdown( void ); + idMD5Anim * GetAnim( const char *name ); + void ReloadAnims( void ); + void ListAnims( void ) const; + void PrintMemInfo( MemInfo *mi ); + int JointIndex( const char *name ); + const char * JointName( int index ) const; + + void ClearAnimsInUse( void ); + void FlushUnusedAnims( void ); + +private: + idHashTable animations; + idStrList jointnames; + idHashIndex jointnamesHash; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + bool insideLevelLoad; +#endif +// RAVEN END +}; + +#endif /* !__ANIM_H__ */ + +// RAVEN END diff --git a/src/mpgame/anim/Anim_Blend.cpp b/src/mpgame/anim/Anim_Blend.cpp new file mode 100644 index 0000000..774adb2 --- /dev/null +++ b/src/mpgame/anim/Anim_Blend.cpp @@ -0,0 +1,6039 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// RAVEN BEGIN +// bdube: note that this file is no longer merged with Doom3 updates +// +// MERGE_DATE 09/30/2004 + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../../mpgame/Projectile.h" +#include "../ai/AI.h" + +static const char *channelNames[ ANIM_NumAnimChannels ] = { + "all", "torso", "legs", "head", "eyelids" +}; + +/*********************************************************************** + + idAnim + +***********************************************************************/ + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim() { + modelDef = NULL; +// RAVEN BEGIN +// bdube: added speed + rate = 1.0f; +// RAVEN END + numAnims = 0; + memset( anims, 0, sizeof( anims ) ); + memset( &flags, 0, sizeof( flags ) ); +} + +/* +===================== +idAnim::idAnim +===================== +*/ +idAnim::idAnim( const idDeclModelDef *modelDef, const idAnim *anim ) { + int i; + + this->modelDef = modelDef; + numAnims = anim->numAnims; + name = anim->name; + realname = anim->realname; + flags = anim->flags; +// RAVEN BEGIN +// bdube: copy speed info + rate = anim->rate; +// RAVEN END + + memset( anims, 0, sizeof( anims ) ); + for( i = 0; i < numAnims; i++ ) { + anims[ i ] = anim->anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + frameLookup.SetNum( anim->frameLookup.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( frameLookup.Ptr(), anim->frameLookup.Ptr(), frameLookup.MemoryUsed() ); +// RAVEN END + + frameCommands.SetNum( anim->frameCommands.Num() ); + for( i = 0; i < frameCommands.Num(); i++ ) { + frameCommands[ i ] = anim->frameCommands[ i ]; + if ( anim->frameCommands[ i ].string ) { + frameCommands[ i ].string = new idStr( *anim->frameCommands[ i ].string ); + } +// RAVEN BEGIN +// bdube: copy joint information + if ( anim->frameCommands[ i ].joint ) { + frameCommands[ i ].joint = new idStr ( *anim->frameCommands[i].joint ); + } + if ( anim->frameCommands[ i ].joint2 ) { + frameCommands[ i ].joint2 = new idStr ( *anim->frameCommands[i].joint2 ); + } +// abahr: + if ( anim->frameCommands[ i ].parmList ) { + frameCommands[ i ].parmList = new idList( *anim->frameCommands[i].parmList ); + } +// RAVEN END + } +} + +/* +===================== +idAnim::~idAnim +===================== +*/ +idAnim::~idAnim() { + int i; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + } + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joint support + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } +} + +/* +===================== +idAnim::SetAnim +===================== +*/ +void idAnim::SetAnim( const idDeclModelDef *modelDef, const char *sourcename, const char *animname, int num, const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ] ) { + int i; + + this->modelDef = modelDef; + + for( i = 0; i < numAnims; i++ ) { + anims[ i ]->DecreaseRefs(); + anims[ i ] = NULL; + } + + assert( ( num > 0 ) && ( num <= ANIM_MaxSyncedAnims ) ); + numAnims = num; + realname = sourcename; + name = animname; + + for( i = 0; i < num; i++ ) { + anims[ i ] = md5anims[ i ]; + anims[ i ]->IncreaseRefs(); + } + + memset( &flags, 0, sizeof( flags ) ); + + for( i = 0; i < frameCommands.Num(); i++ ) { + delete frameCommands[ i ].string; + +// RAVEN BEGIN +// bdube: joints as their own string + delete frameCommands[i].joint; + delete frameCommands[i].joint2; +// abahr: + SAFE_DELETE_PTR( frameCommands[i].parmList ); +// RAVEN END + } + + frameLookup.Clear(); + frameCommands.Clear(); +} + +/* +===================== +idAnim::Name +===================== +*/ +const char *idAnim::Name( void ) const { + return name; +} + +/* +===================== +idAnim::FullName +===================== +*/ +const char *idAnim::FullName( void ) const { + return realname; +} + +/* +===================== +idAnim::MD5Anim + +index 0 will never be NULL. Any anim >= NumAnims will return NULL. +===================== +*/ +const idMD5Anim *idAnim::MD5Anim( int num ) const { + if ( anims == NULL || anims[0] == NULL ) { + return NULL; + } + return anims[ num ]; +} + +/* +===================== +idAnim::ModelDef +===================== +*/ +const idDeclModelDef *idAnim::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnim::Length +===================== +*/ +int idAnim::Length( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->Length(); +} + +/* +===================== +idAnim::NumFrames +===================== +*/ +int idAnim::NumFrames( void ) const { + if ( !anims[ 0 ] ) { + return 0; + } + + return anims[ 0 ]->NumFrames(); +} + +/* +===================== +idAnim::NumAnims +===================== +*/ +int idAnim::NumAnims( void ) const { + return numAnims; +} + +/* +===================== +idAnim::TotalMovementDelta +===================== +*/ +const idVec3 &idAnim::TotalMovementDelta( void ) const { + if ( !anims[ 0 ] ) { + return vec3_zero; + } + + return anims[ 0 ]->TotalMovementDelta(); +} + +/* +===================== +idAnim::GetOrigin +===================== +*/ +bool idAnim::GetOrigin( idVec3 &offset, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + offset.Zero(); + return false; + } + + anims[ animNum ]->GetOrigin( offset, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetOriginRotation +===================== +*/ +bool idAnim::GetOriginRotation( idQuat &rotation, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + rotation.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + return false; + } + + anims[ animNum ]->GetOriginRotation( rotation, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::GetBounds +===================== +*/ +ID_INLINE bool idAnim::GetBounds( idBounds &bounds, int animNum, int currentTime, int cyclecount ) const { + if ( !anims[ animNum ] ) { + return false; + } + + anims[ animNum ]->GetBounds( bounds, currentTime, cyclecount ); + return true; +} + +/* +===================== +idAnim::AddFrameCommand + +Returns NULL if no error. +===================== +*/ +const char *idAnim::AddFrameCommand( const idDeclModelDef *modelDef, const idList& frames, idLexer &src, const idDict *def ) { + int i; + int index; + idStr text; + idStr funcname; + frameCommand_t fc; + idToken token; + + memset( &fc, 0, sizeof( fc ) ); + + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + + if ( token == "call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTION; + fc.function = gameLocal.program.FindFunction( token ); + if ( !fc.function ) { + return va( "Function '%s' not found", token.c_str() ); + } + } else if ( token == "object_call" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SCRIPTFUNCTIONOBJECT; + fc.string = new idStr( token ); + } else if ( token == "event" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_EVENTFUNCTION; + const idEventDef *ev = idEventDef::FindEvent( token ); + if ( !ev ) { + return va( "Event '%s' not found", token.c_str() ); + } + if ( ev->GetNumArgs() != 0 ) { + return va( "Event '%s' has arguments", token.c_str() ); + } + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// abahr: + else if( token == "eventArgs" ) { + src.ParseRestOfLine( token ); + if( token.Length() <= 0 ) { + return "Unexpected end of line"; + } + + fc.type = FC_EVENTFUNCTION_ARGS; + fc.parmList = new idList(); + token.Split( *fc.parmList, ' ' ); + fc.event = idEventDef::FindEvent( (*fc.parmList)[0] ); + if( !fc.event ) { + SAFE_DELETE_PTR( fc.parmList ); + return va( "Event '%s' not found", (*fc.parmList)[0] ); + } + + fc.parmList->RemoveIndex( 0 ); + } +// RAVEN END + else if ( token == "sound" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_voice2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_VOICE2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body2" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY2; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_body3" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_BODY3; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_weapon" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_WEAPON; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_global" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_GLOBAL; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_item" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_ITEM; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "sound_chatter" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SOUND_CHATTER; + if ( !token.Cmpn( "snd_", 4 ) ) { + fc.string = new idStr( token ); + } else { + fc.soundShader = declManager->FindSound( token ); + if ( fc.soundShader->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "Sound '%s' not found", token.c_str() ); + } + } + } else if ( token == "skin" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_SKIN; + if ( token == "none" ) { + fc.skin = NULL; + } else { + fc.skin = declManager->FindSkin( token ); + if ( !fc.skin ) { + return va( "Skin '%s' not found", token.c_str() ); + } + } + } else if ( token == "fx" ) { +// RAVEN BEGIN +// bdube: use Raven effect system + fc.type = FC_FX; + + // Get the effect name + if ( !src.ReadTokenOnLine( &token ) ) { + return va( "missing effect name" ); + } + + // Effect is indirect if it starts with fx_ + if ( !idStr::Icmpn ( token, "fx_", 3 ) ) { + fc.string = new idStr ( token ); + } else { + fc.effect = ( const idDecl * )declManager->FindEffect( token ); + } + + // Joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint = new idStr ( token ); + } + + // End joint specified? + if ( src.ReadTokenOnLine ( &token ) ) { + fc.joint2 = new idStr ( token ); + } +// RAVEN END + } else if ( token == "trigger" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER; + fc.string = new idStr( token ); +// RAVEN BEGIN +// bdube: not using +/* + } else if ( token == "triggerSmokeParticle" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_TRIGGER_SMOKE_PARTICLE; + fc.string = new idStr( token ); +*/ +// RAVEN END + } else if ( token == "direct_damage" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DIRECTDAMAGE; + if ( !gameLocal.FindEntityDef( token.c_str(), false ) ) { + return va( "Unknown entityDef '%s'", token.c_str() ); + } + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + if ( ( token != "" ) && !modelDef->FindJoint( token ) ) { + return va( "Joint '%s' not found", token.c_str() ); + } + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( token ); + } else if ( token == "muzzle_flash" ) { + fc.type = FC_MUZZLEFLASH; + fc.string = new idStr( "" ); + } else if ( token == "footstep" ) { + fc.type = FC_FOOTSTEP; + } else if ( token == "leftfoot" ) { + fc.type = FC_LEFTFOOT; + } else if ( token == "rightfoot" ) { + fc.type = FC_RIGHTFOOT; + } else if ( token == "enableEyeFocus" ) { + fc.type = FC_ENABLE_EYE_FOCUS; + } else if ( token == "disableEyeFocus" ) { + fc.type = FC_DISABLE_EYE_FOCUS; + } else if ( token == "enableBlinking" ) { + fc.type = FC_ENABLE_BLINKING; + } else if ( token == "disableBlinking" ) { + fc.type = FC_DISABLE_BLINKING; + } else if ( token == "enableAutoBlink" ) { + fc.type = FC_ENABLE_AUTOBLINK; + } else if ( token == "disableAutoBlink" ) { + fc.type = FC_DISABLE_AUTOBLINK; + } else if ( token == "disableGravity" ) { + fc.type = FC_DISABLE_GRAVITY; + } else if ( token == "enableGravity" ) { + fc.type = FC_ENABLE_GRAVITY; + } else if ( token == "jump" ) { + fc.type = FC_JUMP; + } else if ( token == "enableClip" ) { + fc.type = FC_ENABLE_CLIP; + } else if ( token == "disableClip" ) { + fc.type = FC_DISABLE_CLIP; + } else if ( token == "enableWalkIK" ) { + fc.type = FC_ENABLE_WALK_IK; + } else if ( token == "disableWalkIK" ) { + fc.type = FC_DISABLE_WALK_IK; + } else if ( token == "enableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_ENABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "disableLegIK" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line"; + } + fc.type = FC_DISABLE_LEG_IK; + fc.index = atoi( token ); + } else if ( token == "recordDemo" ) { + fc.type = FC_RECORDDEMO; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "aviGame" ) { + fc.type = FC_AVIGAME; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN BEGIN +// bdube: added script commands + } else if ( token == "ai_enablePain" ) { + fc.type = FC_AI_ENABLE_PAIN; + } else if ( token == "ai_disablePain" ) { + fc.type = FC_AI_DISABLE_PAIN; + } else if ( token == "ai_enableDamage" ) { + fc.type = FC_AI_ENABLE_DAMAGE; + } else if ( token == "ai_disableDamage" ) { + fc.type = FC_AI_DISABLE_DAMAGE; + } else if ( token == "ai_lockEnemyOrigin" ) { + fc.type = FC_AI_LOCKENEMYORIGIN; + } else if ( token == "ai_attack" ) { + fc.type = FC_AI_ATTACK; + + // Name of attack + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack Name"; + } + fc.string = new idStr( token ); + + // Joint to attack from + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for attack joint"; + } + fc.joint = new idStr( token ); + } else if ( token == "ai_attack_melee" ) { + if( !src.ReadTokenOnLine( &token ) ) { + return "Unexpected end of line while looking for melee attack name"; + } + fc.type = FC_AI_ATTACK_MELEE; + fc.string = new idStr( token ); + } else if ( token == "guievent" ) { + fc.type = FC_GUIEVENT; + if( src.ReadTokenOnLine( &token ) ) + { + fc.string = new idStr( token ); + } + } else if ( token == "speak" ) { + fc.type = FC_AI_SPEAK; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "speak_random" ) { + fc.type = FC_AI_SPEAK_RANDOM; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +//MCG - added attachment frame commands + } else if ( token == "attachment_hide" ) { + fc.type = FC_ACT_ATTACH_HIDE; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } + } else if ( token == "attachment_show" ) { + fc.type = FC_ACT_ATTACH_SHOW; + if( src.ReadTokenOnLine( &token ) ) { + fc.string = new idStr( token ); + } +// RAVEN END + } else { + return va( "Unknown command '%s'", token.c_str() ); + } + + // check if we've initialized the frame loopup table + if ( !frameLookup.Num() ) { + // we haven't, so allocate the table and initialize it + frameLookup.SetGranularity( 1 ); + frameLookup.SetNum( anims[ 0 ]->NumFrames() ); + for( i = 0; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].num = 0; + frameLookup[ i ].firstCommand = 0; + } + } + +// RAVEN BEGIN +// bdube: support multiple frames + for ( int ii = 0; ii < frames.Num(); ii ++ ) { + int framenum = frames[ii]; + +// mekberg: error out of frame command is out of range. +// -1 because we don't want commands on the loop frame. +// If the anim doesn't loop they won't get handled. + if ( ( framenum < 1 ) || ( framenum > anims[ 0 ]->NumFrames() -1 ) ) { + gameLocal.Error("Frame command out of range: %d on anim '%s'. Max %d.", framenum, anims[ 0 ]->Name(), anims[ 0 ]->NumFrames() -1 ); + } + + // Duplicate the frame info + if ( ii != 0 ) { + if ( fc.string ) { + fc.string = new idStr ( fc.string->c_str() ); + } + if ( fc.joint ) { + fc.joint = new idStr ( fc.joint->c_str() ); + } + if ( fc.joint2 ) { + fc.joint2 = new idStr ( fc.joint2->c_str() ); + } + if ( fc.parmList ) { + fc.parmList = new idList( *fc.parmList ); + } + } + + // frame numbers are 1 based in .def files, but 0 based internally + framenum--; +// RAVEN END + + // allocate space for a new command + frameCommands.Alloc(); + + // calculate the index of the new command + index = frameLookup[ framenum ].firstCommand + frameLookup[ framenum ].num; + + // move all commands from our index onward up one to give us space for our new command + for( i = frameCommands.Num() - 1; i > index; i-- ) { + frameCommands[ i ] = frameCommands[ i - 1 ]; + } + + // fix the indices of any later frames to account for the inserted command + for( i = framenum + 1; i < frameLookup.Num(); i++ ) { + frameLookup[ i ].firstCommand++; + } + + // store the new command + frameCommands[ index ] = fc; + + // increase the number of commands on this frame + frameLookup[ framenum ].num++; + +// RAVEN BEGIN +// bdube: loop frame commands + } +// RAVEN END + + // return with no error + return NULL; +} + +// RAVEN BEGIN +// bdube: added for debugging +struct frameCommandInfo_t frameCommandInfo[FC_COUNT] = { + { "call", false }, + { "object_call", false }, + { "event", false }, + { "eventArgs", false }, + + { "sound", true }, + { "sound_voice", true }, + { "sound_voice2", true }, + { "sound_body", true }, + { "sound_body2", true }, + { "sound_body3", true }, + { "sound_weapon", true }, + { "sound_item", true }, + { "sound_global", true }, + { "sound_chatter", true }, + + { "skin", true }, + { "trigger", false }, + { "direct_damage", false }, + { "muzzle_flash", false }, + { "footstep", false }, + { "leftfoot", false }, + { "rightfoot", false }, + { "enableEyeFocus", false }, + { "disableEyeFocus", false }, + { "effect", true }, + { "disable_gravity", false }, + { "enable_gravity", false }, + { "jump", false }, + { "enableClip", false }, + { "disableClip", false }, + { "enableWalkIK", false }, + { "disableWalkIK", false }, + { "enableLegIK", false }, + { "disableLegID", false }, + { "recordDemo", false }, + { "aviGame", false }, + { "guievent", false }, + + { "ai_enablePain", false }, + { "ai_disablePain", false }, + { "ai_enableDamage", false }, + { "ai_disableDamage", false }, + { "ai_lockEnemyOrigin", false }, + { "ai_attack", false }, + { "ai_attack_melee", false }, + { "speak", true }, +}; +// RAVEN END + +// RAVEN BEGIN +// bdube: added frame command methods +/* +===================== +idAnim::CallFrameCommandSound +===================== +*/ +void idAnim::CallFrameCommandSound( const frameCommand_t& command, idEntity* ent, const s_channelType channel ) const { + + int flags = 0; + if ( channel == ( FC_SOUND_GLOBAL - FC_SOUND ) ) { + flags = SSF_PRIVATE_SOUND; + } + + if ( command.string ) { + + // ignore frame command jump sounds + // we trigger jump sounds from the player physics and explicitely send them over the network + if ( command.string->Icmp( "snd_jump" ) == 0 ) { + return; + } + + ent->StartSound( command.string->c_str(), channel, flags, false, NULL ); + } else { + ent->StartSoundShader( command.soundShader, channel, flags, false, NULL ); + } +} +// RAVEN END + +/* +===================== +idAnim::CallFrameCommands +===================== +*/ +void idAnim::CallFrameCommands( idEntity *ent, int from, int to ) const { + int index; + int end; + int frame; + int numframes; + + numframes = anims[ 0 ]->NumFrames(); + + frame = from; + while( frame != to ) { + frame++; + if ( frame >= numframes ) { + frame = 0; + } + + index = frameLookup[ frame ].firstCommand; + end = index + frameLookup[ frame ].num; + while( index < end ) { + const frameCommand_t &command = frameCommands[ index++ ]; + +// RAVEN BEGIN +// bdube: frame command debugging + if ( g_showFrameCmds.GetBool() ) { + idStr shortName; + shortName = name; + shortName.StripPath(); + shortName.StripFileExtension ( ); + gameLocal.Printf ( "framecmd: anim=%s frame=%d cmd=%s parm=%s\n", + shortName.c_str(), + frame + 1, + frameCommandInfo[command.type].name, + command.string?command.string->c_str():"???" ); + } + + if ( ( gameLocal.editors & EDITOR_MODVIEW ) && !frameCommandInfo[command.type].modview ) { + continue; + } +// RAVEN END + + switch( command.type ) { + case FC_SCRIPTFUNCTION: { + gameLocal.CallFrameCommand( ent, command.function ); + break; + } +// RAVEN BEGIN +// bdube: rewrote + case FC_SCRIPTFUNCTIONOBJECT: { + ent->ProcessEvent ( &EV_CallFunction, command.string->c_str() ); + break; + } +// RAVEN END + case FC_EVENTFUNCTION: { + const idEventDef *ev = idEventDef::FindEvent( command.string->c_str() ); + ent->ProcessEvent( ev ); + break; + } +// RAVEN BEGIN +// abahr: + case FC_EVENTFUNCTION_ARGS: { + assert( command.event ); + ent->ProcessEvent( command.event, (int)command.parmList ); + break; + } +// bdube: support indirection and simplify + case FC_SOUND: + case FC_SOUND_VOICE: + case FC_SOUND_VOICE2: + case FC_SOUND_BODY: + case FC_SOUND_BODY2: + case FC_SOUND_BODY3: + case FC_SOUND_WEAPON: + case FC_SOUND_ITEM: + case FC_SOUND_GLOBAL: + case FC_SOUND_CHATTER: + CallFrameCommandSound ( command, ent, (const s_channelType)(command.type - FC_SOUND) ); + break; +// RAVEN END + + case FC_FX: { + + if ( gameLocal.localClientNum == -1 ) { + // early ret on dedicated server + break; + } +// RAVEN BEGIN +// bdube: use raven effect system + rvClientEffect* cent; + if ( command.string ) { + if ( command.joint ) { + cent = ent->PlayEffect( command.string->c_str(), ent->GetAnimator()->GetJointHandle ( *command.joint ) ); + } else { + cent = gameLocal.PlayEffect( ent->spawnArgs, command.string->c_str(), ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } else { + if ( command.joint ) { + cent = ent->PlayEffect( command.effect, ent->GetAnimator()->GetJointHandle ( *command.joint ), vec3_zero, mat3_identity ); + } else { + cent = gameLocal.PlayEffect( command.effect, ent->GetRenderEntity()->origin, ent->GetRenderEntity()->axis ); + } + } + // End origin bone specified? + if ( cent && command.joint2 && ent->IsType( idAnimatedEntity::GetClassType() ) ) { + cent->SetEndOrigin( ent->GetAnimator()->GetJointHandle( *command.joint2 ) ); + } + + // Error print should the effect fail to play + if ( !cent ) { + idStr error = "Failed to play effect"; + + if( command.string ) { + error += va( " \'%s\'", command.string->c_str() ); + } + if ( command.effect ) { + error += va( " \'%s\'", command.effect->GetName() ); + } + if( command.joint ) { + error += va( " on bone \'%s\'", command.joint->c_str() ); + } + common->Warning( error.c_str() ); + } +// RAVEN END + break; + } + case FC_SKIN: + ent->SetSkin( command.skin ); + break; + + case FC_TRIGGER: { + idEntity *target; + + target = gameLocal.FindEntity( command.string->c_str() ); + if ( target ) { + target->Signal( SIG_TRIGGER ); + target->ProcessEvent( &EV_Activate, ent ); + target->TriggerGuis(); + } else { + gameLocal.Warning( "Framecommand 'trigger' on entity '%s', anim '%s', frame %d: Could not find entity '%s'", + ent->name.c_str(), FullName(), frame + 1, command.string->c_str() ); + } + break; + } + + case FC_DIRECTDAMAGE: { + ent->ProcessEvent( &AI_DirectDamage, command.string->c_str() ); + break; + } + case FC_MUZZLEFLASH: { +// RAVEN BEGIN +// nmckenzie: We're not using this. +// ent->ProcessEvent( &AI_MuzzleFlash, command.string->c_str() ); +// RAVEN END + break; + } + case FC_FOOTSTEP : { + ent->ProcessEvent( &EV_Footstep ); + break; + } + case FC_LEFTFOOT: { + ent->ProcessEvent( &EV_FootstepLeft ); + break; + } + case FC_RIGHTFOOT: { + ent->ProcessEvent( &EV_FootstepRight ); + break; + } + case FC_ENABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_EnableEyeFocus ); + break; + } + case FC_DISABLE_EYE_FOCUS: { + ent->ProcessEvent( &AI_DisableEyeFocus ); + break; + } + case FC_ENABLE_BLINKING: { + ent->ProcessEvent( &AI_EnableBlink ); + break; + } + case FC_DISABLE_BLINKING: { + ent->ProcessEvent( &AI_DisableBlink ); + break; + } + case FC_ENABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_EnableAutoBlink ); + break; + } + case FC_DISABLE_AUTOBLINK: { + ent->ProcessEvent( &AI_DisableAutoBlink ); + break; + } + case FC_DISABLE_GRAVITY: { + ent->ProcessEvent( &AI_DisableGravity ); + break; + } + case FC_ENABLE_GRAVITY: { + ent->ProcessEvent( &AI_EnableGravity ); + break; + } + case FC_JUMP: { + ent->ProcessEvent( &AI_JumpFrame ); + break; + } + case FC_ENABLE_CLIP: { + ent->ProcessEvent( &AI_EnableClip ); + break; + } + case FC_DISABLE_CLIP: { + ent->ProcessEvent( &AI_DisableClip ); + break; + } + case FC_ENABLE_WALK_IK: { + ent->ProcessEvent( &EV_EnableWalkIK ); + break; + } + case FC_DISABLE_WALK_IK: { + ent->ProcessEvent( &EV_DisableWalkIK ); + break; + } + case FC_ENABLE_LEG_IK: { + ent->ProcessEvent( &EV_EnableLegIK, command.index ); + break; + } + case FC_DISABLE_LEG_IK: { + ent->ProcessEvent( &EV_DisableLegIK, command.index ); + break; + } + case FC_RECORDDEMO: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "recordDemo %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "stoprecording" ); + } + break; + } + case FC_AVIGAME: { + if ( command.string ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "aviGame %s", command.string->c_str() ) ); + } else { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "aviGame" ); + } + break; + } + + case FC_AI_ENABLE_PAIN: + ent->ProcessEvent ( &AI_EnablePain ); + break; + + case FC_AI_DISABLE_PAIN: + ent->ProcessEvent ( &AI_DisablePain ); + break; + + case FC_AI_ENABLE_DAMAGE: + ent->ProcessEvent ( &AI_EnableDamage ); + break; + + case FC_AI_LOCKENEMYORIGIN: + ent->ProcessEvent ( &AI_LockEnemyOrigin ); + break; + + case FC_AI_ATTACK: + ent->ProcessEvent ( &AI_Attack, command.string->c_str(), command.joint->c_str() ); + break; + + case FC_AI_DISABLE_DAMAGE: + ent->ProcessEvent ( &AI_DisableDamage ); + break; + + case FC_AI_SPEAK: + ent->ProcessEvent( &AI_Speak, command.string->c_str() ); + break; + + case FC_AI_SPEAK_RANDOM: + ent->ProcessEvent( &AI_SpeakRandom, command.string->c_str() ); + break; + + case FC_ACT_ATTACH_HIDE: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->HideAttachment( command.string->c_str() ); + } + break; + + case FC_ACT_ATTACH_SHOW: + if ( ent->IsType(idActor::GetClassType()) ) + { + static_cast(ent)->ShowAttachment( command.string->c_str() ); + } + break; + + case FC_AI_ATTACK_MELEE: + ent->ProcessEvent( &AI_AttackMelee, command.string->c_str() ); + break; + } + } + } +} + +/* +===================== +idAnim::FindFrameForFrameCommand +===================== +*/ +int idAnim::FindFrameForFrameCommand( frameCommandType_t framecommand, const frameCommand_t **command ) const { + int frame; + int index; + int numframes; + int end; + + if ( !frameCommands.Num() ) { + return -1; + } + + numframes = anims[ 0 ]->NumFrames(); + for( frame = 0; frame < numframes; frame++ ) { + end = frameLookup[ frame ].firstCommand + frameLookup[ frame ].num; + for( index = frameLookup[ frame ].firstCommand; index < end; index++ ) { + if ( frameCommands[ index ].type == framecommand ) { + if ( command ) { + *command = &frameCommands[ index ]; + } + return frame; + } + } + } + + if ( command ) { + *command = NULL; + } + + return -1; +} + +/* +===================== +idAnim::HasFrameCommands +===================== +*/ +bool idAnim::HasFrameCommands( void ) const { + if ( !frameCommands.Num() ) { + return false; + } + return true; +} + +/* +===================== +idAnim::SetAnimFlags +===================== +*/ +void idAnim::SetAnimFlags( const animFlags_t &animflags ) { + flags = animflags; +} + +/* +===================== +idAnim::GetAnimFlags +===================== +*/ +const animFlags_t &idAnim::GetAnimFlags( void ) const { + return flags; +} + +/*********************************************************************** + + idAnimBlend + +***********************************************************************/ + +/* +===================== +idAnimBlend::idAnimBlend +===================== +*/ +idAnimBlend::idAnimBlend( void ) { + Reset( NULL ); +} + +/* +===================== +idAnimBlend::Save + +archives object for save game file +===================== +*/ +void idAnimBlend::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( starttime ); + savefile->WriteInt( endtime ); + savefile->WriteInt( timeOffset ); + savefile->WriteFloat( rate ); + + savefile->WriteInt( blendStartTime ); + savefile->WriteInt( blendDuration ); + savefile->WriteFloat( blendStartValue ); + savefile->WriteFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->WriteFloat( animWeights[ i ] ); + } + savefile->WriteShort( cycle ); + savefile->WriteShort( animNum ); + savefile->WriteBool( allowMove ); + savefile->WriteBool( allowFrameCommands ); + savefile->WriteBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Restore + +unarchives object from save game file +===================== +*/ +void idAnimBlend::Restore( idRestoreGame *savefile, const idDeclModelDef *modelDef ) { + int i; + + this->modelDef = modelDef; + + savefile->ReadInt( starttime ); + savefile->ReadInt( endtime ); + savefile->ReadInt( timeOffset ); + savefile->ReadFloat( rate ); + + savefile->ReadInt( blendStartTime ); + savefile->ReadInt( blendDuration ); + savefile->ReadFloat( blendStartValue ); + savefile->ReadFloat( blendEndValue ); + + for( i = 0; i < ANIM_MaxSyncedAnims; i++ ) { + savefile->ReadFloat( animWeights[ i ] ); + } + savefile->ReadShort( cycle ); + savefile->ReadShort( animNum ); + if ( !modelDef ) { + animNum = 0; + } else if ( ( animNum < 0 ) || ( animNum > modelDef->NumAnims() ) ) { + gameLocal.Warning( "Anim number %d out of range for model '%s' during save game", animNum, modelDef->GetModelName() ); + animNum = 0; + } + savefile->ReadBool( allowMove ); + savefile->ReadBool( allowFrameCommands ); + savefile->ReadBool( useFrameBlend ); +} + +/* +===================== +idAnimBlend::Reset +===================== +*/ +void idAnimBlend::Reset( const idDeclModelDef *_modelDef ) { + modelDef = _modelDef; + cycle = 1; + starttime = 0; + endtime = 0; + timeOffset = 0; + rate = 1.0f; + allowMove = true; + allowFrameCommands = true; + animNum = 0; + + memset( animWeights, 0, sizeof( animWeights ) ); + + blendStartValue = 0.0f; + blendEndValue = 0.0f; + blendStartTime = 0; + blendDuration = 0; + useFrameBlend = false; + + memset( &frameBlend, 0, sizeof( frameBlend ) ); +} + +/* +===================== +idAnimBlend::FullName +===================== +*/ +const char *idAnimBlend::AnimFullName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} + +/* +===================== +idAnimBlend::AnimName +===================== +*/ +const char *idAnimBlend::AnimName( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return ""; + } + + return anim->Name(); +} + +/* +===================== +idAnimBlend::NumFrames +===================== +*/ +int idAnimBlend::NumFrames( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumFrames(); +} + +/* +===================== +idAnimBlend::Length +===================== +*/ +int idAnimBlend::Length( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->Length(); +} + +/* +===================== +idAnimBlend::GetWeight +===================== +*/ +float idAnimBlend::GetWeight( int currentTime ) const { + int timeDelta; + float frac; + float w; + + timeDelta = currentTime - blendStartTime; + if ( timeDelta <= 0 ) { + w = blendStartValue; + } else if ( timeDelta >= blendDuration ) { + w = blendEndValue; + } else { + frac = ( float )timeDelta / ( float )blendDuration; + w = blendStartValue + ( blendEndValue - blendStartValue ) * frac; + } + + return w; +} + +/* +===================== +idAnimBlend::GetFinalWeight +===================== +*/ +float idAnimBlend::GetFinalWeight( void ) const { + return blendEndValue; +} + +/* +===================== +idAnimBlend::SetWeight +===================== +*/ +void idAnimBlend::SetWeight( float newweight, int currentTime, int blendTime ) { + blendStartValue = GetWeight( currentTime ); + blendEndValue = newweight; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + + if ( !newweight ) { + endtime = currentTime + blendTime; + } +} + +/* +===================== +idAnimBlend::NumSyncedAnims +===================== +*/ +int idAnimBlend::NumSyncedAnims( void ) const { + const idAnim *anim = Anim(); + if ( !anim ) { + return 0; + } + + return anim->NumAnims(); +} + +/* +===================== +idAnimBlend::SetSyncedAnimWeight +===================== +*/ +bool idAnimBlend::SetSyncedAnimWeight( int num, float weight ) { + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + if ( ( num < 0 ) || ( num > anim->NumAnims() ) ) { + return false; + } + + animWeights[ num ] = weight; + return true; +} + +/* +===================== +idAnimBlend::SetFrame +===================== +*/ +void idAnimBlend::SetFrame( const idDeclModelDef *modelDef, int _animNum, const frameBlend_t & _frameBlend ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + frameBlend = _frameBlend; + animNum = _animNum; + useFrameBlend = true; + + // a frame of 0 means it's not a single frame blend, so we set it to frame + 1 + if ( frameBlend.frame1 < 0 ) { + frameBlend.frame1 = 0; + } else if ( frameBlend.frame1 >= _anim->NumFrames() ) { + frameBlend.frame1 = _anim->NumFrames() - 1; + } + + if ( frameBlend.frame2 < 0 ) { + frameBlend.frame2 = 0; + } else if ( frameBlend.frame2 >= _anim->NumFrames() ) { + frameBlend.frame2 = _anim->NumFrames() - 1; + } +} + +/* +===================== +idAnimBlend::CycleAnim +===================== +*/ +void idAnimBlend::CycleAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + animWeights[ 0 ] = 1.0f; + endtime = -1; + cycle = -1; + if ( _anim->GetAnimFlags().random_cycle_start ) { + // start the animation at a random time so that characters don't walk in sync + starttime = currentTime - gameLocal.random.RandomFloat() * _anim->Length(); + } else { + starttime = currentTime; + } + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } +// RAVEN END +} + +/* +===================== +idAnimBlend::PlayAnim +===================== +*/ +void idAnimBlend::PlayAnim( const idDeclModelDef *modelDef, int _animNum, int currentTime, int blendTime, float _rate ) { + Reset( modelDef ); + if ( !modelDef ) { + return; + } + + const idAnim *_anim = modelDef->GetAnim( _animNum ); + if ( !_anim ) { + return; + } + + const idMD5Anim *md5anim = _anim->MD5Anim( 0 ); + if ( modelDef->Joints().Num() != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", modelDef->GetModelName(), modelDef->Joints().Num(), md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + animNum = _animNum; + starttime = currentTime; + endtime = starttime + _anim->Length(); + cycle = 1; + animWeights[ 0 ] = 1.0f; + + // set up blend + blendEndValue = 1.0f; + blendStartTime = currentTime - 1; + blendDuration = blendTime; + blendStartValue = 0.0f; + +// RAVEN BEGIN +// bdube: configurable playback rate + + _rate *= _anim->GetPlaybackRate ( ); + if ( _rate != 1.0f ) { + SetPlaybackRate ( currentTime, _rate ); + } + +// RAVEN END +} + +/* +===================== +idAnimBlend::Clear +===================== +*/ +void idAnimBlend::Clear( int currentTime, int clearTime ) { + if ( !clearTime ) { + Reset( modelDef ); + } else { + SetWeight( 0.0f, currentTime, clearTime ); + } +} + +/* +===================== +idAnimBlend::IsDone +===================== +*/ +bool idAnimBlend::IsDone( int currentTime ) const { + if ( !useFrameBlend && ( endtime > 0 ) && ( currentTime >= endtime ) ) { + return true; + } + + if ( ( blendEndValue <= 0.0f ) && ( currentTime >= ( blendStartTime + blendDuration ) ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimBlend::FrameHasChanged +===================== +*/ +bool idAnimBlend::FrameHasChanged( int currentTime ) const { + // if we don't have an anim, no change + if ( !animNum ) { + return false; + } + + // if anim is done playing, no change + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + // if our blend weight changes, we need to update + if ( ( currentTime < ( blendStartTime + blendDuration ) && ( blendStartValue != blendEndValue ) ) ) { + return true; + } + + // if we're a single frame anim and this isn't the frame we started on, we don't need to update + if ( ( useFrameBlend || ( NumFrames() == 1 ) ) && ( currentTime != starttime ) ) { + return false; + } + + return true; +} + +/* +===================== +idAnimBlend::GetCycleCount +===================== +*/ +int idAnimBlend::GetCycleCount( void ) const { + return cycle; +} + +/* +===================== +idAnimBlend::SetCycleCount +===================== +*/ +void idAnimBlend::SetCycleCount( int count ) { + const idAnim *anim = Anim(); + + if ( !anim ) { + cycle = -1; + endtime = 0; + } else { + cycle = count; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + if ( count < 0 ) { +// RAVEN END + cycle = -1; + endtime = -1; +// RAVEN BEGIN +// jnewquist: Xenon compiler bug generated bad code. Used count instead of cycle for test. + } else if ( count == 0 ) { +// RAVEN END + cycle = 1; + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length(); + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + anim->Length() / rate; + } else { + endtime = -1; + } + } else { + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + endtime = starttime - timeOffset + anim->Length() * cycle; + } else if ( rate != 0.0f ) { + endtime = starttime - timeOffset + ( anim->Length() * cycle ) / rate; + } else { + endtime = -1; + } + } + } +} + +/* +===================== +idAnimBlend::SetPlaybackRate +===================== +*/ +void idAnimBlend::SetPlaybackRate( int currentTime, float newRate ) { + int animTime; + + if ( rate == newRate ) { + return; + } + + animTime = AnimTime( currentTime ); + if ( newRate == 1.0f ) { + timeOffset = animTime - ( currentTime - starttime ); + } else { + timeOffset = animTime - ( currentTime - starttime ) * newRate; + } + + rate = newRate; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetPlaybackRate +===================== +*/ +float idAnimBlend::GetPlaybackRate( void ) const { + return rate; +} + +/* +===================== +idAnimBlend::SetStartTime +===================== +*/ +void idAnimBlend::SetStartTime( int _startTime ) { + starttime = _startTime; + + // update the anim endtime + SetCycleCount( cycle ); +} + +/* +===================== +idAnimBlend::GetStartTime +===================== +*/ +int idAnimBlend::GetStartTime( void ) const { + if ( !animNum ) { + return 0; + } + + return starttime; +} + +/* +===================== +idAnimBlend::GetEndTime +===================== +*/ +int idAnimBlend::GetEndTime( void ) const { + if ( !animNum ) { + return 0; + } + + return endtime; +} + +/* +===================== +idAnimBlend::PlayLength +===================== +*/ +int idAnimBlend::PlayLength( void ) const { + if ( !animNum ) { + return 0; + } + + if ( endtime < 0 ) { + return -1; + } + + return endtime - starttime + timeOffset; +} + +/* +===================== +idAnimBlend::AllowMovement +===================== +*/ +void idAnimBlend::AllowMovement( bool allow ) { + allowMove = allow; +} + +/* +===================== +idAnimBlend::AllowFrameCommands +===================== +*/ +void idAnimBlend::AllowFrameCommands( bool allow ) { + allowFrameCommands = allow; +} + +/* +===================== +idAnimBlend::AnimNum +===================== +*/ +int idAnimBlend::AnimNum( void ) const { + return animNum; +} + +/* +===================== +idAnimBlend::AnimTime +===================== +*/ +int idAnimBlend::AnimTime( int currentTime ) const { + int time; + int length; + const idAnim *anim = Anim(); + + if ( anim ) { + if ( useFrameBlend ) { + return FRAME2MS( frameBlend.frame1 ); + } + + // most of the time we're running at the original frame rate, so avoid the int-to-float-to-int conversion + if ( rate == 1.0f ) { + time = currentTime - starttime + timeOffset; + } else { + time = static_cast( ( currentTime - starttime ) * rate ) + timeOffset; + } + + // given enough time, we can easily wrap time around in our frame calculations, so + // keep cycling animations' time within the length of the anim. + length = anim->Length(); + if ( ( cycle < 0 ) && ( length > 0 ) ) { + time %= length; + + // time will wrap after 24 days (oh no!), resulting in negative results for the %. + // adding the length gives us the proper result. + if ( time < 0 ) { + time += length; + } + } + return time; + } else { + return 0; + } +} + +/* +===================== +idAnimBlend::GetFrameNumber +===================== +*/ +int idAnimBlend::GetFrameNumber( int currentTime ) const { + const idMD5Anim *md5anim; + frameBlend_t frameinfo; + int animTime; + + const idAnim *anim = Anim(); + if ( !anim ) { + return 1; + } + + if ( useFrameBlend ) { + return frameBlend.frame1; + } + + md5anim = anim->MD5Anim( 0 ); + animTime = AnimTime( currentTime ); + md5anim->ConvertTimeToFrame( animTime, cycle, frameinfo ); + + return frameinfo.frame1 + 1; +} + +/* +===================== +idAnimBlend::CallFrameCommands +===================== +*/ +void idAnimBlend::CallFrameCommands( idEntity *ent, int fromtime, int totime ) const { + const idMD5Anim *md5anim; + frameBlend_t frame1; + frameBlend_t frame2; + int fromFrameTime; + int toFrameTime; + + if ( !allowFrameCommands || !ent || useFrameBlend || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->HasFrameCommands() ) { + return; + } + + if ( totime <= starttime ) { + // don't play until next frame or we'll play commands twice. + // this happens on the player sometimes. + return; + } + + fromFrameTime = AnimTime( fromtime ); + toFrameTime = AnimTime( totime ); + if ( toFrameTime < fromFrameTime ) { + toFrameTime += anim->Length(); + } + + md5anim = anim->MD5Anim( 0 ); + md5anim->ConvertTimeToFrame( fromFrameTime, cycle, frame1 ); + md5anim->ConvertTimeToFrame( toFrameTime, cycle, frame2 ); + + if ( fromFrameTime <= 0 ) { + // make sure first frame is called + anim->CallFrameCommands( ent, -1, frame2.frame1 ); + } else { + anim->CallFrameCommands( ent, frame1.frame1, frame2.frame1 ); + } +} + +/* +===================== +idAnimBlend::BlendAnim +===================== +*/ +bool idAnimBlend::BlendAnim( int currentTime, int channel, int numJoints, idJointQuat *blendFrame, float &blendWeight, bool removeOriginOffset, bool overrideBlend, bool printInfo ) const { + int i; + float lerp; + float mixWeight; + const idMD5Anim *md5anim; + idJointQuat *ptr; + frameBlend_t frametime = {0}; + idJointQuat *jointFrame; + idJointQuat *mixFrame; + int numAnims; + int time; + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( blendWeight > 0.0f ) { + if ( ( endtime >= 0 ) && ( currentTime >= endtime ) ) { + return false; + } + if ( !weight ) { + return false; + } + if ( overrideBlend ) { + blendWeight = 1.0f - weight; + } + } + + if ( ( channel == ANIMCHANNEL_ALL ) && !blendWeight ) { + // we don't need a temporary buffer, so just store it directly in the blend frame + jointFrame = blendFrame; + } else { + // allocate a temporary buffer to copy the joints from + jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + } + + time = AnimTime( currentTime ); + + numAnims = anim->NumAnims(); + if ( numAnims == 1 ) { + md5anim = anim->MD5Anim( 0 ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->ConvertTimeToFrame( time, cycle, frametime ); + md5anim->GetInterpolatedFrame( frametime, jointFrame, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + } else { + // + // need to mix the multipoint anim together first + // + // allocate a temporary buffer to copy the joints to + mixFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + + if ( !useFrameBlend ) { + anim->MD5Anim( 0 )->ConvertTimeToFrame( time, cycle, frametime ); + } + + ptr = jointFrame; + mixWeight = 0.0f; + for( i = 0; i < numAnims; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + lerp = animWeights[ i ] / mixWeight; + md5anim = anim->MD5Anim( i ); + if ( useFrameBlend ) { + md5anim->GetInterpolatedFrame( frameBlend, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } else { + md5anim->GetInterpolatedFrame( frametime, ptr, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + // only blend after the first anim is mixed in + if ( ptr != jointFrame ) { + SIMDProcessor->BlendJoints( jointFrame, ptr, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + ptr = mixFrame; + } + } + + if ( !mixWeight ) { + return false; + } + } + + if ( removeOriginOffset ) { + if ( allowMove ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + if ( anim->GetAnimFlags().anim_turn ) { + jointFrame[ 0 ].q.Set( -0.70710677f, 0.0f, 0.0f, 0.70710677f ); + } + } + + if ( !blendWeight ) { + blendWeight = weight; + if ( channel != ANIMCHANNEL_ALL ) { + const int *index = modelDef->GetChannelJoints( channel ); + const int num = modelDef->NumJointsOnChannel( channel ); + for( i = 0; i < num; i++ ) { + int j = index[i]; + blendFrame[j].t = jointFrame[j].t; + blendFrame[j].q = jointFrame[j].q; + } + } + } else { + blendWeight += weight; + lerp = weight / blendWeight; + SIMDProcessor->BlendJoints( blendFrame, jointFrame, lerp, modelDef->GetChannelJoints( channel ), modelDef->NumJointsOnChannel( channel ) ); + } + + if ( printInfo ) { + if ( useFrameBlend ) { + gameLocal.Printf( " %s: '%s', %d, %.2f%%\n", channelNames[ channel ], anim->FullName(), frameBlend.frame1, weight * 100.0f ); + } else { + gameLocal.Printf( " %s: '%s', %.3f, %.2f%%\n", channelNames[ channel ], anim->FullName(), ( float )frametime.frame1 + frametime.backlerp, weight * 100.0f ); + } + } + + return true; +} + +/* +===================== +idAnimBlend::BlendOrigin +===================== +*/ +void idAnimBlend::BlendOrigin( int currentTime, idVec3 &blendPos, float &blendWeight, bool removeOriginOffset ) const { + float lerp; + idVec3 animpos; + idVec3 pos; + int time; + int num; + int i; + + if ( useFrameBlend || ( ( endtime > 0 ) && ( currentTime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + if ( allowMove && removeOriginOffset ) { + return; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return; + } + + time = AnimTime( currentTime ); + + pos.Zero(); + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time, cycle ); + pos += animpos * animWeights[ i ]; + } + + if ( !blendWeight ) { + blendPos = pos; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendPos += lerp * ( pos - blendPos ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDelta +===================== +*/ +void idAnimBlend::BlendDelta( int fromtime, int totime, idVec3 &blendDelta, float &blendWeight ) const { + idVec3 pos1; + idVec3 pos2; + idVec3 animpos; + idVec3 delta; + int time1; + int time2; + float lerp; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + num = anim->NumAnims(); + + pos1.Zero(); + pos2.Zero(); + for( i = 0; i < num; i++ ) { + anim->GetOrigin( animpos, i, time1, cycle ); + pos1 += animpos * animWeights[ i ]; + + anim->GetOrigin( animpos, i, time2, cycle ); + pos2 += animpos * animWeights[ i ]; + } + + delta = pos2 - pos1; + if ( !blendWeight ) { + blendDelta = delta; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta += lerp * ( delta - blendDelta ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::BlendDeltaRotation +===================== +*/ +void idAnimBlend::BlendDeltaRotation( int fromtime, int totime, idQuat &blendDelta, float &blendWeight ) const { + idQuat q1; + idQuat q2; + idQuat q3; + int time1; + int time2; + float lerp; + float mixWeight; + int num; + int i; + + if ( useFrameBlend || !allowMove || ( ( endtime > 0 ) && ( fromtime > endtime ) ) ) { + return; + } + + const idAnim *anim = Anim(); + if ( !anim || !anim->GetAnimFlags().anim_turn ) { + return; + } + + float weight = GetWeight( totime ); + if ( !weight ) { + return; + } + + time1 = AnimTime( fromtime ); + time2 = AnimTime( totime ); + if ( time2 < time1 ) { + time2 += anim->Length(); + } + + q1.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + q2.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + + mixWeight = 0.0f; + num = anim->NumAnims(); + for( i = 0; i < num; i++ ) { + if ( animWeights[ i ] > 0.0f ) { + mixWeight += animWeights[ i ]; + if ( animWeights[ i ] == mixWeight ) { + anim->GetOriginRotation( q1, i, time1, cycle ); + anim->GetOriginRotation( q2, i, time2, cycle ); + } else { + lerp = animWeights[ i ] / mixWeight; + anim->GetOriginRotation( q3, i, time1, cycle ); + q1.Slerp( q1, q3, lerp ); + + anim->GetOriginRotation( q3, i, time2, cycle ); + q2.Slerp( q1, q3, lerp ); + } + } + } + + q3 = q1.Inverse() * q2; + if ( !blendWeight ) { + blendDelta = q3; + blendWeight = weight; + } else { + lerp = weight / ( blendWeight + weight ); + blendDelta.Slerp( blendDelta, q3, lerp ); + blendWeight += weight; + } +} + +/* +===================== +idAnimBlend::AddBounds +===================== +*/ +bool idAnimBlend::AddBounds( int currentTime, idBounds &bounds, bool removeOriginOffset ) const { + int i; + int num; + idBounds b; + int time; + idVec3 pos; + bool addorigin; + + if ( ( endtime > 0 ) && ( currentTime > endtime ) ) { + return false; + } + + const idAnim *anim = Anim(); + if ( !anim ) { + return false; + } + + float weight = GetWeight( currentTime ); + if ( !weight ) { + return false; + } + + time = AnimTime( currentTime ); + num = anim->NumAnims(); + + addorigin = !allowMove || !removeOriginOffset; + for( i = 0; i < num; i++ ) { + if ( anim->GetBounds( b, i, time, cycle ) ) { + if ( addorigin ) { + anim->GetOrigin( pos, i, time, cycle ); + b.TranslateSelf( pos ); + } + bounds.AddBounds( b ); + } + } + + return true; +} + +/*********************************************************************** + + idDeclModelDef + +***********************************************************************/ + +/* +===================== +idDeclModelDef::idDeclModelDef +===================== +*/ +idDeclModelDef::idDeclModelDef() { + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels=0; +#endif +// RAVEN END +} + +/* +===================== +idDeclModelDef::~idDeclModelDef +===================== +*/ +idDeclModelDef::~idDeclModelDef() { + FreeData(); +} + +/* +================= +idDeclModelDef::Size +================= +*/ +// RAVEN BEGIN +// jscott: made more accurate +size_t idDeclModelDef::Size( void ) const { + + int i; + size_t size; + + size = sizeof( idDeclModelDef ); + size += joints.Allocated(); + size += jointParents.Allocated(); + size += anims.Allocated(); + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + + size += channelJoints[i].Allocated(); + } + + return( size ); +} +// RAVEN END + +/* +===================== +idDeclModelDef::CopyDecl +===================== +*/ +void idDeclModelDef::CopyDecl( const idDeclModelDef *decl ) { + int i; + + FreeData(); + + offset = decl->offset; + modelHandle = decl->modelHandle; + skin = decl->skin; + + anims.SetNum( decl->anims.Num() ); + for( i = 0; i < anims.Num(); i++ ) { + anims[ i ] = new idAnim( this, decl->anims[ i ] ); + } + + joints.SetNum( decl->joints.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( joints.Ptr(), decl->joints.Ptr(), decl->joints.Num() * sizeof( joints[0] ) ); +// RAVEN END + jointParents.SetNum( decl->jointParents.Num() ); +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( jointParents.Ptr(), decl->jointParents.Ptr(), decl->jointParents.Num() * sizeof( jointParents[0] ) ); +// RAVEN END + for ( i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i] = decl->channelJoints[i]; + } +} + +/* +===================== +idDeclModelDef::FreeData +===================== +*/ +void idDeclModelDef::FreeData( void ) { + anims.DeleteContents( true ); + joints.Clear(); + jointParents.Clear(); + modelHandle = NULL; + skin = NULL; + offset.Zero(); + for ( int i = 0; i < ANIM_NumAnimChannels; i++ ) { + channelJoints[i].Clear(); + } +} + +/* +================ +idDeclModelDef::DefaultDefinition +================ +*/ +const char *idDeclModelDef::DefaultDefinition( void ) const { + return "{ }"; +} + +/* +==================== +idDeclModelDef::FindJoint +==================== +*/ +const jointInfo_t *idDeclModelDef::FindJoint( const char *name ) const { + int i; + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + joint = modelHandle->GetJoints(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( !joint->name.Icmp( name ) ) { + return &joints[ i ]; + } + } + + return NULL; +} + +/* +===================== +idDeclModelDef::ModelHandle +===================== +*/ +idRenderModel *idDeclModelDef::ModelHandle( void ) const { + return ( idRenderModel * )modelHandle; +} + +/* +===================== +idDeclModelDef::GetJointList +===================== +*/ +void idDeclModelDef::GetJointList( const char *jointnames, idList &jointList ) const { + const char *pos; + idStr jointname; + const jointInfo_t *joint; + const jointInfo_t *child; + int i; + int num; + bool getChildren; + bool subtract; + + if ( !modelHandle ) { + return; + } + + jointList.Clear(); + + num = modelHandle->NumJoints(); + + // scan through list of joints and add each to the joint list + pos = jointnames; + while( *pos ) { + // skip over whitespace + while( ( *pos != 0 ) && isspace( *pos ) ) { + pos++; + } + + if ( !*pos ) { + // no more names + break; + } + + // copy joint name + jointname = ""; + + if ( *pos == '-' ) { + subtract = true; + pos++; + } else { + subtract = false; + } + + if ( *pos == '*' ) { + getChildren = true; + pos++; + } else { + getChildren = false; + } + + while( ( *pos != 0 ) && !isspace( *pos ) ) { + jointname += *pos; + pos++; + } + + joint = FindJoint( jointname ); + if ( !joint ) { + gameLocal.Warning( "Unknown joint '%s' in '%s' for model '%s'", jointname.c_str(), jointnames, GetName() ); + continue; + } + + if ( !subtract ) { + jointList.AddUnique( joint->num ); + } else { + jointList.Remove( joint->num ); + } + + if ( getChildren ) { + // include all joint's children + child = joint + 1; + for( i = joint->num + 1; i < num; i++, child++ ) { + // all children of the joint should follow it in the list. + // once we reach a joint without a parent or with a parent + // who is earlier in the list than the specified joint, then + // we've gone through all it's children. + if ( child->parentNum < joint->num ) { + break; + } + + if ( !subtract ) { + jointList.AddUnique( child->num ); + } else { + jointList.Remove( child->num ); + } + } + } + } +} + +/* +===================== +idDeclModelDef::Touch +===================== +*/ +void idDeclModelDef::Touch( void ) const { + if ( modelHandle ) { + renderModelManager->FindModel( modelHandle->Name() ); + } +} + +/* +===================== +idDeclModelDef::GetDefaultSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetDefaultSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetDefaultPose +===================== +*/ +const idJointQuat *idDeclModelDef::GetDefaultPose( void ) const { + return modelHandle->GetDefaultPose(); +} + +/* +===================== +idDeclModelDef::SetupJoints +===================== +*/ +void idDeclModelDef::SetupJoints( int *numJoints, idJointMat **jointList, idBounds &frameBounds, bool removeOriginOffset ) const { + int num; + const idJointQuat *pose; + idJointMat *list; + + if ( !modelHandle || modelHandle->IsDefaultModel() ) { + Mem_Free16( (*jointList) ); + (*jointList) = NULL; + frameBounds.Clear(); + return; + } + + // get the number of joints + num = modelHandle->NumJoints(); + + if ( !num ) { + gameLocal.Error( "model '%s' has no joints", modelHandle->Name() ); + } + + // set up initial pose for model (with no pose, model is just a jumbled mess) +//RAVEN BEGIN +//amccarthy: Added allocation tag + list = (idJointMat *) Mem_Alloc16( num * sizeof( list[0] ), MA_ANIM ); +//RAVEN END + pose = GetDefaultPose(); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( list, pose, joints.Num() ); + + // check if we offset the model by the origin joint + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + list[ 0 ].SetTranslation( idVec3( offset.x, offset.y + pose[0].t.y, offset.z + pose[0].t.z ) ); +#else + list[ 0 ].SetTranslation( offset ); +#endif + } else { + list[ 0 ].SetTranslation( pose[0].t + offset ); + } + + // transform the joint hierarchy + SIMDProcessor->TransformJoints( list, jointParents.Ptr(), 1, joints.Num() - 1 ); + + *numJoints = num; + *jointList = list; + + // get the bounds of the default pose + frameBounds = modelHandle->Bounds( NULL ); +} + +/* +===================== +idDeclModelDef::ParseAnim +===================== +*/ +bool idDeclModelDef::ParseAnim( idLexer &src, int numDefaultAnims ) { + int i; + int len; + idAnim *anim; + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + const idMD5Anim *md5anim; + idStr alias; + idToken realname; + idToken token; + int numAnims; + animFlags_t flags; + + numAnims = 0; + memset( md5anims, 0, sizeof( md5anims ) ); + + if( !src.ReadToken( &realname ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + alias = realname; + + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), realname ) ) { + break; + } + } + + if ( ( i < anims.Num() ) && ( i >= numDefaultAnims ) ) { + src.Warning( "Duplicate anim '%s'", realname.c_str() ); + MakeDefault(); + return false; + } + + if ( i < numDefaultAnims ) { + anim = anims[ i ]; + } else { + // create the alias associated with this animation + anim = new idAnim(); + anims.Append( anim ); + } + + // random anims end with a number. find the numeric suffix of the animation. + len = alias.Length(); + for( i = len - 1; i > 0; i-- ) { + if ( !isdigit( alias[ i ] ) ) { + break; + } + } + + // check for zero length name, or a purely numeric name + if ( i <= 0 ) { + src.Warning( "Invalid animation name '%s'", alias.c_str() ); + MakeDefault(); + return false; + } + + // remove the numeric suffix + alias.CapLength( i + 1 ); + + // parse the anims from the string + do { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + // lookup the animation +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( token ); +// RAVEN END + if ( !md5anim ) { + src.Warning( "Couldn't load anim '%s'", token.c_str() ); + MakeDefault(); + return false; + } + + md5anim->CheckModelHierarchy( modelHandle ); + + if ( numAnims > 0 ) { + // make sure it's the same length as the other anims + if ( md5anim->Length() != md5anims[ 0 ]->Length() ) { + src.Warning( "Anim '%s' does not match length of anim '%s'", md5anim->Name(), md5anims[ 0 ]->Name() ); + MakeDefault(); + return false; + } + } + + if ( numAnims >= ANIM_MaxSyncedAnims ) { + src.Warning( "Exceeded max synced anims (%d)", ANIM_MaxSyncedAnims ); + MakeDefault(); + return false; + } + + // add it to our list + md5anims[ numAnims ] = md5anim; + numAnims++; + } while ( src.CheckTokenString( "," ) ); + + if ( !numAnims ) { + src.Warning( "No animation specified" ); + MakeDefault(); + return false; + } + + anim->SetAnim( this, realname, alias, numAnims, md5anims ); + memset( &flags, 0, sizeof( flags ) ); + + // parse any frame commands or animflags + if ( src.CheckTokenString( "{" ) ) { + while( 1 ) { + if( !src.ReadToken( &token ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( token == "}" ) { + break; + }else if ( token == "prevent_idle_override" ) { + flags.prevent_idle_override = true; + } else if ( token == "random_cycle_start" ) { + flags.random_cycle_start = true; +// RAVEN BEGIN +// bdube: added speed + } else if ( token == "sync_cycle" ) { + flags.sync_cycle = true; + } else if ( token == "rate" ) { + anim->SetPlaybackRate ( src.ParseFloat ( ) ); + } else if ( token == "ai_no_look" ) { + flags.ai_no_look = true; + } else if ( token == "ai_look_head_only" ) { + flags.ai_look_head_only = true; +// RAVEN END + } else if ( token == "ai_no_turn" ) { + flags.ai_no_turn = true; + } else if ( token == "anim_turn" ) { + flags.anim_turn = true; + } else if ( token == "frame" ) { + // create a frame command +// RAVEN BEGIN +// bdube: Support a list of frame numbers +// int framenum; + const char *err; + idList frameList; + + do + { +// RAVEN END + // make sure we don't have any line breaks while reading the frame command so the error line # will be correct + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "Missing frame # after 'frame'" ); + MakeDefault(); + return false; + } + if ( token.type == TT_PUNCTUATION && token == "-" ) { + src.Warning( "Invalid frame # after 'frame'" ); + MakeDefault(); + return false; + } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { + src.Error( "expected integer value, found '%s'", token.c_str() ); + } + +// RAVEN BEGIN +// bdube: multiple frames + frameList.Append ( token.GetIntValue() ); + + } while ( src.CheckTokenString ( "," ) ); +// RAVEN END + + // put the command on the specified frame of the animation +// RAVEN BEGIN +// bdube: Support a list of frame numbers + err = anim->AddFrameCommand( this, frameList, src, NULL ); +// RAVEN END + if ( err ) { + src.Warning( "%s", err ); + MakeDefault(); + return false; + } + } else { + src.Warning( "Unknown command '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + } + + // set the flags + anim->SetAnimFlags( flags ); + return true; +} + +/* +================ +idDeclModelDef::Parse +================ +*/ +bool idDeclModelDef::Parse( const char *text, const int textLength, bool noCaching ) { + int i; + int num; + idStr filename; + idStr extension; + const idMD5Joint *md5joint; + const idMD5Joint *md5joints; + idLexer src; + idToken token; + idToken token2; + idStr jointnames; + int channel; + jointHandle_t jointnum; + idList jointList; + int numDefaultAnims; +// RAVEN BEGIN +// bdube: attachments + idList attachJoints; +// RAVEN END + + TIME_THIS_SCOPE( __FUNCLINE__); + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + numDefaultAnims = 0; + while( 1 ) { + if ( !src.ReadToken( &token ) ) { + break; + } + + if ( !token.Icmp( "}" ) ) { + break; + } + + if ( token == "inherit" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + + const idDeclModelDef *copy = static_cast( declManager->FindType( DECL_MODELDEF, token2, false ) ); + if ( !copy ) { + gameLocal.Warning( "Unknown model definition '%s'", token2.c_str() ); + } else if ( copy->GetState() == DS_DEFAULTED ) { + gameLocal.Warning( "inherited model definition '%s' defaulted", token2.c_str() ); + MakeDefault(); + return false; + } else { + CopyDecl( copy ); + numDefaultAnims = anims.Num(); + } + } else if ( token == "skin" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + skin = declManager->FindSkin( token2 ); + if ( !skin ) { + src.Warning( "Skin '%s' not found", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "mesh" ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + filename = token2; + filename.ExtractFileExtension( extension ); + if ( extension != MD5_MESH_EXT ) { + src.Warning( "Invalid model for MD5 mesh" ); + MakeDefault(); + return false; + } + modelHandle = renderModelManager->FindModel( filename ); + if ( !modelHandle ) { + src.Warning( "Model '%s' not found", filename.c_str() ); + MakeDefault(); + return false; + } + + if ( modelHandle->IsDefaultModel() ) { + src.Warning( "Model '%s' defaulted", filename.c_str() ); + MakeDefault(); + return false; + } + + // get the number of joints + num = modelHandle->NumJoints(); + if ( !num ) { + src.Warning( "Model '%s' has no joints", filename.c_str() ); + } + + // set up the joint hierarchy + joints.SetGranularity( 1 ); + joints.SetNum( num ); + jointParents.SetNum( num ); + channelJoints[0].SetNum( num ); + md5joints = modelHandle->GetJoints(); + md5joint = md5joints; + for( i = 0; i < num; i++, md5joint++ ) { + joints[i].channel = ANIMCHANNEL_ALL; + joints[i].num = static_cast( i ); + if ( md5joint->parent ) { + joints[i].parentNum = static_cast( md5joint->parent - md5joints ); + } else { + joints[i].parentNum = INVALID_JOINT; + } + jointParents[i] = joints[i].parentNum; + channelJoints[0][i] = i; + } + } else if ( token == "remove" ) { + // removes any anims whos name matches + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + num = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( ( token2 == anims[ i ]->Name() ) || ( token2 == anims[ i ]->FullName() ) ) { + delete anims[ i ]; + anims.RemoveIndex( i ); + if ( i >= numDefaultAnims ) { + src.Warning( "Anim '%s' was not inherited. Anim should be removed from the model def.", token2.c_str() ); + MakeDefault(); + return false; + } + i--; + numDefaultAnims--; + num++; + continue; + } + } + if ( !num ) { + src.Warning( "Couldn't find anim '%s' to remove", token2.c_str() ); + MakeDefault(); + return false; + } + } else if ( token == "anim" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining anims" ); + MakeDefault(); + return false; + } + if ( !ParseAnim( src, numDefaultAnims ) ) { + MakeDefault(); + return false; + } + } else if ( token == "offset" ) { + if ( !src.Parse1DMatrix( 3, offset.ToFloatPtr() ) ) { + src.Warning( "Expected vector following 'offset'" ); + MakeDefault(); + return false; + } + } else if ( token == "channel" ) { + if ( !modelHandle ) { + src.Warning( "Must specify mesh before defining channels" ); + MakeDefault(); + return false; + } + + // set the channel for a group of joints + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + if ( !src.CheckTokenString( "(" ) ) { + src.Warning( "Expected { after '%s'\n", token2.c_str() ); + MakeDefault(); + return false; + } + + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !stricmp( channelNames[ i ], token2 ) ) { + break; + } + } + + if ( i >= ANIM_NumAnimChannels ) { + src.Warning( "Unknown channel '%s'", token2.c_str() ); + MakeDefault(); + return false; + } + + channel = i; + jointnames = ""; + + while( !src.CheckTokenString( ")" ) ) { + if( !src.ReadToken( &token2 ) ) { + src.Warning( "Unexpected end of file" ); + MakeDefault(); + return false; + } + jointnames += token2; + if ( ( token2 != "*" ) && ( token2 != "-" ) ) { + jointnames += " "; + } + } + + GetJointList( jointnames, jointList ); + + channelJoints[ channel ].SetNum( jointList.Num() ); + for( num = i = 0; i < jointList.Num(); i++ ) { + jointnum = jointList[ i ]; + if ( joints[ jointnum ].channel != ANIMCHANNEL_ALL ) { + src.Warning( "Joint '%s' assigned to multiple channels", modelHandle->GetJointName( jointnum ) ); + continue; + } + joints[ jointnum ].channel = channel; + channelJoints[ channel ][ num++ ] = jointnum; + } + channelJoints[ channel ].SetNum( num ); +// RAVEN BEGIN +// jsinger: I have to track this for binary decl support +#ifdef RV_BINARYDECLS + mNumChannels++; +#endif +// RAVEN END + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + MakeDefault(); + return false; + } + } + + // shrink the anim list down to save space + anims.SetGranularity( 1 ); + anims.SetNum( anims.Num() ); + + return true; +} + +/* +===================== +idDeclModelDef::Validate +===================== +*/ +bool idDeclModelDef::Validate( const char *psText, int iTextLength, idStr &strReportTo ) const { + idDeclModelDef *pSelf = (idDeclModelDef*) declManager->AllocateDecl( DECL_MODELDEF ); + bool bOk = pSelf->Parse( psText, iTextLength, false ); + pSelf->FreeData(); + delete pSelf->base; + delete pSelf; + + return bOk; +} + +/* +===================== +idDeclModelDef::HasAnim +===================== +*/ +bool idDeclModelDef::HasAnim( const char *name ) const { + int i; + + // find any animations with same name + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idDeclModelDef::NumAnims +===================== +*/ +int idDeclModelDef::NumAnims( void ) const { + return anims.Num() + 1; +} + +/* +===================== +idDeclModelDef::GetSpecificAnim + +Gets the exact anim for the name, without randomization. +===================== +*/ +int idDeclModelDef::GetSpecificAnim( const char *name ) const { + int i; + + // find a specific animation + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->FullName(), name ) ) { + return i + 1; + } + } + + // didn't find it + return 0; +} + +/* +===================== +idDeclModelDef::GetAnim +===================== +*/ +int idDeclModelDef::GetAnim( const char *name ) const { + int i; + int which; + const int MAX_ANIMS = 64; + int animList[ MAX_ANIMS ]; + int numAnims; + int len; + + len = strlen( name ); + if ( len && idStr::CharIsNumeric( name[ len - 1 ] ) ) { + // find a specific animation + return GetSpecificAnim( name ); + } + + // find all animations with same name + numAnims = 0; + for( i = 0; i < anims.Num(); i++ ) { + if ( !idStr::Cmp( anims[ i ]->Name(), name ) ) { + animList[ numAnims++ ] = i; + if ( numAnims >= MAX_ANIMS ) { + break; + } + } + } + + if ( !numAnims ) { + return 0; + } + + // get a random anim + //FIXME: don't access gameLocal here? + which = gameLocal.random.RandomInt( numAnims ); + return animList[ which ] + 1; +} + +/* +===================== +idDeclModelDef::GetSkin +===================== +*/ +const idDeclSkin *idDeclModelDef::GetSkin( void ) const { + return skin; +} + +/* +===================== +idDeclModelDef::GetModelName +===================== +*/ +const char *idDeclModelDef::GetModelName( void ) const { + if ( modelHandle ) { + return modelHandle->Name(); + } else { + return ""; + } +} + +/* +===================== +idDeclModelDef::Joints +===================== +*/ +const idList &idDeclModelDef::Joints( void ) const { + return joints; +} + +/* +===================== +idDeclModelDef::JointParents +===================== +*/ +const int * idDeclModelDef::JointParents( void ) const { + return jointParents.Ptr(); +} + +/* +===================== +idDeclModelDef::NumJoints +===================== +*/ +int idDeclModelDef::NumJoints( void ) const { + return joints.Num(); +} + +/* +===================== +idDeclModelDef::GetJoint +===================== +*/ +const jointInfo_t *idDeclModelDef::GetJoint( int jointHandle ) const { + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJoint : joint handle out of range" ); + } + return &joints[ jointHandle ]; +} + +/* +==================== +idDeclModelDef::GetJointName +==================== +*/ +const char *idDeclModelDef::GetJointName( int jointHandle ) const { + const idMD5Joint *joint; + + if ( !modelHandle ) { + return NULL; + } + + if ( ( jointHandle < 0 ) || ( jointHandle > joints.Num() ) ) { + gameLocal.Error( "idDeclModelDef::GetJointName : joint handle out of range" ); + } + + joint = modelHandle->GetJoints(); + return joint[ jointHandle ].name.c_str(); +} + +/* +===================== +idDeclModelDef::NumJointsOnChannel +===================== +*/ +int idDeclModelDef::NumJointsOnChannel( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::NumJointsOnChannel : channel out of range" ); + } + return channelJoints[ channel ].Num(); +} + +/* +===================== +idDeclModelDef::GetChannelJoints +===================== +*/ +const int * idDeclModelDef::GetChannelJoints( int channel ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idDeclModelDef::GetChannelJoints : channel out of range" ); + } + return channelJoints[ channel ].Ptr(); +} + +/* +===================== +idDeclModelDef::GetVisualOffset +===================== +*/ +const idVec3 &idDeclModelDef::GetVisualOffset( void ) const { + return offset; +} + +// RAVEN BEGIN +// jsinger: allow exporting of this decl type in a preparsed form +#ifdef RV_BINARYDECLS +void idDeclModelDef::Write( SerialOutputStream &stream ) const +{ + WriteValue(offset[0], stream); + WriteValue(offset[1], stream); + WriteValue(offset[2], stream); + WriteValue(joints.Num(), stream); + for(int i=0; iName()), stream); + WriteValue(anims.Num(), stream); + for(int i=0; iWrite(stream); + } + + if(skin) + { + WriteValue(idStr(skin->GetName()), stream); + } + else + { + WriteValue(idStr(""), stream); + } +} + +void idDeclModelDef::AddReferences() const +{ +} + +idDeclModelDef::idDeclModelDef( SerialInputStream &stream ) +{ + offset[0] = stream.ReadFloatValue(); + offset[1] = stream.ReadFloatValue(); + offset[2] = stream.ReadFloatValue(); + int numJoints = stream.ReadIntValue(); + joints.AssureSize(numJoints); + for(int i=0; iFindModel( modelName.c_str() ); + } + int numAnims = stream.ReadIntValue(); + anims.AssureSize(numAnims); + for(int i=0; iFindSkin(skinName.c_str()); + } + else + { + skin = NULL; + } +} +#endif +// RAVEN END +/*********************************************************************** + + idAnimator + +***********************************************************************/ + +/* +===================== +idAnimator::idAnimator +===================== +*/ +idAnimator::idAnimator() { + int i, j; + + modelDef = NULL; + entity = NULL; + numJoints = 0; + joints = NULL; + lastTransformTime = -1; + stoppedAnimatingUpdate = false; + removeOriginOffset = false; + forceUpdate = false; + +// RAVEN BEGIN +// jshepard: + rateMultiplier = 1; +// RAVEN END + + frameBounds.Clear(); + + AFPoseJoints.SetGranularity( 1 ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ClearAFPose(); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } +} + +/* +===================== +idAnimator::~idAnimator +===================== +*/ +idAnimator::~idAnimator() { + FreeData(); +} + +/* +===================== +idAnimator::Allocated +===================== +*/ +size_t idAnimator::Allocated( void ) const { + size_t size; + + size = jointMods.Allocated() + numJoints * sizeof( joints[0] ) + jointMods.Num() * sizeof( jointMods[ 0 ] ) + AFPoseJointMods.Allocated() + AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) + AFPoseJoints.Allocated(); + + return size; +} + +/* +===================== +idAnimator::Save + +archives object for save game file +===================== +*/ +void idAnimator::Save( idSaveGame *savefile ) const { + int i; + int j; + + savefile->WriteModelDef( modelDef ); + savefile->WriteObject( entity ); + + savefile->WriteInt( jointMods.Num() ); + for( i = 0; i < jointMods.Num(); i++ ) { + savefile->Write( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->WriteInt( numJoints ); + savefile->Write( joints, numJoints * sizeof( joints[0] ) ); + + savefile->WriteInt( lastTransformTime ); + savefile->WriteBool( stoppedAnimatingUpdate ); + savefile->WriteBool( forceUpdate ); + savefile->WriteBounds( frameBounds ); + + savefile->WriteFloat( AFPoseBlendWeight ); + + savefile->WriteInt( AFPoseJoints.Num() ); + savefile->Write( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointMods.Num() ); + savefile->Write( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->WriteInt( AFPoseJointFrameSize ); + savefile->Write( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->WriteBounds( AFPoseBounds ); + savefile->WriteInt( AFPoseTime ); + + savefile->WriteBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Save( savefile ); + } + } +} + +/* +===================== +idAnimator::Restore + +unarchives object from save game file +===================== +*/ +void idAnimator::Restore( idRestoreGame *savefile ) { + int i; + int j; + int num; + + savefile->ReadModelDef( modelDef ); + savefile->ReadObject( reinterpret_cast( entity ) ); + + savefile->ReadInt( num ); + jointMods.SetNum( num ); + for( i = 0; i < num; i++ ) { + jointMods[ i ] = new jointMod_t; + savefile->Read( jointMods[ i ], sizeof( *jointMods[ i ] ) ); + } + + savefile->ReadInt( numJoints ); +//RAVEN BEGIN +//amccarthy: Added allocation tag + joints = (idJointMat *) Mem_Alloc16( numJoints * sizeof( joints[0] ), MA_ANIM ); +//RAVEN END + savefile->Read( joints, numJoints * sizeof( joints[0] ) ); + + savefile->ReadInt( lastTransformTime ); + savefile->ReadBool( stoppedAnimatingUpdate ); + savefile->ReadBool( forceUpdate ); + savefile->ReadBounds( frameBounds ); + + savefile->ReadFloat( AFPoseBlendWeight ); + + savefile->ReadInt( num ); + AFPoseJoints.SetGranularity( 1 ); + AFPoseJoints.SetNum( num ); + savefile->Read( AFPoseJoints.Ptr(), AFPoseJoints.MemoryUsed() ); + + savefile->ReadInt( num ); + AFPoseJointMods.SetGranularity( 1 ); + AFPoseJointMods.SetNum( num ); + savefile->Read( AFPoseJointMods.Ptr(), AFPoseJointMods.MemoryUsed() ); + + savefile->ReadInt( AFPoseJointFrameSize ); + AFPoseJointFrame = (idJointQuat *)Mem_Alloc16( AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + savefile->Read( AFPoseJointFrame, AFPoseJointFrameSize * sizeof( AFPoseJointFrame[0] ) ); + + savefile->ReadBounds( AFPoseBounds ); + savefile->ReadInt( AFPoseTime ); + + savefile->ReadBool( removeOriginOffset ); + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Restore( savefile, modelDef ); + } + } +} + +/* +===================== +idAnimator::FreeData +===================== +*/ +void idAnimator::FreeData( void ) { + int i, j; + + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + } + + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( NULL ); + } + } + + jointMods.DeleteContents( true ); + + Mem_Free16( joints ); + joints = NULL; + numJoints = 0; + + modelDef = NULL; + + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = NULL; + AFPoseJointFrameSize = 0; + + ForceUpdate(); +} + +/* +===================== +idAnimator::PushAnims +===================== +*/ +void idAnimator::PushAnims( int channelNum, int currentTime, int blendTime ) { + int i; + idAnimBlend *channel; + + channel = channels[ channelNum ]; + if ( !channel[ 0 ].GetWeight( currentTime ) || ( channel[ 0 ].starttime == currentTime ) ) { + return; + } + + for( i = ANIM_MaxAnimsPerChannel - 1; i > 0; i-- ) { + channel[ i ] = channel[ i - 1 ]; + } + + channel[ 0 ].Reset( modelDef ); + channel[ 1 ].Clear( currentTime, blendTime ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetModel +===================== +*/ +idRenderModel *idAnimator::SetModel( const char *modelname ) { + int i, j; + + FreeData(); + + // check if we're just clearing the model + if ( !modelname || !*modelname ) { + return NULL; + } + + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( !modelDef ) { + return NULL; + } + + idRenderModel *renderModel = modelDef->ModelHandle(); + if ( !renderModel ) { + modelDef = NULL; + return NULL; + } + + // make sure model hasn't been purged + modelDef->Touch(); + +// RAVEN BEGIN +// bdube: make sure models dont get purged + if ( modelDef->ModelHandle() ) { + modelDef->ModelHandle()->SetLevelLoadReferenced ( true ); + } +// RAVEN END + + modelDef->SetupJoints( &numJoints, &joints, frameBounds, removeOriginOffset ); + modelDef->ModelHandle()->Reset(); + + // set the modelDef on all channels + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++ ) { + channels[ i ][ j ].Reset( modelDef ); + } + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::Size +===================== +*/ +size_t idAnimator::Size( void ) const { + return sizeof( *this ) + Allocated(); +} + +/* +===================== +idAnimator::SetEntity +===================== +*/ +void idAnimator::SetEntity( idEntity *ent ) { + entity = ent; +} + +/* +===================== +idAnimator::GetEntity +===================== +*/ +idEntity *idAnimator::GetEntity( void ) const { + return entity; +} + +/* +===================== +idAnimator::RemoveOriginOffset +===================== +*/ +void idAnimator::RemoveOriginOffset( bool remove ) { + removeOriginOffset = remove; +} + +/* +===================== +idAnimator::RemoveOrigin +===================== +*/ +bool idAnimator::RemoveOrigin( void ) const { + return removeOriginOffset; +} + +/* +===================== +idAnimator::GetJointList +===================== +*/ +void idAnimator::GetJointList( const char *jointnames, idList &jointList ) const { + if ( modelDef ) { + modelDef->GetJointList( jointnames, jointList ); + } +} + +/* +===================== +idAnimator::NumAnims +===================== +*/ +int idAnimator::NumAnims( void ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->NumAnims(); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +const idAnim *idAnimator::GetAnim( int index ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->GetAnim( index ); +} + +/* +===================== +idAnimator::GetAnim +===================== +*/ +int idAnimator::GetAnim( const char *name ) const { + if ( !modelDef ) { + return 0; + } + + return modelDef->GetAnim( name ); +} + +/* +===================== +idAnimator::HasAnim +===================== +*/ +bool idAnimator::HasAnim( const char *name ) const { + if ( !modelDef ) { + return false; + } + + return modelDef->HasAnim( name ); +} + +/* +===================== +idAnimator::NumJoints +===================== +*/ +int idAnimator::NumJoints( void ) const { + return numJoints; +} + +/* +===================== +idAnimator::ModelHandle +===================== +*/ +idRenderModel *idAnimator::ModelHandle( void ) const { + if ( !modelDef ) { + return NULL; + } + + return modelDef->ModelHandle(); +} + +/* +===================== +idAnimator::ModelDef +===================== +*/ +const idDeclModelDef *idAnimator::ModelDef( void ) const { + return modelDef; +} + +/* +===================== +idAnimator::CurrentAnim +===================== +*/ +idAnimBlend *idAnimator::CurrentAnim( int channelNum ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CurrentAnim : channel out of range" ); + } + + return &channels[ channelNum ][ 0 ]; +} + +/* +===================== +idAnimator::Clear +===================== +*/ +void idAnimator::Clear( int channelNum, int currentTime, int cleartime ) { + int i; + idAnimBlend *blend; + + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::Clear : channel out of range" ); + } + + blend = channels[ channelNum ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->Clear( currentTime, cleartime ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetFrame +===================== +*/ +void idAnimator::SetFrame( int channelNum, int animNum, const frameBlend_t & frameBlend ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SetFrame : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, gameLocal.time, 0 ); + channels[ channelNum ][ 0 ].SetFrame( modelDef, animNum, frameBlend ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::CycleAnim +===================== +*/ +void idAnimator::CycleAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::CycleAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].CycleAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + + // If the old animation and new animation both sync cycles then do so now + idAnimBlend* oldChannel = &channels[channelNum][1]; + idAnimBlend* newChannel = &channels[channelNum][0]; + if ( oldChannel->AnimNum ( ) && newChannel->AnimNum ( ) ) { + if ( oldChannel->Anim ( )->GetAnimFlags ( ).sync_cycle && newChannel->Anim ( )->GetAnimFlags ( ).sync_cycle ) { + // Calculate the percentage through the animation the old channel was + float f = (float)oldChannel->GetFrameNumber ( currentTime ) / (float)oldChannel->NumFrames ( ); + // Move the new channel back in time to start it mid cycle + newChannel->SetStartTime ( gameLocal.time - f * (newChannel->Length ( ) / newChannel->GetPlaybackRate()) ); + } + } +} + +/* +===================== +idAnimator::PlayAnim +===================== +*/ +void idAnimator::PlayAnim( int channelNum, int animNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::PlayAnim : channel out of range" ); + } + + if ( !modelDef || !modelDef->GetAnim( animNum ) ) { + return; + } + + PushAnims( channelNum, currentTime, blendTime ); + channels[ channelNum ][ 0 ].PlayAnim( modelDef, animNum, currentTime, blendTime, rateMultiplier ); + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +/* +===================== +idAnimator::SyncAnimChannels +===================== +*/ +void idAnimator::SyncAnimChannels( int channelNum, int fromChannelNum, int currentTime, int blendTime ) { + if ( ( channelNum < 0 ) || ( channelNum >= ANIM_NumAnimChannels ) || ( fromChannelNum < 0 ) || ( fromChannelNum >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::SyncToChannel : channel out of range" ); + } + + idAnimBlend &fromBlend = channels[ fromChannelNum ][ 0 ]; + idAnimBlend &toBlend = channels[ channelNum ][ 0 ]; + + float weight = fromBlend.blendEndValue; + if ( ( fromBlend.Anim() != toBlend.Anim() ) || ( fromBlend.GetStartTime() != toBlend.GetStartTime() ) || ( fromBlend.GetEndTime() != toBlend.GetEndTime() ) ) { + PushAnims( channelNum, currentTime, blendTime ); + toBlend = fromBlend; + toBlend.blendStartValue = 0.0f; + toBlend.blendEndValue = 0.0f; + } + toBlend.SetWeight( weight, currentTime - 1, blendTime ); + + // disable framecommands on the current channel so that commands aren't called twice + toBlend.AllowFrameCommands( false ); + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } +} + +// RAVEN BEGIN +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindExistingJointMod( jointHandle_t jointnum, int *index ) { + jointMod_t* jointMod; + int i; + + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( index ) + { + *index = i; + } + return jointMod; +} + +// bdube: added methods +/* +===================== +idAnimator::FindJointMod +===================== +*/ +jointMod_t* idAnimator::FindJointMod ( jointHandle_t jointnum ) { + jointMod_t* jointMod; + int i; + + jointMod = FindExistingJointMod( jointnum, &i ); + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMod->transform_pos = JOINTMOD_NONE; + jointMod->angularVelocity.Init ( 0, 0, 0, 0, idAngles(0,0,0), idAngles(0,0,0) ); + jointMod->lastTime = 0; + jointMods.Insert( jointMod, i ); + } + + return jointMod; +} + +// abahr: +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( const char* animName, float rate ) { + SetPlaybackRate( GetAnim(animName), rate ); +} + +/* +===================== +idAnimator::SetPlaybackRate +===================== +*/ +void idAnimator::SetPlaybackRate( int animHandle, float rate ) { + idAnim* anim = const_cast( GetAnim(animHandle) ); + if( anim ) { + anim->SetPlaybackRate( rate ); + } +} +// RAVEN END + +/* +===================== +idAnimator::SetJointPos +===================== +*/ +void idAnimator::SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ) { +// RAVEN BEGIN +// bdube: moved old code to FindJointMod +/* + int i; +*/ +// + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + +// RAVEN BEGIN +// bdube: moved old code to FindJointMod + jointMod = FindJointMod ( jointnum ); + +/* + jointMod = NULL; + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + jointMod = jointMods[ i ]; + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } + + if ( !jointMod ) { + jointMod = new jointMod_t; + jointMod->jointnum = jointnum; + jointMod->mat.Identity(); + jointMod->transform_axis = JOINTMOD_NONE; + jointMods.Insert( jointMod, i ); + } +*/ +// RAVEN END + + jointMod->pos = pos; + jointMod->transform_pos = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAxis +===================== +*/ +void idAnimator::SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ) { + jointMod_t *jointMod; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = FindJointMod( jointnum ); + jointMod->mat = mat; + jointMod->transform_axis = transform_type; + + if ( entity ) { + entity->BecomeActive( TH_ANIMATE ); + } + ForceUpdate(); +} + +// RAVEN BEGIN +// twhitaker: just added this for uniformity +/* +===================== +idAnimator::GetJointAxis +===================== +*/ +void idAnimator::GetJointAxis( jointHandle_t jointnum, idMat3 &mat ) { + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + mat = FindJointMod ( jointnum )->mat; +} +// RAVEN END + +/* +===================== +idAnimator::CollapseJoint + +Add a joint modification for the given joint to collapse it to another joint +===================== +*/ +void idAnimator::CollapseJoint ( jointHandle_t jointnum, jointHandle_t collapseTo ) { + jointMod_t *jointMod; + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + jointMod = FindJointMod ( jointnum ); + + jointMod->transform_pos = JOINTMOD_COLLAPSE; + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat.Zero ( ); + jointMod->collapseJoint = collapseTo; +} + +/* +===================== +idAnimator::CollapseJoints + +Add a collapse joint modification for each joint listed in "jointnames" to the given joint "coppaseJoint" +===================== +*/ +void idAnimator::CollapseJoints ( const char *jointnames, jointHandle_t collpaseJoint ) { + idList jointList; + int i; + + GetJointList( jointnames, jointList ); + + for ( i = 0; i < jointList.Num(); i ++ ) { + CollapseJoint ( jointList[i], collpaseJoint ); + } +} + +// RAVEN BEGIN +// bdube: added more programmer controlled joint methods +/* +===================== +idAnimator::AimJointAt +===================== +*/ +void idAnimator::AimJointAt ( jointHandle_t jointnum, const idVec3& pos, const int currentTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + idVec3 offset; + idVec3 origin; + idMat3 axis; + idVec3 dir; + idVec3 localDir; + + GetJointTransform ( jointnum, currentTime, offset, axis ); + + origin = entity->GetPhysics()->GetOrigin() + offset * entity->GetPhysics()->GetAxis(); + + dir = pos - origin; + dir.NormalizeFast ( ); + entity->GetPhysics()->GetAxis().ProjectVector ( dir, localDir ); + + jointMod->transform_axis = JOINTMOD_WORLD_OVERRIDE; + jointMod->mat = localDir.ToMat3(); + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::SetJointAngularVelocity +===================== +*/ +void idAnimator::SetJointAngularVelocity ( jointHandle_t jointnum, const idAngles& vel, const int currentTime, const int blendTime ) { + jointMod_t *jointMod; + + jointMod = FindJointMod ( jointnum ); + + if ( !jointMod->lastTime ) { + jointMod->lastTime = currentTime; + jointMod->mat = idAngles(0,0,0).ToMat3(); + } + + jointMod->transform_axis = JOINTMOD_LOCAL; + + if ( blendTime <= 0 ) { + jointMod->angularVelocity.Init ( currentTime, 0, 0, 1, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } else { + jointMod->angularVelocity.Init ( currentTime, blendTime/2, blendTime/2, blendTime, jointMod->angularVelocity.GetCurrentValue(currentTime), vel ); + } + + entity->BecomeActive( TH_ANIMATE ); + ForceUpdate(); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +idAngles idAnimator::GetJointAngularVelocity ( jointHandle_t jointnum, const int currentTime ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + return jointMod->angularVelocity.GetCurrentValue( currentTime ); +} + +/* +===================== +idAnimator::GetJointAngularVelocity +===================== +*/ +void idAnimator::ClearJointAngularVelocity ( jointHandle_t jointnum ) { + jointMod_t *jointMod = FindJointMod ( jointnum ); + + jointMod->angularVelocity.Init( 0, 0, 0, 0, jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue(), jointMod->angularVelocity.GetStartValue() - jointMod->angularVelocity.GetStartValue() ); +} +// RAVEN END + +/* +===================== +idAnimator::ClearJoint +===================== +*/ +void idAnimator::ClearJoint( jointHandle_t jointnum ) { + int i; + + if ( !modelDef || !modelDef->ModelHandle() || ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + return; + } + + for( i = 0; i < jointMods.Num(); i++ ) { + if ( jointMods[ i ]->jointnum == jointnum ) { + delete jointMods[ i ]; + jointMods.RemoveIndex( i ); + ForceUpdate(); + break; + } else if ( jointMods[ i ]->jointnum > jointnum ) { + break; + } + } +} + +/* +===================== +idAnimator::ClearAllJoints +===================== +*/ +void idAnimator::ClearAllJoints( void ) { + if ( jointMods.Num() ) { + ForceUpdate(); + } + jointMods.DeleteContents( true ); +} + +/* +===================== +idAnimator::ClearAllAnims +===================== +*/ +void idAnimator::ClearAllAnims( int currentTime, int cleartime ) { + int i; + + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + Clear( i, currentTime, cleartime ); + } + + ClearAFPose(); + ForceUpdate(); +} + +/* +==================== +idAnimator::GetDelta +==================== +*/ +void idAnimator::GetDelta( int fromtime, int totime, idVec3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Zero(); + return; + } + + delta.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDelta( fromtime, totime, delta, blendWeight ); + } + } +} + +/* +==================== +idAnimator::GetDeltaRotation +==================== +*/ +bool idAnimator::GetDeltaRotation( int fromtime, int totime, idMat3 &delta ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + idQuat q; + + if ( !modelDef || !modelDef->ModelHandle() || ( fromtime == totime ) ) { + delta.Identity(); + return false; + } + + q.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendDeltaRotation( blend->starttime + fromtime, totime, q, blendWeight ); + } + } + + if ( blendWeight > 0.0f ) { + delta = q.ToMat3(); + return true; + } else { + delta.Identity(); + return false; + } +} + +/* +==================== +idAnimator::GetOrigin +==================== +*/ +void idAnimator::GetOrigin( int currentTime, idVec3 &pos ) const { + int i; + const idAnimBlend *blend; + float blendWeight; + + if ( !modelDef || !modelDef->ModelHandle() ) { + pos.Zero(); + return; + } + + pos.Zero(); + blendWeight = 0.0f; + + blend = channels[ ANIMCHANNEL_ALL ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + + if ( modelDef->Joints()[ 0 ].channel ) { + blend = channels[ modelDef->Joints()[ 0 ].channel ]; + for( i = 0; i < ANIM_MaxAnimsPerChannel; i++, blend++ ) { + blend->BlendOrigin( currentTime, pos, blendWeight, removeOriginOffset ); + } + } + + pos += modelDef->GetVisualOffset(); +} + +/* +==================== +idAnimator::GetBounds +==================== +*/ +bool idAnimator::GetBounds( int currentTime, idBounds &bounds ) { + int i, j; + const idAnimBlend *blend; + int count; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + if ( AFPoseJoints.Num() ) { + bounds = AFPoseBounds; + count = 1; + } else { + bounds.Clear(); + count = 0; + } + + blend = channels[ 0 ]; + for( i = ANIMCHANNEL_ALL; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->AddBounds( currentTime, bounds, removeOriginOffset ) ) { + count++; + } + } + } + + if ( !count ) { + if ( !frameBounds.IsCleared() ) { + bounds = frameBounds; + return true; + } else { + bounds.Zero(); + return false; + } + } + + bounds.TranslateSelf( modelDef->GetVisualOffset() ); +// RAVEN BEGIN +// jscott: HACK! The bounds only get the range of the skeleton, so add in the distance between the highest bone and the top of the head + bounds[1][2] += 4.0f; +// RAVEN END + + if ( g_debugBounds.GetBool() ) { + if ( bounds[1][0] - bounds[0][0] > 2048 || bounds[1][1] - bounds[0][1] > 2048 ) { + if ( entity ) { + gameLocal.Warning( "big frameBounds on entity '%s' with model '%s': %f,%f", entity->name.c_str(), modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } else { + gameLocal.Warning( "big frameBounds on model '%s': %f,%f", modelDef->ModelHandle()->Name(), bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[0][1] ); + } + } + } + + frameBounds = bounds; + + return true; +} + +/* +===================== +idAnimator::InitAFPose +===================== +*/ +void idAnimator::InitAFPose( void ) { + + if ( !modelDef ) { + return; + } + + AFPoseJoints.SetNum( modelDef->Joints().Num(), false ); + AFPoseJoints.SetNum( 0, false ); + AFPoseJointMods.SetNum( modelDef->Joints().Num(), false ); + + if ( AFPoseJointFrame == NULL || AFPoseJointFrameSize != modelDef->Joints().Num() ) { + Mem_Free16( AFPoseJointFrame ); + AFPoseJointFrame = (idJointQuat *) Mem_Alloc16( modelDef->Joints().Num() * sizeof( AFPoseJointFrame[0] ), MA_ANIM ); + AFPoseJointFrameSize = modelDef->Joints().Num(); + } +} + +/* +===================== +idAnimator::SetAFPoseJointMod +===================== +*/ +void idAnimator::SetAFPoseJointMod( const jointHandle_t jointNum, const AFJointModType_t mod, const idMat3 &axis, const idVec3 &origin ) { + AFPoseJointMods[jointNum].mod = mod; + AFPoseJointMods[jointNum].axis = axis; + AFPoseJointMods[jointNum].origin = origin; + + int index = idBinSearch_GreaterEqual( AFPoseJoints.Ptr(), AFPoseJoints.Num(), jointNum ); + if ( index >= AFPoseJoints.Num() || jointNum != AFPoseJoints[index] ) { + AFPoseJoints.Insert( jointNum, index ); + } +} + +/* +===================== +idAnimator::FinishAFPose +===================== +*/ +void idAnimator::FinishAFPose( int animNum, const idBounds &bounds, const int time ) { + int i, j; + int numJoints; + int parentNum; + int jointMod; + int jointNum; + const int * jointParent; + + if ( !modelDef ) { + return; + } + + const idAnim *anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return; + } + + numJoints = modelDef->Joints().Num(); + if ( !numJoints ) { + return; + } + + idRenderModel *md5 = modelDef->ModelHandle(); + const idMD5Anim *md5anim = anim->MD5Anim( 0 ); + + if ( numJoints != md5anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", md5->Name(), numJoints, md5anim->Name(), md5anim->NumJoints() ); +// RAVEN END + return; + } + + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + md5anim->GetSingleFrame( 0, jointFrame, modelDef->GetChannelJoints( ANIMCHANNEL_ALL ), modelDef->NumJointsOnChannel( ANIMCHANNEL_ALL ) ); + + if ( removeOriginOffset ) { +#ifdef VELOCITY_MOVE + jointFrame[ 0 ].t.x = 0.0f; +#else + jointFrame[ 0 ].t.Zero(); +#endif + } + + idJointMat *joints = ( idJointMat * )_alloca16( numJoints * sizeof( *joints ) ); + + // convert the joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( AFPoseJoints.Num() && AFPoseJoints[0] == 0 ) { + switch( AFPoseJointMods[0].mod ) { + case AF_JOINTMOD_AXIS: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[0].SetRotation( AFPoseJointMods[0].axis ); + joints[0].SetTranslation( AFPoseJointMods[0].origin ); + break; + } + } + j = 1; + } else { + j = 0; + } + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // transform the child joints + for( i = 1; j < AFPoseJoints.Num(); j++, i++ ) { + jointMod = AFPoseJoints[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod - 1 ); + i = jointMod; + + parentNum = jointParent[i]; + + switch( AFPoseJointMods[jointMod].mod ) { + case AF_JOINTMOD_AXIS: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + } + case AF_JOINTMOD_ORIGIN: { + joints[i].SetRotation( joints[i].ToMat3() * joints[parentNum].ToMat3() ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + case AF_JOINTMOD_BOTH: { + joints[i].SetRotation( AFPoseJointMods[jointMod].axis ); + joints[i].SetTranslation( AFPoseJointMods[jointMod].origin ); + break; + } + } + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + // untransform hierarchy + SIMDProcessor->UntransformJoints( joints, jointParent, 1, numJoints - 1 ); + + // convert joint matrices back to joint quaternions + SIMDProcessor->ConvertJointMatsToJointQuats( AFPoseJointFrame, joints, numJoints ); + + // find all modified joints and their parents + bool *blendJoints = (bool *) _alloca16( numJoints * sizeof( bool ) ); + memset( blendJoints, 0, numJoints * sizeof( bool ) ); + + // mark all modified joints and their parents + for( i = 0; i < AFPoseJoints.Num(); i++ ) { + for( jointNum = AFPoseJoints[i]; jointNum != INVALID_JOINT; jointNum = jointParent[jointNum] ) { + blendJoints[jointNum] = true; + } + } + + // lock all parents of modified joints + AFPoseJoints.SetNum( 0, false ); + for ( i = 0; i < numJoints; i++ ) { + if ( blendJoints[i] ) { + AFPoseJoints.Append( i ); + } + } + + AFPoseBounds = bounds; + AFPoseTime = time; + + ForceUpdate(); +} + +/* +===================== +idAnimator::SetAFPoseBlendWeight +===================== +*/ +void idAnimator::SetAFPoseBlendWeight( float blendWeight ) { + AFPoseBlendWeight = blendWeight; +} + +/* +===================== +idAnimator::BlendAFPose +===================== +*/ +bool idAnimator::BlendAFPose( idJointQuat *blendFrame ) const { + + if ( !AFPoseJoints.Num() ) { + return false; + } + + SIMDProcessor->BlendJoints( blendFrame, AFPoseJointFrame, AFPoseBlendWeight, AFPoseJoints.Ptr(), AFPoseJoints.Num() ); + + return true; +} + +/* +===================== +idAnimator::ClearAFPose +===================== +*/ +void idAnimator::ClearAFPose( void ) { + if ( AFPoseJoints.Num() ) { + ForceUpdate(); + } + AFPoseBlendWeight = 1.0f; + AFPoseJoints.SetNum( 0, false ); + AFPoseBounds.Clear(); + AFPoseTime = 0; +} + +/* +===================== +idAnimator::ServiceAnims +===================== +*/ +void idAnimator::ServiceAnims( int fromtime, int totime ) { + int i, j; + idAnimBlend *blend; + + if ( !modelDef ) { + return; + } + + if ( modelDef->ModelHandle() ) { + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + blend->CallFrameCommands( entity, fromtime, totime ); + } + } + } + + if ( !IsAnimating( totime ) ) { + stoppedAnimatingUpdate = true; + if ( entity ) { + entity->BecomeInactive( TH_ANIMATE ); + + // present one more time with stopped animations so the renderer can properly recreate interactions + entity->BecomeActive( TH_UPDATEVISUALS ); + } + } +} + +// RAVEN BEGIN +// rjohnson: added flag to ignore AF when checking for animation +/* +===================== +idAnimator::IsAnimating +===================== +*/ +bool idAnimator::IsAnimating( int currentTime, bool IgnoreAF ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( !IgnoreAF && AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( !blend->IsDone( currentTime ) ) { + return true; + } + } + } + + return false; +} +// RAVEN END + +/* +===================== +idAnimator::FrameHasChanged +===================== +*/ +bool idAnimator::FrameHasChanged( int currentTime ) const { + int i, j; + const idAnimBlend *blend; + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + + // if animating with an articulated figure + if ( AFPoseJoints.Num() && currentTime <= AFPoseTime ) { + return true; + } + + blend = channels[ 0 ]; + for( i = 0; i < ANIM_NumAnimChannels; i++ ) { + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->FrameHasChanged( currentTime ) ) { + return true; + } + } + } + + if ( forceUpdate && IsAnimating( currentTime ) ) { + return true; + } + + return false; +} + +/* +===================== +idAnimator::CreateFrame +===================== +*/ +bool idAnimator::CreateFrame( int currentTime, bool force ) { + int i, j; + int numJoints; + int parentNum; + bool hasAnim; + bool debugInfo; + float baseBlend; + float blendWeight; + const idAnimBlend * blend; + const int * jointParent; +// RAVEN BEGIN +// bdube: removed const + jointMod_t * jointMod; +// RAVEN END + const idJointQuat * defaultPose; + + static idCVar r_showSkel( "r_showSkel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw the skeleton when model animates, 1 = draw model with skeleton, 2 = draw skeleton only, 3 = draw joints only", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); + + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + if ( !modelDef || !modelDef->ModelHandle() ) { + return false; + } + +// RAVEN BEGIN +// rjohnson: always check the time transform except force. Even when skeleton is on + if ( !force ) { + if ( lastTransformTime == currentTime ) { + return false; + } + } + + if ( !force && !r_showSkel.GetInteger() ) { + if ( lastTransformTime != -1 && !stoppedAnimatingUpdate && !IsAnimating( currentTime ) ) { + return false; + } + } + +#if 0 + if ( !gameLocal.isLastPredictFrame ) { + return false; + } +#endif +// RAVEN END + + lastTransformTime = currentTime; + stoppedAnimatingUpdate = false; + + if ( entity && ( ( g_debugAnim.GetInteger() == entity->entityNumber ) || ( g_debugAnim.GetInteger() == -2 ) ) ) { + debugInfo = true; + gameLocal.Printf( "---------------\n%d: entity '%s':\n", gameLocal.time, entity->GetName() ); + gameLocal.Printf( "model '%s':\n", modelDef->GetModelName() ); + } else { + debugInfo = false; + } + + // init the joint buffer + if ( AFPoseJoints.Num() ) { + // initialize with AF pose anim for the case where there are no other animations and no AF pose joint modifications + defaultPose = AFPoseJointFrame; + } else { + defaultPose = modelDef->GetDefaultPose(); + } + + if ( !defaultPose ) { + //gameLocal.Warning( "idAnimator::CreateFrame: no defaultPose on '%s'", modelDef->Name() ); + return false; + } + + numJoints = modelDef->Joints().Num(); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( jointFrame[0] ) ); + SIMDProcessor->Memcpy( jointFrame, defaultPose, numJoints * sizeof( jointFrame[0] ) ); + + hasAnim = false; + + // blend the all channel + baseBlend = 0.0f; + blend = channels[ ANIMCHANNEL_ALL ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_ALL, numJoints, jointFrame, baseBlend, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( baseBlend >= 1.0f ) { + break; + } + } + } + + // only blend other channels if there's enough space to blend into + if ( baseBlend < 1.0f ) { + for( i = ANIMCHANNEL_ALL + 1; i < ANIM_NumAnimChannels; i++ ) { + if ( !modelDef->NumJointsOnChannel( i ) ) { + continue; + } + if ( i == ANIMCHANNEL_EYELIDS ) { + // eyelids blend over any previous anims, so skip it and blend it later + continue; + } + blendWeight = baseBlend; + blend = channels[ i ]; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, i, numJoints, jointFrame, blendWeight, removeOriginOffset, false, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + + if ( debugInfo && !AFPoseJoints.Num() && !blendWeight ) { + gameLocal.Printf( "%d: %s using default pose in model '%s'\n", gameLocal.time, channelNames[ i ], modelDef->GetModelName() ); + } + } + } + + // blend in the eyelids + if ( modelDef->NumJointsOnChannel( ANIMCHANNEL_EYELIDS ) ) { + blend = channels[ ANIMCHANNEL_EYELIDS ]; + blendWeight = baseBlend; + for( j = 0; j < ANIM_MaxAnimsPerChannel; j++, blend++ ) { + if ( blend->BlendAnim( currentTime, ANIMCHANNEL_EYELIDS, numJoints, jointFrame, blendWeight, removeOriginOffset, true, debugInfo ) ) { + hasAnim = true; + if ( blendWeight >= 1.0f ) { + // fully blended + break; + } + } + } + } + + // blend the articulated figure pose + if ( BlendAFPose( jointFrame ) ) { + hasAnim = true; + } + + if ( !hasAnim && !jointMods.Num() ) { + // no animations were updated + return false; + } + + // convert the joint quaternions to rotation matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // check if we need to modify the origin + if ( jointMods.Num() && ( jointMods[0]->jointnum == 0 ) ) { + jointMod = jointMods[0]; + + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetRotation( jointMod->mat * joints[0].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[0].SetRotation( joints[0].ToMat3() * jointMod->mat ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetRotation( jointMod->mat ); + break; + } + + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + break; + + case JOINTMOD_LOCAL: + joints[0].SetTranslation( joints[0].ToVec3() + jointMod->pos ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + case JOINTMOD_WORLD: + case JOINTMOD_WORLD_OVERRIDE: + joints[0].SetTranslation( jointMod->pos ); + break; + } + j = 1; + } else { + j = 0; + } + + // add in the model offset + joints[0].SetTranslation( joints[0].ToVec3() + modelDef->GetVisualOffset() ); + + // pointer to joint info + jointParent = modelDef->JointParents(); + + // add in any joint modifications + for( i = 1; j < jointMods.Num(); j++, i++ ) { + jointMod = jointMods[j]; + + // transform any joints preceding the joint modifier + SIMDProcessor->TransformJoints( joints, jointParent, i, jointMod->jointnum - 1 ); + i = jointMod->jointnum; + + parentNum = jointParent[i]; + +// RAVEN BEGIN +// bdube: angular velocity on joints + if ( jointMod->angularVelocity.GetStartTime ( ) ) { + idAngles angles; + angles = jointMod->angularVelocity.GetCurrentValue ( currentTime ) * MS2SEC(currentTime-jointMod->lastTime); + angles.Normalize360 ( ); + jointMod->mat *= angles.ToMat3 ( ); + } +// RAVEN END + + // modify the axis + switch( jointMod->transform_axis ) { + case JOINTMOD_NONE: + joints[i].SetRotation( joints[i].ToMat3() * joints[ parentNum ].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetRotation( jointMod->mat * ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetRotation( jointMod->mat * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetRotation( ( joints[i].ToMat3() * joints[parentNum].ToMat3() ) * jointMod->mat ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetRotation( jointMod->mat ); + break; + } + + // modify the position + switch( jointMod->transform_pos ) { + case JOINTMOD_NONE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL: + joints[i].SetTranslation( joints[parentNum].ToVec3() + ( joints[i].ToVec3() + jointMod->pos ) * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_LOCAL_OVERRIDE: + joints[i].SetTranslation( joints[parentNum].ToVec3() + jointMod->pos * joints[parentNum].ToMat3() ); + break; + + case JOINTMOD_WORLD: + joints[i].SetTranslation( joints[parentNum].ToVec3() + joints[i].ToVec3() * joints[parentNum].ToMat3() + jointMod->pos ); + break; + + case JOINTMOD_WORLD_OVERRIDE: + joints[i].SetTranslation( jointMod->pos ); + break; + + case JOINTMOD_COLLAPSE: + joints[i].SetTranslation( joints[jointMod->collapseJoint].ToVec3() ); + break; + } + +// RAVEN BEGIN +// bdube: more joint control + jointMod->lastTime = currentTime; +// RAVEN END + } + + // transform the rest of the hierarchy + SIMDProcessor->TransformJoints( joints, jointParent, i, numJoints - 1 ); + + return true; +} + +/* +===================== +idAnimator::ForceUpdate +===================== +*/ +void idAnimator::ForceUpdate( void ) { + lastTransformTime = -1; + forceUpdate = true; +} + +/* +===================== +idAnimator::ClearForceUpdate +===================== +*/ +void idAnimator::ClearForceUpdate( void ) { + forceUpdate = false; +} + +/* +===================== +idAnimator::GetJointTransform + +===================== +*/ +bool idAnimator::GetJointTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef || ( jointHandle < 0 ) || ( jointHandle >= modelDef->NumJoints() ) ) { + return false; + } + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + CreateFrame( currentTime, false ); + + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + + return true; +} + +/* +===================== +idAnimator::GetJointLocalTransform +===================== +*/ +bool idAnimator::GetJointLocalTransform( jointHandle_t jointHandle, int currentTime, idVec3 &offset, idMat3 &axis ) { + if ( !modelDef ) { + return false; + } + + const idList &modelJoints = modelDef->Joints(); + + if ( ( jointHandle < 0 ) || ( jointHandle >= modelJoints.Num() ) ) { + return false; + } + + if ( g_perfTest_noJointTransform.GetBool() ) { + offset = entity->GetPhysics()->GetCenterMass()-entity->GetPhysics()->GetOrigin(); + axis = entity->GetRenderEntity()->axis; + return true; + } + + // FIXME: overkill + CreateFrame( currentTime, false ); + + if ( jointHandle > 0 ) { + idJointMat m = joints[ jointHandle ]; + m /= joints[ modelJoints[ jointHandle ].parentNum ]; + offset = m.ToVec3(); + axis = m.ToMat3(); + } else { + offset = joints[ jointHandle ].ToVec3(); + axis = joints[ jointHandle ].ToMat3(); + } + + return true; +} + +/* +===================== +idAnimator::GetJointHandle +===================== +*/ +jointHandle_t idAnimator::GetJointHandle( const char *name ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return INVALID_JOINT; + } + + return modelDef->ModelHandle()->GetJointHandle( name ); +} + +/* +===================== +idAnimator::GetJointName +===================== +*/ +const char *idAnimator::GetJointName( jointHandle_t handle ) const { + if ( !modelDef || !modelDef->ModelHandle() ) { + return ""; + } + + return modelDef->ModelHandle()->GetJointName( handle ); +} + +/* +===================== +idAnimator::GetChannelForJoint +===================== +*/ +int idAnimator::GetChannelForJoint( jointHandle_t joint ) const { + if ( !modelDef ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: NULL model" ); + } + + if ( ( joint < 0 ) || ( joint >= numJoints ) ) { + gameLocal.Error( "idAnimator::GetChannelForJoint: invalid joint num (%d)", joint ); + } + + return modelDef->GetJoint( joint )->channel; +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( const char *name ) const { + return GetFirstChild( GetJointHandle( name ) ); +} + +/* +===================== +idAnimator::GetFirstChild +===================== +*/ +jointHandle_t idAnimator::GetFirstChild( jointHandle_t jointnum ) const { + int i; + int num; + const jointInfo_t *joint; + + if ( !modelDef ) { + return INVALID_JOINT; + } + + num = modelDef->NumJoints(); + if ( !num ) { + return jointnum; + } + joint = modelDef->GetJoint( 0 ); + for( i = 0; i < num; i++, joint++ ) { + if ( joint->parentNum == jointnum ) { + return ( jointHandle_t )joint->num; + } + } + return jointnum; +} + +/* +===================== +idAnimator::GetJoints +===================== +*/ +void idAnimator::GetJoints( int *numJoints, idJointMat **jointsPtr ) { + *numJoints = this->numJoints; + *jointsPtr = this->joints; +} + +/* +===================== +idAnimator::GetAnimFlags +===================== +*/ +const animFlags_t idAnimator::GetAnimFlags( int animNum ) const { + animFlags_t result; + + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->GetAnimFlags(); + } + + memset( &result, 0, sizeof( result ) ); + return result; +} + +/* +===================== +idAnimator::IsBlending + +Returns true if the given channel number is currently blending more than one animation +===================== +*/ +bool idAnimator::IsBlending ( int channel, int currentTime ) const { + if ( ( channel < 0 ) || ( channel >= ANIM_NumAnimChannels ) ) { + gameLocal.Error( "idAnimator::GetAnimCount : channel (%d) out of range", channel ); + } + + int i; + for( i = 1; i < ANIM_MaxAnimsPerChannel; i++ ) { + if ( !channels[ channel ][ i ].IsDone ( currentTime ) ) { + return true; + } + } + + return false; +} + +/* +===================== +idAnimator::NumFrames +===================== +*/ +int idAnimator::NumFrames( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumFrames(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::NumSyncedAnims +===================== +*/ +int idAnimator::NumSyncedAnims( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->NumAnims(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::AnimName +===================== +*/ +const char *idAnimator::AnimName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Name(); + } else { + return ""; + } +} + +/* +===================== +idAnimator::AnimFullName +===================== +*/ +const char *idAnimator::AnimFullName( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } else { + return ""; + } +} + +// RAVEN BEGIN +// rjohnson: more output for animators +/* +===================== +idAnimator::AnimMD5Name +===================== +*/ +const char *idAnimator::AnimMD5Name( int animNum, int index ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + const idMD5Anim *md5Anim = anim->MD5Anim( index ); + + if ( md5Anim ) { + return md5Anim->Name(); + } else { + return ""; + } + } else { + return ""; + } +} +// RAVEN END + +/* +===================== +idAnimator::AnimLength +===================== +*/ +int idAnimator::AnimLength( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->Length(); + } else { + return 0; + } +} + +/* +===================== +idAnimator::TotalMovementDelta +===================== +*/ +const idVec3 &idAnimator::TotalMovementDelta( int animNum ) const { + const idAnim *anim = GetAnim( animNum ); + if ( anim ) { + return anim->TotalMovementDelta(); + } else { + return vec3_origin; + } +} + +// RAVEN BEGIN +// nrausch: added func +/* +===================== +idAnimator::GetNearestJoint +===================== +*/ +jointHandle_t idAnimator::GetNearestJoint( const idVec3 &start, const idVec3 &end, int currentTime, jointHandle_t *jointList, int cnt ) { + + int numJoints = 0; + idJointMat *joints = 0; + GetJoints( &numJoints, &joints ); + + jointHandle_t closestJoint = INVALID_JOINT; + float closest = 0.0f; + + idVec3 a = end - start; + a.Normalize(); + + for ( int i = 0; i < cnt; ++i ) { + + // If we specified a null jointlist, just run through each joint + jointHandle_t jointNum = (jointHandle_t)i; + if ( jointList ) { + jointNum = jointList[i]; + } + + if ( jointNum != INVALID_JOINT ) { + idVec3 jointPos; + idMat3 jointMat; + + GetJointTransform( jointNum, currentTime, jointPos, jointMat ); + + idVec3 b = jointPos - start; + b.Normalize(); + + float newDot = DotProduct(a, b); + + if ( newDot > closest ) { + closest = newDot; + closestJoint = jointNum; + } + } + } + + return closestJoint; +} +// RAVEN END + +/*********************************************************************** + + Util functions + +***********************************************************************/ + +/* +===================== +ANIM_GetModelDefFromEntityDef +===================== +*/ +const idDeclModelDef *ANIM_GetModelDefFromEntityDef( const idDict *args ) { + const idDeclModelDef *modelDef; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef && modelDef->ModelHandle() ) { + return modelDef; + } + + return NULL; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const idDict *args ) { + idRenderModel *model; + const idDeclModelDef *modelDef; + + model = NULL; + + idStr name = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + + if ( !model ) { + model = renderModelManager->FindModel( name ); + } + + if ( model && model->IsDefaultModel() ) { + return NULL; + } + + return model; +} + +/* +===================== +idGameEdit::ANIM_GetModelFromEntityDef +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromEntityDef( const char *classname ) { + const idDict *args; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + return ANIM_GetModelFromEntityDef( args ); +} + +/* +===================== +idGameEdit::ANIM_GetModelOffsetFromEntityDef +===================== +*/ +const idVec3 &idGameEdit::ANIM_GetModelOffsetFromEntityDef( const char *classname ) { + const idDict *args; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return vec3_origin; + } + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( !modelDef ) { + return vec3_origin; + } + + return modelDef->GetVisualOffset(); +} + +/* +===================== +idGameEdit::ANIM_GetModelFromName +===================== +*/ +idRenderModel *idGameEdit::ANIM_GetModelFromName( const char *modelName ) { + const idDeclModelDef *modelDef; + idRenderModel *model; + + model = NULL; + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelName, false ) ); + if ( modelDef ) { + model = modelDef->ModelHandle(); + } + if ( !model ) { + model = renderModelManager->FindModel( modelName ); + } + return model; +} + +/* +===================== +idGameEdit::ANIM_GetAnimFromEntityDef +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntityDef( const char *classname, const char *animname ) { + const idDict *args; + const idMD5Anim *md5anim; + const idAnim *anim; + int animNum; + const char *modelname; + const idDeclModelDef *modelDef; + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + md5anim = NULL; + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( animNum ) { + anim = modelDef->GetAnim( animNum ); + if ( anim ) { + md5anim = anim->MD5Anim( 0 ); + } + } + } + return md5anim; +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetAnimFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const idMD5Anim *idGameEdit::ANIM_GetAnimFromEntity( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return NULL; + } + + return anim->MD5Anim(0); +} + +/* +===================== +idGameEdit::ANIM_GetAnimPlaybackRateFromEntity +===================== +*/ +float idGameEdit::ANIM_GetAnimPlaybackRateFromEntity ( idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return 1.0f; + } + + return anim->GetPlaybackRate(); +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntity +===================== +*/ +// scork: const-qualified 'ent' +const char* idGameEdit::ANIM_GetAnimNameFromEntity ( const idEntity* ent, int animNum ) { + const idAnim * anim; + + anim = ent->GetAnimator ( )->GetAnim ( animNum ); + if ( !anim ) { + return ""; + } + + return anim->FullName(); +} +// RAVEN END + +/* +===================== +idGameEdit::ANIM_GetNumAnimsFromEntityDef +===================== +*/ +int idGameEdit::ANIM_GetNumAnimsFromEntityDef( const idDict *args ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + return modelDef->NumAnims(); + } + return 0; +} + +/* +===================== +idGameEdit::ANIM_GetAnimNameFromEntityDef +===================== +*/ +const char *idGameEdit::ANIM_GetAnimNameFromEntityDef( const idDict *args, int animNum ) { + const char *modelname; + const idDeclModelDef *modelDef; + + modelname = args->GetString( "model" ); + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, modelname, false ) ); + if ( modelDef ) { + const idAnim* anim = modelDef->GetAnim( animNum ); + if ( anim ) { + return anim->FullName(); + } + } + return ""; +} + +/* +===================== +idGameEdit::ANIM_GetAnim +===================== +*/ +const idMD5Anim *idGameEdit::ANIM_GetAnim( const char *fileName ) { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + return animationLib->GetAnim( fileName ); +// RAVEN END +} + +/* +===================== +idGameEdit::ANIM_GetLength +===================== +*/ +int idGameEdit::ANIM_GetLength( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->Length(); +} + +/* +===================== +idGameEdit::ANIM_GetNumFrames +===================== +*/ +int idGameEdit::ANIM_GetNumFrames( const idMD5Anim *anim ) { + if ( !anim ) { + return 0; + } + return anim->NumFrames(); +} + +// RAVEN BEGIN +// bdube: added +/* +===================== +idGameEdit::ANIM_GetFilename +===================== +*/ +const char * idGameEdit::ANIM_GetFilename( const idMD5Anim* anim ) { + return anim->Name(); +} + +/* +===================== +idGameEdit::ANIM_ConvertFrameToTime +===================== +*/ +int idGameEdit::ANIM_ConvertFrameToTime ( const idMD5Anim* anim, int frame ) { + frameBlend_t fb; + fb.frame1 = frame; + fb.frame2 = frame; + return anim->ConvertFrameToTime ( fb ); +} + +/* +===================== +idGameEdit::ANIM_ConvertTimeToFrame +===================== +*/ +int idGameEdit::ANIM_ConvertTimeToFrame ( const idMD5Anim* anim, int time ) { + frameBlend_t fb; + anim->ConvertTimeToFrame ( time, 0, fb ); + return fb.frame1; +} + +// RAVEN END + +/* +===================== +idGameEdit::ANIM_CreateAnimFrame +===================== +*/ +void idGameEdit::ANIM_CreateAnimFrame( const idRenderModel *model, const idMD5Anim *anim, int numJoints, idJointMat *joints, int time, const idVec3 &offset, bool remove_origin_offset ) { + int i; + frameBlend_t frame; + const idMD5Joint *md5joints; + int *index; + + if ( !model || model->IsDefaultModel() || !anim ) { + return; + } + + if ( numJoints != model->NumJoints() ) { + gameLocal.Error( "ANIM_CreateAnimFrame: different # of joints in renderEntity_t (%d) than in model (%s)(%d)", model->NumJoints(), model->Name(), numJoints ); + } + + if ( !model->NumJoints() ) { + // FIXME: Print out a warning? + return; + } + + if ( !joints ) { + gameLocal.Error( "ANIM_CreateAnimFrame: NULL joint frame pointer on model (%s)", model->Name() ); + } + + if ( numJoints != anim->NumJoints() ) { +// RAVEN BEGIN +// scork: reports the actual joint # mismatch as well + gameLocal.Warning( "Model '%s' has different # of joints (%d) than anim '%s' (%d)", model->Name(), numJoints, anim->Name(), anim->NumJoints() ); +// RAVEN END + for( i = 0; i < numJoints; i++ ) { + joints[i].SetRotation( mat3_identity ); + joints[i].SetTranslation( offset ); + } + return; + } + + // create index for all joints + index = ( int * )_alloca16( numJoints * sizeof( int ) ); + for ( i = 0; i < numJoints; i++ ) { + index[i] = i; + } + + // create the frame + anim->ConvertTimeToFrame( time, 1, frame ); + idJointQuat *jointFrame = ( idJointQuat * )_alloca16( numJoints * sizeof( *jointFrame ) ); + anim->GetInterpolatedFrame( frame, jointFrame, index, numJoints ); + + // convert joint quaternions to joint matrices + SIMDProcessor->ConvertJointQuatsToJointMats( joints, jointFrame, numJoints ); + + // first joint is always root of entire hierarchy + if ( remove_origin_offset ) { + joints[0].SetTranslation( offset ); + } else { + joints[0].SetTranslation( joints[0].ToVec3() + offset ); + } + + // transform the children + md5joints = model->GetJoints(); + for( i = 1; i < numJoints; i++ ) { + joints[i] *= joints[ md5joints[i].parent - md5joints ]; + } +} + +/* +===================== +idGameEdit::ANIM_CreateMeshForAnim +===================== +*/ +idRenderModel *idGameEdit::ANIM_CreateMeshForAnim( idRenderModel *model, const char *classname, const char *animname, int frame, bool remove_origin_offset ) { + renderEntity_t ent; + const idDict *args; + const char *temp; + idRenderModel *newmodel; + const idMD5Anim *md5anim; + idStr filename; + idStr extension; + const idAnim *anim; + int animNum; + idVec3 offset; + const idDeclModelDef *modelDef; + + if ( !model || model->IsDefaultModel() ) { + return NULL; + } + + args = gameLocal.FindEntityDefDict( classname, false ); + if ( !args ) { + return NULL; + } + + memset( &ent, 0, sizeof( ent ) ); + + ent.bounds.Clear(); + ent.suppressSurfaceInViewID = 0; + + modelDef = ANIM_GetModelDefFromEntityDef( args ); + if ( modelDef ) { + animNum = modelDef->GetAnim( animname ); + if ( !animNum ) { + return NULL; + } + anim = modelDef->GetAnim( animNum ); + if ( !anim ) { + return NULL; + } + md5anim = anim->MD5Anim( 0 ); + ent.customSkin = modelDef->GetDefaultSkin(); + offset = modelDef->GetVisualOffset(); + } else { + filename = animname; + filename.ExtractFileExtension( extension ); + if ( !extension.Length() ) { + animname = args->GetString( va( "anim %s", animname ) ); + } + +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + md5anim = animationLib->GetAnim( animname ); +// RAVEN END + offset.Zero(); + } + + if ( !md5anim ) { + return NULL; + } + + temp = args->GetString( "skin", "" ); + if ( temp[ 0 ] ) { + ent.customSkin = declManager->FindSkin( temp ); + } + + ent.numJoints = model->NumJoints(); +//RAVEN BEGIN +//amccarthy: Added allocation tag + ent.joints = ( idJointMat * )Mem_Alloc16( ent.numJoints * sizeof( *ent.joints ), MA_ANIM ); +//RAVEN END + + ANIM_CreateAnimFrame( model, md5anim, ent.numJoints, ent.joints, FRAME2MS( frame ), offset, remove_origin_offset ); + + newmodel = model->InstantiateDynamicModel( &ent, NULL, NULL, ~SURF_COLLISION ); + + Mem_Free16( ent.joints ); + ent.joints = NULL; + + return newmodel; +} + +// RAVEN BEGIN +// jsinger: allow for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +idAnim::idAnim( idDeclModelDef const *def, SerialInputStream &stream ) +{ + modelDef = def; + numAnims = stream.ReadIntValue(); + for(int i=0; i(); + for(int i=0; iAppend(stream.ReadStringValue()); + } + } + else + { + com.parmList = NULL; + } + } + flags.prevent_idle_override = stream.ReadBoolValue(); + flags.random_cycle_start = stream.ReadBoolValue(); + flags.ai_no_turn = stream.ReadBoolValue(); + flags.anim_turn = stream.ReadBoolValue(); + flags.sync_cycle = stream.ReadBoolValue(); + flags.ai_no_look = stream.ReadBoolValue(); + flags.ai_look_head_only = stream.ReadBoolValue(); + rate = stream.ReadFloatValue(); +} + +void idAnim::Write( SerialOutputStream &stream ) const +{ + stream.Write(numAnims); + for(int i=0; iName()); + } + stream.Write(name); + stream.Write(realname); + stream.Write(frameLookup.Num()); + for(int i=0; iNum()); + for(int j=0; jNum(); j++) + { + stream.Write((*frameCommands[i].parmList)[j]); + } + } + } + stream.Write((bool)flags.prevent_idle_override); + stream.Write((bool)flags.random_cycle_start); + stream.Write((bool)flags.ai_no_turn); + stream.Write((bool)flags.anim_turn); + stream.Write((bool)flags.sync_cycle); + stream.Write((bool)flags.ai_no_look); + stream.Write((bool)flags.ai_look_head_only); + stream.Write(rate); +} +#endif +// RAVEN END diff --git a/src/mpgame/anim/Anim_Import.cpp b/src/mpgame/anim/Anim_Import.cpp new file mode 100644 index 0000000..6f367aa --- /dev/null +++ b/src/mpgame/anim/Anim_Import.cpp @@ -0,0 +1,627 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include "../../MayaImport/maya_main.h" + +/*********************************************************************** + + Maya conversion functions + +***********************************************************************/ + +static idStr Maya_Error; + +static exporterInterface_t Maya_ConvertModel = NULL; +static exporterShutdown_t Maya_Shutdown = NULL; +static int importDLL = 0; + +bool idModelExport::initialized = false; + + +// RAVEN BEGIN +// scork: now needed to cleanup after last Maya model conversion since I cache them for speed during "exportmodels" +bool g_bMayaConversionCleanerRunning = false; // so we can check all calls to Maya_ConvertModel have been updated to include this ;-) +struct MayaConversionCleaner +{ + MayaConversionCleaner() + { + g_bMayaConversionCleanerRunning = true; + } + ~MayaConversionCleaner() + { + if ( Maya_ConvertModel ) + { + Maya_ConvertModel( NULL, NULL, NULL ); + } + g_bMayaConversionCleanerRunning = false; + } +}; +// RAVEN END + + +/* +==================== +idModelExport::idModelExport +==================== +*/ +idModelExport::idModelExport() { + Reset(); +} + +/* +==================== +idModelExport::Shutdown +==================== +*/ +void idModelExport::Shutdown( void ) { + if ( Maya_Shutdown ) { + Maya_Shutdown(); + } + + if ( importDLL ) { + sys->DLL_Unload( importDLL ); + } + + importDLL = 0; + Maya_Shutdown = NULL; + Maya_ConvertModel = NULL; + Maya_Error.Clear(); + initialized = false; +} + +/* +===================== +idModelExport::CheckMayaInstall + +Determines if Maya is installed on the user's machine +===================== +*/ +bool idModelExport::CheckMayaInstall( void ) { +// RAVEN BEGIN +// jscott:revisit - this should go into the exe +//#ifdef _WIN32 +#ifndef _WINDOWS + return false; +#elif 0 + HKEY hKey; + long lres, lType; + + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya\\4.5\\Setup\\InstallPath", &hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + + lres = RegQueryValueEx( hKey, "MAYA_INSTALL_LOCATION", NULL, (unsigned long*)&lType, (unsigned char*)NULL, (unsigned long*)NULL ); + + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#else + HKEY hKey; + long lres; + + // only check the non-version specific key so that we only have to update the maya dll when new versions are released + lres = RegOpenKey( HKEY_LOCAL_MACHINE, "SOFTWARE\\Alias|Wavefront\\Maya", &hKey ); + RegCloseKey( hKey ); + + if ( lres != ERROR_SUCCESS ) { + return false; + } + return true; +#endif +} + +/* +===================== +idModelExport::LoadMayaDll + +Checks to see if we can load the Maya export dll +===================== +*/ +void idModelExport::LoadMayaDll( void ) { + exporterDLLEntry_t dllEntry; + char dllPath[ MAX_OSPATH ]; + + fileSystem->FindDLL( "MayaImport", dllPath, false ); + if ( !dllPath[ 0 ] ) { + return; + } + importDLL = sys->DLL_Load( dllPath ); + if ( !importDLL ) { + return; + } + + // look up the dll interface functions + dllEntry = ( exporterDLLEntry_t )sys->DLL_GetProcAddress( importDLL, "dllEntry" ); + Maya_ConvertModel = ( exporterInterface_t )sys->DLL_GetProcAddress( importDLL, "Maya_ConvertModel" ); + Maya_Shutdown = ( exporterShutdown_t )sys->DLL_GetProcAddress( importDLL, "Maya_Shutdown" ); + if ( !Maya_ConvertModel || !dllEntry || !Maya_Shutdown ) { + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Invalid interface on export DLL." ); + return; + } + + // initialize the DLL +// RAVEN BEGIN +// rhummer: unify allocation strategy to try to fix some of our crashes +#ifdef RV_UNIFIED_ALLOCATOR + if ( !dllEntry( MD5_VERSION, common, sys, Memory::Allocate, Memory::Free, Memory::MSize ) ) { +#else + if ( !dllEntry( MD5_VERSION, common, sys ) ) { +#endif +// RAVEN END + // init failed + Maya_ConvertModel = NULL; + Maya_Shutdown = NULL; + sys->DLL_Unload( importDLL ); + importDLL = 0; + gameLocal.Error( "Export DLL init failed." ); + return; + } +// RAVEN BEGIN +// jscott: maya needs the source control in the tools dll + common->LoadToolsDLL(); +// RAVEN END +} + +/* +===================== +idModelExport::ConvertMayaToMD5 + +Checks if a Maya model should be converted to an MD5, and converts if if the time/date or +version number has changed. +===================== +*/ +bool idModelExport::ConvertMayaToMD5( void ) { + unsigned sourceTime; + unsigned destTime; + int version; + idToken cmdLine; + idStr path; + + // check if our DLL got loaded + if ( initialized && !Maya_ConvertModel ) { + Maya_Error = "MayaImport dll not loaded."; + return false; + } + + // if idAnimManager::forceExport is set then we always reexport Maya models + if ( idAnimManager::forceExport ) { + force = true; + } + +// RAVEN BEGIN +// bdube: fs_import path + + // we need to make sure we have a full path, so convert the filename to an OS path + path.AppendPath( cvarSystem->GetCVarString ("fs_importpath")); + path.AppendPath(src); + + idFile* f = fileSystem->OpenExplicitFileRead ( path ); + if ( !f ) { + Maya_Error = "could not open source file"; + return false; + } + sourceTime = f->Timestamp ( ); + fileSystem->CloseFile ( f ); + +/* + // get the source file's time + if ( fileSystem->ReadFile( src, NULL, &sourceTime ) < 0 ) { + // source file doesn't exist + return true; + } +*/ + +// RAVEN END + + // get the destination file's time + if ( !force && ( fileSystem->ReadFile( dest, NULL, &destTime ) >= 0 ) ) { + idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + + parser.LoadFile( dest ); + + // read the file version + if ( parser.CheckTokenString( MD5_VERSION_STRING ) ) { + version = parser.ParseInt(); + + // check the command line + if ( parser.CheckTokenString( "commandline" ) ) { + parser.ReadToken( &cmdLine ); + + // check the file time, scale, and version + if ( ( destTime >= sourceTime ) && ( version == MD5_VERSION ) && ( cmdLine == commandLine ) ) { + // don't convert it + return true; + } + } + } + } + + // if this is the first time we've been run, check if Maya is installed and load our DLL + if ( !initialized ) { + initialized = true; + + if ( !CheckMayaInstall() ) { + Maya_Error = "Maya not installed in registry."; + return false; + } + + LoadMayaDll(); + + // check if our DLL got loaded + if ( !Maya_ConvertModel ) { + Maya_Error = "Could not load MayaImport dll."; + return false; + } + } + + // we need to make sure we have a full path, so convert the filename to an OS path + src = fileSystem->RelativePathToOSPath( src ); + dest = fileSystem->RelativePathToOSPath( dest ); + + dest.ExtractFilePath( path ); + if ( path.Length() ) { + fileSystem->CreateOSPath( path ); + } + + // get the os path in case it needs to create one + path = fileSystem->RelativePathToOSPath( "" ); + + common->SetRefreshOnPrint( true ); +// RAVEN BEGIN +// scork: if this assert ever triggers it means there's a call to ConvertMayaToMD5() that doesn't have a 'MayaConversionCleaner' object above it. Go and put one in. Now. + assert( g_bMayaConversionCleanerRunning ); +// bdube: support src and dest ospath + Maya_Error = Maya_ConvertModel( cvarSystem->GetCVarString ( "fs_importpath" ), path, commandLine ); +// RAVEN END + common->SetRefreshOnPrint( false ); + if ( Maya_Error != "Ok" ) { + return false; + } + + // conversion succeded + return true; +} + +/* +==================== +idModelExport::Reset +==================== +*/ +void idModelExport::Reset( void ) { + force = false; + commandLine = ""; + src = ""; + dest = ""; +} + +/* +==================== +idModelExport::ExportModel +==================== +*/ +bool idModelExport::ExportModel( const char *model ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + const char *game = cvarSystem->GetCVarString( "fs_game" ); + + if ( strlen(game) == 0 ) { + game = BASE_GAMEDIR; + } + + Reset(); + src = model; + dest = model; + dest.SetFileExtension( MD5_MESH_EXT ); + + sprintf( commandLine, "mesh %s -dest %s -game %s", src.c_str(), dest.c_str(), game ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ExportAnim +==================== +*/ +bool idModelExport::ExportAnim( const char *anim ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + Reset(); + src = anim; + dest = anim; + dest.SetFileExtension( MD5_ANIM_EXT ); + + sprintf( commandLine, "anim %s -dest %s -game %s", src.c_str(), dest.c_str(), CD_BASEDIR ); + if ( !ConvertMayaToMD5() ) { + gameLocal.Printf( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + return false; + } + + return true; +} + +/* +==================== +idModelExport::ParseOptions +==================== +*/ +bool idModelExport::ParseOptions( idLexer &lex ) { + idToken token; + idStr destdir; + idStr sourcedir; + + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expected filename" ); + return false; + } + + src = token; + dest = token; + + while( lex.ReadToken( &token ) ) { + if ( token == "-" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Expecting option" ); + return false; + } + if ( token == "sourcedir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -sourcedir" ); + return false; + } + sourcedir = token; + } else if ( token == "destdir" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing pathname after -destdir" ); + return false; + } + destdir = token; + } else if ( token == "dest" ) { + if ( !lex.ReadToken( &token ) ) { + lex.Error( "Missing filename after -dest" ); + return false; + } + dest = token; + } else { + commandLine += va( " -%s", token.c_str() ); + } + } else { + commandLine += va( " %s", token.c_str() ); + } + } + + if ( sourcedir.Length() ) { + src.StripPath(); + sourcedir.BackSlashesToSlashes(); + sprintf( src, "%s/%s", sourcedir.c_str(), src.c_str() ); + } + + if ( destdir.Length() ) { + dest.StripPath(); + destdir.BackSlashesToSlashes(); + sprintf( dest, "%s/%s", destdir.c_str(), dest.c_str() ); + } + + return true; +} + +/* +==================== +idModelExport::ParseExportSection +==================== +*/ +int idModelExport::ParseExportSection( idParser &parser ) { + +// RAVEN BEGIN +// scork: + MayaConversionCleaner _any_old_name; +// RAVEN END + + idToken command; + idToken token; + idStr defaultCommands; + idLexer lex; + idStr temp; + idStr parms; + int count; + + // only export sections that match our export mask + if ( g_exportMask.GetString()[ 0 ] ) { + if ( parser.CheckTokenString( "{" ) ) { + parser.SkipBracedSection( false ); + return 0; + } + + parser.ReadToken( &token ); + if ( token.Icmp( g_exportMask.GetString() ) ) { + parser.SkipBracedSection(); + return 0; + } + parser.ExpectTokenString( "{" ); + } else if ( !parser.CheckTokenString( "{" ) ) { + // skip the export mask + parser.ReadToken( &token ); + parser.ExpectTokenString( "{" ); + } + + count = 0; + + lex.SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + while( 1 ) { + + if ( !parser.ReadToken( &command ) ) { + parser.Error( "Unexpoected end-of-file" ); + break; + } + + if ( command == "}" ) { + break; + } + + if ( command == "options" ) { + parser.ParseRestOfLine( defaultCommands ); + } else if ( command == "addoptions" ) { + parser.ParseRestOfLine( temp ); + defaultCommands += " "; + defaultCommands += temp; + } else if ( ( command == "mesh" ) || ( command == "anim" ) || ( command == "camera" ) ) { + if ( !parser.ReadToken( &token ) ) { + parser.Error( "Expected filename" ); + } + + temp = token; + parser.ParseRestOfLine( parms ); + + if ( defaultCommands.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), defaultCommands.c_str() ); + } + + if ( parms.Length() ) { + sprintf( temp, "%s %s", temp.c_str(), parms.c_str() ); + } + + lex.LoadMemory( temp, temp.Length(), parser.GetFileName() ); + + Reset(); + if ( ParseOptions( lex ) ) { + if ( command == "mesh" ) { + dest.SetFileExtension( MD5_MESH_EXT ); + } else if ( command == "anim" ) { + dest.SetFileExtension( MD5_ANIM_EXT ); + } else if ( command == "camera" ) { + dest.SetFileExtension( MD5_CAMERA_EXT ); + } else { + dest.SetFileExtension( command ); + } + idStr back = commandLine; + sprintf( commandLine, "%s %s -dest %s -game %s%s", command.c_str(), src.c_str(), dest.c_str(), CD_BASEDIR, commandLine.c_str() ); + if ( ConvertMayaToMD5() ) { + count++; + } else { + parser.Warning( "Failed to export '%s' : %s", src.c_str(), Maya_Error.c_str() ); + } + } + lex.FreeSource(); + } else { + parser.Error( "Unknown token: %s", command.c_str() ); + parser.SkipBracedSection( false ); + break; + } + } + + return count; +} + +/* +================ +idModelExport::ExportDefFile +================ +*/ +int idModelExport::ExportDefFile( const char *filename ) { + idParser parser( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + idToken token; + int count; + + count = 0; + + if ( !parser.LoadFile( filename ) ) { + gameLocal.Printf( "Could not load '%s'\n", filename ); + return 0; + } + + while( parser.ReadToken( &token ) ) { + if ( token == "export" ) { + count += ParseExportSection( parser ); + } else { + parser.ReadToken( &token ); + parser.SkipBracedSection(); + } + } + + return count; +} + +/* +================ +idModelExport::ExportModels +================ +*/ +int idModelExport::ExportModels( const char *pathname, const char *extension ) { + int count; + + count = 0; + + idFileList *files; + int i; + + if ( !CheckMayaInstall() ) { + // if Maya isn't installed, don't bother checking if we have anims to export + return 0; + } + + gameLocal.Printf( "--------- Exporting models --------\n" ); + if ( !g_exportMask.GetString()[ 0 ] ) { + gameLocal.Printf( " Export mask: '%s'\n", g_exportMask.GetString() ); + } + + count = 0; + + files = fileSystem->ListFiles( pathname, extension ); + for( i = 0; i < files->GetNumFiles(); i++ ) { + count += ExportDefFile( va( "%s/%s", pathname, files->GetFile( i ) ) ); + } + fileSystem->FreeFileList( files ); + + gameLocal.Printf( "...%d models exported.\n", count ); + gameLocal.Printf( "-----------------------------------\n" ); + + return count; +} diff --git a/src/mpgame/anim/Anim_Testmodel.cpp b/src/mpgame/anim/Anim_Testmodel.cpp new file mode 100644 index 0000000..e47565f --- /dev/null +++ b/src/mpgame/anim/Anim_Testmodel.cpp @@ -0,0 +1,964 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +============================================================================= + + MODEL TESTING + +Model viewing can begin with either "testmodel " + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Extension will default to ".ase" if not specified. + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + + g_testModelRotate + g_testModelAnimate + g_testModelBlend + +============================================================================= +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idAnimatedEntity, idTestModel ) + EVENT( EV_FootstepLeft, idTestModel::Event_Footstep ) + EVENT( EV_FootstepRight, idTestModel::Event_Footstep ) +END_CLASS + +/* +================ +idTestModel::idTestModel +================ +*/ +idTestModel::idTestModel() { + head = NULL; + headAnimator = NULL; + anim = 0; + headAnim = 0; + starttime = 0; + animtime = 0; + mode = 0; + frame = 0; +} + +/* +================ +idTestModel::Save +================ +*/ +void idTestModel::Save( idSaveGame *savefile ) { +} + +/* +================ +idTestModel::Restore +================ +*/ +void idTestModel::Restore( idRestoreGame *savefile ) { + // FIXME: one day we may actually want to save/restore test models, but for now we'll just delete them + delete this; +} + +/* +================ +idTestModel::Spawn +================ +*/ +void idTestModel::Spawn( void ) { + idVec3 size; + idBounds bounds; + const char *headModel; + jointHandle_t joint; + idStr jointName; + idVec3 origin, modelOffset; + idMat3 axis; + const idKeyValue *kv; + copyJoints_t copyJoint; +// RAVEN BEGIN +// ddynerman: new heads + idAFAttachment *headEnt; +// RAVEN END + + if ( renderEntity.hModel && renderEntity.hModel->IsDefaultModel() && !animator.ModelDef() ) { + gameLocal.Warning( "Unable to create testmodel for '%s' : model defaulted", spawnArgs.GetString( "model" ) ); + PostEventMS( &EV_Remove, 0 ); + return; + } + + mode = g_testModelAnimate.GetInteger(); + animator.RemoveOriginOffset( g_testModelAnimate.GetInteger() == 1 ); + + physicsObj.SetSelf( this ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + physicsObj.SetAxis( GetPhysics()->GetAxis() ); + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) ) { + spawnArgs.GetVector( "maxs", NULL, bounds[1] ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + bounds[ 0 ].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[ 1 ].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + physicsObj.SetClipBox( bounds, 1.0f ); + physicsObj.SetContents( 0 ); + } + + spawnArgs.GetVector( "offsetModel", "0 0 0", modelOffset ); + + // add the head model if it has one + headModel = spawnArgs.GetString( "def_head", "" ); + if ( headModel[ 0 ] ) { +// RAVEN BEGIN +// ddynerman: new heads + jointName = spawnArgs.GetString( "joint_head" ); + joint = animator.GetJointHandle( jointName ); + if ( joint == INVALID_JOINT ) { + gameLocal.Warning( "Joint '%s' not found for 'joint_head'", jointName.c_str() ); + } else { + // copy any sounds in case we have frame commands on the head + idDict args; + const idKeyValue *sndKV = spawnArgs.MatchPrefix( "snd_", NULL ); + while( sndKV ) { + args.Set( sndKV->GetKey(), sndKV->GetValue() ); + sndKV = spawnArgs.MatchPrefix( "snd_", sndKV ); + } + + args.Set( "classname", headModel ); + if( !gameLocal.SpawnEntityDef( args, ( idEntity ** )&headEnt ) ) { + gameLocal.Warning( "idTestModel::Spawn() - Unknown head model '%s'\n", headModel ); + return; + } + headEnt->spawnArgs.Set( "classname", headModel ); + + headEnt->SetName( va( "%s_head", name.c_str() ) ); + headEnt->SetBody ( this, headEnt->spawnArgs.GetString ( "model" ), joint ); + + headEnt->BindToJoint( this, joint, true ); + headEnt->SetOrigin( vec3_origin ); + headEnt->SetAxis( mat3_identity ); + headEnt->InitCopyJoints ( ); + + head = headEnt; +// RAVEN END + + headAnimator = head.GetEntity()->GetAnimator(); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + jointName = kv->GetKey(); + + if ( jointName.StripLeadingOnce( "copy_joint_world " ) ) { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } else { + jointName.StripLeadingOnce( "copy_joint " ); + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + } + + copyJoint.from = animator.GetJointHandle( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s'", jointName.c_str() ); + continue; + } + + copyJoint.to = headAnimator->GetJointHandle( jointName ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head", jointName.c_str() ); + continue; + } + + copyJoints.Append( copyJoint ); + } + } + } + + // start any shader effects based off of the spawn time + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + + SetPhysics( &physicsObj ); + + // always keep updating so we can see the skeleton for the default pose + fl.forcePhysicsUpdate = true; + + gameLocal.Printf( "Added testmodel at origin = '%s', angles = '%s'\n", GetPhysics()->GetOrigin().ToString(), GetPhysics()->GetAxis().ToAngles().ToString() ); + BecomeActive( TH_THINK ); +} + +/* +================ +idTestModel::~idTestModel +================ +*/ +idTestModel::~idTestModel() { + StopSound( SND_CHANNEL_ANY, false ); + if ( renderEntity.hModel ) { + gameLocal.Printf( "Removing testmodel %s\n", renderEntity.hModel->Name() ); + } else { + gameLocal.Printf( "Removing testmodel\n" ); + } + + if ( gameLocal.testmodel == this ) { + gameLocal.testmodel = NULL; + } + if ( head.GetEntity() ) { +// RAVEN BEGIN +// ddynerman: allow instant respawning of head + head.GetEntity()->SetName( va( "%s_oldhead", head.GetEntity()->name.c_str() ) ); +// RAVEN END + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + head.GetEntity()->PostEventMS( &EV_Remove, 0 ); + } + + SetPhysics( NULL ); +} + +/* +=============== +idTestModel::Event_Footstep +=============== +*/ +void idTestModel::Event_Footstep( void ) { + StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL ); +} + +/* +================ +idTestModel::ShouldConstructScriptObjectAtSpawn + +Called during idEntity::Spawn to see if it should construct the script object or not. +Overridden by subclasses that need to spawn the script object themselves. +================ +*/ +bool idTestModel::ShouldConstructScriptObjectAtSpawn( void ) const { + return false; +} + +/* +================ +idTestModel::Think +================ +*/ +void idTestModel::Think( void ) { + idVec3 pos; + idMat3 axis; + idAngles ang; + int i; + frameBlend_t frameBlend = { 0 }; + + if ( thinkFlags & TH_THINK ) { + if ( anim && ( gameLocal.testmodel == this ) && ( mode != g_testModelAnimate.GetInteger() ) ) { + StopSound( SND_CHANNEL_ANY, false ); + if ( head.GetEntity() ) { + head.GetEntity()->StopSound( SND_CHANNEL_ANY, false ); + } + switch( g_testModelAnimate.GetInteger() ) { + default: + case 0: + // cycle anim with origin reset + if ( animator.NumFrames( anim ) <= 1 ) { + // single frame animations end immediately, so just cycle it since it's the same result + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + } else { + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + animator.RemoveOriginOffset( false ); + break; + + case 1: + // cycle anim with fixed origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 2: + // cycle anim with continuous origin + animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->CycleAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 3: +// RAVEN BEGIN + // frame by frame with continuous origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + + case 4: + // play anim once + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + animator.RemoveOriginOffset( false ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + } + break; + + case 5: +// RAVEN BEGIN + // frame by frame with fixed origin + frameBlend.frame1 = frame; + frameBlend.frame2 = frame; + frameBlend.frontlerp = 1.0f; + animator.SetFrame( ANIMCHANNEL_ALL, anim, frameBlend ); + animator.RemoveOriginOffset( true ); + if ( headAnim ) { + headAnimator->SetFrame( ANIMCHANNEL_ALL, headAnim, frameBlend ); + } +// RAVEN END + break; + } + + mode = g_testModelAnimate.GetInteger(); + } + + if ( ( mode == 0 ) && ( gameLocal.time >= starttime + animtime ) ) { + starttime = gameLocal.time; + StopSound( SND_CHANNEL_ANY, false ); + animator.PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnim ) { + headAnimator->PlayAnim( ANIMCHANNEL_ALL, headAnim, gameLocal.time, FRAME2MS( g_testModelBlend.GetInteger() ) ); + if ( headAnimator->AnimLength( headAnim ) > animator.AnimLength( anim ) ) { + // loop the body anim when the head anim is longer + animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( -1 ); + } + } + } + + if ( headAnimator ) { + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + idMat3 mat = head.GetEntity()->GetPhysics()->GetAxis().Transpose(); + GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= head.GetEntity()->GetPhysics()->GetOrigin(); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + animator.GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + headAnimator->SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + headAnimator->SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } + } + + // update rotation + RunPhysics(); + + physicsObj.GetAngles( ang ); + physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, ang, idAngles( 0, g_testModelRotate.GetFloat() * 360.0f / 60.0f, 0 ), ang_zero ); + + idClipModel *clip = physicsObj.GetClipModel(); + if ( clip && animator.ModelDef() ) { + idVec3 neworigin; + idMat3 axis; + jointHandle_t joint; + + joint = animator.GetJointHandle( "origin" ); + animator.GetJointTransform( joint, gameLocal.time, neworigin, axis ); + neworigin = ( ( neworigin - animator.ModelDef()->GetVisualOffset() ) * physicsObj.GetAxis() ) + GetPhysics()->GetOrigin(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clip->Link( this, 0, neworigin, clip->GetAxis() ); +// RAVEN END + } + } + + UpdateAnimation(); + Present(); + + if ( ( gameLocal.testmodel == this ) && g_showTestModelFrame.GetInteger() && anim ) { + gameLocal.Printf( "^5 Anim: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n", animator.AnimFullName( anim ), animator.CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + animator.CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - animator.CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + if ( headAnim ) { + gameLocal.Printf( "^5 Head: ^7%s ^5Frame: ^7%d/%d Time: %.3f\n\n", headAnimator->AnimFullName( headAnim ), headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetFrameNumber( gameLocal.time ), + headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->NumFrames(), MS2SEC( gameLocal.time - headAnimator->CurrentAnim( ANIMCHANNEL_ALL )->GetStartTime() ) ); + } else { + gameLocal.Printf( "\n\n" ); + } + } +} + +/* +================ +idTestModel::NextAnim +================ +*/ +void idTestModel::NextAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + anim++; + if ( anim >= animator.NumAnims() ) { + // anim 0 is no anim + anim = 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::PrevAnim +================ +*/ +void idTestModel::PrevAnim( const idCmdArgs &args ) { + if ( !animator.NumAnims() ) { + return; + } + + headAnim = 0; + anim--; + if ( anim < 0 ) { + anim = animator.NumAnims() - 1; + } + + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + animname = animator.AnimFullName( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + if ( headAnim ) { + gameLocal.Printf( "head '%s', %d.%03d seconds, %d frames\n", headAnimator->AnimFullName( headAnim ), headAnimator->AnimLength( headAnim ) / 1000, headAnimator->AnimLength( headAnim ) % 1000, headAnimator->NumFrames( headAnim ) ); + } + + // reset the anim + mode = -1; + frame = 1; +} + +/* +================ +idTestModel::NextFrame +================ +*/ +void idTestModel::NextFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame++; + if ( frame > animator.NumFrames( anim ) ) { + frame = 1; + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::PrevFrame +================ +*/ +void idTestModel::PrevFrame( const idCmdArgs &args ) { + if ( !anim || ( ( g_testModelAnimate.GetInteger() != 3 ) && ( g_testModelAnimate.GetInteger() != 5 ) ) ) { + return; + } + + frame--; + if ( frame < 1 ) { + frame = animator.NumFrames( anim ); + } + + gameLocal.Printf( "^5 Anim: ^7%s\n^5Frame: ^7%d/%d\n\n", animator.AnimFullName( anim ), frame, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +================ +idTestModel::TestAnim +================ +*/ +void idTestModel::TestAnim( const idCmdArgs &args ) { + idStr name; + int animNum; + const idAnim *newanim; + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: testanim \n" ); + return; + } + + newanim = NULL; + + name = args.Argv( 1 ); +#if 0 + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + const idMD5Anim *md5anims[ ANIM_MaxSyncedAnims ]; + idModelExport exporter; + exporter.ExportAnim( name ); + name.SetFileExtension( MD5_ANIM_EXT ); + md5anims[ 0 ] = animationLib.GetAnim( name ); + if ( md5anims[ 0 ] ) { + customAnim.SetAnim( animator.ModelDef(), name, name, 1, md5anims ); + newanim = &customAnim; + } + } else { + animNum = animator.GetAnim( name ); + } +#else + animNum = animator.GetAnim( name ); +#endif + + if ( !animNum ) { + gameLocal.Printf( "Animation '%s' not found.\n", name.c_str() ); + return; + } + + anim = animNum; + starttime = gameLocal.time; + animtime = animator.AnimLength( anim ); + headAnim = 0; + if ( headAnimator ) { + headAnimator->ClearAllAnims( gameLocal.time, 0 ); + headAnim = headAnimator->GetAnim( animname ); + if ( !headAnim ) { + headAnim = headAnimator->GetAnim( "idle" ); + if ( !headAnim ) { + gameLocal.Printf( "Missing 'idle' anim for head.\n" ); + } + } + + if ( headAnim && ( headAnimator->AnimLength( headAnim ) > animtime ) ) { + animtime = headAnimator->AnimLength( headAnim ); + } + } + + animname = name; + gameLocal.Printf( "anim '%s', %d.%03d seconds, %d frames\n", animname.c_str(), animator.AnimLength( anim ) / 1000, animator.AnimLength( anim ) % 1000, animator.NumFrames( anim ) ); + + // reset the anim + mode = -1; +} + +/* +===================== +idTestModel::BlendAnim +===================== +*/ +void idTestModel::BlendAnim( const idCmdArgs &args ) { + int anim1; + int anim2; + + if ( args.Argc() < 4 ) { + gameLocal.Printf( "usage: testblend \n" ); + return; + } + + anim1 = gameLocal.testmodel->animator.GetAnim( args.Argv( 1 ) ); + if ( !anim1 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 1 ) ); + return; + } + + anim2 = gameLocal.testmodel->animator.GetAnim( args.Argv( 2 ) ); + if ( !anim2 ) { + gameLocal.Printf( "Animation '%s' not found.\n", args.Argv( 2 ) ); + return; + } + + animname = args.Argv( 2 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim1, gameLocal.time, 0 ); + animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, FRAME2MS( atoi( args.Argv( 3 ) ) ) ); + + anim = anim2; + headAnim = 0; +} + +/*********************************************************************** + + Testmodel console commands + +***********************************************************************/ + +/* +================= +idTestModel::KeepTestModel_f + +Makes the current test model permanent, allowing you to place +multiple test models +================= +*/ +void idTestModel::KeepTestModel_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel.\n" ); + return; + } + + gameLocal.Printf( "modelDef %i kept\n", gameLocal.testmodel->renderEntity.hModel ); + + gameLocal.testmodel = NULL; +} + +/* +================= +idTestModel::TestSkin_f + +Sets a skin on an existing testModel +================= +*/ +void idTestModel::TestSkin_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "removing testSkin.\n" ); + gameLocal.testmodel->SetSkin( NULL ); + return; + } + + name = args.Argv( 1 ); + gameLocal.testmodel->SetSkin( declManager->FindSkin( name ) ); +} + +/* +================= +idTestModel::TestShaderParm_f + +Sets a shaderParm on an existing testModel +================= +*/ +void idTestModel::TestShaderParm_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No active testModel\n" ); + return; + } + + if ( args.Argc() != 3 ) { + gameLocal.Printf( "USAGE: testShaderParm \n" ); + return; + } + + int parm = atoi( args.Argv( 1 ) ); + if ( parm < 0 || parm >= MAX_ENTITY_SHADER_PARMS ) { + gameLocal.Printf( "parmNum %i out of range\n", parm ); + return; + } + + float value; + if ( !stricmp( args.Argv( 2 ), "time" ) ) { + value = gameLocal.time * -0.001; + } else { + value = atof( args.Argv( 2 ) ); + } + + gameLocal.testmodel->SetShaderParm( parm, value ); +} + +/* +================= +idTestModel::TestModel_f + +Creates a static modelDef in front of the current position, which +can then be moved around +================= +*/ +void idTestModel::TestModel_f( const idCmdArgs &args ) { + idVec3 offset; + idStr name; + idPlayer * player; + const idDict * entityDef; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testmodel ) { + delete gameLocal.testmodel; + gameLocal.testmodel = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + entityDef = gameLocal.FindEntityDefDict( name, false ); + if ( entityDef ) { + dict = *entityDef; + } else { + if ( declManager->FindType( DECL_MODELDEF, name, false ) ) { + dict.Set( "model", name ); + } else { + // allow map models with underscore prefixes to be tested during development + // without appending an ase + if ( name[ 0 ] != '_' ) { + name.DefaultFileExtension( ".ase" ); + } + + if ( strstr( name, ".ma" ) || strstr( name, ".mb" ) ) { + idModelExport exporter; + exporter.ExportModel( name ); + name.SetFileExtension( MD5_MESH_EXT ); + } + + if ( !renderModelManager->CheckModel( name ) ) { + gameLocal.Printf( "Can't register model\n" ); + return; + } + dict.Set( "model", name ); + } + } + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "angle", va( "%f", player->viewAngles.yaw + 180.0f ) ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + gameLocal.testmodel = ( idTestModel * )gameLocal.SpawnEntityType( idTestModel::GetClassType(), &dict ); +// RAVEN END + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); +} + +/* +===================== +idTestModel::ArgCompletion_TestModel +===================== +*/ +void idTestModel::ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ) { + int i, num; + + num = declManager->GetNumDecls( DECL_ENTITYDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_ENTITYDEF, i , false )->GetName() ); + } + num = declManager->GetNumDecls( DECL_MODELDEF ); + for ( i = 0; i < num; i++ ) { + callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( DECL_MODELDEF, i , false )->GetName() ); + } + cmdSystem->ArgCompletion_FolderExtension( args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", ".mb", NULL ); +} + +/* +===================== +idTestModel::TestParticleStopTime_f +===================== +*/ +void idTestModel::TestParticleStopTime_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time ); + gameLocal.testmodel->UpdateVisuals(); +} + +/* +===================== +idTestModel::TestAnim_f +===================== +*/ +void idTestModel::TestAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->TestAnim( args ); +} + + +/* +===================== +idTestModel::ArgCompletion_TestAnim +===================== +*/ +void idTestModel::ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ) { + if ( gameLocal.testmodel ) { + idAnimator *animator = gameLocal.testmodel->GetAnimator(); + for( int i = 0; i < animator->NumAnims(); i++ ) { + callback( va( "%s %s", args.Argv( 0 ), animator->AnimFullName( i ) ) ); + } + } +} + +/* +===================== +idTestModel::TestBlend_f +===================== +*/ +void idTestModel::TestBlend_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->BlendAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextAnim_f +===================== +*/ +void idTestModel::TestModelNextAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextAnim( args ); +} + +/* +===================== +idTestModel::TestModelPrevAnim_f +===================== +*/ +void idTestModel::TestModelPrevAnim_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevAnim( args ); +} + +/* +===================== +idTestModel::TestModelNextFrame_f +===================== +*/ +void idTestModel::TestModelNextFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->NextFrame( args ); +} + +/* +===================== +idTestModel::TestModelPrevFrame_f +===================== +*/ +void idTestModel::TestModelPrevFrame_f( const idCmdArgs &args ) { + if ( !gameLocal.testmodel ) { + gameLocal.Printf( "No testModel active.\n" ); + return; + } + + gameLocal.testmodel->PrevFrame( args ); +} diff --git a/src/mpgame/anim/Anim_Testmodel.h b/src/mpgame/anim/Anim_Testmodel.h new file mode 100644 index 0000000..408765e --- /dev/null +++ b/src/mpgame/anim/Anim_Testmodel.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __ANIM_TESTMODEL_H__ +#define __ANIM_TESTMODEL_H__ + +/* +============================================================================================== + + idTestModel + +============================================================================================== +*/ + +class idTestModel : public idAnimatedEntity { +public: + CLASS_PROTOTYPE( idTestModel ); + + idTestModel(); + ~idTestModel(); + + void Save( idSaveGame *savefile ); + void Restore( idRestoreGame *savefile ); + + void Spawn( void ); + + virtual bool ShouldConstructScriptObjectAtSpawn( void ) const; + + void NextAnim( const idCmdArgs &args ); + void PrevAnim( const idCmdArgs &args ); + void NextFrame( const idCmdArgs &args ); + void PrevFrame( const idCmdArgs &args ); + void TestAnim( const idCmdArgs &args ); + void BlendAnim( const idCmdArgs &args ); + + static void KeepTestModel_f( const idCmdArgs &args ); + static void TestModel_f( const idCmdArgs &args ); + static void ArgCompletion_TestModel( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestSkin_f( const idCmdArgs &args ); + static void TestShaderParm_f( const idCmdArgs &args ); + static void TestParticleStopTime_f( const idCmdArgs &args ); + static void TestAnim_f( const idCmdArgs &args ); + static void ArgCompletion_TestAnim( const idCmdArgs &args, void(*callback)( const char *s ) ); + static void TestBlend_f( const idCmdArgs &args ); + static void TestModelNextAnim_f( const idCmdArgs &args ); + static void TestModelPrevAnim_f( const idCmdArgs &args ); + static void TestModelNextFrame_f( const idCmdArgs &args ); + static void TestModelPrevFrame_f( const idCmdArgs &args ); + +private: +// RAVEN BEGIN +// ddynerman: new head system + idEntityPtr head; +// RAVEN END + idAnimator *headAnimator; + idAnim customAnim; + idPhysics_Parametric physicsObj; + idStr animname; + int anim; + int headAnim; + int mode; + int frame; + int starttime; + int animtime; + + idList copyJoints; + + virtual void Think( void ); + + void Event_Footstep( void ); +}; + +#endif /* !__ANIM_TESTMODEL_H__*/ diff --git a/src/mpgame/client/ClientAFEntity.cpp b/src/mpgame/client/ClientAFEntity.cpp new file mode 100644 index 0000000..23a3ab7 --- /dev/null +++ b/src/mpgame/client/ClientAFEntity.cpp @@ -0,0 +1,720 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientAFEntity.cpp +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +/* +=============================================================================== + +rvClientAFEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvAnimatedClientEntity, rvClientAFEntity ) +END_CLASS + +static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; + +/* +================ +rvClientAFEntity::rvClientAFEntity +================ +*/ +rvClientAFEntity::rvClientAFEntity( void ) { + combatModel = NULL; + combatModelContents = 0; + nextSoundTime = 0; + spawnOrigin.Zero(); + spawnAxis.Identity(); +} + + +/* +================ +idAFEntity_Base::~idAFEntity_Base +================ +*/ +rvClientAFEntity::~rvClientAFEntity( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +================ +rvClientAFEntity::Spawn +================ +*/ +void rvClientAFEntity::Spawn( void ) { + spawnOrigin = worldOrigin; + spawnAxis = worldAxis; + nextSoundTime = 0; + + //if ( !LoadAF() ) { + // gameLocal.Error( "Couldn't load af file on entity %d", entityNumber ); + //} + + SetCombatModel(); + + //af.GetPhysics()->PutToRest(); + //if ( !spawnArgs.GetBool( "nodrop", "0" ) ) { + // af.GetPhysics()->Activate(); + //} +} + +/* +================ +rvClientAFEntity::LoadAF +================ +*/ +bool rvClientAFEntity::LoadAF( const char* keyname ) { + idStr fileName; + + if ( !keyname || !*keyname ) { + keyname = "articulatedFigure"; + } + + if ( !spawnArgs.GetString( keyname, "*unknown*", fileName ) ) { + return false; + } + + af.SetAnimator( GetAnimator() ); + + idDict args = gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs; + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = spawnArgs; + + if ( !af.Load( gameLocal.entities[ ENTITYNUM_CLIENT ], fileName ) ) { + gameLocal.Error( "rvClientAFEntity::LoadAF: Couldn't load af file '%s' on client entity %d", fileName.c_str(), entityNumber ); + } + gameLocal.entities[ ENTITYNUM_CLIENT ]->spawnArgs = args; + + af.Start(); + + af.GetPhysics()->Rotate( spawnAxis.ToRotation() ); + af.GetPhysics()->Translate( spawnOrigin ); + + LoadState( spawnArgs ); + + af.UpdateAnimation(); + animator.CreateFrame( gameLocal.time, true ); + UpdateVisuals(); + + return true; +} + +/* +================ +rvClientAFEntity::Think +================ +*/ +void rvClientAFEntity::Think( void ) { + RunPhysics(); + UpdateAnimation(); + + Present(); + LinkCombat(); +} + +/* +================ +rvClientAFEntity::BodyForClipModelId +================ +*/ +int rvClientAFEntity::BodyForClipModelId( int id ) const { + return af.BodyForClipModelId( id ); +} + +/* +================ +rvClientAFEntity::SaveState +================ +*/ +void rvClientAFEntity::SaveState( idDict &args ) const { + const idKeyValue *kv; + + // save the ragdoll pose + af.SaveState( args ); + + // save all the bind constraints + kv = spawnArgs.MatchPrefix( "bindConstraint ", NULL ); + while ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "bindConstraint ", kv ); + } + + // save the bind if it exists + kv = spawnArgs.FindKey( "bind" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToJoint" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } + kv = spawnArgs.FindKey( "bindToBody" ); + if ( kv ) { + args.Set( kv->GetKey(), kv->GetValue() ); + } +} + +/* +================ +rvClientAFEntity::LoadState +================ +*/ +void rvClientAFEntity::LoadState( const idDict &args ) { + af.LoadState( args ); +} + +/* +================ +rvClientAFEntity::AddBindConstraints +================ +*/ +void rvClientAFEntity::AddBindConstraints( void ) { + af.AddBindConstraints(); +} + +/* +================ +rvClientAFEntity::RemoveBindConstraints +================ +*/ +void rvClientAFEntity::RemoveBindConstraints( void ) { + af.RemoveBindConstraints(); +} + +/* +================ +rvClientAFEntity::GetImpactInfo +================ +*/ +void rvClientAFEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( af.IsActive() ) { + af.GetImpactInfo( ent, id, point, info ); + } else { + rvClientEntity::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFEntity::ApplyImpulse +================ +*/ +void rvClientAFEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( af.IsLoaded() ) { + af.ApplyImpulse( ent, id, point, impulse ); + } + if ( !af.IsActive() ) { + rvClientEntity::ApplyImpulse( ent, id, point, impulse, splash ); + } +} + +/* +================ +rvClientAFEntity::AddForce +================ +*/ +void rvClientAFEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( af.IsLoaded() ) { + af.AddForce( ent, id, point, force ); + } + if ( !af.IsActive() ) { + rvClientEntity::AddForce( ent, id, point, force ); + } +} + +bool rvClientAFEntity::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + // stubbed out + return true; +} + +/* +================ +rvClientAFEntity::Collide +================ +*/ +bool rvClientAFEntity::Collide( const trace_t &collision, const idVec3 &velocity ) { + float v, f; + + if ( af.IsActive() ) { + v = -( velocity * collision.c.normal ); + if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { + // RAVEN BEGIN + // jscott: fixed negative sqrt call + if( v > BOUNCE_SOUND_MAX_VELOCITY ) { + f = 1.0f; + } else if( v <= BOUNCE_SOUND_MIN_VELOCITY ) { + f = 0.0f; + } else { + f = ( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / ( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); + } + // RAVEN END + if ( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) ) { + // don't set the volume unless there is a bounce sound as it overrides the entire channel + // which causes footsteps on ai's to not honor their shader parms + SetSoundVolume( f ); + } + nextSoundTime = gameLocal.time + 500; + } + } + + return false; +} + +/* +================ +rvClientAFEntity::GetPhysicsToVisualTransform +================ +*/ +bool rvClientAFEntity::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + if ( af.IsActive() ) { + af.GetPhysicsToVisualTransform( origin, axis ); + return true; + } + return rvClientModel::GetPhysicsToVisualTransform( origin, axis ); +} + +/* +================ +rvClientAFEntity::UpdateAnimationControllers +================ +*/ +bool rvClientAFEntity::UpdateAnimationControllers( void ) { + if ( af.IsActive() ) { + if ( af.UpdateAnimation() ) { + return true; + } + } + return false; +} + +/* +================ +rvClientAFEntity::SetCombatModel +================ +*/ +void rvClientAFEntity::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } +} + +/* +================ +rvClientAFEntity::GetCombatModel +================ +*/ +idClipModel *rvClientAFEntity::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFEntity::SetCombatContents +================ +*/ +void rvClientAFEntity::SetCombatContents( bool enable ) { + assert( combatModel ); + if ( enable && combatModelContents ) { + assert( !combatModel->GetContents() ); + combatModel->SetContents( combatModelContents ); + combatModelContents = 0; + } else if ( !enable && combatModel->GetContents() ) { + assert( !combatModelContents ); + combatModelContents = combatModel->GetContents(); + combatModel->SetContents( 0 ); + } +} + +/* +================ +rvClientAFEntity::LinkCombat +================ +*/ +void rvClientAFEntity::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFEntity::UnlinkCombat +================ +*/ +void rvClientAFEntity::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} + +/* +================ +rvClientAFEntity::FreeEntityDef +================ +*/ +void rvClientAFEntity::FreeEntityDef( void ) { + UnlinkCombat(); + rvClientEntity::FreeEntityDef(); +} + +/* +================ +rvClientAFEntity::GetNoPlayerImpactFX +================ +*/ +bool rvClientAFEntity::GetNoPlayerImpactFX( void ) { + // stubbed out + return false; +} + + +/* +=============================================================================== + + idAFAttachment + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientAFEntity, rvClientAFAttachment ) +END_CLASS + +/* +===================== +rvClientAFAttachment::rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::rvClientAFAttachment( void ) { + body = NULL; + combatModel = NULL; + idleAnim = 0; + damageJoint = INVALID_JOINT; +} + +/* +===================== +rvClientAFAttachment::~rvClientAFAttachment +===================== +*/ +rvClientAFAttachment::~rvClientAFAttachment( void ) { + delete combatModel; + combatModel = NULL; +} + +/* +===================== +rvClientAFAttachment::Spawn +===================== +*/ +void rvClientAFAttachment::Spawn( void ) { + idleAnim = animator.GetAnim( "idle" ); +} + +/* +===================== +rvClientAFAttachment::InitCopyJoints +===================== +*/ +void rvClientAFAttachment::InitCopyJoints ( void ) { + copyJoints_t copyJoint; + const idKeyValue* kv; + const char* jointName; + idAnimator* bodyAnimator; + + if ( !body ) { + return; + } + + bodyAnimator = body->GetAnimator ( ); + + // set up the list of joints to copy to the head + for( kv = spawnArgs.MatchPrefix( "copy_joint", NULL ); kv != NULL; kv = spawnArgs.MatchPrefix( "copy_joint", kv ) ) { + if ( kv->GetValue() == "" ) { + // probably clearing out inherited key, so skip it + continue; + } + + if ( !body->spawnArgs.GetString ( va("copy_joint_world %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ) ) { + copyJoint.mod = JOINTMOD_LOCAL_OVERRIDE; + body->spawnArgs.GetString ( va("copy_joint %s", kv->GetValue().c_str() ), kv->GetValue().c_str(), &jointName ); + } else { + copyJoint.mod = JOINTMOD_WORLD_OVERRIDE; + } + + copyJoint.from = bodyAnimator->GetJointHandle ( jointName ); + if ( copyJoint.from == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on client entity %d", jointName, entityNumber ); + continue; + } + + copyJoint.to = animator.GetJointHandle( kv->GetValue() ); + if ( copyJoint.to == INVALID_JOINT ) { + gameLocal.Warning( "Unknown copy_joint '%s' on head of entity %d", kv->GetValue().c_str(), entityNumber ); + continue; + } + + copyJoints.Append( copyJoint ); + } +} + +/* +===================== +rvClientAFAttachment::CopyJointsFromBody +===================== +*/ +void rvClientAFAttachment::CopyJointsFromBody ( void ) { + MEM_SCOPED_TAG(tag,MA_ANIM); + + idAnimator* bodyAnimator; + int i; + idMat3 mat; + idMat3 axis; + idVec3 pos; + + if ( !body ) { + return; + } + bodyAnimator = body->GetAnimator(); + + // copy the animation from the body to the head + for( i = 0; i < copyJoints.Num(); i++ ) { + if ( copyJoints[ i ].mod == JOINTMOD_WORLD_OVERRIDE ) { + mat = GetPhysics()->GetAxis().Transpose(); + body->GetJointWorldTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + pos -= GetPhysics()->GetOrigin(); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos * mat ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis * mat ); + } else { + bodyAnimator->GetJointLocalTransform( copyJoints[ i ].from, gameLocal.time, pos, axis ); + animator.SetJointPos( copyJoints[ i ].to, copyJoints[ i ].mod, pos ); + animator.SetJointAxis( copyJoints[ i ].to, copyJoints[ i ].mod, axis ); + } + } +} + +/* +===================== +rvClientAFAttachment::SetBody +===================== +*/ +void rvClientAFAttachment::SetBody( idAnimatedEntity *bodyEnt, const char *model, jointHandle_t _damageJoint ) { + body = bodyEnt; + damageJoint = _damageJoint; + SetModel( model ); + + spawnArgs.SetBool( "bleed", body->spawnArgs.GetBool( "bleed" ) ); +} + +/* +===================== +rvClientAFAttachment::ClearBody +===================== +*/ +void rvClientAFAttachment::ClearBody( void ) { + body = NULL; + damageJoint = INVALID_JOINT; + Hide(); +} + +/* +===================== +rvClientAFAttachment::GetBody +===================== +*/ +idEntity *rvClientAFAttachment::GetBody( void ) const { + return body; +} + +/* +================ +idAFAttachment::Hide +================ +*/ +void rvClientAFAttachment::Hide( void ) { + UnlinkCombat(); +} + +/* +================ +idAFAttachment::Show +================ +*/ +void rvClientAFAttachment::Show( void ) { + LinkCombat(); +} + +/* +================ +idAFAttachment::AddDamageEffect +================ +*/ +void rvClientAFAttachment::AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + if ( body ) { + trace_t c = collision; + c.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ); + body->AddDamageEffect( c, velocity, damageDefName, inflictor ); + } +} + +/* +================ +rvClientAFAttachment::GetImpactInfo +================ +*/ +void rvClientAFAttachment::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + if ( body ) { + body->GetImpactInfo( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, info ); + } else { + rvClientAFAttachment::GetImpactInfo( ent, id, point, info ); + } +} + +/* +================ +rvClientAFAttachment::CanPlayImpactEffect +================ +*/ +bool rvClientAFAttachment::CanPlayImpactEffect ( idEntity* attacker, idEntity* target ) { + return rvClientAFEntity::CanPlayImpactEffect( attacker, target ); +} + +/* +================ +rvClientAFAttachment::ApplyImpulse +================ +*/ +void rvClientAFAttachment::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + if ( body ) { + body->ApplyImpulse( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, impulse ); + } else { + rvClientAFEntity::ApplyImpulse( ent, id, point, impulse ); + } +} + +/* +================ +rvClientAFAttachment::AddForce +================ +*/ +void rvClientAFAttachment::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + if ( body ) { + body->AddForce( ent, JOINT_HANDLE_TO_CLIPMODEL_ID( damageJoint ), point, force ); + } else { + rvClientAFEntity::AddForce( ent, id, point, force ); + } +} + +/* +================ +rvClientAFAttachment::PlayIdleAnim +================ +*/ +void rvClientAFAttachment::PlayIdleAnim( int channel, int blendTime ) { + if ( idleAnim && ( idleAnim != animator.CurrentAnim( channel )->AnimNum() ) ) { + animator.CycleAnim( channel, idleAnim, gameLocal.time, blendTime ); + } +} + +/* +================ +rvClientAFAttachment::Think +================ +*/ +void rvClientAFAttachment::Think( void ) { + rvClientAFEntity::Think(); +} + +/* +================ +rvClientAFAttachment::UpdateAnimationControllers +================ +*/ +bool rvClientAFAttachment::UpdateAnimationControllers( void ) { + CopyJointsFromBody( ); + return rvClientAFEntity::UpdateAnimationControllers( ); +} + +/* +================ +rvClientAFAttachment::SetCombatModel +================ +*/ +void rvClientAFAttachment::SetCombatModel( void ) { + if ( combatModel ) { + combatModel->Unlink(); + combatModel->LoadModel( entityDefHandle ); + } else { + RV_PUSH_HEAP_MEM(this); + combatModel = new idClipModel( entityDefHandle ); + RV_POP_HEAP(); + } + combatModel->SetOwner( body ); +} + +/* +================ +rvClientAFAttachment::GetCombatModel +================ +*/ +idClipModel *rvClientAFAttachment::GetCombatModel( void ) const { + return combatModel; +} + +/* +================ +rvClientAFAttachment::LinkCombat +================ +*/ +void rvClientAFAttachment::LinkCombat( void ) { + if ( combatModel ) { + combatModel->Link( gameLocal.entities[ ENTITYNUM_CLIENT ], 0, renderEntity.origin, renderEntity.axis, entityDefHandle ); + } +} + +/* +================ +rvClientAFAttachment::UnlinkCombat +================ +*/ +void rvClientAFAttachment::UnlinkCombat( void ) { + if ( combatModel ) { + combatModel->Unlink(); + } +} diff --git a/src/mpgame/client/ClientAFEntity.h b/src/mpgame/client/ClientAFEntity.h new file mode 100644 index 0000000..4c1fcc5 --- /dev/null +++ b/src/mpgame/client/ClientAFEntity.h @@ -0,0 +1,150 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientAFEntity.h +// +// Copyright 2002-2006 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_AFENTITY_H__ +#define __GAME_CLIENT_AFENTITY_H__ + +/* +=============================================================================== + +rvClientAFEntity - a regular idAFEntity_Base spawned client-side + +=============================================================================== +*/ + +class rvClientAFEntity : public rvAnimatedClientEntity { +public: + CLASS_PROTOTYPE( rvClientAFEntity ); + + rvClientAFEntity( void ); + virtual ~rvClientAFEntity( void ); + + void Spawn( void ); + + virtual void Think( void ); + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + virtual bool UpdateAnimationControllers( void ); + virtual void FreeEntityDef( void ); + + virtual bool LoadAF( const char* keyname = NULL ); + bool IsActiveAF( void ) const { return af.IsActive(); } + const char * GetAFName( void ) const { return af.GetName(); } + idPhysics_AF * GetAFPhysics( void ) { return af.GetPhysics(); } + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + // contents of combatModel can be set to 0 or re-enabled (mp) + void SetCombatContents( bool enable ); + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + int BodyForClipModelId( int id ) const; + + void SaveState( idDict &args ) const; + void LoadState( const idDict &args ); + + void AddBindConstraints( void ); + void RemoveBindConstraints( void ); + + bool GetNoPlayerImpactFX( void ); + +protected: + idAF af; // articulated figure + idClipModel * combatModel; // render model for hit detection + int combatModelContents; + idVec3 spawnOrigin; // spawn origin + idMat3 spawnAxis; // rotation axis used when spawned + int nextSoundTime; // next time this can make a sound +}; + + +/* +=============================================================================== + +rvClientAFAttachment - a regular idAFAttachment spawned client-side - links to an + idAnimatedEntity rather than rvClientAnimatedEntity + +=============================================================================== +*/ +class rvClientAFAttachment : public rvClientAFEntity { +public: + CLASS_PROTOTYPE( rvClientAFAttachment ); + + rvClientAFAttachment( void ); + virtual ~rvClientAFAttachment( void ); + + void Spawn( void ); + + void SetBody ( idAnimatedEntity* body, const char *headModel, jointHandle_t damageJoint ); + void SetDamageJoint ( jointHandle_t damageJoint ); + void ClearBody ( void ); + idEntity * GetBody ( void ) const; + + virtual void Think ( void ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + virtual bool UpdateAnimationControllers ( void ); + + void PlayIdleAnim( int channel, int blendTime ); + + virtual void GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash = false ); + virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool CanPlayImpactEffect ( idEntity* attacker, idEntity* target ); + virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + void SetCombatModel( void ); + idClipModel * GetCombatModel( void ) const; + virtual void LinkCombat( void ); + virtual void UnlinkCombat( void ); + + // Lipsync + void InitCopyJoints ( void ); + + void CopyJointsFromBody ( void ); + +protected: + idEntityPtr body; + idClipModel * combatModel; // render model for hit detection of head + int idleAnim; + jointHandle_t damageJoint; + + idList copyJoints; // copied from the body animation to the head model +}; + +#endif diff --git a/src/mpgame/client/ClientEffect.cpp b/src/mpgame/client/ClientEffect.cpp new file mode 100644 index 0000000..8055d9e --- /dev/null +++ b/src/mpgame/client/ClientEffect.cpp @@ -0,0 +1,521 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEffect.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientEffect.h" + +/* +=============================================================================== + +rvClientEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEntity, rvClientEffect ) +END_CLASS + +/* +================ +rvClientEffect::rvClientEffect +================ +*/ +rvClientEffect::rvClientEffect ( void ) { + Init( NULL ); + Spawn(); +} + +rvClientEffect::rvClientEffect ( const idDecl *effect ) { + Init( effect ); + Spawn(); +} + +void rvClientEffect::Init ( const idDecl *effect ) { + memset( &renderEffect, 0, sizeof( renderEffect ) ); + + renderEffect.declEffect = effect; + renderEffect.startTime = -1.0f; + renderEffect.referenceSoundHandle = -1; + effectDefHandle = -1; + endOriginJoint = INVALID_JOINT; +} + +/* +================ +rvClientEffect::~rvClientEffect +================ +*/ +rvClientEffect::~rvClientEffect( void ) { + FreeEffectDef( ); + // Prevent a double free of a SoundEmitter resulting in broken in-game sounds, when + // the second free releases a emitter that was reallocated to another sound. rvBSE caches + // this referenceSoundHandle and rvBSE::Destroy also frees the sound. rvBSE::Destroy + // is triggered by FreeEffectDef. Disable this free and let rvBSE do the releasing + + // Actually, the freeing should be done here and not in BSE. The client effect allocates and + // maintains the handle. Handling this here also allows emitters to be recycled for sparse + // looping effects. + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, renderEffect.referenceSoundHandle, true ); + renderEffect.referenceSoundHandle = -1; +} + +/* +================ +rvClientEffect::GetEffectIndex +================ +*/ +int rvClientEffect::GetEffectIndex( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->Index() ); + } + return( -1 ); +} + +/* +================ +rvClientEffect::GetEffectName +================ +*/ +const char *rvClientEffect::GetEffectName( void ) +{ + if( renderEffect.declEffect ) { + return( renderEffect.declEffect->GetName() ); + } + return( "unknown" ); +} + +/* +================ +rvClientEffect::FreeEffectDef +================ +*/ +void rvClientEffect::FreeEffectDef ( void ) { + if ( effectDefHandle != -1 && gameRenderWorld ) { + gameRenderWorld->FreeEffectDef( effectDefHandle ); + } + effectDefHandle = -1; +} + +/* +================ +rvClientEffect::UpdateBind +================ +*/ +void rvClientEffect::UpdateBind ( void ) { + rvClientEntity::UpdateBind ( ); + + renderEffect.origin = worldOrigin; + + if ( endOriginJoint != INVALID_JOINT && bindMaster ) { + idMat3 axis; + idVec3 endOrigin; + idVec3 dir; + + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( endOriginJoint, gameLocal.time, endOrigin, axis ); + SetEndOrigin ( endOrigin ); + + dir = (endOrigin - worldOrigin); + dir.Normalize (); + renderEffect.axis = dir.ToMat3 ( ); + } else { + renderEffect.axis = worldAxis; + } + + if ( bindMaster ) { + renderEffect.groupID = bindMaster->entityNumber + 1; + } else { + renderEffect.groupID = 0; + } +} + +/* +================ +rvClientEffect::Think +================ +*/ +void rvClientEffect::Think ( void ) { + // If we are bound to an entity that is now hidden we can just not render if looping, otherwise stop the effect + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + if ( renderEffect.loop ) { + return; + } + Stop ( ); + } + +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_EFFECT); +// RAVEN END + // If there is a valid effect handle and we havent started playing + // and effect yet then see if its time + if( effectDefHandle < 0 && renderEffect.declEffect ) { + if( renderEffect.startTime >= 0.0f ) { + // Make sure our origins are all straight before starting the effect + UpdateBind(); + renderEffect.attenuation = 1.0f; + + // if the rendereffect needs sound give it an emitter. + if( renderEffect.referenceSoundHandle <= 0 ) { + if( gameRenderWorld->EffectDefHasSound( &renderEffect) ) + { + renderEffect.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } else { + renderEffect.referenceSoundHandle = -1; + } + } + // Add the render effect + effectDefHandle = gameRenderWorld->AddEffectDef( &renderEffect, gameLocal.time ); + if ( effectDefHandle < 0 ) { + PostEventMS( &EV_Remove, 0 ); + } + } + + return; + } + + // If we lost our effect def handle then just remove ourself + if( effectDefHandle < 0 ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Dont do anything else if its not a new client frame + if( !gameLocal.isNewFrame ) { + return; + } + + // Check to see if the player can possibly see the effect or not + renderEffect.inConnectedArea = true; + if( bindMaster ) { + renderEffect.inConnectedArea = gameLocal.InPlayerConnectedArea( bindMaster ); + } + + // Update the bind + UpdateBind(); + + // Update the actual render effect now + if( gameRenderWorld->UpdateEffectDef( effectDefHandle, &renderEffect, gameLocal.time ) ) { + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + return; + } +} + +/* +================ +rvClientEffect::Play +================ +*/ +bool rvClientEffect::Play ( int _startTime, bool _loop, const idVec3& endOrigin ) { + if ( !renderEffect.declEffect ) { + return false; + } + + // Initialize the render entity + if ( bindMaster ) { + renderEntity_t* renderEnt = bindMaster->GetRenderEntity ( ); + assert( renderEnt ); + + // Copy suppress values from parent entity + renderEffect.allowSurfaceInViewID = renderEnt->allowSurfaceInViewID; + renderEffect.suppressSurfaceInViewID = renderEnt->suppressSurfaceInViewID; + renderEffect.weaponDepthHackInViewID = renderEnt->weaponDepthHackInViewID; + } + + renderEffect.shaderParms[SHADERPARM_RED] = 1.0f; + renderEffect.shaderParms[SHADERPARM_GREEN] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BLUE] = 1.0f; + renderEffect.shaderParms[SHADERPARM_ALPHA] = 1.0f; + renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f; + renderEffect.shaderParms[SHADERPARM_TIMEOFFSET] = MS2SEC( gameLocal.time ); + renderEffect.hasEndOrigin = ( endOrigin != vec3_origin ); + renderEffect.endOrigin = endOrigin; + renderEffect.loop = _loop; + + assert( effectDefHandle < 0 ); + + renderEffect.startTime = MS2SEC( _startTime ); + + return true; +} + +/* +================ +rvClientEffect::Stop +================ +*/ +void rvClientEffect::Stop ( bool destroyParticles ) { + if( effectDefHandle < 0 ) { + renderEffect.startTime = -1.0f; + renderEffect.declEffect = NULL; + return; + } + + if ( destroyParticles ) { + // Clear the effect index to make sure the effect isnt started again. This is + // an indirect way of making the effect not think + renderEffect.declEffect = NULL; + + FreeEffectDef ( ); + PostEventMS( &EV_Remove, 0 ); + } else { + gameRenderWorld->StopEffectDef( effectDefHandle ); + // this will ensure the effect doesn't re-up when loaded from a save. + renderEffect.startTime = -1.0f; + Unbind ( ); + } +} + +/* +================ +rvClientEffect::Restart +================ +*/ +void rvClientEffect::Restart ( void ) { + FreeEffectDef ( ); + + if ( renderEffect.loop ) { + Play ( gameLocal.time, true, renderEffect.endOrigin ); + } +} + +/* +================ +rvClientEffect::Attenuate +================ +*/ +void rvClientEffect::Attenuate ( float attenuation ) { + renderEffect.attenuation = attenuation; +} + +/* +================ +rvClientEffect::GetDuration +================ +*/ +float rvClientEffect::GetDuration( void ) const { + if( effectDefHandle < 0 ) { + return 0.0f; + } + + return bse->EffectDuration( gameRenderWorld->GetEffectDef( effectDefHandle ) ); +} + +/* +================ +rvClientEffect::DrawDebugInfo +================ +*/ +void rvClientEffect::DrawDebugInfo ( void ) const { + rvClientEntity::DrawDebugInfo ( ); + + if ( !gameLocal.GetLocalPlayer() ) { + return; + } + + if( !renderEffect.declEffect ) { + return; + } + + idMat3 axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + idVec3 up = axis[ 2 ] * 5.0f; + + gameRenderWorld->DrawText ( renderEffect.declEffect->GetName(), worldOrigin + up, 0.1f, colorWhite, axis, 1 ); +} + +/* +================ +rvClientEffect::Save +================ +*/ +void rvClientEffect::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEffect( renderEffect ); + savefile->WriteJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::Restore +================ +*/ +void rvClientEffect::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEffect( renderEffect ); + effectDefHandle = -1; + savefile->ReadJoint( endOriginJoint ); +} + +/* +================ +rvClientEffect::FreeEntityDef +================ +*/ +void rvClientEffect::FreeEntityDef( void ) { + FreeEffectDef(); +} + +/* +=============================================================================== + +rvClientCrawlEffect + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientEffect, rvClientCrawlEffect ) +END_CLASS + +/* +================ +rvClientCrawlEffect::rvClientCrawlEffect +================ +*/ +rvClientCrawlEffect::rvClientCrawlEffect ( void ) { +} + +rvClientCrawlEffect::rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int _crawlTime, idList* joints ) : rvClientEffect ( effect ) { + int i; + + // Crawl effects require an animated entity + crawlEnt = dynamic_cast(ent); + if ( !crawlEnt) { + return; + } + + // Specific joint list provided? + if ( joints && joints->Num () ) { + crawlJoints.Clear ( ); + for ( i = 0; i < joints->Num(); i ++ ) { + crawlJoints.Append ( (*joints)[i] ); + } + } else { + // Use only parent joints and skip joint zero which is presumed to be the origin + for ( i = ent->GetAnimator()->NumJoints() - 1; i > 0; i -- ) { + if ( i != ent->GetAnimator()->GetFirstChild ( (jointHandle_t)i ) ) { + crawlJoints.Append ( (jointHandle_t)i ); + } + } + } + + //no joints? abort! + if ( !crawlJoints.Num() ) { + return; + } + + jointStart = gameLocal.random.RandomInt ( crawlJoints.Num() ); + crawlDir = gameLocal.random.RandomInt ( 2 ) > 0 ? 1 : -1; + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + crawlTime = _crawlTime; + nextCrawl = gameLocal.time + crawlTime; +} + +/* +================ +rvClientCrawlEffect::Think +================ +*/ +void rvClientCrawlEffect::Think ( void ) { + + // If there is no crawl entity or no crawl joints then just free ourself + if ( !crawlEnt || !crawlJoints.Num() ) { + PostEventMS ( &EV_Remove, 0 ); + return; + } + + // Move to the next joint if its time + if ( gameLocal.time > nextCrawl ) { + jointStart = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + jointEnd = (jointStart + crawlDir + crawlJoints.Num() ) % crawlJoints.Num(); + nextCrawl = gameLocal.time + crawlTime; + } + + idVec3 offsetStart; + idVec3 offsetEnd; + idVec3 dir; + idMat3 axis; + + // Get the start origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointStart], gameLocal.time, offsetStart, axis ); + SetOrigin ( offsetStart ); + + // Get the end origin + crawlEnt->GetJointWorldTransform ( crawlJoints[jointEnd], gameLocal.time, offsetEnd, axis ); + SetEndOrigin ( offsetEnd ); + + // Update the axis to point at the bone + dir = offsetEnd - offsetStart; + dir.Normalize(); + SetAxis ( dir.ToMat3( ) ); + + rvClientEffect::Think ( ); +} + +/* +================ +rvClientCrawlEffect::Save +================ +*/ +void rvClientCrawlEffect::Save( idSaveGame *savefile ) const { + savefile->WriteInt( crawlJoints.Num() ); + for( int ix = 0; ix < crawlJoints.Num(); ++ix ) { + savefile->WriteJoint( crawlJoints[ix] ); + } + + savefile->WriteInt( crawlTime ); + savefile->WriteInt( nextCrawl ); + savefile->WriteInt( jointStart ); + savefile->WriteInt( jointEnd ); + savefile->WriteInt( crawlDir ); + crawlEnt.Save( savefile ); +} + +/* +================ +rvClientCrawlEffect::Restore +================ +*/ +void rvClientCrawlEffect::Restore( idRestoreGame *savefile ) { + int numJoints = 0; + savefile->ReadInt( numJoints ); + crawlJoints.SetNum( numJoints ); + for( int ix = 0; ix < numJoints; ++ix ) { + savefile->ReadJoint( crawlJoints[ix] ); + } + + savefile->ReadInt( crawlTime ); + savefile->ReadInt( nextCrawl ); + savefile->ReadInt( jointStart ); + savefile->ReadInt( jointEnd ); + savefile->ReadInt( crawlDir ); + crawlEnt.Restore( savefile ); +} diff --git a/src/mpgame/client/ClientEffect.h b/src/mpgame/client/ClientEffect.h new file mode 100644 index 0000000..60541b6 --- /dev/null +++ b/src/mpgame/client/ClientEffect.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEffect.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_EFFECT_H__ +#define __GAME_CLIENT_EFFECT_H__ + +class rvClientEffect : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientEffect ); + + rvClientEffect( void ); + rvClientEffect( const idDecl *effect ); + virtual ~rvClientEffect( void ); + + virtual void Think ( void ); + virtual void DrawDebugInfo ( void ) const; + virtual void FreeEntityDef ( void ); + virtual void UpdateBind ( void ); + + bool Play ( int startTime, bool loop = false, const idVec3& origin = vec3_origin ); + void Stop ( bool destroyParticles = false ); + void Restart ( void ); + + int GetEffectIndex ( void ); + const char * GetEffectName ( void ); + + void Attenuate ( float attenuation ); + + float GetDuration ( void ) const; + renderEffect_t* GetRenderEffect ( void ) { return &renderEffect; } + + void SetEndOrigin ( const idVec3& endOrigin ); + void SetEndOrigin ( jointHandle_t joint ) { endOriginJoint = joint; } + + void SetGravity ( const idVec3& gravity ) { renderEffect.gravity = gravity; } + + void SetColor ( const idVec4& color ); + void SetBrightness ( float brightness ) { renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = brightness; } + void SetAmbient ( bool in ) { renderEffect.ambient = in; } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); +protected: + + void Init ( const idDecl *effect ); + void FreeEffectDef ( void ); + + renderEffect_t renderEffect; + int effectDefHandle; + jointHandle_t endOriginJoint; +}; + +ID_INLINE void rvClientEffect::SetEndOrigin ( const idVec3& endOrigin ) { + renderEffect.endOrigin = endOrigin; + renderEffect.hasEndOrigin = !(endOrigin == vec3_origin); +} + +ID_INLINE void rvClientEffect::SetColor ( const idVec4& color ) { + renderEffect.shaderParms[SHADERPARM_RED] = color[0]; + renderEffect.shaderParms[SHADERPARM_GREEN] = color[1]; + renderEffect.shaderParms[SHADERPARM_BLUE] = color[2]; + renderEffect.shaderParms[SHADERPARM_ALPHA] = color[3]; +} + +//---------------------------------------------------------------- +// rvClientCrawlEffect +//---------------------------------------------------------------- + +class idAnimatedEntity; + +class rvClientCrawlEffect : public rvClientEffect { +public: + + CLASS_PROTOTYPE( rvClientCrawlEffect ); + + rvClientCrawlEffect ( void ); + rvClientCrawlEffect ( const idDecl *effect, idEntity* ent, int crawlTime, idList* joints = NULL ); + ~rvClientCrawlEffect ( void ) {} + + virtual void Think ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +protected: + + idList crawlJoints; + int crawlTime; + int nextCrawl; + int jointStart; + int jointEnd; + int crawlDir; + idEntityPtr crawlEnt; +}; + +typedef rvClientEntityPtr rvClientEffectPtr; + +#endif // __GAME_CLIENT_EFFECT_H__ diff --git a/src/mpgame/client/ClientEntity.cpp b/src/mpgame/client/ClientEntity.cpp new file mode 100644 index 0000000..7e1e154 --- /dev/null +++ b/src/mpgame/client/ClientEntity.cpp @@ -0,0 +1,675 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEntity.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, rvClientEntity ) +END_CLASS + +/* +================ +rvClientEntity::rvClientEntity +================ +*/ +rvClientEntity::rvClientEntity( void ) { + bindMaster = NULL; + entityNumber = -1; + + bindOrigin.Zero(); + bindAxis.Identity(); + bindJoint = INVALID_JOINT; + bindOrientated = false; + + memset( &refSound, 0, sizeof(refSound) ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::~rvClientEntity +================ +*/ +rvClientEntity::~rvClientEntity( void ) { + Unbind(); + gameLocal.UnregisterClientEntity( this ); + + // Free sound emitter + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +================ +rvClientEntity::Spawn +================ +*/ +void rvClientEntity::Spawn( void ) { + idVec3 origin; + idMat3 axis; + + gameLocal.RegisterClientEntity( this ); + + spawnNode.SetOwner( this ); + bindNode.SetOwner( this ); + + origin = spawnArgs.GetVector( "origin", "0 0 0" ); + axis = spawnArgs.GetMatrix( "axis", "1 0 0 0 1 0 0 0 1" ); + + InitDefaultPhysics( origin, axis ); + + SetOrigin( origin ); + SetAxis( axis ); +} + +/* +================ +rvClientEntity::Present +================ +*/ +void rvClientEntity::Present ( void ) { +} + +/* +================ +rvClientEntity::FreeEntityDef +================ +*/ +void rvClientEntity::FreeEntityDef ( void ) { +} + +/* +================ +rvClientEntity::Think +================ +*/ +void rvClientEntity::Think ( void ) { + UpdateBind(); + UpdateSound(); + Present(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Bind ( idEntity* master, jointHandle_t joint, bool isOrientated ) { + Unbind(); + + if ( joint != INVALID_JOINT && !dynamic_cast(master) ) { + gameLocal.Warning( "rvClientEntity::Bind: entity '%s' cannot support skeletal models.", master->GetName() ); + joint = INVALID_JOINT; + } + + bindMaster = master; + bindJoint = joint; + bindOrigin = worldOrigin; + bindAxis = worldAxis; + + bindNode.AddToEnd ( bindMaster->clientEntities ); + + bindOrientated = isOrientated; + if( physics ) { + physics->SetMaster( bindMaster, bindOrientated ); + } + + UpdateBind(); +} + +/* +================ +rvClientEntity::Bind +================ +*/ +void rvClientEntity::Unbind ( void ) { + if ( !bindMaster ) { + return; + } + + bindMaster = NULL; + bindNode.Remove ( ); +} + +/* +================ +rvClientEntity::SetOrigin +================ +*/ +void rvClientEntity::SetOrigin( const idVec3& origin ) { + if ( bindMaster ) { + bindOrigin = origin; + } else { + worldOrigin = origin; + } +} + +/* +================ +rvClientEntity::SetAxis +================ +*/ +void rvClientEntity::SetAxis( const idMat3& axis ) { + if ( bindMaster ) { + bindAxis = axis * bindMaster->GetRenderEntity()->axis.Transpose(); + } else { + worldAxis = axis; + } +} + +/* +================ +rvClientEntity::UpdateBind +================ +*/ +void rvClientEntity::UpdateBind ( void ) { + if ( !bindMaster ) { + return; + } + + if ( bindJoint != INVALID_JOINT ) { + static_cast(bindMaster.GetEntity())->GetJointWorldTransform ( bindJoint, gameLocal.time, worldOrigin, worldAxis ); + } else { + bindMaster->GetPosition( worldOrigin, worldAxis ); + //if ( !bindMaster->GetPhysicsToVisualTransform( worldOrigin, worldAxis ) ) { + // bindMaster->GetPosition( worldOrigin, worldAxis ); + //} + } + + worldOrigin += (bindOrigin * worldAxis); + worldAxis = bindAxis * worldAxis; +} + +/* +================ +rvClientEntity::IsClient +================ +*/ +bool rvClientEntity::IsClient ( void ) const { + return true; +} + +/* +================ +rvClientEntity::DrawDebugInfo +================ +*/ +void rvClientEntity::DrawDebugInfo ( void ) const { + idBounds bounds ( idVec3(-8,-8,-8), idVec3(8,8,8) ); + + gameRenderWorld->DebugBounds ( colorGreen, bounds, worldOrigin ); + + if ( gameLocal.GetLocalPlayer() ) { + gameRenderWorld->DrawText ( GetClassname ( ), worldOrigin, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } +} + +/* +================ +rvClientEntity::UpdateSound +================ +*/ +void rvClientEntity::UpdateSound( void ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } +} + +/* +================ +rvClientEntity::SetSoundVolume +================ +*/ +void rvClientEntity::SetSoundVolume( float volume ) { + refSound.parms.volume = volume; +} + + +/* +================ +rvClientEntity::StartSound +================ +*/ +bool rvClientEntity::StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ) { + const idSoundShader *shader; + const char *sound; + + if ( length ) { + *length = 0; + } + + idStr soundNameStr = soundName; + if( soundNameStr.CmpPrefix( "snd_" ) && soundNameStr.CmpPrefix( "lipsync_" ) ) { + common->Warning( "Non precached sound \'%s\'", soundName ); + } + + if ( !spawnArgs.GetString( soundName, "", &sound ) ) { + return false; + } + + if ( *sound == '\0' ) { + return false; + } + + if ( !gameLocal.isNewFrame ) { + // don't play the sound, but don't report an error + return true; + } + + shader = declManager->FindSound( sound ); + return StartSoundShader( shader, channel, soundShaderFlags ); +} + + +/* +================ +rvClientEntity::StartSoundShader +================ +*/ +int rvClientEntity::StartSoundShader ( const idSoundShader* shader, const s_channelType channel, int soundShaderFlags ) { + if ( !shader ) { + return 0; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + UpdateSound(); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return( 0 ); + } + + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + return emitter->StartSound( shader, channel, gameLocal.random.RandomFloat(), soundShaderFlags ); +} + +/* +================ +rvClientEntity::Size + +Returns Returns memory size of an rvClientEntity. +================ +*/ + +size_t rvClientEntity::Size ( void ) const { + return sizeof( rvClientEntity ); +} + +/* +================ +rvClientEntity::Save +================ +*/ +void rvClientEntity::Save( idSaveGame *savefile ) const { + savefile->WriteInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->WriteVec3( worldOrigin ); + savefile->WriteVec3( worldVelocity ); + savefile->WriteMat3( worldAxis ); + + bindMaster.Save( savefile ); + savefile->WriteVec3( bindOrigin ); + savefile->WriteMat3( bindAxis ); + savefile->WriteJoint( bindJoint ); + + savefile->WriteRefSound( refSound ); +} + +/* +================ +rvClientEntity::Restore +================ +*/ +void rvClientEntity::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( entityNumber ); + + // idLinkList spawnNode; - reconstructed in the master entity load + // idLinkList bindNode; - reconstructed in the master entity load + + savefile->ReadVec3( worldOrigin ); + savefile->ReadVec3( worldVelocity ); + savefile->ReadMat3( worldAxis ); + + bindMaster.Restore( savefile ); + savefile->ReadVec3( bindOrigin ); + savefile->ReadMat3( bindAxis ); + savefile->ReadJoint( bindJoint ); + + savefile->ReadRefSound( refSound ); +} + +/* +================ +rvClientEntity::RunPhysics +================ +*/ +void rvClientEntity::RunPhysics ( void ) { + idPhysics* physics = GetPhysics( ); + if( !physics ) { + return; + } + + rvClientPhysics* clientPhysics = (rvClientPhysics*)gameLocal.entities[ENTITYNUM_CLIENT]; + static_cast( clientPhysics )->currentEntityNumber = entityNumber; + + // order important: 1) set client physics bind master to client ent's bind master + // 2) set physics to client ent's physics, which sets physics + // master to client ent's master + // 3) set client physics origin to client ent origin, depends on + // proper bind master from 1 + clientPhysics->PushBindInfo( bindMaster, bindJoint, bindOrientated ); + clientPhysics->SetPhysics( physics ); + clientPhysics->PushOriginInfo( bindMaster ? bindOrigin : worldOrigin, bindMaster ? bindAxis : worldAxis ); + + physics->Evaluate ( gameLocal.time - gameLocal.previousTime, gameLocal.time ); + + worldOrigin = physics->GetOrigin(); + worldVelocity = physics->GetLinearVelocity(); + worldAxis = physics->GetAxis(); + + // order important: 1) restore previous bind master + // 2) reset physics with previous bind master + // 3) reset origin with previous bind master + clientPhysics->PopBindInfo(); + clientPhysics->SetPhysics( NULL ); + clientPhysics->PopOriginInfo(); + + UpdateAnimationControllers(); +} + +/* +================ +rvClientEntity::GetPhysics +================ +*/ +idPhysics* rvClientEntity::GetPhysics ( void ) const { + return physics; +} + +/* +================ +rvClientEntity::Collide +================ +*/ +bool rvClientEntity::Collide ( const trace_t &collision, const idVec3 &velocity ) { + return false; +} + +/* +================ +rvClientEntity::GetImpactInfo +================ +*/ +void rvClientEntity::GetImpactInfo( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ) { + GetPhysics()->GetImpactInfo( id, point, info ); +} + +/* +================ +rvClientEntity::ApplyImpulse +================ +*/ +void rvClientEntity::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ) { + GetPhysics()->ApplyImpulse( id, point, impulse ); +} + +/* +================ +rvClientEntity::AddForce +================ +*/ +void rvClientEntity::AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) { + GetPhysics()->AddForce( id, point, force ); +} + +/* +================ +rvClientEntity::UpdateAnimationControllers +================ +*/ +bool rvClientEntity::UpdateAnimationControllers( void ) { + return false; +} + +/* +================ +rvClientEntity::InitDefaultPhysics +================ +*/ +void rvClientEntity::InitDefaultPhysics( const idVec3 &origin, const idMat3 &axis ) { + const char *temp; + idClipModel *clipModel = NULL; + + // check if a clipmodel key/value pair is set + if ( spawnArgs.GetString( "clipmodel", "", &temp ) ) { + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( temp ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + + if ( !spawnArgs.GetBool( "noclipmodel", "0" ) ) { + + // check if mins/maxs or size key/value pairs are set + if ( !clipModel ) { + idVec3 size; + idBounds bounds; + bool setClipModel = false; + + if ( spawnArgs.GetVector( "mins", NULL, bounds[0] ) && + spawnArgs.GetVector( "maxs", NULL, bounds[1] ) ) { + setClipModel = true; + if ( bounds[0][0] > bounds[1][0] || bounds[0][1] > bounds[1][1] || bounds[0][2] > bounds[1][2] ) { + gameLocal.Error( "Invalid bounds '%s'-'%s' on client entity '%d'", bounds[0].ToString(), bounds[1].ToString(), entityNumber ); + } + } else if ( spawnArgs.GetVector( "size", NULL, size ) ) { + if ( ( size.x < 0.0f ) || ( size.y < 0.0f ) || ( size.z < 0.0f ) ) { + gameLocal.Error( "Invalid size '%s' on client entity '%d'", size.ToString(), entityNumber ); + } + bounds[0].Set( size.x * -0.5f, size.y * -0.5f, 0.0f ); + bounds[1].Set( size.x * 0.5f, size.y * 0.5f, size.z ); + setClipModel = true; + } + + if ( setClipModel ) { + int numSides; + idTraceModel trm; + + if ( spawnArgs.GetInt( "cylinder", "0", numSides ) && numSides > 0 ) { + trm.SetupCylinder( bounds, numSides < 3 ? 3 : numSides ); + } else if ( spawnArgs.GetInt( "cone", "0", numSides ) && numSides > 0 ) { + trm.SetupCone( bounds, numSides < 3 ? 3 : numSides ); + // RAVEN BEGIN + // bdube: added dodecahedron + } else if ( spawnArgs.GetInt( "dodecahedron", "0", numSides ) && numSides > 0 ) { + trm.SetupDodecahedron ( bounds ); + // RAVEN END + } else { + trm.SetupBox( bounds ); + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel( trm ); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + + // check if the visual model can be used as collision model + if ( !clipModel ) { + temp = spawnArgs.GetString( "model" ); + if ( ( temp != NULL ) && ( *temp != 0 ) ) { + // RAVEN BEGIN + // jscott:slash problems + idStr canonical = temp; + canonical.BackSlashesToSlashes(); + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); + // RAVEN END + clipModel = new idClipModel(); + if ( !clipModel->LoadModel( canonical ) ) { + delete clipModel; + clipModel = NULL; + } + // RAVEN BEGIN + // mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); + // RAVEN END + } + } + } + + defaultPhysicsObj.SetSelf( gameLocal.entities[ENTITYNUM_CLIENT] ); + defaultPhysicsObj.SetClipModel( clipModel, 1.0f ); + defaultPhysicsObj.SetOrigin( origin ); + defaultPhysicsObj.SetAxis( axis ); + + physics = &defaultPhysicsObj; + + // by default no collision + physics->SetContents( 0 ); +} + + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, rvClientPhysics ) +END_CLASS + +/* +===================== +rvClientPhysics::Spawn +===================== +*/ +void rvClientPhysics::Spawn( void ) { + pushedOrientated = false; +} + +/* +===================== +rvClientPhysics::Collide +===================== +*/ +bool rvClientPhysics::Collide( const trace_t &collision, const idVec3 &velocity ) { + assert ( currentEntityNumber >= 0 && currentEntityNumber < MAX_CENTITIES ); + + rvClientEntity* cent; + cent = gameLocal.clientEntities [ currentEntityNumber ]; + if ( cent ) { + return cent->Collide ( collision, velocity ); + } + + return false; +} + +/* +===================== +rvClientPhysics::PushBindInfo +===================== +*/ +void rvClientPhysics::PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ) { + pushedBindJoint = joint; + pushedBindMaster = master; + pushedOrientated = fl.bindOrientated; + + bindMaster = master; + bindJoint = joint; + fl.bindOrientated = orientated; +} + +/* +===================== +rvClientPhysics::PopBindInfo +===================== +*/ +void rvClientPhysics::PopBindInfo( void ) { + bindMaster = pushedBindMaster; + bindJoint = pushedBindJoint; + fl.bindOrientated = pushedOrientated; +} + +/* +===================== +rvClientPhysics::PushOriginInfo +===================== +*/ +void rvClientPhysics::PushOriginInfo( const idVec3& origin, const idMat3& axis ) { + if( !GetPhysics() ) { + return; + } + + pushedOrigin = GetPhysics()->GetOrigin(); + pushedAxis = GetPhysics()->GetAxis(); + + GetPhysics()->SetOrigin( origin ); + GetPhysics()->SetAxis( axis ); +} + +/* +===================== +rvClientPhysics::PopOriginInfo +===================== +*/ +void rvClientPhysics::PopOriginInfo( void ) { + if( !GetPhysics() ) { + return; + } + + GetPhysics()->SetOrigin( pushedOrigin ); + GetPhysics()->SetAxis( pushedAxis ); +} diff --git a/src/mpgame/client/ClientEntity.h b/src/mpgame/client/ClientEntity.h new file mode 100644 index 0000000..66a32f7 --- /dev/null +++ b/src/mpgame/client/ClientEntity.h @@ -0,0 +1,243 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientEntity.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_ENTITY_H__ +#define __GAME_CLIENT_ENTITY_H__ + +class rvClientEntity : public idClass { +public: + + ABSTRACT_PROTOTYPE( rvClientEntity ); + + rvClientEntity ( void ); + + virtual ~rvClientEntity ( void ); + + void Spawn ( void ); + + virtual void Present ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); + const idVec3& GetOrigin ( void ); + const idMat3& GetAxis ( void ); + + void Bind ( idEntity* master, jointHandle_t joint = INVALID_JOINT, bool isOrientated = false ); + void Unbind ( void ); + + virtual bool IsClient ( void ) const; + virtual void DrawDebugInfo ( void ) const; + + void SetSoundVolume( float volume ); + bool StartSound( const char *soundName, const s_channelType channel, int soundShaderFlags, bool broadcast, int *length ); + int StartSoundShader ( const idSoundShader *shader, const s_channelType channel, int soundShaderFlags ); + + // I'm guessing someone may decide to derive from rvClientEntity, so this virtual. + virtual size_t Size ( void ) const; + + virtual void FreeEntityDef ( void ); + + const idEntityPtr& GetBindMaster( void ) const; + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void GetImpactInfo ( idEntity *ent, int id, const idVec3 &point, impactInfo_t *info ); + virtual void ApplyImpulse ( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse, bool splash ); + virtual void AddForce ( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); + + virtual bool UpdateAnimationControllers( void ); + + void InitDefaultPhysics ( const idVec3 &origin, const idMat3 &axis ); +protected: + + void RunPhysics ( void ); + + virtual void UpdateBind ( void ); + void UpdateSound ( void ); + +public: + + int entityNumber; + + idLinkList spawnNode; + idLinkList bindNode; + + idDict spawnArgs; + +protected: + idPhysics_Static defaultPhysicsObj; // default physics object + idPhysics* physics; + + idVec3 worldOrigin; + idVec3 worldVelocity; + idMat3 worldAxis; + + idEntityPtr bindMaster; + idVec3 bindOrigin; + idMat3 bindAxis; + jointHandle_t bindJoint; + bool bindOrientated; + refSound_t refSound; +}; + +ID_INLINE const idVec3& rvClientEntity::GetOrigin ( void ) { + return worldOrigin; +} + +ID_INLINE const idMat3& rvClientEntity::GetAxis ( void ) { + return worldAxis; +} + +ID_INLINE const idEntityPtr& rvClientEntity::GetBindMaster( void ) const { + return bindMaster; +} + +//============================================================================ + +template< class type > +class rvClientEntityPtr { +public: + rvClientEntityPtr(); + + rvClientEntityPtr & operator=( type* cent ); + + int GetSpawnId ( void ) const { return spawnId; } + bool SetSpawnId ( int id ); + bool UpdateSpawnId ( void ); + + bool IsValid ( void ) const; + type * GetEntity ( void ) const; + int GetEntityNum ( void ) const; + + type * operator-> ( void ) const { return GetEntity ( ); } + operator type* ( void ) const { return GetEntity ( ); } + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + +private: + int spawnId; +}; + +template< class type > +ID_INLINE rvClientEntityPtr::rvClientEntityPtr() { + spawnId = 0; +} + +template< class type > +ID_INLINE rvClientEntityPtr &rvClientEntityPtr::operator=( type *cent ) { + if ( cent == NULL ) { + spawnId = 0; + } else { + spawnId = ( gameLocal.clientSpawnIds[cent->entityNumber] << CENTITYNUM_BITS ) | cent->entityNumber; + } + return *this; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::SetSpawnId( int id ) { + if ( ( id >> CENTITYNUM_BITS ) == gameLocal.clientSpawnIds[ id & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] ) { + spawnId = id; + return true; + } + return false; +} + +template< class type > +ID_INLINE bool rvClientEntityPtr::IsValid( void ) const { + return ( gameLocal.clientSpawnIds[ spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ); +} + +template< class type > +ID_INLINE type *rvClientEntityPtr::GetEntity( void ) const { + int entityNum = spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ); + if ( ( gameLocal.clientSpawnIds[ entityNum ] == ( ( spawnId >> CENTITYNUM_BITS ) & ( 0xffffffff >> CENTITYNUM_BITS ) ) ) ) { + return static_cast( gameLocal.clientEntities[ entityNum ] ); + } + return NULL; +} + +template< class type > +ID_INLINE int rvClientEntityPtr::GetEntityNum( void ) const { + return ( spawnId & ( ( 1 << CENTITYNUM_BITS ) - 1 ) ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Save( idSaveGame *savefile ) const { + savefile->WriteInt( spawnId ); +} + +template< class type > +ID_INLINE void rvClientEntityPtr::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( spawnId ); +} + +/* +=============================================================================== + +rvClientPhysics + +=============================================================================== +*/ + +class rvClientPhysics : public idEntity { +public: + + CLASS_PROTOTYPE( rvClientPhysics ); + + rvClientPhysics( void ) { currentEntityNumber = -1; } + + void Spawn( void ); + + virtual bool Collide( const trace_t &collision, const idVec3 &velocity ); + virtual void PushBindInfo( idEntity* master, jointHandle_t joint, bool orientated ); + virtual void PopBindInfo( void ); + + virtual void PushOriginInfo( const idVec3& origin, const idMat3& axis ); + virtual void PopOriginInfo( void ); + + // client side entities. make sure the networking doesn't fuck around with this + virtual bool ClientStale( void ) { assert( false ); return false; } + virtual void ClientUnstale( void ) { assert( false ); } + + int currentEntityNumber; +private: + idEntity* pushedBindMaster; + jointHandle_t pushedBindJoint; + idVec3 pushedOrigin; + idMat3 pushedAxis; + bool pushedOrientated; +}; + +#endif // __GAME_CLIENT_ENTITY_H__ diff --git a/src/mpgame/client/ClientModel.cpp b/src/mpgame/client/ClientModel.cpp new file mode 100644 index 0000000..6c56e6c --- /dev/null +++ b/src/mpgame/client/ClientModel.cpp @@ -0,0 +1,474 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientModel.cpp +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "ClientModel.h" + +/* +=============================================================================== + +rvClientModel + +=============================================================================== +*/ +CLASS_DECLARATION( rvClientEntity, rvClientModel ) +END_CLASS + + +/* +================ +rvClientModel::rvClientModel +================ +*/ +rvClientModel::rvClientModel ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + worldAxis = mat3_identity; + entityDefHandle = -1; +} + +/* +================ +rvClientModel::~rvClientModel +================ +*/ +rvClientModel::~rvClientModel ( void ) { + FreeEntityDef ( ); +} + +/* +================ +rvClientModel::FreeEntityDef +================ +*/ +void rvClientModel::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +/* +================ +rvClientModel::Spawn +================ +*/ +void rvClientModel::Spawn ( void ) { + const char* spawnarg; + + spawnArgs.GetString ( "classname", "", classname ); + + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + renderEntity.entityNum = entityNumber; + + spawnarg = spawnArgs.GetString( "model" ); + if ( spawnarg && *spawnarg ) { + SetModel( spawnarg ); + } +} + +/* +================ +rvClientModel::Think +================ +*/ +void rvClientModel::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + UpdateBind(); + Present(); +} + +/* +================ +rvClientModel::Present +================ +*/ +void rvClientModel::Present(void) { + // Hide client entities bound to a hidden entity + if ( bindMaster && (bindMaster->IsHidden ( ) || (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) ) { + return; + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis; + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientModel::SetCustomShader +================ +*/ +bool rvClientModel::SetCustomShader ( const char* shaderName ) { + if ( shaderName == NULL ) { + return false; + } + + const idMaterial* material = declManager->FindMaterial( shaderName ); + + if ( material == NULL ) { + return false; + } + + renderEntity.customShader = material; + + return true; +} + +/* +================ +rvClientModel::Save +================ +*/ +void rvClientModel::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + savefile->WriteString ( classname ); // cnicholson: Added unsaved var + +} + +/* +================ +rvClientModel::Restore +================ +*/ +void rvClientModel::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + savefile->ReadString ( classname ); // cnicholson: Added unrestored var + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } +} + +/* +================ +rvClientModel::SetModel +================ +*/ +void rvClientModel::SetModel( const char* modelname ) { + FreeEntityDef(); + + renderEntity.hModel = renderModelManager->FindModel( modelname ); + + if ( renderEntity.hModel ) { + renderEntity.hModel->Reset(); + } + + renderEntity.callback = NULL; + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + if ( renderEntity.hModel ) { + renderEntity.bounds = renderEntity.hModel->Bounds( &renderEntity ); + } else { + renderEntity.bounds.Zero(); + } +} + +/* +============== +rvClientModel::ProjectOverlay +============== +*/ +void rvClientModel::ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ) { + float s, c; + idMat3 axis, axistemp; + idVec3 localOrigin, localAxis[2]; + idPlane localPlane[2]; + + // make sure the entity has a valid model handle + if ( entityDefHandle < 0 ) { + return; + } + + // only do this on dynamic md5 models + if ( renderEntity.hModel->IsDynamicModel() != DM_CACHED ) { + return; + } + + idMath::SinCos16( gameLocal.random.RandomFloat() * idMath::TWO_PI, s, c ); + + axis[2] = -dir; + axis[2].NormalVectors( axistemp[0], axistemp[1] ); + axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * -s; + axis[1] = axistemp[ 0 ] * -s + axistemp[ 1 ] * -c; + + renderEntity.axis.ProjectVector( origin - renderEntity.origin, localOrigin ); + renderEntity.axis.ProjectVector( axis[0], localAxis[0] ); + renderEntity.axis.ProjectVector( axis[1], localAxis[1] ); + + size = 1.0f / size; + localAxis[0] *= size; + localAxis[1] *= size; + + localPlane[0] = localAxis[0]; + localPlane[0][3] = -( localOrigin * localAxis[0] ) + 0.5f; + + localPlane[1] = localAxis[1]; + localPlane[1][3] = -( localOrigin * localAxis[1] ) + 0.5f; + + const idMaterial *mtr = declManager->FindMaterial( material ); + + // project an overlay onto the model + gameRenderWorld->ProjectOverlay( entityDefHandle, localPlane, mtr ); + + // make sure non-animating models update their overlay + UpdateVisuals(); +} + +/* +================ +rvClientModel::UpdateRenderEntity +================ +*/ +bool rvClientModel::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { + if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { + return false; + } + + idAnimator *animator = GetAnimator(); + if ( animator ) { + return animator->CreateFrame( gameLocal.time, false ); + } + + return false; +} + +/* +================ +rvClientModel::ModelCallback + +NOTE: may not change the game state whatsoever! +================ +*/ +bool rvClientModel::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { + rvClientEntity *cent; + + cent = gameLocal.clientEntities[ renderEntity->entityNum ]; + if ( !cent ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with NULL client entity '%d'", renderEntity->entityNum ); + return false; + } + + if( !cent->IsType( rvClientModel::GetClassType() ) ) { + gameLocal.Error( "rvClientModel::ModelCallback: callback with non-client model on client entity '%d'", renderEntity->entityNum ); + return false; + } + + return ((rvClientModel*)cent)->UpdateRenderEntity( renderEntity, renderView ); +} + +/* +================ +rvClientModel::GetPhysicsToVisualTransform +================ +*/ +bool rvClientModel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { + return false; +} + +/* +================ +rvClientModel::UpdateModelTransform +================ +*/ +void rvClientModel::UpdateModelTransform( void ) { + idVec3 origin; + idMat3 axis; + + if ( GetPhysicsToVisualTransform( origin, axis ) ) { + renderEntity.axis = axis * worldAxis; + renderEntity.origin = worldOrigin + origin * renderEntity.axis; + } else { + renderEntity.axis = worldAxis; + renderEntity.origin = worldOrigin; + } +} + +/* +================ +rvClientModel::UpdateModel +================ +*/ +void rvClientModel::UpdateModel( void ) { + UpdateModelTransform(); + + idAnimator *animator = GetAnimator(); + if ( animator && animator->ModelHandle() ) { + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + } +} + +/* +================ +rvClientModel::UpdateVisuals +================ +*/ +void rvClientModel::UpdateVisuals( void ) { + UpdateModel(); + UpdateSound(); +} + +/* +================ +rvClientModel::SetSkin +================ +*/ +void rvClientModel::SetSkin( const idDeclSkin *skin ) { + renderEntity.customSkin = skin; + UpdateVisuals(); +} + +/* +=============================================================================== + +rvAnimatedClientEntity + +=============================================================================== +*/ + +CLASS_DECLARATION( rvClientModel, rvAnimatedClientEntity ) +END_CLASS + +/* +================ +rvAnimatedClientEntity::rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::~rvAnimatedClientEntity +================ +*/ +rvAnimatedClientEntity::~rvAnimatedClientEntity ( void ) { +} + +/* +================ +rvAnimatedClientEntity::Spawn +================ +*/ +void rvAnimatedClientEntity::Spawn( void ) { + SetModel( spawnArgs.GetString( "model" ) ); +} +/* +================ +rvAnimatedClientEntity::Think +================ +*/ +void rvAnimatedClientEntity::Think ( void ) { + UpdateAnimation(); + + rvClientEntity::Think(); +} + +/* +================ +rvAnimatedClientEntity::UpdateAnimation +================ +*/ +void rvAnimatedClientEntity::UpdateAnimation( void ) { + // is the model an MD5? + if ( !animator.ModelHandle() ) { + // no, so nothing to do + return; + } + + // call any frame commands that have happened in the past frame + animator.ServiceAnims( gameLocal.previousTime, gameLocal.time ); + + // if the model is animating then we have to update it + if ( !animator.FrameHasChanged( gameLocal.time ) ) { + // still fine the way it was + return; + } + + // get the latest frame bounds + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + if ( renderEntity.bounds.IsCleared() ) { + gameLocal.DPrintf( "%d: inside out bounds\n", gameLocal.time ); + } + + // update the renderEntity + UpdateVisuals(); + Present(); + + // the animation is updated + animator.ClearForceUpdate(); +} + +/* +================ +rvAnimatedClientEntity::SetModel +================ +*/ +void rvAnimatedClientEntity::SetModel( const char *modelname ) { + FreeEntityDef(); + + renderEntity.hModel = animator.SetModel( modelname ); + if ( !renderEntity.hModel ) { + rvClientModel::SetModel( modelname ); + return; + } + + if ( !renderEntity.customSkin ) { + renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); + } + + // set the callback to update the joints + renderEntity.callback = rvClientModel::ModelCallback; + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + + //UpdateVisuals(); + Present(); +} diff --git a/src/mpgame/client/ClientModel.h b/src/mpgame/client/ClientModel.h new file mode 100644 index 0000000..9f0121f --- /dev/null +++ b/src/mpgame/client/ClientModel.h @@ -0,0 +1,116 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientModel.h +// +// A non-interactive client-side model +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MODEL_H__ +#define __GAME_CLIENT_MODEL_H__ + +class rvClientModel : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientModel ); + + rvClientModel ( void ); + virtual ~rvClientModel ( void ); + + void Spawn ( void ); + virtual void Think ( void ); + + virtual renderEntity_t* GetRenderEntity ( void ); + const char* GetClassname ( void ) const; + + virtual bool SetCustomShader ( const char* shaderName ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void FreeEntityDef ( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return NULL; } + virtual idAnimator* GetAnimator( void ) { return NULL; } + + + bool UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ); + static bool ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ); + + virtual bool GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ); + void ProjectOverlay( const idVec3 &origin, const idVec3 &dir, float size, const char *material ); + + void UpdateVisuals( void ); + void UpdateModel( void ); + void UpdateModelTransform( void ); + + void SetSkin( const idDeclSkin* skin ); + int GetModelDefHandle( void ); +protected: + void Present( void ); + + renderEntity_t renderEntity; + int entityDefHandle; + + idStr classname; +}; + +ID_INLINE renderEntity_t* rvClientModel::GetRenderEntity ( void ) { + return &renderEntity; +} + +ID_INLINE const char* rvClientModel::GetClassname ( void ) const { + return classname.c_str(); +} + +ID_INLINE int rvClientModel::GetModelDefHandle( void ) { + return entityDefHandle; +} + +class rvAnimatedClientEntity : public rvClientModel { +public: + CLASS_PROTOTYPE( rvAnimatedClientEntity ); + + rvAnimatedClientEntity ( void ); + virtual ~rvAnimatedClientEntity ( void ); + + void Spawn( void ); + virtual void Think( void ); + virtual void UpdateAnimation( void ); + + virtual void SetModel( const char *modelname ); + + virtual const idAnimator* GetAnimator( void ) const { return &animator; } + virtual idAnimator* GetAnimator( void ) { return &animator; } + +protected: + idAnimator animator; +}; + +#endif // __GAME_CLIENT_MODEL_H__ diff --git a/src/mpgame/client/ClientMoveable.cpp b/src/mpgame/client/ClientMoveable.cpp new file mode 100644 index 0000000..f4c90b6 --- /dev/null +++ b/src/mpgame/client/ClientMoveable.cpp @@ -0,0 +1,403 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientMoveable.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +=============================================================================== + +rvClientMoveable + +=============================================================================== +*/ +const idEventDef CL_FadeOut( "", "d" ); +const idEventDef CL_ClearDepthHack ( "" ); + +static const float BOUNCE_SOUND_MIN_VELOCITY = 100.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f; +static const int BOUNCE_SOUND_DELAY = 200; + +CLASS_DECLARATION( rvClientEntity, rvClientMoveable ) + EVENT( CL_FadeOut, rvClientMoveable::Event_FadeOut ) + EVENT( CL_ClearDepthHack, rvClientMoveable::Event_ClearDepthHack ) +END_CLASS + + +/* +================ +rvClientMoveable::rvClientMoveable +================ +*/ +rvClientMoveable::rvClientMoveable ( void ) { + memset ( &renderEntity, 0, sizeof(renderEntity) ); + entityDefHandle = -1; + scale.Init( 0, 0, 1.0f, 1.0f ); +} + +/* +================ +rvClientMoveable::~rvClientMoveable +================ +*/ +rvClientMoveable::~rvClientMoveable ( void ) { + FreeEntityDef ( ); + + // Remove any trail effect if there is one + if ( trailEffect ) { + trailEffect->Stop ( ); + } +} + +/* +================ +rvClientMoveable::FreeEntityDef +================ +*/ +void rvClientMoveable::FreeEntityDef ( void ) { + if ( entityDefHandle >= 0 ) { + gameRenderWorld->FreeEntityDef ( entityDefHandle ); + entityDefHandle = -1; + } +} + +idVec3 simpleTri[3] = +{ + idVec3( -1.0, -1.0, 0.0 ), + idVec3( 0.0, 2.0, 0.0 ), + idVec3( 2.0, 0.0, 0.0 ) +}; + +/* +================ +rvClientMoveable::Spawn +================ +*/ +void rvClientMoveable::Spawn ( void ) { + // parse static models the same way the editor display does + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); + + idTraceModel trm; + int clipShrink; + idStr clipModelName; + + // check if a clip model is set + spawnArgs.GetString( "clipmodel", "", clipModelName ); + if ( !clipModelName.Length () ) { + clipModelName = spawnArgs.GetString( "model" ); // use the visual model + } + + if ( clipModelName == SIMPLE_TRI_NAME ) { + trm.SetupPolygon( simpleTri, 3 ); + } else { + clipModelName.BackSlashesToSlashes(); + + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvClientMoveable '%d': cannot load collision model %s", entityNumber, clipModelName.c_str() ); + return; + } + } + + // if the model should be shrunk + clipShrink = spawnArgs.GetInt( "clipshrink" ); + if ( clipShrink != 0 ) { + trm.Shrink( clipShrink * CM_CLIP_EPSILON ); + } + + physicsObj.SetSelf ( gameLocal.entities[ENTITYNUM_CLIENT] ); + physicsObj.SetClipModel ( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "0.5" ), entityNumber ); + + physicsObj.SetOrigin( GetOrigin() ); + physicsObj.SetAxis( GetAxis() ); + physicsObj.SetBouncyness( spawnArgs.GetFloat( "bouncyness", "0.6" ) ); + physicsObj.SetFriction( spawnArgs.GetFloat("linear_friction", "0.6"), spawnArgs.GetFloat( "angular_friction", "0.6"), spawnArgs.GetFloat("friction", "0.05") ); + physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) ); + physicsObj.SetContents( 0 ); + // abahr: changed to MASK_SHOT_RENDERMODEL because brass was getting pinched between the player and the wall in some cases + // may want to try something cheaper. + physicsObj.SetClipMask( CONTENTS_OPAQUE ); // MASK_SHOT_RENDERMODEL | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER ); + physicsObj.Activate ( ); + + trailEffect = gameLocal.PlayEffect ( spawnArgs, "fx_trail", physicsObj.GetCenterMass(), GetAxis(), true ); + trailAttenuateSpeed = spawnArgs.GetFloat ( "trailAttenuateSpeed", "200" ); + + bounceSoundShader = declManager->FindSound ( spawnArgs.GetString ( "snd_bounce" ), false ); + bounceSoundTime = 0; + mPlayBounceSoundOnce = spawnArgs.GetBool("bounce_sound_once"); + mHasBounced = false; + + scale.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("scale_reset_duration", "0.2")), Max(VECTOR_EPSILON, spawnArgs.GetFloat("scale", "1.0f")), 1.0f ); +} + +/* +================ +rvClientMoveable::SetOrigin +================ +*/ +void rvClientMoveable::SetOrigin( const idVec3& origin ) { + rvClientEntity::SetOrigin( origin ); + physicsObj.SetOrigin( origin ); +} + +/* +================ +rvClientMoveable::SetAxis +================ +*/ +void rvClientMoveable::SetAxis( const idMat3& axis ) { + rvClientEntity::SetAxis( axis ); + physicsObj.SetAxis( axis ); +} + +/* +================ +rvClientMoveable::SetOwner +================ +*/ +void rvClientMoveable::SetOwner( idEntity* owner ) { + physicsObj.GetClipModel()->SetOwner( owner ); + physicsObj.GetClipModel()->SetEntity( owner ); +} + +/* +================ +rvClientMoveable::Think +================ +*/ +void rvClientMoveable::Think ( void ) { + if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) { + return; + } + + RunPhysics ( ); + + // Special case the sound update to use the center mass since the origin may be in an odd place + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( emitter ) { + refSound.origin = worldOrigin; + refSound.velocity = worldVelocity; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + } + + // Keep the trail effect following + if ( trailEffect ) { + float speed; + speed = idMath::ClampFloat ( 0, trailAttenuateSpeed, worldVelocity.LengthFast ( ) ); + if ( physicsObj.IsAtRest ( ) ) { + trailEffect->Stop ( ); + trailEffect = NULL; + } else { + trailEffect->SetOrigin ( worldOrigin ); + trailEffect->SetAxis ( worldAxis ); + trailEffect->Attenuate ( speed / trailAttenuateSpeed ); + } + } + + renderEntity.origin = worldOrigin; + renderEntity.axis = worldAxis * scale.GetCurrentValue( gameLocal.GetTime() ); + + // add to refresh list + if ( entityDefHandle == -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } else { + gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity ); + } +} + +/* +================ +rvClientMoveable::GetPhysics +================ +*/ +idPhysics* rvClientMoveable::GetPhysics ( void ) const { + return (idPhysics*)&physicsObj; +} + +/* +================ +rvClientMoveable::Collide +================ +*/ +bool rvClientMoveable::Collide ( const trace_t &collision, const idVec3 &velocity ) { + if (mPlayBounceSoundOnce && mHasBounced) + { + return false; + } + if ( bounceSoundShader && gameLocal.time > bounceSoundTime ) { + float speed; + speed = velocity.LengthFast ( ); + if ( speed > BOUNCE_SOUND_MIN_VELOCITY ) { + StartSoundShader ( bounceSoundShader, SND_CHANNEL_BODY, 0 ); + bounceSoundTime = BOUNCE_SOUND_DELAY; + mHasBounced = true; + } + } + + return false; +} + +/* +================ +rvClientMoveable::Save +================ +*/ +void rvClientMoveable::Save( idSaveGame *savefile ) const { + savefile->WriteRenderEntity( renderEntity ); + savefile->WriteInt( entityDefHandle ); + + trailEffect.Save( savefile ); + savefile->WriteFloat( trailAttenuateSpeed ); + + savefile->WriteStaticObject( physicsObj ); + + savefile->WriteInt( bounceSoundTime ); + savefile->WriteSoundShader( bounceSoundShader ); + + savefile->WriteBool(mPlayBounceSoundOnce); + savefile->WriteBool(mHasBounced); + + // TOSAVE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Restore +================ +*/ +void rvClientMoveable::Restore( idRestoreGame *savefile ) { + savefile->ReadRenderEntity( renderEntity, NULL ); + savefile->ReadInt( entityDefHandle ); + + trailEffect.Restore( savefile ); + savefile->ReadFloat( trailAttenuateSpeed ); + + savefile->ReadStaticObject( physicsObj ); + + savefile->ReadInt( bounceSoundTime ); + savefile->ReadSoundShader( bounceSoundShader ); + + savefile->ReadBool(mPlayBounceSoundOnce); + savefile->ReadBool(mHasBounced); + + // restore must retrieve entityDefHandle from the renderer + if ( entityDefHandle != -1 ) { + entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); + } + + // TORESTORE: idInterpolate scale; +} + +/* +================ +rvClientMoveable::Event_FadeOut +================ +*/ +void rvClientMoveable::Event_FadeOut ( int duration ) { + renderEntity.noShadow = true; + renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; + PostEventMS ( &EV_Remove, duration ); +} + +/* +================ +rvClientMoveable::Event_ClearDepthHack +================ +*/ +void rvClientMoveable::Event_ClearDepthHack ( void ) { + renderEntity.weaponDepthHackInViewID = 0; +} + +/* +================ +rvClientMoveable::SpawnClientMoveables +================ +*/ +void rvClientMoveable::SpawnClientMoveables( idEntity* ent, const char *type, idList* list ) { + const idKeyValue *kv; + idVec3 origin; + idMat3 axis; + + if( list == NULL || type == NULL ) { + return; + } + + // drop all items + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), NULL ); + while ( kv ) { + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if( ent->IsType( idAnimatedEntity::GetClassType() ) ) { +// RAVEN END + idAnimatedEntity* animEnt = static_cast(ent); + jointHandle_t clientMoveableJoint; + + const char* clientMoveableJointName = ent->spawnArgs.GetString( va( "%s_joint", kv->GetKey().c_str() + 4 ) ); + + // use a joint if specified + if ( idStr::Icmp( clientMoveableJointName, "") ) { + clientMoveableJoint = animEnt->GetAnimator()->GetJointHandle( clientMoveableJointName ); + + if ( !animEnt->GetJointWorldTransform( clientMoveableJoint, gameLocal.time, origin, axis ) ) { + gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", va( "%s_joint", kv->GetKey().c_str() + 4 ), clientMoveableJointName, ent->name.c_str() ); + origin = ent->GetPhysics()->GetOrigin(); + axis = ent->GetPhysics()->GetAxis(); + } + } + } + + // spawn the entity + const idDict* entityDef = gameLocal.FindEntityDefDict ( kv->GetValue().c_str(), false ); + + if ( entityDef == NULL ) { + gameLocal.Warning( "%s refers to invalid entity def '%s' on entity '%s'\n", kv->GetKey().c_str(), kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + + rvClientMoveable* newModel = NULL; + // force spawnclass to rvClientMoveable + gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientMoveable" ); + + if( !newModel ) { + gameLocal.Warning( "error spawning client moveable (invalid entity def '%s' on entity '%s')\n", kv->GetValue().c_str(), ent->name.c_str() ); + break; + } + newModel->SetOrigin ( origin ); + newModel->SetAxis( axis ); + + list->Append( newModel ); + kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), kv ); + } +} + diff --git a/src/mpgame/client/ClientMoveable.h b/src/mpgame/client/ClientMoveable.h new file mode 100644 index 0000000..99b2841 --- /dev/null +++ b/src/mpgame/client/ClientMoveable.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// ClientMoveable.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_CLIENT_MOVEABLE_H__ +#define __GAME_CLIENT_MOVEABLE_H__ + +class rvClientMoveable : public rvClientEntity { +public: + + CLASS_PROTOTYPE( rvClientMoveable ); + + rvClientMoveable ( void ); + virtual ~rvClientMoveable ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual idPhysics* GetPhysics ( void ) const; + virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + renderEntity_t* GetRenderEntity ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + static void SpawnClientMoveables ( idEntity* ent, const char *type, idList* list ); + + virtual void FreeEntityDef ( void ); + + void SetOwner ( idEntity* ent ); + + void SetOrigin ( const idVec3& origin ); + void SetAxis ( const idMat3& axis ); +protected: + renderEntity_t renderEntity; + int entityDefHandle; + + rvClientEffectPtr trailEffect; + float trailAttenuateSpeed; + + idPhysics_RigidBody physicsObj; + + int bounceSoundTime; + const idSoundShader* bounceSoundShader; + bool mPlayBounceSoundOnce; + bool mHasBounced; + + idInterpolate scale; + +private: + + void Event_FadeOut ( int duration ); + void Event_ClearDepthHack ( void ); +}; + +ID_INLINE renderEntity_t* rvClientMoveable::GetRenderEntity ( void ) { + return &renderEntity; +} + +extern const idEventDef CL_FadeOut; +extern const idEventDef CL_ClearDepthHack; + +#define SIMPLE_TRI_NAME "simpletri" + +extern idVec3 simpleTri[3]; + + +#endif // __GAME_CLIENT_MOVEABLE_H__ diff --git a/src/mpgame/gamesys/Callbacks.cpp b/src/mpgame/gamesys/Callbacks.cpp new file mode 100644 index 0000000..4c3c929 --- /dev/null +++ b/src/mpgame/gamesys/Callbacks.cpp @@ -0,0 +1,2624 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// generated file - see CREATE_EVENT_CODE + + /******************************************************* + + 1 args + + *******************************************************/ + + case 512 : + typedef void ( idClass::*eventCallback_i_t )( const int ); + ( this->*( eventCallback_i_t )callback )( data[ 0 ] ); + break; + + case 513 : + typedef void ( idClass::*eventCallback_f_t )( const float ); + ( this->*( eventCallback_f_t )callback )( *( float * )&data[ 0 ] ); + break; + + /******************************************************* + + 2 args + + *******************************************************/ + + case 1024 : + typedef void ( idClass::*eventCallback_ii_t )( const int, const int ); + ( this->*( eventCallback_ii_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 1025 : + typedef void ( idClass::*eventCallback_fi_t )( const float, const int ); + ( this->*( eventCallback_fi_t )callback )( *( float * )&data[ 0 ], data[ 1 ] ); + break; + + case 1026 : + typedef void ( idClass::*eventCallback_if_t )( const int, const float ); + ( this->*( eventCallback_if_t )callback )( data[ 0 ], *( float * )&data[ 1 ] ); + break; + + case 1027 : + typedef void ( idClass::*eventCallback_ff_t )( const float, const float ); + ( this->*( eventCallback_ff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ] ); + break; + + /******************************************************* + + 3 args + + *******************************************************/ + + case 2048 : + typedef void ( idClass::*eventCallback_iii_t )( const int, const int, const int ); + ( this->*( eventCallback_iii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2049 : + typedef void ( idClass::*eventCallback_fii_t )( const float, const int, const int ); + ( this->*( eventCallback_fii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 2050 : + typedef void ( idClass::*eventCallback_ifi_t )( const int, const float, const int ); + ( this->*( eventCallback_ifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2051 : + typedef void ( idClass::*eventCallback_ffi_t )( const float, const float, const int ); + ( this->*( eventCallback_ffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ] ); + break; + + case 2052 : + typedef void ( idClass::*eventCallback_iif_t )( const int, const int, const float ); + ( this->*( eventCallback_iif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2053 : + typedef void ( idClass::*eventCallback_fif_t )( const float, const int, const float ); + ( this->*( eventCallback_fif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2054 : + typedef void ( idClass::*eventCallback_iff_t )( const int, const float, const float ); + ( this->*( eventCallback_iff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + case 2055 : + typedef void ( idClass::*eventCallback_fff_t )( const float, const float, const float ); + ( this->*( eventCallback_fff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ] ); + break; + + /******************************************************* + + 4 args + + *******************************************************/ + + case 4096 : + typedef void ( idClass::*eventCallback_iiii_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_iiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4097 : + typedef void ( idClass::*eventCallback_fiii_t )( const float, const int, const int, const int ); + ( this->*( eventCallback_fiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4098 : + typedef void ( idClass::*eventCallback_ifii_t )( const int, const float, const int, const int ); + ( this->*( eventCallback_ifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4099 : + typedef void ( idClass::*eventCallback_ffii_t )( const float, const float, const int, const int ); + ( this->*( eventCallback_ffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 4100 : + typedef void ( idClass::*eventCallback_iifi_t )( const int, const int, const float, const int ); + ( this->*( eventCallback_iifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4101 : + typedef void ( idClass::*eventCallback_fifi_t )( const float, const int, const float, const int ); + ( this->*( eventCallback_fifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4102 : + typedef void ( idClass::*eventCallback_iffi_t )( const int, const float, const float, const int ); + ( this->*( eventCallback_iffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4103 : + typedef void ( idClass::*eventCallback_fffi_t )( const float, const float, const float, const int ); + ( this->*( eventCallback_fffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ] ); + break; + + case 4104 : + typedef void ( idClass::*eventCallback_iiif_t )( const int, const int, const int, const float ); + ( this->*( eventCallback_iiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4105 : + typedef void ( idClass::*eventCallback_fiif_t )( const float, const int, const int, const float ); + ( this->*( eventCallback_fiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4106 : + typedef void ( idClass::*eventCallback_ifif_t )( const int, const float, const int, const float ); + ( this->*( eventCallback_ifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4107 : + typedef void ( idClass::*eventCallback_ffif_t )( const float, const float, const int, const float ); + ( this->*( eventCallback_ffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4108 : + typedef void ( idClass::*eventCallback_iiff_t )( const int, const int, const float, const float ); + ( this->*( eventCallback_iiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4109 : + typedef void ( idClass::*eventCallback_fiff_t )( const float, const int, const float, const float ); + ( this->*( eventCallback_fiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4110 : + typedef void ( idClass::*eventCallback_ifff_t )( const int, const float, const float, const float ); + ( this->*( eventCallback_ifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + case 4111 : + typedef void ( idClass::*eventCallback_ffff_t )( const float, const float, const float, const float ); + ( this->*( eventCallback_ffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ] ); + break; + + /******************************************************* + + 5 args + + *******************************************************/ + + case 8192 : + typedef void ( idClass::*eventCallback_iiiii_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8193 : + typedef void ( idClass::*eventCallback_fiiii_t )( const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8194 : + typedef void ( idClass::*eventCallback_ifiii_t )( const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8195 : + typedef void ( idClass::*eventCallback_ffiii_t )( const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8196 : + typedef void ( idClass::*eventCallback_iifii_t )( const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8197 : + typedef void ( idClass::*eventCallback_fifii_t )( const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8198 : + typedef void ( idClass::*eventCallback_iffii_t )( const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8199 : + typedef void ( idClass::*eventCallback_fffii_t )( const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 8200 : + typedef void ( idClass::*eventCallback_iiifi_t )( const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8201 : + typedef void ( idClass::*eventCallback_fiifi_t )( const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8202 : + typedef void ( idClass::*eventCallback_ififi_t )( const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8203 : + typedef void ( idClass::*eventCallback_ffifi_t )( const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8204 : + typedef void ( idClass::*eventCallback_iiffi_t )( const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8205 : + typedef void ( idClass::*eventCallback_fiffi_t )( const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8206 : + typedef void ( idClass::*eventCallback_ifffi_t )( const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8207 : + typedef void ( idClass::*eventCallback_ffffi_t )( const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ] ); + break; + + case 8208 : + typedef void ( idClass::*eventCallback_iiiif_t )( const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8209 : + typedef void ( idClass::*eventCallback_fiiif_t )( const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8210 : + typedef void ( idClass::*eventCallback_ifiif_t )( const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8211 : + typedef void ( idClass::*eventCallback_ffiif_t )( const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8212 : + typedef void ( idClass::*eventCallback_iifif_t )( const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8213 : + typedef void ( idClass::*eventCallback_fifif_t )( const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8214 : + typedef void ( idClass::*eventCallback_iffif_t )( const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8215 : + typedef void ( idClass::*eventCallback_fffif_t )( const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8216 : + typedef void ( idClass::*eventCallback_iiiff_t )( const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8217 : + typedef void ( idClass::*eventCallback_fiiff_t )( const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8218 : + typedef void ( idClass::*eventCallback_ififf_t )( const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8219 : + typedef void ( idClass::*eventCallback_ffiff_t )( const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8220 : + typedef void ( idClass::*eventCallback_iifff_t )( const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8221 : + typedef void ( idClass::*eventCallback_fifff_t )( const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8222 : + typedef void ( idClass::*eventCallback_iffff_t )( const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + case 8223 : + typedef void ( idClass::*eventCallback_fffff_t )( const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ] ); + break; + + /******************************************************* + + 6 args + + *******************************************************/ + + case 16384 : + typedef void ( idClass::*eventCallback_iiiiii_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16385 : + typedef void ( idClass::*eventCallback_fiiiii_t )( const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16386 : + typedef void ( idClass::*eventCallback_ifiiii_t )( const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16387 : + typedef void ( idClass::*eventCallback_ffiiii_t )( const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16388 : + typedef void ( idClass::*eventCallback_iifiii_t )( const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iifiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16389 : + typedef void ( idClass::*eventCallback_fifiii_t )( const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16390 : + typedef void ( idClass::*eventCallback_iffiii_t )( const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16391 : + typedef void ( idClass::*eventCallback_fffiii_t )( const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16392 : + typedef void ( idClass::*eventCallback_iiifii_t )( const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16393 : + typedef void ( idClass::*eventCallback_fiifii_t )( const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16394 : + typedef void ( idClass::*eventCallback_ififii_t )( const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16395 : + typedef void ( idClass::*eventCallback_ffifii_t )( const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16396 : + typedef void ( idClass::*eventCallback_iiffii_t )( const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16397 : + typedef void ( idClass::*eventCallback_fiffii_t )( const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16398 : + typedef void ( idClass::*eventCallback_ifffii_t )( const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16399 : + typedef void ( idClass::*eventCallback_ffffii_t )( const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 16400 : + typedef void ( idClass::*eventCallback_iiiifi_t )( const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16401 : + typedef void ( idClass::*eventCallback_fiiifi_t )( const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16402 : + typedef void ( idClass::*eventCallback_ifiifi_t )( const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16403 : + typedef void ( idClass::*eventCallback_ffiifi_t )( const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16404 : + typedef void ( idClass::*eventCallback_iififi_t )( const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16405 : + typedef void ( idClass::*eventCallback_fififi_t )( const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16406 : + typedef void ( idClass::*eventCallback_iffifi_t )( const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16407 : + typedef void ( idClass::*eventCallback_fffifi_t )( const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16408 : + typedef void ( idClass::*eventCallback_iiiffi_t )( const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16409 : + typedef void ( idClass::*eventCallback_fiiffi_t )( const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16410 : + typedef void ( idClass::*eventCallback_ififfi_t )( const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16411 : + typedef void ( idClass::*eventCallback_ffiffi_t )( const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16412 : + typedef void ( idClass::*eventCallback_iifffi_t )( const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16413 : + typedef void ( idClass::*eventCallback_fifffi_t )( const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16414 : + typedef void ( idClass::*eventCallback_iffffi_t )( const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16415 : + typedef void ( idClass::*eventCallback_fffffi_t )( const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ] ); + break; + + case 16416 : + typedef void ( idClass::*eventCallback_iiiiif_t )( const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16417 : + typedef void ( idClass::*eventCallback_fiiiif_t )( const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16418 : + typedef void ( idClass::*eventCallback_ifiiif_t )( const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16419 : + typedef void ( idClass::*eventCallback_ffiiif_t )( const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16420 : + typedef void ( idClass::*eventCallback_iifiif_t )( const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iifiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16421 : + typedef void ( idClass::*eventCallback_fifiif_t )( const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16422 : + typedef void ( idClass::*eventCallback_iffiif_t )( const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16423 : + typedef void ( idClass::*eventCallback_fffiif_t )( const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16424 : + typedef void ( idClass::*eventCallback_iiifif_t )( const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16425 : + typedef void ( idClass::*eventCallback_fiifif_t )( const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16426 : + typedef void ( idClass::*eventCallback_ififif_t )( const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16427 : + typedef void ( idClass::*eventCallback_ffifif_t )( const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16428 : + typedef void ( idClass::*eventCallback_iiffif_t )( const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16429 : + typedef void ( idClass::*eventCallback_fiffif_t )( const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16430 : + typedef void ( idClass::*eventCallback_ifffif_t )( const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16431 : + typedef void ( idClass::*eventCallback_ffffif_t )( const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16432 : + typedef void ( idClass::*eventCallback_iiiiff_t )( const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16433 : + typedef void ( idClass::*eventCallback_fiiiff_t )( const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16434 : + typedef void ( idClass::*eventCallback_ifiiff_t )( const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16435 : + typedef void ( idClass::*eventCallback_ffiiff_t )( const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16436 : + typedef void ( idClass::*eventCallback_iififf_t )( const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16437 : + typedef void ( idClass::*eventCallback_fififf_t )( const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16438 : + typedef void ( idClass::*eventCallback_iffiff_t )( const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16439 : + typedef void ( idClass::*eventCallback_fffiff_t )( const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16440 : + typedef void ( idClass::*eventCallback_iiifff_t )( const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16441 : + typedef void ( idClass::*eventCallback_fiifff_t )( const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16442 : + typedef void ( idClass::*eventCallback_ififff_t )( const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16443 : + typedef void ( idClass::*eventCallback_ffifff_t )( const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16444 : + typedef void ( idClass::*eventCallback_iiffff_t )( const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16445 : + typedef void ( idClass::*eventCallback_fiffff_t )( const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16446 : + typedef void ( idClass::*eventCallback_ifffff_t )( const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + case 16447 : + typedef void ( idClass::*eventCallback_ffffff_t )( const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ] ); + break; + + /******************************************************* + + 7 args + + *******************************************************/ + + case 32768 : + typedef void ( idClass::*eventCallback_iiiiiii_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32769 : + typedef void ( idClass::*eventCallback_fiiiiii_t )( const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32770 : + typedef void ( idClass::*eventCallback_ifiiiii_t )( const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32771 : + typedef void ( idClass::*eventCallback_ffiiiii_t )( const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32772 : + typedef void ( idClass::*eventCallback_iifiiii_t )( const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32773 : + typedef void ( idClass::*eventCallback_fifiiii_t )( const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32774 : + typedef void ( idClass::*eventCallback_iffiiii_t )( const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32775 : + typedef void ( idClass::*eventCallback_fffiiii_t )( const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32776 : + typedef void ( idClass::*eventCallback_iiifiii_t )( const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32777 : + typedef void ( idClass::*eventCallback_fiifiii_t )( const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32778 : + typedef void ( idClass::*eventCallback_ififiii_t )( const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ififiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32779 : + typedef void ( idClass::*eventCallback_ffifiii_t )( const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32780 : + typedef void ( idClass::*eventCallback_iiffiii_t )( const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32781 : + typedef void ( idClass::*eventCallback_fiffiii_t )( const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32782 : + typedef void ( idClass::*eventCallback_ifffiii_t )( const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ifffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32783 : + typedef void ( idClass::*eventCallback_ffffiii_t )( const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32784 : + typedef void ( idClass::*eventCallback_iiiifii_t )( const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32785 : + typedef void ( idClass::*eventCallback_fiiifii_t )( const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32786 : + typedef void ( idClass::*eventCallback_ifiifii_t )( const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32787 : + typedef void ( idClass::*eventCallback_ffiifii_t )( const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32788 : + typedef void ( idClass::*eventCallback_iififii_t )( const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iififii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32789 : + typedef void ( idClass::*eventCallback_fififii_t )( const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32790 : + typedef void ( idClass::*eventCallback_iffifii_t )( const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32791 : + typedef void ( idClass::*eventCallback_fffifii_t )( const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32792 : + typedef void ( idClass::*eventCallback_iiiffii_t )( const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32793 : + typedef void ( idClass::*eventCallback_fiiffii_t )( const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32794 : + typedef void ( idClass::*eventCallback_ififfii_t )( const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ififfii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32795 : + typedef void ( idClass::*eventCallback_ffiffii_t )( const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32796 : + typedef void ( idClass::*eventCallback_iifffii_t )( const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iifffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32797 : + typedef void ( idClass::*eventCallback_fifffii_t )( const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32798 : + typedef void ( idClass::*eventCallback_iffffii_t )( const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32799 : + typedef void ( idClass::*eventCallback_fffffii_t )( const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 32800 : + typedef void ( idClass::*eventCallback_iiiiifi_t )( const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32801 : + typedef void ( idClass::*eventCallback_fiiiifi_t )( const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32802 : + typedef void ( idClass::*eventCallback_ifiiifi_t )( const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32803 : + typedef void ( idClass::*eventCallback_ffiiifi_t )( const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32804 : + typedef void ( idClass::*eventCallback_iifiifi_t )( const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32805 : + typedef void ( idClass::*eventCallback_fifiifi_t )( const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32806 : + typedef void ( idClass::*eventCallback_iffiifi_t )( const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32807 : + typedef void ( idClass::*eventCallback_fffiifi_t )( const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32808 : + typedef void ( idClass::*eventCallback_iiififi_t )( const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32809 : + typedef void ( idClass::*eventCallback_fiififi_t )( const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32810 : + typedef void ( idClass::*eventCallback_ifififi_t )( const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32811 : + typedef void ( idClass::*eventCallback_ffififi_t )( const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32812 : + typedef void ( idClass::*eventCallback_iiffifi_t )( const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32813 : + typedef void ( idClass::*eventCallback_fiffifi_t )( const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32814 : + typedef void ( idClass::*eventCallback_ifffifi_t )( const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ifffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32815 : + typedef void ( idClass::*eventCallback_ffffifi_t )( const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32816 : + typedef void ( idClass::*eventCallback_iiiiffi_t )( const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32817 : + typedef void ( idClass::*eventCallback_fiiiffi_t )( const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32818 : + typedef void ( idClass::*eventCallback_ifiiffi_t )( const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32819 : + typedef void ( idClass::*eventCallback_ffiiffi_t )( const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32820 : + typedef void ( idClass::*eventCallback_iififfi_t )( const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iififfi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32821 : + typedef void ( idClass::*eventCallback_fififfi_t )( const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32822 : + typedef void ( idClass::*eventCallback_iffiffi_t )( const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32823 : + typedef void ( idClass::*eventCallback_fffiffi_t )( const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32824 : + typedef void ( idClass::*eventCallback_iiifffi_t )( const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32825 : + typedef void ( idClass::*eventCallback_fiifffi_t )( const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32826 : + typedef void ( idClass::*eventCallback_ififffi_t )( const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ififffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32827 : + typedef void ( idClass::*eventCallback_ffifffi_t )( const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32828 : + typedef void ( idClass::*eventCallback_iiffffi_t )( const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32829 : + typedef void ( idClass::*eventCallback_fiffffi_t )( const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32830 : + typedef void ( idClass::*eventCallback_ifffffi_t )( const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ifffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32831 : + typedef void ( idClass::*eventCallback_ffffffi_t )( const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ] ); + break; + + case 32832 : + typedef void ( idClass::*eventCallback_iiiiiif_t )( const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32833 : + typedef void ( idClass::*eventCallback_fiiiiif_t )( const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32834 : + typedef void ( idClass::*eventCallback_ifiiiif_t )( const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32835 : + typedef void ( idClass::*eventCallback_ffiiiif_t )( const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32836 : + typedef void ( idClass::*eventCallback_iifiiif_t )( const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32837 : + typedef void ( idClass::*eventCallback_fifiiif_t )( const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32838 : + typedef void ( idClass::*eventCallback_iffiiif_t )( const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32839 : + typedef void ( idClass::*eventCallback_fffiiif_t )( const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32840 : + typedef void ( idClass::*eventCallback_iiifiif_t )( const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32841 : + typedef void ( idClass::*eventCallback_fiifiif_t )( const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32842 : + typedef void ( idClass::*eventCallback_ififiif_t )( const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ififiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32843 : + typedef void ( idClass::*eventCallback_ffifiif_t )( const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32844 : + typedef void ( idClass::*eventCallback_iiffiif_t )( const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32845 : + typedef void ( idClass::*eventCallback_fiffiif_t )( const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32846 : + typedef void ( idClass::*eventCallback_ifffiif_t )( const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ifffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32847 : + typedef void ( idClass::*eventCallback_ffffiif_t )( const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32848 : + typedef void ( idClass::*eventCallback_iiiifif_t )( const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32849 : + typedef void ( idClass::*eventCallback_fiiifif_t )( const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32850 : + typedef void ( idClass::*eventCallback_ifiifif_t )( const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32851 : + typedef void ( idClass::*eventCallback_ffiifif_t )( const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32852 : + typedef void ( idClass::*eventCallback_iififif_t )( const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iififif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32853 : + typedef void ( idClass::*eventCallback_fififif_t )( const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32854 : + typedef void ( idClass::*eventCallback_iffifif_t )( const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32855 : + typedef void ( idClass::*eventCallback_fffifif_t )( const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32856 : + typedef void ( idClass::*eventCallback_iiiffif_t )( const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32857 : + typedef void ( idClass::*eventCallback_fiiffif_t )( const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32858 : + typedef void ( idClass::*eventCallback_ififfif_t )( const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ififfif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32859 : + typedef void ( idClass::*eventCallback_ffiffif_t )( const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32860 : + typedef void ( idClass::*eventCallback_iifffif_t )( const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iifffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32861 : + typedef void ( idClass::*eventCallback_fifffif_t )( const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32862 : + typedef void ( idClass::*eventCallback_iffffif_t )( const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32863 : + typedef void ( idClass::*eventCallback_fffffif_t )( const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32864 : + typedef void ( idClass::*eventCallback_iiiiiff_t )( const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32865 : + typedef void ( idClass::*eventCallback_fiiiiff_t )( const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32866 : + typedef void ( idClass::*eventCallback_ifiiiff_t )( const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32867 : + typedef void ( idClass::*eventCallback_ffiiiff_t )( const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32868 : + typedef void ( idClass::*eventCallback_iifiiff_t )( const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32869 : + typedef void ( idClass::*eventCallback_fifiiff_t )( const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32870 : + typedef void ( idClass::*eventCallback_iffiiff_t )( const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32871 : + typedef void ( idClass::*eventCallback_fffiiff_t )( const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32872 : + typedef void ( idClass::*eventCallback_iiififf_t )( const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32873 : + typedef void ( idClass::*eventCallback_fiififf_t )( const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32874 : + typedef void ( idClass::*eventCallback_ifififf_t )( const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32875 : + typedef void ( idClass::*eventCallback_ffififf_t )( const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32876 : + typedef void ( idClass::*eventCallback_iiffiff_t )( const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32877 : + typedef void ( idClass::*eventCallback_fiffiff_t )( const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32878 : + typedef void ( idClass::*eventCallback_ifffiff_t )( const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ifffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32879 : + typedef void ( idClass::*eventCallback_ffffiff_t )( const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32880 : + typedef void ( idClass::*eventCallback_iiiifff_t )( const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32881 : + typedef void ( idClass::*eventCallback_fiiifff_t )( const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32882 : + typedef void ( idClass::*eventCallback_ifiifff_t )( const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32883 : + typedef void ( idClass::*eventCallback_ffiifff_t )( const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32884 : + typedef void ( idClass::*eventCallback_iififff_t )( const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iififff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32885 : + typedef void ( idClass::*eventCallback_fififff_t )( const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32886 : + typedef void ( idClass::*eventCallback_iffifff_t )( const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32887 : + typedef void ( idClass::*eventCallback_fffifff_t )( const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32888 : + typedef void ( idClass::*eventCallback_iiiffff_t )( const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32889 : + typedef void ( idClass::*eventCallback_fiiffff_t )( const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32890 : + typedef void ( idClass::*eventCallback_ififfff_t )( const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ififfff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32891 : + typedef void ( idClass::*eventCallback_ffiffff_t )( const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32892 : + typedef void ( idClass::*eventCallback_iifffff_t )( const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iifffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32893 : + typedef void ( idClass::*eventCallback_fifffff_t )( const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32894 : + typedef void ( idClass::*eventCallback_iffffff_t )( const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + case 32895 : + typedef void ( idClass::*eventCallback_fffffff_t )( const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ] ); + break; + + /******************************************************* + + 8 args + + *******************************************************/ + + case 65536 : + typedef void ( idClass::*eventCallback_iiiiiiii_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iiiiiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65537 : + typedef void ( idClass::*eventCallback_fiiiiiii_t )( const float, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fiiiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65538 : + typedef void ( idClass::*eventCallback_ifiiiiii_t )( const int, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ifiiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65539 : + typedef void ( idClass::*eventCallback_ffiiiiii_t )( const float, const float, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_ffiiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65540 : + typedef void ( idClass::*eventCallback_iifiiiii_t )( const int, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iifiiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65541 : + typedef void ( idClass::*eventCallback_fifiiiii_t )( const float, const int, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fifiiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65542 : + typedef void ( idClass::*eventCallback_iffiiiii_t )( const int, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_iffiiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65543 : + typedef void ( idClass::*eventCallback_fffiiiii_t )( const float, const float, const float, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_fffiiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65544 : + typedef void ( idClass::*eventCallback_iiifiiii_t )( const int, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiifiiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65545 : + typedef void ( idClass::*eventCallback_fiifiiii_t )( const float, const int, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiifiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65546 : + typedef void ( idClass::*eventCallback_ififiiii_t )( const int, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ififiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65547 : + typedef void ( idClass::*eventCallback_ffifiiii_t )( const float, const float, const int, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffifiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65548 : + typedef void ( idClass::*eventCallback_iiffiiii_t )( const int, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_iiffiiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65549 : + typedef void ( idClass::*eventCallback_fiffiiii_t )( const float, const int, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_fiffiiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65550 : + typedef void ( idClass::*eventCallback_ifffiiii_t )( const int, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ifffiiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65551 : + typedef void ( idClass::*eventCallback_ffffiiii_t )( const float, const float, const float, const float, const int, const int, const int, const int ); + ( this->*( eventCallback_ffffiiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65552 : + typedef void ( idClass::*eventCallback_iiiifiii_t )( const int, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiifiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65553 : + typedef void ( idClass::*eventCallback_fiiifiii_t )( const float, const int, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiifiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65554 : + typedef void ( idClass::*eventCallback_ifiifiii_t )( const int, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ifiifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65555 : + typedef void ( idClass::*eventCallback_ffiifiii_t )( const float, const float, const int, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65556 : + typedef void ( idClass::*eventCallback_iififiii_t )( const int, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iififiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65557 : + typedef void ( idClass::*eventCallback_fififiii_t )( const float, const int, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fififiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65558 : + typedef void ( idClass::*eventCallback_iffifiii_t )( const int, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_iffifiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65559 : + typedef void ( idClass::*eventCallback_fffifiii_t )( const float, const float, const float, const int, const float, const int, const int, const int ); + ( this->*( eventCallback_fffifiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65560 : + typedef void ( idClass::*eventCallback_iiiffiii_t )( const int, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iiiffiii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65561 : + typedef void ( idClass::*eventCallback_fiiffiii_t )( const float, const int, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fiiffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65562 : + typedef void ( idClass::*eventCallback_ififfiii_t )( const int, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ififfiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65563 : + typedef void ( idClass::*eventCallback_ffiffiii_t )( const float, const float, const int, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_ffiffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65564 : + typedef void ( idClass::*eventCallback_iifffiii_t )( const int, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iifffiii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65565 : + typedef void ( idClass::*eventCallback_fifffiii_t )( const float, const int, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fifffiii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65566 : + typedef void ( idClass::*eventCallback_iffffiii_t )( const int, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_iffffiii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65567 : + typedef void ( idClass::*eventCallback_fffffiii_t )( const float, const float, const float, const float, const float, const int, const int, const int ); + ( this->*( eventCallback_fffffiii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65568 : + typedef void ( idClass::*eventCallback_iiiiifii_t )( const int, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iiiiifii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65569 : + typedef void ( idClass::*eventCallback_fiiiifii_t )( const float, const int, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fiiiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65570 : + typedef void ( idClass::*eventCallback_ifiiifii_t )( const int, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ifiiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65571 : + typedef void ( idClass::*eventCallback_ffiiifii_t )( const float, const float, const int, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_ffiiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65572 : + typedef void ( idClass::*eventCallback_iifiifii_t )( const int, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iifiifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65573 : + typedef void ( idClass::*eventCallback_fifiifii_t )( const float, const int, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fifiifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65574 : + typedef void ( idClass::*eventCallback_iffiifii_t )( const int, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_iffiifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65575 : + typedef void ( idClass::*eventCallback_fffiifii_t )( const float, const float, const float, const int, const int, const float, const int, const int ); + ( this->*( eventCallback_fffiifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65576 : + typedef void ( idClass::*eventCallback_iiififii_t )( const int, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiififii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65577 : + typedef void ( idClass::*eventCallback_fiififii_t )( const float, const int, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiififii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65578 : + typedef void ( idClass::*eventCallback_ifififii_t )( const int, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifififii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65579 : + typedef void ( idClass::*eventCallback_ffififii_t )( const float, const float, const int, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffififii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65580 : + typedef void ( idClass::*eventCallback_iiffifii_t )( const int, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_iiffifii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65581 : + typedef void ( idClass::*eventCallback_fiffifii_t )( const float, const int, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_fiffifii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65582 : + typedef void ( idClass::*eventCallback_ifffifii_t )( const int, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ifffifii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65583 : + typedef void ( idClass::*eventCallback_ffffifii_t )( const float, const float, const float, const float, const int, const float, const int, const int ); + ( this->*( eventCallback_ffffifii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65584 : + typedef void ( idClass::*eventCallback_iiiiffii_t )( const int, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iiiiffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65585 : + typedef void ( idClass::*eventCallback_fiiiffii_t )( const float, const int, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fiiiffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65586 : + typedef void ( idClass::*eventCallback_ifiiffii_t )( const int, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ifiiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65587 : + typedef void ( idClass::*eventCallback_ffiiffii_t )( const float, const float, const int, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_ffiiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65588 : + typedef void ( idClass::*eventCallback_iififfii_t )( const int, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iififfii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65589 : + typedef void ( idClass::*eventCallback_fififfii_t )( const float, const int, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fififfii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65590 : + typedef void ( idClass::*eventCallback_iffiffii_t )( const int, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_iffiffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65591 : + typedef void ( idClass::*eventCallback_fffiffii_t )( const float, const float, const float, const int, const float, const float, const int, const int ); + ( this->*( eventCallback_fffiffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65592 : + typedef void ( idClass::*eventCallback_iiifffii_t )( const int, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiifffii_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65593 : + typedef void ( idClass::*eventCallback_fiifffii_t )( const float, const int, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiifffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65594 : + typedef void ( idClass::*eventCallback_ififffii_t )( const int, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ififffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65595 : + typedef void ( idClass::*eventCallback_ffifffii_t )( const float, const float, const int, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffifffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65596 : + typedef void ( idClass::*eventCallback_iiffffii_t )( const int, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_iiffffii_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65597 : + typedef void ( idClass::*eventCallback_fiffffii_t )( const float, const int, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_fiffffii_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65598 : + typedef void ( idClass::*eventCallback_ifffffii_t )( const int, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ifffffii_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65599 : + typedef void ( idClass::*eventCallback_ffffffii_t )( const float, const float, const float, const float, const float, const float, const int, const int ); + ( this->*( eventCallback_ffffffii_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + case 65600 : + typedef void ( idClass::*eventCallback_iiiiiifi_t )( const int, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iiiiiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65601 : + typedef void ( idClass::*eventCallback_fiiiiifi_t )( const float, const int, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fiiiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65602 : + typedef void ( idClass::*eventCallback_ifiiiifi_t )( const int, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ifiiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65603 : + typedef void ( idClass::*eventCallback_ffiiiifi_t )( const float, const float, const int, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_ffiiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65604 : + typedef void ( idClass::*eventCallback_iifiiifi_t )( const int, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iifiiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65605 : + typedef void ( idClass::*eventCallback_fifiiifi_t )( const float, const int, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fifiiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65606 : + typedef void ( idClass::*eventCallback_iffiiifi_t )( const int, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_iffiiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65607 : + typedef void ( idClass::*eventCallback_fffiiifi_t )( const float, const float, const float, const int, const int, const int, const float, const int ); + ( this->*( eventCallback_fffiiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65608 : + typedef void ( idClass::*eventCallback_iiifiifi_t )( const int, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiifiifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65609 : + typedef void ( idClass::*eventCallback_fiifiifi_t )( const float, const int, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiifiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65610 : + typedef void ( idClass::*eventCallback_ififiifi_t )( const int, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ififiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65611 : + typedef void ( idClass::*eventCallback_ffifiifi_t )( const float, const float, const int, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffifiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65612 : + typedef void ( idClass::*eventCallback_iiffiifi_t )( const int, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_iiffiifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65613 : + typedef void ( idClass::*eventCallback_fiffiifi_t )( const float, const int, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_fiffiifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65614 : + typedef void ( idClass::*eventCallback_ifffiifi_t )( const int, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ifffiifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65615 : + typedef void ( idClass::*eventCallback_ffffiifi_t )( const float, const float, const float, const float, const int, const int, const float, const int ); + ( this->*( eventCallback_ffffiifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65616 : + typedef void ( idClass::*eventCallback_iiiififi_t )( const int, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiififi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65617 : + typedef void ( idClass::*eventCallback_fiiififi_t )( const float, const int, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65618 : + typedef void ( idClass::*eventCallback_ifiififi_t )( const int, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ifiififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65619 : + typedef void ( idClass::*eventCallback_ffiififi_t )( const float, const float, const int, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65620 : + typedef void ( idClass::*eventCallback_iifififi_t )( const int, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iifififi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65621 : + typedef void ( idClass::*eventCallback_fifififi_t )( const float, const int, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fifififi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65622 : + typedef void ( idClass::*eventCallback_iffififi_t )( const int, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_iffififi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65623 : + typedef void ( idClass::*eventCallback_fffififi_t )( const float, const float, const float, const int, const float, const int, const float, const int ); + ( this->*( eventCallback_fffififi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65624 : + typedef void ( idClass::*eventCallback_iiiffifi_t )( const int, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iiiffifi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65625 : + typedef void ( idClass::*eventCallback_fiiffifi_t )( const float, const int, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fiiffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65626 : + typedef void ( idClass::*eventCallback_ififfifi_t )( const int, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ififfifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65627 : + typedef void ( idClass::*eventCallback_ffiffifi_t )( const float, const float, const int, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_ffiffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65628 : + typedef void ( idClass::*eventCallback_iifffifi_t )( const int, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iifffifi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65629 : + typedef void ( idClass::*eventCallback_fifffifi_t )( const float, const int, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fifffifi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65630 : + typedef void ( idClass::*eventCallback_iffffifi_t )( const int, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_iffffifi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65631 : + typedef void ( idClass::*eventCallback_fffffifi_t )( const float, const float, const float, const float, const float, const int, const float, const int ); + ( this->*( eventCallback_fffffifi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65632 : + typedef void ( idClass::*eventCallback_iiiiiffi_t )( const int, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iiiiiffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65633 : + typedef void ( idClass::*eventCallback_fiiiiffi_t )( const float, const int, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fiiiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65634 : + typedef void ( idClass::*eventCallback_ifiiiffi_t )( const int, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ifiiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65635 : + typedef void ( idClass::*eventCallback_ffiiiffi_t )( const float, const float, const int, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_ffiiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65636 : + typedef void ( idClass::*eventCallback_iifiiffi_t )( const int, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iifiiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65637 : + typedef void ( idClass::*eventCallback_fifiiffi_t )( const float, const int, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fifiiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65638 : + typedef void ( idClass::*eventCallback_iffiiffi_t )( const int, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_iffiiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65639 : + typedef void ( idClass::*eventCallback_fffiiffi_t )( const float, const float, const float, const int, const int, const float, const float, const int ); + ( this->*( eventCallback_fffiiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65640 : + typedef void ( idClass::*eventCallback_iiififfi_t )( const int, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiififfi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65641 : + typedef void ( idClass::*eventCallback_fiififfi_t )( const float, const int, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiififfi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65642 : + typedef void ( idClass::*eventCallback_ifififfi_t )( const int, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifififfi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65643 : + typedef void ( idClass::*eventCallback_ffififfi_t )( const float, const float, const int, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffififfi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65644 : + typedef void ( idClass::*eventCallback_iiffiffi_t )( const int, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_iiffiffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65645 : + typedef void ( idClass::*eventCallback_fiffiffi_t )( const float, const int, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_fiffiffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65646 : + typedef void ( idClass::*eventCallback_ifffiffi_t )( const int, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ifffiffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65647 : + typedef void ( idClass::*eventCallback_ffffiffi_t )( const float, const float, const float, const float, const int, const float, const float, const int ); + ( this->*( eventCallback_ffffiffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65648 : + typedef void ( idClass::*eventCallback_iiiifffi_t )( const int, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiifffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65649 : + typedef void ( idClass::*eventCallback_fiiifffi_t )( const float, const int, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiifffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65650 : + typedef void ( idClass::*eventCallback_ifiifffi_t )( const int, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ifiifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65651 : + typedef void ( idClass::*eventCallback_ffiifffi_t )( const float, const float, const int, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65652 : + typedef void ( idClass::*eventCallback_iififffi_t )( const int, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iififffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65653 : + typedef void ( idClass::*eventCallback_fififffi_t )( const float, const int, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fififffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65654 : + typedef void ( idClass::*eventCallback_iffifffi_t )( const int, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_iffifffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65655 : + typedef void ( idClass::*eventCallback_fffifffi_t )( const float, const float, const float, const int, const float, const float, const float, const int ); + ( this->*( eventCallback_fffifffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65656 : + typedef void ( idClass::*eventCallback_iiiffffi_t )( const int, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iiiffffi_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65657 : + typedef void ( idClass::*eventCallback_fiiffffi_t )( const float, const int, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fiiffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65658 : + typedef void ( idClass::*eventCallback_ififfffi_t )( const int, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ififfffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65659 : + typedef void ( idClass::*eventCallback_ffiffffi_t )( const float, const float, const int, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_ffiffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65660 : + typedef void ( idClass::*eventCallback_iifffffi_t )( const int, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iifffffi_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65661 : + typedef void ( idClass::*eventCallback_fifffffi_t )( const float, const int, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fifffffi_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65662 : + typedef void ( idClass::*eventCallback_iffffffi_t )( const int, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_iffffffi_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65663 : + typedef void ( idClass::*eventCallback_fffffffi_t )( const float, const float, const float, const float, const float, const float, const float, const int ); + ( this->*( eventCallback_fffffffi_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], data[ 7 ] ); + break; + + case 65664 : + typedef void ( idClass::*eventCallback_iiiiiiif_t )( const int, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iiiiiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65665 : + typedef void ( idClass::*eventCallback_fiiiiiif_t )( const float, const int, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fiiiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65666 : + typedef void ( idClass::*eventCallback_ifiiiiif_t )( const int, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ifiiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65667 : + typedef void ( idClass::*eventCallback_ffiiiiif_t )( const float, const float, const int, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_ffiiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65668 : + typedef void ( idClass::*eventCallback_iifiiiif_t )( const int, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iifiiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65669 : + typedef void ( idClass::*eventCallback_fifiiiif_t )( const float, const int, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fifiiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65670 : + typedef void ( idClass::*eventCallback_iffiiiif_t )( const int, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_iffiiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65671 : + typedef void ( idClass::*eventCallback_fffiiiif_t )( const float, const float, const float, const int, const int, const int, const int, const float ); + ( this->*( eventCallback_fffiiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65672 : + typedef void ( idClass::*eventCallback_iiifiiif_t )( const int, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiifiiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65673 : + typedef void ( idClass::*eventCallback_fiifiiif_t )( const float, const int, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiifiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65674 : + typedef void ( idClass::*eventCallback_ififiiif_t )( const int, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ififiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65675 : + typedef void ( idClass::*eventCallback_ffifiiif_t )( const float, const float, const int, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffifiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65676 : + typedef void ( idClass::*eventCallback_iiffiiif_t )( const int, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_iiffiiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65677 : + typedef void ( idClass::*eventCallback_fiffiiif_t )( const float, const int, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_fiffiiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65678 : + typedef void ( idClass::*eventCallback_ifffiiif_t )( const int, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ifffiiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65679 : + typedef void ( idClass::*eventCallback_ffffiiif_t )( const float, const float, const float, const float, const int, const int, const int, const float ); + ( this->*( eventCallback_ffffiiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65680 : + typedef void ( idClass::*eventCallback_iiiifiif_t )( const int, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiifiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65681 : + typedef void ( idClass::*eventCallback_fiiifiif_t )( const float, const int, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiifiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65682 : + typedef void ( idClass::*eventCallback_ifiifiif_t )( const int, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ifiifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65683 : + typedef void ( idClass::*eventCallback_ffiifiif_t )( const float, const float, const int, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65684 : + typedef void ( idClass::*eventCallback_iififiif_t )( const int, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iififiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65685 : + typedef void ( idClass::*eventCallback_fififiif_t )( const float, const int, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fififiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65686 : + typedef void ( idClass::*eventCallback_iffifiif_t )( const int, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_iffifiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65687 : + typedef void ( idClass::*eventCallback_fffifiif_t )( const float, const float, const float, const int, const float, const int, const int, const float ); + ( this->*( eventCallback_fffifiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65688 : + typedef void ( idClass::*eventCallback_iiiffiif_t )( const int, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iiiffiif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65689 : + typedef void ( idClass::*eventCallback_fiiffiif_t )( const float, const int, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fiiffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65690 : + typedef void ( idClass::*eventCallback_ififfiif_t )( const int, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ififfiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65691 : + typedef void ( idClass::*eventCallback_ffiffiif_t )( const float, const float, const int, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_ffiffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65692 : + typedef void ( idClass::*eventCallback_iifffiif_t )( const int, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iifffiif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65693 : + typedef void ( idClass::*eventCallback_fifffiif_t )( const float, const int, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fifffiif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65694 : + typedef void ( idClass::*eventCallback_iffffiif_t )( const int, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_iffffiif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65695 : + typedef void ( idClass::*eventCallback_fffffiif_t )( const float, const float, const float, const float, const float, const int, const int, const float ); + ( this->*( eventCallback_fffffiif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65696 : + typedef void ( idClass::*eventCallback_iiiiifif_t )( const int, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iiiiifif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65697 : + typedef void ( idClass::*eventCallback_fiiiifif_t )( const float, const int, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fiiiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65698 : + typedef void ( idClass::*eventCallback_ifiiifif_t )( const int, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ifiiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65699 : + typedef void ( idClass::*eventCallback_ffiiifif_t )( const float, const float, const int, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_ffiiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65700 : + typedef void ( idClass::*eventCallback_iifiifif_t )( const int, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iifiifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65701 : + typedef void ( idClass::*eventCallback_fifiifif_t )( const float, const int, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fifiifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65702 : + typedef void ( idClass::*eventCallback_iffiifif_t )( const int, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_iffiifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65703 : + typedef void ( idClass::*eventCallback_fffiifif_t )( const float, const float, const float, const int, const int, const float, const int, const float ); + ( this->*( eventCallback_fffiifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65704 : + typedef void ( idClass::*eventCallback_iiififif_t )( const int, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiififif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65705 : + typedef void ( idClass::*eventCallback_fiififif_t )( const float, const int, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiififif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65706 : + typedef void ( idClass::*eventCallback_ifififif_t )( const int, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifififif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65707 : + typedef void ( idClass::*eventCallback_ffififif_t )( const float, const float, const int, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffififif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65708 : + typedef void ( idClass::*eventCallback_iiffifif_t )( const int, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_iiffifif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65709 : + typedef void ( idClass::*eventCallback_fiffifif_t )( const float, const int, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_fiffifif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65710 : + typedef void ( idClass::*eventCallback_ifffifif_t )( const int, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ifffifif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65711 : + typedef void ( idClass::*eventCallback_ffffifif_t )( const float, const float, const float, const float, const int, const float, const int, const float ); + ( this->*( eventCallback_ffffifif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65712 : + typedef void ( idClass::*eventCallback_iiiiffif_t )( const int, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iiiiffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65713 : + typedef void ( idClass::*eventCallback_fiiiffif_t )( const float, const int, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fiiiffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65714 : + typedef void ( idClass::*eventCallback_ifiiffif_t )( const int, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ifiiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65715 : + typedef void ( idClass::*eventCallback_ffiiffif_t )( const float, const float, const int, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_ffiiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65716 : + typedef void ( idClass::*eventCallback_iififfif_t )( const int, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iififfif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65717 : + typedef void ( idClass::*eventCallback_fififfif_t )( const float, const int, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fififfif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65718 : + typedef void ( idClass::*eventCallback_iffiffif_t )( const int, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_iffiffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65719 : + typedef void ( idClass::*eventCallback_fffiffif_t )( const float, const float, const float, const int, const float, const float, const int, const float ); + ( this->*( eventCallback_fffiffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65720 : + typedef void ( idClass::*eventCallback_iiifffif_t )( const int, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiifffif_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65721 : + typedef void ( idClass::*eventCallback_fiifffif_t )( const float, const int, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiifffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65722 : + typedef void ( idClass::*eventCallback_ififffif_t )( const int, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ififffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65723 : + typedef void ( idClass::*eventCallback_ffifffif_t )( const float, const float, const int, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffifffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65724 : + typedef void ( idClass::*eventCallback_iiffffif_t )( const int, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_iiffffif_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65725 : + typedef void ( idClass::*eventCallback_fiffffif_t )( const float, const int, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_fiffffif_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65726 : + typedef void ( idClass::*eventCallback_ifffffif_t )( const int, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ifffffif_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65727 : + typedef void ( idClass::*eventCallback_ffffffif_t )( const float, const float, const float, const float, const float, const float, const int, const float ); + ( this->*( eventCallback_ffffffif_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65728 : + typedef void ( idClass::*eventCallback_iiiiiiff_t )( const int, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iiiiiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65729 : + typedef void ( idClass::*eventCallback_fiiiiiff_t )( const float, const int, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fiiiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65730 : + typedef void ( idClass::*eventCallback_ifiiiiff_t )( const int, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ifiiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65731 : + typedef void ( idClass::*eventCallback_ffiiiiff_t )( const float, const float, const int, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_ffiiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65732 : + typedef void ( idClass::*eventCallback_iifiiiff_t )( const int, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iifiiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65733 : + typedef void ( idClass::*eventCallback_fifiiiff_t )( const float, const int, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fifiiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65734 : + typedef void ( idClass::*eventCallback_iffiiiff_t )( const int, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_iffiiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65735 : + typedef void ( idClass::*eventCallback_fffiiiff_t )( const float, const float, const float, const int, const int, const int, const float, const float ); + ( this->*( eventCallback_fffiiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65736 : + typedef void ( idClass::*eventCallback_iiifiiff_t )( const int, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiifiiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65737 : + typedef void ( idClass::*eventCallback_fiifiiff_t )( const float, const int, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiifiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65738 : + typedef void ( idClass::*eventCallback_ififiiff_t )( const int, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ififiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65739 : + typedef void ( idClass::*eventCallback_ffifiiff_t )( const float, const float, const int, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffifiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65740 : + typedef void ( idClass::*eventCallback_iiffiiff_t )( const int, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_iiffiiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65741 : + typedef void ( idClass::*eventCallback_fiffiiff_t )( const float, const int, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_fiffiiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65742 : + typedef void ( idClass::*eventCallback_ifffiiff_t )( const int, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ifffiiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65743 : + typedef void ( idClass::*eventCallback_ffffiiff_t )( const float, const float, const float, const float, const int, const int, const float, const float ); + ( this->*( eventCallback_ffffiiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65744 : + typedef void ( idClass::*eventCallback_iiiififf_t )( const int, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiififf_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65745 : + typedef void ( idClass::*eventCallback_fiiififf_t )( const float, const int, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65746 : + typedef void ( idClass::*eventCallback_ifiififf_t )( const int, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ifiififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65747 : + typedef void ( idClass::*eventCallback_ffiififf_t )( const float, const float, const int, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65748 : + typedef void ( idClass::*eventCallback_iifififf_t )( const int, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iifififf_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65749 : + typedef void ( idClass::*eventCallback_fifififf_t )( const float, const int, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fifififf_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65750 : + typedef void ( idClass::*eventCallback_iffififf_t )( const int, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_iffififf_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65751 : + typedef void ( idClass::*eventCallback_fffififf_t )( const float, const float, const float, const int, const float, const int, const float, const float ); + ( this->*( eventCallback_fffififf_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65752 : + typedef void ( idClass::*eventCallback_iiiffiff_t )( const int, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iiiffiff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65753 : + typedef void ( idClass::*eventCallback_fiiffiff_t )( const float, const int, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fiiffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65754 : + typedef void ( idClass::*eventCallback_ififfiff_t )( const int, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ififfiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65755 : + typedef void ( idClass::*eventCallback_ffiffiff_t )( const float, const float, const int, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_ffiffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65756 : + typedef void ( idClass::*eventCallback_iifffiff_t )( const int, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iifffiff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65757 : + typedef void ( idClass::*eventCallback_fifffiff_t )( const float, const int, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fifffiff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65758 : + typedef void ( idClass::*eventCallback_iffffiff_t )( const int, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_iffffiff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65759 : + typedef void ( idClass::*eventCallback_fffffiff_t )( const float, const float, const float, const float, const float, const int, const float, const float ); + ( this->*( eventCallback_fffffiff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65760 : + typedef void ( idClass::*eventCallback_iiiiifff_t )( const int, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iiiiifff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65761 : + typedef void ( idClass::*eventCallback_fiiiifff_t )( const float, const int, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fiiiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65762 : + typedef void ( idClass::*eventCallback_ifiiifff_t )( const int, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ifiiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65763 : + typedef void ( idClass::*eventCallback_ffiiifff_t )( const float, const float, const int, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_ffiiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65764 : + typedef void ( idClass::*eventCallback_iifiifff_t )( const int, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iifiifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65765 : + typedef void ( idClass::*eventCallback_fifiifff_t )( const float, const int, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fifiifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65766 : + typedef void ( idClass::*eventCallback_iffiifff_t )( const int, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_iffiifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65767 : + typedef void ( idClass::*eventCallback_fffiifff_t )( const float, const float, const float, const int, const int, const float, const float, const float ); + ( this->*( eventCallback_fffiifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65768 : + typedef void ( idClass::*eventCallback_iiififff_t )( const int, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiififff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65769 : + typedef void ( idClass::*eventCallback_fiififff_t )( const float, const int, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiififff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65770 : + typedef void ( idClass::*eventCallback_ifififff_t )( const int, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifififff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65771 : + typedef void ( idClass::*eventCallback_ffififff_t )( const float, const float, const int, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffififff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65772 : + typedef void ( idClass::*eventCallback_iiffifff_t )( const int, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_iiffifff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65773 : + typedef void ( idClass::*eventCallback_fiffifff_t )( const float, const int, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_fiffifff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65774 : + typedef void ( idClass::*eventCallback_ifffifff_t )( const int, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ifffifff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65775 : + typedef void ( idClass::*eventCallback_ffffifff_t )( const float, const float, const float, const float, const int, const float, const float, const float ); + ( this->*( eventCallback_ffffifff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65776 : + typedef void ( idClass::*eventCallback_iiiiffff_t )( const int, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iiiiffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65777 : + typedef void ( idClass::*eventCallback_fiiiffff_t )( const float, const int, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fiiiffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65778 : + typedef void ( idClass::*eventCallback_ifiiffff_t )( const int, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ifiiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65779 : + typedef void ( idClass::*eventCallback_ffiiffff_t )( const float, const float, const int, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_ffiiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65780 : + typedef void ( idClass::*eventCallback_iififfff_t )( const int, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iififfff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65781 : + typedef void ( idClass::*eventCallback_fififfff_t )( const float, const int, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fififfff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65782 : + typedef void ( idClass::*eventCallback_iffiffff_t )( const int, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_iffiffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65783 : + typedef void ( idClass::*eventCallback_fffiffff_t )( const float, const float, const float, const int, const float, const float, const float, const float ); + ( this->*( eventCallback_fffiffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65784 : + typedef void ( idClass::*eventCallback_iiifffff_t )( const int, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiifffff_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65785 : + typedef void ( idClass::*eventCallback_fiifffff_t )( const float, const int, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiifffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65786 : + typedef void ( idClass::*eventCallback_ififffff_t )( const int, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ififffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65787 : + typedef void ( idClass::*eventCallback_ffifffff_t )( const float, const float, const int, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffifffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65788 : + typedef void ( idClass::*eventCallback_iiffffff_t )( const int, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_iiffffff_t )callback )( data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65789 : + typedef void ( idClass::*eventCallback_fiffffff_t )( const float, const int, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_fiffffff_t )callback )( *( float * )&data[ 0 ], data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65790 : + typedef void ( idClass::*eventCallback_ifffffff_t )( const int, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ifffffff_t )callback )( data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + + case 65791 : + typedef void ( idClass::*eventCallback_ffffffff_t )( const float, const float, const float, const float, const float, const float, const float, const float ); + ( this->*( eventCallback_ffffffff_t )callback )( *( float * )&data[ 0 ], *( float * )&data[ 1 ], *( float * )&data[ 2 ], *( float * )&data[ 3 ], *( float * )&data[ 4 ], *( float * )&data[ 5 ], *( float * )&data[ 6 ], *( float * )&data[ 7 ] ); + break; + diff --git a/src/mpgame/gamesys/Class.cpp b/src/mpgame/gamesys/Class.cpp new file mode 100644 index 0000000..997eabf --- /dev/null +++ b/src/mpgame/gamesys/Class.cpp @@ -0,0 +1,1458 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Base class for all C++ objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +// this is the head of a singly linked list of all the idTypes +static idTypeInfo *typelist = NULL; +static idHierarchy classHierarchy; +static int eventCallbackMemory = 0; + +/* +================ +idTypeInfo::idClassType() + +Constructor for class. Should only be called from CLASS_DECLARATION macro. +Handles linking class definition into class hierarchy. This should only happen +at startup as idTypeInfos are statically defined. Since static variables can be +initialized in any order, the constructor must handle the case that subclasses +are initialized before superclasses. +================ +*/ +idTypeInfo::idTypeInfo( const char *classname, const char *superclass, idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), +// RAVEN BEGIN +// bdube: added states + void ( idClass::*Spawn )( void ), + rvStateFunc* stateCallbacks, + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ) { +// RAVEN END + + idTypeInfo *type; + idTypeInfo **insert; + + this->classname = classname; + this->superclass = superclass; + this->eventCallbacks = eventCallbacks; + this->eventMap = NULL; + this->Spawn = Spawn; + this->Save = Save; + this->Restore = Restore; + this->CreateInstance = CreateInstance; + this->super = idClass::GetClass( superclass ); + this->freeEventMap = false; + typeNum = 0; + lastChild = 0; + +// RAVEN BEGIN +// bdube: added states + this->stateCallbacks = stateCallbacks; +// RAVEN END + + // Check if any subclasses were initialized before their superclass + for( type = typelist; type != NULL; type = type->next ) { + if ( ( type->super == NULL ) && !idStr::Cmp( type->superclass, this->classname ) && + idStr::Cmp( type->classname, "idClass" ) ) { + type->super = this; + } + } + + // Insert sorted + for ( insert = &typelist; *insert; insert = &(*insert)->next ) { + assert( idStr::Cmp( classname, (*insert)->classname ) ); + if ( idStr::Cmp( classname, (*insert)->classname ) < 0 ) { + next = *insert; + *insert = this; + break; + } + } + if ( !*insert ) { + *insert = this; + next = NULL; + } +} + +/* +================ +idTypeInfo::~idTypeInfo +================ +*/ +idTypeInfo::~idTypeInfo() { + Shutdown(); +} + +/* +================ +idTypeInfo::Init + +Initializes the event callback table for the class. Creates a +table for fast lookups of event functions. Should only be called once. +================ +*/ +void idTypeInfo::Init( void ) { + idTypeInfo *c; + idEventFunc *def; + int ev; + int i; + bool *set; + int num; + + if ( eventMap ) { + // we've already been initialized by a subclass + return; + } + + // make sure our superclass is initialized first + if ( super && !super->eventMap ) { + super->Init(); + } + + // add to our node hierarchy + if ( super ) { + node.ParentTo( super->node ); + } else { + node.ParentTo( classHierarchy ); + } + node.SetOwner( this ); + + // keep track of the number of children below each class + for( c = super; c != NULL; c = c->super ) { + c->lastChild++; + } + + // if we're not adding any new event callbacks, we can just use our superclass's table + if ( ( !eventCallbacks || !eventCallbacks->event ) && super ) { + eventMap = super->eventMap; + return; + } + + // set a flag so we know to delete the eventMap table + freeEventMap = true; + + // Allocate our new table. It has to have as many entries as there + // are events. NOTE: could save some space by keeping track of the maximum + // event that the class responds to and doing range checking. + num = idEventDef::NumEventCommands(); + eventMap = new eventCallback_t[ num ]; + memset( eventMap, 0, sizeof( eventCallback_t ) * num ); + eventCallbackMemory += sizeof( eventCallback_t ) * num; + + // allocate temporary memory for flags so that the subclass's event callbacks + // override the superclass's event callback + set = new bool[ num ]; + memset( set, 0, sizeof( bool ) * num ); + + // go through the inheritence order and copies the event callback function into + // a list indexed by the event number. This allows fast lookups of + // event functions. + for( c = this; c != NULL; c = c->super ) { + def = c->eventCallbacks; + if ( !def ) { + continue; + } + + // go through each entry until we hit the NULL terminator + for( i = 0; def[ i ].event != NULL; i++ ) { + ev = def[ i ].event->GetEventNum(); + + if ( set[ ev ] ) { + continue; + } + set[ ev ] = true; + eventMap[ ev ] = def[ i ].function; + } + } + + delete[] set; +} + +/* +================ +idTypeInfo::Shutdown + +Should only be called when DLL or EXE is being shutdown. +Although it cleans up any allocated memory, it doesn't bother to remove itself +from the class list since the program is shutting down. +================ +*/ +void idTypeInfo::Shutdown() { + // free up the memory used for event lookups + if ( eventMap ) { + if ( freeEventMap ) { + delete[] eventMap; + } + eventMap = NULL; + } + typeNum = 0; + lastChild = 0; +} + + +/*********************************************************************** + + idClass + +***********************************************************************/ + +const idEventDef EV_PostRestore( "", NULL ); +const idEventDef EV_Remove( "", NULL ); +const idEventDef EV_SafeRemove( "remove", NULL ); + +ABSTRACT_DECLARATION( NULL, idClass ) +// RAVEN BEGIN + EVENT( EV_PostRestore, idClass::Event_PostRestore ) +// RAVEN END + EVENT( EV_Remove, idClass::Event_Remove ) + EVENT( EV_SafeRemove, idClass::Event_SafeRemove ) +END_CLASS + +CLASS_STATES_DECLARATION(idClass) +END_CLASS_STATES + +// alphabetical order +idList idClass::types; +// typenum order +idList idClass::typenums; + +bool idClass::initialized = false; +int idClass::typeNumBits = 0; +int idClass::memused = 0; +int idClass::numobjects = 0; + +/* +================ +idClass::CallSpawn +================ +*/ +void idClass::CallSpawn( void ) { + idTypeInfo *type; + + type = GetType(); + CallSpawnFunc( type ); +} + +/* +================ +idClass::CallSpawnFunc +================ +*/ +classSpawnFunc_t idClass::CallSpawnFunc( idTypeInfo *cls ) { + classSpawnFunc_t func; + + if ( cls->super ) { + func = CallSpawnFunc( cls->super ); + if ( func == cls->Spawn ) { + // don't call the same function twice in a row. + // this can happen when subclasses don't have their own spawn function. + return func; + } + } + + // RAVEN BEGIN + // hmmm.... stompage of memory has occured + assert(cls->Spawn != 0); + // RAVEN END + + ( this->*cls->Spawn )(); + + return cls->Spawn; +} + +/* +================ +idClass::FindUninitializedMemory +================ +*/ +void idClass::FindUninitializedMemory( void ) { +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = ( ( unsigned long * )this ) - 1; + int size = *ptr; + assert( ( size & 3 ) == 0 ); + size >>= 2; + for ( int i = 0; i < size; i++ ) { + if ( ptr[i] == 0xcdcdcdcd ) { +// RAVEN BEGIN + gameLocal.Warning( "type '%s' has uninitialized variable (offset %d)", GetClassname(), i << 2 ); +// RAVEN END + } + } +#endif +} + +/* +================ +idClass::Spawn +================ +*/ +void idClass::Spawn( void ) { +} + +/* +================ +idClass::~idClass + +Destructor for object. Cancels any events that depend on this object. +================ +*/ +idClass::~idClass() { + idEvent::CancelEvents( this ); +} + +/* +================ +idClass::DisplayInfo_f +================ +*/ +void idClass::DisplayInfo_f( const idCmdArgs &args ) { + gameLocal.Printf( "Class memory status: %i bytes allocated in %i objects\n", memused, numobjects ); +} + +/* +================ +idClass::ListClasses_f +================ +*/ +void idClass::ListClasses_f( const idCmdArgs &args ) { + int i; + idTypeInfo *type; + + gameLocal.Printf( "%-24s %-24s %-6s %-6s\n", "Classname", "Superclass", "Type", "Subclasses" ); + gameLocal.Printf( "----------------------------------------------------------------------\n" ); + + for( i = 0; i < types.Num(); i++ ) { + type = types[ i ]; + gameLocal.Printf( "%-24s %-24s %6d %6d\n", type->classname, type->superclass, type->typeNum, type->lastChild - type->typeNum ); + } + + gameLocal.Printf( "...%d classes", types.Num() ); +} + +/* +================ +idClass::CreateInstance +================ +*/ +idClass *idClass::CreateInstance( const char *name ) { + const idTypeInfo *type; + idClass *obj; + + type = idClass::GetClass( name ); + if ( !type ) { + return NULL; + } + + obj = type->CreateInstance(); + return obj; +} + +/* +================ +idClass::Init + +Should be called after all idTypeInfos are initialized, so must be called +manually upon game code initialization. Tells all the idTypeInfos to initialize +their event callback table for the associated class. This should only be called +once during the execution of the program or DLL. +================ +*/ +void idClass::Init( void ) { + idTypeInfo *c; + int num; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_CLASS); +// RAVEN END + + gameLocal.Printf( "Initializing class hierarchy\n" ); + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + return; + } + + // init the event callback tables for all the classes + for( c = typelist; c != NULL; c = c->next ) { +// RAVEN BEGIN +// jnewquist: Make sure the superclass was actually registered! + if ( c->super == NULL && (c->superclass && idStr::Cmp(c->superclass, "NULL")) ) { + common->Error("Superclass %s of %s was never registered!", c->superclass, c->classname); + } +// RAVEN END + c->Init(); + } + + // number the types according to the class hierarchy so we can quickly determine if a class + // is a subclass of another + num = 0; + for( c = classHierarchy.GetNext(); c != NULL; c = c->node.GetNext(), num++ ) { + c->typeNum = num; + c->lastChild += num; + } + + // number of bits needed to send types over network + typeNumBits = idMath::BitsForInteger( num ); + + // create a list of the types so we can do quick lookups + // one list in alphabetical order, one in typenum order + types.SetGranularity( 1 ); + types.SetNum( num ); + typenums.SetGranularity( 1 ); + typenums.SetNum( num ); + num = 0; + for( c = typelist; c != NULL; c = c->next, num++ ) { + types[ num ] = c; + typenums[ c->typeNum ] = c; + } + + initialized = true; + + gameLocal.Printf( "...%i classes, %i bytes for event callbacks\n", types.Num(), eventCallbackMemory ); +} + +/* +================ +idClass::Shutdown +================ +*/ +void idClass::Shutdown( void ) { + idTypeInfo *c; + + for( c = typelist; c != NULL; c = c->next ) { + c->Shutdown(); + } + types.Clear(); + typenums.Clear(); + + initialized = false; +} + +/* +================ +idClass::new +================ +*/ +#ifdef ID_DEBUG_MEMORY +#undef new +#endif + +void * idClass::operator new( size_t s ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +void * idClass::operator new( size_t s, int, int, char *, int ) { + int *p; + + s += sizeof( int ); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + p = (int *)Mem_Alloc( s, MA_CLASS ); +//RAVEN END + *p = s; + memused += s; + numobjects++; + +#ifdef ID_DEBUG_MEMORY + unsigned long *ptr = (unsigned long *)p; + int size = s; + assert( ( size & 3 ) == 0 ); + size >>= 3; + for ( int i = 1; i < size; i++ ) { + ptr[i] = 0xcdcdcdcd; + } +#endif + + return p + 1; +} + +#ifdef ID_DEBUG_MEMORY +#define new ID_DEBUG_NEW +#endif + +/* +================ +idClass::delete +================ +*/ +void idClass::operator delete( void *ptr ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +void idClass::operator delete( void *ptr, int, int, char *, int ) { + int *p; + + if ( ptr ) { + p = ( ( int * )ptr ) - 1; + memused -= *p; + numobjects--; + Mem_Free( p ); + } +} + +/* +================ +idClass::GetClass + +Returns the idTypeInfo for the name of the class passed in. This is a static function +so it must be called as idClass::GetClass( classname ) +================ +*/ +idTypeInfo *idClass::GetClass( const char *name ) { + idTypeInfo *c; + int order; + int mid; + int min; + int max; + + if ( !initialized ) { + // idClass::Init hasn't been called yet, so do a slow lookup + for( c = typelist; c != NULL; c = c->next ) { + if ( !idStr::Cmp( c->classname, name ) ) { + return c; + } + } + } else { + // do a binary search through the list of types + min = 0; + max = types.Num() - 1; + while( min <= max ) { + mid = ( min + max ) / 2; + c = types[ mid ]; + order = idStr::Cmp( c->classname, name ); + if ( !order ) { + return c; + } else if ( order > 0 ) { + max = mid - 1; + } else { + min = mid + 1; + } + } + } + + return NULL; +} + +/* +================ +idClass::GetType +================ +*/ +idTypeInfo *idClass::GetType( const int typeNum ) { + idTypeInfo *c; + + if ( !initialized ) { + for( c = typelist; c != NULL; c = c->next ) { + if ( c->typeNum == typeNum ) { + return c; + } + } + } else if ( ( typeNum >= 0 ) && ( typeNum < types.Num() ) ) { + return typenums[ typeNum ]; + } + + return NULL; +} + +/* +================ +idClass::GetClassname + +Returns the text classname of the object. +================ +*/ +const char *idClass::GetClassname( void ) const { + idTypeInfo *type; + + type = GetType(); + return type->classname; +} + +/* +================ +idClass::GetSuperclass + +Returns the text classname of the superclass. +================ +*/ +const char *idClass::GetSuperclass( void ) const { + idTypeInfo *cls; + + cls = GetType(); + return cls->superclass; +} + +/* +================ +idClass::CancelEvents +================ +*/ +void idClass::CancelEvents( const idEventDef *ev ) { + idEvent::CancelEvents( this, ev ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idClass::EventIsPosted +================ +*/ +bool idClass::EventIsPosted( const idEventDef *ev ) const { + return idEvent::EventIsPosted( this, ev ); +} +// RAVEN END + +/* +================ +idClass::PostEventArgs +================ +*/ +bool idClass::PostEventArgs( const idEventDef *ev, int time, int numargs, ... ) { + idTypeInfo *c; + idEvent *event; + va_list args; + + assert( ev ); + + if ( !idEvent::initialized ) { + return false; + } + + c = GetType(); + if ( !c->eventMap[ ev->GetEventNum() ] ) { + // we don't respond to this event, so ignore it + return false; + } + + // we service events on the client to avoid any bad code filling up the event pool + // we don't want them processed usually, unless when the map is (re)loading. + // we allow threads to run fine, though. +// RAVEN BEGIN +// bdube: added a check to see if this is a client entity +// jnewquist: Use accessor for static class type + bool isClient = !IsClient() && gameLocal.isClient; + +#ifdef _XENON + // nrausch: We want to allow selectWeapon to pass through, but that's all + if ( idStr::Cmp( ev->GetName(), "selectWeapon" ) == 0 ) { + isClient = false; + } +#endif + + if( isClient && ( gameLocal.GameState() != GAMESTATE_STARTUP ) && ( gameLocal.GameState() != GAMESTATE_RESTART ) && !IsType( idThread::GetClassType() ) ) { +// RAVEN END + return true; + } + + va_start( args, numargs ); + event = idEvent::Alloc( ev, numargs, args ); + va_end( args ); + + event->Schedule( this, c, time ); + + return true; +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time ) { + return PostEventArgs( ev, time, 0 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ) { + return PostEventArgs( ev, time, 1, &arg1 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, time, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, time, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, time, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, time, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, time, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, time, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventMS +================ +*/ +bool idClass::PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, time, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time ) { + return PostEventArgs( ev, SEC2MS( time ), 0 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ) { + return PostEventArgs( ev, SEC2MS( time ), 1, &arg1 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ) { + return PostEventArgs( ev, SEC2MS( time ), 2, &arg1, &arg2 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return PostEventArgs( ev, SEC2MS( time ), 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return PostEventArgs( ev, SEC2MS( time ), 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return PostEventArgs( ev, SEC2MS( time ), 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return PostEventArgs( ev, SEC2MS( time ), 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return PostEventArgs( ev, SEC2MS( time ), 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::PostEventSec +================ +*/ +bool idClass::PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return PostEventArgs( ev, SEC2MS( time ), 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgs +================ +*/ +bool idClass::ProcessEventArgs( const idEventDef *ev, int numargs, ... ) { + idTypeInfo *c; + int num; + int data[ D_EVENT_MAXARGS ]; + va_list args; + + assert( ev ); + assert( idEvent::initialized ); + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + va_start( args, numargs ); + idEvent::CopyArgs( ev, numargs, args, data ); + va_end( args ); + + ProcessEventArgPtr( ev, data ); + + return true; +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev ) { + return ProcessEventArgs( ev, 0 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1 ) { + return ProcessEventArgs( ev, 1, &arg1 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ) { + return ProcessEventArgs( ev, 2, &arg1, &arg2 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ) { + return ProcessEventArgs( ev, 3, &arg1, &arg2, &arg3 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ) { + return ProcessEventArgs( ev, 4, &arg1, &arg2, &arg3, &arg4 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ) { + return ProcessEventArgs( ev, 5, &arg1, &arg2, &arg3, &arg4, &arg5 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ) { + return ProcessEventArgs( ev, 6, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ) { + return ProcessEventArgs( ev, 7, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7 ); +} + +/* +================ +idClass::ProcessEvent +================ +*/ +bool idClass::ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ) { + return ProcessEventArgs( ev, 8, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arg7, &arg8 ); +} + +/* +================ +idClass::ProcessEventArgPtr +================ +*/ +bool idClass::ProcessEventArgPtr( const idEventDef *ev, int *data ) { + idTypeInfo *c; + int num; + eventCallback_t callback; + + assert( ev ); + assert( idEvent::initialized ); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( g_debugTriggers.GetBool() && ( ev == &EV_Activate ) && IsType( idEntity::GetClassType() ) ) { +// RAVEN END + const idEntity *ent = *reinterpret_cast( data ); + gameLocal.Printf( "%d: '%s' activated by '%s'\n", gameLocal.framenum, static_cast( this )->GetName(), ent ? ent->GetName() : "NULL" ); + } + + c = GetType(); + num = ev->GetEventNum(); + if ( !c->eventMap[ num ] ) { + // we don't respond to this event, so ignore it + return false; + } + + callback = c->eventMap[ num ]; + +#if !CPU_EASYARGS + +/* +on ppc architecture, floats are passed in a seperate set of registers +the function prototypes must have matching float declaration + +http://developer.apple.com/documentation/DeveloperTools/Conceptual/MachORuntime/2rt_powerpc_abi/chapter_9_section_5.html +*/ + + switch( ev->GetFormatspecIndex() ) { + case 1 << D_EVENT_MAXARGS : + ( this->*callback )(); + break; + +// generated file - see CREATE_EVENT_CODE +#include "Callbacks.cpp" + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#else + + assert( D_EVENT_MAXARGS == 8 ); + + switch( ev->GetNumArgs() ) { + case 0 : + ( this->*callback )(); + break; + + case 1 : + typedef void ( idClass::*eventCallback_1_t )( const int ); + ( this->*( eventCallback_1_t )callback )( data[ 0 ] ); + break; + + case 2 : + typedef void ( idClass::*eventCallback_2_t )( const int, const int ); + ( this->*( eventCallback_2_t )callback )( data[ 0 ], data[ 1 ] ); + break; + + case 3 : + typedef void ( idClass::*eventCallback_3_t )( const int, const int, const int ); + ( this->*( eventCallback_3_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ] ); + break; + + case 4 : + typedef void ( idClass::*eventCallback_4_t )( const int, const int, const int, const int ); + ( this->*( eventCallback_4_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] ); + break; + + case 5 : + typedef void ( idClass::*eventCallback_5_t )( const int, const int, const int, const int, const int ); + ( this->*( eventCallback_5_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ] ); + break; + + case 6 : + typedef void ( idClass::*eventCallback_6_t )( const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_6_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ] ); + break; + + case 7 : + typedef void ( idClass::*eventCallback_7_t )( const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_7_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ] ); + break; + + case 8 : + typedef void ( idClass::*eventCallback_8_t )( const int, const int, const int, const int, const int, const int, const int, const int ); + ( this->*( eventCallback_8_t )callback )( data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ] ); + break; + + default: + gameLocal.Warning( "Invalid formatspec on event '%s'", ev->GetName() ); + break; + } + +#endif + + return true; +} + +/* +================ +idClass::Event_Remove +================ +*/ +void idClass::Event_Remove( void ) { + delete this; +} + +/* +================ +idClass::Event_SafeRemove +================ +*/ +void idClass::Event_SafeRemove( void ) { + // Forces the remove to be done at a safe time + PostEventMS( &EV_Remove, 0 ); +} + +// RAVEN BEGIN +// bdube: client entities +/* +================ +idClass::IsClient +================ +*/ +bool idClass::IsClient ( void ) const { + return false; +} + +/* +================ +idClass::GetDebugInfo +================ +*/ +void idClass::GetDebugInfo ( debugInfoProc_t proc, void* userData ) { +} + +/* +================ +idClass::ProcessState +================ +*/ +stateResult_t idClass::ProcessState ( const rvStateFunc* state, const stateParms_t& parms ) { + return (this->*(state->function)) ( parms ); +} + +stateResult_t idClass::ProcessState ( const char* name, const stateParms_t& parms ) { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return (this->*(cls->stateCallbacks[i].function)) ( parms ); + } + } + } + + return SRESULT_ERROR; +} + +/* +================ +idClass::FindState +================ +*/ +const rvStateFunc* idClass::FindState ( const char* name ) const { + int i; + idTypeInfo* cls; + + for ( cls = GetType(); cls; cls = cls->super ) { + for ( i = 0; cls->stateCallbacks[i].function; i ++ ) { + if ( !idStr::Icmp ( cls->stateCallbacks[i].name, name ) ) { + return &cls->stateCallbacks[i]; + } + } + } + + return NULL; +} + +/* +================ +idClass::RegisterClasses +================ +*/ +void idClass::RegisterClasses( void ) +{ +// jnewquist: Register subclasses explicitly so they aren't dead-stripped +#define REGISTER(name) void Register_##name(void); Register_##name(); + REGISTER(idAFAttachment); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Base); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_ClawFourFingers); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Generic); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Gibbable); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_SteamPipe); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_Vehicle); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleFourWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_VehicleSixWheels); // ..\..\code\game\AFEntity.cpp + REGISTER(idAFEntity_WithAttachedHead); // ..\..\code\game\AFEntity.cpp + REGISTER(idAI); // ..\..\code\game\ai\AI_events.cpp + REGISTER(idActivator); // ..\..\code\game\Misc.cpp + REGISTER(idActor); // ..\..\code\game\Actor.cpp + REGISTER(idAnimated); // ..\..\code\game\Misc.cpp + REGISTER(idAnimatedEntity); // ..\..\code\game\Entity.cpp + REGISTER(idBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idBeam); // ..\..\code\game\Misc.cpp + REGISTER(idBobber); // ..\..\code\game\Mover.cpp + REGISTER(idBrittleFracture); // ..\..\code\game\BrittleFracture.cpp + REGISTER(idCamera); // ..\..\code\game\Camera.cpp + REGISTER(idCameraAnim); // ..\..\code\game\Camera.cpp + REGISTER(idCameraView); // ..\..\code\game\Camera.cpp + REGISTER(idChain); // ..\..\code\game\AFEntity.cpp + REGISTER(idClass); // ..\..\code\game\gamesys\Class.cpp + REGISTER(idCursor3D); // ..\..\code\game\GameEdit.cpp + REGISTER(idDamagable); // ..\..\code\game\Misc.cpp + REGISTER(idDoor); // ..\..\code\game\Mover.cpp + REGISTER(idEarthQuake); // ..\..\code\game\Misc.cpp + REGISTER(idElevator); // ..\..\code\game\Mover.cpp + REGISTER(idEntity); // ..\..\code\game\Entity.cpp + REGISTER(idExplodable); // ..\..\code\game\Misc.cpp + REGISTER(idExplodingBarrel); // ..\..\code\game\Moveable.cpp + REGISTER(idForce); // ..\..\code\game\physics\Force.cpp + REGISTER(idForceField); // ..\..\code\game\Misc.cpp + REGISTER(idForce_Constant); // ..\..\code\game\physics\Force_Constant.cpp + REGISTER(idForce_Drag); // ..\..\code\game\physics\Force_Drag.cpp + REGISTER(idForce_Field); // ..\..\code\game\physics\Force_Field.cpp + REGISTER(idForce_Spring); // ..\..\code\game\physics\Force_Spring.cpp + REGISTER(idFuncAASObstacle); // ..\..\code\game\Misc.cpp + REGISTER(idFuncAASPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncEmitter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncPortal); // ..\..\code\game\Misc.cpp + REGISTER(idFuncRadioChatter); // ..\..\code\game\Misc.cpp + REGISTER(idFuncSplat); // ..\..\code\game\Misc.cpp + REGISTER(idGuidedProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idItem); // ..\..\code\game\Item.cpp + REGISTER(idItemPowerup); // ..\..\code\game\Item.cpp + REGISTER(idItemRemover); // ..\..\code\game\Item.cpp + REGISTER(idLight); // ..\..\code\game\Light.cpp + REGISTER(idLiquid); // ..\..\code\game\Misc.cpp + REGISTER(idLocationEntity); // ..\..\code\game\Misc.cpp + REGISTER(idLocationSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idMoveable); // ..\..\code\game\Moveable.cpp + REGISTER(idMoveableItem); // ..\..\code\game\Item.cpp + REGISTER(idMover); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Binary); // ..\..\code\game\Mover.cpp + REGISTER(idMover_Periodic); // ..\..\code\game\Mover.cpp + REGISTER(idMultiModelAF); // ..\..\code\game\AFEntity.cpp + REGISTER(idObjective); // ..\..\code\game\Item.cpp + REGISTER(idObjectiveComplete); // ..\..\code\game\Item.cpp + REGISTER(idPathCorner); // ..\..\code\game\Misc.cpp + REGISTER(idPendulum); // ..\..\code\game\Mover.cpp + REGISTER(idPhantomObjects); // ..\..\code\game\Misc.cpp + REGISTER(idPhysics); // ..\..\code\game\physics\Physics.cpp + REGISTER(idPhysics_AF); // ..\..\code\game\physics\Physics_AF.cpp + REGISTER(idPhysics_Actor); // ..\..\code\game\physics\Physics_Actor.cpp + REGISTER(idPhysics_Base); // ..\..\code\game\physics\Physics_Base.cpp + REGISTER(idPhysics_Monster); // ..\..\code\game\physics\Physics_Monster.cpp + REGISTER(idPhysics_Parametric); // ..\..\code\game\physics\Physics_Parametric.cpp + REGISTER(idPhysics_Player); // ..\..\code\game\physics\Physics_Player.cpp + REGISTER(idPhysics_RigidBody); // ..\..\code\game\physics\Physics_RigidBody.cpp + REGISTER(idPhysics_Static); // ..\..\code\game\physics\Physics_Static.cpp + REGISTER(idPhysics_StaticMulti); // ..\..\code\game\physics\Physics_StaticMulti.cpp + REGISTER(idPlat); // ..\..\code\game\Mover.cpp + REGISTER(idPlayer); // ..\..\code\game\Player.cpp + REGISTER(idPlayerStart); // ..\..\code\game\Misc.cpp + REGISTER(idProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(idRiser); // ..\..\code\game\Mover.cpp + REGISTER(idRotater); // ..\..\code\game\Mover.cpp + REGISTER(idSecurityCamera); // ..\..\code\game\SecurityCamera.cpp + REGISTER(idShaking); // ..\..\code\game\Misc.cpp + REGISTER(idSound); // ..\..\code\game\Sound.cpp + REGISTER(idSpawnableEntity); // ..\..\code\game\Misc.cpp + REGISTER(idSplinePath); // ..\..\code\game\Mover.cpp + REGISTER(idSpring); // ..\..\code\game\Misc.cpp + REGISTER(idStaticEntity); // ..\..\code\game\Misc.cpp + REGISTER(idTarget); // ..\..\code\game\Target.cpp + REGISTER(idTarget_CallObjectFunction); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Damage); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableLevelWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EnableStamina); // ..\..\code\game\Target.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\EndLevel.cpp + REGISTER(idTarget_EndLevel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeEntity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_FadeSoundClass); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Give); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveEmail); // ..\..\code\game\Target.cpp + REGISTER(idTarget_GiveSecurity); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LevelTrigger); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeIn); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LightFadeOut); // ..\..\code\game\Target.cpp + REGISTER(idTarget_LockDoor); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Remove); // ..\..\code\game\Target.cpp + REGISTER(idTarget_RemoveWeapons); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SessionCommand); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetFov); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetGlobalShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetInfluence); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetKeyVal); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetModel); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetPrimaryObjective); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderParm); // ..\..\code\game\Target.cpp + REGISTER(idTarget_SetShaderTime); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Show); // ..\..\code\game\Target.cpp + REGISTER(idTarget_Tip); // ..\..\code\game\Target.cpp + REGISTER(idTarget_WaitForButton); // ..\..\code\game\Target.cpp + REGISTER(idTestModel); // ..\..\code\game\anim\Anim_Testmodel.cpp + REGISTER(idTextEntity); // ..\..\code\game\Misc.cpp + REGISTER(idThread); // ..\..\code\game\script\Script_Thread.cpp + REGISTER(idTrigger); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Count); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_EntityName); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Fade); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Hurt); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Multi); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Timer); // ..\..\code\game\Trigger.cpp + REGISTER(idTrigger_Touch); // ..\..\code\game\Trigger.cpp + REGISTER(idVacuumEntity); // ..\..\code\game\Misc.cpp + REGISTER(idVacuumSeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(idWorldspawn); // ..\..\code\game\WorldSpawn.cpp + REGISTER(rvAFAttractor); // ..\..\code\game\AFEntity.cpp + REGISTER(rvAIAvoid); // ..\..\code\game\ai\AI_Helper.cpp + REGISTER(rvAITactical); // ..\..\code\game\ai\AI_Tactical.cpp + REGISTER(rvAIMedic); // ..\..\code\game\ai\AI_Medic.cpp + REGISTER(rvAITether); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherRadius); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherBehind); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITetherClear); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomePassive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAIBecomeAggressive); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvAITrigger); // ..\..\code\game\ai\AI_Util.cpp + REGISTER(rvCTFAssaultPlayerStart); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCTF_AssaultPoint); // ..\..\code\game\mp\CTF.cpp + REGISTER(rvCameraPlayback); // ..\..\code\game\Camera.cpp + REGISTER(rvCameraPortalSky); // ..\..\code\game\Camera.cpp + REGISTER(rvClientCrawlEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEffect); // ..\..\code\game\client\ClientEffect.cpp + REGISTER(rvClientEntity); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvClientModel); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvAnimatedClientEntity); // ..\..\code\game\client\ClientModel.cpp + REGISTER(rvClientAFEntity); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientAFAttachment); // ..\..\code\game\client\ClientAFEntity.cpp + REGISTER(rvClientMoveable); // ..\..\code\game\client\ClientMoveable.cpp + REGISTER(rvClientPhysics); // ..\..\code\game\client\ClientEntity.cpp + REGISTER(rvConveyor); // ..\..\code\game\Mover.cpp + REGISTER(rvDarkMatterProjectile); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvDebugJumpPoint); // ..\..\code\game\Misc.cpp + REGISTER(rvDriftingProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvEffect); // ..\..\code\game\Effect.cpp + REGISTER(rvFuncSaveGame); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_Static); // ..\..\code\game\Misc.cpp + REGISTER(rvGravityArea_SurfaceNormal); // ..\..\code\game\Misc.cpp + REGISTER(rvGravitySeparatorEntity); // ..\..\code\game\Misc.cpp + REGISTER(rvHealingStation); // ..\..\code\game\Healing_Station.cpp + REGISTER(rvItemCTFFlag); // ..\..\code\game\Item.cpp + REGISTER(rvJumpPad); // ..\..\code\game\Misc.cpp + REGISTER(rvMIRVProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvModviewModel); // ..\..\code\game\GameEdit.cpp + REGISTER(rvPusher); // ..\..\code\game\Mover.cpp +#ifndef _MPBETA + REGISTER(rvMonsterBerserker); // ..\..\code\game\ai\Monster_Berserker.cpp + REGISTER(rvMonsterBossBuddy); // ..\..\code\game\ai\Monster_BossBuddy.cpp + REGISTER(rvMonsterBossMakron); // ..\..\code\game\ai\Monster_BossMakron.cpp + REGISTER(rvMonsterConvoyGround); // ..\..\code\game\ai\Monster_ConvoyGround.cpp + REGISTER(rvMonsterConvoyHover); // ..\..\code\game\ai\Monster_ConvoyHover.cpp + REGISTER(rvMonsterFailedTransfer); // ..\..\code\game\ai\Monster_FailedTransfer.cpp + REGISTER(rvMonsterFatty); // ..\..\code\game\ai\Monster_Fatty.cpp + REGISTER(rvMonsterGladiator); // ..\..\code\game\ai\Monster_Gladiator.cpp + REGISTER(rvMonsterGrunt); // ..\..\code\game\ai\Monster_Grunt.cpp + REGISTER(rvMonsterGunner); // ..\..\code\game\ai\Monster_Gunner.cpp + REGISTER(rvMonsterHarvester); // ..\..\code\game\ai\Monster_Harvester.cpp + REGISTER(rvMonsterHarvesterDispersal); // ..\..\code\game\ai\Monster_HarvesterDispersal.cpp + REGISTER(rvMonsterHeavyHoverTank); // ..\..\code\game\ai\Monster_HeavyHoverTank.cpp + REGISTER(rvMonsterIronMaiden); // ..\..\code\game\ai\Monster_IronMaiden.cpp + REGISTER(rvMonsterLightTank); // ..\..\code\game\ai\Monster_LightTank.cpp + REGISTER(rvMonsterNetworkGuardian); // ..\..\code\game\ai\Monster_NetworkGuardian.cpp + REGISTER(rvMonsterRepairBot); // ..\..\code\game\ai\Monster_RepairBot.cpp + REGISTER(rvMonsterScientist); // ..\..\code\game\ai\Monster_Scientist.cpp + REGISTER(rvMonsterSentry); // ..\..\code\game\ai\Monster_Sentry.cpp + REGISTER(rvMonsterSlimyTransfer); // ..\..\code\game\ai\Monster_SlimyTransfer.cpp + REGISTER(rvMonsterStroggMarine);// ..\..\code\game\ai\Monster_StroggMarine.cpp + REGISTER(rvMonsterStreamProtector); // ..\..\code\game\ai\Monster_StreamProtector.cpp + REGISTER(rvMonsterStroggFlyer); // ..\..\code\game\ai\Monster_StroggFlyer.cpp + REGISTER(rvMonsterStroggHover); // ..\..\code\game\ai\Monster_StroggHover.cpp + REGISTER(rvMonsterTeleportDropper); // ..\..\code\game\ai\Monster_TeleportDropper.cpp + REGISTER(rvMonsterTurret); // ..\..\code\game\ai\Monster_Turret.cpp + REGISTER(rvMonsterTurretFlying); // ..\..\code\game\ai\Monster_TurretFlying.cpp +#endif // !_MPBETA + REGISTER(rvObjectiveFailed); // ..\..\code\game\Item.cpp + REGISTER(rvPhysics_Particle); // ..\..\code\game\physics\Physics_Particle.cpp + REGISTER(rvPhysics_VehicleMonster); // ..\..\code\game\physics\Physics_VehicleMonster.cpp + REGISTER(rvPhysics_Spline); // ..\..\code\game\SplineMover.cpp + REGISTER(rvSpawner); // ..\..\code\game\spawner.cpp + REGISTER(rvSpawnerProjectile); // ..\..\code\game\Projectile.cpp + REGISTER(rvSplineMover); // ..\..\code\game\SplineMover.cpp + // twhitaker: removed database support + // REGISTER(rvTarget_AddDatabaseEntry); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_AmmoStash); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_BossBattle); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_ExitAreaAlert); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_LaunchProjectile); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_Nailable); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_SecretArea); // ..\..\code\game\Target.cpp + REGISTER(rvTarget_TetherAI); // ..\..\code\game\Target.cpp + REGISTER(rvTramCar); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Marine); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramCar_Strogg); // ..\..\code\game\SplineMover.cpp + REGISTER(rvTramGate); // ..\..\code\game\TramGate.cpp + REGISTER(rvVehicle); // ..\..\code\game\vehicle\Vehicle.cpp + REGISTER(rvVehicleAI); // ..\..\code\game\ai\VehicleAI.cpp + REGISTER(rvVehicleAnimated); // ..\..\code\game\vehicle\VehicleAnimated.cpp + REGISTER(rvVehicleDriver); // ..\..\code\game\vehicle\VehicleDriver.cpp + REGISTER(rvVehicleDropPod); // ..\..\code\game\vehicle\Vehicle_DropPod.cpp + REGISTER(rvVehicleHoverpad); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleLight); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleMonster); // ..\..\code\game\vehicle\VehicleMonster.cpp + REGISTER(rvVehiclePart); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleRigid); // ..\..\code\game\vehicle\VehicleRigid.cpp + REGISTER(rvVehicleSound); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleSpline); // ..\..\code\game\vehicle\VehicleSpline.cpp + REGISTER(rvVehicleStatic); // ..\..\code\game\vehicle\VehicleStatic.cpp + REGISTER(rvVehicleThruster); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleTurret); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleUserAnimated); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvVehicleWalker); // ..\..\code\game\vehicle\Vehicle_Walker.cpp + REGISTER(rvVehicleWeapon); // ..\..\code\game\vehicle\VehicleParts.cpp + REGISTER(rvViewWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeapon); // ..\..\code\game\Weapon.cpp + REGISTER(rvWeaponBlaster); // ..\..\code\game\weapon\WeaponBlaster.cpp + REGISTER(rvWeaponDarkMatterGun); // ..\..\code\game\weapon\WeaponDarkMatterGun.cpp + REGISTER(rvWeaponGauntlet); // ..\..\code\game\weapon\WeaponGauntlet.cpp + REGISTER(rvWeaponGrenadeLauncher); // ..\..\code\game\weapon\WeaponGrenadeLauncher.cpp + REGISTER(rvWeaponHyperblaster); // ..\..\code\game\weapon\WeaponHyperblaster.cpp + REGISTER(rvWeaponLightningGun); // ..\..\code\game\weapon\WeaponLightningGun.cpp + REGISTER(rvWeaponMachinegun); // ..\..\code\game\weapon\WeaponMachinegun.cpp + REGISTER(rvWeaponNailgun); // ..\..\code\game\weapon\WeaponNailgun.cpp + REGISTER(rvWeaponRailgun); // ..\..\code\game\weapon\WeaponRailgun.cpp + REGISTER(rvWeaponRocketLauncher); // ..\..\code\game\weapon\WeaponRocketLauncher.cpp + REGISTER(rvWeaponShotgun); // ..\..\code\game\weapon\WeaponShotgun.cpp +// RITUAL BEGIN + REGISTER(riDeadZonePowerup); // ..\..\code\game\Item.cpp + REGISTER(WeaponNapalmGun); // ..\..\code\game\weapon\WeaponNapalmGun.cpp +// RITUAL END +#undef REGISTER +} + +// RAVEN END + diff --git a/src/mpgame/gamesys/Class.h b/src/mpgame/gamesys/Class.h new file mode 100644 index 0000000..061f7a8 --- /dev/null +++ b/src/mpgame/gamesys/Class.h @@ -0,0 +1,550 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +Base class for all game objects. Provides fast run-time type checking and run-time +instancing of objects. + +*/ + +#ifndef __SYS_CLASS_H__ +#define __SYS_CLASS_H__ + +class idClass; +class idTypeInfo; + +// RAVEN BEGIN +extern const idEventDef EV_PostRestore; +// RAVEN END +extern const idEventDef EV_Remove; +extern const idEventDef EV_SafeRemove; + +typedef void ( idClass::*eventCallback_t )( void ); + +template< class Type > +struct idEventFunc { + const idEventDef *event; + eventCallback_t function; +}; + +// added & so gcc could compile this +#define EVENT( event, function ) { &( event ), ( void ( idClass::* )( void ) )( &function ) }, +#define END_CLASS { NULL, NULL } }; + +class idEventArg { +public: + int type; + int value; + + idEventArg() { type = D_EVENT_INTEGER; value = 0; }; + idEventArg( int data ) { type = D_EVENT_INTEGER; value = data; }; + idEventArg( float data ) { type = D_EVENT_FLOAT; value = *reinterpret_cast( &data ); }; + idEventArg( const idVec3 &data ) { type = D_EVENT_VECTOR; value = reinterpret_cast( &data ); }; + idEventArg( const idStr &data ) { type = D_EVENT_STRING; value = reinterpret_cast( data.c_str() ); }; + idEventArg( const char *data ) { type = D_EVENT_STRING; value = reinterpret_cast( data ); }; + idEventArg( const class idEntity *data ) { type = D_EVENT_ENTITY; value = reinterpret_cast( data ); }; + idEventArg( const trace_t *data ) { type = D_EVENT_TRACE; value = reinterpret_cast( data ); }; +}; + +class idAllocError : public idException { +public: + idAllocError( const char *text = "" ) : idException( text ) {} +}; + +/*********************************************************************** + + idClass + +***********************************************************************/ + +/* +================ +CLASS_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance concrete classes only. +================ +*/ +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define CLASS_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +CLASS_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking and run-time instanciation. It also defines the list +of events that the class responds to. Take special care to ensure that the +proper superclass is indicated or the run-time type information will be +incorrect. Use this on concrete classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: Added states +// jnewquist: Use accessor for static class type +// mwhitlock: Dynamic memory consolidation +#define CLASS_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + try { \ + RV_PUSH_SYS_HEAP_ID(RV_HEAP_ID_LEVEL); \ + nameofclass *ptr = new nameofclass; \ + RV_POP_HEAP(); \ + ptr->FindUninitializedMemory(); \ + return ptr; \ + } \ + catch( idAllocError & ) { \ + return NULL; \ + } \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ +idEventFunc nameofclass::eventCallbacks[] = { +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + + +/* +================ +ABSTRACT_PROTOTYPE + +This macro must be included in the definition of any abstract subclass of idClass. +It prototypes variables used in class instanciation and type checking. +Use this on single inheritance abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] + +#else + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type +#define ABSTRACT_PROTOTYPE( nameofclass ) \ +private: \ + static idTypeInfo *Type; \ +public: \ + static void RegisterClass( void ); \ + static idClass *CreateInstance( void ); \ + static idTypeInfo &GetClassType( void ); \ + virtual idTypeInfo *GetType( void ) const; \ + static idEventFunc eventCallbacks[] +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +/* +================ +ABSTRACT_DECLARATION + +This macro must be included in the code to properly initialize variables +used in type checking. It also defines the list of events that the class +responds to. Take special care to ensure that the proper superclass is +indicated or the run-time tyep information will be incorrect. Use this +on abstract classes only. +================ +*/ + +#ifdef USE_STATIC_CLASS_CONSTRUCTION + +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo nameofclass::Type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + void nameofclass::RegisterClass( void ) { \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return &nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +#else + +// RAVEN BEGIN +// bdube: added states +// jnewquist: Use accessor for static class type +#define ABSTRACT_DECLARATION( nameofsuperclass, nameofclass ) \ + idTypeInfo *nameofclass::Type = NULL; \ + void nameofclass::RegisterClass( void ) { \ + static idTypeInfo type( #nameofclass, #nameofsuperclass, \ + ( idEventFunc * )nameofclass::eventCallbacks, nameofclass::CreateInstance, ( void ( idClass::* )( void ) )&nameofclass::Spawn, \ + ( rvStateFunc * )nameofclass::stateCallbacks, \ + ( void ( idClass::* )( idSaveGame * ) const )&nameofclass::Save, ( void ( idClass::* )( idRestoreGame * ) )&nameofclass::Restore ); \ + nameofclass::Type = &type; \ + } \ + void Register_##nameofclass( void ) { \ + nameofclass::RegisterClass(); \ + } \ + idClass *nameofclass::CreateInstance( void ) { \ + gameLocal.Error( "Cannot instanciate abstract class %s.", #nameofclass ); \ + return NULL; \ + } \ + idTypeInfo &nameofclass::GetClassType( void ) { \ + return *nameofclass::Type; \ + } \ + idTypeInfo *nameofclass::GetType( void ) const { \ + return nameofclass::Type; \ + } \ + idEventFunc nameofclass::eventCallbacks[] = { + +// RAVEN END + +#endif // USE_STATIC_CLASS_CONSTRUCTION + +typedef void ( idClass::*classSpawnFunc_t )( void ); + +class idSaveGame; +class idRestoreGame; + +class idClass { +public: + ABSTRACT_PROTOTYPE( idClass ); + +#ifdef ID_REDIRECT_NEWDELETE +#undef new +#endif + void * operator new( size_t ); + void * operator new( size_t s, int, int, char *, int ); + void operator delete( void * ); + void operator delete( void *, int, int, char *, int ); +#ifdef ID_REDIRECT_NEWDELETE +#define new ID_DEBUG_NEW +#endif + + virtual ~idClass(); + + void Spawn( void ); + void CallSpawn( void ); + bool IsType( const idTypeInfo &c ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *c ) const { return IsType(*c); } +// RAVEN END + const char * GetClassname( void ) const; + const char * GetSuperclass( void ) const; + void FindUninitializedMemory( void ); + + void Save( idSaveGame *savefile ) const {}; + void Restore( idRestoreGame *savefile ) {}; + + bool RespondsTo( const idEventDef &ev ) const; + +// RAVEN BEGIN +// bdube: states + stateResult_t ProcessState ( const rvStateFunc* state, const stateParms_t& parms ); + stateResult_t ProcessState ( const char* name, const stateParms_t& parms ); + const rvStateFunc* FindState ( const char* name ) const; + +// bdube: client entities + virtual bool IsClient ( void ) const; + +// jnewquist: Register subclasses explicitly so they aren't dead-stripped + static void RegisterClasses( void ); +// RAVEN END + + bool PostEventMS( const idEventDef *ev, int time ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventMS( const idEventDef *ev, int time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool PostEventSec( const idEventDef *ev, float time ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool PostEventSec( const idEventDef *ev, float time, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEvent( const idEventDef *ev ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7 ); + bool ProcessEvent( const idEventDef *ev, idEventArg arg1, idEventArg arg2, idEventArg arg3, idEventArg arg4, idEventArg arg5, idEventArg arg6, idEventArg arg7, idEventArg arg8 ); + + bool ProcessEventArgPtr( const idEventDef *ev, int *data ); + void CancelEvents( const idEventDef *ev ); +// RAVEN BEGIN +// abahr: + bool EventIsPosted( const idEventDef *ev ) const; + + void Event_PostRestore( void ) {} +// RAVEN END + void Event_Remove( void ); + + // Static functions + static void Init( void ); + static void Shutdown( void ); + static idTypeInfo * GetClass( const char *name ); + static void DisplayInfo_f( const idCmdArgs &args ); + static void ListClasses_f( const idCmdArgs &args ); + static idClass * CreateInstance( const char *name ); + static int GetNumTypes( void ) { return types.Num(); } + static int GetTypeNumBits( void ) { return typeNumBits; } + static idTypeInfo * GetType( int num ); + +// RAVEN BEGIN +// jscott: for memory profiling + static size_t GetUsedMemory( void ) { return( memused ); } + +// bdube: debug info + virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); +// RAVEN END + +private: + classSpawnFunc_t CallSpawnFunc( idTypeInfo *cls ); + + bool PostEventArgs( const idEventDef *ev, int time, int numargs, ... ); + bool ProcessEventArgs( const idEventDef *ev, int numargs, ... ); + + void Event_SafeRemove( void ); + + static bool initialized; + static idList types; + static idList typenums; + static int typeNumBits; + static int memused; + static int numobjects; + +// RAVEN BEGIN +// bdube: states + CLASS_STATES_PROTOTYPE(idClass); +// RAVEN END +}; + +/*********************************************************************** + + idTypeInfo + +***********************************************************************/ + +class idTypeInfo { +public: + const char * classname; + const char * superclass; + idClass * ( *CreateInstance )( void ); + void ( idClass::*Spawn )( void ); + void ( idClass::*Save )( idSaveGame *savefile ) const; + void ( idClass::*Restore )( idRestoreGame *savefile ); + +// RAVEN BEGIN +// bdube: added + rvStateFunc * stateCallbacks; +// RAVEN END + + idEventFunc * eventCallbacks; + eventCallback_t * eventMap; + idTypeInfo * super; + idTypeInfo * next; + bool freeEventMap; + int typeNum; + int lastChild; + + idHierarchy node; + + idTypeInfo( const char *classname, const char *superclass, + idEventFunc *eventCallbacks, idClass *( *CreateInstance )( void ), void ( idClass::*Spawn )( void ), +// RAVEN BEGIN +// bdube: added + rvStateFunc *stateCallbacks, +// RAVEN END + void ( idClass::*Save )( idSaveGame *savefile ) const, void ( idClass::*Restore )( idRestoreGame *savefile ) ); + ~idTypeInfo(); + + void Init( void ); + void Shutdown( void ); + + bool IsType( const idTypeInfo &superclass ) const; +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + bool IsType( const idTypeInfo *superclass ) const { return IsType(*superclass); } +// RAVEN END + bool RespondsTo( const idEventDef &ev ) const; +}; + +/* +================ +idTypeInfo::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idTypeInfo::IsType( const idTypeInfo &type ) const { + return ( ( typeNum >= type.typeNum ) && ( typeNum <= type.lastChild ) ); +} + +/* +================ +idTypeInfo::RespondsTo +================ +*/ +ID_INLINE bool idTypeInfo::RespondsTo( const idEventDef &ev ) const { + assert( idEvent::initialized ); + if ( !eventMap[ ev.GetEventNum() ] ) { + // we don't respond to this event + return false; + } + + return true; +} + +/* +================ +idClass::IsType + +Checks if the object's class is a subclass of the class defined by the +passed in idTypeInfo. +================ +*/ +ID_INLINE bool idClass::IsType( const idTypeInfo &superclass ) const { + idTypeInfo *subclass; + + subclass = GetType(); + return subclass->IsType( superclass ); +} + +/* +================ +idClass::RespondsTo +================ +*/ +ID_INLINE bool idClass::RespondsTo( const idEventDef &ev ) const { + const idTypeInfo *c; + + assert( idEvent::initialized ); + c = GetType(); + return c->RespondsTo( ev ); +} + +#endif /* !__SYS_CLASS_H__ */ diff --git a/src/mpgame/gamesys/DebugGraph.cpp b/src/mpgame/gamesys/DebugGraph.cpp new file mode 100644 index 0000000..b112e9b --- /dev/null +++ b/src/mpgame/gamesys/DebugGraph.cpp @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idDebugGraph::idDebugGraph +================ +*/ +idDebugGraph::idDebugGraph() { + index = 0; +} + +/* +================ +idDebugGraph::SetNumSamples +================ +*/ +void idDebugGraph::SetNumSamples( int num ) { + index = 0; + samples.Clear(); + samples.SetNum( num ); + memset( samples.Ptr(), 0, samples.MemoryUsed() ); +} + +/* +================ +idDebugGraph::AddValue +================ +*/ +void idDebugGraph::AddValue( float value ) { + samples[ index ] = value; + index++; + if ( index >= samples.Num() ) { + index = 0; + } +} + +/* +================ +idDebugGraph::Draw +================ +*/ +void idDebugGraph::Draw( const idVec4 &color, float scale ) const { + int i; + float value1; + float value2; + idVec3 vec1; + idVec3 vec2; + + const idMat3 &axis = gameLocal.GetLocalPlayer()->viewAxis; + const idVec3 pos = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() + axis[ 1 ] * samples.Num() * 0.5f; + + value1 = samples[ index ] * scale; + for( i = 1; i < samples.Num(); i++ ) { + value2 = samples[ ( i + index ) % samples.Num() ] * scale; + + vec1 = pos + axis[ 2 ] * value1 - axis[ 1 ] * ( i - 1 ) + axis[ 0 ] * samples.Num(); + vec2 = pos + axis[ 2 ] * value2 - axis[ 1 ] * i + axis[ 0 ] * samples.Num(); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + gameRenderWorld->DebugLine( color, vec1, vec2, gameLocal.GetMSec ( ), false ); +// RAVEN END + value1 = value2; + } +} diff --git a/src/mpgame/gamesys/DebugGraph.h b/src/mpgame/gamesys/DebugGraph.h new file mode 100644 index 0000000..280ae14 --- /dev/null +++ b/src/mpgame/gamesys/DebugGraph.h @@ -0,0 +1,37 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// DebugGraph.h + +class idDebugGraph { +public: + idDebugGraph(); + void SetNumSamples( int num ); + void AddValue( float value ); + void Draw( const idVec4 &color, float scale ) const; + +private: + idList samples; + int index; +}; diff --git a/src/mpgame/gamesys/Event.cpp b/src/mpgame/gamesys/Event.cpp new file mode 100644 index 0000000..2d254b0 --- /dev/null +++ b/src/mpgame/gamesys/Event.cpp @@ -0,0 +1,837 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +sys_event.cpp + +Event are used for scheduling tasks and for linking script commands. + +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_EVENTSPERFRAME 8192 // Upped from 4096 +//#define CREATE_EVENT_CODE + +/*********************************************************************** + + idEventDef + +***********************************************************************/ + +idEventDef *idEventDef::eventDefList[MAX_EVENTS]; +int idEventDef::numEventDefs = 0; + +static bool eventError = false; +static char eventErrorMsg[ 128 ]; + +/* +================ +idEventDef::idEventDef +================ +*/ +idEventDef::idEventDef( const char *command, const char *formatspec, char returnType ) { + idEventDef *ev; + int i; + unsigned int bits; + + assert( command ); + assert( !idEvent::initialized ); + + // Allow NULL to indicate no args, but always store it as "" + // so we don't have to check for it. + if ( !formatspec ) { + formatspec = ""; + } + + this->name = command; + this->formatspec = formatspec; + this->returnType = returnType; + + numargs = strlen( formatspec ); + assert( numargs <= D_EVENT_MAXARGS ); + if ( numargs > D_EVENT_MAXARGS ) { + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Too many args for '%s' event.", name ); + return; + } + + // make sure the format for the args is valid, calculate the formatspecindex, and the offsets for each arg + bits = 0; + argsize = 0; + memset( argOffset, 0, sizeof( argOffset ) ); + for( i = 0; i < numargs; i++ ) { + argOffset[ i ] = argsize; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + bits |= 1 << i; + argsize += sizeof( float ); + break; + + case D_EVENT_INTEGER : + argsize += sizeof( int ); + break; + + case D_EVENT_VECTOR : + argsize += sizeof( idVec3 ); + break; + + case D_EVENT_STRING : + argsize += MAX_STRING_LEN; + break; + + case D_EVENT_ENTITY : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_ENTITY_NULL : + argsize += sizeof( idEntityPtr ); + break; + + case D_EVENT_TRACE : + argsize += sizeof( trace_t ) + MAX_STRING_LEN + sizeof( bool ); + break; + + default : + eventError = true; + sprintf( eventErrorMsg, "idEventDef::idEventDef : Invalid arg format '%s' string for '%s' event.", formatspec, name ); + return; + break; + } + } + + // calculate the formatspecindex + formatspecIndex = ( 1 << ( numargs + D_EVENT_MAXARGS ) ) | bits; + + // go through the list of defined events and check for duplicates + // and mismatched format strings + eventnum = numEventDefs; + for( i = 0; i < eventnum; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( command, ev->name ) == 0 ) { + if ( idStr::Cmp( formatspec, ev->formatspec ) != 0 ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing format strings ('%s'!='%s').", + command, formatspec, ev->formatspec ); + return; + } + + if ( ev->returnType != returnType ) { + eventError = true; + sprintf( eventErrorMsg, "idEvent '%s' defined twice with same name but differing return types ('%c'!='%c').", + command, returnType, ev->returnType ); + return; + } + // Don't bother putting the duplicate event in list. + eventnum = ev->eventnum; + return; + } + } + + ev = this; + + if ( numEventDefs >= MAX_EVENTS ) { + eventError = true; + sprintf( eventErrorMsg, "numEventDefs >= MAX_EVENTS" ); + return; + } + eventDefList[numEventDefs] = ev; + numEventDefs++; +} + +/* +================ +idEventDef::NumEventCommands +================ +*/ +int idEventDef::NumEventCommands( void ) { + return numEventDefs; +} + +/* +================ +idEventDef::GetEventCommand +================ +*/ +const idEventDef *idEventDef::GetEventCommand( int eventnum ) { + return eventDefList[ eventnum ]; +} + +/* +================ +idEventDef::FindEvent +================ +*/ +const idEventDef *idEventDef::FindEvent( const char *name ) { + idEventDef *ev; + int num; + int i; + + assert( name ); + + num = numEventDefs; + for( i = 0; i < num; i++ ) { + ev = eventDefList[ i ]; + if ( idStr::Cmp( name, ev->name ) == 0 ) { + return ev; + } + } + + return NULL; +} + +/*********************************************************************** + + idEvent + +***********************************************************************/ + +static idLinkList FreeEvents; +static idLinkList EventQueue; +static idEvent EventPool[ MAX_EVENTS ]; + +bool idEvent::initialized = false; + +idDynamicBlockAlloc idEvent::eventDataAllocator; + +/* +================ +idEvent::~idEvent() +================ +*/ +idEvent::~idEvent() { + Free(); +} + + +void idEvent::WriteDebugInfo( void ) { + idEvent *event; + int count = 0; + + idFile *FH = fileSystem->OpenFileAppend( "idEvents.txt" ); + + FH->Printf( "Num Events = %d\n", EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + count++; + + FH->Printf( "%d. %d - %s - %s - %s\n", count, event->time, event->eventdef->GetName(), event->typeinfo->classname, event->object->GetClassname() ); + + event = event->eventNode.Next(); + } + + FH->Printf( "\n\n" ); + fileSystem->CloseFile( FH ); +} + +/* +================ +idEvent::Alloc +================ +*/ +idEvent *idEvent::Alloc( const idEventDef *evdef, int numargs, va_list args ) { + idEvent *ev; + size_t size; + const char *format; + idEventArg *arg; + byte *dataPtr; + int i; + const char *materialName; + + if ( FreeEvents.IsListEmpty() ) { + WriteDebugInfo( ); + gameLocal.Error( "idEvent::Alloc : No more free events for '%s' event.", evdef->GetName() ); + } + + ev = FreeEvents.Next(); + ev->eventNode.Remove(); + + ev->eventdef = evdef; + + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::Alloc : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + size = evdef->GetArgSize(); + if ( size ) { + ev->data = eventDataAllocator.Alloc( size ); + memset( ev->data, 0, size ); + } else { + ev->data = NULL; + } + + format = evdef->GetArgFormat(); + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) || ( format[ i ] == D_EVENT_TRACE ) ) { + // when NULL is passed in for an entity or trace, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + dataPtr = &ev->data[ evdef->GetArgOffset( i ) ]; + + switch( format[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + *reinterpret_cast( dataPtr ) = arg->value; + break; + + case D_EVENT_VECTOR : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = *reinterpret_cast( arg->value ); + } + break; + + case D_EVENT_STRING : + if ( arg->value ) { + idStr::Copynz( reinterpret_cast( dataPtr ), reinterpret_cast( arg->value ), MAX_STRING_LEN ); + } + break; + +// RAVEN BEGIN +// abahr: type checking change as per Jim D. +// jshepard: TODO FIXME HACK this never ever produces desired, positive results. Events should be built to prepare for null entities, especially when dealing with +// script events. This will throw a warning, and events should be prepared to deal with null entities. + case D_EVENT_ENTITY : + if ( reinterpret_cast( arg->value ) == NULL ) { + gameLocal.Warning( "idEvent::Alloc : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, evdef->GetName() ); + } + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast< idEntityPtr * >( dataPtr ) = reinterpret_cast( arg->value ); + break; +//RAVEN END + + case D_EVENT_TRACE : + if ( arg->value ) { + *reinterpret_cast( dataPtr ) = true; + *reinterpret_cast( dataPtr + sizeof( bool ) ) = *reinterpret_cast( arg->value ); + + // save off the material as a string since the pointer won't be valid in save games. + // since we save off the entire trace_t structure, if the material is NULL here, + // it will be NULL when we process it, so we don't need to save off anything in that case. + if ( reinterpret_cast( arg->value )->c.material ) { + materialName = reinterpret_cast( arg->value )->c.material->GetName(); + idStr::Copynz( reinterpret_cast( dataPtr + sizeof( bool ) + sizeof( trace_t ) ), materialName, MAX_STRING_LEN ); + } + } else { + *reinterpret_cast( dataPtr ) = false; + } + break; + + default : + gameLocal.Error( "idEvent::Alloc : Invalid arg format '%s' string for '%s' event.", format, evdef->GetName() ); + break; + } + } + + return ev; +} + +/* +================ +idEvent::CopyArgs +================ +*/ +void idEvent::CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ) { + int i; + const char *format; + idEventArg *arg; + + format = evdef->GetArgFormat(); + if ( numargs != evdef->GetNumArgs() ) { + gameLocal.Error( "idEvent::CopyArgs : Wrong number of args for '%s' event.", evdef->GetName() ); + } + + for( i = 0; i < numargs; i++ ) { + arg = va_arg( args, idEventArg * ); + if ( format[ i ] != arg->type ) { +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + if ( ( format[ i ] == D_EVENT_ENTITY_NULL ) && ( arg->type == D_EVENT_ENTITY ) ) { + // these types are identical, so allow them + } else if ( ( arg->type == D_EVENT_INTEGER ) && ( arg->value == 0 ) ) { + if ( ( format[ i ] == D_EVENT_ENTITY ) || ( format[ i ] == D_EVENT_ENTITY_NULL ) ) { + // when NULL is passed in for an entity, it gets cast as an integer 0, so don't give an error when it happens + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } + } else { + gameLocal.Error( "idEvent::Alloc : Wrong type passed in for arg # %d on '%s' event.", i, evdef->GetName() ); + } +// RAVEN END + } + + data[ i ] = arg->value; + } +} + +/* +================ +idEvent::Free +================ +*/ +void idEvent::Free( void ) { + if ( data ) { + eventDataAllocator.Free( data ); + data = NULL; + } + + eventdef = NULL; + time = 0; + object = NULL; + typeinfo = NULL; + + eventNode.SetOwner( this ); + eventNode.AddToEnd( FreeEvents ); +} + +/* +================ +idEvent::Schedule +================ +*/ +void idEvent::Schedule( idClass *obj, const idTypeInfo *type, int time ) { + idEvent *event; + + assert( initialized ); + if ( !initialized ) { + return; + } + + object = obj; + typeinfo = type; + + // wraps after 24 days...like I care. ;) + this->time = gameLocal.time + time; + + eventNode.Remove(); + + event = EventQueue.Next(); + while( ( event != NULL ) && ( this->time >= event->time ) ) { + event = event->eventNode.Next(); + } + + if ( event ) { + eventNode.InsertBefore( event->eventNode ); + } else { + eventNode.AddToEnd( EventQueue ); + } +} + +/* +================ +idEvent::CancelEvents +================ +*/ +void idEvent::CancelEvents( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if ( event->object == obj ) { + if ( !evdef || ( evdef == event->eventdef ) ) { + event->Free(); + } + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idEvent::EventIsPosted +================ +*/ +bool idEvent::EventIsPosted( const idClass *obj, const idEventDef *evdef ) { + idEvent *event; + idEvent *next; + + if ( !initialized ) { + return false; + } + + for( event = EventQueue.Next(); event != NULL; event = next ) { + next = event->eventNode.Next(); + if( event->object == obj && evdef == event->eventdef ) { + return true; + } + } + + return false; +} +// RAVEN END + +/* +================ +idEvent::ClearEventList +================ +*/ +void idEvent::ClearEventList( void ) { + int i; + + // + // initialize lists + // + FreeEvents.Clear(); + EventQueue.Clear(); + + // + // add the events to the free list + // + for( i = 0; i < MAX_EVENTS; i++ ) { + EventPool[ i ].Free(); + } +} + +/* +================ +idEvent::ServiceEvents +================ +*/ +void idEvent::ServiceEvents( void ) { + idEvent *event; + int num; + int args[ D_EVENT_MAXARGS ]; + int offset; + int i; + int numargs; + const char *formatspec; + trace_t **tracePtr; + const idEventDef *ev; + byte *data; + const char *materialName; + + num = 0; + while( !EventQueue.IsListEmpty() ) { + +#ifdef _XENON + session->PacifierUpdate(); +#endif + + event = EventQueue.Next(); + assert( event ); + + if ( event->time > gameLocal.time ) { + break; + } + + // copy the data into the local args array and set up pointers + ev = event->eventdef; + formatspec = ev->GetArgFormat(); + numargs = ev->GetNumArgs(); + for( i = 0; i < numargs; i++ ) { + offset = ev->GetArgOffset( i ); + data = event->data; + switch( formatspec[ i ] ) { + case D_EVENT_FLOAT : + case D_EVENT_INTEGER : + args[ i ] = *reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_VECTOR : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; + + case D_EVENT_STRING : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast( &data[ offset ] ); + break; +// RAVEN BEGIN +// abahr: type checking change as per Jim D. + case D_EVENT_ENTITY : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + if ( *reinterpret_cast( &args[ i ] ) == NULL ) { + gameLocal.Warning( "idEvent::ServiceEvents : NULL entity passed in to event function that expects a non-NULL pointer on arg # %d on '%s' event.", i, ev->GetName() ); + } + break; + + case D_EVENT_ENTITY_NULL : + *reinterpret_cast( &args[ i ] ) = reinterpret_cast< idEntityPtr * >( &data[ offset ] )->GetEntity(); + break; +// RAVEN END + case D_EVENT_TRACE : + tracePtr = reinterpret_cast( &args[ i ] ); + if ( *reinterpret_cast( &data[ offset ] ) ) { + *tracePtr = reinterpret_cast( &data[ offset + sizeof( bool ) ] ); + + if ( ( *tracePtr )->c.material != NULL ) { + // look up the material name to get the material pointer + materialName = reinterpret_cast( &data[ offset + sizeof( bool ) + sizeof( trace_t ) ] ); + ( *tracePtr )->c.material = declManager->FindMaterial( materialName, true ); + } + } else { + *tracePtr = NULL; + } + break; + + default: + gameLocal.Error( "idEvent::ServiceEvents : Invalid arg format '%s' string for '%s' event.", formatspec, ev->GetName() ); + } + } + + // the event is removed from its list so that if then object + // is deleted, the event won't be freed twice + event->eventNode.Remove(); + assert( event->object ); + + // savegames can trash the object, so do this for safety + if ( event->object ) { + event->object->ProcessEventArgPtr( ev, args ); + } + +#if 0 + // event functions may never leave return values on the FPU stack + // enable this code to check if any event call left values on the FPU stack + if ( !sys->FPU_StackIsEmpty() ) { + gameLocal.Error( "idEvent::ServiceEvents %d: %s left a value on the FPU stack\n", num, ev->GetName() ); + } +#endif + + // return the event to the free list + event->Free(); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > MAX_EVENTSPERFRAME ) { + gameLocal.Error( "Event overflow. Possible infinite loop in script." ); + } + } +} + +/* +================ +idEvent::Init +================ +*/ +void idEvent::Init( void ) { + gameLocal.Printf( "Initializing event system\n" ); + + if ( eventError ) { + gameLocal.Error( "%s", eventErrorMsg ); + } + +#ifdef CREATE_EVENT_CODE + void CreateEventCallbackHandler(); + CreateEventCallbackHandler(); + gameLocal.Error( "Wrote event callback handler" ); +#endif + + if ( initialized ) { + gameLocal.Printf( "...already initialized\n" ); + ClearEventList(); + return; + } + + ClearEventList(); + + eventDataAllocator.Init(); + + gameLocal.Printf( "...%i event definitions\n", idEventDef::NumEventCommands() ); + + // the event system has started + initialized = true; +} + +/* +================ +idEvent::Shutdown +================ +*/ +void idEvent::Shutdown( void ) { + gameLocal.Printf( "Shutdown event system\n" ); + + if ( !initialized ) { + gameLocal.Printf( "...not started\n" ); + return; + } + + ClearEventList(); + + eventDataAllocator.Shutdown(); + + // say it is now shutdown + initialized = false; +} + +/* +================ +idEvent::Save +================ +*/ +void idEvent::Save( idSaveGame *savefile ) { + idEvent *event; + + savefile->WriteInt( EventQueue.Num() ); + + event = EventQueue.Next(); + while( event != NULL ) { + savefile->WriteInt( event->time ); + savefile->WriteString( event->eventdef->GetName() ); + savefile->WriteString( event->typeinfo->classname ); + savefile->WriteObject( event->object ); + savefile->WriteInt( event->eventdef->GetArgSize() ); + savefile->Write( event->data, event->eventdef->GetArgSize() ); + + event = event->eventNode.Next(); + } +} + +/* +================ +idEvent::Restore +================ +*/ +void idEvent::Restore( idRestoreGame *savefile ) { + int i; + int num; + int argsize; + idStr name; + idEvent *event; + + savefile->ReadInt( num ); + + for ( i = 0; i < num; i++ ) { + if ( FreeEvents.IsListEmpty() ) { + gameLocal.Error( "idEvent::Restore : No more free events" ); + } + + event = FreeEvents.Next(); + event->eventNode.Remove(); + event->eventNode.AddToEnd( EventQueue ); + + savefile->ReadInt( event->time ); + + // read the event name + savefile->ReadString( name ); + event->eventdef = idEventDef::FindEvent( name ); + if ( !event->eventdef ) { + savefile->Error( "idEvent::Restore: unknown event '%s'", name.c_str() ); + } + + // read the classtype + savefile->ReadString( name ); + event->typeinfo = idClass::GetClass( name ); + if ( !event->typeinfo ) { + savefile->Error( "idEvent::Restore: unknown class '%s' on event '%s'", name.c_str(), event->eventdef->GetName() ); + } + + savefile->ReadObject( event->object ); + assert( event->object ); + + // read the args + savefile->ReadInt( argsize ); + if ( argsize != event->eventdef->GetArgSize() ) { + savefile->Error( "idEvent::Restore: arg size (%d) doesn't match saved arg size(%d) on event '%s'", event->eventdef->GetArgSize(), argsize, event->eventdef->GetName() ); + } + if ( argsize ) { + event->data = eventDataAllocator.Alloc( argsize ); + savefile->Read( event->data, argsize ); + } else { + event->data = NULL; + } + } +} + +#ifdef CREATE_EVENT_CODE +/* +================ +CreateEventCallbackHandler +================ +*/ +void CreateEventCallbackHandler( void ) { + int num; + int count; + int i, j, k; + char argString[ D_EVENT_MAXARGS + 1 ]; + idStr string1; + idStr string2; + idFile *file; + + file = fileSystem->OpenFileWrite( "Callbacks.cpp" ); + + file->Printf( "// generated file - see CREATE_EVENT_CODE\n\n" ); + + for( i = 1; i <= D_EVENT_MAXARGS; i++ ) { + + file->Printf( "\t/*******************************************************\n\n\t\t%d args\n\n\t*******************************************************/\n\n", i ); + + for ( j = 0; j < ( 1 << i ); j++ ) { + for ( k = 0; k < i; k++ ) { + argString[ k ] = j & ( 1 << k ) ? 'f' : 'i'; + } + argString[ i ] = '\0'; + + string1.Empty(); + string2.Empty(); + + for( k = 0; k < i; k++ ) { + if ( j & ( 1 << k ) ) { + string1 += "const float"; + string2 += va( "*( float * )&data[ %d ]", k ); + } else { + string1 += "const int"; + string2 += va( "data[ %d ]", k ); + } + + if ( k < i - 1 ) { + string1 += ", "; + string2 += ", "; + } + } + + file->Printf( "\tcase %d :\n\t\ttypedef void ( idClass::*eventCallback_%s_t )( %s );\n", ( 1 << ( i + D_EVENT_MAXARGS ) ) + j, argString, string1.c_str() ); + file->Printf( "\t\t( this->*( eventCallback_%s_t )callback )( %s );\n\t\tbreak;\n\n", argString, string2.c_str() ); + + } + } + + fileSystem->CloseFile( file ); +} + +#endif diff --git a/src/mpgame/gamesys/Event.h b/src/mpgame/gamesys/Event.h new file mode 100644 index 0000000..8e04d84 --- /dev/null +++ b/src/mpgame/gamesys/Event.h @@ -0,0 +1,208 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +sys_event.h + +Event are used for scheduling tasks and for linking script commands. +*/ +#ifndef __SYS_EVENT_H__ +#define __SYS_EVENT_H__ + +#define D_EVENT_MAXARGS 8 // if changed, enable the CREATE_EVENT_CODE define in Event.cpp to generate switch statement for idClass::ProcessEventArgPtr. + // running the game will then generate c:\doom\base\events.txt, the contents of which should be copied into the switch statement. + +#define D_EVENT_VOID ( ( char )0 ) +#define D_EVENT_INTEGER 'd' +#define D_EVENT_FLOAT 'f' +#define D_EVENT_VECTOR 'v' +#define D_EVENT_STRING 's' +#define D_EVENT_ENTITY 'e' +#define D_EVENT_ENTITY_NULL 'E' // event can handle NULL entity pointers +#define D_EVENT_TRACE 't' + +#define MAX_EVENTS 8192 // Upped from 4096 + +class idClass; +class idTypeInfo; + +class idEventDef { +private: + const char *name; + const char *formatspec; + unsigned int formatspecIndex; + int returnType; + int numargs; + size_t argsize; + int argOffset[ D_EVENT_MAXARGS ]; + int eventnum; + const idEventDef * next; + + static idEventDef * eventDefList[MAX_EVENTS]; + static int numEventDefs; + +public: + idEventDef( const char *command, const char *formatspec = NULL, char returnType = 0 ); + + const char *GetName( void ) const; + const char *GetArgFormat( void ) const; + unsigned int GetFormatspecIndex( void ) const; + char GetReturnType( void ) const; + int GetEventNum( void ) const; + int GetNumArgs( void ) const; + size_t GetArgSize( void ) const; + int GetArgOffset( int arg ) const; + + static int NumEventCommands( void ); + static const idEventDef *GetEventCommand( int eventnum ); + static const idEventDef *FindEvent( const char *name ); +}; + +class idSaveGame; +class idRestoreGame; + +class idEvent { +private: + const idEventDef *eventdef; + byte *data; + int time; + idClass *object; + const idTypeInfo *typeinfo; + + idLinkList eventNode; + + static idDynamicBlockAlloc eventDataAllocator; + + +public: + static bool initialized; + + ~idEvent(); + + static void WriteDebugInfo( void ); + static idEvent *Alloc( const idEventDef *evdef, int numargs, va_list args ); + static void CopyArgs( const idEventDef *evdef, int numargs, va_list args, int data[ D_EVENT_MAXARGS ] ); + + void Free( void ); + void Schedule( idClass *object, const idTypeInfo *cls, int time ); + byte *GetData( void ); + + static void CancelEvents( const idClass *obj, const idEventDef *evdef = NULL ); +// RAVEN BEGIN +// abahr: + static bool EventIsPosted( const idClass *obj, const idEventDef *evdef ); +// RAVEN END + static void ClearEventList( void ); + static void ServiceEvents( void ); + static void Init( void ); + static void Shutdown( void ); + + // save games + static void Save( idSaveGame *savefile ); // archives object for save game file + static void Restore( idRestoreGame *savefile ); // unarchives object from save game file +}; + +/* +================ +idEvent::GetData +================ +*/ +ID_INLINE byte *idEvent::GetData( void ) { + return data; +} + +/* +================ +idEventDef::GetName +================ +*/ +ID_INLINE const char *idEventDef::GetName( void ) const { + return name; +} + +/* +================ +idEventDef::GetArgFormat +================ +*/ +ID_INLINE const char *idEventDef::GetArgFormat( void ) const { + return formatspec; +} + +/* +================ +idEventDef::GetFormatspecIndex +================ +*/ +ID_INLINE unsigned int idEventDef::GetFormatspecIndex( void ) const { + return formatspecIndex; +} + +/* +================ +idEventDef::GetReturnType +================ +*/ +ID_INLINE char idEventDef::GetReturnType( void ) const { + return returnType; +} + +/* +================ +idEventDef::GetNumArgs +================ +*/ +ID_INLINE int idEventDef::GetNumArgs( void ) const { + return numargs; +} + +/* +================ +idEventDef::GetArgSize +================ +*/ +ID_INLINE size_t idEventDef::GetArgSize( void ) const { + return argsize; +} + +/* +================ +idEventDef::GetArgOffset +================ +*/ +ID_INLINE int idEventDef::GetArgOffset( int arg ) const { + assert( ( arg >= 0 ) && ( arg < D_EVENT_MAXARGS ) ); + return argOffset[ arg ]; +} + +/* +================ +idEventDef::GetEventNum +================ +*/ +ID_INLINE int idEventDef::GetEventNum( void ) const { + return eventnum; +} + +#endif /* !__SYS_EVENT_H__ */ diff --git a/src/mpgame/gamesys/NoGameTypeInfo.h b/src/mpgame/gamesys/NoGameTypeInfo.h new file mode 100644 index 0000000..1fb166a --- /dev/null +++ b/src/mpgame/gamesys/NoGameTypeInfo.h @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAMETYPEINFO_H__ +#define __GAMETYPEINFO_H__ + +/* +=================================================================================== + + This file has been generated with the Type Info Generator v1.0 (c) 2004 id Software + +=================================================================================== +*/ + +typedef struct { + const char * name; + const char * type; + const char * value; +} constantInfo_t; + +typedef struct { + const char * name; + int value; +} enumValueInfo_t; + +typedef struct { + const char * typeName; + const enumValueInfo_t * values; +} enumTypeInfo_t; + +typedef struct { + const char * type; + const char * name; + int offset; + int size; +} classVariableInfo_t; + +typedef struct { + const char * typeName; + const char * superType; + int size; + const classVariableInfo_t * variables; +} classTypeInfo_t; + + +static constantInfo_t constantInfo[] = { + { NULL, NULL, NULL } +}; + +static enumTypeInfo_t enumTypeInfo[] = { + { NULL, NULL } +}; + +static classTypeInfo_t classTypeInfo[] = { + { NULL, NULL, 0, NULL } +}; + +#endif /* !__GAMETYPEINFO_H__ */ diff --git a/src/mpgame/gamesys/SaveGame.cpp b/src/mpgame/gamesys/SaveGame.cpp new file mode 100644 index 0000000..f62a1ba --- /dev/null +++ b/src/mpgame/gamesys/SaveGame.cpp @@ -0,0 +1,2124 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +Save game related helper classes. + +Save games are implemented in two classes, idSaveGame and idRestoreGame, that implement write/read functions for +common types. They're passed in to each entity and object for them to archive themselves. Each class +implements save/restore functions for it's own data. When restoring, all the objects are instantiated, +then the restore function is called on each, superclass first, then subclasses. + +Pointers are restored by saving out an object index for each unique object pointer and adding them to a list of +objects that are to be saved. Restore instantiates all the objects in the list before calling the Restore function +on each object so that the pointers returned are valid. No object's restore function should rely on any other objects +being fully instantiated until after the restore process is complete. Post restore fixup should be done by posting +events with 0 delay. + +The savegame header will have the Game Name, Version, Map Name, and Player Persistent Info. + +Changes in version make savegames incompatible, and the game will start from the beginning of the level with +the player's persistent info. + +Changes to classes that don't need to break compatibilty can use the build number as the savegame version. +Later versions are responsible for restoring from previous versions by ignoring any unused data and initializing +variables that weren't in previous versions with safe information. + +At the head of the save game is enough information to restore the player to the beginning of the level should the +file be unloadable in some way (for example, due to script changes). +*/ + +// RAVEN BEGIN +// jscott: sanity length check for strings +#define MAX_PRINT_MSG 4096 +// RAVEN END + +/* +================ +idSaveGame::idSaveGame() +================ +*/ +idSaveGame::idSaveGame( idFile *savefile ) { + + file = savefile; + + // Put NULL at the start of the list so we can skip over it. + objects.Clear(); + objects.Append( NULL ); +} + +/* +================ +idSaveGame::~idSaveGame() +================ +*/ +idSaveGame::~idSaveGame( void ) { + if ( objects.Num() ) { + Close(); + } +} + +/* +================ +idSaveGame::Close +================ +*/ +void idSaveGame::Close( void ) { + int i; + + WriteSoundCommands(); + + // read trace models + idClipModel::SaveTraceModels( this ); + + for( i = 1; i < objects.Num(); i++ ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( objects[ i ]->GetType(), objects[ i ] ); + } + + objects.Clear(); + +#ifdef ID_DEBUG_MEMORY +// RAVEN BEGIN +// jscott: don't use type info +// idStr gameState = file->GetName(); +// gameState.StripFileExtension(); +// WriteGameState_f( idCmdArgs( va( "test %s_save", gameState.c_str() ), false ) ); +// RAVEN END +#endif +} + +/* +================ +idSaveGame::WriteObjectList +================ +*/ +void idSaveGame::WriteObjectList( void ) { + int i; + + WriteInt( objects.Num() - 1 ); + for( i = 1; i < objects.Num(); i++ ) { + WriteString( objects[ i ]->GetClassname() ); + } +} + +/* +================ +idSaveGame::CallSave_r +================ +*/ +void idSaveGame::CallSave_r( const idTypeInfo *cls, const idClass *obj ) { + if ( cls->super ) { + CallSave_r( cls->super, obj ); + if ( cls->super->Save == cls->Save ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + WriteSyncId(); + ( obj->*cls->Save )( this ); + WriteSyncId(); +} + +/* +================ +idSaveGame::AddObject +================ +*/ +void idSaveGame::AddObject( const idClass *obj ) { + objects.AddUnique( obj ); +} + +/* +================ +idSaveGame::WriteSyncId +================ +*/ +void idSaveGame::WriteSyncId( void ) { + file->WriteSyncId(); +} + +/* +================ +idSaveGame::Write +================ +*/ +void idSaveGame::Write( const void *buffer, int len ) { + file->Write( buffer, len ); +} + +/* +================ +idSaveGame::WriteInt +================ +*/ +void idSaveGame::WriteInt( const int value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteJoint +================ +*/ +void idSaveGame::WriteJoint( const jointHandle_t value ) { + file->WriteInt( value ); +} + +/* +================ +idSaveGame::WriteShort +================ +*/ +void idSaveGame::WriteShort( const short value ) { + file->WriteShort( value ); +} + +/* +================ +idSaveGame::WriteByte +================ +*/ +void idSaveGame::WriteByte( const byte value ) { + file->WriteUnsignedChar( value ); +} + +/* +================ +idSaveGame::WriteSignedChar +================ +*/ +void idSaveGame::WriteSignedChar( const signed char value ) { + file->WriteChar( value ); +} + +/* +================ +idSaveGame::WriteFloat +================ +*/ +void idSaveGame::WriteFloat( const float value ) { + file->WriteFloat( value ); +} + +/* +================ +idSaveGame::WriteBool +================ +*/ +void idSaveGame::WriteBool( const bool value ) { + file->WriteBool( value ); +} + +/* +================ +idSaveGame::WriteString +================ +*/ +void idSaveGame::WriteString( const char *string ) { + int len; + + len = strlen( string ); + +// RAVEN BEGIN +// jscott: added safety check for silly length strings + if( len < 0 || len >= MAX_PRINT_MSG ) { + + common->Error( "idSaveGame::WriteString invalid string length (%d)", len ); + } +// RAVEN END + + file->WriteString( string ); +} + +/* +================ +idSaveGame::WriteVec2 +================ +*/ +void idSaveGame::WriteVec2( const idVec2 &vec ) { + file->WriteVec2( vec ); +} + +/* +================ +idSaveGame::WriteVec3 +================ +*/ +void idSaveGame::WriteVec3( const idVec3 &vec ) { + file->WriteVec3( vec ); +} + +/* +================ +idSaveGame::WriteVec4 +================ +*/ +void idSaveGame::WriteVec4( const idVec4 &vec ) { + file->WriteVec4( vec ); +} + +/* +================ +idSaveGame::WriteVec5 +================ +*/ +void idSaveGame::WriteVec5( const idVec5 &vec ) { + file->WriteVec5( vec ); +} + +/* +================ +idSaveGame::WriteVec6 +================ +*/ +void idSaveGame::WriteVec6( const idVec6 &vec ) { + file->WriteVec6( vec ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteBounds( const idBounds &bounds ) { + file->Write( &bounds, sizeof( bounds ) ); +} + +/* +================ +idSaveGame::WriteBounds +================ +*/ +void idSaveGame::WriteWinding( const idWinding &w ) +{ + int i, num; + num = w.GetNumPoints(); + WriteInt( num ); + for ( i = 0; i < num; i++ ) { + WriteVec5( w[i] ); + } +} + + +/* +================ +idSaveGame::WriteMat3 +================ +*/ +void idSaveGame::WriteMat3( const idMat3 &mat ) { + file->WriteMat3( mat ); +} + +/* +================ +idSaveGame::WriteAngles +================ +*/ +void idSaveGame::WriteAngles( const idAngles &angles ) { + file->Write( &angles, sizeof( angles ) ); +} + +/* +================ +idSaveGame::WriteObject +================ +*/ +void idSaveGame::WriteObject( const idClass *obj ) { + int index; + + index = objects.FindIndex( obj ); + if ( index < 0 ) { + gameLocal.DPrintf( "idSaveGame::WriteObject - WriteObject FindIndex failed\n" ); + + // Use the NULL index + index = 0; + } + + WriteInt( index ); +} + +/* +================ +idSaveGame::WriteStaticObject +================ +*/ +void idSaveGame::WriteStaticObject( const idClass &obj ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + CallSave_r( obj.GetType(), &obj ); +} + +/* +================ +idSaveGame::WriteDict +================ +*/ +void idSaveGame::WriteDict( const idDict *dict ) { + int num; + int i; + const idKeyValue *kv; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !dict ) { + WriteInt( -1 ); + } else { + num = dict->GetNumKeyVals(); + WriteInt( num ); + for( i = 0; i < num; i++ ) { + kv = dict->GetKeyVal( i ); + WriteString( kv->GetKey() ); + WriteString( kv->GetValue() ); + } + } +} + +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterial( const idMaterial *material ) { + if ( !material ) { + WriteString( "" ); + } else { + WriteString( material->GetName() ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idSaveGame::WriteMaterial +================ +*/ +void idSaveGame::WriteMaterialType ( const rvDeclMatType* materialType ) { + if ( !materialType ) { + WriteString( "" ); + } else { + WriteString( materialType->GetName() ); + } +} + +/* +================ +idSaveGame::WriteTable +================ +*/ +void idSaveGame::WriteTable ( const idDeclTable* table ) { + if ( !table ) { + WriteString( "" ); + } else { + WriteString( table->GetName() ); + } +} +// RAVEN END + +/* +================ +idSaveGame::WriteSkin +================ +*/ +void idSaveGame::WriteSkin( const idDeclSkin *skin ) { + if ( !skin ) { + WriteString( "" ); + } else { + WriteString( skin->GetName() ); + } +} + +/* +================ +idSaveGame::WriteModelDef +================ +*/ +void idSaveGame::WriteModelDef( const idDeclModelDef *modelDef ) { + if ( !modelDef ) { + WriteString( "" ); + } else { + WriteString( modelDef->GetName() ); + } +} + +/* +================ +idSaveGame::WriteSoundShader +================ +*/ +void idSaveGame::WriteSoundShader( const idSoundShader *shader ) { + const char *name; + + if ( !shader ) { + WriteString( "" ); + } else { + name = shader->GetName(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteModel +================ +*/ +void idSaveGame::WriteModel( const idRenderModel *model ) { + const char *name; + + if ( !model ) { + WriteString( "" ); + } else { + name = model->Name(); + WriteString( name ); + } +} + +/* +================ +idSaveGame::WriteUserInterface +================ +*/ +void idSaveGame::WriteUserInterface( const idUserInterface *ui, bool unique ) { + const char *name; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + if ( !ui ) { + WriteString( "" ); + } else { + name = ui->Name(); + WriteString( name ); + WriteBool( unique ); + if ( ui->WriteToSaveGame( file ) == false ) { + gameLocal.Error( "idSaveGame::WriteUserInterface: ui failed to write properly\n" ); + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteInt( extrap.GetStartValue() ); + WriteInt( extrap.GetBaseSpeed() ); + WriteInt( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteFloat( extrap.GetStartValue() ); + WriteFloat( extrap.GetBaseSpeed() ); + WriteFloat( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteExtrapolate +================ +*/ +void idSaveGame::WriteExtrapolate( const idExtrapolate& extrap ) { + WriteInt( (int)extrap.GetExtrapolationType() ); + WriteFloat( extrap.GetStartTime() ); + WriteFloat( extrap.GetDuration() ); + + WriteVec3( extrap.GetStartValue() ); + WriteVec3( extrap.GetBaseSpeed() ); + WriteVec3( extrap.GetSpeed() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + WriteFloat( lerp.GetAcceleration() ); + WriteFloat( lerp.GetDeceleration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteInt( lerp.GetStartValue() ); + WriteInt( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteFloat( lerp.GetStartValue() ); + WriteFloat( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteInterpolate +================ +*/ +void idSaveGame::WriteInterpolate( const idInterpolate& lerp ) { + WriteFloat( lerp.GetStartTime() ); + WriteFloat( lerp.GetDuration() ); + + WriteVec3( lerp.GetStartValue() ); + WriteVec3( lerp.GetEndValue() ); +} + +/* +================ +idSaveGame::WriteRenderEffect +================ +*/ +void idSaveGame::WriteRenderEffect( const renderEffect_t &renderEffect ) { + WriteSyncId(); + + WriteFloat( renderEffect.startTime ); + WriteInt( renderEffect.suppressSurfaceInViewID ); + WriteInt( renderEffect.allowSurfaceInViewID ); + WriteInt( renderEffect.groupID ); + + WriteVec3( renderEffect.origin ); + WriteMat3( renderEffect.axis ); + + WriteVec3( renderEffect.gravity ); + WriteVec3( renderEffect.endOrigin ); + + WriteFloat( renderEffect.attenuation ); + WriteBool( renderEffect.hasEndOrigin ); + WriteBool( renderEffect.loop ); + WriteBool( renderEffect.ambient ); + WriteBool( renderEffect.inConnectedArea ); + WriteInt( renderEffect.weaponDepthHackInViewID ); + WriteFloat( renderEffect.modelDepthHack ); + + WriteInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + WriteFloat( renderEffect.shaderParms[ ix ] ); + } + + if( renderEffect.declEffect ) { + WriteString( renderEffect.declEffect->GetName() ); + } else { + WriteString( "" ); + } +} + +/* +================ +idSaveGame::WriteFrustum +================ +*/ +void idSaveGame::WriteFrustum( const idFrustum& frustum ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteVec3( frustum.GetOrigin() ); + WriteMat3( frustum.GetAxis() ); + WriteFloat( frustum.GetNearDistance() ); + WriteFloat( frustum.GetFarDistance() ); + WriteFloat( frustum.GetLeft() ); + WriteFloat( frustum.GetUp() ); +} + +/* +================ +idSaveGame::WriteRenderEntity +================ +*/ +void idSaveGame::WriteRenderEntity( const renderEntity_t &renderEntity ) { + int i; + + WriteSyncId(); + + WriteModel( renderEntity.hModel ); + + WriteInt( renderEntity.entityNum ); + WriteInt( renderEntity.bodyId ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + WriteBounds( renderEntity.bounds ); + + // callback is set by class's Restore function + + WriteInt( renderEntity.suppressSurfaceInViewID ); + WriteInt( renderEntity.suppressShadowInViewID ); + WriteInt( renderEntity.suppressShadowInLightID ); + WriteInt( renderEntity.allowSurfaceInViewID ); + + WriteInt( renderEntity.suppressSurfaceMask ); + + WriteVec3( renderEntity.origin ); + WriteMat3( renderEntity.axis ); + + WriteMaterial( renderEntity.customShader ); + WriteMaterial( renderEntity.referenceShader ); + WriteMaterial( renderEntity.overlayShader ); + WriteSkin( renderEntity.customSkin ); + + WriteInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + WriteUserInterface( renderEntity.gui[ i ], renderEntity.gui[ i ] ? renderEntity.gui[ i ]->IsUniqued() : false ); + } + + WriteFloat( renderEntity.modelDepthHack ); + + WriteBool( renderEntity.noSelfShadow ); + WriteBool( renderEntity.noShadow ); + WriteBool( renderEntity.noDynamicInteractions ); + WriteBool( renderEntity.forceUpdate ); + + WriteInt( renderEntity.weaponDepthHackInViewID ); + WriteFloat( renderEntity.shadowLODDistance ); + WriteInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idSaveGame::WriteRenderLight +================ +*/ +void idSaveGame::WriteRenderLight( const renderLight_t &renderLight ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteMat3( renderLight.axis ); + WriteVec3( renderLight.origin ); + + WriteInt( renderLight.suppressLightInViewID ); + WriteInt( renderLight.allowLightInViewID ); + WriteBool( renderLight.noShadows ); + WriteBool( renderLight.noSpecular ); + WriteBool( renderLight.noDynamicShadows ); + WriteBool( renderLight.pointLight ); + WriteBool( renderLight.parallel ); + WriteBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + WriteFloat( renderLight.detailLevel ); +// RAVEN END + + WriteVec3( renderLight.lightRadius ); + WriteVec3( renderLight.lightCenter ); + + WriteVec3( renderLight.target ); + WriteVec3( renderLight.right ); + WriteVec3( renderLight.up ); + WriteVec3( renderLight.start ); + WriteVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // WriteModel( renderLight.prelightModel ); + + WriteInt( renderLight.lightId ); + + WriteMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + WriteFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + WriteInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idSaveGame::WriteRefSound +================ +*/ +void idSaveGame::WriteRefSound( const refSound_t &refSound ) { +// RAVEN BEGIN + WriteSyncId(); + + WriteInt( refSound.referenceSoundHandle ); +// RAVEN END + WriteVec3( refSound.origin ); +// RAVEN BEGIN + WriteVec3( refSound.velocity ); +// RAVEN END + WriteInt( refSound.listenerId ); + WriteSoundShader( refSound.shader ); + WriteFloat( refSound.diversity ); + WriteBool( refSound.waitfortrigger ); + + WriteFloat( refSound.parms.minDistance ); + WriteFloat( refSound.parms.maxDistance ); + WriteFloat( refSound.parms.volume ); + WriteFloat( refSound.parms.shakes ); + WriteInt( refSound.parms.soundShaderFlags ); + WriteInt( refSound.parms.soundClass ); +} + +/* +================ +idSaveGame::WriteRenderView +================ +*/ +void idSaveGame::WriteRenderView( const renderView_t &view ) { + int i; + +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + + WriteInt( view.viewID ); + WriteInt( view.x ); + WriteInt( view.y ); + WriteInt( view.width ); + WriteInt( view.height ); + + WriteFloat( view.fov_x ); + WriteFloat( view.fov_y ); + WriteVec3( view.vieworg ); + WriteMat3( view.viewaxis ); + + WriteBool( view.cramZNear ); + + WriteInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + WriteFloat( view.shaderParms[ i ] ); + } +} + +/* +=================== +idSaveGame::WriteUsercmd +=================== +*/ +void idSaveGame::WriteUsercmd( const usercmd_t &usercmd ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteInt( usercmd.gameFrame ); + WriteInt( usercmd.gameTime ); + WriteInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + WriteShort( usercmd.buttons ); +// RAVEN END + WriteSignedChar( usercmd.forwardmove ); + WriteSignedChar( usercmd.rightmove ); + WriteSignedChar( usercmd.upmove ); + WriteShort( usercmd.angles[0] ); + WriteShort( usercmd.angles[1] ); + WriteShort( usercmd.angles[2] ); + WriteShort( usercmd.mx ); + WriteShort( usercmd.my ); + WriteSignedChar( usercmd.impulse ); + WriteByte( usercmd.flags ); + WriteInt( usercmd.sequence ); +} + +/* +=================== +idSaveGame::WriteContactInfo +=================== +*/ +void idSaveGame::WriteContactInfo( const contactInfo_t &contactInfo ) { + WriteInt( (int)contactInfo.type ); + WriteVec3( contactInfo.point ); + WriteVec3( contactInfo.normal ); + WriteFloat( contactInfo.dist ); + WriteInt( contactInfo.contents ); + WriteMaterial( contactInfo.material ); + WriteInt( contactInfo.modelFeature ); + WriteInt( contactInfo.trmFeature ); + WriteInt( contactInfo.entityNum ); + WriteInt( contactInfo.id ); + WriteMaterialType( contactInfo.materialType ); +} + +/* +=================== +idSaveGame::WriteTrace +=================== +*/ +void idSaveGame::WriteTrace( const trace_t &trace ) { +// RAVEN BEGIN + WriteSyncId(); +// RAVEN END + WriteFloat( trace.fraction ); + WriteVec3( trace.endpos ); + WriteMat3( trace.endAxis ); + WriteContactInfo( trace.c ); +} + +/* +=================== +idSaveGame::WriteClipModel +=================== +*/ +void idSaveGame::WriteClipModel( const idClipModel *clipModel ) { + if ( clipModel != NULL ) { + WriteBool( true ); + clipModel->Save( this ); + } else { + WriteBool( false ); + } +} + +/* +=================== +idSaveGame::WriteSoundCommands +=================== +*/ +void idSaveGame::WriteSoundCommands( void ) { + soundSystem->WriteToSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +====================== +idSaveGame::WriteBuildNumber +====================== +*/ +void idSaveGame::WriteBuildNumber( const int value ) { + WriteInt( value ); +} + + + + + + +/*********************************************************************** + + idRestoreGame + +***********************************************************************/ + +/* +================ +idRestoreGame::RestoreGame +================ +*/ +idRestoreGame::idRestoreGame( idFile *savefile ) { + file = savefile; +} + +/* +================ +idRestoreGame::~idRestoreGame() +================ +*/ +idRestoreGame::~idRestoreGame() { +} + +// RAVEN BEGIN +/* +================ +void idRestoreGame::CreateObjects +================ +*/ +void idRestoreGame::CreateObjects( void ) { + int i, num; + idStr classname; + idTypeInfo *type; + + ReadInt( num ); + + // create all the objects + objects.SetNum( num + 1 ); + memset( objects.Ptr(), 0, sizeof( objects[ 0 ] ) * objects.Num() ); + + for( i = 1; i < objects.Num(); i++ ) { + ReadString( classname ); + type = idClass::GetClass( classname ); + if ( !type ) { + Error( "idRestoreGame::CreateObjects: Unknown class '%s'", classname.c_str() ); + } + objects[ i ] = type->CreateInstance(); + } +} + +/* +================ +void idRestoreGame::RestoreObjects +================ +*/ +void idRestoreGame::RestoreObjects( void ) { + int i; + + ReadSoundCommands(); + + // read trace models + idClipModel::RestoreTraceModels( this ); + + // restore all the objects + for( i = 1; i < objects.Num(); i++ ) { + file->ReadSyncId( "Restore objects", objects[ i ]->GetClassname() ); + CallRestore_r( objects[ i ]->GetType(), objects[ i ] ); + } + + // regenerate render entities and render lights because are not saved + for( i = 1; i < objects.Num(); i++ ) { + if ( objects[ i ]->IsType( idEntity::GetClassType() ) ) { + idEntity *ent = static_cast( objects[ i ] ); + ent->UpdateVisuals(); + ent->Present(); + } + } +} +// RAVEN END + +/* +==================== +void idRestoreGame::DeleteObjects +==================== +*/ +void idRestoreGame::DeleteObjects( void ) { + + // Remove the NULL object before deleting + objects.RemoveIndex( 0 ); + + objects.DeleteContents( true ); +} + +/* +================ +idRestoreGame::Error +================ +*/ +void idRestoreGame::Error( const char *fmt, ... ) { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + +// RAVEN BEGIN + // FIXME: this crashes. It now leaks, but that's better than crashing. + // The problem is that some entities delete attached ents that are also in this list. When this call gets to them + // it tries to delete an already deleted object +// objects.DeleteContents( true ); +// RAVEN END + + gameLocal.Error( "%s", text ); +} + +/* +================ +idRestoreGame::CallRestore_r +================ +*/ +void idRestoreGame::CallRestore_r( const idTypeInfo *cls, idClass *obj ) { + if ( cls->super ) { + CallRestore_r( cls->super, obj ); + if ( cls->super->Restore == cls->Restore ) { + // don't call save on this inheritance level since the function was called in the super class + return; + } + } + + file->ReadSyncId( "Callrestore_r start ", cls->classname ); + ( obj->*cls->Restore )( this ); + file->ReadSyncId( "Callrestore_r end ", cls->classname ); +} + +/* +================ +idRestoreGame::Read +================ +*/ +void idRestoreGame::Read( void *buffer, int len ) { + file->Read( buffer, len ); +} + +/* +================ +idRestoreGame::ReadInt +================ +*/ +void idRestoreGame::ReadInt( int &value ) { + file->ReadInt( value ); +} + +/* +================ +idRestoreGame::ReadJoint +================ +*/ +void idRestoreGame::ReadJoint( jointHandle_t &value ) { + file->ReadInt( ( int &)value ); +} + +/* +================ +idRestoreGame::ReadShort +================ +*/ +void idRestoreGame::ReadShort( short &value ) { + file->ReadShort( value ); +} + +/* +================ +idRestoreGame::ReadByte +================ +*/ +void idRestoreGame::ReadByte( byte &value ) { + file->ReadUnsignedChar( value ); +} + +/* +================ +idRestoreGame::ReadSignedChar +================ +*/ +void idRestoreGame::ReadSignedChar( signed char &value ) { + file->ReadChar( ( char & )value ); +} + +/* +================ +idRestoreGame::ReadFloat +================ +*/ +void idRestoreGame::ReadFloat( float &value ) { + file->ReadFloat( value ); +} + +/* +================ +idRestoreGame::ReadBool +================ +*/ +void idRestoreGame::ReadBool( bool &value ) { + file->ReadBool( value ); +} + +/* +================ +idRestoreGame::ReadString +================ +*/ +void idRestoreGame::ReadString( idStr &string ) { +/* int len; + + ReadInt( len ); +// RAVEN BEGIN +// jscott: added max check - should be big enough + if ( len < 0 || len > MAX_PRINT_MSG ) { + Error( "idRestoreGame::ReadString: invalid length (%d)", len ); +// RAVEN END + } + + string.Fill( ' ', len );*/ + file->ReadString( string ); +} + +/* +================ +idRestoreGame::ReadVec2 +================ +*/ +void idRestoreGame::ReadVec2( idVec2 &vec ) { + file->ReadVec2( vec ); +} + +/* +================ +idRestoreGame::ReadVec3 +================ +*/ +void idRestoreGame::ReadVec3( idVec3 &vec ) { + file->ReadVec3( vec ); +} + +/* +================ +idRestoreGame::ReadVec4 +================ +*/ +void idRestoreGame::ReadVec4( idVec4 &vec ) { + file->ReadVec4( vec ); +} + +/* +================ +idRestoreGame::ReadVec5 +================ +*/ +void idRestoreGame::ReadVec5( idVec5 &vec ) { + file->ReadVec5( vec ); +} + +/* +================ +idRestoreGame::ReadVec6 +================ +*/ +void idRestoreGame::ReadVec6( idVec6 &vec ) { + file->ReadVec6( vec ); +} + +/* +================ +idRestoreGame::ReadBounds +================ +*/ +void idRestoreGame::ReadBounds( idBounds &bounds ) { + file->Read( &bounds, sizeof( bounds ) ); +} + +/* +================ +idRestoreGame::ReadWinding +================ +*/ +void idRestoreGame::ReadWinding( idWinding &w ) +{ + int i, num; + file->ReadInt( num ); + w.SetNumPoints( num ); + for ( i = 0; i < num; i++ ) { + file->ReadVec5( w[i] ); + } +} + +/* +================ +idRestoreGame::ReadMat3 +================ +*/ +void idRestoreGame::ReadMat3( idMat3 &mat ) { + file->ReadMat3( mat ); +} + +/* +================ +idRestoreGame::ReadAngles +================ +*/ +void idRestoreGame::ReadAngles( idAngles &angles ) { + file->Read( &angles, sizeof( angles ) ); +} + +/* +================ +idRestoreGame::ReadObject +================ +*/ +void idRestoreGame::ReadObject( idClass *&obj ) { + int index; + + ReadInt( index ); + if ( ( index < 0 ) || ( index >= objects.Num() ) ) { + Error( "idRestoreGame::ReadObject: invalid object index" ); + } + obj = objects[ index ]; +} + +/* +================ +idRestoreGame::ReadStaticObject +================ +*/ +void idRestoreGame::ReadStaticObject( idClass &obj ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadStaticObject", obj.GetClassname() ); +// RAVEN END + + CallRestore_r( obj.GetType(), &obj ); + +// RAVEN BEGIN + obj.PostEventMS( &EV_PostRestore, 0 ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadDict +================ +*/ +void idRestoreGame::ReadDict( idDict *dict ) { + int num; + int i; + idStr key; + idStr value; + +// RAVEN BEGIN + file->ReadSyncId( "ReadDict" ); +// RAVEN END + + ReadInt( num ); + + if ( num < 0 ) { + dict = NULL; + } else { + dict->Clear(); + for( i = 0; i < num; i++ ) { + ReadString( key ); + ReadString( value ); + dict->Set( key, value ); + } + } +} + +/* +================ +idRestoreGame::ReadMaterial +================ +*/ +void idRestoreGame::ReadMaterial( const idMaterial *&material ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + material = NULL; + } else { + material = declManager->FindMaterial( name ); + } +} + +// RAVEN BEGIN +// bdube: material type +/* +================ +idRestoreGame::ReadMaterialType +================ +*/ +void idRestoreGame::ReadMaterialType ( const rvDeclMatType* &materialType ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + materialType = NULL; + } else { + materialType = declManager->FindMaterialType ( name ); + } +} + +/* +================ +idRestoreGame::ReadTable +================ +*/ +void idRestoreGame::ReadTable ( const idDeclTable* &table ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + table = NULL; + } else { + table = declManager->FindTable( name ); + } +} + +// RAVEN END + +/* +================ +idRestoreGame::ReadSkin +================ +*/ +void idRestoreGame::ReadSkin( const idDeclSkin *&skin ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + skin = NULL; + } else { + skin = declManager->FindSkin( name ); + } +} + +/* +================ +idRestoreGame::ReadSoundShader +================ +*/ +void idRestoreGame::ReadSoundShader( const idSoundShader *&shader ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + shader = NULL; + } else { + shader = declManager->FindSound( name ); + } +} + +/* +================ +idRestoreGame::ReadModelDef +================ +*/ +void idRestoreGame::ReadModelDef( const idDeclModelDef *&modelDef ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + modelDef = NULL; + } else { + modelDef = static_cast( declManager->FindType( DECL_MODELDEF, name, false ) ); + } +} + +/* +================ +idRestoreGame::ReadModel +================ +*/ +void idRestoreGame::ReadModel( idRenderModel *&model ) { + idStr name; + + ReadString( name ); + if ( !name.Length() ) { + model = NULL; + } else { + model = renderModelManager->FindModel( name ); + } +} + +/* +================ +idRestoreGame::ReadUserInterface +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadUserInterface( idUserInterface *&ui, const idDict *args ) { +// RAVEN END + idStr name; + +// RAVEN BEGIN + file->ReadSyncId( "ReadUserInterface" ); +// RAVEN END + + ReadString( name ); + if ( !name.Length() ) { + ui = NULL; + } else { + bool unique; + ReadBool( unique ); + ui = uiManager->FindGui( name, true, unique ); + if ( ui ) { + if ( ui->ReadFromSaveGame( file ) == false ) { + Error( "idSaveGame::ReadUserInterface: ui failed to read properly\n" ); + } else { +// RAVEN BEGIN + UpdateGuiParms( ui, args ); +// RAVEN END + } + } + } +} + +// RAVEN BEGIN +// abahr +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + int startValue; + int baseSpeed; + int speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( baseSpeed ); + ReadInt( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + float startValue; + float baseSpeed; + float speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( baseSpeed ); + ReadFloat( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadExtrapolate +================ +*/ +void idRestoreGame::ReadExtrapolate( idExtrapolate& extrap ) { + int extrapType; + float startTime; + float duration; + idVec3 startValue; + idVec3 baseSpeed; + idVec3 speed; + + ReadInt( extrapType ); + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( baseSpeed ); + ReadVec3( speed ); + + extrap.Init( startTime, duration, startValue, baseSpeed, speed, (extrapolation_t)extrapType ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolateAccelDecelLinear& lerp ) { + float startTime; + float duration; + float accelTime; + float decelTime; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + ReadFloat( accelTime ); + ReadFloat( decelTime ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + int startValue; + int endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadInt( startValue ); + ReadInt( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + float startValue; + float endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadFloat( startValue ); + ReadFloat( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} + +/* +================ +idRestoreGame::ReadInterpolate +================ +*/ +void idRestoreGame::ReadInterpolate( idInterpolate& lerp ) { + float startTime; + float duration; + + idVec3 startValue; + idVec3 endValue; + + ReadFloat( startTime ); + ReadFloat( duration ); + + ReadVec3( startValue ); + ReadVec3( endValue ); + + lerp.Init( startTime, duration, startValue, endValue ); +} +/* +================ +idRestoreGame::ReadRenderEffect +================ +*/ +void idRestoreGame::ReadRenderEffect( renderEffect_t &renderEffect ) { + idStr name; + + file->ReadSyncId( "ReadRenderEffect" ); + + renderEffect.declEffect = NULL; + + ReadFloat( renderEffect.startTime ); + ReadInt( renderEffect.suppressSurfaceInViewID ); + ReadInt( renderEffect.allowSurfaceInViewID ); + ReadInt( renderEffect.groupID ); + + ReadVec3( renderEffect.origin ); + ReadMat3( renderEffect.axis ); + + ReadVec3( renderEffect.gravity ); + ReadVec3( renderEffect.endOrigin ); + + ReadFloat( renderEffect.attenuation ); + ReadBool( renderEffect.hasEndOrigin ); + ReadBool( renderEffect.loop ); + ReadBool( renderEffect.ambient ); + ReadBool( renderEffect.inConnectedArea ); + ReadInt( renderEffect.weaponDepthHackInViewID ); + ReadFloat( renderEffect.modelDepthHack ); + + ReadInt( renderEffect.referenceSoundHandle ); + + for( int ix = 0; ix < MAX_ENTITY_SHADER_PARMS; ++ix ) { + ReadFloat( renderEffect.shaderParms[ ix ] ); + } + + ReadString( name ); + if( name.Length() ) { + renderEffect.declEffect = declManager->FindType( DECL_EFFECT, name ); + } +} + +/* +================ +idRestoreGame::ReadFrustum +================ +*/ +void idRestoreGame::ReadFrustum( idFrustum& frustum ) { + idVec3 origin; + idMat3 axis; + float dNear = 0.0f, dFar = 0.0f, dLeft = 0.0f, dUp = 0.0f; +// RAVEN BEGIN + file->ReadSyncId( "ReadFrustum" ); +// RAVEN END + ReadVec3( origin ); + frustum.SetOrigin( origin ); + + ReadMat3( axis ); + frustum.SetAxis( axis ); + + ReadFloat( dNear ); + ReadFloat( dFar ); + ReadFloat( dLeft ); + ReadFloat( dUp ); + frustum.SetSize( dNear, dFar, dLeft, dUp ); +} + +/* +================ +idRestoreGame::ReadRenderEntity +================ +*/ +// RAVEN BEGIN +void idRestoreGame::ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ) { +// RAVEN END + int i; + + file->ReadSyncId( "ReadRenderEntity" ); + + ReadModel( renderEntity.hModel ); + + ReadInt( renderEntity.entityNum ); + ReadInt( renderEntity.bodyId ); + + ReadBounds( renderEntity.bounds ); + + assert( renderEntity.bounds[0][0] <= renderEntity.bounds[1][0] ); + assert( renderEntity.bounds[0][1] <= renderEntity.bounds[1][1] ); + assert( renderEntity.bounds[0][2] <= renderEntity.bounds[1][2] ); + + assert( renderEntity.bounds[1][0] - renderEntity.bounds[0][0] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][1] - renderEntity.bounds[0][1] < MAX_BOUND_SIZE ); + assert( renderEntity.bounds[1][2] - renderEntity.bounds[0][2] < MAX_BOUND_SIZE ); + + // callback is set by class's Restore function + renderEntity.callback = NULL; + renderEntity.callbackData = NULL; + + ReadInt( renderEntity.suppressSurfaceInViewID ); + ReadInt( renderEntity.suppressShadowInViewID ); + ReadInt( renderEntity.suppressShadowInLightID ); + ReadInt( renderEntity.allowSurfaceInViewID ); + + ReadInt( renderEntity.suppressSurfaceMask ); + + ReadVec3( renderEntity.origin ); + ReadMat3( renderEntity.axis ); + + ReadMaterial( renderEntity.customShader ); + ReadMaterial( renderEntity.referenceShader ); + ReadMaterial( renderEntity.overlayShader ); + ReadSkin( renderEntity.customSkin ); + + ReadInt( renderEntity.referenceSoundHandle ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderEntity.shaderParms[ i ] ); + } + + for( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { +// RAVEN BEGIN + ReadUserInterface( renderEntity.gui[ i ], args ); +// RAVEN END + } + + // idEntity will restore "cameraTarget", which will be used in idEntity::Present to restore the remoteRenderView + renderEntity.remoteRenderView = NULL; + + renderEntity.numJoints = 0; + renderEntity.joints = NULL; + + ReadFloat( renderEntity.modelDepthHack ); + + ReadBool( renderEntity.noSelfShadow ); + ReadBool( renderEntity.noShadow ); + ReadBool( renderEntity.noDynamicInteractions ); + ReadBool( renderEntity.forceUpdate ); + + ReadInt( renderEntity.weaponDepthHackInViewID ); + ReadFloat( renderEntity.shadowLODDistance ); + ReadInt( renderEntity.suppressLOD ); +} +// RAVEN END + +/* +================ +idRestoreGame::ReadRenderLight +================ +*/ +void idRestoreGame::ReadRenderLight( renderLight_t &renderLight ) { + int i; + + file->ReadSyncId( "ReadRenderLight" ); + + ReadMat3( renderLight.axis ); + ReadVec3( renderLight.origin ); + + ReadInt( renderLight.suppressLightInViewID ); + ReadInt( renderLight.allowLightInViewID ); + ReadBool( renderLight.noShadows ); + ReadBool( renderLight.noSpecular ); + ReadBool( renderLight.noDynamicShadows ); + ReadBool( renderLight.pointLight ); + ReadBool( renderLight.parallel ); + ReadBool( renderLight.globalLight ); + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + ReadFloat( renderLight.detailLevel ); +// RAVEN END + + ReadVec3( renderLight.lightRadius ); + ReadVec3( renderLight.lightCenter ); + + ReadVec3( renderLight.target ); + ReadVec3( renderLight.right ); + ReadVec3( renderLight.up ); + ReadVec3( renderLight.start ); + ReadVec3( renderLight.end ); + + // only idLight has a prelightModel and it's always based on the entityname, so we'll restore it there + // ReadModel( renderLight.prelightModel ); + renderLight.prelightModel = NULL; + + ReadInt( renderLight.lightId ); + + ReadMaterial( renderLight.shader ); + + for( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + ReadFloat( renderLight.shaderParms[ i ] ); + } + +// RAVEN BEGIN + ReadInt( renderLight.referenceSoundHandle ); +// RAVEN END +} + +/* +================ +idRestoreGame::ReadRefSound +================ +*/ +void idRestoreGame::ReadRefSound( refSound_t &refSound ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadRefSound" ); +// RAVEN END + + ReadInt( refSound.referenceSoundHandle ); + ReadVec3( refSound.origin ); +// RAVEN BEGIN + ReadVec3( refSound.velocity ); +// RAVEN END + ReadInt( refSound.listenerId ); + ReadSoundShader( refSound.shader ); + ReadFloat( refSound.diversity ); + ReadBool( refSound.waitfortrigger ); + + ReadFloat( refSound.parms.minDistance ); + ReadFloat( refSound.parms.maxDistance ); + ReadFloat( refSound.parms.volume ); + ReadFloat( refSound.parms.shakes ); + ReadInt( refSound.parms.soundShaderFlags ); + ReadInt( refSound.parms.soundClass ); +} + +/* +================ +idRestoreGame::ReadRenderView +================ +*/ +void idRestoreGame::ReadRenderView( renderView_t &view ) { + int i; + +// RAVEN BEGIN + file->ReadSyncId( "ReadRenderView" ); +// RAVEN END + + ReadInt( view.viewID ); + ReadInt( view.x ); + ReadInt( view.y ); + ReadInt( view.width ); + ReadInt( view.height ); + + ReadFloat( view.fov_x ); + ReadFloat( view.fov_y ); + ReadVec3( view.vieworg ); + ReadMat3( view.viewaxis ); + + ReadBool( view.cramZNear ); + + ReadInt( view.time ); + + for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + ReadFloat( view.shaderParms[ i ] ); + } +} + +/* +================= +idRestoreGame::ReadUsercmd +================= +*/ +void idRestoreGame::ReadUsercmd( usercmd_t &usercmd ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadUsercmd" ); +// RAVEN END + ReadInt( usercmd.gameFrame ); + ReadInt( usercmd.gameTime ); + ReadInt( usercmd.duplicateCount ); +// RAVEN BEGIN +// ddynerman: larger button bitfield + ReadShort( usercmd.buttons ); +// RAVEN END + ReadSignedChar( usercmd.forwardmove ); + ReadSignedChar( usercmd.rightmove ); + ReadSignedChar( usercmd.upmove ); + ReadShort( usercmd.angles[0] ); + ReadShort( usercmd.angles[1] ); + ReadShort( usercmd.angles[2] ); + ReadShort( usercmd.mx ); + ReadShort( usercmd.my ); + ReadSignedChar( usercmd.impulse ); + ReadByte( usercmd.flags ); + ReadInt( usercmd.sequence ); +} + +/* +=================== +idRestoreGame::ReadContactInfo +=================== +*/ +void idRestoreGame::ReadContactInfo( contactInfo_t &contactInfo ) { + ReadInt( (int &)contactInfo.type ); + ReadVec3( contactInfo.point ); + ReadVec3( contactInfo.normal ); + ReadFloat( contactInfo.dist ); + ReadInt( contactInfo.contents ); + ReadMaterial( contactInfo.material ); + ReadInt( contactInfo.modelFeature ); + ReadInt( contactInfo.trmFeature ); + ReadInt( contactInfo.entityNum ); + ReadInt( contactInfo.id ); + ReadMaterialType( contactInfo.materialType ); +} + +/* +=================== +idRestoreGame::ReadTrace +=================== +*/ +void idRestoreGame::ReadTrace( trace_t &trace ) { +// RAVEN BEGIN + file->ReadSyncId( "ReadTrace" ); +// RAVEN END + ReadFloat( trace.fraction ); + ReadVec3( trace.endpos ); + ReadMat3( trace.endAxis ); + ReadContactInfo( trace.c ); +} + +/* +===================== +idRestoreGame::ReadClipModel +===================== +*/ +void idRestoreGame::ReadClipModel( idClipModel *&clipModel ) { + bool restoreClipModel; + + ReadBool( restoreClipModel ); + if ( restoreClipModel ) { + clipModel = new idClipModel(); + clipModel->Restore( this ); + } else { + clipModel = NULL; + } +} + +/* +===================== +idRestoreGame::ReadSoundCommands +===================== +*/ +void idRestoreGame::ReadSoundCommands( void ) { + soundSystem->StopAllSounds( SOUNDWORLD_GAME ); + soundSystem->ReadFromSaveGame( SOUNDWORLD_GAME, file ); +} + +/* +===================== +idRestoreGame::ReadBuildNumber +===================== +*/ +void idRestoreGame::ReadBuildNumber( void ) { + ReadInt( buildNumber ); +} + +/* +===================== +idRestoreGame::GetBuildNumber +===================== +*/ +int idRestoreGame::GetBuildNumber( void ) { + return buildNumber; +} + + + +void Cmd_CheckSave_f( const idCmdArgs &args ) +{ + idPlayer *lp = gameLocal.GetLocalPlayer(); + idFile *mp = fileSystem->GetNewFileMemory(); + idSaveGame sg( mp ); + + sg.CallSave_r( lp->GetType(), lp ); + + + mp->Rewind(); + idPlayer test; + idRestoreGame rg( mp ); + + rg.CallRestore_r( test.GetType(), &test ); +} + diff --git a/src/mpgame/gamesys/SaveGame.h b/src/mpgame/gamesys/SaveGame.h new file mode 100644 index 0000000..2773e68 --- /dev/null +++ b/src/mpgame/gamesys/SaveGame.h @@ -0,0 +1,206 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SAVEGAME_H__ +#define __SAVEGAME_H__ + +/* + +Save game related helper classes. + +*/ + +const int INITIAL_RELEASE_BUILD_NUMBER = 1262; + +class idSaveGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idSaveGame( idFile *savefile ); + ~idSaveGame(); + + void Close( void ); + + void AddObject( const idClass *obj ); + void WriteObjectList( void ); + + void Write( const void *buffer, int len ); + void WriteInt( const int value ); + void WriteJoint( const jointHandle_t value ); + void WriteShort( const short value ); + void WriteByte( const byte value ); + void WriteSignedChar( const signed char value ); + void WriteFloat( const float value ); + void WriteBool( const bool value ); + void WriteString( const char *string ); + void WriteVec2( const idVec2 &vec ); + void WriteVec3( const idVec3 &vec ); + void WriteVec4( const idVec4 &vec ); + void WriteVec5( const idVec5 &vec ); + void WriteVec6( const idVec6 &vec ); + void WriteWinding( const idWinding &winding ); + void WriteBounds( const idBounds &bounds ); + void WriteMat3( const idMat3 &mat ); + void WriteAngles( const idAngles &angles ); + void WriteObject( const idClass *obj ); + void WriteStaticObject( const idClass &obj ); + void WriteDict( const idDict *dict ); + void WriteMaterial( const idMaterial *material ); + void WriteSkin( const idDeclSkin *skin ); +// RAVEN BEGIN +// jscott: not using +// void WriteParticle( const idDeclParticle *particle ); +// void WriteFX( const idDeclFX *fx ); +// RAVEN END + void WriteSoundShader( const idSoundShader *shader ); + void WriteModelDef( const class idDeclModelDef *modelDef ); + void WriteModel( const idRenderModel *model ); +// RAVEN BEGIN +// bdube: material type + void WriteMaterialType ( const rvDeclMatType* matType ); + void WriteTable ( const idDeclTable* table ); +// abahr + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteExtrapolate( const idExtrapolate& extrap ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolateAccelDecelLinear& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteInterpolate( const idInterpolate& lerp ); + void WriteRenderEffect( const renderEffect_t &renderEffect ); + void WriteFrustum( const idFrustum& frustum ); + void WriteSyncId( void ); +// RAVEN END + void WriteUserInterface( const idUserInterface *ui, bool unique ); + void WriteRenderEntity( const renderEntity_t &renderEntity ); + void WriteRenderLight( const renderLight_t &renderLight ); + void WriteRefSound( const refSound_t &refSound ); + void WriteRenderView( const renderView_t &view ); + void WriteUsercmd( const usercmd_t &usercmd ); + void WriteContactInfo( const contactInfo_t &contactInfo ); + void WriteTrace( const trace_t &trace ); + void WriteClipModel( const class idClipModel *clipModel ); + void WriteSoundCommands( void ); + + void WriteBuildNumber( const int value ); + +protected: + idFile * file; + + idList objects; + + void CallSave_r( const idTypeInfo *cls, const idClass *obj ); +}; + +class idRestoreGame { +public: + friend void Cmd_CheckSave_f( const idCmdArgs &args ); + + idRestoreGame( idFile *savefile ); + ~idRestoreGame(); + + void CreateObjects( void ); + void RestoreObjects( void ); + void DeleteObjects( void ); + + void Error( const char *fmt, ... ); + + void Read( void *buffer, int len ); + void ReadInt( int &value ); + void ReadJoint( jointHandle_t &value ); + void ReadShort( short &value ); + void ReadByte( byte &value ); + void ReadSignedChar( signed char &value ); + void ReadFloat( float &value ); + void ReadBool( bool &value ); + void ReadString( idStr &string ); + void ReadVec2( idVec2 &vec ); + void ReadVec3( idVec3 &vec ); + void ReadVec4( idVec4 &vec ); + void ReadVec5( idVec5 &vec ); + void ReadVec6( idVec6 &vec ); + void ReadWinding( idWinding &winding ); + void ReadBounds( idBounds &bounds ); + void ReadMat3( idMat3 &mat ); + void ReadAngles( idAngles &angles ); + void ReadObject( idClass *&obj ); + void ReadStaticObject( idClass &obj ); + void ReadDict( idDict *dict ); + void ReadMaterial( const idMaterial *&material ); + void ReadSkin( const idDeclSkin *&skin ); +// RAVEN BEGIN +// bdube: not using +// void ReadParticle( const idDeclParticle *&particle ); +// void ReadFX( const idDeclFX *&fx ); +// RAVEN END + void ReadSoundShader( const idSoundShader *&shader ); + void ReadModelDef( const idDeclModelDef *&modelDef ); + void ReadModel( idRenderModel *&model ); +// RAVEN BEGIN + void ReadUserInterface( idUserInterface *&ui, const idDict *args ); +// bdube: material type + void ReadMaterialType ( const rvDeclMatType* &matType ); + void ReadTable ( const idDeclTable* &table ); +// abahr + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadExtrapolate( idExtrapolate& extrap ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolateAccelDecelLinear& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadInterpolate( idInterpolate& lerp ); + void ReadRenderEffect( renderEffect_t &renderEffect ); + void ReadFrustum( idFrustum& frustum ); + void ReadSyncId( const char *detail = "unspecified", const char *classname = NULL ) { file->ReadSyncId( detail, classname ); } + void ReadRenderEntity( renderEntity_t &renderEntity, const idDict *args ); +// RAVEN END + void ReadRenderLight( renderLight_t &renderLight ); + void ReadRefSound( refSound_t &refSound ); + void ReadRenderView( renderView_t &view ); + void ReadUsercmd( usercmd_t &usercmd ); + void ReadContactInfo( contactInfo_t &contactInfo ); + void ReadTrace( trace_t &trace ); + void ReadClipModel( idClipModel *&clipModel ); + void ReadSoundCommands( void ); + + void ReadBuildNumber( void ); + + // Used to retrieve the saved game buildNumber from within class Restore methods + int GetBuildNumber( void ); + +private: + int buildNumber; + + idFile * file; + + idList objects; + + void CallRestore_r( const idTypeInfo *cls, idClass *obj ); +}; + +#endif /* !__SAVEGAME_H__*/ diff --git a/src/mpgame/gamesys/State.cpp b/src/mpgame/gamesys/State.cpp new file mode 100644 index 0000000..33a3aaf --- /dev/null +++ b/src/mpgame/gamesys/State.cpp @@ -0,0 +1,448 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const int HISTORY_COUNT = 50; + +/* +===================== +stateParms_t::Save +===================== +*/ +void stateParms_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( blendFrames ); + saveFile->WriteInt( time ); + saveFile->WriteInt( stage ); +} + +/* +===================== +stateParms_t::Restore +===================== +*/ +void stateParms_t::Restore( idRestoreGame *saveFile ) { + saveFile->ReadInt( blendFrames ); + saveFile->ReadInt( time ); + saveFile->ReadInt( stage ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( state->name ); + // TOSAVE: idLinkList node; + saveFile->WriteInt( flags ); + saveFile->WriteInt( delay ); + parms.Save( saveFile ); +} + +/* +===================== +stateCall_t::Save +===================== +*/ +void stateCall_t::Restore( idRestoreGame *saveFile, const idClass* owner ) { + idStr name; + + saveFile->ReadString( name ); + state = owner->FindState( name ); + + saveFile->ReadInt( flags ); + saveFile->ReadInt( delay ); + parms.Restore( saveFile ); +} + +/* +===================== +rvStateThread::rvStateThread +===================== +*/ +rvStateThread::rvStateThread ( void ) { + owner = NULL; + insertAfter = NULL; + lastResult = SRESULT_DONE; + + states.Clear ( ); + interrupted.Clear ( ); + + memset ( &fl, 0, sizeof(fl) ); +} + +/* +===================== +rvStateThread::~rvStateThread +===================== +*/ +rvStateThread::~rvStateThread ( void ) { + Clear ( true ); +} + +/* +===================== +rvStateThread::SetOwner +===================== +*/ +void rvStateThread::SetOwner ( idClass* _owner ) { + owner = _owner; +} + +/* +===================== +rvStateThread::Post +===================== +*/ +stateResult_t rvStateThread::PostState ( const char* name, int blendFrames, int delay, int flags ) { + const rvStateFunc* func; + + // Make sure the state exists before queueing it + if ( NULL == (func = owner->FindState ( name ) ) ) { + return SRESULT_ERROR; + } + + stateCall_t* call; + call = new stateCall_t; + call->state = func; + call->delay = delay; + call->flags = flags; + call->parms.blendFrames = blendFrames; + call->parms.time = -1; + call->parms.stage = 0; + + call->node.SetOwner ( call ); + + if ( fl.executing && insertAfter ) { + call->node.InsertAfter ( insertAfter->node ); + } else { + call->node.AddToEnd ( states ); + } + + insertAfter = call; + + return SRESULT_OK; +} + +/* +===================== +rvStateThread::Set +===================== +*/ +stateResult_t rvStateThread::SetState ( const char* name, int blendFrames, int delay, int flags ) { + Clear ( ); + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::InterruptState +===================== +*/ +stateResult_t rvStateThread::InterruptState ( const char* name, int blendFrames, int delay, int flags ) { + stateCall_t* call; + + // Move all states to the front of the interrupted list in the same order + for ( call = states.Prev(); call; call = states.Prev() ) { + call->node.Remove ( ); + call->node.AddToFront ( interrupted ); + } + + // Nothing to insert after anymore + insertAfter = NULL; + fl.stateInterrupted = true; + + // Post the state now + return PostState ( name, blendFrames, delay, flags ); +} + +/* +===================== +rvStateThread::CurrentStateIs +===================== +*/ +bool rvStateThread::CurrentStateIs( const char* name ) const { + return ( !IsIdle() ) ? owner->FindState(name) == GetState()->state : false; +} + +/* +===================== +rvStateThread::Clear +===================== +*/ +void rvStateThread::Clear ( bool ignoreStateCalls ) { + stateCall_t* call; + + // Clear all states from the main state list + for( call = states.Next(); call != NULL; call = states.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + // Clear all interrupted states + for( call = interrupted.Next(); call != NULL; call = interrupted.Next() ) { + if ( !ignoreStateCalls && (call->flags & (SFLAG_ONCLEAR|SFLAG_ONCLEARONLY) ) ) { + owner->ProcessState ( call->state, call->parms ); + } + call->node.Remove(); + delete call; + } + + insertAfter = NULL; + fl.stateCleared = true; + + states.Clear ( ); + interrupted.Clear ( ); +} + +/* +===================== +rvStateThread::Execute +===================== +*/ +stateResult_t rvStateThread::Execute ( void ) { + stateCall_t* call = NULL; + int count; + const char* stateName; + int stateStage; + const char* historyState[HISTORY_COUNT]; + int historyStage[HISTORY_COUNT]; + int historyStart; + int historyEnd; + + // If our main state loop is empty copy over any states in the interrupted state + if ( !states.Next ( ) ) { + for ( call = interrupted.Next(); call; call = interrupted.Next() ) { + call->node.Remove ( ); + call->node.AddToEnd ( states ); + } + assert ( !interrupted.Next ( ) ); + } + + // State thread is idle if there are no states + if ( !states.Next() ) { + return SRESULT_IDLE; + } + + fl.executing = true; + + // Run through the states until there are no more or one of them tells us to wait + count = 0; + historyStart = 0; + historyEnd = 0; + + for( call = states.Next(); call && count < HISTORY_COUNT; call = states.Next(), ++count ) { + insertAfter = call; + fl.stateCleared = false; + fl.stateInterrupted = false; + + // If this state is only called when being cleared then just skip it + if ( call->flags & SFLAG_ONCLEARONLY ) { + call->node.Remove ( ); + delete call; + continue; + } + + // If the call has a delay on it the time will be set to negative initially and then + // converted to game time. + if ( call->parms.time <= 0 ) { + call->parms.time = gameLocal.time; + } + + // Check for delayed states + if ( call->delay && gameLocal.time < call->parms.time + call->delay ) { + fl.executing = false; + return SRESULT_WAIT; + } + + // Debugging + if ( lastResult != SRESULT_WAIT ) { + if ( *g_debugState.GetString ( ) && (*g_debugState.GetString ( ) == '*' || !idStr::Icmp ( g_debugState.GetString ( ), name ) ) ) { + if ( call->parms.stage ) { + gameLocal.Printf ( "%s: %s (%d)\n", name.c_str(), call->state->name, call->parms.stage ); + } else { + gameLocal.Printf ( "%s: %s\n", name.c_str(), call->state->name ); + } + } + + // Keep a history of the called states so we can dump them on an overflow + historyState[historyEnd] = call->state->name; + historyStage[historyEnd] = call->parms.stage; + historyEnd = (historyEnd+1) % HISTORY_COUNT; + if ( historyEnd == historyStart ) { + historyStart = (historyEnd+1) % HISTORY_COUNT; + } + } + + // Cache name and stage for error messages + stateName = call->state->name; + stateStage = call->parms.stage; + + // Actually call the state function + lastResult = owner->ProcessState ( call->state, call->parms ); + switch ( lastResult ) { + case SRESULT_WAIT: + fl.executing = false; + return SRESULT_WAIT; + + case SRESULT_ERROR: + gameLocal.Error ( "rvStateThread: error reported by state '%s (%d)'", stateName, stateStage ); + fl.executing = false; + return SRESULT_ERROR; + } + + // Dont remove the node if it was interrupted or cleared in the last process + if ( !fl.stateCleared && !fl.stateInterrupted ) { + if( lastResult >= SRESULT_SETDELAY ) { + call->delay = lastResult - SRESULT_SETDELAY; + call->parms.time = gameLocal.GetTime(); + continue; + } else if ( lastResult >= SRESULT_SETSTAGE ) { + call->parms.stage = lastResult - SRESULT_SETSTAGE; + continue; + } + + // Done with state so remove it from list + call->node.Remove ( ); + delete call; + } + + // Finished the last state but wait a frame for next one + if ( lastResult == SRESULT_DONE_WAIT ) { + fl.executing = false; + return SRESULT_WAIT; + } + } + + // Runaway state loop? + if ( count >= HISTORY_COUNT ) { + idFile *file; + + fileSystem->RemoveFile ( "statedump.txt" ); + file = fileSystem->OpenFileWrite( "statedump.txt" ); + + for ( ; historyStart != historyEnd; historyStart = (historyStart + 1) % HISTORY_COUNT ) { + if ( historyStage[historyStart] ) { + gameLocal.Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + gameLocal.Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + if ( file ) { + if ( historyStage[historyStart] ) { + file->Printf ( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] ); + } else { + file->Printf ( "rvStateThread: %s\n", historyState[historyStart] ); + } + } + } + if ( file ) { + fileSystem->CloseFile( file ); + } + + gameLocal.Error ( "rvStateThread: run away state loop '%s'", name.c_str() ); + } + + insertAfter = NULL; + fl.executing = false; + + // Move interrupted states back into the main state list when the main state list is empty + if ( !states.Next() && interrupted.Next ( ) ) { + return Execute ( ); + } + + return lastResult; +} + +/* +===================== +rvStateThread::Save +===================== +*/ +void rvStateThread::Save( idSaveGame *saveFile ) const { + saveFile->WriteString( name.c_str() ); + + // No need to save owner, its setup in restore + + saveFile->WriteInt( lastResult ); + saveFile->Write ( &fl, sizeof(fl) ); + + saveFile->WriteInt( states.Num() ); + for( idLinkList* node = states.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + saveFile->WriteInt( interrupted.Num() ); + for( idLinkList* node = interrupted.NextNode(); node; node = node->NextNode() ) { + node->Owner()->Save( saveFile ); + } + + // TOSAVE: stateCall_t* insertAfter; + // TOSAVE: stateResult_t lastResult; +} + +/* +===================== +rvStateThread::Restore +===================== +*/ +void rvStateThread::Restore( idRestoreGame *saveFile, idClass* owner ) { + int numStates; + stateCall_t* call = NULL; + + saveFile->ReadString( name ); + + this->owner = owner; + + saveFile->ReadInt( (int&)lastResult ); + saveFile->Read ( &fl, sizeof(fl) ); + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( states ); + } + + saveFile->ReadInt( numStates ); + for( ; numStates > 0; numStates-- ) { + call = new stateCall_t; + assert( call ); + + call->Restore( saveFile, owner ); + + call->node.SetOwner ( call ); + call->node.AddToEnd ( interrupted ); + } +} diff --git a/src/mpgame/gamesys/State.h b/src/mpgame/gamesys/State.h new file mode 100644 index 0000000..2355837 --- /dev/null +++ b/src/mpgame/gamesys/State.h @@ -0,0 +1,181 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_STATE_H__ +#define __SYS_STATE_H__ + +typedef enum { + SRESULT_OK, // Call was made successfully + SRESULT_ERROR, // An unrecoverable error occurred + SRESULT_DONE, // Done with current state, move to next + SRESULT_DONE_WAIT, // Done with current state, wait a frame then move to next + SRESULT_WAIT, // Wait a frame and re-run current state + SRESULT_IDLE, // State thread is currently idle (ie. no states) + SRESULT_SETSTAGE, // Sets the current stage of the current state and reruns the state + // NOTE: this has to be the last result becuase the stage is added to + // the result. + SRESULT_SETDELAY = SRESULT_SETSTAGE + 20 +} stateResult_t; + +#define MAX_STATE_CALLS 50 + +#define SRESULT_STAGE(x) ((stateResult_t)((int)SRESULT_SETSTAGE + (int)(x))) +#define SRESULT_DELAY(x) ((stateResult_t)((int)SRESULT_SETDELAY + (int)(x))) + +struct stateParms_t { + int blendFrames; + int time; + int stage; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); +}; + +typedef stateResult_t ( idClass::*stateCallback_t )( const stateParms_t& parms ); + +template< class Type > +struct rvStateFunc { + const char* name; + stateCallback_t function; +}; + +/* +================ +CLASS_STATES_PROTOTYPE + +This macro must be included in the definition of any subclass of idClass that +wishes to have its own custom states. Its prototypes variables used in the process +of managing states. +================ +*/ +#define CLASS_STATES_PROTOTYPE(nameofclass) \ +protected: \ + static rvStateFunc stateCallbacks[] + +/* +================ +CLASS_STATES_DECLARATION + +This macro must be included in the code to properly initialize variables +used in state processing for a idClass dervied class +================ +*/ +#define CLASS_STATES_DECLARATION(nameofclass) \ +rvStateFunc nameofclass::stateCallbacks[] = { + +/* +================ +STATE + +This macro declares a single state. It must be surrounded by the CLASS_STATES_DECLARATION +and END_CLASS_STATES macros. +================ +*/ +#define STATE(statename,function) { statename, (stateCallback_t)( &function ) }, + +/* +================ +END_CLASS_STATES + +Terminates a state block +================ +*/ +#define END_CLASS_STATES { NULL, NULL } }; + +struct stateCall_t { + const rvStateFunc* state; + idLinkList node; + int flags; + int delay; + stateParms_t parms; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, const idClass* owner ); +}; + +class idClass; + +const int SFLAG_ONCLEAR = BIT(0); // Executes, even if the state queue is cleared +const int SFLAG_ONCLEARONLY = BIT(1); // Executes only if the state queue is cleared + +class rvStateThread { +public: + + rvStateThread ( void ); + ~rvStateThread ( void ); + + void SetName ( const char* name ); + void SetOwner ( idClass* owner ); + + bool Interrupt ( void ); + + stateResult_t InterruptState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t PostState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateResult_t SetState ( const char* state, int blendFrames = 0, int delay = 0, int flags = 0 ); + stateCall_t* GetState ( void ) const; + bool CurrentStateIs ( const char* name ) const; + + stateResult_t Execute ( void ); + + void Clear ( bool ignoreStateCalls = false ); + + bool IsIdle ( void ) const; + bool IsExecuting ( void ) const; + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile, idClass* owner ); + +protected: + + struct flags { + bool stateCleared :1; // State list was cleared + bool stateInterrupted :1; // State list was interrupted + bool executing :1; // Execute is currently processing states + } fl; + + idStr name; + idClass* owner; + idLinkList states; + idLinkList interrupted; + stateCall_t* insertAfter; + stateResult_t lastResult; +}; + +ID_INLINE void rvStateThread::SetName ( const char* _name ) { + name = _name; +} + +ID_INLINE stateCall_t* rvStateThread::GetState ( void ) const { + return states.Next(); +} + +ID_INLINE bool rvStateThread::IsIdle ( void ) const { + return !states.Next() && !interrupted.Next(); +} + +ID_INLINE bool rvStateThread::IsExecuting ( void ) const { + return fl.executing; +} + +#endif // __SYS_STATE_H__ diff --git a/src/mpgame/gamesys/SysCmds.cpp b/src/mpgame/gamesys/SysCmds.cpp new file mode 100644 index 0000000..122738a --- /dev/null +++ b/src/mpgame/gamesys/SysCmds.cpp @@ -0,0 +1,3283 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +// RAVEN BEGIN +#include "../ai/AI.h" +#if !defined(__GAME_PROJECTILE_H__) + #include "../Projectile.h" +#endif +#if !defined(__GAME_WEAPON_H__) + #include "../Weapon.h" +#endif +#if !defined(__GAME_SPAWNER_H__) + #include "../spawner.h" +#endif +#if !defined(__GAME_VEHICLE_H__) + #include "../Vehicle/Vehicle.h" +#endif +#if !defined(__AI_MANAGER_H__) + #include "../ai/AI_Manager.h" +#endif +#if !defined(__INSTANCE_H__) + #include "../Instance.h" +#endif +// RAVEN END + +#ifdef _WIN32 +#include "TypeInfo.h" +#else +#include "NoGameTypeInfo.h" +#endif + +/* +================== +Cmd_GetFloatArg +================== +*/ +float Cmd_GetFloatArg( const idCmdArgs &args, int &argNum ) { + const char *value; + + value = args.Argv( argNum++ ); + return atof( value ); +} + +/* +=================== +Cmd_EntityList_f +=================== +*/ +void Cmd_EntityList_f( const idCmdArgs &args ) { + int e; + idEntity *check; + int count; + size_t size; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + size = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_GENTITIES; e++ ) { + check = gameLocal.entities[ e ]; + + if ( !check ) { + continue; + } + + if ( !check->name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s %-20s %s\n", e, + check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + + count++; + size += check->spawnArgs.Allocated(); + } + + gameLocal.Printf( "...%d entities\n...%d bytes of spawnargs\n", count, size ); +} + +/* +=================== +Cmd_ClientEntityList_f +=================== +*/ +void Cmd_ClientEntityList_f( const idCmdArgs &args ) { + int e; + rvClientEntity *check; + int count; + idStr match; + + if ( args.Argc() > 1 ) { + match = args.Args(); + match.Replace( " ", "" ); + } else { + match = ""; + } + + count = 0; + + gameLocal.Printf( "%-4s %-20s\n", " Num", "Classname" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( e = 0; e < MAX_CENTITIES; e++ ) { + check = gameLocal.clientEntities[ e ]; + + idStr name( check->GetClassType().classname ); + + if ( !check ) { + continue; + } + + if ( !name.Filter( match ) ) { + continue; + } + + gameLocal.Printf( "%4i: %-20s\n", e, name.c_str() ); + + count++; + } + + gameLocal.Printf( "...%d entities\n", count ); +} + + +/* +=================== +Cmd_ActiveEntityList_f +=================== +*/ +void Cmd_ActiveEntityList_f( const idCmdArgs &args ) { + idEntity *check; + int count; + + count = 0; + + gameLocal.Printf( "%-4s %-20s %-20s %s\n", " Num", "EntityDef", "Class", "Name" ); + gameLocal.Printf( "--------------------------------------------------------------------\n" ); + for( check = gameLocal.activeEntities.Next(); check != NULL; check = check->activeNode.Next() ) { + char dormant = check->fl.isDormant ? '-' : ' '; + gameLocal.Printf( "%4i:%c%-20s %-20s %s\n", check->entityNumber, dormant, check->GetEntityDefName(), check->GetClassname(), check->name.c_str() ); + count++; + } + + gameLocal.Printf( "...%d active entities\n", count ); +} + +/* +=================== +Cmd_ListSpawnArgs_f +=================== +*/ +void Cmd_ListSpawnArgs_f( const idCmdArgs &args ) { + int i; + idEntity *ent; + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + for ( i = 0; i < ent->spawnArgs.GetNumKeyVals(); i++ ) { + const idKeyValue *kv = ent->spawnArgs.GetKeyVal( i ); + gameLocal.Printf( "\"%s\" "S_COLOR_WHITE"\"%s\"\n", kv->GetKey().c_str(), kv->GetValue().c_str() ); + } +} + +/* +=================== +Cmd_ReloadScript_f +=================== +*/ +void Cmd_ReloadScript_f( const idCmdArgs &args ) { + // shutdown the map because entities may point to script objects + gameLocal.MapShutdown(); + + // recompile the scripts + gameLocal.program.Startup( SCRIPT_DEFAULT ); + + // error out so that the user can rerun the scripts + gameLocal.Error( "Exiting map to reload scripts" ); +} + +/* +=================== +Cmd_Script_f +=================== +*/ +void Cmd_Script_f( const idCmdArgs &args ) { + const char * script; + idStr text; + idStr funcname; + static int funccount = 0; + idThread * thread; + const function_t *func; + idEntity *ent; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + sprintf( funcname, "ConsoleFunction_%d", funccount++ ); + + script = args.Args(); +// RAVEN BEGIN +// jscott: fixed sprintf to idStr + text = va( "void %s() {%s;}\n", funcname.c_str(), script ); +// RAVEN END + if ( gameLocal.program.CompileText( "console", text, true ) ) { + func = gameLocal.program.FindFunction( funcname ); + if ( func ) { + // set all the entity names in case the user named one in the script that wasn't referenced in the default script + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + gameLocal.program.SetEntity( ent->name, ent ); + } + + thread = new idThread( func ); + thread->Start(); + } + } +} + +// RAVEN BEGIN +// jscott: exports for tracking memory +/* +================== +idGameEdit::ScriptSummary +================== +*/ +size_t idGameEdit::ScriptSummary( const idCmdArgs &args ) const { + + return( gameLocal.program.ScriptSummary( args ) ); +} + +/* +================== +idGameEdit::ClassSummary +================== +*/ +size_t idGameEdit::ClassSummary( const idCmdArgs &args ) const { + + common->Printf( "Classes - %dK\n", idClass::GetUsedMemory() / 1024 ); + + return( idClass::GetUsedMemory() / 1024 ); +} + +/* +================== +idGameEdit::EntitySummary +================== +*/ + +size_t idGameEdit::EntitySummary( const idCmdArgs &args ) const { + + common->Printf( "CL & SV ents - %dK\n", gameLocal.GetEntityMemoryUsage () / 1024); + + return gameLocal.GetEntityMemoryUsage() / 1024; +} +// RAVEN END + +/* +================== +KillEntities + +Kills all the entities of the given class in a level. +================== +*/ +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ) { + idEntity *ent; + idStrList ignore; + const char *name; + int i; + + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for( i = 1; i < args.Argc(); i++ ) { + name = args.Argv( i ); + ignore.Append( name ); + } + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( superClass ) ) { + for( i = 0; i < ignore.Num(); i++ ) { + if ( ignore[ i ] == ent->name ) { + break; + } + } + + if ( i >= ignore.Num() ) { + ent->PostEventMS( &EV_Remove, 0 ); + } + } + } +} + +/* +================== +Cmd_KillMonsters_f + +Kills all the monsters in a level. +================== +*/ +void Cmd_KillMonsters_f( const idCmdArgs &args ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAI::GetClassType() ); +// nmckenzie: rvSpawners + KillEntities( args, rvSpawner::GetClassType() ); + + // kill any projectiles as well since they have pointers to the monster that created them + KillEntities( args, idProjectile::GetClassType() ); +// RAVEN END +} + +/* +================== +Cmd_KillMovables_f + +Kills all the moveables in a level. +================== +*/ +void Cmd_KillMovables_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idMoveable::GetClassType() ); +// RAVEN END +} + +// RAVEN BEGIN +// bdube: vehicle code +/* +================== +Cmd_KillVehicles_f +================== +*/ +void Cmd_KillVehicles_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + rvVehicleController::KillVehicles ( ); +} + +void Cmd_KillMessage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + gameLocal.mpGame.SendDeathMessage( gameLocal.GetLocalPlayer(), gameLocal.GetLocalPlayer(), 2, false ); +} + +void Cmd_APState_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for ( int i = 0; i < gameLocal.mpGame.assaultPoints.Num(); i++ ) { + gameLocal.Printf ( "Assault point #%d: owner: %d\n", gameLocal.mpGame.assaultPoints[i]->GetIndex(), gameLocal.mpGame.assaultPoints[i]->GetOwner() ); + } +} +// RAVEN END + +/* +================== +Cmd_KillRagdolls_f + +Kills all the ragdolls in a level. +================== +*/ +void Cmd_KillRagdolls_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + KillEntities( args, idAFEntity_Generic::GetClassType() ); + KillEntities( args, idAFEntity_WithAttachedHead::GetClassType() ); +// RAVEN END +} + + +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +/* +================== +GiveStuffToPlayer + +Used by the "give" and "buy" command line cmds +================== +*/ +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ) +{ + int i; + bool give_all; +// idPlayer* player = gameLocal.GetLocalPlayer(); + + if( !player || !name ) { + return; + } + + if( !value ) { + value = ""; + } + + if ( idStr::Icmp( name, "all" ) == 0 ) { + give_all = true; + } else { + give_all = false; + } + + if ( give_all || ( idStr::Cmpn( name, "weapon", 6 ) == 0 ) ) { + if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) { + gameLocal.world->spawnArgs.SetBool( "no_Weapons", false ); + for( i = 0; i < gameLocal.numClients; i++ ) { + if ( gameLocal.entities[ i ] ) { + gameLocal.entities[ i ]->PostEventSec( &EV_Player_SelectWeapon, 0.5f, gameLocal.entities[ i ]->spawnArgs.GetString( "def_weapon1" ) ); + } + } + } + } + + if ( ( idStr::Cmpn( name, "weapon_", 7 ) == 0 ) || ( idStr::Cmpn( name, "item_", 5 ) == 0 ) || ( idStr::Cmpn( name, "ammo_", 5 ) == 0 ) || ( idStr::Icmp( name, "ammorefill" ) == 0 ) ) { + player->GiveItem( name ); + return; + } + + if ( give_all || idStr::Icmp( name, "health" ) == 0 ) { + player->health = player->inventory.maxHealth; + if ( player->IsInVehicle() ) { + player->GetVehicleController().Give ( "health", "9999" ); + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "weapons" ) == 0 ) { + player->inventory.weapons = BIT( MAX_WEAPONS ) - 1; + player->CacheWeapons(); + + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "ammo" ) == 0 ) { +// RAVEN BEGIN +// bdube: define changed + for ( i = 0 ; i < MAX_AMMOTYPES; i++ ) { + player->inventory.ammo[ i ] = player->inventory.MaxAmmoForAmmoClass( player, rvWeapon::GetAmmoNameForIndex( i ) ); +// RAVEN END + } + if ( !give_all ) { + return; + } + } + + if ( give_all || idStr::Icmp( name, "armor" ) == 0 ) { + player->inventory.armor = player->inventory.maxarmor; + if ( !give_all ) { + return; + } + } +// RAVEN BEGIN + if (idStr::Icmp(name, "quad") == 0) { + player->GivePowerUp( POWERUP_QUADDAMAGE, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "invis" ) == 0 ) { + player->GivePowerUp( POWERUP_INVISIBILITY, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "regen" ) == 0 ) { + player->GivePowerUp( POWERUP_REGENERATION, SEC2MS( 30.0f ) ); + return; + } + + if ( idStr::Icmp( name, "haste" ) == 0 ) { + player->GivePowerUp( POWERUP_HASTE, SEC2MS( 30.0f ) ); + return; + } + + if (idStr::Icmp(name, "ammoregen") == 0) { + player->GivePowerUp( POWERUP_AMMOREGEN, -1 ); + return; + } + + if (idStr::Icmp(name, "scout") == 0) { + player->GivePowerUp( POWERUP_SCOUT, -1 ); + return; + } + + if (idStr::Icmp(name, "doubler") == 0) { + player->GivePowerUp( POWERUP_DOUBLER, -1 ); + return; + } + + if (idStr::Icmp(name, "guard") == 0) { + player->GivePowerUp( POWERUP_GUARD, -1 ); + return; + } +// RAVEN END + + if ( !idStr::Icmp ( name, "wpmod_all" ) ) { + player->GiveWeaponMods ( 0xFFFFFFFF ); + return; + } else if ( !idStr::Cmpn( name, "wpmod_", 6 ) ) { + player->GiveWeaponMod(name); + return; + } + + if ( !idStr::Cmpn( name, "stroggmod_", 10 ) ) { + player->Give ( name, "" ); + return; + } + + if ( !give_all && !player->Give( name, value ) ) { + gameLocal.Printf( "unknown item\n" ); + } +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + GiveStuffToPlayer( player, args.Argv(1), args.Argv(2) ); +} +// RITUAL END + +/* +================== +Cmd_CenterView_f + +Centers the players pitch +================== +*/ +void Cmd_CenterView_f( const idCmdArgs &args ) { + idPlayer *player; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + ang = player->viewAngles; + ang.pitch = 0.0f; + player->SetViewAngles( ang ); +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->godmode ) { + player->godmode = false; + msg = "godmode OFF\n"; + } else { + player->godmode = true; + msg = "godmode ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Undying_f + +Sets client to undying + +argv(0) undying +================== +*/ +void Cmd_Undying_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->undying ) { + player->undying = false; + msg = "undying OFF\n"; + } else { + player->undying = true; + msg = "undying ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->fl.notarget ) { + player->fl.notarget = false; + msg = "notarget OFF\n"; + } else { + player->fl.notarget = true; + msg = "notarget ON\n"; + } + + gameLocal.Printf( "%s", msg ); +} + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( const idCmdArgs &args ) { + char *msg; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + player->noclip = !player->noclip; + + gameLocal.Printf( "%s", msg ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( gameLocal.isMultiplayer ) { + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_KILL ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "kill or kill \n" ); + return; + } + player->Kill( false, false ); +// RAVEN BEGIN +// rhummer: localized this string.. (killed client) + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say %s %d '%s^0'\n", common->GetLocalizedString( "#str_108022" ), player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); +// RAVEN END + } + } else { + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + player->Kill( false, false ); + } +} + +// RAVEN BEGIN +// bdube: jump points +/* +================= +Cmd_DebugJump_f +================= +*/ +void Cmd_DebugJump_f( const idCmdArgs &args ) { + if (args.Argc() > 1) { + // going to a specific jump point as specified by second argument + gameDebug.JumpTo ( args.Argv( 1 ) ); + } else { + // just go to next jump point as specified + gameDebug.JumpNext ( ); + } +} + +/* +================= +Cmd_DebugNextJumpPoint_f +================= +*/ +void Cmd_DebugNextJumpPoint_f( const idCmdArgs &args ) { + // just go to next jump point as specified + gameDebug.JumpNext ( ); +} + +/* +================= +Cmd_DebugPrevJumpPoint_f +================= +*/ +void Cmd_DebugPrevJumpPoint_f( const idCmdArgs &args ) { + // just go to previous jump point as specified + gameDebug.JumpPrev ( ); +} + +/* +================= +Cmd_AASExtractTactical_f +================= +*/ +void Cmd_AASExtractTactical_f( const idCmdArgs &args ) { + if (gameLocal.GetLocalPlayer()) + { + gameLocal.GetLocalPlayer()->aasSensor->SearchDebug(); + } +} + +/* +================= +Cmd_CallScriptFunc_f +================= +*/ +void Cmd_CallScriptFunc_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: call FuncName ...\n" ); + return; + } + + idDict returnDict; + rvScriptFuncUtility util; + + if( !util.Init(args) ) { + return; + } + + util.CallFunc( &returnDict ); + + if( util.ReturnsAVal() && util.GetReturnKey() && util.GetReturnKey()[0] ) { + gameLocal.Printf( "%s: %s\n", util.GetReturnKey(), returnDict.GetString(util.GetReturnKey()) ); + } +} + +void Cmd_SetPlayerGravity_f( const idCmdArgs& args ) { + if( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: setPlayerGravity 'x_magnitude y_magnitude z_magnitude\n" ); + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + if( !player ) { + return; + } + + idVec3 gravity; + sscanf( args.Argv(1), "%f %f %f", &gravity.x, &gravity.y, &gravity.z ); + player->GetPhysics()->SetGravity( gravity ); +} +// RAVEN END + +/* +================= +Cmd_PlayerModel_f +================= +*/ +void Cmd_PlayerModel_f( const idCmdArgs &args ) { + idPlayer *player; + const char *name; + idVec3 pos; + idAngles ang; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: playerModel \n" ); + return; + } + + name = args.Argv( 1 ); + player->spawnArgs.Set( "model", name ); + + pos = player->GetPhysics()->GetOrigin(); + ang = player->viewAngles; + player->SpawnToPoint( pos, ang ); +} + +/* +================== +Cmd_Say +================== +*/ +static void Cmd_Say( bool team, const idCmdArgs &args ) { + const char *name; + idStr text; + const char *cmd = team ? "sayTeam" : "say" ; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "%s can only be used in a multiplayer game\n", cmd ); + return; + } + + if ( args.Argc() < 2 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + + text = args.Args(); +// RAVEN BEGIN +// bdube: make sure text was specified + if ( text.Length() == 0 ) { + gameLocal.Printf( "usage: %s \n", cmd ); + return; + } + + text.Replace( "&bsl;", "\\" ); + text.Replace( "&", "&" ); + +// asalmon: check to see if the text passes the live decency standard +#ifdef _XENON + if(!Sys_VerifyString(text.c_str())) + { + gameLocal.Printf( "Your message did not pass Xbox decency standards\n"); + return; + } +#endif + + +// ddynerman: team speak only in team games + if ( team && !gameLocal.IsTeamGame() ) { + team = false; + } +// RAVEN END + if ( text[ text.Length() - 1 ] == '\n' ) { + text[ text.Length() - 1 ] = '\0'; + } + name = "player"; + + idPlayer * player; + + // here we need to special case a listen server to use the real client name instead of "server" + // "server" will only appear on a dedicated server + if ( gameLocal.isClient || cvarSystem->GetCVarInteger( "net_serverDedicated" ) == 0 ) { + player = gameLocal.GetLocalPlayer(); + if ( player ) { + if ( player->GetUserInfo() ) { + name = player->GetUserInfo()->GetString( "ui_name", "player" ); + } + +// RAVEN BEGIN +// mekberg: activate the mphud gui so the time is right before receiving the chat message + if ( player->mphud ) { + player->mphud->Activate( true, gameLocal.time ); + } + + if (player->IsFakeClient()) + { + name = cvarSystem->GetCVarString( "ui_name" ); + } + } +// RAVEN END + } else { + name = "server"; + } + + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ 256 ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( team ? GAME_RELIABLE_MESSAGE_TCHAT : GAME_RELIABLE_MESSAGE_CHAT ); + outMsg.WriteString( name ); + outMsg.WriteString( text ); + outMsg.WriteString( "" ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + gameLocal.mpGame.ProcessChatMessage( gameLocal.localClientNum, team, name, text, NULL ); + } +} + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( const idCmdArgs &args ) { + Cmd_Say( false, args ); +} + +/* +================== +Cmd_SayTeam_f +================== +*/ +static void Cmd_SayTeam_f( const idCmdArgs &args ) { + Cmd_Say( true, args ); +} + +/* +================== +Cmd_AddChatLine_f +================== +*/ +static void Cmd_AddChatLine_f( const idCmdArgs &args ) { + gameLocal.mpGame.AddChatLine( args.Argv( 1 ) ); +} + +/* +================== +Cmd_Kick_f +================== +*/ +static void Cmd_Kick_f( const idCmdArgs &args ) { + idPlayer *player; + + if ( !gameLocal.isMultiplayer ) { + gameLocal.Printf( "kick can only be used in a multiplayer game\n" ); + return; + } + + if ( gameLocal.isClient ) { + gameLocal.Printf( "You have no such power. This is a server command\n" ); + return; + } + + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + gameLocal.Printf( "usage: kick or kick \n" ); + return; + } + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say kicking out client %d '%s^0'\n", player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %d\n", player->entityNumber ) ); +} + +/* +================== +Cmd_GetViewpos_f +================== +*/ +void Cmd_GetViewpos_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + const renderView_t *view = player->GetRenderView(); + if ( view ) { + gameLocal.Printf( "(%s) %.1f\n", view->vieworg.ToString(), view->viewaxis[0].ToYaw() ); + } else { + player->GetViewPos( origin, axis ); + gameLocal.Printf( "(%s) %.1f\n", origin.ToString(), axis[0].ToYaw() ); + } +} + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + int i; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( ( args.Argc() != 4 ) && ( args.Argc() != 5 ) ) { + gameLocal.Printf( "usage: setviewpos \n" ); + return; + } + + angles.Zero(); + if ( args.Argc() == 5 ) { + angles.yaw = atof( args.Argv( 4 ) ); + } + + for ( i = 0 ; i < 3 ; i++ ) { + origin[i] = atof( args.Argv( i + 1 ) ); + } + origin.z -= pm_normalviewheight.GetFloat() - 0.25f; + + player->Teleport( origin, angles, NULL ); +} + +/* +================= +Cmd_Teleport_f +================= +*/ +void Cmd_Teleport_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: teleport \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + angles.Zero(); + angles.yaw = ent->GetPhysics()->GetAxis()[ 0 ].ToYaw(); + origin = ent->GetPhysics()->GetOrigin(); + + player->Teleport( origin, angles, ent ); +} + +/* +================= +Cmd_Trigger_f +================= +*/ +void Cmd_Trigger_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: trigger \n" ); + return; + } + + ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, player ); + ent->TriggerGuis(); +} + +/* +=================== +Cmd_Spawn_f +=================== +*/ +void Cmd_Spawn_f( const idCmdArgs &args ) { +#ifndef _MPBETA + const char *key, *value; + int i; + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() & 1 ) { // must always have an even number of arguments + gameLocal.Printf( "usage: spawn classname [key/value pairs]\n" ); + return; + } + + yaw = player->viewAngles.yaw; + + value = args.Argv( 1 ); + dict.Set( "classname", value ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw, 0 ).ToForward() * 80 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + +// RAVEN BEGIN +// kfuller: want to know the name of the entity I spawned + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } +// RAVEN END +#endif // !_MPBETA +} + +// RAVEN BEGIN +// ddynerman: MP spawning command for performance testing +/* +=================== +Cmd_EvaluateMPPerformance_f +=================== +*/ +void Cmd_EvaluateMPPerformance_f( const idCmdArgs &args ) { + float yaw; + idVec3 org; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + int num = 15; + + if ( args.Argc() > 1 ) { + num = atoi( args.Argv( 1 ) ); + } + + float angleStep = 360.0f / num; + + const char* className = "char_marine"; + + yaw = player->viewAngles.yaw; + + for( int i = 0; i < num; i++ ) { + dict.Set( "classname", className ); + dict.Set( "angle", va( "%f", yaw + 180 ) ); + + org = player->GetPhysics()->GetOrigin() + idAngles( 0, yaw + (i * angleStep), 0 ).ToForward() * 120 + idVec3( 0, 0, 1 ); + dict.Set( "origin", org.ToString() ); + + idEntity *newEnt = NULL; + gameLocal.SpawnEntityDef( dict, &newEnt ); + + if (newEnt) { + gameLocal.Printf("spawned entity '%s'\n", newEnt->name.c_str()); + } + } +} +// RAVEN END + + +/* +================== +Cmd_Damage_f + +Damages the specified entity +================== +*/ +void Cmd_Damage_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: damage \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + ent->Damage( gameLocal.world, gameLocal.world, idVec3( 0, 0, 1 ), "damage_moverCrush", atoi( args.Argv( 2 ) ), INVALID_JOINT ); +} + + +/* +================== +Cmd_Flashlight_f + +Toggles flashlight on specified entity +================== +*/ +void Cmd_Flashlight_f( const idCmdArgs &args ) { + if ( gameLocal.IsMultiplayer() || !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 3 ) { + gameLocal.Printf( "usage: flashight <0 = off, 1 = on>\n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent || !ent->IsType( idActor::GetClassType() ) ) { + gameLocal.Printf( "entity not found or not an actor\n" ); + return; + } + ent->ProcessEvent( &AI_Flashlight, atoi( args.Argv( 2 ) ) ); +} + +/* +================== +Cmd_Remove_f + +Removes the specified entity +================== +*/ +void Cmd_Remove_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if ( args.Argc() != 2 ) { + gameLocal.Printf( "usage: remove \n" ); + return; + } + + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + + delete ent; +} + +/* +================== +Cmd_AI_DebugFilter_f + +Makes the targeted entity the only one ai_debugMove & ai_debugTactical cares about +================== +*/ +void Cmd_AI_DebugFilter_f( const idCmdArgs &args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + idEntity *ent = NULL; + if ( args.Argc() != 2 ) + { + //trace ahead + trace_t trace; + idVec3 start = player->GetEyePosition(); + idVec3 end = start + player->viewAngles.ToForward() * 2048.0f; + gameLocal.TracePoint( player, trace, start, end, MASK_SHOT_RENDERMODEL, player ); + ent = gameLocal.GetTraceEntity( trace ); + } + else + { + idEntity *ent = gameLocal.FindEntity( args.Argv( 1 ) ); + if ( !ent ) { + gameLocal.Printf( "entity not found\n" ); + return; + } + } + + if ( !ent || !ent->IsType( idAI::GetClassType() ) ) { + ai_debugFilterString.SetString( "" ); + } else { + ai_debugFilterString.SetString( ent->GetName() ); + } +} + +/* +=================== +Cmd_TestLight_f +=================== +*/ +void Cmd_TestLight_f( const idCmdArgs &args ) { + int i; + idStr filename; + const char *key, *value, *name = NULL; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + renderView_t *rv = player->GetRenderView(); + + float fov = idMath::Tan( idMath::M_DEG2RAD * rv->fov_x / 2 ); + + dict.SetMatrix( "rotation", mat3_default ); + dict.SetVector( "origin", rv->vieworg ); + dict.SetVector( "light_target", rv->viewaxis[0] ); + dict.SetVector( "light_right", rv->viewaxis[1] * -fov ); + dict.SetVector( "light_up", rv->viewaxis[2] * fov ); + dict.SetVector( "light_start", rv->viewaxis[0] * 16 ); + dict.SetVector( "light_end", rv->viewaxis[0] * 1000 ); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + filename = args.Argv(1); + filename.DefaultFileExtension( ".tga" ); + dict.Set( "texture", filename ); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "spawned_light_%d", i ); // not just light_, or it might pick up a prelight shadow + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new light\n"); +} + +/* +=================== +Cmd_TestPointLight_f +=================== +*/ +void Cmd_TestPointLight_f( const idCmdArgs &args ) { + const char *key, *value, *name = NULL; + int i; + idPlayer *player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk( false ) ) { + return; + } + + dict.SetVector("origin", player->GetRenderView()->vieworg); + + if ( args.Argc() >= 2 ) { + value = args.Argv( 1 ); + dict.Set("light", value); + } else { + dict.Set("light", "300"); + } + + dict.Set( "classname", "light" ); + for( i = 2; i < args.Argc() - 1; i += 2 ) { + + key = args.Argv( i ); + value = args.Argv( i + 1 ); + + dict.Set( key, value ); + } + + for ( i = 0; i < MAX_GENTITIES; i++ ) { + name = va( "light_%d", i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + dict.Set( "name", name ); + + gameLocal.SpawnEntityDef( dict ); + + gameLocal.Printf( "Created new point light\n"); +} + +/* +================== +Cmd_PopLight_f +================== +*/ +void Cmd_PopLight_f( const idCmdArgs &args ) { + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idLight *lastLight; + int last; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + bool removeFromMap = ( args.Argc() > 1 ); + + lastLight = NULL; + last = -1; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( gameLocal.spawnIds[ ent->entityNumber ] > last ) { + last = gameLocal.spawnIds[ ent->entityNumber ]; + lastLight = static_cast( ent ); + } + } + + if ( lastLight ) { + // find map file entity + mapEnt = mapFile->FindEntity( lastLight->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + gameLocal.Printf( "Removing light %i\n", lastLight->GetLightDefHandle() ); + delete lastLight; + } else { + gameLocal.Printf( "No lights to clear.\n" ); + } + +} + +/* +==================== +Cmd_ClearLights_f +==================== +*/ +void Cmd_ClearLights_f( const idCmdArgs &args ) { + idEntity *ent; + idEntity *next; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + + bool removeFromMap = ( args.Argc() > 1 ); + + gameLocal.Printf( "Clearing all lights.\n" ); + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = next ) { + next = ent->spawnNode.Next(); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + light = static_cast( ent ); + mapEnt = mapFile->FindEntity( light->name ); + + if ( removeFromMap && mapEnt ) { + mapFile->RemoveEntity( mapEnt ); + } + + delete light; + } +} + +// RAVEN BEGIN +// bdube: not using id effects +/* +================== +Cmd_TestFx_f +================== +void Cmd_TestFx_f( const idCmdArgs &args ) { + idVec3 offset; + const char *name; + idPlayer * player; + idDict dict; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + // delete the testModel if active + if ( gameLocal.testFx ) { + delete gameLocal.testFx; + gameLocal.testFx = NULL; + } + + if ( args.Argc() < 2 ) { + return; + } + + name = args.Argv( 1 ); + + offset = player->GetPhysics()->GetOrigin() + player->viewAngles.ToForward() * 100.0f; + + dict.Set( "origin", offset.ToString() ); + dict.Set( "test", "1"); + dict.Set( "fx", name ); + gameLocal.testFx = ( idEntityFx * )gameLocal.SpawnEntityType( idEntityFx::Type, &dict ); +} +*/ +// RAVEN END + +#define MAX_DEBUGLINES 128 + +typedef struct { + bool used; + idVec3 start, end; + int color; + bool blink; + bool arrow; +} gameDebugLine_t; + +gameDebugLine_t debugLines[MAX_DEBUGLINES]; + +/* +================== +Cmd_AddDebugLine_f +================== +*/ +static void Cmd_AddDebugLine_f( const idCmdArgs &args ) { + int i, argNum; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 7 ) { + gameLocal.Printf( "usage: addline \n" ); + return; + } + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( !debugLines[i].used ) { + break; + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "no free debug lines\n" ); + return; + } + value = args.Argv( 0 ); + if ( !idStr::Icmp( value, "addarrow" ) ) { + debugLines[i].arrow = true; + } else { + debugLines[i].arrow = false; + } + debugLines[i].used = true; + debugLines[i].blink = false; + argNum = 1; + debugLines[i].start.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].start.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.x = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.y = Cmd_GetFloatArg( args, argNum ); + debugLines[i].end.z = Cmd_GetFloatArg( args, argNum ); + debugLines[i].color = Cmd_GetFloatArg( args, argNum ); +} + +/* +================== +Cmd_RemoveDebugLine_f +================== +*/ +static void Cmd_RemoveDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: removeline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi(value); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].used = false; +} + +/* +================== +Cmd_BlinkDebugLine_f +================== +*/ +static void Cmd_BlinkDebugLine_f( const idCmdArgs &args ) { + int i, num; + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: blinkline \n" ); + return; + } + value = args.Argv( 1 ); + num = atoi( value ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( --num < 0 ) { + break; + } + } + } + if ( i >= MAX_DEBUGLINES ) { + gameLocal.Printf( "line not found\n" ); + return; + } + debugLines[i].blink = !debugLines[i].blink; +} + +/* +================== +PrintFloat +================== +*/ +static void PrintFloat( float f ) { + char buf[128], i; + + for ( i = sprintf( buf, "%3.2f", f ); i < 7; i++ ) { + buf[i] = ' '; + } + buf[i] = '\0'; + gameLocal.Printf( buf ); +} + +/* +================== +Cmd_ListDebugLines_f +================== +*/ +static void Cmd_ListDebugLines_f( const idCmdArgs &args ) { + int i, num; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + num = 0; + gameLocal.Printf( "line num: x1 y1 z1 x2 y2 z2 c b a\n" ); + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + gameLocal.Printf( "line %3d: ", num ); + PrintFloat( debugLines[i].start.x ); + PrintFloat( debugLines[i].start.y ); + PrintFloat( debugLines[i].start.z ); + PrintFloat( debugLines[i].end.x ); + PrintFloat( debugLines[i].end.y ); + PrintFloat( debugLines[i].end.z ); + gameLocal.Printf( "%d %d %d\n", debugLines[i].color, debugLines[i].blink, debugLines[i].arrow ); + num++; + } + } + if ( !num ) { + gameLocal.Printf( "no debug lines\n" ); + } +} + +/* +================== +D_DrawDebugLines +================== +*/ +void D_DrawDebugLines( void ) { +// RAVEN BEGIN +// ddynerman: this eats about 5k us in release +#ifdef _DEBUG + int i; + idVec3 forward, right, up, p1, p2; + idVec4 color; + float l; + + for ( i = 0; i < MAX_DEBUGLINES; i++ ) { + if ( debugLines[i].used ) { + if ( !debugLines[i].blink || (gameLocal.time & (1<<9)) ) { + color = idVec4( debugLines[i].color&1, (debugLines[i].color>>1)&1, (debugLines[i].color>>2)&1, 1 ); + gameRenderWorld->DebugLine( color, debugLines[i].start, debugLines[i].end ); + // + if ( debugLines[i].arrow ) { + // draw a nice arrow + forward = debugLines[i].end - debugLines[i].start; + l = forward.Normalize() * 0.2f; + forward.NormalVectors( right, up); + + if ( l > 3.0f ) { + l = 3.0f; + } + p1 = debugLines[i].end - l * forward + (l * 0.4f) * right; + p2 = debugLines[i].end - l * forward - (l * 0.4f) * right; + gameRenderWorld->DebugLine( color, debugLines[i].end, p1 ); + gameRenderWorld->DebugLine( color, debugLines[i].end, p2 ); + gameRenderWorld->DebugLine( color, p1, p2 ); + } + } + } + } +#else + return; +#endif +// RAVEN END +} + +/* +================== +Cmd_ListCollisionModels_f +================== +*/ +static void Cmd_ListCollisionModels_f( const idCmdArgs &args ) { + if ( !gameLocal.CheatsOk() ) { + return; + } + + collisionModelManager->ListModels(); +} + +/* +================== +Cmd_CollisionModelInfo_f +================== +*/ +static void Cmd_CollisionModelInfo_f( const idCmdArgs &args ) { + const char *value; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc () < 2 ) { + gameLocal.Printf( "usage: collisionModelInfo \n" + "use 'all' instead of the model number for accumulated info\n" ); + return; + } + + value = args.Argv( 1 ); + if ( !idStr::Icmp( value, "all" ) ) { + collisionModelManager->ModelInfo( -1 ); + } else { + collisionModelManager->ModelInfo( atoi(value) ); + } +} + +/* +================== +Cmd_ExportModels_f +================== +*/ +static void Cmd_ExportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } +} + +/* +================== +Cmd_ReexportModels_f +================== +*/ +static void Cmd_ReexportModels_f( const idCmdArgs &args ) { + idModelExport exporter; + idStr name; + + // don't allow exporting models when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + + idAnimManager::forceExport = true; + if ( args.Argc() < 2 ) { + exporter.ExportModels( "def", ".def" ); + } else { + name = args.Argv( 1 ); + name = "def/" + name; + name.DefaultFileExtension( ".def" ); + exporter.ExportDefFile( name ); + } + idAnimManager::forceExport = false; +} + +/* +================== +Cmd_ReloadAnims_f +================== +*/ +static void Cmd_ReloadAnims_f( const idCmdArgs &args ) { + + // don't allow reloading anims when cheats are disabled, + // but if we're not in the game, it's ok + if ( gameLocal.GetLocalPlayer() && !gameLocal.CheatsOk( false ) ) { + return; + } + +// RAVEN BEGIN +// mekberg: disable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( true ); + +// jsinger: animationLib changed to a pointer + animationLib->ReloadAnims(); + +// mekberg: enable non pre-cached warnings + fileSystem->SetIsFileLoadingAllowed( false ); +// RAVEN END +} + +/* +================== +Cmd_ListAnims_f +================== +*/ +static void Cmd_ListAnims_f( const idCmdArgs &args ) { + idEntity * ent; + int num; + size_t size; + size_t alloced; + idAnimator * animator; + const char * classname; + const idDict * dict; + int i; + + if ( args.Argc() > 1 ) { + idAnimator animator; + + classname = args.Argv( 1 ); + + dict = gameLocal.FindEntityDefDict( classname, false ); + if ( !dict ) { + gameLocal.Printf( "Entitydef '%s' not found\n", classname ); + return; + } + animator.SetModel( dict->GetString( "model" ) ); + + gameLocal.Printf( "----------------\n" ); + num = animator.NumAnims(); +// RAVEN BEGIN +// rjohnson: more output for animators + idFile *FH = fileSystem->OpenFileAppend( "animations.txt" ); + for( i = 0; i < num; i++ ) { + for( int j = 0; ; j++ ) { + const char *fileName = animator.AnimMD5Name( i, j ); + + if ( !fileName[0] ) { + break; + } + gameLocal.Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + if ( FH ) { + FH->Printf( "%s\t%s\n", animator.AnimFullName( i ), animator.AnimMD5Name( i, 0 ) ); + } + } + } + gameLocal.Printf( "%d anims\n", num ); + fileSystem->CloseFile( FH ); +// RAVEN END + } else { +// RAVEN BEGIN +// jsinger: animationLib changed to a pointer + animationLib->ListAnims(); +// RAVEN END + + size = 0; + num = 0; + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + animator = ent->GetAnimator(); + if ( animator ) { + alloced = animator->Allocated(); + size += alloced; + num++; + } + } + + gameLocal.Printf( "%d memory used in %d entity animators\n", size, num ); + } +} + +// RAVEN BEGIN +// jscott: export for memory tracking +/* +================ +idGameEdit::ListAnims +================ +*/ +void idGameEdit::PrintMemInfo( MemInfo *mi ) { + + int i, count, totalSize; + idAAS *aas; + + totalSize = 0; + count = 0; + for( i = 0; i < gameLocal.GetNumAAS(); i++ ) { + + aas = gameLocal.GetAAS( i ); + if( aas ) { + + totalSize += aas->StatsSummary(); + count++; + } + } + + mi->aasAssetsTotal = totalSize; + mi->aasAssetsCount = count; + + // jsinger: animationLib changed to a pointer + animationLib->PrintMemInfo( mi ); +} +// RAVEN END + +/* +================== +Cmd_AASStats_f +================== +*/ +static void Cmd_AASStats_f( const idCmdArgs &args ) { + int aasNum; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + aasNum = aas_test.GetInteger(); + idAAS *aas = gameLocal.GetAAS( aasNum ); + if ( !aas ) { + gameLocal.Printf( "No aas #%d loaded\n", aasNum ); + } else { + aas->Stats(); + } +} + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDamage_f( const idCmdArgs &args ) { + idPlayer *player; + const char *damageDefName; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 2 || args.Argc() > 3 ) { + gameLocal.Printf( "usage: testDamage [angle]\n" ); + return; + } + + damageDefName = args.Argv( 1 ); + + idVec3 dir; + if ( args.Argc() == 3 ) { + float angle = atof( args.Argv( 2 ) ); + + idMath::SinCos( DEG2RAD( angle ), dir[1], dir[0] ); + dir[2] = 0; + } else { + dir.Zero(); + } + + // give the player full health before and after + // running the damage + player->health = player->inventory.maxHealth; + player->Damage( NULL, NULL, dir, damageDefName, 1.0f, INVALID_JOINT ); + player->health = player->inventory.maxHealth; +} + +/* +================== +Cmd_TestBoneFx_f +================== +*/ +// RAVEN BEGIN +// bdube: not using +/* +static void Cmd_TestBoneFx_f( const idCmdArgs &args ) { + idPlayer *player; + const char *bone, *fx; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() < 3 || args.Argc() > 4 ) { + gameLocal.Printf( "usage: testBoneFx \n" ); + return; + } + + fx = args.Argv( 1 ); + bone = args.Argv( 2 ); + + player->StartFxOnBone( fx, bone ); +} +*/ +// RAVEN END + +/* +================== +Cmd_TestDamage_f +================== +*/ +static void Cmd_TestDeath_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + idVec3 dir; + idMath::SinCos( DEG2RAD( 45.0f ), dir[1], dir[0] ); + dir[2] = 0; + + g_testDeath.SetBool( 1 ); + player->Damage( NULL, NULL, dir, "damage_triggerhurt_1000", 1.0f, INVALID_JOINT ); + if ( args.Argc() >= 2) { + player->SpawnGibs( dir, "damage_triggerhurt_1000" ); + } + +} + +/* +================== +Cmd_WeaponSplat_f +================== +*/ +static void Cmd_WeaponSplat_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->weapon->BloodSplat( 2.0f ); +} + +/* +================== +Cmd_SaveSelected_f +================== +*/ +static void Cmd_SaveSelected_f( const idCmdArgs &args ) { + int i; + idPlayer *player; + idEntity *s; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + s = player->dragEntity.GetSelected(); + if ( !s ) { + gameLocal.Printf( "no entity selected, set g_dragShowSelection 1 to show the current selection\n" ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + // find map file entity + mapEnt = mapFile->FindEntity( s->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", s->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + s->name = name; + mapEnt->epairs.Set( "classname", s->GetEntityDefName() ); + mapEnt->epairs.Set( "name", s->name ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( s->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + // save the moveable state + mapEnt->epairs.Set( "origin", s->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", s->GetPhysics()->GetAxis().ToString( 8 ) ); + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + else if ( s->IsType( idAFEntity_Generic::GetClassType() ) || s->IsType( idAFEntity_WithAttachedHead::GetClassType() ) ) { +// RAVEN END + // save the articulated figure state + dict.Clear(); + static_cast(s)->SaveState( dict ); + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_DeleteSelected_f +================== +*/ +static void Cmd_DeleteSelected_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.DeleteSelected(); + } +} + +/* +================== +Cmd_SaveMoveables_f +================== +*/ +static void Cmd_SaveMoveables_f( const idCmdArgs &args ) { + int e, i; + idMoveable *m; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m || !m->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + if ( !m->IsAtRest() ) { + break; + } + } + + if ( e < MAX_GENTITIES ) { + gameLocal.Warning( "map not saved because the moveable entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + m = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !m ) + { + continue; + } +// jdischler: need to check for idMoveableItem as well + if ( !m->IsType( idMoveable::GetClassType()) && !m->IsType( idMoveableItem::GetClassType()) ) + { +// RAVEN END + continue; + } + + if ( m->IsBound() ) { + continue; + } + + // find map file entity + mapEnt = mapFile->FindEntity( m->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", m->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + m->name = name; + mapEnt->epairs.Set( "classname", m->GetEntityDefName() ); + mapEnt->epairs.Set( "name", m->name ); + } + // save the moveable state + mapEnt->epairs.Set( "origin", m->GetPhysics()->GetOrigin().ToString( 8 ) ); + mapEnt->epairs.Set( "rotation", m->GetPhysics()->GetAxis().ToString( 8 ) ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_SaveRagdolls_f +================== +*/ +static void Cmd_SaveRagdolls_f( const idCmdArgs &args ) { + int e, i; + idAFEntity_Base *af; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + af = static_cast(gameLocal.entities[ e ]); + + if ( !af ) { + continue; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !af->IsType( idAFEntity_WithAttachedHead::GetClassType() ) && !af->IsType( idAFEntity_Generic::GetClassType() ) ) { +// RAVEN END + continue; + } + + if ( af->IsBound() ) { + continue; + } + + if ( !af->IsAtRest() ) { + gameLocal.Warning( "the articulated figure for entity %s is not at rest", gameLocal.entities[ e ]->name.c_str() ); + } + + dict.Clear(); + af->SaveState( dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( af->name ); + // create new map file entity if there isn't one for this articulated figure + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", af->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + af->name = name; + mapEnt->epairs.Set( "classname", af->GetEntityDefName() ); + mapEnt->epairs.Set( "name", af->name ); + } + // save the articulated figure state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + +/* +================== +Cmd_BindRagdoll_f +================== +*/ +static void Cmd_BindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.BindSelected(); + } +} + +/* +================== +Cmd_UnbindRagdoll_f +================== +*/ +static void Cmd_UnbindRagdoll_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player ) { + player->dragEntity.UnbindSelected(); + } +} + +/* +================== +Cmd_GameError_f +================== +*/ +static void Cmd_GameError_f( const idCmdArgs &args ) { + gameLocal.Error( "game error" ); +} + +// RAVEN BEGIN +// rjohnson: entity usage stats +/* +================== +Cmd_ListEntityStats_f +================== +*/ +static void Cmd_ListEntityStats_f( const idCmdArgs &args ) { + gameLocal.ListEntityStats( args ); +} +// RAVEN END + +/* +================== +Cmd_SaveLights_f +================== +*/ +static void Cmd_SaveLights_f( const idCmdArgs &args ) { + int e, i; + idLight *light; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName; + const char *name = NULL; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + light = static_cast(gameLocal.entities[ e ]); + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !light || !light->IsType( idLight::GetClassType() ) ) { +// RAVEN END + continue; + } + + dict.Clear(); + light->SaveState( &dict ); + + // find map file entity + mapEnt = mapFile->FindEntity( light->name ); + // create new map file entity if there isn't one for this light + if ( !mapEnt ) { + mapEnt = new idMapEntity(); + mapFile->AddEntity( mapEnt ); + for ( i = 0; i < 9999; i++ ) { + name = va( "%s_%d", light->GetEntityDefName(), i ); + if ( !gameLocal.FindEntity( name ) ) { + break; + } + } + light->name = name; + mapEnt->epairs.Set( "classname", light->GetEntityDefName() ); + mapEnt->epairs.Set( "name", light->name ); + } + // save the light state + mapEnt->epairs.Copy( dict ); + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_SaveParticles_f +================== +*/ +static void Cmd_SaveParticles_f( const idCmdArgs &args ) { + int e; + idEntity *ent; + idMapEntity *mapEnt; + idMapFile *mapFile = gameLocal.GetLevelMap(); + idDict dict; + idStr mapName, strModel; + + if ( !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() > 1 ) { + mapName = args.Argv( 1 ); + mapName = "maps/" + mapName; + } + else { + mapName = mapFile->GetName(); + } + + for( e = 0; e < MAX_GENTITIES; e++ ) { + + ent = static_cast ( gameLocal.entities[ e ] ); + + if ( !ent ) { + continue; + } + + strModel = ent->spawnArgs.GetString( "model" ); + if ( strModel.Length() && strModel.Find( ".prt") > 0 ) { + dict.Clear(); + dict.Set( "model", ent->spawnArgs.GetString( "model" ) ); + dict.SetVector( "origin", ent->GetPhysics()->GetOrigin() ); + + // find map file entity + mapEnt = mapFile->FindEntity( ent->name ); + // create new map file entity if there isn't one for this entity + if ( !mapEnt ) { + continue; + } + // save the particle state + mapEnt->epairs.Copy( dict ); + } + } + + // write out the map file + mapFile->Write( mapName, ".map" ); +} + + +/* +================== +Cmd_DisasmScript_f +================== +*/ +static void Cmd_DisasmScript_f( const idCmdArgs &args ) { + gameLocal.program.Disassemble(); +} + +/* +================== +Cmd_TestSave_f +================== +*/ +static void Cmd_TestSave_f( const idCmdArgs &args ) { + idFile *f; + + f = fileSystem->OpenFileWrite( "test.sav" ); + gameLocal.SaveGame( f ); + fileSystem->CloseFile( f ); +} + +// RAVEN BEGIN +#if 0 +/* +================== +Cmd_RecordViewNotes_f +================== +*/ +static void Cmd_RecordViewNotes_f( const idCmdArgs &args ) { + idPlayer *player; + idVec3 origin; + idMat3 axis; + + if ( args.Argc() <= 3 ) { + return; + } + + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + player->GetViewPos( origin, axis ); + + // Argv(1) = filename for map (viewnotes/mapname/person) + // Argv(2) = note number (person0001) + // Argv(3) = comments + + idStr str = args.Argv(1); + str.SetFileExtension( ".txt" ); + idFile *file = fileSystem->OpenFileAppend( str ); + if ( file ) { + file->WriteFloatString( "\"view\"\t( %s )\t( %s )\r\n", origin.ToString(), axis.ToString() ); + file->WriteFloatString( "\"comments\"\t\"%s: %s\"\r\n\r\n", args.Argv(2), args.Argv(3) ); + fileSystem->CloseFile( file ); + } + + idStr viewComments = args.Argv(1); + viewComments.StripLeading("viewnotes/"); + viewComments += " -- Loc: "; + viewComments += origin.ToString(); + viewComments += "\n"; + viewComments += args.Argv(3); + player->hud->SetStateString( "viewcomments", viewComments ); + player->hud->HandleNamedEvent( "showViewComments" ); +} + +/* +================== +Cmd_CloseViewNotes_f +================== +*/ +static void Cmd_CloseViewNotes_f( const idCmdArgs &args ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + player->hud->SetStateString( "viewcomments", "" ); + player->hud->HandleNamedEvent( "hideViewComments" ); +} + +/* +================== +Cmd_ShowViewNotes_f +================== +*/ +static void Cmd_ShowViewNotes_f( const idCmdArgs &args ) { + static idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT | LEXFL_NOFATALERRORS ); + idToken token; + idPlayer *player; + idVec3 origin; + idMat3 axis; + + player = gameLocal.GetLocalPlayer(); + + if ( !player ) { + return; + } + + if ( !parser.IsLoaded() ) { + idStr str = "viewnotes/"; + str += gameLocal.GetMapName(); + str.StripFileExtension(); + str += "/"; + if ( args.Argc() > 1 ) { + str += args.Argv( 1 ); + } else { + str += "comments"; + } + str.SetFileExtension( ".txt" ); + if ( !parser.LoadFile( str ) ) { + gameLocal.Printf( "No view notes for %s\n", gameLocal.GetMapName() ); + return; + } + } + + if ( parser.ExpectTokenString( "view" ) && parser.Parse1DMatrix( 3, origin.ToFloatPtr() ) && + parser.Parse1DMatrix( 9, axis.ToFloatPtr() ) && parser.ExpectTokenString( "comments" ) && parser.ReadToken( &token ) ) { + player->hud->SetStateString( "viewcomments", token ); + player->hud->HandleNamedEvent( "showViewComments" ); + player->Teleport( origin, axis.ToAngles(), NULL ); + } else { + parser.FreeSource(); + player->hud->HandleNamedEvent( "hideViewComments" ); + return; + } +} +#endif +// RAVEN END + +/* +================= +FindEntityGUIs + +helper function for Cmd_NextGUI_f. Checks the passed entity to determine if it +has any valid gui surfaces. +================= +*/ +bool FindEntityGUIs( idEntity *ent, const modelSurface_t ** surfaces, int maxSurfs, int &guiSurfaces ) { + renderEntity_t *renderEnt; + idRenderModel *renderModel; + const modelSurface_t *surf; + const idMaterial *shader; + int i; + + assert( surfaces != NULL ); + assert( ent != NULL ); + + memset( surfaces, 0x00, sizeof( modelSurface_t *) * maxSurfs ); + guiSurfaces = 0; + + renderEnt = ent->GetRenderEntity(); + renderModel = renderEnt->hModel; + if ( renderModel == NULL ) { + return false; + } + + for( i = 0; i < renderModel->NumSurfaces(); i++ ) { + surf = renderModel->Surface( i ); + if ( surf == NULL ) { + continue; + } + shader = surf->shader; + if ( shader == NULL ) { + continue; + } + if ( shader->GetEntityGui() > 0 ) { + surfaces[ guiSurfaces++ ] = surf; + } + } + + return ( guiSurfaces != 0 ); +} + +/* +================= +Cmd_NextGUI_f +================= +*/ +void Cmd_NextGUI_f( const idCmdArgs &args ) { + idVec3 origin; + idAngles angles; + idPlayer *player; + idEntity *ent; + int guiSurfaces; + bool newEnt; + renderEntity_t *renderEnt; + int surfIndex; + srfTriangles_t *geom; + idMat4 modelMatrix; + idVec3 normal; + idVec3 center; + const modelSurface_t *surfaces[ MAX_RENDERENTITY_GUI ]; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( args.Argc() != 1 ) { + gameLocal.Printf( "usage: nextgui\n" ); + return; + } + + // start at the last entity + ent = gameLocal.lastGUIEnt.GetEntity(); + + // see if we have any gui surfaces left to go to on the current entity. + guiSurfaces = 0; + newEnt = false; + if ( ent == NULL ) { + newEnt = true; + } else if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == true ) { + if ( gameLocal.lastGUI >= guiSurfaces ) { + newEnt = true; + } + } else { + // no actual gui surfaces on this ent, so skip it + newEnt = true; + } + + if ( newEnt == true ) { + // go ahead and skip to the next entity with a gui... + if ( ent == NULL ) { + ent = gameLocal.spawnedEntities.Next(); + } else { + ent = ent->spawnNode.Next(); + } + + for ( ; ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->spawnArgs.GetString( "gui", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui2", NULL ) != NULL ) { + break; + } + + if ( ent->spawnArgs.GetString( "gui3", NULL ) != NULL ) { + break; + } + + // try the next entity + gameLocal.lastGUIEnt = ent; + } + + gameLocal.lastGUIEnt = ent; + gameLocal.lastGUI = 0; + + if ( !ent ) { + gameLocal.Printf( "No more gui entities. Starting over...\n" ); + return; + } + } + + if ( FindEntityGUIs( ent, surfaces, MAX_RENDERENTITY_GUI, guiSurfaces ) == false ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces.\n", ent->name.c_str() ); + } + + if ( guiSurfaces == 0 ) { + gameLocal.Printf( "Entity \"%s\" has gui properties but no gui surfaces!\n", ent->name.c_str() ); + return; + } + + gameLocal.Printf( "Teleporting to gui entity \"%s\", gui #%d.\n" , ent->name.c_str (), gameLocal.lastGUI ); + + renderEnt = ent->GetRenderEntity(); + surfIndex = gameLocal.lastGUI++; + geom = surfaces[ surfIndex ]->geometry; + if ( geom == NULL ) { + gameLocal.Printf( "Entity \"%s\" has gui surface %d without geometry!\n", ent->name.c_str(), surfIndex ); + return; + } + + assert( geom->facePlanes != NULL ); + + modelMatrix = idMat4( renderEnt->axis, renderEnt->origin ); + normal = geom->facePlanes[ 0 ].Normal() * renderEnt->axis; + center = geom->bounds.GetCenter() * modelMatrix; + + origin = center + (normal * 32.0f); + origin.z -= player->EyeHeight(); + normal *= -1.0f; + angles = normal.ToAngles (); + + // make sure the player is in noclip + player->noclip = true; + player->Teleport( origin, angles, NULL ); +} + +static void ArgCompletion_DefFile( const idCmdArgs &args, void(*callback)( const char *s ) ) { + cmdSystem->ArgCompletion_FolderExtension( args, callback, "def/", true, ".def", NULL ); +} + +/* +=============== +Cmd_TestId_f +outputs a string from the string table for the specified id +=============== +*/ +void Cmd_TestId_f( const idCmdArgs &args ) { + idStr id; + int i; + if ( args.Argc() == 1 ) { + common->Printf( "usage: testid \n" ); + return; + } + + for ( i = 1; i < args.Argc(); i++ ) { + id += args.Argv( i ); + } + if ( idStr::Cmpn( id, STRTABLE_ID, STRTABLE_ID_LENGTH ) != 0 ) { + id = STRTABLE_ID + id; + } + gameLocal.mpGame.AddChatLine( common->GetLocalizedString( id ), "", "", "" ); +} + +// RAVEN BEGIN +// ddynerman: instance testing commands +void Cmd_SetInstance_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_SetInstance_f(): setInstance can only be run on a server\n" ); + return; + } + + if ( args.Argc() <= 2 ) { + common->Printf( "usage: setInstance \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + int instanceID = atoi( args.Argv( 2 ) ); + + if( client < 0 || client >= MAX_CLIENTS || !gameLocal.entities[ client ] ) { + gameLocal.Warning( "Cmd_SetInstance_f(): Invalid clientnum %d\n", client ); + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ client ]; + + gameLocal.Printf( "Cmd_SetInstance_f(): Switching %d: %s to instance %d\n", client, gameLocal.userInfo[ client ].GetString( "ui_name", "unknown" ), instanceID ); + + player->JoinInstance( instanceID ); +} + +void Cmd_ListInstances_f( const idCmdArgs& args ) { + if( !gameLocal.isServer ) { + gameLocal.Warning( "Cmd_ListInstances_f(): listInstances can only be run on a server\n" ); + return; + } + + for( int i = 0; i < gameLocal.GetNumInstances(); i++ ) { + gameLocal.Printf("Instance %d:\n", i ); + for( int j = 0; j < MAX_CLIENTS; j++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ j ]; + if( player && player->GetInstance() == i ) { + gameLocal.Printf( "\t%d: %s\n", j, player->GetUserInfo()->GetString( "ui_name" ) ); + } + } + } +} + +void Cmd_AddIcon_f( const idCmdArgs& args ) { + if ( args.Argc() <= 1 ) { + common->Printf( "usage: addIcon \n" ); + return; + } + + int client = atoi( args.Argv( 1 ) ); + + iconManager->AddIcon( client, "textures/mp/awards/capture" ); +} +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +void Cmd_ToggleBuyMenu_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( player && player->CanBuy() ) + { + gameLocal.mpGame.OpenLocalBuyMenu(); + } +} + +void Cmd_BuyItem_f( const idCmdArgs& args ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + common->Printf( "ERROR: Cmd_BuyItem_f() failed, since GetLocalPlayer() was NULL.\n", player ); + return; + } + + player->GenerateImpulseForBuyAttempt( args.Argv(1) ); +} +// RITUAL END + +void Cmd_PlayerEmote_f( const idCmdArgs& args ) { + if( gameLocal.GetLocalPlayer() == NULL ) { + gameLocal.Warning( "Cmd_Emote_f() - local player is NULL" ); + return; + } + + if ( args.Argc() <= 1 ) { + gameLocal.Printf( "usage: emote \n" ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab_a'\n" ); + return; + } + + if( !idStr::Icmp( args.Argv( 1 ), "taunt" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_TAUNT ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_a" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_A ); + } else if( !idStr::Icmp( args.Argv( 1 ), "grab_b" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_GRAB_B ); + } else if( !idStr::Icmp( args.Argv( 1), "salute" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_SALUTE ); + } else if( !idStr::Icmp( args.Argv( 1), "cheer" ) ) { + gameLocal.GetLocalPlayer()->SetEmote( PE_CHEER ); + } else { + gameLocal.Printf( "Invalid emote '%s'\n", args.Argv( 1 ) ); + gameLocal.Printf( "\ttry 'taunt', 'salute', 'grab'\n" ); + } + +} + +// mekberg: added +void Cmd_SetPMCVars_f ( const idCmdArgs &args ) { + if ( gameLocal.isMultiplayer ) { + return; + } + + if ( gameLocal.GetLocalPlayer( ) ) { + gameLocal.GetLocalPlayer( )->SetPMCVars( ); + } +} + +void Cmd_FadeSound_f( const idCmdArgs &args ) { + + if( args.Argc() < 2) { + return; + } + + float fadeDB = 0.0f; + float fadeTime = 0.0f; + + idStr _fadeDB = args.Argv( 1); + fadeDB = atof( _fadeDB ); + + idStr _fadeTime = args.Argv( 2); + fadeTime = atof( _fadeTime ); + + soundSystem->FadeSoundClasses( SOUNDWORLD_GAME, SOUND_CLASS_MUSICAL, 0.0f - fadeDB, fadeTime ); + +} + +void Cmd_TestClientModel_f( const idCmdArgs& args ) { + rvClientEntity* face; + const idDict* dict = gameLocal.FindEntityDefDict( "player_marine_client", false ); + + gameLocal.SpawnClientEntityDef( *dict, &face, false ); + +// face = new rvClientAFEntity( *dict ); +// face->Spawn( dict ); +// face->SetOrigin( vec3_zero ); +// face->SetAxis( mat3_identity ); +// face->SetModel( "model_player_marine" ); +} + + +// RAVEN END + +void Cmd_CheckSave_f( const idCmdArgs &args ); + +void Cmd_ShuffleTeams_f( const idCmdArgs& args ) { + gameLocal.mpGame.ShuffleTeams(); +} + +#ifndef _FINAL +void Cmd_ClientOverflowReliable_f( const idCmdArgs& args ) { + idBitMsg outMsg; + byte msgBuf[ 114688 ]; + + for( int i = 0; i < 10; i++ ) { + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( -1 ); + for( int j = 0; j < 8190; j++ ) { + outMsg.WriteByte( j ); + } + networkSystem->ClientSendReliableMessage( outMsg ); + } +} +#endif + +void Cmd_ListMaps_f( const idCmdArgs& args ) { + gameLocal.mpGame.ListMaps(); +} + +/* +================= +idGameLocal::InitConsoleCommands + +Let the system know about all of our commands +so it can perform tab completion +================= +*/ +void idGameLocal::InitConsoleCommands( void ) { +// RAVEN BEGIN +// jscott: typeinfo gone - didn't work, it was unfinished +// cmdSystem->AddCommand( "listTypeInfo", ListTypeInfo_f, CMD_FL_GAME, "list type info" ); +// cmdSystem->AddCommand( "writeGameState", WriteGameState_f, CMD_FL_GAME, "write game state" ); +// cmdSystem->AddCommand( "testSaveGame", TestSaveGame_f, CMD_FL_GAME|CMD_FL_CHEAT, "test a save game for a level" ); +// RAVEN END + cmdSystem->AddCommand( "game_memory", idClass::DisplayInfo_f, CMD_FL_GAME, "displays game class info" ); + cmdSystem->AddCommand( "listClasses", idClass::ListClasses_f, CMD_FL_GAME, "lists game classes" ); + cmdSystem->AddCommand( "listThreads", idThread::ListThreads_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists script threads" ); + cmdSystem->AddCommand( "listEntities", Cmd_EntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists game entities" ); + cmdSystem->AddCommand( "listClientEntities", Cmd_ClientEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists client entities" ); + cmdSystem->AddCommand( "listActiveEntities", Cmd_ActiveEntityList_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists active game entities" ); + cmdSystem->AddCommand( "listMonsters", idAI::List_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists monsters" ); + cmdSystem->AddCommand( "listSpawnArgs", Cmd_ListSpawnArgs_f, CMD_FL_GAME|CMD_FL_CHEAT, "list the spawn args of an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "listMaps", Cmd_ListMaps_f, CMD_FL_GAME, "list the available maps" ); + cmdSystem->AddCommand( "say", Cmd_Say_f, CMD_FL_GAME, "text chat" ); + cmdSystem->AddCommand( "sayTeam", Cmd_SayTeam_f, CMD_FL_GAME, "team text chat" ); + cmdSystem->AddCommand( "addChatLine", Cmd_AddChatLine_f, CMD_FL_GAME, "internal use - core to game chat lines" ); + cmdSystem->AddCommand( "gameKick", Cmd_Kick_f, CMD_FL_GAME, "same as kick, but recognizes player names" ); + cmdSystem->AddCommand( "give", Cmd_Give_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives one or more items" ); + cmdSystem->AddCommand( "centerview", Cmd_CenterView_f, CMD_FL_GAME, "centers the view" ); + cmdSystem->AddCommand( "god", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "undying", Cmd_Undying_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables undying mode (take damage down to 1 health, but do not die)" ); + cmdSystem->AddCommand( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); + cmdSystem->AddCommand( "noclip", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); + cmdSystem->AddCommand( "kill", Cmd_Kill_f, CMD_FL_GAME, "kills the player" ); + cmdSystem->AddCommand( "where", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "getviewpos", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); + cmdSystem->AddCommand( "setviewpos", Cmd_SetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the current view position" ); + cmdSystem->AddCommand( "teleport", Cmd_Teleport_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleports the player to an entity location", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "trigger", Cmd_Trigger_f, CMD_FL_GAME|CMD_FL_CHEAT, "triggers an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "spawn", Cmd_Spawn_f, CMD_FL_GAME|CMD_FL_CHEAT, "spawns a game entity", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "damage", Cmd_Damage_f, CMD_FL_GAME|CMD_FL_CHEAT, "apply damage to an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "remove", Cmd_Remove_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes an entity", idGameLocal::ArgCompletion_EntityName ); + cmdSystem->AddCommand( "killMonsters", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all monsters" ); + cmdSystem->AddCommand( "killMoveables", Cmd_KillMovables_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all moveables" ); + cmdSystem->AddCommand( "killRagdolls", Cmd_KillRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all ragdolls" ); + cmdSystem->AddCommand( "addline", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug line" ); + cmdSystem->AddCommand( "addarrow", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug arrow" ); + cmdSystem->AddCommand( "removeline", Cmd_RemoveDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes a debug line" ); + cmdSystem->AddCommand( "blinkline", Cmd_BlinkDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "blinks a debug line" ); + cmdSystem->AddCommand( "listLines", Cmd_ListDebugLines_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists all debug lines" ); + cmdSystem->AddCommand( "playerModel", Cmd_PlayerModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the given model on the player", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "flashlight", Cmd_Flashlight_f, CMD_FL_GAME|CMD_FL_CHEAT, "toggle actor's flashlight", idGameLocal::ArgCompletion_AIName ); + + cmdSystem->AddCommand( "shuffleTeams", Cmd_ShuffleTeams_f, CMD_FL_GAME, "shuffle teams" ); +// RAVEN BEGIN +// bdube: not using id effect system +// cmdSystem->AddCommand( "testFx", Cmd_TestFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system", idCmdSystem::ArgCompletion_Decl ); +// cmdSystem->AddCommand( "testBoneFx", Cmd_TestBoneFx_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an FX system bound to a joint", idCmdSystem::ArgCompletion_Decl ); +// RAVEN END + cmdSystem->AddCommand( "testLight", Cmd_TestLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a light" ); + cmdSystem->AddCommand( "testPointLight", Cmd_TestPointLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a point light" ); + cmdSystem->AddCommand( "popLight", Cmd_PopLight_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes the last created light" ); + cmdSystem->AddCommand( "testDeath", Cmd_TestDeath_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests death" ); + cmdSystem->AddCommand( "testSave", Cmd_TestSave_f, CMD_FL_GAME|CMD_FL_CHEAT, "writes out a test savegame" ); + cmdSystem->AddCommand( "testModel", idTestModel::TestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a model", idTestModel::ArgCompletion_TestModel ); + cmdSystem->AddCommand( "testSkin", idTestModel::TestSkin_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a skin on an existing testModel", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "testShaderParm", idTestModel::TestShaderParm_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets a shaderParm on an existing testModel" ); + cmdSystem->AddCommand( "keepTestModel", idTestModel::KeepTestModel_f, CMD_FL_GAME|CMD_FL_CHEAT, "keeps the last test model in the game" ); + cmdSystem->AddCommand( "testAnim", idTestModel::TestAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests an animation", idTestModel::ArgCompletion_TestAnim ); + cmdSystem->AddCommand( "testParticleStopTime", idTestModel::TestParticleStopTime_f,CMD_FL_GAME|CMD_FL_CHEAT, "tests particle stop time on a test model" ); + cmdSystem->AddCommand( "nextAnim", idTestModel::TestModelNextAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation on test model" ); + cmdSystem->AddCommand( "prevAnim", idTestModel::TestModelPrevAnim_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation on test model" ); + cmdSystem->AddCommand( "nextFrame", idTestModel::TestModelNextFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows next animation frame on test model" ); + cmdSystem->AddCommand( "prevFrame", idTestModel::TestModelPrevFrame_f, CMD_FL_GAME|CMD_FL_CHEAT, "shows previous animation frame on test model" ); + cmdSystem->AddCommand( "testBlend", idTestModel::TestBlend_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests animation blending" ); + cmdSystem->AddCommand( "reloadScript", Cmd_ReloadScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads scripts" ); + cmdSystem->AddCommand( "script", Cmd_Script_f, CMD_FL_GAME|CMD_FL_CHEAT, "executes a line of script" ); + cmdSystem->AddCommand( "listCollisionModels", Cmd_ListCollisionModels_f, CMD_FL_GAME, "lists collision models" ); + cmdSystem->AddCommand( "collisionModelInfo", Cmd_CollisionModelInfo_f, CMD_FL_GAME, "shows collision model info" ); + cmdSystem->AddCommand( "reexportmodels", Cmd_ReexportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "reexports models", ArgCompletion_DefFile ); + cmdSystem->AddCommand( "reloadanims", Cmd_ReloadAnims_f, CMD_FL_GAME|CMD_FL_CHEAT, "reloads animations" ); + cmdSystem->AddCommand( "listAnims", Cmd_ListAnims_f, CMD_FL_GAME, "lists all animations" ); + cmdSystem->AddCommand( "aasStats", Cmd_AASStats_f, CMD_FL_GAME, "shows AAS stats" ); + cmdSystem->AddCommand( "testDamage", Cmd_TestDamage_f, CMD_FL_GAME|CMD_FL_CHEAT, "tests a damage def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "weaponSplat", Cmd_WeaponSplat_f, CMD_FL_GAME|CMD_FL_CHEAT, "projects a blood splat on the player weapon" ); + cmdSystem->AddCommand( "saveSelected", Cmd_SaveSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves the selected entity to the .map file" ); + cmdSystem->AddCommand( "deleteSelected", Cmd_DeleteSelected_f, CMD_FL_GAME|CMD_FL_CHEAT, "deletes selected entity" ); + cmdSystem->AddCommand( "saveMoveables", Cmd_SaveMoveables_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all moveables to the .map file" ); + cmdSystem->AddCommand( "saveRagdolls", Cmd_SaveRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "save all ragdoll poses to the .map file" ); + cmdSystem->AddCommand( "bindRagdoll", Cmd_BindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "binds ragdoll at the current drag position" ); + cmdSystem->AddCommand( "unbindRagdoll", Cmd_UnbindRagdoll_f, CMD_FL_GAME|CMD_FL_CHEAT, "unbinds the selected ragdoll" ); + cmdSystem->AddCommand( "saveLights", Cmd_SaveLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "saveParticles", Cmd_SaveParticles_f, CMD_FL_GAME|CMD_FL_CHEAT, "saves all lights to the .map file" ); + cmdSystem->AddCommand( "clearLights", Cmd_ClearLights_f, CMD_FL_GAME|CMD_FL_CHEAT, "clears all lights" ); + cmdSystem->AddCommand( "gameError", Cmd_GameError_f, CMD_FL_GAME|CMD_FL_CHEAT, "causes a game error" ); + +// RAVEN BEGIN +// rjohnson: entity usage stats + cmdSystem->AddCommand( "listEntityStats", Cmd_ListEntityStats_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists global entity stats" ); +// ddynerman: mp spawning test command + cmdSystem->AddCommand( "evaluateMPPerformance", Cmd_EvaluateMPPerformance_f,CMD_FL_GAME|CMD_FL_CHEAT, "spawns serveral player models", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "listMapEntities", idGameLocal::Cmd_PrintMapEntityNumbers_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); + cmdSystem->AddCommand( "listSpawnIds", idGameLocal::Cmd_PrintSpawnIds_f, CMD_FL_GAME|CMD_FL_CHEAT, "lists map entity numbers" ); +// RAVEN END + +#ifndef ID_DEMO_BUILD + cmdSystem->AddCommand( "disasmScript", Cmd_DisasmScript_f, CMD_FL_GAME|CMD_FL_CHEAT, "disassembles script" ); +// RAVEN BEGIN +// rjohnson: removed old not taking system +/* + cmdSystem->AddCommand( "recordViewNotes", Cmd_RecordViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "record the current view position with notes" ); + cmdSystem->AddCommand( "showViewNotes", Cmd_ShowViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "show any view notes for the current map, successive calls will cycle to the next note" ); + cmdSystem->AddCommand( "closeViewNotes", Cmd_CloseViewNotes_f, CMD_FL_GAME|CMD_FL_CHEAT, "close the view showing any notes for this map" ); +*/ +// RAVEN END + cmdSystem->AddCommand( "exportmodels", Cmd_ExportModels_f, CMD_FL_GAME|CMD_FL_CHEAT, "exports models", ArgCompletion_DefFile ); + + // multiplayer client commands ( replaces old impulses stuff ) + //cmdSystem->AddCommand( "clientDropWeapon", idMultiplayerGame::DropWeapon_f, CMD_FL_GAME, "drop current weapon" ); + cmdSystem->AddCommand( "clientMessageMode", idMultiplayerGame::MessageMode_f, CMD_FL_GAME, "ingame gui message mode" ); + // FIXME: implement + cmdSystem->AddCommand( "clientVote", idMultiplayerGame::Vote_f, CMD_FL_GAME, "cast your vote: clientVote yes | no" ); + cmdSystem->AddCommand( "clientCallVote", idMultiplayerGame::CallVote_f, CMD_FL_GAME, "call a vote: clientCallVote si_.. proposed_value" ); + cmdSystem->AddCommand( "clientVoiceChat", idMultiplayerGame::VoiceChat_f, CMD_FL_GAME, "voice chats: clientVoiceChat " ); + cmdSystem->AddCommand( "clientVoiceChatTeam", idMultiplayerGame::VoiceChatTeam_f, CMD_FL_GAME, "team voice chats: clientVoiceChat " ); +// RAVEN BEGIN + // jshepard + cmdSystem->AddCommand( "forceTeamChange", idMultiplayerGame::ForceTeamChange_f, CMD_FL_GAME, "force team change: forceTeamChange " ); + cmdSystem->AddCommand( "removeClientFromBanList", idMultiplayerGame::RemoveClientFromBanList_f, CMD_FL_GAME, "removes a client id from the ban list: removeClientFromBanList " ); + +#ifndef _XBOX +// shouchard: more voice chat stuff (non-XBOX) + cmdSystem->AddCommand( "clientvoicemute", idMultiplayerGame::VoiceMute_f, CMD_FL_GAME, "mute the specified player's incoming voicechat" ); + cmdSystem->AddCommand( "clientvoiceunmute", idMultiplayerGame::VoiceUnmute_f, CMD_FL_GAME, "unmute the specified player's incoming voicechat" ); +#endif // _XBOX +// RAVEN END + + // multiplayer server commands + cmdSystem->AddCommand( "verifyServerSettings", idGameLocal::VerifyServerSettings_f, CMD_FL_GAME, "verifies the game type can be played on the map" ); + cmdSystem->AddCommand( "serverMapRestart", idGameLocal::MapRestart_f, CMD_FL_GAME, "restart the current game" ); + cmdSystem->AddCommand( "serverForceReady", idMultiplayerGame::ForceReady_f,CMD_FL_GAME, "force all players ready" ); + cmdSystem->AddCommand( "serverNextMap", idGameLocal::NextMap_f, CMD_FL_GAME, "change to the next map" ); +#endif + + cmdSystem->AddCommand( "CheckTeamBalance", idMultiplayerGame::CheckTeamBalance_f, CMD_FL_GAME, "helper for team switching in the guis - " ); + + // localization help commands + cmdSystem->AddCommand( "nextGUI", Cmd_NextGUI_f, CMD_FL_GAME|CMD_FL_CHEAT, "teleport the player to the next func_static with a gui" ); + cmdSystem->AddCommand( "testid", Cmd_TestId_f, CMD_FL_GAME|CMD_FL_CHEAT, "output the string for the specified id." ); + +// RAVEN BEGIN +// bdube: vehicle code + cmdSystem->AddCommand( "killVehicles", Cmd_KillVehicles_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills all vehicles" ); + cmdSystem->AddCommand( "killMessage", Cmd_KillMessage_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints a fake death message" ); + cmdSystem->AddCommand( "apState", Cmd_APState_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints AP state" ); +// bdube: jump points + cmdSystem->AddCommand( "jump", Cmd_DebugJump_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to a specific debug jump point" ); + cmdSystem->AddCommand( "nextjumppoint", Cmd_DebugNextJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the next debug jump point " ); + cmdSystem->AddCommand( "prevjumppoint", Cmd_DebugPrevJumpPoint_f, CMD_FL_GAME|CMD_FL_CHEAT, "jumps to the previous debug jump point" ); +// cdr: Added Extract Tactical + cmdSystem->AddCommand( "extract_tactical", Cmd_AASExtractTactical_f, CMD_FL_GAME, "pulls tactical information for the current position." ); +// RAVEN END + +// RAVEN BEGIN +// abahr + cmdSystem->AddCommand( "call", Cmd_CallScriptFunc_f, CMD_FL_GAME|CMD_FL_CHEAT, "calls script function and prints out return val" ); + cmdSystem->AddCommand( "setPlayerGravity", Cmd_SetPlayerGravity_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets players local gravity" ); +// cdr + cmdSystem->AddCommand( "ai_debugFilter", Cmd_AI_DebugFilter_f, CMD_FL_GAME, "ai_debugMove and ai_debugTactical only work on the specified entity (if none, does one you're looking at)", idGameLocal::ArgCompletion_AIName ); +// ddynerman: multiple arena/CW stuff + cmdSystem->AddCommand( "setInstance", Cmd_SetInstance_f, CMD_FL_GAME, "sets a player's world instance" ); + cmdSystem->AddCommand( "addIcon", Cmd_AddIcon_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a test icon" ); + cmdSystem->AddCommand( "listInstances", Cmd_ListInstances_f, CMD_FL_GAME, "lists instances" ); +// ddynerman: emote anims + cmdSystem->AddCommand( "emote", Cmd_PlayerEmote_f, CMD_FL_GAME, "plays an emote" ); + + cmdSystem->AddCommand( "checkSave", Cmd_CheckSave_f, CMD_FL_GAME, "tests save system" ); + +// jshepard: fade music in / out + cmdSystem->AddCommand( "fadeSound", Cmd_FadeSound_f, CMD_FL_GAME|CMD_FL_CHEAT, "fades all sound by X decibles over Y seconds" ); + +// mekberg: added. + cmdSystem->AddCommand( "setPMCVars", Cmd_SetPMCVars_f, CMD_FL_GAME, "Resets player movement cvars" ); + + cmdSystem->AddCommand( "testClientModel", Cmd_TestClientModel_f, CMD_FL_GAME, "" ); +#ifndef _FINAL + cmdSystem->AddCommand( "clientOverflowReliable", Cmd_ClientOverflowReliable_f, CMD_FL_GAME, "" ); +#endif +// RAVEN END +// RITUAL START +// squirrel: Mode-agnostic buymenus + cmdSystem->AddCommand( "buyMenu", Cmd_ToggleBuyMenu_f, CMD_FL_GAME, "Toggle buy menu (if in a buy zone and the game type supports it)" ); + cmdSystem->AddCommand( "buy", Cmd_BuyItem_f, CMD_FL_GAME, "Buy an item (if in a buy zone and the game type supports it)" ); +// RITUAL END + +} + +/* +================= +idGameLocal::ShutdownConsoleCommands +================= +*/ +void idGameLocal::ShutdownConsoleCommands( void ) { + cmdSystem->RemoveFlaggedCommands( CMD_FL_GAME ); +} diff --git a/src/mpgame/gamesys/SysCmds.h b/src/mpgame/gamesys/SysCmds.h new file mode 100644 index 0000000..33fabbf --- /dev/null +++ b/src/mpgame/gamesys/SysCmds.h @@ -0,0 +1,34 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_CMDS_H__ +#define __SYS_CMDS_H__ + +void D_DrawDebugLines( void ); + +void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ); + +void GiveStuffToPlayer( idPlayer* player, const char* name, const char* value ); + +#endif /* !__SYS_CMDS_H__ */ diff --git a/src/mpgame/gamesys/SysCvar.cpp b/src/mpgame/gamesys/SysCvar.cpp new file mode 100644 index 0000000..aa22ca9 --- /dev/null +++ b/src/mpgame/gamesys/SysCvar.cpp @@ -0,0 +1,709 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../../sys/AutoVersion.h" + +#if defined( _DEBUG ) + #define BUILD_DEBUG "-debug" +#else + #define BUILD_DEBUG "-release" +#endif + +/* + +All game cvars should be defined here. + +*/ + +// RAVEN BEGIN +// ddynerman: our gameplay modes +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +const char *si_gameTypeArgs[] = { "singleplayer", "DM", "Tourney", "Team DM", "CTF", "Arena CTF", "DeadZone", NULL }; +const int si_numGameTypeArgs = sizeof( si_gameTypeArgs ) / sizeof( si_gameTypeArgs[0] ); +// RITUAL END +// RAVEN END +const char *si_readyArgs[] = { "Not Ready", "Ready", NULL }; +const char *si_spectateArgs[] = { "Play", "Spectate", NULL }; + +// RAVEN BEGIN +// ddynerman: our teams +const char *ui_teamArgs[] = { "Marine", "Strogg", NULL }; +// RAVEN END + +struct gameVersion_s { + gameVersion_s( void ) { sprintf( string, "%s %s V%s %s %s", GAME_NAME, GAME_BUILD_TYPE, VERSION_STRING_DOTTED, BUILD_STRING, __DATE__ ); } + char string[256]; +} gameVersion; + +idCVar g_version( "g_version", gameVersion.string, CVAR_GAME | CVAR_ROM, "game version" ); + +// noset vars +idCVar gamedate( "gamedate", __DATE__, CVAR_GAME | CVAR_ROM, "" ); + +// server info +idCVar si_name( "si_name", "Quake 4 Server", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "name of the server" ); +// RITUAL BEGIN +// squirrel: added DeadZone multiplayer mode +//idCVar sq_numRoundsPerMatch( "dz_numRoundsPerMatch", "5", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of rounds per match in DeadZone", 1, 999999 ); +//idCVar sq_buyFreezeSeconds( "dz_buyFreezeSeconds", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of seconds players are frozen at the start of each round in DeadZone", 0, 30 ); +//idCVar sq_buyTimeSeconds( "dz_buyTimeSeconds", "20", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of additional seconds after buy freeze that buy zones are active", 0, 999999 ); +// squirrel: Mode-agnostic buymenus +idCVar si_isBuyingEnabled( "si_isBuyingEnabled", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable buying in current mode" ); +idCVar si_dropWeaponsInBuyingModes( "si_dropWeaponsInBuyingModes", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "dead players drop weapons, even in Buying game modes" ); +// RITUAL END +// RAVEN BEGIN +// ddynerman: new gametype strings +idCVar si_gameType( "si_gameType", si_gameTypeArgs[ 0 ], CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "game type - singleplayer, DM, Tourney, Team DM, CTF, Arena CTF, or DeadZone", si_gameTypeArgs, idCmdSystem::ArgCompletion_String ); +idCVar si_map( "si_map", "mp/q4dm1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map to be played next on server", idCmdSystem::ArgCompletion_MapName ); +idCVar si_mapCycle( "si_mapCycle", "", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE, "map cycle list semicolon delimited" ); +// bdube: raise player limit +idCVar si_maxPlayers( "si_maxPlayers", "12", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "max number of players allowed on the server", 1, 16 ); +// ddynerman: min players to start +idCVar si_minPlayers( "si_minPlayers", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "min number of players to start a game (only when warmup is enabled)", 1, 16 ); +// ddynerman: CTF +idCVar si_captureLimit( "si_captureLimit", "5", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "score limit for CTF", 1, MP_PLAYER_MAXFRAGS ); +// shouchard: for tourney +idCVar si_tourneyLimit( "si_tourneyLimit", "3", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of times a tourney will be run before cycling maps", 1, MP_PLAYER_MAXFRAGS ); +idCVar si_useReady( "si_useReady", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "require players to ready before starting a match" ); +idCVar si_allowVoting( "si_allowVoting", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_BOOL, "enable or disable server option voting" ); +// ddynerman: disable hitscan tint +idCVar si_allowHitscanTint( "si_allowHitscanTint", "2", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "use hitscan tint (e.g. rail color) 0 - no tinting allowed, 1 - player hitscan tinting allowed in DM and NO hitscan tinting in team games, 2 - player hitscan tinting allowed in DM and use team-color hitscan tints in team games" ); +idCVar si_privatePlayers( "si_privatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "number of private player slots reserved on the server. subtracts from si_maxPlayers, so a server with si_maxPlayers 16 and 4 private player slots will only allow 12 public players to connect - see g_privatePassword, privatePassword", 0, 16 ); +idCVar g_privatePassword( "g_privatePassword", "", CVAR_GAME | PC_CVAR_ARCHIVE, "server-side password to access reserved client slots, clients set privatePassword" ); +idCVar privatePassword( "privatePassword", "", CVAR_GAME | CVAR_NOCHEAT, "client password used to access a servers private player slots" ); +idCVar si_numPrivatePlayers( "si_numPrivatePlayers", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_ROM, "number of private slots currently in use" ); +idCVar si_suddenDeathRestart( "si_suddenDeathRestart", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "toggles whether or not to respawn players/items when team games enter sudden death" ); +// RAVEN END +idCVar si_fragLimit( "si_fragLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "frag limit", 0, MP_PLAYER_MAXFRAGS ); +idCVar si_timeLimit( "si_timeLimit", "10", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "time limit in minutes", 0, 60 ); +idCVar si_teamDamage( "si_teamDamage", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable team damage" ); +idCVar si_warmup( "si_warmup", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "do pre-game warmup" ); +idCVar si_usePass( "si_usePass", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "enable client password checking" ); +#ifdef _MPBETA + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | CVAR_ROM, "server is pure and does not allow modified data" ); +#else + idCVar si_pure( "si_pure", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "server is pure and does not allow modified data" ); +#endif // _MPBETA +idCVar si_spectators( "si_spectators", "1", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "allow spectators or require all clients to play" ); +idCVar si_shuffle( "si_shuffle", "0", CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_BOOL, "shuffle teams after each round" ); +// shouchard: g_balanceTDM->si_autobalance so we can also use it for CTF +// asalmon: Changed to archive only on PC +idCVar si_autobalance( "si_autobalance", "1", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL | PC_CVAR_ARCHIVE, "maintain even teams" ); + +// RAVEN BEGIN +// jscott: added entity filtering +idCVar si_entityFilter( "si_entityFilter", "", CVAR_GAME | CVAR_SERVERINFO, "filter to use when spawning entities" ); +idCVar si_countDown( "si_countDown", "10", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "pregame countdown in seconds", 4, 3600 ); +// MCG: added "weapon stay" option +idCVar si_weaponStay( "si_weaponStay", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_BOOL, "cannot pick up weapons you already have (get no ammo from them)" ); +// RAVEN END + +// RITUAL BEGIN +// DeadZone Mode and Buying related CVARS +idCVar si_deadZonePowerupTime( "si_deadZonePowerupTime", "45", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of time the dead zone powerup lasts" ); +idCVar si_buyModeStartingCredits( "si_buyModeStartingCredits", "1000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Amount of credits players start with in buying enable games" ); +idCVar si_buyModeMaxCredits( "si_buyModeMaxCredits", "25000", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Maximum amount of credits in buying enable games" ); +idCVar si_buyModeMinCredits( "si_buyModeMinCredits", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER, "Minimum amount of credits in buying enable games" ); +idCVar si_controlTime( "si_controlTime", "120", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "Time required to hold the dead zone", 1, 999 ); +// RITUAL END + +idCVar si_fps( "si_fps", "60", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "Server framerate/game tick rate", 30, 90 ); + +idCVar ri_useViewerPass( "ri_useViewerPass", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "use g_viewerPassword for viewers/repeaters" ); +idCVar g_viewerPassword( "g_viewerPassword", "", CVAR_GAME | CVAR_ARCHIVE, "password for viewers/repeaters" ); + +idCVar ri_privateViewers( "ri_privateViewers", "0", CVAR_GAME | CVAR_REPEATERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "number of private viewer slots" ); +idCVar g_privateViewerPassword( "g_privateViewerPassword", "", CVAR_GAME | CVAR_ARCHIVE, "privatePassword for private viewer slots" ); +idCVar g_repeaterPassword( "g_repeaterPassword", "", CVAR_GAME | CVAR_ARCHIVE, "privatePassword for repeaters" ); + +idCVar ri_numViewers( "ri_numViewers", "0", CVAR_GAME | CVAR_REPEATERINFO | CVAR_INTEGER | CVAR_ROM, "number of viewer slots in use" ); +idCVar ri_numPrivateViewers( "ri_numPrivateViewers", "0", CVAR_GAME | CVAR_REPEATERINFO | CVAR_INTEGER | CVAR_ROM, "number of private viewer slots in use" ); + +idCVar ri_name( "ri_name", "", CVAR_GAME | CVAR_ARCHIVE, "override the server's si_name with this for relays" ); + +idCVar g_noTVChat( "g_noTVChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Server enable/disable flag for viewer chat on Q4TV" ); + +// user info +idCVar ui_name( "ui_name", "Player", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player name" ); +idCVar ui_team( "ui_team", ui_teamArgs[ 0 ], CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player team", ui_teamArgs, idCmdSystem::ArgCompletion_String ); +// RAVEN BEGIN +// ddynerman: new UI cvars +idCVar ui_model( "ui_model", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model, blank uses default model" ); +idCVar ui_model_backup( "ui_model_backup", "", CVAR_GAME | CVAR_USERINFO, "player model backup" ); +idCVar ui_model_marine( "ui_model_marine", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on marine team in team games, blank uses default model" ); +idCVar ui_model_strogg( "ui_model_strogg", "", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "player model used on strogg team in team games, blank uses default model" ); +idCVar ui_clan( "ui_clan", "", CVAR_GAME | CVAR_USERINFO | PC_CVAR_ARCHIVE | CVAR_CASE_SENSITIVE | CVAR_SPECIAL_CONCAT, "player clan" ); +idCVar ui_hitscanTint( "ui_hitscanTint", "120.0 0.6 1.0", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE, "a tint applied to select hitscan effects. Specified as a value in HSV color space. Hue [0.0-360.0] Saturation [0.0-1.0] Value [0.75-1.0]" ); +// RAVEN END +idCVar ui_autoSwitch( "ui_autoSwitch", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto switch weapon" ); +idCVar ui_autoReload( "ui_autoReload", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "auto reload weapon" ); +idCVar ui_showGun( "ui_showGun", "1", CVAR_GAME | CVAR_USERINFO | CVAR_ARCHIVE | CVAR_BOOL, "show gun" ); +idCVar ui_ready( "ui_ready", si_readyArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "player is ready to start playing", idCmdSystem::ArgCompletion_String ); +idCVar ui_spectate( "ui_spectate", si_spectateArgs[ 0 ], CVAR_GAME | CVAR_USERINFO, "play or spectate", idCmdSystem::ArgCompletion_String ); +idCVar ui_chat( "ui_chat", "0", CVAR_GAME | CVAR_USERINFO | CVAR_BOOL | CVAR_ROM | CVAR_CHEAT, "player is chatting" ); +idCVar ui_handicap( "ui_handicap", "100", CVAR_GAME | CVAR_USERINFO | CVAR_INTEGER | CVAR_ARCHIVE, "player damage output handicap" ); + +idCVar hud_showSpeed( "hud_showSpeed", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Show player's movement speed, measured in Units Per Second." ); +idCVar hud_showInput( "hud_showInput", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "When set to 1, shows the players movement controls on the HUD"); +idCVar hud_inputPosition( "hud_inputPosition", "580 90", CVAR_GAME | CVAR_ARCHIVE, "Input display position (x y)"); +idCVar hud_inputColor( "hud_inputColor", "1 0 0", CVAR_GAME | CVAR_ARCHIVE, "Input display color (r g b, range 0 to 1)"); + +// change anytime vars +idCVar developer( "developer", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_forceModel( "g_forceModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces all players to this model in non-team gameplay modes. See g_forceStroggModel, g_forceMarineModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModel ); +idCVar g_forceStroggModel( "g_forceStroggModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Strogg team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelStrogg ); +idCVar g_forceMarineModel( "g_forceMarineModel", "", CVAR_GAME | CVAR_ARCHIVE, "Locally forces Marine team players to this model in team gameplay modes. See g_forceModel. listModels to list available models", idCmdSystem::ArgCompletion_ForceModelMarine ); + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +idCVar g_fixedHorizFOV( "r_fixedHorizFOV", "0", CVAR_RENDERER | CVAR_BOOL, "vertical stretch for letterboxed cinematics authored for 4:3 aspect" ); +idCVar g_cinematic( "g_cinematic", "1", CVAR_GAME | CVAR_BOOL, "skips updating entities that aren't marked 'cinematic' '1' during cinematics" ); +idCVar g_cinematicMaxSkipTime( "g_cinematicMaxSkipTime", "600", CVAR_GAME | CVAR_FLOAT, "# of seconds to allow game to run when skipping cinematic. prevents lock-up when cinematic doesn't end.", 0, 3600 ); + +idCVar g_muzzleFlash( "g_muzzleFlash", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show muzzle flashes" ); +idCVar g_projectileLights( "g_projectileLights", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show dynamic lights on projectiles" ); +idCVar g_doubleVision( "g_doubleVision", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show double vision when taking damage" ); +idCVar g_monsters( "g_monsters", "1", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_decals( "g_decals", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "show decals such as bullet holes" ); +idCVar g_knockback( "g_knockback", "1000", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar g_skill( "g_skill", "1", CVAR_GAME | CVAR_INTEGER, "difficulty level", 0, MAX_SKILL_LEVELS - 1 ); +idCVar g_nightmare( "g_nightmare", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "if nightmare mode is allowed" ); +idCVar g_gravity( "g_gravity", DEFAULT_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "singleplayer gravity" ); +idCVar g_mp_gravity( "g_mp_gravity", DEFAULT_MP_GRAVITY_STRING, CVAR_GAME | CVAR_FLOAT, "multiplayer gravity" ); +idCVar g_skipFX( "g_skipFX", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_skipParticles( "g_skipParticles", "0", CVAR_GAME | CVAR_BOOL, "" ); + +idCVar g_nailTrail( "g_nailTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show smoke trails on nail projectiles" ); +idCVar g_grenadeTrail( "g_grenadeTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show smoke trails on grenade projectiles" ); +idCVar g_rocketTrail( "g_rocketTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show smoke trails on rocket projectiles" ); +idCVar g_railTrail( "g_railTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show spiral fx on rail trail" ); +idCVar g_napalmTrail( "g_napalmTrail", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "show fiery trails on napalm projectiles" ); + +idCVar g_predictedProjectiles( "g_predictedProjectiles", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "predict projectiles on the client" ); + +idCVar g_disasm( "g_disasm", "0", CVAR_GAME | CVAR_BOOL, "disassemble script into base/script/disasm.txt on the local drive when script is compiled" ); +idCVar g_debugBounds( "g_debugBounds", "0", CVAR_GAME | CVAR_BOOL, "checks for models with bounds > 2048" ); +idCVar g_debugAnim( "g_debugAnim", "-1", CVAR_GAME | CVAR_INTEGER, "displays information on which animations are playing on the specified entity number. set to -1 to disable." ); +idCVar g_debugMove( "g_debugMove", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugDamage( "g_debugDamage", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugWeapon( "g_debugWeapon", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugScript( "g_debugScript", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugMover( "g_debugMover", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugTriggers( "g_debugTriggers", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_debugCinematic( "g_debugCinematic", "0", CVAR_GAME, "set to the name of the state you want to debug or * for all" ); +// RAVEN BEGIN +// bdube: added +idCVar g_debugState( "g_debugState", "0", CVAR_GAME, "" ); +idCVar g_stopTime( "g_stopTime", "0", CVAR_GAME | CVAR_BOOL, "" ); +//idCVar g_damageScale( "g_damageScale", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale final damage on player by this factor" ); +// RAVEN END +idCVar g_armorProtection( "g_armorProtection", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage" ); +idCVar g_armorProtectionMP( "g_armorProtectionMP", "0.66667", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "armor takes this percentage of damage in mp" ); +idCVar g_useDynamicProtection( "g_useDynamicProtection", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE, "scale damage and armor dynamically to keep the player alive more often" ); +idCVar g_healthTakeTime( "g_healthTakeTime", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how often to take health in nightmare mode" ); +idCVar g_healthTakeAmt( "g_healthTakeAmt", "5", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how much health to take in nightmare mode" ); +idCVar g_healthTakeLimit( "g_healthTakeLimit", "25", CVAR_GAME | CVAR_INTEGER | PC_CVAR_ARCHIVE, "how low can health get taken in nightmare mode" ); + + + +idCVar g_showPVS( "g_showPVS", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +idCVar g_showTargets( "g_showTargets", "0", CVAR_GAME | CVAR_BOOL, "draws entities and thier targets. hidden entities are drawn grey." ); +idCVar g_showTriggers( "g_showTriggers", "0", CVAR_GAME | CVAR_BOOL, "draws trigger entities (orange) and thier targets (green). disabled triggers are drawn grey." ); +idCVar g_showCollisionWorld( "g_showCollisionWorld", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showCollisionModels( "g_showCollisionModels", "0", CVAR_GAME | CVAR_INTEGER, "0 = off, 1 = draw collision models, 2 = only draw player collision models. g_maxShowDistance controls distance." ); +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces +idCVar g_showCollisionTraces( "g_showCollisionTraces", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); +// ddynerman: SD's clip sector code +idCVar g_showClipSectors( "g_showClipSectors", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showClipSectorFilter( "g_showClipSectorFilter", "0", CVAR_GAME, "" ); +idCVar g_showAreaClipSectors( "g_showAreaClipSectors", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN END +idCVar g_maxShowDistance( "g_maxShowDistance", "128", CVAR_GAME | CVAR_FLOAT, "Distance at which to draw clipmodels and clipworld - Will significantly hurt performance at values above 512" ); +idCVar g_showEntityInfo( "g_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showviewpos( "g_showviewpos", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_showcamerainfo( "g_showcamerainfo", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "displays the current frame # for the camera when playing cinematics" ); +idCVar g_showTestModelFrame( "g_showTestModelFrame", "0", CVAR_GAME | CVAR_BOOL, "displays the current animation and frame # for testmodels" ); +idCVar g_showActiveEntities( "g_showActiveEntities", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around thinking entities. dormant entities (outside of pvs) are drawn yellow. non-dormant are green." ); +idCVar g_showEnemies( "g_showEnemies", "0", CVAR_GAME | CVAR_BOOL, "draws boxes around monsters that have targeted the the player" ); + +idCVar g_frametime( "g_frametime", "0", CVAR_GAME | CVAR_BOOL, "displays timing information for each game frame" ); +idCVar g_timeentities( "g_timeEntities", "0", CVAR_GAME | CVAR_FLOAT, "when non-zero, shows entities whose think functions exceeded the # of milliseconds specified" ); + +// RAVEN BEGIN +// bdube: frame command debugging +idCVar g_showFrameCmds( "g_showFrameCmds", "0", CVAR_GAME | CVAR_BOOL, "displays frame commands as they are executed" ); +idCVar g_showGodDamage( "g_showGodDamage", "0", CVAR_GAME | CVAR_BOOL, "displays the amount of damage taken while in god mode on the hud" ); +idCVar g_debugVehicle( "g_debugVehicle", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +// RAVEN BEGIN +// twhitaker: for rvVehicleDriver +idCVar g_debugVehicleDriver( "g_debugVehicleDriver", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the func_vehicle_driver" ); +idCVar g_debugVehicleAI( "g_debugVehicleAI", "0", CVAR_GAME | CVAR_INTEGER, "enables debug features for the vehicle ai system" ); +idCVar g_vehicleMode( "g_vehicleMode", "1", CVAR_GAME | CVAR_INTEGER, "enables the new vehicle control system for the GEV." ); +// RAVEN END +idCVar g_allowVehicleGunOverheat( "g_allowVehicleGunOverheat","1", CVAR_GAME | CVAR_BOOL, "allows disabling the gun overheating mechanism for vehicles that use it." ); + +idCVar ai_debugScript( "ai_debugScript", "-1", CVAR_GAME | CVAR_INTEGER, "displays script calls for the specified monster entity number" ); +idCVar ai_debugMove( "ai_debugMove", "0", CVAR_GAME | CVAR_BOOL, "draws movement information for monsters" ); +idCVar ai_debugTrajectory( "ai_debugTrajectory", "0", CVAR_GAME | CVAR_BOOL, "draws trajectory tests for monsters" ); +idCVar ai_debugTactical( "ai_debugTactical", "0", CVAR_GAME, "draws tactical information for monsters" ); +idCVar ai_debugHelpers( "ai_debugHelpers", "0", CVAR_GAME, "draws ai helpers" ); +idCVar ai_debugFilterString( "ai_debugFilterString", "", CVAR_GAME, "see ai_debugFilter" ); +idCVar ai_testPredictPath( "ai_testPredictPath", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCombatNodes( "ai_showCombatNodes", "0", CVAR_GAME | CVAR_BOOL, "draws attack cones for monsters" ); +idCVar ai_showPaths( "ai_showPaths", "0", CVAR_GAME | CVAR_BOOL, "draws path_* entities" ); +idCVar ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "0", CVAR_GAME | CVAR_INTEGER, "draws obstacle avoidance information for monsters. if 2, draws obstacles for player, as well", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar ai_blockedFailSafe( "ai_blockedFailSafe", "1", CVAR_GAME | CVAR_BOOL, "enable blocked fail safe handling" ); +idCVar ai_debugSquad( "ai_debugSquad", "0", CVAR_GAME | CVAR_BOOL, "draws squad info for allies" ); +idCVar ai_debugStealth( "ai_debugStealth", "0", CVAR_GAME | CVAR_INTEGER, "draws suspicion info for enemies" ); +idCVar ai_allowTacticalRush( "ai_allowTacticalRush", "1", CVAR_GAME | CVAR_BOOL, "allows tactical ai to rush an enemy when hurt" ); + + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +idCVar ai_speeds( "ai_speeds", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_freeze( "ai_freeze", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_animShow( "ai_animShow", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar ai_showCover( "ai_showCover", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_showTacticalFeatures( "ai_showTacticalFeatures", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar ai_disableEntTactical( "ai_disableEntTactical", "0", CVAR_GAME | CVAR_BOOL, "disables tactical points around entities" ); +idCVar ai_disableAttacks( "ai_disableAttacks", "0", CVAR_GAME | CVAR_BOOL, "disables attack decisions" ); +idCVar ai_disableSimpleThink( "ai_disableSimpleThink", "0", CVAR_GAME | CVAR_BOOL, "disables simple thinking in AI entities" ); +idCVar ai_disableCover( "ai_disableCover", "0", CVAR_GAME | CVAR_BOOL, "disables AI using cover points" ); +//cdr: use new master move functions +idCVar ai_useRVMasterMove( "ai_useRVMasterMove", "0", CVAR_GAME | CVAR_BOOL, "changes AI to use new master move function" ); +//jshepard: allow out of date AAS files to be used, for testing +idCVar ai_allowOldAAS( "ai_allowOldAAS", "0", CVAR_GAME | CVAR_BOOL, "allows AI to use most recent AAS file, even if it is not up-to-date. Enable only for testing."); +// twhitaker: debugging support for eye focus +idCVar ai_debugEyeFocus( "ai_debugEyeFocus", "0", CVAR_GAME | CVAR_BOOL, "draws eye focus info" ); +//mcg: always allow player to push buddies, unless scripted +idCVar ai_playerPushAlways( "ai_playerPushAlways", "1", CVAR_GAME | CVAR_BOOL, "always allow player to push buddies, unless scripted" ); +// RAVEN END + +idCVar g_dvTime( "g_dvTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvAmplitude( "g_dvAmplitude", "0.001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dvFrequency( "g_dvFrequency", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_kickTime( "g_kickTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_kickAmplitude( "g_kickAmplitude", "0.0001", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobTime( "g_blobTime", "1", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_blobSize( "g_blobSize", "1", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar g_testHealthVision( "g_testHealthVision", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_editEntityMode( "g_editEntityMode", "0", CVAR_GAME | CVAR_INTEGER, "0 = off\n" + "1 = lights\n" + "2 = sounds\n" + "3 = articulated figures\n" + "4 = particle systems\n" + "5 = monsters\n" + "6 = entity names\n" +// RAVEN BEGIN +// bdube: extended + "7 = entity models\n" + "8 = effects", 0, 8, idCmdSystem::ArgCompletion_Integer<0,8> ); +// rhummer: Added archive flag. +idCVar g_editEntityDistance( "g_editEntityDistance", "512", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit" ); +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. Also added archive flag. +idCVar g_editEntityTextDistance( "g_editEntityTextDistance", "256", CVAR_GAME | CVAR_ARCHIVE, "range to display entities to edit text information"); +idCVar g_testCTF( "g_testCTF", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "" ); +// rjohnson: entity usage stats +idCVar g_keepEntityStats( "g_keepEntityStats", "0", CVAR_GAME | CVAR_CHEAT |CVAR_BOOL, "keep track of entity usage stats" ); +// RAVEN END +idCVar g_dragEntity( "g_dragEntity", "0", CVAR_GAME | CVAR_BOOL, "allows dragging physics objects around by placing the crosshair over them and holding the fire button" ); +idCVar g_dragDamping( "g_dragDamping", "0.5", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_dragShowSelection( "g_dragShowSelection", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar g_dropItemRotation( "g_dropItemRotation", "", CVAR_GAME, "" ); + +idCVar g_vehicleVelocity( "g_vehicleVelocity", "1000", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_vehicleForce( "g_vehicleForce", "50000", CVAR_GAME | CVAR_FLOAT, "" ); + +idCVar ik_enable( "ik_enable", "1", CVAR_GAME | CVAR_BOOL, "enable IK" ); +idCVar ik_debug( "ik_debug", "0", CVAR_GAME | CVAR_BOOL, "show IK debug lines" ); + +idCVar af_useLinearTime( "af_useLinearTime", "1", CVAR_GAME | CVAR_BOOL, "use linear time algorithm for tree-like structures" ); +idCVar af_useImpulseFriction( "af_useImpulseFriction", "0", CVAR_GAME | CVAR_BOOL, "use impulse based contact friction" ); +idCVar af_useJointImpulseFriction( "af_useJointImpulseFriction","0", CVAR_GAME | CVAR_BOOL, "use impulse based joint friction" ); +idCVar af_useSymmetry( "af_useSymmetry", "1", CVAR_GAME | CVAR_BOOL, "use constraint matrix symmetry" ); +idCVar af_skipSelfCollision( "af_skipSelfCollision", "0", CVAR_GAME | CVAR_BOOL, "skip self collision detection" ); +idCVar af_skipLimits( "af_skipLimits", "0", CVAR_GAME | CVAR_BOOL, "skip joint limits" ); +idCVar af_skipFriction( "af_skipFriction", "0", CVAR_GAME | CVAR_BOOL, "skip friction" ); +idCVar af_forceFriction( "af_forceFriction", "-1", CVAR_GAME | CVAR_FLOAT, "force the given friction value" ); +idCVar af_maxLinearVelocity( "af_maxLinearVelocity", "128", CVAR_GAME | CVAR_FLOAT, "maximum linear velocity" ); +idCVar af_maxAngularVelocity( "af_maxAngularVelocity", "1.57", CVAR_GAME | CVAR_FLOAT, "maximum angular velocity" ); +idCVar af_timeScale( "af_timeScale", "1", CVAR_GAME | CVAR_FLOAT, "scales the time" ); +idCVar af_jointFrictionScale( "af_jointFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the joint friction" ); +idCVar af_contactFrictionScale( "af_contactFrictionScale", "0", CVAR_GAME | CVAR_FLOAT, "scales the contact friction" ); +idCVar af_highlightBody( "af_highlightBody", "", CVAR_GAME, "name of the body to highlight" ); +idCVar af_highlightConstraint( "af_highlightConstraint", "", CVAR_GAME, "name of the constraint to highlight" ); +idCVar af_showTimings( "af_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show articulated figure cpu usage" ); +idCVar af_showConstraints( "af_showConstraints", "0", CVAR_GAME | CVAR_BOOL, "show constraints" ); +idCVar af_showConstraintNames( "af_showConstraintNames", "0", CVAR_GAME | CVAR_BOOL, "show constraint names" ); +idCVar af_showConstrainedBodies( "af_showConstrainedBodies", "0", CVAR_GAME | CVAR_BOOL, "show the two bodies contrained by the highlighted constraint" ); +idCVar af_showPrimaryOnly( "af_showPrimaryOnly", "0", CVAR_GAME | CVAR_BOOL, "show primary constraints only" ); +idCVar af_showTrees( "af_showTrees", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures" ); +idCVar af_showLimits( "af_showLimits", "0", CVAR_GAME | CVAR_BOOL, "show joint limits" ); +idCVar af_showBodies( "af_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show bodies" ); +idCVar af_showBodyNames( "af_showBodyNames", "0", CVAR_GAME | CVAR_BOOL, "show body names" ); +idCVar af_showMass( "af_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each body" ); +idCVar af_showTotalMass( "af_showTotalMass", "0", CVAR_GAME | CVAR_BOOL, "show the total mass of each articulated figure" ); +idCVar af_showInertia( "af_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each body" ); +idCVar af_showVelocity( "af_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each body" ); +idCVar af_showActive( "af_showActive", "0", CVAR_GAME | CVAR_BOOL, "show tree-like structures of articulated figures not at rest" ); +idCVar af_testSolid( "af_testSolid", "1", CVAR_GAME | CVAR_BOOL, "test for bodies initially stuck in solid" ); + +idCVar rb_showTimings( "rb_showTimings", "0", CVAR_GAME | CVAR_BOOL, "show rigid body cpu usage" ); +idCVar rb_showBodies( "rb_showBodies", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies" ); +idCVar rb_showMass( "rb_showMass", "0", CVAR_GAME | CVAR_BOOL, "show the mass of each rigid body" ); +idCVar rb_showInertia( "rb_showInertia", "0", CVAR_GAME | CVAR_BOOL, "show the inertia tensor of each rigid body" ); +idCVar rb_showVelocity( "rb_showVelocity", "0", CVAR_GAME | CVAR_BOOL, "show the velocity of each rigid body" ); +idCVar rb_showActive( "rb_showActive", "0", CVAR_GAME | CVAR_BOOL, "show rigid bodies that are not at rest" ); + +// RAVEN BEGIN +// bdube: more rigid body debug +idCVar rb_showContacts( "rb_showContacts", "0", CVAR_GAME | CVAR_BOOL, "show rigid body contacts" ); +// RAVEN END + +// The default values for player movement cvars are set in def/player.def +idCVar pm_jumpheight( "pm_jumpheight", "48", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "approximate hieght the player can jump" ); +idCVar pm_stepsize( "pm_stepsize", "16", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "maximum height the player can step up without jumping" ); +idCVar pm_crouchspeed( "pm_crouchspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while crouched" ); +// RAVEN BEGIN +idCVar pm_speed( "pm_speed", "160", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while running" ); +idCVar pm_walkspeed( "pm_walkspeed", "80", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while walking" ); +// RAVEN END +idCVar pm_noclipspeed( "pm_noclipspeed", "270", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while in noclip" ); +idCVar pm_spectatespeed( "pm_spectatespeed", "450", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "speed the player can move while spectating" ); +idCVar pm_spectatebbox( "pm_spectatebbox", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "size of the spectator bounding box" ); +idCVar pm_usecylinder( "pm_usecylinder", "1", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER | CVAR_NORESET | CVAR_ARCHIVE, "use a cylinder approximation instead of a bounding box for player collision detection (>= 3 - custom number of sides)" ); +idCVar pm_minviewpitch( "pm_minviewpitch", "-89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look up (negative values are up)" ); +idCVar pm_maxviewpitch( "pm_maxviewpitch", "89", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "amount player's view can look down" ); +idCVar pm_stamina( "pm_stamina", "24", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "length of time player can run" ); +idCVar pm_staminathreshold( "pm_staminathreshold", "45", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "when stamina drops below this value, player gradually slows to a walk" ); +idCVar pm_staminarate( "pm_staminarate", "0.75", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "rate that player regains stamina. divide pm_stamina by this value to determine how long it takes to fully recharge." ); + +// ddynerman: adjusted bboxes to actual height +idCVar pm_normalheight( "pm_normalheight", "77", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while standing" ); +idCVar pm_crouchheight( "pm_crouchheight", "49", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while crouched" ); + +idCVar pm_crouchviewheight( "pm_crouchviewheight", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while crouched" ); +idCVar pm_normalviewheight( "pm_normalviewheight", "68", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while standing" ); + +idCVar pm_deadheight( "pm_deadheight", "20", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's bounding box while dead" ); +idCVar pm_deadviewheight( "pm_deadviewheight", "10", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "height of player's view while dead" ); +idCVar pm_crouchrate( "pm_crouchrate", "0.87", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "time it takes for player's view to change from standing to crouching" ); +idCVar pm_bboxwidth( "pm_bboxwidth", "32", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NORESET, "x/y size of player's bounding box" ); +idCVar pm_crouchbob( "pm_crouchbob", "0.5", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob much faster when crouched" ); +idCVar pm_walkbob( "pm_walkbob", "0.3", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob slowly when walking" ); +idCVar pm_runbob( "pm_runbob", "0.4", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "bob faster when running" ); +idCVar pm_runpitch( "pm_runpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_runroll( "pm_runroll", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobup( "pm_bobup", "0.005", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobpitch( "pm_bobpitch", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT | CVAR_NORESET, "" ); +idCVar pm_bobroll( "pm_bobroll", "0.002", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT | CVAR_NOCHEAT, "" ); +idCVar pm_thirdPersonRange( "pm_thirdPersonRange", "80", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "camera distance from player in 3rd person" ); +idCVar pm_thirdPersonHeight( "pm_thirdPersonHeight", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "height of camera from normal view height in 3rd person" ); +idCVar pm_thirdPersonAngle( "pm_thirdPersonAngle", "0", CVAR_GAME | CVAR_FLOAT | CVAR_NORESET, "direction of camera from player in 3rd person in degrees (0 = behind player, 180 = in front)" ); +idCVar pm_thirdPersonClip( "pm_thirdPersonClip", "1", CVAR_GAME | CVAR_BOOL, "clip third person view into world space" ); +idCVar pm_thirdPerson( "pm_thirdPerson", "0", CVAR_GAME | CVAR_BOOL, "enables third person view" ); +idCVar pm_thirdPersonDeath( "pm_thirdPersonDeath", "0", CVAR_GAME | CVAR_BOOL, "enables third person view when player dies" ); +idCVar pm_modelView( "pm_modelView", "0", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "draws camera from POV of player model (1 = always, 2 = when dead)", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar pm_airTics( "pm_air", "1800", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER, "how long in milliseconds the player can go without air before he starts taking damage" ); + +// RAVEN BEGIN +// asalmon: parameters for aim assistance on Xenon - or a non-final pc build so Caryn can edit the guis +#if defined( _XBOX ) || !defined( _FINAL ) +idCVar pm_AimAssist( "pm_AimAssist", "2", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE , "Enable Xbox aim assistance. 1 to use change player view method. 2 to use change muzzle aim method.\n"); +idCVar pm_AimAssistDistance( "pm_AimAssistDistance", "1000", CVAR_GAME | CVAR_INTEGER, "The max aim assist distance.\n"); +idCVar pm_AimAssistThreshold( "pm_AimAssistThreshold", "1.0", CVAR_GAME | CVAR_FLOAT, "Threshold by which the projectile is aimed at the offending target.\n"); +idCVar pm_AimAssistFOV( "pm_AimAssistFOV", "10", CVAR_GAME | CVAR_INTEGER, "The field of view for aim assistance.\n"); +idCVar pm_AimAssistBump( "pm_AimAssistBump", "10", CVAR_GAME | CVAR_INTEGER, "The percentage of correction applied either to the view or the muzzle aim.\n"); +idCVar pm_showAimAssist( "pm_showAimAssist", "0", CVAR_GAME | CVAR_BOOL, "Draw aim assist frustum and bounding boxes.\n"); +idCVar pm_AimAssistSlow( "pm_AimAssistSlow", "50", CVAR_GAME | CVAR_INTEGER, "The percentage to slow the turning motion by when targeting an enemy.\n"); + +//asalmon: xenon controller config cvars +idCVar pm_ThumbstickConfig( "pm_ThumbstickConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the thumbstick config on Xenon. 0 right handed, 1 left handed.\n"); +idCVar pm_ButtonConfig( "pm_ButtonConfig", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "Change the button configuration for Xenon.\n"); +idCVar pm_Inversion( "pm_Inversion", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_GUI, "invert look up and down\n"); + +idCVar pm_VLookSens( "pm_VLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HLookSens( "pm_HLookSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_VMoveSens( "pm_VMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); +idCVar pm_HMoveSens( "pm_HMoveSens", "1.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT, "Xenon sensitivity\n"); + +idCVar pm_voiceEnabled( "pm_voiceEnabled", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL | CVAR_GUI, "Enable/disable voice.\n"); + +//asalmon: Xenon leaderboard cvars +idCVar ui_LeaderboardView( "ui_LeaderboardView", "17", CVAR_INTEGER | CVAR_NOCHEAT, "Which leaderboard to show.\n"); +idCVar ui_LeaderboardSort( "ui_LeaderboardSort", "1", CVAR_INTEGER | CVAR_NOCHEAT, "How to sort the leaderboard. 0 for rating, 1 for ranking, 2 for friends, 3 find logged in player.\n"); + +//nrausch +idCVar pm_RocketJumpAutocenter( "pm_RocketJumpAutocenter", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "Automatic autocentering following a rocket jump\n"); +idCVar pm_IAmACheater( "pm_IAmACheater", "0", CVAR_GAME | CVAR_BOOL, "Whomever is playing is a dirty, rotten cheater\n"); + +idCVar g_systemLinkMatch( "g_systemLinkMatch", "0", CVAR_INTEGER, "In a system link game\n"); + +//asalmon: cvars for Live teams. Teams will now be a post launch feature but this was left here in case it is of use on future projects +//idCVar ui_LiveClanName( "ui_LiveClanName", "My Clan", CVAR_GAME | CVAR_USERINFO, "The name of the live clan being created\n"); +//idCVar ui_LiveClanDesc( "ui_LiveClanDesc", "A Quake 4 clan", CVAR_GAME | CVAR_USERINFO, "The description of the live clan being created\n"); +//idCVar ui_LiveClanMotto( "ui_LiveClanMotto", "We love Quake 4", CVAR_GAME | CVAR_USERINFO, "The motto of the live clan being created\n"); +//idCVar ui_LiveClanUrl( "ui_LiveClanUrl", "www.ravensoft.com", CVAR_GAME | CVAR_USERINFO, "The url of the live clan being created\n"); +// +//idCVar ui_LiveRecruitName( "ui_LiveRecruitName", "Recruit Name Here", CVAR_GAME | CVAR_USERINFO, "name of the gamer you are trying recruit\n"); +//idCVar ui_LiveRecruitPDelete( "ui_LiveRecruitPDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit delete permissions\n"); +//idCVar ui_LiveRecruitPData( "ui_LiveRecruitPData", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit modify data permissions\n"); +//idCVar ui_LiveRecruitPMemberPermissions("ui_LiveRecruitPMemberPermissions", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member modify permissions\n"); +//idCVar ui_LiveRecruitPMemberDelete( "ui_LiveRecruitPMemberDelete", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member delete permissions\n"); +//idCVar ui_LiveRecruitPMemberRecruit( "ui_LiveRecruitPMemberRecruit", "0", CVAR_GAME | CVAR_USERINFO, "give the recruit member recruit permissions\n"); +#endif + +idCVar pm_zoomedSlow( "pm_zoomedSlow", "100", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "Slow look speed while zoomed 0..100% of speed"); + +#ifndef _XENON +idCVar pm_isZoomed( "pm_isZoomed", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT | CVAR_NORESET, "if nonzero, is the slow speed"); +#endif + +// nmckenzie: added ability to try alternate accelerations. +idCVar pm_acceloverride( "pm_acceloverride", "0", CVAR_GAME | CVAR_FLOAT, "Adjust the player acceleration." ); +idCVar pm_frictionoverride( "pm_frictionoverride", "-1", CVAR_GAME | CVAR_FLOAT, "Adjust the player friciton." ); +idCVar pm_forcespectatormove( "pm_forcespectatormove", "0", CVAR_GAME | CVAR_FLOAT, "Force the player to move like a spectator (fly)." ); +// bdube: added vehicle cvars +idCVar pm_vehicleCameraSnap( "pm_vehicleCameraSnap", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraMinDist( "pm_vehicleCameraMinDist", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraSpeedScale( "pm_vehicleCameraSpeedScale", "0.5", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleCameraScaleMax( "pm_vehicleCameraScaleMax", "300", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar pm_vehicleSoundLerpScale( "pm_vehicleSoundLerpScale", "10", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +// RAVEN END + +idCVar g_showPlayerShadow( "g_showPlayerShadow", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables shadow of player model" ); + +idCVar g_skipPlayerShadowsMP( "g_skipPlayerShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all player shadows in multiplayer" ); +idCVar g_skipItemShadowsMP( "g_skipItemShadowsMP", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "disables all item shadows in multiplayer" ); +idCVar g_simpleItems( "g_simpleItems", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "render icon representations of items instead of the actual model" ); +idCVar g_showHud( "g_showHud", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar g_showProjectilePct( "g_showProjectilePct", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "enables display of player hit percentage" ); +// RAVEN BEGIN +// dluetscher: changed to g_brassTime +idCVar g_brassTime( "g_brassTime", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "amount of time brass should stay in the world before dissapearing, set to 0 to disable brass" ); +// RAVEN END +idCVar g_gun_x( "g_gunX", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_gun_y( "g_gunY", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_gun_z( "g_gunZ", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar g_weaponFovEffect( "g_weaponFovEffect", "0", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "Adjusts the position of the weapon model with increased fov" ); + +idCVar g_viewNodalX( "g_viewNodalX", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_viewNodalZ( "g_viewNodalZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// RAVEN BEGIN +// jshepard: fov as a float for smoother transitions? +idCVar g_fov( "g_fov", "90", CVAR_GAME | CVAR_FLOAT | PC_CVAR_ARCHIVE, "" ); +// RAVEN END +idCVar g_skipViewEffects( "g_skipViewEffects", "0", CVAR_GAME | CVAR_BOOL, "skip damage and other view effects" ); +idCVar g_mpWeaponAngleScale( "g_mpWeaponAngleScale", "0", CVAR_GAME | CVAR_FLOAT, "Control the weapon sway in MP" ); + +// RAVEN BEGIN +// bdube: crosshairs +// mekberg: custom size +idCVar g_crosshairSize( "g_crosshairSize", "32", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "crosshair size: 16,24,32,40,48", 16, 48 ); +//idCVar g_crosshairColor( "g_crosshairColor", "0.458 0.894 0.247 .75", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +idCVar g_crosshairColor( "g_crosshairColor", "1 1 1 1", CVAR_GAME | CVAR_ARCHIVE, "sets the combat crosshair color" ); +// cnicholson: Custom crosshair +idCVar g_crosshairCustom( "g_crosshairCustom", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "sets the custom combat crosshair" ); +idCVar g_crosshairCustomFile( "g_crosshairCustomFile", "0", CVAR_GAME | PC_CVAR_ARCHIVE, "stores the custom crosshair's filename" ); +idCVar g_crosshairCharInfoFar( "g_crosshairCharInfoFar", "1", CVAR_GAME | CVAR_BOOL, "instead of a green crosshair from far away, full character info always draws" ); +// bdube: database entries +idCVar g_showHudPopups( "g_showHudPopups", "1", CVAR_GAME | PC_CVAR_ARCHIVE | CVAR_BOOL, "displays objective and database popups on the hud" ); +idCVar g_showRange( "g_showRange", "0", CVAR_GAME | CVAR_CHEAT | CVAR_BOOL, "shows the range from the player to the first collision under the players crosshair" ); +// bdube: debug hud +idCVar g_showDebugHud( "g_showDebugHud", "0", CVAR_GAME | CVAR_INTEGER, "displays the debug hud\n" + "0 = off\n" + "1 = player\n" + "2 = physics\n" + "3 = AI\n" + "4 = vehicle\n" + "5 = performance\n" + "6 = effects\n" + "7 = map information\n" + "8 = AI performance\n" + "9 = MP\n" + "10 = Sound\n" + "32 = scratch\n" ); +// bdube: cvar for messing with foreshortening and gun position +idCVar g_gun_pitch( "g_gunPitch", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_yaw( "g_gunYaw", "0", CVAR_GAME | CVAR_FLOAT, "" ); +idCVar g_gun_roll( "g_gunRoll", "0", CVAR_GAME | CVAR_FLOAT, "" ); +// abahr: +idCVar g_gunViewStyle( "g_gunViewStyle", "0", CVAR_GAME | CVAR_NOCHEAT | PC_CVAR_ARCHIVE | CVAR_INTEGER, "style presets\n" + "0 = Q3 style\n" + "1 = Shouldered style\n"); +// jscott: cvar for debugging playbacks +idCVar g_showPlayback( "g_showPlayback", "0", CVAR_GAME | CVAR_INTEGER, "show g_currentPlayback" ); +idCVar g_currentPlayback( "g_currentPlayback", "", CVAR_GAME, "name of playback shown by g_showPlayback" ); +// jscott: unused +//idCVar g_testParticle( "g_testParticle", "0", CVAR_GAME | CVAR_INTEGER, "test particle visualation, set by the particle editor" ); +//idCVar g_testParticleName( "g_testParticleName", "", CVAR_GAME, "name of the particle being tested by the particle editor" ); +// RAVEN END +idCVar g_testModelRotate( "g_testModelRotate", "0", CVAR_GAME, "test model rotation speed" ); +idCVar g_testPostProcess( "g_testPostProcess", "", CVAR_GAME, "name of material to draw over screen" ); +idCVar g_testModelAnimate( "g_testModelAnimate", "0", CVAR_GAME | CVAR_INTEGER, "test model animation,\n" + "0 = cycle anim with origin reset\n" + "1 = cycle anim with fixed origin\n" + "2 = cycle anim with continuous origin\n" + "3 = frame by frame with continuous origin\n" + "4 = play anim once\n" + "5 = frame by frame with fixed origin", 0, 5, idCmdSystem::ArgCompletion_Integer<0,5> ); +idCVar g_testModelBlend( "g_testModelBlend", "0", CVAR_GAME | CVAR_INTEGER, "number of frames to blend" ); +idCVar g_testDeath( "g_testDeath", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// bdube: added scoreboard testing +idCVar g_testScoreboard( "g_testScoreboard", "0", CVAR_GAME | CVAR_INTEGER, "number of clients to test in the scoreboard gui" ); +idCVar g_testPlayer( "g_testPlayer", "", CVAR_GAME, "test player classname" ); +// RAVEN END +idCVar g_exportMask( "g_exportMask", "", CVAR_GAME, "" ); +idCVar g_flushSave( "g_flushSave", "0", CVAR_GAME | CVAR_BOOL, "1 = don't buffer file writing for save games." ); + +idCVar aas_test( "aas_test", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreas( "aas_showAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showAreaBounds( "aas_showAreaBounds", "0", CVAR_GAME | CVAR_INTEGER, "When show areas is on, this draws the bounds of the areas, too..." ); +idCVar aas_showPath( "aas_showPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showFlyPath( "aas_showFlyPath", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showWallEdges( "aas_showWallEdges", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_showHideArea( "aas_showHideArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_pullPlayer( "aas_pullPlayer", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_randomPullPlayer( "aas_randomPullPlayer", "0", CVAR_GAME | CVAR_BOOL, "" ); +idCVar aas_goalArea( "aas_goalArea", "0", CVAR_GAME | CVAR_INTEGER, "" ); +idCVar aas_showPushIntoArea( "aas_showPushIntoArea", "0", CVAR_GAME | CVAR_BOOL, "" ); +// RAVEN BEGIN +// rjohnson: added aas help +idCVar aas_showProblemAreas( "aas_showProblemAreas", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// cdr: added rev reach +idCVar aas_showRevReach( "aas_showRevReach", "0", CVAR_GAME | CVAR_INTEGER, "" ); +// RAVEN END + +idCVar g_password( "g_password", "", CVAR_GAME | PC_CVAR_ARCHIVE, "game password" ); +idCVar password( "password", "", CVAR_GAME | CVAR_NOCHEAT, "client password used when connecting" ); + +// RAVEN BEGIN +idCVar g_gameReviewPause( "g_gameReviewPause", "30", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_INTEGER | PC_CVAR_ARCHIVE, "scores review time in seconds (at end game)", 2, 3600 ); +// RAVEN END +idCVar net_clientPredictGUI( "net_clientPredictGUI", "1", CVAR_GAME | CVAR_BOOL, "test guis in networking without prediction" ); + +idCVar si_voteFlags( "si_voteFlags", "0", CVAR_GAME | CVAR_SERVERINFO | CVAR_INTEGER | PC_CVAR_ARCHIVE, "vote flags. bit mask of votes not allowed on this server\n" + "bit 0 (+1) restart now\n" + "bit 1 (+2) min players\n" + "bit 2 (+4) auto balance teams\n" + "bit 3 (+8) shuffle teams\n" + "bit 4 (+16) kick player\n" + "bit 5 (+32) change map\n" + "bit 6 (+64) change gametype\n" + "bit 7 (+128) time limit\n" + "bit 8 (+256) tourney limit\n" + "bit 9 (+512) capture limit\n" + "bit 10 (+1024) frag limit" ); + +idCVar g_mapCycle( "g_mapCycle", "mapcycle", CVAR_GAME | CVAR_ARCHIVE, "map cycling script for multiplayer games - see mapcycle.scriptcfg" ); + +// RAVEN BEGIN +// bdube: client entitiy cvars +idCVar g_gamelog( "g_gamelog", "0", CVAR_GAME | CVAR_BOOL, "enables game logging" ); +idCVar cl_showEntityInfo( "cl_showEntityInfo", "0", CVAR_GAME | CVAR_BOOL, "" ); +// ddynerman: announcer delay time +idCVar g_announcerDelay( "g_announcerDelay", "1000", CVAR_SOUND | PC_CVAR_ARCHIVE, "no more than one announcer sound will be played in this many ms" ); +// jnewquist: Option to force undying state +idCVar g_forceUndying( "g_forceUndying", "0", CVAR_GAME | CVAR_BOOL, "forces undying state" ); +// mcg: combat performance testing cvars +idCVar g_perfTest_weaponNoFX( "g_perfTest_weaponNoFX", "0", CVAR_GAME | CVAR_BOOL, "no muzzle flash, brass eject, muzzle fx, tracers, impact fx, blood decals or blood splats (whew!)" ); +idCVar g_perfTest_hitscanShort( "g_perfTest_hitscanShort", "0", CVAR_GAME | CVAR_BOOL, "all hitscans capped at 2048" ); +idCVar g_perfTest_hitscanBBox( "g_perfTest_hitscanBBox", "0", CVAR_GAME | CVAR_BOOL, "all hitscans vs bbox, not rendermodel" ); +idCVar g_perfTest_aiStationary( "g_perfTest_aiStationary", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no combat movement" ); +idCVar g_perfTest_aiNoDodge( "g_perfTest_aiNoDodge", "0", CVAR_GAME | CVAR_BOOL, "ai attempts no dodging" ); +idCVar g_perfTest_aiNoRagdoll( "g_perfTest_aiNoRagdoll", "0", CVAR_GAME | CVAR_BOOL, "ai does not ragdoll" ); +idCVar g_perfTest_aiNoObstacleAvoid( "g_perfTest_aiNoObstacleAvoid", "0", CVAR_GAME | CVAR_BOOL, "ai does not attempt obstacle avoidance" ); +idCVar g_perfTest_aiUndying( "g_perfTest_aiUndying", "0", CVAR_GAME | CVAR_BOOL, "makes all AI undying" ); +idCVar g_perfTest_aiNoVisTrace( "g_perfTest_aiNoVisTrace", "0", CVAR_GAME | CVAR_BOOL, "ai does no vis traces" ); +idCVar g_perfTest_noJointTransform( "g_perfTest_noJointTransform", "0", CVAR_GAME | CVAR_BOOL, "all joint transforms return origin" ); +idCVar g_perfTest_noPlayerFocus( "g_perfTest_noPlayerFocus", "0", CVAR_GAME | CVAR_BOOL, "doesn't do player focus traces/logic" ); +idCVar g_perfTest_noProjectiles( "g_perfTest_noProjectiles", "0", CVAR_GAME | CVAR_BOOL, "all projectiles are removed instantly" ); + +idCVar net_serverDownload( "net_serverDownload", "0", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "enable server download redirects. 0: off 1: client exits and opens si_serverURL in web browser 2: client downloads pak files from an URL and connects again 3: client downloads pak files from built-in http server and connects again. See net_serverDl* cvars for configuration" ); +idCVar net_serverDlBaseURL( "net_serverDlBaseURL", "", CVAR_GAME | CVAR_ARCHIVE, "base URL for the download redirection. also overrides the URL when net_serverDownload is set to 3 (built-in HTTP server)." ); +idCVar net_serverDlTable( "net_serverDlTable", "", CVAR_GAME | CVAR_ARCHIVE, "pak names for which download is provided, seperated by ; - use a * to mark all paks" ); + +idCVar si_serverURL( "si_serverURL", "", CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE, "server information page" ); + +idCVar g_repeaterReliableOnly( "g_repeaterReliableOnly", "1", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "send unreliable messages reliably. 1: to repeaters 2: to repeaters and viewers" ); + +idCVar net_warnStale( "net_warnStale", "1", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "Warn stale entity occurences on network client - == 1: only on ClientStale call, > 1 all times" ); + +idCVar pm_slidevelocity( "pm_slidevelocity", "1", CVAR_GAME | CVAR_BOOL | CVAR_NETWORKSYNC, "what to do with velocity when hitting a surface at an angle. 0: use horizontal speed, 1: keep some of the impact speed to push along the slide" ); +idCVar pm_powerslide( "pm_powerslide", "0.09", CVAR_GAME | CVAR_FLOAT | CVAR_NETWORKSYNC, "adjust the push when pm_slidevelocity == 1, set power < 1 -> more speed, > 1 -> closer to pm_slidevelocity 0", 0, 4 ); + +idCVar g_playerLean( "g_playerLean", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale down or disable client-side player lean" ); + +idCVar net_clientPredictWeaponSwitch( "net_clientPredictWeaponSwitch", "1", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "predict weapon switches locally (most noticeable on high ping servers)" ); + + +// RAVEN BEGIN +// bdube: cvar helps +static idCVarHelp help_g_showHud ( "g_showHud", "Show Player HUD", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showGun ( "g_showGun", "Show Player Weapon", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTargets ( "g_showTargets", "Show Targets", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showTriggers ( "g_showTriggers", "Show Triggers", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_showEntityInfo ( "g_showEntityInfo", "Show Entity Information", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_timeentities ( "g_timeentities", "Show Entity Times", "Off;0.5s;1.0s", "0;0.5;1.0", CVARHELP_GAME ); +static idCVarHelp help_g_showActiveEntities ( "g_showActiveEntities", "Show Active Entities", "Off;On", "0;1", CVARHELP_GAME ); +static idCVarHelp help_g_frametime ( "g_frametime", "Show Game Frame Times", "Off;On", "0;1", CVARHELP_GAME ); + +static idCVarHelp help_g_showCollisionWorld ( "g_showCollisionWorld", "Show Collision World", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionModels ( "g_showCollisionModels", "Show Collision Models", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_g_showCollisionTraces ( "g_showCollisionTraces", "Show Collision Traces", "Off;Info;Lines", "0;1;2", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showActive ( "rb_showActive", "Show Active Rigid Bodies", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_rb_showTimings ( "rb_showTimings", "Show Rigid Body Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); +static idCVarHelp help_af_showTimings ( "af_showTimings", "Show AF Timings", "Off;On", "0;1", CVARHELP_PHYSICS ); + +// nmckenzie: ai cvar helps +static idCVarHelp help_g_aas_showAreas( "aas_showAreas", "Show AAS areas", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showProblemAreas( "aas_showProblemAreas", "Show AAS areas with Problems", "Off;Single Current;Single All;Complete", "0;1;2;3", CVARHELP_AI ); +static idCVarHelp help_g_aas_showPath( "aas_showPath", "Show AAS Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showFlyPath( "aas_showFlyPath", "Show AAS Flying Paths", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showWallEdges( "aas_showWallEdges", "Show AAS Wall Edges", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_showHideArea( "aas_showHideArea", "Show AAS Hide Areas", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_aas_goalArea( "aas_goalArea", "Show AAS Goal Areas", "Off;On", "0;1", CVARHELP_AI ); + +static idCVarHelp help_g_ai_debugMove( "ai_debugMove", "Show Movement for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_debugTrajectory( "ai_debugTrajectory", "Show Grenade tests for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showCombatNodes( "ai_showCombatNodes", "Show attack cones for monsters", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showPaths( "ai_showPaths", "Show all path_* entities", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showObstacleAvoidance( "ai_showObstacleAvoidance", "Show obstacle avoidance", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_speeds( "ai_speeds", "Show performance load of AI", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_animShow( "ai_animShow", "List animations when used.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_showTacticalFeatures( "ai_showTacticalFeatures", "Show player view tactical features.", "Off;On", "0;1", CVARHELP_AI ); +static idCVarHelp help_g_ai_useRVMasterMove( "ai_useRVMasterMove", "Use new master move functions.", "Off;On", "0;1", CVARHELP_AI ); +// RAVEN END diff --git a/src/mpgame/gamesys/SysCvar.h b/src/mpgame/gamesys/SysCvar.h new file mode 100644 index 0000000..f3cc308 --- /dev/null +++ b/src/mpgame/gamesys/SysCvar.h @@ -0,0 +1,452 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_CVAR_H__ +#define __SYS_CVAR_H__ + +extern idCVar developer; + +extern idCVar g_cinematic; +extern idCVar g_cinematicMaxSkipTime; + +// RAVEN BEGIN +// jnewquist: vertical stretch for letterboxed cinematics authored for 4:3 aspect +extern idCVar g_fixedHorizFOV; +// RAVEN END + +extern idCVar g_monsters; +extern idCVar g_decals; +extern idCVar g_knockback; +extern idCVar g_skill; +extern idCVar g_gravity; +extern idCVar g_mp_gravity; +extern idCVar g_skipFX; +extern idCVar g_skipParticles; +extern idCVar g_projectileLights; +extern idCVar g_doubleVision; +extern idCVar g_muzzleFlash; + +extern idCVar g_nailTrail; +extern idCVar g_grenadeTrail; +extern idCVar g_rocketTrail; +extern idCVar g_railTrail; +extern idCVar g_napalmTrail; + +extern idCVar g_predictedProjectiles; + +extern idCVar g_disasm; +extern idCVar g_debugBounds; +extern idCVar g_debugAnim; +extern idCVar g_debugMove; +extern idCVar g_debugDamage; +extern idCVar g_debugWeapon; +extern idCVar g_debugScript; +extern idCVar g_debugMover; +extern idCVar g_debugTriggers; +extern idCVar g_debugCinematic; +// RAVEN BEGIN +// bdube: added +extern idCVar g_debugState; +extern idCVar g_stopTime; +extern idCVar g_armorProtection; +extern idCVar g_armorProtectionMP; +//extern idCVar g_damageScale; +// jsinger: added to support binary read/write +extern idCVar com_BinaryRead; +#ifdef RV_BINARYDECLS +extern idCVar com_BinaryDeclRead; +#endif +// jsinger: added to support loading all decls from a single file +#ifdef RV_SINGLE_DECL_FILE +extern idCVar com_SingleDeclFile; +extern idCVar com_WriteSingleDeclFile; +#endif +extern idCVar com_BinaryWrite; +// RAVEN END +extern idCVar g_useDynamicProtection; +extern idCVar g_healthTakeTime; +extern idCVar g_healthTakeAmt; +extern idCVar g_healthTakeLimit; + +extern idCVar g_showPVS; +extern idCVar g_showTargets; +extern idCVar g_showTriggers; +extern idCVar g_showCollisionWorld; +extern idCVar g_showCollisionModels; +extern idCVar g_showCollisionTraces; +// RAVEN BEGIN +// ddynerman: SD's clip sector code +extern idCVar g_showClipSectors; +extern idCVar g_showClipSectorFilter; +extern idCVar g_showAreaClipSectors; +// RAVEN END +extern idCVar g_maxShowDistance; +extern idCVar g_showEntityInfo; +extern idCVar g_showviewpos; +extern idCVar g_showcamerainfo; +extern idCVar g_showTestModelFrame; +extern idCVar g_showActiveEntities; +extern idCVar g_showEnemies; +extern idCVar g_frametime; +extern idCVar g_timeentities; + +// RAVEN BEGIN +// bdube: new debug cvar +extern idCVar g_debugVehicle; +extern idCVar g_showFrameCmds; +extern idCVar g_showGodDamage; +// RAVEN END + +// RAVEN BEGIN +// twhitaker: debug cvars for rvVehicleDriver +extern idCVar g_debugVehicleDriver; +extern idCVar g_debugVehicleAI; +extern idCVar g_vehicleMode; +// RAVEN END +extern idCVar g_allowVehicleGunOverheat; + +extern idCVar ai_debugScript; +extern idCVar ai_debugMove; +extern idCVar ai_debugTrajectory; +extern idCVar ai_debugTactical; +extern idCVar ai_debugFilterString; +extern idCVar ai_testPredictPath; +extern idCVar ai_showCombatNodes; +extern idCVar ai_showPaths; +extern idCVar ai_showObstacleAvoidance; +extern idCVar ai_blockedFailSafe; +extern idCVar ai_debugSquad; +extern idCVar ai_debugStealth; +extern idCVar ai_allowTacticalRush; + +// RAVEN BEGIN +// nmckenzie: added speeds and freeze +extern idCVar ai_speeds; +extern idCVar ai_freeze; +extern idCVar ai_animShow; +extern idCVar ai_showCover; +extern idCVar ai_showTacticalFeatures; +extern idCVar ai_disableEntTactical; +extern idCVar ai_disableAttacks; +extern idCVar ai_disableSimpleThink; +extern idCVar ai_disableCover; +extern idCVar ai_debugHelpers; +// cdr: added new master move type +extern idCVar ai_useRVMasterMove; +//jshepard: allow old AAS files +extern idCVar ai_allowOldAAS; +// twhitaker: debugging support for eye focus +extern idCVar ai_debugEyeFocus; +//mcg: always allow player to push buddies, unless scripted +extern idCVar ai_playerPushAlways; +// RAVEN END + +extern idCVar g_dvTime; +extern idCVar g_dvAmplitude; +extern idCVar g_dvFrequency; + +extern idCVar g_kickTime; +extern idCVar g_kickAmplitude; +extern idCVar g_blobTime; +extern idCVar g_blobSize; + +extern idCVar g_testHealthVision; +extern idCVar g_editEntityMode; +// RAVEN BEGIN +extern idCVar g_editEntityDistance; +// rhummer: Allow to customize the distance the text is drawn for edit entities, Zack request. +extern idCVar g_editEntityTextDistance; +// rjohnson: entity usage stats +extern idCVar g_keepEntityStats; +// RAVEN END +extern idCVar g_dragEntity; +extern idCVar g_dragDamping; +extern idCVar g_dragShowSelection; +extern idCVar g_dropItemRotation; + +extern idCVar g_vehicleVelocity; +extern idCVar g_vehicleForce; + +extern idCVar hud_showSpeed; +extern idCVar hud_showInput; +extern idCVar hud_inputPosition; +extern idCVar hud_inputColor; + +extern idCVar ik_enable; +extern idCVar ik_debug; + +extern idCVar af_useLinearTime; +extern idCVar af_useImpulseFriction; +extern idCVar af_useJointImpulseFriction; +extern idCVar af_useSymmetry; +extern idCVar af_skipSelfCollision; +extern idCVar af_skipLimits; +extern idCVar af_skipFriction; +extern idCVar af_forceFriction; +extern idCVar af_maxLinearVelocity; +extern idCVar af_maxAngularVelocity; +extern idCVar af_timeScale; +extern idCVar af_jointFrictionScale; +extern idCVar af_contactFrictionScale; +extern idCVar af_highlightBody; +extern idCVar af_highlightConstraint; +extern idCVar af_showTimings; +extern idCVar af_showConstraints; +extern idCVar af_showConstraintNames; +extern idCVar af_showConstrainedBodies; +extern idCVar af_showPrimaryOnly; +extern idCVar af_showTrees; +extern idCVar af_showLimits; +extern idCVar af_showBodies; +extern idCVar af_showBodyNames; +extern idCVar af_showMass; +extern idCVar af_showTotalMass; +extern idCVar af_showInertia; +extern idCVar af_showVelocity; +extern idCVar af_showActive; +extern idCVar af_testSolid; + +extern idCVar rb_showTimings; +extern idCVar rb_showBodies; +extern idCVar rb_showMass; +extern idCVar rb_showInertia; +extern idCVar rb_showVelocity; +extern idCVar rb_showActive; + +extern idCVar pm_jumpheight; +extern idCVar pm_stepsize; +extern idCVar pm_crouchspeed; +// RAVEN BEGIN +extern idCVar pm_speed; +extern idCVar pm_walkspeed; +extern idCVar pm_zoomedSlow; +extern idCVar pm_isZoomed; +// RAVEN END +extern idCVar pm_noclipspeed; +extern idCVar pm_spectatespeed; +extern idCVar pm_spectatebbox; +extern idCVar pm_usecylinder; +extern idCVar pm_minviewpitch; +extern idCVar pm_maxviewpitch; +extern idCVar pm_stamina; +extern idCVar pm_staminathreshold; +extern idCVar pm_staminarate; +extern idCVar pm_crouchheight; +extern idCVar pm_crouchviewheight; +extern idCVar pm_normalheight; +extern idCVar pm_normalviewheight; +extern idCVar pm_deadheight; +extern idCVar pm_deadviewheight; +extern idCVar pm_crouchrate; +extern idCVar pm_bboxwidth; +extern idCVar pm_crouchbob; +extern idCVar pm_walkbob; +extern idCVar pm_runbob; +extern idCVar pm_runpitch; +extern idCVar pm_runroll; +extern idCVar pm_bobup; +extern idCVar pm_bobpitch; +extern idCVar pm_bobroll; +extern idCVar pm_thirdPersonRange; +extern idCVar pm_thirdPersonHeight; +extern idCVar pm_thirdPersonAngle; +extern idCVar pm_thirdPersonClip; +extern idCVar pm_thirdPerson; +extern idCVar pm_thirdPersonDeath; +extern idCVar pm_modelView; +extern idCVar pm_airTics; + +// nmckenzie: added ability to try alternate accelerations. +extern idCVar pm_acceloverride; +extern idCVar pm_frictionoverride; +extern idCVar pm_forcespectatormove; +extern idCVar pm_thirdPersonTarget; +// bdube: vehicle +extern idCVar pm_vehicleLean; +extern idCVar pm_vehicleCameraSnap; +extern idCVar pm_vehicleCameraScaleMax; +extern idCVar pm_vehicleSoundLerpScale; +extern idCVar pm_vehicleCameraSpeedScale; +extern idCVar pm_vehicleCameraMinDist; +// RAVEN END + +extern idCVar g_showPlayerShadow; + +extern idCVar g_skipPlayerShadowsMP; +extern idCVar g_skipItemShadowsMP; + +extern idCVar g_simpleItems; +extern idCVar g_showHud; +// RAVEN BEGIN +extern idCVar g_crosshairColor; +// cnicholson: Custom Crosshair +extern idCVar g_crosshairCustom; +extern idCVar g_crosshairCustomFile; +extern idCVar g_crosshairCharInfoFar; +// bdube: hud popups +extern idCVar g_showHudPopups; +// bdube: range +extern idCVar g_showRange; +// bdube: debug hud +extern idCVar g_showDebugHud; +// RAVEN END +extern idCVar g_showProjectilePct; +// RAVEN BEGIN +// bdube: brass time +extern idCVar g_brassTime; +// RAVEN END +extern idCVar g_gun_x; +extern idCVar g_gun_y; +extern idCVar g_gun_z; +extern idCVar g_weaponFovEffect; +// RAVEN BEGIN +// bdube: cvar for messing with foreshortening +extern idCVar g_gun_pitch; +extern idCVar g_gun_yaw; +extern idCVar g_gun_roll; +// abahr: +extern idCVar g_gunViewStyle; +// jscott: for playbacks +extern idCVar g_showPlayback; +extern idCVar g_currentPlayback; +// RAVEN END +extern idCVar g_viewNodalX; +extern idCVar g_viewNodalZ; +extern idCVar g_fov; +extern idCVar g_testDeath; +extern idCVar g_skipViewEffects; +extern idCVar g_mpWeaponAngleScale; + +extern idCVar g_testParticle; +extern idCVar g_testParticleName; +// RAVEN BEGIN +// bdube: more rigid body debug +extern idCVar rb_showContacts; +// RAVEN END + +extern idCVar g_testPostProcess; + +extern idCVar g_testModelRotate; +extern idCVar g_testModelAnimate; +extern idCVar g_testModelBlend; + +extern idCVar g_forceModel; +extern idCVar g_forceStroggModel; +extern idCVar g_forceMarineModel; + +// RAVEN BEGIN +// bdube: test scoreboard +extern idCVar g_testScoreboard; +extern idCVar g_testPlayer; +// RAVEN END +extern idCVar g_exportMask; +extern idCVar g_flushSave; + +extern idCVar aas_test; +extern idCVar aas_showAreas; +extern idCVar aas_showAreaBounds; +extern idCVar aas_showPath; +extern idCVar aas_showFlyPath; +extern idCVar aas_showWallEdges; +extern idCVar aas_showHideArea; +extern idCVar aas_pullPlayer; +extern idCVar aas_randomPullPlayer; +extern idCVar aas_goalArea; +extern idCVar aas_showPushIntoArea; +// RAVEN BEGIN +// rjohnson: added aas help +extern idCVar aas_showProblemAreas; +// cdr: added rev reach +extern idCVar aas_showRevReach; +// RAVEN END + +extern idCVar net_clientPredictGUI; + +extern idCVar si_voteFlags; +extern idCVar g_mapCycle; +// RAVEN BEGIN +// shouchard: g_balanceTDM->g_balanceTeams so we can also use it for CTF +extern idCVar si_autobalance; +// RAVEN END + +// RITUAL BEGIN +// squirrel: Mode-agnostic buymenus +extern idCVar si_isBuyingEnabled; +extern idCVar si_dropWeaponsInBuyingModes; +extern idCVar si_controlTime; +// RITUAL END + +extern idCVar si_timeLimit; +extern idCVar si_fragLimit; +extern idCVar si_gameType; +extern idCVar si_map; +extern idCVar si_mapCycle; +extern idCVar si_spectators; +extern idCVar si_minPlayers; +// RAVEN BEGIN +// shouchard: CTF +extern idCVar si_captureLimit; +// shouchard: Tourney +extern idCVar si_tourneyLimit; +// RAVEN END + +extern const char *si_gameTypeArgs[]; + +extern idCVar g_gamelog; +extern idCVar cl_showEntityInfo; +extern idCVar g_forceUndying; +extern idCVar g_perfTest_weaponNoFX; +extern idCVar g_perfTest_hitscanShort; +extern idCVar g_perfTest_hitscanBBox; +extern idCVar g_perfTest_aiStationary; +extern idCVar g_perfTest_aiNoDodge; +extern idCVar g_perfTest_aiNoRagdoll; +extern idCVar g_perfTest_aiNoObstacleAvoid; +extern idCVar g_perfTest_aiUndying; +extern idCVar g_perfTest_aiNoVisTrace; +extern idCVar g_perfTest_noJointTransform; +extern idCVar g_perfTest_noPlayerFocus; +extern idCVar g_perfTest_noProjectiles; + +extern idCVar net_clientLagOMeter; +extern idCVar net_clientLagOMeterResolution; + +extern idCVar net_warnStale; + +extern idCVar ri_useViewerPass; +extern idCVar ri_privateViewers; +extern idCVar ri_numViewers; +extern idCVar ri_numPrivateViewers; +extern idCVar ri_name; + +extern idCVar g_noTVChat; + +extern idCVar pm_slidevelocity; +extern idCVar pm_powerslide; + +extern idCVar g_playerLean; + +extern idCVar net_clientPredictWeaponSwitch; + +#endif /* !__SYS_CVAR_H__ */ diff --git a/src/mpgame/mp/Buying.cpp b/src/mpgame/mp/Buying.cpp new file mode 100644 index 0000000..84ebcb5 --- /dev/null +++ b/src/mpgame/mp/Buying.cpp @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Buying.cpp +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.cpp and Player.cpp, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Buying.h" + + +riBuyingManager::riBuyingManager() : + _buyingGameBalanceConstants( NULL ), + opponentKillCashAward( 0 ), + opponentKillFragCount( -1 ) { } + +riBuyingManager::~riBuyingManager() { } + +int riBuyingManager::GetIntValueForKey( const char* keyName, int defaultValue ) { + if( !keyName ) + { + return defaultValue; + } + + if( !_buyingGameBalanceConstants ) + { + _buyingGameBalanceConstants = static_cast( declManager->FindType( DECL_ENTITYDEF, "BuyingGameBalanceConstants", false ) ); + + if( !_buyingGameBalanceConstants ) + { + return defaultValue; + } + } + + for( int i = 0; i < _buyingGameBalanceConstants->dict.GetNumKeyVals(); i++ ) + { + const idKeyValue* keyValuePair = _buyingGameBalanceConstants->dict.GetKeyVal( i ); + if( !keyValuePair->GetKey().Icmp( keyName ) ) + { + return atoi( keyValuePair->GetValue() ); + } + } + + return defaultValue; +} + +int riBuyingManager::GetOpponentKillCashAward( void ) { + int targetFragCount = gameLocal.serverInfo.GetInt( "si_fragLimit" ); + if ( opponentKillFragCount != targetFragCount ) { + opponentKillFragCount = targetFragCount; + if ( idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "DM" ) && idStr::Icmp( gameLocal.serverInfo.GetString( "si_gameType" ), "Team DM" ) ) { + // only do frag reward scaling in DM/TDM + opponentKillCashAward = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + } else { + targetFragCount = idMath::ClampInt( GetIntValueForKey( "killingOpponent_minFragAdjust", 10 ), GetIntValueForKey( "killingOpponent_maxFragAdjust",50 ), targetFragCount ); + int baseVal = GetIntValueForKey( "playerCashAward_killingOpponent", 600 ); + int fragTarget = GetIntValueForKey( "killingOpponent_bestFragCount", 25 ); + opponentKillCashAward = ( baseVal * fragTarget ) / targetFragCount; + } + } + return opponentKillCashAward; +} diff --git a/src/mpgame/mp/Buying.h b/src/mpgame/mp/Buying.h new file mode 100644 index 0000000..9434076 --- /dev/null +++ b/src/mpgame/mp/Buying.h @@ -0,0 +1,61 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Buying.h +// +// Copyright 2005 Ritual Entertainment +// +// This file essentially serves as an extension to the Game DLL +// source files Multiplayer.h and Player.h, in an attempt +// to isolate, as much as possible, these changes from the main +// body of code (for merge simplification, etc). +//---------------------------------------------------------------- + +#ifndef __BUYING_H__ +#define __BUYING_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + + +class riBuyingManager +{ +private: + const idDeclEntityDef* _buyingGameBalanceConstants; + int opponentKillCashAward; // latch + int opponentKillFragCount; + +public: + riBuyingManager(); + ~riBuyingManager(); + + int GetIntValueForKey( const char* keyName, int defaultValue ); + int GetOpponentKillCashAward( void ); + + void Reset( void ) { opponentKillFragCount = -1; } +}; + + +#endif // __BUYING_H__ diff --git a/src/mpgame/mp/CTF.cpp b/src/mpgame/mp/CTF.cpp new file mode 100644 index 0000000..bbdf3c6 --- /dev/null +++ b/src/mpgame/mp/CTF.cpp @@ -0,0 +1,332 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// CTF.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "CTF.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ +CLASS_DECLARATION( idEntity, rvCTF_AssaultPoint ) + EVENT( EV_PostSpawn, rvCTF_AssaultPoint::Event_InitializeLinks ) + EVENT( EV_Touch, rvCTF_AssaultPoint::Event_Touch ) +END_CLASS + +rvCTF_AssaultPoint::rvCTF_AssaultPoint() { + trigger = NULL; + linked = false; + owner = AS_NEUTRAL; +} + +rvCTF_AssaultPoint::~rvCTF_AssaultPoint() { + delete trigger; + trigger = NULL; +} + +/* +================ +rvCTF_AssaultPoint::Spawn +================ +*/ +void rvCTF_AssaultPoint::Spawn( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvCTF_AssaultPoint::InitializeLinks +================ +*/ +void rvCTF_AssaultPoint::Event_InitializeLinks( void ) { + if( linked ) { + return; + } + + // pull in our targets + toStrogg = gameLocal.FindEntity( spawnArgs.GetString( "targetStroggAP" ) ); + toMarine = gameLocal.FindEntity( spawnArgs.GetString( "targetMarineAP" ) ); + + ResetIndices(); + + trigger = new idClipModel( idTraceModel( idBounds( vec3_origin ).Expand( 16.0f ) ) ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + trigger->Link( this, 0, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); +// RAVEN END + trigger->SetContents ( CONTENTS_TRIGGER ); + + GetPhysics()->SetClipModel( NULL, 1.0f ); + + linked = true; +} + +/* +================ +rvCTF_AssaultPoint::ResetIndices +================ +*/ +void rvCTF_AssaultPoint::ResetIndices( void ) { + // find the closest AP to the marine flag, which is index 0, then work towards strogg flag + if ( !toMarine || !toMarine->IsType ( rvItemCTFFlag::GetClassType() ) ) { + return; + } + + idEntityPtr ptr; + ptr = this; + gameLocal.mpGame.assaultPoints.Append( ptr ); + int assignIndices = 0; + index = assignIndices++; + rvCTF_AssaultPoint* ap = this; + while( !ap->toStrogg->IsType ( rvItemCTFFlag::GetClassType() ) ) { + if( !ap->toStrogg->IsType ( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - non assault point targeted in assault point chain" ); + } + ap = static_cast(ap->toStrogg.GetEntity()); + ap->index = assignIndices++; + + ptr = ap; + gameLocal.mpGame.assaultPoints.Append( ptr ); + + if ( !ap->linked ) { + ap->Event_InitializeLinks(); + } + + if( !ap->toStrogg ) { + gameLocal.Error( "rvCTF_AssaultPoint::ResetIndices() - break in assault point chain" ); + } + } +} + +/* +================ +rvCTF_AssaultPoint::Event_Activate +================ +*/ +void rvCTF_AssaultPoint::Event_Touch( idEntity *activator, trace_t *trace ) { + if( !activator->IsType( idPlayer::GetClassType() ) || ((gameLocal.mpGame.GetGameState())->GetMPGameState() != GAMEON && !cvarSystem->GetCVarBool( "g_testCTF" )) ) { + return; + } + + idPlayer* player = static_cast(activator); + int oldOwner = owner; + + if ( owner == player->team ) + return; + + int enemyPowerup = -1; + int friendlyPowerup = -1; + + if ( owner == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } else if ( owner == TEAM_STROGG ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + // neutral assault point + if ( player->team == TEAM_MARINE ) { + friendlyPowerup = POWERUP_CTF_MARINEFLAG; + enemyPowerup = POWERUP_CTF_STROGGFLAG; + } else { + friendlyPowerup = POWERUP_CTF_STROGGFLAG; + enemyPowerup = POWERUP_CTF_MARINEFLAG; + } + } + + if ( !player->PowerUpActive ( enemyPowerup ) ) { + return; + } + + + switch( player->team ) { + case TEAM_MARINE: { + if( !toMarine || owner == TEAM_MARINE ) { + break; + } + + if( toMarine->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } else if( toMarine->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toMarine.GetEntity())->owner == TEAM_MARINE ) { + owner = TEAM_MARINE; + gameLocal.Printf("Assault point %s captured by marines!\n", name.c_str()); + } + } + break; + } + case TEAM_STROGG: { + if( !toStrogg || owner == TEAM_STROGG ) { + break; + } + + if( toStrogg->IsType( rvItemCTFFlag::GetClassType() ) ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } else if( toStrogg->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + if( static_cast(toStrogg.GetEntity())->owner == TEAM_STROGG ) { + owner = TEAM_STROGG; + gameLocal.Printf("Assault point %s captured by strogg!\n", name.c_str()); + } + } + break; + } + } + + + if( oldOwner != owner ) { + // we switched hands, reset forward spawns + gameLocal.ClearForwardSpawns(); + + if( oldOwner == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( oldOwner ); + } + } else if( oldOwner == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( oldOwner ); + } + } + + rvItemCTFFlag::ResetFlag ( enemyPowerup ); + + gameLocal.mpGame.AddPlayerTeamScore( player, 2 ); + + ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->SetAPOwner( index, owner ); + + ActivateTargets( this ); + + SetOwnerColor (); + } +} + +/* +================ +rvCTF_AssaultPoint::ResetSpawns +================ +*/ +void rvCTF_AssaultPoint::ResetSpawns( int team ) { + // check to see if team can spawn at me, if not pass down + if( owner == team ) { + ActivateTargets( this ); + } else { + if( team == TEAM_MARINE ) { + if( static_cast(toMarine.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toMarine.GetEntity())->ResetSpawns( team ); + } + } else if( team == TEAM_STROGG ) { + if( static_cast(toStrogg.GetEntity())->IsType( rvCTF_AssaultPoint::Type ) ) { + static_cast(toStrogg.GetEntity())->ResetSpawns( team ); + } + } + } +} + +void rvCTF_AssaultPoint::SetOwner ( int newOwner ) { + if ( !gameLocal.isClient ) { + // server should set owner via EVENT_ACTIVATE, not here + return; + } + + owner = newOwner; + SetOwnerColor (); +} + +void rvCTF_AssaultPoint::Reset ( void ) { + owner = AS_NEUTRAL; + SetOwnerColor(); +} + +/* +================ +rvCTF_AssaultPoint::ChangeColor +================ +*/ +void rvCTF_AssaultPoint::SetOwnerColor ( void ) { + const idDeclSkin* skin = NULL; + if( owner == TEAM_MARINE ) { + skin = declManager->FindSkin( "skins/ddynerman/green_glow", false ); + } else if ( owner == TEAM_STROGG ) { + skin = declManager->FindSkin( "skins/ddynerman/orange_glow", false ); + } else { + skin = declManager->FindSkin( "skins/ddynerman/white", false ); + } + + if ( skin ) { + SetSkin( skin ); + } +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ + +CLASS_DECLARATION( idPlayerStart, rvCTFAssaultPlayerStart ) + EVENT( EV_Activate, rvCTFAssaultPlayerStart::Event_Activate ) +END_CLASS + +/* +================ +rvCTFAssaultPlayerStart::Spawn +================ +*/ +void rvCTFAssaultPlayerStart::Spawn(void) { + if( !idStr::Icmp( spawnArgs.GetString( "team" ), "marine" ) ) { + team = TEAM_MARINE; + } else if( !idStr::Icmp( spawnArgs.GetString( "team" ), "strogg" ) ) { + team = TEAM_STROGG; + } else { + gameLocal.Error("rvCTFAssaultPlayerStart::Spawn() - unknown team\n"); + team = -1; + } +} + +void rvCTFAssaultPlayerStart::Event_Activate( idEntity *activator ) { + if ( !activator->IsType( rvCTF_AssaultPoint::GetClassType() ) ) { + gameLocal.Warning( "rvCTFAssaultPlayerStart::Event_Activate() - was activated by something other than an assault point\n" ); + return; + } + + rvCTF_AssaultPoint* ap = static_cast(activator); + + if( ap->GetOwner() == team ) { + gameLocal.UpdateForwardSpawns( this, team ); + } +} diff --git a/src/mpgame/mp/CTF.h b/src/mpgame/mp/CTF.h new file mode 100644 index 0000000..af82495 --- /dev/null +++ b/src/mpgame/mp/CTF.h @@ -0,0 +1,108 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// CTF.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __CTF_H__ +#define __CTF_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" + +/* +=============================================================================== + +rvCTF_AssaultPoint + +=============================================================================== +*/ + +class rvCTF_AssaultPoint : public idEntity { +public: + CLASS_PROTOTYPE( rvCTF_AssaultPoint ); + void Spawn( void ); + + rvCTF_AssaultPoint(); + ~rvCTF_AssaultPoint(); + + int GetOwner( void ); + int GetIndex( void ); + + void SetOwnerColor ( void ); + void SetOwner ( int newOwner ); + void Reset ( void ); + +private: + void Event_Touch( idEntity *activator, trace_t *trace ); + void Event_InitializeLinks( void ); + void ResetSpawns( int team ); + + void ResetIndices( void ); + + // these could be maintained as lists to allow multiple AP paths + // the assault point one step closer to the Strogg base + idEntityPtr toStrogg; + // the assault point one step closer to the Marine base + idEntityPtr toMarine; + + // who currently owns this assault point + int owner; + int index; + bool linked; + + idClipModel* trigger; +}; + +ID_INLINE int rvCTF_AssaultPoint::GetOwner( void ) { + return owner; +} + +ID_INLINE int rvCTF_AssaultPoint::GetIndex( void ) { + return index; +} + +/* +=============================================================================== + +rvCTFAssaultPlayerStart + +=============================================================================== +*/ +class rvCTFAssaultPlayerStart : public idPlayerStart { +public: + CLASS_PROTOTYPE( rvCTFAssaultPlayerStart ); + + void Spawn( void ); + + int GetTeam( void ); +private: + void Event_Activate( idEntity *activator ); + int team; +}; + +#endif diff --git a/src/mpgame/mp/GameState.cpp b/src/mpgame/mp/GameState.cpp new file mode 100644 index 0000000..a7ef124 --- /dev/null +++ b/src/mpgame/mp/GameState.cpp @@ -0,0 +1,3157 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// GameState.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "GameState.h" + +/* +=============================================================================== + +rvGameState + +Game state info for deathmatch, team deathmatch + +=============================================================================== +*/ + +/* +================ +rvGameState::rvGameState +================ +*/ +rvGameState::rvGameState( bool allocPrevious ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvGameState::~rvGameState +================ +*/ +rvGameState::~rvGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +rvGameState::Clear +================ +*/ +void rvGameState::Clear( void ) { + currentState = INACTIVE; + nextState = INACTIVE; + nextStateTime = 0; + fragLimitTimeout = 0; +} + +/* +================ +rvGameState::SendState +================ +*/ +void rvGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious ); + + if( clientNum == -1 && (*this) == (*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvGameState::WriteState +=============== +*/ +void rvGameState::WriteState( idBitMsg &msg ) { + PackState( msg ); +} + +/* +================ +rvGameState::SendInitialState +================ +*/ +void rvGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + rvGameState* previousState = previousGameState; + + rvGameState invalidState; + + previousGameState = &invalidState; + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvGameState::ReceiveState +================ +*/ +void rvGameState::ReceiveState( const idBitMsg& msg ) { + if ( !BaseUnpackState( msg ) ) { + return; + } + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (*previousGameState) = (*this); +} + +/* +================ +rvGameState::PackState +================ +*/ +void rvGameState::PackState( idBitMsg& outMsg ) { + // server and client changing their rvGameState subclass by guesswork + // you can't rely on it being in sync at all times, so read and verify the type first + outMsg.WriteByte( gameLocal.gameType ); + + // for now, we only transmit 3 bytes. If we need to sync more data, we should + // only transmit the diff + outMsg.WriteByte( currentState ); + outMsg.WriteByte( nextState ); + outMsg.WriteLong( nextStateTime ); +} + +/* +================ +rvGameState::BaseUnpackState +================ +*/ +bool rvGameState::BaseUnpackState( const idBitMsg& inMsg ) { + gameType_t t = (gameType_t)inMsg.ReadByte(); + if ( t != gameLocal.gameType ) { + common->Warning( "rvGameState::UnpackState: client gametype (%d) is out of sync with server (%d). Ignoring", gameLocal.gameType, t ); + return false; + } + + currentState = (mpGameState_t)inMsg.ReadByte(); + nextState = (mpGameState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); + return true; +} + +/* +================ +rvGameState::GameStateChanged +================ +*/ +void rvGameState::GameStateChanged( void ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.IsServerDemoPlaying() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + // Check for a currentState change + if( currentState != previousGameState->currentState ) { + if( currentState == WARMUP ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld( true ); + + // reset stats on the client-side + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == COUNTDOWN ) { + if( gameLocal.gameType != GAME_TOURNEY ) { + player->GUIMainNotice( common->GetLocalizedString( "#str_107706" ), true ); + } + soundSystem->SetActiveSoundWorld(true); + if( gameLocal.gameType != GAME_TOURNEY && previousGameState->currentState != INACTIVE ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, nextStateTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, nextStateTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, nextStateTime - 1000 ); + } + } else if( currentState == GAMEON ) { + if ( !player->vsMsgState ) { + player->GUIMainNotice( "" ); + player->GUIFragNotice( "" ); + } else { + player->vsMsgState = false; + } + if( gameLocal.gameType != GAME_TOURNEY ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + } + //if ( gameLocal.gameType == GAME_DEADZONE ) { + // if ( player->team == TEAM_MARINE ) + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_MARINE, gameLocal.time ); + // else + // gameLocal.mpGame.ScheduleAnnouncerSound( AS_TEAM_JOIN_STROGG, gameLocal.time ); + //} + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + soundSystem->SetActiveSoundWorld( true ); + + // clear stats on client + if( gameLocal.isClient ) { + statManager->Init(); + } + } else if( currentState == SUDDENDEATH ) { + soundSystem->SetActiveSoundWorld( true ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } else if( currentState == GAMEREVIEW ) { + // RITUAL BEGIN + gameLocal.mpGame.isBuyingAllowedRightNow = false; + // RITUAL END + gameLocal.mpGame.ShowStatSummary(); + } + + gameLocal.mpGame.ScheduleTimeAnnouncements( ); + } +} + +/* +================ +rvGameState::SpawnDeadZonePowerup +================ +*/ +void rvGameState::SpawnDeadZonePowerup( void ) { + idEntity *ent; + riDeadZonePowerup* spawnSpot = 0; + int count = 0; + for ( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + // If its not a DeadZone powerup then skip it + if ( !ent->IsType( riDeadZonePowerup::GetClassType() ) ) { + continue; + } + + // Make sure its the right type first + riDeadZonePowerup* flag; + flag = static_cast(ent); + if ( flag->powerup != POWERUP_DEADZONE || flag->IsVisible() ) { + continue; + } + + if ( flag->spawnArgs.GetBool("dropped", "0") && !flag->IsVisible() ) { + flag->PostEventMS( &EV_Remove, 0 ); + + } else { + count++; + if ( !(rand()%(count)) ) { + spawnSpot = flag; + } + } + } + if ( spawnSpot ) { + spawnSpot->PostEventMS( &EV_RespawnItem, 0 ); + spawnSpot->srvReady = 1; // Go ahead and set this, so the loop works properly. + } else { + gameLocal.Error("Couldn't find enough dead zone spawn spots for the number of dead zone artifacts specified in the map def!"); + } +} + +/* +================ +rvGameState::Run +================ +*/ +void rvGameState::Run( void ) { + if ( currentState == INACTIVE ) { + +#ifdef _XENON + if(Live()->RoundsPlayed() < cvarSystem->GetCVarInteger("si_matchRounds")) +#endif + { + NewState( WARMUP ); + } + } + + if ( nextState != INACTIVE && gameLocal.time > nextStateTime ) { + NewState( nextState ); + nextState = INACTIVE; + } + + switch( currentState ) { + case INACTIVE: + break; + + case GAMEREVIEW: { + if ( nextState == INACTIVE ) { + + statManager->SendAllStats(); + + nextState = NEXTGAME; + + // allow a little extra time in tourney since we have to display end brackets + if( gameLocal.gameType == GAME_TOURNEY ) { + nextStateTime = gameLocal.time + 5000 + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } else { + nextStateTime = gameLocal.time + (1000 * cvarSystem->GetCVarInteger( "g_gameReviewPause" )); + } + } + break; + } + case NEXTGAME: { + + // the core will force a restart at 12 hours max + // but it's nicer if we can wait for a game transition to perform the restart so the game is not interrupted + // perform a restart once we are past 8 hours + if ( networkSystem->ServerGetServerTime() > 8*60*60*1000 ) { + gameLocal.sessionCommand = "nextMap"; + return; + } + + if ( nextState == INACTIVE ) { + // game rotation, new map, gametype etc. + // only cycle in tourney if tourneylimit is higher than the specified value + if( gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)this)->GetTourneyCount() >= gameLocal.serverInfo.GetInt( "si_tourneyLimit" ) ) { + // whether we switch to the next map or not, reset the tourney count + if( gameLocal.gameType == GAME_TOURNEY ) { + ((rvTourneyGameState*)this)->SetTourneyCount( 0 ); + } + + + if ( gameLocal.NextMap() ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "serverMapRestart\n" ); + return; + } + } + + NewState( WARMUP ); + + // put everyone back in from endgame spectate + for ( int i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( ent && ent->IsType( idPlayer::GetClassType() ) ) { + if ( !static_cast< idPlayer * >( ent )->wantSpectate ) { + gameLocal.mpGame.CheckRespawns( static_cast( ent ) ); + } + } + } + } + break; + } + case WARMUP: { + // check to see if we actually want to do a warmup, or if we fall through + +//RAVEN BEGIN +//asalmon: Live has its own rules for ending warm up +#ifdef _XENON + if(!Live()->RollCall()) + { + break; + } + +#endif +//RAVEN END + if( !gameLocal.serverInfo.GetBool( "si_warmup" ) && gameLocal.gameType != GAME_TOURNEY ) { + // tourney always needs a warmup, to ensure that at least 2 players get seeded for the tournament. + NewState( GAMEON ); + } else if ( gameLocal.mpGame.AllPlayersReady() ) { + NewState( COUNTDOWN ); + nextState = GAMEON; + nextStateTime = gameLocal.time + 1000 * gameLocal.serverInfo.GetInt( "si_countDown" ); + } + break; + } + case COUNTDOWN: { + break; + } + } +} + +/* +================ +rvGameState::NewState +================ +*/ +void rvGameState::NewState( mpGameState_t newState ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + int i; + + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + // asalmon: start the stat manager as soon as the game starts + statManager->Init(); + statManager->BeginGame(); + + // if shuffle is on, shuffle the teams around + if( gameLocal.IsTeamGame() && gameLocal.serverInfo.GetBool( "si_shuffle" ) ) { + gameLocal.mpGame.ShuffleTeams(); + } + + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( false ); + + //asalmon: clear out lingering team scores. + gameLocal.mpGame.ClearTeamScores(); + + if( gameLocal.gameType != GAME_TOURNEY ) { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + ((idPlayer*)ent)->JoinInstance( 0 ); + } + } + + break; + } + case GAMEON: { + // allow damage in warmup + //gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart(); +// RITUAL BEGIN +// squirrel: Buying & Deadzone + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + // If the buy menu is up during a server restart, + // make sure to refresh it. + gameLocal.mpGame.RedrawLocalBuyMenu(); + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) + gameLocal.mpGame.deadZonePowerupCount = mapDict->GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iInit(); + statManager->BeginGame(); + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART ); + outMsg.WriteBits( 0, 1 ); + networkSystem->ServerSendReliableMessage( -1, outMsg ); + + gameLocal.mpGame.SetMatchStartedTime( gameLocal.time ); + + fragLimitTimeout = 0; + + // write server initial reliable messages to give everyone new base + for( i = 0; i < MAX_CLIENTS; i++ ) { + // dont send this to server - we have all the data already and this will + // just trigger extra gamestate detection + if ( gameLocal.entities[ i ] && i != gameLocal.localClientNum ) { + gameLocal.mpGame.ServerWriteInitialReliableMessages( serverReliableSender.To( i, true ), i ); + } + } + + if ( gameLocal.isRepeater ) { + gameLocal.mpGame.ServerWriteInitialReliableMessages( repeaterReliableSender.To( -1 ), ENTITYNUM_NONE ); + } + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + gameLocal.mpGame.SetPlayerScore( p, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( p, 0 ); + + // in normal gameplay modes, spawn the player in. For tourney, the tourney manager handles spawning + if( gameLocal.gameType != GAME_TOURNEY ) { + p->JoinInstance( 0 ); + // in team games, let UserInfoChanged() spawn people on the right teams + if( !gameLocal.IsTeamGame() || p->team != -1 ) { + p->ServerSpectate( static_cast(ent)->wantSpectate ); + } + } + } + + gameLocal.mpGame.ClearTeamScores(); + + cvarSystem->SetCVarString( "ui_ready", "Not Ready" ); + gameLocal.mpGame.switchThrottle[ 1 ] = 0; // passby the throttle + break; + } + case GAMEREVIEW: { + statManager->EndGame(); + + //statManager->DebugPrint(); + nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change + + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + // RAVEN BEGIN + // jnewquist: Use accessor for static class type + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } +// RITUAL BEGIN +// squirrel: support for Buying in multiplayer + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + player->forcedReady = false; + player->ServerSpectate( true ); + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); +// RITUAL END + } + break; + } + case SUDDENDEATH: { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_SUDDENDEATH ); + //unmark all leaders, so we make sure we only let the proper people respawn + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer *p = static_cast( ent ); + p->SetLeader( false ); // don't carry the flag from previous games + } + + // only restart in team games if si_suddenDeathRestart is set. + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) { + gameLocal.LocalMapRestart(); + } + + // Mark everyone tied for the lead as leaders + i = 0; + idPlayer* leader = gameLocal.mpGame.GetRankedPlayer( i ); + if( leader ) { + int highScore = gameLocal.mpGame.GetScore( leader ); + while( leader ) { + if( gameLocal.mpGame.GetScore( leader ) < highScore ) { + break; + } + leader->SetLeader( true ); + leader = gameLocal.mpGame.GetRankedPlayer( ++i ); + } + } + +// RITUAL BEGIN +// squirrel: Buying & Deadzone + /// Reset players' cash and inventory if si_suddenDeathRestart is set + if( !gameLocal.IsTeamGame() || gameLocal.serverInfo.GetBool( "si_suddenDeathRestart" ) ) + { + for( i = 0; i < gameLocal.numClients; i++ ) { + idEntity *ent = gameLocal.entities[ i ]; + if ( !ent || !ent->IsType( idPlayer::GetClassType() ) ) { + continue; + } + idPlayer* player = static_cast< idPlayer* >(ent); + player->inventory.carryOverWeapons = 0; + player->ResetCash(); + } + } + + if ( gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + { + gameLocal.mpGame.isBuyingAllowedRightNow = true; + + // Give all the clients full ammo since this is the start of the round. + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + if( p == NULL || !p->IsType( idPlayer::GetClassType() ) ) + continue; + + GiveStuffToPlayer(p, "ammo", ""); + p->inventory.weapons |= p->inventory.carryOverWeapons & CARRYOVER_WEAPONS_MASK; + } + } + + if ( gameLocal.gameType == GAME_DEADZONE ) { + // Spawn the powerups! + const char *mapName = gameLocal.serverInfo.GetString( "si_map" ); + const idDict *mapDict = fileSystem->GetMapDecl( mapName ); + if ( mapDict ) + gameLocal.mpGame.deadZonePowerupCount = mapDict->GetInt("deadZonePowerupCount", "3"); + else + gameLocal.mpGame.deadZonePowerupCount = 3; + + int pcount = gameLocal.mpGame.deadZonePowerupCount; + if ( pcount == -1 ) + pcount = 3; // Good default. + + pcount = idMath::ClampInt(1, 12, pcount); + for ( int i = 0; iIsType( idPlayer::GetClassType() ) ) { + // RAVEN END + continue; + } + if ( static_cast< idPlayer *>( ent )->IsLeader() ) { + static_cast(ent)->ServerSpectate( false ); + continue; + } + static_cast< idPlayer *>( ent )->forcedReady = false; + static_cast(ent)->ServerSpectate( true ); + } + } + + break; + } + default: { + break; + } + } + + currentState = newState; +} + +/* +================ +rvGameState::ClientDisconnect +================ +*/ +void rvGameState::ClientDisconnect( idPlayer* player ) { + return; +} + +/* +================ +rvGameState::Spectate +================ +*/ +void rvGameState::Spectate( idPlayer* player ) { + if( player->spectating && player->wantSpectate ) { + gameLocal.mpGame.ClearFrags( player->entityNumber ); + } + return; +} + +/* +================ +rvGameState::operator== +================ +*/ +bool rvGameState::operator==( const rvGameState& rhs ) const { + return ( + ( currentState == rhs.currentState ) && + ( nextState == rhs.nextState ) && + ( nextStateTime == rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator!= +================ +*/ +bool rvGameState::operator!=( const rvGameState& rhs ) const { + return ( + ( currentState != rhs.currentState ) || + ( nextState != rhs.nextState ) || + ( nextStateTime != rhs.nextStateTime ) + ); +} + +/* +================ +rvGameState::operator= +================ +*/ +rvGameState& rvGameState::operator=( const rvGameState& rhs ) { + currentState = rhs.currentState; + nextState = rhs.nextState; + nextStateTime = rhs.nextStateTime; + return (*this); +} + +/* +=============== +rvGameState::WriteNetworkInfo +=============== +*/ +void rvGameState::WriteNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.BeginWriting(); + WriteState( msg ); + file->WriteInt( msg.GetSize() ); + file->Write( msg.GetData(), msg.GetSize() ); +} + +/* +=============== +rvGameState::ReadNetworkInfo +=============== +*/ +void rvGameState::ReadNetworkInfo( idFile *file, int clientNum ) { + idBitMsg msg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + int size; + + file->ReadInt( size ); + msg.Init( msgBuf, size ); + msg.SetSize( size ); + file->Read( msg.GetData(), size ); + ReceiveState( msg ); +} + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +rvDMGameState::rvDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvDMGameState::Run( void ) { + idPlayer* player = NULL; + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + player = gameLocal.mpGame.FragLimitHit(); + + bool tiedForFirst = false; + idPlayer* first = gameLocal.mpGame.GetRankedPlayer( 0 ); + idPlayer* second = gameLocal.mpGame.GetRankedPlayer( 1 ); + + if( player == NULL ) { + if( first && second && gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second ) ) { + tiedForFirst = true; + } + } + + if ( player ) { + // delay between detecting frag limit and ending game. let the death anims play + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( first && second && (gameLocal.mpGame.GetScore( first ) == gameLocal.mpGame.GetScore( second )) ) { + //this is a tie... + if( gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it must be tied at fraglimit, so sudden death. + NewState( SUDDENDEATH ); + } + } + //otherwise, just keep playing as normal. + fragLimitTimeout = 0; + + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetScore( first ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + // check for the rare case that two players both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + break; + } + + case SUDDENDEATH: { + player = gameLocal.mpGame.FragLeader(); + + if ( player ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + + } +} + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +rvTeamDMGameState::rvTeamDMGameState( bool allocPrevious ) : rvGameState( allocPrevious ) { + trackPrevious = allocPrevious; +} + +void rvTeamDMGameState::Run( void ) { + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_fragLimit" ) <= 0 ) { + // no fraglimit + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter FragLimit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // + // jshepard: OR it means that the winner killed himself during the fraglimit delay, and the + // game needs to roll on. + if( tiedForFirst ) { + //this is a tie + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) { + //and it's tied at the fraglimit. + NewState( SUDDENDEATH ); + } + //not a tie, game on. + fragLimitTimeout = 0; + } + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +/* +================ +rvCTFGameState::rvCTFGameState +================ +*/ +rvCTFGameState::rvCTFGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvCTFGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; +} + +/* +================ +rvCTFGameState::Clear +================ +*/ +void rvCTFGameState::Clear( void ) { + rvGameState::Clear(); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ].state = FS_AT_BASE; + flagStatus[ i ].clientNum = -1; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = AS_NEUTRAL; + } +} + +/* +================ +rvCTFGameState::SendState +================ +*/ +void rvCTFGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && IsType( rvCTFGameState::GetClassType() ) ); + + if( clientNum == -1 && (rvCTFGameState&)(*this) == (rvCTFGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvCTFGameState::WriteState +=============== +*/ +void rvCTFGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add CTF info + PackState( msg ); +} + +/* +================ +rvCTFGameState::ReceiveState +================ +*/ +void rvCTFGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + if ( !rvGameState::BaseUnpackState( msg ) ) { + return; + } + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvCTFGameState&)(*previousGameState) = (rvCTFGameState&)(*this); +} + +/* +================ +rvCTFGameState::PackState +================ +*/ +void rvCTFGameState::PackState( idBitMsg& outMsg ) { + // use indexing to pack in info + int index = 0; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( flagStatus[ i ].state ); + outMsg.WriteByte( flagStatus[ i ].clientNum ); + } + index++; + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != ((rvCTFGameState*)previousGameState)->apState[ i ] ) { + outMsg.WriteByte( index ); + outMsg.WriteByte( apState[ i ] ); + } + index++; + } +} + +/* +================ +rvCTFGameState::UnpackState +================ +*/ +void rvCTFGameState::UnpackState( const idBitMsg& inMsg ) { + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + if( index >= 0 && index < TEAM_MAX ) { + flagStatus[ index ].state = (flagState_t)inMsg.ReadByte(); + flagStatus[ index ].clientNum = inMsg.ReadByte(); + } else if( index >= TEAM_MAX && index < ( TEAM_MAX + MAX_AP ) ) { + apState[ index - TEAM_MAX ] = (apState_t)inMsg.ReadByte(); + } else { + gameLocal.Error( "rvCTFGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + } +} + +/* +================ +rvCTFGameState::SendInitialState +================ +*/ +void rvCTFGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + assert( IsType( rvCTFGameState::GetClassType() ) ); + + rvCTFGameState* previousState = (rvCTFGameState*)previousGameState; + + rvCTFGameState invalidState; + + previousGameState = &invalidState; + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + +/* +================ +rvCTFGameState::GameStateChanged +================ +*/ +void rvCTFGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // CTF specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + if ( gameLocal.IsServerDemoPlaying() && gameLocal.GetDemoFollowClient() >= 0 ) { + player = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.GetDemoFollowClient() ] ); + } + } + if ( !player ) { + gameLocal.Warning( "rvCTFGameState::GameStateChanged() - NULL local player\n" ) ; + return; + } + + bool noSounds = false; + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] == ((rvCTFGameState*)previousGameState)->flagStatus[ i ] ) { + continue; + } + + // don't play flag messages when flag state changes as a result of the gamestate changing + if( currentState != ((rvCTFGameState*)previousGameState)->currentState && ((rvCTFGameState*)previousGameState)->currentState != INACTIVE ) { + continue; + } + + if ( ((rvCTFGameState*)previousGameState)->currentState == INACTIVE ) { + noSounds = true; + } + + // flagTeam - used to tell the HUD which flag to update + int flagTeam = i; + + // in one flag CTF flagTeam is set to whichever team has the flag + if( gameLocal.gameType == GAME_1F_CTF || gameLocal.gameType == GAME_ARENA_1F_CTF ) { + if( flagStatus[ i ].state == FS_TAKEN_MARINE ) { + flagTeam = TEAM_MARINE; + } else if( flagStatus[ i ].state == FS_TAKEN_STROGG ) { + flagTeam = TEAM_STROGG; + } + } + + if( flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + if ( !player->spectating ) { + if( flagTeam == player->team ) { + // our flag was dropped, so the enemy dropped it + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_DROPS_FLAG, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_DROPS_FLAG, gameLocal.time, -1, true ); + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagDrop" ); + + if ( !player->spectating ) { + if ( flagTeam == player->team ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107723" ) ); + } else { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104420" ) ); + } + + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else if( flagStatus[ i ].state == FS_AT_BASE ) { + if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_TAKEN && !noSounds ) { + // team scores + if ( !player->spectating ) { + if( flagTeam == player->team ) { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_ENEMY_SCORES, gameLocal.time ); + } + } else { + if( gameLocal.mpGame.CanCapture( gameLocal.mpGame.OpposingTeam( flagTeam ) ) ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_TEAM_YOU_SCORE, gameLocal.time ); + } + } + } + } else if( ((rvCTFGameState*)previousGameState)->flagStatus[ i ].state == FS_DROPPED && !noSounds ) { + // flag returned + if ( !player->spectating ) { + if( flagTeam == player->team ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_FLAG_RETURNED, gameLocal.time, -1, true ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_RETURNS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent( "flagReturn" ); + } + } else if( flagStatus[ i ].state == FS_TAKEN || flagStatus[ i ].state == FS_TAKEN_STROGG || flagStatus[ i ].state == FS_TAKEN_MARINE ) { + // flag taken + if( flagTeam == player->team ) { + if ( !player->spectating ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_ENEMY_HAS_FLAG, gameLocal.time, -1, true ); + } + } + + if ( !player->spectating ) { + if ( player->mphud ) { + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_107722" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } + } else { + if ( flagStatus[ i ].clientNum == gameLocal.localClientNum ) { + if ( !noSounds ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOU_HAVE_FLAG, gameLocal.time, -1, true ); + } + + if ( !player->spectating ) { + // shouchard: inform the GUI that you've taken the flag + player->mphud->SetStateString( "main_notice_text", common->GetLocalizedString( "#str_104419" ) ); + player->mphud->HandleNamedEvent( "main_notice" ); + } + } else if ( !noSounds ) { + if ( !player->spectating ) { + gameLocal.mpGame.ScheduleAnnouncerSound ( AS_CTF_YOUR_TEAM_HAS_FLAG, gameLocal.time, -1, true ); + } + } + } + + if( player->mphud ) { + player->mphud->SetStateInt( "team", flagTeam ); + player->mphud->HandleNamedEvent ( "flagTaken" ); + } + } + } +} + +/* +================ +rvCTFGameState::Run +================ +*/ +void rvCTFGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + int team = ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_MARINE : ( ( gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) ) ? TEAM_STROGG : -1 ) ); + if( gameLocal.serverInfo.GetInt( "si_captureLimit" ) <= 0 ) { + // no capture limit games + team = -1; + } + bool tiedForFirst = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter capture limit timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + + } + } else if ( fragLimitTimeout ) { + // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY + // enter sudden death, the next frag leader will win + // OR the winner lost a point in the frag delay, and there's no tie, so no one wins, game on. + if( tiedForFirst && ( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) >= gameLocal.serverInfo.GetInt( "si_captureLimit" ) )) { + NewState( SUDDENDEATH ); + } + fragLimitTimeout = 0; + } else if ( gameLocal.mpGame.TimeLimitHit() ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( tiedForFirst ) { + // if tied at timelimit hit, goto sudden death + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } else { + // or just end the game + NewState( GAMEREVIEW ); + } + } else if( tiedForFirst && team >= 0 ) { + // check for the rare case that two teams both hit the fraglimit the same frame + // two people tied at fraglimit, advance to sudden death after a delay + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + break; + } + + case SUDDENDEATH: { + int team = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) > gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ? TEAM_MARINE : TEAM_STROGG; + bool tiedForFirst = false; + if( gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ) == gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ) ) { + team = -1; + tiedForFirst = true; + } + + if ( team >= 0 && !tiedForFirst ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, team %d is leader\n", team ); + fragLimitTimeout = gameLocal.time + CAPTURELIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( GAMEREVIEW ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_CAPTURELIMIT, team ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + + break; + } + } +} + +/* +================ +rvCTFGameState::SetFlagState +================ +*/ +void rvCTFGameState::SetFlagState( int flag, flagState_t newState ) { + if ( !gameLocal.isServer ) { + return; + } + + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].state = newState; +} + +/* +================ +rvCTFGameState::SetFlagCarrier +================ +*/ +void rvCTFGameState::SetFlagCarrier( int flag, int clientNum ) { + assert( gameLocal.isServer && ( flag >= 0 && flag < TEAM_MAX ) && (clientNum >= 0 && clientNum < MAX_CLIENTS) && IsType( rvCTFGameState::GetClassType() ) ); + + flagStatus[ flag ].clientNum = clientNum; +} + +/* +================ +rvCTFGameState::operator== +================ +*/ +bool rvCTFGameState::operator==( const rvCTFGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( flagStatus[ i ] != rhs.flagStatus[ i ] ) { + return false; + } + } + + for( int i = 0; i < MAX_AP; i++ ) { + if( apState[ i ] != rhs.apState[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvCTFGameState::operator= +================ +*/ +rvCTFGameState& rvCTFGameState::operator=( const rvCTFGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + flagStatus[ i ] = rhs.flagStatus[ i ]; + } + + for( int i = 0; i < MAX_AP; i++ ) { + apState[ i ] = rhs.apState[ i ]; + } + + return (*this); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +/* +================ +rvTourneyGameState::rvTourneyGameState +================ +*/ +rvTourneyGameState::rvTourneyGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new rvTourneyGameState( false ); + } else { + previousGameState = NULL; + } + + packTourneyHistory = false; + + trackPrevious = allocPrevious; +} + +/* +================ +rvTourneyGameState::Clear +================ +*/ +void rvTourneyGameState::Clear( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + // mekberg: clear previous game state. + if ( previousGameState ) { + previousGameState->Clear(); + } + + rvGameState::Clear(); + + tourneyState = TS_INVALID; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + tourneyCount = 0; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Reset +================ +*/ +void rvTourneyGameState::Reset( void ) { + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear( false ); + arenas[ i ].SetArenaID( i ); + } + + for( int i = 0; i < MAX_ROUNDS; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyHistory[ i ][ j ].playerOne[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ i ][ j ].playerOneNum = -1; + tourneyHistory[ i ][ j ].playerTwoNum = -1; + tourneyHistory[ i ][ j ].playerOneScore = -1; + tourneyHistory[ i ][ j ].playerTwoScore = -1; + } + } + + startingRound = 0; + maxRound = 0; + round = -1; + byeArena = -1; + roundTimeout = 0; +} + +/* +================ +rvTourneyGameState::Run +================ +*/ +void rvTourneyGameState::Run( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: { + // check to see if we need to advance to the next round + if( nextState == GAMEREVIEW ) { + return; + } + + bool roundDone = true; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_INACTIVE ) { + continue; + } + + arenas[ i ].UpdateState(); + + if( arenas[ i ].GetState() != AS_DONE ) { + // check to see if we're done + roundDone = false; + } + } + if( roundDone && !roundTimeout ) { + roundTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + + if( roundDone && gameLocal.time > roundTimeout ) { + int pastRound = round - 1; //rounds are 1 indexed + idList > advancingPlayers; + + round++; + roundTimeout = 0; + + assert( round >= 2 ); + + // copy over tourney history for the previous round + UpdateTourneyHistory( pastRound ); + + // transmit history + forceTourneyHistory = true; + + // add who will advance to the next round + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() ) { + advancingPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + gameLocal.mpGame.AddPlayerWin( arenas[ i ].GetWinner(), 1 ); + } + } + + // when only one player advances, that player has won the tournament + if( advancingPlayers.Num() == 1 ) { + idPlayer* winner = advancingPlayers[ 0 ].First(); + assert( winner ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ].Clear(); + } + + gameLocal.Printf( "Round %d: player %d (%s) has won the tourney!\n", round - 1, winner->entityNumber, gameLocal.userInfo[ winner->entityNumber ].GetString( "ui_name" ) ); + // award 5 wins for winning the entire tournament + gameLocal.mpGame.AddPlayerWin( winner, 5 ); + + nextState = GAMEREVIEW; + nextStateTime = gameLocal.time + 5000; + + return; + } + + // see if we have to swap a player from the byeArena this round + bool swapBye = false; + int thisByeArena = -1; + + if( byeArena >= 0 ) { + // look at the bye arena from the round that just ended + thisByeArena = byeArena / (round - 1); + + // if the bye arena is playing in the next round, there's no need to switch players + if( !(thisByeArena % 2) ) { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena + 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena + 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } else { + if( arenas[ thisByeArena ].GetState() == AS_DONE && arenas[ thisByeArena - 1 ].GetState() == AS_DONE ) { + assert( arenas[ thisByeArena ].GetWinner() && arenas[ thisByeArena - 1 ].GetWinner() ); + swapBye = false; + } else { + swapBye = true; + } + } + } + + idPlayer* oldByePlayer = NULL; + idPlayer* newByePlayer = NULL; + + if( swapBye ) { + oldByePlayer = arenas[ thisByeArena ].GetWinner(); + + // pick a new bye player only from players who will be fighting next round + // i.e., don't swap the bye player with a player who will have a disconnect bye this upcoming round + idList > nextRoundPlayers; + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + if( arenas[ i ].GetState() == AS_DONE && arenas[ i ].GetWinner() && arenas[ i + 1 ].GetState() == AS_DONE && arenas[ i + 1 ].GetWinner() ) { + nextRoundPlayers.Append( rvPair( arenas[ i ].GetWinner(), i ) ); + nextRoundPlayers.Append( rvPair( arenas[ i + 1 ].GetWinner(), i + 1 ) ); + } + } + + if ( nextRoundPlayers.Num() ) { + newByePlayer = nextRoundPlayers[ gameLocal.random.RandomInt( nextRoundPlayers.Num() ) ].First(); + } + } + + // assign arenas for the next round + for ( int i = 0; i < MAX_ARENAS; i += 2 ) { + idPlayer* advanceOne = arenas[ i ].GetWinner(); + idPlayer* advanceTwo = arenas[ i + 1 ].GetWinner(); + + // #13266 - bug: bystander from prev round is spectator in his match arena after round change + // we call rvTourneyInstance::Clear when setting up rounds (below), which sets it's players as spectator + // if the former bystander is placed in one of the new arenas and is still referenced in the bye arena then he'll get set spectating + // so just clear him up once identified + // #13631 - we used to call RemovePlayer before entering that loop, but that was messing up the selection of the next bystander + // so have to do it inside the loop now, just before it happens + if ( ( i == thisByeArena || i + 1 == thisByeArena ) && arenas[ thisByeArena ].HasPlayer( oldByePlayer ) ) { + arenas[ thisByeArena ].RemovePlayer( oldByePlayer ); + } + + arenas[ i ].Clear(); + arenas[ i + 1 ].Clear(); + + // assign new arenas, swapping oldByePlayer with newByePlayer + if ( newByePlayer && oldByePlayer ) { + if ( advanceOne == newByePlayer ) { + advanceOne = oldByePlayer; + } else if ( advanceTwo == newByePlayer ) { + advanceTwo = oldByePlayer; + } else if ( advanceOne == oldByePlayer ) { + advanceOne = newByePlayer; + } else if ( advanceTwo == oldByePlayer ) { + advanceTwo = newByePlayer; + } + } + + if ( advanceOne || advanceTwo ) { + arenas[ (i / 2) ].AddPlayers( advanceOne, advanceTwo ); + + if ( advanceOne ) { + advanceOne->JoinInstance( (i / 2) ); + advanceOne->ServerSpectate( false ); + } + + if ( advanceTwo ) { + advanceTwo->JoinInstance( (i / 2) ); + advanceTwo->ServerSpectate( false ); + } + + gameLocal.Printf( "Round %d: Arena %d is starting play with players %d (%s) and %d (%s)\n", round, (i / 2), advanceOne ? advanceOne->entityNumber : -1, advanceOne ? advanceOne->GetUserInfo()->GetString( "ui_name" ) : "NULL", advanceTwo ? advanceTwo->entityNumber : -1, advanceTwo ? advanceTwo->GetUserInfo()->GetString( "ui_name" ) : "NULL" ); + + arenas[ (i / 2) ].Ready(); + } + } + + // once the new round is setup, go through and make sure all the spectators are in valid arena + for( int i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* p = (idPlayer*)gameLocal.entities[ i ]; + + // re-select our arena since round changed + if( p && p->spectating ) { + rvTourneyArena& thisArena = arenas[ p->GetArena() ]; + if( thisArena.GetState() != AS_WARMUP ) { + p->JoinInstance( GetNextActiveArena( p->GetArena() ) ); + } + } + } + } + + break; + } + } +} + +/* +================ +rvTourneyGameState::NewState +================ +*/ +void rvTourneyGameState::NewState( mpGameState_t newState ) { + assert( (newState != currentState) && gameLocal.isServer ); + + switch( newState ) { + case WARMUP: { + Reset(); + // force everyone to spectate + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] ) { + ((idPlayer*)gameLocal.entities[ i ])->ServerSpectate( true ); + } + } + break; + } + case GAMEON: { + tourneyCount++; + SetupInitialBrackets(); + break; + } + } + + rvGameState::NewState( newState ); +} + +/* +================ +rvTourneyGameState::GameStateChanged +================ +*/ +void rvTourneyGameState::GameStateChanged( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + rvGameState::GameStateChanged(); + idPlayer* localPlayer = gameLocal.GetLocalPlayer(); + + int oldRound = ((rvTourneyGameState*)previousGameState)->round; + + if( round != oldRound ) { + if ( round > maxRound && gameLocal.GetLocalPlayer() ) { + gameLocal.GetLocalPlayer()->ForceScoreboard( true, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_DONE, gameLocal.time ); + } + + // we're starting a new round + gameLocal.mpGame.tourneyGUI.RoundStart(); + + // skip announce if the round number doesn't make sense + if ( round >= 1 && round <= MAX_ROUNDS ) { + // play the sound a bit after round restart to let spawn sounds settle + gameLocal.mpGame.ScheduleAnnouncerSound( (announcerSound_t)( AS_TOURNEY_PRELIMS + round - 1 ), gameLocal.time + 1500); + } + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& thisArena = arenas[ i ]; + rvTourneyArena& previousArena = ((rvTourneyGameState*)previousGameState)->arenas[ i ]; + + if( ((thisArena.GetPlayers()[ 0 ] != previousArena.GetPlayers()[ 0 ]) || + (thisArena.GetPlayers()[ 1 ] != previousArena.GetPlayers()[ 1 ]) || + (round != ((rvTourneyGameState*)previousGameState)->round )) /*&& + !((thisArena.GetPlayers()[ 0 ] == NULL && thisArena.GetPlayers()[ 1 ] != NULL) || + (thisArena.GetPlayers()[ 0 ] != NULL && thisArena.GetPlayers()[ 1 ] == NULL) ) */) { + + // don't re-init arenas that have changed and been done for a while + if( thisArena.GetState() != AS_DONE || previousArena.GetState() != AS_DONE ) { + gameLocal.mpGame.tourneyGUI.ArenaStart( i ); + } + + if( localPlayer && thisArena.GetPlayers()[ 0 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_ONE ); + } + if( localPlayer && thisArena.GetPlayers()[ 1 ] == localPlayer ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( i, TGH_PLAYER_TWO ); + } + } + + if( localPlayer->GetArena() == thisArena.GetArenaID() && thisArena.GetState() == AS_SUDDEN_DEATH && thisArena.GetState() != previousArena.GetState() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_SUDDEN_DEATH, gameLocal.time, thisArena.GetArenaID() ); + gameLocal.GetLocalPlayer()->GUIMainNotice( common->GetLocalizedString( "#str_104287" ) ); + } + + if( thisArena.GetState() == AS_DONE && thisArena.GetState() != previousArena.GetState() ) { + if( thisArena.GetWinner() != previousArena.GetWinner() && thisArena.GetWinner() == gameLocal.GetLocalPlayer() ) { + if( round >= maxRound ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_WON, gameLocal.time ); + } else { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ADVANCE, gameLocal.time ); + } + } else if( thisArena.GetLoser() != previousArena.GetLoser() && thisArena.GetLoser() == gameLocal.GetLocalPlayer() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_ELIMINATED, gameLocal.time ); + } + + if( previousArena.GetWinner() == NULL && thisArena.GetWinner() != NULL ) { + gameLocal.mpGame.tourneyGUI.ArenaDone( i ); + } + + // on the client, add this result to our tourneyHistory + //if( gameLocal.isClient && thisArena.GetPlayers()[ 0 ] && thisArena.GetPlayers()[ 1 ] && (round - 1 ) >= 0 && (round - 1) < MAX_ROUNDS ) { + // tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 0 ] ); + // tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( thisArena.GetPlayers()[ 1 ] ); + //} + } + + if( localPlayer && (thisArena.GetState() == AS_WARMUP) && (thisArena.GetState() != previousArena.GetState()) && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_THREE ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_TWO ); + gameLocal.mpGame.RemoveAnnouncerSound( AS_GENERAL_ONE ); + + int warmupEndTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countdown" ) * 1000 ) + 5000; + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_PREPARE_TO_FIGHT, gameLocal.time + 5000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_THREE, warmupEndTime - 3000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_TWO, warmupEndTime - 2000 ); + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_ONE, warmupEndTime - 1000 ); + + localPlayer->vsMsgState = true; + localPlayer->GUIMainNotice( va( "%s^0 vs. %s^0", thisArena.GetPlayerName( 0 ), thisArena.GetPlayerName( 1 ) ), true ); + } + + if( (thisArena.GetState() == AS_ROUND) && (thisArena.GetState() != previousArena.GetState()) ) { + if( localPlayer && localPlayer->GetArena() == thisArena.GetArenaID() ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_GENERAL_FIGHT, gameLocal.time ); + gameLocal.mpGame.ScheduleTimeAnnouncements(); + } + } + } + + if( ((rvTourneyGameState*)previousGameState)->currentState != currentState ) { + if( currentState == WARMUP ) { + gameLocal.mpGame.tourneyGUI.PreTourney(); + } else if( currentState == COUNTDOWN ) { + if( currentState == COUNTDOWN && ((rvTourneyGameState*)previousGameState)->currentState != INACTIVE ) { + gameLocal.mpGame.ScheduleAnnouncerSound( AS_TOURNEY_START, gameLocal.time); + } + } else if( currentState == GAMEON ) { + gameLocal.mpGame.tourneyGUI.TourneyStart(); + } + } + + if( packTourneyHistory ) { + // apply history + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); + packTourneyHistory = false; + } + + if( localPlayer && localPlayer->spectating ) { + + rvTourneyArena& thisArena = arenas[ localPlayer->GetArena() ]; + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL || (localPlayer->spectating && localPlayer->spectator != thisArena.GetPlayers()[ 0 ]->entityNumber && localPlayer->spectator != thisArena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_BRACKET ); + } else if ( thisArena.GetPlayers()[ 0 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 0 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_ONE ); + } else if ( thisArena.GetPlayers()[ 1 ] == localPlayer || (localPlayer->spectating && thisArena.GetPlayers()[ 1 ]->entityNumber == localPlayer->spectator) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( localPlayer->GetArena(), TGH_PLAYER_TWO ); + } + } + +} + +/* +================ +msgTourney_t +Identifiers for transmitted state +================ +*/ +typedef enum { + MSG_TOURNEY_TOURNEYSTATE, + MSG_TOURNEY_STARTINGROUND, + MSG_TOURNEY_ROUND, + MSG_TOURNEY_MAXROUND, + MSG_TOURNEY_HISTORY, + MSG_TOURNEY_TOURNEYCOUNT, + MSG_TOURNEY_ARENAINFO +} msgTourney_t; + +/* +================ +rvTourneyGameState::PackState +================ +*/ +void rvTourneyGameState::PackState( idBitMsg& outMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( tourneyState != ((rvTourneyGameState*)previousGameState)->tourneyState ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYSTATE ); + outMsg.WriteByte( tourneyState ); + } + + if ( startingRound != ((rvTourneyGameState*)previousGameState)->startingRound ) { + outMsg.WriteByte( MSG_TOURNEY_STARTINGROUND ); + outMsg.WriteByte( startingRound ); + } + + if( round != ((rvTourneyGameState*)previousGameState)->round ) { + outMsg.WriteByte( MSG_TOURNEY_ROUND ); + outMsg.WriteByte( round ); + } + + if( maxRound != ((rvTourneyGameState*)previousGameState)->maxRound ) { + outMsg.WriteByte( MSG_TOURNEY_MAXROUND ); + outMsg.WriteByte( maxRound ); + } + + for ( int i = 0; i < MAX_ARENAS; i++ ) { + if ( arenas[ i ] != ((rvTourneyGameState*)previousGameState)->arenas[ i ] ) { + outMsg.WriteByte( MSG_TOURNEY_ARENAINFO + i ); + arenas[ i ].PackState( outMsg ); + } + } + + if ( packTourneyHistory || forceTourneyHistory ) { + if ( forceTourneyHistory ) { + common->Warning( "forcing down tourney history\n" ); + } + // don't write out uninitialized tourney history + if ( startingRound > 0 && round > 1 ) { + outMsg.WriteByte( MSG_TOURNEY_HISTORY ); + + // client might not yet have startingRound or round + outMsg.WriteByte( startingRound ); + outMsg.WriteChar( round ); + outMsg.WriteByte( maxRound ); + + for( int i = startingRound - 1; i <= Min( (round - 1), (maxRound - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + outMsg.WriteString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerOneScore ) ); + outMsg.WriteString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + outMsg.WriteChar( idMath::ClampChar( tourneyHistory[ i ][ j ].playerTwoScore ) ); + outMsg.WriteChar( tourneyHistory[ i ][ j ].playerOneNum ); + outMsg.WriteChar( tourneyHistory[ i ][ j ].playerTwoNum ); + } + } + packTourneyHistory = false; + } + } + + if ( tourneyCount != ((rvTourneyGameState*)previousGameState)->tourneyCount ) { + outMsg.WriteByte( MSG_TOURNEY_TOURNEYCOUNT ); + outMsg.WriteByte( tourneyCount ); + } +} + +/* +================ +rvTourneyGameState::UnpackState +================ +*/ +void rvTourneyGameState::UnpackState( const idBitMsg& inMsg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + while( inMsg.GetRemainingData() ) { + int index = inMsg.ReadByte(); + + switch( index ) { + case MSG_TOURNEY_TOURNEYSTATE: { + tourneyState = (tourneyState_t)inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_STARTINGROUND: { + startingRound = inMsg.ReadByte(); + break; + } + case MSG_TOURNEY_ROUND: { + round = inMsg.ReadByte(); + // not a great way of doing this, should clamp the values + if ( round == 255 ) { + round = -1; + } + break; + } + case MSG_TOURNEY_MAXROUND: { + maxRound = inMsg.ReadByte(); + if ( maxRound == 255 ) { + maxRound = -1; + } + break; + } + case MSG_TOURNEY_HISTORY: { + int startRound = inMsg.ReadByte(); + int rnd = inMsg.ReadChar(); + int maxr = inMsg.ReadByte(); + + assert( rnd >= 1 ); // something is uninitialized + + for( int i = startRound - 1; i <= Min( (rnd - 1), (maxr - 1) ); i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + inMsg.ReadString( tourneyHistory[ i ][ j ].playerOne, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneScore = inMsg.ReadChar(); + inMsg.ReadString( tourneyHistory[ i ][ j ].playerTwo, MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoScore = inMsg.ReadChar(); + tourneyHistory[ i ][ j ].playerOneNum = inMsg.ReadChar(); + tourneyHistory[ i ][ j ].playerTwoNum = inMsg.ReadChar(); + + if( tourneyHistory[ i ][ j ].playerOneNum < 0 || tourneyHistory[ i ][ j ].playerOneNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerOneNum = -1; + } + + if( tourneyHistory[ i ][ j ].playerTwoNum < 0 || tourneyHistory[ i ][ j ].playerTwoNum >= MAX_CLIENTS ) { + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // client side (and listen server) no reason to check for history change all the time + packTourneyHistory = true; + break; + } + case MSG_TOURNEY_TOURNEYCOUNT: { + tourneyCount = inMsg.ReadByte(); + break; + } + default: { + if ( index >= MSG_TOURNEY_ARENAINFO && index < MSG_TOURNEY_ARENAINFO + MAX_ARENAS ) { + arenas[ index - MSG_TOURNEY_ARENAINFO ].UnpackState( inMsg ); + } else { + gameLocal.Error( "rvTourneyGameState::UnpackState() - Unknown data identifier '%d'\n", index ); + } + break; + } + } + + } +} + +/* +================ +rvTourneyGameState::SendState +================ +*/ +void rvTourneyGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && IsType( rvTourneyGameState::GetClassType() ) ); + + if ( clientNum == -1 && (rvTourneyGameState&)(*this) == (rvTourneyGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + if( clientNum == -1 && forceTourneyHistory ) { + forceTourneyHistory = false; + } + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +rvTourneyGameState::WriteState +=============== +*/ +void rvTourneyGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add Tourney info + PackState( msg ); +} + +/* +================ +rvTourneyGameState::ReceiveState +================ +*/ +void rvTourneyGameState::ReceiveState( const idBitMsg& msg ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( !rvGameState::BaseUnpackState( msg ) ) { + return; + } + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (rvTourneyGameState&)(*previousGameState) = (rvTourneyGameState&)(*this); +} + +/* +================ +rvTourneyGameState::SendInitialState +================ +*/ +void rvTourneyGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + rvTourneyGameState* previousState = (rvTourneyGameState*)previousGameState; + + rvTourneyGameState invalidState; + + previousGameState = &invalidState; + + + // if the tourney has been going on, transmit the tourney history + if( round > 0 ) { + // comparing the tourney history for all state changes is wasteful when we really just want to send it to new clients + packTourneyHistory = true; + } + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + + +/* +================ +rvTourneyGameState::operator== +================ +*/ +bool rvTourneyGameState::operator==( const rvTourneyGameState& rhs ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + if( round != rhs.round || startingRound != rhs.startingRound || maxRound != rhs.maxRound || tourneyState != rhs.tourneyState ) { + return false; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ] != rhs.arenas[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +rvTourneyGameState::operator= +================ +*/ +rvTourneyGameState& rvTourneyGameState::operator=( const rvTourneyGameState& rhs ) { + assert( IsType( rvTourneyGameState::GetClassType() ) && rhs.IsType( rvTourneyGameState::GetClassType() ) ); + + (rvGameState&)(*this) = (rvGameState&)rhs; + + round = rhs.round; + startingRound = rhs.startingRound; + maxRound = rhs.maxRound; + tourneyState = rhs.tourneyState; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + arenas[ i ] = rhs.arenas[ i ]; + } + + return (*this); +} + +/* +================ +rvTourneyGameState::GetNumArenas +Returns number of active arenas +================ +*/ +int rvTourneyGameState::GetNumArenas( void ) const { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + int num = 0; + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + num++; + } + } + + return num; +} + +/* +================ +rvTourneyGameState::SetupInitialBrackets + +Sets up the brackets for a new match. fragCount in playerstate is the player's +persistant frag count over an entire level. In teamFragCount we store this +rounds score. + +Initial bracket seeds are random +================ +*/ +void rvTourneyGameState::SetupInitialBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + idList unseededPlayers; + int numRankedPlayers = gameLocal.mpGame.GetNumRankedPlayers(); + + // all this crazy math does is figure out: + // 8 arenas to 4 rounds ( round 1: 8 arenas, round 2: 4 arenas, round 3: 2 arenas, round 4: 1 arena ) + // 16 arenas to 5 rounds ( round 1: 16 arenas, round 2: 8 arenas, round 3: 4 arenas, round 4: 2 arenas, round 5: 1 arena ) + // etc + int newMaxRound = idMath::ILog2( Max( idMath::CeilPowerOfTwo( MAX_ARENAS * 2 ), 2 ) ); + + // we start at a round appropriate for our # of people + // If you have 1-2 players, start in maxRound, if you have 3-4 players start in maxRound - 1, if you have 5-8 players + // start in maxRound - 2, if you have 9 - 16 players start in maxRound - 3, etc. + int newRound = newMaxRound - idMath::ILog2( Max( idMath::CeilPowerOfTwo( gameLocal.mpGame.GetNumRankedPlayers() ), 2 ) ) + 1; + + round = newRound; + maxRound = newMaxRound; + startingRound = round; + + for( int i = 0; i < numRankedPlayers; i++ ) { + unseededPlayers.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + int numArenas = 0; + + while ( unseededPlayers.Num() > 1 ) { + idPlayer* playerOne = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerOne ); + + idPlayer* playerTwo = unseededPlayers[ gameLocal.random.RandomInt( unseededPlayers.Num() ) ]; + unseededPlayers.Remove( playerTwo ); + + rvTourneyArena& arena = arenas[ numArenas ]; + + arena.Clear(); + arena.AddPlayers( playerOne, playerTwo ); + + // place the players in the correct instance + playerOne->JoinInstance( numArenas ); + playerTwo->JoinInstance( numArenas ); + + playerOne->ServerSpectate( false ); + playerTwo->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( playerOne, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( playerTwo, 0 ); + + gameLocal.Printf( "rvTourneyGameState::SetupInitialBrackets() - %s will face %s in arena %d\n", playerOne->GetUserInfo()->GetString( "ui_name" ), playerTwo->GetUserInfo()->GetString( "ui_name" ), numArenas ); + + // this arena is ready to play + arena.Ready(); + + numArenas++; + } + + assert( unseededPlayers.Num() == 0 || unseededPlayers.Num() == 1 ); + + if( unseededPlayers.Num() ) { + assert( unseededPlayers[ 0 ] ); + // the mid player gets a bye + unseededPlayers[ 0 ]->SetTourneyStatus( PTS_UNKNOWN ); + unseededPlayers[ 0 ]->ServerSpectate( true ); + + arenas[ numArenas ].AddPlayers( unseededPlayers[ 0 ], NULL ); + arenas[ numArenas ].Ready(); + + // mark the ancestor arena of the bye player + byeArena = numArenas * startingRound; + } else { + byeArena = -1; + } +} + +/* +================ +rvTourneyGameState::ClientDisconnect +A player has disconnected from the server +================ +*/ +void rvTourneyGameState::ClientDisconnect( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + if( gameLocal.isClient ) { + return; + } + + // go through the tourney history and copy over the disconnecting player's name + if( startingRound > 0 && round <= MAX_ROUNDS ) { + for( int i = startingRound - 1; i < round - 1; i++ ) { + for( int j = 0; j < MAX_ARENAS / (i + 1); j++ ) { + if( tourneyHistory[ i ][ j ].playerOneNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerOneNum = -1; + } else if( tourneyHistory[ i ][ j ].playerTwoNum == player->entityNumber ) { + idStr::Copynz( tourneyHistory[ i ][ j ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ i ][ j ].playerTwoNum = -1; + } + } + } + + // copy over the current rounds info if the player is playing + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetPlayers()[ 0 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerOne, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( player ); + } else if( arenas[ i ].GetPlayers()[ 1 ] == player ) { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + idStr::Copynz( tourneyHistory[ round - 1 ][ i ].playerTwo, player->GetUserInfo()->GetString( "ui_name" ), MAX_TOURNEY_HISTORY_NAME_LEN ); + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( player ); + } + } + + // retransmit tourney history to everyone + packTourneyHistory = true; + } + + RemovePlayer( player ); + + // give RemovePlayer() a chance to update tourney history if needed + if( packTourneyHistory ) { + for( int i = 0; i < gameLocal.numClients; i++ ) { + if( i == player->entityNumber ) { + continue; + } + + if( gameLocal.entities[ i ] ) { + packTourneyHistory = true; + SendState( serverReliableSender.To( i, true ), i ); + } + } + + if ( gameLocal.isRepeater ) { + packTourneyHistory = true; + SendState( repeaterReliableSender.To( -1 ), ENTITYNUM_NONE ); + } + packTourneyHistory = false; + } +} + +/* +================ +rvTourneyGameState::Spectate +================ +*/ +void rvTourneyGameState::Spectate( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer || player->IsFakeClient() ); + + if( player->spectating && player->wantSpectate ) { + RemovePlayer( player ); + } +} + +/* +================ +rvTourneyGameState::RemovePlayer +Removes the specified player from the arena +================ +*/ +void rvTourneyGameState::RemovePlayer( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( round < 1 || round > maxRound ) { + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + idPlayer** players = GetArenaPlayers( i ); + + if( players[ 0 ] == player || players[ 1 ] == player ) { + rvTourneyArena& arena = arenas[ i ]; + + idPlayer* remainingPlayer = players[ 0 ] == player ? players[ 1 ] : players[ 0 ]; + + bool arenaInProgress = arena.GetState() == AS_ROUND || arena.GetState() == AS_WARMUP || arena.GetState() == AS_SUDDEN_DEATH; + bool remainingIsWinner = (remainingPlayer == arena.GetWinner()); + int remainingIndex = (remainingPlayer == arena.GetPlayers()[ 0 ]) ? 0 : 1; + + arena.RemovePlayer( player ); + + // both players have disconnected from this arena, or the winner has disconnected, just return + if( (!arena.GetPlayers()[ 0 ] && !arena.GetPlayers()[ 1 ]) || (!arenaInProgress && remainingPlayer && !remainingIsWinner) ) { + arena.Clear(); + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + return; + } + + assert( remainingPlayer ); + + // if this arena is in progress, try restarting + // ATVI DevTrack #13196 - do not put the bye player into a game with the abandonned player anymore + // if ( arenaInProgress && byeArena >= 0 && arenas[ byeArena / round ].GetWinner() ) { + if ( 0 ) { + idPlayer* byePlayer = arenas[ byeArena / round ].GetWinner(); + + // reset history for this new round + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + + arena.Clear(); + arenas[ byeArena / round ].Clear(); + + arena.AddPlayers( remainingPlayer, byePlayer ); + + // place the players in the correct instance + remainingPlayer->JoinInstance( i ); + byePlayer->JoinInstance( i ); + + remainingPlayer->ServerSpectate( false ); + byePlayer->ServerSpectate( false ); + + gameLocal.mpGame.SetPlayerTeamScore( remainingPlayer, 0 ); + gameLocal.mpGame.SetPlayerTeamScore( byePlayer, 0 ); + + gameLocal.Printf( "rvTourneyManager::RemovePlayer() - %s will face %s in arena %d\n", remainingPlayer->GetUserInfo()->GetString( "ui_name" ), byePlayer->GetUserInfo()->GetString( "ui_name" ), i ); + + byeArena = -1; + // this arena is ready to play + arena.Ready(); + } else { + // if the arena was in progress and didn't get a bye player to step in, this becomes a bye + // arena - clear the tourney history + if( arenaInProgress ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = -1; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = -1; + + tourneyHistory[ round - 1 ][ i ].playerTwoNum = -1; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = -1; + } else { + // since the player is disconnecting, write the history for this round + if( remainingIndex == 0 ) { + tourneyHistory[ round - 1 ][ i ].playerOneNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } else { + tourneyHistory[ round - 1 ][ i ].playerTwoNum = remainingPlayer->entityNumber; + tourneyHistory[ round - 1 ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round - 1 ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( remainingPlayer ); + } + } + } + + return; + } + } +} + +int rvTourneyGameState::GetNextActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena + 1; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = 0; i < arena; i++ ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +int rvTourneyGameState::GetPrevActiveArena( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = arena - 1; i >= 0; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + for( int i = MAX_ARENAS - 1; i > arena; i-- ) { + if( arenas[ i ].GetState() != AS_INACTIVE && arenas[ i ].GetState() != AS_DONE ) { + return i; + } + } + + return arena; +} + +void rvTourneyGameState::SpectateCycleNext( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer || player->IsFakeClient() ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = players[ 1 ]->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetNextActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + player->spectator = player->entityNumber; + } + } + + // this is where the listen server updates it's gui spectating elements + if ( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::SpectateCyclePrev( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + assert( gameLocal.isServer || player->IsFakeClient() ); + + rvTourneyArena& spectatingArena = arenas[ player->GetArena() ]; + + idPlayer** players = spectatingArena.GetPlayers(); + + if( !players[ 0 ] || !players[ 1 ] || players[ 0 ]->spectating || players[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + if( player->spectator != players[ 0 ]->entityNumber && player->spectator != players[ 1 ]->entityNumber ) { + if( gameLocal.time > player->lastArenaChange ) { + if ( GetNumArenas() <= 0 ) { + player->JoinInstance( 0 ); + } else { + player->JoinInstance( GetPrevActiveArena( player->GetArena() ) ); + } + player->lastArenaChange = gameLocal.time + 2000; + + rvTourneyArena& newSpectatingArena = arenas[ player->GetArena() ]; + + idPlayer** newPlayers = newSpectatingArena.GetPlayers(); + + if( !newPlayers[ 0 ] || !newPlayers[ 1 ] || newPlayers[ 0 ]->spectating || newPlayers[ 1 ]->spectating ) { + // setting the spectated client to ourselves will unlock us + player->spectator = player->entityNumber; + return; + } + + player->spectator = newPlayers[ 1 ]->entityNumber; + } + } else if( player->spectator == players[ 0 ]->entityNumber ) { + player->spectator = player->entityNumber; + } else if( player->spectator == players[ 1 ]->entityNumber ) { + player->spectator = players[ 0 ]->entityNumber; + } + + // this is where the listen server updates it gui spectating elements + if( gameLocal.GetLocalPlayer() == player ) { + rvTourneyArena& arena = arenas[ player->GetArena() ]; + + if( arena.GetPlayers()[ 0 ] == NULL || arena.GetPlayers()[ 1 ] == NULL || (player->spectating && player->spectator != arena.GetPlayers()[ 0 ]->entityNumber && player->spectator != arena.GetPlayers()[ 1 ]->entityNumber) ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_BRACKET ); + } else if( arena.GetPlayers()[ 0 ] == player || player->spectator == arena.GetPlayers()[ 0 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_ONE ); + } else if( arena.GetPlayers()[ 1 ] == player || player->spectator == arena.GetPlayers()[ 1 ]->entityNumber ) { + gameLocal.mpGame.tourneyGUI.ArenaSelect( player->GetArena(), TGH_PLAYER_TWO ); + } + + gameLocal.mpGame.tourneyGUI.UpdateScores(); + } +} + +void rvTourneyGameState::UpdateTourneyBrackets( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + gameLocal.mpGame.tourneyGUI.SetupTourneyHistory( startingRound, round - 1, tourneyHistory ); +} + +void rvTourneyGameState::UpdateTourneyHistory( int round ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if ( round >= MAX_ROUNDS ) { + assert( false ); + gameLocal.Error( "rvTourneyGameState::UpdateTourneyHistory: MAX_ROUNDS exceeded" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + // sometimes tourney history might have been updated for the current + // round, so don't clobber any data + + if( (tourneyHistory[ round ][ i ].playerOne[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerOneNum != -1) || + (tourneyHistory[ round ][ i ].playerTwo[ 0 ] != '\0' || tourneyHistory[ round ][ i ].playerTwoNum != -1) ) { + continue; + } + + tourneyHistory[ round ][ i ].playerOne[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneNum = -1; + tourneyHistory[ round ][ i ].playerOneScore = 0; + + tourneyHistory[ round ][ i ].playerTwo[ 0 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoNum = -1; + tourneyHistory[ round ][ i ].playerTwoScore = 0; + + if( arenas[ i ].GetState() == AS_DONE ) { + idPlayer* playerOne = arenas[ i ].GetPlayers()[ 0 ]; + idPlayer* playerTwo = arenas[ i ].GetPlayers()[ 1 ]; + + if( playerOne ) { + tourneyHistory[ round ][ i ].playerOneNum = playerOne->entityNumber; + tourneyHistory[ round ][ i ].playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerOneScore = gameLocal.mpGame.GetTeamScore( playerOne ); + } + + if( playerTwo ) { + tourneyHistory[ round ][ i ].playerTwoNum = playerTwo->entityNumber; + tourneyHistory[ round ][ i ].playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN - 1 ] = '\0'; + tourneyHistory[ round ][ i ].playerTwoScore = gameLocal.mpGame.GetTeamScore( playerTwo ); + } + } + } +} + + +/* +================ +rvTourneyGameState::FirstAvailableArena +Returns the first non-WARMUP arena available for use in the next round +================ +*/ +int rvTourneyGameState::FirstAvailableArena( void ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + for( int i = 0; i < MAX_ARENAS; i++ ) { + if( arenas[ i ].GetState() != AS_WARMUP ) { + return i; + } + } + + // no available arenas + assert( 0 ); + return 0; +} + +arenaOutcome_t* rvTourneyGameState::GetArenaOutcome( int arena ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( arenas[ arena ].GetState() != AS_DONE || (arenas[ arena ].GetPlayers()[ 0 ] && arenas[ arena ].GetPlayers()[ 1 ]) ) { + return NULL; + } + + return &(tourneyHistory[ round - 1 ][ arena ]); +} + +bool rvTourneyGameState::HasBye( idPlayer* player ) { + assert( IsType( rvTourneyGameState::GetClassType() ) ); + + if( player == NULL || player->GetArena() < 0 || player->GetArena() >= MAX_ARENAS ) { + return false; + } + + // if we're one of the players in the arena we're in, and the other player is NULL, we have a bye + if( (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == player || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == player) && + (arenas[ player->GetArena() ].GetPlayers()[ 0 ] == NULL || arenas[ player->GetArena() ].GetPlayers()[ 1 ] == NULL) ) { + return true; + } + + return false; +} + +// simple RTTI +gameStateType_t rvGameState::type = GS_BASE; +gameStateType_t rvDMGameState::type = GS_DM; +gameStateType_t rvTeamDMGameState::type = GS_TEAMDM; +gameStateType_t rvCTFGameState::type = GS_CTF; +gameStateType_t rvTourneyGameState::type = GS_TOURNEY; + +bool rvGameState::IsType( gameStateType_t type ) const { + return ( type == rvGameState::type ); +} + +bool rvDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvDMGameState::type ); +} + +bool rvTeamDMGameState::IsType( gameStateType_t type ) const { + return ( type == rvTeamDMGameState::type ); +} + +bool rvCTFGameState::IsType( gameStateType_t type ) const { + return ( type == rvCTFGameState::type ); +} + +bool rvTourneyGameState::IsType( gameStateType_t type ) const { + return ( type == rvTourneyGameState::type ); +} + +gameStateType_t rvGameState::GetClassType( void ) { + return rvGameState::type; +} + +gameStateType_t rvDMGameState::GetClassType( void ) { + return rvDMGameState::type; +} + +gameStateType_t rvTeamDMGameState::GetClassType( void ) { + return rvTeamDMGameState::type; +} + +gameStateType_t rvCTFGameState::GetClassType( void ) { + return rvCTFGameState::type; +} + +gameStateType_t rvTourneyGameState::GetClassType( void ) { + return rvTourneyGameState::type; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +/* +================ +riDZGameState::riDZGameState +================ +*/ +riDZGameState::riDZGameState( bool allocPrevious ) : rvGameState( false ) { + Clear(); + + if( allocPrevious ) { + previousGameState = new riDZGameState( false ); + } else { + previousGameState = NULL; + } + + trackPrevious = allocPrevious; + + type = GS_DZ; +} + +/* +================ +riDZGameState::~riDZGameState +================ +*/ +riDZGameState::~riDZGameState( void ) { + Clear(); + delete previousGameState; + previousGameState = NULL; +} + +/* +================ +riDZGameState::Clear +================ +*/ +void riDZGameState::Clear( void ) { + rvGameState::Clear(); + + if ( previousGameState ) { + riDZGameState* prevState = (riDZGameState*)previousGameState; + prevState->Clear( ); + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + + dzTriggerEnt = -1; +} + +/* +================ +riDZGameState::SendState +================ +*/ +void riDZGameState::SendState( const idMessageSender &sender, int clientNum ) { + idBitMsg outMsg; + byte msgBuf[MAX_GAME_MESSAGE_SIZE]; + + assert( (gameLocal.isServer || gameLocal.isRepeater) && trackPrevious && type == GS_DZ ); + + if( clientNum == -1 && (riDZGameState&)(*this) == (riDZGameState&)(*previousGameState) ) { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_GAMESTATE ); + + WriteState( outMsg ); + + sender.Send( outMsg ); + + // don't update the state if we are working for a single client + if ( clientNum == -1 ) { + outMsg.ReadByte(); // pop off the msg ID + ReceiveState( outMsg ); + } +} + +/* +=============== +riDZGameState::WriteState +=============== +*/ +void riDZGameState::WriteState( idBitMsg &msg ) { + // send off base info + rvGameState::PackState( msg ); + // add DZ info + PackState( msg ); +} + +/* +================ +riDZGameState::ReceiveState +================ +*/ +void riDZGameState::ReceiveState( const idBitMsg& msg ) { + assert( type == GS_DZ ); + + if ( !rvGameState::BaseUnpackState( msg ) ) { + return; + } + UnpackState( msg ); + + if ( gameLocal.localClientNum >= 0 ) { + GameStateChanged(); + } + + (riDZGameState&)(*previousGameState) = (riDZGameState&)(*this); +} + +/* +================ +riDZGameState::PackState +================ +*/ +void riDZGameState::PackState( idBitMsg& outMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + outMsg.WriteByte( dzStatus[ i ].state ); + outMsg.WriteBits( dzStatus[ i ].clientNum, -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + + outMsg.WriteLong(dzTriggerEnt); + outMsg.WriteLong(dzShaderParm); +} + +/* +================ +riDZGameState::UnpackState +================ +*/ +void riDZGameState::UnpackState( const idBitMsg& inMsg ) { + int i; + for( i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = (dzState_t)inMsg.ReadByte(); + dzStatus[ i ].clientNum = inMsg.ReadBits( -( idMath::BitsForInteger( MAX_CLIENTS ) + 1 ) ); + } + dzTriggerEnt = inMsg.ReadLong(); + dzShaderParm = inMsg.ReadLong(); +} + +/* +================ +riDZGameState::SendInitialState +================ +*/ +void riDZGameState::SendInitialState( const idMessageSender &sender, int clientNum ) { + assert( type == GS_DZ ); + + riDZGameState* previousState = (riDZGameState*)previousGameState; + + riDZGameState invalidState; + + previousGameState = &invalidState; + + SendState( sender, clientNum ); + + previousGameState = previousState; +} + + +/* +================ +riDZGameState::ControlZoneStateChanged +================ +*/ +void riDZGameState::ControlZoneStateChanged( int team ) { + if( !gameLocal.isClient && !gameLocal.isListenServer ) { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + return; + } + + if ( dzTriggerEnt < 0 ) + return; + + idEntity* ent = gameLocal.FindEntity(dzTriggerEnt); + if ( ent ) { + ent->SetShaderParm(7, dzShaderParm); + } + + dzTriggerEnt = -1; +} + + +/* +================ +riDZGameState::GameStateChanged +================ +*/ +void riDZGameState::GameStateChanged( void ) { + // detect any base state changes + rvGameState::GameStateChanged(); + + // DZ specific stuff + idPlayer* player = gameLocal.GetLocalPlayer(); + + if( player == NULL ) { + //gameLocal.Error( "riDZGameState::GameStateChanged() - NULL local player\n" ); + return; + } + + if( currentState == GAMEREVIEW ) + { + // Need to clear the deadzone state at this point. + ((riDZGameState*)previousGameState)->Clear(); + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ].state = DZ_NONE; + dzStatus[ i ].clientNum = -1; + } + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] == ((riDZGameState*)previousGameState)->dzStatus[ i ] ) { + continue; + } + + ControlZoneStateChanged(i); + } +} + +/* +================ +riDZGameState::Run +================ +*/ +void riDZGameState::Run( void ) { + // run common stuff + rvGameState::Run(); + + switch( currentState ) { + case GAMEON: + { + /// Check if we're frozen (in buy mode, etc.) + if( gameLocal.GetIsFrozen() ) + { + /// Check if it's time for freeze to wear off + int unFreezeTime = gameLocal.GetUnFreezeTime(); + if( gameLocal.time >= unFreezeTime ) + { + gameLocal.SetIsFrozen( false ); + // FIXME: say "fight" + } + } + else + { + /// Check if time limit has been reached + if ( gameLocal.mpGame.TimeLimitHit() ) + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + gameLocal.mpGame.PrintMessageEvent( -1, MSG_TIMELIMIT ); + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + else + { + /// Teams are tied and time limit was hit - go to sudden death! + fragLimitTimeout = 0; + NewState( SUDDENDEATH ); + } + } + } + + break; + } + + case SUDDENDEATH: + { + int marineTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_MARINE ); + int stroggTeamScore = gameLocal.mpGame.GetScoreForTeam( TEAM_STROGG ); + + if( marineTeamScore > stroggTeamScore ) + { + /// Marines win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_MARINE ); + } + else if( marineTeamScore < stroggTeamScore ) + { + /// Strogg win! + gameLocal.mpGame.OnDeadZoneTeamVictory( TEAM_STROGG ); + } + + break; + } + } +} + +/* +================ +riDZGameState::SetDZState +================ +*/ +void riDZGameState::SetDZState( int dz, dzState_t newState ) { + assert( gameLocal.isServer && ( dz >= 0 && dz < TEAM_MAX ) && type == GS_DZ ); + + dzStatus[ dz ].state = newState; +} + +/* +================ +riDZGameState::operator== +================ +*/ +bool riDZGameState::operator==( const riDZGameState& rhs ) const { + if( (rvGameState&)(*this) != (rvGameState&)rhs ) { + return false; + } + + for( int i = 0; i < TEAM_MAX; i++ ) { + if( dzStatus[ i ] != rhs.dzStatus[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +riDZGameState::operator= +================ +*/ +riDZGameState& riDZGameState::operator=( const riDZGameState& rhs ) { + (rvGameState&)(*this) = (rvGameState&)rhs; + + for( int i = 0; i < TEAM_MAX; i++ ) { + dzStatus[ i ] = rhs.dzStatus[ i ]; + } + + return (*this); +} + diff --git a/src/mpgame/mp/GameState.h b/src/mpgame/mp/GameState.h new file mode 100644 index 0000000..71c597a --- /dev/null +++ b/src/mpgame/mp/GameState.h @@ -0,0 +1,507 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// GameState.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAMESTATE_H__ +#define __GAMESTATE_H__ + +#include "../Game_local.h" +#include "../MultiplayerGame.h" +#include "Tourney.h" + +/* +=============================================================================== + +rvGameState + +Game state info common for all gametypes + +=============================================================================== +*/ + +typedef enum { + GS_BASE, + GS_DM, + GS_TEAMDM, + GS_CTF, + GS_TOURNEY, + GS_DZ +} gameStateType_t; + +typedef enum { + INACTIVE = 0, // not running + WARMUP, // warming up + COUNTDOWN, // post warmup pre-game + GAMEON, // game is on + SUDDENDEATH, // game is on but in sudden death, first frag wins + GAMEREVIEW, // game is over, scoreboard is up. we wait si_gameReviewPause seconds (which has a min value) + NEXTGAME, + STATE_COUNT +} mpGameState_t; + +class rvGameState { +public: + + rvGameState( bool allocPrevious = true ); + virtual ~rvGameState( void ); + + // clientNum == -1 except for SendInitialState + // when clientNum >= 0, always send the state + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual bool BaseUnpackState( const idBitMsg& inMsg ); // validates the gametype class + + virtual void GameStateChanged( void ); + + virtual void Run( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + virtual void Clear( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + + mpGameState_t GetMPGameState( void ) { return currentState; } + + mpGameState_t GetNextMPGameState( void ) { return nextState; } + int GetNextMPGameStateTime( void ) { return nextStateTime; } + void SetNextMPGameState( mpGameState_t newState ) { nextState = newState; } + void SetNextMPGameStateTime( int time ) { nextStateTime = time; } + + bool operator==( const rvGameState& rhs ) const; + bool operator!=( const rvGameState& rhs ) const; + rvGameState& operator=( const rvGameState& rhs ); + + void WriteNetworkInfo( idFile *file, int clientNum ); + void ReadNetworkInfo( idFile *file, int clientNum ); + + void SpawnDeadZonePowerup(); +protected: + static gameStateType_t type; + rvGameState* previousGameState; + bool trackPrevious; + + mpGameState_t currentState; + mpGameState_t nextState; + int nextStateTime; + + int fragLimitTimeout; +}; + +/* +=============================================================================== + +rvDMGameState + +Game state info for DM + +=============================================================================== +*/ +class rvDMGameState : public rvGameState { +public: + rvDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvTeamDMGameState + +Game state info for Team DM + +=============================================================================== +*/ +class rvTeamDMGameState : public rvGameState { +public: + rvTeamDMGameState( bool allocPrevious = true ); + + virtual void Run( void ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); + +private: + static gameStateType_t type; +}; + +/* +=============================================================================== + +rvCTFGameState + +Game state info for CTF + +=============================================================================== +*/ + +// assault point state for CTF +enum apState_t { + AS_MARINE, + AS_STROGG, + AS_NEUTRAL +}; + +// current flag state for CTF +enum flagState_t { + FS_AT_BASE = 0, + FS_DROPPED, + FS_TAKEN, + // for one flag CTF + FS_TAKEN_STROGG, // taken by strogg team + FS_TAKEN_MARINE // taken by marine team +}; + +struct flagStatus_t { + flagState_t state; + int clientNum; +}; + +class rvCTFGameState : public rvGameState { +public: + rvCTFGameState( bool allocPrevious = true ); + virtual void Clear( void ); + + + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetAPOwner( int ap, int owner ); + int GetAPOwner( int ap ); + + void SetFlagState( int flag, flagState_t newState ); + flagState_t GetFlagState( int flag ); + int GetFlagCarrier( int flag ); + + void SetFlagCarrier( int flag, int clientNum ); + + bool operator==( const rvCTFGameState& rhs ) const; + rvCTFGameState& operator=( const rvCTFGameState& rhs ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + flagStatus_t flagStatus[ TEAM_MAX ]; + apState_t apState[ MAX_AP ]; + + static gameStateType_t type; +}; + +ID_INLINE void rvCTFGameState::SetAPOwner( int ap, int owner ) { + assert( (ap >= 0 && ap < MAX_AP) && (owner >= 0 && owner < TEAM_MAX) ); + + apState[ ap ] = (apState_t)owner; +} + +ID_INLINE int rvCTFGameState::GetAPOwner( int ap ) { + return apState[ ap ]; +} + +ID_INLINE flagState_t rvCTFGameState::GetFlagState( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].state; +} + +ID_INLINE int rvCTFGameState::GetFlagCarrier( int flag ) { + assert( flag >= 0 && flag < TEAM_MAX ); + + return flagStatus[ flag ].clientNum; +} + +ID_INLINE bool operator==( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const flagStatus_t& lhs, const flagStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} + +/* +=============================================================================== + +rvTourneyGameState + +Game state info for tourney + +=============================================================================== +*/ + +enum tourneyState_t { + TS_INVALID = 0, + TS_PREMATCH, + TS_MATCH, + TS_END_GAME +}; + +class rvTourneyGameState : public rvGameState { +public: + rvTourneyGameState( bool allocPrevious = true ); + + virtual void Clear( void ); + + virtual void Run( void ); + + virtual void Reset( void ); + + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void NewState( mpGameState_t newState ); + + virtual void ClientDisconnect( idPlayer* player ); + virtual void Spectate( idPlayer* player ); + + void RemovePlayer( idPlayer* player ); + + bool operator==( const rvTourneyGameState& rhs ) const; + rvTourneyGameState& operator=( const rvTourneyGameState& rhs ); + + int GetNumArenas( void ) const; + bool HasBye( idPlayer* player ); + + int GetMaxRound( void ) const; + int GetRound( void ) const; + int GetStartingRound( void ) const; + + idPlayer** GetArenaPlayers( int arena ); + + rvTourneyArena& GetArena( int arena ); + + const char* GetRoundDescription( void ); + int GetNextActiveArena( int arena ); + int GetPrevActiveArena( int arena ); + + void SpectateCycleNext( idPlayer* player ); + void SpectateCyclePrev( idPlayer* player ); + int GetTourneyCount( void ); + void SetTourneyCount( int count ) { tourneyCount = count; } + + void UpdateTourneyBrackets( void ); + + void UpdateTourneyHistory( int round ); + int FirstAvailableArena( void ); + + arenaOutcome_t* GetArenaOutcome( int arena ); + + virtual bool IsType( gameStateType_t type ) const; + static gameStateType_t GetClassType( void ); +private: + void SetupInitialBrackets( void ); + + tourneyState_t tourneyState; + + // startingRound - The starting round, with the current # of players + int startingRound; + // round - current round ( 1-indexed ) + int round; + // maxRound - the most rounds we'll run (based on MAX_ARENAS) + int maxRound; + // arenas - current brackets, an extra arena to serve as the waiting room + rvTourneyArena arenas[ MAX_ARENAS + 1 ]; + // tourneyHistory - past winners + arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ]; + // byeArena - the arena that natural (non disconnection) bye players go in + int byeArena; + // packTourneyHistory - whether or not we should transmit tourney history to one client through SendState + bool packTourneyHistory; + // forceTourneyHistory - if true will sync down tourney history for all clients + bool forceTourneyHistory; + // tourneyCount - how many tourneys to run per map + int tourneyCount; + // roundTimeout - a delay between all arenas finishing and starting the next round + int roundTimeout; + + static gameStateType_t type; +}; + +ID_INLINE int rvTourneyGameState::GetMaxRound( void ) const { + assert( type == GS_TOURNEY ); + return maxRound; +} + +ID_INLINE int rvTourneyGameState::GetRound( void ) const { + assert( type == GS_TOURNEY ); + return round; +} + +ID_INLINE int rvTourneyGameState::GetStartingRound( void ) const { + assert( type == GS_TOURNEY ); + return startingRound; +} + +ID_INLINE idPlayer** rvTourneyGameState::GetArenaPlayers( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ].GetPlayers(); +} + +ID_INLINE rvTourneyArena& rvTourneyGameState::GetArena( int arena ) { + assert( type == GS_TOURNEY ); + return arenas[ arena ]; +} + +ID_INLINE const char* rvTourneyGameState::GetRoundDescription( void ) { + assert( type == GS_TOURNEY ); + + if( round == maxRound ) { + return common->GetLocalizedString( "#str_107720" ); + } else if( round == maxRound - 1 ) { + return common->GetLocalizedString( "#str_107719" ); + } else if( round == maxRound - 2 ) { + return common->GetLocalizedString( "#str_107718" ); + } else if( round == maxRound - 3 ) { + return common->GetLocalizedString( "#str_107717" ); + } else { + // shouldn't happen in production + return va( "ROUND %d", round ); + } +} + +ID_INLINE int rvTourneyGameState::GetTourneyCount( void ) { + assert( type == GS_TOURNEY ); + return tourneyCount; +} + +/* +=============================================================================== + +riDZGameState + +Game state info for Dead Zone + +=============================================================================== +*/ + +// current control zone state +enum dzState_t { + DZ_NONE = 0, + DZ_TAKEN, + DZ_LOST, + DZ_DEADLOCK, + DZ_MARINES_TAKEN, + DZ_MARINES_LOST, + DZ_STROGG_TAKEN, + DZ_STROGG_LOST, + DZ_MARINE_TO_STROGG, + DZ_STROGG_TO_MARINE, + DZ_MARINE_DEADLOCK, + DZ_STROGG_DEADLOCK, + DZ_MARINE_REGAIN, + DZ_STROGG_REGAIN +}; + +struct dzStatus_t { + dzState_t state; + int clientNum; +}; + +class riDZGameState : public rvGameState { +public: + riDZGameState( bool allocPrevious = true ); + ~riDZGameState( void ); + virtual void Clear( void ); + + + virtual void SendState( const idMessageSender &sender, int clientNum = -1 ); + virtual void ReceiveState( const idBitMsg& msg ); + + virtual void SendInitialState( const idMessageSender &sender, int clientNum ); + + virtual void PackState( idBitMsg& outMsg ); + virtual void WriteState( idBitMsg &msg ); + + virtual void UnpackState( const idBitMsg& inMsg ); + + virtual void GameStateChanged( void ); + virtual void Run( void ); + + void SetDZState( int dz, dzState_t newState ); + dzState_t GetDZState( int dz ); + + bool operator==( const riDZGameState& rhs ) const; + riDZGameState& operator=( const riDZGameState& rhs ); + + int dzTriggerEnt; + int dzShaderParm; + +private: + dzStatus_t dzStatus[ TEAM_MAX ]; + + void ControlZoneStateChanged( int team ); +}; + + +ID_INLINE dzState_t riDZGameState::GetDZState( int dz ) { + assert( dz >= 0 && dz < TEAM_MAX ); + + return dzStatus[ dz ].state; +} + +ID_INLINE bool operator==( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state == rhs.state) && (lhs.clientNum == rhs.clientNum); +} + +ID_INLINE bool operator!=( const dzStatus_t& lhs, const dzStatus_t rhs ) { + return (lhs.state != rhs.state) || (lhs.clientNum != rhs.clientNum); +} +#endif diff --git a/src/mpgame/mp/Tourney.cpp b/src/mpgame/mp/Tourney.cpp new file mode 100644 index 0000000..a55c899 --- /dev/null +++ b/src/mpgame/mp/Tourney.cpp @@ -0,0 +1,1045 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Tourney.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "Tourney.h" + + +/* +=============================================================================== + +rvTourneyArena + +=============================================================================== +*/ + +/* +================ +rvTourneyArena::rvTourneyArena +================ +*/ +rvTourneyArena::rvTourneyArena() { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; +} + +/* +================ +rvTourneyArena::AddPlayer +================ +*/ +void rvTourneyArena::AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ) { + players[ 0 ] = playerOne; + players[ 1 ] = playerTwo; + if( playerOne ) { + playerOne->SetTourneyStatus( PTS_PLAYING ); + } + + if( playerTwo ) { + playerTwo->SetTourneyStatus( PTS_PLAYING ); + } +} + +/* +================ +rvTourneyArena::ClearPlayers +Clears player references if clearPlayer is NULL (client-side) +Clears specific player if clearPlayer is not NULL (server-side) +================ +*/ +void rvTourneyArena::ClearPlayers( idPlayer* clearPlayer /* = NULL */ ) { + if( gameLocal.isServer ) { + if( clearPlayer ) { + assert( clearPlayer == players[ 0 ] || clearPlayer == players[ 1 ] ); + if( clearPlayer == players[ 0 ] ) { + players[ 0 ] = NULL; + } else { + players[ 1 ] = NULL; + } + } + return; + } else { + players[ 0 ] = NULL; + players[ 1 ] = NULL; + } +} + +/* +================ +rvTourneyArena::Ready +================ +*/ +void rvTourneyArena::Ready( void ) { + arenaState = AS_WARMUP; + nextStateTime = gameLocal.time + ( gameLocal.serverInfo.GetInt( "si_countDown" ) * 1000 ) + 5000; + + matchStartTime = 0; + + if( !players[ 0 ] || !players[ 1 ] ) { + // if we don't have both players available, bye this arena + NewState( AS_DONE ); + } +} + +/* +================ +rvTourneyArena::Clear +Clears player list and state, but not round # or arena ID +================ +*/ +void rvTourneyArena::Clear( bool respawnPlayers ) { + if( gameLocal.isServer && respawnPlayers ) { + if( players[ 0 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 0 ]->ServerSpectate( true ); + } + + if( players[ 1 ] ) { + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + players[ 1 ]->ServerSpectate( true ); + } + + // This arena is being cleared so we must also clear out any spectators + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + } + + players[ 0 ] = NULL; + players[ 1 ] = NULL; + winner = NULL; + nextStateTime = 0; + fragLimitTimeout = 0; + matchStartTime = 0; + SetState( AS_INACTIVE ); +} + +/* +================ +rvTourneyArena::GetLeader +Returns winning player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLeader( void ) { + if( players[ 0 ] == NULL || players[ 1 ] == NULL ) { + if( players[ 0 ] ) { + return players[ 0 ]; + } else if( players[ 1 ] ) { + return players[ 1 ]; + } + + return NULL; + } + + int playerOneScore = gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ); + int playerTwoScore = gameLocal.mpGame.GetTeamScore( players[ 1 ]->entityNumber ); + + if ( playerOneScore == playerTwoScore ) { + return NULL; + } + + return ( playerOneScore > playerTwoScore ? players[ 0 ] : players[ 1 ] ); +} + +/* +================ +rvTourneyArena::GetLoser +Returns losing player, or NULL if there's a tie/or its undefined +================ +*/ +idPlayer* rvTourneyArena::GetLoser( void ) { + if( winner == NULL ) { + return NULL; + } else { + return ( winner == players[ 0 ] ? players[ 1 ] : players[ 0 ] ); + } +} + +/* +================ +rvTourneyArena::UpdateState +Updates this arena's state +================ +*/ +void rvTourneyArena::UpdateState( void ) { + if( (players[ 0 ] == NULL || players[ 1 ] == NULL) && arenaState != AS_DONE ) { + gameLocal.Warning( "rvTourneyArena::UpdateState() - UpdateState() called on non-full and non-done arena\n" ); + NewState( AS_DONE ); + return; + } + + switch( arenaState ) { + case AS_DONE: { + // not both players will neccesarily be valid here (if a player wins the arena then his opponent disconnects) + + break; + } + case AS_WARMUP: { + if( gameLocal.time > nextStateTime ) { + SetState( AS_ROUND ); + + // allow damage in warmup + //players[ 0 ]->fl.takedamage = true; + //players[ 1 ]->fl.takedamage = true; + // respawn the players + //players[ 0 ]->ServerSpectate( false ); + //players[ 1 ]->ServerSpectate( false ); + // respawn items + gameLocal.mpGame.EnableDamage( true ); + gameLocal.LocalMapRestart( arenaID ); + + gameLocal.mpGame.SetPlayerTeamScore( players[ 0 ], 0 ); + gameLocal.mpGame.SetPlayerTeamScore( players[ 1 ], 0 ); + matchStartTime = gameLocal.time; + } + + break; + } + + case AS_ROUND: { + if( GetLeader() && gameLocal.serverInfo.GetInt( "si_fragLimit" ) > 0 && gameLocal.mpGame.GetTeamScore( GetLeader()->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + if( fragLimitTimeout && fragLimitTimeout < gameLocal.time ) { + NewState( AS_DONE ); + } else if ( !fragLimitTimeout ) { + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + } else if( fragLimitTimeout ) { + if( !GetLeader() && gameLocal.mpGame.GetTeamScore( players[ 0 ]->entityNumber ) >= gameLocal.serverInfo.GetInt( "si_fraglimit" ) ) { + // players tied at fraglimit + NewState( AS_SUDDEN_DEATH ); + } else { + // player hit fraglimit, but then killed himself and dropped below fraglimit, return to normal play + fragLimitTimeout = 0; + } + + //back to normal play? + } else if ( TimeLimitHit() ) { + // only send to clients in this arena, the times may be getting out of sync between the arenas during a round ( #13196 ) + int i; + for ( i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + if ( !player ) { + continue; + } + if ( player->GetArena() == arenaID ) { + gameLocal.mpGame.PrintMessageEvent( i, MSG_TIMELIMIT ); + } + } + if( GetLeader() == NULL ) { + // if tied at timelimit hit, goto sudden death + NewState( AS_SUDDEN_DEATH ); + } else { + // or just end the game + NewState( AS_DONE ); + } + } + break; + } + case AS_SUDDEN_DEATH: { + if ( GetLeader() ) { + if ( !fragLimitTimeout ) { + common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", GetLeader()->entityNumber ); + fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY; + } + if ( gameLocal.time > fragLimitTimeout ) { + NewState( AS_DONE ); + gameLocal.mpGame.PrintMessageEvent( -1, MSG_FRAGLIMIT, GetLeader()->entityNumber ); + } + } else if ( fragLimitTimeout ) { + gameLocal.mpGame.PrintMessageEvent( -1, MSG_HOLYSHIT ); + fragLimitTimeout = 0; + } + break; + } + } +} + +/* +================ +rvTourneyArena::NewState +================ +*/ +void rvTourneyArena::NewState( arenaState_t newState ) { + switch( newState ) { + case AS_DONE: { + winner = GetLeader(); + assert( winner ); + gameLocal.Printf( "rvTourneyArena::UpdateState() - %s has won this arena\n", GetLeader()->GetUserInfo()->GetString( "ui_name" ) ); + + winner->SetTourneyStatus( PTS_ADVANCED ); + + if( GetLoser() ) { + GetLoser()->SetTourneyStatus( PTS_ELIMINATED ); + } + + + if( players[ 0 ] ) { + players[ 0 ]->ServerSpectate( true ); + players[ 0 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + if( players[ 1 ] ) { + players[ 1 ]->ServerSpectate( true ); + players[ 1 ]->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + + // when we're done, put anyone who was spectating into another arena + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + + if( player == NULL ) { + continue; + } + + if( player->GetArena() == arenaID ) { + player->JoinInstance( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetNextActiveArena( arenaID ) ); + } + } + + matchStartTime = 0; + break; + } + case AS_SUDDEN_DEATH: { + fragLimitTimeout = 0; + + // respawn the arena + players[ 0 ]->ServerSpectate( false ); + players[ 1 ]->ServerSpectate( false ); + gameLocal.LocalMapRestart( arenaID ); + break; + } + default: { + gameLocal.Error( "rvTourneyArena::NewState() - Unknown state '%d'\n", newState ); + return; + } + } + SetState( newState ); +} + +/* +================ +rvTourneyArena::RemovePlayer +The specified client is being removed from the server +================ +*/ +void rvTourneyArena::RemovePlayer( idPlayer* player ) { + // when we call Clear() the arena will clean up (set player instances to 0, spectate them, etc) + // we don't want it to do this for the disconnecting player, since he may not be entirely valid + // anymore. If we NULL out the disconnecting player, Clear() will properly reset the remaining player + // but will leave the volatile disconnecting player's data lone. + + bool playerInArena = false; + if( player == players[ 0 ] ) { + players[ 0 ] = NULL; + playerInArena = true; + } + + if( player == players[ 1 ] ) { + players[ 1 ] = NULL; + playerInArena = true; + } + + if( player == winner ) { + winner = NULL; + } + + if( !playerInArena ) { + // occasionally happens if a client has dropped ( the bystander maybe ) + common->Warning( "rvTourneyArena::RemovePlayer() - Called on player who is not in arena '%d'\n", arenaID ); + } +} + +/* +================ +rvTourneyArena::PackState +================ +*/ +void rvTourneyArena::PackState( idBitMsg& outMsg ) { + if ( players[ 0 ] ) { + outMsg.WriteChar( players[ 0 ]->entityNumber ); + } else { + outMsg.WriteChar( -1 ); + } + + if ( players[ 1 ] ) { + outMsg.WriteChar( players[ 1 ]->entityNumber ); + } else { + outMsg.WriteChar( -1 ); + } + + if ( winner ) { + outMsg.WriteChar( winner->entityNumber ); + } else { + outMsg.WriteChar( -1 ); + } + + outMsg.WriteByte( arenaState ); + outMsg.WriteLong( nextStateTime ); + outMsg.WriteLong( fragLimitTimeout ); + outMsg.WriteLong( matchStartTime ); +} + +/* +================ +rvTourneyArena::UnpackState +================ +*/ +void rvTourneyArena::UnpackState( const idBitMsg& inMsg ) { + int playerOneNum = inMsg.ReadChar(); + int playerTwoNum = inMsg.ReadChar(); + int winnerNum = inMsg.ReadChar(); + + if ( playerOneNum >= 0 && playerOneNum < MAX_CLIENTS ) { + players[ 0 ] = (idPlayer*)gameLocal.entities[ playerOneNum ]; + } else { + players[ 0 ] = NULL; + } + + if ( playerTwoNum >= 0 && playerTwoNum < MAX_CLIENTS ) { + players[ 1 ] = (idPlayer*)gameLocal.entities[ playerTwoNum ]; + } else { + players[ 1 ] = NULL; + } + + if ( winnerNum >= 0 && winnerNum < MAX_CLIENTS ) { + winner = (idPlayer*)gameLocal.entities[ winnerNum ]; + } else { + winner = NULL; + } + + arenaState = (arenaState_t)inMsg.ReadByte(); + nextStateTime = inMsg.ReadLong(); + fragLimitTimeout = inMsg.ReadLong(); + matchStartTime = inMsg.ReadLong(); +} + +/* +================ +rvTourneyArena::SetState +Set's this arena's state - client side only based on UpdateState() results from server +================ +*/ +void rvTourneyArena::SetState( arenaState_t newState ) { + arenaState = newState; +} + +/* +================ +rvTourneyArena::SetNextStateTime +================ +*/ +void rvTourneyArena::SetNextStateTime( int time ) { + nextStateTime = time; +} + +/* +================ +rvTourneyArena::GetNextStateTime +================ +*/ +int rvTourneyArena::GetNextStateTime( void ) { + return nextStateTime; +} + + +/* +================ +rvTourneyArena::GetState +================ +*/ +arenaState_t rvTourneyArena::GetState( void ) const { + return arenaState; +} + +/* +================ +rvTourneyArena::GetPlayerName +================ +*/ +const char* rvTourneyArena::GetPlayerName( int player ) { + assert( player >= 0 && player < 2 ); + + if( players[ player ] ) { + return players[ player ]->GetUserInfo()->GetString( "ui_name" ); + } else { + return NULL; + } +} + +/* +================ +rvTourneyArena::GetPlayerScore +================ +*/ +int rvTourneyArena::GetPlayerScore( int player ) { + assert( player >= 0 && player < 2 ); + + + if( players[ player ] ) { + return gameLocal.mpGame.GetTeamScore( players[ player ] ); + } else { + return 0; + } +} + +/* +================ +rvTourneyArena::TimeLimitHit +================ +*/ +bool rvTourneyArena::TimeLimitHit( void ) { + int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" ); + if ( timeLimit ) { + if ( gameLocal.time >= matchStartTime + timeLimit * 60000 ) { + return true; + } + } + return false; +} + +/* +=============================================================================== + +rvTourneyGUI + +=============================================================================== +*/ + +rvTourneyGUI::rvTourneyGUI() { + tourneyGUI = NULL; + tourneyScoreboard = NULL; +} + +void rvTourneyGUI::SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ) { + tourneyGUI = newTourneyGUI; + tourneyScoreboard = newTourneyScoreboard; +} + +void rvTourneyGUI::RoundStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "roundTransitionIn" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::RoundStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "roundTransitionIn" ); + + // set bye player for new round - the actual byePlayer may not have changed, so the gamestate won't move him over +// UpdateByePlayer(); +} + +void rvTourneyGUI::ArenaStart( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyGUI" ); + return; + } + + //arenaInit sets names to white and scores to orange. needs values "gui::round" , "gui::bracket" and "gui::empty" + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( arena ); + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyGUI->SetStateBool( "empty", true ); + } else { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaStart() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +} + +void rvTourneyGUI::ArenaDone( int arena ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& tourneyArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + if( tourneyArena.GetPlayers()[ 0 ] == NULL || tourneyArena.GetPlayers()[ 1 ] == NULL ) { + // we don't transition bye arenas to done + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + idPlayer* winner = tourneyArena.GetWinner(); + idPlayer** players = tourneyArena.GetPlayers(); + + if( winner == NULL ) { + common->Error( "rvTourneyGUI::ArenaDone() - Called on arena '%d' which is not done!\n", arena ); + return; + } + + if( winner != players[ 0 ] && winner != players[ 1 ] ) { + common->Error( "rvTourneyGUI::ArenaDone() - Arena '%d' is reporting a winner that is not in the arena!\n", arena ); + return; + } + + tourneyGUI->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaDone" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaDone() - Invalid tourneyScoreboard" ); + return; + } + + // arenaDone transitions the blue flash/fade and names of winner/loser. needs values "gui::round" , "gui::bracket" and "gui::winner" (winner is a 1 or 2 value, 1 top 2 bottom) + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + tourneyScoreboard->SetStateInt( "winner", winner == players[ 0 ] ? 1 : 2 ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); +} + +void rvTourneyGUI::ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyGUI" ); + return; + } + + rvTourneyArena& thisArena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ); + + // don't select empty arenas + if( thisArena.GetPlayers()[ 0 ] == NULL || thisArena.GetPlayers()[ 1 ] == NULL ) { + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyGUI->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + tourneyGUI->SetStateInt( "sel", (int)highlightType ); + tourneyGUI->StateChanged( gameLocal.time ); + tourneyGUI->HandleNamedEvent( "arenaFocus" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::ArenaSelect() - Invalid tourneyScoreboard" ); + return; + } + + //arenaFocus sets the green background on bracket, player 1 or player2 using value "gui::sel". ( 0 = bracket, 1 = player1, 2 = player2) arenaFocus also needs "gui::round" and "gui::bracket" values. + tourneyScoreboard->SetStateInt( "round", ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound() ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + tourneyScoreboard->SetStateInt( "sel", (int)highlightType ); + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaFocus" ); +} + +void rvTourneyGUI::UpdateScores( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + + if( round < 0 ) { + // not now + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyGUI->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyGUI->SetStateBool( "empty", false ); + } + } + + tourneyGUI->StateChanged( gameLocal.time ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateScore() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 0; i < MAX_ARENAS; i++ ) { + rvTourneyArena& arena = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( i ); + idPlayer** players = arena.GetPlayers(); + arenaOutcome_t* arenaOutcome = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaOutcome( i ); + + if( arena.GetState() != AS_DONE && arena.GetState() != AS_INACTIVE ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + + tourneyScoreboard->SetStateBool( "empty", false ); + } else if( arenaOutcome ) { + bool isBye = ( (*(arenaOutcome->playerOne)) == '\0' && arenaOutcome->playerOneNum == -1) || + ( (*(arenaOutcome->playerTwo)) == '\0' && arenaOutcome->playerTwoNum == -1); + + if( *(arenaOutcome->playerOne) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerOne ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerOneScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, i + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, i + 1 ), (players[ 0 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + } + + if( *(arenaOutcome->playerTwo) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), arenaOutcome->playerTwo ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), isBye ? "" : va( "%d", arenaOutcome->playerTwoScore ) ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, i + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, i + 1 ), (players[ 1 ] && !isBye) ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + + tourneyScoreboard->SetStateBool( "empty", false ); + } + } + + tourneyScoreboard->StateChanged( gameLocal.time ); +} + +void rvTourneyGUI::PreTourney( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::PreTourney() - Invalid tourneyGUI" ); + return; + } + + tourneyGUI->HandleNamedEvent( "warmupTransitionIn" ); +} + +void rvTourneyGUI::TourneyStart( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyGUI" ); + return; + } + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + int maxRound = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetMaxRound(); + + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "warmupTransitionOut" ); + + // setup and clear the scoreboard + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::TourneyStart() - Invalid tourneyScoreboard" ); + return; + } + + for( int i = 1; i <= maxRound; i++ ) { + for( int j = 0; j < MAX_ARENAS; j++ ) { + tourneyScoreboard->SetStateInt( "round", i ); + tourneyScoreboard->SetStateInt( "bracket", j + 1 ); + + if( i < round ) { + // we aren't using these brackets + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } else if ( i == round ) { + // this is our initial round + idPlayer** players = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArenaPlayers( j ); + + if( players[ 0 ] == NULL && players[ 1 ] == NULL ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? players[ 0 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), players[ 0 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 0 ] ) ) : "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? players[ 1 ]->GetUserInfo()->GetString( "ui_name" ) : common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), players[ 1 ] ? va( "%d", gameLocal.mpGame.GetTeamScore( players[ 1 ] ) ) : "" ); + } + } else { + // this is our future bracket + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", i, j + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", i, j + 1 ), "" ); + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + } + } + + // we might have overwritten a bye player, so update it again +// UpdateByePlayer(); +} + +/*void rvTourneyGUI::UpdateByePlayer( void ) { + if( tourneyGUI == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyGUI" ); + return; + } + + int arena; + for( arena = 0; arena < MAX_ARENAS; arena++ ) { + if( ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( arena ).GetState() == AS_INACTIVE ) { + break; + } + } + + if( arena >= MAX_ARENAS ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Bye player with no inactive arenas!" ); + return; + } + + idPlayer* byePlayer = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetByePlayer(); + + int round = ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetRound(); + tourneyGUI->SetStateInt( "round", round ); + tourneyGUI->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyGUI->SetStateBool( "empty", false ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyGUI->SetStateBool( "empty", true ); + tourneyGUI->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyGUI->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyGUI->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + + tourneyGUI->StateChanged( gameLocal.time ); + + tourneyGUI->HandleNamedEvent( "arenaInit" ); + + if( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::UpdateByePlayer() - Invalid tourneyScoreboard" ); + return; + } + + tourneyScoreboard->SetStateInt( "round", round ); + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + if( byePlayer ) { + tourneyScoreboard->SetStateBool( "empty", false ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), byePlayer->GetUserInfo()->GetString( "ui_name" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + tourneyScoreboard->SetStateBool( "empty", true ); + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), "" ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } + + tourneyScoreboard->StateChanged( gameLocal.time ); + + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); +}*/ + +void rvTourneyGUI::SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ) { + if ( tourneyScoreboard == NULL ) { + common->Warning( "rvTourneyGUI::SetupTourneyHistory() - Invalid tourneyScoreboard" ); + return; + } + + if( startHistory <= 0 ) { + return; + } + + for ( int round = startHistory; round <= endHistory; round++ ) { + tourneyScoreboard->SetStateInt( "round", round ); + + for ( int arena = 0; arena < MAX_ARENAS / round; arena++ ) { + tourneyScoreboard->SetStateInt( "bracket", arena + 1 ); + + // whether we want to send arenaDone to this arena + // don't send for empty brackets or bye rounds + bool arenaNotDone = tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne) && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo); + + if( arenaNotDone ) { + tourneyScoreboard->SetStateBool( "empty", true ); + } else { + tourneyScoreboard->SetStateBool( "empty", false ); + + bool firstSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerOne)); + bool secondSlotBye = (tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 && !(*tourneyHistory[ round - 1 ][ arena ].playerTwo)); + + assert( !(firstSlotBye && secondSlotBye) ); + + if( firstSlotBye || secondSlotBye ) { + // this was a bye round + + if( secondSlotBye ) { + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + } else { + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateString( va( "score2_round%d_bracket%d", round, arena + 1 ), "" ); + + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), common->GetLocalizedString( "#str_107705" ) ); + tourneyScoreboard->SetStateString( va( "score1_round%d_bracket%d", round, arena + 1 ), "" ); + } + + arenaNotDone = true; + } else { + // regular round + if( tourneyHistory[ round - 1 ][ arena ].playerOneNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum ) ) { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOne ); + } else { + tourneyScoreboard->SetStateString( va( "name1_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerOneNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score1_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerOneScore ); + + if( tourneyHistory[ round - 1 ][ arena ].playerTwoNum == -1 || !gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum ) ) { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwo ); + } else { + tourneyScoreboard->SetStateString( va( "name2_round%d_bracket%d", round, arena + 1 ), gameLocal.GetUserInfo( tourneyHistory[ round - 1 ][ arena ].playerTwoNum )->GetString( "ui_name" ) ); + } + + tourneyScoreboard->SetStateInt( va( "score2_round%d_bracket%d", round, arena + 1 ), tourneyHistory[ round - 1 ][ arena ].playerTwoScore ); + + tourneyScoreboard->SetStateInt( "winner", tourneyHistory[ round - 1 ][ arena ].playerOneScore > tourneyHistory[ round - 1 ][ arena ].playerTwoScore ? 1 : 2 ); + } + } + tourneyScoreboard->StateChanged( gameLocal.time ); + tourneyScoreboard->HandleNamedEvent( "arenaInit" ); + if( !arenaNotDone ) { + tourneyScoreboard->HandleNamedEvent( "arenaDone" ); + } + } + } + + UpdateScores(); +} diff --git a/src/mpgame/mp/Tourney.h b/src/mpgame/mp/Tourney.h new file mode 100644 index 0000000..806d6aa --- /dev/null +++ b/src/mpgame/mp/Tourney.h @@ -0,0 +1,180 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Tourney.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __TOURNEY_H__ +#define __TOURNEY_H__ + +#include "../Game_local.h" + +enum arenaState_t { + AS_INACTIVE = 0, + AS_WARMUP, + AS_ROUND, + AS_SUDDEN_DEATH, + AS_DONE +}; + +#define MAX_TOURNEY_HISTORY_NAME_LEN 32 + +typedef struct arenaOutcome_s { + // for clients that have disconnected, copy their names for history purposes + char playerOne[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + char playerTwo[ MAX_TOURNEY_HISTORY_NAME_LEN ]; + + // for currently connected clients, use clientnum to get current name + int playerOneNum; + int playerTwoNum; + + int playerOneScore; + int playerTwoScore; +} arenaOutcome_t; + +// shouldn't exceed MAX_INSTANCES from idMultiplayerGame +const int MAX_ARENAS = 8; + +const int MAX_ROUNDS = 4; + +class rvTourneyArena { +public: + rvTourneyArena(); + + void AddPlayers( idPlayer* playerOne, idPlayer* playerTwo ); + void ClearPlayers( idPlayer* clearPlayer = NULL ); + void Clear( bool respawnPlayers = true ); + void Ready( void ); + + idPlayer* GetLeader( void ); + idPlayer* GetLoser( void ); + idPlayer* GetWinner( void ) { return winner; } + void UpdateState( void ); + void NewState( arenaState_t newState ); + + idPlayer** GetPlayers( void ); + + void SetArenaID( int id ); + int GetArenaID( void ) const; + + arenaState_t GetState( void ) const; + void SetState( arenaState_t newState ); + void SetNextStateTime( int time ); + int GetNextStateTime( void ); + int GetMatchStartTime( void ) { return matchStartTime; } + + void PackState( idBitMsg& outMsg ); + void UnpackState( const idBitMsg& inMsg ); + + void RemovePlayer( idPlayer* player ); + bool TimeLimitHit( void ); + bool IsPlaying( idPlayer* player ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE && ( player == players[ 0 ] || player == players[ 1 ] ) ); } + bool HasPlayer( idPlayer* player ) { return ( player == players[0] || player == players[1] ); } + bool IsPlaying( void ) { return ( arenaState != AS_INACTIVE && arenaState != AS_DONE ); } + + const char* GetPlayerName( int player ); + int GetPlayerScore( int player ); + int GetFraglimitTimeout( void ) { return fragLimitTimeout; } + bool operator==( const rvTourneyArena& rhs ) const; + bool operator!=( const rvTourneyArena& rhs ) const; + rvTourneyArena& operator=( const rvTourneyArena& rhs ); + +private: + // players - players in arena + idPlayer* players[ 2 ]; + // arenaID - this arena's ID + int arenaID; + // arenaState - state of the arena + arenaState_t arenaState; + // nextStateTime - transition time to next state + int nextStateTime; + // winner - the winner of the arena + idPlayer* winner; + // fragLimitTimeout - timeout to let death anims play + int fragLimitTimeout; + // matchStartTime - time arena started + int matchStartTime; +}; + +ID_INLINE idPlayer** rvTourneyArena::GetPlayers( void ) { + return players; +} + +ID_INLINE void rvTourneyArena::SetArenaID( int id ) { + arenaID = id; +} + +ID_INLINE int rvTourneyArena::GetArenaID( void ) const { + return arenaID; +} + +ID_INLINE bool rvTourneyArena::operator==( const rvTourneyArena& rhs ) const { + return ( arenaState == rhs.arenaState && players[ 0 ] == rhs.players[ 0 ] && players[ 1 ] == rhs.players[ 1 ] && nextStateTime == rhs.nextStateTime && arenaID == rhs.arenaID && fragLimitTimeout == rhs.fragLimitTimeout && matchStartTime == rhs.matchStartTime ); +} + +ID_INLINE bool rvTourneyArena::operator!=( const rvTourneyArena& rhs ) const { + return ( arenaState != rhs.arenaState || players[ 0 ] != rhs.players[ 0 ] || players[ 1 ] != rhs.players[ 1 ] || nextStateTime != rhs.nextStateTime || arenaID != rhs.arenaID || fragLimitTimeout != rhs.fragLimitTimeout || matchStartTime != rhs.matchStartTime ); +} + +ID_INLINE rvTourneyArena& rvTourneyArena::operator=( const rvTourneyArena& rhs ) { + players[ 0 ] = rhs.players[ 0 ]; + players[ 1 ] = rhs.players[ 1 ]; + arenaState = rhs.arenaState; + nextStateTime = rhs.nextStateTime; + arenaID = rhs.arenaID; + fragLimitTimeout = rhs.fragLimitTimeout; + matchStartTime = rhs.matchStartTime; + return (*this); +} + +typedef enum { + TGH_BRACKET, + TGH_PLAYER_ONE, + TGH_PLAYER_TWO +} tourneyGUIHighlight_t; + +class rvTourneyGUI { +public: + rvTourneyGUI(); + void SetupTourneyGUI( idUserInterface* newTourneyGUI, idUserInterface* newTourneyScoreboard ); + + void RoundStart( void ); + void ArenaStart( int arena ); + void ArenaDone( int arena ); + void ArenaSelect( int arena, tourneyGUIHighlight_t highlightType ); + void UpdateScores( void ); + void PreTourney( void ); + void TourneyStart( void ); + //void UpdateByePlayer( void ); + void SetupTourneyHistory( int startHistory, int endHistory, arenaOutcome_t tourneyHistory[ MAX_ROUNDS ][ MAX_ARENAS ] ); + +private: + idUserInterface* tourneyGUI; + idUserInterface* tourneyScoreboard; +}; + +#endif diff --git a/src/mpgame/mp/VoiceComms.cpp b/src/mpgame/mp/VoiceComms.cpp new file mode 100644 index 0000000..d239241 --- /dev/null +++ b/src/mpgame/mp/VoiceComms.cpp @@ -0,0 +1,220 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop +#include "../Game_local.h" + +// This equates to about 1 second +#define MAX_VOICE_PACKET_SIZE 382 + +idCVar si_voiceChat( "si_voiceChat", "1", CVAR_GAME | CVAR_BOOL | PC_CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_INFO, "enables or disables voice chat through the server" ); +idCVar g_voiceChatDebug( "g_voiceChatDebug", "0", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "display info on voicechat net traffic" ); + +void idMultiplayerGame::ReceiveAndForwardVoiceData( int clientNum, const idBitMsg &inMsg, int messageType ) { + assert( clientNum >= 0 && clientNum < MAX_CLIENTS ); + + idBitMsg outMsg; + int i; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 2]; + idPlayer * from; + + from = ( idPlayer * )gameLocal.entities[clientNum]; + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) || !from ) { + return; + } + + // Create a new packet with forwarded data + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_UNRELIABLE_MESSAGE_VOICEDATA_SERVER ); + outMsg.WriteByte( clientNum ); + outMsg.WriteData( inMsg.GetReadData(), inMsg.GetRemainingData() ); + + if( g_voiceChatDebug.GetInteger() & 2 ) { + common->Printf( "VC: Received %d bytes, forwarding...\n", inMsg.GetRemainingData() ); + } + + // Forward to appropriate parties + for( i = 0; i < gameLocal.numClients; i++ ) { + idPlayer* to = ( idPlayer * )gameLocal.entities[i]; + if( to && to->GetUserInfo() && to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( i != gameLocal.localClientNum && CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + gameLocal.SendUnreliableMessage( outMsg, to->entityNumber ); + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to client %d\n", to->entityNumber ); + } + } + else + { + if( g_voiceChatDebug.GetInteger() ) + { + common->Printf( " ... suppressed packet to client %d\n", to->entityNumber ); + } + } + } + } + } + } + +#ifdef _USE_VOICECHAT + // Listen servers need to manually call the receive function + if ( gameLocal.isListenServer ) { + // Skip over control byte + outMsg.ReadByte(); + + idPlayer* to = gameLocal.GetLocalPlayer(); + if( to->GetUserInfo()->GetBool( "s_voiceChatReceive" ) ) + { + if( CanTalk( from, to, !!( messageType & 1 ) ) ) + { + if( messageType & 2 ) + { + // If "from" is testing - then only send back to him + if( from == to ) + { + ReceiveAndPlayVoiceData( outMsg ); + } + } + else + { + if( to->AllowedVoiceDest( from->entityNumber ) ) + { + if( g_voiceChatDebug.GetInteger() & 2 ) + { + common->Printf( " ... to local client %d\n", gameLocal.localClientNum ); + } + ReceiveAndPlayVoiceData( outMsg ); + } + } + } + } + } +#endif +} + +bool idMultiplayerGame::CanTalk( idPlayer *from, idPlayer *to, bool echo ) { + if ( !to ) { + return false; + } + + if ( !from ) { + return false; + } + + if ( from->spectating != to->spectating ) { + return false; + } + + if ( to->IsPlayerMuted( from ) ) { + return false; + } + + if ( to->GetArena() != from->GetArena() ) { + return false; + } + + if ( gameLocal.IsTeamGame() && from->team != to->team ) { + return false; + } + + if ( from == to ) { + return echo; + } + + return true; +} + +#ifdef _USE_VOICECHAT + +void idMultiplayerGame::XmitVoiceData( void ) +{ + idBitMsg outMsg; + int count; + byte work; + byte buffer[MAX_VOICE_PACKET_SIZE]; + byte msgBuf[MAX_VOICE_PACKET_SIZE + 1]; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + + // Grab any new input and send up to the server + count = soundSystem->GetVoiceData( buffer, MAX_VOICE_PACKET_SIZE ); + if( count ) + { + outMsg.BeginWriting(); + outMsg.BeginReading(); + + work = GAME_RELIABLE_MESSAGE_VOICEDATA_CLIENT + cvarSystem->GetCVarBool( "s_voiceChatEcho" ) + ( cvarSystem->GetCVarInteger( "s_voiceChatTest" ) * 2 ); + outMsg.WriteByte( work ); + outMsg.WriteData( buffer, count ); + + // FIXME!!! FIXME!!! This should be unreliable - this is very bad + networkSystem->ClientSendReliableMessage( outMsg ); + + if( g_voiceChatDebug.GetInteger() & 1 ) + { + common->Printf( "VC: sent %d bytes at %d\n", count, gameLocal.time ); + } + } +} + +void idMultiplayerGame::ReceiveAndPlayVoiceData( const idBitMsg &inMsg ) +{ + int clientNum; + + if( !gameLocal.serverInfo.GetBool( "si_voiceChat" ) ) + { + return; + } + + clientNum = inMsg.ReadByte(); + soundSystem->PlayVoiceData( clientNum, inMsg.GetReadData(), inMsg.GetRemainingData() ); + if( g_voiceChatDebug.GetInteger() & 4 ) + { + common->Printf( "VC: Playing %d bytes\n", inMsg.GetRemainingData() ); + } +} + +#endif + +// end diff --git a/src/mpgame/mp/stats/StatEvent.cpp b/src/mpgame/mp/stats/StatEvent.cpp new file mode 100644 index 0000000..e1293d9 --- /dev/null +++ b/src/mpgame/mp/stats/StatEvent.cpp @@ -0,0 +1,355 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatEvent.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + + +rvStatHit::rvStatHit( int t, int p, int v, int w, bool countForAccuracy ) : rvStat( t ) { + playerClientNum = p; + victimClientNum = v; + weapon = w; + trackAccuracy = countForAccuracy; + type = ST_HIT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatHit::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS && weapon >= 0 && weapon < MAX_WEAPONS && trackAccuracy ) { + stats[ playerClientNum ].weaponHits[ weapon ]++; + } + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_railgun" ) ) { + + // Apparently it does the rail hit event before the rail fired event, so this is backwards for a reason + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_HIT ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_RAIL_HIT; + } + + if ( rvStatManager::lastRailShot[ playerClientNum ] < stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } else { + if ( rvStatManager::lastRailShot[ playerClientNum ] >= stats[ playerClientNum ].weaponShots[ weapon ] - 1 ) { + if ( (rvStatManager::lastRailShotHits[ playerClientNum ] % 2) == 0 ) { + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + ++rvStatManager::lastRailShotHits[ playerClientNum ]; + } else { + rvStatManager::lastRailShot[ playerClientNum ] = stats[ playerClientNum ].weaponShots[ weapon ]; + } + } + +/* + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 8000, 2, (rvStat**)lastHits ); + if( lastHits[0] ) { + if( !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[0])->weapon ) ), "weapon_railgun" ) ) { + + //Check to make sure we don't chain impressive awards. + if(lastHits[1] + && !idStr::Icmp(player->spawnArgs.GetString( va( "def_weapon%d", ((rvStatHit*)lastHits[1])->weapon ) ), "weapon_railgun" ) + && (lastHits[1]->GetTimeStamp() - lastHits[0]->GetTimeStamp()) < 4000) { + return; + + } + + if(gameLocal.time - lastHits[0]->GetTimeStamp() < 4000){ + statManager->GiveInGameAward( IGA_IMPRESSIVE, playerClientNum ); + } + } + } +*/ + } else if( !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", weapon ) ), "weapon_rocketlauncher" ) ) { + + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_ROCKET_FIRED ) { + rvStatManager::comboKillState[ playerClientNum ] = CKS_ROCKET_HIT; + } + + } +} + +rvStatKill::rvStatKill( int t, int p, int v, bool g, int mod ) : rvStat( t ) { + type = ST_KILL; + playerClientNum = p; + victimClientNum = v; + gibbed = g; + methodOfDeath = mod; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatKill::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum < 0 || playerClientNum >= MAX_CLIENTS || victimClientNum < 0 || victimClientNum >= MAX_CLIENTS ) { + return; + } + + idPlayer* player = (idPlayer*)gameLocal.entities[ playerClientNum ]; + idPlayer* victim = (idPlayer*)gameLocal.entities[ victimClientNum ]; + + // no award processing for suicides + if( victimClientNum == playerClientNum ) { + stats[ playerClientNum ].suicides++; + return; + } + + bool teamKill = false; + + // don't track team kills + if( !gameLocal.IsTeamGame() || player->team != victim->team ) { + stats[ playerClientNum ].kills++; + + if( methodOfDeath >= 0 && methodOfDeath < MAX_WEAPONS ) { + stats[ playerClientNum ].weaponKills[ methodOfDeath ]++; + } + } + + if( gameLocal.IsTeamGame() && player->team == victim->team ) { + teamKill = true; + } + + + // check for humiliation award + if( !teamKill && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_gauntlet" ) ) { + statManager->GiveInGameAward( IGA_HUMILIATION, playerClientNum ); + } + + rvStatKill* lastKills[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_KILL, gameLocal.time - 5000, 2, (rvStat**)lastKills ); + + // check for excellent award + if( !teamKill && lastKills[ 0 ] && lastKills[ 0 ]->GetTimeStamp() >= (gameLocal.time - 2000) && lastKills[ 0 ]->victimClientNum != playerClientNum && victimClientNum != playerClientNum ) { + statManager->GiveInGameAward( IGA_EXCELLENT, playerClientNum ); + } + + // check for rampage award + if( !teamKill && ( gibbed && victimClientNum != playerClientNum ) && + ( lastKills[ 0 ] && lastKills[ 0 ]->gibbed && lastKills[ 0 ]->victimClientNum != playerClientNum ) && + ( lastKills[ 1 ] && lastKills[ 1 ]->gibbed && lastKills[ 1 ]->victimClientNum != playerClientNum ) ) { + statManager->GiveInGameAward( IGA_RAMPAGE, playerClientNum ); + } + + + // check for combo kill award + if( victimClientNum != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", methodOfDeath ) ), "weapon_railgun" ) ) { + // the rail killing shot is the last hit, so look for the one past that + rvStatHit* lastHits[ 2 ] = { NULL, NULL }; + + statManager->GetLastClientStats( playerClientNum, ST_HIT, gameLocal.time - 3000, 2, (rvStat**)lastHits ); + + if( lastHits[ 1 ] && lastHits[ 1 ]->GetVictimClientNum() != playerClientNum && !idStr::Icmp( player->spawnArgs.GetString( va( "def_weapon%d", lastHits[ 1 ]->GetWeapon() ) ), "weapon_rocketlauncher" ) ) { + if ( rvStatManager::comboKillState[ playerClientNum ] == CKS_RAIL_HIT ) { + statManager->GiveInGameAward( IGA_COMBO_KILL, playerClientNum ); + } + } + } + + rvStatManager::comboKillState[ playerClientNum ] = CKS_NONE; + + + // check for defense award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + // defense is given for two conditions +// assert( gameLocal.mpGame.GetFlagEntity( TEAM_STROGG ) && gameLocal.mpGame.GetFlagEntity( TEAM_MARINE ) ); + assert( player->team >= 0 && player->team < TEAM_MAX ); + assert( gameLocal.mpGame.GetGameState()->IsType( rvCTFGameState::GetClassType() ) ); + + if ( gameLocal.mpGame.GetFlagEntity( player->team ) ) { + // defending your flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_AT_BASE || ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( player->team ) == FS_DROPPED ) { + if( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if you're close to flag and you kill an enemy + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if ( ( gameLocal.mpGame.GetFlagEntity( player->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin() ).LengthSqr() < 250000 ) { + // give award if enemy is close to flag and you kill them + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + + // defending your teammate carrying the enemy flag + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( gameLocal.mpGame.OpposingTeam( player->team ) ) == FS_TAKEN ) { + int clientNum = ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagCarrier( gameLocal.mpGame.OpposingTeam( player->team ) ); + + idPlayer* flagCarrier = NULL; + + if( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + flagCarrier = (idPlayer*)gameLocal.entities[ clientNum ]; + } + + if( flagCarrier ) { + if( (flagCarrier->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed enemy while close to the flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } else if( (flagCarrier->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 250000 ) { + // killed an enemy who was close to teh flag carrier + statManager->GiveInGameAward( IGA_DEFENSE, playerClientNum ); + } + } + } + } + + // check for holy shit award + if( gameLocal.IsFlagGameType() && player->team != victim->team && player != victim ) { + if( (player->team == TEAM_MARINE && victim->PowerUpActive( POWERUP_CTF_MARINEFLAG )) || + (player->team == TEAM_STROGG && victim->PowerUpActive( POWERUP_CTF_STROGGFLAG )) ) { + if( ((rvCTFGameState*)gameLocal.mpGame.GetGameState())->GetFlagState( victim->team ) == FS_AT_BASE ) { + + // fixme: something is broken with the mpgame state + if ( gameLocal.mpGame.GetFlagEntity( victim->team ) ) { + if( (gameLocal.mpGame.GetFlagEntity( victim->team )->GetPhysics()->GetOrigin() - victim->GetPhysics()->GetOrigin()).LengthSqr() < 40000 ) { + statManager->GiveInGameAward( IGA_HOLY_SHIT, playerClientNum ); + } + } + } + } + } +} + +/* +================ +rvStatDeath + +A player died +================ +*/ +rvStatDeath::rvStatDeath( int t, int p, int mod ) : rvStat( t ) { + type = ST_DEATH; + playerClientNum = p; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDeath::RegisterInGame( rvPlayerStat* stats ) { + stats[ playerClientNum ].deaths++; +} + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +rvStatDamageDealt::rvStatDamageDealt( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_DEALT; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageDealt::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageGiven += damage; + } +} + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +rvStatDamageTaken::rvStatDamageTaken( int t, int p, int w, int d ) : rvStat( t ) { + playerClientNum = p; + weapon = w; + damage = d; + type = ST_DAMAGE_TAKEN; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatDamageTaken::RegisterInGame( rvPlayerStat* stats ) { + if( playerClientNum >= 0 && playerClientNum < MAX_CLIENTS ) { + stats[ playerClientNum ].damageTaken += damage; + } +} + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +rvStatFlagDrop::rvStatFlagDrop( int t, int p, int a, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + attacker = a; + type = ST_CTF_FLAG_DROP; +} + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +rvStatFlagReturn::rvStatFlagReturn( int t, int p, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + type = ST_CTF_FLAG_RETURN; +} + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +rvStatFlagCapture::rvStatFlagCapture( int t, int p, int f, int tm ) : rvStatTeam( t, tm ) { + playerClientNum = p; + flagTeam = f; + type = ST_CTF_FLAG_CAPTURE; + RegisterInGame( statManager->GetPlayerStats() ); +} + +void rvStatFlagCapture::RegisterInGame( rvPlayerStat* stats ) { + statManager->GiveInGameAward( IGA_CAPTURE, playerClientNum ); + // figure out if we need to give an assist + + // see if someone carried the flag a bit, lost it, but it was capped + rvStatFlagDrop* flagDrop = (rvStatFlagDrop*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_DROP, gameLocal.time - 10000 ); + if( flagDrop && flagDrop->GetTeam() == team ) { + // only give the award if there was no flag return between the flag drop and the flag capture + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( flagTeam, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn == NULL ) { + statManager->GiveInGameAward( IGA_ASSIST, flagDrop->GetPlayerClientNum() ); + } + } + + // see if someone returned the flag, allowing a cap + rvStatFlagReturn* flagReturn = (rvStatFlagReturn*)statManager->GetLastTeamStat( team, ST_CTF_FLAG_RETURN, gameLocal.time - 10000 ); + if( flagReturn ) { + statManager->GiveInGameAward( IGA_ASSIST, flagReturn->GetPlayerClientNum() ); + } +} diff --git a/src/mpgame/mp/stats/StatEvent.h b/src/mpgame/mp/stats/StatEvent.h new file mode 100644 index 0000000..44cfdb3 --- /dev/null +++ b/src/mpgame/mp/stats/StatEvent.h @@ -0,0 +1,385 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatEvent.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATEVENT_H__ +#define __STATEVENT_H__ + +/* +=============================================================================== + +Multiplayer statistics events + +This file contains statistic event definitions. rvStatManager exposes +game-related functions (i.e. 'Kill', 'FlagCaptured') which create the +appropriate statistic event and add it to the statQueue in rvStatManager. + +The statQueue contains a complete picture of an MP game. It can be parsed to +calculate accuracies, award end-game awards, etc. + +Statistic events also have functionality to register in-game information for +on-the-fly statistics. + +=============================================================================== +*/ + +class rvPlayerStat; + +/* +================ +statType_t + +Type identifiers for RTTI of stat events, rvStatTeam-derived events must go +after ST_STAT_TEAM +================ +*/ +enum statType_t { + // not an actual event, undefined marker + ST_NONE = 0, + // rvStat derived + ST_BEGIN_GAME, + ST_END_GAME, + ST_CLIENT_CONNECT, + ST_HIT, + ST_KILL, + ST_DEATH, + ST_DAMAGE_DEALT, + ST_DAMAGE_TAKEN, + // not an actual event, team marker + ST_STAT_TEAM, + // rvStatTeam derived + ST_CTF_FLAG_CAPTURE, + ST_CTF_FLAG_DROP, + ST_CTF_FLAG_RETURN, + + ST_COUNT +}; + +/* +================ +rvStat + +An individual event we want to know about for stats. +================ +*/ +class rvStat { + + friend class rvStatAllocator; + +public: + + statType_t GetType( void ) const { return type; }; + int GetTimeStamp( void ) const { return timeStamp; }; + byte GetPlayerClientNum( void ) const { return playerClientNum; }; + +protected: + + // moved to protected to prevent allocating these on the normal heap. + // these should only be allocatd by rvStatAllocator. + rvStat( int t ) { timeStamp = t; playerClientNum = -1; type = ST_NONE; }; + virtual ~rvStat( void ) {}; + + virtual void RegisterInGame( rvPlayerStat* stats ) {}; + + // the entity number of the player associated with this statistic + byte playerClientNum; + + statType_t type; + int timeStamp; + +private: + // because of the way memory is handled for these we want the compiler + // on our side to find abuses. note that we aren't defining these. + // this change is propagated to all derived classes. + rvStat(); + rvStat( const rvStat &rhs ); + const rvStat &operator=( const rvStat &rhs ); +}; + +/* +================ +rvStatTeam + +A team-related rvStat +================ +*/ +class rvStatTeam : public rvStat { + + friend class rvStatAllocator; + +public: + virtual ~rvStatTeam( void ) {}; + + int GetTeam( void ) { return team; }; +protected: + + // see comment in rvStat + rvStatTeam( int t, int tm ) : rvStat( t ) { team = tm; }; + + byte team; + +private: + // see comment in rvStat + rvStatTeam(); + rvStatTeam( const rvStatTeam &rhs ); + const rvStatTeam &operator=( const rvStat &rhs ); + +}; + +/* +=============================================================================== + +rvStat/rvStatTeam-derived classes + +These are the individual events that get stored in the +statManager's stat queue + +=============================================================================== +*/ + +/* +================ +rvStatBeginGame + +A game has begun +================ +*/ +class rvStatBeginGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatBeginGame( int t ) : rvStat( t ) { type = ST_BEGIN_GAME; }; +}; + +/* +================ +rvStatEndGame + +The current game has ended +================ +*/ +class rvStatEndGame : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatEndGame( int t ) : rvStat( t ) { type = ST_END_GAME; }; +}; + +/* +================ +rvStatClientConnect + +A player has connected +================ +*/ +class rvStatClientConnect : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatClientConnect( int t, int p ) : rvStat( t ) { type = ST_CLIENT_CONNECT; playerClientNum = p; }; +}; + +/* +================ +rvStatHit + +A player hit another player +================ +*/ +class rvStatHit : public rvStat { + + friend class rvStatAllocator; + +public: + int GetVictimClientNum() const { return victimClientNum; } + int GetWeapon() const { return weapon; } + +protected: + rvStatHit( int t, int p, int v, int w, bool countForAccuracy ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + byte victimClientNum; + bool trackAccuracy; +}; + +/* +================ +rvStatKill + +A player killed another player +================ +*/ +class rvStatKill : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatKill( int t, int p, int v, bool g, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; + byte victimClientNum; + bool gibbed; +}; + +/* +================ +rvStatDeath + +A player died +================ +*/ +class rvStatDeath : public rvStat { + + friend class rvStatAllocator; + +protected: + rvStatDeath( int t, int p, int mod ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte methodOfDeath; +}; + +/* +================ +rvStatDamageDealt + +A player damaged another player +================ +*/ +class rvStatDamageDealt : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageDealt( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatDamageTaken + +A player took damage from another player +================ +*/ +class rvStatDamageTaken : public rvStat { + + friend class rvStatAllocator; + +public: + int GetDamage() const { return damage; } + +protected: + rvStatDamageTaken( int t, int p, int w, int d ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte weapon; + short damage; +}; + +/* +================ +rvStatFlagDrop + +A player dropped the flag (CTF) +================ +*/ +class rvStatFlagDrop : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagDrop( int t, int p, int a, int tm ); + + byte attacker; // enemy who caused the flag drop + +private: + // see comment in rvStat + rvStatFlagDrop(); + rvStatFlagDrop( const rvStatFlagDrop &rhs ); + const rvStatFlagDrop &operator=( const rvStatFlagDrop &rhs ); +}; + +/* +================ +rvStatFlagReturn + +A player returned his teams flag +================ +*/ +class rvStatFlagReturn : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagReturn( int t, int p, int tm ); + +private: + // see comment in rvStat + rvStatFlagReturn(); + rvStatFlagReturn( const rvStatFlagReturn &rhs ); + const rvStatFlagReturn &operator=( const rvStatFlagReturn &rhs ); +}; + +/* +================ +rvStatFlagCapture + +A player captured a flag (CTF) +================ +*/ +class rvStatFlagCapture : public rvStatTeam { + + friend class rvStatAllocator; + +protected: + rvStatFlagCapture( int t, int p, int f, int tm ); + virtual void RegisterInGame( rvPlayerStat* stats ); + + byte flagTeam; // team of flag was captured + +private: + // see comment in rvStat + rvStatFlagCapture(); + rvStatFlagCapture( const rvStatFlagCapture &rhs ); + const rvStatFlagCapture &operator=( const rvStatFlagCapture &rhs ); +}; + +#endif diff --git a/src/mpgame/mp/stats/StatManager.cpp b/src/mpgame/mp/stats/StatManager.cpp new file mode 100644 index 0000000..ec5660c --- /dev/null +++ b/src/mpgame/mp/stats/StatManager.cpp @@ -0,0 +1,1369 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatManager.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" +#include "StatManager.h" + +// TTimo - *????????* +#include + +rvStatManager statManagerLocal; +rvStatManager* statManager = &statManagerLocal; + +comboKillState_t rvStatManager::comboKillState[ MAX_CLIENTS ] = { CKS_NONE }; +int rvStatManager::lastRailShot[ MAX_CLIENTS ] = { -2 }; +int rvStatManager::lastRailShotHits[ MAX_CLIENTS ] = { 0 }; + +inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ] = { + //IGA_INVALID + { + NULL + }, + //IGA_CAPTURE + { + "capture" + }, + //IGA_HUMILIATION + { + "humiliation" + }, + //IGA_IMPRESSIVE + { + "impressive" + }, + //IGA_EXCELLENT + { + "excellent" + }, + //IGA_ASSIST + { + "assist" + }, + //IGA_DEFENSE + { + "defense" + }, + //IGA_COMBO_KILL + { + "combo_kill" + }, + //IGA_RAMPAGE + { + "rampage" + }, + //IGA_HOLY_SHIT + { + "holy_shit" + } +}; + +// RAVEN BEGIN +// rhummer: localized these strings. +endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ] = { + //EGA_INVALID + { + NULL + }, + //EGA_LEMMING + { + "#str_107260" + }, + //EGA_RAIL_MASTER + { + "#str_107261" + }, + //EGA_ROCKET_SAUCE + { + "#str_107262" + }, + //EGA_BRAWLER + { + "#str_107263" + }, + //EGA_SNIPER + { + "#str_107264" + }, + //EGA_CRITICAL_FAILURE + { + "#str_107265" + }, + //EGA_TEAM_PLAYER + { + "#str_107266" + }, + //EGA_ACCURACY + { + "#str_107267" + }, + //EGA_FRAGS + { + "#str_107268" + }, + //EGA_PERFECT + { + "#str_107269" + } +}; +// RAVEN END + +void showStats_f( const idCmdArgs &args ) { + + statManager->DebugPrint(); +} + + +/* +=============================================================================== + + rvStatAllocator + + Handles allocating stat events + +=============================================================================== +*/ + +void *rvStatAllocator::GetBlock( size_t blockSize, int* blockNumOut /* = NULL */ ) { + // if not enough bytes for this allocation, get a new block + if ( GetBytesLeftInBlock() < blockSize ) { + // recycle & reuse a block + currentBlock++; + placeInBlock = 0; + if( currentBlock >= MAX_BLOCKS ) { + currentBlock = 0; + if( statManager->GetStat( 0 ) && gameLocal.isMultiplayer ) { +// int delta = gameLocal.time - statManager->GetStat( 0 )->GetTimeStamp(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Tracked %g seconds of stats before recycle\n", delta / 1000.0f ); + } + } +// gameLocal.Printf( "rvStatAllocator::GetBlock() - Using block %d\n", currentBlock ); +// statManager->DebugPrint(); +// int numFreed = + statManager->FreeEvents( currentBlock ); +// statManager->DebugPrint(); +// gameLocal.Printf( "rvStatAllocator::GetBlock() - stat manager freed %d events which used block %d\n", numFreed, currentBlock ); + } + + // if this fires our objects are too large or our alloc size is too small + assert( GetBytesLeftInBlock() >= blockSize ); + + byte *newBlock = blocks + ( BLOCK_SIZE * currentBlock ) + placeInBlock; + placeInBlock += blockSize; + totalAllocations++; + totalBytesUsed += blockSize; + if( blockNumOut ) { + // return the current block number if requested + (*blockNumOut) = currentBlock; + } + return newBlock; +} + +void rvStatAllocator::Reset() { + currentBlock = 0; + placeInBlock = 0; + + totalBytesUsed = 0; + totalAllocations = 0; + totalBytesAllocated = 0; + for ( int i = 0; i < ST_COUNT; i++ ) { + allocationsByType[ i ] = 0; + } +} + +rvStatAllocator::rvStatAllocator() { + Reset(); +} + +void rvStatAllocator::Report() +{ + // shouchard: for debugging and tuning only + common->Printf( "rvStatAllocator: dump of usage stats\n" ); + common->Printf( "\t%d total bytes handed out in %d requests\n", GetTotalBytesUsed(), GetTotalAllocations() ); + common->Printf( "\tbegin game: %3d; end game: %3d\n", + GetAllocationsByType( ST_BEGIN_GAME ), + GetAllocationsByType( ST_END_GAME ) ); + common->Printf( "\tplayer hit: %3d; player kill: %3d\n", + GetAllocationsByType( ST_HIT ), + GetAllocationsByType( ST_KILL ) ); + common->Printf( "\tplayer death: %3d;\n", + GetAllocationsByType( ST_DEATH ) ); + common->Printf( "\tdamage dealt: %3d; damage taken: %3d\n", + GetAllocationsByType( ST_DAMAGE_DEALT ), + GetAllocationsByType( ST_DAMAGE_TAKEN ) ); + common->Printf( "\tstat team: %3d\n", + GetAllocationsByType( ST_STAT_TEAM ) ); + common->Printf( "\tflag capture: %3d;\n", + GetAllocationsByType( ST_CTF_FLAG_CAPTURE ) ), + common->Printf( "\tflag drop: %3d; flag return: %3d\n", + GetAllocationsByType( ST_CTF_FLAG_DROP ), + GetAllocationsByType( ST_CTF_FLAG_RETURN ) ); +} + +// object allocators + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new new +#endif // _INLINEDEBUGMEMORY + +rvStatBeginGame *rvStatAllocator::AllocStatBeginGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatBeginGame ), blockNumOut ); + assert( newBlock ); + rvStatBeginGame *stat = new( newBlock ) rvStatBeginGame( t ); + allocationsByType[ ST_BEGIN_GAME ]++; + return stat; +} + +rvStatEndGame *rvStatAllocator::AllocStatEndGame( int t, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatEndGame ), blockNumOut ); + assert( newBlock ); + rvStatEndGame *stat = new( newBlock ) rvStatEndGame( t ); + allocationsByType[ ST_END_GAME ]++; + return stat; +} + +rvStatClientConnect *rvStatAllocator::AllocStatClientConnect( int t, int client, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatClientConnect ), blockNumOut ); + assert( newBlock ); + rvStatClientConnect *stat = new( newBlock ) rvStatClientConnect( t, client ); + allocationsByType[ ST_CLIENT_CONNECT ]++; + return stat; +} + +rvStatHit *rvStatAllocator::AllocStatHit( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatHit ), blockNumOut ); + assert( newBlock ); + rvStatHit *stat = new( newBlock ) rvStatHit( t, p, v, w, countForAccuracy ); + allocationsByType[ ST_HIT ]++; + return stat; +} + +rvStatKill *rvStatAllocator::AllocStatKill( int t, int p, int v, bool g, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatKill ), blockNumOut ); + assert( newBlock ); + rvStatKill *stat = new( newBlock ) rvStatKill( t, p, v, g, mod ); + allocationsByType[ ST_KILL ]++; + return stat; +} + +rvStatDeath *rvStatAllocator::AllocStatDeath( int t, int p, int mod, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDeath ), blockNumOut ); + assert( newBlock ); + rvStatDeath *stat = new( newBlock ) rvStatDeath( t, p, mod ); + allocationsByType[ ST_DEATH ]++; + return stat; +} + +rvStatDamageDealt *rvStatAllocator::AllocStatDamageDealt( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageDealt ), blockNumOut ); + assert( newBlock ); + rvStatDamageDealt *stat = new( newBlock ) rvStatDamageDealt( t, p, w, d ); + allocationsByType[ ST_DAMAGE_DEALT ]++; + return stat; +} + +rvStatDamageTaken *rvStatAllocator::AllocStatDamageTaken( int t, int p, int w, int d, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatDamageTaken ), blockNumOut ); + assert( newBlock ); + rvStatDamageTaken *stat = new( newBlock ) rvStatDamageTaken( t, p, w, d ); + allocationsByType[ ST_DAMAGE_TAKEN ]++; + return stat; +} + +rvStatFlagDrop *rvStatAllocator::AllocStatFlagDrop( int t, int p, int a, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagDrop ), blockNumOut ); + assert( newBlock ); + rvStatFlagDrop *stat = new( newBlock ) rvStatFlagDrop( t, p, a, tm ); + allocationsByType[ ST_CTF_FLAG_DROP ]++; + return stat; +} + +rvStatFlagReturn *rvStatAllocator::AllocStatFlagReturn( int t, int p, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagReturn ), blockNumOut ); + assert( newBlock ); + rvStatFlagReturn *stat = new( newBlock ) rvStatFlagReturn( t, p, tm ); + allocationsByType[ ST_CTF_FLAG_CAPTURE ]++; + return stat; +} + +rvStatFlagCapture *rvStatAllocator::AllocStatFlagCapture( int t, int p, int f, int tm, int* blockNumOut /* = NULL */ ) { + void *newBlock = GetBlock( sizeof( rvStatFlagCapture ), blockNumOut ); + assert( newBlock ); + rvStatFlagCapture *stat = new( newBlock ) rvStatFlagCapture( t, p, f, tm ); + allocationsByType[ ST_CTF_FLAG_RETURN ]++; + return stat; +} + +#if defined(_INLINEDEBUGMEMORY) +// Because we need inplace new. +#undef new +#define new ID_DEBUG_NEW +#endif // _INLINEDEBUGMEMORY + +/* +=============================================================================== + + rvStatManager + + Stores game statistic events in statQueue + +=============================================================================== +*/ + +// shouchard: stat manager start with 1 meg; we'll tune as we get better data +rvStatManager::rvStatManager() { + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Init( void ) { + Shutdown(); + statQueue.Clear(); + awardQueue.Clear(); + statQueue.SetGranularity( 1024 ); + endGameSetup = false; + cmdSystem->AddCommand( "ShowInGameStats", showStats_f, CMD_FL_SYSTEM, "show in game stats." ); + memset( localInGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + inGameAwardHudTime = 0; +} + +void rvStatManager::Shutdown( void ) { + statAllocator.Report(); + statAllocator.Reset(); + statQueue.Clear(); + awardQueue.Clear(); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::BeginGame( void ) { + int blockNum; + rvStatBeginGame* stat = statAllocator.AllocStatBeginGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + endGameSetup = false; +#if ID_TRAFFICSTATS + startTime = gameLocal.time; + networkSystem->GetTrafficStats( startSent, startPacketsSent, startReceived, startPacketsReceived ); +#endif + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + comboKillState[ q ] = CKS_NONE; + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +} + +void rvStatManager::EndGame( void ) { + int blockNum; + rvStatEndGame* stat = statAllocator.AllocStatEndGame( gameLocal.time, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + CalculateEndGameStats(); + awardQueue.Clear(); +#if ID_TRAFFICSTATS + int sent, packetsSent, received, packetsReceived, time; + networkSystem->GetTrafficStats( sent, packetsSent, received, packetsReceived ); + sent -= startSent; + packetsSent -= startPacketsSent; + received -= startReceived; + packetsReceived -= startPacketsReceived; + time = gameLocal.time - startTime; + common->Printf( "EndGame. bytes sent: %d packets sent: %d bytes received: %d packets received: %d\n", sent, packetsSent, received, packetsReceived ); + // compute averages, including packet overhead + // adjust the UDP overhead, may depend on your TCP stack implementation ( 42 comes from ethereal analysis of the traffic ) + sent += packetsSent * 42; + received += packetsReceived * 42; + float sentBps, recvBps; + sentBps = (float)( sent ) * 1000.0f / time; + recvBps = (float)( received ) * 1000.0f / time; + common->Printf( "avg sent %g B/s, received %g B/s\n", sentBps, recvBps ); +#endif +} + +void rvStatManager::ClientConnect( int clientNum ) { + // push a client connected event into the queue so we don't get confused with old + // events detailing the previous owner of this clientNum + int blockNum; + rvStatClientConnect* stat = statAllocator.AllocStatClientConnect( gameLocal.time, clientNum, &blockNum ); + statQueue.Append( rvPair( (rvStat*)stat, blockNum ) ); +} + +void rvStatManager::ClientDisconnect( int clientNum ) { + // re-init player stats + playerStats[ clientNum ] = rvPlayerStat(); +} + +void rvStatManager::Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ) { + int deathBlock, killBlock; + rvStatDeath* statDeath = statAllocator.AllocStatDeath( gameLocal.time, victim->entityNumber, methodOfDeath, &deathBlock ); + + statQueue.Append( rvPair( (rvStat*)statDeath, deathBlock ) ); + + if( killer && killer->IsType( idPlayer::GetClassType() ) ) { + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, killer->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } else if( !killer ) { + // basically a suicide + rvStatKill* statKill = statAllocator.AllocStatKill( gameLocal.time, victim->entityNumber, victim->entityNumber, victim->IsGibbed(), methodOfDeath, &killBlock ); + statQueue.Append( rvPair( (rvStat*)statKill, killBlock ) ); + } +} + +void rvStatManager::FlagCaptured( const idPlayer* player, int flagTeam ) { + int blockNum; + rvStatFlagCapture* stat = statAllocator.AllocStatFlagCapture( gameLocal.time, player->entityNumber, flagTeam, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::WeaponFired( const idPlayer* player, int weapon, int num ) { + playerStats[ player->entityNumber ].weaponShots[ weapon ] += num; + lastRailShotHits[ player->entityNumber ] = 0; + + comboKillState_t cks = comboKillState[ player->entityNumber ]; + comboKillState[ player->entityNumber ] = CKS_NONE; + if ( player->GetWeaponIndex( "weapon_rocketlauncher" ) == weapon ) { + if ( cks == CKS_NONE ) { + comboKillState[ player->entityNumber ] = CKS_ROCKET_FIRED; + } + } else if ( player->GetWeaponIndex( "weapon_railgun" ) == weapon ) { + // apparently it processes hits before it does the fire.... + if ( cks == CKS_RAIL_HIT ) { + comboKillState[ player->entityNumber ] = CKS_RAIL_FIRED; + } + } + +} + +void rvStatManager::WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if( victim && victim != attacker ) { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + // if attacker was a player, track hit and damage dealt + int hitBlock; + rvStatHit* statHit = statAllocator.AllocStatHit( gameLocal.time, attacker->entityNumber, victim->entityNumber, weapon, countForAccuracy, &hitBlock ); + + statQueue.Append( rvPair( (rvStat*)(statHit), hitBlock ) ); + } + } +} + +//asalmon modified to work for single player stats on Xenon +void rvStatManager::Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ) { + if( victim && attacker && ( victim == attacker || gameLocal.IsTeamGame( ) && victim->IsType( idPlayer::GetClassType( ) ) && attacker->IsType( idPlayer::GetClassType( ) ) + && static_cast( victim )->team == static_cast( attacker )->team ) ) { + return; + } + + if(attacker) + { + if( attacker->IsType( idPlayer::GetClassType() ) ) { + int damageBlock; + rvStatDamageDealt* statDamage = statAllocator.AllocStatDamageDealt( gameLocal.time, attacker->entityNumber, weapon, damage, &damageBlock ); + statQueue.Append( rvPair( (rvStat*)(statDamage), damageBlock ) ); + } + } + + if(victim) + { + if( victim->IsType( idPlayer::GetClassType() ) ) { + // if victim was a player, track damage taken + int blockNum; + rvStatDamageTaken* stat = statAllocator.AllocStatDamageTaken( gameLocal.time, victim->entityNumber, weapon, damage, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); + } + } +} + +void rvStatManager::FlagDropped( const idPlayer* player, const idEntity* attacker ) { + int blockNum; + rvStatFlagDrop* stat = statAllocator.AllocStatFlagDrop( gameLocal.time, player->entityNumber, attacker->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} +void rvStatManager::FlagReturned( const idPlayer* player ) { + int blockNum; + rvStatFlagReturn* stat = statAllocator.AllocStatFlagReturn( gameLocal.time, player->entityNumber, player->team, &blockNum ); + statQueue.Append( rvPair( (rvStat*)(stat), blockNum ) ); +} + +void rvStatManager::DebugPrint( void ) { + if( !gameLocal.isMultiplayer ) { + return; + } + //gameLocal.Printf( "Begin statistics debug dump:\n" ); + + //gameLocal.Printf( "Statistics queue:\n" ); + for( int i = 0; i < Min( statQueue.Num(), 50 ); i++ ) { + gameLocal.Printf( "{%d, %d} ", statQueue[i].First()->GetType(), statQueue[i].First()->GetTimeStamp() ); + } + gameLocal.Printf("\n"); + + //gameLocal.Printf( "In-game statistics\n\n" ); + //for( int i = 0; i < inGameStats.Num(); i++ ) { + // gameLocal.Printf( "\t%d - %s:\n", i, statIndex->index[ i ].GetName().c_str() ); + // gameLocal.Printf( "\t\tKills: %d\n", inGameStats[i].kills ); + // gameLocal.Printf( "\t\tDeaths: %d\n", inGameStats[i].deaths ); + // gameLocal.Printf( "\t\tFlag Caps: %d\n", inGameStats[i].flagCaptures ); + + // for( int j = 0; j < MAX_WEAPONS; j++ ) { + // gameLocal.Printf( "\t\tWeapon %d hits: %d\n\t\tWeapon %d shots: %d\n", j, inGameStats[i].weaponHits[j], j, inGameStats[i].weaponShots[j] ); + // } + //} +} + +int rvStatManager::FreeEvents( int blockNum ) { + int blockStart = -1; + int blockEnd = -1; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( blockStart == -1 && statQueue[ i ].Second() == blockNum ) { + blockStart = i; + } else if( blockStart != -1 && statQueue[ i ].Second() != blockNum ) { + blockEnd = i; + break; + } + } + + if( blockStart == -1 || blockEnd == -1 ) { +// rjohnson: commented out warning - I take it this is not a bad message? +// gameLocal.Warning( "rvStatManager::FreeEvents() - Could not find events with block num '%d'\n", blockNum ); + return 0; + } + + statQueue.RemoveRange( blockStart, blockEnd - 1 ); + + return (blockEnd - blockStart); +} + +void rvStatManager::SendInGameAward( inGameAward_t award, int clientNum ) { + assert( gameLocal.isServer ); + + idBitMsg msg; + byte msgBuf[1024]; + msg.Init( msgBuf, sizeof( msgBuf ) ); + msg.WriteByte( GAME_RELIABLE_MESSAGE_INGAMEAWARD ); + + msg.WriteByte( award ); + msg.WriteByte( clientNum ); + + networkSystem->ServerSendReliableMessage( -1, msg ); + + if( gameLocal.isListenServer ) { + msg.ReadByte(); + ReceiveInGameAward( msg ); + } +} + + +void rvStatManager::ReceiveInGameAward( const idBitMsg& msg ) { + assert( gameLocal.isClient || gameLocal.isListenServer ); + int numAwards = 0; + + inGameAward_t award = (inGameAward_t)msg.ReadByte(); + int client = msg.ReadByte(); + + // display award on hud + idPlayer* player = gameLocal.GetLocalPlayer(); + idPlayer* remote = gameLocal.GetClientByNum(client); + bool justSound = false; + if ( client != gameLocal.localClientNum ) { + justSound = true; + } + + if ( ( gameLocal.time - inGameAwardHudTime ) < 3000 || awardQueue.Num() > 0 ) { + if ( gameLocal.GetDemoFollowClient() == client || ( player != NULL && remote != NULL && player->GetInstance() == remote->GetInstance() ) ) { + rvPair awardPair(award, justSound); + awardQueue.StackAdd(awardPair); + return; + } + } + + if( client == gameLocal.localClientNum ) { + // don't count awards during warmup + + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + localInGameAwards[ award ]++; + numAwards = localInGameAwards[ award ]; + } else { + numAwards = 1; + } + + if( player && player->mphud ) { + player->mphud->HandleNamedEvent( "clearIGA" ); + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, numAwards ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if( numAwards < 10 ) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, numAwards ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + } else { + player->mphud->SetStateInt( "ig_award_num", numAwards ); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + //inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + + if ( player ) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + else if ( player && remote && ( player->GetInstance() == remote->GetInstance() ) && (award == IGA_HOLY_SHIT || award == IGA_CAPTURE || award == IGA_HUMILIATION )) { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + inGameAwardHudTime = gameLocal.time; + + idPlayer* awardee = gameLocal.GetClientByNum( client ); + if ( awardee ) { + if ( player && player->GetInstance() == awardee->GetInstance() ) { + iconManager->AddIcon( client, va( "mtr_award_%s", inGameAwardInfo[ award ].name ) ); + } + } +} + +void rvStatManager::CheckAwardQueue() { + + if(((gameLocal.time - inGameAwardHudTime) < 3000 || awardQueue.Num() == 0)) + { + return; + } + + idPlayer* player = gameLocal.GetLocalPlayer(); + + int award = awardQueue.StackTop().First(); + bool justSound = awardQueue.StackTop().Second(); + awardQueue.StackPop(); + + if ( player ) { + if(!justSound || award == IGA_HOLY_SHIT || award == IGA_CAPTURE) + { + player->StartSound( va( "snd_award_%s", inGameAwardInfo[ award ].name ), SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + if( player && player->mphud && !justSound) { + + player->mphud->HandleNamedEvent( "clearIGA" ); + localInGameAwards[ award ]++; + player->mphud->SetStateInt( "ig_awards", idMath::ClampInt( 0, 10, localInGameAwards[ award ] ) ); + player->mphud->SetStateString( "ig_award", va( "gfx/mp/awards/%s", inGameAwardInfo[ award ].name ) ); + if(localInGameAwards[ award ] < 10) { + player->mphud->SetStateString( "ig_award_num", ""); + for( int i = 0; i < idMath::ClampInt( 0, 10, localInGameAwards[ award ] ); i++ ) { + player->mphud->SetStateInt( va( "ig_awards_%d", i + 1 ), 1 ); + } + + } + else { + player->mphud->SetStateInt( "ig_award_num", localInGameAwards[ award ]); + player->mphud->SetStateInt( "ig_awards", 1 ); + player->mphud->SetStateInt( va( "ig_awards_%d", 1 ), 1 ); + } + inGameAwardHudTime = gameLocal.time; + player->mphud->HandleNamedEvent( "giveIGA" ); + player->mphud->StateChanged( gameLocal.time ); + + } + +} + + +void rvStatManager::GivePlayerCashForAward( idPlayer* player, inGameAward_t award ) +{ + if( !player ) + return; + + if( !gameLocal.isMultiplayer ) + return; + + if( !gameLocal.mpGame.IsBuyingAllowedInTheCurrentGameMode() ) + return; + + mpGameState_t mpGameState = gameLocal.mpGame.GetGameState()->GetMPGameState(); + if( mpGameState != GAMEON && mpGameState != SUDDENDEATH ) + return; + + const char* awardCashValueName = NULL; + switch( award ) + { + case IGA_CAPTURE: awardCashValueName = "playerCashAward_mpAward_capture"; break; + case IGA_HUMILIATION: awardCashValueName = "playerCashAward_mpAward_humiliation"; break; + case IGA_IMPRESSIVE: awardCashValueName = "playerCashAward_mpAward_impressive"; break; + case IGA_EXCELLENT: awardCashValueName = "playerCashAward_mpAward_excellent"; break; + case IGA_ASSIST: awardCashValueName = "playerCashAward_mpAward_assist"; break; + case IGA_DEFENSE: awardCashValueName = "playerCashAward_mpAward_defense"; break; + case IGA_COMBO_KILL: awardCashValueName = "playerCashAward_mpAward_combo_kill"; break; + case IGA_RAMPAGE: awardCashValueName = "playerCashAward_mpAward_rampage"; break; + case IGA_HOLY_SHIT: awardCashValueName = "playerCashAward_mpAward_holy_shit"; break; + } + + if( awardCashValueName ) + { + player->GiveCash( (float) gameLocal.mpGame.mpBuyingManager.GetIntValueForKey( awardCashValueName, 0 ) ); + } +} + + +void rvStatManager::GiveInGameAward( inGameAward_t award, int clientNum ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ clientNum ]; + + if( gameLocal.isMultiplayer ) { + // show in-game awards during warmup, but don't actually let players accumulate them + if( !player || (gameLocal.mpGame.GetGameState()->GetMPGameState() != WARMUP && + (gameLocal.gameType != GAME_TOURNEY || ((rvTourneyGameState*)gameLocal.mpGame.GetGameState())->GetArena( player->GetArena() ).GetState() != AS_WARMUP )) ) { + playerStats[ clientNum ].inGameAwards[ award ]++; + GivePlayerCashForAward( player, award ); + } + SendInGameAward( award, clientNum ); + } +} + +void rvStatManager::SetupStatWindow( idUserInterface* statHud ) { + statWindow.SetupStatWindow( statHud ); +} + +void rvStatManager::SelectStatWindow( int selectionIndex, int selectionTeam ) { + statWindow.SelectPlayer( statWindow.ClientNumFromSelection( selectionIndex, selectionTeam ) ); +} + +int rvStatManager::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + return statWindow.GetSelectedClientNum( selectionIndexOut, selectionTeamOut ); +} + +void rvStatManager::UpdateInGameHud( idUserInterface* statHud, bool visible ) { + idPlayer* player = NULL; + + if( gameLocal.GetLocalPlayer() ) { + player = gameLocal.GetLocalPlayer(); + player->GetHud()->SetStateInt( "stat_visible", visible? 1 : 0); + } + + if( !visible ) { + statHud->SetStateInt( "stat_visible", 0 ); + return; + } else { + statHud->SetStateInt( "stat_visible", 1 ); + } + + if( player ) { + statWindow.SetupStatWindow( statHud, player->spectating ); + } +} + +void rvStatManager::SendStat( int toClient, int statClient ) { + if( statClient < 0 || statClient >= MAX_CLIENTS ) { + gameLocal.Warning( "rvStatManager::SendStat() - Stats requested for invalid client num '%d'\n", statClient ); + return; + } + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( statClient ); + + playerStats[ statClient ].PackStats( outMsg ); + + networkSystem->ServerSendReliableMessage( toClient, outMsg ); +} + +void rvStatManager::ReceiveStat( const idBitMsg& msg ) { + //asalmon: added because this is used to restore single player stats from saves on Xbox 360 + if(gameLocal.IsMultiplayer()) + { + assert( gameLocal.isClient ); + } + + int client = msg.ReadByte(); + + playerStats[ client ].UnpackStats( msg ); + playerStats[ client ].lastUpdateTime = gameLocal.time; + + // display the updated stat + if(gameLocal.IsMultiplayer()) + { + statWindow.SelectPlayer( client ); + } +} + +void rvStatManager::SendAllStats( int clientNum, bool full ) { + + assert( gameLocal.isServer ); + + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_ALL_STATS ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = 0; + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( gameLocal.entities[ i ] ) { + sentClients |= 1 << i; + } + } + + outMsg.WriteBits( sentClients, MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].PackStats( outMsg ); + } + } + + networkSystem->ServerSendReliableMessage( clientNum, outMsg ); + + //common->Printf("SENT ALL STATS %i\n", Sys_Milliseconds()); + +} + + +void rvStatManager::ReceiveAllStats( const idBitMsg& msg ) { + assert( gameLocal.isClient ); + + assert( MAX_CLIENTS <= 32 ); + + unsigned sentClients = msg.ReadBits( MAX_CLIENTS ); + + for(int i=0; i < MAX_CLIENTS; i++) + { + if ( sentClients & ( 1 << i ) ) { + playerStats[ i ].UnpackStats( msg ); + } else { + playerStats[ i ].Clear(); + } + + playerStats[ i ].lastUpdateTime = gameLocal.time; + } + if ( gameLocal.mpGame.GetGameState()->GetMPGameState() == GAMEREVIEW ) { + gameLocal.mpGame.ShowStatSummary(); + } +} + + +void rvStatManager::ClearStats( void ) { + // clear connected stats + for( int i = 0; i < MAX_CLIENTS; i++ ) { + playerStats[ i ] = rvPlayerStat(); + } + + for ( int q = 0; q < MAX_CLIENTS; ++q ) { + lastRailShot[ q ] = -2; + lastRailShotHits[ q ] = 0; + } +#ifdef _XENON + lastFullUpdate = -50000; +#endif +} + +void rvStatManager::CalculateEndGameStats( void ) { + int maxKills = idMath::INT_MIN; + int maxKillPlayer = -1; + + int maxSuicides = idMath::INT_MIN; + int maxSuicidesPlayer = -1; + + int maxGauntletKills = idMath::INT_MIN; + int maxGauntletKillsPlayer = -1; + + float maxDamageKillsRatio = 0.0; + int maxDamageKillsRatioPlayer = -1; + +//Dump the stats to a log file + idFile *log = NULL; + if(cvarSystem->GetCVarBool("com_logMPStats")) + { + log = fileSystem->OpenFileAppend("StatisticsLog.txt"); + } + idStr toFile; + if ( !log ) { +// common->Warning("Statistics log will not be written\n"); + } + else + { + struct tm *newtime; + time_t aclock; + time( &aclock ); + newtime = localtime( &aclock ); + toFile = va("Match on map %s played on %s\n", gameLocal.GetMapName(), asctime( newtime )); + log->Write(toFile.c_str(), toFile.Length()); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( !gameLocal.entities[ i ] ) { + continue; + } + + if(log) + { + toFile = va("Statistics for %s\n", gameLocal.userInfo[ i ].GetString("ui_name")); + log->Write(toFile.c_str(), toFile.Length()); + toFile = va("Kills: %i Deaths: %i Suicides: %i\n", playerStats[ i ].kills, playerStats[ i ].deaths, playerStats[ i ].suicides); + log->Write(toFile.c_str(), toFile.Length()); + } + + gameLocal.Printf( "Calculating stats for client %d (%s)\n", i, gameLocal.userInfo[ i ].GetString("ui_name") ); + // overall accuracy award and sniper accuracy award + idPlayer* player = (idPlayer*)gameLocal.entities[ i ]; + int railgunIndex = player->GetWeaponIndex( "weapon_railgun" ); + int rocketIndex = player->GetWeaponIndex( "weapon_rocketlauncher" ); + + float accuracyAverage = 0.0f; + int numAccuracies = 0; + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats[ i ].weaponShots[ j ] == 0 ) { + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s not used\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name"))); + log->Write(toFile.c_str(), toFile.Length()); + } + } + continue; + } + + float weaponAccuracy = (float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]; + if(log) + { + if(player->GetWeaponDef(j)) + { + toFile = va("%s: %i%%\n", common->GetLocalizedString(player->GetWeaponDef(j)->dict.GetString("inv_name")), (int)(((float)playerStats[ i ].weaponHits[ j ] / (float)playerStats[ i ].weaponShots[ j ]) * 100.0f)); + log->Write(toFile.c_str(), toFile.Length()); + } + } + if( j == railgunIndex ) { + // sniper award + if( weaponAccuracy >= 0.9f && playerStats[ i ].weaponShots[ railgunIndex ] >= 10 ) { + playerStats[ i ].endGameAwards.Append( EGA_SNIPER ); + } + } + + accuracyAverage += weaponAccuracy; + numAccuracies++; + } + + + if ( numAccuracies && ( accuracyAverage / (float)numAccuracies >= 0.5f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ACCURACY ); + } + + + // rail master award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ railgunIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_RAIL_MASTER ); + } + + // rocket sauce award + if ( playerStats[ i ].kills && ( (float)playerStats[ i ].weaponKills[ rocketIndex ] / (float)playerStats[ i ].kills >= 0.8f ) ) { + playerStats[ i ].endGameAwards.Append( EGA_ROCKET_SAUCE ); + } + + // critical failure award + if( playerStats[ i ].kills == 0 ) { + playerStats[ i ].endGameAwards.Append( EGA_CRITICAL_FAILURE ); + } + + // frags award +//asalmon: Made the limit more reasonable for the shorter time limits and less players of Xenon +#ifdef _XENON + if( playerStats[ i ].kills >= 50 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#else + if( playerStats[ i ].kills >= 100 ) { + playerStats[ i ].endGameAwards.Append( EGA_FRAGS ); + } +#endif + + + if( playerStats[ i ].kills > maxKills ) { + maxKills = playerStats[ i ].kills; + maxKillPlayer = i; + } + + if( playerStats[ i ].suicides > maxSuicides ) { + maxSuicides = playerStats[ i ].suicides; + maxSuicidesPlayer = i; + } + + if( playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ] > maxGauntletKills ) { + maxGauntletKills = playerStats[ i ].weaponKills[ player->GetWeaponIndex( "weapon_gauntlet" ) ]; + maxGauntletKillsPlayer = i; + } + +//asalmon: Calculate the damage ratio: + if(playerStats[i].kills > 0) + { + playerStats[i].damageRatio = playerStats[i].damageGiven / playerStats[i].kills; + } + else + { + playerStats[i].damageRatio = playerStats[i].damageGiven; + } + + if( playerStats[ i ].damageRatio > maxDamageKillsRatio ) { + maxDamageKillsRatio = playerStats[ i ].damageRatio; + maxDamageKillsRatioPlayer = i; + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + } + + +//asalmon: hack to test certain achievement awards. +#ifdef _XENON + if(cvarSystem->GetCVarInteger("si_overrideFrags")) + { + common->Printf("Overriding Frags to: %i for player %i\n", cvarSystem->GetCVarInteger("si_overrideFrags"), i); + playerStats[i].kills = cvarSystem->GetCVarInteger("si_overrideFrags"); + } +#endif + + } + + + + // Perfect award + if( maxKillPlayer >= 0 && playerStats[ maxKillPlayer ].deaths == 0 ) { + idPlayer* player = (idPlayer*)gameLocal.entities[ maxKillPlayer ]; + if( !gameLocal.IsTeamGame() || ( gameLocal.IsTeamGame() && player && gameLocal.mpGame.TeamLeader() == player->team ) ) { + playerStats[ maxKillPlayer ].endGameAwards.Append( EGA_PERFECT ); + } + } + + // Lemming award + if( maxSuicidesPlayer >= 0 && maxSuicides >= 5 ) { + playerStats[ maxSuicidesPlayer ].endGameAwards.Append( EGA_LEMMING ); + } + + // Brawler award + if( maxGauntletKillsPlayer >= 0 && maxGauntletKills >= 3 ) { + playerStats[ maxGauntletKillsPlayer ].endGameAwards.Append( EGA_BRAWLER ); + } + + // Team player award + if( maxDamageKillsRatioPlayer >= 0 && maxDamageKillsRatio > 500 ) { + playerStats[ maxDamageKillsRatioPlayer ].endGameAwards.Append( EGA_TEAM_PLAYER ); + } + + if(log) + { + toFile = "\n"; + log->Write(toFile.c_str(), toFile.Length()); + log->Flush(); + fileSystem->CloseFile(log); + } +} + +void rvStatManager::GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ) { + memset( accuracyLeaders, -1, sizeof( int ) * MAX_WEAPONS ); + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( gameLocal.entities[ i ] == NULL ) { + continue; + } + + rvPlayerStat* playerStats = GetPlayerStat( i ); + + for( int j = 0; j < MAX_WEAPONS; j++ ) { + if( playerStats->weaponShots[ j ] == 0 ) { + continue; + } + + float playerAccuracy = (float)playerStats->weaponHits[ j ] / (float)playerStats->weaponShots[ j ]; + float leaderAccuracy = -1.0f; + if( accuracyLeaders[ j ] != -1 ) { + rvPlayerStat* leaderStats = GetPlayerStat( accuracyLeaders[ j ] ); + if( leaderStats->weaponShots[ j ] != 0 ) { + leaderAccuracy = (float)leaderStats->weaponHits[ j ] / (float)leaderStats->weaponShots[ j ]; + } + } + if( playerAccuracy > leaderAccuracy ) { + accuracyLeaders[ j ] = i; + } + } + } +} + +int rvStatManager::DamageGiven( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_DEALT ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && (statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +int rvStatManager::DamageTaken( int playerNum, int lowerBound, int upperBound ) { + if( playerNum < 0 || playerNum >= MAX_CLIENTS ) { + return 0; + } + + int damage = 0; + + for( int i = 0; i < statQueue.Num(); i++ ) { + if( statQueue[ i ].First()->GetType() == ST_DAMAGE_TAKEN ) { + if( statQueue[ i ].First()->GetPlayerClientNum() == playerNum && ( statQueue[ i ].First()->GetTimeStamp() > lowerBound && statQueue[ i ].First()->GetTimeStamp() < upperBound ) ) { + damage += static_cast(statQueue[ i ].First())->GetDamage(); + } + } + } + + return damage; +} + +rvStat* rvStatManager::GetLastClientStat( int clientNum, statType_t type, int time ) { + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + return statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ) { + int numFound = 0; + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( clientNum == -1 || statQueue[ i ].First()->GetPlayerClientNum() == clientNum ) { + results[ numFound++ ] = statQueue[ i ].First(); + if( numFound >= num ) { + return; + } + } + } + } + + return; +} + + +rvStatTeam* rvStatManager::GetLastTeamStat( int team, statType_t type, int time ) { + assert( type > ST_STAT_TEAM ); + + for( int i = (statQueue.Num() - 1); i >= 0; i-- ) { + if( statQueue[ i ].First()->GetTimeStamp() < time ) { + return NULL; + } + + if( statQueue[ i ].First()->GetType() == type ) { + if( ((rvStatTeam*)statQueue[ i ].First())->GetTeam() == team ) { + return (rvStatTeam*)statQueue[ i ].First(); + } + } + } + + return NULL; +} + +void rvStatManager::SetupEndGameHud( idUserInterface* statHud ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + idPlayer* p = gameLocal.mpGame.GetRankedPlayer( i ); + + if( p && gameLocal.mpGame.IsInGame( p->entityNumber ) ) { + statHud->SetStateString( va( "player%d_name", i + 1 ), va( "%d. %s", i + 1, gameLocal.userInfo[ p->entityNumber ].GetString( "ui_name" ) ) ); + statHud->SetStateString( va( "player%d_score", i + 1 ), va( "%d", gameLocal.mpGame.GetScore( i ) ) ); + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 1 ); + statHud->SetStateInt( va( "player%d_team", i + 1 ), gameLocal.IsTeamGame() ? p->team : 0 ); + } else { + statHud->SetStateInt( va( "player%d_visible", i + 1 ), 0 ); + } + } + statHud->HandleNamedEvent( "Setup" ); +} + +rvPlayerStat* rvStatManager::GetPlayerStat( int clientNum ) { + return &playerStats[ clientNum ]; +} + +void rvStatManager::UpdateEndGameHud( idUserInterface* statHud, int clientNum ) { + /*if( !endGameSetup ) { + // no info yet + SetupEndGameHud( statHud ); + endGameSetup = true; + } + + rvPlayerStat* clientStat = &(playerStats[ clientNum ]); + + statHud->HandleNamedEvent( "clear" ); + statHud->SetStateString( "stat_name", gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ); + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", (int)(clientStat->weaponAccuracy[ i ] * 100) ) ); + } + + // in-game awards + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < clientStat->inGameAwards.Num(); i++ ) { + igAwardCount[ clientStat->inGameAwards[ i ] ]++; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < clientStat->endGameAwards.Num(); i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); + statHud->SetStateString( va( "eg_award%d_text", i ), endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ); + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) );*/ +} + +/* +=============================================================================== + + rvStatSummary + + Stores one player's summary information. Transmitted to clients for + intermission summary screen. + +=============================================================================== +*/ +rvPlayerStat::rvPlayerStat() { + Clear(); +} + +void rvPlayerStat::Clear( void ) { + memset( weaponShots, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponHits, 0, sizeof(int) * MAX_WEAPONS ); + memset( weaponKills, 0, sizeof(int) * MAX_WEAPONS ); + memset( inGameAwards, 0, sizeof( int ) * (int)IGA_NUM_AWARDS ); + + kills = deaths = suicides = lastUpdateTime = damageTaken = damageGiven = 0; + damageRatio = 0.0f; +} + +void rvPlayerStat::PackStats( idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponShots[ i ] ); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + msg.WriteShort( weaponHits[ i ] ); + } + + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + msg.WriteByte( inGameAwards[ i ] ); + } + + msg.WriteByte( endGameAwards.Num() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + msg.WriteByte( endGameAwards[ i ] ); + } + + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXDEATHS, deaths ), ASYNC_PLAYER_DEATH_BITS ); + msg.WriteBits( idMath::ClampInt( 0, MP_PLAYER_MAXKILLS, kills ), ASYNC_PLAYER_KILL_BITS ); +} + +void rvPlayerStat::UnpackStats( const idBitMsg& msg ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponShots[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < MAX_WEAPONS; i++ ) { + weaponHits[ i ] = msg.ReadShort(); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + inGameAwards[ i ] = msg.ReadByte(); + } + + endGameAwards.SetNum( msg.ReadByte() ); + for( int i = 0; i < endGameAwards.Num(); i++ ) { + endGameAwards[ i ] = (endGameAward_t)msg.ReadByte(); + } + + deaths = msg.ReadBits( ASYNC_PLAYER_DEATH_BITS ); + kills = msg.ReadBits( ASYNC_PLAYER_KILL_BITS ); +} diff --git a/src/mpgame/mp/stats/StatManager.h b/src/mpgame/mp/stats/StatManager.h new file mode 100644 index 0000000..458f734 --- /dev/null +++ b/src/mpgame/mp/stats/StatManager.h @@ -0,0 +1,373 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatManager.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATMANAGER_H__ +#define __STATMANAGER_H__ + +#include "StatEvent.h" +#include "StatWindow.h" + +#ifndef ID_TRAFFICSTATS + #define ID_TRAFFICSTATS 0 +#endif + +/* +=============================================================================== + + Multiplayer game statistics + +=============================================================================== +*/ + +/* +================ +endGameAward_t + +End-game award IDs +================ +*/ +enum endGameAward_t { + EGA_INVALID = 0, + EGA_LEMMING, + EGA_RAIL_MASTER, + EGA_ROCKET_SAUCE, + EGA_BRAWLER, + EGA_SNIPER, + EGA_CRITICAL_FAILURE, + EGA_TEAM_PLAYER, + EGA_ACCURACY, + EGA_FRAGS, + EGA_PERFECT, + EGA_NUM_AWARDS +}; + +/* +================ +endGameAwardInfo_t + +end-game award info +================ +*/ +struct endGameAwardInfo_t { + char* name; +}; + +/* +================ +inGameAward_t + +in-game award IDs +================ +*/ +enum inGameAward_t { + IGA_INVALID = 0, + IGA_CAPTURE, + IGA_HUMILIATION, + IGA_IMPRESSIVE, + IGA_EXCELLENT, + IGA_ASSIST, + IGA_DEFENSE, + IGA_COMBO_KILL, + IGA_RAMPAGE, + IGA_HOLY_SHIT, + IGA_NUM_AWARDS +}; + +enum comboKillState_t { + CKS_NONE, + CKS_ROCKET_FIRED, + CKS_ROCKET_HIT, + CKS_RAIL_FIRED, + CKS_RAIL_HIT, +}; + +/* +================ +inGameAwardInfo_t + +in-game award info +================ +*/ +struct inGameAwardInfo_t { + char* name; +}; + +/* +================ +rvStatAllocator + +A simple block allocator for different stat objects. +Note: this isn't a real heap; it has no delete or tracking of free blocks. +It matches the memory usage of the rvStatManager which dumps its entire +memory all at once at very specific times. If that ever changes, this will +need to change. +================ +*/ + +const int BLOCK_SIZE = 1024; +const int MAX_BLOCKS = 128; + +class rvStatAllocator { + protected: + byte blocks[ BLOCK_SIZE * MAX_BLOCKS ]; + short currentBlock; // current block + size_t placeInBlock; // current position, offset from start of block + size_t totalBytesUsed; // total bytes handed out from the allocator + size_t totalAllocations; // total blocks handed out from the allocator + size_t totalBytesAllocated; // total bytes in all allocated blocks + size_t allocationsByType[ST_COUNT]; // one spare at [0]; allocations per type of event + + void *GetBlock( size_t blockSize, int* blockNumOut = NULL ); + + public: + rvStatAllocator(); + void Report(); + void Reset(); + + // object allocators + rvStatBeginGame *AllocStatBeginGame ( int t, int* blockNumOut = NULL ); + rvStatEndGame *AllocStatEndGame ( int t, int* blockNumOut = NULL ); + rvStatClientConnect *AllocStatClientConnect ( int t, int client, int* blockNumOut = NULL ); + rvStatHit *AllocStatHit ( int t, int p, int v, int w, bool countForAccuracy, int* blockNumOut = NULL ); + rvStatKill *AllocStatKill ( int t, int p, int v, bool g, int mod, int* blockNumOut = NULL ); + rvStatDeath *AllocStatDeath ( int t, int p, int mod, int* blockNumOut = NULL ); + rvStatDamageDealt *AllocStatDamageDealt ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatDamageTaken *AllocStatDamageTaken ( int t, int p, int w, int d, int* blockNumOut = NULL ); + rvStatFlagDrop *AllocStatFlagDrop ( int t, int p, int a, int tm, int* blockNumOut = NULL ); + rvStatFlagReturn *AllocStatFlagReturn ( int t, int p, int tm, int* blockNumOut = NULL ); + rvStatFlagCapture *AllocStatFlagCapture ( int t, int p, int f, int tm, int* blockNumOut = NULL ); + + // bookkeeping readers + ID_INLINE size_t GetBytesLeftInBlock() const { + assert( placeInBlock <= BLOCK_SIZE ); + return ( size_t )( BLOCK_SIZE - placeInBlock ); + } + + ID_INLINE size_t GetPlaceInBlock() const { + return placeInBlock; + } + + ID_INLINE size_t GetTotalBytesUsed() const { + return totalBytesUsed; + } + + ID_INLINE size_t GetTotalAllocations() const { + return totalAllocations; + } + + ID_INLINE size_t GetAllocationsByType( statType_t type ) const { + return allocationsByType[ type ]; + } + + ID_INLINE size_t GetTotalBytesAllocated() const { + return totalBytesAllocated; + } +}; + + +/* +================ +rvPlayerStat + +Stores one player's stat information. +================ +*/ +class rvPlayerStat { +public: + rvPlayerStat(); + + void Clear( void ); + void CalculateStats( int p, const idList& playerStats ); + + void PackStats( idBitMsg& msg ); + void UnpackStats( const idBitMsg& msg ); + + int weaponShots[ MAX_WEAPONS ]; + int weaponHits[ MAX_WEAPONS ]; + + int weaponKills[ MAX_WEAPONS ]; + + int kills; + int deaths; + int suicides; + float damageRatio; + +// asalmon: added for Xenon +// ddynerman: also used on PC + int damageGiven; + int damageTaken; + + idList endGameAwards; +// asalmon: changed this to just an array of counts + int inGameAwards[IGA_NUM_AWARDS]; + + int lastUpdateTime; +}; + +/* +================ +rvStatManager + +The statistics management interface. This is the entrypoint into +statistics from the game. + +The game calls into rvStatManager at appropriate times (i.e. 'FlagCaptured') + +rvStatManager creates stat events (see StatEvent.h) with the appropriate +information and append them to the statQueue. + +The statQueue is parsed at the end of a game to tabulate statistics, give +end-game awards. + +================ +*/ +class rvStatManager { +public: + rvStatManager(); + + void Init( void ); + void Shutdown( void ); + + // generic events + void BeginGame( void ); + void EndGame( void ); + void ClientConnect( int clientNum ); + void ClientDisconnect( int clientNum ); + void WeaponFired( const idPlayer* player, int weapon, int num ); + void WeaponHit( const idActor* attacker, const idEntity* victim, int weapon, bool countForAccuracy = true ); + void Damage( const idEntity* attacker, const idEntity* victim, int weapon, int damage ); + + + // ctf-specific events + void FlagCaptured( const idPlayer* player, int flagTeam ); + void FlagDropped( const idPlayer* player, const idEntity* attacker ); + void FlagReturned( const idPlayer* player ); + + + // shared events + void Kill( const idPlayer* victim, const idEntity* killer, int methodOfDeath ); + + void DebugPrint( void ); + + rvPlayerStat* GetPlayerStats( void ) { return playerStats; }; + rvPlayerStat* GetPlayerStat( int clientNum ); + + void CalculateEndGameStats( void ); + + void GivePlayerCashForAward( idPlayer* player, inGameAward_t award ); + void GiveInGameAward( inGameAward_t award, int clientNum ); + + // network transmission + void SendStat( int toClient, int statClient ); + void ReceiveStat( const idBitMsg& msg ); + + void SendInGameAward( inGameAward_t award, int clientNum ); + void ReceiveInGameAward( const idBitMsg& msg ); + void CheckAwardQueue(); + + void ClearStats( void ); + + int DamageGiven( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + int DamageTaken( int playerNum, int lowerBound = idMath::INT_MIN, int upperBound = idMath::INT_MAX ); + + void UpdateInGameHud( idUserInterface* statHud, bool visible ); + void UpdateEndGameHud( idUserInterface* statHud, int clientNum ); + void SetupEndGameHud( idUserInterface* statHud ); + + rvStat* GetLastClientStat( int clientNum, statType_t type, int time ); + void GetLastClientStats( int clientNum, statType_t type, int time, int num, rvStat** results ); + rvStatTeam* GetLastTeamStat( int team, statType_t type, int time ); + rvStat* GetStat( int i ); + + void GetAccuracyLeaders( int accuracyLeaders[ MAX_WEAPONS ] ); + + void SetupStatWindow( idUserInterface* statHud ); + void SelectStatWindow( int selectionIndex, int selectionTeam ); + int GetSelectedClientNum( int* selectionIndexOut = NULL, int* selectionTeamOut = NULL ); + + //asalmon: Sends all stats to all clients. For Xenon periodic update of stats. + void SendAllStats( int clientNum = -1, bool full = true ); + void ReceiveAllStats( const idBitMsg& msg ); + + void SetDamageGiven(int client, int damage) { playerStats[client].damageGiven = damage; } + void SetDamageTaken(int client, int damage) { playerStats[client].damageTaken = damage; } + + int FreeEvents( int blockNum ); + + static comboKillState_t comboKillState[ MAX_CLIENTS ]; + static int lastRailShot[ MAX_CLIENTS ]; + static int lastRailShotHits[ MAX_CLIENTS ]; + +private: + idList > statQueue; + idList > awardQueue; +// idList inGameStats; +// rvStatInGame previousInGameStats[ MAX_CLIENTS ]; +// rvStatInGame localClientInGameStats; + + // local hud support + int localInGameAwards[ IGA_NUM_AWARDS ]; + int inGameAwardHudTime; + + rvPlayerStat playerStats[ MAX_CLIENTS ]; + + bool endGameSetup; + + rvStatWindow statWindow; + + // shouchard: stat allocator to avoid lots of little news + rvStatAllocator statAllocator; + +#ifdef _XENON + int lastFullUpdate; +#endif + +#if ID_TRAFFICSTATS + int startSent; + int startPacketsSent; + int startReceived; + int startPacketsReceived; + int startTime; +#endif +}; + +ID_INLINE rvStat* rvStatManager::GetStat( int i ) { + if( i < 0 || i >= statQueue.Num() ) { + return NULL; + } + + return statQueue[ i ].First(); +} + +extern rvStatManager* statManager; +extern inGameAwardInfo_t inGameAwardInfo[ IGA_NUM_AWARDS ]; +extern endGameAwardInfo_t endGameAwardInfo[ EGA_NUM_AWARDS ]; + +#endif /* !__STATMANAGER_H__ */ diff --git a/src/mpgame/mp/stats/StatWindow.cpp b/src/mpgame/mp/stats/StatWindow.cpp new file mode 100644 index 0000000..566dbcb --- /dev/null +++ b/src/mpgame/mp/stats/StatWindow.cpp @@ -0,0 +1,485 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatWindow.cpp +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#include "../../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../Game_local.h" + +#include "StatWindow.h" +#include "StatManager.h" + +/* +================ +rvStatWindow::rvStatWindow() +================ +*/ +rvStatWindow::rvStatWindow() { + statHud = NULL; +} + +/* +================ +rvStatWindow::SetupStatWindow() + +Sets up a selectable window of current stats +================ +*/ +void rvStatWindow::SetupStatWindow( idUserInterface* hud, bool useSpectator ) { + idPlayer* player = gameLocal.GetLocalPlayer(); + assert( player ); + if ( !player ) { + return; + } + +// mekberg: added + idList marineScores; + idList stroggScores; + + int selectionIndex = -1; + int selectionTeam = -1; + + statHud = hud; + + if( gameLocal.IsTeamGame() ) { + if( gameLocal.IsFlagGameType() ) { + statHud->SetStateInt( "ctf_awards", 1 ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + } + + stroggPlayers.Clear(); + marinePlayers.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + idPlayer* player = gameLocal.mpGame.GetRankedPlayer( i ); + + if( player->team == TEAM_MARINE ) { + marinePlayers.Append( player ); + marineScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } else if ( player->team == TEAM_STROGG ) { + stroggPlayers.Append( player ); + stroggScores.Append( gameLocal.mpGame.GetRankedPlayerScore( i ) ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < marinePlayers.Num() ) { + statHud->SetStateString( va( "team_1_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( marinePlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( marinePlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + marinePlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + marineScores[ i ] ) ); + + if( useSpectator ) { + if( marinePlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } else { + if( marinePlayers[ i ] == player ) { + selectionTeam = TEAM_MARINE; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_1_names_item_%d", i ), "" ); + } + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < stroggPlayers.Num() ) { + statHud->SetStateString( va( "team_2_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( stroggPlayers[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( stroggPlayers[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + stroggPlayers[ i ]->GetUserInfo()->GetString( "ui_name"), + stroggScores[ i ] ) ); + if( useSpectator ) { + if( stroggPlayers[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } else { + if( stroggPlayers[ i ] == player ) { + selectionTeam = TEAM_STROGG; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "team_2_names_item_%d", i ), "" ); + } + } + + statHud->SetStateInt ( "num_strogg_players", stroggPlayers.Num() ); + statHud->SetStateInt ( "num_marine_players", marinePlayers.Num() ); + } else { + statHud->SetStateInt( "ctf_awards", 0 ); + + players.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumRankedPlayers(); i++ ) { + players.Append( gameLocal.mpGame.GetRankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < players.Num() ) { + statHud->SetStateString( va( "dm_names_item_%d", i ), + va( "%s\t%s\t%s\t%d\t", ( player->IsFriend( players[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( players[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + players[ i ]->GetUserInfo()->GetString( "ui_name"), + + gameLocal.mpGame.GetScore( players[ i ] ) ) ); + if( useSpectator ) { + if( players[ i ]->entityNumber == player->spectator ) { + selectionTeam = 0; + selectionIndex = i; + } + } else { + if( players[ i ] == player ) { + selectionTeam = 0; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "dm_names_item_%d", i ), "" ); + } + } + statHud->SetStateInt ( "num_players", players.Num() ); + } + + // spectators + spectators.Clear(); + + for( int i = 0; i < gameLocal.mpGame.GetNumUnrankedPlayers(); i++ ) { + spectators.Append( gameLocal.mpGame.GetUnrankedPlayer( i ) ); + } + + for( int i = 0; i < MAX_CLIENTS; i++ ) { + if( i < spectators.Num() ) { + statHud->SetStateString( va( "spec_names_item_%d", i ), + va( "%s\t%s\t%s\t", ( player->IsFriend( spectators[ i ] ) ? I_FRIEND_ENABLED : I_FRIEND_DISABLED ), + ( player->IsPlayerMuted( spectators[ i ] ) ? I_VOICE_DISABLED : I_VOICE_ENABLED ), + spectators[ i ]->GetUserInfo()->GetString( "ui_name") ) ); + + if( useSpectator ) { + if( spectators[ i ]->entityNumber == player->spectator ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } else { + if( spectators[ i ] == player ) { + selectionTeam = TEAM_MAX; + selectionIndex = i; + } + } + } else { + statHud->SetStateString( va( "spec_names_item_%d", i ), "" ); + + } + } + + statHud->SetStateInt( "gametype", gameLocal.gameType ); + statHud->SetStateInt( "playerteam", gameLocal.GetLocalPlayer()->team ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); + + // we shouldn't ever draw a hud unless we're in-game + if ( selectionIndex >= 0 && selectionTeam >= 0 ) { + statManager->SelectStatWindow( selectionIndex, selectionTeam ); + } +} + +/* +================ +rvStatWindow::ClearWindow() + +Clears the stat part of the stat window, but not the player lists boxes +================ +*/ +void rvStatWindow::ClearWindow( void ) { + for( int i = 0; i < MAX_WEAPONS; i++ ) { + statHud->SetStateString( va( "stat_%d_pct", i ), "" ); + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, "" ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + statHud->SetStateInt( va( "eg_award%d", i ), 0 ); + statHud->SetStateString( va( "eg_award%d_text", i ), "" ); + } + + // kills + statHud->SetStateString( "stat_frags", "" ); + + // deaths + statHud->SetStateString( "stat_deaths", "" ); + + // score + statHud->SetStateString( "stat_score", "" ); + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + + +/* +================ +rvStatWindow::SelectPlayer() + +Selects the specified player +================ +*/ +void rvStatWindow::SelectPlayer( int clientNum ) { + if( statHud == NULL ) { + return; + } + + if( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + ClearWindow(); + return; + } + + assert( gameLocal.GetLocalPlayer() ); + + rvPlayerStat* clientStat = statManager->GetPlayerStat( clientNum ); + + if( gameLocal.isClient && ( clientStat == NULL || ( gameLocal.time - clientStat->lastUpdateTime ) > 5000 ) ) { + // get new stats + idBitMsg outMsg; + byte msgBuf[ 128 ]; + + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STAT ); + outMsg.WriteByte( clientNum ); + networkSystem->ClientSendReliableMessage( outMsg ); + return; + } + + // weapon accuracy + for( int i = 0; i < MAX_WEAPONS; i++ ) { + int weaponAccuracy = 0; + if( clientStat->weaponShots[ i ] != 0 ) { + weaponAccuracy = (int)(((float)clientStat->weaponHits[ i ] / (float)clientStat->weaponShots[ i ]) * 100.0f); + } + statHud->SetStateString( va( "stat_%d_pct", i ), va( "%d%%", weaponAccuracy ) ); + } + + int igAwardCount[ IGA_NUM_AWARDS ]; + memset( igAwardCount, 0, sizeof( int ) * IGA_NUM_AWARDS ); + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + igAwardCount[i] = clientStat->inGameAwards[i]; + } + + for( int i = 0; i < IGA_NUM_AWARDS; i++ ) { + statHud->SetStateString( inGameAwardInfo[ i ].name, va( "%d", igAwardCount[ i ] ) ); + } + + // end-game awards + for( int i = 0; i < EGA_NUM_AWARDS; i++ ) { + if( i < clientStat->endGameAwards.Num() ) { + statHud->SetStateInt( va( "eg_award%d", i ), 1 ); +// RAVEN BEGIN +// rhummer: Localized the award strings.. + statHud->SetStateString( va( "eg_award%d_text", i ), common->GetLocalizedString( endGameAwardInfo[ clientStat->endGameAwards[ i ] ].name ) ); +// RAVEN END + } else { + statHud->SetStateInt( va( "eg_award%d", i), 0 ); + } + } + + // kills + statHud->SetStateString( "stat_frags", va( "%d", clientStat->kills ) ); + + // deaths + statHud->SetStateString( "stat_deaths", va( "%d", clientStat->deaths ) ); + + // score + int score = gameLocal.IsTeamGame() ? (gameLocal.mpGame.GetTeamScore( clientNum ) + gameLocal.mpGame.GetScore( clientNum )): gameLocal.mpGame.GetScore( clientNum ); + statHud->SetStateString( "stat_score", va( "%d", score ) ); + + if( gameLocal.GetLocalPlayer()->IsFriend( clientNum ) ) { + // remove friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200249" ) ); + } else { + // add friend label + statHud->SetStateString( "friend_button", common->GetLocalizedString( "#str_200248" ) ); + } + + if( gameLocal.GetLocalPlayer()->IsPlayerMuted( clientNum ) ) { + // unmute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200251" ) ); + } else { + // mute label + statHud->SetStateString( "mute_button", common->GetLocalizedString( "#str_200250" ) ); + } + + statHud->StateChanged ( gameLocal.time ); + statHud->Redraw( gameLocal.time ); +} + +/* +================ +rvStatWindow::ClientNumFromSelection() + +Parses a selection index and team into a clientNum +================ +*/ +int rvStatWindow::ClientNumFromSelection( int selectionIndex, int selectionTeam ) { + int clientNum = -1; + + if( gameLocal.IsTeamGame() ) { + if( selectionTeam == TEAM_MARINE ) { + if( selectionIndex < 0 || selectionIndex >= marinePlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = marinePlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_1_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else if( selectionTeam == TEAM_STROGG ) { + if( selectionIndex < 0 || selectionIndex >= stroggPlayers.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = stroggPlayers[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "team_2_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } else { + if( selectionTeam == TEAM_MAX ) { + if( selectionIndex < 0 || selectionIndex >= spectators.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = spectators[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "spec_names_sel_0", selectionIndex ); + statHud->SetStateString( "dm_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } else { + if( selectionIndex < 0 || selectionIndex >= players.Num() ) { + gameLocal.Warning( "rvStatManager::SelectPlayerStats() - invalid selection '%d'\n", selectionIndex ); + return -1; + } + clientNum = players[ selectionIndex ]->entityNumber; + + // explicitly set the gui selection if we called in here not from a GUI + statHud->SetStateInt( "dm_names_sel_0", selectionIndex ); + statHud->SetStateString( "spec_names_sel_0", "-1" ); + statHud->SetStateString( "team_1_names_sel_0", "-1" ); + statHud->SetStateString( "team_2_names_sel_0", "-1" ); + } + } + + return clientNum; +} + +/* +================ +rvStatWindow::GetSelectedClientNum() + +Queries the gui dict to figure out the currently selected selectionIndex/selectionTeam. +Uses the selectionIndex/selectionTeam to return the selected client num. +================ +*/ +int rvStatWindow::GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ) { + if( statHud == NULL ) { + return -1; + } + int selectionIndex = -1; + int selectionTeam = -1; + + // StatWindow update code should assure that only one selection of all these listDefs is is valid + // Find the valid one + if( statHud->State().GetInt( "spec_names_sel_0", "-1" ) >= 0 ) { + selectionIndex = statHud->State().GetInt( "spec_names_sel_0", "-1" ); + selectionTeam = TEAM_MAX; + } + + if ( statHud->State().GetInt( "dm_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "dm_names_sel_0", "-1" ); + selectionTeam = 0; + } + + if ( statHud->State().GetInt( "team_1_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_1_names_sel_0", "-1" ); + selectionTeam = TEAM_MARINE; + } + + if ( statHud->State().GetInt( "team_2_names_sel_0", "-1" ) >= 0 ) { + // if this assert fails, more than one selection is valid + assert( selectionIndex == -1 && selectionTeam == -1 ); + + selectionIndex = statHud->State().GetInt( "team_2_names_sel_0", "-1" ); + selectionTeam = TEAM_STROGG; + } + + // return the selection index & team + if( selectionIndexOut ) { + (*selectionIndexOut) = selectionIndex; + } + if( selectionTeamOut ) { + (*selectionTeamOut) = selectionTeam; + } + + return ClientNumFromSelection( selectionIndex, selectionTeam ); +} diff --git a/src/mpgame/mp/stats/StatWindow.h b/src/mpgame/mp/stats/StatWindow.h new file mode 100644 index 0000000..9c3d0f6 --- /dev/null +++ b/src/mpgame/mp/stats/StatWindow.h @@ -0,0 +1,60 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// StatWindow.h +// +// Copyright 2002-2005 Raven Software +//---------------------------------------------------------------- + +#ifndef __STATWINDOW_H__ +#define __STATWINDOW_H__ + +/* +=============================================================================== + +Stat selection window + +=============================================================================== +*/ + +class rvStatWindow { +public: + rvStatWindow(); + void SetupStatWindow( idUserInterface* statHud, bool useSpectator = false ); + void SelectPlayer( int clientNum ); + int ClientNumFromSelection( int selectionIndex, int selectionTeam ); + void ClearWindow( void ); + int GetSelectedClientNum( int* selectionIndexOut, int* selectionTeamOut ); +private: + idList stroggPlayers; + idList marinePlayers; + idList players; + idList spectators; + + idUserInterface* statHud; +}; + + +#endif diff --git a/src/mpgame/mpgame.def b/src/mpgame/mpgame.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/src/mpgame/mpgame.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/src/mpgame/physics/Clip.cpp b/src/mpgame/physics/Clip.cpp new file mode 100644 index 0000000..3b1643a --- /dev/null +++ b/src/mpgame/physics/Clip.cpp @@ -0,0 +1,2180 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define MAX_SECTOR_DEPTH 12 +#define MAX_SECTORS ((1<<(MAX_SECTOR_DEPTH+1))-1) + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +typedef struct clipSector_s { + int contents; + int dynamicContents; + struct clipLink_s * clipLinks; +} clipSector_t; +// RAVEN END + +typedef struct clipLink_s { + idClipModel * clipModel; + struct clipSector_s * sector; + struct clipLink_s * prevInSector; + struct clipLink_s * nextInSector; + struct clipLink_s * nextLink; +} clipLink_t; + +typedef struct trmCache_s { + idTraceModel trm; + int refCount; + float volume; + idVec3 centerOfMass; + idMat3 inertiaTensor; + const idMaterial * material; + idCollisionModel * collisionModel; + int hash; // only used to identify non-hashed trm's in a save. +} trmCache_t; + +idVec3 vec3_boxEpsilon( CM_BOX_EPSILON, CM_BOX_EPSILON, CM_BOX_EPSILON ); + +// RAVEN BEGIN +// jnewquist: Mark memory tags for idBlockAlloc +idBlockAlloc clipLinkAllocator; +// RAVEN END + +typedef enum { + CPT_NONE = -1, + CPT_TRANSLATION, + CPT_CONTACTS, + CPT_CONTENTS, + + CPT_MAX_TYPES +} CLIP_PROFILE_TYPES; + +#if 0 + +static idTimer clipProfileTimer[ CPT_MAX_TYPES ]; +static CLIP_PROFILE_TYPES currentProfileType = CPT_NONE; +static int clipProfileDepth = 0; + +static void BeginClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth++; + + if ( clipProfileDepth == 1 ) { + clipProfileTimer[ type ].Start(); + currentProfileType = type; + } +} + +static void EndClipProfile( CLIP_PROFILE_TYPES type ) { + clipProfileDepth--; + + if ( clipProfileDepth == 0 ) { + clipProfileTimer[ currentProfileType ].Stop(); + currentProfileType = CPT_NONE; + } +} + +void ClearClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + clipProfileTimer[ i ].Clear(); + } +} + +void DisplayClipProfile( void ) { + for( int i = 0; i < CPT_MAX_TYPES; i++ ) { + common->Printf( "%d:%d ", i, ( int )clipProfileTimer[ i ].Milliseconds() ); + } + common->Printf( "\n" ); +} +#else + +#define BeginClipProfile( type ) + +#define EndClipProfile( type ) + +#endif + + +/* +=============================================================== + + idClipModel trace model cache + +=============================================================== +*/ + +static idList traceModelCache; +static idHashIndex traceModelHash; + +/* +=============== +idClipModel::ClearTraceModelCache +=============== +*/ +void idClipModel::ClearTraceModelCache( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + collisionModelManager->FreeModel( traceModelCache[i]->collisionModel ); + traceModelCache[i]->collisionModel = NULL; + } + traceModelCache.DeleteContents( true ); + traceModelHash.Free(); +} + +/* +=============== +idClipModel::CacheCollisionModels +=============== +*/ +void idClipModel::CacheCollisionModels( void ) { + int i; + + for ( i = 0; i < traceModelCache.Num(); i++ ) { + if ( traceModelCache[i]->collisionModel == NULL ) { + traceModelCache[i]->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", i ), traceModelCache[i]->trm, traceModelCache[i]->material ); + } + } +} + +/* +=============== +idClipModel::TraceModelCacheSize +=============== +*/ +int idClipModel::TraceModelCacheSize( void ) { + return traceModelCache.Num() * sizeof( idTraceModel ); +} + +/* +=============== +idClipModel::AllocTraceModel +=============== +*/ +int idClipModel::AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + int i, hashKey, traceModelIndex; + trmCache_t *entry; + + if ( notHashed ) { + hashKey = 0xffffffff; + } else { + hashKey = GetTraceModelHashKey( trm ); + + for ( i = traceModelHash.First( hashKey ); i >= 0; i = traceModelHash.Next( i ) ) { + if ( traceModelCache[i]->trm == trm ) { + traceModelCache[i]->refCount++; + return i; + } + } + } + + + entry = new trmCache_t; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->material = material; + entry->hash = hashKey; + traceModelIndex = traceModelCache.Append( entry ); + + if ( !notHashed ) { + traceModelHash.Add( hashKey, traceModelIndex ); + } + + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", traceModelIndex ), trm, material ); + + return traceModelIndex; +} + +/* +=============== +idClipModel::Replace +=============== +*/ +void idClipModel::ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed ) { + common->Error( "ReplaceTraceModel was misused. Replace can only be used on non-hashed models right now.\n" ); + return; + } + + trmCache_t *entry = traceModelCache[ index ]; + entry->trm = trm; + entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor ); + entry->refCount = 1; + entry->hash = 0xffffffff; + entry->material = material; + + if(entry->collisionModel) + { + collisionModelManager->FreeModel( entry->collisionModel ); + } + entry->collisionModel = collisionModelManager->ModelFromTrm( gameLocal.GetMapName(), va( "traceModel%d", index ), trm, material ); +} + +/* +=============== +idClipModel::FreeTraceModel +=============== +*/ +void idClipModel::FreeTraceModel( int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model" ); + return; + } + traceModelCache[traceModelIndex]->refCount--; +} + +/* +=============== +idClipModel::CopyTraceModel +=============== +*/ +int idClipModel::CopyTraceModel( const int traceModelIndex ) { + if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) { + gameLocal.Warning( "idClipModel::CopyTraceModel: tried to copy an uncached trace model" ); + return -1; + } + traceModelCache[traceModelIndex]->refCount++; + return traceModelIndex; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idTraceModel *idClipModel::GetCachedTraceModel( int traceModelIndex ) { + return &traceModelCache[traceModelIndex]->trm; +} + +/* +=============== +idClipModel::GetCachedTraceModel +=============== +*/ +idCollisionModel *idClipModel::GetCachedCollisionModel( int traceModelIndex ) { + return traceModelCache[traceModelIndex]->collisionModel; +} + +/* +=============== +idClipModel::GetTraceModelHashKey +=============== +*/ +int idClipModel::GetTraceModelHashKey( const idTraceModel &trm ) { + const idVec3 &v = trm.bounds[0]; + return ( trm.type << 8 ) ^ ( trm.numVerts << 4 ) ^ ( trm.numEdges << 2 ) ^ ( trm.numPolys << 0 ) ^ idMath::FloatHash( v.ToFloatPtr(), v.GetDimension() ); +} + +/* +=============== +idClipModel::SaveTraceModels +=============== +*/ +void idClipModel::SaveTraceModels( idSaveGame *savefile ) { + int i; + + savefile->WriteInt( traceModelCache.Num() ); + for ( i = 0; i < traceModelCache.Num(); i++ ) { + trmCache_t *entry = traceModelCache[i]; + + savefile->Write( &entry->trm, sizeof( entry->trm ) ); + savefile->WriteFloat( entry->volume ); + savefile->WriteVec3( entry->centerOfMass ); + savefile->WriteMat3( entry->inertiaTensor ); + savefile->WriteMaterial( entry->material ); + savefile->WriteInt( entry->hash ); + } +} + +/* +=============== +idClipModel::RestoreTraceModels +=============== +*/ +void idClipModel::RestoreTraceModels( idRestoreGame *savefile ) { + int i, num; + + ClearTraceModelCache(); + + savefile->ReadInt( num ); + traceModelCache.SetNum( num ); + for ( i = 0; i < num; i++ ) { + trmCache_t *entry = new trmCache_t; + + savefile->Read( &entry->trm, sizeof( entry->trm ) ); + savefile->ReadFloat( entry->volume ); + savefile->ReadVec3( entry->centerOfMass ); + savefile->ReadMat3( entry->inertiaTensor ); + savefile->ReadMaterial( entry->material ); + savefile->ReadInt( entry->hash ); + + entry->refCount = 0; + entry->collisionModel = NULL; + + traceModelCache[i] = entry; + if ( entry->hash != 0xffffffff ) { + traceModelHash.Add( GetTraceModelHashKey( entry->trm ), i ); + } + } + + CacheCollisionModels(); +} + + +/* +=============================================================== + + idClipModel + +=============================================================== +*/ + +/* +================ +idClipModel::FreeModel +================ +*/ +void idClipModel::FreeModel( void ) { + + if ( traceModelIndex != -1 ) { + FreeTraceModel( traceModelIndex ); + traceModelIndex = -1; + } + + if ( collisionModel != NULL ) { + collisionModelManager->FreeModel( collisionModel ); + collisionModel = NULL; + } + + renderModelHandle = -1; +} + +/* +================ +idClipModel::UpdateDynamicContents +================ +*/ +void idClipModel::UpdateDynamicContents( void ) { + idClip::UpdateDynamicContents( this ); +} + +/* +================ +idClipModel::LoadModel +================ +*/ +bool idClipModel::LoadModel( const char *name ) { + FreeModel(); + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), name ); + if ( collisionModel != NULL ) { + collisionModel->GetBounds( bounds ); + collisionModel->GetContents( contents ); + return true; + } else { + bounds.Zero(); + return false; + } +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed ) { + if ( !notHashed || traceModelIndex == -1 ) { + FreeModel(); + traceModelIndex = AllocTraceModel( trm, material, notHashed ); + } else { + ReplaceTraceModel( traceModelIndex, trm, material, notHashed ); + } + + bounds = trm.bounds; +} + +/* +================ +idClipModel::LoadModel +================ +*/ +void idClipModel::LoadModel( const int renderModelHandle ) { + FreeModel(); + this->renderModelHandle = renderModelHandle; + if ( renderModelHandle != -1 ) { + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + bounds = renderEntity->bounds; + } + } +} + +/* +================ +idClipModel::Init +================ +*/ +void idClipModel::Init( void ) { + enabled = true; + entity = NULL; + id = 0; + owner = NULL; + origin.Zero(); + axis.Identity(); + bounds.Zero(); + absBounds.Zero(); + contents = CONTENTS_BODY; + collisionModel = NULL; + renderModelHandle = -1; + traceModelIndex = -1; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( void ) { + Init(); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const char *name ) { + Init(); + LoadModel( name ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idTraceModel &trm, const idMaterial *material ) { + Init(); + LoadModel( trm, material ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const int renderModelHandle ) { + Init(); + contents = CONTENTS_RENDERMODEL; + LoadModel( renderModelHandle ); +} + +/* +================ +idClipModel::idClipModel +================ +*/ +idClipModel::idClipModel( const idClipModel *model ) { + enabled = model->enabled; + entity = model->entity; + id = model->id; + owner = model->owner; + origin = model->origin; + axis = model->axis; + bounds = model->bounds; + absBounds = model->absBounds; + contents = model->contents; + collisionModel = NULL; + if ( model->collisionModel != NULL ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), model->collisionModel->GetName() ); + } + traceModelIndex = -1; + if ( model->traceModelIndex != -1 ) { + traceModelIndex = CopyTraceModel( model->traceModelIndex ); + } + renderModelHandle = model->renderModelHandle; + clipLinks = NULL; + touchCount = -1; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + checked = false; +// RAVEN END +} + +/* +================ +idClipModel::~idClipModel +================ +*/ +idClipModel::~idClipModel( void ) { + // make sure the clip model is no longer linked + Unlink(); + FreeModel(); +} + +/* +================ +idClipModel::Save +================ +*/ +void idClipModel::Save( idSaveGame *savefile ) const { + savefile->WriteBool( enabled ); + savefile->WriteObject( entity ); + savefile->WriteInt( id ); + savefile->WriteObject( owner ); + savefile->WriteVec3( origin ); + savefile->WriteMat3( axis ); + savefile->WriteBounds( bounds ); + savefile->WriteBounds( absBounds ); + savefile->WriteInt( contents ); + if ( collisionModel != NULL ) { + savefile->WriteString( collisionModel->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteInt( traceModelIndex ); + savefile->WriteInt( renderModelHandle ); + savefile->WriteBool( clipLinks != NULL ); + savefile->WriteInt( touchCount ); + + savefile->WriteBool ( checked ); // cnicholson: Added unsaved var +} + +/* +================ +idClipModel::Restore +================ +*/ +void idClipModel::Restore( idRestoreGame *savefile ) { + idStr collisionModelName; + bool linked; + + savefile->ReadBool( enabled ); + savefile->ReadObject( reinterpret_cast( entity ) ); + savefile->ReadInt( id ); + savefile->ReadObject( reinterpret_cast( owner ) ); + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); + savefile->ReadBounds( bounds ); + savefile->ReadBounds( absBounds ); + savefile->ReadInt( contents ); + savefile->ReadString( collisionModelName ); + if ( collisionModelName.Length() ) { + collisionModel = collisionModelManager->LoadModel( gameLocal.GetMapName(), collisionModelName ); + } else { + collisionModel = NULL; + } + savefile->ReadInt( traceModelIndex ); + if ( traceModelIndex >= 0 ) { + traceModelCache[traceModelIndex]->refCount++; + } + savefile->ReadInt( renderModelHandle ); + savefile->ReadBool( linked ); + savefile->ReadInt( touchCount ); + + savefile->ReadBool ( checked ); // cnicholson: Added unrestored var + + // the render model will be set when the clip model is linked + renderModelHandle = -1; + clipLinks = NULL; + touchCount = -1; + + if ( linked ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + Link( entity, id, origin, axis, renderModelHandle ); +// RAVEN END + } +} + +/* +================ +idClipModel::SetPosition +================ +*/ +void idClipModel::SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ) { + if ( clipLinks ) { + Unlink(); // unlink from old position + } + origin = newOrigin; + axis = newAxis; +} + +/* +================ +idClipModel::GetCollisionModel +================ +*/ +idCollisionModel * idClipModel::GetCollisionModel( void ) const { + assert( renderModelHandle == -1 ); + if ( collisionModel != NULL ) { + return collisionModel; + } else if ( traceModelIndex != -1 ) { + return GetCachedCollisionModel( traceModelIndex ); + } else { + // this happens in multiplayer on the combat models + if ( entity ) { + gameLocal.Warning( "idClipModel::GetCollisionModel: clip model %d on '%s' (%x) is not a collision or trace model", id, entity->name.c_str(), entity->entityNumber ); + } + return 0; + } +} + +/* +================ +idClipModel::GetMassProperties +================ +*/ +void idClipModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const { + if ( traceModelIndex == -1 ) { + gameLocal.Error( "idClipModel::GetMassProperties: clip model %d on '%s' is not a trace model\n", id, entity->name.c_str() ); + } + + trmCache_t *entry = traceModelCache[traceModelIndex]; + mass = entry->volume * density; + centerOfMass = entry->centerOfMass; + inertiaTensor = density * entry->inertiaTensor; +} + +/* +=============== +idClipModel::Unlink +=============== +*/ +void idClipModel::Unlink( void ) { + clipLink_t *link; + + for ( link = clipLinks; link; link = clipLinks ) { + clipLinks = link->nextLink; + if ( link->prevInSector ) { + link->prevInSector->nextInSector = link->nextInSector; + } else { + link->sector->clipLinks = link->nextInSector; + } + if ( link->nextInSector ) { + link->nextInSector->prevInSector = link->prevInSector; + } +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idClip::UpdateDynamicContents( link->sector ); +// RAVEN END + + clipLinkAllocator.Free( link ); + } +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( void ) { + + assert( idClipModel::entity ); + if ( !idClipModel::entity ) { + return; + } + + idClip* clp = gameLocal.GetEntityClipWorld( idClipModel::entity ); + + if ( clipLinks ) { + Unlink(); // unlink from old position + } + + if ( bounds.IsCleared() ) { + return; + } + + // set the abs box + if ( axis.IsRotated() ) { + // expand for rotation + absBounds.FromTransformedBounds( bounds, origin, axis ); + } else { + // normal + absBounds[0] = bounds[0] + origin; + absBounds[1] = bounds[1] + origin; + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + absBounds[0] -= vec3_boxEpsilon; + absBounds[1] += vec3_boxEpsilon; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + clp->CoordsForBounds( coords, absBounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clp->clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + sector->dynamicContents |= GetContents(); + + clipLink_t* link = clipLinkAllocator.Alloc(); + link->clipModel = this; + link->sector = sector; + link->nextInSector = sector->clipLinks; + link->prevInSector = NULL; + if ( sector->clipLinks ) { + sector->clipLinks->prevInSector = link; + } + sector->clipLinks = link; + link->nextLink = clipLinks; + clipLinks = link; + } + } +// RAVEN END +} + +/* +=============== +idClipModel::Link +=============== +*/ +// RAVEN BEGIN +// ddynerman: multiple clip worlds +void idClipModel::Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle ) { + this->entity = ent; + this->id = newId; + this->origin = newOrigin; + this->axis = newAxis; + if ( renderModelHandle != -1 ) { + this->renderModelHandle = renderModelHandle; + const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle ); + if ( renderEntity ) { + this->bounds = renderEntity->bounds; + } + } + this->Link(); +} +// RAVEN END + + +/* +=============================================================== + + idClip + +=============================================================== +*/ + +// RAVEN BEGIN +// ddynerman: change to static +idClipModel idClip::defaultClipModel; + +idClipModel *idClip::DefaultClipModel( void ) { + // initialize a default clip model + if( defaultClipModel.traceModelIndex == -1 ) { + defaultClipModel.LoadModel( idTraceModel( idBounds( idVec3( 0, 0, 0 ) ).Expand( 8 ) ), NULL ); + } + + return &defaultClipModel; +} + +void idClip::FreeDefaultClipModel( void ) { + if ( defaultClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( defaultClipModel.traceModelIndex ); + defaultClipModel.traceModelIndex = -1; + } +} +// RAVEN END + +/* +=============== +idClip::idClip +=============== +*/ +idClip::idClip( void ) { + clipSectors = NULL; + world = NULL; + worldBounds.Zero(); + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::CreateClipSectors_r + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +clipSector_t *idClip::CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ) { +// RAVEN BEGIN +// ddynerman: SD's clip sector code + if( clipSectors ) { + delete[] clipSectors; + clipSectors = NULL; + } + nodeOffsetVisual = bounds[ 0 ]; + + int i; + for( i = 0; i < 3; i++ ) { +//jshepard: this crashes too often +#ifdef _DEBUG + if( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ) { + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + } else { + gameLocal.Error("zero size bounds while creating clipsectors"); + nodeScale[ i ] = depth; + } + if( nodeScale[ i ] ) { + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + } else { + gameLocal.Error("zero size nodeScale while creating clipsectors"); + nodeOffset[ i] = nodeOffset[ i] + 0.5f; + } +#else + nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] ); + nodeOffset[ i ] = nodeOffsetVisual[ i ] + ( 0.5f / nodeScale[ i ] ); + +#endif + } + + clipSectors = new clipSector_t[ Square( depth ) ]; + memset( clipSectors, 0, Square( depth ) * sizeof( clipSector_t ) ); + return clipSectors; +// RAVEN END +} + +/* +=============== +idClip::Init +=============== +*/ +void idClip::Init( void ) { + idVec3 size, maxSector = vec3_origin; + + // get world map bounds + world = collisionModelManager->LoadModel( gameLocal.GetMapName(), WORLD_MODEL_NAME ); + world->GetBounds( worldBounds ); + + // create world sectors +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation + RV_PUSH_HEAP_MEM(this); +// ddynerman: SD's clip sector code + CreateClipSectors_r( CLIPSECTOR_WIDTH, worldBounds, maxSector ); + GetClipSectorsStaticContents(); +// mwhitlock: Dynamic memory consolidation + RV_POP_HEAP(); +// RAVEN END + + size = worldBounds[1] - worldBounds[0]; + gameLocal.Printf( "map bounds are (%1.1f, %1.1f, %1.1f)\n", size[0], size[1], size[2] ); + gameLocal.Printf( "max clip sector is (%1.1f, %1.1f, %1.1f)\n", maxSector[0], maxSector[1], maxSector[2] ); + + // set counters to zero + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +/* +=============== +idClip::Shutdown +=============== +*/ +void idClip::Shutdown( void ) { + delete[] clipSectors; + clipSectors = NULL; + + // free the trace model used for the temporaryClipModel + if ( temporaryClipModel.traceModelIndex != -1 ) { + idClipModel::FreeTraceModel( temporaryClipModel.traceModelIndex ); + temporaryClipModel.traceModelIndex = -1; + } + + clipLinkAllocator.Shutdown(); +} + +/* +==================== +idClip::ClipModelsTouchingBounds_r +==================== +*/ +typedef struct listParms_s { + idBounds bounds; + int contentMask; + idClipModel ** list; + int count; + int maxCount; +} listParms_t; + + +/* +================ +idClip::ClipModelsTouchingBounds +================ +*/ +int idClip::ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const { + listParms_t parms; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int clipCount = 0; + static idClipModel* clipModels[ MAX_GENTITIES ]; + + assert( maxCount <= MAX_GENTITIES ); + if( maxCount > MAX_GENTITIES ) { + maxCount = MAX_GENTITIES; + } +// RAVEN END + + if ( bounds[0][0] > bounds[1][0] || + bounds[0][1] > bounds[1][1] || + bounds[0][2] > bounds[1][2] ) { + // we should not go through the tree for degenerate or backwards bounds + assert( false ); + return 0; + } + + parms.bounds[0] = bounds[0] - vec3_boxEpsilon; + parms.bounds[1] = bounds[1] + vec3_boxEpsilon; + parms.contentMask = contentMask; + parms.list = clipModelList; + parms.count = 0; + parms.maxCount = maxCount; + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + int coords[ 4 ]; + CoordsForBounds( coords, parms.bounds ); + + int x, y; + for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) { + for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) { + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + if( !( sector->dynamicContents & contentMask ) ) { + continue; + } + + for ( clipLink_t* link = sector->clipLinks; link && clipCount < MAX_GENTITIES; link = link->nextInSector ) { + idClipModel* model = link->clipModel; + + if( model->checked || !model->enabled || !( model->GetContents() & contentMask ) ) { + continue; + } + + model->checked = true; + clipModels[ clipCount++ ] = model; + } + } + } + + for( x = 0; x < clipCount; x++ ) { + clipModels[ x ]->checked = false; + } + + for( x = 0; x < clipCount; x++ ) { + idClipModel* model = clipModels[ x ]; + + // if the bounds really do overlap + if ( model->absBounds[0].x > parms.bounds[1].x || + model->absBounds[1].x < parms.bounds[0].x || + model->absBounds[0].y > parms.bounds[1].y || + model->absBounds[1].y < parms.bounds[0].y || + model->absBounds[0].z > parms.bounds[1].z || + model->absBounds[1].z < parms.bounds[0].z ) { + continue; + } + + if( parms.count >= parms.maxCount ) { +// gameLocal.Warning( "idClip::ClipModelsTouchingBounds Max Count Hit\n" ); + break; + } + + parms.list[ parms.count++ ] = model; + } +// RAVEN END + + return parms.count; +} + +/* +================ +idClip::EntitiesTouchingBounds +================ +*/ +int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, entCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + entCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < entCount; j++ ) { + if ( entityList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= entCount ) { + if ( entCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return entCount; + } + entityList[entCount] = clipModelList[i]->entity; + entCount++; + } + } + + return entCount; +} + +// RAVEN BEGIN +// ddynerman: MP helper function +/* +================ +idClip::PlayersTouchingBounds +================ +*/ +int idClip::PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **playerList, int maxCount ) const { + idClipModel *clipModelList[MAX_GENTITIES]; + int i, j, count, playerCount; + + count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + playerCount = 0; + for ( i = 0; i < count; i++ ) { + // entity could already be in the list because an entity can use multiple clip models + for ( j = 0; j < playerCount; j++ ) { + if ( playerList[j] == clipModelList[i]->entity ) { + break; + } + } + if ( j >= playerCount ) { + if ( playerCount >= maxCount ) { + gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" ); + return playerCount; + } +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( clipModelList[i]->entity->IsType( idPlayer::GetClassType() ) ) { +// RAVEN END + playerList[playerCount] = static_cast(clipModelList[i]->entity); + playerCount++; + } + } + } + + return playerCount; +} +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + +/* +==================== +idClip::DrawAreaClipSectors +==================== +*/ +void idClip::DrawAreaClipSectors( float range ) const { + idClipModel* clipModels[ MAX_GENTITIES ]; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + idBounds bounds; + bounds[0] = player->GetPhysics()->GetOrigin() - idVec3( range, range, range ); + bounds[1] = player->GetPhysics()->GetOrigin() + idVec3( range, range, range ); + + int count = ClipModelsTouchingBounds( bounds, MASK_ALL, clipModels, MAX_GENTITIES ); + + int i; + for ( i = 0; i < count; i++ ) { + idEntity* owner = clipModels[ i ]->GetEntity(); + + const idVec3& org = clipModels[ i ]->GetOrigin(); + const idBounds& bounds = clipModels[ i ]->GetBounds(); + + gameRenderWorld->DebugBounds( colorCyan, bounds, org ); + gameRenderWorld->DrawText( owner->GetClassname(), org, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + } +} + +/* +==================== +idClip::DrawClipSectors +==================== +*/ +void idClip::DrawClipSectors( void ) const { + idBounds bounds; + + idPlayer* player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + int i; + idVec3 inverseNodeScale; + for( i = 0; i < 3; i++ ) { + inverseNodeScale[ i ] = 1 / nodeScale[ i ]; + } + + const char* filter = g_showClipSectorFilter.GetString(); + idTypeInfo* type = idClass::GetClass( filter ); + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { +// idWinding w( 4 ); + + bounds[ 0 ].x = ( inverseNodeScale.x * x ) + nodeOffsetVisual.x + 1; + bounds[ 0 ].y = ( inverseNodeScale.y * y ) + nodeOffsetVisual.y + 1; + bounds[ 0 ].z = player->GetPhysics()->GetBounds()[0].z; + + bounds[ 1 ].x = ( inverseNodeScale.x * ( x + 1 ) ) + nodeOffsetVisual.x - 1; + bounds[ 1 ].y = ( inverseNodeScale.y * ( y + 1 ) ) + nodeOffsetVisual.y - 1; + bounds[ 1 ].z = player->GetPhysics()->GetBounds()[1].z; + + idVec3 point; + point.x = ( bounds[ 0 ].x + bounds[ 1 ].x ) * 0.5f; + point.y = ( bounds[ 0 ].y + bounds[ 1 ].y ) * 0.5f; + point.z = 0.f; + +/* point.x = bounds[ 0 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 0 ].y; + w.AddPoint( point ); + + point.x = bounds[ 1 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point ); + + point.x = bounds[ 0 ].x; + point.y = bounds[ 1 ].y; + w.AddPoint( point );*/ + + clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ]; + + clipLink_t* link = sector->clipLinks; + while ( link ) { + if ( type && !link->clipModel->GetEntity()->IsType( *type ) ) { + link = link->nextInSector; + } else { + break; + } + } + + if( link ) { + + gameRenderWorld->DrawText( link->clipModel->GetEntity()->GetClassname(), point, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 ); + gameRenderWorld->DebugBounds( colorMagenta, bounds ); + gameRenderWorld->DebugBounds( colorYellow, link->clipModel->GetBounds(), link->clipModel->GetOrigin() ); + + } else { + +// gameRenderWorld->DrawText( sector->clipLinks->clipModel->GetEntity()->GetClassname(), point, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + + } + } + } +} + +/* +==================== +idClip::GetClipSectorsStaticContents +==================== +*/ +void idClip::GetClipSectorsStaticContents( void ) { + idBounds bounds; + + bounds[ 0 ].x = 0; + bounds[ 0 ].y = 0; + bounds[ 0 ].z = worldBounds[ 0 ].z; + + bounds[ 1 ].x = 1 / nodeScale.x; + bounds[ 1 ].y = 1 / nodeScale.y; + bounds[ 1 ].z = worldBounds[ 1 ].z; + + idTraceModel* trm = new idTraceModel( bounds ); + + idVec3 org; + org.z = 0; + + int x; + for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) { + int y; + for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) { + org.x = ( x / nodeScale.x ) + nodeOffset.x; + org.y = ( y / nodeScale.y ) + nodeOffset.y; + + int contents = collisionModelManager->Contents( org, trm, mat3_identity, -1, world, vec3_origin, mat3_default ); + clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ].contents = contents; + } + } + // mwhitlock: Fix leak in SD's code. + delete trm; +} + +// RAVEN END + +/* +==================== +idClip::GetTraceClipModels + + an ent will be excluded from testing if: + cm->entity == passEntity ( don't clip against the pass entity ) + cm->entity == passOwner ( missiles don't clip with owner ) + cm->owner == passEntity ( don't interact with your own missiles ) + cm->owner == passOwner ( don't interact with other missiles from same owner ) +==================== +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +int idClip::GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 ) const { +// RAVEN END + int i, num; + idClipModel *cm; + idEntity *passOwner; + + num = ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES ); + + if ( !passEntity ) { + return num; + } + + if ( passEntity->GetPhysics()->GetNumClipModels() > 0 ) { + passOwner = passEntity->GetPhysics()->GetClipModel()->GetOwner(); + } else { + passOwner = NULL; + } + + for ( i = 0; i < num; i++ ) { + + cm = clipModelList[i]; + + // check if we should ignore this entity + if ( cm->entity == passEntity ) { + clipModelList[i] = NULL; // don't clip against the pass entity + } +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + else if ( cm->entity == passEntity2 ){ + clipModelList[i] = NULL; +// RAVEN END + } else if ( cm->entity == passOwner ) { + clipModelList[i] = NULL; // missiles don't clip with their owner + } else if ( cm->owner ) { + if ( cm->owner == passEntity ) { + clipModelList[i] = NULL; // don't clip against own missiles + } else if ( cm->owner == passOwner ) { + clipModelList[i] = NULL; // don't clip against other missiles from same owner + } + } + } + + return num; +} + +/* +============ +idClip::TraceRenderModel +============ +*/ +void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const { + trace.fraction = 1.0f; + + // if the trace is passing through the bounds + if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) { + modelTrace_t modelTrace; + + // test with exact render model and modify trace_t structure accordingly + if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) { + trace.fraction = modelTrace.fraction; + trace.endAxis = axis; + trace.endpos = modelTrace.point; + trace.c.normal = modelTrace.normal; + trace.c.dist = modelTrace.point * modelTrace.normal; + trace.c.point = modelTrace.point; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.modelFeature = 0; + trace.c.trmFeature = 0; + trace.c.contents = modelTrace.material->GetContentFlags(); + trace.c.material = modelTrace.material; + +// RAVEN BEGIN +// jscott: for material types + trace.c.materialType = modelTrace.materialType; +// RAVEN END + + // NOTE: trace.c.id will be the joint number + touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber ); + } + } +} + +/* +============ +idClip::TraceModelForClipModel +============ +*/ +const idTraceModel *idClip::TraceModelForClipModel( const idClipModel *mdl ) const { + if ( !mdl ) { + return NULL; + } else { + if ( !mdl->IsTraceModel() ) { + if ( mdl->GetEntity() ) { + gameLocal.Error( "TraceModelForClipModel: clip model %d on '%s' is not a trace model\n", mdl->GetId(), mdl->GetEntity()->name.c_str() ); + } else { + gameLocal.Error( "TraceModelForClipModel: clip model %d is not a trace model\n", mdl->GetId() ); + } + } + return idClipModel::GetCachedTraceModel( mdl->traceModelIndex ); + } +} + +/* +============ +idClip::TestHugeTranslation +============ +*/ +ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) { + if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { + assert( 0 ); + + results.fraction = 0.0f; + results.endpos = start; + results.endAxis = trmAxis; + memset( &results.c, 0, sizeof( results.c ) ); + results.c.point = start; + results.c.entityNum = ENTITYNUM_WORLD; + + if ( mdl->GetEntity() ) { + gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); + } else { + gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() ); + } + return true; + } + return false; +} + +/* +============ +idClip::TranslationEntities +============ +*/ +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases +void idClip::TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return; + } + + trm = TraceModelForClipModel( mdl ); + + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + + if ( !trm ) { + traceBounds.FromPointTranslation( start, end - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, end - start ); + radius = trm->bounds.GetRadius(); + } + +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); +// RAVEN END + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } +} + +/* +============ +idClip::Translation +============ +*/ +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation +bool idClip::Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 ) { +// RAVEN END + BeginClipProfile( CPT_TRANSLATION ); + + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + float radius; + trace_t trace; + const idTraceModel *trm; + +// RAVEN BEGIN +// rjohnson: added debug line drawing for traces + if ( g_showCollisionTraces.GetInteger() >= 2 && !g_stopTime.GetBool() ) { + gameRenderWorld->DebugLine( colorCyan, start, end, 1000 ); + } +// RAVEN END + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + EndClipProfile( CPT_TRANSLATION ); + return true; + } + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + EndClipProfile( CPT_TRANSLATION ); + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = end; + results.endAxis = trmAxis; + } + + if ( !trm ) { + traceBounds.FromPointTranslation( start, results.endpos - start ); + radius = 0.0f; + } else { + traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, results.endpos - start ); + radius = trm->bounds.GetRadius(); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, passEntity2 ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + + results.c.materialType = trace.c.materialType; + + if ( touch->IsTraceModel( ) ) { + results.c.contents = touch->GetContents( ); + } + + if ( results.fraction == 0.0f ) { + break; + } + } + } + + EndClipProfile( CPT_TRANSLATION ); + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Rotation +============ +*/ +bool idClip::Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + trace_t trace; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( results.fraction == 0.0f ) { + return true; // blocked immediately by the world + } + } else { + memset( &results, 0, sizeof( results ) ); + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( !trm ) { + traceBounds.FromPointRotation( start, rotation ); + } else { + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, start, rotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < results.fraction ) { + results = trace; + results.c.entityNum = touch->entity->entityNumber; + results.c.id = touch->id; + if ( results.fraction == 0.0f ) { + break; + } + } + } + + return ( results.fraction < 1.0f ); +} + +/* +============ +idClip::Motion +============ +*/ +bool idClip::Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + int i, num; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idVec3 dir, endPosition; + idBounds traceBounds; + float radius; + trace_t translationalTrace, rotationalTrace, trace; + idRotation endRotation; + const idTraceModel *trm; + + assert( rotation.GetOrigin() == start ); + + if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) { + return true; + } + + if ( mdl != NULL && rotation.GetAngle() != 0.0f && rotation.GetVec() != vec3_origin ) { + // if no translation + if ( start == end ) { + // pure rotation + return Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity ); + } + } else if ( start != end ) { + // pure translation + return Translation( results, start, end, mdl, trmAxis, contentMask, passEntity ); + } else { + // no motion + results.fraction = 1.0f; + results.endpos = start; + results.endAxis = trmAxis; + return false; + } + + trm = TraceModelForClipModel( mdl ); + + radius = trm->bounds.GetRadius(); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // translational collision with world + idClip::numTranslations++; + collisionModelManager->Translation( &translationalTrace, start, end, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + translationalTrace.c.entityNum = translationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &translationalTrace, 0, sizeof( translationalTrace ) ); + translationalTrace.fraction = 1.0f; + translationalTrace.endpos = end; + translationalTrace.endAxis = trmAxis; + } + + if ( translationalTrace.fraction != 0.0f ) { + + traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation ); + dir = translationalTrace.endpos - start; + for ( i = 0; i < 3; i++ ) { + if ( dir[i] < 0.0f ) { + traceBounds[0][i] += dir[i]; + } + else { + traceBounds[1][i] += dir[i]; + } + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + if ( touch->renderModelHandle != -1 ) { + idClip::numRenderModelTraces++; + TraceRenderModel( trace, start, end, radius, trmAxis, touch ); + } else { + idClip::numTranslations++; + collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + } + + if ( trace.fraction < translationalTrace.fraction ) { + translationalTrace = trace; + translationalTrace.c.entityNum = touch->entity->entityNumber; + translationalTrace.c.id = touch->id; + if ( translationalTrace.fraction == 0.0f ) { + break; + } + } + } + } else { + num = -1; + } + + endPosition = translationalTrace.endpos; + endRotation = rotation; + endRotation.SetOrigin( endPosition ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // rotational collision with world + idClip::numRotations++; + collisionModelManager->Rotation( &rotationalTrace, endPosition, endRotation, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + rotationalTrace.c.entityNum = rotationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } else { + memset( &rotationalTrace, 0, sizeof( rotationalTrace ) ); + rotationalTrace.fraction = 1.0f; + rotationalTrace.endpos = endPosition; + rotationalTrace.endAxis = trmAxis * rotation.ToMat3(); + } + + if ( rotationalTrace.fraction != 0.0f ) { + + if ( num == -1 ) { + traceBounds.FromBoundsRotation( trm->bounds, endPosition, trmAxis, endRotation ); + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + } + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no rotational collision detection with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numRotations++; + collisionModelManager->Rotation( &trace, endPosition, endRotation, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + if ( trace.fraction < rotationalTrace.fraction ) { + rotationalTrace = trace; + rotationalTrace.c.entityNum = touch->entity->entityNumber; + rotationalTrace.c.id = touch->id; + if ( rotationalTrace.fraction == 0.0f ) { + break; + } + } + } + } + + if ( rotationalTrace.fraction < 1.0f ) { + results = rotationalTrace; + } else { + results = translationalTrace; + results.endAxis = rotationalTrace.endAxis; + } + + results.fraction = Max( translationalTrace.fraction, rotationalTrace.fraction ); + + return ( translationalTrace.fraction < 1.0f || rotationalTrace.fraction < 1.0f ); +} + +/* +============ +idClip::Contacts +============ +*/ +int idClip::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) { + BeginClipProfile( CPT_CONTACTS ); + + int i, j, num, n, numContacts; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContacts++; + numContacts = collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + numContacts = 0; + } + + for ( i = 0; i < numContacts; i++ ) { + contacts[i].entityNum = ENTITYNUM_WORLD; + contacts[i].id = 0; + } + + if ( numContacts >= maxContacts ) { + EndClipProfile( CPT_CONTACTS ); + return numContacts; + } + + if ( !trm ) { + traceBounds = idBounds( start ).Expand( depth ); + } else { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + traceBounds.ExpandSelf( depth ); + } + + num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contacts with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + idClip::numContacts++; + n = collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts, + start, dir, depth, trm, trmAxis, contentMask, + touch->GetCollisionModel(), touch->origin, touch->axis ); + + for ( j = 0; j < n; j++ ) { + contacts[numContacts].entityNum = touch->entity->entityNumber; + contacts[numContacts].id = touch->id; + numContacts++; + } + + if ( numContacts >= maxContacts ) { + break; + } + } + + EndClipProfile( CPT_CONTACTS ); + + return numContacts; +} + +/* +============ +idClip::Contents +============ +*/ +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. +int idClip::Contents( const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity ) { +// RAVEN END + BeginClipProfile( CPT_CONTENTS ); + + int i, num, contents; + idClipModel *touch, *clipModelList[MAX_GENTITIES]; + idBounds traceBounds; + const idTraceModel *trm; + + trm = TraceModelForClipModel( mdl ); + + if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) { + // test world + idClip::numContents++; + contents = collisionModelManager->Contents( start, trm, trmAxis, contentMask, world, vec3_origin, mat3_default ); + } else { + contents = 0; + } + + if ( !trm ) { + traceBounds[0] = start; + traceBounds[1] = start; + } else if ( trmAxis.IsRotated() ) { + traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis ); + } else { + traceBounds[0] = trm->bounds[0] + start; + traceBounds[1] = trm->bounds[1] + start; + } + + num = GetTraceClipModels( traceBounds, -1, passEntity, clipModelList ); + + for ( i = 0; i < num; i++ ) { + touch = clipModelList[i]; + + if ( !touch ) { + continue; + } + + // no contents test with render models + if ( touch->renderModelHandle != -1 ) { + continue; + } + + // if the entity does not have any contents we are looking for + if ( ( touch->contents & contentMask ) == 0 ) { + continue; + } + + // if the entity has no new contents flags + if ( ( touch->contents & contents ) == touch->contents ) { + continue; + } + + idClip::numContents++; + if ( collisionModelManager->Contents( start, trm, trmAxis, contentMask, touch->GetCollisionModel(), touch->origin, touch->axis ) ) { + contents |= ( touch->contents & contentMask ); + } + +// RAVEN BEGIN + // Only sends back one entity for now. Ahh well, if this is a problem, have it send back a list... + if ( touchedEntity ) + { + *touchedEntity = touch->GetEntity(); + } +// RAVEN END + } + + EndClipProfile( CPT_CONTENTS ); + + return contents; +} + +/* +============ +idClip::TranslationModel +============ +*/ +void idClip::TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numTranslations++; + collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::RotationModel +============ +*/ +void idClip::RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numRotations++; + collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContactsModel +============ +*/ +int idClip::ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContacts++; + return collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::ContentsModel +============ +*/ +int idClip::ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) { + const idTraceModel *trm = TraceModelForClipModel( mdl ); + idClip::numContents++; + return collisionModelManager->Contents( start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis ); +} + +/* +============ +idClip::GetModelContactFeature +============ +*/ +bool idClip::GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const { + int i; + idCollisionModel *model; + idVec3 start, end; + + model = NULL; + winding.Clear(); + + if ( clipModel == NULL ) { + model = NULL; + } else { + if ( clipModel->renderModelHandle != -1 ) { + winding += contact.point; + return true; + } else if ( clipModel->traceModelIndex != -1 ) { + model = idClipModel::GetCachedCollisionModel( clipModel->traceModelIndex ); + } else { + model = clipModel->collisionModel; + } + } + + // if contact with a collision model + if ( model != NULL ) { + switch( contact.type ) { + case CONTACT_EDGE: { + // the model contact feature is a collision model edge + model->GetEdge( contact.modelFeature, start, end ); + winding += start; + winding += end; + break; + } + case CONTACT_MODELVERTEX: { + // the model contact feature is a collision model vertex + model->GetVertex( contact.modelFeature, start ); + winding += start; + break; + } + case CONTACT_TRMVERTEX: { + // the model contact feature is a collision model polygon + model->GetPolygon( contact.modelFeature, winding ); + break; + } + } + } + + // transform the winding to world space + if ( clipModel ) { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + winding[i].ToVec3() *= clipModel->axis; + winding[i].ToVec3() += clipModel->origin; + } + } + + return true; +} + +/* +============ +idClip::PrintStatistics +============ +*/ +void idClip::PrintStatistics( void ) { +// RAVEN BEGIN +// rjohnson: added trace model cache size + gameLocal.Printf( "t=%-3d, r=%-3d, m=%-3d, render=%-3d, contents=%-3d, contacts=%-3d, cache=%d\n", + numTranslations, numRotations, numMotions, numRenderModelTraces, numContents, numContacts, traceModelCache.Num() * sizeof( idTraceModel ) ); +// RAVEN END +} + +/* +============ +idClip::DrawClipModels +============ +*/ +void idClip::DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type ) { + int i, num; + idBounds bounds; + idClipModel *clipModelList[MAX_GENTITIES]; + idClipModel *clipModel; + + bounds = idBounds( eye ).Expand( radius ); + + num = idClip::ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < num; i++ ) { + clipModel = clipModelList[i]; + if ( clipModel->GetEntity() == passEntity ) { + continue; + } + if ( type && !clipModel->GetEntity()->IsType( *type ) ) { + continue; + } + if ( clipModel->renderModelHandle != -1 ) { + gameRenderWorld->DebugBounds( colorCyan, clipModel->GetAbsBounds() ); + continue; + } + + idCollisionModel *model = clipModel->GetCollisionModel(); + if ( model != NULL ) { + collisionModelManager->DrawModel( model, clipModel->GetOrigin(), clipModel->GetAxis(), eye, mat3_identity, radius ); + } + } +} + +/* +============ +idClip::DrawModelContactFeature +============ +*/ +bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const { + int i; + idMat3 axis; + idFixedWinding winding; + + if ( !GetModelContactFeature( contact, clipModel, winding ) ) { + return false; + } + + axis = contact.normal.ToMat3(); + + if ( winding.GetNumPoints() == 1 ) { + gameRenderWorld->DebugLine( colorCyan, winding[0].ToVec3(), winding[0].ToVec3() + 2.0f * axis[0], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[1], winding[0].ToVec3() + 1.0f * axis[1], lifetime ); + gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[2], winding[0].ToVec3() + 1.0f * axis[2], lifetime ); + } else { + for ( i = 0; i < winding.GetNumPoints(); i++ ) { + gameRenderWorld->DebugLine( colorCyan, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3(), lifetime ); + } + } + + axis[0] = -axis[0]; + axis[2] = -axis[2]; + gameRenderWorld->DrawText( contact.material->GetName(), winding.GetCenter() - 4.0f * axis[2], 0.1f, colorWhite, axis, 1, 5000 ); + + return true; +} + +// RAVEN BEGIN +// rjohnson: added debug hud support + +void idClip::DebugHudStatistics( void ) +{ + gameDebug.SetInt( "physics_translations", numTranslations ); + gameDebug.SetInt( "physics_rotations", numRotations ); + gameDebug.SetInt( "physics_motions", numMotions ); + gameDebug.SetInt( "physics_render_model_traces", numRenderModelTraces ); + gameDebug.SetInt( "physics_contents", numContents ); + gameDebug.SetInt( "physics_contacts", numContacts ); +} + +void idClip::ClearStatistics( void ) +{ + numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0; +} + +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( clipSector_t* sector ) { + sector->dynamicContents = 0; + + clipLink_t* link; + for( link = sector->clipLinks; link; link = link->nextInSector ) { + sector->dynamicContents |= link->clipModel->GetContents(); + } +} + +/* +============ +idClip::UpdateDynamicContents +============ +*/ +void idClip::UpdateDynamicContents( idClipModel* clipModel ) { + clipLink_s* link = clipModel->clipLinks; + while ( link ) { + idClip::UpdateDynamicContents( link->sector ); + link = link->nextLink; + } +} +// RAVEN END diff --git a/src/mpgame/physics/Clip.h b/src/mpgame/physics/Clip.h new file mode 100644 index 0000000..a51dcf0 --- /dev/null +++ b/src/mpgame/physics/Clip.h @@ -0,0 +1,430 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CLIP_H__ +#define __CLIP_H__ + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +const int CLIPSECTOR_DEPTH = 6; +const int CLIPSECTOR_WIDTH = 1 << CLIPSECTOR_DEPTH; +// RAVEN END + +/* +=============================================================================== + + Handles collision detection with the world and between physics objects. + +=============================================================================== +*/ + +#define CLIPMODEL_ID_TO_JOINT_HANDLE( id ) ( ( id ) >= 0 ? INVALID_JOINT : ((jointHandle_t) ( -1 - id )) ) +#define JOINT_HANDLE_TO_CLIPMODEL_ID( id ) ( -1 - id ) + +class idClip; +class idClipModel; +class idEntity; + + +//=============================================================== +// +// idClipModel +// +//=============================================================== + +class idClipModel { + + friend class idClip; + +public: + idClipModel( void ); + explicit idClipModel( const char *name ); + explicit idClipModel( const idTraceModel &trm, const idMaterial *material = NULL ); + explicit idClipModel( const int renderModelHandle ); + explicit idClipModel( const idClipModel *model ); + ~idClipModel( void ); +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void UpdateDynamicContents( void ); +// RAVEN END + + bool LoadModel( const char *name ); + void LoadModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + void LoadModel( const int renderModelHandle ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + void Link( void ); // must have been linked with an entity and id before + void Link( idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle = -1 ); +// RAVEN END + void Unlink( void ); // unlink from sectors + void SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ); // unlinks the clip model + void Translate( const idVec3 &translation ); // unlinks the clip model + void Rotate( const idRotation &rotation ); // unlinks the clip model + void Enable( void ); // enable for clipping + void Disable( void ); // keep linked but disable for clipping + void SetContents( int newContents ); // override contents + int GetContents( void ) const; + void SetEntity( idEntity *newEntity ); + idEntity * GetEntity( void ) const; + void SetId( int newId ); + int GetId( void ) const; + void SetOwner( idEntity *newOwner ); + idEntity * GetOwner( void ) const; + const idBounds & GetBounds( void ) const; + const idBounds & GetAbsBounds( void ) const; + const idVec3 & GetOrigin( void ) const; + const idMat3 & GetAxis( void ) const; + bool IsTraceModel( void ) const; // returns true if this is a trace model + bool IsRenderModel( void ) const; // returns true if this is a render model + bool IsLinked( void ) const; // returns true if the clip model is linked + bool IsEnabled( void ) const; // returns true if enabled for collision detection + bool IsEqual( const idTraceModel &trm ) const; + idCollisionModel * GetCollisionModel( void ) const; // returns handle used to collide vs this model + const idTraceModel * GetTraceModel( void ) const; + void GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const; + + static void ClearTraceModelCache( void ); + static int TraceModelCacheSize( void ); + static void SaveTraceModels( idSaveGame *savefile ); + static void RestoreTraceModels( idRestoreGame *savefile ); + +private: + bool enabled; // true if this clip model is used for clipping +// RAVEN BEGIN + bool checked; // Splash's clip model code +// RAVEN END + idEntity * entity; // entity using this clip model + int id; // id for entities that use multiple clip models + idEntity * owner; // owner of the entity that owns this clip model + idVec3 origin; // origin of clip model + idMat3 axis; // orientation of clip model + idBounds bounds; // bounds + idBounds absBounds; // absolute bounds + int contents; // all contents ored together + idCollisionModel * collisionModel; // handle to collision model + int traceModelIndex; // trace model used for collision detection + int renderModelHandle; // render model def handle + + struct clipLink_s * clipLinks; // links into sectors + int touchCount; + + void Init( void ); // initialize + void FreeModel( void ); + void Link_r( struct clipSector_s *node ); + + static void CacheCollisionModels( void ); + static int AllocTraceModel( const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void ReplaceTraceModel( int index, const idTraceModel &trm, const idMaterial *material, bool notHashed = false ); + static void FreeTraceModel( int traceModelIndex ); + static int CopyTraceModel( const int traceModelIndex ); + static idTraceModel * GetCachedTraceModel( int traceModelIndex ); + static idCollisionModel*GetCachedCollisionModel( int traceModelIndex ); + static int GetTraceModelHashKey( const idTraceModel &trm ); +}; + + +ID_INLINE void idClipModel::Translate( const idVec3 &translation ) { + Unlink(); + origin += translation; +} + +ID_INLINE void idClipModel::Rotate( const idRotation &rotation ) { + Unlink(); + origin *= rotation; + axis *= rotation.ToMat3(); +} + +ID_INLINE void idClipModel::Enable( void ) { + enabled = true; +} + +ID_INLINE void idClipModel::Disable( void ) { + enabled = false; +} + +ID_INLINE void idClipModel::SetContents( int newContents ) { + contents = newContents; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + UpdateDynamicContents(); +// RAVEN END +} + +ID_INLINE int idClipModel::GetContents( void ) const { + return contents; +} + +ID_INLINE void idClipModel::SetEntity( idEntity *newEntity ) { + entity = newEntity; +} + +ID_INLINE idEntity *idClipModel::GetEntity( void ) const { + return entity; +} + +ID_INLINE void idClipModel::SetId( int newId ) { + id = newId; +} + +ID_INLINE int idClipModel::GetId( void ) const { + return id; +} + +ID_INLINE void idClipModel::SetOwner( idEntity *newOwner ) { + owner = newOwner; +} + +ID_INLINE idEntity *idClipModel::GetOwner( void ) const { + return owner; +} + +ID_INLINE const idBounds &idClipModel::GetBounds( void ) const { + return bounds; +} + +ID_INLINE const idBounds &idClipModel::GetAbsBounds( void ) const { + return absBounds; +} + +ID_INLINE const idVec3 &idClipModel::GetOrigin( void ) const { + return origin; +} + +ID_INLINE const idMat3 &idClipModel::GetAxis( void ) const { + return axis; +} + +ID_INLINE bool idClipModel::IsRenderModel( void ) const { + return ( renderModelHandle != -1 ); +} + +ID_INLINE bool idClipModel::IsTraceModel( void ) const { + return ( traceModelIndex != -1 ); +} + +ID_INLINE bool idClipModel::IsLinked( void ) const { + return ( clipLinks != NULL ); +} + +ID_INLINE bool idClipModel::IsEnabled( void ) const { + return enabled; +} + +ID_INLINE bool idClipModel::IsEqual( const idTraceModel &trm ) const { + return ( traceModelIndex != -1 && *GetCachedTraceModel( traceModelIndex ) == trm ); +} + +ID_INLINE const idTraceModel *idClipModel::GetTraceModel( void ) const { + if ( !IsTraceModel() ) { + return NULL; + } + return idClipModel::GetCachedTraceModel( traceModelIndex ); +} + + +//=============================================================== +// +// idClip +// +//=============================================================== + +class idClip { + + friend class idClipModel; + +public: + idClip( void ); + + void Init( void ); + void Shutdown( void ); + + // clip versus the rest of the world +// RAVEN BEGIN +// nmckenzie: we have cases where both a guy and his target need to be ignored by a translation + bool Translation( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + bool Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + bool Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); + int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ); +// RAVEN BEGIN +// AReis: Added ability to get the entity that was touched as well. + int Contents( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, idEntity **touchedEntity = NULL ); +// RAVEN END + + // special case translations versus the rest of the world + bool TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, + int contentMask, const idEntity *passEntity ); + bool TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, + int contentMask, const idEntity *passEntity ); + + // clip versus a specific model + void TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + void RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + int ContentsModel( const idVec3 &start, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, + idCollisionModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ); + + // clip versus all entities but not the world +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + void TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end, + const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, const idEntity *passEntity2 = 0 ); +// RAVEN END + + // get a contact feature + bool GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const; + + // get entities/clip models within or touching the given bounds + int EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const; + int ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const; + +// RAVEN BEGIN +// ddynerman: another helper function, useful in MP + int PlayersTouchingBounds( const idBounds &bounds, int contentMask, idPlayer **entityList, int maxCount ) const; +// RAVEN END + + const idBounds & GetWorldBounds( void ) const; + idCollisionModel * GetWorldCollisionModel( void ) const { return world; } + +// RAVEN BEGIN +// ddynerman: change to static + static idClipModel * DefaultClipModel( void ); + static void FreeDefaultClipModel( void ); +// RAVEN END + + // stats and debug drawing + void PrintStatistics( void ); + void DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity, const idTypeInfo* type = NULL ); + bool DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const; + +// RAVEN BEGIN +// rjohnson: added debug hud support + void DebugHudStatistics( void ); + void ClearStatistics( void ); +// RAVEN END + +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void CoordsForBounds( int* coords, idBounds& bounds ) const; + void DrawClipSectors( void ) const; + void DrawAreaClipSectors( float range ) const; + static void UpdateDynamicContents( struct clipSector_s* sector ); + static void UpdateDynamicContents( idClipModel* clipModel ); +// RAVEN END + +private: +// RAVEN BEGIN +// ddynerman: SD's clip sector code + idVec3 nodeScale; + idVec3 nodeOffset; + idVec3 nodeOffsetVisual; +// ddynerman: change to static + static idClipModel defaultClipModel; +// RAVEN END + struct clipSector_s * clipSectors; + idCollisionModel * world; + idBounds worldBounds; + idClipModel temporaryClipModel; + + mutable int touchCount; + // statistics + int numTranslations; + int numRotations; + int numMotions; + int numRenderModelTraces; + int numContents; + int numContacts; + +private: + struct clipSector_s * CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ); + void ClipModelsTouchingBounds_r( const struct clipSector_s *node, struct listParms_s &parms ) const; + const idTraceModel * TraceModelForClipModel( const idClipModel *mdl ) const; +// RAVEN BEGIN +// nmckenzie: had to add a second pass entity so we can safely ignore both a guy and his target in some cases + int GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList, const idEntity *passEntity2 = 0 ) const; +// RAVEN END + void TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const; +// RAVEN BEGIN +// ddynerman: SD's clip sector code + void GetClipSectorsStaticContents( void ); +// RAVEN END +}; + + +ID_INLINE bool idClip::TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity ) { + Translation( results, start, end, NULL, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE bool idClip::TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { + temporaryClipModel.LoadModel( idTraceModel( bounds ), NULL, true ); + Translation( results, start, end, &temporaryClipModel, mat3_identity, contentMask, passEntity ); + return ( results.fraction < 1.0f ); +} + +ID_INLINE const idBounds & idClip::GetWorldBounds( void ) const { + return worldBounds; +} + +// RAVEN BEGIN +// ddynerman: SD's clip sector code +ID_INLINE void idClip::CoordsForBounds( int* coords, idBounds& bounds ) const { + float fCoords[ 4 ]; + + fCoords[ 0 ] = ( bounds[ 0 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 1 ] = ( bounds[ 0 ].y - nodeOffset.y ) * nodeScale.y; + fCoords[ 2 ] = ( bounds[ 1 ].x - nodeOffset.x ) * nodeScale.x; + fCoords[ 3 ] = ( bounds[ 1 ].y - nodeOffset.y ) * nodeScale.y; + + int i; + for( i = 0; i < 4; i++ ) { + + coords[ i ] = idMath::FtoiFast( fCoords[ i ] ); + + if( coords[ i ] < 0 ) { + coords[ i ] = 0; + } else if( coords[ i ] > CLIPSECTOR_WIDTH - 1 ) { + coords[ i ] = CLIPSECTOR_WIDTH - 1; + } + } + coords[ 2 ]++; coords[ 3 ]++; +} +// RAVEN END + +#endif /* !__CLIP_H__ */ diff --git a/src/mpgame/physics/Force.cpp b/src/mpgame/physics/Force.cpp new file mode 100644 index 0000000..2a66c94 --- /dev/null +++ b/src/mpgame/physics/Force.cpp @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idClass, idForce ) +END_CLASS + +idList idForce::forceList; + +/* +================ +idForce::idForce +================ +*/ +idForce::idForce( void ) { + forceList.Append( this ); +} + +/* +================ +idForce::~idForce +================ +*/ +idForce::~idForce( void ) { + forceList.Remove( this ); +} + +/* +================ +idForce::DeletePhysics +================ +*/ +void idForce::DeletePhysics( const idPhysics *phys ) { + int i; + + for ( i = 0; i < forceList.Num(); i++ ) { + forceList[i]->RemovePhysics( phys ); + } +} + +/* +================ +idForce::ClearForceList +================ +*/ +void idForce::ClearForceList( void ) { + forceList.Clear(); +} + +/* +================ +idForce::Evaluate +================ +*/ +void idForce::Evaluate( int time ) { +} + +/* +================ +idForce::RemovePhysics +================ +*/ +void idForce::RemovePhysics( const idPhysics *phys ) { +} diff --git a/src/mpgame/physics/Force.h b/src/mpgame/physics/Force.h new file mode 100644 index 0000000..190d636 --- /dev/null +++ b/src/mpgame/physics/Force.h @@ -0,0 +1,62 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_H__ +#define __FORCE_H__ + +/* +=============================================================================== + + Force base class + + A force object applies a force to a physics object. + +=============================================================================== +*/ + +class idEntity; +class idPhysics; + +class idForce : public idClass { + +public: + CLASS_PROTOTYPE( idForce ); + + idForce( void ); + virtual ~idForce( void ); + static void DeletePhysics( const idPhysics *phys ); + static void ClearForceList( void ); + +public: // common force interface + // evalulate the force up to the given time + virtual void Evaluate( int time ); + // removes any pointers to the physics object + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + static idList forceList; +}; + +#endif /* !__FORCE_H__ */ diff --git a/src/mpgame/physics/Force_Constant.cpp b/src/mpgame/physics/Force_Constant.cpp new file mode 100644 index 0000000..063a823 --- /dev/null +++ b/src/mpgame/physics/Force_Constant.cpp @@ -0,0 +1,132 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Constant ) +END_CLASS + +/* +================ +idForce_Constant::idForce_Constant +================ +*/ +idForce_Constant::idForce_Constant( void ) { + force = vec3_zero; + physics = NULL; + id = 0; + point = vec3_zero; +} + +/* +================ +idForce_Constant::~idForce_Constant +================ +*/ +idForce_Constant::~idForce_Constant( void ) { +} + +/* +================ +idForce_Constant::Save +================ +*/ +void idForce_Constant::Save( idSaveGame *savefile ) const { + savefile->WriteVec3( force ); + // TOSAVE: idPhysics * physics + savefile->WriteInt( id ); + savefile->WriteVec3( point ); +} + +/* +================ +idForce_Constant::Restore +================ +*/ +void idForce_Constant::Restore( idRestoreGame *savefile ) { + // Owner needs to call SetPhysics!! + savefile->ReadVec3( force ); + savefile->ReadInt( id ); + savefile->ReadVec3( point ); +} + +/* +================ +idForce_Constant::SetPosition +================ +*/ +void idForce_Constant::SetPosition( idPhysics *physics, int id, const idVec3 &point ) { + this->physics = physics; + this->id = id; + this->point = point; +} + +/* +================ +idForce_Constant::SetForce +================ +*/ +void idForce_Constant::SetForce( const idVec3 &force ) { + this->force = force; +} + +/* +================ +idForce_Constant::SetPhysics +================ +*/ +void idForce_Constant::SetPhysics( idPhysics *physics ) { + this->physics = physics; +} + +/* +================ +idForce_Constant::Evaluate +================ +*/ +void idForce_Constant::Evaluate( int time ) { + idVec3 p; + + if ( !physics ) { + return; + } + + p = physics->GetOrigin( id ) + point * physics->GetAxis( id ); + + physics->AddForce( id, p, force ); +} + +/* +================ +idForce_Constant::RemovePhysics +================ +*/ +void idForce_Constant::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/src/mpgame/physics/Force_Constant.h b/src/mpgame/physics/Force_Constant.h new file mode 100644 index 0000000..4c8de39 --- /dev/null +++ b/src/mpgame/physics/Force_Constant.h @@ -0,0 +1,67 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_CONSTANT_H__ +#define __FORCE_CONSTANT_H__ + +/* +=============================================================================== + + Constant force + +=============================================================================== +*/ + +class idForce_Constant : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Constant ); + + idForce_Constant( void ); + virtual ~idForce_Constant( void ); + + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // constant force + void SetForce( const idVec3 &force ); + // set force position + void SetPosition( idPhysics *physics, int id, const idVec3 &point ); + + void SetPhysics( idPhysics *physics ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + // force properties + idVec3 force; + idPhysics * physics; + int id; + idVec3 point; +}; + +#endif /* !__FORCE_CONSTANT_H__ */ diff --git a/src/mpgame/physics/Force_Drag.cpp b/src/mpgame/physics/Force_Drag.cpp new file mode 100644 index 0000000..3a5fba2 --- /dev/null +++ b/src/mpgame/physics/Force_Drag.cpp @@ -0,0 +1,154 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Drag ) +END_CLASS + +/* +================ +idForce_Drag::idForce_Drag +================ +*/ +idForce_Drag::idForce_Drag( void ) { + damping = 0.5f; + dragPosition = vec3_zero; + physics = NULL; + id = 0; + p = vec3_zero; + dragPosition = vec3_zero; +} + +/* +================ +idForce_Drag::~idForce_Drag +================ +*/ +idForce_Drag::~idForce_Drag( void ) { +} + +/* +================ +idForce_Drag::Init +================ +*/ +void idForce_Drag::Init( float damping ) { + if ( damping >= 0.0f && damping < 1.0f ) { + this->damping = damping; + } +} + +/* +================ +idForce_Drag::SetPhysics +================ +*/ +void idForce_Drag::SetPhysics( idPhysics *phys, int id, const idVec3 &p ) { + this->physics = phys; + this->id = id; + this->p = p; +} + +/* +================ +idForce_Drag::SetDragPosition +================ +*/ +void idForce_Drag::SetDragPosition( const idVec3 &pos ) { + this->dragPosition = pos; +} + +/* +================ +idForce_Drag::GetDragPosition +================ +*/ +const idVec3 &idForce_Drag::GetDragPosition( void ) const { + return this->dragPosition; +} + +/* +================ +idForce_Drag::GetDraggedPosition +================ +*/ +const idVec3 idForce_Drag::GetDraggedPosition( void ) const { + return ( physics->GetOrigin( id ) + p * physics->GetAxis( id ) ); +} + +/* +================ +idForce_Drag::Evaluate +================ +*/ +void idForce_Drag::Evaluate( int time ) { + float l1, l2, mass; + idVec3 dragOrigin, dir1, dir2, velocity, centerOfMass; + idMat3 inertiaTensor; + idRotation rotation; + idClipModel *clipModel; + + if ( !physics ) { + return; + } + + clipModel = physics->GetClipModel( id ); + if ( clipModel != NULL && clipModel->IsTraceModel() ) { + clipModel->GetMassProperties( 1.0f, mass, centerOfMass, inertiaTensor ); + } else { + centerOfMass.Zero(); + } + + centerOfMass = physics->GetOrigin( id ) + centerOfMass * physics->GetAxis( id ); + dragOrigin = physics->GetOrigin( id ) + p * physics->GetAxis( id ); + + dir1 = dragPosition - centerOfMass; + dir2 = dragOrigin - centerOfMass; + l1 = dir1.Normalize(); + l2 = dir2.Normalize(); + + rotation.Set( centerOfMass, dir2.Cross( dir1 ), RAD2DEG( idMath::ACos( dir1 * dir2 ) ) ); + physics->SetAngularVelocity( rotation.ToAngularVelocity() / MS2SEC( gameLocal.GetMSec() ), id ); + +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + velocity = physics->GetLinearVelocity( id ) * damping + dir1 * ( ( l1 - l2 ) * ( 1.0f - damping ) / MS2SEC( gameLocal.GetMSec ( ) ) ); +// RAVEN END + physics->SetLinearVelocity( velocity, id ); +} + +/* +================ +idForce_Drag::RemovePhysics +================ +*/ +void idForce_Drag::RemovePhysics( const idPhysics *phys ) { + if ( physics == phys ) { + physics = NULL; + } +} diff --git a/src/mpgame/physics/Force_Drag.h b/src/mpgame/physics/Force_Drag.h new file mode 100644 index 0000000..5d6bca2 --- /dev/null +++ b/src/mpgame/physics/Force_Drag.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_DRAG_H__ +#define __FORCE_DRAG_H__ + +/* +=============================================================================== + + Drag force + +=============================================================================== +*/ + +class idForce_Drag : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Drag ); + + idForce_Drag( void ); + virtual ~idForce_Drag( void ); + // initialize the drag force + void Init( float damping ); + // set physics object being dragged + void SetPhysics( idPhysics *physics, int id, const idVec3 &p ); + // set position to drag towards + void SetDragPosition( const idVec3 &pos ); + // get the position dragged towards + const idVec3 & GetDragPosition( void ) const; + // get the position on the dragged physics object + const idVec3 GetDraggedPosition( void ) const; + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // properties + float damping; + + // positioning + idPhysics * physics; // physics object + int id; // clip model id of physics object + idVec3 p; // position on clip model + idVec3 dragPosition; // drag towards this position +}; + +#endif /* !__FORCE_DRAG_H__ */ diff --git a/src/mpgame/physics/Force_Field.cpp b/src/mpgame/physics/Force_Field.cpp new file mode 100644 index 0000000..dc8efc0 --- /dev/null +++ b/src/mpgame/physics/Force_Field.cpp @@ -0,0 +1,295 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Field ) +END_CLASS + +/* +================ +idForce_Field::idForce_Field +================ +*/ +idForce_Field::idForce_Field( void ) { + type = FORCEFIELD_UNIFORM; + applyType = FORCEFIELD_APPLY_FORCE; + magnitude = 0.0f; + dir.Set( 0, 0, 1 ); + randomTorque = 0.0f; + playerOnly = false; + monsterOnly = false; + clipModel = NULL; +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = -1; + owner = NULL; +// RAVEN END +} + +/* +================ +idForce_Field::~idForce_Field +================ +*/ +idForce_Field::~idForce_Field( void ) { + if ( this->clipModel ) { + delete this->clipModel; + } +} + +/* +================ +idForce_Field::Save +================ +*/ +void idForce_Field::Save( idSaveGame *savefile ) const { + savefile->WriteInt( type ); + savefile->WriteInt( applyType); + savefile->WriteFloat( magnitude ); + savefile->WriteVec3( dir ); + savefile->WriteFloat( randomTorque ); + savefile->WriteBool( playerOnly ); + savefile->WriteBool( monsterOnly ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteInt ( lastApplyTime ); // cnicholson: Added unsaved var + // TOSAVE: idEntity* owner; +} + +/* +================ +idForce_Field::Restore +================ +*/ +void idForce_Field::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( (int &)type ); + savefile->ReadInt( (int &)applyType); + savefile->ReadFloat( magnitude ); + savefile->ReadVec3( dir ); + savefile->ReadFloat( randomTorque ); + savefile->ReadBool( playerOnly ); + savefile->ReadBool( monsterOnly ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadInt ( lastApplyTime ); // cnicholson: Added unrestored var +} + +/* +================ +idForce_Field::SetClipModel +================ +*/ +void idForce_Field::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && clipModel != this->clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idForce_Field::Uniform +================ +*/ +void idForce_Field::Uniform( const idVec3 &force ) { + dir = force; + magnitude = dir.Normalize(); + type = FORCEFIELD_UNIFORM; +} + +/* +================ +idForce_Field::Explosion +================ +*/ +void idForce_Field::Explosion( float force ) { + magnitude = force; + type = FORCEFIELD_EXPLOSION; +} + +/* +================ +idForce_Field::Implosion +================ +*/ +void idForce_Field::Implosion( float force ) { + magnitude = force; + type = FORCEFIELD_IMPLOSION; +} + +/* +================ +idForce_Field::RandomTorque +================ +*/ +void idForce_Field::RandomTorque( float force ) { + randomTorque = force; +} + +/* +================ +idForce_Field::Evaluate +================ +*/ +void idForce_Field::Evaluate( int time ) { + int numClipModels, i; + idBounds bounds; + idVec3 force, torque, angularVelocity; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + + assert( clipModel ); + + bounds.FromTransformedBounds( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numClipModels = gameLocal.ClipModelsTouchingBounds( owner, bounds, -1, clipModelList, MAX_GENTITIES ); +// RAVEN END + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + + if ( !cm->IsTraceModel() ) { + continue; + } + + idEntity *entity = cm->GetEntity(); + + if ( !entity ) { + continue; + } + + idPhysics *physics = entity->GetPhysics(); + + if ( playerOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Player::GetClassType() ) ) { +// RAVEN END + continue; + } + } else if ( monsterOnly ) { +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !physics->IsType( idPhysics_Monster::GetClassType() ) ) { + + continue; + } + } + +// nrausch: It was undesireable to have gibs and discarded weapons bouncing on jump pads + if ( gameLocal.isMultiplayer ) { + if ( entity->IsType( idItem::GetClassType() ) ) { + continue; + } + if ( entity->IsType( rvClientPhysics::GetClassType() ) ) { + continue; + } + if ( entity->IsType( idPlayer::GetClassType() ) ) { + if ( ((idPlayer*)entity)->health <= 0 ) { + continue; + } + } + + } + +// ddynerman: multiple clip worlds + if ( !gameLocal.ContentsModel( owner, cm->GetOrigin(), cm, cm->GetAxis(), -1, + clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { +// RAVEN END + continue; + } + + switch( type ) { + case FORCEFIELD_UNIFORM: { + force = dir; + break; + } + case FORCEFIELD_EXPLOSION: { + force = cm->GetOrigin() - clipModel->GetOrigin(); + force.Normalize(); + break; + } + case FORCEFIELD_IMPLOSION: { + force = clipModel->GetOrigin() - cm->GetOrigin(); + force.Normalize(); + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid type" ); + break; + } + } + + if ( randomTorque != 0.0f ) { + torque[0] = gameLocal.random.CRandomFloat(); + torque[1] = gameLocal.random.CRandomFloat(); + torque[2] = gameLocal.random.CRandomFloat(); + if ( torque.Normalize() == 0.0f ) { + torque[2] = 1.0f; + } + } + + switch( applyType ) { + case FORCEFIELD_APPLY_FORCE: { + if ( randomTorque != 0.0f ) { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->AddForce( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + case FORCEFIELD_APPLY_VELOCITY: { + physics->SetLinearVelocity( force * magnitude, cm->GetId() ); + if ( randomTorque != 0.0f ) { + angularVelocity = physics->GetAngularVelocity( cm->GetId() ); + physics->SetAngularVelocity( 0.5f * (angularVelocity + torque * randomTorque), cm->GetId() ); + } + break; + } + case FORCEFIELD_APPLY_IMPULSE: { + if ( randomTorque != 0.0f ) { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin() + torque.Cross( dir ) * randomTorque, dir * magnitude ); + } + else { + entity->ApplyImpulse( gameLocal.world, cm->GetId(), cm->GetOrigin(), force * magnitude ); + } + break; + } + default: { + gameLocal.Error( "idForce_Field: invalid apply type" ); + break; + } + } + +// RAVEN BEGIN +// bdube: added last apply time + lastApplyTime = time; +// RAVEN END + } +} diff --git a/src/mpgame/physics/Force_Field.h b/src/mpgame/physics/Force_Field.h new file mode 100644 index 0000000..2b72311 --- /dev/null +++ b/src/mpgame/physics/Force_Field.h @@ -0,0 +1,115 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_FIELD_H__ +#define __FORCE_FIELD_H__ + +/* +=============================================================================== + + Force field + +=============================================================================== +*/ + +enum forceFieldType { + FORCEFIELD_UNIFORM, + FORCEFIELD_EXPLOSION, + FORCEFIELD_IMPLOSION +}; + +enum forceFieldApplyType { + FORCEFIELD_APPLY_FORCE, + FORCEFIELD_APPLY_VELOCITY, + FORCEFIELD_APPLY_IMPULSE +}; + +class idForce_Field : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Field ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idForce_Field( void ); + virtual ~idForce_Field( void ); + // uniform constant force + void Uniform( const idVec3 &force ); + // explosion from clip model origin + void Explosion( float force ); + // implosion towards clip model origin + void Implosion( float force ); + // add random torque + void RandomTorque( float force ); + // should the force field apply a force, velocity or impulse + void SetApplyType( const forceFieldApplyType type ) { applyType = type; } + // make the force field only push players + void SetPlayerOnly( bool set ) { playerOnly = set; } + // make the force field only push monsters + void SetMonsterOnly( bool set ) { monsterOnly = set; } + // clip model describing the extents of the force field + void SetClipModel( idClipModel *clipModel ); + +// RAVEN BEGIN +// ddynerman: owner information + void SetOwner( idEntity* ent ); +// bdube: added last apply time + int GetLastApplyTime( void ) const; +// RAVEN END + +public: // common force interface + virtual void Evaluate( int time ); + +private: + // force properties + forceFieldType type; + forceFieldApplyType applyType; + float magnitude; + idVec3 dir; + float randomTorque; + bool playerOnly; + bool monsterOnly; + idClipModel * clipModel; + +// RAVEN BEGIN +// bdube: added last apply time + int lastApplyTime; + idEntity* owner; +// RAVEN END +}; + +// RAVEN BEGIN +// bdube: added last apply time +ID_INLINE int idForce_Field::GetLastApplyTime ( void ) const { + return lastApplyTime; +} + +// ddynerman: owner information +ID_INLINE void idForce_Field::SetOwner( idEntity* ent ) { + owner = ent; +} +// RAVEN END + +#endif /* !__FORCE_FIELD_H__ */ diff --git a/src/mpgame/physics/Force_Spring.cpp b/src/mpgame/physics/Force_Spring.cpp new file mode 100644 index 0000000..f692339 --- /dev/null +++ b/src/mpgame/physics/Force_Spring.cpp @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idForce, idForce_Spring ) +END_CLASS + +/* +================ +idForce_Spring::idForce_Spring +================ +*/ +idForce_Spring::idForce_Spring( void ) { + Kstretch = 100.0f; + Kcompress = 100.0f; + damping = 0.0f; + restLength = 0.0f; + physics1 = NULL; + id1 = 0; + p1 = vec3_zero; + physics2 = NULL; + id2 = 0; + p2 = vec3_zero; +} + +/* +================ +idForce_Spring::~idForce_Spring +================ +*/ +idForce_Spring::~idForce_Spring( void ) { +} + +/* +================ +idForce_Spring::InitSpring +================ +*/ +void idForce_Spring::InitSpring( float Kstretch, float Kcompress, float damping, float restLength ) { + this->Kstretch = Kstretch; + this->Kcompress = Kcompress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idForce_Spring::SetPosition +================ +*/ +void idForce_Spring::SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, idPhysics *physics2, int id2, const idVec3 &p2 ) { + this->physics1 = physics1; + this->id1 = id1; + this->p1 = p1; + this->physics2 = physics2; + this->id2 = id2; + this->p2 = p2; +} + +/* +================ +idForce_Spring::Evaluate +================ +*/ +void idForce_Spring::Evaluate( int time ) { + float length; + idMat3 axis; + idVec3 pos1, pos2, velocity1, velocity2, force, dampingForce; + impactInfo_t info; + + pos1 = p1; + pos2 = p2; + velocity1 = velocity2 = vec3_origin; + + if ( physics1 ) { + axis = physics1->GetAxis( id1 ); + pos1 = physics1->GetOrigin( id1 ); + pos1 += p1 * axis; + if ( damping > 0.0f ) { + physics1->GetImpactInfo( id1, pos1, &info ); + velocity1 = info.velocity; + } + } + + if ( physics2 ) { + axis = physics2->GetAxis( id2 ); + pos2 = physics2->GetOrigin( id2 ); + pos2 += p2 * axis; + if ( damping > 0.0f ) { + physics2->GetImpactInfo( id2, pos2, &info ); + velocity2 = info.velocity; + } + } + + force = pos2 - pos1; + dampingForce = ( damping * ( ((velocity2 - velocity1) * force) / (force * force) ) ) * force; + length = force.Normalize(); + + // if the spring is stretched + if ( length > restLength ) { + if ( Kstretch > 0.0f ) { + force = ( Square( length - restLength ) * Kstretch ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, -force ); + } + } + } + else { + if ( Kcompress > 0.0f ) { + force = ( Square( length - restLength ) * Kcompress ) * force - dampingForce; + if ( physics1 ) { + physics1->AddForce( id1, pos1, -force ); + } + if ( physics2 ) { + physics2->AddForce( id2, pos2, force ); + } + } + } +} + +/* +================ +idForce_Spring::RemovePhysics +================ +*/ +void idForce_Spring::RemovePhysics( const idPhysics *phys ) { + if ( physics1 == phys ) { + physics1 = NULL; + } + if ( physics2 == phys ) { + physics2 = NULL; + } +} + +/* +================ +idForce_Spring::Save +================ +*/ +void idForce_Spring::Save( idSaveGame *savefile ) const { + savefile->WriteFloat ( Kstretch ); + savefile->WriteFloat ( Kcompress ); + savefile->WriteFloat ( damping ); + savefile->WriteFloat ( restLength ); +} + +/* +================ +idForce_Spring::Restore +================ +*/ +void idForce_Spring::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat ( Kstretch ); + savefile->ReadFloat ( Kcompress ); + savefile->ReadFloat ( damping ); + savefile->ReadFloat ( restLength ); +} + diff --git a/src/mpgame/physics/Force_Spring.h b/src/mpgame/physics/Force_Spring.h new file mode 100644 index 0000000..c045eb6 --- /dev/null +++ b/src/mpgame/physics/Force_Spring.h @@ -0,0 +1,74 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __FORCE_SPRING_H__ +#define __FORCE_SPRING_H__ + +/* +=============================================================================== + + Spring force + +=============================================================================== +*/ + +class idForce_Spring : public idForce { + +public: + CLASS_PROTOTYPE( idForce_Spring ); + + idForce_Spring( void ); + virtual ~idForce_Spring( void ); + // initialize the spring + void InitSpring( float Kstretch, float Kcompress, float damping, float restLength ); + // set the entities and positions on these entities the spring is attached to + void SetPosition( idPhysics *physics1, int id1, const idVec3 &p1, + idPhysics *physics2, int id2, const idVec3 &p2 ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common force interface + virtual void Evaluate( int time ); + virtual void RemovePhysics( const idPhysics *phys ); + +private: + + // spring properties + float Kstretch; + float Kcompress; + float damping; + float restLength; + + // positioning + idPhysics * physics1; // first physics object + int id1; // clip model id of first physics object + idVec3 p1; // position on clip model + idPhysics * physics2; // second physics object + int id2; // clip model id of second physics object + idVec3 p2; // position on clip model + +}; + +#endif /* !__FORCE_SPRING_H__ */ diff --git a/src/mpgame/physics/Physics.cpp b/src/mpgame/physics/Physics.cpp new file mode 100644 index 0000000..393a6f1 --- /dev/null +++ b/src/mpgame/physics/Physics.cpp @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +ABSTRACT_DECLARATION( idClass, idPhysics ) +END_CLASS + + +/* +================ +idPhysics::~idPhysics +================ +*/ +idPhysics::~idPhysics( void ) { +} + +/* +================ +idPhysics::Save +================ +*/ +void idPhysics::Save( idSaveGame *savefile ) const { +} + +/* +================ +idPhysics::Restore +================ +*/ +void idPhysics::Restore( idRestoreGame *savefile ) { +} + +/* +================ +idPhysics::SetClipBox +================ +*/ +void idPhysics::SetClipBox( const idBounds &bounds, float density ) { + SetClipModel( new idClipModel( idTraceModel( bounds ) ), density ); +} + +/* +================ +idPhysics::SnapTimeToPhysicsFrame +================ +*/ +int idPhysics::SnapTimeToPhysicsFrame( int t ) { + int s; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + s = t + gameLocal.GetMSec() - 1; + return ( s - s % gameLocal.GetMSec() ); +// RAVEN END +} diff --git a/src/mpgame/physics/Physics.h b/src/mpgame/physics/Physics.h new file mode 100644 index 0000000..e106835 --- /dev/null +++ b/src/mpgame/physics/Physics.h @@ -0,0 +1,202 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_H__ +#define __PHYSICS_H__ + +/* +=============================================================================== + + Physics abstract class + + A physics object is a tool to manipulate the position and orientation of + an entity. The physics object is a container for idClipModels used for + collision detection. The physics deals with moving these collision models + through the world according to the laws of physics or other rules. + + The mass of a clip model is the volume of the clip model times the density. + An arbitrary mass can however be set for specific clip models or the + whole physics object. The contents of a clip model is a set of bit flags + that define the contents. The clip mask defines the contents a clip model + collides with. + + The linear velocity of a physics object is a vector that defines the + translation of the center of mass in units per second. The angular velocity + of a physics object is a vector that passes through the center of mass. The + direction of this vector defines the axis of rotation and the magnitude + defines the rate of rotation about the axis in radians per second. + The gravity is the change in velocity per second due to gravitational force. + + Entities update their visual position and orientation from the physics + using GetOrigin() and GetAxis(). Direct origin and axis changes of + entities should go through the physics. In other words the physics origin + and axis are updated first and the entity updates it's visual position + from the physics. + +=============================================================================== +*/ + +#define CONTACT_EPSILON 0.25f // maximum contact seperation distance + +class idEntity; + +typedef struct impactInfo_s { + float invMass; // inverse mass + idMat3 invInertiaTensor; // inverse inertia tensor + idVec3 position; // impact position relative to center of mass + idVec3 velocity; // velocity at the impact position +} impactInfo_t; + + +class idPhysics : public idClass { + +public: + ABSTRACT_PROTOTYPE( idPhysics ); + + virtual ~idPhysics( void ); + static int SnapTimeToPhysicsFrame( int t ); + + // Must not be virtual + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + // set pointer to entity using physics + virtual void SetSelf( idEntity *e ) = 0; + // clip models + virtual void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ) = 0; + virtual void SetClipBox( const idBounds &bounds, float density ); + virtual idClipModel * GetClipModel( int id = 0 ) const = 0; + virtual int GetNumClipModels( void ) const = 0; + // get/set the mass of a specific clip model or the whole physics object + virtual void SetMass( float mass, int id = -1 ) = 0; + virtual float GetMass( int id = -1 ) const = 0; + +// RAVEN BEGIN +// bdube: added center mass + // get the center of mass origin + virtual idVec3 GetCenterMass ( int id = -1 ) const = 0; +// RAVEN END + + // get/set the contents of a specific clip model or the whole physics object + virtual void SetContents( int contents, int id = -1 ) = 0; + virtual int GetContents( int id = -1 ) const = 0; + // get/set the contents a specific clip model or the whole physics object collides with + virtual void SetClipMask( int mask, int id = -1 ) = 0; + virtual int GetClipMask( int id = -1 ) const = 0; + // get the bounds of a specific clip model or the whole physics object + virtual const idBounds & GetBounds( int id = -1 ) const = 0; + virtual const idBounds & GetAbsBounds( int id = -1 ) const = 0; + // evaluate the physics with the given time step, returns true if the object moved + virtual bool Evaluate( int timeStepMSec, int endTimeMSec ) = 0; + // update the time without moving + virtual void UpdateTime( int endTimeMSec ) = 0; + // get the last physics update time + virtual int GetTime( void ) const = 0; + // collision interaction between different physics objects + virtual void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const = 0; + virtual void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) = 0; + virtual void AddForce( const int id, const idVec3 &point, const idVec3 &force ) = 0; + virtual void Activate( void ) = 0; + virtual void PutToRest( void ) = 0; + virtual bool IsAtRest( void ) const = 0; + virtual int GetRestStartTime( void ) const = 0; + virtual bool IsPushable( void ) const = 0; +// RAVEN BEGIN +// bdube: water interraction + virtual bool IsInWater ( void ) const = 0; +// RAVEN END + // save and restore the physics state + virtual void SaveState( void ) = 0; + virtual void RestoreState( void ) = 0; + // set the position and orientation in master space or world space if no master set + virtual void SetOrigin( const idVec3 &newOrigin, int id = -1 ) = 0; + virtual void SetAxis( const idMat3 &newAxis, int id = -1 ) = 0; + // translate or rotate the physics object in world space + virtual void Translate( const idVec3 &translation, int id = -1 ) = 0; + virtual void Rotate( const idRotation &rotation, int id = -1 ) = 0; + // get the position and orientation in world space + virtual const idVec3 & GetOrigin( int id = 0 ) const = 0; + virtual const idMat3 & GetAxis( int id = 0 ) const = 0; + // set linear and angular velocity + virtual void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ) = 0; + virtual void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ) = 0; + // get linear and angular velocity + virtual const idVec3 & GetLinearVelocity( int id = 0 ) const = 0; + virtual const idVec3 & GetAngularVelocity( int id = 0 ) const = 0; + // gravity + virtual void SetGravity( const idVec3 &newGravity ) = 0; + virtual const idVec3 & GetGravity( void ) const = 0; + virtual const idVec3 & GetGravityNormal( void ) const = 0; + // get first collision when translating or rotating this physics object + virtual void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const = 0; + virtual void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const = 0; + virtual int ClipContents( const idClipModel *model ) const = 0; + // disable/enable the clip models contained by this physics object + virtual void DisableClip( void ) = 0; + virtual void EnableClip( void ) = 0; + // link/unlink the clip models contained by this physics object + virtual void UnlinkClip( void ) = 0; + virtual void LinkClip( void ) = 0; + // contacts + virtual bool EvaluateContacts( void ) = 0; + virtual int GetNumContacts( void ) const = 0; + virtual const contactInfo_t &GetContact( int num ) const = 0; + virtual void ClearContacts( void ) = 0; + virtual void AddContactEntity( idEntity *e ) = 0; + virtual void RemoveContactEntity( idEntity *e ) = 0; + // ground contacts + virtual bool HasGroundContacts( void ) const = 0; + virtual bool IsGroundEntity( int entityNum ) const = 0; + virtual bool IsGroundClipModel( int entityNum, int id ) const = 0; +// RAVEN BEGIN +// abahr + virtual const idVec3 GetContactNormal() const { return vec3_zero; } + virtual const idVec3 GetGroundContactNormal() const { return vec3_zero; } + virtual idEntity* GetSelf() const = 0; +// RAVEN END + // set the master entity for objects bound to a master + virtual void SetMaster( idEntity *master, const bool orientated = true ) = 0; + // set pushed state + virtual void SetPushed( int deltaTime ) = 0; + virtual const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const = 0; + virtual const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const = 0; + // get blocking info, returns NULL if the object is not blocked + virtual const trace_t * GetBlockingInfo( void ) const = 0; + virtual idEntity * GetBlockingEntity( void ) const = 0; + // movement end times in msec for reached events at the end of predefined motion + virtual int GetLinearEndTime( void ) const = 0; + virtual int GetAngularEndTime( void ) const = 0; + // networking + virtual void WriteToSnapshot( idBitMsgDelta &msg ) const = 0; + virtual void ReadFromSnapshot( const idBitMsgDelta &msg ) = 0; + +// RAVEN BEGIN +// kfuller: we want to debug draw the bbox + virtual void DebugDraw() {} +// RAVEN END + +}; + +#endif /* !__PHYSICS_H__ */ diff --git a/src/mpgame/physics/Physics_AF.cpp b/src/mpgame/physics/Physics_AF.cpp new file mode 100644 index 0000000..cd4d6e3 --- /dev/null +++ b/src/mpgame/physics/Physics_AF.cpp @@ -0,0 +1,8099 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_AF ) +END_CLASS + +const float ERROR_REDUCTION = 0.5f; +const float ERROR_REDUCTION_MAX = 256.0f; +const float LIMIT_ERROR_REDUCTION = 0.3f; +const float LCP_EPSILON = 1e-7f; +const float LIMIT_LCP_EPSILON = 1e-4f; +const float CONTACT_LCP_EPSILON = 1e-6f; +const float CENTER_OF_MASS_EPSILON = 1e-4f; +const float NO_MOVE_TIME = 1.0f; +const float NO_MOVE_TRANSLATION_TOLERANCE = 10.0f; +const float NO_MOVE_ROTATION_TOLERANCE = 10.0f; +const float MIN_MOVE_TIME = -1.0f; +const float MAX_MOVE_TIME = -1.0f; +const float IMPULSE_THRESHOLD = 500.0f; +const float SUSPEND_LINEAR_VELOCITY = 10.0f; +const float SUSPEND_ANGULAR_VELOCITY = 15.0f; +const float SUSPEND_LINEAR_ACCELERATION = 20.0f; +const float SUSPEND_ANGULAR_ACCELERATION = 30.0f; +const idVec6 vec6_lcp_epsilon = idVec6( LCP_EPSILON, LCP_EPSILON, LCP_EPSILON, + LCP_EPSILON, LCP_EPSILON, LCP_EPSILON ); + +#define AF_TIMINGS + +#ifdef AF_TIMINGS +static int lastTimerReset = 0; +static int numArticulatedFigures = 0; +static idTimer timer_total, timer_pc, timer_ac, timer_collision, timer_lcp; +#endif + + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +/* +================ +idAFConstraint::idAFConstraint +================ +*/ +idAFConstraint::idAFConstraint( void ) { + type = CONSTRAINT_INVALID; + name = "noname"; + body1 = NULL; + body2 = NULL; + physics = NULL; + + lo.Zero( 6 ); + lo.SubVec6(0) = -vec6_infinity; + hi.Zero( 6 ); + hi.SubVec6(0) = vec6_infinity; + e.SetSize( 6 ); + e.SubVec6(0) = vec6_lcp_epsilon; + + boxConstraint = NULL; + boxIndex[0] = -1; + boxIndex[1] = -1; + boxIndex[2] = -1; + boxIndex[3] = -1; + boxIndex[4] = -1; + boxIndex[5] = -1; + + firstIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); +} + +/* +================ +idAFConstraint::~idAFConstraint +================ +*/ +idAFConstraint::~idAFConstraint( void ) { +} + +/* +================ +idAFConstraint::SetBody1 +================ +*/ +void idAFConstraint::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::SetBody2 +================ +*/ +void idAFConstraint::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint::GetMultiplier +================ +*/ +const idVecX &idAFConstraint::GetMultiplier( void ) { + return lm; +} + +/* +================ +idAFConstraint::Evaluate +================ +*/ +void idAFConstraint::Evaluate( float invTimeStep ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::ApplyFriction +================ +*/ +void idAFConstraint::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint::GetForce +================ +*/ +void idAFConstraint::GetForce( idAFBody *body, idVec6 &force ) { + idVecX v; + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + if ( body == body1 ) { + J1.TransposeMultiply( v, lm ); + } + else if ( body == body2 ) { + J2.TransposeMultiply( v, lm ); + } + else { + v.Zero(); + } + force[0] = v[0]; force[1] = v[1]; force[2] = v[2]; force[3] = v[3]; force[4] = v[4]; force[5] = v[5]; +} + +/* +================ +idAFConstraint::Translate +================ +*/ +void idAFConstraint::Translate( const idVec3 &translation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::Rotate +================ +*/ +void idAFConstraint::Rotate( const idRotation &rotation ) { + assert( 0 ); +} + +/* +================ +idAFConstraint::GetCenter +================ +*/ +void idAFConstraint::GetCenter( idVec3 ¢er ) { + center.Zero(); +} + +/* +================ +idAFConstraint::DebugDraw +================ +*/ +void idAFConstraint::DebugDraw( void ) { +} + +/* +================ +idAFConstraint::InitSize +================ +*/ +void idAFConstraint::InitSize( int size ) { + J1.Zero( size, 6 ); + J2.Zero( size, 6 ); + c1.Zero( size ); + c2.Zero( size ); + s.Zero( size ); + lm.Zero( size ); +} + +/* +================ +idAFConstraint::Save +================ +*/ +void idAFConstraint::Save( idSaveGame *saveFile ) const { + saveFile->WriteInt( type ); + saveFile->WriteString ( name ); // cnicholson: Added unsaved var + // TOSAVE: idAFBody * body1; + // TOSAVE: idAFBody * body2; + // TOSAVE: idPhysics_AF * physics; + + // TOSAVE: idMatX J1, J2; + // TOSAVE: idVecX c1, c2; + // TOSAVE: idVecX lo, hi, e; + // TOSAVE: idAFConstraint * boxConstraint; + for (int i=0; i<6; ++i) { // cnicholson: Added unsaved var + saveFile->WriteInt ( boxIndex[i] ); + } + + // TOSAVE: idMatX invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX lm; + saveFile->WriteInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ));// cnicholson: Added unsaved var +} + +/* +================ +idAFConstraint::Restore +================ +*/ +void idAFConstraint::Restore( idRestoreGame *saveFile ) { + constraintType_t t; + saveFile->ReadInt( (int &)t ); + assert( t == type ); + + saveFile->ReadString ( name ); // cnicholson: Added unrestored var + + for (int i=0; i<6; ++i) { + saveFile->ReadInt ( boxIndex[i] ); + } + + saveFile->ReadInt ( firstIndex ); // cnicholson: Added unsaved var + + saveFile->Read( &fl, sizeof( fl )); // cnicholson: Added unsaved var +} + + +//=============================================================== +// +// idAFConstraint_Fixed +// +//=============================================================== + +/* +================ +idAFConstraint_Fixed::idAFConstraint_Fixed +================ +*/ +idAFConstraint_Fixed::idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_FIXED; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 6 ); + fl.allowPrimary = true; + fl.noCollision = true; + + InitOffset(); +} + +/* +================ +idAFConstraint_Fixed::InitOffset +================ +*/ +void idAFConstraint_Fixed::InitOffset( void ) { + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Fixed::SetBody1 +================ +*/ +void idAFConstraint_Fixed::SetBody1( idAFBody *body ) { + if ( body1 != body) { + body1 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::SetBody2 +================ +*/ +void idAFConstraint_Fixed::SetBody2( idAFBody *body ) { + if ( body2 != body ) { + body2 = body; + InitOffset(); + if ( physics ) { + physics->SetChanged(); + } + } +} + +/* +================ +idAFConstraint_Fixed::Evaluate +================ +*/ +void idAFConstraint_Fixed::Evaluate( float invTimeStep ) { + idVec3 ofs, a2; + idMat3 ax; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + a2 = offset * master->GetWorldAxis(); + ofs = a2 + master->GetWorldOrigin(); + ax = relAxis * master->GetWorldAxis(); + } + else { + a2.Zero(); + ofs = offset; + ax = relAxis; + } + + J1.Set( mat3_identity, mat3_zero, + mat3_zero, mat3_identity ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, -mat3_identity ); + } + else { + J2.Zero( 6, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( ofs - body1->GetWorldOrigin() ); + r = ( body1->GetWorldAxis().Transpose() * ax ).ToRotation(); + c1.SubVec3(1) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * -(float) DEG2RAD( r.GetAngle() ) ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Fixed::ApplyFriction +================ +*/ +void idAFConstraint_Fixed::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Fixed::Translate +================ +*/ +void idAFConstraint_Fixed::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Fixed::Rotate +================ +*/ +void idAFConstraint_Fixed::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + relAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Fixed::GetCenter +================ +*/ +void idAFConstraint_Fixed::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin(); +} + +/* +================ +idAFConstraint_Fixed::DebugDraw +================ +*/ +void idAFConstraint_Fixed::DebugDraw( void ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), master->GetWorldOrigin() ); + } + else { + gameRenderWorld->DebugLine( colorRed, body1->GetWorldOrigin(), vec3_origin ); + } +} + +/* +================ +idAFConstraint_Fixed::Save +================ +*/ +void idAFConstraint_Fixed::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Fixed::Restore +================ +*/ +void idAFConstraint_Fixed::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJoint +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_BALLANDSOCKETJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 3 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint +================ +*/ +idAFConstraint_BallAndSocketJoint::~idAFConstraint_BallAndSocketJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetAnchor +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_BallAndSocketJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetNoLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetConeLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, + body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_BallAndSocketJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetFriction +================ +*/ +float idAFConstraint_BallAndSocketJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( anchor2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + J1.Set( mat3_identity, -SkewSymmetric( a1 ) ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ) ); + } + else { + J2.Zero( 3, 6 ); + } + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_BallAndSocketJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetForce +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Translate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::GetCenter +================ +*/ +void idAFConstraint_BallAndSocketJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_BallAndSocketJoint::DebugDraw +================ +*/ +void idAFConstraint_BallAndSocketJoint::DebugDraw( void ) { + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 5, 0, 0 ), a1 + idVec3( 5, 0, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 5, 0 ), a1 + idVec3( 0, 5, 0 ) ); + gameRenderWorld->DebugLine( colorBlue, a1 - idVec3( 0, 0, 5 ), a1 + idVec3( 0, 0, 5 ) ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Save +================ +*/ +void idAFConstraint_BallAndSocketJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( friction ); +// cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); // cnicholson: Bad way to save and restore + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_BallAndSocketJointFriction *fc; +} + +/* +================ +idAFConstraint_BallAndSocketJoint::Restore +================ +*/ +void idAFConstraint_BallAndSocketJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( friction ); + + // cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_BallAndSocketJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_BallAndSocketJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction +================ +*/ +idAFConstraint_BallAndSocketJointFriction::idAFConstraint_BallAndSocketJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "ballAndSocketJointFriction"; + InitSize( 3 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Setup +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Setup( idAFConstraint_BallAndSocketJoint *bsj ) { + this->joint = bsj; + body1 = bsj->GetBody1(); + body2 = bsj->GetBody2(); +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Evaluate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Add +================ +*/ +bool idAFConstraint_BallAndSocketJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = lo[2] = -f; + hi[0] = hi[1] = hi[2] = f; + + J1.Zero( 3, 6 ); + J1[0][3] = J1[1][4] = J1[2][5] = 1.0f; + + if ( body2 ) { + + J2.Zero( 3, 6 ); + J2[0][3] = J2[1][4] = J2[2][5] = 1.0f; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Translate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_BallAndSocketJointFriction::Rotate +================ +*/ +void idAFConstraint_BallAndSocketJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_UniversalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_UNIVERSALJOINT; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 4 ); + coneLimit = NULL; + pyramidLimit = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint +================ +*/ +idAFConstraint_UniversalJoint::~idAFConstraint_UniversalJoint( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( pyramidLimit ) { + delete pyramidLimit; + } + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetAnchor +================ +*/ +void idAFConstraint_UniversalJoint::SetAnchor( const idVec3 &worldPosition ) { + + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetAnchor +================ +*/ +idVec3 idAFConstraint_UniversalJoint::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_UniversalJoint::SetShafts +================ +*/ +void idAFConstraint_UniversalJoint::SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ) { + idVec3 cardanAxis; + float l; + + shaft1 = cardanShaft1; + l = shaft1.Normalize(); + assert( l != 0.0f ); + shaft2 = cardanShaft2; + l = shaft2.Normalize(); + assert( l != 0.0f ); + + // the cardan axis is a vector orthogonal to both cardan shafts + cardanAxis = shaft1.Cross( shaft2 ); + if ( cardanAxis.Normalize() == 0.0f ) { + idVec3 vecY; + shaft1.OrthogonalBasis( cardanAxis, vecY ); + cardanAxis.Normalize(); + } + + shaft1 *= body1->GetWorldAxis().Transpose(); + axis1 = cardanAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + shaft2 *= body2->GetWorldAxis().Transpose(); + axis2 = cardanAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = cardanAxis; + } + + if ( coneLimit ) { + coneLimit->SetBody1Axis( shaft1 ); + } + if ( pyramidLimit ) { + pyramidLimit->SetBody1Axis( shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetNoLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetConeLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetConeLimit( const idVec3 &coneAxis, const float coneAngle ) { + if ( pyramidLimit ) { + delete pyramidLimit; + pyramidLimit = NULL; + } + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, coneAxis * body2->GetWorldAxis().Transpose(), coneAngle, shaft1 ); + } + else { + coneLimit->Setup( body1, body2, anchor2, coneAxis, coneAngle, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetPyramidLimit +================ +*/ +void idAFConstraint_UniversalJoint::SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !pyramidLimit ) { + pyramidLimit = new idAFConstraint_PyramidLimit; + pyramidLimit->SetPhysics( physics ); + } + if ( body2 ) { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis * body2->GetWorldAxis().Transpose(), + baseAxis * body2->GetWorldAxis().Transpose(), angle1, angle2, shaft1 ); + } + else { + pyramidLimit->Setup( body1, body2, anchor2, pyramidAxis, baseAxis, angle1, angle2, shaft1 ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::SetLimitEpsilon +================ +*/ +void idAFConstraint_UniversalJoint::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } + if ( pyramidLimit ) { + pyramidLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetFriction +================ +*/ +float idAFConstraint_UniversalJoint::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_UniversalJoint::Evaluate + + NOTE: this joint is homokinetic +================ +*/ +void idAFConstraint_UniversalJoint::Evaluate( float invTimeStep ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = s1.Cross( axis1 * body1->GetWorldAxis() ); + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( s1[0], s1[1], s1[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 4, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( s2[0], s2[1], s2[2], + 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 4, 6 ); + } + else { + J2.Zero( 4, 6 ); + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( d1 * d2 ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } + else if ( pyramidLimit ) { + pyramidLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJoint::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_UniversalJointFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetForce +================ +*/ +void idAFConstraint_UniversalJoint::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_UniversalJoint::Translate +================ +*/ +void idAFConstraint_UniversalJoint::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::Rotate +================ +*/ +void idAFConstraint_UniversalJoint::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + shaft2 *= rotation.ToMat3(); + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } + else if ( pyramidLimit ) { + pyramidLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_UniversalJoint::GetCenter +================ +*/ +void idAFConstraint_UniversalJoint::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_UniversalJoint::DebugDraw +================ +*/ +void idAFConstraint_UniversalJoint::DebugDraw( void ) { + idVec3 a1, a2, s1, s2, d1, d2, v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + s1 = shaft1 * body1->GetWorldAxis(); + d1 = axis1 * body1->GetWorldAxis(); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + s2 = shaft2 * master->GetWorldAxis(); + d2 = axis2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + s2 = shaft2; + d2 = axis2; + } + + v = s1.Cross( s2 ); + if ( v.Normalize() != 0.0f ) { + idMat3 m1, m2; + + m1[0] = s1; + m1[1] = v; + m1[2] = v.Cross( m1[0] ); + + m2[0] = -s2; + m2[1] = v; + m2[2] = v.Cross( m2[0] ); + + d2 *= m2.Transpose() * m1; + } + + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + s1 * 5.0f, 1.0f ); + gameRenderWorld->DebugArrow( colorBlue, a2, a2 + s2 * 5.0f, 1.0f ); + gameRenderWorld->DebugLine( colorGreen, a1, a1 + d1 * 5.0f ); + gameRenderWorld->DebugLine( colorGreen, a2, a2 + d2 * 5.0f ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + if ( pyramidLimit ) { + pyramidLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_UniversalJoint::Save +================ +*/ +void idAFConstraint_UniversalJoint::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( shaft1 ); + saveFile->WriteVec3( shaft2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteFloat( friction ); + + // cnicholson: Changed saving to use bools to check if restore is needed + //if ( coneLimit ) { + // saveFile->WriteBool( true ); + // coneLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( pyramidLimit ) { + // saveFile->WriteBool( true ); + // pyramidLimit->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( fc ) { + // saveFile->WriteBool( true ); + // fc->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} + + //if ( coneLimit ) { + // coneLimit->Save( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Save( saveFile ); + //} + + // TOSAVE: idAFConstraint_UniversalJointFriction *fc; +} + +/* +================ +idAFConstraint_UniversalJoint::Restore +================ +*/ +void idAFConstraint_UniversalJoint::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( shaft1 ); + saveFile->ReadVec3( shaft2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadFloat( friction ); + +// cnicholson: Used bools to save/restore these pointers, old way = bad + //bool b; + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !coneLimit ) { + // coneLimit = new idAFConstraint_ConeLimit; + // } + // coneLimit->SetPhysics( physics ); + // coneLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !pyramidLimit ) { + // pyramidLimit = new idAFConstraint_PyramidLimit; + // } + // pyramidLimit->SetPhysics( physics ); + // pyramidLimit->Restore( saveFile ); + //} + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !fc ) { + // fc = new idAFConstraint_UniversalJointFriction; + // } + // fc->SetPhysics( physics ); + // fc->Restore( saveFile ); + //} + + //if ( coneLimit ) { + // coneLimit->Restore( saveFile ); + //} + //if ( pyramidLimit ) { + // pyramidLimit->Restore( saveFile ); + //} +} + + +//=============================================================== +// +// idAFConstraint_UniversalJointFriction +// +//=============================================================== + +/* +================ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction +================ +*/ +idAFConstraint_UniversalJointFriction::idAFConstraint_UniversalJointFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "universalJointFriction"; + InitSize( 2 ); + joint = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Setup +================ +*/ +void idAFConstraint_UniversalJointFriction::Setup( idAFConstraint_UniversalJoint *uj ) { + this->joint = uj; + body1 = uj->GetBody1(); + body2 = uj->GetBody2(); +} + +/* +================ +idAFConstraint_UniversalJointFriction::Evaluate +================ +*/ +void idAFConstraint_UniversalJointFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::ApplyFriction +================ +*/ +void idAFConstraint_UniversalJointFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_UniversalJointFriction::Add +================ +*/ +bool idAFConstraint_UniversalJointFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 s1, s2, dir1, dir2; + float f; + + physics = phys; + + f = joint->GetFriction() * joint->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = lo[1] = -f; + hi[0] = hi[1] = f; + + joint->GetShafts( s1, s2 ); + + s1 *= body1->GetWorldAxis(); + s1.NormalVectors( dir1, dir2 ); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = dir1; + J1.SubVec6(1).SubVec3(0).Zero(); + J1.SubVec6(1).SubVec3(1) = dir2; + + if ( body2 ) { + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -dir1; + J2.SubVec6(1).SubVec3(0).Zero(); + J2.SubVec6(1).SubVec3(1) = -dir2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_UniversalJointFriction::Translate +================ +*/ +void idAFConstraint_UniversalJointFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_UniversalJointFriction::Rotate +================ +*/ +void idAFConstraint_UniversalJointFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_CylindricalJoint +// +//=============================================================== + +/* +================ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint +================ +*/ +idAFConstraint_CylindricalJoint::idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Evaluate +================ +*/ +void idAFConstraint_CylindricalJoint::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::ApplyFriction +================ +*/ +void idAFConstraint_CylindricalJoint::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Translate +================ +*/ +void idAFConstraint_CylindricalJoint::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::Rotate +================ +*/ +void idAFConstraint_CylindricalJoint::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_CylindricalJoint::DebugDraw +================ +*/ +void idAFConstraint_CylindricalJoint::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Hinge +// +//=============================================================== + +/* +================ +idAFConstraint_Hinge::idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_HINGE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + coneLimit = NULL; + steering = NULL; + friction = 0.0f; + fc = NULL; + fl.allowPrimary = true; + fl.noCollision = true; + initialAxis = body1->GetWorldAxis(); + if ( body2 ) { + initialAxis *= body2->GetWorldAxis().Transpose(); + } +} + +/* +================ +idAFConstraint_Hinge::~idAFConstraint_Hinge +================ +*/ +idAFConstraint_Hinge::~idAFConstraint_Hinge( void ) { + if ( coneLimit ) { + delete coneLimit; + } + if ( fc ) { + delete fc; + } + if ( steering ) { + delete steering; + } +} + +/* +================ +idAFConstraint_Hinge::SetAnchor +================ +*/ +void idAFConstraint_Hinge::SetAnchor( const idVec3 &worldPosition ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldPosition - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldPosition - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldPosition; + } + + if ( coneLimit ) { + coneLimit->SetAnchor( anchor2 ); + } +} + +/* +================ +idAFConstraint_Hinge::GetAnchor +================ +*/ +idVec3 idAFConstraint_Hinge::GetAnchor( void ) const { + if ( body2 ) { + return body2->GetWorldOrigin() + body2->GetWorldAxis() * anchor2; + } + return anchor2; +} + +/* +================ +idAFConstraint_Hinge::SetAxis +================ +*/ +void idAFConstraint_Hinge::SetAxis( const idVec3 &axis ) { + idVec3 normAxis; + + normAxis = axis; + normAxis.Normalize(); + + // get axis relative to body1 + axis1 = normAxis * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get axis relative to body2 + axis2 = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis2 = normAxis; + } +} + +/* +================ +idAFConstraint_Hinge::GetAxis +================ +*/ +idVec3 idAFConstraint_Hinge::GetAxis( void ) const { + if ( body2 ) { + return axis2 * body2->GetWorldAxis(); + } + return axis2; +} + +/* +================ +idAFConstraint_Hinge::SetNoLimit +================ +*/ +void idAFConstraint_Hinge::SetNoLimit( void ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } +} + +/* +================ +idAFConstraint_Hinge::SetLimit +================ +*/ +void idAFConstraint_Hinge::SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + coneLimit->SetPhysics( physics ); + } + if ( body2 ) { + coneLimit->Setup( body1, body2, anchor2, axis * body2->GetWorldAxis().Transpose(), angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } + else { + coneLimit->Setup( body1, body2, anchor2, axis, angle, body1Axis * body1->GetWorldAxis().Transpose() ); + } +} + +/* +================ +idAFConstraint_Hinge::SetLimitEpsilon +================ +*/ +void idAFConstraint_Hinge::SetLimitEpsilon( const float e ) { + if ( coneLimit ) { + coneLimit->SetEpsilon( e ); + } +} + +/* +================ +idAFConstraint_Hinge::GetFriction +================ +*/ +float idAFConstraint_Hinge::GetFriction( void ) const { + if ( af_forceFriction.GetFloat() > 0.0f ) { + return af_forceFriction.GetFloat(); + } + return friction * physics->GetJointFrictionScale(); +} + +/* +================ +idAFConstraint_Hinge::GetAngle +================ +*/ +float idAFConstraint_Hinge::GetAngle( void ) const { + idMat3 axis; + idRotation rotation; + float angle; + + axis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose() * initialAxis.Transpose(); + rotation = axis.ToRotation(); + angle = rotation.GetAngle(); + if ( rotation.GetVec() * axis1 < 0.0f ) { + return -angle; + } + return angle; +} + +/* +================ +idAFConstraint_Hinge::SetSteerAngle +================ +*/ +void idAFConstraint_Hinge::SetSteerAngle( const float degrees ) { + if ( coneLimit ) { + delete coneLimit; + coneLimit = NULL; + } + if ( !steering ) { + steering = new idAFConstraint_HingeSteering(); + steering->Setup( this ); + } + steering->SetSteerAngle( degrees ); +} + +/* +================ +idAFConstraint_Hinge::SetSteerSpeed +================ +*/ +void idAFConstraint_Hinge::SetSteerSpeed( const float speed ) { + if ( steering ) { + steering->SetSteerSpeed( speed ); + } +} + +/* +================ +idAFConstraint_Hinge::Evaluate +================ +*/ +void idAFConstraint_Hinge::Evaluate( float invTimeStep ) { + idVec3 a1, a2; + idVec3 x1, x2, cross; + idVec3 vecX, vecY; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + x1 = axis1 * body1->GetWorldAxis(); // axis in body1 space + x1.OrthogonalBasis( vecX, vecY ); // basis for axis in body1 space + + a1 = anchor1 * body1->GetWorldAxis(); // anchor in body1 space + + if ( master ) { + a2 = anchor2 * master->GetWorldAxis(); // anchor in master space + x2 = axis2 * master->GetWorldAxis(); + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 + master->GetWorldOrigin() - ( a1 + body1->GetWorldOrigin() ) ); + } + else { + a2 = anchor2; + x2 = axis2; + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( a2 - ( a1 + body1->GetWorldOrigin() ) ); + } + + J1.Set( mat3_identity, -SkewSymmetric( a1 ), + mat3_zero, idMat3( vecX[0], vecX[1], vecX[2], + vecY[0], vecY[1], vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + J2.Set( -mat3_identity, SkewSymmetric( a2 ), + mat3_zero, idMat3( -vecX[0], -vecX[1], -vecX[2], + -vecY[0], -vecY[1], -vecY[2], + 0.0f, 0.0f, 0.0f ) ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + cross = x1.Cross( x2 ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecX ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( cross * vecY ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); + + if ( steering ) { + steering->Add( physics, invTimeStep ); + } + else if ( coneLimit ) { + coneLimit->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::ApplyFriction +================ +*/ +void idAFConstraint_Hinge::ApplyFriction( float invTimeStep ) { + idVec3 angular; + float invMass, currentFriction; + + currentFriction = GetFriction(); + + if ( currentFriction <= 0.0f ) { + return; + } + + if ( af_useImpulseFriction.GetBool() || af_useJointImpulseFriction.GetBool() ) { + + angular = body1->GetAngularVelocity(); + invMass = body1->GetInverseMass(); + if ( body2 ) { + angular -= body2->GetAngularVelocity(); + invMass += body2->GetInverseMass(); + } + + angular *= currentFriction / invMass; + + body1->SetAngularVelocity( body1->GetAngularVelocity() - angular * body1->GetInverseMass() ); + if ( body2 ) { + body2->SetAngularVelocity( body2->GetAngularVelocity() + angular * body2->GetInverseMass() ); + } + } + else { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + fc->Setup( this ); + } + + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Hinge::GetForce +================ +*/ +void idAFConstraint_Hinge::GetForce( idAFBody *body, idVec6 &force ) { + idAFConstraint::GetForce( body, force ); + // FIXME: add limit force +} + +/* +================ +idAFConstraint_Hinge::Translate +================ +*/ +void idAFConstraint_Hinge::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } + if ( coneLimit ) { + coneLimit->Translate( translation ); + } +} + +/* +================ +idAFConstraint_Hinge::Rotate +================ +*/ +void idAFConstraint_Hinge::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + axis2 *= rotation.ToMat3(); + } + if ( coneLimit ) { + coneLimit->Rotate( rotation ); + } +} + +/* +================ +idAFConstraint_Hinge::GetCenter +================ +*/ +void idAFConstraint_Hinge::GetCenter( idVec3 ¢er ) { + center = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); +} + +/* +================ +idAFConstraint_Hinge::DebugDraw +================ +*/ +void idAFConstraint_Hinge::DebugDraw( void ) { + idVec3 vecX, vecY; + idVec3 a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + idVec3 x1 = axis1 * body1->GetWorldAxis(); + x1.OrthogonalBasis( vecX, vecY ); + + gameRenderWorld->DebugArrow( colorBlue, a1 - 4.0f * x1, a1 + 4.0f * x1, 1 ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecX, a1 + 2.0f * vecX ); + gameRenderWorld->DebugLine( colorBlue, a1 - 2.0f * vecY, a1 + 2.0f * vecY ); + + if ( af_showLimits.GetBool() ) { + if ( coneLimit ) { + coneLimit->DebugDraw(); + } + } +} + +/* +================ +idAFConstraint_Hinge::Save +================ +*/ +void idAFConstraint_Hinge::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( axis1 ); + saveFile->WriteVec3( axis2 ); + saveFile->WriteMat3( initialAxis ); + saveFile->WriteFloat( friction ); + if ( coneLimit ) { + saveFile->WriteBool( true ); + coneLimit->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( steering ) { + saveFile->WriteBool( true ); + steering->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + if ( fc ) { + saveFile->WriteBool( true ); + fc->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } +} + +/* +================ +idAFConstraint_Hinge::Restore +================ +*/ +void idAFConstraint_Hinge::Restore( idRestoreGame *saveFile ) { + bool b; + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( axis1 ); + saveFile->ReadVec3( axis2 ); + saveFile->ReadMat3( initialAxis ); + saveFile->ReadFloat( friction ); + + saveFile->ReadBool( b ); + if ( b ) { + if ( !coneLimit ) { + coneLimit = new idAFConstraint_ConeLimit; + } + coneLimit->SetPhysics( physics ); + coneLimit->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !steering ) { + steering = new idAFConstraint_HingeSteering; + } + steering->Setup( this ); + steering->Restore( saveFile ); + } + saveFile->ReadBool( b ); + if ( b ) { + if ( !fc ) { + fc = new idAFConstraint_HingeFriction; + } + fc->Setup( this ); + fc->Restore( saveFile ); + } +} + + +//=============================================================== +// +// idAFConstraint_HingeFriction +// +//=============================================================== + +/* +================ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction +================ +*/ +idAFConstraint_HingeFriction::idAFConstraint_HingeFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_HingeFriction::Setup +================ +*/ +void idAFConstraint_HingeFriction::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeFriction::Evaluate +================ +*/ +void idAFConstraint_HingeFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::ApplyFriction +================ +*/ +void idAFConstraint_HingeFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeFriction::Add +================ +*/ +bool idAFConstraint_HingeFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 a1, a2; + float f; + + physics = phys; + + f = hinge->GetFriction() * hinge->GetMultiplier().Length(); + if ( f == 0.0f ) { + return false; + } + + lo[0] = -f; + hi[0] = f; + + hinge->GetAxis( a1, a2 ); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeFriction::Translate +================ +*/ +void idAFConstraint_HingeFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeFriction::Rotate +================ +*/ +void idAFConstraint_HingeFriction::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_HingeSteering +// +//=============================================================== + +/* +================ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering +================ +*/ +idAFConstraint_HingeSteering::idAFConstraint_HingeSteering( void ) { + type = CONSTRAINT_HINGESTEERING; + name = "hingeFriction"; + InitSize( 1 ); + hinge = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; + steerSpeed = 0.0f; + epsilon = LCP_EPSILON; +} + +/* +================ +idAFConstraint_HingeSteering::Save +================ +*/ +void idAFConstraint_HingeSteering::Save( idSaveGame *saveFile ) const { + + saveFile->WriteFloat(steerAngle); + saveFile->WriteFloat(steerSpeed); + saveFile->WriteFloat(epsilon); + + //if ( hinge ) { // cnicholson: Added unsaved var (doesnt work) + // saveFile->WriteBool( true ); + // hinge->Save( saveFile ); + //} else { + // saveFile->WriteBool( false ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Restore +================ +*/ +void idAFConstraint_HingeSteering::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadFloat(steerAngle); + saveFile->ReadFloat(steerSpeed); + saveFile->ReadFloat(epsilon); + +// cnicholson: Added unrestored var (doesnt work) + //bool b; + + //saveFile->ReadBool( b ); + //if ( b ) { + // if ( !hinge ) { + // hinge = new idAFConstraint_Hinge; + // } + // hinge->SetSteerAngle( steerAngle ); + // hinge->SetSteerSpeed( steerSpeed ); + // hinge->SetLimitEpsilon( epsilon ); + // hinge->Restore( saveFile ); + //} +} + +/* +================ +idAFConstraint_HingeSteering::Setup +================ +*/ +void idAFConstraint_HingeSteering::Setup( idAFConstraint_Hinge *h ) { + this->hinge = h; + body1 = h->GetBody1(); + body2 = h->GetBody2(); +} + +/* +================ +idAFConstraint_HingeSteering::Evaluate +================ +*/ +void idAFConstraint_HingeSteering::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::ApplyFriction +================ +*/ +void idAFConstraint_HingeSteering::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_HingeSteering::Add +================ +*/ +bool idAFConstraint_HingeSteering::Add( idPhysics_AF *phys, float invTimeStep ) { + float angle, speed; + idVec3 a1, a2; + + physics = phys; + + hinge->GetAxis( a1, a2 ); + angle = hinge->GetAngle(); + + a1 *= body1->GetWorldAxis(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0).Zero(); + J1.SubVec6(0).SubVec3(1) = a1; + + if ( body2 ) { + a2 *= body2->GetWorldAxis(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0).Zero(); + J2.SubVec6(0).SubVec3(1) = -a2; + } + + speed = steerAngle - angle; + if ( steerSpeed != 0.0f ) { + if ( speed > steerSpeed ) { + speed = steerSpeed; + } + else if ( speed < -steerSpeed ) { + speed = -steerSpeed; + } + } + + c1[0] = DEG2RAD( speed ) * invTimeStep; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_HingeSteering::Translate +================ +*/ +void idAFConstraint_HingeSteering::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_HingeSteering::Rotate +================ +*/ +void idAFConstraint_HingeSteering::Rotate( const idRotation &rotation ) { +} + + +//=============================================================== +// +// idAFConstraint_Slider +// +//=============================================================== + +/* +================ +idAFConstraint_Slider::idAFConstraint_Slider +================ +*/ +idAFConstraint_Slider::idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SLIDER; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 5 ); + fl.allowPrimary = true; + fl.noCollision = true; + + if ( body2 ) { + offset = ( body1->GetWorldOrigin() - body2->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + relAxis = body1->GetWorldAxis() * body2->GetWorldAxis().Transpose(); + } + else { + offset = body1->GetWorldOrigin(); + relAxis = body1->GetWorldAxis(); + } +} + +/* +================ +idAFConstraint_Slider::SetAxis +================ +*/ +void idAFConstraint_Slider::SetAxis( const idVec3 &ax ) { + idVec3 normAxis; + + // get normalized axis relative to body1 + normAxis = ax; + normAxis.Normalize(); + if ( body2 ) { + axis = normAxis * body2->GetWorldAxis().Transpose(); + } + else { + axis = normAxis; + } +} + +/* +================ +idAFConstraint_Slider::Evaluate +================ +*/ +void idAFConstraint_Slider::Evaluate( float invTimeStep ) { + idVec3 vecX, vecY, ofs; + idRotation r; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + (axis * master->GetWorldAxis()).OrthogonalBasis( vecX, vecY ); + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * (relAxis * master->GetWorldAxis()) ).ToRotation(); + } + else { + axis.OrthogonalBasis( vecX, vecY ); + ofs = offset - body1->GetWorldOrigin(); + r = ( body1->GetWorldAxis().Transpose() * relAxis ).ToRotation(); + } + + J1.Set( mat3_zero, mat3_identity, + idMat3( vecX, vecY, vec3_origin ), mat3_zero ); + J1.SetSize( 5, 6 ); + + if ( body2 ) { + + J2.Set( mat3_zero, -mat3_identity, + idMat3( -vecX, -vecY, vec3_origin ), mat3_zero ); + J2.SetSize( 5, 6 ); + } + else { + J2.Zero( 5, 6 ); + } + + c1.SubVec3(0) = -( invTimeStep * ERROR_REDUCTION ) * ( r.GetVec() * - (float) DEG2RAD( r.GetAngle() ) ); + + c1[3] = -( invTimeStep * ERROR_REDUCTION ) * ( vecX * ofs ); + c1[4] = -( invTimeStep * ERROR_REDUCTION ) * ( vecY * ofs ); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Slider::ApplyFriction +================ +*/ +void idAFConstraint_Slider::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Slider::Translate +================ +*/ +void idAFConstraint_Slider::Translate( const idVec3 &translation ) { + if ( !body2 ) { + offset += translation; + } +} + +/* +================ +idAFConstraint_Slider::Rotate +================ +*/ +void idAFConstraint_Slider::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + offset *= rotation; + } +} + +/* +================ +idAFConstraint_Slider::GetCenter +================ +*/ +void idAFConstraint_Slider::GetCenter( idVec3 ¢er ) { + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + center = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + center = offset - body1->GetWorldOrigin(); + } +} + +/* +================ +idAFConstraint_Slider::DebugDraw +================ +*/ +void idAFConstraint_Slider::DebugDraw( void ) { + idVec3 ofs; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + if ( master ) { + ofs = master->GetWorldOrigin() + master->GetWorldAxis() * offset - body1->GetWorldOrigin(); + } + else { + ofs = offset - body1->GetWorldOrigin(); + } + gameRenderWorld->DebugLine( colorGreen, ofs, ofs + axis * body1->GetWorldAxis() ); +} + +/* +================ +idAFConstraint_Slider::Save +================ +*/ +void idAFConstraint_Slider::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( axis ); + saveFile->WriteVec3( offset ); + saveFile->WriteMat3( relAxis ); +} + +/* +================ +idAFConstraint_Slider::Restore +================ +*/ +void idAFConstraint_Slider::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( axis ); + saveFile->ReadVec3( offset ); + saveFile->ReadMat3( relAxis ); +} + + +//=============================================================== +// +// idAFConstraint_Line +// +//=============================================================== + +/* +================ +idAFConstraint_Line::idAFConstraint_Line +================ +*/ +idAFConstraint_Line::idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Evaluate +================ +*/ +void idAFConstraint_Line::Evaluate( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::ApplyFriction +================ +*/ +void idAFConstraint_Line::ApplyFriction( float invTimeStep ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Translate +================ +*/ +void idAFConstraint_Line::Translate( const idVec3 &translation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::Rotate +================ +*/ +void idAFConstraint_Line::Rotate( const idRotation &rotation ) { + assert( 0 ); // FIXME: implement +} + +/* +================ +idAFConstraint_Line::DebugDraw +================ +*/ +void idAFConstraint_Line::DebugDraw( void ) { + assert( 0 ); // FIXME: implement +} + + +//=============================================================== +// +// idAFConstraint_Plane +// +//=============================================================== + +/* +================ +idAFConstraint_Plane::idAFConstraint_Plane +================ +*/ +idAFConstraint_Plane::idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_PLANE; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = true; + fl.noCollision = true; +} + +/* +================ +idAFConstraint_Plane::SetPlane +================ +*/ +void idAFConstraint_Plane::SetPlane( const idVec3 &normal, const idVec3 &anchor ) { + // get anchor relative to center of mass of body1 + anchor1 = ( anchor - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( anchor - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + planeNormal = normal * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = anchor; + planeNormal = normal; + } +} + +/* +================ +idAFConstraint_Plane::Evaluate +================ +*/ +void idAFConstraint_Plane::Evaluate( float invTimeStep ) { + idVec3 a1, a2, normal, p; + idVec6 v; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + normal = planeNormal * master->GetWorldAxis(); + } + else { + a2 = anchor2; + normal = planeNormal; + } + + p = a1 - body1->GetWorldOrigin(); + v.SubVec3(0) = normal; + v.SubVec3(1) = p.Cross( normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + + if ( body2 ) { + p = a1 - body2->GetWorldOrigin(); + v.SubVec3(0) = -normal; + v.SubVec3(1) = p.Cross( -normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + } + + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * (a1 * normal - a2 * normal); + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Plane::ApplyFriction +================ +*/ +void idAFConstraint_Plane::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Plane::Translate +================ +*/ +void idAFConstraint_Plane::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Plane::Rotate +================ +*/ +void idAFConstraint_Plane::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + planeNormal *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_Plane::DebugDraw +================ +*/ +void idAFConstraint_Plane::DebugDraw( void ) { + idVec3 a1, normal, right, up; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + normal = planeNormal * master->GetWorldAxis(); + } + else { + normal = planeNormal; + } + normal.NormalVectors( right, up ); + normal *= 4.0f; + right *= 4.0f; + up *= 4.0f; + + gameRenderWorld->DebugLine( colorCyan, a1 - right, a1 + right ); + gameRenderWorld->DebugLine( colorCyan, a1 - up, a1 + up ); + gameRenderWorld->DebugArrow( colorCyan, a1, a1 + normal, 1 ); +} + +/* +================ +idAFConstraint_Plane::Save +================ +*/ +void idAFConstraint_Plane::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteVec3( planeNormal ); +} + +/* +================ +idAFConstraint_Plane::Restore +================ +*/ +void idAFConstraint_Plane::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadVec3( planeNormal ); +} + + +//=============================================================== +// +// idAFConstraint_Spring +// +//=============================================================== + +/* +================ +idAFConstraint_Spring::idAFConstraint_Spring +================ +*/ +idAFConstraint_Spring::idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ) { + assert( body1 ); + type = CONSTRAINT_SPRING; + this->name = name; + this->body1 = body1; + this->body2 = body2; + InitSize( 1 ); + fl.allowPrimary = false; + kstretch = kcompress = damping = 1.0f; + minLength = maxLength = restLength = 0.0f; +} + +/* +================ +idAFConstraint_Spring::SetAnchor +================ +*/ +void idAFConstraint_Spring::SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ) { + // get anchor relative to center of mass of body1 + anchor1 = ( worldAnchor1 - body1->GetWorldOrigin() ) * body1->GetWorldAxis().Transpose(); + if ( body2 ) { + // get anchor relative to center of mass of body2 + anchor2 = ( worldAnchor2 - body2->GetWorldOrigin() ) * body2->GetWorldAxis().Transpose(); + } + else { + anchor2 = worldAnchor2; + } +} + +/* +================ +idAFConstraint_Spring::SetSpring +================ +*/ +void idAFConstraint_Spring::SetSpring( const float stretch, const float compress, const float damping, const float restLength ) { + assert( stretch >= 0.0f && compress >= 0.0f && restLength >= 0.0f ); + this->kstretch = stretch; + this->kcompress = compress; + this->damping = damping; + this->restLength = restLength; +} + +/* +================ +idAFConstraint_Spring::SetLimit +================ +*/ +void idAFConstraint_Spring::SetLimit( const float minLength, const float maxLength ) { + assert( minLength >= 0.0f && maxLength >= 0.0f && maxLength >= minLength ); + this->minLength = minLength; + this->maxLength = maxLength; +} + +/* +================ +idAFConstraint_Spring::Evaluate +================ +*/ +void idAFConstraint_Spring::Evaluate( float invTimeStep ) { + idVec3 a1, a2, velocity1, velocity2, force; + idVec6 v1, v2; + float d, dampingForce, length, error; + bool limit; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + velocity1 = body1->GetPointVelocity( a1 ); + + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + velocity2 = master->GetPointVelocity( a2 ); + } + else { + a2 = anchor2; + velocity2.Zero(); + } + + force = a2 - a1; + d = force * force; + if ( d != 0.0f ) { + dampingForce = damping * idMath::Fabs( (velocity2 - velocity1) * force ) / d; + } + else { + dampingForce = 0.0f; + } + length = force.Normalize(); + + if ( length > restLength ) { + if ( kstretch > 0.0f ) { + idVec3 springForce = force * ( Square( length - restLength ) * kstretch - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + else { + if ( kcompress > 0.0f ) { + idVec3 springForce = force * -( Square( restLength - length ) * kcompress - dampingForce ); + body1->AddForce( a1, springForce ); + if ( master ) { + master->AddForce( a2, -springForce ); + } + } + } + + // check for spring limits + if ( length < minLength ) { + force = -force; + error = minLength - length; + limit = true; + } + else if ( maxLength > 0.0f && length > maxLength ) { + error = length - maxLength; + limit = true; + } + else { + error = 0.0f; + limit = false; + } + + if ( limit ) { + a1 -= body1->GetWorldOrigin(); + v1.SubVec3(0) = force; + v1.SubVec3(1) = a1.Cross( force ); + J1.Set( 1, 6, v1.ToFloatPtr() ); + if ( body2 ) { + a2 -= body2->GetWorldOrigin(); + v2.SubVec3(0) = -force; + v2.SubVec3(1) = a2.Cross( -force ); + J2.Set( 1, 6, v2.ToFloatPtr() ); + } + c1[0] = -( invTimeStep * ERROR_REDUCTION ) * error; + lo[0] = 0.0f; + } + else { + J1.Zero( 0, 0 ); + J2.Zero( 0, 0 ); + } + + c1.Clamp( -ERROR_REDUCTION_MAX, ERROR_REDUCTION_MAX ); +} + +/* +================ +idAFConstraint_Spring::ApplyFriction +================ +*/ +void idAFConstraint_Spring::ApplyFriction( float invTimeStep ) { + // no friction +} + +/* +================ +idAFConstraint_Spring::Translate +================ +*/ +void idAFConstraint_Spring::Translate( const idVec3 &translation ) { + if ( !body2 ) { + anchor2 += translation; + } +} + +/* +================ +idAFConstraint_Spring::Rotate +================ +*/ +void idAFConstraint_Spring::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + anchor2 *= rotation; + } +} + +/* +================ +idAFConstraint_Spring::GetCenter +================ +*/ +void idAFConstraint_Spring::GetCenter( idVec3 ¢er ) { + idAFBody *master; + idVec3 a1, a2; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + center = ( a1 + a2 ) * 0.5f; +} + +/* +================ +idAFConstraint_Spring::DebugDraw +================ +*/ +void idAFConstraint_Spring::DebugDraw( void ) { + idAFBody *master; + float length; + idVec3 a1, a2, dir, mid, p; + + master = body2 ? body2 : physics->GetMasterBody(); + a1 = body1->GetWorldOrigin() + anchor1 * body1->GetWorldAxis(); + if ( master ) { + a2 = master->GetWorldOrigin() + anchor2 * master->GetWorldAxis(); + } + else { + a2 = anchor2; + } + dir = a2 - a1; + mid = a1 + 0.5f * dir; + length = dir.Normalize(); + + // draw spring + gameRenderWorld->DebugLine( colorGreen, a1, a2 ); + + // draw rest length + p = restLength * 0.5f * dir; + gameRenderWorld->DebugCircle( colorWhite, mid + p, dir, 1.0f, 10 ); + gameRenderWorld->DebugCircle( colorWhite, mid - p, dir, 1.0f, 10 ); + if ( restLength > length ) { + gameRenderWorld->DebugLine( colorWhite, a2, mid + p ); + gameRenderWorld->DebugLine( colorWhite, a1, mid - p ); + } + + if ( minLength > 0.0f ) { + // draw min length + gameRenderWorld->DebugCircle( colorBlue, mid + minLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorBlue, mid - minLength * 0.5f * dir, dir, 2.0f, 10 ); + } + + if ( maxLength > 0.0f ) { + // draw max length + gameRenderWorld->DebugCircle( colorRed, mid + maxLength * 0.5f * dir, dir, 2.0f, 10 ); + gameRenderWorld->DebugCircle( colorRed, mid - maxLength * 0.5f * dir, dir, 2.0f, 10 ); + } +} + +/* +================ +idAFConstraint_Spring::Save +================ +*/ +void idAFConstraint_Spring::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( anchor1 ); + saveFile->WriteVec3( anchor2 ); + saveFile->WriteFloat( kstretch ); + saveFile->WriteFloat( kcompress ); + saveFile->WriteFloat( damping ); + saveFile->WriteFloat( restLength ); + saveFile->WriteFloat( minLength ); + saveFile->WriteFloat( maxLength ); +} + +/* +================ +idAFConstraint_Spring::Restore +================ +*/ +void idAFConstraint_Spring::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( anchor1 ); + saveFile->ReadVec3( anchor2 ); + saveFile->ReadFloat( kstretch ); + saveFile->ReadFloat( kcompress ); + saveFile->ReadFloat( damping ); + saveFile->ReadFloat( restLength ); + saveFile->ReadFloat( minLength ); + saveFile->ReadFloat( maxLength ); +} + + +//=============================================================== +// +// idAFConstraint_Contact +// +//=============================================================== + +/* +================ +idAFConstraint_Contact::idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::idAFConstraint_Contact( void ) { + name = "contact"; + type = CONSTRAINT_CONTACT; + InitSize( 1 ); + fc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_Contact::~idAFConstraint_Contact +================ +*/ +idAFConstraint_Contact::~idAFConstraint_Contact( void ) { + if ( fc ) { + delete fc; + } +} + +/* +================ +idAFConstraint_Contact::Setup +================ +*/ +void idAFConstraint_Contact::Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ) { + idVec3 p; + idVec6 v; + float vel; + + assert( b1 ); + + body1 = b1; + body2 = b2; + contact = c; + + p = c.point - body1->GetWorldOrigin(); + v.SubVec3(0) = c.normal; + v.SubVec3(1) = p.Cross( c.normal ); + J1.Set( 1, 6, v.ToFloatPtr() ); + vel = v.SubVec3(0) * body1->GetLinearVelocity() + v.SubVec3(1) * body1->GetAngularVelocity(); + + if ( body2 ) { + p = c.point - body2->GetWorldOrigin(); + v.SubVec3(0) = -c.normal; + v.SubVec3(1) = p.Cross( -c.normal ); + J2.Set( 1, 6, v.ToFloatPtr() ); + vel += v.SubVec3(0) * body2->GetLinearVelocity() + v.SubVec3(1) * body2->GetAngularVelocity(); + c2[0] = 0.0f; + } + + if ( body1->GetBouncyness() > 0.0f ) { + c1[0] = body1->GetBouncyness() * -vel; + } + else { + c1[0] = 0.0f; + } + + e[0] = CONTACT_LCP_EPSILON; + lo[0] = 0.0f; + hi[0] = idMath::INFINITY; + boxConstraint = NULL; + boxIndex[0] = -1; +} + +/* +================ +idAFConstraint_Contact::Evaluate +================ +*/ +void idAFConstraint_Contact::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_Contact::ApplyFriction +================ +*/ +void idAFConstraint_Contact::ApplyFriction( float invTimeStep ) { + idVec3 r, velocity, normal, dir1, dir2; + float friction, magnitude, forceNumerator, forceDenominator; + idVecX impulse, dv; + + friction = body1->GetContactFriction(); + if ( body2 && body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + + friction *= physics->GetContactFrictionScale(); + + if ( friction <= 0.0f ) { + return; + } + + // seperate friction per contact is silly but it's fast and often looks close enough + if ( af_useImpulseFriction.GetBool() ) { + + impulse.SetData( 6, VECX_ALLOCA( 6 ) ); + dv.SetData( 6, VECX_ALLOCA( 6 ) ); + + // calculate velocity in the contact plane + r = contact.point - body1->GetWorldOrigin(); + velocity = body1->GetLinearVelocity() + body1->GetAngularVelocity().Cross( r ); + velocity -= contact.normal * velocity * contact.normal; + + // get normalized direction of friction and magnitude of velocity + normal = -velocity; + magnitude = normal.Normalize(); + + forceNumerator = friction * magnitude; + forceDenominator = body1->GetInverseMass() + ( ( body1->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse.SubVec3(0) = (forceNumerator / forceDenominator) * normal; + impulse.SubVec3(1) = r.Cross( impulse.SubVec3(0) ); + body1->InverseWorldSpatialInertiaMultiply( dv, impulse.ToFloatPtr() ); + + // modify velocity with friction force + body1->SetLinearVelocity( body1->GetLinearVelocity() + dv.SubVec3(0) ); + body1->SetAngularVelocity( body1->GetAngularVelocity() + dv.SubVec3(1) ); + } + else { + + if ( !fc ) { + fc = new idAFConstraint_ContactFriction; + } + // call setup each frame because contact constraints are re-used for different bodies + fc->Setup( this ); + fc->Add( physics, invTimeStep ); + } +} + +/* +================ +idAFConstraint_Contact::Translate +================ +*/ +void idAFConstraint_Contact::Translate( const idVec3 &translation ) { + assert( 0 ); // contact should never be translated +} + +/* +================ +idAFConstraint_Contact::Rotate +================ +*/ +void idAFConstraint_Contact::Rotate( const idRotation &rotation ) { + assert( 0 ); // contact should never be rotated +} + +/* +================ +idAFConstraint_Contact::GetCenter +================ +*/ +void idAFConstraint_Contact::GetCenter( idVec3 ¢er ) { + center = contact.point; +} + +/* +================ +idAFConstraint_Contact::DebugDraw +================ +*/ +void idAFConstraint_Contact::DebugDraw( void ) { + idVec3 x, y; + contact.normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contact.point, contact.point + 6.0f * contact.normal ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * x, contact.point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contact.point - 2.0f * y, contact.point + 2.0f * y ); +} + + +//=============================================================== +// +// idAFConstraint_ContactFriction +// +//=============================================================== + +/* +================ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction +================ +*/ +idAFConstraint_ContactFriction::idAFConstraint_ContactFriction( void ) { + type = CONSTRAINT_FRICTION; + name = "contactFriction"; + InitSize( 2 ); + cc = NULL; + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ContactFriction::Setup +================ +*/ +void idAFConstraint_ContactFriction::Setup( idAFConstraint_Contact *cc ) { + this->cc = cc; + body1 = cc->GetBody1(); + body2 = cc->GetBody2(); +} + +/* +================ +idAFConstraint_ContactFriction::Evaluate +================ +*/ +void idAFConstraint_ContactFriction::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::ApplyFriction +================ +*/ +void idAFConstraint_ContactFriction::ApplyFriction( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ContactFriction::Add +================ +*/ +bool idAFConstraint_ContactFriction::Add( idPhysics_AF *phys, float invTimeStep ) { + idVec3 r, dir1, dir2; + float friction; + int newRow; + + physics = phys; + + friction = body1->GetContactFriction() * physics->GetContactFrictionScale(); + + // if the body only has friction in one direction + if ( body1->GetFrictionDirection( dir1 ) ) { + // project the friction direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 1, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + c1.SetSize( 1 ); + c1[0] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 1, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + c2.SetSize( 1 ); + c2[0] = 0.0f; + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + } + else { + // get two friction directions orthogonal to contact normal + cc->GetContact().normal.NormalVectors( dir1, dir2 ); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + J1.SetSize( 2, 6 ); + J1.SubVec6(0).SubVec3(0) = dir1; + J1.SubVec6(0).SubVec3(1) = r.Cross( dir1 ); + J1.SubVec6(1).SubVec3(0) = dir2; + J1.SubVec6(1).SubVec3(1) = r.Cross( dir2 ); + c1.SetSize( 2 ); + c1[0] = c1[1] = 0.0f; + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.SetSize( 2, 6 ); + J2.SubVec6(0).SubVec3(0) = -dir1; + J2.SubVec6(0).SubVec3(1) = r.Cross( -dir1 ); + J2.SubVec6(1).SubVec3(0) = -dir2; + J2.SubVec6(1).SubVec3(1) = r.Cross( -dir2 ); + c2.SetSize( 2 ); + c2[0] = c2[1] = 0.0f; + + if ( body2->GetContactFriction() < friction ) { + friction = body2->GetContactFriction(); + } + } + + lo[0] = -friction; + hi[0] = friction; + boxConstraint = cc; + boxIndex[0] = 0; + lo[1] = -friction; + hi[1] = friction; + boxIndex[1] = 0; + } + + if ( body1->GetContactMotorDirection( dir1 ) && body1->GetContactMotorForce() > 0.0f ) { + // project the motor force direction into the contact plane + dir1 -= dir1 * cc->GetContact().normal * dir1; + dir1.Normalize(); + + r = cc->GetContact().point - body1->GetWorldOrigin(); + + newRow = J1.GetNumRows(); + J1.ChangeSize( newRow+1, J1.GetNumColumns() ); + J1.SubVec6(newRow).SubVec3(0) = -dir1; + J1.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c1.ChangeSize( newRow+1 ); + c1[newRow] = body1->GetContactMotorVelocity(); + + if ( body2 ) { + r = cc->GetContact().point - body2->GetWorldOrigin(); + + J2.ChangeSize( newRow+1, J2.GetNumColumns() ); + J2.SubVec6(newRow).SubVec3(0) = -dir1; + J2.SubVec6(newRow).SubVec3(1) = r.Cross( -dir1 ); + c2.ChangeSize( newRow+1 ); + c2[newRow] = 0.0f; + } + + lo[newRow] = -body1->GetContactMotorForce(); + hi[newRow] = body1->GetContactMotorForce(); + boxIndex[newRow] = -1; + } + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ContactFriction::Translate +================ +*/ +void idAFConstraint_ContactFriction::Translate( const idVec3 &translation ) { +} + +/* +================ +idAFConstraint_ContactFriction::Rotate +================ +*/ +void idAFConstraint_ContactFriction::Rotate( const idRotation &rotation ) { +} + +/* +================ +idAFConstraint_ContactFriction::DebugDraw +================ +*/ +void idAFConstraint_ContactFriction::DebugDraw( void ) { +} + + +//=============================================================== +// +// idAFConstraint_ConeLimit +// +//=============================================================== + +/* +================ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit +================ +*/ +idAFConstraint_ConeLimit::idAFConstraint_ConeLimit( void ) { + type = CONSTRAINT_CONELIMIT; + name = "coneLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_ConeLimit::Setup + + the coneAnchor is the top of the cone in body2 space + the coneAxis is the axis of the cone in body2 space + the coneAngle is the angle the cone hull makes at the top + the body1Axis is the axis in body1 space that should stay within the cone +================ +*/ +void idAFConstraint_ConeLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ) { + this->body1 = b1; + this->body2 = b2; + this->coneAxis = coneAxis; + this->coneAxis.Normalize(); + this->coneAnchor = coneAnchor; + this->body1Axis = body1Axis; + this->body1Axis.Normalize(); + this->cosAngle = idMath::Cos( DEG2RAD( coneAngle * 0.5f ) ); + this->sinHalfAngle = idMath::Sin( DEG2RAD( coneAngle * 0.25f ) ); + this->cosHalfAngle = idMath::Cos( DEG2RAD( coneAngle * 0.25f ) ); +} + +/* +================ +idAFConstraint_ConeLimit::SetAnchor +================ +*/ +void idAFConstraint_ConeLimit::SetAnchor( const idVec3 &coneAnchor ) { + this->coneAnchor = coneAnchor; +} + +/* +================ +idAFConstraint_ConeLimit::SetBody1Axis +================ +*/ +void idAFConstraint_ConeLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_ConeLimit::Evaluate +================ +*/ +void idAFConstraint_ConeLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_ConeLimit::ApplyFriction +================ +*/ +void idAFConstraint_ConeLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_ConeLimit::Add +================ +*/ +bool idAFConstraint_ConeLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + float a; + idVec6 J1row, J2row; + idVec3 ax, anchor, body1ax, normal, coneVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + a = ax * body1ax; + + // if the body1 axis is inside the cone + if ( a > cosAngle ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward cone normal for the position the body1 axis went outside the cone + normal = body1ax.Cross( ax ); + normal.Normalize(); + q.x = normal.x * sinHalfAngle; + q.y = normal.y * sinHalfAngle; + q.z = normal.z * sinHalfAngle; + q.w = cosHalfAngle; + coneVector = ax * q.ToMat3(); + normal = coneVector.Cross( ax ).Cross( coneVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * coneVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * coneVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_ConeLimit::Translate +================ +*/ +void idAFConstraint_ConeLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + coneAnchor += translation; + } +} + +/* +================ +idAFConstraint_ConeLimit::Rotate +================ +*/ +void idAFConstraint_ConeLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + coneAnchor *= rotation; + coneAxis *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_ConeLimit::DebugDraw +================ +*/ +void idAFConstraint_ConeLimit::DebugDraw( void ) { + idVec3 ax, anchor, x, y, z, start, end; + float sinAngle, a, size = 10.0f; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + ax = coneAxis * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + coneAnchor * master->GetWorldAxis(); + } + else { + ax = coneAxis; + anchor = coneAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw cone + ax.NormalVectors( x, y ); + sinAngle = idMath::Sqrt( 1.0f - cosAngle * cosAngle ); + x *= size * sinAngle; + y *= size * sinAngle; + z = anchor + ax * size * cosAngle; + start = x + z; + for ( a = 0.0f; a < 360.0f; a += 45.0f ) { + end = x * idMath::Cos( DEG2RAD(a + 45.0f) ) + y * idMath::Sin( DEG2RAD(a + 45.0f) ) + z; + gameRenderWorld->DebugLine( colorMagenta, anchor, start ); + gameRenderWorld->DebugLine( colorMagenta, start, end ); + start = end; + } +} + +/* +================ +idAFConstraint_ConeLimit::Save +================ +*/ +void idAFConstraint_ConeLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( coneAnchor ); + saveFile->WriteVec3( coneAxis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle ); + saveFile->WriteFloat( sinHalfAngle ); + saveFile->WriteFloat( cosHalfAngle ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_ConeLimit::Restore +================ +*/ +void idAFConstraint_ConeLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( coneAnchor ); + saveFile->ReadVec3( coneAxis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle ); + saveFile->ReadFloat( sinHalfAngle ); + saveFile->ReadFloat( cosHalfAngle ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFConstraint_PyramidLimit +// +//=============================================================== + +/* +================ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit +================ +*/ +idAFConstraint_PyramidLimit::idAFConstraint_PyramidLimit( void ) { + type = CONSTRAINT_PYRAMIDLIMIT; + name = "pyramidLimit"; + InitSize( 1 ); + fl.allowPrimary = false; + fl.frameConstraint = true; +} + +/* +================ +idAFConstraint_PyramidLimit::Setup +================ +*/ +void idAFConstraint_PyramidLimit::Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ) { + body1 = b1; + body2 = b2; + // setup the base and make sure the basis is orthonormal + pyramidBasis[2] = pyramidAxis; + pyramidBasis[2].Normalize(); + pyramidBasis[0] = baseAxis; + pyramidBasis[0] -= pyramidBasis[2] * baseAxis * pyramidBasis[2]; + pyramidBasis[0].Normalize(); + pyramidBasis[1] = pyramidBasis[0].Cross( pyramidBasis[2] ); + // pyramid top + this->pyramidAnchor = pyramidAnchor; + // angles + cosAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.5f ) ); + cosAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.5f ) ); + sinHalfAngle[0] = idMath::Sin( DEG2RAD( pyramidAngle1 * 0.25f ) ); + sinHalfAngle[1] = idMath::Sin( DEG2RAD( pyramidAngle2 * 0.25f ) ); + cosHalfAngle[0] = idMath::Cos( DEG2RAD( pyramidAngle1 * 0.25f ) ); + cosHalfAngle[1] = idMath::Cos( DEG2RAD( pyramidAngle2 * 0.25f ) ); + + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::SetAnchor +================ +*/ +void idAFConstraint_PyramidLimit::SetAnchor( const idVec3 &pyramidAnchor ) { + this->pyramidAnchor = pyramidAnchor; +} + +/* +================ +idAFConstraint_PyramidLimit::SetBody1Axis +================ +*/ +void idAFConstraint_PyramidLimit::SetBody1Axis( const idVec3 &body1Axis ) { + this->body1Axis = body1Axis; +} + +/* +================ +idAFConstraint_PyramidLimit::Evaluate +================ +*/ +void idAFConstraint_PyramidLimit::Evaluate( float invTimeStep ) { + // do nothing +} + +/* +================ +idAFConstraint_PyramidLimit::ApplyFriction +================ +*/ +void idAFConstraint_PyramidLimit::ApplyFriction( float invTimeStep ) { +} + +/* +================ +idAFConstraint_PyramidLimit::Add +================ +*/ +bool idAFConstraint_PyramidLimit::Add( idPhysics_AF *phys, float invTimeStep ) { + int i; + float a[2]; + idVec6 J1row, J2row; + idMat3 worldBase; + idVec3 anchor, body1ax, ax[2], v, normal, pyramidVector, p1, p2; + idQuat q; + idAFBody *master; + + if ( af_skipLimits.GetBool() ) { + lm.Zero(); // constraint exerts no force + return false; + } + + physics = phys; + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + body1ax = body1Axis * body1->GetWorldAxis(); + + for ( i = 0; i < 2; i++ ) { + ax[i] = body1ax - worldBase[!i] * body1ax * worldBase[!i]; + ax[i].Normalize(); + a[i] = worldBase[2] * ax[i]; + } + + // if the body1 axis is inside the pyramid + if ( a[0] > cosAngle[0] && a[1] > cosAngle[1] ) { + lm.Zero(); // constraint exerts no force + return false; + } + + // calculate the inward pyramid normal for the position the body1 axis went outside the pyramid + pyramidVector = worldBase[2]; + for ( i = 0; i < 2; i++ ) { + if ( a[i] <= cosAngle[i] ) { + v = ax[i].Cross( worldBase[2] ); + v.Normalize(); + q.x = v.x * sinHalfAngle[i]; + q.y = v.y * sinHalfAngle[i]; + q.z = v.z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + pyramidVector *= q.ToMat3(); + } + } + normal = pyramidVector.Cross( worldBase[2] ).Cross( pyramidVector ); + normal.Normalize(); + + p1 = anchor + 32.0f * pyramidVector - body1->GetWorldOrigin(); + + J1row.SubVec3(0) = normal; + J1row.SubVec3(1) = p1.Cross( normal ); + J1.Set( 1, 6, J1row.ToFloatPtr() ); + + c1[0] = (invTimeStep * LIMIT_ERROR_REDUCTION) * ( normal * (32.0f * body1ax) ); + + if ( body2 ) { + + p2 = anchor + 32.0f * pyramidVector - master->GetWorldOrigin(); + + J2row.SubVec3(0) = -normal; + J2row.SubVec3(1) = p2.Cross( -normal ); + J2.Set( 1, 6, J2row.ToFloatPtr() ); + + c2[0] = 0.0f; + } + + lo[0] = 0.0f; + e[0] = LIMIT_LCP_EPSILON; + + physics->AddFrameConstraint( this ); + + return true; +} + +/* +================ +idAFConstraint_PyramidLimit::Translate +================ +*/ +void idAFConstraint_PyramidLimit::Translate( const idVec3 &translation ) { + if ( !body2 ) { + pyramidAnchor += translation; + } +} + +/* +================ +idAFConstraint_PyramidLimit::Rotate +================ +*/ +void idAFConstraint_PyramidLimit::Rotate( const idRotation &rotation ) { + if ( !body2 ) { + pyramidAnchor *= rotation; + pyramidBasis[0] *= rotation.ToMat3(); + pyramidBasis[1] *= rotation.ToMat3(); + pyramidBasis[2] *= rotation.ToMat3(); + } +} + +/* +================ +idAFConstraint_PyramidLimit::DebugDraw +================ +*/ +void idAFConstraint_PyramidLimit::DebugDraw( void ) { + int i; + float size = 10.0f; + idVec3 anchor, dir, p[4]; + idMat3 worldBase, m[2]; + idQuat q; + idAFBody *master; + + master = body2 ? body2 : physics->GetMasterBody(); + + if ( master ) { + worldBase[0] = pyramidBasis[0] * master->GetWorldAxis(); + worldBase[1] = pyramidBasis[1] * master->GetWorldAxis(); + worldBase[2] = pyramidBasis[2] * master->GetWorldAxis(); + anchor = master->GetWorldOrigin() + pyramidAnchor * master->GetWorldAxis(); + } + else { + worldBase = pyramidBasis; + anchor = pyramidAnchor; + } + + // draw body1 axis + gameRenderWorld->DebugLine( colorGreen, anchor, anchor + size * (body1Axis * body1->GetWorldAxis()) ); + + // draw the pyramid + for ( i = 0; i < 2; i++ ) { + q.x = worldBase[!i].x * sinHalfAngle[i]; + q.y = worldBase[!i].y * sinHalfAngle[i]; + q.z = worldBase[!i].z * sinHalfAngle[i]; + q.w = cosHalfAngle[i]; + m[i] = q.ToMat3(); + } + + dir = worldBase[2] * size; + p[0] = anchor + m[0] * (m[1] * dir); + p[1] = anchor + m[0] * (m[1].Transpose() * dir); + p[2] = anchor + m[0].Transpose() * (m[1].Transpose() * dir); + p[3] = anchor + m[0].Transpose() * (m[1] * dir); + + for ( i = 0; i < 4; i++ ) { + gameRenderWorld->DebugLine( colorMagenta, anchor, p[i] ); + gameRenderWorld->DebugLine( colorMagenta, p[i], p[(i+1)&3] ); + } +} + +/* +================ +idAFConstraint_PyramidLimit::Save +================ +*/ +void idAFConstraint_PyramidLimit::Save( idSaveGame *saveFile ) const { + idAFConstraint::Save( saveFile ); + saveFile->WriteVec3( pyramidAnchor ); + saveFile->WriteMat3( pyramidBasis ); + saveFile->WriteVec3( body1Axis ); + saveFile->WriteFloat( cosAngle[0] ); + saveFile->WriteFloat( cosAngle[1] ); + saveFile->WriteFloat( sinHalfAngle[0] ); + saveFile->WriteFloat( sinHalfAngle[1] ); + saveFile->WriteFloat( cosHalfAngle[0] ); + saveFile->WriteFloat( cosHalfAngle[1] ); + saveFile->WriteFloat( epsilon ); +} + +/* +================ +idAFConstraint_PyramidLimit::Restore +================ +*/ +void idAFConstraint_PyramidLimit::Restore( idRestoreGame *saveFile ) { + idAFConstraint::Restore( saveFile ); + saveFile->ReadVec3( pyramidAnchor ); + saveFile->ReadMat3( pyramidBasis ); + saveFile->ReadVec3( body1Axis ); + saveFile->ReadFloat( cosAngle[0] ); + saveFile->ReadFloat( cosAngle[1] ); + saveFile->ReadFloat( sinHalfAngle[0] ); + saveFile->ReadFloat( sinHalfAngle[1] ); + saveFile->ReadFloat( cosHalfAngle[0] ); + saveFile->ReadFloat( cosHalfAngle[1] ); + saveFile->ReadFloat( epsilon ); +} + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( void ) { + Init(); +} + +/* +================ +idAFBody::idAFBody +================ +*/ +idAFBody::idAFBody( const idStr &name, idClipModel *clipModel, float density ) { + + assert( clipModel ); + assert( clipModel->IsTraceModel() ); + + Init(); + + this->name = name; + this->clipModel = NULL; + + SetClipModel( clipModel ); + SetDensity( density ); + + current->worldOrigin = clipModel->GetOrigin(); + current->worldAxis = clipModel->GetAxis(); + *next = *current; + +} + +/* +================ +idAFBody::~idAFBody +================ +*/ +idAFBody::~idAFBody( void ) { + delete clipModel; +} + +/* +================ +idAFBody::Init +================ +*/ +void idAFBody::Init( void ) { + name = "noname"; + parent = NULL; + clipModel = NULL; + primaryConstraint = NULL; + tree = NULL; + + linearFriction = -1.0f; + angularFriction = -1.0f; + contactFriction = -1.0f; + bouncyness = -1.0f; + clipMask = 0; + + frictionDir = vec3_zero; + contactMotorDir = vec3_zero; + contactMotorVelocity = 0.0f; + contactMotorForce = 0.0f; + + mass = 1.0f; + invMass = 1.0f; + centerOfMass = vec3_zero; + inertiaTensor = mat3_identity; + inverseInertiaTensor = mat3_identity; + + current = &state[0]; + next = &state[1]; + current->worldOrigin = vec3_zero; + current->worldAxis = mat3_identity; + current->spatialVelocity = vec6_zero; + current->externalForce = vec6_zero; + *next = *current; + saved = *current; + atRestOrigin = vec3_zero; + atRestAxis = mat3_identity; + + s.Zero( 6 ); + totalForce.Zero( 6 ); + auxForce.Zero( 6 ); + acceleration.Zero( 6 ); + + response = NULL; + responseIndex = NULL; + numResponses = 0; + maxAuxiliaryIndex = 0; + maxSubTreeAuxiliaryIndex = 0; + + memset( &fl, 0, sizeof( fl ) ); + + fl.selfCollision = true; + fl.isZero = true; +} + +/* +================ +idAFBody::SetClipModel +================ +*/ +void idAFBody::SetClipModel( idClipModel *clipModel ) { + if ( this->clipModel && this->clipModel != clipModel ) { + delete this->clipModel; + } + this->clipModel = clipModel; +} + +/* +================ +idAFBody::SetFriction +================ +*/ +void idAFBody::SetFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f ) { + gameLocal.Warning( "idAFBody::SetFriction: friction out of range, linear = %.1f, angular = %.1f, contact = %.1f", linear, angular, contact ); + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idAFBody::SetBouncyness +================ +*/ +void idAFBody::SetBouncyness( float bounce ) { + if ( bounce < 0.0f || bounce > 1.0f ) { + gameLocal.Warning( "idAFBody::SetBouncyness: bouncyness out of range, bounce = %.1f", bounce ); + return; + } + bouncyness = bounce; +} + +/* +================ +idAFBody::SetDensity +================ +*/ +void idAFBody::SetDensity( float density, const idMat3 &inertiaScale ) { + + // get the body mass properties + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // make sure we have a valid mass + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idAFBody::SetDensity: invalid mass for body '%s'", name.c_str() ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // make sure the center of mass is at the body origin + if ( !centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + gameLocal.Warning( "idAFBody::SetDentity: center of mass not at origin for body '%s'", name.c_str() ); + } + centerOfMass.Zero(); + + // calculate the inverse mass and inverse inertia tensor + invMass = 1.0f / mass; + if ( inertiaScale != mat3_identity ) { + inertiaTensor *= inertiaScale; + } + if ( inertiaTensor.IsDiagonal( 1e-3f ) ) { + inertiaTensor[0][1] = inertiaTensor[0][2] = 0.0f; + inertiaTensor[1][0] = inertiaTensor[1][2] = 0.0f; + inertiaTensor[2][0] = inertiaTensor[2][1] = 0.0f; + inverseInertiaTensor.Identity(); + inverseInertiaTensor[0][0] = 1.0f / inertiaTensor[0][0]; + inverseInertiaTensor[1][1] = 1.0f / inertiaTensor[1][1]; + inverseInertiaTensor[2][2] = 1.0f / inertiaTensor[2][2]; + } + else { + inverseInertiaTensor = inertiaTensor.Inverse(); + } +} + +/* +================ +idAFBody::SetFrictionDirection +================ +*/ +void idAFBody::SetFrictionDirection( const idVec3 &dir ) { + frictionDir = dir * current->worldAxis.Transpose(); + fl.useFrictionDir = true; +} + +/* +================ +idAFBody::GetFrictionDirection +================ +*/ +bool idAFBody::GetFrictionDirection( idVec3 &dir ) const { + if ( fl.useFrictionDir ) { + dir = frictionDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::SetContactMotorDirection +================ +*/ +void idAFBody::SetContactMotorDirection( const idVec3 &dir ) { + contactMotorDir = dir * current->worldAxis.Transpose(); + fl.useContactMotorDir = true; +} + +/* +================ +idAFBody::GetContactMotorDirection +================ +*/ +bool idAFBody::GetContactMotorDirection( idVec3 &dir ) const { + if ( fl.useContactMotorDir ) { + dir = contactMotorDir * current->worldAxis; + return true; + } + return false; +} + +/* +================ +idAFBody::GetPointVelocity +================ +*/ +idVec3 idAFBody::GetPointVelocity( const idVec3 &point ) const { + idVec3 r = point - current->worldOrigin; + return current->spatialVelocity.SubVec3(0) + current->spatialVelocity.SubVec3(1).Cross( r ); +} + +/* +================ +idAFBody::AddForce +================ +*/ +void idAFBody::AddForce( const idVec3 &point, const idVec3 &force ) { + current->externalForce.SubVec3(0) += force; + current->externalForce.SubVec3(1) += (point - current->worldOrigin).Cross( force ); +} + +/* +================ +idAFBody::InverseWorldSpatialInertiaMultiply + + dst = this->inverseWorldSpatialInertia * v; +================ +*/ +ID_INLINE void idAFBody::InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const { + const float *mPtr = inverseWorldSpatialInertia.ToFloatPtr(); + const float *vPtr = v; + float *dstPtr = dst.ToFloatPtr(); + + if ( fl.spatialInertiaSparse ) { + dstPtr[0] = mPtr[0*6+0] * vPtr[0]; + dstPtr[1] = mPtr[1*6+1] * vPtr[1]; + dstPtr[2] = mPtr[2*6+2] * vPtr[2]; + dstPtr[3] = mPtr[3*6+3] * vPtr[3] + mPtr[3*6+4] * vPtr[4] + mPtr[3*6+5] * vPtr[5]; + dstPtr[4] = mPtr[4*6+3] * vPtr[3] + mPtr[4*6+4] * vPtr[4] + mPtr[4*6+5] * vPtr[5]; + dstPtr[5] = mPtr[5*6+3] * vPtr[3] + mPtr[5*6+4] * vPtr[4] + mPtr[5*6+5] * vPtr[5]; + } else { + gameLocal.Warning( "spatial inertia is not sparse for body %s", name.c_str() ); + } +} + +/* +================ +idAFBody::Save +================ +*/ +void idAFBody::Save( idSaveGame *saveFile ) { + + saveFile->WriteString( name ); // cniciholson: Added Unsaved Var + //if ( parent ) { + // saveFile->WriteBool( true ); + // parent->Save( savefile ); // cniciholson: Added Unsaved Var + //} + //else { + // saveFile->WriteBool( false ); + //} + // TOSAVE: idList children + // TOSAVE: idClipModel * clipModel + // TOSAVE: idAFConstraint * primaryConstraint + // TOSAVE: idList constraints + // TOSAVE: idAFTree * tree; + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteInt( clipMask ); + saveFile->WriteVec3( frictionDir ); + saveFile->WriteVec3( contactMotorDir ); + saveFile->WriteFloat( contactMotorVelocity ); + saveFile->WriteFloat( contactMotorForce ); + + saveFile->WriteFloat( mass ); + saveFile->WriteFloat( invMass ); + saveFile->WriteVec3( centerOfMass ); + saveFile->WriteMat3( inertiaTensor ); + saveFile->WriteMat3( inverseInertiaTensor ); + + //saveFile->WriteVec3( state[0].worldOrigin ); // cnicholson: Added unsaved var state[0] + //saveFile->WriteMat3( state[0].worldAxis ); + //saveFile->WriteVec6( state[0].spatialVelocity ); + //saveFile->WriteVec6( state[0].externalForce ); + // + //saveFile->WriteVec3( state[1].worldOrigin ); // cnicholson: Added unsaved var state[1] + //saveFile->WriteMat3( state[1].worldAxis ); + //saveFile->WriteVec6( state[1].spatialVelocity ); + //saveFile->WriteVec6( state[1].externalForce ); + + saveFile->WriteVec3( current->worldOrigin ); + saveFile->WriteMat3( current->worldAxis ); + saveFile->WriteVec6( current->spatialVelocity ); + saveFile->WriteVec6( current->externalForce ); + + //saveFile->WriteVec3( next->worldOrigin ); // cnicholson: Added unsaved var next-> + //saveFile->WriteMat3( next->worldAxis ); + //saveFile->WriteVec6( next->spatialVelocity ); + //saveFile->WriteVec6( next->externalForce ); + // + //saveFile->WriteVec3( saved.worldOrigin ); // cnicholson: Added unsaved var saved + //saveFile->WriteMat3( saved.worldAxis ); + //saveFile->WriteVec6( saved.spatialVelocity ); + //saveFile->WriteVec6( saved.externalForce ); + + saveFile->WriteVec3( atRestOrigin ); + saveFile->WriteMat3( atRestAxis ); + + // TOSAVE: idMatX inverseWorldSpatialInerti + // TOSAVE: idMatX I, invI; + // TOSAVE: idMatX J; + // TOSAVE: idVecX s; + // TOSAVE: idVecX totalForce; + // TOSAVE: idVecX auxForce; + // TOSAVE: idVecX acceleration; + // TOSAVE: float * response; + // TOSAVE: int * responseIndex; + saveFile->WriteInt( numResponses ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxAuxiliaryIndex ); // cnicholson: Added unsaved var + saveFile->WriteInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unsaved var + + saveFile->Write( &fl, sizeof( fl ) ); // cnicholson: Added unsaved var +} + +/* +================ +idAFBody::Restore +================ +*/ +void idAFBody::Restore( idRestoreGame *saveFile ) { + + saveFile->ReadString( name ); // cniciholson: Added Unrestored Var + // TORESTORE: idList parent + // TORESTORE: idList children + // TORESTORE: idClipModel * clipModel + // TORESTORE: idAFConstraint * primaryConstraint + // TORESTORE: idList constraints + // TORESTORE: idAFTree * tree; + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadInt( clipMask ); + saveFile->ReadVec3( frictionDir ); + saveFile->ReadVec3( contactMotorDir ); + saveFile->ReadFloat( contactMotorVelocity ); + saveFile->ReadFloat( contactMotorForce ); + + saveFile->ReadFloat( mass ); + saveFile->ReadFloat( invMass ); + saveFile->ReadVec3( centerOfMass ); + saveFile->ReadMat3( inertiaTensor ); + saveFile->ReadMat3( inverseInertiaTensor ); + + //saveFile->ReadVec3( state[0].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[0].worldAxis ); + //saveFile->ReadVec6( state[0].spatialVelocity ); + //saveFile->ReadVec6( state[0].externalForce ); + + //saveFile->ReadVec3( state[1].worldOrigin ); // cnicholson: Added unrestored var state[0] + //saveFile->ReadMat3( state[1].worldAxis ); + //saveFile->ReadVec6( state[1].spatialVelocity ); + //saveFile->ReadVec6( state[1].externalForce ); + + saveFile->ReadVec3( current->worldOrigin ); + saveFile->ReadMat3( current->worldAxis ); + saveFile->ReadVec6( current->spatialVelocity ); + saveFile->ReadVec6( current->externalForce ); + + //saveFile->ReadVec3( next->worldOrigin ); // cnicholson: Added unrestored var next-> + //saveFile->ReadMat3( next->worldAxis ); + //saveFile->ReadVec6( next->spatialVelocity ); + //saveFile->ReadVec6( next->externalForce ); + + //saveFile->ReadVec3( saved.worldOrigin ); // cnicholson: Added unrestored var saved + //saveFile->ReadMat3( saved.worldAxis ); + //saveFile->ReadVec6( saved.spatialVelocity ); + //saveFile->ReadVec6( saved.externalForce ); + + saveFile->ReadVec3( atRestOrigin ); + saveFile->ReadMat3( atRestAxis ); + + // TORESTORE: idMatX inverseWorldSpatialInerti + // TORESTORE: idMatX I, invI; + // TORESTORE: idMatX J; + // TORESTORE: idVecX s; + // TORESTORE: idVecX totalForce; + // TORESTORE: idVecX auxForce; + // TORESTORE: idVecX acceleration; + // TORESTORE: float * response; + // TORESTORE: int * responseIndex; + saveFile->ReadInt( numResponses ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxAuxiliaryIndex ); // cnicholson: Added unrestored var + saveFile->ReadInt( maxSubTreeAuxiliaryIndex ); // cnicholson: Added unrestored var + + saveFile->Read( &fl, sizeof( fl ) ); // cnicholson: Added unrestored var +} + + +//=============================================================== +// M +// idAFTree MrE +// E +//=============================================================== + +/* +================ +idAFTree::Factor + + factor matrix for the primary constraints in the tree +================ +*/ +void idAFTree::Factor( void ) const { + int i, j; + idAFBody *body; + idAFConstraint *child; + idMatX childI; + + childI.SetData( 6, 6, MATX_ALLOCA( 6 * 6 ) ); + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + if ( body->children.Num() ) { + +// RAVEN BEGIN +// jscott: fixed warning + child = NULL; +// RAVEN END + + for ( j = 0; j < body->children.Num(); j++ ) { + + child = body->children[j]->primaryConstraint; + + // child->I = - child->body1->J.Transpose() * child->body1->I * child->body1->J; + childI.SetSize( child->J1.GetNumRows(), child->J1.GetNumRows() ); + child->body1->J.TransposeMultiply( child->body1->I ).Multiply( childI, child->body1->J ); + childI.Negate(); + + child->invI = childI; + if ( !child->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for constraint '%s'", + child->invI.GetNumRows(), child->invI.GetNumColumns(), child->GetName().c_str() ); + } + child->J = child->invI * child->J; + + body->I -= child->J.TransposeMultiply( childI ) * child->J; + } + + body->invI = body->I; + if ( !body->invI.InverseFastSelf() ) { + gameLocal.Warning( "idAFTree::Factor: couldn't invert %dx%d matrix for body %s", + child->invI.GetNumRows(), child->invI.GetNumColumns(), body->GetName().c_str() ); + } + if ( body->primaryConstraint ) { + body->J = body->invI * body->J; + } + } + else if ( body->primaryConstraint ) { + body->J = body->inverseWorldSpatialInertia * body->J; + } + } +} + +/* +================ +idAFTree::Solve + + solve for primary constraints in the tree +================ +*/ +void idAFTree::Solve( int auxiliaryIndex ) const { + int i, j; + idAFBody *body, *child; + idAFConstraint *primaryConstraint; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + primaryConstraint = child->primaryConstraint; + + if ( !child->fl.isZero ) { + child->J.TransposeMultiplySub( primaryConstraint->s, child->s ); + primaryConstraint->fl.isZero = false; + } + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->J.TransposeMultiplySub( body->s, primaryConstraint->s ); + body->fl.isZero = false; + } + } + } + + bool useSymmetry = af_useSymmetry.GetBool(); + + // from the root down towards the leaves + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + + if ( primaryConstraint ) { + + if ( useSymmetry && body->parent->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( !primaryConstraint->fl.isZero ) { + primaryConstraint->s = primaryConstraint->invI * primaryConstraint->s; + } + primaryConstraint->J.MultiplySub( primaryConstraint->s, primaryConstraint->body2->s ); + + primaryConstraint->lm = primaryConstraint->s; + + if ( useSymmetry && body->maxSubTreeAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + if ( body->children.Num() ) { + if ( !body->fl.isZero ) { + body->s = body->invI * body->s; + } + body->J.MultiplySub( body->s, primaryConstraint->s ); + } + } else if ( body->children.Num() ) { + body->s = body->invI * body->s; + } + } +} + +/* +================ +idAFTree::Response + + calculate body forces in the tree in response to a constraint force +================ +*/ +void idAFTree::Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const { + int i, j; + idAFBody *body; + idAFConstraint *child, *primaryConstraint; + idVecX v; + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + body = constraint->body1; + if ( body->tree == this ) { + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + else { + body = constraint->body2; + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } + return; + } + + v.SetData( 6, VECX_ALLOCA( 6 ) ); + + // initialize right hand side to zero + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->s.Zero(); + primaryConstraint->fl.isZero = true; + } + body->s.Zero(); + body->fl.isZero = true; + body->GetResponseForce( body->numResponses ).Zero(); + } + + // set right hand side for first constrained body + body = constraint->body1; + if ( body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J1[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.Multiply( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.Multiply( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J1.SubVec6( row ); + } + + // set right hand side for second constrained body + body = constraint->body2; + if ( body && body->tree == this ) { + body->InverseWorldSpatialInertiaMultiply( v, constraint->J2[row] ); + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.MultiplyAdd( primaryConstraint->s, v ); + primaryConstraint->fl.isZero = false; + } + for ( i = 0; i < body->children.Num(); i++ ) { + child = body->children[i]->primaryConstraint; + child->J2.MultiplyAdd( child->s, v ); + child->fl.isZero = false; + } + body->GetResponseForce( body->numResponses ) = constraint->J2.SubVec6( row ); + } + + + // solve for primary constraints + Solve( auxiliaryIndex ); + + bool useSymmetry = af_useSymmetry.GetBool(); + + // store body forces in response to the constraint force + idVecX force; + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + if ( useSymmetry && body->maxAuxiliaryIndex < auxiliaryIndex ) { + continue; + } + + force.SetData( 6, body->response + body->numResponses * 8 ); + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( force, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( force, child->lm ); + } + + body->responseIndex[body->numResponses++] = auxiliaryIndex; + } +} + +/* +================ +idAFTree::CalculateForces + + calculate forces on the bodies in the tree +================ +*/ +void idAFTree::CalculateForces( float timeStep ) const { + int i, j; + float invStep; + idAFBody *body; + idAFConstraint *child, *c, *primaryConstraint; + + // forces on bodies + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->totalForce.SubVec6(0) = body->current->externalForce + body->auxForce.SubVec6(0); + } + + // if a single body don't waste time because there aren't any primary constraints + if ( sortedBodies.Num() == 1 ) { + return; + } + + invStep = 1.0f / timeStep; + + // initialize right hand side + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + // b = ( J * acc + c ) + c = primaryConstraint; + c->s = c->J1 * c->body1->acceleration + c->J2 * c->body2->acceleration + invStep * ( c->c1 + c->c2 ); + c->fl.isZero = false; + } + body->s.Zero(); + body->fl.isZero = true; + } + + // solve for primary constraints + Solve(); + + // calculate forces on bodies after applying primary constraints + for ( i = 0; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + + // add forces of all primary constraints acting on this body + primaryConstraint = body->primaryConstraint; + if ( primaryConstraint ) { + primaryConstraint->J1.TransposeMultiplyAdd( body->totalForce, primaryConstraint->lm ); + } + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]->primaryConstraint; + child->J2.TransposeMultiplyAdd( body->totalForce, child->lm ); + } + } +} + +/* +================ +idAFTree::SetMaxSubTreeAuxiliaryIndex +================ +*/ +void idAFTree::SetMaxSubTreeAuxiliaryIndex( void ) { + int i, j; + idAFBody *body, *child; + + // from the leaves up towards the root + for ( i = sortedBodies.Num() - 1; i >= 0; i-- ) { + body = sortedBodies[i]; + + body->maxSubTreeAuxiliaryIndex = body->maxAuxiliaryIndex; + for ( j = 0; j < body->children.Num(); j++ ) { + child = body->children[j]; + if ( child->maxSubTreeAuxiliaryIndex > body->maxSubTreeAuxiliaryIndex ) { + body->maxSubTreeAuxiliaryIndex = child->maxSubTreeAuxiliaryIndex; + } + } + } +} + +/* +================ +idAFTree::SortBodies_r +================ +*/ +void idAFTree::SortBodies_r( idList&sortedList, idAFBody *body ) { + int i; + + for ( i = 0; i < body->children.Num(); i++ ) { + sortedList.Append( body->children[i] ); + } + for ( i = 0; i < body->children.Num(); i++ ) { + SortBodies_r( sortedList, body->children[i] ); + } +} + +/* +================ +idAFTree::SortBodies + + sort body list to make sure parents come first +================ +*/ +void idAFTree::SortBodies( void ) { + int i; + idAFBody *body; + + // find the root + for ( i = 0; i < sortedBodies.Num(); i++ ) { + if ( !sortedBodies[i]->parent ) { + break; + } + } + + if ( i >= sortedBodies.Num() ) { + gameLocal.Error( "Articulated figure tree has no root." ); + } + + body = sortedBodies[i]; + sortedBodies.Clear(); + sortedBodies.Append( body ); + SortBodies_r( sortedBodies, body ); +} + +/* +================ +idAFTree::DebugDraw +================ +*/ +void idAFTree::DebugDraw( const idVec4 &color ) const { + int i; + idAFBody *body; + + for ( i = 1; i < sortedBodies.Num(); i++ ) { + body = sortedBodies[i]; + gameRenderWorld->DebugArrow( color, body->parent->current->worldOrigin, body->current->worldOrigin, 1 ); + } +} + + +//=============================================================== +// M +// idPhysics_AF MrE +// E +//=============================================================== + +/* +================ +idPhysics_AF::EvaluateConstraints +================ +*/ +void idPhysics_AF::EvaluateConstraints( float timeStep ) { + int i; + float invTimeStep; + idAFBody *body; + idAFConstraint *c; + + invTimeStep = 1.0f / timeStep; + + // setup the constraint equations for the current position and orientation of the bodies + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + c = primaryConstraints[i]; + c->Evaluate( invTimeStep ); + c->J = c->J2; + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->Evaluate( invTimeStep ); + } + + // add contact constraints to the list with frame constraints + for ( i = 0; i < contactConstraints.Num(); i++ ) { + AddFrameConstraint( contactConstraints[i] ); + } + + // setup body primary constraint matrix + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->primaryConstraint ) { + body->J = body->primaryConstraint->J1.Transpose(); + } + } +} + +/* +================ +idPhysics_AF::EvaluateBodies +================ +*/ +void idPhysics_AF::EvaluateBodies( float timeStep ) { + int i; + idAFBody *body; + idMat3 axis; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // we transpose the axis before using it because idMat3 is column-major + axis = body->current->worldAxis.Transpose(); + + // if the center of mass is at the body point of reference + if ( body->centerOfMass.Compare( vec3_origin, CENTER_OF_MASS_EPSILON ) ) { + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia.Set( body->invMass * mat3_identity, mat3_zero, + mat3_zero, axis * body->inverseInertiaTensor * axis.Transpose() ); + + body->fl.spatialInertiaSparse = true; + } + else { + idMat3 massMoment = body->mass * SkewSymmetric( body->centerOfMass ); + + // spatial inertia in world space + body->I.Set( body->mass * mat3_identity, massMoment, + massMoment.Transpose(), axis * body->inertiaTensor * axis.Transpose() ); + + // inverse spatial inertia in world space + body->inverseWorldSpatialInertia = body->I.InverseFast(); + + body->fl.spatialInertiaSparse = false; + } + + // initialize auxiliary constraint force to zero + body->auxForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddFrameConstraints +================ +*/ +void idPhysics_AF::AddFrameConstraints( void ) { + int i; + + // add frame constraints to auxiliary constraints + for ( i = 0; i < frameConstraints.Num(); i++ ) { + auxiliaryConstraints.Append( frameConstraints[i] ); + } +} + +/* +================ +idPhysics_AF::RemoveFrameConstraints +================ +*/ +void idPhysics_AF::RemoveFrameConstraints( void ) { + // remove all the frame constraints from the auxiliary constraints + auxiliaryConstraints.SetNum( auxiliaryConstraints.Num() - frameConstraints.Num(), false ); + frameConstraints.SetNum( 0, false ); +} + +/* +================ +idPhysics_AF::ApplyFriction +================ +*/ +void idPhysics_AF::ApplyFriction( float timeStep, float endTimeMSec ) { + int i; + float invTimeStep; + + if ( af_skipFriction.GetBool() ) { + return; + } + + if ( jointFrictionDentStart < MS2SEC( endTimeMSec ) && jointFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( jointFrictionDentEnd - jointFrictionDentStart ) * 0.5f; + if ( jointFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + jointFrictionDentScale = 1.0f - ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart ) / halfTime; + } else { + jointFrictionDentScale = jointFrictionDent + ( 1.0f - jointFrictionDent ) * ( MS2SEC( endTimeMSec ) - jointFrictionDentStart - halfTime ) / halfTime; + } + } else { + jointFrictionDentScale = 0.0f; + } + + if ( contactFrictionDentStart < MS2SEC( endTimeMSec ) && contactFrictionDentEnd > MS2SEC( endTimeMSec ) ) { + float halfTime = ( contactFrictionDentEnd - contactFrictionDentStart ) * 0.5f; + if ( contactFrictionDentStart + halfTime > MS2SEC( endTimeMSec ) ) { + contactFrictionDentScale = 1.0f - ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart ) / halfTime; + } else { + contactFrictionDentScale = contactFrictionDent + ( 1.0f - contactFrictionDent ) * ( MS2SEC( endTimeMSec ) - contactFrictionDentStart - halfTime ) / halfTime; + } + } else { + contactFrictionDentScale = 0.0f; + } + + invTimeStep = 1.0f / timeStep; + + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + primaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->ApplyFriction( invTimeStep ); + } + for ( i = 0; i < frameConstraints.Num(); i++ ) { + frameConstraints[i]->ApplyFriction( invTimeStep ); + } +} + +/* +================ +idPhysics_AF::PrimaryFactor +================ +*/ +void idPhysics_AF::PrimaryFactor( void ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->Factor(); + } +} + +/* +================ +idPhysics_AF::PrimaryForces +================ +*/ +void idPhysics_AF::PrimaryForces( float timeStep ) { + int i; + + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->CalculateForces( timeStep ); + } +} + +/* +================ +idPhysics_AF::AuxiliaryForces +================ +*/ +void idPhysics_AF::AuxiliaryForces( float timeStep ) { + int i, j, k, l, n, m, s, numAuxConstraints, *index, *boxIndex; + float *ptr, *j1, *j2, *dstPtr, *forcePtr; + float invStep, u; + idAFBody *body; + idAFConstraint *constraint; + idVecX tmp; + idMatX jmk; + idVecX rhs, w, lm, lo, hi; + + // get the number of one dimensional auxiliary constraints + for ( numAuxConstraints = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxConstraints += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + if ( numAuxConstraints == 0 ) { + return; + } + + // allocate memory to store the body response to auxiliary constraint forces + forcePtr = (float *) _alloca16( bodies.Num() * numAuxConstraints * 8 * sizeof( float ) ); + index = (int *) _alloca16( bodies.Num() * numAuxConstraints * sizeof( int ) ); + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = forcePtr; + body->responseIndex = index; + body->numResponses = 0; + body->maxAuxiliaryIndex = 0; + forcePtr += numAuxConstraints * 8; + index += numAuxConstraints; + } + + // set on each body the largest index of an auxiliary constraint constraining the body + if ( af_useSymmetry.GetBool() ) { + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + if ( k > constraint->body1->maxAuxiliaryIndex ) { + constraint->body1->maxAuxiliaryIndex = k; + } + if ( constraint->body2 && k > constraint->body2->maxAuxiliaryIndex ) { + constraint->body2->maxAuxiliaryIndex = k; + } + } + } + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SetMaxSubTreeAuxiliaryIndex(); + } + } + + // calculate forces of primary constraints in response to the auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + // calculate body forces in the tree in response to the constraint force + constraint->body1->tree->Response( constraint, j, k ); + // if there is a second body which is part of a different tree + if ( constraint->body2 && constraint->body2->tree != constraint->body1->tree ) { + // calculate body forces in the second tree in response to the constraint force + constraint->body2->tree->Response( constraint, j, k ); + } + } + } + + // NOTE: the rows are 16 byte padded + jmk.SetData( numAuxConstraints, ((numAuxConstraints+3)&~3), MATX_ALLOCA( numAuxConstraints * ((numAuxConstraints+3)&~3) ) ); + tmp.SetData( 6, VECX_ALLOCA( 6 ) ); + + // create constraint matrix for auxiliary constraints using a mass matrix adjusted for the primary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->body1->InverseWorldSpatialInertiaMultiply( tmp, constraint->J1[j] ); + j1 = tmp.ToFloatPtr(); + ptr = constraint->body1->response; + index = constraint->body1->responseIndex; + dstPtr = jmk[k]; + s = af_useSymmetry.GetBool() ? k + 1 : numAuxConstraints; + for ( l = n = 0, m = index[n]; n < constraint->body1->numResponses && m < s; n++, m = index[n] ) { + while( l < m ) { + dstPtr[l++] = 0.0f; + } + dstPtr[l++] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + ptr += 8; + } + + while( l < s ) { + dstPtr[l++] = 0.0f; + } + + if ( constraint->body2 ) { + constraint->body2->InverseWorldSpatialInertiaMultiply( tmp, constraint->J2[j] ); + j2 = tmp.ToFloatPtr(); + ptr = constraint->body2->response; + index = constraint->body2->responseIndex; + for ( n = 0, m = index[n]; n < constraint->body2->numResponses && m < s; n++, m = index[n] ) { + dstPtr[m] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + ptr += 8; + } + } + } + } + + if ( af_useSymmetry.GetBool() ) { + n = jmk.GetNumColumns(); + for ( i = 0; i < numAuxConstraints; i++ ) { + ptr = jmk.ToFloatPtr() + ( i + 1 ) * n + i; + dstPtr = jmk.ToFloatPtr() + i * n + i + 1; + for ( j = i+1; j < numAuxConstraints; j++ ) { + *dstPtr++ = *ptr; + ptr += n; + } + } + } + + invStep = 1.0f / timeStep; + + // calculate body acceleration + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->acceleration.SubVec6(0) += body->current->spatialVelocity * invStep; + } + + rhs.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lo.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + hi.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + lm.SetData( numAuxConstraints, VECX_ALLOCA( numAuxConstraints ) ); + boxIndex = (int *) _alloca16( numAuxConstraints * sizeof( int ) ); + + // set first index for special box constrained variables + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + auxiliaryConstraints[i]->firstIndex = k; + k += auxiliaryConstraints[i]->J1.GetNumRows(); + } + + // initialize right hand side and low and high bounds for auxiliary constraints + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + n = k; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + + j1 = constraint->J1[j]; + ptr = constraint->body1->acceleration.ToFloatPtr(); + rhs[k] = j1[0] * ptr[0] + j1[1] * ptr[1] + j1[2] * ptr[2] + j1[3] * ptr[3] + j1[4] * ptr[4] + j1[5] * ptr[5]; + rhs[k] += constraint->c1[j] * invStep; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->acceleration.ToFloatPtr(); + rhs[k] += j2[0] * ptr[0] + j2[1] * ptr[1] + j2[2] * ptr[2] + j2[3] * ptr[3] + j2[4] * ptr[4] + j2[5] * ptr[5]; + rhs[k] += constraint->c2[j] * invStep; + } + + rhs[k] = -rhs[k]; + lo[k] = constraint->lo[j]; + hi[k] = constraint->hi[j]; + + if ( constraint->boxIndex[j] >= 0 ) { + if ( constraint->boxConstraint->fl.isPrimary ) { + gameLocal.Error( "cannot reference primary constraints for the box index" ); + } + boxIndex[k] = constraint->boxConstraint->firstIndex + constraint->boxIndex[j]; + } + else { + boxIndex[k] = -1; + } + jmk[k][k] += constraint->e[j] * invStep; + } + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Start(); + } +#endif + + // calculate lagrange multipliers for auxiliary constraints + if ( !lcp->Solve( jmk, lm, rhs, lo, hi, boxIndex ) ) { + return; // bad monkey! + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_lcp.Stop(); + } +#endif + + // calculate auxiliary constraint forces + for ( k = 0, i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + + for ( j = 0; j < constraint->J1.GetNumRows(); j++, k++ ) { + constraint->lm[j] = u = lm[k]; + + j1 = constraint->J1[j]; + ptr = constraint->body1->auxForce.ToFloatPtr(); + ptr[0] += j1[0] * u; ptr[1] += j1[1] * u; ptr[2] += j1[2] * u; + ptr[3] += j1[3] * u; ptr[4] += j1[4] * u; ptr[5] += j1[5] * u; + + if ( constraint->body2 ) { + j2 = constraint->J2[j]; + ptr = constraint->body2->auxForce.ToFloatPtr(); + ptr[0] += j2[0] * u; ptr[1] += j2[1] * u; ptr[2] += j2[2] * u; + ptr[3] += j2[3] * u; ptr[4] += j2[4] * u; ptr[5] += j2[5] * u; + } + } + } + + // recalculate primary constraint forces in response to auxiliary constraint forces + PrimaryForces( timeStep ); + + // clear pointers pointing to stack space so tools don't get confused + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->response = NULL; + body->responseIndex = NULL; + } +} + +/* +================ +idPhysics_AF::VerifyContactConstraints +================ +*/ +void idPhysics_AF::VerifyContactConstraints( void ) { +#if 0 + int i; + float impulseNumerator, impulseDenominator; + idVec3 r, velocity, normalVelocity, normal, impulse; + idAFBody *body; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + + r = contact.point - body->GetCenterOfMass(); + + // calculate velocity at contact point + velocity = body->GetLinearVelocity() + body->GetAngularVelocity().Cross( r ); + + // velocity along normal vector + normalVelocity = ( velocity * contact.normal ) * contact.normal; + + // if moving towards the surface at the contact point + if ( normalVelocity * contact.normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = body->GetInverseMass() + ( ( body->GetInverseWorldInertia() * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal * 1.0001f; + + // apply impulse + body->SetLinearVelocity( body->GetLinearVelocity() + impulse ); + body->SetAngularVelocity( body->GetAngularVelocity() + r.Cross( impulse ) ); + } + } +#else + int i; + idAFBody *body; + idVec3 normal; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + body = contactConstraints[i]->body1; + normal = contactConstraints[i]->GetContact().normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + body = contactConstraints[i]->body2; + if ( !body ) { + continue; + } + normal = -normal; + if ( normal * body->next->spatialVelocity.SubVec3(0) <= 0.0f ) { + body->next->spatialVelocity.SubVec3(0) -= 1.0001f * (normal * body->next->spatialVelocity.SubVec3(0)) * normal; + } + } +#endif +} + +/* +================ +idPhysics_AF::Evolve +================ +*/ +void idPhysics_AF::Evolve( float timeStep ) { + int i; + float angle; + idVec3 vec; + idAFBody *body; + idVec6 force; + idRotation rotation; + float vSqr, maxLinearVelocity, maxAngularVelocity; + + maxLinearVelocity = af_maxLinearVelocity.GetFloat() / timeStep; + maxAngularVelocity = af_maxAngularVelocity.GetFloat() / timeStep; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // calculate the spatial velocity for the next physics state + body->InverseWorldSpatialInertiaMultiply( body->acceleration, body->totalForce.ToFloatPtr() ); + body->next->spatialVelocity = body->current->spatialVelocity + timeStep * body->acceleration.SubVec6(0); + + if ( maxLinearVelocity > 0.0f ) { + // cap the linear velocity + vSqr = body->next->spatialVelocity.SubVec3(0).LengthSqr(); + if ( vSqr > Square( maxLinearVelocity ) ) { + body->next->spatialVelocity.SubVec3(0) *= idMath::InvSqrt( vSqr ) * maxLinearVelocity; + } + } + + if ( maxAngularVelocity > 0.0f ) { + // cap the angular velocity + vSqr = body->next->spatialVelocity.SubVec3(1).LengthSqr(); + if ( vSqr > Square( maxAngularVelocity ) ) { + body->next->spatialVelocity.SubVec3(1) *= idMath::InvSqrt( vSqr ) * maxAngularVelocity; + } + } + } + + // make absolutely sure all contact constraints are satisfied + VerifyContactConstraints(); + + // calculate the position of the bodies for the next physics state + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // translate world origin + body->next->worldOrigin = body->current->worldOrigin + timeStep * body->next->spatialVelocity.SubVec3( 0 ); + + // convert angular velocity to a rotation matrix + vec = body->next->spatialVelocity.SubVec3( 1 ); + angle = -timeStep * (float) RAD2DEG( vec.Normalize() ); + rotation = idRotation( vec3_origin, vec, angle ); + rotation.Normalize180(); + + // rotate world axis + body->next->worldAxis = body->current->worldAxis * rotation.ToMat3(); + body->next->worldAxis.OrthoNormalizeSelf(); + + // linear and angular friction + body->next->spatialVelocity.SubVec3(0) -= body->linearFriction * body->next->spatialVelocity.SubVec3(0); + body->next->spatialVelocity.SubVec3(1) -= body->angularFriction * body->next->spatialVelocity.SubVec3(1); + } +} + +/* +================ +idPhysics_AF::CollisionImpulse + + apply impulse to the colliding bodies + the current state of the body should be set to the moment of impact + this is silly as it doesn't take the AF structure into account +================ +*/ +bool idPhysics_AF::CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ) { + idVec3 r, velocity, impulse; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator; + impactInfo_t info; + idEntity *ent; + + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent == self || !ent ) { + return false; + } + + // get info from other entity involved + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + // collision point relative to the body center of mass + r = collision.c.point - (body->current->worldOrigin + body->centerOfMass * body->current->worldAxis); + // the velocity at the collision point + velocity = body->current->spatialVelocity.SubVec3(0) + body->current->spatialVelocity.SubVec3(1).Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + // never stick + if ( velocity * collision.c.normal > 0.0f ) { + velocity = collision.c.normal; + } + inverseWorldInertiaTensor = body->current->worldAxis.Transpose() * body->inverseInertiaTensor * body->current->worldAxis; + impulseNumerator = -( 1.0f + body->bouncyness ) * ( velocity * collision.c.normal ); + impulseDenominator = body->invMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + + // callback to self to let the entity know about the impact + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_AF::ApplyCollisions +================ +*/ +bool idPhysics_AF::ApplyCollisions( float timeStep ) { + int i; + + for ( i = 0; i < collisions.Num(); i++ ) { + if ( CollisionImpulse( timeStep, collisions[i].body, collisions[i].trace ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_AF::SetupCollisionForBody +================ +*/ +idEntity *idPhysics_AF::SetupCollisionForBody( idAFBody *body ) const { + int i; + idAFBody *b; + idEntity *passEntity; + + passEntity = NULL; + + if ( !selfCollision || !body->fl.selfCollision || af_skipSelfCollision.GetBool() ) { + + // disable all bodies + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } + + // don't collide with world collision model if attached to the world + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } + } + + } else { + + // enable all bodies that have self collision + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->fl.selfCollision ) { + bodies[i]->clipModel->Enable(); + } else { + bodies[i]->clipModel->Disable(); + } + } + + // don't let the body collide with itself + body->clipModel->Disable(); + + // disable any bodies attached with constraints + for ( i = 0; i < body->constraints.Num(); i++ ) { + if ( !body->constraints[i]->fl.noCollision ) { + continue; + } + // if this constraint attaches the body to the world + if ( body->constraints[i]->body2 == NULL ) { + // don't collide with the world collision model + passEntity = gameLocal.world; + } else { + if ( body->constraints[i]->body1 == body ) { + b = body->constraints[i]->body2; + } else if ( body->constraints[i]->body2 == body ) { + b = body->constraints[i]->body1; + } else { + continue; + } + // don't collide with this body + b->clipModel->Disable(); + } + } + } + + return passEntity; +} + +/* +================ +idPhysics_AF::CheckForCollisions + + check for collisions between the current and next state + if there is a collision the next state is set to the state at the moment of impact + assumes all bodies are linked for collision detection and relinks all bodies after moving them +================ +*/ +void idPhysics_AF::CheckForCollisions( float timeStep ) { +// #define TEST_COLLISION_DETECTION + int i, index; + idAFBody *body; + idMat3 axis; + idRotation rotation; + trace_t collision; + idEntity *passEntity; + + // clear list with collisions + collisions.SetNum( 0, false ); + + if ( !enableCollision ) { + return; + } + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask != 0 && !fastEval ) { +// RAVEN END + + passEntity = SetupCollisionForBody( body ); + +#ifdef TEST_COLLISION_DETECTION + bool startsolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->current->worldOrigin, body->clipModel, + body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( body->current->worldAxis, body->next->worldAxis, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( body->current->worldOrigin ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, body->current->worldOrigin, body->next->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + body->next->worldOrigin = collision.endpos; + body->next->worldAxis = collision.endAxis; + + // add collision to the list + index = collisions.Num(); + collisions.SetNum( index + 1, false ); + collisions[index].trace = collision; + collisions[index].body = body; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, body->next->worldOrigin, body->clipModel, + body->next->worldAxis, body->clipMask, passEntity ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + } + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->next->worldOrigin, body->next->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::EvaluateContacts +================ +*/ +bool idPhysics_AF::EvaluateContacts( void ) { + int i, j, k, numContacts, numBodyContacts; + idAFBody *body; + contactInfo_t contactInfo[10]; + idEntity *passEntity; + idVecX dir( 6, VECX_ALLOCA( 6 ) ); + + // evaluate bodies + EvaluateBodies( current.lastTimeStep ); + + // remove all existing contacts + ClearContacts(); + + contactBodies.SetNum( 0, false ); + + if ( !enableCollision ) { + return false; + } + + // find all the contacts + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + if ( body->clipMask == 0 || fastEval ) { +// RAVEN END + continue; + } + + passEntity = SetupCollisionForBody( body ); + + body->InverseWorldSpatialInertiaMultiply( dir, body->current->externalForce.ToFloatPtr() ); + dir.SubVec6(0) = body->current->spatialVelocity + current.lastTimeStep * dir.SubVec6(0); + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + numContacts = gameLocal.Contacts( self, contactInfo, 10, body->current->worldOrigin, dir.SubVec6(0), 2.0f, //CONTACT_EPSILON, + body->clipModel, body->current->worldAxis, body->clipMask, passEntity ); +// RAVEN END +#if 1 + // merge nearby contacts between the same bodies + // and assure there are at most three planar contacts between any pair of bodies + for ( j = 0; j < numContacts; j++ ) { + + numBodyContacts = 0; + for ( k = 0; k < contacts.Num(); k++ ) { + if ( contacts[k].entityNum == contactInfo[j].entityNum ) { + if ( ( contacts[k].id == i && contactInfo[j].id == contactBodies[k] ) || + ( contactBodies[k] == i && contacts[k].id == contactInfo[j].id ) ) { + + if ( ( contacts[k].point - contactInfo[j].point ).LengthSqr() < Square( 2.0f ) ) { + break; + } + if ( idMath::Fabs( contacts[k].normal * contactInfo[j].normal ) > 0.9f ) { + numBodyContacts++; + } + } + } + } + + if ( k >= contacts.Num() && numBodyContacts < 3 ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } + } + +#else + + for ( j = 0; j < numContacts; j++ ) { + contacts.Append( contactInfo[j] ); + contactBodies.Append( i ); + } +#endif + + } + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_AF::SetupContactConstraints +================ +*/ +void idPhysics_AF::SetupContactConstraints( void ) { + int i; + + // make sure enough contact constraints are allocated + contactConstraints.AssureSizeAlloc( contacts.Num(), idListNewElement ); + contactConstraints.SetNum( contacts.Num(), false ); + + // setup contact constraints + for ( i = 0; i < contacts.Num(); i++ ) { + // add contact constraint + contactConstraints[i]->physics = this; + if ( contacts[i].entityNum == self->entityNumber ) { + contactConstraints[i]->Setup( bodies[contactBodies[i]], bodies[ contacts[i].id ], contacts[i] ); + } + else { + contactConstraints[i]->Setup( bodies[contactBodies[i]], NULL, contacts[i] ); + } + } +} + +/* +================ +idPhysics_AF::ApplyContactForces +================ +*/ +void idPhysics_AF::ApplyContactForces( void ) { +#if 0 + int i; + idEntity *ent; + idVec3 force; + + for ( i = 0; i < contactConstraints.Num(); i++ ) { + if ( contactConstraints[i]->body2 != NULL ) { + continue; + } + const contactInfo_t &contact = contactConstraints[i]->GetContact(); + ent = gameLocal.entities[contact.entityNum]; + if ( !ent ) { + continue; + } + force.Zero(); + ent->AddForce( self, contact.id, contact.point, force ); + } +#endif +} + +/* +================ +idPhysics_AF::ClearExternalForce +================ +*/ +void idPhysics_AF::ClearExternalForce( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + // clear external force + body->current->externalForce.Zero(); + body->next->externalForce.Zero(); + } +} + +/* +================ +idPhysics_AF::AddGravity +================ +*/ +void idPhysics_AF::AddGravity( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + // add gravitational force + body->current->externalForce.SubVec3( 0 ) += body->mass * gravityVector; + } +} + +/* +================ +idPhysics_AF::SwapStates +================ +*/ +void idPhysics_AF::SwapStates( void ) { + int i; + idAFBody *body; + AFBodyPState_t *swap; + + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + + // swap the current and next state for next simulation step + swap = body->current; + body->current = body->next; + body->next = swap; + } +} + +/* +================ +idPhysics_AF::UpdateClipModels +================ +*/ +void idPhysics_AF::UpdateClipModels( void ) { + int i; + idAFBody *body; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + body->clipModel->Link( self, body->clipModel->GetId(), body->current->worldOrigin, body->current->worldAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::SetSuspendSpeed +================ +*/ +void idPhysics_AF::SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ) { + this->suspendVelocity = velocity; + this->suspendAcceleration = acceleration; +} + +/* +================ +idPhysics_AF::SetSuspendTime +================ +*/ +void idPhysics_AF::SetSuspendTime( const float minTime, const float maxTime ) { + this->minMoveTime = minTime; + this->maxMoveTime = maxTime; +} + +/* +================ +idPhysics_AF::SetSuspendTolerance +================ +*/ +void idPhysics_AF::SetSuspendTolerance( const float noMoveTime, const float noMoveTranslation, const float noMoveRotation ) { + this->noMoveTime = noMoveTime; + this->noMoveTranslation = noMoveTranslation; + this->noMoveRotation = noMoveRotation; +} + +/* +================ +idPhysics_AF::SetTimeScaleRamp +================ +*/ +void idPhysics_AF::SetTimeScaleRamp( const float start, const float end ) { + timeScaleRampStart = start; + timeScaleRampEnd = end; +} + +/* +================ +idPhysics_AF::SetJointFrictionDent +================ +*/ +void idPhysics_AF::SetJointFrictionDent( const float dent, const float start, const float end ) { + jointFrictionDent = dent; + jointFrictionDentStart = start; + jointFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetJointFrictionScale +================ +*/ +float idPhysics_AF::GetJointFrictionScale( void ) const { + if ( jointFrictionDentScale > 0.0f ) { + return jointFrictionDentScale; + } else if ( jointFrictionScale > 0.0f ) { + return jointFrictionScale; + } else if ( af_jointFrictionScale.GetFloat() > 0.0f ) { + return af_jointFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::SetContactFrictionDent +================ +*/ +void idPhysics_AF::SetContactFrictionDent( const float dent, const float start, const float end ) { + contactFrictionDent = dent; + contactFrictionDentStart = start; + contactFrictionDentEnd = end; +} + +/* +================ +idPhysics_AF::GetContactFrictionScale +================ +*/ +float idPhysics_AF::GetContactFrictionScale( void ) const { + if ( contactFrictionDentScale > 0.0f ) { + return contactFrictionDentScale; + } else if ( contactFrictionScale > 0.0f ) { + return contactFrictionScale; + } else if ( af_contactFrictionScale.GetFloat() > 0.0f ) { + return af_contactFrictionScale.GetFloat(); + } + return 1.0f; +} + +/* +================ +idPhysics_AF::TestIfAtRest +================ +*/ +bool idPhysics_AF::TestIfAtRest( float timeStep ) { + int i; + float translationSqr, maxTranslationSqr, rotation, maxRotation; + idAFBody *body; + + if ( current.atRest >= 0 ) { + return true; + } + + current.activateTime += timeStep; + + // if the simulation should never be suspended before a certaint amount of time passed + if ( minMoveTime > 0.0f && current.activateTime < minMoveTime ) { + return false; + } + + // if the simulation should always be suspended after a certain amount time passed + if ( maxMoveTime > 0.0f && current.activateTime > maxMoveTime ) { + return true; + } + + // test if all bodies hardly moved over a period of time + if ( current.noMoveTime == 0.0f ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + body->atRestOrigin = body->current->worldOrigin; + body->atRestAxis = body->current->worldAxis; + } + current.noMoveTime += timeStep; + } + else if ( current.noMoveTime > noMoveTime ) { + current.noMoveTime = 0.0f; + maxTranslationSqr = 0.0f; + maxRotation = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + translationSqr = ( body->current->worldOrigin - body->atRestOrigin ).LengthSqr(); + if ( translationSqr > maxTranslationSqr ) { + maxTranslationSqr = translationSqr; + } + rotation = ( body->atRestAxis.Transpose() * body->current->worldAxis ).ToRotation().GetAngle(); + if ( rotation > maxRotation ) { + maxRotation = rotation; + } + } + + if ( maxTranslationSqr < Square( noMoveTranslation ) && maxRotation < noMoveRotation ) { + // hardly moved over a period of time so the articulated figure may come to rest + return true; + } + } else { + current.noMoveTime += timeStep; + } + + // test if the velocity or acceleration of any body is still too large to come to rest + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->current->spatialVelocity.SubVec3(0).LengthSqr() > Square( suspendVelocity[0] ) ) { + return false; + } + if ( body->current->spatialVelocity.SubVec3(1).LengthSqr() > Square( suspendVelocity[1] ) ) { + return false; + } + if ( body->acceleration.SubVec3(0).LengthSqr() > Square( suspendAcceleration[0] ) ) { + return false; + } + if ( body->acceleration.SubVec3(1).LengthSqr() > Square( suspendAcceleration[1] ) ) { + return false; + } + } + + // all bodies have a velocity and acceleration small enough to come to rest + return true; +} + +/* +================ +idPhysics_AF::Rest +================ +*/ +void idPhysics_AF::Rest( void ) { + int i; + + current.atRest = gameLocal.time; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity.Zero(); + bodies[i]->current->externalForce.Zero(); + } + + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::Activate +================ +*/ +void idPhysics_AF::Activate( void ) { + // if the articulated figure was at rest + if ( current.atRest >= 0 ) { + // normally gravity is added at the end of a simulation frame + // if the figure was at rest add gravity here so it is applied this simulation frame + AddGravity(); + // reset the active time for the max move time + current.activateTime = 0.0f; + } + current.atRest = -1; + current.noMoveTime = 0.0f; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_AF::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_AF::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_AF::EnableImpact +================ +*/ +void idPhysics_AF::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_AF::DisableImpact +================ +*/ +void idPhysics_AF::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_AF::AddPushVelocity +================ +*/ +void idPhysics_AF::AddPushVelocity( const idVec6 &pushVelocity ) { + int i; + + if ( pushVelocity != vec6_origin ) { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->current->spatialVelocity += pushVelocity; + } + } +} + +/* +================ +idPhysics_AF::SetClipModel +================ +*/ +void idPhysics_AF::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_AF::GetClipModel +================ +*/ +idClipModel *idPhysics_AF::GetClipModel( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel(); + } + return NULL; +} + +/* +================ +idPhysics_AF::GetNumClipModels +================ +*/ +int idPhysics_AF::GetNumClipModels( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::SetMass +================ +*/ +void idPhysics_AF::SetMass( float mass, int id ) { + if ( id >= 0 && id < bodies.Num() ) { + } + else { + forceTotalMass = mass; + } + SetChanged(); +} + +/* +================ +idPhysics_AF::GetMass +================ +*/ +float idPhysics_AF::GetMass( int id ) const { + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->mass; + } + return totalMass; +} + +/* +================ +idPhysics_AF::SetContents +================ +*/ +void idPhysics_AF::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < bodies.Num() ) { + bodies[id]->GetClipModel()->SetContents( contents ); + } + else { + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->GetClipModel()->SetContents( contents ); + } + } +} + +/* +================ +idPhysics_AF::GetContents +================ +*/ +int idPhysics_AF::GetContents( int id ) const { + int i, contents; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetContents(); + } + else { + contents = 0; + for ( i = 0; i < bodies.Num(); i++ ) { + contents |= bodies[i]->GetClipModel()->GetContents(); + } + return contents; + } +} + +/* +================ +idPhysics_AF::GetBounds +================ +*/ +const idBounds &idPhysics_AF::GetBounds( int id ) const { + int i; + static idBounds relBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetBounds(); + } + else if ( !bodies.Num() ) { + relBounds.Zero(); + return relBounds; + } + else { + relBounds = bodies[0]->GetClipModel()->GetBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + idBounds bounds; + idVec3 origin = ( bodies[i]->GetWorldOrigin() - bodies[0]->GetWorldOrigin() ) * bodies[0]->GetWorldAxis().Transpose(); + idMat3 axis = bodies[i]->GetWorldAxis() * bodies[0]->GetWorldAxis().Transpose(); + bounds.FromTransformedBounds( bodies[i]->GetClipModel()->GetBounds(), origin, axis ); + relBounds += bounds; + } + return relBounds; + } +} + +/* +================ +idPhysics_AF::GetAbsBounds +================ +*/ +const idBounds &idPhysics_AF::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < bodies.Num() ) { + return bodies[id]->GetClipModel()->GetAbsBounds(); + } + else if ( !bodies.Num() ) { + absBounds.Zero(); + return absBounds; + } + else { + absBounds = bodies[0]->GetClipModel()->GetAbsBounds(); + for ( i = 1; i < bodies.Num(); i++ ) { + absBounds += bodies[i]->GetClipModel()->GetAbsBounds(); + } + return absBounds; + } +} + +/* +================ +idPhysics_AF::Evaluate +================ +*/ +bool idPhysics_AF::Evaluate( int timeStepMSec, int endTimeMSec ) { + float timeStep; + + if ( timeScaleRampStart < MS2SEC( endTimeMSec ) && timeScaleRampEnd > MS2SEC( endTimeMSec ) ) { + timeStep = MS2SEC( timeStepMSec ) * ( MS2SEC( endTimeMSec ) - timeScaleRampStart ) / ( timeScaleRampEnd - timeScaleRampStart ); + } else if ( af_timeScale.GetFloat() != 1.0f ) { + timeStep = MS2SEC( timeStepMSec ) * af_timeScale.GetFloat(); + } else { + timeStep = MS2SEC( timeStepMSec ) * timeScale; + } + current.lastTimeStep = timeStep; + + + // if the articulated figure changed + if ( changedAF || ( linearTime != af_useLinearTime.GetBool() ) ) { + BuildTrees(); + changedAF = false; + linearTime = af_useLinearTime.GetBool(); + } + + // get the new master position + if ( masterBody ) { + idVec3 masterOrigin; + idMat3 masterAxis; + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( current.atRest >= 0 && ( masterBody->current->worldOrigin != masterOrigin || masterBody->current->worldAxis != masterAxis ) ) { + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + + // if the simulation is suspended because the figure is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // move the af velocity into the frame of a pusher + AddPushVelocity( -current.pushVelocity ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Start(); + timer_collision.Start(); + } +#endif + + // evaluate contacts + EvaluateContacts(); + + // setup contact constraints + SetupContactConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // evaluate constraint equations + EvaluateConstraints( timeStep ); + + // apply friction + ApplyFriction( timeStep, endTimeMSec ); + + // add frame constraints + AddFrameConstraints(); + +#ifdef AF_TIMINGS + int numPrimary = 0, numAuxiliary = 0; + if ( af_showTimings.GetInteger() != 0 ) { + int i; + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + numPrimary += primaryConstraints[i]->J1.GetNumRows(); + } + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + numAuxiliary += auxiliaryConstraints[i]->J1.GetNumRows(); + } + timer_pc.Start(); + } +#endif + + // factor matrices for primary constraints + PrimaryFactor(); + + // calculate forces on bodies after applying primary constraints + PrimaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_pc.Stop(); + timer_ac.Start(); + } +#endif + + // calculate and apply auxiliary constraint forces + AuxiliaryForces( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_ac.Stop(); + } +#endif + + // evolve current state to next state + Evolve( timeStep ); + + // debug graphics + DebugDraw(); + + // clear external forces on all bodies + ClearExternalForce(); + + // apply contact force to other entities + ApplyContactForces(); + + // remove all frame constraints + RemoveFrameConstraints(); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions between current and next state + CheckForCollisions( timeStep ); + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_collision.Stop(); + } +#endif + + // swap the current and next state + SwapStates(); + + // make sure all clip models are disabled in case they were enabled for self collision + if ( selfCollision && !af_skipSelfCollision.GetBool() ) { + DisableClip(); + } + + // apply collision impulses + if ( ApplyCollisions( timeStep ) ) { + current.atRest = gameLocal.time; + comeToRest = true; + } + + // test if the simulation can be suspended because the whole figure is at rest + if ( comeToRest && TestIfAtRest( timeStep ) ) { + Rest(); + } else { + ActivateContactEntities(); + } + + // add gravitational force + AddGravity(); + + // move the af velocity back into the world frame + AddPushVelocity( current.pushVelocity ); + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { +// RAVEN BEGIN +// kfuller: warnings shouldn't crash the game + if ( bodies.Num() && bodies[0] && bodies[0]->current && self ) { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) ); + } else { + gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' -- no body", + self->name.c_str(), self->GetType()->classname ); + } +// RAVEN END + Rest(); + } + +#ifdef AF_TIMINGS + if ( af_showTimings.GetInteger() != 0 ) { + timer_total.Stop(); + + if ( af_showTimings.GetInteger() == 1 ) { + gameLocal.Printf( "%12s: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + else if ( af_showTimings.GetInteger() == 2 ) { + numArticulatedFigures++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "af %d: t %1.4f pc %2d, %1.4f ac %2d %1.4f lcp %1.4f cd %1.4f\n", + numArticulatedFigures, + timer_total.Milliseconds(), + numPrimary, timer_pc.Milliseconds(), + numAuxiliary, timer_ac.Milliseconds() - timer_lcp.Milliseconds(), + timer_lcp.Milliseconds(), timer_collision.Milliseconds() ); + } + } + + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numArticulatedFigures = 0; + timer_total.Clear(); + timer_pc.Clear(); + timer_ac.Clear(); + timer_collision.Clear(); + timer_lcp.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_AF::UpdateTime +================ +*/ +void idPhysics_AF::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_AF::GetTime +================ +*/ +int idPhysics_AF::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +DrawTraceModelSilhouette +================ +*/ +void DrawTraceModelSilhouette( const idVec3 &projectionOrigin, const idClipModel *clipModel ) { + int i, numSilEdges; + int silEdges[MAX_TRACEMODEL_EDGES]; + idVec3 v1, v2; + const idTraceModel *trm = clipModel->GetTraceModel(); + const idVec3 &origin = clipModel->GetOrigin(); + const idMat3 &axis = clipModel->GetAxis(); + + numSilEdges = trm->GetProjectionSilhouetteEdges( ( projectionOrigin - origin ) * axis.Transpose(), silEdges ); + for ( i = 0; i < numSilEdges; i++ ) { + v1 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITSET( silEdges[i] ) ] ]; + v2 = trm->verts[ trm->edges[ abs(silEdges[i]) ].v[ INTSIGNBITNOTSET( silEdges[i] ) ] ]; + gameRenderWorld->DebugArrow( colorRed, origin + v1 * axis, origin + v2 * axis, 1 ); + } +} + +/* +================ +idPhysics_AF::DebugDraw +================ +*/ +void idPhysics_AF::DebugDraw( void ) { + int i; + idAFBody *body, *highlightBody = NULL, *constrainedBody1 = NULL, *constrainedBody2 = NULL; + idAFConstraint *constraint; + idVec3 center; + idMat3 axis; + + if ( af_highlightConstraint.GetString()[0] ) { + constraint = GetConstraint( af_highlightConstraint.GetString() ); + if ( constraint ) { + constraint->GetCenter( center ); + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DebugCone( colorYellow, center, (axis[2] - axis[1]) * 4.0f, 0.0f, 1.0f, 0 ); + + if ( af_showConstrainedBodies.GetBool() ) { + cvarSystem->SetCVarString( "cm_drawColor", colorCyan.ToString( 0 ) ); + constrainedBody1 = constraint->body1; + if ( constrainedBody1 ) { + collisionModelManager->DrawModel( constrainedBody1->clipModel->GetCollisionModel(), constrainedBody1->clipModel->GetOrigin(), + constrainedBody1->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorBlue.ToString( 0 ) ); + constrainedBody2 = constraint->body2; + if ( constrainedBody2 ) { + collisionModelManager->DrawModel( constrainedBody2->clipModel->GetCollisionModel(), constrainedBody2->clipModel->GetOrigin(), + constrainedBody2->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + } + + if ( af_highlightBody.GetString()[0] ) { + highlightBody = GetBody( af_highlightBody.GetString() ); + if ( highlightBody ) { + cvarSystem->SetCVarString( "cm_drawColor", colorYellow.ToString( 0 ) ); + collisionModelManager->DrawModel( highlightBody->clipModel->GetCollisionModel(), highlightBody->clipModel->GetOrigin(), + highlightBody->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + cvarSystem->SetCVarString( "cm_drawColor", colorRed.ToString( 0 ) ); + } + } + + if ( af_showBodies.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + if ( body == constrainedBody1 || body == constrainedBody2 ) { + continue; + } + if ( body == highlightBody ) { + continue; + } + collisionModelManager->DrawModel( body->clipModel->GetCollisionModel(), body->clipModel->GetOrigin(), + body->clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + //DrawTraceModelSilhouette( gameLocal.GetLocalPlayer()->GetEyePosition(), body->clipModel ); + } + } + + if ( af_showBodyNames.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( body->GetName().c_str(), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showMass.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + gameRenderWorld->DrawText( va( "\n%1.2f", 1.0f / body->GetInverseMass() ), body->GetWorldOrigin(), 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showTotalMass.GetBool() ) { + axis = gameLocal.GetLocalPlayer()->viewAngles.ToMat3(); + gameRenderWorld->DrawText( va( "\n%1.2f", totalMass ), bodies[0]->GetWorldOrigin() + axis[2] * 8.0f, 0.15f, colorCyan, axis, 1 ); + } + + if ( af_showInertia.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + idMat3 &I = body->inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + body->GetWorldOrigin(), 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + + if ( af_showVelocity.GetBool() ) { + for ( i = 0; i < bodies.Num(); i++ ) { + DrawVelocity( bodies[i]->clipModel->GetId(), 0.1f, 4.0f ); + } + } + + if ( af_showConstraints.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->DebugDraw(); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->DebugDraw(); + } + } + } + + if ( af_showConstraintNames.GetBool() ) { + for ( i = 0; i < primaryConstraints.Num(); i++ ) { + constraint = primaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + if ( !af_showPrimaryOnly.GetBool() ) { + for ( i = 0; i < auxiliaryConstraints.Num(); i++ ) { + constraint = auxiliaryConstraints[i]; + constraint->GetCenter( center ); + gameRenderWorld->DrawText( constraint->GetName().c_str(), center, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + } + } + + if ( af_showTrees.GetBool() || ( af_showActive.GetBool() && current.atRest < 0 ) ) { + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->DebugDraw( idStr::ColorForIndex( i+3 ) ); + } + } +} + +/* +================ +idPhysics_AF::idPhysics_AF +================ +*/ +idPhysics_AF::idPhysics_AF( void ) { + trees.Clear(); + bodies.Clear(); + constraints.Clear(); + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + frameConstraints.Clear(); + contacts.Clear(); + collisions.Clear(); + changedAF = true; + masterBody = NULL; + + lcp = idLCP::AllocSymmetric(); + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + saved = current; + + linearFriction = 0.005f; + angularFriction = 0.005f; + contactFriction = 0.8f; + bouncyness = 0.4f; + totalMass = 0.0f; + forceTotalMass = -1.0f; + + suspendVelocity.Set( SUSPEND_LINEAR_VELOCITY, SUSPEND_ANGULAR_VELOCITY ); + suspendAcceleration.Set( SUSPEND_LINEAR_ACCELERATION, SUSPEND_LINEAR_ACCELERATION ); + noMoveTime = NO_MOVE_TIME; + noMoveTranslation = NO_MOVE_TRANSLATION_TOLERANCE; + noMoveRotation = NO_MOVE_ROTATION_TOLERANCE; + minMoveTime = MIN_MOVE_TIME; + maxMoveTime = MAX_MOVE_TIME; + impulseThreshold = IMPULSE_THRESHOLD; + + timeScale = 1.0f; + timeScaleRampStart = 0.0f; + timeScaleRampEnd = 0.0f; + + jointFrictionScale = 0.0f; + jointFrictionDent = 0.0f; + jointFrictionDentStart = 0.0f; + jointFrictionDentEnd = 0.0f; + jointFrictionDentScale = 0.0f; + + contactFrictionScale = 0.0f; + contactFrictionDent = 0.0f; + contactFrictionDentStart = 0.0f; + contactFrictionDentEnd = 0.0f; + contactFrictionDentScale = 0.0f; + + enableCollision = true; + selfCollision = true; + comeToRest = true; + linearTime = true; + noImpact = false; + worldConstraintsLocked = false; + forcePushable = false; + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + fastEval = false; +// RAVEN END + +#ifdef AF_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_AF::~idPhysics_AF +================ +*/ +idPhysics_AF::~idPhysics_AF( void ) { + int i; + + trees.DeleteContents( true ); + + for ( i = 0; i < bodies.Num(); i++ ) { + delete bodies[i]; + } + + for ( i = 0; i < constraints.Num(); i++ ) { + delete constraints[i]; + } + + contactConstraints.SetNum( contactConstraints.NumAllocated(), false ); + for ( i = 0; i < contactConstraints.NumAllocated(); i++ ) { + delete contactConstraints[i]; + } + + delete lcp; + + if ( masterBody ) { + delete masterBody; + } +} + +/* +================ +idPhysics_AF_SavePState +================ +*/ +void idPhysics_AF_SavePState( idSaveGame *saveFile, const AFPState_t &state ) { + saveFile->WriteInt( state.atRest ); + saveFile->WriteFloat( state.noMoveTime ); + saveFile->WriteFloat( state.activateTime ); + saveFile->WriteFloat( state.lastTimeStep ); + saveFile->WriteVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF_RestorePState +================ +*/ +void idPhysics_AF_RestorePState( idRestoreGame *saveFile, AFPState_t &state ) { + saveFile->ReadInt( state.atRest ); + saveFile->ReadFloat( state.noMoveTime ); + saveFile->ReadFloat( state.activateTime ); + saveFile->ReadFloat( state.lastTimeStep ); + saveFile->ReadVec6( state.pushVelocity ); +} + +/* +================ +idPhysics_AF::Save +================ +*/ +void idPhysics_AF::Save( idSaveGame *saveFile ) const { + int i; + + // the articulated figure structure is handled by the owner + + // TOSAVE: idList trees; + saveFile->WriteInt( bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Save( saveFile ); + } + if ( masterBody ) { + saveFile->WriteBool( true ); + masterBody->Save( saveFile ); + } else { + saveFile->WriteBool( false ); + } + + saveFile->WriteInt( constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Save( saveFile ); + } + + // TOSAVE: idListprimaryConstraints; + // TOSAVE: idListauxiliaryConstraints; + // TOSAVE: idListframeConstraints; + // TOSAVE: idListcontactConstraints; + // TOSAVE: idList contactBodies; + // TOSAVE: idList collisions; + + saveFile->WriteBool( changedAF ); + + saveFile->WriteFloat( linearFriction ); + saveFile->WriteFloat( angularFriction ); + saveFile->WriteFloat( contactFriction ); + saveFile->WriteFloat( bouncyness ); + saveFile->WriteFloat( totalMass ); + saveFile->WriteFloat( forceTotalMass ); + + saveFile->WriteVec2( suspendVelocity ); + saveFile->WriteVec2( suspendAcceleration ); + saveFile->WriteFloat( noMoveTime ); + saveFile->WriteFloat( noMoveTranslation ); + saveFile->WriteFloat( noMoveRotation ); + saveFile->WriteFloat( minMoveTime ); + saveFile->WriteFloat( maxMoveTime ); + saveFile->WriteFloat( impulseThreshold ); + + saveFile->WriteFloat( timeScale ); + saveFile->WriteFloat( timeScaleRampStart ); + saveFile->WriteFloat( timeScaleRampEnd ); + + saveFile->WriteFloat( jointFrictionScale ); + saveFile->WriteFloat( jointFrictionDent ); + saveFile->WriteFloat( jointFrictionDentStart ); + saveFile->WriteFloat( jointFrictionDentEnd ); + saveFile->WriteFloat( jointFrictionDentScale ); + + saveFile->WriteFloat( contactFrictionScale ); + saveFile->WriteFloat( contactFrictionDent ); + saveFile->WriteFloat( contactFrictionDentStart ); + saveFile->WriteFloat( contactFrictionDentEnd ); + saveFile->WriteFloat( contactFrictionDentScale ); + + saveFile->WriteBool( enableCollision ); + saveFile->WriteBool( selfCollision ); + saveFile->WriteBool( comeToRest ); + saveFile->WriteBool( linearTime ); + saveFile->WriteBool( noImpact ); + saveFile->WriteBool( worldConstraintsLocked ); + saveFile->WriteBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->WriteBool( fastEval ); +// RAVEN END + + idPhysics_AF_SavePState( saveFile, current ); + idPhysics_AF_SavePState( saveFile, saved ); + + // TOSAVE: idAFBody * masterBody; + // TOSAVE: idLCP * lcp; +} + +/* +================ +idPhysics_AF::Restore +================ +*/ +void idPhysics_AF::Restore( idRestoreGame *saveFile ) { + int i, num; + bool hasMaster; + + // the articulated figure structure should have already been restored + + saveFile->ReadInt( num ); + assert( num == bodies.Num() ); + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->Restore( saveFile ); + } + saveFile->ReadBool( hasMaster ); + if ( hasMaster ) { + masterBody = new idAFBody(); + masterBody->Restore( saveFile ); + } + + saveFile->ReadInt( num ); + assert( num == constraints.Num() ); + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Restore( saveFile ); + } + + saveFile->ReadBool( changedAF ); + + saveFile->ReadFloat( linearFriction ); + saveFile->ReadFloat( angularFriction ); + saveFile->ReadFloat( contactFriction ); + saveFile->ReadFloat( bouncyness ); + saveFile->ReadFloat( totalMass ); + saveFile->ReadFloat( forceTotalMass ); + + saveFile->ReadVec2( suspendVelocity ); + saveFile->ReadVec2( suspendAcceleration ); + saveFile->ReadFloat( noMoveTime ); + saveFile->ReadFloat( noMoveTranslation ); + saveFile->ReadFloat( noMoveRotation ); + saveFile->ReadFloat( minMoveTime ); + saveFile->ReadFloat( maxMoveTime ); + saveFile->ReadFloat( impulseThreshold ); + + saveFile->ReadFloat( timeScale ); + saveFile->ReadFloat( timeScaleRampStart ); + saveFile->ReadFloat( timeScaleRampEnd ); + + saveFile->ReadFloat( jointFrictionScale ); + saveFile->ReadFloat( jointFrictionDent ); + saveFile->ReadFloat( jointFrictionDentStart ); + saveFile->ReadFloat( jointFrictionDentEnd ); + saveFile->ReadFloat( jointFrictionDentScale ); + + saveFile->ReadFloat( contactFrictionScale ); + saveFile->ReadFloat( contactFrictionDent ); + saveFile->ReadFloat( contactFrictionDentStart ); + saveFile->ReadFloat( contactFrictionDentEnd ); + saveFile->ReadFloat( contactFrictionDentScale ); + + saveFile->ReadBool( enableCollision ); + saveFile->ReadBool( selfCollision ); + saveFile->ReadBool( comeToRest ); + saveFile->ReadBool( linearTime ); + saveFile->ReadBool( noImpact ); + saveFile->ReadBool( worldConstraintsLocked ); + saveFile->ReadBool( forcePushable ); + +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + saveFile->ReadBool( fastEval ); +// RAVEN END + + idPhysics_AF_RestorePState( saveFile, current ); + idPhysics_AF_RestorePState( saveFile, saved ); + + changedAF = true; + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::IsClosedLoop +================ +*/ +bool idPhysics_AF::IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const { + const idAFBody *b1, *b2; + + for ( b1 = body1; b1->parent; b1 = b1->parent ) { + } + for ( b2 = body2; b2->parent; b2 = b2->parent ) { + } + return ( b1 == b2 ); +} + +/* +================ +idPhysics_AF::BuildTrees +================ +*/ +void idPhysics_AF::BuildTrees( void ) { + int i; + float scale; + idAFBody *b; + idAFConstraint *c; + idAFTree *tree; + + primaryConstraints.Clear(); + auxiliaryConstraints.Clear(); + trees.DeleteContents( true ); + + totalMass = 0.0f; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->parent = NULL; + b->primaryConstraint = NULL; + b->constraints.SetNum( 0, false ); + b->children.Clear(); + b->tree = NULL; + totalMass += b->mass; + } + + if ( forceTotalMass > 0.0f ) { + scale = forceTotalMass / totalMass; + for ( i = 0; i < bodies.Num(); i++ ) { + b = bodies[i]; + b->mass *= scale; + b->invMass = 1.0f / b->mass; + b->inertiaTensor *= scale; + b->inverseInertiaTensor = b->inertiaTensor.Inverse(); + } + totalMass = forceTotalMass; + } + + if ( af_useLinearTime.GetBool() ) { + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + // only bilateral constraints between two non-world bodies that do not + // create loops can be used as primary constraints + if ( !c->body1->primaryConstraint && c->fl.allowPrimary && c->body2 != NULL && !IsClosedLoop( c->body1, c->body2 ) ) { + c->body1->primaryConstraint = c; + c->body1->parent = c->body2; + c->body2->children.Append( c->body1 ); + c->fl.isPrimary = true; + c->firstIndex = 0; + primaryConstraints.Append( c ); + } else { + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } + + // create trees for all parent bodies + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->parent ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + } + + // add each child body to the appropriate tree + for ( i = 0; i < bodies.Num(); i++ ) { + if ( bodies[i]->parent ) { + for ( b = bodies[i]->parent; !b->tree; b = b->parent ) { + } + b->tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = b->tree; + } + } + + if ( trees.Num() > 1 ) { + gameLocal.Warning( "Articulated figure has multiple seperate tree structures for entity '%s' type '%s'.", + self->name.c_str(), self->GetType()->classname ); + } + + // sort bodies in each tree to make sure parents come first + for ( i = 0; i < trees.Num(); i++ ) { + trees[i]->SortBodies(); + } + + } else { + + // create a tree for each body + for ( i = 0; i < bodies.Num(); i++ ) { + tree = new idAFTree(); + tree->sortedBodies.Clear(); + tree->sortedBodies.Append( bodies[i] ); + bodies[i]->tree = tree; + trees.Append( tree ); + } + + for ( i = 0; i < constraints.Num(); i++ ) { + c = constraints[i]; + + c->body1->constraints.Append( c ); + if ( c->body2 ) { + c->body2->constraints.Append( c ); + } + + c->fl.isPrimary = false; + auxiliaryConstraints.Append( c ); + } + } +} + +/* +================ +idPhysics_AF::AddBody + + bodies get an id in the order they are added starting at zero + as such the first body added will get id zero +================ +*/ +int idPhysics_AF::AddBody( idAFBody *body ) { + int id = 0; + + if ( !body->clipModel ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' has no clip model.", body->name.c_str() ); + } + + if ( bodies.Find( body ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: body '%s' added twice.", body->name.c_str() ); + } + + if ( GetBody( body->name ) ) { + gameLocal.Error( "idPhysics_AF::AddBody: a body with the name '%s' already exists.", body->name.c_str() ); + } + + id = bodies.Num(); + body->clipModel->SetId( id ); + if ( body->linearFriction < 0.0f ) { + body->linearFriction = linearFriction; + body->angularFriction = angularFriction; + body->contactFriction = contactFriction; + } + if ( body->bouncyness < 0.0f ) { + body->bouncyness = bouncyness; + } + if ( !body->fl.clipMaskSet ) { + body->clipMask = clipMask; + } + + bodies.Append( body ); + + changedAF = true; + + return id; +} + +/* +================ +idPhysics_AF::AddConstraint +================ +*/ +void idPhysics_AF::AddConstraint( idAFConstraint *constraint ) { + + if ( constraints.Find( constraint ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: constraint '%s' added twice.", constraint->name.c_str() ); + } + if ( GetConstraint( constraint->name ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: a constraint with the name '%s' already exists.", constraint->name.c_str() ); + } + if ( !constraint->body1 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 == NULL on constraint '%s'.", constraint->name.c_str() ); + } + if ( !bodies.Find( constraint->body1 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body2 && !bodies.Find( constraint->body2 ) ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body2 of constraint '%s' is not part of the articulated figure.", constraint->name.c_str() ); + } + if ( constraint->body1 == constraint->body2 ) { + gameLocal.Error( "idPhysics_AF::AddConstraint: body1 and body2 of constraint '%s' are the same.", constraint->name.c_str() ); + } + + constraints.Append( constraint ); + constraint->physics = this; + + changedAF = true; +} + +/* +================ +idPhysics_AF::AddFrameConstraint +================ +*/ +void idPhysics_AF::AddFrameConstraint( idAFConstraint *constraint ) { + frameConstraints.Append( constraint ); + constraint->physics = this; +} + +/* +================ +idPhysics_AF::ForceBodyId +================ +*/ +void idPhysics_AF::ForceBodyId( idAFBody *body, int newId ) { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 ) { + gameLocal.Error( "ForceBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + if ( id != newId ) { + idAFBody *b = bodies[newId]; + bodies[newId] = bodies[id]; + bodies[id] = b; + changedAF = true; + } +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( idAFBody *body ) const { + int id; + + id = bodies.FindIndex( body ); + if ( id == -1 && body ) { + gameLocal.Error( "GetBodyId: body '%s' is not part of the articulated figure.\n", body->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetBodyId +================ +*/ +int idPhysics_AF::GetBodyId( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return i; + } + } + gameLocal.Error( "GetBodyId: no body with the name '%s' is not part of the articulated figure.\n", bodyName ); + return 0; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( idAFConstraint *constraint ) const { + int id; + + id = constraints.FindIndex( constraint ); + if ( id == -1 && constraint ) { + gameLocal.Error( "GetConstraintId: constraint '%s' is not part of the articulated figure.\n", constraint->name.c_str() ); + } + return id; +} + +/* +================ +idPhysics_AF::GetConstraintId +================ +*/ +int idPhysics_AF::GetConstraintId( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return i; + } + } + gameLocal.Error( "GetConstraintId: no constraint with the name '%s' is not part of the articulated figure.\n", constraintName ); + return 0; +} + +/* +================ +idPhysics_AF::GetNumBodies +================ +*/ +int idPhysics_AF::GetNumBodies( void ) const { + return bodies.Num(); +} + +/* +================ +idPhysics_AF::GetNumConstraints +================ +*/ +int idPhysics_AF::GetNumConstraints( void ) const { + return constraints.Num(); +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const char *bodyName ) const { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + return bodies[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetBody +================ +*/ +idAFBody *idPhysics_AF::GetBody( const int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + gameLocal.Error( "GetBody: no body with id %d exists\n", id ); + return NULL; + } + return bodies[id]; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const char *constraintName ) const { + int i; + + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->name.Icmp( constraintName ) == 0 ) { + return constraints[i]; + } + } + + return NULL; +} + +/* +================ +idPhysics_AF::GetConstraint +================ +*/ +idAFConstraint *idPhysics_AF::GetConstraint( const int id ) const { + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "GetConstraint: no constraint with id %d exists\n", id ); + return NULL; + } + return constraints[id]; +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const char *bodyName ) { + int i; + + // find the body with the given name + for ( i = 0; i < bodies.Num(); i++ ) { + if ( !bodies[i]->name.Icmp( bodyName ) ) { + break; + } + } + + if ( i >= bodies.Num() ) { + gameLocal.Warning( "DeleteBody: no body found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + bodyName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteBody( i ); +} + +/* +================ +idPhysics_AF::DeleteBody +================ +*/ +void idPhysics_AF::DeleteBody( const int id ) { + int j; + + if ( id < 0 || id > bodies.Num() ) { + gameLocal.Error( "DeleteBody: no body with id %d.", id ); + return; + } + + // remove any constraints attached to this body + for ( j = 0; j < constraints.Num(); j++ ) { + if ( constraints[j]->body1 == bodies[id] || constraints[j]->body2 == bodies[id] ) { + delete constraints[j]; + constraints.RemoveIndex( j ); + j--; + } + } + + // remove the body + delete bodies[id]; + bodies.RemoveIndex( id ); + + // set new body ids + for ( j = 0; j < bodies.Num(); j++ ) { + bodies[j]->clipModel->SetId( j ); + } + + changedAF = true; +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const char *constraintName ) { + int i; + + // find the constraint with the given name + for ( i = 0; i < constraints.Num(); i++ ) { + if ( !constraints[i]->name.Icmp( constraintName ) ) { + break; + } + } + + if ( i >= constraints.Num() ) { + gameLocal.Warning( "DeleteConstraint: no constriant found in the articulated figure with the name '%s' for entity '%s' type '%s'.", + constraintName, self->name.c_str(), self->GetType()->classname ); + return; + } + + DeleteConstraint( i ); +} + +/* +================ +idPhysics_AF::DeleteConstraint +================ +*/ +void idPhysics_AF::DeleteConstraint( const int id ) { + + if ( id < 0 || id >= constraints.Num() ) { + gameLocal.Error( "DeleteConstraint: no constraint with id %d.", id ); + return; + } + + // remove the constraint + delete constraints[id]; + constraints.RemoveIndex( id ); + + changedAF = true; +} + +/* +================ +idPhysics_AF::GetBodyContactConstraints +================ +*/ +int idPhysics_AF::GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const { + int i, numContacts; + idAFBody *body; + idAFConstraint_Contact *contact; + + if ( id < 0 || id >= bodies.Num() || maxContacts <= 0 ) { + return 0; + } + + numContacts = 0; + body = bodies[id]; + for ( i = 0; i < contactConstraints.Num(); i++ ) { + contact = contactConstraints[i]; + if ( contact->body1 == body || contact->body2 == body ) { + contacts[numContacts++] = contact; + if ( numContacts >= maxContacts ) { + return numContacts; + } + } + } + return numContacts; +} + +/* +================ +idPhysics_AF::SetDefaultFriction +================ +*/ +void idPhysics_AF::SetDefaultFriction( float linear, float angular, float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_AF::GetImpactInfo +================ +*/ +void idPhysics_AF::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + if ( id < 0 || id >= bodies.Num() ) { + memset( info, 0, sizeof( *info ) ); + return; + } + info->invMass = 1.0f / bodies[id]->mass; + info->invInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + info->position = point - bodies[id]->current->worldOrigin; + info->velocity = bodies[id]->current->spatialVelocity.SubVec3(0) + bodies[id]->current->spatialVelocity.SubVec3(1).Cross( info->position ); +} + +/* +================ +idPhysics_AF::ApplyImpulse +================ +*/ +void idPhysics_AF::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + if ( noImpact || impulse.LengthSqr() < Square( impulseThreshold ) ) { + return; + } + idMat3 invWorldInertiaTensor = bodies[id]->current->worldAxis.Transpose() * bodies[id]->inverseInertiaTensor * bodies[id]->current->worldAxis; + bodies[id]->current->spatialVelocity.SubVec3(0) += bodies[id]->invMass * impulse; + bodies[id]->current->spatialVelocity.SubVec3(1) += invWorldInertiaTensor * (point - bodies[id]->current->worldOrigin).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_AF::AddForce +================ +*/ +void idPhysics_AF::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->externalForce.SubVec3( 0 ) += force; + bodies[id]->current->externalForce.SubVec3( 1 ) += (point - bodies[id]->current->worldOrigin).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_AF::IsAtRest +================ +*/ +bool idPhysics_AF::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_AF::GetRestStartTime +================ +*/ +int idPhysics_AF::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_AF::IsPushable +================ +*/ +bool idPhysics_AF::IsPushable( void ) const { + return ( !noImpact && ( masterBody == NULL || forcePushable ) ); +} + +/* +================ +idPhysics_AF::SaveState +================ +*/ +void idPhysics_AF::SaveState( void ) { + int i; + + saved = current; + + for ( i = 0; i < bodies.Num(); i++ ) { +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( &bodies[i]->saved, bodies[i]->current, sizeof( AFBodyPState_t ) ); +// RAVEN END + } +} + +/* +================ +idPhysics_AF::RestoreState +================ +*/ +void idPhysics_AF::RestoreState( void ) { + int i; + + current = saved; + + for ( i = 0; i < bodies.Num(); i++ ) { + *(bodies[i]->current) = bodies[i]->saved; + } + + EvaluateContacts(); +} + +/* +================ +idPhysics_AF::SetOrigin +================ +*/ +void idPhysics_AF::SetOrigin( const idVec3 &newOrigin, int id ) { + if ( masterBody ) { + Translate( masterBody->current->worldOrigin + masterBody->current->worldAxis * newOrigin - bodies[0]->current->worldOrigin ); + } else { + Translate( newOrigin - bodies[0]->current->worldOrigin ); + } +} + +/* +================ +idPhysics_AF::SetAxis +================ +*/ +void idPhysics_AF::SetAxis( const idMat3 &newAxis, int id ) { + idMat3 axis; + idRotation rotation; + + if ( masterBody ) { + axis = bodies[0]->current->worldAxis.Transpose() * ( newAxis * masterBody->current->worldAxis ); + } else { + axis = bodies[0]->current->worldAxis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( bodies[0]->current->worldOrigin ); + + Rotate( rotation ); +} + +/* +================ +idPhysics_AF::Translate +================ +*/ +void idPhysics_AF::Translate( const idVec3 &translation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // translate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Translate( translation ); + } + } + + // translate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + + body = bodies[i]; + body->current->worldOrigin += translation; + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::Rotate +================ +*/ +void idPhysics_AF::Rotate( const idRotation &rotation, int id ) { + int i; + idAFBody *body; + + if ( !worldConstraintsLocked ) { + // rotate constraints attached to the world + for ( i = 0; i < constraints.Num(); i++ ) { + constraints[i]->Rotate( rotation ); + } + } + + // rotate all the bodies + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + body->current->worldOrigin *= rotation; + body->current->worldAxis *= rotation.ToMat3(); + } + + Activate(); + + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::GetOrigin +================ +*/ +const idVec3 &idPhysics_AF::GetOrigin( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->worldOrigin; + } +} + +/* +================ +idPhysics_AF::GetAxis +================ +*/ +const idMat3 &idPhysics_AF::GetAxis( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return mat3_identity; + } + else { + return bodies[id]->current->worldAxis; + } +} + +/* +================ +idPhysics_AF::SetLinearVelocity +================ +*/ +void idPhysics_AF::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 0 ) = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::SetAngularVelocity +================ +*/ +void idPhysics_AF::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + if ( id < 0 || id >= bodies.Num() ) { + return; + } + bodies[id]->current->spatialVelocity.SubVec3( 1 ) = newAngularVelocity; + Activate(); +} + +/* +================ +idPhysics_AF::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetLinearVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 0 ); + } +} + +/* +================ +idPhysics_AF::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetAngularVelocity( int id ) const { + if ( id < 0 || id >= bodies.Num() ) { + return vec3_origin; + } + else { + return bodies[id]->current->spatialVelocity.SubVec3( 1 ); + } +} + +/* +================ +idPhysics_AF::ClipTranslation +================ +*/ +void idPhysics_AF::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { + gameLocal.TranslationModel( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, bodyResults, body->current->worldOrigin, body->current->worldOrigin + translation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + results.endpos = bodies[0]->current->worldOrigin + results.fraction * translation; + results.endAxis = bodies[0]->current->worldAxis; +} + +/* +================ +idPhysics_AF::ClipRotation +================ +*/ +void idPhysics_AF::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + int i; + idAFBody *body; + trace_t bodyResults; + idRotation partialRotation; + + results.fraction = 1.0f; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, bodyResults, body->current->worldOrigin, rotation, + body->clipModel, body->current->worldAxis, body->clipMask, self ); +// RAVEN END + } + if ( bodyResults.fraction < results.fraction ) { + results = bodyResults; + } + } + } + + partialRotation = rotation * results.fraction; + results.endpos = bodies[0]->current->worldOrigin * partialRotation; + results.endAxis = bodies[0]->current->worldAxis * partialRotation.ToMat3(); +} + +/* +================ +idPhysics_AF::ClipContents +================ +*/ +int idPhysics_AF::ClipContents( const idClipModel *model ) const { + int i, contents; + idAFBody *body; + + contents = 0; + + for ( i = 0; i < bodies.Num(); i++ ) { + body = bodies[i]; + + if ( body->clipModel->IsTraceModel() ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + contents |= gameLocal.Contents( self, body->current->worldOrigin, + body->clipModel, body->current->worldAxis, -1, NULL ); +// RAVEN END + } + } + } + + return contents; +} + +/* +================ +idPhysics_AF::DisableClip +================ +*/ +void idPhysics_AF::DisableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Disable(); + } +} + +/* +================ +idPhysics_AF::EnableClip +================ +*/ +void idPhysics_AF::EnableClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Enable(); + } +} + +/* +================ +idPhysics_AF::UnlinkClip +================ +*/ +void idPhysics_AF::UnlinkClip( void ) { + int i; + + for ( i = 0; i < bodies.Num(); i++ ) { + bodies[i]->clipModel->Unlink(); + } +} + +/* +================ +idPhysics_AF::LinkClip +================ +*/ +void idPhysics_AF::LinkClip( void ) { + UpdateClipModels(); +} + +/* +================ +idPhysics_AF::SetPushed +================ +*/ +void idPhysics_AF::SetPushed( int deltaTime ) { + idAFBody *body; + idRotation rotation; + + if ( bodies.Num() ) { + body = bodies[0]; + rotation = ( body->saved.worldAxis.Transpose() * body->current->worldAxis ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( body->current->worldOrigin - body->saved.worldOrigin ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); + } +} + +/* +================ +idPhysics_AF::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_AF::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_AF::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_AF::SetMaster + + the binding is orientated based on the constraints being used +================ +*/ +void idPhysics_AF::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + idRotation rotation; + + if ( master ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( !masterBody ) { + masterBody = new idAFBody(); + // translate and rotate all the constraints with body2 == NULL from world space to master space + rotation = masterAxis.Transpose().ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Translate( -masterOrigin ); + constraints[i]->Rotate( rotation ); + } + } + Activate(); + } + masterBody->current->worldOrigin = masterOrigin; + masterBody->current->worldAxis = masterAxis; + } + else { + if ( masterBody ) { + // translate and rotate all the constraints with body2 == NULL from master space to world space + rotation = masterBody->current->worldAxis.ToRotation(); + for ( i = 0; i < constraints.Num(); i++ ) { + if ( constraints[i]->GetBody2() == NULL ) { + constraints[i]->Rotate( rotation ); + constraints[i]->Translate( masterBody->current->worldOrigin ); + } + } + delete masterBody; + masterBody = NULL; + Activate(); + } + } +} + + +const float AF_VELOCITY_MAX = 16000; +const int AF_VELOCITY_TOTAL_BITS = 16; +const int AF_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_VELOCITY_MAX ) ) + 1; +const int AF_VELOCITY_MANTISSA_BITS = AF_VELOCITY_TOTAL_BITS - 1 - AF_VELOCITY_EXPONENT_BITS; +const float AF_FORCE_MAX = 1e20f; +const int AF_FORCE_TOTAL_BITS = 16; +const int AF_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( AF_FORCE_MAX ) ) + 1; +const int AF_FORCE_MANTISSA_BITS = AF_FORCE_TOTAL_BITS - 1 - AF_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_AF::WriteToSnapshot +================ +*/ +void idPhysics_AF::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat; + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.noMoveTime ); + msg.WriteFloat( current.activateTime ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + msg.WriteByte( bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + quat = state->worldAxis.ToCQuat(); + + msg.WriteFloat( state->worldOrigin[0] ); + msg.WriteFloat( state->worldOrigin[1] ); + msg.WriteFloat( state->worldOrigin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[0], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[1], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[2], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[3], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[4], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->spatialVelocity[5], AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* msg.WriteDeltaFloat( 0.0f, state->externalForce[0], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[1], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[2], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[3], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[4], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, state->externalForce[5], AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + } +} + +/* +================ +idPhysics_AF::ReadFromSnapshot +================ +*/ +void idPhysics_AF::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat; + + // TODO: Check that this conditional write to delta message is OK + current.atRest = msg.ReadLong(); + current.noMoveTime = msg.ReadFloat(); + current.activateTime = msg.ReadFloat(); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + + num = msg.ReadByte(); + assert( num == bodies.Num() ); + + for ( i = 0; i < bodies.Num(); i++ ) { + AFBodyPState_t *state = bodies[i]->current; + + state->worldOrigin[0] = msg.ReadFloat(); + state->worldOrigin[1] = msg.ReadFloat(); + state->worldOrigin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + state->spatialVelocity[0] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[1] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[2] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[3] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[4] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); + state->spatialVelocity[5] = msg.ReadDeltaFloat( 0.0f, AF_VELOCITY_EXPONENT_BITS, AF_VELOCITY_MANTISSA_BITS ); +/* state->externalForce[0] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[1] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[2] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[3] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[4] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); + state->externalForce[5] = msg.ReadDeltaFloat( 0.0f, AF_FORCE_EXPONENT_BITS, AF_FORCE_MANTISSA_BITS ); +*/ + state->worldAxis = quat.ToMat3(); + } + + UpdateClipModels(); +} diff --git a/src/mpgame/physics/Physics_AF.h b/src/mpgame/physics/Physics_AF.h new file mode 100644 index 0000000..3d39b90 --- /dev/null +++ b/src/mpgame/physics/Physics_AF.h @@ -0,0 +1,1011 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_AF_H__ +#define __PHYSICS_AF_H__ + +/* +=================================================================================== + + Articulated Figure physics + + Employs a constraint force based dynamic simulation using a lagrangian + multiplier method to solve for the constraint forces. + +=================================================================================== +*/ + +class idAFConstraint; +class idAFConstraint_Fixed; +class idAFConstraint_BallAndSocketJoint; +class idAFConstraint_BallAndSocketJointFriction; +class idAFConstraint_UniversalJoint; +class idAFConstraint_UniversalJointFriction; +class idAFConstraint_CylindricalJoint; +class idAFConstraint_Hinge; +class idAFConstraint_HingeFriction; +class idAFConstraint_HingeSteering; +class idAFConstraint_Slider; +class idAFConstraint_Line; +class idAFConstraint_Plane; +class idAFConstraint_Spring; +class idAFConstraint_Contact; +class idAFConstraint_ContactFriction; +class idAFConstraint_ConeLimit; +class idAFConstraint_PyramidLimit; +class idAFBody; +class idAFTree; +class idPhysics_AF; + +typedef enum { + CONSTRAINT_INVALID, + CONSTRAINT_FIXED, + CONSTRAINT_BALLANDSOCKETJOINT, + CONSTRAINT_UNIVERSALJOINT, + CONSTRAINT_HINGE, + CONSTRAINT_HINGESTEERING, + CONSTRAINT_SLIDER, + CONSTRAINT_CYLINDRICALJOINT, + CONSTRAINT_LINE, + CONSTRAINT_PLANE, + CONSTRAINT_SPRING, + CONSTRAINT_CONTACT, + CONSTRAINT_FRICTION, + CONSTRAINT_CONELIMIT, + CONSTRAINT_PYRAMIDLIMIT +} constraintType_t; + + +//=============================================================== +// +// idAFConstraint +// +//=============================================================== + +// base class for all constraints +class idAFConstraint { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFConstraint( void ); + virtual ~idAFConstraint( void ); + constraintType_t GetType( void ) const { return type; } + const idStr & GetName( void ) const { return name; } + idAFBody * GetBody1( void ) const { return body1; } + idAFBody * GetBody2( void ) const { return body2; } + void SetPhysics( idPhysics_AF *p ) { physics = p; } + const idVecX & GetMultiplier( void ); + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + constraintType_t type; // constraint type + idStr name; // name of constraint + idAFBody * body1; // first constrained body + idAFBody * body2; // second constrained body, NULL for world + idPhysics_AF * physics; // for adding additional constraints like limits + + // simulation variables set by Evaluate + idMatX J1, J2; // matrix with left hand side of constraint equations + idVecX c1, c2; // right hand side of constraint equations + idVecX lo, hi, e; // low and high bounds and lcp epsilon + idAFConstraint * boxConstraint; // constraint the boxIndex refers to + int boxIndex[6]; // indexes for special box constrained variables + + // simulation variables used during calculations + idMatX invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX lm; // lagrange multipliers + int firstIndex; // index of the first constraint row in the lcp matrix + + struct constraintFlags_s { + bool allowPrimary : 1; // true if the constraint can be used as a primary constraint + bool frameConstraint : 1; // true if this constraint is added to the frame constraints + bool noCollision : 1; // true if body1 and body2 never collide with each other + bool isPrimary : 1; // true if this is a primary constraint + bool isZero : 1; // true if 's' is zero during calculations + } fl; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitSize( int size ); +}; + +// fixed or rigid joint which allows zero degrees of freedom +// constrains body1 to have a fixed position and orientation relative to body2 +class idAFConstraint_Fixed : public idAFConstraint { + +public: + idAFConstraint_Fixed( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetRelativeOrigin( const idVec3 &origin ) { this->offset = origin; } + void SetRelativeAxis( const idMat3 &axis ) { this->relAxis = axis; } + virtual void SetBody1( idAFBody *body ); + virtual void SetBody2( idAFBody *body ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 offset; // offset of body1 relative to body2 in body2 space + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); + void InitOffset( void ); +}; + +// ball and socket or spherical joint which allows 3 degrees of freedom +// constrains body1 relative to body2 with a ball and socket joint +class idAFConstraint_BallAndSocketJoint : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_BallAndSocketJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle, const idVec3 &body1Axis ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_BallAndSocketJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// ball and socket joint friction +class idAFConstraint_BallAndSocketJointFriction : public idAFConstraint { + +public: + idAFConstraint_BallAndSocketJointFriction( void ); + void Setup( idAFConstraint_BallAndSocketJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_BallAndSocketJoint *joint; + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal, Cardan or Hooke joint which allows 2 degrees of freedom +// like a ball and socket joint but also constrains the rotation about the cardan shafts +class idAFConstraint_UniversalJoint : public idAFConstraint { + +public: + idAFConstraint_UniversalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_UniversalJoint( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetShafts( const idVec3 &cardanShaft1, const idVec3 &cardanShaft2 ); + void GetShafts( idVec3 &cardanShaft1, idVec3 &cardanShaft2 ) { cardanShaft1 = shaft1; cardanShaft2 = shaft2; } + void SetNoLimit( void ); + void SetConeLimit( const idVec3 &coneAxis, const float coneAngle ); + void SetPyramidLimit( const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float angle1, const float angle2 ); + void SetLimitEpsilon( const float e ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 shaft1; // body1 cardan shaft in body1 space + idVec3 shaft2; // body2 cardan shaft in body2 space + idVec3 axis1; // cardan axis in body1 space + idVec3 axis2; // cardan axis in body2 space + float friction; // joint friction + idAFConstraint_ConeLimit *coneLimit; // cone shaped limit + idAFConstraint_PyramidLimit *pyramidLimit; // pyramid shaped limit + idAFConstraint_UniversalJointFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// universal joint friction +class idAFConstraint_UniversalJointFriction : public idAFConstraint { + +public: + idAFConstraint_UniversalJointFriction( void ); + void Setup( idAFConstraint_UniversalJoint *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_UniversalJoint *joint; // universal joint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// cylindrical joint which allows 2 degrees of freedom +// constrains body1 to lie on a line relative to body2 and allows only translation along and rotation about the line +class idAFConstraint_CylindricalJoint : public idAFConstraint { + +public: + idAFConstraint_CylindricalJoint( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge, revolute or pin joint which allows 1 degree of freedom +// constrains all motion of body1 relative to body2 except the rotation about the hinge axis +class idAFConstraint_Hinge : public idAFConstraint { + +public: + idAFConstraint_Hinge( const idStr &name, idAFBody *body1, idAFBody *body2 ); + ~idAFConstraint_Hinge( void ); + void SetAnchor( const idVec3 &worldPosition ); + idVec3 GetAnchor( void ) const; + void SetAxis( const idVec3 &axis ); + void GetAxis( idVec3 &a1, idVec3 &a2 ) const { a1 = axis1; a2 = axis2; } + idVec3 GetAxis( void ) const; + void SetNoLimit( void ); + void SetLimit( const idVec3 &axis, const float angle, const idVec3 &body1Axis ); + void SetLimitEpsilon( const float e ); + float GetAngle( void ) const; + void SetSteerAngle( const float degrees ); + void SetSteerSpeed( const float speed ); + void SetFriction( const float f ) { friction = f; } + float GetFriction( void ) const; + virtual void DebugDraw( void ); + virtual void GetForce( idAFBody *body, idVec6 &force ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 axis1; // axis in body1 space + idVec3 axis2; // axis in body2 space + idMat3 initialAxis; // initial axis of body1 relative to body2 + float friction; // hinge friction + idAFConstraint_ConeLimit *coneLimit; // cone limit + idAFConstraint_HingeSteering *steering; // steering + idAFConstraint_HingeFriction *fc; // friction constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// hinge joint friction +class idAFConstraint_HingeFriction : public idAFConstraint { + +public: + idAFConstraint_HingeFriction( void ); + void Setup( idAFConstraint_Hinge *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains two bodies attached to each other with a hinge to get a specified relative orientation +class idAFConstraint_HingeSteering : public idAFConstraint { + +public: + idAFConstraint_HingeSteering( void ); + void Setup( idAFConstraint_Hinge *cc ); + void SetSteerAngle( const float degrees ) { steerAngle = degrees; } + void SetSteerSpeed( const float speed ) { steerSpeed = speed; } + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idAFConstraint_Hinge * hinge; // hinge + float steerAngle; // desired steer angle in degrees + float steerSpeed; // steer speed + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// slider, prismatic or translational constraint which allows 1 degree of freedom +// constrains body1 to lie on a line relative to body2, the orientation is also fixed relative to body2 +class idAFConstraint_Slider : public idAFConstraint { + +public: + idAFConstraint_Slider( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAxis( const idVec3 &ax ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 axis; // axis along which body1 slides in body2 space + idVec3 offset; // offset of body1 relative to body2 + idMat3 relAxis; // rotation of body1 relative to body2 + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// line constraint which allows 4 degrees of freedom +// constrains body1 to lie on a line relative to body2, does not constrain the orientation. +class idAFConstraint_Line : public idAFConstraint { + +public: + idAFConstraint_Line( const idStr &name, idAFBody *body1, idAFBody *body2 ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// plane constraint which allows 5 degrees of freedom +// constrains body1 to lie in a plane relative to body2, does not constrain the orientation. +class idAFConstraint_Plane : public idAFConstraint { + +public: + idAFConstraint_Plane( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetPlane( const idVec3 &normal, const idVec3 &anchor ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + idVec3 planeNormal; // plane normal in body2 space + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// spring constraint which allows 6 or 5 degrees of freedom based on the spring limits +// constrains body1 relative to body2 with a spring +class idAFConstraint_Spring : public idAFConstraint { + +public: + idAFConstraint_Spring( const idStr &name, idAFBody *body1, idAFBody *body2 ); + void SetAnchor( const idVec3 &worldAnchor1, const idVec3 &worldAnchor2 ); + void SetSpring( const float stretch, const float compress, const float damping, const float restLength ); + void SetLimit( const float minLength, const float maxLength ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 anchor1; // anchor in body1 space + idVec3 anchor2; // anchor in body2 space + float kstretch; // spring constant when stretched + float kcompress; // spring constant when compressed + float damping; // spring damping + float restLength; // rest length of spring + float minLength; // minimum spring length + float maxLength; // maximum spring length + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains body1 to either be in contact with or move away from body2 +class idAFConstraint_Contact : public idAFConstraint { + +public: + idAFConstraint_Contact( void ); + ~idAFConstraint_Contact( void ); + void Setup( idAFBody *b1, idAFBody *b2, contactInfo_t &c ); + const contactInfo_t & GetContact( void ) const { return contact; } + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void GetCenter( idVec3 ¢er ); + +protected: + contactInfo_t contact; // contact information + idAFConstraint_ContactFriction *fc; // contact friction + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// contact friction +class idAFConstraint_ContactFriction : public idAFConstraint { + +public: + idAFConstraint_ContactFriction( void ); + void Setup( idAFConstraint_Contact *cc ); + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + +protected: + idAFConstraint_Contact *cc; // contact constraint + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a cone relative to body2 +class idAFConstraint_ConeLimit : public idAFConstraint { + +public: + idAFConstraint_ConeLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &coneAnchor, const idVec3 &coneAxis, + const float coneAngle, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &coneAnchor ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 coneAnchor; // top of the cone in body2 space + idVec3 coneAxis; // cone axis in body2 space + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle; // cos( coneAngle / 2 ) + float sinHalfAngle; // sin( coneAngle / 4 ) + float cosHalfAngle; // cos( coneAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + +// constrains an axis attached to body1 to be inside a pyramid relative to body2 +class idAFConstraint_PyramidLimit : public idAFConstraint { + +public: + idAFConstraint_PyramidLimit( void ); + void Setup( idAFBody *b1, idAFBody *b2, const idVec3 &pyramidAnchor, + const idVec3 &pyramidAxis, const idVec3 &baseAxis, + const float pyramidAngle1, const float pyramidAngle2, const idVec3 &body1Axis ); + void SetAnchor( const idVec3 &pyramidAxis ); + void SetBody1Axis( const idVec3 &body1Axis ); + void SetEpsilon( const float e ) { epsilon = e; } + bool Add( idPhysics_AF *phys, float invTimeStep ); + virtual void DebugDraw( void ); + virtual void Translate( const idVec3 &translation ); + virtual void Rotate( const idRotation &rotation ); + virtual void Save( idSaveGame *saveFile ) const; + virtual void Restore( idRestoreGame *saveFile ); + +protected: + idVec3 pyramidAnchor; // top of the pyramid in body2 space + idMat3 pyramidBasis; // pyramid basis in body2 space with base[2] being the pyramid axis + idVec3 body1Axis; // axis in body1 space that should stay within the cone + float cosAngle[2]; // cos( pyramidAngle / 2 ) + float sinHalfAngle[2]; // sin( pyramidAngle / 4 ) + float cosHalfAngle[2]; // cos( pyramidAngle / 4 ) + float epsilon; // lcp epsilon + +protected: + virtual void Evaluate( float invTimeStep ); + virtual void ApplyFriction( float invTimeStep ); +}; + + +//=============================================================== +// +// idAFBody +// +//=============================================================== + +typedef struct AFBodyPState_s { + idVec3 worldOrigin; // position in world space + idMat3 worldAxis; // axis at worldOrigin + idVec6 spatialVelocity; // linear and rotational velocity of body + idVec6 externalForce; // external force and torque applied to body +} AFBodyPState_t; + + +class idAFBody { + + friend class idPhysics_AF; + friend class idAFTree; + +public: + idAFBody( void ); + idAFBody( const idStr &name, idClipModel *clipModel, float density ); + ~idAFBody( void ); + + void Init( void ); + const idStr & GetName( void ) const { return name; } + const idVec3 & GetWorldOrigin( void ) const { return current->worldOrigin; } + const idMat3 & GetWorldAxis( void ) const { return current->worldAxis; } + const idVec3 & GetLinearVelocity( void ) const { return current->spatialVelocity.SubVec3(0); } + const idVec3 & GetAngularVelocity( void ) const { return current->spatialVelocity.SubVec3(1); } + idVec3 GetPointVelocity( const idVec3 &point ) const; + const idVec3 & GetCenterOfMass( void ) const { return centerOfMass; } + void SetClipModel( idClipModel *clipModel ); + idClipModel * GetClipModel( void ) const { return clipModel; } + void SetClipMask( const int mask ) { clipMask = mask; fl.clipMaskSet = true; } + int GetClipMask( void ) const { return clipMask; } + void SetSelfCollision( const bool enable ) { fl.selfCollision = enable; } + void SetWorldOrigin( const idVec3 &origin ) { current->worldOrigin = origin; } + void SetWorldAxis( const idMat3 &axis ) { current->worldAxis = axis; } + void SetLinearVelocity( const idVec3 &linear ) const { current->spatialVelocity.SubVec3(0) = linear; } + void SetAngularVelocity( const idVec3 &angular ) const { current->spatialVelocity.SubVec3(1) = angular; } + void SetFriction( float linear, float angular, float contact ); + float GetContactFriction( void ) const { return contactFriction; } + void SetBouncyness( float bounce ); + float GetBouncyness( void ) const { return bouncyness; } + void SetDensity( float density, const idMat3 &inertiaScale = mat3_identity ); + float GetInverseMass( void ) const { return invMass; } + idMat3 GetInverseWorldInertia( void ) const { return current->worldAxis.Transpose() * inverseInertiaTensor * current->worldAxis; } + + void SetFrictionDirection( const idVec3 &dir ); + bool GetFrictionDirection( idVec3 &dir ) const; + + void SetContactMotorDirection( const idVec3 &dir ); + bool GetContactMotorDirection( idVec3 &dir ) const; + void SetContactMotorVelocity( float vel ) { contactMotorVelocity = vel; } + float GetContactMotorVelocity( void ) const { return contactMotorVelocity; } + void SetContactMotorForce( float force ) { contactMotorForce = force; } + float GetContactMotorForce( void ) const { return contactMotorForce; } + + void AddForce( const idVec3 &point, const idVec3 &force ); + void InverseWorldSpatialInertiaMultiply( idVecX &dst, const float *v ) const; + idVec6 & GetResponseForce( int index ) { return reinterpret_cast(response[ index * 8 ]); } + + void Save( idSaveGame *saveFile ); + void Restore( idRestoreGame *saveFile ); + +private: + // properties + idStr name; // name of body + idAFBody * parent; // parent of this body + idList children; // children of this body + idClipModel * clipModel; // model used for collision detection + idAFConstraint * primaryConstraint; // primary constraint (this->constraint->body1 = this) + idListconstraints; // all constraints attached to this body + idAFTree * tree; // tree structure this body is part of + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bounce + int clipMask; // contents this body collides with + idVec3 frictionDir; // specifies a single direction of friction in body space + idVec3 contactMotorDir; // contact motor direction + float contactMotorVelocity; // contact motor velocity + float contactMotorForce; // maximum force applied to reach the motor velocity + + // derived properties + float mass; // mass of body + float invMass; // inverse mass + idVec3 centerOfMass; // center of mass of body + idMat3 inertiaTensor; // inertia tensor + idMat3 inverseInertiaTensor; // inverse inertia tensor + + // physics state + AFBodyPState_t state[2]; + AFBodyPState_t * current; // current physics state + AFBodyPState_t * next; // next physics state + AFBodyPState_t saved; // saved physics state + idVec3 atRestOrigin; // origin at rest + idMat3 atRestAxis; // axis at rest + + // simulation variables used during calculations + idMatX inverseWorldSpatialInertia; // inverse spatial inertia in world space + idMatX I, invI; // transformed inertia + idMatX J; // transformed constraint matrix + idVecX s; // temp solution + idVecX totalForce; // total force acting on body + idVecX auxForce; // force from auxiliary constraints + idVecX acceleration; // acceleration + float * response; // forces on body in response to auxiliary constraint forces + int * responseIndex; // index to response forces + int numResponses; // number of response forces + int maxAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body + int maxSubTreeAuxiliaryIndex; // largest index of an auxiliary constraint constraining this body or one of it's children + + struct bodyFlags_s { + bool clipMaskSet : 1; // true if this body has a clip mask set + bool selfCollision : 1; // true if this body can collide with other bodies of this AF + bool spatialInertiaSparse: 1; // true if the spatial inertia matrix is sparse + bool useFrictionDir : 1; // true if a single friction direction should be used + bool useContactMotorDir : 1; // true if a contact motor should be used + bool isZero : 1; // true if 's' is zero during calculations + } fl; +}; + + +//=============================================================== +// +// idAFTree +// +//=============================================================== + +class idAFTree { + friend class idPhysics_AF; + +public: + void Factor( void ) const; + void Solve( int auxiliaryIndex = 0 ) const; + void Response( const idAFConstraint *constraint, int row, int auxiliaryIndex ) const; + void CalculateForces( float timeStep ) const; + void SetMaxSubTreeAuxiliaryIndex( void ); + void SortBodies( void ); + void SortBodies_r( idList&sortedList, idAFBody *body ); + void DebugDraw( const idVec4 &color ) const; + +private: + idList sortedBodies; +}; + + +//=============================================================== +// +// idPhysics_AF +// +//=============================================================== + +typedef struct AFPState_s { + int atRest; // >= 0 if articulated figure is at rest + float noMoveTime; // time the articulated figure is hardly moving + float activateTime; // time since last activation + float lastTimeStep; // last time step + idVec6 pushVelocity; // velocity with which the af is pushed +} AFPState_t; + +typedef struct AFCollision_s { + trace_t trace; + idAFBody * body; +} AFCollision_t; + +class idPhysics_AF : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_AF ); + + idPhysics_AF( void ); + ~idPhysics_AF( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + int AddBody( idAFBody *body ); // returns body id + void AddConstraint( idAFConstraint *constraint ); + void AddFrameConstraint( idAFConstraint *constraint ); + // force a body to have a certain id + void ForceBodyId( idAFBody *body, int newId ); + // get body or constraint id + int GetBodyId( idAFBody *body ) const; + int GetBodyId( const char *bodyName ) const; + int GetConstraintId( idAFConstraint *constraint ) const; + int GetConstraintId( const char *constraintName ) const; + // number of bodies and constraints + int GetNumBodies( void ) const; + int GetNumConstraints( void ) const; + // retrieve body or constraint + idAFBody * GetBody( const char *bodyName ) const; + idAFBody * GetBody( const int id ) const; + idAFBody * GetMasterBody( void ) const { return masterBody; } + idAFConstraint * GetConstraint( const char *constraintName ) const; + idAFConstraint * GetConstraint( const int id ) const; + // delete body or constraint + void DeleteBody( const char *bodyName ); + void DeleteBody( const int id ); + void DeleteConstraint( const char *constraintName ); + void DeleteConstraint( const int id ); + + // get all the contact constraints acting on the body + int GetBodyContactConstraints( const int id, idAFConstraint_Contact *contacts[], int maxContacts ) const; + // set the default friction for bodies + void SetDefaultFriction( float linear, float angular, float contact ); + // suspend settings + void SetSuspendSpeed( const idVec2 &velocity, const idVec2 &acceleration ); + // set the time and tolerances used to determine if the simulation can be suspended when the figure hardly moves for a while + void SetSuspendTolerance( const float noMoveTime, const float translationTolerance, const float rotationTolerance ); + // set minimum and maximum simulation time in seconds + void SetSuspendTime( const float minTime, const float maxTime ); + // set the time scale value + void SetTimeScale( const float ts ) { timeScale = ts; } + // set time scale ramp + void SetTimeScaleRamp( const float start, const float end ); + // set the joint friction scale + void SetJointFrictionScale( const float scale ) { jointFrictionScale = scale; } + // set joint friction dent + void SetJointFrictionDent( const float dent, const float start, const float end ); + // get the current joint friction scale + float GetJointFrictionScale( void ) const; + // set the contact friction scale + void SetContactFrictionScale( const float scale ) { contactFrictionScale = scale; } + // set contact friction dent + void SetContactFrictionDent( const float dent, const float start, const float end ); + // get the current contact friction scale + float GetContactFrictionScale( void ) const; + // enable or disable collision detection + void SetCollision( const bool enable ) { enableCollision = enable; } + // enable or disable self collision + void SetSelfCollision( const bool enable ) { selfCollision = enable; } + // enable or disable coming to a dead stop + void SetComeToRest( bool enable ) { comeToRest = enable; } + // call when structure of articulated figure changes + void SetChanged( void ) { changedAF = true; } +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + // enable or disable fast evaluation + void SetFastEval( const bool enable ) { fastEval = enable; } +// RAVEN END + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + // lock of unlock the world constraints + void LockWorldConstraints( const bool lock ) { worldConstraintsLocked = lock; } + // set force pushable + void SetForcePushable( const bool enable ) { forcePushable = enable; } + // update the clip model positions + void UpdateClipModels( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + //MCG: added SetImpulseThreshold + void SetImpulseThreshold( float newIT ) { impulseThreshold = newIT; }; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + void Activate( void ); + void PutToRest( void ); + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // articulated figure + idList trees; // tree structures + idList bodies; // all bodies + idListconstraints; // all frame independent constraints + idListprimaryConstraints; // list with primary constraints + idListauxiliaryConstraints; // list with auxiliary constraints + idListframeConstraints; // constraints that only live one frame + idListcontactConstraints; // contact constraints + idList contactBodies; // body id for each contact + idList collisions; // collisions + bool changedAF; // true when the articulated figure just changed + + // properties + float linearFriction; // default translational friction + float angularFriction; // default rotational friction + float contactFriction; // default friction with contact surfaces + float bouncyness; // default bouncyness + float totalMass; // total mass of articulated figure + float forceTotalMass; // force this total mass + + idVec2 suspendVelocity; // simulation may not be suspended if a body has more velocity + idVec2 suspendAcceleration; // simulation may not be suspended if a body has more acceleration + float noMoveTime; // suspend simulation if hardly any movement for this many seconds + float noMoveTranslation; // maximum translation considered no movement + float noMoveRotation; // maximum rotation considered no movement + float minMoveTime; // if > 0 the simulation is never suspended before running this many seconds + float maxMoveTime; // if > 0 the simulation is always suspeded after running this many seconds + float impulseThreshold; // threshold below which impulses are ignored to avoid continuous activation + + float timeScale; // the time is scaled with this value for slow motion effects + float timeScaleRampStart; // start of time scale change + float timeScaleRampEnd; // end of time scale change + + float jointFrictionScale; // joint friction scale + float jointFrictionDent; // joint friction dives from 1 to this value and goes up again + float jointFrictionDentStart; // start time of joint friction dent + float jointFrictionDentEnd; // end time of joint friction dent + float jointFrictionDentScale; // dent scale + + float contactFrictionScale; // contact friction scale + float contactFrictionDent; // contact friction dives from 1 to this value and goes up again + float contactFrictionDentStart; // start time of contact friction dent + float contactFrictionDentEnd; // end time of contact friction dent + float contactFrictionDentScale; // dent scale + + bool enableCollision; // if true collision detection is enabled + bool selfCollision; // if true the self collision is allowed + bool comeToRest; // if true the figure can come to rest + bool linearTime; // if true use the linear time algorithm + bool noImpact; // if true do not activate when another object collides + bool worldConstraintsLocked; // if true world constraints cannot be moved + bool forcePushable; // if true can be pushed even when bound to a master +// RAVEN BEGIN +// rjohnson: fast AF eval to skip some things that are not needed for specific circumstances + bool fastEval; // if true the fast eval is on +// RAVEN END + + // physics state + AFPState_t current; + AFPState_t saved; + + idAFBody * masterBody; // master body + idLCP * lcp; // linear complementarity problem solver + +private: + void BuildTrees( void ); + bool IsClosedLoop( const idAFBody *body1, const idAFBody *body2 ) const; + void PrimaryFactor( void ); + void EvaluateBodies( float timeStep ); + void EvaluateConstraints( float timeStep ); + void AddFrameConstraints( void ); + void RemoveFrameConstraints( void ); + void ApplyFriction( float timeStep, float endTimeMSec ); + void PrimaryForces( float timeStep ); + void AuxiliaryForces( float timeStep ); + void VerifyContactConstraints( void ); + void SetupContactConstraints( void ); + void ApplyContactForces( void ); + void Evolve( float timeStep ); + idEntity * SetupCollisionForBody( idAFBody *body ) const; + bool CollisionImpulse( float timeStep, idAFBody *body, trace_t &collision ); + bool ApplyCollisions( float timeStep ); + void CheckForCollisions( float timeStep ); + void ClearExternalForce( void ); + void AddGravity( void ); + void SwapStates( void ); + bool TestIfAtRest( float timeStep ); + void Rest( void ); + void AddPushVelocity( const idVec6 &pushVelocity ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_AF_H__ */ diff --git a/src/mpgame/physics/Physics_Actor.cpp b/src/mpgame/physics/Physics_Actor.cpp new file mode 100644 index 0000000..0e4b407 --- /dev/null +++ b/src/mpgame/physics/Physics_Actor.cpp @@ -0,0 +1,390 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Actor ) +END_CLASS + +/* +================ +idPhysics_Actor::idPhysics_Actor +================ +*/ +idPhysics_Actor::idPhysics_Actor( void ) { + clipModel = NULL; + SetClipModelAxis(); + mass = 100.0f; + invMass = 1.0f / mass; + masterEntity = NULL; + masterYaw = 0.0f; + masterDeltaYaw = 0.0f; + groundEntityPtr = NULL; +} + +/* +================ +idPhysics_Actor::~idPhysics_Actor +================ +*/ +idPhysics_Actor::~idPhysics_Actor( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +idPhysics_Actor::Save +================ +*/ +void idPhysics_Actor::Save( idSaveGame *savefile ) const { + + savefile->WriteClipModel( clipModel ); + savefile->WriteMat3( clipModelAxis ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( invMass ); + + savefile->WriteObject( masterEntity ); + savefile->WriteFloat( masterYaw ); + savefile->WriteFloat( masterDeltaYaw ); + + groundEntityPtr.Save( savefile ); +} + +/* +================ +idPhysics_Actor::Restore +================ +*/ +void idPhysics_Actor::Restore( idRestoreGame *savefile ) { + + savefile->ReadClipModel( clipModel ); + savefile->ReadMat3( clipModelAxis ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( invMass ); + + savefile->ReadObject( reinterpret_cast( masterEntity ) ); + savefile->ReadFloat( masterYaw ); + savefile->ReadFloat( masterDeltaYaw ); + + groundEntityPtr.Restore( savefile ); +} + +/* +================ +idPhysics_Actor::SetClipModelAxis +================ +*/ +void idPhysics_Actor::SetClipModelAxis( void ) { + // align clip model to gravity direction + if ( ( gravityNormal[2] == -1.0f ) || ( gravityNormal == vec3_zero ) ) { + clipModelAxis.Identity(); + } + else { + clipModelAxis[2] = -gravityNormal; + clipModelAxis[2].NormalVectors( clipModelAxis[0], clipModelAxis[1] ); + clipModelAxis[1] = -clipModelAxis[1]; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::GetGravityAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetGravityAxis( void ) const { + return clipModelAxis; +} + +/* +================ +idPhysics_Actor::GetMasterDeltaYaw +================ +*/ +float idPhysics_Actor::GetMasterDeltaYaw( void ) const { + return masterDeltaYaw; +} + +/* +================ +idPhysics_Actor::GetGroundEntity +================ +*/ +idEntity *idPhysics_Actor::GetGroundEntity( void ) const { + return groundEntityPtr.GetEntity(); +} + +/* +================ +idPhysics_Actor::SetClipModel +================ +*/ +void idPhysics_Actor::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + assert( self ); + assert( model ); // a clip model is required + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModelAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::GetClipModel +================ +*/ +idClipModel *idPhysics_Actor::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Actor::GetNumClipModels +================ +*/ +int idPhysics_Actor::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_Actor::SetMass +================ +*/ +void idPhysics_Actor::SetMass( float _mass, int id ) { + assert( _mass > 0.0f ); + mass = _mass; + invMass = 1.0f / _mass; +} + +/* +================ +idPhysics_Actor::GetMass +================ +*/ +float idPhysics_Actor::GetMass( int id ) const { + return mass; +} + +/* +================ +idPhysics_Actor::SetContents +================ +*/ +void idPhysics_Actor::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_Actor::GetContents +================ +*/ +int idPhysics_Actor::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_Actor::GetBounds +================ +*/ +const idBounds &idPhysics_Actor::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_Actor::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Actor::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_Actor::IsPushable +================ +*/ +bool idPhysics_Actor::IsPushable( void ) const { + return ( masterEntity == NULL ); +} + +/* +================ +idPhysics_Actor::GetOrigin +================ +*/ +const idVec3 &idPhysics_Actor::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +idPhysics_Player::GetAxis +================ +*/ +const idMat3 &idPhysics_Actor::GetAxis( int id ) const { + return clipModel->GetAxis(); +} + +/* +================ +idPhysics_Actor::SetGravity +================ +*/ +void idPhysics_Actor::SetGravity( const idVec3 &newGravity ) { + if ( newGravity != gravityVector ) { + idPhysics_Base::SetGravity( newGravity ); + SetClipModelAxis(); + } +} + +/* +================ +idPhysics_Actor::ClipTranslation +================ +*/ +void idPhysics_Actor::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_Actor::ClipRotation +================ +*/ +void idPhysics_Actor::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +idPhysics_Actor::ClipContents +================ +*/ +int idPhysics_Actor::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip models + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_Actor::DisableClip +================ +*/ +void idPhysics_Actor::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_Actor::EnableClip +================ +*/ +void idPhysics_Actor::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_Actor::UnlinkClip +================ +*/ +void idPhysics_Actor::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_Actor::LinkClip +================ +*/ +void idPhysics_Actor::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Actor::EvaluateContacts +================ +*/ +bool idPhysics_Actor::EvaluateContacts( void ) { + + // get all the ground contacts + ClearContacts(); + AddGroundContacts( clipModel ); + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} diff --git a/src/mpgame/physics/Physics_Actor.h b/src/mpgame/physics/Physics_Actor.h new file mode 100644 index 0000000..11bf12d --- /dev/null +++ b/src/mpgame/physics/Physics_Actor.h @@ -0,0 +1,112 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_ACTOR_H__ +#define __PHYSICS_ACTOR_H__ + +/* +=================================================================================== + + Actor physics base class + + An actor typically uses one collision model which is aligned with the gravity + direction. The collision model is usually a simple box with the origin at the + bottom center. + +=================================================================================== +*/ + +class idPhysics_Actor : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Actor ); + + idPhysics_Actor( void ); + ~idPhysics_Actor( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // get delta yaw of master + float GetMasterDeltaYaw( void ) const; + // returns the ground entity + idEntity * GetGroundEntity( void ) const; + // align the clip model with the gravity direction + void SetClipModelAxis( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool IsPushable( void ) const; + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); +// RAVEN BEGIN +// abahr: made virtual + virtual const idMat3& GetGravityAxis( void ) const; +// RAVEN END + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + +protected: + idClipModel * clipModel; // clip model used for collision detection + idMat3 clipModelAxis; // axis of clip model aligned with gravity direction + + // derived properties + float mass; + float invMass; + + // master + idEntity * masterEntity; + float masterYaw; + float masterDeltaYaw; + + // results of last evaluate + idEntityPtr groundEntityPtr; +}; + +#endif /* !__PHYSICS_ACTOR_H__ */ diff --git a/src/mpgame/physics/Physics_Base.cpp b/src/mpgame/physics/Physics_Base.cpp new file mode 100644 index 0000000..80e1a9a --- /dev/null +++ b/src/mpgame/physics/Physics_Base.cpp @@ -0,0 +1,895 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Base ) +END_CLASS + +/* +================ +idPhysics_Base::idPhysics_Base +================ +*/ +idPhysics_Base::idPhysics_Base( void ) { + self = NULL; + clipMask = 0; + SetGravity( gameLocal.GetGravity() ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::~idPhysics_Base +================ +*/ +idPhysics_Base::~idPhysics_Base( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + ClearContacts(); +} + +/* +================ +idPhysics_Base::Save +================ +*/ +void idPhysics_Base::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + savefile->WriteInt( clipMask ); + savefile->WriteVec3( gravityVector ); + savefile->WriteVec3( gravityNormal ); + + savefile->WriteInt( contacts.Num() ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->WriteContactInfo( contacts[i] ); + } + + savefile->WriteInt( contactEntities.Num() ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Save( savefile ); + } +} + +/* +================ +idPhysics_Base::Restore +================ +*/ +void idPhysics_Base::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + savefile->ReadInt( clipMask ); + savefile->ReadVec3( gravityVector ); + savefile->ReadVec3( gravityNormal ); + + savefile->ReadInt( num ); + contacts.SetNum( num ); + for ( i = 0; i < contacts.Num(); i++ ) { + savefile->ReadContactInfo( contacts[i] ); + } + + savefile->ReadInt( num ); + contactEntities.SetNum( num ); + for ( i = 0; i < contactEntities.Num(); i++ ) { + contactEntities[i].Restore( savefile ); + } +} + +/* +================ +idPhysics_Base::SetSelf +================ +*/ +void idPhysics_Base::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Base::SetClipModel +================ +*/ +void idPhysics_Base::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { +} + +/* +================ +idPhysics_Base::GetClipModel +================ +*/ +idClipModel *idPhysics_Base::GetClipModel( int id ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetNumClipModels +================ +*/ +int idPhysics_Base::GetNumClipModels( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetMass +================ +*/ +void idPhysics_Base::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Base::GetMass +================ +*/ +float idPhysics_Base::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Base::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Base::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Base::SetContents +================ +*/ +void idPhysics_Base::SetContents( int contents, int id ) { +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +int idPhysics_Base::GetContents( int id ) const { + return 0; +} + +/* +================ +idPhysics_Base::SetClipMask +================ +*/ +void idPhysics_Base::SetClipMask( int mask, int id ) { + clipMask = mask; +} + +/* +================ +idPhysics_Base::GetClipMask +================ +*/ +int idPhysics_Base::GetClipMask( int id ) const { + return clipMask; +} + +/* +================ +idPhysics_Base::GetBounds +================ +*/ +const idBounds &idPhysics_Base::GetBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Base::GetAbsBounds( int id ) const { + return bounds_zero; +} + +/* +================ +idPhysics_Base::Evaluate +================ +*/ +bool idPhysics_Base::Evaluate( int timeStepMSec, int endTimeMSec ) { + return false; +} + +/* +================ +idPhysics_Base::UpdateTime +================ +*/ +void idPhysics_Base::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Base::GetTime +================ +*/ +int idPhysics_Base::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetImpactInfo +================ +*/ +void idPhysics_Base::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Base::ApplyImpulse +================ +*/ +void idPhysics_Base::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Base::AddForce +================ +*/ +void idPhysics_Base::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Base::Activate +================ +*/ +void idPhysics_Base::Activate( void ) { +} + +/* +================ +idPhysics_Base::PutToRest +================ +*/ +void idPhysics_Base::PutToRest( void ) { +} + +/* +================ +idPhysics_Base::IsAtRest +================ +*/ +bool idPhysics_Base::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Base::GetRestStartTime +================ +*/ +int idPhysics_Base::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::IsPushable +================ +*/ +bool idPhysics_Base::IsPushable( void ) const { + return true; +} + +// RAVEN BEGIN +// bdube: water interraction +bool idPhysics_Base::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Base::SaveState +================ +*/ +void idPhysics_Base::SaveState( void ) { +} + +/* +================ +idPhysics_Base::RestoreState +================ +*/ +void idPhysics_Base::RestoreState( void ) { +} + +/* +================ +idPhysics_Base::SetOrigin +================ +*/ +void idPhysics_Base::SetOrigin( const idVec3 &newOrigin, int id ) { +} + +/* +================ +idPhysics_Base::SetAxis +================ +*/ +void idPhysics_Base::SetAxis( const idMat3 &newAxis, int id ) { +} + +/* +================ +idPhysics_Base::Translate +================ +*/ +void idPhysics_Base::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Base::Rotate +================ +*/ +void idPhysics_Base::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Base::GetOrigin +================ +*/ +const idVec3 &idPhysics_Base::GetOrigin( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAxis +================ +*/ +const idMat3 &idPhysics_Base::GetAxis( int id ) const { + return mat3_identity; +} + +/* +================ +idPhysics_Base::SetLinearVelocity +================ +*/ +void idPhysics_Base::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Base::SetAngularVelocity +================ +*/ +void idPhysics_Base::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Base::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetGravity +================ +*/ +void idPhysics_Base::SetGravity( const idVec3 &newGravity ) { + gravityVector = newGravity; + gravityNormal = newGravity; + gravityNormal.Normalize(); +} + +/* +================ +idPhysics_Base::GetGravity +================ +*/ +const idVec3 &idPhysics_Base::GetGravity( void ) const { + return gravityVector; +} + +/* +================ +idPhysics_Base::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Base::GetGravityNormal( void ) const { + return gravityNormal; +} + +/* +================ +idPhysics_Base::ClipTranslation +================ +*/ +void idPhysics_Base::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipRotation +================ +*/ +void idPhysics_Base::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); +} + +/* +================ +idPhysics_Base::ClipContents +================ +*/ +int idPhysics_Base::ClipContents( const idClipModel *model ) const { + return 0; +} + +/* +================ +idPhysics_Base::DisableClip +================ +*/ +void idPhysics_Base::DisableClip( void ) { +} + +/* +================ +idPhysics_Base::EnableClip +================ +*/ +void idPhysics_Base::EnableClip( void ) { +} + +/* +================ +idPhysics_Base::UnlinkClip +================ +*/ +void idPhysics_Base::UnlinkClip( void ) { +} + +/* +================ +idPhysics_Base::LinkClip +================ +*/ +void idPhysics_Base::LinkClip( void ) { +} + +/* +================ +idPhysics_Base::EvaluateContacts +================ +*/ +bool idPhysics_Base::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Base::GetNumContacts +================ +*/ +int idPhysics_Base::GetNumContacts( void ) const { + return contacts.Num(); +} + +/* +================ +idPhysics_Base::GetContact +================ +*/ +const contactInfo_t &idPhysics_Base::GetContact( int num ) const { + return contacts[num]; +} + +/* +================ +idPhysics_Base::ClearContacts +================ +*/ +void idPhysics_Base::ClearContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent ) { + ent->RemoveContactEntity( self ); + } + } + contacts.SetNum( 0, false ); +} + +/* +================ +idPhysics_Base::AddContactEntity +================ +*/ +void idPhysics_Base::AddContactEntity( idEntity *e ) { + int i; + idEntity *ent; + bool found = false; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent == NULL ) { + contactEntities.RemoveIndex( i-- ); + } + if ( ent == e ) { + found = true; + } + } + if ( !found ) { + contactEntities.Alloc() = e; + } +} + +/* +================ +idPhysics_Base::RemoveContactEntity +================ +*/ +void idPhysics_Base::RemoveContactEntity( idEntity *e ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( !ent ) { + contactEntities.RemoveIndex( i-- ); + continue; + } + if ( ent == e ) { + contactEntities.RemoveIndex( i-- ); + return; + } + } +} + +// RAVEN BEGIN +// abahr: +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + normal += GetContact( ix ).normal; + } + + return normal.ToNormal(); +} + +/* +================ +idPhysics_Base::GetContactNormal +================ +*/ +const idVec3 idPhysics_Base::GetGroundContactNormal() const { + idVec3 normal( vec3_zero ); + + for( int ix = 0; ix < GetNumContacts(); ++ix ) { + if ( GetContact(ix).normal * -gravityNormal > 0.0f ) { + normal += GetContact( ix ).normal; + } + } + + return normal.ToNormal(); +} +// RAVEN END + +/* +================ +idPhysics_Base::HasGroundContacts +================ +*/ +bool idPhysics_Base::HasGroundContacts( void ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].normal * -gravityNormal > 0.0f ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundEntity +================ +*/ +bool idPhysics_Base::IsGroundEntity( int entityNum ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::IsGroundClipModel +================ +*/ +bool idPhysics_Base::IsGroundClipModel( int entityNum, int id ) const { + int i; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].entityNum == entityNum && contacts[i].id == id && ( contacts[i].normal * -gravityNormal > 0.0f ) ) { + return true; + } + } + return false; +} + +/* +================ +idPhysics_Base::SetPushed +================ +*/ +void idPhysics_Base::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Base::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Base::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Base::SetMaster +================ +*/ +void idPhysics_Base::SetMaster( idEntity *master, const bool orientated ) { +} + +/* +================ +idPhysics_Base::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Base::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Base::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Base::GetLinearEndTime +================ +*/ +int idPhysics_Base::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::GetAngularEndTime +================ +*/ +int idPhysics_Base::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Base::AddGroundContacts +================ +*/ +void idPhysics_Base::AddGroundContacts( const idClipModel *clipModel ) { + idVec6 dir; + int index, num; + + index = contacts.Num(); + contacts.SetNum( index + 10, false ); + + dir.SubVec3(0) = gravityNormal; + dir.SubVec3(1) = vec3_origin; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[index], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( index + num, false ); +} + +/* +================ +idPhysics_Base::AddContactEntitiesForContacts +================ +*/ +void idPhysics_Base::AddContactEntitiesForContacts( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contacts.Num(); i++ ) { + ent = gameLocal.entities[ contacts[i].entityNum ]; + if ( ent && ent != self ) { + ent->AddContactEntity( self ); + } + } +} + +/* +================ +idPhysics_Base::ActivateContactEntities +================ +*/ +void idPhysics_Base::ActivateContactEntities( void ) { + int i; + idEntity *ent; + + for ( i = 0; i < contactEntities.Num(); i++ ) { + ent = contactEntities[i].GetEntity(); + if ( ent ) { + ent->ActivatePhysics( self ); + } else { + contactEntities.RemoveIndex( i-- ); + } + } +} + +/* +================ +idPhysics_Base::IsOutsideWorld +================ +*/ +bool idPhysics_Base::IsOutsideWorld( void ) const { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( !gameLocal.GetWorldBounds( self ).Expand( 128.0f ).IntersectsBounds( GetAbsBounds() ) ) { +// RAVEN END + return true; + } + return false; +} + +/* +================ +idPhysics_Base::DrawVelocity +================ +*/ +void idPhysics_Base::DrawVelocity( int id, float linearScale, float angularScale ) const { + idVec3 dir, org, vec, start, end; + idMat3 axis; + float length, a; + + dir = GetLinearVelocity( id ); + dir *= linearScale; + if ( dir.LengthSqr() > Square( 0.1f ) ) { + dir.Truncate( 10.0f ); + org = GetOrigin( id ); + gameRenderWorld->DebugArrow( colorRed, org, org + dir, 1 ); + } + + dir = GetAngularVelocity( id ); + length = dir.Normalize(); + length *= angularScale; + if ( length > 0.1f ) { + if ( length < 60.0f ) { + length = 60.0f; + } + else if ( length > 360.0f ) { + length = 360.0f; + } + axis = GetAxis( id ); + vec = axis[2]; + if ( idMath::Fabs( dir * vec ) > 0.99f ) { + vec = axis[0]; + } + vec -= vec * dir * vec; + vec.Normalize(); + vec *= 4.0f; + start = org + vec; + for ( a = 20.0f; a < length; a += 20.0f ) { + end = org + idRotation( vec3_origin, dir, -a ).ToMat3() * vec; + gameRenderWorld->DebugLine( colorBlue, start, end, 1 ); + start = end; + } + end = org + idRotation( vec3_origin, dir, -length ).ToMat3() * vec; + gameRenderWorld->DebugArrow( colorBlue, start, end, 1 ); + } +} + +/* +================ +idPhysics_Base::WriteToSnapshot +================ +*/ +void idPhysics_Base::WriteToSnapshot( idBitMsgDelta &msg ) const { +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Base::ReadFromSnapshot( const idBitMsgDelta &msg ) { +} diff --git a/src/mpgame/physics/Physics_Base.h b/src/mpgame/physics/Physics_Base.h new file mode 100644 index 0000000..2c3bc32 --- /dev/null +++ b/src/mpgame/physics/Physics_Base.h @@ -0,0 +1,183 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_BASE_H__ +#define __PHYSICS_BASE_H__ + +/* +=============================================================================== + + Physics base for a moving object using one or more collision models. + +=============================================================================== +*/ + +// RAVEN BEGIN +// jnewquist: Changed from #define to typedef to fix compiler issues +typedef idEntityPtr contactEntity_t; +// RAVEN END + +class idPhysics_Base : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Base ); + + idPhysics_Base( void ); + ~idPhysics_Base( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: added center mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + +// RAVEN BEGIN +// abahr: helper function used in projectiles + virtual const idVec3 GetContactNormal() const; + virtual const idVec3 GetGroundContactNormal() const; +// RAVEN END + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object + int clipMask; // contents the physics object collides with + idVec3 gravityVector; // direction and magnitude of gravity + idVec3 gravityNormal; // normalized direction of gravity + idList contacts; // contacts with other physics objects + idList contactEntities; // entities touching this physics object + +protected: + // add ground contacts for the clip model + void AddGroundContacts( const idClipModel *clipModel ); + // add contact entity links to contact entities + void AddContactEntitiesForContacts( void ); + // active all contact entities + void ActivateContactEntities( void ); + // returns true if the whole physics object is outside the world bounds + bool IsOutsideWorld( void ) const; + // draw linear and angular velocity + void DrawVelocity( int id, float linearScale, float angularScale ) const; +}; + +#endif /* !__PHYSICS_BASE_H__ */ diff --git a/src/mpgame/physics/Physics_Monster.cpp b/src/mpgame/physics/Physics_Monster.cpp new file mode 100644 index 0000000..168d02a --- /dev/null +++ b/src/mpgame/physics/Physics_Monster.cpp @@ -0,0 +1,861 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Monster ) +END_CLASS + +const float OVERCLIP = 1.001f; + +/* +===================== +idPhysics_Monster::CheckGround +===================== +*/ +void idPhysics_Monster::CheckGround( monsterPState_t &state ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + down = state.origin + gravityNormal * CONTACT_EPSILON; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, groundTrace, state.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + if ( groundTrace.fraction == 1.0f ) { + state.onGround = false; + groundEntityPtr = NULL; + return; + } + + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + if ( ( groundTrace.c.normal * -gravityNormal ) < minFloorCosine ) { + state.onGround = false; + return; + } + + state.onGround = true; + + // let the entity know about the collision + self->Collide( groundTrace, state.velocity ); + + // apply impact to a non world floor entity + if ( groundTrace.c.entityNum != ENTITYNUM_WORLD && groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, 0, groundTrace.c.point, state.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +===================== +idPhysics_Monster::SlideMove +===================== +*/ +monsterMoveResult_t idPhysics_Monster::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + + blockingEntity = NULL; + move = delta; + for( i = 0; i < 3; i++ ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return MM_SLIDING; + } + return MM_OK; + } + + if ( tr.c.entityNum != ENTITYNUM_NONE ) { + blockingEntity = gameLocal.entities[ tr.c.entityNum ]; + } + + // clip the movement delta and velocity + move.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, OVERCLIP ); + } + + return MM_BLOCKED; +} + +/* +===================== +idPhysics_Monster::StepMove + + move start into the delta direction + the velocity is clipped conform any collisions +===================== +*/ +monsterMoveResult_t idPhysics_Monster::StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + trace_t tr; + idVec3 up, down, noStepPos, noStepVel, stepPos, stepVel; + monsterMoveResult_t result1, result2; + float stepdist; + float nostepdist; + + if ( delta == vec3_origin ) { + return MM_OK; + } + + // try to move without stepping up + noStepPos = start; + noStepVel = velocity; + result1 = SlideMove( noStepPos, noStepVel, delta ); + if ( result1 == MM_OK ) { + velocity = noStepVel; +// RAVEN BEGIN +// bdube: dont step when there is no gravity + if ( gravityNormal == vec3_zero || forceDeltaMove ) { +// RAVEN END + start = noStepPos; + return MM_OK; + } + + // try to step down so that we walk down slopes and stairs at a normal rate + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction < 1.0f ) { + start = tr.endpos; + return MM_STEPPED; + } else { + start = noStepPos; + return MM_OK; + } + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( blockingEntity && blockingEntity->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // try to step down in case walking into an actor while going down steps + down = noStepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, noStepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + start = tr.endpos; + velocity = noStepVel; + return MM_BLOCKED; + } + + if ( gravityNormal == vec3_zero ) { + return result1; + } + + // try to step up + up = start - gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, start, up, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + if ( tr.fraction == 0.0f ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // try to move at the stepped up position + stepPos = tr.endpos; + stepVel = velocity; + result2 = SlideMove( stepPos, stepVel, delta ); + if ( result2 == MM_BLOCKED ) { + start = noStepPos; + velocity = noStepVel; + return result1; + } + + // step down again + down = stepPos + gravityNormal * maxStepHeight; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, stepPos, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + stepPos = tr.endpos; + + // if the move is further without stepping up, or the slope is too steap, don't step up + nostepdist = ( noStepPos - start ).LengthSqr(); + stepdist = ( stepPos - start ).LengthSqr(); + if ( ( nostepdist >= stepdist ) || ( ( tr.c.normal * -gravityNormal ) < minFloorCosine ) ) { + start = noStepPos; + velocity = noStepVel; + return MM_SLIDING; + } + + start = stepPos; + velocity = stepVel; + + return MM_STEPPED; +} + +/* +================ +idPhysics_Monster::Activate +================ +*/ +void idPhysics_Monster::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::Rest +================ +*/ +void idPhysics_Monster::Rest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Monster::PutToRest +================ +*/ +void idPhysics_Monster::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_Monster::idPhysics_Monster +================ +*/ +idPhysics_Monster::idPhysics_Monster( void ) { + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + saved = current; + + delta.Zero(); + maxStepHeight = 18.0f; + minFloorCosine = 0.7f; + moveResult = MM_OK; + forceDeltaMove = false; + fly = false; + useVelocityMove = false; + noImpact = false; + blockingEntity = NULL; +} + +/* +================ +idPhysics_Monster_SavePState +================ +*/ +void idPhysics_Monster_SavePState( idSaveGame *savefile, const monsterPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.onGround ); + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Monster_RestorePState +================ +*/ +void idPhysics_Monster_RestorePState( idRestoreGame *savefile, monsterPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.onGround ); + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson: Added unrestored var +} + +/* +================ +idPhysics_Monster::Save +================ +*/ +void idPhysics_Monster::Save( idSaveGame *savefile ) const { + + idPhysics_Monster_SavePState( savefile, current ); + idPhysics_Monster_SavePState( savefile, saved ); + + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( minFloorCosine ); + savefile->WriteVec3( delta ); + + savefile->WriteBool( forceDeltaMove ); + savefile->WriteBool( fly ); + savefile->WriteBool( useVelocityMove ); + savefile->WriteBool( noImpact ); + + savefile->WriteInt( (int)moveResult ); + savefile->WriteObject( blockingEntity ); +} + +/* +================ +idPhysics_Monster::Restore +================ +*/ +void idPhysics_Monster::Restore( idRestoreGame *savefile ) { + + idPhysics_Monster_RestorePState( savefile, current ); + idPhysics_Monster_RestorePState( savefile, saved ); + + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( minFloorCosine ); + savefile->ReadVec3( delta ); + + savefile->ReadBool( forceDeltaMove ); + savefile->ReadBool( fly ); + savefile->ReadBool( useVelocityMove ); + savefile->ReadBool( noImpact ); + + savefile->ReadInt( (int &)moveResult ); + savefile->ReadObject( reinterpret_cast( blockingEntity ) ); +} + +/* +================ +idPhysics_Monster::SetDelta +================ +*/ +void idPhysics_Monster::SetDelta( const idVec3 &d ) { + delta = d; + if ( delta != vec3_origin ) { + Activate(); + } +} + +/* +================ +idPhysics_Monster::SetMaxStepHeight +================ +*/ +void idPhysics_Monster::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Monster::GetMaxStepHeight +================ +*/ +float idPhysics_Monster::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Monster::OnGround +================ +*/ +bool idPhysics_Monster::OnGround( void ) const { + return current.onGround; +} + +/* +================ +idPhysics_Monster::GetSlideMoveEntity +================ +*/ +idEntity *idPhysics_Monster::GetSlideMoveEntity( void ) const { + return blockingEntity; +} + +/* +================ +idPhysics_Monster::GetMoveResult +================ +*/ +monsterMoveResult_t idPhysics_Monster::GetMoveResult( void ) const { + return moveResult; +} + +/* +================ +idPhysics_Monster::ForceDeltaMove +================ +*/ +void idPhysics_Monster::ForceDeltaMove( bool force ) { + forceDeltaMove = force; +} + +/* +================ +idPhysics_Monster::UseFlyMove +================ +*/ +void idPhysics_Monster::UseFlyMove( bool force ) { + fly = force; +} + +/* +================ +idPhysics_Monster::UseVelocityMove +================ +*/ +void idPhysics_Monster::UseVelocityMove( bool force ) { + useVelocityMove = force; +} + +/* +================ +idPhysics_Monster::EnableImpact +================ +*/ +void idPhysics_Monster::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_Monster::DisableImpact +================ +*/ +void idPhysics_Monster::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_Monster::Evaluate +================ +*/ +bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + float timeStep; + + timeStep = MS2SEC( timeStepMSec ); + + moveResult = MM_OK; + blockingEntity = NULL; + oldOrigin = current.origin; + + // if bound to a master + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + current.velocity = ( current.origin - oldOrigin ) / timeStep; + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + // if the monster is at rest + if ( current.atRest >= 0 ) { + return true; + } + + ActivateContactEntities(); + + // move the monster velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + clipModel->Unlink(); + + // check if on the ground + idPhysics_Monster::CheckGround( current ); + + // if not on the ground or moving upwards + float upspeed; + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + if ( fly || ( !forceDeltaMove && ( !current.onGround || upspeed > 1.0f ) ) ) { + if ( upspeed < 0.0f ) { + moveResult = MM_FALLING; + } + else { + current.onGround = false; + moveResult = MM_OK; + } + delta = current.velocity * timeStep; + if ( delta != vec3_origin ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + delta.Zero(); + } + + if ( !fly ) { + current.velocity += gravityVector * timeStep; + } + } else { + if ( useVelocityMove ) { + delta = current.velocity * timeStep; + } else { + current.velocity = delta / timeStep; + } + + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + + if ( delta == vec3_origin ) { + Rest(); + } else { + // try moving into the desired direction +// RAVEN BEGIN +// jshepard: flying creatures, even if not using fly move, shouldn't use step move + if( self->IsType( idAI::GetClassType() ) && ((idAI*)self)->move.moveType == MOVETYPE_FLY ) { + moveResult = idPhysics_Monster::SlideMove( current.origin, current.velocity, delta ); + } else { + moveResult = idPhysics_Monster::StepMove( current.origin, current.velocity, delta ); + } + delta.Zero(); +// RAVEN END + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + // get all the ground contacts + EvaluateContacts(); + + // move the monster velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + Rest(); + } + +// RAVEN BEGIN +// bdube: determine if we moved this frame + return true; +// RAVEN END +} + +/* +================ +idPhysics_Monster::UpdateTime +================ +*/ +void idPhysics_Monster::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Monster::GetTime +================ +*/ +int idPhysics_Monster::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Monster::GetImpactInfo +================ +*/ +void idPhysics_Monster::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Monster::ApplyImpulse +================ +*/ +void idPhysics_Monster::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.velocity += impulse * invMass; + Activate(); +} + +/* +================ +idPhysics_Monster::IsAtRest +================ +*/ +bool idPhysics_Monster::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Monster::GetRestStartTime +================ +*/ +int idPhysics_Monster::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Monster::SaveState +================ +*/ +void idPhysics_Monster::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Monster::RestoreState +================ +*/ +void idPhysics_Monster::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Monster::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Monster::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Translate +================ +*/ +void idPhysics_Monster::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::Rotate +================ +*/ +void idPhysics_Monster::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +idPhysics_Monster::SetLinearVelocity +================ +*/ +void idPhysics_Monster::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; + Activate(); +} + +/* +================ +idPhysics_Monster::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Monster::SetPushed +================ +*/ +void idPhysics_Monster::SetPushed( int deltaTime ) { + // velocity with which the monster is pushed + current.pushVelocity += ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_Monster::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Monster::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Monster::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Monster::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } + else { + if ( masterEntity ) { + masterEntity = NULL; + Activate(); + } + } +} + +const float MONSTER_VELOCITY_MAX = 4000; +const int MONSTER_VELOCITY_TOTAL_BITS = 16; +const int MONSTER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( MONSTER_VELOCITY_MAX ) ) + 1; +const int MONSTER_VELOCITY_MANTISSA_BITS = MONSTER_VELOCITY_TOTAL_BITS - 1 - MONSTER_VELOCITY_EXPONENT_BITS; + +/* +================ +idPhysics_Monster::WriteToSnapshot +================ +*/ +void idPhysics_Monster::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.velocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteFloat( current.velocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + msg.WriteLong( current.atRest ); + msg.WriteBits( current.onGround, 1 ); +} + +/* +================ +idPhysics_Monster::ReadFromSnapshot +================ +*/ +void idPhysics_Monster::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.velocity[0] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[1] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.velocity[2] = msg.ReadFloat( MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, MONSTER_VELOCITY_EXPONENT_BITS, MONSTER_VELOCITY_MANTISSA_BITS ); + current.atRest = msg.ReadLong(); + current.onGround = msg.ReadBits( 1 ) != 0; +} diff --git a/src/mpgame/physics/Physics_Monster.h b/src/mpgame/physics/Physics_Monster.h new file mode 100644 index 0000000..93ac796 --- /dev/null +++ b/src/mpgame/physics/Physics_Monster.h @@ -0,0 +1,152 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_MONSTER_H__ +#define __PHYSICS_MONSTER_H__ + +/* +=================================================================================== + + Monster physics + + Simulates the motion of a monster through the environment. The monster motion + is typically driven by animations. + +=================================================================================== +*/ + +typedef enum { + MM_OK, + MM_SLIDING, + MM_BLOCKED, + MM_STEPPED, + MM_FALLING +} monsterMoveResult_t; + +typedef struct monsterPState_s { + int atRest; + bool onGround; + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; +// RAVEN BEGIN +// bdube: added + idVec3 lastPushVelocity; +// RAVEN END +} monsterPState_t; + +class idPhysics_Monster : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Monster ); + + idPhysics_Monster( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // maximum step up the monster can take, default 18 units + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + // minimum cosine of floor angle to be able to stand on the floor + void SetMinFloorCosine( const float newMinFloorCosine ); + // set delta for next move + void SetDelta( const idVec3 &d ); + // returns true if monster is standing on the ground + bool OnGround( void ) const; + // returns the movement result + monsterMoveResult_t GetMoveResult( void ) const; + // overrides any velocity for pure delta movement + void ForceDeltaMove( bool force ); + // whether velocity should be affected by gravity + void UseFlyMove( bool force ); + // don't use delta movement + void UseVelocityMove( bool force ); + // get entity blocking the move + idEntity * GetSlideMoveEntity( void ) const; + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // monster physics state + monsterPState_t current; + monsterPState_t saved; + + // properties + float maxStepHeight; // maximum step height + float minFloorCosine; // minimum cosine of floor angle + idVec3 delta; // delta for next move + + bool forceDeltaMove; + bool fly; + bool useVelocityMove; + bool noImpact; // if true do not activate when another object collides + + // results of last evaluate + monsterMoveResult_t moveResult; + idEntity * blockingEntity; + +private: + void CheckGround( monsterPState_t &state ); + monsterMoveResult_t SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + monsterMoveResult_t StepMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ); + void Rest( void ); +}; + +#endif /* !__PHYSICS_MONSTER_H__ */ diff --git a/src/mpgame/physics/Physics_Parametric.cpp b/src/mpgame/physics/Physics_Parametric.cpp new file mode 100644 index 0000000..926bdf5 --- /dev/null +++ b/src/mpgame/physics/Physics_Parametric.cpp @@ -0,0 +1,1214 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_Parametric ) +END_CLASS + + +/* +================ +idPhysics_Parametric::Activate +================ +*/ +void idPhysics_Parametric::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::TestIfAtRest +================ +*/ +bool idPhysics_Parametric::TestIfAtRest( void ) const { + + if ( ( current.linearExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + ( current.angularExtrapolation.GetExtrapolationType() & ~EXTRAPOLATION_NOSTOP ) == EXTRAPOLATION_NONE && + current.linearInterpolation.GetDuration() == 0 && + current.angularInterpolation.GetDuration() == 0 && + current.spline == NULL ) { + return true; + } + + if ( !current.linearExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularExtrapolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.linearInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( !current.angularInterpolation.IsDone( current.time ) ) { + return false; + } + + if ( current.spline != NULL && !current.spline->IsDone( current.time ) ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_Parametric::Rest +================ +*/ +void idPhysics_Parametric::Rest( void ) { + current.atRest = gameLocal.time; + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_Parametric::idPhysics_Parametric +================ +*/ +idPhysics_Parametric::idPhysics_Parametric( void ) { + + current.time = gameLocal.time; + current.atRest = -1; + current.useSplineAngles = false; + current.origin.Zero(); + current.angles.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAngles.Zero(); + current.linearExtrapolation.Init( 0, 0, vec3_zero, vec3_zero, vec3_zero, EXTRAPOLATION_NONE ); + current.angularExtrapolation.Init( 0, 0, ang_zero, ang_zero, ang_zero, EXTRAPOLATION_NONE ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + current.spline = NULL; + current.splineInterpolate.Init( 0, 1, 1, 2, 0, 0 ); + + saved = current; + + isPusher = false; + pushFlags = 0; + clipModel = NULL; + isBlocked = false; + memset( &pushResults, 0, sizeof( pushResults ) ); + + hasMaster = false; + isOrientated = false; + +// RAVEN BEGIN +// abahr: + useAxisOffset = false; + axisOffset.Identity(); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::~idPhysics_Parametric +================ +*/ +idPhysics_Parametric::~idPhysics_Parametric( void ) { + if ( clipModel != NULL ) { + delete clipModel; + clipModel = NULL; + } + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } +} + +/* +================ +idPhysics_Parametric_SavePState +================ +*/ +void idPhysics_Parametric_SavePState( idSaveGame *savefile, const parametricPState_t &state ) { + savefile->WriteInt( state.time ); + savefile->WriteInt( state.atRest ); + savefile->WriteBool( state.useSplineAngles ); + savefile->WriteVec3( state.origin ); + savefile->WriteAngles( state.angles ); + savefile->WriteMat3( state.axis ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteAngles( state.localAngles ); + + savefile->WriteInt( (int)state.linearExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.linearExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.linearExtrapolation.GetDuration() ); + savefile->WriteVec3( state.linearExtrapolation.GetStartValue() ); + savefile->WriteVec3( state.linearExtrapolation.GetBaseSpeed() ); + savefile->WriteVec3( state.linearExtrapolation.GetSpeed() ); + + savefile->WriteInt( (int)state.angularExtrapolation.GetExtrapolationType() ); + savefile->WriteFloat( state.angularExtrapolation.GetStartTime() ); + savefile->WriteFloat( state.angularExtrapolation.GetDuration() ); + savefile->WriteAngles( state.angularExtrapolation.GetStartValue() ); + savefile->WriteAngles( state.angularExtrapolation.GetBaseSpeed() ); + savefile->WriteAngles( state.angularExtrapolation.GetSpeed() ); + + savefile->WriteFloat( state.linearInterpolation.GetStartTime() ); + savefile->WriteFloat( state.linearInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.linearInterpolation.GetDuration() ); + savefile->WriteVec3( state.linearInterpolation.GetStartValue() ); + savefile->WriteVec3( state.linearInterpolation.GetEndValue() ); + + savefile->WriteFloat( state.angularInterpolation.GetStartTime() ); + savefile->WriteFloat( state.angularInterpolation.GetAcceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDeceleration() ); + savefile->WriteFloat( state.angularInterpolation.GetDuration() ); + savefile->WriteAngles( state.angularInterpolation.GetStartValue() ); + savefile->WriteAngles( state.angularInterpolation.GetEndValue() ); + + // spline is handled by owner + + savefile->WriteFloat( state.splineInterpolate.GetStartTime() ); + savefile->WriteFloat( state.splineInterpolate.GetAcceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetDuration() ); + savefile->WriteFloat( state.splineInterpolate.GetDeceleration() ); + savefile->WriteFloat( state.splineInterpolate.GetStartValue() ); + savefile->WriteFloat( state.splineInterpolate.GetEndValue() ); +} + +/* +================ +idPhysics_Parametric_RestorePState +================ +*/ +void idPhysics_Parametric_RestorePState( idRestoreGame *savefile, parametricPState_t &state ) { + extrapolation_t etype; + float startTime, duration, accelTime, decelTime, startValue, endValue; + idVec3 linearStartValue, linearBaseSpeed, linearSpeed, startPos, endPos; + idAngles angularStartValue, angularBaseSpeed, angularSpeed, startAng, endAng; + + savefile->ReadInt( state.time ); + savefile->ReadInt( state.atRest ); + savefile->ReadBool( state.useSplineAngles ); + savefile->ReadVec3( state.origin ); + savefile->ReadAngles( state.angles ); + savefile->ReadMat3( state.axis ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadAngles( state.localAngles ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( linearStartValue ); + savefile->ReadVec3( linearBaseSpeed ); + savefile->ReadVec3( linearSpeed ); + + state.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, etype ); + + savefile->ReadInt( (int &)etype ); + savefile->ReadFloat( startTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( angularStartValue ); + savefile->ReadAngles( angularBaseSpeed ); + savefile->ReadAngles( angularSpeed ); + + state.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, etype ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadVec3( startPos ); + savefile->ReadVec3( endPos ); + + state.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( duration ); + savefile->ReadAngles( startAng ); + savefile->ReadAngles( endAng ); + + state.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + // spline is handled by owner + + savefile->ReadFloat( startTime ); + savefile->ReadFloat( accelTime ); + savefile->ReadFloat( duration ); + savefile->ReadFloat( decelTime ); + savefile->ReadFloat( startValue ); + savefile->ReadFloat( endValue ); + + state.splineInterpolate.Init( startTime, accelTime, decelTime, duration, startValue, endValue ); +} + +/* +================ +idPhysics_Parametric::Save +================ +*/ +void idPhysics_Parametric::Save( idSaveGame *savefile ) const { + + idPhysics_Parametric_SavePState( savefile, current ); + idPhysics_Parametric_SavePState( savefile, saved ); + + savefile->WriteBool( isPusher ); + savefile->WriteClipModel( clipModel ); + savefile->WriteInt( pushFlags ); + + savefile->WriteTrace( pushResults ); + savefile->WriteBool( isBlocked ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); + + savefile->WriteBool ( useAxisOffset ); // cnicholson: Added unsaved var + savefile->WriteMat3 ( axisOffset ); // cnicholson: Added unsaved var +} + +/* +================ +idPhysics_Parametric::Restore +================ +*/ +void idPhysics_Parametric::Restore( idRestoreGame *savefile ) { + + idPhysics_Parametric_RestorePState( savefile, current ); + idPhysics_Parametric_RestorePState( savefile, saved ); + + savefile->ReadBool( isPusher ); + savefile->ReadClipModel( clipModel ); + savefile->ReadInt( pushFlags ); + + savefile->ReadTrace( pushResults ); + savefile->ReadBool( isBlocked ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); + + savefile->ReadBool ( useAxisOffset ); // cnicholson: Added unrestored var + savefile->ReadMat3 ( axisOffset ); // cnicholson: Added unrestored var + +} + +/* +================ +idPhysics_Parametric::SetPusher +================ +*/ +void idPhysics_Parametric::SetPusher( int flags ) { + assert( clipModel ); + isPusher = true; + pushFlags = flags; +} + +/* +================ +idPhysics_Parametric::IsPusher +================ +*/ +bool idPhysics_Parametric::IsPusher( void ) const { + return isPusher; +} + +/* +================ +idPhysics_Parametric::SetLinearExtrapolation +================ +*/ +void idPhysics_Parametric::SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ) { + current.time = gameLocal.time; + current.linearExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localOrigin = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularExtrapolation +================ +*/ +void idPhysics_Parametric::SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ) { + current.time = gameLocal.time; + current.angularExtrapolation.Init( time, duration, base, baseSpeed, speed, type ); + current.localAngles = base; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetLinearExtrapolationType( void ) const { + return current.linearExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::GetAngularExtrapolationType +================ +*/ +extrapolation_t idPhysics_Parametric::GetAngularExtrapolationType( void ) const { + return current.angularExtrapolation.GetExtrapolationType(); +} + +/* +================ +idPhysics_Parametric::SetLinearInterpolation +================ +*/ +void idPhysics_Parametric::SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ) { + current.time = gameLocal.time; + current.linearInterpolation.Init( time, accelTime, decelTime, duration, startPos, endPos ); + current.localOrigin = startPos; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularInterpolation +================ +*/ +void idPhysics_Parametric::SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ) { + current.time = gameLocal.time; + current.angularInterpolation.Init( time, accelTime, decelTime, duration, startAng, endAng ); + current.localAngles = startAng; + Activate(); +} + +/* +================ +idPhysics_Parametric::SetSpline +================ +*/ +void idPhysics_Parametric::SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ) { + if ( current.spline != NULL ) { + delete current.spline; + current.spline = NULL; + } + current.spline = spline; + if ( current.spline != NULL ) { + float startTime = current.spline->GetTime( 0 ); + float endTime = current.spline->GetTime( current.spline->GetNumValues() - 1 ); + float length = current.spline->GetLengthForTime( endTime ); + current.splineInterpolate.Init( startTime, accelTime, decelTime, endTime - startTime, 0.0f, length ); + } + current.useSplineAngles = useSplineAngles; + Activate(); +} + +/* +================ +idPhysics_Parametric::GetSpline +================ +*/ +idCurve_Spline *idPhysics_Parametric::GetSpline( void ) const { + return current.spline; +} + +/* +================ +idPhysics_Parametric::GetSplineAcceleration +================ +*/ +int idPhysics_Parametric::GetSplineAcceleration( void ) const { + return current.splineInterpolate.GetAcceleration(); +} + +/* +================ +idPhysics_Parametric::GetSplineDeceleration +================ +*/ +int idPhysics_Parametric::GetSplineDeceleration( void ) const { + return current.splineInterpolate.GetDeceleration(); +} + +/* +================ +idPhysics_Parametric::UsingSplineAngles +================ +*/ +bool idPhysics_Parametric::UsingSplineAngles( void ) const { + return current.useSplineAngles; +} + +/* +================ +idPhysics_Parametric::GetLocalOrigin +================ +*/ +void idPhysics_Parametric::GetLocalOrigin( idVec3 &curOrigin ) const { + curOrigin = current.localOrigin; +} + +/* +================ +idPhysics_Parametric::GetLocalAngles +================ +*/ +void idPhysics_Parametric::GetLocalAngles( idAngles &curAngles ) const { + curAngles = current.localAngles; +} + +/* +================ +idPhysics_Parametric::SetClipModel +================ +*/ +void idPhysics_Parametric::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END +} + +/* +================ +idPhysics_Parametric::GetClipModel +================ +*/ +idClipModel *idPhysics_Parametric::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_Parametric::GetNumClipModels +================ +*/ +int idPhysics_Parametric::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Parametric::SetMass +================ +*/ +void idPhysics_Parametric::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Parametric::GetMass +================ +*/ +float idPhysics_Parametric::GetMass( int id ) const { + return 0.0f; +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +void idPhysics_Parametric::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Parametric::SetClipMask +================ +*/ +int idPhysics_Parametric::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Parametric::GetBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return idPhysics_Base::GetBounds(); +} + +/* +================ +idPhysics_Parametric::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Parametric::GetAbsBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + return idPhysics_Base::GetAbsBounds(); +} + +/* +================ +idPhysics_Parametric::Evaluate +================ +*/ +bool idPhysics_Parametric::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 oldLocalOrigin, oldOrigin, masterOrigin; + idAngles oldLocalAngles, oldAngles; + idMat3 oldAxis, masterAxis; + + isBlocked = false; + oldLocalOrigin = current.localOrigin; + oldOrigin = current.origin; + oldLocalAngles = current.localAngles; + oldAngles = current.angles; + oldAxis = current.axis; + + current.localOrigin.Zero(); + current.localAngles.Zero(); + + if ( current.spline != NULL ) { + float length = current.splineInterpolate.GetCurrentValue( endTimeMSec ); + float t = current.spline->GetTimeForLength( length, 0.01f ); + current.localOrigin = current.spline->GetCurrentValue( t ); + if ( current.useSplineAngles ) { + current.localAngles = current.spline->GetCurrentFirstDerivative( t ).ToAngles(); + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + current.localOrigin += current.linearInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localOrigin += current.linearExtrapolation.GetCurrentValue( endTimeMSec ); + } + + if ( current.angularInterpolation.GetDuration() != 0 ) { + current.localAngles += current.angularInterpolation.GetCurrentValue( endTimeMSec ); + } else { + current.localAngles += current.angularExtrapolation.GetCurrentValue( endTimeMSec ); + } + + current.localAngles.Normalize360(); + current.origin = current.localOrigin; + current.angles = current.localAngles; + current.axis = current.localAngles.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + if ( masterAxis.IsRotated() ) { + current.origin = current.origin * masterAxis + masterOrigin; + if ( isOrientated ) { + current.axis *= masterAxis; + current.angles = current.axis.ToAngles(); + } + } + else { + current.origin += masterOrigin; + } + } + + if ( isPusher ) { + + gameLocal.push.ClipPush( pushResults, self, pushFlags, oldOrigin, oldAxis, current.origin, current.axis ); + if ( pushResults.fraction < 1.0f ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, oldOrigin, oldAxis ); +// RAVEN END + current.localOrigin = oldLocalOrigin; + current.origin = oldOrigin; + current.localAngles = oldLocalAngles; + current.angles = oldAngles; + current.axis = oldAxis; + isBlocked = true; + return false; + } + + current.angles = current.axis.ToAngles(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// abahr: a hack way of hiding gimble lock from movers. + clipModel->Link( self, 0, current.origin, UseAxisOffset() ? GetAxisOffset() * current.axis : current.axis ); +// RAVEN END + } + + current.time = endTimeMSec; + + if ( TestIfAtRest() ) { + Rest(); + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); +} + +/* +================ +idPhysics_Parametric::UpdateTime +================ +*/ +void idPhysics_Parametric::UpdateTime( int endTimeMSec ) { + int timeLeap = endTimeMSec - current.time; + + current.time = endTimeMSec; + // move the trajectory start times to sync the trajectory with the current endTime + current.linearExtrapolation.SetStartTime( current.linearExtrapolation.GetStartTime() + timeLeap ); + current.angularExtrapolation.SetStartTime( current.angularExtrapolation.GetStartTime() + timeLeap ); + current.linearInterpolation.SetStartTime( current.linearInterpolation.GetStartTime() + timeLeap ); + current.angularInterpolation.SetStartTime( current.angularInterpolation.GetStartTime() + timeLeap ); + if ( current.spline != NULL ) { + current.spline->ShiftTime( timeLeap ); + current.splineInterpolate.SetStartTime( current.splineInterpolate.GetStartTime() + timeLeap ); + } +} + +/* +================ +idPhysics_Parametric::GetTime +================ +*/ +int idPhysics_Parametric::GetTime( void ) const { + return current.time; +} + +/* +================ +idPhysics_Parametric::IsAtRest +================ +*/ +bool idPhysics_Parametric::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_Parametric::GetRestStartTime +================ +*/ +int idPhysics_Parametric::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_Parametric::IsPushable +================ +*/ +bool idPhysics_Parametric::IsPushable( void ) const { + return false; +} + +/* +================ +idPhysics_Parametric::SaveState +================ +*/ +void idPhysics_Parametric::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Parametric::RestoreState +================ +*/ +void idPhysics_Parametric::RestoreState( void ) { + + current = saved; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::SetOrigin +================ +*/ +void idPhysics_Parametric::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.linearExtrapolation.SetStartValue( newOrigin ); + current.linearInterpolation.SetStartValue( newOrigin ); + + current.localOrigin = current.linearExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + } + else { + current.origin = current.localOrigin; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAxis +================ +*/ +void idPhysics_Parametric::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAngles = newAxis.ToAngles(); + + current.angularExtrapolation.SetStartValue( current.localAngles ); + current.angularInterpolation.SetStartValue( current.localAngles ); + + current.localAngles = current.angularExtrapolation.GetCurrentValue( current.time ); + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = current.localAngles.ToMat3() * masterAxis; + current.angles = current.axis.ToAngles(); + } + else { + current.axis = current.localAngles.ToMat3(); + current.angles = current.localAngles; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + Activate(); +} + +/* +================ +idPhysics_Parametric::Move +================ +*/ +void idPhysics_Parametric::Translate( const idVec3 &translation, int id ) { +} + +/* +================ +idPhysics_Parametric::Rotate +================ +*/ +void idPhysics_Parametric::Rotate( const idRotation &rotation, int id ) { +} + +/* +================ +idPhysics_Parametric::GetOrigin +================ +*/ +const idVec3 &idPhysics_Parametric::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Parametric::GetAxis +================ +*/ +const idMat3 &idPhysics_Parametric::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Parametric::GetAngles +================ +*/ +void idPhysics_Parametric::GetAngles( idAngles &curAngles ) const { + curAngles = current.angles; +} + +/* +================ +idPhysics_Parametric::SetLinearVelocity +================ +*/ +void idPhysics_Parametric::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + SetLinearExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.origin, newLinearVelocity, vec3_origin ); + current.linearInterpolation.Init( 0, 0, 0, 0, vec3_zero, vec3_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::SetAngularVelocity +================ +*/ +void idPhysics_Parametric::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + idRotation rotation; + idVec3 vec; + float angle; + + vec = newAngularVelocity; + angle = vec.Normalize(); + rotation.Set( vec3_origin, vec, (float) RAD2DEG( angle ) ); + + SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, current.angles, rotation.ToAngles(), ang_zero ); + current.angularInterpolation.Init( 0, 0, 0, 0, ang_zero, ang_zero ); + Activate(); +} + +/* +================ +idPhysics_Parametric::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + + curLinearVelocity = current.linearExtrapolation.GetCurrentSpeed( gameLocal.time ); + return curLinearVelocity; +} + +/* +================ +idPhysics_Parametric::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Parametric::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idAngles angles; + + angles = current.angularExtrapolation.GetCurrentSpeed( gameLocal.time ); + curAngularVelocity = angles.ToAngularVelocity(); + return curAngularVelocity; +} + +/* +================ +idPhysics_Parametric::DisableClip +================ +*/ +void idPhysics_Parametric::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Parametric::EnableClip +================ +*/ +void idPhysics_Parametric::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Parametric::UnlinkClip +================ +*/ +void idPhysics_Parametric::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Parametric::LinkClip +================ +*/ +void idPhysics_Parametric::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Parametric::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Parametric::GetBlockingInfo( void ) const { + return ( isBlocked ? &pushResults : NULL ); +} + +/* +================ +idPhysics_Parametric::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Parametric::GetBlockingEntity( void ) const { + if ( isBlocked ) { + return gameLocal.entities[ pushResults.c.entityNum ]; + } + return NULL; +} + +/* +================ +idPhysics_Parametric::SetMaster +================ +*/ +void idPhysics_Parametric::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAngles = ( current.axis * masterAxis.Transpose() ).ToAngles(); + } + else { + current.localAngles = current.axis.ToAngles(); + } + + current.linearExtrapolation.SetStartValue( current.localOrigin ); + current.angularExtrapolation.SetStartValue( current.localAngles ); + hasMaster = true; + isOrientated = orientated; + } + } + else { + if ( hasMaster ) { + // transform from master space to world space + current.localOrigin = current.origin; + current.localAngles = current.angles; + SetLinearExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.origin, vec3_origin, vec3_origin ); + SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, current.angles, ang_zero, ang_zero ); + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Parametric::GetLinearEndTime +================ +*/ +int idPhysics_Parametric::GetLinearEndTime( void ) const { + if ( current.spline != NULL ) { + if ( current.spline->GetBoundaryType() != idCurve_Spline::BT_CLOSED ) { + return current.spline->GetTime( current.spline->GetNumValues() - 1 ); + } else { + return 0; + } + } else if ( current.linearInterpolation.GetDuration() != 0 ) { + return current.linearInterpolation.GetEndTime(); + } else { + return current.linearExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::GetAngularEndTime +================ +*/ +int idPhysics_Parametric::GetAngularEndTime( void ) const { + if ( current.angularInterpolation.GetDuration() != 0 ) { + return current.angularInterpolation.GetEndTime(); + } else { + return current.angularExtrapolation.GetEndTime(); + } +} + +/* +================ +idPhysics_Parametric::WriteToSnapshot +================ +*/ +void idPhysics_Parametric::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.time ); + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( current.angles[0] ); + msg.WriteFloat( current.angles[1] ); + msg.WriteFloat( current.angles[2] ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( current.angles[0], current.localAngles[0] ); + msg.WriteDeltaFloat( current.angles[1], current.localAngles[1] ); + msg.WriteDeltaFloat( current.angles[2], current.localAngles[2] ); + + msg.WriteBits( current.linearExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteBits( current.angularExtrapolation.GetExtrapolationType(), 8 ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetSpeed()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularExtrapolation.GetBaseSpeed()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.linearInterpolation.GetEndValue()[2] ); + + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartTime() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetAcceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDeceleration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetDuration() ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetStartValue()[2] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[0] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[1] ); + msg.WriteDeltaFloat( 0.0f, current.angularInterpolation.GetEndValue()[2] ); +} + +/* +================ +idPhysics_Parametric::ReadFromSnapshot +================ +*/ +void idPhysics_Parametric::ReadFromSnapshot( const idBitMsgDelta &msg ) { + extrapolation_t linearType, angularType; + float startTime, duration, accelTime, decelTime; + idVec3 linearStartValue, linearSpeed, linearBaseSpeed, startPos, endPos; + idAngles angularStartValue, angularSpeed, angularBaseSpeed, startAng, endAng; + + current.time = msg.ReadLong(); + current.atRest = msg.ReadLong(); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + current.angles[0] = msg.ReadFloat(); + current.angles[1] = msg.ReadFloat(); + current.angles[2] = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAngles[0] = msg.ReadDeltaFloat( current.angles[0] ); + current.localAngles[1] = msg.ReadDeltaFloat( current.angles[1] ); + current.localAngles[2] = msg.ReadDeltaFloat( current.angles[2] ); + + linearType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + linearStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + linearBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearExtrapolation.Init( startTime, duration, linearStartValue, linearBaseSpeed, linearSpeed, linearType ); + + angularType = (extrapolation_t) msg.ReadBits( 8 ); + startTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[0] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[1] = msg.ReadDeltaFloat( 0.0f ); + angularStartValue[2] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[0] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[1] = msg.ReadDeltaFloat( 0.0f ); + angularBaseSpeed[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularExtrapolation.Init( startTime, duration, angularStartValue, angularBaseSpeed, angularSpeed, angularType ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startPos[0] = msg.ReadDeltaFloat( 0.0f ); + startPos[1] = msg.ReadDeltaFloat( 0.0f ); + startPos[2] = msg.ReadDeltaFloat( 0.0f ); + endPos[0] = msg.ReadDeltaFloat( 0.0f ); + endPos[1] = msg.ReadDeltaFloat( 0.0f ); + endPos[2] = msg.ReadDeltaFloat( 0.0f ); + current.linearInterpolation.Init( startTime, accelTime, decelTime, duration, startPos, endPos ); + + startTime = msg.ReadDeltaFloat( 0.0f ); + accelTime = msg.ReadDeltaFloat( 0.0f ); + decelTime = msg.ReadDeltaFloat( 0.0f ); + duration = msg.ReadDeltaFloat( 0.0f ); + startAng[0] = msg.ReadDeltaFloat( 0.0f ); + startAng[1] = msg.ReadDeltaFloat( 0.0f ); + startAng[2] = msg.ReadDeltaFloat( 0.0f ); + endAng[0] = msg.ReadDeltaFloat( 0.0f ); + endAng[1] = msg.ReadDeltaFloat( 0.0f ); + endAng[2] = msg.ReadDeltaFloat( 0.0f ); + current.angularInterpolation.Init( startTime, accelTime, decelTime, duration, startAng, endAng ); + + current.axis = current.angles.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} diff --git a/src/mpgame/physics/Physics_Parametric.h b/src/mpgame/physics/Physics_Parametric.h new file mode 100644 index 0000000..68fe9a7 --- /dev/null +++ b/src/mpgame/physics/Physics_Parametric.h @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_PARAMETRIC_H__ +#define __PHYSICS_PARAMETRIC_H__ + +/* +=================================================================================== + + Parametric physics + + Used for predefined or scripted motion. The motion of an object is completely + parametrized. By adjusting the parameters an object is forced to follow a + predefined path. The parametric physics is typically used for doors, bridges, + rotating fans etc. + +=================================================================================== +*/ + +typedef struct parametricPState_s { + int time; // physics time + int atRest; // set when simulation is suspended + idVec3 origin; // world origin + idAngles angles; // world angles + idMat3 axis; // world axis + idVec3 localOrigin; // local origin + idAngles localAngles; // local angles + idExtrapolate linearExtrapolation; // extrapolation based description of the position over time + idExtrapolate angularExtrapolation; // extrapolation based description of the orientation over time + idInterpolateAccelDecelLinear linearInterpolation; // interpolation based description of the position over time + idInterpolateAccelDecelLinear angularInterpolation; // interpolation based description of the orientation over time + idCurve_Spline * spline; // spline based description of the position over time + idInterpolateAccelDecelLinear splineInterpolate; // position along the spline over time + bool useSplineAngles; // set the orientation using the spline +} parametricPState_t; + +class idPhysics_Parametric : public idPhysics_Base { + +public: + CLASS_PROTOTYPE( idPhysics_Parametric ); + + idPhysics_Parametric( void ); + ~idPhysics_Parametric( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void SetPusher( int flags ); + bool IsPusher( void ) const; + + void SetLinearExtrapolation( extrapolation_t type, int time, int duration, const idVec3 &base, const idVec3 &speed, const idVec3 &baseSpeed ); + void SetAngularExtrapolation( extrapolation_t type, int time, int duration, const idAngles &base, const idAngles &speed, const idAngles &baseSpeed ); + extrapolation_t GetLinearExtrapolationType( void ) const; + extrapolation_t GetAngularExtrapolationType( void ) const; + + void SetLinearInterpolation( int time, int accelTime, int decelTime, int duration, const idVec3 &startPos, const idVec3 &endPos ); + void SetAngularInterpolation( int time, int accelTime, int decelTime, int duration, const idAngles &startAng, const idAngles &endAng ); + + void SetSpline( idCurve_Spline *spline, int accelTime, int decelTime, bool useSplineAngles ); + idCurve_Spline *GetSpline( void ) const; + int GetSplineAcceleration( void ) const; + int GetSplineDeceleration( void ) const; + bool UsingSplineAngles( void ) const; + + void GetLocalOrigin( idVec3 &curOrigin ) const; + void GetLocalAngles( idAngles &curAngles ) const; + + void GetAngles( idAngles &curAngles ) const; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + void SetAxisOffset( const idMat3& offset ) { axisOffset = offset; useAxisOffset = true; } + const idMat3& GetAxisOffset() const { return axisOffset; } + idMat3& GetAxisOffset() { return axisOffset; } + bool UseAxisOffset() const { return useAxisOffset; } +// RAVEN END + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +private: + // parametric physics state + parametricPState_t current; + parametricPState_t saved; + + // pusher + bool isPusher; + idClipModel * clipModel; + int pushFlags; + + // results of last evaluate + trace_t pushResults; + bool isBlocked; + + // master + bool hasMaster; + bool isOrientated; + +// RAVEN BEGIN +// abahr: a method for hiding gimblelock + bool useAxisOffset; + idMat3 axisOffset; +// RAVEN END + +private: + bool TestIfAtRest( void ) const; + void Rest( void ); +}; + +#endif /* !__PHYSICS_PARAMETRIC_H__ */ diff --git a/src/mpgame/physics/Physics_Particle.cpp b/src/mpgame/physics/Physics_Particle.cpp new file mode 100644 index 0000000..16d7d03 --- /dev/null +++ b/src/mpgame/physics/Physics_Particle.cpp @@ -0,0 +1,1051 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" + +CLASS_DECLARATION( idPhysics_Base, rvPhysics_Particle ) +END_CLASS + +const float PRT_OVERCLIP = 1.001f; +const float PRT_BOUNCESTOP = 10.0f; + +/* +================ +rvPhysics_Particle::DropToFloorAndRest + +Drops the object straight down to the floor +================ +*/ +void rvPhysics_Particle::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + testSolid = false; + if ( gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ) ) { + gameLocal.DWarning( "entity in solid '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.origin + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + current.origin = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, clipModel->GetAxis() ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + PutToRest(); + EvaluateContacts();//Do a final contact check. Items that drop to floor never do this check otherwise + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "entity outside world bounds '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.origin.ToString(0) ); + PutToRest(); + dropToFloor = false; + } +} + +/* +================ +rvPhysics_Particle::DebugDraw +================ +*/ +void rvPhysics_Particle::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +} + +/* +================ +rvPhysics_Particle::rvPhysics_Particle +================ +*/ +rvPhysics_Particle::rvPhysics_Particle( void ) { + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f, true ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + current.atRest = -1; + current.origin.Zero(); + saved = current; + + dropToFloor = false; + testSolid = false; + hasMaster = false; + + SetFriction( 0.6f, 0.6f, 0.0f ); + SetBouncyness ( 0.5f, true ); + + gravityNormal.Zero(); +} + +/* +================ +rvPhysics_Particle::~rvPhysics_Particle +================ +*/ +rvPhysics_Particle::~rvPhysics_Particle( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } +} + +/* +================ +rvPhysics_Particle::Save +================ +*/ +void rvPhysics_Particle::Save( idSaveGame *savefile ) const { + savefile->WriteInt( current.atRest ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteVec3( current.pushVelocity ); + savefile->WriteVec3( current.origin ); + savefile->WriteVec3( current.velocity ); + savefile->WriteBool( current.onGround ); + savefile->WriteBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->WriteInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->WriteVec3( saved.localOrigin ); + savefile->WriteMat3( saved.localAxis ); + savefile->WriteVec3( saved.pushVelocity ); + savefile->WriteVec3( saved.origin ); + savefile->WriteVec3( saved.velocity ); + savefile->WriteBool( saved.onGround ); + savefile->WriteBool( saved.inWater ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteBool ( allowBounce ); + savefile->WriteBounds ( clipModel->GetBounds ( ) ); // cnicholson: Added unsaved var + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); // cnicholson: Added unsaved var + extraPassEntity.Save( savefile ); + + savefile->WriteClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::Restore +================ +*/ +void rvPhysics_Particle::Restore( idRestoreGame *savefile ) { + savefile->ReadInt( current.atRest ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadVec3( current.pushVelocity ); + savefile->ReadVec3( current.origin ); + savefile->ReadVec3( current.velocity ); + savefile->ReadBool( current.onGround ); + savefile->ReadBool( current.inWater ); // cnicholson: Added unsaved var + + savefile->ReadInt( saved.atRest ); // cnicholson: Added unsaved vars + savefile->ReadVec3( saved.localOrigin ); + savefile->ReadMat3( saved.localAxis ); + savefile->ReadVec3( saved.pushVelocity ); + savefile->ReadVec3( saved.origin ); + savefile->ReadVec3( saved.velocity ); + savefile->ReadBool( saved.onGround ); + savefile->ReadBool( saved.inWater ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadBool ( allowBounce ); + + idBounds bounds; // cnicholson: Added unrestored var + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); // cnicholson: Added unrestored var + extraPassEntity.Restore( savefile ); + + savefile->ReadClipModel( clipModel ); +} + +/* +================ +rvPhysics_Particle::SetClipModel +================ +*/ +void rvPhysics_Particle::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::GetClipModel +================ +*/ +idClipModel *rvPhysics_Particle::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +rvPhysics_Particle::GetNumClipModels +================ +*/ +int rvPhysics_Particle::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +rvPhysics_Particle::SetBouncyness +================ +*/ +void rvPhysics_Particle::SetBouncyness( const float b, bool _allowBounce ) { + allowBounce = _allowBounce; + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +rvPhysics_Particle::SetFriction +================ +*/ +void rvPhysics_Particle::SetFriction( const float linear, const float angular, const float contact ) { + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + + +/* +================ +rvPhysics_Particle::PutToRest +================ +*/ +void rvPhysics_Particle::PutToRest( void ) { + current.atRest = gameLocal.time; + current.velocity.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::DropToFloor +================ +*/ +void rvPhysics_Particle::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +rvPhysics_Particle::Activate +================ +*/ +void rvPhysics_Particle::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +rvPhysics_Particle::EvaluateContacts +================ +*/ +bool rvPhysics_Particle::EvaluateContacts( void ) { + ClearContacts(); + AddGroundContacts( clipModel ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +rvPhysics_Particle::SetContents +================ +*/ +void rvPhysics_Particle::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +rvPhysics_Particle::GetContents +================ +*/ +int rvPhysics_Particle::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +rvPhysics_Particle::GetBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +rvPhysics_Particle::GetAbsBounds +================ +*/ +const idBounds &rvPhysics_Particle::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +rvPhysics_Particle::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_Particle::Evaluate( int timeStepMSec, int endTimeMSec ) { + particlePState_t next; + float timeStep; + float upspeed; + + timeStep = MS2SEC( timeStepMSec ); + + // if bound to a master + if ( hasMaster ) { + idVec3 masterOrigin; + idMat3 masterAxis; + idVec3 oldOrigin; + + oldOrigin = current.origin; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, current.localAxis * masterAxis ); +// RAVEN END + + trace_t tr; + gameLocal.Translation( self, tr, oldOrigin, current.origin, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( tr.fraction < 1.0f ) { + self->Collide ( tr, current.origin - oldOrigin ); + } + + DebugDraw(); + + return true; + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + return true; + } + + clipModel->Unlink(); + + // Determine if currently on the ground + CheckGround ( ); + + // Determine the current upward velocity + if ( gravityNormal != vec3_zero ) { + upspeed = -( current.velocity * gravityNormal ); + } else { + upspeed = current.velocity.z; + } + + // If not on the ground, or moving upwards, or bouncing and moving toward gravity then do a straight + // forward slide move and gravity. + if ( !current.onGround || upspeed > 1.0f || (bouncyness > 0.0f && upspeed < -PRT_BOUNCESTOP && !current.inWater) ) { + // Force ground off when moving upward + if ( upspeed > 0.0f ) { + current.onGround = false; + } + SlideMove( current.origin, current.velocity, current.velocity * timeStep ); + if ( current.onGround && upspeed < PRT_BOUNCESTOP ) { + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + } else { + current.velocity += (gravityVector * timeStep); + } + } else { + idVec3 delta; + + // Slow down due to friction + ApplyFriction ( timeStep ); + + delta = current.velocity * timeStep; + current.velocity -= ( current.velocity * gravityNormal ) * gravityNormal; + if ( delta == vec3_origin ) { + PutToRest( ); + } else { + SlideMove( current.origin, current.velocity, delta ); + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + DebugDraw(); + + // get all the ground contacts + EvaluateContacts(); + + current.pushVelocity.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + PutToRest(); + } + + return true; +} + +/* +================ +rvPhysics_Particle::UpdateTime +================ +*/ +void rvPhysics_Particle::UpdateTime( int endTimeMSec ) { +} + +/* +================ +rvPhysics_Particle::GetTime +================ +*/ +int rvPhysics_Particle::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +rvPhysics_Particle::IsAtRest +================ +*/ +bool rvPhysics_Particle::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +rvPhysics_Particle::GetRestStartTime +================ +*/ +int rvPhysics_Particle::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +rvPhysics_Particle::IsPushable +================ +*/ +bool rvPhysics_Particle::IsPushable( void ) const { + return ( !hasMaster ); +} + +/* +================ +rvPhysics_Particle::SaveState +================ +*/ +void rvPhysics_Particle::SaveState( void ) { + saved = current; +} + +/* +================ +rvPhysics_Particle::RestoreState +================ +*/ +void rvPhysics_Particle::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void rvPhysics_Particle::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void rvPhysics_Particle::SetAxis( const idMat3 &newAxis, int id ) { + current.localAxis = newAxis; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::Translate +================ +*/ +void rvPhysics_Particle::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +rvPhysics_Particle::Rotate( +================ +*/ +void rvPhysics_Particle::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END + Activate(); +} + +/* +================ +rvPhysics_Particle::GetOrigin +================ +*/ +const idVec3 &rvPhysics_Particle::GetOrigin( int id ) const { + return clipModel->GetOrigin(); +} + +/* +================ +rvPhysics_Particle::GetAxis +================ +*/ +const idMat3 &rvPhysics_Particle::GetAxis( int id ) const { + if ( !clipModel ) { + return idPhysics_Base::GetAxis ( id ); + } + return clipModel->GetAxis(); +} + +/* +================ +rvPhysics_Particle::SetLinearVelocity +================ +*/ +void rvPhysics_Particle::SetLinearVelocity( const idVec3 &velocity, int id ) { + current.velocity = velocity; + Activate(); +} + +/* +================ +rvPhysics_Particle::GetLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +rvPhysics_Particle::ClipTranslation +================ +*/ +void rvPhysics_Particle::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipRotation +================ +*/ +void rvPhysics_Particle::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); + } +} + +/* +================ +rvPhysics_Particle::ClipContents +================ +*/ +int rvPhysics_Particle::ClipContents( const idClipModel *model ) const { + if ( model ) { + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); + } +} + +/* +================ +rvPhysics_Particle::DisableClip +================ +*/ +void rvPhysics_Particle::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +rvPhysics_Particle::EnableClip +================ +*/ +void rvPhysics_Particle::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +rvPhysics_Particle::UnlinkClip +================ +*/ +void rvPhysics_Particle::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +rvPhysics_Particle::LinkClip +================ +*/ +void rvPhysics_Particle::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +rvPhysics_Particle::SetPushed +================ +*/ +void rvPhysics_Particle::SetPushed( int deltaTime ) { + // velocity with which the particle is pushed + current.pushVelocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +rvPhysics_Particle::GetPushedLinearVelocity +================ +*/ +const idVec3 &rvPhysics_Particle::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity; +} + +/* +================ +rvPhysics_Particle::SetMaster +================ +*/ +void rvPhysics_Particle::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + hasMaster = true; + } + ClearContacts(); + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +/* +===================== +rvPhysics_Particle::CheckGround +===================== +*/ +void rvPhysics_Particle::CheckGround( void ) { + trace_t groundTrace; + idVec3 down; + + if ( gravityNormal == vec3_zero ) { + current.onGround = false; + return; + } + + down = current.origin + gravityNormal * CONTACT_EPSILON; + gameLocal.Translation( self, groundTrace, current.origin, down, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( groundTrace.fraction == 1.0f ) { + current.onGround = false; + return; + } + + if ( ( groundTrace.c.normal * -gravityNormal ) < 0.7f ) { + current.onGround = false; + return; + } + + if ( groundTrace.c.entityNum >= 0 && groundTrace.c.entityNum < MAX_CLIENTS ) { + current.onGround = false; + return; + } + + current.onGround = true; +} + +/* +================ +rvPhysics_Particle::ApplyFriction +================ +*/ +void rvPhysics_Particle::ApplyFriction( float timeStep ) { + idVec3 vel; + float speed; + float newspeed; + + // ignore slope movement, remove all velocity in gravity direction + vel = current.velocity + (current.velocity * gravityNormal) * gravityNormal; + + speed = vel.Length(); + if ( speed < 1.0f ) { + // remove all movement orthogonal to gravity, allows for sinking underwater + current.velocity = (current.velocity * gravityNormal) * gravityNormal; + return; + } + + // scale the velocity + if ( current.onGround ) { + newspeed = speed - ((speed * contactFriction) * timeStep); + } else { + newspeed = speed - ((speed * linearFriction) * timeStep); + } + + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= ( newspeed / speed ); +} + +/* +================ +rvPhysics_Particle::SlideMove +================ +*/ +bool rvPhysics_Particle::SlideMove( idVec3 &start, idVec3 &velocity, const idVec3 &delta ) { + int i; + trace_t tr; + idVec3 move; + bool collide, rtnValue = false; + + move = delta; + for( i = 0; i < 3; i++ ) { // be sure if you change this upper value in the for() to update the exit condition below!!!!! + gameLocal.Translation( self, tr, start, start + move, clipModel, clipModel->GetAxis(), clipMask, self, extraPassEntity ); + + start = tr.endpos; + + if ( tr.fraction == 1.0f ) { + if ( i > 0 ) { + return false; + } + return true; + } + + bool hitTeleporter = false; + + // let the entity know about the collision + collide = self->Collide( tr, current.velocity, hitTeleporter ); + + idEntity* ent; + ent = gameLocal.entities[tr.c.entityNum]; + assert ( ent ); + + // If we hit water just clip the move for now and keep on going + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + // Make sure we dont collide with water again + clipMask &= ~CONTENTS_WATER; + + current.inWater = true; + + // Allow the loop to go one more round to push us through the water + i--; + + velocity *= 0.4f; + + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + continue; + // bounce the projectile + } else if ( !current.inWater && allowBounce && bouncyness ) { + if ( !hitTeleporter ) { + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ) * bouncyness; + } + return true; +//RAVEN BEGIN +//jshepard: tr.c.material can (did) crash here if null + } else if ( allowBounce && tr.c.material && (tr.c.material->GetSurfaceFlags ( ) & SURF_BOUNCE) ) { +//RAVEN END + float dot; + move = tr.endpos; + dot = DotProduct( velocity, tr.c.normal ); + velocity = ( velocity - ( 2.0f * dot * tr.c.normal ) ); + return true; + } +// RAVEN BEGIN +// dluetscher: removed redundant trace calls + else { + i = 4; + rtnValue = true; + } +// RAVEN END + + // clip the movement delta and velocity + if( collide ) { + move.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + velocity.ProjectOntoPlane( tr.c.normal, PRT_OVERCLIP ); + } + } + + return rtnValue; +} + +const float PRT_VELOCITY_MAX = 16000; +const int PRT_VELOCITY_TOTAL_BITS = 16; +const int PRT_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PRT_VELOCITY_MAX ) ) + 1; +const int PRT_VELOCITY_MANTISSA_BITS = PRT_VELOCITY_TOTAL_BITS - 1 - PRT_VELOCITY_EXPONENT_BITS; + +/* +================ +rvPhysics_Particle::WriteToSnapshot +================ +*/ +void rvPhysics_Particle::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteLong( current.atRest ); + msg.WriteBits ( current.onGround, 1 ); + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); +// msg.WriteFloat( current.velocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteFloat( current.velocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + // TODO: Check that this conditional write to delta message is OK + if ( hasMaster ) { + idCQuat localQuat; + localQuat = current.localAxis.ToCQuat(); + + msg.WriteBits ( 1, 1 ); + msg.WriteFloat( localQuat.x ); + msg.WriteFloat( localQuat.y ); + msg.WriteFloat( localQuat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + } else { + msg.WriteBits ( 0, 1 ); + } +} + +/* +================ +rvPhysics_Particle::ReadFromSnapshot +================ +*/ +void rvPhysics_Particle::ReadFromSnapshot( const idBitMsgDelta &msg ) { + current.atRest = msg.ReadLong(); + current.onGround = ( msg.ReadBits( 1 ) != 0 ); + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); +// current.velocity[0] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[1] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.velocity[2] = msg.ReadFloat( PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); +// current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); +// current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, PRT_VELOCITY_EXPONENT_BITS, PRT_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + if ( msg.ReadBits ( 1 ) ) { + idCQuat localQuat; + localQuat.x = msg.ReadFloat( ); + localQuat.y = msg.ReadFloat( ); + localQuat.z = msg.ReadFloat( ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.localAxis = localQuat.ToMat3(); + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.origin, clipModel->GetAxis() ); +// RAVEN END + } +} diff --git a/src/mpgame/physics/Physics_Particle.h b/src/mpgame/physics/Physics_Particle.h new file mode 100644 index 0000000..47b9788 --- /dev/null +++ b/src/mpgame/physics/Physics_Particle.h @@ -0,0 +1,176 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_PARTICLE_H__ +#define __PHYSICS_PARTICLE_H__ + +/* +=================================================================================== + + Particle Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct particlePState_s { + int atRest; // set when simulation is suspended + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec3 pushVelocity; // push velocity + idVec3 origin; + idVec3 velocity; + bool onGround; + bool inWater; +} particlePState_t; + +class rvPhysics_Particle : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( rvPhysics_Particle ); + + rvPhysics_Particle( void ); + ~rvPhysics_Particle( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b, bool allowBounce ); + + // put to rest untill something collides with this physics object + void PutToRest( void ); + // same as above but drop to the floor first + void DropToFloor( void ); + + // returns true if touching the ground + bool IsOnGround ( void ) const; + bool IsInWater ( void ) const; + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + bool CanBounce ( void ) const; + + bool EvaluateContacts( void ); + + void Activate( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + idEntityPtr extraPassEntity; + +private: + // state of the particle + particlePState_t current; + particlePState_t saved; + + // particle properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + bool allowBounce; // Allowed to bounce at all? + idClipModel * clipModel; // clip model used for collision detection + + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for inside solid during a drop + + // master + bool hasMaster; + bool isOrientated; + +private: + + void DropToFloorAndRest ( void ); + bool SlideMove ( idVec3& start, idVec3& velocity, const idVec3& delta ); + void CheckGround ( void ); + void ApplyFriction ( float timeStep ); + void DebugDraw ( void ); +}; + +ID_INLINE bool rvPhysics_Particle::IsOnGround ( void ) const { + return current.onGround; +} + +ID_INLINE bool rvPhysics_Particle::IsInWater ( void ) const { + return current.inWater; +} + +ID_INLINE bool rvPhysics_Particle::CanBounce ( void ) const { + return allowBounce; +} + +#endif /* !__PHYSICS_PARTICLE_H__ */ diff --git a/src/mpgame/physics/Physics_Player.cpp b/src/mpgame/physics/Physics_Player.cpp new file mode 100644 index 0000000..e18046a --- /dev/null +++ b/src/mpgame/physics/Physics_Player.cpp @@ -0,0 +1,2281 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Actor, idPhysics_Player ) +END_CLASS + +// movement parameters +const float PM_STOPSPEED = 100.0f; +const float PM_SWIMSCALE = 0.5f; +const float PM_LADDERSPEED = 100.0f; +const float PM_STEPSCALE = 1.0f; + +const float PM_ACCELERATE_SP = 10.0f; +const float PM_AIRACCELERATE_SP = 1.0f; +const float PM_ACCELERATE_MP = 15.0f; +const float PM_AIRACCELERATE_MP = 1.18f; +const float PM_WATERACCELERATE = 4.0f; +const float PM_FLYACCELERATE = 8.0f; + +const float PM_FRICTION = 6.0f; +const float PM_AIRFRICTION = 0.0f; +const float PM_WATERFRICTION = 2.0f; +const float PM_FLYFRICTION = 3.0f; +const float PM_NOCLIPFRICTION = 12.0f; +const float PM_SLIDEFRICTION = 0.5f; + +const float MIN_WALK_NORMAL = 0.7f; // can't walk on very steep slopes +const float OVERCLIP = 1.001f; + +// movementFlags +const int PMF_DUCKED = 1; // set when ducking +const int PMF_JUMPED = 2; // set when the player jumped this frame +const int PMF_STEPPED_UP = 4; // set when the player stepped up this frame +const int PMF_STEPPED_DOWN = 8; // set when the player stepped down this frame +const int PMF_JUMP_HELD = 16; // set when jump button is held down +const int PMF_TIME_LAND = 32; // movementTime is time before rejump +const int PMF_TIME_KNOCKBACK = 64; // movementTime is an air-accelerate only time +const int PMF_TIME_WATERJUMP = 128; // movementTime is waterjump +const int PMF_ALL_TIMES = (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK); + +float idPhysics_Player::Pm_Accelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_ACCELERATE_MP : PM_ACCELERATE_SP; +} + +float idPhysics_Player::Pm_AirAccelerate( void ) { + return gameLocal.IsMultiplayer() ? PM_AIRACCELERATE_MP : PM_AIRACCELERATE_SP; +} + +/* +============ +idPhysics_Player::CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +float idPhysics_Player::CmdScale( const usercmd_t &cmd ) const { + int max; + float total; + float scale; + int forwardmove; + int rightmove; + int upmove; + + forwardmove = cmd.forwardmove; + rightmove = cmd.rightmove; + + // since the crouch key doubles as downward movement, ignore downward movement when we're on the ground + // otherwise crouch speed will be lower than specified + if ( walking ) { + upmove = 0; + } else { + upmove = cmd.upmove; + } + + max = abs( forwardmove ); + if ( abs( rightmove ) > max ) { + max = abs( rightmove ); + } + if ( abs( upmove ) > max ) { + max = abs( upmove ); + } + + if ( !max ) { + return 0.0f; + } + + total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove ); + scale = (float) playerSpeed * max / ( 127.0f * total ); + + return scale; +} + +/* +============== +idPhysics_Player::Accelerate + +Handles user intended acceleration +============== +*/ +void idPhysics_Player::Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ) { +#if 1 + // q2 style + float addspeed, accelspeed, currentspeed; + + currentspeed = current.velocity * wishdir; + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } +// RAVEN BEGIN +// nmckenzie: added ability to try alternate accelerations. + if ( pm_acceloverride.GetFloat() > 0.0f ) { + accelspeed = pm_acceloverride.GetFloat() * frametime * wishspeed; + } else { + accelspeed = accel * frametime * wishspeed; + } +// RAVEN END + if ( accelspeed > addspeed ) { + accelspeed = addspeed; + } + + current.velocity += accelspeed * wishdir; + +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + idVec3 wishVelocity; + idVec3 pushDir; + float pushLen; + float canPush; + + wishVelocity = wishdir * wishspeed; + pushDir = wishVelocity - current.velocity; + pushLen = pushDir.Normalize(); + + canPush = accel * frametime * wishspeed; + if ( canPush > pushLen ) { + canPush = pushLen; + } + + current.velocity += canPush * pushDir; +#endif +} + +/* +=============== +idPhysics_Player::AdjustVertically +given a walkable plane normal, adjust z to be in the plane without changing the horizontal direction +=============== +*/ +idVec3 idPhysics_Player::AdjustVertically( const idVec3 &normal, const idVec3 &_in, int forceLength ) const { + idVec3 out; + idVec3 in = _in; + + idVec3 velocityPlane; + velocityPlane[0] = in[1]; + velocityPlane[1] = -in[0]; + velocityPlane[2] = 0.0f; + + // same length as horizontal input, and in the plane + out = velocityPlane.Cross( normal ); + + // make sure direction is ok + if ( in * out < 0 ) { + out = -out; + } + + switch ( forceLength ) { + case 0: { + if ( pm_slidevelocity.GetInteger() == 1 ) { + double inSpeed = in.Normalize(); + double hSpeed = out.Normalize(); + // incidence angle, between 0 (just touching, result speed at inSpeed) and M_PI/2 (perpendicular hit, result speed at hSpeed) + float angle = idMath::ACos( in * out ); + double ratio = idMath::Pow64( angle * 2.0f / idMath::PI, pm_powerslide.GetFloat() ); + float targetSpeed = ratio * hSpeed + ( 1.0 - ratio ) * inSpeed; + out *= targetSpeed; + } + break; + } + case 1: { + double inSpeed = in.Normalize(); + out.Normalize(); + out *= inSpeed; + break; + } + case 2: { + // scale the out vector so horizontal speed is maintained + idVec3 hout = out; hout.z = 0.0f; + float ratio = velocityPlane.Length() / hout.Length(); + out *= ratio; + break; + } + } + + // and maintain an OVERCLIP + if ( out.z >= 0 ) { + out.z *= OVERCLIP; + } else { + out.z /= OVERCLIP; + } + + return out; +} + + +/* +================== +idPhysics_Player::SlideMove + +Returns true if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 + +bool idPhysics_Player::SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ) { + int i, j, k, pushFlags; + int bumpcount, numbumps, numplanes; + float d, time_left, into, totalMass; + idVec3 dir; + idVec3 planes[MAX_CLIP_PLANES]; + idVec3 end, stepEnd, primal_velocity, endVelocity, endClipVelocity, clipVelocity; + trace_t trace, stepTrace, downTrace; + bool nearGround, stepped, pushed; + bool sliding = ( current.crouchSlideTime > 0 ) && ( command.upmove < 0 ) && groundPlane; + int forceLength = sliding ? 2 : 0; + + numbumps = 4; + + primal_velocity = current.velocity; + + if ( gravity ) { + endVelocity = current.velocity + gravityVector * frametime; + current.velocity = ( current.velocity + endVelocity ) * 0.5f; + primal_velocity = endVelocity; + if ( groundPlane ) { + // slide along the ground plane + if ( groundTrace.c.normal.z >= MIN_WALK_NORMAL ) { + current.velocity = AdjustVertically( groundTrace.c.normal, current.velocity, forceLength ); + } else { + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + } + } else { + endVelocity = current.velocity; + } + + time_left = frametime; + + // never turn against the ground plane + if ( groundPlane ) { + numplanes = 1; + planes[0] = groundTrace.c.normal; + } else { + numplanes = 0; + } + + // never turn against original velocity + planes[numplanes] = current.velocity; + planes[numplanes].Normalize(); + numplanes++; + + for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) { + + // calculate position we are trying to move to + end = current.origin + time_left * current.velocity; + + // see if we can make it there + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + current.origin = trace.endpos; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + + time_left -= time_left * trace.fraction; + stepped = pushed = false; + + // if we are allowed to step up + if ( stepUp && ( trace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + nearGround = groundPlane | ladder; + + if ( !nearGround ) { + // trace down to see if the player is near the ground + // step checking when near the ground allows the player to move up stairs smoothly while jumping + stepEnd = current.origin + maxStepHeight * gravityNormal; + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + nearGround = ( downTrace.fraction < 1.0f && ( downTrace.c.normal * -gravityNormal ) > MIN_WALK_NORMAL ); + } + + // may only step up if near the ground or on a ladder + if ( nearGround ) { + + // step up + stepEnd = current.origin - maxStepHeight * gravityNormal; + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + // trace along velocity + stepEnd = downTrace.endpos + time_left * current.velocity; + gameLocal.Translation( self, stepTrace, downTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + // step down + stepEnd = stepTrace.endpos + maxStepHeight * gravityNormal; + gameLocal.Translation( self, downTrace, stepTrace.endpos, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + + if ( downTrace.fraction >= 1.0f || ( downTrace.c.normal * -gravityNormal ) > MIN_WALK_NORMAL ) { + // if moved the entire distance + if ( stepTrace.fraction >= 1.0f ) { + time_left = 0; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + break; + } + + // if the move is further when stepping up + if ( stepTrace.fraction > trace.fraction ) { + time_left -= time_left * stepTrace.fraction; + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_UP; + trace = stepTrace; + stepped = true; + } + } + } + } + + // if we can push other entities and not blocked by the world + if ( push && trace.c.entityNum != ENTITYNUM_WORLD ) { + + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + // clip movement, only push idMoveables, don't push entities the player is standing on + // apply impact to pushed objects + pushFlags = PUSHFL_CLIP|PUSHFL_ONLYMOVEABLE|PUSHFL_NOGROUNDENTITIES|PUSHFL_APPLYIMPULSE; + + // clip & push + totalMass = gameLocal.push.ClipTranslationalPush( trace, self, pushFlags, end, end - current.origin ); + + if ( totalMass > 0.0f ) { + // decrease velocity based on the total mass of the objects being pushed ? + current.velocity *= 1.0f - idMath::ClampFloat( 0.0f, 1000.0f, totalMass - 20.0f ) * ( 1.0f / 950.0f ); + pushed = true; + } + + current.origin = trace.endpos; + time_left -= time_left * trace.fraction; + + // if moved the entire distance + if ( trace.fraction >= 1.0f ) { + break; + } + } + + if ( !stepped && self ) { + // let the entity know about the collision + self->Collide( trace, current.velocity ); + } + + if ( numplanes >= MAX_CLIP_PLANES ) { + // MrElusive: I think we have some relatively high poly LWO models with a lot of slanted tris + // where it may hit the max clip planes + current.velocity = vec3_origin; + return true; + } + + // + // if this is the same plane we hit before, nudge velocity out along it, + // which fixes some epsilon issues with non-axial planes + // + for ( i = 0; i < numplanes; i++ ) { + if ( ( trace.c.normal * planes[i] ) > 0.999f ) { + + if ( planes[i].z >= MIN_WALK_NORMAL ) { + // cargo cult? + current.velocity = AdjustVertically( trace.c.normal, current.velocity, forceLength ); + } else { + // clip into the trace normal just in case this normal is almost but not exactly the same as the groundTrace normal + current.velocity.ProjectOntoPlane( trace.c.normal, OVERCLIP ); + // also add the normal to nudge the velocity out + current.velocity += trace.c.normal; + } + + break; + } + } + if ( i < numplanes ) { + continue; + } + planes[numplanes] = trace.c.normal; + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0; i < numplanes; i++ ) { + into = current.velocity * planes[i]; + if ( into >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + if ( planes[i].z >= MIN_WALK_NORMAL ) { + clipVelocity = AdjustVertically( planes[i], current.velocity, forceLength ); + endClipVelocity = AdjustVertically( planes[i], endVelocity, forceLength ); + } else { + // slide along the plane + clipVelocity = current.velocity; + clipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + + // slide along the plane + endClipVelocity = endVelocity; + endClipVelocity.ProjectOntoPlane( planes[i], OVERCLIP ); + } + + // see if there is a second plane that the new move enters + for ( j = 0; j < numplanes; j++ ) { + if ( j == i ) { + continue; + } + if ( ( clipVelocity * planes[j] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + if ( planes[j].z >= MIN_WALK_NORMAL ) { + clipVelocity = AdjustVertically( planes[j], clipVelocity, forceLength ); + endClipVelocity = AdjustVertically( planes[j], endClipVelocity, forceLength ); + } else { + // try clipping the move to the plane + clipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + endClipVelocity.ProjectOntoPlane( planes[j], OVERCLIP ); + } + + // see if it goes back into the first clip plane + if ( ( clipVelocity * planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * current.velocity; + clipVelocity = d * dir; + + dir = planes[i].Cross( planes[j] ); + dir.Normalize(); + d = dir * endVelocity; + endClipVelocity = d * dir; + + // see if there is a third plane the the new move enters + for ( k = 0; k < numplanes; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( ( clipVelocity * planes[k] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a triple plane interaction (duh) + current.velocity = vec3_origin; + return true; + } + } + + // if we have fixed all interactions, try another move + current.velocity = clipVelocity; + endVelocity = endClipVelocity; + + break; + } + } + + // step down + if ( stepDown && groundPlane ) { + stepEnd = current.origin + gravityNormal * maxStepHeight; + gameLocal.Translation( self, downTrace, current.origin, stepEnd, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( downTrace.fraction > 1e-4f && downTrace.fraction < 1.0f ) { + current.stepUp -= ( downTrace.endpos - current.origin ) * gravityNormal; + current.origin = downTrace.endpos; + current.movementFlags |= PMF_STEPPED_DOWN; + } + } + + if ( gravity ) { + current.velocity = endVelocity; + } + + // come to a dead stop when the velocity orthogonal to the gravity flipped + clipVelocity = current.velocity - gravityNormal * current.velocity * gravityNormal; + endClipVelocity = endVelocity - gravityNormal * endVelocity * gravityNormal; + if ( clipVelocity * endClipVelocity < 0.0f ) { + current.velocity = gravityNormal * current.velocity * gravityNormal; + } + + return ( bumpcount == 0 ); +} + +/* +================== +idPhysics_Player::Friction + +Handles both ground friction and water friction +================== +*/ +void idPhysics_Player::Friction( void ) { + idVec3 vel; + float speed, newspeed, control; + float drop; + + vel = current.velocity; + if ( walking ) { + // ignore slope movement, remove all velocity in gravity direction + vel += (vel * gravityNormal) * gravityNormal; + } + + speed = vel.Length(); + if ( speed < 1.0f ) { + current.velocity.Zero(); + return; + } + + drop = 0; + + // spectator friction + if ( pm_frictionoverride.GetFloat() > -1 ) { + drop += speed * pm_frictionoverride.GetFloat() * frametime; + } else if ( current.movementType == PM_SPECTATOR ) { + drop += speed * PM_FLYFRICTION * frametime; + } + // apply ground friction + else if ( walking && waterLevel <= WATERLEVEL_FEET ) { + // no friction on slick surfaces + if ( !(groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( !(current.movementFlags & PMF_TIME_KNOCKBACK) ) { + control = speed < PM_STOPSPEED ? PM_STOPSPEED : speed; + if ( current.crouchSlideTime > 0 ) { + drop += control * PM_SLIDEFRICTION * frametime; + } else { + drop += control * PM_FRICTION * frametime; + } + } + } + } + // apply water friction even if just wading + else if ( waterLevel ) { + drop += speed * PM_WATERFRICTION * waterLevel * frametime; + } + // apply air friction + else { + drop += speed * PM_AIRFRICTION * frametime; + } + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0 ) { + newspeed = 0; + } + current.velocity *= ( newspeed / speed ); + + // TTimo - snap to avoid denormals + if ( fabs( current.velocity.x ) < 1.0e-5f ) { + current.velocity.x = 0.0f; + } + if ( fabs( current.velocity.y ) < 1.0e-5f ) { + current.velocity.y = 0.0f; + } + if ( fabs( current.velocity.z ) < 1.0e-5f ) { + current.velocity.z = 0.0f; + } +} + +/* +=================== +idPhysics_Player::WaterJumpMove + +Flying out of the water +=================== +*/ +void idPhysics_Player::WaterJumpMove( void ) { + + // waterjump has no control, but falls + idPhysics_Player::SlideMove( true, true, false, false ); + + // add gravity + current.velocity += gravityNormal * frametime; + // if falling down + if ( current.velocity * gravityNormal > 0.0f ) { + // cancel as soon as we are falling down again + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } +} + +/* +=================== +idPhysics_Player::WaterMove +=================== +*/ +void idPhysics_Player::WaterMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + float vel; + + if ( idPhysics_Player::CheckWaterJump() ) { + idPhysics_Player::WaterJumpMove(); + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // user intentions + if ( !scale ) { + wishvel = gravityNormal * 60; // sink towards bottom + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + if ( wishspeed > playerSpeed * PM_SWIMSCALE ) { + wishspeed = playerSpeed * PM_SWIMSCALE; + } + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_WATERACCELERATE ); + + // make sure we can go up slopes easily under water + if ( groundPlane && ( current.velocity * groundTrace.c.normal ) < 0.0f ) { + vel = current.velocity.Length(); + // slide along the ground plane + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + + current.velocity.Normalize(); + current.velocity *= vel; + } + + idPhysics_Player::SlideMove( false, true, false, false ); +} + +/* +=================== +idPhysics_Player::FlyMove +=================== +*/ +void idPhysics_Player::FlyMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + // normal slowdown + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +=================== +idPhysics_Player::AirMove +=================== +*/ +void idPhysics_Player::AirMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + + // if the player isnt pressing crouch and heading down then accumulate slide time + if ( command.upmove >= 0 && current.velocity * gravityNormal > 0 ) { + current.crouchSlideTime = idMath::ClampInt( 0, 2000, current.crouchSlideTime + framemsec * 2 ); + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishvel -= (wishvel * gravityNormal) * gravityNormal; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // not on ground, so little effect on velocity + idPhysics_Player::Accelerate( wishdir, wishspeed, Pm_AirAccelerate() ); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( groundPlane ) { + current.velocity.ProjectOntoPlane( groundTrace.c.normal, OVERCLIP ); + } + + // NOTE: enable stair checking while moving through the air in multiplayer to allow bunny hopping onto stairs + idPhysics_Player::SlideMove( true, gameLocal.isMultiplayer, false, false ); +} + +/* +=================== +idPhysics_Player::WalkMove +=================== +*/ +void idPhysics_Player::WalkMove( void ) { + idVec3 wishvel; + idVec3 wishdir; + float wishspeed; + float scale; + float accelerate; + idVec3 vel; + + if ( waterLevel > WATERLEVEL_WAIST && ( viewForward * groundTrace.c.normal ) > 0.0f ) { + // begin swimming + idPhysics_Player::WaterMove(); + return; + } + + if ( idPhysics_Player::CheckJump() ) { + // jumped away + if ( waterLevel > WATERLEVEL_FEET ) { + idPhysics_Player::WaterMove(); + } else { + idPhysics_Player::AirMove(); + } + return; + } + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + // project moves down to flat plane + viewForward -= (viewForward * gravityNormal) * gravityNormal; + viewRight -= (viewRight * gravityNormal) * gravityNormal; + + assert( groundTrace.c.normal.z >= MIN_WALK_NORMAL ); + viewForward = AdjustVertically( groundTrace.c.normal, viewForward ); + viewRight = AdjustVertically( groundTrace.c.normal, viewRight ); + + viewForward.Normalize(); + viewRight.Normalize(); + + wishvel = viewForward * command.forwardmove + viewRight * command.rightmove; + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + // clamp the speed lower if wading or walking on the bottom + if ( waterLevel ) { + float waterScale; + + waterScale = waterLevel / 3.0f; + waterScale = 1.0f - ( 1.0f - PM_SWIMSCALE ) * waterScale; + if ( wishspeed > playerSpeed * waterScale ) { + wishspeed = playerSpeed * waterScale; + } + } + + // lower acceleration (control) when on slippery stuff or being smacked around + bool fLowControl = ( ( current.movementFlags & PMF_TIME_KNOCKBACK ) || ( groundMaterial && groundMaterial->GetSurfaceFlags() & SURF_SLICK ) ); + accelerate = fLowControl ? Pm_AirAccelerate() : Pm_Accelerate(); + idPhysics_Player::Accelerate( wishdir, wishspeed, accelerate ); + if ( fLowControl ) { + current.velocity += gravityVector * frametime; + } + + // slide along the ground plane + current.velocity = AdjustVertically( groundTrace.c.normal, current.velocity, 1 ); + + // don't do anything if standing still + vel = current.velocity - (current.velocity * gravityNormal) * gravityNormal; + if ( vel.IsZero() ) { + return; + } + + gameLocal.push.InitSavingPushedEntityPositions(); + + idPhysics_Player::SlideMove( false, true, true, !gameLocal.isMultiplayer ); +} + +/* +============== +idPhysics_Player::DeadMove +============== +*/ +void idPhysics_Player::DeadMove( void ) { + float forward; + + if ( !walking ) { + return; + } + + // extra friction + forward = current.velocity.Length(); + forward -= 20; + if ( forward <= 0 ) { + current.velocity = vec3_origin; + } + else { + current.velocity.Normalize(); + current.velocity *= forward; + } +} + +/* +=============== +idPhysics_Player::NoclipMove +=============== +*/ +void idPhysics_Player::NoclipMove( void ) { + float speed, drop, friction, newspeed, stopspeed; + float scale, wishspeed; + idVec3 wishdir; + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + if ( pm_frictionoverride.GetFloat ( ) > -1 ) { + idPhysics_Player::Friction(); + } else { +// RAVEN END + + // friction + speed = current.velocity.Length(); + if ( speed < 20.0f ) { + current.velocity = vec3_origin; + } + else { + stopspeed = playerSpeed * 0.3f; + if ( speed < stopspeed ) { + speed = stopspeed; + } + friction = PM_NOCLIPFRICTION; + drop = speed * friction * frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + + current.velocity *= newspeed / speed; + } + +// RAVEN BEGIN +// nmckenzie: allow trying custom frictions + } +// RAVEN END + + // accelerate + scale = idPhysics_Player::CmdScale( command ); + + wishdir = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishdir -= scale * gravityNormal * command.upmove; + wishspeed = wishdir.Normalize(); + wishspeed *= scale; + + idPhysics_Player::Accelerate( wishdir, wishspeed, Pm_Accelerate() ); + + // move + current.origin += frametime * current.velocity; +} + +/* +=============== +idPhysics_Player::SpectatorMove +=============== +*/ +void idPhysics_Player::SpectatorMove( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + trace_t trace; + idVec3 end; + + // fly movement + + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( false, false, false, false ); +} + +/* +============ +idPhysics_Player::LadderMove +============ +*/ +void idPhysics_Player::LadderMove( void ) { + idVec3 wishdir, wishvel, right; + float wishspeed, scale; + float upscale; + + // stick to the ladder + wishvel = -100.0f * ladderNormal; + current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel; + + upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f; + if ( upscale > 1.0f ) { + upscale = 1.0f; + } + else if ( upscale < -1.0f ) { + upscale = -1.0f; + } + + scale = idPhysics_Player::CmdScale( command ); + wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove; + + // strafe + if ( command.rightmove ) { + // right vector orthogonal to gravity + right = viewRight - (gravityNormal * viewRight) * gravityNormal; + // project right vector into ladder plane + right = right - (ladderNormal * right) * ladderNormal; + right.Normalize(); + + // if we are looking away from the ladder, reverse the right vector + if ( ladderNormal * viewForward > 0.0f ) { + right = -right; + } + wishvel += 2.0f * right * scale * (float) command.rightmove; + } + + // up down movement + if ( command.upmove ) { + wishvel += -0.5f * gravityNormal * scale * (float) command.upmove; + } + + // do strafe friction + idPhysics_Player::Friction(); + + // accelerate + wishspeed = wishvel.Normalize(); + idPhysics_Player::Accelerate( wishvel, wishspeed, Pm_Accelerate() ); + + // cap the vertical velocity + upscale = current.velocity * -gravityNormal; + if ( upscale < -PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale + PM_LADDERSPEED); + } + else if ( upscale > PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale - PM_LADDERSPEED); + } + + if ( (wishvel * gravityNormal) == 0.0f ) { + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity += gravityVector * frametime; + if ( current.velocity * gravityNormal > 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + else { + current.velocity -= gravityVector * frametime; + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + } + + idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false ); +} + +/* +============= +idPhysics_Player::CorrectAllSolid +============= +*/ +void idPhysics_Player::CorrectAllSolid( trace_t &trace, int contents ) { + // FIXME: jitter around to find a free spot ? + + if ( trace.fraction >= 1.0f ) { + memset( &trace, 0, sizeof( trace ) ); + trace.endpos = current.origin; + trace.endAxis = clipModelAxis; + trace.fraction = 0.0f; + trace.c.dist = current.origin.z; + trace.c.normal.Set( 0, 0, 1 ); + trace.c.point = current.origin; + trace.c.entityNum = ENTITYNUM_WORLD; + trace.c.id = 0; + trace.c.type = CONTACT_TRMVERTEX; + trace.c.material = NULL; + trace.c.contents = contents; + } +} + +/* +============= +idPhysics_Player::CheckGround +============= +*/ +void idPhysics_Player::CheckGround( bool checkStuck ) { + int i, contents; + idVec3 point; + bool hadGroundContacts; + + hadGroundContacts = HasGroundContacts(); + + // set the clip model origin before getting the contacts + clipModel->SetPosition( current.origin, clipModel->GetAxis() ); + + EvaluateContacts(); + + // setup a ground trace from the contacts + groundTrace.endpos = current.origin; + groundTrace.endAxis = clipModel->GetAxis(); + if ( contacts.Num() ) { + groundTrace.fraction = 0.0f; + groundTrace.c = contacts[0]; + for ( i = 1; i < contacts.Num(); i++ ) { + groundTrace.c.normal += contacts[i].normal; + } + groundTrace.c.normal.Normalize(); + } else { + groundTrace.fraction = 1.0f; + } + + if ( checkStuck && contacts.Num() ) { + contents = gameLocal.Contents( self, current.origin, clipModel, clipModel->GetAxis(), -1, self ); + if ( contents & MASK_SOLID ) { + // do something corrective if stuck in solid + idPhysics_Player::CorrectAllSolid( groundTrace, contents ); + } + } + + // if the trace didn't hit anything, we are in free fall + if ( groundTrace.fraction == 1.0f ) { + groundPlane = false; + walking = false; + groundEntityPtr = NULL; + return; + } + + groundMaterial = groundTrace.c.material; + groundEntityPtr = gameLocal.entities[ groundTrace.c.entityNum ]; + + // check if getting thrown off the ground + if ( ( current.velocity * -gravityNormal ) > 0.0f && ( current.velocity * groundTrace.c.normal ) > 1.0f ) { + groundPlane = false; + walking = false; + return; + } + + // slopes that are too steep will not be considered onground + if ( ( groundTrace.c.normal * -gravityNormal ) < MIN_WALK_NORMAL ) { + // in multiplayer, instead of sliding push the player out from the normal for some free fall + current.origin += groundTrace.c.normal; + + groundPlane = false; + walking = false; + + return; + } + + groundPlane = true; + walking = true; + + // hitting solid ground will end a waterjump + if ( current.movementFlags & PMF_TIME_WATERJUMP ) { + current.movementFlags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); + current.movementTime = 0; + } + + // if the player didn't have ground contacts the previous frame + if ( !hadGroundContacts ) { + // don't do landing time if we were just going down a slope + if ( (current.velocity * -gravityNormal) < -200.0f ) { + // don't allow another jump for a little while + current.movementFlags |= PMF_TIME_LAND; + current.movementTime = 250; + } + } + + // let the entity know about the collision + if ( self ) { + self->Collide( groundTrace, current.velocity ); + } + + if ( groundEntityPtr.GetEntity() ) { + impactInfo_t info; + groundEntityPtr.GetEntity()->GetImpactInfo( self, groundTrace.c.id, groundTrace.c.point, &info ); + if ( info.invMass != 0.0f ) { + groundEntityPtr.GetEntity()->ApplyImpulse( self, groundTrace.c.id, groundTrace.c.point, current.velocity / ( info.invMass * 10.0f ) ); + } + } +} + +/* +============== +idPhysics_Player::CheckDuck + +Sets clip model size +============== +*/ +void idPhysics_Player::CheckDuck( void ) { + trace_t trace; + idVec3 end; + idBounds bounds; + float maxZ; + + if ( current.movementType == PM_DEAD ) { + maxZ = pm_deadheight.GetFloat(); + } else { + // stand up when up against a ladder + if ( command.upmove < 0 && !ladder ) { + // duck + current.movementFlags |= PMF_DUCKED; + } else { + // stand up if possible + if ( current.movementFlags & PMF_DUCKED ) { + // try to stand up + end = current.origin - ( pm_normalheight.GetFloat() - pm_crouchheight.GetFloat() ) * gravityNormal; + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + if ( trace.fraction >= 1.0f ) { + current.movementFlags &= ~PMF_DUCKED; + } + } + } + + if ( current.movementFlags & PMF_DUCKED ) { + if ( !current.crouchSlideTime ) { + playerSpeed = crouchSpeed; + } + maxZ = pm_crouchheight.GetFloat(); + } else { + maxZ = pm_normalheight.GetFloat(); + if ( groundPlane && current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } + } + } + // if the clipModel height should change + if ( clipModel->GetBounds()[1][2] != maxZ ) { + + bounds = clipModel->GetBounds(); + bounds[1][2] = maxZ; + + // < 0: don't use alternate alignment, > 0: use alternate alignment (faces aligned with axes for even-sided cylinders) + // 0: use AABB; 1: use 8-sided cylinder; 3+: use custom number of sides for cylinder + int sides = pm_usecylinder.GetInteger(); + bool alt_align = (sides > 0); + sides = idMath::Abs( sides ); + + if ( sides >= 1 ) { + if ( sides < 3 ) { + sides = 8; + } + idTraceModel trm( bounds, sides, alt_align ); +// trm.findWalkSurfaces = true; + clipModel->LoadModel( trm, NULL ); + } else { + idTraceModel trm( bounds ); +// trm.findWalkSurfaces = true; + clipModel->LoadModel( trm, NULL ); + } + } +} + +/* +================ +idPhysics_Player::CheckLadder +================ +*/ +void idPhysics_Player::CheckLadder( void ) { + idVec3 forward, start, end; + trace_t trace; + float tracedist; + + if ( current.movementTime ) { + return; + } + + // if on the ground moving backwards + if ( walking && command.forwardmove <= 0 ) { + return; + } + + // forward vector orthogonal to gravity + forward = viewForward - (gravityNormal * viewForward) * gravityNormal; + forward.Normalize(); + + if ( walking ) { + // don't want to get sucked towards the ladder when still walking + tracedist = 1.0f; + } else { + tracedist = 48.0f; + } + + end = current.origin + tracedist * forward; + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + // if near a surface + if ( trace.fraction < 1.0f ) { + + // if a ladder surface + if ( trace.c.material && ( trace.c.material->GetSurfaceFlags() & SURF_LADDER ) ) { + + // check a step height higher + end = current.origin - gravityNormal * ( maxStepHeight * 0.75f ); + gameLocal.Translation( self, trace, current.origin, end, clipModel, clipModel->GetAxis(), clipMask, self ); + start = trace.endpos; + end = start + tracedist * forward; + gameLocal.Translation( self, trace, start, end, clipModel, clipModel->GetAxis(), clipMask, self ); + + // if also near a surface a step height higher + if ( trace.fraction < 1.0f ) { + + // if it also is a ladder surface + if ( trace.c.material && trace.c.material->GetSurfaceFlags() & SURF_LADDER ) { + ladder = true; + ladderNormal = trace.c.normal; + } + } + } + } +} + +/* +============= +idPhysics_Player::CheckJump +============= +*/ +bool idPhysics_Player::CheckJump( void ) { + idVec3 addVelocity; + + // CheckJump only called from WalkMove, therefore with walking == true + // in MP game we always have groundPlane == walking + // (this mostly matters to velocity clipping against ground when the jump is ok'ed) + assert( groundPlane ); + + if ( command.upmove < 10 ) { + // not holding jump + return false; + } + + // must wait for jump to be released + if ( current.movementFlags & PMF_JUMP_HELD ) { + return false; + } + + // don't jump if we can't stand up + if ( current.movementFlags & PMF_DUCKED ) { + return false; + } + + // start by setting up the normal ground slide velocity + // this will make sure that when we add the jump velocity we actually get off of the ground plane + if ( current.velocity * groundTrace.c.normal < 0.0f ) { + current.velocity = AdjustVertically( groundTrace.c.normal, current.velocity ); + } + + addVelocity = 2.0f * maxJumpHeight * -gravityVector; + addVelocity *= idMath::Sqrt( addVelocity.Normalize() ); + current.velocity += addVelocity; + + groundPlane = false; // jumping away + walking = false; + groundEntityPtr = NULL; + current.movementFlags |= PMF_JUMP_HELD | PMF_JUMPED; + + // crouch slide + current.crouchSlideTime = 0; + + return true; +} + +/* +============= +idPhysics_Player::CheckWaterJump +============= +*/ +bool idPhysics_Player::CheckWaterJump( void ) { + idVec3 spot; + int cont; + idVec3 flatforward; + + if ( current.movementTime ) { + return false; + } + + // check for water jump + if ( waterLevel != WATERLEVEL_WAIST ) { + return false; + } + + flatforward = viewForward - (viewForward * gravityNormal) * gravityNormal; + flatforward.Normalize(); + + spot = current.origin + 30.0f * flatforward; + spot -= 4.0f * gravityNormal; + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); + if ( !(cont & CONTENTS_SOLID) ) { + return false; + } + + spot -= 16.0f * gravityNormal; + cont = gameLocal.Contents( self, spot, NULL, mat3_identity, -1, self ); + if ( cont ) { + return false; + } + + // jump out of water + current.velocity = 200.0f * viewForward - 350.0f * gravityNormal; + current.movementFlags |= PMF_TIME_WATERJUMP; + current.movementTime = 2000; + + return true; +} + +/* +============= +idPhysics_Player::SetWaterLevel +============= +*/ +void idPhysics_Player::SetWaterLevel( void ) { + idVec3 point; + idBounds bounds; + int contents; + + // + // get waterlevel, accounting for ducking + // + waterLevel = WATERLEVEL_NONE; + waterType = 0; + + bounds = clipModel->GetBounds(); + +// AReis: Get back the water entity (if there is one), so we can grab his density +// then apply some force to the fluid since we're moving through it. + idEntity *other = NULL; + + // check at feet level + point = current.origin - ( bounds[0][2] + 1.0f ) * gravityNormal; + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self, &other ); + if ( contents & MASK_WATER ) { + + waterType = contents; + waterLevel = WATERLEVEL_FEET; + + // check at waist level + point = current.origin - ( bounds[1][2] - bounds[0][2] ) * 0.5f * gravityNormal; + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); + if ( contents & MASK_WATER ) { + + waterLevel = WATERLEVEL_WAIST; + + // check at head level + point = current.origin - ( bounds[1][2] - 1.0f ) * gravityNormal; + contents = gameLocal.Contents( self, point, NULL, mat3_identity, -1, self ); + if ( contents & MASK_WATER ) { + waterLevel = WATERLEVEL_HEAD; + } + } + } +} + +/* +================ +idPhysics_Player::DropTimers +================ +*/ +void idPhysics_Player::DropTimers( void ) { + // drop misc timing counter + if ( current.movementTime ) { + if ( framemsec >= current.movementTime ) { + current.movementFlags &= ~PMF_ALL_TIMES; + current.movementTime = 0; + } + else { + current.movementTime -= framemsec; + } + } + + if ( groundPlane && current.crouchSlideTime ) { + if ( framemsec >= current.crouchSlideTime ) { + current.crouchSlideTime = 0; + } else { + current.crouchSlideTime -= framemsec; + } + } +} + +/* +================ +idPhysics_Player::MovePlayer +================ +*/ +void idPhysics_Player::MovePlayer( int msec ) { + walking = false; + groundPlane = false; + ladder = false; + + // determine the time + framemsec = msec; + frametime = framemsec * 0.001f; + + // default speed + playerSpeed = walkSpeed; + + // remove jumped and stepped up flag + current.movementFlags &= ~(PMF_JUMPED|PMF_STEPPED_UP|PMF_STEPPED_DOWN); + current.stepUp = 0.0f; + + if ( command.upmove < 10 ) { + // not holding jump + current.movementFlags &= ~PMF_JUMP_HELD; + } + + // if no movement at all + if ( current.movementType == PM_FREEZE ) { + return; + } + + // move the player velocity into the frame of a pusher + current.velocity -= current.pushVelocity; + + // view vectors + viewAngles.ToVectors( &viewForward, NULL, NULL ); + viewForward *= clipModelAxis; + viewRight = gravityNormal.Cross( viewForward ); + viewRight.Normalize(); + + // fly in spectator mode +// RAVEN BEGIN +// nmckenzie: Allowing ways to force spectator movement. + if ( current.movementType == PM_SPECTATOR || pm_forcespectatormove.GetBool() ) { +// RAVEN END + SpectatorMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // special no clip mode + if ( current.movementType == PM_NOCLIP ) { + idPhysics_Player::NoclipMove(); + idPhysics_Player::DropTimers(); +// RAVEN BEGIN +// abahr: need to clear pushVelocity. Was causing problems when noclipping while on a mover + ClearPushedVelocity(); +// RAVEN END + return; + } + + // no control when dead + if ( current.movementType == PM_DEAD ) { + command.forwardmove = 0; + command.rightmove = 0; + command.upmove = 0; + } + + // set watertype and waterlevel +// RAVEN BEGIN +// ddynerman: water disabled in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } +// RAVEN END + + // check for ground + idPhysics_Player::CheckGround( true ); + + // check if up against a ladder +// RAVEN BEGIN +// MrE: no ladders in MP + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::CheckLadder(); + } +// RAVEN END + + // set clip model size + idPhysics_Player::CheckDuck(); + + // handle timers + idPhysics_Player::DropTimers(); + + // move + if ( current.movementType == PM_DEAD ) { + // dead + idPhysics_Player::DeadMove(); + } + else if ( ladder ) { + // going up or down a ladder + idPhysics_Player::LadderMove(); + } +// RAVEN BEGIN +// ddynerman: water disabled in MP + else if ( !gameLocal.isMultiplayer && current.movementFlags & PMF_TIME_WATERJUMP ) { + + // jumping out of water + idPhysics_Player::WaterJumpMove(); + } + else if ( !gameLocal.isMultiplayer && waterLevel > 1 ) { +// RAVEN END + // swimming + idPhysics_Player::WaterMove(); + } + else if ( walking ) { + // walking on ground + idPhysics_Player::WalkMove(); + } + else { + // airborne + idPhysics_Player::AirMove(); + } + + if ( !gameLocal.isMultiplayer ) { + idPhysics_Player::SetWaterLevel(); + } + + idPhysics_Player::CheckGround( false ); + + // move the player velocity back into the world frame + current.velocity += current.pushVelocity; + current.lastPushVelocity = current.pushVelocity; + current.pushVelocity.Zero(); +} + + +/* +================ +idPhysics_Player::GetWaterLevel +================ +*/ +waterLevel_t idPhysics_Player::GetWaterLevel( void ) const { + return waterLevel; +} + +/* +================ +idPhysics_Player::GetWaterType +================ +*/ +int idPhysics_Player::GetWaterType( void ) const { + return waterType; +} + +/* +================ +idPhysics_Player::HasJumped +================ +*/ +bool idPhysics_Player::HasJumped( void ) const { + return ( ( current.movementFlags & PMF_JUMPED ) != 0 ); +} + +/* +================ +idPhysics_Player::HasSteppedUp +================ +*/ +bool idPhysics_Player::HasSteppedUp( void ) const { + return ( ( current.movementFlags & ( PMF_STEPPED_UP | PMF_STEPPED_DOWN ) ) != 0 ); +} + +/* +================ +idPhysics_Player::GetStepUp +================ +*/ +float idPhysics_Player::GetStepUp( void ) const { + return current.stepUp; +} + +/* +================ +idPhysics_Player::IsCrouching +================ +*/ +bool idPhysics_Player::IsCrouching( void ) const { + //MCG: if bound, never think we're crouched + return ( !masterEntity&&( current.movementFlags & PMF_DUCKED ) != 0 ); +} + +/* +================ +idPhysics_Player::IsJumping +================ +*/ +bool idPhysics_Player::IsJumping( void ) const { + return ( !masterEntity&&( current.movementFlags & PMF_JUMP_HELD ) != 0 ); +} + +/* +================ +idPhysics_Player::OnLadder +================ +*/ +bool idPhysics_Player::OnLadder( void ) const { + return ladder; +} + +/* +================ +idPhysics_Player::idPhysics_Player +================ +*/ +idPhysics_Player::idPhysics_Player( void ) { + debugLevel = false; + clipModel = NULL; + clipMask = 0; + memset( ¤t, 0, sizeof( current ) ); + saved = current; + walkSpeed = 0; + crouchSpeed = 0; + maxStepHeight = 0; + maxJumpHeight = 0; + memset( &command, 0, sizeof( command ) ); + viewAngles.Zero(); + framemsec = 0; + frametime = 0; + playerSpeed = 0; + viewForward.Zero(); + viewRight.Zero(); + walking = false; + groundPlane = false; + memset( &groundTrace, 0, sizeof( groundTrace ) ); + groundMaterial = NULL; + ladder = false; + ladderNormal.Zero(); + waterLevel = WATERLEVEL_NONE; + waterType = 0; +} + +/* +================ +idPhysics_Player_SavePState +================ +*/ +void idPhysics_Player_SavePState( idSaveGame *savefile, const playerPState_t &state ) { + savefile->WriteVec3( state.origin ); + savefile->WriteVec3( state.velocity ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteVec3( state.pushVelocity ); + + savefile->WriteVec3( state.lastPushVelocity ); // cnicholson Added unsaved var + + savefile->WriteFloat( state.stepUp ); + savefile->WriteInt( state.movementType ); + savefile->WriteInt( state.movementFlags ); + savefile->WriteInt( state.movementTime ); + + savefile->WriteInt( state.crouchSlideTime ); // cnicholson Added unsaved var +} + +/* +================ +idPhysics_Player_RestorePState +================ +*/ +void idPhysics_Player_RestorePState( idRestoreGame *savefile, playerPState_t &state ) { + savefile->ReadVec3( state.origin ); + savefile->ReadVec3( state.velocity ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadVec3( state.pushVelocity ); + + savefile->ReadVec3( state.lastPushVelocity ); // cnicholson Added unrestored var + + savefile->ReadFloat( state.stepUp ); + savefile->ReadInt( state.movementType ); + savefile->ReadInt( state.movementFlags ); + savefile->ReadInt( state.movementTime ); + + savefile->ReadInt( state.crouchSlideTime ); // cnicholson Added unrestored var +} + +/* +================ +idPhysics_Player::Save +================ +*/ +void idPhysics_Player::Save( idSaveGame *savefile ) const { + + idPhysics_Player_SavePState( savefile, current ); + idPhysics_Player_SavePState( savefile, saved ); + + savefile->WriteFloat( walkSpeed ); + savefile->WriteFloat( crouchSpeed ); + savefile->WriteFloat( maxStepHeight ); + savefile->WriteFloat( maxJumpHeight ); + savefile->WriteInt( debugLevel ); + + savefile->WriteUsercmd( command ); + savefile->WriteAngles( viewAngles ); + + savefile->WriteInt( framemsec ); + savefile->WriteFloat( frametime ); + savefile->WriteFloat( playerSpeed ); + savefile->WriteVec3( viewForward ); + savefile->WriteVec3( viewRight ); + + savefile->WriteBool( walking ); + savefile->WriteBool( groundPlane ); + savefile->WriteTrace( groundTrace ); + savefile->WriteMaterial( groundMaterial ); + + savefile->WriteBool( ladder ); + savefile->WriteVec3( ladderNormal ); + + savefile->WriteInt( (int)waterLevel ); + savefile->WriteInt( waterType ); +} + +/* +================ +idPhysics_Player::Restore +================ +*/ +void idPhysics_Player::Restore( idRestoreGame *savefile ) { + + idPhysics_Player_RestorePState( savefile, current ); + idPhysics_Player_RestorePState( savefile, saved ); + + savefile->ReadFloat( walkSpeed ); + savefile->ReadFloat( crouchSpeed ); + savefile->ReadFloat( maxStepHeight ); + savefile->ReadFloat( maxJumpHeight ); + savefile->ReadInt( debugLevel ); + + savefile->ReadUsercmd( command ); + savefile->ReadAngles( viewAngles ); + + savefile->ReadInt( framemsec ); + savefile->ReadFloat( frametime ); + savefile->ReadFloat( playerSpeed ); + savefile->ReadVec3( viewForward ); + savefile->ReadVec3( viewRight ); + + savefile->ReadBool( walking ); + savefile->ReadBool( groundPlane ); + savefile->ReadTrace( groundTrace ); + savefile->ReadMaterial( groundMaterial ); + + savefile->ReadBool( ladder ); + savefile->ReadVec3( ladderNormal ); + + savefile->ReadInt( (int &)waterLevel ); + savefile->ReadInt( waterType ); +} + +/* +================ +idPhysics_Player::SetPlayerInput +================ +*/ +void idPhysics_Player::SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ) { + command = cmd; + viewAngles = newViewAngles; // can't use cmd.angles cause of the delta_angles +} + +/* +================ +idPhysics_Player::SetSpeed +================ +*/ +void idPhysics_Player::SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ) { + walkSpeed = newWalkSpeed; + crouchSpeed = newCrouchSpeed; +} + +/* +================ +idPhysics_Player::SetMaxStepHeight +================ +*/ +void idPhysics_Player::SetMaxStepHeight( const float newMaxStepHeight ) { + maxStepHeight = newMaxStepHeight; +} + +/* +================ +idPhysics_Player::GetMaxStepHeight +================ +*/ +float idPhysics_Player::GetMaxStepHeight( void ) const { + return maxStepHeight; +} + +/* +================ +idPhysics_Player::SetMaxJumpHeight +================ +*/ +void idPhysics_Player::SetMaxJumpHeight( const float newMaxJumpHeight ) { + maxJumpHeight = newMaxJumpHeight; +} + +/* +================ +idPhysics_Player::SetMovementType +================ +*/ +void idPhysics_Player::SetMovementType( const pmtype_t type ) { + current.movementType = type; +} + +/* +================ +idPhysics_Player::SetKnockBack +================ +*/ +void idPhysics_Player::SetKnockBack( const int knockBackTime ) { + if ( current.movementTime ) { + return; + } + current.movementFlags |= PMF_TIME_KNOCKBACK; + current.movementTime = knockBackTime; +} + +/* +================ +idPhysics_Player::SetDebugLevel +================ +*/ +void idPhysics_Player::SetDebugLevel( bool set ) { + debugLevel = set; +} + +/* +================ +idPhysics_Player::Evaluate +================ +*/ +bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) { + idVec3 masterOrigin, oldOrigin; + idMat3 masterAxis; + + waterLevel = WATERLEVEL_NONE; + waterType = 0; + oldOrigin = current.origin; + + clipModel->Unlink(); + + // if bound to a master + if ( masterEntity ) { + assert( self ); + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + current.velocity = ( current.origin - oldOrigin ) / ( timeStepMSec * 0.001f ); + masterDeltaYaw = masterYaw; + masterYaw = masterAxis[0].ToYaw(); + masterDeltaYaw = masterYaw - masterDeltaYaw; + return true; + } + + ActivateContactEntities(); + + idPhysics_Player::MovePlayer( timeStepMSec ); + // I think the self is a remnant from early TV free fly code, can be removed now? + if ( self ) { + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + + // IsOutsideWorld uses self, so it needs to be non null + if ( IsOutsideWorld() ) { + gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self ? "NULL" : self->name.c_str(), current.origin.ToString(0) ); + } + } + + return true; //( current.origin != oldOrigin ); +} + +/* +================ +idPhysics_Player::UpdateTime +================ +*/ +void idPhysics_Player::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Player::GetTime +================ +*/ +int idPhysics_Player::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_Player::GetImpactInfo +================ +*/ +void idPhysics_Player::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + info->invMass = invMass; + info->invInertiaTensor.Zero(); + info->position.Zero(); + info->velocity = current.velocity; +} + +/* +================ +idPhysics_Player::ApplyImpulse +================ +*/ +void idPhysics_Player::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( current.movementType != PM_NOCLIP ) { + current.velocity += impulse * invMass; + } +} + +/* +================ +idPhysics_Player::IsAtRest +================ +*/ +bool idPhysics_Player::IsAtRest( void ) const { + return false; +} + +/* +================ +idPhysics_Player::GetRestStartTime +================ +*/ +int idPhysics_Player::GetRestStartTime( void ) const { + return -1; +} + +/* +================ +idPhysics_Player::SaveState +================ +*/ +void idPhysics_Player::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_Player::RestoreState +================ +*/ +void idPhysics_Player::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics_Player::SetOrigin +================ +*/ +void idPhysics_Player::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( masterEntity ) { + assert( self ); + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } + else { + current.origin = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// TTimo: only if tied to an ent + if ( self ) { + clipModel->Link( self, 0, newOrigin, clipModel->GetAxis() ); + } +// RAVEN END +} + +/* +================ +idPhysics_Player::GetOrigin +================ +*/ +const idVec3 & idPhysics_Player::PlayerGetOrigin( void ) const { + return current.origin; +} + +/* +================ +idPhysics_Player::SetAxis +================ +*/ +void idPhysics_Player::SetAxis( const idMat3 &newAxis, int id ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, clipModel->GetOrigin(), newAxis ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Translate +================ +*/ +void idPhysics_Player::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.origin += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::Rotate +================ +*/ +void idPhysics_Player::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + if ( masterEntity ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localOrigin = current.origin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() * rotation.ToMat3() ); +// RAVEN END +} + +/* +================ +idPhysics_Player::SetLinearVelocity +================ +*/ +void idPhysics_Player::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.velocity = newLinearVelocity; +} + +/* +================ +idPhysics_Player::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetLinearVelocity( int id ) const { + return current.velocity; +} + +/* +================ +idPhysics_Player::SetPushed +================ +*/ +void idPhysics_Player::SetPushed( int deltaTime ) { + idVec3 velocity; + float d; + + // velocity with which the player is pushed + velocity = ( current.origin - saved.origin ) / ( deltaTime * idMath::M_MS2SEC ); + + // remove any downward push velocity + d = velocity * gravityNormal; + if ( d > 0.0f ) { + velocity -= d * gravityNormal; + } + + current.pushVelocity += velocity; +} + +/* +================ +idPhysics_Player::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Player::GetPushedLinearVelocity( const int id ) const { + return current.lastPushVelocity; +} + +/* +================ +idPhysics_Player::ClearPushedVelocity +================ +*/ +void idPhysics_Player::ClearPushedVelocity( void ) { + current.pushVelocity.Zero(); + current.lastPushVelocity.Zero(); +} + +/* +================ +idPhysics_Player::SetMaster + + the binding is never orientated +================ +*/ +void idPhysics_Player::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !masterEntity ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + masterEntity = master; + masterYaw = masterAxis[0].ToYaw(); + } + ClearContacts(); + } else { + if ( masterEntity ) { + masterEntity = NULL; + } + } +} + +const float PLAYER_VELOCITY_MAX = 4000; +const int PLAYER_VELOCITY_TOTAL_BITS = 16; +const int PLAYER_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( PLAYER_VELOCITY_MAX ) ) + 1; +const int PLAYER_VELOCITY_MANTISSA_BITS = PLAYER_VELOCITY_TOTAL_BITS - 1 - PLAYER_VELOCITY_EXPONENT_BITS; +const int PLAYER_MOVEMENT_TYPE_BITS = 3; +const int PLAYER_MOVEMENT_FLAGS_BITS = 8; + +/* +================ +idPhysics_Player::WriteToSnapshot +================ +*/ +void idPhysics_Player::WriteToSnapshot( idBitMsgDelta &msg ) const { + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.velocity[2] ); + + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1] ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2] ); + + msg.WriteDeltaFloat( 0.0f, current.stepUp ); + msg.WriteBits( current.movementType, PLAYER_MOVEMENT_TYPE_BITS ); + msg.WriteBits( current.movementFlags, PLAYER_MOVEMENT_FLAGS_BITS ); + msg.WriteDeltaLong( 0, current.movementTime ); + msg.WriteDeltaLong( 0, current.crouchSlideTime ); +} + +/* +================ +idPhysics_Player::ReadFromSnapshot +================ +*/ +void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) { + + idVec3 oldOrigin = current.origin; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + + GAMELOG_SET( "origin_delta_x", (current.origin - oldOrigin).x ); + GAMELOG_SET( "origin_delta_y", (current.origin - oldOrigin).y ); + GAMELOG_SET( "origin_delta_z", (current.origin - oldOrigin).z ); + + idVec3 oldVelocity = current.velocity; + + current.velocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.velocity[2] = msg.ReadDeltaFloat( 0.0f ); + + GAMELOG_SET( "velocity_delta_x", (current.velocity - oldVelocity).x ); + GAMELOG_SET( "velocity_delta_y", (current.velocity - oldVelocity).y ); + GAMELOG_SET( "velocity_delta_z", (current.velocity - oldVelocity).z ); + + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f ); + + current.stepUp = msg.ReadDeltaFloat( 0.0f ); + current.movementType = msg.ReadBits( PLAYER_MOVEMENT_TYPE_BITS ); + + current.movementFlags = msg.ReadBits( PLAYER_MOVEMENT_FLAGS_BITS ); + + current.movementTime = msg.ReadDeltaLong( 0 ); + current.crouchSlideTime = msg.ReadDeltaLong( 0 ); + + if ( clipModel ) { + clipModel->Link( self, 0, current.origin, clipModel->GetAxis() ); + } +} + +/* +=============== +idPhysics_Player::SetClipModelNoLink +=============== +*/ +void idPhysics_Player::SetClipModelNoLink( idClipModel *model ) { + assert( model ); + assert( model->IsTraceModel() ); + + if ( clipModel && clipModel != model ) { + delete clipModel; + } + clipModel = model; +} diff --git a/src/mpgame/physics/Physics_Player.h b/src/mpgame/physics/Physics_Player.h new file mode 100644 index 0000000..d0c574a --- /dev/null +++ b/src/mpgame/physics/Physics_Player.h @@ -0,0 +1,221 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_PLAYER_H__ +#define __PHYSICS_PLAYER_H__ + +/* +=================================================================================== + + Player physics + + Simulates the motion of a player through the environment. Input from the + player is used to allow a certain degree of control over the motion. + +=================================================================================== +*/ + +// movementType +typedef enum { + PM_NORMAL, // normal physics + PM_DEAD, // no acceleration or turning, but free falling + PM_SPECTATOR, // flying without gravity but with collision detection + PM_FREEZE, // stuck in place without control + PM_NOCLIP // flying without collision detection nor gravity +} pmtype_t; + +typedef enum { + WATERLEVEL_NONE, + WATERLEVEL_FEET, + WATERLEVEL_WAIST, + WATERLEVEL_HEAD +} waterLevel_t; + +#define MAXTOUCH 32 + +typedef struct playerPState_s { + idVec3 origin; + idVec3 velocity; + idVec3 localOrigin; + idVec3 pushVelocity; + idVec3 lastPushVelocity; + float stepUp; + int movementType; + int movementFlags; + int movementTime; + int crouchSlideTime; +} playerPState_t; + +class idPhysics_Player : public idPhysics_Actor { + +public: + CLASS_PROTOTYPE( idPhysics_Player ); + + idPhysics_Player( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetSpeed( const float newWalkSpeed, const float newCrouchSpeed ); + void SetMaxStepHeight( const float newMaxStepHeight ); + float GetMaxStepHeight( void ) const; + void SetMaxJumpHeight( const float newMaxJumpHeight ); + void SetMovementType( const pmtype_t type ); + void SetPlayerInput( const usercmd_t &cmd, const idAngles &newViewAngles ); + void SetKnockBack( const int knockBackTime ); + void SetDebugLevel( bool set ); + // feed back from last physics frame + waterLevel_t GetWaterLevel( void ) const; + int GetWaterType( void ) const; + bool HasJumped( void ) const; + bool HasSteppedUp( void ) const; + float GetStepUp( void ) const; + bool IsCrouching( void ) const; + bool IsJumping( void ) const; + bool OnLadder( void ) const; + bool OnGround( void ) const; + const idVec3 & PlayerGetOrigin( void ) const; // != GetOrigin + +public: // common physics interface + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + void ClearPushedVelocity( void ); + + void SetMaster( idEntity *master, const bool orientated = true ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + + bool IsNoclip( void ) const; + bool IsDead( void ) const; + + void SetClipModelNoLink( idClipModel *clip ); + +private: + // player physics state + playerPState_t current; + playerPState_t saved; + + // properties + float walkSpeed; + float crouchSpeed; + float maxStepHeight; + float maxJumpHeight; + int debugLevel; // if set, diagnostic output will be printed + + // player input + usercmd_t command; + idAngles viewAngles; + + // run-time variables + int framemsec; + float frametime; + float playerSpeed; + idVec3 viewForward; + idVec3 viewRight; + + // walk movement + bool walking; + bool groundPlane; + trace_t groundTrace; + const idMaterial * groundMaterial; + + // ladder movement + bool ladder; + idVec3 ladderNormal; + + // results of last evaluate + waterLevel_t waterLevel; + int waterType; + +private: + float CmdScale( const usercmd_t &cmd ) const; + void Accelerate( const idVec3 &wishdir, const float wishspeed, const float accel ); + bool SlideMove( bool gravity, bool stepUp, bool stepDown, bool push ); + void Friction( void ); + void WaterJumpMove( void ); + void WaterMove( void ); + void FlyMove( void ); + void AirMove( void ); + void WalkMove( void ); + void DeadMove( void ); + void NoclipMove( void ); + void SpectatorMove( void ); + void LadderMove( void ); + void CorrectAllSolid( trace_t &trace, int contents ); + void CheckGround( bool checkStuck ); + void CheckDuck( void ); + void CheckLadder( void ); + bool CheckJump( void ); + bool CheckWaterJump( void ); + void SetWaterLevel( void ); + void DropTimers( void ); + void MovePlayer( int msec ); + + // forceLength: + // 0 => use the pm_slideVelocity / pm_powerSlide settings + // 1 => maintain velocity + // 2 => maintain horizontal velocity + idVec3 AdjustVertically( const idVec3 &normal, const idVec3 &in, int forceLength = 0 ) const; + + float Pm_Accelerate( void ); + float Pm_AirAccelerate( void ); +}; + +ID_INLINE bool idPhysics_Player::IsNoclip( void ) const { + return current.movementType == PM_NOCLIP; +} + +ID_INLINE bool idPhysics_Player::IsDead( void ) const { + return current.movementType == PM_DEAD; +} + +ID_INLINE bool idPhysics_Player::OnGround( void ) const { + return groundPlane; +} + +#endif /* !__PHYSICS_PLAYER_H__ */ diff --git a/src/mpgame/physics/Physics_RigidBody.cpp b/src/mpgame/physics/Physics_RigidBody.cpp new file mode 100644 index 0000000..b88f7c7 --- /dev/null +++ b/src/mpgame/physics/Physics_RigidBody.cpp @@ -0,0 +1,1646 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_Base, idPhysics_RigidBody ) +END_CLASS + +const float STOP_SPEED = 10.0f; + + +#undef RB_TIMINGS + +#ifdef RB_TIMINGS +static int lastTimerReset = 0; +static int numRigidBodies = 0; +static idTimer timer_total, timer_collision; +#endif + + +/* +================ +RigidBodyDerivatives +================ +*/ +void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ) { + const idPhysics_RigidBody *p = (idPhysics_RigidBody *) clientData; + rigidBodyIState_t *s = (rigidBodyIState_t *) state; + // NOTE: this struct should be build conform rigidBodyIState_t + struct rigidBodyDerivatives_s { + idVec3 linearVelocity; + idMat3 angularMatrix; + idVec3 force; + idVec3 torque; + } *d = (struct rigidBodyDerivatives_s *) derivatives; + idVec3 angularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = s->orientation * p->inverseInertiaTensor * s->orientation.Transpose(); + angularVelocity = inverseWorldInertiaTensor * s->angularMomentum; + // derivatives + d->linearVelocity = p->inverseMass * s->linearMomentum; + d->angularMatrix = SkewSymmetric( angularVelocity ) * s->orientation; + d->force = - p->linearFriction * s->linearMomentum + p->current.externalForce; + d->torque = - p->angularFriction * s->angularMomentum + p->current.externalTorque; +} + +/* +================ +idPhysics_RigidBody::Integrate + + Calculate next state from the current state using an integrator. +================ +*/ +void idPhysics_RigidBody::Integrate( float deltaTime, rigidBodyPState_t &next ) { + idVec3 position; + + position = current.i.position; + current.i.position += centerOfMass * current.i.orientation; + + current.i.orientation.TransposeSelf(); + + integrator->Evaluate( (float *) ¤t.i, (float *) &next.i, 0, deltaTime ); + next.i.orientation.OrthoNormalizeSelf(); + + // apply gravity + next.i.linearMomentum += deltaTime * gravityVector * mass; + + current.i.orientation.TransposeSelf(); + next.i.orientation.TransposeSelf(); + + current.i.position = position; + next.i.position -= centerOfMass * next.i.orientation; + + next.atRest = current.atRest; +} + +/* +================ +idPhysics_RigidBody::CollisionImpulse + + Calculates the collision impulse using the velocity relative to the collision object. + The current state should be set to the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CollisionImpulse( const trace_t &collision, idVec3 &impulse ) { + idVec3 r, linearVelocity, angularVelocity, velocity; + idMat3 inverseWorldInertiaTensor; + float impulseNumerator, impulseDenominator, vel; + impactInfo_t info; + idEntity *ent; + + // get info from other entity involved + ent = gameLocal.entities[collision.c.entityNum]; + ent->GetImpactInfo( self, collision.c.id, collision.c.point, &info ); + +// RAVEN BEGIN +// bdube: once in water take out the water flag and increase friction + if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { + clipMask &= ~CONTENTS_WATER; + linearFriction *= 20.0f; + angularFriction *= 20.0f; + } +// RAVEN END + + // collision point relative to the body center of mass + r = collision.c.point - (current.i.position + centerOfMass * current.i.orientation); + + // the velocity at the collision point + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + // subtract velocity of other entity + velocity -= info.velocity; + + // velocity in normal direction + vel = velocity * collision.c.normal; + + if ( vel > -STOP_SPEED ) { + impulseNumerator = STOP_SPEED; + } + else { + impulseNumerator = -( 1.0f + bouncyness ) * vel; + } + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( collision.c.normal ) ).Cross( r ) * collision.c.normal ); + if ( info.invMass ) { + impulseDenominator += info.invMass + ( ( info.invInertiaTensor * info.position.Cross( collision.c.normal ) ).Cross( info.position ) * collision.c.normal ); + } + impulse = (impulseNumerator / impulseDenominator) * collision.c.normal; + + // update linear and angular momentum with impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if no movement at all don't blow up + if ( collision.fraction < 0.0001f ) { + current.i.linearMomentum *= 0.5f; + current.i.angularMomentum *= 0.5f; + } + + // callback to self to let the entity know about the collision + return self->Collide( collision, velocity ); +} + +/* +================ +idPhysics_RigidBody::CheckForCollisions + + Check for collisions between the current and next state. + If there is a collision the next state is set to the state at the moment of impact. +================ +*/ +bool idPhysics_RigidBody::CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ) { +//#define TEST_COLLISION_DETECTION + idMat3 axis; + idRotation rotation; + bool collided = false; + +#ifdef TEST_COLLISION_DETECTION + bool startsolid; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + startsolid = true; + } +#endif + + TransposeMultiply( current.i.orientation, next.i.orientation, axis ); + rotation = axis.ToRotation(); + rotation.SetOrigin( current.i.position ); + + // if there was a collision +// RAVEN BEGIN +// ddynerman: multiple clip worlds + if ( gameLocal.Motion( self, collision, current.i.position, next.i.position, rotation, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + // set the next state to the state at the moment of impact + next.i.position = collision.endpos; + next.i.orientation = collision.endAxis; + next.i.linearMomentum = current.i.linearMomentum; + next.i.angularMomentum = current.i.angularMomentum; + collided = true; + } + +#ifdef TEST_COLLISION_DETECTION +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, next.i.position, clipModel, next.i.orientation, clipMask, self ) ) { +// RAVEN END + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return collided; +} + +/* +================ +idPhysics_RigidBody::ContactFriction + + Does not solve friction for multiple simultaneous contacts but applies contact friction in isolation. + Uses absolute velocity at the contact points instead of the velocity relative to the contact object. +================ +*/ +void idPhysics_RigidBody::ContactFriction( float deltaTime ) { + int i; + float magnitude, impulseNumerator, impulseDenominator; + idMat3 inverseWorldInertiaTensor; + idVec3 linearVelocity, angularVelocity; + idVec3 massCenter, r, velocity, normal, impulse, normalVelocity; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + + massCenter = current.i.position + centerOfMass * current.i.orientation; + + for ( i = 0; i < contacts.Num(); i++ ) { + + r = contacts[i].point - massCenter; + + // calculate velocity at contact point + linearVelocity = inverseMass * current.i.linearMomentum; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + velocity = linearVelocity + angularVelocity.Cross(r); + + // velocity along normal vector + normalVelocity = ( velocity * contacts[i].normal ) * contacts[i].normal; + + // calculate friction impulse + normal = -( velocity - normalVelocity ); + magnitude = normal.Normalize(); + impulseNumerator = contactFriction * magnitude; + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply friction impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross(impulse); + + // if moving towards the surface at the contact point + if ( normalVelocity * contacts[i].normal < 0.0f ) { + // calculate impulse + normal = -normalVelocity; + impulseNumerator = normal.Normalize(); + impulseDenominator = inverseMass + ( ( inverseWorldInertiaTensor * r.Cross( normal ) ).Cross( r ) * normal ); + impulse = (impulseNumerator / impulseDenominator) * normal; + + // apply impulse + current.i.linearMomentum += impulse; + current.i.angularMomentum += r.Cross( impulse ); + } + } +} + +/* +================ +idPhysics_RigidBody::TestIfAtRest + + Returns true if the body is considered at rest. + Does not catch all cases where the body is at rest but is generally good enough. +================ +*/ +bool idPhysics_RigidBody::TestIfAtRest( void ) const { + int i; + float gv; + idVec3 v, av, normal, point; + idMat3 inverseWorldInertiaTensor; + idFixedWinding contactWinding; + + if ( current.atRest >= 0 ) { + return true; + } + + // need at least 3 contact points to come to rest + if ( contacts.Num() < 3 ) { + return false; + } + + // get average contact plane normal + normal.Zero(); + for ( i = 0; i < contacts.Num(); i++ ) { + normal += contacts[i].normal; + } + normal /= (float) contacts.Num(); + normal.Normalize(); + + // if on a too steep surface + if ( (normal * gravityNormal) > -0.7f ) { + return false; + } + + // create bounds for contact points + contactWinding.Clear(); + for ( i = 0; i < contacts.Num(); i++ ) { + // project point onto plane through origin orthogonal to the gravity + point = contacts[i].point - (contacts[i].point * gravityNormal) * gravityNormal; + contactWinding.AddToConvexHull( point, gravityNormal ); + } + + // need at least 3 contact points to come to rest + if ( contactWinding.GetNumPoints() < 3 ) { + return false; + } + + // center of mass in world space + point = current.i.position + centerOfMass * current.i.orientation; + point -= (point * gravityNormal) * gravityNormal; + + // if the point is not inside the winding + if ( !contactWinding.PointInside( gravityNormal, point, 0 ) ) { + return false; + } + + // linear velocity of body + v = inverseMass * current.i.linearMomentum; + // linear velocity in gravity direction + gv = v * gravityNormal; + // linear velocity orthogonal to gravity direction + v -= gv * gravityNormal; + + // if too much velocity orthogonal to gravity direction + if ( v.Length() > STOP_SPEED ) { + return false; + } + // if too much velocity in gravity direction + if ( gv > 2.0f * STOP_SPEED || gv < -2.0f * STOP_SPEED ) { + return false; + } + + // calculate rotational velocity + inverseWorldInertiaTensor = current.i.orientation * inverseInertiaTensor * current.i.orientation.Transpose(); + av = inverseWorldInertiaTensor * current.i.angularMomentum; + + // if too much rotational velocity + if ( av.LengthSqr() > STOP_SPEED ) { + return false; + } + + return true; +} + +/* +================ +idPhysics_RigidBody::DropToFloorAndRest + + Drops the object straight down to the floor and verifies if the object is at rest on the floor. +================ +*/ +void idPhysics_RigidBody::DropToFloorAndRest( void ) { + idVec3 down; + trace_t tr; + + if ( testSolid ) { + + testSolid = false; +// RAVEN BEGIN +// ddynerman: multiple collision worlds + if ( gameLocal.Contents( self, current.i.position, clipModel, current.i.orientation, clipMask, self ) ) { +// RAVEN END + gameLocal.DWarning( "rigid body in solid for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + return; + } + } + + // put the body on the floor + down = current.i.position + gravityNormal * 128.0f; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( self, tr, current.i.position, down, clipModel, current.i.orientation, clipMask, self ); +// RAVEN END + current.i.position = tr.endpos; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), tr.endpos, current.i.orientation ); +// RAVEN END + + // if on the floor already + if ( tr.fraction == 0.0f ) { + // test if we are really at rest + EvaluateContacts(); + if ( !TestIfAtRest() ) { + gameLocal.DWarning( "rigid body not at rest for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + } + Rest(); + dropToFloor = false; + } else if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + dropToFloor = false; + } +} + +/* +================ +idPhysics_RigidBody::DebugDraw +================ +*/ +void idPhysics_RigidBody::DebugDraw( void ) { + + if ( rb_showBodies.GetBool() || ( rb_showActive.GetBool() && current.atRest < 0 ) ) { + collisionModelManager->DrawModel( clipModel->GetCollisionModel(), clipModel->GetOrigin(), clipModel->GetAxis(), vec3_origin, mat3_identity, 0.0f ); + } + + if ( rb_showMass.GetBool() ) { +// RAVEN BEGIN +// bdube: draw center of mass at the center of mass + gameRenderWorld->DrawText( va( "\n%1.2f", mass ), current.i.position + centerOfMass * current.i.orientation, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); +// RAVEN END + } + + if ( rb_showInertia.GetBool() ) { + idMat3 &I = inertiaTensor; + gameRenderWorld->DrawText( va( "\n\n\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )\n( %.1f %.1f %.1f )", + I[0].x, I[0].y, I[0].z, + I[1].x, I[1].y, I[1].z, + I[2].x, I[2].y, I[2].z ), + current.i.position, 0.05f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 ); + } + + if ( rb_showVelocity.GetBool() ) { + DrawVelocity( clipModel->GetId(), 0.1f, 4.0f ); + } + +// RAVEN BEGIN +// bdube: added more debug info + if ( rb_showContacts.GetBool() ) { + int i; + for ( i = 0; i < contacts.Num(); i ++ ) { + idVec3 x, y; + contacts[i].normal.NormalVectors( x, y ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point, contacts[i].point + 6.0f * contacts[i].normal ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * x, contacts[i].point + 2.0f * x ); + gameRenderWorld->DebugLine( colorWhite, contacts[i].point - 2.0f * y, contacts[i].point + 2.0f * y ); + } + } +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::idPhysics_RigidBody( void ) { + + // set default rigid body properties + SetClipMask( MASK_SOLID ); + SetBouncyness( 0.6f ); + SetFriction( 0.6f, 0.6f, 0.0f ); + clipModel = NULL; + + memset( ¤t, 0, sizeof( current ) ); + + current.atRest = -1; +// RAVEN BEGIN +// bdube: use GetMSec access rather than USERCMD_TIME + current.lastTimeStep = gameLocal.GetMSec(); +// RAVEN END + + current.i.position.Zero(); + current.i.orientation.Identity(); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + + saved = current; + + mass = 1.0f; + inverseMass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + inverseInertiaTensor.Identity(); + + // use the least expensive euler integrator + integrator = new idODE_Euler( sizeof(rigidBodyIState_t) / sizeof(float), RigidBodyDerivatives, this ); + + dropToFloor = false; + noImpact = false; + noContact = false; + + hasMaster = false; + isOrientated = false; + +#ifdef RB_TIMINGS + lastTimerReset = 0; +#endif +} + +/* +================ +idPhysics_RigidBody::~idPhysics_RigidBody +================ +*/ +idPhysics_RigidBody::~idPhysics_RigidBody( void ) { + if ( clipModel ) { + delete clipModel; + clipModel = NULL; + } + delete integrator; +} + +/* +================ +idPhysics_RigidBody_SavePState +================ +*/ +void idPhysics_RigidBody_SavePState( idSaveGame *savefile, const rigidBodyPState_t &state ) { + savefile->WriteInt( state.atRest ); + savefile->WriteFloat( state.lastTimeStep ); + savefile->WriteVec3( state.localOrigin ); + savefile->WriteMat3( state.localAxis ); + savefile->Write( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->WriteVec3( state.externalForce ); + savefile->WriteVec3( state.externalTorque ); + + savefile->WriteVec3( state.i.position ); + savefile->WriteMat3( state.i.orientation ); + savefile->WriteVec3( state.i.linearMomentum ); + savefile->WriteVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody_RestorePState +================ +*/ +void idPhysics_RigidBody_RestorePState( idRestoreGame *savefile, rigidBodyPState_t &state ) { + savefile->ReadInt( state.atRest ); + savefile->ReadFloat( state.lastTimeStep ); + savefile->ReadVec3( state.localOrigin ); + savefile->ReadMat3( state.localAxis ); + savefile->Read( &state.pushVelocity, sizeof( state.pushVelocity ) ); + savefile->ReadVec3( state.externalForce ); + savefile->ReadVec3( state.externalTorque ); + + savefile->ReadVec3( state.i.position ); + savefile->ReadMat3( state.i.orientation ); + savefile->ReadVec3( state.i.linearMomentum ); + savefile->ReadVec3( state.i.angularMomentum ); +} + +/* +================ +idPhysics_RigidBody::Save +================ +*/ +void idPhysics_RigidBody::Save( idSaveGame *savefile ) const { + + idPhysics_RigidBody_SavePState( savefile, current ); + idPhysics_RigidBody_SavePState( savefile, saved ); + + savefile->WriteFloat( linearFriction ); + savefile->WriteFloat( angularFriction ); + savefile->WriteFloat( contactFriction ); + savefile->WriteFloat( bouncyness ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteFloat( mass ); + savefile->WriteFloat( inverseMass ); + savefile->WriteVec3( centerOfMass ); + savefile->WriteMat3( inertiaTensor ); + savefile->WriteMat3( inverseInertiaTensor ); + + savefile->WriteBool( dropToFloor ); + savefile->WriteBool( testSolid ); + savefile->WriteBool( noImpact ); + savefile->WriteBool( noContact ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::Restore +================ +*/ +void idPhysics_RigidBody::Restore( idRestoreGame *savefile ) { + + idPhysics_RigidBody_RestorePState( savefile, current ); + idPhysics_RigidBody_RestorePState( savefile, saved ); + + savefile->ReadFloat( linearFriction ); + savefile->ReadFloat( angularFriction ); + savefile->ReadFloat( contactFriction ); + savefile->ReadFloat( bouncyness ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadFloat( mass ); + savefile->ReadFloat( inverseMass ); + savefile->ReadVec3( centerOfMass ); + savefile->ReadMat3( inertiaTensor ); + savefile->ReadMat3( inverseInertiaTensor ); + + savefile->ReadBool( dropToFloor ); + savefile->ReadBool( testSolid ); + savefile->ReadBool( noImpact ); + savefile->ReadBool( noContact ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_RigidBody::SetClipModel +================ +*/ +#define MAX_INERTIA_SCALE 10.0f + +void idPhysics_RigidBody::SetClipModel( idClipModel *model, const float density, int id, bool freeOld ) { + int minIndex; + idMat3 inertiaScale; + + assert( self ); + assert( model ); // we need a clip model + assert( model->IsTraceModel() ); // and it should be a trace model + assert( density > 0.0f ); // density should be valid + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.i.position, current.i.orientation ); +// RAVEN END + + // get mass properties from the trace model + clipModel->GetMassProperties( density, mass, centerOfMass, inertiaTensor ); + + // check whether or not the clip model has valid mass properties + if ( mass <= 0.0f || FLOAT_IS_NAN( mass ) ) { + gameLocal.Warning( "idPhysics_RigidBody::SetClipModel: invalid mass for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + mass = 1.0f; + centerOfMass.Zero(); + inertiaTensor.Identity(); + } + + // check whether or not the inertia tensor is balanced + minIndex = Min3Index( inertiaTensor[0][0], inertiaTensor[1][1], inertiaTensor[2][2] ); + inertiaScale.Identity(); + inertiaScale[0][0] = inertiaTensor[0][0] / inertiaTensor[minIndex][minIndex]; + inertiaScale[1][1] = inertiaTensor[1][1] / inertiaTensor[minIndex][minIndex]; + inertiaScale[2][2] = inertiaTensor[2][2] / inertiaTensor[minIndex][minIndex]; + + if ( inertiaScale[0][0] > MAX_INERTIA_SCALE || inertiaScale[1][1] > MAX_INERTIA_SCALE || inertiaScale[2][2] > MAX_INERTIA_SCALE ) { + gameLocal.DWarning( "idPhysics_RigidBody::SetClipModel: unbalanced inertia tensor for entity '%s' type '%s'", + self->name.c_str(), self->GetType()->classname ); + float min = inertiaTensor[minIndex][minIndex] * MAX_INERTIA_SCALE; + inertiaScale[(minIndex+1)%3][(minIndex+1)%3] = min / inertiaTensor[(minIndex+1)%3][(minIndex+1)%3]; + inertiaScale[(minIndex+2)%3][(minIndex+2)%3] = min / inertiaTensor[(minIndex+2)%3][(minIndex+2)%3]; + inertiaTensor *= inertiaScale; + } + + inverseMass = 1.0f / mass; + inverseInertiaTensor = inertiaTensor.Inverse() * ( 1.0f / 6.0f ); + + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); +} + +/* +================ +idPhysics_RigidBody::GetClipModel +================ +*/ +idClipModel *idPhysics_RigidBody::GetClipModel( int id ) const { + return clipModel; +} + +/* +================ +idPhysics_RigidBody::GetNumClipModels +================ +*/ +int idPhysics_RigidBody::GetNumClipModels( void ) const { + return 1; +} + +/* +================ +idPhysics_RigidBody::SetMass +================ +*/ +void idPhysics_RigidBody::SetMass( float mass, int id ) { + assert( mass > 0.0f ); + inertiaTensor *= mass / this->mass; + inverseInertiaTensor = inertiaTensor.Inverse() * (1.0f / 6.0f); + this->mass = mass; + inverseMass = 1.0f / mass; +} + +/* +================ +idPhysics_RigidBody::GetMass +================ +*/ +float idPhysics_RigidBody::GetMass( int id ) const { + return mass; +} + +// RAVEN BEGIN +// bdube: means of getting center of mass +/* +================ +idPhysics_RigidBody::GetCenterMass +================ +*/ +idVec3 idPhysics_RigidBody::GetCenterMass( int id ) const +{ + return (current.i.position + centerOfMass * current.i.orientation); +} +// RAVEN END + +/* +================ +idPhysics_RigidBody::SetFriction +================ +*/ +void idPhysics_RigidBody::SetFriction( const float linear, const float angular, const float contact ) { + if ( linear < 0.0f || linear > 1.0f || + angular < 0.0f || angular > 1.0f || + contact < 0.0f || contact > 1.0f ) { + return; + } + linearFriction = linear; + angularFriction = angular; + contactFriction = contact; +} + +/* +================ +idPhysics_RigidBody::SetBouncyness +================ +*/ +void idPhysics_RigidBody::SetBouncyness( const float b ) { + if ( b < 0.0f || b > 1.0f ) { + return; + } + bouncyness = b; +} + +/* +================ +idPhysics_RigidBody::Rest +================ +*/ +void idPhysics_RigidBody::Rest( void ) { + current.atRest = gameLocal.time; + current.i.linearMomentum.Zero(); + current.i.angularMomentum.Zero(); + self->BecomeInactive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::DropToFloor +================ +*/ +void idPhysics_RigidBody::DropToFloor( void ) { + dropToFloor = true; + testSolid = true; +} + +/* +================ +idPhysics_RigidBody::NoContact +================ +*/ +void idPhysics_RigidBody::NoContact( void ) { + noContact = true; +} + +/* +================ +idPhysics_RigidBody::Activate +================ +*/ +void idPhysics_RigidBody::Activate( void ) { + current.atRest = -1; + self->BecomeActive( TH_PHYSICS ); +} + +/* +================ +idPhysics_RigidBody::PutToRest + + put to rest untill something collides with this physics object +================ +*/ +void idPhysics_RigidBody::PutToRest( void ) { + Rest(); +} + +/* +================ +idPhysics_RigidBody::EnableImpact +================ +*/ +void idPhysics_RigidBody::EnableImpact( void ) { + noImpact = false; +} + +/* +================ +idPhysics_RigidBody::DisableImpact +================ +*/ +void idPhysics_RigidBody::DisableImpact( void ) { + noImpact = true; +} + +/* +================ +idPhysics_RigidBody::SetContents +================ +*/ +void idPhysics_RigidBody::SetContents( int contents, int id ) { + clipModel->SetContents( contents ); +} + +/* +================ +idPhysics_RigidBody::GetContents +================ +*/ +int idPhysics_RigidBody::GetContents( int id ) const { + return clipModel->GetContents(); +} + +/* +================ +idPhysics_RigidBody::GetBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetBounds( int id ) const { + return clipModel->GetBounds(); +} + +/* +================ +idPhysics_RigidBody::GetAbsBounds +================ +*/ +const idBounds &idPhysics_RigidBody::GetAbsBounds( int id ) const { + return clipModel->GetAbsBounds(); +} + +/* +================ +idPhysics_RigidBody::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool idPhysics_RigidBody::Evaluate( int timeStepMSec, int endTimeMSec ) { + rigidBodyPState_t next; + idAngles angles; + trace_t collision; + idVec3 impulse; + idEntity *ent; + idVec3 oldOrigin, masterOrigin; + idMat3 oldAxis, masterAxis; + float timeStep; + bool collided, cameToRest = false; + + timeStep = MS2SEC( timeStepMSec ); + current.lastTimeStep = timeStep; + + if ( hasMaster ) { + oldOrigin = current.i.position; + oldAxis = current.i.orientation; + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.i.orientation = current.localAxis * masterAxis; + } + else { + current.i.orientation = current.localAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + current.i.linearMomentum = mass * ( ( current.i.position - oldOrigin ) / timeStep ); + current.i.angularMomentum = inertiaTensor * ( ( current.i.orientation * oldAxis.Transpose() ).ToAngularVelocity() / timeStep ); + current.externalForce.Zero(); + current.externalTorque.Zero(); + + return ( current.i.position != oldOrigin || current.i.orientation != oldAxis ); + } + + // if the body is at rest + if ( current.atRest >= 0 || timeStep <= 0.0f ) { + DebugDraw(); + return false; + } + + // if putting the body to rest + if ( dropToFloor ) { + DropToFloorAndRest(); + current.externalForce.Zero(); + current.externalTorque.Zero(); + return true; + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Start(); + } +#endif + + // move the rigid body velocity into the frame of a pusher +// current.i.linearMomentum -= current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum -= current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + + clipModel->Unlink(); + + next = current; + + // calculate next position and orientation + Integrate( timeStep, next ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + + // check for collisions from the current to the next state + collided = CheckForCollisions( timeStep, next, collision ); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // set the new state + current = next; + +// trace_t pushResults; +// gameLocal.push.ClipPush( pushResults, self, PUSHFL_CRUSH | PUSHFL_CLIP, saved.localOrigin, saved.localAxis, current.localOrigin, current.localAxis ); + + if ( collided ) { + // apply collision impulse + if ( CollisionImpulse( collision, impulse ) ) { + current.atRest = gameLocal.time; + } + } + + // update the position of the clip model +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + DebugDraw(); + + if ( !noContact ) { + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Start(); + } +#endif + // get contacts + EvaluateContacts(); + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_collision.Stop(); + } +#endif + + // check if the body has come to rest + if ( TestIfAtRest() ) { + // put to rest + Rest(); + cameToRest = true; + } else { + // apply contact friction + ContactFriction( timeStep ); + } + } + + if ( current.atRest < 0 ) { + ActivateContactEntities(); + } + + if ( collided ) { + // if the rigid body didn't come to rest or the other entity is not at rest + ent = gameLocal.entities[collision.c.entityNum]; + if ( ent && ( !cameToRest || !ent->IsAtRest() ) ) { + // apply impact to other entity + ent->ApplyImpulse( self, collision.c.id, collision.c.point, -impulse ); + } + } + + // move the rigid body velocity back into the world frame +// current.i.linearMomentum += current.pushVelocity.SubVec3( 0 ) * mass; +// current.i.angularMomentum += current.pushVelocity.SubVec3( 1 ) * inertiaTensor; + current.pushVelocity.Zero(); + + current.lastTimeStep = timeStep; + current.externalForce.Zero(); + current.externalTorque.Zero(); + + if ( IsOutsideWorld() ) { + gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)", + self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); + Rest(); + } + +#ifdef RB_TIMINGS + if ( rb_showTimings->integer != 0 ) { + timer_total.Stop(); + + if ( rb_showTimings->integer == 1 ) { + gameLocal.Printf( "%12s: t %1.4f cd %1.4f\n", + self->name.c_str(), + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + lastTimerReset = 0; + } + else if ( rb_showTimings->integer == 2 ) { + numRigidBodies++; + if ( endTimeMSec > lastTimerReset ) { + gameLocal.Printf( "rb %d: t %1.4f cd %1.4f\n", + numRigidBodies, + timer_total.Milliseconds(), timer_collision.Milliseconds() ); + } + } + if ( endTimeMSec > lastTimerReset ) { + lastTimerReset = endTimeMSec; + numRigidBodies = 0; + timer_total.Clear(); + timer_collision.Clear(); + } + } +#endif + + return true; +} + +/* +================ +idPhysics_RigidBody::UpdateTime +================ +*/ +void idPhysics_RigidBody::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_RigidBody::GetTime +================ +*/ +int idPhysics_RigidBody::GetTime( void ) const { + return gameLocal.time; +} + +/* +================ +idPhysics_RigidBody::GetImpactInfo +================ +*/ +void idPhysics_RigidBody::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + idVec3 linearVelocity, angularVelocity; + idMat3 inverseWorldInertiaTensor; + + linearVelocity = inverseMass * current.i.linearMomentum; + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + angularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + + info->invMass = inverseMass; + info->invInertiaTensor = inverseWorldInertiaTensor; + info->position = point - ( current.i.position + centerOfMass * current.i.orientation ); + info->velocity = linearVelocity + angularVelocity.Cross( info->position ); +} + +/* +================ +idPhysics_RigidBody::ApplyImpulse +================ +*/ +void idPhysics_RigidBody::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { + if ( noImpact ) { + return; + } + current.i.linearMomentum += impulse; + current.i.angularMomentum += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( impulse ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::AddForce +================ +*/ +void idPhysics_RigidBody::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { + if ( noImpact ) { + return; + } + current.externalForce += force; + current.externalTorque += ( point - ( current.i.position + centerOfMass * current.i.orientation ) ).Cross( force ); + Activate(); +} + +/* +================ +idPhysics_RigidBody::IsAtRest +================ +*/ +bool idPhysics_RigidBody::IsAtRest( void ) const { + return current.atRest >= 0; +} + +/* +================ +idPhysics_RigidBody::GetRestStartTime +================ +*/ +int idPhysics_RigidBody::GetRestStartTime( void ) const { + return current.atRest; +} + +/* +================ +idPhysics_RigidBody::IsPushable +================ +*/ +bool idPhysics_RigidBody::IsPushable( void ) const { + return ( !noImpact && !hasMaster ); +} + +/* +================ +idPhysics_RigidBody::SaveState +================ +*/ +void idPhysics_RigidBody::SaveState( void ) { + saved = current; +} + +/* +================ +idPhysics_RigidBody::RestoreState +================ +*/ +void idPhysics_RigidBody::RestoreState( void ) { + current = saved; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + EvaluateContacts(); +} + +/* +================ +idPhysics::SetOrigin +================ +*/ +void idPhysics_RigidBody::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.position = masterOrigin + newOrigin * masterAxis; + } + else { + current.i.position = newOrigin; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::SetAxis +================ +*/ +void idPhysics_RigidBody::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.i.orientation = newAxis * masterAxis; + } + else { + current.i.orientation = newAxis; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), clipModel->GetOrigin(), current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Move +================ +*/ +void idPhysics_RigidBody::Translate( const idVec3 &translation, int id ) { + + current.localOrigin += translation; + current.i.position += translation; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, clipModel->GetAxis() ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics::Rotate +================ +*/ +void idPhysics_RigidBody::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.i.orientation *= rotation.ToMat3(); + current.i.position *= rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + current.localOrigin = current.i.position; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetOrigin +================ +*/ +const idVec3 &idPhysics_RigidBody::GetOrigin( int id ) const { + return current.i.position; +} + +/* +================ +idPhysics_RigidBody::GetAxis +================ +*/ +const idMat3 &idPhysics_RigidBody::GetAxis( int id ) const { + return current.i.orientation; +} + +/* +================ +idPhysics_RigidBody::SetLinearVelocity +================ +*/ +void idPhysics_RigidBody::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { + current.i.linearMomentum = newLinearVelocity * mass; + Activate(); +} + +/* +================ +idPhysics_RigidBody::SetAngularVelocity +================ +*/ +void idPhysics_RigidBody::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { + current.i.angularMomentum = newAngularVelocity * inertiaTensor; + Activate(); +} + +/* +================ +idPhysics_RigidBody::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetLinearVelocity( int id ) const { + static idVec3 curLinearVelocity; + curLinearVelocity = current.i.linearMomentum * inverseMass; + return curLinearVelocity; +} + +/* +================ +idPhysics_RigidBody::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetAngularVelocity( int id ) const { + static idVec3 curAngularVelocity; + idMat3 inverseWorldInertiaTensor; + + inverseWorldInertiaTensor = current.i.orientation.Transpose() * inverseInertiaTensor * current.i.orientation; + curAngularVelocity = inverseWorldInertiaTensor * current.i.angularMomentum; + return curAngularVelocity; +} + +/* +================ +idPhysics_RigidBody::ClipTranslation +================ +*/ +void idPhysics_RigidBody::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Translation( self, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipRotation +================ +*/ +void idPhysics_RigidBody::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + gameLocal.Rotation( self, results, clipModel->GetOrigin(), rotation, + clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::ClipContents +================ +*/ +int idPhysics_RigidBody::ClipContents( const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } + else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } +} + +/* +================ +idPhysics_RigidBody::DisableClip +================ +*/ +void idPhysics_RigidBody::DisableClip( void ) { + clipModel->Disable(); +} + +/* +================ +idPhysics_RigidBody::EnableClip +================ +*/ +void idPhysics_RigidBody::EnableClip( void ) { + clipModel->Enable(); +} + +/* +================ +idPhysics_RigidBody::UnlinkClip +================ +*/ +void idPhysics_RigidBody::UnlinkClip( void ) { + clipModel->Unlink(); +} + +/* +================ +idPhysics_RigidBody::LinkClip +================ +*/ +void idPhysics_RigidBody::LinkClip( void ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END +} + +/* +================ +idPhysics_RigidBody::EvaluateContacts +================ +*/ +bool idPhysics_RigidBody::EvaluateContacts( void ) { + idVec6 dir; + int num; + + ClearContacts(); + + contacts.SetNum( 10, false ); + + dir.SubVec3(0) = current.i.linearMomentum + current.lastTimeStep * gravityVector * mass; + dir.SubVec3(1) = current.i.angularMomentum; + dir.SubVec3(0).Normalize(); + dir.SubVec3(1).Normalize(); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + num = gameLocal.Contacts( self, &contacts[0], 10, clipModel->GetOrigin(), + dir, CONTACT_EPSILON, clipModel, clipModel->GetAxis(), clipMask, self ); +// RAVEN END + contacts.SetNum( num, false ); + + AddContactEntitiesForContacts(); + + return ( contacts.Num() != 0 ); +} + +/* +================ +idPhysics_RigidBody::SetPushed +================ +*/ +void idPhysics_RigidBody::SetPushed( int deltaTime ) { + idRotation rotation; + + rotation = ( saved.i.orientation * current.i.orientation ).ToRotation(); + + // velocity with which the af is pushed + current.pushVelocity.SubVec3(0) += ( current.i.position - saved.i.position ) / ( deltaTime * idMath::M_MS2SEC ); + current.pushVelocity.SubVec3(1) += rotation.GetVec() * -DEG2RAD( rotation.GetAngle() ) / ( deltaTime * idMath::M_MS2SEC ); +} + +/* +================ +idPhysics_RigidBody::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedLinearVelocity( const int id ) const { + return current.pushVelocity.SubVec3(0); +} + +/* +================ +idPhysics_RigidBody::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_RigidBody::GetPushedAngularVelocity( const int id ) const { + return current.pushVelocity.SubVec3(1); +} + +/* +================ +idPhysics_RigidBody::SetMaster +================ +*/ +void idPhysics_RigidBody::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.i.position - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.i.orientation * masterAxis.Transpose(); + } + else { + current.localAxis = current.i.orientation; + } + hasMaster = true; + isOrientated = orientated; + ClearContacts(); + } + } + else { + if ( hasMaster ) { + hasMaster = false; + Activate(); + } + } +} + +const float RB_VELOCITY_MAX = 16000; +const int RB_VELOCITY_TOTAL_BITS = 16; +const int RB_VELOCITY_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_VELOCITY_MAX ) ) + 1; +const int RB_VELOCITY_MANTISSA_BITS = RB_VELOCITY_TOTAL_BITS - 1 - RB_VELOCITY_EXPONENT_BITS; +const float RB_MOMENTUM_MAX = 1e20f; +const int RB_MOMENTUM_TOTAL_BITS = 16; +const int RB_MOMENTUM_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_MOMENTUM_MAX ) ) + 1; +const int RB_MOMENTUM_MANTISSA_BITS = RB_MOMENTUM_TOTAL_BITS - 1 - RB_MOMENTUM_EXPONENT_BITS; +const float RB_FORCE_MAX = 1e20f; +const int RB_FORCE_TOTAL_BITS = 16; +const int RB_FORCE_EXPONENT_BITS = idMath::BitsForInteger( idMath::BitsForFloat( RB_FORCE_MAX ) ) + 1; +const int RB_FORCE_MANTISSA_BITS = RB_FORCE_TOTAL_BITS - 1 - RB_FORCE_EXPONENT_BITS; + +/* +================ +idPhysics_RigidBody::WriteToSnapshot +================ +*/ +void idPhysics_RigidBody::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.i.orientation.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteLong( current.atRest ); + msg.WriteFloat( current.i.position[0] ); + msg.WriteFloat( current.i.position[1] ); + msg.WriteFloat( current.i.position[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteFloat( current.i.linearMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.linearMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[0], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[1], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteFloat( current.i.angularMomentum[2], RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + msg.WriteDeltaFloat( current.i.position[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.i.position[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.i.position[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[0], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[1], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.pushVelocity[2], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalForce[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[0], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[1], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + msg.WriteDeltaFloat( 0.0f, current.externalTorque[2], RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); +} + +/* +================ +idPhysics_RigidBody::ReadFromSnapshot +================ +*/ +void idPhysics_RigidBody::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.atRest = msg.ReadLong(); + current.i.position[0] = msg.ReadFloat(); + current.i.position[1] = msg.ReadFloat(); + current.i.position[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.i.linearMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.linearMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[0] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[1] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.i.angularMomentum[2] = msg.ReadFloat( RB_MOMENTUM_EXPONENT_BITS, RB_MOMENTUM_MANTISSA_BITS ); + current.localOrigin[0] = msg.ReadDeltaFloat( current.i.position[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.i.position[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.i.position[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + current.pushVelocity[0] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[1] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.pushVelocity[2] = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); + current.externalForce[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalForce[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[0] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[1] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + current.externalTorque[2] = msg.ReadDeltaFloat( 0.0f, RB_FORCE_EXPONENT_BITS, RB_FORCE_MANTISSA_BITS ); + + current.i.orientation = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, clipModel->GetId(), current.i.position, current.i.orientation ); +// RAVEN END + } +} diff --git a/src/mpgame/physics/Physics_RigidBody.h b/src/mpgame/physics/Physics_RigidBody.h new file mode 100644 index 0000000..bcada6b --- /dev/null +++ b/src/mpgame/physics/Physics_RigidBody.h @@ -0,0 +1,195 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_RIGIDBODY_H__ +#define __PHYSICS_RIGIDBODY_H__ + +/* +=================================================================================== + + Rigid body physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + +=================================================================================== +*/ + +typedef struct rididBodyIState_s { + idVec3 position; // position of trace model + idMat3 orientation; // orientation of trace model + idVec3 linearMomentum; // translational momentum relative to center of mass + idVec3 angularMomentum; // rotational momentum relative to center of mass +} rigidBodyIState_t; + +typedef struct rigidBodyPState_s { + int atRest; // set when simulation is suspended + float lastTimeStep; // length of last time step + idVec3 localOrigin; // origin relative to master + idMat3 localAxis; // axis relative to master + idVec6 pushVelocity; // push velocity + idVec3 externalForce; // external force relative to center of mass + idVec3 externalTorque; // external torque relative to center of mass + rigidBodyIState_t i; // state used for integration +} rigidBodyPState_t; + +class idPhysics_RigidBody : public idPhysics_Base { + +public: + + CLASS_PROTOTYPE( idPhysics_RigidBody ); + + idPhysics_RigidBody( void ); + ~idPhysics_RigidBody( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + // initialisation + void SetFriction( const float linear, const float angular, const float contact ); + void SetBouncyness( const float b ); + // same as above but drop to the floor first + void DropToFloor( void ); + // no contact determination and contact friction + void NoContact( void ); + // enable/disable activation by impact + void EnableImpact( void ); + void DisableImpact( void ); + +public: // common physics interface + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass ( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated ); + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + // state of the rigid body + rigidBodyPState_t current; + rigidBodyPState_t saved; + +private: + + // rigid body properties + float linearFriction; // translational friction + float angularFriction; // rotational friction + float contactFriction; // friction with contact surfaces + float bouncyness; // bouncyness + idClipModel * clipModel; // clip model used for collision detection + + // derived properties + float mass; // mass of body + float inverseMass; // 1 / mass + idVec3 centerOfMass; // center of mass of trace model + idMat3 inertiaTensor; // mass distribution + idMat3 inverseInertiaTensor; // inverse inertia tensor + + idODE * integrator; // integrator + bool dropToFloor; // true if dropping to the floor and putting to rest + bool testSolid; // true if testing for solid when dropping to the floor + bool noImpact; // if true do not activate when another object collides + bool noContact; // if true do not determine contacts and no contact friction + + // master + bool hasMaster; + bool isOrientated; + +private: + friend void RigidBodyDerivatives( const float t, const void *clientData, const float *state, float *derivatives ); + void Integrate( const float deltaTime, rigidBodyPState_t &next ); + bool CheckForCollisions( const float deltaTime, rigidBodyPState_t &next, trace_t &collision ); + bool CollisionImpulse( const trace_t &collision, idVec3 &impulse ); + void ContactFriction( float deltaTime ); + void DropToFloorAndRest( void ); + bool TestIfAtRest( void ) const; + void Rest( void ); + void DebugDraw( void ); +}; + +#endif /* !__PHYSICS_RIGIDBODY_H__ */ diff --git a/src/mpgame/physics/Physics_Static.cpp b/src/mpgame/physics/Physics_Static.cpp new file mode 100644 index 0000000..b0295c4 --- /dev/null +++ b/src/mpgame/physics/Physics_Static.cpp @@ -0,0 +1,905 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_Static ) +END_CLASS + +/* +================ +idPhysics_Static::idPhysics_Static +================ +*/ +idPhysics_Static::idPhysics_Static( void ) { + self = NULL; + clipModel = NULL; + current.origin.Zero(); + current.axis.Identity(); + current.localOrigin.Zero(); + current.localAxis.Identity(); + hasMaster = false; + isOrientated = false; +} + +/* +================ +idPhysics_Static::~idPhysics_Static +================ +*/ +idPhysics_Static::~idPhysics_Static( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + if ( clipModel ) { + delete clipModel; + } +} + +/* +================ +idPhysics_Static::Save +================ +*/ +void idPhysics_Static::Save( idSaveGame *savefile ) const { + savefile->WriteObject( self ); + + savefile->WriteVec3( current.origin ); + savefile->WriteMat3( current.axis ); + savefile->WriteVec3( current.localOrigin ); + savefile->WriteMat3( current.localAxis ); + savefile->WriteClipModel( clipModel ); + + savefile->WriteBool( hasMaster ); + savefile->WriteBool( isOrientated ); +} + +/* +================ +idPhysics_Static::Restore +================ +*/ +void idPhysics_Static::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadVec3( current.origin ); + savefile->ReadMat3( current.axis ); + savefile->ReadVec3( current.localOrigin ); + savefile->ReadMat3( current.localAxis ); + savefile->ReadClipModel( clipModel ); + + savefile->ReadBool( hasMaster ); + savefile->ReadBool( isOrientated ); +} + +/* +================ +idPhysics_Static::SetSelf +================ +*/ +void idPhysics_Static::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_Static::SetClipModel +================ +*/ +void idPhysics_Static::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + assert( self ); + + if ( clipModel && clipModel != model && freeOld ) { + delete clipModel; + } + clipModel = model; + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetClipModel +================ +*/ +idClipModel *idPhysics_Static::GetClipModel( int id ) const { + if ( clipModel ) { + return clipModel; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_Static::GetNumClipModels +================ +*/ +int idPhysics_Static::GetNumClipModels( void ) const { + return ( clipModel != NULL ); +} + +/* +================ +idPhysics_Static::SetMass +================ +*/ +void idPhysics_Static::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_Static::GetMass +================ +*/ +float idPhysics_Static::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_Static::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_Static::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_Static::SetContents +================ +*/ +void idPhysics_Static::SetContents( int contents, int id ) { + if ( clipModel ) { + clipModel->SetContents( contents ); + } +} + +/* +================ +idPhysics_Static::GetContents +================ +*/ +int idPhysics_Static::GetContents( int id ) const { + if ( clipModel ) { + return clipModel->GetContents(); + } + return 0; +} + +/* +================ +idPhysics_Static::SetClipMask +================ +*/ +void idPhysics_Static::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_Static::GetClipMask +================ +*/ +int idPhysics_Static::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetBounds +================ +*/ +const idBounds &idPhysics_Static::GetBounds( int id ) const { + if ( clipModel ) { + return clipModel->GetBounds(); + } + return bounds_zero; +} + +/* +================ +idPhysics_Static::GetAbsBounds +================ +*/ +const idBounds &idPhysics_Static::GetAbsBounds( int id ) const { + static idBounds absBounds; + + if ( clipModel ) { + return clipModel->GetAbsBounds(); + } + absBounds[0] = absBounds[1] = current.origin; + return absBounds; +} + +/* +================ +idPhysics_Static::Evaluate +================ +*/ +bool idPhysics_Static::Evaluate( int timeStepMSec, int endTimeMSec ) { +// RAVEN BEGIN +// bdube: draw bbox + if ( hasMaster ) { + idVec3 masterOrigin; + idVec3 oldOrigin; + idMat3 masterAxis; + idMat3 oldAxis; +// RAVEN END + + oldOrigin = current.origin; + oldAxis = current.axis; + + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + current.localOrigin * masterAxis; + if ( isOrientated ) { + current.axis = current.localAxis * masterAxis; + } else { + current.axis = current.localAxis; + } + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } + + return ( current.origin != oldOrigin || current.axis != oldAxis ); + } + return false; +} + +/* +================ +idPhysics_Static::UpdateTime +================ +*/ +void idPhysics_Static::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_Static::GetTime +================ +*/ +int idPhysics_Static::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetImpactInfo +================ +*/ +void idPhysics_Static::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_Static::ApplyImpulse +================ +*/ +void idPhysics_Static::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_Static::AddForce +================ +*/ +void idPhysics_Static::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_Static::Activate +================ +*/ +void idPhysics_Static::Activate( void ) { +} + +/* +================ +idPhysics_Static::PutToRest +================ +*/ +void idPhysics_Static::PutToRest( void ) { +} + +/* +================ +idPhysics_Static::IsAtRest +================ +*/ +bool idPhysics_Static::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_Static::GetRestStartTime +================ +*/ +int idPhysics_Static::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::IsPushable +================ +*/ +bool idPhysics_Static::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_Static::IsInWater +================ +*/ +bool idPhysics_Static::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_Static::SaveState +================ +*/ +void idPhysics_Static::SaveState( void ) { +} + +/* +================ +idPhysics_Static::RestoreState +================ +*/ +void idPhysics_Static::RestoreState( void ) { +} + +/* +================ +idPhysics_Static::SetOrigin +================ +*/ +void idPhysics_Static::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localOrigin = newOrigin; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.origin = masterOrigin + newOrigin * masterAxis; + } else { + current.origin = newOrigin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::SetAxis +================ +*/ +void idPhysics_Static::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.localAxis = newAxis; + + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.axis = newAxis * masterAxis; + } else { + current.axis = newAxis; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Translate +================ +*/ +void idPhysics_Static::Translate( const idVec3 &translation, int id ) { + current.localOrigin += translation; + current.origin += translation; + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::Rotate +================ +*/ +void idPhysics_Static::Rotate( const idRotation &rotation, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + current.origin *= rotation; + current.axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localAxis *= rotation.ToMat3(); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + current.localOrigin = current.origin; + } + + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::GetOrigin +================ +*/ +const idVec3 &idPhysics_Static::GetOrigin( int id ) const { + return current.origin; +} + +/* +================ +idPhysics_Static::GetAxis +================ +*/ +const idMat3 &idPhysics_Static::GetAxis( int id ) const { + return current.axis; +} + +/* +================ +idPhysics_Static::SetLinearVelocity +================ +*/ +void idPhysics_Static::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_Static::SetAngularVelocity +================ +*/ +void idPhysics_Static::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_Static::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetGravity +================ +*/ +void idPhysics_Static::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_Static::GetGravity +================ +*/ +const idVec3 &idPhysics_Static::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_Static::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_Static::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_Static::ClipTranslation +================ +*/ +void idPhysics_Static::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TranslationModel( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Translation( self, results, current.origin, current.origin + translation, + clipModel, current.axis, MASK_SOLID, self ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::ClipRotation +================ +*/ +void idPhysics_Static::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.RotationModel( self, results, current.origin, rotation, + clipModel, current.axis, MASK_SOLID, model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + gameLocal.Rotation( self, results, current.origin, rotation, clipModel, current.axis, MASK_SOLID, self ); + } +// RAVEN END +} + +/* +================ +idPhysics_Static::ClipContents +================ +*/ +int idPhysics_Static::ClipContents( const idClipModel *model ) const { + if ( clipModel ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + return gameLocal.ContentsModel( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + return gameLocal.Contents( self, clipModel->GetOrigin(), clipModel, clipModel->GetAxis(), -1, NULL ); +// RAVEN END + } + } + return 0; +} + +/* +================ +idPhysics_Static::DisableClip +================ +*/ +void idPhysics_Static::DisableClip( void ) { + if ( clipModel ) { + clipModel->Disable(); + } +} + +/* +================ +idPhysics_Static::EnableClip +================ +*/ +void idPhysics_Static::EnableClip( void ) { + if ( clipModel ) { + clipModel->Enable(); + } +} + +/* +================ +idPhysics_Static::UnlinkClip +================ +*/ +void idPhysics_Static::UnlinkClip( void ) { + if ( clipModel ) { + clipModel->Unlink(); + } +} + +/* +================ +idPhysics_Static::LinkClip +================ +*/ +void idPhysics_Static::LinkClip( void ) { + if ( clipModel ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( self, 0, current.origin, current.axis ); +// RAVEN END + } +} + +/* +================ +idPhysics_Static::EvaluateContacts +================ +*/ +bool idPhysics_Static::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_Static::GetNumContacts +================ +*/ +int idPhysics_Static::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetContact +================ +*/ +const contactInfo_t &idPhysics_Static::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_Static::ClearContacts +================ +*/ +void idPhysics_Static::ClearContacts( void ) { +} + +/* +================ +idPhysics_Static::AddContactEntity +================ +*/ +void idPhysics_Static::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::RemoveContactEntity +================ +*/ +void idPhysics_Static::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_Static::HasGroundContacts +================ +*/ +bool idPhysics_Static::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundEntity +================ +*/ +bool idPhysics_Static::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_Static::IsGroundClipModel +================ +*/ +bool idPhysics_Static::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_Static::SetPushed +================ +*/ +void idPhysics_Static::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_Static::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_Static::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_Static::SetMaster +================ +*/ +void idPhysics_Static::SetMaster( idEntity *master, const bool orientated ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + current.localOrigin = ( current.origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current.localAxis = current.axis * masterAxis.Transpose(); + } else { + current.localAxis = current.axis; + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_Static::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_Static::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetBlockingEntity +================ +*/ +idEntity *idPhysics_Static::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_Static::GetLinearEndTime +================ +*/ +int idPhysics_Static::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::GetAngularEndTime +================ +*/ +int idPhysics_Static::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_Static::WriteToSnapshot +================ +*/ +void idPhysics_Static::WriteToSnapshot( idBitMsgDelta &msg ) const { + idCQuat quat, localQuat; + + quat = current.axis.ToCQuat(); + localQuat = current.localAxis.ToCQuat(); + + msg.WriteFloat( current.origin[0] ); + msg.WriteFloat( current.origin[1] ); + msg.WriteFloat( current.origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current.origin[0], current.localOrigin[0] ); + msg.WriteDeltaFloat( current.origin[1], current.localOrigin[1] ); + msg.WriteDeltaFloat( current.origin[2], current.localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); +} + +/* +================ +idPhysics_Base::ReadFromSnapshot +================ +*/ +void idPhysics_Static::ReadFromSnapshot( const idBitMsgDelta &msg ) { + idCQuat quat, localQuat; + + current.origin[0] = msg.ReadFloat(); + current.origin[1] = msg.ReadFloat(); + current.origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current.localOrigin[0] = msg.ReadDeltaFloat( current.origin[0] ); + current.localOrigin[1] = msg.ReadDeltaFloat( current.origin[1] ); + current.localOrigin[2] = msg.ReadDeltaFloat( current.origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current.axis = quat.ToMat3(); + current.localAxis = localQuat.ToMat3(); +} diff --git a/src/mpgame/physics/Physics_Static.h b/src/mpgame/physics/Physics_Static.h new file mode 100644 index 0000000..6e713bf --- /dev/null +++ b/src/mpgame/physics/Physics_Static.h @@ -0,0 +1,178 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_STATIC_H__ +#define __PHYSICS_STATIC_H__ + +/* +=============================================================================== + + Physics for a non moving object using at most one collision model. + +=============================================================================== +*/ + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +class idStaticPState { + +public: + idVec3 origin; + idMat3 axis; + idVec3 localOrigin; + idMat3 localAxis; + + idStaticPState( void ) { } +}; +// RAVEN END + +class idPhysics_Static : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_Static ); + + idPhysics_Static( void ); + ~idPhysics_Static( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: // common physics interface + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idStaticPState current; // physics state +// RAVEN END + idClipModel * clipModel; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATIC_H__ */ diff --git a/src/mpgame/physics/Physics_StaticMulti.cpp b/src/mpgame/physics/Physics_StaticMulti.cpp new file mode 100644 index 0000000..6f7bcad --- /dev/null +++ b/src/mpgame/physics/Physics_StaticMulti.cpp @@ -0,0 +1,1111 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics, idPhysics_StaticMulti ) +END_CLASS + +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class +idStaticPState defaultState; +// RAVEN END + +/* +================ +idPhysics_StaticMulti::idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::idPhysics_StaticMulti( void ) { + self = NULL; + hasMaster = false; + isOrientated = false; + + defaultState.origin.Zero(); + defaultState.axis.Identity(); + defaultState.localOrigin.Zero(); + defaultState.localAxis.Identity(); + + current.SetNum( 1 ); + current[0] = defaultState; + clipModels.SetNum( 1 ); + clipModels[0] = NULL; +} + +/* +================ +idPhysics_StaticMulti::~idPhysics_StaticMulti +================ +*/ +idPhysics_StaticMulti::~idPhysics_StaticMulti( void ) { + if ( self && self->GetPhysics() == this ) { + self->SetPhysics( NULL ); + } + idForce::DeletePhysics( this ); + for ( int i = 0; i < clipModels.Num(); i++ ) { + delete clipModels[i]; + } +} + +/* +================ +idPhysics_StaticMulti::Save +================ +*/ +void idPhysics_StaticMulti::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteObject( self ); + + savefile->WriteInt(current.Num()); + for ( i = 0; i < current.Num(); i++ ) { + savefile->WriteVec3( current[i].origin ); + savefile->WriteMat3( current[i].axis ); + savefile->WriteVec3( current[i].localOrigin ); + savefile->WriteMat3( current[i].localAxis ); + } + + savefile->WriteInt( clipModels.Num() ); + for ( i = 0; i < clipModels.Num(); i++ ) { + savefile->WriteClipModel( clipModels[i] ); + } + + savefile->WriteBool(hasMaster); + savefile->WriteBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::Restore +================ +*/ +void idPhysics_StaticMulti::Restore( idRestoreGame *savefile ) { + int i, num; + + savefile->ReadObject( reinterpret_cast( self ) ); + + savefile->ReadInt(num); + current.AssureSize( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadVec3( current[i].origin ); + savefile->ReadMat3( current[i].axis ); + savefile->ReadVec3( current[i].localOrigin ); + savefile->ReadMat3( current[i].localAxis ); + } + + savefile->ReadInt(num); + clipModels.SetNum( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadClipModel( clipModels[i] ); + } + + savefile->ReadBool(hasMaster); + savefile->ReadBool(isOrientated); +} + +/* +================ +idPhysics_StaticMulti::SetSelf +================ +*/ +void idPhysics_StaticMulti::SetSelf( idEntity *e ) { + assert( e ); + self = e; +} + +/* +================ +idPhysics_StaticMulti::RemoveIndex +================ +*/ +void idPhysics_StaticMulti::RemoveIndex( int id, bool freeClipModel ) { + if ( id < 0 || id >= clipModels.Num() ) { + return; + } + if ( clipModels[id] && freeClipModel ) { + delete clipModels[id]; + clipModels[id] = NULL; + } + clipModels.RemoveIndex( id ); + current.RemoveIndex( id ); +} + +/* +================ +idPhysics_StaticMulti::SetClipModel +================ +*/ +void idPhysics_StaticMulti::SetClipModel( idClipModel *model, float density, int id, bool freeOld ) { + int i; + + assert( self ); + + if ( id >= clipModels.Num() ) { + current.AssureSize( id+1, defaultState ); + clipModels.AssureSize( id+1, NULL ); + } + + if ( clipModels[id] && clipModels[id] != model && freeOld ) { + delete clipModels[id]; + } + clipModels[id] = model; + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + + for ( i = clipModels.Num() - 1; i >= 1; i-- ) { + if ( clipModels[i] ) { + break; + } + } + current.SetNum( i+1, false ); + clipModels.SetNum( i+1, false ); +} + +/* +================ +idPhysics_StaticMulti::GetClipModel +================ +*/ +idClipModel *idPhysics_StaticMulti::GetClipModel( int id ) const { + if ( id >= 0 && id < clipModels.Num() && clipModels[id] ) { + return clipModels[id]; + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + return idClip::DefaultClipModel(); +// RAVEN END +} + +/* +================ +idPhysics_StaticMulti::GetNumClipModels +================ +*/ +int idPhysics_StaticMulti::GetNumClipModels( void ) const { + return clipModels.Num(); +} + +/* +================ +idPhysics_StaticMulti::SetMass +================ +*/ +void idPhysics_StaticMulti::SetMass( float mass, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetMass +================ +*/ +float idPhysics_StaticMulti::GetMass( int id ) const { + return 0.0f; +} + +// RAVEN BEGIN +// bdube: Added center mass call +/* +================ +idPhysics_StaticMulti::GetCenterMass + +default center of mass is origin +================ +*/ +idVec3 idPhysics_StaticMulti::GetCenterMass ( int id ) const { + return GetOrigin(); +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SetContents +================ +*/ +void idPhysics_StaticMulti::SetContents( int contents, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + clipModels[id]->SetContents( contents ); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->SetContents( contents ); + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetContents +================ +*/ +int idPhysics_StaticMulti::GetContents( int id ) const { + int i, contents = 0; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + contents = clipModels[id]->GetContents(); + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + contents |= clipModels[i]->GetContents(); + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::SetClipMask +================ +*/ +void idPhysics_StaticMulti::SetClipMask( int mask, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetClipMask +================ +*/ +int idPhysics_StaticMulti::GetClipMask( int id ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetBounds( int id ) const { + int i; + static idBounds bounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetBounds(); + } + } + if ( id == -1 ) { + bounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + bounds[0] -= clipModels[i]->GetOrigin(); + bounds[1] -= clipModels[i]->GetOrigin(); + break; + } + } + return bounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::GetAbsBounds +================ +*/ +const idBounds &idPhysics_StaticMulti::GetAbsBounds( int id ) const { + int i; + static idBounds absBounds; + + if ( id >= 0 && id < clipModels.Num() ) { + if ( clipModels[id] ) { + return clipModels[id]->GetAbsBounds(); + } + } + if ( id == -1 ) { + absBounds.Clear(); + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + absBounds.AddBounds( clipModels[i]->GetAbsBounds() ); + } + } + return absBounds; + } + return bounds_zero; +} + +/* +================ +idPhysics_StaticMulti::Evaluate +================ +*/ +bool idPhysics_StaticMulti::Evaluate( int timeStepMSec, int endTimeMSec ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin = masterOrigin + current[i].localOrigin * masterAxis; + if ( isOrientated ) { + current[i].axis = current[i].localAxis * masterAxis; + } else { + current[i].axis = current[i].localAxis; + } + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + + // FIXME: return false if master did not move + return true; + } + return false; +} + +/* +================ +idPhysics_StaticMulti::UpdateTime +================ +*/ +void idPhysics_StaticMulti::UpdateTime( int endTimeMSec ) { +} + +/* +================ +idPhysics_StaticMulti::GetTime +================ +*/ +int idPhysics_StaticMulti::GetTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetImpactInfo +================ +*/ +void idPhysics_StaticMulti::GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const { + memset( info, 0, sizeof( *info ) ); +} + +/* +================ +idPhysics_StaticMulti::ApplyImpulse +================ +*/ +void idPhysics_StaticMulti::ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ) { +} + +/* +================ +idPhysics_StaticMulti::AddForce +================ +*/ +void idPhysics_StaticMulti::AddForce( const int id, const idVec3 &point, const idVec3 &force ) { +} + +/* +================ +idPhysics_StaticMulti::Activate +================ +*/ +void idPhysics_StaticMulti::Activate( void ) { +} + +/* +================ +idPhysics_StaticMulti::PutToRest +================ +*/ +void idPhysics_StaticMulti::PutToRest( void ) { +} + +/* +================ +idPhysics_StaticMulti::IsAtRest +================ +*/ +bool idPhysics_StaticMulti::IsAtRest( void ) const { + return true; +} + +/* +================ +idPhysics_StaticMulti::GetRestStartTime +================ +*/ +int idPhysics_StaticMulti::GetRestStartTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::IsPushable +================ +*/ +bool idPhysics_StaticMulti::IsPushable( void ) const { + return false; +} + +// RAVEN BEGIN +// bdube: water interraction +/* +================ +idPhysics_StaticMulti::IsInWater +================ +*/ +bool idPhysics_StaticMulti::IsInWater ( void ) const { + return false; +} +// RAVEN END + +/* +================ +idPhysics_StaticMulti::SaveState +================ +*/ +void idPhysics_StaticMulti::SaveState( void ) { +} + +/* +================ +idPhysics_StaticMulti::RestoreState +================ +*/ +void idPhysics_StaticMulti::RestoreState( void ) { +} + +/* +================ +idPhysics_StaticMulti::SetOrigin +================ +*/ +void idPhysics_StaticMulti::SetOrigin( const idVec3 &newOrigin, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin = newOrigin; + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].origin = masterOrigin + newOrigin * masterAxis; + } else { + current[id].origin = newOrigin; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + Translate( masterOrigin + masterAxis * newOrigin - current[0].origin ); + } else { + Translate( newOrigin - current[0].origin ); + } + } +} + +/* +================ +idPhysics_StaticMulti::SetAxis +================ +*/ +void idPhysics_StaticMulti::SetAxis( const idMat3 &newAxis, int id ) { + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localAxis = newAxis; + if ( hasMaster && isOrientated ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].axis = newAxis * masterAxis; + } else { + current[id].axis = newAxis; + } + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + idMat3 axis; + idRotation rotation; + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + axis = current[0].axis.Transpose() * ( newAxis * masterAxis ); + } else { + axis = current[0].axis.Transpose() * newAxis; + } + rotation = axis.ToRotation(); + rotation.SetOrigin( current[0].origin ); + + Rotate( rotation ); + } +} + +/* +================ +idPhysics_StaticMulti::Translate +================ +*/ +void idPhysics_StaticMulti::Translate( const idVec3 &translation, int id ) { + int i; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].localOrigin += translation; + current[id].origin += translation; + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin += translation; + current[i].origin += translation; + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::Rotate +================ +*/ +void idPhysics_StaticMulti::Rotate( const idRotation &rotation, int id ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( id >= 0 && id < clipModels.Num() ) { + current[id].origin *= rotation; + current[id].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[id].localAxis *= rotation.ToMat3(); + current[id].localOrigin = ( current[id].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[id].localAxis = current[id].axis; + current[id].localOrigin = current[id].origin; + } + + if ( clipModels[id] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[id]->Link( self, id, current[id].origin, current[id].axis ); +// RAVEN END + } + } else if ( id == -1 ) { + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].origin *= rotation; + current[i].axis *= rotation.ToMat3(); + + if ( hasMaster ) { + self->GetMasterPosition( masterOrigin, masterAxis ); + current[i].localAxis *= rotation.ToMat3(); + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + current[i].localOrigin = current[i].origin; + } + + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } + } +} + +/* +================ +idPhysics_StaticMulti::GetOrigin +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetOrigin( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].origin; + } + if ( clipModels.Num() ) { + return current[0].origin; + } else { + return vec3_origin; + } +} + +/* +================ +idPhysics_StaticMulti::GetAxis +================ +*/ +const idMat3 &idPhysics_StaticMulti::GetAxis( int id ) const { + if ( id >= 0 && id < clipModels.Num() ) { + return current[id].axis; + } + if ( clipModels.Num() ) { + return current[0].axis; + } else { + return mat3_identity; + } +} + +/* +================ +idPhysics_StaticMulti::SetLinearVelocity +================ +*/ +void idPhysics_StaticMulti::SetLinearVelocity( const idVec3 &newLinearVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::SetAngularVelocity +================ +*/ +void idPhysics_StaticMulti::SetAngularVelocity( const idVec3 &newAngularVelocity, int id ) { +} + +/* +================ +idPhysics_StaticMulti::GetLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetLinearVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetAngularVelocity( int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetGravity +================ +*/ +void idPhysics_StaticMulti::SetGravity( const idVec3 &newGravity ) { +} + +/* +================ +idPhysics_StaticMulti::GetGravity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravity( void ) const { + static idVec3 gravity( 0, 0, -g_gravity.GetFloat() ); + if( gameLocal.isMultiplayer ) { + gravity = idVec3( 0, 0, -g_mp_gravity.GetFloat() ); + } + + return gravity; +} + +/* +================ +idPhysics_StaticMulti::GetGravityNormal +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetGravityNormal( void ) const { + static idVec3 gravity( 0, 0, -1 ); + return gravity; +} + +/* +================ +idPhysics_StaticMulti::ClipTranslation +================ +*/ +void idPhysics_StaticMulti::ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipTranslation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipRotation +================ +*/ +void idPhysics_StaticMulti::ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const { + memset( &results, 0, sizeof( trace_t ) ); + gameLocal.Warning( "idPhysics_StaticMulti::ClipRotation called" ); +} + +/* +================ +idPhysics_StaticMulti::ClipContents +================ +*/ +int idPhysics_StaticMulti::ClipContents( const idClipModel *model ) const { + int i, contents; + + contents = 0; + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + if ( model ) { +// RAVEN BEGIN +// ddynerman: multiple collision worlds + contents |= gameLocal.ContentsModel( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, + model->GetCollisionModel(), model->GetOrigin(), model->GetAxis() ); + } else { + contents |= gameLocal.Contents( self, clipModels[i]->GetOrigin(), clipModels[i], clipModels[i]->GetAxis(), -1, NULL ); +// RAVEN END + } + } + } + return contents; +} + +/* +================ +idPhysics_StaticMulti::DisableClip +================ +*/ +void idPhysics_StaticMulti::DisableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Disable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::EnableClip +================ +*/ +void idPhysics_StaticMulti::EnableClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Enable(); + } + } +} + +/* +================ +idPhysics_StaticMulti::UnlinkClip +================ +*/ +void idPhysics_StaticMulti::UnlinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { + clipModels[i]->Unlink(); + } + } +} + +/* +================ +idPhysics_StaticMulti::LinkClip +================ +*/ +void idPhysics_StaticMulti::LinkClip( void ) { + int i; + + for ( i = 0; i < clipModels.Num(); i++ ) { + if ( clipModels[i] ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModels[i]->Link( self, i, current[i].origin, current[i].axis ); +// RAVEN END + } + } +} + +/* +================ +idPhysics_StaticMulti::EvaluateContacts +================ +*/ +bool idPhysics_StaticMulti::EvaluateContacts( void ) { + return false; +} + +/* +================ +idPhysics_StaticMulti::GetNumContacts +================ +*/ +int idPhysics_StaticMulti::GetNumContacts( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetContact +================ +*/ +const contactInfo_t &idPhysics_StaticMulti::GetContact( int num ) const { + static contactInfo_t info; + memset( &info, 0, sizeof( info ) ); + return info; +} + +/* +================ +idPhysics_StaticMulti::ClearContacts +================ +*/ +void idPhysics_StaticMulti::ClearContacts( void ) { +} + +/* +================ +idPhysics_StaticMulti::AddContactEntity +================ +*/ +void idPhysics_StaticMulti::AddContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::RemoveContactEntity +================ +*/ +void idPhysics_StaticMulti::RemoveContactEntity( idEntity *e ) { +} + +/* +================ +idPhysics_StaticMulti::HasGroundContacts +================ +*/ +bool idPhysics_StaticMulti::HasGroundContacts( void ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundEntity +================ +*/ +bool idPhysics_StaticMulti::IsGroundEntity( int entityNum ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::IsGroundClipModel +================ +*/ +bool idPhysics_StaticMulti::IsGroundClipModel( int entityNum, int id ) const { + return false; +} + +/* +================ +idPhysics_StaticMulti::SetPushed +================ +*/ +void idPhysics_StaticMulti::SetPushed( int deltaTime ) { +} + +/* +================ +idPhysics_StaticMulti::GetPushedLinearVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedLinearVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::GetPushedAngularVelocity +================ +*/ +const idVec3 &idPhysics_StaticMulti::GetPushedAngularVelocity( const int id ) const { + return vec3_origin; +} + +/* +================ +idPhysics_StaticMulti::SetMaster +================ +*/ +void idPhysics_StaticMulti::SetMaster( idEntity *master, const bool orientated ) { + int i; + idVec3 masterOrigin; + idMat3 masterAxis; + + if ( master ) { + if ( !hasMaster ) { + // transform from world space to master space + self->GetMasterPosition( masterOrigin, masterAxis ); + for ( i = 0; i < clipModels.Num(); i++ ) { + current[i].localOrigin = ( current[i].origin - masterOrigin ) * masterAxis.Transpose(); + if ( orientated ) { + current[i].localAxis = current[i].axis * masterAxis.Transpose(); + } else { + current[i].localAxis = current[i].axis; + } + } + hasMaster = true; + isOrientated = orientated; + } + } else { + if ( hasMaster ) { + hasMaster = false; + } + } +} + +/* +================ +idPhysics_StaticMulti::GetBlockingInfo +================ +*/ +const trace_t *idPhysics_StaticMulti::GetBlockingInfo( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetBlockingEntity +================ +*/ +idEntity *idPhysics_StaticMulti::GetBlockingEntity( void ) const { + return NULL; +} + +/* +================ +idPhysics_StaticMulti::GetLinearEndTime +================ +*/ +int idPhysics_StaticMulti::GetLinearEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::GetAngularEndTime +================ +*/ +int idPhysics_StaticMulti::GetAngularEndTime( void ) const { + return 0; +} + +/* +================ +idPhysics_StaticMulti::WriteToSnapshot +================ +*/ +void idPhysics_StaticMulti::WriteToSnapshot( idBitMsgDelta &msg ) const { + int i; + idCQuat quat, localQuat; + + // TODO: Check that this conditional write to delta message is OK + msg.WriteByte( current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + quat = current[i].axis.ToCQuat(); + localQuat = current[i].localAxis.ToCQuat(); + + msg.WriteFloat( current[i].origin[0] ); + msg.WriteFloat( current[i].origin[1] ); + msg.WriteFloat( current[i].origin[2] ); + msg.WriteFloat( quat.x ); + msg.WriteFloat( quat.y ); + msg.WriteFloat( quat.z ); + msg.WriteDeltaFloat( current[i].origin[0], current[i].localOrigin[0] ); + msg.WriteDeltaFloat( current[i].origin[1], current[i].localOrigin[1] ); + msg.WriteDeltaFloat( current[i].origin[2], current[i].localOrigin[2] ); + msg.WriteDeltaFloat( quat.x, localQuat.x ); + msg.WriteDeltaFloat( quat.y, localQuat.y ); + msg.WriteDeltaFloat( quat.z, localQuat.z ); + } +} + +/* +================ +idPhysics_StaticMulti::ReadFromSnapshot +================ +*/ +void idPhysics_StaticMulti::ReadFromSnapshot( const idBitMsgDelta &msg ) { + int i, num; + idCQuat quat, localQuat; + + num = msg.ReadByte(); + assert( num == current.Num() ); + + for ( i = 0; i < current.Num(); i++ ) { + current[i].origin[0] = msg.ReadFloat(); + current[i].origin[1] = msg.ReadFloat(); + current[i].origin[2] = msg.ReadFloat(); + quat.x = msg.ReadFloat(); + quat.y = msg.ReadFloat(); + quat.z = msg.ReadFloat(); + current[i].localOrigin[0] = msg.ReadDeltaFloat( current[i].origin[0] ); + current[i].localOrigin[1] = msg.ReadDeltaFloat( current[i].origin[1] ); + current[i].localOrigin[2] = msg.ReadDeltaFloat( current[i].origin[2] ); + localQuat.x = msg.ReadDeltaFloat( quat.x ); + localQuat.y = msg.ReadDeltaFloat( quat.y ); + localQuat.z = msg.ReadDeltaFloat( quat.z ); + + current[i].axis = quat.ToMat3(); + current[i].localAxis = localQuat.ToMat3(); + } +} diff --git a/src/mpgame/physics/Physics_StaticMulti.h b/src/mpgame/physics/Physics_StaticMulti.h new file mode 100644 index 0000000..6c15a10 --- /dev/null +++ b/src/mpgame/physics/Physics_StaticMulti.h @@ -0,0 +1,166 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_STATICMULTI_H__ +#define __PHYSICS_STATICMULTI_H__ + +/* +=============================================================================== + + Physics for a non moving object using no or multiple collision models. + +=============================================================================== +*/ + +class idPhysics_StaticMulti : public idPhysics { + +public: + CLASS_PROTOTYPE( idPhysics_StaticMulti ); + + idPhysics_StaticMulti( void ); + ~idPhysics_StaticMulti( void ); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + void RemoveIndex( int id = 0, bool freeClipModel = true ); + +public: // common physics interface + + void SetSelf( idEntity *e ); +//RAVEN BEGIN +// abahr: for gravity + virtual idEntity* GetSelf() const { return self; } +// RAVEN END + + void SetClipModel( idClipModel *model, float density, int id = 0, bool freeOld = true ); + idClipModel * GetClipModel( int id = 0 ) const; + int GetNumClipModels( void ) const; + + void SetMass( float mass, int id = -1 ); + float GetMass( int id = -1 ) const; + +// RAVEN BEGIN +// bdube: means of getting center of mass + idVec3 GetCenterMass( int id = -1 ) const; +// RAVEN END + + void SetContents( int contents, int id = -1 ); + int GetContents( int id = -1 ) const; + + void SetClipMask( int mask, int id = -1 ); + int GetClipMask( int id = -1 ) const; + + const idBounds & GetBounds( int id = -1 ) const; + const idBounds & GetAbsBounds( int id = -1 ) const; + + bool Evaluate( int timeStepMSec, int endTimeMSec ); + void UpdateTime( int endTimeMSec ); + int GetTime( void ) const; + + void GetImpactInfo( const int id, const idVec3 &point, impactInfo_t *info ) const; + void ApplyImpulse( const int id, const idVec3 &point, const idVec3 &impulse ); + void AddForce( const int id, const idVec3 &point, const idVec3 &force ); + void Activate( void ); + void PutToRest( void ); + bool IsAtRest( void ) const; + int GetRestStartTime( void ) const; + bool IsPushable( void ) const; +// RAVEN BEGIN +// bdube: water interraction + bool IsInWater ( void ) const; +// RAVEN END + + void SaveState( void ); + void RestoreState( void ); + + void SetOrigin( const idVec3 &newOrigin, int id = -1 ); + void SetAxis( const idMat3 &newAxis, int id = -1 ); + + void Translate( const idVec3 &translation, int id = -1 ); + void Rotate( const idRotation &rotation, int id = -1 ); + + const idVec3 & GetOrigin( int id = 0 ) const; + const idMat3 & GetAxis( int id = 0 ) const; + + void SetLinearVelocity( const idVec3 &newLinearVelocity, int id = 0 ); + void SetAngularVelocity( const idVec3 &newAngularVelocity, int id = 0 ); + + const idVec3 & GetLinearVelocity( int id = 0 ) const; + const idVec3 & GetAngularVelocity( int id = 0 ) const; + + void SetGravity( const idVec3 &newGravity ); + const idVec3 & GetGravity( void ) const; + const idVec3 & GetGravityNormal( void ) const; + + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; + void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; + int ClipContents( const idClipModel *model ) const; + + void DisableClip( void ); + void EnableClip( void ); + + void UnlinkClip( void ); + void LinkClip( void ); + + bool EvaluateContacts( void ); + int GetNumContacts( void ) const; + const contactInfo_t & GetContact( int num ) const; + void ClearContacts( void ); + void AddContactEntity( idEntity *e ); + void RemoveContactEntity( idEntity *e ); + + bool HasGroundContacts( void ) const; + bool IsGroundEntity( int entityNum ) const; + bool IsGroundClipModel( int entityNum, int id ) const; + + void SetPushed( int deltaTime ); + const idVec3 & GetPushedLinearVelocity( const int id = 0 ) const; + const idVec3 & GetPushedAngularVelocity( const int id = 0 ) const; + + void SetMaster( idEntity *master, const bool orientated = true ); + + const trace_t * GetBlockingInfo( void ) const; + idEntity * GetBlockingEntity( void ) const; + + int GetLinearEndTime( void ) const; + int GetAngularEndTime( void ) const; + + void WriteToSnapshot( idBitMsgDelta &msg ) const; + void ReadFromSnapshot( const idBitMsgDelta &msg ); + +protected: + idEntity * self; // entity using this physics object +// RAVEN BEGIN +// rjohnson: converted this from a struct to a class + idList current; // physics state +// RAVEN END + idList clipModels; // collision model + + // master + bool hasMaster; + bool isOrientated; +}; + +#endif /* !__PHYSICS_STATICMULTI_H__ */ diff --git a/src/mpgame/physics/Physics_VehicleMonster.cpp b/src/mpgame/physics/Physics_VehicleMonster.cpp new file mode 100644 index 0000000..56793ca --- /dev/null +++ b/src/mpgame/physics/Physics_VehicleMonster.cpp @@ -0,0 +1,60 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +CLASS_DECLARATION( idPhysics_RigidBody, rvPhysics_VehicleMonster ) +END_CLASS + +/* +================ +rvPhysics_VehicleMonster::Evaluate + + Evaluate the impulse based rigid body physics. + When a collision occurs an impulse is applied at the moment of impact but + the remaining time after the collision is ignored. +================ +*/ +bool rvPhysics_VehicleMonster::Evaluate( int timeStepMSec, int endTimeMSec ) { + if ( idPhysics_RigidBody::Evaluate( timeStepMSec, endTimeMSec ) ) { + + idAngles euler = current.i.orientation.ToAngles(); + euler.pitch = 0.0f; + euler.roll = 0.0f; + current.i.orientation = euler.ToMat3(); + + return true; + } + + return false; +} + +void rvPhysics_VehicleMonster::SetGravity ( const idVec3 & v ) { + gravityVector = v; + gravityNormal = gameLocal.GetGravity( ); + gravityNormal.Normalize(); +} diff --git a/src/mpgame/physics/Physics_VehicleMonster.h b/src/mpgame/physics/Physics_VehicleMonster.h new file mode 100644 index 0000000..6764790 --- /dev/null +++ b/src/mpgame/physics/Physics_VehicleMonster.h @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PHYSICS_VEHICLE_MONSTER_H__ +#define __PHYSICS_VEHICLE_MONSTER_H__ + +/* +=================================================================================== + + Vehicle Monster Physics + + Employs an impulse based dynamic simulation which is not very accurate but + relatively fast and still reliable due to the continuous collision detection. + Extents particle physics with the ability to apply impulses. + +=================================================================================== +*/ + +class rvPhysics_VehicleMonster : public idPhysics_RigidBody { +public: + CLASS_PROTOTYPE( rvPhysics_VehicleMonster ); + + bool Evaluate ( int timeStepMSec, int endTimeMSec ); + void SetGravity ( const idVec3 & v ); +}; + +#endif /* !__PHYSICS_VEHICLE_MONSTER_H__ */ diff --git a/src/mpgame/physics/Push.cpp b/src/mpgame/physics/Push.cpp new file mode 100644 index 0000000..62ddf25 --- /dev/null +++ b/src/mpgame/physics/Push.cpp @@ -0,0 +1,1526 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +// RAVEN BEGIN +// bdube: projectile +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif +// RAVEN END + +/* +============ +idPush::InitSavingPushedEntityPositions +============ +*/ +void idPush::InitSavingPushedEntityPositions( void ) { + numPushed = 0; +} + +/* +============ +idPush::SaveEntityPosition +============ +*/ +void idPush::SaveEntityPosition( idEntity *ent ) { + int i; + + // if already saved the physics state for this entity + for ( i = 0; i < numPushed; i++ ) { + if ( pushed[i].ent == ent ) { + return; + } + } + + // don't overflow + if ( numPushed >= MAX_GENTITIES ) { + gameLocal.Error( "more than MAX_GENTITIES pushed entities" ); + return; + } + + pushed[numPushed].ent = ent; + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // save the delta view angles + pushed[numPushed].deltaViewAngles = static_cast(ent)->GetDeltaViewAngles(); + } + + // save the physics state + ent->GetPhysics()->SaveState(); + + numPushed++; +} + +/* +============ +idPush::RestorePushedEntityPositions +============ +*/ +void idPush::RestorePushedEntityPositions( void ) { + int i; + + for ( i = 0; i < numPushed; i++ ) { + + // if the entity is an actor +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( pushed[i].ent->IsType( idActor::GetClassType() ) ) { +// RAVEN END + // set back the delta view angles + static_cast(pushed[i].ent)->SetDeltaViewAngles( pushed[i].deltaViewAngles ); + } + + // restore the physics state + pushed[i].ent->GetPhysics()->RestoreState(); + } +} + +/* +============ +idPush::RotateEntityToAxial +============ +*/ +bool idPush::RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ) { + int i; + trace_t trace; + idRotation rotation; + idMat3 axis; + idPhysics *physics; + + physics = ent->GetPhysics(); + axis = physics->GetAxis(); + if ( !axis.IsRotated() ) { + return true; + } + +// RAVEN BEGIN +// abahr: caching current upVector to reduce visual effects of fp error + idVec3 upVector = -physics->GetGravityNormal(); +// RAVEN END + + // try to rotate the bbox back to axial with at most four rotations + for ( i = 0; i < 4; i++ ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + axis = physics->GetAxis()[2].ToMat3( 2 ); +// RAVEN END + rotation = axis.ToRotation(); + rotation.Scale( -1 ); + rotation.SetOrigin( rotationPoint ); + // tiny float numbers in the clip axis, this can get the entity stuck + if ( rotation.GetAngle() == 0.0f ) { +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // + ent->GetPhysics()->ClipRotation( trace, rotation, NULL ); + // if the full rotation is possible + if ( trace.fraction >= 1.0f ) { + // set bbox in final axial position + physics->SetOrigin( trace.endpos ); +// RAVEN BEGIN +// abahr: because of gravity we need to build axis from up vector + physics->SetAxis( upVector.ToMat3(2) ); +// RAVEN END + return true; + } + // if partial rotation was possible + else if ( trace.fraction > 0.0f ) { + // partial rotation + physics->SetOrigin( trace.endpos ); + physics->SetAxis( trace.endAxis ); + } + // next rotate around collision point + rotationPoint = trace.c.point; + } + return false; +} + +#ifdef NEW_PUSH + +/* +============ +idPush::CanPushEntity +============ +*/ +bool idPush::CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ) { + + // if the physics object is not pushable + if ( !ent->GetPhysics()->IsPushable() ) { + return false; + } + + // if the entity doesn't clip with this pusher + if ( !( ent->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + return false; + } + + // don't push players in noclip mode + if ( ent->client && ent->client->noclip ) { + return false; + } + + // if we should only push idMoveable entities + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !ent->IsType( idMoveable::Type ) ) { + return false; + } + + // if we shouldn't push entities the original pusher rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( initialPusher->GetPhysics()->IsGroundEntity( ent->entityNumber ) ) { + return false; + } + } + + return true; +} + +/* +============ +idPush::AddEntityToPushedGroup +============ +*/ +void idPush::AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ) { + int i, j; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + if ( fraction > pushedGroup[i].fraction ) { + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact &= groundContact; + pushedGroup[i].test = true; + } + return; + } + else if ( fraction > pushedGroup[i].fraction ) { + for ( j = pushedGroupSize; j > i; j-- ) { + pushedGroup[j] = pushedGroup[j-1]; + } + break; + } + } + + // put the entity in the group + pushedGroupSize++; + pushedGroup[i].ent = ent; + pushedGroup[i].fraction = fraction; + pushedGroup[i].groundContact = groundContact; + pushedGroup[i].test = true; + + // remove any further occurances of the same entity in the group + for ( i++; i < pushedGroupSize; i++ ) { + if ( ent == pushedGroup[i].ent ) { + for ( j = i+1; j < pushedGroupSize; j++ ) { + pushedGroup[j-1] = pushedGroup[j]; + } + pushedGroupSize--; + break; + } + } +} + +/* +============ +idPush::IsFullyPushed +============ +*/ +bool idPush::IsFullyPushed( idEntity *ent ) { + int i; + + for ( i = 0; i < pushedGroupSize; i++ ) { + if ( pushedGroup[i].fraction < 1.0f ) { + return false; + } + if ( ent == pushedGroup[i].ent ) { + return true; + } + } + return false; +} + +/* +============ +idPush::ClipTranslationAgainstPusher +============ +*/ +bool idPush::ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipTranslation( t, translation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForTranslation +============ +*/ +int idPush::GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsTranslation( bounds, physics->GetOrigin(), physics->GetAxis(), translation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( this, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by translating the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idVec3 realTranslation, partialTranslation; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = pusher->GetPhysics()->GetAxis(); + memset( results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForTranslation( pusher, pusher, flags, translation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip translation + pusher->GetPhysics()->ClipTranslation( results, translation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realTranslation = results.fraction * translation; + } + else { + realTranslation = translation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForTranslation( curPusher, pusher, flags, realTranslation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( !CanPushEntity( ent, curPusher, pusher, flags ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipTranslationAgainstPusher( trace, ent, curPusher, -fraction * realTranslation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 0; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipTranslation( trace, partialTranslation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipTranslation( results, realTranslation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // translate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialTranslation = realTranslation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Translate( partialTranslation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // translate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Translate( results.fraction * realTranslation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + } + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationAgainstPusher +============ +*/ +bool idPush::ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ) { + int i, n; + trace_t t; + + results.fraction = 1.0f; + + n = pusher->GetPhysics()->GetNumClipModels(); + for ( i = 0; i < n; i++ ) { + ent->GetPhysics()->ClipRotation( t, rotation, pusher->GetPhysics()->GetClipModel( i ) ); + if ( t.fraction < results.fraction ) { + results = t; + } + } + return ( results.fraction < 1.0f ); +} + +/* +============ +idPush::GetPushableEntitiesForRotation +============ +*/ +int idPush::GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ) { + int i, n, l; + idBounds bounds, pushBounds; + idPhysics *physics; + + // get bounds for the whole movement + physics = pusher->GetPhysics(); + bounds = physics->GetBounds(); + pushBounds.FromBoundsRotation( bounds, physics->GetOrigin(), physics->GetAxis(), rotation ); + pushBounds.ExpandSelf( 2.0f ); + + // get all entities within the push bounds +// RAVEN BEGIN +// ddynerman: multiple clip worlds + n = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + for ( l = i = 0; i < n; i++ ) { + if ( entityList[i] == pusher || entityList[i] == initialPusher ) { + continue; + } + if ( CanPushEntity( entityList[i], pusher, initialPusher, flags ) ) { + entityList[l++] = entityList[i]; + } + } + + return l; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by rotating the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, j, numListedEntities; + idEntity *curPusher, *ent, *entityList[ MAX_GENTITIES ]; + float fraction; + bool groundContact, blocked = false; + float totalMass; + trace_t trace; + idRotation realRotation, partialRotation; + idMat3 oldAxis; + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = newAxis; + memset( results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // clip against all non-pushable physics objects + if ( flags & PUSHFL_CLIP ) { + + numListedEntities = GetPushableEntitiesForRotation( pusher, pusher, flags, rotation, entityList, MAX_GENTITIES ); + // disable pushable entities for collision detection + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } + // clip rotation + pusher->GetPhysics()->ClipRotation( results, rotation, NULL ); + // enable pushable entities + for ( i = 0; i < numListedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + if ( results.fraction == 0.0f ) { + return totalMass; + } + realRotation = results.fraction * rotation; + } + else { + realRotation = rotation; + } + + // put the pusher in the group of pushed physics objects + pushedGroup[0].ent = pusher; + pushedGroup[0].fraction = 1.0f; + pushedGroup[0].groundContact = true; + pushedGroup[0].test = true; + pushedGroupSize = 1; + + // get all physics objects that need to be pushed + for ( i = 0; i < pushedGroupSize; ) { + if ( !pushedGroup[i].test ) { + i++; + continue; + } + pushedGroup[i].test = false; + curPusher = pushedGroup[i].ent; + fraction = pushedGroup[i].fraction; + groundContact = pushedGroup[i].groundContact; + i = 0; + + numListedEntities = GetPushableEntitiesForRotation( curPusher, pusher, flags, realRotation, entityList, MAX_GENTITIES ); + + for ( j = 0; j < numListedEntities; j++ ) { + ent = entityList[ j ]; + + if ( IsFullyPushed( ent ) ) { + continue; + } + + if ( ent->GetPhysics()->IsGroundEntity( curPusher->entityNumber ) ) { + AddEntityToPushedGroup( ent, 1.0f * fraction, false ); + } + else if ( ClipRotationAgainstPusher( trace, ent, curPusher, -fraction * realRotation ) ) { + AddEntityToPushedGroup( ent, ( 1.0f - trace.fraction ) * fraction, groundContact ); + } + } + } + + // save physics states and disable physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + SaveEntityPosition( pushedGroup[i].ent ); + pushedGroup[i].ent->GetPhysics()->DisableClip(); + } + + // clip all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + + pushedGroup[i].ent->GetPhysics()->ClipRotation( trace, partialRotation, NULL ); + + if ( trace.fraction < 1.0f ) { + blocked = true; + break; + } + } + + // enable all physics objects for collision detection + for ( i = 1; i < pushedGroupSize; i++ ) { + pushedGroup[i].ent->GetPhysics()->EnableClip(); + } + + // push all or nothing + if ( blocked ) { + if ( flags & PUSHFL_CLIP ) { + pusher->GetPhysics()->ClipRotation( results, realRotation, NULL ); + } + else { + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + else { + // rotate all pushed physics objects + for ( i = 1; i < pushedGroupSize; i++ ) { + partialRotation = realRotation * pushedGroup[i].fraction; + pushedGroup[i].ent->GetPhysics()->Rotate( partialRotation ); + totalMass += pushedGroup[i].ent->GetPhysics()->GetMass(); + } + // rotate the clip models of the pusher + for ( i = 0; i < pusher->GetPhysics()->GetNumClipModels(); i++ ) { + pusher->GetPhysics()->GetClipModel(i)->Rotate( realRotation ); + pusher->GetPhysics()->GetClipModel(i)->Link( gameLocal.clip ); + pusher->GetPhysics()->GetClipModel(i)->Enable(); + } + // rotate any actors back to axial + for ( i = 1; i < pushedGroupSize; i++ ) { + // if the entity is using actor physics + if ( pushedGroup[i].ent->GetPhysics()->IsType( idPhysics_Actor::Type ) ) { + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( pushedGroup[i].ent, pushedGroup[i].ent->GetPhysics()->GetOrigin() ) ) { + // don't allow rotation if the bbox is no longer axial + results.fraction = 0.0f; + results.endpos = pusher->GetPhysics()->GetOrigin(); + results.endAxis = pusher->GetPhysics()->GetAxis(); + } + } + } + } + + return totalMass; +} + +#else /* !NEW_PUSH */ + +enum { + PUSH_NO, // not pushed + PUSH_OK, // pushed ok + PUSH_BLOCKED // blocked +}; + +/* +============ +idPush::ClipEntityRotation +============ +*/ +void idPush::ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idRotation &rotation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipRotation( trace, rotation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::ClipEntityTranslation +============ +*/ +void idPush::ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, idClipModel *skip, const idVec3 &translation ) { + + if ( skip ) { + skip->Disable(); + } + + ent->GetPhysics()->ClipTranslation( trace, translation, clipModel ); + + if ( skip ) { + skip->Enable(); + } +} + +/* +============ +idPush::TryRotatePushEntity +============ +*/ +#ifdef _DEBUG +// #define ROTATIONAL_PUSH_DEBUG +#endif + +int idPush::TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + trace_t trace; + idVec3 rotationPoint; + idRotation newRotation; + float checkAngle; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef ROTATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate the entity colliding with all other entities except the pusher itself + ClipEntityRotation( trace, check, NULL, clipModel, rotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle() * trace.fraction; + // test if the entity can stay at it's partly pushed position by rotating + // the entity in reverse only colliding with pusher + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), -(rotation.GetAngle() - checkAngle) ); + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // angle along which the entity is pushed + checkAngle = rotation.GetAngle(); + } + // point to rotate entity bbox around back to axial + rotationPoint = physics->GetOrigin(); + } + else { + // rotate entity in reverse only colliding with pusher + newRotation = rotation; + newRotation.Scale( -1 ); + // + ClipEntityRotation( results, check, clipModel, NULL, newRotation ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { +#ifdef ROTATIONAL_PUSH_DEBUG + // set pusher into final position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + return PUSH_NO; + } + // get point to rotate bbox around back to axial + rotationPoint = results.c.point; + // angle along which the entity will be pushed + checkAngle = rotation.GetAngle() * (1.0f - results.fraction); + // rotate the entity colliding with all other entities except the pusher itself + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + ClipEntityRotation( trace, check, NULL, clipModel, newRotation ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + + SaveEntityPosition( check ); + + newRotation.Set( rotation.GetOrigin(), rotation.GetVec(), checkAngle ); + // NOTE: this code prevents msvc 6.0 & 7.0 from screwing up the above code in + // release builds moving less floats than it should + static float shit = checkAngle; + + newRotation.RotatePoint( rotationPoint ); + + // rotate the entity + physics->Rotate( newRotation ); + + // set pusher into final position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity uses actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // rotate the collision model back to axial + if ( !RotateEntityToAxial( check, rotationPoint ) ) { + // don't allow rotation if the bbox is no longer axial + return PUSH_BLOCKED; + } + } + +#ifdef ROTATIONAL_PUSH_DEBUG + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + // if the entity is an actor using actor physics +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idActor::GetClassType() ) && physics->IsType( idPhysics_Actor::GetClassType() ) ) { +// RAVEN END + + // if the entity is standing ontop of the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // rotate actor view + idActor *actor = static_cast(check); + idAngles delta = actor->GetDeltaViewAngles(); + delta.yaw += newRotation.ToMat3()[0].ToYaw(); + actor->SetDeltaViewAngles( delta ); + } + } + + return PUSH_OK; +} + +/* +============ +idPush::TryTranslatePushEntity +============ +*/ +#ifdef _DEBUG +// #define TRANSLATIONAL_PUSH_DEBUG +#endif + +int idPush::TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ) { + trace_t trace; + idVec3 checkMove; + idVec3 oldOrigin; + idPhysics *physics; + + physics = check->GetPhysics(); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + bool startsolid = false; + if ( physics->ClipContents( clipModel ) ) { + startsolid = true; + } +#endif + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + // always pushed when standing on the pusher + if ( physics->IsGroundClipModel( clipModel->GetEntity()->entityNumber, clipModel->GetId() ) ) { + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, move ); + // if there is a collision + if ( trace.fraction < 1.0f ) { + // vector along which the entity is pushed + checkMove = move * trace.fraction; + // test if the entity can stay at it's partly pushed position by moving the entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -(move - checkMove) ); + // if there is a collision + if ( results.fraction < 1.0f ) { + + // FIXME: try to push the blocking entity as well or try to slide along collision plane(s)? + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // the entity will be crushed between the pusher and some other entity + return PUSH_BLOCKED; + } + } + else { + // vector along which the entity is pushed + checkMove = move; + } + } + else { + // move entity in reverse only colliding with pusher + ClipEntityTranslation( results, check, clipModel, NULL, -move ); + // if no collision with the pusher then the entity is not pushed by the pusher + if ( results.fraction >= 1.0f ) { + return PUSH_NO; + } + // vector along which the entity is pushed + checkMove = move * (1.0f - results.fraction); + // move the entity colliding with all other entities except the pusher itself + ClipEntityTranslation( trace, check, NULL, clipModel, checkMove ); + // if there is a collisions + if ( trace.fraction < 1.0f ) { + + results.c.normal = -results.c.normal; + results.c.dist = -results.c.dist; + + // FIXME: try to push the blocking entity as well ? + // FIXME: handle sliding along more than one collision plane ? + // FIXME: this code has issues, player pushing box into corner in "maps/mre/aaron/test.map" + +/* + oldOrigin = physics->GetOrigin(); + + // movement still remaining + checkMove *= (1.0f - trace.fraction); + + // project the movement along the collision plane + if ( !checkMove.ProjectAlongPlane( trace.c.normal, 0.1f, 1.001f ) ) { + return PUSH_BLOCKED; + } + checkMove *= 1.001f; + + // move entity from collision point along the collision plane + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, NULL, NULL, checkMove ); + + if ( trace.fraction < 1.0f ) { + physics->SetOrigin( oldOrigin ); + return PUSH_BLOCKED; + } + + checkMove = trace.endpos - oldOrigin; + + // move entity in reverse only colliding with pusher + physics->SetOrigin( trace.endpos ); + ClipEntityTranslation( trace, check, clipModel, NULL, -move ); + + physics->SetOrigin( oldOrigin ); +*/ + if ( trace.fraction < 1.0f ) { + return PUSH_BLOCKED; + } + } + } + + SaveEntityPosition( check ); + + // translate the entity + physics->Translate( checkMove ); + +#ifdef TRANSLATIONAL_PUSH_DEBUG + // set the pusher in the translated position + clipModel->Link( gameLocal.clip, clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); + if ( physics->ClipContents( clipModel ) ) { + if ( !startsolid ) { + int bah = 1; + } + } +#endif + + return PUSH_OK; +} + +/* +============ +idPush::DiscardEntities +============ +*/ +int idPush::DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ) { + int i, num; + idEntity *check; + + // remove all entities we cannot or should not push from the list + for ( num = i = 0; i < numEntities; i++ ) { + check = entityList[ i ]; + + // if the physics object is not pushable + if ( !check->GetPhysics()->IsPushable() ) { + continue; + } + + // if the entity doesn't clip with this pusher + if ( !( check->GetPhysics()->GetClipMask() & pusher->GetPhysics()->GetContents() ) ) { + continue; + } + + // don't push players in noclip mode +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idPlayer::GetClassType() ) && static_cast(check)->noclip ) { +// RAVEN END + continue; + } + + // if we should only push idMoveable entities +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( ( flags & PUSHFL_ONLYMOVEABLE ) && !check->IsType( idMoveable::GetClassType() ) ) { +// RAVEN END + continue; + } + + // if we shouldn't push entities the clip model rests upon + if ( flags & PUSHFL_NOGROUNDENTITIES ) { + if ( pusher->GetPhysics()->IsGroundEntity( check->entityNumber ) ) { + continue; + } + } + + // keep entity in list + entityList[ num++ ] = entityList[i]; + } + + return num; +} + +/* +============ +idPush::ClipTranslationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &translation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idVec3 clipMove, clipOrigin, oldOrigin, dir, impulse; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = clipModel->GetAxis(); + memset( &results.c, 0, sizeof( results.c ) ); + + if ( translation == vec3_origin ) { + return totalMass; + } + + dir = translation; + dir.Normalize(); + dir.z += 1.0f; + dir *= 10.0f; + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsTranslation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), translation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( pusher, results, clipModel->GetOrigin(), clipModel->GetOrigin() + translation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipMove = results.endpos - clipModel->GetOrigin(); + clipOrigin = results.endpos; + + } + else { + + clipMove = translation; + clipOrigin = newOrigin; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldOrigin = clipModel->GetOrigin(); + + // try to push the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryTranslatePushEntity( pushResults, check, clipModel, flags, clipOrigin, clipMove ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the translated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), newOrigin, clipModel->GetAxis() ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), oldOrigin, clipModel->GetAxis() ); +// RAVEN END + + // wake up this object + if ( flags & PUSHFL_APPLYIMPULSE ) { + impulse = physics->GetMass() * dir; + } else { + impulse.Zero(); + } + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), impulse ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // if the entity is a moveable item and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idMoveableItem::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +/* +============ +idPush::ClipRotationalPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ) { + int i, listedEntities, res; + idEntity *check, *entityList[ MAX_GENTITIES ]; + idBounds bounds, pushBounds; + idRotation clipRotation; + idMat3 clipAxis, oldAxis; + trace_t pushResults; + bool wasEnabled; + float totalMass; + idClipModel *clipModel; + + clipModel = pusher->GetPhysics()->GetClipModel(); + + totalMass = 0.0f; + + results.fraction = 1.0f; + results.endpos = clipModel->GetOrigin(); + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + if ( !rotation.GetAngle() ) { + return totalMass; + } + + // get bounds for the whole movement + bounds = clipModel->GetBounds(); + if ( bounds[0].x >= bounds[1].x ) { + return totalMass; + } + pushBounds.FromBoundsRotation( bounds, clipModel->GetOrigin(), clipModel->GetAxis(), rotation ); + + wasEnabled = clipModel->IsEnabled(); + + // make sure we don't get the pushing clip model in the list + clipModel->Disable(); + +// RAVEN BEGIN +// ddynerman: multiple clip worlds + listedEntities = gameLocal.EntitiesTouchingBounds( pusher, pushBounds, -1, entityList, MAX_GENTITIES ); +// RAVEN END + + // discard entities we cannot or should not push + listedEntities = DiscardEntities( entityList, listedEntities, flags, pusher ); + + if ( flags & PUSHFL_CLIP ) { + + // can only clip movement of a trace model + assert( clipModel->IsTraceModel() ); + + // disable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->DisableClip(); + } +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Rotation( pusher, results, clipModel->GetOrigin(), rotation, clipModel, clipModel->GetAxis(), pusher->GetPhysics()->GetClipMask(), NULL ); +// RAVEN END + // enable to be pushed entities for collision detection + for ( i = 0; i < listedEntities; i++ ) { + entityList[i]->GetPhysics()->EnableClip(); + } + + if ( results.fraction == 0.0f ) { + if ( wasEnabled ) { + clipModel->Enable(); + } + return totalMass; + } + + clipRotation = rotation * results.fraction; + clipAxis = results.endAxis; + } + else { + + clipRotation = rotation; + clipAxis = newAxis; + } + + // we have to enable the clip model because we use it during pushing + clipModel->Enable(); + + // save pusher old position + oldAxis = clipModel->GetAxis(); + + // try to push all the entities + for ( i = 0; i < listedEntities; i++ ) { + + check = entityList[ i ]; + + idPhysics *physics = check->GetPhysics(); + + // disable the entity for collision detection + physics->DisableClip(); + + res = TryRotatePushEntity( pushResults, check, clipModel, flags, clipAxis, clipRotation ); + + // enable the entity for collision detection + physics->EnableClip(); + + // if the entity is pushed + if ( res == PUSH_OK ) { + // set the pusher in the rotated position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), newAxis ); +// RAVEN END + // the entity might be pushed off the ground + physics->EvaluateContacts(); + // put pusher back in old position +// RAVEN BEGIN +// ddynerman: multiple clip worlds + clipModel->Link( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), oldAxis ); +// RAVEN END + + // wake up this object + check->ApplyImpulse( clipModel->GetEntity(), clipModel->GetId(), clipModel->GetOrigin(), vec3_origin ); + + // add mass of pushed entity + totalMass += physics->GetMass(); + } + + // if the entity is not blocking + if ( res != PUSH_BLOCKED ) { + continue; + } + + // if the blocking entity is a projectile +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idProjectile::GetClassType() ) ) { +// RAVEN END + check->ProcessEvent( &EV_Explode ); + continue; + } + + // if blocking entities should be crushed + if ( flags & PUSHFL_CRUSH ) { + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + continue; + } + + // if the entity is an active articulated figure and gibs +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( check->IsType( idAFEntity_Base::GetClassType() ) && check->spawnArgs.GetBool( "gib" ) ) { +// RAVEN END + if ( static_cast(check)->IsActiveAF() ) { + check->ProcessEvent( &EV_Gib, "damage_Gib" ); + } + } + + // blocked + results = pushResults; + results.fraction = 0.0f; + results.endAxis = clipModel->GetAxis(); + results.endpos = clipModel->GetOrigin(); + results.c.entityNum = check->entityNumber; + results.c.id = 0; + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; + } + + if ( !wasEnabled ) { + clipModel->Disable(); + } + + return totalMass; +} + +#endif /* !NEW_PUSH */ + + +/* +============ +idPush::ClipPush + + Try to push other entities by moving the given entity. +============ +*/ +float idPush::ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ) { + idVec3 translation; + idRotation rotation; + float mass; + + mass = 0.0f; + + results.fraction = 1.0f; + results.endpos = newOrigin; + results.endAxis = newAxis; + memset( &results.c, 0, sizeof( results.c ) ); + + // translational push + translation = newOrigin - oldOrigin; + + // if the pusher translates + if ( translation != vec3_origin ) { + + mass += ClipTranslationalPush( results, pusher, flags, newOrigin, translation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newOrigin = oldOrigin; + } + + // rotational push + rotation = ( oldAxis.Transpose() * newAxis ).ToRotation(); + rotation.SetOrigin( newOrigin ); + rotation.Normalize180(); + rotation.ReCalculateMatrix(); // recalculate the rotation matrix to avoid accumulating rounding errors + + // if the pusher rotates + if ( rotation.GetAngle() != 0.0f ) { + + // recalculate new axis to avoid floating point rounding problems + newAxis = oldAxis * rotation.ToMat3(); + newAxis.OrthoNormalizeSelf(); + newAxis.FixDenormals(); + newAxis.FixDegeneracies(); + + pusher->GetPhysics()->GetClipModel()->SetPosition( newOrigin, oldAxis ); + + mass += ClipRotationalPush( results, pusher, flags, newAxis, rotation ); + if ( results.fraction < 1.0f ) { + newOrigin = oldOrigin; + newAxis = oldAxis; + return mass; + } + } else { + newAxis = oldAxis; + } + + return mass; +} diff --git a/src/mpgame/physics/Push.h b/src/mpgame/physics/Push.h new file mode 100644 index 0000000..b169bdb --- /dev/null +++ b/src/mpgame/physics/Push.h @@ -0,0 +1,109 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __PUSH_H__ +#define __PUSH_H__ + +/* +=============================================================================== + + Allows physics objects to be pushed geometrically. + +=============================================================================== +*/ + +#define PUSHFL_ONLYMOVEABLE 1 // only push moveable entities +#define PUSHFL_NOGROUNDENTITIES 2 // don't push entities the clip model rests upon +#define PUSHFL_CLIP 4 // also clip against all non-moveable entities +#define PUSHFL_CRUSH 8 // kill blocking entities +#define PUSHFL_APPLYIMPULSE 16 // apply impulse to pushed entities + +//#define NEW_PUSH + +class idPush { +public: + // Try to push other entities by moving the given entity. + // If results.fraction < 1.0 the move was blocked by results.c.entityNum + // Returns total mass of all pushed entities. + float ClipTranslationalPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + + float ClipRotationalPush( trace_t &results, idEntity *pusher, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + + float ClipPush( trace_t &results, idEntity *pusher, const int flags, + const idVec3 &oldOrigin, const idMat3 &oldAxis, + idVec3 &newOrigin, idMat3 &newAxis ); + + // initialize saving the positions of entities being pushed + void InitSavingPushedEntityPositions( void ); + // move all pushed entities back to their previous position + void RestorePushedEntityPositions( void ); + // returns the number of pushed entities + int GetNumPushedEntities( void ) const { return numPushed; } + // get the ith pushed entity + idEntity * GetPushedEntity( int i ) const { assert( i >= 0 && i < numPushed ); return pushed[i].ent; } + +private: + struct pushed_s { + idEntity * ent; // pushed entity + idAngles deltaViewAngles; // actor delta view angles + } pushed[MAX_GENTITIES]; // pushed entities + int numPushed; // number of pushed entities + + struct pushedGroup_s { + idEntity * ent; + float fraction; + bool groundContact; + bool test; + } pushedGroup[MAX_GENTITIES]; + int pushedGroupSize; + +private: + void SaveEntityPosition( idEntity *ent ); + bool RotateEntityToAxial( idEntity *ent, idVec3 rotationPoint ); +#ifdef NEW_PUSH + bool CanPushEntity( idEntity *ent, idEntity *pusher, idEntity *initialPusher, const int flags ); + void AddEntityToPushedGroup( idEntity *ent, float fraction, bool groundContact ); + bool IsFullyPushed( idEntity *ent ); + bool ClipTranslationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idVec3 &translation ); + int GetPushableEntitiesForTranslation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idVec3 &translation, idEntity *entityList[], int maxEntities ); + bool ClipRotationAgainstPusher( trace_t &results, idEntity *ent, idEntity *pusher, const idRotation &rotation ); + int GetPushableEntitiesForRotation( idEntity *pusher, idEntity *initialPusher, const int flags, + const idRotation &rotation, idEntity *entityList[], int maxEntities ); +#else + void ClipEntityRotation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idRotation &rotation ); + void ClipEntityTranslation( trace_t &trace, const idEntity *ent, const idClipModel *clipModel, + idClipModel *skip, const idVec3 &translation ); + int TryTranslatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idVec3 &newOrigin, const idVec3 &move ); + int TryRotatePushEntity( trace_t &results, idEntity *check, idClipModel *clipModel, const int flags, + const idMat3 &newAxis, const idRotation &rotation ); + int DiscardEntities( idEntity *entityList[], int numEntities, int flags, idEntity *pusher ); +#endif +}; + +#endif /* !__PUSH_H__ */ diff --git a/src/mpgame/script/ScriptFuncUtility.cpp b/src/mpgame/script/ScriptFuncUtility.cpp new file mode 100644 index 0000000..e13729b --- /dev/null +++ b/src/mpgame/script/ScriptFuncUtility.cpp @@ -0,0 +1,493 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility* sfu ) { + Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const rvScriptFuncUtility& sfu ) { + Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const char* source ) { + Init( source ); +} + +/* +================ +rvScriptFuncUtility::rvScriptFuncUtility +================ +*/ +rvScriptFuncUtility::rvScriptFuncUtility( const idCmdArgs& args ) { + Init( args ); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const char* source ) { + Clear(); + + if( !source || !source[0] ) { + return SFU_NOFUNC; + } + + idStr::Split( source, parms, ' ' ); + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init( const idCmdArgs& args ) { + Clear(); + + //Start at index 1 so we ignore 'call' + for( int ix = 1; ix < args.Argc(); ++ix ) { + InsertString( args.Argv(ix), ix ); + } + + return Init(); +} + +/* +================ +rvScriptFuncUtility::Init +================ +*/ +sfuReturnType rvScriptFuncUtility::Init() { + assert( parms.Num() ); + + func = FindFunction( GetParm(0) ); + if( func ) { + RemoveIndex( 0 );// remove Function name + returnKey.Clear(); + } else if( parms.Num() >= 2 ) { + returnKey = GetParm( 0 ); + RemoveIndex( 0 );// remove key name + func = FindFunction( GetParm(0) ); + RemoveIndex( 0 );// remove Function name + } else { + gameLocal.Warning( "Unable to find function %s in rvScriptFuncUtility::Init\n", parms[0].c_str() ); + } + + return func != NULL ? SFU_OK : SFU_ERROR; +} + +/* +================ +rvScriptFuncUtility::Clear +================ +*/ +void rvScriptFuncUtility::Clear() { + func = NULL; + parms.Clear(); +} + +/* +================ +rvScriptFuncUtility::Save +================ +*/ +void rvScriptFuncUtility::Save( idSaveGame *savefile ) const { + bool validFunc = GetFunc() != NULL; + savefile->WriteBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->WriteString( GetReturnKey() ); + + savefile->WriteString( GetFunc()->Name() ); + + savefile->WriteInt( NumParms() ); + for( int ix = 0; ix < NumParms(); ++ix ) { + savefile->WriteString( func->Name() ); + } +} + +/* +================ +rvScriptFuncUtility::Restore +================ +*/ +void rvScriptFuncUtility::Restore( idRestoreGame *savefile ) { + idStr value; + idStr element; + int numParms = 0; + bool validFunc = false; + + savefile->ReadBool( validFunc ); + if( !validFunc ) { + return; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadString( element ); + if( element.Length() ) { + value += element; + value += ' '; + } + + savefile->ReadInt( numParms ); + for( int ix = 0; ix < numParms; ++ix ) { + savefile->ReadString( element ); + value += element; + value += ' '; + } + + value.StripTrailing( ' ' ); + sfuReturnType status = Init(value.c_str()); + if ( status != SFU_OK ) { + assert( 0 ); + } +} + +/* +================ +rvScriptFuncUtility::NumParms +================ +*/ +int rvScriptFuncUtility::NumParms() const { + return (func && func->type) ? func->type->NumParameters() : 0; +} + +/* +================ +rvScriptFuncUtility::ReturnsAVal +================ +*/ +bool rvScriptFuncUtility::ReturnsAVal() const { + return GetReturnType() != &type_void; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +idTypeDef* rvScriptFuncUtility::GetParmType( int index ) const { + return (func && func->type) ? func->type->GetParmType( index ) : NULL; +} + +/* +================ +rvScriptFuncUtility::GetReturnType +================ +*/ +idTypeDef* rvScriptFuncUtility::GetReturnType() const { + return (func && func->type) ? func->type->ReturnType() : &type_void; +} + +/* +================ +rvScriptFuncUtility::SetFunction +================ +*/ +void rvScriptFuncUtility::SetFunction( const function_t* func ) { + this->func = func; +} + +/* +================ +rvScriptFuncUtility::SetParms +================ +*/ +void rvScriptFuncUtility::SetParms( const idList& parms ) { + this->parms = parms; +} + +/* +================ +rvScriptFuncUtility::GetParm +================ +*/ +const char* rvScriptFuncUtility::GetParm( int index ) const { + return parms[ index ].c_str(); +} + +/* +================ +rvScriptFuncUtility::GetFuncName +================ +*/ +const char* rvScriptFuncUtility::GetFuncName() const { + if( !func ) { + return NULL; + } + + return func->Name(); +} + +/* +================ +rvScriptFuncUtility::InsertInt +================ +*/ +void rvScriptFuncUtility::InsertInt( int parm, int index ) { + InsertString( va("%d", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertFloat +================ +*/ +void rvScriptFuncUtility::InsertFloat( float parm, int index ) { + InsertString( va("%f", parm), index ); +} + +/* +================ +rvScriptFuncUtility::InsertVec3 +================ +*/ +void rvScriptFuncUtility::InsertVec3( const idVec3& parm, int index ) { + InsertString( parm.ToString(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertEntity +================ +*/ +void rvScriptFuncUtility::InsertEntity( const idEntity* parm, int index ) { + assert( parm ); + + InsertString( parm->GetName(), index ); +} + +/* +================ +rvScriptFuncUtility::InsertString +================ +*/ +void rvScriptFuncUtility::InsertString( const char* parm, int index ) { + assert( parm ); + + parms.Insert( parm, index ); +} + +/* +================ +rvScriptFuncUtility::InsertBool +================ +*/ +void rvScriptFuncUtility::InsertBool( bool parm, int index ) { + InsertInt( (int)parm, index ); +} + +/* +================ +rvScriptFuncUtility::RemoveIndex +================ +*/ +void rvScriptFuncUtility::RemoveIndex( int index ) { + parms.RemoveIndex( index ); +} + +/* +================ +rvScriptFuncUtility::FindFunction +================ +*/ +const function_t* rvScriptFuncUtility::FindFunction( const char* name ) const { + idTypeDef* type = gameLocal.program.FindType( name ); + if( type ) {//Find based on type + return gameLocal.program.FindFunction( name, type ); + } + + //Find based on scope + return gameLocal.program.FindFunction( name ); +} + +/* +================ +rvScriptFuncUtility::CallFunc +================ +*/ +void rvScriptFuncUtility::CallFunc( idDict* returnDict ) const { + idTypeDef* type = NULL; + + if( !Valid() ) { + return; + } + + idThread* thread = new idThread(); + if( !thread ) { + return; + } + + thread->ClearStack(); + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + type->PushOntoStack( thread, GetParm(ix) ); + } + + thread->CallFunction( func, false ); + + if( thread->Execute() && returnDict && ReturnsAVal() ) { + returnDict->Set( GetReturnKey(), GetReturnType()->GetReturnedValAsString(gameLocal.program) ); + } +} + +/* +================ +rvScriptFuncUtility::Valid +================ +*/ +bool rvScriptFuncUtility::Valid() const { +//#if _DEBUG + idTypeDef* type = NULL; + + if( !GetFunc() ) { + return false; + } + + if( GetFunc()->eventdef ) { + gameLocal.Warning( "Function, %s, is an event\n", GetFunc()->Name() ); + return false; + } + + // FIXME: designers call functions with no parms even though parms are pushed on stack + //if( NumParms() != parms.Num() ) { + // gameLocal.Warning( "Number of parms doesn't equal the num required by function %s\n", func->Name() ); + // return false; + //} + + for( int ix = 0; ix < NumParms(); ++ix ) { + type = GetParmType( ix ); + if( !type->IsValid( GetParm(ix) ) ) { + gameLocal.Warning( "(Func: %s) Parm '%s' doesn't match expected type '%s'\n", GetFunc()->Name(), GetParm(ix), type->Name() ); + return false; + } + } + + if( returnKey.Length() && !ReturnsAVal() ) { + gameLocal.Warning( "Expecting return val from function %s which doesn't return one\n", func->Name() ); + return false; + } +//#endif + + return true; +} + +/* +================ +rvScriptFuncUtility::Assign +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::Assign( const rvScriptFuncUtility* sfu ) { + assert( sfu ); + func = sfu->func; + parms = sfu->parms; + returnKey = sfu->returnKey; + + return *this; +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility* sfu ) { + return Assign( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator= +================ +*/ +rvScriptFuncUtility& rvScriptFuncUtility::operator=( const rvScriptFuncUtility& sfu ) { + return Assign( &sfu ); +} + +/* +================ +rvScriptFuncUtility::IsEqualTo +================ +*/ +bool rvScriptFuncUtility::IsEqualTo( const rvScriptFuncUtility* sfu ) const { + assert( sfu ); + return GetFunc() == sfu->GetFunc(); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility* sfu ) const { + return IsEqualTo( sfu ); +} + +/* +================ +rvScriptFuncUtility::operator== +================ +*/ +bool rvScriptFuncUtility::operator==( const rvScriptFuncUtility& sfu ) const { + return IsEqualTo( &sfu ); +} diff --git a/src/mpgame/script/ScriptFuncUtility.h b/src/mpgame/script/ScriptFuncUtility.h new file mode 100644 index 0000000..2657c21 --- /dev/null +++ b/src/mpgame/script/ScriptFuncUtility.h @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RV_SCRIPT_FUNC_UTILITY_H +#define __RV_SCRIPT_FUNC_UTILITY_H + +class idThread; + +enum sfuReturnType { + SFU_NOFUNC = -1, + SFU_ERROR = 0, + SFU_OK = 1 +}; + +class rvScriptFuncUtility { +public: + rvScriptFuncUtility(); + explicit rvScriptFuncUtility( const rvScriptFuncUtility* sfu ); + explicit rvScriptFuncUtility( const rvScriptFuncUtility& sfu ); + explicit rvScriptFuncUtility( const char* source ); + explicit rvScriptFuncUtility( const idCmdArgs& args ); + + sfuReturnType Init( const char* source ); + sfuReturnType Init( const idCmdArgs& args ); + void Clear(); + + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + + idTypeDef* GetParmType( int index ) const; + idTypeDef* GetReturnType() const ; + int NumParms() const; + bool ReturnsAVal() const; + + void SetFunction( const function_t* func ); + void SetParms( const idList& parms ); + void SetReturnKey( const char* key ) { returnKey = key; } + + const char* GetFuncName() const; + const function_t* GetFunc() const { return func; } + const char* GetParm( int index ) const; + const char* GetReturnKey() const { return returnKey.c_str(); } + + void InsertInt( int parm, int index ); + void InsertFloat( float parm, int index ); + void InsertVec3( const idVec3& parm, int index ); + void InsertEntity( const idEntity* parm, int index ); + void InsertString( const char* parm, int index ); + void InsertBool( bool parm, int index ); + void RemoveIndex( int index ); + + const function_t* FindFunction( const char* name ) const; + void CallFunc( idDict* returnDict ) const; + + bool Valid() const; + + rvScriptFuncUtility& Assign( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility* sfu ); + rvScriptFuncUtility& operator=( const rvScriptFuncUtility& sfu ); + + bool IsEqualTo( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility* sfu ) const; + bool operator==( const rvScriptFuncUtility& sfu ) const; + +private: + sfuReturnType Init(); + +protected: + const function_t* func; + idList parms; + idStr returnKey; +}; + +#endif diff --git a/src/mpgame/script/Script_Compiler.cpp b/src/mpgame/script/Script_Compiler.cpp new file mode 100644 index 0000000..d83f087 --- /dev/null +++ b/src/mpgame/script/Script_Compiler.cpp @@ -0,0 +1,2666 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#define FUNCTION_PRIORITY 2 +#define INT_PRIORITY 2 +#define NOT_PRIORITY 5 +#define TILDE_PRIORITY 5 +#define TOP_PRIORITY 7 + +bool idCompiler::punctuationValid[ 256 ]; +char *idCompiler::punctuation[] = { + "+=", "-=", "*=", "/=", "%=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", "::", ";", ",", + "~", "!", "*", "/", "%", "(", ")", "-", "+", + "=", "[", "]", ".", "<", ">" , "&", "|", ":", NULL +}; + +opcode_t idCompiler::opcodes[] = { + { "", "RETURN", -1, false, &def_void, &def_void, &def_void }, + + { "++", "UINC_F", 1, true, &def_float, &def_void, &def_void }, + { "++", "UINCP_F", 1, true, &def_object, &def_field, &def_float }, + { "--", "UDEC_F", 1, true, &def_float, &def_void, &def_void }, + { "--", "UDECP_F", 1, true, &def_object, &def_field, &def_float }, + + { "~", "COMP_F", -1, false, &def_float, &def_void, &def_float }, + + { "*", "MUL_F", 3, false, &def_float, &def_float, &def_float }, + { "*", "MUL_V", 3, false, &def_vector, &def_vector, &def_float }, + { "*", "MUL_FV", 3, false, &def_float, &def_vector, &def_vector }, + { "*", "MUL_VF", 3, false, &def_vector, &def_float, &def_vector }, + + { "/", "DIV", 3, false, &def_float, &def_float, &def_float }, + { "%", "MOD_F", 3, false, &def_float, &def_float, &def_float }, + + { "+", "ADD_F", 4, false, &def_float, &def_float, &def_float }, + { "+", "ADD_V", 4, false, &def_vector, &def_vector, &def_vector }, + { "+", "ADD_S", 4, false, &def_string, &def_string, &def_string }, + { "+", "ADD_FS", 4, false, &def_float, &def_string, &def_string }, + { "+", "ADD_SF", 4, false, &def_string, &def_float, &def_string }, + { "+", "ADD_VS", 4, false, &def_vector, &def_string, &def_string }, + { "+", "ADD_SV", 4, false, &def_string, &def_vector, &def_string }, + + { "-", "SUB_F", 4, false, &def_float, &def_float, &def_float }, + { "-", "SUB_V", 4, false, &def_vector, &def_vector, &def_vector }, + + { "==", "EQ_F", 5, false, &def_float, &def_float, &def_float }, + { "==", "EQ_V", 5, false, &def_vector, &def_vector, &def_float }, + { "==", "EQ_S", 5, false, &def_string, &def_string, &def_float }, + { "==", "EQ_E", 5, false, &def_entity, &def_entity, &def_float }, + { "==", "EQ_EO", 5, false, &def_entity, &def_object, &def_float }, + { "==", "EQ_OE", 5, false, &def_object, &def_entity, &def_float }, + { "==", "EQ_OO", 5, false, &def_object, &def_object, &def_float }, + + { "!=", "NE_F", 5, false, &def_float, &def_float, &def_float }, + { "!=", "NE_V", 5, false, &def_vector, &def_vector, &def_float }, + { "!=", "NE_S", 5, false, &def_string, &def_string, &def_float }, + { "!=", "NE_E", 5, false, &def_entity, &def_entity, &def_float }, + { "!=", "NE_EO", 5, false, &def_entity, &def_object, &def_float }, + { "!=", "NE_OE", 5, false, &def_object, &def_entity, &def_float }, + { "!=", "NE_OO", 5, false, &def_object, &def_object, &def_float }, + + { "<=", "LE", 5, false, &def_float, &def_float, &def_float }, + { ">=", "GE", 5, false, &def_float, &def_float, &def_float }, + { "<", "LT", 5, false, &def_float, &def_float, &def_float }, + { ">", "GT", 5, false, &def_float, &def_float, &def_float }, + + { ".", "INDIRECT_F", 1, false, &def_object, &def_field, &def_float }, + { ".", "INDIRECT_V", 1, false, &def_object, &def_field, &def_vector }, + { ".", "INDIRECT_S", 1, false, &def_object, &def_field, &def_string }, + { ".", "INDIRECT_E", 1, false, &def_object, &def_field, &def_entity }, + { ".", "INDIRECT_BOOL", 1, false, &def_object, &def_field, &def_boolean }, + { ".", "INDIRECT_OBJ", 1, false, &def_object, &def_field, &def_object }, + + { ".", "ADDRESS", 1, false, &def_entity, &def_field, &def_pointer }, + + { ".", "EVENTCALL", 2, false, &def_entity, &def_function, &def_void }, + { ".", "OBJECTCALL", 2, false, &def_object, &def_function, &def_void }, + { ".", "SYSCALL", 2, false, &def_void, &def_function, &def_void }, + + { "=", "STORE_F", 6, true, &def_float, &def_float, &def_float }, + { "=", "STORE_V", 6, true, &def_vector, &def_vector, &def_vector }, + { "=", "STORE_S", 6, true, &def_string, &def_string, &def_string }, + { "=", "STORE_ENT", 6, true, &def_entity, &def_entity, &def_entity }, + { "=", "STORE_BOOL", 6, true, &def_boolean, &def_boolean, &def_boolean }, + { "=", "STORE_OBJENT", 6, true, &def_object, &def_entity, &def_object }, + { "=", "STORE_OBJ", 6, true, &def_object, &def_object, &def_object }, + { "=", "STORE_OBJENT", 6, true, &def_entity, &def_object, &def_object }, + + { "=", "STORE_FTOS", 6, true, &def_string, &def_float, &def_string }, + { "=", "STORE_BTOS", 6, true, &def_string, &def_boolean, &def_string }, + { "=", "STORE_VTOS", 6, true, &def_string, &def_vector, &def_string }, + { "=", "STORE_FTOBOOL", 6, true, &def_boolean, &def_float, &def_boolean }, + { "=", "STORE_BOOLTOF", 6, true, &def_float, &def_boolean, &def_float }, + + { "=", "STOREP_F", 6, true, &def_pointer, &def_float, &def_float }, + { "=", "STOREP_V", 6, true, &def_pointer, &def_vector, &def_vector }, + { "=", "STOREP_S", 6, true, &def_pointer, &def_string, &def_string }, + { "=", "STOREP_ENT", 6, true, &def_pointer, &def_entity, &def_entity }, + { "=", "STOREP_FLD", 6, true, &def_pointer, &def_field, &def_field }, + { "=", "STOREP_BOOL", 6, true, &def_pointer, &def_boolean, &def_boolean }, + { "=", "STOREP_OBJ", 6, true, &def_pointer, &def_object, &def_object }, + { "=", "STOREP_OBJENT", 6, true, &def_pointer, &def_object, &def_object }, + + { "<=>", "STOREP_FTOS", 6, true, &def_pointer, &def_float, &def_string }, + { "<=>", "STOREP_BTOS", 6, true, &def_pointer, &def_boolean, &def_string }, + { "<=>", "STOREP_VTOS", 6, true, &def_pointer, &def_vector, &def_string }, + { "<=>", "STOREP_FTOBOOL", 6, true, &def_pointer, &def_float, &def_boolean }, + { "<=>", "STOREP_BOOLTOF", 6, true, &def_pointer, &def_boolean, &def_float }, + + { "*=", "UMUL_F", 6, true, &def_float, &def_float, &def_void }, + { "*=", "UMUL_V", 6, true, &def_vector, &def_float, &def_void }, + { "/=", "UDIV_F", 6, true, &def_float, &def_float, &def_void }, + { "/=", "UDIV_V", 6, true, &def_vector, &def_float, &def_void }, + { "%=", "UMOD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_V", 6, true, &def_vector, &def_vector, &def_void }, + { "-=", "USUB_F", 6, true, &def_float, &def_float, &def_void }, + { "-=", "USUB_V", 6, true, &def_vector, &def_vector, &def_void }, + { "&=", "UAND_F", 6, true, &def_float, &def_float, &def_void }, + { "|=", "UOR_F", 6, true, &def_float, &def_float, &def_void }, + + { "!", "NOT_BOOL", -1, false, &def_boolean, &def_void, &def_float }, + { "!", "NOT_F", -1, false, &def_float, &def_void, &def_float }, + { "!", "NOT_V", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_S", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_ENT", -1, false, &def_entity, &def_void, &def_float }, + + { "", "NEG_F", -1, false, &def_float, &def_void, &def_float }, + { "", "NEG_V", -1, false, &def_vector, &def_void, &def_vector }, + + { "int", "INT_F", -1, false, &def_float, &def_void, &def_float }, + + { "", "IF", -1, false, &def_float, &def_jumpoffset, &def_void }, + { "", "IFNOT", -1, false, &def_float, &def_jumpoffset, &def_void }, + + // calls returns REG_RETURN + { "", "CALL", -1, false, &def_function, &def_argsize, &def_void }, + { "", "THREAD", -1, false, &def_function, &def_argsize, &def_void }, + { "", "OBJTHREAD", -1, false, &def_function, &def_argsize, &def_void }, + + { "", "PUSH_F", -1, false, &def_float, &def_float, &def_void }, + { "", "PUSH_V", -1, false, &def_vector, &def_vector, &def_void }, + { "", "PUSH_S", -1, false, &def_string, &def_string, &def_void }, + { "", "PUSH_ENT", -1, false, &def_entity, &def_entity, &def_void }, + { "", "PUSH_OBJ", -1, false, &def_object, &def_object, &def_void }, + { "", "PUSH_OBJENT", -1, false, &def_entity, &def_object, &def_void }, + { "", "PUSH_FTOS", -1, false, &def_string, &def_float, &def_void }, + { "", "PUSH_BTOF", -1, false, &def_float, &def_boolean, &def_void }, + { "", "PUSH_FTOB", -1, false, &def_boolean, &def_float, &def_void }, + { "", "PUSH_VTOS", -1, false, &def_string, &def_vector, &def_void }, + { "", "PUSH_BTOS", -1, false, &def_string, &def_boolean, &def_void }, + + { "", "GOTO", -1, false, &def_jumpoffset, &def_void, &def_void }, + + { "&&", "AND", 7, false, &def_float, &def_float, &def_float }, + { "&&", "AND_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "&&", "AND_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "&&", "AND_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + { "||", "OR", 7, false, &def_float, &def_float, &def_float }, + { "||", "OR_BOOLF", 7, false, &def_boolean, &def_float, &def_float }, + { "||", "OR_FBOOL", 7, false, &def_float, &def_boolean, &def_float }, + { "||", "OR_BOOLBOOL", 7, false, &def_boolean, &def_boolean, &def_float }, + + { "&", "BITAND", 3, false, &def_float, &def_float, &def_float }, + { "|", "BITOR", 3, false, &def_float, &def_float, &def_float }, + + { "", "BREAK", -1, false, &def_float, &def_void, &def_void }, + { "", "CONTINUE", -1, false, &def_float, &def_void, &def_void }, + + { NULL } +}; + +/* +================ +idCompiler::idCompiler() +================ +*/ +idCompiler::idCompiler() { + char **ptr; + int id; + + // make sure we have the right # of opcodes in the table + assert( ( sizeof( opcodes ) / sizeof( opcodes[ 0 ] ) ) == ( NUM_OPCODES + 1 ) ); + + eof = true; + parserPtr = &parser; + + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + basetype = NULL; + currentLineNumber = 0; + currentFileNumber = 0; + errorCount = 0; + console = false; + scope = &def_namespace; + + memset( &immediate, 0, sizeof( immediate ) ); + memset( punctuationValid, 0, sizeof( punctuationValid ) ); + for( ptr = punctuation; *ptr != NULL; ptr++ ) { + id = parserPtr->GetPunctuationId( *ptr ); + if ( ( id >= 0 ) && ( id < 256 ) ) { + punctuationValid[ id ] = true; + } + } +} + +/* +============ +idCompiler::Error + +Aborts the current file load +============ +*/ +void idCompiler::Error( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + throw idCompileError( string ); +} + +/* +============ +idCompiler::Warning + +Prints a warning about the current line +============ +*/ +void idCompiler::Warning( const char *message, ... ) const { + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, message ); + vsprintf( string, message, argptr ); + va_end( argptr ); + + parserPtr->Warning( "%s", string ); +} + +/* +============ +idCompiler::VirtualFunctionConstant + +Creates a def for an index into a virtual function table +============ +*/ +ID_INLINE idVarDef *idCompiler::VirtualFunctionConstant( idVarDef *func ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = func->scope->TypeDef()->GetFunctionNumber( func->value.functionPtr ); + if ( eval._int < 0 ) { +// RAVEN BEGIN +// bdube: added scope to print + if( !func->scope || !func->scope->initialized ) { + Error( "Function '%s' not found in uninitialized scope. Make sure function is an object method.", func->Name() ); + } else { + Error( "Function '%s' not found in scope '%s'", func->Name(), func->scope->Name() ); + } +// RAVEN END + } + + return GetImmediate( &type_virtualfunction, &eval, "" ); +} + +/* +============ +idCompiler::SizeConstant + +Creates a def for a size constant +============ +*/ +ID_INLINE idVarDef *idCompiler::SizeConstant( int size ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = size; + return GetImmediate( &type_argsize, &eval, "" ); +} + +/* +============ +idCompiler::JumpConstant + +Creates a def for a jump constant +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpConstant( int value ) { + eval_t eval; + + memset( &eval, 0, sizeof( eval ) ); + eval._int = value; + return GetImmediate( &type_jumpoffset, &eval, "" ); +} + +/* +============ +idCompiler::JumpDef + +Creates a def for a relative jump from one code location to another +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpDef( int jumpfrom, int jumpto ) { + return JumpConstant( jumpto - jumpfrom ); +} + +/* +============ +idCompiler::JumpTo + +Creates a def for a relative jump from current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpTo( int jumpto ) { + return JumpDef( gameLocal.program.NumStatements(), jumpto ); +} + +/* +============ +idCompiler::JumpFrom + +Creates a def for a relative jump from code location to current code location +============ +*/ +ID_INLINE idVarDef *idCompiler::JumpFrom( int jumpfrom ) { + return JumpDef( jumpfrom, gameLocal.program.NumStatements() ); +} + +/* +============ +idCompiler::Divide +============ +*/ +ID_INLINE float idCompiler::Divide( float numerator, float denominator ) { + if ( denominator == 0 ) { + Error( "Divide by zero" ); + return 0; + } + + return numerator / denominator; +} + +/* +============ +idCompiler::FindImmediate + +tries to find an existing immediate with the same value +============ +*/ +idVarDef *idCompiler::FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const { + idVarDef *def; + etype_t etype; + + etype = type->Type(); + + // check for a constant with the same value + for( def = gameLocal.program.GetDefList( "" ); def != NULL; def = def->Next() ) { + if ( def->TypeDef() != type ) { + continue; + } + + switch( etype ) { + case ev_field : + if ( *def->value.intPtr == eval->_int ) { + return def; + } + break; + + case ev_argsize : + if ( def->value.argSize == eval->_int ) { + return def; + } + break; + + case ev_jumpoffset : + if ( def->value.jumpOffset == eval->_int ) { + return def; + } + break; + + case ev_entity : + if ( *def->value.intPtr == eval->entity ) { + return def; + } + break; + + case ev_string : + if ( idStr::Cmp( def->value.stringPtr, string ) == 0 ) { + return def; + } + break; + + case ev_float : + if ( *def->value.floatPtr == eval->_float ) { + return def; + } + break; + + case ev_virtualfunction : + if ( def->value.virtualFunction == eval->_int ) { + return def; + } + break; + + + case ev_vector : + if ( ( def->value.vectorPtr->x == eval->vector[ 0 ] ) && + ( def->value.vectorPtr->y == eval->vector[ 1 ] ) && + ( def->value.vectorPtr->z == eval->vector[ 2 ] ) ) { + return def; + } + break; + + default : + Error( "weird immediate type" ); + break; + } + } + + return NULL; +} + +/* +============ +idCompiler::GetImmediate + +returns an existing immediate with the same value, or allocates a new one +============ +*/ +idVarDef *idCompiler::GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ) { + idVarDef *def; + + def = FindImmediate( type, eval, string ); + if ( def ) { + def->numUsers++; + } else { + // allocate a new def + def = gameLocal.program.AllocDef( type, "", &def_namespace, true ); + if ( type->Type() == ev_string ) { + def->SetString( string, true ); + } else { + def->SetValue( *eval, true ); + } + } + + return def; +} + +/* +============ +idCompiler::OptimizeOpcode + +try to optimize when the operator works on constants only +============ +*/ +idVarDef *idCompiler::OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + eval_t c; + idTypeDef *type; + + if ( var_a && var_a->initialized != idVarDef::initializedConstant ) { + return NULL; + } + if ( var_b && var_b->initialized != idVarDef::initializedConstant ) { + return NULL; + } + + idVec3 &vec_c = *reinterpret_cast( &c.vector[ 0 ] ); + + memset( &c, 0, sizeof( c ) ); + switch( op - opcodes ) { + case OP_ADD_F: c._float = *var_a->value.floatPtr + *var_b->value.floatPtr; type = &type_float; break; + case OP_ADD_V: vec_c = *var_a->value.vectorPtr + *var_b->value.vectorPtr; type = &type_vector; break; + case OP_SUB_F: c._float = *var_a->value.floatPtr - *var_b->value.floatPtr; type = &type_float; break; + case OP_SUB_V: vec_c = *var_a->value.vectorPtr - *var_b->value.vectorPtr; type = &type_vector; break; + case OP_MUL_F: c._float = *var_a->value.floatPtr * *var_b->value.floatPtr; type = &type_float; break; + case OP_MUL_V: c._float = *var_a->value.vectorPtr * *var_b->value.vectorPtr; type = &type_float; break; + case OP_MUL_FV: vec_c = *var_b->value.vectorPtr * *var_a->value.floatPtr; type = &type_vector; break; + case OP_MUL_VF: vec_c = *var_a->value.vectorPtr * *var_b->value.floatPtr; type = &type_vector; break; + case OP_DIV_F: c._float = Divide( *var_a->value.floatPtr, *var_b->value.floatPtr ); type = &type_float; break; + case OP_MOD_F: c._float = (int)*var_a->value.floatPtr % (int)*var_b->value.floatPtr; type = &type_float; break; + case OP_BITAND: c._float = ( int )*var_a->value.floatPtr & ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_BITOR: c._float = ( int )*var_a->value.floatPtr | ( int )*var_b->value.floatPtr; type = &type_float; break; + case OP_GE: c._float = *var_a->value.floatPtr >= *var_b->value.floatPtr; type = &type_float; break; + case OP_LE: c._float = *var_a->value.floatPtr <= *var_b->value.floatPtr; type = &type_float; break; + case OP_GT: c._float = *var_a->value.floatPtr > *var_b->value.floatPtr; type = &type_float; break; + case OP_LT: c._float = *var_a->value.floatPtr < *var_b->value.floatPtr; type = &type_float; break; + case OP_AND: c._float = *var_a->value.floatPtr && *var_b->value.floatPtr; type = &type_float; break; + case OP_OR: c._float = *var_a->value.floatPtr || *var_b->value.floatPtr; type = &type_float; break; + case OP_NOT_BOOL: c._int = !*var_a->value.intPtr; type = &type_boolean; break; + case OP_NOT_F: c._float = !*var_a->value.floatPtr; type = &type_float; break; + case OP_NOT_V: c._float = !var_a->value.vectorPtr->x && !var_a->value.vectorPtr->y && !var_a->value.vectorPtr->z; type = &type_float; break; + case OP_NEG_F: c._float = -*var_a->value.floatPtr; type = &type_float; break; + case OP_NEG_V: vec_c = -*var_a->value.vectorPtr; type = &type_vector; break; + case OP_INT_F: c._float = ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_EQ_F: c._float = ( *var_a->value.floatPtr == *var_b->value.floatPtr ); type = &type_float; break; + case OP_EQ_V: c._float = var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_EQ_E: c._float = ( *var_a->value.intPtr == *var_b->value.intPtr ); type = &type_float; break; + case OP_NE_F: c._float = ( *var_a->value.floatPtr != *var_b->value.floatPtr ); type = &type_float; break; + case OP_NE_V: c._float = !var_a->value.vectorPtr->Compare( *var_b->value.vectorPtr ); type = &type_float; break; + case OP_NE_E: c._float = ( *var_a->value.intPtr != *var_b->value.intPtr ); type = &type_float; break; + case OP_UADD_F: c._float = *var_b->value.floatPtr + *var_a->value.floatPtr; type = &type_float; break; + case OP_USUB_F: c._float = *var_b->value.floatPtr - *var_a->value.floatPtr; type = &type_float; break; + case OP_UMUL_F: c._float = *var_b->value.floatPtr * *var_a->value.floatPtr; type = &type_float; break; + case OP_UDIV_F: c._float = Divide( *var_b->value.floatPtr, *var_a->value.floatPtr ); type = &type_float; break; + case OP_UMOD_F: c._float = ( int ) *var_b->value.floatPtr % ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UOR_F: c._float = ( int )*var_b->value.floatPtr | ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UAND_F: c._float = ( int )*var_b->value.floatPtr & ( int )*var_a->value.floatPtr; type = &type_float; break; + case OP_UINC_F: c._float = *var_a->value.floatPtr + 1; type = &type_float; break; + case OP_UDEC_F: c._float = *var_a->value.floatPtr - 1; type = &type_float; break; + case OP_COMP_F: c._float = ( float )~( int )*var_a->value.floatPtr; type = &type_float; break; + default: type = NULL; break; + } + + if ( !type ) { + return NULL; + } + + if ( var_a ) { + var_a->numUsers--; + if ( var_a->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_a, NULL ); + } + } + if ( var_b ) { + var_b->numUsers--; + if ( var_b->numUsers <= 0 ) { + gameLocal.program.FreeDef( var_b, NULL ); + } + } + + return GetImmediate( type, &c, "" ); +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +idVarDef *idCompiler::EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ) { + statement_t *statement; + idVarDef *var_c; + + var_c = OptimizeOpcode( op, var_a, var_b ); + if ( var_c ) { + return var_c; + } + + if ( var_a && !idStr::Cmp( var_a->Name(), RESULT_STRING ) ) { + var_a->numUsers++; + } + if ( var_b && !idStr::Cmp( var_b->Name(), RESULT_STRING ) ) { + var_b->numUsers++; + } + + statement = gameLocal.program.AllocStatement(); + statement->linenumber = currentLineNumber; + statement->file = currentFileNumber; + + if ( ( op->type_c == &def_void ) || op->rightAssociative ) { + // ifs, gotos, and assignments don't need vars allocated + var_c = NULL; + } else { + // allocate result space + // try to reuse result defs as much as possible + var_c = gameLocal.program.FindFreeResultDef( op->type_c->TypeDef(), RESULT_STRING, scope, var_a, var_b ); + // set user count back to 1, a result def needs to be used twice before it can be reused + var_c->numUsers = 1; + } + + statement->op = op - opcodes; + statement->a = var_a; + statement->b = var_b; + statement->c = var_c; + + if ( op->rightAssociative ) { + return var_a; + } + + return var_c; +} + +/* +============ +idCompiler::EmitOpcode + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +ID_INLINE idVarDef *idCompiler::EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ) { + return EmitOpcode( &opcodes[ op ], var_a, var_b ); +} + +/* +============ +idCompiler::EmitPush + +Emits an opcode to push the variable onto the stack. +============ +*/ +bool idCompiler::EmitPush( idVarDef *expression, const idTypeDef *funcArg ) { + opcode_t *op; + opcode_t *out; + + out = NULL; + for( op = &opcodes[ OP_PUSH_F ]; op->name && !idStr::Cmp( op->name, "" ); op++ ) { + if ( ( funcArg->Type() == op->type_a->Type() ) && ( expression->Type() == op->type_b->Type() ) ) { + out = op; + break; + } + } + + if ( !out ) { + if ( ( expression->TypeDef() != funcArg ) && !expression->TypeDef()->Inherits( funcArg ) ) { + return false; + } + + out = &opcodes[ OP_PUSH_ENT ]; + } + + EmitOpcode( out, expression, 0 ); + + return true; +} + +/* +============== +idCompiler::NextToken + +Sets token, immediateType, and possibly immediate +============== +*/ +void idCompiler::NextToken( void ) { + int i; + + // reset our type + immediateType = NULL; + memset( &immediate, 0, sizeof( immediate ) ); + + // Save the token's line number and filename since when we emit opcodes the current + // token is always the next one to be read + currentLineNumber = token.line; + currentFileNumber = gameLocal.program.GetFilenum( parserPtr->GetFileName() ); + + if ( !parserPtr->ReadToken( &token ) ) { + eof = true; + return; + } + + if ( currentFileNumber != gameLocal.program.GetFilenum( parserPtr->GetFileName() ) ) { + if ( ( braceDepth > 0 ) && ( token != "}" ) ) { + // missing a closing brace. try to give as much info as possible. + if ( scope->Type() == ev_function ) { + Error( "Unexpected end of file inside function '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_object ) { + Error( "Unexpected end of file inside object '%s'. Missing closing braces.", scope->Name() ); + } else if ( scope->Type() == ev_namespace ) { + Error( "Unexpected end of file inside namespace '%s'. Missing closing braces.", scope->Name() ); + } else { + Error( "Unexpected end of file inside braced section" ); + } + } + } + + switch( token.type ) { + case TT_STRING: + // handle quoted strings as a unit + immediateType = &type_string; + return; + + case TT_LITERAL: { + // handle quoted vectors as a unit + immediateType = &type_vector; + idLexer lex( token, token.Length(), parserPtr->GetFileName(), LEXFL_NOERRORS ); + idToken token2; + for( i = 0; i < 3; i++ ) { + if ( !lex.ReadToken( &token2 ) ) { + Error( "Couldn't read vector. '%s' is not in the form of 'x y z'", token.c_str() ); + } + if ( token2.type == TT_PUNCTUATION && token2 == "-" ) { + if ( !lex.CheckTokenType( TT_NUMBER, 0, &token2 ) ) { + Error( "expected a number following '-' but found '%s' in vector '%s'", token2.c_str(), token.c_str() ); + } + immediate.vector[ i ] = -token2.GetFloatValue(); + } else if ( token2.type == TT_NUMBER ) { + immediate.vector[ i ] = token2.GetFloatValue(); + } else { + Error( "vector '%s' is not in the form of 'x y z'. expected float value, found '%s'", token.c_str(), token2.c_str() ); + } + } + return; + } + + case TT_NUMBER: + immediateType = &type_float; + immediate._float = token.GetFloatValue(); + return; + + case TT_PUNCTUATION: + // entity names + if ( token == "$" ) { + immediateType = &type_entity; + parserPtr->ReadToken( &token ); + return; + } + + if ( token == "{" ) { + braceDepth++; + return; + } + + if ( token == "}" ) { + braceDepth--; + return; + } + + if ( punctuationValid[ token.subtype ] ) { + return; + } + + Error( "Unknown punctuation '%s'", token.c_str() ); + break; + + case TT_NAME: + return; + + default: + Error( "Unknown token '%s'", token.c_str() ); + } +} + +/* +============= +idCompiler::ExpectToken + +Issues an Error if the current token isn't equal to string +Gets the next token +============= +*/ +void idCompiler::ExpectToken( const char *string ) { + if ( token != string ) { + Error( "expected '%s', found '%s'", string, token.c_str() ); + } + + NextToken(); +} + +/* +============= +idCompiler::CheckToken + +Returns true and gets the next token if the current token equals string +Returns false and does nothing otherwise +============= +*/ +bool idCompiler::CheckToken( const char *string ) { + if ( token != string ) { + return false; + } + + NextToken(); + + return true; +} + +/* +============ +idCompiler::ParseName + +Checks to see if the current token is a valid name +============ +*/ +void idCompiler::ParseName( idStr &name ) { + if ( token.type != TT_NAME ) { + Error( "'%s' is not a name", token.c_str() ); + } + + name = token; + NextToken(); +} + +/* +============ +idCompiler::SkipOutOfFunction + +For error recovery, pops out of nested braces +============ +*/ +void idCompiler::SkipOutOfFunction( void ) { + while( braceDepth ) { + parserPtr->SkipBracedSection( false ); + braceDepth--; + } + NextToken(); +} + +/* +============ +idCompiler::SkipToSemicolon + +For error recovery +============ +*/ +void idCompiler::SkipToSemicolon( void ) { + do { + if ( CheckToken( ";" ) ) { + return; + } + + NextToken(); + } while( !eof ); +} + +/* +============ +idCompiler::CheckType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::CheckType( void ) { + idTypeDef *type; + + if ( token == "float" ) { + type = &type_float; + } else if ( token == "vector" ) { + type = &type_vector; + } else if ( token == "entity" ) { + type = &type_entity; + } else if ( token == "string" ) { + type = &type_string; + } else if ( token == "void" ) { + type = &type_void; + } else if ( token == "object" ) { + type = &type_object; + } else if ( token == "boolean" ) { + type = &type_boolean; + } else if ( token == "namespace" ) { + type = &type_namespace; + } else if ( token == "scriptEvent" ) { + type = &type_scriptevent; + } else { + type = gameLocal.program.FindType( token.c_str() ); + if ( type && !type->Inherits( &type_object ) ) { + type = NULL; + } + } + + return type; +} + +/* +============ +idCompiler::ParseType + +Parses a variable type, including functions types +============ +*/ +idTypeDef *idCompiler::ParseType( void ) { + idTypeDef *type; + + type = CheckType(); + if ( !type ) { + Error( "\"%s\" is not a type", token.c_str() ); + } + + if ( ( type == &type_scriptevent ) && ( scope != &def_namespace ) ) { + Error( "scriptEvents can only defined in the global namespace" ); + } + + if ( ( type == &type_namespace ) && ( scope->Type() != ev_namespace ) ) { + Error( "A namespace may only be defined globally, or within another namespace" ); + } + + NextToken(); + + return type; +} + +/* +============ +idCompiler::ParseImmediate + +Looks for a preexisting constant +============ +*/ +idVarDef *idCompiler::ParseImmediate( void ) { + idVarDef *def; + + def = GetImmediate( immediateType, &immediate, token.c_str() ); + NextToken(); + + return def; +} + +/* +============ +idCompiler::EmitFunctionParms +============ +*/ +idVarDef *idCompiler::EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ) { + idVarDef *e; + const idTypeDef *type; + const idTypeDef *funcArg; + idVarDef *returnDef; + idTypeDef *returnType; + int arg; + int size; + int resultOp; + + type = func->TypeDef(); + if ( func->Type() != ev_function ) { + Error( "'%s' is not a function", func->Name() ); + } + + // copy the parameters to the global parameter variables + arg = startarg; + size = startsize; + if ( !CheckToken( ")" ) ) { + do { + if ( arg >= type->NumParameters() ) { + Error( "too many parameters" ); + } + + e = GetExpression( TOP_PRIORITY ); + + funcArg = type->GetParmType( arg ); + if ( !EmitPush( e, funcArg ) ) { + Error( "type mismatch on parm %i of call to '%s'", arg + 1, func->Name() ); + } + + if ( funcArg->Type() == ev_object ) { + size += type_object.Size(); + } else { + size += funcArg->Size(); + } + + arg++; + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + if ( arg < type->NumParameters() ) { + Error( "too few parameters for function '%s'", func->Name() ); + } + + if ( op == OP_CALL ) { + EmitOpcode( op, func, 0 ); + } else if ( ( op == OP_OBJECTCALL ) || ( op == OP_OBJTHREAD ) ) { + EmitOpcode( op, object, VirtualFunctionConstant( func ) ); + + // need arg size seperate since script object may be NULL + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = SizeConstant( func->value.functionPtr->parmTotal ); + } else { + EmitOpcode( op, func, SizeConstant( size ) ); + } + + // we need to copy off the result into a temporary result location, so figure out the opcode + returnType = type->ReturnType(); + if ( returnType->Type() == ev_string ) { + resultOp = OP_STORE_S; + returnDef = gameLocal.program.returnStringDef; + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + returnDef = gameLocal.program.returnDef; + + switch( returnType->Type() ) { + case ev_void : + resultOp = OP_STORE_F; + break; + + case ev_boolean : + resultOp = OP_STORE_BOOL; + break; + + case ev_float : + resultOp = OP_STORE_F; + break; + + case ev_vector : + resultOp = OP_STORE_V; + break; + + case ev_entity : + resultOp = OP_STORE_ENT; + break; + + case ev_object : + resultOp = OP_STORE_OBJ; + break; + + default : + Error( "Invalid return type for function '%s'", func->Name() ); + // shut up compiler + resultOp = OP_STORE_OBJ; + break; + } + } + + if ( returnType->Type() == ev_void ) { + // don't need result space since there's no result, so just return the normal result def. + return returnDef; + } + + // allocate result space + // try to reuse result defs as much as possible + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + idVarDef *resultDef = gameLocal.program.FindFreeResultDef( returnType, RESULT_STRING, scope, statement.a, statement.b ); + // set user count back to 0, a result def needs to be used twice before it can be reused + resultDef->numUsers = 0; + + EmitOpcode( resultOp, returnDef, resultDef ); + + return resultDef; +} + +/* +============ +idCompiler::ParseFunctionCall +============ +*/ +idVarDef *idCompiler::ParseFunctionCall( idVarDef *funcDef ) { + assert( funcDef ); + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( funcDef->initialized == idVarDef::uninitialized ) { + Error( "Function '%s' has not been defined yet", funcDef->GlobalName() ); + } + + assert( funcDef->value.functionPtr ); + if ( callthread ) { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + Error( "Built-in functions cannot be called as threads" ); + } + callthread = false; + return EmitFunctionParms( OP_THREAD, funcDef, 0, 0, NULL ); + } else { + if ( ( funcDef->initialized != idVarDef::uninitialized ) && funcDef->value.functionPtr->eventdef ) { + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + if ( !thisdef ) { + Error( "No 'self' within scope" ); + } + + return ParseEventCall( thisdef, funcDef ); + } else { + Error( "Built-in functions cannot be called without an object" ); + } + } + + return EmitFunctionParms( OP_CALL, funcDef, 0, 0, NULL ); + } +} + +/* +============ +idCompiler::ParseObjectCall +============ +*/ +idVarDef *idCompiler::ParseObjectCall( idVarDef *object, idVarDef *func ) { + EmitPush( object, object->TypeDef() ); + if ( callthread ) { + callthread = false; + return EmitFunctionParms( OP_OBJTHREAD, func, 1, type_object.Size(), object ); + } else { + return EmitFunctionParms( OP_OBJECTCALL, func, 1, 0, object ); + } +} + +/* +============ +idCompiler::ParseEventCall +============ +*/ +idVarDef *idCompiler::ParseEventCall( idVarDef *object, idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + + if ( object->Type() == ev_object ) { + EmitPush( object, &type_entity ); + } else { + EmitPush( object, object->TypeDef() ); + } + + return EmitFunctionParms( OP_EVENTCALL, funcDef, 0, type_object.Size(), NULL ); +} + +/* +============ +idCompiler::ParseSysObjectCall +============ +*/ +idVarDef *idCompiler::ParseSysObjectCall( idVarDef *funcDef ) { + if ( callthread ) { + Error( "Cannot call built-in functions as a thread" ); + } + + if ( funcDef->Type() != ev_function ) { + Error( "'%s' is not a function", funcDef->Name() ); + } + + if ( !funcDef->value.functionPtr->eventdef ) { + Error( "\"%s\" cannot be called with object notation", funcDef->Name() ); + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !idThread::GetClassType().RespondsTo( *funcDef->value.functionPtr->eventdef ) ) { +// RAVEN END + Error( "\"%s\" is not callable as a 'sys' function", funcDef->Name() ); + } + + return EmitFunctionParms( OP_SYSCALL, funcDef, 0, 0, NULL ); +} + +/* +============ +idCompiler::LookupDef +============ +*/ +idVarDef *idCompiler::LookupDef( const char *name, const idVarDef *baseobj ) { + idVarDef *def; + idVarDef *field; + etype_t type_b; + etype_t type_c; + opcode_t *op; + + // check if we're accessing a field + if ( baseobj && ( baseobj->Type() == ev_object ) ) { + const idVarDef *tdef; + + def = NULL; + for( tdef = baseobj; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = gameLocal.program.GetDef( NULL, name, tdef ); + if ( def ) { + break; + } + } + } else { + // first look through the defs in our scope + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + // if we're in a member function, check types local to the object + if ( ( scope->Type() != ev_namespace ) && ( scope->scope->Type() == ev_object ) ) { + // get the local object pointer + idVarDef *thisdef = gameLocal.program.GetDef( scope->scope->TypeDef(), "self", scope ); + + field = LookupDef( name, scope->scope->TypeDef()->def ); + if ( !field ) { + Error( "Unknown value \"%s\"", name ); + } + + // type check + type_b = field->Type(); + if ( field->Type() == ev_function ) { + type_c = field->TypeDef()->ReturnType()->Type(); + } else { + type_c = field->TypeDef()->FieldType()->Type(); // field access gets type from field + if ( CheckToken( "++" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for ++" ); + } + def = EmitOpcode( OP_UINCP_F, thisdef, field ); + return def; + } else if ( CheckToken( "--" ) ) { + if ( type_c != ev_float ) { + Error( "Invalid type for --" ); + } + def = EmitOpcode( OP_UDECP_F, thisdef, field ); + return def; + } + } + + op = &opcodes[ OP_INDIRECT_F ]; + while( ( op->type_a->Type() != ev_object ) + || ( type_b != op->type_b->Type() ) || ( type_c != op->type_c->Type() ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && ( op->type_a->Type() == ev_object ) && ( op->type_c->Type() == ev_void ) && + ( type_c != op->type_c->Type() ) ) { + // catches object calls that return a value + break; + } + op++; + if ( !op->name || idStr::Cmp( op->name, "." ) ) { + Error( "no valid opcode to access type '%s'", field->TypeDef()->SuperClass()->Name() ); + } + } + + if ( ( op - opcodes ) == OP_OBJECTCALL ) { + ExpectToken( "(" ); + def = ParseObjectCall( thisdef, field ); + } else { + // emit the conversion opcode + def = EmitOpcode( op, thisdef, field ); + + // field access gets type from field + def->SetTypeDef( field->TypeDef()->FieldType() ); + } + } + } + } + + return def; +} + +/* +============ +idCompiler::ParseValue + +Returns the def for the current token +============ +*/ +idVarDef *idCompiler::ParseValue( void ) { + idVarDef *def; + idVarDef *namespaceDef; + idStr name; + + if ( immediateType == &type_entity ) { + // if an immediate entity ($-prefaced name) then create or lookup a def for it. + // when entities are spawned, they'll lookup the def and point it to them. + def = gameLocal.program.GetDef( &type_entity, "$" + token, &def_namespace ); + if ( !def ) { + def = gameLocal.program.AllocDef( &type_entity, "$" + token, &def_namespace, true ); + } + NextToken(); + return def; + } else if ( immediateType ) { + // if the token is an immediate, allocate a constant for it + return ParseImmediate(); + } + + ParseName( name ); + def = LookupDef( name, basetype ); + if ( !def ) { + if ( basetype ) { + Error( "%s is not a member of %s", name.c_str(), basetype->TypeDef()->Name() ); + } else { + Error( "Unknown value \"%s\"", name.c_str() ); + } + // if namespace, then look up the variable in that namespace + } else if ( def->Type() == ev_namespace ) { + while( def->Type() == ev_namespace ) { + ExpectToken( "::" ); + ParseName( name ); + namespaceDef = def; + def = gameLocal.program.GetDef( NULL, name, namespaceDef ); + if ( !def ) { + Error( "Unknown value \"%s::%s\"", namespaceDef->GlobalName(), name.c_str() ); + } + } + //def = LookupDef( name, basetype ); + } + + return def; +} + +/* +============ +idCompiler::GetTerm +============ +*/ +idVarDef *idCompiler::GetTerm( void ) { + idVarDef *e; + int op; + + if ( !immediateType && CheckToken( "~" ) ) { + e = GetExpression( TILDE_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_COMP_F; + break; + + default : + Error( "type mismatch for ~" ); + + // shut up compiler + op = OP_COMP_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + if ( !immediateType && CheckToken( "!" ) ) { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_boolean : + op = OP_NOT_BOOL; + break; + + case ev_float : + op = OP_NOT_F; + break; + + case ev_string : + op = OP_NOT_S; + break; + + case ev_vector : + op = OP_NOT_V; + break; + + case ev_entity : + op = OP_NOT_ENT; + break; + + case ev_function : + Error( "Invalid type for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + + case ev_object : + op = OP_NOT_ENT; + break; + + default : + Error( "type mismatch for !" ); + + // shut up compiler + op = OP_NOT_F; + break; + } + + return EmitOpcode( op, e, 0 ); + } + + // check for negation operator + if ( !immediateType && CheckToken( "-" ) ) { + // constants are directly negated without an instruction + if ( immediateType == &type_float ) { + immediate._float = -immediate._float; + return ParseImmediate(); + } else if ( immediateType == &type_vector ) { + immediate.vector[0] = -immediate.vector[0]; + immediate.vector[1] = -immediate.vector[1]; + immediate.vector[2] = -immediate.vector[2]; + return ParseImmediate(); + } else { + e = GetExpression( NOT_PRIORITY ); + switch( e->Type() ) { + case ev_float : + op = OP_NEG_F; + break; + + case ev_vector : + op = OP_NEG_V; + break; + default : + Error( "type mismatch for -" ); + + // shut up compiler + op = OP_NEG_F; + break; + } + return EmitOpcode( &opcodes[ op ], e, 0 ); + } + } + + if ( CheckToken( "int" ) ) { + ExpectToken( "(" ); + + e = GetExpression( INT_PRIORITY ); + if ( e->Type() != ev_float ) { + Error( "type mismatch for int()" ); + } + + ExpectToken( ")" ); + + return EmitOpcode( OP_INT_F, e, 0 ); + } + + if ( CheckToken( "thread" ) ) { + callthread = true; + e = GetExpression( FUNCTION_PRIORITY ); + + if ( callthread ) { + Error( "Invalid thread call" ); + } + + // threads return the thread number + gameLocal.program.returnDef->SetTypeDef( &type_float ); + return gameLocal.program.returnDef; + } + + if ( !immediateType && CheckToken( "(" ) ) { + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + return e; + } + + return ParseValue(); +} + +/* +============== +idCompiler::TypeMatches +============== +*/ +bool idCompiler::TypeMatches( etype_t type1, etype_t type2 ) const { + if ( type1 == type2 ) { + return true; + } + + //if ( ( type1 == ev_entity ) && ( type2 == ev_object ) ) { + // return true; + //} + + //if ( ( type2 == ev_entity ) && ( type1 == ev_object ) ) { + // return true; + //} + + return false; +} + +/* +============== +idCompiler::GetExpression +============== +*/ +idVarDef *idCompiler::GetExpression( int priority ) { + opcode_t *op; + opcode_t *oldop; + idVarDef *e; + idVarDef *e2; + const idVarDef *oldtype; + etype_t type_a; + etype_t type_b; + etype_t type_c; + + if ( priority == 0 ) { + return GetTerm(); + } + + e = GetExpression( priority - 1 ); + if ( token == ";" ) { + // save us from searching through the opcodes unneccesarily + return e; + } + + while( 1 ) { + if ( ( priority == FUNCTION_PRIORITY ) && CheckToken( "(" ) ) { + return ParseFunctionCall( e ); + } + + // has to be a punctuation + if ( immediateType ) { + break; + } + + for( op = opcodes; op->name; op++ ) { + if ( ( op->priority == priority ) && CheckToken( op->name ) ) { + break; + } + } + + if ( !op->name ) { + // next token isn't at this priority level + break; + } + + // unary operators act only on the left operand + if ( op->type_b == &def_void ) { + e = EmitOpcode( op, e, 0 ); + return e; + } + + // preserve our base type + oldtype = basetype; + + // field access needs scope from object + if ( ( op->name[ 0 ] == '.' ) && e->TypeDef()->Inherits( &type_object ) ) { + // save off what type this field is part of + basetype = e->TypeDef()->def; + } + + if ( op->rightAssociative ) { + // if last statement is an indirect, change it to an address of + if ( gameLocal.program.NumStatements() > 0 ) { + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + if ( ( statement.op >= OP_INDIRECT_F ) && ( statement.op < OP_ADDRESS ) ) { + statement.op = OP_ADDRESS; + type_pointer.SetPointerType( e->TypeDef() ); + e->SetTypeDef( &type_pointer ); + } + } + + e2 = GetExpression( priority ); + } else { + e2 = GetExpression( priority - 1 ); + } + + // restore type + basetype = oldtype; + + // type check + type_a = e->Type(); + type_b = e2->Type(); + + // field access gets type from field + if ( op->name[ 0 ] == '.' ) { + if ( ( e2->Type() == ev_function ) && e2->TypeDef()->ReturnType() ) { + type_c = e2->TypeDef()->ReturnType()->Type(); + } else if ( e2->TypeDef()->FieldType() ) { + type_c = e2->TypeDef()->FieldType()->Type(); + } else { + // not a field + type_c = ev_error; + } + } else { + type_c = ev_void; + } + + oldop = op; + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) || + ( ( type_c != ev_void ) && !TypeMatches( type_c, op->type_c->Type() ) ) ) { + if ( ( op->priority == FUNCTION_PRIORITY ) && TypeMatches( type_a, op->type_a->Type() ) && TypeMatches( type_b, op->type_b->Type() ) ) { + break; + } + + op++; + if ( !op->name || idStr::Cmp( op->name, oldop->name ) ) { + Error( "type mismatch for '%s'", oldop->name ); + } + } + + switch( op - opcodes ) { + case OP_SYSCALL : + ExpectToken( "(" ); + e = ParseSysObjectCall( e2 ); + break; + + case OP_OBJECTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + case OP_EVENTCALL : + ExpectToken( "(" ); + if ( ( e2->initialized != idVarDef::uninitialized ) && e2->value.functionPtr->eventdef ) { + e = ParseEventCall( e, e2 ); + } else { + e = ParseObjectCall( e, e2 ); + } + break; + + default: + if ( callthread ) { + Error( "Expecting function call after 'thread'" ); + } + + if ( ( type_a == ev_pointer ) && ( type_b != e->TypeDef()->PointerType()->Type() ) ) { + // FIXME: need to make a general case for this + if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_boolean ) ) { + // copy from float to boolean pointer + op = &opcodes[ OP_STOREP_FTOBOOL ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_float ) ) { + // copy from boolean to float pointer + op = &opcodes[ OP_STOREP_BOOLTOF ]; + } else if ( ( op - opcodes == OP_STOREP_F ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from float to string pointer + op = &opcodes[ OP_STOREP_FTOS ]; + } else if ( ( op - opcodes == OP_STOREP_BOOL ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from boolean to string pointer + op = &opcodes[ OP_STOREP_BTOS ]; + } else if ( ( op - opcodes == OP_STOREP_V ) && ( e->TypeDef()->PointerType()->Type() == ev_string ) ) { + // copy from vector to string pointer + op = &opcodes[ OP_STOREP_VTOS ]; + } else if ( ( op - opcodes == OP_STOREP_ENT ) && ( e->TypeDef()->PointerType()->Type() == ev_object ) ) { + // store an entity into an object pointer + op = &opcodes[ OP_STOREP_OBJENT ]; + } else { + Error( "type mismatch for '%s'", op->name ); + } + } + + if ( op->rightAssociative ) { + e = EmitOpcode( op, e2, e ); + } else { + e = EmitOpcode( op, e, e2 ); + } + + if ( op - opcodes == OP_STOREP_OBJENT ) { + // statement.b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in statement.c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + statement_t &statement = gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ); + statement.c = type_pointer.PointerType()->def; + } + + // field access gets type from field + if ( type_c != ev_void ) { + e->SetTypeDef( e2->TypeDef()->FieldType() ); + } + break; + } + } + + return e; +} + +/* +================ +idCompiler::PatchLoop +================ +*/ +void idCompiler::PatchLoop( int start, int continuePos ) { + int i; + statement_t *pos; + + pos = &gameLocal.program.GetStatement( start ); + for( i = start; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_BREAK ) { + pos->op = OP_GOTO; + pos->a = JumpFrom( i ); + } else if ( pos->op == OP_CONTINUE ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, continuePos ); + } + } +} + +/* +================ +idCompiler::ParseReturnStatement +================ +*/ +void idCompiler::ParseReturnStatement( void ) { + idVarDef *e; + etype_t type_a; + etype_t type_b; + opcode_t *op; + + if ( CheckToken( ";" ) ) { + if ( scope->TypeDef()->ReturnType()->Type() != ev_void ) { + Error( "expecting return value" ); + } + + EmitOpcode( OP_RETURN, 0, 0 ); + return; + } + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + type_a = e->Type(); + type_b = scope->TypeDef()->ReturnType()->Type(); + + if ( TypeMatches( type_a, type_b ) ) { + EmitOpcode( OP_RETURN, e, 0 ); + return; + } + + for( op = opcodes; op->name; op++ ) { + if ( !idStr::Cmp( op->name, "=" ) ) { + break; + } + } + + assert( op->name ); + + while( !TypeMatches( type_a, op->type_a->Type() ) || !TypeMatches( type_b, op->type_b->Type() ) ) { + op++; + if ( !op->name || idStr::Cmp( op->name, "=" ) ) { + Error( "type mismatch for return value" ); + } + } + + idTypeDef *returnType = scope->TypeDef()->ReturnType(); + if ( returnType->Type() == ev_string ) { + EmitOpcode( op, e, gameLocal.program.returnStringDef ); + } else { + gameLocal.program.returnDef->SetTypeDef( returnType ); + EmitOpcode( op, e, gameLocal.program.returnDef ); + } + EmitOpcode( OP_RETURN, 0, 0 ); +} + +/* +================ +idCompiler::ParseWhileStatement +================ +*/ +void idCompiler::ParseWhileStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + loopDepth++; + + ExpectToken( "(" ); + + patch2 = gameLocal.program.NumStatements(); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + if ( ( e->initialized == idVarDef::initializedConstant ) && ( *e->value.intPtr != 0 ) ) { + //FIXME: we can completely skip generation of this code in the opposite case + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + } else { + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + ParseStatement(); + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } + + // fixup breaks and continues + PatchLoop( patch2, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseForStatement + +Form of for statement with a counter: + + a = 0; +start: << patch4 + if ( !( a < 10 ) ) { + goto end; << patch1 + } else { + goto process; << patch3 + } + +increment: << patch2 + a = a + 1; + goto start; << goto patch4 + +process: + statements; + goto increment; << goto patch2 + +end: + +Form of for statement without a counter: + + a = 0; +start: << patch2 + if ( !( a < 10 ) ) { + goto end; << patch1 + } + +process: + statements; + goto start; << goto patch2 + +end: +================ +*/ +void idCompiler::ParseForStatement( void ) { + idVarDef *e; + int start; + int patch1; + int patch2; + int patch3; + int patch4; + + loopDepth++; + + start = gameLocal.program.NumStatements(); + + ExpectToken( "(" ); + + // init + if ( !CheckToken( ";" ) ) { + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ";" ); + } + + // condition + patch2 = gameLocal.program.NumStatements(); + + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ";" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + // counter + if ( !CheckToken( ")" ) ) { + patch3 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IF, e, 0 ); + + patch4 = patch2; + patch2 = gameLocal.program.NumStatements(); + do { + GetExpression( TOP_PRIORITY ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + + // goto patch4 + EmitOpcode( OP_GOTO, JumpTo( patch4 ), 0 ); + + // fixup patch3 + gameLocal.program.GetStatement( patch3 ).b = JumpFrom( patch3 ); + } + + ParseStatement(); + + // goto patch2 + EmitOpcode( OP_GOTO, JumpTo( patch2 ), 0 ); + + // fixup patch1 + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + + // fixup breaks and continues + PatchLoop( start, patch2 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseDoWhileStatement +================ +*/ +void idCompiler::ParseDoWhileStatement( void ) { + idVarDef *e; + int patch1; + + loopDepth++; + + patch1 = gameLocal.program.NumStatements(); + ParseStatement(); + ExpectToken( "while" ); + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + ExpectToken( ";" ); + + EmitOpcode( OP_IF, e, JumpTo( patch1 ) ); + + // fixup breaks and continues + PatchLoop( patch1, patch1 ); + + loopDepth--; +} + +/* +================ +idCompiler::ParseIfStatement +================ +*/ +void idCompiler::ParseIfStatement( void ) { + idVarDef *e; + int patch1; + int patch2; + + ExpectToken( "(" ); + e = GetExpression( TOP_PRIORITY ); + ExpectToken( ")" ); + + //FIXME: add check for constant expression + patch1 = gameLocal.program.NumStatements(); + EmitOpcode( OP_IFNOT, e, 0 ); + + ParseStatement(); + + if ( CheckToken( "else" ) ) { + patch2 = gameLocal.program.NumStatements(); + EmitOpcode( OP_GOTO, 0, 0 ); + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + ParseStatement(); + gameLocal.program.GetStatement( patch2 ).a = JumpFrom( patch2 ); + } else { + gameLocal.program.GetStatement( patch1 ).b = JumpFrom( patch1 ); + } +} + +/* +============ +idCompiler::ParseStatement +============ +*/ +void idCompiler::ParseStatement( void ) { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + if ( CheckToken( "{" ) ) { + do { + ParseStatement(); + } while( !CheckToken( "}" ) ); + + return; + } + + if ( CheckToken( "return" ) ) { + ParseReturnStatement(); + return; + } + + if ( CheckToken( "while" ) ) { + ParseWhileStatement(); + return; + } + + if ( CheckToken( "for" ) ) { + ParseForStatement(); + return; + } + + if ( CheckToken( "do" ) ) { + ParseDoWhileStatement(); + return; + } + + if ( CheckToken( "break" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot break outside of a loop" ); + } + EmitOpcode( OP_BREAK, 0, 0 ); + return; + } + + if ( CheckToken( "continue" ) ) { + ExpectToken( ";" ); + if ( !loopDepth ) { + Error( "cannot contine outside of a loop" ); + } + EmitOpcode( OP_CONTINUE, 0, 0 ); + return; + } + + if ( CheckType() != NULL ) { + ParseDefs(); + return; + } + + if ( CheckToken( "if" ) ) { + ParseIfStatement(); + return; + } + + GetExpression( TOP_PRIORITY ); + ExpectToken(";"); +} + +/* +================ +idCompiler::ParseObjectDef +================ +*/ +void idCompiler::ParseObjectDef( const char *objname ) { + idTypeDef *objtype; + idTypeDef *type; + idTypeDef *parentType; + idTypeDef *fieldtype; + idStr name; + const char *fieldname; + idTypeDef newtype( ev_field, NULL, "", 0, NULL ); + idVarDef *oldscope; + int num; + int i; + + oldscope = scope; + if ( scope->Type() != ev_namespace ) { + Error( "Objects cannot be defined within functions or other objects" ); + } + + // make sure it doesn't exist before we create it + if ( gameLocal.program.FindType( objname ) != NULL ) { + Error( "'%s' : redefinition; different basic types", objname ); + } + + // base type + if ( !CheckToken( ":" ) ) { + parentType = &type_object; + } else { + parentType = ParseType(); + if ( !parentType->Inherits( &type_object ) ) { + Error( "Objects may only inherit from objects." ); + } + } + + objtype = gameLocal.program.AllocType( ev_object, NULL, objname, parentType == &type_object ? 0 : parentType->Size(), parentType ); + objtype->def = gameLocal.program.AllocDef( objtype, objname, scope, true ); + scope = objtype->def; + + // inherit all the functions + num = parentType->NumFunctions(); + for( i = 0; i < parentType->NumFunctions(); i++ ) { + const function_t *func = parentType->GetFunction( i ); + objtype->AddFunction( func ); + } + + ExpectToken( "{" ); + + do { + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + continue; + } + + fieldtype = ParseType(); + newtype.SetFieldType( fieldtype ); + + fieldname = va( "%s field", fieldtype->Name() ); + newtype.SetName( fieldname ); + + ParseName( name ); + + // check for a function prototype or declaraction + if ( CheckToken( "(" ) ) { + ParseFunctionDef( newtype.FieldType(), name ); + } else { + type = gameLocal.program.GetType( newtype, true ); + assert( !type->def ); + gameLocal.program.AllocDef( type, name, scope, true ); + objtype->AddField( type, name ); + ExpectToken( ";" ); + } + } while( !CheckToken( "}" ) ); + + scope = oldscope; + + ExpectToken( ";" ); +} + +/* +============ +idCompiler::ParseFunction + +parse a function type +============ +*/ +idTypeDef *idCompiler::ParseFunction( idTypeDef *returnType, const char *name ) { + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + idTypeDef *type; + + if ( scope->Type() != ev_namespace ) { + // create self pointer + newtype.AddFunctionParm( scope->TypeDef(), "self" ); + } + + if ( !CheckToken( ")" ) ) { + idStr parmName; + do { + type = ParseType(); + ParseName( parmName ); + newtype.AddFunctionParm( type, parmName ); + } while( CheckToken( "," ) ); + + ExpectToken( ")" ); + } + + return gameLocal.program.GetType( newtype, true ); +} + +/* +================ +idCompiler::ParseFunctionDef +================ +*/ +void idCompiler::ParseFunctionDef( idTypeDef *returnType, const char *name ) { + idTypeDef *type; + idVarDef *def; + const idVarDef *parm; + idVarDef *oldscope; + int i; + int numParms; + const idTypeDef *parmType; + function_t *func; + statement_t *pos; + + if ( ( scope->Type() != ev_namespace ) && !scope->TypeDef()->Inherits( &type_object ) ) { + Error( "Functions may not be defined within other functions" ); + } + + type = ParseFunction( returnType, name ); + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + type->def = def; + + func = &gameLocal.program.AllocFunction( def ); + if ( scope->TypeDef()->Inherits( &type_object ) ) { + scope->TypeDef()->AddFunction( func ); + } + } else { + func = def->value.functionPtr; + assert( func ); + if ( func->firstStatement ) { + Error( "%s redeclared", def->GlobalName() ); + } + } + +// RAVEN BEGIN +// abahr: moved to below parm size calc as per Jim D +// RAVEN END + + // calculate stack space used by parms + numParms = type->NumParameters(); +// RAVEN BEGIN +// abahr + func->parmTotal = 0; + func->parmSize.Clear(); +// RAVEN END + func->parmSize.SetNum( numParms ); + for( i = 0; i < numParms; i++ ) { + parmType = type->GetParmType( i ); + if ( parmType->Inherits( &type_object ) ) { + func->parmSize[ i ] = type_object.Size(); + } else { + func->parmSize[ i ] = parmType->Size(); + } + func->parmTotal += func->parmSize[ i ]; + } + +// RAVEN BEGIN +// abahr: moved here so prototypes calculate parm sizes + // check if this is a prototype or declaration + if ( !CheckToken( "{" ) ) { + // it's just a prototype, so get the ; and move on + ExpectToken( ";" ); + return; + } +// RAVEN END + + // define the parms + for( i = 0; i < numParms; i++ ) { + if ( gameLocal.program.GetDef( type->GetParmType( i ), type->GetParmName( i ), def ) ) { + Error( "'%s' defined more than once in function parameters", type->GetParmName( i ) ); + } + parm = gameLocal.program.AllocDef( type->GetParmType( i ), type->GetParmName( i ), def, false ); + } + + oldscope = scope; + scope = def; + + func->firstStatement = gameLocal.program.NumStatements(); + + // check if we should call the super class constructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "init" ) ) { + idTypeDef *superClass; + function_t *constructorFunc = NULL; + + // find the superclass constructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + constructorFunc = gameLocal.program.FindFunction( va( "%s::init", superClass->Name() ) ); + if ( constructorFunc ) { + break; + } + } + + // emit the call to the constructor + if ( constructorFunc ) { + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], constructorFunc->def, 0 ); + } + } + + // parse regular statements + while( !CheckToken( "}" ) ) { + ParseStatement(); + } + + // check if we should call the super class destructor + if ( oldscope->TypeDef()->Inherits( &type_object ) && !stricmp( name, "destroy" ) ) { + idTypeDef *superClass; + function_t *destructorFunc = NULL; + + // find the superclass destructor + for( superClass = oldscope->TypeDef()->SuperClass(); superClass != &type_object; superClass = superClass->SuperClass() ) { + destructorFunc = gameLocal.program.FindFunction( va( "%s::destroy", superClass->Name() ) ); + if ( destructorFunc ) { + break; + } + } + + if ( destructorFunc ) { + // change all returns to point to the call to the destructor + pos = &gameLocal.program.GetStatement( func->firstStatement ); + for( i = func->firstStatement; i < gameLocal.program.NumStatements(); i++, pos++ ) { + if ( pos->op == OP_RETURN ) { + pos->op = OP_GOTO; + pos->a = JumpDef( i, gameLocal.program.NumStatements() ); + } + } + + // emit the call to the destructor + idVarDef *selfDef = gameLocal.program.GetDef( type->GetParmType( 0 ), type->GetParmName( 0 ), def ); + assert( selfDef ); + EmitPush( selfDef, selfDef->TypeDef() ); + EmitOpcode( &opcodes[ OP_CALL ], destructorFunc->def, 0 ); + } + } + +// Disabled code since it caused a function to fall through to the next function when last statement is in the form "if ( x ) { return; }" +#if 0 + // don't bother adding a return opcode if the "return" statement was used. + if ( ( func->firstStatement == gameLocal.program.NumStatements() ) || ( gameLocal.program.GetStatement( gameLocal.program.NumStatements() - 1 ).op != OP_RETURN ) ) { + // emit an end of statements opcode + EmitOpcode( OP_RETURN, 0, 0 ); + } +#else + // always emit the return opcode + EmitOpcode( OP_RETURN, 0, 0 ); +#endif + + // record the number of statements in the function + func->numStatements = gameLocal.program.NumStatements() - func->firstStatement; + + scope = oldscope; +} + +/* +================ +idCompiler::ParseVariableDef +================ +*/ +void idCompiler::ParseVariableDef( idTypeDef *type, const char *name ) { + idVarDef *def, *def2; + bool negate; + + def = gameLocal.program.GetDef( type, name, scope ); + if ( def ) { + Error( "%s redeclared", name ); + } + + def = gameLocal.program.AllocDef( type, name, scope, false ); + + // check for an initialization + if ( CheckToken( "=" ) ) { + // if a local variable in a function then write out interpreter code to initialize variable + if ( scope->Type() == ev_function ) { + def2 = GetExpression( TOP_PRIORITY ); + if ( ( type == &type_float ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_F, def2, def ); + } else if ( ( type == &type_vector ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_V, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_string ) ) { + EmitOpcode( OP_STORE_S, def2, def ); + } else if ( ( type == &type_entity ) && ( ( def2->TypeDef() == &type_entity ) || ( def2->TypeDef()->Inherits( &type_object ) ) ) ) { + EmitOpcode( OP_STORE_ENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef() == &type_entity ) ) { + EmitOpcode( OP_STORE_OBJENT, def2, def ); + } else if ( ( type->Inherits( &type_object ) ) && ( def2->TypeDef()->Inherits( type ) ) ) { + EmitOpcode( OP_STORE_OBJ, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOL, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BTOS, def2, def ); + } else if ( ( type == &type_string ) && ( def2->TypeDef() == &type_vector ) ) { + EmitOpcode( OP_STORE_VTOS, def2, def ); + } else if ( ( type == &type_boolean ) && ( def2->TypeDef() == &type_float ) ) { + EmitOpcode( OP_STORE_FTOBOOL, def2, def ); + } else if ( ( type == &type_float ) && ( def2->TypeDef() == &type_boolean ) ) { + EmitOpcode( OP_STORE_BOOLTOF, def2, def ); + } else { + Error( "bad initialization for '%s'", name ); + } + } else { + // global variables can only be initialized with immediate values + negate = false; + if ( token.type == TT_PUNCTUATION && token == "-" ) { + negate = true; + NextToken(); + if ( immediateType != &type_float ) { + Error( "wrong immediate type for '-' on variable '%s'", name ); + } + } + + if ( immediateType != type ) { + Error( "wrong immediate type for '%s'", name ); + } + + // global variables are initialized at start up + if ( type == &type_string ) { + def->SetString( token, false ); + } else { + if ( negate ) { + immediate._float = -immediate._float; + } + def->SetValue( immediate, false ); + } + NextToken(); + } + } else if ( type == &type_string ) { + // local strings on the stack are initialized in the interpreter + if ( scope->Type() != ev_function ) { + def->SetString( "", false ); + } + } else if ( type->Inherits( &type_object ) ) { + if ( scope->Type() != ev_function ) { + def->SetObject( NULL ); + } + } +} + +/* +================ +idCompiler::GetTypeForEventArg +================ +*/ +idTypeDef *idCompiler::GetTypeForEventArg( char argType ) { + idTypeDef *type; + + switch( argType ) { + case D_EVENT_INTEGER : + // this will get converted to int by the interpreter + type = &type_float; + break; + + case D_EVENT_FLOAT : + type = &type_float; + break; + + case D_EVENT_VECTOR : + type = &type_vector; + break; + + case D_EVENT_STRING : + type = &type_string; + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + type = &type_entity; + break; + + case D_EVENT_VOID : + type = &type_void; + break; + + case D_EVENT_TRACE : + // This data type isn't available from script + type = NULL; + break; + + default: + // probably a typo + type = NULL; + break; + } + + return type; +} + +/* +================ +idCompiler::ParseEventDef +================ +*/ +void idCompiler::ParseEventDef( idTypeDef *returnType, const char *name ) { + const idTypeDef *expectedType; + idTypeDef *argType; + idTypeDef *type; + int i; + int num; + const char *format; + const idEventDef *ev; + idStr parmName; + + ev = idEventDef::FindEvent( name ); + if ( !ev ) { + Error( "Unknown event '%s'", name ); + } + + // set the return type + expectedType = GetTypeForEventArg( ev->GetReturnType() ); + if ( !expectedType ) { + Error( "Invalid return type '%c' in definition of '%s' event.", ev->GetReturnType(), name ); + } + if ( returnType != expectedType ) { + Error( "Return type doesn't match internal return type '%s'", expectedType->Name() ); + } + + idTypeDef newtype( ev_function, NULL, name, type_function.Size(), returnType ); + + ExpectToken( "(" ); + + format = ev->GetArgFormat(); + num = strlen( format ); + for( i = 0; i < num; i++ ) { + expectedType = GetTypeForEventArg( format[ i ] ); + if ( !expectedType || ( expectedType == &type_void ) ) { + Error( "Invalid parameter '%c' in definition of '%s' event.", format[ i ], name ); + } + + argType = ParseType(); + ParseName( parmName ); + if ( argType != expectedType ) { + Error( "The type of parm %d ('%s') does not match the internal type '%s' in definition of '%s' event.", + i + 1, parmName.c_str(), expectedType->Name(), name ); + } + + newtype.AddFunctionParm( argType, "" ); + + if ( i < num - 1 ) { + if ( CheckToken( ")" ) ) { + Error( "Too few parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( "," ); + } + } + if ( !CheckToken( ")" ) ) { + Error( "Too many parameters for event definition. Internal definition has %d parameters.", num ); + } + ExpectToken( ";" ); + + type = gameLocal.program.FindType( name ); + if ( type ) { + if ( !newtype.MatchesType( *type ) || ( type->def->value.functionPtr->eventdef != ev ) ) { + Error( "Type mismatch on redefinition of '%s'", name ); + } + } else { + type = gameLocal.program.AllocType( newtype ); + type->def = gameLocal.program.AllocDef( type, name, &def_namespace, true ); + + function_t &func = gameLocal.program.AllocFunction( type->def ); + func.eventdef = ev; + func.parmSize.SetNum( num ); + for( i = 0; i < num; i++ ) { + argType = newtype.GetParmType( i ); + func.parmTotal += argType->Size(); + func.parmSize[ i ] = argType->Size(); + } + + // mark the parms as local + func.locals = func.parmTotal; + } +} + +/* +================ +idCompiler::ParseDefs + +Called at the outer layer and when a local statement is hit +================ +*/ +void idCompiler::ParseDefs( void ) { + idStr name; + idTypeDef *type; + idVarDef *def; + idVarDef *oldscope; + + if ( CheckToken( ";" ) ) { + // skip semicolons, which are harmless and ok syntax + return; + } + + type = ParseType(); + if ( type == &type_scriptevent ) { + type = ParseType(); + ParseName( name ); + ParseEventDef( type, name ); + return; + } + + ParseName( name ); + + if ( type == &type_namespace ) { + def = gameLocal.program.GetDef( type, name, scope ); + if ( !def ) { + def = gameLocal.program.AllocDef( type, name, scope, true ); + } + ParseNamespace( def ); + } else if ( CheckToken( "::" ) ) { + def = gameLocal.program.GetDef( NULL, name, scope ); + if ( !def ) { + Error( "Unknown object name '%s'", name.c_str() ); + } + ParseName( name ); + oldscope = scope; + scope = def; + + ExpectToken( "(" ); + ParseFunctionDef( type, name.c_str() ); + scope = oldscope; + } else if ( type == &type_object ) { + ParseObjectDef( name.c_str() ); + } else if ( CheckToken( "(" ) ) { // check for a function prototype or declaraction + ParseFunctionDef( type, name.c_str() ); + } else { + ParseVariableDef( type, name.c_str() ); + while( CheckToken( "," ) ) { + ParseName( name ); + ParseVariableDef( type, name.c_str() ); + } + ExpectToken( ";" ); + } +} + +/* +================ +idCompiler::ParseNamespace + +Parses anything within a namespace definition +================ +*/ +void idCompiler::ParseNamespace( idVarDef *newScope ) { + idVarDef *oldscope; + + oldscope = scope; + if ( newScope != &def_namespace ) { + ExpectToken( "{" ); + } + + while( !eof ) { + scope = newScope; + callthread = false; + + if ( ( newScope != &def_namespace ) && CheckToken( "}" ) ) { + break; + } + + ParseDefs(); + } + + scope = oldscope; +} + +/* +============ +idCompiler::CompileFile + +compiles the 0 terminated text, adding definitions to the program structure +============ +*/ +void idCompiler::CompileFile( const char *text, const char *filename, bool toConsole ) { + idStr orig = filename; + idTimer compile_time; + bool error; + + compile_time.Start(); + + scope = &def_namespace; + basetype = NULL; + callthread = false; + loopDepth = 0; + eof = false; + braceDepth = 0; + immediateType = NULL; + currentLineNumber = 0; + console = toConsole; + + memset( &immediate, 0, sizeof( immediate ) ); + + parser.SetFlags( LEXFL_ALLOWMULTICHARLITERALS ); + parser.LoadMemory( text, strlen( text ), filename ); + parserPtr = &parser; + + // unread tokens to include script defines + token = SCRIPT_DEFAULTDEFS; + token.type = TT_STRING; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "include"; + token.type = TT_NAME; + token.subtype = token.Length(); + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + token = "#"; + token.type = TT_PUNCTUATION; + token.subtype = P_PRECOMP; + token.line = token.linesCrossed = 0; + parser.UnreadToken( &token ); + + // init the current token line to be the first line so that currentLineNumber is set correctly in NextToken + token.line = 1; + + error = false; + try { + // read first token + NextToken(); + while( !eof && !error ) { + // parse from global namespace + ParseNamespace( &def_namespace ); + } + } + + catch( idCompileError &err ) { + idStr error; + + if ( console ) { + // don't print line number of an error if were calling script from the console using the "script" command + sprintf( error, "Error: %s\n", err.error ); + } else { + sprintf( error, "Error: file %s, line %d: %s\n", gameLocal.program.GetFilename( currentFileNumber ), currentLineNumber, err.error ); + } + + parser.FreeSource(); + + throw idCompileError( error ); + } + + parser.FreeSource(); + + compile_time.Stop(); + if ( !toConsole ) { + gameLocal.Printf( "Compiled '%s': %.1f ms\n", orig.c_str(), compile_time.Milliseconds() ); + } +} diff --git a/src/mpgame/script/Script_Compiler.h b/src/mpgame/script/Script_Compiler.h new file mode 100644 index 0000000..0ec1c60 --- /dev/null +++ b/src/mpgame/script/Script_Compiler.h @@ -0,0 +1,275 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_COMPILER_H__ +#define __SCRIPT_COMPILER_H__ + +const char * const RESULT_STRING = ""; + +typedef struct opcode_s { + char *name; + char *opname; + int priority; + bool rightAssociative; + idVarDef *type_a; + idVarDef *type_b; + idVarDef *type_c; +} opcode_t; + +// These opcodes are no longer necessary: +// OP_PUSH_OBJ: +// OP_PUSH_OBJENT: + +enum { + OP_RETURN, + + OP_UINC_F, + OP_UINCP_F, + OP_UDEC_F, + OP_UDECP_F, + OP_COMP_F, + + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_MOD_F, + OP_ADD_F, + OP_ADD_V, + OP_ADD_S, + OP_ADD_FS, + OP_ADD_SF, + OP_ADD_VS, + OP_ADD_SV, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_EO, + OP_EQ_OE, + OP_EQ_OO, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_EO, + OP_NE_OE, + OP_NE_OO, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_INDIRECT_F, + OP_INDIRECT_V, + OP_INDIRECT_S, + OP_INDIRECT_ENT, + OP_INDIRECT_BOOL, + OP_INDIRECT_OBJ, + + OP_ADDRESS, + + OP_EVENTCALL, + OP_OBJECTCALL, + OP_SYSCALL, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_BOOL, + OP_STORE_OBJENT, + OP_STORE_OBJ, + OP_STORE_ENTOBJ, + + OP_STORE_FTOS, + OP_STORE_BTOS, + OP_STORE_VTOS, + OP_STORE_FTOBOOL, + OP_STORE_BOOLTOF, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_BOOL, + OP_STOREP_OBJ, + OP_STOREP_OBJENT, + + OP_STOREP_FTOS, + OP_STOREP_BTOS, + OP_STOREP_VTOS, + OP_STOREP_FTOBOOL, + OP_STOREP_BOOLTOF, + + OP_UMUL_F, + OP_UMUL_V, + OP_UDIV_F, + OP_UDIV_V, + OP_UMOD_F, + OP_UADD_F, + OP_UADD_V, + OP_USUB_F, + OP_USUB_V, + OP_UAND_F, + OP_UOR_F, + + OP_NOT_BOOL, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + + OP_NEG_F, + OP_NEG_V, + + OP_INT_F, + OP_IF, + OP_IFNOT, + + OP_CALL, + OP_THREAD, + OP_OBJTHREAD, + + OP_PUSH_F, + OP_PUSH_V, + OP_PUSH_S, + OP_PUSH_ENT, + OP_PUSH_OBJ, + OP_PUSH_OBJENT, + OP_PUSH_FTOS, + OP_PUSH_BTOF, + OP_PUSH_FTOB, + OP_PUSH_VTOS, + OP_PUSH_BTOS, + + OP_GOTO, + + OP_AND, + OP_AND_BOOLF, + OP_AND_FBOOL, + OP_AND_BOOLBOOL, + OP_OR, + OP_OR_BOOLF, + OP_OR_FBOOL, + OP_OR_BOOLBOOL, + + OP_BITAND, + OP_BITOR, + + OP_BREAK, // placeholder op. not used in final code + OP_CONTINUE, // placeholder op. not used in final code + + NUM_OPCODES +}; + +class idCompiler { +private: + static bool punctuationValid[ 256 ]; + static char *punctuation[]; + + idParser parser; + idParser *parserPtr; + idToken token; + + idTypeDef *immediateType; + eval_t immediate; + + bool eof; + bool console; + bool callthread; + int braceDepth; + int loopDepth; + int currentLineNumber; + int currentFileNumber; + int errorCount; + + idVarDef *scope; // the function being parsed, or NULL + const idVarDef *basetype; // for accessing fields + + float Divide( float numerator, float denominator ); + void Error( const char *error, ... ) const; + void Warning( const char *message, ... ) const; + idVarDef *OptimizeOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( const opcode_t *op, idVarDef *var_a, idVarDef *var_b ); + idVarDef *EmitOpcode( int op, idVarDef *var_a, idVarDef *var_b ); + bool EmitPush( idVarDef *expression, const idTypeDef *funcArg ); + void NextToken( void ); + void ExpectToken( const char *string ); + bool CheckToken( const char *string ); + void ParseName( idStr &name ); + void SkipOutOfFunction( void ); + void SkipToSemicolon( void ); + idTypeDef *CheckType( void ); + idTypeDef *ParseType( void ); + idVarDef *FindImmediate( const idTypeDef *type, const eval_t *eval, const char *string ) const; + idVarDef *GetImmediate( idTypeDef *type, const eval_t *eval, const char *string ); + idVarDef *VirtualFunctionConstant( idVarDef *func ); + idVarDef *SizeConstant( int size ); + idVarDef *JumpConstant( int value ); + idVarDef *JumpDef( int jumpfrom, int jumpto ); + idVarDef *JumpTo( int jumpto ); + idVarDef *JumpFrom( int jumpfrom ); + idVarDef *ParseImmediate( void ); + idVarDef *EmitFunctionParms( int op, idVarDef *func, int startarg, int startsize, idVarDef *object ); + idVarDef *ParseFunctionCall( idVarDef *func ); + idVarDef *ParseObjectCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseEventCall( idVarDef *object, idVarDef *func ); + idVarDef *ParseSysObjectCall( idVarDef *func ); + idVarDef *LookupDef( const char *name, const idVarDef *baseobj ); + idVarDef *ParseValue( void ); + idVarDef *GetTerm( void ); + bool TypeMatches( etype_t type1, etype_t type2 ) const; + idVarDef *GetExpression( int priority ); + idTypeDef *GetTypeForEventArg( char argType ); + void PatchLoop( int start, int continuePos ); + void ParseReturnStatement( void ); + void ParseWhileStatement( void ); + void ParseForStatement( void ); + void ParseDoWhileStatement( void ); + void ParseIfStatement( void ); + void ParseStatement( void ); + void ParseObjectDef( const char *objname ); + idTypeDef *ParseFunction( idTypeDef *returnType, const char *name ); + void ParseFunctionDef( idTypeDef *returnType, const char *name ); + void ParseVariableDef( idTypeDef *type, const char *name ); + void ParseEventDef( idTypeDef *type, const char *name ); + void ParseDefs( void ); + void ParseNamespace( idVarDef *newScope ); + +public : + static opcode_t opcodes[]; + + idCompiler(); + void CompileFile( const char *text, const char *filename, bool console ); +}; + +#endif /* !__SCRIPT_COMPILER_H__ */ diff --git a/src/mpgame/script/Script_Interpreter.cpp b/src/mpgame/script/Script_Interpreter.cpp new file mode 100644 index 0000000..3e349c9 --- /dev/null +++ b/src/mpgame/script/Script_Interpreter.cpp @@ -0,0 +1,1914 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +/* +================ +idInterpreter::idInterpreter() +================ +*/ +idInterpreter::idInterpreter() { + localstackUsed = 0; + terminateOnExit = true; + debug = 0; + LastScriptVariable = 0; + memset( localstack, 0, sizeof( localstack ) ); + memset( callStack, 0, sizeof( callStack ) ); + Reset(); +} + +/* +================ +idInterpreter::Save +================ +*/ +void idInterpreter::Save( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->WriteInt( callStack[i].s ); + if ( callStack[i].f ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( callStack[i].f ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( callStack[i].stackbase ); + } + savefile->WriteInt( maxStackDepth ); + + savefile->WriteInt( localstackUsed ); + savefile->Write( &localstack, localstackUsed ); + + savefile->WriteInt( localstackBase ); + savefile->WriteInt( maxLocalstackUsed ); + + if ( currentFunction ) { + savefile->WriteInt( gameLocal.program.GetFunctionIndex( currentFunction ) ); + } else { + savefile->WriteInt( -1 ); + } + savefile->WriteInt( instructionPointer ); + + savefile->WriteInt( popParms ); + + // TOSAVE: idVarDef *LastScriptVariable; + if ( multiFrameEvent ) { + savefile->WriteString( multiFrameEvent->GetName() ); + } else { + savefile->WriteString( "" ); + } + savefile->WriteObject( eventEntity ); + + savefile->WriteObject( thread ); + + savefile->WriteBool( doneProcessing ); + savefile->WriteBool( threadDying ); + savefile->WriteBool( terminateOnExit ); + savefile->WriteBool( debug ); +} + +/* +================ +idInterpreter::Restore +================ +*/ +void idInterpreter::Restore( idRestoreGame *savefile ) { + int i; + idStr funcname; + int func_index; + + savefile->ReadInt( callStackDepth ); + for( i = 0; i < callStackDepth; i++ ) { + savefile->ReadInt( callStack[i].s ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + callStack[i].f = gameLocal.program.GetFunction( func_index ); + } else { + callStack[i].f = NULL; + } + + savefile->ReadInt( callStack[i].stackbase ); + } + savefile->ReadInt( maxStackDepth ); + + savefile->ReadInt( localstackUsed ); + savefile->Read( &localstack, localstackUsed ); + + savefile->ReadInt( localstackBase ); + savefile->ReadInt( maxLocalstackUsed ); + + savefile->ReadInt( func_index ); + if ( func_index >= 0 ) { + currentFunction = gameLocal.program.GetFunction( func_index ); + } else { + currentFunction = NULL; + } + savefile->ReadInt( instructionPointer ); + + savefile->ReadInt( popParms ); + + // TORESTORE: idVarDef *LastScriptVariable; + + savefile->ReadString( funcname ); + if ( funcname.Length() ) { + multiFrameEvent = idEventDef::FindEvent( funcname ); + } + + savefile->ReadObject( reinterpret_cast( eventEntity ) ); + savefile->ReadObject( reinterpret_cast( thread ) ); + + savefile->ReadBool( doneProcessing ); + savefile->ReadBool( threadDying ); + savefile->ReadBool( terminateOnExit ); + savefile->ReadBool( debug ); +} + +/* +================ +idInterpreter::Reset +================ +*/ +void idInterpreter::Reset( void ) { + callStackDepth = 0; + localstackUsed = 0; + localstackBase = 0; + + maxLocalstackUsed = 0; + maxStackDepth = 0; + + popParms = 0; + multiFrameEvent = NULL; + eventEntity = NULL; + + currentFunction = 0; + NextInstruction( 0 ); + + threadDying = false; + doneProcessing = true; +} + +/* +================ +idInterpreter::GetRegisterValue + +Returns a string representation of the value of the register. This is +used primarily for the debugger and debugging + +//FIXME: This is pretty much wrong. won't access data in most situations. +================ +*/ +// RAVEN BEGIN +// bdube: took this over again +bool idInterpreter::GetRegisterValue( const char *name, idStr &out, int scopeDepth ) { + varEval_t reg; + idVarDef *d; + char funcObject[ 1024 ]; + char *funcName; + const idVarDef *scope = NULL; + const idVarDef *scopeObj; + const idTypeDef *field; + const function_t *func; + + out.Empty(); + + if ( scopeDepth == -1 ) { + scopeDepth = callStackDepth; + } + + if ( scopeDepth == callStackDepth ) { + func = currentFunction; + } else { + func = callStack[ scopeDepth ].f; + } + if ( !func ) { + return false; + } + + idStr::Copynz( funcObject, func->Name(), sizeof( funcObject ) ); + funcName = strstr( funcObject, "::" ); + if ( funcName ) { + *funcName = '\0'; + scopeObj = gameLocal.program.GetDef( NULL, funcObject, &def_namespace ); + funcName += 2; + if ( scopeObj ) + { + scope = gameLocal.program.GetDef( NULL, funcName, scopeObj ); + } + } else { + funcName = funcObject; + scope = gameLocal.program.GetDef( NULL, func->Name(), &def_namespace ); + scopeObj = NULL; + } + + if ( !scope ) + { + return false; + } + + d = gameLocal.program.GetDef( NULL, name, scope ); + + // Check the objects for it if it wasnt local to the function + if ( !d ) + { + for ( ; scopeObj && scopeObj->TypeDef()->SuperClass(); scopeObj = scopeObj->TypeDef()->SuperClass()->def ) + { + d = gameLocal.program.GetDef( NULL, name, scopeObj ); + if ( d ) + { + break; + } + } + } + + if ( !d ) + { + out = "???"; + return false; + } + + reg = GetVariable( d ); + switch( d->Type() ) { + case ev_float: + if ( reg.floatPtr ) { + out = va("%g", *reg.floatPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_vector: + if ( reg.vectorPtr ) { + out = va( "%g,%g,%g", reg.vectorPtr->x, reg.vectorPtr->y, reg.vectorPtr->z ); + } else { + out = "0,0,0"; + } + return true; + break; + + case ev_boolean: + if ( reg.intPtr ) { + out = va( "%d", *reg.intPtr ); + } else { + out = "0"; + } + return true; + break; + + case ev_field: + { + idEntity* entity; + idScriptObject* obj; + + if ( scope == &def_namespace ) { + // should never happen, but handle it safely anyway + return false; + } + + field = d->TypeDef()->FieldType(); + entity = GetEntity ( *((int*)&localstack[ localstackBase ]) ); + if ( !entity || !field ) + { + return false; + } + + obj = &entity->scriptObject; + if ( !obj ) { + return false; + } + + switch ( field->Type() ) { + case ev_boolean: + out = va( "%d", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_float: + out = va( "%g", *( reinterpret_cast( &obj->data[ reg.ptrOffset ] ) ) ); + return true; + + case ev_string: { + const char* str; + str = reinterpret_cast( &obj->data[ reg.ptrOffset ] ); + if ( !str ) { + out = "\"\""; + } else { + out = "\""; + out += str; + out += "\""; + } + return true; + } + + default: + return false; + } + + break; + } + + case ev_string: + if ( reg.stringPtr ) { + out = "\""; + out += reg.stringPtr; + out += "\""; + } else { + out = "\"\""; + } + return true; + + default: + return false; + } +} +// RAVEN END + +/* +================ +idInterpreter::GetCallstackDepth +================ +*/ +int idInterpreter::GetCallstackDepth( void ) const { + return callStackDepth; +} + +/* +================ +idInterpreter::GetCallstack +================ +*/ +const prstack_t *idInterpreter::GetCallstack( void ) const { + return &callStack[ 0 ]; +} + +/* +================ +idInterpreter::GetCurrentFunction +================ +*/ +const function_t *idInterpreter::GetCurrentFunction( void ) const { + return currentFunction; +} + +/* +================ +idInterpreter::GetThread +================ +*/ +idThread *idInterpreter::GetThread( void ) const { + return thread; +} + + +/* +================ +idInterpreter::SetThread +================ +*/ +void idInterpreter::SetThread( idThread *pThread ) { + thread = pThread; +} + +/* +================ +idInterpreter::CurrentLine +================ +*/ +int idInterpreter::CurrentLine( void ) const { + if ( instructionPointer < 0 ) { + return 0; + } + return gameLocal.program.GetLineNumberForStatement( instructionPointer ); +} + +/* +================ +idInterpreter::CurrentFile +================ +*/ +const char *idInterpreter::CurrentFile( void ) const { + if ( instructionPointer < 0 ) { + return ""; + } + return gameLocal.program.GetFilenameForStatement( instructionPointer ); +} + +/* +============ +idInterpreter::StackTrace +============ +*/ +void idInterpreter::StackTrace( void ) const { + const function_t *f; + int i; + int top; + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + return; + } + + top = callStackDepth; + if ( top >= MAX_STACK_DEPTH ) { + top = MAX_STACK_DEPTH - 1; + } + + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = top; i >= 0; i-- ) { + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } +} + +/* +============ +idInterpreter::Error + +Aborts the currently executing function +============ +*/ +void idInterpreter::Error( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + StackTrace(); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Error( "%s(%d): Thread '%s': %s\n", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Error( "Thread '%s': %s\n", thread->GetThreadName(), text ); + } +} + +/* +============ +idInterpreter::Warning + +Prints file and line number information with warning. +============ +*/ +void idInterpreter::Warning( char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( ( instructionPointer >= 0 ) && ( instructionPointer < gameLocal.program.NumStatements() ) ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + common->Warning( "%s(%d): Thread '%s': %s", gameLocal.program.GetFilename( line.file ), line.linenumber, thread->GetThreadName(), text ); + } else { + common->Warning( "Thread '%s' : %s", thread->GetThreadName(), text ); + } +} + +/* +================ +idInterpreter::DisplayInfo +================ +*/ +void idInterpreter::DisplayInfo( void ) const { + const function_t *f; + int i; + + gameLocal.Printf( " Stack depth: %d bytes, %d max\n", localstackUsed, maxLocalstackUsed ); + gameLocal.Printf( " Call depth: %d, %d max\n", callStackDepth, maxStackDepth ); + gameLocal.Printf( " Call Stack: " ); + + if ( callStackDepth == 0 ) { + gameLocal.Printf( "\n" ); + } else { + if ( !currentFunction ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( currentFunction->filenum ), currentFunction->Name() ); + } + + for( i = callStackDepth; i > 0; i-- ) { + gameLocal.Printf( " " ); + f = callStack[ i ].f; + if ( !f ) { + gameLocal.Printf( "\n" ); + } else { + gameLocal.Printf( "%12s : %s\n", gameLocal.program.GetFilename( f->filenum ), f->Name() ); + } + } + } +} + +/* +==================== +idInterpreter::ThreadCall + +Copys the args from the calling thread's stack +==================== +*/ +void idInterpreter::ThreadCall( idInterpreter *source, const function_t *func, int args ) { + Reset(); + +// RAVEN BEGIN +// JSinger: Changed to call optimized memcpy + SIMDProcessor->Memcpy( localstack, &source->localstack[ source->localstackUsed - args ], args ); +// RAVEN END + + localstackUsed = args; + localstackBase = 0; + + maxLocalstackUsed = localstackUsed; + EnterFunction( func, false ); + + thread->SetThreadName( currentFunction->Name() ); +} + +/* +================ +idInterpreter::EnterObjectFunction + +Calls a function on a script object. + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +================ +*/ +void idInterpreter::EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ) { + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + Push( self->entityNumber + 1 ); + EnterFunction( func, false ); +} + +/* +==================== +idInterpreter::EnterFunction + +Returns the new program statement counter + +NOTE: If this is called from within a event called by this interpreter, the function arguments will be invalid after calling this function. +==================== +*/ +void idInterpreter::EnterFunction( const function_t *func, bool clearStack ) { + int c; + prstack_t *stack; + + if ( clearStack ) { + Reset(); + } + if ( popParms ) { + PopParms( popParms ); + popParms = 0; + } + + if ( callStackDepth >= MAX_STACK_DEPTH ) { + Error( "call stack overflow" ); + } + + stack = &callStack[ callStackDepth ]; + + stack->s = instructionPointer + 1; // point to the next instruction to execute + stack->f = currentFunction; + stack->stackbase = localstackBase; + + callStackDepth++; + if ( callStackDepth > maxStackDepth ) { + maxStackDepth = callStackDepth; + } + + if ( !func ) { + Error( "NULL function" ); + } + + if ( debug ) { + if ( currentFunction ) { + gameLocal.Printf( "%d: call '%s' from '%s'(line %d)%s\n", gameLocal.time, func->Name(), currentFunction->Name(), + gameLocal.program.GetStatement( instructionPointer ).linenumber, clearStack ? " clear stack" : "" ); + } else { + gameLocal.Printf( "%d: call '%s'%s\n", gameLocal.time, func->Name(), clearStack ? " clear stack" : "" ); + } + } + + currentFunction = func; + assert( !func->eventdef ); + NextInstruction( func->firstStatement ); + + // allocate space on the stack for locals + // parms are already on stack + c = func->locals - func->parmTotal; + assert( c >= 0 ); + + if ( localstackUsed + c > LOCALSTACK_SIZE ) { + Error( "EnterFuncton: locals stack overflow\n" ); + } + + // initialize local stack variables to zero + memset( &localstack[ localstackUsed ], 0, c ); + + localstackUsed += c; + localstackBase = localstackUsed - func->locals; + + if ( localstackUsed > maxLocalstackUsed ) { + maxLocalstackUsed = localstackUsed ; + } +} + +/* +==================== +idInterpreter::LeaveFunction +==================== +*/ +void idInterpreter::LeaveFunction( idVarDef *returnDef ) { + prstack_t *stack; + varEval_t ret; + + if ( callStackDepth <= 0 ) { + Error( "prog stack underflow" ); + } + + // return value + if ( returnDef ) { + switch( returnDef->Type() ) { + case ev_string : + gameLocal.program.ReturnString( GetString( returnDef ) ); + break; + + case ev_vector : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnVector( *ret.vectorPtr ); + break; + + default : + ret = GetVariable( returnDef ); + gameLocal.program.ReturnInteger( *ret.intPtr ); + } + } + + // remove locals from the stack + PopParms( currentFunction->locals ); + assert( localstackUsed == localstackBase ); + + if ( debug ) { + statement_t &line = gameLocal.program.GetStatement( instructionPointer ); + gameLocal.Printf( "%d: %s(%d): exit %s", gameLocal.time, gameLocal.program.GetFilename( line.file ), line.linenumber, currentFunction->Name() ); + if ( callStackDepth > 1 ) { + gameLocal.Printf( " return to %s(line %d)\n", callStack[ callStackDepth - 1 ].f->Name(), gameLocal.program.GetStatement( callStack[ callStackDepth - 1 ].s ).linenumber ); + } else { + gameLocal.Printf( " done\n" ); + } + } + + // up stack + callStackDepth--; + stack = &callStack[ callStackDepth ]; + currentFunction = stack->f; + localstackBase = stack->stackbase; + NextInstruction( stack->s ); + + if ( !callStackDepth ) { + // all done + doneProcessing = true; + threadDying = true; + currentFunction = 0; + } +} + +/* +================ +idInterpreter::CallEvent +================ +*/ +void idInterpreter::CallEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t var; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + var.intPtr = ( int * )&localstack[ start ]; + eventEntity = GetEntity( *var.entityNumberPtr ); + + if ( !eventEntity || !eventEntity->RespondsTo( *evdef ) ) { +// RAVEN BEGIN +// jshepard: added catch for entities that don't exist + if ( eventEntity && developer.GetBool() ) { + // give a warning in developer mode + Warning( "Function '%s' not supported on entity '%s'", evdef->GetName(), eventEntity->name.c_str() ); + } else if( !eventEntity ) { + //check the last script variable. + if(!!LastScriptVariable) { + //if it's $null_entity, then we should ignore this. + if(idStr::Cmp( "$null_entity", LastScriptVariable->Name()) ) { + Warning( "Entity '%s' is a null entity", LastScriptVariable->Name() ); + } + } else { + Warning( "Null entity referenced -- check entity name spelling."); + } + } +// RAVEN END + // always return a safe value when an object doesn't exist + switch( evdef->GetReturnType() ) { + case D_EVENT_INTEGER : + gameLocal.program.ReturnInteger( 0 ); + break; + + case D_EVENT_FLOAT : + gameLocal.program.ReturnFloat( 0 ); + break; + + case D_EVENT_VECTOR : + gameLocal.program.ReturnVector( vec3_zero ); + break; + + case D_EVENT_STRING : + gameLocal.program.ReturnString( "" ); + break; + + case D_EVENT_ENTITY : + case D_EVENT_ENTITY_NULL : + gameLocal.program.ReturnEntity( ( idEntity * )NULL ); + break; + + case D_EVENT_TRACE : + default: + // unsupported data type + break; + } + + PopParms( argsize ); + eventEntity = NULL; + return; + } + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = type_object.Size(); ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + var.intPtr = ( int * )&localstack[ start + pos ]; + data[ i ] = int( *var.floatPtr ); + break; + + case D_EVENT_FLOAT : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( float * )&data[ i ] ) = *var.floatPtr; + break; + + case D_EVENT_VECTOR : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idVec3 ** )&data[ i ] ) = var.vectorPtr; + break; + + case D_EVENT_STRING : + ( *( const char ** )&data[ i ] ) = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + if ( !( *( idEntity ** )&data[ i ] ) ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + var.intPtr = ( int * )&localstack[ start + pos ]; + ( *( idEntity ** )&data[ i ] ) = GetEntity( *var.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + eventEntity->ProcessEventArgPtr( evdef, data ); + if ( !multiFrameEvent ) { + if ( popParms ) { + PopParms( popParms ); + } + eventEntity = NULL; + } else { + doneProcessing = true; + } + popParms = 0; +} + +/* +================ +idInterpreter::BeginMultiFrameEvent +================ +*/ +bool idInterpreter::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( eventEntity != ent ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong entity" ); + } + if ( multiFrameEvent ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::BeginMultiFrameEvent called with wrong event" ); + } + return false; + } + + multiFrameEvent = event; + return true; +} + +/* +================ +idInterpreter::EndMultiFrameEvent +================ +*/ +void idInterpreter::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( multiFrameEvent != event ) { + Error( "idInterpreter::EndMultiFrameEvent called with wrong event" ); + } + + multiFrameEvent = NULL; +} + +/* +================ +idInterpreter::MultiFrameEventInProgress +================ +*/ +bool idInterpreter::MultiFrameEventInProgress( void ) const { + return multiFrameEvent != NULL; +} + +/* +================ +idInterpreter::CallSysEvent +================ +*/ +void idInterpreter::CallSysEvent( const function_t *func, int argsize ) { + int i; + int j; + varEval_t source; + int pos; + int start; + int data[ D_EVENT_MAXARGS ]; + const idEventDef *evdef; + const char *format; + + if ( !func ) { + Error( "NULL function" ); + } + + assert( func->eventdef ); + evdef = func->eventdef; + + start = localstackUsed - argsize; + + format = evdef->GetArgFormat(); + for( j = 0, i = 0, pos = 0; ( pos < argsize ) || ( format[ i ] != 0 ); i++ ) { + switch( format[ i ] ) { + case D_EVENT_INTEGER : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( int * )&data[ i ] = int( *source.floatPtr ); + break; + + case D_EVENT_FLOAT : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( float * )&data[ i ] = *source.floatPtr; + break; + + case D_EVENT_VECTOR : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idVec3 ** )&data[ i ] = source.vectorPtr; + break; + + case D_EVENT_STRING : + *( const char ** )&data[ i ] = ( char * )&localstack[ start + pos ]; + break; + + case D_EVENT_ENTITY : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + if ( !*( idEntity ** )&data[ i ] ) { + Warning( "Entity not found for event '%s'. Terminating thread.", evdef->GetName() ); + threadDying = true; + PopParms( argsize ); + return; + } + break; + + case D_EVENT_ENTITY_NULL : + source.intPtr = ( int * )&localstack[ start + pos ]; + *( idEntity ** )&data[ i ] = GetEntity( *source.entityNumberPtr ); + break; + + case D_EVENT_TRACE : + Error( "trace type not supported from script for '%s' event.", evdef->GetName() ); + break; + + default : + Error( "Invalid arg format string for '%s' event.", evdef->GetName() ); + break; + } + + pos += func->parmSize[ j++ ]; + } + + popParms = argsize; + thread->ProcessEventArgPtr( evdef, data ); + if ( popParms ) { + PopParms( popParms ); + } + popParms = 0; +} + +/* +==================== +idInterpreter::Execute +==================== +*/ +bool idInterpreter::Execute( void ) { + varEval_t var_a; + varEval_t var_b; + varEval_t var_c; + varEval_t var; + statement_t *st; + int runaway; + idThread *newThread; + float floatVal; + idScriptObject *obj; + const function_t *func; +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag,MA_SCRIPT); +// RAVEN END + + if ( threadDying || !currentFunction ) { + return true; + } + + if ( multiFrameEvent ) { + // move to previous instruction and call it again + instructionPointer--; + } + + runaway = 5000000; + + doneProcessing = false; + while( !doneProcessing && !threadDying ) { + instructionPointer++; + + if ( !--runaway ) { + Error( "runaway loop error" ); + } + + // next statement + st = &gameLocal.program.GetStatement( instructionPointer ); + +// RAVEN BEGIN +// bdube: if the debugger is running then we need to check to see if any breakpoints have beeng hit + if ( gameLocal.editors & EDITOR_DEBUGGER ) { + common->DebuggerCheckBreakpoint ( this, &gameLocal.program, instructionPointer ); + } else if ( g_debugScript.GetBool ( ) ) { + static int lastLineNumber = -1; + if ( lastLineNumber != gameLocal.program.GetStatement ( instructionPointer ).linenumber ) { + gameLocal.Printf ( "%s (%d)\n", + gameLocal.program.GetFilename ( gameLocal.program.GetStatement ( instructionPointer ).file ), + gameLocal.program.GetStatement ( instructionPointer ).linenumber + ); + lastLineNumber = gameLocal.program.GetStatement ( instructionPointer ).linenumber; + } + } +// RAVEN END + + switch( st->op ) { + case OP_RETURN: + LeaveFunction( st->a ); + break; + + case OP_THREAD: + newThread = new idThread( this, st->a->value.functionPtr, st->b->value.argSize ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + PopParms( st->b->value.argSize ); + break; + + case OP_OBJTHREAD: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + assert( st->c->value.argSize == func->parmTotal ); + newThread = new idThread( this, GetEntity( *var_a.entityNumberPtr ), func, func->parmTotal ); + newThread->Start(); + + // return the thread number to the script + gameLocal.program.ReturnFloat( newThread->GetThreadNum() ); + } else { + // return a null thread to the script + gameLocal.program.ReturnFloat( 0.0f ); + } + PopParms( st->c->value.argSize ); + break; + + case OP_CALL: + EnterFunction( st->a->value.functionPtr, false ); + break; + + case OP_EVENTCALL: + CallEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_OBJECTCALL: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + func = obj->GetTypeDef()->GetFunction( st->b->value.virtualFunction ); + EnterFunction( func, false ); + } else { + // return a 'safe' value + gameLocal.program.ReturnVector( vec3_zero ); + gameLocal.program.ReturnString( "" ); + PopParms( st->c->value.argSize ); + } + break; + + case OP_SYSCALL: + CallSysEvent( st->a->value.functionPtr, st->b->value.argSize ); + break; + + case OP_IFNOT: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr == 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_IF: + var_a = GetVariable( st->a ); + if ( *var_a.intPtr != 0 ) { + NextInstruction( instructionPointer + st->b->value.jumpOffset ); + } + break; + + case OP_GOTO: + NextInstruction( instructionPointer + st->a->value.jumpOffset ); + break; + + case OP_ADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr + *var_b.floatPtr; + break; + + case OP_ADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr + *var_b.vectorPtr; + break; + + case OP_ADD_S: + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_FS: + var_a = GetVariable( st->a ); + SetString( st->c, FloatToString( *var_a.floatPtr ) ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SF: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, FloatToString( *var_b.floatPtr ) ); + break; + + case OP_ADD_VS: + var_a = GetVariable( st->a ); + SetString( st->c, var_a.vectorPtr->ToString() ); + AppendString( st->c, GetString( st->b ) ); + break; + + case OP_ADD_SV: + var_b = GetVariable( st->b ); + SetString( st->c, GetString( st->a ) ); + AppendString( st->c, var_b.vectorPtr->ToString() ); + break; + + case OP_SUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr - *var_b.floatPtr; + break; + + case OP_SUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr - *var_b.vectorPtr; + break; + + case OP_MUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.floatPtr * *var_b.floatPtr; + break; + + case OP_MUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = *var_a.vectorPtr * *var_b.vectorPtr; + break; + + case OP_MUL_FV: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.floatPtr * *var_b.vectorPtr; + break; + + case OP_MUL_VF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = *var_a.vectorPtr * *var_b.floatPtr; + break; + + case OP_DIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = idMath::INFINITY; + } else { + *var_c.floatPtr = *var_a.floatPtr / *var_b.floatPtr; + } + break; + + case OP_MOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable ( st->c ); + + if ( *var_b.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_c.floatPtr = *var_a.floatPtr; + } else { + *var_c.floatPtr = static_cast( *var_a.floatPtr ) % static_cast( *var_b.floatPtr ); + } + break; + + case OP_BITAND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) & static_cast( *var_b.floatPtr ); + break; + + case OP_BITOR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ) | static_cast( *var_b.floatPtr ); + break; + + case OP_GE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr >= *var_b.floatPtr ); + break; + + case OP_LE: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr <= *var_b.floatPtr ); + break; + + case OP_GT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr > *var_b.floatPtr ); + break; + + case OP_LT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr < *var_b.floatPtr ); + break; + + case OP_AND: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.floatPtr != 0.0f ); + break; + + case OP_AND_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) && ( *var_b.intPtr != 0 ); + break; + + case OP_AND_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) && ( *var_b.intPtr != 0 ); + break; + + case OP_OR: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_BOOLF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.floatPtr != 0.0f ); + break; + + case OP_OR_FBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != 0.0f ) || ( *var_b.intPtr != 0 ); + break; + + case OP_OR_BOOLBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr != 0 ) || ( *var_b.intPtr != 0 ); + break; + + case OP_NOT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.intPtr == 0 ); + break; + + case OP_NOT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == 0.0f ); + break; + + case OP_NOT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == vec3_zero ); + break; + + case OP_NOT_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( strlen( GetString( st->a ) ) == 0 ); + break; + + case OP_NOT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( GetEntity( *var_a.entityNumberPtr ) == NULL ); + break; + + case OP_NEG_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = -*var_a.floatPtr; + break; + + case OP_NEG_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.vectorPtr = -*var_a.vectorPtr; + break; + + case OP_INT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = static_cast( *var_a.floatPtr ); + break; + + case OP_EQ_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr == *var_b.floatPtr ); + break; + + case OP_EQ_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr == *var_b.vectorPtr ); + break; + + case OP_EQ_S: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) == 0 ); + break; + + case OP_EQ_E: + case OP_EQ_EO: + case OP_EQ_OE: + case OP_EQ_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr == *var_b.entityNumberPtr ); + break; + + case OP_NE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.floatPtr != *var_b.floatPtr ); + break; + + case OP_NE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.vectorPtr != *var_b.vectorPtr ); + break; + + case OP_NE_S: + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( idStr::Cmp( GetString( st->a ), GetString( st->b ) ) != 0 ); + break; + + case OP_NE_E: + case OP_NE_EO: + case OP_NE_OE: + case OP_NE_OO: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ( *var_a.entityNumberPtr != *var_b.entityNumberPtr ); + break; + + case OP_UADD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr += *var_a.floatPtr; + break; + + case OP_UADD_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr += *var_a.vectorPtr; + break; + + case OP_USUB_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr -= *var_a.floatPtr; + break; + + case OP_USUB_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr -= *var_a.vectorPtr; + break; + + case OP_UMUL_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr *= *var_a.floatPtr; + break; + + case OP_UMUL_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr *= *var_a.floatPtr; + break; + + case OP_UDIV_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = idMath::INFINITY; + } else { + *var_b.floatPtr = *var_b.floatPtr / *var_a.floatPtr; + } + break; + + case OP_UDIV_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + var_b.vectorPtr->Set( idMath::INFINITY, idMath::INFINITY, idMath::INFINITY ); + } else { + *var_b.vectorPtr = *var_b.vectorPtr / *var_a.floatPtr; + } + break; + + case OP_UMOD_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + + if ( *var_a.floatPtr == 0.0f ) { + Warning( "Divide by zero" ); + *var_b.floatPtr = *var_a.floatPtr; + } else { + *var_b.floatPtr = static_cast( *var_b.floatPtr ) % static_cast( *var_a.floatPtr ); + } + break; + + case OP_UOR_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) | static_cast( *var_a.floatPtr ); + break; + + case OP_UAND_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_b.floatPtr ) & static_cast( *var_a.floatPtr ); + break; + + case OP_UINC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )++; + break; + + case OP_UINCP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )++; + } + break; + + case OP_UDEC_F: + var_a = GetVariable( st->a ); + ( *var_a.floatPtr )--; + break; + + case OP_UDECP_F: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + ( *var.floatPtr )--; + } + break; + + case OP_COMP_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + *var_c.floatPtr = ~static_cast( *var_a.floatPtr ); + break; + + case OP_STORE_F: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = *var_a.floatPtr; + break; + + case OP_STORE_ENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_BOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.intPtr = *var_a.intPtr; + break; + + case OP_STORE_OBJENT: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.entityNumberPtr = 0; + } else if ( !obj->GetTypeDef()->Inherits( st->b->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->b->TypeDef()->Name() ); + *var_b.entityNumberPtr = 0; + } else { + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STORE_OBJ: + case OP_STORE_ENTOBJ: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.entityNumberPtr = *var_a.entityNumberPtr; + break; + + case OP_STORE_S: + SetString( st->b, GetString( st->a ) ); + break; + + case OP_STORE_V: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.vectorPtr = *var_a.vectorPtr; + break; + + case OP_STORE_FTOS: + var_a = GetVariable( st->a ); + SetString( st->b, FloatToString( *var_a.floatPtr ) ); + break; + + case OP_STORE_BTOS: + var_a = GetVariable( st->a ); + SetString( st->b, *var_a.intPtr ? "true" : "false" ); + break; + + case OP_STORE_VTOS: + var_a = GetVariable( st->a ); + SetString( st->b, var_a.vectorPtr->ToString() ); + break; + + case OP_STORE_FTOBOOL: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.intPtr = 1; + } else { + *var_b.intPtr = 0; + } + break; + + case OP_STORE_BOOLTOF: + var_a = GetVariable( st->a ); + var_b = GetVariable( st->b ); + *var_b.floatPtr = static_cast( *var_a.intPtr ); + break; + + case OP_STOREP_F: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = *var_a.floatPtr; + } + break; + + case OP_STOREP_ENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_FLD: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_BOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->intPtr = *var_a.intPtr; + } + break; + + case OP_STOREP_S: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + idStr::Copynz( var_b.evalPtr->stringPtr, GetString( st->a ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_V: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->vectorPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->vectorPtr = *var_a.vectorPtr; + } + break; + + case OP_STOREP_FTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, FloatToString( *var_a.floatPtr ), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_BTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + idStr::Copynz( var_b.evalPtr->stringPtr, "true", MAX_STRING_LEN ); + } else { + idStr::Copynz( var_b.evalPtr->stringPtr, "false", MAX_STRING_LEN ); + } + } + break; + + case OP_STOREP_VTOS: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->stringPtr ) { + var_a = GetVariable( st->a ); + idStr::Copynz( var_b.evalPtr->stringPtr, var_a.vectorPtr->ToString(), MAX_STRING_LEN ); + } + break; + + case OP_STOREP_FTOBOOL: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->intPtr ) { + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + *var_b.evalPtr->intPtr = 1; + } else { + *var_b.evalPtr->intPtr = 0; + } + } + break; + + case OP_STOREP_BOOLTOF: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->floatPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->floatPtr = static_cast( *var_a.intPtr ); + } + break; + + case OP_STOREP_OBJ: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + break; + + case OP_STOREP_OBJENT: + var_b = GetVariable( st->b ); + if ( var_b.evalPtr && var_b.evalPtr->entityNumberPtr ) { + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_b.evalPtr->entityNumberPtr = 0; + + // st->b points to type_pointer, which is just a temporary that gets its type reassigned, so we store the real type in st->c + // so that we can do a type check during run time since we don't know what type the script object is at compile time because it + // comes from an entity + } else if ( !obj->GetTypeDef()->Inherits( st->c->TypeDef() ) ) { + //Warning( "object '%s' cannot be converted to '%s'", obj->GetTypeName(), st->c->TypeDef()->Name() ); + *var_b.evalPtr->entityNumberPtr = 0; + } else { + *var_b.evalPtr->entityNumberPtr = *var_a.entityNumberPtr; + } + } + break; + + case OP_ADDRESS: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var_c.evalPtr->bytePtr = &obj->data[ st->b->value.ptrOffset ]; + } else { + var_c.evalPtr->bytePtr = NULL; + } + break; + + case OP_INDIRECT_F: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.floatPtr = *var.floatPtr; + } else { + *var_c.floatPtr = 0.0f; + } + break; + + case OP_INDIRECT_ENT: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } else { + *var_c.entityNumberPtr = 0; + } + break; + + case OP_INDIRECT_BOOL: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.intPtr = *var.intPtr; + } else { + *var_c.intPtr = 0; + } + break; + + case OP_INDIRECT_S: + var_a = GetVariable( st->a ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + SetString( st->c, var.stringPtr ); + } else { + SetString( st->c, "" ); + } + break; + + case OP_INDIRECT_V: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( obj ) { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.vectorPtr = *var.vectorPtr; + } else { + var_c.vectorPtr->Zero(); + } + break; + + case OP_INDIRECT_OBJ: + var_a = GetVariable( st->a ); + var_c = GetVariable( st->c ); + obj = GetScriptObject( *var_a.entityNumberPtr ); + if ( !obj ) { + *var_c.entityNumberPtr = 0; + } else { + var.bytePtr = &obj->data[ st->b->value.ptrOffset ]; + *var_c.entityNumberPtr = *var.entityNumberPtr; + } + break; + + case OP_PUSH_F: + var_a = GetVariable( st->a ); + Push( *var_a.intPtr ); + break; + + case OP_PUSH_FTOS: + var_a = GetVariable( st->a ); + PushString( FloatToString( *var_a.floatPtr ) ); + break; + + case OP_PUSH_BTOF: + var_a = GetVariable( st->a ); + floatVal = *var_a.intPtr; + Push( *reinterpret_cast( &floatVal ) ); + break; + + case OP_PUSH_FTOB: + var_a = GetVariable( st->a ); + if ( *var_a.floatPtr != 0.0f ) { + Push( 1 ); + } else { + Push( 0 ); + } + break; + + case OP_PUSH_VTOS: + var_a = GetVariable( st->a ); + PushString( var_a.vectorPtr->ToString() ); + break; + + case OP_PUSH_BTOS: + var_a = GetVariable( st->a ); + PushString( *var_a.intPtr ? "true" : "false" ); + break; + + case OP_PUSH_ENT: + var_a = GetVariable( st->a ); +// RAVEN BEGIN +// jshepard: keep tabs on this guy, he's the last referenced script variable. + assert(st->a); + LastScriptVariable = st->a; + //This line leads to memory corruption, I need to come up with a better way of keeping track. + //LastScriptVariable->initialized = idVarDef::stackVariable; +// RAVEN END + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_S: + PushString( GetString( st->a ) ); + break; + + case OP_PUSH_V: + var_a = GetVariable( st->a ); + Push( *reinterpret_cast( &var_a.vectorPtr->x ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->y ) ); + Push( *reinterpret_cast( &var_a.vectorPtr->z ) ); + break; + + case OP_PUSH_OBJ: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_PUSH_OBJENT: + var_a = GetVariable( st->a ); + Push( *var_a.entityNumberPtr ); + break; + + case OP_BREAK: + case OP_CONTINUE: + default: + Error( "Bad opcode %i", st->op ); + break; + } + } + + return threadDying; +} diff --git a/src/mpgame/script/Script_Interpreter.h b/src/mpgame/script/Script_Interpreter.h new file mode 100644 index 0000000..08b063c --- /dev/null +++ b/src/mpgame/script/Script_Interpreter.h @@ -0,0 +1,277 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_INTERPRETER_H__ +#define __SCRIPT_INTERPRETER_H__ + +#define MAX_STACK_DEPTH 64 +#define LOCALSTACK_SIZE 6144 + +typedef struct prstack_s { + int s; + const function_t *f; + int stackbase; +} prstack_t; + +class idInterpreter { +private: + prstack_t callStack[ MAX_STACK_DEPTH ]; + int callStackDepth; + int maxStackDepth; + + byte localstack[ LOCALSTACK_SIZE ]; + int localstackUsed; + int localstackBase; + int maxLocalstackUsed; + + const function_t *currentFunction; + int instructionPointer; + + int popParms; + const idEventDef *multiFrameEvent; + idEntity *eventEntity; + + idThread *thread; + + void PopParms( int numParms ); +// RAVEN BEGIN +// abahr: making Push public to allow parms to be put on stack +public: + void PushString( const char *string ); + void Push( int value ); +private: +// RAVEN END + const char *FloatToString( float value ); + void AppendString( idVarDef *def, const char *from ); + void SetString( idVarDef *def, const char *from ); + const char *GetString( idVarDef *def ); + varEval_t GetVariable( idVarDef *def ); + idEntity *GetEntity( int entnum ) const; + idScriptObject *GetScriptObject( int entnum ) const; + void NextInstruction( int position ); + + void LeaveFunction( idVarDef *returnDef ); + void CallEvent( const function_t *func, int argsize ); + void CallSysEvent( const function_t *func, int argsize ); +// RAVEN BEGIN +// jshepard: last variable referenced in the script-- keep tabs on it so we can print it for warnings. + idVarDef *LastScriptVariable; +// RAVEN END + + +public: + bool doneProcessing; + bool threadDying; + bool terminateOnExit; + bool debug; + + idInterpreter(); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void SetThread( idThread *pThread ); + + void StackTrace( void ) const; + + int CurrentLine( void ) const; + const char *CurrentFile( void ) const; + + void Error( char *fmt, ... ) const; + void Warning( char *fmt, ... ) const; + void DisplayInfo( void ) const; + + bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + bool MultiFrameEventInProgress( void ) const; + + void ThreadCall( idInterpreter *source, const function_t *func, int args ); + void EnterFunction( const function_t *func, bool clearStack ); + void EnterObjectFunction( idEntity *self, const function_t *func, bool clearStack ); + + bool Execute( void ); + void Reset( void ); + + bool GetRegisterValue( const char *name, idStr &out, int scopeDepth ); + int GetCallstackDepth( void ) const; + const prstack_t *GetCallstack( void ) const; + const function_t *GetCurrentFunction( void ) const; + idThread *GetThread( void ) const; + +}; + +/* +==================== +idInterpreter::PopParms +==================== +*/ +ID_INLINE void idInterpreter::PopParms( int numParms ) { + // pop our parms off the stack + if ( localstackUsed < numParms ) { + Error( "locals stack underflow\n" ); + } + + localstackUsed -= numParms; +} + +/* +==================== +idInterpreter::Push +==================== +*/ +ID_INLINE void idInterpreter::Push( int value ) { + if ( localstackUsed + sizeof( int ) > LOCALSTACK_SIZE ) { + Error( "Push: locals stack overflow\n" ); + } + *( int * )&localstack[ localstackUsed ] = value; + localstackUsed += sizeof( int ); +} + +/* +==================== +idInterpreter::PushString +==================== +*/ +ID_INLINE void idInterpreter::PushString( const char *string ) { + if ( localstackUsed + MAX_STRING_LEN > LOCALSTACK_SIZE ) { + Error( "PushString: locals stack overflow\n" ); + } + idStr::Copynz( ( char * )&localstack[ localstackUsed ], string, MAX_STRING_LEN ); + localstackUsed += MAX_STRING_LEN; +} + +/* +==================== +idInterpreter::FloatToString +==================== +*/ +ID_INLINE const char *idInterpreter::FloatToString( float value ) { + static char text[ 32 ]; + + if ( value == ( float )( int )value ) { + sprintf( text, "%d", ( int )value ); + } else { + sprintf( text, "%f", value ); + } + return text; +} + +/* +==================== +idInterpreter::AppendString +==================== +*/ +ID_INLINE void idInterpreter::AppendString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Append( ( char * )&localstack[ localstackBase + def->value.stackOffset ], MAX_STRING_LEN, from ); + } else { + idStr::Append( def->value.stringPtr, MAX_STRING_LEN, from ); + } +} + +/* +==================== +idInterpreter::SetString +==================== +*/ +ID_INLINE void idInterpreter::SetString( idVarDef *def, const char *from ) { + if ( def->initialized == idVarDef::stackVariable ) { + idStr::Copynz( ( char * )&localstack[ localstackBase + def->value.stackOffset ], from, MAX_STRING_LEN ); + } else { + idStr::Copynz( def->value.stringPtr, from, MAX_STRING_LEN ); + } +} + +/* +==================== +idInterpreter::GetString +==================== +*/ +ID_INLINE const char *idInterpreter::GetString( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + return ( char * )&localstack[ localstackBase + def->value.stackOffset ]; + } else { + return def->value.stringPtr; + } +} + +/* +==================== +idInterpreter::GetVariable +==================== +*/ +ID_INLINE varEval_t idInterpreter::GetVariable( idVarDef *def ) { + if ( def->initialized == idVarDef::stackVariable ) { + varEval_t val; + val.intPtr = ( int * )&localstack[ localstackBase + def->value.stackOffset ]; + return val; + } else { + return def->value; + } +} + +/* +================ +idInterpreter::GetEntity +================ +*/ +ID_INLINE idEntity *idInterpreter::GetEntity( int entnum ) const{ + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + return gameLocal.entities[ entnum - 1 ]; + } + return NULL; +} + +/* +================ +idInterpreter::GetScriptObject +================ +*/ +ID_INLINE idScriptObject *idInterpreter::GetScriptObject( int entnum ) const { + idEntity *ent; + + assert( entnum <= MAX_GENTITIES ); + if ( ( entnum > 0 ) && ( entnum <= MAX_GENTITIES ) ) { + ent = gameLocal.entities[ entnum - 1 ]; + if ( ent && ent->scriptObject.data ) { + return &ent->scriptObject; + } + } + return NULL; +} + +/* +==================== +idInterpreter::NextInstruction +==================== +*/ +ID_INLINE void idInterpreter::NextInstruction( int position ) { + // Before we execute an instruction, we increment instructionPointer, + // therefore we need to compensate for that here. + instructionPointer = position - 1; +} + +#endif /* !__SCRIPT_INTERPRETER_H__ */ diff --git a/src/mpgame/script/Script_Program.cpp b/src/mpgame/script/Script_Program.cpp new file mode 100644 index 0000000..c722229 --- /dev/null +++ b/src/mpgame/script/Script_Program.cpp @@ -0,0 +1,2559 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + + +// simple types. function types are dynamically allocated +idTypeDef type_void( ev_void, &def_void, "void", 0, NULL ); +idTypeDef type_scriptevent( ev_scriptevent, &def_scriptevent, "scriptevent", sizeof( void * ), NULL ); +idTypeDef type_namespace( ev_namespace, &def_namespace, "namespace", sizeof( void * ), NULL ); +// RAVEN BEGIN +// abahr +rvTypeDefString type_string( ev_string, &def_string, "string", MAX_STRING_LEN, NULL ); +rvTypeDefFloat type_float( ev_float, &def_float, "float", sizeof( float ), NULL ); +rvTypeDefVec3 type_vector( ev_vector, &def_vector, "vector", sizeof( idVec3 ), NULL ); +rvTypeDefEntity type_entity( ev_entity, &def_entity, "entity", sizeof( int * ), NULL ); // stored as entity number pointer +// RAVEN END +idTypeDef type_field( ev_field, &def_field, "field", sizeof( void * ), NULL ); +idTypeDef type_function( ev_function, &def_function, "function", sizeof( void * ), &type_void ); +idTypeDef type_virtualfunction( ev_virtualfunction, &def_virtualfunction, "virtual function", sizeof( int ), NULL ); +idTypeDef type_pointer( ev_pointer, &def_pointer, "pointer", sizeof( void * ), NULL ); +idTypeDef type_object( ev_object, &def_object, "object", sizeof( int * ), NULL ); // stored as entity number pointer +idTypeDef type_jumpoffset( ev_jumpoffset, &def_jumpoffset, "", sizeof( int ), NULL ); // only used for jump opcodes +idTypeDef type_argsize( ev_argsize, &def_argsize, "", sizeof( int ), NULL ); // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +rvTypeDefBool type_boolean( ev_boolean, &def_boolean, "boolean", sizeof( int ), NULL ); +// RAVEN END + +idVarDef def_void( &type_void ); +idVarDef def_scriptevent( &type_scriptevent ); +idVarDef def_namespace( &type_namespace ); +idVarDef def_string( &type_string ); +idVarDef def_float( &type_float ); +idVarDef def_vector( &type_vector ); +idVarDef def_entity( &type_entity ); +idVarDef def_field( &type_field ); +idVarDef def_function( &type_function ); +idVarDef def_virtualfunction( &type_virtualfunction ); +idVarDef def_pointer( &type_pointer ); +idVarDef def_object( &type_object ); +idVarDef def_jumpoffset( &type_jumpoffset ); // only used for jump opcodes +idVarDef def_argsize( &type_argsize ); +idVarDef def_boolean( &type_boolean ); + +/*********************************************************************** + + function_t + +***********************************************************************/ + +/* +================ +function_t::function_t +================ +*/ +function_t::function_t() { + Clear(); +} + +/* +================ +function_t::Allocated +================ +*/ +size_t function_t::Allocated( void ) const { + return name.Allocated() + parmSize.Allocated(); +} + +/* +================ +function_t::SetName +================ +*/ +void function_t::SetName( const char *name ) { + this->name = name; +} + +/* +================ +function_t::Name +================ +*/ +const char *function_t::Name( void ) const { + return name; +} + +/* +================ +function_t::Clear +================ +*/ +void function_t::Clear( void ) { + eventdef = NULL; + def = NULL; + type = NULL; + firstStatement = 0; + numStatements = 0; + parmTotal = 0; + locals = 0; + filenum = 0; + name.Clear(); + parmSize.Clear(); +} + +/*********************************************************************** + + idTypeDef + +***********************************************************************/ + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + name = ename; + type = etype; + def = edef; + size = esize; + auxType = aux; + + parmTypes.SetGranularity( 1 ); + parmNames.SetGranularity( 1 ); + functions.SetGranularity( 1 ); +} + +/* +================ +idTypeDef::idTypeDef +================ +*/ +idTypeDef::idTypeDef( const idTypeDef &other ) { + *this = other; +} + +/* +================ +idTypeDef::operator= +================ +*/ +void idTypeDef::operator=( const idTypeDef& other ) { + type = other.type; + def = other.def; + name = other.name; + size = other.size; + auxType = other.auxType; + parmTypes = other.parmTypes; + parmNames = other.parmNames; + functions = other.functions; +} + +/* +================ +idTypeDef::Allocated +================ +*/ +size_t idTypeDef::Allocated( void ) const { + size_t memsize; + int i; + + memsize = name.Allocated() + parmTypes.Allocated() + parmNames.Allocated() + functions.Allocated(); + for( i = 0; i < parmTypes.Num(); i++ ) { + memsize += parmNames[ i ].Allocated(); + } + + return memsize; +} + +/* +================ +idTypeDef::Inherits + +Returns true if basetype is an ancestor of this type. +================ +*/ +bool idTypeDef::Inherits( const idTypeDef *basetype ) const { + idTypeDef *superType; + + if ( type != ev_object ) { + return false; + } + + if ( this == basetype ) { + return true; + } + for( superType = auxType; superType != NULL; superType = superType->auxType ) { + if ( superType == basetype ) { + return true; + } + } + + return false; +} + +/* +================ +idTypeDef::MatchesType + +Returns true if both types' base types and parameters match +================ +*/ +bool idTypeDef::MatchesType( const idTypeDef &matchtype ) const { + int i; + + if ( this == &matchtype ) { + return true; + } + + if ( ( type != matchtype.type ) || ( auxType != matchtype.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchtype.parmTypes.Num() ) { + return false; + } + + for( i = 0; i < matchtype.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchtype.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::MatchesVirtualFunction + +Returns true if both functions' base types and parameters match +================ +*/ +bool idTypeDef::MatchesVirtualFunction( const idTypeDef &matchfunc ) const { + int i; + + if ( this == &matchfunc ) { + return true; + } + + if ( ( type != matchfunc.type ) || ( auxType != matchfunc.auxType ) ) { + return false; + } + + if ( parmTypes.Num() != matchfunc.parmTypes.Num() ) { + return false; + } + + if ( parmTypes.Num() > 0 ) { + if ( !parmTypes[ 0 ]->Inherits( matchfunc.parmTypes[ 0 ] ) ) { + return false; + } + } + + for( i = 1; i < matchfunc.parmTypes.Num(); i++ ) { + if ( parmTypes[ i ] != matchfunc.parmTypes[ i ] ) { + return false; + } + } + + return true; +} + +/* +================ +idTypeDef::AddFunctionParm + +Adds a new parameter for a function type. +================ +*/ +void idTypeDef::AddFunctionParm( idTypeDef *parmtype, const char *name ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::AddFunctionParm : tried to add parameter on non-function type" ); + } + + parmTypes.Append( parmtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; +} + +/* +================ +idTypeDef::AddField + +Adds a new field to an object type. +================ +*/ +void idTypeDef::AddField( idTypeDef *fieldtype, const char *name ) { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::AddField : tried to add field to non-object type" ); + } + + parmTypes.Append( fieldtype ); + idStr &parmName = parmNames.Alloc(); + parmName = name; + + if ( fieldtype->FieldType()->Inherits( &type_object ) ) { + size += type_object.Size(); + } else { + size += fieldtype->FieldType()->Size(); + } +} + +/* +================ +idTypeDef::SetName +================ +*/ +void idTypeDef::SetName( const char *newname ) { + name = newname; +} + +/* +================ +idTypeDef::Name +================ +*/ +const char *idTypeDef::Name( void ) const { + return name; +} + +/* +================ +idTypeDef::Type +================ +*/ +etype_t idTypeDef::Type( void ) const { + return type; +} + +/* +================ +idTypeDef::Size +================ +*/ +int idTypeDef::Size( void ) const { + return size; +} + +/* +================ +idTypeDef::SuperClass + +If type is an object, then returns the object's superclass +================ +*/ +idTypeDef *idTypeDef::SuperClass( void ) const { + if ( type != ev_object ) { + throw idCompileError( "idTypeDef::SuperClass : tried to get superclass of a non-object type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::ReturnType + +If type is a function, then returns the function's return type +================ +*/ +idTypeDef *idTypeDef::ReturnType( void ) const { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::ReturnType: tried to get return type on non-function type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetReturnType + +If type is a function, then sets the function's return type +================ +*/ +void idTypeDef::SetReturnType( idTypeDef *returntype ) { + if ( type != ev_function ) { + throw idCompileError( "idTypeDef::SetReturnType: tried to set return type on non-function type" ); + } + + auxType = returntype; +} + +/* +================ +idTypeDef::FieldType + +If type is a field, then returns it's type +================ +*/ +idTypeDef *idTypeDef::FieldType( void ) const { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::FieldType: tried to get field type on non-field type" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetFieldType + +If type is a field, then sets the function's return type +================ +*/ +void idTypeDef::SetFieldType( idTypeDef *fieldtype ) { + if ( type != ev_field ) { + throw idCompileError( "idTypeDef::SetFieldType: tried to set return type on non-function type" ); + } + + auxType = fieldtype; +} + +/* +================ +idTypeDef::PointerType + +If type is a pointer, then returns the type it points to +================ +*/ +idTypeDef *idTypeDef::PointerType( void ) const { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::PointerType: tried to get pointer type on non-pointer" ); + } + + return auxType; +} + +/* +================ +idTypeDef::SetPointerType + +If type is a pointer, then sets the pointer's type +================ +*/ +void idTypeDef::SetPointerType( idTypeDef *pointertype ) { + if ( type != ev_pointer ) { + throw idCompileError( "idTypeDef::SetPointerType: tried to set type on non-pointer" ); + } + + auxType = pointertype; +} + +/* +================ +idTypeDef::NumParameters +================ +*/ +int idTypeDef::NumParameters( void ) const { + return parmTypes.Num(); +} + +/* +================ +idTypeDef::GetParmType +================ +*/ +idTypeDef *idTypeDef::GetParmType( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmTypes[ parmNumber ]; +} + +/* +================ +idTypeDef::GetParmName +================ +*/ +const char *idTypeDef::GetParmName( int parmNumber ) const { + assert( parmNumber >= 0 ); + assert( parmNumber < parmTypes.Num() ); + return parmNames[ parmNumber ]; +} + +/* +================ +idTypeDef::NumFunctions +================ +*/ +int idTypeDef::NumFunctions( void ) const { + return functions.Num(); +} + +/* +================ +idTypeDef::GetFunctionNumber +================ +*/ +int idTypeDef::GetFunctionNumber( const function_t *func ) const { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( functions[ i ] == func ) { + return i; + } + } + return -1; +} + +/* +================ +idTypeDef::GetFunction +================ +*/ +const function_t *idTypeDef::GetFunction( int funcNumber ) const { + assert( funcNumber >= 0 ); + assert( funcNumber < functions.Num() ); + return functions[ funcNumber ]; +} + +/* +================ +idTypeDef::AddFunction +================ +*/ +void idTypeDef::AddFunction( const function_t *func ) { + int i; + + for( i = 0; i < functions.Num(); i++ ) { + if ( !idStr::Cmp( functions[ i ]->def->Name(), func->def->Name() ) ) { + if ( func->def->TypeDef()->MatchesVirtualFunction( *functions[ i ]->def->TypeDef() ) ) { + functions[ i ] = func; + return; + } + } + } + functions.Append( func ); +} + +// RAVEN BEGIN +// abahr +/* +================ +rvTypeDefInt::Parse +================ +*/ +int rvTypeDefInt::Parse( const char* source ) const { + int i; + + sscanf( source, Format(), &i ); + + return i; +} + +/* +================ +rvTypeDefInt::GetReturnedValAsString +================ +*/ +const char* rvTypeDefInt::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedInteger() ); +} + +/* +================ +rvTypeDefInt::PushOntoStack +================ +*/ +void rvTypeDefInt::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushInt( Parse(source) ); +} + +/* +================ +rvTypeDefInt::IsValid +================ +*/ +bool rvTypeDefInt::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ + rvTypeDefFloat::Parse +================ +*/ +float rvTypeDefFloat::Parse( const char* source ) const { + float f; + + sscanf( source, Format(), &f ); + + return f; +} + +/* +================ +rvTypeDefFloat::GetReturnedValAsString +================ +*/ +const char* rvTypeDefFloat::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedFloat() ); +} + +/* +================ +rvTypeDefFloat::PushOntoStack +================ +*/ +void rvTypeDefFloat::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushFloat( Parse(source) ); +} + +/* +================ +rvTypeDefFloat::IsValid +================ +*/ +bool rvTypeDefFloat::IsValid( const char* source ) const { + return idStr::IsNumeric( source ); +} + +/* +================ +rvTypeDefVec3::Parse +================ +*/ +idVec3 rvTypeDefVec3::Parse( const char* source ) const { + idVec3 v; + + sscanf( source, Format(), &v[0], &v[1], &v[2] ); + + return v; +} + +/* +================ +rvTypeDefVec3::GetReturnedValAsString +================ +*/ +const char* rvTypeDefVec3::GetReturnedValAsString( idProgram& program ) { + idVec3 v( program.GetReturnedVec3() ); + return va( Format(), v[0], v[1], v[2] ); +} + +/* +================ +rvTypeDefVec3::PushOntoStack +================ +*/ +void rvTypeDefVec3::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushVec3( Parse(source) ); +} + +/* +================ +rvTypeDefVec3::IsValid +================ +*/ +bool rvTypeDefVec3::IsValid( const char* source ) const { + //Looking for two ' ' + return idStr::FindChar(source, ' ', idStr::FindChar(source, ' ') ) != -1; +} + +/* +================ +rvTypeDefEntity::Parse +================ +*/ +idEntity* rvTypeDefEntity::Parse( const char* source ) const { + return gameLocal.FindEntity( source ); +} + +/* +================ +rvTypeDefEntity::GetReturnedValAsString +================ +*/ + +const char* rvTypeDefEntity::GetReturnedValAsString( idProgram& program ) { + idEntity* entity = program.GetReturnedEntity(); + return (entity) ? entity->GetName() : ""; +} + +/* +================ +rvTypeDefEntity::PushOntoStack +================ +*/ +void rvTypeDefEntity::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushEntity( Parse(source) ); +} + +/* +================ +rvTypeDefEntity::IsValid +================ +*/ +bool rvTypeDefEntity::IsValid( const char* source ) const { + return Parse( source ) != NULL; +} + +/* +================ +rvTypeDefString::Parse +================ +*/ +const char* rvTypeDefString::Parse( const char* source ) const { + return source; +} + +/* +================ +rvTypeDefString::GetReturnedValAsString +================ +*/ +const char* rvTypeDefString::GetReturnedValAsString( idProgram& program ) { + return program.GetReturnedString(); +} + +/* +================ +rvTypeDefString::PushOntoStack +================ +*/ +void rvTypeDefString::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushString( Parse(source) ); +} + +/* +================ +vTypeDefString::IsValid +================ +*/ +bool rvTypeDefString::IsValid( const char* source ) const { + return true; +} + +/* +================ +rvTypeDefBool::Parse +================ +*/ +bool rvTypeDefBool::Parse( const char* source ) const { + unsigned int b; + + sscanf( source, Format(), &b ); + + return !!b; +} + +/* +================ +rvTypeDefBool::GetReturnedValAsString +================ +*/ +const char* rvTypeDefBool::GetReturnedValAsString( idProgram& program ) { + return va( Format(), program.GetReturnedBool() ); +} + +/* +================ +rvTypeDefBool::PushOntoStack +================ +*/ +void rvTypeDefBool::PushOntoStack( idThread* thread, const char* source ) { + if( !thread ) { + return; + } + + thread->PushBool( Parse(source) ); +} + +/* +================ +rvTypeDefBool::IsValid +================ +*/ +bool rvTypeDefBool::IsValid( const char* source ) const { + return !idStr::Icmp(source, "true") || !idStr::Icmp(source, "false") || !idStr::Icmp(source, "1") || !idStr::Icmp(source, "0"); +} + +/* +================ +idProgram::GetReturnedEntity +================ +*/ +idEntity* idProgram::GetReturnedEntity() { + //This is here because gameLocal isn't known about yet in the header + int entityNumber = *returnDef->value.entityNumberPtr; + if( !entityNumber ) { + return NULL; + } + + return gameLocal.entities[ entityNumber - 1 ]; +} +// RAVEN END + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +================ +idVarDef::idVarDef() +================ +*/ +idVarDef::idVarDef( idTypeDef *typeptr ) { + typeDef = typeptr; + num = 0; + scope = NULL; + numUsers = 0; + initialized = idVarDef::uninitialized; + memset( &value, 0, sizeof( value ) ); + name = NULL; + next = NULL; +} + +/* +============ +idVarDef::~idVarDef +============ +*/ +idVarDef::~idVarDef() { + if ( name ) { + name->RemoveDef( this ); + } +} + +/* +============ +idVarDef::Name +============ +*/ +const char *idVarDef::Name( void ) const { + return name->Name(); +} + +/* +============ +idVarDef::GlobalName +============ +*/ +const char *idVarDef::GlobalName( void ) const { + if ( scope != &def_namespace ) { + return va( "%s::%s", scope->GlobalName(), name->Name() ); + } else { + return name->Name(); + } +} + +/* +============ +idVarDef::DepthOfScope +============ +*/ +int idVarDef::DepthOfScope( const idVarDef *otherScope ) const { + const idVarDef *def; + int depth; + + depth = 1; + for( def = otherScope; def != NULL; def = def->scope ) { + if ( def == scope ) { + return depth; + } + depth++; + } + + return 0; +} + +/* +============ +idVarDef::SetFunction +============ +*/ +void idVarDef::SetFunction( function_t *func ) { + assert( typeDef ); + initialized = initializedConstant; + assert( typeDef->Type() == ev_function ); + value.functionPtr = func; +} + +/* +============ +idVarDef::SetObject +============ +*/ +void idVarDef::SetObject( idScriptObject *object ) { + assert( typeDef ); + initialized = initialized; + assert( typeDef->Inherits( &type_object ) ); + *value.objectPtrPtr = object; +} + +/* +============ +idVarDef::SetValue +============ +*/ +void idVarDef::SetValue( const eval_t &_value, bool constant ) { + assert( typeDef ); + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + switch( typeDef->Type() ) { + case ev_pointer : + case ev_boolean : + case ev_field : + *value.intPtr = _value._int; + break; + + case ev_jumpoffset : + value.jumpOffset = _value._int; + break; + + case ev_argsize : + value.argSize = _value._int; + break; + + case ev_entity : + *value.entityNumberPtr = _value.entity; + break; + + case ev_string : + idStr::Copynz( value.stringPtr, _value.stringPtr, MAX_STRING_LEN ); + break; + + case ev_float : + *value.floatPtr = _value._float; + break; + + case ev_vector : + value.vectorPtr->x = _value.vector[ 0 ]; + value.vectorPtr->y = _value.vector[ 1 ]; + value.vectorPtr->z = _value.vector[ 2 ]; + break; + + case ev_function : + value.functionPtr = _value.function; + break; + + case ev_virtualfunction : + value.virtualFunction = _value._int; + break; + + case ev_object : + *value.entityNumberPtr = _value.entity; + break; + + default : + throw idCompileError( va( "weird type on '%s'", Name() ) ); + break; + } +} + +/* +============ +idVarDef::SetString +============ +*/ +void idVarDef::SetString( const char *string, bool constant ) { + if ( constant ) { + initialized = initializedConstant; + } else { + initialized = initializedVariable; + } + + assert( typeDef && ( typeDef->Type() == ev_string ) ); + idStr::Copynz( value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +============ +idVarDef::PrintInfo +============ +*/ +void idVarDef::PrintInfo( idFile *file, int instructionPointer ) const { + statement_t *jumpst; + int jumpto; + etype_t etype; + int i; + int len; + const char *ch; + + if ( initialized == initializedConstant ) { + file->Printf( "const " ); + } + + etype = typeDef->Type(); + switch( etype ) { + case ev_jumpoffset : + jumpto = instructionPointer + value.jumpOffset; + jumpst = &gameLocal.program.GetStatement( jumpto ); + file->Printf( "address %d [%s(%d)]", jumpto, gameLocal.program.GetFilename( jumpst->file ), jumpst->linenumber ); + break; + + case ev_function : + if ( value.functionPtr->eventdef ) { + file->Printf( "event %s", GlobalName() ); + } else { + file->Printf( "function %s", GlobalName() ); + } + break; + + case ev_field : + file->Printf( "field %d", value.ptrOffset ); + break; + + case ev_argsize: + file->Printf( "args %d", value.argSize ); + break; + + default: + file->Printf( "%s ", typeDef->Name() ); + if ( initialized == initializedConstant ) { + switch( etype ) { + case ev_string : + file->Printf( "\"" ); + len = strlen( value.stringPtr ); + ch = value.stringPtr; + for( i = 0; i < len; i++, ch++ ) { + if ( idStr::CharIsPrintable( *ch ) ) { + file->Printf( "%c", *ch ); + } else if ( *ch == '\n' ) { + file->Printf( "\\n" ); + } else { + file->Printf( "\\x%.2x", static_cast( *ch ) ); + } + } + file->Printf( "\"" ); + break; + + case ev_vector : + file->Printf( "'%s'", value.vectorPtr->ToString() ); + break; + + case ev_float : + file->Printf( "%f", *value.floatPtr ); + break; + + case ev_virtualfunction : + file->Printf( "vtable[ %d ]", value.virtualFunction ); + break; + + default : + file->Printf( "%d", *value.intPtr ); + break; + } + } else if ( initialized == stackVariable ) { + file->Printf( "stack[%d]", value.stackOffset ); + } else { + file->Printf( "global[%d]", num ); + } + break; + } +} + +/*********************************************************************** + + idVarDef + +***********************************************************************/ + +/* +============ +idVarDefName::AddDef +============ +*/ +void idVarDefName::AddDef( idVarDef *def ) { + assert( def->next == NULL ); + def->name = this; + def->next = defs; + defs = def; +} + +/* +============ +idVarDefName::RemoveDef +============ +*/ +void idVarDefName::RemoveDef( idVarDef *def ) { + if ( defs == def ) { + defs = def->next; + } else { + for ( idVarDef *d = defs; d->next != NULL; d = d->next ) { + if ( d->next == def ) { + d->next = def->next; + break; + } + } + } + def->next = NULL; + def->name = NULL; +} + +/*********************************************************************** + + idScriptObject + +***********************************************************************/ + +/* +============ +idScriptObject::idScriptObject +============ +*/ +idScriptObject::idScriptObject() { + data = NULL; + type = &type_object; +} + +/* +============ +idScriptObject::~idScriptObject +============ +*/ +idScriptObject::~idScriptObject() { + Free(); +} + +/* +============ +idScriptObject::Free +============ +*/ +void idScriptObject::Free( void ) { + if ( data ) { + Mem_Free( data ); + } + + data = NULL; + type = &type_object; +} + +/* +================ +idScriptObject::Save +================ +*/ +void idScriptObject::Save( idSaveGame *savefile ) const { + size_t size; + + if ( type == &type_object && data == NULL ) { + // Write empty string for uninitialized object + savefile->WriteString( "" ); + } else { + savefile->WriteString( type->Name() ); + size = type->Size(); + savefile->WriteInt( size ); + savefile->Write( data, size ); + } +} + +/* +================ +idScriptObject::Restore +================ +*/ +void idScriptObject::Restore( idRestoreGame *savefile ) { + idStr typeName; + size_t size; + + savefile->ReadString( typeName ); + + // Empty string signals uninitialized object + if ( typeName.Length() == 0 ) { + return; + } + + if ( !SetType( typeName ) ) { + savefile->Error( "idScriptObject::Restore: failed to restore object of type '%s'.", typeName.c_str() ); + } + + savefile->ReadInt( (int &)size ); + if ( size != type->Size() ) { + savefile->Error( "idScriptObject::Restore: size of object '%s' doesn't match size in save game.", typeName.c_str() ); + } + + savefile->Read( data, size ); +} + +/* +============ +idScriptObject::SetType + +Allocates an object and initializes memory. +============ +*/ +bool idScriptObject::SetType( const char *typeName ) { + size_t size; + idTypeDef *newtype; + + // lookup the type + newtype = gameLocal.program.FindType( typeName ); + + // only allocate memory if the object type changes + if ( newtype != type ) { + Free(); + if ( !newtype ) { + gameLocal.Warning( "idScriptObject::SetType: Unknown type '%s'", typeName ); + return false; + } + + if ( !newtype->Inherits( &type_object ) ) { + gameLocal.Warning( "idScriptObject::SetType: Can't create object of type '%s'. Must be an object type.", newtype->Name() ); + return false; + } + + // set the type + type = newtype; + + // allocate the memory + size = type->Size(); +//RAVEN BEGIN +//amccarthy: Added memory allocation tag + data = ( byte * )Mem_Alloc( size, MA_SCRIPT ); +//RAVEN END + } + + // init object memory + ClearObject(); + + return true; +} + +/* +============ +idScriptObject::ClearObject + +Resets the memory for the script object without changing its type. +============ +*/ +void idScriptObject::ClearObject( void ) { + size_t size; + + if ( type != &type_object ) { + // init object memory + size = type->Size(); + memset( data, 0, size ); + } +} + +/* +============ +idScriptObject::HasObject +============ +*/ +bool idScriptObject::HasObject( void ) const { + return ( type != &type_object ); +} + +/* +============ +idScriptObject::GetTypeDef +============ +*/ +idTypeDef *idScriptObject::GetTypeDef( void ) const { + return type; +} + +/* +============ +idScriptObject::GetTypeName +============ +*/ +const char *idScriptObject::GetTypeName( void ) const { + return type->Name(); +} + +/* +============ +idScriptObject::GetConstructor +============ +*/ +const function_t *idScriptObject::GetConstructor( void ) const { + const function_t *func; + + func = GetFunction( "init" ); + return func; +} + +/* +============ +idScriptObject::GetDestructor +============ +*/ +const function_t *idScriptObject::GetDestructor( void ) const { + const function_t *func; + + func = GetFunction( "destroy" ); + return func; +} + +/* +============ +idScriptObject::GetFunction +============ +*/ +const function_t *idScriptObject::GetFunction( const char *name ) const { + const function_t *func; + + if ( type == &type_object ) { + return NULL; + } + + func = gameLocal.program.FindFunction( name, type ); + return func; +} + +/* +============ +idScriptObject::GetVariable +============ +*/ +byte *idScriptObject::GetVariable( const char *name, etype_t etype ) const { + int i; + int pos; + const idTypeDef *t; + const idTypeDef *parm; + + if ( type == &type_object ) { + return NULL; + } + + t = type; + do { + if ( t->SuperClass() != &type_object ) { + pos = t->SuperClass()->Size(); + } else { + pos = 0; + } + for( i = 0; i < t->NumParameters(); i++ ) { + parm = t->GetParmType( i ); + if ( !idStr::Cmp( t->GetParmName( i ), name ) ) { + if ( etype != parm->FieldType()->Type() ) { + return NULL; + } + return &data[ pos ]; + } + + if ( parm->FieldType()->Inherits( &type_object ) ) { + pos += type_object.Size(); + } else { + pos += parm->FieldType()->Size(); + } + } + t = t->SuperClass(); + } while( t && ( t != &type_object ) ); + + return NULL; +} + +/*********************************************************************** + + idProgram + +***********************************************************************/ + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( idTypeDef &type ) { + idTypeDef *newtype; + + newtype = new idTypeDef( type ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::AllocType +============ +*/ +idTypeDef *idProgram::AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) { + idTypeDef *newtype; + + newtype = new idTypeDef( etype, edef, ename, esize, aux ); + types.Append( newtype ); + + return newtype; +} + +/* +============ +idProgram::GetType + +Returns a preexisting complex type that matches the parm, or allocates +a new one and copies it out. +============ +*/ +idTypeDef *idProgram::GetType( idTypeDef &type, bool allocate ) { + int i; + + //FIXME: linear search == slow + for( i = types.Num() - 1; i >= 0; i-- ) { + if ( types[ i ]->MatchesType( type ) && !idStr::Cmp( types[ i ]->Name(), type.Name() ) ) { + return types[ i ]; + } + } + + if ( !allocate ) { + return NULL; + } + + // allocate a new one + return AllocType( type ); +} + +/* +============ +idProgram::FindType + +Returns a preexisting complex type that matches the name, or returns NULL if not found +============ +*/ +idTypeDef *idProgram::FindType( const char *name ) { + idTypeDef *check; + int i; + + for( i = types.Num() - 1; i >= 0; i-- ) { + check = types[ i ]; + if ( !idStr::Cmp( check->Name(), name ) ) { + return check; + } + } + + return NULL; +} + +/* +============ +idProgram::GetDefList +============ +*/ +idVarDef *idProgram::GetDefList( const char *name ) const { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + return varDefNames[i]->GetDefs(); + } + } + return NULL; +} + +/* +============ +idProgram::AddDefToNameList +============ +*/ +void idProgram::AddDefToNameList( idVarDef *def, const char *name ) { + int i, hash; + + hash = varDefNameHash.GenerateKey( name, true ); + for ( i = varDefNameHash.First( hash ); i != -1; i = varDefNameHash.Next( i ) ) { + if ( idStr::Cmp( varDefNames[i]->Name(), name ) == 0 ) { + break; + } + } + if ( i == -1 ) { + i = varDefNames.Append( new idVarDefName( name ) ); + varDefNameHash.Add( hash, i ); + } + varDefNames[i]->AddDef( def ); +} + +/* +============ +idProgram::AllocDef +============ +*/ +idVarDef *idProgram::AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ) { + idVarDef *def; + idStr element; + idVarDef *def_x; + idVarDef *def_y; + idVarDef *def_z; + + // allocate a new def + def = new idVarDef( type ); + def->scope = scope; + def->numUsers = 1; + def->num = varDefs.Append( def ); + def->compileStamp = compileStamp; + + // add the def to the list with defs with this name and set the name pointer + AddDefToNameList( def, name ); + + if ( ( type->Type() == ev_vector ) || ( ( type->Type() == ev_field ) && ( type->FieldType()->Type() == ev_vector ) ) ) { + // + // vector + // + if ( !idStr::Cmp( name, RESULT_STRING ) ) { + // vector defs don't need the _x, _y and _z components + assert( scope->Type() == ev_function ); + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + scope->value.functionPtr->locals += type->Size(); + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + idTypeDef newtype( ev_field, NULL, "float field", 0, &type_float ); + idTypeDef *type = GetType( newtype, true ); + + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( type, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( type, element, scope, constant ); + def_y->value.ptrOffset = def_x->value.ptrOffset + type_float.Size(); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( type, element, scope, constant ); + def_z->value.ptrOffset = def_y->value.ptrOffset + type_float.Size(); + } else { + // make automatic defs for the vectors elements + // origin can be accessed as origin_x, origin_y, and origin_z + sprintf( element, "%s_x", def->Name() ); + def_x = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_y", def->Name() ); + def_y = AllocDef( &type_float, element, scope, constant ); + + sprintf( element, "%s_z", def->Name() ); + def_z = AllocDef( &type_float, element, scope, constant ); + + // point the vector def to the x coordinate + def->value = def_x->value; + def->initialized = def_x->initialized; + } + } else if ( scope->TypeDef()->Inherits( &type_object ) ) { + // + // object variable + // + // set the value to the variable's position in the object + def->value.ptrOffset = scope->TypeDef()->Size(); + } else if ( scope->Type() == ev_function ) { + // + // stack variable + // + // since we don't know how many local variables there are, + // we have to have them go backwards on the stack + def->value.stackOffset = scope->value.functionPtr->locals; + def->initialized = idVarDef::stackVariable; + + if ( type->Inherits( &type_object ) ) { + // objects only have their entity number on the stack, not the entire object + scope->value.functionPtr->locals += type_object.Size(); + } else { + scope->value.functionPtr->locals += type->Size(); + } + } else { + // + // global variable + // + def->value.bytePtr = &variables[ numVariables ]; + numVariables += def->TypeDef()->Size(); + if ( numVariables > sizeof( variables ) ) { + throw idCompileError( va( "Exceeded global memory size (%d bytes)", sizeof( variables ) ) ); + } + + memset( def->value.bytePtr, 0, def->TypeDef()->Size() ); + } + + return def; +} + +/* +============ +idProgram::GetDef + +If type is NULL, it will match any type +============ +*/ +idVarDef *idProgram::GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const { + idVarDef *def; + idVarDef *bestDef; + int bestDepth; + int depth; + + bestDepth = 0; + bestDef = NULL; + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def->scope->Type() == ev_namespace ) { + depth = def->DepthOfScope( scope ); + if ( !depth ) { + // not in the same namespace + continue; + } + } else if ( def->scope != scope ) { + // in a different function + continue; + } else { + depth = 1; + } + + if ( !bestDef || ( depth < bestDepth ) ) { + bestDepth = depth; + bestDef = def; + } + } + + // see if the name is already in use for another type + if ( bestDef && type && ( bestDef->TypeDef() != type ) ) { + throw idCompileError( va( "Type mismatch on redeclaration of %s", name ) ); + } + + return bestDef; +} + +/* +============ +idProgram::FreeDef +============ +*/ +void idProgram::FreeDef( idVarDef *def, const idVarDef *scope ) { + idVarDef *e; + int i; + + if ( def->Type() == ev_vector ) { + idStr name; + + sprintf( name, "%s_x", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_y", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + + sprintf( name, "%s_z", def->Name() ); + e = GetDef( NULL, name, scope ); + if ( e ) { + FreeDef( e, scope ); + } + } + + varDefs.RemoveIndex( def->num ); + for( i = def->num; i < varDefs.Num(); i++ ) { + varDefs[ i ]->num = i; + } + + delete def; +} + +/* +============ +idProgram::FindFreeResultDef +============ +*/ +idVarDef *idProgram::FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ) { + idVarDef *def; + + for( def = GetDefList( name ); def != NULL; def = def->Next() ) { + if ( def == a || def == b ) { + continue; + } + if ( def->TypeDef() != type ) { + continue; + } + if ( def->scope != scope ) { + continue; + } + if ( def->numUsers <= 1 ) { + continue; + } + return def; + } + + return AllocDef( type, name, scope, false ); +} + +/* +================ +idProgram::FindFunction + +Searches for the specified function in the currently loaded script. A full namespace should be +specified if not in the global namespace. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name ) const { + int start; + int pos; + idVarDef *namespaceDef; + idVarDef *def; + + assert( name ); + + idStr fullname = name; + start = 0; + namespaceDef = &def_namespace; + do { + pos = fullname.Find( "::", true, start ); + if ( pos < 0 ) { + break; + } + + idStr namespaceName = fullname.Mid( start, pos - start ); + def = GetDef( NULL, namespaceName, namespaceDef ); + if ( !def ) { + // couldn't find namespace + return NULL; + } + namespaceDef = def; + + // skip past the :: + start = pos + 2; + } while( def->Type() == ev_namespace ); + + idStr funcName = fullname.Right( fullname.Length() - start ); + def = GetDef( NULL, funcName, namespaceDef ); + if ( !def ) { + // couldn't find function + return NULL; + } + + if ( ( def->Type() == ev_function ) && ( def->value.functionPtr->eventdef == NULL ) ) { + return def->value.functionPtr; + } + + // is not a function, or is an eventdef + return NULL; +} + +// RAVEN BEGIN +// bgeisler: list functions +/* +================ +idProgram::ListFunctions +================ +*/ +void idProgram::ListStates( void ) +{ + gameLocal.Printf( "Script States: \n"); + // function 0 is a NULL function + for( int i = 1; i < functions.Num(); i++ ) + { + gameLocal.Printf( "%s \n", functions[ i ].Name() ); + } + +} +// RAVEN END + +/* +================ +idProgram::FindFunction + +Searches for the specified object function in the currently loaded script. + +Returns 0 if function not found. +Returns >0 if function found. +================ +*/ +function_t *idProgram::FindFunction( const char *name, const idTypeDef *type ) const { + const idVarDef *tdef; + const idVarDef *def; + + // look for the function + def = NULL; + for( tdef = type->def; tdef != &def_object; tdef = tdef->TypeDef()->SuperClass()->def ) { + def = GetDef( NULL, name, tdef ); + if ( def ) { + return def->value.functionPtr; + } + } + + return NULL; +} + +/* +================ +idProgram::AllocFunction +================ +*/ +function_t &idProgram::AllocFunction( idVarDef *def ) { + if ( functions.Num() >= functions.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of functions (%d)", functions.Max() ) ); + } + + // fill in the dfunction + function_t &func = *functions.Alloc(); + func.eventdef = NULL; + func.def = def; + func.type = def->TypeDef(); + func.firstStatement = 0; + func.numStatements = 0; + func.parmTotal = 0; + func.locals = 0; + func.filenum = filenum; + func.parmSize.SetGranularity( 1 ); + func.SetName( def->GlobalName() ); + + def->SetFunction( &func ); + + return func; +} + +/* +================ +idProgram::SetEntity +================ +*/ +void idProgram::SetEntity( const char *name, idEntity *ent ) { + idVarDef *def; + idStr defName( "$" ); + + defName += name; + + def = GetDef( &type_entity, defName, &def_namespace ); + if ( def && ( def->initialized != idVarDef::stackVariable ) ) { + // 0 is reserved for NULL entity + if ( !ent ) { + *def->value.entityNumberPtr = 0; + } else { + *def->value.entityNumberPtr = ent->entityNumber + 1; + } + } +} + +/* +================ +idProgram::AllocStatement +================ +*/ +statement_t *idProgram::AllocStatement( void ) { + if ( statements.Num() >= statements.Max() ) { + throw idCompileError( va( "Exceeded maximum allowed number of statements (%d)", statements.Max() ) ); + } + return statements.Alloc(); +} + +/* +============== +idProgram::BeginCompilation + +called before compiling a batch of files, clears the pr struct +============== +*/ +void idProgram::BeginCompilation( void ) { + statement_t *statement; + + FreeData(); + + try { + // make the first statement a return for a "NULL" function + statement = AllocStatement(); + statement->linenumber = 0; + statement->file = 0; + statement->op = OP_RETURN; + statement->a = NULL; + statement->b = NULL; + statement->c = NULL; + + // define NULL + //AllocDef( &type_void, "", &def_namespace, true ); + + // define the return def + returnDef = AllocDef( &type_vector, "", &def_namespace, false ); + + // define the return def for strings + returnStringDef = AllocDef( &type_string, "", &def_namespace, false ); + + // define the sys object + sysDef = AllocDef( &type_void, "sys", &def_namespace, true ); + } + + catch( idCompileError &err ) { + gameLocal.Error( "%s", err.error ); + } +} + +/* +============== +idProgram::DisassembleStatement +============== +*/ +void idProgram::DisassembleStatement( idFile *file, int instructionPointer ) const { + opcode_t *op; + const statement_t *statement; + + statement = &statements[ instructionPointer ]; + op = &idCompiler::opcodes[ statement->op ]; + file->Printf( "%20s(%d):\t%6d: %15s\t", fileList[ statement->file ].c_str(), statement->linenumber, instructionPointer, op->opname ); + + if ( statement->a ) { + file->Printf( "\ta: " ); + statement->a->PrintInfo( file, instructionPointer ); + } + + if ( statement->b ) { + file->Printf( "\tb: " ); + statement->b->PrintInfo( file, instructionPointer ); + } + + if ( statement->c ) { + file->Printf( "\tc: " ); + statement->c->PrintInfo( file, instructionPointer ); + } + + file->Printf( "\n" ); +} + +/* +============== +idProgram::Disassemble +============== +*/ +void idProgram::Disassemble( void ) const { + int i; + int instructionPointer; + const function_t *func; + idFile *file; + + file = fileSystem->OpenFileByMode( "script/disasm.txt", FS_WRITE ); + + for( i = 0; i < functions.Num(); i++ ) { + func = &functions[ i ]; + if ( func->eventdef ) { + // skip eventdefs + continue; + } + + file->Printf( "\nfunction %s() %d stack used, %d parms, %d locals {\n", func->Name(), func->locals, func->parmTotal, func->locals - func->parmTotal ); + + for( instructionPointer = 0; instructionPointer < func->numStatements; instructionPointer++ ) { + DisassembleStatement( file, func->firstStatement + instructionPointer ); + } + + file->Printf( "}\n" ); + } + + fileSystem->CloseFile( file ); +} + +/* +============== +idProgram::FinishCompilation + +Called after all files are compiled to check for errors +============== +*/ +void idProgram::FinishCompilation( void ) { + int i; + + top_functions = functions.Num(); + top_statements = statements.Num(); + top_types = types.Num(); + top_defs = varDefs.Num(); + top_files = fileList.Num(); + + variableDefaults.Clear(); + variableDefaults.SetNum( numVariables ); + + for( i = 0; i < numVariables; i++ ) { + variableDefaults[ i ] = variables[ i ]; + } +} + +/* +============== +idProgram::CompileStats + +called after all files are compiled to report memory usage. +============== +*/ +void idProgram::CompileStats( void ) { + int memused; + int memallocated; + int numdefs; + int stringspace; + int funcMem; + int i; + + gameLocal.Printf( "-------------- Compile stats ----------------\n" ); + gameLocal.DPrintf( "Files loaded:\n" ); + + stringspace = 0; + for( i = 0; i < fileList.Num(); i++ ) { + gameLocal.DPrintf( " %s\n", fileList[ i ].c_str() ); + stringspace += fileList[ i ].Allocated(); + } + stringspace += fileList.Size(); + + numdefs = varDefs.Num(); + memused = varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + memused += stringspace; + + for( i = 0; i < types.Num(); i++ ) { + memused += types[ i ]->Allocated(); + } + + funcMem = functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + funcMem += functions[ i ].Allocated(); + } + + memallocated = funcMem + memused + sizeof( idProgram ); + + memused += statements.MemoryUsed(); + memused += functions.MemoryUsed(); // name and filename of functions are shared, so no need to include them + memused += sizeof( variables ); + + gameLocal.Printf( "\nMemory usage:\n" ); + gameLocal.Printf( " Strings: %d, %d bytes\n", fileList.Num(), stringspace ); + gameLocal.Printf( " Statements: %d, %d bytes\n", statements.Num(), statements.MemoryUsed() ); + gameLocal.Printf( " Functions: %d, %d bytes\n", functions.Num(), funcMem ); + gameLocal.Printf( " Variables: %d bytes\n", numVariables ); + gameLocal.Printf( " Mem used: %d bytes\n", memused ); + gameLocal.Printf( " Static data: %d bytes\n", sizeof( idProgram ) ); + gameLocal.Printf( " Allocated: %d bytes\n", memallocated ); + gameLocal.Printf( " Thread size: %d bytes\n\n", sizeof( idThread ) ); +} + +// RAVEN BEGIN +// jscott: summary of script memory usage +/* +================ +idProgram::ScriptSummary +================ +*/ +size_t idProgram::ScriptSummary( const idCmdArgs &args ) { + + int memused; + int i; + + memused = 0; + for( i = 0; i < fileList.Num(); i++ ) { + + memused += fileList[i].Allocated(); + } + memused += fileList.Size(); + + memused += varDefs.Num() * sizeof( idVarDef ); + memused += types.Num() * sizeof( idTypeDef ); + + for( i = 0; i < types.Num(); i++ ) { + + memused += types[i]->Allocated(); + } + + memused += functions.MemoryUsed(); + for( i = 0; i < functions.Num(); i++ ) { + + memused += functions[i].Allocated(); + } + + memused += statements.MemoryUsed(); + memused += sizeof( variables ); + memused += sizeof( idProgram ); + memused += sizeof( idThread ); + + common->Printf( "Scripts - %dK\n", memused >> 10 ); + + return( memused >> 10 ); +} +// RAVEN END + +/* +================ +idProgram::CompileText +================ +*/ +bool idProgram::CompileText( const char *source, const char *text, bool console, bool fatal ) { + idCompiler compiler; + int i; + idVarDef *def; + idStr ospath; + +// RAVEN BEGIN +// bdube: Make sure the file hasnt already been loaded + if ( -1 != fileList.FindIndex ( idStr(source) ) ) { + return true; + } +// RAVEN END + + // use a full os path for GetFilenum since it calls OSPathToRelativePath to convert filenames from the parser + ospath = fileSystem->RelativePathToOSPath( source ); + filenum = GetFilenum( ospath ); + + ++compileStamp; + + try { + compiler.CompileFile( text, filename, console ); + + // check to make sure all functions prototyped have code + for( i = 0; i < varDefs.Num(); i++ ) { + def = varDefs[ i ]; + if ( ( def->Type() == ev_function ) && ( ( def->scope->Type() == ev_namespace ) || def->scope->TypeDef()->Inherits( &type_object ) ) ) { + if ( !def->value.functionPtr->eventdef && !def->value.functionPtr->firstStatement ) { + throw idCompileError( va( "function %s was not defined\n", def->GlobalName() ) ); + } + } + } + } + + catch( idCompileError &err ) { + // attempt to recover by deleting anything new in the script + for( i = 0; i < varDefs.Num(); i++ ) { + def = varDefs[ i ]; + + if ( def->compileStamp == compileStamp ) { + FreeDef( def, def->scope ); + i--; + } + } + + if ( console || !fatal ) { + gameLocal.Printf( "%s\n", err.error ); + return false; + } else { + gameLocal.Error( "%s\n", err.error ); + } + }; + + if ( !console ) { + CompileStats(); + } + + return true; +} + +/* +================ +idProgram::CompileFunction +================ +*/ +const function_t *idProgram::CompileFunction( const char *functionName, const char *text ) { + bool result; + + result = CompileText( functionName, text, false ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + gameLocal.Error( "Compile failed." ); + } + + return FindFunction( functionName ); +} + +/* +================ +idProgram::CompileFile +================ +*/ +bool idProgram::CompileFile( const char *filename, bool fatal ) { + char *src; + bool result; + + if ( fileSystem->ReadFile( filename, ( void ** )&src, NULL ) < 0 ) { + gameLocal.Error( "Couldn't load %s\n", filename ); + } + + result = CompileText( filename, src, false, fatal ); + + fileSystem->FreeFile( src ); + + if ( g_disasm.GetBool() ) { + Disassemble(); + } + + if ( !result ) { + if ( fatal ) { + gameLocal.Error( "Compile failed in file %s.", filename ); + } else { + gameLocal.Warning( "Compile failed in file %s.", filename ); + } + return false; + } + + return true; +} + +/* +================ +idProgram::FreeData +================ +*/ +void idProgram::FreeData( void ) { + int i; + + // free the defs + varDefs.DeleteContents( true ); + varDefNames.DeleteContents( true ); + varDefNameHash.Free(); + + returnDef = NULL; + returnStringDef = NULL; + sysDef = NULL; + + // free any special types we've created + types.DeleteContents( true ); + + filenum = 0; + + numVariables = 0; + memset( variables, 0, sizeof( variables ) ); + + // clear all the strings in the functions so that it doesn't look like we're leaking memory. + for( i = 0; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + + filename.Clear(); + fileList.Clear(); + statements.Clear(); + functions.Clear(); + + top_functions = 0; + top_statements = 0; + top_types = 0; + top_defs = 0; + top_files = 0; + + filename = ""; + + compileStamp = 0; +} + +/* +================ +idProgram::Startup +================ +*/ +void idProgram::Startup( const char *defaultScript ) { +// RAVEN BEGIN +// jnewquist: Tag scope and callees to track allocations using "new". + MEM_SCOPED_TAG(tag, MA_SCRIPT); +// RAVEN END + gameLocal.Printf( "Initializing scripts\n" ); + + // make sure all data is freed up + idThread::Restart(); + + // get ready for loading scripts + BeginCompilation(); + + // load the default script + if ( defaultScript && *defaultScript ) { + CompileFile( defaultScript ); + } + + FinishCompilation(); +} + +/* +================ +idProgram::Save +================ +*/ +void idProgram::Save( idSaveGame *savefile ) const { + int i; + int currentFileNum = top_files; + + savefile->WriteInt( (fileList.Num() - currentFileNum) ); + while ( currentFileNum < fileList.Num() ) { + savefile->WriteString( fileList[ currentFileNum ] ); + currentFileNum++; + } + savefile->WriteString( filename ); // cnicholson: Added unsaved var + savefile->WriteInt ( filenum ); // cnicholson: Added unsaved var + + for ( i = 0; i < variableDefaults.Num(); i++ ) { + if ( variables[i] != variableDefaults[i] ) { + savefile->WriteInt( i ); + savefile->WriteByte( variables[i] ); + } + } + // Mark the end of the diff with default variables with -1 + savefile->WriteInt( -1 ); + + savefile->WriteInt( numVariables ); + for ( i = variableDefaults.Num(); i < numVariables; i++ ) { + savefile->WriteByte( variables[i] ); + } + + savefile->WriteInt ( top_functions ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_statements ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_types ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_defs ); // cnicholson: Added unsaved var + savefile->WriteInt ( top_files ); // cnicholson: Added unsaved var + + int checksum = CalculateChecksum(); + savefile->WriteInt( checksum ); +} + +/* +================ +idProgram::Restore +================ +*/ +bool idProgram::Restore( idRestoreGame *savefile ) { + int i, num, index; + bool result = true; + idStr scriptname; + + savefile->ReadInt( num ); + for ( i = 0; i < num; i++ ) { + savefile->ReadString( scriptname ); + CompileFile( scriptname ); + } + + savefile->ReadString( filename ); // cnicholson: Added unrestored var + savefile->ReadInt ( filenum ); // cnicholson: Added unrestored var + + savefile->ReadInt( index ); + while( index >= 0 ) { + savefile->ReadByte( variables[index] ); + savefile->ReadInt( index ); + } + + savefile->ReadInt( num ); + for ( i = variableDefaults.Num(); i < num; i++ ) { + savefile->ReadByte( variables[i] ); + } + + savefile->ReadInt ( top_functions ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_statements ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_types ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_defs ); // cnicholson: Added unrestored var + savefile->ReadInt ( top_files ); // cnicholson: Added unrestored var + + int saved_checksum, checksum; + + savefile->ReadInt( saved_checksum ); + checksum = CalculateChecksum(); + + if ( saved_checksum != checksum ) { + result = false; + } + + return result; +} + +/* +================ +idProgram::CalculateChecksum +================ +*/ +int idProgram::CalculateChecksum( void ) const { + int i, result; + + typedef struct { + unsigned short op; + int a; + int b; + int c; + unsigned short linenumber; + unsigned short file; + } statementBlock_t; + + statementBlock_t *statementList = new statementBlock_t[ statements.Num() ]; + + memset( statementList, 0, ( sizeof(statementBlock_t) * statements.Num() ) ); + + // Copy info into new list, using the variable numbers instead of a pointer to the variable + for( i = 0; i < statements.Num(); i++ ) { + statementList[i].op = statements[i].op; + + if ( statements[i].a ) { + statementList[i].a = statements[i].a->num; + } else { + statementList[i].a = -1; + } + if ( statements[i].b ) { + statementList[i].b = statements[i].b->num; + } else { + statementList[i].b = -1; + } + if ( statements[i].c ) { + statementList[i].c = statements[i].c->num; + } else { + statementList[i].c = -1; + } + + statementList[i].linenumber = statements[i].linenumber; + statementList[i].file = statements[i].file; + } + + result = MD4_BlockChecksum( statementList, ( sizeof(statementBlock_t) * statements.Num() ) ); + + delete [] statementList; + + return result; +} + +/* +============== +idProgram::Restart + +Restores all variables to their initial value +============== +*/ +void idProgram::Restart( void ) { + int i; + + idThread::Restart(); + + // + // since there may have been a script loaded by the map or the user may + // have typed "script" from the console, free up any types and vardefs that + // have been allocated after the initial startup + // + for( i = top_types; i < types.Num(); i++ ) { + delete types[ i ]; + } + types.SetNum( top_types, false ); + + for( i = top_defs; i < varDefs.Num(); i++ ) { + delete varDefs[ i ]; + } + varDefs.SetNum( top_defs, false ); + + for( i = top_functions; i < functions.Num(); i++ ) { + functions[ i ].Clear(); + } + functions.SetNum( top_functions ); + + statements.SetNum( top_statements ); + fileList.SetNum( top_files, false ); + filename.Clear(); + + // reset the variables to their default values + numVariables = variableDefaults.Num(); + for( i = 0; i < numVariables; i++ ) { + variables[ i ] = variableDefaults[ i ]; + } +} + +/* +================ +idProgram::GetFilenum +================ +*/ +int idProgram::GetFilenum( const char *name ) { + if ( filename == name ) { + return filenum; + } + + idStr strippedName; + strippedName = fileSystem->OSPathToRelativePath( name ); + if ( !strippedName.Length() ) { + // not off the base path so just use the full path + filenum = fileList.AddUnique( name ); + } else { + filenum = fileList.AddUnique( strippedName ); + } + + // save the unstripped name so that we don't have to strip the incoming name every time we call GetFilenum + filename = name; + + return filenum; +} + +/* +================ +idProgram::idProgram +================ +*/ +idProgram::idProgram() { + FreeData(); +} + +/* +================ +idProgram::~idProgram +================ +*/ +idProgram::~idProgram() { + FreeData(); +} + +// RAVEN BEGIN +// jscott: for debug with inlines and memory log +/* +================ +idProgram::Shutdown +================ +*/ +void idProgram::Shutdown( void ) +{ + FreeData(); +} +// RAVEN END + +/* +================ +idProgram::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idProgram::ReturnEntity( const idEntity *ent ) { + if ( ent ) { + *returnDef->value.entityNumberPtr = ent->entityNumber + 1; + } else { + *returnDef->value.entityNumberPtr = 0; + } +} + diff --git a/src/mpgame/script/Script_Program.h b/src/mpgame/script/Script_Program.h new file mode 100644 index 0000000..a6ea857 --- /dev/null +++ b/src/mpgame/script/Script_Program.h @@ -0,0 +1,823 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_PROGRAM_H__ +#define __SCRIPT_PROGRAM_H__ + +class idScriptObject; +class idEventDef; +class idVarDef; +class idTypeDef; +class idEntity; +class idThread; +class idSaveGame; +class idRestoreGame; + +#define MAX_STRING_LEN 128 +// RAVEN BEGIN +// ddynerman: higher max limit, initially 196608 +// jshepard: ... then 393216 +#define MAX_GLOBALS 589824 // in bytes + +// jshepard: raise the limits. Formerly 1024, 3072 and 81920. +#define MAX_STRINGS 2048 +#define MAX_FUNCS 6144 +#define MAX_STATEMENTS 163840 // statement_t - 18 bytes last I checked +// RAVEN END +typedef enum { + ev_error = -1, ev_void, ev_scriptevent, ev_namespace, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_virtualfunction, ev_pointer, ev_object, ev_jumpoffset, ev_argsize, ev_boolean +} etype_t; + +class function_t { +public: + function_t(); + + size_t Allocated( void ) const; + void SetName( const char *name ); + const char *Name( void ) const; + void Clear( void ); + +private: + idStr name; +public: + const idEventDef *eventdef; + idVarDef *def; + const idTypeDef *type; + int firstStatement; + int numStatements; + int parmTotal; + int locals; // total ints of parms + locals + int filenum; // source file defined in + idList parmSize; +}; + +typedef union eval_s { + const char *stringPtr; + float _float; + float vector[ 3 ]; + function_t *function; + int _int; + int entity; +} eval_t; + +/*********************************************************************** + +idTypeDef + +Contains type information for variables and functions. + +***********************************************************************/ + +class idTypeDef { +private: + etype_t type; + idStr name; + int size; + + // function types are more complex + idTypeDef *auxType; // return type + idList parmTypes; + idStrList parmNames; + idList functions; + +public: + idVarDef *def; // a def that points to this type + + idTypeDef( const idTypeDef &other ); + idTypeDef( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + virtual ~idTypeDef( void ) { } + void operator=( const idTypeDef& other ); + size_t Allocated( void ) const; + + bool Inherits( const idTypeDef *basetype ) const; + bool MatchesType( const idTypeDef &matchtype ) const; + bool MatchesVirtualFunction( const idTypeDef &matchfunc ) const; + void AddFunctionParm( idTypeDef *parmtype, const char *name ); + void AddField( idTypeDef *fieldtype, const char *name ); + + void SetName( const char *newname ); + const char *Name( void ) const; + + etype_t Type( void ) const; + int Size( void ) const; + + idTypeDef *SuperClass( void ) const; + + idTypeDef *ReturnType( void ) const; + void SetReturnType( idTypeDef *type ); + + idTypeDef *FieldType( void ) const; + void SetFieldType( idTypeDef *type ); + + idTypeDef *PointerType( void ) const; + void SetPointerType( idTypeDef *type ); + + int NumParameters( void ) const; + idTypeDef *GetParmType( int parmNumber ) const; + const char *GetParmName( int parmNumber ) const; + + int NumFunctions( void ) const; + int GetFunctionNumber( const function_t *func ) const; + const function_t *GetFunction( int funcNumber ) const; + void AddFunction( const function_t *func ); + +// RAVEN BEGIN + // abahr + virtual const char* GetReturnedValAsString( idProgram& program ) { return ""; } + virtual void PushOntoStack( idThread* thread, const char* source ) { assert(0); } + virtual bool IsValid( const char* source ) const { return true; } + virtual const char* Format() const { return ""; } +// RAVEN END +}; + +// RAVEN BEGIN +// abahr: subclasses for overwritting helper functions + +/*********************************************************************** + +rvTypeDefInt + +***********************************************************************/ +class rvTypeDefInt : public idTypeDef { +public: + rvTypeDefInt( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefInt( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%d"; } + int Parse( const char* source ) const; + +}; + +/*********************************************************************** + +rvTypeDefFloat + +***********************************************************************/ +class rvTypeDefFloat : public idTypeDef { +public: + rvTypeDefFloat( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefFloat( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f"; } + float Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefVec3 + +***********************************************************************/ +class rvTypeDefVec3 : public idTypeDef { +public: + rvTypeDefVec3( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefVec3( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%f %f %f"; } + idVec3 Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefEntity + +***********************************************************************/ +class rvTypeDefEntity : public idTypeDef { +public: + rvTypeDefEntity( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefEntity( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + idEntity* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefString + +***********************************************************************/ +class rvTypeDefString : public idTypeDef { +public: + rvTypeDefString( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefString( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Parse( const char* source ) const; +}; + +/*********************************************************************** + +rvTypeDefBool + +***********************************************************************/ +class rvTypeDefBool : public idTypeDef { +public: + rvTypeDefBool( const idTypeDef &other ) : idTypeDef( other ) {} + rvTypeDefBool( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ) : idTypeDef( etype, edef, ename, esize, aux ) {} + + const char* GetReturnedValAsString( idProgram& program ); + void PushOntoStack( idThread* thread, const char* source ); + bool IsValid( const char* source ) const; + +protected: + const char* Format() const { return "%u"; } + bool Parse( const char* source ) const; +}; +// RAVEN END + +/*********************************************************************** + +idScriptObject + +In-game representation of objects in scripts. Use the idScriptVariable template +(below) to access variables. + +***********************************************************************/ + +class idScriptObject { +private: + idTypeDef *type; + +public: + byte *data; + + idScriptObject(); + ~idScriptObject(); + + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void Free( void ); + bool SetType( const char *typeName ); + void ClearObject( void ); + bool HasObject( void ) const; + idTypeDef *GetTypeDef( void ) const; + const char *GetTypeName( void ) const; + const function_t *GetConstructor( void ) const; + const function_t *GetDestructor( void ) const; + const function_t *GetFunction( const char *name ) const; + + byte *GetVariable( const char *name, etype_t etype ) const; +}; + +/*********************************************************************** + +idScriptVariable + +Helper template that handles looking up script variables stored in objects. +If the specified variable doesn't exist, or is the wrong data type, idScriptVariable +will cause an error. + +***********************************************************************/ + +template +class idScriptVariable { +private: + type *data; + +public: + idScriptVariable(); + bool IsLinked( void ) const; + void Unlink( void ); + void LinkTo( idScriptObject &obj, const char *name ); + idScriptVariable &operator=( const returnType &value ); + operator returnType() const; +}; + +template +ID_INLINE idScriptVariable::idScriptVariable() { + data = NULL; +} + +template +ID_INLINE bool idScriptVariable::IsLinked( void ) const { + return ( data != NULL ); +} + +template +ID_INLINE void idScriptVariable::Unlink( void ) { + data = NULL; +} + +template +ID_INLINE void idScriptVariable::LinkTo( idScriptObject &obj, const char *name ) { + data = ( type * )obj.GetVariable( name, etype ); + if ( !data ) { + gameError( "Missing '%s' field in script object '%s'", name, obj.GetTypeName() ); + } +} + +template +ID_INLINE idScriptVariable &idScriptVariable::operator=( const returnType &value ) { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + *data = ( type )value; + } + return *this; +} + +template +ID_INLINE idScriptVariable::operator returnType() const { + // check if we attempt to access the object before it's been linked + assert( data ); + + // make sure we don't crash if we don't have a pointer + if ( data ) { + return ( const returnType )*data; + } else { + // reasonably safe value + return ( const returnType )0; + } +} + +/*********************************************************************** + +Script object variable access template instantiations + +These objects will automatically handle looking up of the current value +of a variable in a script object. They can be stored as part of a class +for up-to-date values of the variable, or can be used in functions to +sample the data for non-dynamic values. + +***********************************************************************/ + +typedef idScriptVariable idScriptBool; +typedef idScriptVariable idScriptFloat; +typedef idScriptVariable idScriptInt; +typedef idScriptVariable idScriptVector; +typedef idScriptVariable idScriptString; + +/*********************************************************************** + +idCompileError + +Causes the compiler to exit out of compiling the current function and +display an error message with line and file info. + +***********************************************************************/ + +class idCompileError : public idException { +public: + idCompileError( const char *text ) : idException( text ) {} +}; + +/*********************************************************************** + +idVarDef + +Define the name, type, and location of variables, functions, and objects +defined in script. + +***********************************************************************/ + +typedef union varEval_s { + idScriptObject **objectPtrPtr; + char *stringPtr; + float *floatPtr; + idVec3 *vectorPtr; + function_t *functionPtr; + int *intPtr; + byte *bytePtr; + int *entityNumberPtr; + int virtualFunction; + int jumpOffset; + int stackOffset; // offset in stack for local variables + int argSize; + varEval_s *evalPtr; + int ptrOffset; +} varEval_t; + +class idVarDefName; + +class idVarDef { + friend class idVarDefName; + +public: + int num; + varEval_t value; + idVarDef * scope; // function, namespace, or object the var was defined in + int numUsers; // number of users if this is a constant + int compileStamp; // compilation stamp for deleting failed compiles + + typedef enum { + uninitialized, initializedVariable, initializedConstant, stackVariable + } initialized_t; + + initialized_t initialized; + +public: + idVarDef( idTypeDef *typeptr = NULL ); + ~idVarDef(); + + const char * Name( void ) const; + const char * GlobalName( void ) const; + + void SetTypeDef( idTypeDef *_type ) { typeDef = _type; } + idTypeDef * TypeDef( void ) const { return typeDef; } + etype_t Type( void ) const { return ( typeDef != NULL ) ? typeDef->Type() : ev_void; } + + int DepthOfScope( const idVarDef *otherScope ) const; + + void SetFunction( function_t *func ); + void SetObject( idScriptObject *object ); + void SetValue( const eval_t &value, bool constant ); + void SetString( const char *string, bool constant ); + + idVarDef * Next( void ) const { return next; } // next var def with same name + + void PrintInfo( idFile *file, int instructionPointer ) const; + +private: + idTypeDef * typeDef; + idVarDefName * name; // name of this var + idVarDef * next; // next var with the same name +}; + +/*********************************************************************** + + idVarDefName + +***********************************************************************/ + +class idVarDefName { +public: + idVarDefName( void ) { defs = NULL; } + idVarDefName( const char *n ) { name = n; defs = NULL; } + + const char * Name( void ) const { return name; } + idVarDef * GetDefs( void ) const { return defs; } + + void AddDef( idVarDef *def ); + void RemoveDef( idVarDef *def ); + +private: + idStr name; + idVarDef * defs; +}; + +/*********************************************************************** + + Variable and type defintions + +***********************************************************************/ + +extern idTypeDef type_void; +extern idTypeDef type_scriptevent; +extern idTypeDef type_namespace; +// RAVEN BEGIN +// abahr +extern rvTypeDefString type_string; +extern rvTypeDefFloat type_float; +extern rvTypeDefVec3 type_vector; +extern rvTypeDefEntity type_entity; +// RAVEN END +extern idTypeDef type_field; +extern idTypeDef type_function; +extern idTypeDef type_virtualfunction; +extern idTypeDef type_pointer; +extern idTypeDef type_object; +extern idTypeDef type_jumpoffset; // only used for jump opcodes +extern idTypeDef type_argsize; // only used for function call and thread opcodes +// RAVEN BEGIN +// abahr +extern rvTypeDefBool type_boolean; +// RAVEN END + +extern idVarDef def_void; +extern idVarDef def_scriptevent; +extern idVarDef def_namespace; +extern idVarDef def_string; +extern idVarDef def_float; +extern idVarDef def_vector; +extern idVarDef def_entity; +extern idVarDef def_field; +extern idVarDef def_function; +extern idVarDef def_virtualfunction; +extern idVarDef def_pointer; +extern idVarDef def_object; +extern idVarDef def_jumpoffset; // only used for jump opcodes +extern idVarDef def_argsize; // only used for function call and thread opcodes +extern idVarDef def_boolean; + +typedef struct statement_s { + unsigned short op; + idVarDef *a; + idVarDef *b; + idVarDef *c; + unsigned short linenumber; + unsigned short file; +} statement_t; + +/*********************************************************************** + +idProgram + +Handles compiling and storage of script data. Multiple idProgram objects +would represent seperate programs with no knowledge of each other. Scripts +meant to access shared data and functions should all be compiled by a +single idProgram. + +***********************************************************************/ + +class idProgram { +private: + idStrList fileList; + idStr filename; + int filenum; + + int numVariables; + byte variables[ MAX_GLOBALS ]; + idStaticList variableDefaults; + idStaticList functions; + idStaticList statements; + idList types; + idList varDefNames; + idHashIndex varDefNameHash; + idList varDefs; + + idVarDef *sysDef; + + int top_functions; + int top_statements; + int top_types; + int top_defs; + int top_files; + + void CompileStats( void ); + + int compileStamp; + +public: + idVarDef *returnDef; + idVarDef *returnStringDef; + + idProgram(); + ~idProgram(); + + // save games + void Save( idSaveGame *savefile ) const; + bool Restore( idRestoreGame *savefile ); + int CalculateChecksum( void ) const; // Used to insure program code has not + // changed between savegames + + void Startup( const char *defaultScript ); + void Restart( void ); + bool CompileText( const char *source, const char *text, bool console, bool fatal = true ); + const function_t *CompileFunction( const char *functionName, const char *text ); + bool CompileFile( const char *filename, bool fatal = true ); + void BeginCompilation( void ); + void FinishCompilation( void ); + void DisassembleStatement( idFile *file, int instructionPointer ) const; + void Disassemble( void ) const; + void FreeData( void ); + + const char *GetFilename( int num ); + int GetFilenum( const char *name ); + int GetLineNumberForStatement( int index ); + const char *GetFilenameForStatement( int index ); + + idTypeDef *AllocType( idTypeDef &type ); + idTypeDef *AllocType( etype_t etype, idVarDef *edef, const char *ename, int esize, idTypeDef *aux ); + idTypeDef *GetType( idTypeDef &type, bool allocate ); + idTypeDef *FindType( const char *name ); + + idVarDef *AllocDef( idTypeDef *type, const char *name, idVarDef *scope, bool constant ); + idVarDef *GetDef( const idTypeDef *type, const char *name, const idVarDef *scope ) const; + void FreeDef( idVarDef *d, const idVarDef *scope ); + idVarDef *FindFreeResultDef( idTypeDef *type, const char *name, idVarDef *scope, const idVarDef *a, const idVarDef *b ); + idVarDef *GetDefList( const char *name ) const; + void AddDefToNameList( idVarDef *def, const char *name ); + + function_t *FindFunction( const char *name ) const; // returns NULL if function not found + function_t *FindFunction( const char *name, const idTypeDef *type ) const; // returns NULL if function not found + function_t &AllocFunction( idVarDef *def ); + function_t *GetFunction( int index ); + int GetFunctionIndex( const function_t *func ); + + void SetEntity( const char *name, idEntity *ent ); + + statement_t *AllocStatement( void ); + statement_t &GetStatement( int index ); + int NumStatements( void ) { return statements.Num(); } + + int GetReturnedInteger( void ); + +// RAVEN BEGIN +// abahr: adding helper functions for other types + float GetReturnedFloat(); + idVec3 GetReturnedVec3(); + idEntity* GetReturnedEntity(); + const char* GetReturnedString(); + bool GetReturnedBool(); +// RAVEN END + + void ReturnFloat( float value ); + void ReturnInteger( int value ); + void ReturnVector( idVec3 const &vec ); + void ReturnString( const char *string ); +// RAVEN BEGIN +// abahr: added const + void ReturnEntity( const idEntity *ent ); +// RAVEN END + + int NumFilenames( void ) { return fileList.Num( ); } + +// RAVEN BEGIN +// bdube: added + void ListStates( void ); +// jscott: added for debug with inlines and memory log + void Shutdown( void ); +// jscott: added for stats + size_t ScriptSummary( const idCmdArgs &args ); + size_t ClassSummary( const idCmdArgs &args ); +// RAVEN END +}; + +/* +================ +idProgram::GetStatement +================ +*/ +ID_INLINE statement_t &idProgram::GetStatement( int index ) { + return statements[ index ]; +} + +/* +================ +idProgram::GetFunction +================ +*/ +ID_INLINE function_t *idProgram::GetFunction( int index ) { + return &functions[ index ]; +} + +/* +================ +idProgram::GetFunctionIndex +================ +*/ +ID_INLINE int idProgram::GetFunctionIndex( const function_t *func ) { + return func - &functions[0]; +} + +/* +================ +idProgram::GetReturnedInteger +================ +*/ +ID_INLINE int idProgram::GetReturnedInteger( void ) { +// RAVEN BEGIN +// abahr: because we only return floats in idThread we need to only get floats + return (int)GetReturnedFloat(); +// RAVEN END +} + +// RAVEN BEGIN +// abahr: adding helper functions for other types +/* +================ +idProgram::GetReturnedFloat +================ +*/ +ID_INLINE float idProgram::GetReturnedFloat() { + return *returnDef->value.floatPtr; +} + +/* +================ +idProgram::GetReturnedVec3 +================ +*/ +ID_INLINE idVec3 idProgram::GetReturnedVec3() { + return *returnDef->value.vectorPtr; +} + +/* +================ +idProgram::GetReturnedString +================ +*/ +ID_INLINE const char* idProgram::GetReturnedString() { + return returnDef->value.stringPtr; +} + +/* +================ +idProgram::GetReturnedBool +================ +*/ +ID_INLINE bool idProgram::GetReturnedBool() { + return GetReturnedInteger() != 0; +} +// RAVEN END + +/* +================ +idProgram::ReturnFloat +================ +*/ +ID_INLINE void idProgram::ReturnFloat( float value ) { + *returnDef->value.floatPtr = value; +} + +/* +================ +idProgram::ReturnInteger +================ +*/ +ID_INLINE void idProgram::ReturnInteger( int value ) { + *returnDef->value.intPtr = value; +} + +/* +================ +idProgram::ReturnVector +================ +*/ +ID_INLINE void idProgram::ReturnVector( idVec3 const &vec ) { + *returnDef->value.vectorPtr = vec; +} + +/* +================ +idProgram::ReturnString +================ +*/ +ID_INLINE void idProgram::ReturnString( const char *string ) { + idStr::Copynz( returnStringDef->value.stringPtr, string, MAX_STRING_LEN ); +} + +/* +================ +idProgram::GetFilename +================ +*/ +ID_INLINE const char *idProgram::GetFilename( int num ) { + return fileList[ num ]; +} + +/* +================ +idProgram::GetLineNumberForStatement +================ +*/ +ID_INLINE int idProgram::GetLineNumberForStatement( int index ) { + return statements[ index ].linenumber; +} + +/* +================ +idProgram::GetFilenameForStatement +================ +*/ +ID_INLINE const char *idProgram::GetFilenameForStatement( int index ) { + return GetFilename( statements[ index ].file ); +} + +#endif /* !__SCRIPT_PROGRAM_H__ */ diff --git a/src/mpgame/script/Script_Thread.cpp b/src/mpgame/script/Script_Thread.cpp new file mode 100644 index 0000000..fa7c0a7 --- /dev/null +++ b/src/mpgame/script/Script_Thread.cpp @@ -0,0 +1,2332 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +const idEventDef EV_Thread_Execute( "", NULL ); +const idEventDef EV_Thread_SetCallback( "", NULL ); + +// script callable events +const idEventDef EV_Thread_TerminateThread( "terminate", "d" ); +const idEventDef EV_Thread_Pause( "pause", NULL ); +const idEventDef EV_Thread_Wait( "wait", "f" ); +const idEventDef EV_Thread_WaitFrame( "waitFrame" ); +const idEventDef EV_Thread_WaitFor( "waitFor", "e" ); +const idEventDef EV_Thread_WaitForThread( "waitForThread", "d" ); +const idEventDef EV_Thread_Print( "print", "s" ); +const idEventDef EV_Thread_PrintLn( "println", "s" ); +const idEventDef EV_Thread_Say( "say", "s" ); +const idEventDef EV_Thread_Assert( "assert", "f" ); +const idEventDef EV_Thread_Trigger( "trigger", "e" ); +const idEventDef EV_Thread_SetCvar( "setcvar", "ss" ); +const idEventDef EV_Thread_GetCvar( "getcvar", "s", 's' ); +const idEventDef EV_Thread_Random( "random", "f", 'f' ); +const idEventDef EV_Thread_GetTime( "getTime", NULL, 'f' ); +const idEventDef EV_Thread_KillThread( "killthread", "s" ); +const idEventDef EV_Thread_SetThreadName( "threadname", "s" ); +const idEventDef EV_Thread_GetEntity( "getEntity", "s", 'e' ); +const idEventDef EV_Thread_Spawn( "spawn", "s", 'e' ); +const idEventDef EV_Thread_CopySpawnArgs( "copySpawnArgs", "e" ); +const idEventDef EV_Thread_SetSpawnArg( "setSpawnArg", "ss" ); +const idEventDef EV_Thread_SpawnString( "SpawnString", "ss", 's' ); +const idEventDef EV_Thread_SpawnFloat( "SpawnFloat", "sf", 'f' ); +const idEventDef EV_Thread_SpawnVector( "SpawnVector", "sv", 'v' ); +const idEventDef EV_Thread_ClearPersistantArgs( "clearPersistantArgs" ); +const idEventDef EV_Thread_SetPersistantArg( "setPersistantArg", "ss" ); +const idEventDef EV_Thread_GetPersistantString( "getPersistantString", "s", 's' ); +const idEventDef EV_Thread_GetPersistantFloat( "getPersistantFloat", "s", 'f' ); +const idEventDef EV_Thread_GetPersistantVector( "getPersistantVector", "s", 'v' ); +const idEventDef EV_Thread_AngToForward( "angToForward", "v", 'v' ); +const idEventDef EV_Thread_AngToRight( "angToRight", "v", 'v' ); +const idEventDef EV_Thread_AngToUp( "angToUp", "v", 'v' ); +const idEventDef EV_Thread_Sine( "sin", "f", 'f' ); +const idEventDef EV_Thread_Cosine( "cos", "f", 'f' ); +const idEventDef EV_Thread_SquareRoot( "sqrt", "f", 'f' ); +const idEventDef EV_Thread_Normalize( "vecNormalize", "v", 'v' ); +const idEventDef EV_Thread_VecLength( "vecLength", "v", 'f' ); +const idEventDef EV_Thread_VecDotProduct( "DotProduct", "vv", 'f' ); +const idEventDef EV_Thread_VecCrossProduct( "CrossProduct", "vv", 'v' ); +const idEventDef EV_Thread_VecToAngles( "VecToAngles", "v", 'v' ); +const idEventDef EV_Thread_OnSignal( "onSignal", "des" ); +const idEventDef EV_Thread_ClearSignal( "clearSignalThread", "de" ); +const idEventDef EV_Thread_SetCamera( "setCamera", "e" ); +const idEventDef EV_Thread_FirstPerson( "firstPerson", NULL ); +const idEventDef EV_Thread_Trace( "trace", "vvvvde", 'f' ); +const idEventDef EV_Thread_TracePoint( "tracePoint", "vvde", 'f' ); +const idEventDef EV_Thread_GetTraceFraction( "getTraceFraction", NULL, 'f' ); +const idEventDef EV_Thread_GetTraceEndPos( "getTraceEndPos", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceNormal( "getTraceNormal", NULL, 'v' ); +const idEventDef EV_Thread_GetTraceEntity( "getTraceEntity", NULL, 'e' ); +const idEventDef EV_Thread_GetTraceJoint( "getTraceJoint", NULL, 's' ); +const idEventDef EV_Thread_GetTraceBody( "getTraceBody", NULL, 's' ); +const idEventDef EV_Thread_FadeIn( "fadeIn", "vf" ); +const idEventDef EV_Thread_FadeOut( "fadeOut", "vf" ); +const idEventDef EV_Thread_FadeTo( "fadeTo", "vff" ); +const idEventDef EV_Thread_StartMusic( "music", "s" ); +const idEventDef EV_Thread_Error( "error", "s" ); +const idEventDef EV_Thread_Warning( "warning", "s" ); +const idEventDef EV_Thread_StrLen( "strLength", "s", 'd' ); +const idEventDef EV_Thread_StrLeft( "strLeft", "sd", 's' ); +const idEventDef EV_Thread_StrRight( "strRight", "sd", 's' ); +const idEventDef EV_Thread_StrSkip( "strSkip", "sd", 's' ); +const idEventDef EV_Thread_StrMid( "strMid", "sdd", 's' ); +const idEventDef EV_Thread_StrToFloat( "strToFloat", "s", 'f' ); +const idEventDef EV_Thread_RadiusDamage( "radiusDamage", "vEEEsf" ); +const idEventDef EV_Thread_IsClient( "isClient", NULL, 'f' ); +const idEventDef EV_Thread_IsMultiplayer( "isMultiplayer", NULL, 'f' ); +const idEventDef EV_Thread_GetFrameTime( "getFrameTime", NULL, 'f' ); +const idEventDef EV_Thread_GetTicsPerSecond( "getTicsPerSecond", NULL, 'f' ); +const idEventDef EV_Thread_DebugLine( "debugLine", "vvvf" ); +const idEventDef EV_Thread_DebugArrow( "debugArrow", "vvvdf" ); +const idEventDef EV_Thread_DebugCircle( "debugCircle", "vvvfdf" ); +const idEventDef EV_Thread_DebugBounds( "debugBounds", "vvvf" ); +const idEventDef EV_Thread_DrawText( "drawText", "svfvdf" ); +const idEventDef EV_Thread_InfluenceActive( "influenceActive", NULL, 'd' ); + +// RAVEN BEGIN +// kfuller: added everything below the above block +const idEventDef EV_Thread_SetSpawnVector( "setSpawnVector", "sv" ); +const idEventDef EV_Thread_ArcSine( "asin", "f", 'f' ); +const idEventDef EV_Thread_ArcCosine( "acos", "f", 'f' ); +const idEventDef EV_Thread_VecRotate( "vecRotate", "vv", 'v'); +const idEventDef EV_Thread_ClearSignalAllThreads( "clearSignalAllThreads", "de" ); +// bgeisler: added everything below the added block that was added by keith +const idEventDef EV_Thread_PlayWorldEffect( "playWorldEffect", "svv"); +// abahr: +const idEventDef EV_Thread_ReferenceScriptObjectProxy( "refProxy", "s", 'e' ); +const idEventDef EV_Thread_ReleaseScriptObjectProxy( "releaseProxy", "s" ); +const idEventDef EV_Thread_ClampFloat( "clampFloat", "fff", 'f' ); +const idEventDef EV_Thread_MinFloat( "minFloat", "ff", 'f' ); +const idEventDef EV_Thread_MaxFloat( "maxFloat", "ff", 'f' ); +const idEventDef EV_Thread_StrFind( "strFind", "ss", 'f' ); +const idEventDef EV_Thread_RandomInt( "randomInt", "f", 'f' ); +// rjohnson: new blur special effect +const idEventDef EV_Thread_SetSpecialEffect( "setSpecialEffect", "dd" ); +const idEventDef EV_Thread_SetSpecialEffectParm( "setSpecialEffectParm", "ddf" ); +// asalmon: award achievement +const idEventDef EV_Thread_AwardAchievement( "awardAchievement", "s" ); +// twhitaker: ceil, floor and intVal +const idEventDef EV_Thread_Ceil( "ceil", "f", 'f' ); +const idEventDef EV_Thread_Floor( "floor", "f", 'f' ); +const idEventDef EV_Thread_ToInt( "intVal", "f", 'f' ); +// jdischler: send named event string to specified gui +const idEventDef EV_Thread_SendNamedEvent( "sendNamedEvent", "ds" ); +// material streaming +const idEventDef EV_Thread_BeginManualStreaming( "beginManualStreaming" ); +const idEventDef EV_Thread_EndManualStreaming( "endManualStreaming" ); + +// nrausch: change material sort order on the fly +const idEventDef EV_Thread_SetMatSort( "setMatSort", "ss", 0 ); +// RAVEN END + +CLASS_DECLARATION( idClass, idThread ) + EVENT( EV_Thread_Execute, idThread::Event_Execute ) + EVENT( EV_Thread_TerminateThread, idThread::Event_TerminateThread ) + EVENT( EV_Thread_Pause, idThread::Event_Pause ) + EVENT( EV_Thread_Wait, idThread::Event_Wait ) + EVENT( EV_Thread_WaitFrame, idThread::Event_WaitFrame ) + EVENT( EV_Thread_WaitFor, idThread::Event_WaitFor ) + EVENT( EV_Thread_WaitForThread, idThread::Event_WaitForThread ) + EVENT( EV_Thread_Print, idThread::Event_Print ) + EVENT( EV_Thread_PrintLn, idThread::Event_PrintLn ) + EVENT( EV_Thread_Say, idThread::Event_Say ) + EVENT( EV_Thread_Assert, idThread::Event_Assert ) + EVENT( EV_Thread_Trigger, idThread::Event_Trigger ) + EVENT( EV_Thread_SetCvar, idThread::Event_SetCvar ) + EVENT( EV_Thread_GetCvar, idThread::Event_GetCvar ) + EVENT( EV_Thread_Random, idThread::Event_Random ) + EVENT( EV_Thread_GetTime, idThread::Event_GetTime ) + EVENT( EV_Thread_KillThread, idThread::Event_KillThread ) + EVENT( EV_Thread_SetThreadName, idThread::Event_SetThreadName ) + EVENT( EV_Thread_GetEntity, idThread::Event_GetEntity ) + EVENT( EV_Thread_Spawn, idThread::Event_Spawn ) + EVENT( EV_Thread_CopySpawnArgs, idThread::Event_CopySpawnArgs ) + EVENT( EV_Thread_SetSpawnArg, idThread::Event_SetSpawnArg ) + EVENT( EV_Thread_SpawnString, idThread::Event_SpawnString ) + EVENT( EV_Thread_SpawnFloat, idThread::Event_SpawnFloat ) + EVENT( EV_Thread_SpawnVector, idThread::Event_SpawnVector ) + EVENT( EV_Thread_ClearPersistantArgs, idThread::Event_ClearPersistantArgs ) + EVENT( EV_Thread_SetPersistantArg, idThread::Event_SetPersistantArg ) + EVENT( EV_Thread_GetPersistantString, idThread::Event_GetPersistantString ) + EVENT( EV_Thread_GetPersistantFloat, idThread::Event_GetPersistantFloat ) + EVENT( EV_Thread_GetPersistantVector, idThread::Event_GetPersistantVector ) + EVENT( EV_Thread_AngToForward, idThread::Event_AngToForward ) + EVENT( EV_Thread_AngToRight, idThread::Event_AngToRight ) + EVENT( EV_Thread_AngToUp, idThread::Event_AngToUp ) + EVENT( EV_Thread_Sine, idThread::Event_GetSine ) + EVENT( EV_Thread_Cosine, idThread::Event_GetCosine ) + EVENT( EV_Thread_SquareRoot, idThread::Event_GetSquareRoot ) + EVENT( EV_Thread_Normalize, idThread::Event_VecNormalize ) + EVENT( EV_Thread_VecLength, idThread::Event_VecLength ) + EVENT( EV_Thread_VecDotProduct, idThread::Event_VecDotProduct ) + EVENT( EV_Thread_VecCrossProduct, idThread::Event_VecCrossProduct ) + EVENT( EV_Thread_VecToAngles, idThread::Event_VecToAngles ) + EVENT( EV_Thread_OnSignal, idThread::Event_OnSignal ) + EVENT( EV_Thread_ClearSignal, idThread::Event_ClearSignalThread ) + EVENT( EV_Thread_SetCamera, idThread::Event_SetCamera ) + EVENT( EV_Thread_FirstPerson, idThread::Event_FirstPerson ) + EVENT( EV_Thread_Trace, idThread::Event_Trace ) + EVENT( EV_Thread_TracePoint, idThread::Event_TracePoint ) + EVENT( EV_Thread_GetTraceFraction, idThread::Event_GetTraceFraction ) + EVENT( EV_Thread_GetTraceEndPos, idThread::Event_GetTraceEndPos ) + EVENT( EV_Thread_GetTraceNormal, idThread::Event_GetTraceNormal ) + EVENT( EV_Thread_GetTraceEntity, idThread::Event_GetTraceEntity ) + EVENT( EV_Thread_GetTraceJoint, idThread::Event_GetTraceJoint ) + EVENT( EV_Thread_GetTraceBody, idThread::Event_GetTraceBody ) + EVENT( EV_Thread_FadeIn, idThread::Event_FadeIn ) + EVENT( EV_Thread_FadeOut, idThread::Event_FadeOut ) + EVENT( EV_Thread_FadeTo, idThread::Event_FadeTo ) + EVENT( EV_SetShaderParm, idThread::Event_SetShaderParm ) + EVENT( EV_Thread_StartMusic, idThread::Event_StartMusic ) + EVENT( EV_Thread_Warning, idThread::Event_Warning ) + EVENT( EV_Thread_Error, idThread::Event_Error ) + EVENT( EV_Thread_StrLen, idThread::Event_StrLen ) + EVENT( EV_Thread_StrLeft, idThread::Event_StrLeft ) + EVENT( EV_Thread_StrRight, idThread::Event_StrRight ) + EVENT( EV_Thread_StrSkip, idThread::Event_StrSkip ) + EVENT( EV_Thread_StrMid, idThread::Event_StrMid ) + EVENT( EV_Thread_StrToFloat, idThread::Event_StrToFloat ) + EVENT( EV_Thread_RadiusDamage, idThread::Event_RadiusDamage ) + EVENT( EV_Thread_IsClient, idThread::Event_IsClient ) + EVENT( EV_Thread_IsMultiplayer, idThread::Event_IsMultiplayer ) + EVENT( EV_Thread_GetFrameTime, idThread::Event_GetFrameTime ) + EVENT( EV_Thread_GetTicsPerSecond, idThread::Event_GetTicsPerSecond ) + EVENT( EV_CacheSoundShader, idThread::Event_CacheSoundShader ) + EVENT( EV_Thread_DebugLine, idThread::Event_DebugLine ) + EVENT( EV_Thread_DebugArrow, idThread::Event_DebugArrow ) + EVENT( EV_Thread_DebugCircle, idThread::Event_DebugCircle ) + EVENT( EV_Thread_DebugBounds, idThread::Event_DebugBounds ) + EVENT( EV_Thread_DrawText, idThread::Event_DrawText ) + EVENT( EV_Thread_InfluenceActive, idThread::Event_InfluenceActive ) + +// RAVEN BEGIN +// kfuller: added events + EVENT( EV_Thread_SetSpawnVector, idThread::Event_SetSpawnVector ) + EVENT( EV_Thread_ArcSine, idThread::Event_GetArcSine ) + EVENT( EV_Thread_ArcCosine, idThread::Event_GetArcCosine ) + EVENT( EV_Thread_VecRotate, idThread::Event_VecRotate ) + EVENT( EV_Thread_ClearSignalAllThreads, idThread::Event_ClearSignalAllThreads ) +// nmckenzie: added signal strings + EVENT( EV_Thread_PlayWorldEffect, idThread::Event_PlayWorldEffect ) +// abahr: + EVENT( EV_Thread_ReferenceScriptObjectProxy, idThread::Event_ReferenceScriptObjectProxy ) + EVENT( EV_Thread_ReleaseScriptObjectProxy, idThread::Event_ReleaseScriptObjectProxy ) + EVENT( EV_Thread_ClampFloat, idThread::Event_ClampFloat ) + EVENT( EV_Thread_MinFloat, idThread::Event_MinFloat ) + EVENT( EV_Thread_MaxFloat, idThread::Event_MaxFloat ) + EVENT( EV_Thread_StrFind, idThread::Event_StrFind ) + EVENT( EV_Thread_RandomInt, idThread::Event_RandomInt ) +// rjohnson: new blur special effect + EVENT( EV_Thread_SetSpecialEffect, idThread::Event_SetSpecialEffect ) + EVENT( EV_Thread_SetSpecialEffectParm, idThread::Event_SetSpecialEffectParm ) +// asalmon: award a Xenon achievement + EVENT( EV_Thread_AwardAchievement, idThread::Event_AwardAchievement ) +// twhitaker: ceil and floor + EVENT( EV_Thread_Ceil, idThread::Event_GetCeil ) + EVENT( EV_Thread_Floor, idThread::Event_GetFloor ) + EVENT( EV_Thread_ToInt, idThread::Event_ToInt ) +// jdischler: send named event string to specified gui + EVENT( EV_Thread_SendNamedEvent, idThread::Event_SendNamedEvent ) +// manual streaming + EVENT( EV_Thread_BeginManualStreaming, idThread::Event_BeginManualStreaming ) + EVENT( EV_Thread_EndManualStreaming, idThread::Event_EndManualStreaming ) + EVENT( EV_Thread_SetMatSort, idThread::Event_SetMatSort ) +// RAVEN END +END_CLASS + +idThread *idThread::currentThread = NULL; +int idThread::threadIndex = 0; +idList idThread::threadList; +trace_t idThread::trace; + +/* +================ +idThread::CurrentThread +================ +*/ +idThread *idThread::CurrentThread( void ) { + return currentThread; +} + +/* +================ +idThread::CurrentThreadNum +================ +*/ +int idThread::CurrentThreadNum( void ) { + if ( currentThread ) { + return currentThread->GetThreadNum(); + } else { + return 0; + } +} + +/* +================ +idThread::BeginMultiFrameEvent +================ +*/ +bool idThread::BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::BeginMultiFrameEvent called without a current thread" ); + } + return currentThread->interpreter.BeginMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::EndMultiFrameEvent +================ +*/ +void idThread::EndMultiFrameEvent( idEntity *ent, const idEventDef *event ) { + if ( !currentThread ) { + gameLocal.Error( "idThread::EndMultiFrameEvent called without a current thread" ); + } + currentThread->interpreter.EndMultiFrameEvent( ent, event ); +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread() { + Init(); + SetThreadName( va( "thread_%d", threadIndex ) ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idEntity *self, const function_t *func ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.EnterObjectFunction( self, func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( const function_t *func ) { + assert( func ); + + Init(); + SetThreadName( func->Name() ); + interpreter.EnterFunction( func, false ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, const function_t *func, int args ) { + Init(); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::idThread +================ +*/ +idThread::idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ) { + assert( self ); + + Init(); + SetThreadName( self->name ); + interpreter.ThreadCall( source, func, args ); + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: create thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } +} + +/* +================ +idThread::~idThread +================ +*/ +idThread::~idThread() { + idThread *thread; + int i; + int n; + + if ( g_debugScript.GetBool() ) { + gameLocal.Printf( "%d: end thread (%d) '%s'\n", gameLocal.time, threadNum, threadName.c_str() ); + } + threadList.Remove( this ); + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->WaitingOnThread() == this ) { + thread->ThreadCallback( this ); + } + } + + if ( currentThread == this ) { + currentThread = NULL; + } +} + +/* +================ +idThread::ManualDelete +================ +*/ +void idThread::ManualDelete( void ) { + interpreter.terminateOnExit = false; +} + +/* +================ +idThread::Save +================ +*/ +void idThread::Save( idSaveGame *savefile ) const { + savefile->WriteObject( currentThread ); + + savefile->WriteObject( waitingForThread ); + savefile->WriteInt( waitingFor ); + savefile->WriteInt( waitingUntil ); + interpreter.Save( savefile ); + + savefile->WriteDict( &spawnArgs ); + + savefile->WriteInt( threadNum ); + savefile->WriteString( threadName ); + + savefile->WriteInt( lastExecuteTime ); + savefile->WriteInt( creationTime ); + + savefile->WriteBool( manualControl ); + + savefile->WriteInt( threadIndex ); +} + +/* +================ +idThread::Restore +================ +*/ +void idThread::Restore( idRestoreGame *savefile ) { + savefile->ReadObject( reinterpret_cast( currentThread ) ); + + savefile->ReadObject( reinterpret_cast( waitingForThread ) ); + savefile->ReadInt( waitingFor ); + savefile->ReadInt( waitingUntil ); + interpreter.Restore( savefile ); + + savefile->ReadDict( &spawnArgs ); + + savefile->ReadInt( threadNum ); + savefile->ReadString( threadName ); + + savefile->ReadInt( lastExecuteTime ); + savefile->ReadInt( creationTime ); + + savefile->ReadBool( manualControl ); + + savefile->ReadInt( threadIndex ); +} + +/* +================ +idThread::Init +================ +*/ +void idThread::Init( void ) { + // create a unique threadNum + do { + threadIndex++; + if ( threadIndex == 0 ) { + threadIndex = 1; + } + } while( GetThread( threadIndex ) ); + + threadNum = threadIndex; + threadList.Append( this ); + + creationTime = gameLocal.time; + lastExecuteTime = 0; + manualControl = false; + + ClearWaitFor(); + + interpreter.SetThread( this ); +} + +/* +================ +idThread::GetThread +================ +*/ +idThread *idThread::GetThread( int num ) { + int i; + int n; + idThread *thread; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + thread = threadList[ i ]; + if ( thread->GetThreadNum() == num ) { + return thread; + } + } + + return NULL; +} + +/* +================ +idThread::DisplayInfo +================ +*/ +void idThread::DisplayInfo( void ) { + gameLocal.Printf( + "%12i: '%s'\n" + " File: %s(%d)\n" + " Created: %d (%d ms ago)\n" + " Status: ", + threadNum, threadName.c_str(), + interpreter.CurrentFile(), interpreter.CurrentLine(), + creationTime, gameLocal.time - creationTime ); + + if ( interpreter.threadDying ) { + gameLocal.Printf( "Dying\n" ); + } else if ( interpreter.doneProcessing ) { + gameLocal.Printf( + "Paused since %d (%d ms)\n" + " Reason: ", lastExecuteTime, gameLocal.time - lastExecuteTime ); + if ( waitingForThread ) { + gameLocal.Printf( "Waiting for thread #%3i '%s'\n", waitingForThread->GetThreadNum(), waitingForThread->GetThreadName() ); + } else if ( ( waitingFor != ENTITYNUM_NONE ) && ( gameLocal.entities[ waitingFor ] ) ) { + gameLocal.Printf( "Waiting for entity #%3i '%s'\n", waitingFor, gameLocal.entities[ waitingFor ]->name.c_str() ); + } else if ( waitingUntil ) { + gameLocal.Printf( "Waiting until %d (%d ms total wait time)\n", waitingUntil, waitingUntil - lastExecuteTime ); + } else { + gameLocal.Printf( "None\n" ); + } + } else { + gameLocal.Printf( "Processing\n" ); + } + + interpreter.DisplayInfo(); + + gameLocal.Printf( "\n" ); +} + +/* +================ +idThread::ListThreads_f +================ +*/ +void idThread::ListThreads_f( const idCmdArgs &args ) { + int i; + int n; + + n = threadList.Num(); + for( i = 0; i < n; i++ ) { + //threadList[ i ]->DisplayInfo(); + gameLocal.Printf( "%3i: %-20s : %s(%d)\n", threadList[ i ]->threadNum, threadList[ i ]->threadName.c_str(), threadList[ i ]->interpreter.CurrentFile(), threadList[ i ]->interpreter.CurrentLine() ); + } + gameLocal.Printf( "%d active threads\n\n", n ); +} + +/* +================ +idThread::Restart +================ +*/ +void idThread::Restart( void ) { + int i; + int n; + + // reset the threadIndex + threadIndex = 0; + + currentThread = NULL; + n = threadList.Num(); + for( i = n - 1; i >= 0; i-- ) { + delete threadList[ i ]; + } + threadList.Clear(); + + memset( &trace, 0, sizeof( trace ) ); + trace.c.entityNum = ENTITYNUM_NONE; +} + +/* +================ +idThread::DelayedStart +================ +*/ +void idThread::DelayedStart( int delay ) { + CancelEvents( &EV_Thread_Execute ); + if ( gameLocal.time <= 0 ) { + delay++; + } + PostEventMS( &EV_Thread_Execute, delay ); +} + +/* +================ +idThread::Start +================ +*/ +bool idThread::Start( void ) { + bool result; + + CancelEvents( &EV_Thread_Execute ); + result = Execute(); + + return result; +} + +/* +================ +idThread::SetThreadName +================ +*/ +void idThread::SetThreadName( const char *name ) { + threadName = name; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( int threadnum, idEntity *obj ) { + idThread *thread; + + if ( !threadnum ) { + return; + } + + thread = GetThread( threadnum ); + if ( thread ) { + thread->ObjectMoveDone( obj ); + } +} + +/* +================ +idThread::End +================ +*/ +void idThread::End( void ) { + // Tell thread to die. It will exit on its own. + Pause(); + interpreter.threadDying = true; +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( const char *name ) { + int i; + int num; + int len; + const char *ptr; + idThread *thread; + + // see if the name uses a wild card + ptr = strchr( name, '*' ); + if ( ptr ) { + len = ptr - name; + } else { + len = strlen( name ); + } + + // kill only those threads whose name matches name + num = threadList.Num(); + for( i = 0; i < num; i++ ) { + thread = threadList[ i ]; + if ( !idStr::Cmpn( thread->GetThreadName(), name, len ) ) { + thread->End(); + } + } +} + +/* +================ +idThread::KillThread +================ +*/ +void idThread::KillThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( thread ) { + // Tell thread to die. It will delete itself on it's own. + thread->End(); + } +} + +/* +================ +idThread::Execute +================ +*/ +bool idThread::Execute( void ) { + idThread *oldThread; + bool done; + + if ( manualControl && ( waitingUntil > gameLocal.time ) ) { + return false; + } + + oldThread = currentThread; + currentThread = this; + + lastExecuteTime = gameLocal.time; + ClearWaitFor(); + done = interpreter.Execute(); + if ( done ) { + End(); + if ( interpreter.terminateOnExit ) { + PostEventMS( &EV_Remove, 0 ); + } + } else if ( !manualControl ) { + if ( waitingUntil > lastExecuteTime ) { + PostEventMS( &EV_Thread_Execute, waitingUntil - lastExecuteTime ); + } else if ( interpreter.MultiFrameEventInProgress() ) { + PostEventMS( &EV_Thread_Execute, gameLocal.msec ); + } + } + + currentThread = oldThread; + + return done; +} + +/* +================ +idThread::IsWaiting + +Checks if thread is still waiting for some event to occur. +================ +*/ +bool idThread::IsWaiting( void ) { + if ( waitingForThread || ( waitingFor != ENTITYNUM_NONE ) ) { + return true; + } + + if ( waitingUntil && ( waitingUntil > gameLocal.time ) ) { + return true; + } + + return false; +} + +// RAVEN BEGIN +// bgeisler: +/* +================ +idThread::ListFunctions +================ +*/ +void idThread::ListStates(void) +{ + gameLocal.program.ListStates(); +} + +// abahr: added helper functions +/* +================ +idThread::ClearStack +================ +*/ +void idThread::ClearStack() { + interpreter.Reset(); +} + +/* +================ +idThread::PushInt +================ +*/ +void idThread::PushInt( int value ) { + interpreter.Push( value ); +} + +/* +================ +idThread::PushFloat +================ +*/ +void idThread::PushFloat( float value ) { + interpreter.Push( *(int*)&value ); +} + +/* +================ +idThread::PushVec3 +================ +*/ +void idThread::PushVec3( const idVec3& value ) { + for( int ix = 0; ix < value.GetDimension(); ++ix ) { + PushFloat( value[ix] ); + } +} + +/* +================ +idThread::PushEntity +================ +*/ +void idThread::PushEntity( const idEntity* ent ) { + assert( ent ); + + PushInt( ent->entityNumber + 1 ); +} + +/* +================ +idThread::PushString +================ +*/ +void idThread::PushString( const char* string ) { + interpreter.PushString( string ); +} + +/* +================ +idThread::PushBool +================ +*/ +void idThread::PushBool( bool value ) { + PushInt( (int)value ); +} +// RAVEN END + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( const function_t *func, bool clearStack ) { + ClearWaitFor(); + interpreter.EnterFunction( func, clearStack ); +} + +/* +================ +idThread::CallFunction + +NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. +================ +*/ +void idThread::CallFunction( idEntity *self, const function_t *func, bool clearStack ) { + assert( self ); + ClearWaitFor(); + interpreter.EnterObjectFunction( self, func, clearStack ); +} + +/* +================ +idThread::ClearWaitFor +================ +*/ +void idThread::ClearWaitFor( void ) { + waitingFor = ENTITYNUM_NONE; + waitingForThread = NULL; + waitingUntil = 0; +} + +/* +================ +idThread::IsWaitingFor +================ +*/ +bool idThread::IsWaitingFor( idEntity *obj ) { + assert( obj ); + return waitingFor == obj->entityNumber; +} + +/* +================ +idThread::ObjectMoveDone +================ +*/ +void idThread::ObjectMoveDone( idEntity *obj ) { + assert( obj ); + + if ( IsWaitingFor( obj ) ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::ThreadCallback +================ +*/ +void idThread::ThreadCallback( idThread *thread ) { + if ( interpreter.threadDying ) { + return; + } + + if ( thread == waitingForThread ) { + ClearWaitFor(); + DelayedStart( 0 ); + } +} + +/* +================ +idThread::Event_SetThreadName +================ +*/ +void idThread::Event_SetThreadName( const char *name ) { + SetThreadName( name ); +} + +/* +================ +idThread::Error +================ +*/ +void idThread::Error( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Error( text ); +} + +/* +================ +idThread::Warning +================ +*/ +void idThread::Warning( const char *fmt, ... ) const { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + interpreter.Warning( text ); +} + +/* +================ +idThread::ReturnString +================ +*/ +void idThread::ReturnString( const char *text ) { + gameLocal.program.ReturnString( text ); +} + +/* +================ +idThread::ReturnFloat +================ +*/ +void idThread::ReturnFloat( float value ) { + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnInt +================ +*/ +void idThread::ReturnInt( int value ) { + // true integers aren't supported in the compiler, + // so int values are stored as floats + gameLocal.program.ReturnFloat( value ); +} + +/* +================ +idThread::ReturnVector +================ +*/ +void idThread::ReturnVector( idVec3 const &vec ) { + gameLocal.program.ReturnVector( vec ); +} + +/* +================ +idThread::ReturnEntity +================ +*/ +// RAVEN BEGIN +// abahr: added const +void idThread::ReturnEntity( const idEntity *ent ) { + gameLocal.program.ReturnEntity( ent ); +} + +/* +================ +idThread::Event_Execute +================ +*/ +void idThread::Event_Execute( void ) { + Execute(); +} + +/* +================ +idThread::Pause +================ +*/ +void idThread::Pause( void ) { + ClearWaitFor(); + interpreter.doneProcessing = true; +} + +/* +================ +idThread::WaitMS +================ +*/ +void idThread::WaitMS( int time ) { + Pause(); +// RAVEN BEGIN +// bdube: add 1 ms to ensure a time of zero still works + waitingUntil = gameLocal.time + time + 1; +// RAVEN END +} + +/* +================ +idThread::WaitSec +================ +*/ +void idThread::WaitSec( float time ) { + WaitMS( SEC2MS( time ) ); +} + +/* +================ +idThread::WaitFrame +================ +*/ +void idThread::WaitFrame( void ) { + Pause(); + + // manual control threads don't set waitingUntil so that they can be run again + // that frame if necessary. + if ( !manualControl ) { + waitingUntil = gameLocal.time + gameLocal.msec; + } +} + +/*********************************************************************** + + Script callable events + +***********************************************************************/ + +/* +================ +idThread::Event_TerminateThread +================ +*/ +void idThread::Event_TerminateThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + KillThread( num ); +} + +/* +================ +idThread::Event_Pause +================ +*/ +void idThread::Event_Pause( void ) { + Pause(); +} + +/* +================ +idThread::Event_Wait +================ +*/ +void idThread::Event_Wait( float time ) { + WaitSec( time ); +} + +/* +================ +idThread::Event_WaitFrame +================ +*/ +void idThread::Event_WaitFrame( void ) { + WaitFrame(); +} + +/* +================ +idThread::Event_WaitFor +================ +*/ +void idThread::Event_WaitFor( idEntity *ent ) { + if ( ent && ent->RespondsTo( EV_Thread_SetCallback ) ) { + ent->ProcessEvent( &EV_Thread_SetCallback ); + if ( gameLocal.program.GetReturnedInteger() ) { + Pause(); + waitingFor = ent->entityNumber; + } + } +} + +/* +================ +idThread::Event_WaitForThread +================ +*/ +void idThread::Event_WaitForThread( int num ) { + idThread *thread; + + thread = GetThread( num ); + if ( !thread ) { + if ( g_debugScript.GetBool() ) { + // just print a warning and continue executing + Warning( "Thread %d not running", num ); + } + } else { + Pause(); + waitingForThread = thread; + } +} + +/* +================ +idThread::Event_Print +================ +*/ +void idThread::Event_Print( const char *text ) { + gameLocal.Printf( "%s", text ); +} + +/* +================ +idThread::Event_PrintLn +================ +*/ +void idThread::Event_PrintLn( const char *text ) { + gameLocal.Printf( "%s\n", text ); +} + +/* +================ +idThread::Event_Say +================ +*/ +void idThread::Event_Say( const char *text ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", text ) ); +} + +/* +================ +idThread::Event_Assert +================ +*/ +void idThread::Event_Assert( float value ) { +// RAVEN BEGIN +// jnewquist: assert with a useful callstack, since this is script +#ifdef _DEBUG + if ( !value ) { + Error("Script assert fired"); + } +#endif +// RAVEN END +} + +/* +================ +idThread::Event_Trigger +================ +*/ +void idThread::Event_Trigger( idEntity *ent ) { + if ( ent ) { + ent->Signal( SIG_TRIGGER ); + ent->ProcessEvent( &EV_Activate, gameLocal.GetLocalPlayer() ); + ent->TriggerGuis(); + } +} + +/* +================ +idThread::Event_SetCvar +================ +*/ +void idThread::Event_SetCvar( const char *name, const char *value ) const { + cvarSystem->SetCVarString( name, value ); +} + +/* +================ +idThread::Event_GetCvar +================ +*/ +void idThread::Event_GetCvar( const char *name ) const { + ReturnString( cvarSystem->GetCVarString( name ) ); +} + +/* +================ +idThread::Event_Random +================ +*/ +void idThread::Event_Random( float range ) const { + float result; + + result = gameLocal.random.RandomFloat(); + ReturnFloat( range * result ); +} + +// RAVEN BEGIN +// abahr: +/* +================ +idThread::Event_RandomInt +================ +*/ +void idThread::Event_RandomInt( float range ) const { + ReturnFloat( rvRandom::irand(0, range) ); +} + + +// rjohnson: new blur special effect +void idThread::Event_SetSpecialEffect( int Effect, int Enabled ) { + renderSystem->SetSpecialEffect( (ESpecialEffectType)Effect, !!Enabled ); +} + +void idThread::Event_SetSpecialEffectParm( int Effect, int Parm, float Value ) { + renderSystem->SetSpecialEffectParm( (ESpecialEffectType)Effect, Parm, Value ); +} + +// RAVEN END + +/* +================ +idThread::Event_GetTime +================ +*/ +void idThread::Event_GetTime( void ) { + ReturnFloat( MS2SEC( gameLocal.realClientTime ) ); +} + +/* +================ +idThread::Event_KillThread +================ +*/ +void idThread::Event_KillThread( const char *name ) { + KillThread( name ); +} + +/* +================ +idThread::Event_GetEntity +================ +*/ +void idThread::Event_GetEntity( const char *name ) { + int entnum; + idEntity *ent; + + assert( name ); + + if ( name[ 0 ] == '*' ) { + entnum = atoi( &name[ 1 ] ); + if ( ( entnum < 0 ) || ( entnum >= MAX_GENTITIES ) ) { + Error( "Entity number in string out of range." ); + } + ReturnEntity( gameLocal.entities[ entnum ] ); + } else { + ent = gameLocal.FindEntity( name ); + ReturnEntity( ent ); + } +} + +/* +================ +idThread::Event_Spawn +================ +*/ +void idThread::Event_Spawn( const char *classname ) { + idEntity *ent; + + spawnArgs.Set( "classname", classname ); + gameLocal.SpawnEntityDef( spawnArgs, &ent ); + ReturnEntity( ent ); + spawnArgs.Clear(); +} + +/* +================ +idThread::Event_CopySpawnArgs +================ +*/ +void idThread::Event_CopySpawnArgs( idEntity *ent ) { + spawnArgs.Copy( ent->spawnArgs ); +} + +/* +================ +idThread::Event_SetSpawnArg +================ +*/ +void idThread::Event_SetSpawnArg( const char *key, const char *value ) { + spawnArgs.Set( key, value ); +} + +// RAVEN BEGIN +// kfuller: added events + +/* +================ +idThread::Event_SetSpawnVector +================ +*/ +void idThread::Event_SetSpawnVector( const char *key, idVec3 &vec) { + spawnArgs.SetVector(key, vec); +} +// RAVEN END + +/* +================ +idThread::Event_SpawnString +================ +*/ +void idThread::Event_SpawnString( const char *key, const char *defaultvalue ) { + const char *result; + + spawnArgs.GetString( key, defaultvalue, &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_SpawnFloat +================ +*/ +void idThread::Event_SpawnFloat( const char *key, float defaultvalue ) { + float result; + + spawnArgs.GetFloat( key, va( "%f", defaultvalue ), result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_SpawnVector +================ +*/ +void idThread::Event_SpawnVector( const char *key, idVec3 &defaultvalue ) { + idVec3 result; + + spawnArgs.GetVector( key, va( "%f %f %f", defaultvalue.x, defaultvalue.y, defaultvalue.z ), result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_ClearPersistantArgs +================ +*/ +void idThread::Event_ClearPersistantArgs( void ) { + gameLocal.persistentLevelInfo.Clear(); +} + + +/* +================ +idThread::Event_SetPersistantArg +================ +*/ +void idThread::Event_SetPersistantArg( const char *key, const char *value ) { + gameLocal.persistentLevelInfo.Set( key, value ); +} + +/* +================ +idThread::Event_GetPersistantString +================ +*/ +void idThread::Event_GetPersistantString( const char *key ) { + const char *result; + + gameLocal.persistentLevelInfo.GetString( key, "", &result ); + ReturnString( result ); +} + +/* +================ +idThread::Event_GetPersistantFloat +================ +*/ +void idThread::Event_GetPersistantFloat( const char *key ) { + float result; + + gameLocal.persistentLevelInfo.GetFloat( key, "0", result ); + ReturnFloat( result ); +} + +/* +================ +idThread::Event_GetPersistantVector +================ +*/ +void idThread::Event_GetPersistantVector( const char *key ) { + idVec3 result; + + gameLocal.persistentLevelInfo.GetVector( key, "0 0 0", result ); + ReturnVector( result ); +} + +/* +================ +idThread::Event_AngToForward +================ +*/ +void idThread::Event_AngToForward( idAngles &ang ) { + ReturnVector( ang.ToForward() ); +} + +/* +================ +idThread::Event_AngToRight +================ +*/ +void idThread::Event_AngToRight( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, &vec ); + ReturnVector( vec ); +} + +/* +================ +idThread::Event_AngToUp +================ +*/ +void idThread::Event_AngToUp( idAngles &ang ) { + idVec3 vec; + + ang.ToVectors( NULL, NULL, &vec ); + ReturnVector( vec ); +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_GetArcSine +================ +*/ +void idThread::Event_GetArcSine( float sinValue ) { + ReturnFloat( asinf( sinValue ) ); +} + +/* +================ +idThread::Event_GetArcCosine +================ +*/ +void idThread::Event_GetArcCosine( float cosValue ) { + ReturnFloat( acosf( cosValue ) ); +} +// RAVEN END + +/* +================ +idThread::Event_GetSine +================ +*/ +void idThread::Event_GetSine( float angle ) { + ReturnFloat( idMath::Sin( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetCosine +================ +*/ +void idThread::Event_GetCosine( float angle ) { + ReturnFloat( idMath::Cos( DEG2RAD( angle ) ) ); +} + +/* +================ +idThread::Event_GetSquareRoot +================ +*/ +void idThread::Event_GetSquareRoot( float theSquare ) { + ReturnFloat( idMath::Sqrt( theSquare ) ); +} + +/* +================ +idThread::Event_VecNormalize +================ +*/ +void idThread::Event_VecNormalize( idVec3 &vec ) { + idVec3 n; + + n = vec; + n.Normalize(); + ReturnVector( n ); +} + +/* +================ +idThread::Event_VecLength +================ +*/ +void idThread::Event_VecLength( idVec3 &vec ) { + ReturnFloat( vec.Length() ); +} + +/* +================ +idThread::Event_VecDotProduct +================ +*/ +void idThread::Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnFloat( vec1 * vec2 ); +} + +/* +================ +idThread::Event_VecCrossProduct +================ +*/ +void idThread::Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ) { + ReturnVector( vec1.Cross( vec2 ) ); +} + +/* +================ +idThread::Event_VecToAngles +================ +*/ +void idThread::Event_VecToAngles( idVec3 &vec ) { + idAngles ang = vec.ToAngles(); + ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); +} + +// RAVEN BEGIN +// kef 12/2/02 -- made these into events + +void idThread::Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch) +{ + idVec3 vecResult; + +// VectorRotate3(vecToBeRotated, rotateHowMuch, vecResult); + gameLocal.Printf("'vecRotate' doesn't work -- I guess Keith should find a replacement for VectorRotate3 now...\n"); + ReturnVector(vecResult); +} + +// RAVEN END + +/* +================ +idThread::Event_OnSignal +================ +*/ +void idThread::Event_OnSignal( int signal, idEntity *ent, const char *func ) { + const function_t *function; + + assert( func ); + + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + function = gameLocal.program.FindFunction( func ); + if ( !function ) { + Error( "Function '%s' not found", func ); + } + + ent->SetSignal( ( signalNum_t )signal, this, function ); +} + +/* +================ +idThread::Event_ClearSignalThread +================ +*/ +void idThread::Event_ClearSignalThread( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignalThread( ( signalNum_t )signal, this ); +} + +// RAVEN BEGIN +// kfuller: added +/* +================ +idThread::Event_ClearSignalAllThreads +================ +*/ +void idThread::Event_ClearSignalAllThreads( int signal, idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + } + + if ( ( signal < 0 ) || ( signal >= NUM_SIGNALS ) ) { + Error( "Signal out of range" ); + } + + ent->ClearSignal(this, ( signalNum_t )signal); +} +// RAVEN END + +/* +================ +idThread::Event_SetCamera +================ +*/ +void idThread::Event_SetCamera( idEntity *ent ) { + if ( !ent ) { + Error( "Entity not found" ); + return; + } + +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( !ent->IsType( idCamera::GetClassType() ) ) { +// RAVEN END + Error( "Entity is not a camera" ); + return; + } + + gameLocal.SetCamera( ( idCamera * )ent ); +} + +/* +================ +idThread::Event_FirstPerson +================ +*/ +void idThread::Event_FirstPerson( void ) { + gameLocal.SetCamera( NULL ); +} + +/* +================ +idThread::Event_Trace +================ +*/ +void idThread::Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ) { + if ( mins == vec3_origin && maxs == vec3_origin ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); + } else { + gameLocal.TraceBounds( NULL, trace, start, end, idBounds( mins, maxs ), contents_mask, passEntity ); +// RAVEN END + } + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_TracePoint +================ +*/ +void idThread::Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( NULL, trace, start, end, contents_mask, passEntity ); +// RAVEN END + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceFraction +================ +*/ +void idThread::Event_GetTraceFraction( void ) { + ReturnFloat( trace.fraction ); +} + +/* +================ +idThread::Event_GetTraceEndPos +================ +*/ +void idThread::Event_GetTraceEndPos( void ) { + ReturnVector( trace.endpos ); +} + +/* +================ +idThread::Event_GetTraceNormal +================ +*/ +void idThread::Event_GetTraceNormal( void ) { + if ( trace.fraction < 1.0f ) { + ReturnVector( trace.c.normal ); + } else { + ReturnVector( vec3_origin ); + } +} + +/* +================ +idThread::Event_GetTraceEntity +================ +*/ +void idThread::Event_GetTraceEntity( void ) { + if ( trace.fraction < 1.0f ) { + ReturnEntity( gameLocal.entities[ trace.c.entityNum ] ); + } else { + ReturnEntity( ( idEntity * )NULL ); + } +} + +/* +================ +idThread::Event_GetTraceJoint +================ +*/ +void idThread::Event_GetTraceJoint( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + ReturnString( af->GetAnimator()->GetJointName( CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ) ) ); + return; + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_GetTraceBody +================ +*/ +void idThread::Event_GetTraceBody( void ) { + if ( trace.fraction < 1.0f && trace.c.id < 0 ) { + idAFEntity_Base *af = static_cast( gameLocal.entities[ trace.c.entityNum ] ); +// RAVEN BEGIN +// jnewquist: Use accessor for static class type + if ( af && af->IsType( idAFEntity_Base::GetClassType() ) && af->IsActiveAF() ) { +// RAVEN END + int bodyId = af->BodyForClipModelId( trace.c.id ); + idAFBody *body = af->GetAFPhysics()->GetBody( bodyId ); + if ( body ) { + ReturnString( body->GetName() ); + return; + } + } + } + ReturnString( "" ); +} + +/* +================ +idThread::Event_FadeIn +================ +*/ +void idThread::Event_FadeIn( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 0.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeOut +================ +*/ +void idThread::Event_FadeOut( idVec3 &color, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], 1.0f ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_FadeTo +================ +*/ +void idThread::Event_FadeTo( idVec3 &color, float alpha, float time ) { + idVec4 fadeColor; + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player ) { + fadeColor.Set( color[ 0 ], color[ 1 ], color[ 2 ], alpha ); + player->playerView.Fade(fadeColor, SEC2MS( time ) ); + } +} + +/* +================ +idThread::Event_SetShaderParm +================ +*/ +void idThread::Event_SetShaderParm( int parmnum, float value ) { + if ( ( parmnum < 0 ) || ( parmnum >= MAX_GLOBAL_SHADER_PARMS ) ) { + Error( "shader parm index (%d) out of range", parmnum ); + } + + gameLocal.globalShaderParms[ parmnum ] = value; +} + +/* +================ +idThread::Event_StartMusic +================ +*/ +void idThread::Event_StartMusic( const char *text ) { + soundSystem->PlayShaderDirectly( SOUNDWORLD_GAME, text ); +} + +/* +================ +idThread::Event_Warning +================ +*/ +void idThread::Event_Warning( const char *text ) { + Warning( "%s", text ); +} + +/* +================ +idThread::Event_Error +================ +*/ +void idThread::Event_Error( const char *text ) { + Error( "%s", text ); +} + +/* +================ +idThread::Event_StrLen +================ +*/ +void idThread::Event_StrLen( const char *string ) { + int len; + + len = strlen( string ); + idThread::ReturnInt( len ); +} + +/* +================ +idThread::Event_StrLeft +================ +*/ +void idThread::Event_StrLeft( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idStr result( string, 0, num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrRight +================ +*/ +void idThread::Event_StrRight( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( string ); + return; + } + + idThread::ReturnString( string + len - num ); +} + +/* +================ +idThread::Event_StrSkip +================ +*/ +void idThread::Event_StrSkip( const char *string, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( string ); + return; + } + + len = strlen( string ); + if ( len < num ) { + idThread::ReturnString( "" ); + return; + } + + idThread::ReturnString( string + num ); +} + +/* +================ +idThread::Event_StrMid +================ +*/ +void idThread::Event_StrMid( const char *string, int start, int num ) { + int len; + + if ( num < 0 ) { + idThread::ReturnString( "" ); + return; + } + + if ( start < 0 ) { + start = 0; + } + len = strlen( string ); + if ( start > len ) { + start = len; + } + + if ( start + num > len ) { + num = len - start; + } + + idStr result( string, start, start + num ); + idThread::ReturnString( result ); +} + +/* +================ +idThread::Event_StrToFloat( const char *string ) +================ +*/ +void idThread::Event_StrToFloat( const char *string ) { + float result; + + result = atof( string ); + idThread::ReturnFloat( result ); +} + +/* +================ +idThread::Event_RadiusDamage +================ +*/ +void idThread::Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ) { + gameLocal.RadiusDamage( origin, inflictor, attacker, ignore, ignore, damageDefName, dmgPower ); +} + +/* +================ +idThread::Event_IsClient +================ +*/ +void idThread::Event_IsClient( void ) { + idThread::ReturnFloat( gameLocal.isClient ); +} + +/* +================ +idThread::Event_IsMultiplayer +================ +*/ +void idThread::Event_IsMultiplayer( void ) { + idThread::ReturnFloat( gameLocal.isMultiplayer ); +} + +/* +================ +idThread::Event_GetFrameTime +================ +*/ +void idThread::Event_GetFrameTime( void ) { + idThread::ReturnFloat( MS2SEC( gameLocal.msec ) ); +} + +/* +================ +idThread::Event_GetTicsPerSecond +================ +*/ +void idThread::Event_GetTicsPerSecond( void ) { + idThread::ReturnFloat( gameLocal.GetMHz() ); +} + +/* +================ +idThread::Event_CacheSoundShader +================ +*/ +void idThread::Event_CacheSoundShader( const char *soundName ) { + declManager->FindSound( soundName ); +} + +/* +================ +idThread::Event_DebugLine +================ +*/ +void idThread::Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ) { + gameRenderWorld->DebugLine( idVec4( color.x, color.y, color.z, 0.0f ), start, end, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugArrow +================ +*/ +void idThread::Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ) { + gameRenderWorld->DebugArrow( idVec4( color.x, color.y, color.z, 0.0f ), start, end, size, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugCircle +================ +*/ +void idThread::Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ) { + gameRenderWorld->DebugCircle( idVec4( color.x, color.y, color.z, 0.0f ), origin, dir, radius, numSteps, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DebugBounds +================ +*/ +void idThread::Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ) { + gameRenderWorld->DebugBounds( idVec4( color.x, color.y, color.z, 0.0f ), idBounds( mins, maxs ), vec3_origin, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_DrawText +================ +*/ +void idThread::Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ) { + gameRenderWorld->DrawText( text, origin, scale, idVec4( color.x, color.y, color.z, 0.0f ), gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), align, SEC2MS( lifetime ) ); +} + +/* +================ +idThread::Event_InfluenceActive +================ +*/ +void idThread::Event_InfluenceActive( void ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( player && player->GetInfluenceLevel() ) { + idThread::ReturnInt( true ); + } else { + idThread::ReturnInt( false ); + } +} + +// RAVEN BEGIN +// kfuller: added events +/* +================ +idThread::Event_PlayWorldEffect +================ +*/ +void idThread::Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ) { + gameLocal.PlayEffect ( ( const idDecl * )declManager->FindEffect( effectName ), org, angle.ToMat3() ); +} + +// abahr: +/* +================ +idThread::Event_ReferenceScriptObjectProxy +================ +*/ +void idThread::Event_ReferenceScriptObjectProxy( const char* scriptObjectName ) { + ReturnEntity( gameLocal.ReferenceScriptObjectProxy(scriptObjectName) ); + Event_WaitFrame();// Needed because the constructor call is delayed by one frame +} + +/* +================ +idThread::Event_ReleaseScriptObjectProxy +================ +*/ +void idThread::Event_ReleaseScriptObjectProxy( const char* proxyName ) { + gameLocal.ReleaseScriptObjectProxy( proxyName ); + Event_WaitFrame();// Needed because the destructor call is delayed by one frame +} + +/* +================ +idThread::Event_ClampFloat +================ +*/ +void idThread::Event_ClampFloat( float min, float max, float val ) { + ReturnFloat( idMath::ClampFloat(min, max, val) ); +} + +/* +================ +idThread::Event_MinFloat +================ +*/ +void idThread::Event_MinFloat( float val1, float val2 ) { + ReturnFloat( Min(val1, val2) ); +} + +/* +================ +idThread::Event_MaxFloat +================ +*/ +void idThread::Event_MaxFloat( float val1, float val2 ) { + ReturnFloat( Max(val1, val2) ); +} + +/* +================ +idThread::Event_StrFind +================ +*/ +void idThread::Event_StrFind( idStr& sourceStr, idStr& subStr ) { + idThread::ReturnInt( sourceStr.Find(subStr.c_str()) ); +} + +// asalmon: award achievement +/* +================ +idThread::Event_AwardAchievement +================ +*/ +void idThread::Event_AwardAchievement( const char *name ) { +#ifdef _XENON + Live()->AwardAchievement(name); +#endif +} +// twhitaker: ceil, floor and intVal +/* +================ +idThread::Event_GetCeil +================ +*/ +void idThread::Event_GetCeil( float val ) { + ReturnFloat( idMath::Ceil( val ) ); +} + +/* +================ +idThread::Event_GetFloor +================ +*/ +void idThread::Event_GetFloor( float val ) { + ReturnFloat( idMath::Floor( val ) ); +} + +/* +================ +idThread::Event_ToInt +================ +*/ +void idThread::Event_ToInt( float val ) { + ReturnFloat( idMath::Ftoi( val ) ); +// ReturnFloat( idMath::FtoiFast( val ) ); +} + +// jdischler: send named event string to specified gui +/* +=============================== +idThread::Event_SendNamedEvent +=============================== +*/ +void idThread::Event_SendNamedEvent( int guiEnum, const char *namedEvent ) { + + typedef enum { + SNE_PLAYERHUD = 0, + SNE_CINEMATICHUD, + SNE_VEHICLECHUD, + SNE_CURSORHUD, + }; + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + switch( guiEnum ) { + case SNE_PLAYERHUD: + if ( player->hud ) { + player->hud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_CINEMATICHUD: + if ( player->cinematicHud ) { + player->cinematicHud->HandleNamedEvent( namedEvent ); + } + break; + case SNE_VEHICLECHUD: + // twhitaker: I'd really just rather use player->GetHud() ... which returns the vehicle hud if the player is in a vehicle + // but this way the scripter will be able to strictly enforce which hud the named event gets played on. See idPlayer::GetHud() + if ( player->vehicleController.GetVehicle() ) { + idUserInterface * hud = player->vehicleController.GetVehicle()->GetHud(); + + if ( hud ) { + hud->HandleNamedEvent( namedEvent ); + } + } + break; + case SNE_CURSORHUD: + if ( player->GetCursorGUI() ) { + player->GetCursorGUI()->HandleNamedEvent( namedEvent ); + } + break; + } +} + +/* +================ +idThread::Event_SetMatSort +================ +*/ +void idThread::Event_SetMatSort( const char *name, const char *val ) const { + const idMaterial *mat = declManager->FindMaterial( name ); + if ( mat ) { + int srt = SS_DECAL; + if ( idStr::Icmp( val, "SS_MIN" ) == 0 ) { + srt = SS_MIN; + } else if ( idStr::Icmp( val, "SS_SUBVIEW" ) == 0 ) { + srt = SS_SUBVIEW; + } else if ( idStr::Icmp( val, "SS_PREGUI" ) == 0 ) { + srt = SS_PREGUI; + } else if ( idStr::Icmp( val, "SS_GUI" ) == 0 ) { + srt = SS_GUI; + } else if ( idStr::Icmp( val, "SS_BAD" ) == 0 ) { + srt = SS_BAD; + } else if ( idStr::Icmp( val, "SS_OPAQUE" ) == 0 ) { + srt = SS_OPAQUE; + } else if ( idStr::Icmp( val, "SS_PORTAL_SKY" ) == 0 ) { + srt = SS_PORTAL_SKY; + } else if ( idStr::Icmp( val, "SS_DECAL" ) == 0 ) { + srt = SS_DECAL; + } else if ( idStr::Icmp( val, "SS_FAR" ) == 0 ) { + srt = SS_FAR; + } else if ( idStr::Icmp( val, "SS_MEDIUM" ) == 0 ) { + srt = SS_MEDIUM; + } else if ( idStr::Icmp( val, "SS_CLOSE" ) == 0 ) { + srt = SS_CLOSE; + } else if ( idStr::Icmp( val, "SS_ALMOST_NEAREST" ) == 0 ) { + srt = SS_ALMOST_NEAREST; + } else if ( idStr::Icmp( val, "SS_NEAREST" ) == 0 ) { + srt = SS_NEAREST; + } else if ( idStr::Icmp( val, "SS_POST_PROCESS" ) == 0 ) { + srt = SS_POST_PROCESS; + } + + mat->SetSort( srt ); + } +} + +// jdischler: Adding ability to create texture streams from scripts +/* +================ +idThread::Event_BeginManualStreaming +================ +*/ +void idThread::Event_BeginManualStreaming() +{ +} + +/* +================ +idThread::Event_EndManualStreaming +================ +*/ +void idThread::Event_EndManualStreaming() +{ +} +// RAVEN END diff --git a/src/mpgame/script/Script_Thread.h b/src/mpgame/script/Script_Thread.h new file mode 100644 index 0000000..aab7f1f --- /dev/null +++ b/src/mpgame/script/Script_Thread.h @@ -0,0 +1,388 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SCRIPT_THREAD_H__ +#define __SCRIPT_THREAD_H__ + +extern const idEventDef EV_Thread_Execute; +extern const idEventDef EV_Thread_SetCallback; +extern const idEventDef EV_Thread_TerminateThread; +extern const idEventDef EV_Thread_Pause; +extern const idEventDef EV_Thread_Wait; +extern const idEventDef EV_Thread_WaitFrame; +extern const idEventDef EV_Thread_WaitFor; +extern const idEventDef EV_Thread_WaitForThread; +extern const idEventDef EV_Thread_Print; +extern const idEventDef EV_Thread_PrintLn; +extern const idEventDef EV_Thread_Say; +extern const idEventDef EV_Thread_Assert; +extern const idEventDef EV_Thread_Trigger; +extern const idEventDef EV_Thread_SetCvar; +extern const idEventDef EV_Thread_GetCvar; +extern const idEventDef EV_Thread_Random; +extern const idEventDef EV_Thread_GetTime; +extern const idEventDef EV_Thread_KillThread; +extern const idEventDef EV_Thread_SetThreadName; +extern const idEventDef EV_Thread_GetEntity; +extern const idEventDef EV_Thread_Spawn; +extern const idEventDef EV_Thread_SetSpawnArg; +extern const idEventDef EV_Thread_SpawnString; +extern const idEventDef EV_Thread_SpawnFloat; +extern const idEventDef EV_Thread_SpawnVector; +extern const idEventDef EV_Thread_AngToForward; +extern const idEventDef EV_Thread_AngToRight; +extern const idEventDef EV_Thread_AngToUp; +extern const idEventDef EV_Thread_Sine; +extern const idEventDef EV_Thread_Cosine; +extern const idEventDef EV_Thread_Normalize; +extern const idEventDef EV_Thread_VecLength; +extern const idEventDef EV_Thread_VecDotProduct; +extern const idEventDef EV_Thread_VecCrossProduct; +extern const idEventDef EV_Thread_OnSignal; +extern const idEventDef EV_Thread_ClearSignal; +extern const idEventDef EV_Thread_SetCamera; +extern const idEventDef EV_Thread_FirstPerson; +extern const idEventDef EV_Thread_TraceFraction; +extern const idEventDef EV_Thread_TracePos; +extern const idEventDef EV_Thread_FadeIn; +extern const idEventDef EV_Thread_FadeOut; +extern const idEventDef EV_Thread_FadeTo; +extern const idEventDef EV_Thread_Restart; +extern const idEventDef EV_Thread_SetMatSort; + +// RAVEN BEGIN +// rjohnson: new blur special effect +extern const idEventDef EV_Thread_SetSpecialEffect; +extern const idEventDef EV_Thread_SetSpecialEffectParm; +// RAVEN END + +class idThread : public idClass { +private: + static idThread *currentThread; + + idThread *waitingForThread; + int waitingFor; + int waitingUntil; + idInterpreter interpreter; + + idDict spawnArgs; + + int threadNum; + idStr threadName; + + int lastExecuteTime; + int creationTime; + + bool manualControl; + + static int threadIndex; + static idList threadList; + + static trace_t trace; + + void Init( void ); + void Pause( void ); + + void Event_Execute( void ); + void Event_SetThreadName( const char *name ); + + // + // script callable Events + // + void Event_TerminateThread( int num ); + void Event_Pause( void ); + void Event_Wait( float time ); + void Event_WaitFrame( void ); + void Event_WaitFor( idEntity *ent ); + void Event_WaitForThread( int num ); + void Event_Print( const char *text ); + void Event_PrintLn( const char *text ); + void Event_Say( const char *text ); + void Event_Assert( float value ); + void Event_Trigger( idEntity *ent ); + void Event_SetCvar( const char *name, const char *value ) const; + void Event_GetCvar( const char *name ) const; + void Event_Random( float range ) const; + void Event_GetTime( void ); + void Event_KillThread( const char *name ); + void Event_GetEntity( const char *name ); + void Event_Spawn( const char *classname ); + void Event_CopySpawnArgs( idEntity *ent ); + void Event_SetSpawnArg( const char *key, const char *value ); + void Event_SpawnString( const char *key, const char *defaultvalue ); + void Event_SpawnFloat( const char *key, float defaultvalue ); + void Event_SpawnVector( const char *key, idVec3 &defaultvalue ); + void Event_ClearPersistantArgs( void ); + void Event_SetPersistantArg( const char *key, const char *value ); + void Event_GetPersistantString( const char *key ); + void Event_GetPersistantFloat( const char *key ); + void Event_GetPersistantVector( const char *key ); + void Event_AngToForward( idAngles &ang ); + void Event_AngToRight( idAngles &ang ); + void Event_AngToUp( idAngles &ang ); + void Event_GetSine( float angle ); + void Event_GetCosine( float angle ); + void Event_GetSquareRoot( float theSquare ); + void Event_VecNormalize( idVec3 &vec ); + void Event_VecLength( idVec3 &vec ); + void Event_VecDotProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecCrossProduct( idVec3 &vec1, idVec3 &vec2 ); + void Event_VecToAngles( idVec3 &vec ); + void Event_OnSignal( int signal, idEntity *ent, const char *func ); + void Event_ClearSignalThread( int signal, idEntity *ent ); + void Event_SetCamera( idEntity *ent ); + void Event_FirstPerson( void ); + void Event_Trace( const idVec3 &start, const idVec3 &end, const idVec3 &mins, const idVec3 &maxs, int contents_mask, idEntity *passEntity ); + void Event_TracePoint( const idVec3 &start, const idVec3 &end, int contents_mask, idEntity *passEntity ); + void Event_GetTraceFraction( void ); + void Event_GetTraceEndPos( void ); + void Event_GetTraceNormal( void ); + void Event_GetTraceEntity( void ); + void Event_GetTraceJoint( void ); + void Event_GetTraceBody( void ); + void Event_FadeIn( idVec3 &color, float time ); + void Event_FadeOut( idVec3 &color, float time ); + void Event_FadeTo( idVec3 &color, float alpha, float time ); + void Event_SetShaderParm( int parmnum, float value ); + void Event_StartMusic( const char *name ); + void Event_Warning( const char *text ); + void Event_Error( const char *text ); + void Event_StrLen( const char *string ); + void Event_StrLeft( const char *string, int num ); + void Event_StrRight( const char *string, int num ); + void Event_StrSkip( const char *string, int num ); + void Event_StrMid( const char *string, int start, int num ); + void Event_StrToFloat( const char *string ); + void Event_RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignore, const char *damageDefName, float dmgPower ); + void Event_IsClient( void ); + void Event_IsMultiplayer( void ); + void Event_GetFrameTime( void ); + void Event_GetTicsPerSecond( void ); + void Event_CacheSoundShader( const char *soundName ); + void Event_DebugLine( const idVec3 &color, const idVec3 &start, const idVec3 &end, const float lifetime ); + void Event_DebugArrow( const idVec3 &color, const idVec3 &start, const idVec3 &end, const int size, const float lifetime ); + void Event_DebugCircle( const idVec3 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const float lifetime ); + void Event_DebugBounds( const idVec3 &color, const idVec3 &mins, const idVec3 &maxs, const float lifetime ); + void Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ); + void Event_InfluenceActive( void ); + +// RAVEN BEGIN +// kfuller: added + void Event_SetSpawnVector( const char *key, idVec3 &vec ); + void Event_GetArcSine( float sinValue ); + void Event_GetArcCosine( float cosValue ); + void Event_ClearSignalAllThreads( int signal, idEntity *ent ); + void Event_VecRotate(idVec3 &vecToBeRotated, idVec3 &rotateHowMuch); + void Event_IsStringEmpty( const char *checkString ); + void Event_AnnounceToAI( const char *announcement); + void Event_ChangeCrossings(const char *originalType, const char *newType); + +// abahr: so we can fake having pure script objects + void Event_ReferenceScriptObjectProxy( const char* scriptObjectName ); + void Event_ReleaseScriptObjectProxy( const char* proxyName ); + void Event_ClampFloat( float min, float max, float val ); + void Event_MinFloat( float val1, float val2 ); + void Event_MaxFloat( float val1, float val2 ); + void Event_StrFind( idStr& sourceStr, idStr& subStr ); + void Event_RandomInt( float range ) const; + +// rjohnson: new blur special effect + void Event_SetSpecialEffect( int Effect, int Enabled ); + void Event_SetSpecialEffectParm( int Effect, int Parm, float Value ); + +// nmckenzie: string signaling + void Event_PlayWorldEffect( const char *effectName, idVec3 &org, idVec3 &angle ); +// asalmon: achievements for Xenon + void Event_AwardAchievement( const char *name); +// twhitaker: ceil, floor and intVal + void Event_GetCeil( float val ); + void Event_GetFloor( float val ); + void Event_ToInt( float val ); +// jdischler: send named event string to specified gui + void Event_SendNamedEvent( int guiEnum, const char *namedEvent ); + void Event_BeginManualStreaming( void ); + void Event_EndManualStreaming( void ); + void Event_SetMatSort( const char *name, const char *val ) const; +// RAVEN END + +public: + CLASS_PROTOTYPE( idThread ); + + idThread(); + idThread( idEntity *self, const function_t *func ); + idThread( const function_t *func ); + idThread( idInterpreter *source, const function_t *func, int args ); + idThread( idInterpreter *source, idEntity *self, const function_t *func, int args ); + + virtual ~idThread(); + + // tells the thread manager not to delete this thread when it ends + void ManualDelete( void ); + + // save games + void Save( idSaveGame *savefile ) const; // archives object for save game file + void Restore( idRestoreGame *savefile ); // unarchives object from save game file + + void EnableDebugInfo( void ) { interpreter.debug = true; }; + void DisableDebugInfo( void ) { interpreter.debug = false; }; + + void WaitMS( int time ); + void WaitSec( float time ); + void WaitFrame( void ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( const function_t *func, bool clearStack ); + + // NOTE: If this is called from within a event called by this thread, the function arguments will be invalid after calling this function. + void CallFunction( idEntity *obj, const function_t *func, bool clearStack ); + +// RAVEN BEGIN +// bgeisler: added way to list functions + void ListStates( void ); + +// abahr: added helper functions for pushing parms onto stack + void ClearStack( void ); + void PushInt( int value ); + void PushFloat( float value ); + void PushVec3( const idVec3& value ); + void PushEntity( const idEntity* ent ); + void PushString( const char* str ); + void PushBool( bool value ); +// RAVEN END + + void DisplayInfo( void ); + static idThread *GetThread( int num ); + static void ListThreads_f( const idCmdArgs &args ); + static void Restart( void ); + static void ObjectMoveDone( int threadnum, idEntity *obj ); + + static idList& GetThreads( void ); + + bool IsDoneProcessing( void ); + bool IsDying ( void ); + + void End( void ); + static void KillThread( const char *name ); + static void KillThread( int num ); + bool Execute( void ); + void ManualControl( void ) { manualControl = true; CancelEvents( &EV_Thread_Execute ); }; + void DoneProcessing( void ) { interpreter.doneProcessing = true; }; + void ContinueProcessing( void ) { interpreter.doneProcessing = false; }; + bool ThreadDying( void ) { return interpreter.threadDying; }; + void EndThread( void ) { interpreter.threadDying = true; }; + bool IsWaiting( void ); + void ClearWaitFor( void ); + bool IsWaitingFor( idEntity *obj ); + void ObjectMoveDone( idEntity *obj ); + void ThreadCallback( idThread *thread ); + void DelayedStart( int delay ); + bool Start( void ); + idThread *WaitingOnThread( void ); + void SetThreadNum( int num ); + int GetThreadNum( void ); + void SetThreadName( const char *name ); + const char *GetThreadName( void ); + + void Error( const char *fmt, ... ) const; + void Warning( const char *fmt, ... ) const; + + static idThread *CurrentThread( void ); + static int CurrentThreadNum( void ); + static bool BeginMultiFrameEvent( idEntity *ent, const idEventDef *event ); + static void EndMultiFrameEvent( idEntity *ent, const idEventDef *event ); + + static void ReturnString( const char *text ); + static void ReturnFloat( float value ); + static void ReturnInt( int value ); + static void ReturnVector( idVec3 const &vec ); +// RAVEN BEGIN +// abahr: added const + static void ReturnEntity( const idEntity *ent ); +// RAVEN END +}; + +/* +================ +idThread::WaitingOnThread +================ +*/ +ID_INLINE idThread *idThread::WaitingOnThread( void ) { + return waitingForThread; +} + +/* +================ +idThread::SetThreadNum +================ +*/ +ID_INLINE void idThread::SetThreadNum( int num ) { + threadNum = num; +} + +/* +================ +idThread::GetThreadNum +================ +*/ +ID_INLINE int idThread::GetThreadNum( void ) { + return threadNum; +} + +/* +================ +idThread::GetThreadName +================ +*/ +ID_INLINE const char *idThread::GetThreadName( void ) { + return threadName.c_str(); +} + +/* +================ +idThread::GetThreads +================ +*/ +ID_INLINE idList& idThread::GetThreads ( void ) { + return threadList; +} + +/* +================ +idThread::IsDoneProcessing +================ +*/ +ID_INLINE bool idThread::IsDoneProcessing ( void ) { + return interpreter.doneProcessing; +} + +/* +================ +idThread::IsDying +================ +*/ +ID_INLINE bool idThread::IsDying ( void ) { + return interpreter.threadDying; +} + +#endif /* !__SCRIPT_THREAD_H__ */ diff --git a/src/mpgame/spawner.cpp b/src/mpgame/spawner.cpp new file mode 100644 index 0000000..e188a08 --- /dev/null +++ b/src/mpgame/spawner.cpp @@ -0,0 +1,624 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +Spawner.cpp + +================ +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Game_local.h" +#include "spawner.h" +#include "vehicle/Vehicle.h" +#include "ai/AI.h" +#include "ai/AI_Manager.h" +#include "ai/AI_Util.h" + +const idEventDef EV_Spawner_RemoveNullActiveEntities( "removeNullActiveEntities" ); +const idEventDef EV_Spawner_NumActiveEntities( "numActiveEntities", "", 'd' ); +const idEventDef EV_Spawner_GetActiveEntity( "getActiveEntity", "d", 'e' ); + +CLASS_DECLARATION( idEntity, rvSpawner ) + EVENT( EV_Activate, rvSpawner::Event_Activate ) + EVENT( EV_Spawner_RemoveNullActiveEntities, rvSpawner::Event_RemoveNullActiveEntities ) + EVENT( EV_Spawner_NumActiveEntities, rvSpawner::Event_NumActiveEntities ) + EVENT( EV_Spawner_GetActiveEntity, rvSpawner::Event_GetActiveEntity ) +END_CLASS + +/* +============== +rvSpawner::Spawn +============== +*/ +void rvSpawner::Spawn( void ){ + GetPhysics()->SetContents( 0 ); + + // TEMP: read max_team_test until we can get it out of all the current maps + if ( !spawnArgs.GetInt ( "max_active", "4", maxActive ) ) { + if ( spawnArgs.GetInt ( "max_team_test", "4", maxActive ) ) { + gameLocal.Warning ( "spawner '%s' using outdated 'max_team_test', please change to 'max_active'", GetName() ); + } + } + + maxToSpawn = spawnArgs.GetInt( "count", "-1" ); + skipVisible = spawnArgs.GetBool ( "skipvisible", "1" ); + spawnWaves = spawnArgs.GetInt( "waves", "1" ); + spawnDelay = SEC2MS( spawnArgs.GetFloat( "delay", "2" ) ); + numSpawned = 0; + nextSpawnTime = 0; + + // Spawn waves has to be less than max active + if ( spawnWaves > maxActive ) { + spawnWaves = maxActive; + } + + FindSpawnTypes ( ); +} + +/* +============== +rvSpawner::Save +============== +*/ +void rvSpawner::Save ( idSaveGame *savefile ) const{ + savefile->WriteInt( numSpawned ); + savefile->WriteInt( maxToSpawn ); + savefile->WriteFloat( nextSpawnTime ); + savefile->WriteInt( maxActive ); + + int i; + savefile->WriteInt( currentActive.Num() ); + for( i = 0; i < currentActive.Num(); i++ ) { + currentActive[i].Save ( savefile ); + } + + savefile->WriteInt( spawnWaves ); + savefile->WriteInt( spawnDelay ); + savefile->WriteBool( skipVisible ); + + savefile->WriteInt( spawnPoints.Num() ); + for ( i = 0; i < spawnPoints.Num(); i++ ) { + spawnPoints[ i ].Save ( savefile ); + } + + savefile->WriteInt ( callbacks.Num() ); + for ( i = 0; i < callbacks.Num(); i ++ ) { + callbacks[i].ent.Save ( savefile ); + savefile->WriteString ( callbacks[i].event ); + } +} + +/* +============== +rvSpawner::Restore +============== +*/ +void rvSpawner::Restore ( idRestoreGame *savefile ){ + int num; + int i; + + savefile->ReadInt( numSpawned ); + savefile->ReadInt( maxToSpawn ); + savefile->ReadFloat( nextSpawnTime ); + savefile->ReadInt( maxActive ); + + savefile->ReadInt( num ); + currentActive.Clear ( ); + currentActive.SetNum( num ); + for( i = 0; i < num; i++ ) { + currentActive[i].Restore ( savefile ); + } + + savefile->ReadInt( spawnWaves ); + savefile->ReadInt( spawnDelay ); + savefile->ReadBool( skipVisible ); + + savefile->ReadInt( num ); + spawnPoints.SetNum( num); + for( i = 0; i < num; i ++ ) { + spawnPoints[i].Restore ( savefile ); + } + + savefile->ReadInt ( num ); + callbacks.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + callbacks[i].ent.Restore ( savefile ); + savefile->ReadString ( callbacks[i].event ); + } + + FindSpawnTypes (); +} + +/* +============== +rvSpawner::FindSpawnTypes + +Generate the list of classnames to spawn from the spawnArgs. Anything matching the +prefix "def_spawn" will be included in the list. +============== +*/ +void rvSpawner::FindSpawnTypes ( void ){ + const idKeyValue *kv; + for ( kv = spawnArgs.MatchPrefix( "def_spawn", NULL ); kv; kv = spawnArgs.MatchPrefix( "def_spawn", kv ) ) { + spawnTypes.Append ( kv->GetValue ( ) ); + + // precache decls + declManager->FindType( DECL_AF, kv->GetValue(), true, false ); + } +} + +/* +============== +rvSpawner::FindTargets +============== +*/ +void rvSpawner::FindTargets ( void ) { + int i; + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + trace_t tr; + + idEntity::FindTargets ( ); + + // Copy the relevant targets to the spawn point list (right now only target_null entities) + for ( i = targets.Num() - 1; i >= 0; i -- ) { + idEntity* ent; + ent = targets[i]; + if ( idStr::Icmp ( ent->spawnArgs.GetString ( "classname" ), "target_null" ) ) { + continue; + } + + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = ent; + + if( !spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + gameLocal.TraceBounds( this, tr, ent->GetPhysics()->GetOrigin(), ent->GetPhysics()->GetOrigin(), bounds, MASK_MONSTERSOLID, NULL ); + if ( gameLocal.entities[tr.c.entityNum] && !gameLocal.entities[tr.c.entityNum]->IsType ( idActor::GetClassType ( ) ) ) { + //drop a console warning here + gameLocal.Warning ( "Spawner '%s' can't spawn at point '%s', the monster won't fit.", GetName(), ent->GetName() ); + } + } + } +} + +/* +============== +rvSpawner::ValidateSpawnPoint +============== +*/ +bool rvSpawner::ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ){ + trace_t tr; + if( spawnArgs.GetBool("ignoreSpawnPointValidation") ) { + return true; + } + + gameLocal.TraceBounds( this, tr, origin, origin, bounds, MASK_MONSTERSOLID, NULL ); + return tr.fraction >= 1.0f; +} + +/* +============== +rvSpawner::AddSpawnPoint +============== +*/ +void rvSpawner::AddSpawnPoint ( idEntity* point ) { + idEntityPtr &entityPtr = spawnPoints.Alloc(); + entityPtr = point; + + // If there were no spawnPoints then start with the delay + if ( spawnPoints.Num () == 1 ) { + nextSpawnTime = gameLocal.time + spawnDelay; + } +} + +/* +============== +rvSpawner::RemoveSpawnPoint +============== +*/ +void rvSpawner::RemoveSpawnPoint ( idEntity* point ) { + int i; + for ( i = spawnPoints.Num()-1; i >= 0; i -- ) { + if ( spawnPoints[i] == point ) { + spawnPoints.RemoveIndex ( i ); + break; + } + } +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +void rvSpawner::AddCallback ( idEntity* owner, const idEventDef* ev ) { + spawnerCallback_t& callback = callbacks.Alloc ( ); + callback.event = ev->GetName ( ); + callback.ent = owner; +} + +/* +============== +rvSpawner::GetSpawnPoint +============== +*/ +idEntity *rvSpawner::GetSpawnPoint ( void ) { + idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 72 ) ); + idList< idEntityPtr > spawns; + int spawnIndex; + idEntity* spawnEnt; + + // Run through all spawnPoints and choose a random one. Each time a spawn point is excluded + // it will be removed from the list until there are no more items in the list. + for ( spawns = spawnPoints ; spawns.Num(); spawns.RemoveIndex ( spawnIndex ) ) { + spawnIndex = gameLocal.random.RandomInt ( spawns.Num() ); + spawnEnt = spawns[spawnIndex]; + + if ( !spawnEnt || !spawnEnt->GetPhysics() ) { + continue; + } + + // Check to see if something is in the way at this spawn point + if ( !ValidateSpawnPoint ( spawnEnt->GetPhysics()->GetOrigin(), bounds ) ) { + continue; + } + + // Skip the spawn point because its currently visible? + if ( skipVisible && gameLocal.GetLocalPlayer()->CanSee ( spawnEnt, true ) ) { + continue; + } + + // Found one! + return spawnEnt; + } + + return NULL; +} + +/* +============== +rvSpawner::GetSpawnType +============== +*/ +const char* rvSpawner::GetSpawnType ( idEntity* spawnPoint ) { + const idKeyValue* kv; + + if ( spawnPoint ) { + // If the spawn point has any "def_spawn" keys then they override the normal spawn keys + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", NULL ); + if ( kv ) { + const char* types [ MAX_SPAWN_TYPES ]; + int typeCount; + + for ( typeCount = 0; + typeCount < MAX_SPAWN_TYPES && kv; + kv = spawnPoint->spawnArgs.MatchPrefix ( "def_spawn", kv ) ) { + types [ typeCount++ ] = kv->GetValue ( ).c_str(); + } + + return types[ gameLocal.random.RandomInt( typeCount ) ]; + } + } + + // No spawn types? + if ( !spawnTypes.Num ( ) ) { + return ""; + } + + // Return from the spawners list of types + return spawnTypes[ gameLocal.random.RandomInt( spawnTypes.Num() ) ]; +} + +/* +============== +rvSpawner::CopyPrefixedSpawnArgs +============== +*/ +void rvSpawner::CopyPrefixedSpawnArgs( idEntity *src, const char *prefix, idDict &args ){ + const idKeyValue *kv = src->spawnArgs.MatchPrefix( prefix, NULL ); + while( kv ) { + args.Set( kv->GetKey().c_str() + idStr::Length( prefix ), kv->GetValue() ); + kv = src->spawnArgs.MatchPrefix( prefix, kv ); + } +} + +/* +============== +rvSpawner::SpawnEnt +============== +*/ +bool rvSpawner::SpawnEnt( void ){ + idDict args; + idEntity* spawnPoint; + idEntity* spawnedEnt; + const char* temp; + + // Find a spawn point to spawn the entity + spawnPoint = GetSpawnPoint ( ); + if( !spawnPoint ){ + return false; + } + + // No valid spawn types for this point + temp = GetSpawnType ( spawnPoint ); + if ( !temp || !*temp ) { + gameLocal.Warning ( "Spawner '%s' could not find any valid spawn types for spawn point '%s'", GetName(), spawnPoint->GetName() ); + return false; + } + + // Build the spawn parameters for the entity about to be spawned + args.Set ( "origin", spawnPoint->GetPhysics()->GetOrigin().ToString() ); + args.SetFloat ( "angle", spawnPoint->GetPhysics()->GetAxis().ToAngles()[YAW] ); + args.Set ( "classname", temp ); + args.SetBool ( "forceEnemy", spawnArgs.GetBool ( "auto_target", "1" ) ); + args.SetBool ( "faceEnemy", spawnArgs.GetBool ( "faceEnemy", "0" ) ); + + // Copy all keywords prefixed with "spawn_" to the entity being spawned. + CopyPrefixedSpawnArgs( this, "spawn_", args ); + if( spawnPoint != this ) { + CopyPrefixedSpawnArgs( spawnPoint, "spawn_", args ); + } + + // Spawn the entity + if ( !gameLocal.SpawnEntityDef ( args, &spawnedEnt ) ) { + return false; + } + + // Activate the spawned entity + spawnedEnt->ProcessEvent( &EV_Activate, this ); + + // Play a spawning effect if it has one - do we possibly want some script hooks in here? + gameLocal.PlayEffect ( spawnArgs, "fx_spawning", spawnPoint->GetPhysics()->GetOrigin(), idVec3(0,0,1).ToMat3() ); + + // script function for spawning guys + if( spawnArgs.GetString( "call", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + + // script function for the guy being spawned + if ( spawnArgs.GetString( "call_spawned", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( spawnedEnt, temp ); + } + + // Call all of our callbacks + int c; + for ( c = callbacks.Num() - 1; c >= 0; c-- ) { + if ( callbacks[c].ent ) { + callbacks[c].ent->ProcessEvent ( idEventDef::FindEvent ( callbacks[c].event ), this, spawnedEnt ); + } + } + + // Activate the spawn point entity when an enemy is spawned there and all of its targets + if( spawnPoint != this ){ + spawnPoint->ProcessEvent( &EV_Activate, spawnPoint ); + spawnPoint->ActivateTargets( spawnedEnt ); + + // One time use on this target? + if ( spawnPoint->spawnArgs.GetBool ( "remove" ) ) { + spawnPoint->PostEventMS ( &EV_Remove, 0 ); + } + } + + // Increment the total number spawned + numSpawned++; + + return true; +} + +/* +============== +rvSpawner::Think +============== +*/ +void rvSpawner::Think( void ){ + if ( thinkFlags & TH_THINK ) { + if( ActiveListChanged() ) {// If an entity has been removed and we have not been informed via Detach + nextSpawnTime = gameLocal.GetTime() + spawnDelay; + } + + CheckSpawn ( ); + } +} + +/* +============== +rvSpawner::CheckSpawn +============== +*/ +void rvSpawner::CheckSpawn ( void ) { + int count; + + // Any spawn points? + if ( !spawnPoints.Num ( ) ) { + return; + } + + // Is it time to spawn yet? + if ( nextSpawnTime == 0 || gameLocal.time < nextSpawnTime ) { + return; + } + + // Any left to spawn? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ){ + return; + } + + // Spawn in waves? + for ( count = 0; count < spawnWaves; count ++ ) { + // Too many active? + if( currentActive.Num() >= maxActive ) { + return; + } + + // Spawn a new entity + SpawnEnt ( ); + + // Are we at the limit now? + if ( maxToSpawn > -1 && numSpawned >= maxToSpawn ) { + CallScriptEvents( "script_used_up", this ); + PostEventMS ( &EV_Remove, 0 ); + break; + } + } + + // Dont spawn again until after the delay + nextSpawnTime = gameLocal.time + spawnDelay; +} + +/* +============== +rvSpawner::CallScriptEvents +============== +*/ +void rvSpawner::CallScriptEvents( const char* prefixKey, idEntity* parm ) { + if( !prefixKey || !prefixKey[0] ) { + return; + } + + rvScriptFuncUtility func; + for( const idKeyValue* kv = spawnArgs.MatchPrefix(prefixKey); kv; kv = spawnArgs.MatchPrefix(prefixKey, kv) ) { + if( !kv->GetValue().Length() ) { + continue; + } + + if( func.Init(kv->GetValue()) <= SFU_ERROR ) { + continue; + } + + func.InsertEntity( parm, 0 ); + func.CallFunc( &spawnArgs ); + } +} + +/* +============== +rvSpawner::ActiveListChanged +============== +*/ +bool rvSpawner::ActiveListChanged() { + int previousCount = currentActive.Num(); + + currentActive.RemoveNull(); + + return previousCount > currentActive.Num(); +} + +/* +============== +rvSpawner::Attach + +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Attach ( idEntity* ent ) { + currentActive.AddUnique( ent ); +} + +/* +============== +rvSpawner::Detach + +Detaches the given AI from the spawner which will free up an active spot for spawning. +Attach the given AI to the spawner. This will increase the active count of the spawner and +set the spawner pointer in the ai. +============== +*/ +void rvSpawner::Detach ( idEntity* ent ){ + currentActive.Remove( ent ); + + nextSpawnTime = gameLocal.GetTime() + spawnDelay; +} + +/* +============== +rvSpawner::Event_Activate +============== +*/ +void rvSpawner::Event_Activate ( idEntity *activator ) { + + // "trigger_only" spawners will attempt to spawn when triggered + if ( spawnArgs.GetBool ( "trigger_only" ) ) { + // Update next spawn time to follo CheckSpawn into thinking its time to spawn again + nextSpawnTime = gameLocal.time; + CheckSpawn ( ); + return; + } + + // If nextSpawnTime is zero then the spawner is currently deactivated + if ( nextSpawnTime == 0 ) { + // Start thinking + BecomeActive( TH_THINK ); + + // Allow immediate spawn + nextSpawnTime = gameLocal.time; + + // Spawn any ai targets and add them to the current count + ActivateTargets ( this ); + } else { + nextSpawnTime = 0; + BecomeInactive( TH_THINK ); + + // Remove the spawner if need be + if ( spawnArgs.GetBool ( "remove", "1" ) ) { + PostEventMS ( &EV_Remove, 0 ); + } + } +} + +/* +============== +rvSpawner::Event_RemoveNullActiveEntities +============== +*/ +void rvSpawner::Event_RemoveNullActiveEntities( void ) { + for( int ix = currentActive.Num() - 1; ix >= 0; --ix ) { + if( !currentActive[ix].IsValid() ) { + currentActive.RemoveIndex( ix ); + } + } +} + +/* +============== +rvSpawner::Event_NumActiveEntities +============== +*/ +void rvSpawner::Event_NumActiveEntities( void ) { + idThread::ReturnInt( currentActive.Num() ); +} + +/* +============== +rvSpawner::Event_GetActiveEntity +============== +*/ +void rvSpawner::Event_GetActiveEntity( int index ) { + idThread::ReturnEntity( (index < 0 || index >= currentActive.Num()) ? NULL : currentActive[index] ); +} + diff --git a/src/mpgame/spawner.h b/src/mpgame/spawner.h new file mode 100644 index 0000000..fa0cef1 --- /dev/null +++ b/src/mpgame/spawner.h @@ -0,0 +1,144 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +================ + +Spawner.h + +================ +*/ + +#ifndef __GAME_SPAWNER_H__ +#define __GAME_SPAWNER_H__ + +const int MAX_SPAWN_TYPES = 32; + +class rvSpawner; + +typedef void (*spawnerCallbackProc_t) ( rvSpawner* spawner, idEntity* spawned, int userdata ); + +typedef struct { + idEntityPtr ent; + idStr event; +} spawnerCallback_t; + +/* +=============================================================================== + + rvSpawner + +=============================================================================== +*/ +class rvSpawner : public idEntity { +public: + CLASS_PROTOTYPE( rvSpawner ); + + void Spawn ( void ); + void Think ( void ); + + void Attach ( idEntity* ent ); + void Detach ( idEntity* ent ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void AddSpawnPoint ( idEntity* point ); + void RemoveSpawnPoint ( idEntity* point ); + + int GetNumSpawnPoints ( void ) const; + int GetNumActive ( void ) const; + int GetMaxActive ( void ) const; + idEntity* GetSpawnPoint ( int index ); + + virtual void FindTargets ( void ); + bool ActiveListChanged ( void ); + + void CallScriptEvents ( const char* prefixKey, idEntity* parm ); + + void AddCallback ( idEntity* owner, const idEventDef* ev ); + +protected: + + int numSpawned; + int maxToSpawn; + float nextSpawnTime; + int maxActive; + idList< idEntityPtr > currentActive; + int spawnWaves; + int spawnDelay; + bool skipVisible; + idStrList spawnTypes; + + idList< idEntityPtr > spawnPoints; + + idList< spawnerCallback_t > callbacks; + + // Check to see if its time to spawn + void CheckSpawn ( void ); + + // Spawn a new entity + bool SpawnEnt ( void ); + + // Populate the spawnType list with the available spawn types + void FindSpawnTypes ( void ); + + // Get a random spawnpoint to spawn at + idEntity* GetSpawnPoint ( void ); + + // Get a random spawn type + const char* GetSpawnType ( idEntity* spawnPoint ); + + // Validate the given spawn point for spawning + bool ValidateSpawnPoint ( const idVec3 origin, const idBounds &bounds ); + + // Copy key/values from the given entity to the given dictionary using the specified prefix + void CopyPrefixedSpawnArgs ( idEntity *src, const char *prefix, idDict &args ); + +private: + + void Event_Activate ( idEntity *activator ); + void Event_RemoveNullActiveEntities( void ); + void Event_NumActiveEntities ( void ); + void Event_GetActiveEntity ( int index ); +}; + + +ID_INLINE int rvSpawner::GetNumSpawnPoints( void ) const { + return spawnPoints.Num ( ); +} + +ID_INLINE idEntity* rvSpawner::GetSpawnPoint( int index ) { + return spawnPoints[index]; +} + +ID_INLINE int rvSpawner::GetNumActive( void ) const { + return currentActive.Num(); +} + +ID_INLINE int rvSpawner::GetMaxActive( void ) const { + return maxActive; +} + +#endif // __GAME_SPAWNER_H__ diff --git a/src/mpgame/vehicle/Vehicle.cpp b/src/mpgame/vehicle/Vehicle.cpp new file mode 100644 index 0000000..af673d9 --- /dev/null +++ b/src/mpgame/vehicle/Vehicle.cpp @@ -0,0 +1,1706 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Effect.h" +#include "../ai/AI_Manager.h" +#include "../Projectile.h" + +#define VEHICLE_CRASH_DELAY 500 +#define VEHICLE_HAZARD_TIMEOUT 5000 +#define VEHICLE_LOCK_TIMEOUT 5000 + +const idEventDef EV_LaunchProjectiles( "launchProjectiles", "d" ); +const idEventDef EV_HUDShockWarningOff( ""); +const idEventDef EV_StalledRestart( "", "dd" ); +const idEventDef EV_GetViewAngles( "getViewAngles", NULL, 'v' ); + +CLASS_DECLARATION( idActor, rvVehicle ) + EVENT( EV_Door_Lock, rvVehicle::Event_Lock ) + EVENT( EV_Door_IsLocked, rvVehicle::Event_IsLocked ) + EVENT( AI_EnableMovement, rvVehicle::Event_EnableMovement ) + EVENT( AI_DisableMovement, rvVehicle::Event_DisableMovement ) + EVENT( EV_Player_EnableWeapon, rvVehicle::Event_EnableWeapon ) + EVENT( EV_Player_DisableWeapon, rvVehicle::Event_DisableWeapon ) + EVENT( AI_EnableClip, rvVehicle::Event_EnableClip ) + EVENT( AI_DisableClip, rvVehicle::Event_DisableClip ) + EVENT( EV_Activate, rvVehicle::Event_Activate ) + EVENT( EV_LaunchProjectiles, rvVehicle::Event_LaunchProjectiles ) + EVENT( AI_SetScript, rvVehicle::Event_SetScript ) + EVENT( AI_SetHealth, rvVehicle::Event_SetHealth ) + EVENT( EV_HUDShockWarningOff, rvVehicle::Event_HUDShockWarningOff ) + EVENT( EV_StalledRestart, rvVehicle::Event_StalledRestart ) + EVENT( EV_GetViewAngles, rvVehicle::Event_GetViewAngles ) +END_CLASS + +/* +===================== +rvVehicle::rvVehicle +===================== +*/ +rvVehicle::rvVehicle ( void ) { + autoRight = false; + hud = NULL; + shieldModel = NULL; + shieldMaxHealth = 0; + hazardWarningTime = 0; + lockWarningTime = 0; + godModeDamage = 0; + drivers = 0; + + autoCorrectionBegin = 0; + + crashEffect = 0; + crashTime = 0; + crashNextSound = 0; + + fl.networkSync = true; +} + +rvVehicle::~rvVehicle ( void ) { + int i; + + // Force all the drivers out + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver ( ); + if ( !driver ) { + continue; + } + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + } + + positions.Clear ( ); +} + +/* +================ +rvVehicle::Spawn +================ +*/ +void rvVehicle::Spawn( void ) { + const char* temp; + + memset ( &vfl, 0, sizeof(vfl) ); + + SetPositions ( ); + + healthMax = health; + healthLow = spawnArgs.GetInt ( "lowhealth", va("%d", health / 10 ) ); + damageStaticChance = spawnArgs.GetFloat ( "damageStaticChance", "0" ); + crashSpeedSmall = spawnArgs.GetFloat ( "crashSpeedSmall", "50" ); + crashSpeedMedium = spawnArgs.GetFloat ( "crashSpeedMedium", "125" ); + crashSpeedLarge = spawnArgs.GetFloat ( "crashSpeedLarge", "200" ); + crashDamage = spawnArgs.GetString ( "def_crashDamage" ); + + healthRegenDelay = SEC2MS(spawnArgs.GetFloat( "healthRegenDelay", "0" )); + healthRegenRate = spawnArgs.GetInt( "healthRegenRate", "0" ); + healthRegenAmount.Init( gameLocal.time, 0, healthMax, healthMax ); + + vfl.disableMovement = spawnArgs.GetBool ( "disableMovement", "0" ); + vfl.disableWeapons = spawnArgs.GetBool ( "disableWeapon", "0" ); + vfl.scripted = 0; + vfl.flipEject = spawnArgs.GetBool( "allowFlipEject", "1" ); + + health = spawnArgs.GetInt ( "health", "100" ); + fl.takedamage = ( health > 0 ); + + // Load the HUD + if ( NULL != ( temp = spawnArgs.GetString( "gui_hud", "" ) ) ) { + hud = uiManager->FindGui( temp, true, false, false ); + if ( hud ) { + hud->SetStateInt ( "vehicle_id", spawnArgs.GetInt ( "hudid" ) ); + hud->Activate( true, gameLocal.time ); + } + } + + // Get shield parameters + shieldMaxHealth = spawnArgs.GetInt ( "shieldHealth", "0" ); + shieldRegenTime = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenTime", "0" ) ); + shieldRegenDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenDelay", "0" ) ); + shieldHitTime = 0; + shieldHealth.Init ( gameLocal.time, 0, shieldMaxHealth, shieldMaxHealth ); + + SetCombatModel(); + + cachedContents = GetPhysics()->GetContents(); + + funcs.enter.Init( spawnArgs.GetString( "enter_script" ) ); + funcs.exit.Init( spawnArgs.GetString( "exit_script" ) ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::Save +================ +*/ +void rvVehicle::Save ( idSaveGame *savefile ) const { + int i; + + savefile->WriteInt ( positions.Num ( ) ); + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].Save ( savefile ); + } + savefile->WriteInt ( drivers ); + + savefile->WriteUserInterface( hud, false ); + + savefile->WriteFloat ( crashSpeedSmall ); + savefile->WriteFloat ( crashSpeedMedium ); + savefile->WriteFloat ( crashSpeedLarge ); + savefile->WriteString ( crashDamage ); + crashEffect.Save ( savefile ); + savefile->WriteInt ( crashNextSound ); + savefile->WriteInt ( crashTime ); + + savefile->WriteFloat ( autoRightDir ); + savefile->WriteBool ( autoRight ); + + savefile->WriteInt( autoCorrectionBegin ); + + savefile->Write( &vfl, sizeof( vfl ) ); + + savefile->WriteFloat ( damageStaticChance ); + + savefile->WriteFloat ( shieldMaxHealth ); + savefile->WriteInterpolate( shieldHealth ); + savefile->WriteInt ( shieldHitTime ); + savefile->WriteFloat ( shieldRegenTime ); + savefile->WriteInt ( shieldRegenDelay ); +// TOSAVE: idClipModel* shieldModel; + + savefile->WriteInt( healthRegenDelay ); + savefile->WriteInt( healthRegenRate ); + savefile->WriteInterpolate( healthRegenAmount ); + + savefile->WriteInt ( hazardWarningTime ); + savefile->WriteInt ( lockWarningTime ); + savefile->WriteInt ( healthMax ); + savefile->WriteInt ( healthLow ); + savefile->WriteInt ( godModeDamage ); + + savefile->WriteInt ( cachedContents ); + + funcs.enter.Save( savefile ); + funcs.exit.Save ( savefile ); + +// cnicholson: Don't save crash Velocities, they are assigned in Restore +// savefile->WriteFloat( crashVelocitySmall ); // cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityMedium );// cnicholson: Added unsaved var +// savefile->WriteFloat( crashVelocityLarge ); // cnicholson: Added unsaved var + +// TOSAVE: idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; + +} + +/* +================ +rvVehicle::Restore +================ +*/ +void rvVehicle::Restore ( idRestoreGame *savefile ) { + int i; + int num; + + savefile->ReadInt ( num ); + positions.Clear(); + positions.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + positions[i].Restore ( savefile ); + } + savefile->ReadInt ( drivers ); + + savefile->ReadUserInterface( hud, &spawnArgs ); + + savefile->ReadFloat ( crashSpeedSmall ); + savefile->ReadFloat ( crashSpeedMedium ); + savefile->ReadFloat ( crashSpeedLarge ); + savefile->ReadString ( crashDamage ); + crashEffect.Restore ( savefile ); + savefile->ReadInt ( crashNextSound ); + savefile->ReadInt ( crashTime ); + + savefile->ReadFloat ( autoRightDir ); + savefile->ReadBool ( autoRight ); + + savefile->ReadInt( (int&)autoCorrectionBegin ); + + savefile->Read( &vfl, sizeof( vfl ) ); + + savefile->ReadFloat ( damageStaticChance ); + + savefile->ReadFloat ( shieldMaxHealth ); + savefile->ReadInterpolate( shieldHealth ); + savefile->ReadInt ( shieldHitTime ); + savefile->ReadFloat ( shieldRegenTime ); + savefile->ReadInt ( shieldRegenDelay ); +// TORESTORE: idClipModel* shieldModel; + + savefile->ReadInt( healthRegenDelay ); + savefile->ReadInt( healthRegenRate ); + savefile->ReadInterpolate( healthRegenAmount ); + + savefile->ReadInt ( hazardWarningTime ); + savefile->ReadInt ( lockWarningTime ); + savefile->ReadInt ( healthMax ); + savefile->ReadInt ( healthLow ); + savefile->ReadInt ( godModeDamage ); + + savefile->ReadInt ( cachedContents ); + + funcs.enter.Restore ( savefile ); + funcs.exit.Restore ( savefile ); + + SetCombatModel ( ); + + crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); + crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); + crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); + + alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); + + // precache hard-coded entitydefs + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); + declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); +} + +/* +================ +rvVehicle::SetPositions +================ +*/ +void rvVehicle::SetPositions ( void ) { + int positionCount; + int index; + const idKeyValue* kv; + + // Count the positions first so we can allocate them all at once + positionCount = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + positionCount++; + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } + + // Every vehicle needs a def_position in it's def file + if ( positionCount == 0) { + //gameLocal.Error ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + gameLocal.Warning ( "Vehicle '%s' has no def_position entries.", name.c_str() ); + positionCount = 1; + spawnArgs.Set( "def_position", "vehicle_ai_null_position" ); + } + + // Initialize the positions + positions.SetNum ( positionCount ); + + // Initialize all of the positions + index = 0; + kv = spawnArgs.MatchPrefix( "def_position", NULL ); + while ( kv ) { + const idDict* dict; + + // Get the position dictionary + dict = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Initialize the position + positions[index++].Init ( this, *dict ); + + kv = spawnArgs.MatchPrefix( "def_position", kv ); + } +} + +/* +================ +rvVehicle::SetCombatModel +================ +*/ +void rvVehicle::SetCombatModel ( void ) { + idActor::SetCombatModel ( ); + + if ( shieldMaxHealth ) { + idBounds bounds; + bounds.Clear ( ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMins", "0 0 0" ) ); + bounds.AddPoint ( spawnArgs.GetVector ( "shieldMaxs", "0 0 0" ) ); + + if ( shieldModel ) { + shieldModel->Unlink(); + delete shieldModel; + shieldModel = NULL; + } + + //twhitaker: dodecahedron support + idStr shieldModelName; + if ( spawnArgs.GetString( "shieldModel", "", shieldModelName ) ) { + if ( shieldModelName.Length() && !shieldModelName.Icmp( "dodecahedron" ) ) { + idTraceModel trm; + trm.SetupDodecahedron ( GetPhysics()->GetBounds() ); + shieldModel = new idClipModel ( trm ); + } + } + //twhitaker: end + + if ( !shieldModel ) { + shieldModel = new idClipModel ( idTraceModel ( bounds, spawnArgs.GetInt ( "shieldSides", "6" ) ) ); + } + + shieldModel->SetOwner ( this ); + shieldModel->SetContents ( CONTENTS_SOLID ); + } else { + shieldModel = NULL; + } +} + +/* +================ +rvVehicle::LinkCombat +================ +*/ +void rvVehicle::LinkCombat ( void ) { + if ( fl.hidden ) { + return; + } + + if ( g_debugVehicle.GetInteger() == 1 && shieldModel ) { + collisionModelManager->DrawModel( shieldModel->GetCollisionModel(), renderEntity.origin, renderEntity.axis, vec3_origin, mat3_identity, 0.0f ); + } + + if ( shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 && HasDrivers ( ) ) { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + shieldModel->Link( this, 0, renderEntity.origin, renderEntity.axis ); +// RAVEN END + + if ( combatModel ) { + combatModel->Unlink ( ); + } + } else { +// RAVEN BEGIN +// ddynerman: multiple clip worlds + combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); +// RAVEN END + + if ( shieldModel ) { + shieldModel->Unlink ( ); + } + } +} + +/* +================ +rvVehicle::ClientPredictionThink +================ +*/ +void rvVehicle::ClientPredictionThink ( void ) { + Think ( ); +} + +/* +================ +rvVehicle::WriteToSnapshot +================ +*/ +void rvVehicle::WriteToSnapshot ( idBitMsgDelta &msg ) const { + int i; + // TODO: Check that this conditional write to delta message is OK + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].WriteToSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::ReadFromSnapshot +================ +*/ +void rvVehicle::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + int i; + for ( i = 0; i < positions.Num(); i ++ ) { + positions[i].ReadFromSnapshot ( msg ); + } +} + +/* +================ +rvVehicle::UpdateState +================ +*/ +void rvVehicle::UpdateState ( void ) { + rvVehiclePosition* pos; + pos = &positions[0]; + + vfl.forward = (pos->IsOccupied() && pos->mInputCmd.forwardmove > 0); + vfl.backward = (pos->IsOccupied() && pos->mInputCmd.forwardmove < 0); + vfl.right = (pos->IsOccupied() && pos->mInputCmd.rightmove > 0); + vfl.left = (pos->IsOccupied() && pos->mInputCmd.rightmove < 0); + vfl.driver = pos->IsOccupied(); + vfl.strafe = (pos->IsOccupied() && pos->mInputCmd.buttons & BUTTON_STRAFE ); +} + + +/* +================ +rvVehicle::Think +================ +*/ +void rvVehicle::Think ( void ) { + UpdateState(); + UpdateAnimState ( ); + UpdateIncomingProjectiles(); + + // If we are the current debug entity then output some info to the hud + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, this ) ) { + gameDebug.SetInt ( "vehicle", 1 ); + gameDebug.SetInt ( "shields", shieldHealth.GetCurrentValue(gameLocal.time) ); + gameDebug.SetInt ( "positions", positions.Num() ); + gameDebug.SetInt ( "drivers", drivers ); + } + + if ( g_debugVehicle.GetInteger() == 1 ) { + idMat3 flatAxis = idAngles(0,GetPhysics()->GetAxis().ToAngles().yaw,0).ToMat3(); + gameRenderWorld->DebugLine ( colorOrange, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[0] ); + gameRenderWorld->DebugLine ( colorYellow, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[1] ); + gameRenderWorld->DebugLine ( colorCyan, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() - 50.0f * flatAxis[2] ); + + gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); + gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); + gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); + } + +/* VEH_FIXME: where to put this? + // For engine glow + renderEntity.shaderParms[7] = mEngineStatus.GetCurrentValue ( gameLocal.time ); +*/ + + if( thinkFlags & TH_THINK ) { + int i; + + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPrePhysics( ); + } + + if( autoRight ) { + if( idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) < 30.0f ) { + GetPhysics()->SetLinearVelocity( vec3_origin ); + autoRight = false; + } else { + idVec3 upSpeed = -GetPhysics()->GetGravityNormal() * 72.0f; + idMat3 axis = GetPhysics()->GetAxis(); + + // Rotate around the forward axis + axis.RotateRelative ( 0, (renderEntity.axis.ToAngles().roll<0?180:-180) * MS2SEC(gameLocal.GetMSec()) * 1.5f ); + GetPhysics()->SetAxis ( axis ); + + // Move up to make room for the rotation + GetPhysics()->SetLinearVelocity( upSpeed ); + } + } + + // twhitaker: nothing special here + RunPrePhysics(); + + // Run the physics + RunPhysics(); + + // twhitaker: nothing special here + RunPostPhysics(); + + // Give each position a chance to think after physics has been run + vfl.godmode = false; + fl.notarget = false; + for( i = 0; i < positions.Num(); i++ ) { + positions[i].RunPostPhysics( ); + + // Include the eye origin into the bounds to ensure the driver + // is inside the render bounds when the vehicle is rendered. + if ( positions[i].IsOccupied ( ) ) { + AddToBounds ( positions[i].GetEyeOrigin ( ) ); + + // Transfer godmode from driving players + if ( positions[i].mDriver && positions[i].mDriver->IsType ( idPlayer::GetClassType() ) ) { + if ( static_cast(positions[i].mDriver.GetEntity())->godmode ) { + vfl.godmode = true; + } + } + + // Inherit notarget if our driver has it on + if ( positions[i].mDriver && positions[i].mDriver->fl.notarget ) { + fl.notarget = true; + } + } + } + + // If the vehicle is flipped then kick out all drivers + if ( HasDrivers() && IsFlipped ( ) && GetPhysics()->GetLinearVelocity ( ).LengthSqr() < (100.0f * 100.0f)) { + if ( spawnArgs.GetBool( "locked_flip_death", false ) ) { + Killed( this, this, 999999999, GetPhysics()->GetLinearVelocity(), 0 ); + } else if ( vfl.flipEject ) { + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver (); + if ( driver ) { + driver->ProcessEvent ( &AI_ExitVehicle, true ); + } + } + } + } + } + + // Regenerate the shield + if ( shieldMaxHealth && shieldHealth.GetCurrentValue(gameLocal.time) < shieldMaxHealth ) { + if ( gameLocal.time > shieldHitTime + shieldRegenDelay && shieldHealth.IsDone ( gameLocal.time ) ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldRecharge", SND_CHANNEL_BODY2, 0, false, NULL ); + + float regenTime = shieldHealth.GetCurrentValue(gameLocal.time); + regenTime /= (float)shieldMaxHealth; + regenTime = 1.0f - regenTime; + regenTime *= (float)shieldRegenTime; + shieldHealth.Init ( gameLocal.time, regenTime, shieldHealth.GetCurrentValue(gameLocal.time), shieldMaxHealth ); + } + } + + // Regenerate health, if needed + // do nothing if the vehicle is at full hit points + if (health < healthMax) + { + // also do nothing if we've been damaged less than healthRegenDelay seconds ago + if ( healthRegenRate && shieldHitTime + healthRegenDelay <= gameLocal.time ) + { + // do we need to start the interpolation? + if ( healthRegenAmount.IsDone( gameLocal.time )) + { + healthRegenAmount.Init( gameLocal.time, SEC2MS((float)(healthMax - health)/healthRegenRate), + health, healthMax ); + } + else // get our interpolated health value for the current time. + { + health = healthRegenAmount.GetCurrentValue( gameLocal.time ); + } + } + } + + // Check hazards + if ( hazardWarningTime && gameLocal.time > hazardWarningTime + VEHICLE_HAZARD_TIMEOUT ) { + hazardWarningTime = 0; + StartSound ( "snd_voiceSafe", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "info_safe" ); + } + } + + // Stop the crash effect if no collide happened this frame + if ( crashEffect && crashTime != gameLocal.time ) { + crashEffect->Stop ( ); + crashEffect = NULL; + } + + UpdateAnimation(); + + if ( thinkFlags & TH_UPDATEVISUALS ) { + Present(); + LinkCombat(); + } + + if (spawnArgs.GetBool("touchtriggers")) { + TouchTriggers(); + } +} + +/* +================ +rvVehicle::UpdateDrivers +================ +*/ +void rvVehicle::UpdateDrivers ( int delta ) { + int oldDrivers = drivers; + + drivers = idMath::ClampInt ( 0, positions.Num(), drivers + delta ); + + if ( drivers && !oldDrivers ) { + if ( fl.takedamage ) { + aiManager.AddTeammate ( this ); + } + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_enter", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } else if ( !drivers ) { + aiManager.RemoveTeammate ( this ); + + refSound.listenerId = entityNumber + 1; + + // script function for spawning guys + const char* temp; + if( spawnArgs.GetString( "call_exit", "", &temp ) && *temp ) { + gameLocal.CallFrameCommand ( this, temp ); + } + } +} + +/* +================ +rvVehicle::GetAxis +================ +*/ +const idMat3& rvVehicle::GetAxis( int id ) const { + return GetPhysics()->GetAxis( id ); +} + +/* +================ +rvVehicle::GetOrigin +================ +*/ +const idVec3& rvVehicle::GetOrigin( int id ) const { + return GetPhysics()->GetOrigin(); +} + +/* +================ +rvVehicle::GetEyePosition +================ +*/ +void rvVehicle::GetEyePosition ( int pos, idVec3& origin, idMat3& axis ) { + axis = positions[pos].GetEyeAxis ( ); + origin = positions[pos].GetEyeOrigin ( ); +} + +/* +================ +rvVehicle::GetDriverPosition +================ +*/ +void rvVehicle::GetDriverPosition ( int pos, idVec3& origin, idMat3& axis ) { + const rvVehiclePosition *position = GetPosition( pos ); + + position->GetDriverPosition( origin, axis ); +} + +/* +===================== +rvVehicle::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicle::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + idMat3 identity; + identity.Identity( ); + + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + + // mekberg: vehicle bounds are resized based on rotation but not rotated with the axis. Get transformed bounds. + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, identity ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +================ +rvVehicle::AddDriver + +Add a driver to the vehicle at the given position. Its possible that the given position is +occupied, in that case the driver will be assigned the closest valid position. If no position +can be found then (-1) will be returned, otherwise the position the driver is now driving will +be returned. +================ +*/ +int rvVehicle::AddDriver ( int position, idActor* driver ) { + int wraparound; + + // Ensure the given position is valid + if ( position < 0 || position >= positions.Num() ) { + gameLocal.Warning ( "position %d is invalid for vehicle '%s'", position, name.c_str() ); + return -1; + } + + wraparound = position; + + do { + // If the position isnt occupied then set the driver + if ( !positions[position].IsOccupied ( ) ) { + positions[position].SetDriver ( driver ); + + // Vehicle is on same team as driver + team = driver->team; + + UpdateDrivers ( 1 ); + + // The local player will hear all private sounds of the vehicle + if ( driver == gameLocal.GetLocalPlayer ( ) ) { + refSound.listenerId = driver->GetListenerId ( ); + } + + return position; + } + + // Check the next position, wrap around if necessary + position = (position + 1) % positions.Num(); + + } while ( wraparound != position ); + + return -1; +} + +/* +================ +rvVehicle::RemoveDriver + +Removes the given driver from the vehicle. This includes physically taking the driver out +of the vehicle and placing them back into the world and will follow all constraints that limit +the driver from exiting the vehicle. +================ +*/ +bool rvVehicle::RemoveDriver ( int position, bool force ) { + if ( !positions[position].EjectDriver ( force ) ) { + return false; + } + + UpdateDrivers ( -1 ); + return true; +} + +/* +================ +rvVehicle::EjectAllDrivers +================ +*/ +void rvVehicle::EjectAllDrivers( bool force ) { + for( int ix = positions.Num() - 1; ix >= 0; --ix ) { + if( !GetPosition(ix)->GetDriver() ) { + continue; + } + + GetPosition(ix)->GetDriver()->ProcessEvent( &AI_ExitVehicle, force ); + } +} + +/* +============ +rvVehicle::Damage +============ +*/ +void rvVehicle::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, int location ) { + int damage; + + if ( !fl.takedamage ) { + return; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); + if ( !damageDef ) { + gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); + return; + } + + if ( damageDef->GetBool( "ignore_vehicles", "0" ) ) { + return; + } + + damageDef->GetInt( "damage", "20", damage ); + damage *= damageScale; + + int save = HasDrivers() ? shieldHealth.GetCurrentValue ( gameLocal.time ) : 0; + + // If one of the drivers is the player, do the player HUD effects + for ( int i = 0; i < positions.Num(); i++ ) { + rvVehiclePosition & pos = positions[ i ]; + + if ( pos.mDriver.IsValid() && pos.mDriver->IsType( idPlayer::GetClassType() ) ) { + idPlayer & driver = static_cast< idPlayer & >( *pos.mDriver.GetEntity() ); + + if ( driver.GetHud() ) { + driver.GetHud()->HandleNamedEvent( "vehicleHit" ); + } + + if ( !stricmp( damageDef->GetString( "filter" ), "electrical" ) ) { + driver.GetHud()->HandleNamedEvent( "electricWarningOn" ); + PostEventMS( &EV_HUDShockWarningOff, spawnArgs.GetInt( "hud_shock_warning_time", "2500" ) ); + + if ( damage >= spawnArgs.GetInt( "electric_stall_damage", "20" ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect ( gameLocal.GetEffect( spawnArgs , "fx_electrical_stall" ), this, SEC2MS( spawnArgs.GetFloat ( "hud_shock_warning_time", "2.5" ) ) ); + effect->Play ( gameLocal.time, false ); + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall" ); + } + + vfl.stalled = true; + PostEventMS( &EV_StalledRestart, spawnArgs.GetInt( "electric_stall_delay", "3500" ), save, damage ); + } + } + } + } + + + shieldHitTime = gameLocal.time; + + // Shields off when there is no controller + if ( !damageDef->GetBool( "noShields", "0" ) ) + { + if ( save > 0 ) { + int shield; + save = Min( save, damage ); + damage -= save; + + shield = shieldHealth.GetCurrentValue ( gameLocal.time ) - save; + shieldHealth.Init ( gameLocal.time, 0, shield, shield ); + // always stop the sound because we may be playing the recharge just now. + StopSound ( SND_CHANNEL_BODY2, false ); + + // Looping warning sound for shield + if ( shield <= 0 && !vfl.stalled ) { + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + } + } + + // God Mode? + if ( vfl.godmode && !damageDef->GetBool( "noGod" ) ) { + godModeDamage += damage; + damage = 0; + } + + if ( !damage ) { + return; + } + + // Static on the gui when hit + if ( renderEntity.gui[0] && gameLocal.random.RandomFloat() < damageStaticChance ) { + renderEntity.gui[0]->HandleNamedEvent ( "shot_static" ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } + +// RAVEN BEGIN +// MCG - added damage over time + if ( !inDamageEvent ) { + if ( damageDef->GetFloat( "dot_duration" ) ) { + int endTime; + if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { + endTime = -1; + } else { + endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); + } + int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); + if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again + PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); + } + if ( damageDef->GetString( "fx_dot", NULL ) ) { + ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); + } + if ( damageDef->GetString( "snd_dot_start", NULL ) ) { + StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } +// RAVEN END + + health -= damage; + if ( health < 1 && damageDef->GetBool( "nonLethal" ) ) { + health = 1; + } + if ( health <= 0 ) { + // keep the driver from being killed if we're forcing undying state + if ( g_forceUndying.GetBool() && HasDrivers() ) { + idActor * driver = NULL; + for ( int i = 0; i < positions.Num(); i++ ) { + idActor * currentDriver = positions[ i ].GetDriver(); + if ( currentDriver && currentDriver->IsType( idPlayer::GetClassType() ) ) { + driver = currentDriver; + break; + } + } + + if ( driver ) { + health = 1; + Pain( inflictor, attacker, damage, dir, 0 ); + return; + } + } + + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, 0 ); + } +} + +/* +================ +rvVehicle::Killed +================ +*/ +void rvVehicle::Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + int i; + + lockWarningTime = 0; + hazardWarningTime = 0; + vfl.locked = false; + vfl.dead = true; + + // Try removing the vehicle from all lists it could be part of. + aiManager.RemoveTeammate ( this ); + + // Remove all drivers from the vehicle and kill them + for ( i = 0; i < positions.Num(); i ++ ) { + idActor* driver = positions[i].GetDriver(); + if ( !driver ) { + continue; + } + + // Dump the driver out of the vehicle and kill them + driver->health = 0; + driver->ProcessEvent ( &AI_ExitVehicle, true ); + driver->Killed( inflictor, attacker, damage, dir, location ); + } + + OnDeath(); + CheckDeathObjectives(); + + if ( spawnArgs.GetBool( "remove_on_death", "1" ) ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + + Hide ( ); + + PostEventMS( &EV_Remove, 0 ); + } +} + +/* +================ +rvVehicle::AddDamageEffect +================ +*/ +void rvVehicle::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { + // If there are still shields remaining then play a shield effect at the impact point + if ( HasDrivers() && shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 ) { + jointHandle_t joint = animator.GetJointHandle( spawnArgs.GetString( "fx_shield_joint", "" ) ); + idVec3 dir; + dir = collision.c.point - GetPhysics()->GetCenterMass (); + dir.NormalizeFast ( ); + if ( INVALID_JOINT == joint ) { + PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); + } else { + PlayEffect ( "fx_shield", joint, false, vec3_origin, true ); + } + } +} + +/* +================ +rvVehicle::Collide +================ +*/ +bool rvVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { + idVec3 dir; + float speed; + float collisionSelfDamage = 0.0f; + + dir = velocity; + speed = dir.Normalize(); + + // No collision effect when hitting a no impact surfac +// if ( collision.c.material && collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) { +// return false; +// } + + if ( vfl.dead ) { + StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); + if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); + } else { + gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); + } + Hide ( ); + PostEventMS( &EV_Remove, 0 ); + } + + if ( !alwaysImpactDamage ) { + if ( speed < crashSpeedSmall ) { + return false; + } + } + + // When colliding with the world play a collision effect + if ( collision.c.entityNum == ENTITYNUM_WORLD ) { + // TODO: MAterial types + if ( !crashEffect ) { + crashEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_crash" ), collision.c.point, collision.c.normal.ToMat3(), true ); + } + if ( crashEffect ) { + crashEffect->SetOrigin ( collision.c.point ); + crashEffect->SetAxis ( collision.c.normal.ToMat3() ); + crashEffect->Attenuate ( idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ) ); + } + } + + idStr collDmgDef = "damage_gev_collision_self"; + if ( gameLocal.time > crashNextSound ) { + + float dot = dir * collision.c.normal; + if( dot < -0.5f ) { + + // Crash impact sounds + if ( speed > crashSpeedLarge ) { + collisionSelfDamage += 50.0f; + StartSound ( "snd_crash_large", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedMedium ) { + collisionSelfDamage += 25.0f; + StartSound ( "snd_crash_medium", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + // damage = true; + } else if ( speed > crashSpeedSmall ) { + StartSound ( "snd_crash_small", SND_CHANNEL_ANY, 0, false, NULL ); + crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; + } + } + } + + crashTime = gameLocal.time; + float vel = GetPhysics()->GetLinearVelocity().Length(); + + if ( vel > crashVelocitySmall && crashDamage.Length ( ) ) { + idEntity* ent = gameLocal.entities[ collision.c.entityNum ]; + + if ( ent ) { + float f = vel > crashVelocityLarge ? 1.0f : idMath::Sqrt( vel - crashVelocityMedium ) * ( 1.0f / idMath::Sqrt( crashVelocityLarge - crashVelocityMedium ) ); + + // Now hurt him + if ( !( team != gameLocal.GetLocalPlayer()->team && ent->IsType( idPlayer::GetClassType() ) ) ) { + + const idDict* damageDef = gameLocal.FindEntityDefDict ( crashDamage, false ); + //MCG NOTE: This used to call vehicleController.GetDriver(), which was always NULL... + idActor* theDriver = positions[0].GetDriver(); + + if ( ent->fl.takedamage + && ent->health >= 0 + && !ent->spawnArgs.GetBool( "ignore_vehicle_damage" ) + && (vel > 100.0f || !ent->IsType(idPlayer::GetClassType())) ) { + //MCG NOTE: now that theDriver is being set correctly, this damage will get credited to the driver (as the attacker), unlike before + float dScale = f * ent->spawnArgs.GetFloat( "vehicle_damage_scale", "1" ); + ent->Damage( this, theDriver, dir, crashDamage, dScale, INVALID_JOINT ); + if ( vel > 100.0f ) { + //NOTE: we PURPOSELY override this here, so you don't take damage from slamming into damageable things, only invulnerable things... + collisionSelfDamage = ent->spawnArgs.GetFloat( "vehicle_impact_damage", "0" ); + collDmgDef = "damage_gev_collision"; + } + } + + // ApplyImpulse doesn't like it when you give it a null driver + // MCG: okay, now ApplyImpulse actually is getting called, + // but I'm only going to allow it on idMoveables since ApplyImpulse is + // rejected by idActors if it comes from an idActor and this code was + // never being called before. + if ( theDriver && damageDef && !ent->spawnArgs.GetBool("ignore_vehicle_push") && ent->IsType( idMoveable::GetClassType() ) ) { + float push = damageDef->GetFloat( "push" ) * speed / idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ); + idVec3 impulse = -push * f * collision.c.normal; + impulse[2] = push * f * 0.75f; + + // Send him flying + ent->ApplyImpulse( theDriver, collision.c.id, collision.c.point, impulse ); + + if ( g_debugVehicle.GetInteger() ) { + gameRenderWorld->DebugArrow ( colorGreen, collision.c.point, collision.c.point + impulse, 3, 10000 ); + } + } + } + } + } + if ( collisionSelfDamage ) { + idVec3 dmgDir = GetPhysics()->GetLinearVelocity()*-1.0f; + Damage( this, this, dmgDir, collDmgDef.c_str(), collisionSelfDamage, 0 ); + } + return false; +} + +/* +=============== +rvVehicle::Give +=============== +*/ +bool rvVehicle::Give( const char *statname, const char *value ) { + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= healthMax ) { + return false; + } + health = Min ( atoi( value ) + health, healthMax ); + } + + return true; +} + +/* +================ +rvVehicle::AutoRight +================ +*/ +void rvVehicle::AutoRight( idEntity* activator ) { + if( autoRight ) { + return; + } + + autoRight = true; + +/* + idVec3 vec = renderEntity.origin - activator->renderEntity.origin; + vec.Normalize(); + + float dot = DotProduct( vec, renderEntity.axis[ 1 ] ); + + if( dot < 0 ) + { + autoRightDir = -1.0f; + } + else + { + autoRightDir = 1.0f; + } +*/ +} + +/* +================ +rvVehicle::GetPositionIndex +================ +*/ +int rvVehicle::GetPositionIndex ( const rvVehiclePosition* position ) const { + int i; + for ( i = positions.Num() - 1; i >= 0; i -- ) { + if ( &positions[i] == position ) { + return i; + } + } + return -1; +} + +/* +================ +rvVehicle::UpdateHUD +================ +*/ +void rvVehicle::UpdateHUD ( int position, idUserInterface* gui ) { +// VEH_FIXME: godmode ? +/* + if ( mController && mController->GetDriver() && mController->GetDriver()->IsType ( idPlayer::Type ) ) + { + idPlayer* player = static_cast(mController->GetDriver()); + gui->State()->SetInt ( "vehicle_god", player->godmode && g_showGodDamage->integer ); + gui->State()->SetInt ( "vehicle_god_damage", godModeDamage ); + } + else + { + gui->State()->SetInt ( "vehicle_god", 0 ); + } +*/ + gui->SetStateFloat( "vehicle_health", health / spawnArgs.GetFloat( "health", "1" ) ); + gui->SetStateInt ( "vehicle_armor", 0 ); + gui->SetStateInt ( "vehicle_position", position ); + gui->SetStateFloat ( "vehicle_shield", (float)shieldHealth.GetCurrentValue(gameLocal.time) / (float)shieldMaxHealth ); + gui->SetStateInt ( "vehicle_haz", hazardWarningTime != 0 ); + gui->SetStateInt ( "vehicle_locked", IsLocked ( ) ); + + // Update position specific information + positions[position].UpdateHUD ( gui ); +} + +/* +================ +rvVehicle::UpdateCursorGUI +================ +*/ +void rvVehicle::UpdateCursorGUI ( int position, idUserInterface* gui ) { + positions[position].UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehicle::DrawHUD +================ +*/ +void rvVehicle::DrawHUD ( int position ) { + if ( !hud || gameLocal.GetLocalPlayer()->IsObjectiveUp() || gameLocal.GetLocalPlayer()->objectiveSystemOpen ) { + return; + } + + UpdateHUD ( position, hud ); + + hud->Redraw ( gameLocal.time ); +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueHazardWarning ( void ) { + if ( !hazardWarningTime ) { + StartSound ( "snd_voiceHazard", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_hazard" ); + } + } + + hazardWarningTime = gameLocal.time; +} + +/* +================== +rvVehicle::IssueHazardWarning +================== +*/ +void rvVehicle::IssueLockedWarning ( void ) { + if ( gameLocal.time > lockWarningTime + VEHICLE_LOCK_TIMEOUT ) { + StartSound ( "snd_voiceNoExit", SND_CHANNEL_VOICE, 0, false, NULL ); + + if ( renderEntity.gui[0] ) { + renderEntity.gui[0]->HandleNamedEvent ( "warning_locked" ); + } + + lockWarningTime = gameLocal.time; + } +} + +/* +================== +rvVehicle::Hide +================== +*/ +void rvVehicle::Hide ( void ) { + idActor::Hide ( ); + + GetPhysics()->SetContents( 0 ); + GetPhysics()->GetClipModel()->Unlink(); +} + +/* +================== +rvVehicle::Show +================== +*/ +void rvVehicle::Show ( void ) { + idActor::Show ( ); + + GetPhysics()->SetContents ( CONTENTS_BODY ); + GetPhysics()->GetClipModel()->Link(); +} + +/* +================== +rvVehicle::Lock +================== +*/ +void rvVehicle::Lock( void ) { + Event_Lock( true ); +} + +/* +================== +rvVehicle::Unlock +================== +*/ +void rvVehicle::Unlock( void ) { + Event_Lock( false ); +} + +/* +================== +rvVehicle::GuidedProjectileLocked +================== +*/ +void rvVehicle::GuidedProjectileIncoming( idGuidedProjectile * projectile ) { + if ( projectile ) { + incomingProjectiles.Insert( projectile ); + } +} + +/* +================== +rvVehicle::UpdateIncomingProjectiles +================== +*/ +void rvVehicle::UpdateIncomingProjectiles( void ) { + idGuidedProjectile * proj = NULL; + float dist = 0.0f; + + for( int i = incomingProjectiles.Num() - 1; i >= 0; i-- ) { + if ( !incomingProjectiles[ i ].IsValid() ) { + incomingProjectiles.RemoveIndex( i ); + continue; + } + + if ( proj ) { + float d = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + + if ( dist > d ) { + proj = incomingProjectiles[ i ]; + dist = d; + } + } else { + proj = incomingProjectiles[ i ]; + dist = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); + } + } + + if ( proj ) { + idVec3 localDir; + int length; + + GetPosition( 0 )->mEyeAxis.ProjectVector( proj->GetPhysics()->GetOrigin() - GetOrigin(), localDir ); + GetHud()->SetStateFloat ( "missiledir", localDir.ToAngles()[YAW] ); + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + + if ( !vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatUp" ); + } + + if ( emitter ) { + StartSound( "snd_incomingProjectile", SND_CHANNEL_BODY3, 0, false, &length ); + } + + vfl.missileWarningOn = true; + } + + if ( emitter ) { + //float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist / 100000.0f ) ); + //refSound.parms.volume = lerp * 0.6f + 0.5f; + //refSound.parms.frequencyShift = lerp * 0.4f + 0.8f; + //emitter->ModifySound ( SND_CHANNEL_ANY, &refSound.parms ); + } + } else if ( vfl.missileWarningOn ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "missileThreatDown" ); + } + + StopSound( SND_CHANNEL_BODY3, false ); + + vfl.missileWarningOn = false; + } +} + +/* +================== +rvVehicle::Event_Lock +================== +*/ +void rvVehicle::Event_Lock ( bool lock ) { + vfl.locked = lock; +} + +/* +================== +rvVehicle::Event_IsLocked +================== +*/ +void rvVehicle::Event_IsLocked ( void ) { + idThread::ReturnFloat( (float)IsLocked() ); +} + +/* +================== +rvVehicle::Event_EnableWeapon +================== +*/ +void rvVehicle::Event_EnableWeapon ( void ) { + vfl.disableWeapons = false; +} + +/* +================== +rvVehicle::Event_DisableWeapon +================== +*/ +void rvVehicle::Event_DisableWeapon ( void ) { + vfl.disableWeapons = true; +} + + +/* +================== +rvVehicle::Event_EnableMovement +================== +*/ +void rvVehicle::Event_EnableMovement( void ) { + vfl.disableMovement = false; +} + +/* +================== +rvVehicle::Event_DisableMovement +================== +*/ +void rvVehicle::Event_DisableMovement ( void ) { + vfl.disableMovement = true; +} + +/* +================== +rvVehicle::Event_EnableClip +================== +*/ +void rvVehicle::Event_EnableClip( void ) { + GetPhysics()->SetContents( cachedContents ); +} + +/* +================== +rvVehicle::Event_DisableClip +================== +*/ +void rvVehicle::Event_DisableClip( void ) { + cachedContents = GetPhysics()->GetContents(); + GetPhysics()->SetContents( 0 ); +} + +/* +================== +rvVehicle::Event_Activate +================== +*/ +void rvVehicle::Event_Activate( idEntity* activator ) { + RemoveNullTargets(); + + if( !targets.Num() && activator && activator->IsType(idPlayer::GetClassType()) ) { + static_cast( activator )->EnterVehicle( this ); + } +} + +/* +================== +rvVehicle::Event_LaunchProjectiles +================== +*/ +void rvVehicle::Event_LaunchProjectiles( const idList* parms ) { + int pos = -1; + + assert( parms && parms->Num() ); + + sscanf( (*parms)[0].c_str(), "%d", &pos ); + GetPosition( pos )->FireWeapon(); +} + +/* +================== +rvVehicle::Event_SetScript +================== +*/ +void rvVehicle::Event_SetScript( const char* scriptName, const char* funcName ) { + if ( !funcName || !funcName[0] ) { + return; + } + + // Set the associated script + if ( !idStr::Icmp ( scriptName, "enter" ) ) { + funcs.enter.Init( funcName ); + } else if ( !idStr::Icmp ( scriptName, "exit" ) ) { + funcs.exit.Init( funcName ); + } else { + gameLocal.Warning ( "unknown script '%s' specified on vehicle '%s'", scriptName, name.c_str() ); + } +} + +/* +================ +rvVehicle::Event_SetHealth +================ +*/ +void rvVehicle::Event_SetHealth ( float health ) { + this->health = health; +} + +/* +================ +rvVehicle::Event_HUDShockWarningOff +================ +*/ +void rvVehicle::Event_HUDShockWarningOff ( ) { + if ( GetHud() ) { + GetHud()->HandleNamedEvent( "electricWarningOff" ); + } +} + +/* +================ +rvVehicle::Event_StalledRestart +================ +*/ +void rvVehicle::Event_StalledRestart ( float shield, float damage ) { + vfl.stalled = false; + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + if ( renderEntity.gui[ 1 ] ) { + renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall_restart" ); + } + + // Looping warning sound for shield + if ( shield <= 0 ) { + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); + } + + // Play low health warning on transition to low health value + if ( health >= healthLow && health - damage < healthLow ) { + StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); + } +} + +/* +================ +rvVehicle::Event_GetViewAngles +================ +*/ +void rvVehicle::Event_GetViewAngles ( ) { + + idThread::ReturnVector( GetPosition( 0)->GetEyeAxis()[0]); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicle ) + STATE ( "Wait_Driver", rvVehicle::State_Wait_Driver ) +END_CLASS_STATES + +/* +================ +rvVehicle::State_Wait_Driver +================ +*/ +stateResult_t rvVehicle::State_Wait_Driver ( int blendFrames ) { + if ( !vfl.driver || vfl.stalled ) { + return SRESULT_WAIT; + } + + return SRESULT_DONE; +} diff --git a/src/mpgame/vehicle/Vehicle.h b/src/mpgame/vehicle/Vehicle.h new file mode 100644 index 0000000..fafeeeb --- /dev/null +++ b/src/mpgame/vehicle/Vehicle.h @@ -0,0 +1,440 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLE_H__ +#define __GAME_VEHICLE_H__ + +class rvVehiclePosition { +public: + + rvVehiclePosition( void ); + virtual ~rvVehiclePosition( void ); + + usercmd_t mInputCmd; + idAngles mInputAngles; + usercmd_t mOldInputCmd; + idAngles mOldInputAngles; + int mOldInputFlags; + + int mCurrentWeapon; + + idEntityPtr mDriver; + idEntityPtr mParent; + + rvVehiclePartList_t mParts; + rvVehiclePartList_t mWeapons; + + idVec3 mEyeOrigin; + idMat3 mEyeAxis; + + jointHandle_t mEyeJoint; + idVec3 mEyeOffset; + idMat3 mEyeJointTransform; + idAngles mDeltaEyeAxisScale; + int mEyeJointAxisMap[3]; + int mEyeJointDirMap[3]; + + idMat3 mAxisOffset; + + jointHandle_t mDriverJoint; + idVec3 mDriverOffset; + idMat3 mDriverJointTransform; + idAngles mDeltaDriverAxisScale; + int mDriverJointAxisMap[3]; + int mDriverJointDirMap[3]; + + idVec3 mExitPosOffset; + idMat3 mExitAxisOffset; + + idStr mDriverAnim; + + idStr mInternalSurface; + + struct positionFlags_s { + bool inputValid :1; + bool engine :1; + bool driverVisible :1; + bool depthHack :1; + bool internalView :1; + bool bindDriver :1; + bool stalled :1; + } fl; + + void Init ( rvVehicle* parent, const idDict& args ); + + int AddPart ( const idTypeInfo &classdef, const idDict& args ); + rvVehiclePart* GetPart ( int partIndex ); + + bool IsOccupied ( void ) const; + bool IsEngine ( void ) const; + + bool SetDriver ( idActor* driver ); + void SetInput ( const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( usercmd_t& cmd, idAngles& newAngles ) const; + + bool EjectDriver ( bool force = false ); + + void RunPrePhysics ( void ); + void RunPostPhysics ( void ); + + void SelectWeapon ( int weapon ); + + void UpdateHUD ( idUserInterface* gui ); + void UpdateCursorGUI ( idUserInterface* gui ); + void UpdateInternalView ( bool force = false ); + + virtual idMat3 GetAxis ( void ) const; + virtual idVec3 GetOrigin ( const idVec3& offset = vec3_zero ) const; + + const idVec3& GetEyeOrigin ( void ) const; + const idMat3& GetEyeAxis ( void ) const; + rvVehicle* GetParent ( void ) const; + idActor* GetDriver ( void ) const; + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + void GetPosition ( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const; + + void FireWeapon ( void ); + + rvVehicleWeapon * GetWeapon ( int weaponIndex ); + rvVehicleWeapon * GetActiveWeapon ( void ); + +private: + + void SetParts ( const idDict& args ); + void ActivateParts ( bool active ); + + int mSoundPart; + float mSoundMaxSpeed; +}; + +typedef struct rvVehicleFuncs_s { + rvScriptFuncUtility enter; // script to run when the vehicle becomes occupied + rvScriptFuncUtility exit; // script to run when the vehicle becomes unoccupied +} rvVehicleFuncs_t; + +class rvVehicle : public idActor { +public: + + CLASS_PROTOTYPE( rvVehicle ); + + rvVehicle ( void ); + ~rvVehicle ( void ); + + void Spawn ( void ); + void Think ( void ); + bool Give ( const char *statname, const char *value ); + + virtual void Hide ( void ); + virtual void Show ( void ); + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + virtual void AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { return false; } + + virtual const idMat3& GetAxis ( int id = 0 ) const; + virtual const idVec3& GetOrigin ( int id = 0 ) const; + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + virtual void EjectAllDrivers ( bool force = false ); + rvVehiclePosition* GetPosition ( int index ); + const rvVehiclePosition* GetPosition ( int index ) const; + int GetPositionIndex ( const rvVehiclePosition* position ) const; + + int GetNumPositions ( void ) const; + int GetNumDrivers ( void ) const; + bool HasOpenPositions ( void ) const; + + bool Collide ( const trace_t &collision, const idVec3 &velocity ); + + virtual void UpdateState (); + + float GetEngineOffTime ( void ) const; + float GetEngineOnTime ( void ) const; + + bool IsFlipped ( void ) const; + bool IsFrozen ( void ) const; + bool IsLocked ( void ) const; + bool HasDrivers ( void ) const; + + bool IsShootingEnabled ( void ) const; + bool IsMovementEnabled ( void ) const; + + void Lock ( void ); + void Unlock ( void ); + + void AutoRight ( idEntity* activator ); + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( int position, idVec3& origin, idMat3& axis ); + virtual void GetEyePosition ( int position, idVec3& origin, idMat3& axis ); + void DrawHUD ( int position ); + idUserInterface* GetHud ( void ); + void UpdateCursorGUI ( int position, idUserInterface* ui ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + void GetInput ( int position, usercmd_t& cmd, idAngles& newAngles ) const; + + void IssueHazardWarning ( void ); + void IssueLockedWarning ( void ); + + void AddToBounds ( const idVec3& vec ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ) {} + + float FocusLength ( void ) const { return spawnArgs.GetFloat("focusLength_enter", "60"); } + + bool IsAutoCorrecting ( void ) const { return autoCorrectionBegin != 0; } + bool IsStalled ( void ) const { return vfl.stalled; } + + void OnEnter ( void ) { funcs.enter.CallFunc( NULL ); fl.exitedVehicle=false; }; + void OnExit ( void ) { if ( !fl.exitedVehicle ) { fl.exitedVehicle=true; funcs.exit.CallFunc( NULL );} }; + + bool IsStrafing ( void ) const { return vfl.strafe; } +// void PlayAnim ( int channel, const char *name, int blendFrames ); + + virtual void GuidedProjectileIncoming( idGuidedProjectile * projectile ); +protected: + + void SelectWeapon ( int weapon ); + + void UpdateDrivers ( int delta ); + virtual void UpdateHUD ( int position, idUserInterface* gui ); + + void SetPositions ( void ); + + void SetCombatModel ( void ); + void LinkCombat ( void ); + + // twhitaker: + friend class rvVehicleDriver; + virtual void RunPrePhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + + idList positions; + int drivers; + + idUserInterface * hud; + + float crashSpeedSmall; + float crashSpeedMedium; + float crashSpeedLarge; + idStr crashDamage; + rvClientEffectPtr crashEffect; + int crashNextSound; + int crashTime; + + float autoRightDir; + bool autoRight; + + // twhitaker: for rvVehicleDriver, to avoid getting stuck. + unsigned autoCorrectionBegin; // time when obstacle avoidance state began (0 if not correcting) + + struct vehicleFlags_s { + bool forward :1; + bool backward :1; + bool left :1; + bool right :1; + bool driver :1; + bool locked :1; + bool godmode :1; + bool frozen :1; + bool disableWeapons :1; + bool disableMovement :1; + bool scripted :1; + bool endWithIdle :1; + bool flipEject :1; + bool dead :1; + bool strafe :1; + bool missileWarningOn :1; + bool stalled :1; + } vfl; + + float damageStaticChance; + + // Shields + float shieldMaxHealth; + idInterpolate shieldHealth; + int shieldHitTime; + float shieldRegenTime; + int shieldRegenDelay; + idClipModel* shieldModel; + + int healthRegenDelay; + int healthRegenRate; + idInterpolate healthRegenAmount; + + int hazardWarningTime; + int lockWarningTime; + int healthMax; + int healthLow; + int godModeDamage; + + int cachedContents; + + rvVehicleFuncs_t funcs; + + float crashVelocitySmall; + float crashVelocityMedium; + float crashVelocityLarge; + + bool alwaysImpactDamage; + + void UpdateIncomingProjectiles( void ); + +public: + idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; +private: + + void Event_Lock ( bool locked ); + void Event_IsLocked ( void ); + void Event_EnableWeapon ( void ); + void Event_DisableWeapon ( void ); + void Event_EnableMovement ( void ); + void Event_DisableMovement ( void ); + void Event_EnableClip ( void ); + void Event_DisableClip ( void ); + void Event_Activate ( idEntity* activator ); + void Event_LaunchProjectiles ( const idList* parms ); + void Event_SetScript ( const char* scriptName, const char* funcName ); + void Event_SetHealth ( float health ); + void Event_HUDShockWarningOff( void ); + void Event_StalledRestart ( float shield, float damage ); + void Event_GetViewAngles ( void ); + + virtual void OnDeath ( void ) { } + + stateResult_t State_Wait_Driver ( int blendFrames ); + + CLASS_STATES_PROTOTYPE( rvVehicle ); +}; + +/* +=============== +rvVehiclePosition inlines +=============== +*/ + +ID_INLINE bool rvVehiclePosition::IsOccupied ( void ) const { return mDriver.IsValid();} +ID_INLINE bool rvVehiclePosition::IsEngine ( void ) const { return fl.engine; } +ID_INLINE const idVec3& rvVehiclePosition::GetEyeOrigin ( void ) const { return mEyeOrigin; } +ID_INLINE const idMat3& rvVehiclePosition::GetEyeAxis ( void ) const { return mEyeAxis; } +ID_INLINE rvVehicle* rvVehiclePosition::GetParent ( void ) const { return mParent; } +ID_INLINE idActor* rvVehiclePosition::GetDriver ( void ) const { return mDriver; } +ID_INLINE rvVehiclePart* rvVehiclePosition::GetPart ( int partIndex ) { return mParts[partIndex]; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetWeapon ( int weaponIndex ){ return ( weaponIndex >= 0 && weaponIndex < mWeapons.Num() ) ? static_cast(mWeapons[weaponIndex]) : 0; } +ID_INLINE rvVehicleWeapon* rvVehiclePosition::GetActiveWeapon ( void ) { return GetWeapon( mCurrentWeapon ); } + +/* +=============== +rvVehicle inlines +=============== +*/ + +ID_INLINE bool rvVehicle::IsFlipped( void ) const { + return (( !vfl.flipEject ) ? false : + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) > 60.f || + idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().pitch ) > 60.0f )); +} + +ID_INLINE bool rvVehicle::IsFrozen( void ) const { + return vfl.frozen; +} + +ID_INLINE idUserInterface* rvVehicle::GetHud ( void ) { + return hud; +} + +ID_INLINE bool rvVehicle::IsLocked ( void ) const { + return vfl.locked || hazardWarningTime != 0; +} + +ID_INLINE rvVehiclePosition* rvVehicle::GetPosition ( int index ) { + return &positions[index]; +} + +ID_INLINE const rvVehiclePosition* rvVehicle::GetPosition ( int index ) const { + return &positions[index]; +} + +ID_INLINE void rvVehicle::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +ID_INLINE void rvVehicle::GetInput( int position, usercmd_t& cmd, idAngles& newAngles ) const { + GetPosition(position)->GetInput( cmd, newAngles ); +} + +ID_INLINE bool rvVehicle::HasDrivers ( void ) const { + return drivers > 0; +} + +ID_INLINE void rvVehicle::AddToBounds ( const idVec3& vec ) { + renderEntity.bounds.AddPoint ( (vec-renderEntity.origin) * GetPhysics()->GetAxis().Transpose() ); +} + +ID_INLINE bool rvVehicle::IsShootingEnabled ( void ) const { + return !vfl.disableWeapons; +} + +ID_INLINE bool rvVehicle::IsMovementEnabled ( void ) const { + return !vfl.disableMovement; +} + +ID_INLINE int rvVehicle::GetNumPositions ( void ) const { + return positions.Num(); +} + +ID_INLINE int rvVehicle::GetNumDrivers ( void ) const { + return drivers; +} + +ID_INLINE bool rvVehicle::HasOpenPositions ( void ) const { + return GetNumPositions() > GetNumDrivers(); +} + +#endif // __GAME_VEHICLE_H__ diff --git a/src/mpgame/vehicle/VehicleAnimated.cpp b/src/mpgame/vehicle/VehicleAnimated.cpp new file mode 100644 index 0000000..700ce4f --- /dev/null +++ b/src/mpgame/vehicle/VehicleAnimated.cpp @@ -0,0 +1,260 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleAnimated ) +END_CLASS + +rvVehicleAnimated::rvVehicleAnimated ( void ) { +} + +rvVehicleAnimated::~rvVehicleAnimated ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleAnimated::Spawn +================ +*/ +void rvVehicleAnimated::Spawn( void ) { + + turnRate = spawnArgs.GetFloat ( "turnRate", "90" ); + viewAngles = GetPhysics()->GetAxis ( ).ToAngles ( ); + viewAxis = viewAngles.ToMat3(); + + // Initialize the physics object + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetMaxStepHeight( spawnArgs.GetFloat ( "stepheight", "14" ) ); + + // Start just a tad above the floor + physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) ); + physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) ); + physicsObj.SetDelta( vec3_origin ); + + // Gravity + idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" ); + gravity *= g_gravity.GetFloat ( ); + physicsObj.SetGravity( gravity ); + SetPhysics( &physicsObj ); + + animator.RemoveOriginOffset( true ); + + additionalDelta.Zero(); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleAnimated::ClientPredictionThink +================ +*/ +void rvVehicleAnimated::ClientPredictionThink ( void ) { + Think(); +} + +/* +================ +rvVehicleAnimated::Think +================ +*/ +void rvVehicleAnimated::Think ( void ) { + + rvVehicle::Think(); + + float rate = 0.0f; + usercmd_t& cmd = positions[0].mInputCmd; + idVec3 delta; + + if( positions[0].IsOccupied() && !IsFrozen() && IsMovementEnabled() ) { + + if (( g_vehicleMode.GetInteger() == 0 && !( cmd.buttons & BUTTON_STRAFE )) || // If we're in the old driving mode and we aren't strafing or... + ( g_vehicleMode.GetInteger() != 0 && ( cmd.buttons & BUTTON_STRAFE ))) // If we're in the new driving mode and we are strafing + { + rate = SignZero(cmd.forwardmove) * turnRate; + rate *= idMath::MidPointLerp( 0.0f, 0.9f, 1.0f, idMath::Fabs(cmd.rightmove) / 127.0f ); + viewAngles.yaw += Sign(cmd.rightmove) * rate * MS2SEC(gameLocal.GetMSec()); + } + } + + viewAngles.Normalize360(); + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + delta += additionalDelta; + + idStr alignmentJoint; + if ( g_vehicleMode.GetInteger() != 0 && spawnArgs.GetString( "alignment_joint", 0, alignmentJoint ) ) { + idVec3 offset; + idMat3 axis; + GetJointWorldTransform( animator.GetJointHandle( alignmentJoint ), gameLocal.time, offset, axis ); + delta *= axis; + } else { + viewAxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis(); + delta *= viewAxis; + } + + physicsObj.SetDelta( delta ); + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetAxis +================ +*/ +const idMat3& rvVehicleAnimated::GetAxis( int id ) const { + return viewAxis; +} + +/* +================ +rvVehicleAnimated::WriteToSnapshot +================ +*/ +void rvVehicleAnimated::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot ( msg ); + + physicsObj.WriteToSnapshot( msg ); + msg.WriteFloat( viewAngles[0] ); + msg.WriteFloat( viewAngles[1] ); + msg.WriteFloat( viewAngles[2] ); +} + +/* +================ +rvVehicleAnimated::ReadFromSnapshot +================ +*/ +void rvVehicleAnimated::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot ( msg ); + + physicsObj.ReadFromSnapshot( msg ); + viewAngles[0] = msg.ReadFloat( ); + viewAngles[1] = msg.ReadFloat( ); + viewAngles[2] = msg.ReadFloat( ); +} + +/* +================ +rvVehicleAnimated::Save +================ +*/ +void rvVehicleAnimated::Save ( idSaveGame *savefile ) const { + savefile->WriteVec3( storedPosition ); + + savefile->WriteStaticObject ( physicsObj ); + + savefile->WriteAngles ( viewAngles ); + savefile->WriteFloat ( turnRate ); + + // why are we writing out the viewAxis here??? + savefile->WriteMat3 ( viewAxis ); + + // TEMP + animator.Save( savefile ); +} + +/* +================ +rvVehicleAnimated::Restore +================ +*/ +void rvVehicleAnimated::Restore ( idRestoreGame *savefile ) { + savefile->ReadVec3( storedPosition ); + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadAngles ( viewAngles ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadMat3 ( viewAxis ); + + // TEMP - restore animator, because it was cleared when loading the AF + animator.Restore( savefile ); + animator.GetJoints( &renderEntity.numJoints, &renderEntity.joints ); + animator.GetBounds( gameLocal.time, renderEntity.bounds ); + // TEMP + + additionalDelta.Zero(); +} + +/* +================ +rvVehicleAnimated::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleAnimated::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { + origin = modelOffset; + axis = viewAxis; + return true; +} + +/* +================ +rvVehicleAnimated::RunPrePhysics +================ +*/ +void rvVehicleAnimated::RunPrePhysics ( void ) { + storedPosition = physicsObj.GetOrigin(); +} + +/* +================ +rvVehicleAnimated::RunPostPhysics +================ +*/ +void rvVehicleAnimated::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 3000 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + idVec3 delta; + idVec3 actualDelta = physicsObj.GetOrigin() - storedPosition; + + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( !autoCorrectionBegin && delta.LengthSqr() * 0.2f > actualDelta.LengthSqr() ) { + autoCorrectionBegin = gameLocal.time; + } + } +} + + diff --git a/src/mpgame/vehicle/VehicleAnimated.h b/src/mpgame/vehicle/VehicleAnimated.h new file mode 100644 index 0000000..2bed08b --- /dev/null +++ b/src/mpgame/vehicle/VehicleAnimated.h @@ -0,0 +1,73 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleAnimated.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEANIMATED_H__ +#define __GAME_VEHICLEANIMATED_H__ + +#include "Vehicle.h" +#include "../physics/Physics_Monster.h" + +class rvVehicleAnimated : public rvVehicle { +public: + + CLASS_PROTOTYPE( rvVehicleAnimated ); + + rvVehicleAnimated ( void ); + ~rvVehicleAnimated ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual const idMat3& GetAxis ( int id = 0 ) const; + + void ClientPredictionThink ( void ); + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +protected: + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedPosition; + // end twhitaker: + + idPhysics_Monster physicsObj; + + idAngles viewAngles; + float turnRate; + idVec3 additionalDelta; +}; + +#endif // __GAME_VEHICLEANIMATED_H__ diff --git a/src/mpgame/vehicle/VehicleController.cpp b/src/mpgame/vehicle/VehicleController.cpp new file mode 100644 index 0000000..6b309d1 --- /dev/null +++ b/src/mpgame/vehicle/VehicleController.cpp @@ -0,0 +1,325 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleController.h" +#include "Vehicle.h" + +/* +===================== +rvVehicleController::rvVehicleController +===================== +*/ +rvVehicleController::rvVehicleController ( void ) { + mVehicle = NULL; + //mDriver = NULL; + mPosition = 0; +} + +/* +===================== +rvVehicleController::Save +===================== +*/ +void rvVehicleController::Save ( idSaveGame *savefile ) const { + mVehicle.Save ( savefile ); + savefile->WriteInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Restore +===================== +*/ +void rvVehicleController::Restore ( idRestoreGame *savefile ) { + mVehicle.Restore ( savefile ); + savefile->ReadInt ( mPosition ); +} + +/* +===================== +rvVehicleController::Drive +===================== +*/ +bool rvVehicleController::Drive ( rvVehicle* vehicle, idActor* driver ) { + assert ( vehicle && driver ); + + // Flip the vehicle back over? + if ( vehicle->IsFlipped ( ) ) { + vehicle->AutoRight ( vehicle ); + return false; + } + + // Add the driver to the vehicle and cache the position it was added. + if ( -1 == (mPosition = vehicle->AddDriver ( 0, driver ) ) ) { + return false; + } + + mVehicle = vehicle; + + //twhitaker: for scripted callback events + vehicle->OnEnter(); + + if ( driver->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( driver ); + + if ( player->GetHud() ) { + GetHud()->Activate( true, gameLocal.time ); + GetHud()->HandleNamedEvent( "showExitmessage" ); + } + } + + return true; +} + +/* +===================== +rvVehicleController::Eject +===================== +*/ +bool rvVehicleController::Eject ( bool force ) { + if ( !GetVehicle() ) { + return true; + } + + if ( GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast< idPlayer *>( GetDriver() ); + + if ( player->GetHud() ) { + GetHud()->HandleNamedEvent( "hideExitmessage" ); + } + } + + if ( mVehicle->RemoveDriver ( GetPosition(), force ) ) { + mVehicle->OnExit(); + mVehicle = NULL; + + return true; + } + + return false; +} + +/* +===================== +rvVehicleController::FindClearExitPoint +===================== +*/ +bool rvVehicleController::FindClearExitPoint( idVec3& origin, idMat3& axis ) const { + return GetVehicle()->FindClearExitPoint( mPosition, origin, axis ); +} + +/* +===================== +rvVehicleController::KillVehicles +===================== +*/ +void rvVehicleController::KillVehicles ( void ) { + KillEntities( idCmdArgs(), rvVehicle::GetClassType() ); +} + +/* +===================== +rvVehicleController::DrawHUD +===================== +*/ +void rvVehicleController::DrawHUD ( void ) { + assert ( mVehicle ); + + if ( GetDriver() && GetDriver()->IsType( idPlayer::GetClassType() ) ) { + idPlayer * player = static_cast( GetDriver() ); + rvVehicleWeapon * weapon = mVehicle->GetPosition( mPosition )->GetActiveWeapon(); + if ( weapon ) { + if ( weapon->CanZoom() && player->IsZoomed() && player == gameLocal.GetLocalPlayer() ) { + weapon->GetZoomGui()->Redraw( gameLocal.time ); + } +// if ( mVehicle->GetHud() ) { +// mVehicle->GetHud()->SetStateFloat( "tram_heatpct", weapon->GetOverheatPercent()); +// mVehicle->GetHud()->SetStateFloat( "tram_overheat", weapon->GetOverheatState()); +// } + } + } + + mVehicle->DrawHUD ( mPosition ); +} + +/* +===================== +rvVehicleController::StartRadioChatter +===================== +*/ +void rvVehicleController::StartRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterUp" ); + } +} + +/* +===================== +rvVehicleController::StopRadioChatter +===================== +*/ +void rvVehicleController::StopRadioChatter ( void ) { + assert ( mVehicle ); + if ( mVehicle->GetHud () ) { + mVehicle->GetHud ()->HandleNamedEvent( "radioChatterDown" ); + } +} + +/* +===================== +rvVehicleController::Give +===================== +*/ +void rvVehicleController::Give ( const char* statname, const char* value ) { + if( mVehicle.IsValid() ) { + mVehicle->Give ( statname, value ); + } +} + +/* +===================== +rvVehicleController::GetEyePosition +===================== +*/ +void rvVehicleController::GetEyePosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetEyePosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetDriverPosition +===================== +*/ +void rvVehicleController::GetDriverPosition ( idVec3& origin, idMat3& axis ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetDriverPosition ( mPosition, origin, axis ); + } +} + +/* +===================== +rvVehicleController::GetVehicle +===================== +*/ +rvVehicle* rvVehicleController::GetVehicle ( void ) const { + return mVehicle; +} + +idActor* rvVehicleController::GetDriver ( void ) const { + return (GetVehicle()) ? GetVehicle()->GetPosition(GetPosition())->GetDriver() : NULL; +} + +/* +===================== +rvVehicleController::SetInput +===================== +*/ +void rvVehicleController::SetInput ( const usercmd_t& cmd, const idAngles &angles ) { + if( mVehicle.IsValid() ) { + mVehicle->SetInput ( mPosition, cmd, angles ); + } +} + +/* +===================== +rvVehicleController::GetInput +===================== +*/ +void rvVehicleController::GetInput( usercmd_t& cmd, idAngles &newAngles ) const { + if( mVehicle.IsValid() ) { + mVehicle->GetInput( mPosition, cmd, newAngles ); + } +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +idUserInterface* rvVehicleController::GetHud( void ) { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::GetHud +================ +*/ +const idUserInterface* rvVehicleController::GetHud( void ) const { + return (IsDriving()) ? mVehicle->GetHud() : NULL; +} + +/* +================ +rvVehicleController::WriteToSnapshot +================ +*/ +void rvVehicleController::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteLong ( mPosition ); + msg.WriteLong ( mVehicle.GetSpawnId ( ) ); +} + +/* +================ +rvVehicleController::ReadFromSnapshot +================ +*/ +void rvVehicleController::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mPosition = msg.ReadLong ( ); + mVehicle.SetSpawnId ( msg.ReadLong ( ) ); +} + +/* +================ +rvVehicleController::UpdateCursorGUI +================ +*/ +void rvVehicleController::UpdateCursorGUI ( idUserInterface* ui ) { + assert ( mVehicle ); + mVehicle->UpdateCursorGUI ( mPosition, ui ); +} + +/* +================ +rvVehicleController::SelectWeapon +================ +*/ +void rvVehicleController::SelectWeapon ( int weapon ) { + if( mVehicle.IsValid() ) { + mVehicle->GetPosition( mPosition )->SelectWeapon( weapon ); + } +} diff --git a/src/mpgame/vehicle/VehicleController.h b/src/mpgame/vehicle/VehicleController.h new file mode 100644 index 0000000..3cd245f --- /dev/null +++ b/src/mpgame/vehicle/VehicleController.h @@ -0,0 +1,93 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleController.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLECONTROLLER_H__ +#define __GAME_VEHICLECONTROLLER_H__ + +class rvVehicle; +class rvVehiclePosition; + +class rvVehicleController { +public: + + rvVehicleController ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool Drive ( rvVehicle* vehicle, idActor* driver ); + bool Eject ( bool force = false ); + + bool FindClearExitPoint ( idVec3& origin, idMat3& axis ) const; + + bool IsDriving ( void ) const; + + rvVehicle* GetVehicle ( void ) const; + idActor* GetDriver ( void ) const; + int GetPosition ( void ) const; + + void SetInput ( const usercmd_t& cmd, const idAngles &newAngles ); + void GetInput ( usercmd_t& cmd, idAngles &newAngles ) const; + + idUserInterface* GetHud ( void ); + const idUserInterface* GetHud ( void ) const; + void DrawHUD ( void ); + void UpdateCursorGUI ( idUserInterface* ui ); + + void StartRadioChatter ( void ); + void StopRadioChatter ( void ); + + void Give ( const char* statname, const char* value ); + void GetEyePosition ( idVec3& origin, idMat3& axis ) const; + void GetDriverPosition ( idVec3& origin, idMat3& axis ) const; + + static void KillVehicles ( void ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + void SelectWeapon ( int weapon ); + +protected: + + idEntityPtr mVehicle; + int mPosition; +}; + +ID_INLINE bool rvVehicleController::IsDriving ( void ) const { + return mVehicle.IsValid ( ); +} + +ID_INLINE int rvVehicleController::GetPosition ( void ) const { + return mPosition; +} + +#endif // __GAME_VEHICLECONTROLLER_H__ + diff --git a/src/mpgame/vehicle/VehicleDriver.cpp b/src/mpgame/vehicle/VehicleDriver.cpp new file mode 100644 index 0000000..225ad6e --- /dev/null +++ b/src/mpgame/vehicle/VehicleDriver.cpp @@ -0,0 +1,859 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// VehicleDriver.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" + +#include + +const idEventDef VD_ChoosePathTarget( "choosePathTarget", "e", 'e' ); +const idEventDef EV_FollowOffset( "followOffset", "v" ); +const idEventDef EV_FireWeapon ( "fireWeapon", "ff" ); + +CLASS_DECLARATION( idActor, rvVehicleDriver ) + EVENT( EV_PostSpawn , rvVehicleDriver::Event_PostSpawn ) + EVENT( AI_EnterVehicle , rvVehicleDriver::Event_EnterVehicle ) + EVENT( AI_ExitVehicle , rvVehicleDriver::Event_ExitVehicle ) + EVENT( AI_ScriptedMove , rvVehicleDriver::Event_ScriptedMove ) + EVENT( AI_ScriptedDone , rvVehicleDriver::Event_ScriptedDone ) + EVENT( AI_ScriptedStop , rvVehicleDriver::Event_ScriptedStop ) + EVENT( EV_Activate , rvVehicleDriver::Event_Trigger ) + EVENT( EV_Speed , rvVehicleDriver::Event_SetSpeed ) + EVENT( EV_FireWeapon , rvVehicleDriver::Event_FireWeapon ) + EVENT( AI_FaceEntity , rvVehicleDriver::Event_FaceEntity ) + EVENT( AI_LookAt , rvVehicleDriver::Event_LookAt ) + EVENT( AI_SetLeader , rvVehicleDriver::Event_SetLeader ) + +//twhitaker: remove - begin + EVENT( EV_FollowOffset , rvVehicleDriver::Event_SetFollowOffset ) +//twhitaker: remove - end +END_CLASS + +/* +================ +rvVehicleDriver::rvVehicleDriver +================ +*/ +rvVehicleDriver::rvVehicleDriver ( void ) { +} + +/* +================ +rvVehicleDriver::Spawn +================ +*/ +void rvVehicleDriver::Spawn ( void ) { + currentThrottle = 1.0f; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; + leaderFlags = 0; + decelDistance = 0.0f; + minDistance = 0.0f; + fireEndTime = 0.0f; + isMoving = false; + avoidingLeader = false; + pathingMode = VDPM_Random; + pathingOrigin = vec3_origin; + pathingEntity = NULL; + + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + SIMDProcessor->Memset( &lastPathTargetInfo, 0, sizeof( PathTargetInfo ) ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleDriver::Think +================ +*/ +void rvVehicleDriver::Think ( void ) { + if ( !IsDriving() ) { + if ( leader ) { + leader->SetLeaderHint( VDLH_Continue ); + leader = NULL; + } + return; + } + + if ( pathTargetInfo.node ) { + float targetDistance = 0.0f; + float dotForward = 0.0f; + float dotRight = 0.0f; + float desiredThrottle = 1.0f; + idEntity* currentPathTarget = pathTargetInfo.node; + rvVehicle* vehicle = vehicleController.GetVehicle(); + idMat3 vehicleAxis = vehicle->GetAxis(); + idVec3 targetOrigin; + idVec3 dirToTarget; + usercmd_t cmd; + idAngles ang; + + // We may want to hack the auto correction variable based on what the state of the driver and the driver's leader. + UpdateAutoCorrection(); + + if ( vehicle->IsAutoCorrecting( ) ) { + if ( lastPathTargetInfo.node ) { + currentPathTarget = lastPathTargetInfo.node; + } + + if ( g_debugVehicleDriver.GetInteger() != 0 ) { + gameRenderWorld->DebugBounds( colorCyan, vehicle->GetPhysics()->GetAbsBounds().Expand( 30 ) ); + } + } + + // Touch all the triggers that the vehicle touches + vehicle->TouchTriggers(); + + GetTargetInfo( currentPathTarget, &targetOrigin, &dirToTarget, &dotForward, &dotRight, &targetDistance ); + + // The primary purpose of the following portions of code is to set the desiredThrottle variable. + if ( IsMoving() ) { + + // if we're slowing down lerp throttle. + if ( pathTargetInfo.throttle < lastPathTargetInfo.throttle ) { + desiredThrottle = idMath::Lerp( lastPathTargetInfo.throttle, pathTargetInfo.throttle, targetDistance / pathTargetInfo.initialDistance ); + } else { + // otherwise accelerate rapidly or maintain throttle. + desiredThrottle = pathTargetInfo.throttle; + } + + // we could potentially be a leader, so check the leader flags + if ( leaderFlags & VDLH_SlowDown ) { + desiredThrottle = 0.1f; + } else if ( leaderFlags & VDLH_Wait ) { + desiredThrottle = 0.0f; + } + + // if we're following a someone ... + if ( leader ) { + bool canSeeLeader = vehicle->CanSee( leader->vehicleController.GetVehicle(), false ); + float distanceToLeader = ( leader->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + avoidingLeader = false; + + // update the leaders flags based on distance and visibility + if ( !canSeeLeader ) { + // if we can't see the leader, tell him to stop + leader->SetLeaderHint( VDLH_Wait ); + + } else if ( distanceToLeader > decelDistance ) { + // if we're too far from the leader, tell him to slow down + leader->SetLeaderHint( VDLH_SlowDown ); + + } else { + // we're within range of the leader so let him move along at a normal rate + leader->SetLeaderHint( VDLH_Continue ); + + // if we're too close to the leader, we need to slow ourself down + if ( distanceToLeader < minDistance ) { + desiredThrottle = -0.2f; + avoidingLeader = true; + } + } + } + + // the desiredThrottle variable should be set at this point, the only other thing that could change it + // would be in SimulateKeys, if the dotForward < 0. + } + + vehicleController.GetInput( cmd, ang ); + + // Simulate Input + SimulateKeys( cmd, dotForward, dotRight, desiredThrottle * currentThrottle, targetDistance ); + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + + // Node Transition + if( (!vehicle->IsAutoCorrecting() || !IsValidPathNode( currentPathTarget )) && isMoving ) { + idVec3 point = targetOrigin - dirToTarget * pathTargetInfo.minDistance; + + if( vehicle->GetPhysics()->GetAbsBounds().ContainsPoint( point ) ) { + int numTargets = NumValidTargets( currentPathTarget ); + lastPathTargetInfo = pathTargetInfo; + //TODO: ponder - should I be setting lastPathTargetInfo.node to pathTargetInfo.node or currentPathTarget??? + + if( numTargets ) { + Event_ScriptedMove( ChooseNextNode( currentPathTarget ), 0, 0 ); + } + + if( lastPathTargetInfo.exitVehicle ) { + Event_ExitVehicle( true ); + } else if( lastPathTargetInfo.throttle == 0 || !numTargets ) { + Event_ScriptedStop(); + + if( !numTargets ) { + pathTargetInfo.node = NULL; + } + } + + // Debug Output + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugBounds( colorRed, idBounds(idVec3(-5, -5, -5), idVec3(5, 5, 5)), point ); + } + } + } + } else { // no path + float dotForward; + float dotRight; + usercmd_t cmd; + idAngles ang; + + vehicleController.GetInput( cmd, ang ); + + if ( GetTargetInfo( faceTarget, NULL, NULL, &dotForward, &dotRight, NULL) ) { + if( ( 1.0f - dotForward ) < VECTOR_EPSILON ) { + Event_ScriptedStop(); + } else { + SimulateKeys( cmd, dotForward, dotRight, 0.0f ); + } + } + + SimulateMouseMove( cmd ); + SimulateButtons( cmd ); + + vehicleController.SetInput( cmd, ang ); + } +} + +/* +================ +rvVehicleDriver::GetTargetInfo +================ +*/ +bool rvVehicleDriver::GetTargetInfo( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, float* dotForward, float* dotRight, float* distance ) const { + if( !target || !IsDriving() ) { + return false; + } + + idMat3 vehicleAxis = vehicleController.GetVehicle()->GetAxis(); + idVec3 vehicleOrigin = vehicleController.GetVehicle()->GetOrigin(); + idVec3 forward = vehicleAxis[ 0 ]; + idVec3 localTargetOrigin = target->GetPhysics()->GetOrigin(); + localTargetOrigin.z = vehicleOrigin.z;// Find way to include vehicleAxis here + idVec3 vectorToTarget = localTargetOrigin - vehicleOrigin; + idVec3 localDirToTarget = vectorToTarget; + float distToTarget = localDirToTarget.Normalize(); + + if( targetOrigin ) { + *targetOrigin = localTargetOrigin; + } + + if( dirToTarget ) { + *dirToTarget = localDirToTarget; + } + + if( distance ) { + *distance = distToTarget; + } + + if( dotForward ) { + *dotForward = localDirToTarget * forward; + } + + if( dotRight ) { + *dotRight = localDirToTarget * vehicleAxis[ 1 ]; + } + + // Debug help + if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { + idStr temp; + const idVec3 & origin = GetPhysics()->GetOrigin(); + gameRenderWorld->DebugLine( colorBlue, origin, target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DebugBounds( colorBlue, GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DebugBounds( colorBlue, target->GetPhysics()->GetBounds(), target->GetPhysics()->GetOrigin() ); + gameRenderWorld->DrawText( target->GetName(), vehicleOrigin + vectorToTarget + vehicleAxis[2] * 5.0f, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + gameRenderWorld->DrawText( idStr( "State: " ) + LeaderHintsString( leaderFlags, temp ), origin, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); + + if ( vehicleController.GetVehicle()->IsAutoCorrecting() ) { + gameRenderWorld->DebugBounds( colorPurple, vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds() ); + gameRenderWorld->DrawText( "Auto-Correcting", vehicleOrigin, 1.0f, colorPurple, gameLocal.GetLocalPlayer()->viewAxis ); + } + + if ( pathTargetInfo.node && g_debugVehicleDriver.GetInteger( ) == 2 ) { + gameRenderWorld->DebugBounds( colorOrange, pathTargetInfo.node->GetPhysics()->GetBounds() ); + + for ( int ix = 0; ix < pathTargetInfo.node->targets.Num(); ix++ ) { + gameRenderWorld->DebugLine( colorOrange, pathTargetInfo.node->GetPhysics()->GetOrigin(), pathTargetInfo.node->targets[ ix ]->GetPhysics()->GetOrigin(), 0, true ); + } + } + if ( leader ) { + idVec4 & color = colorGreen; + + if ( leader->GetLeaderHint() & VDLH_SlowDown ) { + color = colorYellow; + } else if ( leader->GetLeaderHint() & VDLH_Wait ) { + color = colorRed; + } + + idStr str = idStr( "decel_distance: " ) + idStr( decelDistance ) + idStr( "\nmin_distance: " ) + idStr( minDistance ); + + gameRenderWorld->DrawText( str, leader->GetPhysics()->GetOrigin(), 1.0f, color, gameLocal.GetLocalPlayer()->viewAxis, 1, 0, true ); + gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), leader->GetPhysics()->GetOrigin(), 0, true ); + gameRenderWorld->DebugBounds( color, leader->vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds(), vec3_origin, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), decelDistance, 10, 0, true ); + gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), minDistance, 10, 0, true ); + } + } + + return true; +} + +/* +================ +rvVehicleDriver::ChooseNextNode +================ +*/ +idEntity* rvVehicleDriver::ChooseNextNode( idEntity* target ) { + if( !target ) { + return NULL; + } + + if( func.Init( target->spawnArgs.GetString( "call_nextNode" )) <= 0 ) { + idEntity * best = NULL; + float bestDist; + + switch ( pathingMode ) { + case VDPM_MoveTo: + bestDist = FLT_MAX; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist > distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + break; + + case VDPM_MoveAway: + bestDist = FLT_MIN; + + for ( int i = target->targets.Num() - 1; i; i -- ) { + float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); + + if ( bestDist < distance ) { + bestDist = distance; + best = target->targets[ i ]; + } + } + + case VDPM_Custom: + if ( pathingEntity ) { +// best = pathingCallback( target ); + pathingEntity->ProcessEvent( &VD_ChoosePathTarget, target ); + best = gameLocal.program.GetReturnedEntity(); + } + } + + return ( best ) ? best : RandomValidTarget( target ); + } + + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + + return ( func.ReturnsAVal()) ? gameLocal.FindEntity( spawnArgs.GetString( func.GetReturnKey() )) : const_cast( target ); +} + +/* +================ +rvVehicleDriver::SimulateButtons +================ +*/ +void rvVehicleDriver::SimulateButtons( usercmd_t& cmd ) { + cmd.buttons = (fireEndTime >= gameLocal.time) ? BUTTON_ATTACK : 0; +} + +/* +================ +rvVehicleDriver::SimulateMouseMove +================ +*/ +void rvVehicleDriver::SimulateMouseMove( usercmd_t& cmd ) { + idVec3 origin; + idMat3 axis; + + idVec3 vectorToTarget; + idAngles anglesToTarget; + idAngles turretAngles; + idAngles deltaAngles; + + if( !lookTarget ) { + for( int ix = 0; ix < 3; ++ix ) { + cmd.angles[ix] = 0; + } + return; + } + + vehicleController.GetEyePosition( origin, axis ); + vectorToTarget = (lookTarget->GetPhysics()->GetOrigin() - origin).ToNormal(); + anglesToTarget = vectorToTarget.ToAngles().Normalize360(); + turretAngles = (axis[0]).ToAngles().Normalize360(); + deltaAngles = (anglesToTarget - turretAngles).Normalize180(); + + for( int ix = 0; ix < deltaAngles.GetDimension(); ++ix ) { + cmd.angles[ix] += ANGLE2SHORT( deltaAngles[ix] ); + } + + // Debug Output + if( g_debugVehicleDriver.GetInteger( ) != 0 ) { + gameRenderWorld->DebugLine( colorGreen, origin, origin + anglesToTarget.ToForward() * 100.0f, 17, true ); + gameRenderWorld->DebugLine( colorYellow, origin, origin + turretAngles.ToForward() * 100.0f, 17, true ); + } +} + +/* +================ +rvVehicleDriver::SimulateKeys +================ +*/ +void rvVehicleDriver::SimulateKeys( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + if( !vehicle ) { + cmd.forwardmove = 0; + cmd.rightmove = 0; + return; + } + + cmd.forwardmove = static_cast< signed char >( (!vehicle->IsAutoCorrecting() ? 127.0f : forwardThrustScale) * dotForward * speed ); + cmd.rightmove = static_cast< signed char >( ( ( dotForward < 0.0f ) ? rightThrustScale : -rightThrustScale ) * dotRight ); +} + + +/* +================ +rvVehicleDriver::SortValid +================ +*/ +int rvVehicleDriver::SortValid( const void* a, const void* b ) { + idEntityPtr A = *(idEntityPtr*)a; + idEntityPtr B = *(idEntityPtr*)b; + + return rvVehicleDriver::IsValidTarget( B ) - rvVehicleDriver::IsValidTarget( A ); +} + +/* +================ +rvVehicleDriver::SortTargetList +================ +*/ +int rvVehicleDriver::SortTargetList( idEntity* ent ) const { + if( !ent ) { + return 0; + } + + int numValidEntities = 0; + idEntity * target; + + ent->RemoveNullTargets(); + qsort( ent->targets.Ptr(), NumTargets( ent ), ent->targets.TypeSize(), rvVehicleDriver::SortValid ); + + for( int ix = NumTargets( ent ) - 1; ix >= 0; --ix ) { + target = GetTarget( ent, ix ); + + if( IsValidTarget( target ) ) { + ++numValidEntities; + } + } + + return numValidEntities; +} + +/* +================ +rvVehicleDriver::RandomValidTarget +================ +*/ +idEntity* rvVehicleDriver::RandomValidTarget( idEntity* ent ) const { + int numValid = NumValidTargets( ent ); + return (!numValid) ? NULL : ent->targets[ rvRandom::irand(0, numValid - 1) ]; +} + +/* +================ +rvVehicleDriver::NumValidTargets +================ +*/ +int rvVehicleDriver::NumValidTargets( idEntity* ent ) const { + return SortTargetList(ent); +} + +/* +================ +rvVehicleDriver::NumTargets +================ +*/ +int rvVehicleDriver::NumTargets( const idEntity* ent ) const { + return (ent) ? ent->targets.Num() : 0; +} + +/* +================ +rvVehicleDriver::GetTarget +================ +*/ +idEntity* rvVehicleDriver::GetTarget( const idEntity* ent, int index ) const { + return (ent) ? ent->targets[index] : NULL; +} + +/* +================ +rvVehicleDriver::IsValidPathNode +================ +*/ +bool rvVehicleDriver::IsValidPathNode( const idEntity* ent ) { + if( !ent ) { + return false; + } + + return ent->IsType( idTarget::GetClassType() ); +} + +/* +================ +rvVehicleDriver::IsValidTarget +================ +*/ +bool rvVehicleDriver::IsValidTarget( const idEntity* ent ) { + return IsValidPathNode( ent ) || ent->IsType( rvVehicle::GetClassType() ) || ent->IsType( idPlayer::GetClassType() ); +} + +/* +================ +rvVehicleDriver::SetLeader +================ +*/ +bool rvVehicleDriver::SetLeader( idEntity* ent ) { + if ( !ent || !ent->IsType( rvVehicleDriver::GetClassType() ) ) { + return false; + } + + leader = static_cast( ent ); + minDistance = leader->spawnArgs.GetFloat( "min_distance", "500" ); + idStr decel_distance( minDistance * 3.0f ); + decelDistance = leader->spawnArgs.GetFloat( "decel_distance", decel_distance ); + + return true; +} + +/* +================ +rvVehicleDriver::Event_PostSpawn +================ +*/ +void rvVehicleDriver::Event_PostSpawn ( void ) { + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( rvVehicle::GetClassType() ) ) { + Event_EnterVehicle( ent ); + } + } + + for ( int i = targets.Num() - 1; i >= 0; i-- ) { + idEntity * ent = targets[ i ].GetEntity(); + if ( ent->IsType( idTarget::GetClassType() ) ) { + Event_ScriptedMove( ent, 0, 0 ); + } + if ( ent->IsType( rvVehicleDriver::GetClassType() ) ) { + SetLeader( ent ); + } + } +} + +/* +================ +rvVehicleDriver::Event_EnterVehicle +================ +*/ +void rvVehicleDriver::Event_EnterVehicle ( idEntity * vehicle ) { + if ( vehicle ) { + forwardThrustScale = vehicle->spawnArgs.GetFloat( "driver_forward_thrust", ".75" ) * 127.0f; + rightThrustScale = vehicle->spawnArgs.GetFloat( "driver_right_thrust", "1" ) * 127.0f; + + EnterVehicle( vehicle ); + } +} + +/* +================ +rvVehicleDriver::Event_ExitVehicle +================ +*/ +void rvVehicleDriver::Event_ExitVehicle( bool force ) { + Event_ScriptedStop(); + + if( vehicleController.GetVehicle() && force && !ExitVehicle(force) ) { + vehicleController.GetVehicle()->RemoveDriver( vehicleController.GetPosition(), force ); + } + + pathTargetInfo.node = NULL; + faceTarget = NULL; + lookTarget = NULL; + leader = NULL; +} + +/* +================ +rvVehicleDriver::Event_ScriptedMove +================ +*/ +void rvVehicleDriver::Event_ScriptedMove( idEntity *target, float minDist, bool exitVehicle ) { + isMoving = false; + + if( !target ) { + return; + } + + if( IsValidTarget( target ) ) { + isMoving = true; + faceTarget = NULL; + pathTargetInfo.node = target; + pathTargetInfo.initialDistance = ( target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); + + if ( !target->IsType( idPlayer::GetClassType() ) ) { + pathTargetInfo.minDistance = target->spawnArgs.GetFloat( "min_distance", va("%f", Max(200.0f, minDist)) ); + pathTargetInfo.throttle = idMath::ClampFloat( 0.0f, 1.0f, target->spawnArgs.GetFloat( "throttle", "1" ) ); + pathTargetInfo.exitVehicle = target->spawnArgs.GetBool( "exit_vehicle", exitVehicle ? "1" : "0" ); + + if( pathTargetInfo.exitVehicle ) { + pathTargetInfo.throttle = 0.0f; + } + } else { + pathTargetInfo.minDistance = minDist; + pathTargetInfo.throttle = 1.0f; + pathTargetInfo.exitVehicle = false; + } + + } else { + SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); + } +} + +/* +================ +rvVehicleDriver::Event_ScriptedDone +================ +*/ +void rvVehicleDriver::Event_ScriptedDone( void ) { + idThread::ReturnFloat( !IsMoving() ); +} + +/* +================ +rvVehicleDriver::Event_ScriptedStop +================ +*/ +void rvVehicleDriver::Event_ScriptedStop( void ) { + if( IsDriving() ) { + usercmd_t cmd = { 0 }; + + vehicleController.SetInput( cmd, ang_zero ); + + if( pathTargetInfo.node ) { + if( func.Init( pathTargetInfo.node->spawnArgs.GetString( "call_doneMoving" )) > 0 ) { + func.InsertEntity( this, 0 ); + func.CallFunc( &spawnArgs ); + func.RemoveIndex( 0 ); + } + + pathTargetInfo.node->ActivateTargets(this); + pathTargetInfo.node = NULL; + } + + isMoving = false; + } +} + +/* +================ +rvVehicleDriver::Event_Trigger +================ +*/ +void rvVehicleDriver::Event_Trigger( idEntity *activator ) { + if( IsDriving() && pathTargetInfo.node ) { + isMoving = true; + } +} + +/* +================ +rvVehicleDriver::Event_SetSpeed +================ +*/ +void rvVehicleDriver::Event_SetSpeed( float speed ) { + currentThrottle = speed; +} + +//twhitaker: remove - begin +/* +================ +rvVehicleDriver::Event_SetFollowOffset +================ +*/ +void rvVehicleDriver::Event_SetFollowOffset( const idVec3 &offset ) { + gameLocal.Warning( "Script Event \"followOffset\" is deprecated, please remove it immediately to avoid errors." ); +} +//twhitaker: remove - end + +/* +================ +rvVehicleDriver::Event_FireWeapon +================ +*/ +void rvVehicleDriver::Event_FireWeapon( float weapon_index, float time ) { + if( IsDriving() ) { + fireEndTime = gameLocal.GetTime() + SEC2MS( time ); + vehicleController.SelectWeapon( weapon_index ); + } +} + +/* +================ +rvVehicleDriver::Event_FaceEntity +================ +*/ +void rvVehicleDriver::Event_FaceEntity( const idEntity* entity ) { + if( IsMoving() ) { + return; + } + + faceTarget = entity; + isMoving = true; +} + +/* +================ +rvVehicleDriver::Event_LookAt +================ +*/ +void rvVehicleDriver::Event_LookAt( const idEntity* entity ) { + lookTarget = entity; +} + +/* +================ +rvVehicleDriver::Event_SetLeader +================ +*/ +void rvVehicleDriver::Event_SetLeader( idEntity* newLeader ) { + SetLeader( newLeader ); +} + +/* +================ +rvVehicleDriver::UpdateAutoCorrection +================ +*/ +void rvVehicleDriver::UpdateAutoCorrection ( void ) { + if ( IsDriving() ) { + rvVehicle * vehicle = vehicleController.GetVehicle(); + + // Disregard your autocorrection state if we're slowing down to avoid collision with the leader. + if ( vehicle->IsAutoCorrecting() ) { + if ( avoidingLeader == true ) { + vehicle->autoCorrectionBegin = 0; + } + } + } +} + +/* +================ +rvVehicleDriver::Save +================ +*/ +void rvVehicleDriver::Save ( idSaveGame *savefile ) const { + savefile->Write ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Write ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->WriteFloat ( currentThrottle ); + + faceTarget.Save ( savefile ); + lookTarget.Save ( savefile ); + + leader.Save ( savefile ); + savefile->WriteInt ( leaderFlags ); + savefile->WriteFloat ( decelDistance ); + savefile->WriteFloat ( minDistance ); + savefile->WriteBool ( avoidingLeader ); + + savefile->WriteFloat ( fireEndTime ); + + func.Save ( savefile ); + + savefile->WriteBool ( isMoving ); + + savefile->WriteInt ( (int&)pathingMode ); + pathingEntity.Save ( savefile ); + savefile->WriteVec3 ( pathingOrigin ); + + savefile->WriteFloat( forwardThrustScale ); // cnicholson: Added unsaved var + savefile->WriteFloat( rightThrustScale ); // cnicholson: Added unsaved var + +} + +/* +================ +rvVehicleDriver::Restore +================ +*/ +void rvVehicleDriver::Restore ( idRestoreGame *savefile ) { + savefile->Read ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); + savefile->Read ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); + savefile->ReadFloat ( currentThrottle ); + + faceTarget.Restore ( savefile ); + lookTarget.Restore ( savefile ); + + leader.Restore ( savefile ); + savefile->ReadInt ( leaderFlags ); + savefile->ReadFloat ( decelDistance ); + savefile->ReadFloat ( minDistance ); + savefile->ReadBool ( avoidingLeader ); + + savefile->ReadFloat ( fireEndTime ); + + func.Restore ( savefile ); + + savefile->ReadBool ( isMoving ); + + savefile->ReadInt ( (int&)pathingMode ); + pathingEntity.Restore ( savefile ); + savefile->ReadVec3 ( pathingOrigin ); + + savefile->ReadFloat( forwardThrustScale ); // cnicholson: Added unrestored var + savefile->ReadFloat( rightThrustScale ); // cnicholson: Added unrestored var +} diff --git a/src/mpgame/vehicle/VehicleDriver.h b/src/mpgame/vehicle/VehicleDriver.h new file mode 100644 index 0000000..aae54d4 --- /dev/null +++ b/src/mpgame/vehicle/VehicleDriver.h @@ -0,0 +1,209 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleDriver.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +extern const idEventDef VD_ChoosePathTarget; + +class rvVehicleDriver : public idActor { + friend class rvVehicleAI; + friend class rvVehicleMonster; +public: + + CLASS_PROTOTYPE( rvVehicleDriver ); + + rvVehicleDriver ( void ); + + void Think ( void ); + void Spawn ( void ); + + static bool IsValidTarget ( const idEntity* ent ); // valid entity for looking or facing + static bool IsValidPathNode ( const idEntity* ent ); // valid entity for pathing + static bool IsValidLeader ( const idEntity* ent ); // valid entity to set as a leader + + static int SortValid ( const void* a, const void* b ); + + virtual void Present ( void ) { } + +protected: + void SimulateKeys ( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance = 0 ); + void SimulateButtons ( usercmd_t& cmd ); + void SimulateMouseMove ( usercmd_t& cmd ); + + bool GetTargetInfo ( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, + float* dotForward, float* dotRight, float* distance ) const; + idEntity* ChooseNextNode ( idEntity* target ); + + bool IsMoving ( void ) const { return isMoving && IsDriving(); } + bool IsDriving ( void ) const { return vehicleController.IsDriving(); } + + bool SetLeader ( idEntity* newLeader ); + const idEntity* GetLeader ( void ) const { return leader; } + void SetLeaderHint ( int flags ) { leaderFlags = flags; } + int GetLeaderHint ( void ) const { return leaderFlags; } + + int SortTargetList ( idEntity* ent ) const; + idEntity* RandomValidTarget ( idEntity* ent ) const; + int NumValidTargets ( idEntity* ent ) const; + int NumTargets ( const idEntity* ent ) const; + idEntity* GetTarget ( const idEntity* ent, int index ) const; + + void UpdateAutoCorrection ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + enum VD_PathingMode { + VDPM_Random = 0, + VDPM_MoveTo = 1, + VDPM_MoveAway = 2, + VDPM_Custom = 4, + }; + + + void SetPathingMode ( VD_PathingMode mode, const idVec3 & origin ); +// CustomPathingFunc SetPathingMode ( VD_PathingMode mode, CustomPathingFunc cb ); + idEntity * SetPathingMode ( VD_PathingMode mode, idEntity * ); + VD_PathingMode GetPathingMode ( void ) const; + const idVec3 & GetPathingOrigin ( void ) const; +// CustomPathingFunc GetPathingCustomCallback ( void ) const; + idEntity * GetPathingEntity ( void ) const; + +protected: + void Event_PostSpawn ( void ); + void Event_EnterVehicle ( idEntity * vehicle ); + void Event_ExitVehicle ( bool force ); + void Event_ScriptedMove ( idEntity *target, float minDist, bool exitVehicle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + void Event_Trigger ( idEntity *activator ); + void Event_SetSpeed ( float speed ); + void Event_FireWeapon ( float weapon_index, float time ); + void Event_FaceEntity ( const idEntity* entity ); + void Event_LookAt ( const idEntity* entity ); + void Event_SetLeader ( idEntity* newLeader ); + +//twhitaker: remove - begin + void Event_SetFollowOffset ( const idVec3 &offset ); +//twhitaker: remove - end + + // Leader hints : a driver will send the following driving hints to it's leader + enum VD_LeaderHints { + VDLH_SlowDown = 1, // Set when distance is too great + VDLH_Wait = 2, // Set when there is no line of site + VDLH_Continue = 0, // Default following behavior + }; + + idStr & LeaderHintsString( int hints, idStr & out ) const { + switch ( hints ) { + case VDLH_SlowDown: out = "SlowDown"; break; + case VDLH_Wait: out = "Wait"; break; + default: out = "Continue"; break; + } + return out; + } + + // Stores information about a path target + struct PathTargetInfo { + idEntityPtr node; // the target entity + float initialDistance; // the distance to this entity when we first decide to move there + float minDistance; // the range that specifies when this entity has been reached + float throttle; // 0 -> 1 speed setting + bool exitVehicle; // set to true to exit a vehicle when we reach this target + }; + + struct PathNavigationData { + float dotForward; + float dotRight; + }; + + // standard movement + PathTargetInfo pathTargetInfo; + PathTargetInfo lastPathTargetInfo; + float currentThrottle; + + // facing + idEntityPtr faceTarget; + idEntityPtr lookTarget; + + // following + idEntityPtr leader; //TODO: twhitaker: see if I can make this an rvVehicle (so entities can follow player) + int leaderFlags; + float decelDistance; + float minDistance; + bool avoidingLeader; + + // firing + float fireEndTime; + + // event callbacks + rvScriptFuncUtility func; + + // state + bool isMoving; + + VD_PathingMode pathingMode; +// idEntity * (* pathingCallback)( idEntity * ); + idEntityPtr pathingEntity; + idVec3 pathingOrigin; + + float forwardThrustScale; + float rightThrustScale; +}; + + +ID_INLINE void rvVehicleDriver::SetPathingMode( VD_PathingMode mode, const idVec3 & origin ) { + if ( mode != VDPM_Custom ) { + pathingMode = mode; + pathingOrigin = origin; + } +} + +ID_INLINE idEntity * rvVehicleDriver::SetPathingMode( VD_PathingMode mode, idEntity * entity ) { + if ( mode != VDPM_Custom ) { + return NULL; + } + + pathingMode = mode; + idEntity * prev = pathingEntity; + pathingEntity = entity; + return prev; +} + +ID_INLINE rvVehicleDriver::VD_PathingMode rvVehicleDriver::GetPathingMode ( void ) const { + return pathingMode; +} + +ID_INLINE const idVec3 & rvVehicleDriver::GetPathingOrigin ( void ) const { + return pathingOrigin; +} + +ID_INLINE idEntity * rvVehicleDriver::GetPathingEntity ( void ) const { + return pathingEntity; +} + diff --git a/src/mpgame/vehicle/VehicleMonster.cpp b/src/mpgame/vehicle/VehicleMonster.cpp new file mode 100644 index 0000000..092551c --- /dev/null +++ b/src/mpgame/vehicle/VehicleMonster.cpp @@ -0,0 +1,212 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleMonster.h" +#include "../ai/VehicleAI.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleMonster ) +END_CLASS + +rvVehicleMonster::rvVehicleMonster ( void ) { +} + +rvVehicleMonster::~rvVehicleMonster ( void ) { +} + +/* +================ +rvVehicleMonster::Spawn +================ +*/ +void rvVehicleMonster::Spawn( void ) { + idDict dict; + int count = 0; + const idKeyValue * val = NULL; + + for (;;) { + val = spawnArgs.MatchPrefix( "target", val ); + + if ( !val ) + break; + + dict.Set( "target" + (( count ) ? idStr( count ) : idStr( "" ) ), val->GetValue() ); + count++; + } + + dict.Set( "bind", name ); + dict.SetBool( "hide", true ); + + driver = static_cast( gameLocal.SpawnEntityDef( "ai_vehicle_driver", &dict ) ); + + if ( driver ) { + driver->SetVehicle( this ); + driver->GetPhysics()->SetOrigin( vec3_zero ); + } else { + gameLocal.Warning( "Unable to find \"entityDef ai_vehicle_driver\"." ); + } +} + +/* +================ +rvVehicleMonster::SetClipModel +================ +*/ +void rvVehicleMonster::SetClipModel ( idPhysics & physicsObj ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleMonster '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + +/* +================ +rvVehicleMonster::Save +================ +*/ +void rvVehicleMonster::Save ( idSaveGame *savefile ) const { + driver.Save ( savefile ); +} + +/* +================ +rvVehicleMonster::Restore +================ +*/ +void rvVehicleMonster::Restore ( idRestoreGame *savefile ) { + driver.Restore ( savefile ); +} + +/* +================ +rvVehicleMonster::Think +================ +*/ +void rvVehicleMonster::Think ( void ) { + if ( !driver ) { + PostEventMS( &EV_Remove, 0 ); + return; + } + + stateThread.Execute(); + rvVehicle::Think(); +} + +/* +================ +rvVehicleMonster::GetTargetOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetTargetOrigin ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToTarget +================ +*/ +idVec3 rvVehicleMonster::GetVectorToTarget ( void ) { + if ( driver && driver->driver && driver->driver->pathTargetInfo.node ) { + return driver->driver->pathTargetInfo.node->GetPhysics()->GetOrigin() - GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetEnemyOrigin +================ +*/ +const idVec3 & rvVehicleMonster::GetEnemyOrigin ( void ) { + if ( driver && driver->enemy.ent ) { + return driver->enemy.ent->GetPhysics()->GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::GetVectorToEnemy +================ +*/ +idVec3 rvVehicleMonster::GetVectorToEnemy ( void ) { + if ( driver && driver->enemy.ent ) { + //HACK: this is hideous, I'm ashamed. + return ( driver->enemy.ent->GetPhysics()->GetOrigin() + idVec3( 0, 0, 36 ) )- GetOrigin(); + } + return vec3_origin; +} + +/* +================ +rvVehicleMonster::LookAtEntity +================ +*/ +void rvVehicleMonster::LookAtEntity ( idEntity *ent, float duration ) { + const idVec3 entOrigin = ent->GetPhysics()->GetOrigin(); + const idVec3 origin = GetOrigin(); + idVec3 toEnt = entOrigin - origin; + toEnt.Normalize(); + GetPhysics()->SetAxis( toEnt.ToMat3() ); +} + +/* +================ +rvVehicleMonster::SkipImpulse +================ +*/ +bool rvVehicleMonster::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/src/mpgame/vehicle/VehicleMonster.h b/src/mpgame/vehicle/VehicleMonster.h new file mode 100644 index 0000000..8849bef --- /dev/null +++ b/src/mpgame/vehicle/VehicleMonster.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleMonster.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEMONSTER_H__ +#define __GAME_VEHICLEMONSTER_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleAI; + +class rvVehicleMonster : public rvVehicle { + friend class rvVehicleAI; +public: + + CLASS_PROTOTYPE( rvVehicleMonster ); + + rvVehicleMonster ( void ); + ~rvVehicleMonster ( void ); + + void Spawn ( void ); + void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( idPhysics & physicsObj ); + + const idVec3 & GetTargetOrigin ( void ); + idVec3 GetVectorToTarget ( void ); + const idVec3 & GetEnemyOrigin ( void ); + idVec3 GetVectorToEnemy ( void ); + void LookAtEntity ( idEntity *ent, float duration ); + + idEntityPtr driver; +}; + +#endif // __GAME_VEHICLEMONSTER_H__ diff --git a/src/mpgame/vehicle/VehicleParts.cpp b/src/mpgame/vehicle/VehicleParts.cpp new file mode 100644 index 0000000..c69be39 --- /dev/null +++ b/src/mpgame/vehicle/VehicleParts.cpp @@ -0,0 +1,2410 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleParts.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Projectile.h" +#include "../Effect.h" +#include "Vehicle.h" +#include "VehicleParts.h" +#include "../ai/AI_Manager.h" + +/*********************************************************************** + + rvVehiclePart + +***********************************************************************/ + +ABSTRACT_DECLARATION( idClass, rvVehiclePart ) +END_CLASS + +/* +===================== +rvVehiclePart::Init +===================== +*/ +bool rvVehiclePart::Init ( rvVehiclePosition* _position, const idDict& args, s_channelType _soundChannel ) { + spawnArgs.Copy ( args ); + + soundChannel = _soundChannel; + position = _position; + parent = position->GetParent(); + fl.active = false; + fl.useCenterMass = false; + + return true; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehiclePart::Spawn ( void ) { + // Get joint for orienting the part + joint = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint", "" ) ); + + // More position information + fl.useCenterMass = spawnArgs.GetBool ( "useCenterMass", "0" ); + spawnArgs.GetVector ( "offset", "0 0 0", localOffset ); + + fl.active = false; + + fl.useViewAxis = spawnArgs.GetBool( "useViewAxis", "0" ); + + // Initialize the origin so we can determine which side of the vehicle we are on + UpdateOrigin ( ); + + // Determine if this part is on the left and/or front side of the vehicle + idVec3 localOrigin; + localOrigin = (worldOrigin - parent->GetPhysics()->GetCenterMass()); + fl.front = (localOrigin * parent->GetPhysics()->GetAxis()[0] < 0.0f); + fl.left = (localOrigin * parent->GetPhysics()->GetAxis()[1] < 0.0f); +} + +/* +================ +rvVehiclePart::UpdateOrigin +================ +*/ +void rvVehiclePart::UpdateOrigin ( void ) { + if ( joint != INVALID_JOINT ) { + parent->GetJointWorldTransform ( joint, gameLocal.time, worldOrigin, worldAxis ); + } else { + if ( fl.useViewAxis ) { + worldAxis = parent->viewAxis; + } else { + worldAxis = parent->GetPhysics()->GetAxis(); + } + worldOrigin = fl.useCenterMass ? parent->GetPhysics()->GetCenterMass() : parent->GetPhysics()->GetOrigin(); + } + + worldOrigin += (localOffset * worldAxis); + + // Include this part in the total bounds + // FIXME: bounds are local + parent->AddToBounds ( worldOrigin ); +} + +/* +================ +rvVehiclePart::Save +================ +*/ +void rvVehiclePart::Save ( idSaveGame* savefile ) const { + savefile->Write( &fl, sizeof( fl ) ); + + savefile->WriteDict ( &spawnArgs ); + parent.Save ( savefile ); + savefile->WriteJoint ( joint ); + savefile->WriteInt ( soundChannel ); + savefile->WriteInt ( parent->GetPositionIndex ( position ) ); + + savefile->WriteVec3 ( worldOrigin ); + savefile->WriteMat3 ( worldAxis ); + savefile->WriteVec3 ( localOffset ); +} + +/* +================ +rvVehiclePart::Restore +================ +*/ +void rvVehiclePart::Restore ( idRestoreGame* savefile ) { + int temp; + + savefile->Read( &fl, sizeof( fl ) ); + + savefile->ReadDict ( &spawnArgs ); + parent.Restore ( savefile ); + savefile->ReadJoint ( joint ); + savefile->ReadInt ( soundChannel ); + + savefile->ReadInt ( temp ); + position = parent->GetPosition ( temp ); + + savefile->ReadVec3 ( worldOrigin ); + savefile->ReadMat3 ( worldAxis ); + savefile->ReadVec3 ( localOffset ); +} + +/*********************************************************************** + + rvVehicleSound + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleSound ) +END_CLASS + +/* +===================== +rvVehicleSound::rvVehicleSound +===================== +*/ +rvVehicleSound::rvVehicleSound ( void ) { + memset( &refSound, 0, sizeof( refSound ) ); + refSound.referenceSoundHandle = -1; + fade = false; + autoActivate = true; +} + +/* +===================== +rvVehicleSound::~rvVehicleSound +===================== +*/ +rvVehicleSound::~rvVehicleSound ( void ) { + Stop(); + soundSystem->FreeSoundEmitter( SOUNDWORLD_GAME, refSound.referenceSoundHandle, true ); + refSound.referenceSoundHandle = -1; +} + +/* +===================== +rvVehiclePart::Spawn +===================== +*/ +void rvVehicleSound::Spawn ( void ) { + soundName = spawnArgs.GetString ( "snd_loop" ); + + spawnArgs.GetVec2 ( "freqShift", "1 1", freqShift ); + spawnArgs.GetVec2 ( "volume", "0 0", volume ); + + // Temp fix for volume + volume[0] = idMath::dBToScale( volume[0] ); + volume[1] = idMath::dBToScale( volume[1] ); + + declManager->FindSound ( soundName )->GetParms ( &refSound.parms ); +} + +/* +===================== +rvVehicleSound::RunPostPhysics +===================== +*/ +void rvVehicleSound::RunPostPhysics ( void ) { + Update ( ); +} + +/* +===================== +rvVehicleSound::Activate +===================== +*/ +void rvVehicleSound::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( autoActivate ) { + if ( active ) { + Play ( ); + } else { + Stop ( ); + } + } +} + +/* +===================== +rvVehicleSound::Play +===================== +*/ +void rvVehicleSound::Play ( void ) { + if ( !soundName.Length ( ) ) { + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if ( !emitter ) { + refSound.referenceSoundHandle = soundSystem->AllocSoundEmitter( SOUNDWORLD_GAME ); + } + + Attenuate ( 0.0f, 0.0f ); + + Update ( true ); + + emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms ); + emitter->StartSound ( declManager->FindSound( soundName ), soundChannel, 0, 0 ); + } +} + +/* +===================== +rvVehicleSound::Stop +===================== +*/ +void rvVehicleSound::Stop ( void ) { + if ( IsPlaying ( ) ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + emitter->StopSound( soundChannel ); + } + } +} + +/* +===================== +rvVehicleSound::Update +===================== +*/ +void rvVehicleSound::Update ( bool force ) { + if ( !force && !IsPlaying ( ) ) { + return; + } + + if ( fade && currentVolume.IsDone ( gameLocal.time ) ) { + Stop ( ); + return; + } + + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( !emitter ) { + return; + } + + UpdateOrigin ( ); + + refSound.parms.volume = currentVolume.GetCurrentValue ( gameLocal.time ); + refSound.parms.frequencyShift = currentFreqShift.GetCurrentValue ( gameLocal.time ); + emitter->ModifySound ( soundChannel, &refSound.parms ); + + refSound.origin = worldOrigin; + // bdube: please put something sensible here if you want doppler from the sound system + refSound.velocity = vec3_origin; + emitter->UpdateEmitter( refSound.origin, refSound.velocity, parent->entityNumber + 1, &refSound.parms ); +} + +/* +===================== +rvVehicleSound::Attenuate +===================== +*/ +void rvVehicleSound::Attenuate ( float volumeAttenuate, float freqAttenuate ) { + float f; + + fade = false; + + f = volume[0] + (volume[1]-volume[0]) * volumeAttenuate; + currentVolume.Init ( gameLocal.time, 100, currentVolume.GetCurrentValue(gameLocal.time), f ); + + f = freqShift[0] + (freqShift[1]-freqShift[0]) * freqAttenuate; + currentFreqShift.Init ( gameLocal.time, 100, currentFreqShift.GetCurrentValue(gameLocal.time), f ); +} + +/* +===================== +rvVehicleSound::Fade +===================== +*/ +void rvVehicleSound::Fade ( int duration, float toVolume, float toFreq ) { + currentVolume.Init ( gameLocal.time, duration, currentVolume.GetCurrentValue(gameLocal.time), toVolume ); + currentFreqShift.Init ( gameLocal.time, duration, currentFreqShift.GetCurrentValue(gameLocal.time), toFreq ); +} + +/* +===================== +rvVehicleSound::Save +===================== +*/ +void rvVehicleSound::Save ( idSaveGame* savefile ) const { + savefile->WriteVec2 ( volume ); + savefile->WriteVec2 ( freqShift ); + savefile->WriteString ( soundName ); + savefile->WriteRefSound ( refSound ); + + savefile->WriteInterpolate ( currentVolume ); + savefile->WriteInterpolate ( currentFreqShift ); + + savefile->WriteBool ( fade ); + savefile->WriteBool ( autoActivate ); +} + +/* +===================== +rvVehicleSound::Restore +===================== +*/ +void rvVehicleSound::Restore ( idRestoreGame* savefile ) { + savefile->ReadVec2 ( volume ); + savefile->ReadVec2 ( freqShift ); + savefile->ReadString ( soundName ); + savefile->ReadRefSound ( refSound ); + + savefile->ReadInterpolate ( currentVolume ); + savefile->ReadInterpolate ( currentFreqShift ); + + savefile->ReadBool ( fade ); + savefile->ReadBool ( autoActivate ); +} + +//---------------------------------------------------------------- +// +// rvVehicleLight +// +//---------------------------------------------------------------- + +CLASS_DECLARATION( rvVehiclePart, rvVehicleLight ) +END_CLASS + +/* +===================== +rvVehicleLight::rvVehicleLight +===================== +*/ +rvVehicleLight::rvVehicleLight ( void ) { + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::~rvVehicleLight +===================== +*/ +rvVehicleLight::~rvVehicleLight ( void ) { + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + } +} + +/* +===================== +rvVehicleLight::Spawn +===================== +*/ +void rvVehicleLight::Spawn ( void ) { + idVec3 color; + const char* temp; + + memset ( &renderLight, 0, sizeof(renderLight) ); + + renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); + renderLight.pointLight = spawnArgs.GetBool( "pointlight", "1" ); + + spawnArgs.GetVector( "color", "0 0 0", color ); + renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; + renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; + renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; + renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + renderLight.lightRadius[0] = renderLight.lightRadius[1] = + renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "radius" ); + + if ( !renderLight.pointLight ) { + renderLight.target = spawnArgs.GetVector( "target" ); + renderLight.up = spawnArgs.GetVector( "up" ); + renderLight.right = spawnArgs.GetVector( "right" ); + renderLight.end = spawnArgs.GetVector( "target" );; + } + + // Hide flare surface if there is one + temp = spawnArgs.GetString ( "flaresurface", "" ); + if ( temp && *temp ) { + parent->ProcessEvent ( &EV_HideSurface, temp ); + } + + // Sounds shader when turning light + spawnArgs.GetString ( "snd_on", "", soundOn ); + + // Sound shader when turning light off + spawnArgs.GetString ( "snd_off", "", soundOff); + + lightOn = spawnArgs.GetBool( "start_on", "1" ); + lightHandle = -1; +} + +/* +===================== +rvVehicleLight::RunPostPhysics +===================== +*/ +void rvVehicleLight::RunPostPhysics ( void ) { + if ( lightHandle == -1 || !lightOn ) { + return; + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::UpdateLightDef +===================== +*/ +void rvVehicleLight::UpdateLightDef ( void ) { + UpdateOrigin ( ); + + renderLight.origin = worldOrigin; + renderLight.axis = worldAxis; + + if ( lightHandle == -1 ) { + return; + } + gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); +} + +/* +===================== +rvVehicleLight::Activate +===================== +*/ +void rvVehicleLight::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active && lightOn ) { + TurnOn ( ); + } else { + TurnOff ( ); + } +} + +/* +===================== +rvVehicleLight::TurnOff +===================== +*/ +void rvVehicleLight::TurnOff ( void ) { + const char* surface; + + if ( lightHandle != -1 ) { + gameRenderWorld->FreeLightDef( lightHandle ); + lightHandle = -1; + + // Play off sound + if ( soundOff.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOff ), soundChannel, 0, false, NULL ); + } + } + + // Hide flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_HideSurface, surface ); + } +} + +/* +===================== +rvVehicleLight::TurnOn +===================== +*/ +void rvVehicleLight::TurnOn ( void ) { + const char* surface; + + if ( lightHandle == -1 ) { + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + // Play off sound + if ( soundOn.Length() ) { + parent->StartSoundShader ( declManager->FindSound ( soundOn ), soundChannel, 0, false, NULL ); + } + + // Show flare surface if there is one + surface = spawnArgs.GetString ( "flaresurface", "" ); + if ( surface && *surface ) { + parent->ProcessEvent ( &EV_ShowSurface, surface ); + } + + UpdateLightDef ( ); +} + +/* +===================== +rvVehicleLight::Impulse +===================== +*/ +void rvVehicleLight::Impulse ( int impulse ) { + switch ( impulse ) { + case IMPULSE_50: + if ( lightOn ) { + lightOn = false; + TurnOff ( ); + } else { + lightOn = true; + TurnOn ( ); + } + break; + } +} + +/* +===================== +rvVehicleLight::Save +===================== +*/ +void rvVehicleLight::Save ( idSaveGame* savefile ) const { + savefile->WriteRenderLight ( renderLight ); + savefile->WriteInt ( lightHandle ); + savefile->WriteBool ( lightOn ); + savefile->WriteString ( soundOn ); + savefile->WriteString ( soundOff ); +} + +/* +===================== +rvVehicleLight::Restore +===================== +*/ +void rvVehicleLight::Restore ( idRestoreGame* savefile ) { + savefile->ReadRenderLight ( renderLight ); + savefile->ReadInt ( lightHandle ); + if ( lightHandle != -1 ) { + //get the handle again as it's out of date after a restore! + lightHandle = gameRenderWorld->AddLightDef( &renderLight ); + } + + savefile->ReadBool ( lightOn ); + savefile->ReadString ( soundOn ); + savefile->ReadString ( soundOff ); +} + +/*********************************************************************** + + rvVehicleWeapon + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleWeapon ) +END_CLASS + +#define WEAPON_DELAY_FIRE 500 + +/* +===================== +rvVehicleWeapon::rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::rvVehicleWeapon ( void ) { +#ifdef _XENON + bestEnemy = 0; +#endif + nextFireTime = 0; + fireDelay = 0; + hitScanDef = NULL; + projectileDef = NULL; + animNum = 0; +} + +/* +===================== +rvVehicleWeapon::~rvVehicleWeapon +===================== +*/ +rvVehicleWeapon::~rvVehicleWeapon ( void ) { + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + StopTargetEffect( ); +} + +/* +===================== +rvVehicleWeapon::Spawn +===================== +*/ +void rvVehicleWeapon::Spawn ( void ) { + int i; + idStr temp; + idVec3 color; + +#ifdef _XENON + bestEnemy = 0; +#endif + + launchFromJoint = spawnArgs.GetBool ( "launchFromJoint", "0" ); + lockScanning = spawnArgs.GetBool ( "lockScanning", "0" ); + lastLockTime = 0; + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + fireDelay = SEC2MS ( spawnArgs.GetFloat ( "firedelay" ) ); + spread = spawnArgs.GetFloat ( "spread" ); + jointIndex = 0; + count = spawnArgs.GetInt ( "count", "1" ); + lockRange = spawnArgs.GetFloat ( "lockrange", "0" ); + ammoPerCharge = spawnArgs.GetInt ( "ammopercharge", "-1" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargetime" ) ); + currentAmmo = ammoPerCharge; + muzzleFlashHandle = -1; + + if( spawnArgs.GetString("anim", "", temp) && *temp ) { + animNum = parent->GetAnimator()->GetAnim( temp ); + animChannel = spawnArgs.GetInt( "animChannel" ); + } + + spawnArgs.GetVector ( "force", "0 0 0", force ); + + // Vehicle guns can have multiple joints to fire from. They will be cycled through + // when firing. + joints.Append ( joint ); + for ( i = 2; ; i ++ ) { + jointHandle_t joint2; + joint2 = parent->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint%d", i ) ) ); + if ( joint2 == INVALID_JOINT ) { + break; + } + + joints.Append ( joint2 ); + } + + // Muzzle Flash + memset ( &muzzleFlash, 0, sizeof(muzzleFlash) ); + + spawnArgs.GetVector ( "flashOffset", "0 0 0", muzzleFlashOffset ); + muzzleFlash.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_flashShader", "lights/muzzleflash" ), false ); + muzzleFlash.pointLight = spawnArgs.GetBool( "flashPointLight", "1" ); +// RAVEN BEGIN +// dluetscher: added detail levels to render lights + muzzleFlash.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; +// RAVEN END + + spawnArgs.GetVector( "flashColor", "0 0 0", color ); + muzzleFlash.shaderParms[ SHADERPARM_RED ] = color[0]; + muzzleFlash.shaderParms[ SHADERPARM_GREEN ] = color[1]; + muzzleFlash.shaderParms[ SHADERPARM_BLUE ] = color[2]; + muzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; + + muzzleFlash.lightRadius[0] = muzzleFlash.lightRadius[1] = muzzleFlash.lightRadius[2] = (float)spawnArgs.GetInt( "flashRadius" ); + + if ( !muzzleFlash.pointLight ) { + muzzleFlash.target = spawnArgs.GetVector( "flashTarget" ); + muzzleFlash.up = spawnArgs.GetVector( "flashUp" ); + muzzleFlash.right = spawnArgs.GetVector( "flashRight" ); + muzzleFlash.end = spawnArgs.GetVector( "flashTarget" ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // get the brass def + idStr name; + brassDict = NULL; + if ( spawnArgs.GetString( "def_ejectBrass", "", name ) && *name ) { + brassDict = gameLocal.FindEntityDefDict( name, false ); + + if ( !brassDict ) { + gameLocal.Warning( "Unknown brass def '%s' for weapon on vehicle '%s'", name, position->mParent->name ); + } + } + + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + + // Zoom + zoomFov = spawnArgs.GetInt( "zoomFov", "-1" ); + zoomGui = uiManager->FindGui ( spawnArgs.GetString ( "gui_zoom", "" ), true ); + zoomTime = spawnArgs.GetFloat ( "zoomTime", ".15" ); +// wfl.zoomHideCrosshair = spawnArgs.GetBool ( "zoomHideCrosshair", "1" ); +} + +/* +===================== +rvVehicleWeapon::Activate +===================== +*/ +void rvVehicleWeapon::Activate ( bool activate ) { + rvVehiclePart::Activate ( activate ); + + nextFireTime = gameLocal.time + WEAPON_DELAY_FIRE; + +#ifdef _XENON + bestEnemy = 0; +#endif +} + +/* +===================== +rvVehicleWeapon::StopTargetEffect +===================== +*/ +void rvVehicleWeapon::StopTargetEffect ( void ) { + if ( targetEffect ) { + targetEffect->Stop( ); + targetEffect = NULL; + } +} + +/* +===================== +rvVehicleWeapon::UpdateLock +===================== +*/ +void rvVehicleWeapon::UpdateLock ( void ) { + +#ifdef _XENON + + bestEnemy = 0; + + // Handle auto-aim if it's enabled + if ( cvarSystem->GetCVarBool("pm_AimAssist") ) { + + const float testDist = 2000.0f; // huge because we're outdoors + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + + idVec3 start = position->GetEyeOrigin(); + idVec3 end = start + (position->GetEyeAxis().ToAngles().ToForward() * testDist); + + idBounds bounds( start ); + bounds.AddPoint( end ); + + idClipModel *clipModelList[ MAX_GENTITIES ]; + int numClipModels = gameLocal.ClipModelsTouchingBounds( player, bounds, -1, clipModelList, MAX_GENTITIES ); + + float bDist = testDist; + + float fovX, fovY; + gameLocal.CalcFov( player->CalcFov( true ), fovX, fovY ); + float dNear = cvarSystem->GetCVarFloat( "r_znear" ); + float dFar = testDist; + float size = dFar * idMath::Tan( DEG2RAD( fovY * 0.5f ) ) * float(cvarSystem->GetCVarInteger("pm_AimAssistFOV")) / 100.0f; + + idFrustum aimArea; + aimArea.SetOrigin( start ); + aimArea.SetAxis( position->GetEyeAxis() ); + aimArea.SetSize( dNear, dFar, size, size ); + + for ( int i = 0; i < numClipModels; i++ ) { + + idClipModel *clip = clipModelList[ i ]; + idEntity *ent = clip->GetEntity(); + + if ( ent->IsHidden() ) { + continue; + } + + bool isAI = ent->IsType( idAI::GetClassType() ); + bool isFriendly = false; + + if ( isAI ) { + if ( static_cast( ent )->team == player->team ) { + continue; + } + } else { + continue; + } + + float focusLength = (ent->GetPhysics()->GetOrigin() - start).LengthFast() - ent->GetPhysics()->GetBounds().GetRadius(); + + const idBounds &b = ent->GetPhysics()->GetAbsBounds(); + bool inside = aimArea.IntersectsBounds(b); + + if ( inside ) { + float dist = b.ShortestDistance(start); + if ( bDist > dist ) { + bDist = dist; + bestEnemy = (idActor *)ent; + } + } + } + } + } +#endif + + if ( !position->GetDriver() || parent->health <= 0 || !lockScanning) { + StopTargetEffect( ); + } else if ( lockScanning && position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //always update locking info + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player && position->GetDriver() == player ) { + // Update the guide effect + if ( targetEnt && targetEnt.IsValid() && targetEnt->health > 0 && targetEnt->IsType( idActor::GetClassType() ) ) { + idVec3 eyePos; + if ( targetJoint != INVALID_JOINT ) { + idMat3 jointAxis; + targetEnt->GetAnimator()->GetJointTransform( targetJoint, gameLocal.GetTime(), eyePos, jointAxis ); + eyePos = targetEnt->GetRenderEntity()->origin + (eyePos*targetEnt->GetRenderEntity()->axis); + if ( !targetPos.Compare( vec3_origin ) ) { + jointAxis = jointAxis * targetEnt->GetRenderEntity()->axis; + eyePos += jointAxis[0]*targetPos[0]; + eyePos += jointAxis[1]*targetPos[1]; + eyePos += jointAxis[2]*targetPos[2]; + } + } else { + eyePos = static_cast(targetEnt.GetEntity())->GetEyePosition(); + eyePos += targetEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + eyePos *= 0.5f; + } + if ( targetEffect ) { + targetEffect->SetOrigin ( eyePos ); + targetEffect->SetAxis ( player->firstPersonViewAxis.Transpose() ); + } else { + targetEffect = gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_guide" ), eyePos, player->firstPersonViewAxis.Transpose(), true, vec3_origin, false ); + if ( targetEffect ) { + targetEffect->GetRenderEffect()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + targetEffect->GetRenderEffect()->allowSurfaceInViewID = position->GetDriver()->entityNumber + 1; + } + } + } else { + StopTargetEffect( ); + } + } + } +} + +/* +===================== +rvVehicleWeapon::RunPostPhysics +===================== +*/ +void rvVehicleWeapon::RunPostPhysics ( void ) { + if ( !IsActive() || !parent->IsShootingEnabled ( ) ) { + return; + } + + if ( currentAmmo <= 0 && currentCharge.IsDone( gameLocal.GetTime() ) ) { + currentAmmo = ammoPerCharge; + } + + UpdateLock(); + + if ( !(position->mInputCmd.buttons & BUTTON_ATTACK ) ) { + return; + } + + if ( gameLocal.time <= nextFireTime || !currentCharge.IsDone(gameLocal.GetTime()) ) { + return; + } + + // Gun animation? + if ( animNum ) { + parent->GetAnimator()->PlayAnim ( animChannel, animNum, gameLocal.time, 0 ); + } + + if( !spawnArgs.GetBool("launchOnFrameCommand") ) { + if (!Fire()) + { + return; + } + } + + nextFireTime = gameLocal.time + fireDelay; +} + +/* +================ +rvVehicleWeapon::WeaponFeedback +================ +*/ +void rvVehicleWeapon::WeaponFeedback( const idDict* dict ) { + if( !dict || !GetPosition() || !GetPosition()->IsOccupied() ) { + return; + } + + idActor* actor = GetPosition()->GetDriver(); + if( !actor || !actor->IsType( idPlayer::GetClassType() ) ) { + return; + } + + //abahr: This feels like a hack. I hate using def files for logic but it just seems the easiest way to do it + idPlayer* player = static_cast( actor ); + if( dict->GetInt("recoilTime") ) { + player->playerView.WeaponFireFeedback( dict ); + } + if( dict->GetInt("shakeTime") ) { + player->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + dict->GetInt("shakeTime")), dict->GetFloat("shakeMagnitude") ); + } + EjectBrass(); +} + +/* +================ +rvVehicleWeapon::MuzzleFlashLight +================ +*/ +void rvVehicleWeapon::MuzzleFlashLight( const idVec3& origin, const idMat3& axis ) { + if ( !muzzleFlash.lightRadius[0] ) { + return; + } + + muzzleFlash.origin = origin; + muzzleFlash.axis = axis; + + // these will be different each fire + muzzleFlash.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); + muzzleFlash.shaderParms[ SHADERPARM_DIVERSITY ] = parent->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ]; + + // the light will be removed at this time + muzzleFlashEnd = gameLocal.time + muzzleFlashTime; + + if ( muzzleFlashHandle != -1 ) { + gameRenderWorld->UpdateLightDef( muzzleFlashHandle, &muzzleFlash ); + } else { + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } +} + +/* +===================== +rvVehicleWeapon::UpdateCursorGUI +===================== +*/ +void rvVehicleWeapon::UpdateCursorGUI ( idUserInterface* gui ) const { + // cnicholson: I do't see a reason why this if statement is here. Its ALWAYS false and so this block never executed, + // thus if the player was in a vehicle, the crosshair was always the player's last held weapon, not the crosshair defined in the vehicle .def. + // So I commented the if part out. + //if ( spawnArgs.GetBool( "hide_crosshair", "0" ) ) { + gui->SetStateString ( "crossImage", spawnArgs.GetString ( "mtr_crosshair" ) ); + const idMaterial *material = declManager->FindMaterial( spawnArgs.GetString ( "mtr_crosshair" ) ); + if ( material ) { + material->SetSort( SS_GUI ); + } + + gui->SetStateString ( "crossColor", g_crosshairColor.GetString() ); + gui->SetStateInt ( "crossOffsetX", spawnArgs.GetInt ( "crosshairOffsetX", "0" ) ); + gui->SetStateInt ( "crossOffsetY", spawnArgs.GetInt ( "crosshairOffsetY", "0" ) ); + gui->StateChanged ( gameLocal.time ); + //} +} + +/* +===================== +rvVehicleWeapon::Save +===================== +*/ +void rvVehicleWeapon::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteInt ( nextFireTime ); + savefile->WriteInt ( fireDelay ); + savefile->WriteInt ( count ); + // TOSAVE: const idDict* projectileDef; + // TOSVAE: const idDict* hitScanDef; + + savefile->WriteFloat ( spread ); + savefile->WriteBool ( launchFromJoint ); + savefile->WriteBool ( lockScanning ); + savefile->WriteInt ( lastLockTime ); + savefile->WriteFloat ( lockRange ); + + savefile->WriteInt ( joints.Num() ); + for ( i = 0; i < joints.Num(); i ++ ) { + savefile->WriteJoint ( joints[i] ); + } + savefile->WriteInt ( jointIndex ); + + savefile->WriteVec3 ( force ); + + savefile->WriteInt ( ammoPerCharge ); + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( currentAmmo ); + savefile->WriteInterpolate ( currentCharge ); + + savefile->WriteRenderLight ( muzzleFlash ); + savefile->WriteInt ( muzzleFlashHandle ); + savefile->WriteInt ( muzzleFlashEnd ); + savefile->WriteInt ( muzzleFlashTime ); + savefile->WriteVec3 ( muzzleFlashOffset ); + + savefile->WriteInt ( animNum ); + savefile->WriteInt ( animChannel ); + + targetEnt.Save( savefile ); + savefile->WriteJoint( targetJoint ); + savefile->WriteVec3( targetPos ); + targetEffect.Save( savefile ); + // Don't save shaderFire or shaderReload, they are setup in Restore + + savefile->WriteInt( zoomFov ); + savefile->WriteUserInterface( zoomGui, true ); + savefile->WriteFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +void rvVehicleWeapon::Restore ( idRestoreGame* savefile ) { + int i; + int num; + idStr temp; + +#ifdef _XENON + bestEnemy = 0; +#endif + + savefile->ReadInt ( nextFireTime ); + savefile->ReadInt ( fireDelay ); + savefile->ReadInt ( count ); + savefile->ReadFloat ( spread ); + savefile->ReadBool ( launchFromJoint ); + savefile->ReadBool ( lockScanning ); + savefile->ReadInt ( lastLockTime ); + savefile->ReadFloat ( lockRange ); + + savefile->ReadInt ( num ); + joints.Clear ( ); + joints.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + savefile->ReadJoint ( joints[i] ); + } + savefile->ReadInt ( jointIndex ); + + savefile->ReadVec3 ( force ); + + savefile->ReadInt ( ammoPerCharge ); + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( currentAmmo ); + savefile->ReadInterpolate( currentCharge ); + + savefile->ReadRenderLight ( muzzleFlash ); + savefile->ReadInt ( muzzleFlashHandle ); + if ( muzzleFlashHandle != -1 ) { + //get the handle again as it's out of date after a restore! + muzzleFlashHandle = gameRenderWorld->AddLightDef( &muzzleFlash ); + } + + savefile->ReadInt ( muzzleFlashEnd ); + savefile->ReadInt ( muzzleFlashTime ); + savefile->ReadVec3 ( muzzleFlashOffset ); + + savefile->ReadInt ( animNum ); + savefile->ReadInt ( animChannel ); + + if ( spawnArgs.GetString ( "def_hitscan", "", temp ) ) { + hitScanDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_hitscan" ) ); + } else { + projectileDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_projectile" ) ); + } + + shaderFire = declManager->FindSound ( spawnArgs.GetString ( "snd_fire" ), false ); + shaderReload = declManager->FindSound ( spawnArgs.GetString ( "snd_reload" ), false ); + + // Brass Def + brassDict = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_ejectBrass" ), false ); + spawnArgs.GetVector ( "ejectOffset", "0 0 0", brassEjectOffset ); + brassEjectJoint = parent->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_view_eject", "eject" ) ); + if ( brassDict ) { + brassEjectDelay = SEC2MS( brassDict->GetFloat( "delay", "0.01" ) ); + } + brassEjectNext = 0; + + targetEnt.Restore( savefile ); + savefile->ReadJoint( targetJoint ); + savefile->ReadVec3( targetPos ); + targetEffect.Restore( savefile ); + + savefile->ReadInt( zoomFov ); + savefile->ReadUserInterface( zoomGui, &spawnArgs ); + savefile->ReadFloat( zoomTime ); +} + +/* +===================== +rvVehicleWeapon::Restore +===================== +*/ +bool rvVehicleWeapon::Fire() { + if ( muzzleFlashHandle != -1 && gameLocal.time >= muzzleFlashEnd ) { + gameRenderWorld->FreeLightDef( muzzleFlashHandle ); + muzzleFlashHandle = -1; + } + +// twhitaker: moved to rvVehicleWeapon::RunPostPhysics so that the HUD would update without having to fire the gun +// if ( currentAmmo == 0 ) { +// currentAmmo = ammoPerCharge; +// } + + LaunchProjectiles(); + + if ( currentAmmo == -1 ) { + currentCharge.Init ( gameLocal.time, fireDelay, 0.0, 1.0f ); + } else { + currentAmmo--; + if ( currentAmmo <= 0 ) { + currentAmmo = 0; + if ( chargeTime ) { + parent->StartSoundShader( shaderReload, SND_CHANNEL_ANY, 0, false, NULL ); + currentCharge.Init ( gameLocal.time, chargeTime, 0.0f, 1.0f ); + } + } + } + return true; +} + +#ifdef _XENON +/* +===================== +rvVehicleWeapon::AutoAim +===================== +*/ +void rvVehicleWeapon::AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ) { + + if ( player ) { + + // If we have a enemy selected, handle aim correction + if ( bestEnemy ) { + + idVec3 dif = bestEnemy->GetRenderEntity()->origin - origin; + idVec3 muzzleDest = origin + (dir * dif.Length() ); + + // Transform start and end into the enemy's body space + idVec3 localStart = bestEnemy->GetRenderEntity()->axis / (origin - bestEnemy->GetRenderEntity()->origin); + idVec3 localEnd = bestEnemy->GetRenderEntity()->axis / (muzzleDest - bestEnemy->GetRenderEntity()->origin); + + if ( bestEnemy->GetAnimator() ) { + int numJoints = bestEnemy->GetAnimator()->NumJoints(); + + if ( numJoints ) { + + jointHandle_t nearJoint = bestEnemy->GetAnimator()->GetNearestJoint( localStart, localEnd, gameLocal.time, 0, numJoints ); + + // If we found a valid joint to snap to... + if ( nearJoint > 0 ) { + idMat3 dummy; + bestEnemy->GetJointWorldTransform( nearJoint, gameLocal.time, muzzleDest, dummy ); + dir = muzzleDest - origin; + dir.Normalize(); + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + } + } else { + dir = bestEnemy->GetRenderEntity()->origin - origin; + dir.Normalize(); + } + + } + } +} +#endif + +/* +===================== +rvVehicleWeapon::LaunchHitScan +===================== +*/ +void rvVehicleWeapon::LaunchHitScan( const idVec3& origin, const idVec3& _dir, const idVec3& jointOrigin ) { + idPlayer* player = 0; + + idVec3 dir = _dir; + + // Let the AI know about the new attack + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + gameLocal.HitScan ( *hitScanDef, origin, dir, jointOrigin, position->GetDriver(), false, 1.0f, parent ); +} + +/* +===================== +rvVehicleWeapon::LaunchProjectile +===================== +*/ +void rvVehicleWeapon::LaunchProjectile( const idVec3& origin, const idVec3& _dir, const idVec3& pushVelocity ) { + + idPlayer *player = 0; + idEntity* ent; + idProjectile* projectile; + + idVec3 dir = _dir; + + gameLocal.SpawnEntityDef ( *projectileDef, &ent ); + if ( !ent ) { + return; + } + + if ( !gameLocal.isMultiplayer ) { + if ( position->GetDriver() && position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + player = dynamic_cast(position->GetDriver()); + if ( player ) { + aiManager.ReactToPlayerAttack ( player, origin, dir ); + } + } + } + +#ifdef _XENON + + AutoAim( player, origin, dir ); + +#endif + + + projectile = ( idProjectile * )ent; + projectile->Create( position->GetDriver(), origin, dir, parent ); + projectile->Launch( origin, dir, pushVelocity, 0.0f, 1.0f ); + + if ( projectile->IsType ( idGuidedProjectile::GetClassType() ) ) { + if ( spawnArgs.GetBool("guideTowardsDir") && (!targetEnt || targetJoint == INVALID_JOINT) ) { +#ifndef _XENON + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); +#else + if ( bestEnemy ) { + static_cast(projectile)->GuideTo ( origin, dir ); + } else { + static_cast(projectile)->GuideTo ( position->GetEyeOrigin(), position->GetEyeAxis()[0] ); + } +#endif + + } else if ( targetEnt ) { + static_cast(projectile)->GuideTo ( targetEnt, targetJoint, targetPos ); + } else { + static_cast(projectile)->GuideTo ( targetPos ); + } + } +} + +/* +===================== +rvVehicleWeapon::LaunchProjectiles +===================== +*/ +void rvVehicleWeapon::LaunchProjectiles() { + idVec3 jointOrigin; + idMat3 jointAxis; + idVec3 origin; + idMat3 axis; + + float spreadRad = DEG2RAD( spread ); + idVec3 dir; + float ang = 0.0f; + float spin = 0.0f; + + parent->GetJointWorldTransform ( joints[jointIndex], gameLocal.time, jointOrigin, jointAxis ); + MuzzleFlashLight ( jointOrigin + muzzleFlashOffset * jointAxis, jointAxis ); + + if( launchFromJoint ) { + origin = jointOrigin; + axis = jointAxis; + } else { + origin = position->GetEyeOrigin(); + axis = position->GetEyeAxis(); + } + + if ( !lockScanning || !targetEnt || !position->GetDriver() || !position->GetDriver()->IsType( idPlayer::GetClassType() ) ) { + //don't do this continuously, so have to do it here + GetLockInfo( position->GetEyeOrigin(), position->GetEyeAxis() ); + } + + parent->StartSoundShader( shaderFire, SND_CHANNEL_WEAPON, 0, false, NULL ); + + parent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_muzzleflash" ), joints[jointIndex], vec3_origin, mat3_identity ); + + gameLocal.AlertAI( parent ); + + for( int i = 0; i < count; i ++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = axis[0] + axis[ 2 ] * ( ang * idMath::Sin( spin ) ) - axis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( g_debugWeapon.GetBool() ) { + gameRenderWorld->DebugLine ( colorBlue, origin, origin + dir * 10000.0f, 10000 ); + } + + if ( hitScanDef ) { + LaunchHitScan( origin, dir, jointOrigin ); + } else { + LaunchProjectile( origin, dir, parent->GetPhysics()->GetPushedLinearVelocity() ); + } + } + + jointIndex = (jointIndex+1) % joints.Num(); + + parent->GetPhysics()->ApplyImpulse ( 0, origin, force * axis ); + + WeaponFeedback( &spawnArgs ); +} + +/* +===================== +rvVehicleWeapon::GetLockInfo +===================== +*/ +void rvVehicleWeapon::GetLockInfo( const idVec3& eyeOrigin, const idMat3& eyeAxis ) { + if ( lastLockTime < gameLocal.GetTime() - 2000 + || (targetEnt && (!targetEnt.IsValid() || targetEnt.GetEntity()->health <= 0)) ) { + targetEnt = NULL; + targetJoint = INVALID_JOINT; + targetPos.Zero (); + } + + if ( lockRange > 0.0f ) { + idVec3 end; + trace_t tr; + bool lockFound = false; + idEntity* newTargetEnt = NULL; + jointHandle_t newTargetJoint = INVALID_JOINT; + + end = eyeOrigin + eyeAxis[0] * lockRange; +// gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_BOUNDINGBOX, NULL ); + gameLocal.TracePoint( parent.GetEntity(), tr, eyeOrigin, end, MASK_SHOT_RENDERMODEL, NULL ); + + if ( tr.fraction < 1.0 ) { + newTargetEnt = gameLocal.entities[ tr.c.entityNum ]; + lockFound = true; + + // Make sure the target is an actor and is alive + if ( !(newTargetEnt->IsType ( idActor::GetClassType() ) || newTargetEnt->IsType ( idProjectile::GetClassType() )) || newTargetEnt->health <= 0 ) { + newTargetEnt = NULL; + lockFound = false; + } else { + //see if there's a joint to lock onto + if ( newTargetEnt->spawnArgs.MatchPrefix ( "lockJoint" ) ) { + jointHandle_t testJointHandle; + idVec3 testJointOffset; + idStr lockJointName; + idVec3 jointOrg; + idMat3 jointAxis; + + int lockJointNum = 1; + float bestDist = 100.0f; + float dist = 0.0f; + + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",lockJointNum), NULL ); + while ( lockJointName.Length() ) { + testJointHandle = newTargetEnt->GetAnimator()->GetJointHandle( lockJointName ); + if ( testJointHandle != INVALID_JOINT ) { + newTargetEnt->GetAnimator()->GetJointTransform( testJointHandle, gameLocal.GetTime(), jointOrg, jointAxis ); + jointOrg = newTargetEnt->GetRenderEntity()->origin + (jointOrg*newTargetEnt->GetRenderEntity()->axis); + jointAxis = jointAxis * newTargetEnt->GetRenderEntity()->axis; + testJointOffset = newTargetEnt->spawnArgs.GetVector( va("lockJointOffset%d",lockJointNum), "0 0 0" ); + jointOrg += jointAxis*testJointOffset; + dist = (jointOrg - tr.endpos).Length(); + if ( dist < bestDist ) { + bestDist = dist; + newTargetJoint = testJointHandle; + targetPos = testJointOffset; + } + } + lockJointName = newTargetEnt->spawnArgs.GetString( va("lockJoint%d",++lockJointNum), NULL ); + } + } + } + if ( spawnArgs.GetBool("guideTowardsDir") ) { + if ( newTargetJoint == INVALID_JOINT ) { + newTargetEnt = NULL; + lockFound = false; + } + } + } else if ( spawnArgs.GetBool( "guideTowardsDir" ) && !targetEnt ) { + targetPos = tr.endpos; + } + if ( lockFound ) { + targetEnt = newTargetEnt; + targetJoint = newTargetJoint; + lastLockTime = gameLocal.GetTime(); + } + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow ( colorGreen, eyeOrigin, end, 3, 10000 ); + gameRenderWorld->DebugArrow ( colorWhite, eyeOrigin, tr.endpos, 4, 10000 ); + } + } +} + +/* +===================== +rvVehicleWeapon::EjectBrass +===================== +*/ +void rvVehicleWeapon::EjectBrass ( void ) { + if ( !brassDict || gameLocal.time > brassEjectNext || g_brassTime.GetFloat() <= 0.0f || gameLocal.isMultiplayer || brassEjectJoint == INVALID_JOINT || !brassDict->GetNumKeyVals() ) { + return; + } + + idMat3 axis; + idVec3 origin; + idVec3 linear_velocity; + idVec3 angular_velocity; + int brassTime; + + if ( !parent->GetJointWorldTransform( brassEjectJoint, gameLocal.time, origin, axis ) ) { + return; + } + + brassEjectNext += brassEjectDelay; + + // Spawn the client side moveable for the brass + idVec3 offset; + idMat3 playerViewAxis; + gameLocal.GetPlayerView( offset, playerViewAxis ); + rvClientMoveable* cent = NULL; + gameLocal.SpawnClientEntityDef( *brassDict, (rvClientEntity**)(¢), false ); + + if( !cent ) { + return; + } + + cent->SetOrigin ( origin + axis * brassEjectOffset ); + cent->SetAxis ( playerViewAxis ); + cent->SetOwner ( position->GetDriver() ); + + // Depth hack the brass to make sure it clips in front of view weapon properly + cent->GetRenderEntity()->weaponDepthHackInViewID = position->GetDriver()->entityNumber + 1; + + // Clear the depth hack soon after it clears the view + cent->PostEventMS ( &CL_ClearDepthHack, 200 ); + + // Fade the brass out so they dont accumulate + brassTime =(int)SEC2MS(g_brassTime.GetFloat() / 6.0f); + cent->PostEventMS ( &CL_FadeOut, brassTime, brassTime ); + + // Generate a good velocity for the brass + idVec3 linearVelocity = brassDict->GetVector("linear_velocity").Random( brassDict->GetVector("linear_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetLinearVelocity( position->GetDriver()->GetPhysics()->GetLinearVelocity() + linearVelocity * cent->GetPhysics()->GetAxis() ); + idAngles angularVelocity = brassDict->GetAngles("angular_velocity").Random( brassDict->GetVector("angular_velocity_range"), gameLocal.random ); + cent->GetPhysics()->SetAngularVelocity( angularVelocity.ToAngularVelocity() * cent->GetPhysics()->GetAxis() ); +} + +/*********************************************************************** + + rvVehicleTurret + +***********************************************************************/ + +#define TURRET_MOVESOUND_FADE 200 + +CLASS_DECLARATION( rvVehiclePart, rvVehicleTurret ) +END_CLASS + +/* +===================== +rvVehicleTurret::rvVehicleTurret +===================== +*/ +rvVehicleTurret::rvVehicleTurret ( void ) { + moveTime = 0; + soundPart = -1; +} + +/* +===================== +rvVehicleTurret::Spawn +===================== +*/ +void rvVehicleTurret::Spawn ( void ) { + angles[0][PITCH] = spawnArgs.GetFloat ( "minpitch", "0" ); + angles[1][PITCH] = spawnArgs.GetFloat ( "maxpitch", "0" ); + axisMap[PITCH] = spawnArgs.GetInt ( "pitch", "-1" ); + invert[PITCH] = spawnArgs.GetBool ( "pitchinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][ROLL] = spawnArgs.GetFloat ( "minroll", "0" ); + angles[1][ROLL] = spawnArgs.GetFloat ( "maxroll", "0" ); + axisMap[ROLL] = spawnArgs.GetInt ( "roll", "-1" ); + invert[ROLL] = spawnArgs.GetBool ( "rollinvert", "0" ) ? -1.0f : 1.0f; + + angles[0][YAW] = spawnArgs.GetFloat ( "minyaw", "0" ); + angles[1][YAW] = spawnArgs.GetFloat ( "maxyaw", "0" ); + axisMap[YAW] = spawnArgs.GetInt ( "yaw", "-1" ); + invert[YAW] = spawnArgs.GetBool ( "yawinvert", "0" ) ? -1.0f : 1.0f; + + turnRate = spawnArgs.GetFloat ( "turnrate", "360" ); + + currentAngles.Zero ( ); + + //the parent is *not* stuck on spawn. + parentStuck = false; + + + // Find the vehicle part for the turret sound + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } +} + +/* +===================== +rvVehicleTurret::Activate +===================== +*/ +void rvVehicleTurret::Activate ( bool active ) { + rvVehiclePart::Activate( active ); + + if ( !IsActive() && soundPart >= 0 ) { + static_cast(position->GetPart(soundPart))->Stop ( ); + } + +} + +/* +===================== +rvVehicleTurret::RunPostPhysics +===================== +*/ +void rvVehicleTurret::RunPostPhysics ( void ) { + int i; + idAngles inputAngles; + idAngles lastInputAngles; + idMat3 mat[3]; + idAngles oldAngles; + + if ( IsActive ( ) ) { + for ( i = 0; i < 3; i++ ) { + inputAngles[i] = SHORT2ANGLE( position->mInputCmd.angles[i] ); + lastInputAngles[i] = SHORT2ANGLE( position->mOldInputCmd.angles[i] ); + } + } else { + inputAngles.Zero ( ); + lastInputAngles.Zero ( ); + } + + oldAngles = currentAngles; + + mat[PITCH].Identity(); + mat[YAW].Identity(); + mat[ROLL].Identity(); + for ( i = 0; i < 3; i ++ ) { + if ( axisMap[i] != -1 ) { + float diff = (invert[i] * idMath::AngleDelta ( inputAngles[i], lastInputAngles[i] )); + + diff = SignZero( diff ) * idMath::ClampFloat ( 0.0f, turnRate * MS2SEC(gameLocal.GetMSec()), idMath::Fabs ( diff ) ); + if ( angles[0][i] == angles[1][i] ) { + currentAngles[axisMap[i]] = idMath::AngleNormalize360( currentAngles[axisMap[i]] + diff ); + } else { + currentAngles[axisMap[i]] = idMath::ClampFloat ( angles[0][i], angles[1][i], currentAngles[axisMap[i]] + diff ); + } + + idAngles angles; + angles.Zero(); + angles[axisMap[i]] = currentAngles[axisMap[i]]; + mat[axisMap[i]] = angles.ToMat3(); + } + } + + // Update the turret moving sound + if ( soundPart >= 0 ) { + float speed; + speed = idMath::Fabs( idMath::AngleDelta( oldAngles[YAW], currentAngles[YAW] ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[PITCH], currentAngles[PITCH] ) ) ); + speed = Max( speed, idMath::Fabs( idMath::AngleDelta( oldAngles[ROLL], currentAngles[ROLL] ) ) ); + + if ( speed ) { + if ( !moveTime ) { + static_cast(position->GetPart(soundPart))->Play ( ); + } + moveTime = gameLocal.time + TURRET_MOVESOUND_FADE; + } else if ( moveTime && gameLocal.time > moveTime ) { + static_cast(position->GetPart(soundPart))->Fade ( TURRET_MOVESOUND_FADE, 0.0f, 1.0f ); + moveTime = 0; + } + + // Update the volume of the turret move sound using the current speed of the + // turret as well as how long it has been since it has last moved. + if ( moveTime ) { + float f; + f = idMath::ClampFloat ( 0.0f, 1.0f, fabs(speed) / (turnRate * MS2SEC(gameLocal.GetMSec())) ); + static_cast(position->GetPart(soundPart))->Attenuate ( f, f ); + } + } + + // Rotate the turret with the mouse + parent->GetAnimator()->SetJointAxis( joint, JOINTMOD_LOCAL, mat[YAW] * mat[PITCH] * mat[ROLL] ); + + if ( g_vehicleMode.GetInteger() != 0 && joint != INVALID_JOINT && parent->GetAnimator()->GetJointHandle( spawnArgs.GetString( "alignment_joint" ) ) == joint ) { + idMat3 turretAxis; + idVec3 temp; + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + const idMat3 & vehicleAxis = parent->GetPhysics()->GetAxis(); + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + float dotRight = vehicleAxis[1] * turretAxis[alignmentAxis]; + float dotForward = vehicleAxis[0] * turretAxis[alignmentAxis]; + float acosForward = idMath::ACos( dotForward ); + + idAngles oldAngles = currentAngles; + idMat3 oldAxis = vehicleAxis; + + if ( idMath::Fabs(1.0f - dotForward) > VECTOR_EPSILON ) { + float sinus = idMath::Cos( dotForward ); + float power = idMath::Pow( sinus, spawnArgs.GetFloat( "alignment_power", "5" ) ); + float deg = idMath::ClampFloat( 0.0f, spawnArgs.GetFloat( "alignment_delta", "2.4" ), RAD2DEG( acosForward ) ) * power; + idAngles delta( 0, deg * SignZero(dotRight), 0 ); + + //move the tank + parent->GetPhysics()->SetAxis( (delta).ToMat3() * vehicleAxis ); + currentAngles -= delta; + trace_t tr; + + //trace the tank against the world. + idBounds traceBounds; + idVec3 raiseVector( 0, 0, 80); + idVec3 reduceVector( 0, 0, -16); + idVec3 parentOrigin = parent->GetPhysics()->GetOrigin(); + idVec3 parentVecForward = parent->GetPhysics()->GetAxis()[0]; + + traceBounds.Zero(); + traceBounds = parent->GetPhysics()->GetAbsBounds(); + traceBounds.ExpandSelf( reduceVector ); + + //zero the tracebounds-- abs bounds is the right size but not the right location :) + traceBounds.TranslateSelf( traceBounds.GetCenter() * -1 ); + + //raise the bounds up so the hover tank can hover. + traceBounds.TranslateSelf( raiseVector ); + + gameLocal.TraceBounds( parent, tr, parentOrigin, parentOrigin, traceBounds, MASK_SOLID | CONTENTS_VEHICLECLIP ,parent); + + //draw the debug turning bounds if we need to. It will be redder if there's collision. + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec4 vecColor(1.0f - ( tr.fraction) , 0.25f, (0.5f * tr.fraction), 1.0f); + gameRenderWorld->DebugBounds( vecColor, traceBounds, parentOrigin, 10, true ); + } + + //roll it back if it's colliding. + if( tr.fraction < 1.0f) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + gameLocal.Warning("Turret move caused vehicle block %d distance %f", tr.c.entityNum, tr.fraction ); + } + parent->GetPhysics()->SetAxis( oldAxis ); + currentAngles = oldAngles; + + //apply a push to the parent based on the direction of the collision? + parentStuck = true; + } + } + + } +} +/* +===================== +rvVehicleTurret::RunPrePhysics +===================== +*/ +void rvVehicleTurret::RunPrePhysics ( void ) { + + //in case we're stuck, apply an impulse to the parent based on the direction the turret is facing. This will help us pull out in the right direction. + if( parentStuck ) { + + idVec3 impulseForce( 0,0,0); + idMat3 turretAxis; + idVec3 temp; + int alignmentAxis = spawnArgs.GetInt( "alignment_axis" ); + parent->GetJointWorldTransform( joint, gameLocal.GetTime(), temp, turretAxis ); + impulseForce += ( turretAxis[alignmentAxis] * 50 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass()); + //impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * 25 * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + + // Apply the impulse to the parent entity + + parent->GetPhysics()->ApplyImpulse( 0, parent->GetPhysics()->GetOrigin(), impulseForce); + + parentStuck = false; + } + + +} + + +/* +===================== +rvVehicleTurret::Save +===================== +*/ +void rvVehicleTurret::Save ( idSaveGame* savefile ) const { + savefile->WriteBounds ( angles ); + savefile->WriteInt ( axisMap[0] ); + savefile->WriteInt ( axisMap[1] ); + savefile->WriteInt ( axisMap[2] ); + + savefile->WriteFloat ( invert[0] ); + savefile->WriteFloat ( invert[1] ); + savefile->WriteFloat ( invert[2] ); + + savefile->WriteAngles ( currentAngles ); + + savefile->WriteInt ( moveTime ); + savefile->WriteFloat ( turnRate ); + + savefile->WriteInt ( soundPart ); +} + +/* +===================== +rvVehicleTurret::Restore +===================== +*/ +void rvVehicleTurret::Restore ( idRestoreGame* savefile ) { + savefile->ReadBounds ( angles ); + savefile->ReadInt ( axisMap[0] ); + savefile->ReadInt ( axisMap[1] ); + savefile->ReadInt ( axisMap[2] ); + + savefile->ReadFloat ( invert[0] ); + savefile->ReadFloat ( invert[1] ); + savefile->ReadFloat ( invert[2] ); + + savefile->ReadAngles ( currentAngles ); + + savefile->ReadInt ( moveTime ); + savefile->ReadFloat ( turnRate ); + + savefile->ReadInt ( soundPart ); +} + +/*********************************************************************** + + rvVehicleHoverpad + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleHoverpad ) +END_CLASS + +rvVehicleHoverpad::rvVehicleHoverpad ( void ) { + clipModel = NULL; + effectDust = NULL; + soundPart = -1; + effectDustMaterialType = NULL; +} + +rvVehicleHoverpad::~rvVehicleHoverpad ( void ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + delete clipModel; +} + +/* +================ +rvVehicleHoverpad::Spawn +================ +*/ +void rvVehicleHoverpad::Spawn ( void ) { + float size; + + spawnArgs.GetFloat ( "size", "10", size ); + spawnArgs.GetFloat ( "height", "70", height ); + spawnArgs.GetFloat ( "dampen", "10", dampen ); + spawnArgs.GetFloat ( "forceRandom", "10", forceRandom ); + spawnArgs.GetFloat ( "thrustForward", "0", thrustForward ); + spawnArgs.GetFloat ( "thrustLeft", "0", thrustLeft ); + + maxRestAngle = idMath::Cos ( DEG2RAD(spawnArgs.GetFloat ( "maxRestAngle", "20" ) ) ); + + fadeTime = SEC2MS(spawnArgs.GetFloat ( "fadetime", ".5" )); + + effectDust = NULL; + atRest = true; + + delete clipModel; + clipModel = new idClipModel ( idTraceModel ( idBounds ( spawnArgs.GetVector("mins"),spawnArgs.GetVector("maxs")) ) ); + + force = spawnArgs.GetFloat ( "force", "1066" ); + forceUpTime = SEC2MS ( spawnArgs.GetFloat ( "forceUpTime", "1" ) ); + forceDownTime = SEC2MS ( spawnArgs.GetFloat ( "forceDownTime", "1" ) ); + forceTable = declManager->FindTable( spawnArgs.GetString ( "forceTable", "linear" ), false ); + + currentForce.Init ( 0, 0, 0, 0 ); + + // Is a sound part specified? + if ( *spawnArgs.GetString ( "snd_loop", "" ) ) { + soundPart = position->AddPart ( rvVehicleSound::GetClassType(), spawnArgs ); + static_cast(position->GetPart(soundPart))->SetAutoActivate ( false ); + } + + Activate ( false ); +} + +/* +================ +rvVehicleHoverpad::Activate +================ +*/ +void rvVehicleHoverpad::Activate ( bool active ) { + rvVehiclePart::Activate ( active ); + + if ( active ) { + currentForce.Init ( gameLocal.time, forceUpTime, currentForce.GetCurrentValue( gameLocal.time ), force ); + atRest = false; + } else { + currentForce.Init ( gameLocal.time, forceDownTime, currentForce.GetCurrentValue( gameLocal.time ), 0 ); + } +} + +/* +================ +rvVehicleHoverpad::RunPrePhysics + +Called before RunPhysics. Since impact velocities are altered by calls to ApplyImpulse +we need to cache them to ensure other hoverpads are not altering the data. The start +position of the hoverpad is also cached here for efficiency. +================ +*/ +void rvVehicleHoverpad::RunPrePhysics ( void ) { + impactInfo_t info; + + UpdateOrigin ( ); + + // Cache velocity at start point + parent->GetPhysics()->GetImpactInfo( 0, worldOrigin, &info ); + velocity = info.velocity; +} + +/* +================ +rvVehicleHoverpad::RunPhysics +================ +*/ +void rvVehicleHoverpad::RunPhysics ( void ) { + idVec3 end; + idMat3 axis; + trace_t tr; + idVec3 impulseForce; + idVec3 dampingForce; + float hoverForce; + float curlength; + + // Disable pad? + if ( !IsActive() && !atRest ) { + if ( parent->IsAtRest ( ) || currentForce.GetCurrentValue ( gameLocal.time ) <= 0.0f || parent->IsFlipped( ) || parent->IsStalled() ) { + if ( soundPart >= 0 ) { + float fadeVolume; + fadeVolume = idMath::dBToScale ( spawnArgs.GetFloat ( "fadevolume", "0" ) ); + static_cast(position->GetPart(soundPart))->Fade ( fadeTime, fadeVolume, spawnArgs.GetFloat ( "fadefreqshift", "0.1" ) ); + } + + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + atRest = true; + } + } + + // If the vehicle isnt activate then kill the effect on it and dont think + if ( atRest ) { + return; + } + + // Prepare for trace + axis[0] = -parent->GetPhysics()->GetAxis()[2]; + axis[1] = parent->GetPhysics()->GetAxis()[0]; + axis[2] = parent->GetPhysics()->GetAxis()[1]; + + // Always point the hover jets towards gravity + end = worldOrigin + (parent->GetPhysics()->GetGravityNormal ( ) * height); + + // Determine how far away the hoverpad is from the ground + gameLocal.Translation ( parent.GetEntity(), tr, worldOrigin, end, clipModel, axis, MASK_SOLID|CONTENTS_VEHICLECLIP, parent ); + if ( tr.fraction >= 1.0f && tr.endpos == end ) { + UpdateDustEffect ( worldOrigin, axis, 0.0f, NULL ); + return; + } + + // Determine spring properties from trace + tr.c.point = worldOrigin + (end-worldOrigin) * (tr.fraction + 0.001f); + curlength = height - (worldOrigin - tr.c.point).Length ( ); + + // Dampening + dampingForce = tr.c.point - worldOrigin; + dampingForce = ( dampen * ( (velocity * dampingForce) / (dampingForce * dampingForce) ) ) * dampingForce; + + // Random swap forces + float rnd = 0.0f; + if ( !position->mInputCmd.forwardmove && !position->mInputCmd.rightmove ) { + float angle = DEG2RAD( ( (float)( gameLocal.time % 5000 ) / 5000.0f ) * 360.f ); + if ( fl.left ) { + angle += 90; + } + if ( fl.front ) { + angle += 180; + } + rnd = forceRandom * idMath::Sin( angle ); + } + + // Determine how much force should be applied at the center of the hover spring + hoverForce = (currentForce.GetCurrentValue(gameLocal.time) + rnd) * parent->GetPhysics()->GetMass () * MS2SEC(gameLocal.GetMSec()); + + // If the slope under the hoverpad is < 30 degress then use the gravity vector to allow the hovertank to + // stop on slopes, otherwise use the vehicles axis which will cause it to slide down steep slopes + float f = (-parent->GetPhysics()->GetGravityNormal ( ) * tr.c.normal); + if ( f < maxRestAngle || (thrustForward && position->mInputCmd.forwardmove != 0 ) ) { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -axis[0] - dampingForce; + } else { + impulseForce = (forceTable->TableLookup ( curlength / height ) * hoverForce) * -parent->GetPhysics()->GetGravityNormal ( ) - dampingForce; + } + + // No thrust if movement is disabled + if ( parent->IsMovementEnabled ( ) ) { + idMat3 axis; + idVec3 offset; + jointHandle_t axisJoint = joint; + if ( axisJoint == INVALID_JOINT ) { + axisJoint = parent->GetAnimator()->GetJointHandle( "gun_pivot" ); + } + parent->GetJointWorldTransform( axisJoint, gameLocal.time, offset, axis ); + + // Add forward/backward thrust + if ( thrustForward ) { + if ( g_vehicleMode.GetInteger() == 2 ) { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * axis[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.forwardmove / 127.0f ) * parent->GetPhysics()->GetAxis()[0] * thrustForward * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } + } + + // Add right/left thrust. The front pads will add force to the right if the right button is pressed and the rear will do the opposite. If moving backward the directions are flipped again + if ( thrustLeft ) { + if ( !parent->IsStrafing() && g_vehicleMode.GetInteger() != 0 ) { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * -axis[2] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass (); + } else { + impulseForce += ( position->mInputCmd.rightmove / 127.0f ) * (position->mInputCmd.forwardmove<0?-1:1) * parent->GetPhysics()->GetAxis()[1] * thrustLeft * MS2SEC(gameLocal.msec) * parent->GetPhysics()->GetMass () * (fl.front ? 1 : -1); + } + } + } + + // Apply the impulse to the parent entity + parent->GetPhysics()->ApplyImpulse( 0, worldOrigin, impulseForce); + + // Update the position and intensity of the dust effect + UpdateDustEffect ( tr.c.point, tr.c.normal.ToMat3( ), (curlength / height), tr.c.materialType ); + + // Debug information + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, parent ) ) { + idVec3 offset; + offset = worldOrigin - parent->GetPhysics()->GetOrigin(); + offset *= parent->GetPhysics()->GetAxis().Transpose(); + gameDebug.AppendList ( "hover", va("%c%c %d\t%0.1f\t%0.1f\t%d\t", (offset[0]>0?'F':'B'),(offset[1]<0?'R':'L'), (int)impulseForce.Length(), curlength, dampingForce.Length(), (int)(velocity*axis[0] ) ) ); + } + + // Draw debug information + if ( g_debugVehicle.GetInteger() == 2 ) { + collisionModelManager->DrawModel ( clipModel->GetCollisionModel(), tr.c.point, axis, vec3_origin, mat3_identity, 0.0f ); + gameRenderWorld->DebugCircle ( colorGreen, tr.c.point, axis[0], 5, 10 ); + gameRenderWorld->DebugLine ( colorMagenta, worldOrigin, worldOrigin + idVec3(0,0,-height)); + gameRenderWorld->DebugLine ( colorWhite, worldOrigin, end ); + gameRenderWorld->DebugCircle ( colorBlue, worldOrigin, axis[0], 5, 10 ); + gameRenderWorld->DebugCircle ( colorBlue, end, axis[0], 5, 10 ); + + if ( thrustForward && position->mInputCmd.forwardmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[1] * 50.0f * (fl.front ? 1 : -1) * ( position->mInputCmd.rightmove / 127.0f ), 10); + } + + if ( thrustLeft && position->mInputCmd.rightmove != 0 ) { + gameRenderWorld->DebugArrow( colorCyan, worldOrigin, worldOrigin + parent->GetPhysics()->GetAxis()[0] * 50.0f * ( position->mInputCmd.forwardmove / 127.0f ), 10); + } + } +} + +/* +================ +rvVehicleHoverpad::UpdateDustEffect +================ +*/ +void rvVehicleHoverpad::UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ) { + if ( !effectDust || (mtype && effectDustMaterialType != mtype) ) { + if ( effectDust ) { + effectDust->Stop ( ); + effectDust = NULL; + } + + effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust", mtype ), origin, axis, true ); + effectDustMaterialType = mtype; + } + + if ( effectDust ) { + effectDust->Attenuate ( attenuation ); + effectDust->SetOrigin ( origin ); + effectDust->SetAxis ( axis ); + } + + // If there is a sound playing update it as well + if ( soundPart >= 0 ) { + if ( static_cast(position->GetPart(soundPart))->IsPlaying ( ) ) { + static_cast(position->GetPart(soundPart))->Attenuate ( attenuation, attenuation ); + } else { + static_cast(position->GetPart(soundPart))->Play ( ); + } + } +} + +/* +===================== +rvVehicleHoverpad::Save +===================== +*/ +void rvVehicleHoverpad::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( height ); + savefile->WriteFloat ( dampen ); + savefile->WriteBool ( atRest ); + + savefile->WriteBounds ( clipModel->GetBounds ( ) ); + + savefile->WriteInterpolate ( currentForce ); + savefile->WriteFloat ( force ); + savefile->WriteTable ( forceTable ); + savefile->WriteFloat ( forceRandom ); + + savefile->WriteFloat ( fadeTime ); + savefile->WriteVec3 ( velocity ); + + savefile->WriteFloat ( thrustForward ); + savefile->WriteFloat ( thrustLeft ); + + savefile->WriteFloat ( maxRestAngle ); + + savefile->WriteInt ( soundPart ); + + savefile->WriteInt ( forceUpTime ); + savefile->WriteInt ( forceDownTime ); + + effectDust.Save ( savefile ); + savefile->WriteMaterialType ( effectDustMaterialType ); +} + +/* +===================== +rvVehicleHoverpad::Restore +===================== +*/ +void rvVehicleHoverpad::Restore ( idRestoreGame* savefile ) { + idBounds bounds; + + savefile->ReadFloat ( height ); + savefile->ReadFloat ( dampen ); + savefile->ReadBool ( atRest ); + + delete clipModel; + savefile->ReadBounds ( bounds ); + clipModel = new idClipModel ( idTraceModel ( bounds ) ); + + savefile->ReadInterpolate ( currentForce ); + savefile->ReadFloat ( force ); + savefile->ReadTable ( forceTable ); + savefile->ReadFloat ( forceRandom ); + + savefile->ReadFloat ( fadeTime ); + savefile->ReadVec3 ( velocity ); + + savefile->ReadFloat ( thrustForward ); + savefile->ReadFloat ( thrustLeft ); + + savefile->ReadFloat ( maxRestAngle ); + + savefile->ReadInt ( soundPart ); + + savefile->ReadInt ( forceUpTime ); + savefile->ReadInt ( forceDownTime ); + + effectDust.Restore ( savefile ); + savefile->ReadMaterialType ( effectDustMaterialType ); +} + +/*********************************************************************** + + rvVehicleThruster + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleThruster ) +END_CLASS + +rvVehicleThruster::rvVehicleThruster ( void ) { +} + +rvVehicleThruster::~rvVehicleThruster ( void ) { +} + +/* +===================== +rvVehicleThruster::Save +===================== +*/ +void rvVehicleThruster::Save ( idSaveGame* savefile ) const { + savefile->WriteFloat ( force ); + savefile->WriteInt ( forceAxis ); + savefile->WriteInt ( key ); +} + +/* +===================== +rvVehicleThruster::Restore +===================== +*/ +void rvVehicleThruster::Restore ( idRestoreGame* savefile ) { + savefile->ReadFloat ( force ); + savefile->ReadInt ( forceAxis ); + savefile->ReadInt ( (int&)key ); +} + +/* +================ +rvVehicleThruster::Spawn +================ +*/ +void rvVehicleThruster::Spawn ( void ) { + idStr keyName; + + force = spawnArgs.GetFloat ( "force", "0" ); + forceAxis = spawnArgs.GetInt ( "forceAxis", "0" ); + + // Determine which key controls the thruster + spawnArgs.GetString ( "key", "", keyName ); + if ( !keyName.Icmp ( "forward" ) ) { + key = KEY_FORWARD; + } else if ( !keyName.Icmp ( "right" ) ) { + key = KEY_RIGHT; + } else if ( !keyName.Icmp ( "up" ) ) { + key = KEY_UP; + } +} + +/* +================ +rvVehicleThruster::RunPhysics +================ +*/ +void rvVehicleThruster::RunPhysics ( void ) { + float mult; + + if ( !IsActive ( ) ) { + return; + } + + // Determine the force multiplier from the key being pressed + mult = 0.0f; + switch ( key ) { + case KEY_FORWARD: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.forwardmove ); + break; + + case KEY_RIGHT: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.rightmove ); + break; + + case KEY_UP: + mult = idMath::ClampFloat ( -1.0f, 1.0f, position->mInputCmd.upmove ); + break; + } + + // No multiplier, no move + if ( mult == 0.0f ) { + return; + } + + UpdateOrigin ( ); + + // Apply the force + parent->GetPhysics()->ApplyImpulse ( 0, worldOrigin, worldAxis[forceAxis] * force * mult ); + + // Debug Information + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugBounds ( colorCyan, idBounds(idVec3(-4,-4,-4), idVec3(4,4,4) ), worldOrigin ); + gameRenderWorld->DebugArrow ( colorCyan, worldOrigin, worldOrigin + worldAxis[forceAxis] * 100.0f * mult * (force < 0 ? -1 : 1), 10 ); + } +} + +/*********************************************************************** + + rvVehicleUserAnimated + +***********************************************************************/ + +CLASS_DECLARATION( rvVehiclePart, rvVehicleUserAnimated ) + EVENT( EV_PostSpawn, rvVehicleUserAnimated::Event_PostSpawn ) +END_CLASS + +rvVehicleUserAnimated::rvVehicleUserAnimated ( void ) { +} + +/* +===================== +rvVehicleUserAnimated::Save +===================== +*/ +void rvVehicleUserAnimated::Save ( idSaveGame* savefile ) const { +} + +/* +===================== +rvVehicleUserAnimated::Restore +===================== +*/ +void rvVehicleUserAnimated::Restore ( idRestoreGame* savefile ) { + Event_PostSpawn(); +} + +/* +================ +rvVehicleUserAnimated::Spawn +================ +*/ +void rvVehicleUserAnimated::Spawn ( void ) { + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleUserAnimated::Event_PostSpawn +================ +*/ +void rvVehicleUserAnimated::Event_PostSpawn ( void ) { + InitAnim( "forward", anims[ VUAA_Forward ] ); + InitAnim( "strafe", anims[ VUAA_Strafe ] ); + InitAnim( "crouch", anims[ VUAA_Crouch ] ); + InitAnim( "attack", anims[ VUAA_Attack ] ); + + InitFunc( "forward", funcs[ VUAF_Forward ] ); + InitFunc( "strafe", funcs[ VUAF_Strafe ] ); + InitFunc( "crouch", funcs[ VUAF_Crouch ] ); + InitFunc( "attack", funcs[ VUAF_Attack ] ); +} + + +/* +================ +rvVehicleUserAnimated::RunPrePhysics +================ +*/ +void rvVehicleUserAnimated::RunPrePhysics ( void ) { + if ( !IsActive ( ) ) { + return; + } + + rvVehicle *vehicle = position->GetParent(); + const rvVehiclePosition *position = vehicle->GetPosition( 0 ); + const usercmd_t & input = position->mInputCmd; + + if ( position->IsOccupied() ) { + LateralMove( input.forwardmove, VUAA_Forward, VUAF_Forward ); + LateralMove( input.rightmove, VUAA_Strafe, VUAF_Strafe ); + LateralMove( input.upmove, VUAA_Crouch, VUAF_Crouch ); + + // cheat and use LateralMove for the attack + LateralMove( input.buttons & BUTTON_ATTACK, VUAA_Attack, VUAF_Attack ); + } +} + +/* +================ +rvVehicleUserAnimated::InitAnim +================ +*/ +void rvVehicleUserAnimated::InitAnim( const char * action, rvUserAnimatedAnim_t & anim ) { + idStr prefix( action ); + rvVehicle * vehicle = position->GetParent(); + + anim.index = vehicle->GetAnimator()->GetAnim( spawnArgs.GetString( prefix + "_anim" ) ); + anim.rate = spawnArgs.GetFloat( prefix + "_rate", "1" ); + anim.frame = spawnArgs.GetInt( prefix + "_frame" ) * anim.rate; + anim.channel = spawnArgs.GetInt( prefix + "_channel" ); + anim.loop = spawnArgs.GetBool( prefix + "_loop" ); + anim.length = vehicle->GetAnimator()->NumFrames( anim.index ); + + // NOTE: I may want to add the suffix "_play" in the InitAnim() function + // this would specify that the animation should be played rather than stepped through + // which could be useful especially for the attack command +} + +/* +================ +rvVehicleUserAnimated::InitFunc +================ +*/ +void rvVehicleUserAnimated::InitFunc( const char * action, rvScriptFuncUtility & func ) { + idStr temp = idStr( action ) + "_script"; + + if ( spawnArgs.GetString( temp, NULL, temp ) ) { + func.Init( temp ); + } +} + +/* +================ +rvVehicleUserAnimated::SetFrame +================ +*/ +void rvVehicleUserAnimated::SetFrame( const rvUserAnimatedAnim_t & anim ) { + float lerp = anim.frame - int( anim.frame ); + frameBlend_t frameBlend = { 0, anim.frame, anim.frame + 1.0f, 1.0f - lerp, lerp }; + position->GetParent()->GetAnimator()->SetFrame( anim.channel, anim.index, frameBlend ); +} + +/* +================ +rvVehicleUserAnimated::LateralMove +================ +*/ +void rvVehicleUserAnimated::LateralMove( signed char input, int anim, int func ) { + if ( !input ) { + return; + } + + rvUserAnimatedAnim_t & theAnim = anims[ anim ]; + + if ( theAnim.index ) { + + float sign = ( ( input > 0 ) ? 1.0f : -1.0f ); + theAnim.frame += theAnim.rate * sign; + + if ( theAnim.frame < 1 ) { + theAnim.frame = ( ( theAnim.loop ) ? theAnim.length : 1 ); + } else if ( theAnim.frame > theAnim.length ) { + theAnim.frame = ( ( theAnim.loop ) ? 1 : theAnim.length ); + } + + SetFrame( theAnim ); + } + + if ( funcs[ func ].Valid( ) ) + funcs[ func ].CallFunc( &spawnArgs ); +} diff --git a/src/mpgame/vehicle/VehicleParts.h b/src/mpgame/vehicle/VehicleParts.h new file mode 100644 index 0000000..f355838 --- /dev/null +++ b/src/mpgame/vehicle/VehicleParts.h @@ -0,0 +1,468 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleParts.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLEPARTS_H__ +#define __GAME_VEHICLEPARTS_H__ + +class idWeapon; +class rvVehicle; +class rvVehiclePosition; +class rvEffect; + +//---------------------------------------------------------------- +// Base Part +//---------------------------------------------------------------- + +class rvVehiclePart : public idClass +{ +public: + + ABSTRACT_PROTOTYPE( rvVehiclePart ); + + virtual ~rvVehiclePart( void ) { } + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual bool Init ( rvVehiclePosition* position, const idDict& args, s_channelType soundChannel ); + + virtual void RunPrePhysics ( void ) { } + virtual void RunPhysics ( void ) { } + virtual void RunPostPhysics ( void ) { } + + bool IsLeft ( void ) const; + bool IsFront ( void ) const; + bool IsUsingCenterMass ( void ) const; + bool IsActive ( void ) const; + + virtual void Activate ( bool activate ) { fl.active = activate; } + + virtual void Impulse ( int impulse ) { } + + virtual rvVehiclePosition* GetPosition ( void ) const; + const idVec3& GetOrigin ( void ) const; + const idMat3& GetAxis ( void ) const; + +protected: + + void UpdateOrigin ( void ); + + typedef struct partFlags_s { + bool active : 1; // Is part active + bool left : 1; // Is part on left side of vehicle + bool front : 1; // Is part on right side of vehicle + bool useCenterMass : 1; // Use center of mass for origin + bool useViewAxis : 1; // Uses parent->viewAxis instead of the physics axis (::UpdateOrigin()) + } partFlags_t; + + partFlags_t fl; + + idDict spawnArgs; + idEntityPtr parent; + jointHandle_t joint; + s_channelType soundChannel; + rvVehiclePosition* position; + + idVec3 worldOrigin; + idMat3 worldAxis; + idVec3 localOffset; +}; + +ID_INLINE rvVehiclePosition* rvVehiclePart::GetPosition ( void ) const { return position; } +ID_INLINE const idVec3& rvVehiclePart::GetOrigin ( void ) const { return worldOrigin; } +ID_INLINE const idMat3& rvVehiclePart::GetAxis ( void ) const { return worldAxis; } + +ID_INLINE bool rvVehiclePart::IsLeft ( void ) const { return fl.left; } +ID_INLINE bool rvVehiclePart::IsFront ( void ) const { return fl.front; } +ID_INLINE bool rvVehiclePart::IsUsingCenterMass ( void ) const { return fl.useCenterMass; } +ID_INLINE bool rvVehiclePart::IsActive ( void ) const { return fl.active; } + +//---------------------------------------------------------------- +// Sound +//---------------------------------------------------------------- + +class rvVehicleSound : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleSound ); + + rvVehicleSound ( void ); + ~rvVehicleSound ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + bool IsPlaying ( void ) const; + + void Play ( void ); + void Stop ( void ); + void Update ( bool force = false ); + + void Fade ( int time, float toVolume, float toFreq ); + void Attenuate ( float volumeAttenuate, float freqAttenuate ); + + void SetAutoActivate ( bool activate ); + +protected: + + idVec2 volume; + idVec2 freqShift; + idStr soundName; + refSound_t refSound; + idInterpolate currentVolume; + idInterpolate currentFreqShift; + bool fade; + bool autoActivate; +}; + +ID_INLINE bool rvVehicleSound::IsPlaying ( void ) const { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); + if( emitter ) { + return ( emitter->CurrentlyPlaying ( ) ); + } + return( false ); +} + +ID_INLINE void rvVehicleSound::SetAutoActivate ( bool activate ){ + autoActivate = activate; +} + +//---------------------------------------------------------------- +// Hoverpad +//---------------------------------------------------------------- + +class rvVehicleHoverpad : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleHoverpad ); + + rvVehicleHoverpad ( void ); + ~rvVehicleHoverpad ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + void UpdateDustEffect ( const idVec3& origin, const idMat3& axis, float attenuation, const rvDeclMatType* mtype ); + + float height; + float dampen; + bool atRest; + + idClipModel* clipModel; + + idInterpolate currentForce; + float force; + const idDeclTable* forceTable; + float forceRandom; + + float fadeTime; + idVec3 velocity; + + float thrustForward; + float thrustLeft; + + float maxRestAngle; + + int soundPart; + + int forceUpTime; + int forceDownTime; + + // Dust effects + rvClientEntityPtr effectDust; + const rvDeclMatType* effectDustMaterialType; +}; + +//---------------------------------------------------------------- +// Thruster +//---------------------------------------------------------------- + +class rvVehicleThruster : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleThruster ); + + rvVehicleThruster ( void ); + ~rvVehicleThruster ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPhysics ( void ); + +protected: + + enum EThrusterKey + { + KEY_FORWARD, + KEY_RIGHT, + KEY_UP + }; + + float force; + int forceAxis; + EThrusterKey key; +}; + +//---------------------------------------------------------------- +// Light +//---------------------------------------------------------------- + +class rvVehicleLight : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleLight ); + + rvVehicleLight ( void ); + ~rvVehicleLight ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + + virtual void Impulse ( int impulse ); + +protected: + + void TurnOff ( void ); + void TurnOn ( void ); + + void UpdateLightDef ( void ); + + renderLight_t renderLight; + int lightHandle; + bool lightOn; + + idStr soundOn; + idStr soundOff; +}; + +//---------------------------------------------------------------- +// Weapon +//---------------------------------------------------------------- +class rvVehicleWeapon : public rvVehiclePart { +public: + CLASS_PROTOTYPE( rvVehicleWeapon ); + + rvVehicleWeapon ( void ); + ~rvVehicleWeapon ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool activate ); + + int GetCurrentAmmo ( void ) const; + float GetCurrentCharge ( void ) const; + + void UpdateCursorGUI ( idUserInterface* gui ) const; + + bool Fire (); + + int GetZoomFov ( void ) const; + idUserInterface * GetZoomGui ( void ) const; + float GetZoomTime ( void ) const; + bool CanZoom ( void ) const; + + void StopTargetEffect ( void ); + +protected: +#ifdef _XENON + idActor* bestEnemy; + void AutoAim( idPlayer* player, const idVec3& origin, idVec3& dir ); +#endif + void LaunchHitScan ( const idVec3& origin, const idVec3& dir, const idVec3& jointOrigin ); + void LaunchProjectile ( const idVec3& origin, const idVec3& dir, const idVec3& pushVelocity ); + void LaunchProjectiles (); + + void UpdateLock ( void ); + void GetLockInfo ( const idVec3& eyeOrigin, const idMat3& eyeAxis ); + + void EjectBrass ( void ); + + void MuzzleFlashLight ( const idVec3& origin, const idMat3& axis ); + void WeaponFeedback ( const idDict* dict ); + + int nextFireTime; + int fireDelay; + int count; + const idDict* projectileDef; + const idDict* hitScanDef; + float spread; + bool launchFromJoint; + bool lockScanning; + int lastLockTime; + float lockRange; + + idEntityPtr targetEnt; + jointHandle_t targetJoint; + idVec3 targetPos; + rvClientEntityPtr targetEffect; + + idList joints; + int jointIndex; + + idVec3 force; + + int ammoPerCharge; + int chargeTime; + int currentAmmo; + idInterpolate currentCharge; + + renderLight_t muzzleFlash; + int muzzleFlashHandle; + int muzzleFlashEnd; + int muzzleFlashTime; + idVec3 muzzleFlashOffset; + + int animNum; + int animChannel; + + const idSoundShader* shaderFire; + const idSoundShader* shaderReload; + + const idDict* brassDict; + jointHandle_t brassEjectJoint; + idVec3 brassEjectOffset; + int brassEjectNext; + int brassEjectDelay; + + int zoomFov; + idUserInterface * zoomGui; + float zoomTime; +}; + +ID_INLINE int rvVehicleWeapon::GetCurrentAmmo ( void ) const { return currentAmmo; } +ID_INLINE float rvVehicleWeapon::GetCurrentCharge ( void ) const { return currentCharge.IsDone(gameLocal.time) ? 1.0f : currentCharge.GetCurrentValue(gameLocal.time); } +ID_INLINE int rvVehicleWeapon::GetZoomFov ( void ) const { return zoomFov; } +ID_INLINE idUserInterface* rvVehicleWeapon::GetZoomGui ( void ) const { return zoomGui; } +ID_INLINE float rvVehicleWeapon::GetZoomTime ( void ) const { return zoomTime; } +ID_INLINE bool rvVehicleWeapon::CanZoom ( void ) const { return zoomFov != -1; } + + +//---------------------------------------------------------------- +// Turret +//---------------------------------------------------------------- + +class rvVehicleTurret : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleTurret ); + + rvVehicleTurret ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + virtual void Activate ( bool active ); + +protected: + + idBounds angles; + int axisMap[3]; + + float invert[3]; + + idAngles currentAngles; + + int moveTime; + float turnRate; + + int soundPart; + + bool parentStuck; +}; + +//---------------------------------------------------------------- +// Animated Vehicle Part +//---------------------------------------------------------------- + +class rvVehicleUserAnimated : public rvVehiclePart { +public: + + CLASS_PROTOTYPE( rvVehicleUserAnimated ); + + rvVehicleUserAnimated ( void ); + + void Spawn ( void ); + void Save ( idSaveGame* saveFile ) const; + void Restore ( idRestoreGame* saveFile ); + + virtual void RunPrePhysics ( void ); + + void Event_PostSpawn ( void ); + +protected: + + typedef struct rvUserAnimatedAnim_s { + int index; + float frame; + short length; + short channel; + float rate; + bool loop; + } rvUserAnimatedAnim_t; + + enum { VUAA_Forward, VUAA_Strafe, VUAA_Crouch, VUAA_Attack, VUAA_Count }; + enum { VUAF_Forward, VUAF_Strafe, VUAF_Crouch, VUAF_Attack, VUAF_Count }; + + rvUserAnimatedAnim_t anims[ VUAA_Count ]; + rvScriptFuncUtility funcs[ VUAF_Count ]; + + void InitAnim( const char * action, rvUserAnimatedAnim_t & anim ); + void InitFunc( const char * action, rvScriptFuncUtility & func ); + void SetFrame( const rvUserAnimatedAnim_t & anim ); + void LateralMove( signed char input, int anim, int func ); +}; + +typedef idList rvVehiclePartList_t; + +#endif // __GAME_VEHICLEPARTS_H__ diff --git a/src/mpgame/vehicle/VehiclePosition.cpp b/src/mpgame/vehicle/VehiclePosition.cpp new file mode 100644 index 0000000..451ae76 --- /dev/null +++ b/src/mpgame/vehicle/VehiclePosition.cpp @@ -0,0 +1,869 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleController.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "Vehicle.h" + +/* +================ +rvVehiclePosition::rvVehiclePosition +================ +*/ +rvVehiclePosition::rvVehiclePosition ( void ) { + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + + memset ( &fl, 0, sizeof(fl) ); + + mCurrentWeapon = -1; + mSoundPart = -1; + + mDriver = NULL; + mParent = NULL; + + mEyeOrigin.Zero(); + mEyeAxis.Identity(); + + mEyeOffset.Zero(); + mEyeJoint = INVALID_JOINT; + + mDriverOffset.Zero ( ); + mDriverJoint = INVALID_JOINT; +} + +/* +================ +rvVehiclePosition::~rvVehiclePosition +================ +*/ +rvVehiclePosition::~rvVehiclePosition ( void ) { + mParts.DeleteContents ( true ); + mWeapons.DeleteContents ( true ); +} + +void InitIntArray( const idVec3& vec, int array[] ) { + int ix; + for( ix = 0; ix < vec.GetDimension(); ++ix ) { + array[ix] = (int)vec[ix]; + } +} + +/* +================ +rvVehiclePosition::Init +================ +*/ +void rvVehiclePosition::Init ( rvVehicle* parent, const idDict& args ) { + idVec3 garbage; + + mParent = parent; + + mEyeJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "eyeJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mEyeJoint, gameLocal.GetTime(), garbage, mEyeJointTransform) ) { + mEyeJointTransform.Identity(); + //} + //mEyeJointTransform.TransposeSelf(); + mEyeOffset = args.GetVector ( "eyeOffset", "0 0 0" ); + mDeltaEyeAxisScale = args.GetAngles( "deltaEyeAxisScale", "1 1 0" ); + mDeltaEyeAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("eyeJointAxisMap", "0 1 2"), mEyeJointAxisMap ); + InitIntArray( args.GetVector("eyeJointDirMap", "1 1 1"), mEyeJointDirMap ); + + mAxisOffset = args.GetAngles( "angles_offset" ).ToMat3(); + + mDriverJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "driverJoint" ) ); + // abahr & twhitaker: removed this due to other changes. + //if( !mParent->GetAnimator()->GetJointTransform(mDriverJoint, gameLocal.GetTime(), garbage, mDriverJointTransform) ) { + mDriverJointTransform.Identity(); + //} + //mDriverJointTransform.TransposeSelf( ); + mDriverOffset = args.GetVector ( "driverOffset", "0 0 0" ); + mDeltaDriverAxisScale = args.GetAngles( "deltaDriverAxisScale", "1 1 0" ); + mDeltaDriverAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) ); + InitIntArray( args.GetVector("driverJointAxisMap", "0 1 2"), mDriverJointAxisMap ); + InitIntArray( args.GetVector("driverJointDirMap", "1 1 1"), mDriverJointDirMap ); + + mExitPosOffset = args.GetVector( "exitPosOffset" ); + mExitAxisOffset = args.GetAngles( "exitAxisOffset" ).ToMat3(); + + mDriverAnim = args.GetString ( "driverAnim", "driver" ); + + fl.driverVisible = args.GetBool ( "driverVisible", "0" ); + fl.engine = args.GetBool ( "engine", "0" ); + fl.depthHack = args.GetBool ( "depthHack", "1" ); + fl.bindDriver = args.GetBool ( "bindDriver", "1" ); + + args.GetString ( "internalSurface", "", mInternalSurface ); + + SetParts ( args ); + + mSoundMaxSpeed = args.GetFloat ( "maxsoundspeed", "0" ); + + // Looping sound when occupied? + if ( *args.GetString ( "snd_loop", "" ) ) { + mSoundPart = AddPart ( rvVehicleSound::GetClassType(), args ); + static_cast(mParts[mSoundPart])->SetAutoActivate ( false ); + } + + SelectWeapon ( 0 ); + + UpdateInternalView ( true ); +} + +/* +================ +rvVehiclePosition::SetParts +================ +*/ +void rvVehiclePosition::SetParts ( const idDict& args ) { + const idKeyValue* kv; + + // Spawn all parts + kv = args.MatchPrefix( "def_part", NULL ); + while ( kv ) { + const idDict* dict; + idTypeInfo* typeInfo; + + // Skip empty strings + if ( !kv->GetValue().Length() ) { + kv = args.MatchPrefix( "def_part", kv ); + continue; + } + + // Get the dictionary for the part + dict = gameLocal.FindEntityDefDict ( kv->GetValue() ); + if ( !dict ) { + gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); + } + + // Determine the part type + typeInfo = idClass::GetClass ( dict->GetString ( "spawnclass" ) ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) { + gameLocal.Error ( "Class '%s' is not a vehicle part", dict->GetString ( "spawnclass" ) ); + } + + // Add the new part + AddPart ( *typeInfo, *dict ); + + kv = args.MatchPrefix( "def_part", kv ); + } +} + +/* +================ +rvVehiclePosition::AddPart +================ +*/ +int rvVehiclePosition::AddPart ( const idTypeInfo &classdef, const idDict& args ) { + rvVehiclePart* part; + int soundChannel; + + // Get a sound channel + soundChannel = SND_CHANNEL_CUSTOM; + soundChannel += mParts.Num(); + + // Allocate the new part + part = static_cast(classdef.CreateInstance ( )); + part->Init ( this, args, (s_channelType)soundChannel ); + part->CallSpawn ( ); + + // Weapons go into their own list since only one can be active + // and any given point in time. + if ( part->IsType ( rvVehicleWeapon::GetClassType() ) ) { + return mWeapons.Append ( part ); + } + + return mParts.Append ( part ); +} + +/* +================ +rvVehiclePosition::SetDriver +================ +*/ +bool rvVehiclePosition::SetDriver ( idActor* driver ) { + if ( mDriver == driver ) { + return false; + } + + fl.inputValid = false; + + if ( driver ) { + mDriver = driver; + + // Keep the driver visible if the position is exposed + if ( fl.driverVisible ) { + mDriver->Show ( ); + } else { + mDriver->Hide ( ); + + // Dont let the player take damage when inside the if not visible + mDriver->fl.takedamage = false; + } + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Play ( ); + } + + // Bind the driver to a joint or to the vehicles origin? + if( fl.bindDriver ) { + if ( INVALID_JOINT != mDriverJoint ) { + mDriver->GetPhysics()->SetAxis ( mParent->GetAxis( ) );// Not sure if this is needed + mDriver->BindToJoint ( mParent, mDriverJoint, true ); + mDriver->GetPhysics()->SetOrigin ( vec3_origin ); + } else { + mDriver->Bind ( mParent, true ); + mDriver->GetPhysics()->SetOrigin( mDriverOffset ); + } + } else {// If not bound put the vehicle first in the active list + mParent->activeNode.Remove(); + mParent->activeNode.AddToFront( gameLocal.activeEntities ); + } + + // Play a certain animation on the driver + if ( mDriverAnim.Length() ) { + mDriver->GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, mDriver->GetAnimator()->GetAnim( mDriverAnim ), gameLocal.time, 0 ); + } + } else { + if ( !fl.driverVisible ) { + mDriver->Show ( ); + + // Take damage again + mDriver->fl.takedamage = true; + } + + // Driver is no longer bound to the vehicle + mDriver->Unbind(); + + mDriver = driver; + + if ( mSoundPart != -1 ) { + static_cast(mParts[mSoundPart])->Stop ( ); + } + + // Clear out the input commands so the guns dont keep firing and what not when + // the player gets out + memset ( &mInputCmd, 0, sizeof(mInputCmd) ); + mInputAngles.Zero ( ); + } + + return true; +} + +/* +================ +rvVehiclePosition::GetAxis +================ +*/ +idMat3 rvVehiclePosition::GetAxis() const { + return mAxisOffset * GetParent()->GetPhysics()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetOrigin +================ +*/ +idVec3 rvVehiclePosition::GetOrigin( const idVec3& offset ) const { + return GetParent()->GetPhysics()->GetOrigin() + (mDriverOffset + offset) * GetAxis(); +} + +/* +================ +rvVehiclePosition::ActivateParts +================ +*/ +void rvVehiclePosition::ActivateParts ( bool active ) { + int i; + + // Activate or deactive the parts based on whether there is a driver + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + mParts[i]->Activate ( active ); + } + + for ( i = mWeapons.Num() - 1; i >= 0; i -- ) { + mWeapons[i]->Activate ( active ); + } +} + +/* +================ +rvVehiclePosition::EjectDriver +================ +*/ +bool rvVehiclePosition::EjectDriver ( bool force ) { + if ( !IsOccupied ( ) ) { + return false; + } + + // Physically eject the actor from the position + if ( !force && mParent->IsLocked ( ) ) { + mParent->IssueLockedWarning ( ); + return false; + } + + // Remove the driver and if successful disable all parts for + // this position + if ( SetDriver ( NULL ) ) { + ActivateParts ( false ); + return true; + } + + return false; +} + +/* +================ +rvVehiclePosition::SetInput +================ +*/ +void rvVehiclePosition::SetInput ( const usercmd_t& cmd, const idAngles& newAngles ) { + if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE ) ) { + if ( mDriver == gameLocal.GetLocalPlayer ( ) ) { + gameDebug.SetFocusEntity ( mParent ); + } + } + + if ( fl.inputValid || !mDriver ) { + mOldInputCmd = mInputCmd; + mOldInputAngles = mInputAngles; + } else { + mOldInputCmd = cmd; + mInputCmd = cmd; + mInputAngles = newAngles; + + // We have valid input now and there is a driver so activate all of the parts + if ( !fl.inputValid && mDriver ) { + ActivateParts ( true ); + } + } + + fl.inputValid = true; + mInputCmd = cmd; + mInputAngles = newAngles; +} + +/* +================ +rvVehiclePosition::GetInput +================ +*/ +void rvVehiclePosition::GetInput( usercmd_t& cmd, idAngles& newAngles ) const { + cmd = mInputCmd; + newAngles = mInputAngles; +} + +/* +================ +rvVehiclePosition::UpdateHUD +================ +*/ +void rvVehiclePosition::UpdateHUD ( idUserInterface* gui ) { + + // HACK: twhitaker: this is ugly but since the GEV and the Walker now have a unified HUD, it is a necessary evil + int guiWeaponID; + + if ( !stricmp( mParent->spawnArgs.GetString( "classname" ), "vehicle_walker" ) ) { + guiWeaponID = mCurrentWeapon + 2; + } else { + guiWeaponID = mCurrentWeapon; + } + + gui->SetStateInt ( "vehicle_weapon", guiWeaponID ); + // HACK: twhitaker end + + gui->SetStateInt ( "vehicle_weaponcount", mWeapons.Num() ); + if ( mCurrentWeapon >= 0 && mCurrentWeapon < mWeapons.Num() ) { + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + gui->SetStateFloat ( "vehicle_weaponcharge", weapon->GetCurrentCharge() ); + gui->SetStateInt ( "vehicle_weaponammo", weapon->GetCurrentAmmo() ); + } + + // Calculate the rotation of the view in relation to the vehicle itself + gui->SetStateFloat ( "vehicle_rotate", -idMath::AngleDelta ( mEyeAxis.ToAngles()[YAW], mParent->GetAxis ( ).ToAngles()[YAW] ) ); + + if( GetParent() ) { + GetParent()->UpdateHUD( GetDriver(), gui ); + } +} + +/* +================ +rvVehiclePosition::UpdateCursorGUI +================ +*/ +void rvVehiclePosition::UpdateCursorGUI ( idUserInterface* gui ) { + if ( mCurrentWeapon < 0 || mCurrentWeapon >= mWeapons.Num() ) { + return; + } + + rvVehicleWeapon* weapon; + weapon = static_cast(mWeapons[mCurrentWeapon]); + assert ( weapon ); + weapon->UpdateCursorGUI ( gui ); +} + +/* +================ +rvVehiclePosition::UpdateInternalView +================ +*/ +void rvVehiclePosition::UpdateInternalView ( bool force ) { + bool internal = false; + + // If the local player is driving and not in third person then use internal + internal = ( mDriver == gameLocal.GetLocalPlayer() && !pm_thirdPerson.GetInteger() ); + + // force the update? + if ( !force && internal == fl.internalView ) { + return; + } + + fl.internalView = internal; + + if ( fl.depthHack ) { + mParent->GetRenderEntity()->weaponDepthHackInViewID = (IsOccupied() && fl.internalView) ? GetDriver()->entityNumber + 1 : 0; + } + + // Show and hide the internal surface + if ( mInternalSurface.Length() ) { + mParent->ProcessEvent ( fl.internalView ? &EV_ShowSurface : &EV_HideSurface, mInternalSurface.c_str() ); + } +} + +/* +================ +rvVehiclePosition::RunPrePhysics +================ +*/ +void rvVehiclePosition::RunPrePhysics ( void ) { + int i; + + UpdateInternalView ( ); + + if ( mParent->IsStalled() ) { + if ( !fl.stalled ) { + // we just stalled + fl.stalled = true; + ActivateParts( false ); + } + return; + } else if ( IsOccupied() && !mParent->IsStalled() && fl.stalled ) { + // we just restarted + fl.stalled = false; + ActivateParts( true ); + } + + // Give the parts a chance to set up for physics + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + mParts[i]->RunPrePhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPrePhysics ( ); + } + + // Run physics for each part + for ( i = mParts.Num() - 1; i >= 0; i -- ) { + assert ( mParts[i] ); + + mParts[i]->RunPhysics ( ); + } + + // Attenuate the attached sound if speed based + if ( mSoundPart != -1 && mSoundMaxSpeed > 0.0f ) { + float f; + float speed; + idVec3 vel; + + // Only interested in forward or backward velocity + vel = mParent->GetPhysics()->GetLinearVelocity ( ) * mParent->GetPhysics()->GetAxis ( ); + speed = idMath::ClampFloat ( 0.0f, mSoundMaxSpeed, vel.Normalize ( ) ); + f = speed / mSoundMaxSpeed; + + static_cast(mParts[mSoundPart])->Attenuate ( f, f ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPhysics ( ); + } +} + +/* +================ +rvVehiclePosition::RunPostPhysics +================ +*/ +void rvVehiclePosition::RunPostPhysics ( void ) { + int i; + for ( i = 0; i < mParts.Num(); i ++ ) { + assert ( mParts[i] ); + mParts[i]->RunPostPhysics ( ); + } + + if ( mCurrentWeapon >= 0 ) { + mWeapons[mCurrentWeapon]->RunPostPhysics ( ); + } + + if ( !IsOccupied ( ) ) { + return; + } + + if ( fl.inputValid ) { + if ( mInputCmd.upmove > 0 && !mParent->IsLocked() ) { + // inform the driver that its time to get out of the vehicle. + if( !mDriver->EventIsPosted(&AI_ExitVehicle) ) { + mDriver->PostEventMS( &AI_ExitVehicle, 250, false );// To remove jump when exiting + } + + // If the position isnt occupied anymore then the vehicle exit was successful and there + // is nothing else to do + if ( !IsOccupied ( ) ) { + return; + } + } else if ( (mInputCmd.flags & UCF_IMPULSE_SEQUENCE) != (mOldInputFlags & UCF_IMPULSE_SEQUENCE) ) { + int i; + + if ( mInputCmd.impulse >= IMPULSE_0 && mInputCmd.impulse <= IMPULSE_12 ) { + SelectWeapon( mInputCmd.impulse - IMPULSE_0 ); + } + + if ( mWeapons.Num () ) { + switch ( mInputCmd.impulse ) { + case IMPULSE_14: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() + 1) % mWeapons.Num() ); + break; + + case IMPULSE_15: + SelectWeapon ( (mCurrentWeapon + mWeapons.Num() - 1) % mWeapons.Num() ); + break; + } + } + + for( i = 0; i < mParts.Num(); i++ ) { + mParts[ i ]->Impulse ( mInputCmd.impulse ); + } + + mOldInputFlags = mInputCmd.flags; + } + } + + // Update the eye origin and axis + GetEyePosition( mEyeOrigin, mEyeAxis ); + + if ( g_debugVehicle.GetInteger() == 2 ) { + gameRenderWorld->DebugArrow( colorMagenta, mEyeOrigin, mEyeOrigin + mEyeAxis[0] * 30.0f, 3 ); + } +} + +/* +================ +rvVehiclePosition::GetEyePosition +================ +*/ +void rvVehiclePosition::GetEyePosition( idVec3& origin, idMat3& axis ) const { + GetPosition( mEyeJoint, mEyeOffset, mEyeJointTransform, mDeltaEyeAxisScale, mEyeJointAxisMap, mEyeJointDirMap, origin, axis ); + axis *= GetParent()->GetAxis(); +} + +/* +================ +rvVehiclePosition::GetDriverPosition +================ +*/ +void rvVehiclePosition::GetDriverPosition( idVec3& origin, idMat3& axis ) const { + if( fl.bindDriver ) { + GetPosition( mDriverJoint, mDriverOffset, mDriverJointTransform, mDeltaDriverAxisScale, mDriverJointAxisMap, mDriverJointDirMap, origin, axis ); + } else { + origin = GetDriver()->GetPhysics()->GetOrigin(); + axis = GetDriver()->viewAxis; + } +} + +/* +================ +rvVehiclePosition::GetPosition +================ +*/ +void rvVehiclePosition::GetPosition( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const { + if( GetParent()->GetAnimator()->GetJointTransform(jointHandle, gameLocal.GetTime(), origin, axis) ) { + axis *= jointTransform; + idAngles ang( axis.ToAngles().Remap(axisMap, dirMap).Scale(scale) ); + axis = ang.Normalize360().ToMat3() * mAxisOffset; + + origin = GetParent()->GetOrigin() + (origin + offset * axis) * GetParent()->GetAxis(); + } else { + origin = GetOrigin( mEyeOffset ); + axis = GetAxis(); + } +} + +/* +================ +rvVehiclePosition::FireWeapon +================ +*/ +void rvVehiclePosition::FireWeapon( void ) { + if ( mCurrentWeapon >= 0 ) { + static_cast( mWeapons[mCurrentWeapon] )->Fire(); + } +} + +/* +================ +rvVehiclePosition::SelectWeapon +================ +*/ +void rvVehiclePosition::SelectWeapon ( int weapon ) { + if ( weapon < 0 || weapon >= mWeapons.Num ( ) ) { + return; + } + +// mekberg: clear effect + if ( mCurrentWeapon != -1 ) { + static_cast ( mWeapons[ mCurrentWeapon ] )->StopTargetEffect( ); + } + + mCurrentWeapon = weapon; +} + +/* +================ +rvVehiclePosition::WriteToSnapshot +================ +*/ +void rvVehiclePosition::WriteToSnapshot ( idBitMsgDelta &msg ) const { + msg.WriteBits ( mCurrentWeapon, 3 ); +} + +/* +================ +rvVehiclePosition::ReadFromSnapshot +================ +*/ +void rvVehiclePosition::ReadFromSnapshot ( const idBitMsgDelta &msg ) { + mCurrentWeapon = msg.ReadBits ( 3 ); +} + +/* +================ +rvVehiclePosition::Save +================ +*/ +void rvVehiclePosition::Save ( idSaveGame* savefile ) const { + int i; + + savefile->WriteUsercmd( mInputCmd ); + savefile->WriteAngles ( mInputAngles ); + savefile->WriteUsercmd ( mOldInputCmd ); + savefile->WriteAngles ( mOldInputAngles ); + savefile->WriteInt ( mOldInputFlags ); + + savefile->WriteInt ( mCurrentWeapon ); + + mDriver.Save ( savefile ); + mParent.Save ( savefile ); + + savefile->WriteInt ( mParts.Num ( ) ); + for ( i = 0; i < mParts.Num(); i ++ ) { + savefile->WriteString ( mParts[i]->GetClassname() ); + savefile->WriteStaticObject ( *mParts[i] ); + } + + savefile->WriteInt ( mWeapons.Num ( ) ); + for ( i = 0; i < mWeapons.Num(); i ++ ) { + savefile->WriteString ( mWeapons[i]->GetClassname() ); + savefile->WriteStaticObject ( *mWeapons[i] ); + } + + savefile->WriteVec3 ( mEyeOrigin ); + savefile->WriteMat3 ( mEyeAxis ); + + savefile->WriteJoint ( mEyeJoint ); + savefile->WriteVec3 ( mEyeOffset ); + savefile->WriteMat3( mEyeJointTransform ); + savefile->WriteAngles( mDeltaEyeAxisScale ); + savefile->Write ( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Write ( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->WriteMat3 ( mAxisOffset ); + + savefile->WriteJoint ( mDriverJoint ); + savefile->WriteVec3 ( mDriverOffset ); + savefile->WriteMat3 ( mDriverJointTransform ); + savefile->WriteAngles( mDeltaDriverAxisScale ); + savefile->Write ( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Write ( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->WriteVec3 ( mExitPosOffset ); + savefile->WriteMat3 ( mExitAxisOffset ); + + savefile->WriteString ( mDriverAnim ); + + savefile->WriteString ( mInternalSurface ); + + savefile->Write ( &fl, sizeof ( fl ) ); + + savefile->WriteInt ( mSoundPart ); + savefile->WriteFloat ( mSoundMaxSpeed ); +} + +/* +================ +rvVehiclePosition::Restore +================ +*/ +void rvVehiclePosition::Restore ( idRestoreGame* savefile ) { + int i; + int num; + + savefile->ReadUsercmd( mInputCmd ); + savefile->ReadAngles ( mInputAngles ); + savefile->ReadUsercmd ( mOldInputCmd ); + savefile->ReadAngles ( mOldInputAngles ); + savefile->ReadInt ( mOldInputFlags ); + + savefile->ReadInt ( mCurrentWeapon ); + + mDriver.Restore ( savefile ); + mParent.Restore ( savefile ); + + savefile->ReadInt ( num ); + mParts.Clear ( ); + mParts.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mParts[i] = part; + } + + savefile->ReadInt ( num ); + mWeapons.Clear ( ); + mWeapons.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + idStr spawnclass; + rvVehiclePart* part; + idTypeInfo* typeInfo; + + savefile->ReadString ( spawnclass ); + + // Determine the part type + typeInfo = idClass::GetClass ( spawnclass ); + if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) + { + gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() ); + } + + part = static_cast(typeInfo->CreateInstance ( )); + savefile->ReadStaticObject ( *part ); + mWeapons[i] = part; + } + + savefile->ReadVec3 ( mEyeOrigin ); + savefile->ReadMat3 ( mEyeAxis ); + + savefile->ReadJoint ( mEyeJoint ); + savefile->ReadVec3 ( mEyeOffset ); + savefile->ReadMat3 ( mEyeJointTransform ); + savefile->ReadAngles( mDeltaEyeAxisScale ); + savefile->Read( mEyeJointAxisMap, sizeof mEyeJointAxisMap ); + savefile->Read( mEyeJointDirMap, sizeof mEyeJointDirMap ); + + savefile->ReadMat3 ( mAxisOffset ); + + savefile->ReadJoint ( mDriverJoint ); + savefile->ReadVec3 ( mDriverOffset ); + savefile->ReadMat3 ( mDriverJointTransform ); + savefile->ReadAngles( mDeltaDriverAxisScale ); + savefile->Read( mDriverJointAxisMap, sizeof mDriverJointAxisMap ); + savefile->Read( mDriverJointDirMap, sizeof mDriverJointDirMap ); + + savefile->ReadVec3 ( mExitPosOffset ); + savefile->ReadMat3 ( mExitAxisOffset ); + + savefile->ReadString ( mDriverAnim ); + + savefile->ReadString ( mInternalSurface ); + + savefile->Read ( &fl, sizeof(fl) ); + savefile->ReadInt ( mSoundPart ); + savefile->ReadFloat ( mSoundMaxSpeed ); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mpgame/vehicle/VehicleRigid.cpp b/src/mpgame/vehicle/VehicleRigid.cpp new file mode 100644 index 0000000..e5325c3 --- /dev/null +++ b/src/mpgame/vehicle/VehicleRigid.cpp @@ -0,0 +1,187 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// Vehicle.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleRigid.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleRigid ) +END_CLASS + +rvVehicleRigid::rvVehicleRigid ( void ) { +} + +rvVehicleRigid::~rvVehicleRigid ( void ) { + SetPhysics( NULL ); +} + +/* +================ +rvVehicleRigid::Spawn +================ +*/ +void rvVehicleRigid::Spawn( void ) { + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) ); + physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) ); + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP ); + physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) ); + physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) ); + physicsObj.SetGravity( gameLocal.GetGravity() ); + SetPhysics( &physicsObj ); + + animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 ); + + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleRigid::SetClipModel +================ +*/ +void rvVehicleRigid::SetClipModel ( void ) { + idStr clipModelName; + idTraceModel trm; + float mass; + + // rebuild clipmodel + spawnArgs.GetString( "clipmodel", "", clipModelName ); + + // load the trace model + if ( clipModelName.Length() ) { + if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) { + gameLocal.Error( "rvVehicleRigid '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); + return; + } + + physicsObj.SetClipModel( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "1" ) ); + } else { + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat ( "density", "1" ) ); + } + + if ( spawnArgs.GetFloat ( "mass", "0", mass ) && mass > 0 ) { + physicsObj.SetMass ( mass ); + } +} + + +/* +================ +rvVehicleRigid::RunPrePhysics +================ +*/ +void rvVehicleRigid::RunPrePhysics ( void ) { + storedVelocity = physicsObj.GetLinearVelocity(); +} + +/* +================ +rvVehicleRigid::RunPostPhysics +================ +*/ +void rvVehicleRigid::RunPostPhysics ( void ) { + + if ( autoCorrectionBegin + 1250 > static_cast( gameLocal.time )) { + autoCorrectionBegin = 0; + + float lengthSq = physicsObj.GetLinearVelocity().LengthSqr(); + if ( !autoCorrectionBegin && ( ( storedVelocity * 0.4f ).LengthSqr() >= lengthSq ) || ( lengthSq < 0.01f ) ) { + autoCorrectionBegin = gameLocal.time; + } + } + + if ( g_debugVehicle.GetInteger() == 10 ) { + gameLocal.Printf( "Speed: %f\n", physicsObj.GetLinearVelocity().Length() ); + } +} + +/* +================ +rvVehicleRigid::WriteToSnapshot +================ +*/ +void rvVehicleRigid::WriteToSnapshot( idBitMsgDelta &msg ) const { + rvVehicle::WriteToSnapshot( msg ); + physicsObj.WriteToSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::ReadFromSnapshot +================ +*/ +void rvVehicleRigid::ReadFromSnapshot( const idBitMsgDelta &msg ) { + rvVehicle::ReadFromSnapshot( msg ); + physicsObj.ReadFromSnapshot( msg ); +} + +/* +================ +rvVehicleRigid::Save +================ +*/ +void rvVehicleRigid::Save ( idSaveGame *savefile ) const { + + savefile->WriteVec3 ( storedVelocity ); // cnicholson: Added unsaved var + savefile->WriteStaticObject ( physicsObj ); +} + +/* +================ +rvVehicleRigid::Restore +================ +*/ +void rvVehicleRigid::Restore ( idRestoreGame *savefile ) { + + savefile->ReadVec3 ( storedVelocity ); // cnicholson: Added unrestored var + + physicsObj.SetSelf( this ); + + SetClipModel ( ); + + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics( &physicsObj ); +} + +/* +===================== +rvVehicleRigid::SkipImpulse +===================== +*/ +bool rvVehicleRigid::SkipImpulse( idEntity* ent, int id ) { + return false; +} diff --git a/src/mpgame/vehicle/VehicleRigid.h b/src/mpgame/vehicle/VehicleRigid.h new file mode 100644 index 0000000..eb6a317 --- /dev/null +++ b/src/mpgame/vehicle/VehicleRigid.h @@ -0,0 +1,69 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleRigid.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLERIGID_H__ +#define __GAME_VEHICLERIGID_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleRigid : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleRigid ); + + rvVehicleRigid ( void ); + ~rvVehicleRigid ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void WriteToSnapshot ( idBitMsgDelta &msg ) const; + void ReadFromSnapshot ( const idBitMsgDelta &msg ); + + bool SkipImpulse ( idEntity* ent, int id ); + +protected: + + void SetClipModel ( void ); + + // twhitaker: + virtual void RunPrePhysics ( void ); + virtual void RunPostPhysics ( void ); + idVec3 storedVelocity; + // end twhitaker: + + idPhysics_RigidBody physicsObj; // physics object +}; + +#endif // __GAME_VEHICLERIGID_H__ diff --git a/src/mpgame/vehicle/VehicleSpline.cpp b/src/mpgame/vehicle/VehicleSpline.cpp new file mode 100644 index 0000000..0495376 --- /dev/null +++ b/src/mpgame/vehicle/VehicleSpline.cpp @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleSplineCoupling.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleSpline.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleSpline ) + EVENT( EV_PostSpawn, rvVehicleSpline::Event_PostSpawn ) + EVENT( EV_SetSpline, rvVehicleSpline::Event_SetSpline ) + EVENT( EV_DoneMoving, rvVehicleSpline::Event_DoneMoving ) +END_CLASS + +rvVehicleSpline::rvVehicleSpline ( void ) { +} + +rvVehicleSpline::~rvVehicleSpline ( void ) { + SetPhysics( NULL ); +} + +void rvVehicleSpline::Spawn ( void ) { + + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f ); + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( 0 ); + physicsObj.SetLinearVelocity( GetPhysics()->GetLinearVelocity() ); + physicsObj.SetLinearAcceleration( spawnArgs.GetFloat( "accel", "200" ) ); + physicsObj.SetLinearDeceleration( spawnArgs.GetFloat( "decel", "200" ) ); + + viewAxis = idAngles( 0, spawnArgs.GetFloat( "angle" ) , 0 ).ToMat3(); + + physicsObj.SetAxis( GetPhysics()->GetAxis() * viewAxis ); + physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + + SetPhysics( &physicsObj ); + + BecomeActive( TH_THINK ); + + accelWithStrafe = Sign( spawnArgs.GetFloat("accel_strafe") ); + + idealSpeed = spawnArgs.GetFloat( "speed", "200" ); + + PostEventMS( &EV_PostSpawn, 0 ); +} + +/* +================ +rvVehicleSpline::Save +================ +*/ +void rvVehicleSpline::Save ( idSaveGame *savefile ) const { + savefile->WriteStaticObject( physicsObj ); + savefile->WriteMat3( angleOffset ); + savefile->WriteFloat( idealSpeed ); + savefile->WriteFloat( accelWithStrafe ); +} + +/* +================ +rvVehicleSpline::Restore +================ +*/ +void rvVehicleSpline::Restore ( idRestoreGame *savefile ) { + physicsObj.SetSelf( this ); + physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); + savefile->ReadStaticObject ( physicsObj ); + RestorePhysics ( &physicsObj ); + physicsObj.EnableClip(); + + savefile->ReadMat3( angleOffset ); + savefile->ReadFloat( idealSpeed ); + savefile->ReadFloat( accelWithStrafe ); +} + +void rvVehicleSpline::Think( void ) { + float moveAmount = 0.0f; + + if ( positions[0].IsOccupied ( ) && !IsFrozen () && IsMovementEnabled ( ) ) { + + if ( accelWithStrafe != 0.0f ) { + moveAmount = positions[0].mInputCmd.rightmove * accelWithStrafe; + } else { + moveAmount = positions[0].mInputCmd.forwardmove; + } + + moveAmount = Sign( moveAmount ); + } + + physicsObj.SetSpeed( idealSpeed * moveAmount ); + + rvVehicle::Think( ); +} + +void rvVehicleSpline::Event_PostSpawn( void ) { + idStr splinePath; + + if ( spawnArgs.GetString( "spline_path", "", splinePath ) ) { + Event_SetSpline( gameLocal.FindEntity( splinePath.c_str() ) ); + } else { + gameLocal.Warning( "\"spline_path\" key not found on %s (rvVehicleSpline)", this->GetName() ); + } +} + +void rvVehicleSpline::Event_SetSpline ( idEntity * spline ) { + if ( spline && spline->IsType( idSplinePath::GetClassType() ) ) { + physicsObj.SetSplineEntity( static_cast< idSplinePath * >( spline ) ); + //HACK: force an intitial physics update so that the object orients itself with the spline + //TEMP: this should be fixed after the build on friday (Jan 28, 2005) + physicsObj.SetSpeed( 0.1f ); + RunPhysics(); + //END HACK + } +} + +void rvVehicleSpline::Event_DoneMoving() { + idThread::ReturnInt( true ); +} diff --git a/src/mpgame/vehicle/VehicleSpline.h b/src/mpgame/vehicle/VehicleSpline.h new file mode 100644 index 0000000..f6cea51 --- /dev/null +++ b/src/mpgame/vehicle/VehicleSpline.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleSpline.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESPLINECOUPLING_H__ +#define __GAME_VEHICLESPLINECOUPLING_H__ + +class rvVehicleSpline : public rvVehicle { +public: + CLASS_PROTOTYPE( rvVehicleSpline ); + + rvVehicleSpline ( void ); + ~rvVehicleSpline ( void ); + + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + void Think ( void ); + + void Event_PostSpawn ( void ); + void Event_SetSpline ( idEntity * spline ); + void Event_DoneMoving ( void ); + +protected: + rvPhysics_Spline physicsObj; + idMat3 angleOffset; + float idealSpeed; + float accelWithStrafe; +}; + +#endif // __GAME_VEHICLESPLINECOUPLING_H__ diff --git a/src/mpgame/vehicle/VehicleStatic.cpp b/src/mpgame/vehicle/VehicleStatic.cpp new file mode 100644 index 0000000..81b132c --- /dev/null +++ b/src/mpgame/vehicle/VehicleStatic.cpp @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleStatic.cpp +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +CLASS_DECLARATION( rvVehicle, rvVehicleStatic ) + EVENT( AI_ScriptedAnim, rvVehicleStatic::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleStatic::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleStatic::Event_ScriptedStop ) +END_CLASS + +rvVehicleStatic::rvVehicleStatic ( void ) { +} + +rvVehicleStatic::~rvVehicleStatic ( void ) { +} + +/* +================ +rvVehicleStatic::Spawn +================ +*/ +void rvVehicleStatic::Spawn( void ) { + BecomeActive( TH_THINK ); +} + +/* +================ +rvVehicleStatic::AddDriver +================ +*/ +int rvVehicleStatic::AddDriver ( int position, idActor* driver ) { + int pos = rvVehicle::AddDriver( position, driver ); + + if( pos < 0 ) { + return pos; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "hideGunInfo" ); + } + + return pos; +} + +/* +================ +rvVehicleStatic::RemoveDriver +================ +*/ +bool rvVehicleStatic::RemoveDriver ( int position, bool force ) { + bool result = rvVehicle::RemoveDriver( position, force ); + + if( !result ) { + return result; + } + + if( GetHud() ) { + GetHud()->HandleNamedEvent( "showGunInfo" ); + } + + return result; +} + +/* +================ +rvVehicleStatic::UpdateHUD +================ +*/ +void rvVehicleStatic::UpdateHUD( idActor* driver, idUserInterface* gui ) { + if( driver && driver->IsType( idPlayer::GetClassType() ) ) { + static_cast(driver)->UpdateHudStats( gui ); + } +} + +/* +================ +rvVehicleStatic::Event_ScriptedAnim +================ +*/ +void rvVehicleStatic::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_TORSO, animname, blendFrames ); + } + vfl.scripted = true; +} + +/* +================ +rvVehicleStatic::Event_ScriptedDone +================ +*/ +void rvVehicleStatic::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleStatic::Event_ScriptedStop +================ +*/ +void rvVehicleStatic::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + PlayCycle( ANIMCHANNEL_TORSO, spawnArgs.GetString( "idle" ), 2 ); + } +} diff --git a/src/mpgame/vehicle/VehicleStatic.h b/src/mpgame/vehicle/VehicleStatic.h new file mode 100644 index 0000000..278f244 --- /dev/null +++ b/src/mpgame/vehicle/VehicleStatic.h @@ -0,0 +1,59 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +//---------------------------------------------------------------- +// VehicleStatic.h +// +// Copyright 2002-2004 Raven Software +//---------------------------------------------------------------- + +#ifndef __GAME_VEHICLESTATIC_H__ +#define __GAME_VEHICLESTATIC_H__ + +#ifndef __GAME_VEHICLE_H__ +#include "Vehicle.h" +#endif + +class rvVehicleStatic : public rvVehicle +{ +public: + + CLASS_PROTOTYPE( rvVehicleStatic ); + + rvVehicleStatic ( void ); + ~rvVehicleStatic ( void ); + + void Spawn ( void ); + + virtual int AddDriver ( int position, idActor* driver ); + virtual bool RemoveDriver ( int position, bool force = false ); + + virtual void UpdateHUD ( idActor* driver, idUserInterface* gui ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); +}; + +#endif // __GAME_VEHICLESTATIC_H__ diff --git a/src/mpgame/vehicle/Vehicle_DropPod.cpp b/src/mpgame/vehicle/Vehicle_DropPod.cpp new file mode 100644 index 0000000..7d9ce04 --- /dev/null +++ b/src/mpgame/vehicle/Vehicle_DropPod.cpp @@ -0,0 +1,201 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleStatic.h" + +class rvVehicleDropPod : public rvVehicleStatic { +public: + + CLASS_PROTOTYPE( rvVehicleDropPod ); + + rvVehicleDropPod ( void ); + + void Spawn ( void ); + + virtual bool GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleDropPod ); +}; + +CLASS_DECLARATION( rvVehicleStatic, rvVehicleDropPod ) + EVENT( AI_ScriptedAnim, rvVehicleDropPod::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleDropPod::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleDropPod::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleDropPod::rvVehicleDropPod +================ +*/ +rvVehicleDropPod::rvVehicleDropPod ( void ) { +} + +/* +================ +rvVehicleDropPod::Spawn +================ +*/ +void rvVehicleDropPod::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleDropPod ) + STATE ( "State_Idle", rvVehicleDropPod::State_Idle ) + STATE ( "State_IdleThink", rvVehicleDropPod::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleDropPod::State_IdleOffline ) + + STATE ( "State_ScriptedAnim", rvVehicleDropPod::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleDropPod::State_IdleOffline +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 0 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 0 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_Idle +================ +*/ +stateResult_t rvVehicleDropPod::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::State_IdleThink +================ +*/ +stateResult_t rvVehicleDropPod::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", parms.blendFrames ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleDropPod::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleDropPod::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedAnim +================ +*/ +void rvVehicleDropPod::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleDropPod::Event_ScriptedDone +================ +*/ +void rvVehicleDropPod::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleDropPod::Event_ScriptedStop +================ +*/ +void rvVehicleDropPod::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 1 ); + } +} + +/* +================ +rvVehicleDropPod::GetPhysicsToVisualTransform +================ +*/ +bool rvVehicleDropPod::GetPhysicsToVisualTransform ( idVec3 &origin, idMat3 &axis ) { +// if ( GetBindMaster() ) { +// axis = viewAxis; +// origin.Zero(); +// return true; +// } + return false; +} + diff --git a/src/mpgame/vehicle/Vehicle_Walker.cpp b/src/mpgame/vehicle/Vehicle_Walker.cpp new file mode 100644 index 0000000..9384ed2 --- /dev/null +++ b/src/mpgame/vehicle/Vehicle_Walker.cpp @@ -0,0 +1,617 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "VehicleAnimated.h" + +class rvVehicleWalker : public rvVehicleAnimated { +public: + + CLASS_PROTOTYPE( rvVehicleWalker ); + + rvVehicleWalker ( void ); + + void Think ( void ); + void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void UpdateState ( void ); + virtual void SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ); + + const char* stopAnimName; + + virtual bool FindClearExitPoint ( int pos, idVec3& origin, idMat3& axis ) const; + +private: + void HandleStrafing ( void ); + + + stateResult_t Frame_ForwardRight ( int ); + stateResult_t Frame_ForwardLeft ( int ); + stateResult_t Frame_BackwardRight ( int ); + stateResult_t Frame_BackwardLeft ( int ); + + stateResult_t State_Wait_OnlineAnim ( const stateParms_t& parms ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_IdleThink ( const stateParms_t& parms ); + stateResult_t State_IdleOffline ( const stateParms_t& parms ); + stateResult_t State_Offline ( const stateParms_t& parms ); + stateResult_t State_Online ( const stateParms_t& parms ); + + stateResult_t State_ForwardStart ( const stateParms_t& parms ); + stateResult_t State_Forward ( const stateParms_t& parms ); + stateResult_t State_BackwardStart ( const stateParms_t& parms ); + stateResult_t State_Backward ( const stateParms_t& parms ); + stateResult_t State_Stop ( const stateParms_t& parms ); + stateResult_t State_Turn ( const stateParms_t& parms ); + stateResult_t State_TurnThink ( const stateParms_t& parms ); + stateResult_t State_ScriptedAnim ( const stateParms_t& parms ); + + void Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ); + void Event_ScriptedDone ( void ); + void Event_ScriptedStop ( void ); + + CLASS_STATES_PROTOTYPE ( rvVehicleWalker ); +}; + +CLASS_DECLARATION( rvVehicleAnimated, rvVehicleWalker ) + EVENT( AI_ScriptedAnim, rvVehicleWalker::Event_ScriptedAnim ) + EVENT( AI_ScriptedDone, rvVehicleWalker::Event_ScriptedDone ) + EVENT( AI_ScriptedStop, rvVehicleWalker::Event_ScriptedStop ) +END_CLASS + +/* +================ +rvVehicleWalker::rvVehicleWalker +================ +*/ +rvVehicleWalker::rvVehicleWalker ( void ) { + stopAnimName = ""; +} + +/* +================ +rvVehicleWalker::Think +================ +*/ +void rvVehicleWalker::Think ( void ) { + rvVehicleAnimated::Think(); + + if ( !HasDrivers() || IsStalled() ) { + return; + } + + idVec3 delta; + animator.GetDelta( gameLocal.time - gameLocal.GetMSec(), gameLocal.time, delta ); + + if ( delta.LengthSqr() > 0.1f ) { + gameLocal.RadiusDamage( GetOrigin(), this, this, this, this, spawnArgs.GetString( "def_stompDamage", "damage_Smallexplosion" ) ); + } +} + +/* +================ +rvVehicleWalker::Spawn +================ +*/ +void rvVehicleWalker::Spawn ( void ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 0 ); +} + +/* +================ +rvVehicleWalker::Save +================ +*/ +void rvVehicleWalker::Save ( idSaveGame *savefile ) const { + savefile->WriteString( stopAnimName ); +} + +/* +================ +rvVehicleWalker::Restore +================ +*/ +void rvVehicleWalker::Restore ( idRestoreGame *savefile ) { + //twhitaker: I just happened to see this, while going through this code which I originally wrote (at 3am or so). + //TODO: fix this. Make stopAnimName an idStr? + idStr str; + savefile->ReadString( str ); + stopAnimName = str; +} + +/* +================ +rvVehicleWalker::UpdateState +================ +*/ +void rvVehicleWalker::UpdateState ( void ) { + rvVehiclePosition& pos = positions[0]; + usercmd_t& cmd = pos.mInputCmd; + + vfl.driver = pos.IsOccupied(); + vfl.forward = (vfl.driver && cmd.forwardmove > 0); + vfl.backward = (vfl.driver && cmd.forwardmove < 0); + vfl.right = (vfl.driver && cmd.rightmove < 0); + vfl.left = (vfl.driver && cmd.rightmove > 0); + vfl.strafe = (vfl.driver && cmd.buttons & BUTTON_STRAFE ); + + if ( g_vehicleMode.GetInteger() != 0 ) { + vfl.strafe = !vfl.strafe; + } +} + +/* +================ +rvVehicleWalker::SetInput +================ +*/ +void rvVehicleWalker::SetInput ( int position, const usercmd_t& cmd, const idAngles& newAngles ) { + usercmd_t* pcmd = const_cast( &cmd ); + pcmd->rightmove *= -1; + GetPosition(position)->SetInput ( cmd, newAngles ); +} + +/* +================ +rvVehicleWalker::HandleStrafing +================ +*/ +void rvVehicleWalker::HandleStrafing ( void ) { + if ( vfl.right ) { + additionalDelta -= spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } + if ( vfl.left ) { + additionalDelta += spawnArgs.GetVector( "strafe_delta", "0 0 1.2" ); + } +} + +// mekberg: overloaded this because physics bounds code is significantly different +/* +===================== +rvVehicleWalker::FindClearExitPoint +===================== +*/ +// FIXME: this whole function could be cleaned up +bool rvVehicleWalker::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { + trace_t trace; + const rvVehiclePosition* position = GetPosition( pos ); + idActor* driver = position->GetDriver(); + idVec3 end; + idVec3 traceOffsetPoints[4]; + const float error = 1.1f; + + origin.Zero(); + axis.Identity(); + + idMat3 driverAxis = driver->viewAxis; + idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); + + idMat3 vehicleAxis = position->GetEyeAxis(); + idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); + + idBounds driverBounds( driver->GetPhysics()->GetBounds() ); + idBounds vehicleBounds( GetPhysics()->GetBounds() ); + idBounds driverAbsBounds; + idBounds vehicleAbsBounds; + + vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, GetPhysics()->GetAxis() ); + if( position->fl.driverVisible ) { + // May want to do this even if the driver isn't visible + if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { + axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; + origin = vehicleOrigin + position->mExitPosOffset * axis; + } else { + origin = driverOrigin; + axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; + } + return true; + } + + // Build list + // FIXME: try and find a cleaner way to do this + traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); + traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); + traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); + traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); + + for( int ix = 0; ix < 4; ++ix ) { + //Try all four sides and on top if need be + end = vehicleOrigin + traceOffsetPoints[ ix ] * error; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); +// RAVEN END + driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); + if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { + origin = trace.endpos; + axis = vehicleAxis; + return true; + } + } + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvVehicleWalker ) + STATE ( "ForwardLeft", rvVehicleWalker::Frame_ForwardLeft ) + STATE ( "ForwardRight", rvVehicleWalker::Frame_ForwardRight ) + STATE ( "BackwardLeft", rvVehicleWalker::Frame_BackwardLeft ) + STATE ( "BackwardRight", rvVehicleWalker::Frame_BackwardRight ) + + STATE ( "Wait_OnlineAnim", rvVehicleWalker::State_Wait_OnlineAnim ) + + STATE ( "State_Idle", rvVehicleWalker::State_Idle ) + STATE ( "State_IdleThink", rvVehicleWalker::State_IdleThink ) + STATE ( "State_IdleOffline", rvVehicleWalker::State_IdleOffline ) + STATE ( "State_Offline", rvVehicleWalker::State_Offline ) + STATE ( "State_Online", rvVehicleWalker::State_Online ) + + STATE ( "State_ForwardStart", rvVehicleWalker::State_ForwardStart ) + STATE ( "State_Forward", rvVehicleWalker::State_Forward ) + STATE ( "State_BackwardStart", rvVehicleWalker::State_BackwardStart ) + STATE ( "State_Backward", rvVehicleWalker::State_Backward ) + STATE ( "State_Stop", rvVehicleWalker::State_Stop ) + STATE ( "State_Turn", rvVehicleWalker::State_Turn ) + STATE ( "State_TurnThink", rvVehicleWalker::State_TurnThink ) + STATE ( "State_ScriptedAnim", rvVehicleWalker::State_ScriptedAnim ) +END_CLASS_STATES + +/* +================ +rvVehicleWalker::State_IdleOffline +================ +*/ +stateResult_t rvVehicleWalker::State_IdleOffline ( const stateParms_t& parms ) { + vfl.frozen = true; + + PlayCycle ( ANIMCHANNEL_LEGS, "idle_offline", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_Driver", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Online", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Online +================ +*/ +stateResult_t rvVehicleWalker::State_Online ( const stateParms_t& parms ) { + vfl.frozen = false; + + PlayAnim ( ANIMCHANNEL_LEGS, "start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_OnlineAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 4 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Offline +================ +*/ +stateResult_t rvVehicleWalker::State_Offline ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "stop", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 4 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleOffline", 4 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_Idle ( const stateParms_t& parms ) { + if ( SRESULT_WAIT != State_IdleThink ( parms ) ) { + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_IdleThink", 2 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Idle +================ +*/ +stateResult_t rvVehicleWalker::State_IdleThink ( const stateParms_t& parms ) { + if ( !vfl.driver || vfl.stalled ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Offline", parms.blendFrames ); + return SRESULT_DONE; + } + + if ( IsMovementEnabled ( ) ) { + if ( vfl.forward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_ForwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_BackwardStart", 2 ); + return SRESULT_DONE; + } + + if ( vfl.right || vfl.left ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 2 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_ForwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_ForwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "forward_start", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Forward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Forward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.forward ) { + stopAnimName = "forward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 4 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "forward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_BackwardStart +================ +*/ +stateResult_t rvVehicleWalker::State_BackwardStart ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, "backward_start", parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Backward", 2 ); + return SRESULT_DONE; +} + +stateResult_t rvVehicleWalker::State_Backward ( const stateParms_t& parms ) { + // If not moving anymore by the time we get here just play the stop anim + if ( !vfl.backward ) { + stopAnimName = "backward_stop_leftmid"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + return SRESULT_DONE; + } + + if ( !parms.stage ) { + PlayCycle( ANIMCHANNEL_LEGS, "backward", 2 ); + return SRESULT_STAGE(parms.stage + 1); + } + + if ( AnimDone( ANIMCHANNEL_LEGS, 2 ) ) { + return SRESULT_DONE; + } + + HandleStrafing(); + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::State_Stop +================ +*/ +stateResult_t rvVehicleWalker::State_Stop ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_LEGS, stopAnimName, parms.blendFrames ); + PostAnimState ( ANIMCHANNEL_LEGS, "Wait_TorsoAnim", 2 ); + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_Turn +================ +*/ +stateResult_t rvVehicleWalker::State_Turn ( const stateParms_t& parms ) { + if ( vfl.left ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_left", parms.blendFrames ); + } else if ( vfl.right ) { + PlayAnim ( ANIMCHANNEL_LEGS, "turn_right", parms.blendFrames ); + } else { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PostAnimState ( ANIMCHANNEL_LEGS, "State_TurnThink", 16 ); + + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_TurnThink +================ +*/ +stateResult_t rvVehicleWalker::State_TurnThink ( const stateParms_t& parms ) { + // If moving again bail on the turn, reguardless of whether its in mid animation + if ( vfl.forward || vfl.backward ) { + PostAnimState ( ANIMCHANNEL_LEGS, "State_Idle", parms.blendFrames ); + return SRESULT_DONE; + } + // If the animation is done then repeat + if ( AnimDone ( ANIMCHANNEL_LEGS, 8 ) ){ + PostAnimState ( ANIMCHANNEL_LEGS, "State_Turn", 8 ); + return SRESULT_DONE; + } + + HandleStrafing(); + + return SRESULT_WAIT; +} + +/* +================ +rvVehicleWalker::Frame_ForwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardLeft ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_ForwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_ForwardRight ( int ) { + if ( !vfl.forward ) { + stopAnimName = "forward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + + +/* +================ +rvVehicleWalker::Frame_BackwardLeft +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardLeft ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_left"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::Frame_BackwardRight +================ +*/ +stateResult_t rvVehicleWalker::Frame_BackwardRight ( int ) { + if ( !vfl.backward ) { + stopAnimName = "backward_stop_right"; + SetAnimState ( ANIMCHANNEL_LEGS, "State_Stop", 2 ); + } + return SRESULT_OK; +} + +/* +================ +rvVehicleWalker::State_Wait_OnlineAnim +================ +*/ +stateResult_t rvVehicleWalker::State_Wait_OnlineAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) && vfl.driver ) { + return SRESULT_WAIT; + } + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::State_ScriptedAnim +================ +*/ +stateResult_t rvVehicleWalker::State_ScriptedAnim ( const stateParms_t& parms ) { + if ( !AnimDone ( ANIMCHANNEL_LEGS, parms.blendFrames ) ) { + return SRESULT_WAIT; + } + Event_ScriptedStop(); + return SRESULT_DONE; +} + +/* +================ +rvVehicleWalker::Event_ScriptedAnim +================ +*/ +void rvVehicleWalker::Event_ScriptedAnim( const char* animname, int blendFrames, bool loop, bool endWithIdle ) { + vfl.endWithIdle = endWithIdle; + if ( loop ) { + PlayCycle ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_LEGS, animname, blendFrames ); + } + SetAnimState ( ANIMCHANNEL_LEGS, "State_ScriptedAnim", blendFrames ); + vfl.scripted = true; +} + +/* +================ +rvVehicleWalker::Event_ScriptedDone +================ +*/ +void rvVehicleWalker::Event_ScriptedDone( void ) { + idThread::ReturnInt( !vfl.scripted ); +} + +/* +================ +rvVehicleWalker::Event_ScriptedStop +================ +*/ +void rvVehicleWalker::Event_ScriptedStop( void ) { + vfl.scripted = false; + + if ( vfl.endWithIdle ) { + SetAnimState ( ANIMCHANNEL_LEGS, "State_Idle", 2 ); + } +} diff --git a/src/mpgame/weapon/WeaponBlaster.cpp b/src/mpgame/weapon/WeaponBlaster.cpp new file mode 100644 index 0000000..00b2251 --- /dev/null +++ b/src/mpgame/weapon/WeaponBlaster.cpp @@ -0,0 +1,511 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +#define BLASTER_SPARM_CHARGEGLOW 6 + +class rvWeaponBlaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponBlaster ); + + rvWeaponBlaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + bool UpdateAttack ( void ); + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + int chargeTime; + int chargeDelay; + idVec2 chargeGlow; + bool fireForced; + int fireHeldTime; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Charge ( const stateParms_t& parms ); + stateResult_t State_Charged ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponBlaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponBlaster ) +END_CLASS + +/* +================ +rvWeaponBlaster::rvWeaponBlaster +================ +*/ +rvWeaponBlaster::rvWeaponBlaster ( void ) { +} + +/* +================ +rvWeaponBlaster::UpdateFlashlight +================ +*/ +bool rvWeaponBlaster::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponBlaster::Flashlight +================ +*/ +void rvWeaponBlaster::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +================ +rvWeaponBlaster::UpdateAttack +================ +*/ +bool rvWeaponBlaster::UpdateAttack ( void ) { + // Clear fire forced + if ( fireForced ) { + if ( !wsfl.attack ) { + fireForced = false; + } else { + return false; + } + } + + // If the player is pressing the fire button and they have enough ammo for a shot + // then start the shooting process. + if ( wsfl.attack && gameLocal.time >= nextAttackTime ) { + // Save the time which the fire button was pressed + if ( fireHeldTime == 0 ) { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + fireHeldTime = gameLocal.time; + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + } + } + + // If they have the charge mod and they have overcome the initial charge + // delay then transition to the charge state. + if ( fireHeldTime != 0 ) { + if ( gameLocal.time - fireHeldTime > chargeDelay ) { + SetState ( "Charge", 4 ); + return true; + } + + // If the fire button was let go but was pressed at one point then + // release the shot. + if ( !wsfl.attack ) { + idPlayer * player = gameLocal.GetLocalPlayer(); + if( player ) { + + if( player->GuiActive()) { + //make sure the player isn't looking at a gui first + SetState ( "Lower", 0 ); + } else { + SetState ( "Fire", 0 ); + } + } + return true; + } + } + + return false; +} + +/* +================ +rvWeaponBlaster::Spawn +================ +*/ +void rvWeaponBlaster::Spawn ( void ) { + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + SetState ( "Raise", 0 ); + + chargeGlow = spawnArgs.GetVec2 ( "chargeGlow" ); + chargeTime = SEC2MS ( spawnArgs.GetFloat ( "chargeTime" ) ); + chargeDelay = SEC2MS ( spawnArgs.GetFloat ( "chargeDelay" ) ); + + fireHeldTime = 0; + fireForced = false; + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponBlaster::Save +================ +*/ +void rvWeaponBlaster::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( chargeTime ); + savefile->WriteInt ( chargeDelay ); + savefile->WriteVec2 ( chargeGlow ); + savefile->WriteBool ( fireForced ); + savefile->WriteInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::Restore +================ +*/ +void rvWeaponBlaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( chargeTime ); + savefile->ReadInt ( chargeDelay ); + savefile->ReadVec2 ( chargeGlow ); + savefile->ReadBool ( fireForced ); + savefile->ReadInt ( fireHeldTime ); +} + +/* +================ +rvWeaponBlaster::PreSave +================ +*/ +void rvWeaponBlaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponBlaster::PostSave +================ +*/ +void rvWeaponBlaster::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponBlaster ) + STATE ( "Raise", rvWeaponBlaster::State_Raise ) + STATE ( "Lower", rvWeaponBlaster::State_Lower ) + STATE ( "Idle", rvWeaponBlaster::State_Idle) + STATE ( "Charge", rvWeaponBlaster::State_Charge ) + STATE ( "Charged", rvWeaponBlaster::State_Charged ) + STATE ( "Fire", rvWeaponBlaster::State_Fire ) + STATE ( "Flashlight", rvWeaponBlaster::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponBlaster::State_Raise +================ +*/ +stateResult_t rvWeaponBlaster::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE(RAISE_WAIT); + + case RAISE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Lower +================ +*/ +stateResult_t rvWeaponBlaster::State_Lower ( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Idle +================ +*/ +stateResult_t rvWeaponBlaster::State_Idle ( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus ( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + if ( UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charge +================ +*/ +stateResult_t rvWeaponBlaster::State_Charge ( const stateParms_t& parms ) { + enum { + CHARGE_INIT, + CHARGE_WAIT, + }; + switch ( parms.stage ) { + case CHARGE_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, chargeGlow[0] ); + StartSound ( "snd_charge", SND_CHANNEL_ITEM, 0, false, NULL ); + PlayCycle( ANIMCHANNEL_ALL, "charging", parms.blendFrames ); + return SRESULT_STAGE ( CHARGE_WAIT ); + + case CHARGE_WAIT: + if ( gameLocal.time - fireHeldTime < chargeTime ) { + float f; + f = (float)(gameLocal.time - fireHeldTime) / (float)chargeTime; + f = chargeGlow[0] + f * (chargeGlow[1] - chargeGlow[0]); + f = idMath::ClampFloat ( chargeGlow[0], chargeGlow[1], f ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, f ); + + if ( !wsfl.attack ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + return SRESULT_WAIT; + } + SetState ( "Charged", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Charged +================ +*/ +stateResult_t rvWeaponBlaster::State_Charged ( const stateParms_t& parms ) { + enum { + CHARGED_INIT, + CHARGED_WAIT, + }; + switch ( parms.stage ) { + case CHARGED_INIT: + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 1.0f ); + + StopSound ( SND_CHANNEL_ITEM, false ); + StartSound ( "snd_charge_loop", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound ( "snd_charge_click", SND_CHANNEL_BODY, 0, false, NULL ); + return SRESULT_STAGE(CHARGED_WAIT); + + case CHARGED_WAIT: + if ( !wsfl.attack ) { + fireForced = true; + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Fire +================ +*/ +stateResult_t rvWeaponBlaster::State_Fire ( const stateParms_t& parms ) { + enum { + FIRE_INIT, + FIRE_WAIT, + }; + switch ( parms.stage ) { + case FIRE_INIT: + + StopSound ( SND_CHANNEL_ITEM, false ); + viewModel->SetShaderParm ( BLASTER_SPARM_CHARGEGLOW, 0 ); + //don't fire if we're targeting a gui. + idPlayer* player; + player = gameLocal.GetLocalPlayer(); + + //make sure the player isn't looking at a gui first + if( player && player->GuiActive() ) { + fireHeldTime = 0; + SetState ( "Lower", 0 ); + return SRESULT_DONE; + } + + if( player && !player->CanFire() ) { + fireHeldTime = 0; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + + + + if ( gameLocal.time - fireHeldTime > chargeTime ) { + Attack ( true, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_chargedflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "chargedfire", parms.blendFrames ); + } else { + Attack ( false, 1, spread, 0, 1.0f ); + PlayEffect ( "fx_normalflash", barrelJointView, false ); + PlayAnim( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + fireHeldTime = 0; + + return SRESULT_STAGE(FIRE_WAIT); + + case FIRE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) || UpdateAttack ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponBlaster::State_Flashlight +================ +*/ +stateResult_t rvWeaponBlaster::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/weapon/WeaponDarkMatterGun.cpp b/src/mpgame/weapon/WeaponDarkMatterGun.cpp new file mode 100644 index 0000000..14543a8 --- /dev/null +++ b/src/mpgame/weapon/WeaponDarkMatterGun.cpp @@ -0,0 +1,494 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../Projectile.h" + +class rvWeaponDarkMatterGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponDarkMatterGun ); + + rvWeaponDarkMatterGun ( void ); + ~rvWeaponDarkMatterGun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + enum darkMatterRing_t { + RING_OUTER, + RING_MIDDLE, + RING_INNER, + RING_MAX + }; + + struct rings_s { + idAngles angularVelocity; + jointHandle_t joint; + }; + rings_s rings[ RING_MAX ]; + + int nextRotateTime; + int ringStartTime; + int chargeDuration; + bool clientReload; + rvClientEffectPtr coreEffect; + rvClientEffectPtr coreStartEffect; + jointHandle_t jointCore; + + void InitRing ( darkMatterRing_t ring, const char* name ); + void StartRings ( bool chargeUp ); + void StopRings ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponDarkMatterGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponDarkMatterGun ) +END_CLASS + +/* +================ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::rvWeaponDarkMatterGun ( void ) { + coreStartEffect = NULL; + coreEffect = NULL; + ringStartTime = -1; + clientReload = false; +} + +/* +================ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun +================ +*/ +rvWeaponDarkMatterGun::~rvWeaponDarkMatterGun ( void ) { + StopRings ( ); +} + +/* +================ +rvWeaponDarkMatterGun::Spawn +================ +*/ +void rvWeaponDarkMatterGun::Spawn ( void ) { + SetState ( "Raise", 0 ); + + InitRing ( RING_OUTER, "outer" ); + InitRing ( RING_INNER, "inner" ); + InitRing ( RING_MIDDLE, "middle" ); + + nextRotateTime = 0; + + chargeDuration = SEC2MS ( spawnArgs.GetFloat ( "chargeDuration", ".5" ) ); + + jointCore = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_core" ) ); +} + +/* +================ +rvWeaponDarkMatterGun::Save +================ +*/ +void rvWeaponDarkMatterGun::Save ( idSaveGame *savefile ) const { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->WriteAngles ( rings[ i ].angularVelocity ); + savefile->WriteJoint ( rings[ i ].joint ); + } + savefile->WriteInt ( nextRotateTime ); + savefile->WriteInt ( ringStartTime ); + savefile->WriteInt ( chargeDuration ); + savefile->WriteObject( coreEffect.GetEntity() ); + savefile->WriteObject( coreStartEffect.GetEntity() ); + savefile->WriteJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::Restore +================ +*/ +void rvWeaponDarkMatterGun::Restore ( idRestoreGame *savefile ) { + for ( int i = 0; i < RING_MAX; i++ ) { + savefile->ReadAngles ( rings[ i ].angularVelocity ); + savefile->ReadJoint ( rings[ i ].joint ); + } + savefile->ReadInt ( nextRotateTime ); + savefile->ReadInt ( ringStartTime ); + savefile->ReadInt ( chargeDuration ); + savefile->ReadObject( reinterpret_cast( coreEffect ) ); + savefile->ReadObject( reinterpret_cast( coreStartEffect ) ); + savefile->ReadJoint ( jointCore ); +} + +/* +================ +rvWeaponDarkMatterGun::PreSave +================ +*/ +void rvWeaponDarkMatterGun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + +} + +/* +================ +rvWeaponDarkMatterGun::PostSave +================ +*/ +void rvWeaponDarkMatterGun::PostSave ( void ) { + + //start the ring sounds + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); +} + + +/* +================ +rvWeaponDarkMatterGun::InitRing +================ +*/ +void rvWeaponDarkMatterGun::InitRing ( darkMatterRing_t ring, const char* name ) { + rings[ring].angularVelocity = spawnArgs.GetAngles ( va("ring_%s_velocity", name ) ); + rings[ring].joint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("ring_%s_joint", name ) ) ); +} + +/* +================ +rvWeaponDarkMatterGun::StartRings +================ +*/ +void rvWeaponDarkMatterGun::StartRings ( bool chargeUp ) { + int i; + + if ( ringStartTime == -1 ) { + StartSound ( "snd_rings", SND_CHANNEL_VOICE, 0, false, NULL ); + ringStartTime = gameLocal.time; + } + + if ( chargeUp ) { + coreStartEffect = viewModel->PlayEffect( "fx_core_start", jointCore ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, chargeDuration / 2 ); + } + } else if ( !coreEffect ) { + coreEffect = viewModel->PlayEffect( "fx_core", jointCore, true ); + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( rings[i].joint, rings[i].angularVelocity, gameLocal.time, 0 ); + } + } +} + +/* +================ +rvWeaponDarkMatterGun::StopRings +================ +*/ +void rvWeaponDarkMatterGun::StopRings ( void ) { + int i; + + if ( !viewModel ) { + return; + } + + viewModel->StopSound ( SND_CHANNEL_VOICE, false ); + + if ( coreEffect ) { + coreEffect->Stop ( ); + coreEffect = NULL; + } + + if ( coreStartEffect ) { + coreStartEffect->Stop ( ); + coreStartEffect = NULL; + } + + for ( i = 0; i < RING_MAX; i ++ ) { + viewModel->GetAnimator()->ClearJoint ( rings[i].joint ); + } + + ringStartTime = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponDarkMatterGun ) + STATE ( "Idle", rvWeaponDarkMatterGun::State_Idle) + STATE ( "Fire", rvWeaponDarkMatterGun::State_Fire ) + STATE ( "Reload", rvWeaponDarkMatterGun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponDarkMatterGun::State_Idle +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + // Auto reload? + if ( !AmmoInClip ( ) && AmmoAvailable () && !clientReload ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + clientReload = false; + + StartRings ( false ); + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload ) { + if ( owner->entityNumber != gameLocal.localClientNum ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } else { + wsfl.netReload = false; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Fire +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + StopRings ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 2 ) || (gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponDarkMatterGun::State_Reload +================ +*/ +stateResult_t rvWeaponDarkMatterGun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + StartRings ( true ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + clientReload = true; + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + AddToClip ( ClipSize() ); + SetStatus ( WP_READY ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + rvDarkMatterProjectile + +=============================================================================== +*/ + +class rvDarkMatterProjectile : public idProjectile { +public : + CLASS_PROTOTYPE( rvDarkMatterProjectile ); + + rvDarkMatterProjectile ( void ); + ~rvDarkMatterProjectile ( void ); + + void Spawn ( void ); + + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + + virtual void Think ( void ); + +protected: + + int nextDamageTime; + const idDict* radiusDamageDef; +}; + +CLASS_DECLARATION( idProjectile, rvDarkMatterProjectile ) +END_CLASS + +/* +================ +rvDarkMatterProjectile::rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::rvDarkMatterProjectile ( void ) { + radiusDamageDef = NULL; +} + +/* +================ +rvDarkMatterProjectile::~rvDarkMatterProjectile +================ +*/ +rvDarkMatterProjectile::~rvDarkMatterProjectile ( void ) { +} + +/* +================ +rvDarkMatterProjectile::Spawn +================ +*/ +void rvDarkMatterProjectile::Spawn ( void ) { + nextDamageTime = 0; + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Save +================ +*/ +void rvDarkMatterProjectile::Save ( idSaveGame *savefile ) const { + savefile->WriteInt ( nextDamageTime ); +} + +/* +================ +rvDarkMatterProjectile::Restore +================ +*/ +void rvDarkMatterProjectile::Restore ( idRestoreGame *savefile ) { + savefile->ReadInt ( nextDamageTime ); + + radiusDamageDef = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( "def_radius_damage" ) ); +} + +/* +================ +rvDarkMatterProjectile::Think +================ +*/ +void rvDarkMatterProjectile::Think ( void ) { + physicsObj.SetClipMask( MASK_DMGSOLID ); + idProjectile::Think ( ); + + if ( gameLocal.time > nextDamageTime ) { + gameLocal.RadiusDamage ( GetPhysics()->GetOrigin(), this, owner, owner, NULL, spawnArgs.GetString( "def_radius_damage" ), 1.0f, &hitCount ); + nextDamageTime = gameLocal.time + SEC2MS ( spawnArgs.GetFloat ( "damageRate", ".05" ) ); + } +} + + diff --git a/src/mpgame/weapon/WeaponGauntlet.cpp b/src/mpgame/weapon/WeaponGauntlet.cpp new file mode 100644 index 0000000..7692ad2 --- /dev/null +++ b/src/mpgame/weapon/WeaponGauntlet.cpp @@ -0,0 +1,539 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGauntlet : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGauntlet ); + + rvWeaponGauntlet( void ); + ~rvWeaponGauntlet( void ); + + virtual void Spawn ( void ); + virtual void CleanupWeapon ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idAngles bladeSpinFast; + idAngles bladeSpinSlow; + jointHandle_t bladeJoint; + jointHandle_t bladeJoint_world; + int bladeAccel; + + float range; + + rvClientEffectPtr impactEffect; + int impactMaterial; + + void Attack ( void ); + void StartBlade ( void ); + void StopBlade ( void ); + +private: + + void PlayLoopSound ( int sndType ); + int loopSound; + enum { + LOOP_NONE, + LOOP_WALL, + LOOP_FLESH + }; + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponGauntlet ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGauntlet ) +END_CLASS + +/* +================ +rvWeaponGauntlet::rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::rvWeaponGauntlet( void ) { + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::~rvWeaponGauntlet +================ +*/ +rvWeaponGauntlet::~rvWeaponGauntlet(void) +{ + if ( viewModel ) { + StopSound( SND_CHANNEL_WEAPON, false ); + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + +} +/* +================ +rvWeaponGauntlet::Spawn +================ +*/ +void rvWeaponGauntlet::Spawn ( void ) { + SetState ( "Raise", 0 ); + + bladeJoint = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + bladeJoint_world= GetWorldModel()->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( "joint_blade", "center" ) ); + + bladeSpinFast = spawnArgs.GetAngles ( "blade_spinfast" ); + bladeSpinSlow = spawnArgs.GetAngles ( "blade_spinslow" ); + bladeAccel = SEC2MS ( spawnArgs.GetFloat ( "blade_accel", ".25" ) ); + + range = spawnArgs.GetFloat ( "range", "32" ); + + impactMaterial = -1; + impactEffect = NULL; + loopSound = LOOP_NONE; +} + +/* +================ +rvWeaponGauntlet::Save +================ +*/ +void rvWeaponGauntlet::Save ( idSaveGame *savefile ) const { + savefile->WriteAngles ( bladeSpinFast ); + savefile->WriteAngles ( bladeSpinSlow ); + savefile->WriteJoint ( bladeJoint ); + savefile->WriteJoint ( bladeJoint_world ); + + savefile->WriteInt ( bladeAccel ); + + savefile->WriteFloat ( range ); + + savefile->WriteObject ( impactEffect ); + savefile->WriteInt ( impactMaterial ); + savefile->WriteInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::Restore +================ +*/ +void rvWeaponGauntlet::Restore ( idRestoreGame *savefile ) { + savefile->ReadAngles ( bladeSpinFast ); + savefile->ReadAngles ( bladeSpinSlow ); + savefile->ReadJoint ( bladeJoint ); + savefile->ReadJoint ( bladeJoint_world ); + + savefile->ReadInt ( bladeAccel ); + + savefile->ReadFloat ( range ); + + savefile->ReadObject ( reinterpret_cast( impactEffect ) ); + savefile->ReadInt ( impactMaterial ); + savefile->ReadInt ( loopSound ); +} + +/* +================ +rvWeaponGauntlet::PreSave +================ +*/ +void rvWeaponGauntlet::PreSave ( void ) { + +} + +/* +================ +rvWeaponGauntlet::PostSave +================ +*/ +void rvWeaponGauntlet::PostSave( void ) { +} + +void rvWeaponGauntlet::PlayLoopSound( int sndType ) { + if ( loopSound == sndType ) { + return; + } + const char *loopSoundString = NULL; + switch ( sndType ) { + case LOOP_NONE: + default: + loopSoundString = "snd_spin_loop"; + break; + case LOOP_WALL: + loopSoundString = "snd_spin_wall"; + break; + case LOOP_FLESH: + loopSoundString = "snd_spin_flesh"; + break; + } + if ( loopSoundString ) { + loopSound = sndType; + StartSound( loopSoundString, SND_CHANNEL_WEAPON, 0, false, 0 ); + } +} + +/* +================ +rvWeaponGauntlet::CleanupWeapon +================ +*/ +void rvWeaponGauntlet::CleanupWeapon( void ) { + + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); +} + +/* +================ +rvWeaponGauntlet::Attack +================ +*/ +void rvWeaponGauntlet::Attack ( void ) { + trace_t tr; + idEntity* ent; + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + owner->WeaponFireFeedback( &weaponDef->dict ); + + if ( tr.fraction >= 1.0f ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + PlayLoopSound( LOOP_NONE ); + return; + } + + // Entity we hit? + ent = gameLocal.entities[tr.c.entityNum]; + + // If the impact material changed then stop the impact effect + if ( (tr.c.materialType && tr.c.materialType->Index ( ) != impactMaterial) || + (!tr.c.materialType && impactMaterial != -1) ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; + } + + // In singleplayer-- the gauntlet never effects marine AI + if( !gameLocal.isMultiplayer ) { + idActor* actor_ent = 0; + + //ignore both the body and the head. + if (ent->IsType( idActor::GetClassType()) ) { + actor_ent = static_cast(ent); + } else if (ent->IsType ( idAFAttachment::GetClassType()) ) { + actor_ent = static_cast(ent->GetBindMaster()); + } + + if ( actor_ent && actor_ent->team == gameLocal.GetLocalPlayer()->team ) { + PlayLoopSound( LOOP_NONE ); + return; + } + } + + //multiplayer-- don't gauntlet dead stuff + if( gameLocal.isMultiplayer ) { + idPlayer * player; + if ( ent->IsType( idPlayer::GetClassType() )) { + player = static_cast< idPlayer* >(ent); + if (player->health <= 0) { + return; + } + } + + } + + if ( !impactEffect ) { + impactMaterial = tr.c.materialType ? tr.c.materialType->Index() : -1; + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_impact", tr.c.materialType ), tr.endpos, tr.c.normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( tr.endpos ); + impactEffect->SetAxis ( tr.c.normal.ToMat3() ); + } + + // Do damage? + if ( gameLocal.time > nextAttackTime ) { + if ( ent ) { + if ( ent->fl.takedamage ) { + float dmgScale = 1.0f; + dmgScale *= owner->PowerUpModifier( PMOD_MELEE_DAMAGE ); + ent->Damage ( owner, owner, playerViewAxis[0], spawnArgs.GetString ( "def_damage" ), dmgScale, 0 ); + StartSound( "snd_hit", SND_CHANNEL_ANY, 0, false, NULL ); + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + PlayLoopSound( LOOP_FLESH ); + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_WALL ); + } + } else { + PlayLoopSound( LOOP_NONE ); + } + nextAttackTime = gameLocal.time + fireRate; + } +} + +/* +================ +rvWeaponGauntlet::StartBlade +================ +*/ +void rvWeaponGauntlet::StartBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinFast, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_ITEM, false ); +// StartSound ( "snd_blade_fast", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_spin_up", SND_CHANNEL_ITEM, 0, false, 0 ); +} + +/* +================ +rvWeaponGauntlet::StopBlade +================ +*/ +void rvWeaponGauntlet::StopBlade ( void ) { + if ( viewModel ) { + viewModel->GetAnimator()->SetJointAngularVelocity ( bladeJoint, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + if ( GetWorldModel() ) { + GetWorldModel()->GetAnimator()->SetJointAngularVelocity ( bladeJoint_world, bladeSpinSlow, gameLocal.time, bladeAccel ); + } + + StopSound ( SND_CHANNEL_WEAPON, false ); +// StartSound ( "snd_blade_slow", SND_CHANNEL_ITEM, 0, false, NULL ); + + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect = NULL; + } + impactMaterial = -1; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGauntlet ) + STATE ( "Raise", rvWeaponGauntlet::State_Raise ) + STATE ( "Lower", rvWeaponGauntlet::State_Lower ) + STATE ( "Idle", rvWeaponGauntlet::State_Idle) + STATE ( "Fire", rvWeaponGauntlet::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponGauntlet::State_Raise +================ +*/ +stateResult_t rvWeaponGauntlet::State_Raise( const stateParms_t& parms ) { + enum { + RAISE_INIT, + RAISE_WAIT, + }; + switch ( parms.stage ) { + case RAISE_INIT: + SetStatus( WP_RISING ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE( RAISE_WAIT ); + case RAISE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Lower +================ +*/ +stateResult_t rvWeaponGauntlet::State_Lower( const stateParms_t& parms ) { + enum { + LOWER_INIT, + LOWER_WAIT, + LOWER_WAITRAISE + }; + switch ( parms.stage ) { + case LOWER_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "lower", parms.blendFrames ); + return SRESULT_STAGE(LOWER_WAIT); + + case LOWER_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(LOWER_WAITRAISE); + } + return SRESULT_WAIT; + + case LOWER_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Idle +================ +*/ +stateResult_t rvWeaponGauntlet::State_Idle( const stateParms_t& parms ) { + enum { + IDLE_INIT, + IDLE_WAIT, + }; + switch ( parms.stage ) { + case IDLE_INIT: + SetStatus( WP_READY ); + StopBlade(); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE( IDLE_WAIT ); + + case IDLE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack ) { + SetState( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGauntlet::State_Fire +================ +*/ +stateResult_t rvWeaponGauntlet::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_START, + STAGE_START_WAIT, + STAGE_LOOP, + STAGE_LOOP_WAIT, + STAGE_END, + STAGE_END_WAIT + }; + switch ( parms.stage ) { + case STAGE_START: + PlayAnim( ANIMCHANNEL_ALL, "attack_start", parms.blendFrames ); + StartBlade(); + loopSound = LOOP_NONE; + // #32 - no gauntlet spin up + //return SRESULT_STAGE( STAGE_START_WAIT ); + return SRESULT_STAGE( STAGE_LOOP ); + + case STAGE_START_WAIT: + if ( !wsfl.attack ) { + return SRESULT_STAGE( STAGE_END ); + } + if ( AnimDone( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + return SRESULT_STAGE( STAGE_LOOP ); + } + return SRESULT_WAIT; + + case STAGE_LOOP: + PlayCycle( ANIMCHANNEL_ALL, "attack_loop", parms.blendFrames ); + StartSound( "snd_spin_loop", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE(STAGE_LOOP_WAIT); + + case STAGE_LOOP_WAIT: + if ( !wsfl.attack || wsfl.lowerWeapon ) { + return SRESULT_STAGE( STAGE_END ); + } + Attack(); + return SRESULT_WAIT; + + case STAGE_END: + PlayAnim( ANIMCHANNEL_ALL, "attack_end", parms.blendFrames ); + StopBlade(); + StartSound( "snd_spin_down", SND_CHANNEL_WEAPON, 0, false, 0 ); + return SRESULT_STAGE( STAGE_END_WAIT ); + + case STAGE_END_WAIT: + if ( wsfl.attack || AnimDone( ANIMCHANNEL_ALL, parms.blendFrames ) ) { + PostState( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/weapon/WeaponGrenadeLauncher.cpp b/src/mpgame/weapon/WeaponGrenadeLauncher.cpp new file mode 100644 index 0000000..dd47d41 --- /dev/null +++ b/src/mpgame/weapon/WeaponGrenadeLauncher.cpp @@ -0,0 +1,226 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponGrenadeLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponGrenadeLauncher ); + + rvWeaponGrenadeLauncher ( void ); + + virtual void Spawn ( void ); + void PreSave ( void ); + void PostSave ( void ); + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + const char* GetFireAnim() const { return (!AmmoInClip()) ? "fire_empty" : "fire"; } + const char* GetIdleAnim() const { return (!AmmoInClip()) ? "idle_empty" : "idle"; } + + CLASS_STATES_PROTOTYPE ( rvWeaponGrenadeLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponGrenadeLauncher ) +END_CLASS + +/* +================ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher +================ +*/ +rvWeaponGrenadeLauncher::rvWeaponGrenadeLauncher ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::Spawn +================ +*/ +void rvWeaponGrenadeLauncher::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponGrenadeLauncher::PreSave +================ +*/ +void rvWeaponGrenadeLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponGrenadeLauncher::PostSave +================ +*/ +void rvWeaponGrenadeLauncher::PostSave ( void ) { +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponGrenadeLauncher ) + STATE ( "Idle", rvWeaponGrenadeLauncher::State_Idle) + STATE ( "Fire", rvWeaponGrenadeLauncher::State_Fire ) + STATE ( "Reload", rvWeaponGrenadeLauncher::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponGrenadeLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, GetIdleAnim(), parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, GetFireAnim(), 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t rvWeaponGrenadeLauncher::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/mpgame/weapon/WeaponHyperblaster.cpp b/src/mpgame/weapon/WeaponHyperblaster.cpp new file mode 100644 index 0000000..b91cb51 --- /dev/null +++ b/src/mpgame/weapon/WeaponHyperblaster.cpp @@ -0,0 +1,319 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int HYPERBLASTER_SPARM_BATTERY = 6; +const int HYPERBLASTER_SPIN_SPEED = 300; + +class rvWeaponHyperblaster : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponHyperblaster ); + + rvWeaponHyperblaster ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + jointHandle_t jointBatteryView; + bool spinning; + + void SpinUp ( void ); + void SpinDown ( void ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponHyperblaster ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponHyperblaster ) +END_CLASS + +/* +================ +rvWeaponHyperblaster::rvWeaponHyperblaster +================ +*/ +rvWeaponHyperblaster::rvWeaponHyperblaster ( void ) { +} + +/* +================ +rvWeaponHyperblaster::Spawn +================ +*/ +void rvWeaponHyperblaster::Spawn ( void ) { + jointBatteryView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_battery" ) ); + spinning = false; + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponHyperblaster::Save +================ +*/ +void rvWeaponHyperblaster::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint ( jointBatteryView ); + savefile->WriteBool ( spinning ); +} + +/* +================ +rvWeaponHyperblaster::Restore +================ +*/ +void rvWeaponHyperblaster::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint ( jointBatteryView ); + savefile->ReadBool ( spinning ); +} + +/* +================ +rvWeaponHyperBlaster::PreSave +================ +*/ +void rvWeaponHyperblaster::PreSave ( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, false ); + StopSound( SND_CHANNEL_BODY, false ); + StopSound( SND_CHANNEL_ITEM, false ); + StopSound( SND_CHANNEL_ANY, false ); + +} + +/* +================ +rvWeaponHyperBlaster::PostSave +================ +*/ +void rvWeaponHyperblaster::PostSave ( void ) { +} + +/* +================ +rvWeaponHyperblaster::SpinUp +================ +*/ +void rvWeaponHyperblaster::SpinUp ( void ) { + if ( spinning ) { + return; + } + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,HYPERBLASTER_SPIN_SPEED,0), gameLocal.time, 50 ); + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spin", SND_CHANNEL_BODY2, 0, false, NULL ); + spinning = true; +} + +/* +================ +rvWeaponHyperblaster::SpinDown +================ +*/ +void rvWeaponHyperblaster::SpinDown ( void ) { + if ( !spinning ) { + return; + } + + StopSound ( SND_CHANNEL_BODY2, false ); + StartSound ( "snd_battery_spindown", SND_CHANNEL_BODY2, 0, false, NULL ); + + if ( jointBatteryView != INVALID_JOINT ) { + viewAnimator->SetJointAngularVelocity ( jointBatteryView, idAngles(0,0,0), gameLocal.time, 500 ); + } + + spinning = false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponHyperblaster ) + STATE ( "Idle", rvWeaponHyperblaster::State_Idle) + STATE ( "Fire", rvWeaponHyperblaster::State_Fire ) + STATE ( "Reload", rvWeaponHyperblaster::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponHyperblaster::State_Idle +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + SpinDown ( ); + + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Fire +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SpinUp ( ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + if ( ClipSize() ) { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, (float)AmmoInClip()/ClipSize() ); + } else { + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( (!wsfl.attack || !AmmoInClip() || wsfl.lowerWeapon) && AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponHyperblaster::State_Reload +================ +*/ +stateResult_t rvWeaponHyperblaster::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SpinDown ( ); + + viewModel->SetShaderParm ( HYPERBLASTER_SPARM_BATTERY, 0 ); + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/mpgame/weapon/WeaponLightningGun.cpp b/src/mpgame/weapon/WeaponLightningGun.cpp new file mode 100644 index 0000000..3a51e45 --- /dev/null +++ b/src/mpgame/weapon/WeaponLightningGun.cpp @@ -0,0 +1,1007 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" +#include "../ai/AI_Manager.h" + +const int LIGHTNINGGUN_NUM_TUBES = 3; +const int LIGHTNINGGUN_MAX_PATHS = 3; + +const idEventDef EV_Lightninggun_RestoreHum( "", "" ); + +class rvLightningPath { +public: + idEntityPtr target; + idVec3 origin; + idVec3 normal; + rvClientEffectPtr trailEffect; + rvClientEffectPtr impactEffect; + + void StopEffects ( void ); + void UpdateEffects ( const idVec3& from, const idDict& dict ); + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); +}; + +class rvWeaponLightningGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponLightningGun ); + + rvWeaponLightningGun( void ); + ~rvWeaponLightningGun( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + virtual void ClientStale ( void ); + virtual void ClientUnstale( void ); + + void PreSave ( void ); + void PostSave ( void ); + + void Save ( idSaveGame* savefile ) const; + void Restore ( idRestoreGame* savefile ); + + bool NoFireWhileSwitching( void ) const { return true; } + +protected: + + void UpdateTubes ( void ); + + // Tube effects + rvClientEntityPtr tubeEffects[LIGHTNINGGUN_NUM_TUBES]; + idInterpolate tubeOffsets[LIGHTNINGGUN_NUM_TUBES]; + jointHandle_t tubeJoints[LIGHTNINGGUN_NUM_TUBES]; + float tubeMaxOffset; + float tubeThreshold; + int tubeTime; + + rvClientEntityPtr trailEffectView; + + int nextCrawlTime; + + float range; + jointHandle_t spireJointView; + jointHandle_t chestJointView; + + + rvLightningPath currentPath; + + // Chain lightning mod + idList chainLightning; + idVec3 chainLightningRange; + +private: + + void Attack ( idEntity* ent, const idVec3& dir, float power = 1.0f ); + + void UpdateChainLightning ( void ); + void StopChainLightning ( void ); + void UpdateEffects ( const idVec3& origin ); + void UpdateTrailEffect ( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view = false ); + + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponLightningGun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponLightningGun ) +EVENT( EV_Lightninggun_RestoreHum, rvWeaponLightningGun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponLightningGun::rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::rvWeaponLightningGun( void ) { +} + +/* +================ +rvWeaponLightningGun::~rvWeaponLightningGun +================ +*/ +rvWeaponLightningGun::~rvWeaponLightningGun( void ) { + int i; + + if ( trailEffectView ) { + trailEffectView->Stop( ); + } + currentPath.StopEffects( ); + StopChainLightning( ); + + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop( ); + } + } +} + +/* +================ +rvWeaponLightningGun::Spawn +================ +*/ +void rvWeaponLightningGun::Spawn( void ) { + int i; + + trailEffectView = NULL; + nextCrawlTime = 0; + + chainLightning.Clear( ); + + // get hitscan range for our firing + range = weaponDef->dict.GetFloat( "range", "10000" ); + + // Initialize tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeJoints[i] = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint_tube_%d",i), "" ) ); + tubeOffsets[i].Init ( gameLocal.time, 0, 0, 0 ); + } + + // Cache the max ammo for the weapon and the max tube offset + tubeMaxOffset = spawnArgs.GetFloat ( "tubeoffset" ); + tubeThreshold = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ) / (float)LIGHTNINGGUN_NUM_TUBES; + tubeTime = SEC2MS ( spawnArgs.GetFloat ( "tubeTime", ".25" ) ); + + spireJointView = viewModel->GetAnimator ( )->GetJointHandle ( "spire_1" ); + + if( gameLocal.GetLocalPlayer()) { + chestJointView = gameLocal.GetLocalPlayer()->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_hideGun_flash" ) ); + } else { + chestJointView = spireJointView; + } + + chainLightningRange = spawnArgs.GetVec2( "chainLightningRange", "150 300" ); + + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponLightningGun::Save +================ +*/ +void rvWeaponLightningGun::Save ( idSaveGame* savefile ) const { + int i; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Save ( savefile ); + savefile->WriteInterpolate ( tubeOffsets[i] ); + savefile->WriteJoint ( tubeJoints[i] ); + } + savefile->WriteFloat ( tubeMaxOffset ); + savefile->WriteFloat ( tubeThreshold ); + savefile->WriteInt ( tubeTime ); + + // General + trailEffectView.Save ( savefile ); + savefile->WriteInt ( nextCrawlTime ); + savefile->WriteFloat ( range ); + savefile->WriteJoint ( spireJointView ); + savefile->WriteJoint ( chestJointView ); + + currentPath.Save ( savefile ); + + // Chain Lightning mod + savefile->WriteInt ( chainLightning.Num() ); + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].Save ( savefile ); + } + savefile->WriteVec3 ( chainLightningRange ); +} + +/* +================ +rvWeaponLightningGun::Restore +================ +*/ +void rvWeaponLightningGun::Restore ( idRestoreGame* savefile ) { + int i; + int num; + float f; + + // Lightning Tubes + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + tubeEffects[i].Restore ( savefile ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartTime ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetDuration ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetStartValue ( f ); + savefile->ReadFloat ( f ); + tubeOffsets[i].SetEndValue ( f ); + savefile->ReadJoint ( tubeJoints[i] ); + } + savefile->ReadFloat ( tubeMaxOffset ); + savefile->ReadFloat ( tubeThreshold ); + savefile->ReadInt ( tubeTime ); + + // General + trailEffectView.Restore ( savefile ); + + savefile->ReadInt ( nextCrawlTime ); + savefile->ReadFloat ( range ); + savefile->ReadJoint ( spireJointView ); + savefile->ReadJoint ( chestJointView ); + + currentPath.Restore ( savefile ); + + // Chain lightning mod + savefile->ReadInt ( num ); + chainLightning.SetNum ( num ); + for ( i = 0; i < num; i ++ ) { + chainLightning[i].Restore ( savefile ); + } + savefile->ReadVec3 ( chainLightningRange ); + + +} + +/* +================ +rvWeaponLightningGun::Think +================ +*/ +void rvWeaponLightningGun::Think ( void ) { + trace_t tr; + + rvWeapon::Think(); + + UpdateTubes(); + + // If no longer firing or out of ammo then nothing to do in the think + if ( !wsfl.attack || !IsReady() || !AmmoAvailable() ) { + if ( trailEffectView ) { + trailEffectView->Stop ( ); + trailEffectView = NULL; + } + + currentPath.StopEffects(); + StopChainLightning(); + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds +// jshepard: allow projectile hits + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * range, + (MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE), owner ); +// RAVEN END + // Calculate the direction of the lightning effect using the barrel joint of the weapon + // and the end point of the trace + idVec3 origin; + idMat3 axis; + + //fire from chest if show gun models is off. + if( !cvarSystem->GetCVarBool("ui_showGun")) { + GetGlobalJointTransform( true, chestJointView, origin, axis ); + } else { + GetGlobalJointTransform( true, barrelJointView, origin, axis ); + } + + // Cache the target we are hitting + currentPath.origin = tr.endpos; + currentPath.normal = tr.c.normal; + currentPath.target = gameLocal.entities[tr.c.entityNum]; + + UpdateChainLightning(); + + UpdateEffects( origin ); + + MuzzleFlash(); + + // Inflict damage on all targets being attacked + if ( !gameLocal.isClient && gameLocal.time >= nextAttackTime ) { + int i; + float power = 1.0f; + idVec3 dir; + + owner->inventory.UseAmmo( ammoType, ammoRequired ); + + dir = tr.endpos - origin; + dir.Normalize ( ); + + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( currentPath.target, dir, power ); + for ( i = 0; i < chainLightning.Num(); i ++, power *= 0.75f ) { + Attack ( chainLightning[i].target, chainLightning[i].normal, power ); + } + + statManager->WeaponFired( owner, owner->GetCurrentWeapon(), chainLightning.Num() + 1 ); + } + + // Play the lightning crawl effect every so often when doing damage + if ( gameLocal.time > nextCrawlTime ) { + nextCrawlTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat ( "crawlDelay", ".3" )); + } +} + +/* +================ +rvWeaponLightningGun::Attack +================ +*/ +void rvWeaponLightningGun::Attack ( idEntity* ent, const idVec3& dir, float power ) { + // Double check + if ( !ent || !ent->fl.takedamage ) { + return; + } + + // Start a lightning crawl effect every so often + // we don't synchronize it, so let's not show it in multiplayer for a listen host. also fixes seeing it on the host from other instances + if ( !gameLocal.isMultiplayer && gameLocal.time > nextCrawlTime ) { + if ( ent->IsType( idActor::GetClassType() ) ) { + rvClientCrawlEffect* effect; + effect = new rvClientCrawlEffect( gameLocal.GetEffect( weaponDef->dict, "fx_crawl" ), ent, SEC2MS( spawnArgs.GetFloat ( "crawlTime", ".2" ) ) ); + effect->Play( gameLocal.time, false ); + } + } + +// RAVEN BEGIN +// mekberg: stats + if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { + statManager->WeaponHit( (idActor*)owner, ent, owner->GetCurrentWeapon() ); + } +// RAVEN END + ent->Damage( owner, owner, dir, spawnArgs.GetString ( "def_damage" ), power * owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ), 0 ); +} + +/* +================ +rvWeaponLightningGun::UpdateChainLightning +================ +*/ +void rvWeaponLightningGun::UpdateChainLightning ( void ) { + int i; + rvLightningPath* parent; + rvLightningPath* path; + idActor* target; + + // Chain lightning not enabled + if ( !chainLightningRange[0] ) { + return; + } + + // Need to have a primary target that is not on the same team for chain lightning to work + path = ¤tPath; + target = dynamic_cast(path->target.GetEntity()); + if ( !target || !target->health || target->team == owner->team ) { + StopChainLightning ( ); + return; + } + + currentPath.target->fl.takedamage = false; + + // Look through the chain lightning list and remove any paths that are no longer valid due + // to their range or visibility + for ( i = 0; i < chainLightning.Num(); i ++ ) { + parent = path; + path = &chainLightning[i]; + target = dynamic_cast(path->target.GetEntity()); + + // If the entity isnt valid anymore or is dead then remove it from the list + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Choose a destination origin the chain lightning path target + path->origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + path->origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); + path->normal = path->origin - parent->origin; + path->normal.Normalize(); + + // Make sure the entity is still within range of its parent + if ( (path->origin - parent->origin).LengthSqr ( ) > Square ( chainLightningRange[1] ) ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + // Trace to make sure we can still hit them + trace_t tr; +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, parent->origin, path->origin, MASK_SHOT_RENDERMODEL, parent->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + path->StopEffects ( ); + chainLightning.RemoveIndex ( i-- ); + continue; + } + + path->origin = tr.endpos; + + // Temporarily disable taking damage to flag this entity is used + target->fl.takedamage = false; + } + + // Start path at the end of the current path + if ( chainLightning.Num () ) { + path = &chainLightning[chainLightning.Num()-1]; + } else { + path = ¤tPath; + } + + // Cap the number of chain lightning jumps + while ( chainLightning.Num ( ) + 1 < LIGHTNINGGUN_MAX_PATHS ) { + for ( target = aiManager.GetEnemyTeam ( (aiTeam_t)owner->team ); target; target = target->teamNode.Next() ) { + // Must be a valid entity that takes damage to chain lightning too + if ( !target || target->health <= 0 || !target->fl.takedamage ) { + continue; + } + // Must be within starting chain path range + if ( (target->GetPhysics()->GetOrigin() - path->target->GetPhysics()->GetOrigin()).LengthSqr() > Square ( chainLightningRange[0] ) ) { + continue; + } + + // Make sure we can trace to the target from the current path + trace_t tr; + idVec3 origin; + origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; + origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint ( owner, tr, path->origin, origin, MASK_SHOT_RENDERMODEL, path->target ); +// RAVEN END + if ( tr.c.entityNum != target->entityNumber ) { + continue; + } + + path = &chainLightning.Alloc ( ); + path->target = target; + path->normal = tr.endpos - path->origin; + path->normal.Normalize(); + path->origin = tr.endpos; + + // Flag this entity to ensure it is skipped + target->fl.takedamage = false; + break; + } + // Found nothing? just break out early + if ( !target ) { + break; + } + } + + // Reset the take damage flag + currentPath.target->fl.takedamage = true; + for ( i = chainLightning.Num() - 1; i >= 0; i -- ) { + chainLightning[i].target->fl.takedamage = true; + } +} + +/* +================ +rvWeaponLightningGun::UpdateEffects +================ +*/ +void rvWeaponLightningGun::UpdateEffects( const idVec3& origin ) { + int i; + rvLightningPath* parent; + idVec3 dir; + + // Main path (world effects) + currentPath.UpdateEffects ( origin, weaponDef->dict ); + if ( currentPath.trailEffect ) { + currentPath.trailEffect->GetRenderEffect()->suppressSurfaceInViewID = owner->entityNumber + 1; + } + + // In view trail effect + dir = currentPath.origin - origin; + dir.Normalize(); + if ( !trailEffectView ) { + trailEffectView = gameLocal.PlayEffect ( gameLocal.GetEffect ( weaponDef->dict, "fx_trail" ), origin, dir.ToMat3(), true, currentPath.origin ); + } else { + trailEffectView->SetOrigin( origin ); + trailEffectView->SetAxis( dir.ToMat3() ); + trailEffectView->SetEndOrigin( currentPath.origin ); + } + if ( trailEffectView ) { + trailEffectView->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1;; + } + + if ( !currentPath.target ) { + return; + } + + // Chain lightning effects + parent = ¤tPath; + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].UpdateEffects( parent->origin, weaponDef->dict ); + parent = &chainLightning[i]; + } +} + +/* +================ +rvWeaponLightningGun::UpdateTrailEffect +================ +*/ +void rvWeaponLightningGun::UpdateTrailEffect( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view ) { + idVec3 dir; + dir = end - start; + dir.Normalize(); + + if ( !effect ) { + effect = gameLocal.PlayEffect( gameLocal.GetEffect( weaponDef->dict, view ? "fx_trail" : "fx_trail_world" ), start, dir.ToMat3(), true, end ); + } else { + effect->SetOrigin( start ); + effect->SetAxis( dir.ToMat3() ); + effect->SetEndOrigin( end ); + } +} + +/* +================ +rvWeaponLightningGun::StopChainLightning +================ +*/ +void rvWeaponLightningGun::StopChainLightning( void ) { + int i; + + if ( !chainLightning.Num( ) ) { + return; + } + + for ( i = 0; i < chainLightning.Num(); i ++ ) { + chainLightning[i].StopEffects( ); + } + + chainLightning.Clear( ); +} + +/* +================ +rvWeaponLightningGun::ClientStale +================ +*/ +void rvWeaponLightningGun::ClientStale( void ) { + rvWeapon::ClientStale( ); + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + + currentPath.StopEffects( ); +} + +/* +================ +rvWeaponLightningGun::PreSave +================ +*/ +void rvWeaponLightningGun::PreSave( void ) { + + SetState ( "Idle", 4 ); + + StopSound( SND_CHANNEL_WEAPON, 0); + StopSound( SND_CHANNEL_BODY, 0); + StopSound( SND_CHANNEL_BODY2, 0); + StopSound( SND_CHANNEL_BODY3, 0); + StopSound( SND_CHANNEL_ITEM, 0); + StopSound( SND_CHANNEL_ANY, false ); + + viewModel->StopSound( SND_CHANNEL_ANY, false ); + viewModel->StopSound( SND_CHANNEL_BODY, 0); + + worldModel->StopSound( SND_CHANNEL_ANY, false ); + worldModel->StopSound( SND_CHANNEL_BODY, 0); + + + if ( trailEffectView ) { + trailEffectView->Stop(); + trailEffectView->Event_Remove(); + trailEffectView = NULL; + } + for ( int i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + if ( tubeEffects[i] ) { + tubeEffects[i]->Event_Remove(); + } + } + currentPath.StopEffects( ); + +} + +/* +================ +rvWeaponLightningGun::PostSave +================ +*/ +void rvWeaponLightningGun::PostSave( void ) { + //restore the humming + PostEventMS( &EV_Lightninggun_RestoreHum, 10 ); +} + +/* +================ +rvWeaponLightningGun::UpdateTubes +================ +*/ +void rvWeaponLightningGun::UpdateTubes( void ) { + idAnimator* animator; + animator = viewModel->GetAnimator ( ); + assert ( animator ); + + int i; + float ammo; + + ammo = AmmoAvailable ( ); + for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { + float offset; + if ( ammo > tubeThreshold * i ) { + offset = tubeMaxOffset; + + if ( !tubeEffects[i] ) { + tubeEffects[i] = viewModel->PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_tube" ), tubeJoints[i], vec3_origin, mat3_identity, true ); + if( tubeEffects[i] ) { + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + } else { + offset = 0; + + if ( tubeEffects[i] ) { + tubeEffects[i]->Stop ( ); + tubeEffects[i] = NULL; + viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); + } + } + + // Attenuate the tube effect to how much ammo is left in the tube + if ( tubeEffects[i] ) { + tubeEffects[i]->Attenuate ( (ammo - tubeThreshold * (float)i) / tubeThreshold ); + } + + if ( offset > tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (1.0f - (current/tubeMaxOffset)) * (float)tubeTime, current, offset ); + } else if ( offset < tubeOffsets[i].GetEndValue ( ) ) { + float current; + current = tubeOffsets[i].GetCurrentValue(gameLocal.time); + tubeOffsets[i].Init ( gameLocal.time, (current/tubeMaxOffset) * (float)tubeTime, current, offset ); + } + + animator->SetJointPos ( tubeJoints[i], JOINTMOD_LOCAL, idVec3(tubeOffsets[i].GetCurrentValue(gameLocal.time),0,0) ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponLightningGun ) + STATE ( "Raise", rvWeaponLightningGun::State_Raise ) + STATE ( "Lower", rvWeaponLightningGun::State_Lower ) + STATE ( "Idle", rvWeaponLightningGun::State_Idle) + STATE ( "Fire", rvWeaponLightningGun::State_Fire ) +END_CLASS_STATES + +/* +================ +rvWeaponLightningGun::State_Raise +================ +*/ +stateResult_t rvWeaponLightningGun::State_Raise( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus( WP_RISING ); + viewModel->SetShaderParm( 6, 1 ); + PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); + return SRESULT_STAGE( STAGE_WAIT ); + + // Wait for the weapon to finish raising and allow it to be cancelled by + // lowering the weapon + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Lower +================ +*/ +stateResult_t rvWeaponLightningGun::State_Lower( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_LOWERING ); + PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetStatus( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Idle +================ +*/ +stateResult_t rvWeaponLightningGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus( WP_READY ); + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); + + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY3, false ); + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time > nextAttackTime && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponLightningGun::State_Fire +================ +*/ +stateResult_t rvWeaponLightningGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_ATTACKLOOP, + STAGE_DONE, + STAGE_DONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + StartSound( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + StartSound( "snd_fire_stereo", SND_CHANNEL_ITEM, 0, false, NULL ); + StartSound( "snd_fire_loop", SND_CHANNEL_BODY2, 0, false, NULL ); + + viewModel->SetShaderParm( 6, 0 ); + + viewModel->PlayEffect( "fx_spire", spireJointView, true ); + viewModel->PlayEffect( "fx_flash", barrelJointView, true ); + + if ( worldModel && flashJointWorld != INVALID_JOINT ) { + worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict,"fx_flash_world"), flashJointWorld, vec3_origin, mat3_identity, true ); + } + + PlayAnim( ANIMCHANNEL_ALL, "shoot_start", parms.blendFrames ); + return SRESULT_STAGE( STAGE_ATTACKLOOP ); + + case STAGE_ATTACKLOOP: + if ( !wsfl.attack || wsfl.lowerWeapon || !AmmoAvailable ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + PlayCycle( ANIMCHANNEL_ALL, "shoot_loop", 0 ); + if ( !gameLocal.isMultiplayer + && owner == gameLocal.GetLocalPlayer() ) { + owner->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + 500), 2.0f ); + } + } + return SRESULT_WAIT; + + case STAGE_DONE: + StopSound( SND_CHANNEL_BODY2, false ); + + viewModel->StopEffect( "fx_spire" ); + viewModel->StopEffect( "fx_flash" ); + if ( worldModel ) { + worldModel->StopEffect( gameLocal.GetEffect( weaponDef->dict, "fx_flash_world" ) ); + } + viewModel->SetShaderParm( 6, 1 ); + + PlayAnim( ANIMCHANNEL_ALL, "shoot_end", 0 ); + return SRESULT_STAGE( STAGE_DONEWAIT ); + + case STAGE_DONEWAIT: + if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { + SetState( "Idle", 4 ); + return SRESULT_DONE; + } + if ( !wsfl.lowerWeapon && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponLightningGun::Event_RestoreHum +================ +*/ +void rvWeaponLightningGun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY3, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); +} + +/* +================ +rvWeaponLightningGun::ClientUnStale +================ +*/ +void rvWeaponLightningGun::ClientUnstale( void ) { + Event_RestoreHum(); +} + +/* +=============================================================================== + + rvLightningPath + +=============================================================================== +*/ + +/* +================ +rvLightningPath::StopEffects +================ +*/ +void rvLightningPath::StopEffects( void ) { + if ( trailEffect ) { + trailEffect->Stop( ); + trailEffect->Event_Remove( ); + trailEffect = NULL; + } + if ( impactEffect ) { + impactEffect->Stop( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } +} + +/* +================ +rvLightningPath::UpdateEffects +================ +*/ +void rvLightningPath::UpdateEffects ( const idVec3& from, const idDict& dict ) { + idVec3 dir; + dir = origin - from; + dir.Normalize(); + + // Trail effect + if ( !trailEffect ) { + trailEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_trail_world" ), from, dir.ToMat3(), true, origin ); + } else { + trailEffect->SetOrigin ( from ); + trailEffect->SetAxis ( dir.ToMat3() ); + trailEffect->SetEndOrigin ( origin ); + } + + // Impact effect + if ( !target || target.GetEntityNum ( ) == ENTITYNUM_NONE ) { + if ( impactEffect ) { + impactEffect->Stop ( ); + impactEffect->PostEventMS( &EV_Remove, 1000 ); + impactEffect = NULL; + } + } else { + if ( !impactEffect ) { + impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_impact" ), origin, normal.ToMat3(), true ); + } else { + impactEffect->SetOrigin ( origin ); + impactEffect->SetAxis ( normal.ToMat3 ( ) ); + } + } +} + +/* +================ +rvLightningPath::Save +================ +*/ +void rvLightningPath::Save( idSaveGame* savefile ) const { + target.Save( savefile ); + savefile->WriteVec3( origin ); + savefile->WriteVec3( normal ); + trailEffect.Save( savefile ); + impactEffect.Save( savefile ); +} + +/* +================ +rvLightningPath::Restore +================ +*/ +void rvLightningPath::Restore( idRestoreGame* savefile ) { + target.Restore( savefile ); + savefile->ReadVec3( origin ); + savefile->ReadVec3( normal ); + trailEffect.Restore( savefile ); + impactEffect.Restore( savefile ); +} diff --git a/src/mpgame/weapon/WeaponMachinegun.cpp b/src/mpgame/weapon/WeaponMachinegun.cpp new file mode 100644 index 0000000..5c9217e --- /dev/null +++ b/src/mpgame/weapon/WeaponMachinegun.cpp @@ -0,0 +1,351 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +class rvWeaponMachinegun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponMachinegun ); + + rvWeaponMachinegun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + float spreadZoom; + bool fireHeld; + + bool UpdateFlashlight ( void ); + void Flashlight ( bool on ); + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Flashlight ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponMachinegun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponMachinegun ) +END_CLASS + +/* +================ +rvWeaponMachinegun::rvWeaponMachinegun +================ +*/ +rvWeaponMachinegun::rvWeaponMachinegun ( void ) { +} + +/* +================ +rvWeaponMachinegun::Spawn +================ +*/ +void rvWeaponMachinegun::Spawn ( void ) { + spreadZoom = spawnArgs.GetFloat ( "spreadZoom" ); + fireHeld = false; + + SetState ( "Raise", 0 ); + + Flashlight ( owner->IsFlashlightOn() ); +} + +/* +================ +rvWeaponMachinegun::Save +================ +*/ +void rvWeaponMachinegun::Save ( idSaveGame *savefile ) const { + savefile->WriteFloat ( spreadZoom ); + savefile->WriteBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::Restore +================ +*/ +void rvWeaponMachinegun::Restore ( idRestoreGame *savefile ) { + savefile->ReadFloat ( spreadZoom ); + savefile->ReadBool ( fireHeld ); +} + +/* +================ +rvWeaponMachinegun::PreSave +================ +*/ +void rvWeaponMachinegun::PreSave ( void ) { +} + +/* +================ +rvWeaponMachinegun::PostSave +================ +*/ +void rvWeaponMachinegun::PostSave ( void ) { +} + + +/* +================ +rvWeaponMachinegun::Think +================ +*/ +void rvWeaponMachinegun::Think() +{ + rvWeapon::Think(); + if ( zoomGui && owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat( "playerYaw", playerViewAxis.ToAngles().yaw ); + } +} + +/* +================ +rvWeaponMachinegun::UpdateFlashlight +================ +*/ +bool rvWeaponMachinegun::UpdateFlashlight ( void ) { + if ( !wsfl.flashlight ) { + return false; + } + + SetState ( "Flashlight", 0 ); + return true; +} + +/* +================ +rvWeaponMachinegun::Flashlight +================ +*/ +void rvWeaponMachinegun::Flashlight ( bool on ) { + owner->Flashlight ( on ); + + if ( on ) { + viewModel->ShowSurface ( "models/weapons/blaster/flare" ); + worldModel->ShowSurface ( "models/weapons/blaster/flare" ); + } else { + viewModel->HideSurface ( "models/weapons/blaster/flare" ); + worldModel->HideSurface ( "models/weapons/blaster/flare" ); + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponMachinegun ) + STATE ( "Idle", rvWeaponMachinegun::State_Idle) + STATE ( "Fire", rvWeaponMachinegun::State_Fire ) + STATE ( "Reload", rvWeaponMachinegun::State_Reload ) + STATE ( "Flashlight", rvWeaponMachinegun::State_Flashlight ) +END_CLASS_STATES + +/* +================ +rvWeaponMachinegun::State_Idle +================ +*/ +stateResult_t rvWeaponMachinegun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + + if ( fireHeld && !wsfl.attack ) { + fireHeld = false; + } + if ( !clipSize ) { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( !fireHeld && gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Fire +================ +*/ +stateResult_t rvWeaponMachinegun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spreadZoom, 0, 1.0f ); + fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + } + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( !fireHeld && wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + if ( UpdateFlashlight ( ) ) { + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponMachinegun::State_Reload +================ +*/ +stateResult_t rvWeaponMachinegun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponMachinegun::State_Flashlight +================ +*/ +stateResult_t rvWeaponMachinegun::State_Flashlight ( const stateParms_t& parms ) { + enum { + FLASHLIGHT_INIT, + FLASHLIGHT_WAIT, + }; + switch ( parms.stage ) { + case FLASHLIGHT_INIT: + SetStatus ( WP_FLASHLIGHT ); + // Wait for the flashlight anim to play + PlayAnim( ANIMCHANNEL_ALL, "flashlight", 0 ); + return SRESULT_STAGE ( FLASHLIGHT_WAIT ); + + case FLASHLIGHT_WAIT: + if ( !AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + return SRESULT_WAIT; + } + + if ( owner->IsFlashlightOn() ) { + Flashlight ( false ); + } else { + Flashlight ( true ); + } + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_ERROR; +} diff --git a/src/mpgame/weapon/WeaponNailgun.cpp b/src/mpgame/weapon/WeaponNailgun.cpp new file mode 100644 index 0000000..00e831f --- /dev/null +++ b/src/mpgame/weapon/WeaponNailgun.cpp @@ -0,0 +1,953 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" +#include "../Projectile.h" + +// Available drum speeds +const int NAILGUN_DRUMSPEED_STOPPED = 0; +const int NAILGUN_DRUMSPEED_SLOW = 1; +const int NAILGUN_DRUMSPEED_FAST = 2; + +// Spinup and spindown times +const int NAILGUN_SPINDOWN_TIME = 1000; +const int NAILGUN_SPINUP_TIME = 1000; + +// Nailgun shader parms +const int NAILGUN_SPARM_PLAYLEADIN = 7; + +// Nailgun weapon modifications +const int NAILGUN_MOD_ROF_AMMO = BIT(0); // power mod combines Rate of fire and ammoo +const int NAILGUN_MOD_SEEK = BIT(1); +//const int NAILGUN_MOD_AMMO = BIT(2); + +const int NAILGUN_SPIN_SNDCHANNEL = SND_CHANNEL_BODY2; + +class rvWeaponNailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponNailgun ); + + rvWeaponNailgun ( void ); + ~rvWeaponNailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + + idEntityPtr guideEnt; + int guideTime; + int guideStartTime; + rvClientEntityPtr guideEffect; + bool guideLocked; + float guideRange; + int guideHoldTime; + int guideAquireTime; + + jointHandle_t jointDrumView; + jointHandle_t jointPinsView; + jointHandle_t jointSteamRightView; + jointHandle_t jointSteamLeftView; + + jointHandle_t jointGuideEnt; + + int drumSpeed; + int drumSpeedIdeal; + float drumMultiplier; + + virtual void OnLaunchProjectile ( idProjectile* proj ); + +private: + + void UpdateGuideStatus ( float range = 0.0f ); + void CancelGuide ( void ); + bool DrumSpin ( int speed, int blendFrames ); + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_DrumSpinDown ( const stateParms_t& parms ); + stateResult_t State_DrumSpinUp ( const stateParms_t& parms ); + + stateResult_t Frame_ClaspOpen ( const stateParms_t& parms ); + stateResult_t Frame_ClaspClose ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponNailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponNailgun ) +END_CLASS + +/* +================ +rvWeaponNailgun::rvWeaponNailgun +================ +*/ +rvWeaponNailgun::rvWeaponNailgun ( void ) { +} + +/* +================ +rvWeaponNailgun::~rvWeaponNailgun +================ +*/ +rvWeaponNailgun::~rvWeaponNailgun ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } + if( viewModel ) { + viewModel->StopSound( NAILGUN_SPIN_SNDCHANNEL, false ); + } +} + + +/* +================ +rvWeaponNailgun::Spawn +================ +*/ +void rvWeaponNailgun::Spawn ( void ) { + spawnArgs.GetFloat ( "lockRange", "1000", guideRange ); + guideHoldTime = SEC2MS ( spawnArgs.GetFloat ( "lockHoldTime", "10" ) ); + guideAquireTime = SEC2MS ( spawnArgs.GetFloat ( "lockAquireTime", ".1" ) ); + + jointDrumView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_drum" ) ); + jointPinsView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_pins" ) ); + jointSteamRightView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamRight" ) ); + jointSteamLeftView = viewAnimator->GetJointHandle ( spawnArgs.GetString ( "joint_view_steamLeft" ) ); + + jointGuideEnt = INVALID_JOINT; + + drumSpeed = NAILGUN_DRUMSPEED_STOPPED; + drumSpeedIdeal = drumSpeed; + drumMultiplier = spawnArgs.GetFloat ( "drumSpeed" ); + + ExecuteState ( "ClaspClose" ); + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponNailgun::Save +================ +*/ +void rvWeaponNailgun::Save ( idSaveGame *savefile ) const { + guideEnt.Save( savefile ); + savefile->WriteInt( guideTime ); + savefile->WriteInt( guideStartTime ); + guideEffect.Save( savefile ); + savefile->WriteBool ( guideLocked ); + savefile->WriteFloat ( guideRange ); + savefile->WriteInt ( guideHoldTime ); + savefile->WriteInt ( guideAquireTime ); + + savefile->WriteJoint ( jointDrumView ); + savefile->WriteJoint ( jointPinsView ); + savefile->WriteJoint ( jointSteamRightView ); + savefile->WriteJoint ( jointSteamLeftView ); + savefile->WriteJoint ( jointGuideEnt ); + + savefile->WriteInt ( drumSpeed ); + savefile->WriteInt ( drumSpeedIdeal ); + savefile->WriteFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::Restore +================ +*/ +void rvWeaponNailgun::Restore ( idRestoreGame *savefile ) { + guideEnt.Restore( savefile ); + savefile->ReadInt( guideTime ); + savefile->ReadInt( guideStartTime ); + guideEffect.Restore( savefile ); + savefile->ReadBool ( guideLocked ); + savefile->ReadFloat ( guideRange ); + savefile->ReadInt ( guideHoldTime ); + savefile->ReadInt ( guideAquireTime ); + + savefile->ReadJoint ( jointDrumView ); + savefile->ReadJoint ( jointPinsView ); + savefile->ReadJoint ( jointSteamRightView ); + savefile->ReadJoint ( jointSteamLeftView ); + savefile->ReadJoint ( jointGuideEnt ); + + + savefile->ReadInt ( drumSpeed ); + savefile->ReadInt ( drumSpeedIdeal ); + savefile->ReadFloat ( drumMultiplier ); +} + +/* +================ +rvWeaponNailgun::PreSave +================ +*/ +void rvWeaponNailgun::PreSave ( void ) { + + //disable sounds + StopSound( SND_CHANNEL_ANY, false); + + //remove the guide gui cursor if there is one. + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect->Event_Remove(); + guideEffect = NULL; + } +} + +/* +================ +rvWeaponNailgun::PostSave +================ +*/ +void rvWeaponNailgun::PostSave ( void ) { + + //restore sounds-- but which one? + if( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } else if( drumSpeed == NAILGUN_DRUMSPEED_SLOW ) { + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + } + + //the guide gui effect will restore itself naturally +} + +/* +================ +rvWeaponNailgun::CancelGuide +================ +*/ +void rvWeaponNailgun::CancelGuide ( void ) { + if ( zoomGui && guideEnt ) { + zoomGui->HandleNamedEvent ( "lockStop" ); + } + + guideEnt = NULL; + guideLocked = false; + jointGuideEnt = INVALID_JOINT; + UpdateGuideStatus ( ); +} + +/* +================ +rvWeaponNailgun::UpdateGuideStatus +================ +*/ +void rvWeaponNailgun::UpdateGuideStatus ( float range ) { + // Update the zoom GUI variables + if ( zoomGui ) { + float lockStatus; + if ( guideEnt ) { + if ( guideLocked ) { + lockStatus = 1.0f; + } else { + lockStatus = Min((float)(gameLocal.time - guideStartTime)/(float)guideTime, 1.0f); + } + } else { + lockStatus = 0.0f; + } + + if ( owner == gameLocal.GetLocalPlayer( ) ) { + zoomGui->SetStateFloat ( "lockStatus", lockStatus ); + zoomGui->SetStateFloat ( "playerYaw", playerViewAxis.ToAngles().yaw ); + } + + if ( guideEnt ) { + idVec3 diff; + diff = guideEnt->GetPhysics()->GetOrigin ( ) - playerViewOrigin; + diff.NormalizeFast ( ); + zoomGui->SetStateFloat ( "lockYaw", idMath::AngleDelta ( diff.ToAngles ( ).yaw, playerViewAxis.ToAngles().yaw ) ); + zoomGui->SetStateString ( "lockRange", va("%4.2f", range) ); + zoomGui->SetStateString ( "lockTime", va("%02d", (int)MS2SEC(guideStartTime + guideTime - gameLocal.time) + 1) ); + } + } + + // Update the guide effect + if ( guideEnt ) { + idVec3 eyePos = static_cast(guideEnt.GetEntity())->GetEyePosition(); + if( jointGuideEnt == INVALID_JOINT ) { + eyePos += guideEnt->GetPhysics()->GetAbsBounds().GetCenter ( ); + } else { + idMat3 jointAxis; + idVec3 jointLoc; + static_cast(guideEnt.GetEntity())->GetJointWorldTransform( jointGuideEnt, gameLocal.GetTime(),jointLoc,jointAxis ); + eyePos += jointLoc; + } + eyePos *= 0.5f; + if ( guideEffect ) { + guideEffect->SetOrigin ( eyePos ); + guideEffect->SetAxis ( playerViewAxis.Transpose() ); + } else { + guideEffect = gameLocal.PlayEffect ( + gameLocal.GetEffect( spawnArgs, guideLocked ? "fx_guide" : "fx_guidestart" ), + eyePos, playerViewAxis.Transpose(), true, vec3_origin, false ); + if ( guideEffect ) { + guideEffect->GetRenderEffect()->weaponDepthHackInViewID = owner->entityNumber + 1; + guideEffect->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1; + } + } + } else { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + } +} + +/* +================ +rvWeaponNailgun::Think +================ +*/ +void rvWeaponNailgun::Think ( void ) { + idEntity* ent; + trace_t tr; + + // Let the real weapon think first + rvWeapon::Think ( ); + + // If no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + // If the zoom button isnt down then dont update the lock + if ( !wsfl.zoom || IsReloading ( ) || IsHolstered ( ) ) { + CancelGuide ( ); + return; + } + + // Dont update the target if the current target is still alive, unhidden and we have already locked + if ( guideEnt && guideEnt->health > 0 && !guideEnt->IsHidden() && guideLocked ) { + float range; + range = (guideEnt->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast(); + if ( range > guideRange || gameLocal.time > guideStartTime + guideTime ) { + CancelGuide ( ); + } else { + UpdateGuideStatus ( range ); + return; + } + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_BOUNDINGBOX, owner ); +// RAVEN END + + if ( tr.fraction >= 1.0f ) { + CancelGuide( ); + return; + } + + ent = gameLocal.entities[tr.c.entityNum]; + + //if we're using a target nailable... + if( ent->IsType ( rvTarget_Nailable::GetClassType() ) ) { + const char* jointName = ent->spawnArgs.GetString("lock_joint"); + ent = ent->targets[ 0 ].GetEntity(); + if( !ent) { + CancelGuide( ); + return; + } + + if( ent->GetAnimator() ) { + jointGuideEnt = ent->GetAnimator()->GetJointHandle( jointName ); + } + } + + + if ( !ent->IsType ( idActor::GetClassType() ) ) { + CancelGuide( ); + return; + } + if ( gameLocal.GetLocalPlayer() && static_cast(ent)->team == gameLocal.GetLocalPlayer()->team ) { + CancelGuide( ); + return; + } + + if ( guideEnt != ent ) { + guideStartTime = gameLocal.time; + guideTime = guideAquireTime; + guideEnt = ent; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockStart" ); + } + } else if ( gameLocal.time > guideStartTime + guideTime ) { + // Stop the guide effect since it was just the guide_Start effect + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + guideLocked = true; + guideTime = guideHoldTime; + guideStartTime = gameLocal.time; + if ( zoomGui ) { + zoomGui->HandleNamedEvent ( "lockAquired" ); + } + } + + UpdateGuideStatus ( (ent->GetPhysics()->GetOrigin() - playerViewOrigin).LengthFast() ); +} + +/* +================ +rvWeaponNailgun::OnLaunchProjectile +================ +*/ +void rvWeaponNailgun::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + idGuidedProjectile* guided; + guided = dynamic_cast(proj); + if ( guided ) { + guided->GuideTo ( guideEnt, jointGuideEnt ); + } +} + +/* +================ +rvWeaponNailgun::DrumSpin + +Set the drum spin speed +================ +*/ +bool rvWeaponNailgun::DrumSpin ( int speed, int blendFrames ) { + // Dont bother if the drum is already spinning at the desired speed + if ( drumSpeedIdeal == speed ) { + return false; + } + + drumSpeedIdeal = speed; + + switch ( speed ) { + case NAILGUN_DRUMSPEED_STOPPED: + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,0), gameLocal.time, 250 ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,0), gameLocal.time, 250 ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_SLOW: + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,45), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-35), gameLocal.time, NAILGUN_SPINDOWN_TIME ); + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + viewModel->StartSound ( "snd_spinslow", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + + // Spin the barrel down if we were spinning fast + if ( drumSpeed == NAILGUN_DRUMSPEED_FAST ) { + PostState ( "DrumSpinDown", blendFrames ); + return true; + } + break; + + case NAILGUN_DRUMSPEED_FAST: + // Start the barrel spinning faster + viewAnimator->SetJointAngularVelocity ( jointDrumView, idAngles(0,0,360.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + viewAnimator->SetJointAngularVelocity ( jointPinsView, idAngles(0,0,-300.0f * drumMultiplier), gameLocal.time, NAILGUN_SPINUP_TIME ); + + PostState ( "DrumSpinUp", blendFrames ); + return true; + } + + drumSpeed = drumSpeedIdeal; + + return false; +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponNailgun ) + STATE ( "Raise", rvWeaponNailgun::State_Raise ) + STATE ( "Lower", rvWeaponNailgun::State_Lower ) + STATE ( "Idle", rvWeaponNailgun::State_Idle) + STATE ( "Fire", rvWeaponNailgun::State_Fire ) + STATE ( "Reload", rvWeaponNailgun::State_Reload ) + STATE ( "DrumSpinDown", rvWeaponNailgun::State_DrumSpinDown ) + STATE ( "DrumSpinUp", rvWeaponNailgun::State_DrumSpinUp ) + + STATE ( "ClaspOpen", rvWeaponNailgun::Frame_ClaspOpen ) + STATE ( "ClaspClose", rvWeaponNailgun::Frame_ClaspClose ) +END_CLASS_STATES + +/* +================ +rvWeaponNailgun::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + // Stop any looping sounds + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Idle + +Manage the idle state of the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoInClip ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + // Do we need to spin the drum down? + if ( DrumSpin ( NAILGUN_DRUMSPEED_SLOW, parms.blendFrames ) ) { + PostState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Fire + +Fire the weapon +================ +*/ +stateResult_t rvWeaponNailgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_FIRE, + STAGE_FIREWAIT, + STAGE_DONE, + STAGE_SPINEMPTY, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !wsfl.attack ) { + SetState ( "Idle", parms.blendFrames ); + return SRESULT_DONE; + } + if ( DrumSpin ( NAILGUN_DRUMSPEED_FAST, 2 ) ) { + PostState ( "Fire", 2 ); + return SRESULT_DONE; + } + nextAttackTime = gameLocal.time; + + return SRESULT_STAGE ( STAGE_FIRE ); + + case STAGE_FIRE: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || !AmmoInClip ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_fast", 4 ); + } else { + PlayCycle ( ANIMCHANNEL_LEGS, "fire_slow", 4 ); + } + + if ( wsfl.zoom ) { + Attack ( true, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } else { + Attack ( false, 1, spread, 0.0f, 1.0f ); + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + } + + // Play the exhaust effects + viewModel->PlayEffect ( "fx_exhaust", jointSteamRightView, false ); + viewModel->PlayEffect ( "fx_exhaust", jointSteamLeftView, false ); + + viewModel->StartSound ( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); + viewModel->StartSound ( "snd_fireStereo", SND_CHANNEL_ITEM, 0, false, NULL ); + + return SRESULT_STAGE ( STAGE_FIREWAIT ); + + case STAGE_FIREWAIT: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon || !AmmoInClip ( ) ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + if ( gameLocal.time >= nextAttackTime ) { + return SRESULT_STAGE ( STAGE_FIRE ); + } + return SRESULT_WAIT; + + case STAGE_DONE: + if ( clipSize && wsfl.attack && !wsfl.lowerWeapon && !wsfl.reload ) { + PlayCycle ( ANIMCHANNEL_LEGS, "spinempty", 4 ); + return SRESULT_STAGE ( STAGE_SPINEMPTY ); + } + DrumSpin ( NAILGUN_DRUMSPEED_SLOW, 4 ); + if ( !wsfl.attack && !AmmoInClip() && AmmoAvailable() && AutoReload ( ) && !wsfl.lowerWeapon ) { + PostState ( "Reload", 4 ); + } else { + PostState ( "Idle", 4 ); + } + return SRESULT_DONE; + + case STAGE_SPINEMPTY: + if ( !wsfl.attack || wsfl.reload || wsfl.lowerWeapon ) { + return SRESULT_STAGE ( STAGE_DONE ); + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_Reload +================ +*/ +stateResult_t rvWeaponNailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_RELOAD, + STAGE_RELOADWAIT, + STAGE_RELOADLEFT, + STAGE_RELOADLEFTWAIT, + STAGE_RELOADRIGHT, + STAGE_RELOADRIGHTWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( DrumSpin ( NAILGUN_DRUMSPEED_STOPPED, parms.blendFrames ) ) { + PostState ( "reload", parms.blendFrames ); + return SRESULT_DONE; + } + + SetStatus ( WP_RELOAD ); + + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + return SRESULT_STAGE( STAGE_RELOADLEFT ); + } + return SRESULT_STAGE( STAGE_RELOAD ); + + case STAGE_RELOAD: + PlayAnim( ANIMCHANNEL_LEGS, "reload", parms.blendFrames ); + return SRESULT_STAGE( STAGE_RELOADWAIT ); + + case STAGE_RELOADWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize ( ) ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLEFT: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1hold", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADLEFTWAIT ); + + case STAGE_RELOADLEFTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + AddToClip ( ClipSize() / 2 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHT ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADRIGHT: + if ( wsfl.attack || AmmoInClip() >= ClipSize() || !AmmoAvailable() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip2", 0 ); + return SRESULT_STAGE ( STAGE_RELOADRIGHTWAIT ); + + case STAGE_RELOADRIGHTWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + AddToClip( ClipSize() / 2 ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + PlayAnim ( ANIMCHANNEL_LEGS, "reload_clip1finish", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinUp + +Spin the drum from a slow speed to a fast speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinUp ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spinup", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL); + PlayAnim ( ANIMCHANNEL_LEGS, "spinup", 4 ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + viewModel->StartSound ( "snd_spinfast", NAILGUN_SPIN_SNDCHANNEL, 0, false, NULL ); + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( !wsfl.attack ) { + int oldSpeed = drumSpeed; + drumSpeed = drumSpeedIdeal; + DrumSpin ( oldSpeed, 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::State_DrumSpinDown + +Spin the drum down from a faster speed +================ +*/ +stateResult_t rvWeaponNailgun::State_DrumSpinDown ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + viewModel->StartSound ( "snd_spindown", SND_CHANNEL_ANY, 0, false, 0 ); + + // Spin down animation + PlayAnim( ANIMCHANNEL_LEGS, "spindown", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + drumSpeed = drumSpeedIdeal; + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) ) { + viewModel->StopSound ( NAILGUN_SPIN_SNDCHANNEL, false ); + SetState ( "Fire", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoAvailable() > AmmoInClip() && AmmoInClip() < ClipSize()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Open the clasp that holds in the clips +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspOpen ( const stateParms_t& parms ) { + PlayAnim ( ANIMCHANNEL_TORSO, "clasp_open", 0 ); + return SRESULT_OK; +} + +/* +================ +rvWeaponNailgun::Frame_ClaspOpen + +Close the clasp that holds in the clips and make sure to use the +correct positioning depending on whether you have one or two clips +in the gun. +================ +*/ +stateResult_t rvWeaponNailgun::Frame_ClaspClose ( const stateParms_t& parms ) { + if ( mods & NAILGUN_MOD_ROF_AMMO ) { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_2clip", 0 ); + } else { + PlayAnim( ANIMCHANNEL_TORSO, "clasp_1clip", 0 ); + } + return SRESULT_OK; +} diff --git a/src/mpgame/weapon/WeaponNapalmGun.cpp b/src/mpgame/weapon/WeaponNapalmGun.cpp new file mode 100644 index 0000000..a9f9899 --- /dev/null +++ b/src/mpgame/weapon/WeaponNapalmGun.cpp @@ -0,0 +1,467 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + + +const int NAPALM_GUN_NUM_CYLINDERS = 5; + +class WeaponNapalmGun : public rvWeapon { +public: + + CLASS_PROTOTYPE( WeaponNapalmGun ); + + WeaponNapalmGun ( void ); + ~WeaponNapalmGun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + virtual void MuzzleRise ( idVec3 &origin, idMat3 &axis ); + + virtual void SpectatorCycle ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + +protected: + + void UpdateCylinders(void); + + typedef enum {CYLINDER_RESET_POSITION,CYLINDER_MOVE_POSITION, CYLINDER_UPDATE_POSITION } CylinderState; + CylinderState cylinderState; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + stateResult_t State_EmptyReload ( const stateParms_t& parms ); + + stateResult_t Frame_MoveCylinder ( const stateParms_t& parms ); + stateResult_t Frame_ResetCylinder ( const stateParms_t& parms ); + + + float cylinderMaxOffsets[NAPALM_GUN_NUM_CYLINDERS]; + idInterpolate cylinderOffsets[NAPALM_GUN_NUM_CYLINDERS]; + jointHandle_t cylinderJoints[NAPALM_GUN_NUM_CYLINDERS]; + + + int cylinderMoveTime; + int previousAmmo; + bool zoomed; + + CLASS_STATES_PROTOTYPE ( WeaponNapalmGun ); +}; + +CLASS_DECLARATION( rvWeapon, WeaponNapalmGun ) +END_CLASS + +/* +================ +WeaponNapalmGun::WeaponNapalmGun +================ +*/ +WeaponNapalmGun::WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::~WeaponNapalmGun +================ +*/ +WeaponNapalmGun::~WeaponNapalmGun( void ) { } + +/* +================ +WeaponNapalmGun::Spawn +================ +*/ +void WeaponNapalmGun::Spawn( void ) { + assert(viewModel); + idAnimator* animator = viewModel->GetAnimator(); + assert(animator); + + SetState( "Raise", 0 ); + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + idStr argName = "cylinder_offset"; + argName += i; + cylinderMaxOffsets[i] = spawnArgs.GetFloat(argName, "0.0"); + + argName = "cylinder_joint"; + argName += i; + cylinderJoints[i] = animator->GetJointHandle( spawnArgs.GetString( argName, "" ) ); + + cylinderOffsets[i].Init( gameLocal.time, 0.0f, 0, 0); + } + + previousAmmo = AmmoInClip(); + cylinderMoveTime = spawnArgs.GetFloat( "cylinderMoveTime", "500" ); + cylinderState = CYLINDER_RESET_POSITION; + zoomed = false; +} + +/* +================ +WeaponNapalmGun::Think +================ +*/ +void WeaponNapalmGun::Think( void ) { + + rvWeapon::Think(); + + //Check to see if the ammo level has changed. + //This is to account for ammo pickups. + if ( previousAmmo != AmmoInClip() ) { + // don't do this in MP, the weap script doesn't sync the canisters anyway + if ( !gameLocal.isMultiplayer ) { + //change the cylinder state to reflect the new change in ammo. + cylinderState = CYLINDER_MOVE_POSITION; + } + previousAmmo = AmmoInClip(); + } + + UpdateCylinders(); +} + +/* +=============== +WeaponNapalmGun::MuzzleRise +=============== +*/ +void WeaponNapalmGun::MuzzleRise( idVec3 &origin, idMat3 &axis ) { + if ( wsfl.zoom ) + return; + + rvWeapon::MuzzleRise( origin, axis ); +} + +/* +=============== +WeaponNapalmGun::UpdateCylinders +=============== +*/ +void WeaponNapalmGun::UpdateCylinders(void) +{ + idAnimator* animator; + animator = viewModel->GetAnimator(); + assert( animator ); + + float ammoInClip = AmmoInClip(); + float clipSize = ClipSize(); + if ( clipSize <= idMath::FLOAT_EPSILON ) { + clipSize = maxAmmo; + } + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; ++i) + { + // move the local position of the joint along the x-axis. + float currentOffset = cylinderOffsets[i].GetCurrentValue(gameLocal.time); + + switch(cylinderState) + { + case CYLINDER_MOVE_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, cylinderMoveTime, currentOffset, endValue ); + } + break; + + case CYLINDER_RESET_POSITION: + { + float cylinderMaxOffset = cylinderMaxOffsets[i]; + float endValue = cylinderMaxOffset * (1.0f - (ammoInClip / clipSize)); + cylinderOffsets[i].Init( gameLocal.time, 0, endValue, endValue ); + } + break; + } + + + animator->SetJointPos( cylinderJoints[i], JOINTMOD_LOCAL, idVec3( currentOffset, 0.0f, 0.0f ) ); + } + + cylinderState = CYLINDER_UPDATE_POSITION; +} + + +/* +===================== +WeaponNapalmGun::Save +===================== +*/ +void WeaponNapalmGun::Save( idSaveGame *saveFile ) const +{ + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->WriteFloat(cylinderMaxOffsets[i]); + saveFile->WriteInterpolate(cylinderOffsets[i]); + saveFile->WriteJoint(cylinderJoints[i]); + } + + saveFile->WriteInt(cylinderMoveTime); + saveFile->WriteInt(previousAmmo); +} + +/* +===================== +WeaponNapalmGun::Restore +===================== +*/ +void WeaponNapalmGun::Restore( idRestoreGame *saveFile ) { + + for(int i = 0; i < NAPALM_GUN_NUM_CYLINDERS; i++) + { + saveFile->ReadFloat(cylinderMaxOffsets[i]); + saveFile->ReadInterpolate(cylinderOffsets[i]); + saveFile->ReadJoint(cylinderJoints[i]); + } + + saveFile->ReadInt(cylinderMoveTime); + saveFile->ReadInt(previousAmmo); +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( WeaponNapalmGun ) + STATE ( "Idle", WeaponNapalmGun::State_Idle) + STATE ( "Fire", WeaponNapalmGun::State_Fire ) + STATE ( "Reload", WeaponNapalmGun::State_Reload ) + STATE ( "EmptyReload", WeaponNapalmGun::State_EmptyReload ) + STATE ( "MoveCylinder", WeaponNapalmGun::Frame_MoveCylinder ) + STATE ( "ResetCylinder", WeaponNapalmGun::Frame_ResetCylinder) +END_CLASS_STATES + + + +stateResult_t WeaponNapalmGun::State_Reload( const stateParms_t& parms) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponGrenadeLauncher::State_Reload +================ +*/ +stateResult_t WeaponNapalmGun::State_EmptyReload( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_empty", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip ( ClipSize() ); + + cylinderState = CYLINDER_MOVE_POSITION; + + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Idle +================ +*/ +stateResult_t WeaponNapalmGun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + if ( wsfl.zoom ) + PlayCycle( ANIMCHANNEL_LEGS, "altidle", parms.blendFrames ); + else + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.zoom && !zoomed ) { + SetState ( "Idle", 4 ); + zoomed = true; + return SRESULT_DONE; + } + + if ( !wsfl.zoom && zoomed ) { + SetState ( "Idle", 4 ); + zoomed = false; + return SRESULT_DONE; + } + + if(!clipSize) + { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + } + else + { + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "EmptyReload", 4 ); + return SRESULT_DONE; + } + + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +WeaponNapalmGun::State_Fire +================ +*/ +stateResult_t WeaponNapalmGun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.zoom ) { + nextAttackTime = gameLocal.time + (altFireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( true, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + //fireHeld = true; + } else { + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + + int animNum = viewModel->GetAnimator()->GetAnim ( "fire" ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim ( ANIMCHANNEL_ALL, "fire", parms.blendFrames ); + } + + previousAmmo = AmmoInClip(); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + if ( !wsfl.zoom ) + SetState ( "Reload", 4 ); + else + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +stateResult_t WeaponNapalmGun::Frame_MoveCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_MOVE_POSITION; + return SRESULT_OK; +} + +stateResult_t WeaponNapalmGun::Frame_ResetCylinder( const stateParms_t& parms) { + cylinderState = CYLINDER_RESET_POSITION; + return SRESULT_OK; +} + +void WeaponNapalmGun::SpectatorCycle( void ) { + cylinderState = CYLINDER_RESET_POSITION; +} diff --git a/src/mpgame/weapon/WeaponRailgun.cpp b/src/mpgame/weapon/WeaponRailgun.cpp new file mode 100644 index 0000000..969af50 --- /dev/null +++ b/src/mpgame/weapon/WeaponRailgun.cpp @@ -0,0 +1,294 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const idEventDef EV_Railgun_RestoreHum( "", "" ); + +class rvWeaponRailgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRailgun ); + + rvWeaponRailgun ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + void ClientUnstale ( void ); + +protected: + jointHandle_t jointBatteryView; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + void Event_RestoreHum ( void ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRailgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRailgun ) + EVENT( EV_Railgun_RestoreHum, rvWeaponRailgun::Event_RestoreHum ) +END_CLASS + +/* +================ +rvWeaponRailgun::rvWeaponRailgun +================ +*/ +rvWeaponRailgun::rvWeaponRailgun ( void ) { +} + +/* +================ +rvWeaponRailgun::Spawn +================ +*/ +void rvWeaponRailgun::Spawn ( void ) { + SetState ( "Raise", 0 ); +} + +/* +================ +rvWeaponRailgun::Save +================ +*/ +void rvWeaponRailgun::Save ( idSaveGame *savefile ) const { + savefile->WriteJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::Restore +================ +*/ +void rvWeaponRailgun::Restore ( idRestoreGame *savefile ) { + savefile->ReadJoint( jointBatteryView ); +} + +/* +================ +rvWeaponRailgun::PreSave +================ +*/ +void rvWeaponRailgun::PreSave ( void ) { + + //this should shoosh the humming but not the shooting sound. + StopSound( SND_CHANNEL_BODY2, 0); +} + +/* +================ +rvWeaponRailgun::PostSave +================ +*/ +void rvWeaponRailgun::PostSave ( void ) { + + //restore the humming + PostEventMS( &EV_Railgun_RestoreHum, 10); +} + +/* +================ +rvWeaponRailgun::Think +================ +*/ +void rvWeaponRailgun::Think ( void ) { + + // Let the real weapon think first + rvWeapon::Think ( ); + + if ( zoomGui && wsfl.zoom && !gameLocal.isMultiplayer ) { + int ammo = AmmoInClip(); + if ( ammo >= 0 ) { + zoomGui->SetStateInt( "player_ammo", ammo ); + } + } +} + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRailgun ) + STATE ( "Idle", rvWeaponRailgun::State_Idle) + STATE ( "Fire", rvWeaponRailgun::State_Fire ) + STATE ( "Reload", rvWeaponRailgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponRailgun::State_Idle +================ +*/ +stateResult_t rvWeaponRailgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); + SetStatus ( WP_READY ); + } + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + // Auto reload? + if ( AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState ( "reload", 2 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState ( "Reload", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRailgun::State_Fire +================ +*/ +stateResult_t rvWeaponRailgun::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( ( gameLocal.isMultiplayer && gameLocal.time >= nextAttackTime ) || + ( !gameLocal.isMultiplayer && ( AnimDone ( ANIMCHANNEL_ALL, 2 ) ) ) ) { + SetState ( "Idle", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +stateResult_t rvWeaponRailgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + PlayAnim ( ANIMCHANNEL_ALL, "reload", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + StopSound( SND_CHANNEL_BODY2, false ); + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +=============================================================================== + + Event + +=============================================================================== +*/ + +/* +================ +rvWeaponRailgun::State_Reload +================ +*/ +void rvWeaponRailgun::Event_RestoreHum ( void ) { + StopSound( SND_CHANNEL_BODY2, false ); + StartSound( "snd_idle_hum", SND_CHANNEL_BODY2, 0, false, NULL ); +} + +/* +================ +rvWeaponRailgun::ClientUnStale +================ +*/ +void rvWeaponRailgun::ClientUnstale( void ) { + Event_RestoreHum(); +} + diff --git a/src/mpgame/weapon/WeaponRocketLauncher.cpp b/src/mpgame/weapon/WeaponRocketLauncher.cpp new file mode 100644 index 0000000..a5d7c59 --- /dev/null +++ b/src/mpgame/weapon/WeaponRocketLauncher.cpp @@ -0,0 +1,607 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" +#include "../client/ClientEffect.h" + +#ifndef __GAME_PROJECTILE_H__ +#include "../Projectile.h" +#endif + +class rvWeaponRocketLauncher : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponRocketLauncher ); + + rvWeaponRocketLauncher ( void ); + ~rvWeaponRocketLauncher ( void ); + + virtual void Spawn ( void ); + virtual void Think ( void ); + + void Save( idSaveGame *saveFile ) const; + void Restore( idRestoreGame *saveFile ); + void PreSave ( void ); + void PostSave ( void ); + + +#ifdef _XENON + virtual bool AllowAutoAim ( void ) const { return false; } +#endif + +protected: + + virtual void OnLaunchProjectile ( idProjectile* proj ); + + void SetRocketState ( const char* state, int blendFrames ); + + rvClientEntityPtr guideEffect; + idList< idEntityPtr > guideEnts; + float guideSpeedSlow; + float guideSpeedFast; + float guideRange; + float guideAccelTime; + + rvStateThread rocketThread; + + float reloadRate; + + bool idleEmpty; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Raise ( const stateParms_t& parms ); + stateResult_t State_Lower ( const stateParms_t& parms ); + + stateResult_t State_Rocket_Idle ( const stateParms_t& parms ); + stateResult_t State_Rocket_Reload ( const stateParms_t& parms ); + + stateResult_t Frame_AddToClip ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE ( rvWeaponRocketLauncher ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponRocketLauncher ) +END_CLASS + +/* +================ +rvWeaponRocketLauncher::rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::rvWeaponRocketLauncher ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher +================ +*/ +rvWeaponRocketLauncher::~rvWeaponRocketLauncher ( void ) { + if ( guideEffect ) { + guideEffect->Stop(); + } +} + +/* +================ +rvWeaponRocketLauncher::Spawn +================ +*/ +void rvWeaponRocketLauncher::Spawn ( void ) { + float f; + + idleEmpty = false; + + spawnArgs.GetFloat ( "lockRange", "0", guideRange ); + + spawnArgs.GetFloat ( "lockSlowdown", ".25", f ); + attackDict.GetFloat ( "speed", "0", guideSpeedFast ); + guideSpeedSlow = guideSpeedFast * f; + + reloadRate = SEC2MS ( spawnArgs.GetFloat ( "reloadRate", ".8" ) ); + + guideAccelTime = SEC2MS ( spawnArgs.GetFloat ( "lockAccelTime", ".25" ) ); + + // Start rocket thread + rocketThread.SetName ( viewModel->GetName ( ) ); + rocketThread.SetOwner ( this ); + + // Adjust reload animations to match the fire rate + idAnim* anim; + int animNum; + float rate; + animNum = viewModel->GetAnimator()->GetAnim ( "reload" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + animNum = viewModel->GetAnimator()->GetAnim ( "reload_empty" ); + if ( animNum ) { + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" )); + anim->SetPlaybackRate ( rate ); + } + + SetState ( "Raise", 0 ); + SetRocketState ( "Rocket_Idle", 0 ); +} + +/* +================ +rvWeaponRocketLauncher::Think +================ +*/ +void rvWeaponRocketLauncher::Think ( void ) { + trace_t tr; + int i; + + rocketThread.Execute ( ); + + // Let the real weapon think first + rvWeapon::Think ( ); + + // IF no guide range is set then we dont have the mod yet + if ( !guideRange ) { + return; + } + + if ( !wsfl.zoom ) { + if ( guideEffect ) { + guideEffect->Stop(); + guideEffect = NULL; + } + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden ( ) ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket is still guiding then stop the guide and slow it down + if ( proj->GetGuideType ( ) != idGuidedProjectile::GUIDE_NONE ) { + proj->CancelGuide ( ); + proj->SetSpeed ( guideSpeedFast, (1.0f - (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow)) * guideAccelTime ); + } + } + + return; + } + + // Cast a ray out to the lock range +// RAVEN BEGIN +// ddynerman: multiple clip worlds + gameLocal.TracePoint( owner, tr, + playerViewOrigin, + playerViewOrigin + playerViewAxis[0] * guideRange, + MASK_SHOT_RENDERMODEL, owner ); +// RAVEN END + + for ( i = guideEnts.Num() - 1; i >= 0; i -- ) { + idGuidedProjectile* proj = static_cast(guideEnts[i].GetEntity()); + if ( !proj || proj->IsHidden() ) { + guideEnts.RemoveIndex ( i ); + continue; + } + + // If the rocket isnt guiding yet then adjust its speed back to normal + if ( proj->GetGuideType ( ) == idGuidedProjectile::GUIDE_NONE ) { + proj->SetSpeed ( guideSpeedSlow, (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow) * guideAccelTime ); + } + proj->GuideTo ( tr.endpos ); + } + + if ( !guideEffect ) { + guideEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_guide" ), tr.endpos, tr.c.normal.ToMat3(), true, vec3_origin, true ); + } else { + guideEffect->SetOrigin ( tr.endpos ); + guideEffect->SetAxis ( tr.c.normal.ToMat3() ); + } +} + +/* +================ +rvWeaponRocketLauncher::OnLaunchProjectile +================ +*/ +void rvWeaponRocketLauncher::OnLaunchProjectile ( idProjectile* proj ) { + rvWeapon::OnLaunchProjectile(proj); + + // Double check that its actually a guided projectile + if ( !proj || !proj->IsType ( idGuidedProjectile::GetClassType() ) ) { + return; + } + + // Launch the projectile + idEntityPtr ptr; + ptr = proj; + guideEnts.Append ( ptr ); +} + +/* +================ +rvWeaponRocketLauncher::SetRocketState +================ +*/ +void rvWeaponRocketLauncher::SetRocketState ( const char* state, int blendFrames ) { + rocketThread.SetState ( state, blendFrames ); +} + +/* +===================== +rvWeaponRocketLauncher::Save +===================== +*/ +void rvWeaponRocketLauncher::Save( idSaveGame *saveFile ) const { + saveFile->WriteObject( guideEffect ); + + idEntity* ent = NULL; + saveFile->WriteInt( guideEnts.Num() ); + for( int ix = 0; ix < guideEnts.Num(); ++ix ) { + ent = guideEnts[ ix ].GetEntity(); + if( ent ) { + saveFile->WriteObject( ent ); + } + } + + saveFile->WriteFloat( guideSpeedSlow ); + saveFile->WriteFloat( guideSpeedFast ); + saveFile->WriteFloat( guideRange ); + saveFile->WriteFloat( guideAccelTime ); + + saveFile->WriteFloat ( reloadRate ); + + rocketThread.Save( saveFile ); +} + +/* +===================== +rvWeaponRocketLauncher::Restore +===================== +*/ +void rvWeaponRocketLauncher::Restore( idRestoreGame *saveFile ) { + int numEnts = 0; + idEntity* ent = NULL; + rvClientEffect* clientEffect = NULL; + + saveFile->ReadObject( reinterpret_cast(clientEffect) ); + guideEffect = clientEffect; + + saveFile->ReadInt( numEnts ); + guideEnts.Clear(); + guideEnts.SetNum( numEnts ); + for( int ix = 0; ix < numEnts; ++ix ) { + saveFile->ReadObject( reinterpret_cast(ent) ); + guideEnts[ ix ] = ent; + } + + saveFile->ReadFloat( guideSpeedSlow ); + saveFile->ReadFloat( guideSpeedFast ); + saveFile->ReadFloat( guideRange ); + saveFile->ReadFloat( guideAccelTime ); + + saveFile->ReadFloat ( reloadRate ); + + rocketThread.Restore( saveFile, this ); +} + +/* +================ +rvWeaponRocketLauncher::PreSave +================ +*/ +void rvWeaponRocketLauncher::PreSave ( void ) { +} + +/* +================ +rvWeaponRocketLauncher::PostSave +================ +*/ +void rvWeaponRocketLauncher::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION ( rvWeaponRocketLauncher ) + STATE ( "Idle", rvWeaponRocketLauncher::State_Idle) + STATE ( "Fire", rvWeaponRocketLauncher::State_Fire ) + STATE ( "Raise", rvWeaponRocketLauncher::State_Raise ) + STATE ( "Lower", rvWeaponRocketLauncher::State_Lower ) + + STATE ( "Rocket_Idle", rvWeaponRocketLauncher::State_Rocket_Idle ) + STATE ( "Rocket_Reload", rvWeaponRocketLauncher::State_Rocket_Reload ) + + STATE ( "AddToClip", rvWeaponRocketLauncher::Frame_AddToClip ) +END_CLASS_STATES + + +/* +================ +rvWeaponRocketLauncher::State_Raise + +Raise the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Raise ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + // Start the weapon raising + case STAGE_INIT: + SetStatus ( WP_RISING ); + PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Lower + +Lower the weapon +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Lower ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITRAISE + }; + switch ( parms.stage ) { + case STAGE_INIT: + SetStatus ( WP_LOWERING ); + PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames ); + return SRESULT_STAGE(STAGE_WAIT); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_LEGS, 0 ) ) { + SetStatus ( WP_HOLSTERED ); + return SRESULT_STAGE(STAGE_WAITRAISE); + } + return SRESULT_WAIT; + + case STAGE_WAITRAISE: + if ( wsfl.raiseWeapon ) { + SetState ( "Raise", 0 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable ( ) ) { + SetStatus ( WP_OUTOFAMMO ); + } else { + SetStatus ( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && wsfl.attack && ( gameLocal.isClient || AmmoInClip ( ) ) ) { + SetState ( "Fire", 2 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Fire +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Fire ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack ( false, 1, spread, 0, 1.0f ); + PlayAnim ( ANIMCHANNEL_LEGS, "fire", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.attack && gameLocal.time >= nextAttackTime && ( gameLocal.isClient || AmmoInClip ( ) ) && !wsfl.lowerWeapon ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( gameLocal.time > nextAttackTime && AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Idle +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Idle ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_WAITEMPTY, + }; + + switch ( parms.stage ) { + case STAGE_INIT: + if ( AmmoAvailable ( ) <= AmmoInClip() ) { + PlayAnim( ANIMCHANNEL_TORSO, "idle_empty", parms.blendFrames ); + idleEmpty = true; + } else { + PlayAnim( ANIMCHANNEL_TORSO, "idle", parms.blendFrames ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AmmoAvailable ( ) > AmmoInClip() ) { + if ( idleEmpty ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } else if ( ClipSize ( ) > 1 ) { + if ( gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) ) { + if ( !AmmoInClip() || !wsfl.attack ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } else { + if ( AmmoInClip ( ) == 0 ) { + SetRocketState ( "Rocket_Reload", 0 ); + return SRESULT_DONE; + } + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::State_Rocket_Reload +================ +*/ +stateResult_t rvWeaponRocketLauncher::State_Rocket_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + + switch ( parms.stage ) { + case STAGE_INIT: { + const char* animName; + int animNum; + + if ( idleEmpty ) { + animName = "ammo_pickup"; + idleEmpty = false; + } else if ( AmmoAvailable ( ) == AmmoInClip( ) + 1 ) { + animName = "reload_empty"; + } else { + animName = "reload"; + } + + animNum = viewModel->GetAnimator()->GetAnim ( animName ); + if ( animNum ) { + idAnim* anim; + anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum ); + anim->SetPlaybackRate ( (float)anim->Length() / (reloadRate * owner->PowerUpModifier ( PMOD_FIRERATE )) ); + } + + PlayAnim( ANIMCHANNEL_TORSO, animName, parms.blendFrames ); + + return SRESULT_STAGE ( STAGE_WAIT ); + } + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { + if ( !wsfl.attack && gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) && AmmoAvailable() > AmmoInClip() ) { + SetRocketState ( "Rocket_Reload", 0 ); + } else { + SetRocketState ( "Rocket_Idle", 0 ); + } + return SRESULT_DONE; + } + /* + if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) { + if ( AmmoInClip ( ) == 0 ) + { + AddToClip ( ClipSize() ); + } + SetRocketState ( "Rocket_Idle", 0 ); + return SRESULT_DONE; + } + */ + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponRocketLauncher::Frame_AddToClip +================ +*/ +stateResult_t rvWeaponRocketLauncher::Frame_AddToClip ( const stateParms_t& parms ) { + AddToClip ( 1 ); + return SRESULT_OK; +} + diff --git a/src/mpgame/weapon/WeaponShotgun.cpp b/src/mpgame/weapon/WeaponShotgun.cpp new file mode 100644 index 0000000..7c12965 --- /dev/null +++ b/src/mpgame/weapon/WeaponShotgun.cpp @@ -0,0 +1,313 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../Game_local.h" +#include "../Weapon.h" + +const int SHOTGUN_MOD_AMMO = BIT(0); + +class rvWeaponShotgun : public rvWeapon { +public: + + CLASS_PROTOTYPE( rvWeaponShotgun ); + + rvWeaponShotgun ( void ); + + virtual void Spawn ( void ); + void Save ( idSaveGame *savefile ) const; + void Restore ( idRestoreGame *savefile ); + void PreSave ( void ); + void PostSave ( void ); + +protected: + int hitscans; + +private: + + stateResult_t State_Idle ( const stateParms_t& parms ); + stateResult_t State_Fire ( const stateParms_t& parms ); + stateResult_t State_Reload ( const stateParms_t& parms ); + + CLASS_STATES_PROTOTYPE( rvWeaponShotgun ); +}; + +CLASS_DECLARATION( rvWeapon, rvWeaponShotgun ) +END_CLASS + +/* +================ +rvWeaponShotgun::rvWeaponShotgun +================ +*/ +rvWeaponShotgun::rvWeaponShotgun( void ) { +} + +/* +================ +rvWeaponShotgun::Spawn +================ +*/ +void rvWeaponShotgun::Spawn( void ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); + + SetState( "Raise", 0 ); +} + +/* +================ +rvWeaponShotgun::Save +================ +*/ +void rvWeaponShotgun::Save( idSaveGame *savefile ) const { +} + +/* +================ +rvWeaponShotgun::Restore +================ +*/ +void rvWeaponShotgun::Restore( idRestoreGame *savefile ) { + hitscans = spawnArgs.GetFloat( "hitscans" ); +} + +/* +================ +rvWeaponShotgun::PreSave +================ +*/ +void rvWeaponShotgun::PreSave ( void ) { +} + +/* +================ +rvWeaponShotgun::PostSave +================ +*/ +void rvWeaponShotgun::PostSave ( void ) { +} + + +/* +=============================================================================== + + States + +=============================================================================== +*/ + +CLASS_STATES_DECLARATION( rvWeaponShotgun ) + STATE( "Idle", rvWeaponShotgun::State_Idle) + STATE( "Fire", rvWeaponShotgun::State_Fire ) + STATE( "Reload", rvWeaponShotgun::State_Reload ) +END_CLASS_STATES + +/* +================ +rvWeaponShotgun::State_Idle +================ +*/ +stateResult_t rvWeaponShotgun::State_Idle( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( !AmmoAvailable( ) ) { + SetStatus( WP_OUTOFAMMO ); + } else { + SetStatus( WP_READY ); + } + + PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( wsfl.lowerWeapon ) { + SetState( "Lower", 4 ); + return SRESULT_DONE; + } + if ( !clipSize ) { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoAvailable ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + } else { + if ( gameLocal.time > nextAttackTime && wsfl.attack && AmmoInClip ( ) ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AutoReload() && !AmmoInClip ( ) && AmmoAvailable () ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + if ( wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip()) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Fire +================ +*/ +stateResult_t rvWeaponShotgun::State_Fire( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + }; + switch ( parms.stage ) { + case STAGE_INIT: + nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); + Attack( false, hitscans, spread, 0, 1.0f ); + PlayAnim( ANIMCHANNEL_ALL, "fire", 0 ); + return SRESULT_STAGE( STAGE_WAIT ); + + case STAGE_WAIT: + if ( (!gameLocal.isMultiplayer && (wsfl.lowerWeapon || AnimDone( ANIMCHANNEL_ALL, 0 )) ) || AnimDone( ANIMCHANNEL_ALL, 0 ) ) { + SetState( "Idle", 0 ); + return SRESULT_DONE; + } + if ( wsfl.attack && gameLocal.time >= nextAttackTime && AmmoInClip() ) { + SetState( "Fire", 0 ); + return SRESULT_DONE; + } + if ( clipSize ) { + if ( (wsfl.netReload || (wsfl.reload && AmmoInClip() < ClipSize() && AmmoAvailable()>AmmoInClip())) ) { + SetState( "Reload", 4 ); + return SRESULT_DONE; + } + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + +/* +================ +rvWeaponShotgun::State_Reload +================ +*/ +stateResult_t rvWeaponShotgun::State_Reload ( const stateParms_t& parms ) { + enum { + STAGE_INIT, + STAGE_WAIT, + STAGE_RELOADSTARTWAIT, + STAGE_RELOADLOOP, + STAGE_RELOADLOOPWAIT, + STAGE_RELOADDONE, + STAGE_RELOADDONEWAIT + }; + switch ( parms.stage ) { + case STAGE_INIT: + if ( wsfl.netReload ) { + wsfl.netReload = false; + } else { + NetReload ( ); + } + + SetStatus ( WP_RELOAD ); + + if ( mods & SHOTGUN_MOD_AMMO ) { + PlayAnim ( ANIMCHANNEL_ALL, "reload_clip", parms.blendFrames ); + } else { + PlayAnim ( ANIMCHANNEL_ALL, "reload_start", parms.blendFrames ); + return SRESULT_STAGE ( STAGE_RELOADSTARTWAIT ); + } + return SRESULT_STAGE ( STAGE_WAIT ); + + case STAGE_WAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + AddToClip ( ClipSize() ); + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADSTARTWAIT: + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + + case STAGE_RELOADLOOP: + if ( (wsfl.attack && AmmoInClip() ) || AmmoAvailable ( ) <= AmmoInClip ( ) || AmmoInClip() == ClipSize() ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + PlayAnim ( ANIMCHANNEL_ALL, "reload_loop", 0 ); + return SRESULT_STAGE ( STAGE_RELOADLOOPWAIT ); + + case STAGE_RELOADLOOPWAIT: + if ( (wsfl.attack && AmmoInClip() ) || wsfl.netEndReload ) { + return SRESULT_STAGE ( STAGE_RELOADDONE ); + } + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 0 ) ) { + AddToClip( 1 ); + return SRESULT_STAGE ( STAGE_RELOADLOOP ); + } + return SRESULT_WAIT; + + case STAGE_RELOADDONE: + NetEndReload ( ); + PlayAnim ( ANIMCHANNEL_ALL, "reload_end", 0 ); + return SRESULT_STAGE ( STAGE_RELOADDONEWAIT ); + + case STAGE_RELOADDONEWAIT: + if ( wsfl.lowerWeapon ) { + SetState ( "Lower", 4 ); + return SRESULT_DONE; + } + if ( wsfl.attack && AmmoInClip ( ) && gameLocal.time > nextAttackTime ) { + SetState ( "Fire", 0 ); + return SRESULT_DONE; + } + if ( AnimDone ( ANIMCHANNEL_ALL, 4 ) ) { + SetState ( "Idle", 4 ); + return SRESULT_DONE; + } + return SRESULT_WAIT; + } + return SRESULT_ERROR; +} + diff --git a/src/openal/docs/ChangeLog b/src/openal/docs/ChangeLog new file mode 100644 index 0000000..b4eb754 --- /dev/null +++ b/src/openal/docs/ChangeLog @@ -0,0 +1,378 @@ +2001-02-06 Bernd Kreimeier + + * chp-rendering.sgml: added annotation about CONE calculation. + * chp-state.sgml: added more annotation on log vs. linear GAIN. + +2001-01-09 Bernd Kreimeier + + * chp-state.sgml: ungrep of scratched DISTANCE_SCALE. + +2001-01-08 Bernd Kreimeier + + * chp-rendering.sgml: amended MAX_DISTANCE. + * chp-state.sgml: culling clarification. + Note: MAX_DISTANCE (and consequently the current + Inverse Clamped Distance Model) do not fit the API. + The clamping of the distance (instead of clamping + to the effective gain at the max. distance) will + potentially break other distance-dependend calculations + further down in the processing chain. + TODO: revisit Inverse Clamped Distance Model. _DS3D extension? + +2001-01-05 Bernd Kreimeier + + * chp-rendering.sgml: SOURCE_RELATIVE specification. + + * chp-state.sgml: clarified distance attentuation calculation + a bit, and added back in the linear version as an annotation. + Note: watch this, it's tied into the GAIN_LINEAR issue. + TODO: added dist calculation requirements. + +2001-01-04 Bernd Kreimeier + + * chp-operation.sgml: INVALID_ENUM for getters and enable, + not INVALID_OPERATION. Later: Delete{Object}s accepts zero. + +2000-12-04 Bernd Kreimeier + + * Makefile: added HTML ZIP targets ("update" for website). + +2000-11-10 Bernd Kreimeier + + * WWW: reworked the OpenAL web site as in CVS. This + includes emptying HTML files with obsolete whitepaper + and specification content not even linked from the + official pages, and reworking the official pages to + remove redundancy, and generally point to the snapshots. + Also created snapshots page (along with cronjob and + automated update installed by Rafael). + + * Makefile: added ZIP file generation to update target. + + * index.html: edited for snapshots/ page on www.openal.org. + As it is next to impossible to reproduce the Official OpenAL + Look w/o lots of tap dancing, I use the style from the + DocBook HTML rendering instead. + +2000-11-06 Bernd Kreimeier + + * Makefile (full): full rendering target added. + * ent-marks-full.sgml: created. + +2000-10-27 Bernd Kreimeier + + * Makefile: rendering targets for cronjob driven update. + +2000-10-26 Bernd Kreimeier + + * alc-context.sgml: annotation on release of hardware resources. + Removed version query enry point, added size-safe GetIntegerv + and GetString, removed GetErrorString, added integer and string + query tokens, added device enumeration query. Added more annotation. + + * chp-rendering.sgml: remark on release of hardware resources. + + * alc-context.sgml: had INVALID_DEVICE also marked by RFC. + Removed the "device parameter should be NULL" leftovers. + Added Query functions. Removed redundant device parameters + to avoid GLX-style redundancy and resulting BadMatch errors. + + +2000-10-25 Bernd Kreimeier + + * oalspecs.sgml: include extension list entity (below). + * ent-extensions.sgml: created. + Note: during several unsuccessful attempts to convince + DocBook and the DocBook toolchain to handle CDATA external + entities properly for and , + I also had to recover accidentally deleted files. + +2000-10-24 Bernd Kreimeier + + * alc-context.sgml: shared object section. Mike Kelly request + as RFC. ALC Error section placeholder. ALC attribute section + as placeholder. Process/Suspend added plus annotation, Update + removed. More typo fixes. Added extension query. Removed old + RFC's. No UNDERFLOW error. No CHANNELS attribute, MIXAHEAD + redundant. Removed alcUpdate( PROCESS | SUSPEND ) RFC. + + +2000-10-23 Bernd Kreimeier + + * alc-context.sgml: entities for ALCdevice and ALCcontext. + Fixed typos reported by CarloV on non-pointer handles. + + * chp-rendering.sgml: infinite LOOPING attribute. + +2000-10-20 Bernd Kreimeier + + * ent-examples.sgml: experimenting with external sample.c + files as CDATA entities, to no avail. + * oalspecs.sgml: example entities. + +2000-10-16 Bernd Kreimeier + + * chp-rendering.sgml: moved in buffer queueing. + Scratched PLAY_COUNT. Fixed SourceStart{v} residue. + + * chp-queueing.sgml: proof-reading, contents then moved out. + +2000-10-13 Bernd Kreimeier + + * chp-queueing.sgml: changed according to discussion. + Removed some, but not all redundant annotation, and removed + remainders of old attribute based approach. Added annotation + on repetition by multiple calls. Changed signatures. + +2000-10-11 Bernd Kreimeier + + * chp-state.sgml: removed GAIN_LINAR based equations. + * chp-rendering.sgml: removed GAIN_LINAR. + * ext-gainlinear.sgml: collected GAIN_LINEAR related sections. + Note: choosing to use GAIN_LINEAR_LOKI for now. The duplication + of tokens complicates specification and implementation, and + requires further discussion. + + * ent-marks.sgml: added Revision mark. + + * oalspecs.sgml: added per-file revision history (appendix). + Note: we might wind up never rendering these, I can't see + a consistent way to do this. + + * ChangeLog: moved in revision history from oalspecs.sgml. + Note: see the very first entry in this document for the + revision history that predates this ChangeLog. + TODO: include ChangeLog in RFC/Revision rendering. + + * chp-introduction.sgml: removed CVS based revision history. + Added a %Revision marked section (we now have these per-file). + +2000-10-10 Bernd Kreimeier + + * oalspecs.sgml: reworked for new distributed document. + + * app-annotations.sgml: created from original doc. + * app-extensionprocess.sgml: created from original doc. + * app-extensions.sgml: created from original doc. + * app-constants.sgml: created from original doc. + + * alc-context.sgml: created from original doc. + Note: this breaks the scheme a bit, as this is technically + a chapter, but if ALC turns into a multi-chapter + documentation at some point down the road it will have + to be a separate document anyway. + + * chp-multichannel.sgml: created from original doc. + * chp-queueing.sgml: created from original doc. + * chp-buffers.sgml: created from original doc. + * chp-rendering.sgml: created from original doc. + * chp-state.sgml: created from original doc. + * chp-operation.sgml: created from original doc. + * chp-introduction.sgml: created from original doc. + * sec-bookinfo.sgml: created from original doc. + + * ent-names.sgml: created from original doc. + * ent-marks.sgml: got it working. + Note: this will be a temporary file (created from the Makefile), + not sure whether it should be in CVS at all. + + * ChangeLog: created. + +2000--1999 Bernd Kreimeier + + * oalspecs.sgml: moved out CVS Revision History (below). + + The AL Specification was originally maintained as an HTML + document, and subsequently as a single SGML file. With the + split into several files, it is no longer possible or + desirable to maintain a single revision history. As we + have a manually maintained revhistory within the DocBook + source, I decided to move the old revision history into + the ChangeLog. + + Revision 1.11 2000/10/11 18:44:46 bk + More JMJ typo corrections. IA-SID I3DL2 section rephrased. Moved + marked section toggles in ent-marks.sgml file so that these can + be generated or modified from the Makefile. Split document up into + several separate files using external entities. Created external + ChangeLog. From now on, revision history and changes will be + maintained in the ChangeLog as it is scattered over files. + + Revision 1.10 2000/09/27 22:57:02 bk + Commit w/o subdoc (doesn't work). + + Revision 1.9 2000/09/27 22:54:36 bk + Typos from MKV and JMJ. Fixed Inverse Square Law. Scratched + ProgagationSpeed. Described distance models in full. Changed + to INVERSE_DISTANCE (both models use ROF). Added GH text (parts) + as annotation to "View" sections, added Basic AL Operation text + he sent (some changes, removed redundant section), marked source + as Example sections. Changed Terminology annotation. Added TBA + on Fundamentals. RFC'ed distributed error section from GL and + annotation. Added TBA on Floating Point Computation. Added AL + State TBA. Added AL entity as opposed to OAL. Added Command Syntax + TBA (minimal) plus annotation. More fixes from JMJ. No NaN + check comment. Rephrased invalid commmands. Disclaimer on + Enable/Disable being unused. Query with NULL destination + clarified. Example values for simple query. TBA on data + conversions. Cleaned out Time query/scaling, added RFC, + marked DURATION/REMAINDER as Scratch. Euclidean. No Transforms + now annotation. Promoted RFC on Source( BUFER, NONE ) to spec + text. Scratch'ed Streaming Buffer remarks. Removed IMPLIED. + Scratch'ed Buffer Traversal and a whole shebang of queueing + annotation to go with it. ALC cleanup, RFC on multiple + current contexts. Skipped Buffer Queueing section for now. + SUBDOC NO in DocBook declaration. Validation pass for CVS. + + Revision 1.8 2000/09/20 01:21:33 bk + Fixes from Ian Ollman: fixed wrong title on Buffer Attributes + section. Annotation on No application selected Names. Fixes + from JMJ: typos and mistakes on I3DL2. Renamed LOOP_COUNT to + PLAY_COUNT and clarified counting as requested. DOPPLER_FACTOR, + DOPPLER_VELOCITY, Doppler Effect section rewritten according to + announced changes. Removed DistanceFactor, added scale invariance. + Scratch-marked Multichannel sketch. Added BufferWriteData as Loki + extension. Removed obsolete RFC on depercating BUFFER. Rewrote + Buffer queueing state section and renamed BUFFERS_USED to + BUFFERS_PROCESSED following discussion with John Kraft, to remove + ambiguity. Rewrote BUFFERS_PROCESSED description. Added + MAX_DISTANCE plus MUTE annotation. Added No Culling section. + Edited DISTANCE_MODEL section. Distance Model Source Attributes + added. Rolloff quantization annotation added. Evaluation order + on attenuation/gain. Marked Scratch the current IASIG section + per JMJ's request, added Source Environment Attribute section + as TBA. Removed and moved RFC's (AppendData, GenStreaming). + Clarified directional attenuation. Clarified CONE_OUTER_GAIN + as scaling factor. Added a Programmers and Implementors View. + + Revision 1.7 2000/08/15 02:47:03 bk + Fixed RFC markup error needed for Full version. + + Revision 1.6 2000/08/15 02:20:49 bk + Clarified BUFFER_USED vs. buffer state. Started section on + handling multichannel output. Moved out GenStreamingBuffer + in annotation on BufferAppendData extension. Added BufferWriteData + with internalFormat parameter and discussion on mono vs. + multichannel buffers. Added Multichannel objects (kept short in + case we demote to extension or replace). Some annotation. + Tried GGI docbook2texi in attempt to end those rogue linux/doc + activities, to no avail. Removed messy scratchpad section. + Removed outdated example listing. Turned remaining scratch + annotation into appendix. Created minimal Makefile (cleanup). + + Revision 1.5 2000/08/08 02:56:05 bk + alcGetCurrentContext. Attempt to formulate Design Objectives as + a guideline. RFC's on INITIAL state, ACTIVE misnomer. RFC on + INVALID_POINTER. Lots of scratch stuff (fallout from buffer + queueing discussions). Annotations on various buffer queue + rejects and related issues. Added Scaling and Timing sections + and related calls. Added CONE_OUTER_GAIN. Added Doppler sections. + Rewrote and shuffle globale state, simple query, string query. + Moved Extensions sections in Appendix. Added annotation and + RFC's regarding distance/gain model. No more ILLGEAL - finally + checked the specs and Mesa 3.3 to be sure. Picked OPERATION at + the same time, also specs conformat (no comment...). Removed + JMJ as responsible author (all blame shall be mine). Fixed + Play(), INITIAL vs. STOPPED. Edited related RFC's, stripped + obsolete commentary. Rewrote State/State Transition section. + Fixes on SGML. CHANNELS and BITS moved in RFC's. Moved and + rewrote RFC on Duration/Remainder queries into Time section. + Stripped BYTE_LOKI of all but the essentials. Added placeholders + for Callback and AppendData in same (backwards compatibility) + section. Lots of Buffer Queueing. + + Revision 1.4 2000/07/25 17:19:17 bk + Changed ILLEGAL_OPERATION to ILLEGAL_COMMAND consistently. Added + excerpts from mailinglist to scratch section. Added Appendix on + Extension Process. Typos. RFC on EXTENSIONS changed to Annotation. + SGML validation and error fixes (not done on cvs1.3, sorry). + + Revision 1.3 2000/07/24 21:18:53 bk + Reordered manual revision history to be CVS compliant. Diff against + Rev. 1.4/bk000626 as it was not checked in CVS. Created scratch + section for removed RFC 's. Checked edits on Extension section and + removal of related RFC/bk000506. Converted removed RFC/bk000504 + on IsExtensionPresent in Annotation. Rewrote Mike's annotations + on source types and distance attentuation clamping as RFC's and + added some removed RFC sections back. Rewrote annotation added by + MikeV on compressed buffers, split of RFC on AL_SIZE query. + Re-added RFC's an buffer attribute defaults, edited annotation + on GetEnumValue return. RFC's on factory methods for streaming + buffers, memory budgeting, initial object state. Re-inserted and + extended RFC on format enum issue. Re-inserted the global + setter/getter in scratch section. Implementation requirement + section. Resurrected CONE_OUTSIDE_GAIN as RFC. BITS bitching, + annotation to explain why not enumerating frequencies. Got a grip + and recognized BITS and CHANNELS as query-only. More on frequency + in BufferData. Into CVS before current discussions are added. + + Revision 1.2 2000/07/19 15:16:58 briareos + Enable/Disable return void, not enum. + + Revision 1.1 2000/06/29 00:27:24 briareos + Initial draft of OpenAL specs. + + + 1.4/briareos : not in CVS + Finished editing related to my comments, with Joe's + feedback, in preparation for use by Creative and eventual + public dissemination later this week. + + 1.4/bk000626 : not in CVS + Started using id attribute on RFC note elements, to ensure unique + identifiers. Added Annote level. Added oal15-jm/overview.html + changes. Added oal15-jm/object-state.html (mostly). Removed + Filtering/Spatialization distinction and merged attribute table. + Moved Filter chapter to annotation. Moved Geometry to annotation, + rewrote annotation with my latest conclusions on A3D/EAGLE like + approach. Stripped out ALU and ALUT, added annotation on reasons. + Added some text to introduction chapter. Moved ALC chapter to end + of specification. Followed John Hall advice to enable section + numbers in print stylesheet. Rewrote section on GetError. Created + I3DL2 extension appendix. Reworked all tables for better + formatting with separated descriptions. Took out a bunch of + outdated RFC's. Rewrote Query and String Query section. Added + Scratch sections. Added ARB/Ack section. Didn't get RFC comments + from MikeV. Fixed permission to copy. + + 1.4/bk000508 : not in CVS. + Added RFC to Device section. Made nested RFC around entire + device section to make sure. Added trailing blanks to data type + entities as a cheap workaround to the HTML backend problem in + FuncSynopsis. Added RFC from BYTE_LOKI discussion on mailinglist. + + 1.4/bk000507 : not in CVS. + Added altypes.h info on scalar types, copied Linux OpenGL Base + ABI layout. Also added some missing enums. Checked alc.h header, + added UpdateContext and RFC's. Changed alc*Context signatures to + include ALCdevice handle. Checked al.h header, fixed outdated + BufferData/BufferAppendData signatures, added RFC's. Added comments + on differing return types. This pretty much concludes the inventory. + + 1.4/bk000506 : not in CVS. + The HTML in CVS was completely mangled between the 1.2 and 1.3 + revisions. Also, redundant al and AL_ prefixes were added, and + the document was restructured. I tried SaveAs ASCII and + whitespace insensitive compare, and finally did the compare + manually (no guarantees). The text sections that looked + relevant have been added in RFC's with the bk000506 tag. + Also added a revision history and CVS tags for future CVS based + revision control (post-E3). + + 1.4/bk000505 : not in CVS. + SGML version. Merges AL specification and API reference as + of GDC (v1.2) with each other. Adds ALC placeholder. + Addition of several RFCs based on recent discussions. + + 1.3/cvogelsa : only in CVS. + api-reference.html CVS v1.14 + specification.html CVS v1.3 + + 1.2/briareos : 8 March 2000, released at GDC. + api-reference.html CVS v1.13 + specification.html CVS v1.2 + + x.xx/nn : early specifications. + Neither the early working papers by Terry Sikes, Sean L. Palmer + and others nor the first versions of the &OAL; specification + as compiled at Loki were archived and versioned in a coherent way. \ No newline at end of file diff --git a/src/openal/docs/Makefile b/src/openal/docs/Makefile new file mode 100644 index 0000000..ce5932b --- /dev/null +++ b/src/openal/docs/Makefile @@ -0,0 +1,148 @@ +## ------------------------------------------------ +## Makefile for openal/docs/ + + +## DocBook stuff +DB2PS = db2ps +DB2PDF = db2pdf +DB2HTML = db2html + +## TexInfo stuff +MAKEINFO = makeinfo + + + +## Targets +OALSPEC = oalspecs +DB_OBJS = $(OALSPEC).ps $(OALSPEC)/index.html + +TEXI_OBJS = openal.info + +.SUFFIXES: .texi .info + +## Rules +%.ps : %.sgml + $(DB2PS) $< + +%/index.html : %.sgml + $(DB2HTML) $< + + +## Fixme - config.h +%.info : %.texi ../config.h + $(MAKEINFO) $< + + + +## ---------------- +## Standard targets +default: all + +all: render + +render: $(DB_OBJS) + +texi: $(TEXI_OBJS) + + +## -------------- +## Cronjob target +update: specs annotated full + + +## ---------------------------------------------------------- +## Specification - prototypes, tokens, explanation +## +specs-ps: + -rm -f $(OALSPEC)-specs.ps + cp ent-marks-specification.sgml ent-marks.sgml + $(DB2PS) $(OALSPEC).sgml + mv $(OALSPEC).ps $(OALSPEC)-specs.ps + zip -9 $(OALSPEC)-specs.ps.zip $(OALSPEC)-specs.ps + +specs-html: + -rm -rf $(OALSPEC)-specs/ + cp ent-marks-specification.sgml ent-marks.sgml + $(DB2HTML) $(OALSPEC).sgml + mv $(OALSPEC) $(OALSPEC)-specs + -rm $(OALSPEC)-specs.html.zip + zip -r9 $(OALSPEC)-specs.html.zip $(OALSPEC)-specs/ + +specs: specs-ps specs-html + + +## ----------------------------------------------------------- +## Annotated Version - including annotation seections +## (not Programmer's Guide, no examples) +annotated-ps: + -rm -f $(OALSPEC)-annote.ps + cp ent-marks-annotated.sgml ent-marks.sgml + $(DB2PS) $(OALSPEC).sgml + mv $(OALSPEC).ps $(OALSPEC)-annote.ps + zip -9 $(OALSPEC)-annote.ps.zip $(OALSPEC)-annote.ps + +annotated-html: + -rm -rf $(OALSPEC)-annote/ + cp ent-marks-annotated.sgml ent-marks.sgml + $(DB2HTML) $(OALSPEC).sgml + mv $(OALSPEC) $(OALSPEC)-annote + -rm $(OALSPEC)-annote.html.zip + zip -r9 $(OALSPEC)-annote.html.zip $(OALSPEC)-annote/ + +annotated: annotated-ps annotated-html + +## ---------------------------------------------------------- +## FULL Version - include's the RFC's and everything else +## +full-ps: + -rm -f $(OALSPEC)-full.ps + cp ent-marks-full.sgml ent-marks.sgml + $(DB2PS) $(OALSPEC).sgml + mv $(OALSPEC).ps $(OALSPEC)-full.ps + zip -9 $(OALSPEC)-full.ps.zip $(OALSPEC)-full.ps + +full-html: + -rm -rf $(OALSPEC)-full/ + cp ent-marks-full.sgml ent-marks.sgml + $(DB2HTML) $(OALSPEC).sgml + mv $(OALSPEC) $(OALSPEC)-full + -rm $(OALSPEC)-full.html.zip + zip -r9 $(OALSPEC)-full.html.zip $(OALSPEC)-full/ + +full: full-ps full-html + + +## ---------------------------------------------------------- +## ZIP - create archive +## + + + +clean: db_clean texi_clean + +db_clean: + -rm -f $(OALSPEC).dvi + -rm -f $(OALSPEC).aux + -rm -f $(OALSPEC).log + -rm -f $(OALSPEC).tex + -rm -rf $(OALSPEC).junk + -rm -f \#*\# + -rm -f *~ + +texi_clean: + @echo "Not done." + +install: + @echo "Not done." + +distclean: clean + -rm -rf \ + $(OALSPEC)-full/ \ + $(OALSPEC)-full.ps \ + $(OALSPEC)-annote/ \ + $(OALSPEC)-annote.ps \ + $(OALSPEC)-specs/ \ + $(OALSPEC)-specs.ps \ + *.zip + + diff --git a/src/openal/docs/alc-context.sgml b/src/openal/docs/alc-context.sgml new file mode 100644 index 0000000..3b0ae0f --- /dev/null +++ b/src/openal/docs/alc-context.sgml @@ -0,0 +1,846 @@ + + + + AL Contexts and the ALC API + + This section of the AL specification describes ALC, the AL Context API. + ALC is a portable API for managing &AL; contexts, including + resource sharing, locking, and unlocking. Within the core AL API the + existence of a Context is implied, but the Context is not exposed. The + Context encapsulates the state of a given intance of the AL state machine. + + + To avoid confusion with the AL related prefixes implied throughout + this document, the "alc" and "ALC_" prefixes have been made explicit + in the ALC related sections. + + + ALC defines the following objects: Contexts. + + + + Annotation (ALC entry points) + While the actual ALC implemention might be supplied + as a separate library, or as part of a server or + daemon, the specification requires that the AL library + provides the actual ALC entry points. + + ]]> + + Annotation (ALC OS independend) + ALC is meant to be OS-independent. OS specifics are + expected to be adressed by defining proper device + specifiers strings, and configuration attributes. + In this, ALC differs from GLX/WGL, which (due to the + tighter coupling with the window manager and + operating system) attempt to abstract OS specifics + to a much lesser degree. + + ]]> + + + RFC/ Issues + Hardware init/deinit, mapping many processes with + multiple contexts to many devices, sharing resources + among processes. + + ]]> + + + + Managing Devices + + ALC introduces the notion of a Device. A Device can be, + depending on the implementation, a hardware device, or + a daemon/OS service/actual server. This mechanism also + permits different drivers (and hardware) to coexist + within the same system, as well as allowing several + applications to share system resources for audio, including + a single hardware output device. The details are left + to the implementation, which has to map the available + backends to uniq7ue device specifiers (represented as + strings). + + + + Annotation (Network transparency) + &AL; is meant for interoperability with &OGL;. Some + implementations of &OGL; bindings (e.g. GLX) are network + transparent. The Device API theoretically allows for + a network transparent AL implementation. No wire protocol + is specified, no specification or implementation is planned. + + ]]> + + Annotation (Device Enumeration) + At this time, ALC does not provide mechanism to query + for available devices, and request device enumerations. + This might be added at a later time, depending on demand + and the ability to abstract OS and configuration specifics. + + ]]> + + Annotation (X11 Audio) + The ALC API intentionally mimicks XOpenDisplay + and XCloseDisplay. There is no X Audio standard, + although proposals have been made in the past. + The ALC API design accounts for this possibility + in a minimal way. + + ]]> + + + Connecting to a Device + + The alcOpenDevice function allows the application (i.e. the + client program) to connect to a device (i.e. the server). + + &device; * alcOpenDevice + const &ubyte;* deviceSpecifier + + If the function returns NULL, then no sound driver/device has + been found. The argument is a null terminated string that requests + a certain device or device configuration. If NULL is specified, + the implementation will provide an implementation specific default. + + Annotation (Operating system dependencies) + At this point, system specific configuration, and operating system + specific details, are handled by leaving the details of the + string specifier to the implementation. The application coder + has to determine how he wants to obtain this information from the + OS or the user. If, at a later point, device enumeration and + configuration requests are supported through ALC, the resulting + string might still be operating system and implementation specific. + + ]]> + + + + Disconnecting from a Device + + The alcCloseDevice function allows the application (i.e. the + client program) to disconnect from a device (i.e. the server). + + &void; alcCloseDevice + &device; * deviceHandle + + If deviceHandle is NULL or invalid, an ALC_INVALID_DEVICE error + will be generated. Once closed, a deviceHandle is invalid. + + + + + + + + + Managing Rendering Contexts + + All operations of the AL core API affect a current AL context. + Within the scope of AL, the ALC is implied - it is not visible as + a handle or function parameter. Only one AL Context per INprocess + can be current at a time. Applications maintaining multiple + AL Contexts, whether threaded or not, have to set the current + context accordingly. Applications can have multiple threads + that share one more or contexts. In other words, AL and ALC are + threadsafe. + + + The default AL Context interoperates with a hardware device driver. + The application manages hardware and driver resources by + communicating through the ALC API, and configures and uses + such Contexts by issuing AL API calls. A default AL Context + processes AL calls and sound data to generate sound output. + Such a Context is called a Rendering Context. There might + be non-rendering contexts in the future. + + + The word "rendering" was chosen intentionally to emphasize the + primary objective of the AL API - spatialized sound - and the + underlying concept of AL as a sound synthesis pipeline that + simulates sound propagation by specifying spatial arrangements + of listeners, filters, and sources. If used in describing an + application that uses both &OGL; and &AL;, "sound rendering + context" and "graphics rendering context" should be used for + clarity. Throughout this document, "rendering" is used + to describe spatialized audio synthesis (avoiding ambiguous + words like "processing", as well as proprietary and restrictive + terms like "wavetracing"). + + + + + Context Attributes + + The application can choose to specify certain attributes for + a context. Attributes not specified explicitely are set to + implementation dependend defaults. + + + + Context Attributes + + + + + + Name + Description + + + + + ALC_FREQUENCY + Frequency for mixing output buffer, in units of Hz. + + + ALC_REFRESH + Refresh intervalls, in units of Hz. + + + ALC_SYNC + Flag, indicating a synchronous context. + + + +
+
+ + Annotation (Refresh Control) + Applications might have a fixed, or bounded, schedule for + state changes (e.g. synchronously with the GL framerate). + In this case it is desirable to specify the mixahead + interval (milliseconds), or refresh rate (Hz), for the + mixing thread. This is especially important for a synchronous + context, where the application has to specify the refresh + interval it intends to keep. + + ]]> + + + RFC / Mixing Buffer Configuration + ALC_RESOLUTION was originally used to specify the accuracy of the + mixing output buffer. For the time being this is not supported, but + could be added if mixing path and result accuracy control is desirable + to scale resource requirements. + A full ALC_FORMAT (including channel and other attributes) does not + make sense for rendering contexts, but will be necessary for PBuffers + (mix to memory). + + ]]> + + RFC / LOKI extensions + + + ALC_SOURCES_HINT_LOKI, /* # of sources to pre-allocate */ + ALC_BUFFERS_HINT_LOKI, /* # of buffers to pre-allocate */ + + ALC_CD_LOKI, /* demand CD/DVD control */ + + + + ]]> +
+ + + + Creating a Context + + A context is created using alcCreateContext. The device parameter + has to be a valid device. The attribute list can be NULL, + or a zero terminated list of integer pairs composed of valid + ALC attribute tokens and requested values. + + &context; * alcCreateContext + const &device; * deviceHandle + ∫ * attrList + + Context creation will fail + if the application requests attributes that, by themselves, + can not be provided. Context creation will fail if the + combination of specified attributes can not be provided. + Context creation will fail if a specified attribute, or + the combination of attributes, does not match the default + values for unspecified attributes. + + + + + Selecting a Context for Operation + + To make a Context current with respect to AL Operation (state + changes by issueing commands), alcMakeContextCurrent is used. + The context parameter can be NULL or a valid context pointer. + The operation will apply to the device that the context was + created for. + + &bool; alcMakeContextCurrent + &context; * context + + For each OS process (usually this means for each application), only + one context can be current at any given time. All AL commands apply + to the current context. Commands that affect objects shared among + contexts (e.g. buffers) have side effects on other contexts. + + Annotation (No Explicit Device) + An ALC context is bound to the device it was created for. + The context carries this information, thus removing the + need to specify the device explicitely. Contexts can not + be made current for any other device aside from the one + they were created for. + + ]]> + + Annotation (No Multiple Current) + There is only one current context per process, even + in multithreaded applications, even if multiple + devices are used. + + ]]> + + + Annotation (Current NULL) + The implementation is encouraged to exploit optimizations + possible if the application sets the current context + to NULL, indicating that no state changes are intended + for the time being. The application should not set the + current context to NULL if more state changes are + pending on the most recent, or another context created + for the same device. + + ]]> + + Annotation (Shared Objects) + Buffers are shared among contexts. As mutiple contexts + can exist at the same time, the state of shared objects + is also shared among contexts. + + ]]> + + RFC / Buffer Deletion + Buffers that have not yet been processed in another context + can not be deleted from any other context. + + ]]> + + + + Initiate Context Processing + + The current context is the only context accessible to state + changes by AL commands (aside from state changes affecting + shared objects). However, multiple contexts can be processed + at the same time. To indicate that a context should be + processed (i.e. that internal execution state like offset + increments are supposed to be performed), the application + has to use alcProcessContext. + + &void; alcProcessContext + &context; * context + + Repeated calls to alcProcessContext are legal, and do not + affect a context that is already marked as processing. The + default state of a context created by alcCreateContext is + that it is not marked as processing. + + + Annotation (Sync and async implementations) + Unfortunately, the exact semantics of alcProcessContext is + not independend of the implementation. Ideally it should be completely + transparent to the application whether the sound driver is threaded or + synced. Unfortunately a synced context has to have its execution + initiated by the application, which requires calls of + alcProcessContext timed in accordance to the drivers mixahead, + or the rendering buffer will underflow. For a threaded driver, + the implementation is free to consider alcProcessContext + a NOP once the context has been marked as processing. + + One consequence is that an application that was developed + using a threaded implementation of AL might not work + properly with a synchronous implementation of AL (on + the other hand, an AL application that works using a + synchronous implementation is guaranteed to work with + a threaded implementation. + + Enforcing alcProcessContext calls would defeat the purpose + of a threaded implementation. Permitting the AL implementation + to e.g. schedule optimizations based on alcProcessContext + calls would similarly obfuscate the exact semantincs. + Consequently, the application coder has to accept this + implementation dependency, and has to rely on the ALC_SYNC + attribute to explicitely request a synchronous implementation. + The implementation can expect the application to be aware + of the additonal constraints imposed on alcProcessContext in + this case. + + ]]> + + Annotation (Multiple Contexts and SYNC refresh) + The application can request SYNC contexts or threaded contexts, + however, the implementation is not obliged to provide both, or provide + a mixture of both on the same device. + + ]]> + + + RFC/ Implications for Networking + What does alcProcessContext imply for networking? + For AL, will we add alFlush/alFinish? + + ]]> + + + + Suspend Context Processing + + The application can suspend any context from processing + (including the current one). To indicate that a context should be + suspended from processing (i.e. that internal execution state like + offset increments is not supposed to be changed), the application + has to use alcSuspendContext. + + &void; alcSuspendContext + &context; * context + + Repeated calls to alcSuspendContext are legal, and do not + affect a context that is already marked as suspended. The + default state of a context created by alcCreateContext is + that it is marked as suspended. + + + Annotation (Sync and async implementations) + Unfortunately, the exact semantics of alcSuspendContext is + also not independend of the implementation. For a threaded + implementation, alcSuspendContext is necessary to ensure a context + is not processed. For a synchronous implementation, omitting + alcProcessContext calls will ultimately have the same effect, + but will also generate rendering buffer underflow errors. + Again, the application coder that requests a synchronous + context using ALC_SYNC has to make sure that alcSuspendContext + is used accordingly. + + ]]> + + Annotation (Suspending vs. Muting a context) + By setting Listener GAIN to zero, an application can mute a + context, and expect the implementation to bypass all rendering. + However, the context is still processing, and the internal + execution state is still updated accordingly. Suspending a + context, whether muted or not, will incidentally suspend rendering + as well. However, it is the application's responsibility to + prevent artifacts (e.g. by proper GAIN control to fade in and + out). It is recommended to mute a context before suspending. + + ]]> + + Annotation (Current Context Suspended) + It is possible to make a suspended context current, or + suspend the current context. In this case, the implementation + is still obliged to immediately verify AL commands as they + are issued, and generate errors accordingly. The implementation + is permitted to postpone propagating the actual state changes + until the context is marked for processing again, with the + exception of dereferencing data (e.g. buffer contents). + For efficiency reasons (memory usage), most if not all + AL commands applied to a suspended context will usually + be applied immediately. State changes will have to be applied + in the sequence they were requested. It is possible to + use suspension of a current context as an explicit locking + (to enforce apparent synchronicity), but execution is still + guaranteed to be in sequence, and the implementation is not + expected to optimize this operation. A typical use would + be setting up the initial configuration while loading a + scene. + + ]]> + + Annotation (Release of Hardware Resources) + The specification does not guarantee that the implementation + will release hardware resources used by a suspended context. + This might well depend on the details of the hardware and driver. + Neither a muted context nor a suspended context can be expected + to free device resources. If all contexts for a given device + are suspended, and no context of this device is current, the + implementation is expected to release all hardware resources if possible. + + ]]> + + + + + + + Destroying a Context + + + &void; alcDestroyContext + &context; * context + + The correct way to destroy a context is to first release it using + alcMakeCurrent and NULL. Applications should not attempt to destroy + a current context. + + +
+ + + + + ALC Queries + + + Query for Current Context + + The application can query for, and obtain an handle to, the current + context for the application. If there is no current context, NULL is + returned. + + &context; * alcGetCurrentContext + + + + + + + Query for a Context's Device + + The application can query for, and obtain an handle to, the device + of a given context. + + &device; * alcGetContextsDevice + &context; * context + + + + + + + Query For Extensions + + To verify that a given extension is available for the current + context and the device it is associated with, use + + + &bool;IsExtensionPresent + const &device; * deviceHandle + const &ubyte; * extName + + + A NULL name argument returns FALSE, as do invalid and unsupported string + tokens. A NULL deviceHandle will result in an INVALID_DEVICE error. + Annotation (Exlicit Device Parameter) + Certain ALC Extensions might be relevant to context creation + (like additional attributes, or support for unusual multi-context + combinations), thus the application might have to query these + before a context is created. On the other hand, ALC Extensions + are specific to the device. + + ]]> + + + + + Query for Function Entry Addresses + + The application is expected to verify the applicability of + an extension or core function entry point before requesting + it by name, by use of alcIsExtensionPresent. + + &void; * alcGetProcAddress + const &device; * deviceHandle + const &ubyte; * funcName + + Entry points can be device specific, but are not context specific. + Using a NULL device handle does not guarantee that the entry + point is returned, even if available for one of the available + devices. Specifying a NULL name parameter will cause an + ALC_INVALID_VALUE error. + + + + + Retrieving Enumeration Values + + Enumeration/token values are device independend, but tokens + defined for extensions might not be present for a given device. + Using a NULL handle is legal, but only the tokens defined by + the AL core are guaranteed. Availability of extension tokens + dependends on the ALC extension. + + &uint; alcGetEnumValue + const &device; * deviceHandle + const &ubyte; enumName + + Specifying a NULL name parameter will cause an + ALC_INVALID_VALUE error. + + + + + Query for Error Conditions + + ALC uses the same conventions and mechanisms as AL for error + handling. In particular, ALC does not use conventions + derived from X11 (GLX) or Windows (WGL). + The alcGetError function can be used to query ALC errors. + + &enum;alcGetError + &device; * deviceHandle + + Error conditions are specific to the device. + + + + + Error Conditions + + + + + + Name + Description + + + + + ALC_NO_ERROR + The device handle or specifier does name an accessible driver/server. + + + ALC_INVALID_DEVICE + The Context argument does not name a valid context. + + + ALC_INVALID_CONTEXT + The Context argument does not name a valid context. + + + ALC_INVALID_ENUM + A token used is not valid, or not applicable. + + + ALC_INVALID_VALUE + An value (e.g. attribute) is not valid, or not applicable. + + + +
+
+ + + Annotation (No UNDERFLOW error) + Applications using synchronous (and, depending on CPU + load, even an asynchronous implementation itself) might + fail to prevent underflow of the rendering output + buffer. No ALC error is generated in these cases, + as it this error condition can not be applied to a + specific command. + + ]]> +
+ + + String Query + + The application can obtain certain strings from ALC. + + const &ubyte; * alcGetString + &device; * deviceHandle + &enum; token + + For some tokens, NULL is is a legal value for the deviceHandle. + In other cases, specifying a NULL device will generate an + ALC_INVALID_DEVICE error. + + + + String Query Tokens + + + + + + Name + Description + + + + + ALC_DEFAULT_DEVICE_SPECIFIER + The specifier string for the default device (NULL handle is legal). + + + ALC_DEVICE_SPECIFIER + The specifier string for the device (NULL handle is not legal). + + + ALC_EXTENSIONS + The extensions string for diagnostics and printing. + + + +
+
+ + + In addition, printable error message strings are provided for + all valid error tokens, including ALC_NO_ERROR, ALC_INVALID_DEVICE, + ALC_INVALID_CONTEXT, ALC_INVALID_ENUM, ALC_INVALID_VALUE. + +
+ + + Integer Query + + The application can query ALC for information using an integer + query function. + + &void; alcGetIntegerv + &device; * deviceHandle + &enum; token + &sizei; size + dest + + For some tokens, NULL is a legal deviceHandle. In other + cases, specifying a NULL device will generate an ALC_INVALID_DEVICE + error. The application + has to specify the size of the destination buffer provided. + A NULL destination or a zero size parameter will cause ALC to ignore + the query. + + + + + Integer Query Tokens + + + + + + Name + Description + + + + + ALC_MAJOR_VERSION + Major version query. + + + ALC_MINOR_VERSION + Minor version query. + + + ALC_ATTRIBUTES_SIZE + The size required for the zero-terminated attributes + list, for the current context. NULL is an invalid + device. NULL (no current context for the + specified device) is legal. + + + ALC_ALL_ATTRIBUTES + Expects a destination of ALC_CURRENT_ATTRIBUTES_SIZE, + and provides the attribute list for the current + context of the specified device. NULL is an invalid + device. NULL (no current context for the + specified device) will return the default attributes + defined by the specified device. + + + +
+
+ + Annotation (Backward Compatibility) + Backward compatibility is guaranteed only for minor revisions. + Breaking ABI backwards compatibility will require a issuing + major revision. + + ]]> + + RFC / Version Matching + The ALC version can be different from the AL version. + The ALC major version has to be identical between + application and driver (client and server). + The ALC minor version can differ between client and server, + and the minimum of the two minor version numbers is returned. + + ]]> + + RFC / Device Enumeration + ALC_NUM_DEVICE_SPECIFIERS could be provided as an extension, + but it requires the number of device configurations to be + finite and small, as they are to be represented by strings. + + ]]> + +
+
+ + + + + Shared Objects + + For efficiency reasons, certain AL objects are shared across + ALC contexts. At this time, AL buffers are the only shared + objects. + + + RFC/ Sharing Sources? + Mike Kelly from Dynamix describes an application scenario with + multiple camera views of the same scene (implemented using + several viewports on a single, "split screen", context) + in which he needs multiple listeners (one per view) to + preceive the same sources, with the rendering results of + each listener to mixed for a single output device. As an + alternative to permitting multiple listeners within a context, + it might be preferable to introduce a mechanism to explicitely + share certain AL objects among contexts, at a later time. + It is not desirable to share Sources by default, especially + as the more common multi-listener application is expected to + maintain listeners with disjunct sets of sources. + + ]]> + + Shared Buffers + + Buffers are shared among contexts. The processing state of a buffer + is determined by the dependencies imposed by all contexts, not just + the current context. This includes suspended contexts as well as + contexts that are processing. + + + + + +
diff --git a/src/openal/docs/api-reference.html b/src/openal/docs/api-reference.html new file mode 100644 index 0000000..3760770 --- /dev/null +++ b/src/openal/docs/api-reference.html @@ -0,0 +1,15 @@ + + + + +OpenAL API Reference + + + +

OpenAL API Reference

+

+ This document has been removed. +

+ + + diff --git a/src/openal/docs/app-annotations.sgml b/src/openal/docs/app-annotations.sgml new file mode 100644 index 0000000..6eabf4b --- /dev/null +++ b/src/openal/docs/app-annotations.sgml @@ -0,0 +1,297 @@ + + + Miscellaneous Annotations + +
+ Reverberation Objects? + + In a generalization of the I3DL2 Extension for + listener specific reverberation effects, it might + be best to implement Reverb Objects. A Reverb Object + is one example of a parametrized filter. Each + such object encapsulates a set of attributes (the + filter parameters). Sources and Listener alike + have an attribute that allows the application coder + to choose a reverb object for application either + at the origin of the sound, or at the position of + the listener. Initial implementation would only + support one Reverb Object per Context, applied at + the listener position. + + + The I3DL2 Environment is a filter that alters the + way the user experiences the virtual world. As + filters require DSP operations it is limited by hardware + processing capabilities. + + + The I3DL2 Environment models the surroundings of the + listener by simplifying the presumed acoustic properties + of those surroundings into a small set of parameters. + It allows to reproduce the effects of sound reflections + and reverberation caused by walls and obstacles, and + the muffling effects of obstacles inside environments + or partitions between environments. + + + Environment properties: + Early reflections level and delay. + Late reverberation level and delay, low- and high-frequency decay time. + Reverberation diffusion, density and spectrum. + + + Source properties: + Direct path intensity and spectrum. + Reverberation intensity and spectrum. + +
+ +
+ On Filters + + RFC/bk000502: Filters as the general concept of modifiers? + Environment as a special case filter? + Can we break down EAX environments into ReverbFilters where we + parametrize late reflections, and ReflectFilters, which fake + early reflections? Do we need this separation if we have + calculated or distinct echo effect reflections instead of + stocastic ones? Does it make sense to superimpose a general + reverb kicking in after a delay t, with reflections (random + or not) or should reverb only kick in after reflections are + discarded? + + + RFC/bk000502: old text. + (Environment) Properties: + Geometry - geometry is specified using an immediate mode API which is + similar to OpenGL. Support for scene lists are also provided on a basis + similar to OpenGL's display lists. + Materials - specify the absorptive and reflective qualities of a piece + of geometry. &AL; should provide a facility for accessing preset + materials, and storing and retrieving new materials at runtime. + + + RFC/nn: Atmospheric/ambient properties? + REF/nn: A3D 2.0 IA3dMaterial + + +
+ Atmospheric Filter + + The atmospheric filter the effects of media with constant density, + on propagating sound waves. The effect of the atmospheric filter + is distance dependent. Atmospheric effects can be parameterized + by specifying attenuation per unit distance, the scale for the + unit distance, for one of a minimum of two frequency ranges + (low frequency and high frequency roll-off). + + + RFC/bk000502: do we specify the atmospheric filter per-source? + The effect is clearly dominated by the most dense medium, but + we have little chance simulating crossings between different + media this way. Distance attenuation in media clearly depends + on source and listener being embedded in the same medium, + without any obstruction along the LOS. + +
+ +
+ Listener Reverb + + Listener Reverb is a parameterized filter that modifies the sound at + listener position to emulate effects of the surroundings, namely + effects of late reflections. Without simulating sound propagation + this reverb accounts for the averaged outcome of different arrangements + of reflecting/absorbing surfaces around the listener. + +
+ +
+ Source Reverb + + There is currently no support for reverb at the source position. + +
+ +
+ Reflection Filter + + First order reflection (and, if support, O(n) reflection for small n) + can choose to simulate the effects of different materials by + parametrizing reflection filters. + There is currently no support for reflections. + +
+ +
+ Transmission Filter + + Sound propagation along the LOS can pass through obstructions + specified as convex polygons. The effects of lossy transmission + can be approximated by applying a once-off filtering. Like + atmospheric filters, this can be a frequency-dependent roll-off, + unlike atmospheric filters this does not take distance into + account. Transmission filters can be used to emulate losses + on crossing separating surfaces between different media (water/air + borders). + There is currently no support for transmissions. + +
+
+ +
+ Parameterization over Time + + Fading and cross-fading. There are three ways to handle any kind + of gain control as a function of time: + + + + manipulate gain per frame/sufficiently often + + + + + parameterize, i.e. specify a target gain, + a duration over which to interpolate, and an interpolation function + + + + + provide an buffer that indicates amplitude, stretched over + a duration/by a frequency + + + The last mechanism also works for early reflections and echos, + and any other temporal filtering. The first and second approach + also work for attributes like Pitch. + +
+ + +
+ On Geometry + + Both the A3D API and implementation as well as EAX related utilities + like EAGLE seem to indicate that any effort to handle scene geoemtry + at API level will inevitably duplicate modules found in common game + engines for purposes of collision detection, path planning, AI + support, visibility and sound propagation culling. + + + In other words, any such effort will inevitably lead to competing + subsystems and multiple use of processing and memory resources to + implement the same functionality. While it makes sense to provide + templates, examples, and even utilities like EAGLE and SDK's to + developers, it makes no sense to integrate any such functionality + with the API. + + + The geometry based processing inevitably leads to a scene graph + API, with all the resulting problems. On closer examination it + seems that the specification and storage of source and listener + positions is a red herring. + + + Second and higher order reflections seem to be irrelevant. + + + Reflection can be faked by stochastic means, but an actual + presence/immersion effect will require smooth transitions + depending on the continuous change of distance between + sources, listener, and dominant reflectors. + + + Dominant reflectors are presumed to be 1st order, + with material properties that incur little or no loss + (or even provide amplification), and significant + surface area. + + + Transmission loss through dense media is equivalent to + the distance attenuation model. + + + Refraction/reflection loss at border surfaces separating + media.... + + + No explicit geometry to check whether there is any indirect + (1st order reflection, multiple reflections) path between + source and listener - the application is usually better + equipped to handle this (portal states, PHS). The benefit + of forcing the AL implementation to check for obstruction + (object inersecting LOS) is questionable at best - LOS + checking is also better done by the main application. + In essence, the application might even handle the 1st + order reflections IFF we provide the means to generate + early reflection instead of rolling dice, and if we + make it cheap to enable a path between a source and + the listener complete with a material. Come to think of + it: the implementation guarantees n paths with m filters + one of which is transmission or reflection, one is + distance attenuation, one is source reverb, one is + listener reverb.... + +
+ +
+ No ALU + RFC + ALU, like GLU, is a problem: linkage dependencies, multiple drivers + sharing one ALU etc. It would be best to not clutter the specification + with ALU/ALUT. Any support code/template repository/SDK can be + maintained as a separate open source project. + + + ALU provides operations that do not affect driver or hardware state. + These can be resampling/conversion methods or other sample data + processing, or utilities for (optimized) filter generation. ALU + does not provide I/O operations. At this time, + ALU is not specified and not implemented. + + + RFC/bk000502: GLU is becoming a bit of a problem right now, with + most applications avoiding it as they load GL DLL's explicitely, + but do not trust the ABI specification enough to link against GLU, + and not bothering to load it. A vendor-neutral open source ALU + works for me, but we can not accept link time dependencies to AL. + ALU (like GLU) is meant for minimal convenience, in small + building blocks, it is not meant as an SDK. + + + RFC/bk000502: old text. + ALU is the AL Utility library, and provide functions for performing audio + conversion, preset material properties, and a compatability layer for + legacy stereo format audio. This includes support for panning and per + channel volume control. + + + RFC/nn: Er, what else does the world of 2D sound usually need? + +
+ + +
+ No ALUT + + Application coders frequently request additional support for sound + handling to the extent of sophisticated SDKs. It is expected that + SDK vendors will provide such products on top of AL. ALUT (in analogy + to GLUT) would constitute an "official" SDK if desired. At this time, + ALUT is not specified and not implemented, and not intended to be part + of &AL; proper. + + + ALUT is a utility toolkit for &AL;. It sits on top of ALC. + It provides convenience functions for accessing files, for playing sounds, + and an API for accessing CDROM functionality. + +
+ + + +
\ No newline at end of file diff --git a/src/openal/docs/app-constants.sgml b/src/openal/docs/app-constants.sgml new file mode 100644 index 0000000..8c852e2 --- /dev/null +++ b/src/openal/docs/app-constants.sgml @@ -0,0 +1,43 @@ + + + Global Constants + + + Misc. &AL; Global Constants + + + + + + Name + &OAL; datatype + Description + Literal value + + + + + ALenum + FALSE + boolean false + 0 + + + ALenum + TRUE + boolean true + 1 + + + +
+
+ + + RFC + NONE, ONE, ZERO needed? + + ]]> +
+ diff --git a/src/openal/docs/app-extensionprocess.sgml b/src/openal/docs/app-extensionprocess.sgml new file mode 100644 index 0000000..778c091 --- /dev/null +++ b/src/openal/docs/app-extensionprocess.sgml @@ -0,0 +1,36 @@ + + + Extension Process + + There are two ways to suggest an Extension to AL or ALC. + The simplest way is to write an ASCII text that matches + the following template: + + + + RFC: rfc-iiyymmdd-nn + Name: (indicating the purpose/feature) + Maintainer: (name and spam-secured e-mail) + Date: (last revision) + Revision: (last revision) + + new enums + new functions + + description of operation + + + + Such an RFC can be submitted on the &AL; discussion + list (please use RFC in the Subject line), or send to the + maintainer of the &AL; specification. If you are shipping + an actual implementation as a patch or as part of the + &AL; CVS a formal writeup is recommend. In this case, + the Extension has to be described as part of the + specification, which is maintained in DocBook SGML + (available for UNIX, Linux and Win32). The SGML source of + the specification is available by CVS, and the Appendix + on Extensions can be used as a template. Contact the + maintainer for details. + + diff --git a/src/openal/docs/app-extensions.sgml b/src/openal/docs/app-extensions.sgml new file mode 100644 index 0000000..1f4b3b9 --- /dev/null +++ b/src/openal/docs/app-extensions.sgml @@ -0,0 +1,754 @@ + + + Extensions + + Extensions are a way to provide for future expansion of the &AL; API. + Typically, extensions are specified and proposed by a vendor, and + can be treated as vendor neutral if no intellectual property + restrictions apply. Extensions can also be specified as, or + promoted to be, ARB extensions, which is usually the final step + before adding a tried and true extension to the core API. ARB + extensions, once specified, have mandatory presence for + backwards compatibility. The handling of vendors-specific or + multi-vendor extensions is left to the implementation. The IA-SIG + I3DL2 Extension is an example of multi-vender extensions to + the current &AL; core API. + + + + Extension Query + + To use an extension, the application will have to obtain function addresses + and enumeration values. Before an extension can be used, the application will + have to verify + the presence of an extension using IsExtensionPresent(). The application + can then retrieve the address (function pointer) of an extension entry + point using GetProcAddress. Extensions and entry points can be + Context-specific, and the application can not count on an Extension being available + based on the mere return of an entry point. The application also + has to maintain pointers on a per-Context basis. + + + + &bool; IsExtensionPresent + const &ubyte; * extName + + + + Returns TRUE if the given extension is supported for the current + context, FALSE otherwise. + + + Annotation (IsExtensionPresent) + This function is inspired by the GLU addition, but placed in + the core API as we intend to avoid a separate ALU. This function + avoids potential string overflow and string parsing issues (strstr) + raised by GetString( EXTENSIONS ). + + ]]> + + + Annotation/ EXTENSIONS + GetString( EXTENSIONS ) is supported as well, as it allows for + easy archiving and priting of the list of supported extensions. + + ]]> + + + + Retrieving Function Entry Addresses + + + &void;* GetProcAddress + const &ubyte; * funcName + + Returns NULL if no entry point with the name funcName can be found. + Implementations are free to return NULL if an entry point is present, + but not applicable for the current context. However the specification + does not guarantee this behavior. + + + Applications can use GetProcAddress to obtain core API entry points, + not just extensions. This is the recommended way to dynamically load + and unload &AL; DLL's as sound drivers. + + + RFC/bk000504: + Original spec required all addresses are Context independent. + This matches the Linux &OGL; ABI specification, but imposes + additional implementation constraints. For now, the specification + does not guarantee this. + + ]]> + + + + + Retrieving Enumeration Values + + To obtain enumeration values for extensions, the application has + to use GetEnumValue of an extension token. Enumeration values are + defined within the &AL; namespace and allocated according to + specification of the core API and the extensions, thus they are + context-independent. + + &uint; GetEnumValue + const &ubyte; enumName + + Returns 0 if the enumeration can not be found. The presence of an + enum value does not guarantee the applicability of an extension + to the current context. A non-zero return indicates merely that + the implementation is aware of the existence of this extension. + Implementations should not attempt to return 0 to indicate that + the extensions is not supported for the current context. + + + Annotation/ enums with value zero + The literal value 0 is guaranteed for a number of AL + enums, such as FALSE, NONE, ZERO. As with GL applications + might employ sloppy use of this identity. It also means + that enums with zero value can not be queried through + GetEnumValue, a minor flaw given the constraints of + ABI backward compatibility. The recommended value to + compare GetEnumValue results with is NONE. + + ]]> + + + + Naming Conventions + + Extensions are required to use a postfix that separates the + extension namespace from the core API's namespace. + For example, an ARB-approved extension would use + "_ARB" with tokens (ALenum), and "ARB" with commands (function + names). A vendor specific extension uses a vendor-chosen + postfix, e.g. Loki Extensions use "_LOKI" and "LOKI", + respectively. + + + + + ARB Extensions + + There are no ARB Extensions defined yet, as the ARB has + yet to be installed. + + + + + + Other Extension + + For the time being this section + will list externally proposed extensions, namely + the extension based on the IASIG Level 2 guideline. + + + + + + + IA-SIG I3DL2 Extension + + + The IA-SIG I3DL2 guideline defines a set of parameters to control + the reverberation characteristics of the environment the listener + is located in, as well as filtering or muffling effects applied to + individual Sources (useful for simulating the effects of obstacles + and partitions). These features are supported by a vendor neutral + extension to &AL; (TBA). + The + IA-SIG 3D Level 2 rendering guideline + provides related information. + + + RFC: Global Attributes + Do we need global setters for these: + DIRECT_IASIG/DIRECT_HIGH_FREQUENCY_IASIG? + ROOM_IASIG/ROOM_HIGH_FREQUENCY_IASIG? + + ]]> + + + + Listener Environment Attributes + + + + + + ENV_ROOM Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_ROOM_EXT + f + [0.0, 1.0] + 1.0f + + + +
+ Description: ??? +
+ + + + + ENV_ROOM_HIGH_FREQUENCY Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_ROOM_HIGH_FREQUENCY_EXT + f + [0.0, 1.0] + 0.1f ??? + + + +
+ Description: ??? +
+ + + + + ENV_DECAY_TIME Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_DECAY_TIME_EXT + f + [0.1, 20.0] + 1.0f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + NV_DECAY_HIGH_FREQUENCY_RATIO Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_DECAY_HIGH_FREQUENCY_RATIO_EXT + f + [0.1, 2.0] + 0.5f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + ENV_REFLECTIONS Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_REFLECTIONS_EXT + f + [0.1, 3.0] + 1.0f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + ENV_REFLECTIONS_DELAY Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_REFLECTIONS_DELAY_EXT + f + [0.0, 0.3] + 0.02f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + ENV_REVERB Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_REVERB_EXT + f + [0.0, 10.0] + 1.0f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + ENV_REVERB_DELAY Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_REVERB_DELAY_EXT + f + [0.0, 0.1] + 0.04f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + ENV_DIFFUSION Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_DIFFUSION_EXT + f + [0.0, 100.0] + 100.0f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + ENV_DENSITY Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_DENSITY_EXT + f + [0.0, 100.0] + 100.0f + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+ + + + + ENV_HIGH_FREQUENCY_REFERENCE Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ENV_HIGH_FREQUENCY_REFERENCE_EXT + f + [20.0, 20000.0] + 5000.0 + + + +
+ Description: ??? + What's up with the defaults? Not a normalized range + and a global Scale to set? +
+
+ + Source Environment Attributes + + TBA. + + + ]]> +
+ + ]]> + + + + + Compatibility Extensions + + The extensions described have at one point been in use for + experimental purposes, proof of concept, or short term needs. + They are preserved for backwards compatibility. Use is not + recommended, avialability not guaranteed. Most of these will + be officially dropped by the time API revision 2.0 is released. + + + + + Loki Buffer InternalFormat Extension + + &AL; currently does not provide a separate processing + chain for multichannel data. To handle stereo samples, + the following alternative entry point to BufferData + has been defined. + + &void; BufferWriteData + &uint; bufferName + &enum; format + &void;* data + &sizei; size + &uint; frequency + &enum; internalFormat + + Valid formats for internalFormat are FORMAT_MONO8, FORMAT_MONO16, + FORMAT_STEREO8, and FORMAT_STEREO16. + + + + + Loki BufferAppendData Extension + + Experimental implementation to append data to an existing + buffer. Obsoleted by Buffer Queueing. TBA. + + Annotation (GenStreamingBuffers): + It is possible that a consistent implementation of this + extension will require distinguishing streaming from + regular buffers at creation time, instead of making + this distinction implied by the use of BufferData vs. + BufferAppendData. + + ]]> + + RFC: alBufferAppendData + Specify data to be filled into a streaming buffer. + This takes the current position at the time of the + call, and returns the number of samples written. + ALsizei ALAPIENTRY alBufferAppendData( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALuint freq ); + + + + + ]]> + + RFC: GenStreamingBuffers + Currently there is a function call on the Linux side, + alGenStreamingBuffers(), which generates a Buffer intended + for streaming. The intent of this function was the provide + a clear creation point for streaming buffers, rather than + the previous behaviour of a Buffer "magically" becoming + streaming Buffer the first time BufferAppendData() was + called on it. However, it's hard to believe this anomaly + in the API can be any better. What about + DelStreamingBuffers()? IsStreamingBuffer()? + + The design problem here is that we handle qualitatively different + objects using the same API. Streaming and non-streaming buffers + are fundamentally different. If we create an API that makes it + easy to mistake one for the other, or worse, if we decide to + quietly convert one type of object into another in some cases, + we create a plethora of error cases for the implementation and + the app coder to catch. Separating the Factory methods for the + objects allows us to omit an specialization API that will + accidentally be called more than once, and saves us handling + different stages of "initialization state". AL should not have + any notion of "partially initialized" or "incomplete" objects: + misery and despair lie down that road. If necessary the entire + API should be duplicated (after all, nobody handles 1D, 2D, and + 3D textures using the same GL API hooks), but as the AL + implementation has the ability to distinguish streaming and + non-streamin buffers internally there might not be a need. + Unless a concrete alternative is proposed to resolve the "anomaly" + it will be the preferred method to avoid an API that is + leaner at the expense of being more error-prone. + + ]]> + + + + + + + Loki Decoding Callback Extension + + Experimental implementation to allow the application to + specify a decoding callback for compression formats + and codecs not supported by &AL;. This is supposed to + be used if full uncompression by the application is prohibited + by memory footprint, but streaming (by queueing) is not + desired as the compressed data can be kept in memory + in its entirety. + + + If mixing can be done from the compressed data directly, + several sources can use the sample without having to + be synchronized. For compression formats not supported + by AL, however, partial decompression has to be done by + the application. This extension allows for the implementation + to "pull" data, using apllication provided decompression + code. + + + The use of this callback by the &AL; + implementation makes sense only if late decompression + (incremerntal, on demand, as needed for mixing) is done, + as full early compression (ahead-of-time) inside the + implementation would exact a similar memory footprint. + + + TBA. + + + This extension forces execution of third party code + during (possibly threaded) driver operation, and + might also require state management with global + variables for decoder state, which raises issues + of thread safety and use for multiple buffers. This + extension should be obsolete as soon as &AL; + supports a reasonable set of state of the art + compression and encoding schemes. + + + + + + + + Loki Infinite Loop Extension + + To support infinite looping, a boolean LOOP was introduced. + With the introduction of buffer queueing and the request for + support for a limited number of repetitions, this mechanism + was redundant. This extension is not supported for + buffer queue operations, attempts to use it will cause + an ILLEGAL_OPERATION error. For backwards compatibility + it is supported as the equivalent to + + Source( sName, PLAY_COUNT, MAX_INTEGER ) + + For the query LOOP==TRUE, the comparison + PLAY_COUNT!=MAX_INTEGER has to be executed on + the queue, not the current value which is decremented + for a PLAYING Source. + + Source LOOP_LOKI Attribute + + + + + + + + + &Par; + &Sig; + &Val + &Def; + + + + + LOOP_LOKI + b + &TRUE; &FALSE; + &FALSE; + + + +
+ Description: + &TRUE; indicates that the Source will perform an inifinite + loop over the content of the current Buffer it refers to. +
+
+ + + + + Loki Byte Offset Extension + + The following has been obsoleted by explicit Source State query. + hack. + + + + + Buffer BYTE Offset attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + BYTE_LOKI + ui + n/a + n/a + + + +
+ Current byte for the buffer bound to the source interpreted + as an offset from the beginning of the buffer. +
+ +
+ + ]]> + + + + Loop Point Extension + + + In external file now. + + + + ]]> + +
+ diff --git a/src/openal/docs/architecture.html b/src/openal/docs/architecture.html new file mode 100644 index 0000000..78d985b --- /dev/null +++ b/src/openal/docs/architecture.html @@ -0,0 +1,16 @@ + + + + OpenAL Architecture + + + +

OpenAL Architecture

+

+ This document has been removed. + See Index + for the specification. +

+ + + diff --git a/src/openal/docs/chp-buffers.sgml b/src/openal/docs/chp-buffers.sgml new file mode 100644 index 0000000..5a7cdff --- /dev/null +++ b/src/openal/docs/chp-buffers.sgml @@ -0,0 +1,691 @@ + + + + Buffers + + A Buffer encapsulates &AL; state related to storing sample data. The + application can request and + release Buffer objects, and fill them with data. Data can be supplied + compressed and encoded as long as the format is supported. + Buffers can, internally, contain waveform data as uncompressed or + compressed samples. + + + Unlike Sources and Listener, Buffer Objects can be shared among AL contexts. + Buffers are referenced by Sources. + A single Buffer can be referred to by multiple Sources. This separation allows + drivers and hardware to optimize storage and processing where applicable. + + + The simplest supported format for buffer data is PCM. + + + + + Annotation/ Compressed Buffers + Compressed formats are in no way guaranteed by the implementation + to remain compressed. The driver might have to uncompres in memory + at once, if no hardware-assisted or incremental decoding is possible. + In many cases an implementation has to decompress the buffer, + converting the uncompressed data to a canonical internal format, + and resample it into the format native to the current context. + + ]]> + + RFC: Compressed Buffers + MikeV suggests: an application can query the amount of memory buffer + is consuming. He suggests using GetBufferi(AL_SIZE, ... ). This seems + a bad idea as (a) the application is not meant to micromanage + driver-internal memory, (b) the memory requirements known to the + application might differ from the actual, (c) there are OS + mechanisms to query free memory, (d) AL_SIZE is now ambiguous as + it announces app-side memory as allocated vs. driver side memory + as used by the driver. For clarity AL_INTERNAL_SIZE (analog to + internal format enums) might be a better choice. + + ]]> + + + Buffers can be non-streaming (default) or streaming. Non-streaming + buffers are used to store looping and non-looping (single-shot) sound + data. Streaming buffers have to be used to cache streaming + media that the application can not keep in memory for technical + reasons: e.g. data delivered in real time over network. Streaming buffers + can also be used to partially store sound samples that the application + coder consider too large to keep in memory at once. + + ]]> + + + + + + Buffer States + + At this time, Buffer states are defined for purposes of discussion. + The states described in this section are not exposed through the API + (can not be queried, or be set directly), and the state description + used in the implementation might differ from this. + + + A Buffer is considered to be in one of the following States, with respect + to all Sources: + + + + UNUSED: the Buffer is not included in any queue + for any Source. In particular, the + Buffer is neither pending nor current + for any Source. The Buffer name can be + deleted at this time. + + + + + + PROCESSED: the Buffer is listed in the queue of + at least one Source, but is neither pending + nor current for any Source. The Buffer can + be deleted as soon as it has been unqueued + for all Sources it is queued with. + + + + + PENDING: there is at least one Source for which the + Buffer has been queued, for which the Buffer + data has not yet been dereferenced. The Buffer + can only be unqueued for those Sources that + have dereferenced the data in the Buffer + in its entirety, and cannot be deleted or + changed. + + + + The Buffer state is dependent on the state of all Sources + that is has been queued for. + A single queue occurrence of a Buffer propagates the + Buffer state (over all Sources) from UNUSED to PROCESSED + or higher. Sources that are STOPPED or INITIAL still have + queue entries that cause Buffers to be PROCESSED. + + + A single queue entry + with a single Source for which the Buffer is not yet + PROCESSED propagates the buffer's queueing state to + PENDING. + + + Buffers that are PROCESSED for a given Source can be + unqueued from that Source's queue. Buffers that have + been unqueued from all Sources are UNUSED. + Buffers that are UNUSED can be deleted, or changed by + BufferData commands. + + + Annotation (No CURRENT State) + For buffer queueing, it is not relevant whether + the Buffer data is currently dereferenced by any + Source or not. It is therefore not necessary to + distinguish a CURRENT state (being referenced as + current buffer by a single PLAYING or PAUSED Source). + + ]]> + + Annotation (State Query and Shared Buffers) + A buffer that is unused by one Source might be used + by another. The Unqueue operation is determined by + the number of queue entries already processed by the + given Source. + However, the application has to check whether the + Buffer is still in use by other Sources. For now, + applications have to maintain their own lists of + buffer consumer (source) lists. If necessary, an + explicit call to determine current buffer state + with respect to all Sources might be added in + future revisions. + + ]]> + RFC: IsBufferProcessed? + Instead of exposing the internal state, a simple boolean query + whether a buffer can be deleted or refilled can be used. + + ]]> + + RFC: BufferData on QUEUED + The error on using BufferData in QUEUED buffers is introduced + because the implementation might not be able to guarantee when + the Buffer is dereferenced. Applications have to account for the + possibility that the Buffer is dereferenced at the latest possible + moment, e.g. when it becomes CURRENT. As it is easier to relax + this restricition at a later time (no effect on backwards + compatibility) than doing the reverse, we are conserative here. + + ]]> + + RFC: Buffer State Query + Buffer State could be queried using alBuffer(), but it can't be + set. Prohibiting deferred deletion of buffers would make such a + state query desirable. + + ]]> + + + + + Managing Buffer Names + + &AL; provides calls to obtain Buffer names, to request + deletion of a Buffer object associated with a valid Buffer name, + and to validate a Buffer name. Calls to control Buffer attributes + are also provided. + + + + Requesting Buffers Names + + The application requests a number of Buffers using GenBuffers. + + void GenBuffers + &sizei; n + &uint;* bufferNames + + + + RFC: Buffer Name 0 + NONE or 0 is a reserved buffer name. What properties does + this buffer have? Does it have content? If there is no content, + what is its duration? 0? 1 microsecond? Should we use this buffer + to schedule a limited duration "silence"? + + ]]> + + + + Releasing Buffer Names + + The application requests deletion of a number of Buffers + by calling DeleteBuffers. + + + Once deleted, Names are no longer valid for use with AL + function calls. Any such use will cause an INVALID_NAME + error. The implementation is free to defer actual + release of resources. + + &void; DeleteBuffers + &sizei; n + &uint;* bufferNames + + IsBuffer(bname) can be used to verify deletion of a buffer. + Deleting bufferName 0 is a legal NOP in both scalar and + vector forms of the command. The same is true for unused + buffer names, e.g. such as not allocated yet, or as + released already. + + + RFC: Force Deletion + If a buffer name is deleted, we could replace all occurences + in queues with bname 0. This is the GL behavior for deleting + the texture currently bound. + + ]]> + RFC: Relasing used Buffers + If a Buffer is USED or QUEUED, it cannot be deleted, and the operation + should fail. We have three possible responses: throw an error, deferr + deletion, or force deletion by replacing every use + of the buffer in question with bname zero. + Throwing an error requires that we lock, verify that all specified + buffers can be deleted, then perform deletion, then unlock. If there + is one buffer that can not be deleted we have to throw an error and + make the entire operation a NOP. + Deferred deletion has its own set of problems (see other RFC). + Forcing deletion makes the mistake obvious to the application + for current buffers (sound artifacts) but still doesn't expose + errors for queued buffers. It also requires complete consumer + book-keeping for each buffer. GL uses this approach for textures + at little expense because it only has one current texture. + + ]]> + + RFC: Deferred Buffer Release + Buffer deletion could be performed as a deferred operation. + In this case actual deletion would be deferred until a Buffer is + unused, i.e. not QUEUED or CURRENT anymore. The specification + would not guarantee that they are deleted as soon as possible. + + However, such a deferred execution would be muddying the borders + between immediate and deferred execution in general (as we + might want to add scheduling and deferred commands at a later time). + Introduced as the default it makes impossible for the application + to force deletion or errors. Errors caused by improper use of + &AL; will be triggered at some distance from the original mistaken + command. Debugging such conditions is usually expensive. This approach + also does not take into account sharing of buffers among contexts. + + It might be possible to introduce this behavior as a Hint() + in case that it is not desirable to explicitely introduce + deferred commands. + + ]]> + + RFC: sourceName 0 + Is there a useful application for this? Do we mark that this + is reserved? + + ]]> + + + + Validating a Buffer Name + + The application can verify whether a buffer Name is valid + using the IsBuffer query. + + &bool; IsBuffer + &uint; bufferName + + + + + + + + Manipulating Buffer Attributes + + + Buffer Attributes + + This section lists the attributes that can be set, or + queried, per Buffer. Note that some of these attributes + can not be set using the Buffer commands, but are set + using commands like BufferData. + + + Querying the attributes of a Buffer with a buffer name that + is not valid throws an INVALID_OPERATION. Passing in an + attribute name that is invalid throws an INVALID_VALUE error. + + + + + Buffer FREQUENCY Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + FREQUENCY + float + none + (0, any] + + + +
+ Description: Frequency, specified in samples per second, + i.e. units of Hertz [Hz]. + Query by GetBuffer. The frequency state of a buffer is set by + BufferData calls. +
+ + Annotation (No Frequency enumeration) + As the implementation has to support conversion from + one frequency to another to implement pitch, it is + feasible to offer support for arbitrary sample + frequencies, instead of restricting the application + to an enumeration of supported sample frequencies. + Another reason not to limit frequency to an enumerated + set is that future hardware might support variable + frequencies as well (it might be preferable to choose + the sampling frequency according to the PSD of the + signal then). + + However, it is desirable to avoid conversions due + to differences between the sample frequency used in + the original data, the frequency supported during the + mixing, and the frequency expected by the output device. + + ]]> + + + Annotation (Implied Frequency) + To account for the possibility of future AL implementations + supporting encoding formats for the application might + not want, or be able, to retrieve the actual frequency + from the encoded sample, the specification will be + amended to guarantee the following behavior: If a nonzero + frequency is specified, it will force a conversion from + the actual to the requested frequency. If the application + specifies a 0 frequency, AL will use the actual frequency. + If there is no frequency information implied by the format + or contained in the encoded data, specifying a 0 frequency + will yield INVALID_VALUE. It is recommended that applications + use NONE instead of the literal value. + + ]]> + + RFC: BITS not needed + This is not a setter. As a state query it doesn't provide useful + information about the internal canonical format (which could be + queried independent of the buffer). + + + + Buffer BITS Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + BITS + ui + 8,16 + 0 + + + +
+ Description: + Bits per sample. This is a query-only attribute. The + default value for an (empty) buffer is zero. +
+ ]]> + + + Annotation (No Format query) + As of this time there is no query for FORMAT, or format + related state information. Query of the channels or + bits of a given buffer make little sense if the query + the internal (canonical, not buffer specific) format. + Query of the original sample data format makes little + sense unless the implementation is obliged to preserve + the original data. + + ]]> + + RFC: CHANNELS needed? + No setter. Does this indicate the channels in the original data, + or those in the (canonical) format internally used? Redundant to + querying the buffer type. Should be enums as speaker configurations + might have to be destribed by two integers: 5.1. + + + + Buffer CHANNELS Attribute + + + + &Par; + &Sig; + &Val; + &Def; + + + + + CHANNELS + ui + RFC: enums or N/A? + 0 + + + +
+ Description: Channels that buffer stores. Query only + attribute. This is almost + always 1, as applications using spatialized sound always + downsample to mono. This depends on the purpose of the + buffer: buffers used for spatialization have to provide + single-channel data. The default value for an (empty) + buffer is zero. +
+ ]]> + + + + Buffer SIZE Attribute + + + + &Par; + &Sig; + &Val; + &Def; + + + + + SIZE + &sizei; + [0, MAX_UINT] + 0 + + + +
+ Description: Size in bytes of the buffer data. Query through + GetBuffer, can be set only using BufferData calls. + Setting a SIZE of 0 is a legal NOP. The number of bytes does + not necessarily equal the number of samples (e.g. for compressed data). +
+ + + RFC: buffer overflow/underflow + If a SIZE is specified for a buffer and an attempt is made to + write less or more data to the buffer, is this an error? Do we + have SubData updates? Is trying to write data to a zero size buffer + an error? Which error? + + ]]> + + RFC: query for samples/duration? + Do we need a query for samples (which does not equal memory size)? + Do we prefer a duration query? As integral, for precision? Which, + combined with frequency, has to guarantee accurate sample number? + + ]]> + + + RFC: memory budgeting + SIZE comment said: Useful for memory budgeting with compressed data. + Sounds bogus: the application can only announce how many bytes + of data it intends to provide, it might not be able to estimate + the uncompressed, decoded size in the internal format chosen by + the implementation w/o decompressing, decoding, and querying for + the internal format. Micromanaging memory is usually a bad idea. + Using SIZE to configure a buffer might ge a mistake. Using the + same enum to query the actual size internally (which might + consists of two or more buffer areas and cached decoding state) + will be confusing. + + ]]> + + RFC: buffer initial state + MikeV added: + "The default attribute values for a Buffer are nonsensical insofar + as a Buffer is incomplete without data having been + specified." + This seems wrong. An AL object will always be complete and valid, + albeit useless. A Buffer in its default state might not produce + any useful outout, but it can be specified and used. + + ]]> +
+ + + + Querying Buffer Attributes + + Buffer state is maintained inside the &AL; implementation and can be + queried in full. The valid values for paramName are identical to the + ones for Buffer*. + + void GetBuffer{n}{sifd}{v} + &uint; bufferName + &enum; paramName + &type;* values + + + + + + Specifying Buffer Content + + A special case of Buffer state is the actual sound sample data stored + in asociation with the Buffer. Applications can specify sample data using + BufferData. + + &void; BufferData + &uint; bufferName + &enum; format + &void;* data + &sizei; size + &sizei; frequency + + The data specified is copied to an internal software, or if possible, + hardware buffer. The implementation is free to apply decompression, + conversion, resampling, and filtering as needed. The internal format + of the Buffer is not exposed to the application, and not + accessible. Valid formats are FORMAT_MONO8, FORMAT_MONO16, + FORMAT_STEREO8, and FORMAT_STEREO16. An implementation may + expose other formats, see the chapter on Extensions for + information on determining if additional formats are supported. + + + + Applications should always check for an error condition after attempting + to specify buffer data in case an implementation has to generate an + OUT_OF_MEMORY or conversion related INVALID_VALUE error. The application + is free to reuse the memory specified by the data pointer once the + call to BufferData returns. The implementation has to dereference, + e.g. copy, the data during BufferData execution. + + + RFC: format enums + With the possible exception of sample frequency, all + details of a sample (mono/stero, bit resolution, channels, + encoding,compression) should be specified in a format parameter. + In other words, I opt for an enumeration of formats. GL has a + quite large number of those w/o suffering damage. Allowing + parameters the way we do increases error cases and/or + conversion load. The space is combinatorial to begin with, + but an enumeration of valid formats seems better to impose + restrictions. Using enums helps dealing with formats + opaque to the application (compressed data with compressed + header) where sample size and sampling frequency might not + be available. + + There is the related issue of internal formats. A buffer used + for spatialization will have to convert to mono. A buffer used + to pass through to the output hardware will have to + remap an n-channel format to an m-channel format (or let the + hardware do it) including crosstalk handling depending on + the output device (headphones vs. speaker). To prevent that + every buffer has to do both AL needs to know the purpose of a + buffer when dereferencing the data. + + ]]> + + RFC: Frequency woes + Frequency as a uint rises precision issues. + Frequency might not be known for compressed data, we might need + a (yuck) wildcard frequency specifier? + We have a redundancy: frequency per context (mixing quality + desired), frequency internally used by the implementation when + storing the buffers, frequency provided in the data. We need + to specify the latter in some cases and can't in others. Format + enum or frequency enum again. + + ]]> + + + + RFC: data mover mess + API to copy data from output buffer to a buffer? + BufferWriteData to supersede BufferData, BufferReadData + as later extensions for buffer readback for those who want it? + Reading the output stream reverses the problems we have + with appendData: the application provides a memory buffer + into which AL copies as much data as available/as fits. + + ]]> + + + + RFC: expose internal format + Implementations are free to use whatever internal data format is best + suited for their hardware/software implementation, leaving the actual + sample format and structure opaque to the application. Should applications + be able to influence this? Through context creation? Per Buffer? + Do we use Lowest Common Denominator or highest possible quality as a + default? + + ]]> + + RFC: memory management with compressed data + If we allow for mixing from compressed data (perfectly reasonable + for hardware) then it seems even more unlikely the application could + estimate memory usage. + If a compressed format is supported by AL, do we require support for mixing from + compressed data? I daresay not - some formats might not allow for + cheap incremental decompression. + + ]]> + + RFC: conversion and sample retrieval + To retrieve a sample, the size has to be queried first for allocating a + properly sized memory segment as destination. This is dependent on the + format. Conversion can be implemented as creating a buffer, and then + requesting the data in a different format. Is this desirable? Even if + we restrict reading from buffers to the same format they were written + to, conversion from the internal format might be inevitable. Querying + and setting the internal format however might be desirable for certain + purposes. + + ]]> + + +
+
+ diff --git a/src/openal/docs/chp-introduction.sgml b/src/openal/docs/chp-introduction.sgml new file mode 100644 index 0000000..c948553 --- /dev/null +++ b/src/openal/docs/chp-introduction.sgml @@ -0,0 +1,304 @@ + + + Introduction + + + CVS Document: $Id: chp-introduction.sgml,v 1.3 2000/11/10 21:23:52 bk Exp $ + CVS Revision: $Revision: 1.3 $ + + $Log: chp-introduction.sgml,v $ + Revision 1.3 2000/11/10 21:23:52 bk + Use ../CREDITS for list of contributors. + + Revision 1.2 2000/10/26 02:58:55 bk + Cleanup (typos). + + Revision 1.1 2000/10/11 18:44:46 bk + Document split into separate files. Minor fixes. + + ]]> + +
+ Formatting and Conventions + + This API Specification and Reference uses a style that is a blend of the + OpenGL v1.2 specification and the OpenGL Programming Guide, 2nd ed. + Conventions: 'T' is used to designate a type for those functions which + exist in multiple signatures for different types. 'Object' is used + to designate a target Object for those functions which exist in multiple + versions for different Object categories. The 'al' and 'AL_' prefix + is omitted throughout the document. + + + Annotation (Terminology) + "State" refers to state within the context of the &OAL; + state machine description of the &OAL; implementation. + "Objects" refer to &OAL; primitives. + "Attribute" refers to attributes of &OAL; Objects. + Attributes of a &OAL; Objects are one part of the + &OAL; state. Some attributes are not specific to + single objects, but apply to the entire context. + "Parameter" is used for function arguments that might + or might not be attributes, in particular for + command arguments that are not stored as state. + The use of "Property" is to be avoided. + + ]]> + + + + + + 1.8/1.7./1.6/1.5 + September-August 2000 + bk + Final Draft for Public Review + + + + 1.4 + June 2000 + bk + First Draft for Public Review + + + + 1.2 + March 2000 + mkv + Draft released for GDC + + + + TODO + + - work thru past changes + - add GH section + - add rewrite of GH section + + - add section on rfc/ann id's + - add section on procedure + - add proper id's to rfc/ann/sections etc. + + + + ]]> + +
+ +
+ What is the &OAL; Audio System? + + &OAL; (for "Open Audio Library") is a software interface to audio hardware. + The interface consists of a number of functions that allow a programmer + to specify the objects and operations in producing high-quality audio + output, specifically multichannel output of 3D arrangements of sound + sources around a listener. + + + The &OAL; API is designed to be cross-platform and easy to use. + It resembles the &OGL; API in coding style and conventions. &OAL; uses a + syntax resembling that of &OGL; where applicable. + + + &OAL; is foremost a means to generate audio in a simulated three-dimensional + space. Consequently, legacy audio concepts such as panning and left/right + channels are not directly supported. &OAL; does include extensions compatible + with the IA-SIG 3D Level 1 and Level 2 rendering guidelines to handle + sound-source directivity and distance-related attenuation and Doppler effects, + as well as environmental effects such as reflection, obstruction, transmission, + reverberation. + + + + Like &OGL;, the &OAL; core API has no notion of an explicit rendering context, + and operates on an implied current &OAL; Context. Unlike the &OGL; + specification the &OAL; specification includes both the core API (the + actual &OAL; API) and the operating system bindings + of the ALC API (the "Audio Library Context"). Unlike &OGL;'s GLX, WGL + and other OS-specific bindings, the ALC API is portable across platforms + as well. + +
+ +
+ Programmer's View of &OAL; + + To the programmer, &OAL; is a set of commands that allow the + specification of sound sources and a listener in three + dimensions, combined with commands that control how these + sound sources are rendered into the output buffer. The + effect of &OAL; commands is not guaranteed to be immediate, + as there are latencies depending on the implementation, + but ideally such latency should not be noticeable to the + user. + + + A typical program that uses &OAL; begins with calls to + open a sound device which is used to process output and + play it on attached hardware (e.g. speakers or headphones). + Then, calls are made to allocate an AL context and + associate it with the device. Once an AL context is + allocated, the programmer is free to issue AL commands. + Some calls are used to render Sources (point and directional + Sources, looping or not), while others affect the rendering + of these Sources including how they are attenuated by + distance and relative orientation. + + + Annotation (&OAL; and &OGL; use) + Often, &OAL; will be used to render a 3D audio environment + matched by a 3D visual scenery. For this purpose, &OAL; is + meant to be a seamlessly integrating complement to &OGL;. + &OAL; state can be updated in sync with the &OGL; or video + updates (synchronized), or in timesteps independent of the + graphics framerate. Audio rendering loops usually update + the current locations of the sources and the listener, updates + global settings, and manages buffers. + + ]]> +
+ + +
+ Implementor's View of &OAL; + + To the implementor, &OAL; is a set of commands that affect + the operation of CPU and sound hardware. If the hardware + consists only of an addressable output buffer, then &OAL; must + be implemented almost entirely on the host CPU. In some cases + audio hardware provides DSP-based and other acceleration in + various degress. The &OAL; implementors task is to provide + the CPU software interface while dividing the work for each + AL command between the CPU and the audio hardware. This + division should be tailored to the available audio hardware + to obtain optimum performance in carrying out AL calls. + + + &OAL; maintains a considerable amount of state information. + This state controls how the Sources are rendered into the + output buffer. Some of this state is directly available to + the user: he or she can make calls to obtain its value. + Some of it, however, is visible only by the effect it has + on what is rendered. One of the main goals of this + specification is to make &OAL; state information explicit, + to eludicate how it changes, and to indicate what its + effects are. + + + Annotation (Native audio APIs) + Implementors can choose to implement &OAL; on top + of an existing native audio API. + + ]]> + +
+ + +
+ Our View + + We view &OAL; as a state machine that controls a multichannel + processing system to synthesize a digital stream, passing sample + data through a chain of parametrized digital audio signal + processing operations. This model should engender a specification + that satisfies the needs of both programmers and implementors. + It does not, however, necessarily + provide a model for implementation. Any conformant implementation + must produce results conforming to those produced by the specified + methods, but there may be ways to carry out a particular computation + that are more efficient than the one specified. + + +
+ + +
+ Requirements, Conformance and Extensions + + The specification has to guarantee a minimum number of resources. + However, implementations are encouraged to compete on performance, + available resources, and output quality. + + + RFC: suggested requirements + These have been taken from earlier specs drafts, suggested by + Creative: + A minimum of sixteen (16) Sources per Context should always be available. + The number and size of Buffers available is limited only by the amount + of memory available and/or accessible by the implementation. + The specification could also list requirements by the implementation: + A minimum storage space of half a megabyte (512kB) should always be available. + + ]]> + + RFC: no requirements + I3DL1-like requirements might be so arbitrary that they are useless. + The dangers here are cap bits, and a subtle aliasing of source and + hardware buffers. AL implementations should implement as much quality + and performance as possible for any given number of sources/ + Application request resources when creating a context and can use Hints + to indicate desired trade-offs. + + ]]> + + + + There will be an &OAL; set of conformance tests available along + with the open source sample implementation. Vendors and individuals + are encouraged to specify and implement extensions to &OAL; in + the same way &OGL; is extensible. Successful extensions will + become part of the core specification as necessary and desirable. + &OAL; implementations have to guarantee backwards compatibility + and ABI compatibility for minor revisions. + + + The current sample implementation and documentation for &OAL; can be + obtained from + openal.org. + &OAL; is also available from the the + + &OAL; CVS repository. For more information on how to get + &OAL; from CVS also see + &coLoki; CVS. + +
+ + + +
+ Architecture Review and Acknowledgements + + Like &OGL;, &OAL; is meant to evolve through a joined effort of + implementators and application programmers meeting in regular + sessions of an Architecture Review Board (ARB). As of this time + the ARB has not yet been set up. Currently, the two companies + committed to implementing &OAL; drivers have appointed two + contacts responsible for preparing the specification draft. + + + Consequently &OAL; is a cooperative effort, one in a sequence of + earlier attempts to create a cross-platform audio API. The current + authors/editors have assembled this draft of the specification, + but many have, directly and indirectly, contributed to the content + of the actual document. The following list (in all likelihood + incomplete) gives in alphabetical order participants in the discussion + and contributors to the specification processs and related efforts: + + &CREDITS; + + + +
+
+ + \ No newline at end of file diff --git a/src/openal/docs/chp-multichannel.sgml b/src/openal/docs/chp-multichannel.sgml new file mode 100644 index 0000000..425ec06 --- /dev/null +++ b/src/openal/docs/chp-multichannel.sgml @@ -0,0 +1,261 @@ + + + Handling Multichannel Data + + &AL; is foremost an API to control processing of spatialized sound. For that + reason, the internal, canonical format of data stored in buffers is mono. + The specification does not require the implementation to preserve the + original data, thus multichannel (e.g. stereo) data is usually downsampled + using an algorithm of the implementor's choosing. Implementations are free + to defer decompression, decoding, and conversion until the latest possible + moment, but they are not required to do so by the specification. + + + However, &AL; is an audio API, and consequently has to provide means to + pass through preprocessed multichannel data to the sound driver and + hardware, which in turn will map it to the actual output channels. This + processing might involve mapping from n channels in the data to m channels + in the output, as well as filtering to account for varying requirements + for a given setup. For example, headphone and speaker setups differ with + respect to handling of crosstalk and noise. + + + For this reason, mono buffers can not be used to store data meant for + direct multichannel output. The API has to provide means by which the + application can signal its intention to use a given buffer in this way, + causing the implementation to either preserve the original data, or + convert it as needed for the direct output to the given speaker or + headphone configuration. In many cases, multichannel buffers could be + used with Sources, but the specification does not endorse this at this + time as this might not be guaranteed for all multichannel data formats + &AL; will support. + + + While spatialized sound is often played once, or looping a limited number + of times, multichannel data is usually streaming: typically stereo or MP3 + music, soundtracks, maybe voice data accompanying animation and cinematic + sequences. The queueing mechanism defined for mono buffers and spatialized + Sources is thus also applied to multichannel buffers and passthrough + output. + + + Applications can expect &AL; implementations to support more than one + multichannel output at once (e.g. mixing two stereo streams, or fading + from one to the other for transition). For that reason we can not restrict + multichannel output to just one stream or buffer at a time. + + + Annotation (per-case Buffer use) + The specification will likely be amended at a later time to + allow for the use of mono buffers with multichannel output, + and multichannel buffers with spatialized Sources. + The decision to use the same buffer API for mono + and multichannel buffers was taken in anticipation of this. + In cases where the specification can not guarantee + use of a given multichannel data encoding with a + spatialized source, an error can be generated at the + attempted use, validating the buffer's internal format + against the output chosen. In cases were the specification + can not guarantee the implementation will defer the + conversion of multichannel buffer content as needed (e.g. + PCM stereo data), the application can either specify the + internal format, or the internal format enumeration + can be extended to explicitely request storage of both + multichannel and mono data within the same buffer, at memory + expense. + + ]]> + + +
+ Specifying Buffer Content and Internal Format + + The existing BufferData command does not permit the application + to specify an internal format. The implementation is free to + apply conversions, downsampling, upsampling, including + interpolation and filtering, in accordance with the specification. + + + However, applications might want to use different trade-offs + between quality and resource expenditure. More imortantly, + applications might choose to use preprocessed, multichannel + data instead of doing runtime spatialization, e.g. for + recorded music, voice, and realtime streams. In this case + the application expects efficient passthrough of the data + to the hardware. + + + To account for this requirement, an extended command to specify + sample data to be stored in a given buffer is introduced. + + &void; BufferWriteData + &uint; bufferName + &enum; format + &void;* data + &sizei; size + &uint; frequency + &enum; internalFormat + + The internal format of the Buffer can be requested by the + application. The implementation is not required to match + the request excatly. The specification does guarantee that + use of a multichannel internalFormat parameter will cause + the implementation to treat the data as multichannel data. + The implementation is free to perform any remapping from + the number of channels used in the data to the number of + channels available at the output, and apply other prcessing + as needed. + + + Valid formats for internalFormat are FORMAT_MONO8, FORMAT_MONO16, + FORMAT_STEREO8, and FORMAT_STEREO16. An implementation may + expose other formats, see the chapter on Extensions for + information on determining if additional formats are supported. + In general, the set of valid internalFormat parameters will be + a subset of those available as format parameters. + + + Annotation (atomic WriteData) + The internal format for a buffer can not be specified + by using Bufferi. The modular way to set state + is preferable for operations that are always, or + nearly legal and orthogonal, but specifying the + internal format affect allocation and use of a buffer, + and would cause too many error cases. + + ]]> + + Annotation (change buffer format on write) + The API allows for changing a buffer on every write operation, + turning a multichannel buffer into a mono buffer and vice versa. + The specification does not require the implementation to handle + these operations efficiently, as they might require reallocation + of memory. The same is true for resize operations at write. + Applications might prefer to release buffer names that do not + match current requirements, and request new buffer names instead, + an operation which implementations are encouraged to optimize. + + ]]> + + Annotation (BufferReadData) + The specification might be amended at a later time + with a command to retrieve the data from a buffer, + following use of alGetBuffer to retrieve size and + internalFormat. Reading has to be queued for + continuous reads from the actual mixing buffer. + + ]]> + RFC: frequency for write/read? + Do we (ever) want to provide the application control over the internal frequency? + + ]]> +
+ + +
+ Rendering Multichannel Buffers + + For a variety of reasons, Sources are not used to feed buffer + data into multichannel processing. Instead, a different class + of &AL; object is introduced to encapsulate state management + and resource management for multichannel output: Multichannel + objects. + + + Multichannel objects are referred to by name, and they + are managed by an API partly duplicating the Source API: + GenMultichannels, IsMultichannel, DeleteMultichannels, + Multichanneli and GetMultichanneli. However, the + SourcePlay/Stop commands as well as the Queue/Unqueue + commands can be applied to both: these commands accept + mcName parameters as well as sName parameters. It is + left to the implementation how a Name is mapped to an + object, and how the implementation dinstiguishes sNames + from mcNames. Within a 32bit namespace, and given the + small number of multichannel sources to expect (more than + one is quite possible, but not common), the implementation + can exploit the fact that Names are opaque to the + application. + + + Annotation (Source NONE) + The literal value "0", i.e. NONE, is legal. All + operations on Source/Multichannel NONE are legal NOPs + and quietly ignored. Because of the ambiguity + (is NONE a Source or Multichannel) NONE will never + be available as an actual object, nor will operations + on it ever have effects. + + ]]> + + Annotation (implicit ambient) + One way to feed multichannel data into the rendering is + using the Listener as a Source (sname==0 implies + Listener). This was rejected, as it + does not allow for multiple streams. Using multiple + Sources with appropriate type SOURCE_AMBIENT or + SOURCE_MULTICHHANEL was rejected, as Sources + are by definition spatialized, and support operations + which are meaningless or even illegal for an output + that is not spatialized at all. Modifying the semantics + of Source spatialization by a provision that a relative + position of (0,0,0) would mark a Source as ambient was + rejected for clarity and cleanness. Updates of Source + position can not be expected to convert Sources from + spatialized to ambient and vice versa at any time, + a Source State dependent semantics of position updates + is outright confusing, handling of sources that are + SOURCE_ABSOLUTE but incidentally at listener position + can not be different from that of SOURCE_RELATIVE with + a zero offset. Furthermore, multichannel data is not + necessarily ambient (surround can rotate, while ambient + attempts to prevent any perception of direction). + Finally, multichannel processing might require per-channel + gain adjustment at user request, which is a meaningless + concept for Sources. + + ]]> + + + Annotation (no direct mixing) + Direct mixing into the rendering buffer, allowing the + application to superimpose its multichannel data + (or custom software mixing results) on the audio stream + generated by spatialization was rejected, as it suffers + from similar problems as BufferAppendData and loop points, + namely the need to operate at sample level on buffers of + unknown format that might not even be directly accessible. + The same rationale that led to the adoption of maintaining + a processing queue for Sources applies to multichannel data. + + ]]> + + Annotation (no combined Buffer/Passthrough) + Multichannel buffers cannot serve as multichannel output + objects for two reasons: it would complicate the buffer + API with operations only applicable to a subset of buffers, + and it does not allow for queueing. + + ]]> + + RFC: Per-Channel access + At a later time we might want to amend the specification to + gain access to a single channel within a multichannel buffer. + I suggest that any such operation should be restricted to the + ability of reading samples from just one channel into application + memory, which allows for writing it into a mono buffer subsequently. + + ]]> + +
+
\ No newline at end of file diff --git a/src/openal/docs/chp-operation.sgml b/src/openal/docs/chp-operation.sgml new file mode 100644 index 0000000..78fa87b --- /dev/null +++ b/src/openal/docs/chp-operation.sgml @@ -0,0 +1,977 @@ + + + &OAL; Operation + + + &OAL; Fundamentals + + &OAL; (henceforth, the "&AL;") is concerned only with rendering audio + into an output buffer, + and primarily meant for spatialized audio. + There is no support for reading audio input from buffers at this + time, and no support for MIDI and other components usually + associated with audio hardware. Programmers must relay on other + mechanisms to obtain audio (e.g. voice) input or generate music. + + + The &AL; has three fundamental primitives or objects -- Buffers, Sources, + and a single Listener. Each object can be changed independently, + the setting of one object does not affect the setting of others. + The application can also set modes that affect processing. Modes + are set, objects specified, and other &AL; operations performed + by sending commands in the form of function or procedure calls. + + Sources store locations, directions, and other attributes of an object in 3D + space and have a buffer associated with them for playback. There are + normally far more sources defined than buffers. When the program wants to play + a sound, it controls execution through a source object. Sources are + processed independently from each other. + + Buffers store compressed or un-compressed audio data. It is common to + initialize a large set of buffers when the program first starts (or at + non-critical times during execution -- between levels in a game, for instance). + Buffers are referred to by Sources. Data (audio sample data) is associated + with buffers. + + There is only one listener (per audio context). The listener attributes are + similar to source attributes, but are used to represent where the user is + hearing the audio from. The influence of all the sources from the + perspective of the listener is mixed and played for the user. + + + RFC: Data Binding + Have to specifiy when pointer arguments are dereferenced. + + ]]> + + Primitive Types + + As &AL; is meant to allow for seamless integration with &OGL; code + if needed, the &AL; primitive (scalar) data types mimic the + &OGL; data types. Guaranteed minimum sizes are stated for &OGL; + data types (see table 2.2 of the &OGL; 1.2 Specification), but + the actual choice of C datatype is left to the implementation. + All implementations on a given binary architecture, however, must + use a common definition of these datatypes. + + + RFC/000507: + ALlong/ALulong are omitted from the Linux OpenGL Base ABI, + and the GL specification. Do we want to go ahead on this, + or trail GL? Do we include non-i386 architectures to list + sizes explicitely. I.e. do we make the ABI part of our + mandate? + + ]]> + + + Note that this table uses explicit AL prefixes for clarity, + while they might be omitted from the rest of the document + for brevity. GCC equivalents are given for IA32, i.e. a + portable and widely available compiler on the most common + target architecture. + + &AL; Primitive Data Types + + + + + + AL Type + Description + GL Type + GCC IA32 + + + + + ALboolean + 8-bit boolean + GLboolean + unsigned char + + + ALbyte + signed 8-bit 2's-complement integer + GLbyte + signed char + + + ALubyte + unsigned 8-bit integer + GLubyte + unsigned char + + + ALshort + signed 16-bit 2's-complement integer + GLshort + short + + ALushort + unsigned 16-bit integer + GLushort + unsigned short + + + ALint + signed 32-bit 2's-complement integer + GLint + int + + + ALuint + unsigned 32-bit integer + GLuint + unsigned int + + + ALlong + signed 64-bit 2's-complement integer + n/a + long long + + + ALulong + unsigned 64-bit integer + n/a + unsigned long long + + ]]> + + ALsizei + non-negative 32-bit binary integer size + GLsizei + int + + + ALenum + enumerated 32-bit value + GLenum + unsigned int + + + ALbitfield + 32 bit bitfield + GLbitfield + unsigned int + + + ALfloat + 32-bit IEEE754 floating-point + GLfloat + float + + + ALclampf + Same as ALfloat, but in range [0, 1] + GLclampf + float + + + ALdouble + 64-bit IEEE754 floating-point + GLdouble + double + + + ALclampd + Same as ALdouble, but in range [0, 1] + GLclampd + double + + + +
+
+ + + + Annotation on Type Sizes + It would be desirable to guarantee the bit size of &AL; data + types, but this might affect the mapping to &OGL; types + for which the &OGL; specification only guarantees a minimum + size. + + Annotation on 64bit integral + It would be desirable to define ulong and long, but again + we defer to &OGL; in this decision. + + Annotation on Enumeration + &enum; is not a C or C++ enumeration, but implemented as + C preprocesor defines. This makes it easier to handle + extensions to the &AL; namespace, in particular in + dealing with delays in distributing updated reference + headers. + + ]]> +
+ + Floating-Point Computation + + Any representable floating-point value is legal as input + to a &AL; command that requires floating point data. + The result of providing a value that is not a floating + point number to such a command is unspecified, but must not + lead to &AL; interruption or termination. In IEEE arithmetic, + for example, providing a negative zero or a denormalized + number to a GL command yields predictable results, while + providing an NaN or infinity yields unspecified results. + + Some calculations require division. In such cases (including + implied divisions required by vector normalizations), a + division by zero produces an unspecified result but must + not lead to GL interruption or termination. + + +
+ + + AL State + + The &AL; maintains considerable state. This documents enumerates + each state variable and describes how each variable can be + changed. For purposes of discussion, state variables are + categorized somewhat arbitrarily by their function. For example, + although we describe operations that the &AL; performs on the + implied output buffer, the outbut buffer is not part of the + &AL; state. Certain states of &AL; objects (e.g. buffer states + with respect to queueing) are introduced for discussion purposes, + but not exposed through the API. + + + + + AL Command Syntax + + &AL; commands are functions or procedures. Various groups of + commands perform the same operation but differ in how + arguments are supplied to them. To conveniently accomodate + this variation, we adopt the &OGL; nnotation for describing + commands and their arguments. + + + Annotation (Not all types supported yet) + At this time &AL; does not support the full flexibility that + &OGL; offers. Certain entry points are supported only for + some data types. In general, &AL; tends to use less entry + points, using setter commands that use the same tokens + as the matching query commands. + + ]]> + + + + + Basic AL Operation + + &AL; can be used for a variety of audio playback tasks, and is an + excellent complement to &OGL; for real-time rendering. A programmer who is + familiar with &OGL; will immediately notice the similarities between the + two APIs in that they describe their 3D environments using similar methods. + + + For an &OGL;/&AL; program, most of the audio programming will be in two + places in the code: initialization of the program, and the rendering loop. + An &OGL;/&AL; program will typically contain a section where the graphics and + audio systems are initialized, although it may be spread into multiple functions. + For OpenAL, initialization normally consists of creating a context, creating + the initial set of buffers, loading the buffers with sample data, creating + sources, attaching buffers to sources, setting locations and directions for + the listener and sources, and setting the initial values for state global + to &AL;. + + + + Initialization Example + + &sample.c; + + + + + + + Initialization Example + + &ExInitAL.c; + + + ]]> + + + The audio update within + the rendering loop normally consists of telling &AL; the current locations + of the sources and listener, updating the environment settings, and managing + buffers. + + + + Processing Loop + +// PlaceCamera -- places OpenGL camera and updates OpenAL listener position and source state +void 3DEnvironemnt:PlaceCamera() +{ + // update OpenGL camera position + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(-0.1333, 0.1333, -0.1, 0.1, 0.2, 50.0); + + gluLookAt(listenerPos[0], listenerPos[1], listenerPos[2], + (listenerPos[0] + sin(listenerAngle)), listenerPos[1], (listenerPos[2] - cos(listenerAngle)), + 0.0, 1.0, 0.0); + + // OpenAL stuff... + // place listener at camera + alListener3f(AL_POSITION, listenerPos[0], listenerPos[1], listenerPos[2]); + float directionvect[6]; + directionvect[0] = (float) sin(listenerAngle); + directionvect[1] = 0; + directionvect[2] = (float) cos(listenerAngle); + directionvect[3] = 0; + directionvect[4] = 1; + directionvect[5] = 0; + alListenerfv(AL_ORIENTATION, directionvect); + + // play phasor if in range, else stop playback + if (range() < 9) + { + alSourcePlay(source[1]); + } else + { + alSourceStop(source[1]); + } +} + + + ]]> + + + + + AL Errors + + The AL detects only a subset of those conditions that could be + considered errors. This is because in many cases error checking + would adversely impact the performance of an error-free program. + The command + + &enum; GetError + + + is used to obtain error information. Each detectable error is + assigned a numeric code. When an error is detected by AL, + a flag is set and the error code is recorded. Further errors, + if they occur, do not affect this recorded code. When GetError + is called, the code is returned and the flag is cleared, so that + a further error will again record its code. If a call to GetError + returns NO_ERROR then there has been no detectable error since + the last call to GetError (or since the AL was initialized). + + + RFC: GL distributed error + To allow for distributed implementations there may be several + flag/code pairs. In this case, after a call to GetError returns a + value other than NO_ERROR each subsequent call returns the + non-NO_ERROR code of another distinct flag-code pair (in + unspecified order), until all NO_ERROR codes have been returned. + When there are no more non-NO_ERROR codes, all flags be reset. + The initial state of all flags is cleared and the initial value + of all codes is NO_ERROR. + + Annotation (Looping GetError) + &AL; applications are advised to loop calls of GetError to + make sure that all flags are reset. Only the first error + occurence for each flag/code pair is recorded, subsequent + errors are ignored. The result of a repeated GetError call + is not a stack trace or LIFO sequence. All error handling + is context specific. + + + ]]> + + + Annotation (Only First Error) + Like &OGL; &AL; will ignore subsequent errors once an + error conditation has been encountered. + + ]]> + + + Error codes can be mapped to strings. The GetString function + returns a pointer to a constant (literal) string that is + identical to the identifier used for the enumeration value, + as defined in the specification. + + + Annotation/ Verbose Error String + There is no need to maintain a separate GetErrorString + function (inspired by the proposed gluGetErrorStrings) + as the existing GetString entry point can be used. + + ]]> + + + + + Error Conditions + + + + + + Name + Description + + + + + NO_ERROR + "No Error" token. + + + INVALID_NAME + Invalid Name parameter. + + + INVALID_ENUM + Invalid parameter. + + + INVALID_VALUE + Invalid enum parameter value. + + + INVALID_OPERATION + Illegal call. + + + OUT_OF_MEMORY + Unable to allocate memory. + + + +
+ The table summarizes the AL errors. Currently, when an error flag + is set, results of AL operations are undefined only if OUT_OF_MEMORY + has occured. In other cases, the command generating the error is + ignored so that it has no effect on AL state or output buffer + contents. If the error generating command returns a value, + it returns zero. If the generating command modifies values + through a pointer argument, no change is made to these values. + These error semantics apply only to AL errors, not to system errors + such as memory access errors. +
+ + Several error generation conditions are implicit in the description + of the various AL commands. First, if a command that requires + an enumerated value is passed a value that is not one of those + specified as allowable for that command, the error INVALID_ENUM + results. This is the case even if the argument is a pointer to + a symbolic constant if that value is not allowable for the given + command. + This will occur whether the value is allowable for other functions, + or an invalid integer value. + + + Integer parameters that are used as names for &AL; objects + such as Buffers and Sources are checked for validity. If an invalid + name parameter is specified in an &AL; command, an + INVALID_NAME error will be generated, and the command is ignored. + + + If a negative integer is provided where an argument of type + &sizei; is specified, the error INVALID_VALUE results. The same + error will result from attempts to set integral and floating + point values for attributes exceeding the legal range for + these. The specification does not guarantee that the implementation + emits INVALID_VALUE if a &NaN; or &Infty; value is + passed in for a &float; or &double; argument (as the specification + does not enforce possibly expensive testing of floating point + values). + + + + Commands can be invalid. For example, certain commands might not be + applicable to a given object. There are also illegal combinations + of tokens and values as arguments to a command. &AL; responds to any + such illegal command with an INVALID_OPERATION error. + + + + No longer true except for extensions. To be avoided + in general: &AL; has + mutually exclusive commands operating on similar objects. + One example is treating a streaming buffer as a + non-streaming buffer, another is appending data to a + non-streaming buffer. + + ]]> + + + If memory is exhausted as a side effect of the execution of an + AL command, either on system level or by exhausting the allocated + resources at AL's internal disposal, the error OUT_OF_MEMORY + may be generated. This can also happen independent of recent + commands if &AL; has to request memory for an internal task + and fails to allocate the required memory from the operating + system. + + + Otherwise errors are generated only for conditions that are + explicitely described in this specification. + + + + RFC: INVALID_SIZE? + Specific error case in which the size argument is + negative, or mismatches internal conditions for a getter? + + ]]> + + + RFC: INVALID_POINTER? + GL seemingly does not specify a response to NULL pointer + destinations, and does not assign an error case. INVALID_VALUE + could be used, also we could introduce a separate INVALID_POINTER. + Is there a good reason not to catch these cases? + + ]]> + + +
+ + + + + Controlling AL Execution + + The application can temporarily disable certain AL capabilities + on a per Context basis. This allows the driver implementation + to optimize for certain subsets of operations. + Enabling and disabling capabilities is handled using a function + pair. + + &void; Enable + &enum; target + + + &void; Disable + &enum; target + + The application can also query whether a given capability is + currently enabled or not. + + &bool; IsEnabled + &enum; target + + If the token used to specify target is not legal, + an INVALID_ENUM error will be generated. + + + At this time, this mechanism is not used. There are no valid + targets. + + Annotation (Enable/Disable) + Currently, &AL; is controlled exploiting existing + commands. For example, to disable sound output but + not processing, the Listener can be muted setting + GAIN to zero. Selecting NONE as the distance model + disables distance attenuation. Setting DOPPLER_FACTOR + to zero disables the Doppler Effect. A redundant + mechanism to accomplish the same is not needed. + + ]]> + + + + + Object Paradigm + + &AL; is an object-oriented API, but it does not expose classes, structs, + or other explicit data structures to the application. + + + + + Object Categories + + &AL; has three primary categories of Objects: + + + + one unique Listener per Context + + + + + multiple Buffers shared among Contexts + + + + + multiple Sources, each local to a Context + + + + In the following, "{Object}" will stand for either Source, + Listener, or Buffer. + + + + + Static vs. Dynamic Objects + + The vast majority of &AL; objects are dynamic, and will be created + on application demand. There are also &AL; objects that do not have + to be created, and can not be created, on application demand. + Currently, the Listener is the only such static object in &AL;. + + + + + + Object Names + + Dynamic Objects are manipulated using an integer, which in + analogy to &OGL; is referred to as the object's "name". These + are of type unsigned integer (uint). Names can be valid + beyond the lifetime of the context they were requested + if the objects in question can be shared among contexts. + No guarantees or assumptions are + made in the specification about the precise values or their distribution + over the lifetime of the application. As objects might be shared, + Names are guaranteed to be + unique within a class of &AL; objects, but no guarantees are made + across different classes of objects. Objects that are unique + (singletons), like the Listener, do not require and do not have + an integer "name". + + + + + + Requesting Object Names + + &AL; provides calls to obtain Object Names. The application requests + a number of Objects of a given category using Gen{Object}s. + If the number n of Objects requested is negative, + an INVALID_VALUE error will caused. The actual values of the + Names returned are implementation dependent. No guarantees on + range or value are made. Unlike &OGL; &OAL does not offer alternative + means to define (bind) a Name. + + + Allocation of Object Names does not imply immediate allocation of + resources or creation of Objects: the implementation is free to + defer this until a given Object is actually used in mutator calls. + The Names are written at the memory location specified by the caller. + + void Gen{Object}s + &sizei; n + &uint;* objectNames + + + + Requesting zero names is a legal NOP. Requesting a negative + number of names causes an INVALID_VALUE error. + &AL; will respond with an OUT_OF_MEMORY if the application + requests too many objects. The specification does not guarantee + that the &AL; implementation will allocate all resources + needed for the actual objects at the time the names are + reserved. In many cases (Buffers) this could only be + implemented by worst case estimation. Allocation of names + does not guarantee that all the named objects can actually + be used. + + + + + We do not re-use Names under any circumstance. Do we require + implementations throwing OUT_OF_MERMORY errors on allocation of + Names? No - we don't even specify buffer sizes. Ambiguity - could + an implementation throw OOM because of no names, or OOM because + of a (worst case) estimate of object sizes? Do we need OUT_OF_NAMES? + + ]]> + + + + The current headers include a sizei return parameter: + "Returns the number of ids actually allocated." + This violates the "failed commands are NOPs" design + and introduces ambiguity in error handling, and has + thus been changed breaking backwards compatibility. + + ]]> + + Annotation (No application selected Names) + Unlike GL, applications are not free to choose Names; all + Names have to be requested. Aside from possible benefits for + the implementation, and avoidance of errors in projects + that have many modules using the AL implementation (a problem + encountered in GL, when the two generation mechanisms are + mixed), this also leaves open the door to feed different + kinds of objects by Name through the same API entry points. + + ]]> + + Annotate (Negative/zero sizei) + The specification does not guarantee that sizei is an + unsigned integer, but legal values have to be non-negative. + However, requesting zero names is a legal NOP. + + ]]> + + RFC: Resource Release Hint + Do we need a hint that resource release has to be done on DeleteXXX, + instead of leaving this housekeeping to &AL;? + + RFC: Zero Name + Do we reserve the name "0"? &OGL; provides an alternative mechanism + which lets the application pick texture names, which we discarded + because it is prone to create error conditions when mixing both + approaches. As all our names are generated using GenXXXX, there + is no real need to treat "0" special. + + ]]> + + + + + + Releasing Object Names + + &AL; provides calls to the application to release Object Names + using Delete{Object}s, implicitly requesting deletion of the + Objects associated with the Names released. If the number n of Objects named + is negative, an INVALID_VALUE error will be caused. + If one or more of the specified Names is not valid, an INVALID_NAME + error will be caused. Implementation behavior following any error + is undefined. + + + Once deleted (even if an error occured on deletion), the Names are + no longer valid for use with any &AL; function calls including + calls to Delete{Objects}s. Any such use will cause an INVALID_NAME + error. + + + The &AL; implementation is free to defer actual release of + resources. Ideally, resources should be released as soon as + possible, but no guarantees are made. + + &void;Delete{Object}s + &sizei;n + &uint;*objectNames + + + + Annotation + GenXXX and DeleteXXX can not reasonably be expected to be used + for controlling driver-side resource management from the + application. A driver might never release a Source once allocated + during the lifetime of the application. + + ]]> + + RFC: Deletion Errors + chasan@acm.org: + What happens if an active source (or its associated buffer) is deleted? + The source should be stopped? Or the delete operation is invalid? + + ]]> + + + + + + Validating an Object Name + + &AL; provides calls to validate the Name of an Object. + The application can verify whether an Object Name is valid + using the Is{Object} query. There is no vector (array) + version of this function as it defeats the purpose of + unambiguous (in)valdiation. Returns &TRUE; if id is a + valid Object Name, and &FALSE; otherwise. Object Names are + valid between request (Gen{Object}s) and release (Delete{Object}s). + Is{Object} does not distinguish between invalid and deleted Names. + + &bool;Is{Object} + &uint;objectName + + + RFC/bk000504: + If zero is a valid name, this function will have to accept + it without an actyual object (or only an internal dummy) + being associated with it. I recommend that implementations + never return "0" as an object name. + + ]]> + + + + + Setting Object Attributes + + For &AL; Objects, calls to control their attributes are provided. + These depend on the actual properties of a given Object + Category. The precise API is discussed for each category, + below. Each &AL; command affecting the state of + a named Object is usually of the form + + void {Object}{n}{sifd}{v} + &uint; objectName + &enum; paramName + &type; values + + In the case of unnamed (unique) Objects, the (integer) objectName + is omitted, as it is implied by the {Object} part of function name: + + void {Object}{n}{sifd}{v} + &enum; paramName + &type; values + + For example, the Listener3d command would not require an (integer) + objectName argument. + + + The objectName specifies the &AL; object affected by this call. + Use of an invalid Name will cause an INVALID_NAME error. + + + The Object's Attribute to be affected has to be named + as paramName. &AL; parameters applicable to one category + of Objects are not necessarily legal for another catetgory + of &AL; Objects. Specification of a parameter illegal for + a given object will cause an INVALID_OPERATION error. + + + Not all possible values for a type will be legal for a + given objectName and parameterName. Use of an illegal value + or a NULL value pointer will cause an INVALID_VALUE error. + + + Any command that causes an error is a NOP. + + + + + Querying Object Attributes + + For named and for unique &AL; Objects, calls to query their + current attributes are provided. + These depend on the actual properties of a given Object + Category. The performance of such queries is implementation + dependent, no performance guarantees are made. The valid values for the + parameter paramName are identical to the ones legal for the complementing + attribute setting function. + + void Get{Object}{n}{sifd}{v} + &uint; objectName + &enum; paramName + &type;* destination + + For unnamed unique Objects, the objectName is omitted as it is + implied by the function name: + + void Get{Object}{n}{sifd}{v} + &enum; paramName + &type;* destination + + + + The precise API is discussed for each category separately, below. + Unlike their matching mutators, Query functions for non-scalar + properties (vectors etc.) are only available in array form. + + + Use of an invalid Name will cause an INVALID_NAME error. + Specification of an illegal parameter type (token) will cause + an INVALID_ENUM error. A call with a destination + NULL pointer will be quietly ignored. The &AL; state will not + be affected by errors. In case of errors, destination memory + will not be changed. + + + + + + Object Attributes + + + Attributes affecting the processing of sounds can be set for various + &AL; Object categories, or might change as an effect of &AL; calls. + The vast majority of these Object properties are specific to the + &AL; Object category, in question, but some are applicable to two + or more categories, and are listed separately. + + + The general form in which this document describes parameters is + + {Object} Parameters + + + + + + + + &Par; + &Sig; + &Val + &Def; + + + + + paramName + T + range or set + scalar or n-tuple + + + +
+ Description: + The description specifies additional restrictions and details. + paramName is given as the &AL; enum defined as its name. + T can be a list of legal signatures, usually the array form + as well as the flat (unfolded) form. +
+ + + RFC: Initial (Default) State + The default state of objects will have to be specified here. + There will be no commands that allow the application to set + other defaults. + + ]]> +
+
+
+ diff --git a/src/openal/docs/chp-queueing.sgml b/src/openal/docs/chp-queueing.sgml new file mode 100644 index 0000000..8bce727 --- /dev/null +++ b/src/openal/docs/chp-queueing.sgml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/openal/docs/chp-rendering.sgml b/src/openal/docs/chp-rendering.sgml new file mode 100644 index 0000000..1237e88 --- /dev/null +++ b/src/openal/docs/chp-rendering.sgml @@ -0,0 +1,2076 @@ + + + + Listener and Sources + + + Basic Listener and Source Attributes + + This section introduces basic attributes that can be set both for + the Listener object and for Source objects. + + + RFC: attribute grouping + JM: "These attributes are of + two types: non-positional and positional. + Non-positional properties include gain control and Environment Name." + + I said: (low pass) Filters are applied to the sound during processing + at various stages. The exact sequence in which Filters are applied is + determined based on the location of the Objects they are + set for - spatial arrangement of Objects determines the + sequence unless invariance is guaranteed, or invariance + violation is permitted by the specification and current &AL; + configuration state. + + Is there a required order of application, i.e. a pipeline? + + Filter Parameters vs. Non-positional properties. + Spatialization vs. Positional properties. + Spatial attributes? + Let's postpone grouping of attributes. + + ]]> + + + + The &AL; Listener and Sources have attributes to describe + their position, velocity and orientation in three dimensional space. + &AL; like &OGL;, uses a right-handed Cartesian coordinate system (RHS), + where in a frontal default view X (thumb) points right, + Y (index finger) points up, and Z (middle finger) points towards + the viewer/camera. To switch from a left handed coordinate system (LHS) + to a right handed coordinate systems, flip the sign on the Z coordinate. + + + + + Listener/Source Position + + + + &Par; + &Sig; + &Val + &Def; + + + + + POSITION + 3fv, 3f + any except NaN + { 0.0f, 0.0f, 0.0f } + + + +
+ Description: + POSITION specifies the current location of the Object in the + world coordinate system. Any 3-tuple of valid float/double values + is allowed. Implementation behavior on encountering &NaN; and &Infty; + is not defined. The Object position is always defined in the + world coordinate system. +
+ + Annotation (No Transformation) + &AL; does not support transformation operations on Objects. + Support for transformation matrices is not planned. + + ]]> + + + + + Listener/Source Velocity + + + + &Par; + &Sig; + &Val + &Def; + + + + + VELOCITY + 3fv, 3f + any except NaN + { 0.0f, 0.0f, 0.0f } + + + +
+ Description: + VELOCITY specifies the current velocity (speed and direction) of + the Object, in the world coordinate system. Any 3-tuple of valid + float/double values is allowed. The Object VELOCITY does not affect + its position. + &AL; does not calculate the velocity from subsequent position + updates, nor does it adjust the position over time based on + the specified velocity. Any such calculation is left to the + application. For the purposes of sound processing, position and + velocity are independent parameters affecting different aspects + of the sounds. +
+ VELOCITY is taken into account by the driver to synthesize the + Doppler effect perceived by the Listener for each source, based + on the velocity of both Source and Listener, and the Doppler + related parameters. + + + + + + + + Listener/Source Gain (logarithmic) + + + + &Par; + &Sig; + &Val + &Def; + + + + + GAIN + f + 0.0f, (0.0f, any + 1.0f + + + +
+ Description: + GAIN defines a scalar amplitude multiplier. As a Source attribute, it applies + to that particular source only. As a Listener attribute, it effectively + applies to all Sources in the current Context. The default 1.0 means + that the sound is un-attenuated. A GAIN value of 0.5 is equivalent to + an attenuation of 6 dB. The value zero equals silence (no output). Driver + implementations are free to optimize this case and skip mixing and + processing stages where applicable. The implementation is in charge of + ensuring artifact-free (click-free) changes of gain values and is free + to defer actual modification of the sound samples, within the limits of + acceptable latencies. +
+ + GAIN larger than 1 (amplification) is permitted for Source and + Listener. However, the implementation is free to clamp the + total gain (effective gain per source times listener gain) + to 1 to prevent overflow. + + + + Annotation/ Effective Minimal Distance + Presuming that the sample uses the entire dynamic range of + the encoding format, an effective gain of 1 represents the + maximum volume at which a source can reasonably be played. + During processing, the implementation combines the Source + GAIN (or MIN_GAIN, if set and larger) with distance based + attenuation. The distance at which the effective gain is 1 + is equivalent to the DirectSound3D MIN_DISTANCE parameter. + Once the effective gain has reached the maximum possible + value, it will not increase with decreasing distance anymore. + + ]]> + + + Annotation (Muting a Context) + To mute the current context, simply set Listener GAIN to zero. + The implementation is expected to optimize for this case, + calculating necessary (offset) updates but bypassing the + mixing and releasing hardware resources. + The specification does not guarantee that the implementation + will release hardware resources used by a muted context. + + ]]> + + Annotation (Muting a Source) + To mute a Source, set Source GAIN to zero. The &AL; implementation + is encouraged to optimize for this case. + + ]]> + + + RFC: GAIN > 1? + GAIN could exceed 1 (to compensate attenuation elsewhere, or to account + for grater dynamic range of the hardware? No guarantees are made with + respect to range overflows? Precision loss? Culling by effective gain? + Does &AL; clip values during processing, and when/at what stages? + + + RFC: Doppler + JM wrote: "VELOCITY is used by the driver + to synthesize the Doppler effect perceived by the listener for each + source, based on the relative velocity of this source with respect + to the listener." + Doppler is calculated using Source and Listener velocities measured + with respect to the medium. Do we have to account for the medium + to move (offsetting listener/source) in later revisions (air/water currents)? + + + RFC: + JM removed: "For the purposes of sound processing, position and + velocity are independent parameters affecting different paths + in the sound synthesis." I think the "different aspects of sounds" + is ambiguous. Is there a problem with describing &AL; as a + multichannel processing machine? + + + ]]> + +
+ + + + Listener Object + + + The Listener Object defines various properties that affect processing of + the sound for the actual output. The Listener is unique for an &AL; Context, + and has no Name. By controlling the listener, the application controls + the way the user experiences the virtual world, as the listener defines + the sampling/pickup point and orientation, and other parameters that + affect the output stream. + + + It is entirely up to the driver and hardware configuration, i.e. + the installation of &AL; as part of the operating system and + hardware setup, whether the output stream is generated for + headphones or 2 speakers, 4.1 speakers, or other arrangements, + whether (and which) HRTF's are applied, etc.. + + + Annotation (Listener Anatomy) + The API is ignorant with respect to the real world + listener, it does not need to make assumptions on the + listening capabilities of the user, its species or its + number of ears. It only describes a scene and the position + of the listener in this scene. It is the &AL; implementation + that is designed for humans with ears on either side of the + head. + + ]]> + + + Annotation (Listener State Evaluation) + Some Listener state (GAIN) affects only the very last + stage of sound synthesis, and is thus applied to the sound stream + as sampled at the Listener position. Other Listener state is + applied earlier. One example is Listener velocity as used to + compute the amount of Doppler pitch-shifting applied to each source: + In a typical implementation, pitch-shifting (sample-rate conversion) + might be the first stage of the audio processing for each source. + + ]]> + + + + Listener Attributes + + + Several Source attributes also apply to Listener: e.g. POSITION, VELOCITY, + GAIN. In addition, some attributes are listener specific. + + Listener Orientation + + + + &Par; + &Sig; + &Val + &Def; + + + + + ORIENTATION + fv + any except NaN + { { 0.0f, 0.0f, -1.0f }, { 0.0f, 1.0f, 0.0f } } + + + +
+ Description: + ORIENTATION is a pair of 3-tuples representing the 'at' direction vector + and 'up' direction of the Object in Cartesian space. &AL; expects two + vectors that are orthogonal to each other. These + vectors are not expected to be normalized. If one or more vectors + have zero length, implementation behavior is undefined. If the two + vectors are linearly dependent, behavior is undefined. +
+ RFC: Orientation Paranoia + Watch LHS vs. RHS (sign on 'at'). + Confirm sequence is (up, at) not vice versa. + Do we want to allow for different representations down the road? + + ]]> +
+ + + Changing Listener Attributes + + Listener attributes are changed using the Listener group of commands. + + void Listener{n}{sifd}{v} + &enum; paramName + &type; values + + + + + + Querying Listener Attributes + + Listener state is maintained inside the &AL; implementation and can be + queried in full. See Querying Object Attributes. The valid values for + paramName are identical to the ones for the Listener* command. + + void GetListener{sifd}v + &enum; paramName + &type;* values + + + +
+ + + + + Source Objects + + Sources specify attributes like position, velocity, and a buffer with + sample data. By controlling a Source's attributes the + application can modify and parameterize the static sample data provided + by the Buffer referenced by the Source. + Sources define a localized sound, and encapsulate + a set of attributes applied to a sound at its origin, i.e. in the + very first stage of the processing on the way to the listener. + Source related effects have to be applied + before Listener related effects unless the output is invariant + to any collapse or reversal of order. + + + &AL; also provides additional functions to manipulate and query the + execution state of Sources: the current playing status of a + source (started, stopped, paused), including access to the current + sampling position within the associated Buffer. + + + RFC: Mike on Source Types + AL_SOURCE_ABSOLUTE and AL_SOURCE_AMBIENT have been + deprecated. AL_SOURCE_ABSOLUTE was simply the converse of the + AL_SOURCE_RELATIVE pname, and as such was unnecessary. The + effect of AL_SOURCE_AMBIENT is trivially emulated by either + querying the Listener position and setting the Source position + accordingly, or setting the Source position to (0,0,0) and the + type to AL_SOURCE_RELATIVE, and is therefore also unnecessary. + + ]]> + + + RFC: Bernd on Source Types + Mike seems to miss a few problems here. W/o a converse we can't + reset source attributes to ABSOLUTE. Ambient sounds are not + necessarily trivial. A3D manual suggested some magic number + to fake the effect of ambient (i.e. sound that ideally + can't be localized by listener). If we can get away with such magic + numbers in a tutorial in a driver-independent way, fine. If there is any + risk that the impression of ambient sound requires driver specific + hacks, then we need AMBIENT. As soon as we have a third localization + type, ABSOLUTE and RELATIVE are needed as there is no unambiguous + converse. + + From the A3D 2.0 Optimize.doc: + "Adding some ambient background noise is a great way to fill in the gaps + when the audio content is reduced. A great way to make an ambient sound + seem like it is coming from everywhere is to load up two buffers with the + same sound, and position them about 2 meters behind the listener at + about 4 and 8 o\rquote clock. The waves have to be looping (make sure + there is no beating when you play them back). Starting the sounds 180 + degrees out of phase can help, as will playing them with slightly different + pitch-shift values." + + ]]> + + RFC: Bernd on Source Types (2) + There is a point to be made in using POSITION_RELATIVE and + VELOCITY_RELATIVE iff we do not have AMBIENT to consider. + This makes it a call-by-call choice when setting Source3f{v} + vectors, as it is applied when dereferencing. + + ]]> + + RFC: Bernd on Source Types (3) + Semantically, AMBIENT has nothing to do with coordinate systems, + it is a qualifier just like multichannel direct passthru. + + ]]> + + RFC: Source Attenuation Clamping + Using AL_SOURCE_ATTENUATION_MIN and AL_SOURCE_ATTENUATION_MAX + to specify the clamping values for the normalized attenuation + factor (which is a function of distance) is in contradiction + to the distance based model that Creative is pushing for + (DirectSound). As driver-interall culling of source and other + processing might be based on the effective (overall, ultimate) + gain composed of amplifications and attenuations accumulated + over the entire processing, I raise the question whether a sound + designer might not want to control the effective GAIN ranges + instead of the distance attenuation itself. Samples commonly + use the entire dynamic range provided by the format, which is + mapped to the entire dynamic range of the output device. An + effective gain exceeding 1 does not make sense, an amplification + during processing might. + + ]]> + + + + + + + Managing Source Names + + &AL; provides calls to request and release Source Names handles. + Calls to control Source Execution State are also provided. + + + + Requesting a Source Name + + The application requests a number of Sources using GenSources. + + &void; GenSources + &sizei; n + &uint;* sources + + + + + + + Releasing Source Names + + The application requests deletion of a number of Sources + by DeleteSources. + + &void; DeleteSources + &sizei; n + &uint;* sources + + + + + + Validating a Source Name + + The application can verify whether a source name is valid + using the IsSource query. + + &bool; IsSource + &uint; sourceName + + + + + + + + Source Attributes + + This section lists the attributes that are set per Source, + affecting the processing of the current buffer. Some of + these attributes can also be set for buffer queue entries. + + Annotation (No Priorities) + There are no per Source priorities, and no explicit priority + handling, defined at this point. A mechanism that lets the + application express preferences in case that the implementation + provides culling and prioritization mechanisms might be added + at some later time. This topic is under discussion for GL as + well, which already has one exlicit priority API along with + internally used MRU heuristics (for resident texture memory). + + ]]> + + + + + + Source Positioning + + + SOURCE_RELATIVE Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + SOURCE_RELATIVE + &bool; + FALSE, TRUE + FALSE + + + +
+ SOURCE_RELATIVE set to TRUE indicates that the values + specified by POSITION are to be interpreted relative + to the listener position. +
+ + + Annotation (Position only) + SOURCE_RELATIVE does not affect velocity or orientation + calculation. + + ]]> + +
+ + + Buffer Looping + + + Source LOOPING Attribute + + + + + + + + + &Par; + &Sig; + &Val + &Def; + + + + + LOOPING + &uint; + TURE, FALSE + FALSE + + + +
+ Description: + LOOPING is a flag that indicates that the Source will not + be in STOPPED state once it reaches the end of last buffer + in the buffer queue. Instead, the Source will immediately + promote to INITIAL and PLAYING. The default value is FALSE. + LOOPING can be changed on a Source in any execution state. + In particular, it can be changed on a PLAYING Source. +
+ + Annotation (Finite Repetition) + Finite reptition is implemented by buffer queueing. + + ]]> + + Annotation (Loop Control) + To implement a 3 stage "loop point" solution, the + application has to queue the FadeIn buffer first, + then queue the buffer it wants to loop, and set + LOOPING to TRUE once the FadeIn buffer has been + processed and unqueued. To fade from looping, the + application can queue a FadeOut buffer, then + set LOOPING to false on the PLAYING source. Alternatively, + the application can decide to not use the LOOPING + attribute at all, and just continue to queue the buffer + it wants repeated. + + ]]> + + Annotation (Rejected alternatives) + A finite loop counter was rejected because it is + ambiguous with respect to persistent (initial counter) + vs. transient (current counter). For similar reasons, + a Play-equivalent command with a (transient) loop counter + was rejected. + + ]]> +
+ + + Current Buffer + + + Source BUFFER Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + BUFFER + &uint; + any valid bufferName + &NONE; + + + +
+ Description: + Specifies the current Buffer object, making it the + head entry in the Source's queue. Using BUFFER on a + STOPPED or INITIAL Source empties the entire queue, + then appends the one Buffer specified. +
+ + For a PLAYING or PAUSED Source, using the Source command + with BUFFER is an INVALID_OPERATION. + It can be applied to INITIAL and STOPPED Sources only. + Specifying an invalid bufferName will + result in an INVALID_VALUE error while specifying an + invalid sourceName results in an INVALID_NAME error. + + + NONE, i.e. 0, is a valid buffer Name. + Source( sName, BUFFER, 0 ) is a legal way to release the + current buffer queue on an INITIAL or STOPPED Source, + whether it has just one entry (current buffer) or more. + The Source( sName, BUFFER, NONE) call still causes an + INVALID_OPERATION for any source PLAYING or PAUSED, + consequently it cannot be abused to mute or stop a source. + + + Annotation (repeated Source+BUFFER does not queue) + Using repeated Source(BUFFER) calls to queue a buffer on + an active source would imply that there is no way to + release the current buffer e.g. by setting it to 0. + On the other hand read-only queues do not allow for + releasing a buffer without releasing the entire queue. + + We can not require BUFFER state to be transient and lost + as soon as a Source is implicitely or explicitely stopped. + This contradicts queue state being part of the Source's + configuration state that is preserved through Stop() + operations and available for Play(). + + ]]> + + +
+ + + + Queue State Queries + + + BUFFERS_QUEUED Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + BUFFERS_QUEUED + &uint; + [0, any] + none + + + +
+ Query only. Query the number of buffers in the queue + of a given Source. This includes those not yet played, + the one currently playing, and the ones that have been + played already. This will return 0 if the current and + only bufferName is 0. +
+ + + + + BUFFERS_PROCESSED Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + BUFFERS_PROCESSED + &uint; + [0, any] + none + + + +
+ Query only. Query the number of buffers that have + been played by a given Source. + Indirectly, this gives the index of the buffer + currently playing. Used to determine how much + slots are needed for unqueueing them. + On an STOPPED Source, all buffers are processed. + On an INITIAL Source, no buffers are processed, + all buffers are pending. + This will return 0 if the current and + only bufferName is 0. +
+ + Annotation (per-Source vs. Buffer State) + BUFFERS_PROCESSED is only defined within the scope of a given + Source's queue. It indicates that the given number of buffer names + can be unqueued for this Source. It does not guarantee that the + buffers can safely be deleted or refilled, as they might still be + queued with other Sources. One way to keep track of this is to + store, per buffer, the Source for which a given buffer was most + recently scheduled (this will not work if Sources sharing buffers + might be paused by the application). If necessary an explicit + query for a given buffer name can be added in later revisions. + + ]]> + + Annotation (No Looping Queues) + Unqueueing requires nonzero BUFFERS_PROCESSED, + which necessitates no looping on entire queues, + unless we accept that no unqueueing is possible + from Source looping over the entire queue. + Currently not supported, as queueing is + primarily meant for streaming, which implies + unqueue-refill-requeue operations. + + ]]> + + +
+ + + Bounds on Gain + + + Source Minimal Gain + + + + &Par; + &Sig; + &Val + &Def; + + + + + MIN_GAIN + f + 0.0f, (0.0f, 1.0f] + 0.0f + + + +
+ Description: + MIN_GAIN is a scalar amplitude threshold. It indicates the minimal GAIN + that is always guaranteed for this Source. At the end of the processing + of various attenuation factors such as distance based attenuation and + Source GAIN, the effective gain calculated is compared to this value. + If the effective gain is lower than MIN_GAIN, MIN_GAIN is applied. + This happens before the Listener GAIN is applied. If a zero MIN_GAIN + is set, then the effective gain will not be corrected. +
+ + + Annotation (Effective Maximal Distance) + By setting MIN_GAIN, the application implicitely defines a + maximum distance for a given distance attenuation model and + Source GAIN. The distance at which the effective gain is MIN_GAIN + can be used as a replacement to the DirectSound3D MAX_DISTANCE parameter. + Once the effective gain has reached the MIN_GAIN value, it will + no longer decrease with increasing distance. + + ]]> + + + + + Source Maximal Gain (logarithmic) + + + + &Par; + &Sig; + &Val + &Def; + + + + + MAX_GAIN + f + 0.0f, (0.0f, 1.0f] + 1.0f + + + +
+ Description: + MAX_GAIN defines a scalar amplitude threshold. It indicates the maximal + GAIN permitted for this Source. At the end of the processing + of various attenuation factors such as distance based attenuation and + Source GAIN, the effective gain calculated is compared to this value. + If the effective gain is higher than MAX_GAIN, MAX_GAIN is applied. + This happens before the Listener GAIN is applied. If the Listener gain + times MAX_GAIN still exceeds the maximum gain the implementation can + handle, the implementation is free to clamp. If a zero MAX_GAIN + is set, then the Source is effectively muted. The implementation is free + to optimize for this situation, but no optimization is required or + recommended as setting GAIN to zero is the proper way to mute a Source. +
+ + Annotation (Un-attenuated Source) + Setting MIN_GAIN and MAX_GAIN to the GAIN value will effectively + make the Source amplitude independent of distance. The + implementation is free to optimize for this situation. However, the + recommended way to accomplish this effect is using a ROLLOFF_FACTOR + of zero. + + ]]> + + + + Annotation (Internal GAIN threshold) + The &AL; implementation is free to use an internally chosen + threshold level below which a Source is ignored for mixing. + Reasonable choices would set this threshold low enough so + that the user will not perceive a difference. Setting MIN_GAIN + for a source will override any implementation defined test. + + ]]> +
+ + + + + Distance Model Attributes + + + REFERENCE_DISTANCE Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + REFERENCE_DISTANCE + &float; + [0, any] + 1.0f + + + +
+ This is used for distance attenuation calculations + based on inverse distance with rolloff. Depending + on the distance model it will also act as a distance + threshold below which gain is clamped. See the + section on distance models for details. +
+ + + + + ROLLOFF_FACTOR Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + ROLLOFF_FACTOR + &float; + [0, any] + 1.0f + + + +
+ This is used for distance attenuation calculations + based on inverse distance with rolloff. For + distances smaller than MAX_DISTANCE (and, depending + on the distance model, larger than REFERENCE_DISTANCE), + this will scale the distance attenuation over the + applicable range. See section on distance models for + details how the attenuation is computed as a function + of the distance. +
+ + In particular, ROLLOFF_FACTOR can be set to zero for + those Sources that are supposed to be exempt from + distance attenuation. The implementation is encouraged + to optimize this case, bypassing distance attenuation + calculation entirely on a per-Source basis. + + + + + MAX_DISTANCE Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + MAX_DISTANCE + &float; + [0, any] + MAX_FLOAT + + + +
+ This is used for distance attenuation calculations + based on inverse distance with rolloff, if the + Inverse Clamped Distance Model is used. In this case, + distances greater than MAX_DISTANCE will + be clamped to MAX_DISTANCE. + MAX_DISTANCE based clamping is applied before MIN_GAIN clamping, + so if the effective gain at MAX_DISTANCE is larger than MIN_GAIN, + MIN_GAIN will have no effect. No culling is supported. +
+ + Annotation (No Culling) + This is a per-Source attribute supported for DS3D compatibility + only. Other API features might suffer from side effects due to + the clamping of distance (instead of e.g. clamping to an effective + gain at MAX_DISTANCE). + + ]]> + +
+ + + + + + Frequency Shift by Pitch + + + Source PITCH Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + PITCH + f + (0.0f, 2.0f] + 1.0f + + + +
+ Description: + Desired pitch shift, where 1.0 equals identity. Each reduction by 50 percent + equals a pitch shift of -12 semitones (one octave reduction). Zero is not + a legal value. +
+
+ + + + Direction and Cone + + Each Source can be directional, depending on the settings for + CONE_INNER_ANGLE and CONE_OUTER_ANGLE. There are three zones + defined: the inner cone, the outside zone, and the transitional + zone in between. + The angle-dependent gain for a directional source is constant + inside the inner cone, and changes over the transitional zone + to the value specified outside the outer cone. + Source GAIN is applied for the inner cone, + with an application selectable CONE_OUTER_GAIN factor to + define the gain in the outer zone. In the transitional + zone implementation-dependent interpolation between + GAIN and GAIN times CONE_OUTER_GAIN is applied. + + + + Annotation (Interpolation Restrictions) + The specification does not specify the exact interpolation + applied in the transitional zone, to calculate gain as a + function of angle. The implementation is free to use + linear or other interpolation, as long as the values + are monotonically decreasing from GAIN to GAIN times CONE_OUTER_GAIN. + + ]]> + + + + + Source DIRECTION Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + DIRECTION + 3fv, 3f + any except NaN + { 0.0f, 0.0f, 0.0f } + + + +
+ Description: + If DIRECTION does not equal the zero vector, the Source is directional. + The sound emission is presumed to be symmetric + around the direction vector (cylinder symmetry). Sources are not + oriented in full 3 degrees of freedom, only two angles are effectively + needed. +
+ The zero vector is default, indicating that a Source is not directional. + Specifying a non-zero vector will make the Source directional. + Specifying a zero vector for a directional Source will effectively + mark it as nondirectional. + + + RFC: Oriented Sources + Do we want an alternative AZIMUTH/ALTITUDE parametrization? + Do we need ORIENTATION later? Is this superimposable? Can we mix both? + + ]]> + + Annotation (All Sources Directional) + From the point of view of the &AL; implementation, all + Sources are directional. Certain choices for cone angles + as well as a direction vector with zero length are treated + equivalent to an omnidirectional source. The &AL; + implementation is free to flag and optimize these cases. + + ]]> + + RFC: Separate GenDirectionSource? + Is there any risk that directional sources require different + resources that have to be allocated from the beginning, and + that we can not change an omnidirectional source to a + bidirectional source at runtime? + + ]]> + + + + + Source CONE_INNER_ANGLE Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + CONE_INNER_ANGLE + i,f + any except NaN + 360.0f + + + +
+ Description: + Inside angle of the sound cone, in degrees. The default of 360 means that the + inner angle covers the entire world, which is equivalent to an omnidirectional + source. +
+ RFC: inconsistent cone angles? + Is (inner <= outer) required? Do we generate an error? + Shouldn't this be a CONE_ANGLES 2f call specifying both angles at once? + + ]]> + + + + + + + Source CONE_OUTER_ANGLE Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + CONE_OUTER_ANGLE + i,f + any except NaN + 360.0f + + + +
+ Description: Outer angle of the sound cone, in degrees. The default of 360 means that the + outer angle covers the entire world. If the inner angle is also 360, then + the zone for angle-dependent attenuation is zero. +
+ + RFC: addition? + More generally, we could specify: + "If the sum of inner and outer angles is larger than 360, + CONE_OUTER_ANGLE is clamped to (360-CONE_INNER_ANGLE) and + there is no transition zone." + + ]]> + + + + Source CONE_OUTER_GAIN Attribute + + + + &Par; + &Sig; + &Val + &Def; + + + + + CONE_OUTER_GAIN + i,f + [0.0f, 1.0f] + 0.0f + + + +
+ Description: the factor with which GAIN is multiplied to + determine the effective gain outside the cone defined by + the outer angle. The effective gain applied outside the + outer cone is GAIN times CONE_OUTER_GAIN. Changing + GAIN affects all directions, i.e. the source is attenuated + in all directions, for any position of the listener. + The application has to change CONE_OUTER_GAIN as well if + a different behavior is desired. +
+ + + Annotation (GAIN calculation) + The angle-dependend gain DGAIN is multiplied with the + gain determined by the source's GAIN and any distance + attenuation as applicable. Let theta be the angle + between the source's direction vector, and the vector + connection the source and the listener. This multiplier + DGAIN is calculated as: + + OUTER = CONE_OUTER_ANGLE/2; + INNER = CONE_INNER_ANGLE/2; + if ( theta less/equal INNER ) + DGAIN = 1 + else if ( theta greater/equal OUTER ) + DGAIN = CONE_OUTER_GAIN + else + DGAIN = 1 - (1-CONE_OUTER_GAIN)*((theta-INNER)/(OUTER-INNER)) + GAIN *= DGAIN + + in the case of linear interpolation. The implementation + is free to use a different interplation across the (INNER,OUTER) + range as long as it is monotone. + + ]]> + + Annotation (CONE_OUTER_GAIN always less than GAIN) + CONE_OUTER_GAIN is not an absolute value, but (like all GAIN + parameters) a scaling factor. This avoids a possible error + case (implementations can count on effective gain outside the + outer cone being smaller than GAIN), and ensures the common + case in which changing GAIN should affect inner, transitional, + and outer zone simultaneously. + + In case that the application desires to have an outer zone + volume exceeding that of the inner cone, the mapping to + &AL; will require to rotate the Source direction to the + opposite direction (negate vector), and swapping + inner and outer angle. + + ]]> +
+
+ + + + Changing Source Attributes + + The Source specifies the position and other properties as + taken into account during sound processing. + + void Source{n}{sifd} + &uint; sourceName + &enum; paramName + &type; value + + + void Source{n}{sifd}v + &uint; sourceName + &enum; paramName + &type;* values + + + + + + Querying Source Attributes + + Source state is maintained inside the &AL; implementation, and the + current attributes can be queried. The performance of such queries is + implementation dependent, no performance guarantees are made. The + valid values for the paramName parameter are identical to the ones + for Source*. + + void GetSource{n}{sifd}{v} + &uint; sourceName + &enum; paramName + &type;* values + + + + + Old signature: T GetSource{sifd}{v}( uint id, enum param ); + + ]]> + + + + + + + Queueing Buffers with a Source + + &AL; does not specify a built-in streaming mechanism. There + is no mechanism to stream data e.g. into a Buffer object. + Instead, the API introduces a more flexible and versatile + mechanism to queue Buffers for Sources. + + + There are many ways to use this feature, with + streaming being only one of them. + + + + Streaming is replaced by queuing static + buffers. This effectively moves any multi-buffer + caching into the application and allows the + application to select how many buffers it wants + to use, whether these are re-used in cycle, + pooled, or thrown away. + + + + + Looping (over a finite number of repititions) can be + implemented by explicitely repeating buffers + in the queue. Infinite loops can (theoretically) + be accomplished by sufficiently large repetition counters. + If only a single buffer is supposed to be repeated + infinitely, using the respective Source attribute is + recommended. + + + + + Loop Points for restricted looping inside a buffer + can in many cases be replaced by splitting the + sample into several buffers, queueing the sample + fragments (including repetitions) accordingly. + + + + Buffers can be queued, unqueued after they have been + used, and either be deleted, or refilled and queued again. + Splitting large samples over several buffers maintained + in a queue has a distinct advantages over approaches that + require explicit management of samples and sample indices. + + + RFC: Unified Handling + Jonathan Blow has proposed removing the distinction between + streaming and non-streaming buffers. An existing example is + the unified for directional and omnidirectional sources, where + all sources are treated as directional. + + ]]> + + + + Queueing command + + The application can queue up one or multiple buffer names + using SourceQueueBuffers. The buffers will be queued in the sequence + in which they appear in the array. + + &void; alSourceQueueBuffers + &uint; sourceName + &sizei; numBuffers + &uint; * bufferNames + + This command is legal on a Source in any state (to allow for + streaming, queueing has to be possible on a PLAYING Source). + Queues are read-only with exception of the unqueue operation. + The Buffer Name NONE (i.e. 0) can be queued. + + + Annotation (BUFFER vs. SourceQueueBuffers) + A Sourcei( sname, BUFFER, bname ) command is an immediate + command, and executed immediately. It effectively unqueues + all buffers, and then adds the specified buffer to the + then empty queue as its single entry. Consequently, this + call is only legal if SourceUnqueueBuffers is legal. + In particular, the Source has to be STOPPED or INITIAL. + The application is still obliged to delete all + buffers as were contained in the queue. + Sourcei( sname, BUFFER, NONE ) is a legal command, + effectively wiping the queue without specifying an + actually playable buffer. + + ]]> + + + Annotation (Buffer Repetition) + To accomplish a finite number of repetitions of a buffer name multiple times, + the buffer has to be queued multiple times. If the need occurs, the + API could be extended by SourceQueueBuffer( sname, bname, repetitions ) + call for brevity. + + ]]> + + RFC: Duration of bName==0? + The buffer is considered empty, it should have zero length, + thus zero duration for consistency. If an application wants to + schedule a pause, specifying duration for a gain==0 queue entry + might be a cleaner solution. + + ]]> + + + Annotation (Backwards Compatiblity) + Sourcei( sname, BUFFER, bname ) has been rejected as + a queueing command, as it would make semantics dependent on + source state (queueing if PLAYING, immediate else). + The command is not legal on a PLAYING or PAUSED Source. + + ]]> + + Annotation (No BUFFER_QUEUE) + Duplication of one entry point is preferable to + duplicating token enums, and tokens do not express + commands, but specify the attribute/state affected. + From the same reason, there is no BUFFER_UNQUEUE + token-as-command. + + ]]> + + + + + Unqueueing command + + Once a queue entry for a buffer has been appended to a queue + and is pending processing, it should not be changed. + Removal of a given queue entry is not possible unless + either the Source is STOPPED (in which case then entire queue + is considered processed), or if the queue entry has already + been processed (PLAYING or PAUSED Source). + + + The Unqueue command removes a number of buffers entries that + have finished processing, in the order of appearance, from + the queue. The operation will fail if more buffers are + requested than available, leaving the destination arguments + unchanged. An INVALID_VALUE error will be thrown. + If no error, the destination argument will have been updated + accordingly. + + void SourceUnqueueBuffers + &uint; sourceName + &sizei; numEntries + &uint;* bufferNames + + + + Annotation (Unqueueing shared buffers) + If a buffer is queued with more than one source, it might have + been processed for some not all of them. With the current + interface, the application is forced to maintain its own list + of consumers (Sources) for a buffer it wishes to unqueue. + For groups of Sources that are never individually PAUSED + nor STOPPED, the application can save the MRU Source for + which the buffer was scheduled last. + + ]]> + + Annotation (Looping a Queue vs. Unqueue): + If a Source is playing repeatedly, it will traverse + the entire Queue repeatedly. Consequently, no buffer + in the queue can be considered processed until + there is no further repetition scheduled. + + ]]> + + Annotation (No Name based access) + No interface is provided to access a queue entry by name, + due to ambiguity (same buffer name scheduled several times + in a sequence). + + ]]> + + Annotation (No Index based access) + No interface is provided for random access to a queue entry + by index. + + ]]> + + + + + + More Annotation on Queueing + + Annotation (No Queue Copying) + The current queue of a source could be copied to another source, + as repetition and traversal parameters are stored unless the + queue entry is unqueued, or the queue is replaced using + AL_BUFFER. Copying a queue is a special case of + copying Source state in one sense, and a special case of + a synching problem in another. Due to these unresolved issues + no such command is included in the current specification. + To share queues, the application can keep buffer names + and the selected attributes that define the queue entries + in an array or other lookup table. + + ]]> + + Annotation (No Explicit QueueClear) + Sourcei( sname, BUFFER, NONE ) serves the + same purpose. The operation is also redundant + with respect to Unqueue for a STOPPED Source. + + ]]> + + Annotation (Queueing vs. AppendData): + Buffer queueing does not solve the synchronization and timing + issues raised by possible underflow, as these are inherent + to application-driven (pushed) streaming. However, it turns + an internal AL error condition (offset exceeds valid data) + into an audible artifact (Source stops). + Its main advantage is that it allows the application coder + to operate at a scale of her own choice, selecting the + number and size of buffers used for caching the stream, + and to schedule buffer refill and queueing according to + preferences and constraints. Queueing effectively moves + all problems related to replacing or appending Buffer data + to the scale of entire arrays istead of single samples and + indices. + + ]]> + + Annotation (Multiple Sources on a stream) + Queueing allows for the application to determine how much of + a backlog of the data stream is preserved. The application can + keep buffers, and queue them with other Sources after they have + been used already by the original Source. Unlike the mechanism + for appending data to a buffer, the backlog is visible to the + application and under its control, and no synchronization of + Sources using the stream is required. + + ]]> + + + + Annotation (Loop Points and Compressed Data) + For compressed data, uncompression by the application might be + impossible or undesireable. In consequence, splitting the sample + into several buffers is not possible without explicit support + by the API. Buffer-Buffer operations will be added as needed, + for the time being applications should not try to use compressed + samples if more than full looping is required. + + ]]> + + + + Annotation (No Explicit Queue Objects) + Explicit Queue objects have been considered and rejected, + as they introduce another producer-consumer dependency with + another level of indirection. Further, e.g. QUEUE would + also require deprecating BUFFER (breaking backwards + compatibility) as an alSource argument, or would introduce + a confusing set of precedence and overide rules if both + are used in sequence. However, in the absence of explicit + queue objects the application will be forced to keep track + where buffers have been queued in case it intends to + unqueue them for refill or deletion. If several sources + use the same buffers (e.g. for synchronous or + asynchronous streaming) the buffer will have to be + unqueued from each single one. + + ]]> + + + Annotation (Queue no Display List) + An interface resembling &OGL; display-lists has been + considered and rejected. The problem with this approach + is that not only commands would have to be prohibited + (similarly, not all GL calls are legal within a display + list), but also parameters (enumerations). + + In particular, only a small set of operations is meant + to be legal for a queue at this point, and appending + to a queue has to be possible at any time. + Within a hypothetical AL display list, only relative + timing/conditionals are allowed as arguments. This + might necessitate to have multiple forms for deferred + commands, or to not allow for absolute timing. + + Example: + + // lock this queue for overwriting/appending + alBeginQueue( qname, APPEND | REPLACE ); + + // queue a buffer in sequence, with parameters + // boolean: never skip? never bail? + alQueue( AL_BUFFER, bid, loopdir, repetitions ); + ... + + // end lock. + // Existing queue content will be replaced + // or appended at this point. + alEndQueue(); + + + ]]> + + + + Queue Then Delete + + + create source + queue buffer1 + queue buffer2 + queue buffer3 + play + request deletion of buffer1,2,3 + + + + ]]> + + + Queue and Refill with Dual Buffering + + + create source + fill buffer1 + queue buffer1 + play + fill buffer2 + queue buffer2 + check for unused buffers + unqueue buffer1 + fill buffer1 + queue buffer1 + ... + + + + ]]> + + + Queue for Loop Points + + + create source + read sample data + split sample data into pre/main/post + queue pre + queue main with repetitions + queue post + play + set repetitions to 0 on main when needed + wait till post has been played + + + + ]]> + + +]]> + + + Attributes Specific to Queueing + +
+ Buffer Traversal + + The Buffer traversal attribute specifies the direction + in which the sample in the buffer is supposed to be + processed. To account for the 3 basic modes of traversal that + can be implemented in software and hardware, the following + tokens are defined: + + LOOP_DIRECTION /* traversal direction */ + + FORWARD /* linear forward (increment) */ + BACKWARD /* linear backward (decrement) */ + FORWARD_AND_BACK /* RESERVED: ping-pong-looping */ + + + The first and the next two tokens are legal with a buffer queue command. + They are not legal for a Source command, in any possible + Source state. The last token is reserved, but not yet legal to use. + + + + Annotation (Ping-Pong postponed) + Definition and implementation of ping-pong looping + has been postponed. Applications can fake it at doubling + memory expense by reverse copying the buffer (2nd buffer queued + or in a double size single buffer). If there is hardware support + for this feature, AL will have to support it eventually. A boolean + flag is not acceptable because of this possibility. + + ]]> + +]]> + + + + + + + + + + + Managing Source Execution + + The execution state of a source can be queried. &AL; provides + a set of functions that initiate state transitions causing + Sources to start and stop execution. + + + TBA: State Transition Diagram. + + + Annotation/ Source Config/Exec State + Sources have configuration state and execution state. + Configuration state is directly set by the application using + AL commands, starting with the INITIAL configuration. Execution + state (e.g. the offset to the current sample) is not under direct + application control and not exposed. + + ]]> + + + Source State Query + + The application can query the current state of any Source + using GetSource with the parameter Name SOURCE_STATE. + Each Source can be in one of four possible execution states: + INITIAL, PLAYING, PAUSED, STOPPED. Sources that are either + PLAYING or PAUSED are considered active. Sources that are + STOPPED or INITIAL are considered inactive. Only PLAYING + Sources are included in the processing. The implementation + is free to skip those processing stages for Sources that + have no effect on the output (e.g. mixing for a Source + muted by zero GAIN, but not sample offset increments). + Depending on the current state of a Source certain (e.g. repeated) + state transition commands are legal NOPs: they will be ignored, + no error is generated. + + + + + State Transition Commands + + The default state of any Source is INITIAL. From this state + it can be propagated to any other state by appropriate use + of the commands below. There are no irreversible state + transitions. + + void SourcePlay + &uint; sName + + + void SourcePause + &uint; sName + + + void SourceStop + &uint; sName + + + void SourceRewind + &uint; sName + + + + + + The functions are also available as a vector variant, + which guarantees synchronized operation on a set of + Sources. + + void SourcePlayv + &sizei; n + &uint;* sNames + + + void SourcePausev + &sizei; n + &uint;* sNames + + + void SourceStopv + &sizei; n + &uint;* sNames + + + + void SourceRewindv + &sizei; n + &uint;* sNames + + + + + The following state/command/state transitions are defined: + + + + Play() applied to an INITIAL Source will promote the Source + to PLAYING, thus the data found in the Buffer will be fed + into the processing, starting at the beginning. + Play() applied to a PLAYING Source will restart the Source + from the beginning. It will not affect the configuration, + and will leave the Source in PLAYING state, but reset the + sampling offset to the beginning. + Play() applied to a PAUSED Source will + resume processing using the Source state + as preserved at the Pause() operation. + Play() applied to a STOPPED Source will propagate it + to INITIAL then to PLAYING immediately. + + + + + Pause() applied to an INITIAL Source is a legal NOP. + Pause() applied to a PLAYING Source will change its state to + PAUSED. The Source is exempt from processing, its current + state is preserved. + Pause() applied to a PAUSED Source is a legal NOP. + Pause() applied to a STOPPED Source is a legal NOP. + + + + + Stop() applied to an INITIAL Source is a legal NOP. + Stop() applied to a PLAYING Source will change its state to + STOPPED. The Source is exempt from processing, its current + state is preserved. + Stop() applied to a PAUSED Source will change its state + to STOPPED, with the same consequences as on a PLAYING + Source. + Stop() applied to a STOPPED Source is a legal NOP. + + + + + Rewind() applied to an INITIAL Source is a legal NOP. + Rewind() applied to a PLAYING Source will change its state to + STOPPED then INITIAL. The Source is exempt from processing: + its current state is preserved, with the exception of the + sampling offset, which is reset to the beginning. + Rewind() applied to a PAUSED Source will change its state + to INITIAL, with the same consequences as on a PLAYING + Source. + Rewind() applied to a STOPPED Source promotes the Source + to INITIAL, resetting the sampling offset to the beginning. + + + + + + Annotation (SourceNext) + The specification does not provide any means to + immediately skip from the current Buffer to the + next in the queue. A conditional stop (following + the next complete traversal) is available. + If necessary an additonal entry point could be + provided in future revisions. + + ]]> + + Annotation (Rewind() optional) + The INITIAL state is not identical to the STOPPED state. + Applications that want to verify whether a Source + has indeed been PLAYING before becoming STOPPED can + use Rewind() to reset the Source state to INITIAL. + This is an optional operation that can safely be + omitted by application without this constraint. + Applications that want to guard against Play() on + a Source that is INITIAL can query the Source state + first. + + ]]> + + Annotation (Play() on a PLAYING Source) + Repeated Play() commands applied a PLAYING Source are + interpreted as an (atomic) sequence to stop and restart a + Source. This can be used by applications that want to restart + a sound but do not care whether the Source has finished or not, + and do not want an audible pause. One example is the DOOM + chaingun repeatedly abbreviating the pistol sound. To guard + against redundant Play() commands, an application can query + the current state before executing Play(). If the application + coder wants to be sure that the Source will play the buffer + again, she can either increment PLAY_COUNT, or queue the buffer. + + ]]> + + Annotation (redundant commands) + The simple variant (e.g. SourcePlay) is redundant to + the vector variant (e.g. SourcePlayv). However, these + calls will be used frequently, and the simple variant + is provided for convenience. However, &AL; does not + enable applications to use literals as source names. + + ]]> + + + + + Resetting Configuration + + The INITIAL state is not necessarily identical to the + default state in which Source is created. INITIAL merely + indicates that the Source can be executed using the + SourcePlay command. A STOPPED or INITIAL Source can + be reset into the default configuration by using a + sequence Source commands as necessary. As the application + has to specify all relevant state anyway to create a + useful Source configuration, no reset command is provided. + + + + RFC: remove INITIAL + INITIAL is identical to STOPPED. The only additional information + conveyed is that INITIAL indicates a source has never been played. + Once a Source is STOPPED, it is not possible by state query alone + to decide whether it has played again. If Sources are used only + once, an application can use INITIAL to verify a Source has been + played. + The problem that I have with this is that if we acknowledge that + the application might need to verify a Source has played once, + why force the application to throw away Sources to accomplish + this? An explicit state PLAYABLE replacing INITIAL and its + inauspicious connotations (default state) and a state transition + function Rewind() that makes a STOPPED Source PLAYABLE again would + be one possibility to address this need. The obvious drawback is + that it breaks backwards compatibility. + + ]]> + + + RFC: state issues + A Source is active if it is PAUSED or PLAYING. + + A Source that is STOPPED preserves configuration state, + including buffer/queue information. + + Only a Source that is Reset() to INITIAL looses all + buffer and queue information. In this case, the INITIAL + + Sources will be stopped implicitely when reaching the + end of a non-repeating (non-looping) buffer traversal. + Sources can be stopped explicitely by the application + with either Stop() or Reset(). + + Stop() propagates + the source to STOPPED preserving its configuration state, + setting its execution state to the same as if it reached + the end of execution. + + + ]]> + + + Annotation (illegal NOPs) + In the current specification there are no illegal NOPs. + In other words, no sequence of commands affecting the + execution state will generate an INVALID_OPERATION error. + + ]]> + + + RFC/bk000504: + No UNDEFINED state. Always valid state. I.e. we have a default Buffer + that is used for sources where the application doesn't specify, + and what's in it? Default gain is zero? We need to specify + INITIAL. + + + RFC/bk000504: + Potential ambiguity: how to we distinguish STOPPED as + requested by the application from INACTIVE for + non-looping sounds once the buffer has been iterated? + Related: handling of Sources using an underflowing + streaming buffer? IMO not recommended, make this + undefined on error. + + + + RFC/bk000504: + Possible redundancy: the only reason for STOP seems to + be resetting the play positions. Redundant if we + ever manipulate offsets directly (rewind/set). + + + RFC/bk000504: + Possible redundancy: + If we ever want to support explicit setting of the start + position/offset into Buffer, START is equivalent to Set(0). + Also see LOOP (implies has to be STOPPED). Fade-Out and + Fade-In control - always manually? + + + ]]> + + + + + + diff --git a/src/openal/docs/chp-state.sgml b/src/openal/docs/chp-state.sgml new file mode 100644 index 0000000..8175a36 --- /dev/null +++ b/src/openal/docs/chp-state.sgml @@ -0,0 +1,730 @@ + + + State and State Requests + + The majority of &AL; state is associated with individual &AL; objects, + and has to be set and queried referencing the objects. However, some + state - e.g. processing errors - is + defined context specific. &AL; has global state that affects all + objects and processing equally. This state is set using a variety + of functions, and + can be queried using query functions. The majority of queries + has to use the interface described in "Simple Queries". + + + + + Querying &AL; State + + + + Simple Queries + + Like &OGL;, &AL; uses a simplified interface for querying global + state. The following functions accept a set of enumerations. + + void GetBooleanv + &enum; paramName + &bool;* dest + + + + void GetIntegerv + &enum; paramName + ∫* dest + + + + void GetFloatv + &enum; paramName + &float;* dest + + + + void GetDoublev + &enum; paramName + &double;* dest + + Legal values are e.g. DOPPLER_FACTOR, DOPPLER_VELOCITY, DISTANCE_MODEL. + + + NULL destinations are quietly ignored. INVALID_ENUM is the + response to errors in specifying paramName. The amount of memory + required in the destination depends on the actual state requested. + Usually, state variables are returned in only one or some of the + formats above. + + + To query state controlled by Enable/Disable there is an additional + IsEnabled function defined (see "Controlling &AL; Execution"). + + + + + + Data Conversions + + If a Get command is issued that returns value types different from + the type of the value being obtained, a type converswion is + performed. If GetBooleanv is called, a floating-point or integer + value converts to FALSE if and only if it is zero (otherwise + it converts to TRUE). If GetIntegerv is called, a boolean value + is interpreted as either 1 or 0, and a floating-point value is + rounded to the nearest integer. If GetFloatv is called, a boolean + value is interpreted as either 1.0 or 0.0, an integer is + coerced to floating point, and a double-presicion foating-point + value is converted to single precision. Analogous conversions are + carried out in the case of GetDoublev. If a value is so large + in magnitude that it cannot be represented with the requested + type, then the nearest value is representable using the requested + type is returned. + + Annotation (Query of Modes) + Modes (e.g. the current distance model) can be queried + using the respective tokens. The recommended query + command is GetIntegerv. + + ]]> + + + + + String Queries + + The application can retrieve state information global to the current + &AL; Context. GetString will return a pointer to a constant string. + Valid values for param are VERSION, RENDERER, VENDOR, and EXTENSIONS, + as well as the error codes defined by &AL;. The application can + use GetString to retrieve a string for an error code. + + const &ubyte;* GetString + &enum; paramName + + + + + + + + Time and Frequency + + By default, &AL; uses seconds and Hertz as units for + time and frequency, respectively. + A float or integral value of one + for a variable that specifies quantities like duration, + latency, delay, or any other parameter measured as time, + specifies 1 second. + For frequency, the basic unit is 1/second, or Hertz. + In other words, sample frequencies and frequency + cut-offs or filter parameters specifying frequencies + are expressed in units of Hertz. + + + RFC: Query and Factor? + Will time be an &AL; state that can be queried and scaled? + &AL; usually (always) implements a real-time + process with the constraint that it has to be + synchronized with the time as experienced by the user. + Do the units used with respect to time and frequency have + a fixed meaning? + + ]]> + + RFC: Setting Time/Long + Given a frequency range from a few Hz to 50kHz or more, + we need a temporal resolution of 20 microseconds or less. + For simplicity that means we either resolve Milliseconds + and loose precision, or we resolve microseconds. + Applications might run hours or days, which is 10E5 seconds + or more. If we cover 12 orders of magnitude (10E6 to 10E-6) + 32bit unsigned integer will not suffice. Do we introduce + a 64 signed integer for the purpose of specifying time + over a duration of several days at microseconds resolution, + or do we use two 32bit integers, and how do we split them? + + ]]> + + RFC: Duration Query + + The application might want to cull Sources based on + how many milliseconds are left for their current buffer + or queue (either to stop those that will stop soon + anyway, or to stop those that will take too long, + depending). + + + We need to divorce sample (memory) count from duration, + because of compression and differences between + internal and external sample format. + + + Duration can be queried in microsecond resolution in + case we want to account for O(10Khz) sampling + frequencies properly, or milliseconds if we + do not anticipate the need to go beyond typical + operating system time resolution. + + + We need query as double, float, and possibly + long (as of now an undefined data type). + We might need an INVALID_RANGE error on the getter, + especially if large reptition counters can be set. + For paused source, the remainder will be the same + as for playing sources. The application can query + the Source State to find out the additional + information. INFINITE is not needed, + no is UNKNOWN as we operate on known + queues. + + ]]> + + Annotation (Buffer vs. Queue Duration) + Sourcel( sName , REMAINDER, &rem ) queries the remainder + for the current buffer (zero if the Source is STOPPED, + duration of the first buffer if the source is INITIAL). + Sourcel( sName, REMAINDER_QUEUE, &rem ) queries the remainder + of the entire queue. + A STOPPED Source has remainder==0. + An INITIAL Source has full remainder. + + ]]> + + + Annotation (DURATION vs. REMAINDER) + DURATION is a buffer attribute independent of + execution state. REMAINDER is a Source attribute + that encapsulates part of the execution state. + Sources and Buffers should not share these attributes: + there is no Buffer REMAINDER and no Source DURATION. + + ]]> + ]]> + + + + + + + Space and Distance + + &AL; does not define the units of measurement for distances. + The application is free to use meters, inches, or parsecs. + &AL; provides means for simulating the natural attenuation + of sound according to distance, and to exagerate or reduce + this effect. However, the resulting effects do not depend on + the distance unit used by the application to express source + and listener coordinates. &AL; calculations are scale + invariant. + + + The specification assumes Euclidean calculation of + distances, and mandates that if two Sources are + sorted with respect to the Euclidean metric, the + distance calculation used by the implementation has + to preserve that order. + + + Annotation (No DistanceFactor) + &AL; does not provide a global, per Context DISTANCE_FACTOR, + as all calculations are scale invariant. + + ]]> + + Annotation (Distance Calculations) + The specification does not enforce that distances + are calculated using the euclidean norm, to permit + using computationally cheaper approximations. + Implementations that opt to use approximations + might cause audible artifacts. The specification does + not enforce any upper limits on distance calculation + errors yet. + + ]]> + + RFC: Limiting Errors + Do we limit permissible errors on distance or gain + calculations? How much quality + and accuracy do we expect from conformant implementations? + + ]]> + + + Annotation (DS3D DistanceFactor) + The DS3D documentation explicitely states that the default + unit is 1 meter. &AL; does not specify any units. + &AL; calculations are scale invariant. The main purpose + of the DS3D DistanceFactor (modifying Doppler Shift + by scaling velocity but not reference velocity) is + accomplished using DOPPLER_VELOCITY in &AL;. &AL; + does not have an equivalent to A3D's SetUnitsPerMeter + either. + + + ]]> + + + + + + + Attenuation By Distance + + Samples usually use the entire dynamic range of the + chosen format/encoding, independent of their real world + intensity. In other words, a jet engine and a clockwork + both will have samples with full amplitude. The application + will then have to adjust Source GAIN accordingly to account + for relative differences. + + + Source GAIN is then attenuated by distance. + The effective attenuation of a Source depends on many + factors, among which distance attenuation and source + and Listener GAIN are only some of the contributing + factors. Even if the source and Listener GAIN exceed 1.0 + (amplification beyond the guaranteed dynamic range), + distance and other attenuation might ultimately limit + the overall GAIN to a value below 1.0. + + + &AL; currently supports three modes of operation + with respect to distance attenuation. It supports + two distance-dependent attenuation models, including one + that is similar to the IASIG I3DL2 (and DS3D) model. + The application chooses one of these two models (or + chooses to disable distance-dependent attenuation) + on a per-context basis. + + void DistanceModel + &enum; modelName + + Legal arguments are NONE, INVERSE_DISTANCE, and + INVERSE_DISTANCE_CLAMPED. NONE bypasses all distance + attenuation calculation for all Sources. The implementation + is expected to optimize this situation. INVERSE_DISTANCE_CLAMPED + is the DS3D model, with REFERENCE_DISTANCE indicating both the + reference distance and the distance below which gain will + be clamped. INVERSE_DISTANCE is equivalent to the DS3D model + with the exception that REFERENCE_DISTANCE does not imply any + clamping. The &AL; implementation is still free to apply + any range clamping as necessary. The current distance model + chosen can be queried using GetIntegerv and DISTANCE_MODEL. + + + Annotation (Inverse Square Law) + The "inverse square law" used in physics applies to sound + intensity (energy), which is proportional to the square + of linear gain (amplitude). Thus the inverse distance model + describes a physically correct inverse square behavior + if ROLLOFF_FACTOR is set to 1.0. + + ]]> + + Annotation (Enable/Disable Attenuation) + As ROLLOFF_FACTOR is a per-Source attribute, setting it to zero + can not be used to globally enable or disable distance + attenuation, which (e.g. when using tables) can be resource + intensive. Using Enable/Disable/IsEnabled with a DISTANCE_ATTENUATION + token is redundant with respect to the possibility that support + for different distance models might be desired at a later time. + + ]]> + + + + Inverse Distance Rolloff Model + + The following formula describes the distance attenutation + defined by the Rolloff Attenutation Model, as logarithmic + calculation. + + + G_dB = GAIN - 20*log10(1 + ROLLOFF_FACTOR*(dist-REFERENCE_DISTANCE)/REFERENCE_DISTANCE ); + G_dB = min(G_dB,MAX_GAIN); + G_dB = max(G_dB,MIN_GAIN); + + + The REFERENCE_DISTANCE parameter used here is a per-Source attribute + that can be set and queried using the REFERENCE_DISTANCE token. + REFERENCE_DISTANCE is the distance at which the Listener will + experience GAIN (unless the implementation had to clamp effective + GAIN to the available dynamic range). + ROLLOFF_FACTOR is per-Source parameter the application can use + to increase or decrease the range of a source by decreasing + or increasing the attenuation, respectively. The default value + is 1. The implementation is free to optimize for a + ROLLOFF_FACTOR value of 0, which indicates that the application + does not wish any distance attenuation on the respective Source. + + + Annotation (Linear Calculation) + The logarithmic formula above is equivalent to + + G = gain_linear / ( 1 + ROLLOFF_FACTOR*((dist-REFERENCE_DISTANCE)/REFERENCE_DISTANCE) ); + G = min(G,max_gain_linear); + G = max(G,min_gain_linear); + + with linear gains calculated from the logarithmic GAIN, + MIN_GAIN, MAX_GAIN accordingly. + By means of explanation: linear GAIN is applied to the sample, which describes + an amplitude ultimately (DAC) converted into voltage. The actual power of the + signal is proportional to the square of the amplitude (voltage). Logarithmic + measurement is done by comparing the actual power with a reference value, + i.e. the power (e.g in Watt) at the reference distance. The original Bel unit + of measure (named after Alexander Graham Bell) was defined to account for the + logarithmic response of the human ear: our subjective impression of "loudness" + is not linear in the power of the acoustic signal. For practical purposes (range + of volumes the human ear can handle) the deciBel (dB) is a better unit: + + dB = 10 * log( P/P0 ) = 10 * log( sqr(A/A0 ) = 20 * log( A/A0 ) + + Common power/amplitude ratios and attenuations per distance are: + + Logarithmic Scale and Gain + + + + Distance + Attenuation + Power Ratio + Amplitude Ratio + + + + + REF + 0dB + 1:1 + 1:1 + + + 2*REF + -6dB + 1:4 + 1:2 + + + 4*REF + -12dB + 1:16 + 1:4 + + + 8*REF + -18dB + 1:64 + 1:8 + + + 0.5*REF + 6dB + 2:1 + 4:1 + + + 0.25*REF + 12dB + 4:1 + 16:1 + + + + +
+ The logarithmic gain will drop from zero (linear gain 1) to negative infinity + (approaching linear gain 0). A linear gain of zero can not be represented + logarithmically. Any doubling of the reference distance will add another + -6dB (i.e. 6dB of attenuation). This approximates an inverse square law falloff + of signal power with distance, as long as the distance exceeds the reference + distance. +
+ ]]> + + Annotation (Rolloff quantization) + Implementations that use lookup tables to speed up + distance attenuation calculation may opt to map + ROLLOFF_FACTOR to a limited set of internally used + values, to minimize expense of per-Source calculations + and setup/memory costs. + + ]]> + + Annotation (Gain Clamping) + In the absence of user MIN_GAIN and MAX_GAIN selections, + clamping is implied by implementation constraints, and + clamping behavior might change. + The &AL; implementation should not clamp intermediate + values of effective gain to unit range. + Any clamping, if necessary, should be applied at the latest + possible stage. In other words, GAIN>1 is perfectly + valid as the implementation is free to clamp the value as + needed for maximum mixing accuracy and to account for the + actual dynamic range of the output device. + + ]]> + + + Annotation (Extended Dynamic Range) + For applications that + change GAIN but do not want to adjust ROLLOFF_FACTOR + and REFERENCE_DISTANCE to account for different ranges, + the separation in this distance model might allow for + more intuitive adjustments: + If we put a damper on the jet engine by lowering GAIN, + we still want the listener to perceive the full volume, + but now at a closer distance, without changing the + reference distance. + + ]]> +
+ + + Inverse Distance Clamped Model + + This is essentially the Inverse Distance model, + extended to guarantee that for distances below + REFERENCE_DISTANCE, gain is clamped. This mode + is equivalent to the IASIG I3DL2 (and DS3D) distance + model. + + dist = max(dist,REFERENCE_DISTANCE); + dist = min(dist,MAX_DISTANCE); + G_dB = GAIN - 20*log10(1 + ROLLOFF_FACTOR*(dist-REFERENCE_DISTANCE)/REFERENCE_DISTANCE ) + G_dB = min(G_dB,MAX_GAIN); + G_dB = max(G_dB,MIN_GAIN); + + + + + Annotation (DS3D MIN_DISTANCE) + The DS3D attenuation model is extended by an explicit + clamping mechanism. REFERENCE_DISTANCE is equivalent + to DS3D MIN_DISTANCE if the INVERSE_DISTANCE_CLAMPED + mode is used. + + ]]> + + Annotation (High Frequency Rolloff) + To simulate different atmospheric conditions, a frequency + dependent attenuation is used in A3D and EAX. + At this time &AL; does not have a mechanism to specify + lowpass filtering parameterized by distance. + + ]]> + +
+ + + + Evaluation of Gain/Attenuation Related State + + While amplification/attenuation commute (mulplication of + scaling factors), clamping operations do not. The order + in which various gain related operations are applied is: + Distance attenuation is calculated first, including + minimum (REFERENCE_DISTANCE) and maximum (MAX_DISTANCE) + thresholds. If the Source is directional (CONE_INNER_ANGLE + less than CONE_OUTER_ANGLE), an angle-dependent attenuation + is calculated depending on CONE_OUTER_GAIN, and multiplied + with the distance dependent attenuation. + The resulting attenuation factor for the given angle and + distance between Listener and Source is multiplied + with Source GAIN. The effective GAIN computed this way + is compared against MIN_GAIN and MAX_GAIN thresholds. + The result is guaranteed to be clamped to [MIN_GAIN, MAX_GAIN], + and subsequently multiplied by Listener GAIN which serves + as an overall volume control. The implementation is free + to clamp Listener GAIN if necessary due to hardware or + implementation constraints. + + + + + + + No Culling By Distance + + With the DS3D compatible Inverse Clamped Distance Model, + &AL; provides a per-Source MAX_DISTANCE attribute + that can be used to define a distance beyond which the Source will + not be further attenuated by distance. + The DS3D distance attenuation model and its clamping of volume + is also extended by a mechanism to cull (mute) sources from proccessing, + based on distance. However, &AL; does not + support culling a Source from processing based on a distance + threshold. + + + At this time &AL; is not meant to support culling at all. Culling + based on distance, or bounding volumes, or other criteria, is best + left to the application. For example, the application might + employ sophisticated techniques to determine whether sources + are audible that are beyond the scope of &AL;. In particular, + rule based culling inevitably introduces acoustic artifacts. + E.g. if the Listener-Source distance is nearly equal to the + culling threshold distance, but varies above and below, there + will be popping artifacts in the absence of hysteresis. + + + Annotation (No MAX_DISTANCE plus MUTE) + &AL; does support the AUDIBLE mode with MAX_DISTANCE (clamping), + but does not support culling. Applications that employ this + DS3D feature will have to perform their own distance calculation + and mute the source (Source GAIN equals zero) as desired. + + ]]> + + RFC: DS3D-like Extension + For ease of portability, should we define an Extension that + provides MAX_DISTANCE for MUTE mode? + + ]]> + + + + + + NO Sound Propagation Speed + + &AL; implementations can choose to model sound propagation with + limited speed for certain effects, e.g. early reflections. + In addition, the Doppler Effect is defined with respect to the + speed of sound in the predominant medium. The application can + set the speed of sound as a scalar value. The speed is defined + with respect to the scaled unit. If D I S T A N C E _ S C A L E is changed, + this will affect both the distance and the propagation speed, + leaving the propagation time unaffected. + + void PropagationSpeed + &float; propSpeed + + + + A negative scale will result in an INVALID_VALUE error, the + command is then ignored. The default value is 1. The current + setting can be queried using GetFloatv and PROPAGATION_SPEED. + + + ]]> + + + Velocity Dependent Doppler Effect + + The Doppler Effect depends on the velocities of Source and + Listener relative to the medium, and the propagation speed + of sound in that medium. The application might want to + emphasize or de-emphasize the Doppler Effect as physically + accurate calculation might not give the desired results. The + amount of frequency shift (pitch change) is proportional + to the speed of listener and source along their line of + sight. The application can increase or decrease that + frequency shift by specifying the scaling factor &AL; + should apply to the result of the calculation. + + + The Doppler Effect as implemented by &AL; is described + by the formula below. Effects of the medium (air, water) + moving with respect to listener and source are ignored. + DOPPLER_VELOCITY is the propagation speed relative to + which the Source velocities are interpreted. + + VD: DOPPLER_VELOCITY + DF: DOPPLER_FACTOR + vl: Listener velocity (scalar, projected on source-listener vector) + vs: Source velocity (scalar, projected on source-listener vector) + f: Frequency of sample + f': effective Doppler shifted frequency + + f' = DF * f * (VD-vl)/(VD+vs) + + vl<0, vs>0 : source and listener approaching each other + vl>0, vs<0 : source and listener moving away from each other + + The implementation has to clamp the projected Listener + velocity vl, if abs(vl) is greater or equal to VD. It similarly has to + clamp the projected Source velocity vs if abs(vs) is greater or equal + to VD. + + + There are two API calls global to the current context that provide + control of the two related parameters. DOPPLER_FACTOR is a simple + scaling to exaggerate or deemphasize the Doppler (pitch) shift + resulting from the calculation. + + void DopplerFactor + &float; dopplerFactor + + A negative value will result in an INVALID_VALUE error, the + command is then ignored. The default value is 1. The current + setting can be queried using GetFloatv and DOPPLER_FACTOR. + The implementation is free to optimize the case of DOPPLER_FACTOR + being set to zero, as this effectively disables the effect. + + + Annotation (No Enable) + There is currently no mechanism to switch on/off Doppler + calculation using e.g. a DOPPLER_SHIFT token and Enable/Disable. + For the time being, DopplerFactor(0) may be used to signal + to the implementation that no Doppler Effect calculation is + required. + + ]]> + + + DOPPLER_VELOCITY allows the application to change the + reference (propagation) velocity used in the Doppler Effect + calculation. This permits the application to use a velocity + scale appropriate to its purposes. + + void DopplerVelocity + &float; dopplerVelocity + + A negative or zero value will result in an INVALID_VALUE error, and the + command is ignored. The default value is 1. + The current setting can be queried using GetFloatv and DOPPLER_VELOCITY. + + + Annotation (No Sideeffects on Delay) + To permit independent control of Doppler Effect as opposed + to other, sound wave propagation related effects (delays, + echos, reverberation), DOPPLER_VELOCITY is not taken into + account for any other calculation than Doppler Shift. + + ]]> + + Annotation (SetUnitsPerMeter) + DOPPLER_VELOCITY accomplishes the purposes of DS3D + scaling parameters in a straightforward way, without + introducing the undesirable connotations of real world + units. + + ]]> + + +
+ diff --git a/src/openal/docs/ent-examples.sgml b/src/openal/docs/ent-examples.sgml new file mode 100644 index 0000000..2495773 --- /dev/null +++ b/src/openal/docs/ent-examples.sgml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/openal/docs/ent-extensions.sgml b/src/openal/docs/ent-extensions.sgml new file mode 100644 index 0000000..c4acd93 --- /dev/null +++ b/src/openal/docs/ent-extensions.sgml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/openal/docs/ent-marks-annotated.sgml b/src/openal/docs/ent-marks-annotated.sgml new file mode 100644 index 0000000..2cbe4d3 --- /dev/null +++ b/src/openal/docs/ent-marks-annotated.sgml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/openal/docs/ent-marks-full.sgml b/src/openal/docs/ent-marks-full.sgml new file mode 100644 index 0000000..eb5515a --- /dev/null +++ b/src/openal/docs/ent-marks-full.sgml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/openal/docs/ent-marks-reference.sgml b/src/openal/docs/ent-marks-reference.sgml new file mode 100644 index 0000000..bb34d5e --- /dev/null +++ b/src/openal/docs/ent-marks-reference.sgml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/openal/docs/ent-marks-specification.sgml b/src/openal/docs/ent-marks-specification.sgml new file mode 100644 index 0000000..f68660a --- /dev/null +++ b/src/openal/docs/ent-marks-specification.sgml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/openal/docs/ent-names.sgml b/src/openal/docs/ent-names.sgml new file mode 100644 index 0000000..9da1b97 --- /dev/null +++ b/src/openal/docs/ent-names.sgml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/openal/docs/index.html b/src/openal/docs/index.html new file mode 100644 index 0000000..d4060d1 --- /dev/null +++ b/src/openal/docs/index.html @@ -0,0 +1,21 @@ + + + + + Index + + + + +

Index

+

+ This document has been removed. + See www.openal.org + for information. +

+ + + + + + diff --git a/src/openal/docs/oalspecs.sgml b/src/openal/docs/oalspecs.sgml new file mode 100644 index 0000000..fb8d3b3 --- /dev/null +++ b/src/openal/docs/oalspecs.sgml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + %Marks; + %Entities; + %Examples; + %Extensions; + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + + + &secBookInfo; + + + &chpIntroduction; + + + &chpOperation; + &chpState; + &chpRendering; + &chpBuffers; + &chpQueueing; + + + + &alcContext; + + + + &appConstants; + &appExtensions; + &appExtensionProcess; + + + + + + Main Document CVS Revision History + + CVS Document: $Id: oalspecs.sgml,v 1.15 2000/11/10 23:10:44 bk Exp $ + CVS Revision: $Revision: 1.15 $ + + $Log: oalspecs.sgml,v $ + Revision 1.15 2000/11/10 23:10:44 bk + Cleanup. + + Revision 1.14 2000/11/04 00:11:40 bk + Maintenance update + + Revision 1.13 2000/10/26 03:01:55 bk + Fixed typo for ALC entity. + + Revision 1.12 2000/10/26 02:59:46 bk + External entities. + + + +]]> + + + + + diff --git a/src/openal/docs/sec-bookinfo.sgml b/src/openal/docs/sec-bookinfo.sgml new file mode 100644 index 0000000..26b3b7a --- /dev/null +++ b/src/openal/docs/sec-bookinfo.sgml @@ -0,0 +1,53 @@ + + + + &OAL; Specification and Reference + + + + Version 1.0 Draft + + June 2000 + + 1999-2000 + Loki Software + + + + + Permission is granted to make and distribute verbatim copies of this + manual provided the copyright notice and this permission notice are + preserved on all copies. + + + + + Permission is granted to copy and distribute translations of this + manual into another language, under the above conditions for modified + versions, except that this permission notice may be stated in a + translation approved by the copyright owners. + + + + UNIX is a trademark of X/Open Group. + X Window System is a trademark of X Consortium, Inc. + Linux is a trademark of Linus Torvalds. + Windows is a trademark of Microsoft Corp. + Macintosh and Apple are trademarks of Apple Computer, Inc. + Loki and &OAL; are trademarks of Loki Software, Inc. + All other trademarks are property of their respective owners. + + + + Draft document of &OAL; Specification. + + + + + + \ No newline at end of file diff --git a/src/openal/docs/specification.html b/src/openal/docs/specification.html new file mode 100644 index 0000000..0d51804 --- /dev/null +++ b/src/openal/docs/specification.html @@ -0,0 +1,18 @@ + + + + +OpenAL Specification + + + + +

OpenAL Specification

+

+ This document has been removed. + See Index + for the specification. +

+ + + diff --git a/src/openal/docs/white-paper.html b/src/openal/docs/white-paper.html new file mode 100644 index 0000000..3c76354 --- /dev/null +++ b/src/openal/docs/white-paper.html @@ -0,0 +1,16 @@ + + + + OpenAL Whitepaper + + + +

OpenAL Whitepaper

+

+ This document has been removed. + See Index + for the specification. +

+ + + diff --git a/src/openal/idal.cpp b/src/openal/idal.cpp new file mode 100644 index 0000000..3e40cdb --- /dev/null +++ b/src/openal/idal.cpp @@ -0,0 +1,185 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// generated header. do not edit +// C:\Python23\Lib\idlelib\idle.pyw +// Mon Mar 28 12:31:26 2005 + +#include "../idlib/precompiled.h" +#include "../sound/snd_local.h" + +ALenum ( ALAPIENTRY * idalGetError )( ALvoid ) = NULL; +ALvoid ( ALAPIENTRY * idalGenBuffers )( ALsizei, ALuint * ) = NULL; +ALboolean ( ALAPIENTRY * idalIsSource )( ALuint ) = NULL; +ALvoid ( ALAPIENTRY * idalSourceStop )( ALuint ) = NULL; +ALvoid ( ALAPIENTRY * idalGetSourcei )( ALuint, ALenum, ALint * ) = NULL; +ALint ( ALAPIENTRY * idalGetInteger )( ALenum ) = NULL; +ALCvoid ( ALAPIENTRY * idalcSuspendContext )( ALCcontext * ) = NULL; +ALCboolean ( ALAPIENTRY * idalcMakeContextCurrent )( ALCcontext * ) = NULL; +ALCvoid ( ALAPIENTRY * idalcProcessContext )( ALCcontext * ) = NULL; +ALCvoid ( ALAPIENTRY * idalcDestroyContext )( ALCcontext * ) = NULL; +ALCubyte * ( ALAPIENTRY * idalcGetString )( ALCdevice *, ALCenum ) = NULL; +ALvoid ( ALAPIENTRY * idalBufferData )( ALuint, ALenum, ALvoid *, ALsizei, ALsizei ) = NULL; +ALvoid ( ALAPIENTRY * idalDeleteBuffers )( ALsizei, ALuint * ) = NULL; +ALboolean ( ALAPIENTRY * idalIsExtensionPresent )( ALubyte * ) = NULL; +ALvoid ( ALAPIENTRY * idalDeleteSources )( ALsizei, ALuint * ) = NULL; +ALenum ( ALAPIENTRY * idalGetEnumValue )( ALubyte * ) = NULL; +ALvoid * ( ALAPIENTRY * idalGetProcAddress )( ALubyte * ) = NULL; +ALCcontext * ( ALAPIENTRY * idalcCreateContext )( ALCdevice *, ALCint * ) = NULL; +ALCdevice * ( ALAPIENTRY * idalcOpenDevice )( ALubyte * ) = NULL; +ALvoid ( ALAPIENTRY * idalListenerfv )( ALenum, ALfloat* ) = NULL; +ALvoid ( ALAPIENTRY * idalSourceQueueBuffers )( ALuint, ALsizei, ALuint * ) = NULL; +ALvoid ( ALAPIENTRY * idalSourcei )( ALuint, ALenum, ALint ) = NULL; +ALvoid ( ALAPIENTRY * idalListenerf )( ALenum, ALfloat ) = NULL; +ALCvoid ( ALAPIENTRY * idalcCloseDevice )( ALCdevice * ) = NULL; +ALboolean ( ALAPIENTRY * idalIsBuffer )( ALuint ) = NULL; +ALvoid ( ALAPIENTRY * idalSource3f )( ALuint, ALenum, ALfloat, ALfloat, ALfloat ) = NULL; +ALvoid ( ALAPIENTRY * idalGenSources )( ALsizei, ALuint * ) = NULL; +ALvoid ( ALAPIENTRY * idalSourcef )( ALuint, ALenum, ALfloat ) = NULL; +ALvoid ( ALAPIENTRY * idalSourceUnqueueBuffers )( ALuint, ALsizei, ALuint * ) = NULL; +ALvoid ( ALAPIENTRY * idalSourcePlay )( ALuint ) = NULL; + +const char* InitializeIDAL( HMODULE h ) { +idalGetError = ( ALenum ( ALAPIENTRY * ) ( ALvoid ) )GetProcAddress( h, "alGetError" ); +if ( !idalGetError) { + return "alGetError"; +} +idalGenBuffers = ( ALvoid ( ALAPIENTRY * ) ( ALsizei, ALuint * ) )GetProcAddress( h, "alGenBuffers" ); +if ( !idalGenBuffers) { + return "alGenBuffers"; +} +idalIsSource = ( ALboolean ( ALAPIENTRY * ) ( ALuint ) )GetProcAddress( h, "alIsSource" ); +if ( !idalIsSource) { + return "alIsSource"; +} +idalSourceStop = ( ALvoid ( ALAPIENTRY * ) ( ALuint ) )GetProcAddress( h, "alSourceStop" ); +if ( !idalSourceStop) { + return "alSourceStop"; +} +idalGetSourcei = ( ALvoid ( ALAPIENTRY * ) ( ALuint, ALenum, ALint * ) )GetProcAddress( h, "alGetSourcei" ); +if ( !idalGetSourcei) { + return "alGetSourcei"; +} +idalGetInteger = ( ALint ( ALAPIENTRY * ) ( ALenum ) )GetProcAddress( h, "alGetInteger" ); +if ( !idalGetInteger) { + return "alGetInteger"; +} +idalcSuspendContext = ( ALCvoid ( ALAPIENTRY * ) ( ALCcontext * ) )GetProcAddress( h, "alcSuspendContext" ); +if ( !idalcSuspendContext) { + return "alcSuspendContext"; +} +idalcMakeContextCurrent = ( ALCboolean ( ALAPIENTRY * ) ( ALCcontext * ) )GetProcAddress( h, "alcMakeContextCurrent" ); +if ( !idalcMakeContextCurrent) { + return "alcMakeContextCurrent"; +} +idalcProcessContext = ( ALCvoid ( ALAPIENTRY * ) ( ALCcontext * ) )GetProcAddress( h, "alcProcessContext" ); +if ( !idalcProcessContext) { + return "alcProcessContext"; +} +idalcDestroyContext = ( ALCvoid ( ALAPIENTRY * ) ( ALCcontext * ) )GetProcAddress( h, "alcDestroyContext" ); +if ( !idalcDestroyContext) { + return "alcDestroyContext"; +} +idalcGetString = ( ALCubyte * ( ALAPIENTRY * ) ( ALCdevice *, ALCenum ) )GetProcAddress( h, "alcGetString" ); +if ( !idalcGetString) { + return "alcGetString"; +} +idalBufferData = ( ALvoid ( ALAPIENTRY * ) ( ALuint, ALenum, ALvoid *, ALsizei, ALsizei ) )GetProcAddress( h, "alBufferData" ); +if ( !idalBufferData) { + return "alBufferData"; +} +idalDeleteBuffers = ( ALvoid ( ALAPIENTRY * ) ( ALsizei, ALuint * ) )GetProcAddress( h, "alDeleteBuffers" ); +if ( !idalDeleteBuffers) { + return "alDeleteBuffers"; +} +idalIsExtensionPresent = ( ALboolean ( ALAPIENTRY * ) ( ALubyte * ) )GetProcAddress( h, "alIsExtensionPresent" ); +if ( !idalIsExtensionPresent) { + return "alIsExtensionPresent"; +} +idalDeleteSources = ( ALvoid ( ALAPIENTRY * ) ( ALsizei, ALuint * ) )GetProcAddress( h, "alDeleteSources" ); +if ( !idalDeleteSources) { + return "alDeleteSources"; +} +idalGetEnumValue = ( ALenum ( ALAPIENTRY * ) ( ALubyte * ) )GetProcAddress( h, "alGetEnumValue" ); +if ( !idalGetEnumValue) { + return "alGetEnumValue"; +} +idalGetProcAddress = ( ALvoid * ( ALAPIENTRY * ) ( ALubyte * ) )GetProcAddress( h, "alGetProcAddress" ); +if ( !idalGetProcAddress) { + return "alGetProcAddress"; +} +idalcCreateContext = ( ALCcontext * ( ALAPIENTRY * ) ( ALCdevice *, ALCint * ) )GetProcAddress( h, "alcCreateContext" ); +if ( !idalcCreateContext) { + return "alcCreateContext"; +} +idalcOpenDevice = ( ALCdevice * ( ALAPIENTRY * ) ( ALubyte * ) )GetProcAddress( h, "alcOpenDevice" ); +if ( !idalcOpenDevice) { + return "alcOpenDevice"; +} +idalListenerfv = ( ALvoid ( ALAPIENTRY * ) ( ALenum, ALfloat* ) )GetProcAddress( h, "alListenerfv" ); +if ( !idalListenerfv) { + return "alListenerfv"; +} +idalSourceQueueBuffers = ( ALvoid ( ALAPIENTRY * ) ( ALuint, ALsizei, ALuint * ) )GetProcAddress( h, "alSourceQueueBuffers" ); +if ( !idalSourceQueueBuffers) { + return "alSourceQueueBuffers"; +} +idalSourcei = ( ALvoid ( ALAPIENTRY * ) ( ALuint, ALenum, ALint ) )GetProcAddress( h, "alSourcei" ); +if ( !idalSourcei) { + return "alSourcei"; +} +idalListenerf = ( ALvoid ( ALAPIENTRY * ) ( ALenum, ALfloat ) )GetProcAddress( h, "alListenerf" ); +if ( !idalListenerf) { + return "alListenerf"; +} +idalcCloseDevice = ( ALCvoid ( ALAPIENTRY * ) ( ALCdevice * ) )GetProcAddress( h, "alcCloseDevice" ); +if ( !idalcCloseDevice) { + return "alcCloseDevice"; +} +idalIsBuffer = ( ALboolean ( ALAPIENTRY * ) ( ALuint ) )GetProcAddress( h, "alIsBuffer" ); +if ( !idalIsBuffer) { + return "alIsBuffer"; +} +idalSource3f = ( ALvoid ( ALAPIENTRY * ) ( ALuint, ALenum, ALfloat, ALfloat, ALfloat ) )GetProcAddress( h, "alSource3f" ); +if ( !idalSource3f) { + return "alSource3f"; +} +idalGenSources = ( ALvoid ( ALAPIENTRY * ) ( ALsizei, ALuint * ) )GetProcAddress( h, "alGenSources" ); +if ( !idalGenSources) { + return "alGenSources"; +} +idalSourcef = ( ALvoid ( ALAPIENTRY * ) ( ALuint, ALenum, ALfloat ) )GetProcAddress( h, "alSourcef" ); +if ( !idalSourcef) { + return "alSourcef"; +} +idalSourceUnqueueBuffers = ( ALvoid ( ALAPIENTRY * ) ( ALuint, ALsizei, ALuint * ) )GetProcAddress( h, "alSourceUnqueueBuffers" ); +if ( !idalSourceUnqueueBuffers) { + return "alSourceUnqueueBuffers"; +} +idalSourcePlay = ( ALvoid ( ALAPIENTRY * ) ( ALuint ) )GetProcAddress( h, "alSourcePlay" ); +if ( !idalSourcePlay) { + return "alSourcePlay"; +} +return NULL; +}; diff --git a/src/openal/idal.h b/src/openal/idal.h new file mode 100644 index 0000000..9b3e69b --- /dev/null +++ b/src/openal/idal.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// generated header. do not edit +// C:\Python23\Lib\idlelib\idle.pyw +// Mon Mar 28 12:31:26 2005 + +extern ALenum ( ALAPIENTRY * idalGetError )( ALvoid ); +extern ALvoid ( ALAPIENTRY * idalGenBuffers )( ALsizei, ALuint * ); +extern ALboolean ( ALAPIENTRY * idalIsSource )( ALuint ); +extern ALvoid ( ALAPIENTRY * idalSourceStop )( ALuint ); +extern ALvoid ( ALAPIENTRY * idalGetSourcei )( ALuint, ALenum, ALint * ); +extern ALint ( ALAPIENTRY * idalGetInteger )( ALenum ); +extern ALCvoid ( ALAPIENTRY * idalcSuspendContext )( ALCcontext * ); +extern ALCboolean ( ALAPIENTRY * idalcMakeContextCurrent )( ALCcontext * ); +extern ALCvoid ( ALAPIENTRY * idalcProcessContext )( ALCcontext * ); +extern ALCvoid ( ALAPIENTRY * idalcDestroyContext )( ALCcontext * ); +extern ALCubyte * ( ALAPIENTRY * idalcGetString )( ALCdevice *, ALCenum ); +extern ALvoid ( ALAPIENTRY * idalBufferData )( ALuint, ALenum, ALvoid *, ALsizei, ALsizei ); +extern ALvoid ( ALAPIENTRY * idalDeleteBuffers )( ALsizei, ALuint * ); +extern ALboolean ( ALAPIENTRY * idalIsExtensionPresent )( ALubyte * ); +extern ALvoid ( ALAPIENTRY * idalDeleteSources )( ALsizei, ALuint * ); +extern ALenum ( ALAPIENTRY * idalGetEnumValue )( ALubyte * ); +extern ALvoid * ( ALAPIENTRY * idalGetProcAddress )( ALubyte * ); +extern ALCcontext * ( ALAPIENTRY * idalcCreateContext )( ALCdevice *, ALCint * ); +extern ALCdevice * ( ALAPIENTRY * idalcOpenDevice )( ALubyte * ); +extern ALvoid ( ALAPIENTRY * idalListenerfv )( ALenum, ALfloat* ); +extern ALvoid ( ALAPIENTRY * idalSourceQueueBuffers )( ALuint, ALsizei, ALuint * ); +extern ALvoid ( ALAPIENTRY * idalSourcei )( ALuint, ALenum, ALint ); +extern ALvoid ( ALAPIENTRY * idalListenerf )( ALenum, ALfloat ); +extern ALCvoid ( ALAPIENTRY * idalcCloseDevice )( ALCdevice * ); +extern ALboolean ( ALAPIENTRY * idalIsBuffer )( ALuint ); +extern ALvoid ( ALAPIENTRY * idalSource3f )( ALuint, ALenum, ALfloat, ALfloat, ALfloat ); +extern ALvoid ( ALAPIENTRY * idalGenSources )( ALsizei, ALuint * ); +extern ALvoid ( ALAPIENTRY * idalSourcef )( ALuint, ALenum, ALfloat ); +extern ALvoid ( ALAPIENTRY * idalSourceUnqueueBuffers )( ALuint, ALsizei, ALuint * ); +extern ALvoid ( ALAPIENTRY * idalSourcePlay )( ALuint ); + +#define alGetError idalGetError +#define alGenBuffers idalGenBuffers +#define alIsSource idalIsSource +#define alSourceStop idalSourceStop +#define alGetSourcei idalGetSourcei +#define alGetInteger idalGetInteger +#define alcSuspendContext idalcSuspendContext +#define alcMakeContextCurrent idalcMakeContextCurrent +#define alcProcessContext idalcProcessContext +#define alcDestroyContext idalcDestroyContext +#define alcGetString idalcGetString +#define alBufferData idalBufferData +#define alDeleteBuffers idalDeleteBuffers +#define alIsExtensionPresent idalIsExtensionPresent +#define alDeleteSources idalDeleteSources +#define alGetEnumValue idalGetEnumValue +#define alGetProcAddress idalGetProcAddress +#define alcCreateContext idalcCreateContext +#define alcOpenDevice idalcOpenDevice +#define alListenerfv idalListenerfv +#define alSourceQueueBuffers idalSourceQueueBuffers +#define alSourcei idalSourcei +#define alListenerf idalListenerf +#define alcCloseDevice idalcCloseDevice +#define alIsBuffer idalIsBuffer +#define alSource3f idalSource3f +#define alGenSources idalGenSources +#define alSourcef idalSourcef +#define alSourceUnqueueBuffers idalSourceUnqueueBuffers +#define alSourcePlay idalSourcePlay diff --git a/src/openal/idal.py b/src/openal/idal.py new file mode 100644 index 0000000..3a8151b --- /dev/null +++ b/src/openal/idal.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# create win32 source for OpenAL on-demand loading from AL API definitions +# a set of defines al* -> idal* +# typedefs and code pointer functions definition + +# 1: get linking with no OpenAL DLL link in anymore +# i.e. have the defines and the code pointer working +# 2: do the load code + +import time + +funcs = [ + [ 'ALenum', 'alGetError', 'ALvoid' ], + [ 'ALvoid', 'alGenBuffers', 'ALsizei', 'ALuint *' ], + [ 'ALboolean', 'alIsSource', 'ALuint' ], + [ 'ALvoid', 'alSourceStop', 'ALuint' ], + [ 'ALvoid', 'alGetSourcei', 'ALuint', 'ALenum', 'ALint *' ], + [ 'ALint', 'alGetInteger', 'ALenum' ], + [ 'ALCvoid', 'alcSuspendContext', 'ALCcontext *' ], + [ 'ALCboolean', 'alcMakeContextCurrent', 'ALCcontext *' ], + [ 'ALCvoid', 'alcProcessContext', 'ALCcontext *' ], + [ 'ALCvoid', 'alcDestroyContext', 'ALCcontext *' ], + [ 'ALCubyte *', 'alcGetString', 'ALCdevice *', 'ALCenum' ], + [ 'ALvoid', 'alBufferData', 'ALuint', 'ALenum', 'ALvoid *', 'ALsizei', 'ALsizei' ], + [ 'ALvoid', 'alDeleteBuffers', 'ALsizei', 'ALuint *' ], + [ 'ALboolean', 'alIsExtensionPresent', 'ALubyte *' ], + [ 'ALvoid', 'alDeleteSources', 'ALsizei', 'ALuint *' ], + [ 'ALenum', 'alGetEnumValue', 'ALubyte *' ], + [ 'ALvoid *', 'alGetProcAddress', 'ALubyte *' ], + [ 'ALCcontext *', 'alcCreateContext', 'ALCdevice *', 'ALCint *' ], + [ 'ALCdevice *', 'alcOpenDevice', 'ALubyte *' ], + [ 'ALvoid', 'alListenerfv', 'ALenum', 'ALfloat*' ], + [ 'ALvoid', 'alSourceQueueBuffers', 'ALuint', 'ALsizei', 'ALuint *' ], + [ 'ALvoid', 'alSourcei', 'ALuint', 'ALenum', 'ALint' ], + [ 'ALvoid', 'alListenerf', 'ALenum', 'ALfloat' ], + [ 'ALCvoid', 'alcCloseDevice', 'ALCdevice *' ], + [ 'ALboolean', 'alIsBuffer', 'ALuint' ], + [ 'ALvoid', 'alSource3f', 'ALuint', 'ALenum', 'ALfloat', 'ALfloat', 'ALfloat' ], + [ 'ALvoid', 'alGenSources', 'ALsizei', 'ALuint *' ], + [ 'ALvoid', 'alSourcef', 'ALuint', 'ALenum', 'ALfloat' ], + [ 'ALvoid', 'alSourceUnqueueBuffers', 'ALuint', 'ALsizei', 'ALuint *' ], + [ 'ALvoid', 'alSourcePlay', 'ALuint' ], + ] + +def warningHeader( f ): + f.write( '// generated header. do not edit\n' ) + f.write( '// ' + __file__ + '\n' ) + f.write( '// ' + time.asctime() + '\n\n' ) + +def genIDALFunc( f, declare ): + if ( declare ): + extern = 'extern ' + else: + extern = '' + for func in funcs: + f.write( extern + func[0] + ' ( ALAPIENTRY * id' + func[1] + ' )( ' ) + i = 2 + while ( i < len( func ) ): + if ( i != 2 ): + f.write( ', ' ) + f.write( func[i] ) + i += 1 + if ( declare ): + f.write( ' );\n' ) + else: + f.write( ' ) = NULL;\n' ) + +def genDefineMapping( f ): + for func in funcs: + fname = func[1] + f.write( '#define %s id%s\n' % ( fname, fname ) ) + +def genIDALInit( f ): + for func in funcs: + # annoying casting + cast = func[0] + ' ( ALAPIENTRY * ) ( ' + i = 2 + while ( i < len( func ) ): + if ( i != 2 ): + cast += ', ' + cast += func[i] + i += 1 + cast += ' )' + # function + f.write( 'id' + func[1] + ' = ( ' + cast + ' )GetProcAddress( h, "' + func[1] + '" );\n' ) + f.write( 'if ( !id' + func[1] + ') {\n return "' + func[1] + '";\n}\n' ) + +if __name__ == '__main__': + f = open( 'idal.h', 'w' ) + warningHeader( f ) + genIDALFunc( f, True ) + f.write( '\n' ) + genDefineMapping( f ) + f.close() + f = open( 'idal.cpp', 'w' ) + warningHeader( f ) + genIDALFunc( f, False ) + f.write( '\n' ); + f.write( 'const char* InitializeIDAL( HMODULE h ) {\n' ) + genIDALInit( f ) + f.write( 'return NULL;\n' ); + f.write( '};\n' ) + f.close() diff --git a/src/openal/include/al.h b/src/openal/include/al.h new file mode 100644 index 0000000..da1d50c --- /dev/null +++ b/src/openal/include/al.h @@ -0,0 +1,515 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _AL_H_ +#define _AL_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ +#include "altypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #ifdef _OPENAL32LIB + #define ALAPI __declspec(dllexport) + #else + #define ALAPI __declspec(dllimport) + #endif + #define ALAPIENTRY __cdecl + #define AL_CALLBACK +#else + #ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif + #endif + #define ALAPI + #define ALAPIENTRY __cdecl + #define AL_CALLBACK +#endif + +#define OPENAL + +#ifndef AL_NO_PROTOTYPES + +/** + * OpenAL Maintenance Functions + * Initialization and exiting. + * State Management and Query. + * Error Handling. + * Extension Support. + */ + +/** State management. */ +ALAPI ALvoid ALAPIENTRY alEnable( ALenum capability ); +ALAPI ALvoid ALAPIENTRY alDisable( ALenum capability ); +ALAPI ALboolean ALAPIENTRY alIsEnabled( ALenum capability ); + +/** Application preferences for driver performance choices. */ +ALAPI ALvoid ALAPIENTRY alHint( ALenum target, ALenum mode ); + +/** State retrieval. */ +ALAPI ALboolean ALAPIENTRY alGetBoolean( ALenum param ); +ALAPI ALint ALAPIENTRY alGetInteger( ALenum param ); +ALAPI ALfloat ALAPIENTRY alGetFloat( ALenum param ); +ALAPI ALdouble ALAPIENTRY alGetDouble( ALenum param ); +ALAPI ALvoid ALAPIENTRY alGetBooleanv( ALenum param, ALboolean* data ); +ALAPI ALvoid ALAPIENTRY alGetIntegerv( ALenum param, ALint* data ); +ALAPI ALvoid ALAPIENTRY alGetFloatv( ALenum param, ALfloat* data ); +ALAPI ALvoid ALAPIENTRY alGetDoublev( ALenum param, ALdouble* data ); +ALAPI ALubyte* ALAPIENTRY alGetString( ALenum param ); + +/** + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY alGetError( ALvoid ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALboolean ALAPIENTRY alIsExtensionPresent( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALvoid* ALAPIENTRY alGetProcAddress( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the integer value of an enumeration (usually an extension) with the name ename. + */ +ALAPI ALenum ALAPIENTRY alGetEnumValue( ALubyte* ename ); + + + + +/** + * LISTENER + * Listener is the sample position for a given context. + * The multi-channel (usually stereo) output stream generated + * by the mixer is parametrized by this Listener object: + * its position and velocity relative to Sources, within + * occluder and reflector geometry. + */ + + + +/** + * + * Listener Environment: default 0. + */ +ALAPI ALvoid ALAPIENTRY alListeneri( ALenum param, ALint value ); + + +/** + * + * Listener Gain: default 1.0f. + */ +ALAPI ALvoid ALAPIENTRY alListenerf( ALenum param, ALfloat value ); + + +/** + * + * Listener Position. + * Listener Velocity. + */ +ALAPI ALvoid ALAPIENTRY alListener3f( ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); + + +/** + * + * Listener Position: ALfloat[3] + * Listener Velocity: ALfloat[3] + * Listener Orientation: ALfloat[6] (forward and up vector). + */ +ALAPI ALvoid ALAPIENTRY alListenerfv( ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY alGetListeneri( ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetListenerf( ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY alGetListener3f( ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY alGetListenerfv( ALenum param, ALfloat* values ); + + +/** + * SOURCE + * Source objects are by default localized. Sources + * take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial + * arrangement etc. + */ + + + +/** Create Source objects. */ +ALAPI ALvoid ALAPIENTRY alGenSources( ALsizei n, ALuint* sources ); + +/** Delete Source objects. */ +ALAPI ALvoid ALAPIENTRY alDeleteSources( ALsizei n, ALuint* sources ); + +/** Verify a handle is a valid Source. */ +ALAPI ALboolean ALAPIENTRY alIsSource( ALuint id ); + +/** Set an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY alSourcei( ALuint source, ALenum param, ALint value ); +ALAPI ALvoid ALAPIENTRY alSourcef( ALuint source, ALenum param, ALfloat value ); +ALAPI ALvoid ALAPIENTRY alSource3f( ALuint source, ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); +ALAPI ALvoid ALAPIENTRY alSourcefv( ALuint source, ALenum param, ALfloat* values ); + +/** Get an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY alGetSourcei( ALuint source, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetSourcef( ALuint source, ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY alGetSource3f( ALuint source, ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY alGetSourcefv( ALuint source, ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY alSourcePlayv( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourcePausev( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourceStopv( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourceRewindv(ALsizei n,ALuint *sources); + +/** Activate a source, start replay. */ +ALAPI ALvoid ALAPIENTRY alSourcePlay( ALuint source ); + +/** + * Pause a source, + * temporarily remove it from the mixer list. + */ +ALAPI ALvoid ALAPIENTRY alSourcePause( ALuint source ); + +/** + * Stop a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + * To remove a Source completely, it has to be + * deleted following Stop, or before Play. + */ +ALAPI ALvoid ALAPIENTRY alSourceStop( ALuint source ); + +/** + * Rewinds a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + */ +ALAPI ALvoid ALAPIENTRY alSourceRewind( ALuint source ); + + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. There can be more than + * one Source using the same Buffer data. If Buffers have + * to be duplicated on a per-Source basis, the driver has to + * take care of allocation, copying, and deallocation as well + * as propagating buffer data changes. + */ + + + + +/** Buffer object generation. */ +ALAPI ALvoid ALAPIENTRY alGenBuffers( ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY alDeleteBuffers( ALsizei n, ALuint* buffers ); +ALAPI ALboolean ALAPIENTRY alIsBuffer( ALuint buffer ); + +/** + * Specify the data to be filled into a buffer. + */ +ALAPI ALvoid ALAPIENTRY alBufferData( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALsizei freq ); + + +ALAPI ALvoid ALAPIENTRY alGetBufferi( ALuint buffer, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetBufferf( ALuint buffer, ALenum param, ALfloat* value ); + + + + +/** + * Queue stuff + */ + +ALAPI ALvoid ALAPIENTRY alSourceQueueBuffers( ALuint source, ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY alSourceUnqueueBuffers( ALuint source, ALsizei n, ALuint* buffers ); + +/** + * Knobs and dials + */ +ALAPI ALvoid ALAPIENTRY alDistanceModel( ALenum value ); +ALAPI ALvoid ALAPIENTRY alDopplerFactor( ALfloat value ); +ALAPI ALvoid ALAPIENTRY alDopplerVelocity( ALfloat value ); + +#else /* AL_NO_PROTOTYPES */ + +/** + * OpenAL Maintenance Functions + * Initialization and exiting. + * State Management and Query. + * Error Handling. + * Extension Support. + */ + +/** State management. */ +ALAPI ALvoid ALAPIENTRY (*alEnable)( ALenum capability ); +ALAPI ALvoid ALAPIENTRY (*alDisable)( ALenum capability ); +ALAPI ALboolean ALAPIENTRY (*alIsEnabled)( ALenum capability ); + +/** Application preferences for driver performance choices. */ +ALAPI ALvoid ALAPIENTRY (*alHint)( ALenum target, ALenum mode ); + +/** State retrieval. */ +ALAPI ALboolean ALAPIENTRY (*alGetBoolean)( ALenum param ); +ALAPI ALint ALAPIENTRY (*alGetInteger)( ALenum param ); +ALAPI ALfloat ALAPIENTRY (*alGetFloat)( ALenum param ); +ALAPI ALdouble ALAPIENTRY (*alGetDouble)( ALenum param ); +ALAPI ALvoid ALAPIENTRY (*alGetBooleanv)( ALenum param, ALboolean* data ); +ALAPI ALvoid ALAPIENTRY (*alGetIntegerv)( ALenum param, ALint* data ); +ALAPI ALvoid ALAPIENTRY (*alGetFloatv)( ALenum param, ALfloat* data ); +ALAPI ALvoid ALAPIENTRY (*alGetDoublev)( ALenum param, ALdouble* data ); +ALAPI ALubyte* ALAPIENTRY (*alGetString)( ALenum param ); + +/** + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY (*alGetError)( ALvoid ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALboolean ALAPIENTRY (*alIsExtensionPresent)( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALvoid* ALAPIENTRY (*alGetProcAddress)( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the integer value of an enumeration (usually an extension) with the name ename. + */ +ALAPI ALenum ALAPIENTRY (*alGetEnumValue)( ALubyte* ename ); + + + + +/** + * LISTENER + * Listener is the sample position for a given context. + * The multi-channel (usually stereo) output stream generated + * by the mixer is parametrized by this Listener object: + * its position and velocity relative to Sources, within + * occluder and reflector geometry. + */ + + + +/** + * + * Listener Environment: default 0. + */ +ALAPI ALvoid ALAPIENTRY (*alListeneri)( ALenum param, ALint value ); + + +/** + * + * Listener Gain: default 1.0f. + */ +ALAPI ALvoid ALAPIENTRY (*alListenerf)( ALenum param, ALfloat value ); + + +/** + * + * Listener Position. + * Listener Velocity. + */ +ALAPI ALvoid ALAPIENTRY (*alListener3f)( ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); + + +/** + * + * Listener Position: ALfloat[3] + * Listener Velocity: ALfloat[3] + * Listener Orientation: ALfloat[6] (forward and up vector). + */ +ALAPI ALvoid ALAPIENTRY (*alListenerfv)( ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY (*alGetListeneri)( ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetListenerf)( ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY (*alGetListener3f)( ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY (*alGetListenerfv)( ALenum param, ALfloat* values ); + + +/** + * SOURCE + * Source objects are by default localized. Sources + * take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial + * arrangement etc. + */ + + + +/** Create Source objects. */ +ALAPI ALvoid ALAPIENTRY (*alGenSources)( ALsizei n, ALuint* sources ); + +/** Delete Source objects. */ +ALAPI ALvoid ALAPIENTRY (*alDeleteSources)( ALsizei n, ALuint* sources ); + +/** Verify a handle is a valid Source. */ +ALAPI ALboolean ALAPIENTRY (*alIsSource)( ALuint id ); + +/** Set an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY (*alSourcei)( ALuint source, ALenum param, ALint value ); +ALAPI ALvoid ALAPIENTRY (*alSourcef)( ALuint source, ALenum param, ALfloat value ); +ALAPI ALvoid ALAPIENTRY (*alSource3f)( ALuint source, ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); +ALAPI ALvoid ALAPIENTRY (*alSourcefv)( ALuint source, ALenum param, ALfloat* values ); + +/** Get an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY (*alGetSourcei)( ALuint source, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetSourcef)( ALuint source, ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY (*alGetSourcefv)( ALuint source, ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY (*alSourcePlayv)( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY (*alSourceStopv)( ALsizei n, ALuint *sources ); + +/** Activate a source, start replay. */ +ALAPI ALvoid ALAPIENTRY (*alSourcePlay)( ALuint source ); + +/** + * Pause a source, + * temporarily remove it from the mixer list. + */ +ALAPI ALvoid ALAPIENTRY (*alSourcePause)( ALuint source ); + +/** + * Stop a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + * To remove a Source completely, it has to be + * deleted following Stop, or before Play. + */ +ALAPI ALvoid ALAPIENTRY (*alSourceStop)( ALuint source ); + + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. There can be more than + * one Source using the same Buffer data. If Buffers have + * to be duplicated on a per-Source basis, the driver has to + * take care of allocation, copying, and deallocation as well + * as propagating buffer data changes. + */ + + + + +/** Buffer object generation. */ +ALAPI ALvoid ALAPIENTRY (*alGenBuffers)( ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY (*alDeleteBuffers)( ALsizei n, ALuint* buffers ); +ALAPI ALboolean ALAPIENTRY (*alIsBuffer)( ALuint buffer ); + +/** + * Specify the data to be filled into a buffer. + */ +ALAPI ALvoid ALAPIENTRY (*alBufferData)( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALsizei freq ); + +ALAPI ALvoid ALAPIENTRY (*alGetBufferi)( ALuint buffer, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetBufferf)( ALuint buffer, ALenum param, ALfloat* value ); + + + + +/** + * Queue stuff + */ +ALAPI ALvoid ALAPIENTRY (*alSourceQueueBuffers)( ALuint source, ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY (*alSourceUnqueueBuffers)( ALuint source, ALsizei n, ALuint* buffers ); + +/** + * Knobs and dials + */ +ALAPI ALvoid ALAPIENTRY (*alDistanceModel)( ALenum value ); +ALAPI ALvoid ALAPIENTRY (*alDopplerFactor)( ALfloat value ); +ALAPI ALvoid ALAPIENTRY (*alDopplerVelocity)( ALfloat value ); + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export off + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/openal/include/alc.h b/src/openal/include/alc.h new file mode 100644 index 0000000..71ea21b --- /dev/null +++ b/src/openal/include/alc.h @@ -0,0 +1,115 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _ALC_H_ +#define _ALC_H_ + +#include "altypes.h" +#include "alctypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #ifdef _OPENAL32LIB + #define ALCAPI __declspec(dllexport) + #else + #define ALCAPI __declspec(dllimport) + #endif + + typedef struct ALCdevice_struct ALCdevice; + typedef struct ALCcontext_struct ALCcontext; + + #define ALCAPIENTRY __cdecl +#else + #ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif + #endif + #define ALCAPI + #define ALCAPIENTRY __cdecl +#endif + + + +#ifndef ALC_NO_PROTOTYPES + +ALCAPI ALCubyte* ALCAPIENTRY alcGetString(ALCdevice *device,ALCenum param); +ALCAPI ALCvoid ALCAPIENTRY alcGetIntegerv(ALCdevice *device,ALCenum param,ALCsizei size,ALCint *data); + +ALCAPI ALCdevice* ALCAPIENTRY alcOpenDevice(ALCubyte *deviceName); +ALCAPI ALCvoid ALCAPIENTRY alcCloseDevice(ALCdevice *device); + +ALCAPI ALCcontext*ALCAPIENTRY alcCreateContext(ALCdevice *device,ALCint *attrList); +ALCAPI ALCboolean ALCAPIENTRY alcMakeContextCurrent(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcProcessContext(ALCcontext *context); +ALCAPI ALCcontext*ALCAPIENTRY alcGetCurrentContext(ALCvoid); +ALCAPI ALCdevice* ALCAPIENTRY alcGetContextsDevice(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcSuspendContext(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcDestroyContext(ALCcontext *context); + +ALCAPI ALCenum ALCAPIENTRY alcGetError(ALCdevice *device); + +ALCAPI ALCboolean ALCAPIENTRY alcIsExtensionPresent(ALCdevice *device,ALCubyte *extName); +ALCAPI ALCvoid * ALCAPIENTRY alcGetProcAddress(ALCdevice *device,ALCubyte *funcName); +ALCAPI ALCenum ALCAPIENTRY alcGetEnumValue(ALCdevice *device,ALCubyte *enumName); + +#else /* ALC_NO_PROTOTYPES */ + +ALCAPI ALCubyte* ALCAPIENTRY (*alcGetString)(ALCdevice *device,ALCenum param); +ALCAPI ALCvoid ALCAPIENTRY (*alcGetIntegerv)(ALCdevice * device,ALCenum param,ALCsizei size,ALCint *data); + +ALCAPI ALCdevice* ALCAPIENTRY (*alcOpenDevice)(ALubyte *deviceName); +ALCAPI ALCvoid ALCAPIENTRY (*alcCloseDevice)(ALCdevice *device); + +ALCAPI ALCcontext*ALCAPIENTRY (*alcCreateContext)(ALCdevice *device,ALCint *attrList); +ALCAPI ALCboolean ALCAPIENTRY (*alcMakeContextCurrent)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcProcessContext)(ALCcontext *context); +ALCAPI ALCcontext*ALCAPIENTRY (*alcGetCurrentContext)(ALCvoid); +ALCAPI ALCdevice* ALCAPIENTRY (*alcGetContextsDevice)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcSuspendContext)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcDestroyContext)(ALCcontext *context); + +ALCAPI ALCenum ALCAPIENTRY (*alcGetError)(ALCdevice *device); + +ALCAPI ALCboolean ALCAPIENTRY (*alcIsExtensionPresent)(ALCdevice *device,ALCubyte *extName); +ALCAPI ALCvoid * ALCAPIENTRY (*alcGetProcAddress)(ALCdevice *device,ALCubyte *funcName); +ALCAPI ALCenum ALCAPIENTRY (*alcGetEnumValue)(ALCdevice *device,ALCubyte *enumName); + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export off + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/openal/include/alctypes.h b/src/openal/include/alctypes.h new file mode 100644 index 0000000..3d24a4a --- /dev/null +++ b/src/openal/include/alctypes.h @@ -0,0 +1,149 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _ALCTYPES_H_ +#define _ALCTYPES_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** ALC boolean type. */ +typedef char ALCboolean; + +/** ALC 8bit signed byte. */ +typedef char ALCbyte; + +/** ALC 8bit unsigned byte. */ +typedef unsigned char ALCubyte; + +/** ALC 16bit signed short integer type. */ +typedef short ALCshort; + +/** ALC 16bit unsigned short integer type. */ +typedef unsigned short ALCushort; + +/** ALC 32bit unsigned integer type. */ +typedef unsigned ALCuint; + +/** ALC 32bit signed integer type. */ +typedef int ALCint; + +/** ALC 32bit floating point type. */ +typedef float ALCfloat; + +/** ALC 64bit double point type. */ +typedef double ALCdouble; + +/** ALC 32bit type. */ +typedef unsigned int ALCsizei; + +/** ALC void type */ +typedef void ALCvoid; + +/** ALC enumerations. */ +typedef int ALCenum; + +/* Bad value. */ +#define ALC_INVALID (-1) + +/* Boolean False. */ +#define ALC_FALSE 0 + +/* Boolean True. */ +#define ALC_TRUE 1 + +/** Errors: No Error. */ +#define ALC_NO_ERROR ALC_FALSE + +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 + +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_EXTENSIONS 0x1006 + +#define ALC_FREQUENCY 0x1007 +#define ALC_REFRESH 0x1008 +#define ALC_SYNC 0x1009 + +/** + * The device argument does not name a valid dvice. + */ +#define ALC_INVALID_DEVICE 0xA001 + +/** + * The context argument does not name a valid context. + */ +#define ALC_INVALID_CONTEXT 0xA002 + +/** + * A function was called at inappropriate time, + * or in an inappropriate way, causing an illegal state. + * This can be an incompatible ALenum, object ID, + * and/or function. + */ +#define ALC_INVALID_ENUM 0xA003 + +/** + * Illegal value passed as an argument to an AL call. + * Applies to parameter values, but not to enumerations. + */ +#define ALC_INVALID_VALUE 0xA004 + +/** + * A function could not be completed, + * because there is not enough memory available. + */ +#define ALC_OUT_OF_MEMORY 0xA005 + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/openal/include/altypes.h b/src/openal/include/altypes.h new file mode 100644 index 0000000..1b57e3a --- /dev/null +++ b/src/openal/include/altypes.h @@ -0,0 +1,357 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _ALTYPES_H_ +#define _ALTYPES_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** OpenAL boolean type. */ +typedef char ALboolean; + +/** OpenAL 8bit signed byte. */ +typedef char ALbyte; + +/** OpenAL 8bit unsigned byte. */ +typedef unsigned char ALubyte; + +/** OpenAL 16bit signed short integer type. */ +typedef short ALshort; + +/** OpenAL 16bit unsigned short integer type. */ +typedef unsigned short ALushort; + +/** OpenAL 32bit unsigned integer type. */ +typedef unsigned ALuint; + +/** OpenAL 32bit signed integer type. */ +typedef int ALint; + +/** OpenAL 32bit floating point type. */ +typedef float ALfloat; + +/** OpenAL 64bit double point type. */ +typedef double ALdouble; + +/** OpenAL 32bit type. */ +typedef unsigned int ALsizei; + +/** OpenAL void type */ +typedef void ALvoid; + +/** OpenAL enumerations. */ +typedef int ALenum; + +/* Bad value. */ +#define AL_INVALID (-1) + +/* Disable value. */ +#define AL_NONE 0 + +/* Boolean False. */ +#define AL_FALSE 0 + +/* Boolean True. */ +#define AL_TRUE 1 + +/** + * Indicate the type of AL_SOURCE. + * Sources can be spatialized + */ +#define AL_SOURCE_TYPE 0x200 + +/** Indicate source has absolute coordinates. */ +#define AL_SOURCE_ABSOLUTE 0x201 + +/** Indicate Source has listener relative coordinates. */ +#define AL_SOURCE_RELATIVE 0x202 + +/** + * Directional source, inner cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_INNER_ANGLE 0x1001 + +/** + * Directional source, outer cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_OUTER_ANGLE 0x1002 + +/** + * Specify the pitch to be applied, either at source, + * or on mixer results, at listener. + * Range: [0.5-2.0] + * Default: 1.0 + */ +#define AL_PITCH 0x1003 + +/** + * Specify the current location in three dimensional space. + * OpenAL, like OpenGL, uses a right handed coordinate system, + * where in a frontal default view X (thumb) points right, + * Y points up (index finger), and Z points towards the + * viewer/camera (middle finger). + * To switch from a left handed coordinate system, flip the + * sign on the Z coordinate. + * Listener position is always in the world coordinate system. + */ +#define AL_POSITION 0x1004 + +/** Specify the current direction as forward vector. */ +#define AL_DIRECTION 0x1005 + +/** Specify the current velocity in three dimensional space. */ +#define AL_VELOCITY 0x1006 + +/** + * Indicate whether source has to loop infinite. + * Type: ALboolean + * Range: [AL_TRUE, AL_FALSE] + * Default: AL_FALSE + */ +#define AL_LOOPING 0x1007 + +/** + * Indicate the buffer to provide sound samples. + * Type: ALuint. + * Range: any valid Buffer id. + */ +#define AL_BUFFER 0x1009 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_GAIN 0x100A + +/** + * Indicate minimum source attenuation. + * Type: ALfloat + * Range: [0.0 - 1.0] + */ +#define AL_MIN_GAIN 0x100D + +/** + * Indicate maximum source attenuation. + * Type: ALfloat + * Range: [0.0 - 1.0] + */ +#define AL_MAX_GAIN 0x100E + +/** + * Specify the current orientation. + * Type: ALfv6 (at/up) + * Range: N/A + */ +#define AL_ORIENTATION 0x100F + +/* byte offset into source (in canon format). -1 if source + * is not playing. Don't set this, get this. + * + * Type: ALfloat + * Range: [0.0 - ] + * Default: 1.0 + */ +#define AL_REFERENCE_DISTANCE 0x1020 + + /** + * Indicate the rolloff factor for the source. + * Type: ALfloat + * Range: [0.0 - ] + * Default: 1.0 + */ +#define AL_ROLLOFF_FACTOR 0x1021 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_CONE_OUTER_GAIN 0x1022 + +/** + * Specify the maximum distance. + * Type: ALfloat + * Range: [0.0 - ] + */ +#define AL_MAX_DISTANCE 0x1023 + +/** + * Specify the channel mask. (Creative) + * Type: ALuint + * Range: [0 - 255] + */ +#define AL_CHANNEL_MASK 0x3000 + +/** + * Source state information + */ +#define AL_SOURCE_STATE 0x1010 +#define AL_INITIAL 0x1011 +#define AL_PLAYING 0x1012 +#define AL_PAUSED 0x1013 +#define AL_STOPPED 0x1014 + +/** + * Buffer Queue params + */ +#define AL_BUFFERS_QUEUED 0x1015 +#define AL_BUFFERS_PROCESSED 0x1016 + +/** Sound buffers: format specifier. */ +#define AL_FORMAT_MONO8 0x1100 +#define AL_FORMAT_MONO16 0x1101 +#define AL_FORMAT_STEREO8 0x1102 +#define AL_FORMAT_STEREO16 0x1103 + +/** + * Sound buffers: frequency, in units of Hertz [Hz]. + * This is the number of samples per second. Half of the + * sample frequency marks the maximum significant + * frequency component. + */ +#define AL_FREQUENCY 0x2001 +#define AL_BITS 0x2002 +#define AL_CHANNELS 0x2003 +#define AL_SIZE 0x2004 +#define AL_DATA 0x2005 + +/** + * Buffer state. + * + * Not supported for public use (yet). + */ +#define AL_UNUSED 0x2010 +#define AL_PENDING 0x2011 +#define AL_PROCESSED 0x2012 + +/** Errors: No Error. */ +#define AL_NO_ERROR AL_FALSE + +/** + * Illegal name passed as an argument to an AL call. + */ +#define AL_INVALID_NAME 0xA001 + +/** + * Illegal enum passed as an argument to an AL call. + */ +#define AL_INVALID_ENUM 0xA002 +/** + * Illegal value passed as an argument to an AL call. + * Applies to parameter values, but not to enumerations. + */ +#define AL_INVALID_VALUE 0xA003 + +/** + * A function was called at inappropriate time, + * or in an inappropriate way, causing an illegal state. + * This can be an incompatible ALenum, object ID, + * and/or function. + */ +#define AL_INVALID_OPERATION 0xA004 + +/** + * A function could not be completed, + * because there is not enough memory available. + */ +#define AL_OUT_OF_MEMORY 0xA005 + +/** Context strings: Vendor Name. */ +#define AL_VENDOR 0xB001 +#define AL_VERSION 0xB002 +#define AL_RENDERER 0xB003 +#define AL_EXTENSIONS 0xB004 + +/** Global tweakage. */ + +/** + * Doppler scale. Default 1.0 + */ +#define AL_DOPPLER_FACTOR 0xC000 + +/** + * Doppler velocity. Default 1.0 + */ +#define AL_DOPPLER_VELOCITY 0xC001 + +/** + * Distance model. Default AL_INVERSE_DISTANCE_CLAMPED + */ +#define AL_DISTANCE_MODEL 0xD000 + +/** Distance models. */ + +#define AL_INVERSE_DISTANCE 0xD001 +#define AL_INVERSE_DISTANCE_CLAMPED 0xD002 + + /** + * enables + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/openal/include/alu.h b/src/openal/include/alu.h new file mode 100644 index 0000000..8a8a6c7 --- /dev/null +++ b/src/openal/include/alu.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _ALU_H_ +#define _ALU_H_ + +#define ALUAPI +#define ALUAPIENTRY __cdecl + +#define BUFFERSIZE 48000 +#define FRACTIONBITS 14 +#define FRACTIONMASK ((1L<. + +=========================================================================== +*/ + +/****************************************************************** +* +* EAX.H - DirectSound3D Environmental Audio Extensions version 2.0 +* Updated July 8, 1999 +* +******************************************************************* +*/ + +#ifndef EAX20_H_INCLUDED +#define EAX20_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#pragma pack(push, 4) + +/* +* EAX 2.0 listener property set {0306A6A8-B224-11d2-99E5-0000E8D8C722} +*/ +DEFINE_GUID(DSPROPSETID_EAX20_ListenerProperties, + 0x306a6a8, + 0xb224, + 0x11d2, + 0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22); + +typedef enum +{ + DSPROPERTY_EAX20LISTENER_NONE, + DSPROPERTY_EAX20LISTENER_ALLPARAMETERS, + DSPROPERTY_EAX20LISTENER_ROOM, + DSPROPERTY_EAX20LISTENER_ROOMHF, + DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAX20LISTENER_DECAYTIME, + DSPROPERTY_EAX20LISTENER_DECAYHFRATIO, + DSPROPERTY_EAX20LISTENER_REFLECTIONS, + DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAX20LISTENER_REVERB, + DSPROPERTY_EAX20LISTENER_REVERBDELAY, + DSPROPERTY_EAX20LISTENER_ENVIRONMENT, + DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF, + DSPROPERTY_EAX20LISTENER_FLAGS +} DSPROPERTY_EAX20_LISTENERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAX20LISTENER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAX20LISTENER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAX20LISTENER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAX20LISTENER_NONE | \ + DSPROPERTY_EAX20LISTENER_IMMEDIATE) + +// Use this structure for DSPROPERTY_EAX20LISTENER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times are in seconds +// - the reference for high frequency controls is 5 kHz +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myListener.lRoom = -1000; +// myListener.lRoomHF = -100; +// ... +// myListener.dwFlags = myFlags /* see EAXLISTENERFLAGS below */ ; +// instead of: +// myListener = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +typedef struct _EAX20LISTENERPROPERTIES +{ + long lRoom; // room effect level at low frequencies + long lRoomHF; // room effect high-frequency level re. low frequency level + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flDecayTime; // reverberation decay time at low frequencies + float flDecayHFRatio; // high-frequency to low-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + unsigned long dwEnvironment; // sets all listener properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + float flAirAbsorptionHF; // change in level per meter at 5 kHz + unsigned long dwFlags; // modifies the behavior of properties +} EAX20LISTENERPROPERTIES, *LPEAX20LISTENERPROPERTIES; + +// used by DSPROPERTY_EAX20LISTENER_ENVIRONMENT +enum +{ + EAX20_ENVIRONMENT_GENERIC, + EAX20_ENVIRONMENT_PADDEDCELL, + EAX20_ENVIRONMENT_ROOM, + EAX20_ENVIRONMENT_BATHROOM, + EAX20_ENVIRONMENT_LIVINGROOM, + EAX20_ENVIRONMENT_STONEROOM, + EAX20_ENVIRONMENT_AUDITORIUM, + EAX20_ENVIRONMENT_CONCERTHALL, + EAX20_ENVIRONMENT_CAVE, + EAX20_ENVIRONMENT_ARENA, + EAX20_ENVIRONMENT_HANGAR, + EAX20_ENVIRONMENT_CARPETEDHALLWAY, + EAX20_ENVIRONMENT_HALLWAY, + EAX20_ENVIRONMENT_STONECORRIDOR, + EAX20_ENVIRONMENT_ALLEY, + EAX20_ENVIRONMENT_FOREST, + EAX20_ENVIRONMENT_CITY, + EAX20_ENVIRONMENT_MOUNTAINS, + EAX20_ENVIRONMENT_QUARRY, + EAX20_ENVIRONMENT_PLAIN, + EAX20_ENVIRONMENT_PARKINGLOT, + EAX20_ENVIRONMENT_SEWERPIPE, + EAX20_ENVIRONMENT_UNDERWATER, + EAX20_ENVIRONMENT_DRUGGED, + EAX20_ENVIRONMENT_DIZZY, + EAX20_ENVIRONMENT_PSYCHOTIC, + + EAX20_ENVIRONMENT_COUNT +}; + +// Used by DS20PROPERTY_EAXLISTENER_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXLISTENERFLAGS_DECAYTIMESCALE | EAXLISTENERFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAX20LISTENERFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAX20LISTENERFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAX20LISTENERFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAX20LISTENERFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAX20LISTENERFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time + +// This flag limits high-frequency decay time according to air absorption. +#define EAX20LISTENERFLAGS_DECAYHFLIMIT 0x00000020 + +#define EAX20LISTENERFLAGS_RESERVED 0xFFFFFFC0 // reserved future use + +// property ranges and defaults: + +#define EAX20LISTENER_MINROOM (-10000) +#define EAX20LISTENER_MAXROOM 0 +#define EAX20LISTENER_DEFAULTROOM (-1000) + +#define EAX20LISTENER_MINROOMHF (-10000) +#define EAX20LISTENER_MAXROOMHF 0 +#define EAX20LISTENER_DEFAULTROOMHF (-100) + +#define EAX20LISTENER_MINROOMROLLOFFFACTOR 0.0f +#define EAX20LISTENER_MAXROOMROLLOFFFACTOR 10.0f +#define EAX20LISTENER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAX20LISTENER_MINDECAYTIME 0.1f +#define EAX20LISTENER_MAXDECAYTIME 20.0f +#define EAX20LISTENER_DEFAULTDECAYTIME 1.49f + +#define EAX20LISTENER_MINDECAYHFRATIO 0.1f +#define EAX20LISTENER_MAXDECAYHFRATIO 2.0f +#define EAX20LISTENER_DEFAULTDECAYHFRATIO 0.83f + +#define EAX20LISTENER_MINREFLECTIONS (-10000) +#define EAX20LISTENER_MAXREFLECTIONS 1000 +#define EAX20LISTENER_DEFAULTREFLECTIONS (-2602) + +#define EAX20LISTENER_MINREFLECTIONSDELAY 0.0f +#define EAX20LISTENER_MAXREFLECTIONSDELAY 0.3f +#define EAX20LISTENER_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAX20LISTENER_MINREVERB (-10000) +#define EAX20LISTENER_MAXREVERB 2000 +#define EAX20LISTENER_DEFAULTREVERB 200 + +#define EAX20LISTENER_MINREVERBDELAY 0.0f +#define EAX20LISTENER_MAXREVERBDELAY 0.1f +#define EAX20LISTENER_DEFAULTREVERBDELAY 0.011f + +#define EAX20LISTENER_MINENVIRONMENT 0 +#define EAX20LISTENER_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAX20LISTENER_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAX20LISTENER_MINENVIRONMENTSIZE 1.0f +#define EAX20LISTENER_MAXENVIRONMENTSIZE 100.0f +#define EAX20LISTENER_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAX20LISTENER_MINENVIRONMENTDIFFUSION 0.0f +#define EAX20LISTENER_MAXENVIRONMENTDIFFUSION 1.0f +#define EAX20LISTENER_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAX20LISTENER_MINAIRABSORPTIONHF (-100.0f) +#define EAX20LISTENER_MAXAIRABSORPTIONHF 0.0f +#define EAX20LISTENER_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAX20LISTENER_DEFAULTFLAGS (EAX20LISTENERFLAGS_DECAYTIMESCALE | \ + EAX20LISTENERFLAGS_REFLECTIONSSCALE | \ + EAX20LISTENERFLAGS_REFLECTIONSDELAYSCALE | \ + EAX20LISTENERFLAGS_REVERBSCALE | \ + EAX20LISTENERFLAGS_REVERBDELAYSCALE | \ + EAX20LISTENERFLAGS_DECAYHFLIMIT) + + + +/* +* EAX 2.0 buffer property set {0306A6A7-B224-11d2-99E5-0000E8D8C722} +*/ +DEFINE_GUID(DSPROPSETID_EAX20_BufferProperties, + 0x306a6a7, + 0xb224, + 0x11d2, + 0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX20_BufferProperties DSPROPSETID_EAX20_BufferProperties + +typedef enum +{ + DSPROPERTY_EAX20BUFFER_NONE, + DSPROPERTY_EAX20BUFFER_ALLPARAMETERS, + DSPROPERTY_EAX20BUFFER_DIRECT, + DSPROPERTY_EAX20BUFFER_DIRECTHF, + DSPROPERTY_EAX20BUFFER_ROOM, + DSPROPERTY_EAX20BUFFER_ROOMHF, + DSPROPERTY_EAX20BUFFER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAX20BUFFER_OBSTRUCTION, + DSPROPERTY_EAX20BUFFER_OBSTRUCTIONLFRATIO, + DSPROPERTY_EAX20BUFFER_OCCLUSION, + DSPROPERTY_EAX20BUFFER_OCCLUSIONLFRATIO, + DSPROPERTY_EAX20BUFFER_OCCLUSIONROOMRATIO, + DSPROPERTY_EAX20BUFFER_OUTSIDEVOLUMEHF, + DSPROPERTY_EAX20BUFFER_AIRABSORPTIONFACTOR, + DSPROPERTY_EAX20BUFFER_FLAGS +} DSPROPERTY_EAX20_BUFFERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAX20BUFFER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAX20BUFFER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAX20BUFFER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAX20BUFFER_NONE | \ + DSPROPERTY_EAX20BUFFER_IMMEDIATE) + +// Use this structure for DSPROPERTY_EAX20BUFFER_ALLPARAMETERS +// - all levels are hundredths of decibels +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXBUFFERFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +typedef struct _EAX20BUFFERPROPERTIES +{ + long lDirect; // direct path level + long lDirectHF; // direct path level at high frequencies + long lRoom; // room effect level + long lRoomHF; // room effect level at high frequencies + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // occlusion room effect level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF + unsigned long dwFlags; // modifies the behavior of properties +} EAX20BUFFERPROPERTIES, *LPEAX20BUFFERPROPERTIES; + +// Used by DSPROPERTY_EAX20BUFFER_FLAGS +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXBUFFERFLAGS_DIRECTHFAUTO | EAXBUFFERFLAGS_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAX20BUFFERFLAGS_DIRECTHFAUTO 0x00000001 // affects DSPROPERTY_EAXBUFFER_DIRECTHF +#define EAX20BUFFERFLAGS_ROOMAUTO 0x00000002 // affects DSPROPERTY_EAXBUFFER_ROOM +#define EAX20BUFFERFLAGS_ROOMHFAUTO 0x00000004 // affects DSPROPERTY_EAXBUFFER_ROOMHF + +#define EAX20BUFFERFLAGS_RESERVED 0xFFFFFFF8 // reserved future use + +// property ranges and defaults: + +#define EAX20BUFFER_MINDIRECT (-10000) +#define EAX20BUFFER_MAXDIRECT 1000 +#define EAX20BUFFER_DEFAULTDIRECT 0 + +#define EAX20BUFFER_MINDIRECTHF (-10000) +#define EAX20BUFFER_MAXDIRECTHF 0 +#define EAX20BUFFER_DEFAULTDIRECTHF 0 + +#define EAX20BUFFER_MINROOM (-10000) +#define EAX20BUFFER_MAXROOM 1000 +#define EAX20BUFFER_DEFAULTROOM 0 + +#define EAX20BUFFER_MINROOMHF (-10000) +#define EAX20BUFFER_MAXROOMHF 0 +#define EAX20BUFFER_DEFAULTROOMHF 0 + +#define EAX20BUFFER_MINROOMROLLOFFFACTOR 0.0f +#define EAX20BUFFER_MAXROOMROLLOFFFACTOR 10.f +#define EAX20BUFFER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAX20BUFFER_MINOBSTRUCTION (-10000) +#define EAX20BUFFER_MAXOBSTRUCTION 0 +#define EAX20BUFFER_DEFAULTOBSTRUCTION 0 + +#define EAX20BUFFER_MINOBSTRUCTIONLFRATIO 0.0f +#define EAX20BUFFER_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAX20BUFFER_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAX20BUFFER_MINOCCLUSION (-10000) +#define EAX20BUFFER_MAXOCCLUSION 0 +#define EAX20BUFFER_DEFAULTOCCLUSION 0 + +#define EAX20BUFFER_MINOCCLUSIONLFRATIO 0.0f +#define EAX20BUFFER_MAXOCCLUSIONLFRATIO 1.0f +#define EAX20BUFFER_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAX20BUFFER_MINOCCLUSIONROOMRATIO 0.0f +#define EAX20BUFFER_MAXOCCLUSIONROOMRATIO 10.0f +#define EAX20BUFFER_DEFAULTOCCLUSIONROOMRATIO 0.5f + +#define EAX20BUFFER_MINOUTSIDEVOLUMEHF (-10000) +#define EAX20BUFFER_MAXOUTSIDEVOLUMEHF 0 +#define EAX20BUFFER_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAX20BUFFER_MINAIRABSORPTIONFACTOR 0.0f +#define EAX20BUFFER_MAXAIRABSORPTIONFACTOR 10.0f +#define EAX20BUFFER_DEFAULTAIRABSORPTIONFACTOR 1.0f + +#define EAX20BUFFER_DEFAULTFLAGS (EAX20BUFFERFLAGS_DIRECTHFAUTO | \ + EAX20BUFFERFLAGS_ROOMAUTO | \ + EAX20BUFFERFLAGS_ROOMHFAUTO) + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/openal/include/eax3.h b/src/openal/include/eax3.h new file mode 100644 index 0000000..f02d987 --- /dev/null +++ b/src/openal/include/eax3.h @@ -0,0 +1,571 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/*******************************************************************\ +* * +* EAX.H - Environmental Audio Extensions version 3.0 * +* for OpenAL and DirectSound3D * +* * +********************************************************************/ + +#ifndef EAX30_H_INCLUDED +#define EAX30_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef OPENAL + #include + + /* + * EAX Wrapper Interface (using Direct X 7) {4FF53B81-1CE0-11d3-AAB8-00A0C95949D5} + */ + DEFINE_GUID(CLSID_EAXDirectSound, + 0x4ff53b81, + 0x1ce0, + 0x11d3, + 0xaa, 0xb8, 0x0, 0xa0, 0xc9, 0x59, 0x49, 0xd5); + + /* + * EAX Wrapper Interface (using Direct X 8) {CA503B60-B176-11d4-A094-D0C0BF3A560C} + */ + DEFINE_GUID(CLSID_EAXDirectSound8, + 0xca503b60, + 0xb176, + 0x11d4, + 0xa0, 0x94, 0xd0, 0xc0, 0xbf, 0x3a, 0x56, 0xc); + + + +#ifdef DIRECTSOUND_VERSION +#if DIRECTSOUND_VERSION == 0x0800 + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate8(GUID*, LPDIRECTSOUND8*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE8)(GUID*, LPDIRECTSOUND8*, IUnknown FAR*); +#endif +#endif + + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate(GUID*, LPDIRECTSOUND*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); + +#else // OPENAL + //#include + + #ifndef GUID_DEFINED + #define GUID_DEFINED + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; + #endif // !GUID_DEFINED + + #ifndef DEFINE_GUID + #ifndef INITGUID + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID /*FAR*/ name + #else + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + #endif // INITGUID + #endif // DEFINE_GUID + + + /* + * EAX OpenAL Extension + */ + typedef ALenum (*EAXSet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALenum (*EAXGet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALboolean (*EAXSetBufferMode)(ALsizei,ALuint*,ALint); + typedef ALenum (*EAXGetBufferMode)(ALuint,ALint*); +#endif + +#pragma pack(push, 4) + +/* + * EAX 3.0 listener property set {A8FA6880-B476-11d3-BDB9-00C0F02DDF87} + */ +DEFINE_GUID(DSPROPSETID_EAX30_ListenerProperties, + 0xa8fa6882, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x00, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_ListenerProperties DSPROPSETID_EAX30_ListenerProperties + +typedef enum +{ + DSPROPERTY_EAXLISTENER_NONE, + DSPROPERTY_EAXLISTENER_ALLPARAMETERS, + DSPROPERTY_EAXLISTENER_ENVIRONMENT, + DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAXLISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAXLISTENER_ROOM, + DSPROPERTY_EAXLISTENER_ROOMHF, + DSPROPERTY_EAXLISTENER_ROOMLF, + DSPROPERTY_EAXLISTENER_DECAYTIME, + DSPROPERTY_EAXLISTENER_DECAYHFRATIO, + DSPROPERTY_EAXLISTENER_DECAYLFRATIO, + DSPROPERTY_EAXLISTENER_REFLECTIONS, + DSPROPERTY_EAXLISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAXLISTENER_REFLECTIONSPAN, + DSPROPERTY_EAXLISTENER_REVERB, + DSPROPERTY_EAXLISTENER_REVERBDELAY, + DSPROPERTY_EAXLISTENER_REVERBPAN, + DSPROPERTY_EAXLISTENER_ECHOTIME, + DSPROPERTY_EAXLISTENER_ECHODEPTH, + DSPROPERTY_EAXLISTENER_MODULATIONTIME, + DSPROPERTY_EAXLISTENER_MODULATIONDEPTH, + DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF, + DSPROPERTY_EAXLISTENER_HFREFERENCE, + DSPROPERTY_EAXLISTENER_LFREFERENCE, + DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXLISTENER_FLAGS +} DSPROPERTY_EAX_LISTENERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXLISTENER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXLISTENER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXLISTENER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXLISTENER_NONE | \ + DSPROPERTY_EAXLISTENER_IMMEDIATE) +#ifndef EAXVECTOR_DEFINED +#define EAXVECTOR_DEFINED +typedef struct _EAXVECTOR { + float x; + float y; + float z; +} EAXVECTOR; +#endif + +// Use this structure for DSPROPERTY_EAXLISTENER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times and delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myListener.lRoom = -1000; +// myListener.lRoomHF = -100; +// ... +// myListener.dwFlags = myFlags /* see EAXLISTENERFLAGS below */ ; +// instead of: +// myListener = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +typedef struct _EAXLISTENERPROPERTIES +{ + unsigned long ulEnvironment; // sets all listener properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +} EAXLISTENERPROPERTIES, *LPEAXLISTENERPROPERTIES; + +// used by DSPROPERTY_EAXLISTENER_ENVIRONMENT +#ifndef EAX_ENVIRONMENTS_DEFINED +#define EAX_ENVIRONMENTS_DEFINED +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_UNDEFINED, + + EAX_ENVIRONMENT_COUNT +}; +#endif + +// Used by DSPROPERTY_EAXLISTENER_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXLISTENERFLAGS_DECAYTIMESCALE | EAXLISTENERFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAXLISTENERFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAXLISTENERFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAXLISTENERFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAXLISTENERFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time +#define EAXLISTENERFLAGS_ECHOTIMESCALE 0x00000040 // echo time +#define EAXLISTENERFLAGS_MODULATIONTIMESCALE 0x00000080 // modulation time + +// This flag limits high-frequency decay time according to air absorption. +#define EAXLISTENERFLAGS_DECAYHFLIMIT 0x00000020 + +#define EAXLISTENERFLAGS_RESERVED 0xFFFFFF00 // reserved future use + +// Property ranges and defaults: + +#define EAXLISTENER_MINENVIRONMENT 0 +#define EAXLISTENER_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAXLISTENER_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAXLISTENER_MINENVIRONMENTSIZE 1.0f +#define EAXLISTENER_MAXENVIRONMENTSIZE 100.0f +#define EAXLISTENER_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAXLISTENER_MINENVIRONMENTDIFFUSION 0.0f +#define EAXLISTENER_MAXENVIRONMENTDIFFUSION 1.0f +#define EAXLISTENER_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAXLISTENER_MINROOM (-10000) +#define EAXLISTENER_MAXROOM 0 +#define EAXLISTENER_DEFAULTROOM (-1000) + +#define EAXLISTENER_MINROOMHF (-10000) +#define EAXLISTENER_MAXROOMHF 0 +#define EAXLISTENER_DEFAULTROOMHF (-100) + +#define EAXLISTENER_MINROOMLF (-10000) +#define EAXLISTENER_MAXROOMLF 0 +#define EAXLISTENER_DEFAULTROOMLF 0 + +#define EAXLISTENER_MINDECAYTIME 0.1f +#define EAXLISTENER_MAXDECAYTIME 20.0f +#define EAXLISTENER_DEFAULTDECAYTIME 1.49f + +#define EAXLISTENER_MINDECAYHFRATIO 0.1f +#define EAXLISTENER_MAXDECAYHFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYHFRATIO 0.83f + +#define EAXLISTENER_MINDECAYLFRATIO 0.1f +#define EAXLISTENER_MAXDECAYLFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYLFRATIO 1.00f + +#define EAXLISTENER_MINREFLECTIONS (-10000) +#define EAXLISTENER_MAXREFLECTIONS 1000 +#define EAXLISTENER_DEFAULTREFLECTIONS (-2602) + +#define EAXLISTENER_MINREFLECTIONSDELAY 0.0f +#define EAXLISTENER_MAXREFLECTIONSDELAY 0.3f +#define EAXLISTENER_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAXLISTENER_MINREVERB (-10000) +#define EAXLISTENER_MAXREVERB 2000 +#define EAXLISTENER_DEFAULTREVERB 200 + +#define EAXLISTENER_MINREVERBDELAY 0.0f +#define EAXLISTENER_MAXREVERBDELAY 0.1f +#define EAXLISTENER_DEFAULTREVERBDELAY 0.011f + +#define EAXLISTENER_MINECHOTIME 0.075f +#define EAXLISTENER_MAXECHOTIME 0.25f +#define EAXLISTENER_DEFAULTECHOTIME 0.25f + +#define EAXLISTENER_MINECHODEPTH 0.0f +#define EAXLISTENER_MAXECHODEPTH 1.0f +#define EAXLISTENER_DEFAULTECHODEPTH 0.0f + +#define EAXLISTENER_MINMODULATIONTIME 0.04f +#define EAXLISTENER_MAXMODULATIONTIME 4.0f +#define EAXLISTENER_DEFAULTMODULATIONTIME 0.25f + +#define EAXLISTENER_MINMODULATIONDEPTH 0.0f +#define EAXLISTENER_MAXMODULATIONDEPTH 1.0f +#define EAXLISTENER_DEFAULTMODULATIONDEPTH 0.0f + +#define EAXLISTENER_MINAIRABSORPTIONHF (-100.0f) +#define EAXLISTENER_MAXAIRABSORPTIONHF 0.0f +#define EAXLISTENER_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXLISTENER_MINHFREFERENCE 1000.0f +#define EAXLISTENER_MAXHFREFERENCE 20000.0f +#define EAXLISTENER_DEFAULTHFREFERENCE 5000.0f + +#define EAXLISTENER_MINLFREFERENCE 20.0f +#define EAXLISTENER_MAXLFREFERENCE 1000.0f +#define EAXLISTENER_DEFAULTLFREFERENCE 250.0f + +#define EAXLISTENER_MINROOMROLLOFFFACTOR 0.0f +#define EAXLISTENER_MAXROOMROLLOFFFACTOR 10.0f +#define EAXLISTENER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXLISTENER_DEFAULTFLAGS (EAXLISTENERFLAGS_DECAYTIMESCALE | \ + EAXLISTENERFLAGS_REFLECTIONSSCALE | \ + EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE | \ + EAXLISTENERFLAGS_REVERBSCALE | \ + EAXLISTENERFLAGS_REVERBDELAYSCALE | \ + EAXLISTENERFLAGS_DECAYHFLIMIT) + + + +/* +* EAX 3.0 buffer property set {A8FA6881-B476-11d3-BDB9-00C0F02DDF87} +*/ +DEFINE_GUID(DSPROPSETID_EAX30_BufferProperties, + 0xa8fa6881, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x0, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_BufferProperties DSPROPSETID_EAX30_BufferProperties +#define DSPROPSETID_EAX_SourceProperties DSPROPSETID_EAX30_BufferProperties + +typedef enum +{ + DSPROPERTY_EAXBUFFER_NONE, + DSPROPERTY_EAXBUFFER_ALLPARAMETERS, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONPARAMETERS, + DSPROPERTY_EAXBUFFER_OCCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_EXCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_DIRECT, + DSPROPERTY_EAXBUFFER_DIRECTHF, + DSPROPERTY_EAXBUFFER_ROOM, + DSPROPERTY_EAXBUFFER_ROOMHF, + DSPROPERTY_EAXBUFFER_OBSTRUCTION, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSION, + DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONDIRECTRATIO, + DSPROPERTY_EAXBUFFER_EXCLUSION, + DSPROPERTY_EAXBUFFER_EXCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OUTSIDEVOLUMEHF, + DSPROPERTY_EAXBUFFER_DOPPLERFACTOR, + DSPROPERTY_EAXBUFFER_ROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_AIRABSORPTIONFACTOR, + DSPROPERTY_EAXBUFFER_FLAGS +} DSPROPERTY_EAX_BUFFERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXBUFFER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXBUFFER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXBUFFER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXBUFFER_NONE | \ + DSPROPERTY_EAXBUFFER_IMMEDIATE) + +// Use this structure for DSPROPERTY_EAXBUFFER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXBUFFERFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +typedef struct _EAXBUFFERPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +} EAXBUFFERPROPERTIES, *LPEAXBUFFERPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_OBSTRUCTION, +#ifndef EAX_OBSTRUCTIONPROPERTIES_DEFINED +#define EAX_OBSTRUCTIONPROPERTIES_DEFINED +typedef struct _EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +} EAXOBSTRUCTIONPROPERTIES, *LPEAXOBSTRUCTIONPROPERTIES; +#endif + +// Use this structure for DSPROPERTY_EAXBUFFER_OCCLUSION +#ifndef EAX_OCCLUSIONPROPERTIES_DEFINED +#define EAX_OCCLUSIONPROPERTIES_DEFINED +typedef struct _EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXOCCLUSIONPROPERTIES, *LPEAXOCCLUSIONPROPERTIES; +#endif + +// Use this structure for DSPROPERTY_EAXBUFFER_EXCLUSION +#ifndef EAX_EXCLUSIONPROPERTIES_DEFINED +#define EAX_EXCLUSIONPROPERTIES_DEFINED +typedef struct _EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +} EAXEXCLUSIONPROPERTIES, *LPEAXEXCLUSIONPROPERTIES; +#endif + +// Used by DSPROPERTY_EAXBUFFER_FLAGS +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXBUFFERFLAGS_DIRECTHFAUTO | EAXBUFFERFLAGS_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAXBUFFERFLAGS_DIRECTHFAUTO 0x00000001 // affects DSPROPERTY_EAXBUFFER_DIRECTHF +#define EAXBUFFERFLAGS_ROOMAUTO 0x00000002 // affects DSPROPERTY_EAXBUFFER_ROOM +#define EAXBUFFERFLAGS_ROOMHFAUTO 0x00000004 // affects DSPROPERTY_EAXBUFFER_ROOMHF + +#define EAXBUFFERFLAGS_RESERVED 0xFFFFFFF8 // reserved future use + +// Property ranges and defaults: + +#define EAXBUFFER_MINDIRECT (-10000) +#define EAXBUFFER_MAXDIRECT 1000 +#define EAXBUFFER_DEFAULTDIRECT 0 + +#define EAXBUFFER_MINDIRECTHF (-10000) +#define EAXBUFFER_MAXDIRECTHF 0 +#define EAXBUFFER_DEFAULTDIRECTHF 0 + +#define EAXBUFFER_MINROOM (-10000) +#define EAXBUFFER_MAXROOM 1000 +#define EAXBUFFER_DEFAULTROOM 0 + +#define EAXBUFFER_MINROOMHF (-10000) +#define EAXBUFFER_MAXROOMHF 0 +#define EAXBUFFER_DEFAULTROOMHF 0 + +#define EAXBUFFER_MINOBSTRUCTION (-10000) +#define EAXBUFFER_MAXOBSTRUCTION 0 +#define EAXBUFFER_DEFAULTOBSTRUCTION 0 + +#define EAXBUFFER_MINOBSTRUCTIONLFRATIO 0.0f +#define EAXBUFFER_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAXBUFFER_MINOCCLUSION (-10000) +#define EAXBUFFER_MAXOCCLUSION 0 +#define EAXBUFFER_DEFAULTOCCLUSION 0 + +#define EAXBUFFER_MINOCCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXBUFFER_MINOCCLUSIONROOMRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONROOMRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONROOMRATIO 1.5f + +#define EAXBUFFER_MINOCCLUSIONDIRECTRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONDIRECTRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONDIRECTRATIO 1.0f + +#define EAXBUFFER_MINEXCLUSION (-10000) +#define EAXBUFFER_MAXEXCLUSION 0 +#define EAXBUFFER_DEFAULTEXCLUSION 0 + +#define EAXBUFFER_MINEXCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXEXCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTEXCLUSIONLFRATIO 1.0f + +#define EAXBUFFER_MINOUTSIDEVOLUMEHF (-10000) +#define EAXBUFFER_MAXOUTSIDEVOLUMEHF 0 +#define EAXBUFFER_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAXBUFFER_MINDOPPLERFACTOR 0.0f +#define EAXBUFFER_MAXDOPPLERFACTOR 10.f +#define EAXBUFFER_DEFAULTDOPPLERFACTOR 0.0f + +#define EAXBUFFER_MINROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINROOMROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROOMROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINAIRABSORPTIONFACTOR 0.0f +#define EAXBUFFER_MAXAIRABSORPTIONFACTOR 10.0f +#define EAXBUFFER_DEFAULTAIRABSORPTIONFACTOR 1.0f + +#define EAXBUFFER_DEFAULTFLAGS (EAXBUFFERFLAGS_DIRECTHFAUTO | \ + EAXBUFFERFLAGS_ROOMAUTO | \ + EAXBUFFERFLAGS_ROOMHFAUTO ) + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/openal/include/eax4.h b/src/openal/include/eax4.h new file mode 100644 index 0000000..c6bc3e0 --- /dev/null +++ b/src/openal/include/eax4.h @@ -0,0 +1,1599 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/*******************************************************************\ +* * +* EAX.H - Environmental Audio Extensions version 4.0 * +* for OpenAL and DirectSound3D * +* * +* File revision 1.0.0 (GDC Beta SDK Release) * +* * +\*******************************************************************/ + +#ifndef EAX_H_INCLUDED +#define EAX_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef OPENAL + #include + + /* + * EAX Unified Interface (using Direct X 7) {4FF53B81-1CE0-11d3-AAB8-00A0C95949D5} + */ + DEFINE_GUID(CLSID_EAXDirectSound, + 0x4ff53b81, + 0x1ce0, + 0x11d3, + 0xaa, 0xb8, 0x0, 0xa0, 0xc9, 0x59, 0x49, 0xd5); + + /* + * EAX Unified Interface (using Direct X 8) {CA503B60-B176-11d4-A094-D0C0BF3A560C} + */ + DEFINE_GUID(CLSID_EAXDirectSound8, + 0xca503b60, + 0xb176, + 0x11d4, + 0xa0, 0x94, 0xd0, 0xc0, 0xbf, 0x3a, 0x56, 0xc); + + + +#ifdef DIRECTSOUND_VERSION +#if DIRECTSOUND_VERSION >= 0x0800 + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate8(GUID*, LPDIRECTSOUND8*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE8)(GUID*, LPDIRECTSOUND8*, IUnknown FAR*); +#endif +#endif + + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate(GUID*, LPDIRECTSOUND*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); + +#else // OPENAL +// #include + + #ifndef GUID_DEFINED + #define GUID_DEFINED + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; + #endif // GUID_DEFINED + + #ifndef DEFINE_GUID + #ifndef INITGUID + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID /*FAR*/ name + #else + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + #endif // INITGUID + #endif // DEFINE_GUID + + /* + * EAX OpenAL Extensions + */ + typedef ALenum (*EAXSet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALenum (*EAXGet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALboolean (*EAXSetBufferMode)(ALsizei, ALuint*, ALint); + typedef ALenum (*EAXGetBufferMode)(ALuint, ALint*); +#endif + +#pragma pack(push, 4) + + + + +//////////////////////////////////////////////////////////////////////////// +// Constants + +#define EAX_MAX_FXSLOTS 4 +#define EAX_MAX_ACTIVE_FXSLOTS 2 + +// The EAX_NULL_GUID is used by EAXFXSLOT_LOADEFFECT, EAXCONTEXT_PRIMARYFXSLOTID +// and EAXSOURCE_ACTIVEFXSLOTID + +// {00000000-0000-0000-0000-000000000000} +DEFINE_GUID(EAX_NULL_GUID, + 0x00000000, + 0x0000, + 0x0000, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + +// The EAX_PrimaryFXSlotID GUID is used by EAXSOURCE_ACTIVEFXSLOTID + +// {F317866D-924C-450C-861B-E6DAA25E7C20} +DEFINE_GUID(EAX_PrimaryFXSlotID, + 0xf317866d, + 0x924c, + 0x450c, + 0x86, 0x1b, 0xe6, 0xda, 0xa2, 0x5e, 0x7c, 0x20); + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Structures + +// Use this structure for EAXCONTEXT_ALL property. +typedef struct _EAXCONTEXTPROPERTIES +{ + GUID guidPrimaryFXSlotID; + float flDistanceFactor; + float flAirAbsorptionHF; + float flHFReference; +} EAXCONTEXTPROPERTIES, *LPEAXCONTEXTPROPERTIES; + +// Use this structure for EAXSOURCE_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXSOURCEFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +typedef struct _EAXSOURCEPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +} EAXSOURCEPROPERTIES, *LPEAXSOURCEPROPERTIES; + +// Use this structure for EAXSOURCE_ALLSENDPARAMETERS +// - all levels are hundredths of decibels +// +typedef struct _EAXSOURCEALLSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; // send level (at low and mid frequencies) + long lSendHF; // relative send level at high frequencies + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; + long lExclusion; + float flExclusionLFRatio; +} EAXSOURCEALLSENDPROPERTIES, *LPEAXSOURCEALLSENDPROPERTIES; + +// Use this structure for EAXSOURCE_ACTIVEFXSLOTID +typedef struct _EAXACTIVEFXSLOTS +{ + GUID guidActiveFXSlots[EAX_MAX_ACTIVE_FXSLOTS]; +} EAXACTIVEFXSLOTS, *LPEAXACTIVEFXSLOTS; + +// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. +#ifndef EAX_OBSTRUCTIONPROPERTIES_DEFINED +#define EAX_OBSTRUCTIONPROPERTIES_DEFINED +typedef struct _EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +} EAXOBSTRUCTIONPROPERTIES, *LPEAXOBSTRUCTIONPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. +#ifndef EAX_OCCLUSIONPROPERTIES_DEFINED +#define EAX_OCCLUSIONPROPERTIES_DEFINED +typedef struct _EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXOCCLUSIONPROPERTIES, *LPEAXOCCLUSIONPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. +#ifndef EAX_EXCLUSIONPROPERTIES_DEFINED +#define EAX_EXCLUSIONPROPERTIES_DEFINED +typedef struct _EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +} EAXEXCLUSIONPROPERTIES, *LPEAXEXCLUSIONPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_SENDPARAMETERS properties. +typedef struct _EAXSOURCESENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; + long lSendHF; +} EAXSOURCESENDPROPERTIES, *LPEAXSOURCESENDPROPERTIES; + +// Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS +typedef struct _EAXSOURCEOCCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXSOURCEOCCLUSIONSENDPROPERTIES, *LPEAXSOURCEOCCLUSIONSENDPROPERTIES; + +// Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS +typedef struct _EAXSOURCEEXCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lExclusion; + float flExclusionLFRatio; +} EAXSOURCEEXCLUSIONSENDPROPERTIES, *LPEAXSOURCEEXCLUSIONSENDPROPERTIES; + +// Use this structure for EAXFXSLOT_ALLPARAMETERS +// - all levels are hundredths of decibels +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myFXSlot.guidLoadEffect = EAX_REVERB_EFFECT; +// myFXSlot.lVolume = 0; +// myFXSlot.lLock = 1; +// myFXSlot.ulFlags = myFlags /* see EAXFXSLOTFLAGS below */ ; +// instead of: +// myFXSlot = { EAX_REVERB_EFFECT, 0, 1, 0x00000001 }; +// +typedef struct _EAXFXSLOTPROPERTIES +{ + GUID guidLoadEffect; + long lVolume; + long lLock; + unsigned long ulFlags; +} EAXFXSLOTPROPERTIES, *LPEAXFXSLOTPROPERTIES; + +// Use this structure for EAXREVERB_REFLECTIONSPAN and EAXREVERB_REVERBPAN properties. +#ifndef EAXVECTOR_DEFINED +#define EAXVECTOR_DEFINED +typedef struct _EAXVECTOR { + float x; + float y; + float z; +} EAXVECTOR; +#endif + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Error Codes + +#define EAX_OK 0 +#define EAXERR_INVALID_OPERATION (-1) +#define EAXERR_INVALID_VALUE (-2) +#define EAXERR_NO_EFFECT_LOADED (-3) +#define EAXERR_UNKNOWN_EFFECT (-4) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Context Object + +// {1D4870AD-0DEF-43c0-A40C-523632296342} +DEFINE_GUID(EAXPROPERTYID_EAX40_Context, + 0x1d4870ad, + 0xdef, + 0x43c0, + 0xa4, 0xc, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42); + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_Context EAXPROPERTYID_EAX40_Context + +typedef enum +{ + EAXCONTEXT_NONE = 0, + EAXCONTEXT_ALLPARAMETERS, + EAXCONTEXT_PRIMARYFXSLOTID, + EAXCONTEXT_DISTANCEFACTOR, + EAXCONTEXT_AIRABSORPTIONHF, + EAXCONTEXT_HFREFERENCE, + EAXCONTEXT_LASTERROR +} EAXCONTEXT_PROPERTY; + +// OR these flags with property id +#define EAXCONTEXT_PARAMETER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXCONTEXT_PARAMETER_DEFER 0x80000000 // changes take effect later +#define EAXCONTEXT_PARAMETER_COMMITDEFERREDSETTINGS (EAXCONTEXT_NONE | \ + EAXCONTEXT_PARAMETER_IMMEDIATE) + +// EAX Context property ranges and defaults: +#define EAXCONTEXT_DEFAULTPRIMARYFXSLOTID EAXPROPERTYID_EAX40_FXSlot0 + +#define EAXCONTEXT_MINDISTANCEFACTOR FLT_MIN //minimum positive value +#define EAXCONTEXT_MAXDISTANCEFACTOR FLT_MAX +#define EAXCONTEXT_DEFAULTDISTANCEFACTOR 1.0f + +#define EAXCONTEXT_MINAIRABSORPTIONHF (-100.0f) +#define EAXCONTEXT_MAXAIRABSORPTIONHF 0.0f +#define EAXCONTEXT_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXCONTEXT_MINHFREFERENCE 1000.0f +#define EAXCONTEXT_MAXHFREFERENCE 20000.0f +#define EAXCONTEXT_DEFAULTHFREFERENCE 5000.0f + +#define EAXCONTEXT_DEFAULTLASTERROR EAX_OK + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Effect Slot Objects + +// {C4D79F1E-F1AC-436b-A81D-A738E7045469} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot0, + 0xc4d79f1e, + 0xf1ac, + 0x436b, + 0xa8, 0x1d, 0xa7, 0x38, 0xe7, 0x4, 0x54, 0x69); + +// {08C00E96-74BE-4491-93AA-E8AD35A49117} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot1, + 0x8c00e96, + 0x74be, + 0x4491, + 0x93, 0xaa, 0xe8, 0xad, 0x35, 0xa4, 0x91, 0x17); + +// {1D433B88-F0F6-4637-919F-60E7E06B5EDD} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot2, + 0x1d433b88, + 0xf0f6, + 0x4637, + 0x91, 0x9f, 0x60, 0xe7, 0xe0, 0x6b, 0x5e, 0xdd); + +// {EFFF08EA-C7D8-44ab-93AD-6DBD5F910064} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot3, + 0xefff08ea, + 0xc7d8, + 0x44ab, + 0x93, 0xad, 0x6d, 0xbd, 0x5f, 0x91, 0x0, 0x64); + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_FXSlot0 EAXPROPERTYID_EAX40_FXSlot0 +#define EAXPROPERTYID_EAX_FXSlot1 EAXPROPERTYID_EAX40_FXSlot1 +#define EAXPROPERTYID_EAX_FXSlot2 EAXPROPERTYID_EAX40_FXSlot2 +#define EAXPROPERTYID_EAX_FXSlot3 EAXPROPERTYID_EAX40_FXSlot3 + +// FXSlot object properties +typedef enum +{ + EAXFXSLOT_PARAMETER = 0, // range 0-0x40 reserved for loaded effect parameters + EAXFXSLOT_NONE = 0x10000, + EAXFXSLOT_ALLPARAMETERS, + EAXFXSLOT_LOADEFFECT, + EAXFXSLOT_VOLUME, + EAXFXSLOT_LOCK, + EAXFXSLOT_FLAGS +} EAXFXSLOT_PROPERTY; + +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXFXSLOTFLAGS_ENVIRONMENT; +// instead of: +// myFlags = 0x00000001; +// +#define EAXFXSLOTFLAGS_ENVIRONMENT 0x00000001 +#define EAXFXSLOTFLAGS_RESERVED 0xFFFFFFFE // reserved future use + +// EAX Effect Slot property ranges and defaults: +#define EAXFXSLOT_MINVOLUME (-10000) +#define EAXFXSLOT_MAXVOLUME 0 +#define EAXFXSLOT_DEFAULTVOLUME 0 + +#define EAXFXSLOT_MINLOCK 0 +#define EAXFXSLOT_MAXLOCK 1 + +enum +{ + EAXFXSLOT_UNLOCKED = 0, + EAXFXSLOT_LOCKED = 1 +}; + +#define EAXFXSLOT_DEFAULTFLAGS (EAXFXSLOTFLAGS_ENVIRONMENT) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Source Object + +// {1B86B823-22DF-4eae-8B3C-1278CE544227} +DEFINE_GUID(EAXPROPERTYID_EAX40_Source, + 0x1b86b823, + 0x22df, + 0x4eae, + 0x8b, 0x3c, 0x12, 0x78, 0xce, 0x54, 0x42, 0x27); + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_Source EAXPROPERTYID_EAX40_Source + +// Source object properties +typedef enum +{ + EAXSOURCE_NONE, + EAXSOURCE_ALLPARAMETERS, + EAXSOURCE_OBSTRUCTIONPARAMETERS, + EAXSOURCE_OCCLUSIONPARAMETERS, + EAXSOURCE_EXCLUSIONPARAMETERS, + EAXSOURCE_DIRECT, + EAXSOURCE_DIRECTHF, + EAXSOURCE_ROOM, + EAXSOURCE_ROOMHF, + EAXSOURCE_OBSTRUCTION, + EAXSOURCE_OBSTRUCTIONLFRATIO, + EAXSOURCE_OCCLUSION, + EAXSOURCE_OCCLUSIONLFRATIO, + EAXSOURCE_OCCLUSIONROOMRATIO, + EAXSOURCE_OCCLUSIONDIRECTRATIO, + EAXSOURCE_EXCLUSION, + EAXSOURCE_EXCLUSIONLFRATIO, + EAXSOURCE_OUTSIDEVOLUMEHF, + EAXSOURCE_DOPPLERFACTOR, + EAXSOURCE_ROLLOFFFACTOR, + EAXSOURCE_ROOMROLLOFFFACTOR, + EAXSOURCE_AIRABSORPTIONFACTOR, + EAXSOURCE_FLAGS, + EAXSOURCE_SENDPARAMETERS, + EAXSOURCE_ALLSENDPARAMETERS, + EAXSOURCE_OCCLUSIONSENDPARAMETERS, + EAXSOURCE_EXCLUSIONSENDPARAMETERS, + EAXSOURCE_ACTIVEFXSLOTID, +} EAXSOURCE_PROPERTY; + +// OR these flags with property id +#define EAXSOURCE_PARAMETER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXSOURCE_PARAMETER_DEFERRED 0x80000000 // changes take effect later +#define EAXSOURCE_PARAMETER_COMMITDEFERREDSETTINGS (EAXSOURCE_NONE | \ + EAXSOURCE_PARAMETER_IMMEDIATE) +// Used by EAXSOURCE_FLAGS for EAXSOURCEFLAGS_xxxAUTO +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXSOURCE_DIRECTHFAUTO | EAXSOURCE_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAXSOURCEFLAGS_DIRECTHFAUTO 0x00000001 // relates to EAXSOURCE_DIRECTHF +#define EAXSOURCEFLAGS_ROOMAUTO 0x00000002 // relates to EAXSOURCE_ROOM +#define EAXSOURCEFLAGS_ROOMHFAUTO 0x00000004 // relates to EAXSOURCE_ROOMHF +#define EAXSOURCEFLAGS_RESERVED 0xFFFFFFF8 // reserved future use + +// EAX Source property ranges and defaults: +#define EAXSOURCE_MINSEND (-10000) +#define EAXSOURCE_MAXSEND 0 +#define EAXSOURCE_DEFAULTSEND 0 + +#define EAXSOURCE_MINSENDHF (-10000) +#define EAXSOURCE_MAXSENDHF 0 +#define EAXSOURCE_DEFAULTSENDHF 0 + +#define EAXSOURCE_MINDIRECT (-10000) +#define EAXSOURCE_MAXDIRECT 1000 +#define EAXSOURCE_DEFAULTDIRECT 0 + +#define EAXSOURCE_MINDIRECTHF (-10000) +#define EAXSOURCE_MAXDIRECTHF 0 +#define EAXSOURCE_DEFAULTDIRECTHF 0 + +#define EAXSOURCE_MINROOM (-10000) +#define EAXSOURCE_MAXROOM 1000 +#define EAXSOURCE_DEFAULTROOM 0 + +#define EAXSOURCE_MINROOMHF (-10000) +#define EAXSOURCE_MAXROOMHF 0 +#define EAXSOURCE_DEFAULTROOMHF 0 + +#define EAXSOURCE_MINOBSTRUCTION (-10000) +#define EAXSOURCE_MAXOBSTRUCTION 0 +#define EAXSOURCE_DEFAULTOBSTRUCTION 0 + +#define EAXSOURCE_MINOBSTRUCTIONLFRATIO 0.0f +#define EAXSOURCE_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAXSOURCE_MINOCCLUSION (-10000) +#define EAXSOURCE_MAXOCCLUSION 0 +#define EAXSOURCE_DEFAULTOCCLUSION 0 + +#define EAXSOURCE_MINOCCLUSIONLFRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXSOURCE_MINOCCLUSIONROOMRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONROOMRATIO 10.0f +#define EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO 1.5f + +#define EAXSOURCE_MINOCCLUSIONDIRECTRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONDIRECTRATIO 10.0f +#define EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO 1.0f + +#define EAXSOURCE_MINEXCLUSION (-10000) +#define EAXSOURCE_MAXEXCLUSION 0 +#define EAXSOURCE_DEFAULTEXCLUSION 0 + +#define EAXSOURCE_MINEXCLUSIONLFRATIO 0.0f +#define EAXSOURCE_MAXEXCLUSIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTEXCLUSIONLFRATIO 1.0f + +#define EAXSOURCE_MINOUTSIDEVOLUMEHF (-10000) +#define EAXSOURCE_MAXOUTSIDEVOLUMEHF 0 +#define EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAXSOURCE_MINDOPPLERFACTOR 0.0f +#define EAXSOURCE_MAXDOPPLERFACTOR 10.f +#define EAXSOURCE_DEFAULTDOPPLERFACTOR 1.0f + +#define EAXSOURCE_MINROLLOFFFACTOR 0.0f +#define EAXSOURCE_MAXROLLOFFFACTOR 10.f +#define EAXSOURCE_DEFAULTROLLOFFFACTOR 0.0f + +#define EAXSOURCE_MINROOMROLLOFFFACTOR 0.0f +#define EAXSOURCE_MAXROOMROLLOFFFACTOR 10.f +#define EAXSOURCE_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXSOURCE_MINAIRABSORPTIONFACTOR 0.0f +#define EAXSOURCE_MAXAIRABSORPTIONFACTOR 10.0f +#define EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR 0.0f + +#define EAXSOURCE_DEFAULTFLAGS (EAXSOURCEFLAGS_DIRECTHFAUTO | \ + EAXSOURCEFLAGS_ROOMAUTO | \ + EAXSOURCEFLAGS_ROOMHFAUTO ) + +#define EAXSOURCE_DEFAULTACTIVEFXSLOTID {{ EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] }, \ + { EAX_PrimaryFXSlotID.Data1, EAX_PrimaryFXSlotID.Data2, \ + EAX_PrimaryFXSlotID.Data3, EAX_PrimaryFXSlotID.Data4[0],\ + EAX_PrimaryFXSlotID.Data4[1],EAX_PrimaryFXSlotID.Data4[2],\ + EAX_PrimaryFXSlotID.Data4[3],EAX_PrimaryFXSlotID.Data4[4],\ + EAX_PrimaryFXSlotID.Data4[5],EAX_PrimaryFXSlotID.Data4[6],\ + EAX_PrimaryFXSlotID.Data4[7] }} + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Reverb Effect + +// EAX REVERB {0CF95C8F-A3CC-4849-B0B6-832ECC1822DF} +DEFINE_GUID(EAX_REVERB_EFFECT, + 0xcf95c8f, + 0xa3cc, + 0x4849, + 0xb0, 0xb6, 0x83, 0x2e, 0xcc, 0x18, 0x22, 0xdf); + +// Reverb effect properties +typedef enum +{ + EAXREVERB_NONE, + EAXREVERB_ALLPARAMETERS, + EAXREVERB_ENVIRONMENT, + EAXREVERB_ENVIRONMENTSIZE, + EAXREVERB_ENVIRONMENTDIFFUSION, + EAXREVERB_ROOM, + EAXREVERB_ROOMHF, + EAXREVERB_ROOMLF, + EAXREVERB_DECAYTIME, + EAXREVERB_DECAYHFRATIO, + EAXREVERB_DECAYLFRATIO, + EAXREVERB_REFLECTIONS, + EAXREVERB_REFLECTIONSDELAY, + EAXREVERB_REFLECTIONSPAN, + EAXREVERB_REVERB, + EAXREVERB_REVERBDELAY, + EAXREVERB_REVERBPAN, + EAXREVERB_ECHOTIME, + EAXREVERB_ECHODEPTH, + EAXREVERB_MODULATIONTIME, + EAXREVERB_MODULATIONDEPTH, + EAXREVERB_AIRABSORPTIONHF, + EAXREVERB_HFREFERENCE, + EAXREVERB_LFREFERENCE, + EAXREVERB_ROOMROLLOFFFACTOR, + EAXREVERB_FLAGS, +} EAXREVERB_PROPERTY; + +// OR these flags with property id +#define EAXREVERB_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXREVERB_DEFERRED 0x80000000 // changes take effect later +#define EAXREVERB_COMMITDEFERREDSETTINGS (EAXREVERB_NONE | \ + EAXREVERB_IMMEDIATE) + +// used by EAXREVERB_ENVIRONMENT +#ifndef EAX_ENVIRONMENTS_DEFINED +#define EAX_ENVIRONMENTS_DEFINED +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_UNDEFINED, + + EAX_ENVIRONMENT_COUNT +}; +#endif + +// Used by EAXREVERB_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXREVERBFLAGS_DECAYTIMESCALE | EAXREVERBFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAXREVERBFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAXREVERBFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAXREVERBFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAXREVERBFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAXREVERBFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time +#define EAXREVERBFLAGS_ECHOTIMESCALE 0x00000040 // echo time +#define EAXREVERBFLAGS_MODULATIONTIMESCALE 0x00000080 // modulation time +// This flag limits high-frequency decay time according to air absorption. +#define EAXREVERBFLAGS_DECAYHFLIMIT 0x00000020 +#define EAXREVERBFLAGS_RESERVED 0xFFFFFF00 // reserved future use + +// Use this structure for EAXREVERB_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times and delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myReverb.lRoom = -1000; +// myReverb.lRoomHF = -100; +// ... +// myReverb.dwFlags = myFlags /* see EAXREVERBFLAGS below */ ; +// instead of: +// myReverb = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +typedef struct _EAXREVERBPROPERTIES +{ + unsigned long ulEnvironment; // sets all reverb properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +} EAXREVERBPROPERTIES, *LPEAXREVERBPROPERTIES; + +// Property ranges and defaults: +#define EAXREVERB_MINENVIRONMENT 0 +#define EAXREVERB_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAXREVERB_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAXREVERB_MINENVIRONMENTSIZE 1.0f +#define EAXREVERB_MAXENVIRONMENTSIZE 100.0f +#define EAXREVERB_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAXREVERB_MINENVIRONMENTDIFFUSION 0.0f +#define EAXREVERB_MAXENVIRONMENTDIFFUSION 1.0f +#define EAXREVERB_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAXREVERB_MINROOM (-10000) +#define EAXREVERB_MAXROOM 0 +#define EAXREVERB_DEFAULTROOM (-1000) + +#define EAXREVERB_MINROOMHF (-10000) +#define EAXREVERB_MAXROOMHF 0 +#define EAXREVERB_DEFAULTROOMHF (-100) + +#define EAXREVERB_MINROOMLF (-10000) +#define EAXREVERB_MAXROOMLF 0 +#define EAXREVERB_DEFAULTROOMLF 0 + +#define EAXREVERB_MINDECAYTIME 0.1f +#define EAXREVERB_MAXDECAYTIME 20.0f +#define EAXREVERB_DEFAULTDECAYTIME 1.49f + +#define EAXREVERB_MINDECAYHFRATIO 0.1f +#define EAXREVERB_MAXDECAYHFRATIO 2.0f +#define EAXREVERB_DEFAULTDECAYHFRATIO 0.83f + +#define EAXREVERB_MINDECAYLFRATIO 0.1f +#define EAXREVERB_MAXDECAYLFRATIO 2.0f +#define EAXREVERB_DEFAULTDECAYLFRATIO 1.00f + +#define EAXREVERB_MINREFLECTIONS (-10000) +#define EAXREVERB_MAXREFLECTIONS 1000 +#define EAXREVERB_DEFAULTREFLECTIONS (-2602) + +#define EAXREVERB_MINREFLECTIONSDELAY 0.0f +#define EAXREVERB_MAXREFLECTIONSDELAY 0.3f +#define EAXREVERB_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAXREVERB_DEFAULTREFLECTIONSPAN {0.0f, 0.0f, 0.0f} + +#define EAXREVERB_MINREVERB (-10000) +#define EAXREVERB_MAXREVERB 2000 +#define EAXREVERB_DEFAULTREVERB 200 + +#define EAXREVERB_MINREVERBDELAY 0.0f +#define EAXREVERB_MAXREVERBDELAY 0.1f +#define EAXREVERB_DEFAULTREVERBDELAY 0.011f + +#define EAXREVERB_DEFAULTREVERBPAN {0.0f, 0.0f, 0.0f} + +#define EAXREVERB_MINECHOTIME 0.075f +#define EAXREVERB_MAXECHOTIME 0.25f +#define EAXREVERB_DEFAULTECHOTIME 0.25f + +#define EAXREVERB_MINECHODEPTH 0.0f +#define EAXREVERB_MAXECHODEPTH 1.0f +#define EAXREVERB_DEFAULTECHODEPTH 0.0f + +#define EAXREVERB_MINMODULATIONTIME 0.04f +#define EAXREVERB_MAXMODULATIONTIME 4.0f +#define EAXREVERB_DEFAULTMODULATIONTIME 0.25f + +#define EAXREVERB_MINMODULATIONDEPTH 0.0f +#define EAXREVERB_MAXMODULATIONDEPTH 1.0f +#define EAXREVERB_DEFAULTMODULATIONDEPTH 0.0f + +#define EAXREVERB_MINAIRABSORPTIONHF (-100.0f) +#define EAXREVERB_MAXAIRABSORPTIONHF 0.0f +#define EAXREVERB_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXREVERB_MINHFREFERENCE 1000.0f +#define EAXREVERB_MAXHFREFERENCE 20000.0f +#define EAXREVERB_DEFAULTHFREFERENCE 5000.0f + +#define EAXREVERB_MINLFREFERENCE 20.0f +#define EAXREVERB_MAXLFREFERENCE 1000.0f +#define EAXREVERB_DEFAULTLFREFERENCE 250.0f + +#define EAXREVERB_MINROOMROLLOFFFACTOR 0.0f +#define EAXREVERB_MAXROOMROLLOFFFACTOR 10.0f +#define EAXREVERB_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXREVERB_DEFAULTFLAGS (EAXREVERBFLAGS_DECAYTIMESCALE | \ + EAXREVERBFLAGS_REFLECTIONSSCALE | \ + EAXREVERBFLAGS_REFLECTIONSDELAYSCALE | \ + EAXREVERBFLAGS_REVERBSCALE | \ + EAXREVERBFLAGS_REVERBDELAYSCALE | \ + EAXREVERBFLAGS_DECAYHFLIMIT) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// + +// New Effect Types + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// AGC Compressor Effect + +// EAX AGC COMPRESSOR {BFB7A01E-7825-4039-927F-3AABDA0C560} + +DEFINE_GUID(EAX_AGCCOMPRESSOR_EFFECT, + 0xbfb7a01e, + 0x7825, + 0x4039, + 0x92, 0x7f, 0x3, 0xaa, 0xbd, 0xa0, 0xc5, 0x60); + +// AGC Compressor properties +typedef enum +{ + EAXAGCCOMPRESSOR_NONE, + EAXAGCCOMPRESSOR_ALLPARAMETERS, + EAXAGCCOMPRESSOR_ONOFF +} EAXAGCCOMPRESSOR_PROPERTY; + +// OR these flags with property id +#define EAXAGCCOMPRESSOR_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXAGCCOMPRESSOR_DEFERRED 0x80000000 // changes take effect later +#define EAXAGCCOMPRESSOR_COMMITDEFERREDSETTINGS (EAXAGCCOMPRESSOR_NONE | \ + EAXAGCCOMPRESSOR_IMMEDIATE) + +// Use this structure for EAXAGCCOMPRESSOR_ALLPARAMETERS +typedef struct _EAXAGCCOMPRESSORPROPERTIES +{ + unsigned long ulOnOff; // Switch Compressor on or off +} EAXAGCCOMPRESSORPROPERTIES, *LPEAXAGCCOMPRESSORPROPERTIES; + +// Property ranges and defaults: + +#define EAXAGCCOMPRESSOR_MINONOFF 0 +#define EAXAGCCOMPRESSOR_MAXONOFF 1 +#define EAXAGCCOMPRESSOR_DEFAULTONOFF 1 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Autowah Effect + +// EAX AUTOWAH {EC3130C0-AC7A-11D2-88DD-A024D13CE1} +DEFINE_GUID(EAX_AUTOWAH_EFFECT, + 0xec3130c0, + 0xac7a, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Autowah properties +typedef enum +{ + EAXAUTOWAH_NONE, + EAXAUTOWAH_ALLPARAMETERS, + EAXAUTOWAH_ATTACKTIME, + EAXAUTOWAH_RELEASETIME, + EAXAUTOWAH_RESONANCE, + EAXAUTOWAH_PEAKLEVEL +} EAXAUTOWAH_PROPERTY; + +// OR these flags with property id +#define EAXAUTOWAH_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXAUTOWAH_DEFERRED 0x80000000 // changes take effect later +#define EAXAUTOWAH_COMMITDEFERREDSETTINGS (EAXAUTOWAH_NONE | \ + EAXAUTOWAH_IMMEDIATE) + +// Use this structure for EAXAUTOWAH_ALLPARAMETERS +typedef struct _EAXAUTOWAHPROPERTIES +{ + float flAttackTime; // Attack time (seconds) + float flReleaseTime; // Release time (seconds) + long lResonance; // Resonance (mB) + long lPeakLevel; // Peak level (mB) +} EAXAUTOWAHPROPERTIES, *LPEAXAUTOWAHPROPERTIES; + +// Property ranges and defaults: + +#define EAXAUTOWAH_MINATTACKTIME 0.0001f +#define EAXAUTOWAH_MAXATTACKTIME 1.0f +#define EAXAUTOWAH_DEFAULTATTACKTIME 0.06f + +#define EAXAUTOWAH_MINRELEASETIME 0.0001f +#define EAXAUTOWAH_MAXRELEASETIME 1.0f +#define EAXAUTOWAH_DEFAULTRELEASETIME 0.06f + +#define EAXAUTOWAH_MINRESONANCE 600 +#define EAXAUTOWAH_MAXRESONANCE 6000 +#define EAXAUTOWAH_DEFAULTRESONANCE 6000 + +#define EAXAUTOWAH_MINPEAKLEVEL (-9000) +#define EAXAUTOWAH_MAXPEAKLEVEL 9000 +#define EAXAUTOWAH_DEFAULTPEAKLEVEL 2100 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Chorus Effect + +// EAX CHORUS {DE6D6FE0-AC79-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_CHORUS_EFFECT, + 0xde6d6fe0, + 0xac79, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + + +// Chorus properties +typedef enum +{ + EAXCHORUS_NONE, + EAXCHORUS_ALLPARAMETERS, + EAXCHORUS_WAVEFORM, + EAXCHORUS_PHASE, + EAXCHORUS_RATE, + EAXCHORUS_DEPTH, + EAXCHORUS_FEEDBACK, + EAXCHORUS_DELAY +} EAXCHORUS_PROPERTY; + +// OR these flags with property id +#define EAXCHORUS_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXCHORUS_DEFERRED 0x80000000 // changes take effect later +#define EAXCHORUS_COMMITDEFERREDSETTINGS (EAXCHORUS_NONE | \ + EAXCHORUS_IMMEDIATE) + +// used by EAXCHORUS_WAVEFORM +enum +{ + EAX_CHORUS_SINUSOID, + EAX_CHORUS_TRIANGLE +}; + +// Use this structure for EAXCHORUS_ALLPARAMETERS +typedef struct _EAXCHORUSPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (-1 to 1) + float flDelay; // Delay (seconds) +} EAXCHORUSPROPERTIES, *LPEAXCHORUSPROPERTIES; + +// Property ranges and defaults: + +#define EAXCHORUS_MINWAVEFORM 0 +#define EAXCHORUS_MAXWAVEFORM 1 +#define EAXCHORUS_DEFAULTWAVEFORM 1 + +#define EAXCHORUS_MINPHASE (-180) +#define EAXCHORUS_MAXPHASE 180 +#define EAXCHORUS_DEFAULTPHASE 90 + +#define EAXCHORUS_MINRATE 0.0f +#define EAXCHORUS_MAXRATE 10.0f +#define EAXCHORUS_DEFAULTRATE 1.1f + +#define EAXCHORUS_MINDEPTH 0.0f +#define EAXCHORUS_MAXDEPTH 1.0f +#define EAXCHORUS_DEFAULTDEPTH 0.1f + +#define EAXCHORUS_MINFEEDBACK (-1.0f) +#define EAXCHORUS_MAXFEEDBACK 1.0f +#define EAXCHORUS_DEFAULTFEEDBACK 0.25f + +#define EAXCHORUS_MINDELAY 0.0f +#define EAXCHORUS_MAXDELAY 0.016f +#define EAXCHORUS_DEFAULTDELAY 0.016f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Distortion Effect + +// EAX DISTORTION {975A4CE0-AC7E-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_DISTORTION_EFFECT, + 0x975a4ce0, + 0xac7e, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Distortion properties +typedef enum +{ + EAXDISTORTION_NONE, + EAXDISTORTION_ALLPARAMETERS, + EAXDISTORTION_EDGE, + EAXDISTORTION_GAIN, + EAXDISTORTION_LOWPASSCUTOFF, + EAXDISTORTION_EQCENTER, + EAXDISTORTION_EQBANDWIDTH +} EAXDISTORTION_PROPERTY; + +// OR these flags with property id +#define EAXDISTORTION_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXDISTORTION_DEFERRED 0x80000000 // changes take effect later +#define EAXDISTORTION_COMMITDEFERREDSETTINGS (EAXDISTORTION_NONE | \ + EAXDISTORTION_IMMEDIATE) + +// Use this structure for EAXDISTORTION_ALLPARAMETERS +typedef struct _EAXDISTORTIONPROPERTIES +{ + float flEdge; // Controls the shape of the distortion (0 to 1) + long lGain; // Controls the post distortion gain (mB) + float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) + float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) + float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) +} EAXDISTORTIONPROPERTIES, *LPEAXDISTORTIONPROPERTIES; + +// Property ranges and defaults: + +#define EAXDISTORTION_MINEDGE 0.0f +#define EAXDISTORTION_MAXEDGE 1.0f +#define EAXDISTORTION_DEFAULTEDGE 0.2f + +#define EAXDISTORTION_MINGAIN (-6000) +#define EAXDISTORTION_MAXGAIN 0 +#define EAXDISTORTION_DEFAULTGAIN (-2600) + +#define EAXDISTORTION_MINLOWPASSCUTOFF 80.0f +#define EAXDISTORTION_MAXLOWPASSCUTOFF 24000.0f +#define EAXDISTORTION_DEFAULTLOWPASSCUTOFF 8000.0f + +#define EAXDISTORTION_MINEQCENTER 80.0f +#define EAXDISTORTION_MAXEQCENTER 24000.0f +#define EAXDISTORTION_DEFAULTEQCENTER 3600.0f + +#define EAXDISTORTION_MINEQBANDWIDTH 80.0f +#define EAXDISTORTION_MAXEQBANDWIDTH 24000.0f +#define EAXDISTORTION_DEFAULTEQBANDWIDTH 3600.0f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Echo Effect + +// EAX ECHO {E9F1BC0-AC82-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_ECHO_EFFECT, + 0xe9f1bc0, + 0xac82, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Echo properties +typedef enum +{ + EAXECHO_NONE, + EAXECHO_ALLPARAMETERS, + EAXECHO_DELAY, + EAXECHO_LRDELAY, + EAXECHO_DAMPING, + EAXECHO_FEEDBACK, + EAXECHO_SPREAD +} EAXECHO_PROPERTY; + +// OR these flags with property id +#define EAXECHO_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXECHO_DEFERRED 0x80000000 // changes take effect later +#define EAXECHO_COMMITDEFERREDSETTINGS (EAXECHO_NONE | \ + EAXECHO_IMMEDIATE) + +// Use this structure for EAXECHO_ALLPARAMETERS +typedef struct _EAXECHOPROPERTIES +{ + float flDelay; // Controls the initial delay time (seconds) + float flLRDelay; // Controls the delay time between the first and second taps (seconds) + float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) + float flFeedback; // Controls the duration of echo repetition (0 to 1) + float flSpread; // Controls the left-right spread of the echoes +} EAXECHOPROPERTIES, *LPEAXECHOPROPERTIES; + +// Property ranges and defaults: + +#define EAXECHO_MINDAMPING 0.0f +#define EAXECHO_MAXDAMPING 0.99f +#define EAXECHO_DEFAULTDAMPING 0.5f + +#define EAXECHO_MINDELAY 0.0f +#define EAXECHO_MAXDELAY 0.207f +#define EAXECHO_DEFAULTDELAY 0.1f + +#define EAXECHO_MINLRDELAY 0.0f +#define EAXECHO_MAXLRDELAY 0.404f +#define EAXECHO_DEFAULTLRDELAY 0.1f + +#define EAXECHO_MINFEEDBACK 0.0f +#define EAXECHO_MAXFEEDBACK 1.0f +#define EAXECHO_DEFAULTFEEDBACK 0.5f + +#define EAXECHO_MINSPREAD (-1.0f) +#define EAXECHO_MAXSPREAD 1.0f +#define EAXECHO_DEFAULTSPREAD (-1.0f) + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Equalizer Effect + +// EAX EQUALIZER {65F94CE0-9793-11D3-939D-C0F02DD6F0} + +DEFINE_GUID(EAX_EQUALIZER_EFFECT, + 0x65f94ce0, + 0x9793, + 0x11d3, + 0x93, 0x9d, 0x0, 0xc0, 0xf0, 0x2d, 0xd6, 0xf0); + + +// Equalizer properties +typedef enum +{ + EAXEQUALIZER_NONE, + EAXEQUALIZER_ALLPARAMETERS, + EAXEQUALIZER_LOWGAIN, + EAXEQUALIZER_LOWCUTOFF, + EAXEQUALIZER_MID1GAIN, + EAXEQUALIZER_MID1CENTER, + EAXEQUALIZER_MID1WIDTH, + EAXEQUALIZER_MID2GAIN, + EAXEQUALIZER_MID2CENTER, + EAXEQUALIZER_MID2WIDTH, + EAXEQUALIZER_HIGHGAIN, + EAXEQUALIZER_HIGHCUTOFF +} EAXEQUALIZER_PROPERTY; + +// OR these flags with property id +#define EAXEQUALIZER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXEQUALIZER_DEFERRED 0x80000000 // changes take effect later +#define EAXEQUALIZER_COMMITDEFERREDSETTINGS (EAXEQUALIZER_NONE | \ + EAXEQUALIZER_IMMEDIATE) + +// Use this structure for EAXEQUALIZER_ALLPARAMETERS +typedef struct _EAXEQUALIZERPROPERTIES +{ + long lLowGain; // (mB) + float flLowCutOff; // (Hz) + long lMid1Gain; // (mB) + float flMid1Center; // (Hz) + float flMid1Width; // (octaves) + long lMid2Gain; // (mB) + float flMid2Center; // (Hz) + float flMid2Width; // (octaves) + long lHighGain; // (mB) + float flHighCutOff; // (Hz) +} EAXEQUALIZERPROPERTIES, *LPEAXEQUALIZERPROPERTIES; + +// Property ranges and defaults: + +#define EAXEQUALIZER_MINLOWGAIN (-1800) +#define EAXEQUALIZER_MAXLOWGAIN 1800 +#define EAXEQUALIZER_DEFAULTLOWGAIN 0 + +#define EAXEQUALIZER_MINLOWCUTOFF 50.0f +#define EAXEQUALIZER_MAXLOWCUTOFF 800.0f +#define EAXEQUALIZER_DEFAULTLOWCUTOFF 200.0f + +#define EAXEQUALIZER_MINMID1GAIN (-1800) +#define EAXEQUALIZER_MAXMID1GAIN 1800 +#define EAXEQUALIZER_DEFAULTMID1GAIN 0 + +#define EAXEQUALIZER_MINMID1CENTER 200.0f +#define EAXEQUALIZER_MAXMID1CENTER 3000.0f +#define EAXEQUALIZER_DEFAULTMID1CENTER 500.0f + +#define EAXEQUALIZER_MINMID1WIDTH 0.01f +#define EAXEQUALIZER_MAXMID1WIDTH 1.0f +#define EAXEQUALIZER_DEFAULTMID1WIDTH 1.0f + +#define EAXEQUALIZER_MINMID2GAIN (-1800) +#define EAXEQUALIZER_MAXMID2GAIN 1800 +#define EAXEQUALIZER_DEFAULTMID2GAIN 0 + +#define EAXEQUALIZER_MINMID2CENTER 1000.0f +#define EAXEQUALIZER_MAXMID2CENTER 8000.0f +#define EAXEQUALIZER_DEFAULTMID2CENTER 3000.0f + +#define EAXEQUALIZER_MINMID2WIDTH 0.01f +#define EAXEQUALIZER_MAXMID2WIDTH 1.0f +#define EAXEQUALIZER_DEFAULTMID2WIDTH 1.0f + +#define EAXEQUALIZER_MINHIGHGAIN (-1800) +#define EAXEQUALIZER_MAXHIGHGAIN 1800 +#define EAXEQUALIZER_DEFAULTHIGHGAIN 0 + +#define EAXEQUALIZER_MINHIGHCUTOFF 4000.0f +#define EAXEQUALIZER_MAXHIGHCUTOFF 16000.0f +#define EAXEQUALIZER_DEFAULTHIGHCUTOFF 6000.0f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Flanger Effect + +// EAX FLANGER {A70007C0-7D2-11D3-9B1E-A024D13CE1} + +DEFINE_GUID(EAX_FLANGER_EFFECT, + 0xa70007c0, + 0x7d2, + 0x11d3, + 0x9b, 0x1e, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Flanger properties +typedef enum +{ + EAXFLANGER_NONE, + EAXFLANGER_ALLPARAMETERS, + EAXFLANGER_WAVEFORM, + EAXFLANGER_PHASE, + EAXFLANGER_RATE, + EAXFLANGER_DEPTH, + EAXFLANGER_FEEDBACK, + EAXFLANGER_DELAY +} EAXFLANGER_PROPERTY; + +// OR these flags with property id +#define EAXFLANGER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXFLANGER_DEFERRED 0x80000000 // changes take effect later +#define EAXFLANGER_COMMITDEFERREDSETTINGS (EAXFLANGER_NONE | \ + EAXFLANGER_IMMEDIATE) + +// used by EAXFLANGER_WAVEFORM +enum +{ + EAX_FLANGER_SINUSOID, + EAX_FLANGER_TRIANGLE +}; + +// Use this structure for EAXFLANGER_ALLPARAMETERS +typedef struct _EAXFLANGERPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (0 to 1) + float flDelay; // Delay (seconds) +} EAXFLANGERPROPERTIES, *LPEAXFLANGERPROPERTIES; + +// Property ranges and defaults: + +#define EAXFLANGER_MINWAVEFORM 0 +#define EAXFLANGER_MAXWAVEFORM 1 +#define EAXFLANGER_DEFAULTWAVEFORM 1 + +#define EAXFLANGER_MINPHASE (-180) +#define EAXFLANGER_MAXPHASE 180 +#define EAXFLANGER_DEFAULTPHASE 0 + +#define EAXFLANGER_MINRATE 0.0f +#define EAXFLANGER_MAXRATE 10.0f +#define EAXFLANGER_DEFAULTRATE 0.27f + +#define EAXFLANGER_MINDEPTH 0.0f +#define EAXFLANGER_MAXDEPTH 1.0f +#define EAXFLANGER_DEFAULTDEPTH 1.0f + +#define EAXFLANGER_MINFEEDBACK (-1.0f) +#define EAXFLANGER_MAXFEEDBACK 1.0f +#define EAXFLANGER_DEFAULTFEEDBACK (-0.5f) + +#define EAXFLANGER_MINDELAY 0.0f +#define EAXFLANGER_MAXDELAY 0.004f +#define EAXFLANGER_DEFAULTDELAY 0.002f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Frequency Shifter Effect + +// EAX FREQUENCY SHIFTER {DC3E1880-9212-11D3-939D-C0F02DD6F0} + +DEFINE_GUID(EAX_FREQUENCYSHIFTER_EFFECT, + 0xdc3e1880, + 0x9212, + 0x11d3, + 0x93, 0x9d, 0x0, 0xc0, 0xf0, 0x2d, 0xd6, 0xf0); + +// Frequency Shifter properties +typedef enum +{ + EAXFREQUENCYSHIFTER_NONE, + EAXFREQUENCYSHIFTER_ALLPARAMETERS, + EAXFREQUENCYSHIFTER_FREQUENCY, + EAXFREQUENCYSHIFTER_LEFTDIRECTION, + EAXFREQUENCYSHIFTER_RIGHTDIRECTION +} EAXFREQUENCYSHIFTER_PROPERTY; + +// OR these flags with property id +#define EAXFREQUENCYSHIFTER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXFREQUENCYSHIFTER_DEFERRED 0x80000000 // changes take effect later +#define EAXFREQUENCYSHIFTER_COMMITDEFERREDSETTINGS (EAXFREQUENCYSHIFTER_NONE | \ + EAXFREQUENCYSHIFTER_IMMEDIATE) + +// used by EAXFREQUENCYSHIFTER_LEFTDIRECTION and EAXFREQUENCYSHIFTER_RIGHTDIRECTION +enum +{ + EAX_FREQUENCYSHIFTER_DOWN, + EAX_FREQUENCYSHIFTER_UP, + EAX_FREQUENCYSHIFTER_OFF +}; + +// Use this structure for EAXFREQUENCYSHIFTER_ALLPARAMETERS +typedef struct _EAXFREQUENCYSHIFTERPROPERTIES +{ + float flFrequency; // (Hz) + unsigned long ulLeftDirection; // see enum above + unsigned long ulRightDirection; // see enum above +} EAXFREQUENCYSHIFTERPROPERTIES, *LPEAXFREQUENCYSHIFTERPROPERTIES; + +// Property ranges and defaults: + +#define EAXFREQUENCYSHIFTER_MINFREQUENCY 0.0f +#define EAXFREQUENCYSHIFTER_MAXFREQUENCY 24000.0f +#define EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY 0.0f + +#define EAXFREQUENCYSHIFTER_MINLEFTDIRECTION 0 +#define EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION 2 +#define EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION 0 + +#define EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION 0 +#define EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION 2 +#define EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION 0 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Vocal Morpher Effect + +// EAX VOCAL MORPHER {E41CF10C-3383-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_VOCALMORPHER_EFFECT, + 0xe41cf10c, + 0x3383, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Vocal Morpher properties +typedef enum +{ + EAXVOCALMORPHER_NONE, + EAXVOCALMORPHER_ALLPARAMETERS, + EAXVOCALMORPHER_PHONEMEA, + EAXVOCALMORPHER_PHONEMEACOARSETUNING, + EAXVOCALMORPHER_PHONEMEB, + EAXVOCALMORPHER_PHONEMEBCOARSETUNING, + EAXVOCALMORPHER_WAVEFORM, + EAXVOCALMORPHER_RATE +} EAXVOCALMORPHER_PROPERTY; + +// OR these flags with property id +#define EAXVOCALMORPHER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXVOCALMORPHER_DEFERRED 0x80000000 // changes take effect later +#define EAXVOCALMORPHER_COMMITDEFERREDSETTINGS (EAXVOCALMORPHER_NONE | \ + EAXVOCALMORPHER_IMMEDIATE) + +// used by EAXVOCALMORPHER_PHONEMEA and EAXVOCALMORPHER_PHONEMEB +enum +{ + A, E, I, O, U, AA, AE, AH, AO, EH, ER, IH, IY, UH, UW, B, D, F, G, + J, K, L, M, N, P, R, S, T, V, Z +}; + +// used by EAXVOCALMORPHER_WAVEFORM +enum +{ + EAX_VOCALMORPHER_SINUSOID, + EAX_VOCALMORPHER_TRIANGLE, + EAX_VOCALMORPHER_SAWTOOTH +}; + +// Use this structure for EAXVOCALMORPHER_ALLPARAMETERS +typedef struct _EAXVOCALMORPHERPROPERTIES +{ + unsigned long ulPhonemeA; // see enum above + long lPhonemeACoarseTuning; // (semitones) + unsigned long ulPhonemeB; // see enum above + long lPhonemeBCoarseTuning; // (semitones) + unsigned long ulWaveform; // Waveform selector - see enum above + float flRate; // (Hz) +} EAXVOCALMORPHERPROPERTIES, *LPEAXVOCALMORPHERPROPERTIES; + +// Property ranges and defaults: + +#define EAXVOCALMORPHER_MINPHONEMEA 0 +#define EAXVOCALMORPHER_MAXPHONEMEA 29 +#define EAXVOCALMORPHER_DEFAULTPHONEMEA 0 + +#define EAXVOCALMORPHER_MINPHONEMEACOARSETUNING (-24) +#define EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING 24 +#define EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING 0 + +#define EAXVOCALMORPHER_MINPHONEMEB 0 +#define EAXVOCALMORPHER_MAXPHONEMEB 29 +#define EAXVOCALMORPHER_DEFAULTPHONEMEB 10 + +#define EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING (-24) +#define EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING 24 +#define EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING 0 + +#define EAXVOCALMORPHER_MINWAVEFORM 0 +#define EAXVOCALMORPHER_MAXWAVEFORM 2 +#define EAXVOCALMORPHER_DEFAULTWAVEFORM 0 + +#define EAXVOCALMORPHER_MINRATE 0.0f +#define EAXVOCALMORPHER_MAXRATE 10.0f +#define EAXVOCALMORPHER_DEFAULTRATE 1.41f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Pitch Shifter Effect + +// EAX PITCH SHIFTER {E7905100-AFB2-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_PITCHSHIFTER_EFFECT, + 0xe7905100, + 0xafb2, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Pitch Shifter properties +typedef enum +{ + EAXPITCHSHIFTER_NONE, + EAXPITCHSHIFTER_ALLPARAMETERS, + EAXPITCHSHIFTER_COARSETUNE, + EAXPITCHSHIFTER_FINETUNE +} EAXPITCHSHIFTER_PROPERTY; + +// OR these flags with property id +#define EAXPITCHSHIFTER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXPITCHSHIFTER_DEFERRED 0x80000000 // changes take effect later +#define EAXPITCHSHIFTER_COMMITDEFERREDSETTINGS (EAXPITCHSHIFTER_NONE | \ + EAXPITCHSHIFTER_IMMEDIATE) + +// Use this structure for EAXPITCHSHIFTER_ALLPARAMETERS +typedef struct _EAXPITCHSHIFTERPROPERTIES +{ + long lCoarseTune; // Amount of pitch shift (semitones) + long lFineTune; // Amount of pitch shift (cents) +} EAXPITCHSHIFTERPROPERTIES, *LPEAXPITCHSHIFTERPROPERTIES; + +// Property ranges and defaults: + +#define EAXPITCHSHIFTER_MINCOARSETUNE (-12) +#define EAXPITCHSHIFTER_MAXCOARSETUNE 12 +#define EAXPITCHSHIFTER_DEFAULTCOARSETUNE 12 + +#define EAXPITCHSHIFTER_MINFINETUNE (-50) +#define EAXPITCHSHIFTER_MAXFINETUNE 50 +#define EAXPITCHSHIFTER_DEFAULTFINETUNE 0 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Ring Modulator Effect + +// EAX RING MODULATOR {B89FE60-AFB5-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_RINGMODULATOR_EFFECT, + 0xb89fe60, + 0xafb5, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Ring Modulator properties +typedef enum +{ + EAXRINGMODULATOR_NONE, + EAXRINGMODULATOR_ALLPARAMETERS, + EAXRINGMODULATOR_FREQUENCY, + EAXRINGMODULATOR_HIGHPASSCUTOFF, + EAXRINGMODULATOR_WAVEFORM +} EAXRINGMODULATOR_PROPERTY; + +// OR these flags with property id +#define EAXRINGMODULATOR_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXRINGMODULATOR_DEFERRED 0x80000000 // changes take effect later +#define EAXRINGMODULATOR_COMMITDEFERREDSETTINGS (EAXRINGMODULATOR_NONE | \ + EAXRINGMODULATOR_IMMEDIATE) + +// used by EAXRINGMODULATOR_WAVEFORM +enum +{ + EAX_RINGMODULATOR_SINUSOID, + EAX_RINGMODULATOR_SAWTOOTH, + EAX_RINGMODULATOR_SQUARE +}; + +// Use this structure for EAXRINGMODULATOR_ALLPARAMETERS +typedef struct _EAXRINGMODULATORPROPERTIES +{ + float flFrequency; // Frequency of modulation (Hz) + float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) + unsigned long ulWaveform; // Waveform selector - see enum above +} EAXRINGMODULATORPROPERTIES, *LPEAXRINGMODULATORPROPERTIES; + +// Property ranges and defaults: + +#define EAXRINGMODULATOR_MINFREQUENCY 0.0f +#define EAXRINGMODULATOR_MAXFREQUENCY 8000.0f +#define EAXRINGMODULATOR_DEFAULTFREQUENCY 440.0f + +#define EAXRINGMODULATOR_MINHIGHPASSCUTOFF 0.0f +#define EAXRINGMODULATOR_MAXHIGHPASSCUTOFF 24000.0f +#define EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF 800.0f + +#define EAXRINGMODULATOR_MINWAVEFORM 0 +#define EAXRINGMODULATOR_MAXWAVEFORM 2 +#define EAXRINGMODULATOR_DEFAULTWAVEFORM 0 + +//////////////////////////////////////////////////////////////////////////// + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/openal/include/eax5.h b/src/openal/include/eax5.h new file mode 100644 index 0000000..929de67 --- /dev/null +++ b/src/openal/include/eax5.h @@ -0,0 +1,1816 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/*******************************************************************\ +* * +* EAX.H - Environmental Audio Extensions version 5.0 * +* for OpenAL and DirectSound3D * +* * +* File revision 0.9.6 version a (July 14th 2004) * +* EAX 5.0 API Spec version 1.5 * +* * +\*******************************************************************/ + +#ifndef EAX_H_INCLUDED +#define EAX_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef OPENAL + #include + + /* + * EAX Unified Interface (using Direct X 7) {4FF53B81-1CE0-11d3-AAB8-00A0C95949D5} + */ + DEFINE_GUID(CLSID_EAXDirectSound, + 0x4ff53b81, + 0x1ce0, + 0x11d3, + 0xaa, 0xb8, 0x0, 0xa0, 0xc9, 0x59, 0x49, 0xd5); + + /* + * EAX Unified Interface (using Direct X 8) {CA503B60-B176-11d4-A094-D0C0BF3A560C} + */ + DEFINE_GUID(CLSID_EAXDirectSound8, + 0xca503b60, + 0xb176, + 0x11d4, + 0xa0, 0x94, 0xd0, 0xc0, 0xbf, 0x3a, 0x56, 0xc); + + + +#ifdef DIRECTSOUND_VERSION +#if DIRECTSOUND_VERSION >= 0x0800 + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate8(GUID*, LPDIRECTSOUND8*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE8)(GUID*, LPDIRECTSOUND8*, IUnknown FAR*); +#endif +#endif + + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate(GUID*, LPDIRECTSOUND*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); + +#else // OPENAL + #include + + #ifndef GUID_DEFINED + #define GUID_DEFINED + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; + #endif // GUID_DEFINED + + #ifndef DEFINE_GUID + #ifndef INITGUID + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID /*FAR*/ name + #else + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + #endif // INITGUID + #endif // DEFINE_GUID + + /* + * EAX OpenAL Extensions + */ + typedef ALenum (*EAXSet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALenum (*EAXGet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALboolean (*EAXSetBufferMode)(ALsizei, ALuint*, ALint); + typedef ALenum (*EAXGetBufferMode)(ALuint, ALint*); +#endif + +#pragma pack(push, 4) + + + + +//////////////////////////////////////////////////////////////////////////// +// Constants + +#define EAX_MAX_FXSLOTS 4 +#define EAX_MAX_ACTIVE_FXSLOTS 4 + +// The EAX_NULL_GUID is used by EAXFXSLOT_LOADEFFECT, EAXCONTEXT_PRIMARYFXSLOTID +// and EAXSOURCE_ACTIVEFXSLOTID + +// {00000000-0000-0000-0000-000000000000} +DEFINE_GUID(EAX_NULL_GUID, + 0x00000000, + 0x0000, + 0x0000, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + +// The EAX_PrimaryFXSlotID GUID is used by EAXSOURCE_ACTIVEFXSLOTID +// {F317866D-924C-450C-861B-E6DAA25E7C20} +DEFINE_GUID(EAX_PrimaryFXSlotID, + 0xf317866d, + 0x924c, + 0x450c, + 0x86, 0x1b, 0xe6, 0xda, 0xa2, 0x5e, 0x7c, 0x20); + + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Structures + +// Use this structure for EAXCONTEXT_EAXSESSION property +#ifndef EAXSESSIONPROPERTIES_DEFINED +#define EAXSESSIONPROPERTIES_DEFINED +typedef struct _EAXSESSIONPROPERTIES +{ + unsigned long ulEAXVersion; + unsigned long ulMaxActiveSends; +} EAXSESSIONPROPERTIES, *LPEAXSESSIONPROPERTIES; +#endif + +// Use this structure for EAXCONTEXT_ALL property. +#ifndef EAXCONTEXTPROPERTIES_DEFINED +#define EAXCONTEXTPROPERTIES_DEFINED +typedef struct _EAXCONTEXTPROPERTIES +{ + GUID guidPrimaryFXSlotID; + float flDistanceFactor; + float flAirAbsorptionHF; + float flHFReference; + float flMacroFXFactor; +} EAXCONTEXTPROPERTIES, *LPEAXCONTEXTPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXSOURCEFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +#ifndef EAXSOURCEPROPERTIES_DEFINED +#define EAXSOURCEPROPERTIES_DEFINED +typedef struct _EAXSOURCEPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties + float flMacroFXFactor; //###TODO### add comment here +} EAXSOURCEPROPERTIES, *LPEAXSOURCEPROPERTIES; +#endif + + +// Use this structure for EAXSOURCE_ALL2DPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXSOURCEFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +#ifndef EAXSOURCE2DPROPERTIES_DEFINED +#define EAXSOURCE2DPROPERTIES_DEFINED +typedef struct _EAXSOURCE2DPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + unsigned long ulFlags; // modifies the behavior of properties +} EAXSOURCE2DPROPERTIES, *LPEAXSOURCE2DPROPERTIES; +#endif + + +// Use this structure for EAXSOURCE_ALLSENDPARAMETERS +// - all levels are hundredths of decibels +// +#ifndef EAXSOURCEALLSENDPROPERTIES_DEFINED +#define EAXSOURCEALLSENDPROPERTIES_DEFINED +typedef struct _EAXSOURCEALLSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; // send level (at low and mid frequencies) + long lSendHF; // relative send level at high frequencies + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; + long lExclusion; + float flExclusionLFRatio; +} EAXSOURCEALLSENDPROPERTIES, *LPEAXSOURCEALLSENDPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_SPEAKERLEVELS +// - level is in hundredths of decibels +// +#ifndef EAXSPEAKERLEVELPROPERTIES_DEFINED +#define EAXSPEAKERLEVELPROPERTIES_DEFINED +typedef struct _EAXSPEAKERLEVELPROPERTIES +{ + long lSpeakerID; + long lLevel; +} EAXSPEAKERLEVELPROPERTIES, *LPEAXSPEAKERLEVELPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_ACTIVEFXSLOTID +#ifndef EAXACTIVEFXSLOTS_DEFINED +#define EAXACTIVEFXSLOTS_DEFINED +typedef struct _EAXACTIVEFXSLOTS +{ + GUID guidActiveFXSlots[EAX_MAX_ACTIVE_FXSLOTS]; +} EAXACTIVEFXSLOTS, *LPEAXACTIVEFXSLOTS; +#endif + +// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. +#ifndef EAXOBSTRUCTIONPROPERTIES_DEFINED +#define EAXOBSTRUCTIONPROPERTIES_DEFINED +typedef struct _EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +} EAXOBSTRUCTIONPROPERTIES, *LPEAXOBSTRUCTIONPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. +#ifndef EAXOCCLUSIONPROPERTIES_DEFINED +#define EAXOCCLUSIONPROPERTIES_DEFINED +typedef struct _EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXOCCLUSIONPROPERTIES, *LPEAXOCCLUSIONPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. +#ifndef EAXEXCLUSIONPROPERTIES_DEFINED +#define EAXEXCLUSIONPROPERTIES_DEFINED +typedef struct _EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +} EAXEXCLUSIONPROPERTIES, *LPEAXEXCLUSIONPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_SENDPARAMETERS properties. +#ifndef EAXSOURCESENDPROPERTIES_DEFINED +#define EAXSOURCESENDPROPERTIES_DEFINED +typedef struct _EAXSOURCESENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; + long lSendHF; +} EAXSOURCESENDPROPERTIES, *LPEAXSOURCESENDPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS +#ifndef EAXSOURCEOCCLUSIONSENDPROPERTIES_DEFINED +#define EAXSOURCEOCCLUSIONSENDPROPERTIES_DEFINED +typedef struct _EAXSOURCEOCCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXSOURCEOCCLUSIONSENDPROPERTIES, *LPEAXSOURCEOCCLUSIONSENDPROPERTIES; +#endif + +// Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS +#ifndef EAXSOURCEEXCLUSIONSENDPROPERTIES_DEFINED +#define EAXSOURCEEXCLUSIONSENDPROPERTIES_DEFINED +typedef struct _EAXSOURCEEXCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lExclusion; + float flExclusionLFRatio; +} EAXSOURCEEXCLUSIONSENDPROPERTIES, *LPEAXSOURCEEXCLUSIONSENDPROPERTIES; +#endif + +// Use this structure for EAXFXSLOT_ALLPARAMETERS +// - all levels are hundredths of decibels +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myFXSlot.guidLoadEffect = EAX_REVERB_EFFECT; +// myFXSlot.lVolume = 0; +// myFXSlot.lLock = 1; +// myFXSlot.ulFlags = myFlags /* see EAXFXSLOTFLAGS below */ ; +// instead of: +// myFXSlot = { EAX_REVERB_EFFECT, 0, 1, 0x00000001 }; +// +#ifndef EAXFXSLOTPROPERTIES_DEFINED +#define EAXFXSLOTPROPERTIES_DEFINED +typedef struct _EAXFXSLOTPROPERTIES +{ + GUID guidLoadEffect; + long lVolume; + long lLock; + unsigned long ulFlags; + long lOcclusion; + float flOcclusionLFRatio; +} EAXFXSLOTPROPERTIES, *LPEAXFXSLOTPROPERTIES; +#endif + + +// Use this structure for EAXREVERB_REFLECTIONSPAN and EAXREVERB_REVERBPAN properties. +#ifndef EAXVECTOR_DEFINED +#define EAXVECTOR_DEFINED +typedef struct _EAXVECTOR { + float x; + float y; + float z; +} EAXVECTOR; +#endif + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Error Codes + +#define EAX_OK 0 +#define EAXERR_INVALID_OPERATION (-1) +#define EAXERR_INVALID_VALUE (-2) +#define EAXERR_NO_EFFECT_LOADED (-3) +#define EAXERR_UNKNOWN_EFFECT (-4) +#define EAXERR_INCOMPATIBLE_SOURCE_TYPE (-5) +#define EAXERR_INCOMPATIBLE_EAX_VERSION (-6) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Context Object + +// {57E13437-B932-4ab2-B8BD-5266C1A887EE} +DEFINE_GUID(EAXPROPERTYID_EAX50_Context, + 0x57e13437, + 0xb932, + 0x4ab2, + 0xb8, 0xbd, 0x52, 0x66, 0xc1, 0xa8, 0x87, 0xee); + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_Context EAXPROPERTYID_EAX50_Context + +typedef enum +{ + EAXCONTEXT_NONE = 0, + EAXCONTEXT_ALLPARAMETERS, + EAXCONTEXT_PRIMARYFXSLOTID, + EAXCONTEXT_DISTANCEFACTOR, + EAXCONTEXT_AIRABSORPTIONHF, + EAXCONTEXT_HFREFERENCE, + EAXCONTEXT_LASTERROR, + EAXCONTEXT_SPEAKERCONFIG, + EAXCONTEXT_EAXSESSION, + EAXCONTEXT_MACROFXFACTOR +} EAXCONTEXT_PROPERTY; + +// OR these flags with property id +#define EAXCONTEXT_PARAMETER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXCONTEXT_PARAMETER_DEFER 0x80000000 // changes take effect later +#define EAXCONTEXT_PARAMETER_COMMITDEFERREDSETTINGS (EAXCONTEXT_NONE | \ + EAXCONTEXT_PARAMETER_IMMEDIATE) + +// EAX Context property ranges and defaults: +#define EAXCONTEXT_DEFAULTPRIMARYFXSLOTID EAXPROPERTYID_EAX50_FXSlot0 + +#define EAXCONTEXT_MINDISTANCEFACTOR FLT_MIN //minimum positive value +#define EAXCONTEXT_MAXDISTANCEFACTOR FLT_MAX +#define EAXCONTEXT_DEFAULTDISTANCEFACTOR 1.0f + +#define EAXCONTEXT_MINAIRABSORPTIONHF (-100.0f) +#define EAXCONTEXT_MAXAIRABSORPTIONHF 0.0f +#define EAXCONTEXT_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXCONTEXT_MINHFREFERENCE 1000.0f +#define EAXCONTEXT_MAXHFREFERENCE 20000.0f +#define EAXCONTEXT_DEFAULTHFREFERENCE 5000.0f + +#define EAXCONTEXT_DEFAULTLASTERROR EAX_OK + +enum { + HEADPHONES = 0, + SPEAKERS_2, + SPEAKERS_4, + SPEAKERS_5, // 5.1 speakers + SPEAKERS_6, // 6.1 speakers + SPEAKERS_7, // 7.1 speakers +}; + +enum { + EAX_40 = 5, // EAX 4.0 + EAX_50 = 6, // EAX 5.0 +}; + +// min,max, default values for ulEAXVersion in struct EAXSESSIONPROPERTIES +#define EAXCONTEXT_MINEAXSESSION EAX_40 +#define EAXCONTEXT_MAXEAXSESSION EAX_50 +#define EAXCONTEXT_DEFAULTEAXSESSION EAX_40 + +// min,max, default values for ulMaxActiveSends in struct EAXSESSIONPROPERTIES +#define EAXCONTEXT_MINMAXACTIVESENDS 2 +#define EAXCONTEXT_MAXMAXACTIVESENDS 4 +#define EAXCONTEXT_DEFAULTMAXACTIVESENDS 2 + +#define EAXCONTEXT_MINMACROFXFACTOR 0.0f +#define EAXCONTEXT_MAXMACROFXFACTOR 1.0f +#define EAXCONTEXT_DEFAULTMACROFXFACTOR 0.0f + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Effect Slot Objects + +// {91F9590F-C388-407a-84B0-1BAE0EF71ABC} +DEFINE_GUID(EAXPROPERTYID_EAX50_FXSlot0, + 0x91f9590f, + 0xc388, + 0x407a, + 0x84, 0xb0, 0x1b, 0xae, 0xe, 0xf7, 0x1a, 0xbc); + +// {8F5F7ACA-9608-4965-8137-8213C7B9D9DE} +DEFINE_GUID(EAXPROPERTYID_EAX50_FXSlot1, + 0x8f5f7aca, + 0x9608, + 0x4965, + 0x81, 0x37, 0x82, 0x13, 0xc7, 0xb9, 0xd9, 0xde); + +// {3C0F5252-9834-46f0-A1D8-5B95C4A00A30} +DEFINE_GUID(EAXPROPERTYID_EAX50_FXSlot2, + 0x3c0f5252, + 0x9834, + 0x46f0, + 0xa1, 0xd8, 0x5b, 0x95, 0xc4, 0xa0, 0xa, 0x30); + +// {E2EB0EAA-E806-45e7-9F86-06C1571A6FA3} +DEFINE_GUID(EAXPROPERTYID_EAX50_FXSlot3, + 0xe2eb0eaa, + 0xe806, + 0x45e7, + 0x9f, 0x86, 0x6, 0xc1, 0x57, 0x1a, 0x6f, 0xa3); + + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_FXSlot0 EAXPROPERTYID_EAX50_FXSlot0 +#define EAXPROPERTYID_EAX_FXSlot1 EAXPROPERTYID_EAX50_FXSlot1 +#define EAXPROPERTYID_EAX_FXSlot2 EAXPROPERTYID_EAX50_FXSlot2 +#define EAXPROPERTYID_EAX_FXSlot3 EAXPROPERTYID_EAX50_FXSlot3 + +// FXSlot object properties +typedef enum +{ + EAXFXSLOT_PARAMETER = 0, // range 0-0x40 reserved for loaded effect parameters + EAXFXSLOT_NONE = 0x10000, + EAXFXSLOT_ALLPARAMETERS, + EAXFXSLOT_LOADEFFECT, + EAXFXSLOT_VOLUME, + EAXFXSLOT_LOCK, + EAXFXSLOT_FLAGS, + EAXFXSLOT_OCCLUSION, + EAXFXSLOT_OCCLUSIONLFRATIO +} EAXFXSLOT_PROPERTY; + +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXFXSLOTFLAGS_ENVIRONMENT; +// instead of: +// myFlags = 0x00000001; +// +#define EAXFXSLOTFLAGS_ENVIRONMENT 0x00000001 +#define EAXFXSLOTFLAGS_UPMIX 0x00000002 +#define EAXFXSLOTFLAGS_RESERVED 0xFFFFFFFC // reserved future use + +// EAX Effect Slot property ranges and defaults: +#define EAXFXSLOT_MINVOLUME (-10000) +#define EAXFXSLOT_MAXVOLUME 0 +#define EAXFXSLOT_DEFAULTVOLUME 0 + +enum +{ + EAXFXSLOT_UNLOCKED = 0, + EAXFXSLOT_LOCKED = 1 +}; + +#define EAXFXSLOT_MINLOCK 0 +#define EAXFXSLOT_MAXLOCK 1 + +#define EAXFXSLOT_MINOCCLUSION (-10000) +#define EAXFXSLOT_MAXOCCLUSION 0 +#define EAXFXSLOT_DEFAULTOCCLUSION 0 + +#define EAXFXSLOT_MINOCCLUSIONLFRATIO 0.0f +#define EAXFXSLOT_MAXOCCLUSIONLFRATIO 1.0f +#define EAXFXSLOT_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXFXSLOT_DEFAULTFLAGS (EAXFXSLOTFLAGS_ENVIRONMENT | \ + EAXFXSLOTFLAGS_UPMIX ) // ignored for reverb +//////////////////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////////////////// +// Source Object + +// {5EDF82F0-24A7-4f38-8E64-2F09CA05DEE1} +DEFINE_GUID(EAXPROPERTYID_EAX50_Source, + 0x5edf82f0, + 0x24a7, + 0x4f38, + 0x8e, 0x64, 0x2f, 0x9, 0xca, 0x5, 0xde, 0xe1); + + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_Source EAXPROPERTYID_EAX50_Source + +// Source object properties +typedef enum +{ + EAXSOURCE_NONE, + EAXSOURCE_ALLPARAMETERS, + EAXSOURCE_OBSTRUCTIONPARAMETERS, + EAXSOURCE_OCCLUSIONPARAMETERS, + EAXSOURCE_EXCLUSIONPARAMETERS, + EAXSOURCE_DIRECT, + EAXSOURCE_DIRECTHF, + EAXSOURCE_ROOM, + EAXSOURCE_ROOMHF, + EAXSOURCE_OBSTRUCTION, + EAXSOURCE_OBSTRUCTIONLFRATIO, + EAXSOURCE_OCCLUSION, + EAXSOURCE_OCCLUSIONLFRATIO, + EAXSOURCE_OCCLUSIONROOMRATIO, + EAXSOURCE_OCCLUSIONDIRECTRATIO, + EAXSOURCE_EXCLUSION, + EAXSOURCE_EXCLUSIONLFRATIO, + EAXSOURCE_OUTSIDEVOLUMEHF, + EAXSOURCE_DOPPLERFACTOR, + EAXSOURCE_ROLLOFFFACTOR, + EAXSOURCE_ROOMROLLOFFFACTOR, + EAXSOURCE_AIRABSORPTIONFACTOR, + EAXSOURCE_FLAGS, + EAXSOURCE_SENDPARAMETERS, + EAXSOURCE_ALLSENDPARAMETERS, + EAXSOURCE_OCCLUSIONSENDPARAMETERS, + EAXSOURCE_EXCLUSIONSENDPARAMETERS, + EAXSOURCE_ACTIVEFXSLOTID, + EAXSOURCE_MACROFXFACTOR, + EAXSOURCE_SPEAKERLEVELS, + EAXSOURCE_ALL2DPARAMETERS, +} EAXSOURCE_PROPERTY; + +// OR these flags with property id +#define EAXSOURCE_PARAMETER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXSOURCE_PARAMETER_DEFERRED 0x80000000 // changes take effect later +#define EAXSOURCE_PARAMETER_COMMITDEFERREDSETTINGS (EAXSOURCE_NONE | \ + EAXSOURCE_PARAMETER_IMMEDIATE) +// Used by EAXSOURCE_FLAGS for EAXSOURCEFLAGS_xxxAUTO +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXSOURCE_DIRECTHFAUTO | EAXSOURCE_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAXSOURCEFLAGS_DIRECTHFAUTO 0x00000001 // relates to EAXSOURCE_DIRECTHF +#define EAXSOURCEFLAGS_ROOMAUTO 0x00000002 // relates to EAXSOURCE_ROOM +#define EAXSOURCEFLAGS_ROOMHFAUTO 0x00000004 // relates to EAXSOURCE_ROOMHF +#define EAXSOURCEFLAGS_3DELEVATIONFILTER 0x00000008 +#define EAXSOURCEFLAGS_UPMIX 0x00000010 +#define EAXSOURCEFLAGS_APPLYSPEAKERLEVELS 0x00000020 +#define EAXSOURCEFLAGS_RESERVED 0xFFFFFFC0 // reserved future use + +// EAX Source property ranges and defaults: +#define EAXSOURCE_MINSEND (-10000) +#define EAXSOURCE_MAXSEND 0 +#define EAXSOURCE_DEFAULTSEND 0 + +#define EAXSOURCE_MINSENDHF (-10000) +#define EAXSOURCE_MAXSENDHF 0 +#define EAXSOURCE_DEFAULTSENDHF 0 + +#define EAXSOURCE_MINDIRECT (-10000) +#define EAXSOURCE_MAXDIRECT 1000 +#define EAXSOURCE_DEFAULTDIRECT 0 + +#define EAXSOURCE_MINDIRECTHF (-10000) +#define EAXSOURCE_MAXDIRECTHF 0 +#define EAXSOURCE_DEFAULTDIRECTHF 0 + +#define EAXSOURCE_MINROOM (-10000) +#define EAXSOURCE_MAXROOM 1000 +#define EAXSOURCE_DEFAULTROOM 0 + +#define EAXSOURCE_MINROOMHF (-10000) +#define EAXSOURCE_MAXROOMHF 0 +#define EAXSOURCE_DEFAULTROOMHF 0 + +#define EAXSOURCE_MINOBSTRUCTION (-10000) +#define EAXSOURCE_MAXOBSTRUCTION 0 +#define EAXSOURCE_DEFAULTOBSTRUCTION 0 + +#define EAXSOURCE_MINOBSTRUCTIONLFRATIO 0.0f +#define EAXSOURCE_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAXSOURCE_MINOCCLUSION (-10000) +#define EAXSOURCE_MAXOCCLUSION 0 +#define EAXSOURCE_DEFAULTOCCLUSION 0 + +#define EAXSOURCE_MINOCCLUSIONLFRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXSOURCE_MINOCCLUSIONROOMRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONROOMRATIO 10.0f +#define EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO 1.5f + +#define EAXSOURCE_MINOCCLUSIONDIRECTRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONDIRECTRATIO 10.0f +#define EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO 1.0f + +#define EAXSOURCE_MINEXCLUSION (-10000) +#define EAXSOURCE_MAXEXCLUSION 0 +#define EAXSOURCE_DEFAULTEXCLUSION 0 + +#define EAXSOURCE_MINEXCLUSIONLFRATIO 0.0f +#define EAXSOURCE_MAXEXCLUSIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTEXCLUSIONLFRATIO 1.0f + +#define EAXSOURCE_MINOUTSIDEVOLUMEHF (-10000) +#define EAXSOURCE_MAXOUTSIDEVOLUMEHF 0 +#define EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAXSOURCE_MINDOPPLERFACTOR 0.0f +#define EAXSOURCE_MAXDOPPLERFACTOR 10.f +#define EAXSOURCE_DEFAULTDOPPLERFACTOR 1.0f + +#define EAXSOURCE_MINROLLOFFFACTOR 0.0f +#define EAXSOURCE_MAXROLLOFFFACTOR 10.f +#define EAXSOURCE_DEFAULTROLLOFFFACTOR 0.0f + +#define EAXSOURCE_MINROOMROLLOFFFACTOR 0.0f +#define EAXSOURCE_MAXROOMROLLOFFFACTOR 10.f +#define EAXSOURCE_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXSOURCE_MINAIRABSORPTIONFACTOR 0.0f +#define EAXSOURCE_MAXAIRABSORPTIONFACTOR 10.0f +#define EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR 0.0f + +#define EAXSOURCE_MINMACROFXFACTOR 0.0f +#define EAXSOURCE_MAXMACROFXFACTOR 1.0f +#define EAXSOURCE_DEFAULTMACROFXFACTOR 1.0f + +#define EAXSOURCE_MINSPEAKERLEVEL (-10000) +#define EAXSOURCE_MAXSPEAKERLEVEL 0 +#define EAXSOURCE_DEFAULTSPEAKERLEVEL (-10000) + +enum +{ + EAXSPEAKER_FRONT_LEFT = 1, + EAXSPEAKER_FRONT_CENTER = 2, + EAXSPEAKER_FRONT_RIGHT = 3, + EAXSPEAKER_SIDE_RIGHT = 4, + EAXSPEAKER_REAR_RIGHT = 5, + EAXSPEAKER_REAR_CENTER = 6, + EAXSPEAKER_REAR_LEFT = 7, + EAXSPEAKER_SIDE_LEFT = 8, + EAXSPEAKER_LOW_FREQUENCY = 9 +}; + + +// EAXSOURCEFLAGS_DIRECTHFAUTO, EAXSOURCEFLAGS_ROOMAUTO and EAXSOURCEFLAGS_ROOMHFAUTO are ignored for 2D sources +// EAXSOURCEFLAGS_UPMIX is ignored for 3D sources +#define EAXSOURCE_DEFAULTFLAGS (EAXSOURCEFLAGS_DIRECTHFAUTO | \ + EAXSOURCEFLAGS_ROOMAUTO | \ + EAXSOURCEFLAGS_ROOMHFAUTO | \ + EAXSOURCEFLAGS_UPMIX ) + + +// A 3D Source's default ACTIVEFXSLOTID is { EAX_NULL_GUID, EAX_PrimaryFXSlotID, EAX_NULL_GUID, EAX_NULL_GUID } +#define EAXSOURCE_3DDEFAULTACTIVEFXSLOTID {{ EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] }, \ + { EAX_PrimaryFXSlotID.Data1, EAX_PrimaryFXSlotID.Data2, \ + EAX_PrimaryFXSlotID.Data3, EAX_PrimaryFXSlotID.Data4[0], \ + EAX_PrimaryFXSlotID.Data4[1],EAX_PrimaryFXSlotID.Data4[2], \ + EAX_PrimaryFXSlotID.Data4[3],EAX_PrimaryFXSlotID.Data4[4], \ + EAX_PrimaryFXSlotID.Data4[5],EAX_PrimaryFXSlotID.Data4[6], \ + EAX_PrimaryFXSlotID.Data4[7] }, \ + { EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] }, \ + { EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] } } + +// A 2D Source's default ACTIVEFXSLOTID is { EAX_NULL_GUID, EAX_NULL_GUID, EAX_NULL_GUID, EAX_NULL_GUID } +#define EAXSOURCE_2DDEFAULTACTIVEFXSLOTID {{ EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] }, \ + { EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] }, \ + { EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] }, \ + { EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] } } + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Reverb Effect + +// EAX REVERB {0CF95C8F-A3CC-4849-B0B6-832ECC1822DF} +DEFINE_GUID(EAX_REVERB_EFFECT, + 0xcf95c8f, + 0xa3cc, + 0x4849, + 0xb0, 0xb6, 0x83, 0x2e, 0xcc, 0x18, 0x22, 0xdf); + +// Reverb effect properties +typedef enum +{ + EAXREVERB_NONE, + EAXREVERB_ALLPARAMETERS, + EAXREVERB_ENVIRONMENT, + EAXREVERB_ENVIRONMENTSIZE, + EAXREVERB_ENVIRONMENTDIFFUSION, + EAXREVERB_ROOM, + EAXREVERB_ROOMHF, + EAXREVERB_ROOMLF, + EAXREVERB_DECAYTIME, + EAXREVERB_DECAYHFRATIO, + EAXREVERB_DECAYLFRATIO, + EAXREVERB_REFLECTIONS, + EAXREVERB_REFLECTIONSDELAY, + EAXREVERB_REFLECTIONSPAN, + EAXREVERB_REVERB, + EAXREVERB_REVERBDELAY, + EAXREVERB_REVERBPAN, + EAXREVERB_ECHOTIME, + EAXREVERB_ECHODEPTH, + EAXREVERB_MODULATIONTIME, + EAXREVERB_MODULATIONDEPTH, + EAXREVERB_AIRABSORPTIONHF, + EAXREVERB_HFREFERENCE, + EAXREVERB_LFREFERENCE, + EAXREVERB_ROOMROLLOFFFACTOR, + EAXREVERB_FLAGS, +} EAXREVERB_PROPERTY; + +// OR these flags with property id +#define EAXREVERB_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXREVERB_DEFERRED 0x80000000 // changes take effect later +#define EAXREVERB_COMMITDEFERREDSETTINGS (EAXREVERB_NONE | \ + EAXREVERB_IMMEDIATE) + +// used by EAXREVERB_ENVIRONMENT +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_UNDEFINED, + + EAX_ENVIRONMENT_COUNT +}; + +// Used by EAXREVERB_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXREVERBFLAGS_DECAYTIMESCALE | EAXREVERBFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAXREVERBFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAXREVERBFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAXREVERBFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAXREVERBFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAXREVERBFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time +#define EAXREVERBFLAGS_ECHOTIMESCALE 0x00000040 // echo time +#define EAXREVERBFLAGS_MODULATIONTIMESCALE 0x00000080 // modulation time +// This flag limits high-frequency decay time according to air absorption. +#define EAXREVERBFLAGS_DECAYHFLIMIT 0x00000020 +#define EAXREVERBFLAGS_RESERVED 0xFFFFFF00 // reserved future use + +// Use this structure for EAXREVERB_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times and delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myReverb.lRoom = -1000; +// myReverb.lRoomHF = -100; +// ... +// myReverb.dwFlags = myFlags /* see EAXREVERBFLAGS below */ ; +// instead of: +// myReverb = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +#ifndef EAXREVERBPROPERTIES_DEFINED +#define EAXREVERBPROPERTIES_DEFINED +typedef struct _EAXREVERBPROPERTIES +{ + unsigned long ulEnvironment; // sets all reverb properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +} EAXREVERBPROPERTIES, *LPEAXREVERBPROPERTIES; +#endif + +// Property ranges and defaults: +#define EAXREVERB_MINENVIRONMENT 0 +#define EAXREVERB_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAXREVERB_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAXREVERB_MINENVIRONMENTSIZE 1.0f +#define EAXREVERB_MAXENVIRONMENTSIZE 100.0f +#define EAXREVERB_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAXREVERB_MINENVIRONMENTDIFFUSION 0.0f +#define EAXREVERB_MAXENVIRONMENTDIFFUSION 1.0f +#define EAXREVERB_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAXREVERB_MINROOM (-10000) +#define EAXREVERB_MAXROOM 0 +#define EAXREVERB_DEFAULTROOM (-1000) + +#define EAXREVERB_MINROOMHF (-10000) +#define EAXREVERB_MAXROOMHF 0 +#define EAXREVERB_DEFAULTROOMHF (-100) + +#define EAXREVERB_MINROOMLF (-10000) +#define EAXREVERB_MAXROOMLF 0 +#define EAXREVERB_DEFAULTROOMLF 0 + +#define EAXREVERB_MINDECAYTIME 0.1f +#define EAXREVERB_MAXDECAYTIME 20.0f +#define EAXREVERB_DEFAULTDECAYTIME 1.49f + +#define EAXREVERB_MINDECAYHFRATIO 0.1f +#define EAXREVERB_MAXDECAYHFRATIO 2.0f +#define EAXREVERB_DEFAULTDECAYHFRATIO 0.83f + +#define EAXREVERB_MINDECAYLFRATIO 0.1f +#define EAXREVERB_MAXDECAYLFRATIO 2.0f +#define EAXREVERB_DEFAULTDECAYLFRATIO 1.00f + +#define EAXREVERB_MINREFLECTIONS (-10000) +#define EAXREVERB_MAXREFLECTIONS 1000 +#define EAXREVERB_DEFAULTREFLECTIONS (-2602) + +#define EAXREVERB_MINREFLECTIONSDELAY 0.0f +#define EAXREVERB_MAXREFLECTIONSDELAY 0.3f +#define EAXREVERB_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAXREVERB_DEFAULTREFLECTIONSPAN {0.0f, 0.0f, 0.0f} + +#define EAXREVERB_MINREVERB (-10000) +#define EAXREVERB_MAXREVERB 2000 +#define EAXREVERB_DEFAULTREVERB 200 + +#define EAXREVERB_MINREVERBDELAY 0.0f +#define EAXREVERB_MAXREVERBDELAY 0.1f +#define EAXREVERB_DEFAULTREVERBDELAY 0.011f + +#define EAXREVERB_DEFAULTREVERBPAN {0.0f, 0.0f, 0.0f} + +#define EAXREVERB_MINECHOTIME 0.075f +#define EAXREVERB_MAXECHOTIME 0.25f +#define EAXREVERB_DEFAULTECHOTIME 0.25f + +#define EAXREVERB_MINECHODEPTH 0.0f +#define EAXREVERB_MAXECHODEPTH 1.0f +#define EAXREVERB_DEFAULTECHODEPTH 0.0f + +#define EAXREVERB_MINMODULATIONTIME 0.04f +#define EAXREVERB_MAXMODULATIONTIME 4.0f +#define EAXREVERB_DEFAULTMODULATIONTIME 0.25f + +#define EAXREVERB_MINMODULATIONDEPTH 0.0f +#define EAXREVERB_MAXMODULATIONDEPTH 1.0f +#define EAXREVERB_DEFAULTMODULATIONDEPTH 0.0f + +#define EAXREVERB_MINAIRABSORPTIONHF (-100.0f) +#define EAXREVERB_MAXAIRABSORPTIONHF 0.0f +#define EAXREVERB_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXREVERB_MINHFREFERENCE 1000.0f +#define EAXREVERB_MAXHFREFERENCE 20000.0f +#define EAXREVERB_DEFAULTHFREFERENCE 5000.0f + +#define EAXREVERB_MINLFREFERENCE 20.0f +#define EAXREVERB_MAXLFREFERENCE 1000.0f +#define EAXREVERB_DEFAULTLFREFERENCE 250.0f + +#define EAXREVERB_MINROOMROLLOFFFACTOR 0.0f +#define EAXREVERB_MAXROOMROLLOFFFACTOR 10.0f +#define EAXREVERB_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXREVERB_DEFAULTFLAGS (EAXREVERBFLAGS_DECAYTIMESCALE | \ + EAXREVERBFLAGS_REFLECTIONSSCALE | \ + EAXREVERBFLAGS_REFLECTIONSDELAYSCALE | \ + EAXREVERBFLAGS_REVERBSCALE | \ + EAXREVERBFLAGS_REVERBDELAYSCALE | \ + EAXREVERBFLAGS_DECAYHFLIMIT) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// + +// New Effect Types + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// AGC Compressor Effect + +// EAX AGC COMPRESSOR {BFB7A01E-7825-4039-927F-3AABDA0C560} + +DEFINE_GUID(EAX_AGCCOMPRESSOR_EFFECT, + 0xbfb7a01e, + 0x7825, + 0x4039, + 0x92, 0x7f, 0x3, 0xaa, 0xbd, 0xa0, 0xc5, 0x60); + +// AGC Compressor properties +typedef enum +{ + EAXAGCCOMPRESSOR_NONE, + EAXAGCCOMPRESSOR_ALLPARAMETERS, + EAXAGCCOMPRESSOR_ONOFF +} EAXAGCCOMPRESSOR_PROPERTY; + +// OR these flags with property id +#define EAXAGCCOMPRESSOR_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXAGCCOMPRESSOR_DEFERRED 0x80000000 // changes take effect later +#define EAXAGCCOMPRESSOR_COMMITDEFERREDSETTINGS (EAXAGCCOMPRESSOR_NONE | \ + EAXAGCCOMPRESSOR_IMMEDIATE) + +// Use this structure for EAXAGCCOMPRESSOR_ALLPARAMETERS +#ifndef EAXAGCCOMPRESSORPROPERTIES_DEFINED +#define EAXAGCCOMPRESSORPROPERTIES_DEFINED +typedef struct _EAXAGCCOMPRESSORPROPERTIES +{ + unsigned long ulOnOff; // Switch Compressor on or off +} EAXAGCCOMPRESSORPROPERTIES, *LPEAXAGCCOMPRESSORPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXAGCCOMPRESSOR_MINONOFF 0 +#define EAXAGCCOMPRESSOR_MAXONOFF 1 +#define EAXAGCCOMPRESSOR_DEFAULTONOFF 1 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Autowah Effect + +// EAX AUTOWAH {EC3130C0-AC7A-11D2-88DD-A024D13CE1} +DEFINE_GUID(EAX_AUTOWAH_EFFECT, + 0xec3130c0, + 0xac7a, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Autowah properties +typedef enum +{ + EAXAUTOWAH_NONE, + EAXAUTOWAH_ALLPARAMETERS, + EAXAUTOWAH_ATTACKTIME, + EAXAUTOWAH_RELEASETIME, + EAXAUTOWAH_RESONANCE, + EAXAUTOWAH_PEAKLEVEL +} EAXAUTOWAH_PROPERTY; + +// OR these flags with property id +#define EAXAUTOWAH_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXAUTOWAH_DEFERRED 0x80000000 // changes take effect later +#define EAXAUTOWAH_COMMITDEFERREDSETTINGS (EAXAUTOWAH_NONE | \ + EAXAUTOWAH_IMMEDIATE) + +// Use this structure for EAXAUTOWAH_ALLPARAMETERS +#ifndef EAXAUTOWAHPROPERTIES_DEFINED +#define EAXAUTOWAHPROPERTIES_DEFINED +typedef struct _EAXAUTOWAHPROPERTIES +{ + float flAttackTime; // Attack time (seconds) + float flReleaseTime; // Release time (seconds) + long lResonance; // Resonance (mB) + long lPeakLevel; // Peak level (mB) +} EAXAUTOWAHPROPERTIES, *LPEAXAUTOWAHPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXAUTOWAH_MINATTACKTIME 0.0001f +#define EAXAUTOWAH_MAXATTACKTIME 1.0f +#define EAXAUTOWAH_DEFAULTATTACKTIME 0.06f + +#define EAXAUTOWAH_MINRELEASETIME 0.0001f +#define EAXAUTOWAH_MAXRELEASETIME 1.0f +#define EAXAUTOWAH_DEFAULTRELEASETIME 0.06f + +#define EAXAUTOWAH_MINRESONANCE 600 +#define EAXAUTOWAH_MAXRESONANCE 6000 +#define EAXAUTOWAH_DEFAULTRESONANCE 6000 + +#define EAXAUTOWAH_MINPEAKLEVEL (-9000) +#define EAXAUTOWAH_MAXPEAKLEVEL 9000 +#define EAXAUTOWAH_DEFAULTPEAKLEVEL 2100 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Chorus Effect + +// EAX CHORUS {DE6D6FE0-AC79-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_CHORUS_EFFECT, + 0xde6d6fe0, + 0xac79, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + + +// Chorus properties +typedef enum +{ + EAXCHORUS_NONE, + EAXCHORUS_ALLPARAMETERS, + EAXCHORUS_WAVEFORM, + EAXCHORUS_PHASE, + EAXCHORUS_RATE, + EAXCHORUS_DEPTH, + EAXCHORUS_FEEDBACK, + EAXCHORUS_DELAY +} EAXCHORUS_PROPERTY; + +// OR these flags with property id +#define EAXCHORUS_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXCHORUS_DEFERRED 0x80000000 // changes take effect later +#define EAXCHORUS_COMMITDEFERREDSETTINGS (EAXCHORUS_NONE | \ + EAXCHORUS_IMMEDIATE) + +// used by EAXCHORUS_WAVEFORM +enum +{ + EAX_CHORUS_SINUSOID, + EAX_CHORUS_TRIANGLE +}; + +// Use this structure for EAXCHORUS_ALLPARAMETERS +#ifndef EAXCHORUSPROPERTIES_DEFINED +#define EAXCHORUSPROPERTIES_DEFINED +typedef struct _EAXCHORUSPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (-1 to 1) + float flDelay; // Delay (seconds) +} EAXCHORUSPROPERTIES, *LPEAXCHORUSPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXCHORUS_MINWAVEFORM 0 +#define EAXCHORUS_MAXWAVEFORM 1 +#define EAXCHORUS_DEFAULTWAVEFORM 1 + +#define EAXCHORUS_MINPHASE (-180) +#define EAXCHORUS_MAXPHASE 180 +#define EAXCHORUS_DEFAULTPHASE 90 + +#define EAXCHORUS_MINRATE 0.0f +#define EAXCHORUS_MAXRATE 10.0f +#define EAXCHORUS_DEFAULTRATE 1.1f + +#define EAXCHORUS_MINDEPTH 0.0f +#define EAXCHORUS_MAXDEPTH 1.0f +#define EAXCHORUS_DEFAULTDEPTH 0.1f + +#define EAXCHORUS_MINFEEDBACK (-1.0f) +#define EAXCHORUS_MAXFEEDBACK 1.0f +#define EAXCHORUS_DEFAULTFEEDBACK 0.25f + +#define EAXCHORUS_MINDELAY 0.0002f +#define EAXCHORUS_MAXDELAY 0.016f +#define EAXCHORUS_DEFAULTDELAY 0.016f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Distortion Effect + +// EAX DISTORTION {975A4CE0-AC7E-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_DISTORTION_EFFECT, + 0x975a4ce0, + 0xac7e, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Distortion properties +typedef enum +{ + EAXDISTORTION_NONE, + EAXDISTORTION_ALLPARAMETERS, + EAXDISTORTION_EDGE, + EAXDISTORTION_GAIN, + EAXDISTORTION_LOWPASSCUTOFF, + EAXDISTORTION_EQCENTER, + EAXDISTORTION_EQBANDWIDTH +} EAXDISTORTION_PROPERTY; + +// OR these flags with property id +#define EAXDISTORTION_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXDISTORTION_DEFERRED 0x80000000 // changes take effect later +#define EAXDISTORTION_COMMITDEFERREDSETTINGS (EAXDISTORTION_NONE | \ + EAXDISTORTION_IMMEDIATE) + +// Use this structure for EAXDISTORTION_ALLPARAMETERS +#ifndef EAXDISTORTIONPROPERTIES_DEFINED +#define EAXDISTORTIONPROPERTIES_DEFINED +typedef struct _EAXDISTORTIONPROPERTIES +{ + float flEdge; // Controls the shape of the distortion (0 to 1) + long lGain; // Controls the post distortion gain (mB) + float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) + float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) + float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) +} EAXDISTORTIONPROPERTIES, *LPEAXDISTORTIONPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXDISTORTION_MINEDGE 0.0f +#define EAXDISTORTION_MAXEDGE 1.0f +#define EAXDISTORTION_DEFAULTEDGE 0.2f + +#define EAXDISTORTION_MINGAIN (-6000) +#define EAXDISTORTION_MAXGAIN 0 +#define EAXDISTORTION_DEFAULTGAIN (-2600) + +#define EAXDISTORTION_MINLOWPASSCUTOFF 80.0f +#define EAXDISTORTION_MAXLOWPASSCUTOFF 24000.0f +#define EAXDISTORTION_DEFAULTLOWPASSCUTOFF 8000.0f + +#define EAXDISTORTION_MINEQCENTER 80.0f +#define EAXDISTORTION_MAXEQCENTER 24000.0f +#define EAXDISTORTION_DEFAULTEQCENTER 3600.0f + +#define EAXDISTORTION_MINEQBANDWIDTH 80.0f +#define EAXDISTORTION_MAXEQBANDWIDTH 24000.0f +#define EAXDISTORTION_DEFAULTEQBANDWIDTH 3600.0f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Echo Effect + +// EAX ECHO {E9F1BC0-AC82-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_ECHO_EFFECT, + 0xe9f1bc0, + 0xac82, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Echo properties +typedef enum +{ + EAXECHO_NONE, + EAXECHO_ALLPARAMETERS, + EAXECHO_DELAY, + EAXECHO_LRDELAY, + EAXECHO_DAMPING, + EAXECHO_FEEDBACK, + EAXECHO_SPREAD +} EAXECHO_PROPERTY; + +// OR these flags with property id +#define EAXECHO_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXECHO_DEFERRED 0x80000000 // changes take effect later +#define EAXECHO_COMMITDEFERREDSETTINGS (EAXECHO_NONE | \ + EAXECHO_IMMEDIATE) + +// Use this structure for EAXECHO_ALLPARAMETERS +#ifndef EAXECHOPROPERTIES_DEFINED +#define EAXECHOPROPERTIES_DEFINED +typedef struct _EAXECHOPROPERTIES +{ + float flDelay; // Controls the initial delay time (seconds) + float flLRDelay; // Controls the delay time between the first and second taps (seconds) + float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) + float flFeedback; // Controls the duration of echo repetition (0 to 1) + float flSpread; // Controls the left-right spread of the echoes +} EAXECHOPROPERTIES, *LPEAXECHOPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXECHO_MINDAMPING 0.0f +#define EAXECHO_MAXDAMPING 0.99f +#define EAXECHO_DEFAULTDAMPING 0.5f + +#define EAXECHO_MINDELAY 0.002f +#define EAXECHO_MAXDELAY 0.207f +#define EAXECHO_DEFAULTDELAY 0.1f + +#define EAXECHO_MINLRDELAY 0.0f +#define EAXECHO_MAXLRDELAY 0.404f +#define EAXECHO_DEFAULTLRDELAY 0.1f + +#define EAXECHO_MINFEEDBACK 0.0f +#define EAXECHO_MAXFEEDBACK 1.0f +#define EAXECHO_DEFAULTFEEDBACK 0.5f + +#define EAXECHO_MINSPREAD (-1.0f) +#define EAXECHO_MAXSPREAD 1.0f +#define EAXECHO_DEFAULTSPREAD (-1.0f) + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Equalizer Effect + +// EAX EQUALIZER {65F94CE0-9793-11D3-939D-C0F02DD6F0} + +DEFINE_GUID(EAX_EQUALIZER_EFFECT, + 0x65f94ce0, + 0x9793, + 0x11d3, + 0x93, 0x9d, 0x0, 0xc0, 0xf0, 0x2d, 0xd6, 0xf0); + + +// Equalizer properties +typedef enum +{ + EAXEQUALIZER_NONE, + EAXEQUALIZER_ALLPARAMETERS, + EAXEQUALIZER_LOWGAIN, + EAXEQUALIZER_LOWCUTOFF, + EAXEQUALIZER_MID1GAIN, + EAXEQUALIZER_MID1CENTER, + EAXEQUALIZER_MID1WIDTH, + EAXEQUALIZER_MID2GAIN, + EAXEQUALIZER_MID2CENTER, + EAXEQUALIZER_MID2WIDTH, + EAXEQUALIZER_HIGHGAIN, + EAXEQUALIZER_HIGHCUTOFF +} EAXEQUALIZER_PROPERTY; + +// OR these flags with property id +#define EAXEQUALIZER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXEQUALIZER_DEFERRED 0x80000000 // changes take effect later +#define EAXEQUALIZER_COMMITDEFERREDSETTINGS (EAXEQUALIZER_NONE | \ + EAXEQUALIZER_IMMEDIATE) + +// Use this structure for EAXEQUALIZER_ALLPARAMETERS +#ifndef EAXEQUALIZERPROPERTIES_DEFINED +#define EAXEQUALIZERPROPERTIES_DEFINED +typedef struct _EAXEQUALIZERPROPERTIES +{ + long lLowGain; // (mB) + float flLowCutOff; // (Hz) + long lMid1Gain; // (mB) + float flMid1Center; // (Hz) + float flMid1Width; // (octaves) + long lMid2Gain; // (mB) + float flMid2Center; // (Hz) + float flMid2Width; // (octaves) + long lHighGain; // (mB) + float flHighCutOff; // (Hz) +} EAXEQUALIZERPROPERTIES, *LPEAXEQUALIZERPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXEQUALIZER_MINLOWGAIN (-1800) +#define EAXEQUALIZER_MAXLOWGAIN 1800 +#define EAXEQUALIZER_DEFAULTLOWGAIN 0 + +#define EAXEQUALIZER_MINLOWCUTOFF 50.0f +#define EAXEQUALIZER_MAXLOWCUTOFF 800.0f +#define EAXEQUALIZER_DEFAULTLOWCUTOFF 200.0f + +#define EAXEQUALIZER_MINMID1GAIN (-1800) +#define EAXEQUALIZER_MAXMID1GAIN 1800 +#define EAXEQUALIZER_DEFAULTMID1GAIN 0 + +#define EAXEQUALIZER_MINMID1CENTER 200.0f +#define EAXEQUALIZER_MAXMID1CENTER 3000.0f +#define EAXEQUALIZER_DEFAULTMID1CENTER 500.0f + +#define EAXEQUALIZER_MINMID1WIDTH 0.01f +#define EAXEQUALIZER_MAXMID1WIDTH 1.0f +#define EAXEQUALIZER_DEFAULTMID1WIDTH 1.0f + +#define EAXEQUALIZER_MINMID2GAIN (-1800) +#define EAXEQUALIZER_MAXMID2GAIN 1800 +#define EAXEQUALIZER_DEFAULTMID2GAIN 0 + +#define EAXEQUALIZER_MINMID2CENTER 1000.0f +#define EAXEQUALIZER_MAXMID2CENTER 8000.0f +#define EAXEQUALIZER_DEFAULTMID2CENTER 3000.0f + +#define EAXEQUALIZER_MINMID2WIDTH 0.01f +#define EAXEQUALIZER_MAXMID2WIDTH 1.0f +#define EAXEQUALIZER_DEFAULTMID2WIDTH 1.0f + +#define EAXEQUALIZER_MINHIGHGAIN (-1800) +#define EAXEQUALIZER_MAXHIGHGAIN 1800 +#define EAXEQUALIZER_DEFAULTHIGHGAIN 0 + +#define EAXEQUALIZER_MINHIGHCUTOFF 4000.0f +#define EAXEQUALIZER_MAXHIGHCUTOFF 16000.0f +#define EAXEQUALIZER_DEFAULTHIGHCUTOFF 6000.0f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Flanger Effect + +// EAX FLANGER {A70007C0-7D2-11D3-9B1E-A024D13CE1} + +DEFINE_GUID(EAX_FLANGER_EFFECT, + 0xa70007c0, + 0x7d2, + 0x11d3, + 0x9b, 0x1e, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Flanger properties +typedef enum +{ + EAXFLANGER_NONE, + EAXFLANGER_ALLPARAMETERS, + EAXFLANGER_WAVEFORM, + EAXFLANGER_PHASE, + EAXFLANGER_RATE, + EAXFLANGER_DEPTH, + EAXFLANGER_FEEDBACK, + EAXFLANGER_DELAY +} EAXFLANGER_PROPERTY; + +// OR these flags with property id +#define EAXFLANGER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXFLANGER_DEFERRED 0x80000000 // changes take effect later +#define EAXFLANGER_COMMITDEFERREDSETTINGS (EAXFLANGER_NONE | \ + EAXFLANGER_IMMEDIATE) + +// used by EAXFLANGER_WAVEFORM +enum +{ + EAX_FLANGER_SINUSOID, + EAX_FLANGER_TRIANGLE +}; + +// Use this structure for EAXFLANGER_ALLPARAMETERS +#ifndef EAXFLANGERPROPERTIES_DEFINED +#define EAXFLANGERPROPERTIES_DEFINED +typedef struct _EAXFLANGERPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (0 to 1) + float flDelay; // Delay (seconds) +} EAXFLANGERPROPERTIES, *LPEAXFLANGERPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXFLANGER_MINWAVEFORM 0 +#define EAXFLANGER_MAXWAVEFORM 1 +#define EAXFLANGER_DEFAULTWAVEFORM 1 + +#define EAXFLANGER_MINPHASE (-180) +#define EAXFLANGER_MAXPHASE 180 +#define EAXFLANGER_DEFAULTPHASE 0 + +#define EAXFLANGER_MINRATE 0.0f +#define EAXFLANGER_MAXRATE 10.0f +#define EAXFLANGER_DEFAULTRATE 0.27f + +#define EAXFLANGER_MINDEPTH 0.0f +#define EAXFLANGER_MAXDEPTH 1.0f +#define EAXFLANGER_DEFAULTDEPTH 1.0f + +#define EAXFLANGER_MINFEEDBACK (-1.0f) +#define EAXFLANGER_MAXFEEDBACK 1.0f +#define EAXFLANGER_DEFAULTFEEDBACK (-0.5f) + +#define EAXFLANGER_MINDELAY 0.0002f +#define EAXFLANGER_MAXDELAY 0.004f +#define EAXFLANGER_DEFAULTDELAY 0.002f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Frequency Shifter Effect + +// EAX FREQUENCY SHIFTER {DC3E1880-9212-11D3-939D-C0F02DD6F0} + +DEFINE_GUID(EAX_FREQUENCYSHIFTER_EFFECT, + 0xdc3e1880, + 0x9212, + 0x11d3, + 0x93, 0x9d, 0x0, 0xc0, 0xf0, 0x2d, 0xd6, 0xf0); + +// Frequency Shifter properties +typedef enum +{ + EAXFREQUENCYSHIFTER_NONE, + EAXFREQUENCYSHIFTER_ALLPARAMETERS, + EAXFREQUENCYSHIFTER_FREQUENCY, + EAXFREQUENCYSHIFTER_LEFTDIRECTION, + EAXFREQUENCYSHIFTER_RIGHTDIRECTION +} EAXFREQUENCYSHIFTER_PROPERTY; + +// OR these flags with property id +#define EAXFREQUENCYSHIFTER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXFREQUENCYSHIFTER_DEFERRED 0x80000000 // changes take effect later +#define EAXFREQUENCYSHIFTER_COMMITDEFERREDSETTINGS (EAXFREQUENCYSHIFTER_NONE | \ + EAXFREQUENCYSHIFTER_IMMEDIATE) + +// used by EAXFREQUENCYSHIFTER_LEFTDIRECTION and EAXFREQUENCYSHIFTER_RIGHTDIRECTION +enum +{ + EAX_FREQUENCYSHIFTER_DOWN, + EAX_FREQUENCYSHIFTER_UP, + EAX_FREQUENCYSHIFTER_OFF +}; + +// Use this structure for EAXFREQUENCYSHIFTER_ALLPARAMETERS +#ifndef EAXFREQUENCYSHIFTERPROPERTIES_DEFINED +#define EAXFREQUENCYSHIFTERPROPERTIES_DEFINED +typedef struct _EAXFREQUENCYSHIFTERPROPERTIES +{ + float flFrequency; // (Hz) + unsigned long ulLeftDirection; // see enum above + unsigned long ulRightDirection; // see enum above +} EAXFREQUENCYSHIFTERPROPERTIES, *LPEAXFREQUENCYSHIFTERPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXFREQUENCYSHIFTER_MINFREQUENCY 0.0f +#define EAXFREQUENCYSHIFTER_MAXFREQUENCY 24000.0f +#define EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY 0.0f + +#define EAXFREQUENCYSHIFTER_MINLEFTDIRECTION 0 +#define EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION 2 +#define EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION 0 + +#define EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION 0 +#define EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION 2 +#define EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION 0 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Vocal Morpher Effect + +// EAX VOCAL MORPHER {E41CF10C-3383-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_VOCALMORPHER_EFFECT, + 0xe41cf10c, + 0x3383, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Vocal Morpher properties +typedef enum +{ + EAXVOCALMORPHER_NONE, + EAXVOCALMORPHER_ALLPARAMETERS, + EAXVOCALMORPHER_PHONEMEA, + EAXVOCALMORPHER_PHONEMEACOARSETUNING, + EAXVOCALMORPHER_PHONEMEB, + EAXVOCALMORPHER_PHONEMEBCOARSETUNING, + EAXVOCALMORPHER_WAVEFORM, + EAXVOCALMORPHER_RATE +} EAXVOCALMORPHER_PROPERTY; + +// OR these flags with property id +#define EAXVOCALMORPHER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXVOCALMORPHER_DEFERRED 0x80000000 // changes take effect later +#define EAXVOCALMORPHER_COMMITDEFERREDSETTINGS (EAXVOCALMORPHER_NONE | \ + EAXVOCALMORPHER_IMMEDIATE) + +// used by EAXVOCALMORPHER_PHONEMEA and EAXVOCALMORPHER_PHONEMEB +enum +{ + A, E, I, O, U, AA, AE, AH, AO, EH, ER, IH, IY, UH, UW, B, D, F, G, + J, K, L, M, N, P, R, S, T, V, Z +}; + +// used by EAXVOCALMORPHER_WAVEFORM +enum +{ + EAX_VOCALMORPHER_SINUSOID, + EAX_VOCALMORPHER_TRIANGLE, + EAX_VOCALMORPHER_SAWTOOTH +}; + +// Use this structure for EAXVOCALMORPHER_ALLPARAMETERS +#ifndef EAXVOCALMORPHERPROPERTIES_DEFINED +#define EAXVOCALMORPHERPROPERTIES_DEFINED +typedef struct _EAXVOCALMORPHERPROPERTIES +{ + unsigned long ulPhonemeA; // see enum above + long lPhonemeACoarseTuning; // (semitones) + unsigned long ulPhonemeB; // see enum above + long lPhonemeBCoarseTuning; // (semitones) + unsigned long ulWaveform; // Waveform selector - see enum above + float flRate; // (Hz) +} EAXVOCALMORPHERPROPERTIES, *LPEAXVOCALMORPHERPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXVOCALMORPHER_MINPHONEMEA 0 +#define EAXVOCALMORPHER_MAXPHONEMEA 29 +#define EAXVOCALMORPHER_DEFAULTPHONEMEA 0 + +#define EAXVOCALMORPHER_MINPHONEMEACOARSETUNING (-24) +#define EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING 24 +#define EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING 0 + +#define EAXVOCALMORPHER_MINPHONEMEB 0 +#define EAXVOCALMORPHER_MAXPHONEMEB 29 +#define EAXVOCALMORPHER_DEFAULTPHONEMEB 10 + +#define EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING (-24) +#define EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING 24 +#define EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING 0 + +#define EAXVOCALMORPHER_MINWAVEFORM 0 +#define EAXVOCALMORPHER_MAXWAVEFORM 2 +#define EAXVOCALMORPHER_DEFAULTWAVEFORM 0 + +#define EAXVOCALMORPHER_MINRATE 0.0f +#define EAXVOCALMORPHER_MAXRATE 10.0f +#define EAXVOCALMORPHER_DEFAULTRATE 1.41f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Pitch Shifter Effect + +// EAX PITCH SHIFTER {E7905100-AFB2-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_PITCHSHIFTER_EFFECT, + 0xe7905100, + 0xafb2, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Pitch Shifter properties +typedef enum +{ + EAXPITCHSHIFTER_NONE, + EAXPITCHSHIFTER_ALLPARAMETERS, + EAXPITCHSHIFTER_COARSETUNE, + EAXPITCHSHIFTER_FINETUNE +} EAXPITCHSHIFTER_PROPERTY; + +// OR these flags with property id +#define EAXPITCHSHIFTER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXPITCHSHIFTER_DEFERRED 0x80000000 // changes take effect later +#define EAXPITCHSHIFTER_COMMITDEFERREDSETTINGS (EAXPITCHSHIFTER_NONE | \ + EAXPITCHSHIFTER_IMMEDIATE) + +// Use this structure for EAXPITCHSHIFTER_ALLPARAMETERS +#ifndef EAXPITCHSHIFTERPROPERTIES_DEFINED +#define EAXPITCHSHIFTERPROPERTIES_DEFINED +typedef struct _EAXPITCHSHIFTERPROPERTIES +{ + long lCoarseTune; // Amount of pitch shift (semitones) + long lFineTune; // Amount of pitch shift (cents) +} EAXPITCHSHIFTERPROPERTIES, *LPEAXPITCHSHIFTERPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXPITCHSHIFTER_MINCOARSETUNE (-12) +#define EAXPITCHSHIFTER_MAXCOARSETUNE 12 +#define EAXPITCHSHIFTER_DEFAULTCOARSETUNE 12 + +#define EAXPITCHSHIFTER_MINFINETUNE (-50) +#define EAXPITCHSHIFTER_MAXFINETUNE 50 +#define EAXPITCHSHIFTER_DEFAULTFINETUNE 0 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Ring Modulator Effect + +// EAX RING MODULATOR {B89FE60-AFB5-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_RINGMODULATOR_EFFECT, + 0xb89fe60, + 0xafb5, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Ring Modulator properties +typedef enum +{ + EAXRINGMODULATOR_NONE, + EAXRINGMODULATOR_ALLPARAMETERS, + EAXRINGMODULATOR_FREQUENCY, + EAXRINGMODULATOR_HIGHPASSCUTOFF, + EAXRINGMODULATOR_WAVEFORM +} EAXRINGMODULATOR_PROPERTY; + +// OR these flags with property id +#define EAXRINGMODULATOR_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXRINGMODULATOR_DEFERRED 0x80000000 // changes take effect later +#define EAXRINGMODULATOR_COMMITDEFERREDSETTINGS (EAXRINGMODULATOR_NONE | \ + EAXRINGMODULATOR_IMMEDIATE) + +// used by EAXRINGMODULATOR_WAVEFORM +enum +{ + EAX_RINGMODULATOR_SINUSOID, + EAX_RINGMODULATOR_SAWTOOTH, + EAX_RINGMODULATOR_SQUARE +}; + +// Use this structure for EAXRINGMODULATOR_ALLPARAMETERS +#ifndef EAXRINGMODULATORPROPERTIES_DEFINED +#define EAXRINGMODULATORPROPERTIES_DEFINED +typedef struct _EAXRINGMODULATORPROPERTIES +{ + float flFrequency; // Frequency of modulation (Hz) + float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) + unsigned long ulWaveform; // Waveform selector - see enum above +} EAXRINGMODULATORPROPERTIES, *LPEAXRINGMODULATORPROPERTIES; +#endif + +// Property ranges and defaults: + +#define EAXRINGMODULATOR_MINFREQUENCY 0.0f +#define EAXRINGMODULATOR_MAXFREQUENCY 8000.0f +#define EAXRINGMODULATOR_DEFAULTFREQUENCY 440.0f + +#define EAXRINGMODULATOR_MINHIGHPASSCUTOFF 0.0f +#define EAXRINGMODULATOR_MAXHIGHPASSCUTOFF 24000.0f +#define EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF 800.0f + +#define EAXRINGMODULATOR_MINWAVEFORM 0 +#define EAXRINGMODULATOR_MAXWAVEFORM 2 +#define EAXRINGMODULATOR_DEFAULTWAVEFORM 0 + +//////////////////////////////////////////////////////////////////////////// + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/openal/include/eaxac3.h b/src/openal/include/eaxac3.h new file mode 100644 index 0000000..7906270 --- /dev/null +++ b/src/openal/include/eaxac3.h @@ -0,0 +1,256 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/************************************************************************************************ +/ +/ EAX-AC3 Open AL Extension Header file +/ +/ Description : The EAX-AC3 extension to Open AL provides a way to playback Dolby Digital AC3 +/ files on systems equipped with a SB Live! card. The packaged AC3 data is output +/ via the MMSYSTEM Wave device. +/ If a SB Live! 5.1 card is installed then the AC3 data will be decoded by the +/ audio card. +/ If a legacy SB Live! card is installed then the AC3 data will be sent directly +/ to the S/PDIF Out. +/ The API supports multiple EAX-AC3 devices, and multiple AC3 streams. However +/ the current implementation provides one EAX-AC3 device capable of playing +/ one AC3 Stream at a time. +/ +/ Programmer : Daniel Peacock Creative Labs, Inc February 2001 +/ +/************************************************************************************************/ + +#ifndef _EAXAC3_H_ +#define _EAXAC3_H_ + +// Do not define the symbol EAXAC3_EXPORTS in any projects that use the EAX-AC3 Open AL Extension +#ifdef EAXAC3_EXPORTS +#define EAXAC3_API __declspec(dllexport) +#else +#define EAXAC3_API __declspec(dllimport) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _HRESULT_DEFINED +#define _HRESULT_DEFINED +typedef signed long HRESULT; +#endif + +enum POSFORMAT { MILLISECONDS, BYTES, AC3FRAMES }; + +enum SOURCE { AC3FILE, MEMORY }; + +// Success Codes +#define EAXAC3_OK 0 +#define EAXAC3_ALREADYPLAYING 1 +#define EAXAC3_EOF 2 + +// Error Codes +#define EAXAC3ERR_UNABLETOOPENEAXAC3DEVICE -1 +#define EAXAC3ERR_WAVEOUTPREPAREHEADERFAILURE -2 +#define EAXAC3ERR_OUTOFMEMORY -3 +#define EAXAC3ERR_FILENOTFOUND -4 +#define EAXAC3ERR_AC3FILETOBIG -5 +#define EAXAC3ERR_AC3FRAMENOTFOUND -6 +#define EAXAC3ERR_AC3NOTAT48KHZ -7 +#define EAXAC3ERR_INVALIDAC3FRAME -8 +#define EAXAC3ERR_AC3FILENOTOPEN -9 +#define EAXAC3ERR_BUFFERNOTMULTIPLEOFAC3FRAMESIZE -10 +#define EAXAC3ERR_WAVEOUTERROR -11 +#define EAXAC3ERR_FAILEDTOCREATEEVENT -12 +#define EAXAC3ERR_EAXAC3DEVICENOTOPEN -13 +#define EAXAC3ERR_AC3STREAMALREADYOPEN -14 +#define EAXAC3ERR_POSITIONOUTOFRANGE -15 +#define EAXAC3ERR_NOTATSTARTOFAC3FRAME -16 +#define EAXAC3ERR_AC3STREAMNOTOPEN -17 +#define EAXAC3ERR_SETPOSITIONONLYWORKSONAC3FILES -18 +#define EAXAC3ERR_WRITEDATAONLYWORKSWITHMEMORYSTREAMS -19 +#define EAXAC3ERR_INVALIDPARAMETER -20 +#define EAXAC3ERR_NOTENOUGHAC3DATAINAC3DATABUFFER -21 +#define EAXAC3ERR_NOTENOUGHDATA -22 +#define EAXAC3ERR_EAXAC3DEVICEALREADYOPEN -23 +#define EAXAC3ERR_EAXAC3DEVICENOTFOUND -24 +#define EAXAC3ERR_UNSUPPORTED -25 +#define EAXAC3ERR_FAILEDTOCREATEFNTABLE -26 + +#define DEFAULTEAXAC3DEVICE 0 + +#define ENTIREBUFFER 0 +#define FROMWRITECURSOR 1 + +#define LOOPING 1 + +#define ENDOFDATA 1 + +typedef unsigned int EAXAC3HANDLE; + +typedef unsigned int AC3STREAM; + +// Callback function +typedef void (__stdcall *LPAC3CALLBACK)(AC3STREAM AC3Stream, int msg); + +// Callback messages +#define EAXAC3NEEDMOREDATA 0 +#define EAXAC3REACHEDEND 1 + +typedef struct +{ + unsigned int nNumOfAC3Frames; + unsigned int nAC3FrameSize; + unsigned int nSizeOfFile; + unsigned int nDuration; + unsigned int nFrequency; +} AC3FILEINFO, *LPAC3FILEINFO; + +#define UNKNOWN 1 +#define SPDIFPASSTHRU 2 +#define FULLDECODE 4 + +typedef struct +{ + char szDeviceName[256]; + unsigned int uFlags; + unsigned int uStreams; + unsigned int uReserved; +} EAXAC3DEVICEINFO, *LPEAXAC3DEVICEINFO; + +// Function typedefs + +typedef int (*LPEAXAC3QUERYNUMBEROFDEVICES) (void); +typedef HRESULT (*LPEAXAC3QUERYFILE) (char *, LPAC3FILEINFO, int); +typedef HRESULT (*LPEAXAC3QUERYMEMORY) (char *, int, LPAC3FILEINFO, int); +typedef int (*LPEAXAC3QUERYNOOFFRAMESREQFORPLAYBACK) (AC3STREAM); +typedef HRESULT (*LPEAXAC3OPENPLAYBACKDEVICE) (EAXAC3HANDLE); +typedef HRESULT (*LPEAXAC3CLOSEPLAYBACKDEVICE) (EAXAC3HANDLE); +typedef HRESULT (*LPEAXAC3QUERYDEVICECAPS) (EAXAC3HANDLE, LPEAXAC3DEVICEINFO, int); +typedef HRESULT (*LPEAXAC3GETPOSITION) (AC3STREAM, enum POSFORMAT, int *); +typedef HRESULT (*LPEAXAC3SETFILEPOSITION) (AC3STREAM, enum POSFORMAT, int); +typedef HRESULT (*LPEAXAC3OPENSTREAM) (EAXAC3HANDLE, AC3STREAM *, LPAC3CALLBACK, char *, enum SOURCE); +typedef HRESULT (*LPEAXAC3CLOSESTREAM) (AC3STREAM); +typedef HRESULT (*LPEAXAC3PREPLAYSTREAM) (AC3STREAM); +typedef HRESULT (*LPEAXAC3PLAYSTREAM) (AC3STREAM, int); +typedef HRESULT (*LPEAXAC3STOPSTREAM) (AC3STREAM); +typedef HRESULT (*LPEAXAC3PAUSESTREAM) (AC3STREAM); +typedef HRESULT (*LPEAXAC3RESUMESTREAM) (AC3STREAM); +typedef HRESULT (*LPEAXAC3LOCKBUFFER) (AC3STREAM, unsigned long, void **, unsigned long *, void **, + unsigned long *, unsigned long); +typedef HRESULT (*LPEAXAC3UNLOCKBUFFER) (AC3STREAM, void *, unsigned long, void *, unsigned long, int); +typedef HRESULT (*LPEAXAC3SETPLAYBACKMODE) (EAXAC3HANDLE, unsigned int); +typedef char * (*LPEAXAC3GETERRORSTRING) (HRESULT, char *, int); +typedef HRESULT (*LPEAXAC3GETLASTERROR) (HRESULT *); + +// Function table declaration +typedef struct +{ + LPEAXAC3QUERYNUMBEROFDEVICES EAXAC3QueryNumberOfDevices; + LPEAXAC3QUERYFILE EAXAC3QueryFile; + LPEAXAC3QUERYMEMORY EAXAC3QueryMemory; + LPEAXAC3QUERYNOOFFRAMESREQFORPLAYBACK EAXAC3QueryNoOfFramesReqForPlayback; + LPEAXAC3OPENPLAYBACKDEVICE EAXAC3OpenPlaybackDevice; + LPEAXAC3CLOSEPLAYBACKDEVICE EAXAC3ClosePlaybackDevice; + LPEAXAC3QUERYDEVICECAPS EAXAC3QueryDeviceCaps; + LPEAXAC3GETPOSITION EAXAC3GetPosition; + LPEAXAC3SETFILEPOSITION EAXAC3SetFilePosition; + LPEAXAC3OPENSTREAM EAXAC3OpenStream; + LPEAXAC3CLOSESTREAM EAXAC3CloseStream; + LPEAXAC3PREPLAYSTREAM EAXAC3PrePlayStream; + LPEAXAC3PLAYSTREAM EAXAC3PlayStream; + LPEAXAC3STOPSTREAM EAXAC3StopStream; + LPEAXAC3PAUSESTREAM EAXAC3PauseStream; + LPEAXAC3RESUMESTREAM EAXAC3ResumeStream; + LPEAXAC3LOCKBUFFER EAXAC3LockBuffer; + LPEAXAC3UNLOCKBUFFER EAXAC3UnLockBuffer; + LPEAXAC3SETPLAYBACKMODE EAXAC3SetPlaybackMode; + LPEAXAC3GETERRORSTRING EAXAC3GetErrorString; + LPEAXAC3GETLASTERROR EAXAC3GetLastError; +} EAXAC3FNTABLE, *LPEAXAC3FNTABLE; + + +#ifndef OPENAL +typedef EAXAC3_API HRESULT (*LPEAXAC3GETFUNCTIONTABLE) (LPEAXAC3FNTABLE); +#else +typedef ALboolean (*LPALEAXAC3GETFUNCTIONTABLE) (LPEAXAC3FNTABLE); +#endif + +// Functions exposed in the DLL + +EAXAC3_API HRESULT EAXAC3GetFunctionTable(LPEAXAC3FNTABLE lpEAXAC3FnTable); + +EAXAC3_API int EAXAC3QueryNumberOfDevices(); + +EAXAC3_API HRESULT EAXAC3QueryFile(char *szAC3Filename, LPAC3FILEINFO lpAC3Caps, int nSizeOfAC3FileInfoStruct); + +EAXAC3_API HRESULT EAXAC3QueryMemory(char *lpBuffer, int nSizeOfBuffer, LPAC3FILEINFO lpAC3FileInfo, + int nSizeOfAC3FileInfoStruct); + +EAXAC3_API int EAXAC3QueryNoOfFramesReqForPlayback(AC3STREAM AC3Stream); + +EAXAC3_API HRESULT EAXAC3OpenPlaybackDevice(EAXAC3HANDLE EAXAC3Handle); + +EAXAC3_API HRESULT EAXAC3ClosePlaybackDevice(EAXAC3HANDLE EAXAC3Handle); + +EAXAC3_API HRESULT EAXAC3QueryDeviceCaps(EAXAC3HANDLE EAXAC3Handle, LPEAXAC3DEVICEINFO lpEAXAC3DeviceInfo, + int nSizeOfAC3DeviceInfoStruct); + +EAXAC3_API HRESULT EAXAC3GetPosition(AC3STREAM AC3Stream, enum POSFORMAT posFormat, int *lpAmount); + +EAXAC3_API HRESULT EAXAC3SetFilePosition(AC3STREAM AC3Stream, enum POSFORMAT posFormat, int nAmount); + +EAXAC3_API HRESULT EAXAC3OpenStream(EAXAC3HANDLE EAXAC3Handle, AC3STREAM *lpAC3Stream, + LPAC3CALLBACK pAC3CallbackFn, char *szAC3Filename, enum SOURCE src); + +EAXAC3_API HRESULT EAXAC3CloseStream(AC3STREAM AC3Stream); + +EAXAC3_API HRESULT EAXAC3PrePlayStream(AC3STREAM AC3Stream); + +EAXAC3_API HRESULT EAXAC3PlayStream(AC3STREAM AC3Stream, int nLooping); + +EAXAC3_API HRESULT EAXAC3StopStream(AC3STREAM AC3Stream); + +EAXAC3_API HRESULT EAXAC3PauseStream(AC3STREAM AC3Stream); + +EAXAC3_API HRESULT EAXAC3ResumeStream(AC3STREAM AC3Stream); + +EAXAC3_API HRESULT EAXAC3LockBuffer(AC3STREAM AC3Stream, unsigned long ulBytes, void **ppvPointer1, + unsigned long *pdwBytes1, void **ppvPointer2, unsigned long *pdwBytes2, + unsigned long ulFlags); + +EAXAC3_API HRESULT EAXAC3UnLockBuffer(AC3STREAM AC3Stream, void *pvPointer1, unsigned long ulSize1, + void *pvPointer2, unsigned long ulSize2, int nFinished); + +EAXAC3_API HRESULT EAXAC3SetPlaybackMode(EAXAC3HANDLE EAXAC3Handle, unsigned int ulPlayMode); + +EAXAC3_API char * EAXAC3GetErrorString(HRESULT hr, char *szErrorString, int nSizeOfErrorString); + +EAXAC3_API HRESULT EAXAC3GetLastError(HRESULT *hr); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/openal/include/eaxman.h b/src/openal/include/eaxman.h new file mode 100644 index 0000000..a8c757d --- /dev/null +++ b/src/openal/include/eaxman.h @@ -0,0 +1,195 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +*/ +#ifndef __EAXMANH +#define __EAXMANH + +#define COM_NO_WINDOWS_H +#include +#include "eax3.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +//#define CLSID_EAXMANAGER CLSID_EAX20_Manager +//#define IID_IEaxManager IID_EAX20_Manager +#define EM_MAX_NAME 32 + +#define EMFLAG_IDDEFAULT (-1) +#define EMFLAG_IDNONE (-2) +#define EMFLAG_LOCKPOSITION 1 +#define EMFLAG_LOADFROMMEMORY 2 +#define EMFLAG_NODIFFRACTION 4 + +typedef struct _EMPOINT { + float fX; + float fY; + float fZ; +} EMPOINT; +typedef EMPOINT FAR *LPEMPOINT; + +typedef struct _LISTENERATTRIBUTES { + float fDistanceFactor; + float fRolloffFactor; + float fDopplerFactor; +} LISTENERATTRIBUTES; +typedef LISTENERATTRIBUTES FAR *LPLISTENERATTRIBUTES; + +typedef struct _SOURCEATTRIBUTES { + EAXBUFFERPROPERTIES eaxAttributes; + unsigned long ulInsideConeAngle; + unsigned long ulOutsideConeAngle; + long lConeOutsideVolume; + float fConeXdir; + float fConeYdir; + float fConeZdir; + float fMinDistance; + float fMaxDistance; + long lDupCount; + long lPriority; +} SOURCEATTRIBUTES; +typedef SOURCEATTRIBUTES FAR *LPSOURCEATTRIBUTES; + +typedef struct _MATERIALATTRIBUTES { + long lLevel; + float fLFRatio; + float fRoomRatio; + DWORD dwFlags; +} MATERIALATTRIBUTES; +typedef MATERIALATTRIBUTES FAR *LPMATERIALATTRIBUTES; + +#define EMMATERIAL_OBSTRUCTS 1 +#define EMMATERIAL_OCCLUDES 3 + +typedef struct _DIFFRACTIONBOX { + long lSubspaceID; + EMPOINT empMin; + EMPOINT empMax; +} DIFFRACTIONBOX; +typedef DIFFRACTIONBOX FAR *LPDIFFRACTIONBOX; + +// {7CE4D6E6-562F-11d3-8812-005004062F83} +DEFINE_GUID(CLSID_EAXMANAGER, 0x60b721a1, 0xf7c8, 0x11d2, 0xa0, 0x2e, 0x0, 0x50, 0x4, 0x6, 0x18, 0xb8); + +#ifdef __cplusplus +struct IEaxManager; +#endif // __cplusplus + +typedef struct IEaxManager *LPEAXMANAGER; + +// {7CE4D6E8-562F-11d3-8812-005004062F83} +DEFINE_GUID(IID_IEaxManager, 0x60b721a2, 0xf7c8, 0x11d2, 0xa0, 0x2e, 0x0, 0x50, 0x4, 0x6, 0x18, 0xb8); + +#undef INTERFACE +#define INTERFACE IEaxManager + +extern HRESULT __stdcall EaxManagerCreate(LPEAXMANAGER*); +typedef HRESULT (__stdcall *LPEAXMANAGERCREATE)(LPEAXMANAGER*); + +DECLARE_INTERFACE_(IEaxManager, IUnknown) +{ + // IUnknown methods + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetDataSetSize) (THIS_ unsigned long*, DWORD) PURE; + STDMETHOD(LoadDataSet) (THIS_ char*, DWORD) PURE; + STDMETHOD(FreeDataSet) (THIS_ DWORD) PURE; + STDMETHOD(GetListenerAttributes) (THIS_ LPLISTENERATTRIBUTES) PURE; + STDMETHOD(GetSourceID) (THIS_ char*, long*) PURE; + STDMETHOD(GetSourceAttributes) (THIS_ long, LPSOURCEATTRIBUTES) PURE; + STDMETHOD(GetSourceNumInstances) (THIS_ long, long*) PURE; + STDMETHOD(GetSourceInstancePos) (THIS_ long, long, LPEMPOINT) PURE; + STDMETHOD(GetEnvironmentID) (THIS_ char*, long*) PURE; + STDMETHOD(GetEnvironmentAttributes) (THIS_ long, LPEAXLISTENERPROPERTIES) PURE; + STDMETHOD(GetMaterialID) (THIS_ char*, long*) PURE; + STDMETHOD(GetMaterialAttributes) (THIS_ long, LPMATERIALATTRIBUTES) PURE; + STDMETHOD(GetGeometrySetID) (THIS_ char*, long*) PURE; + STDMETHOD(GetListenerDynamicAttributes) (THIS_ long, LPEMPOINT, long*, DWORD) PURE; + STDMETHOD(GetSourceDynamicAttributes) (THIS_ long, LPEMPOINT, long*, float*, long*, float*, float*, LPEMPOINT, DWORD) PURE; +// STDMETHOD(GetSubSpaceID) (THIS_ long, LPEMPOINT, long *) PURE; + STDMETHOD(GetEnvironmentName) (THIS_ long, char *szString, long lStrlen) PURE; +}; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IEaxManager_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IEaxManager_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IEaxManager_Release(p) (p)->lpVtbl->Release(p) +#define IEaxManager_GetDataSetSize(p,a,b) (p)->lpVtbl->GetDataSetSize(p,a,b) +#define IEaxManager_LoadDataSet(p,a,b) (p)->lpVtbl->LoadDataSet(p,a,b) +#define IEaxManager_FreeDataSet(p,a) (p)->lpVtbl->FreeDataSet(p,a) +#define IEaxManager_GetListenerAttributes(p,a) (p)->lpVtbl->GetListenerAttributes(p,a) +#define IEaxManager_GetSourceID(p,a,b) (p)->lpVtbl->GetSourceID(p,a,b) +#define IEaxManager_GetSourceAttributes(p,a,b) (p)->lpVtbl->GetSourceAttributes(p,a,b) +#define IEaxManager_GetSourceNumInstances(p,a,b) (p)->lpVtbl->GetSourceNumInstances(p,a,b) +#define IEaxManager_GetSourceInstancePos(p,a,b,c) (p)->lpVtbl->GetSourceInstancePos(p,a,b,c) +#define IEaxManager_GetEnvironmentID(p,a,b) (p)->lpVtbl->GetEnvironmentID(p,a,b) +#define IEaxManager_GetEnvironmentAttributes(p,a,b) (p)->lpVtbl->GetEnvironmentAttributes(p,a,b) +#define IEaxManager_GetMaterialID(p,a,b) (p)->lpVtbl->GetMaterialID(p,a,b) +#define IEaxManager_GetMaterialAttributes(p,a,b) (p)->lpVtbl->GetMaterialAttributes(p,a,b) +#define IEaxManager_GetGeometrySetID(p,a,b) (p)->lpVtbl->GetGeometrySetID(p,a,b) +#define IEaxManager_GetListenerDynamicAttributes(p,a,b,c,d) (p)->lpVtbl->GetListenerDynamicAttributes(p,a,b,c,d) +#define IEaxManager_GetSourceDynamicAttributes(p,a,b,c,d,e,f,g,h,i) (p)->lpVtbl->GetSourceDynamicAttributes(p,a,b,c,d,e,f,g,h,i) +//#define IEaxManager_GetSubSpaceID(p,a,b,c) (p)->lpVtbl->GetSubSpaceID(p,a,b,c) +#define IEaxManager_GetEnvironmentName(p,a,b,c) (p)->lpVtbl->GetEnvironmentName(p,a,b,c) +#else +#define IEaxManager_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IEaxManager_AddRef(p) (p)->AddRef() +#define IEaxManager_Release(p) (p)->Release() +#define IEaxManager_GetDataSetSize(p,a,b) (p)->GetDataSetSize(a,b) +#define IEaxManager_LoadDataSet(p,a,b) (p)->LoadDataSet(a,b) +#define IEaxManager_FreeDataSet(p,a) (p)->FreeDataSet(a) +#define IEaxManager_GetListenerAttributes(p,a) (p)->GetListenerAttributes(a) +#define IEaxManager_GetSourceID(p,a,b) (p)->GetSourceID(a,b) +#define IEaxManager_GetSourceAttributes(p,a,b) (p)->GetSourceAttributes(a,b) +#define IEaxManager_GetSourceNumInstances(p,a,b) (p)->GetSourceNumInstances(a,b) +#define IEaxManager_GetSourceInstancePos(p,a,b,c) (p)->GetSourceInstancePos(a,b,c) +#define IEaxManager_GetEnvironmentID(p,a,b) (p)->GetEnvironmentID(a,b) +#define IEaxManager_GetEnvironmentAttributes(p,a,b) (p)->GetEnvironmentAttributes(a,b) +#define IEaxManager_GetMaterialID(p,a,b) (p)->GetMaterialID(a,b) +#define IEaxManager_GetMaterialAttributes(p,a,b) (p)->GetMaterialAttributes(a,b) +#define IEaxManager_GetGeometrySetID(p,a,b) (p)->GetGeometrySetID(a,b) +#define IEaxManager_GetListenerDynamicAttributes(p,a,b,c,d) (p)->GetListenerDynamicAttributes(a,b,c,d) +#define IEaxManager_GetSourceDynamicAttributes(p,a,b,c,d,e,f,g,h,i) (p)->GetSourceDynamicAttributes(a,b,c,d,e,f,g,h,i) +//#define IEaxManager_GetSubSpaceID(p,a,b,c) (p)->GetSubSpaceID(a,b,c) +#define IEaxManager_GetEnvironmentName(p,a,b,c) (p)->GetEnvironmentName(a,b,c) +#endif + +#define EM_OK 0 +#define EM_INVALIDID MAKE_HRESULT(1, FACILITY_ITF, 1) +#define EM_IDNOTFOUND MAKE_HRESULT(1, FACILITY_ITF, 2) +#define EM_FILENOTFOUND MAKE_HRESULT(1, FACILITY_ITF, 3) +#define EM_FILEINVALID MAKE_HRESULT(1, FACILITY_ITF, 4) +#define EM_VERSIONINVALID MAKE_HRESULT(1, FACILITY_ITF, 5) +#define EM_INSTANCENOTFOUND MAKE_HRESULT(1, FACILITY_ITF, 6) + +#ifdef __cplusplus +}; +#endif // __cplusplus + +#endif diff --git a/src/openal/include/efxlib.h b/src/openal/include/efxlib.h new file mode 100644 index 0000000..eef3deb --- /dev/null +++ b/src/openal/include/efxlib.h @@ -0,0 +1,85 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +*/ + +#ifndef __EFXLIBH +#define __EFXLIBH + +#include "eax4.h" + + + + +/////////////////////////////////////////////////////////// +// Class definitions. +class idSoundEffect +{ +public: + idSoundEffect() { + }; + ~idSoundEffect() { + if ( data && datasize ) { + Mem_Free( data ); + data = NULL; + } + } + + idStr name; + int datasize; + void *data; +}; + +class idEFXFile +{ +private: + +protected: + // Protected data members. + +public: + // Public data members. + +private: + +public: + idEFXFile(); + ~idEFXFile(); + + bool FindEffect( idStr &name, idSoundEffect **effect, int *index ); + bool ReadEffect( idLexer &lexer, idSoundEffect *effect ); + bool LoadFile( const char *filename, bool OSPath = false ); + void UnloadFile( void ); + void Clear( void ); + + idListeffects; +}; +/////////////////////////////////////////////////////////// + + + + +#endif // __EFXLIBH + diff --git a/src/openal/osx/OpenAL.framework/Headers b/src/openal/osx/OpenAL.framework/Headers new file mode 100644 index 0000000..a177d2a --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/src/openal/osx/OpenAL.framework/OpenAL b/src/openal/osx/OpenAL.framework/OpenAL new file mode 100644 index 0000000..7a4d340 --- /dev/null +++ b/src/openal/osx/OpenAL.framework/OpenAL @@ -0,0 +1 @@ +Versions/Current/OpenAL \ No newline at end of file diff --git a/src/openal/osx/OpenAL.framework/Resources b/src/openal/osx/OpenAL.framework/Resources new file mode 100644 index 0000000..953ee36 --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Headers/al.h b/src/openal/osx/OpenAL.framework/Versions/A/Headers/al.h new file mode 100644 index 0000000..77b08d6 --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Headers/al.h @@ -0,0 +1,522 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _AL_H_ +#define _AL_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "altypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #ifdef _LIB + #define ALAPI __declspec(dllexport) + #else + #define ALAPI __declspec(dllimport) + #endif + #define ALAPIENTRY __cdecl + #define AL_CALLBACK +#else + #ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif + #endif + #define ALAPI + #define ALAPIENTRY + #define AL_CALLBACK +#endif + +#define OPENAL + +#ifndef AL_NO_PROTOTYPES + +/** + * OpenAL Maintenance Functions + * Initialization and exiting. + * State Management and Query. + * Error Handling. + * Extension Support. + */ + +/** State management. */ +ALAPI ALvoid ALAPIENTRY alEnable( ALenum capability ); +ALAPI ALvoid ALAPIENTRY alDisable( ALenum capability ); +ALAPI ALboolean ALAPIENTRY alIsEnabled( ALenum capability ); + +/** Application preferences for driver performance choices. */ +ALAPI ALvoid ALAPIENTRY alHint( ALenum target, ALenum mode ); + +/** State retrieval. */ +ALAPI ALboolean ALAPIENTRY alGetBoolean( ALenum param ); +ALAPI ALint ALAPIENTRY alGetInteger( ALenum param ); +ALAPI ALfloat ALAPIENTRY alGetFloat( ALenum param ); +ALAPI ALdouble ALAPIENTRY alGetDouble( ALenum param ); +ALAPI ALvoid ALAPIENTRY alGetBooleanv( ALenum param, ALboolean* data ); +ALAPI ALvoid ALAPIENTRY alGetIntegerv( ALenum param, ALint* data ); +ALAPI ALvoid ALAPIENTRY alGetFloatv( ALenum param, ALfloat* data ); +ALAPI ALvoid ALAPIENTRY alGetDoublev( ALenum param, ALdouble* data ); +ALAPI ALubyte* ALAPIENTRY alGetString( ALenum param ); + +ALAPI ALvoid ALAPIENTRY alSetInteger( ALenum pname, ALint value ); +ALAPI ALvoid ALAPIENTRY alSetDouble( ALenum pname, ALdouble value ); + +/** + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY alGetError(); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALboolean ALAPIENTRY alIsExtensionPresent( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALvoid* ALAPIENTRY alGetProcAddress( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the integer value of an enumeration (usually an extension) with the name ename. + */ +ALAPI ALenum ALAPIENTRY alGetEnumValue( ALubyte* ename ); + + + + +/** + * LISTENER + * Listener is the sample position for a given context. + * The multi-channel (usually stereo) output stream generated + * by the mixer is parametrized by this Listener object: + * its position and velocity relative to Sources, within + * occluder and reflector geometry. + */ + + + +/** + * + * Listener Environment: default 0. + */ +ALAPI ALvoid ALAPIENTRY alListeneri( ALenum param, ALint value ); + + +/** + * + * Listener Gain: default 1.0f. + */ +ALAPI ALvoid ALAPIENTRY alListenerf( ALenum param, ALfloat value ); + + +/** + * + * Listener Position. + * Listener Velocity. + */ +ALAPI ALvoid ALAPIENTRY alListener3f( ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); + + +/** + * + * Listener Position: ALfloat[3] + * Listener Velocity: ALfloat[3] + * Listener Orientation: ALfloat[6] (forward and up vector). + */ +ALAPI ALvoid ALAPIENTRY alListenerfv( ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY alGetListeneri( ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetListenerf( ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY alGetListener3f( ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY alGetListenerfv( ALenum param, ALfloat* values ); + + +/** + * SOURCE + * Source objects are by default localized. Sources + * take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial + * arrangement etc. + */ + + + +/** Create Source objects. */ +ALAPI ALvoid ALAPIENTRY alGenSources( ALsizei n, ALuint* sources ); + +/** Delete Source objects. */ +ALAPI ALvoid ALAPIENTRY alDeleteSources( ALsizei n, ALuint* sources ); + +/** Verify a handle is a valid Source. */ +ALAPI ALboolean ALAPIENTRY alIsSource( ALuint id ); + +/** Set an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY alSourcei( ALuint source, ALenum param, ALint value ); +ALAPI ALvoid ALAPIENTRY alSourcef( ALuint source, ALenum param, ALfloat value ); +ALAPI ALvoid ALAPIENTRY alSource3f( ALuint source, ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); +ALAPI ALvoid ALAPIENTRY alSourcefv( ALuint source, ALenum param, ALfloat* values ); + +/** Get an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY alGetSourcei( ALuint source, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetSourcef( ALuint source, ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY alGetSource3f( ALuint source, ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY alGetSourcefv( ALuint source, ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY alSourcePlayv( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourcePausev( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourceStopv( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourceRewindv(ALsizei n,ALuint *sources); + +/** Activate a source, start replay. */ +ALAPI ALvoid ALAPIENTRY alSourcePlay( ALuint source ); + +/** + * Pause a source, + * temporarily remove it from the mixer list. + */ +ALAPI ALvoid ALAPIENTRY alSourcePause( ALuint source ); + +/** + * Stop a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + * To remove a Source completely, it has to be + * deleted following Stop, or before Play. + */ +ALAPI ALvoid ALAPIENTRY alSourceStop( ALuint source ); + +/** + * Rewinds a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + */ +ALAPI ALvoid ALAPIENTRY alSourceRewind( ALuint source ); + + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. There can be more than + * one Source using the same Buffer data. If Buffers have + * to be duplicated on a per-Source basis, the driver has to + * take care of allocation, copying, and deallocation as well + * as propagating buffer data changes. + */ + + + + +/** Buffer object generation. */ +ALAPI ALvoid ALAPIENTRY alGenBuffers( ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY alDeleteBuffers( ALsizei n, ALuint* buffers ); +ALAPI ALboolean ALAPIENTRY alIsBuffer( ALuint buffer ); + +/** + * Specify the data to be filled into a buffer. + */ +ALAPI ALvoid ALAPIENTRY alBufferData( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALsizei freq ); + + +ALAPI ALvoid ALAPIENTRY alGetBufferi( ALuint buffer, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetBufferf( ALuint buffer, ALenum param, ALfloat* value ); + + + + +/** + * Queue stuff + */ + +ALAPI ALvoid ALAPIENTRY alSourceQueueBuffers( ALuint source, ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY alSourceUnqueueBuffers( ALuint source, ALsizei n, ALuint* buffers ); + +/** + * Knobs and dials + */ +ALAPI ALvoid ALAPIENTRY alDistanceModel( ALenum value ); +ALAPI ALvoid ALAPIENTRY alDopplerFactor( ALfloat value ); +ALAPI ALvoid ALAPIENTRY alDopplerVelocity( ALfloat value ); + +#else /* AL_NO_PROTOTYPES */ + +/** + * OpenAL Maintenance Functions + * Initialization and exiting. + * State Management and Query. + * Error Handling. + * Extension Support. + */ + +/** State management. */ +ALAPI ALvoid ALAPIENTRY (*alEnable)( ALenum capability ); +ALAPI ALvoid ALAPIENTRY (*alDisable)( ALenum capability ); +ALAPI ALboolean ALAPIENTRY (*alIsEnabled)( ALenum capability ); + +/** Application preferences for driver performance choices. */ +ALAPI ALvoid ALAPIENTRY (*alHint)( ALenum target, ALenum mode ); + +/** State retrieval. */ +ALAPI ALboolean ALAPIENTRY (*alGetBoolean)( ALenum param ); +ALAPI ALint ALAPIENTRY (*alGetInteger)( ALenum param ); +ALAPI ALfloat ALAPIENTRY (*alGetFloat)( ALenum param ); +ALAPI ALdouble ALAPIENTRY (*alGetDouble)( ALenum param ); +ALAPI ALvoid ALAPIENTRY (*alGetBooleanv)( ALenum param, ALboolean* data ); +ALAPI ALvoid ALAPIENTRY (*alGetIntegerv)( ALenum param, ALint* data ); +ALAPI ALvoid ALAPIENTRY (*alGetFloatv)( ALenum param, ALfloat* data ); +ALAPI ALvoid ALAPIENTRY (*alGetDoublev)( ALenum param, ALdouble* data ); +ALAPI ALubyte* ALAPIENTRY (*alGetString)( ALenum param ); + +ALAPI ALvoid ALAPIENTRY (*alSetInteger)( ALenum pname, ALint value ); +ALAPI ALvoid ALAPIENTRY (*alSetDouble)( ALenum pname, ALdouble value ); + +/** + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY (*alGetError)( ALvoid ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALboolean ALAPIENTRY (*alIsExtensionPresent)( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALvoid* ALAPIENTRY (*alGetProcAddress)( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the integer value of an enumeration (usually an extension) with the name ename. + */ +ALAPI ALenum ALAPIENTRY (*alGetEnumValue)( ALubyte* ename ); + + + + +/** + * LISTENER + * Listener is the sample position for a given context. + * The multi-channel (usually stereo) output stream generated + * by the mixer is parametrized by this Listener object: + * its position and velocity relative to Sources, within + * occluder and reflector geometry. + */ + + + +/** + * + * Listener Environment: default 0. + */ +ALAPI ALvoid ALAPIENTRY (*alListeneri)( ALenum param, ALint value ); + + +/** + * + * Listener Gain: default 1.0f. + */ +ALAPI ALvoid ALAPIENTRY (*alListenerf)( ALenum param, ALfloat value ); + + +/** + * + * Listener Position. + * Listener Velocity. + */ +ALAPI ALvoid ALAPIENTRY (*alListener3f)( ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); + + +/** + * + * Listener Position: ALfloat[3] + * Listener Velocity: ALfloat[3] + * Listener Orientation: ALfloat[6] (forward and up vector). + */ +ALAPI ALvoid ALAPIENTRY (*alListenerfv)( ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY (*alGetListeneri)( ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetListenerf)( ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY (*alGetListener3f)( ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY (*alGetListenerfv)( ALenum param, ALfloat* values ); + + +/** + * SOURCE + * Source objects are by default localized. Sources + * take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial + * arrangement etc. + */ + + + +/** Create Source objects. */ +ALAPI ALvoid ALAPIENTRY (*alGenSources)( ALsizei n, ALuint* sources ); + +/** Delete Source objects. */ +ALAPI ALvoid ALAPIENTRY (*alDeleteSources)( ALsizei n, ALuint* sources ); + +/** Verify a handle is a valid Source. */ +ALAPI ALboolean ALAPIENTRY (*alIsSource)( ALuint id ); + +/** Set an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY (*alSourcei)( ALuint source, ALenum param, ALint value ); +ALAPI ALvoid ALAPIENTRY (*alSourcef)( ALuint source, ALenum param, ALfloat value ); +ALAPI ALvoid ALAPIENTRY (*alSource3f)( ALuint source, ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); +ALAPI ALvoid ALAPIENTRY (*alSourcefv)( ALuint source, ALenum param, ALfloat* values ); + +/** Get an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY (*alGetSourcei)( ALuint source, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetSourcef)( ALuint source, ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY (*alGetSourcefv)( ALuint source, ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY (*alSourcePlayv)( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY (*alSourceStopv)( ALsizei n, ALuint *sources ); + +/** Activate a source, start replay. */ +ALAPI ALvoid ALAPIENTRY (*alSourcePlay)( ALuint source ); + +/** + * Pause a source, + * temporarily remove it from the mixer list. + */ +ALAPI ALvoid ALAPIENTRY (*alSourcePause)( ALuint source ); + +/** + * Stop a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + * To remove a Source completely, it has to be + * deleted following Stop, or before Play. + */ +ALAPI ALvoid ALAPIENTRY (*alSourceStop)( ALuint source ); + + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. There can be more than + * one Source using the same Buffer data. If Buffers have + * to be duplicated on a per-Source basis, the driver has to + * take care of allocation, copying, and deallocation as well + * as propagating buffer data changes. + */ + + + + +/** Buffer object generation. */ +ALAPI ALvoid ALAPIENTRY (*alGenBuffers)( ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY (*alDeleteBuffers)( ALsizei n, ALuint* buffers ); +ALAPI ALboolean ALAPIENTRY (*alIsBuffer)( ALuint buffer ); + +/** + * Specify the data to be filled into a buffer. + */ +ALAPI ALvoid ALAPIENTRY (*alBufferData)( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALsizei freq ); + +ALAPI ALvoid ALAPIENTRY (*alGetBufferi)( ALuint buffer, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetBufferf)( ALuint buffer, ALenum param, ALfloat* value ); + + + + +/** + * Queue stuff + */ +ALAPI ALvoid ALAPIENTRY (*alSourceQueueBuffers)( ALuint source, ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY (*alSourceUnqueueBuffers)( ALuint source, ALsizei n, ALuint* buffers ); + +/** + * Knobs and dials + */ +ALAPI ALvoid ALAPIENTRY (*alDistanceModel)( ALenum value ); +ALAPI ALvoid ALAPIENTRY (*alDopplerFactor)( ALfloat value ); +ALAPI ALvoid ALAPIENTRY (*alDopplerVelocity)( ALfloat value ); + + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export off + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Headers/alc.h b/src/openal/osx/OpenAL.framework/Versions/A/Headers/alc.h new file mode 100644 index 0000000..9fea325 --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Headers/alc.h @@ -0,0 +1,112 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _ALC_H_ +#define _ALC_H_ + +#include "altypes.h" +#include "alctypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #ifdef _LIB + #define ALCAPI __declspec(dllexport) + #else + #define ALCAPI __declspec(dllimport) + typedef ALCvoid ALCdevice; + typedef ALCvoid ALCcontext; + #endif + #define ALCAPIENTRY __cdecl +#else + #ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif + #endif + #define ALCAPI + #define ALCAPIENTRY + typedef ALCvoid ALCdevice; + typedef ALCvoid ALCcontext; +#endif + +#ifndef ALC_NO_PROTOTYPES + +ALCAPI ALCubyte* ALCAPIENTRY alcGetString(ALCdevice *device,ALCenum param); +ALCAPI ALCvoid ALCAPIENTRY alcGetIntegerv(ALCdevice *device,ALCenum param,ALCsizei size,ALCint *data); + +ALCAPI ALCdevice* ALCAPIENTRY alcOpenDevice(ALCubyte *deviceName); +ALCAPI ALCvoid ALCAPIENTRY alcCloseDevice(ALCdevice *device); + +ALCAPI ALCcontext*ALCAPIENTRY alcCreateContext(ALCdevice *device,ALCint *attrList); +ALCAPI ALCboolean ALCAPIENTRY alcMakeContextCurrent(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcProcessContext(ALCcontext *context); +ALCAPI ALCcontext*ALCAPIENTRY alcGetCurrentContext(); +ALCAPI ALCdevice* ALCAPIENTRY alcGetContextsDevice(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcSuspendContext(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcDestroyContext(ALCcontext *context); + +ALCAPI ALCenum ALCAPIENTRY alcGetError(ALCdevice *device); + +ALCAPI ALCboolean ALCAPIENTRY alcIsExtensionPresent(ALCdevice *device,ALCubyte *extName); +ALCAPI ALCvoid * ALCAPIENTRY alcGetProcAddress(ALCdevice *device,ALCubyte *funcName); +ALCAPI ALCenum ALCAPIENTRY alcGetEnumValue(ALCdevice *device,ALCubyte *enumName); + +#else /* AL_NO_PROTOTYPES */ + +ALCAPI ALCubyte* ALCAPIENTRY (*alcGetString)(ALCdevice *device,ALCenum param); +ALCAPI ALCvoid ALCAPIENTRY (*alcGetIntegerv)(ALCdevice * device,ALCenum param,ALCsizei size,ALCint *data); + +ALCAPI ALCdevice* ALCAPIENTRY (*alcOpenDevice)(ALubyte *deviceName); +ALCAPI ALCvoid ALCAPIENTRY (*alcCloseDevice)(ALCdevice *device); + +ALCAPI ALCcontext*ALCAPIENTRY (*alcCreateContext)(ALCdevice *device,ALCint *attrList); +ALCAPI ALCboolean ALCAPIENTRY (*alcMakeContextCurrent)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcProcessContext)(ALCcontext *context); +ALCAPI ALCcontext*ALCAPIENTRY (*alcGetCurrentContext)(ALCvoid); +ALCAPI ALCdevice* ALCAPIENTRY (*alcGetContextsDevice)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcSuspendContext)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcDestroyContext)(ALCcontext *context); + +ALCAPI ALCenum ALCAPIENTRY (*alcGetError)(ALCdevice *device); + +ALCAPI ALCboolean ALCAPIENTRY (*alcIsExtensionPresent)(ALCdevice *device,ALCubyte *extName); +ALCAPI ALCvoid * ALCAPIENTRY (*alcGetProcAddress)(ALCdevice *device,ALCubyte *funcName); +ALCAPI ALCenum ALCAPIENTRY (*alcGetEnumValue)(ALCdevice *device,ALCubyte *enumName); + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export off + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Headers/alctypes.h b/src/openal/osx/OpenAL.framework/Versions/A/Headers/alctypes.h new file mode 100644 index 0000000..9374e8e --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Headers/alctypes.h @@ -0,0 +1,189 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _ALCTYPES_H_ +#define _ALCTYPES_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * Portions Copyright (C) 2004 by Apple Computer Inc. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** ALC boolean type. */ +typedef char ALCboolean; + +/** ALC 8bit signed byte. */ +typedef char ALCbyte; + +/** ALC 8bit unsigned byte. */ +typedef unsigned char ALCubyte; + +/** ALC 16bit signed short integer type. */ +typedef short ALCshort; + +/** ALC 16bit unsigned short integer type. */ +typedef unsigned short ALCushort; + +/** ALC 32bit unsigned integer type. */ +typedef unsigned ALCuint; + +/** ALC 32bit signed integer type. */ +typedef int ALCint; + +/** ALC 32bit floating point type. */ +typedef float ALCfloat; + +/** ALC 64bit double point type. */ +typedef double ALCdouble; + +/** ALC 32bit type. */ +typedef unsigned int ALCsizei; + +/** ALC void type */ +typedef void ALCvoid; + +/** ALC enumerations. */ +typedef int ALCenum; + +/* Bad value. */ +#define ALC_INVALID (-1) + +/* Boolean False. */ +#define ALC_FALSE 0 + +/* Boolean True. */ +#define ALC_TRUE 1 + +/** Errors: No Error. */ +#define ALC_NO_ERROR ALC_FALSE + +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 + +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_EXTENSIONS 0x1006 + +#define ALC_FREQUENCY 0x1007 +#define ALC_REFRESH 0x1008 +#define ALC_SYNC 0x1009 + +/** + * The device argument does not name a valid dvice. + */ +#define ALC_INVALID_DEVICE 0xA001 + +/** + * The context argument does not name a valid context. + */ +#define ALC_INVALID_CONTEXT 0xA002 + +/** + * A function was called at inappropriate time, + * or in an inappropriate way, causing an illegal state. + * This can be an incompatible ALenum, object ID, + * and/or function. + */ +#define ALC_INVALID_ENUM 0xA003 + +/** + * Illegal value passed as an argument to an AL call. + * Applies to parameter values, but not to enumerations. + */ +#define ALC_INVALID_VALUE 0xA004 + +/** + * A function could not be completed, + * because there is not enough memory available. + */ +#define ALC_OUT_OF_MEMORY 0xA005 + + +//********************************************************************************* +// OSX Specific Properties +//********************************************************************************* + +/** + * Convert Data When Loading. Default false, currently applies only to monophonic sounds + */ +#define ALC_CONVERT_DATA_UPON_LOADING 0xF001 + +/** + * Render Quality. + */ +#define ALC_SPATIAL_RENDERING_QUALITY 0xF002 + #define ALC_SPATIAL_RENDERING_QUALITY_HIGH 'rqhi' + #define ALC_SPATIAL_RENDERING_QUALITY_LOW 'rdlo' + +/** + * Mixer Output Rate. + */ +#define ALC_MIXER_OUTPUT_RATE 0xF003 + +/** + * Maximum Mixer Busses. + * Set this before opening a new OAL device to indicate how many busses on the mixer + * are desired. Get returns either the current devices bus count value, or the value + * that will be used to open a device + */ +#define ALC_MIXER_MAXIMUM_BUSSES 0xF004 + +/** + * Render Channels. + * Allows a user to force OpenAL to render to stereo, regardless of the audio hardware being used + */ +#define ALC_RENDER_CHANNEL_COUNT 0xF005 + #define ALC_RENDER_CHANNEL_COUNT_STEREO 'rcst' + #define ALC_RENDER_CHANNEL_COUNT_MULTICHANNEL 'rcmc' + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Headers/altypes.h b/src/openal/osx/OpenAL.framework/Versions/A/Headers/altypes.h new file mode 100644 index 0000000..ffdc71b --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Headers/altypes.h @@ -0,0 +1,350 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _ALTYPES_H_ +#define _ALTYPES_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** OpenAL boolean type. */ +typedef char ALboolean; + +/** OpenAL 8bit signed byte. */ +typedef char ALbyte; + +/** OpenAL 8bit unsigned byte. */ +typedef unsigned char ALubyte; + +/** OpenAL 16bit signed short integer type. */ +typedef short ALshort; + +/** OpenAL 16bit unsigned short integer type. */ +typedef unsigned short ALushort; + +/** OpenAL 32bit unsigned integer type. */ +typedef unsigned ALuint; + +/** OpenAL 32bit signed integer type. */ +typedef int ALint; + +/** OpenAL 32bit floating point type. */ +typedef float ALfloat; + +/** OpenAL 64bit double point type. */ +typedef double ALdouble; + +/** OpenAL 32bit type. */ +typedef unsigned int ALsizei; + +/** OpenAL void type */ +typedef void ALvoid; + +/** OpenAL enumerations. */ +typedef int ALenum; + +/* Bad value. */ +#define AL_INVALID (-1) + +/* Disable value. */ +#define AL_NONE 0 + +/* Boolean False. */ +#define AL_FALSE 0 + +/* Boolean True. */ +#define AL_TRUE 1 + +/** + * Indicate the type of AL_SOURCE. + * Sources can be spatialized + */ +#define AL_SOURCE_TYPE 0x200 + +/** Indicate source has absolute coordinates. */ +#define AL_SOURCE_ABSOLUTE 0x201 + +/** Indicate Source has listener relative coordinates. */ +#define AL_SOURCE_RELATIVE 0x202 + +/** + * Directional source, inner cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_INNER_ANGLE 0x1001 + +/** + * Directional source, outer cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_OUTER_ANGLE 0x1002 + +/** + * Specify the pitch to be applied, either at source, + * or on mixer results, at listener. + * Range: [0.5-2.0] + * Default: 1.0 + */ +#define AL_PITCH 0x1003 + +/** + * Specify the current location in three dimensional space. + * OpenAL, like OpenGL, uses a right handed coordinate system, + * where in a frontal default view X (thumb) points right, + * Y points up (index finger), and Z points towards the + * viewer/camera (middle finger). + * To switch from a left handed coordinate system, flip the + * sign on the Z coordinate. + * Listener position is always in the world coordinate system. + */ +#define AL_POSITION 0x1004 + +/** Specify the current direction as forward vector. */ +#define AL_DIRECTION 0x1005 + +/** Specify the current velocity in three dimensional space. */ +#define AL_VELOCITY 0x1006 + +/** + * Indicate whether source has to loop infinite. + * Type: ALboolean + * Range: [AL_TRUE, AL_FALSE] + * Default: AL_FALSE + */ +#define AL_LOOPING 0x1007 + +/** + * Indicate the buffer to provide sound samples. + * Type: ALuint. + * Range: any valid Buffer id. + */ +#define AL_BUFFER 0x1009 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_GAIN 0x100A + +/** + * Indicate minimum source attenuation. + * Type: ALfloat + * Range: [0.0 - 1.0] + */ +#define AL_MIN_GAIN 0x100D + +/** + * Indicate maximum source attenuation. + * Type: ALfloat + * Range: [0.0 - 1.0] + */ +#define AL_MAX_GAIN 0x100E + +/** + * Specify the current orientation. + * Type: ALfv6 (at/up) + * Range: N/A + */ +#define AL_ORIENTATION 0x100F + +/* byte offset into source (in canon format). -1 if source + * is not playing. Don't set this, get this. + * + * Type: ALfloat + * Range: [0.0 - ] + * Default: 1.0 + */ +#define AL_REFERENCE_DISTANCE 0x1020 + + /** + * Indicate the rolloff factor for the source. + * Type: ALfloat + * Range: [0.0 - ] + * Default: 1.0 + */ +#define AL_ROLLOFF_FACTOR 0x1021 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_CONE_OUTER_GAIN 0x1022 + +/** + * Specify the maximum distance. + * Type: ALfloat + * Range: [0.0 - ] + */ +#define AL_MAX_DISTANCE 0x1023 + +/** + * Source state information + */ +#define AL_SOURCE_STATE 0x1010 +#define AL_INITIAL 0x1011 +#define AL_PLAYING 0x1012 +#define AL_PAUSED 0x1013 +#define AL_STOPPED 0x1014 + +/** + * Buffer Queue params + */ +#define AL_BUFFERS_QUEUED 0x1015 +#define AL_BUFFERS_PROCESSED 0x1016 + +/** Sound buffers: format specifier. */ +#define AL_FORMAT_MONO8 0x1100 +#define AL_FORMAT_MONO16 0x1101 +#define AL_FORMAT_STEREO8 0x1102 +#define AL_FORMAT_STEREO16 0x1103 + +/** + * Sound buffers: frequency, in units of Hertz [Hz]. + * This is the number of samples per second. Half of the + * sample frequency marks the maximum significant + * frequency component. + */ +#define AL_FREQUENCY 0x2001 +#define AL_BITS 0x2002 +#define AL_CHANNELS 0x2003 +#define AL_SIZE 0x2004 +#define AL_DATA 0x2005 + +/** + * Buffer state. + * + * Not supported for public use (yet). + */ +#define AL_UNUSED 0x2010 +#define AL_PENDING 0x2011 +#define AL_PROCESSED 0x2012 + +/** Errors: No Error. */ +#define AL_NO_ERROR AL_FALSE + +/** + * Illegal name passed as an argument to an AL call. + */ +#define AL_INVALID_NAME 0xA001 + +/** + * Illegal enum passed as an argument to an AL call. + */ +#define AL_INVALID_ENUM 0xA002 +/** + * Illegal value passed as an argument to an AL call. + * Applies to parameter values, but not to enumerations. + */ +#define AL_INVALID_VALUE 0xA003 + +/** + * A function was called at inappropriate time, + * or in an inappropriate way, causing an illegal state. + * This can be an incompatible ALenum, object ID, + * and/or function. + */ +#define AL_INVALID_OPERATION 0xA004 + +/** + * A function could not be completed, + * because there is not enough memory available. + */ +#define AL_OUT_OF_MEMORY 0xA005 + +/** Context strings: Vendor Name. */ +#define AL_VENDOR 0xB001 +#define AL_VERSION 0xB002 +#define AL_RENDERER 0xB003 +#define AL_EXTENSIONS 0xB004 + +/** Global tweakage. */ + +/** + * Doppler scale. Default 1.0 + */ +#define AL_DOPPLER_FACTOR 0xC000 + +/** + * Doppler velocity. Default 1.0 + */ +#define AL_DOPPLER_VELOCITY 0xC001 + +/** + * Distance model. Default AL_INVERSE_DISTANCE_CLAMPED + */ +#define AL_DISTANCE_MODEL 0xD000 + +/** Distance models. */ + +#define AL_INVERSE_DISTANCE 0xD001 +#define AL_INVERSE_DISTANCE_CLAMPED 0xD002 + + /** + * enables + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Headers/alut.h b/src/openal/osx/OpenAL.framework/Versions/A/Headers/alut.h new file mode 100644 index 0000000..b5b646b --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Headers/alut.h @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef _ALUT_H_ +#define _ALUT_H_ + +#define ALUTAPI +#define ALUTAPIENTRY + +#include "al.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif +#endif + +ALUTAPI ALvoid ALUTAPIENTRY alutInit(ALint *argc,ALbyte **argv); +ALUTAPI ALvoid ALUTAPIENTRY alutExit(ALvoid); +ALUTAPI ALvoid ALUTAPIENTRY alutLoadWAVFile(ALbyte *file,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq); +ALUTAPI ALvoid ALUTAPIENTRY alutLoadWAVMemory(ALbyte *memory,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq); +ALUTAPI ALvoid ALUTAPIENTRY alutUnloadWAV(ALenum format,ALvoid *data,ALsizei size,ALsizei freq); + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export off + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/openal/osx/OpenAL.framework/Versions/A/OpenAL b/src/openal/osx/OpenAL.framework/Versions/A/OpenAL new file mode 100644 index 0000000000000000000000000000000000000000..b03b04f17d6aaf2492c27e9eace8a9f0b377d3f6 GIT binary patch literal 256696 zcmeFa4R}=LnehLd$pi)scF?$QP@ogRzM)2mE&l%#6J$ohb||2+4ZE@BW5$Fw)Sy8_ zH?$LTCIbi+-<>r0E-GN6fY@pm_qF??0!{?l;Kw#p>_UYa1T3`B#)>Vr!TJC0=bZT> z!Q$?_+xNQuo9lXV=6pOK_xJtW&wZZr(WyWFi&E-Riu?_sT%we#pW{OmL#do`^X64e zt*+uM^UvY4&aC55PI4(1%f)%~8s|4P$}j70V10LyGPbNyilb7FhdGqV$eeIsm!ujWIqg$zIeYC+b4NIL(SE&U)?!F6* zEUsz1`yPFM&Nv*Nt@4~o9`#A~zj^a&=VvrA5Fg39))5{Y_-FB0&E28;d3On`4Zx|c z`{{gp94VaolvDGlv}nVUYJ6ZDlY6-NS>@2jOq{0b@PqL=s^`tC@K^aWE<1djlyJ*F zDkU-^ZMZ1(T^ni)$uH|~@cPDnab$ox2cHOI8POV~TL?7Uj^#%yEdmdD?2@ z>oqm=<~P;MZ)lv?7((999mka8O68Z+2FI*%NOk=;ke=tZQC)VisL8 z`Rd#@=5C{EzjDYiJq}-%jNEbWy?R8}zk#sa5vOX*ek3KIoTmq3{r1kAH)Gl@)2CKU zOX^=o19wcS%kf(xYeI))J!xw=Wj3YY=0%?!s_$^uH@|6q&0-K+KYw0BsIhLsb=OV! zYf%M%clSMa-aFyehWU5-XI!V1gIt;LH}mhgcL9<8Nm?jF49)a_*3)%+;9S=xRWHeuW!)Al##*U;nO z4O{o5Rcp+39Hrfhjcdj#^*=whT^ZD0-@W)Q1QZMm-hTEox*Lrfkw)nU`^K-2AHT*+ z)!ld3-*I=-;Jsu%k?tdj|H2FHW7b^@8b783xh<^=ZIk2oC^u&xKYqc%m*?N}%>^~{ z?;X7P%qOG=s}He);OO=D)fbE$-N)j4@0n1);0`GkD_wWL*n04Y(f^2fRP@X0H9Ma=e3VaPD~D!@0o`33Ajb$J@eV=+(ES zF4vJG<+z=He?@UC$Dz|4|CaJ!fB%Jn|H8n3Vc@?o@Lw4C|2+o6F2|;uA5*!_D;%4` zuAEKHW=+$iRmu@rb^Ye%l{uTjc{%p^gq~YWr>FUWnx^TA#A~T~bymGecT(rh>l>RF zc|0prp==DN?7x9=o?(4kboxcrAL zPyD(`U;w_Elu-dAzc66@)%DbE;&-HF=ldLo&NoA<3A{DD2<+iH!6ofl_3}761U~_M zCXG+<^in8sG6pULU*N(g_%f#e_qVTXD#lAoj*BtfP##}!S2^Pg#uqf2%dXF(t?(qz z=W8dKp@zw3XmRMX3!7>lSlAR=(NHfmE~8&yQf(pSYxAp$HbVv4BWebvGGIL0Ou1d~ z%TdE2F!DRMyjK~)BaTFPOo8%u+IVRZyo`9(eDr#kS0xhA16&wIxDHz}hm#qkJ!@yt245+TT)n-fyddfU#v+IDF+2U#r@0a$epWT3jBlonPJ& zY6$-N?#01ZzUB#T*5eNf?!9r!pVT;YZobR#EXY1!_-+WXR>ppswD!o#b@Jw=i-i&9)Wjs^FGa>*YL6EBDGt zk0DR1uFq?aCVA+xMJ+XNH3d}eDeAb2!5_~{A0=M%;+Gk>K_7C`Z^?poDj6%i(V|R+(W{RO(SquQ{{_*c>)&K47b8D^wNqdni-x z0>-Ci32&RruB#FnitNGjlgfMVy$6}{1>e1^R^&+2_pqucRdWBCkphqO>3iIe@r!I5 z0psbzy6slgGf7$XLqoFbw{(lVKK-ycSE=Gwjc1=ZM!6;^#{Yjk(mya%X2P{4okOu z|MloW_-4HxYu<#>%1OPvqR}dce&l)4E$*UqD%ZD6*UJ@ojz%m!-U2;egpNtx(E3?) zPntip?kPitrYAlcUKT`$-iJJaXZWND+`DW#s+RkYGzpJlUur2nN(CiAnL(+fR8gua zvnj#$knr>OPoZm&d&3)9uYBcZeM9$6kj|`GDD9W}loJ>{Vjtj}YwZ3+CanIHDr)ak zLD~yoJ7%K?s?Yu)u|w;t-n&}B^&?9Wqg3j8hHv&SjVYq+`hyo@ugSMsi( z{5CSOzIZLqsP-pB20k@1;EdXjUNSQ&eDjqW{{W9Odvu zR6g>OW6ptg&1wd)jFaEx@q226yXx;2+6zDWTcN$cH(JfWJ}=_8Gc>er0q{14TfGWA z^nMe%QuK+~g$8i-psg?KOuw2v!J!P_3u?Ai=i;q` zQ(fn*ngI=*0b}zV``(MGdxsgXbNcz&F#SR3uT1I6#4vSqAuF0 zHhT+IN6Y4H9jxWdNc2MU6dgoQLW5@dC@|Mye>#BekQ(Rh6?#6oQFNEDRn=N!$T=nV zMGm}YRi!n)Cp)Ds&)#l7>y>kGBlWKqo#<;#^p($pSDNAB$FDvucx%}t_I=Z_Y`r_| zkfnFWPn3Qvy_>h2dak7Xp{_3m{p1Lbtm0k?G(oqkiv>S=Y=>0?v}m+=db2S>#_$OE zr=HoR8p6&J;iJm3L!46|UT@5--|}P8$D8&@pZ-noA-{Ft&;3DHETCMA zfX5^J)DkYdjy^r%ZCB*C&sR?5(GyvxDx1TX=9jHid2J8S{v0(byuQ-sUz(`Ze(5?b z(<7pW&4Vs@5S~lw`j?XY_bp2g2v0`voxz>9+2Nc@v2*2-8_~buFT5gfU9)3KQ*+%H z^NXFx5xUX*zH$~X1J30}wYLTx?NE6w)bp=N>=QhNM@bvTsPcGyLwWF?MbcIWJn#!MTQ-mu2mV9Di% zVJ*=k_;{)uS{e8Ud4d=8gGsCLl%~K5()E>&#Y}9i%f%;fH7>A=(m=-Y1d(| zEj=RkI+KR}#POKu5atD<%g;;0vOh~h2Q(}iKtsk69IQmip8+;=e*aPnl28@hmw(&uHB&HanuK+eF7lu+`8|=!~770W4L} z)1oE%I$%8hy6r>T^vj(p@?tI@cTjw2a9SU^Qu_zFDf+GVUne@vlLW*f`xnql z=pq>x{b3g#U##afLKFNoWGt@*IN{rgX_`0>l$t`5ar@io%c9B9u0d%69Yn6oCzNMY zg{6POcZ|PI%i4~Z zmyfNGF|&JF6z5<8*vL=DE%28-c%gh;qn$ z0oqF3VGDXAA7Anmw95+^&i4XF?(O*UdXABt8{6}X<8K{KoJsV$Vp3OlTXRX3(pJ(moDtWF*{LrKwk!Rso=8caX zu%9WGwvzN6NW;h4fskF;)K z9&r3s@SU9Fm3Q1z6Yh3w3O>HDMsOc?x|NO}b>TBW1ED26>&5r02@SosG3@lp+_gyL zCtw)sfJ;e?C%CdB$NY|GsnDbi-3HB@BQ?y0mg;#=pxmcpJQcwi_cpdTgQ8ROkt1Jm zu44YC^5K(l=1%1+TCN<#5(>=27OzPBDSSLfe5Q^h|LALV8NRN}bvskMvqi>D93=qW znvlhM+m99h<$%UbYW(Vuj9+X;a5>|@|I=DFi4!sYob59ALN4p4Cr&?`>|4)|Gy6sc zq;Kg%cl=SP?Y5#{gzXzX5%ZcuPJ#chm z59o(+6hTL8o-Q$_6&4P#xwXMN>hBiXL8}6M_(jl11$Wmi2s?|^3@g56Xg#%=xj|>* z;9H%E<3jJZx{!77B{Hb-C9td;E3lY}4tETAToQ|dMveV1yNZPOUj5OtEBc=p_CkMQ z5qfFui1NqqZEBy5mA9~(7*W6L9{cQ?{)z(bJ^E(6nIn8P&RST|HI_m8Y7gBQYob6HdLfHi+?$=TV| zvfotL$}>qzJhjT>?Z9lrQIO&9`sXaWzE8~(}P9WI2ZLiZ8d7tfhn{L z?40Lu?xqw_#HNYfSHW*|GQ9PgCXpxPvNrti*rxE$ zKi?ESR@TJ1k5WN7Z=8*|YdO4z{x>F;tydGnyFT9}^O5O^vj-%OL`-2HygX`x`0zWJ z(-DUYf(wcB5sRBB_!FElp4l?T!lngyE{`!@0~`_qx_HuL=*U?1JoU8r+(v|F%kD#G zn{|q@&6xEC_l-Q~nX&uxt)hdth8`gncvw{;hxz?JHDnw8uS4;1K`V+o{Ze@o<-1#|IHz&3ej*BSlZsUM2C|8D>b$ zZmGWPf0`eWlSC$0^CKgFHY@7B5jdU5emV91;sb=gj&E?E)=%1(VLnP5XI`)Ib~xL) zKRKxv9vzt%P=9*1_4f|cfoI{0|T-hY;l?72cV#ixq55vY`&muCnvh5rG z{g63=k9PE*TqpLa+LtS**s<6~Y!~-pHQ2608rQPc=M#RQtt#({%MM1L#IGGq3}f4` zo7%uT?H7t)*fQ?bChsek?UH_4Hp*P($RXFyhChL!1zNcJW?sYi7j?;KiC!`w%dsKCEgVE&2d1o2Uz zp-*6l9=y~E3}TN8z&CyT{%Z6Zj3oUrpB6lXluz4m@B#dDgkC~(;-Nn7&H_BXBUmvG$NkD^)B6~Iqo1~NQZIVtM0po)oYC3fcLMQZF@w!Ypoxa{1hfZzJ6~I>oi>=vH@K%fm;S+Pl;SjR*WVcxnUuIy`EC7tW(0eJ|K4N;nSU#4F7T&^&V=O@`kLdS#rE0J5HzP7)f3HnB^0jqb@0tvBDpfVPuPb zi5DAN#b-8aFR@}hDY^dL%rw6k`NgIs!^FgWS~d-3Mt^ikKyU}%OiK>`P$+UJ@Mu}b z*0wmbEV@i&UFQ;vh94eR<3#Qy*4@7Cl9Kk#mrO*SdM)19G~1`?Oxw|)(?_RTimWfS zWqo^OE`9h?zICu=7{2u**oA5EDRxD8AKx=Ca+PvM$0G;OfpMFDy^ZmmkDlEP~JyKu-jywyFTUQDU z8onDZkrAONFenpcjMD-FcNZ{el)QJ_v~p$kfx)7j-oKbeu1xtNX6S z?^2WEaW6#8u;0d0~FJ5WMfbaryLZ?}W zl0fdV@zH0~-;Ry8aTgRMI$0FpK#9}1hA`9+w=pWs6QDp|q zw%z{CLZKJ7Hfayef${xW7tRa6DfGtAwFkpzSaR_me1FSCpJB_OyNgHcMt8h?T;B=JGMz^qV-V=zB#_MnKN_?xX^qk z^Rju|H7wrO;=2FHT`bLjp%|-kXcVs1V+^K~ccUneI={%Q!diz8`o%}G`Wy@$c`q{>v zmeCbfoJ4SxiW5#5vPE!+?obk^n%Sz?9#!+$=}oc6?YLogMm^dre;hw(>X0V0l{(C| zXHX}0M|O;HM=0@{$b!fOu|+Kl;s;Gn{JL4mY43By(|o54s-0by+v5 zqtsIxDe`QXVp5h;R!~+^VwBaCH54C3)>+n4I@%+|7>&Ngk&xPQU+tq!#3zHua4j-= z6rZ36e(VhxE8oKw^~hMb9w#w))NTQB*4+McIykke@6pkW{me)-@zIB3YQ;SB-fw+Aj92fy?W#KDk83kSa{ zoUqR0@UK^O{&mE4!3VzMC}3_Z9_`6Nzf~8n^Emy>$qhhMbll3JTzj5+ctt|hsatP< zqm}3L`#r>?3NcB<+jy>|KS(^vS0FK}I&f0I&}YgVp}=&gDvoZB<2Z_sS`r&|F3$h- zCigOM{>!szod5DYtqW^9oYND3dM2Qb&m@MihIwX8^i=bTY@52{aN?lG`AZhx;YWD9 zQyp4Yi9a8H1U+uW)@{Gy-ODrZUK|z?lH^f@ z*Ah!#nplw@o8gq0ec3e*)#81%>2KG$Zw!y_xy~Nb7Fj2-$Fu>vw!!kTPlZ}PVoVX` z`|e?E{Hm?WJ7d6@@|iCwi46i9cE0uY@b;eTq^;fI61LUkP8>1`=OXTd93I?JAaYP})$zdNx@>^?ly5Psg$S>Z#( zmH$}Qgq(-md4t%0t*6BH=Xg}vPD@Xf#la8vkgI&lXXzYm=X%*RJo2E>PGU)- zvw*3OIO1$_0|ciwuO3-o%Qf&M4iXP75&1NSCAJltx@~vq?B9HJ%|*)aKdhFDPpD~v ztrdSO`~bLKPEPm^Vn>?aqWGxFtp(0Ft0d!I%8g8 z&ynA`T;nurE~QRea&+{Afi(WrZ5ciBRB$ksY-00mfuINO~SzMLfh5= z!|#db9%%*-vhJ3iC%~J+m!iuAS4sag?HeV6vc`dqsusWK#$JiroGlW5w|&&QqIHJ$ zf3W4^$J_jKc)aZ^v1X>KB#t67YC6xlkxxCRw*1(4hl#Bz^Fa^4ZF3z(dR`s9nfc-) zw3(Vu^uFnhYoE%Qj;*0DJ2b6+C}V5+Z}?Pe)YIChDt{H9>THR`+9V!SdJ}StFD*V( zKusQY;~(%z4x&G)BmN0;?j#RvALFgJ@mU#AhYtxprzbvGC;pe_Ib7li1@P@7#KOos z*RgYj?g4)9QspGy{0wc3v(^aT8D^~!?mIQK+jjSElt4O-8sNqbACBofV6 zjdaT#1sF@xKJMFXY1w>xS(+E#_cJ#->;-r5gC(01yRi8oDUKZ6Z_%toEyus<@iv@rZ@C+Sh`%5p9Skt7gt^vKK`Zr@Co5*r$^zpP}Wk8DrKBJPeFoG z_r~#?ch^(r=c{ue*Ei74Qu=n8mMOW`1w1N}pE|TQS07AG(YG{^|ZtyVMN- zWv#x)jXGK9>7M=fOZy*ls~NjK-|E|BRAtn=oq83MUs8^E^WT>zKFu1=8r7Y?|ApP$ zAD2E4po=rdF#}y-I48eN{q=ue-v4P2ijw#Jq`i%Q@9TdId0nQO()FiZ4`1yQof_XI z{go(mUcST{^^R3PJd$ywMSIvAg( zRE^`Dwdiluv##GNyizsr`YnsOUS8^f2E=!!Cw{Y?^WwB0@$+M8UG?(=XGmY;`aOMeJ}pT*D^ISeG@Ew& zuOCReKODF8&08accG7OYuP-Lg^#{qdQ!}R0U%i$ixyC*F`d+SEdM4}o%sE`opxr5< zeFp6gj8D_TxEc~y+2Oq2)!zkbPT^J@@Bl2 zwy*E!K{L~~uV%nFIR4MkjNGSH7JdF7>pkCAV>7ZPaf1fvQ!rt)gPbTMj9ke(9IOW! zPji15_q&CD#p~Qd@n@dq`sxXz-B%%pb^Km&cb%!qd#99$9wla6E9>9gSFfxPT68m? z>Yw652f7OaYGxJieR}EV@z| z{!XL%?y#vQ2d9*whz| zIO@&^hI}iY|2LB+lee#W_$&~O`3L&tSHiOy@xGreOY`W@ zmS~wdBDUaHHw4tr-Xt!u6~B0kUf*|R$6b>7p=mqNw+esRnma#wK0DCYn5|0=wEa20 zWZq%>K>OwhJhmMeXZd-bKOsJlFSY);uXLc?zrVu5!@rL7KAQBC49QtiRjcrmzO4>6 znx`LkciZyulJC#_cKA4>0e3VuxL-Zp;k$~f=+cl_Q*-Pb5@<^MdWgHaesI+_E~1aS5dJy$EEEI z-(X+L|1awh+1NL~<_Y14<5%;0H){sk-n4YHW`JJ$_*lws!eQ$@sw}@C{1)+)4|O~x zV?OYkbLIp8y5z$7;-^!y_=4DXKwZDrwhy)~|LGv>fA86H^rm6^5MHgvM&m;;Cj&3U zMe!k)>%4~%X6lwC(j7a9+Z11KV9?;UC-Hs*Ym^UGO~by{RxZd?+@27x+8P zvzPPiew$Wqo|WqddG@b(w%fM%m0asa@2u-5g;;EN_xQ+Ke(U+0$W(v!8bXkFI#X*1 zCVBkS4^aP2j-s!o>UOZb-Jhi$lXl3JrN8`sleGxm*SG5w^v7wZqh!o}d<3r5)+R#{uz?6SHl5X{O>wEy?)EiHdC5{OJO72TDJv z2E^ww;v=@5eEo(DpWC*R#a-fa`%-rD^%1sg8tFB&lhu}N{^UJyQz(2zOxU!1p_$hg z=eRG0hU1E&?n@^`@r#zRmXzbZ3?3Q9c|QChGFxdqAMy)st>5!RHoeyGYJ9Iud-E^3 zX?(u^p*;@*pND@KQ2#P3wbmwan7}#y#{hgD&pSUpe|b6^pL<7MI6hxJHV~iJEXtO< zSGxw{^Vcp5pZ|hiI|uuj3HR&6GvGGTaQAKxsMqIwGPr-4e}1^%?azk0r|`nze&y6a zxIeoj8}3*3421hvZVT?$v0L*>v*CX2@-*D94NJrQ_U?dsZT=^N``u9=1Gh69?w^dm zaJct;Fc9wXk!<DfdADD_v8nvt9#2jn6vQS z4)LxTE$fdo$vTk4mRYa8T;A&m%R4=$-tQ%2KK#naH0@s*o~HewJpuK~0%)I(Wt=PP z17JVu`FPlmy0Yp2x;6j3AlQHW!Qim}k~Q;J-uxKY7n~pVJ)_gG?-`MX{ov~XwP)cc zgZ;?3kB9w;%>6TB>#tpV;dKAehl9iZF1mfsfscWG{`q0=E=#e>1F z*8taz9c&HIC;nylg>+m>x&ACG6<$?!u)8RZ>b74*1gfEB% zy)ydaa-b!ShM!>j61z@htwm{{bKGLq)Wnyt*Rgl))_7ALhP{p*E<=}u*Xs4BRT8Tf zxXV6!*c0-04}$9&hCg=yk4G8Dj{KWK6h*lRr&7Xa8YO znW0wvZJGPV=z8uwYUMAqyGpuu&t+>rv;x^mtT!;>uv-iXQyBD#Z-A#-oF6-IJ`~-=ugyZTn%pi?BB

Zi2=29ugDbePP}fkJ{Gq;#riMLdKK%x;KI8xwf>8Lzq7N{ zO#12niqF6EEPlGSHKreY+P-?YGNN-vWiLkjiY&a_I5&~A0>7zNmp3O8-kb@EA{Me( z<5?04EF$N7o_?29=lYnfy&drTOe_9%mH3tA zr9R-B(vdSg@iUq0WZ2x7H>UN^%MX_Zc}}nO{A^Z0z5D?=KIwJN73cWZtP=>_vPJ-3 z@g|Qe79V?0mG~aArYq}mzK*1xh<7F9kMv(FG05odgHNaCHE-LxXvjgGM=U&ttiyM^ z+s?&@?32YDPv`Sz`9&Em_(g|>i27hWW<_HrOyX zBw-dkEbkv@!+u~@Htfja1;f65k*4pE%=bMG+VqXz2JEuV>9*H6(XYx29!3cs;=`1u z=SqFVZ&3yD1*)hA8y26YirEuW?w9mjVaGY3LrCN6r_euBZ|tz&e~|ddj!>FUema`- z?cmGu)xpgGd=21{{f!^bBl~A(5Hzl03rtEWC|UPVh4jZ>;TVyp57Lk!t7~ue^J~b=NRezE|p;P`)05Rq;KlqUWH> zi$A3TJ=@^Tm1;)MS{+Nyh(Y$S-js>!?M9k6w|$Vtbr18%?YDg_Z+6x{McpF1u6w*5Bo50dM*)*9{T z1@u)l@cJ0>KQeIt6YPH`?qAG^|GtY2BSaMGl;q^h+Xb158o94&! z{F`;zINz~Scs_;m481rI=g-f|zmd0L`@GdCSp5ezluk-)7#xQid(r~}_5O_%Ee{?2J4(huM;LD#%PNS^Rp6C6hpY~+K{o;EUu8aS9``~cD zd6coP)4u07;`;q2Y*&Hsdd+KbVnsojzXB_^YM9oQE{+8~qt3NauUdZHQgE|Lp8wx_ zlDLuiCg;z~J5SRSuPy;^BUx+LHeK%l0Uq>PooIi>;Bi~^l+biqFKvC z=Xrz7QI@;*k@scY|N5N$+#kmM`Jw~${YPBS5r?<#*UdS~{foFi)oP#mt*+Jhyw?2% zbNcM|ueRG~uSr)U^K9$>H|CtN+t>0<`@HYrs$!mH-M@3L=+VmpM%6K^eOdc)O`PueWxterKQi%dGa@ zykjzSUmAaN<-JSrS2f>mpLb7&K9|Pd+-mL*=l)c?{ao5#oyOnXI=lYWcKhUc4Q)*0 zZ?3%e0{yCT?e@t_8CsRb-&~XX;P=bN?DmJz{=_u?=C0yC^!xH|yZwu3KR*M1tF8XO zywPrdxPSSO(;4^^KMnZ4yv%O@68|GZ_GRGjY3_fD`}6Jg@kNF_XXEd$z~45(Gxw+3 z?R#i{bq4a-U1%Z=TPtKhFF$?(eqRXMd>Wx%p}Q&0A~rf7?c@ec3C@c{&4s zTeuJYZ(C-!5C1#&W#CWr2mF28e5-vA?K_{#z@N|;{=03e)jscLJ>p!Qfxm;?2Y?_`~|miAN_snM!WrC{#Hj)|K)+dU6%aby3A@{^uJ?Y2L8l8 zz<;;Smw1)*5zOg8M?1Epc`Vp#KjRl)Px=Y&>_AUDR;BqUD0Tooy>+zMUFj>7(~(18 zb!k{bqU*ucEgy>SAbrVu5A;=$h9@NQOgp!{S!!B+g`yp~^yRU6Vz{?9RARw&%d;6U zsWeO>o2Iw4Wy53~Ov4lsy22N?EX;<<*qVkZRA-Mx&+F1K85#T^3iAy7cgxk;Fc}RQ zFvaX=jM6Naj2RhY-oP`+(M-LsrSuhyZh@EiR>#}6t@~O?WEokS`Ft9#E$}n%)qUE2 zrY^LNXW*Ba56PTG)9T0;d9SiJEz_ZHo`L6PE|M69hHLB=dFOIln(spUc?P&<&J5vZ{PFi%Bso(3-eNBl=KBm_HQs4WvJCyxQF6X+h;1fOYpsGyM;*Nao zL5mrO({T03d!fZ?xb7(88NS~#V|!WLf~#g{k3-(~6Q3wjXz#6wFO~T&h}R?ri*Frl z?Qph4-AdjKmOWDK80r@Pdc);suUz2)_BS+IqU6=DS3O*F3p~KG6<@I5_9Y$RsE!A2 z6IzF+v3|gtOpH^W*sG}^4klaJIn>$5_c$uHyq|Sx_$ii*nL&- zJ74G#%;_NaB>t>7sOR9-%t0&lKCY}0WaZr9_v_fyW6B|ZeRS84UF9w8W6L~8<~2fx zokwPGNzHFwvg5G1Kk5@65ntbm!)_VIyNBtxT$jY|^k~H-(2o5|>V7#HH_l zg&4pyKfC}xzkKHjcu%Q`918>&>3sL^e~|L&JH)5|{xK{6@TH9b_4HKJjyuWRi8;C6 zV>7wGso+X*BSm;HyqSG^Tl);*zi^JdPj6>(uL1U4CLc+71AMWc%I@Amv5V{Zf_KIN<4e#5P1M(9^*+EIVU=}#~Q!yZFr_84|NJJ=XfFxv2D z!kv0YX#ijBI({x+yn5uT62G?Qy(zxfbugPRzURMizSz8daK8BQN$6LA9^EH0D6&!{>ul2#oBaINd5+T)8yE9CFd5pluiGj7 zTQ2ip=D^@`o7~HgtIZkl@6BymN8$(Y9P#hXr;w{u{5wpHOK_ONUj{E(@spR={`vUH z%dzY@!jm6fc>HA3;=ysayCa~Unq}hj-c-)E~Arq``0so5@{3%{~G6Vi6 z+iac#{uhD&$y3A%bUa)0(Pnb@Y(7fiAjw0I*=qp1x>D;Nf^*Te&?#fxqwAd5|0WH; z$cHKG9-X3Ju}6dI*Nx|af7e=QHOtbk!b9vS#=fM&LuGM^r3eoNlRQ*n*+JnUZ3iX3 zGAg=K@+;X}ZYAH9!cK7<#WA4ecC6s-g-M)yMIQ&$rUjZ0pP;YN1@dl#TX-$Ht}Dhf zNnHsJUl_)-Ps#hG(TE*$S&`mTM)a_ZMdSy#U9nntdrC)+#9F2&o_`&DXgwPaSbQhX zoll212lRKd(Cs;!WS{QxSFoX9i@Wvn$$BbjKc8<152~+ctr)1UpRY*K8rmmg=C;q$ zA^SN)jXOSJVmKM90R*&Z##jR;X~m`YftyB9lVc`bv?Pil)z70vNquq z*^ze2eUnY`N7>6)u|15V+x5vM%ybp1?Mvf2q$4 zvYazMdGA%$0`V;;icSrv|JN{iR$|8FJg`pu^f7sVM$1k}WCwn1P3-5}6)y7#dqbGC zvDPc^J9m`%*4J0py@ApMA-s^8@k-e;5NbgG# znF}7jL+<%2!1X~uG@QZ7LV4lpKtAdHq71!?SGG#u}S_(q=tPM z)>g~D9QCZX?}x5EFUqqVW~7zcFUL+f@?g9UAssb%}1lSE)uW@&f9gKh*nE zf7(X}#o%BAl@N5V7gV0NKe~e>; z>@(X=J`eAFnb5IX2u~+Fes7_?E!j?_o4Yw9(SY^)^!K0Qfb$JAO z>wQG!eLlV^HdXvYEuX><%pH7>B=$ke6wc2_%Q$+c-{BJ6>GAZ8P2utrj3@s+tuNTq z#L^d=e<=Qj9#5x?r$~>7F{j2OIyEJG8N9`K3eFi%!6(O0e(0R>T>Wnu&)E+BUhWo| z!^Fp`iDHjLUiytX!B?Q)Qy1I&gnSpL^m(y8-8HugeR6w%PgQlZopWU7*s;dTiUkt3^w~O!e_OD`ozFcV=0e-zUe#?Q$*ApcV z;R$jOu!aaLkk!b9<*8RN=(C!4%i{G8s0(o7^uJuc-{?h$S4)tUD7 zMhX1xY@2I%+io(dVq=WiJvSMRv5yRswrJO2uXn>qDdw4pvGqnd#mD_1rIu1hsi!nh znz$F1@4#_%#de@e*Bf~hHzl7^Kp92xP{vV;u;a3Szu0vA@y%i<1($VV+p*u`J3FJ; zjrO(RXb0aURzG+z{rr>Uk?QB=+eOLe^Wad^Ty%$GnGmbf%sE zfy|lKdON5m?O<=9iF{8>@ogBV?Mt|#+-v_f-wBy3`|}$r{uRDAJ=bW6U%@wb#~6*# z8}V62;$a}o4vOS7?W4~40?a+1ziHGK8GIy>h8AGx&CJG-PZS|)q}aj2b!>n4LUAh?>}tm zlMFv~g=3TCTg&=ySybh*2W79|iFe3-8drW*Q8q_a(t-$$J$NYbFOt+LC-h$v+mjgl;C^5+I&e zi9H^veOtf&zD+RP>JvYsIC3+4KTF&%gl|SJc;q{3c4<9(vVKF&jui52q4;S}4YP8Y z^=bA#?n=8lyDBi|IAgl{djy%HM4 z#6J<(z+IkxH{SA3x0vs$+1{bhU$rq3*T3r@|QMYfWOUjJfzq?YDWf-Z6+V z(zXTLCVQ{@@6VC%=mgYVo;`CI0Z*M(-SZ{M$0+4j)$3HGlw~t(dL@SSNV4CMU032=p&3n#$@t6WadA^%@?uV zCiLa|63lB9ddu1RC#Amo2fm~7sN}%2k39O-9zXKEN%V*(eZKKMOSgWHoEcRt&qn!9 zN^t@GJ+3OmCzZKagl{e-d0d|3dWEc4KtuWMi`MYWqgl`Bxq`P@5uf;4A$9WY-4IoF1>pR6yOi$Ks=vGwt)|qkf}^y+%EmdGXb3@Boumvseg@3T`*1o0=-S)W1dfBV=OpJcdz>7|^p6{SxGsSlc z7!$9hUlkjpW<<{uw-Vfhx2JI>^CqE#*j$NY42CD>X`cj7!T!h@k-L2G5)|55`REh; z@VC~xsn|up^0WUF#uUyNQ#d`Q>z?@wj>*gzlbJE5k>?vzlE<|V1s#Rg#qZCc;r}UK zZxCK*y|WydIRhOVCWsAt#3)9;yJQYve*?!8zt3nCevFSX>e=&^eQ^}Auo&`eC~ur^ zs&}G`@y&%tv6Us^<$TNHN&UULmrD)t*(Zz?9fDl-AUCeAxkkPDb(JS;oanl8S^q`$ z8utvFQZz~VJze)14gKA&aXUXU8s*zar>;3OuK$b1KlQJ4eD18zUC^&Q1?UeK<7wzY zmg4l^#d9Z*7Zsg+W&9scE-M+=@A1R}MsX)=Yex64I6D5mJC9}hJK$-Lr;>1e~-R4oEJvJYiHRs+&2gf<@-hk^l02O-d`&; zgmw-6C)7BhtEOR#Yh3^L9ryHq$M{{puc+Xx&r{Ij7hhK8b*(oVp+!}^3py4;?_niH zCx7+18&7WXLdPQL_}K%Rjt}@}TXgh?`~Ak}`h)O7PNnJzS#(VF7F3=Mo+;SNxwLy> zbiAhd{{lMJ+jN|0)3M&Be@+I+cl|lq>DX7={&nz9UDIIp@00im%zKK_-*wC{ zBu0Naxpoo$WtrulbS93!)RpMfd#z!2MTg7YYNEr%{%Si+e*U22IDFxD*c(&^GVfv! z--pDZVqf)N5IyJBT-= z<~S0s(DNMsKsxU{Yt*LZIy-re@BJj_I?R8xpDuo#`0BFmFE~%G8KmY||F(YX{zCl* z|3dwS|3dwmf1!RG{zCmW{)PIj|F`z5?L%6IIwUrs=y!$~Ps+de3ysHPk7t~XpOh~#U_9vee+Pe*d}99i_uG?+=k4=9)}DC(Lj9Ki zTl@WY&~^5C`#hiA|GW5z7)&xAdajRfzWxF>-!h+(brSJO&}-G^cGjIGHYu@J@jZ>S zPlC>?;(A4Ckl1|%mQZG$WKN_6H}FX zOmfY4sCPg2Pn0#0dr&273z2EW>}!bYU*uNJ(^y~ft14MrAkJRV^6C}&`ujdMjFB4r zpa;@^5IH&4IuS8Unad^fawK<5(=d{T(VSw;mT?q2+`RLoma@iHO{}|G;FVk}{3};k z9QY4sz`fmqn|!B1VU7%A9f388@CwF0Y)TWcl1kRI_1GtHkF^9HtBy>e{qLm5EwRLi zEpvL@_;1>GJI=U`L@#4V=3coqZQf$6K8rRoMj5MoQv^T0LcXP*^b4aeTH}>CwYflO z#9GuHnnrO=qgH#otT=>QsUJ~sp^>cfmJ!bhukG{sH|RC0dT9&V>F<6J$5!TM`tMAx zbBX_WlL%vLEjp_l`aq?KlxtKL7dDbs$RR3eX!e7r`dwQHPlb_3&J0eqAQ@e;YwOq#CskTHTtUX247V180tv%^I zg4W6!DRNgKYcl$qIk~dNr1nrsq6Rb}Zcx02XL#3M)+(Z7f$!mL`Uh0Q=x9h)=x=S? zd?MdPk1#(-(vz{CAx_cvB0Q0@%Ng=3vP+#x)_!!p6EdvK-N+Mg1G6t+ta@G6erd-i zaw@rZA_MS(;+Unzk@~$dmM47O7QM(gJ$FysOFllHnp+NN|6|6f$)s@ zYsgzez*u@YG_54AtfZfCPKn?;h#YN>b{QRdkA>yNHpyks-}38FMeSY2hW0MT*##_J zz|sXQ)S+a4Td;xi#(**ML?oo@CtR!yA34(WH9Od(JnaVWshIT*-G!<$Xx1!5XE~v> zQR-3G5ZCs^I@K=fKPUY%?$^BVEjoNF-x1t^jBZ1xbVJjg{p<ls?L7${9*Oa4L$ELeAyL z!e}9I^Zl;ri6#E%Y{SZ72pO9h(`IkTaBDgk4=jpA3^(KLjmW(YRm{DLwur$#@&-Iy za3JD0O7s}I3?KJAd>4^5a06OAu-JSHSq&Mc@Eg}+Wy{GM=`u>%9~GYB7&0a@o^p7^ z$GMyHe9j9fqqyeb=#662+Wm&)Mpv=sI=c+GrM-QnGi}MDwdgG2=3Psl6w^ zx!4<5D!9wu`z8uJ%O0w;_-M(jvKLjgtb>9pof8H>R+xWM)xhFP>OAa0RjCjC6;c%u z^mR+?ktS@uYRRis%`0n~+9JFfMNU-|`&#-%a9U8UT88axLOx<*%fjm`)6dTtoAF6{E+oM-ud!DC{(cQA9)h+ZdGtif#u~C+7c}~V9 z_M^;0T@$##udIj1I+Hf>{*XBvoEM_^{N#tObPK&o@Qbv4;alO7Q`(?&8#>9aN#-|1 z;H8Mz#rtPYxB~h;qVoQBgZsvoaeJ?`_9oPJl3ZVG3w$6xo8+|1cfDvopzTWy{bsN0 zmqfeZhcTKTYUPEV&sunE+$itQwaYm;F;@w`{_a7k z7f|0^WIexN7BF{Xr{ZF#l00SS=k9xpc)HjPiKmObkl2*i6>#JtXQvN5y3A#2BJzPv z&0P~v!$aiXR+h!-ubHwF{XL`ro0S`GKd545LH7R>+r}Q_@|^HBvKo`|MMm@dad_vj z*uG_ZZ6C#l{v(eD9|gauo!pMdY*Oo~>*d>fu~C?Yb`mVVhUYc*=3mrilOHY_{xYk8|>_>RLTb1n~Z$SJ_ z#ynv(%EA6l&}48OQ1@Q1W#~|c(_Gu`PV3=sUQRxO)BLq^My^3o9Z@%ir(KaB9_IG> zOjXo8%?&)CN>sU?xT%?MszvbsO2@ke4)bP}I4yOl7nJXZBd9?302 zwtC=0Yv0Xa3g%b}W}O$4zrZ|5&eV`Z!pC<;fY%AUW)0$f5eiBIuK6{jML+ z^Dgqi&P0Et@^&6%ts1`Exy@+o3LEujO}^E_K0@Hsb8^S=agsaO-TIk=a@Kx#K5_NF z{;9_2Sbr%v)fp^0HNECrr;00CR}91g#w8KS5!~bYj9cD~?f&s+O8O5wKG(0jtTB_r zY%2HJpsGytRu{-vWc_OAuda^En3zYniqR?X!!GP6?>=1oGCZ=+i#?S!aPMKN17oS7 z_5}?0NLlM9&tq%6Q2F|eKILP+(-lz_{i}{E=;+?rsRD`7XM`_SqXS%T99DrytI8|R zqmNcq65q!A&5yChdeCT`9Q1p-Yd=#U>)h~1MdGOXT_S#_;0^eLaprf8A|J1xHC2OL z(8K7*I~Wh+(_>>yGB$K5`Q?{Hcz#^hY@?y8+-QuCVtwCJ8SD22V#vc8Yn{L0tiWMz zv*tElpSIr}@Zmmmn5(@@ZJ@mlj++Hmj!#p>KX{I^m9mWzr|dH4TX`%2<5RJzpd5<-uw#V_XkVXMe!BbaZr^TGNsPo?cL%vTfuj@*ahyH>Qn{d>r(S z(C0b1XI=k-e8+LWEC(l(r&)_F5nKd};nT3K@?Fev*cTUk;V{1wE0Xso%screhcQCmPx?gU zDRzp@Xh#pxzA{&~^N!0sDp}`MS>ga8d7!_pIs*7#B|~^|&uzL?#JO46SuT#^W@dwnx zX+lSx(<^n?JvffK*ITyr?#nHn3VjFy!JY$bJq;5J|k)8BQ| zay1$nC1~UR6|ZR;)d`J;Ti<thyeDkf%>&O-_aX6HsJq0%Rd7yjaABi{=J`%cF`$+UDeV2KtPcAmLO_>|0OB=CHVt2Gb{bShmWSoO|6#k;n zHI9ya8Gn!5T&WwmQu{hvBzKzmSxoYsfw#(=huyeH&Bh0awfWU*#=e%hNeAZ*oHueV z@PxT$a$I8`)jsAH`aL0IleT3%GL~pL<#w4*YPe=%WAwP%LK1($KDK_e)wE+ylK)M- zW_H=~M529}jyc8A`|{peoVF)TPkglM#$I>+ch7=f=I(Y($<|f=St1Yi-pa_sHx`?I z)uZi=L;D@JJov9o^L5>u=DP2UO3TBYU6wq2L*B{nvG+i>>-j#AdI43}={3<)Q3OZJ z^mp`ougKOi`9>7|F0gXs=gc%imYtBfW$In@&BQcCS9K=kAz_MLASR^aWO^S~hs-~i zhZD;Z9Bz)1e+wSq>16y49$6y#EcyPO{$972JV*46m4Dj`%*)luhy#Cgvl?gHyn`ZB z_#8oe+*)W}*K+?6#$51vSEs6Dy{WE$xC@%7yuCY&I(bJ0AFnoG4BaR)NPBrAKkz}V zUI z>4~4}oPKLxZ|5H5X$A8#vjw}sw+d7H=$#WA-$H-1=N=J5@4`dX)TxxXbSmc9ZO7BQ z@FSm4)!6q2@e!b56}W9Mp&NFrGWG=f8_2iG)#Mw%M4 zjGynuyV@$4CqV1cD8Famn-`*U3RO^I>&Tn0*)gSwIgbjvyfUwoHHFtqLp4bKwz+C` zv8j(S{IEQ$@0%uJJJtY${BXxO zd{*myh)Il}c~nLmeIH{VWx{LnK17&VxgAH}CvpEW#?*2}H{aGUW|slejnEd|8AFc> zj$P8O@HBK(GFQ~^2w3lZz~A5OLKmz5kG(gJkFvTFzwa|MAp^vAtmAf&h!co5NQ7wV z?t}z*0zoGvwo#J>743vz1;t&o?Ea!YlX)fy8ian{i9s7Q&;-$9wcXUxRuOQb*aoG$ zacf;vv_Y|A8&HPtuCLjb+1E)UZ`nPzI`WtF5i}cJLtx0G9B8*R&K*)BUaE8 zqD|T7GxP zIs47XVEj19z<+#4VAJ&j8*_*f86@qr_lgYs#|msr2evqUa7CQ8f;rAwLE}S@$yqkZ zk^5PsABQu0PquOsIg`#AR>n$l}G>9{VL^E0tsBIiUG7G5zo!2|z|_%39~k-{c) z;Jehm-kpYpe2JvaKt`=+PvK6(9^yAPZ;tFM6?IF#1NF zU7j*1+u@Xv*x$a2v&-dN1%~c|TJIN+FUw{=JZ^Z*Zn=XiHBQHXK6Z9Kwvy3z)7dpc zapSh3U@$Ng$EVMp6LerBS?C|6eC#^tsXxR-8f0ed%t>&H7 zflp?5kI8kzd~An}+jeL^-mh%J{O z8oC%cKhkbnglrSL&9GNlEhp?YL!Uc~O#IV!n>yBZLO;EBS#gkcx|!<8CvCWpoPjXH zhO4`d`QS7*T|Kg;yX72R=FS7uGoap2c%Y+6(DZ6(n%`0`>Y2;DHYzIRoZn|ZA2*!& zeRxlQ>xq4oX&TUWJ9B$Nc&((2{!6=?nA0ZlZ5$e6jY*z!*2m?}oN1WwkN9S}izUEW z9)_QJ&t~51lC@<`^IeYqE;^K^@q-M~ewiQse)q_EFUGuAJP(@3$69P6LvB(YnRlAM zN57kyx4s8&%DlCawQe_j*R5=5t);!2`HT5h-vLvHu2+YgM}D=()l$-OQY&dK@s_GB zuDZg;ii-rUk-Gbc{aylB+X(+Lx;y^kP3gUg&KkGqls$3wD25KAAG2pMJ#R55{(T(z zl92tN)`MX=KSZIwCua<>=kGW=djNcHWB$|oV!!gO=8X;Sq3}118Q63SH-ng|sDfKzj|eS0k|;g?H1ByHZ_R$F`MG znfk-o>@6M=`qgV0+wg-C16IeRhNkbLyMebY@1kFc z&3861|CIQ-10@Z#tL#4ZtR|toUcv1xm)o^Gt4rIR#I%$==is)4GUB^2@lUq@8+`x{ z@|^a)0<+uC_OLPCHc;plnWSx_>GEdR|s&xC{Gi=;k8^4-<@*8-nt(IyP;@p(X zJZsH4OYX?3u@@PI%((`#s6~7}m8lOuk}=c#Wb^&&PMyt|!<`+53rJ1z^?3PkhcVpzQ>i(_|eWY)S@}^waq@1%68ej~D z^toT*rtMCdRqI3+w|^VI z|F@yz4xwZCliX^GAhS7JY9V!|>ALw*Xfb+?zx{uqKV#cA^Ukh>&bOWUO5&&J(eQQS zkqiCEGq32y=)?uG=l3L?82w^rS*j13@B)K^N0Tzg*vwC&8#jC4-3N$o9jEj<^MWrP z-f7yLGsOB;F>wQoNe=d}3SlcGu)~Tc>${;2TjQQIP1}8hedg=%)gtTMgEM-uw`^t1 zxK=G}x--pJ52||ww)k1VelRoNEW6tNCI7oMPgKi%Q$oC(%r}Cc!d&V@*T0v#;q!I( za?b(0tl~4k)0(j0Y5Ia$tj|{bvLSkXvbyO}-sAUbW9*{$edupn&Hu^3eGYvb%NcJS zsR72!6EA1&ZH%tFwfS{Juf4$|vi5dR*52T&gV5!V!L=&s0zMf__`qtyXN3NT&gU0> z-n#lp;(b;IY|cH3ofTw_$5q`pw_R-N!(lb;;>N4m8?=6~30e2Ls+O_X z!k7sCGWY8J)$|eiz47_@_56=(eq3R#tyU#_9DXcGBCjbg-`=|uJUoqV`=BFJPM*7N z+%LAL;ElP<9`*q;2MP~RzX!R}gU&2CPsTYnLHht_@eOPHA>wUD`T+064i-H@_zM|P z&i^o}if7g29VcYnjqeCd0-wMoeQVl5U*Jc0QTkRPHot~zzwl=06MBUG2tL4vxt9!? z*UoRzIoo?>E%wG@=BC5i7xf6TuKqz}-9^`ltdq88EJ4=YsS-`;_A|KEvf&=uk4yXC zSf*veen&RMvQ5XPfZjtUH0{VbzdfE<4kdm#MH`H%{D+^APhqj;D^rJ#$rwr-y033K zvM>G(+Nsm+7}%&vY;EYUIyTy;Lhyv}m9%4Hvq0~jzAR)M_R5gbezzce7N)%baW~Zx zV(xsp|JU{E3%}BgE~(G6!4?v|RL9KR zWfYV!PVIG~Gygt{?#=r20J4iU$(?$QY?mV^FTB-}n|e*MTHZO&C)Xlhb|FuO3gI#Q zj)4>U@1bMG)Q!$Jgq&G7jdca;^~?Lp)ZzO=5p>!{e5}OkrDGp6b*_d6N7$@aK8kL# zOY}kf`=Sfvgc@b-cyb*y<_W7S&JbNX6B}Z0ybW9Msy|W(HtUtQpug`*`%J|yM0pQ2sUt6;U-eERp+_arp$Fe{J<$v7gOAJz^>e3VauTTpfB9UV8O9}i z=NX)+j`W|eyi#w{!~5rSIW>5fx+J-ovb4o>?_gQp9fOm6Gc-@3Q)K*WX?wLtbP4%Z z?A2tmv}rsc-wx(ao-ye59vup*L|7gEK<3Y(pf`}5i{BrA7dn!@J0XeON_vdtz047Z zBHsMojr7Uo`tVSp>%+m{x;}hqf>JNd;w-e|%EQ<#4CUb;$3JClad47+NO{0XPI4D_ zBzctY(D~p6Tnw%GS^iM=2LL6b~K+R_|bU^)~P%{m&gNFWxq| zWbur_{DK+%W#|p0{<_q`e$GJBYyC#*AkPk9_sW@{`dqgtGG=CEwoMFQAM)NS@m3+l z-C@0~NrDd@Z*@f5M_Tu4Lte{XqTYuLt$X!08##i%=N>YA)>Bg&;*%U-@k`jYFU8f? zgrQnVHmT*8-RjBSWyI{$_Dkp{y%j1Lck6jI`BIr$!rf~5FXgGFy<=3tOZbnIzvY}Z ze8|ZcxVNrXH6*vFP=B{FldY<>pZJ;NQes@{R5%&t%(F7qUc8)pW^g7%0kK`!#Qk~5 z`628h&SOc(aVNvZ*Vm)7h|QK{p6Qo;2q&!rVWTpK`9n4gg^W(D7y9-*E|` zGN*Z}IIF5C9oI8G*{gbz^fg(go=on?wbHGYBmcz0kHHC*R%DP z#KrKwhWHJGGG-E5IMi(1){A~XJynBesN1Nk{Qd9OJ^9=|6*{!Uc;fv7M#JEB*|&ke z&4=bLY@nX5L$kkl6r622G-m#`p`ZzU{`(a|n-X87=@R}1o-N65sM6;s|NcYJWgg$p z$-eE-dD*uOzL$NQd=H+x!0~2aDyMyOFw2nk!{9psuGbA-U~B`|9fy{To-q_$NIQq# z)9u7*=Pu|!rHo$QCoj|OB;Mbo+gW$0>ZEo$XeU8C>u6)E%)9ivNBFF_PCYpoFSujy z=85oGKb9pwm~zkgQMr% z;qd993i)31>3(tpeW{7__s;{)GS%9{d+=u9l<#j+A0GM^sa$;sO}0X_E#SpQ z7Wyb})3+92Z0$9`5qKQTaa}?mwhUfq936aYG`Q!C3N`p+@q_QKP)Y6MzDM;9)w!Mk z|1Gq!j&JQ_&B1*7kl@?aqyauBy77TW0)q#?b_cX^7@hktXI{)0T7^y5q5|SGf9^5P z!-#qdfJ?uZegV(nRLf* zlpQpe7Jxrpr@hnhMZUO0ZF%l_&g0y8P?--K4Y6$2+MTt}0rbiP=(-1IgTu;H-(>L8 z0==v}W`F2Q#?^sCS*|B`H>mzY`MS?G{acYD-WGff3`WdtlxZFOd3YORevt1P4wYRz z5rGfTBUEpml(wSisu;kl1h z2;D^dLEiKlaFNhJ??z)qU!}5#a(o&mavsn7sYw2yJCZ;29b^@@XKFwtet8s~1X)GQ zyw)XktW`le)(Y7^9Bbue{xecZe9!l>4pP+YR@E{OZg~iQ7joP_f_!IPQ_dh3AE?Mp zYx5NC`$`}mxUcbQd!d$}T2DWn{1jgo_Xh2Wn_EQerz!MaGw$$C@EmE+ zQ%eL#FRuC6ml`;nAdWC1ZSP)tG&wYD;KCHKp~r5C6dbGk{2j-F)e~N2Zi$zv<$p6K zbsYE#@y{$=Fu^yYopnBYpRut0XRLKvMor9(9ovCDvE5jRO_<}@prZG`b4&VJAZ=60 zv#aG<`#!#dU&89(Xt5Kd9(f*C?|#JpfIVa^6x|NHOC|7o>Uwry<8R`w>#+sWUSV;N zy(1N0@QXj7{RQj=>;x$%J~=68jWuP@(}3)ea{NjEzTc{sSVVUO-PDl&gp$}$E|nv6 zVLz(^$xfqu+Fi(7%LpUKS1@0$WbnCkMKzRdRJn{tRdSzE-M?Si{k`xtz9{5=D?Gm< z#4{6JCNx0%*U$0yo|8KAQm#>+JSR2OpKFBqJ`kOCiNCjk=ZlQ0-pQ$MaJ)hxxWBKwMIv#CVrJgWm+7((ZX__#%fjd_%JiUbvTeKAxwR-!c){Du4|=ruIT$ zD+9L6d^4gKmQOvlsk$I`clp#qR=6NF08A5(P`?-1KmTa|0S6{xtRD|6W8p|Vr36nM zYP0tH#8s!pQxH6jKf1S6@MJHXkN{7q-=*;sS4*3r@4v4AmTmZy;Ex4Wz(N_plQmd6 zb!e_x5N#}-DmaU71Yeb*f@592z+mA(!Qh)qf$KI6#g0Xe<>>PY*QYFzzXIcn&pG%zcAntR3^4Duo`k@aL%+ zgWnxlJwc+;@HK>pOCEua+4< z10NA@fY7$U`;VCgzub;pp2xZET4r9;)9U7MR+pM)xm16R`}4=erg)|-bx`&z$i51Z zrO<#^WT{@eM|R2{7?BOK4<@XBqt81pc_5YI_e}AVTWY{8>*nqt@EB@7tnEGC>Gcfq zaS|sbJ`{eRVLd?1zK8O06|}(}utlV9k(XjmJPjdITh)^TKN%@uZ;^Q|7UuoL@KnW}7w65qhw$sQHPn!SplE^PHY)}9Ug z%8Wby>7a~z;%R3NZJkRl{^`A6Zjdu?@z+dCbQ@jZY-tJlb;xE-O3xVwYSZ~eLJtM7 z9P}V@w9v!9=Q{7+{vcguw5$&ctYP)`F>Av){(=_DKTK>+3-hL~@9pO$$B$|XjFh`? zDs*e!>k=QP3H}Uu&OWy;-d)WeU)C#(L4j$ex#Szf;dRA+#`goNpsxIum9htrvDli} zuk!@I*O59%oun?3yx&54oYWImZ@nY!SEi1&g`Rs~CGJpa$|&V7TW>VTeBFmFu=}y! zx1i5)NVUEf!#@;M6ZYmA3EMF6o1+6pmWmvou(!)2lMZ>f|ED)%N%YkWiQd4 z_(+J0vH4DDOWM)+NrEHYXJhm1uH`GjYX2eh^p5%`>u+hyO)B#V)~vc!&lG%;=2NP( zBu^#zu0Z_p&f4q%F}}v*_|`usZ9^w}Xm^>=oZj~D_NP08@|l^YbjMey#5p6rRKHsbt*kZiD&b*WBkL_5FHY*%PdVX&Tx0 zl=KJK&jhdRt*NPPu5((RWwsbCm%$bU&Tbet$l4YV4m zw_CeYZ?(o#`&)Zc10B#*$8*Zv!JORDtXxvIi?TgV+5MDr$}}rO*Nv_s{39|&_(Jvy z3%=ibASC^TO7|K3QUD!5uBl_4z)}gQ=yL#c7I<=*zSM{_S>W9GG z(_yGC;Oy=uXMTE|y_g1beS=x z6_WD1diJZWJ$33Q)cfPrd1@)dOcd7I7^>Oum_%higY{}cdp&2OLocKpl8@vk1%6boa=W_K4+Q>cS;)~huknTg%i_)}?tr0M}2hFT~cNj@rs2jh2^tU+fh8uEM1Q z-@GOA_KM|JEAeiRFz0otQh_s6C$YNwisc(%uMz%~-_kz6{qjuy%X9f%e4#RG!`a7c z3;F)dIsWMKg6h!N)RAexXl1z@5{8_8aV*?yIQ#x3-a&X8M?Ki>waoU=|{;2b?OLnTRlb?u!QNi}E;OP7qHCAj{%#Aub7p^?-*>#FrZq9%8!3;U zT*{Zq^G)ySIZ5W@l84^KkItHVir?RTOUi0rOG){=TlDj1cs}plo~d4Fx{Y!EAL zhd2Q7!%v^d9{f7?-$yTXH3Wadxc&TMf8i+JZBjnk3{uwV!vvLUx!m{Dmtf79K*+kM zK<21&`XO;%`cCE&bidH*cb}wPpUkWJ|C)FAM6>3m=bwG&3cjQc%J?N0dX{qVdj$Bb z417}szI#;&8Rh7RqriRhB@My1f$_$1{&Z}k@75`wgt2A<@J%QX9?n_OrLuu9|JE;Q z-B9nX3|;o_0oo4;?n71Y{(;|mKV@kCTT1HF<1(Ar^koM<`rUc_Uiy|oRz$8YsVb=Aq zN6}3}&@5{lubjrcEljzqy@QFvqJ;DQ4wKGqS z)TKp_uG)kz8{PrGW6_(?cj47beKX*z9QZ2;Px)Ch@sR?;kGvn&VT2dqJq7Q1_$|J9 zpDsUF$(>xo{Fu34L-gamxh&Rff)D%LkMy1YUjC2^-wF8dTf!#7did{c;`p$`LNqAhNfx^cjAyI#9*$V5T0iBa81kApTV?3ySYPu!Od>ZA1G(>J!_@Cvb!+ z4v4%-)BFLOawcW8+z4$xpyiL$7kcEtT3~!!$0jwYMTJekd>4MfDc~RloxZ4YLw6tO zAhs_U&7Rv}jsEieR`@K=PSN|QLN#LpY5G=O(l;r?x4}@;fvxgww6$K~=t|3?!uVJf zur52RLEcF@%lqZ~rHtm&wFmm5W9CZvD#00LgS!2D4h%%^*7YRQa4t#1Ii39)0f9xz zS@Ws)clv%?!}5Ij{?M8OhihDcknmtw{o5+^1C1Lgqw8J$PQH8_$A$Uc>@ zdgZVltM}e%ZT-3O*x%Kt#>Q68vS7XBy`3cS21Ce8$cPqoe*fk%M3I0$`UFWB(6=urGFweZhCGt*14S0bC>kFm_3 z+n9GF5^v0SE4^;%vmy(9*xMx4Pd$d5H&yk33TF7qT2rLZRi?-DfnhX0@J*Z}G9TV& zowBAX`*_W!?Bmv2>SF)8%voa3iyk6%*kh0Z4m^d(k6@ZUg6Xyc0*lNi)?#AV(lF?@ z`&dKy9tR=a^-j^yw<1z^VV?*8u;Mp7C3_TxzNP!=ht~}&qAsYZpLSeiP`cgRLRtbb!+@pU=-etz_8QH&v+)Mj}dUiiV-_f5+W5C1y>fOz6uPg!X zp)+(p10(vJBmG6@?qiITkX4aoR5SwsQ}I`bf{!4`7>4;C?q}90Hsn z;Y*K0P7E4=CR%y#AMxIQ%KN3f4~%#pIOTnq_qik9=brMu0GlT`?4#Mh?-o5aIWc!o z+V|3D!%q8dWd3~`JZ0kBt=2T%%UlPolr9EWt;}_k-eP;cSNjc|@jp-d;A9?=^W1Q&haS@C z3jIe;_`8HY#3zOAx7=ESPeT0f;uq`IdqGyBFU$FG7P_{LoyVNu=r^@WJgTJ+o zKTLP*7tr&q__hveejA%tud9!UtE;1O&?Wck0|!nXnKKhbSxtrEHkt4U`% zey^;5_-2=$?{kOgigFpc3ahzmk+TkrLThO`I}FPRdZO;oe+D_| z;O3&;8aF9$nS_p4RrKqanD18X0mf3wsF2wEjShbK{?;^Z^V8q2mib1{XR9mv?JX61 z1jk`jL?8dVm{06b30=vGu z7TV6heulsz`0wy+tIWt)D z05+_uyWj;SWAE79wcVwvn2yx#&{9Vv}{sUi>9x?6DIZS?J^z zI3i9KS~tV0U(T+QJqd+VC#X{VSQ_Tp-;w#Nuy!W7-B}T|AFt5+kcktWDdocI7a!m= z@H#T#JI9;5o%tQVgh$St&@|AW_HkusFE917#JNa}i{MT33hSO}UzBa#E;#&`+XYub z@8aLLSIys3LLY4O>@+=qUt@>BB{o>Ra6aLQbBp4Y^LGhN+svyOT-d**zu*QtSNn_l zzyW++-gm(pd!QlJSEtO>Ti$}ybKU}CwBFP8$5{^@#_?5efWyKCj~r18o;%XG;Q8$1 z!e7>#;~N%i!spO@R)e)!c=D6V?(7e#>yYfLK|c9o9EJ)+kZY=kLf0rru_@QB{&rw)n0Y!T6@!_tAL0Iy%VulBX~b!mpDHBR6=0KHv=vH^Gy6MrudO${RkRfD04P>wa8G$PTLU3 z>P65pF$k|eDRi*gPzy!pWPPbxczkF}sGx7jzQE93p@PJwdBk9;2H`!S52vZ{pE;`w|D|4J3Xu?||rpLlf19dne877a0HzKyxExC~`|+J|RP| z%MtrhWT?n7El1H!MV1P^U{iV-7j)EL96MPi75q`dm1104=at*voo6~SY09hYZTAl2 zPvmzT;C$!*lsJ?az|mGxrR`@L!bQC0zpF?q4}Fe9swao;8{C z=>MJ_C-?tLf7GjxW4?cwP`5X?5m>XY|?Iu189eN(mj0 z&^Nn8cD3(9PJrjI>f6oU8R+LlBaI6=$CmbAyp?rI*|&o4qwitrk$7jJvmDM5@}Xnq zwC~enE$2WNIdj+r&yRqy2p9|7>!r-(M`ixgby&#CxFd^Yj@D;{r(vGftWuU2xobd= z%30^b{;fuLYUIC*_b&d+J@-C-i~TONU&wDa?>+p_=eOiOO@llm7kfnDmOfr5zGEAH zgukjSuY)VT8E#j`hwoc!>PP(}&x8Cfr9D5trM(s8Qg@Ktlr^$D>MU-7%RpqyGD&_#Fi=w%~9FW z$?gWuG&sw0ge^*C$WGGFdveRIc@2Jqky%~S50Ici36lZ=ItQ?o34 z7H#Fw*2~CyRXf=?1GpocJ2%x*U*P;XJ;wmGT1lEtill$rt!~#2Rc| znVfU-1~S#;r9b_woM#{LtlU~IbGTa6HBZy7cMEy(TCIzQZTXEI&->EWdCJ3Y@9G;= z7B>E0HZr!F#Bly=&kc z8tsyP${mZ0i4q!t{zM*!sZ;w)GPF%Sd(!pjJp|&nm%7-OI9lowSwMXW;YB$ETGp|{ z>iHkHo~uS%*Pr7rX~K_e;;**V4fG}0%Gr+mUMgd2yLi{6vP#}iV-w@uUE0t3pu2;0 z-s?gSy4iPmY?bV17FsP~&9CEH_r{L#?#|Zhsjo@hK^+D98$D*nCc~!&@4q9uD*Z6m zUXSl~uFG$kdQWN#bUur=tEBDLTmRn?m9 z=r{T7H|Cwl>&nznmYj7WHdg3$cfxsgY@F0BaL`}ryD~SXTG*#2@=)LXni)HE9`NbN zkyvxk6x?Qb!_gm(+rlG4cOq}m$<6e+#)jFM?OC%&)1Og1D|HOMXfN9^>c|MbsQx5h zxLTDOwbx1RKRCH-A>t7(b-^<(B&?LtX&t$wBIlsYv@ z8$moxX9a-$&mwtwf{-~CCkF<<-F9A0E6WATjH(}mq@jfiy)hJ@Yu2oax z6E$pG(q*bBb0*&<_%8RvcSn9fo4h;n$C2MhOVm{USK+T=-3NqU@$U7JBd?94qf=#@q?fAshknKtxi7&=S<4O&+O-%ivZE@-d<@X8< zWwvWz+D3Jr)E6p4hCHjLCVCCgLH11Hxy;?XAH{!*d|cU1^*G-?&hJS)>!Hjf%J}&o zw30rph_XI@8`Ogjz_oEG-4-AUR^dOZE?Wq$7>!ADvr zIl%LrsJSr2*gtNKx%L(E&DPlJYVuC`Z>g(E`u|#M%+>O~C2C$PdV^(+xrTnJm3&`B zzC3C!l6*gUXQMI`6*XLxoU?F)xt-?)`CYJIuzZh ztiz_$Y*4eIqXId1TE;Q+jWzmA;!Ow4vd)h@4u6Skc&2xlwij!@ zY-JCmt?h$T=T~AFM0d&9=OsbY08`}=QhZmB#EzVzX1KV)6*0GhI_4@WhA3F zUE2^_qfDN4YFz<6pvE=o67&(%M1Qcab$6g=?6lOGuUKP6CKxvFY^V&KqgTD%iouZ?(j;?)CFYÐgC|Gmu}0IQ>iZ-;x(_oXNzy?QbY*x* zK@v1-w4{^JXFqe9msCKiAuS=Y|31lW=b1<2e+;xY-o2LZUdDG?F1W8u{odd= z^T&rDu?A8eztg==#ymbtnKg5WI{@#(BMH`TqmzZtTsp2qzUQ0gi|r z?aI^#A31pV-~-w>u?w}n3cjqwweD8RX7q6>`wQCNm7!h!?~re#A5xyOD@VNRrY|Nq zawYC`xA49-)~w}jbp}6tA5FLY4{3a2<2kq&TnawoG9H4DJB_Ka=J~=$z7gNgwwysZjvlx8T zE_PsQo1;9`TP|^awG){yY~sb{q>{Chc|X?OU{$<2RlYGdh%UsLj#BRf#>=wDR@&oP z%?a2K$DO_(zMX!W@e+srq#w~>#VZ2iNL)lGbM6Rr3ofE1LK`CMw*4yBnM4Mu0Q9@o zKIep7D(n1sxt*tNL$AbYWo(O#-KK3c;>Sh)h2VOz-Jq4tPJHQ4qV}Ce5*SqIL8C7* z-q@vW#_`6>VYT3oJkO#(;!8ike*Au+zu5W2!HffjMfk;^9guy=9*18)c$M;Qu`iM7 zTW#VV^%)TtU^|)c&xVP9&e-n{(M% zr!e*kPGzja>c5&~D8?LeHfD=6a_N-`GB6aDbgJF@RVz6-0L{wlWfcMa{-6Wj21 zL&-+tN!GAE>Gp&0r-9BA(Qr6AryIBm`L>F3${NqSfL+f2Qu(jpy9Rw2zXHz+02oC*AEEjqT8x-UlLO5{8?zaNOHTls$fSd!qq;cmkn3 zk8h;jBy^yCcF=dN>* zj6L!nIK0I{d}PSm_4J!$kX$4;$wSH_jUr`}yrj{jF{H7i9MT!2&yhY)k~+>LeSvfq z$xk|)^jD-Wk^-c0q;p8)Nx7tRNfSutb+}ZJlt-FPnn;>N$|s#qx`6a0QUPf)=|a*J zQX%Og(p1tkQW5E5(j}xXlctltLi%gc-;hG2V$uv!38|DclQe7nM7D%Z7J^fgj7=?c=7q^n3Zq*~J7k`|B_lCCCQL%No< zh;$w4deYZP5mFs#F{z%kgmeS#+(N#b^bOLDq@|=~q?<@LqmLRT@Mmbw*A~e+Z20nS zu{Q3VYBlyxoz|k|er{U+`(rHunFn%OcL^j4%^P$vzmC^lR{>Qn6D^hk_eW-XTQ#O9ZA^74JniMl_YM?X4j!#xl(iM7t7t~3l% zCQ(5w!f08``2KEN;6`2}LvEm+Y^e|ZNn~ju?YpVx59C?ogR&0ll>PYoQn$pRC5+Ka z!N*q1Bo;yZpXoV=H7AeA%XN9kJmhCH@ukcetSx8sf&aWnuW=EJ!i@PH!++6p&f%SL>iEW%p}$n2161fWgN!d+#x_Dc@WNplVGT#Vr=BsV)-eK)*&?6m zyR36~H7pxd-~_B6{gM8MMLuU_Y`ji+GV?`rMLL#%{uf7%R)dAk)(|l zQY&dKDM4CC>L7KJx=5QzTS$+SdPrMIPm;EilB8XvKGGgiKj~%CUeZ3&0ONV%jSDW6n8DkK$=rjtUXQj$q3CxuB>q-s(PX(4G5 zDbi7?6v-gDNGu8|4=IZ@ij+>=A1=3k0Kk01JUy;5@ z3XsN;&LNE_<-(6a@^i`a$tRE(ke^3h*g?E9c@cRY`E>G$eNqz}= z1NoQ9E%NE)Hu+b`Tgd;Kyp{ZK$k&pG$P?tn6n@UF4UN_mN*lzK1+a-cMdZ{xbP|^1bAh zQJ0e+Apa`)LGrJW50Y1tA11$o{14<;k{=jA|4y*4S65FAso;`CT;t`mqWbXZB3=MT?Js$l@WS!`5Ql~v9 zPv7gV?Gxs(&>X#IggSCB8jgRY9ce@6uZ)ctR_CnE*e;o8B3u8A_H$%@|782)zyBxN zXHIkA`tP+LZ2gq>|3v@C{e}7;^qju^bN)pC$Nh!+f1dp*?f;4X2mV6+pD=W~_R*J$ zKYRQGf1&ZJ|4hG^Rbes#mqp%wH9y*W2 zVHoH}vZki}RAF`bA^d_WJ`=sYiS@l@sUgblpsYbz(W4?a+r;p=_^yew$*zN!@$0ZQ zAot0YFEyQaV*fAb#Mf~hzA1EPct+b+@h0@sUBqo*n`l4SUBt|zduMd1Ou3AXDt(&5 zeYP2$DWj*0ED^h3_XqhQV=4V(4K!E#35X4}Th7J*GA`p2m`*Hzj50XSXwVIonj-VZ3xF_0&Kv9-1JE-F0#`n>pR@LbF1JI zJUZ~Kdi!K}UjGbu(rwE)jKq)FFcO0;9^GL~#ncIbjV$BCxo8vs&b6xmCSr_YM z-q;av&LgQGU$tPzv$MkLp*Ham6;GaxPbzg>*3cKJgR{h!WX)l}0OhbTCB{j^qT?bN zW4;Szp5dGBIq|&H@y$w1FZeEbF4Zrvz(c=)hx|6U#&60xvC0X~(Q!Rq<~akOpFak_ zL0cYt`f~rBtW)RUAM{ClWTq|Xwcu%qj|2wc1I^Rvw$klfB>NDdF}e4^KW(*cEB`C_ zWZ}gs&Z?$<+9L-RU)paw{VS8@-cpq>&+y$A$@5H`nf6$-3Yc9oR-4{nPo)3U_ZfIH z`T@I+UZp-y5wTO&Vnf!AG!8ScPl4t&{IWj*Tot!CxH5OZW6LFWv*M}vTKt>4uw@+| ziBD)YW9w$>)4G7f?t&9ywFZafLYgjw9*Y?xd(QYhGSB9S?@bHQCv;%02FDeNy{f2Po`Gj7z)xCtHO z|L=D4W1K%)arVf5I^$HiL&iwtE#oYB_j3kRk;O4p#53#Ic#=rXJs5-Th6umRd#aq1d} z-dA}-Ms>+LOKZJi?PBXCyOMW@Pek zoqDYhJp}n@6ZIgxW@nLuJ9HW^cr-KeH7%Qbb{Ts`kcsjxqr2&IKDx2RCB`~A?Dxv} zE@h0Rf2Qb?;6mmSt?P}nkF-oY$%mfSkMOUu_r$g?^;MTVn@Wj%cW^v(J~8(i$1<*I z-+sQVSE3{DmbF0E$ZYiWGacAdebWAE+eSa4jGljXat4~vPbeFI9Qu*fu5Ccm`U$%6 zzj1C4;}i|dZJ2R)s-ODY=0o!9rjC)C$T2fM{magemwPOD|^nA1|L*p^l zy`5(t*Gd^_V??=e!{si;AFX-e8E|6rEGYcQ3JA@($RyqxY7nrD@q9wp4i|F>vI|#ZN2z%KK`mYsMxyUryyp z-=*JWovFZ0;IT%%e>_a=n&7!?Bl&&sol>l68{XR%qlbY*(Qk&w>waf^MvfQl+#GI-{=q@40PdrpCGzd-P zm_N|_C~DaAS1M;(G2VPXbc?3ZoqE4Th<9@CU4wPkB)#?`Zk*RH^hhkm!I$*^{56z6D0hox0}K8r z?x?^P*apvvjEmMe>lI=H>bU^9CN=?jUBB>j+SXaY9Za{d#-7iby2!YN&2Ok+b5sSI zuM?POf1mf4%bj*%Rdz^ZTk}Kk4t5yrD)t2+Uq5O?<|?6kkvFmq&wWf~=`mz|>_75E zXOwr%&z`wY-ebqG2T|faJ|34IM_GrWZy(;^=$B{im3>B1#(wzBmoxq@a9JiX4cxu? zl*nmdJC`)h;j`Zy6Z$0SbIwQp7Cz>`>_v8E-iv(Kb3i(mb7bW|x>0sb{lViBS0n#3 z^#~3Hf6*}WD)=qE345^qb1TE@{-vkZ|0!j|l+AVOSE0=OqwO^2Y#9%+F=cE@wtub) z9X7$Z=yL;vzM-d5vAsg|-3zJb#~J#!%!37Dw^-(T7nz$v$1#kjuA;EI?^`GEbU2Nt4@Tfg zo(Z0uXT-Cn@pMN1FrI*kJ@n~2`oPnUPs3BrA>U@ zcslldd6vP|NIXqxK_^Jlr{HWPo|=h#+fypEk=ojWi!1B1lm#NOCHm44=(7LHY)0SEfk2{^_!Xj>nebm-9ij)vvo z)54*r-S`ms>9=~Eso^pQ-7bz!;b~-u=!4RZ?6;?_TjS)@?qPnz9u8fvvc=A?nRDch z;>n}eLHk3|`Vall#T8SFgUFUKsZ@0FwGAv_1Rl=$!%Pj%}mM?W;$Jqn7cC#B)U_`Kn{97n)_; zRe|Nc#-HAE7qZ79TPo-KtubX&6O?beV{|!iBx9=wr0jHS^xOAK9SQ0b9o%wV<4+(r z!LQbRs-65~cQM;J!vn$hJ6@E!uj_zds_zN3>r^jnJi!iq{;_6*-= zcs}i;?~=121n=esl|KcZBo}?f!3XCPXJkiMtyvyAS7i%b)X<#9f$h`rk>H_JU}Ap1 zF1GQYzHd$DW9ssXJz=ey)F8STbu82KKF^rXZx>mSX)Du?(5&D|_(fD zNxm=PC+PQdGQS5s)mxHu;^V^Vo;H0J`X+sD3iJB^)tmmW_bOMWeml;Qd80$(;}(6w zZW%pYbP4<6(M9;9z~SHrdf&-1C%(iP(|d}gp0Xx6pBsC&g}pf&CC=wf;$Q#c#~FAt zzCY>PtG%feukKGZyk?~A*YZ-Wua%_|uhpeGUW=!?Uh7V6d963q^V>U)v;UZg&`2LKqoa%O|*DVg&c((>(7*BpoSn)&#K(1-0gSpylpOXMi@ zX8x%{HtNhtstu zF>t4q>DUe@)^xw6byn;NO^ca&q#q+`Li7{ehjfn2+C2Qs*81qDj)nLnM1BhWI(YPi zPuDNT*}3MEmYtr*HqW*Z*ig=P%U0dHVK0d;Jf2blU#wG0v<% zz4>MFcRTBS$V_Pm{pKT)SB#geJ(Z$A>+{M*u4nu_^h?WA{5-@?5*H4y$$Ht-)caZM zb0g_X;`XF%=2Rc!9M)(0aIca0uJE@=DvwWM&Uu(M3+{}~_@8LITxh9=u*Ye(8pYgm z!l#6emVs>~J;v@Xkp7Iid?S2Czl1jBnTChhd7-oPIXGR`$0g1h;cM)%LYJU^zqRYj z;Ki}K!AXeq7CjGS+LEQBxdX2P5B3D`1k&(aksc@UIdw2L5_?a)tCraIm~CzK_&8!E**bVw-3BoUzSgcZZ!mXJ`?;Yg%Mqo2=E!9o*6D zuNv+%^js(XWj@Mjzh2wA1!5C8K9P^-rQhlOQSFWBB@=WxPX-3yIe9IV`FLkf$yKJ)@rZr?sk zU)na*_=mqVpN)D>=qoq&^WQf#9(=k%bS@c_*!*8MY@6>)ZHz$1v_c0{{O?|NmIh0KNF4XM%&ju57SYVoTve$KLgF_opv* zUyt-7w^93IK6*YgHuv7C`%u-`{7^$$-i9^=?t%WDv z3QZN>vHm#2j~?bHZa0VXi``|LT)S!>&OXX`sm9$u(&O29X}ji^#)35s#mF%ks|?TJ z>#VY`Evon-N{rw2^)7ujB6?Pk*m<_U}#WKzC-Zo(=Bg zTvmRYQr2o1FZ(UZk%fXAUA}CiD_8V?{l;v4W2jGgSeco}(Rh0hx6D+Ujl^J8;32T<0L^|~^?)vj;S zR#}tLA$J+Y_Te{sTF2J9(VvT0dlP@7k9R)7Dc|L4zlrD+UDzj`x^Lpk>6GVXJMgD6 z2k~w2n=hunrBCT^Gd|X;vbFGF);50#`6@I;-=rkDP~tf3jEjTwtQ! zT-qJQIa(rzJ^c2HJm#Iu3EJj)dW5d4>&(mp%(;3VKqkwaEU_@?kJ^_7%}XrIr_a6k zSK+t7>GTk3Tb_SH7N+L_J>SAhv~64W)Amn?+t%ww^1Zpy*awcqm+lfA3y(PWYZ!@U z`1qT|U_&e3<`8MG=4sXlMJ^c~HwW?6@r}&AvWBqW*+3|^=~+KEwZHM5ryJVKlv(3S z9k0pFJ}&bz|I6EvPcl#Qy9zq?nm;hi^qI^7;S=zjBj@_aoNYY}uSb=yao3OJo@-O) z48cW4_Bb-w5dBE}pk5QbX1I=XwGLLrJCP$odp_YCbgZL{bGh7Wm0r^zPE_k(1~eJQ zo_EKQseEUG2d~JVwi5R2-Kjj((SwYXbLazhV5AIE9pJEmcZT4U_pS0?_?h1p?-n5= z1x{1Wp)hwIU#IwwI+8O` zHO@q5(c{A0Ao^g&zaeWE&{I(KjtosnOc863_>!_MzLB;>7nQS`WNy;<9O(y1)Uo~& zPrWQ<6Wzqv^rjAQM{B<=J~PJp@=g`R&dBANF2nN!JU8Vzx~9kvSK6-BI=sjYy@t^3 ztRaZa`1=0Tkv`WN?(y;_L`J3J+RjMGTBD4aw9UL0k+PIqDEaP9_Z%5A)Fml@{>h%7y2-zlXcS)Pz^NHNLBsD;vIZ>$M=+UnaH!V?BKSWhf~=7H7>F zUKnw%rIAH_g{(EG1xe1aobPT}@aU07fos7o^zwRit(L3ASNevWYne)U3(~Nv)OVos zPE|9cj#TSs6Yn{avc0FmEc76A0WfN-z-dR=b@{IMNp`t|*O_yTB4>z@)op?#2ykI3DOKSsvQT6TPeb~31!)t8qV;C<1t@4GJ< z%2NeH7md~T9z$FG(ALZ1ONbl%u464fp4v676}iH5yDqiM-p{RV8(qs|cZE#GXOH!Q znvSnncp*2)ncmi0)kRjsQ)K;&{4#fgRbO3H^Fqt4nq8h*R`yl**DPz9WtmsJB6pEQ zRo*Mn#i8ojh&{m?Tge$5rmSt}M{mBwAL6e1*qS2Nq@e-ld5%1jchRK_t811~-`K0} z*L96?>atusSJb7b%hq*qp7$-gD=U#bM(?!|QLNl&i`n(zD1!R(8B{?%c z-7okuVErakUGt`ARv{jWuMhhvk&;B z@4suA72Ei>V;fh{-}AAHM(=+mIww+HYtsIhYa3$sr29Reem@GV_oVynJa_udyJ)z& zy5>FV9rHW-{oX59w)A`dE7lyIOIhxGMKF&9~E#)aL*%8liqEbsPJCt^ zDW2LYav#2CPhV>5Gw5;fbx#R%2fWQ4QmM^p-rj0=r#8pSQeE)2irrN$^|n~gfp2sK z&D(BpIww$MbqQ~iBY&-j14T7IPxJPelKX2O1&3zwD-K`N=Lj@;g0FLhchfx0xkH*x zy~F(cC}T0U1iEp)M|2f_f+y4PF#i7NrQjN#O!H&Uq=$qb14YrQ!4v$rX|O120_zxu zA8RYqya?Rlvy<_*-h&ri@S*}%FT4o7G1j?JFR-?zWrp+2E6<|YW_4`^uzJ%xIIti3 zHb=-E=X;SmCuEL(=9OpB%ZR-h1HWlpL2pWU?a#rLLu2y&Y4|Jm52Uru1t#WW= zjVka(!&mr3u1g!#Uz(|dZwq96qAu{BWK0+%&68b&MYR)s~qUomb#GXx-NKfL|w&eKUtSN%ha{NrhQf4V43h_Q^>SbnkSn=!jplRjZ8o; zMK3_N4WH?Y1}oF}P=(PTIGR)Ji%!giE)Nz(D{_lO$A)i{G6ovg=P=jSq1&PFiQOgq z3lILk9IL$Y>eIz43vGz({Xg-)`aCzL7PXZ0ze)V>ZGW-&-;6zwSlF1o=iK;FLHi=Zdfh<03BHSIkXOpUaQy0_zs(3Yv$dlJ`mA`GqB4U#iguA3cRK2 z>l)V3oHKF+-VCfZegX|Aw$mwavNx0Xj0~Lkwbtcv&x8Y8ZKnNU*d+GIfi0=OV;x#- zr*wRUz8gaND0suhlX-OO%ouC@H|JQ~rKB8RBlhBG8bd#5W5mCui~~=Bh&W+2lx{qz+_Ifz%;-4ErgRy-fV)OJ9X}r;lhS_cZPFgw={G zMt&Q6R@)@bct}jdh`0^L!#A?ef~OJVnalhA!}wwxepHS+x#Ql9qwl&caL~ z{GsG0==IyYrLmt7TYnBXnnPT;ofJK$f5uzHJARwr+ttzq+XEq42a1)x=5Kubu?Bm+ z@-{x|e#CxC;#Bq7H3}W0O73fya{4{xtetzOO1Tkb3a#sTcI@c})@r=g1kSX(+F56k zZ>1d>7x^9Qq%EndQ+cG#PpeDrjJI>auhg|jd89uh`$7zPXqv=>Z%p~8bU?cs49|=t zKHNYm6)yo7_b1H-F3$6C?VVcEg&ZDFoYKTsr-Fl*#m_tA=>Jdg(B&<)&cR1?jnIA) zT{IPv{+De?c{4P>4LTY2qeJsM_ow!0x?vpyy0K;Njo_dpNQ#h@_Lcf)3~0M{FKf|q z9-1oKsgjm?xOxG65aJxeSW~~u9o6D9vaUG`zyHURg=d9N^u6-TQ}E{o#?2?|UNx@k zXW<)8A%}9Vifnlq8u0z!?7a_sRMnY3d}ort1O^+oX&W`@ph2NV4T^1$AQMP5Gyz74 zHY$|TmA*SP*n$QcExNd3i!OAr1&s<8E4sAO{b7r3w788c?xJF) z73TiF-{+ir=gwr38QOl{x4W-_JNMpmp7WgNdCqg5^PE5ToYqx`h{IcF_>3^%xVF6T z8Pr+3Uj6Y-RaZV*hHnkiShp+ZK~LYp83Xg=I|9^i(_}pg`g0}}KWyY6KV#wqWeZTg z9`0%1Rs=s`F50%a|ByWq`xdos!m3}4N84+0H<_Hjkugxt_5K7hN+>gBzd~Fih;~QW zFRWpcxq{reS5szReGqm6Ys)Z~`cT^>tVh6HGiYDDv~QT@6{nhg>@f zHu+rmml%VETx%fLrv7sM@byB-o$V&=`B#516z$iCdllXfUK&i+ZNR_6r**X$a{-w> zvKK~cD2si`{1kMRb0)@s^3`)>Wy)yXfy`To1b}W9zN!(O!1B$KYN2b*_EQ zZqdtat6n#}&}XCl2Gp1D9Y7o5>I;71m)Vu);C!cf+$;R4?Dpp|f3TA69&}jRy^lvz zR$>FFvtk4OxPdwu+g^UKAx3`fl-~z;vmUpu&tlu;ZT}n=Wuy;-9=Lag!g>Bg+<+uRz`*PlD8^7b-gmHx~S3%G-E4woD#7geD8k}-=c8F^$ z-O%HC$OrhC--<7mOMXG)rtArs$dBRhXILMR2hQ0IFu$Fksh6_(>i>amg4dP5=em%O zcC`N(Z7<4&a&LXGk2rUJ_18nM4xWRxCX~s!m^XmW_0{Lmej<&?5A%=oC9tRLy$*R) ze0Ay&$m9KGkVlyhG|>l%EUVV6Kq^8^^i&-1c9qJRz6$kco%!ps@=1rpr3+xoBUijC8zb zvEEds&Al3JPrWw1XZ!Q4 zp3l^Myl%=GHZzy=Sh{Tv71nT1-}&6zlyWS>e5oPdNpwZJWM2i)K5=(sJ?3lE!?!>l zD~%lJ*mJO-;tz4XAlI*O9ht_p95A^PyQvR1j{uL^SR;@tbH7**%k*W&O&=jlzE|U# z0bI2Es;>Z=Z65S~e4;Cw7g=|YHBIytV5hp7C;e{lFiQ5b0B_h=A{YATDh#pjb<;~$ zIM%ThfFAkApEj4aJ?uVpn7Yz-HTGIN|I{JqLOpbFx&L9vsT=39KJxvD1&j4aJWsNP z>*1J&-x`FboI1>XTieE-IQU|!gdvD1?bpV))-4DgXq=C7lVi+v@H64W^l_OM6~0gtvV<54%9 zbdTp4tnB04#_uow_zG#a=oWi|vwj`%Ptew11z252vrs3~Ni*8qW|fbRbxqE>U-j1O zoY&2(G+^gN7IwHF;oQ&O1(>4HLF9=u9Keea+73GdIda_|%DbU$Jam3Lc*w+sGnIJ8 zlgJ2dQ<+iyiUo^y=n;GenZD{E+P(__w-j}3192Lg+rNvxhYxLt|Lp8m76#V#o%sV*e=h>B0RGZs559>iT;hVeJ;N$a(k1+0}8GB#Wi>$#r z`6h1E@n2EaZC7Ef$N7)}%8>7MM#!LEKv%rb@AX(u5vFec1UgLpj@NDKv(#p)K zYEVxuk+Tlae}*h;sY9%LSXZ`>Wi!!l7^~j;_XTK+xUSXZf7rDd{rPl!?_l722cxi~ zp|S9hhGw*nHD$)!H}&@s4#qMcbu${o5tvpVN7tZvURa9s%eJOZx*lr2U}}>}-ElLN9Ee?7g+p z{=i16w&uM|yZwRgtFU)6MNgG1U8J*pH*C#D+MNi-gj|=D8MllD&s(_GjCO*$LOr2> zZ|e&Cm)Zuh%;|qpZ`wygzv&-9CS0r4`oKOv$1ON!5&ZWnS|)zu<6ihnO|31zX}Jo* zn)oQn0cW0ZojK^r3Vz{Gp!F`}(v^2%5B~>ySQqp3xPh#X+S}$DZlokBxN5OZq@-Q9rGE z#W69q5gZqzY*wtsUZHEyZms#1$P@eJ7(v6hFyCt!um6p{*V+A$nTI^nC$yQq;HVoH zp-uD#Kl8p%#^-RKP-FU6-dFo7?tzt$%e`jh%W`j8`L5hUt;6LWZoOFU-L2(v-`2WN z?meyd$^Ag<<8tq9eYy74pfLsez2X~@cWYk_gCFJxy%D*$@eF#uv}Jh=TXF5|7KVjh z#(u?*WB=FtB9)2wr?uHrIk%I?;)9Rbi{7&QMoSkG7SY z|MEUIZd?)rA&rXH^`{S3A;CMpe_~p(hj$dw9IDWZJ7)yR^Al!(1G7e{2 z9L{^=aApH1_ffFZ#m}LT9?B8)Os8#TUqipY!M+6XpG|pZY`YuL4~D<;OkB1=Szc~!a4DRYs#x#Xcq$f7L@t4Lw}tzqCUsrtSp@WssrZ@ zmk=lYP56KC1z#;=JI?-hp#dKh?-tF(%{-j=t$8@k!G)&0(r5XU-1{Ij|NZr#*{k+J zct7k+M|qO2$-h(m)``D;Wt+&+^2cHG(Kh&?V-qYNo%E^w4c@Op`32awnz;M@8u%)Z z`3;Y-d|+S4L(kw>2JYuO$|Q&J>{9?nzW+oR>e9*GI0GV`=RmNGEFPWZqRJA!OUQ5| zKkf|yU1Hm-^v{wl%ZD;-3W*HT$p`B->ooeL$ZId#Zz*@D43puvWJKJ|BQ#~d?14!9 z)Nd;f$mqt{Zx{DGZRO|WN7g!@&pjyB{ufR@`rEu8o?^Z%)2O|A<*2e(uN+bK>d9Xv z!SS@lvE6|~WD|Pim8}}b+9WuZX&h@DI96*qIy8>yNpOtQIEoxN3N?=L8b>B^Jd1ft z%9{2Cat?9qjCxvFi+R3>xW~zvqo`ly<`ZwIIYEvwXDn|Uh4@Ug9SyJ(eq+o8jFGQ^ z@5cQ$q)zOH9<|ZOo>UASLi&gAbkI^FI{3&d=)Y}iOO1}K4VsSYqjU@zn>rmuPb)ee zdM8T9{_Tp6{b=)k^rsZ*I8)KF73V8*j#T9F-f7S=PSa8Apd(Myf%^ZWJ5!_MGxusb zN*#1OnK~U4A5(O6zaFLI<#mdVm&4`e58p|V4ws_i@!bwOmYoJ2x~=cN&Ot}IrUQP) z4{KASB>Oh}!Mt2QWkeE+2=9Y1eYbo{&t`u}=rbc}dk(eckuIOuriH0U@} z(@`9ytO-09J z8y$4qc^Y)MG#%rjbnM%s=-Af-I*KWeLHr}MWiy`EZKaIgv_Jpi0?_29j|LxQ67W0x zv5ha4n>)nsU>=2;_tOjd^@%++x=dm{3rulc3a%Pt=zkswg@)7 z+iE8{Pf})NqCaiqaLkVU;QW=5svq9P^($)pl)j$!bN&LpvaEc`UmM1;?ED9BAH39u zGULmm6kQ+3?SHU~`yce9&nj#BVQt`#ei&&FO?j~k{juZsV%%xP#jYQ6Zvy&XiR8xi zj%}Ry6_5+oP&^{-CChoro8#$Uk_Y1D_%(fs)4+9O6j$$ozY^C72d>7y4%eyY9JoBE zgG>Br+b4TL<^uZb*^s@ldiK!{rDqQ;PgIVluV>G1{_FH?{L@O$J{lQY-+qEKT24pL z_Kg4A=-F@o%JPgj^z65%Rh|j--=Sx3Y#vBAP^V;EC5*n4wO1U&urHj{j}X=*Vn1Sm zq3<2Sxz4ONSm%VXN7xwb$w=Q*!sc^r)n_8|Ojx&hlKn7#(}gkgG1A6it-r%JRdZ3m z#dRd|4Jhg^@nSzszaL|W=qrJi*{IJSPow|JJ`nl}cc6S_?NrcsD`p=g=l&nek96|e zAm&N^mT&e*H@;`3{S0QHe|rz-v0)p|#abBT0sJGJ__-eu#xyx+u51_ZS+qw@6;~yjw`l0GyBV!=4orE-;UW(r`^PJS*m(pWQnA zi`*yI2$o=-55Fxa#okwYv5p`82+pI(zNH(D?X>5P_q>ZYedhOAE0$Y{E~$pWd%<v&_4?PJBKcI#Dl$)L6H?y3CMr-}4@)4VY*vE$UOTH7r_w-ml zx3(Sc^ohA=4fm@-JlDOx!~C#+Z3)K%s7LVGE|E1hq)U9tQ?x%(8uWJ^<>q%*ApdAt zjnaGSsJXnboL7#rqn#C6u&)jB%d}`{haSk@sEUqS(!hE{IVd{5^a$T&%mV92Fb~gh z)E3C1No0Zi66%Zk=GxkaZ=38P559sOU(BXVu@_gI&YU_6c%`ny>5T9uI=D&geaN{! z(M9ZiKwZSxTQ+5cePgc=ZaO=g@+b`98yw^#wn@ozcdU+{d~C4%q&#&y#r&k+w?mGb zFK!9$MOy{_%8Zd$id>kF=>5s)`hJJrYd_512Uc*y-xVG(7Q{Nr{cTx3SZDYVb+>SZ z9`lK72VkqUPk%A%m}>tVeTy<9YoFdPGG@${dFlNjPjdYU+jHa_rr#;;Jam@pxT9@+ z`F5Ol@CMd}7-&PpZ$RF;tqgD<&wdawv6%)UWIQf2o?TGA!B;m#Plb+6YoEvF? zuDNQQ6?49{9cCvq5C6YmM8U*{9^2E6y-u2P-9?Ro9B|)e$H2R;>_XK zvHmA@8Nh~M?)Vm42I{?DWB?tB%HRXY;POYJGB_5M0roU5Gp^#;K&%WT+`pFebiSZv zFb6UK-k@9BvcFIUMI#56!8GUr&O1JB8GLkOtPDP?O)P_P4-G7X33n%!!Q^kBt_+^F zY%_dJ>V5Em3&b9>Joude_H%x$y-n*a!tnm2&< zQC1$%FR2#_-?TXV*TvumZrEG2Ctski8^y2NnuPXQT1R7zg6IL(G|(@uV*kSF zdxz4a{f5DDefYZUH3yxd-!J_M=vYFW@a1h7_(*IA1h-||lpMO1T{FK89MVUC@1Bn5 zezt3nv>X2Nw2_zoinOmJOY7`|3H`eLrQNaYD`CXuKp(gUqWxjWm3!R%X(DtBYfEi= zb4^vb`Of>nbNcjN`l=5kb#rcCfc7bP7Wzk63mTW^O70h8`J}MZIjs-K`s1vin|q^S zy>A!xL!pfpd-VS9*t+@t6Ro>{oh@zu?hmr*mxxX2__egv!9V{Tw(62Up)L|fiN>)U zI7%Ei8#E4o>i{^oMx)HQ*w*i#LRM_+ZN0JjGTL{c4Cy=3Zc9FSFH2pOJ>pK%k2&^H z=tk)MibMP+3~P;h1MTqbTm6vV1IHXPD#g5V--h7X#{#Vn>b~VUX)Pkxn~y2E=ElpF zY5wpA@yEy&>zUF1L9UQfsK=6P3D*;Wo_Z~lGW~vi>+WAtu4R->>yQ^@y>77MmrAZD z*be5?{!y-0no+=^sd1DHghR=7><-94+5hV%bmK1v!os+2d>p>DT9K z+0Rk3kJV?w|3^LX#>jnxQ|_|Hhcc?5Zva2#JF3 z^JsmZ(pYZ3T~FVJwiRm{{r+7>PA~+WrVk~4(2xwQVXxr2YpmlhVR?aW?SK83_|-p+syp!MY@0Nt67<{>JR5#mzw$dIbtks` z2!G@eO~2(g=Cs=7hk6illpp8;9hcbUr`|{QD?jiaTYh63?DCT|N8ho_Prbk1ul(Y0 zsPemzeY<|;$G#!@eMQbTN!xT8Bf}WHtkLB-S>ZH{v2eXB%C>kT*E{I`?M7oAZH)Mp z*fS6^%~s=Q{_W-F@7vlYp^P&jbH+KwyXccZChu{L0sE<;AHdg}k2d3xW0)&G?-bKf zZ&DjCId3%9Gs5n2ez@a0@Y=84W&2bZ!qo$M&^96OEmphcOG92*7eX7&amXOXk!*Wy z^^a6Lzm0wJa`W8{q#OB&4-%aJR<_d*amth9eW(lDC|BZ^IstjIZy~ZopFqiLa&x)) z*6Xo&Kgf2jLvZ5N<)pry25(ZwP}ihBv5a$?QxEe2)~x!4S$<Ft+7^Nz@^L=+xDWkJ#fRR%`((X?7gsDVeeVk|_|}*9 zQS)rC(^wn6wxqvw$yjj8_Vlb@b^b=|o9G4oiuf^yXDxpV{yxg3TkKnhwr@Nm0pE_aZ-&leujSUe z(Z1X5!d2tT%{SiXxOwngwD+LZZgcTH2IM5RJJMo_cR^a9?Dpc=m*C?*cZXK`t;d2SJ2ww^p~$113DV4woCdPs{PXQKa4;8T>CD_ z0b^Ah%T;|2Io~xaIFi1x^f?^;gSRSfen!``Tk}!{XNVN zY=-|&IOI`gj2#=?y)O{l^z-cC%KtvUG8vzKK(o?T?_Go`%d(j#8!=hnDXq zK0&|k9+9VRzX}gSUT5L^JtLSK+Xun-KJcI4!nc>u<@L@oV4B~&ZI{70(C3&kkoZaM zKZz@YE@v6^(yvfu0GkJ&W3Q9}o>dwAeupZ9=d;D$bl?uXk}?q6*8aQ7jU0m@Ly;G+APmt6(}^kbZ5Ao|Su zbT?$X0doS7V|$SPgHx7Yt|~WwyC#@K|jCaL^XZIW4gJtQ+O#n;Re-nP+JICh9osu(DI>qpY%) zFpil6pY2~-z5~iv+BeW9xUnxW`%L9#??*__Hb%{7UYRJ?{55pjqU)!iA)(wXp9^i1(4^?855dv1=ACgoCp%+2he@Gu8o8|wEwf_Xlgk>ac9bm+l=YIa!E28}#*hTn%w3RDHPQ_kn zuuFEEcSTLP`R}JfXj6mdqWt~|Z8X0pW`78KAA%<3d%TD?CG7>Ji}c@M)A_9u#-`WA z_?gOA=nlRB9pM-o^#dxIS)ADl%2{AUe_ z-_p+k%i)42Q!9rE*2_8e^s(fD@}!QnzA+|Xj|);ZvDp3@y6_-4XdhZ#i!$Bw(>HL#An!@0_+U!S55#^GN!F#bN;=6?7&_lUlq{mqNafhs>)%|&8< z)z7o+uokXxO4pb|jHRsbVeR`^oN27r!xUnl2ehSFBjJ65{ZOtq;rP0gq1gHJUg7*8 zzW0=MBEgXZ&y6Mg7N7k%;FGcfZR|^N+@9-_qIT=DT$~$Rs>j`r2S;lE411|^-BNHB zd{f$%y`0NCcekbU^pT`Io$XxgdM?xBC-5wN zAUr?B?_Fs3xo(Qz#e<%mm?PR2KZe4(rPqBL*1FHaDeL#ft;dtO9&4?d)Kg36GcM0& zpND-4883qErR{IW_!Z}hi#HlY!I5c@%h}3b)V93L$bFjM3$Z?74Noq4 zLwoP{Gepl@Rq z2p+3ky45;^iQT5T9%m}!8-FA84EY(uu~}cRdrTnMF%;(zyI~v7|AqCPTLAi(TlJlNF?@Ht->H-QhT4*$^Bd~H^)WZP{%T--#5m=trWk&m zd^-4({t)iU=;O5aO#J3j(G8EsTM+9;lB+W8#zU_t)|@qV7MS7v-p_P>DN;|Z?pW1FGsmt$R*lOMeX zGHk8&C4X!y=nuEQ0AGc5;vTf6UB~>PbXjLs2EQdo&81wprrdn#+guYH+K8(d^BNeF zVq3y_Jhm-D8`_li=Y@wdj1`rU=GKieu12~`NH^zpK)ZYwF0`*WM%oj%C6<3+txKd| z1sOSMU+XfuI9?z;z?bJ34*B3%DcU}7>o+l0y3yie^nr47-(H^o73$J_oRgRj(A2f! zD#&#TWP@AK9m`se~fZ$ZO0k6oC~Rq)d{g-XvfQp&pwneKj4qaPxrgD zt}9*m0rEuKK;7VYZ#%~)**8#QV|r{d4^8vWR&XqreGt%frsxD^$@yaF1a%{!UbJbw zfSzetDZN;Mu|W8BWyU|eZPOlf=muy_N;~C+KB`4~_)TLyX>UJ_Iut|y1$)ZP7k9Tq zmlge=;~eLD?tuiF*SCgby`l%teuRftOavXVdQjou&8Y_!(1S9E&MQ3#+Pr1jygBvW z!5i0SJLgM9*PZJy0n7D<(2tOemw;c?Q*DFJN56FMErlbEk=O%x#Koa31G=AGX=GgO zGqMcc;XCHwjL;D4M5Qrgr4QAB_txF+Y^GtmU05SNz)9}a(*EGu%22M_o64`wtH-w- zqmH1=t+N$#g8p8;PW(;BdG!p#{`Gzp#yn9UvqLunpIhVOxI28Ma`PU32Z*van$gY< zWxij1{>0Zq-&k8Qag}+J{krxkYcY@gxyORR1DCSR0WH~IeZeR!$4UjAHVbRS)x2%) zFD@k?fI)xjxp#mUW5uxBzy}T9JR_yeq?2R^RphYqYpzbDkM z&#vpMu0K*9nBz}pUWJ*Izwn$R-*-*<$88S2z2)ZJ8dulE>a^a;81P+Q?J%yyvmpV zfcdU0+Q__%+x0gaetffmI)HfqPp}4k+O#yv$fM>XPu&qhy(f?CL(BPQTyKlMN+J52 zSWBG2`0z8J6=9&MDt=v|v`gCd5D#sg^tb41V}1$Wwm&C*bJ2}|yHVyl@ElqNThw)a zHpW>Vl0K`f*?4)9b564L!M70q@>rIoj6GvqhU3Q^Q^TCs1{sqPyuBZRr(D{LLhP%c z%Kn!!-&bzVzXf!Qjb?bia<}BkeyXB>8^;XG%~zkGZ_G5L72ir=@9Z}|V!w)E$OrFl zt&p{$iiSHA&~O&zR&LIF-IATqfIMUDpViOA+_*&p@_(JMdVKnG{c-q8r}x!26us3OW9faBw()O3@2Ru;)BEBhvGl(9L}Gdku7!=0@2i(6dgs0uNAKtU zHuRo+u0OqdKZ>Py@2LUkoz|b;S5|0xC&kkHO8ubpYCqnZOKp9z5Z`Yc^E>@cYo0>Z z7{GS4;(N?u+pjqr?ffpTMbvAN_i{YW&-x=iZ#U*JUbO7q&qm1@hI0%a^FADdhtD?& z=~|$-O-$z;50rZLSJQb`xZM2d8&Nv<+&eIxFD@UL&b@C8N~dl+DNo8$)(R$)C)?A0 zvdqDJ)lVlxX?)@Bfoa_Pt9~@1U#sRbJR@Uhboj8&_WduU5pDHP?sw2wJP3{B2BvZ7 z@_}eU2ANkVY;8XfQ0>|A>0Co3^9vX& zu)d2cH(z*8{K^NFUlbha`2NTtyT5=ge`<7nxMKji?mTU}wBJiPiSI^PVJwO3^x$)A zd9B(q2wef~!|Q&_=ex_ze;t9d)&t_RiC^$xMhv}N*MznPdmKG7AiWO1H%5*r(c5++ zb$UN{4EyZe5=-v~*C(bo{iD;N_gTxYQ*EdCmBko?q>ZMZivCeL`raR5tO5I^nMa}D z;yXF#30VKA*DDraU&)1PAC9~4YqQQ4mhWx)$A?bpb7BXI?@bcFCrSL4B=KvK#5X00 zuS^nOm?Yjh_kAG#jRDfbj-)+$_kFELlEm*z62CP`d^kybbCUR~B=JQ_;(bZt(+7`V z*_R}Kf0FoZN#fTfiEl{~KPO4NKS_LEl6cSH@hgrdi9e7eetVMm^-1D`N#bjg#Fr$A zACn~BJ9vEXSd#dIN#b`ViSJ4hA4(Emmn6O{Nql~i_^iR>zi~WC{GlZAyOP9rCyDP! z62Blxd_|J@f+X?TgU5gUM3VT!N#c8w#BWX#zdA{LW0LqllKAmS;&TR%Uw$%4d~cHY zJxSuXB#B>>B)%z0d}WgO!X)vzN#c#c<6Dj-iQktberuBWaFY1uB=J>A;){~R`;x?` z4<7%uz9jMct@!Hdy5)^^)$?l`8>{bFT(_*TzPe#iLvurIW5d_$mn^Nmb@38HG|a== zCjJ#KT5im2s$W!G;Tw19<%ThP@vY5Yt6fshxEYImrAwA9Uc%>Ti|zu7I^T@iJ0#9} zFI}|k4re^yeK*uLE(4SW1N=*Bn;RA{0^rPL&A!FA`li?4v3Lol5{=@D>S;4>D6W`R z!sn^>3&Lm2oKed7YlX~qYxd0RXH6}wo>f{=TsiFqqypSj z-~rwlrL(GwXIxt$I3;Z6^_9-Bz_iM#7`Bl#ftj`?x zS~>%ymrR>oS&RpTXV%P$ika731GcAD&eXUav%J8cHoDXvhw zRTST7(Htnges(EDPaLx=X9fa%!L#CJcJ+1FmtJqhTVa7&GpCl$R>=r^&CFTTiz}Ww}fX%{nyWk(ite7QDV~{OUqOU z;wv*}Ro_rLYqp3MI1x5&M!&G)%F0>Ora%E^SL-~cRu@-PR7b-}&(!LY(rb#Zuc*ZR zhG{6B*@4oj)2^8&#jN8KhnH4Wmd+pzvlSjEW5kOBpH(^=-%{au_Khv44tUyI|)&VQbSr;w+hbESkLe;g5pbCy!rQ{kDRz0<%csdn7rqUq2QuylY@Mz2| zt|(qs*RVLySi8J_$yHZfyR3EzrgV(UaE)F%dZ~~1%Ny%_^B3g%Mt|wjf?IuMU(fel zH2RY9myTb0k#9^v%jkl_#;UOZ^v!Fy?UMRMbq%$P3}0hIb8}<8^=kN*Hr%$TzRvJ1 zTcn>1qhV2V{cZJ2j9VKQ*ES=crrP-n>+5_Ci+rQ&eDjt!*Dp1UWs4GpBCiDvw=K8? zrdzP$y>ao^9Ir;cudbf>mo(Pb-i3VgLEq@cx=ZFYGz(k&QU8W99Hcidy=?x1+C_`% z8^I_;NWf*1d$S6;bqN}Y*dPTY5yOlMVcA!zBw!fVXujm$u;?~lU@=HSTQrToxC`+< zyonf$>vj0`T13$ptnC52OjEJ=YsU13mfCrZ^~Uv!?$A#xD++Du0v{jve{|ht{9&5d zxWr)=ume^At#=U&vzXG*tn(;V!snYRGDM-oqWSetGjQBM$Og#YD4u%B70iow9~yt( z>_Ex1YYgKqgaz;uD8I{~FA%^_pnRV3H9q4fQ2sSzKA-UuD4%cO=nx}-pFnx7K>-Kw z6DVI`kdXj>0_98Pko5pcEKuHTG%y@Lf$|39c0S`LP=347$Y=Zn${P(j4gvfG%I`2* z_>7-Gd5b~6Ab_7h`Euh{KI11)eyhPk3g9PDUT4(v89#yYdgI!PX;Y>K0&Iw2qGyzT z6>-(oCB>D+yw@+N7QN%Mm@s2r?b3$%)k~X~K-aFis(O0$`~~&%@!fN5c53GihYLh{4v9rx*gXyVMlp?)4*3?ra9R(PXf&Fzr8xd zd-0_ma-R%%{`tr2d!D*aEl~I3UUj#A86Lt*&s-Jazm;!DpE>H28r-km_TtCgJKp=Y zU#FkkgFyWG5$1OCg9k1-w*3(Fas zbw&UZU_8Rzxri?TT$Eq$v+nPwZA#ym(KSSn0a)&e-8`fTn2~AgkiLiEl7FtqRDlH; z$TD@en31or41zYCCHp8O_;GgwE(EyM{os@edd^TdNiWL39OZB9w_&rNN#8x>`$IQ* zHfAy`=ZugG)I96PGhg`=qx%i@mhO`_=zjTDKm?HX_SrC9v>@&L4w7d!t3*IaC zYkA#1GY(!A^7k^oJB$%oXM{-!p76)CYs|<@q;*#?ubT;y9<4Z01z}+_(sRZ(QA?JS zu@A@3A4b0Wm{!WsK_}BPMfMp7tQ^T3%6@he(sVOTCjlI3_?m@^swvdEUwH)d`3V1b z9DJ0%oM&-}27{Nyow@?g?&_>8?Rf2fJA7S^H zksBNFO#BPaBZ%gMkGj0^B(TTK$W4^Bz)rMb`4EE_@}cgt6kLt?S@=~Oc4JysI$yG) z6pQU~SD2AGb%4(y{NjG_4odJDA@B#y$SvCePhDO3O~Oa(Db*{gF82>#=j|Lp2oyRN zS*iSXnUQKg(vzBnOBrjIA75hfBR@yYNKH22n+czYK8HS{ywkvcZ57fL5q@C|{wQ$_ z&N`#lDm&<6TBRA8Py2I#>8cZ?V^B_1`%#8ztIbH=2?SGr9kM25Ou3M*?PjEY9q_NQ zVf)pS@9DbU?W7C*-`afp7pl1u=h+mo@Y$jwdR z67PS_pBV`@TJYyMp`4?_0oGGq~}>TZ&C9Ccu$rYS+xi7Uc!IE_`%`*W~99vP{#@Xv4Yoig)a_j zq%HI`nURh<;HMtn{$UI}ZK_IR*DkRup3P?D?i!?FJ9PWIF=-UMZH2^-@EkNF_jCb% zHR1oD;G_PJWgytrC<$1eD)`R?|KIFG+AgO3KPv4Y{A7avdn>^x_4fAH6uj6xKIYh2 zcu|T75PmY7&B(WMkq6t!+h0~`2FnBd-@hO5*@XX@g17VV*?D-ZJYbVDQUAZU4QZs@ zpG}?yybtr5k@c&Q#?O5IRizn(u3<%HWW##EQ=4x8m$*C<^F9pa{{U=_tAuGDA3P7} z`mk;@(v=6f?II`}7JMeea#Joi-#us4;1_jp7-Uog*tA0qSgAR&USu>8u^Gm21Wew8 zXYy_RK_0_VMw5?%R<;8wZ_7WAHXWWbnj{JFQ~(8NFsP^2AMwi=2`kAWP>3-z@WXE0^E*%V|EM-WH%f*rUU>}VdK%Zh3^U+4C%D{{KrnUf2oD94u^Qn|giZbvb&yv-EA4CS;U4S_Rm@=EcSQ(&9yuE-q z37B}kP8ddMUg~^}fIP2ZHgSB7@FTnm;VJSp0y4f9bPdQCXc~d?zqSrA70giSyj`{% z#ru^q8v*{WrHonxj<72HAj13b9F7Y=3Q^S{d`@im8F($*92X9L&&WkM@VK1vb&fgE zU?W+AKIF}#tUhD`;(f3aTY)EE^5su#JyK@hgKiVCiH$TWVJEgAjSt|r{-}Q%hFB@` z@Z4cW9)!*qRg4gsWvOnGs{0I-@e%lt5AMVBVH+-%7KOJ*C?Vd%us=cEl`I+CA1~ls z@S(bnqyU$1MmAQY00;gzp)MKQnB@TcFkkr4n^*_dB!NR4^8F&f^$r4;4}AMk=7d{s z!zGfBYRsU=u9I4K`~r`J_yZm{;>v(4RlDIX!HD8kq-~7DGsiH#LYzkjrKg4-J=9P_d4YYyUK$3&{Gi_>Sy+}#D5>b^n3?0%=}7c$5f9Tyyr zSCr>ND9<#OXWgrkM(3#7605Jv82BS%=kxKr#*A!+zDl1>+XAMO=HK#Ov}-}%(Ab6J z;CgUhM?|7G>CvV#M`)4`7-8WPKgxRwu&!*thEsqA%`W&vkDkDNk4;0iv%!uT_aQud zf-P%s-HUUh=2n)=})&-}TrVeSKLn-G2KHTfg$P;Bq6SQed%m-xS-UHb6fQ{Az3pUCz zaUKQSkrJx&#Vjv0BXFhzKjcLwa=Q|ker zjCR8A1ngnJrcf>!N6g5-AWbT;L$UzdoFZ%yV0!?Y0=+{Pn33%ib1Jaw09yvwBX+sO zm!X&=){!ASW@HCIjh#tgp%X(;|98Oly7K@#_hBZ8;^ojtbXePS#xd=mtR-JHT#F98)$V)1|Gs_5 zBTRI0byf|(d*I(PPVAy*yBYa8Xp?!Ycz8t-t&oE41#AJHqwRA%ugar9JIbKE?ibNoJmeuQ#>Dm?1rJp=57J(s7po|$0S&MrnXvz_<^pbx4d=8u3brT8 zYi73@`L{B_vmLrcjXBvejbfJhz0Cb){uJJj zln>-G4Eq0C2<5stir1#WnJy|9<)32YezO^LA4JqG6B(blTvW;^-G|M{?+zgEV~mizb2%VJ+GU80RpbHhh7s!}PfUQh zRV{(6hNJy|y8~(ccG@g8)%C2KICgoypSfw+#^GJw2S+?`#)i+V&q5B_NC(ZEd<4%m zl8*VR@CAn9NBJB1j_^FdhY;>ZctFDW6YbZt6+!^*-0&@c0}kD1V6amL@-8!@KENIX zEZ`mXDvA@qLgxv<9Ko~VkD;QUUhq-ug}$*4$JZ^EHG>`FKwdxKq4)898E+NFQV^GF zU&gx{@FxM^Z08$G^B6F^72_%ufKjp~U{nT*E=V7Lggpe<5T2v;G4Xg%9>yS$b}DcW zItG!0c=nTI*~E}AdR1T4t0p8r_WS!N^DPD`iLu`d$dLsMctG9fx`}u8<6b4Ov3&Xv z-e^YtlZpHiujc4gTch-$5OHC^0M>m(U_4f|RiT(XTMec2(Q}4sZKC5BHXmP^XY-K< zNbvEe6X3K+c)R!eo@=VH(4cPGT@66s)mKHd6M$p%9uF57-X#}$j38m&8Z6T zUt+^rcAT|Qp{{{oi3xVXIA}&9HGpT^t#G6Nhh9|1rXd@Lc6lDmd|=oH-45F$_j!Q# znh`UI6#d%)B2p9)Zam<&C4qz8am_(JBt1eR?)pCENHDuLLc{QidMzSyY3?Yu9MjAoZ-puktNIGR z;}KUSGAA+zZfpKCJ9a#z4o6QP0pQ6PdYxk-iuNYCjW`!bC7&+S{0G<+m#Rkz@=;?P z1gFh2_M7HsdvHH0`9y7_&gQx^b6xnOc68w z!+<$)oY7d6vCyA!dr3w^S;m5jjN1Yk zw^nAekGTf`e`X) z4dMw4`*Qw1JZ~0QXL}V@SPM36niu#1t8Ay!M#REq0Wa$DRYw4ui;I8#%5utuc3F~E zvtb0Yd;f~z5u#p?3NbbI%b-eF2MaL@C3?<%)yUR&Rn(r2~qg#FQ3Wp z#mVQg?a((L@m)rIwjEH7k)u7OJRzTg&43?6_%9OPkw0G@@+5r7G{<$}zM63&|JXXh zV5?6<_-%kcjyrwJWbk=_=rzsDx8WY`Ygm4(!W=*x;9MTlyn-S9%T>YEAP(|%6#%Xo zcku^=UWYuQ<3U+gy9=6W1ICkvL_Th`-^LsgmB-b$SmgmasjpWS;=UbZ&5$t%kuQm~ zZL(FSwr_d$*>?TYIKF%g2~x1@g=HzgHQ}_C?j&%uQ?Tn3=HT9<%2`B2^+u5B?oQLZ zisf-4fgV`y8wIO8eD=Yh=cZY-8j7Ime*&MNf1RPMtfe;WC$LP5k*_;n=bK=yhN^ce zWr%eR_1m?;G^du}u6#ge-M0KR0Zr(~QXV;`c@64T+M!rILQG=c*N1SV`AQYi^u(rN z1Hoho^J1AJU0^fr%H~FCRV-2J%I^A2^E$#8D*Sfa=7jSh9=u-%*%`-T;G$6EZ;ffr zVu+GY)HWkX`e$2Bv$79zkIyT9+-jXy;ce3Cj+o|6`;e|FCU2+DZY}MOj!m*2=9=a$ zK;f$EFFWgF0lW9X@~tqv7ywJ%mkEnd=@T;u&X}w`C6kW91QTt0&mszfhl$$8CgR z$KWd$F@vZ>l7~0w?rnrCWR#;E_-c(KFdn*l8}z}*jl$VZ1@Wv#Jj#DiJd8(MbQ@$U z<6_ffZHU#j#C{J!*u#JgS+I{Y9b;yy?C(`_r!Ir{ZJyp{p3 z7xzk4&Z<1(U>Z$xK^94TJwCuYk&@;Pcl2zwN;>n+#|2ZeRP z=HAXaA$W5Jheh33NGmL`!v=>1{|l)DguV9U;ILh$+32xg|7&pAeSodAV0#CLJr39& z3-*_T!qVp4!Fnh3?im<15B90TFfOH>{RDIBibLNB*93Mc6*T7NiE7 z4*o9-qzLN=Y*UJ`jerfO0BhONXj^i{=Pa898PSd|%)*^>8l^KS)WPj>6wl?a#N*kH z)HTGDPdtvks_jjvaXt7GQfH!eD{-Er5B@}BFYc+pZbtnAXiBgL0P9N;_9S5afF*Aw ziw5P*HO4d-RiuX3RS8&>V=DBzLV#V90&M^C9ydC!oE9Ai9!I~;_HC^(*c!x-$Axlg zS`8nGea5NZ6TTg=mVd?YXxre!TZd`hNnLQvH;7+h^>J9PTTF9Ftp3J~#n>%kK41?5 zwhwpCEKR+nKOKq!nZCc+*w z&1D|IO7kZ)V4j@sLZJ2S=naq$~wk%$2lI z--{B!Dy%h#Rd{Ov8^Uuk*aFb72C$TOB3P?$r|>J8Gfj_aF7HJe*11G7i4Lu((IBRQ z55iTiE@4=7AYSh3b6cyHLiXvfQ^7K%>9OZK(;<;mhG>WDsf(v|?X|_?%LMg)U z2Yd(M6Y(3*1L-*lc-E(#z<>2}W)hWU6kc%vo`>X{<|^`>TyJUsC1X=Vp#Q7F(3`}z zNwo>g*&3^4KI=@geGAeIB&P&AJ!GG0cAPkke8B$@@PAJg@}5HijDh&K%VQ|`zo!F` z>jr?g%A6^Hf9M>*_u)Ck@*BFwG{0F0_<{Lm#!6Dqi=n#!zaAm%A0(pR8kdNsk}|W5rxo*uM zdBoL`VIkAJKL_xK2h1Z%)fnK1{lEVJ>UkG}C;Xmq@$Hamg-FX`)BK(v5L*Z<-`Ngq z-++6#7w|g)pSXT2Is%9X?%}Zi>wS2pEGO(^Tr6%UpTxZY@LK`JvYqhbczAiYe6-=P ze;XJwhp;5xfjbH*ZQO9^|At0Dq~KEyNBQ%6&s1Q&Ie^`hB5Vm@kEH+$xp|vR^MP!@ zZUZBQace)LZI1G^3EvI)bryV_ACUjXN7G%a}MWE=VQT8G0xEr4afP}!JAw1 zLwLUjXQ$<+wS;mF&!tTJ9rJkmVWiE?F^V5B)&+B`j8MDbY2EnymN3o>>+CSnfg_)1 za^QTx^w!7T&S#ADwjv{aZP4(BaBf)VOMt!GNDuuQoy&W24F97>R_6nToJ&>) z%f<7_+T4a`r3Z0O0e+R7YvjS*8^%aAa2ah=4P)&bq{kU2YcJrWka6&`b5*{e%_DhY zn}*=(|a3C+i&Gf(*T!ZjO5&pc!{k-9AA8i;b&yqa-?U2i5=YZ!2jI7Y_jr8!_ zl70-%#rCv5|6vPd30|{hwoUZm{OcN|SpnWQiA;Cl z*^6?QJs$l(F>*U0CpiNRXKC0fAwSRB z^8pWf)cKFz4Je}{Adz(7HHS`?n%xav2hB_QP(B_*F zLK$?ROfd^a`2klx&lU|mk24E13jF>M?j@gX!TFRt_sfI3lnL~(H{6a1eWVwgz%x)_ z6M{IiPR?w>d3C^P1b>TjUx81~wS5cm8HI-bVc38@$Pe?s$Zu=gXd|ojXCJmu*Mj%@ z{r(PP4f0yont}5re`4f?_QD=89=wE5kAuT-UUbN)ROi^^yd|95gflt)WyY{ez@MLT z2;B;}RXAs}!>9=D!u#FGBR$Jsyxu5kyA?dZ7D3N)m*}E1xz&tG3ufSO>Jj)yB)OvVE4rJdgXYEqoxcHnZ|d`g!CSCT$(4Ery)mAB&OAX~1Fzd8&mpJ>|8%isFY_t0;1JkM>VO+O zhG3(HX#E`m+Kja2{#9l#X$fYe;f%n@?oeCwyuu*s2lB9WTjy6;W8_Gln|K!E@awLe zZ)8EP*`ZC45p*s*)yN9phv$d%nK?)o+@fgbxjVs47c3_aJlo8oAL((9J63a07v=n+ zA@Ap-9?03|tIT6K_cnU|PFbveS-SNo;ES< ztWWFM|Fk;he&X-Gj`18hw83Z->2v7wiPI;sJsz*x#Vq7lpaQ)I%Y zM881NPBGGn6MZ{xFw+me$2^MuhBx@FEQ9F?!*;!GKJb;pUxv>MKhPI?9JtV548s5N zKMddfW%L7IP_XQ;=7->`-ectJzL=ML+S5O?-c`RBGO_Sq41DjvU%C&p!T)Ffv2849 zLws8%`gg-X+XL|15H9qfuK|8Mqz5>>kc9_6Ryz6!>0$B-c?8qMfAg-*6Mszlu;P=o z!zV*O6MY8p(Q)?zZ+f`F@Mzo~OUA(MISt(2U=%m{WYRD3Xxv`nF7$)0E$~5kX6QrW zlXZ)K13u}GN*^GN{-=xOk{)U>j1^axx8#$TRmL&&Ctc{L7;O!RD~G)9h2Qz8QH5vS zFGN{DCb};HG(O~?(@A<(+}zSR4{}GJ0B}X22t&|x0MI_DRv$!(x$d_A`xgPw)|%3 zMNZo!l=U>t>oDlbD$@r)*tHhQD#%{T%Vu1EPg!u zQ_v5@)1UQEY%}~pcbTzvlo2R?*vO?Hj<}j~^W>9l6QSFHV|lT^h%+5s!A*VW3z-KP z#<*6U7WJ2U>i= zu)ZUBe<$rxY5OwK*M@zyV2NMwbtK1&u@*zdbK1;fv_;akWBjunV~Vt=p?lD-Y(yV@ zGY15W-110dZRakrH_}g~9r`KK26g+s0{z`*@xB(gcz!R(QUzx_&hy3j@~}tKL$?5T z9nLbQ4J(0toFr{z`r35Z^Hae6Gvs*>?zn8JpbEl;l~-D+QPrs z7XIQdA18C&|Cf%Fm79Ou@ON(ub)SI#AL|}`SEK!T)a`R+{Lx2$4L+K-8-Ko0>OJBx z7S|V^f_k|T^&9g$p<2h7*8)}d1{pugXFcZF8T`CAP=7tl2e!!{ocmeS{XfZg8uB=t z2(Q?TXkW%JHUqv^dF10e6ZB;Y%FTQ4wro#)Uq<|;`G&XfMZ*oh$xHs*Fb`mzJ7}Go z-T9_@l4qM2RfAs_`cP9w?PR`%Q(%LTrrh)|3u*hqv!B}mPuzo~uZc^4id+N zS3fB_89DhmCocAVon@H-*SZ9_>J_d}UOG)&1qpDSrE#q|JzVGR?~iNJD;n3h)5Nu) zKd#B|C|rMj`!sQ-C&0B)+C|q zePfg1u4=*6jcXsSV+hN_Re&pS`9D978Ke~dTo^PHp!3IHz;oJ`&L3aZ{oSYjBR6ae+ZkT6 zVSm`T+z2n(vAuJr7cghnb^h4j1b81{>N9}c_fPljEyyTh)EZQ(RO^8XzU z;yJq{Y@Bl->?%h1C3RsV)InK?4G(ZV-V;uHzAl`;uqW&qyE^Q;Ixp;+RDiHOpuZ>V z{?G2P(YhsU>YrWxWMp?1EYJ!IA8+&ly zj`BLtZG8Tq;VLS^`(eay!Kl-IJfkeB50ikW_9X6wxF0jzMMz(Z9u?LwyK4{OeJAdK zFKWRZ@I{5V1MkEWxC2)ZI#P>k@}EFM5$FMq;svHLF6|)K_8hyCZaUI2F zoX{_Z!6^Qj#^|)OapyuOg5@&8r(dFASUV-Cxo(GG@`tXIyj1XQ4z6B45NBx`0oUPb z1`dwBxr*?Dl(O@Yt5Ux(k!t~dJh<>^xL5E_S>w+%#*#LdAJ6*{ngu*=tjy9BC}9tC#7{2nOzYZ$9|xVF z)XrP+8vsBb0L#d46<)}14eqWc{nEmh4*WRq;LXOPc%XC^05D7*_TeHACqStSgb7l{ zxW?$00>;e23we;3op{N{%OTud z03MVq`~aXl){%$JxX1(X8N2ig@finj4T`4%06DlwkEACa(qk|^Ur3LUlpf+CJ>A5! z4HuK{#AOSZq!@>B4N4DjAbVFf<&%dO;Bn>S9;@I48x&6&0Ge_2kRE1DddP)cVUO|U zB(4GQNZnvnrK+iNr(B2!?^2U&y;881h@c$yG0&s8lEb&_b- z7JR!vgNu6MqWlNJOBvx}pmqj*+97+(qAFxsn3zNe( z17sKQN*k2~@9Dro=ZrnX+lOlqywv3dxTp`tk-vY~8pyT^7j1>rgn;UVb_IH4bR$m6 zB8jbFH1#JHnKc3qG^Av&NCRQ_Q@+P=B`FK)zc63FW{t2CS=q zuw-%&8YqVY086C~0j~?FTs6eoF*sh*O5CaFx~A7SOuR@l7`>Jp4gl6To*HZ_WlS2n z$RBAKGdN!IR);H9yo5h7DBk)sdYaK}@nYGM1$btQB0@Pz@vPA=>nMVqxC(Gd%n2QH z5_gwJzlco5orUkbiU>;V7(5U^3YEJwpoP;f364Rt(S_rmI zy{MQiI%XU0?hf^WUy#8}HG^qocqU2osNJO8t<$*=@Z~6eNFQU;{OU#0q|MPUBsPt? z11v4XmoR?3xFj$3M2c`BR{a4zYmhjSVp8&SglSAqE5>sPE*rWJc{c)Q6{MZkp<>cj zi0ju#iDjrQHbSBKXz$nXS(e+U;+y4ZNTj^PC;BnJBf$&2(X+);{=$i`Qj zHUmJAT-s)QOxb~`bW#8`>GaDpz?R{|y>KXg0(i;8y_qjN@dD6{UASlLQ!n_1{V+i8 zay;TsXjXnR5@Hi(*aj*Y$@Bp@3wNm;h^6;r_b&Jt-)D;AIf&?!WH%QNKHSNi7$)Lr z;Y$a8(sAK&Ks@d;P}l@K?l9ndxNpPVO=f^c6vcf4FUjzT+fG{}Uab#Vk}gqq;E@iK zrQr;Zr{_A+k@1M31o-Fb#?Km%K!fWJ;Khe~6YlQS_#tsy`J&4Jj~c1v1FAkPsZg}z zp%7O-t~y-epsN_v9rluuyMR)V(kyuSz(1?+%@`)}yYN$ki()>6E5B3gM%KW1o&IV=zVR#DUk8;I#BeEc0l@B|ntB@J8AG|0Xs@W`cAtN_838OxH3h2+JS*#NNs)5*gsg^MWUhYxn_96U0Bx*$XqAp-q;#hkyPq%ou|ZkPH9BAc?p7m+C(v7rn^nOWDK#b^bT{JJm4_c( z%KX*f1z1%~vwFcV{w#eY`W##xv^Q&T*|LRE*TV55m?C8~9oSexDO(?|N?fsoQaHq| z_`(Be;|qvI&Bvu;kPSf@$^i0aBWmJH2d=*pP?95m?zu2QA05MW0*1N**FnQLh2sAs z#Po3?j8f1r{&c`F{w&;LTpID8;QfN3N1em1ULG(g92VT}9yF2gkmD@pRa z8VXjry2x;iT89g9=dX!{yFj!>(N|pn7~mV#AD?^TVF$iE!{q}$AMjcEPBJ840=cVm z5C=Ryjpuy8#>8VTvxocz~NosdUW_64QIXY({OkfIE)($K9mXm+&5zGK;BVKf)DQk zcSwgTnYnMu((kfx+caFZhRY#3q}O~M%++vs7koYqm#5%3 zo-g>uC^)_gT)qx3&~Rh)`*;n9cY)J#I9R0N0vgV*;qWeSB|5xJ!?o!53Jr&MfwR(A zYPfCnc#VeJr{C)|9NqxOH z81=OXSBc?TR^tdiU~mq_HSu6vcpbt(B`uI|`ynU_JC zfF@UKS2X`x$dB_ilgGz}qka^fFx*#z&lq^l-|R#<@>xOMQGaSa2l%fJ#D&8SaGvLC z*u@z5Eo$Cp($3iMn(Z>*z;#pF&8>L z_u!YO7GJxpb_w+yb#+b%VUWGtk0EaQvgZ00MvWUcwYaopetlDO!{S8>7C5Rap^J5Z z=X22UjCy5P4jA(*2el& zwi>eRn%z9^ig~ql)%CGugL16djC3)gv1hNDL_YIL{NMe;#&m*SPq$hWXX? zOO`BNLjEtWTYN`t!y+93;Uery4U6uoZES$Rm)y3D8cmrKwy}QEZOsc}U@u?1thsve zt<_6v7u{B`;jUP+Y*BN=9rZRK^A&(-zT+3)Rlnrc#>HQ=fB{ptFikBJAqW9(DzdHU|t zcZ~N*c#ghj>pR9?B^+b2a`&kFOoX2ad^3*d`%Zmdqwh8Pj&U%7Lpt84AJ+G6`re`M zRr(G(13&J^H?0-y8Ma zukVlp;ivWK`!0PC>wBHP7wWrL-ODuHWt#3XO?R25yG+x4t)}~0P4~4OD*d%Z>VC~} zec!6@I=^c)AEp17z4MRHYX1NK>BoK`BuSD)lOb$t)go-Q^+T&>TeWI+?P|N$#ZbzSFb8~Ob8-K}TO`+1({ zdA**mALn&m=jX+8rsc@`)GgF2L?`F6{juKc580x#I?DRYwbVZ9KB6z!NzL>x7$nOX zyF@1=j!vkTJGBs+m-ldaHx!$mI=O zUeD!q)F_AP$8t&JMCS$+?l!q?a^Fr&t*fFC+?%( zBld~Axx9;dC+*w0yoJk~xx9&b1MTa%ypDP;?Q6KaipwjwyqtO|?G0RBLS0XL9hVn! zxt7cGsprsM&1D~T3GGE(F5q$=mvgDJY0uztI&~`T$y^@9dAQum=fmdi2J-D&U2 z<<4C0$mI^y?P)h;dBQ&GJz}4*o6Ebn+{ERb)LUrZ%;ing8)#q8<#k+cv^Q{h36~dfxt@9QRhLoLlc@($Cs8MIyFOfwrH-NQN!^{gD|KgX*MZB?)a|Ja(K-95_fqeX?Q)tpKJTP` zJM|Xo&D5KyH&Sol_KjR#OTC7A6*bz+9M_jqFQZ<{?H6&mp1O{DA$2YFeCitNIkJ6T z5p{vImn%b-UFp=rX;0;HGW8(ZlenBn9Y=c~F2_>G(B6~F-Ko3M-kHlCsXNdf&E@vg zhS-yLNjdTw#E$za=5Zx&K9_5#QBHNd$!7X_YiUP4cY+=FB+d0H@K=5*^iQegvX8o) zxh089B){B^T;4#vo_ZbEBQDk6xrocuE2x)qJ<4m^mryUF zuBWb}UPxU_je0S+Lpe>2a&}VrlykX+x`?`fI*&S+I)^%&I)gf$=_PY{5OorDB6S>f zAL>}@80wzX-6cJDGu(YzMjkLs2gct%jGrHt7u=r<>l1NXkW_Z2I?hZPeD3rJeiER%=STCrbb+* zW?U)A-yC!GdN_3|HR3bvh|koB&(w&|)N#~(s1dJeN4%y+yrxFHrtV7JnHuq% zcEoRL#BXZEZ)!ty%06n8N7++Q9#f+{rbc;8-9){Udb{Ys<$I zKI(evI_ibgwbb*eYpCZ?S5vQ|UO~N_dKvXn>IUj1)QhOsQm>(Iq~1=wg?cmfChCpU z8>rV)ucO{cy_b3q^=|52)J>v?7+l`R<@VIk)E%h1Q+K8AOx=;XCv_j{Sn3$+MCv%D z4P$G!!_>p-ZBtObt>+zvT#UBK_;2f2+EMt2PrfE@XWP}rA94+ zj|MLy9|K-S#uLjOSCNkeqrSF26XV}@>nAm8rTMq@n;OG73Ycsd+uAfr3`lcZ+wH=L zd0RUaO#M}Z+cUPkSZ?3LAG6V&oCo`v*gr&^ zhuqmC%BV5gd|3~Ei#$iYpdsR|VRX5q$HAzpwr9=Z9I`}r)cN#|5V`*;$Go3zr!0(Fv^gP2jW`uBTGWa3kQ&FGY4yC;Ye53Gb zx!_-gPpLDEV>8J6z^@6%z}`Jp_~az;RkUw3jN>}cjDB;l_O@LDqB?0-DDD|jh+ zm0=7xh&&uTm7Iq+9nvJP1jLh6LPlAV-VpAWjbE?mL;Ei9W3;a~jI%lk_eEWuRVBQjEg!f3yXKey+5+0C)r_K%$?!N?_ zNk-X6-$kD_;NOJLLLH77Py1f*`@&~0#9JKwga<~0YiUme|46&fFfQ;2_eUSN;AL`0 z)ZysE+M{XPY}^f=BK3*njcrATnPb``06B*^*eiFxXW$!QcBXhy&haHJ}Ydf_OJXP#R zHG$^}AB}vs-znTJ3H-F(7Te#^_#2IXxB1gUG(Jt^^E4i(@kJWfN`0z#Kt8=ucwaR5 zelp_u^jUH__+9b_a1(j2VeBx3|3JO%V0-38f4Q*(s>>oAMbY_HJ?D zT;YyIV6Vi#uK{(NEc-IvQ89K5BQFP!A+G@EkXM0SmApVH-c{^Zvr=vH-jG_Zvj6>-VR}axRG20ewADTev4cV-Yom_Ux@jj(LIhp6{~l6jDyAsPlBOfy}j=g{9v=E zo5n+=-~P41Fgzui{T7WIHU66Za}1-fvu!UtSK}g$)$tc&$X|QlzgDw1**r~4XIir9 z-_}6;PSfI8v7Qz0mZNI1^?duHVq6*D^ql_VVkIuO0X>vVyJ9!~q=8Ynw zj3s9Xg(C6|(w|NUg;|E4hB=QBz;*5Um=qqGO@9l%4$G2k*X(l1>^M*5{M zl97JtE;7mrQz{EV^`GSV+AAtU{=TggbjjOpOLKBH{6*zum9;blC>!{3`uJN&(~ zgz{CAMyU(Og#gn#+@v?KoV>p0KZ0%kqn-AChM)@Lv5Ul&iNKgxJ9%d6g- zym+tJdu%t1iY_wGaUuPRL1g$;UpCESyZzW?Vs63dAaij7a?z6Grp~4fe z|5lEnUHRv5Uq%d-Zeev#yYfQX*MKYOp8=jjUJt&4jQ&}99sLnc<*npoa07j+!4Hxf zz$=9()PkQOZw9Xuo>T<>FS!o~7`~(bM({7>z2HB|%kV*fNa2Zy z&371i4Y&(=JGck^qrtt&ao{uPgV=oi$%xH&uJD9vuvO3N!K1lt4tN~720TUD2=BNU zzH+A15j=zGECOFjZUVEOC#f_SGM(Mvo5-jK-(um3N#MK5o59QI6AONf_B`-Y)tkHvJc?yRxvHkqjyS6t$l2ik(kIa{ zrW@oEa3{$t2W6a|#Mt7%F4~dr^ef3ob2`g~?~NMMKcs(#Va#A#=OE_KcF5d2AL)Fy zN7w`2*`9NCly~P*=JBp=oAx-yao!b!`l%iu`{_xDt$LJldB&SpOX#PU)SuP*u3);ITtv&57PHC{W|bW@^YlVSkj5@3Vx7j z&NqyiGS_X}W-s^*#<>yO^1LQC7ra%{kKJS#pC2N8N)7l#@(S=k@;)%nSxzeg-!1k& z$>7(?_250irz7Ss672JfFETW)(fBrvS8BY~<}ahAyluN1#+RMQN#HCp;`#D2GGaLB zDX9m1_tTiUg8d5pbLOk$C1Cd9Jhbo3-Lx+?j9G1E+j#VuS;t7dwTlL;>m-=}v~5IQ z&r4qI2H{P`=fuBXF8F`KeLKUy!EVP{Piy?P#@czx?6$Ujwl;T~&3xN68OH33^un+wfR*8ovh?a?Ra%+-AE z(0GN$jT&q7nK_?n_FrtiBvNB-PIXCd&EB8&vj!gls$@K9_e&nve7>c<$S^KF!?s_l zoqJyTfM(xj^JPbAtj*;vyIiyXhcV>flP&6+lB$zr!>H*i{Ul)#m~)8)?AJAUv^Rh` zhV)+xzEZW&f__If7IbT7wyZyrDW9SyxHUxV2;THv%xR-=%CsqMaA%^`1Fm& zuMi~BUIeZXPTmb(PJ0sg3vwJj1j0B6>;zlwh#2NK&}R^Mr=*{NHof8)@*41HawGV1 zGSazX4S5UrTkkQ-C1bf?SCuw}I#;<9tmHoOyZ2NWE7=GP3nthzcUX8EN zSUbL4r;Vl8y{h?WiZlw zbKe=13eFJwph4hiN#{?(Df?p*N6!c*6Rd7q*P>3lUr?4CYg##UGdo+0*&R64cHHyM13*h^8CukN$t%Eezo~8XZt%HcKM3toFkkx5H25##Ts<0nU=go(O=tj56rTx8 zz#QLlmV$_fl)@#D1T!C&q( zwmk^;>f^*-jxsLhwX*U#;BAurK{epr73FOJ3e+BxL*U^nMih-YRk zc@fUPIFG#;G2GssY3>A{DtvGs@CD>_a3L9Gxt(=&a0B=*+L71oE1551zWrtLD)1-5 z6^QMd3ho!F;CW=E|IPJsogoE%;G4Uo4)Hr7#y6|Ud%Zz_N<5V)@5~7Da(t!tXerAf7!Q~9vDNl zjC_$Qn1I9y}xeC-Zx9&$&v=5rBaFbv}k zo@@B958lCha8=RZrx_dS?GE-Ud>6{N<1^Zqf%i(eCab!dc9?lDWAYmCC1h;7>sE3t z_z`kB_$6`8PuFzLNeh9qHW5`vNmpfKL+pjA$_DuGL+^ zoPSm~fakFad5S|$eZWNw{eDD2~+=w6Q;C?!z4$SMZ_!g0|^lZ+xc7ij=%fN+X zwe1}82JnsK&EN;gyTLDz_klkmqn|I`BaH7L8TTDTMtSe+K}NamOC+Pu+;=`X2b@PP z0nZ>K{`;;cF9k0n_rbn)r0i=Q(I>BbNXm}i?J=%=p8NPl!+2nkJ%&7>9pfH&MYC_! zc#rhAj&b;Ub0?;WdRTTcc`bMlc@vn|9A*#4e$mtezXOf$%I$@{l*gboi1RoZAEuz5 zzwJv#xxO7mt_J6m^T0F7Nb}q4$cX3L`^jCwy!O~J7j?qmHZx1D`GF)N}`P46H$#4^%V#Rj8jicAYO|e(F0a#gCF=qGWceocE9`^IdEI6X`rac4(~i1&?MmyuDQkE|2E6n*TG?PTm1kL)977{;TV17BJX?nnC)aHcT6pJ+VFd&HM@245-m z8tg}pa=u;D6U_an2Jt+~v9t#9JZ6ahB`d%^$!oyLGR~;qb9;=>LQHPLzR7EDoe=YV zjB`~C_MPss?SggiFX346KQQCDq7Gbd*ZE`GHSiT$pI>o??XyC==DgxU&8}UmS)pAQ zTJfUhqh0e~q4mSZwd-t;548Os*RC}^eu-vZqVaPYYw53SZ`)U@`47g)E~w9yeKq?K zji+inSNxA%2>+WkyLOFkr8aJ?d`t7$sqrs1KM|#|R^BJHef)_*n$KvB^EKASmM66R z@d<6;f8t5af4#<>^BlX@FrN6{wy$cZu@>7ZZJ%8=So2ZyM$~6_>eQdV;k+wn}?WW|h_-o;=dd>&X*j+a3*u@uYg@7Wwul2k#O-9)_nlp7huV ze$?Lfskb%WWAkdBk93-E7^`^>du%S4=Vd)HX0Be%^!MTyJ9z!JXDv8S>^*b9T0dX2 z)=vLvLu0LvJzb#LFK7A%hVk?ka-3oOrw@5Ocn*0Hc!O}4E#QxYkBSC=CEOkMXSDJ5 znbYmOo*ALBM`P{S@yvai{S`^`IE-V@e8Kcr;Fn>vKD_oA#*hd5DfYH&&lY=U#Isf# zE7ndHyLt|D?TuvYUu$1yI(xudv~+kresqapJR2cCN29#Ys`CrCu z!8yW5?gp3AXCIj7*IoO7d0xCiA&mGL8k$ zsdFgA))jd@S1Wu}B3PSmJjcAc#KQi(mcF)MJjXge7Cz6PY5P21B58JOgq`VhQ$9RD z>XvI5jfu<`K8=j08|t=k6}PPeYuD#rh@wB*;)Ubwyj~a}_G8w<&i&#Tr1Jvrv35cK ze_@vRbV>)`Abj)&ur^M1 zpS-N?`!BDw(|K9zpD%9^|ITRZmp|5gIN$5M9QM89(`CM4yb@viywX|Y7>(mK<~76P zP_9>QXKeN0cgaPD@#-NmpIO}*JY4uG>}#*i7k*gz@cPU{o58%E`OpUN2lT->_Udlo zhidQ(VI71YYyzJ`-U8-z)Q8rC^Ta-HDR`#vBiPs0FA`pkHd?=ito%3ANBQ&Gsd|O% zwWGxTFzWoZ{^V6)-j`X9dV8%(>^EaSeU0bf*CMai>cn2N5B!MmJjDOn>%#YU2X7I6 zK>2?|#u)h8ALIhVc)cTeC%BjJGSvC&eTA=!0S^|g*#hSI=C#paH@EErzF7EXwe4Kt z2Nl-~FIx)cyn5Lp@LKvH=GXr#T(b_$^T+G*z?|D(i@x**=fIEb1@pYP25t97jBGo1 zA(;1QZum_6|Fgs}{>!?3dKdU^vCm%% zev!NeO#k_fhVf>5+LOV@3g3sm@g~o4o|^-{Q|!;8jBm9SejauE)(~=MFyonD558UO z^K!vk$?)0OgPa8BweGp-KO1=;;Te>DBd@VPgYs_tOZ=ZjY;TVeUW+omeLJs3pl;vh zHPV~WMsM@l^|i?B?RS_L+Vt%oh3CWn9qy0wmVtZsh%nUqK0U$7^hpG>ednS6-8jC>gt^)rr90LHoX?Hz1vRGxxWE?r0_z-^X`elH^+eEg&R=z zcMHhK>)pk|Hz3$|9}>Pr`LNF~$_BGv-Le_HLws&UJn#N0ya@J9>{nOq19ubqRY-Hw z>B6_6Z){>cEG`F+75l>W;6mZMln?7+G0L^+ZQ4=in|2B>-V5FHUy3x}A1?m) zEC;iH-rE7(DE8}@f&Uanex_c-18nIu8^grM}b$14Mwb<`QSw3J4 z_n`lL!2PQ}8N5e)=54``Z5Tw{Pgp?x>_Tj7O>?PK=;o6*)=xbNSBG`H~hc*}4w z$HQATfH^m4^nu4qI!~g`x3JEiY5=o+pX!c`rM&=nMSIHOZItd5170oM3w7B3BjMxs zg8vfkju;|N5{^M$5$VD`GQd9J6IXzj(5C^sUbrjjA>wD@o=7t?MYuEKk1QAN0{_Un zg?sG+uN79;65D;;PK9aywR=V5$8B!kR{Yg_XYH2Q_IC3QSLZ%$-bdTDE7I(vHI5gb z4-0UT^r!F#b>RP!(JpN-6W-hi&Y)fKiNYVkzs+89XYiZ!&ja6Q=hfyC+GAlKM@HXh zW5s~}*5(kgf6x(;Z?d;FR??mf`wiT7JGg@O-Ke)stT$E9yM)!vy{PuWow|aLCL{f* zQ^`HS=LjE-Yz^R z2mGCISvmMm;en`!6Al)hnFQ`4TnhgadI?|J0FD<XrmMAg)czNC)_JMW)=8x;jCnEqj0vezbQOpBigPn3D-C&_$Z{2s3Pf0GA<+`vDd>+%u2B#n1!9Z6_?g}1H?g-8o zpG#2o6S*JFQR!SN_K|AatA%qC!M6$L_5?pDJPvhoBHM0K4EPnXyE=on2v1aY_N6H* zhCjqUp^stoIz)If%G;}(Fn+tl=#?NmbUB#)YJ}nuVjrdavsspC@Dwsi+N)T6&Opq) zW(uFV8GMa!|0eLA!ijsqtH@ixuL~!kU-e?UoVf|yL_6xI*IsfFe&~+zCuM_=p*;^A zCmfITV}=R$DFKfY?z?_$U=Fvr6kDxIZT`cG;6x@`MNd-!}TyloTs8`{wpz4wvPH%{(gZ+r5w z8lNOSThT@*_tor!HD;Y}-4353@!wGot``0bF`vAEcC^LGw`$uyNuLd{zbw411pKb> zC+omFg|}COf1w?99vewJVv9Xm_|s_cnY4EYr;s;;vxPs|4KAR48MsRL(`4}V!rM^) zu}jG4C$SHamx5Odf3z0-8hICZtMJELz(11b8^$RI+vPpwM2-7toT~9SjW5!8w#M~L zKL+VP!We47&(WR(W*vUK0`+#k*gxBccVxb?(>b`C#;0qXqVWWcr)$i9_#xW;;72t3 z8;mC%_FrxLA?hdQ`FBX7#^dSV5#PF*CA>2aywLXP%rPyt2%qUWO2(yu8^FDV)%#gT zB?-?$d5;<`JRAGhQ5?H!kmgZ5CZ2@;c@*bwvy;GU#Q)Tu;2pv>i2o>#|Fh8tx*W^& zv%!Oed+!8KrvE~4sqoz4IBwo7oLmj&G51`gb3DiJ)FoifA7-Jwj^8N$QxV(op9<%L zdvZQgp!|On`>ZXnAI&inG50)$+#P&2ITk#UjDFJ7I_~rVm($)E%wy=lz2JqC{t&c9 z&&9&CQ}G@_v9wDQ>h@F~Bj(qDi^S*h_Fx{f7AyikD)uXK!EclEz?>&6K)$DSrM&>m z70Md%GVdeyi~t8b7G<0*yHyHr3)MWJfSfv~?oK ztFM+BMqiG}U!gwxJ}*9B!Kd%duhccb*VD0#l@VlLF!uuL)=MD+lK9$7d z{@l%marRu>zYqIE1JdtvyVTEJ2AKQEohVD6_r!kR8Zi6hBDD4C9mRel%5wS{!Z)Ce zPEQwB*91=I9QAIbfBGV^FG9PV{l@9kBfazBlvCMpV6<*X)F9a;ymY6;oWGvb25c@DLaqv-?j%Y;>8CI_>BiLp-$Cf-j|g51280w*>rz*uPQfye0f| zBA9d9?@-UFUB&)GM=DsV>>sObIe&ON z2K=JfUs?y|JmCeDaU{pd=T?9@PCkovADJfpFBgF8gkLNIuN1yx5t!roTEsk(bDs4z zhLO&(`sHNsI`Mz67W|v=vup6Jvm=BzAhuB)Kd;&V&K3Jro4|8~7nXysCnNpr?ZP{J zVAj=+RfaJ()2`dG=Sh9;T?l($@^CQs&AlkgSdQVJqby@1B+bv!Mq{Vh>135_d==Bd zex3DzW`9BBy*6h@YOJ;Usb@0&XvBZ2=EHi}j{3>Y5})m;lWecBI#HqkI9a1QY8ZtxxAv#SyO^WlfUwe5Ku4P#<= zv44g7nK+b;J~OeLTn%1KMqN$(K=_9ke1k4V_~#DbBP7oM#TrKDeY7Lr$#)*EE)llP zfqgRD?iUy)^Vs)u6Zla2@5Q(582`_?NR!tv|A#i6#PR>PbntxUg*utU@%A^wKZ#@b zZ=J#K+I2NaYokejaN9iO)yekBJxSvvjYradA^bT${Gzy4V*VfMF!xU3Uz5O`Gym2H z%;_mZpeeq1jyd|dZRY`-A} z*LvEEz<-kGBmE1=HQ-ChXx}{TJTa5w!0*Y3ZJWflZxfi;yY`{ZGdW-SBf~J<+PS3r z1iP-L43TYrAA~qLuKvCrdEF^-{=OW{T|Gd{}M_JrAaNF(R8uDf^&+mRu1@pSc z?@8cccE0X=*vE2U@4z%WBK;km`KC%P&R+FFHhB8Jy zZ1S3R%fQbGN3H>H6rVL)z}tlzQCCsln*3C9Irw+sZUu%B9W8t`(m$n1cw{5a%OXsE z@-f7dd6IDFy|8x|z8~ey94h|3ux<3whZ=3#)K%QJ0eq(LldCZPREz&o#51;5_+7O7 z*gJ(^i2*+%yczWqJ<{B^@`5I?=TIY}%`GdNijdbN;qPK$znu1-;M;`1Kv^=Y=(8O2 zSd?9D`z6{s`ce9H2d|@jH~8a2jmS22ePWdT8#9JA>b;e*FPQez$Lxe1bp@Z(e(i(n zB={^bIDTq-a4(5@CEDm9lo$5X>)@Yo?x99ho4U3y!M=Dc*k~}ip#B&4G3{;6iNm`I zZ=2liMZ~-~Q|#x);N7v`XkQ2WER)+`gtjQS1#HwEr22nWve-{SUI%S3*+?)j?)@eF z-gekG2_KC9Fc$p*@wAzObT0c*><@z%FBbbLwebJQYH`Zd?gbb69XzU5j5q=>L-&k&Ea~Apx{Ev7R_Jh7K*?2k` z=|>4K%mp8Ba+|CMa6jSn6=3FDjcr|%O?%r78Q{6%^Whe7y|8B=-c6IdjAI(X=s&7m zzDAnv72=bk_*LQ43&7|Psw~H%T<-6MKS7_I8Y$c@6>V{caLs(Q#c1J#8l>+PzHAxF zo+o?_`pl$g;fn3>&sE%D6#CFEo5hZWf7^D-V z3k2V?Hjah#fb@dIKzc(4LevK!20_k)Bts%0YWu;Ep^)<-qafWO2Se1+_Hc+g5_X0h z15xRB$1T!3p{KPm+FS;C4Dub8m$xz6p|0DEgM5O6Cw$u;2SF@tdqPgY{l=3aD8Cw? zB9Lwb@`@;cTm)%ENSh%aBCJCph$ku=awTK|1bs3J4_QQAjs33*@)!i|AN@FFCFDuS zQ;_E%>maW}HbCBlyajn1@(yGx@keeVELl!~iLgquNAy+`GI-du=95NGfE#x}LLI~O*>Ke%PkTS?E5Fg}1 zNGSyE6NNU7Du$FmthT!WJR5Q;`6=iZQ$ASdf~Cx%avll|YcVZZ;y zF#jTcrQRz#@+WHa7xfp3eZkb*MMwNkbR_l_^%sf#!qlHq?+_jF8};wh*x%J(#2?gu zijMq@%NPsPU!*7;!v!MSQtzbxTy)eyTy96*p85;wCTg^S`isPvVQP#ErpDM{YK#e{ z#<*bWq10*AsnkQL2UDj|CsUtCeJ=Gm)Ptzcrap^$py;SrF7KiKnVRhr$@Yo-fp)8% z*bY%0#U6D8btmd0sXJ32Mcsw^XzH%i$53~pK9;&a^#JN5>SXHEsagK0zFbbAW<5l) z9wH`*Jz^sD1nL~>@zmp}$5LlgXHjFESAP)~P-E;>e-Rj~O^vbH)a<_z7|+dR_Jaua zg9!G62=;>r_Jatve}vWk7(3NpB*uMH*HB{&SAUThvrTL;n6qFznChWcsh|4=_ey_Wh}>gT9`6CGiV2fx$4k2-=nk~)g|59;Ts8>zpdj;4Nr z`bFxmslTK?i27jaL#R7YA4>fV^jnkFN*aU#rln6y+xf(|Ffu5s9Aqe17$g4DD_n8d}{is z^9J=7!TOG1J4CQuBCK?nPXzOg7{u+)p+1-TJnCd>#uYJ`%emB(si#o8sPjZej;0<% zok4v8btZKdbvE@&)a$6nQjen^Pn|>kGW7)NSEwgaze+ucn(;@j=kjaRuT#H4&GJQV z;4;e@$#O>K(e9>Z`$V#RBH1pH9ovgEOR3AK z-=_9bms4L%{SI{nbtQE*wU4@rdOGz?>UXI(QO}}&kNSPlQIVn}SWi)h(9ZrA`6HL3 zL`Qa}K8m^v_0iN_sgI%VMtv+b_wPvV-;q6N=l&hplglSib3c#l#pM{$5p1^zwp#?- zErRV9!FG#ad#Q7G^%ueRh+um}ustH!4iRjJ2)086>pz0^AHjN$V7*7M-XqvQBG^6= zY=;Qed&F$!KZp7f>Px9FqpqQzOFfVJa_afiS5RL`y@0xw`YP(Hsjs14NPR8!b=22W z*HPa9_o9kmr~y+I`RYR z-PD_@w^45u9dSSP1Jui?AEbVW`eEwj)Q?aZhnzQ?H?Z zn)*M~&rq+WewO+<>gTB&sb8Rek@_X-b<{6Yze4>g^?K^ps9&dkgL(t?f2rT3ev5h| z_1o0%P;=ag*u>@csNbjlnVRhu!S;&ykoJ$LKc?P7ZM7rYHR2Q6w^M&gy@UEQ>Ydb| zQ-4AIC3O?^SJYoqe?z^C`djMnsK2LXzl->R%Rf^8M7>9JB+C)Waz(P7>N=wOi)4AW zcEA|b57!|N6?@wP(aL82wV}3E#H6-H1U@GiF#(bT84tnFBSnmbWJ9tbnUD)0_2N zh2ZC!B9b8~kin2#$YjVAhzpVjaks@~$u_uL2}y;dL54zxL54#{K+cDZgj@j0gp7ye zKqf*aL8d@lkUWSRk`F0>cp!z4a!3VaI%Ec zltRiNUPw9QVn_v~65@kYL8e1yK&l}#A+sQ}A#)&?KrV$`2C0F}h0KFo4w(iDP6Y>^hBjjz! zJCJuFn;`E&-iQ1Q`2eyR@*(6S$j6W^kgbqykWV1nA)i8aKt6-)gnSP90`euK3Gx-> zYsfc{U65}f-$A~I?1uaR`4RFHWDoYabnFMCAYgd{-*K>9<{ zk>_OOF#}u;nF*N%nGG2QnFF~5aw+68NDX8zWFF*l$b85ZkSiezAhnRIAXh`Kfh>ev z3%L$*J){nD1LQ`?O^|xX&5&Clw?Y;{Zi6g_+zwd+xdU=1yf)MWDVqL$bTTuK-NN@g**p& z9?}ST0rDc`CCEC+%aB(fuR_*CUW2?2c>}Tm@?XfCkhdTkA#X$8fxHXZ1bGkgKH6b4 z+B*X>0g?+j5w?RN@sK#knUFIes*Qhy_QD6Z4WsHH^=QNGuzw2K0r?EF6Y@Fa3rG{> zE6CT7Zy>uM-$K5Fd=L2mvKz7)vJJ8ovIX)n~v-Um(9iet_(S{15UQ_K#qr;0O<)i9C8@sP)G;J zA&?lzNs!)mX&gTy#D3Ef62%Ldd0%BG^ly ziy;?5N+Gq78z5Ifsvxt?`I*b*DlRMb6}wA{XL(#h%1f#$l^8Bp!OW5ZS7B9IzN^IR zy{M|($S*8)Rr;!?x~3MF6%?0Ea}{|?$~_f^>FBh=#nqmQOt;UIRuJmYH{0W`DDciG3!6kD zl1M9?UR>cVEA^E5(lX;O}O6(f0anm86)#Uz4{~ZOivlgbwL%X&o?uz zG}I|6(^Kj34JmS$m3d0i-7~#aKBuw{9Om(*Om`QTxTltQMtRG;gR3eX{1W0uRE|Sl z18VW1bVz)FS<^$k6{T)pMuit0%QrI}nR&`Q6=@mMGZOj^@s^f* z%g~G?$|QzNPrkRJptRH>wImcj)l*qq;bBz;lr6Ng^^F_iEh{XZR#oA#;%KhT2AcIY z-BU5up{|C+r=ic3IuvwBLfAEl;nt+aJM@Lr1Seyn-#Aj+(*__`)O|{&zmGBI{p^q! z0s{8dpxrTeb@0v@yuL-dV(>;zJ7Tbp!)_St;= zG8~WnmeHOWYD53>m~u~5MI{bxh6{D+-$vD=-vb?BY}pK?;=&QWsub1c znwUMJSX2p=E5F+9a+j2N^HHfTPj$YhTpcZ~HB&v)ipyMBmydamIAbmmXsGb`iYmM_ zto8ndY1P#(93U&bW#+uYH9gM2hAJz=RaoIcEG1ra2mBY0HRURWiScnmi%UvIl%-@1 zPR&e69FmgdtMIr>2fHha^KqoiuP8Rdvks>5aqbe-b6Q1(x1w04HSuatLw#g>FY=V} z5E|dlU1CiMvJir&kiJRo5}9VHc~|j>l&lfME-bbZ!b;VE?rG-yA*;6=$V9nif;xiZXzWXM``nmD$krL^AV{306elaFf(xoV zRi45Ni&+56QtYp%(OJHP1Xt!%7h1~Wn(D6fxQa28bNjp%@d>U`t^!Yfg~y!NS`(UP zHYhF&-5|-zU_@z_FR|QRTrpx~saI_=n5C35n2c{RcI*7m12V0v-i+f;e6K-w)3m7je zJX75z>IzGkVKg%1r$f&>;3aKY=ldH~*nDTi@?mn?(zIU2BtVU7thU`j4tS(@)jVe`D6l&00NY z<#rA+wqnLAiTt0`F+OV7J|NvJJeN{%a|(Pn8Hz>Qg4MMT#XyL>dLwqI4-kl<-(FyC|qkE?Ddv- z+_++{F34I-xbq^5=G9@$E43xeD$S)-Zx!nU+sT5pnJSMzKCCq_aC5VvC0@5#Be0un zZ8QRxai^)r7tGYTWUaLBW%RXTu?!*0!CP42;=CXqH%BUPO_frdj8=}|XnRH^i3 zZ8NzAX029B+RCwI7ptrEA*q^mAqf_0?q)rV!+jjt*Zg;p+$H(u4Jo+=pn}OCVqWLv zg-+9gyD+{A?@Zle>oseG!TbGYrVB>-v7}CLi3iAwrt*b(}qU>_8c7j z%l;l@Thtvu|B^W@1}@oovxE6pTOH1Cu!f7ic6^q}!D1(DIoZX~`mR59sVgmQ?w>&p zg?75)V5chGU;Ne4QOFudttAen{>7l(#Ij4`vSby&3eH+)sr`!#&01z;{t;9Xv&T>_ zn==6`Y01_cNA=aFfyvVaV`+c~mIEC0@l`zRYrNBgX68IB4FSRUdiVqg3Y+?uO`Cr14dNJBYIi5;p?sNc?vK) z#Fae#u3daWCgyw7J4>mvi7%O?&7jE9NL3(QjeHfPdS)lyi=h}Cw0obG8S>ELNTziFj8rlJ`02YG%I$vUMx5OdP7ZK$4>3%e4JW0jgu zURVe2pb8Cm%t}&;k3)j#UT-;OuWTlD@~W-G!QN~i=?TFri_5~Vydd3EHqD1;jLqiI zw^WtPJWHjqg4SA@vMGbtGS|?x>V|>8*)j`p&j$ZOa!_9WCJXO{DE(=np+b{^gFhK3 zhXmx4YTY173!9NVWXSs{sL7|jSgpI4DmHm$@HnwRBcM!X*%>8qKZ66{se zozh{?O_oSlMl3+=uGQ}9SmS%FMsv%TigoV%sig7*fsOLN-Gk=0WWQX2_s zKh5;u!7LAM-i7T53F_JYP`8kxB=A_le1}$eORYfVxMA;ci5Zxngv67mCKe&qe&%#1 z#N1yV)eEt!7Iy54>bYriMM&W4S=WqWJiHf@m|BB}Eb(|)@OY!fe%U4@IlJydwo>D} zR8MH@;bK}M-qi3Ex~ocjIIF}J9GA~iT8=L08Ie9UJ}tdC4iCu06?m)%5%DO!RBX#O z#VtH}fKiTk1tl3~p~_~cL|ny{W$rQ@l1lmPVX3DyzZ9ojN|)nA9jCbRP~!e42<99m z$R%Wo;OL~PTAw61It5P?9G!wE3QkU;Qw1k4hsi=%x1i~Qlb`d1!O2sf2-{tr;`ppc!}2E)#NIG9++^30bf9i%_HW9z^r|gt_ej>rEj~8z0!@ z13PQWZc+Y&9j=9Y!+|ZM}jPrQ{}sU zexJ&;;8Pzpt21AG2$`Rm$1D9{5tM

`shNz+JJ?>h(tTrfr6KkqZ}8`ofy(^_5q6 z3##&c8HHJVxKF)UQ8lE(TUn{C3RpSHjaxm{7s#T;T{UopR#6dGoy~$Ch=t5aN&$8ZG!z zh_hGl#gI@h>r#hyiNc|qSYG-C5$Cj|>g`J+4ql92%h72kP$%O4LRyzaoKp+Rv(%x5 zn!Pj40rxT=+X9z zgc+e7XMY!a9AcD)9PRZL7Z&3^Nn8N`r@L>$fFZc^UZt*%SXZu6$_i3u;U+RJ598n~ zmuW*{;iacgyIgzlv7rR1M+Iy2jU4*&d6_QBE#4FT8T`16}W}zPHyl1~2+Jypp zZlPT!(2Vv)0!-=STQ!m=%48hgS{s0Oe(`jZJ?^WwDzSy5bG*ZJ z-FgiKPtk|pu%G!_hPu#@DDhY?kg6cmt0WmJ7*@;v66(*F`ntQACpCljVG%xtkdw*LTfXZ{|H3zhW_9@;0j5-N#l1i^t zPm-c~`2MqcBi4M|_yFeBf|JRBw^oB+TngT)^c`Kj1)TwD)tgFqJT~xA(-!4`xp8aW zg$g@GQ+??f%Zug5878g+f*bp5O$p4nB1WULShK z5#kYYz7gW__s=#$k_tK32=;I|i3oB@2sbAT_WM7X5r$;?kEeLSDYk5a7qnGB&dP$! z`4ibC<#P#c5q?78JdLWtDbRg$6!4BqFs$F`=Lu7IyYM#jBp#bxuf%=S-JS7;o64##x z3`rSQg%3&L^bViUGn>pZ`JWquVHBPps_vUzF*7YwuGL$%#B^`|MZ8LH+4_$y!($A= z*1#Oh1g#uA7iW%47@%F>2>wnICU$u6J>QErHt@6qPU`WUC48R=pSg5ZdP?w2MCHZ! z%vJ>+=^vV5RJx~opfJr!98qaq4a;!*iu~V|jPI|W`b?Qx=`BIIxGE!`9}9AN>{4dI z=Jh7rkd~{F>hYOozV9TP^~dWv)x|zuhrp8h>QY8=Mn;jWADA`6T|RiG&r_Mf>*S_o zJU+Kr$&Va47DlyJxT2_atqxahWCct1hpLwKK>f=MQ@xmguWh27LTW>ybg#Q$e9AcV zW%FH=D%_?p4fcCVRX){YOe0);9@UUl&J@@L>(mr7p+E|{6RM1A^gVtjm- z8&S_3Qsl|MsF|K^MmTYF_)mnz$D8{&FMwz6cQ^H`YeCrBn_#_h&{kQJYi_37?CS%B`7De1_1z-?TpD)iz`Cg~gJPtz>q?F~!OQC#wEA zhdt9WzlIv*nHm?8nL`P+3*lB4sn%t1lpv&n%$Bu0Sv>(Zf8_*N?Wzed>lG8YLaUa5 z6;l2{lUe!zlPY_l!Fv433SAXad1Q|WD597wC;yT;wKSKqLfx|h+|`opK2BdZIv!u` z9F9i;)IDp=NAUr5H5HLhw=>`nmjRX8-qG$dZ{=uwqtyI#r0m-vjsq>nR8N_=w75)t zWzyMoVC9HPl`(FBsxql=WJVlVw=$h(y=9NX8$#+Mr`7JM#na>QZAIMta938kd^5}O zQA>3@z^tA?H+B3{i1;q*2##LBcT~$_DP*{L>Z?S|)o3Eg_06VqIg$dL2 zg5tg=mU;_JEOqA>x!jy*yL_lgd*Yt&#RpiYR$$tf@2=D)^rpQ)-p&p3E=D~BnJSB? z;UiQ*MwJmhja%%)Gtu&XfU67-N?WH4W)5n#Z8E=fslJFG(ozAf&N0ftt8!=s(!f*t zYBV>Ct%8uRh?<}5bxON`<=9HksN#G)c!ZCumlae>6Nkhw5UG1hThg%-;%(O!bgfav zc#pWm=SIGE&*EvGJwlnCizj+PR{yD9kUe-f)Vgf2XV7pcJvm@F6qEf`c54?191g`x z8xAcuJZ*_l82!>NqCVvKw^JV3Y78(fcHbyf=e~ICAM<=K&Zw_DK>TT8|>$+ znCWD$GS8F^>*L1q^$Gt$&a5ha_gH;GIjt-oA6mv^21D^f5$YCwfP)ubs3;I`-Hy4C zdg1^l!fF%?S~Dy&$S@)!$dHbQ9ID*YJos*KzIrr3r4{6oi3ftbWw>o!A*Zj57#U~d zd%O-AXS$0kJ&vWsxei87J$-f8Js?OJ7-X1QLp$!T&a~B9joH?~`U>wPf1^>&Uj7^%1aWhJi0pcx9ThNlPvXQj_I!&`x0C8@w?t<^OZSu_Vp{pix)K3=^hUf_PY|`5JZf4HK)QZ#l zHsCPM&Jl-kb`Cj=bC{U>nlTq(ug06hCC!H&);~<-Vg17dAJ#ui{CfL%)n(hME$A%? z&1@B>EX`~crbNwbWtVGYz^D_t>#5ZRUimcv%r$Ta!oUA!WK8Fm>_<17NU z|C$+{tUH+j<4JSdIF_WD*sSsdj_08}RWpYqvwY2_;V|U#faI{f9(ee}QA;;;$a+7c zeQ2uA-Pqw!nXEo+kp-mRPIXB4|iBEK@`pOt-K7^payso1FH)3U##{zX#(sWio`DB8+ zXlcTQ7O0<&GA-t38dN%X#6T=KDHh zA}b&(9xEIx8Y>tp7Aq7h5-SkKq4zS0jDayOhQ+8D6k}pYjD!I&e`e3znK|>8tg~9w zg@e4pb>UDq$1WV?rgx#hE*$8%zb+i)x&JO4{v{2wR_;CAvkXN{h3w3j> zxF9z@@W6@-blhLX1$pkj;)0y@{KHh-=zw#o;A5M4ilq8`kXyWYR#sK6?l0h6DzJ;9 z4ufxs;Izgem5?(k2cOV0oKPpv#p)-7tWUq&XNY_n)%*dwsd%7CojMltu@d!p7j?bM zZT`?)z|Yz_|K6QkC>MSCx6HaeW&%*#i8s)z78$RgI$mpdgMHsK-DHc5Ej{ zwZH3*W+~Ng2WmO8I)kiI=+K`YbgYa7y#!iqgalBzXi@p|?h;8$Ws>&{s4~nIgW}2T}#opMmZ-(LeT$XusP_HxM=6>Kuky;W$KbLxj zS8wJPm71Groxe4kwYGUBH@(-iA~(HrwPv%{2C40HX7|I9&r_z3vn_1PfvO*Q3h{`h z{e$7bLoe@S2b<;owpg`c6bBcl(b&;!&g;!%07egcNH=}WQ7L#Fw%6EWki=oSYqfzf z*ml;(A2kBoYdHD@7_<#J@@T8AD=21u7-H-RmO7BBF~@ecj(wv|XU@9NGRo6>z(_5H zjU~Uyt9}ZFLnn95h;!v(q7?72D%ylC$KOX3Y;ys%O{m>(_YUqZMh`2c6b$ zw3O@xM_8G_Q=wT@azGoMH}e{3Kbw`^EjV1&zA5FgaVmLV91!LJ|QYI zA$&yC1a-X%XC;0wwRyHWPYJN#fMpx35k;Lf*j8+Kk!NPi#dTGd$o8f{E;~ggIFy3Lp4&!ad9dgSdr~y@t{f#^6!Kok)q~gEwqi$M^oSr!v zT3x*tyL!Y4-|@9R$I)zL4ryBZR_Lw$;DB(3j(`eg;#v!iFU4jgs;gB^bzP($?2|kF zBd6m95d7S#M2hNmcH^&2LukXkaIJ_yQ6brHSZ@ZAU08;*$mRlOmCmerec2>Y$NFhvhn zoz6SMzf+%K^7=omQ0Ex_VU6O5j%Fs_cVI`5v&!G`g5j5ds~W4K{_t#|_tM-KaG@BpBV4eF6JvwfOP12MFCZt%BL zwC*G|(@Wz+258Jsf{t(I+-3jsf)<6T`bsmig3xrFJJ>%yhP61Okoq}5GlG@|u-{4x zKOb;N-nr}l8#Ngwr~kvMbdKPk)Md~y&N>JLnXU0D$ZQQoL1t^z2|7%gZKvDpRPBS| z|EuoGdKzdt%4KR_rE|SG+vRF;BqR0q~^3=>0YpQ@zq5?Yh!akqG0p>7ELRNlHi}^5{EgDi1K*UnO*l+9ghv1m2T(T@xO>DwQ-rgjWWG+vpZThr^%h$rdPEi}-4=bb;k^ug zeSv4*6XKQ|(gqKaH4eiP{~oVq*r6f&7D`l@xwc`0h9;wzH)7lp4#2mhkRX_MhlpI@ z&IuQp3{q{5krq&3RhNKf5~v{GVM@}0QvH+#N?CDgkBpQ|x>G>jCBqnh{FM649m$NR z_fSsVAPBo@BMj}0gtA2{3_xyR66GM~o9tmRi@OwuNog39L&| z)Vcr#_OkhlYg*wr6{nauo2M8zDN{H+C@n0R3{cJKkfDd=(jg zt)dM^D_}H!SS^t?v&3XkmJ8PsAtPgv%b+aRYP+ddk2P1*R&Z=khU*-uNITpM@VQi4 ziZq-T*grAMnqdtuZaVygOdt60(LLe(@T-k?jB<3~hRq{h7{8p*lsKQ4TF;d{J zHJc1^!bn5J)$597qOUr%b*qp?9~4y|0nVGxQVeIZo6Qk3G#-UY>$;S^u~8Yspb z={TXh{0(u_UFjWgr~#W#vDV@RJ817IT6YA@BSmYiM|7}cYw3-9BQ;b+pwd-SYUvsCKeEx2{DG2Y*}j16c8&1ful_O-+OI_0 zk4R6yRXw%^!AuRz7AE_K_)rFoXzKtFm6H!L(DNZXt#py0ca8?yNk-GMl zP3@*gyD*WMc4UIix-;Nueu&!|jHjc(7!9L;N<#T6_HeU0QjhN}9d7bmL4VpG8un@KLbc1p6rW|9py zlU#Udm1Kj>BpYlh*}l7(niF&?J3;RjZf(V~T5Cyg#$FPfv6y7W$Xb)))8?f3v^@g@ zR_uw4AT1#yNQ)R6^kNw~MOsKsp-K_w+PvVxFH@SGquje+LTPVVOH=#e%z|0A0>_y! ze?@&M_9e5Gvp8v1;4`mK!e?*^pLszNK7+G-))JHUCCFWzT9cMd>oT=XThA10%ge0J zGG}PZotu+o&+(C;k1c8Qpw5!BOqmM{e@gr<+D`0ZfeYnn!OepCAOUAxKfP`dg!|I| z2S>ACskeh9aNV6K<+EgjXI3RyiW`FGg7pL96*2g zq>LQKQj3PMM(kTI^Ksr43oT%^+ytE#o1ksE6w(ZvhJ))wExn9tKoLa1A|>%HcF}89iCK@ajKHlrNNVB*wK#ey*ZhtrRvHt|-#-ohSlEfi6caM#qWZk=6`@=nFcqM6(BmfLYh08xMOwp@C>F$I7V0 zUc-*{QZ;BC80{(pLKONUjxcpUd;Hd6CbsQp!NpN!hrE*pkzMjm88d=B?WR<{@lw1O zQiyd1cJWh~V&1QD1GbNb7ASBimfaRV^=tXSnHkpO-lCf+@|Ji=9!;G^X!4L~t4f%> z90ASJPr&$E#a;f6fak)+>NV8c&yx;MXSC#7JkE3{qb9fA%zM^Df`}eDfoNh2+DJSk zI6p~7+uo&(+xJ$o)kU$A{qrCutpitBaCWNxa<*QR2Lme&(iJSK#{;32*gOp^2wiHK zJ>B}AloMdO904ZfM4%~0z`7ilg!drTsOlZVRwz!nZ_ud|MnKgt54A1RP~9{S*M%kS zD@wv~D7wN!mEs|>ZNZ(5w{7$UqjyZbKv&Lzn;fJ%SQVXz@{4acR>6FZ7Nl+O6lOhF zox(5L<8XUV?>F!D=#Q3U`t@jTOonI3`WfjO; zMH#n}WQ*f3COvGq&VmNfXVSJl*giE*PUgv$0&_!t@zW zQ<~}vNw2CMAfQWGb0-(napt=j?e#zXx`=)Jyo9~{&M(oPCofc|E@S1G%dx}SfiQpdZL3gnB66{5zF;zDub<4k=ZCKI-)s z-kw6}L07ZNxFC2Wd(`MSq8MLq&AHwJAs!7Qo*g6B|JY9A?suwZ+b{2@SiOJZ7#EgvBiVbx~6c;ChZuc=>Z}O(eN08(>WxxOhwQwRjj>R8wRw*&i$ib7lvmSHxJJ) zY95|r%)9^!_0sY3gf7-B#L7KIh?EC_UuXnCh?09qSX9VnO}teEaS?J&B=PY8QPJ@X zMz)aw+1V^ZL$i~pc!8SDF3_upxTjU|@Bm7#(?acZES$+OzWlFa-=~!0W>`cb_C~%- z;@MZE_6M#<;Zr72)>aV`I~(y8D=Ns)P{T9uz=j zQ1WxyXj6#5Q)JsCj}AZk6%D;WC?xrOcCy&wh%qSbqyb`B+9~6KlZI{QxUr5tG1G?+ ze0*MY$b09vj6@g0$GRQcU>rNchbVRlqg)3UK*rhd3aKRB)w+f!jcprb9@*Atnp=~& zwnpbsZH+C&v^82HUTmu8uL$XT-Dw9#g|;25abRLt+hdQ2PoKbovCDETWokF!Y)9ck z)G*nUDEJ$ZPx%QExOBH&B3K>*)!;F-)-`KDrF8 zB&BoEI)!OXc~;vP4~<0_A5w*?z3dREc7WhfPJNXKB0*|T;vtO!qkL&4sxjxJgC+0ncbj6Sv2P<(ttk1jh{P~eP9026FhZ?qyUHQNWE(*_}? z9Bd8dD8Xk(WJAv$r~=OpgfK5v1jSN}E&@vc#61lGo-?x4gQPHWU^>`=glmTz>5@9_ z<#tYh_6uXtXr&DxE0kbD_}F7~@Yul8W{1FWq(bH0PGQ7(Io1}H=bw`Xjx+ZgoI!c} zgT3ChDk*P2_YUP10>z=BSwz!gbdcD{8xI zA>ml`pylP^V8;}XCUGd(Lt}#^4D8@82ppgMprBkM3I9407yOM+%y4+%mlzKZ-o~)+ zaeOcW4*DL)1eH5a4`Hb6E7W4bs-BH1;lvxr-t5+}EwNDXw_CvMW;v(o9B%p79 zxaqg_?;qz^KMW$~&3duz>EHLw*;CVuvDIn0o-f*m@6l>X^F7B2^FP}9#?$@y?yqnD zKKkw_{{Pvh)%@Ydjr_g-5x?W_e)=1J-hTJfqd?E_-{fC%XUhm5hChx*zqFg*+Rt~Z z7hDrR8I7B5hmEouepaNBDgITzOlO;C^ED;Kc4EUr|#4wV9!?;nOq-H*oCXIRDI zm|kz5jF|fK9JeCSl`dyE@{X@1&8BYVE#LG)khVeQce(#N#2&giO57?`S zs~OrSzIHrZw&N}gvbn?SDK2hl$FO35pODIX9*|s-qS#^=P``7g>a=eNp}QeGyN(FY z16@IQuV}A&f@SKfM@29J2iro(Xk2NZ6V?~4`q&kdBv^M)+kXL=bQr5Ku-c@qFAbusU!d)c^-m0npbqVq+$<oV?Dk{zy^`rljk2m4i!<9}WHyh<|0Ra5_a>;900ch%JY-nu^|?OZkWzqjrW zNr+cX{qL>&gPmWL>wjZbnp*8L&L@2aW)y>)*`io9y-e_eXP z3L^z|!_@!Yw#uFmJZifq*!9=?e*5@{QCUH@eG6fc0(()~FN!&uB}$-Nsg(OET*QG~ zW$o;ByT(-_{vB^;J+hhKU{stUqVAxRFHOrMx|00g5a$eVJSq+bYz*U3aXfgQ;>vAT zyBiq|Nr$7c9wer>&(ie{?8XWLvw@7SnR;Nzc>X=|U*W(6zKJjThne#c^74s#bg4+( zh>u!b(8tpaU-rh%M}dwlb9BjHSs#Ai;vP2Kvc2ixTB%#&Frm6^a^p9AU_x~z-Nsaq zYXwX{q2V8g^v&G??4cSL`hw04LTlZXaERWWj6+mMq{`4gj!y3`SL#9i;2LvsPuOb5 z2JHSzrsi~hbZ*H>PRjPRZ`;>;b^kkVYr~LhpV%1a{x(N&#utc@L&NV49O|?B5m(3I z@-yd5xi6>!7N0!Ovipm}M}l0=zrf7BoQPY_x|g;iKSicT%7oIdI*j&wY32O!tE)^+ zw*pb|s5bJ;xxW_k*A^R9di<|>b};uKjXYz|J2c1-B=6{Idw)M4Du&Au&yc$16MVNN zV)n9S1}WZFGz5RaU5&4BBE8n+584cf0hl!cw|~CDqNLzzQrGK|Dm)+I&ZHefGHCeF zT|=;9=Xk_G_jq)$ zM7rT_q6Y-nHgYpz-9FddRd=nb87u6wPO@u0hpXeVP||gLaI9(Tlw>U7XIeHyuO^o!zaP2-PRn15{rm6M)H~@c>WHgR91?uKK^M_1T}p)@@V( literal 0 HcmV?d00001 diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Resources/English.lproj/InfoPlist.strings b/src/openal/osx/OpenAL.framework/Versions/A/Resources/English.lproj/InfoPlist.strings new file mode 100644 index 0000000000000000000000000000000000000000..fa858023ea538216b8928a45db9220c8d9e2f684 GIT binary patch literal 486 zcmbu5OKZYV5Jt~h=zj<9Y4lY;VFyjg>jz)u{5}jKtfH1XW&2--OIm%T<}!`%4sB$)(Gfmd?B1AfP34(|DGq z^{3vOD!bd3|5bUEttbCRcP_M3x4HQBBuSI@jbcQ|yH2fiC}S)`BxS^B;ak~+tAk}X H_p^ThA7)Pz literal 0 HcmV?d00001 diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Resources/Info.plist b/src/openal/osx/OpenAL.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000..a811c8b --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + OpenAL + CFBundleGetInfoString + OpenAL + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + OpenAL + CFBundlePackageType + FMWK + CFBundleShortVersionString + OpenAL + CFBundleSignature + ???? + CFBundleVersion + 1.0.1d1 + CSResourcesFileMapped + + + diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Resources/READ_ME b/src/openal/osx/OpenAL.framework/Versions/A/Resources/READ_ME new file mode 100644 index 0000000..378b781 --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Resources/READ_ME @@ -0,0 +1,86 @@ + +February 13th, 2004 - Apple Computer Inc. +updated: March 15th, 2004 - Apple Computer Inc. + +---------------------------------------------------------------------------------------------------------------------- +READ ME: OPEN AL - OSX IMPLEMENTATION USING THE 3DMIXER AUDIOUNIT +---------------------------------------------------------------------------------------------------------------------- + +This Read Me should accompany the 3DMixer implementation of OpenAL (Open Audio Library). + +CoreAUdio SDK Requirements +----------------------- +Building this implementation of OpenAL for Mac OSX requires the latest CoreAudio SDK (version 1.3.1), due to it use +of new CoreAudio Public Utility classes. + +CoreAudio Version requirements +----------------------- +There are Recommended and Minimum CoreAudio framework and component pieces for running this implementation +of OpenAL for Mac OSX: + + Recommended: + ------------ + OSX: version 10.2.6 AND + QuickTime: version 6.5.1 + AudioToolbox.framework (version 1.3.2) + AudioUnit.framework (version 1.3.2) + CoreAudio.component (version 1.3.2 - this version contains the 2.0 version of the 3DMixer AudioUnit) + + Minimum: + ------------ + OSX: version 10.2 (Jaguar) AND + QuickTime: version 6.4 + AudioToolbox.framework (version 1.2xxxxx) + AudioUnit.framework (version 1.2xxxxx) + CoreAudio.component (version 1.2xxxxxx - this version contains the 1.3 version of the 3DMixer AudioUnit) + +---------------------------------------------------------------------------------------------------------------------- +OpenAL Extensions: +---------------------------------------------------------------------------------------------------------------------- + +This implementation has the following OpenAL extensions. These constants can be found in the included "alctypes.h" header. + +***** ALC_CONVERT_DATA_UPON_LOADING +This extension allows the caller to tell OpenAL to preconvert to the native CoreAudio format, the audio data passed to the +library with the alBufferData() call. Preconverting the audio data, reduces CPU usage by removing an audio data conversion +(per source) at render timem at the expense of a larger memory footprint. + +This feature is toggled on/off by using the alDisable() & alEnable() APIs. This setting will be applied to all subsequent +calls to alBufferData(). + +***** ALC_SPATIAL_RENDERING_QUALITY +This extension allows the application to specify the quality of spatial rendering to better suit the resources of the CPU. +At this time, the quality settings are only applied when rendering to stereo hw. All multichannel rendering uses the same +spatilaization setting on the 3DMixer. Use the alSetInteger() & alGetInteger() APIs to specify and retrieve this setting. +This implmentation provides 2 setting constants: ALC_SPATIAL_RENDERING_QUALITY_HIGH (HRTF) + ALC_SPATIAL_RENDERING_QUALITY_LOW (EqualPowerPanning) + +note: This implementation applies the setting to all the OAL Sources of an OAL Context. However, spatial settings can be applied to +each input bus of the 3DMixer, so it is possible to have this setting on a per OAL Source basis, allowing the developer to give +quality priorities to the various sounds used in an application. + +note: Currently, all stereo sounds are 'passed thru' with no spatial rendering applied. This has the best output quality for rendering +what are typically background sound tracks. However, the 3DMixer has the ability to render a stereo source to a spatial coordinate +if this was desired and support to do so would be trivial. + +***** ALC_MIXER_OUTPUT_RATE +This extension allows the developer to let the AudioGraph be efficient about sample rate conversion. If for example, all sounds +being played have a sample rate of 44k, but the output hardware is set to 48k, then it is best for the 3DMixer to leave the +the audio data (Mixer Outputut Rate) at 44k, letting the output AU rate convert the streams after they have been mixed. By default, +this is set to 44k which is very common sample rate for sound hw. + +***** ALC_MIXER_MAXIMUM_BUSSES +This extension allows the developer to optimize the 3DMixer by setting it available input bus count. This allows the 3DMixer to be as +efficient as possible in resource allocations. By default, the 3DMixer currently defaults to 64 busses (note: the 1.3 version of the +3DMixer does not respect this setting, so always confirm the bus count with a get call, after setting the bus count and creating a new device). +Use: set the bus count before making a call to alOpenDevice(). This will cause the library to set the mixer to you desired bus count. +as it creates the AUGraph. Use the alSetInteger() & alGetInteger() APIs to specify and retrieve this setting. + +***** ALC_RENDER_CHANNEL_COUNT +Because the audio system has no way to know if a user has actually connected a speaker to an output of the audio hardware, it may be desired +to allow the user to clamp all rendering to stereo. Use the alSetInteger() & alGetInteger() APIs to specify and retrieve this setting. +This implmentation provides 2 setting constants: ALC_RENDER_CHANNEL_COUNT_STEREO (clamp the 3DMixer output rendering to stereo, regardless of the hw capabilities) + ALC_RENDER_CHANNEL_COUNT_MULTICHANNEL (try and render to the maximum speakers possible by interrogating the device) + +---------------------------------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------------------------- diff --git a/src/openal/osx/OpenAL.framework/Versions/A/Resources/pbdevelopment.plist b/src/openal/osx/OpenAL.framework/Versions/A/Resources/pbdevelopment.plist new file mode 100644 index 0000000..151e2d6 --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/A/Resources/pbdevelopment.plist @@ -0,0 +1,8 @@ + + + + + PBXProjectSourcePath + /Users/mmarks/Development/OpenAL/openal/macosx/al_osx.xcode + + diff --git a/src/openal/osx/OpenAL.framework/Versions/Current b/src/openal/osx/OpenAL.framework/Versions/Current new file mode 100644 index 0000000..8c7e5a6 --- /dev/null +++ b/src/openal/osx/OpenAL.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/src/openal/stubs.cpp b/src/openal/stubs.cpp new file mode 100644 index 0000000..86109eb --- /dev/null +++ b/src/openal/stubs.cpp @@ -0,0 +1,131 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#include "../sound/snd_local.h" + +AL_API ALenum AL_APIENTRY alGetError() { + return AL_NO_ERROR; +} + +AL_API ALboolean AL_APIENTRY alIsSource( ALuint sid ) { + return AL_FALSE; +} + +AL_API void AL_APIENTRY alGenBuffers( ALsizei n, ALuint* buffers ) { } + +AL_API void AL_APIENTRY alSourceStop( ALuint sid ) { } + +AL_API void AL_APIENTRY alGetSourcei( ALuint sid, ALenum pname, ALint* value ) { } + +AL_API ALint AL_APIENTRY alGetInteger( ALenum param ) { return 0; } + +ALC_API void ALC_APIENTRY alcSuspendContext( ALCcontext *alcHandle ) { } + +ALC_API ALCdevice * ALC_APIENTRY alcOpenDevice( const ALchar *tokstr ) { + return NULL; +} + +AL_API void AL_APIENTRY alDeleteBuffers( ALsizei n, const ALuint* buffers ) { } + +AL_API ALboolean AL_APIENTRY alIsExtensionPresent( const ALchar* fname ) { + return AL_FALSE; +} + +AL_API void AL_APIENTRY alBufferData( ALuint buffer, + ALenum format, + const ALvoid* data, + ALsizei size, + ALsizei freq ) { } + +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent( ALCcontext *alcHandle ) { + return 0; +} + +ALC_API ALCvoid ALC_APIENTRY alcProcessContext( ALCcontext *alcHandle ) { + return; +} + +ALC_API ALCvoid ALC_APIENTRY alcDestroyContext( ALCcontext *alcHandle ) { + return; +} + +ALC_API const ALCchar * ALC_APIENTRY alcGetString( ALCdevice *deviceHandle, ALCenum token ) { + return NULL; +} + +AL_API void AL_APIENTRY alBufferData( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALsizei freq ) { } + +AL_API void AL_APIENTRY alDeleteBuffers( ALsizei n, ALuint* buffers ) { } + +AL_API ALboolean AL_APIENTRY alIsExtensionPresent( ALubyte* fname ) { + return AL_FALSE; +} + +AL_API void AL_APIENTRY alDeleteSources( ALsizei n, const ALuint* sources ) { } + +AL_API ALenum AL_APIENTRY alGetEnumValue( const ALchar* ename ) { + return 0; +} + +AL_API void* AL_APIENTRY alGetProcAddress( const ALchar* fname ) { + return NULL; +} + +ALC_API ALCcontext * ALC_APIENTRY alcCreateContext( ALCdevice *dev, + const ALCint* attrlist ) { + return NULL; +} + +ALC_API ALCdevice * ALC_APIENTRY alcOpenDevice( ALubyte *tokstr ) { + return NULL; +} + +AL_API void AL_APIENTRY alListenerfv( ALenum pname, const ALfloat* param ) { } + +AL_API void AL_APIENTRY alSourceQueueBuffers( ALuint sid, ALsizei numEntries, const ALuint *bids ) { } + +AL_API void AL_APIENTRY alSourcei( ALuint sid, ALenum param, ALint value ) { } + +AL_API void AL_APIENTRY alListenerf( ALenum pname, ALfloat param ) { } + +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice( ALCdevice *dev ) { return false; } + +AL_API ALboolean AL_APIENTRY alIsBuffer( ALuint buffer ) { + return AL_FALSE; +} + +AL_API void AL_APIENTRY alSource3f( ALuint sid, ALenum param, + ALfloat f1, ALfloat f2, ALfloat f3 ) { } + +AL_API void AL_APIENTRY alGenSources( ALsizei n, ALuint* sources ) { } + +AL_API void AL_APIENTRY alSourcef( ALuint sid, ALenum param, ALfloat value ) { } + +AL_API void AL_APIENTRY alSourceUnqueueBuffers( ALuint sid, ALsizei numEntries, ALuint *bids ) { } + +AL_API void AL_APIENTRY alSourcePlay( ALuint sid ) { } diff --git a/src/renderer/Cinematic.h b/src/renderer/Cinematic.h new file mode 100644 index 0000000..240691e --- /dev/null +++ b/src/renderer/Cinematic.h @@ -0,0 +1,156 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// +#ifndef __CINEMATIC_H__ +#define __CINEMATIC_H__ + +// RAVEN BEGIN +//nrausch: I made some semi-heavy changes to this entire file +// - changed to idCinematic to use a private implementation which +// is determined & allocated when InitFromFile is called. A different +// PIMPL is used depending on if the video file is a roq or a wmv. +// This replaces the functionality that was in a few versions ago under the +// "StandaloneCinematic" name. +// RAVEN END + +/* +=============================================================================== + + RoQ cinematic + + Multiple idCinematics can run simultaniously. + A single idCinematic can be reused for multiple files if desired. + +=============================================================================== +*/ + +// cinematic states +typedef enum { + FMV_IDLE, + FMV_PLAY, // play + FMV_EOF, // all other conditions, i.e. stop/EOF/abort + FMV_ID_BLT, + FMV_ID_IDLE, + FMV_LOOPED, + FMV_ID_WAIT +} cinStatus_t; + +// a cinematic stream generates an image buffer, which the caller will upload to a texture +typedef struct { + int imageWidth, imageHeight; // will be a power of 2 + const byte * image; // RGBA format, alpha will be 255 + int status; +} cinData_t; + +class idCinematic { + idCinematic* PIMPL; + + // Store off the current mode - wmv or roq + // If the cinematic is in the same mode if InitFromFile + // is called again on it, this will prevent reallocation + // of the PIMPL + int mode; +public: + // initialize cinematic play back data + static void InitCinematic( void ); + + // shutdown cinematic play back data + static void ShutdownCinematic( void ); + + // allocates and returns a private subclass that implements the methods + // This should be used instead of new + static idCinematic *Alloc(); + + idCinematic(); + + // frees all allocated memory + virtual ~idCinematic(); + + enum { + SUPPORT_DRAW = 1, + SUPPORT_IMAGEFORTIME = 2, + SUPPORT_DEFAULT = SUPPORT_IMAGEFORTIME + }; + + // returns false if it failed to load + // this interface can take either a wmv or roq file + // wmv will imply movie audio, unless there is no audio encoded in the stream + // right now there is no way to disable this. + virtual bool InitFromFile(const char *qpath, bool looping, int options = SUPPORT_DEFAULT); + + // returns the length of the animation in milliseconds + virtual int AnimationLength(); + + // the pointers in cinData_t will remain valid until the next UpdateForTime() call + // will do nothing if InitFromFile was not called with SUPPORT_IMAGEFORTIME + virtual cinData_t ImageForTime(int milliseconds); + + // closes the file and frees all allocated memory + virtual void Close(); + + // closes the file and frees all allocated memory + virtual void ResetTime(int time); + + // draw the current animation frame to screen + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual void Draw(); + + // Set draw position & size + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual void SetScreenRect(int left, int right, int bottom, int top); + + // Get draw position & size + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual void GetScreenRect(int &left, int &right, int &bottom, int &top); + + // True if the video is playing + // will do nothing if InitFromFile was not called with SUPPORT_DRAW + virtual bool IsPlaying(); +}; + +/* +=============================================== + + Sound meter. + +=============================================== +*/ + +class idSndWindow : public idCinematic { +public: + + idSndWindow() { showWaveform = false; } + ~idSndWindow() {} + + bool InitFromFile( const char *qpath, bool looping, int options = SUPPORT_DEFAULT ); + cinData_t ImageForTime( int milliseconds ); + int AnimationLength(); + +private: + bool showWaveform; +}; + +#endif /* !__CINEMATIC_H__ */ diff --git a/src/renderer/GuiModel.h b/src/renderer/GuiModel.h new file mode 100644 index 0000000..9940b18 --- /dev/null +++ b/src/renderer/GuiModel.h @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + + +typedef struct { + const idMaterial *material; + float color[4]; + int firstVert; + int numVerts; + int firstIndex; + int numIndexes; +} guiModelSurface_t; + +class idGuiModel { +public: + idGuiModel(); + + void Clear(); + + void WriteToDemo( idDemoFile *demo ); + void ReadFromDemo( idDemoFile *demo ); + + void EmitToCurrentView( float modelMatrix[16], int depthHackInViewID, float *shaderParms ); + void EmitFullScreen(); + + // these calls are forwarded from the renderer + void SetColor( float r, float g, float b, float a ); + void DrawStretchPic( const idDrawVert *verts, const glIndex_t *indexes, int vertCount, int indexCount, const idMaterial *hShader, + bool clip = true, float min_x = 0.0f, float min_y = 0.0f, float max_x = 640.0f, float max_y = 480.0f ); + void DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, const idMaterial *hShader); + void DrawStretchTri ( idVec2 p1, idVec2 p2, idVec2 p3, idVec2 t1, idVec2 t2, idVec2 t3, const idMaterial *material ); + + //--------------------------- +private: + void AdvanceSurf(); + void EmitSurface( guiModelSurface_t *surf, float modelMatrix[16], float modelViewMatrix[16], int depthHackInViewID, float *shaderParms ); + + guiModelSurface_t *surf; + + idList surfaces; + idList indexes; + idList verts; +}; diff --git a/src/renderer/Image.h b/src/renderer/Image.h new file mode 100644 index 0000000..bad3d25 --- /dev/null +++ b/src/renderer/Image.h @@ -0,0 +1,514 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +==================================================================== + +IMAGE + +idImage have a one to one correspondance with OpenGL textures. + +No texture is ever used that does not have a corresponding idImage. + +no code outside this unit should call any of these OpenGL functions: + +qglGenTextures +qglDeleteTextures +qglBindTexture + +qglTexParameter + +qglTexImage +qglTexSubImage + +qglCopyTexImage +qglCopyTexSubImage + +qglEnable( GL_TEXTURE_* ) +qglDisable( GL_TEXTURE_* ) + +==================================================================== +*/ + +typedef enum { + IS_UNLOADED, // no gl texture number + IS_PARTIAL, // has a texture number and the low mip levels loaded + IS_LOADED // has a texture number and the full mip hierarchy +} imageState_t; + +static const int MAX_TEXTURE_LEVELS = 14; + +// surface description flags +const unsigned long DDSF_CAPS = 0x00000001l; +const unsigned long DDSF_HEIGHT = 0x00000002l; +const unsigned long DDSF_WIDTH = 0x00000004l; +const unsigned long DDSF_PITCH = 0x00000008l; +const unsigned long DDSF_PIXELFORMAT = 0x00001000l; +const unsigned long DDSF_MIPMAPCOUNT = 0x00020000l; +const unsigned long DDSF_LINEARSIZE = 0x00080000l; +const unsigned long DDSF_DEPTH = 0x00800000l; + +// pixel format flags +const unsigned long DDSF_ALPHAPIXELS = 0x00000001l; +const unsigned long DDSF_FOURCC = 0x00000004l; +const unsigned long DDSF_RGB = 0x00000040l; +const unsigned long DDSF_RGBA = 0x00000041l; + +// our extended flags +const unsigned long DDSF_ID_INDEXCOLOR = 0x10000000l; +const unsigned long DDSF_ID_MONOCHROME = 0x20000000l; + +// dwCaps1 flags +const unsigned long DDSF_COMPLEX = 0x00000008l; +const unsigned long DDSF_TEXTURE = 0x00001000l; +const unsigned long DDSF_MIPMAP = 0x00400000l; + +#define DDS_MAKEFOURCC(a, b, c, d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)) + +typedef struct { + unsigned long dwSize; + unsigned long dwFlags; + unsigned long dwFourCC; + unsigned long dwRGBBitCount; + unsigned long dwRBitMask; + unsigned long dwGBitMask; + unsigned long dwBBitMask; + unsigned long dwABitMask; +} ddsFilePixelFormat_t; + +typedef struct +{ + unsigned long dwSize; + unsigned long dwFlags; + unsigned long dwHeight; + unsigned long dwWidth; + unsigned long dwPitchOrLinearSize; + unsigned long dwDepth; + unsigned long dwMipMapCount; + unsigned long dwReserved1[11]; + ddsFilePixelFormat_t ddspf; + unsigned long dwCaps1; + unsigned long dwCaps2; + unsigned long dwReserved2[3]; +} ddsFileHeader_t; + + +// increasing numeric values imply more information is stored +typedef enum { + TD_SPECULAR, // may be compressed, and always zeros the alpha channel + TD_DIFFUSE, // may be compressed + TD_DEFAULT, // will use compressed formats when possible + TD_BUMP, // may be compressed with 8 bit lookup + TD_HIGH_QUALITY // either 32 bit or a component format, no loss at all +} textureDepth_t; + +typedef enum { + TT_DISABLED, + TT_2D, + TT_3D, + TT_CUBIC, + TT_RECT +} textureType_t; + +typedef enum { + CF_2D, // not a cube map + CF_NATIVE, // _px, _nx, _py, etc, directly sent to GL + CF_CAMERA // _forward, _back, etc, rotated and flipped as needed before sending to GL +} cubeFiles_t; + +#define MAX_IMAGE_NAME 256 + +class idImage { +public: + idImage(); + virtual ~idImage() {} + virtual void Bind(); + virtual void UnBind(); + virtual void PurgeImage(); + virtual void Reload( bool checkPrecompressed, bool force ); + + // Makes this image active on the current GL texture unit. + // automatically enables or disables cube mapping or texture3D + // May perform file loading if the image was not preloaded. + // May start a background image read. + // for use with fragment programs, doesn't change any enable2D/3D/cube states + void BindFragment(); + + // deletes the texture object, but leaves the structure so it can be reloaded + + // used by callback functions to specify the actual data + // data goes from the bottom to the top line of the image, as OpenGL expects it + // These perform an implicit Bind() on the current texture unit + // FIXME: should we implement cinematics this way, instead of with explicit calls? + void GenerateImage( const byte *pic, int width, int height, + textureFilter_t filter, bool allowDownSize, + textureRepeat_t repeat, textureDepth_t depth ); + void Generate3DImage( const byte *pic, int width, int height, int depth, + textureFilter_t filter, bool allowDownSize, + textureRepeat_t repeat, textureDepth_t minDepth ); + void GenerateCubeImage( const byte *pic[6], int size, + textureFilter_t filter, bool allowDownSize, + textureDepth_t depth ); + + void CopyFramebuffer( int x, int y, int width, int height, bool useOversizedBuffer ); + + void CopyDepthbuffer( int x, int y, int width, int height ); + + void UploadScratch( const byte *pic, int width, int height ); + + // just for resource tracking + void SetClassification( int tag ); + + // estimates size of the GL image based on dimensions and storage type + int StorageSize() const; + + // print a one line summary of the image + void Print() const; + + // check for changed timestamp on disk and reload if necessary + void AddReference() { refCount++; }; + +//========================================================== + + void GetDownsize( int &scaled_width, int &scaled_height ) const; + void MakeDefault(); // fill with a grid pattern + void SetImageFilterAndRepeat() const; + bool ShouldImageBePartialCached(); + void WritePrecompressedImage(); + bool CheckPrecompressedImage( bool fullLoad ); + void UploadPrecompressedImage( byte *data, int len ); + void ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd ); + void StartBackgroundImageLoad(); + int BitsForInternalFormat( int internalFormat ) const; + void UploadCompressedNormalMap( int width, int height, const byte *rgba, int mipLevel ); + GLenum SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, int width, int height, + textureDepth_t minimumDepth, bool *monochromeResult ) const; + void ImageProgramStringToCompressedFileName( const char *imageProg, char *fileName ) const; + int NumLevelsForImageSize( int width, int height ) const; + + // data commonly accessed is grouped here + static const int TEXTURE_NOT_LOADED = -1; + GLuint texnum; // gl texture binding, will be TEXTURE_NOT_LOADED if not loaded + textureType_t type; + int frameUsed; // for texture usage in frame statistics + int bindCount; // incremented each bind + int cubeFaceBound; // last cube-map face bound by the backend + + // background loading information + idImage *partialImage; // shrunken, space-saving version + bool isPartialImage; // true if this is pointed to by another image + bool backgroundLoadInProgress; // true if another thread is reading the complete d3t file + backgroundDownload_t bgl; + idImage * bglNext; // linked from tr.backgroundImageLoads + + // parameters that define this image + idStr imgName; // game path, including extension (except for cube maps), may be an image program + void (*generatorFunction)( idImage *image ); // NULL for files + bool allowDownSize; // this also doubles as a don't-partially-load flag + textureFilter_t filter; + textureRepeat_t repeat; + textureDepth_t depth; + cubeFiles_t cubeFiles; // determines the naming and flipping conventions for the six images + + bool referencedOutsideLevelLoad; + bool levelLoadReferenced; // for determining if it needs to be purged + bool precompressedFile; // true when it was loaded from a .d3t file + bool defaulted; // true if the default image was generated because a file couldn't be loaded + bool isMonochrome; // so the NV20 path can use a reduced pass count + ID_TIME_T timestamp; // the most recent of all images used in creation, for reloadImages command + + int imageHash; // for identical-image checking + + int classification; // just for resource profiling + + // data for listImages + int uploadWidth, uploadHeight, uploadDepth; // after power of two, downsample, and MAX_TEXTURE_SIZE + int internalFormat; + int flags; + int useCount; + + idImage *cacheUsagePrev, *cacheUsageNext; // for dynamic cache purging of old images + + idImage * hashNext; // for hash chains to speed lookup + + int refCount; // overall ref count +}; + +ID_INLINE idImage::idImage() { + texnum = TEXTURE_NOT_LOADED; + partialImage = NULL; + type = TT_DISABLED; + isPartialImage = false; + frameUsed = 0; + classification = 0; + backgroundLoadInProgress = false; + bgl.opcode = DLTYPE_FILE; + bgl.f = NULL; + bglNext = NULL; + imgName[0] = '\0'; + generatorFunction = NULL; + allowDownSize = false; + filter = TF_DEFAULT; + repeat = TR_REPEAT; + depth = TD_DEFAULT; + cubeFiles = CF_2D; + referencedOutsideLevelLoad = false; + levelLoadReferenced = false; + precompressedFile = false; + defaulted = false; + timestamp = 0; + bindCount = 0; + uploadWidth = uploadHeight = uploadDepth = 0; + internalFormat = 0; + cacheUsagePrev = cacheUsageNext = NULL; + hashNext = NULL; + isMonochrome = false; + refCount = 0; + cubeFaceBound = 0; + flags = 0; + useCount = 0; +} + +class rvTexRenderTarget; + +class idPBufferImage : public idImage { +public: + virtual ~idPBufferImage(); + virtual void Bind(); + virtual void UnBind(); + virtual void PurgeImage(); + virtual void Reload( bool checkPrecompressed, bool force ); + + rvTexRenderTarget *mRenderTarget = NULL; +}; + + +// data is RGBA +void R_WriteTGA( const char *filename, const byte *data, int width, int height, bool flipVertical = false ); +// data is an 8 bit index into palette, which is RGB (no A) +void R_WritePalTGA( const char *filename, const byte *data, const byte *palette, int width, int height, bool flipVertical = false ); +// data is in top-to-bottom raster order unless flipVertical is set + + +class idImageManager { +public: + void Init(); + void Shutdown(); + + // If the exact combination of parameters has been asked for already, an existing + // image will be returned, otherwise a new image will be created. + // Be careful not to use the same image file with different filter / repeat / etc parameters + // if possible, because it will cause a second copy to be loaded. + // If the load fails for any reason, the image will be filled in with the default + // grid pattern. + // Will automatically resample non-power-of-two images and execute image programs if needed. + idImage * ImageFromFile( const char *name, + textureFilter_t filter, bool allowDownSize, + textureRepeat_t repeat, textureDepth_t depth, cubeFiles_t cubeMap = CF_2D ); + + // look for a loaded image, whatever the parameters + idImage * GetImage( const char *name ) const; + + // The callback will be issued immediately, and later if images are reloaded or vid_restart + // The callback function should call one of the idImage::Generate* functions to fill in the data + idImage * ImageFromFunction( const char *name, void (*generatorFunction)( idImage *image )); + + // called once a frame to allow any background loads that have been completed + // to turn into textures. + void CompleteBackgroundImageLoads(); + + // returns the number of bytes of image data bound in the previous frame + int SumOfUsedImages(); + + // called each frame to allow some cvars to automatically force changes + void CheckCvars(); + + // purges all the images before a vid_restart + void PurgeAllImages(); + + // reloads all apropriate images after a vid_restart + void ReloadAllImages(); + + // disable the active texture unit + void BindNull(); + + // Mark all file based images as currently unused, + // but don't free anything. Calls to ImageFromFile() will + // either mark the image as used, or create a new image without + // loading the actual data. + // Called only by renderSystem::BeginLevelLoad + void BeginLevelLoad(); + + // Free all images marked as unused, and load all images that are necessary. + // This architecture prevents us from having the union of two level's + // worth of data present at one time. + // Called only by renderSystem::EndLevelLoad + void EndLevelLoad(); + + // used to clear and then write the dds conversion batch file + void StartBuild(); + void FinishBuild( bool removeDups = false ); + void AddDDSCommand( const char *cmd ); + + void PrintMemInfo( MemInfo *mi ); + + // cvars + static idCVar image_roundDown; // round bad sizes down to nearest power of two + static idCVar image_colorMipLevels; // development aid to see texture mip usage + static idCVar image_downSize; // controls texture downsampling + static idCVar image_useCompression; // 0 = force everything to high quality + static idCVar image_filter; // changes texture filtering on mipmapped images + static idCVar image_anisotropy; // set the maximum texture anisotropy if available + static idCVar image_lodbias; // change lod bias on mipmapped images + static idCVar image_useAllFormats; // allow alpha/intensity/luminance/luminance+alpha + static idCVar image_usePrecompressedTextures; // use .dds files if present + static idCVar image_writePrecompressedTextures; // write .dds files if necessary + static idCVar image_writeNormalTGA; // debug tool to write out .tgas of the final normal maps + static idCVar image_writeNormalTGAPalletized; // debug tool to write out palletized versions of the final normal maps + static idCVar image_writeTGA; // debug tool to write out .tgas of the non normal maps + static idCVar image_useNormalCompression; // 1 = use 256 color compression for normal maps if available, 2 = use rxgb compression + static idCVar image_useOffLineCompression; // will write a batch file with commands for the offline compression + static idCVar image_preload; // if 0, dynamically load all images + static idCVar image_cacheMinK; // maximum K of precompressed files to read at specification time, + // the remainder will be dynamically cached + static idCVar image_cacheMegs; // maximum bytes set aside for temporary loading of full-sized precompressed images + static idCVar image_useCache; // 1 = do background load image caching + static idCVar image_showBackgroundLoads; // 1 = print number of outstanding background loads + static idCVar image_forceDownSize; // allows the ability to force a downsize + static idCVar image_downSizeSpecular; // downsize specular + static idCVar image_downSizeSpecularLimit;// downsize specular limit + static idCVar image_downSizeBump; // downsize bump maps + static idCVar image_downSizeBumpLimit; // downsize bump limit + static idCVar image_ignoreHighQuality; // ignore high quality on materials + static idCVar image_downSizeLimit; // downsize diffuse limit + + // built-in images + idImage * defaultImage; + idImage * flatNormalMap; // 128 128 255 in all pixels + idImage * ambientNormalMap; // tr.ambientLightVector encoded in all pixels + idImage * rampImage; // 0-255 in RGBA in S + idImage * alphaRampImage; // 0-255 in alpha, 255 in RGB + idImage * alphaNotchImage; // 2x1 texture with just 1110 and 1111 with point sampling + idImage * whiteImage; // full of 0xff + idImage * blackImage; // full of 0x00 + idImage * normalCubeMapImage; // cube map to normalize STR into RGB + idImage * noFalloffImage; // all 255, but zero clamped + idImage * fogImage; // increasing alpha is denser fog + idImage * fogEnterImage; // adjust fogImage alpha based on terminator plane + idImage * cinematicImage; + idImage * scratchImage; + idImage * scratchImage2; + idImage * accumImage; + idImage * currentRenderImage; // for SS_POST_PROCESS shaders + idImage * scratchCubeMapImage; + idImage * specularTableImage; // 1D intensity texture with our specular function + idImage * specular2DTableImage; // 2D intensity texture with our specular function with variable specularity + idImage * borderClampImage; // white inside, black outside + idImage * reflectionImage; // water reflection subviews + + //-------------------------------------------------------- + + idImage * AllocImage( const char *name ); + idPBufferImage * AllocPBufferImage( const char *name ); + void SetNormalPalette(); + void ChangeTextureFilter(); + + idList images; + idStrList ddsList; + idHashIndex ddsHash; + + bool insideLevelLoad; // don't actually load images now + + byte originalToCompressed[256]; // maps normal maps to 8 bit textures + byte compressedPalette[768]; // the palette that normal maps use + + // default filter modes for images + GLenum textureMinFilter; + GLenum textureMaxFilter; + float textureAnisotropy; + float textureLODBias; + + idImage * imageHashTable[FILE_HASH_SIZE]; + + idImage * backgroundImageLoads; // chain of images that have background file loads active + idImage cacheLRU; // head/tail of doubly linked list + int totalCachedImageSize; // for determining when something should be purged + + int numActiveBackgroundImageLoads; + const static int MAX_BACKGROUND_IMAGE_LOADS = 8; +}; + +extern idImageManager *globalImages; // pointer to global list for the rest of the system + +int MakePowerOfTwo( int num ); + +/* +==================================================================== + +IMAGEPROCESS + +FIXME: make an "imageBlock" type to hold byte*,width,height? +==================================================================== +*/ + +byte *R_Dropsample( const byte *in, int inwidth, int inheight, + int outwidth, int outheight ); +byte *R_ResampleTexture( const byte *in, int inwidth, int inheight, + int outwidth, int outheight ); +byte *R_MipMapWithAlphaSpecularity( const byte *in, int width, int height ); +byte *R_MipMap( const byte *in, int width, int height, bool preserveBorder ); +byte *R_MipMap3D( const byte *in, int width, int height, int depth, bool preserveBorder ); + +// these operate in-place on the provided pixels +void R_SetBorderTexels( byte *inBase, int width, int height, const byte border[4] ); +void R_SetBorderTexels3D( byte *inBase, int width, int height, int depth, const byte border[4] ); +void R_BlendOverTexture( byte *data, int pixelCount, const byte blend[4] ); +void R_HorizontalFlip( byte *data, int width, int height ); +void R_VerticalFlip( byte *data, int width, int height ); +void R_RotatePic( byte *data, int width ); + +/* +==================================================================== + +IMAGEFILES + +==================================================================== +*/ + +void R_LoadImage( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp, bool makePowerOf2 ); +// pic is in top to bottom raster format +bool R_LoadCubeImages( const char *cname, cubeFiles_t extensions, byte *pic[6], int *size, ID_TIME_T *timestamp ); +extern ID_TIME_T ProgramImagesTimestamp; +extern bool gWriteProgramFlag; + +/* +==================================================================== + +IMAGEPROGRAM + +==================================================================== +*/ + +void R_LoadImageProgram( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp, textureDepth_t *depth = NULL ); +const char *R_ParsePastImageProgram( idLexer &src ); diff --git a/src/renderer/Image_stats.cpp b/src/renderer/Image_stats.cpp new file mode 100644 index 0000000..f79bb7c --- /dev/null +++ b/src/renderer/Image_stats.cpp @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* R_ImageStats_f -- Image_stats.obj, quake4.pdb RVA 0x000B08A0. */ +void R_ImageStats_f( const idCmdArgs &args ) { + if ( args.Argc() < 3 ) { + common->Printf( "Usage - imageStats \n" ); + return; + } + + const float cutoffLo = atof( args.Argv( 1 ) ); + const float cutoffHi = atof( args.Argv( 2 ) ); + float totalLuma = 0.0f; + int numImages = 0; + int numImagesOutOfRange = 0; + int totalPixels = 0; + int totalBrightPixels = 0; + + for ( int i = 0; i < globalImages->images.Num(); ++i ) { + idImage *image = globalImages->images[i]; + if ( image == NULL || image->depth != TD_DIFFUSE ) { + continue; + } + + byte *pixels = NULL; + int width = 0; + int height = 0; + R_LoadImage( image->imgName, &pixels, &width, &height, NULL, true ); + if ( pixels == NULL ) { + continue; + } + + float imageLuma = 0.0f; + int opaquePixels = 0; + const int pixelCount = width * height; + for ( int pixel = 0; pixel < pixelCount; ++pixel ) { + const byte *rgba = pixels + pixel * 4; + if ( rgba[3] == 0 ) { + continue; + } + const float luma = Max( rgba[0], Max( rgba[1], rgba[2] ) ) * ( 1.0f / 255.0f ); + imageLuma += luma; + ++opaquePixels; + if ( luma > cutoffHi ) { + ++totalBrightPixels; + } + } + if ( opaquePixels != 0 ) { + imageLuma /= opaquePixels; + } + totalPixels += opaquePixels; + if ( imageLuma < cutoffLo || imageLuma > cutoffHi ) { + common->Printf( "%0.4g: %s\n", imageLuma, image->imgName.c_str() ); + ++numImagesOutOfRange; + } + totalLuma += imageLuma; + ++numImages; + R_StaticFree( pixels ); + } + + if ( numImages != 0 ) { + common->Printf( "Average intensity of %g from %d diffuse images\n", totalLuma / numImages, numImages ); + common->Printf( "%d%% lie between %g and %g\n", 100 * ( numImages - numImagesOutOfRange ) / numImages, cutoffLo, cutoffHi ); + if ( totalPixels != 0 ) { + common->Printf( "%g%% of pixels are brighter than %g\n", totalBrightPixels * 100.0f / totalPixels, cutoffHi ); + } + } +} diff --git a/src/renderer/Interaction.h b/src/renderer/Interaction.h new file mode 100644 index 0000000..ce910f7 --- /dev/null +++ b/src/renderer/Interaction.h @@ -0,0 +1,180 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __INTERACTION_H__ +#define __INTERACTION_H__ + +/* +=============================================================================== + + Interaction between entityDef surfaces and a lightDef. + + Interactions with no lightTris and no shadowTris are still + valid, because they show that a given entityDef / lightDef + do not interact, even though they share one or more areas. + +=============================================================================== +*/ + +#define LIGHT_TRIS_DEFERRED ((srfTriangles_t *)-1) +#define LIGHT_CULL_ALL_FRONT ((byte *)-1) +#define LIGHT_CLIP_EPSILON 0.1f + + +typedef struct srfCullInfo_s { + // For each triangle a byte set to 1 if facing the light origin. + byte * facing; + + // For each vertex a byte with the bits [0-5] set if the + // vertex is at the back side of the corresponding clip plane. + // If the 'cullBits' pointer equals LIGHT_CULL_ALL_FRONT all + // vertices are at the front of all the clip planes. + byte * cullBits; + + // Clip planes in surface space used to calculate the cull bits. + idPlane localClipPlanes[6]; +} srfCullInfo_t; + + +typedef struct { + // if lightTris == LIGHT_TRIS_DEFERRED, then the calculation of the + // lightTris has been deferred, and must be done if ambientTris is visible + srfTriangles_t * lightTris; + + // shadow volume triangle surface + srfTriangles_t * shadowTris; + + // so we can check ambientViewCount before adding lightTris, and get + // at the shared vertex and possibly shadowVertex caches + srfTriangles_t * ambientTris; + + const idMaterial * shader; + + int expCulled; // only for the experimental shadow buffer renderer + + srfCullInfo_t cullInfo; +} surfaceInteraction_t; + + +typedef struct areaNumRef_s { + struct areaNumRef_s * next; + int areaNum; +} areaNumRef_t; + + +class idRenderEntityLocal; +class idRenderLightLocal; + +class idInteraction { +public: + // this may be 0 if the light and entity do not actually intersect + // -1 = an untested interaction + int numSurfaces; + + // if there is a whole-entity optimized shadow hull, it will + // be present as a surfaceInteraction_t with a NULL ambientTris, but + // possibly having a shader to specify the shadow sorting order + surfaceInteraction_t * surfaces; + + // get space from here, if NULL, it is a pre-generated shadow volume from dmap + idRenderEntityLocal * entityDef; + idRenderLightLocal * lightDef; + + idInteraction * lightNext; // for lightDef chains + idInteraction * lightPrev; + idInteraction * entityNext; // for entityDef chains + idInteraction * entityPrev; + +public: + idInteraction( void ); + + // because these are generated and freed each game tic for active elements all + // over the world, we use a custom pool allocater to avoid memory allocation overhead + // and fragmentation + static idInteraction * AllocAndLink( idRenderEntityLocal *edef, idRenderLightLocal *ldef ); + + // unlinks from the entity and light, frees all surfaceInteractions, + // and puts it back on the free list + void UnlinkAndFree( void ); + + // free the interaction surfaces + void FreeSurfaces( void ); + + // makes the interaction empty for when the light and entity do not actually intersect + // all empty interactions are linked at the end of the light's and entity's interaction list + void MakeEmpty( void ); + + // returns true if the interaction is empty + bool IsEmpty( void ) const { return ( numSurfaces == 0 ); } + + // returns true if the interaction is not yet completely created + bool IsDeferred( void ) const { return ( numSurfaces == -1 ); } + + // returns true if the interaction has shadows + bool HasShadows( void ) const; + + // counts up the memory used by all the surfaceInteractions, which + // will be used to determine when we need to start purging old interactions + int MemoryUsed( void ); + + // makes sure all necessary light surfaces and shadow surfaces are created, and + // calls R_LinkLightSurf() for each one + void AddActiveInteraction( void ); + +private: + enum { + FRUSTUM_UNINITIALIZED, + FRUSTUM_INVALID, + FRUSTUM_VALID, + FRUSTUM_VALIDAREAS, + } frustumState; + idFrustum frustum; // frustum which contains the interaction + areaNumRef_t * frustumAreas; // numbers of the areas the frustum touches + + int dynamicModelFrameCount; // so we can tell if a callback model animated + +private: + // actually create the interaction + void CreateInteraction( const idRenderModel *model ); + + // unlink from entity and light lists + void Unlink( void ); + + // try to determine if the entire interaction, including shadows, is guaranteed + // to be outside the view frustum + bool CullInteractionByViewFrustum( const idFrustum &viewFrustum ); + + // determine the minimum scissor rect that will include the interaction shadows + // projected to the bounds of the light + idScreenRect CalcInteractionScissorRectangle( const idFrustum &viewFrustum ); +}; + + +void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ); +void R_CalcInteractionCullBits( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ); +void R_FreeInteractionCullInfo( srfCullInfo_t &cullInfo ); + +void R_ShowInteractionMemory_f( const idCmdArgs &args ); + +#endif /* !__INTERACTION_H__ */ diff --git a/src/renderer/Material.h b/src/renderer/Material.h new file mode 100644 index 0000000..2889437 --- /dev/null +++ b/src/renderer/Material.h @@ -0,0 +1,915 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MATERIAL_H__ +#define __MATERIAL_H__ + +/* +=============================================================================== + + Material + +=============================================================================== +*/ + +class idImage; +class idCinematic; +class idUserInterface; +class idMegaTexture; +// RAVEN BEGIN +// rjohnson: new shader stage system +class rvNewShaderStage; +class rvGLSLShaderStage; +// RAVEN END + +// moved from image.h for default parm +typedef enum { + TF_LINEAR, + TF_NEAREST, + TF_DEFAULT +} textureFilter_t; + +typedef enum { + TR_REPEAT, + TR_CLAMP, + TR_CLAMP_TO_BORDER, // this should replace TR_CLAMP_TO_ZERO and TR_CLAMP_TO_ZERO_ALPHA, + // but I don't want to risk changing it right now + TR_CLAMP_TO_ZERO, // guarantee 0,0,0,255 edge for projected textures, + // set AFTER image format selection + TR_CLAMP_TO_ZERO_ALPHA, // guarantee 0 alpha edge for projected textures, + // set AFTER image format selection + TR_MIRRORED_REPEAT, +} textureRepeat_t; + +typedef struct { + int stayTime; // msec for no change + float maxAngle; // maximum dot product to reject projection angles +} decalInfo_t; + +typedef enum { + DFRM_NONE, + DFRM_SPRITE, + DFRM_TUBE, + DFRM_FLARE, + DFRM_EXPAND, + DFRM_MOVE, + DFRM_EYEBALL, +// ddynerman: rectangular sprites + DFRM_RECTSPRITE, +// RAVEN END + DFRM_TURB +} deform_t; + +typedef enum { + DI_STATIC, + DI_SCRATCH, // video, screen wipe, etc + DI_CUBE_RENDER, + DI_MIRROR_RENDER, +// RAVEN BEGIN +// AReis: Used for water reflection/refraction. + DI_REFLECTION_RENDER, + DI_REFRACTION_RENDER, +// RAVEN END + DI_REMOTE_RENDER +} dynamicidImage_t; + +// note: keep opNames[] in sync with changes +typedef enum { + OP_TYPE_ADD, + OP_TYPE_SUBTRACT, + OP_TYPE_MULTIPLY, + OP_TYPE_DIVIDE, + OP_TYPE_MOD, + OP_TYPE_TABLE, + OP_TYPE_GT, + OP_TYPE_GE, + OP_TYPE_LT, + OP_TYPE_LE, + OP_TYPE_EQ, + OP_TYPE_NE, + OP_TYPE_AND, + OP_TYPE_OR, + OP_TYPE_SOUND +// RAVEN BEGIN +// rjohnson: new shader stage system + , + OP_TYPE_GLSL_ENABLED, + OP_TYPE_POT_X, + OP_TYPE_POT_Y, +// RAVEN END +} expOpType_t; + +typedef enum { + EXP_REG_TIME, + + EXP_REG_PARM0, + EXP_REG_PARM1, + EXP_REG_PARM2, + EXP_REG_PARM3, + EXP_REG_PARM4, + EXP_REG_PARM5, + EXP_REG_PARM6, + EXP_REG_PARM7, + EXP_REG_PARM8, + EXP_REG_PARM9, + EXP_REG_PARM10, + EXP_REG_PARM11, + + EXP_REG_GLOBAL0, + EXP_REG_GLOBAL1, + EXP_REG_GLOBAL2, + EXP_REG_GLOBAL3, + EXP_REG_GLOBAL4, + EXP_REG_GLOBAL5, + EXP_REG_GLOBAL6, + EXP_REG_GLOBAL7, + +// RAVEN BEGIN +// rjohnson: added vertex randomizing + EXP_REG_VERTEX_RANDOMIZER, +// RAVEN END + + EXP_REG_NUM_PREDEFINED +} expRegister_t; + +// RAVEN BEGIN +// rjohnson: added new decal support + +// decal registers +#define EXP_REG_DECAL_LIFE EXP_REG_PARM4 +#define REG_DECAL_LIFE 4 +#define EXP_REG_DECAL_SPAWN EXP_REG_PARM5 +#define REG_DECAL_SPAWN 5 +// RAVEN END + +typedef struct { + expOpType_t opType; + int a, b, c; +} expOp_t; + +typedef struct { + int registers[4]; +} colorStage_t; + +typedef enum { + TG_EXPLICIT, + TG_DIFFUSE_CUBE, + TG_REFLECT_CUBE, + TG_SKYBOX_CUBE, + TG_WOBBLESKY_CUBE, + TG_SCREEN, // screen aligned, for mirrorRenders and screen space temporaries + TG_SCREEN2, + TG_GLASSWARP, + TG_POT_CORRECTION +} texgen_t; + +typedef struct { + idCinematic * cinematic; + idImage * image; + texgen_t texgen; + bool hasMatrix; + int matrix[2][3]; // we only allow a subset of the full projection matrix + + // dynamic image variables + dynamicidImage_t dynamic; + int width, height; + int dynamicFrameCount; +} textureStage_t; + +// the order BUMP / DIFFUSE / SPECULAR is necessary for interactions to draw correctly on low end cards +typedef enum { + SL_AMBIENT, // execute after lighting + SL_BUMP, + SL_DIFFUSE, + SL_SPECULAR +} stageLighting_t; + +// cross-blended terrain textures need to modulate the color by +// the vertex color to smoothly blend between two textures +typedef enum { + SVC_IGNORE, + SVC_MODULATE, + SVC_INVERSE_MODULATE +} stageVertexColor_t; + +static const int MAX_FRAGMENT_IMAGES = 8; +// RAVEN BEGIN +// AReis: Increased MAX_VERTEX_PARMS from 4 to 16 and added MAX_FRAGMENT_PARMS. +static const int MAX_VERTEX_PARMS = 16; +static const int MAX_FRAGMENT_PARMS = 8; +// RAVEN END + +typedef struct { + int vertexProgram; + +// RAVEN BEGIN +// dluetscher: added support for specifying MD5R specfic vertex programs +// Q4SDK: maintain compatible structure padding +#if defined( _MD5R_SUPPORT ) || defined( Q4SDK_MD5R ) + int md5rVertexProgram; +#endif +// RAVEN END + + int numVertexParms; + int vertexParms[MAX_VERTEX_PARMS][4]; // evaluated register indexes + +// RAVEN BEGIN +// AReis: New Fragment Parm stuff. + int numFragmentParms; + int fragmentParms[MAX_FRAGMENT_PARMS][4]; // evaluated register indexes +// RAVEN END + + int fragmentProgram; + int numFragmentProgramImages; + idImage * fragmentProgramImages[MAX_FRAGMENT_IMAGES]; + +// RAVEN BEGIN +// AReis: Custom Bindings. These override existing parm values. + bool vertexParmsBindings[MAX_VERTEX_PARMS]; + bool fragmentParmsBindings[MAX_FRAGMENT_PARMS]; + +// AReis: So fragment images can be bound to program specified binding, this +// list keeps track of which image to use (if any, 0 if use specified image). + int fragmentProgramBindings[MAX_FRAGMENT_IMAGES]; +// RAVEN END + + idMegaTexture *megaTexture; // handles all the binding and parameter setting +} newShaderStage_t; + +typedef struct { + int conditionRegister; // if registers[conditionRegister] == 0, skip stage + stageLighting_t lighting; // determines which passes interact with lights + int drawStateBits; + colorStage_t color; + bool hasAlphaTest; +// RAVEN BEGIN + bool hasAlphaFunc; + int alphaTestMode; +// RAVEN END + int alphaTestRegister; + textureStage_t texture; + stageVertexColor_t vertexColor; + bool ignoreAlphaTest; // this stage should act as translucent, even + // if the surface is alpha tested + float privatePolygonOffset; // a per-stage polygon offset + + newShaderStage_t *newStage; // vertex / fragment program based stage + +// RAVEN BEGIN +// rjohnson: new shader stage system + rvNewShaderStage *newShaderStage; + +// rjohnson: added new decal support + int mStageRegisterStart; + int mNumStageRegisters; + int mStageOpsStart; + int mNumStageOps; +// RAVEN END +} shaderStage_t; + +typedef enum { + MC_BAD, + MC_OPAQUE, // completely fills the triangle, will have black drawn on fillDepthBuffer + MC_PERFORATED, // may have alpha tested holes + MC_TRANSLUCENT // blended with background +} materialCoverage_t; + +typedef enum { + SS_MIN = -10000, +// RAVEN BEGIN + SS_SUBVIEW = -4, // mirrors, viewscreens, etc + SS_PREGUI = -3, // guis +// RAVEN END + SS_GUI = -2, // guis + SS_BAD = -1, + SS_OPAQUE, // opaque + + SS_PORTAL_SKY, + + SS_DECAL, // scorch marks, etc. + + SS_FAR, + SS_MEDIUM, // normal translucent + SS_CLOSE, + + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST, // screen blood blobs + + SS_POST_PROCESS = 100, // after a screen copy to texture + SS_MAX = 10000 +} materialSort_t; + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +// these don't effect per-material storage, so they can be very large +const int MAX_SHADER_STAGES = 256; + +const int MAX_TEXGEN_REGISTERS = 4; + +const int MAX_ENTITY_SHADER_PARMS = 12; + +// material flags +typedef enum { + MF_DEFAULTED = BIT(0), + MF_POLYGONOFFSET = BIT(1), + MF_NOSHADOWS = BIT(2), + MF_FORCESHADOWS = BIT(3), + MF_NOSELFSHADOW = BIT(4), + MF_NOPORTALFOG = BIT(5), // this fog volume won't ever consider a portal fogged out + MF_EDITOR_VISIBLE = BIT(6) // in use (visible) per editor +// RAVEN BEGIN +// jscott: for portal skies + , + MF_SKY = BIT(7), + MF_NEED_CURRENT_RENDER = BIT(8) // for hud guis that need sort order preseved but need back end too +// RAVEN END +} materialFlags_t; + +// contents flags, NOTE: make sure to keep the defines in doom_defs.script up to date with these! +typedef enum { + CONTENTS_SOLID = BIT(0), // an eye is never valid in a solid + CONTENTS_OPAQUE = BIT(1), // blocks visibility (for ai) + CONTENTS_WATER = BIT(2), // used for water + CONTENTS_PLAYERCLIP = BIT(3), // solid to players + CONTENTS_MONSTERCLIP = BIT(4), // solid to monsters + CONTENTS_MOVEABLECLIP = BIT(5), // solid to moveable entities + CONTENTS_IKCLIP = BIT(6), // solid to IK + CONTENTS_BLOOD = BIT(7), // used to detect blood decals + CONTENTS_BODY = BIT(8), // used for actors + CONTENTS_PROJECTILE = BIT(9), // used for projectiles + CONTENTS_CORPSE = BIT(10), // used for dead bodies + CONTENTS_RENDERMODEL = BIT(11), // used for render models for collision detection + CONTENTS_TRIGGER = BIT(12), // used for triggers + CONTENTS_AAS_SOLID = BIT(13), // solid for AAS + CONTENTS_AAS_OBSTACLE = BIT(14), // used to compile an obstacle into AAS that can be enabled/disabled + CONTENTS_FLASHLIGHT_TRIGGER = BIT(15), // used for triggers that are activated by the flashlight +// RAVEN BEGIN +// bdube: new clip that blocks monster visibility + CONTENTS_SIGHTCLIP = BIT(16), // used for blocking sight for actors and cameras + CONTENTS_LARGESHOTCLIP = BIT(17), // used to block large shots (fence that allows bullets through but not rockets for example) +// cdr: AASTactical + CONTENTS_NOTACTICALFEATURES = BIT(18), // don't place tactical features here + CONTENTS_VEHICLECLIP = BIT(19), // solid to vehicles + + // contents used by utils + CONTENTS_AREAPORTAL = BIT(20), // portal separating renderer areas + CONTENTS_NOCSG = BIT(21), // don't cut this brush with CSG operations in the editor + CONTENTS_FLYCLIP = BIT(22), // solid to vehicles + +// mekberg: added + CONTENTS_ITEMCLIP = BIT(23), // so items can collide + CONTENTS_PROJECTILECLIP = BIT(24), // unlike contents_projectile, projectiles only NOT hitscans +// RAVEN END + + CONTENTS_REMOVE_UTIL = ~(CONTENTS_AREAPORTAL|CONTENTS_NOCSG) +} contentsFlags_t; + +// surface types +const int NUM_SURFACE_BITS = 4; +const int MAX_SURFACE_TYPES = 1 << NUM_SURFACE_BITS; + +typedef enum { + SURFTYPE_NONE, // default type + SURFTYPE_METAL, + SURFTYPE_STONE, + SURFTYPE_FLESH, + SURFTYPE_WOOD, + SURFTYPE_CARDBOARD, + SURFTYPE_LIQUID, + SURFTYPE_GLASS, + SURFTYPE_PLASTIC, + SURFTYPE_RICOCHET, + SURFTYPE_10, + SURFTYPE_11, + SURFTYPE_12, + SURFTYPE_13, + SURFTYPE_14, + SURFTYPE_15 +} surfTypes_t; + +// surface flags +typedef enum { + SURF_TYPE_BIT0 = BIT(0), // encodes the material type (metal, flesh, concrete, etc.) + SURF_TYPE_BIT1 = BIT(1), // " + SURF_TYPE_BIT2 = BIT(2), // " + SURF_TYPE_BIT3 = BIT(3), // " + SURF_TYPE_MASK = ( 1 << NUM_SURFACE_BITS ) - 1, + + SURF_NODAMAGE = BIT(4), // never give falling damage + SURF_SLICK = BIT(5), // effects game physics + SURF_COLLISION = BIT(6), // collision surface + SURF_LADDER = BIT(7), // player can climb up this surface + SURF_NOIMPACT = BIT(8), // don't make missile explosions + SURF_NOSTEPS = BIT(9), // no footstep sounds + SURF_DISCRETE = BIT(10), // not clipped or merged by utilities + SURF_NOFRAGMENT = BIT(11), // dmap won't cut surface at each bsp boundary + SURF_NULLNORMAL = BIT(12), // renderbump will draw this surface as 0x80 0x80 0x80, which + // won't collect light from any angle +// RAVEN BEGIN +// bdube: added bounce + SURF_BOUNCE = BIT(13), // projectiles should bounce off this surface + +// dluetscher: added no T fix + SURF_NO_T_FIX = BIT(14), // merge surfaces (like decals), but does not try to T-fix them + +// RAVEN END +} surfaceFlags_t; + +class idSoundEmitter; + +// RAVEN BEGIN +// jsinger: added to allow support for serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idMaterial : public idDecl, public Serializable<'IMAT'> { +public: +// jsinger: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idMaterial( SerialInputStream &stream ); +#else +class idMaterial : public idDecl { +#endif +// rjohnson: new shader stage system + friend class rvNewShaderStage; + friend class rvGLSLShaderStage; +// RAVEN END + +public: + idMaterial(); + virtual ~idMaterial(); + + virtual size_t Size( void ) const; + virtual bool SetDefaultText( void ); + virtual const char *DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); + virtual void Print( void ) const; + + // returns the internal image name for stage 0, which can be used + // for the renderer CaptureRenderToImage() call + // I'm not really sure why this needs to be virtual... + virtual const char *ImageName( void ) const; + + void ReloadImages( bool force ) const; +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + int streamCount; + int streamTimeStamp; + static int masterStreamTimeStamp; + bool StreamImages( bool inBackground ); + void UnstreamImages( void ); + void UpdateImage( int stage, const byte *data, int width, int height ); +#endif +// RAVEN END + // returns number of stages this material contains + const int GetNumStages( void ) const { return numStages; } + + // get a specific stage + const shaderStage_t *GetStage( const int index ) const { assert(index >= 0 && index < numStages); return &stages[index]; } + + // get the first bump map stage, or NULL if not present. + // used for bumpy-specular + const shaderStage_t *GetBumpStage( void ) const; + + // returns true if the material will draw anything at all. Triggers, portals, + // etc, will not have anything to draw. A not drawn surface can still castShadow, + // which can be used to make a simplified shadow hull for a complex object set + // as noShadow + bool IsDrawn( void ) const { return ( numStages > 0 || entityGui != 0 || gui != NULL ); } + + // returns true if the material will draw any non light interaction stages + bool HasAmbient( void ) const { return ( numAmbientStages > 0 ); } + + // returns true if material has a gui + bool HasGui( void ) const { return ( entityGui != 0 || gui != NULL ); } + + // returns true if the material will generate another view, either as + // a mirror or dynamic rendered image + bool HasSubview( void ) const { return hasSubview; } + + // returns true if the material will generate shadows, not making a + // distinction between global and no-self shadows + bool SurfaceCastsShadow( void ) const { return TestMaterialFlag( MF_FORCESHADOWS ) || !TestMaterialFlag( MF_NOSHADOWS ); } + + // returns true if the material will generate interactions with fog/blend lights + // All non-translucent surfaces receive fog unless they are explicitly noFog + bool ReceivesFog( void ) const { return ( IsDrawn() && !noFog && coverage != MC_TRANSLUCENT ); } + + // returns true if the material will generate interactions with normal lights + // Many special effect surfaces don't have any bump/diffuse/specular + // stages, and don't interact with lights at all + bool ReceivesLighting( void ) const { return numAmbientStages != numStages; } + + // returns true if the material should generate interactions on sides facing away + // from light centers, as with noshadow and noselfshadow options + bool ReceivesLightingOnBackSides( void ) const { return ( materialFlags & (MF_NOSELFSHADOW|MF_NOSHADOWS) ) != 0; } + + // Standard two-sided triangle rendering won't work with bump map lighting, because + // the normal and tangent vectors won't be correct for the back sides. When two + // sided lighting is desired. typically for alpha tested surfaces, this is + // addressed by having CleanupModelSurfaces() create duplicates of all the triangles + // with apropriate order reversal. + bool ShouldCreateBackSides( void ) const { return shouldCreateBackSides; } + + // characters and models that are created by a complete renderbump can use a faster + // method of tangent and normal vector generation than surfaces which have a flat + // renderbump wrapped over them. + bool UseUnsmoothedTangents( void ) const { return unsmoothedTangents; } + + // by default, monsters can have blood overlays placed on them, but this can + // be overrided on a per-material basis with the "noOverlays" material command. + // This will always return false for translucent surfaces + bool AllowOverlays( void ) const { return allowOverlays; } + + // MC_OPAQUE, MC_PERFORATED, or MC_TRANSLUCENT, for interaction list linking and + // dmap flood filling + // The depth buffer will not be filled for MC_TRANSLUCENT surfaces + // FIXME: what do nodraw surfaces return? + materialCoverage_t Coverage( void ) const { return coverage; } + + // returns true if this material takes precedence over other in coplanar cases + bool HasHigherDmapPriority( const idMaterial &other ) const { return ( IsDrawn() && !other.IsDrawn() ) || + ( Coverage() < other.Coverage() ); } + + // returns a idUserInterface if it has a global gui, or NULL if no gui + idUserInterface * GlobalGui( void ) const { return gui; } + + // a discrete surface will never be merged with other surfaces by dmap, which is + // necessary to prevent mutliple gui surfaces, mirrors, autosprites, and some other + // special effects from being combined into a single surface + // guis, merging sprites or other effects, mirrors and remote views are always discrete + bool IsDiscrete( void ) const { return ( entityGui || gui || deform != DFRM_NONE || sort == SS_SUBVIEW || + ( surfaceFlags & SURF_DISCRETE ) != 0 ); } + + // Normally, dmap chops each surface by every BSP boundary, then reoptimizes. + // For gigantic polygons like sky boxes, this can cause a huge number of planar + // triangles that make the optimizer take forever to turn back into a single + // triangle. The "noFragment" option causes dmap to only break the polygons at + // area boundaries, instead of every BSP boundary. This has the negative effect + // of not automatically fixing up interpenetrations, so when this is used, you + // should manually make the edges of your sky box exactly meet, instead of poking + // into each other. + bool NoFragment( void ) const { return ( surfaceFlags & SURF_NOFRAGMENT ) != 0; } + +// RAVEN BEGIN +// dluetscher: added SURF_NO_T_FIX to merge surfaces (like decals), but skipping any T-junction fixing + bool NoTFix( void ) const { return ( surfaceFlags & SURF_NO_T_FIX ) != 0; } +// RAVEN END + + //------------------------------------------------------------------ + +// RAVEN BEGIN +// jscott: added accessor + const rvDeclMatType * GetMaterialType( void ) const { return( materialType ); } + const rvDeclMatType * GetMaterialType( idVec2 &tc ) const; + byte * GetMaterialTypeArray( void ) const { return( materialTypeArray ); } + const char * GetMaterialTypeArrayName( void ) const { return( materialTypeArrayName.c_str() ); } + +// jscott: for profiling + int GetTexelCount( void ) const; + +// jscott: for error checking + bool HasDefaultedImage( void ) const; + +// jscott: for Radiant + idImage * GetDiffuseImage( void ) const; + +// AReis: New portal distance culling stuff. + float GetPortalNear( void ) const { return( portalDistanceNear ); } + float GetPortalFar( void ) const { return( portalDistanceFar ); } + const idImage * GetPortalImage( void ) const { return( portalImage ); } + +// jscott: to prevent a recursive crash + virtual bool RebuildTextSource( void ) { return( false ); } +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; +// RAVEN END + + //================================================================== + // light shader specific functions, only called for light entities + + // lightshader option to fill with fog from viewer instead of light from center + bool IsFogLight() const { return fogLight; } + + // perform simple blending of the projection, instead of interacting with bumps and textures + bool IsBlendLight() const { return blendLight; } + + // an ambient light has non-directional bump mapping and no specular + bool IsAmbientLight() const { return ambientLight; } + + // implicitly no-shadows lights (ambients, fogs, etc) will never cast shadows + // but individual light entities can also override this value + bool LightCastsShadows() const { return TestMaterialFlag( MF_FORCESHADOWS ) || + ( !fogLight && !ambientLight && !blendLight && !TestMaterialFlag( MF_NOSHADOWS ) ); } + + // fog lights, blend lights, ambient lights, etc will all have to have interaction + // triangles generated for sides facing away from the light as well as those + // facing towards the light. It is debatable if noshadow lights should effect back + // sides, making everything "noSelfShadow", but that would make noshadow lights + // potentially slower than normal lights, which detracts from their optimization + // ability, so they currently do not. + bool LightEffectsBackSides() const { return fogLight || ambientLight || blendLight; } + + // NULL unless an image is explicitly specified in the shader with "lightFalloffShader " + idImage * LightFalloffImage() const { return lightFalloffImage; } + + //------------------------------------------------------------------ + + // returns the renderbump command line for this shader, or an empty string if not present + const char * GetRenderBump() const { return renderBump; }; + + // set specific material flag(s) + void SetMaterialFlag( const int flag ) const { materialFlags |= flag; } + + // clear specific material flag(s) + void ClearMaterialFlag( const int flag ) const { materialFlags &= ~flag; } + + // test for existance of specific material flag(s) + bool TestMaterialFlag( const int flag ) const { return ( materialFlags & flag ) != 0; } + + // get content flags + const int GetContentFlags( void ) const { return contentFlags; } + + // get surface flags + const int GetSurfaceFlags( void ) const { return surfaceFlags; } + + // gets name for surface type (stone, metal, flesh, etc.) + const surfTypes_t GetSurfaceType( void ) const { return static_cast( surfaceFlags & SURF_TYPE_MASK ); } + + // get material description + const char * GetDescription( void ) const { return desc; } + + // get sort order + const float GetSort( void ) const { return sort; } + // this is only used by the gui system to force sorting order + // on images referenced from tga's instead of materials. + // this is done this way as there are 2000 tgas the guis use + void SetSort( float s ) const { sort = s; }; + + // DFRM_NONE, DFRM_SPRITE, etc + deform_t Deform( void ) const { return deform; } + + // flare size, expansion size, etc + const int GetDeformRegister( int index ) const { return deformRegisters[index]; } + + // particle system to emit from surface and table for turbulent + const idDecl *GetDeformDecl( void ) const { return deformDecl; } + + // currently a surface can only have one unique texgen for all the stages + texgen_t Texgen() const; + + // wobble sky parms + const int * GetTexGenRegisters( void ) const { return texGenRegisters; } + + // get cull type + const cullType_t GetCullType( void ) const { return cullType; } + + float GetEditorAlpha( void ) const { return editorAlpha; } + + int GetEntityGui( void ) const { return entityGui; } + + decalInfo_t GetDecalInfo( void ) const { return decalInfo; } + + // spectrums are used for "invisible writing" that can only be + // illuminated by a light of matching spectrum + int Spectrum( void ) const { return spectrum; } + + float GetPolygonOffset( void ) const { return polygonOffset; } + + float GetSurfaceArea( void ) const { return surfaceArea; } + void AddToSurfaceArea( float area ) { surfaceArea += area; } + + //------------------------------------------------------------------ + + // returns the length, in milliseconds, of the videoMap on this material, + // or zero if it doesn't have one + int CinematicLength( void ) const; + + void CloseCinematic( void ) const; + + void ResetCinematicTime( int time ) const; + + void UpdateCinematic( int time ) const; + + //------------------------------------------------------------------ + + // gets an image for the editor to use + idImage * GetEditorImage( void ) const; + int GetImageWidth( void ) const; + int GetImageHeight( void ) const; + + void SetGui( const char *_gui ) const; + + // just for resource tracking + void SetImageClassifications( int tag ) const; + + //------------------------------------------------------------------ + + // returns number of registers this material contains + const int GetNumRegisters() const { return numRegisters; } + +// RAVEN BEGIN +// rjohnson: added vertex randomizing + // regs should point to a float array large enough to hold GetNumRegisters() floats + void EvaluateRegisters( float *regs, const float entityParms[MAX_ENTITY_SHADER_PARMS], + const struct viewDef_s *view, int soundEmitter = 0, idVec3 *randomizer = NULL ) const; +// RAVEN END + +// RAVEN BEGIN +// rjohnson: added new decal support + void EvaluateStageRegisters( int StageIndex, float *registers, const float shaderParms[MAX_ENTITY_SHADER_PARMS], float FloatTime) const; + +// rjohnson: started tracking image/material usage + void ClearUseCount( void ) { useCount = 0; } + void IncreaseUseCount( void ) { useCount++; globalUseCount++; } + int GetUseCount( void ) { return useCount; } + int GetGlobalUseCount( void ) const { return globalUseCount; } + void ResolveUse( void ); +// RAVEN END + + // if a material only uses constants (no entityParm or globalparm references), this + // will return a pointer to an internal table, and EvaluateRegisters will not need + // to be called. If NULL is returned, EvaluateRegisters must be used. + const float * ConstantRegisters() const; + + bool SuppressInSubview() const { return suppressInSubview; }; + bool IsPortalSky() const { return portalSky; }; + void AddReference(); + +private: + // parse the entire material + void CommonInit(); +// RAVEN BEGIN +// scork: now returns false (WHEN VALIDATING) under circumstances which would cause a common->FatalError normally, caller should bail ASAP on return. + bool ParseMaterial( idLexer &src ); +// RAVEN END + bool MatchToken( idLexer &src, const char *match ); + void ParseSort( idLexer &src ); + void ParseBlend( idLexer &src, shaderStage_t *stage ); + void ParseVertexParm( idLexer &src, newShaderStage_t *newStage ); +// RAVEN BEGIN +// AReis: New fragment parm stuff. + void ParseFragmentParm( idLexer &src, newShaderStage_t *newStage ); +// RAVEN END + void ParseFragmentMap( idLexer &src, newShaderStage_t *newStage ); + +// RAVEN BEGIN +// scork: now returns false (WHEN VALIDATING) under circumstances which would cause a common->FatalError normally, caller should bail ASAP on return. + bool ParseStage( idLexer &src, const textureRepeat_t trpDefault = TR_REPEAT ); +// RAVEN END + void ParseDeform( idLexer &src ); + void ParseDecalInfo( idLexer &src ); + bool CheckSurfaceParm( idToken *token ); + int GetExpressionConstant( float f ); + int GetExpressionTemporary( void ); + expOp_t * GetExpressionOp( void ); + int EmitOp( int a, int b, expOpType_t opType ); + int ParseEmitOp( idLexer &src, int a, expOpType_t opType, int priority ); + int ParseTerm( idLexer &src ); + int ParseExpressionPriority( idLexer &src, int priority ); + int ParseExpression( idLexer &src ); + void ClearStage( shaderStage_t *ss ); + int NameToSrcBlendMode( const idStr &name ); + int NameToDstBlendMode( const idStr &name ); + void MultiplyTextureMatrix( textureStage_t *ts, int registers[2][3] ); // FIXME: for some reason the const is bad for gcc and Mac + void SortInteractionStages(); +// RAVEN BEGIN +// scork: now returns false (WHEN VALIDATING) under circumstances which would cause a common->FatalError normally, caller should bail ASAP on return. + bool AddImplicitStages( const textureRepeat_t trpDefault = TR_REPEAT ); +// RAVEN END + void CheckForConstantRegisters(); + +private: + idStr desc; // description + idStr renderBump; // renderbump command options, without the "renderbump" at the start + + idImage * lightFalloffImage; + + int entityGui; // draw a gui with the idUserInterface from the renderEntity_t + // non zero will draw gui, gui2, or gui3 from renderEnitty_t + mutable idUserInterface *gui; // non-custom guis are shared by all users of a material + +// RAVEN BEGIN +// jscott: for material types + const rvDeclMatType *materialType; + byte *materialTypeArray; // an array of material type indices generated from the hit image + idStr materialTypeArrayName; + int MTAWidth; + int MTAHeight; + +// rjohnson: started tracking image/material usage + int useCount; + int globalUseCount; + +// AReis: New portal distance culling stuff. + float portalDistanceNear; + float portalDistanceFar; + idImage * portalImage; +// RAVEN END + + bool noFog; // surface does not create fog interactions + + int spectrum; // for invisible writing, used for both lights and surfaces + + float polygonOffset; + + int contentFlags; // content flags + int surfaceFlags; // surface flags + mutable int materialFlags; // material flags + + decalInfo_t decalInfo; + + + mutable float sort; // lower numbered shaders draw before higher numbered + deform_t deform; + int deformRegisters[4]; // numeric parameter for deforms + const idDecl *deformDecl; // for surface emitted particle deforms and tables + + int texGenRegisters[MAX_TEXGEN_REGISTERS]; // for wobbleSky + + materialCoverage_t coverage; + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + bool shouldCreateBackSides; + + bool fogLight; + bool blendLight; + bool ambientLight; + bool unsmoothedTangents; + bool hasSubview; // mirror, remote render, etc + bool allowOverlays; + + int numOps; + expOp_t * ops; // evaluate to make expressionRegisters + + int numRegisters; // + float * expressionRegisters; + + float * constantRegisters; // NULL if ops ever reference globalParms or entityParms + + int numStages; + int numAmbientStages; + + shaderStage_t * stages; + + struct mtrParsingData_s *pd; // only used during parsing + + float surfaceArea; // only for listSurfaceAreas + + // we defer loading of the editor image until it is asked for, so the game doesn't load up + // all the invisible and uncompressed images. + // If editorImage is NULL, it will atempt to load editorImageName, and set editorImage to that or defaultImage + idStr editorImageName; + mutable idImage * editorImage; // image used for non-shaded preview + float editorAlpha; + + bool suppressInSubview; + bool portalSky; + int refCount; +}; + +typedef idList idMatList; + +// RAVEN BEGIN +class rvMaterialEdit +{ +public: + virtual ~rvMaterialEdit() {} + virtual void SetGui( idMaterial *edit, const char *name ) = 0; + virtual int GetImageWidth( const idMaterial *edit ) const = 0; + virtual int GetImageHeight( const idMaterial *edit ) const = 0; +}; + +extern rvMaterialEdit *materialEdit; +// RAVEN END + +#endif /* !__MATERIAL_H__ */ diff --git a/src/renderer/MegaTexture.h b/src/renderer/MegaTexture.h new file mode 100644 index 0000000..ad6e79c --- /dev/null +++ b/src/renderer/MegaTexture.h @@ -0,0 +1,95 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +class idTextureTile { +public: + int x, y; +}; + +static const int TILE_PER_LEVEL = 4; +static const int MAX_MEGA_CHANNELS = 3; // normal, diffuse, specular +static const int MAX_LEVELS = 12; +static const int MAX_LEVEL_WIDTH = 512; +static const int TILE_SIZE = MAX_LEVEL_WIDTH / TILE_PER_LEVEL; + +class idMegaTexture; + +class idTextureLevel { +public: + idMegaTexture *mega; + + int tileOffset; + int tilesWide; + int tilesHigh; + + idImage *image; + idTextureTile tileMap[TILE_PER_LEVEL][TILE_PER_LEVEL]; + + float parms[4]; + + void UpdateForCenter( float center[2] ); + void UpdateTile( int localX, int localY, int globalX, int globalY ); + void Invalidate(); +}; + +typedef struct { + int tileSize; + int tilesWide; + int tilesHigh; +} megaTextureHeader_t; + + +class idMegaTexture { +public: + bool InitFromMegaFile( const char *fileBase ); + void SetMappingForSurface( const srfTriangles_t *tri ); // analyzes xyz and st to create a mapping + void BindForViewOrigin( const idVec3 origin ); // binds images and sets program parameters + void Unbind(); // removes texture bindings + + static void MakeMegaTexture_f( const idCmdArgs &args ); +private: +friend class idTextureLevel; + void SetViewOrigin( const idVec3 origin ); + static void GenerateMegaMipMaps( megaTextureHeader_t *header, idFile *file ); + static void GenerateMegaPreview( const char *fileName ); + + idFile *fileHandle; + + const srfTriangles_t *currentTriMapping; + + idVec3 currentViewOrigin; + + float localViewToTextureCenter[2][4]; + + int numLevels; + idTextureLevel levels[MAX_LEVELS]; // 0 is the highest resolution + megaTextureHeader_t header; + + static idCVar r_megaTextureLevel; + static idCVar r_showMegaTexture; + static idCVar r_showMegaTextureLabels; + static idCVar r_skipMegaTexture; + static idCVar r_terrainScale; +}; + diff --git a/src/renderer/Model.h b/src/renderer/Model.h new file mode 100644 index 0000000..207016f --- /dev/null +++ b/src/renderer/Model.h @@ -0,0 +1,591 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MODEL_H__ +#define __MODEL_H__ + +/* +=============================================================================== + + Render Model + +=============================================================================== +*/ + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#include "../idlib/rvMemSys.h" +// RAVEN END + +// RAVEN BEGIN +// dluetscher: declare some classes for MD5R support +#ifdef _MD5R_SUPPORT +class rvMesh; +#endif +// RAVEN END + +// shared between the renderer, game, and Maya export DLL +#define MD5_VERSION_STRING "MD5Version" +#define MD5_MESH_EXT "md5mesh" +#define MD5_ANIM_EXT "md5anim" +#define MD5_CAMERA_EXT "md5camera" +#define MD5_VERSION 10 + +// using shorts for triangle indexes can save a significant amount of traffic, but +// to support the large models that renderBump loads, they need to be 32 bits +#if 1 + +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef int glIndex_t; + +#else + +#define GL_INDEX_TYPE GL_UNSIGNED_SHORT +typedef short glIndex_t; + +#endif + + +typedef struct silEdge_t { + // NOTE: making this a glIndex is dubious, as there can be 2x the faces as verts + int p1, p2; // planes defining the edge + int v1, v2; // verts defining the edge +} silEdge_t; + +// this is used for calculating unsmoothed normals and tangents for deformed models +typedef struct dominantTri_s { + glIndex_t v2, v3; + float normalizationScale[3]; +} dominantTri_t; + +typedef struct lightingCache_s { + idVec3 localLightVector; // this is the statically computed vector to the light + // in texture space for cards without vertex programs +} lightingCache_t; + +typedef struct shadowCache_s { + idVec4 xyz; // we use homogenous coordinate tricks +} shadowCache_t; + +// RAVEN BEGIN +// DLuetscher: Added the vertex cache neded for penumbra map support +#ifdef _PENUMBRA_MAP_SUPPORT +typedef struct penumbraCache_s { + idVec3 xyz; // local coordinates + idVec2 colorParam; +} penumbraCache_t; +#endif +// RAVEN END + +const int SHADOW_CAP_INFINITE = 64; + +// our only drawing geometry type +typedef struct srfTriangles_s { + idBounds bounds; // for culling + + int ambientViewCount; // if == tr.viewCount, it is visible this view + + bool generateNormals; // create normals from geometry, instead of using explicit ones + bool tangentsCalculated; // set when the vertex tangents have been calculated + bool facePlanesCalculated; // set when the face planes have been calculated + bool perfectHull; // true if there aren't any dangling edges + bool deformedSurface; // if true, indexes, silIndexes, mirrorVerts, and silEdges are + // pointers into the original surface, and should not be freed + + int numVerts; // number of vertices + idDrawVert * verts; // vertices, allocated with special allocator +// RAVEN BEGIN +// dluetscher: added support for the rvSilTraceVertT as a replacement for some system-memory idDrawVerts (MD5R case) +#ifdef _MD5R_SUPPORT + rvSilTraceVertT * silTraceVerts; // sil-trace vertices (system memory copy of verts for sil-trace usage) + rvSilTraceVertT * silTraceVertsAlloc; // if not NULL, same array of sil-trace vertices as above, but must be freed +#elif defined( Q4SDK_MD5R ) +// Q4SDK: maintain compatible structure padding + void* silTraceVerts; + void* silTraceVertsAlloc; +#endif +// RAVEN END + + int numIndexes; // for shadows, this has both front and rear end caps and silhouette planes + glIndex_t * indexes; // indexes, allocated with special allocator + + glIndex_t * silIndexes; // indexes changed to be the first vertex with same XYZ, ignoring normal and texcoords + + int numMirroredVerts; // this many verts at the end of the vert list are tangent mirrors + int * mirroredVerts; // tri->mirroredVerts[0] is the mirror of tri->numVerts - tri->numMirroredVerts + 0 + + int numDupVerts; // number of duplicate vertexes + int * dupVerts; // pairs of the number of the first vertex and the number of the duplicate vertex + + int numSilEdges; // number of silhouette edges + silEdge_t * silEdges; // silhouette edges + + idPlane * facePlanes; // [numIndexes/3] plane equations + + dominantTri_t * dominantTris; // [numVerts] for deformed surface fast tangent calculation + + int numShadowIndexesNoFrontCaps; // shadow volumes with front caps omitted + int numShadowIndexesNoCaps; // shadow volumes with the front and rear caps omitted + + int shadowCapPlaneBits; // bits 0-5 are set when that plane of the interacting light has triangles + // projected on it, which means that if the view is on the outside of that + // plane, we need to draw the rear caps of the shadow volume + // turboShadows will have SHADOW_CAP_INFINITE + + shadowCache_t * shadowVertexes; // these will be copied to shadowCache when it is going to be drawn. + // these are NULL when vertex programs are available + + struct srfTriangles_s * ambientSurface; // for light interactions, point back at the original surface that generated + // the interaction, which we will get the ambientCache from + + struct srfTriangles_s * nextDeferredFree; // chain of tris to free next frame + + // data in vertex object space, not directly readable by the CPU + struct vertCache_s * indexCache; // int + struct vertCache_s * ambientCache; // idDrawVert + struct vertCache_s * lightingCache; // lightingCache_t + struct vertCache_s * shadowCache; // shadowCache_t + +// RAVEN BEGIN +// dluetscher: Added the vertex cache neded for penumbra map support +#ifdef _PENUMBRA_MAP_SUPPORT + struct vertCache_s * penumbraCache; // penumbraCache_t +#endif +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added support for new style of meshes (rvMesh used by rvRenderModelMD5R) that +// are based on primitive batches of "static" geometry (can be skinned) whose +// vertices always live on the video card for the purposes of drawing +#ifdef _MD5R_SUPPORT + rvMesh * primBatchMesh; // rvMesh that is based on static vertex buffers, index buffers, and primitive batches + float * skinToModelTransforms; // array of skin-to-model transforms, 4x4, stored in row-major array ordering, with translation in last column (column-major matrix) + float * skinToModelTransformsAlloc; // if not NULL, same array of skin-to-model transforms as above, but must be freed + int numSkinToModelTransforms; // the number of skin-to-model transforms stored in the above array +#elif defined( Q4SDK_MD5R ) +// Q4SDK: maintain compatible structure padding + void* primBatchMesh; + float* skinToModelTransforms; + float* skinToModelTransformsAlloc; + int numSkinToModelTransforms; +#endif +// RAVEN END + +// RAVEN BEGIN +// jscott: for modview + bool modviewSelected; +// jscott: for security + int numAllocedVerts; + int numAllocedIndices; +// rjohnson: attempt to fix editor crashes + int referenceCount; + struct srfTriangles_s * topAmbientSurface; + int myID; + bool noAmbientSurfaces; + bool didSilPremultiply; + bool tempAmbientCache; +#ifdef _DEBUG + char description[64]; +#endif +// RAVEN END +} srfTriangles_t; + +typedef idList idTriList; + +typedef struct modelSurface_s { + int id; + const idMaterial * shader; + srfTriangles_t * geometry; + +// RAVEN BEGIN +// rjohnson: added block + idStr* mOriginalSurfaceName; +// RAVEN END +} modelSurface_t; + +// RAVEN BEGIN +// bdube: tag system +typedef struct modelTag_s { + idStr name; + idVec3 t; + idMat3 m; +} modelTag_t; +// RAVEN END + +typedef enum { + DM_STATIC, // never creates a dynamic model + DM_CACHED, // once created, stays constant until the entity is updated (animating characters) + DM_CONTINUOUS // must be recreated for every single view (time dependent things like particles) +} dynamicModel_t; + +typedef enum { + INVALID_JOINT = -1 +} jointHandle_t; + +struct jointWeight_t { + float weight; // joint weight + int jointMatOffset; // offset in bytes to the joint matrix + int nextVertexOffset; // offset in bytes to the first weight for the next vertex +}; + +// offsets for SIMD code +#define BASEVECTOR_SIZE (4*4) // sizeof( idVec4 ) +#define JOINTWEIGHT_SIZE (3*4) // sizeof( jointWeight_t ) +#define JOINTWEIGHT_WEIGHT_OFFSET (0*4) // offsetof( jointWeight_t, weight ) +#define JOINTWEIGHT_JOINTMATOFFSET_OFFSET (1*4) // offsetof( jointWeight_t, jointMatOffset ) +#define JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET (2*4) // offsetof( jointWeight_t, nextVertexOffset ) + +assert_sizeof( idVec4, BASEVECTOR_SIZE ); +assert_sizeof( jointWeight_t, JOINTWEIGHT_SIZE ); +assert_offsetof( jointWeight_t, weight, JOINTWEIGHT_WEIGHT_OFFSET ); +assert_offsetof( jointWeight_t, jointMatOffset, JOINTWEIGHT_JOINTMATOFFSET_OFFSET ); +assert_offsetof( jointWeight_t, nextVertexOffset, JOINTWEIGHT_NEXTVERTEXOFFSET_OFFSET ); + +class idMD5Joint { +public: + idMD5Joint() { parent = NULL; } + idStr name; + const idMD5Joint * parent; +}; + +// RAVEN BEGIN +// AReis: Used for callback. +class idRenderModel; +typedef bool(*modelCallback_t)( idRenderModel *model, void *callbackData ); +// RAVEN END + +// RAVEN BEGIN +// Used for Fluid Interaction. +typedef struct fluidImpact_s +{ + // The absolute position of the impact. + idVec3 vAbsPos; + + // The force of the impact. + float fForce; + + float radius; + +} fluidImpact_t; +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + +// Create a new idRenderModel instance or one of it's derivitive classes, such +// that any further memory allocated by the instance can be directed to the same +// heap. +template +T* NewRenderModel(Rv_Sys_Heap_ID_t heapID) +{ + // Push the heap context to be used for both instance and all allocations + // that the instance makes. + RV_PUSH_SYS_HEAP_ID(heapID); + + T* model=new T; + assert(static_cast(model)!=0); +#if defined(_DEBUG) + if(model) + { + model->heapID=heapID; + } +#endif + + // Pop the heap context. + RV_POP_HEAP(); + + return model; +} + +// Create a new idRenderModel instance or one of it's derivitive classes, such +// that any further memory allocated by the instance can be directed to the same +// heap. +template +T* NewRenderModel(const idRenderModel* m) +{ + // Push the heap context to be used for both instance and all allocations + // that the instance makes. + bool ok=rvPushHeapContainingMemory(m); + + T* model=new T; + assert(static_cast(model)!=0); +#if defined(_DEBUG) + if(model) + { + model->heapPtr=currentHeapArena->GetHeap(const_cast((const void*)m)); + } +#endif + + // Pop the heap context. + if(ok) + { + RV_POP_HEAP(); + } + + return model; +} + +#else + +// Single heap memory model. +template +T* NewRenderModel(Rv_Sys_Heap_ID_t) +{ + return new T; +} + +// Single heap memory model. +template +T* NewRenderModel(const idRenderModel*) +{ + return new T; +} + +#endif +// RAVEN END + +// the init methods may be called again on an already created model when +// a reloadModels is issued +class idRenderModel { +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) && defined(_DEBUG) +public: + // Debug info... the heap this instance was allocated into. + Rv_Sys_Heap_ID_t heapID; + rvHeap* heapPtr; +#endif +// RAVEN END + +public: +// RAVEN BEGIN +// AReis: Needed to send data to model. + // Callbacks to a model specified function. + modelCallback_t callback; + +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + // All the materials that are referenced by an instance of an idRenderModel. + idList allMaterials; +#endif + +// AReis: Specific just to a fluid model. + // Dampen a grid element that intersects the world. + virtual void DampenFluidGrid( int iX, int iY, float fAmount ) {} + +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) && defined(_DEBUG) + idRenderModel( void ) : + heapID(RV_HEAP_ID_DEFAULT), + heapPtr(0) + { + } +#endif + +// rjohnson: added debugging code to try and catch a free error + // purges all the data before deleting + virtual ~idRenderModel( void ); +// RAVEN END + + // Loads static models only, dynamic models must be loaded by the modelManager + virtual void InitFromFile( const char *fileName ) = 0; + + // renderBump uses this to load the very high poly count models, skipping the + // shadow and tangent generation, along with some surface cleanup to make it load faster + virtual void PartialInitFromFile( const char *fileName ) = 0; + + // this is used for dynamically created surfaces, which are assumed to not be reloadable. + // It can be called again to clear out the surfaces of a dynamic model for regeneration. + virtual void InitEmpty( const char *name ) = 0; + +// RAVEN BEGIN +// AReis: Added this function for the height map model. + // Like InitEmpty but allows a set of arguments to be passed in through a dict. + virtual void InitEmptyFromArgs( const char *name, idDict &Args ) = 0; +// RAVEN END + + // dynamic model instantiations will be created with this + // the geometry data will be owned by the model, and freed when it is freed + // the geoemtry should be raw triangles, with no extra processing + virtual void AddSurface( modelSurface_t surface ) = 0; + + // cleans all the geometry and performs cross-surface processing + // like shadow hulls + // Creates the duplicated back side geometry for two sided, alpha tested, lit materials + // This does not need to be called if none of the surfaces added with AddSurface require + // light interaction, and all the triangles are already well formed. + virtual void FinishSurfaces( void ) = 0; + + // frees all the data, but leaves the class around for dangling references, + // which can regenerate the data with LoadModel() + virtual void PurgeModel() = 0; + + // resets any model information that needs to be reset on a same level load etc.. + // currently only implemented for liquids + virtual void Reset() = 0; + + // used for initial loads, reloadModel, and reloading the data of purged models + // Upon exit, the model will absolutely be valid, but possibly as a default model + virtual void LoadModel() = 0; + + // internal use + virtual bool IsLoaded() = 0; + virtual void SetLevelLoadReferenced( bool referenced ) = 0; + virtual bool IsLevelLoadReferenced() = 0; + + // models that are already loaded at level start time + // will still touch their data to make sure they + // are kept loaded + virtual void TouchData() = 0; + + // dump any ambient caches on the model surfaces + virtual void FreeVertexCache() = 0; + + // returns the name of the model + virtual const char * Name() const = 0; + + // prints a detailed report on the model for printModel + virtual void Print() const = 0; + + // prints a single line report for listModels + virtual void List() const = 0; + + // reports the amount of memory (roughly) consumed by the model + virtual int Memory() const = 0; + + // for reloadModels + virtual unsigned int Timestamp() const = 0; + + // returns the number of surfaces + virtual int NumSurfaces() const = 0; + + // NumBaseSurfaces will not count any overlays added to dynamic models + virtual int NumBaseSurfaces() const = 0; + + // get a pointer to a surface + virtual const modelSurface_t *Surface( int surfaceNum ) const = 0; + + // Allocates surface triangles. + // Allocates memory for srfTriangles_t::verts and srfTriangles_t::indexes + // The allocated memory is not initialized. + // srfTriangles_t::numVerts and srfTriangles_t::numIndexes are set to zero. + virtual srfTriangles_t * AllocSurfaceTriangles( int numVerts, int numIndexes ) const = 0; + + // Frees surfaces triangles. + virtual void FreeSurfaceTriangles( srfTriangles_t *tris ) const = 0; + + // created at load time by stitching together all surfaces and sharing + // the maximum number of edges. This may be incorrect if a skin file + // remaps surfaces between shadow casting and non-shadow casting, or + // if some surfaces are noSelfShadow and others aren't + virtual srfTriangles_t * ShadowHull( void ) const = 0; + + // models of the form "_area*" may have a prelight shadow model associated with it + virtual bool IsStaticWorldModel( void ) const = 0; + + // models parsed from inside map files or dynamically created cannot be reloaded by + // reloadmodels + virtual bool IsReloadable( void ) const = 0; + + // md3, md5, particles, etc + virtual dynamicModel_t IsDynamicModel( void ) const = 0; + + // if the load failed for any reason, this will return true + virtual bool IsDefaultModel( void ) const = 0; + + // dynamic models should return a fast, conservative approximation + // static models should usually return the exact value + virtual idBounds Bounds( const struct renderEntity_s *ent = NULL ) const = 0; + + // returns value != 0.0f if the model requires the depth hack + virtual float DepthHack( void ) const = 0; + +// RAVEN BEGIN +// dluetscher: added call to determine if a collision surface exists within this model + virtual bool HasCollisionSurface( const struct renderEntity_s *ent ) const = 0; +// RAVEN END + + // returns a static model based on the definition and view + // currently, this will be regenerated for every view, even though + // some models, like character meshes, could be used for multiple (mirror) + // views in a frame, or may stay static for multiple frames (corpses) + // The renderer will delete the returned dynamic model the next view + // This isn't const, because it may need to reload a purged model if it + // wasn't precached correctly. +// RAVEN BEGIN +// dluetscher: added surface mask parameter + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask = ~SURF_COLLISION ) = 0; +// RAVEN END + + // Returns the number of joints or 0 if the model is not an MD5 + virtual int NumJoints( void ) const = 0; + + // Returns the MD5 joints or NULL if the model is not an MD5 + virtual const idMD5Joint * GetJoints( void ) const = 0; + + // Returns the handle for the joint with the given name. + virtual jointHandle_t GetJointHandle( const char *name ) const = 0; + + // Returns the name for the joint with the given handle. + virtual const char * GetJointName( jointHandle_t handle ) const = 0; + + // Returns the default animation pose or NULL if the model is not an MD5. + virtual const idJointQuat * GetDefaultPose( void ) const = 0; + + // Returns number of the joint nearest to the given triangle. + virtual int NearestJoint( int surfaceNum, int a, int c, int b ) const = 0; + + // Writing to and reading from a demo file. + virtual void ReadFromDemoFile( class idDemoFile *f ) = 0; + virtual void WriteToDemoFile( class idDemoFile *f ) = 0; + +// RAVEN BEGIN +// bdube: surface flag manipulation + virtual int GetSurfaceMask ( const char* surface ) const = 0;; + +// jscott: for portal skies + virtual void SetHasSky( bool on ) = 0; + virtual bool GetHasSky( void ) const = 0; +// ddynerman: Wolf LOD code + virtual void SetViewEntity( const struct viewEntity_s *ve ) = 0; +// RAVEN END + +// RAVEN BEGIN +#if defined( _MD5R_SUPPORT ) +// dluetscher: added method to determine if model maintains system-memory dynamic meshes (used +// for traces and silhouette determination) that are separate from pairs of video-memory +// meshes - one used for shadow volume drawing and one used for normal interaction drawing +// NOTE: currently, only MD5R models return true, all others return false + virtual bool HasSeparateSilTraceMeshes( void ) const; +#endif +// RAVEN END + +// RAVEN END +}; + +#endif /* !__MODEL_H__ */ diff --git a/src/renderer/ModelDecal.h b/src/renderer/ModelDecal.h new file mode 100644 index 0000000..6f3e7d8 --- /dev/null +++ b/src/renderer/ModelDecal.h @@ -0,0 +1,122 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MODELDECAL_H__ +#define __MODELDECAL_H__ + +/* +=============================================================================== + + Decals are lightweight primitives for bullet / blood marks. + Decals with common materials will be merged together, but additional + decals will be allocated as needed. The material should not be + one that receives lighting, because no interactions are generated + for these lightweight surfaces. + + FIXME: Decals on models in portalled off areas do not get freed + until the area becomes visible again. + +=============================================================================== +*/ + +const int NUM_DECAL_BOUNDING_PLANES = 6; + +typedef struct decalProjectionInfo_s { + idVec3 projectionOrigin; + idBounds projectionBounds; + idPlane boundingPlanes[6]; + idPlane fadePlanes[2]; + idPlane textureAxis[2]; + const idMaterial * material; + bool parallel; + float fadeDepth; + int startTime; + float maxAngle; + bool force; +} decalProjectionInfo_t; + + +class idRenderModelDecal { +friend class rvPrimBatch; +public: + idRenderModelDecal( void ) { + memset( &tri, 0, sizeof( tri ) ); + tri.verts = verts; + tri.indexes = indexes; + material = NULL; + nextDecal = NULL; + } + ~idRenderModelDecal( void ) {} + + static idRenderModelDecal * Alloc( void ); + static void Free( idRenderModelDecal *decal ); + + // Creates decal projection info. + static bool CreateProjectionInfo( decalProjectionInfo_t &info, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ); + + // Transform the projection info from global space to local. + static void GlobalProjectionInfoToLocal( decalProjectionInfo_t &localInfo, const decalProjectionInfo_t &info, const idVec3 &origin, const idMat3 &axis ); + + // Creates a deal on the given model. + void CreateDecal( const idRenderModel *model, const decalProjectionInfo_t &localInfo ); + + // Remove decals that are completely faded away. + static idRenderModelDecal * RemoveFadedDecals( idRenderModelDecal *decals, int time ); + + // Updates the vertex colors, removing any faded indexes, + // then copy the verts to temporary vertex cache and adds a drawSurf. + void AddDecalDrawSurf( struct viewEntity_s *space ); + + // Returns the next decal in the chain. + idRenderModelDecal * Next( void ) const { return nextDecal; } + + void ReadFromDemoFile( class idDemoFile *f ) { (void)f; } + void WriteToDemoFile( class idDemoFile *f ) const { (void)f; } + +private: + static const int MAX_DECAL_VERTS = 40; + static const int MAX_DECAL_INDEXES = 60; + static int allocatedDecalCount; + + const idMaterial * material; + srfTriangles_t tri; + idDrawVert verts[MAX_DECAL_VERTS]; + float vertDepthFade[MAX_DECAL_VERTS]; + glIndex_t indexes[MAX_DECAL_INDEXES]; + float indexStartTime[MAX_DECAL_INDEXES]; + float vertLifeSpan[MAX_DECAL_VERTS]; + idRenderModelDecal * nextDecal; + + // Adds the winding triangles to the appropriate decal in the + // chain, creating a new one if necessary. + void AddWinding( const idWinding &w, const idMaterial *decalMaterial, const idPlane fadePlanes[2], float fadeDepth, int startTime ); + + // Adds depth faded triangles for the winding to the appropriate + // decal in the chain, creating a new one if necessary. + // The part of the winding at the front side of both fade planes is not faded. + // The parts at the back sides of the fade planes are faded with the given depth. + void AddDepthFadedWinding( const idWinding &w, const idMaterial *decalMaterial, const idPlane fadePlanes[2], float fadeDepth, int startTime ); +}; + +#endif /* !__MODELDECAL_H__ */ diff --git a/src/renderer/ModelManager.h b/src/renderer/ModelManager.h new file mode 100644 index 0000000..86ba8e3 --- /dev/null +++ b/src/renderer/ModelManager.h @@ -0,0 +1,133 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __MODELMANAGER_H__ +#define __MODELMANAGER_H__ + +// RAVEN BEGIN +// Modview +#define MVJOINT_SELECTED BIT( 0 ) +#define MVJOINT_HIDDEN BIT( 1 ) +// RAVEN END + +/* +=============================================================================== + + Model Manager + + Temporarily created models do not need to be added to the model manager. + +=============================================================================== +*/ + +class idRenderModelManager { +public: + virtual ~idRenderModelManager() {} + + // registers console commands and clears the list + virtual void Init() = 0; + + // frees all the models + virtual void Shutdown() = 0; + +// RAVEN BEGIN + // Reset list of model to initial state (i.e. destroys all models except the default models). + // This also will void the incremental creep in memory consumption as one progresses form + // one level to the next, since we delete the idRenderModel instances which can add up to + // several MB. + virtual void Reset(void) = 0; +// RAVEN END + + // called only by renderer::BeginLevelLoad + virtual void BeginLevelLoad() = 0; + + // called only by renderer::EndLevelLoad + virtual void EndLevelLoad() = 0; + + // allocates a new empty render model. + virtual idRenderModel * AllocModel() = 0; + + // frees a render model + virtual void FreeModel( idRenderModel *model ) = 0; + + // returns NULL if modelName is NULL or an empty string, otherwise + // it will create a default model if not loadable + virtual idRenderModel * FindModel( const char *modelName ) = 0; + + // returns NULL if not loadable + virtual idRenderModel * CheckModel( const char *modelName ) = 0; + +// RAVEN BEGIN +// jscott: for tools + virtual srfTriangles_t *AllocStaticTriSurf( int verts, int indices ) = 0; + virtual void FreeStaticTriSurf( srfTriangles_t *tris ) = 0; + virtual srfTriangles_t *CopyStaticTriSurf( const srfTriangles_t *tri ) = 0; + virtual srfTriangles_t *PolytopeSurface( int numPlanes, const idPlane *planes, idWinding **windings ) = 0; + virtual void CreateSilIndexes( srfTriangles_t *tris ) = 0; + virtual void DeriveFacePlanes( srfTriangles_t *tris ) = 0; + virtual void BoundTriSurf( srfTriangles_t *tri ) = 0; + virtual void CleanupTriangles( srfTriangles_t *tris, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents, bool needSilMultiply ) = 0; + virtual void SimpleCleanupTriangles( srfTriangles_t *tri ) = 0; + virtual srfTriangles_t *CreateShadowVolume( const srfTriangles_t *tri, const class idRenderLight *light, int optimize ) = 0; + + virtual class idRenderLight *CreateLightDef( void ) = 0; + virtual void FreeLightDef( class idRenderLight *light ) = 0; + +// rjohnson: added debugging code to try and catch a free error + virtual bool CheckModel( idRenderModel *model ) = 0; +// RAVEN END + + // returns the default cube model + virtual idRenderModel * DefaultModel() = 0; + + // world map parsing will add all the inline models with this call + virtual void AddModel( idRenderModel *model ) = 0; + + // when a world map unloads, it removes its internal models from the list + // before freeing them. + // There may be an issue with multiple renderWorlds that share data... + virtual void RemoveModel( idRenderModel *model ) = 0; + + // the reloadModels console command calls this, but it can + // also be explicitly invoked + virtual void ReloadModels( bool forceAll = false ) = 0; + + // write "touchModel " commands for each non-world-map model + virtual void WritePrecacheCommands( idFile *f ) = 0; + + // called during vid_restart + virtual void FreeModelVertexCaches() = 0; + + // print memory info + virtual void PrintMemInfo( MemInfo *mi ) = 0; + virtual size_t ListModelSummary( void ) = 0; +}; + +// this will be statically pointed at a private implementation +extern idRenderModelManager *renderModelManager; + +#endif /* !__MODELMANAGER_H__ */ diff --git a/src/renderer/ModelOverlay.h b/src/renderer/ModelOverlay.h new file mode 100644 index 0000000..5598a18 --- /dev/null +++ b/src/renderer/ModelOverlay.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MODELOVERLAY_H__ +#define __MODELOVERLAY_H__ + +/* +=============================================================================== + + Render model overlay for adding decals on top of dynamic models. + +=============================================================================== +*/ + +const int MAX_OVERLAY_SURFACES = 4; + +typedef struct overlayVertex_t { + int vertexNum; + float st[2]; +} overlayVertex_t; + +typedef struct overlaySurface_t { + int surfaceNum; + int surfaceId; + int surfaceNumVerts; + int numIndexes; + int * indexes; + int numVerts; + overlayVertex_t * verts; +} overlaySurface_t; + +typedef struct overlayMaterial_t { + const idMaterial * material; + idList surfaces; + bool modified; +} overlayMaterial_t; + + +class idRenderModelOverlay { +public: + idRenderModelOverlay() {} + ~idRenderModelOverlay(); + + static idRenderModelOverlay *Alloc( void ); + static void Free( idRenderModelOverlay *overlay ); + + // Projects an overlay onto deformable geometry and can be added to + // a render entity to allow decals on top of dynamic models. + // This does not generate tangent vectors, so it can't be used with + // light interaction shaders. Materials for overlays should always + // be clamped, because the projected texcoords can run well off the + // texture since no new clip vertexes are generated. + void CreateOverlay( const idRenderModel *model, const idPlane localTextureAxis[2], const idMaterial *material ); + + // Creates new model surfaces for baseModel, which should be a static instantiation of a dynamic model. + void AddOverlaySurfacesToModel( idRenderModel *baseModel ); + + // Removes overlay surfaces from the model. + static void RemoveOverlaySurfacesFromModel( idRenderModel *baseModel ); + + void ReadFromDemoFile( class idDemoFile *f ) { (void)f; } + void WriteToDemoFile( class idDemoFile *f ) const { (void)f; } + +private: + idList materials; +}; + +#endif /* !__MODELOVERLAY_H__ */ diff --git a/src/renderer/Model_ase.h b/src/renderer/Model_ase.h new file mode 100644 index 0000000..1901287 --- /dev/null +++ b/src/renderer/Model_ase.h @@ -0,0 +1,91 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MODEL_ASE_H__ +#define __MODEL_ASE_H__ + +/* +=============================================================================== + + ASE loader. (3D Studio Max ASCII Export) + +=============================================================================== +*/ + +typedef struct { + int vertexNum[3]; + int tVertexNum[3]; + idVec3 faceNormal; + idVec3 vertexNormals[3]; + byte vertexColors[3][4]; +} aseFace_t; + +typedef struct { + int timeValue; + + int numVertexes; + int numTVertexes; + int numCVertexes; + int numFaces; + int numTVFaces; + int numCVFaces; + + idVec3 transform[4]; // applied to normals + + bool colorsParsed; + bool normalsParsed; + idVec3 * vertexes; + idVec2 * tvertexes; + idVec3 * cvertexes; + aseFace_t * faces; +} aseMesh_t; + +typedef struct { + char name[128]; + float uOffset, vOffset; // max lets you offset by material without changing texCoords + float uTiling, vTiling; // multiply tex coords by this + float angle; // in clockwise radians +} aseMaterial_t; + +typedef struct { + char name[128]; + int materialRef; + + aseMesh_t mesh; + + // frames are only present with animations + idList frames; // aseMesh_t +} aseObject_t; + +typedef struct aseModel_s { + ID_TIME_T timeStamp; + idList materials; + idList objects; +} aseModel_t; + + +aseModel_t *ASE_Load( const char *fileName ); +void ASE_Free( aseModel_t *ase ); + +#endif /* !__MODEL_ASE_H__ */ diff --git a/src/renderer/Model_local.h b/src/renderer/Model_local.h new file mode 100644 index 0000000..f0dc379 --- /dev/null +++ b/src/renderer/Model_local.h @@ -0,0 +1,393 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MODEL_LOCAL_H__ +#define __MODEL_LOCAL_H__ + +/* +=============================================================================== + + Static model + +=============================================================================== +*/ + +class idRenderModelStatic : public idRenderModel { +friend class rvRenderModelMD5R; +public: + // the inherited public interface + static idRenderModel * Alloc(); + + idRenderModelStatic(); + virtual ~idRenderModelStatic(); + + virtual void InitFromFile( const char *fileName ); + virtual void PartialInitFromFile( const char *fileName ); + virtual void PurgeModel(); + virtual void Reset() {}; + virtual void LoadModel(); + virtual bool IsLoaded(); + virtual void SetLevelLoadReferenced( bool referenced ); + virtual bool IsLevelLoadReferenced(); + virtual void TouchData(); + virtual void InitEmpty( const char *name ); + virtual void InitEmptyFromArgs( const char *name, idDict &args ); + virtual void AddSurface( modelSurface_t surface ); + virtual void FinishSurfaces(); + virtual void FreeVertexCache(); + virtual const char * Name() const; + virtual void Print() const; + virtual void List() const; + virtual int Memory() const; + virtual ID_TIME_T Timestamp() const; + virtual int NumSurfaces() const; + virtual int NumBaseSurfaces() const; + virtual const modelSurface_t *Surface( int surfaceNum ) const; + virtual srfTriangles_t * AllocSurfaceTriangles( int numVerts, int numIndexes ) const; + virtual void FreeSurfaceTriangles( srfTriangles_t *tris ) const; + virtual srfTriangles_t * ShadowHull() const; + virtual bool IsStaticWorldModel() const; + virtual dynamicModel_t IsDynamicModel() const; + virtual bool IsDefaultModel() const; + virtual bool IsReloadable() const; + virtual bool HasCollisionSurface( const struct renderEntity_s *ent ) const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual int NumJoints( void ) const; + virtual const idMD5Joint * GetJoints( void ) const; + virtual jointHandle_t GetJointHandle( const char *name ) const; + virtual const char * GetJointName( jointHandle_t handle ) const; + virtual const idJointQuat * GetDefaultPose( void ) const; + virtual int NearestJoint( int surfaceNum, int a, int b, int c ) const; + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + virtual void ReadFromDemoFile( class idDemoFile *f ); + virtual void WriteToDemoFile( class idDemoFile *f ); + virtual float DepthHack() const; + virtual int GetSurfaceMask( const char *surface ) const; + virtual void SetHasSky( bool on ); + virtual bool GetHasSky() const; + virtual void SetViewEntity( const struct viewEntity_s *ve ); + + void MakeDefaultModel(); + + bool LoadASE( const char *fileName ); + bool LoadLWO( const char *fileName ); + bool LoadFLT( const char *fileName ); + bool LoadMA( const char *filename ); + + bool ConvertASEToModelSurfaces( const struct aseModel_s *ase ); + bool ConvertLWOToModelSurfaces( const struct st_lwObject *lwo ); + bool ConvertMAToModelSurfaces (const struct maModel_s *ma ); + + struct aseModel_s * ConvertLWOToASE( const struct st_lwObject *obj, const char *fileName ); + + bool DeleteSurfaceWithId( int id ); + void DeleteSurfacesWithNegativeId( void ); + bool FindSurfaceWithId( int id, int &surfaceNum ); + +public: + idList surfaces; + idBounds bounds; + int overlaysAdded; + +protected: + int lastModifiedFrame; + int lastArchivedFrame; + int levelOfDetail; + + idStr name; + srfTriangles_t * shadowHull; + bool isStaticWorldModel; + bool defaulted; + bool purged; // eventually we will have dynamic reloading + bool fastLoad; // don't generate tangents and shadow data + bool reloadable; // if not, reloadModels won't check timestamp + bool levelLoadReferenced; // for determining if it needs to be freed + bool hasSky; + ID_TIME_T timeStamp; + + static idCVar r_mergeModelSurfaces; // combine model surfaces with the same material + static idCVar r_slopVertex; // merge xyz coordinates this far apart + static idCVar r_slopTexCoord; // merge texture coordinates this far apart + static idCVar r_slopNormal; // merge normals that dot less than this +}; + +/* +=============================================================================== + + Transient BSE effect model + +=============================================================================== +*/ + +class rvRenderModelBSE : public idRenderModelStatic { +public: + rvRenderModelBSE(); + virtual ~rvRenderModelBSE(); + virtual void InitFromFile( const char *fileName ); + virtual dynamicModel_t IsDynamicModel() const; +}; + +/* +=============================================================================== + + MD5 animated model + +=============================================================================== +*/ + +class idMD5Mesh { + friend class idRenderModelMD5; + friend class rvRenderModelMD5R; + +public: + idMD5Mesh(); + ~idMD5Mesh(); + + void ParseMesh( idLexer &parser, int numJoints, const idJointMat *joints ); + void UpdateSurface( const struct renderEntity_s *ent, const idJointMat *joints, modelSurface_t *surf, bool calculateTangents ); + idBounds CalcBounds( const idJointMat *joints ); + int NearestJoint( int a, int b, int c ) const; + int NumVerts( void ) const; + int NumTris( void ) const; + int NumWeights( void ) const; + +private: + bool UpdateLod( const struct renderEntity_s *ent, const struct viewEntity_s *viewEnt, const modelSurface_t *surf ); + idList texCoords; // texture coordinates + int numWeights; // number of weights + jointWeight_t * weights; + idVec4 * baseVectors; + idVec4 * scaledBaseVectors; + const idMaterial * shader; // material applied to mesh + int numTris; // number of triangles + struct deformInfo_s * deformInfo; // used to create srfTriangles_t from base frames and new vertexes + int surfaceNum; // number of the static surface created for this mesh + int flags; + float currentTime; +}; + +class idRenderModelMD5 : public idRenderModelStatic { + friend class rvRenderModelMD5R; +public: + idRenderModelMD5(); + virtual ~idRenderModelMD5(); + virtual void InitFromFile( const char *fileName ); + virtual dynamicModel_t IsDynamicModel() const; + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + virtual void Print() const; + virtual void List() const; + virtual void TouchData(); + virtual void PurgeModel(); + virtual void LoadModel(); + virtual int Memory() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual int NumJoints( void ) const; + virtual const idMD5Joint * GetJoints( void ) const; + virtual jointHandle_t GetJointHandle( const char *name ) const; + virtual const char * GetJointName( jointHandle_t handle ) const; + virtual const idJointQuat * GetDefaultPose( void ) const; + virtual const idJointMat * GetSkinSpaceToLocalMats( void ) const; + virtual int NearestJoint( int surfaceNum, int a, int b, int c ) const; + virtual bool HasCollisionSurface( const struct renderEntity_s *ent ) const; + virtual int GetSurfaceMask( const char *surface ) const; + virtual void SetViewEntity( const struct viewEntity_s *ve ); + +private: + idList joints; + idList meshes; + idJointQuat * defaultPose; + idJointMat * skinSpaceToLocalMats; + const viewEntity_s * viewEnt; + + void CalculateBounds( const idJointMat *joints ); + void GetFrameBounds( const renderEntity_t *ent, idBounds &bounds ) const; + void DrawJoints( const renderEntity_t *ent, const struct viewDef_s *view, bool drawBones ) const; + void ParseJoint( idLexer &parser, idMD5Joint *joint, idJointQuat *defaultPose ); +}; + +/* +=============================================================================== + + MD3 animated model + +=============================================================================== +*/ + +struct md3Header_s; +struct md3Surface_s; + +class idRenderModelMD3 : public idRenderModelStatic { +public: + virtual void InitFromFile( const char *fileName ); + virtual dynamicModel_t IsDynamicModel() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + +private: + int index; // model = tr.models[model->index] + int dataSize; // just for listing purposes + struct md3Header_s * md3; // only if type == MOD_MESH + int numLods; + + void LerpMeshVertexes( srfTriangles_t *tri, const struct md3Surface_s *surf, const float backlerp, const int frame, const int oldframe ) const; +}; + +/* +=============================================================================== + + Liquid model + +=============================================================================== +*/ + +class idRenderModelLiquid : public idRenderModelStatic { +public: + idRenderModelLiquid(); + + virtual void InitFromFile( const char *fileName ); + virtual dynamicModel_t IsDynamicModel() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + + virtual void Reset(); + void IntersectBounds( const idBounds &bounds, float displacement ); + +private: + modelSurface_t GenerateSurface( float lerp ); + void WaterDrop( int x, int y, float *page ); + void Update( void ); + + int verts_x; + int verts_y; + float scale_x; + float scale_y; + int time; + int liquid_type; + int update_tics; + int seed; + + idRandom random; + + const idMaterial * shader; + struct deformInfo_s * deformInfo; // used to create srfTriangles_t from base frames + // and new vertexes + + float density; + float drop_height; + int drop_radius; + float drop_delay; + + idList pages; + float * page1; + float * page2; + + idList verts; + + int nextDropTime; + +}; + +/* +=============================================================================== + + Beam model + +=============================================================================== +*/ + +class idRenderModelBeam : public idRenderModelStatic { +public: + virtual dynamicModel_t IsDynamicModel() const; + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; +}; + +/* +=============================================================================== + + Beam model + +=============================================================================== +*/ +#define MAX_TRAIL_PTS 20 + +struct Trail_t { + int lastUpdateTime; + int duration; + + idVec3 pts[MAX_TRAIL_PTS]; + int numPoints; +}; + +class idRenderModelTrail : public idRenderModelStatic { + idList trails; + int numActive; + idBounds trailBounds; + +public: + idRenderModelTrail(); + + virtual dynamicModel_t IsDynamicModel() const; + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; + + int NewTrail( idVec3 pt, int duration ); + void UpdateTrail( int index, idVec3 pt ); + void DrawTrail( int index, const struct renderEntity_s *ent, srfTriangles_t *tri, float globalAlpha ); +}; + +/* +=============================================================================== + + Lightning model + +=============================================================================== +*/ + +class idRenderModelLightning : public idRenderModelStatic { +public: + virtual dynamicModel_t IsDynamicModel() const; + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; +}; + +/* +================================================================================ + + idRenderModelSprite + +================================================================================ +*/ +class idRenderModelSprite : public idRenderModelStatic { +public: + virtual dynamicModel_t IsDynamicModel() const; + virtual bool IsLoaded() const; + virtual idRenderModel * InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ); + virtual idBounds Bounds( const struct renderEntity_s *ent ) const; +}; + +#endif /* !__MODEL_LOCAL_H__ */ diff --git a/src/renderer/Model_lwo.h b/src/renderer/Model_lwo.h new file mode 100644 index 0000000..d1b8cbe --- /dev/null +++ b/src/renderer/Model_lwo.h @@ -0,0 +1,672 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LWO2_H__ +#define __LWO2_H__ + +/* +====================================================================== + + LWO2 loader. (LightWave Object) + + Ernie Wright 17 Sep 00 + +====================================================================== +*/ + +/* chunk and subchunk IDs */ + +#define LWID_(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) + +#define ID_FORM LWID_('F','O','R','M') +#define ID_LWO2 LWID_('L','W','O','2') +#define ID_LWOB LWID_('L','W','O','B') + +/* top-level chunks */ +#define ID_LAYR LWID_('L','A','Y','R') +#define ID_TAGS LWID_('T','A','G','S') +#define ID_PNTS LWID_('P','N','T','S') +#define ID_BBOX LWID_('B','B','O','X') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_VMAD LWID_('V','M','A','D') +#define ID_POLS LWID_('P','O','L','S') +#define ID_PTAG LWID_('P','T','A','G') +#define ID_ENVL LWID_('E','N','V','L') +#define ID_CLIP LWID_('C','L','I','P') +#define ID_SURF LWID_('S','U','R','F') +#define ID_DESC LWID_('D','E','S','C') +#define ID_TEXT LWID_('T','E','X','T') +#define ID_ICON LWID_('I','C','O','N') + +/* polygon types */ +#define ID_FACE LWID_('F','A','C','E') +#define ID_CURV LWID_('C','U','R','V') +#define ID_PTCH LWID_('P','T','C','H') +#define ID_MBAL LWID_('M','B','A','L') +#define ID_BONE LWID_('B','O','N','E') + +/* polygon tags */ +#define ID_SURF LWID_('S','U','R','F') +#define ID_PART LWID_('P','A','R','T') +#define ID_SMGP LWID_('S','M','G','P') + +/* envelopes */ +#define ID_PRE LWID_('P','R','E',' ') +#define ID_POST LWID_('P','O','S','T') +#define ID_KEY LWID_('K','E','Y',' ') +#define ID_SPAN LWID_('S','P','A','N') +#define ID_TCB LWID_('T','C','B',' ') +#define ID_HERM LWID_('H','E','R','M') +#define ID_BEZI LWID_('B','E','Z','I') +#define ID_BEZ2 LWID_('B','E','Z','2') +#define ID_LINE LWID_('L','I','N','E') +#define ID_STEP LWID_('S','T','E','P') + +/* clips */ +#define ID_STIL LWID_('S','T','I','L') +#define ID_ISEQ LWID_('I','S','E','Q') +#define ID_ANIM LWID_('A','N','I','M') +#define ID_XREF LWID_('X','R','E','F') +#define ID_STCC LWID_('S','T','C','C') +#define ID_TIME LWID_('T','I','M','E') +#define ID_CONT LWID_('C','O','N','T') +#define ID_BRIT LWID_('B','R','I','T') +#define ID_SATR LWID_('S','A','T','R') +#define ID_HUE LWID_('H','U','E',' ') +#define ID_GAMM LWID_('G','A','M','M') +#define ID_NEGA LWID_('N','E','G','A') +#define ID_IFLT LWID_('I','F','L','T') +#define ID_PFLT LWID_('P','F','L','T') + +/* surfaces */ +#define ID_COLR LWID_('C','O','L','R') +#define ID_LUMI LWID_('L','U','M','I') +#define ID_DIFF LWID_('D','I','F','F') +#define ID_SPEC LWID_('S','P','E','C') +#define ID_GLOS LWID_('G','L','O','S') +#define ID_REFL LWID_('R','E','F','L') +#define ID_RFOP LWID_('R','F','O','P') +#define ID_RIMG LWID_('R','I','M','G') +#define ID_RSAN LWID_('R','S','A','N') +#define ID_TRAN LWID_('T','R','A','N') +#define ID_TROP LWID_('T','R','O','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_RIND LWID_('R','I','N','D') +#define ID_TRNL LWID_('T','R','N','L') +#define ID_BUMP LWID_('B','U','M','P') +#define ID_SMAN LWID_('S','M','A','N') +#define ID_SIDE LWID_('S','I','D','E') +#define ID_CLRH LWID_('C','L','R','H') +#define ID_CLRF LWID_('C','L','R','F') +#define ID_ADTR LWID_('A','D','T','R') +#define ID_SHRP LWID_('S','H','R','P') +#define ID_LINE LWID_('L','I','N','E') +#define ID_LSIZ LWID_('L','S','I','Z') +#define ID_ALPH LWID_('A','L','P','H') +#define ID_AVAL LWID_('A','V','A','L') +#define ID_GVAL LWID_('G','V','A','L') +#define ID_BLOK LWID_('B','L','O','K') + +/* texture layer */ +#define ID_TYPE LWID_('T','Y','P','E') +#define ID_CHAN LWID_('C','H','A','N') +#define ID_NAME LWID_('N','A','M','E') +#define ID_ENAB LWID_('E','N','A','B') +#define ID_OPAC LWID_('O','P','A','C') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_PROJ LWID_('P','R','O','J') +#define ID_STCK LWID_('S','T','C','K') +#define ID_TAMP LWID_('T','A','M','P') + +/* texture coordinates */ +#define ID_TMAP LWID_('T','M','A','P') +#define ID_AXIS LWID_('A','X','I','S') +#define ID_CNTR LWID_('C','N','T','R') +#define ID_SIZE LWID_('S','I','Z','E') +#define ID_ROTA LWID_('R','O','T','A') +#define ID_OREF LWID_('O','R','E','F') +#define ID_FALL LWID_('F','A','L','L') +#define ID_CSYS LWID_('C','S','Y','S') + +/* image map */ +#define ID_IMAP LWID_('I','M','A','P') +#define ID_IMAG LWID_('I','M','A','G') +#define ID_WRAP LWID_('W','R','A','P') +#define ID_WRPW LWID_('W','R','P','W') +#define ID_WRPH LWID_('W','R','P','H') +#define ID_VMAP LWID_('V','M','A','P') +#define ID_AAST LWID_('A','A','S','T') +#define ID_PIXB LWID_('P','I','X','B') + +/* procedural */ +#define ID_PROC LWID_('P','R','O','C') +#define ID_COLR LWID_('C','O','L','R') +#define ID_VALU LWID_('V','A','L','U') +#define ID_FUNC LWID_('F','U','N','C') +#define ID_FTPS LWID_('F','T','P','S') +#define ID_ITPS LWID_('I','T','P','S') +#define ID_ETPS LWID_('E','T','P','S') + +/* gradient */ +#define ID_GRAD LWID_('G','R','A','D') +#define ID_GRST LWID_('G','R','S','T') +#define ID_GREN LWID_('G','R','E','N') +#define ID_PNAM LWID_('P','N','A','M') +#define ID_INAM LWID_('I','N','A','M') +#define ID_GRPT LWID_('G','R','P','T') +#define ID_FKEY LWID_('F','K','E','Y') +#define ID_IKEY LWID_('I','K','E','Y') + +/* shader */ +#define ID_SHDR LWID_('S','H','D','R') +#define ID_DATA LWID_('D','A','T','A') + + +/* generic linked list */ + +typedef struct st_lwNode { + struct st_lwNode *next, *prev; + void *data; +} lwNode; + + +/* plug-in reference */ + +typedef struct st_lwPlugin { + struct st_lwPlugin *next, *prev; + char *ord; + char *name; + int flags; + void *data; +} lwPlugin; + + +/* envelopes */ + +typedef struct st_lwKey { + struct st_lwKey *next, *prev; + float value; + float time; + unsigned int shape; /* ID_TCB, ID_BEZ2, etc. */ + float tension; + float continuity; + float bias; + float param[ 4 ]; +} lwKey; + +typedef struct st_lwEnvelope { + struct st_lwEnvelope *next, *prev; + int index; + int type; + char *name; + lwKey *key; /* linked list of keys */ + int nkeys; + int behavior[ 2 ]; /* pre and post (extrapolation) */ + lwPlugin *cfilter; /* linked list of channel filters */ + int ncfilters; +} lwEnvelope; + +#define BEH_RESET 0 +#define BEH_CONSTANT 1 +#define BEH_REPEAT 2 +#define BEH_OSCILLATE 3 +#define BEH_OFFSET 4 +#define BEH_LINEAR 5 + + +/* values that can be enveloped */ + +typedef struct st_lwEParam { + float val; + int eindex; +} lwEParam; + +typedef struct st_lwVParam { + float val[ 3 ]; + int eindex; +} lwVParam; + + +/* clips */ + +typedef struct st_lwClipStill { + char *name; +} lwClipStill; + +typedef struct st_lwClipSeq { + char *prefix; /* filename before sequence digits */ + char *suffix; /* after digits, e.g. extensions */ + int digits; + int flags; + int offset; + int start; + int end; +} lwClipSeq; + +typedef struct st_lwClipAnim { + char *name; + char *server; /* anim loader plug-in */ + void *data; +} lwClipAnim; + +typedef struct st_lwClipXRef { + char *string; + int index; + struct st_lwClip *clip; +} lwClipXRef; + +typedef struct st_lwClipCycle { + char *name; + int lo; + int hi; +} lwClipCycle; + +typedef struct st_lwClip { + struct st_lwClip *next, *prev; + int index; + unsigned int type; /* ID_STIL, ID_ISEQ, etc. */ + union { + lwClipStill still; + lwClipSeq seq; + lwClipAnim anim; + lwClipXRef xref; + lwClipCycle cycle; + } source; + float start_time; + float duration; + float frame_rate; + lwEParam contrast; + lwEParam brightness; + lwEParam saturation; + lwEParam hue; + lwEParam gamma; + int negative; + lwPlugin *ifilter; /* linked list of image filters */ + int nifilters; + lwPlugin *pfilter; /* linked list of pixel filters */ + int npfilters; +} lwClip; + + +/* textures */ + +typedef struct st_lwTMap { + lwVParam size; + lwVParam center; + lwVParam rotate; + lwVParam falloff; + int fall_type; + char *ref_object; + int coord_sys; +} lwTMap; + +typedef struct st_lwImageMap { + int cindex; + int projection; + char *vmap_name; + int axis; + int wrapw_type; + int wraph_type; + lwEParam wrapw; + lwEParam wraph; + float aa_strength; + int aas_flags; + int pblend; + lwEParam stck; + lwEParam amplitude; +} lwImageMap; + +#define PROJ_PLANAR 0 +#define PROJ_CYLINDRICAL 1 +#define PROJ_SPHERICAL 2 +#define PROJ_CUBIC 3 +#define PROJ_FRONT 4 + +#define WRAP_NONE 0 +#define WRAP_EDGE 1 +#define WRAP_REPEAT 2 +#define WRAP_MIRROR 3 + +typedef struct st_lwProcedural { + int axis; + float value[ 3 ]; + char *name; + void *data; +} lwProcedural; + +typedef struct st_lwGradKey { + struct st_lwGradKey *next, *prev; + float value; + float rgba[ 4 ]; +} lwGradKey; + +typedef struct st_lwGradient { + char *paramname; + char *itemname; + float start; + float end; + int repeat; + lwGradKey *key; /* array of gradient keys */ + short *ikey; /* array of interpolation codes */ +} lwGradient; + +typedef struct st_lwTexture { + struct st_lwTexture *next, *prev; + char *ord; + unsigned int type; + unsigned int chan; + lwEParam opacity; + short opac_type; + short enabled; + short negative; + short axis; + union { + lwImageMap imap; + lwProcedural proc; + lwGradient grad; + } param; + lwTMap tmap; +} lwTexture; + + +/* values that can be textured */ + +typedef struct st_lwTParam { + float val; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwTParam; + +typedef struct st_lwCParam { + float rgb[ 3 ]; + int eindex; + lwTexture *tex; /* linked list of texture layers */ +} lwCParam; + + +/* surfaces */ + +typedef struct st_lwGlow { + short enabled; + short type; + lwEParam intensity; + lwEParam size; +} Glow; + +typedef struct st_lwRMap { + lwTParam val; + int options; + int cindex; + float seam_angle; +} lwRMap; + +typedef struct st_lwLine { + short enabled; + unsigned short flags; + lwEParam size; +} lwLine; + +typedef struct st_lwSurface { + struct st_lwSurface *next, *prev; + char *name; + char *srcname; + lwCParam color; + lwTParam luminosity; + lwTParam diffuse; + lwTParam specularity; + lwTParam glossiness; + lwRMap reflection; + lwRMap transparency; + lwTParam eta; + lwTParam translucency; + lwTParam bump; + float smooth; + int sideflags; + float alpha; + int alpha_mode; + lwEParam color_hilite; + lwEParam color_filter; + lwEParam add_trans; + lwEParam dif_sharp; + lwEParam glow; + lwLine line; + lwPlugin *shader; /* linked list of shaders */ + int nshaders; +} lwSurface; + + +/* vertex maps */ + +typedef struct st_lwVMap { + struct st_lwVMap *next, *prev; + char *name; + unsigned int type; + int dim; + int nverts; + int perpoly; + int *vindex; /* array of point indexes */ + int *pindex; /* array of polygon indexes */ + float **val; + + // added by duffy + int offset; +} lwVMap; + +typedef struct st_lwVMapPt { + lwVMap *vmap; + int index; /* vindex or pindex element */ +} lwVMapPt; + + +/* points and polygons */ + +typedef struct st_lwPoint { + float pos[ 3 ]; + int npols; /* number of polygons sharing the point */ + int *pol; /* array of polygon indexes */ + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPoint; + +typedef struct st_lwPolVert { + int index; /* index into the point array */ + float norm[ 3 ]; + int nvmaps; + lwVMapPt *vm; /* array of vmap references */ +} lwPolVert; + +typedef struct st_lwPolygon { + lwSurface *surf; + int part; /* part index */ + int smoothgrp; /* smoothing group */ + int flags; + unsigned int type; + float norm[ 3 ]; + int nverts; + lwPolVert *v; /* array of vertex records */ +} lwPolygon; + +typedef struct st_lwPointList { + int count; + int offset; /* only used during reading */ + lwPoint *pt; /* array of points */ +} lwPointList; + +typedef struct st_lwPolygonList { + int count; + int offset; /* only used during reading */ + int vcount; /* total number of vertices */ + int voffset; /* only used during reading */ + lwPolygon *pol; /* array of polygons */ +} lwPolygonList; + + +/* geometry layers */ + +typedef struct st_lwLayer { + struct st_lwLayer *next, *prev; + char *name; + int index; + int parent; + int flags; + float pivot[ 3 ]; + float bbox[ 6 ]; + lwPointList point; + lwPolygonList polygon; + int nvmaps; + lwVMap *vmap; /* linked list of vmaps */ +} lwLayer; + + +/* tag strings */ + +typedef struct st_lwTagList { + int count; + int offset; /* only used during reading */ + char **tag; /* array of strings */ +} lwTagList; + + +/* an object */ + +typedef struct st_lwObject { + ID_TIME_T timeStamp; + lwLayer * layer; /* linked list of layers */ + lwEnvelope * env; /* linked list of envelopes */ + lwClip * clip; /* linked list of clips */ + lwSurface * surf; /* linked list of surfaces */ + lwTagList taglist; + int nlayers; + int nenvs; + int nclips; + int nsurfs; +} lwObject; + + +/* lwo2.c */ + +lwObject *lwGetObject( const char *filename, unsigned int *failID, int *failpos ); +void lwFreeObject( lwObject *object ); +void lwFreeLayer( lwLayer *layer ); + +/* pntspols.c */ + +void lwFreePoints( lwPointList *point ); +void lwFreePolygons( lwPolygonList *plist ); +int lwGetPoints( idFile *fp, int cksize, lwPointList *point ); +void lwGetBoundingBox( lwPointList *point, float bbox[] ); +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ); +int lwGetPolygons( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ); +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ); +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ); +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ); +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ); +void lwFreeTags( lwTagList *tlist ); +int lwGetTags( idFile *fp, int cksize, lwTagList *tlist ); +int lwGetPolygonTags( idFile *fp, int cksize, lwTagList *tlist, + lwPolygonList *plist ); + +/* vmap.c */ + +void lwFreeVMap( lwVMap *vmap ); +lwVMap *lwGetVMap( idFile *fp, int cksize, int ptoffset, int poloffset, + int perpoly ); +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ); +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ); + +/* clip.c */ + +void lwFreeClip( lwClip *clip ); +lwClip *lwGetClip( idFile *fp, int cksize ); +lwClip *lwFindClip( lwClip *list, int index ); + +/* envelope.c */ + +void lwFreeEnvelope( lwEnvelope *env ); +lwEnvelope *lwGetEnvelope( idFile *fp, int cksize ); +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ); +float lwEvalEnvelope( lwEnvelope *env, float time ); + +/* surface.c */ + +void lwFreePlugin( lwPlugin *p ); +void lwFreeTexture( lwTexture *t ); +void lwFreeSurface( lwSurface *surf ); +int lwGetTHeader( idFile *fp, int hsz, lwTexture *tex ); +int lwGetTMap( idFile *fp, int tmapsz, lwTMap *tmap ); +int lwGetImageMap( idFile *fp, int rsz, lwTexture *tex ); +int lwGetProcedural( idFile *fp, int rsz, lwTexture *tex ); +int lwGetGradient( idFile *fp, int rsz, lwTexture *tex ); +lwTexture *lwGetTexture( idFile *fp, int bloksz, unsigned int type ); +lwPlugin *lwGetShader( idFile *fp, int bloksz ); +lwSurface *lwGetSurface( idFile *fp, int cksize ); +lwSurface *lwDefaultSurface( void ); + +/* lwob.c */ + +lwSurface *lwGetSurface5( idFile *fp, int cksize, lwObject *obj ); +int lwGetPolygons5( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ); +lwObject *lwGetObject5( const char *filename, unsigned int *failID, int *failpos ); + +/* list.c */ + +void lwListFree( void *list, void ( *freeNode )( void * )); +void lwListAdd( void **list, void *node ); +void lwListInsert( void **vlist, void *vitem, + int ( *compare )( void *, void * )); + +/* vecmath.c */ + +float dot( float a[], float b[] ); +void cross( float a[], float b[], float c[] ); +void normalize( float v[] ); +#define vecangle( a, b ) ( float ) idMath::ACos( dot( a, b ) ) + +/* lwio.c */ + +void set_flen( int i ); +int get_flen( void ); +void *getbytes( idFile *fp, int size ); +void skipbytes( idFile *fp, int n ); +int getI1( idFile *fp ); +short getI2( idFile *fp ); +int getI4( idFile *fp ); +unsigned char getU1( idFile *fp ); +unsigned short getU2( idFile *fp ); +unsigned int getU4( idFile *fp ); +int getVX( idFile *fp ); +float getF4( idFile *fp ); +char *getS0( idFile *fp ); +int sgetI1( unsigned char **bp ); +short sgetI2( unsigned char **bp ); +int sgetI4( unsigned char **bp ); +unsigned char sgetU1( unsigned char **bp ); +unsigned short sgetU2( unsigned char **bp ); +unsigned int sgetU4( unsigned char **bp ); +int sgetVX( unsigned char **bp ); +float sgetF4( unsigned char **bp ); +char *sgetS0( unsigned char **bp ); + +#endif /* !__LWO2_H__ */ diff --git a/src/renderer/Model_ma.h b/src/renderer/Model_ma.h new file mode 100644 index 0000000..eb75cf2 --- /dev/null +++ b/src/renderer/Model_ma.h @@ -0,0 +1,141 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MODEL_MA_H__ +#define __MODEL_MA_H__ + +/* +=============================================================================== + + MA loader. (Maya Ascii Format) + +=============================================================================== +*/ + +typedef struct { + char name[128]; + char parent[128]; +} maNodeHeader_t; + +typedef struct { + char name[128]; + int size; +} maAttribHeader_t; + +typedef struct maTransform_s { + idVec3 translate; + idVec3 rotate; + idVec3 scale; + maTransform_s* parent; +} maTransform_t; + +typedef struct { + int edge[3]; + int vertexNum[3]; + int tVertexNum[3]; + int vertexColors[3]; + idVec3 vertexNormals[3]; +} maFace_t; + +typedef struct { + + //Transform to be applied + maTransform_t* transform; + + //Verts + int numVertexes; + idVec3 * vertexes; + int numVertTransforms; + idVec4 * vertTransforms; + int nextVertTransformIndex; + + //Texture Coordinates + int numTVertexes; + idVec2 * tvertexes; + + //Edges + int numEdges; + idVec3 * edges; + + //Colors + int numColors; + byte* colors; + + //Faces + int numFaces; + maFace_t * faces; + + //Normals + int numNormals; + idVec3 * normals; + bool normalsParsed; + int nextNormal; + +} maMesh_t; + +typedef struct { + char name[128]; + float uOffset, vOffset; // max lets you offset by material without changing texCoords + float uTiling, vTiling; // multiply tex coords by this + float angle; // in clockwise radians +} maMaterial_t; + +typedef struct { + char name[128]; + int materialRef; + char materialName[128]; + + maMesh_t mesh; +} maObject_t; + + +typedef struct { + char name[128]; + char path[1024]; +} maFileNode_t; + +typedef struct maMaterialNode_s { + char name[128]; + + maMaterialNode_s* child; + maFileNode_t* file; + +} maMaterialNode_t; + +typedef struct maModel_s { + ID_TIME_T timeStamp; + idList materials; + idList objects; + idHashTable transforms; + + //Material Resolution + idHashTable fileNodes; + idHashTable materialNodes; + +} maModel_t; + +maModel_t *MA_Load( const char *fileName ); +void MA_Free( maModel_t *ma ); + +#endif /* !__MODEL_MA_H__ */ diff --git a/src/renderer/Model_md3.h b/src/renderer/Model_md3.h new file mode 100644 index 0000000..f8c19be --- /dev/null +++ b/src/renderer/Model_md3.h @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __MODEL_MD3_H__ +#define __MODEL_MD3_H__ + +/* +======================================================================== + +.MD3 triangle model file format + +Private structures used by the MD3 loader. + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + +// limits +#define MD3_MAX_LODS 4 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 // per model +#define MD3_MAX_TAGS 16 // per frame +#define MAX_MD3PATH 64 // from quake3 + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +typedef struct md3Frame_s { + idVec3 bounds[2]; + idVec3 localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_MD3PATH]; // tag name + idVec3 origin; + idVec3 axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ + +typedef struct md3Surface_s { + int ident; // + + char name[MAX_MD3PATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_MD3PATH]; + const idMaterial * shader; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct md3Header_s { + int ident; + int version; + + char name[MAX_MD3PATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + +#endif /* !__MODEL_MD3_H__ */ diff --git a/src/renderer/RenderSystem.h b/src/renderer/RenderSystem.h new file mode 100644 index 0000000..8349482 --- /dev/null +++ b/src/renderer/RenderSystem.h @@ -0,0 +1,399 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __RENDERER_H__ +#define __RENDERER_H__ + +/* +=============================================================================== + + idRenderSystem is responsible for managing the screen, which can have + multiple idRenderWorld and 2D drawing done on it. + +=============================================================================== +*/ + + +// Contains variables specific to the OpenGL configuration being run right now. +// These are constant once the OpenGL subsystem is initialized. +typedef struct glconfig_s { + const char *renderer_string; + const char *vendor_string; + const char *version_string; + const char *extensions_string; + const char *wgl_extensions_string; + + float glVersion; // atof( version_string ) + + + int maxTextureSize; // queried from GL + int maxTextureUnits; + int maxTextureCoords; + int maxTextureImageUnits; + float maxTextureAnisotropy; + + int colorBits, depthBits, stencilBits; +// RAVEN BEGIN + int alphaBits; +// RAVEN END + + bool multitextureAvailable; + bool anisotropicAvailable; + bool textureLODBiasAvailable; + bool textureEnvAddAvailable; + bool textureEnvCombineAvailable; +// RAVEN BEGIN +// jscott: added + bool blendSquareAvailable; +// RAVEN END + bool registerCombinersAvailable; + bool cubeMapAvailable; + bool envDot3Available; + bool texture3DAvailable; + bool sharedTexturePaletteAvailable; +// RAVEN BEGIN +// dluetscher: added + bool drawRangeElementsAvailable; + bool blendMinMaxAvailable; + bool floatBufferAvailable; +// RAVEN END + bool ARBVertexBufferObjectAvailable; + bool ARBVertexProgramAvailable; + bool ARBFragmentProgramAvailable; + bool twoSidedStencilAvailable; + bool textureNonPowerOfTwoAvailable; + bool depthBoundsTestAvailable; + +// RAVEN BEGIN +// rjohnson: new shader stage system + bool GLSLProgramAvailable; +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added check for NV_vertex_program and NV_fragment_program support + bool nvProgramsAvailable; +// RAVEN END + + // ati r200 extensions + bool atiFragmentShaderAvailable; + + // ati r300 + bool atiTwoSidedStencilAvailable; + + int vidWidth, vidHeight; // passed to R_BeginFrame + + int displayFrequency; + + bool isFullscreen; + +#ifndef _CONSOLE + bool preferNV20Path; // for FX5200 cards +// RAVEN BEGIN +// dluetscher: added preferSimpleLighting flag + bool preferSimpleLighting; // for the ATI 9700 cards +// RAVEN END +#endif + + bool allowNV20Path; + bool allowNV10Path; + bool allowR200Path; + bool allowARB2Path; + + bool isInitialized; +// INTEL BEGIN +// Anu adding support to toggle SMP +#ifdef ENABLE_INTEL_SMP + bool isSmpAvailable; + int isSmpActive; + int rearmSmp; +#endif +// INTEL END + +} glconfig_t; + + +// font support +#define GLYPH_COUNT 256 + +typedef struct glyphInfo_s +{ + float width; // number of pixels wide + float height; // number of scan lines + float horiAdvance; // number of pixels to advance to the next char + float horiBearingX; // x offset into space to render glyph + float horiBearingY; // y offset + float s1; // x start tex coord + float t1; // y start tex coord + float s2; // x end tex coord + float t2; // y end tex coord +} glyphInfo_t; + +typedef struct fontInfo_s +{ + glyphInfo_t glyphs[GLYPH_COUNT]; + + float pointSize; + float fontHeight; // max height of font + float ascender; + float descender; + + idMaterial *material; +} fontInfo_t; + +typedef struct { + fontInfo_t fontInfoSmall; + fontInfo_t fontInfoMedium; + fontInfo_t fontInfoLarge; + float maxHeight; + float maxWidth; + float maxHeightSmall; + float maxWidthSmall; + float maxHeightMedium; + float maxWidthMedium; + float maxHeightLarge; + float maxWidthLarge; + char name[64]; +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + idList allMaterials; +#endif +// RAVEN END +} fontInfoEx_t; + +const int TINYCHAR_WIDTH = 4; +const int TINYCHAR_HEIGHT = 8; +const int SMALLCHAR_WIDTH = 8; +const int SMALLCHAR_HEIGHT = 16; +const int BIGCHAR_WIDTH = 16; +const int BIGCHAR_HEIGHT = 16; + +// all drawing is done to a 640 x 480 virtual screen size +// and will be automatically scaled to the real resolution +const int SCREEN_WIDTH = 640; +const int SCREEN_HEIGHT = 480; + +// RAVEN BEGIN +// rjohnson: new blur special effect +typedef enum +{ + SPECIAL_EFFECT_NONE = 0, + SPECIAL_EFFECT_BLUR = 0x00000001, + SPECIAL_EFFECT_AL = 0x00000002, + SPECIAL_EFFECT_MAX, +} ESpecialEffectType; +// RAVEN END + +class idRenderWorld; + + +class idRenderSystem { +public: + + virtual ~idRenderSystem() {} + + // set up cvars and basic data structures, but don't + // init OpenGL, so it can also be used for dedicated servers +// RAVEN BEGIN + // nrausch: Init things that touch the render state seperately + virtual void DeferredInit( void ) { } +// RAVEN END + virtual void Init( void ) = 0; + + // only called before quitting + virtual void Shutdown( void ) = 0; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushLevelImages( void ) = 0; +#endif +// RAVEN END + + virtual void InitOpenGL( void ) = 0; + + virtual void ShutdownOpenGL( void ) = 0; + + virtual bool IsOpenGLRunning( void ) const = 0; + virtual void GetValidModes( idStr &Mode4x3Text, idStr &Mode4x3Values, idStr &Mode16x9Text, idStr &Mode16x9Values, + idStr &Mode16x10Text, idStr &Mode16x10Values ) = 0; + + virtual bool IsFullScreen( void ) const = 0; + virtual int GetScreenWidth( void ) const = 0; + virtual int GetScreenHeight( void ) const = 0; + + // allocate a renderWorld to be used for drawing + virtual idRenderWorld * AllocRenderWorld( void ) = 0; + virtual void FreeRenderWorld( idRenderWorld * rw ) = 0; + +// RAVEN BEGIN + virtual void RemoveAllModelReferences( idRenderModel *model ) = 0; +// RAVEN BEGIN + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + virtual void BeginLevelLoad( void ) = 0; + virtual void EndLevelLoad( void ) = 0; + +// RAVEN BEGIN +// mwhitlock: changes for Xenon to enable us to use texture resources from .xpr +// bundles. +#if defined(_XENON) + // Like BeginLevelLoad and EndLevelLoad, but only deals with textures. + virtual void XenonBeginLevelLoadTextures( void ) = 0; + virtual void XenonEndLevelLoadTextures( void ) = 0; +#endif +// RAVEN END + +#ifdef Q4SDK_MD5R + + virtual void ExportMD5R( bool compressed ) = 0; + virtual void CopyPrimBatchTriangles( idDrawVert *destDrawVerts, glIndex_t *destIndices, void *primBatchMesh, void *silTraceVerts ) = 0; + +#else // Q4SDK_MD5R + +// RAVEN BEGIN +// dluetscher: added call to write out the MD5R models that have been converted at load, +// also added call to retrieve idDrawVert geometry from a MD5R primitive batch +// from the game DLL +#if defined( _MD5R_WRITE_SUPPORT ) && defined( _MD5R_SUPPORT ) + virtual void ExportMD5R( bool compressed ) = 0; +#endif +#if defined( _MD5R_SUPPORT ) + virtual void CopyPrimBatchTriangles( idDrawVert *destDrawVerts, glIndex_t *destIndices, rvMesh *primBatchMesh, const rvSilTraceVertT *silTraceVerts ) = 0; +#endif + +#endif // !Q4SDK_MD5R + +// jnewquist: Track texture usage during cinematics for streaming purposes +#ifndef _CONSOLE + enum TextureTrackCommand { + TEXTURE_TRACK_BEGIN, + TEXTURE_TRACK_UPDATE, + TEXTURE_TRACK_END + }; + virtual void TrackTextureUsage( TextureTrackCommand command, int frametime = 0, const char *name=NULL ) = 0; +#endif +// RAVEN END + + // font support + virtual bool RegisterFont( const char *fontName, fontInfoEx_t &font ) = 0; + // GUI drawing just involves shader parameter setting and axial image subsections + virtual void SetColor( const idVec4 &rgba ) = 0; + virtual void SetColor4( float r, float g, float b, float a ) = 0; + + virtual void DrawStretchPic( const idDrawVert *verts, const glIndex_t *indexes, int vertCount, int indexCount, const idMaterial *material, bool clip = true ) = 0; + virtual void DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) = 0; +// RAVEN BEGIN +// jnewquist: Deal with flipped back-buffer copies on Xenon + virtual void DrawStretchCopy( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) = 0; +// RAVEN END + + virtual void DrawStretchTri ( idVec2 p1, idVec2 p2, idVec2 p3, idVec2 t1, idVec2 t2, idVec2 t3, const idMaterial *material ) = 0; + virtual void GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ) = 0; + virtual void GetGLSettings( int& width, int& height ) = 0; + virtual void PrintMemInfo( MemInfo *mi ) = 0; + + virtual void DrawTinyChar( int x, int y, int ch, const idMaterial *material ) = 0; + virtual void DrawTinyStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) = 0; + virtual void DrawSmallChar( int x, int y, int ch, const idMaterial *material ) = 0; + virtual void DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) = 0; + virtual void DrawBigChar( int x, int y, int ch, const idMaterial *material ) = 0; + virtual void DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) = 0; + + // dump all 2D drawing so far this frame to the demo file + virtual void WriteDemoPics() = 0; + + // draw the 2D pics that were saved out with the current demo frame + virtual void DrawDemoPics() = 0; + + // FIXME: add an interface for arbitrary point/texcoord drawing + + + // a frame cam consist of 2D drawing and potentially multiple 3D scenes + // window sizes are needed to convert SCREEN_WIDTH / SCREEN_HEIGHT values + virtual void BeginFrame( int windowWidth, int windowHeight ) = 0; +// RAVEN BEGIN + virtual void BeginFrame( struct viewDef_s *viewDef, int windowWidth, int windowHeight ) = 0; + virtual void RenderLightFrustum( const struct renderLight_s &renderLight, idPlane lightFrustum[6] ) = 0; + virtual void LightProjectionMatrix( const idVec3 &origin, const idPlane &rearPlane, idVec4 mat[4] ) = 0; + virtual void ToggleSmpFrame( void ) = 0; +// RAVEN END + +// RAVEN BEGIN +// rjohnson: new blur special effect + virtual void SetSpecialEffect( ESpecialEffectType Which, bool Enabled ) = 0; + virtual void SetSpecialEffectParm( ESpecialEffectType Which, int Parm, float Value ) = 0; + virtual void ShutdownSpecialEffects( void ) = 0; +// RAVEN END + + // if the pointers are not NULL, timing info will be returned + virtual void EndFrame( int *frontEndMsec = NULL, int *backEndMsec = NULL, int *numVerts = NULL, int *numIndexes = NULL ) = 0; + + // aviDemo uses this. + // Will automatically tile render large screen shots if necessary + // Samples is the number of jittered frames for anti-aliasing + // If ref == NULL, session->updateScreen will be used + // This will perform swapbuffers, so it is NOT an approppriate way to + // generate image files that happen during gameplay, as for savegame + // markers. Use WriteRender() instead. +// RAVEN BEGIN +// rjohnson: added basePath + virtual void TakeJPGScreenshot( int width, int height, const char *fileName, int blends, struct renderView_s *ref, const char *basePath = "fs_savepath" ) = 0; + virtual void TakeScreenshot( int width, int height, const char *fileName, int blends, struct renderView_s *ref, const char *basePath = "fs_savepath" ) = 0; +// RAVEN END + + // the render output can be cropped down to a subset of the real screen, as + // for save-game reviews and split-screen multiplayer. Users of the renderer + // will not know the actual pixel size of the area they are rendering to + + // the x,y,width,height values are in virtual SCREEN_WIDTH / SCREEN_HEIGHT coordinates + + // to render to a texture, first set the crop size with makePowerOfTwo = true, + // then perform all desired rendering, then capture to an image + // if the specified physical dimensions are larger than the current cropped region, they will be cut down to fit + virtual void CropRenderSize( int width, int height, bool makePowerOfTwo = false, bool forceDimensions = false ) = 0; + virtual void CaptureRenderToImage( const char *imageName ) = 0; + virtual void CaptureRenderToMemory( void *buffer ) = 0; + // fixAlpha will set all the alpha channel values to 0xff, which allows screen captures + // to use the default tga loading code without having dimmed down areas in many places + virtual void CaptureRenderToFile( const char *fileName, bool fixAlpha = false ) = 0; + virtual void UnCrop() = 0; + virtual void GetCardCaps( bool &oldCard, bool &nv10or20 ) = 0; + virtual bool UploadImage( const char *imageName, const byte *data, int width, int height ) = 0; + + virtual void DebugGraph( float cur, float min, float max, const idVec4 &color ) = 0; + virtual void ShowDebugGraph( void ) = 0; +}; + +extern idRenderSystem * renderSystem; + +void R_ScreenshotFilename( int &lastNumber, const char *base, const char *extension, idStr &fileName ); + +#endif /* !__RENDERER_H__ */ diff --git a/src/renderer/RenderWorld.h b/src/renderer/RenderWorld.h new file mode 100644 index 0000000..d2e493f --- /dev/null +++ b/src/renderer/RenderWorld.h @@ -0,0 +1,652 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __RENDERWORLD_H__ +#define __RENDERWORLD_H__ + +/* +=============================================================================== + + Render World + +=============================================================================== +*/ + +// RAVEN BEGIN +// jscott: new proc format +//#define PROC_FILE_ID "mapProcFile003" +#define PROC_FILE_ID "PROC" +#define PROC_FILE_EXT "proc" +#define PROC_FILEVERSION 4 +// RAVEN END + +// RAVEN BEGIN +// dluetscher: new MD5RProc file format +#define MD5R_PROC_FILE_ID "MD5RProcVersion" +#define MD5R_PROC_FILE_EXT "MD5RProc" +#define MD5R_PROC_FILEVERSION 4 +// RAVEN END + +// shader parms +const int MAX_GLOBAL_SHADER_PARMS = 12; + +const int SHADERPARM_RED = 0; +const int SHADERPARM_GREEN = 1; +const int SHADERPARM_BLUE = 2; +const int SHADERPARM_ALPHA = 3; +const int SHADERPARM_TIMESCALE = 3; +const int SHADERPARM_TIMEOFFSET = 4; +const int SHADERPARM_DIVERSITY = 5; // random between 0.0 and 1.0 for some effects (muzzle flashes, etc) +const int SHADERPARM_MODE = 7; // for selecting which shader passes to enable +const int SHADERPARM_TIME_OF_DEATH = 7; // for the monster skin-burn-away effect enable and time offset + +// model parms +const int SHADERPARM_MD5_SKINSCALE = 8; // for scaling vertex offsets on md5 models (jack skellington effect) + +const int SHADERPARM_MD3_FRAME = 8; +const int SHADERPARM_MD3_LASTFRAME = 9; +const int SHADERPARM_MD3_BACKLERP = 10; + +const int SHADERPARM_BEAM_END_X = 8; // for _beam models +const int SHADERPARM_BEAM_END_Y = 9; +const int SHADERPARM_BEAM_END_Z = 10; +const int SHADERPARM_BEAM_WIDTH = 11; + +const int SHADERPARM_SPRITE_WIDTH = 8; +const int SHADERPARM_SPRITE_HEIGHT = 9; + +const int SHADERPARM_PARTICLE_STOPTIME = 8; // don't spawn any more particles after this time + +// guis +const int MAX_RENDERENTITY_GUI = 3; + +// RAVEN BEGIN +// jscott: for effect brightness +const int SHADERPARM_BRIGHTNESS = 6; // for the overall brightness of effects +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added a default value for light detail levels +#define DEFAULT_LIGHT_DETAIL_LEVEL 10.f +// RAVEN END + +enum { + RD_MISC = 0, + RD_RENDERVIEW, + RD_RENDER_STATE, + + RD_RENDERENT_UPDATE, + RD_RENDERENT_FREE, + RD_RENDERENT_ADD, + RD_RENDERENT, + + RD_RENDERLIGHT_UPDATE, + RD_RENDERLIGHT_FREE, + RD_RENDERLIGHT_ADD, + RD_RENDERLIGHT, + + RD_RENDEREFFECT_UPDATE, + RD_RENDEREFFECT_FREE, + RD_RENDEREFFECT_STOP, + RD_RENDEREFFECT_ADD, + RD_RENDEREFFECT, + + RD_SOUND_STATE, + RD_SOUND, + + RD_HUD, + + RD_MAX_STATS +}; + +typedef bool(*deferredEntityCallback_t)( renderEntity_s *, const renderView_s * ); + + +// RAVEN BEGIN +// bdube: attachments +typedef struct attachedModel_s { + idRenderModel* model; + jointHandle_t joint; +} attachedModel_t; +// RAVEN END + +typedef struct renderEntity_s { + idRenderModel * hModel; // this can only be null if callback is set + + int entityNum; + int bodyId; + + // Entities that are expensive to generate, like skeletal models, can be + // deferred until their bounds are found to be in view, in the frustum + // of a shadowing light that is in view, or contacted by a trace / overlay test. + // This is also used to do visual cueing on items in the view + // The renderView may be NULL if the callback is being issued for a non-view related + // source. + // The callback function should clear renderEntity->callback if it doesn't + // want to be called again next time the entity is referenced (ie, if the + // callback has now made the entity valid until the next updateEntity) + idBounds bounds; // only needs to be set for deferred models and md5s + deferredEntityCallback_t callback; + + void * callbackData; // used for whatever the callback wants + + // player bodies and possibly player shadows should be suppressed in views from + // that player's eyes, but will show up in mirrors and other subviews + // security cameras could suppress their model in their subviews if we add a way + // of specifying a view number for a remoteRenderMap view + int suppressSurfaceInViewID; + int suppressShadowInViewID; + + // world models for the player and weapons will not cast shadows from view weapon + // muzzle flashes + int suppressShadowInLightID; + + // if non-zero, the surface and shadow (if it casts one) + // will only show up in the specific view, ie: player weapons + int allowSurfaceInViewID; + +// RAVEN BEGIN +// bdube: hiding surfaces + // Mask of surfaces which should be suppresesed when rendering the entity + int suppressSurfaceMask; +// RAVEN END + + // positioning + // axis rotation vectors must be unit length for many + // R_LocalToGlobal functions to work, so don't scale models! + // axis vectors are [0] = forward, [1] = left, [2] = up + idVec3 origin; + idMat3 axis; + + // texturing + const idMaterial * customShader; // if non-0, all surfaces will use this + const idMaterial * referenceShader; // used so flares can reference the proper light shader +// RAVEN BEGIN +// bdube: overlay shaders + const idMaterial * overlayShader; // overlays the model on top of itself with this shader (originally for powerups) +// RAVEN END + const idDeclSkin * customSkin; // 0 for no remappings +// RAVEN BEGIN + int referenceSoundHandle; // for shader sound tables, allowing effects to vary with sounds +// RAVEN END + float shaderParms[ MAX_ENTITY_SHADER_PARMS ]; // can be used in any way by shader or model generation + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + idList allMaterials; +#endif +// RAVEN END + + // networking: see WriteGUIToSnapshot / ReadGUIFromSnapshot + class idUserInterface * gui[ MAX_RENDERENTITY_GUI ]; + + struct renderView_s * remoteRenderView; // any remote camera surfaces will use this + + int numJoints; + idJointMat * joints; // array of joints that will modify vertices. + // NULL if non-deformable model. NOT freed by renderer + + float modelDepthHack; // squash depth range so particle effects don't clip into walls + + // options to override surface shader flags (replace with material parameters?) + bool noSelfShadow; // cast shadows onto other objects,but not self + bool noShadow; // no shadow at all + + bool noDynamicInteractions; // don't create any light / shadow interactions after + // the level load is completed. This is a performance hack + // for the gigantic outdoor meshes in the monorail map, so + // all the lights in the moving monorail don't touch the meshes + +// RAVEN BEGIN + bool forceUpdate; // force an update + +// bdube: weapon depth hack only in a given view id + int weaponDepthHackInViewID;// squash depth range so view weapons don't poke into walls +// RAVEN END + // this automatically implies noShadow + +// RAVEN BEGIN +// ddynerman: Wolf LOD code + float shadowLODDistance; + int suppressLOD; +// RAVEN END +} renderEntity_t; + +typedef struct renderLight_s { + idMat3 axis; // rotation vectors, must be unit length + idVec3 origin; + + // if non-zero, the light will not show up in the specific view, + // which may be used if we want to have slightly different muzzle + // flash lights for the player and other views + int suppressLightInViewID; + + // if non-zero, the light will only show up in the specific view + // which can allow player gun gui lights and such to not effect everyone + int allowLightInViewID; + +// RAVEN BEGIN +// dluetscher: added a min light detail level setting that describes when this light is visible + float detailLevel; +// RAVEN END + + // I am sticking the four bools together so there are no unused gaps in + // the padded structure, which could confuse the memcmp that checks for redundant + // updates + bool noShadows; // (should we replace this with material parameters on the shader?) + bool noSpecular; // (should we replace this with material parameters on the shader?) +// RAVEN BEGIN +// ddynerman: no dynamic shadows - allows dmap to create optimized static shadows, but prevents dynamic shadows + bool noDynamicShadows; +// RAVEN END + + bool pointLight; // otherwise a projection light (should probably invert the sense of this, because points are way more common) + bool parallel; // lightCenter gives the direction to the light at infinity + bool globalLight; // Whether this is a global light or not. + idVec3 lightRadius; // xyz radius for point lights + idVec3 lightCenter; // offset the lighting direction for shading and + // shadows, relative to origin + + // frustum definition for projected lights, all reletive to origin + // FIXME: we should probably have real plane equations here, and offer + // a helper function for conversion from this format + idVec3 target; + idVec3 right; + idVec3 up; + idVec3 start; + idVec3 end; + + // Dmap will generate an optimized shadow volume named _prelight_ + // for the light against all the _area* models in the map. The renderer will + // ignore this value if the light has been moved after initial creation + idRenderModel * prelightModel; + + // muzzle flash lights will not cast shadows from player and weapon world models + int lightId; + + + const idMaterial * shader; // NULL = either lights/defaultPointLight or lights/defaultProjectedLight + float shaderParms[MAX_ENTITY_SHADER_PARMS]; // can be used in any way by shader +// RAVEN BEGIN + int referenceSoundHandle; // for shader sound tables, allowing effects to vary with sounds +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + idList allMaterials; +#endif +// RAVEN END +} renderLight_t; + +// RAVEN BEGIN +// jscott: for handling of effects +typedef struct renderEffect_s { + + const idDecl *declEffect; + + float startTime; + int suppressSurfaceInViewID; + int allowSurfaceInViewID; + int groupID; + + idVec3 origin; + idMat3 axis; + + idVec3 gravity; + idVec3 endOrigin; + + float attenuation; + bool hasEndOrigin; + bool loop; // effect is looping + bool ambient; // effect is from an entity + bool inConnectedArea; + int weaponDepthHackInViewID; // squash depth range so view weapons don't poke into walls + float modelDepthHack; + + int referenceSoundHandle; // for shader sound tables, allowing effects to vary with sounds + + float shaderParms[ MAX_ENTITY_SHADER_PARMS ]; // can be used in any way by shader or model generation +} renderEffect_t; +// RAVEN END + +typedef struct renderView_s { + // player views will set this to a non-zero integer for model suppress / allow + // subviews (mirrors, cameras, etc) will always clear it to zero + int viewID; + + // sized from 0 to SCREEN_WIDTH / SCREEN_HEIGHT (640/480), not actual resolution + int x, y, width, height; + + float fov_x, fov_y; + idVec3 vieworg; + idMat3 viewaxis; // transformation matrix, view looks down the positive X axis + + bool cramZNear; // for cinematics, we want to set ZNear much lower + bool forceUpdate; // for an update + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + float shaderParms[MAX_GLOBAL_SHADER_PARMS]; // can be used in any way by shader + const idMaterial *globalMaterial; // used to override everything draw + +// RAVEN BEGIN +// mwhitlock: Xenon texture streaming. +#if defined(_XENON) + bool streamingPrecache; // If true, we precache all the textures that are visible in the world +#endif +// RAVEN END +} renderView_t; + + +// exitPortal_t is returned by idRenderWorld::GetPortal() +typedef struct { + int areas[2]; // areas connected by this portal + const idWinding * w; // winding points have counter clockwise ordering seen from areas[0] + int blockingBits; // PS_BLOCK_VIEW, PS_BLOCK_AIR, etc + qhandle_t portalHandle; +} exitPortal_t; + + +// guiPoint_t is returned by idRenderWorld::GuiTrace() +typedef struct { + float x, y; // 0.0 to 1.0 range if trace hit a gui, otherwise -1 + int guiId; // id of gui ( 0, 1, or 2 ) that the trace happened against +} guiPoint_t; + + +// modelTrace_t is for tracing vs. visual geometry +typedef struct modelTrace_s { + float fraction; // fraction of trace completed + idVec3 point; // end point of trace in global space + idVec3 normal; // hit triangle normal vector in global space + const idMaterial * material; // material of hit surface + const renderEntity_t * entity; // render entity that was hit + int jointNumber; // md5 joint nearest to the hit triangle + +// RAVEN BEGIN +// jscott: added block + const rvDeclMatType * materialType; +// RAVEN END +} modelTrace_t; + +// RAVEN BEGIN +// abahr: changed to 4 to include gravity +static const int NUM_PORTAL_ATTRIBUTES = 4; +// RAVEN END + +typedef enum { + PS_BLOCK_NONE = 0, + + PS_BLOCK_VIEW = 1, + PS_BLOCK_LOCATION = 2, // game map location strings often stop in hallways + PS_BLOCK_AIR = 4, // windows between pressurized and unpresurized areas +// RAVEN BEGIN +// abahr + PS_BLOCK_GRAVITY = 8, // PS_BLOCK_ALL does not block gravity. Must use info_gravityseperator + + PS_BLOCK_ALL = PS_BLOCK_VIEW|PS_BLOCK_LOCATION|PS_BLOCK_AIR +// RAVEN END +} portalConnection_t; + + +// RAVEN BEGIN +// AReis: Render flags. +enum +{ + RF_NORMAL = 0, + // Let the renderer know its in a editor of sorts. + RF_IS_EDITOR = BIT( 0 ), + // Viewdef is fullscreen and is 2d (mostly for main menu) + RF_IS_FULLSCREEN_2D = BIT( 1 ), + // Don't draw the GUI, just the world. + RF_NO_GUI = BIT( 2 ), + // Only draw the GUI, not the world. + RF_GUI_ONLY = BIT( 3 ), +// RAVEN BEGIN +// dluetscher: added render flag to denote that penumbra map rendering is desired + RF_PENUMBRA_MAP = BIT( 4 ), +// dluetscher: added render flag that defers the command buffer submission of render +// commands until the first non-deferred render command (with the exception of +// certain render commands like RC_DRAW_PENUMBRA_MAPS - which ignores deferred +// render commands and lets them get submitted past) + RF_DEFER_COMMAND_SUBMIT = BIT( 5 ), + // this is a portal sky view + RF_PORTAL_SKY = BIT( 6 ), + // this the primary view - when this is rendered, we then know we can capture the screen buffer + RF_PRIMARY_VIEW = BIT( 7 ), +// RAVEN END +}; + +// RAVEN END + +class idRenderWorld { +public: + virtual ~idRenderWorld( void ) {}; + + // The same render world can be reinitialized as often as desired + // a NULL or empty mapName will create an empty, single area world + virtual bool InitFromMap( const char *mapName ) = 0; + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void Flush( void ) = 0; +#endif +// RAVEN END + + //-------------- Entity and Light Defs ----------------- + + // entityDefs and lightDefs are added to a given world to determine + // what will be drawn for a rendered scene. Most update work is defered + // until it is determined that it is actually needed for a given view. + virtual qhandle_t AddEntityDef( const renderEntity_t *re ) = 0; + virtual void UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t *re ) = 0; + virtual void FreeEntityDef( qhandle_t entityHandle ) = 0; + virtual const renderEntity_t *GetRenderEntity( qhandle_t entityHandle ) const = 0; + + virtual qhandle_t AddLightDef( const renderLight_t *rlight ) = 0; + virtual void UpdateLightDef( qhandle_t lightHandle, const renderLight_t *rlight ) = 0; + virtual void FreeLightDef( qhandle_t lightHandle ) = 0; + virtual const renderLight_t *GetRenderLight( qhandle_t lightHandle ) const = 0; +// RAVEN BEGIN + virtual void WriteRenderLight( idDemoFile *writeDemo, const renderLight_t *light ) = 0; + virtual void ReadRenderLight( idDemoFile *readDemo, renderLight_t &light ) = 0; + +// jscott: handling of effects + virtual qhandle_t AddEffectDef( const renderEffect_t *reffect, int time ) = 0; + virtual bool UpdateEffectDef( qhandle_t effectHandle, const renderEffect_t *reffect, int time ) = 0; + virtual void StopEffectDef( qhandle_t effectHandle ) = 0; + virtual const class rvRenderEffectLocal* GetEffectDef( qhandle_t effectHandle ) const = 0; + virtual void FreeEffectDef( qhandle_t effectHandle ) = 0; + virtual bool EffectDefHasSound( const renderEffect_s *reffect ) = 0; + +// jscott: for optimised pushes + virtual void PushMarkedDefs( void ) = 0; + virtual void ClearMarkedDefs( void ) = 0; + virtual void FreeDefs( void ) = 0; + +// jscott: fix for FreeWorld crash + virtual void RemoveAllModelReferences( idRenderModel *model ) = 0; + +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + virtual void StreamAreaTextures(bool inBackground) = 0; + virtual void ClearAreaTextureStreamer(void) = 0; +#endif +// RAVEN END + + // Force the generation of all light / surface interactions at the start of a level + // If this isn't called, they will all be dynamically generated + virtual void GenerateAllInteractions( void ) = 0; + + //-------------- Decals and Overlays ----------------- + + // Creates decals on all world surfaces that the winding projects onto. + // The projection origin should be infront of the winding plane. + // The decals are projected onto world geometry between the winding plane and the projection origin. + // The decals are depth faded from the winding plane to a certain distance infront of the + // winding plane and the same distance from the projection origin towards the winding. + virtual void ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) = 0; + + // Creates decals on static models. + virtual void ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) = 0; + + // Creates overlays on dynamic models. + virtual void ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial *material ) = 0; + + // Removes all decals and overlays from the given entity def. + virtual void RemoveDecals( qhandle_t entityHandle ) = 0; + + //-------------- Scene Rendering ----------------- + + // some calls to material functions use the current renderview time when servicing cinematics. this function + // ensures that any parms accessed (such as time) are properly set. + virtual void SetRenderView( const renderView_t *renderView ) = 0; + + // rendering a scene may actually render multiple subviews for mirrors and portals, and + // may render composite textures for gui console screens and light projections + // It would also be acceptable to render a scene multiple times, for "rear view mirrors", etc +// RAVEN BEGIN +// AReis: Modified RenderScene() signature to include renderFlags variable. + virtual void RenderScene( const renderView_t *renderView, int renderFlags = RF_NORMAL ) = 0; +// jscott: for portal skies + virtual bool HasSkybox( int areaNum ) = 0; + virtual void FindVisibleAreas( idVec3 origin, int areaNum, bool *visibleAreas ) = 0; +// AReis: This is where we draw the portal fadeout polygon + virtual void RenderPortalFades( void ) = 0; +// ddynerman: Helper function + virtual idVec3 WorldToScreen( renderView_t* view, idVec3 point ) = 0; + virtual idBounds WorldToScreen( renderView_t* view, idBounds bounds ) = 0; +// RAVEN END + + //-------------- Portal Area Information ----------------- + + // returns the number of portals + virtual int NumPortals( void ) const = 0; + + // returns 0 if no portal contacts the bounds + // This is used by the game to identify portals that are contained + // inside doors, so the connection between areas can be topologically + // terminated when the door shuts. + virtual qhandle_t FindPortal( const idBounds &b ) const = 0; + + // doors explicitly close off portals when shut + // multiple bits can be set to block multiple things, ie: ( PS_VIEW | PS_LOCATION | PS_AIR ) + virtual void SetPortalState( qhandle_t portal, int blockingBits ) = 0; + virtual int GetPortalState( qhandle_t portal ) = 0; + + // returns true only if a chain of portals without the given connection bits set + // exists between the two areas (a door doesn't separate them, etc) + virtual bool AreasAreConnected( int areaNum1, int areaNum2, portalConnection_t connection ) = 0; + + // returns the number of portal areas in a map, so game code can build information + // tables for the different areas + virtual int NumAreas( void ) const = 0; + + // Will return -1 if the point is not in an area, otherwise + // it will return 0 <= value < NumAreas() + virtual int PointInArea( const idVec3 &point ) const = 0; + + // fills the *areas array with the numbers of the areas the bounds cover + // returns the total number of areas the bounds cover + virtual int BoundsInAreas( const idBounds &bounds, int *areas, int maxAreas ) const = 0; + + // Used by the sound system to do area flowing + virtual int NumPortalsInArea( int areaNum ) = 0; + + // returns one portal from an area + virtual void GetPortals( int areaNum, exitPortal_t *ret, int size ) = 0; + virtual void GetPortal( int areaNum, int portalNum, exitPortal_t *ret ) = 0; + virtual exitPortal_t GetPortal( int areaNum, int portalNum ) = 0; + + //-------------- Tracing ----------------- + + // Checks a ray trace against any gui surfaces in an entity, returning the + // fraction location of the trace on the gui surface, or -1,-1 if no hit. + // This doesn't do any occlusion testing, simply ignoring non-gui surfaces. + // start / end are in global world coordinates. + virtual guiPoint_t GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const = 0; + + // Traces vs the render model, possibly instantiating a dynamic version, and returns true if something was hit + virtual bool ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const = 0; + + // Traces vs the whole rendered world. FIXME: we need some kind of material flags. + virtual bool Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic = true, bool skipPlayer = false ) const = 0; + + // Traces vs the world model bsp tree. + virtual bool FastWorldTrace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end ) const = 0; + + //-------------- Demo Control ----------------- + + // Writes a loadmap command to the demo, and clears archive counters. + virtual void StartWritingDemo( idDemoFile *demo ) = 0; + virtual void StopWritingDemo( void ) = 0; + + // Returns true when demoRenderView has been filled in. + // adds/updates/frees entityDefs and lightDefs based on the current demo file + // and returns the renderView to be used to render this frame. + // a demo file may need to be advanced multiple times if the framerate + // is less than 30hz + // demoTimeOffset will be set if a new map load command was processed before + // the next renderScene + virtual bool ProcessDemoCommand( idDemoFile *readDemo, renderView_t *demoRenderView, renderView_t *portalSkyRenderView, int *demoTimeOffset ) = 0; + + // this is used to regenerate all interactions ( which is currently only done during influences ), there may be a less + // expensive way to do it + virtual void RegenerateWorld( void ) = 0; + + //-------------- Debug Visualization ----------------- + + // Line drawing for debug visualization + virtual void DebugClear( int time ) = 0; // a time of 0 will clear all lines and text + virtual void DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime = 0, const bool depthTest = false ) = 0; + virtual void DebugArrow( const idVec4 &color, const idVec3 &start, const idVec3 &end, int size, const int lifetime = 0 ) = 0; + virtual void DebugWinding( const idVec4 &color, const idWinding &w, const idVec3 &origin, const idMat3 &axis, const int lifetime = 0, const bool depthTest = false ) = 0; + virtual void DebugCircle( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const int lifetime = 0, const bool depthTest = false ) = 0; + virtual void DebugSphere( const idVec4 &color, const idSphere &sphere, const int lifetime = 0, bool depthTest = false ) = 0; +// RAVEN BEGIN +// jscott: want to be able to specify depth test + virtual void DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org = vec3_origin, const int lifetime = 0, bool depthTest = false ) = 0; + virtual size_t MemorySummary( const idCmdArgs &args ) = 0; + virtual void ShowDebugLines( void ) = 0; + virtual void ShowDebugPolygons( void ) = 0; + virtual void ShowDebugText( void ) = 0; +// cdr: added debug FOV + virtual void DebugFOV( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, float farDot, float farDist, float nearDot=1.0f, float nearDist=0.0f, float alpha=0.3f, int lifetime = 0 ) = 0; +// RAVEN END + virtual void DebugBox( const idVec4 &color, const idBox &box, const int lifetime = 0 ) = 0; + virtual void DebugFrustum( const idVec4 &color, const idFrustum &frustum, const bool showFromOrigin = false, const int lifetime = 0 ) = 0; + virtual void DebugCone( const idVec4 &color, const idVec3 &apex, const idVec3 &dir, float radius1, float radius2, const int lifetime = 0 ) = 0; + virtual void DebugAxis( const idVec3 &origin, const idMat3 &axis ) = 0; + + // Polygon drawing for debug visualization. + virtual void DebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime = 0, const bool depthTest = false ) = 0; + + // Text drawing for debug visualization. + virtual void DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align = 1, const int lifetime = 0, bool depthTest = false ) = 0; +}; + +#endif /* !__RENDERWORLD_H__ */ diff --git a/src/renderer/RenderWorld_local.h b/src/renderer/RenderWorld_local.h new file mode 100644 index 0000000..0a0f35e --- /dev/null +++ b/src/renderer/RenderWorld_local.h @@ -0,0 +1,296 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RENDERWORLDLOCAL_H__ +#define __RENDERWORLDLOCAL_H__ + +// assume any lightDef or entityDef index above this is an internal error +const int LUDICROUS_INDEX = 10000; + + +typedef struct portal_s { + int intoArea; // area this portal leads to + idWinding * w; // winding points have counter clockwise ordering seen this area + idPlane plane; // view must be on the positive side of the plane to cross + struct portal_s * next; // next portal of the area + struct doublePortal_s * doublePortal; +} portal_t; + + +typedef struct doublePortal_s { + struct portal_s * portals[2]; + int blockingBits; // PS_BLOCK_VIEW, PS_BLOCK_AIR, etc, set by doors that shut them off + + // A portal will be considered closed if it is past the + // fog-out point in a fog volume. We only support a single + // fog volume over each portal. + idRenderLightLocal * fogLight; + struct doublePortal_s * nextFoggedPortal; +} doublePortal_t; + + +typedef struct portalArea_s { + int areaNum; + int connectedAreaNum[NUM_PORTAL_ATTRIBUTES]; // if two areas have matching connectedAreaNum, they are + // not separated by a portal with the apropriate PS_BLOCK_* blockingBits + int viewCount; // set by R_FindViewLightsAndEntities + portal_t * portals; // never changes after load + areaReference_t entityRefs; // head/tail of doubly linked list, may change + areaReference_t lightRefs; // head/tail of doubly linked list, may change + areaReference_t effectRefs; // Quake 4 BSE effects touching this area + bool hasSkybox; // set while the area's world model is installed +} portalArea_t; + + +static const int CHILDREN_HAVE_MULTIPLE_AREAS = -2; +static const int AREANUM_SOLID = -1; +typedef struct { + idPlane plane; + int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid + int commonChildrenArea; // if all children are either solid or a single area, + // this is the area number, else CHILDREN_HAVE_MULTIPLE_AREAS +} areaNode_t; + + +class idRenderWorldLocal : public idRenderWorld { +public: + idRenderWorldLocal(); + virtual ~idRenderWorldLocal(); + + virtual qhandle_t AddEntityDef( const renderEntity_t *re ); + virtual void UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t *re ); + virtual void FreeEntityDef( qhandle_t entityHandle ); + virtual const renderEntity_t *GetRenderEntity( qhandle_t entityHandle ) const; + + virtual qhandle_t AddLightDef( const renderLight_t *rlight ); + virtual void UpdateLightDef( qhandle_t lightHandle, const renderLight_t *rlight ); + virtual void FreeLightDef( qhandle_t lightHandle ); + virtual const renderLight_t *GetRenderLight( qhandle_t lightHandle ) const; + virtual void WriteRenderLight( idDemoFile *demo, const renderLight_t *light ); + virtual void ReadRenderLight( idDemoFile *demo, renderLight_t &light ); + + virtual qhandle_t AddEffectDef( const renderEffect_t *effect, int time ); + virtual bool UpdateEffectDef( qhandle_t effectHandle, const renderEffect_t *effect, int time ); + virtual void StopEffectDef( qhandle_t effectHandle ); + virtual const rvRenderEffectLocal *GetEffectDef( qhandle_t effectHandle ) const; + virtual void FreeEffectDef( qhandle_t effectHandle ); + virtual bool EffectDefHasSound( const renderEffect_t *effect ); + virtual void PushMarkedDefs(); + virtual void ClearMarkedDefs(); + virtual void RemoveAllModelReferences( idRenderModel *model ); + + virtual bool CheckAreaForPortalSky( int areaNum ); + + virtual void GenerateAllInteractions(); + virtual void RegenerateWorld(); + + virtual void ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ); + virtual void ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ); + virtual void ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial *material ); + virtual void RemoveDecals( qhandle_t entityHandle ); + + virtual void SetRenderView( const renderView_t *renderView ); + virtual void RenderScene( const renderView_t *renderView ); + virtual void RenderScene( const renderView_t *renderView, int renderFlags ); + virtual bool HasSkybox( int areaNum ); + virtual void FindVisibleAreas( idVec3 origin, int areaNum, bool *visibleAreas ); + virtual void RenderPortalFades(); + virtual idVec3 WorldToScreen( renderView_t *renderView, idVec3 worldPoint ); + virtual idBounds WorldToScreen( renderView_t *renderView, idBounds worldBounds ); + + virtual int NumAreas( void ) const; + virtual int PointInArea( const idVec3 &point ) const; + virtual int BoundsInAreas( const idBounds &bounds, int *areas, int maxAreas ) const; + virtual int NumPortalsInArea( int areaNum ); + virtual exitPortal_t GetPortal( int areaNum, int portalNum ); + virtual void GetPortals( int areaNum, exitPortal_t *portals, int maxPortals ); + virtual void GetPortal( int areaNum, int portalNum, exitPortal_t *portal ); + virtual bool ProcessDemoCommand( idDemoFile *demo, renderView_t *renderView, renderView_t *portalRenderView, int *demoTimeOffset ); + + virtual guiPoint_t GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const; + virtual bool ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const; + virtual bool Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic = true, bool skipPlayer = false ) const; + virtual bool FastWorldTrace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end ) const; + + virtual void DebugClearLines( int time ); + virtual void DebugClear( int time ); + virtual void DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime = 0, const bool depthTest = false ); + virtual void DebugArrow( const idVec4 &color, const idVec3 &start, const idVec3 &end, int size, const int lifetime = 0 ); + virtual void DebugWinding( const idVec4 &color, const idWinding &w, const idVec3 &origin, const idMat3 &axis, const int lifetime = 0, const bool depthTest = false ); + virtual void DebugCircle( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const int lifetime = 0, const bool depthTest = false ); + virtual void DebugSphere( const idVec4 &color, const idSphere &sphere, const int lifetime = 0, bool depthTest = false ); + virtual void DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org = vec3_origin, const int lifetime = 0 ); + virtual void DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org, const int lifetime, bool depthTest ); + virtual size_t MemorySummary( const idCmdArgs &args ); + virtual void ShowDebugLines(); + virtual void ShowDebugPolygons(); + virtual void ShowDebugText(); + virtual void DebugFOV( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, float fovX, float fovY, float radius, float nearDist, float farDist, int lifetime ); + virtual void DebugBox( const idVec4 &color, const idBox &box, const int lifetime = 0 ); + virtual void DebugFrustum( const idVec4 &color, const idFrustum &frustum, const bool showFromOrigin = false, const int lifetime = 0 ); + virtual void DebugCone( const idVec4 &color, const idVec3 &apex, const idVec3 &dir, float radius1, float radius2, const int lifetime = 0 ); + virtual void DebugScreenRect( const idVec4 &color, const idScreenRect &rect, const viewDef_t *viewDef, const int lifetime = 0 ); + virtual void DebugAxis( const idVec3 &origin, const idMat3 &axis ); + + virtual void DebugClearPolygons( int time ); + virtual void DebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime = 0, const bool depthTest = false ); + + virtual void DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align = 1, const int lifetime = 0, bool depthTest = false ); + + //----------------------- + + idStr mapName; // ie: maps/tim_dm2.proc, written to demoFile + ID_TIME_T mapTimeStamp; // for fast reloads of the same level + + areaNode_t * areaNodes; + int numAreaNodes; + + portalArea_t * portalAreas; + int numPortalAreas; + int connectedAreaNum; // incremented every time a door portal state changes + + idScreenRect * areaScreenRect; + + doublePortal_t * doublePortals; + int numInterAreaPortals; + + idList localModels; + + idList entityDefs; + idList lightDefs; + idList effectDefs; + idList markedEntityDefs; + idList markedLightDefs; + idList markedEffectDefs; + + idBlockAlloc areaReferenceAllocator; + idBlockAlloc interactionAllocator; + idBlockAlloc areaNumRefAllocator; + + // all light / entity interactions are referenced here for fast lookup without + // having to crawl the doubly linked lists. EnntityDefs are sequential for better + // cache access, because the table is accessed by light in idRenderWorldLocal::CreateLightDefInteractions() + // Growing this table is time consuming, so we add a pad value to the number + // of entityDefs and lightDefs + idInteraction ** interactionTable; + int interactionTableWidth; // entityDefs + int interactionTableHeight; // lightDefs + + + bool generateAllInteractionsCalled; + + //----------------------- + // RenderWorld_load.cpp + + idRenderModel * ParseModel( idLexer *src ); + idRenderModel * ParseShadowModel( idLexer *src ); + void SetupAreaRefs(); + void ParseInterAreaPortals( idLexer *src ); + void ParseNodes( idLexer *src ); + int CommonChildrenArea_r( areaNode_t *node ); + void FreeWorld(); + void ClearWorld(); + void FreeDefs(); + void TouchWorldModels( void ); + void AddWorldModelEntities(); + void ClearPortalStates(); + virtual bool InitFromMap( const char *mapName ); + + //-------------------------- + // RenderWorld_portals.cpp + + idScreenRect ScreenRectFromWinding( const idWinding *w, viewEntity_t *space ); + bool PortalIsFoggedOut( const portal_t *p ); + void FloodViewThroughArea_r( const idVec3 origin, int areaNum, const struct portalStack_s *ps ); + void FlowViewThroughPortals( const idVec3 origin, int numPlanes, const idPlane *planes ); + void FloodLightThroughArea_r( idRenderLightLocal *light, int areaNum, const struct portalStack_s *ps ); + void FlowLightThroughPortals( idRenderLightLocal *light ); + areaNumRef_t * FloodFrustumAreas_r( const idFrustum &frustum, const int areaNum, const idBounds &bounds, areaNumRef_t *areas ); + areaNumRef_t * FloodFrustumAreas( const idFrustum &frustum, areaNumRef_t *areas ); + bool CullEntityByPortals( const idRenderEntityLocal *entity, const struct portalStack_s *ps ); + void AddAreaEntityRefs( int areaNum, const struct portalStack_s *ps ); + bool CullLightByPortals( const idRenderLightLocal *light, const struct portalStack_s *ps ); + void AddAreaLightRefs( int areaNum, const struct portalStack_s *ps ); + void AddAreaRefs( int areaNum, const struct portalStack_s *ps ); + void BuildConnectedAreas_r( int areaNum ); + void BuildConnectedAreas( void ); + void FindViewLightsAndEntities( void ); + void FindVisibleAreas_r( idVec3 origin, int areaNum, bool *visibleAreas ); + + int NumPortals( void ) const; + qhandle_t FindPortal( const idBounds &b ) const; + void SetPortalState( qhandle_t portal, int blockingBits ); + int GetPortalState( qhandle_t portal ); + bool AreasAreConnected( int areaNum1, int areaNum2, portalConnection_t connection ); + void FloodConnectedAreas( portalArea_t *area, int portalAttributeIndex ); + idScreenRect & GetAreaScreenRect( int areaNum ) const { return areaScreenRect[areaNum]; } + void ShowPortals(); + + //-------------------------- + // RenderWorld_demo.cpp + + void StartWritingDemo( idDemoFile *demo ); + void StopWritingDemo(); + bool ProcessDemoCommand( idDemoFile *readDemo, renderView_t *demoRenderView, int *demoTimeOffset ); + + void WriteLoadMap(); + void WriteRenderView( const renderView_t *renderView ); + void WriteVisibleDefs( const viewDef_t *viewDef ); + void WriteFreeLight( qhandle_t handle ); + void WriteFreeEntity( qhandle_t handle ); + void WriteRenderLight( qhandle_t handle, const renderLight_t *light ); + void WriteRenderEntity( qhandle_t handle, const renderEntity_t *ent ); + void ReadRenderEntity(); + void ReadRenderLight(); + + + //-------------------------- + // RenderWorld.cpp + + void ResizeInteractionTable(); + + void AddEntityRefToArea( idRenderEntityLocal *def, portalArea_t *area ); + void AddLightRefToArea( idRenderLightLocal *light, portalArea_t *area ); + void AddEffectRefToArea( rvRenderEffectLocal *effect, portalArea_t *area ); + void FreeEffectDefDerivedData( rvRenderEffectLocal *effect ); + void PushEffectDef( rvRenderEffectLocal *effect ); + + void RecurseProcBSP_r( modelTrace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 ) const; + + void BoundsInAreas_r( int nodeNum, const idBounds &bounds, int *areas, int *numAreas, int maxAreas ) const; + + float DrawTextLength( const char *text, float scale, int len = 0 ); + + void FreeInteractions(); + + void PushVolumeIntoTree_r( idRenderEntityLocal *def, idRenderLightLocal *light, const idSphere *sphere, int numPoints, const idVec3 (*points), int nodeNum ); + + void PushVolumeIntoTree( idRenderEntityLocal *def, idRenderLightLocal *light, int numPoints, const idVec3 (*points) ); + + //------------------------------- + // tr_light.c + void CreateLightDefInteractions( idRenderLightLocal *ldef ); +}; + +#endif /* !__RENDERWORLDLOCAL_H__ */ diff --git a/src/renderer/Shaders.cpp b/src/renderer/Shaders.cpp new file mode 100644 index 0000000..cec61fc --- /dev/null +++ b/src/renderer/Shaders.cpp @@ -0,0 +1,489 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Shaders.h" + +extern idMat4 colorMatrix; + +static const int MAX_SHADER_CONSTANTS = 36; +static const int MAX_GAUSSIAN_SAMPLES = 15; + +static const char *shaderConstantNames[MAX_SHADER_CONSTANTS] = { + "lightOrigin", "viewOrigin", "lightProject_s", "lightProject_t", + "lightProject_q", "lightFalloff_s", "bumpMatrix_s", "bumpMatrix_t", + "diffuseMatrix_s", "diffuseMatrix_t", "specularMatrix_s", "specularMatrix_t", + "colorModulate", "colorAdd", "diffuseColor", "specularColor", + "colorMatrix0", "colorMatrix1", "colorMatrix2", "projectionMatrix0", + "projectionMatrix1", "projectionMatrix2", "projectionMatrix3", "modelMatrix0", + "modelMatrix1", "modelMatrix2", "globalEyePos", "mvpMatrix0", + "mvpMatrix1", "mvpMatrix2", "mvpMatrix3", "gaussianSampleOffsets", + "gaussianSampleWeights", "gaussianSampleOffsetsHorizontal", + "gaussianSampleOffsetsVertical", "gaussianSampleWeights2" +}; + +static const int shaderConstantsNumRegisters[MAX_SHADER_CONSTANTS] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 16, 16, 16, 16, 16 +}; + +static idVec4 gaussianSampleOffsets[16]; +static idVec4 gaussianSampleWeights[16]; +static idVec4 gaussianSampleWeights2[16]; +static float gaussianSampleOffsetsVertical[16]; +static float gaussianSampleOffsetsHorizontal[16]; +static bool gaussianCoefficientsAlreadyComputed; +static float lastWidth; + +rvShader *rvNewShaderStage::ShaderList[200]; +int rvNewShaderStage::NumShaders; + +rvShader::rvShader( unsigned int initTarget, const char *initName ) { + target = initTarget; + ident = rvNewShaderStage::NumShaders; + idStr::Copynz( name, initName, sizeof( name ) ); +} + +bool rvNewShaderStage::AddShaderProgram( rvShader *shaderProgram ) { + if ( shaderProgram == NULL || NumShaders >= 200 ) { + return false; + } + ShaderList[NumShaders++] = shaderProgram; + return shaderProgram->LoadProgram(); +} + +void rvNewShaderStage::UnBind() { + if ( shaderProgram == NULL ) { + return; + } + for ( int i = 0; i < 8 && textureParmNames[i][0] != '\0'; ++i ) { + shaderProgram->UnBindTexture( textureParmLocations[i], i, textureParmImages[i] ); + } + GL_SelectTexture( 0 ); + shaderProgram->UnBind(); +} + +void rvNewShaderStage::Shutdown() { + shaderProgram = NULL; +} + +void rvNewShaderStage::BindShaderTextureConstant( int slot, int bindingType, const drawInteraction_t *din ) { + if ( din == NULL || shaderProgram == NULL ) { + return; + } + idImage *image = NULL; + if ( bindingType == 1 ) { + image = din->lightFalloffImage; + } else if ( bindingType == 2 ) { + image = din->lightImage; + } + if ( image != NULL ) { + shaderProgram->SetTexture( textureParmLocations[slot], slot, image ); + } +} + +void rvNewShaderStage::SetTextureParm( const char *parmName, idImage *image ) { + for ( int i = 0; i < 8; ++i ) { + if ( idStr::Icmp( parmName, textureParmNames[i] ) == 0 ) { + textureParmImages[i] = image; + return; + } + } +} + +int rvNewShaderStage::FindShaderParameter( const char *parmName ) { + for ( int i = 0; i < 96; ++i ) { + if ( idStr::Icmp( parmName, shaderParmNames[i] ) == 0 ) { + return i; + } + } + return -1; +} + +void rvNewShaderStage::SetShaderParameter( int index, float *registers, const float *floatVector, int arraySize ) { + if ( index < 0 || index >= 96 || registers == NULL || floatVector == NULL ) { + return; + } + const int count = Min( arraySize, 4 * ( 96 - index ) ); + const int *parameterRegisters = &shaderParmRegisters[index][0]; + for ( int i = 0; i < count; ++i ) { + const int registerIndex = parameterRegisters[i]; + if ( registerIndex >= 0 ) { + registers[registerIndex] = floatVector[i]; + } + } +} + +void CalculateGaussianCoefficients( int width, int height, float multiplier ) { + const float inverseWidth = 1.0f / Max( width, 1 ); + const float inverseHeight = 1.0f / Max( height, 1 ); + float total = 0.0f; + int sample = 0; + for ( int x = -2; x <= 2; ++x ) { + for ( int y = -2; y <= 2; ++y ) { + if ( abs( x ) + abs( y ) > 2 ) { + continue; + } + gaussianSampleOffsets[sample].Set( x * inverseWidth, y * inverseHeight, 0.0f, 0.0f ); + const float weight = idMath::Exp( -0.5f * ( x * x + y * y ) ) / idMath::Sqrt( idMath::TWO_PI ); + gaussianSampleWeights[sample].Set( weight, weight, weight, weight ); + total += weight; + ++sample; + } + } + const float scale = total > 0.0f ? multiplier / total : 0.0f; + for ( int i = 0; i < sample; ++i ) { + gaussianSampleWeights[i] *= scale; + } +} + +void CalculateGaussianCoefficients1D( int size, float multiplier, float deviation, + float *sampleOffsets, idVec4 *sampleWeights ) { + const int halfSamples = 8; + const float inverseSize = 1.0f / Max( size, 1 ); + const float divisor = 2.0f * deviation * deviation; + const float normalization = 1.0f / idMath::Sqrt( idMath::TWO_PI * deviation * deviation ); + + for ( int i = 0; i < halfSamples; ++i ) { + sampleOffsets[i] = i * inverseSize; + if ( sampleWeights != NULL ) { + const float weight = idMath::Exp( -( i * i ) / divisor ) * normalization * multiplier; + sampleWeights[i].Set( weight, weight, weight, 1.0f ); + } + } + for ( int i = halfSamples; i < MAX_GAUSSIAN_SAMPLES; ++i ) { + const int mirror = MAX_GAUSSIAN_SAMPLES - i; + sampleOffsets[i] = -sampleOffsets[mirror]; + if ( sampleWeights != NULL ) { + sampleWeights[i] = sampleWeights[mirror]; + } + } +} + +rvShader *rvNewShaderStage::FindShaderProgram( const char *program ) { + idStr stripped = program; + stripped.StripFileExtension(); + for ( int i = 0; i < NumShaders; ++i ) { + idStr compare = ShaderList[i]->name; + compare.StripFileExtension(); + if ( stripped.Icmp( compare ) == 0 ) { + return ShaderList[i]; + } + } + if ( NumShaders >= 200 ) { + common->Error( "R_FindShaderProgram: MAX_SHADER_PROGS" ); + } + return NULL; +} + +void rvNewShaderStage::R_Shaders_Init() { + NumShaders = 0; + const int materialCount = declManager->GetNumDecls( DECL_MATERIAL ); + for ( int i = 0; i < materialCount; ++i ) { + const idMaterial *material = static_cast( declManager->DeclByIndex( DECL_MATERIAL, i, false ) ); + for ( int stageIndex = 0; stageIndex < material->GetNumStages(); ++stageIndex ) { + rvNewShaderStage *stage = material->GetStage( stageIndex )->newShaderStage; + if ( stage != NULL ) { + stage->Resolve(); + } + } + } +} + +void rvNewShaderStage::R_Shaders_Shutdown() { + for ( int i = 0; i < NumShaders; ++i ) { + delete ShaderList[i]; + ShaderList[i] = NULL; + } + NumShaders = 0; + + const int materialCount = declManager->GetNumDecls( DECL_MATERIAL ); + for ( int i = 0; i < materialCount; ++i ) { + const idMaterial *material = static_cast( declManager->DeclByIndex( DECL_MATERIAL, i, false ) ); + for ( int stageIndex = 0; stageIndex < material->GetNumStages(); ++stageIndex ) { + rvNewShaderStage *stage = material->GetStage( stageIndex )->newShaderStage; + if ( stage != NULL ) { + stage->Shutdown(); + } + } + } +} + +bool rvNewShaderStage::ParseProgram( idLexer &src, idMaterial *material ) { + idToken token; + if ( !src.ReadToken( &token ) ) { + return false; + } + idStr::Copynz( shaderName, token.c_str(), sizeof( shaderName ) ); + return IsSupported(); +} + +bool rvNewShaderStage::ParseShaderParm( idLexer &src, idMaterial *material ) { + int slot; + for ( slot = 0; slot < 96 && shaderParmNames[slot][0] != '\0'; ++slot ) { + } + if ( slot >= 96 ) { + return false; + } + + idToken token; + if ( !src.ReadToken( &token ) ) { + return false; + } + idStr::Copynz( shaderParmNames[slot], token.c_str(), sizeof( shaderParmNames[slot] ) ); + + int count = 0; + for ( ; count < 4; ++count ) { + const int expression = material->ParseExpression( src ); + shaderParmRegisters[slot][count] = expression; + if ( count == 0 && expression < 0 ) { + const int constant = -expression - 1; + if ( constant < 0 || constant >= MAX_SHADER_CONSTANTS ) { + return false; + } + const int extraRegisters = shaderConstantsNumRegisters[constant] - 1; + if ( slot + extraRegisters >= 96 ) { + common->Warning( "Shader constant takes more registers than are allowed." ); + return false; + } + for ( int i = 1; i <= extraRegisters; ++i ) { + idStr::Copynz( shaderParmNames[slot + i], "x", sizeof( shaderParmNames[slot + i] ) ); + } + count = 1; + break; + } + if ( count == 3 || !src.ReadToken( &token ) ) { + ++count; + break; + } + if ( token != "," ) { + src.UnreadToken( &token ); + ++count; + break; + } + } + shaderParmNumRegisters[slot] = count; + return true; +} + +bool rvNewShaderStage::ParseTextureParm( idLexer &src, idMaterial *material, textureRepeat_t defaultRepeat ) { + int slot; + for ( slot = 0; slot < 8 && textureParmNames[slot][0] != '\0'; ++slot ) { + } + if ( slot >= 8 ) { + return false; + } + + idToken token; + if ( !src.ReadToken( &token ) ) { + return false; + } + idStr::Copynz( textureParmNames[slot], token.c_str(), sizeof( textureParmNames[slot] ) ); + + textureFilter_t filter = TF_DEFAULT; + textureRepeat_t repeat = defaultRepeat; + textureDepth_t depth = TD_DEFAULT; + bool allowPicmip = true; + cubeFiles_t cubeMap = CF_2D; + + while ( src.ReadTokenOnLine( &token ) ) { + if ( !token.Icmp( "nearest" ) ) { filter = TF_NEAREST; continue; } + if ( !token.Icmp( "linear" ) ) { filter = TF_LINEAR; continue; } + if ( !token.Icmp( "clamp" ) ) { repeat = TR_CLAMP; continue; } + if ( !token.Icmp( "noclamp" ) ) { repeat = TR_REPEAT; continue; } + if ( !token.Icmp( "zeroclamp" ) ) { repeat = TR_CLAMP_TO_ZERO; continue; } + if ( !token.Icmp( "alphazeroclamp" ) ) { repeat = TR_CLAMP_TO_ZERO_ALPHA; continue; } + if ( !token.Icmp( "mirroredrepeat" ) ) { repeat = TR_MIRRORED_REPEAT; continue; } + if ( !token.Icmp( "uncompressed" ) || !token.Icmp( "highquality" ) ) { + if ( !globalImages->image_ignoreHighQuality.GetBool() ) { depth = TD_HIGH_QUALITY; } + continue; + } + if ( !token.Icmp( "forceHighQuality" ) ) { depth = TD_HIGH_QUALITY; continue; } + if ( !token.Icmp( "nopicmip" ) ) { allowPicmip = false; continue; } + if ( !token.Icmp( "cubeMap" ) ) { cubeMap = CF_NATIVE; continue; } + if ( !token.Icmp( "cameraCubeMap" ) ) { cubeMap = CF_CAMERA; continue; } + if ( !token.Icmp( "nomips" ) ) { continue; } + break; + } + + if ( !token.Icmp( "lightfalloffImage" ) ) { + textureParmLocations[slot] = -1; + textureParmImages[slot] = NULL; + } else if ( !token.Icmp( "lightImage" ) ) { + textureParmLocations[slot] = -2; + textureParmImages[slot] = NULL; + } else if ( !token.Icmp( "ambientNormalMap" ) ) { + textureParmLocations[slot] = -3; + textureParmImages[slot] = globalImages->ambientNormalMap; + } else if ( !token.Icmp( "normalCubeMap" ) ) { + textureParmLocations[slot] = -4; + textureParmImages[slot] = globalImages->normalCubeMapImage; + } else if ( !token.Icmp( "specularTableImage" ) ) { + textureParmLocations[slot] = -5; + textureParmImages[slot] = globalImages->specularTableImage; + } else { + src.UnreadToken( &token ); + const char *imageName = R_ParsePastImageProgram( src ); + textureParmImages[slot] = globalImages->ImageFromFile( imageName, filter, allowPicmip, repeat, depth, cubeMap ); + if ( textureParmImages[slot] == NULL ) { + textureParmImages[slot] = globalImages->defaultImage; + } + } + return true; +} + +void rvNewShaderStage::Bind( const float *registers, const drawInteraction_t *din ) { + if ( shaderProgram == NULL ) { + Resolve(); + } + if ( shaderProgram == NULL || invalidShader ) { + return; + } + shaderProgram->Bind(); + UpdateShaderParms( registers, din ); + for ( int i = 0; i < 8 && textureParmNames[i][0] != '\0'; ++i ) { + if ( textureParmLocations[i] >= 0 || textureParmImages[i] != NULL ) { + shaderProgram->SetTexture( textureParmLocations[i], i, textureParmImages[i] ); + } else { + BindShaderTextureConstant( i, -textureParmLocations[i], din ); + } + } +} + +static void BuildGaussianConstants() { + const int width = tr.viewDef != NULL ? tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 + 1 : glConfig.vidWidth; + const int height = tr.viewDef != NULL ? tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 + 1 : glConfig.vidHeight; + if ( gaussianCoefficientsAlreadyComputed && lastWidth == width ) { + return; + } + gaussianCoefficientsAlreadyComputed = true; + lastWidth = static_cast( width ); + CalculateGaussianCoefficients( width, height, 1.0f ); + CalculateGaussianCoefficients1D( width, 1.0f, 3.0f, gaussianSampleOffsetsHorizontal, gaussianSampleWeights2 ); + CalculateGaussianCoefficients1D( width, 1.0f, 3.0f, gaussianSampleOffsetsVertical, NULL ); +} + +void rvNewShaderStage::BindShaderParameterConstant( int slot, int bindingType, const drawInteraction_t *din ) { + static const float zero[4] = { 0, 0, 0, 0 }; + static const float one[4] = { 1, 1, 1, 1 }; + static const float negOne[4] = { -1, -1, -1, -1 }; + float value[4]; + + if ( bindingType >= 0 && bindingType <= 11 ) { + if ( din == NULL ) { return; } + const idVec4 *constants[] = { + &din->localLightOrigin, &din->localViewOrigin, + &din->lightProjection[0], &din->lightProjection[1], &din->lightProjection[2], &din->lightProjection[3], + &din->bumpMatrix[0], &din->bumpMatrix[1], &din->diffuseMatrix[0], &din->diffuseMatrix[1], + &din->specularMatrix[0], &din->specularMatrix[1] + }; + BindShaderParameter( slot, 4, constants[bindingType]->ToFloatPtr(), 1 ); + return; + } + + switch ( bindingType ) { + case 12: + if ( din != NULL ) { + const float *color = din->vertexColor == SVC_IGNORE ? one : ( din->vertexColor == SVC_MODULATE ? zero : negOne ); + BindShaderParameter( slot, 4, color, 1 ); + } + break; + case 13: + if ( din != NULL ) { + const float *color = din->vertexColor == SVC_IGNORE ? zero : one; + BindShaderParameter( slot, 4, color, 1 ); + } + break; + case 14: if ( din != NULL ) { BindShaderParameter( slot, 4, din->diffuseColor.ToFloatPtr(), 1 ); } break; + case 15: if ( din != NULL ) { BindShaderParameter( slot, 4, din->specularColor.ToFloatPtr(), 1 ); } break; + case 16: BindShaderParameter( slot, 4, colorMatrix[0].ToFloatPtr(), 1 ); break; + case 17: BindShaderParameter( slot, 4, colorMatrix[1].ToFloatPtr(), 1 ); break; + case 18: BindShaderParameter( slot, 4, colorMatrix[2].ToFloatPtr(), 1 ); break; + case 19: case 20: case 21: case 22: + if ( backEnd.viewDef != NULL ) { + const int column = bindingType - 19; + for ( int row = 0; row < 4; ++row ) { value[row] = backEnd.viewDef->projectionMatrix[column + row * 4]; } + BindShaderParameter( slot, 4, value, 1 ); + } + break; + case 23: case 24: case 25: + if ( backEnd.currentSpace != NULL ) { + const int column = bindingType - 23; + for ( int row = 0; row < 4; ++row ) { value[row] = backEnd.currentSpace->modelMatrix[column + row * 4]; } + BindShaderParameter( slot, 4, value, 1 ); + } + break; + case 26: + if ( backEnd.viewDef != NULL ) { + value[0] = backEnd.viewDef->renderView.vieworg[0]; value[1] = backEnd.viewDef->renderView.vieworg[1]; + value[2] = backEnd.viewDef->renderView.vieworg[2]; value[3] = 1.0f; + BindShaderParameter( slot, 4, value, 1 ); + } + break; + case 27: case 28: case 29: case 30: + BindShaderParameter( slot, 4, &backEnd.modelViewProjection[( bindingType - 27 ) * 4], 1 ); + break; + case 31: + case 32: + case 33: + case 34: + case 35: { + BuildGaussianConstants(); + if ( bindingType == 31 ) { BindShaderParameter( slot, 4, gaussianSampleOffsets[0].ToFloatPtr(), 15 ); } + else if ( bindingType == 32 ) { BindShaderParameter( slot, 4, gaussianSampleWeights[0].ToFloatPtr(), 15 ); } + else if ( bindingType == 35 ) { BindShaderParameter( slot, 4, gaussianSampleWeights2[0].ToFloatPtr(), 15 ); } + else { + idVec4 samples[16]; + for ( int i = 0; i < 15; ++i ) { + samples[i].Zero(); + if ( bindingType == 33 ) { samples[i][0] = gaussianSampleOffsetsHorizontal[i]; } + else { samples[i][1] = gaussianSampleOffsetsVertical[i]; } + } + BindShaderParameter( slot, 4, samples[0].ToFloatPtr(), 15 ); + } + break; + } + } +} + +void rvNewShaderStage::UpdateShaderParms( const float *registers, const drawInteraction_t *din ) { + for ( int slot = 0; slot < 96 && shaderParmNames[slot][0] != '\0'; ++slot ) { + if ( shaderParmRegisters[slot][0] < 0 ) { + BindShaderParameterConstant( slot, -shaderParmRegisters[slot][0] - 1, din ); + continue; + } + float data[4] = { 0, 0, 0, 0 }; + const int count = Min( shaderParmNumRegisters[slot], 4 ); + for ( int i = 0; i < count; ++i ) { + data[i] = registers[shaderParmRegisters[slot][i]]; + } + BindShaderParameter( slot, count, data, 1 ); + } +} diff --git a/src/renderer/Shaders.h b/src/renderer/Shaders.h new file mode 100644 index 0000000..2c14673 --- /dev/null +++ b/src/renderer/Shaders.h @@ -0,0 +1,134 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software +// +// Quake 4 renderer shader-stage declarations reconstructed from quake4.pdb. + +#ifndef __RENDERER_SHADERS_H__ +#define __RENDERER_SHADERS_H__ + +class idImage; +class idLexer; +class idMaterial; +struct drawInteraction_s; +typedef struct drawInteraction_s drawInteraction_t; + +class rvShader { +public: + rvShader( unsigned int initTarget, const char *initName ); + virtual ~rvShader(); + + virtual bool LoadProgram(); + virtual unsigned int GetVariablePosition( const char *variableName ); + virtual void Bind(); + virtual void SetTexture( int position, int unit, idImage *image ); + virtual void UnBindTexture( int position, int unit, idImage *image ); + virtual void UnBind(); + + unsigned int target; + unsigned int ident; + char name[64]; +}; + +class rvNewShaderStage { +public: + virtual ~rvNewShaderStage() {} + + // Keep this declaration order synchronized with the retail vtable at + // 0x102BAAA0. Material startup calls Resolve through slot one. + virtual void Resolve(); + virtual void UpdateShaderParms( const float *registers, const drawInteraction_t *din ); + virtual void Bind( const float *registers, const drawInteraction_t *din ); + virtual void UnBind(); + virtual void Shutdown(); + virtual bool ParseProgram( idLexer &src, idMaterial *material ); + virtual bool ParseShaderParm( idLexer &src, idMaterial *material ); + virtual bool ParseTextureParm( idLexer &src, idMaterial *material, textureRepeat_t defaultRepeat ); + virtual bool IsSupported() = 0; + virtual bool IsValid(); + virtual void BindShaderParameter( int slot, int numParms, const float *floatVector, int arraySize ) = 0; + + void BindShaderTextureConstant( int slot, int bindingType, const drawInteraction_t *din ); + void BindShaderParameterConstant( int slot, int bindingType, const drawInteraction_t *din ); + void SetTextureParm( const char *name, idImage *image ); + int FindShaderParameter( const char *name ); + void SetShaderParameter( int index, float *registers, const float *floatVector, int arraySize ); + + static bool AddShaderProgram( rvShader *shaderProgram ); + static rvShader *FindShaderProgram( const char *program ); + static void R_Shaders_Init(); + static void R_Shaders_Shutdown(); + + static rvShader *ShaderList[200]; + static int NumShaders; + +protected: + char shaderName[256]; + char shaderParmNames[96][32]; + int shaderParmRegisters[96][4]; + int shaderParmNumRegisters[96]; + char textureParmNames[8][32]; + idImage * textureParmImages[8]; + int textureParmLocations[8]; + rvShader * shaderProgram; + bool invalidShader; +}; + +class rvGLSLShader : public rvShader { +public: + rvGLSLShader( unsigned int initTarget, const char *initName ) : rvShader( initTarget, initName ) {} + virtual ~rvGLSLShader(); + + virtual bool LoadProgram(); + virtual unsigned int GetVariablePosition( const char *variableName ); + virtual void Bind(); + virtual void SetTexture( int position, int unit, idImage *image ); + virtual void UnBindTexture( int position, int unit, idImage *image ); + virtual void UnBind(); + +private: + unsigned int vertexShader; + unsigned int fragmentShader; + unsigned int program; +}; + +class rvGLSLShaderStage : public rvNewShaderStage { +public: + rvGLSLShaderStage(); + virtual ~rvGLSLShaderStage(); + + virtual void Resolve(); + virtual bool IsSupported(); + virtual void BindShaderParameter( int slot, int numParms, const float *floatVector, int arraySize ); + +private: + int shaderParmLocations[96]; +}; + +void CalculateGaussianCoefficients( int width, int height, float multiplier ); +void CalculateGaussianCoefficients1D( int size, float multiplier, float deviation, + float *sampleOffsets, idVec4 *sampleWeights ); + +#endif diff --git a/src/renderer/VertexCache.h b/src/renderer/VertexCache.h new file mode 100644 index 0000000..e8d7e58 --- /dev/null +++ b/src/renderer/VertexCache.h @@ -0,0 +1,139 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// vertex cache calls should only be made by the front end + +const int NUM_VERTEX_FRAMES = 2; + +typedef enum { + TAG_FREE, + TAG_USED, + TAG_FIXED, // for the temp buffers + TAG_TEMP // in frame temp area, not static area +} vertBlockTag_t; + +typedef struct vertCache_s { + GLuint vbo; + void *virtMem; // only one of vbo / virtMem will be set + bool indexBuffer; // holds indexes instead of vertexes + + int offset; + int size; // may be larger than the amount asked for, due + // to round up and minimum fragment sizes + int tag; // a tag of 0 is a free block + struct vertCache_s ** user; // will be set to zero when purged + struct vertCache_s *next, *prev; // may be on the static list or one of the frame lists + int frameUsed; // it can't be purged if near the current frame +} vertCache_t; + + +class idVertexCache { +public: + void Init(); + void Shutdown(); + + // just for gfxinfo printing + bool IsFast(); + + // called when vertex programs are enabled or disabled, because + // the cached data is no longer valid + void PurgeAll(); + + // Tries to allocate space for the given data in fast vertex + // memory, and copies it over. + // Alloc does NOT do a touch, which allows purging of things + // created at level load time even if a frame hasn't passed yet. + // These allocations can be purged, which will zero the pointer. + void Alloc( void *data, int bytes, vertCache_t **buffer, bool indexBuffer = false ); + + // This will be a real pointer with virtual memory, + // but it will be an int offset cast to a pointer of ARB_vertex_buffer_object + void * Position( vertCache_t *buffer ); + + // if r_useIndexBuffers is enabled, but you need to draw something without + // an indexCache, this must be called to reset GL_ELEMENT_ARRAY_BUFFER_ARB + void UnbindIndex(); + + // automatically freed at the end of the next frame + // used for specular texture coordinates and gui drawing, which + // will change every frame. + // will return NULL if the vertex cache is completely full + // As with Position(), this may not actually be a pointer you can access. + vertCache_t * AllocFrameTemp( void *data, int bytes, unsigned int flags = 0 ); + + // notes that a buffer is used this frame, so it can't be purged + // out from under the GPU + void Touch( vertCache_t *buffer ); + + // this block won't have to zero a buffer pointer when it is purged, + // but it must still wait for the frames to pass, in case the GPU + // is still referencing it + void Free( vertCache_t *buffer ); + + // updates the counter for determining which temp space to use + // and which blocks can be purged + // Also prints debugging info when enabled + void EndFrame(); + + // listVertexCache calls this + void List(); + +private: + void InitMemoryBlocks( int size ); + void ActuallyFree( vertCache_t *block ); + + static idCVar r_showVertexCache; + static idCVar r_vertexBufferMegs; + + int staticCountTotal; + int staticAllocTotal; // for end of frame purging + + int staticAllocThisFrame; // debug counter + int staticCountThisFrame; + int dynamicAllocThisFrame; + int dynamicCountThisFrame; + + int currentFrame; // for purgable block tracking + int listNum; // currentFrame % NUM_VERTEX_FRAMES, determines which tempBuffers to use + + bool virtualMemory; // not fast stuff + + bool allocatingTempBuffer; // force GL_STREAM_DRAW_ARB + + vertCache_t *tempBuffers[NUM_VERTEX_FRAMES]; // allocated at startup + bool tempOverflow; // had to alloc a temp in static memory + + idBlockAlloc headerAllocator; + + vertCache_t freeStaticHeaders; // head of doubly linked list + vertCache_t freeDynamicHeaders; // head of doubly linked list + vertCache_t dynamicHeaders; // head of doubly linked list + vertCache_t deferredFreeList; // head of doubly linked list + vertCache_t staticHeaders; // head of doubly linked list in MRU order, + // staticHeaders.next is most recently used + + int frameBytes; // for each of NUM_VERTEX_FRAMES frames +}; + +extern idVertexCache vertexCache; diff --git a/src/renderer/cg_explicit.h b/src/renderer/cg_explicit.h new file mode 100644 index 0000000..c056875 --- /dev/null +++ b/src/renderer/cg_explicit.h @@ -0,0 +1,862 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef CG_EXTERNAL___H +#define CG_EXTERNAL___H + + + +typedef int CGbool; +typedef struct _CGcontext *CGcontext; +typedef struct _CGprogram *CGprogram; +typedef struct _CGparameter *CGparameter; + + +typedef enum +{ + CG_UNKNOWN_TYPE, + CG_STRUCT, + CG_ARRAY, + + CG_TYPE_START_ENUM = 1024, + + + CG_HALF , + CG_HALF2 , + CG_HALF3 , + CG_HALF4 , + CG_HALF1x1 , + CG_HALF1x2 , + CG_HALF1x3 , + CG_HALF1x4 , + CG_HALF2x1 , + CG_HALF2x2 , + CG_HALF2x3 , + CG_HALF2x4 , + CG_HALF3x1 , + CG_HALF3x2 , + CG_HALF3x3 , + CG_HALF3x4 , + CG_HALF4x1 , + CG_HALF4x2 , + CG_HALF4x3 , + CG_HALF4x4 , + CG_FLOAT , + CG_FLOAT2 , + CG_FLOAT3 , + CG_FLOAT4 , + CG_FLOAT1x1 , + CG_FLOAT1x2 , + CG_FLOAT1x3 , + CG_FLOAT1x4 , + CG_FLOAT2x1 , + CG_FLOAT2x2 , + CG_FLOAT2x3 , + CG_FLOAT2x4 , + CG_FLOAT3x1 , + CG_FLOAT3x2 , + CG_FLOAT3x3 , + CG_FLOAT3x4 , + CG_FLOAT4x1 , + CG_FLOAT4x2 , + CG_FLOAT4x3 , + CG_FLOAT4x4 , + CG_SAMPLER1D , + CG_SAMPLER2D , + CG_SAMPLER3D , + CG_SAMPLERRECT , + CG_SAMPLERCUBE , + CG_FIXED , + CG_FIXED2 , + CG_FIXED3 , + CG_FIXED4 , + CG_FIXED1x1 , + CG_FIXED1x2 , + CG_FIXED1x3 , + CG_FIXED1x4 , + CG_FIXED2x1 , + CG_FIXED2x2 , + CG_FIXED2x3 , + CG_FIXED2x4 , + CG_FIXED3x1 , + CG_FIXED3x2 , + CG_FIXED3x3 , + CG_FIXED3x4 , + CG_FIXED4x1 , + CG_FIXED4x2 , + CG_FIXED4x3 , + CG_FIXED4x4 , + CG_HALF1 , + CG_FLOAT1 , + CG_FIXED1 , + + +} CGtype; + +typedef enum +{ + + CG_TEXUNIT0 = 2048, + CG_TEXUNIT1 = 2049, + CG_TEXUNIT2 = 2050, + CG_TEXUNIT3 = 2051, + CG_TEXUNIT4 = 2052, + CG_TEXUNIT5 = 2053, + CG_TEXUNIT6 = 2054, + CG_TEXUNIT7 = 2055, + CG_TEXUNIT8 = 2056, + CG_TEXUNIT9 = 2057, + CG_TEXUNIT10 = 2058, + CG_TEXUNIT11 = 2059, + CG_TEXUNIT12 = 2060, + CG_TEXUNIT13 = 2061, + CG_TEXUNIT14 = 2062, + CG_TEXUNIT15 = 2063, + + CG_ATTR0 = 2113, + CG_ATTR1 = 2114, + CG_ATTR2 = 2115, + CG_ATTR3 = 2116, + CG_ATTR4 = 2117, + CG_ATTR5 = 2118, + CG_ATTR6 = 2119, + CG_ATTR7 = 2120, + CG_ATTR8 = 2121, + CG_ATTR9 = 2122, + CG_ATTR10 = 2123, + CG_ATTR11 = 2124, + CG_ATTR12 = 2125, + CG_ATTR13 = 2126, + CG_ATTR14 = 2127, + CG_ATTR15 = 2128, + + CG_C = 2178, + + CG_TEX0 = 2179, + CG_TEX1 = 2180, + CG_TEX2 = 2181, + CG_TEX3 = 2192, + CG_TEX4 = 2193, + CG_TEX5 = 2194, + CG_TEX6 = 2195, + CG_TEX7 = 2196, + + CG_HPOS = 2243, + CG_COL0 = 2245, + CG_COL1 = 2246, + CG_COL2 = 2247, + CG_COL3 = 2248, + CG_PSIZ = 2309, + CG_WPOS = 2373, + + CG_POSITION0 = 2437, + CG_POSITION1 = 2438, + CG_POSITION2 = 2439, + CG_POSITION3 = 2440, + CG_POSITION4 = 2441, + CG_POSITION5 = 2442, + CG_POSITION6 = 2443, + CG_POSITION7 = 2444, + CG_POSITION8 = 2445, + CG_POSITION9 = 2446, + CG_POSITION10 = 2447, + CG_POSITION11 = 2448, + CG_POSITION12 = 2449, + CG_POSITION13 = 2450, + CG_POSITION14 = 2451, + CG_POSITION15 = 2452, + CG_DIFFUSE0 = 2501, + CG_TANGENT0 = 2565, + CG_TANGENT1 = 2566, + CG_TANGENT2 = 2567, + CG_TANGENT3 = 2568, + CG_TANGENT4 = 2569, + CG_TANGENT5 = 2570, + CG_TANGENT6 = 2571, + CG_TANGENT7 = 2572, + CG_TANGENT8 = 2573, + CG_TANGENT9 = 2574, + CG_TANGENT10 = 2575, + CG_TANGENT11 = 2576, + CG_TANGENT12 = 2577, + CG_TANGENT13 = 2578, + CG_TANGENT14 = 2579, + CG_TANGENT15 = 2580, + CG_SPECULAR0 = 2629, + CG_BLENDINDICES0 = 2693, + CG_BLENDINDICES1 = 2694, + CG_BLENDINDICES2 = 2695, + CG_BLENDINDICES3 = 2696, + CG_BLENDINDICES4 = 2697, + CG_BLENDINDICES5 = 2698, + CG_BLENDINDICES6 = 2699, + CG_BLENDINDICES7 = 2700, + CG_BLENDINDICES8 = 2701, + CG_BLENDINDICES9 = 2702, + CG_BLENDINDICES10 = 2703, + CG_BLENDINDICES11 = 2704, + CG_BLENDINDICES12 = 2705, + CG_BLENDINDICES13 = 2706, + CG_BLENDINDICES14 = 2707, + CG_BLENDINDICES15 = 2708, + CG_COLOR0 = 2757, + CG_COLOR1 = 2758, + CG_COLOR2 = 2759, + CG_COLOR3 = 2760, + CG_COLOR4 = 2761, + CG_COLOR5 = 2762, + CG_COLOR6 = 2763, + CG_COLOR7 = 2764, + CG_COLOR8 = 2765, + CG_COLOR9 = 2766, + CG_COLOR10 = 2767, + CG_COLOR11 = 2768, + CG_COLOR12 = 2769, + CG_COLOR13 = 2770, + CG_COLOR14 = 2771, + CG_COLOR15 = 2772, + CG_PSIZE0 = 2821, + CG_PSIZE1 = 2822, + CG_PSIZE2 = 2823, + CG_PSIZE3 = 2824, + CG_PSIZE4 = 2825, + CG_PSIZE5 = 2826, + CG_PSIZE6 = 2827, + CG_PSIZE7 = 2828, + CG_PSIZE8 = 2829, + CG_PSIZE9 = 2830, + CG_PSIZE10 = 2831, + CG_PSIZE11 = 2832, + CG_PSIZE12 = 2833, + CG_PSIZE13 = 2834, + CG_PSIZE14 = 2835, + CG_PSIZE15 = 2836, + CG_BINORMAL0 = 2885, + CG_BINORMAL1 = 2886, + CG_BINORMAL2 = 2887, + CG_BINORMAL3 = 2888, + CG_BINORMAL4 = 2889, + CG_BINORMAL5 = 2890, + CG_BINORMAL6 = 2891, + CG_BINORMAL7 = 2892, + CG_BINORMAL8 = 2893, + CG_BINORMAL9 = 2894, + CG_BINORMAL10 = 2895, + CG_BINORMAL11 = 2896, + CG_BINORMAL12 = 2897, + CG_BINORMAL13 = 2898, + CG_BINORMAL14 = 2899, + CG_BINORMAL15 = 2900, + CG_FOG0 = 2917, + CG_FOG1 = 2918, + CG_FOG2 = 2919, + CG_FOG3 = 2920, + CG_FOG4 = 2921, + CG_FOG5 = 2922, + CG_FOG6 = 2923, + CG_FOG7 = 2924, + CG_FOG8 = 2925, + CG_FOG9 = 2926, + CG_FOG10 = 2927, + CG_FOG11 = 2928, + CG_FOG12 = 2929, + CG_FOG13 = 2930, + CG_FOG14 = 2931, + CG_FOG15 = 2932, + CG_DEPTH0 = 2933, + CG_DEPTH1 = 2934, + CG_DEPTH2 = 2935, + CG_DEPTH3 = 2936, + CG_DEPTH4 = 2937, + CG_DEPTH5 = 2938, + CG_DEPTH6 = 2939, + CG_DEPTH7 = 2940, + CG_DEPTH8 = 2941, + CG_DEPTH9 = 29542, + CG_DEPTH10 = 2943, + CG_DEPTH11 = 2944, + CG_DEPTH12 = 2945, + CG_DEPTH13 = 2946, + CG_DEPTH14 = 2947, + CG_DEPTH15 = 2948, + CG_SAMPLE0 = 2949, + CG_SAMPLE1 = 2950, + CG_SAMPLE2 = 2951, + CG_SAMPLE3 = 2952, + CG_SAMPLE4 = 2953, + CG_SAMPLE5 = 2954, + CG_SAMPLE6 = 2955, + CG_SAMPLE7 = 2956, + CG_SAMPLE8 = 2957, + CG_SAMPLE9 = 2958, + CG_SAMPLE10 = 2959, + CG_SAMPLE11 = 2960, + CG_SAMPLE12 = 2961, + CG_SAMPLE13 = 2962, + CG_SAMPLE14 = 2963, + CG_SAMPLE15 = 2964, + CG_BLENDWEIGHT0 = 3028, + CG_BLENDWEIGHT1 = 3029, + CG_BLENDWEIGHT2 = 3030, + CG_BLENDWEIGHT3 = 3031, + CG_BLENDWEIGHT4 = 3032, + CG_BLENDWEIGHT5 = 3033, + CG_BLENDWEIGHT6 = 3034, + CG_BLENDWEIGHT7 = 3035, + CG_BLENDWEIGHT8 = 3036, + CG_BLENDWEIGHT9 = 3037, + CG_BLENDWEIGHT10 = 3038, + CG_BLENDWEIGHT11 = 3039, + CG_BLENDWEIGHT12 = 3040, + CG_BLENDWEIGHT13 = 3041, + CG_BLENDWEIGHT14 = 3042, + CG_BLENDWEIGHT15 = 3043, + CG_NORMAL0 = 3092, + CG_NORMAL1 = 3093, + CG_NORMAL2 = 3094, + CG_NORMAL3 = 3095, + CG_NORMAL4 = 3096, + CG_NORMAL5 = 3097, + CG_NORMAL6 = 3098, + CG_NORMAL7 = 3099, + CG_NORMAL8 = 3100, + CG_NORMAL9 = 3101, + CG_NORMAL10 = 3102, + CG_NORMAL11 = 3103, + CG_NORMAL12 = 3104, + CG_NORMAL13 = 3105, + CG_NORMAL14 = 3106, + CG_NORMAL15 = 3107, + CG_FOGCOORD = 3156, + CG_TEXCOORD0 = 3220, + CG_TEXCOORD1 = 3221, + CG_TEXCOORD2 = 3222, + CG_TEXCOORD3 = 3223, + CG_TEXCOORD4 = 3224, + CG_TEXCOORD5 = 3225, + CG_TEXCOORD6 = 3226, + CG_TEXCOORD7 = 3227, + CG_TEXCOORD8 = 3228, + CG_TEXCOORD9 = 3229, + CG_TEXCOORD10 = 3230, + CG_TEXCOORD11 = 3231, + CG_TEXCOORD12 = 3232, + CG_TEXCOORD13 = 3233, + CG_TEXCOORD14 = 3234, + CG_TEXCOORD15 = 3235, + CG_COMBINER_CONST0 = 3284, + CG_COMBINER_CONST1 = 3285, + CG_COMBINER_STAGE_CONST0 = 3286, + CG_COMBINER_STAGE_CONST1 = 3287, + CG_OFFSET_TEXTURE_MATRIX = 3288, + CG_OFFSET_TEXTURE_SCALE = 3289, + CG_OFFSET_TEXTURE_BIAS = 3290, + CG_CONST_EYE = 3291, + CG_TESSFACTOR = 3255, + + + CG_UNDEFINED, + + } CGresource; + + typedef enum + { + CG_PROFILE_START = 6144, + CG_PROFILE_UNKNOWN, + + CG_PROFILE_VP20 = 6146, + CG_PROFILE_FP20 = 6147, + CG_PROFILE_VP30 = 6148, + CG_PROFILE_FP30 = 6149, + CG_PROFILE_ARBVP1 = 6150, + CG_PROFILE_ARBFP1 = 7000, + + + CG_PROFILE_VS_1_1 = 6153, + CG_PROFILE_VS_2_0 = 6154, + CG_PROFILE_VS_2_X = 6155, + + CG_PROFILE_PS_1_1 = 6159, + CG_PROFILE_PS_1_2 = 6160, + CG_PROFILE_PS_1_3 = 6161, + CG_PROFILE_PS_2_0 = 6162, + CG_PROFILE_PS_2_X = 6163, + + CG_PROFILE_MAX, + } CGprofile; + + typedef enum + { + + + CG_NO_ERROR = 0, + CG_COMPILER_ERROR = 1, + CG_INVALID_PARAMETER_ERROR = 2, + CG_INVALID_PROFILE_ERROR = 3, + CG_PROGRAM_LOAD_ERROR = 4, + CG_PROGRAM_BIND_ERROR = 5, + CG_PROGRAM_NOT_LOADED_ERROR = 6, + CG_UNSUPPORTED_GL_EXTENSION_ERROR = 7, + CG_INVALID_VALUE_TYPE_ERROR = 8, + CG_NOT_MATRIX_PARAM_ERROR = 9, + CG_INVALID_ENUMERANT_ERROR = 10, + CG_NOT_4x4_MATRIX_ERROR = 11, + CG_FILE_READ_ERROR = 12, + CG_FILE_WRITE_ERROR = 13, + CG_NVPARSE_ERROR = 14, + CG_MEMORY_ALLOC_ERROR = 15, + CG_INVALID_CONTEXT_HANDLE_ERROR = 16, + CG_INVALID_PROGRAM_HANDLE_ERROR = 17, + CG_INVALID_PARAM_HANDLE_ERROR = 18, + CG_UNKNOWN_PROFILE_ERROR = 19, + CG_VAR_ARG_ERROR = 20, + CG_INVALID_DIMENSION_ERROR = 21, + CG_ARRAY_PARAM_ERROR = 22, + CG_OUT_OF_ARRAY_BOUNDS_ERROR = 23, + } CGerror; + + typedef enum + { + CG_UNKNOWN = 4096, + CG_IN, + CG_OUT, + CG_INOUT, + CG_MIXED, + CG_VARYING, + CG_UNIFORM, + CG_CONSTANT, + CG_PROGRAM_SOURCE, + CG_PROGRAM_ENTRY, + CG_COMPILED_PROGRAM, + CG_PROGRAM_PROFILE, + + CG_GLOBAL, + CG_PROGRAM, + + CG_DEFAULT, + CG_ERROR, + + CG_SOURCE, + CG_OBJECT, + + } CGenum; + + + extern "C" { + + typedef void (*CGerrorCallbackFunc)(void); + + + + typedef CGcontext (*PFNCGCREATECONTEXTPROC)(void); + typedef void (*PFNCGDESTROYCONTEXTPROC)(CGcontext ctx); + typedef CGbool (*PFNCGISCONTEXTPROC)(CGcontext ctx); + typedef const char * (*PFNCGGETLASTLISTINGPROC)(CGcontext ctx); + typedef CGprogram (*PFNCGCREATEPROGRAMPROC)(CGcontext ctx, + CGenum program_type, + const char *program, + CGprofile profile, + const char *entry, + const char **args); + typedef CGprogram (*PFNCGCREATEPROGRAMFROMFILEPROC)(CGcontext ctx, + CGenum program_type, + const char *program_file, + CGprofile profile, + const char *entry, + const char **args); + typedef CGprogram (*PFNCGCOPYPROGRAMPROC)(CGprogram program); + typedef void (*PFNCGDESTROYPROGRAMPROC)(CGprogram program); + typedef CGprogram (*PFNCGGETFIRSTPROGRAMPROC)(CGcontext ctx); + typedef CGprogram (*PFNCGGETNEXTPROGRAMPROC)(CGprogram current); + typedef CGcontext (*PFNCGGETPROGRAMCONTEXTPROC)(CGprogram prog); + typedef CGbool (*PFNCGISPROGRAMPROC)(CGprogram program); + typedef void (*PFNCGCOMPILEPROGRAMPROC)(CGprogram program); + typedef CGbool (*PFNCGISPROGRAMCOMPILEDPROC)(CGprogram program); + typedef const char * (*PFNCGGETPROGRAMSTRINGPROC)(CGprogram prog, CGenum pname); + typedef CGprofile (*PFNCGGETPROGRAMPROFILEPROC)(CGprogram prog); + typedef CGparameter (*PFNCGGETNAMEDPARAMETERPROC)(CGprogram prog, const char *name); + typedef CGparameter (*PFNCGGETFIRSTPARAMETERPROC)(CGprogram prog, CGenum name_space); + typedef CGparameter (*PFNCGGETNEXTPARAMETERPROC)(CGparameter current); + typedef CGparameter (*PFNCGGETFIRSTLEAFPARAMETERPROC)(CGprogram prog, CGenum name_space); + typedef CGparameter (*PFNCGGETNEXTLEAFPARAMETERPROC)(CGparameter current); + typedef CGparameter (*PFNCGGETFIRSTSTRUCTPARAMETERPROC)(CGparameter param); + typedef CGparameter (*PFNCGGETFIRSTDEPENDENTPARAMETERPROC)(CGparameter param); + typedef CGparameter (*PFNCGGETARRAYPARAMETERPROC)(CGparameter aparam, int index); + typedef int (*PFNCGGETARRAYDIMENSIONPROC)(CGparameter param); + typedef int (*PFNCGGETARRAYSIZEPROC)(CGparameter param, int dimension); + typedef CGprogram (*PFNCGGETPARAMETERPROGRAMPROC)(CGparameter prog); + typedef CGbool (*PFNCGISPARAMETERPROC)(CGparameter param); + typedef const char * (*PFNCGGETPARAMETERNAMEPROC)(CGparameter param); + typedef CGtype (*PFNCGGETPARAMETERTYPEPROC)(CGparameter param); + typedef const char * (*PFNCGGETPARAMETERSEMANTICPROC)(CGparameter param); + typedef CGresource (*PFNCGGETPARAMETERRESOURCEPROC)(CGparameter param); + typedef CGresource (*PFNCGGETPARAMETERBASERESOURCEPROC)(CGparameter param); + typedef unsigned long (*PFNCGGETPARAMETERRESOURCEINDEXPROC)(CGparameter param); + typedef CGenum (*PFNCGGETPARAMETERVARIABILITYPROC)(CGparameter param); + typedef CGenum (*PFNCGGETPARAMETERDIRECTIONPROC)(CGparameter param); + typedef CGbool (*PFNCGISPARAMETERREFERENCEDPROC)(CGparameter param); + typedef void (*PFNCGGETPARAMETERVALUESPROC)(CGparameter param, + CGenum value_type, + int *nvalues); + typedef const char * (*PFNCGGETTYPESTRINGPROC)(CGtype type); + typedef CGtype (*PFNCGGETTYPEPROC)(const char *type_string); + typedef const char * (*PFNCGGETRESOURCESTRINGPROC)(CGresource resource); + typedef CGresource (*PFNCGGETRESOURCEPROC)(const char *resource_string); + typedef const char * (*PFNCGGETPROFILESTRINGPROC)(CGprofile profile); + typedef CGprofile (*PFNCGGETPROFILEPROC)(const char *profile_string); + typedef CGerror (*PFNCGGETERRORPROC)(void); + typedef const char * (*PFNCGGETERRORSTRINGPROC)(CGerror error); + typedef void (*PFNCGSETERRORCALLBACKPROC)(CGerrorCallbackFunc func); + typedef CGerrorCallbackFunc (*PFNCGGETERRORCALLBACKPROC)(void); + + + extern PFNCGCREATECONTEXTPROC cgCreateContext; + extern PFNCGDESTROYCONTEXTPROC cgDestroyContext; + extern PFNCGISCONTEXTPROC cgIsContext; + extern PFNCGGETLASTLISTINGPROC cgGetLastListing; + extern PFNCGCREATEPROGRAMPROC cgCreateProgram; + extern PFNCGCREATEPROGRAMFROMFILEPROC cgCreateProgramFromFile; + extern PFNCGCOPYPROGRAMPROC cgCopyProgram; + extern PFNCGDESTROYPROGRAMPROC cgDestroyProgram; + extern PFNCGGETFIRSTPROGRAMPROC cgGetFirstProgram; + extern PFNCGGETNEXTPROGRAMPROC cgGetNextProgram; + extern PFNCGGETPROGRAMCONTEXTPROC cgGetProgramContext; + extern PFNCGISPROGRAMPROC cgIsProgram; + extern PFNCGCOMPILEPROGRAMPROC cgCompileProgram; + extern PFNCGISPROGRAMCOMPILEDPROC cgIsProgramCompiled; + extern PFNCGGETPROGRAMSTRINGPROC cgGetProgramString; + extern PFNCGGETPROGRAMPROFILEPROC cgGetProgramProfile; + extern PFNCGGETNAMEDPARAMETERPROC cgGetNamedParameter; + extern PFNCGGETFIRSTPARAMETERPROC cgGetFirstParameter; + extern PFNCGGETNEXTPARAMETERPROC cgGetNextParameter; + extern PFNCGGETFIRSTLEAFPARAMETERPROC cgGetFirstLeafParameter; + extern PFNCGGETNEXTLEAFPARAMETERPROC cgGetNextLeafParameter; + extern PFNCGGETFIRSTSTRUCTPARAMETERPROC cgGetFirstStructParameter; + extern PFNCGGETFIRSTDEPENDENTPARAMETERPROC cgGetFirstDependentParameter; + extern PFNCGGETARRAYPARAMETERPROC cgGetArrayParameter; + extern PFNCGGETARRAYDIMENSIONPROC cgGetArrayDimension; + extern PFNCGGETARRAYSIZEPROC cgGetArraySize; + extern PFNCGGETPARAMETERPROGRAMPROC cgGetParameterProgram; + extern PFNCGISPARAMETERPROC cgIsParameter; + extern PFNCGGETPARAMETERNAMEPROC cgGetParameterName; + extern PFNCGGETPARAMETERTYPEPROC cgGetParameterType; + extern PFNCGGETPARAMETERSEMANTICPROC cgGetParameterSemantic; + extern PFNCGGETPARAMETERRESOURCEPROC cgGetParameterResource; + extern PFNCGGETPARAMETERBASERESOURCEPROC cgGetParameterBaseResource; + extern PFNCGGETPARAMETERRESOURCEINDEXPROC cgGetParameterResourceIndex; + extern PFNCGGETPARAMETERVARIABILITYPROC cgGetParameterVariability; + extern PFNCGGETPARAMETERDIRECTIONPROC cgGetParameterDirection; + extern PFNCGISPARAMETERREFERENCEDPROC cgIsParameterReferenced; + extern PFNCGGETPARAMETERVALUESPROC cgGetParameterValues; + extern PFNCGGETTYPESTRINGPROC cgGetTypeString; + extern PFNCGGETTYPEPROC cgGetType; + extern PFNCGGETRESOURCESTRINGPROC cgGetResourceString; + extern PFNCGGETRESOURCEPROC cgGetResource; + extern PFNCGGETPROFILESTRINGPROC cgGetProfileString; + extern PFNCGGETPROFILEPROC cgGetProfile; + extern PFNCGGETERRORPROC cgGetError; + extern PFNCGGETERRORSTRINGPROC cgGetErrorString; + extern PFNCGSETERRORCALLBACKPROC cgSetErrorCallback; + extern PFNCGGETERRORCALLBACKPROC cgGetErrorCallback; + + + } + + + + extern "C" { + + typedef enum + { + CG_GL_MATRIX_IDENTITY = 0, + CG_GL_MATRIX_TRANSPOSE = 1, + CG_GL_MATRIX_INVERSE = 2, + CG_GL_MATRIX_INVERSE_TRANSPOSE = 3, + + CG_GL_MODELVIEW_MATRIX, + CG_GL_PROJECTION_MATRIX, + CG_GL_TEXTURE_MATRIX, + CG_GL_MODELVIEW_PROJECTION_MATRIX, + + CG_GL_VERTEX, + CG_GL_FRAGMENT, + + } CGGLenum; + + + + + typedef CGbool (*PFNCGGLISPROFILESUPPORTEDPROC)(CGprofile profile); + typedef void (*PFNCGGLENABLEPROFILEPROC)(CGprofile profile); + typedef void (*PFNCGGLDISABLEPROFILEPROC)(CGprofile profile); + typedef CGprofile (*PFNCGGLGETLATESTPROFILEPROC)(CGGLenum profile_type); + typedef void (*PFNCGGLSETOPTIMALOPTIONSPROC)(CGprofile profile); + typedef void (*PFNCGGLLOADPROGRAMPROC)(CGprogram program); + typedef void (*PFNCGGLBINDPROGRAMPROC)(CGprogram program); + typedef void (*PFNCGGLSETPARAMETER1FPROC)(CGparameter param, + float x); + typedef void (*PFNCGGLSETPARAMETER2FPROC)(CGparameter param, + float x, + float y); + typedef void (*PFNCGGLSETPARAMETER3FPROC)(CGparameter param, + float x, + float y, + float z); + typedef void (*PFNCGGLSETPARAMETER4FPROC)(CGparameter param, + float x, + float y, + float z, + float w); + typedef void (*PFNCGGLSETPARAMETER1FVPROC)(CGparameter param, const float *v); + typedef void (*PFNCGGLSETPARAMETER2FVPROC)(CGparameter param, const float *v); + typedef void (*PFNCGGLSETPARAMETER3FVPROC)(CGparameter param, const float *v); + typedef void (*PFNCGGLSETPARAMETER4FVPROC)(CGparameter param, const float *v); + typedef void (*PFNCGGLSETPARAMETER1DPROC)(CGparameter param, + double x); + typedef void (*PFNCGGLSETPARAMETER2DPROC)(CGparameter param, + double x, + double y); + typedef void (*PFNCGGLSETPARAMETER3DPROC)(CGparameter param, + double x, + double y, + double z); + typedef void (*PFNCGGLSETPARAMETER4DPROC)(CGparameter param, + double x, + double y, + double z, + double w); + typedef void (*PFNCGGLSETPARAMETER1DVPROC)(CGparameter param, const double *v); + typedef void (*PFNCGGLSETPARAMETER2DVPROC)(CGparameter param, const double *v); + typedef void (*PFNCGGLSETPARAMETER3DVPROC)(CGparameter param, const double *v); + typedef void (*PFNCGGLSETPARAMETER4DVPROC)(CGparameter param, const double *v); + typedef void (*PFNCGGLSETPARAMETER4DVPROC)(CGparameter param, const double *v); + typedef void (*PFNCGGLGETPARAMETER1FPROC)(CGparameter param, float *v); + typedef void (*PFNCGGLGETPARAMETER2FPROC)(CGparameter param, float *v); + typedef void (*PFNCGGLGETPARAMETER3FPROC)(CGparameter param, float *v); + typedef void (*PFNCGGLGETPARAMETER4FPROC)(CGparameter param, float *v); + typedef void (*PFNCGGLGETPARAMETER1DPROC)(CGparameter param, double *v); + typedef void (*PFNCGGLGETPARAMETER2DPROC)(CGparameter param, double *v); + typedef void (*PFNCGGLGETPARAMETER3DPROC)(CGparameter param, double *v); + typedef void (*PFNCGGLGETPARAMETER4DPROC)(CGparameter param, double *v); + typedef void (*PFNCGGLSETPARAMETERARRAY1FPROC)(CGparameter param, + long offset, + long nelements, + const float *v); + typedef void (*PFNCGGLSETPARAMETERARRAY2FPROC)(CGparameter param, + long offset, + long nelements, + const float *v); + typedef void (*PFNCGGLSETPARAMETERARRAY3FPROC)(CGparameter param, + long offset, + long nelements, + const float *v); + typedef void (*PFNCGGLSETPARAMETERARRAY4FPROC)(CGparameter param, + long offset, + long nelements, + const float *v); + typedef void (*PFNCGGLSETPARAMETERARRAY1DPROC)(CGparameter param, + long offset, + long nelements, + const double *v); + typedef void (*PFNCGGLSETPARAMETERARRAY2DPROC)(CGparameter param, + long offset, + long nelements, + const double *v); + typedef void (*PFNCGGLSETPARAMETERARRAY3DPROC)(CGparameter param, + long offset, + long nelements, + const double *v); + typedef void (*PFNCGGLSETPARAMETERARRAY4DPROC)(CGparameter param, + long offset, + long nelements, + const double *v); + typedef void (*PFNCGGLGETPARAMETERARRAY1FPROC)(CGparameter param, + long offset, + long nelements, + float *v); + typedef void (*PFNCGGLGETPARAMETERARRAY2FPROC)(CGparameter param, + long offset, + long nelements, + float *v); + typedef void (*PFNCGGLGETPARAMETERARRAY3FPROC)(CGparameter param, + long offset, + long nelements, + float *v); + typedef void (*PFNCGGLGETPARAMETERARRAY4FPROC)(CGparameter param, + long offset, + long nelements, + float *v); + typedef void (*PFNCGGLGETPARAMETERARRAY1DPROC)(CGparameter param, + long offset, + long nelements, + double *v); + typedef void (*PFNCGGLGETPARAMETERARRAY2DPROC)(CGparameter param, + long offset, + long nelements, + double *v); + typedef void (*PFNCGGLGETPARAMETERARRAY3DPROC)(CGparameter param, + long offset, + long nelements, + double *v); + typedef void (*PFNCGGLGETPARAMETERARRAY4DPROC)(CGparameter param, + long offset, + long nelements, + double *v); + typedef void (*PFNCGGLSETPARAMETERPOINTERPROC)(CGparameter param, + GLint fsize, + GLenum type, + GLsizei stride, + GLvoid *pointer); + typedef void (*PFNCGGLENABLECLIENTSTATEPROC)(CGparameter param); + typedef void (*PFNCGGLDISABLECLIENTSTATEPROC)(CGparameter param); + typedef void (*PFNCGGLSETMATRIXPARAMETERDRPROC)(CGparameter param, const double *matrix); + typedef void (*PFNCGGLSETMATRIXPARAMETERFRPROC)(CGparameter param, const float *matrix); + typedef void (*PFNCGGLSETMATRIXPARAMETERDCPROC)(CGparameter param, const double *matrix); + typedef void (*PFNCGGLSETMATRIXPARAMETERFCPROC)(CGparameter param, const float *matrix); + typedef void (*PFNCGGLGETMATRIXPARAMETERDRPROC)(CGparameter param, double *matrix); + typedef void (*PFNCGGLGETMATRIXPARAMETERFRPROC)(CGparameter param, float *matrix); + typedef void (*PFNCGGLGETMATRIXPARAMETERDCPROC)(CGparameter param, double *matrix); + typedef void (*PFNCGGLGETMATRIXPARAMETERFCPROC)(CGparameter param, float *matrix); + typedef void (*PFNCGGLSETSTATEMATRIXPARAMETERPROC)(CGparameter param, + GLenum matrix, + GLenum transform); + typedef void (*PFNCGGLSETMATRIXPARAMETERARRAYFCPROC)(CGparameter param, + long offset, + long nelements, + const float *matrices); + typedef void (*PFNCGGLSETMATRIXPARAMETERARRAYFRPROC)(CGparameter param, + long offset, + long nelements, + const float *matrices); + typedef void (*PFNCGGLSETMATRIXPARAMETERARRAYDCPROC)(CGparameter param, + long offset, + long nelements, + const double *matrices); + typedef void (*PFNCGGLSETMATRIXPARAMETERARRAYDRPROC)(CGparameter param, + long offset, + long nelements, + const double *matrices); + typedef void (*PFNCGGLGETMATRIXPARAMETERARRAYFCPROC)(CGparameter param, + long offset, + long nelements, + float *matrices); + typedef void (*PFNCGGLGETMATRIXPARAMETERARRAYFRPROC)(CGparameter param, + long offset, + long nelements, + float *matrices); + typedef void (*PFNCGGLGETMATRIXPARAMETERARRAYDCPROC)(CGparameter param, + long offset, + long nelements, + double *matrices); + typedef void (*PFNCGGLGETMATRIXPARAMETERARRAYDRPROC)(CGparameter param, + long offset, + long nelements, + double *matrices); + typedef void (*PFNCGGLSETTEXTUREPARAMETERPROC)(CGparameter param, GLuint texobj); + typedef GLuint (*PFNCGGLGETTEXTUREPARAMETERPROC)(CGparameter param); + typedef void (*PFNCGGLENABLETEXTUREPARAMETERPROC)(CGparameter param); + typedef void (*PFNCGGLDISABLETEXTUREPARAMETERPROC)(CGparameter param); + typedef GLenum (*PFNCGGLGETTEXTUREENUMPROC)(CGparameter param); + + + extern PFNCGGLISPROFILESUPPORTEDPROC cgGLIsProfileSupported; + extern PFNCGGLENABLEPROFILEPROC cgGLEnableProfile; + extern PFNCGGLDISABLEPROFILEPROC cgGLDisableProfile; + extern PFNCGGLGETLATESTPROFILEPROC cgGLGetLatestProfile; + extern PFNCGGLSETOPTIMALOPTIONSPROC cgGLSetOptimalOptions; + extern PFNCGGLLOADPROGRAMPROC cgGLLoadProgram; + extern PFNCGGLBINDPROGRAMPROC cgGLBindProgram; + extern PFNCGGLSETPARAMETER1FPROC cgGLSetParameter1f; + extern PFNCGGLSETPARAMETER2FPROC cgGLSetParameter2f; + extern PFNCGGLSETPARAMETER3FPROC cgGLSetParameter3f; + extern PFNCGGLSETPARAMETER4FPROC cgGLSetParameter4f; + extern PFNCGGLSETPARAMETER1FVPROC cgGLSetParameter1fv; + extern PFNCGGLSETPARAMETER2FVPROC cgGLSetParameter2fv; + extern PFNCGGLSETPARAMETER3FVPROC cgGLSetParameter3fv; + extern PFNCGGLSETPARAMETER4FVPROC cgGLSetParameter4fv; + extern PFNCGGLSETPARAMETER1DPROC cgGLSetParameter1d; + extern PFNCGGLSETPARAMETER2DPROC cgGLSetParameter2d; + extern PFNCGGLSETPARAMETER3DPROC cgGLSetParameter3d; + extern PFNCGGLSETPARAMETER4DPROC cgGLSetParameter4d; + extern PFNCGGLSETPARAMETER1DVPROC cgGLSetParameter1dv; + extern PFNCGGLSETPARAMETER2DVPROC cgGLSetParameter2dv; + extern PFNCGGLSETPARAMETER3DVPROC cgGLSetParameter3dv; + extern PFNCGGLSETPARAMETER4DVPROC cgGLSetParameter4dv; + extern PFNCGGLGETPARAMETER1FPROC cgGLGetParameter1f; + extern PFNCGGLGETPARAMETER2FPROC cgGLGetParameter2f; + extern PFNCGGLGETPARAMETER3FPROC cgGLGetParameter3f; + extern PFNCGGLGETPARAMETER4FPROC cgGLGetParameter4f; + extern PFNCGGLGETPARAMETER1DPROC cgGLGetParameter1d; + extern PFNCGGLGETPARAMETER2DPROC cgGLGetParameter2d; + extern PFNCGGLGETPARAMETER3DPROC cgGLGetParameter3d; + extern PFNCGGLGETPARAMETER4DPROC cgGLGetParameter4d; + extern PFNCGGLSETPARAMETERARRAY1FPROC cgGLSetParameterArray1f; + extern PFNCGGLSETPARAMETERARRAY2FPROC cgGLSetParameterArray2f; + extern PFNCGGLSETPARAMETERARRAY3FPROC cgGLSetParameterArray3f; + extern PFNCGGLSETPARAMETERARRAY4FPROC cgGLSetParameterArray4f; + extern PFNCGGLSETPARAMETERARRAY1DPROC cgGLSetParameterArray1d; + extern PFNCGGLSETPARAMETERARRAY2DPROC cgGLSetParameterArray2d; + extern PFNCGGLSETPARAMETERARRAY3DPROC cgGLSetParameterArray3d; + extern PFNCGGLSETPARAMETERARRAY4DPROC cgGLSetParameterArray4d; + extern PFNCGGLGETPARAMETERARRAY1FPROC cgGLGetParameterArray1f; + extern PFNCGGLGETPARAMETERARRAY2FPROC cgGLGetParameterArray2f; + extern PFNCGGLGETPARAMETERARRAY3FPROC cgGLGetParameterArray3f; + extern PFNCGGLGETPARAMETERARRAY4FPROC cgGLGetParameterArray4f; + extern PFNCGGLGETPARAMETERARRAY1DPROC cgGLGetParameterArray1d; + extern PFNCGGLGETPARAMETERARRAY2DPROC cgGLGetParameterArray2d; + extern PFNCGGLGETPARAMETERARRAY3DPROC cgGLGetParameterArray3d; + extern PFNCGGLGETPARAMETERARRAY4DPROC cgGLGetParameterArray4d; + extern PFNCGGLSETPARAMETERPOINTERPROC cgGLSetParameterPointer; + extern PFNCGGLENABLECLIENTSTATEPROC cgGLEnableClientState; + extern PFNCGGLDISABLECLIENTSTATEPROC cgGLDisableClientState; + extern PFNCGGLSETMATRIXPARAMETERDRPROC cgGLSetMatrixParameterdr; + extern PFNCGGLSETMATRIXPARAMETERFRPROC cgGLSetMatrixParameterfr; + extern PFNCGGLSETMATRIXPARAMETERDCPROC cgGLSetMatrixParameterdc; + extern PFNCGGLSETMATRIXPARAMETERFCPROC cgGLSetMatrixParameterfc; + extern PFNCGGLGETMATRIXPARAMETERDRPROC cgGLGetMatrixParameterdr; + extern PFNCGGLGETMATRIXPARAMETERFRPROC cgGLGetMatrixParameterfr; + extern PFNCGGLGETMATRIXPARAMETERDCPROC cgGLGetMatrixParameterdc; + extern PFNCGGLGETMATRIXPARAMETERFCPROC cgGLGetMatrixParameterfc; + extern PFNCGGLSETSTATEMATRIXPARAMETERPROC cgGLSetStateMatrixParameter; + extern PFNCGGLSETMATRIXPARAMETERARRAYFCPROC cgGLSetMatrixParameterArrayfc; + extern PFNCGGLSETMATRIXPARAMETERARRAYFRPROC cgGLSetMatrixParameterArrayfr; + extern PFNCGGLSETMATRIXPARAMETERARRAYDCPROC cgGLSetMatrixParameterArraydc; + extern PFNCGGLSETMATRIXPARAMETERARRAYDRPROC cgGLSetMatrixParameterArraydr; + extern PFNCGGLGETMATRIXPARAMETERARRAYFCPROC cgGLGetMatrixParameterArrayfc; + extern PFNCGGLGETMATRIXPARAMETERARRAYFRPROC cgGLGetMatrixParameterArrayfr; + extern PFNCGGLGETMATRIXPARAMETERARRAYDCPROC cgGLGetMatrixParameterArraydc; + extern PFNCGGLGETMATRIXPARAMETERARRAYDRPROC cgGLGetMatrixParameterArraydr; + extern PFNCGGLSETTEXTUREPARAMETERPROC cgGLSetTextureParameter; + extern PFNCGGLGETTEXTUREPARAMETERPROC cgGLGetTextureParameter; + extern PFNCGGLENABLETEXTUREPARAMETERPROC cgGLEnableTextureParameter; + extern PFNCGGLDISABLETEXTUREPARAMETERPROC cgGLDisableTextureParameter; + extern PFNCGGLGETTEXTUREENUMPROC cgGLGetTextureEnum; + + +} + +bool init_explicit_Cg(); + +#endif + diff --git a/src/renderer/cinematic.cpp b/src/renderer/cinematic.cpp new file mode 100644 index 0000000..46b4439 --- /dev/null +++ b/src/renderer/cinematic.cpp @@ -0,0 +1,1837 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#define JPEG_INTERNALS +extern "C" { +#include "jpeg-6/jpeglib.h" +} + +#include "tr_local.h" + +#define CIN_system 1 +#define CIN_loop 2 +#define CIN_hold 4 +#define CIN_silent 8 +#define CIN_shader 16 + +class idCinematicLocal : public idCinematic { +public: + idCinematicLocal(); + virtual ~idCinematicLocal(); + + virtual bool InitFromFile( const char *qpath, bool looping, int options = SUPPORT_DEFAULT ); + virtual cinData_t ImageForTime( int milliseconds ); + virtual int AnimationLength(); + virtual void Close(); + virtual void ResetTime(int time); + +private: + unsigned int mcomp[256]; + byte ** qStatus[2]; + idStr fileName; + int CIN_WIDTH, CIN_HEIGHT; + idFile * iFile; + cinStatus_t status; + long tfps; + long RoQPlayed; + long ROQSize; + unsigned int RoQFrameSize; + long onQuad; + long numQuads; + long samplesPerLine; + unsigned int roq_id; + long screenDelta; + byte * buf; + long samplesPerPixel; // defaults to 2 + unsigned int xsize, ysize, maxsize, minsize; + long normalBuffer0; + long roq_flags; + long roqF0; + long roqF1; + long t[2]; + long roqFPS; + long drawX, drawY; + + int animationLength; + int startTime; + float frameRate; + + byte * image; + + bool looping; + bool dirty; + bool half; + bool smootheddouble; + bool inMemory; + + void RoQ_init( void ); + void blitVQQuad32fs( byte **status, unsigned char *data ); + void RoQShutdown( void ); + void RoQInterrupt(void); + + void move8_32( byte *src, byte *dst, int spl ); + void move4_32( byte *src, byte *dst, int spl ); + void blit8_32( byte *src, byte *dst, int spl ); + void blit4_32( byte *src, byte *dst, int spl ); + void blit2_32( byte *src, byte *dst, int spl ); + + unsigned short yuv_to_rgb( long y, long u, long v ); + unsigned int yuv_to_rgb24( long y, long u, long v ); + + void decodeCodeBook( byte *input, unsigned short roq_flags ); + void recurseQuad( long startX, long startY, long quadSize, long xOff, long yOff ); + void setupQuad( long xOff, long yOff ); + void readQuadInfo( byte *qData ); + void RoQPrepMcomp( long xoff, long yoff ); + void RoQReset(); +}; + +idCinematic::idCinematic() { + PIMPL = NULL; + mode = 2; +} + +const int DEFAULT_CIN_WIDTH = 512; +const int DEFAULT_CIN_HEIGHT = 512; +const int MAXSIZE = 8; +const int MINSIZE = 4; + +const int ROQ_FILE = 0x1084; +const int ROQ_QUAD = 0x1000; +const int ROQ_QUAD_INFO = 0x1001; +const int ROQ_CODEBOOK = 0x1002; +const int ROQ_QUAD_VQ = 0x1011; +const int ROQ_QUAD_JPEG = 0x1012; +const int ROQ_QUAD_HANG = 0x1013; +const int ROQ_PACKET = 0x1030; +const int ZA_SOUND_MONO = 0x1020; +const int ZA_SOUND_STEREO = 0x1021; + +// temporary buffers used by all cinematics +static long ROQ_YY_tab[256]; +static long ROQ_UB_tab[256]; +static long ROQ_UG_tab[256]; +static long ROQ_VG_tab[256]; +static long ROQ_VR_tab[256]; +static byte * file = NULL; +static unsigned short * vq2 = NULL; +static unsigned short * vq4 = NULL; +static unsigned short * vq8 = NULL; + + + +//=========================================== + +/* +============== +idCinematicLocal::InitCinematic +============== +*/ +void idCinematic::InitCinematic( void ) { + float t_ub,t_vr,t_ug,t_vg; + long i; + + // generate YUV tables + t_ub = (1.77200f/2.0f) * (float)(1<<6) + 0.5f; + t_vr = (1.40200f/2.0f) * (float)(1<<6) + 0.5f; + t_ug = (0.34414f/2.0f) * (float)(1<<6) + 0.5f; + t_vg = (0.71414f/2.0f) * (float)(1<<6) + 0.5f; + for( i = 0; i < 256; i++ ) { + float x = (float)(2 * i - 255); + + ROQ_UB_tab[i] = (long)( ( t_ub * x) + (1<<5)); + ROQ_VR_tab[i] = (long)( ( t_vr * x) + (1<<5)); + ROQ_UG_tab[i] = (long)( (-t_ug * x) ); + ROQ_VG_tab[i] = (long)( (-t_vg * x) + (1<<5)); + ROQ_YY_tab[i] = (long)( (i << 6) | (i >> 2) ); + } + + file = (byte *)Mem_Alloc( 65536 ); + vq2 = (word *)Mem_Alloc( 256*16*4 * sizeof( word ) ); + vq4 = (word *)Mem_Alloc( 256*64*4 * sizeof( word ) ); + vq8 = (word *)Mem_Alloc( 256*256*4 * sizeof( word ) ); +} + +/* +============== +idCinematicLocal::ShutdownCinematic +============== +*/ +void idCinematic::ShutdownCinematic( void ) { + Mem_Free( file ); + file = NULL; + Mem_Free( vq2 ); + vq2 = NULL; + Mem_Free( vq4 ); + vq4 = NULL; + Mem_Free( vq8 ); + vq8 = NULL; +} + +/* +============== +idCinematicLocal::Alloc +============== +*/ +idCinematic *idCinematic::Alloc() { + return new idCinematic; +} + +/* +============== +idCinematicLocal::~idCinematic +============== +*/ +idCinematic::~idCinematic( ) { + Close(); + delete PIMPL; + PIMPL = NULL; +} + +/* +============== +idCinematicLocal::InitFromFile +============== +*/ +bool idCinematic::InitFromFile( const char *qpath, bool looping, int options ) { + Close(); + + // The retail executable selects a private decoder from the extension. + // WMV playback depended on the original Windows Media SDK; the shipped + // startup and material cinematics are RoQ and use the in-tree decoder. + if ( idStr::FindText( qpath, ".wmv", false ) >= 0 ) { + delete PIMPL; + PIMPL = NULL; + mode = 0; + return false; + } + + delete PIMPL; + PIMPL = new idCinematicLocal; + mode = 1; + return PIMPL->InitFromFile( qpath, looping, options ); +} + +/* +============== +idCinematicLocal::AnimationLength +============== +*/ +int idCinematic::AnimationLength() { + return PIMPL ? PIMPL->AnimationLength() : 0; +} + +/* +============== +idCinematicLocal::ResetTime +============== +*/ +void idCinematic::ResetTime(int milliseconds) { + if ( PIMPL ) { + PIMPL->ResetTime( milliseconds ); + } +} + +/* +============== +idCinematicLocal::ImageForTime +============== +*/ +cinData_t idCinematic::ImageForTime( int milliseconds ) { + if ( PIMPL ) { + return PIMPL->ImageForTime( milliseconds ); + } + cinData_t data; + memset( &data, 0, sizeof( data ) ); + return data; +} + +/* +============== +idCinematicLocal::Close +============== +*/ +void idCinematic::Close() { + if ( PIMPL ) { + PIMPL->Close(); + } + mode = 2; +} + +void idCinematic::Draw() { + if ( PIMPL ) { + PIMPL->Draw(); + } +} + +void idCinematic::SetScreenRect( int left, int right, int bottom, int top ) { + if ( PIMPL ) { + PIMPL->SetScreenRect( left, right, bottom, top ); + } +} + +void idCinematic::GetScreenRect( int &left, int &right, int &bottom, int &top ) { + if ( PIMPL ) { + PIMPL->GetScreenRect( left, right, bottom, top ); + } else { + left = right = bottom = top = 0; + } +} + +bool idCinematic::IsPlaying() { + return PIMPL ? PIMPL->IsPlaying() : false; +} + +//=========================================== + +/* +============== +idCinematicLocal::idCinematicLocal +============== +*/ +idCinematicLocal::idCinematicLocal() { + image = NULL; + status = FMV_EOF; + buf = NULL; + iFile = NULL; + + qStatus[0] = (byte **)Mem_Alloc( 32768 * sizeof( byte *) ); + qStatus[1] = (byte **)Mem_Alloc( 32768 * sizeof( byte *) ); +} + +/* +============== +idCinematicLocal::~idCinematicLocal +============== +*/ +idCinematicLocal::~idCinematicLocal() { + Close(); + + Mem_Free( qStatus[0] ); + qStatus[0] = NULL; + Mem_Free( qStatus[1] ); + qStatus[1] = NULL; +} + +/* +============== +idCinematicLocal::InitFromFile +============== +*/ +bool idCinematicLocal::InitFromFile( const char *qpath, bool amilooping, int options ) { + unsigned short RoQID; + (void)options; + + Close(); + + inMemory = 0; + animationLength = 100000; + + if ( strstr( qpath, "/" ) == NULL && strstr( qpath, "\\" ) == NULL ) { + fileName = va( "video/%s", qpath ); + } else { + fileName = qpath; + } + fileName.BackSlashesToSlashes(); + if ( idStr::Icmp( fileName.Right( 4 ), ".roq" ) != 0 ) { + fileName += ".roq"; + } + + iFile = fileSystem->OpenFileRead( fileName ); + + if ( !iFile ) { + return false; + } + + ROQSize = iFile->Length(); + + looping = amilooping; + + CIN_HEIGHT = DEFAULT_CIN_HEIGHT; + CIN_WIDTH = DEFAULT_CIN_WIDTH; + samplesPerPixel = 4; + startTime = 0; //Sys_Milliseconds(); + buf = NULL; + + iFile->Read( file, 16 ); + + RoQID = (unsigned short)(file[0]) + (unsigned short)(file[1])*256; + + frameRate = file[6]; + if ( frameRate == 32.0f ) { + frameRate = 1000.0f / 32.0f; + } + + if ( RoQID == ROQ_FILE ) { + RoQ_init(); + status = FMV_PLAY; + ImageForTime( 0 ); + status = ( looping ) ? FMV_PLAY : FMV_IDLE; + return true; + } + + RoQShutdown(); + return false; +} + +/* +============== +idCinematicLocal::Close +============== +*/ +void idCinematicLocal::Close() { + if ( image ) { + Mem_Free( (void *)image ); + image = NULL; + buf = NULL; + status = FMV_EOF; + } + RoQShutdown(); +} + +/* +============== +idCinematicLocal::AnimationLength +============== +*/ +int idCinematicLocal::AnimationLength() { + return animationLength; +} + +/* +============== +idCinematicLocal::ResetTime +============== +*/ +void idCinematicLocal::ResetTime(int time) { + // Quake 4 deliberately defers the new epoch until the next + // ImageForTime call. GUI scripts reset cinematics while the front end is + // assembling a view, so using the previous backend view time here can put + // a freshly-reset movie ahead of (or behind) its GUI timeline. + startTime = -1; + status = FMV_PLAY; +} + +/* +============== +idCinematicLocal::ImageForTime +============== +*/ +cinData_t idCinematicLocal::ImageForTime( int thisTime ) { + cinData_t cinData; + + if ( thisTime < 0 ) { + thisTime = 0; + } + + memset( &cinData, 0, sizeof(cinData) ); + + if ( r_skipROQ.GetBool() ) { + return cinData; + } + + if ( status == FMV_EOF || status == FMV_IDLE ) { + return cinData; + } + + if ( buf == NULL || startTime == -1 ) { + if ( startTime == -1 ) { + RoQReset(); + } + startTime = thisTime; + } + + tfps = ( ( thisTime - startTime ) * frameRate ) / 1000; + + if ( tfps < 0 ) { + tfps = 0; + } + + if ( tfps < numQuads ) { + RoQReset(); + buf = NULL; + status = FMV_PLAY; + } + + if ( buf == NULL ) { + while( buf == NULL ) { + RoQInterrupt(); + } + } else { + while( (tfps != numQuads && status == FMV_PLAY) ) { + RoQInterrupt(); + } + } + + if ( status == FMV_LOOPED ) { + status = FMV_PLAY; + while( buf == NULL && status == FMV_PLAY ) { + RoQInterrupt(); + } + startTime = thisTime; + } + + if ( status == FMV_EOF ) { + if ( looping ) { + RoQReset(); + buf = NULL; + if ( status == FMV_LOOPED ) { + status = FMV_PLAY; + } + while ( buf == NULL && status == FMV_PLAY ) { + RoQInterrupt(); + } + startTime = thisTime; + } else { + status = FMV_IDLE; + RoQShutdown(); + } + } + + cinData.imageWidth = CIN_WIDTH; + cinData.imageHeight = CIN_HEIGHT; + cinData.status = status; + cinData.image = buf; + + return cinData; +} + +/* +============== +idCinematicLocal::move8_32 +============== +*/ +void idCinematicLocal::move8_32( byte *src, byte *dst, int spl ) { +#if 1 + int *dsrc, *ddst; + int dspl; + + dsrc = (int *)src; + ddst = (int *)dst; + dspl = spl>>2; + + ddst[0*dspl+0] = dsrc[0*dspl+0]; + ddst[0*dspl+1] = dsrc[0*dspl+1]; + ddst[0*dspl+2] = dsrc[0*dspl+2]; + ddst[0*dspl+3] = dsrc[0*dspl+3]; + ddst[0*dspl+4] = dsrc[0*dspl+4]; + ddst[0*dspl+5] = dsrc[0*dspl+5]; + ddst[0*dspl+6] = dsrc[0*dspl+6]; + ddst[0*dspl+7] = dsrc[0*dspl+7]; + + ddst[1*dspl+0] = dsrc[1*dspl+0]; + ddst[1*dspl+1] = dsrc[1*dspl+1]; + ddst[1*dspl+2] = dsrc[1*dspl+2]; + ddst[1*dspl+3] = dsrc[1*dspl+3]; + ddst[1*dspl+4] = dsrc[1*dspl+4]; + ddst[1*dspl+5] = dsrc[1*dspl+5]; + ddst[1*dspl+6] = dsrc[1*dspl+6]; + ddst[1*dspl+7] = dsrc[1*dspl+7]; + + ddst[2*dspl+0] = dsrc[2*dspl+0]; + ddst[2*dspl+1] = dsrc[2*dspl+1]; + ddst[2*dspl+2] = dsrc[2*dspl+2]; + ddst[2*dspl+3] = dsrc[2*dspl+3]; + ddst[2*dspl+4] = dsrc[2*dspl+4]; + ddst[2*dspl+5] = dsrc[2*dspl+5]; + ddst[2*dspl+6] = dsrc[2*dspl+6]; + ddst[2*dspl+7] = dsrc[2*dspl+7]; + + ddst[3*dspl+0] = dsrc[3*dspl+0]; + ddst[3*dspl+1] = dsrc[3*dspl+1]; + ddst[3*dspl+2] = dsrc[3*dspl+2]; + ddst[3*dspl+3] = dsrc[3*dspl+3]; + ddst[3*dspl+4] = dsrc[3*dspl+4]; + ddst[3*dspl+5] = dsrc[3*dspl+5]; + ddst[3*dspl+6] = dsrc[3*dspl+6]; + ddst[3*dspl+7] = dsrc[3*dspl+7]; + + ddst[4*dspl+0] = dsrc[4*dspl+0]; + ddst[4*dspl+1] = dsrc[4*dspl+1]; + ddst[4*dspl+2] = dsrc[4*dspl+2]; + ddst[4*dspl+3] = dsrc[4*dspl+3]; + ddst[4*dspl+4] = dsrc[4*dspl+4]; + ddst[4*dspl+5] = dsrc[4*dspl+5]; + ddst[4*dspl+6] = dsrc[4*dspl+6]; + ddst[4*dspl+7] = dsrc[4*dspl+7]; + + ddst[5*dspl+0] = dsrc[5*dspl+0]; + ddst[5*dspl+1] = dsrc[5*dspl+1]; + ddst[5*dspl+2] = dsrc[5*dspl+2]; + ddst[5*dspl+3] = dsrc[5*dspl+3]; + ddst[5*dspl+4] = dsrc[5*dspl+4]; + ddst[5*dspl+5] = dsrc[5*dspl+5]; + ddst[5*dspl+6] = dsrc[5*dspl+6]; + ddst[5*dspl+7] = dsrc[5*dspl+7]; + + ddst[6*dspl+0] = dsrc[6*dspl+0]; + ddst[6*dspl+1] = dsrc[6*dspl+1]; + ddst[6*dspl+2] = dsrc[6*dspl+2]; + ddst[6*dspl+3] = dsrc[6*dspl+3]; + ddst[6*dspl+4] = dsrc[6*dspl+4]; + ddst[6*dspl+5] = dsrc[6*dspl+5]; + ddst[6*dspl+6] = dsrc[6*dspl+6]; + ddst[6*dspl+7] = dsrc[6*dspl+7]; + + ddst[7*dspl+0] = dsrc[7*dspl+0]; + ddst[7*dspl+1] = dsrc[7*dspl+1]; + ddst[7*dspl+2] = dsrc[7*dspl+2]; + ddst[7*dspl+3] = dsrc[7*dspl+3]; + ddst[7*dspl+4] = dsrc[7*dspl+4]; + ddst[7*dspl+5] = dsrc[7*dspl+5]; + ddst[7*dspl+6] = dsrc[7*dspl+6]; + ddst[7*dspl+7] = dsrc[7*dspl+7]; +#else + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +#endif +} + +/* +============== +idCinematicLocal::move4_32 +============== +*/ +void idCinematicLocal::move4_32( byte *src, byte *dst, int spl ) { +#if 1 + int *dsrc, *ddst; + int dspl; + + dsrc = (int *)src; + ddst = (int *)dst; + dspl = spl>>2; + + ddst[0*dspl+0] = dsrc[0*dspl+0]; + ddst[0*dspl+1] = dsrc[0*dspl+1]; + ddst[0*dspl+2] = dsrc[0*dspl+2]; + ddst[0*dspl+3] = dsrc[0*dspl+3]; + + ddst[1*dspl+0] = dsrc[1*dspl+0]; + ddst[1*dspl+1] = dsrc[1*dspl+1]; + ddst[1*dspl+2] = dsrc[1*dspl+2]; + ddst[1*dspl+3] = dsrc[1*dspl+3]; + + ddst[2*dspl+0] = dsrc[2*dspl+0]; + ddst[2*dspl+1] = dsrc[2*dspl+1]; + ddst[2*dspl+2] = dsrc[2*dspl+2]; + ddst[2*dspl+3] = dsrc[2*dspl+3]; + + ddst[3*dspl+0] = dsrc[3*dspl+0]; + ddst[3*dspl+1] = dsrc[3*dspl+1]; + ddst[3*dspl+2] = dsrc[3*dspl+2]; + ddst[3*dspl+3] = dsrc[3*dspl+3]; +#else + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +#endif +} + +/* +============== +idCinematicLocal::blit8_32 +============== +*/ +void idCinematicLocal::blit8_32( byte *src, byte *dst, int spl ) { +#if 1 + int *dsrc, *ddst; + int dspl; + + dsrc = (int *)src; + ddst = (int *)dst; + dspl = spl>>2; + + ddst[0*dspl+0] = dsrc[ 0]; + ddst[0*dspl+1] = dsrc[ 1]; + ddst[0*dspl+2] = dsrc[ 2]; + ddst[0*dspl+3] = dsrc[ 3]; + ddst[0*dspl+4] = dsrc[ 4]; + ddst[0*dspl+5] = dsrc[ 5]; + ddst[0*dspl+6] = dsrc[ 6]; + ddst[0*dspl+7] = dsrc[ 7]; + + ddst[1*dspl+0] = dsrc[ 8]; + ddst[1*dspl+1] = dsrc[ 9]; + ddst[1*dspl+2] = dsrc[10]; + ddst[1*dspl+3] = dsrc[11]; + ddst[1*dspl+4] = dsrc[12]; + ddst[1*dspl+5] = dsrc[13]; + ddst[1*dspl+6] = dsrc[14]; + ddst[1*dspl+7] = dsrc[15]; + + ddst[2*dspl+0] = dsrc[16]; + ddst[2*dspl+1] = dsrc[17]; + ddst[2*dspl+2] = dsrc[18]; + ddst[2*dspl+3] = dsrc[19]; + ddst[2*dspl+4] = dsrc[20]; + ddst[2*dspl+5] = dsrc[21]; + ddst[2*dspl+6] = dsrc[22]; + ddst[2*dspl+7] = dsrc[23]; + + ddst[3*dspl+0] = dsrc[24]; + ddst[3*dspl+1] = dsrc[25]; + ddst[3*dspl+2] = dsrc[26]; + ddst[3*dspl+3] = dsrc[27]; + ddst[3*dspl+4] = dsrc[28]; + ddst[3*dspl+5] = dsrc[29]; + ddst[3*dspl+6] = dsrc[30]; + ddst[3*dspl+7] = dsrc[31]; + + ddst[4*dspl+0] = dsrc[32]; + ddst[4*dspl+1] = dsrc[33]; + ddst[4*dspl+2] = dsrc[34]; + ddst[4*dspl+3] = dsrc[35]; + ddst[4*dspl+4] = dsrc[36]; + ddst[4*dspl+5] = dsrc[37]; + ddst[4*dspl+6] = dsrc[38]; + ddst[4*dspl+7] = dsrc[39]; + + ddst[5*dspl+0] = dsrc[40]; + ddst[5*dspl+1] = dsrc[41]; + ddst[5*dspl+2] = dsrc[42]; + ddst[5*dspl+3] = dsrc[43]; + ddst[5*dspl+4] = dsrc[44]; + ddst[5*dspl+5] = dsrc[45]; + ddst[5*dspl+6] = dsrc[46]; + ddst[5*dspl+7] = dsrc[47]; + + ddst[6*dspl+0] = dsrc[48]; + ddst[6*dspl+1] = dsrc[49]; + ddst[6*dspl+2] = dsrc[50]; + ddst[6*dspl+3] = dsrc[51]; + ddst[6*dspl+4] = dsrc[52]; + ddst[6*dspl+5] = dsrc[53]; + ddst[6*dspl+6] = dsrc[54]; + ddst[6*dspl+7] = dsrc[55]; + + ddst[7*dspl+0] = dsrc[56]; + ddst[7*dspl+1] = dsrc[57]; + ddst[7*dspl+2] = dsrc[58]; + ddst[7*dspl+3] = dsrc[59]; + ddst[7*dspl+4] = dsrc[60]; + ddst[7*dspl+5] = dsrc[61]; + ddst[7*dspl+6] = dsrc[62]; + ddst[7*dspl+7] = dsrc[63]; +#else + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +#endif +} + +/* +============== +idCinematicLocal::blit4_32 +============== +*/ +void idCinematicLocal::blit4_32( byte *src, byte *dst, int spl ) { +#if 1 + int *dsrc, *ddst; + int dspl; + + dsrc = (int *)src; + ddst = (int *)dst; + dspl = spl>>2; + + ddst[0*dspl+0] = dsrc[ 0]; + ddst[0*dspl+1] = dsrc[ 1]; + ddst[0*dspl+2] = dsrc[ 2]; + ddst[0*dspl+3] = dsrc[ 3]; + ddst[1*dspl+0] = dsrc[ 4]; + ddst[1*dspl+1] = dsrc[ 5]; + ddst[1*dspl+2] = dsrc[ 6]; + ddst[1*dspl+3] = dsrc[ 7]; + ddst[2*dspl+0] = dsrc[ 8]; + ddst[2*dspl+1] = dsrc[ 9]; + ddst[2*dspl+2] = dsrc[10]; + ddst[2*dspl+3] = dsrc[11]; + ddst[3*dspl+0] = dsrc[12]; + ddst[3*dspl+1] = dsrc[13]; + ddst[3*dspl+2] = dsrc[14]; + ddst[3*dspl+3] = dsrc[15]; +#else + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +#endif +} + +/* +============== +idCinematicLocal::blit2_32 +============== +*/ +void idCinematicLocal::blit2_32( byte *src, byte *dst, int spl ) { +#if 1 + int *dsrc, *ddst; + int dspl; + + dsrc = (int *)src; + ddst = (int *)dst; + dspl = spl>>2; + + ddst[0*dspl+0] = dsrc[0]; + ddst[0*dspl+1] = dsrc[1]; + ddst[1*dspl+0] = dsrc[2]; + ddst[1*dspl+1] = dsrc[3]; +#else + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; + ddst[dspl] = dsrc[1]; +#endif +} + +/* +============== +idCinematicLocal::blitVQQuad32fs +============== +*/ +void idCinematicLocal::blitVQQuad32fs( byte **status, unsigned char *data ) { + unsigned short newd, celdata, code; + unsigned int index, i; + + newd = 0; + celdata = 0; + index = 0; + + do { + if (!newd) { + newd = 7; + celdata = data[0] + data[1]*256; + data += 2; + } else { + newd--; + } + + code = (unsigned short)(celdata&0xc000); + celdata <<= 2; + + switch (code) { + case 0x8000: // vq code + blit8_32( (byte *)&vq8[(*data)*128], status[index], samplesPerLine ); + data++; + index += 5; + break; + case 0xc000: // drop + index++; // skip 8x8 + for(i=0;i<4;i++) { + if (!newd) { + newd = 7; + celdata = data[0] + data[1]*256; + data += 2; + } else { + newd--; + } + + code = (unsigned short)(celdata&0xc000); celdata <<= 2; + + switch (code) { // code in top two bits of code + case 0x8000: // 4x4 vq code + blit4_32( (byte *)&vq4[(*data)*32], status[index], samplesPerLine ); + data++; + break; + case 0xc000: // 2x2 vq code + blit2_32( (byte *)&vq2[(*data)*8], status[index], samplesPerLine ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+8, samplesPerLine ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+samplesPerLine*2, samplesPerLine ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+samplesPerLine*2+8, samplesPerLine ); + data++; + break; + case 0x4000: // motion compensation + move4_32( status[index] + mcomp[(*data)], status[index], samplesPerLine ); + data++; + break; + } + index++; + } + break; + case 0x4000: // motion compensation + move8_32( status[index] + mcomp[(*data)], status[index], samplesPerLine ); + data++; + index += 5; + break; + case 0x0000: + index += 5; + break; + } + } while ( status[index] != NULL ); +} + +#define VQ2TO4(a,b,c,d) { \ + *c++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *c++ = a[1]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *c++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *c++ = b[1]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + a += 2; b += 2; } + +#define VQ2TO2(a,b,c,d) { \ + *c++ = *a; \ + *d++ = *a; \ + *d++ = *a; \ + *c++ = *b; \ + *d++ = *b; \ + *d++ = *b; \ + *d++ = *a; \ + *d++ = *a; \ + *d++ = *b; \ + *d++ = *b; \ + a++; b++; } + +/* +============== +idCinematicLocal::yuv_to_rgb +============== +*/ +unsigned short idCinematicLocal::yuv_to_rgb( long y, long u, long v ) { + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 9; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; + b = (YY + ROQ_UB_tab[u]) >> 9; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31; + + return (unsigned short)((r<<11)+(g<<5)+(b)); +} + +/* +============== +idCinematicLocal::yuv_to_rgb24 +============== +*/ +unsigned int idCinematicLocal::yuv_to_rgb24( long y, long u, long v ) { + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + + return LittleLong((r)+(g<<8)+(b<<16)); +} + +/* +============== +idCinematicLocal::decodeCodeBook +============== +*/ +void idCinematicLocal::decodeCodeBook( byte *input, unsigned short roq_flags ) { + long i, j, two, four; + unsigned short *aptr, *bptr, *cptr, *dptr; + long y0,y1,y2,y3,cr,cb; + unsigned int *iaptr, *ibptr, *icptr, *idptr; + + if (!roq_flags) { + two = four = 256; + } else { + two = roq_flags>>8; + if (!two) two = 256; + four = roq_flags&0xff; + } + + four *= 2; + + bptr = (unsigned short *)vq2; + + if (!half) { + if (!smootheddouble) { +// +// normal height +// + if (samplesPerPixel==2) { + for(i=0;i CIN_WIDTH) bigx = CIN_WIDTH; + if (bigy > CIN_HEIGHT) bigy = CIN_HEIGHT; + + if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) { + useY = startY; + scroff = image + (useY+((CIN_HEIGHT-bigy)>>1)+yOff)*(samplesPerLine) + (((startX+xOff))*samplesPerPixel); + + qStatus[0][onQuad ] = scroff; + qStatus[1][onQuad++] = scroff+offset; + } + + if ( quadSize != MINSIZE ) { + quadSize >>= 1; + recurseQuad( startX, startY , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY , quadSize, xOff, yOff ); + recurseQuad( startX, startY+quadSize , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff ); + } +} + +/* +============== +idCinematicLocal::setupQuad +============== +*/ +void idCinematicLocal::setupQuad( long xOff, long yOff ) { + long numQuadCels, i,x,y; + byte *temp; + + numQuadCels = (CIN_WIDTH*CIN_HEIGHT) / (16); + numQuadCels += numQuadCels/4 + numQuadCels/16; + numQuadCels += 64; // for overflow + + numQuadCels = (xsize*ysize) / (16); + numQuadCels += numQuadCels/4; + numQuadCels += 64; // for overflow + + onQuad = 0; + + for(y=0;y<(long)ysize;y+=16) + for(x=0;x<(long)xsize;x+=16) + recurseQuad( x, y, 16, xOff, yOff ); + + temp = NULL; + + for(i=(numQuadCels-64);iSeek( 0, FS_SEEK_SET ); + iFile->Read( file, 16 ); + RoQ_init(); + status = FMV_LOOPED; +} + + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + byte *infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ + int memsize; +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 32768 /* choose an efficiently fread'able size */ + +/* jpeg error handling */ +struct jpeg_error_mgr jerr; + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + + +METHODDEF boolean fill_input_buffer( j_decompress_ptr cinfo ) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + int nbytes; + + nbytes = INPUT_BUF_SIZE; + if (nbytes > src->memsize) nbytes = src->memsize; + if (nbytes == 0) { + /* Insert a fake EOI marker */ + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + } else { + memcpy( src->buffer, src->infile, INPUT_BUF_SIZE ); + src->infile = src->infile + nbytes; + src->memsize = src->memsize - INPUT_BUF_SIZE; + } + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + + return TRUE; +} +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + + +METHODDEF void init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF void +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + src->infile = src->infile + num_bytes; + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_source (j_decompress_ptr cinfo) +{ + cinfo = cinfo; + /* no work necessary here */ +} + +GLOBAL void +jpeg_memory_src (j_decompress_ptr cinfo, byte *infile, int size) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * sizeof(JOCTET)); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->memsize = size; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} + +int JPEGBlit( byte *wStatus, byte *data, int datasize ) +{ + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We set up the normal JPEG error routines, then override error_exit. */ + cinfo.err = jpeg_std_error(&jerr); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_memory_src(&cinfo, data, datasize); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + cinfo.dct_method = JDCT_IFAST; + cinfo.dct_method = JDCT_FASTEST; + cinfo.dither_mode = JDITHER_NONE; + cinfo.do_fancy_upsampling = FALSE; +// cinfo.out_color_space = JCS_GRAYSCALE; + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + /* Make a one-row-high sample array that will go away when done with image */ + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + + wStatus += (cinfo.output_height-1)*row_stride; + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + (void) jpeg_read_scanlines(&cinfo, &buffer[0], 1); + + /* Assume put_scanline_someplace wants a pointer and sample count. */ + memcpy( wStatus, &buffer[0][0], row_stride ); + /* + int x; + unsigned int *buf = (unsigned int *)&buffer[0][0]; + unsigned int *out = (unsigned int *)wStatus; + for(x=0;xRead( file, RoQFrameSize+8 ); + if ( RoQPlayed >= ROQSize ) { + if (looping) { + RoQReset(); + } else { + status = FMV_EOF; + } + return; + } + + framedata = file; +// +// new frame is ready +// +redump: + switch(roq_id) + { + case ROQ_QUAD_VQ: + if ((numQuads&1)) { + normalBuffer0 = t[1]; + RoQPrepMcomp( roqF0, roqF1 ); + blitVQQuad32fs( qStatus[1], framedata); + buf = image + screenDelta; + } else { + normalBuffer0 = t[0]; + RoQPrepMcomp( roqF0, roqF1 ); + blitVQQuad32fs( qStatus[0], framedata ); + buf = image; + } + if (numQuads == 0) { // first frame + memcpy(image+screenDelta, image, samplesPerLine*ysize); + } + numQuads++; + dirty = true; + break; + case ROQ_CODEBOOK: + decodeCodeBook( framedata, (unsigned short)roq_flags ); + break; + case ZA_SOUND_MONO: + break; + case ZA_SOUND_STEREO: + break; + case ROQ_QUAD_INFO: + if (numQuads == -1) { + readQuadInfo( framedata ); + setupQuad( 0, 0 ); + } + if (numQuads != 1) numQuads = 0; + break; + case ROQ_PACKET: + inMemory = ( roq_flags != 0 ); + RoQFrameSize = 0; // for header + break; + case ROQ_QUAD_HANG: + RoQFrameSize = 0; + break; + case ROQ_QUAD_JPEG: + if (!numQuads) { + normalBuffer0 = t[0]; + JPEGBlit( image, framedata, RoQFrameSize ); + memcpy(image+screenDelta, image, samplesPerLine*ysize); + numQuads++; + } + break; + default: + status = FMV_EOF; + break; + } +// +// read in next frame data +// + if ( RoQPlayed >= ROQSize ) { + if (looping) { + RoQReset(); + } else { + status = FMV_EOF; + } + return; + } + + framedata += RoQFrameSize; + roq_id = framedata[0] + framedata[1]*256; + RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536; + roq_flags = framedata[6] + framedata[7]*256; + roqF0 = (char)framedata[7]; + roqF1 = (char)framedata[6]; + + if (RoQFrameSize>65536||roq_id==0x1084) { + common->DPrintf("roq_size>65536||roq_id==0x1084\n"); + status = FMV_EOF; + if (looping) { + RoQReset(); + } + return; + } + if (inMemory && (status != FMV_EOF)) { + inMemory = false; framedata += 8; goto redump; + } +// +// one more frame hits the dust +// +// assert(RoQFrameSize <= 65536); +// r = Sys_StreamedRead( file, RoQFrameSize+8, 1, iFile ); + RoQPlayed += RoQFrameSize+8; +} + +/* +============== +idCinematicLocal::RoQ_init +============== +*/ +void idCinematicLocal::RoQ_init( void ) { + + RoQPlayed = 24; + + /* get frame rate */ + roqFPS = file[ 6] + file[ 7]*256; + + if (!roqFPS) roqFPS = 30; + + numQuads = -1; + + roq_id = file[ 8] + file[ 9]*256; + RoQFrameSize= file[10] + file[11]*256 + file[12]*65536; + roq_flags = file[14] + file[15]*256; +} + +/* +============== +idCinematicLocal::RoQShutdown +============== +*/ +void idCinematicLocal::RoQShutdown( void ) { + if ( status == FMV_IDLE ) { + return; + } + status = FMV_IDLE; + + if ( iFile ) { + fileSystem->CloseFile( iFile ); + iFile = NULL; + } + + fileName = ""; +} + +//=========================================== + +/* +============== +idSndWindow::InitFromFile +============== +*/ +bool idSndWindow::InitFromFile( const char *qpath, bool looping, int options ) { + (void)looping; + (void)options; + idStr fname = qpath; + + fname.ToLower(); + if ( !fname.Icmp( "waveform" ) ) { + showWaveform = true; + } else { + showWaveform = false; + } + return true; +} + +/* +============== +idSndWindow::ImageForTime +============== +*/ +cinData_t idSndWindow::ImageForTime( int milliseconds ) { + return soundSystem->ImageForTime( milliseconds, showWaveform ); +} + +/* +============== +idSndWindow::AnimationLength +============== +*/ +int idSndWindow::AnimationLength() { + return -1; +} diff --git a/src/renderer/draw_arb.cpp b/src/renderer/draw_arb.cpp new file mode 100644 index 0000000..cd87b72 --- /dev/null +++ b/src/renderer/draw_arb.cpp @@ -0,0 +1,530 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* + + with standard calls, we can't do bump mapping or vertex colors with + shader colors + + 2 texture units: + +falloff +-- +light cube +bump +-- +light projection +diffuse + + + 3 texture units: + +light cube +bump +-- +falloff +light projection +diffuse + + + 5 texture units: + +light cube +bump +falloff +light projection +diffuse + +*/ + +/* +================== +RB_ARB_DrawInteraction + +backEnd.vLight + +backEnd.depthFunc must be equal for alpha tested surfaces to work right, +it is set to lessThan for blended transparent surfaces + +================== +*/ +static void RB_ARB_DrawInteraction( const drawInteraction_t *din ) { + const drawSurf_t *surf = din->surf; + const srfTriangles_t *tri = din->surf->geo; + + // set the vertex arrays, which may not all be enabled on a given pass + idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->st ); + + //----------------------------------------------------- + // + // bump / falloff + // + //----------------------------------------------------- + // render light falloff * bumpmap lighting + + // + // draw light falloff to the alpha channel + // + GL_State( GLS_COLORMASK | GLS_DEPTHMASK | backEnd.depthFunc ); + GL_PolygonOffset( surf->material, true ); + + qglColor3f( 1, 1, 1 ); + GL_DisableVertexAttribState( 8 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, din->lightProjection[3].ToFloatPtr() ); + qglTexCoord2f( 0, 0.5 ); + +// ATI R100 can't do partial texgens +#define NO_MIXED_TEXGEN + +#ifdef NO_MIXED_TEXGEN +idVec4 plane; +plane[0] = 0; +plane[1] = 0; +plane[2] = 0; +plane[3] = 0.5; +qglEnable( GL_TEXTURE_GEN_T ); +qglTexGenfv( GL_T, GL_OBJECT_PLANE, plane.ToFloatPtr() ); + +plane[0] = 0; +plane[1] = 0; +plane[2] = 0; +plane[3] = 1; +qglEnable( GL_TEXTURE_GEN_Q ); +qglTexGenfv( GL_Q, GL_OBJECT_PLANE, plane.ToFloatPtr() ); + +#endif + + din->lightFalloffImage->Bind(); + + // draw it + RB_DrawElementsWithCounters( tri ); + + qglDisable( GL_TEXTURE_GEN_S ); +#ifdef NO_MIXED_TEXGEN +qglDisable( GL_TEXTURE_GEN_T ); +qglDisable( GL_TEXTURE_GEN_Q ); +#endif + +#if 0 +GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO | GLS_DEPTHMASK + | backEnd.depthFunc ); +// the texccords are the non-normalized vector towards the light origin +GL_SelectTexture( 0 ); +globalImages->normalCubeMapImage->Bind(); +qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +qglTexCoordPointer( 3, GL_FLOAT, sizeof( lightingCache_t ), ((lightingCache_t *)vertexCache.Position(tri->lightingCache))->localLightVector.ToFloatPtr() ); +// draw it +RB_DrawElementsWithCounters( tri ); +return; +#endif + + // we can't do bump mapping with standard calls, so skip it + if ( glConfig.envDot3Available && glConfig.cubeMapAvailable ) { + // + // draw the bump map result onto the alpha channel + // + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ZERO | GLS_COLORMASK | GLS_DEPTHMASK + | backEnd.depthFunc ); + + // texture 0 will be the per-surface bump map + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); +// FIXME: matrix work! RB_BindStageTexture( surfaceRegs, &surfaceStage->texture, surf ); + din->bumpImage->Bind(); + + // texture 1 is the normalization cube map + // the texccords are the non-normalized vector towards the light origin + GL_SelectTexture( 1 ); + if ( din->ambientLight ) { + globalImages->ambientNormalMap->Bind(); // fixed value + } else { + globalImages->normalCubeMapImage->Bind(); + } + GL_EnableVertexAttribState( 16 ); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( lightingCache_t ), ((lightingCache_t *)vertexCache.Position(tri->lightingCache))->localLightVector.ToFloatPtr() ); + + // I just want alpha = Dot( texture0, texture1 ) + GL_TexEnv( GL_COMBINE_ARB ); + + qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGBA_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); + qglTexEnvi( GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1 ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + GL_TexEnv( GL_MODULATE ); + + globalImages->BindNull(); + GL_DisableVertexAttribState( 16 ); + + GL_SelectTexture( 0 ); +// RB_FinishStageTexture( &surfaceStage->texture, surf ); + } + + //----------------------------------------------------- + // + // projected light / surface color for diffuse maps + // + //----------------------------------------------------- + // don't trash alpha + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_ALPHAMASK | GLS_DEPTHMASK + | backEnd.depthFunc ); + + // texture 0 will get the surface color texture + GL_SelectTexture( 0 ); + vertexCache.Position( tri->ambientCache ); + + // select the vertex color source + if ( din->vertexColor == SVC_IGNORE ) { + qglColor4fv( din->diffuseColor.ToFloatPtr() ); + } else { + // FIXME: does this not get diffuseColor blended in? + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + GL_EnableVertexAttribState( 4 ); + + if ( din->vertexColor == SVC_INVERSE_MODULATE ) { + GL_TexEnv( GL_COMBINE_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_ONE_MINUS_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); + } + } + + GL_EnableVertexAttribState( 8 ); + // FIXME: does this not get the texture matrix? +// RB_BindStageTexture( surfaceRegs, &surfaceStage->texture, surf ); + din->diffuseImage->Bind(); + + // texture 1 will get the light projected texture + GL_SelectTexture( 1 ); + GL_DisableVertexAttribState( 16 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_Q ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, din->lightProjection[0].ToFloatPtr() ); + qglTexGenfv( GL_T, GL_OBJECT_PLANE, din->lightProjection[1].ToFloatPtr() ); + qglTexGenfv( GL_Q, GL_OBJECT_PLANE, din->lightProjection[2].ToFloatPtr() ); + + din->lightImage->Bind(); + + // draw it + RB_DrawElementsWithCounters( tri ); + + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_Q ); + + globalImages->BindNull(); + GL_SelectTexture( 0 ); + + if ( din->vertexColor != SVC_IGNORE ) { + GL_DisableVertexAttribState( 4 ); + GL_TexEnv( GL_MODULATE ); + } + +// RB_FinishStageTexture( &surfaceStage->texture, surf ); + GL_PolygonOffset( surf->material, false ); +} + +/* +================== +RB_ARB_DrawThreeTextureInteraction + +Used by radeon R100 and Intel graphics parts + +backEnd.vLight + +backEnd.depthFunc must be equal for alpha tested surfaces to work right, +it is set to lessThan for blended transparent surfaces + +================== +*/ +static void RB_ARB_DrawThreeTextureInteraction( const drawInteraction_t *din ) { + const drawSurf_t *surf = din->surf; + const srfTriangles_t *tri = din->surf->geo; + + // set the vertex arrays, which may not all be enabled on a given pass + idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->st ); + qglColor3f( 1, 1, 1 ); + + // + // bump map dot cubeMap into the alpha channel + // + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO | GLS_COLORMASK | GLS_DEPTHMASK + | backEnd.depthFunc ); + GL_PolygonOffset( surf->material, true ); + + // texture 0 will be the per-surface bump map + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); +// FIXME: matrix work! RB_BindStageTexture( surfaceRegs, &surfaceStage->texture, surf ); + din->bumpImage->Bind(); + + // texture 1 is the normalization cube map + // the texccords are the non-normalized vector towards the light origin + GL_SelectTexture( 1 ); + if ( din->ambientLight ) { + globalImages->ambientNormalMap->Bind(); // fixed value + } else { + globalImages->normalCubeMapImage->Bind(); + } + GL_EnableVertexAttribState( 16 ); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( lightingCache_t ), ((lightingCache_t *)vertexCache.Position(tri->lightingCache))->localLightVector.ToFloatPtr() ); + + // I just want alpha = Dot( texture0, texture1 ) + GL_TexEnv( GL_COMBINE_ARB ); + + qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_DOT3_RGBA_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); + qglTexEnvi( GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1 ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + GL_TexEnv( GL_MODULATE ); + + globalImages->BindNull(); + GL_DisableVertexAttribState( 16 ); + + GL_SelectTexture( 0 ); +// RB_FinishStageTexture( &surfaceStage->texture, surf ); + + + //----------------------------------------------------- + // + // light falloff / projected light / surface color for diffuse maps + // + //----------------------------------------------------- + // multiply result by alpha, but don't trash alpha + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_ALPHAMASK | GLS_DEPTHMASK + | backEnd.depthFunc ); + + // texture 0 will get the surface color texture + GL_SelectTexture( 0 ); + vertexCache.Position( tri->ambientCache ); + + // select the vertex color source + if ( din->vertexColor == SVC_IGNORE ) { + qglColor4fv( din->diffuseColor.ToFloatPtr() ); + } else { + // FIXME: does this not get diffuseColor blended in? + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + GL_EnableVertexAttribState( 4 ); + + if ( din->vertexColor == SVC_INVERSE_MODULATE ) { + GL_TexEnv( GL_COMBINE_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_ONE_MINUS_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); + } + } + + GL_EnableVertexAttribState( 8 ); + // FIXME: does this not get the texture matrix? +// RB_BindStageTexture( surfaceRegs, &surfaceStage->texture, surf ); + din->diffuseImage->Bind(); + + // texture 1 will get the light projected texture + GL_SelectTexture( 1 ); + GL_DisableVertexAttribState( 16 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_Q ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, din->lightProjection[0].ToFloatPtr() ); + qglTexGenfv( GL_T, GL_OBJECT_PLANE, din->lightProjection[1].ToFloatPtr() ); + qglTexGenfv( GL_Q, GL_OBJECT_PLANE, din->lightProjection[2].ToFloatPtr() ); + din->lightImage->Bind(); + + // texture 2 will get the light falloff texture + GL_SelectTexture( 2 ); + GL_DisableVertexAttribState( 32 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_Q ); + + qglTexGenfv( GL_S, GL_OBJECT_PLANE, din->lightProjection[3].ToFloatPtr() ); + + idVec4 plane; + plane[0] = 0; + plane[1] = 0; + plane[2] = 0; + plane[3] = 0.5; + qglTexGenfv( GL_T, GL_OBJECT_PLANE, plane.ToFloatPtr() ); + + plane[0] = 0; + plane[1] = 0; + plane[2] = 0; + plane[3] = 1; + qglTexGenfv( GL_Q, GL_OBJECT_PLANE, plane.ToFloatPtr() ); + + din->lightFalloffImage->Bind(); + + // draw it + RB_DrawElementsWithCounters( tri ); + + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_Q ); + globalImages->BindNull(); + + GL_SelectTexture( 1 ); + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_Q ); + globalImages->BindNull(); + + GL_SelectTexture( 0 ); + + if ( din->vertexColor != SVC_IGNORE ) { + GL_DisableVertexAttribState( 4 ); + GL_TexEnv( GL_MODULATE ); + } + +// RB_FinishStageTexture( &surfaceStage->texture, surf ); + GL_PolygonOffset( surf->material, false ); +} + + +/* +================== +RB_CreateDrawInteractions +================== +*/ +static void RB_CreateDrawInteractions( const drawSurf_t *surf ) { + if ( !surf ) { + return; + } + + // force a space calculation + backEnd.currentSpace = NULL; + + if ( r_useTripleTextureARB.GetBool() && glConfig.maxTextureUnits >= 3 ) { + for ( ; surf ; surf = surf->nextOnLight ) { + // break it up into multiple primitive draw interactions if necessary + RB_CreateSingleDrawInteractions( surf, RB_ARB_DrawThreeTextureInteraction ); + } + } else { + for ( ; surf ; surf = surf->nextOnLight ) { + // break it up into multiple primitive draw interactions if necessary + RB_CreateSingleDrawInteractions( surf, RB_ARB_DrawInteraction ); + } + } +} + + + +/* +================== +RB_RenderViewLight + +================== +*/ +static void RB_RenderViewLight( viewLight_t *vLight ) { + backEnd.vLight = vLight; + + // do fogging later + if ( vLight->lightShader->IsFogLight() ) { + return; + } + if ( vLight->lightShader->IsBlendLight() ) { + return; + } + + RB_LogComment( "---------- RB_RenderViewLight 0x%p ----------\n", vLight ); + + // clear the stencil buffer if needed + if ( vLight->globalShadows || vLight->localShadows ) { + backEnd.currentScissor = vLight->scissorRect; + if ( r_useScissor.GetBool() ) { + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + qglClear( GL_STENCIL_BUFFER_BIT ); + } else { + // no shadows, so no need to read or write the stencil buffer + // we might in theory want to use GL_ALWAYS instead of disabling + // completely, to satisfy the invarience rules + qglStencilFunc( GL_ALWAYS, 128, 255 ); + } + + backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; + RB_StencilShadowPass( vLight->globalShadows ); + RB_CreateDrawInteractions( vLight->localInteractions ); + RB_StencilShadowPass( vLight->localShadows ); + RB_CreateDrawInteractions( vLight->globalInteractions ); + + if ( r_skipTranslucent.GetBool() ) { + return; + } + + // disable stencil testing for translucent interactions, because + // the shadow isn't calculated at their point, and the shadow + // behind them may be depth fighting with a back side, so there + // isn't any reasonable thing to do + qglStencilFunc( GL_ALWAYS, 128, 255 ); + backEnd.depthFunc = GLS_DEPTHFUNC_LESS; + RB_CreateDrawInteractions( vLight->translucentInteractions ); +} + + +/* +================== +RB_ARB_DrawInteractions +================== +*/ +void RB_ARB_DrawInteractions( void ) { + qglEnable( GL_STENCIL_TEST ); + + for ( viewLight_t *vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + RB_RenderViewLight( vLight ); + } +} diff --git a/src/renderer/draw_arb2.cpp b/src/renderer/draw_arb2.cpp new file mode 100644 index 0000000..9f38004 --- /dev/null +++ b/src/renderer/draw_arb2.cpp @@ -0,0 +1,868 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" + +#include "cg_explicit.h" + +CGcontext cg_context; + +static const int texDimArray[7] = { 2, 0, 0, 0, 0, 0, 0 }; +rvVertexFormat supportedMD5RVertexFormats[3] = { + rvVertexFormat( 0x4BD, 3, 3, 4, texDimArray ), + rvVertexFormat( 0x4F5, 3, 0, 1, texDimArray ), + rvVertexFormat( 0x4F1, 4, 0, 1, texDimArray ) +}; +rvVertexFormat supportedMD5RDepthVertexFormats[3] = { + rvVertexFormat( 0x0D, 3, 3, 4, NULL ), + rvVertexFormat( 0x05, 3, 0, 1, NULL ), + rvVertexFormat( 0x01, 4, 0, 1, NULL ) +}; +rvVertexFormat supportedMD5RShadowVolVertexFormats[3] = { + rvVertexFormat( 0x0D, 4, 3, 4, NULL ), + rvVertexFormat( 0x05, 3, 0, 1, NULL ), + rvVertexFormat( 0x01, 4, 0, 1, NULL ) +}; +idMat4 colorMatrix; + +/* +========================================================================================= + +GENERAL INTERACTION RENDERING + +========================================================================================= +*/ + +/* +==================== +GL_SelectTextureNoClient +==================== +*/ +static __forceinline void GL_SelectTextureNoClient( int unit ) { + backEnd.glState.currenttmu = unit; + qglActiveTextureARB( GL_TEXTURE0_ARB + unit ); + RB_LogComment( "glActiveTextureARB( %i )\n", unit ); +} + +void RB_ARB2_LoadMVPMatrixIntoVPParams( const drawSurf_t *surf ) { + if ( backEnd.mvpSpace != surf->space ) { + myGlMultMatrix( surf->space->modelViewMatrix, backEnd.projectionMatrix, + backEnd.modelViewProjection ); + backEnd.mvpSpace = surf->space; + } + float parm[4]; + for ( int column = 0; column < 4; ++column ) { + for ( int row = 0; row < 4; ++row ) { + parm[row] = backEnd.modelViewProjection[column + row * 4]; + } + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 75 + column, parm ); + } +} + +void RB_ARB2_LoadProjectionMatrixIntoVPParams( const drawSurf_t *surf ) { + (void)surf; + float parm[4]; + for ( int column = 0; column < 4; ++column ) { + for ( int row = 0; row < 4; ++row ) { + parm[row] = backEnd.projectionMatrix[column + row * 4]; + } + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 81 + column, parm ); + } +} + +void RB_ARB2_LoadShaderTextureMatrixIntoVPParams( const float *shaderRegisters, + const textureStage_t *texture ) { + float matrix[16]; + float parm[4]; + RB_GetShaderTextureMatrix( shaderRegisters, texture, matrix ); + for ( int column = 0; column < 2; ++column ) { + for ( int row = 0; row < 4; ++row ) { + parm[row] = matrix[column + row * 4]; + } + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 85 + column, parm ); + } +} + +void RB_ARB2_LoadModelMatrixIntoVPParams( const drawSurf_t *surf ) { + float parm[4]; + for ( int column = 0; column < 3; ++column ) { + for ( int row = 0; row < 4; ++row ) { + parm[row] = surf->space->modelMatrix[column + row * 4]; + } + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 87 + column, parm ); + } +} + +void RB_ARB2_LoadModelViewMatrixIntoVPParams( const drawSurf_t *surf ) { + float parm[4]; + for ( int column = 0; column < 3; ++column ) { + for ( int row = 0; row < 4; ++row ) { + parm[row] = surf->space->modelViewMatrix[column + row * 4]; + } + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 87 + column, parm ); + } +} + +void RB_ARB2_LoadLocalViewOriginIntoVPParams( const drawSurf_t *surf ) { + idVec4 parm; + R_GlobalPointToLocal( surf->space->modelMatrix, + backEnd.viewDef->renderView.vieworg, parm.ToVec3() ); + parm[3] = 1.0f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 80, parm.ToFloatPtr() ); +} + +void RB_ARB2_MD5R_DrawDepthElements( const drawSurf_t *surf ) { + rvMesh *mesh = surf->geo->primBatchMesh; + const rvVertexFormat *drawFormat = mesh->GetDrawVertexBufferFormat(); + int formatIndex; + for ( formatIndex = 0; formatIndex < 3; ++formatIndex ) { + if ( drawFormat->HasSameComponents( supportedMD5RDepthVertexFormats[formatIndex] ) ) { + break; + } + } + if ( formatIndex == 3 ) { + return; + } + const rvVertexFormat *format = &supportedMD5RDepthVertexFormats[formatIndex]; + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 24 + formatIndex ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + RB_ARB2_LoadMVPMatrixIntoVPParams( surf ); + mesh->SetupForDrawRender( format ); + GL_DisableVertexAttribState( 4 ); + mesh->Draw( surf->geo->skinToModelTransforms, format ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); + qglDisable( GL_VERTEX_PROGRAM_ARB ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); +} + +/* +================== +RB_ARB2_DrawInteraction +================== +*/ +void RB_ARB2_DrawInteraction( const drawInteraction_t *din ) { + int parmBase = 0; + if ( din->surf->geo->primBatchMesh ) { + RB_ARB2_LoadMVPMatrixIntoVPParams( din->surf ); + parmBase = 75; + } + // load all the vertex program parameters + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr() ); + + // testing fragment based normal mapping + if ( r_testARBProgram.GetBool() ) { + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, din->localLightOrigin.ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, din->localViewOrigin.ToFloatPtr() ); + } + + static const float zeroOne[4] = { 0, 0, 0, 1 }; + static const float oneZero[4] = { 1, 1, 1, 0 }; + static const float negOneOne[4] = { -1, -1, -1, 1 }; + + switch ( din->vertexColor ) { + case SVC_IGNORE: + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_COLOR_MODULATE, zeroOne ); + break; + case SVC_MODULATE: + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_COLOR_MODULATE, oneZero ); + break; + case SVC_INVERSE_MODULATE: + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, parmBase + PP_COLOR_MODULATE, negOneOne ); + break; + } + + // set the constant colors + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, din->diffuseColor.ToFloatPtr() ); + float specularColor_x2[4] = { + din->specularColor[0] * 2.0f, din->specularColor[1] * 2.0f, + din->specularColor[2] * 2.0f, din->specularColor[3] * 2.0f + }; + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, specularColor_x2 ); + GL_PolygonOffset( din->surf->material, true ); + + // set the textures + + // texture 1 will be the per-surface bump map + GL_SelectTextureNoClient( 1 ); + din->bumpImage->Bind(); + + // texture 2 will be the light falloff texture + GL_SelectTextureNoClient( 2 ); + din->lightFalloffImage->Bind(); + + // texture 3 will be the light projection texture + GL_SelectTextureNoClient( 3 ); + din->lightImage->Bind(); + + // texture 4 is the per-surface diffuse map + GL_SelectTextureNoClient( 4 ); + din->diffuseImage->Bind(); + + // texture 5 is the per-surface specular map + GL_SelectTextureNoClient( 5 ); + din->specularImage->Bind(); + + // draw it + RB_DrawElementsWithCounters( din->surf->geo ); + GL_PolygonOffset( din->surf->material, false ); +} + + +/* +============= +RB_ARB2_CreateDrawInteractions + +============= +*/ +void RB_ARB2_CreateDrawInteractions( const drawSurf_t *surf ) { + if ( !surf ) { + return; + } + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | backEnd.depthFunc ); + + GLuint defaultVertexProgram; + if ( r_testARBProgram.GetBool() ) { + defaultVertexProgram = 10; + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 10 ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, 14 ); + } else if ( r_useSimpleInteraction.GetBool() ) { + defaultVertexProgram = 19; + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 19 ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, 20 ); + } else { + defaultVertexProgram = 1; + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 1 ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, 11 ); + } + + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + + GL_SelectTextureNoClient( 0 ); + if ( backEnd.vLight->lightShader->IsAmbientLight() ) { + globalImages->ambientNormalMap->Bind(); + } else { + globalImages->normalCubeMapImage->Bind(); + } + + rvMesh *previousMesh = NULL; + for ( ; surf ; surf=surf->nextOnLight ) { + rvMesh *mesh = surf->geo->primBatchMesh; + if ( mesh ) { + const rvVertexFormat *drawFormat = mesh->GetDrawVertexBufferFormat(); + int formatIndex; + for ( formatIndex = 0; formatIndex < 3; ++formatIndex ) { + if ( drawFormat->HasSameComponents( supportedMD5RVertexFormats[formatIndex] ) ) { + break; + } + } + if ( formatIndex < 3 ) { + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 21 + formatIndex ); + } + previousMesh = mesh; + } else { + if ( previousMesh ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, defaultVertexProgram ); + previousMesh = NULL; + } + idDrawVert *ac = (idDrawVert *)vertexCache.Position( surf->geo->ambientCache ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), ac->color ); + qglVertexAttribPointerARB( 11, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); + qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); + qglVertexAttribPointerARB( 8, 2, GL_FLOAT, false, sizeof( idDrawVert ), ac->st.ToFloatPtr() ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + GL_VertexAttribState( 0x0F000005 ); + } + RB_CreateSingleDrawInteractions( surf, RB_ARB2_DrawInteraction ); + } + + GL_VertexAttribState( 1 ); + if ( previousMesh ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + + GL_SelectTextureNoClient( 5 ); + globalImages->BindNull(); + + GL_SelectTextureNoClient( 4 ); + globalImages->BindNull(); + + GL_SelectTextureNoClient( 3 ); + globalImages->BindNull(); + + GL_SelectTextureNoClient( 2 ); + globalImages->BindNull(); + + GL_SelectTextureNoClient( 1 ); + globalImages->BindNull(); + + backEnd.glState.currenttmu = -1; + GL_SelectTexture( 0 ); + + qglDisable( GL_VERTEX_PROGRAM_ARB ); + qglDisable( GL_FRAGMENT_PROGRAM_ARB ); +} + + +/* +================== +RB_ARB2_DrawInteractions +================== +*/ +void RB_ARB2_DrawInteractions( void ) { + viewLight_t *vLight; + const idMaterial *lightShader; + + GL_SelectTexture( 0 ); + GL_DisableVertexAttribState( 8 ); + + // + // for each light, perform adding and shadowing + // + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + backEnd.vLight = vLight; + + // do fogging later + if ( vLight->lightShader->IsFogLight() ) { + continue; + } + if ( vLight->lightShader->IsBlendLight() ) { + continue; + } + + if ( !vLight->localInteractions && !vLight->globalInteractions + && !vLight->translucentInteractions ) { + continue; + } + + lightShader = vLight->lightShader; + + // clear the stencil buffer if needed + if ( vLight->globalShadows || vLight->localShadows ) { + backEnd.currentScissor = vLight->scissorRect; + if ( r_useScissor.GetBool() ) { + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + qglClear( GL_STENCIL_BUFFER_BIT ); + } else { + // no shadows, so no need to read or write the stencil buffer + // we might in theory want to use GL_ALWAYS instead of disabling + // completely, to satisfy the invarience rules + qglStencilFunc( GL_ALWAYS, 128, 255 ); + } + + if ( r_useShadowVertexProgram.GetBool() ) { + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); + RB_StencilShadowPass( vLight->globalShadows ); + RB_ARB2_CreateDrawInteractions( vLight->localInteractions ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); + RB_StencilShadowPass( vLight->localShadows ); + RB_ARB2_CreateDrawInteractions( vLight->globalInteractions ); + qglDisable( GL_VERTEX_PROGRAM_ARB ); // if there weren't any globalInteractions, it would have stayed on + } else { + RB_StencilShadowPass( vLight->globalShadows ); + RB_ARB2_CreateDrawInteractions( vLight->localInteractions ); + RB_StencilShadowPass( vLight->localShadows ); + RB_ARB2_CreateDrawInteractions( vLight->globalInteractions ); + } + + // translucent surfaces never get stencil shadowed + if ( r_skipTranslucent.GetBool() ) { + continue; + } + + qglStencilFunc( GL_ALWAYS, 128, 255 ); + + backEnd.depthFunc = GLS_DEPTHFUNC_LESS; + RB_ARB2_CreateDrawInteractions( vLight->translucentInteractions ); + + backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; + } + + // disable stencil shadow test + qglStencilFunc( GL_ALWAYS, 128, 255 ); + + GL_SelectTexture( 0 ); + GL_VertexAttribState( 9 ); +} + +//=================================================================================== + + +typedef struct { + GLenum target; + GLuint ident; + char name[64]; +} progDef_t; + +static const int MAX_GLPROGS = 200; + +// a single file can have both a vertex program and a fragment program +static progDef_t progs[MAX_GLPROGS] = { + { GL_VERTEX_PROGRAM_ARB, VPROG_TEST, "test.vfp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_TEST, "test.vfp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_INTERACTION, "interaction.vfp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_INTERACTION, "interaction.vfp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_BUMPY_ENVIRONMENT, "bumpyEnvironment.vfp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_BUMPY_ENVIRONMENT, "bumpyEnvironment.vfp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_AMBIENT, "ambientLight.vfp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_AMBIENT, "ambientLight.vfp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_SIMPLE_INTERACTION, "SimpleInteraction.vfp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_SIMPLE_INTERACTION, "SimpleInteraction.vfp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_INTERACTION4, "md5rInteraction4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_INTERACTION1, "md5rInteraction1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_INTERACTION, "md5rInteraction.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SIMPLE4, "md5rSimple4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SIMPLE1, "md5rSimple1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SIMPLE, "md5rSimple.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_STDTEX4, "md5rStdTex4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_STDTEX1, "md5rStdTex1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_STDTEX, "md5rStdTex.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SKYBOX4, "md5rSkyBox4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SKYBOX1, "md5rSkyBox1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SKYBOX, "md5rSkyBox.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVNORMAL4, "md5rEnvNormal4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVNORMAL1, "md5rEnvNormal1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVNORMAL, "md5rEnvNormal.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVREFLECT4, "md5rEnvReflect4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVREFLECT1, "md5rEnvReflect1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVREFLECT, "md5rEnvReflect.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVBUMP4, "md5rEnvBump4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVBUMP1, "md5rEnvBump1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_ENVBUMP, "md5rEnvBump.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SHADOW4, "md5rShadow4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SHADOW1, "md5rShadow1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_SHADOW, "md5rShadow.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_BASICFOG4, "md5rBasicFog4.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_BASICFOG1, "md5rBasicFog1.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_MD5R_BASICFOG, "md5rBasicFog.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW, "shadow.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_R200_INTERACTION, "R200_interaction.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_NV20_BUMP_AND_LIGHT, "nv20_bumpAndLight.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_NV20_DIFFUSE_COLOR, "nv20_diffuseColor.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_NV20_SPECULAR_COLOR, "nv20_specularColor.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_NV20_DIFFUSE_AND_SPECULAR_COLOR, "nv20_diffuseAndSpecularColor.vp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_ENVIRONMENT, "environment.vfp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_ENVIRONMENT, "environment.vfp" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_GLASSWARP, "arbVP_glasswarp.txt" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_GLASSWARP, "arbFP_glasswarp.txt" }, + { GL_VERTEX_PROGRAM_ARB, VPROG_DEPTH, "arbVP_depth.vp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_DEPTH, "arbFP_depth.fp" }, + { GL_FRAGMENT_PROGRAM_ARB, FPROG_DEPTH_COC, "arbFP_depth_coc.fp" }, + + // additional programs can be dynamically specified in materials +}; + +/* +================= +R_LoadARBProgram +================= +*/ +void R_LoadARBProgram( int progIndex ) { + int ofs; + int err; + idStr fullPath = "glprogs/"; + const char *expecting = NULL; + if ( strstr( fullPath.c_str(), ".cg" ) ) { + idStr newName = progs[progIndex].name; + newName.StripFileExtension(); + if ( progs[progIndex].target == GL_VERTEX_PROGRAM_ARB ) { + newName += ".vp"; + } else if ( progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB ) { + newName += ".fp"; + } + strncpy( progs[progIndex].name, newName.c_str(), sizeof( progs[progIndex].name ) - 1 ); + } + fullPath += progs[progIndex].name; + char *fileBuffer; + char *buffer; + char *start, *end; + + common->Printf( "%s", fullPath.c_str() ); + + // load the program even if we don't support it, so + // fs_copyfiles can generate cross-platform data dumps + fileSystem->ReadFile( fullPath.c_str(), (void **)&fileBuffer, NULL ); + if ( !fileBuffer ) { + common->Printf( ": File not found\n" ); + progs[progIndex].ident = 0; + return; + } + + // copy to stack memory and free + buffer = (char *)_alloca( strlen( fileBuffer ) + 1 ); + strcpy( buffer, fileBuffer ); + fileSystem->FreeFile( fileBuffer ); + + if ( !glConfig.isInitialized ) { + return; + } + + // + // submit the program string at start to GL + // + if ( progs[progIndex].ident == 0 ) { + // allocate a new identifier for this program + progs[progIndex].ident = PROG_USER + progIndex; + } + + // vertex and fragment programs can both be present in a single file, so + // scan for the proper header to be the start point, and stamp a 0 in after the end + + if ( progs[progIndex].target == GL_VERTEX_PROGRAM_ARB ) { + if ( !glConfig.ARBVertexProgramAvailable ) { + common->Printf( ": GL_VERTEX_PROGRAM_ARB not available\n" ); + return; + } + expecting = "!!ARBvp"; + } + if ( progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB ) { + if ( !glConfig.ARBFragmentProgramAvailable ) { + common->Printf( ": GL_FRAGMENT_PROGRAM_ARB not available\n" ); + return; + } + expecting = "!!ARBfp"; + } + if ( progs[progIndex].target == GL_FRAGMENT_PROGRAM_NV ) { + if ( !glConfig.nvProgramsAvailable ) { + common->Printf( ": GL_FRAGMENT_PROGRAM_NV not available\n" ); + return; + } + expecting = "!!FP"; + } + start = strstr( (char *)buffer, expecting ); + if ( !start ) { + common->Printf( ": %s not found\n", expecting ); + return; + } + end = strstr( start, "END" ); + + if ( !end ) { + common->Printf( ": END not found\n" ); + return; + } + end[3] = 0; + + if ( progs[progIndex].target == GL_FRAGMENT_PROGRAM_NV ) { + qglLoadProgramNV( GL_FRAGMENT_PROGRAM_NV, progs[progIndex].ident, + strlen( start ), (unsigned char *)start ); + } else { + qglBindProgramARB( progs[progIndex].target, progs[progIndex].ident ); + qglGetError(); + qglProgramStringARB( progs[progIndex].target, GL_PROGRAM_FORMAT_ASCII_ARB, + strlen( start ), (unsigned char *)start ); + } + + err = qglGetError(); + qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, (GLint *)&ofs ); + if ( err == GL_INVALID_OPERATION ) { + const GLubyte *str = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); + common->Printf( "\nGL_PROGRAM_ERROR_STRING_ARB: %s\n", str ); + if ( ofs < 0 ) { + common->Printf( "GL_PROGRAM_ERROR_POSITION_ARB < 0 with error\n" ); + } else if ( ofs >= (int)strlen( (char *)start ) ) { + common->Printf( "error at end of program\n" ); + } else { + common->Printf( "error at %i:\n%s", ofs, start + ofs ); + } + return; + } + if ( ofs != -1 ) { + common->Printf( "\nGL_PROGRAM_ERROR_POSITION_ARB != -1 without error\n" ); + return; + } + + common->Printf( "\n" ); +} + +/* +================== +R_FindARBProgram + +Returns a GL identifier that can be bound to the given target, parsing +a text file if it hasn't already been loaded. +================== +*/ +int R_FindARBProgram( GLenum target, const char *program ) { + int i; + idStr stripped = program; + + stripped.StripFileExtension(); + + // see if it is already loaded + for ( i = 0 ; progs[i].name[0] ; i++ ) { + if ( progs[i].target != target ) { + continue; + } + + idStr compare = progs[i].name; + compare.StripFileExtension(); + + if ( !idStr::Icmp( stripped.c_str(), compare.c_str() ) ) { + return progs[i].ident; + } + } + + if ( i == MAX_GLPROGS ) { + common->Error( "R_FindARBProgram: MAX_GLPROGS" ); + } + + // add it to the list and load it + progs[i].ident = (program_t)0; // will be gen'd by R_LoadARBProgram + progs[i].target = target; + strncpy( progs[i].name, program, sizeof( progs[i].name ) - 1 ); + + R_LoadARBProgram( i ); + + return progs[i].ident; +} + +/* +================== +R_ReloadARBPrograms_f +================== +*/ +void R_ReloadARBPrograms_f( const idCmdArgs &args ) { + int i; + + fileSystem->SetIsFileLoadingAllowed( true ); + common->Printf( "------------ R_ReloadARBPrograms ------------\n" ); + for ( i = 0 ; progs[i].name[0] ; i++ ) { + R_LoadARBProgram( i ); + } + common->Printf( "---------------------------------------------\n" ); + fileSystem->SetIsFileLoadingAllowed( false ); +} + +/* +================== +R_ARB2_Init + +================== +*/ +void R_ARB2_Init( void ) { + glConfig.allowARB2Path = false; + glConfig.preferNV20Path = false; + + common->Printf( "---------------- R_ARB2_Init ----------------\n" ); + + if ( !glConfig.ARBVertexProgramAvailable || !glConfig.ARBFragmentProgramAvailable ) { + common->Printf( "Not available.\n" ); + return; + } + + common->Printf( "Available.\n" ); + common->Printf( "---------------------------------------------\n" ); + + glConfig.allowARB2Path = true; + idStr renderer = glConfig.renderer_string; + glConfig.preferSimpleLighting = false; + if ( renderer.Find( "GeForce", false ) >= 0 && + ( renderer.Find( "5200", false ) >= 0 || renderer.Find( "5600", false ) >= 0 ) ) { + if ( glConfig.allowNV20Path ) { + glConfig.preferNV20Path = true; + } + } else if ( renderer.Find( "RADEON", false ) >= 0 && + ( renderer.Find( "9700", false ) >= 0 || renderer.Find( "9600", false ) >= 0 ) ) { + common->Printf( "%s: prefers simple lighting\n", renderer.c_str() ); + glConfig.preferSimpleLighting = true; + } +} + +void RB_ARB2_MD5R_DrawShadowElements( const drawSurf_t *surf, int numIndices ) { + const srfTriangles_t *tri = surf->geo; + rvMesh *mesh = tri->primBatchMesh; + const rvVertexFormat *drawFormat = mesh->GetShadowVolVertexBufferFormat(); + int formatIndex; + for ( formatIndex = 0; formatIndex < 3; ++formatIndex ) { + if ( drawFormat->HasSameComponents( supportedMD5RShadowVolVertexFormats[formatIndex] ) ) { + break; + } + } + if ( formatIndex >= 3 ) { + return; + } + const rvVertexFormat *format = &supportedMD5RShadowVolVertexFormats[formatIndex]; + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 42 + formatIndex ); + idVec4 localLight; + R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.vLight->globalLightOrigin, + localLight.ToVec3() ); + localLight.w = 0.0f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 79, localLight.ToFloatPtr() ); + RB_ARB2_LoadMVPMatrixIntoVPParams( surf ); + mesh->SetupForShadowVolRender( format ); + mesh->DrawShadowVolume( tri->skinToModelTransforms, tri->indexes, + numIndices == tri->numIndexes, format ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); +} + +static const float envParam[2][4] = { + { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f } +}; + +void RB_ARB2_PrepareStageTexturing( const shaderStage_t *pStage, + const drawSurf_t *surf, bool fillingDepth ) { + rvMesh *mesh = surf->geo->primBatchMesh; + const rvVertexFormat *drawFormat = mesh->GetDrawVertexBufferFormat(); + int formatIndex; + for ( formatIndex = 0; formatIndex < 3; ++formatIndex ) { + if ( drawFormat->HasSameComponents( supportedMD5RVertexFormats[formatIndex] ) ) { + break; + } + } + if ( formatIndex >= 3 ) { + return; + } + + if ( pStage->privatePolygonOffset != 0.0f ) { + GL_PolygonOffsetState( true, r_offsetFactor.GetFloat(), + r_offsetUnits.GetFloat() * pStage->privatePolygonOffset ); + } + if ( !mesh->m_drawSetUp ) { + mesh->SetupForDrawRender( NULL ); + } + + switch ( pStage->texture.texgen ) { + case TG_DIFFUSE_CUBE: + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 33 + formatIndex ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + break; + case TG_REFLECT_CUBE: { + const shaderStage_t *bumpStage = surf->material->GetBumpStage(); + if ( bumpStage ) { + GL_SelectTexture( 1 ); + bumpStage->texture.image->Bind(); + GL_SelectTexture( 0 ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, 13 ); + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 39 + formatIndex ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + RB_ARB2_LoadModelMatrixIntoVPParams( surf ); + } else { + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 36 + formatIndex ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + } + RB_ARB2_LoadLocalViewOriginIntoVPParams( surf ); + break; + } + case TG_SKYBOX_CUBE: + case TG_WOBBLESKY_CUBE: + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 30 + formatIndex ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 80, + surf->texGenTransformAndViewOrg + 12 ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 81, + surf->texGenTransformAndViewOrg ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 82, + surf->texGenTransformAndViewOrg + 4 ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 83, + surf->texGenTransformAndViewOrg + 8 ); + break; + case TG_SCREEN: + return; + default: + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 27 + formatIndex ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + break; + } + + float color[4]; + if ( fillingDepth ) { + color[0] = color[1] = color[2] = color[3] = 0.0f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 91, color ); + color[3] = surf->shaderRegisters[pStage->color.registers[3]]; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 92, color ); + } else if ( pStage->vertexColor != SVC_IGNORE ) { + color[0] = color[1] = color[2] = color[3] = 1.0f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 91, color ); + color[0] = color[1] = color[2] = color[3] = 0.0f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 92, color ); + } else { + color[0] = color[1] = color[2] = color[3] = 0.0f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 91, color ); + if ( surf->mFlags & 1 ) { + color[0] = color[1] = color[2] = color[3] = 1.0f; + } else { + for ( int i = 0; i < 4; ++i ) { + color[i] = surf->shaderRegisters[pStage->color.registers[i]]; + } + } + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 92, color ); + } + + RB_ARB2_LoadMVPMatrixIntoVPParams( surf ); + if ( pStage->texture.hasMatrix ) { + RB_ARB2_LoadShaderTextureMatrixIntoVPParams( surf->shaderRegisters, &pStage->texture ); + } else { + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 85, envParam[0] ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 86, envParam[1] ); + } +} + +void RB_ARB2_DisableStageTexturing( const shaderStage_t *pStage, + const drawSurf_t *surf ) { + if ( pStage->privatePolygonOffset != 0.0f && + !surf->material->TestMaterialFlag( MF_POLYGONOFFSET ) ) { + GL_PolygonOffsetState( false, 0.0f, 0.0f ); + } + qglDisable( GL_FRAGMENT_PROGRAM_ARB ); + qglDisable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + if ( pStage->texture.texgen == TG_REFLECT_CUBE && surf->material->GetBumpStage() ) { + GL_SelectTexture( 1 ); + globalImages->BindNull(); + GL_SelectTexture( 0 ); + } +} diff --git a/src/renderer/draw_common.cpp b/src/renderer/draw_common.cpp new file mode 100644 index 0000000..703322b --- /dev/null +++ b/src/renderer/draw_common.cpp @@ -0,0 +1,1769 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" + +/* +===================== +RB_BakeTextureMatrixIntoTexgen +===================== +*/ +void RB_BakeTextureMatrixIntoTexgen( idPlane lightProject[3], const float *textureMatrix ) { + float genMatrix[16]; + float final[16]; + + genMatrix[0] = lightProject[0][0]; + genMatrix[4] = lightProject[0][1]; + genMatrix[8] = lightProject[0][2]; + genMatrix[12] = lightProject[0][3]; + + genMatrix[1] = lightProject[1][0]; + genMatrix[5] = lightProject[1][1]; + genMatrix[9] = lightProject[1][2]; + genMatrix[13] = lightProject[1][3]; + + genMatrix[2] = 0; + genMatrix[6] = 0; + genMatrix[10] = 0; + genMatrix[14] = 0; + + genMatrix[3] = lightProject[2][0]; + genMatrix[7] = lightProject[2][1]; + genMatrix[11] = lightProject[2][2]; + genMatrix[15] = lightProject[2][3]; + + myGlMultMatrix( genMatrix, backEnd.lightTextureMatrix, final ); + + lightProject[0][0] = final[0]; + lightProject[0][1] = final[4]; + lightProject[0][2] = final[8]; + lightProject[0][3] = final[12]; + + lightProject[1][0] = final[1]; + lightProject[1][1] = final[5]; + lightProject[1][2] = final[9]; + lightProject[1][3] = final[13]; +} + +/* +================ +RB_PrepareStageTexturing +================ +*/ +void RB_PrepareStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf, + idDrawVert *ac, bool fillingDepth ) { + if ( surf->geo->primBatchMesh ) { + if ( tr.backEndRenderer == BE_ARB2 ) { + RB_ARB2_PrepareStageTexturing( pStage, surf, fillingDepth ); + } + return; + } + // set privatePolygonOffset if necessary + if ( pStage->privatePolygonOffset ) { + GL_PolygonOffsetState( true, r_offsetFactor.GetFloat(), + r_offsetUnits.GetFloat() * pStage->privatePolygonOffset ); + } + + // set the texture matrix if needed + if ( pStage->texture.hasMatrix ) { + RB_LoadShaderTextureMatrix( surf->shaderRegisters, &pStage->texture ); + } + + // texgens + if ( pStage->texture.texgen == TG_DIFFUSE_CUBE ) { + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + } + if ( pStage->texture.texgen == TG_SKYBOX_CUBE || pStage->texture.texgen == TG_WOBBLESKY_CUBE ) { + qglTexCoordPointer( 3, GL_FLOAT, 0, vertexCache.Position( surf->dynamicTexCoords ) ); + } + if ( pStage->texture.texgen == TG_SCREEN || pStage->texture.texgen == TG_SCREEN2 ) { + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_Q ); + + float mat[16], plane[4]; + myGlMultMatrix( surf->space->modelViewMatrix, backEnd.viewDef->projectionMatrix, mat ); + + plane[0] = mat[0]; + plane[1] = mat[4]; + plane[2] = mat[8]; + plane[3] = mat[12]; + qglTexGenfv( GL_S, GL_OBJECT_PLANE, plane ); + + plane[0] = mat[1]; + plane[1] = mat[5]; + plane[2] = mat[9]; + plane[3] = mat[13]; + qglTexGenfv( GL_T, GL_OBJECT_PLANE, plane ); + + plane[0] = mat[3]; + plane[1] = mat[7]; + plane[2] = mat[11]; + plane[3] = mat[15]; + qglTexGenfv( GL_Q, GL_OBJECT_PLANE, plane ); + } + if ( pStage->texture.texgen == TG_GLASSWARP && tr.backEndRenderer == BE_ARB2 ) { + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_GLASSWARP ); + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + GL_SelectTexture( 2 ); + globalImages->scratchImage->Bind(); + GL_SelectTexture( 1 ); + globalImages->scratchImage2->Bind(); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_Q ); + float mat[16], plane[4]; + myGlMultMatrix( surf->space->modelViewMatrix, backEnd.viewDef->projectionMatrix, mat ); + plane[0] = mat[0]; plane[1] = mat[4]; plane[2] = mat[8]; plane[3] = mat[12]; + qglTexGenfv( GL_S, GL_OBJECT_PLANE, plane ); + plane[0] = mat[1]; plane[1] = mat[5]; plane[2] = mat[9]; plane[3] = mat[13]; + qglTexGenfv( GL_T, GL_OBJECT_PLANE, plane ); + plane[0] = mat[3]; plane[1] = mat[7]; plane[2] = mat[11]; plane[3] = mat[15]; + qglTexGenfv( GL_Q, GL_OBJECT_PLANE, plane ); + GL_SelectTexture( 0 ); + } + + if ( pStage->texture.texgen == TG_REFLECT_CUBE ) { + if ( tr.backEndRenderer == BE_ARB2 ) { + // see if there is also a bump map specified + const shaderStage_t *bumpStage = surf->material->GetBumpStage(); + if ( bumpStage ) { + // per-pixel reflection mapping with bump mapping + GL_SelectTexture( 1 ); + bumpStage->texture.image->Bind(); + GL_SelectTexture( 0 ); + + qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); + qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); + + qglEnableVertexAttribArrayARB( 9 ); + qglEnableVertexAttribArrayARB( 10 ); + qglEnableClientState( GL_NORMAL_ARRAY ); + + // Program env 5, 6, 7, 8 have been set in RB_SetProgramEnvironmentSpace + + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_BUMPY_ENVIRONMENT ); + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_BUMPY_ENVIRONMENT ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + } else { + // per-pixel reflection mapping without a normal map + qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + qglEnableClientState( GL_NORMAL_ARRAY ); + + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_ENVIRONMENT ); + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_ENVIRONMENT ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + } + } else { + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_R ); + qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); + qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); + qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + + qglMatrixMode( GL_TEXTURE ); + float mat[16]; + + R_TransposeGLMatrix( backEnd.viewDef->worldSpace.modelViewMatrix, mat ); + + qglLoadMatrixf( mat ); + qglMatrixMode( GL_MODELVIEW ); + } + } +} + +/* +================ +RB_FinishStageTexturing +================ +*/ +void RB_FinishStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf, + idDrawVert *ac, bool fillingDepth ) { + (void)fillingDepth; + if ( surf->geo->primBatchMesh ) { + RB_ARB2_DisableStageTexturing( pStage, surf ); + return; + } + // unset privatePolygonOffset if necessary + if ( pStage->privatePolygonOffset && !surf->material->TestMaterialFlag(MF_POLYGONOFFSET) ) { + GL_PolygonOffsetState( false, 0.0f, 0.0f ); + } + + if ( pStage->texture.texgen == TG_DIFFUSE_CUBE || pStage->texture.texgen == TG_SKYBOX_CUBE + || pStage->texture.texgen == TG_WOBBLESKY_CUBE ) { + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->st ); + } + + if ( pStage->texture.texgen == TG_SCREEN || pStage->texture.texgen == TG_SCREEN2 ) { + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_Q ); + } + if ( pStage->texture.texgen == TG_GLASSWARP && tr.backEndRenderer == BE_ARB2 ) { + GL_SelectTexture( 2 ); + globalImages->BindNull(); + GL_SelectTexture( 1 ); + if ( pStage->texture.hasMatrix ) { + RB_LoadShaderTextureMatrix( surf->shaderRegisters, &pStage->texture ); + } + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_Q ); + qglDisable( GL_FRAGMENT_PROGRAM_ARB ); + globalImages->BindNull(); + GL_SelectTexture( 0 ); + } + if ( pStage->texture.texgen == TG_REFLECT_CUBE ) { + if ( tr.backEndRenderer == BE_ARB2 ) { + // see if there is also a bump map specified + const shaderStage_t *bumpStage = surf->material->GetBumpStage(); + if ( bumpStage ) { + // per-pixel reflection mapping with bump mapping + GL_SelectTexture( 1 ); + globalImages->BindNull(); + GL_SelectTexture( 0 ); + + qglDisableVertexAttribArrayARB( 9 ); + qglDisableVertexAttribArrayARB( 10 ); + } else { + // per-pixel reflection mapping without bump mapping + } + + qglDisableClientState( GL_NORMAL_ARRAY ); + qglDisable( GL_FRAGMENT_PROGRAM_ARB ); + qglDisable( GL_VERTEX_PROGRAM_ARB ); + // Fixme: Hack to get around an apparent bug in ATI drivers. Should remove as soon as it gets fixed. + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); + } else { + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_R ); + qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglDisableClientState( GL_NORMAL_ARRAY ); + + qglMatrixMode( GL_TEXTURE ); + qglLoadIdentity(); + qglMatrixMode( GL_MODELVIEW ); + } + } + + if ( pStage->texture.hasMatrix ) { + qglMatrixMode( GL_TEXTURE ); + qglLoadIdentity(); + qglMatrixMode( GL_MODELVIEW ); + } +} + +/* +============================================================================================= + +FILL DEPTH BUFFER + +============================================================================================= +*/ + + +/* +================== +RB_T_FillDepthBuffer +================== +*/ +void RB_T_FillDepthBuffer( const drawSurf_t *surf ) { + int stage; + const idMaterial *shader; + const shaderStage_t *pStage; + const float *regs; + float color[4]; + const srfTriangles_t *tri; + + tri = surf->geo; + shader = surf->material; + + // update the clip plane if needed + if ( backEnd.viewDef->numClipPlanes && surf->space != backEnd.currentSpace ) { + GL_SelectTexture( 1 ); + + idPlane plane; + + R_GlobalPlaneToLocal( surf->space->modelMatrix, backEnd.viewDef->clipPlanes[0], plane ); + plane[3] += 0.5; // the notch is in the middle + qglTexGenfv( GL_S, GL_OBJECT_PLANE, plane.ToFloatPtr() ); + GL_SelectTexture( 0 ); + qglEnable( GL_CLIP_PLANE0 ); + GLdouble clipPlane[4] = { plane[0], plane[1], plane[2], plane[3] }; + qglClipPlane( GL_CLIP_PLANE0, clipPlane ); + } + + if ( !shader->IsDrawn() ) { + return; + } + + // some deforms may disable themselves by setting numIndexes = 0 + if ( !tri->numIndexes ) { + return; + } + + // translucent surfaces don't put anything in the depth buffer and don't + // test against it, which makes them fail the mirror clip plane operation + if ( shader->Coverage() == MC_TRANSLUCENT ) { + return; + } + + if ( !tri->primBatchMesh && !tri->ambientCache ) { + common->Printf( "RB_T_FillDepthBuffer: !tri->ambientCache\n" ); + return; + } + + // get the expressions for conditionals / color / texcoords + regs = surf->shaderRegisters; + + // if all stages of a material have been conditioned off, don't do anything + for ( stage = 0; stage < shader->GetNumStages() ; stage++ ) { + pStage = shader->GetStage(stage); + // check the stage enable condition + if ( regs[ pStage->conditionRegister ] != 0 ) { + break; + } + } + if ( stage == shader->GetNumStages() ) { + return; + } + + GL_PolygonOffset( shader, true ); + + // subviews will just down-modulate the color buffer by overbright + if ( shader->GetSort() == SS_SUBVIEW ) { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO | GLS_DEPTHFUNC_LESS ); + color[0] = + color[1] = + color[2] = ( 1.0 / backEnd.overBright ); + color[3] = 1; + } else { + // others just draw black + color[0] = 0; + color[1] = 0; + color[2] = 0; + color[3] = 1; + } + + idDrawVert *ac = NULL; + if ( !tri->primBatchMesh ) { + ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), reinterpret_cast(&ac->st) ); + } + + bool drawSolid = false; + + if ( shader->Coverage() == MC_OPAQUE ) { + drawSolid = true; + } + + // we may have multiple alpha tested stages + if ( shader->Coverage() == MC_PERFORATED ) { + // if the only alpha tested stages are condition register omitted, + // draw a normal opaque surface + bool didDraw = false; + const bool useAlphaToCoverage = r_alphaToCoverage.GetBool() && r_multiSamples.GetInteger() > 0; + bool alphaTestEnabled = true; + + qglEnable( GL_ALPHA_TEST ); + // perforated surfaces may have multiple alpha tested stages + for ( stage = 0; stage < shader->GetNumStages() ; stage++ ) { + pStage = shader->GetStage(stage); + if ( pStage->texture.dynamic == DI_REFLECTION_RENDER || + pStage->texture.dynamic == DI_REFRACTION_RENDER ) { + continue; + } + + if ( !pStage->hasAlphaTest ) { + continue; + } + + // check the stage enable condition + if ( regs[ pStage->conditionRegister ] == 0 ) { + continue; + } + + // if we at least tried to draw an alpha tested stage, + // we won't draw the opaque surface + didDraw = true; + + // set the alpha modulate + color[3] = regs[ pStage->color.registers[3] ]; + + // skip the entire stage if alpha would be black + if ( color[3] <= 0 ) { + continue; + } + qglColor4fv( color ); + + const float alphaRef = regs[pStage->alphaTestRegister]; + if ( useAlphaToCoverage && + ( pStage->alphaTestMode == GL_GREATER || pStage->alphaTestMode == GL_GEQUAL ) && + alphaRef >= 0.4f && alphaRef < 0.6f ) { + if ( alphaTestEnabled ) { + qglDisable( GL_ALPHA_TEST ); + qglEnable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB ); + alphaTestEnabled = false; + } + } else { + if ( !alphaTestEnabled ) { + qglDisable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB ); + qglEnable( GL_ALPHA_TEST ); + alphaTestEnabled = true; + } + qglAlphaFunc( pStage->alphaTestMode, alphaRef ); + } + + // bind the texture + pStage->texture.image->Bind(); + + // set texture matrix and texGens + RB_PrepareStageTexturing( pStage, surf, ac, true ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + RB_FinishStageTexturing( pStage, surf, ac, true ); + } + if ( alphaTestEnabled ) { + qglDisable( GL_ALPHA_TEST ); + } else { + qglDisable( GL_SAMPLE_ALPHA_TO_COVERAGE_ARB ); + } + if ( !didDraw ) { + drawSolid = true; + } + } + + // draw the entire surface solid + if ( drawSolid ) { + qglColor4fv( color ); + globalImages->whiteImage->Bind(); + + if ( tri->primBatchMesh ) { + RB_ARB2_MD5R_DrawDepthElements( surf ); + } else { + GL_VertexAttribState( 9 ); + RB_DrawElementsWithCounters( tri ); + } + } + + GL_PolygonOffset( shader, false ); + + // reset blending + if ( shader->GetSort() == SS_SUBVIEW ) { + GL_State( GLS_DEPTHFUNC_LESS ); + } + +} + +/* +===================== +RB_STD_FillDepthBuffer + +If we are rendering a subview with a near clip plane, use a second texture +to force the alpha test to fail when behind that clip plane +===================== +*/ +void RB_STD_FillDepthBuffer( drawSurf_t **drawSurfs, int numDrawSurfs ) { + // if we are just doing 2D rendering, no need to fill the depth buffer + if ( !backEnd.viewDef->viewEntitys ) { + return; + } + + RB_LogComment( "---------- RB_STD_FillDepthBuffer ----------\n" ); + + // enable the second texture for mirror plane clipping if needed + if ( backEnd.viewDef->numClipPlanes ) { + GL_SelectTexture( 1 ); + globalImages->alphaNotchImage->Bind(); + GL_DisableVertexAttribState( 0x10 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglTexCoord2f( 1, 0.5 ); + } + + // the first texture will be used for alpha tested surfaces + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); + + // decal surfaces may enable polygon offset + qglPolygonOffset( r_offsetFactor.GetFloat(), r_offsetUnits.GetFloat() ); + + GL_State( GLS_DEPTHFUNC_LESS ); + + // Enable stencil test if we are going to be using it for shadows. + // If we didn't do this, it would be legal behavior to get z fighting + // from the ambient pass and the light passes. + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + RB_RenderDrawSurfListWithFunction( drawSurfs, numDrawSurfs, RB_T_FillDepthBuffer ); + + if ( backEnd.viewDef->numClipPlanes ) { + GL_SelectTexture( 1 ); + globalImages->BindNull(); + qglDisable( GL_TEXTURE_GEN_S ); + GL_SelectTexture( 0 ); + qglDisable( GL_CLIP_PLANE0 ); + } + +} + +/* +============================================================================================= + +SHADER PASSES + +============================================================================================= +*/ + +/* +================== +RB_SetProgramEnvironment + +Sets variables that can be used by all vertex programs +================== +*/ +void RB_SetProgramEnvironment( void ) { + float parm[4]; + int pot; + + if ( !glConfig.ARBVertexProgramAvailable ) { + return; + } + +#if 0 + // screen power of two correction factor, one pixel in so we don't get a bilerp + // of an uncopied pixel + int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; + pot = globalImages->currentRenderImage->uploadWidth; + if ( w == pot ) { + parm[0] = 1.0; + } else { + parm[0] = (float)(w-1) / pot; + } + + int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; + pot = globalImages->currentRenderImage->uploadHeight; + if ( h == pot ) { + parm[1] = 1.0; + } else { + parm[1] = (float)(h-1) / pot; + } + + parm[2] = 0; + parm[3] = 1; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); +#else + // screen power of two correction factor, assuming the copy to _currentRender + // also copied an extra row and column for the bilerp + int w = backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1; + pot = globalImages->currentRenderImage->uploadWidth; + parm[0] = (float)w / pot; + + int h = backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1; + pot = globalImages->currentRenderImage->uploadHeight; + parm[1] = (float)h / pot; + + parm[2] = 0; + parm[3] = 1; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 0, parm ); +#endif + + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, parm ); + + // window coord to 0.0 to 1.0 conversion + parm[0] = 1.0 / w; + parm[1] = 1.0 / h; + parm[2] = 0; + parm[3] = 1; + qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, parm ); + + // + // set eye position in global space + // + parm[0] = backEnd.viewDef->renderView.vieworg[0]; + parm[1] = backEnd.viewDef->renderView.vieworg[1]; + parm[2] = backEnd.viewDef->renderView.vieworg[2]; + parm[3] = 1.0; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 1, parm ); + + +} + +/* +================== +RB_SetProgramEnvironmentSpace + +Sets variables related to the current space that can be used by all vertex programs +================== +*/ +void RB_SetProgramEnvironmentSpace( void ) { + if ( !glConfig.ARBVertexProgramAvailable ) { + return; + } + + const struct viewEntity_s *space = backEnd.currentSpace; + float parm[4]; + + // set eye position in local space + R_GlobalPointToLocal( space->modelMatrix, backEnd.viewDef->renderView.vieworg, *(idVec3 *)parm ); + parm[3] = 1.0; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 5, parm ); + + // we need the model matrix without it being combined with the view matrix + // so we can transform local vectors to global coordinates + parm[0] = space->modelMatrix[0]; + parm[1] = space->modelMatrix[4]; + parm[2] = space->modelMatrix[8]; + parm[3] = space->modelMatrix[12]; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 6, parm ); + parm[0] = space->modelMatrix[1]; + parm[1] = space->modelMatrix[5]; + parm[2] = space->modelMatrix[9]; + parm[3] = space->modelMatrix[13]; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 7, parm ); + parm[0] = space->modelMatrix[2]; + parm[1] = space->modelMatrix[6]; + parm[2] = space->modelMatrix[10]; + parm[3] = space->modelMatrix[14]; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 8, parm ); +} + +void RB_GetCurrentRender( bool force ) { + backEnd.currentRenderNeeded = false; + if ( !r_skipPostProcess.GetBool() && glConfig.allowARB2Path && + ( backEnd.viewDef->renderFlags & RF_PRIMARY_VIEW ) && + ( force || !backEnd.currentRenderCopied ) && tr.backEndRenderer == BE_ARB2 ) { + globalImages->currentRenderImage->CopyFramebuffer( + backEnd.viewDef->viewport.x1, backEnd.viewDef->viewport.y1, + backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1, + backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1, true ); + backEnd.currentRenderCopied = true; + } +} + +/* +================== +RB_STD_T_RenderShaderPasses + +This is also called for the generated 2D rendering +================== +*/ +void RB_STD_T_RenderShaderPasses( const drawSurf_t *surf ) { + int stage; + const idMaterial *shader; + const shaderStage_t *pStage; + const float *regs; + float color[4]; + const srfTriangles_t *tri; + + tri = surf->geo; + shader = surf->material; + + if ( !shader->HasAmbient() ) { + return; + } + + if ( shader->IsPortalSky() ) { + return; + } + + // change the matrix if needed + if ( surf->space != backEnd.currentSpace ) { + qglLoadMatrixf( surf->space->modelViewMatrix ); + backEnd.currentSpace = surf->space; + RB_SetProgramEnvironmentSpace(); + } + + // change the scissor if needed + if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( surf->scissorRect ) ) { + backEnd.currentScissor = surf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + + // some deforms may disable themselves by setting numIndexes = 0 + if ( !tri->numIndexes ) { + return; + } + + if ( !tri->primBatchMesh && !tri->ambientCache ) { + common->Printf( "RB_T_RenderShaderPasses: !tri->ambientCache\n" ); + return; + } + + // get the expressions for conditionals / color / texcoords + regs = surf->shaderRegisters; + + // set face culling appropriately + GL_Cull( shader->GetCullType() ); + + GL_PolygonOffset( shader, true ); + + if ( surf->space->weaponDepthHackInViewID && + surf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) { + RB_EnterWeaponDepthHack(); + } + + if ( surf->space->modelDepthHack != 0.0f ) { + RB_EnterModelDepthHack( surf->space->modelDepthHack ); + } + + idDrawVert *ac = NULL; + if ( !tri->primBatchMesh ) { + ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), reinterpret_cast(&ac->st) ); + GL_VertexAttribState( 9 ); + } + + for ( stage = 0; stage < shader->GetNumStages() ; stage++ ) { + pStage = shader->GetStage(stage); + + // check the enable condition + if ( regs[ pStage->conditionRegister ] == 0 ) { + continue; + } + + // skip the stages involved in lighting + if ( pStage->lighting != SL_AMBIENT ) { + continue; + } + + // skip if the stage is ( GL_ZERO, GL_ONE ), which is used for some alpha masks + if ( ( pStage->drawStateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS) ) == ( GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE ) ) { + continue; + } + + // see if we are a new-style stage + newShaderStage_t *newStage = pStage->newStage; + if ( newStage ) { + //-------------------------- + // + // new style stages + // + //-------------------------- + + // completely skip the stage if we don't have the capability + if ( tr.backEndRenderer != BE_ARB2 ) { + continue; + } + if ( r_skipNewAmbient.GetBool() ) { + continue; + } + if ( tri->primBatchMesh ) { + if ( newStage->md5rVertexProgram > 0 ) { + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, newStage->md5rVertexProgram ); + RB_ARB2_LoadLocalViewOriginIntoVPParams( surf ); + RB_ARB2_LoadMVPMatrixIntoVPParams( surf ); + RB_ARB2_LoadProjectionMatrixIntoVPParams( surf ); + RB_ARB2_LoadModelViewMatrixIntoVPParams( surf ); + } else { + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, newStage->vertexProgram ); + } + if ( !tri->primBatchMesh->m_drawSetUp ) { + tri->primBatchMesh->SetupForDrawRender( NULL ); + } + } else { + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); + qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); + qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + GL_VertexAttribState( 0x0600000F ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, newStage->vertexProgram ); + } + + GL_State( pStage->drawStateBits ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + + // megaTextures bind a lot of images and set a lot of parameters + if ( newStage->megaTexture ) { + newStage->megaTexture->SetMappingForSurface( tri ); + idVec3 localViewer; + R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.viewDef->renderView.vieworg, localViewer ); + newStage->megaTexture->BindForViewOrigin( localViewer ); + } + + for ( int i = 0 ; i < newStage->numVertexParms ; i++ ) { + float parm[4]; + parm[0] = regs[ newStage->vertexParms[i][0] ]; + parm[1] = regs[ newStage->vertexParms[i][1] ]; + parm[2] = regs[ newStage->vertexParms[i][2] ]; + parm[3] = regs[ newStage->vertexParms[i][3] ]; + qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i, parm ); + } + + for ( int i = 0 ; i < newStage->numFragmentProgramImages ; i++ ) { + if ( newStage->fragmentProgramImages[i] ) { + GL_SelectTexture( i ); + newStage->fragmentProgramImages[i]->Bind(); + } + } + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, newStage->fragmentProgram ); + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + for ( int i = 1 ; i < newStage->numFragmentProgramImages ; i++ ) { + if ( newStage->fragmentProgramImages[i] ) { + GL_SelectTexture( i ); + globalImages->BindNull(); + } + } + if ( newStage->megaTexture ) { + newStage->megaTexture->Unbind(); + } + + GL_SelectTexture( 0 ); + + qglDisable( GL_VERTEX_PROGRAM_ARB ); + qglDisable( GL_FRAGMENT_PROGRAM_ARB ); + // Fixme: Hack to get around an apparent bug in ATI drivers. Should remove as soon as it gets fixed. + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 0 ); + + continue; + } + + //-------------------------- + // + // old style stages + // + //-------------------------- + + // set the color + color[0] = regs[ pStage->color.registers[0] ]; + color[1] = regs[ pStage->color.registers[1] ]; + color[2] = regs[ pStage->color.registers[2] ]; + color[3] = regs[ pStage->color.registers[3] ]; + + // skip the entire stage if an add would be black + if ( ( pStage->drawStateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS) ) == ( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ) + && color[0] <= 0 && color[1] <= 0 && color[2] <= 0 ) { + continue; + } + + // skip the entire stage if a blend would be completely transparent + if ( ( pStage->drawStateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS) ) == ( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) + && color[3] <= 0 ) { + continue; + } + + // select the vertex color source + if ( pStage->vertexColor == SVC_IGNORE ) { + qglColor4fv( color ); + } else if ( !tri->primBatchMesh ) { + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), (void *)&ac->color ); + GL_EnableVertexAttribState( 4 ); + + if ( pStage->vertexColor == SVC_INVERSE_MODULATE ) { + GL_TexEnv( GL_COMBINE_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_ONE_MINUS_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); + } + + // for vertex color and modulated color, we need to enable a second + // texture stage + if ( color[0] != 1 || color[1] != 1 || color[2] != 1 || color[3] != 1 ) { + GL_SelectTexture( 1 ); + + globalImages->whiteImage->Bind(); + GL_TexEnv( GL_COMBINE_ARB ); + + qglTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color ); + + qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_CONSTANT_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + qglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1 ); + + qglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_CONSTANT_ARB ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA ); + qglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA ); + qglTexEnvi( GL_TEXTURE_ENV, GL_ALPHA_SCALE, 1 ); + + GL_SelectTexture( 0 ); + } + } + + // bind the texture + RB_BindVariableStageImage( &pStage->texture ); + + // set the state + GL_State( pStage->drawStateBits ); + + RB_PrepareStageTexturing( pStage, surf, ac, false ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + RB_FinishStageTexturing( pStage, surf, ac, false ); + + if ( pStage->vertexColor != SVC_IGNORE ) { + GL_SelectTexture( 1 ); + GL_TexEnv( GL_MODULATE ); + globalImages->BindNull(); + GL_SelectTexture( 0 ); + GL_TexEnv( GL_MODULATE ); + } + } + + GL_PolygonOffset( shader, false ); + if ( ( surf->space->weaponDepthHackInViewID && + surf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) || + surf->space->modelDepthHack != 0.0f ) { + RB_LeaveDepthHack(); + } + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); +} + +/* +===================== +RB_STD_DrawShaderPasses + +Draw non-light dependent passes +===================== +*/ +int RB_STD_DrawShaderPasses( drawSurf_t **drawSurfs, int numDrawSurfs, + materialSort_t minSort, materialSort_t maxSort ) { + int i; + + RB_LogComment( "---------- RB_STD_DrawShaderPasses ----------\n" ); + + if ( minSort == SS_POST_PROCESS && !backEnd.viewDef->isSubview && + backEnd.currentRenderNeeded ) { + RB_GetCurrentRender( false ); + } + + GL_SelectTexture( 1 ); + globalImages->BindNull(); + + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); + + RB_SetProgramEnvironment(); + + // we don't use RB_RenderDrawSurfListWithFunction() + // because we want to defer the matrix load because many + // surfaces won't draw any ambient passes + backEnd.currentSpace = NULL; + for ( i = 0; i < numDrawSurfs; i++ ) { + if ( drawSurfs[i]->material->SuppressInSubview() ) { + continue; + } + + const idMaterial *material = drawSurfs[i]->material; + const float sort = material->GetSort(); + if ( sort < minSort || sort > maxSort ) { + if ( sort >= SS_POST_PROCESS ) { + backEnd.currentRenderNeeded = true; + } + continue; + } + + if ( minSort != SS_POST_PROCESS ) { + if ( material->TestMaterialFlag( MF_NEED_CURRENT_RENDER ) && + !backEnd.currentRenderCopied ) { + RB_GetCurrentRender( true ); + } + if ( !material->TestMaterialFlag( MF_NEED_CURRENT_RENDER ) || + backEnd.currentRenderCopied ) { + RB_STD_T_RenderShaderPasses( drawSurfs[i] ); + } + } else if ( backEnd.currentRenderCopied ) { + RB_STD_T_RenderShaderPasses( drawSurfs[i] ); + } + + if ( sort >= SS_POST_PROCESS ) { + backEnd.currentRenderNeeded = true; + } + } + + GL_Cull( CT_FRONT_SIDED ); + qglColor4f( 1, 1, 1, 1 ); + GL_VertexAttribState( 9 ); + + return 0; +} + + + +/* +============================================================================== + +BACK END RENDERING OF STENCIL SHADOWS + +============================================================================== +*/ + +/* +===================== +RB_T_Shadow + +the shadow volumes face INSIDE +===================== +*/ +static void RB_T_Shadow( const drawSurf_t *surf ) { + const srfTriangles_t *tri; + + // set the light position if we are using a vertex program to project the rear surfaces + if ( tr.backEndRendererHasVertexPrograms && r_useShadowVertexProgram.GetBool() + && surf->space != backEnd.currentSpace && !surf->geo->primBatchMesh ) { + idVec4 localLight; + + R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.vLight->globalLightOrigin, localLight.ToVec3() ); + localLight.w = 0.0f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, localLight.ToFloatPtr() ); + } + + tri = surf->geo; + + if ( !tri->primBatchMesh ) { + if ( !tri->shadowCache ) { + return; + } + qglVertexPointer( 4, GL_FLOAT, sizeof( shadowCache_t ), vertexCache.Position( tri->shadowCache ) ); + } + + // we always draw the sil planes, but we may not need to draw the front or rear caps + int numIndexes; + bool external = false; + + if ( !r_useExternalShadows.GetInteger() ) { + numIndexes = tri->numIndexes; + } else if ( r_useExternalShadows.GetInteger() == 2 ) { // force to no caps for testing + numIndexes = tri->numShadowIndexesNoCaps; + } else if ( !(surf->dsFlags & DSF_VIEW_INSIDE_SHADOW) ) { + // if we aren't inside the shadow projection, no caps are ever needed needed + numIndexes = tri->numShadowIndexesNoCaps; + external = true; + } else if ( !backEnd.vLight->viewInsideLight && !(surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE) ) { + // if we are inside the shadow projection, but outside the light, and drawing + // a non-infinite shadow, we can skip some caps + if ( backEnd.vLight->viewSeesShadowPlaneBits & surf->geo->shadowCapPlaneBits ) { + // we can see through a rear cap, so we need to draw it, but we can skip the + // caps on the actual surface + numIndexes = tri->numShadowIndexesNoFrontCaps; + } else { + // we don't need to draw any caps + numIndexes = tri->numShadowIndexesNoCaps; + } + external = true; + } else { + // must draw everything + numIndexes = tri->numIndexes; + } + + // set depth bounds + if( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { + qglDepthBoundsEXT( surf->scissorRect.zmin, surf->scissorRect.zmax ); + } + + // debug visualization + if ( r_showShadows.GetInteger() ) { + if ( r_showShadows.GetInteger() == 3 ) { + if ( external ) { + qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); + } else { + // these are the surfaces that require the reverse + qglColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); + } + } else { + // draw different color for turboshadows + if ( surf->geo->shadowCapPlaneBits & SHADOW_CAP_INFINITE ) { + if ( numIndexes == tri->numIndexes ) { + qglColor3f( 1/backEnd.overBright, 0.1/backEnd.overBright, 0.1/backEnd.overBright ); + } else { + qglColor3f( 1/backEnd.overBright, 0.4/backEnd.overBright, 0.1/backEnd.overBright ); + } + } else { + if ( numIndexes == tri->numIndexes ) { + qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); + } else if ( numIndexes == tri->numShadowIndexesNoFrontCaps ) { + qglColor3f( 0.1/backEnd.overBright, 1/backEnd.overBright, 0.6/backEnd.overBright ); + } else { + qglColor3f( 0.6/backEnd.overBright, 1/backEnd.overBright, 0.1/backEnd.overBright ); + } + } + } + + qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + qglDisable( GL_STENCIL_TEST ); + GL_Cull( CT_TWO_SIDED ); + RB_DrawShadowElementsWithCounters( surf, numIndexes ); + GL_Cull( CT_FRONT_SIDED ); + qglEnable( GL_STENCIL_TEST ); + + return; + } + + // patent-free work around + if ( !external ) { + // "preload" the stencil buffer with the number of volumes + // that get clipped by the near or far clip plane + qglStencilOp( GL_KEEP, tr.stencilDecr, tr.stencilDecr ); + GL_Cull( CT_FRONT_SIDED ); + RB_DrawShadowElementsWithCounters( surf, numIndexes ); + qglStencilOp( GL_KEEP, tr.stencilIncr, tr.stencilIncr ); + GL_Cull( CT_BACK_SIDED ); + RB_DrawShadowElementsWithCounters( surf, numIndexes ); + } + + // traditional depth-pass stencil shadows + qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilIncr ); + GL_Cull( CT_FRONT_SIDED ); + RB_DrawShadowElementsWithCounters( surf, numIndexes ); + + qglStencilOp( GL_KEEP, GL_KEEP, tr.stencilDecr ); + GL_Cull( CT_BACK_SIDED ); + RB_DrawShadowElementsWithCounters( surf, numIndexes ); +} + +/* +===================== +RB_StencilShadowPass + +Stencil test should already be enabled, and the stencil buffer should have +been set to 128 on any surfaces that might receive shadows +===================== +*/ +void RB_StencilShadowPass( const drawSurf_t *drawSurfs ) { + if ( !r_shadows.GetBool() ) { + return; + } + + if ( !drawSurfs ) { + return; + } + + RB_LogComment( "---------- RB_StencilShadowPass ----------\n" ); + + globalImages->BindNull(); + GL_DisableVertexAttribState( 8 ); + + // for visualizing the shadows + if ( r_showShadows.GetInteger() ) { + if ( r_showShadows.GetInteger() == 2 ) { + // draw filled in + GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_LESS ); + } else { + // draw as lines, filling the depth buffer + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO | GLS_POLYMODE_LINE | GLS_DEPTHFUNC_ALWAYS ); + } + } else { + // don't write to the color buffer, just the stencil buffer + GL_State( GLS_DEPTHMASK | GLS_COLORMASK | GLS_ALPHAMASK | GLS_DEPTHFUNC_LESS ); + } + + if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat() ) { + qglPolygonOffset( r_shadowPolygonFactor.GetFloat(), -r_shadowPolygonOffset.GetFloat() ); + qglEnable( GL_POLYGON_OFFSET_FILL ); + } + + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + if ( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { + qglEnable( GL_DEPTH_BOUNDS_TEST_EXT ); + } + + RB_RenderDrawSurfChainWithFunction( drawSurfs, RB_T_Shadow ); + + GL_Cull( CT_FRONT_SIDED ); + + if ( r_shadowPolygonFactor.GetFloat() || r_shadowPolygonOffset.GetFloat() ) { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } + + if ( glConfig.depthBoundsTestAvailable && r_useDepthBoundsTest.GetBool() ) { + qglDisable( GL_DEPTH_BOUNDS_TEST_EXT ); + } + + GL_EnableVertexAttribState( 8 ); + + qglStencilFunc( GL_GEQUAL, 128, 255 ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); +} + + + +/* +============================================================================================= + +BLEND LIGHT PROJECTION + +============================================================================================= +*/ + +/* +===================== +RB_T_BlendLight + +===================== +*/ +static void RB_T_BlendLight( const drawSurf_t *surf ) { + const srfTriangles_t *tri; + + tri = surf->geo; + + if ( backEnd.currentSpace != surf->space ) { + idPlane lightProject[4]; + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + R_GlobalPlaneToLocal( surf->space->modelMatrix, backEnd.vLight->lightProject[i], lightProject[i] ); + } + + GL_SelectTexture( 0 ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, lightProject[0].ToFloatPtr() ); + qglTexGenfv( GL_T, GL_OBJECT_PLANE, lightProject[1].ToFloatPtr() ); + qglTexGenfv( GL_Q, GL_OBJECT_PLANE, lightProject[2].ToFloatPtr() ); + + GL_SelectTexture( 1 ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, lightProject[3].ToFloatPtr() ); + } + + // this gets used for both blend lights and shadow draws + if ( tri->ambientCache ) { + idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + } else if ( tri->shadowCache ) { + shadowCache_t *sc = (shadowCache_t *)vertexCache.Position( tri->shadowCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( shadowCache_t ), sc->xyz.ToFloatPtr() ); + } + + RB_DrawElementsWithCounters( tri ); +} + + +/* +===================== +RB_BlendLight + +Dual texture together the falloff and projection texture with a blend +mode to the framebuffer, instead of interacting with the surface texture +===================== +*/ +static void RB_BlendLight( const drawSurf_t *drawSurfs, const drawSurf_t *drawSurfs2 ) { + const idMaterial *lightShader; + const shaderStage_t *stage; + int i; + const float *regs; + + if ( !drawSurfs ) { + return; + } + if ( r_skipBlendLights.GetBool() ) { + return; + } + RB_LogComment( "---------- RB_BlendLight ----------\n" ); + + lightShader = backEnd.vLight->lightShader; + regs = backEnd.vLight->shaderRegisters; + + // texture 1 will get the falloff texture + GL_SelectTexture( 1 ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnable( GL_TEXTURE_GEN_S ); + qglTexCoord2f( 0, 0.5 ); + backEnd.vLight->falloffImage->Bind(); + + // texture 0 will get the projected texture + GL_SelectTexture( 0 ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_Q ); + + for ( i = 0 ; i < lightShader->GetNumStages() ; i++ ) { + stage = lightShader->GetStage(i); + + if ( !regs[ stage->conditionRegister ] ) { + continue; + } + + GL_State( GLS_DEPTHMASK | stage->drawStateBits | GLS_DEPTHFUNC_EQUAL ); + + GL_SelectTexture( 0 ); + stage->texture.image->Bind(); + + if ( stage->texture.hasMatrix ) { + RB_LoadShaderTextureMatrix( regs, &stage->texture ); + } + + // get the modulate values from the light, including alpha, unlike normal lights + backEnd.lightColor[0] = regs[ stage->color.registers[0] ]; + backEnd.lightColor[1] = regs[ stage->color.registers[1] ]; + backEnd.lightColor[2] = regs[ stage->color.registers[2] ]; + backEnd.lightColor[3] = regs[ stage->color.registers[3] ]; + qglColor4fv( backEnd.lightColor ); + + RB_RenderDrawSurfChainWithFunction( drawSurfs, RB_T_BlendLight ); + RB_RenderDrawSurfChainWithFunction( drawSurfs2, RB_T_BlendLight ); + + if ( stage->texture.hasMatrix ) { + GL_SelectTexture( 0 ); + qglMatrixMode( GL_TEXTURE ); + qglLoadIdentity(); + qglMatrixMode( GL_MODELVIEW ); + } + } + + GL_SelectTexture( 1 ); + qglDisable( GL_TEXTURE_GEN_S ); + globalImages->BindNull(); + + GL_SelectTexture( 0 ); + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_Q ); +} + + +//======================================================================== + +rvVertexFormat supportedMD5RBasicFogVertexFormats[3] = { + rvVertexFormat( 0x0D, 3, 3, 4, NULL ), + rvVertexFormat( 0x05, 3, 0, 1, NULL ), + rvVertexFormat( 0x01, 4, 0, 1, NULL ) +}; +static idPlane fogPlanes[4]; + +/* +===================== +RB_T_BasicFog + +===================== +*/ +static void RB_T_BasicFog( const drawSurf_t *surf ) { + rvMesh *mesh = surf->geo->primBatchMesh; + if ( mesh ) { + qglEnable( GL_VERTEX_PROGRAM_ARB ); + const rvVertexFormat *drawFormat = mesh->GetDrawVertexBufferFormat(); + int formatIndex; + for ( formatIndex = 0; formatIndex < 3; ++formatIndex ) { + if ( drawFormat->HasSameComponents( supportedMD5RBasicFogVertexFormats[formatIndex] ) ) { + break; + } + } + if ( formatIndex < 3 ) { + mesh->SetupForDrawRender( &supportedMD5RBasicFogVertexFormats[formatIndex] ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, 45 + formatIndex ); + } + RB_ARB2_LoadMVPMatrixIntoVPParams( surf ); + + idPlane local; + R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[0], local ); + local[3] += 0.5f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 93, local.ToFloatPtr() ); + local[0] = local[1] = local[2] = 0.0f; + local[3] = 0.5f; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 94, local.ToFloatPtr() ); + R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[2], local ); + local[3] += FOG_ENTER; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 95, local.ToFloatPtr() ); + R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[3], local ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 96, local.ToFloatPtr() ); + } else if ( backEnd.currentSpace != surf->space ) { + idPlane local; + + GL_SelectTexture( 0 ); + + R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[0], local ); + local[3] += 0.5; + qglTexGenfv( GL_S, GL_OBJECT_PLANE, local.ToFloatPtr() ); + +// R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[1], local ); +// local[3] += 0.5; +local[0] = local[1] = local[2] = 0; local[3] = 0.5; + qglTexGenfv( GL_T, GL_OBJECT_PLANE, local.ToFloatPtr() ); + + GL_SelectTexture( 1 ); + + // GL_S is constant per viewer + R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[2], local ); + local[3] += FOG_ENTER; + qglTexGenfv( GL_T, GL_OBJECT_PLANE, local.ToFloatPtr() ); + + R_GlobalPlaneToLocal( surf->space->modelMatrix, fogPlanes[3], local ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, local.ToFloatPtr() ); + } + + if ( mesh ) { + RB_DrawElementsWithCounters( surf->geo ); + qglDisable( GL_VERTEX_PROGRAM_ARB ); + } else { + RB_T_RenderTriangleSurface( surf ); + } +} + + + +/* +================== +RB_FogPass +================== +*/ +static void RB_FogPass( const drawSurf_t *drawSurfs, const drawSurf_t *drawSurfs2 ) { + const srfTriangles_t*frustumTris; + drawSurf_t ds; + const idMaterial *lightShader; + const shaderStage_t *stage; + const float *regs; + + RB_LogComment( "---------- RB_FogPass ----------\n" ); + + // create a surface for the light frustom triangles, which are oriented drawn side out + frustumTris = backEnd.vLight->frustumTris; + + // if we ran out of vertex cache memory, skip it + if ( !frustumTris->ambientCache ) { + return; + } + memset( &ds, 0, sizeof( ds ) ); + ds.space = &backEnd.viewDef->worldSpace; + ds.geo = frustumTris; + ds.scissorRect = backEnd.viewDef->scissor; + + // find the current color and density of the fog + lightShader = backEnd.vLight->lightShader; + regs = backEnd.vLight->shaderRegisters; + // assume fog shaders have only a single stage + stage = lightShader->GetStage(0); + + backEnd.lightColor[0] = regs[ stage->color.registers[0] ]; + backEnd.lightColor[1] = regs[ stage->color.registers[1] ]; + backEnd.lightColor[2] = regs[ stage->color.registers[2] ]; + backEnd.lightColor[3] = regs[ stage->color.registers[3] ]; + + qglColor3fv( backEnd.lightColor ); + + // calculate the falloff planes + float a; + + // if they left the default value on, set a fog distance of 500 + if ( backEnd.lightColor[3] <= 1.0 ) { + a = -0.5f / DEFAULT_FOG_DISTANCE; + } else { + // otherwise, distance = alpha color + a = -0.5f / backEnd.lightColor[3]; + } + + GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + + // texture 0 is the falloff image + GL_SelectTexture( 0 ); + globalImages->fogImage->Bind(); + //GL_Bind( tr.whiteImage ); + GL_DisableVertexAttribState( 8 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglTexCoord2f( 0.5f, 0.5f ); // make sure Q is set + + fogPlanes[0][0] = a * backEnd.viewDef->worldSpace.modelViewMatrix[2]; + fogPlanes[0][1] = a * backEnd.viewDef->worldSpace.modelViewMatrix[6]; + fogPlanes[0][2] = a * backEnd.viewDef->worldSpace.modelViewMatrix[10]; + fogPlanes[0][3] = a * backEnd.viewDef->worldSpace.modelViewMatrix[14]; + + fogPlanes[1][0] = a * backEnd.viewDef->worldSpace.modelViewMatrix[0]; + fogPlanes[1][1] = a * backEnd.viewDef->worldSpace.modelViewMatrix[4]; + fogPlanes[1][2] = a * backEnd.viewDef->worldSpace.modelViewMatrix[8]; + fogPlanes[1][3] = a * backEnd.viewDef->worldSpace.modelViewMatrix[12]; + + + // texture 1 is the entering plane fade correction + GL_SelectTexture( 1 ); + globalImages->fogEnterImage->Bind(); + GL_DisableVertexAttribState( 16 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + + // T will get a texgen for the fade plane, which is always the "top" plane on unrotated lights + fogPlanes[2][0] = 0.001f * backEnd.vLight->fogPlane[0]; + fogPlanes[2][1] = 0.001f * backEnd.vLight->fogPlane[1]; + fogPlanes[2][2] = 0.001f * backEnd.vLight->fogPlane[2]; + fogPlanes[2][3] = 0.001f * backEnd.vLight->fogPlane[3]; + + // S is based on the view origin + float s = backEnd.viewDef->renderView.vieworg * fogPlanes[2].Normal() + fogPlanes[2][3]; + + fogPlanes[3][0] = 0; + fogPlanes[3][1] = 0; + fogPlanes[3][2] = 0; + fogPlanes[3][3] = FOG_ENTER + s; + + qglTexCoord2f( FOG_ENTER + s, FOG_ENTER ); + + + // draw it + RB_RenderDrawSurfChainWithFunction( drawSurfs, RB_T_BasicFog ); + RB_RenderDrawSurfChainWithFunction( drawSurfs2, RB_T_BasicFog ); + + // the light frustum bounding planes aren't in the depth buffer, so use depthfunc_less instead + // of depthfunc_equal + GL_State( GLS_DEPTHMASK | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_LESS ); + GL_Cull( CT_BACK_SIDED ); + RB_RenderDrawSurfChainWithFunction( &ds, RB_T_BasicFog ); + GL_Cull( CT_FRONT_SIDED ); + + GL_SelectTexture( 1 ); + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + globalImages->BindNull(); + + GL_SelectTexture( 0 ); + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); +} + + +/* +================== +RB_STD_FogAllLights +================== +*/ +void RB_STD_FogAllLights( void ) { + viewLight_t *vLight; + + if ( r_skipFogLights.GetBool() || r_showOverDraw.GetInteger() != 0 + || backEnd.viewDef->isXraySubview /* dont fog in xray mode*/ + ) { + return; + } + + RB_LogComment( "---------- RB_STD_FogAllLights ----------\n" ); + + qglDisable( GL_STENCIL_TEST ); + + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + backEnd.vLight = vLight; + + if ( !vLight->lightShader->IsFogLight() && !vLight->lightShader->IsBlendLight() ) { + continue; + } + +#if 0 // _D3XP disabled that + if ( r_ignore.GetInteger() ) { + // we use the stencil buffer to guarantee that no pixels will be + // double fogged, which happens in some areas that are thousands of + // units from the origin + backEnd.currentScissor = vLight->scissorRect; + if ( r_useScissor.GetBool() ) { + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + qglClear( GL_STENCIL_BUFFER_BIT ); + + qglEnable( GL_STENCIL_TEST ); + + // only pass on the cleared stencil values + qglStencilFunc( GL_EQUAL, 128, 255 ); + + // when we pass the stencil test and depth test and are going to draw, + // increment the stencil buffer so we don't ever draw on that pixel again + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + } +#endif + + if ( vLight->lightShader->IsFogLight() ) { + RB_FogPass( vLight->globalInteractions, vLight->localInteractions ); + } else if ( vLight->lightShader->IsBlendLight() ) { + RB_BlendLight( vLight->globalInteractions, vLight->localInteractions ); + } + qglDisable( GL_STENCIL_TEST ); + } + + qglEnable( GL_STENCIL_TEST ); +} + +//========================================================================================= + +/* +================== +RB_STD_LightScale + +Perform extra blending passes to multiply the entire buffer by +a floating point value +================== +*/ +void RB_STD_LightScale( void ) { + float v, f; + + if ( backEnd.overBright == 1.0f ) { + return; + } + + if ( r_skipLightScale.GetBool() ) { + return; + } + + RB_LogComment( "---------- RB_STD_LightScale ----------\n" ); + + // the scissor may be smaller than the viewport for subviews + if ( r_useScissor.GetBool() ) { + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.viewDef->scissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.viewDef->scissor.y1, + backEnd.viewDef->scissor.x2 - backEnd.viewDef->scissor.x1 + 1, + backEnd.viewDef->scissor.y2 - backEnd.viewDef->scissor.y1 + 1 ); + backEnd.currentScissor = backEnd.viewDef->scissor; + } + + // full screen blends + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_SRC_COLOR ); + GL_Cull( CT_TWO_SIDED ); // so mirror views also get it + globalImages->BindNull(); + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_STENCIL_TEST ); + + v = 1; + while ( idMath::Fabs( v - backEnd.overBright ) > 0.01 ) { // a little extra slop + f = backEnd.overBright / v; + f /= 2; + if ( f > 1 ) { + f = 1; + } + qglColor3f( f, f, f ); + v = v * f * 2; + + qglBegin( GL_QUADS ); + qglVertex2f( 0,0 ); + qglVertex2f( 0,1 ); + qglVertex2f( 1,1 ); + qglVertex2f( 1,0 ); + qglEnd(); + } + + + qglPopMatrix(); + qglEnable( GL_DEPTH_TEST ); + qglMatrixMode( GL_MODELVIEW ); + GL_Cull( CT_FRONT_SIDED ); +} + +//========================================================================================= + +/* +============= +RB_STD_DrawView + +============= +*/ +void RB_STD_DrawView( void ) { + drawSurf_t **drawSurfs; + int numDrawSurfs; + + RB_LogComment( "---------- RB_STD_DrawView ----------\n" ); + + backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; + + drawSurfs = (drawSurf_t **)&backEnd.viewDef->drawSurfs[0]; + numDrawSurfs = backEnd.viewDef->numDrawSurfs; + + RB_DisplaySpecialEffects( backEnd.viewDef->viewEntitys, true ); + + // clear the z buffer, set the projection matrix, etc + RB_BeginDrawingView(); + + if ( r_showEditorImages.GetBool() ) { + RB_RenderDrawSurfListWithFunction( drawSurfs, numDrawSurfs, RB_T_RenderSimpleSurface ); + } else { + RB_DetermineLightScale(); + RB_STD_FillDepthBuffer( drawSurfs, numDrawSurfs ); + RB_DisplaySpecialEffects( backEnd.viewDef->viewEntitys, false ); + + switch( tr.backEndRenderer ) { + case BE_ARB: + RB_ARB_DrawInteractions(); + break; + case BE_ARB2: + RB_ARB2_DrawInteractions(); + break; + case BE_NV20: + RB_NV20_DrawInteractions(); + break; + case BE_NV10: + RB_NV10_DrawInteractions(); + break; + case BE_R200: + RB_R200_DrawInteractions(); + break; + } + } + + // disable stencil shadow test + qglStencilFunc( GL_ALWAYS, 128, 255 ); + + // uplight the entire screen to crutch up not having better blending range + RB_STD_LightScale(); + + if ( r_portalsDistanceCull.GetBool() && backEnd.viewDef->viewEntitys ) { + backEnd.viewDef->renderWorld->RenderPortalFades(); + } + if ( !r_skipDecals.GetBool() ) { + RB_STD_DrawShaderPasses( drawSurfs, numDrawSurfs, SS_MIN, SS_FAR ); + } + if ( !r_skipFogLights.GetBool() ) { + RB_STD_FogAllLights(); + } + if ( !r_skipAmbient.GetBool() || !backEnd.viewDef->viewEntitys ) { + RB_STD_DrawShaderPasses( drawSurfs, numDrawSurfs, SS_MEDIUM, SS_NEAREST ); + } + if ( !r_skipPostProcess.GetBool() ) { + RB_STD_DrawShaderPasses( drawSurfs, numDrawSurfs, SS_POST_PROCESS, SS_MAX ); + } + + RB_RenderDebugTools( drawSurfs, numDrawSurfs ); + +} diff --git a/src/renderer/draw_nv10.cpp b/src/renderer/draw_nv10.cpp new file mode 100644 index 0000000..5ebf10a --- /dev/null +++ b/src/renderer/draw_nv10.cpp @@ -0,0 +1,651 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + + +/* +================== +RB_RenderInteraction + +backEnd.vLight +backEnd.lightScale + + +backEnd.depthFunc must be equal for alpha tested surfaces to work right, +it is set to lessThan for blended transparent surfaces + +This expects a bumpmap stage before a diffuse stage before a specular stage +The material code is responsible for guaranteeing that, but conditional stages +can still make it invalid. + +you can't blend two bumpmaps, but you can change bump maps between +blended diffuse / specular maps to get the same effect + + +================== +*/ +static void RB_RenderInteraction( const drawSurf_t *surf ) { + const idMaterial *surfaceShader = surf->material; + const float *surfaceRegs = surf->shaderRegisters; + const viewLight_t *vLight = backEnd.vLight; + const idMaterial *lightShader = vLight->lightShader; + const float *lightRegs = vLight->shaderRegisters; + static idPlane lightProject[4]; // reused across function calls + const srfTriangles_t *tri = surf->geo; + const shaderStage_t *lastBumpStage = NULL; + + RB_LogComment( "---------- RB_RenderInteraction %s on %s ----------\n", + lightShader->GetName(), surfaceShader->GetName() ); + + // change the matrix and light projection vectors if needed + if ( surf->space != backEnd.currentSpace ) { + backEnd.currentSpace = surf->space; + qglLoadMatrixf( surf->space->modelViewMatrix ); + + for ( int i = 0 ; i < 4 ; i++ ) { + R_GlobalPlaneToLocal( surf->space->modelMatrix, backEnd.vLight->lightProject[i], lightProject[i] ); + } + } + + // change the scissor if needed + if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( surf->scissorRect ) ) { + backEnd.currentScissor = surf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + + // hack depth range if needed + if ( surf->space->weaponDepthHackInViewID && + surf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) { + RB_EnterWeaponDepthHack(); + } + + if ( surf->space->modelDepthHack != 0.0f ) { + RB_EnterModelDepthHack( surf->space->modelDepthHack ); + } + + + // set the vertex arrays, which may not all be enabled on a given pass + idDrawVert *ac = (idDrawVert *)vertexCache.Position(tri->ambientCache); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), ac->st.ToFloatPtr() ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), ac->color ); + GL_PolygonOffset( surfaceShader, true ); + + const bool diffuseOnly = r_forceDiffuseOnly.GetBool(); + + // go through the individual stages + for ( int i = 0 ; i < surfaceShader->GetNumStages() ; i++ ) { + const shaderStage_t *surfaceStage = surfaceShader->GetStage( i ); + + // ignore ambient stages while drawing interactions + if ( surfaceStage->lighting == SL_AMBIENT ) { + continue; + } + + // ignore stages that fail the condition + if ( !surfaceRegs[ surfaceStage->conditionRegister ] ) { + continue; + } + + //----------------------------------------------------- + // + // bump / falloff + // + //----------------------------------------------------- + if ( surfaceStage->lighting == SL_BUMP ) { + if ( diffuseOnly ) { + continue; + } + // render light falloff * bumpmap lighting + + if ( surfaceStage->vertexColor != SVC_IGNORE ) { + common->Printf( "shader %s: vertexColor on a bump stage\n", + surfaceShader->GetName() ); + } + + // check for RGBA modulations in the stage, which are also illegal? + + // save the bump map stage for the specular calculation and diffuse + // error checking + lastBumpStage = surfaceStage; + + // + // ambient lights combine non-directional bump and falloff + // and write to the alpha channel + // + if ( lightShader->IsAmbientLight() ) { + GL_State( GLS_COLORMASK | GLS_DEPTHMASK | backEnd.depthFunc ); + + // texture 0 will be the per-surface bump map + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); + + RB_BindStageTexture( surfaceRegs, &surfaceStage->texture, surf ); + // development aid + if ( r_skipBump.GetBool() ) { + globalImages->flatNormalMap->Bind(); + } + + // texture 1 will be the light falloff + GL_SelectTexture( 1 ); + + qglEnable( GL_TEXTURE_GEN_S ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, lightProject[3].ToFloatPtr() ); + qglTexCoord2f( 0, 0.5 ); + vLight->falloffImage->Bind(); + + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 2 ); + + // set the constant color to a bit of an angle + qglCombinerParameterfvNV( GL_CONSTANT_COLOR0_NV, tr.ambientLightVector.ToFloatPtr() ); + + // stage 0 sets primary_color = bump dot constant color + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_CONSTANT_COLOR0_NV, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_PRIMARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_TRUE, GL_FALSE, GL_FALSE ); + + // stage 1 alpha sets primary_color = primary_color * falloff + qglCombinerInputNV( GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_A_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_BLUE ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_B_NV, + GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); + qglCombinerOutputNV( GL_COMBINER1_NV, GL_ALPHA, + GL_PRIMARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // final combiner takes the result for the alpha channel + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_PRIMARY_COLOR_NV, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + globalImages->BindNull(); + qglDisable( GL_TEXTURE_GEN_S ); + + GL_SelectTexture( 0 ); + RB_FinishStageTexture( &surfaceStage->texture, surf ); + continue; + } + + // + // draw light falloff to the alpha channel + // + GL_State( GLS_COLORMASK | GLS_DEPTHMASK | backEnd.depthFunc ); + GL_DisableVertexAttribState( 12 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, lightProject[3].ToFloatPtr() ); + qglTexCoord2f( 0, 0.5 ); + vLight->falloffImage->Bind(); + + // make sure a combiner output doesn't step on the texture + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 1 ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_ALPHA, + GL_DISCARD_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // final combiner + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_TEXTURE0_ARB, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + qglDisable( GL_TEXTURE_GEN_S ); + + // + // draw the bump map result onto the alpha channel + // + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ZERO | GLS_COLORMASK | GLS_DEPTHMASK + | backEnd.depthFunc ); + + // texture 0 will be the per-surface bump map + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); + RB_BindStageTexture( surfaceRegs, &surfaceStage->texture, surf ); + + // texture 1 is the normalization cube map + // the texccords are the non-normalized vector towards the light origin + GL_SelectTexture( 1 ); + globalImages->normalCubeMapImage->Bind(); + GL_EnableVertexAttribState( 16 ); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( lightingCache_t ), ((lightingCache_t *)vertexCache.Position(tri->lightingCache))->localLightVector.ToFloatPtr() ); + + GL_DisableVertexAttribState( 4 ); + + // program the nvidia register combiners + // I just want alpha = Dot( texture0, texture1 ) + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 1 ); + + // stage 0 rgb performs the dot product + // SPARE0 = TEXTURE0 dot TEXTURE1 + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE1_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_TRUE, GL_FALSE, GL_FALSE ); + + // final combiner just takes the dot result and puts it in alpha + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_SPARE0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_BLUE ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + globalImages->BindNull(); + GL_DisableVertexAttribState( 16 ); + + GL_SelectTexture( 0 ); + RB_FinishStageTexture( &surfaceStage->texture, surf ); + continue; + } + + if ( surfaceStage->lighting == SL_DIFFUSE ) { + if ( !lastBumpStage && !diffuseOnly ) { + common->Printf( "shader %s: diffuse stage without a preceeding bumpmap stage\n", + surfaceShader->GetName() ); + continue; + } + } + + //----------------------------------------------------- + // + // specular exponent modification of the bump / falloff + // + //----------------------------------------------------- + if ( surfaceStage->lighting == SL_SPECULAR ) { + // put specular bump map into alpha channel, then treat as a diffuse + + // allow the specular to be skipped as a user speed optimization + if ( diffuseOnly || r_skipSpecular.GetBool() ) { + continue; + } + + // ambient lights don't have specular + if ( lightShader->IsAmbientLight() ) { + continue; + } + + if ( !lastBumpStage ) { + common->Printf( "shader %s: specular stage without a preceeding bumpmap stage\n", + surfaceShader->GetName() ); + continue; + } + + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_SRC_ALPHA | GLS_COLORMASK | GLS_DEPTHMASK + | backEnd.depthFunc ); + + // texture 0 will be the per-surface bump map + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); + RB_BindStageTexture( surfaceRegs, &lastBumpStage->texture, surf ); + + // development aid + if ( r_skipBump.GetBool() ) { + globalImages->flatNormalMap->Bind(); + } + + // texture 1 is the normalization cube map + // indexed by the dynamic halfangle texcoords + GL_SelectTexture( 1 ); + globalImages->normalCubeMapImage->Bind(); + GL_EnableVertexAttribState( 16 ); + qglTexCoordPointer( 4, GL_FLOAT, 0, vertexCache.Position( surf->dynamicTexCoords ) ); + + // program the nvidia register combiners + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 2 ); + + // stage 0 rgb performs the dot product + // GL_PRIMARY_COLOR_NV = ( TEXTURE0 dot TEXTURE1 - 0.5 ) * 2 + // the scale and bias steepen the specular curve + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE1_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_PRIMARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_SCALE_BY_TWO_NV, GL_BIAS_BY_NEGATIVE_ONE_HALF_NV, GL_TRUE, GL_FALSE, GL_FALSE ); + + // stage 0 alpha does nothing + qglCombinerOutputNV( GL_COMBINER0_NV, GL_ALPHA, + GL_DISCARD_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 1 rgb does nothing + qglCombinerOutputNV( GL_COMBINER1_NV, GL_RGB, + GL_PRIMARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 1 alpha takes bump * bump + // PRIMARY_COLOR = ( GL_PRIMARY_COLOR_NV * GL_PRIMARY_COLOR_NV - 0.5 ) * 2 + // the scale and bias steepen the specular curve + qglCombinerInputNV( GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_A_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_BLUE ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_ALPHA, GL_VARIABLE_B_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_BLUE ); + qglCombinerOutputNV( GL_COMBINER1_NV, GL_ALPHA, + GL_PRIMARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_SCALE_BY_TWO_NV, GL_BIAS_BY_NEGATIVE_ONE_HALF_NV, GL_FALSE, GL_FALSE, GL_FALSE ); + + // final combiner + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_PRIMARY_COLOR_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_PRIMARY_COLOR_NV, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); + + // draw it + RB_DrawElementsWithCounters( tri ); + + globalImages->BindNull(); + GL_DisableVertexAttribState( 16 ); + + GL_SelectTexture( 0 ); + + RB_FinishStageTexture( &lastBumpStage->texture, surf ); + + // the bump map in the alpha channel is now corrupted, so a normal diffuse + // map can't be drawn unless a new bumpmap is put down + lastBumpStage = NULL; + + // fall through to the common handling of diffuse and specular projected lighting + } + + //----------------------------------------------------- + // + // projected light / surface color for diffuse and specular maps + // + //----------------------------------------------------- + if ( surfaceStage->lighting == SL_DIFFUSE || surfaceStage->lighting == SL_SPECULAR ) { + // don't trash alpha + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_ALPHAMASK | GLS_DEPTHMASK + | backEnd.depthFunc ); + + // texture 0 will get the surface color texture + GL_SelectTexture( 0 ); + GL_EnableVertexAttribState( 8 ); + RB_BindStageTexture( surfaceRegs, &surfaceStage->texture, surf ); + + // development aid + if ( ( surfaceStage->lighting == SL_DIFFUSE && r_skipDiffuse.GetBool() ) + || ( surfaceStage->lighting == SL_SPECULAR && r_skipSpecular.GetBool() ) ) { + globalImages->blackImage->Bind(); + } + + // texture 1 will get the light projected texture + GL_SelectTexture( 1 ); + GL_DisableVertexAttribState( 16 ); + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_Q ); + qglTexGenfv( GL_S, GL_OBJECT_PLANE, lightProject[0].ToFloatPtr() ); + qglTexGenfv( GL_T, GL_OBJECT_PLANE, lightProject[1].ToFloatPtr() ); + qglTexGenfv( GL_Q, GL_OBJECT_PLANE, lightProject[2].ToFloatPtr() ); + + // texture0 * texture1 * primaryColor * constantColor + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 1 ); + + // SPARE0 = TEXTURE0 * PRIMARY_COLOR + // SPARE1 = TEXTURE1 * CONSTANT_COLOR + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + // variable B will be overriden based on the stage vertexColor option + if ( !diffuseOnly && surfaceStage->vertexColor == SVC_MODULATE ) { + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + GL_EnableVertexAttribState( 4 ); + } else if ( !diffuseOnly && surfaceStage->vertexColor == SVC_INVERSE_MODULATE ) { + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_INVERT_NV, GL_RGB ); + GL_EnableVertexAttribState( 4 ); + } else { // SVC_IGNORE + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB ); + GL_DisableVertexAttribState( 4 ); + } + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, + GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, + GL_CONSTANT_COLOR1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_SPARE0_NV, GL_SPARE1_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // final combiner + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_SPARE1_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_SPARE0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); + + // for all light stages, multiply the projected color by the surface + // color, and blend with the framebuffer + for ( int j = 0 ; j < lightShader->GetNumStages() ; j++ ) { + const shaderStage_t *lightStage = lightShader->GetStage( j ); + float color[4]; + + // ignore stages that fail the condition + if ( !lightRegs[ lightStage->conditionRegister ] ) { + continue; + } + + // set the color to the light color times the surface color + color[0] = backEnd.lightScale + * lightRegs[ lightStage->color.registers[0] ] + * surfaceRegs[ surfaceStage->color.registers[0] ]; + color[1] = backEnd.lightScale + * lightRegs[ lightStage->color.registers[1] ] + * surfaceRegs[ surfaceStage->color.registers[1] ]; + color[2] = backEnd.lightScale + * lightRegs[ lightStage->color.registers[2] ] + * surfaceRegs[ surfaceStage->color.registers[2] ]; + color[3] = 1; + + // don't draw if it would be all black + if ( color[0] == 0 && color[1] == 0 && color[2] == 0 ) { + continue; + } + + qglCombinerParameterfvNV( GL_CONSTANT_COLOR1_NV, color ); + + RB_BindStageTexture( lightRegs, &lightStage->texture, surf ); + + RB_DrawElementsWithCounters( tri ); + + RB_FinishStageTexture( &lightStage->texture, surf ); + } + + if ( surfaceStage->vertexColor != SVC_IGNORE ) { + GL_DisableVertexAttribState( 4 ); + } + + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_Q ); + + globalImages->BindNull(); + GL_SelectTexture( 0 ); + RB_FinishStageTexture( &surfaceStage->texture, surf ); + + continue; + } + + } + GL_PolygonOffset( surfaceShader, false ); + // unhack depth range if needed + if ( ( surf->space->weaponDepthHackInViewID && + surf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) || + surf->space->modelDepthHack != 0.0f ) { + RB_LeaveDepthHack(); + } +} + +/* +================== +RB_RenderInteractionList +================== +*/ +static void RB_RenderInteractionList( const drawSurf_t *surf ) { + if ( !surf ) { + return; + } + + qglEnable( GL_REGISTER_COMBINERS_NV ); + + // force a space calculation for light vectors + backEnd.currentSpace = NULL; + + for ( const drawSurf_t *s = surf ; s ; s = s->nextOnLight ) { + RB_RenderInteraction( s ); + } + + qglDisable( GL_REGISTER_COMBINERS_NV ); +} + + + +/* +================== +RB_RenderViewLight + +================== +*/ +static void RB_RenderViewLight( viewLight_t *vLight ) { + backEnd.vLight = vLight; + + // do fogging later + if ( vLight->lightShader->IsFogLight() ) { + return; + } + if ( vLight->lightShader->IsBlendLight() ) { + return; + } + + RB_LogComment( "---------- RB_RenderViewLight 0x%p ----------\n", vLight ); + + // clear the stencil buffer if needed + if ( vLight->globalShadows || vLight->localShadows ) { + backEnd.currentScissor = vLight->scissorRect; + if ( r_useScissor.GetBool() ) { + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + qglClear( GL_STENCIL_BUFFER_BIT ); + } else { + // no shadows, so no need to read or write the stencil buffer + // we might in theory want to use GL_ALWAYS instead of disabling + // completely, to satisfy the invarience rules + qglStencilFunc( GL_ALWAYS, 128, 255 ); + } + + backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; + RB_StencilShadowPass( vLight->globalShadows ); + RB_RenderInteractionList( vLight->localInteractions ); + RB_StencilShadowPass( vLight->localShadows ); + RB_RenderInteractionList( vLight->globalInteractions ); + + if ( r_skipTranslucent.GetBool() ) { + return; + } + + // disable stencil testing for translucent interactions, because + // the shadow isn't calculated at their point, and the shadow + // behind them may be depth fighting with a back side, so there + // isn't any reasonable thing to do + qglStencilFunc( GL_ALWAYS, 128, 255 ); + backEnd.depthFunc = GLS_DEPTHFUNC_LESS; + RB_RenderInteractionList( vLight->translucentInteractions ); +} + + +/* +================== +RB_NV10_DrawInteractions +================== +*/ +void RB_NV10_DrawInteractions( void ) { + qglEnable( GL_STENCIL_TEST ); + + for ( viewLight_t *vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + RB_RenderViewLight( vLight ); + } +} + + +/* +================== +R_NV10_Init + +================== +*/ +void R_NV10_Init( void ) { + glConfig.allowNV10Path = false; + + if ( !glConfig.registerCombinersAvailable ) { + return; + } + + glConfig.allowNV10Path = true; +} diff --git a/src/renderer/draw_nv20.cpp b/src/renderer/draw_nv20.cpp new file mode 100644 index 0000000..f544198 --- /dev/null +++ b/src/renderer/draw_nv20.cpp @@ -0,0 +1,876 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +typedef enum { + FPROG_BUMP_AND_LIGHT, + FPROG_DIFFUSE_COLOR, + FPROG_SPECULAR_COLOR, + FPROG_DIFFUSE_AND_SPECULAR_COLOR, + + FPROG_NUM_FRAGMENT_PROGRAMS +} fragmentProgram_t; + +GLuint fragmentDisplayListBase; // FPROG_NUM_FRAGMENT_PROGRAMS lists + +void RB_NV20_DependentSpecularPass( const drawInteraction_t *din ); +void RB_NV20_DependentAmbientPass( void ); + +/* +========================================================================================= + +GENERAL INTERACTION RENDERING + +========================================================================================= +*/ + +/* +==================== +GL_SelectTextureNoClient +==================== +*/ +static __forceinline void GL_SelectTextureNoClient( int unit ) { + backEnd.glState.currenttmu = unit; + qglActiveTextureARB( GL_TEXTURE0_ARB + unit ); + RB_LogComment( "glActiveTextureARB( %i )\n", unit ); +} + +/* +================== +RB_NV20_BumpAndLightFragment +================== +*/ +static void RB_NV20_BumpAndLightFragment( void ) { + if ( r_useCombinerDisplayLists.GetBool() ) { + qglCallList( fragmentDisplayListBase + FPROG_BUMP_AND_LIGHT ); + return; + } + + // program the nvidia register combiners + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 3 ); + + // stage 0 rgb performs the dot product + // SPARE0 = TEXTURE0 dot TEXTURE1 + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE1_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_TRUE, GL_FALSE, GL_FALSE ); + + + // stage 1 rgb multiplies texture 2 and 3 together + // SPARE1 = TEXTURE2 * TEXTURE3 + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE2_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER1_NV, GL_RGB, + GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 1 alpha does nohing + + // stage 2 color multiplies spare0 * spare 1 just for debugging + // SPARE0 = SPARE0 * SPARE1 + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER2_NV, GL_RGB, + GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 2 alpha multiples spare0 * spare 1 + // SPARE0 = SPARE0 * SPARE1 + qglCombinerInputNV( GL_COMBINER2_NV, GL_ALPHA, GL_VARIABLE_A_NV, + GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_BLUE ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_ALPHA, GL_VARIABLE_B_NV, + GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_BLUE ); + qglCombinerOutputNV( GL_COMBINER2_NV, GL_ALPHA, + GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // final combiner + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_SPARE0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_SPARE0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); +} + +/* +================== +RB_NV20_DI_BumpAndLightPass + +We are going to write alpha as light falloff * ( bump dot light ) * lightProjection +If the light isn't a monoLightShader, the lightProjection will be skipped, because +it will have to be done on an itterated basis +================== +*/ +static void RB_NV20_DI_BumpAndLightPass( const drawInteraction_t *din, bool monoLightShader ) { + RB_LogComment( "---------- RB_NV_BumpAndLightPass ----------\n" ); + + GL_State( GLS_COLORMASK | GLS_DEPTHMASK | backEnd.depthFunc ); + + // texture 0 is the normalization cube map + // GL_TEXTURE0_ARB will be the normalized vector + // towards the light source +#ifdef MACOS_X + GL_SelectTexture( 0 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 0 ); +#endif + if ( din->ambientLight ) { + globalImages->ambientNormalMap->Bind(); + } else { + globalImages->normalCubeMapImage->Bind(); + } + + // texture 1 will be the per-surface bump map +#ifdef MACOS_X + GL_SelectTexture( 1 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 1 ); +#endif + din->bumpImage->Bind(); + + // texture 2 will be the light falloff texture +#ifdef MACOS_X + GL_SelectTexture( 2 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 2 ); +#endif + din->lightFalloffImage->Bind(); + + // texture 3 will be the light projection texture +#ifdef MACOS_X + GL_SelectTexture( 3 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 3 ); +#endif + if ( monoLightShader ) { + din->lightImage->Bind(); + } else { + // if the projected texture is multi-colored, we + // will need to do it in subsequent passes + globalImages->whiteImage->Bind(); + } + + // bind our "fragment program" + RB_NV20_BumpAndLightFragment(); + + // draw it + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_NV20_BUMP_AND_LIGHT ); + RB_DrawElementsWithCounters( din->surf->geo ); +} + + +/* +================== +RB_NV20_DiffuseColorFragment +================== +*/ +static void RB_NV20_DiffuseColorFragment( void ) { + if ( r_useCombinerDisplayLists.GetBool() ) { + qglCallList( fragmentDisplayListBase + FPROG_DIFFUSE_COLOR ); + return; + } + + // program the nvidia register combiners + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 1 ); + + // stage 0 is free, so we always do the multiply of the vertex color + // when the vertex color is inverted, qglCombinerInputNV(GL_VARIABLE_B_NV) will be changed + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_TEXTURE0_ARB, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglCombinerOutputNV( GL_COMBINER0_NV, GL_ALPHA, + GL_DISCARD_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + + // for GL_CONSTANT_COLOR0_NV * TEXTURE0 * TEXTURE1 + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_CONSTANT_COLOR0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_E_TIMES_F_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_E_NV, GL_TEXTURE0_ARB, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_F_NV, GL_TEXTURE1_ARB, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); + +} + +/* +================== +RB_NV20_DI_DiffuseColorPass + +================== +*/ +static void RB_NV20_DI_DiffuseColorPass( const drawInteraction_t *din ) { + RB_LogComment( "---------- RB_NV20_DiffuseColorPass ----------\n" ); + + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_ALPHAMASK + | backEnd.depthFunc ); + + // texture 0 will be the per-surface diffuse map +#ifdef MACOS_X + GL_SelectTexture( 0 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 0 ); +#endif + din->diffuseImage->Bind(); + + // texture 1 will be the light projected texture +#ifdef MACOS_X + GL_SelectTexture( 1 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 1 ); +#endif + din->lightImage->Bind(); + + // texture 2 is disabled +#ifdef MACOS_X + GL_SelectTexture( 2 ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 2 ); +#endif + globalImages->BindNull(); + + // texture 3 is disabled +#ifdef MACOS_X + GL_SelectTexture( 3 ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 3 ); +#endif + globalImages->BindNull(); + + // bind our "fragment program" + RB_NV20_DiffuseColorFragment(); + + // override one parameter for inverted vertex color + if ( din->vertexColor == SVC_INVERSE_MODULATE ) { + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_INVERT_NV, GL_RGB ); + } + + // draw it + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_NV20_DIFFUSE_COLOR ); + RB_DrawElementsWithCounters( din->surf->geo ); +} + + +/* +================== +RB_NV20_SpecularColorFragment +================== +*/ +static void RB_NV20_SpecularColorFragment( void ) { + if ( r_useCombinerDisplayLists.GetBool() ) { + qglCallList( fragmentDisplayListBase + FPROG_SPECULAR_COLOR ); + return; + } + + // program the nvidia register combiners + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 4 ); + + // we want GL_CONSTANT_COLOR1_NV * PRIMARY_COLOR * TEXTURE2 * TEXTURE3 * specular( TEXTURE0 * TEXTURE1 ) + + // stage 0 rgb performs the dot product + // GL_SPARE0_NV = ( TEXTURE0 dot TEXTURE1 - 0.5 ) * 2 + // TEXTURE2 = TEXTURE2 * PRIMARY_COLOR + // the scale and bias steepen the specular curve + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE1_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_SCALE_BY_TWO_NV, GL_BIAS_BY_NEGATIVE_ONE_HALF_NV, GL_TRUE, GL_FALSE, GL_FALSE ); + + // stage 0 alpha does nothing + + // stage 1 color takes bump * bump + // GL_SPARE0_NV = ( GL_SPARE0_NV * GL_SPARE0_NV - 0.5 ) * 2 + // the scale and bias steepen the specular curve + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER1_NV, GL_RGB, + GL_SPARE0_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_SCALE_BY_TWO_NV, GL_BIAS_BY_NEGATIVE_ONE_HALF_NV, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 1 alpha does nothing + + // stage 2 color + // GL_SPARE0_NV = GL_SPARE0_NV * TEXTURE3 + // SECONDARY_COLOR = CONSTANT_COLOR * TEXTURE2 + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_C_NV, + GL_CONSTANT_COLOR1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_D_NV, + GL_TEXTURE2_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER2_NV, GL_RGB, + GL_SPARE0_NV, GL_SECONDARY_COLOR_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 2 alpha does nothing + + + // stage 3 scales the texture by the vertex color + qglCombinerInputNV( GL_COMBINER3_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_SECONDARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER3_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER3_NV, GL_RGB, + GL_SECONDARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 3 alpha does nothing + + // final combiner = GL_SPARE0_NV * SECONDARY_COLOR + PRIMARY_COLOR * SECONDARY_COLOR + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_SPARE0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_SECONDARY_COLOR_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_E_TIMES_F_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_E_NV, GL_SPARE0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_F_NV, GL_SECONDARY_COLOR_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); +} + + +/* +================== +RB_NV20_DI_SpecularColorPass + +================== +*/ +static void RB_NV20_DI_SpecularColorPass( const drawInteraction_t *din ) { + RB_LogComment( "---------- RB_NV20_SpecularColorPass ----------\n" ); + + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_ALPHAMASK + | backEnd.depthFunc ); + + // texture 0 is the normalization cube map for the half angle +#ifdef MACOS_X + GL_SelectTexture( 0 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 0 ); +#endif + globalImages->normalCubeMapImage->Bind(); + + // texture 1 will be the per-surface bump map +#ifdef MACOS_X + GL_SelectTexture( 1 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 1 ); +#endif + din->bumpImage->Bind(); + + // texture 2 will be the per-surface specular map +#ifdef MACOS_X + GL_SelectTexture( 2 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 2 ); +#endif + din->specularImage->Bind(); + + // texture 3 will be the light projected texture +#ifdef MACOS_X + GL_SelectTexture( 3 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 3 ); +#endif + din->lightImage->Bind(); + + // bind our "fragment program" + RB_NV20_SpecularColorFragment(); + + // override one parameter for inverted vertex color + if ( din->vertexColor == SVC_INVERSE_MODULATE ) { + qglCombinerInputNV( GL_COMBINER3_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_PRIMARY_COLOR_NV, GL_UNSIGNED_INVERT_NV, GL_RGB ); + } + + // draw it + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_NV20_SPECULAR_COLOR ); + RB_DrawElementsWithCounters( din->surf->geo ); +} + + + +/* +================== +RB_NV20_DiffuseAndSpecularColorFragment +================== +*/ +static void RB_NV20_DiffuseAndSpecularColorFragment( void ) { + if ( r_useCombinerDisplayLists.GetBool() ) { + qglCallList( fragmentDisplayListBase + FPROG_DIFFUSE_AND_SPECULAR_COLOR ); + return; + } + + // program the nvidia register combiners + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 3 ); + + // GL_CONSTANT_COLOR0_NV will be the diffuse color + // GL_CONSTANT_COLOR1_NV will be the specular color + + // stage 0 rgb performs the dot product + // GL_SECONDARY_COLOR_NV = ( TEXTURE0 dot TEXTURE1 - 0.5 ) * 2 + // the scale and bias steepen the specular curve + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_TEXTURE1_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, + GL_SECONDARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_SCALE_BY_TWO_NV, GL_BIAS_BY_NEGATIVE_ONE_HALF_NV, GL_TRUE, GL_FALSE, GL_FALSE ); + + // stage 0 alpha does nothing + + // stage 1 color takes bump * bump + // PRIMARY_COLOR = ( GL_SECONDARY_COLOR_NV * GL_SECONDARY_COLOR_NV - 0.5 ) * 2 + // the scale and bias steepen the specular curve + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_SECONDARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_SECONDARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER1_NV, GL_RGB, + GL_SECONDARY_COLOR_NV, GL_DISCARD_NV, GL_DISCARD_NV, + GL_SCALE_BY_TWO_NV, GL_BIAS_BY_NEGATIVE_ONE_HALF_NV, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 1 alpha does nothing + + // stage 2 color + // PRIMARY_COLOR = ( PRIMARY_COLOR * TEXTURE3 ) * 2 + // SPARE0 = 1.0 * 1.0 (needed for final combiner) + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_A_NV, + GL_SECONDARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_B_NV, + GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_C_NV, + GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER2_NV, GL_RGB, GL_VARIABLE_D_NV, + GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER2_NV, GL_RGB, + GL_SECONDARY_COLOR_NV, GL_SPARE0_NV, GL_DISCARD_NV, + GL_SCALE_BY_TWO_NV, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // stage 2 alpha does nothing + + // final combiner = TEXTURE2_ARB * CONSTANT_COLOR0_NV + PRIMARY_COLOR_NV * CONSTANT_COLOR1_NV + // alpha = GL_ZERO + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_CONSTANT_COLOR1_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_SECONDARY_COLOR_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_E_TIMES_F_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_E_NV, GL_TEXTURE2_ARB, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_F_NV, GL_CONSTANT_COLOR0_NV, + GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_G_NV, GL_ZERO, + GL_UNSIGNED_IDENTITY_NV, GL_ALPHA ); +} + + +/* +================== +RB_NV20_DI_DiffuseAndSpecularColorPass + +================== +*/ +static void RB_NV20_DI_DiffuseAndSpecularColorPass( const drawInteraction_t *din ) { + RB_LogComment( "---------- RB_NV20_DI_DiffuseAndSpecularColorPass ----------\n" ); + + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | backEnd.depthFunc ); + + // texture 0 is the normalization cube map for the half angle +// still bound from RB_NV_BumpAndLightPass +// GL_SelectTextureNoClient( 0 ); +// GL_Bind( tr.normalCubeMapImage ); + + // texture 1 is the per-surface bump map +// still bound from RB_NV_BumpAndLightPass +// GL_SelectTextureNoClient( 1 ); +// GL_Bind( din->bumpImage ); + + // texture 2 is the per-surface diffuse map +#ifdef MACOS_X + GL_SelectTexture( 2 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 2 ); +#endif + din->diffuseImage->Bind(); + + // texture 3 is the per-surface specular map +#ifdef MACOS_X + GL_SelectTexture( 3 ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 3 ); +#endif + din->specularImage->Bind(); + + // bind our "fragment program" + RB_NV20_DiffuseAndSpecularColorFragment(); + + // draw it + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_NV20_DIFFUSE_AND_SPECULAR_COLOR ); + RB_DrawElementsWithCounters( din->surf->geo ); +} + + +/* +================== +RB_NV20_DrawInteraction +================== +*/ +static void RB_NV20_DrawInteraction( const drawInteraction_t *din ) { + const drawSurf_t *surf = din->surf; + + // load all the vertex program parameters + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr() ); + + // set the constant colors + qglCombinerParameterfvNV( GL_CONSTANT_COLOR0_NV, din->diffuseColor.ToFloatPtr() ); + qglCombinerParameterfvNV( GL_CONSTANT_COLOR1_NV, din->specularColor.ToFloatPtr() ); + GL_PolygonOffset( surf->material, true ); + + const bool diffuseOnly = r_forceDiffuseOnly.GetBool(); + + // vertex color passes should be pretty rare (cross-faded bump map surfaces), so always + // run them down as three-passes + if ( din->vertexColor != SVC_IGNORE ) { + GL_EnableVertexAttribState( 4 ); + if ( !diffuseOnly ) { + RB_NV20_DI_BumpAndLightPass( din, false ); + } + RB_NV20_DI_DiffuseColorPass( din ); + if ( !diffuseOnly ) { + RB_NV20_DI_SpecularColorPass( din ); + } + GL_DisableVertexAttribState( 4 ); + return; + } else { + qglColor3f( 1, 1, 1 ); + + if ( ( r_useNV20MonoLights.GetInteger() == 2 ) || + ( din->lightImage->isMonochrome && r_useNV20MonoLights.GetInteger() ) ) { + if ( diffuseOnly ) { + RB_NV20_DI_DiffuseColorPass( din ); + } else { + RB_NV20_DI_BumpAndLightPass( din, true ); + RB_NV20_DI_DiffuseAndSpecularColorPass( din ); + } + } else { + if ( !diffuseOnly ) { + RB_NV20_DI_BumpAndLightPass( din, false ); + } + RB_NV20_DI_DiffuseColorPass( din ); + if ( !diffuseOnly ) { + RB_NV20_DI_SpecularColorPass( din ); + } + } + } + + GL_PolygonOffset( surf->material, false ); +} + + +/* +============= +RB_NV20_CreateDrawInteractions + +============= +*/ +static void RB_NV20_CreateDrawInteractions( const drawSurf_t *surf ) { + if ( !surf ) { + return; + } + + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglEnable( GL_REGISTER_COMBINERS_NV ); + +#ifdef MACOS_X + GL_SelectTexture(0); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_VertexAttribState( 0x0F000001 ); +#endif + + for ( ; surf ; surf=surf->nextOnLight ) { + // set the vertex pointers + idDrawVert *ac = (idDrawVert *)vertexCache.Position( surf->geo->ambientCache ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), ac->color ); +#ifdef MACOS_X + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), ac->st.ToFloatPtr() ); + GL_SelectTexture( 1 ); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); + GL_SelectTexture( 2 ); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); + GL_SelectTexture( 3 ); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + GL_SelectTexture( 0 ); +#else + qglVertexAttribPointerARB( 11, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); + qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); + qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); + qglVertexAttribPointerARB( 8, 2, GL_FLOAT, false, sizeof( idDrawVert ), ac->st.ToFloatPtr() ); +#endif + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + + RB_CreateSingleDrawInteractions( surf, RB_NV20_DrawInteraction ); + } + +#ifndef MACOS_X + GL_VertexAttribState( 1 ); +#endif + + // disable features +#ifdef MACOS_X + GL_SelectTexture( 3 ); + globalImages->BindNull(); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + GL_SelectTexture( 2 ); + globalImages->BindNull(); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + GL_SelectTexture( 1 ); + globalImages->BindNull(); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); +#else + GL_SelectTextureNoClient( 3 ); + globalImages->BindNull(); + + GL_SelectTextureNoClient( 2 ); + globalImages->BindNull(); + + GL_SelectTextureNoClient( 1 ); + globalImages->BindNull(); +#endif + + backEnd.glState.currenttmu = -1; + GL_SelectTexture( 0 ); + + GL_EnableVertexAttribState( 8 ); + + qglDisable( GL_VERTEX_PROGRAM_ARB ); + qglDisable( GL_REGISTER_COMBINERS_NV ); +} + + +//====================================================================================== + + +/* +================== +RB_NV20_DrawInteractions +================== +*/ +void RB_NV20_DrawInteractions( void ) { + viewLight_t *vLight; + + // + // for each light, perform adding and shadowing + // + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + // do fogging later + if ( vLight->lightShader->IsFogLight() ) { + continue; + } + if ( vLight->lightShader->IsBlendLight() ) { + continue; + } + if ( !vLight->localInteractions && !vLight->globalInteractions + && !vLight->translucentInteractions ) { + continue; + } + + backEnd.vLight = vLight; + + RB_LogComment( "---------- RB_RenderViewLight 0x%p ----------\n", vLight ); + + // clear the stencil buffer if needed + if ( vLight->globalShadows || vLight->localShadows ) { + backEnd.currentScissor = vLight->scissorRect; + if ( r_useScissor.GetBool() ) { + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + qglClear( GL_STENCIL_BUFFER_BIT ); + } else { + // no shadows, so no need to read or write the stencil buffer + // we might in theory want to use GL_ALWAYS instead of disabling + // completely, to satisfy the invarience rules + qglStencilFunc( GL_ALWAYS, 128, 255 ); + } + + backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; + + if ( r_useShadowVertexProgram.GetBool() ) { + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); + RB_StencilShadowPass( vLight->globalShadows ); + RB_NV20_CreateDrawInteractions( vLight->localInteractions ); + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); + RB_StencilShadowPass( vLight->localShadows ); + RB_NV20_CreateDrawInteractions( vLight->globalInteractions ); + qglDisable( GL_VERTEX_PROGRAM_ARB ); // if there weren't any globalInteractions, it would have stayed on + } else { + RB_StencilShadowPass( vLight->globalShadows ); + RB_NV20_CreateDrawInteractions( vLight->localInteractions ); + RB_StencilShadowPass( vLight->localShadows ); + RB_NV20_CreateDrawInteractions( vLight->globalInteractions ); + } + + // translucent surfaces never get stencil shadowed + if ( r_skipTranslucent.GetBool() ) { + continue; + } + + qglStencilFunc( GL_ALWAYS, 128, 255 ); + + backEnd.depthFunc = GLS_DEPTHFUNC_LESS; + RB_NV20_CreateDrawInteractions( vLight->translucentInteractions ); + + backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; + } +} + +//======================================================================= + +/* +================== +R_NV20_Init + +================== +*/ +void R_NV20_Init( void ) { + glConfig.allowNV20Path = false; + + common->Printf( "---------------- R_NV20_Init ----------------\n" ); + + if ( !glConfig.registerCombinersAvailable || !glConfig.ARBVertexProgramAvailable || glConfig.maxTextureUnits < 4 ) { + common->Printf( "Not available.\n" ); + return; + } + + GL_CheckErrors(); + + // create our "fragment program" display lists + fragmentDisplayListBase = qglGenLists( FPROG_NUM_FRAGMENT_PROGRAMS ); + + // force them to issue commands to build the list + bool temp = r_useCombinerDisplayLists.GetBool(); + r_useCombinerDisplayLists.SetBool( false ); + + qglNewList( fragmentDisplayListBase + FPROG_BUMP_AND_LIGHT, GL_COMPILE ); + RB_NV20_BumpAndLightFragment(); + qglEndList(); + + qglNewList( fragmentDisplayListBase + FPROG_DIFFUSE_COLOR, GL_COMPILE ); + RB_NV20_DiffuseColorFragment(); + qglEndList(); + + qglNewList( fragmentDisplayListBase + FPROG_SPECULAR_COLOR, GL_COMPILE ); + RB_NV20_SpecularColorFragment(); + qglEndList(); + + qglNewList( fragmentDisplayListBase + FPROG_DIFFUSE_AND_SPECULAR_COLOR, GL_COMPILE ); + RB_NV20_DiffuseAndSpecularColorFragment(); + qglEndList(); + + r_useCombinerDisplayLists.SetBool( temp ); + + common->Printf( "---------------------------------------------\n" ); + + glConfig.allowNV20Path = true; +} diff --git a/src/renderer/draw_r200.cpp b/src/renderer/draw_r200.cpp new file mode 100644 index 0000000..3959f5a --- /dev/null +++ b/src/renderer/draw_r200.cpp @@ -0,0 +1,509 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* + + There are not enough vertex program texture coordinate outputs + to have unique texture coordinates for bump, specular, and diffuse, + so diffuse and specular are assumed to be the same mapping. + + To handle properly, those cases with different diffuse and specular + mapping will need to be run as two passes. + +*/ + +// changed from 1 to 255 to not conflict with ARB2 program names +static int FPROG_FAST_PATH = 255; + +typedef struct { + GLint numFragmentRegisters; // 6 + GLint numFragmentConstants; // 8 + GLint numPasses; // 2 + GLint numInstructionsPerPass; // 8 + GLint numInstructionsTotal; // 16 + GLint colorAlphaPairing; // 1 + GLint numLoopbackComponenets; // 3 + GLint numInputInterpolatorComponents; // 3 +} atiFragmentShaderInfo_t; + +static atiFragmentShaderInfo_t fsi; + +typedef struct { + // vertex shader invariants + int lightPos; // light position in object coordinates + int viewerPos; // viewer position in object coordinates + int lightProjectS; // projected light s texgen + int lightProjectT; // projected light t texgen + int lightProjectQ; // projected light q texgen + int lightFalloffS; // projected light falloff s texgen + int bumpTransformS; // bump TEX0 S transformation + int bumpTransformT; // bump TEX0 T transformation + int colorTransformS; // diffuse/specular texture matrix + int colorTransformT; // diffuse/specular texture matrix + + // vertex shader variants + int texCoords; + int vertexColors; + int normals; + int tangents; + int biTangents; + +} atiVertexShaderInfo_t; + +static atiVertexShaderInfo_t vsi; + +/* +=================== +RB_R200_ARB_DrawInteraction + +=================== +*/ +static void RB_R200_ARB_DrawInteraction( const drawInteraction_t *din ) { + GL_PolygonOffset( din->surf->material, true ); + + // check for the case we can't handle in a single pass (we could calculate this at shader parse time to optimize) + if ( din->diffuseImage != globalImages->blackImage && din->specularImage != globalImages->blackImage + && memcmp( din->specularMatrix, din->diffuseMatrix, sizeof( din->diffuseMatrix ) ) ) { +// common->Printf( "Note: Shader %s drawn as two pass on R200\n", din->surf->shader->getName() ); + + // draw the specular as a separate pass with a black diffuse map + drawInteraction_t d; + d = *din; + d.diffuseImage = globalImages->blackImage; + SIMDProcessor->Memcpy( d.diffuseMatrix, d.specularMatrix, sizeof( d.diffuseMatrix ) ); + RB_R200_ARB_DrawInteraction( &d ); + + // now fall through and draw the diffuse pass with a black specular map + d = *din; + din = &d; + d.specularImage = globalImages->blackImage; + } + + // load all the vertex program parameters + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); + + const srfTriangles_t *tri = din->surf->geo; + idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->xyz ); + + static const float zeroOne[4] = { 0, 1, 0, 0 }; + static const float oneZero[4] = { 1, 0, 0, 0 }; + static const float negOneOne[4] = { -1, 1, 0, 0 }; + + switch ( din->vertexColor ) { + case SVC_IGNORE: + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, zeroOne ); + break; + case SVC_MODULATE: + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, oneZero ); + break; + case SVC_INVERSE_MODULATE: + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, negOneOne ); + break; + } + + + // texture 0 = light projection + // texture 1 = light falloff + // texture 2 = surface diffuse + // texture 3 = surface specular + // texture 4 = surface bump + // texture 5 = normalization cube map + + GL_SelectTexture( 5 ); + if ( din->ambientLight ) { + globalImages->ambientNormalMap->Bind(); + } else { + globalImages->normalCubeMapImage->Bind(); + } + + GL_SelectTexture( 4 ); + din->bumpImage->Bind(); + + GL_SelectTexture( 3 ); + din->specularImage->Bind(); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->normal ); + + GL_SelectTexture( 2 ); + din->diffuseImage->Bind(); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->tangents[1][0] ); + + GL_SelectTexture( 1 ); + din->lightFalloffImage->Bind(); + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->tangents[0][0] ); + + GL_SelectTexture( 0 ); + din->lightImage->Bind(); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), (void *)&ac->st[0] ); + + qglSetFragmentShaderConstantATI( GL_CON_0_ATI, din->diffuseColor.ToFloatPtr() ); + qglSetFragmentShaderConstantATI( GL_CON_1_ATI, din->specularColor.ToFloatPtr() ); + + if ( din->vertexColor != SVC_IGNORE ) { + qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof(idDrawVert), (void *)&ac->color ); + GL_EnableVertexAttribState( 4 ); + + RB_DrawElementsWithCounters( tri ); + + GL_DisableVertexAttribState( 4 ); + qglColor4f( 1, 1, 1, 1 ); + } else { + RB_DrawElementsWithCounters( tri ); + } + GL_PolygonOffset( din->surf->material, false ); +} + +/* +================== +RB_R200_ARB_CreateDrawInteractions +================== +*/ +static void RB_R200_ARB_CreateDrawInteractions( const drawSurf_t *surf ) { + if ( !surf ) { + return; + } + + // force a space calculation for light vectors + backEnd.currentSpace = NULL; + + // set the depth test + if ( surf->material->Coverage() == MC_TRANSLUCENT /* != C_PERFORATED */ ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_DEPTHFUNC_LESS ); + } else { + // only draw on the alpha tested pixels that made it to the depth buffer + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | GLS_DEPTHFUNC_EQUAL ); + } + + // start the vertex shader + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_R200_INTERACTION ); + qglEnable(GL_VERTEX_PROGRAM_ARB); + + // start the fragment shader + qglBindFragmentShaderATI( FPROG_FAST_PATH ); +#if defined( MACOS_X ) + qglEnable( GL_TEXT_FRAGMENT_SHADER_ATI ); +#else + qglEnable( GL_FRAGMENT_SHADER_ATI ); +#endif + + qglColor4f( 1, 1, 1, 1 ); + + GL_EnableVertexAttribState( 0x30 ); + GL_SelectTexture( 3 ); + GL_EnableVertexAttribState( 0x40 ); + + for ( ; surf ; surf=surf->nextOnLight ) { + RB_CreateSingleDrawInteractions( surf, RB_R200_ARB_DrawInteraction ); + } + + GL_SelectTexture( 5 ); + globalImages->BindNull(); + + GL_SelectTexture( 4 ); + globalImages->BindNull(); + + GL_SelectTexture( 3 ); + globalImages->BindNull(); + GL_DisableVertexAttribState( 0x40 ); + + GL_SelectTexture( 2 ); + globalImages->BindNull(); + GL_DisableVertexAttribState( 0x20 ); + + GL_SelectTexture( 1 ); + globalImages->BindNull(); + GL_DisableVertexAttribState( 0x10 ); + + GL_SelectTexture( 0 ); + + qglDisable( GL_VERTEX_PROGRAM_ARB ); +#if defined( MACOS_X ) + qglDisable( GL_TEXT_FRAGMENT_SHADER_ATI ); +#else + qglDisable( GL_FRAGMENT_SHADER_ATI ); +#endif +} + + + +/* +================== +RB_R200_DrawInteractions + +================== +*/ +void RB_R200_DrawInteractions( void ) { + qglEnable( GL_STENCIL_TEST ); + + for ( viewLight_t *vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + // do fogging later + if ( vLight->lightShader->IsFogLight() ) { + continue; + } + if ( vLight->lightShader->IsBlendLight() ) { + continue; + } + + backEnd.vLight = vLight; + + RB_LogComment( "---------- RB_RenderViewLight 0x%p ----------\n", vLight ); + + // clear the stencil buffer if needed + if ( vLight->globalShadows || vLight->localShadows ) { + backEnd.currentScissor = vLight->scissorRect; + if ( r_useScissor.GetBool() ) { + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + qglClear( GL_STENCIL_BUFFER_BIT ); + } else { + // no shadows, so no need to read or write the stencil buffer + // we might in theory want to use GL_ALWAYS instead of disabling + // completely, to satisfy the invarience rules + qglStencilFunc( GL_ALWAYS, 128, 255 ); + } + + if ( r_useShadowVertexProgram.GetBool() ) { + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); + RB_StencilShadowPass( vLight->globalShadows ); + + RB_R200_ARB_CreateDrawInteractions( vLight->localInteractions ); + + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); + RB_StencilShadowPass( vLight->localShadows ); + + RB_R200_ARB_CreateDrawInteractions( vLight->globalInteractions ); + + qglDisable( GL_VERTEX_PROGRAM_ARB ); // if there weren't any globalInteractions, it would have stayed on + } else { + RB_StencilShadowPass( vLight->globalShadows ); + RB_R200_ARB_CreateDrawInteractions( vLight->localInteractions ); + + RB_StencilShadowPass( vLight->localShadows ); + RB_R200_ARB_CreateDrawInteractions( vLight->globalInteractions ); + } + + if ( r_skipTranslucent.GetBool() ) { + continue; + } + + // disable stencil testing for translucent interactions, because + // the shadow isn't calculated at their point, and the shadow + // behind them may be depth fighting with a back side, so there + // isn't any reasonable thing to do + qglStencilFunc( GL_ALWAYS, 128, 255 ); + RB_R200_ARB_CreateDrawInteractions( vLight->translucentInteractions ); + } +} + + +/* +================= +R_BuildSurfaceFragmentProgram +================= +*/ +static void R_BuildSurfaceFragmentProgram( int programNum ) { + qglBindFragmentShaderATI( programNum ); + + qglBeginFragmentShaderATI(); + + // texture 0 = light projection + // texture 1 = light falloff + // texture 2 = surface diffuse + // texture 3 = surface specular + // texture 4 = surface bump + // texture 5 = normalization cube map + + // texcoord 0 = light projection texGen + // texcoord 1 = light falloff texGen + // texcoord 2 = bumpmap texCoords + // texcoord 3 = specular / diffuse texCoords + // texcoord 4 = halfangle vector in local tangent space + // texcoord 5 = vector to light in local tangent space + + // constant 0 = diffuse modulate + // constant 1 = specular modulate + // constant 5 = internal use for 0.75 constant + + qglSampleMapATI( GL_REG_0_ATI, GL_TEXTURE0_ARB, GL_SWIZZLE_STQ_DQ_ATI ); + qglSampleMapATI( GL_REG_1_ATI, GL_TEXTURE1_ARB, GL_SWIZZLE_STR_ATI ); + qglSampleMapATI( GL_REG_4_ATI, GL_TEXTURE2_ARB, GL_SWIZZLE_STR_ATI ); + qglSampleMapATI( GL_REG_5_ATI, GL_TEXTURE5_ARB, GL_SWIZZLE_STR_ATI ); + + // move the alpha component to the red channel to support rxgb normal map compression + if ( globalImages->image_useNormalCompression.GetInteger() == 2 ) { + qglColorFragmentOp1ATI( GL_MOV_ATI, GL_REG_4_ATI, GL_RED_BIT_ATI, GL_NONE, + GL_REG_4_ATI, GL_ALPHA, GL_NONE ); + } + + // light projection * light falloff + qglColorFragmentOp2ATI( GL_MUL_ATI, GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_REG_1_ATI, GL_NONE, GL_NONE ); + + // vectorToLight dot bumpMap + qglColorFragmentOp2ATI( GL_DOT3_ATI, GL_REG_1_ATI, GL_NONE, GL_SATURATE_BIT_ATI, + GL_REG_4_ATI, GL_NONE, GL_2X_BIT_ATI | GL_BIAS_BIT_ATI, + GL_REG_5_ATI, GL_NONE, GL_2X_BIT_ATI | GL_BIAS_BIT_ATI ); + + // bump * light + qglColorFragmentOp2ATI( GL_MUL_ATI, GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_REG_1_ATI, GL_NONE, GL_NONE ); + + //------------------- + + // carry over the incomingLight calculation + qglPassTexCoordATI( GL_REG_0_ATI, GL_REG_0_ATI, GL_SWIZZLE_STR_ATI ); + + // sample the diffuse surface map + qglSampleMapATI( GL_REG_2_ATI, GL_TEXTURE3_ARB, GL_SWIZZLE_STR_ATI ); + + // sample the specular surface map + qglSampleMapATI( GL_REG_3_ATI, GL_TEXTURE3_ARB, GL_SWIZZLE_STR_ATI ); + + // we will use the surface bump map again + qglPassTexCoordATI( GL_REG_4_ATI, GL_REG_4_ATI, GL_SWIZZLE_STR_ATI ); + + // normalize the specular halfangle + qglSampleMapATI( GL_REG_5_ATI, GL_TEXTURE4_ARB, GL_SWIZZLE_STR_ATI ); + + // R1 = halfangle dot surfaceNormal + qglColorFragmentOp2ATI( GL_DOT3_ATI, GL_REG_1_ATI, GL_NONE, GL_SATURATE_BIT_ATI, + GL_REG_4_ATI, GL_NONE, GL_2X_BIT_ATI | GL_BIAS_BIT_ATI, + GL_REG_5_ATI, GL_NONE, GL_2X_BIT_ATI | GL_BIAS_BIT_ATI ); + + // R1 = 4 * ( R1 - 0.75 ) + // subtract 0.75 and quadruple to tighten the specular spot + float data[4] = { 0.75, 0.75, 0.75, 0.75 }; + qglSetFragmentShaderConstantATI( GL_CON_5_ATI, data ); + qglColorFragmentOp2ATI( GL_SUB_ATI, GL_REG_1_ATI, GL_NONE, GL_4X_BIT_ATI | GL_SATURATE_BIT_ATI, + GL_REG_1_ATI, GL_NONE, GL_NONE, + GL_CON_5_ATI, GL_NONE, GL_NONE ); + + // R1 = R1 * R1 + // sqare the stretched specular result + qglColorFragmentOp2ATI( GL_MUL_ATI, GL_REG_1_ATI, GL_NONE, GL_SATURATE_BIT_ATI, + GL_REG_1_ATI, GL_NONE, GL_NONE, + GL_REG_1_ATI, GL_NONE, GL_NONE ); + + // R1 = R1 * R3 + // R1 = specular power * specular texture * 2 + qglColorFragmentOp2ATI( GL_MUL_ATI, GL_REG_1_ATI, GL_NONE, GL_2X_BIT_ATI | GL_SATURATE_BIT_ATI, + GL_REG_1_ATI, GL_NONE, GL_NONE, + GL_REG_3_ATI, GL_NONE, GL_NONE ); + + // R2 = R2 * CONST0 + // down modulate the diffuse map + qglColorFragmentOp2ATI( GL_MUL_ATI, GL_REG_2_ATI, GL_NONE, GL_SATURATE_BIT_ATI, + GL_REG_2_ATI, GL_NONE, GL_NONE, + GL_CON_0_ATI, GL_NONE, GL_NONE ); + + // R2 = R2 + R1 * CONST1 + // diffuse + specular * specular color + qglColorFragmentOp3ATI( GL_MAD_ATI, GL_REG_2_ATI, GL_NONE, GL_SATURATE_BIT_ATI, + GL_REG_1_ATI, GL_NONE, GL_NONE, + GL_CON_1_ATI, GL_NONE, GL_NONE, + GL_REG_2_ATI, GL_NONE, GL_NONE ); + + // out = reflectance * incoming light + qglColorFragmentOp2ATI( GL_MUL_ATI, GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI, + GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_REG_2_ATI, GL_NONE, GL_NONE ); + + // out * vertex color + qglColorFragmentOp2ATI( GL_MUL_ATI, GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_PRIMARY_COLOR_ARB, GL_NONE, GL_NONE ); + + // out alpha = 0 to allow blending optimization + qglAlphaFragmentOp1ATI( GL_MOV_ATI, GL_REG_0_ATI, GL_NONE, + GL_ZERO, GL_NONE, GL_NONE ); + + qglEndFragmentShaderATI(); + + GL_CheckErrors(); +} + +/* +================= +R_R200_Init +================= +*/ +void R_R200_Init( void ) { + glConfig.allowR200Path = false; + + common->Printf( "----------------- R200_Init -----------------\n" ); + + if ( !glConfig.atiFragmentShaderAvailable || !glConfig.ARBVertexProgramAvailable || !glConfig.ARBVertexBufferObjectAvailable ) { + common->Printf( "Not available.\n" ); + return; + } + + GL_CheckErrors(); + + qglGetIntegerv( GL_NUM_FRAGMENT_REGISTERS_ATI, &fsi.numFragmentRegisters ); + qglGetIntegerv( GL_NUM_FRAGMENT_CONSTANTS_ATI, &fsi.numFragmentConstants ); + qglGetIntegerv( GL_NUM_PASSES_ATI, &fsi.numPasses ); + qglGetIntegerv( GL_NUM_INSTRUCTIONS_PER_PASS_ATI, &fsi.numInstructionsPerPass ); + qglGetIntegerv( GL_NUM_INSTRUCTIONS_TOTAL_ATI, &fsi.numInstructionsTotal ); + qglGetIntegerv( GL_COLOR_ALPHA_PAIRING_ATI, &fsi.colorAlphaPairing ); + qglGetIntegerv( GL_NUM_LOOPBACK_COMPONENTS_ATI, &fsi.numLoopbackComponenets ); + qglGetIntegerv( GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI, &fsi.numInputInterpolatorComponents ); + + common->Printf( "GL_NUM_FRAGMENT_REGISTERS_ATI: %i\n", fsi.numFragmentRegisters ); + common->Printf( "GL_NUM_FRAGMENT_CONSTANTS_ATI: %i\n", fsi.numFragmentConstants ); + common->Printf( "GL_NUM_PASSES_ATI: %i\n", fsi.numPasses ); + common->Printf( "GL_NUM_INSTRUCTIONS_PER_PASS_ATI: %i\n", fsi.numInstructionsPerPass ); + common->Printf( "GL_NUM_INSTRUCTIONS_TOTAL_ATI: %i\n", fsi.numInstructionsTotal ); + common->Printf( "GL_COLOR_ALPHA_PAIRING_ATI: %i\n", fsi.colorAlphaPairing ); + common->Printf( "GL_NUM_LOOPBACK_COMPONENTS_ATI: %i\n", fsi.numLoopbackComponenets ); + common->Printf( "GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI: %i\n", fsi.numInputInterpolatorComponents ); + + common->Printf( "FPROG_FAST_PATH\n" ); + R_BuildSurfaceFragmentProgram( FPROG_FAST_PATH ); + + common->Printf( "---------------------\n" ); + + glConfig.allowR200Path = true; +} diff --git a/src/renderer/glext.h b/src/renderer/glext.h new file mode 100644 index 0000000..ae578e4 --- /dev/null +++ b/src/renderer/glext.h @@ -0,0 +1,5953 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2002 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +/* glext.h last updated 2004/2/23 */ +/* Current version at http://oss.sgi.com/projects/ogl-sample/registry/ */ +#define GL_GLEXT_VERSION 22 + +#ifndef GL_VERSION_1_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#endif + +#ifndef GL_ARB_imaging +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#endif + +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#endif + +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#endif + +#ifndef GL_VERSION_1_5 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_FOG_COORD_SOURCE GL_FOG_COORDINATE_SOURCE +#define GL_FOG_COORD GL_FOG_COORDINATE +#define GL_CURRENT_FOG_COORD GL_CURRENT_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY_TYPE GL_FOG_COORDINATE_ARRAY_TYPE +#define GL_FOG_COORD_ARRAY_STRIDE GL_FOG_COORDINATE_ARRAY_STRIDE +#define GL_FOG_COORD_ARRAY_POINTER GL_FOG_COORDINATE_ARRAY_POINTER +#define GL_FOG_COORD_ARRAY GL_FOG_COORDINATE_ARRAY +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING +#define GL_SRC0_RGB GL_SOURCE0_RGB +#define GL_SRC1_RGB GL_SOURCE1_RGB +#define GL_SRC2_RGB GL_SOURCE2_RGB +#define GL_SRC0_ALPHA GL_SOURCE0_ALPHA +#define GL_SRC1_ALPHA GL_SOURCE1_ALPHA +#define GL_SRC2_ALPHA GL_SOURCE2_ALPHA +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_env_add +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif + +#ifndef GL_ARB_point_parameters +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifndef GL_ARB_shadow +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif + +#ifndef GL_ARB_window_pos +#endif + +#ifndef GL_ARB_vertex_program +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#endif + +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +#endif + +#ifndef GL_ARB_shader_objects +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#endif + +#ifndef GL_ARB_shading_language_100 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#endif + +#ifndef GL_ARB_point_sprite +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_EXT_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_FfdMaskSGIX +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_SGIX_impact_pixel_texture +#define GL_PIXEL_TEX_GEN_Q_CEILING_SGIX 0x8184 +#define GL_PIXEL_TEX_GEN_Q_ROUND_SGIX 0x8185 +#define GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX 0x8186 +#define GL_PIXEL_TEX_GEN_ALPHA_REPLACE_SGIX 0x8187 +#define GL_PIXEL_TEX_GEN_ALPHA_NO_REPLACE_SGIX 0x8188 +#define GL_PIXEL_TEX_GEN_ALPHA_LS_SGIX 0x8189 +#define GL_PIXEL_TEX_GEN_ALPHA_MS_SGIX 0x818A +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif + +#ifndef GL_NV_fence +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +#endif + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif + +#ifndef GL_NV_evaluators +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +#endif + +#ifndef GL_NV_texture_compression_vtc +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif + +#ifndef GL_NV_texture_shader +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV GL_OFFSET_TEXTURE_MATRIX_NV +#define GL_OFFSET_TEXTURE_2D_SCALE_NV GL_OFFSET_TEXTURE_SCALE_NV +#define GL_OFFSET_TEXTURE_2D_BIAS_NV GL_OFFSET_TEXTURE_BIAS_NV +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif + +#ifndef GL_NV_vertex_program +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif + +#ifndef GL_OML_interlace +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif + +#ifndef GL_OML_subsample +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif + +#ifndef GL_OML_resample +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +#endif + +#ifndef GL_ATI_element_array +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +#endif + +#ifndef GL_SUN_mesh_array +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_DEPTH_CLAMP_NV 0x864F +#endif + +#ifndef GL_NV_occlusion_query +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +#endif + +#ifndef GL_NV_point_sprite +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif + +#ifndef GL_NV_vertex_program1_1 +#endif + +#ifndef GL_EXT_shadow_funcs +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif + +#ifndef GL_APPLE_element_array +#define GL_ELEMENT_ARRAY_APPLE 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x876A +#endif + +#ifndef GL_APPLE_fence +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#endif + +#ifndef GL_S3_s3tc +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif + +#ifndef GL_ATI_texture_float +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif + +#ifndef GL_NV_float_buffer +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif + +#ifndef GL_NV_fragment_program +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +#endif + +#ifndef GL_NV_half_float +#define GL_HALF_FLOAT_NV 0x140B +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +#endif + +#ifndef GL_NV_primitive_restart +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif + +#ifndef GL_NV_vertex_program2 +#endif + +#ifndef GL_ATI_map_object_buffer +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_BLEND_EQUATION_RGB_EXT GL_BLEND_EQUATION +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +#endif + +#ifndef GL_MESA_pack_invert +#define GL_PACK_INVERT_MESA 0x8758 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif + + +/*************************************************************/ + +#include +#ifndef GL_VERSION_1_5 +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptr; +typedef ptrdiff_t GLsizeiptr; +#endif + +#ifndef GL_ARB_vertex_buffer_object +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptrARB; +typedef ptrdiff_t GLsizeiptrARB; +#endif + +#ifndef GL_ARB_shader_objects +/* GL types for handling shader object handles and characters */ +typedef char GLcharARB; /* native character */ +typedef unsigned int GLhandleARB; /* shader object handle */ +#endif + +#ifndef GL_NV_half_float +/* GL type for representing NVIDIA "half" floating point type in host memory */ +typedef unsigned short GLhalfNV; +#endif + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +GLAPI void APIENTRY glBlendEquation (GLenum); +GLAPI void APIENTRY glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterf (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteri (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +GLAPI void APIENTRY glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogram (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmax (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogram (GLenum); +GLAPI void APIENTRY glResetMinmax (GLenum); +GLAPI void APIENTRY glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum); +GLAPI void APIENTRY glClientActiveTexture (GLenum); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum, const GLshort *); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glSampleCoverage (GLclampf, GLboolean); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glFogCoordf (GLfloat); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *); +GLAPI void APIENTRY glFogCoordd (GLdouble); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointer (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glMultiDrawArrays (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElements (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +GLAPI void APIENTRY glPointParameterf (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfv (GLenum, const GLfloat *); +GLAPI void APIENTRY glPointParameteri (GLenum, GLint); +GLAPI void APIENTRY glPointParameteriv (GLenum, const GLint *); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3i (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *); +GLAPI void APIENTRY glSecondaryColor3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3us (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointer (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glWindowPos2d (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos2f (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos2i (GLint, GLint); +GLAPI void APIENTRY glWindowPos2iv (const GLint *); +GLAPI void APIENTRY glWindowPos2s (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *); +GLAPI void APIENTRY glWindowPos3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos3i (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3iv (const GLint *); +GLAPI void APIENTRY glWindowPos3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +#endif + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueries (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQuery (GLuint); +GLAPI void APIENTRY glBeginQuery (GLenum, GLuint); +GLAPI void APIENTRY glEndQuery (GLenum); +GLAPI void APIENTRY glGetQueryiv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint, GLenum, GLuint *); +GLAPI void APIENTRY glBindBuffer (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffers (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffers (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint); +GLAPI void APIENTRY glBufferData (GLenum, GLsizeiptr, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubData (GLenum, GLintptr, GLsizeiptr, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubData (GLenum, GLintptr, GLsizeiptr, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBuffer (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointerv (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLclampf, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#endif + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvARB (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint, const GLbyte *); +GLAPI void APIENTRY glWeightsvARB (GLint, const GLshort *); +GLAPI void APIENTRY glWeightivARB (GLint, const GLint *); +GLAPI void APIENTRY glWeightfvARB (GLint, const GLfloat *); +GLAPI void APIENTRY glWeightdvARB (GLint, const GLdouble *); +GLAPI void APIENTRY glWeightubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glWeightusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glWeightuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glWeightPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexBlendARB (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#endif + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#endif + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iARB (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos2sARB (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos3sARB (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#endif + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid *); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glProgramStringARB (GLenum, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBindProgramARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenProgramsARB (GLsizei, GLuint *); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringARB (GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#endif + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +/* All ARB_fragment_program entry points are shared with ARB_vertex_program. */ +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffersARB (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint); +GLAPI void APIENTRY glBufferDataARB (GLenum, GLsizeiptrARB, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBufferARB (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint); +GLAPI void APIENTRY glBeginQueryARB (GLenum, GLuint); +GLAPI void APIENTRY glEndQueryARB (GLenum); +GLAPI void APIENTRY glGetQueryivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB, GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB, GLsizei, const GLcharARB* *, const GLint *); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB, GLhandleARB); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB); +GLAPI void APIENTRY glUniform1fARB (GLint, GLfloat); +GLAPI void APIENTRY glUniform2fARB (GLint, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform3fARB (GLint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform4fARB (GLint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform1iARB (GLint, GLint); +GLAPI void APIENTRY glUniform2iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glUniform3iARB (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform4iARB (GLint, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform1fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform2fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform3fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform4fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform1ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform2ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform3ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform4ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB, GLenum, GLint *); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB, GLsizei, GLsizei *, GLhandleARB *); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB, const GLcharARB *); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB, GLint, GLfloat *); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB, GLint, GLint *); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB, GLuint, const GLcharARB *); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB, const GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmaxEXT (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogramEXT (GLenum); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum, GLint); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum, const GLint *); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindTextureEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint); +GLAPI void APIENTRY glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glDrawArraysEXT (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +GLAPI void APIENTRY glGetPointervEXT (GLenum, GLvoid* *); +GLAPI void APIENTRY glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum, GLint); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei, GLint *); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, const GLdouble *); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, const GLfloat *); +GLAPI void APIENTRY glDeformSGIX (GLbitfield); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum, GLenum, GLint); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glListParameterfSGIX (GLuint, GLenum, GLfloat); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +GLAPI void APIENTRY glListParameteriSGIX (GLuint, GLenum, GLint); +GLAPI void APIENTRY glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint, GLsizei); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum, GLdouble *); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum, GLenum); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum, GLint); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum, const GLint *); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum); +GLAPI void APIENTRY glTextureLightEXT (GLenum); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint, GLsizei); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum, const GLvoid* *); +GLAPI void APIENTRY glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *); +GLAPI void APIENTRY glTangent3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *); +GLAPI void APIENTRY glTangent3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *); +GLAPI void APIENTRY glTangent3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *); +GLAPI void APIENTRY glTangent3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *); +GLAPI void APIENTRY glBinormal3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *); +GLAPI void APIENTRY glBinormal3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *); +GLAPI void APIENTRY glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum, const GLfloat *); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum, GLfloat); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum, const GLint *); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iMESA (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iMESA (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos4iMESA (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *, const GLint *, const GLsizei *, GLsizei, GLint); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* const *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const GLvoid *params); +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#endif + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenFencesNV (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint); +GLAPI void APIENTRY glGetFenceivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFinishFenceNV (GLuint); +GLAPI void APIENTRY glSetFenceNV (GLuint, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#endif + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLint, GLint, GLboolean, const GLvoid *); +GLAPI void APIENTRY glMapParameterivNV (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glMapParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLboolean, GLvoid *); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glEvalMapsNV (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#endif + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#endif + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindProgramNV (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glExecuteProgramNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGenProgramsNV (GLsizei, GLuint *); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum, GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetProgramivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringNV (GLuint, GLenum, GLubyte *); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint); +GLAPI void APIENTRY glLoadProgramNV (GLenum, GLuint, GLsizei, const GLubyte *); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum, GLuint, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum, GLuint, GLuint, const GLfloat *); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glTrackMatrixNV (GLenum, GLuint, GLenum, GLenum); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint, GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint, GLsizei, const GLubyte *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLuint count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLuint count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#endif + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#endif + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#endif + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum, const GLint *); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum, GLint *); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glSampleMapATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum, GLint); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei, const GLvoid *, GLenum); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint, GLuint, GLsizei, const GLvoid *, GLenum); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint); +GLAPI void APIENTRY glArrayObjectATI (GLenum, GLint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const GLvoid *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint); +GLAPI void APIENTRY glShaderOp1EXT (GLenum, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp2EXT (GLenum, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp3EXT (GLenum, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSwizzleEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glWriteMaskEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glInsertComponentEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glExtractComponentEXT (GLuint, GLuint, GLuint); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum, GLenum, GLenum, GLuint); +GLAPI void APIENTRY glSetInvariantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glVariantbvEXT (GLuint, const GLbyte *); +GLAPI void APIENTRY glVariantsvEXT (GLuint, const GLshort *); +GLAPI void APIENTRY glVariantivEXT (GLuint, const GLint *); +GLAPI void APIENTRY glVariantfvEXT (GLuint, const GLfloat *); +GLAPI void APIENTRY glVariantdvEXT (GLuint, const GLdouble *); +GLAPI void APIENTRY glVariantubvEXT (GLuint, const GLubyte *); +GLAPI void APIENTRY glVariantusvEXT (GLuint, const GLushort *); +GLAPI void APIENTRY glVariantuivEXT (GLuint, const GLuint *); +GLAPI void APIENTRY glVariantPointerEXT (GLuint, GLenum, GLuint, const GLvoid *); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum, GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint, GLenum); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint, GLenum, GLvoid* *); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, GLvoid* *data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum, GLshort); +GLAPI void APIENTRY glVertexStream1svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream1iATI (GLenum, GLint); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream1fATI (GLenum, GLfloat); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream1dATI (GLenum, GLdouble); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream2sATI (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream2svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream2iATI (GLenum, GLint, GLint); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream2fATI (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream2dATI (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream4sATI (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream4svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream4iATI (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream4fATI (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream4dATI (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glNormalStream3bATI (GLenum, GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum, const GLbyte *); +GLAPI void APIENTRY glNormalStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glNormalStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glNormalStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glNormalStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glNormalStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum, GLint); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum, GLuint, GLuint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#endif + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glPointParameterivNV (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#endif + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#endif + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, GLint, GLsizei); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum, const GLint *, const GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, const GLint *, const GLsizei *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum, GLuint); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#endif + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei, const GLenum *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#endif + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#endif + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#endif + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +/* Some NV_fragment_program entry points are shared with ARB_vertex_program. */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint, GLsizei, const GLubyte *, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint, GLsizei, const GLubyte *, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint, GLsizei, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint, GLsizei, const GLubyte *, const GLdouble *); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint, GLsizei, const GLubyte *, GLfloat *); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint, GLsizei, const GLubyte *, GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint, GLsizei, const GLhalfNV *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum, GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#endif + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapObjectBufferATI (GLuint); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum, GLenum, GLint, GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint, GLint, GLenum, GLboolean, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd, GLclampd); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd, GLclampd); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/renderer/guimodel.cpp b/src/renderer/guimodel.cpp new file mode 100644 index 0000000..5af6c43 --- /dev/null +++ b/src/renderer/guimodel.cpp @@ -0,0 +1,651 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + + +/* +================ +idGuiModel::idGuiModel +================ +*/ +idGuiModel::idGuiModel() { + indexes.SetGranularity( 1000 ); + verts.SetGranularity( 1000 ); +} + +/* +================ +idGuiModel::Clear + +Begins collecting draw commands into surfaces +================ +*/ +void idGuiModel::Clear() { + surfaces.SetNum( 0, false ); + indexes.SetNum( 0, false ); + verts.SetNum( 0, false ); + AdvanceSurf(); +} + +/* +================ +idGuiModel::WriteToDemo +================ +*/ +void idGuiModel::WriteToDemo( idDemoFile *demo ) { + int i, j; + + i = verts.Num(); + demo->WriteInt( i ); + for ( j = 0; j < i; j++ ) + { + demo->WriteVec3( verts[j].xyz ); + demo->WriteVec2( verts[j].st ); + demo->WriteVec3( verts[j].normal ); + demo->WriteVec3( verts[j].tangents[0] ); + demo->WriteVec3( verts[j].tangents[1] ); + demo->WriteUnsignedChar( verts[j].color[0] ); + demo->WriteUnsignedChar( verts[j].color[1] ); + demo->WriteUnsignedChar( verts[j].color[2] ); + demo->WriteUnsignedChar( verts[j].color[3] ); + } + + i = indexes.Num(); + demo->WriteInt( i ); + for ( j = 0; j < i; j++ ) { + demo->WriteInt(indexes[j] ); + } + + i = surfaces.Num(); + demo->WriteInt( i ); + for ( j = 0 ; j < i ; j++ ) { + guiModelSurface_t *surf = &surfaces[j]; + + demo->WriteInt( (int&)surf->material ); + demo->WriteFloat( surf->color[0] ); + demo->WriteFloat( surf->color[1] ); + demo->WriteFloat( surf->color[2] ); + demo->WriteFloat( surf->color[3] ); + demo->WriteInt( surf->firstVert ); + demo->WriteInt( surf->numVerts ); + demo->WriteInt( surf->firstIndex ); + demo->WriteInt( surf->numIndexes ); + demo->WriteHashString( surf->material->GetName() ); + } +} + +/* +================ +idGuiModel::ReadFromDemo +================ +*/ +void idGuiModel::ReadFromDemo( idDemoFile *demo ) { + int i, j; + + i = verts.Num(); + demo->ReadInt( i ); + verts.SetNum( i, false ); + for ( j = 0; j < i; j++ ) + { + demo->ReadVec3( verts[j].xyz ); + demo->ReadVec2( verts[j].st ); + demo->ReadVec3( verts[j].normal ); + demo->ReadVec3( verts[j].tangents[0] ); + demo->ReadVec3( verts[j].tangents[1] ); + demo->ReadUnsignedChar( verts[j].color[0] ); + demo->ReadUnsignedChar( verts[j].color[1] ); + demo->ReadUnsignedChar( verts[j].color[2] ); + demo->ReadUnsignedChar( verts[j].color[3] ); + } + + i = indexes.Num(); + demo->ReadInt( i ); + indexes.SetNum( i, false ); + for ( j = 0; j < i; j++ ) { + demo->ReadInt(indexes[j] ); + } + + i = surfaces.Num(); + demo->ReadInt( i ); + surfaces.SetNum( i, false ); + for ( j = 0 ; j < i ; j++ ) { + guiModelSurface_t *surf = &surfaces[j]; + + demo->ReadInt( (int&)surf->material ); + demo->ReadFloat( surf->color[0] ); + demo->ReadFloat( surf->color[1] ); + demo->ReadFloat( surf->color[2] ); + demo->ReadFloat( surf->color[3] ); + demo->ReadInt( surf->firstVert ); + demo->ReadInt( surf->numVerts ); + demo->ReadInt( surf->firstIndex ); + demo->ReadInt( surf->numIndexes ); + surf->material = declManager->FindMaterial( demo->ReadHashString() ); + } +} + +/* +================ +EmitSurface +================ +*/ +void idGuiModel::EmitSurface( guiModelSurface_t *surf, float modelMatrix[16], float modelViewMatrix[16], int depthHackInViewID, float *shaderParms ) { + srfTriangles_t *tri; + + if ( surf->numVerts == 0 ) { + return; // nothing in the surface + } + + // copy verts and indexes + tri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *tri ) ); + + tri->numIndexes = surf->numIndexes; + tri->numVerts = surf->numVerts; + if ( r_useIndexBuffers.GetBool() ) { + tri->indexCache = vertexCache.AllocFrameTemp( &indexes[surf->firstIndex], tri->numIndexes * sizeof( tri->indexes[0] ), 1 ); + } + tri->indexes = (glIndex_t *)R_FrameAlloc( tri->numIndexes * sizeof( tri->indexes[0] ) ); + memcpy( tri->indexes, &indexes[surf->firstIndex], tri->numIndexes * sizeof( tri->indexes[0] ) ); + + // we might be able to avoid copying these and just let them reference the list vars + // but some things, like deforms and recursive + // guis, need to access the verts in cpu space, not just through the vertex range + tri->verts = (idDrawVert *)R_FrameAlloc( tri->numVerts * sizeof( tri->verts[0] ) ); + memcpy( tri->verts, &verts[surf->firstVert], tri->numVerts * sizeof( tri->verts[0] ) ); + + // move the verts to the vertex cache + tri->ambientCache = vertexCache.AllocFrameTemp( tri->verts, tri->numVerts * sizeof( tri->verts[0] ) ); + + // if we are out of vertex cache, don't create the surface + if ( !tri->ambientCache ) { + return; + } + + renderEntity_t renderEntity; + memset( &renderEntity, 0, sizeof( renderEntity ) ); + memcpy( renderEntity.shaderParms, surf->color, sizeof( surf->color ) ); + + viewEntity_t *guiSpace = (viewEntity_t *)R_ClearedFrameAlloc( sizeof( *guiSpace ) ); + memcpy( guiSpace->modelMatrix, modelMatrix, sizeof( guiSpace->modelMatrix ) ); + memcpy( guiSpace->modelViewMatrix, modelViewMatrix, sizeof( guiSpace->modelViewMatrix ) ); + guiSpace->weaponDepthHackInViewID = depthHackInViewID; + + // add the surface, which might recursively create another gui + R_AddDrawSurf( tri, guiSpace, &renderEntity, surf->material, tr.viewDef->scissor, this == tr.guiModel ? 2 : 0 ); + (void)shaderParms; +} + +/* +==================== +EmitToCurrentView +==================== +*/ +void idGuiModel::EmitToCurrentView( float modelMatrix[16], int depthHackInViewID, float *shaderParms ) { + float modelViewMatrix[16]; + + myGlMultMatrix( modelMatrix, tr.viewDef->worldSpace.modelViewMatrix, + modelViewMatrix ); + + for ( int i = 0 ; i < surfaces.Num() ; i++ ) { + EmitSurface( &surfaces[i], modelMatrix, modelViewMatrix, depthHackInViewID, shaderParms ); + } +} + +/* +================ +idGuiModel::EmitFullScreen + +Creates a view that covers the screen and emit the surfaces +================ +*/ +void idGuiModel::EmitFullScreen( void ) { + viewDef_t *viewDef; + + if ( surfaces[0].numVerts == 0 ) { + return; + } + + viewDef = (viewDef_t *)R_ClearedFrameAlloc( sizeof( *viewDef ) ); + + // for gui editor + if ( !tr.viewDef || !( tr.viewDef->renderFlags & RF_IS_EDITOR ) ) { + viewDef->renderView.x = 0; + viewDef->renderView.y = 0; + viewDef->renderView.width = SCREEN_WIDTH; + viewDef->renderView.height = SCREEN_HEIGHT; + + tr.RenderViewToViewport( &viewDef->renderView, &viewDef->viewport ); + + viewDef->scissor.x1 = 0; + viewDef->scissor.y1 = 0; + viewDef->scissor.x2 = viewDef->viewport.x2 - viewDef->viewport.x1; + viewDef->scissor.y2 = viewDef->viewport.y2 - viewDef->viewport.y1; + } else { + viewDef->renderView.x = tr.viewDef->renderView.x; + viewDef->renderView.y = tr.viewDef->renderView.y; + viewDef->renderView.width = tr.viewDef->renderView.width; + viewDef->renderView.height = tr.viewDef->renderView.height; + + viewDef->viewport.x1 = tr.viewDef->renderView.x; + viewDef->viewport.x2 = tr.viewDef->renderView.x + tr.viewDef->renderView.width; + viewDef->viewport.y1 = tr.viewDef->renderView.y; + viewDef->viewport.y2 = tr.viewDef->renderView.y + tr.viewDef->renderView.height; + + viewDef->scissor.x1 = tr.viewDef->scissor.x1; + viewDef->scissor.y1 = tr.viewDef->scissor.y1; + viewDef->scissor.x2 = tr.viewDef->scissor.x2; + viewDef->scissor.y2 = tr.viewDef->scissor.y2; + } + + viewDef->floatTime = tr.frameShaderTime; + + // qglOrtho( 0, 640, 480, 0, 0, 1 ); // always assume 640x480 virtual coordinates + viewDef->projectionMatrix[0] = 2.0f / 640.0f; + viewDef->projectionMatrix[5] = -2.0f / 480.0f; + viewDef->projectionMatrix[10] = -2.0f / 1.0f; + viewDef->projectionMatrix[12] = -1.0f; + viewDef->projectionMatrix[13] = 1.0f; + viewDef->projectionMatrix[14] = -1.0f; + viewDef->projectionMatrix[15] = 1.0f; + + viewDef->worldSpace.modelViewMatrix[0] = 1.0f; + viewDef->worldSpace.modelViewMatrix[5] = 1.0f; + viewDef->worldSpace.modelViewMatrix[10] = 1.0f; + viewDef->worldSpace.modelViewMatrix[15] = 1.0f; + + viewDef->maxDrawSurfs = surfaces.Num(); + viewDef->drawSurfs = (drawSurf_t **)R_FrameAlloc( viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ) ); + viewDef->numDrawSurfs = 0; + + viewDef_t *oldViewDef = tr.viewDef; + tr.viewDef = viewDef; + + // add the surfaces to this view + for ( int i = 0 ; i < surfaces.Num() ; i++ ) { + EmitSurface( &surfaces[i], viewDef->worldSpace.modelMatrix, viewDef->worldSpace.modelViewMatrix, 0, NULL ); + } + + tr.viewDef = oldViewDef; + + // add the command to draw this view + R_AddDrawViewCmd( viewDef ); +} + +/* +============= +AdvanceSurf +============= +*/ +void idGuiModel::AdvanceSurf() { + guiModelSurface_t s; + + if ( surfaces.Num() ) { + s.color[0] = surf->color[0]; + s.color[1] = surf->color[1]; + s.color[2] = surf->color[2]; + s.color[3] = surf->color[3]; + s.material = surf->material; + } else { + s.color[0] = 1; + s.color[1] = 1; + s.color[2] = 1; + s.color[3] = 1; + s.material = tr.defaultMaterial; + } + s.numIndexes = 0; + s.firstIndex = indexes.Num(); + s.numVerts = 0; + s.firstVert = verts.Num(); + + surfaces.Append( s ); + surf = &surfaces[ surfaces.Num() - 1 ]; +} + +/* +============= +SetColor +============= +*/ +void idGuiModel::SetColor( float r, float g, float b, float a ) { + if ( !glConfig.isInitialized ) { + return; + } + if ( r == surf->color[0] && g == surf->color[1] + && b == surf->color[2] && a == surf->color[3] ) { + return; // no change + } + + if ( surf->numVerts ) { + AdvanceSurf(); + } + + // change the parms + surf->color[0] = r; + surf->color[1] = g; + surf->color[2] = b; + surf->color[3] = a; +} + +/* +============= +DrawStretchPic +============= +*/ +void idGuiModel::DrawStretchPic( const idDrawVert *dverts, const glIndex_t *dindexes, int vertCount, int indexCount, const idMaterial *hShader, + bool clip, float min_x, float min_y, float max_x, float max_y ) { + if ( !glConfig.isInitialized ) { + return; + } + if ( !( dverts && dindexes && vertCount && indexCount && hShader ) ) { + return; + } + + // break the current surface if we are changing to a new material + if ( hShader != surf->material ) { + if ( surf->numVerts ) { + AdvanceSurf(); + } + const_cast(hShader)->EnsureNotPurged(); // in case it was a gui item started before a level change + surf->material = hShader; + } + + // add the verts and indexes to the current surface + + if ( clip ) { + int i, j; + + // FIXME: this is grim stuff, and should be rewritten if we have any significant + // number of guis asking for clipping + idFixedWinding w; + for ( i = 0; i < indexCount; i += 3 ) { + w.Clear(); + w.AddPoint(idVec5(dverts[dindexes[i]].xyz.x, dverts[dindexes[i]].xyz.y, dverts[dindexes[i]].xyz.z, dverts[dindexes[i]].st.x, dverts[dindexes[i]].st.y)); + w.AddPoint(idVec5(dverts[dindexes[i+1]].xyz.x, dverts[dindexes[i+1]].xyz.y, dverts[dindexes[i+1]].xyz.z, dverts[dindexes[i+1]].st.x, dverts[dindexes[i+1]].st.y)); + w.AddPoint(idVec5(dverts[dindexes[i+2]].xyz.x, dverts[dindexes[i+2]].xyz.y, dverts[dindexes[i+2]].xyz.z, dverts[dindexes[i+2]].st.x, dverts[dindexes[i+2]].st.y)); + + for ( j = 0; j < 3; j++ ) { + if ( w[j].x < min_x || w[j].x > max_x || + w[j].y < min_y || w[j].y > max_y ) { + break; + } + } + if ( j < 3 ) { + idPlane p; + p.Normal().y = p.Normal().z = 0.0f; p.Normal().x = 1.0f; p.SetDist( min_x ); + w.ClipInPlace( p ); + p.Normal().y = p.Normal().z = 0.0f; p.Normal().x = -1.0f; p.SetDist( -max_x ); + w.ClipInPlace( p ); + p.Normal().x = p.Normal().z = 0.0f; p.Normal().y = 1.0f; p.SetDist( min_y ); + w.ClipInPlace( p ); + p.Normal().x = p.Normal().z = 0.0f; p.Normal().y = -1.0f; p.SetDist( -max_y ); + w.ClipInPlace( p ); + } + + int numVerts = verts.Num(); + verts.SetNum( numVerts + w.GetNumPoints(), false ); + for ( j = 0 ; j < w.GetNumPoints() ; j++ ) { + idDrawVert *dv = &verts[numVerts+j]; + + dv->xyz.x = w[j].x; + dv->xyz.y = w[j].y; + dv->xyz.z = w[j].z; + dv->st.x = w[j].s; + dv->st.y = w[j].t; + dv->normal.Set(0, 0, 1); + dv->tangents[0].Set(1, 0, 0); + dv->tangents[1].Set(0, 1, 0); + } + surf->numVerts += w.GetNumPoints(); + + for ( j = 2; j < w.GetNumPoints(); j++ ) { + indexes.Append( numVerts - surf->firstVert ); + indexes.Append( numVerts + j - 1 - surf->firstVert ); + indexes.Append( numVerts + j - surf->firstVert ); + surf->numIndexes += 3; + } + } + + } else { + + int numVerts = verts.Num(); + int numIndexes = indexes.Num(); + + verts.AssureSize( numVerts + vertCount ); + indexes.AssureSize( numIndexes + indexCount ); + + surf->numVerts += vertCount; + surf->numIndexes += indexCount; + + for ( int i = 0; i < indexCount; i++ ) { + indexes[numIndexes + i] = numVerts + dindexes[i] - surf->firstVert; + } + + memcpy( &verts[numVerts], dverts, vertCount * sizeof( verts[0] ) ); + } +} + +/* +============= +DrawStretchPic + +x/y/w/h are in the 0,0 to 640,480 range +============= +*/ +void idGuiModel::DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *hShader ) { + idDrawVert verts[4]; + glIndex_t indexes[6]; + + if ( !glConfig.isInitialized ) { + return; + } + if ( !hShader ) { + return; + } + + // clip to edges, because the pic may be going into a guiShader + // instead of full screen + if ( x < 0 ) { + s1 += ( s2 - s1 ) * -x / w; + w += x; + x = 0; + } + if ( y < 0 ) { + t1 += ( t2 - t1 ) * -y / h; + h += y; + y = 0; + } + if ( x + w > 640 ) { + s2 -= ( s2 - s1 ) * ( x + w - 640 ) / w; + w = 640 - x; + } + if ( y + h > 480 ) { + t2 -= ( t2 - t1 ) * ( y + h - 480 ) / h; + h = 480 - y; + } + + if ( w <= 0 || h <= 0 ) { + return; // completely clipped away + } + + indexes[0] = 3; + indexes[1] = 0; + indexes[2] = 2; + indexes[3] = 2; + indexes[4] = 0; + indexes[5] = 1; + verts[0].xyz[0] = x; + verts[0].xyz[1] = y; + verts[0].xyz[2] = 0; + verts[0].st[0] = s1; + verts[0].st[1] = t1; + verts[0].normal[0] = 0; + verts[0].normal[1] = 0; + verts[0].normal[2] = 1; + verts[0].tangents[0][0] = 1; + verts[0].tangents[0][1] = 0; + verts[0].tangents[0][2] = 0; + verts[0].tangents[1][0] = 0; + verts[0].tangents[1][1] = 1; + verts[0].tangents[1][2] = 0; + verts[1].xyz[0] = x + w; + verts[1].xyz[1] = y; + verts[1].xyz[2] = 0; + verts[1].st[0] = s2; + verts[1].st[1] = t1; + verts[1].normal[0] = 0; + verts[1].normal[1] = 0; + verts[1].normal[2] = 1; + verts[1].tangents[0][0] = 1; + verts[1].tangents[0][1] = 0; + verts[1].tangents[0][2] = 0; + verts[1].tangents[1][0] = 0; + verts[1].tangents[1][1] = 1; + verts[1].tangents[1][2] = 0; + verts[2].xyz[0] = x + w; + verts[2].xyz[1] = y + h; + verts[2].xyz[2] = 0; + verts[2].st[0] = s2; + verts[2].st[1] = t2; + verts[2].normal[0] = 0; + verts[2].normal[1] = 0; + verts[2].normal[2] = 1; + verts[2].tangents[0][0] = 1; + verts[2].tangents[0][1] = 0; + verts[2].tangents[0][2] = 0; + verts[2].tangents[1][0] = 0; + verts[2].tangents[1][1] = 1; + verts[2].tangents[1][2] = 0; + verts[3].xyz[0] = x; + verts[3].xyz[1] = y + h; + verts[3].xyz[2] = 0; + verts[3].st[0] = s1; + verts[3].st[1] = t2; + verts[3].normal[0] = 0; + verts[3].normal[1] = 0; + verts[3].normal[2] = 1; + verts[3].tangents[0][0] = 1; + verts[3].tangents[0][1] = 0; + verts[3].tangents[0][2] = 0; + verts[3].tangents[1][0] = 0; + verts[3].tangents[1][1] = 1; + verts[3].tangents[1][2] = 0; + + DrawStretchPic( &verts[0], &indexes[0], 4, 6, hShader, false, 0.0f, 0.0f, 640.0f, 480.0f ); +} + +/* +============= +DrawStretchTri + +x/y/w/h are in the 0,0 to 640,480 range +============= +*/ +void idGuiModel::DrawStretchTri( idVec2 p1, idVec2 p2, idVec2 p3, idVec2 t1, idVec2 t2, idVec2 t3, const idMaterial *material ) { + idDrawVert tempVerts[3]; + glIndex_t tempIndexes[3]; + int vertCount = 3; + int indexCount = 3; + + if ( !glConfig.isInitialized ) { + return; + } + if ( !material ) { + return; + } + + tempIndexes[0] = 1; + tempIndexes[1] = 0; + tempIndexes[2] = 2; + tempVerts[0].xyz[0] = p1.x; + tempVerts[0].xyz[1] = p1.y; + tempVerts[0].xyz[2] = 0; + tempVerts[0].st[0] = t1.x; + tempVerts[0].st[1] = t1.y; + tempVerts[0].normal[0] = 0; + tempVerts[0].normal[1] = 0; + tempVerts[0].normal[2] = 1; + tempVerts[0].tangents[0][0] = 1; + tempVerts[0].tangents[0][1] = 0; + tempVerts[0].tangents[0][2] = 0; + tempVerts[0].tangents[1][0] = 0; + tempVerts[0].tangents[1][1] = 1; + tempVerts[0].tangents[1][2] = 0; + tempVerts[1].xyz[0] = p2.x; + tempVerts[1].xyz[1] = p2.y; + tempVerts[1].xyz[2] = 0; + tempVerts[1].st[0] = t2.x; + tempVerts[1].st[1] = t2.y; + tempVerts[1].normal[0] = 0; + tempVerts[1].normal[1] = 0; + tempVerts[1].normal[2] = 1; + tempVerts[1].tangents[0][0] = 1; + tempVerts[1].tangents[0][1] = 0; + tempVerts[1].tangents[0][2] = 0; + tempVerts[1].tangents[1][0] = 0; + tempVerts[1].tangents[1][1] = 1; + tempVerts[1].tangents[1][2] = 0; + tempVerts[2].xyz[0] = p3.x; + tempVerts[2].xyz[1] = p3.y; + tempVerts[2].xyz[2] = 0; + tempVerts[2].st[0] = t3.x; + tempVerts[2].st[1] = t3.y; + tempVerts[2].normal[0] = 0; + tempVerts[2].normal[1] = 0; + tempVerts[2].normal[2] = 1; + tempVerts[2].tangents[0][0] = 1; + tempVerts[2].tangents[0][1] = 0; + tempVerts[2].tangents[0][2] = 0; + tempVerts[2].tangents[1][0] = 0; + tempVerts[2].tangents[1][1] = 1; + tempVerts[2].tangents[1][2] = 0; + + // break the current surface if we are changing to a new material + if ( material != surf->material ) { + if ( surf->numVerts ) { + AdvanceSurf(); + } + const_cast(material)->EnsureNotPurged(); // in case it was a gui item started before a level change + surf->material = material; + } + + + int numVerts = verts.Num(); + int numIndexes = indexes.Num(); + + verts.AssureSize( numVerts + vertCount ); + indexes.AssureSize( numIndexes + indexCount ); + + surf->numVerts += vertCount; + surf->numIndexes += indexCount; + + for ( int i = 0; i < indexCount; i++ ) { + indexes[numIndexes + i] = numVerts + tempIndexes[i] - surf->firstVert; + } + + memcpy( &verts[numVerts], tempVerts, vertCount * sizeof( verts[0] ) ); +} diff --git a/src/renderer/image_files.cpp b/src/renderer/image_files.cpp new file mode 100644 index 0000000..d1d0d38 --- /dev/null +++ b/src/renderer/image_files.cpp @@ -0,0 +1,1176 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* + +This file only has a single entry point: + +void R_LoadImage( const char *name, byte **pic, int *width, int *height, bool makePowerOf2 ); + +*/ + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +extern "C" { +#include "jpeg-6/jpeglib.h" + + // hooks from jpeg lib to our system + + void jpg_Error( const char *fmt, ... ) { + va_list argptr; + char msg[2048]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + common->FatalError( "%s", msg ); + } + + void jpg_Printf( const char *fmt, ... ) { + va_list argptr; + char msg[2048]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + common->Printf( "%s", msg ); + } + +} + + +/* +================ +R_WriteTGA +================ +*/ +void R_WriteTGA( const char *filename, const byte *data, int width, int height, bool flipVertical ) { + byte *buffer; + int i; + int bufferSize = width*height*4 + 18; + int imgStart = 18; + + buffer = (byte *)Mem_Alloc( bufferSize ); + memset( buffer, 0, 18 ); + buffer[2] = 2; // uncompressed type + buffer[12] = width&255; + buffer[13] = width>>8; + buffer[14] = height&255; + buffer[15] = height>>8; + buffer[16] = 32; // pixel size + if ( !flipVertical ) { + buffer[17] = (1<<5); // flip bit, for normal top to bottom raster order + } + + // swap rgb to bgr + for ( i=imgStart ; iWriteFile( filename, buffer, bufferSize, + image_writeProgramImages.GetBool() ? "fs_devpath" : "fs_savepath" ); + + Mem_Free (buffer); +} + + +/* +================ +R_WritePalTGA +================ +*/ +void R_WritePalTGA( const char *filename, const byte *data, const byte *palette, int width, int height, bool flipVertical ) { + byte *buffer; + int i; + int bufferSize = (width * height) + (256 * 3) + 18; + int palStart = 18; + int imgStart = 18 + (256 * 3); + + buffer = (byte *)Mem_Alloc( bufferSize ); + memset( buffer, 0, 18 ); + buffer[1] = 1; // color map type + buffer[2] = 1; // uncompressed color mapped image + buffer[5] = 0; // number of palette entries (lo) + buffer[6] = 1; // number of palette entries (hi) + buffer[7] = 24; // color map bpp + buffer[12] = width&255; + buffer[13] = width>>8; + buffer[14] = height&255; + buffer[15] = height>>8; + buffer[16] = 8; // pixel size + if ( !flipVertical ) { + buffer[17] = (1<<5); // flip bit, for normal top to bottom raster order + } + + // store palette, swapping rgb to bgr + for ( i=palStart ; iWriteFile( filename, buffer, bufferSize ); + + Mem_Free (buffer); +} + + +static void LoadBMP( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ); +static void LoadTGA( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ); +static void LoadJPG( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ); + + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + + +/* +========================================================= + +BMP LOADING + +========================================================= +*/ +typedef struct +{ + char id[2]; + unsigned long fileSize; + unsigned long reserved0; + unsigned long bitmapDataOffset; + unsigned long bitmapHeaderSize; + unsigned long width; + unsigned long height; + unsigned short planes; + unsigned short bitsPerPixel; + unsigned long compression; + unsigned long bitmapDataSize; + unsigned long hRes; + unsigned long vRes; + unsigned long colors; + unsigned long importantColors; + unsigned char palette[256][4]; +} BMPHeader_t; + +/* +============== +LoadBMP +============== +*/ +static void LoadBMP( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ) +{ + int columns, rows, numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *buffer; + int length; + BMPHeader_t bmpHeader; + byte *bmpRGBA; + + if ( !pic ) { + fileSystem->ReadFile ( name, NULL, timestamp ); + return; // just getting timestamp + } + + *pic = NULL; + + // + // load the file + // + length = fileSystem->ReadFile( name, (void **)&buffer, timestamp ); + if ( !buffer ) { + return; + } + + buf_p = buffer; + + bmpHeader.id[0] = *buf_p++; + bmpHeader.id[1] = *buf_p++; + bmpHeader.fileSize = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.reserved0 = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataOffset = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapHeaderSize = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.width = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.height = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.planes = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.compression = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataSize = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.hRes = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.vRes = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.colors = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + bmpHeader.importantColors = LittleLong( * ( long * ) buf_p ); + buf_p += 4; + + memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); + + if ( bmpHeader.bitsPerPixel == 8 ) + buf_p += 1024; + + if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) + { + common->Error( "LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + } + if ( bmpHeader.fileSize != length ) + { + common->Error( "LoadBMP: header size does not match file size (%lu vs. %d) (%s)\n", bmpHeader.fileSize, length, name ); + } + if ( bmpHeader.compression != 0 ) + { + common->Error( "LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + } + if ( bmpHeader.bitsPerPixel < 8 ) + { + common->Error( "LoadBMP: monochrome and 4-bit BMP files not supported (%s)\n", name ); + } + + columns = bmpHeader.width; + rows = bmpHeader.height; + if ( rows < 0 ) + rows = -rows; + numPixels = columns * rows; + + if ( width ) + *width = columns; + if ( height ) + *height = rows; + + bmpRGBA = (byte *)R_StaticAlloc( numPixels * 4 ); + *pic = bmpRGBA; + + + for ( row = rows-1; row >= 0; row-- ) + { + pixbuf = bmpRGBA + row*columns*4; + + for ( column = 0; column < columns; column++ ) + { + unsigned char red, green, blue, alpha; + int palIndex; + unsigned short shortPixel; + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + palIndex = *buf_p++; + *pixbuf++ = bmpHeader.palette[palIndex][2]; + *pixbuf++ = bmpHeader.palette[palIndex][1]; + *pixbuf++ = bmpHeader.palette[palIndex][0]; + *pixbuf++ = 0xff; + break; + case 16: + shortPixel = * ( unsigned short * ) pixbuf; + pixbuf += 2; + *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7; + *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2; + *pixbuf++ = ( shortPixel & ( 31 ) ) << 3; + *pixbuf++ = 0xff; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + default: + common->Error( "LoadBMP: illegal pixel_size '%d' in file '%s'\n", bmpHeader.bitsPerPixel, name ); + break; + } + } + } + + fileSystem->FreeFile( buffer ); + +} + + +/* +================================================================= + +PCX LOADING + +================================================================= +*/ + + +/* +============== +LoadPCX +============== +*/ +static void LoadPCX ( const char *filename, byte **pic, byte **palette, int *width, int *height, + ID_TIME_T *timestamp ) { + byte *raw; + pcx_t *pcx; + int x, y; + int len; + int dataByte, runLength; + byte *out, *pix; + int xmax, ymax; + + if ( !pic ) { + fileSystem->ReadFile( filename, NULL, timestamp ); + return; // just getting timestamp + } + + *pic = NULL; + *palette = NULL; + + // + // load the file + // + len = fileSystem->ReadFile( filename, (void **)&raw, timestamp ); + if (!raw) { + return; + } + + // + // parse the PCX file + // + pcx = (pcx_t *)raw; + raw = &pcx->data; + + xmax = LittleShort(pcx->xmax); + ymax = LittleShort(pcx->ymax); + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->bits_per_pixel != 8 + || xmax >= 1024 + || ymax >= 1024) + { + common->Printf( "Bad pcx file %s (%i x %i) (%i x %i)\n", filename, xmax+1, ymax+1, pcx->xmax, pcx->ymax); + return; + } + + out = (byte *)R_StaticAlloc( (ymax+1) * (xmax+1) ); + + *pic = out; + + pix = out; + + if (palette) + { + *palette = (byte *)R_StaticAlloc(768); + memcpy (*palette, (byte *)pcx + len - 768, 768); + } + + if (width) + *width = xmax+1; + if (height) + *height = ymax+1; +// FIXME: use bytes_per_line here? + + for (y=0 ; y<=ymax ; y++, pix += xmax+1) + { + for (x=0 ; x<=xmax ; ) + { + dataByte = *raw++; + + if((dataByte & 0xC0) == 0xC0) + { + runLength = dataByte & 0x3F; + dataByte = *raw++; + } + else + runLength = 1; + + while(runLength-- > 0) + pix[x++] = dataByte; + } + + } + + if ( raw - (byte *)pcx > len) + { + common->Printf( "PCX file %s was malformed", filename ); + R_StaticFree (*pic); + *pic = NULL; + } + + fileSystem->FreeFile( pcx ); +} + + +/* +============== +LoadPCX32 +============== +*/ +static void LoadPCX32 ( const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp) { + byte *palette; + byte *pic8; + int i, c, p; + byte *pic32; + + if ( !pic ) { + fileSystem->ReadFile( filename, NULL, timestamp ); + return; // just getting timestamp + } + LoadPCX (filename, &pic8, &palette, width, height, timestamp); + if (!pic8) { + *pic = NULL; + return; + } + + c = (*width) * (*height); + pic32 = *pic = (byte *)R_StaticAlloc(4 * c ); + for (i = 0 ; i < c ; i++) { + p = pic8[i]; + pic32[0] = palette[p*3]; + pic32[1] = palette[p*3 + 1]; + pic32[2] = palette[p*3 + 2]; + pic32[3] = 255; + pic32 += 4; + } + + R_StaticFree( pic8 ); + R_StaticFree( palette ); +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +/* +============= +LoadTGA +============= +*/ +static void LoadTGA( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp ) { + int columns, rows, numPixels, fileSize, numBytes; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *buffer; + TargaHeader targa_header; + byte *targa_rgba; + + if ( !pic ) { + fileSystem->ReadFile( name, NULL, timestamp ); + return; // just getting timestamp + } + + *pic = NULL; + + // + // load the file + // + fileSize = fileSystem->ReadFile( name, (void **)&buffer, timestamp ); + if ( !buffer ) { + return; + } + + buf_p = buffer; + + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_length = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.y_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.width = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.height = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + if ( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) { + common->Error( "LoadTGA( %s ): Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n", name ); + } + + if ( targa_header.colormap_type != 0 ) { + common->Error( "LoadTGA( %s ): colormaps not supported\n", name ); + } + + if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) { + common->Error( "LoadTGA( %s ): Only 32 or 24 bit images supported (no colormaps)\n", name ); + } + + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) { + numBytes = targa_header.width * targa_header.height * ( targa_header.pixel_size >> 3 ); + if ( numBytes > fileSize - 18 - targa_header.id_length ) { + common->Error( "LoadTGA( %s ): incomplete file\n", name ); + } + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if ( width ) { + *width = columns; + } + if ( height ) { + *height = rows; + } + + targa_rgba = (byte *)R_StaticAlloc(numPixels*4); + *pic = targa_rgba; + + if ( targa_header.id_length != 0 ) { + buf_p += targa_header.id_length; // skip TARGA image comment + } + + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) + { + // Uncompressed RGB or gray scale image + for( row = rows - 1; row >= 0; row-- ) + { + pixbuf = targa_rgba + row*columns*4; + for( column = 0; column < columns; column++) + { + unsigned char red,green,blue,alphabyte; + switch( targa_header.pixel_size ) + { + + case 8: + blue = *buf_p++; + green = blue; + red = blue; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + } + } + } + else if ( targa_header.image_type == 10 ) { // Runlength encoded RGB images + unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j; + + red = 0; + green = 0; + blue = 0; + alphabyte = 0xff; + + for( row = rows - 1; row >= 0; row-- ) { + pixbuf = targa_rgba + row*columns*4; + for( column = 0; column < columns; ) { + packetHeader= *buf_p++; + packetSize = 1 + (packetHeader & 0x7f); + if ( packetHeader & 0x80 ) { // run-length packet + switch( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + + for( j = 0; j < packetSize; j++ ) { + *pixbuf++=red; + *pixbuf++=green; + *pixbuf++=blue; + *pixbuf++=alphabyte; + column++; + if ( column == columns ) { // run spans across rows + column = 0; + if ( row > 0) { + row--; + } + else { + goto breakOut; + } + pixbuf = targa_rgba + row*columns*4; + } + } + } + else { // non run-length packet + for( j = 0; j < packetSize; j++ ) { + switch( targa_header.pixel_size ) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + column++; + if ( column == columns ) { // pixel packet run spans across rows + column = 0; + if ( row > 0 ) { + row--; + } + else { + goto breakOut; + } + pixbuf = targa_rgba + row*columns*4; + } + } + } + } + breakOut: ; + } + } + + if ( (targa_header.attributes & (1<<5)) ) { // image flp bit + R_VerticalFlip( *pic, *width, *height ); + } + + fileSystem->FreeFile( buffer ); +} + +/* +========================================================= + +JPG LOADING + +Interfaces with the huge libjpeg +========================================================= +*/ + +/* +============= +LoadJPG +============= +*/ +static void LoadJPG( const char *filename, unsigned char **pic, int *width, int *height, ID_TIME_T *timestamp ) { + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char *out; + byte *fbuffer; + byte *bbuf; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + // JDC: because fill_input_buffer() blindly copies INPUT_BUF_SIZE bytes, + // we need to make sure the file buffer is padded or it may crash + if ( pic ) { + *pic = NULL; // until proven otherwise + } + { + int len; + idFile *f; + + f = fileSystem->OpenFileRead( filename ); + if ( !f ) { + return; + } + len = f->Length(); + if ( timestamp ) { + *timestamp = f->Timestamp(); + } + if ( !pic ) { + fileSystem->CloseFile( f ); + return; // just getting timestamp + } + fbuffer = (byte *)Mem_ClearedAlloc( len + 4096 ); + f->Read( fbuffer, len ); + fileSystem->CloseFile( f ); + } + + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src(&cinfo, fbuffer); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, true ); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + if (cinfo.output_components!=4) { + common->DWarning( "JPG %s is unsupported color depth (%d)", + filename, cinfo.output_components); + } + out = (byte *)R_StaticAlloc(cinfo.output_width*cinfo.output_height*4); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + bbuf = ((out+(row_stride*cinfo.output_scanline))); + buffer = &bbuf; + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + } + + // clear all the alphas to 255 + { + int i, j; + byte *buf; + + buf = *pic; + + j = cinfo.output_width * cinfo.output_height * 4; + for ( i = 3 ; i < j ; i+=4 ) { + buf[i] = 255; + } + } + + /* Step 7: Finish decompression */ + + (void) jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + Mem_Free( fbuffer ); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ +} + +//=================================================================== + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. + +Automatically attempts to load .jpg files if .tga files fail to load. + +*pic will be NULL if the load failed. + +Anything that is going to make this into a texture would use +makePowerOf2 = true, but something loading an image as a lookup +table of some sort would leave it in identity form. + +It is important to do this at image load time instead of texture load +time for bump maps. + +Timestamp may be NULL if the value is going to be ignored + +If pic is NULL, the image won't actually be loaded, it will just find the +timestamp. +================= +*/ +void R_LoadImage( const char *cname, byte **pic, int *width, int *height, ID_TIME_T *timestamp, bool makePowerOf2 ) { + idStr name = cname; + + if ( pic ) { + *pic = NULL; + } + if ( timestamp ) { + *timestamp = 0xFFFFFFFF; + } + if ( width ) { + *width = 0; + } + if ( height ) { + *height = 0; + } + + if ( name.Length() < 1 ) { + return; + } + + name.DefaultFileExtension( ".tga" ); + name.ToLower(); + idStr ext; + name.ExtractFileExtension( ext ); + + if ( ext == "tga" ) { + LoadTGA( name.c_str(), pic, width, height, timestamp ); // try tga first + if ( ( pic && *pic == 0 ) || ( timestamp && *timestamp == -1 ) ) { + name.StripFileExtension(); + name.DefaultFileExtension( ".jpg" ); + LoadJPG( name.c_str(), pic, width, height, timestamp ); + } + } else if ( ext == "jpg" ) { + LoadJPG( name.c_str(), pic, width, height, timestamp ); + } + + if ( ( width && *width < 1 ) || ( height && *height < 1 ) ) { + if ( pic && *pic ) { + R_StaticFree( *pic ); + *pic = 0; + } + } + + // + // convert to exact power of 2 sizes + // + if ( pic && *pic && makePowerOf2 ) { + int w, h; + int scaled_width, scaled_height; + byte *resampledBuffer; + + w = *width; + h = *height; + + for (scaled_width = 1 ; scaled_width < w ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < h ; scaled_height<<=1) + ; + + if ( scaled_width != w || scaled_height != h ) { + if ( scaled_width > w ) { + scaled_width >>= 1; + } + if ( scaled_height > h ) { + scaled_height >>= 1; + } + + resampledBuffer = R_ResampleTexture( *pic, w, h, scaled_width, scaled_height ); + R_StaticFree( *pic ); + *pic = resampledBuffer; + *width = scaled_width; + *height = scaled_height; + } + } +} + + +/* +======================= +R_LoadCubeImages + +Loads six files with proper extensions +======================= +*/ +bool R_LoadCubeImages( const char *imgName, cubeFiles_t extensions, byte *pics[6], int *outSize, ID_TIME_T *timestamp ) { + int i, j; + char *cameraSides[6] = { "_forward.tga", "_back.tga", "_left.tga", "_right.tga", + "_up.tga", "_down.tga" }; + char *axisSides[6] = { "_px.tga", "_nx.tga", "_py.tga", "_ny.tga", + "_pz.tga", "_nz.tga" }; + char **sides; + char fullName[MAX_IMAGE_NAME]; + int width, height, size = 0; + const bool savedWriteProgramFlag = gWriteProgramFlag; + + if ( extensions == CF_CAMERA ) { + sides = cameraSides; + } else { + sides = axisSides; + } + + // FIXME: precompressed cube map files + if ( pics ) { + memset( pics, 0, 6*sizeof(pics[0]) ); + } + if ( timestamp ) { + *timestamp = 0; + } + + for ( i = 0 ; i < 6 ; i++ ) { + idStr::snPrintf( fullName, sizeof( fullName ), "%s%s", imgName, sides[i] ); + + ID_TIME_T thisTime; + if ( !pics ) { + // just checking timestamps + R_LoadImageProgram( fullName, NULL, &width, &height, &thisTime ); + } else { + R_LoadImageProgram( fullName, &pics[i], &width, &height, &thisTime ); + } + if ( thisTime == FILE_NOT_FOUND_TIMESTAMP ) { + break; + } + if ( i == 0 ) { + size = width; + } + if ( width != size || height != size ) { + common->Warning( "Mismatched sizes on cube map '%s'", imgName ); + break; + } + if ( timestamp ) { + if ( thisTime > *timestamp ) { + *timestamp = thisTime; + } + } + if ( pics && extensions == CF_CAMERA ) { + // convert from "camera" images to native cube map images + switch( i ) { + case 0: // forward + R_RotatePic( pics[i], width); + break; + case 1: // back + R_RotatePic( pics[i], width); + R_HorizontalFlip( pics[i], width, height ); + R_VerticalFlip( pics[i], width, height ); + break; + case 2: // left + R_VerticalFlip( pics[i], width, height ); + break; + case 3: // right + R_HorizontalFlip( pics[i], width, height ); + break; + case 4: // up + R_RotatePic( pics[i], width); + break; + case 5: // down + R_RotatePic( pics[i], width); + break; + } + } + } + + if ( *timestamp > ProgramImagesTimestamp ) { + gWriteProgramFlag = savedWriteProgramFlag; + } + + if ( i != 6 ) { + // we had an error, so free everything + if ( pics ) { + for ( j = 0 ; j < i ; j++ ) { + R_StaticFree( pics[j] ); + } + } + + *timestamp = 0; + return false; + } + + if ( outSize ) { + *outSize = size; + } + return true; +} diff --git a/src/renderer/image_init.cpp b/src/renderer/image_init.cpp new file mode 100644 index 0000000..83e5e1b --- /dev/null +++ b/src/renderer/image_init.cpp @@ -0,0 +1,2250 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvTexRenderTarget.h" + +const char *imageFilter[] = { + "GL_LINEAR_MIPMAP_NEAREST", + "GL_LINEAR_MIPMAP_LINEAR", + "GL_NEAREST", + "GL_LINEAR", + "GL_NEAREST_MIPMAP_NEAREST", + "GL_NEAREST_MIPMAP_LINEAR", + NULL +}; + +idCVar idImageManager::image_filter( "image_filter", imageFilter[1], CVAR_RENDERER | CVAR_ARCHIVE, "changes texture filtering on mipmapped images", imageFilter, idCmdSystem::ArgCompletion_String ); +idCVar idImageManager::image_anisotropy( "image_anisotropy", "1", CVAR_RENDERER | CVAR_ARCHIVE, "set the maximum texture anisotropy if available" ); +idCVar idImageManager::image_lodbias( "image_lodbias", "0", CVAR_RENDERER | CVAR_ARCHIVE, "change lod bias on mipmapped images" ); +idCVar idImageManager::image_downSize( "image_downSize", "0", CVAR_RENDERER | CVAR_ARCHIVE, "controls texture downsampling" ); +idCVar idImageManager::image_forceDownSize( "image_forceDownSize", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar idImageManager::image_roundDown( "image_roundDown", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "round bad sizes down to nearest power of two" ); +idCVar idImageManager::image_colorMipLevels( "image_colorMipLevels", "0", CVAR_RENDERER | CVAR_BOOL, "development aid to see texture mip usage" ); +idCVar idImageManager::image_preload( "image_preload", "1", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "if 0, dynamically load all images" ); +idCVar idImageManager::image_useCompression( "image_useCompression", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "0 = force everything to high quality" ); +idCVar idImageManager::image_useAllFormats( "image_useAllFormats", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "allow alpha/intensity/luminance/luminance+alpha" ); +idCVar idImageManager::image_useNormalCompression( "image_useNormalCompression", "2", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "2 = use rxgb compression for normal maps, 1 = use 256 color compression for normal maps if available" ); +idCVar idImageManager::image_usePrecompressedTextures( "image_usePrecompressedTextures", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "use .dds files if present" ); +idCVar idImageManager::image_writePrecompressedTextures( "image_writePrecompressedTextures", "0", CVAR_RENDERER | CVAR_BOOL, "write .dds files if necessary" ); +idCVar idImageManager::image_writeNormalTGA( "image_writeNormalTGA", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the final normal maps for debugging" ); +idCVar idImageManager::image_writeNormalTGAPalletized( "image_writeNormalTGAPalletized", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the final palletized normal maps for debugging" ); +idCVar idImageManager::image_writeTGA( "image_writeTGA", "0", CVAR_RENDERER | CVAR_BOOL, "write .tgas of the non normal maps for debugging" ); +idCVar idImageManager::image_useOffLineCompression( "image_useOfflineCompression", "0", CVAR_RENDERER | CVAR_BOOL, "write a batch file for offline compression of DDS files" ); +idCVar idImageManager::image_cacheMinK( "image_cacheMinK", "200", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "maximum KB of precompressed files to read at specification time" ); +idCVar idImageManager::image_cacheMegs( "image_cacheMegs", "20", CVAR_RENDERER | CVAR_ARCHIVE, "maximum MB set aside for temporary loading of full-sized precompressed images" ); +idCVar idImageManager::image_useCache( "image_useCache", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "1 = do background load image caching" ); +idCVar idImageManager::image_showBackgroundLoads( "image_showBackgroundLoads", "0", CVAR_RENDERER | CVAR_BOOL, "1 = print number of outstanding background loads" ); +idCVar idImageManager::image_downSizeSpecular( "image_downSizeSpecular", "0", CVAR_RENDERER | CVAR_ARCHIVE, "controls specular downsampling" ); +idCVar idImageManager::image_downSizeBump( "image_downSizeBump", "0", CVAR_RENDERER | CVAR_ARCHIVE, "controls normal map downsampling" ); +idCVar idImageManager::image_downSizeSpecularLimit( "image_downSizeSpecularLimit", "64", CVAR_RENDERER | CVAR_ARCHIVE, "controls specular downsampled limit" ); +idCVar idImageManager::image_downSizeBumpLimit( "image_downSizeBumpLimit", "128", CVAR_RENDERER | CVAR_ARCHIVE, "controls normal map downsample limit" ); +idCVar idImageManager::image_ignoreHighQuality( "image_ignoreHighQuality", "0", CVAR_RENDERER | CVAR_ARCHIVE, "ignore high quality setting on materials" ); +idCVar idImageManager::image_downSizeLimit( "image_downSizeLimit", "256", CVAR_RENDERER | CVAR_ARCHIVE, "controls diffuse map downsample limit" ); +idCVar image_writeProgramImages( "image_writeProgramImages", "0", CVAR_RENDERER | CVAR_BOOL, "copy program targas to compression directory", idCmdSystem::ArgCompletion_Boolean ); +// do this with a pointer, in case we want to make the actual manager +// a private virtual subclass +idImageManager imageManager; +idImageManager *globalImages = &imageManager; + +// Retail Image_init.obj supplies this empty base hook. Pbuffer-backed images +// override it to restore the previous render context. +void idImage::UnBind() { +} + + +enum IMAGE_CLASSIFICATION { + IC_NPC, + IC_WEAPON, + IC_MONSTER, + IC_MODELGEOMETRY, + IC_ITEMS, + IC_MODELSOTHER, + IC_GUIS, + IC_WORLDGEOMETRY, + IC_OTHER, + IC_COUNT +}; + +struct imageClassificate_t { + const char *rootPath; + const char *desc; + int type; + int maxWidth; + int maxHeight; +}; + +typedef idList< int > intList; + +const imageClassificate_t IC_Info[] = { + { "models/characters", "Characters", IC_NPC, 512, 512 }, + { "models/weapons", "Weapons", IC_WEAPON, 512, 512 }, + { "models/monsters", "Monsters", IC_MONSTER, 512, 512 }, + { "models/mapobjects", "Model Geometry", IC_MODELGEOMETRY, 512, 512 }, + { "models/items", "Items", IC_ITEMS, 512, 512 }, + { "models", "Other model textures", IC_MODELSOTHER, 512, 512 }, + { "guis/assets", "Guis", IC_GUIS, 256, 256 }, + { "textures", "World Geometry", IC_WORLDGEOMETRY, 256, 256 }, + { "", "Other", IC_OTHER, 256, 256 } +}; + + + +static int ClassifyImage( const char *name ) { + idStr str; + str = name; + for ( int i = 0; i < IC_COUNT; i++ ) { + if ( str.Find( IC_Info[i].rootPath, false ) == 0 ) { + return IC_Info[i].type; + } + } + return IC_OTHER; +} + +/* +================ +R_RampImage + +Creates a 0-255 ramp image +================ +*/ +static void R_RampImage( idImage *image ) { + int x; + byte data[256][4]; + + for (x=0 ; x<256 ; x++) { + data[x][0] = + data[x][1] = + data[x][2] = + data[x][3] = x; + } + + image->GenerateImage( (byte *)data, 256, 1, + TF_NEAREST, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + +/* +================ +R_SpecularTableImage + +Creates a ramp that matches our fudged specular calculation +================ +*/ +static void R_SpecularTableImage( idImage *image ) { + int x; + byte data[256][4]; + + for (x=0 ; x<256 ; x++) { + float f = x/255.f; +#if 0 + f = pow(f, 16); +#else + // this is the behavior of the hacked up fragment programs that + // can't really do a power function + f = (f-0.75)*4; + if ( f < 0 ) { + f = 0; + } + f = f * f; +#endif + int b = (int)(f * 255); + + data[x][0] = + data[x][1] = + data[x][2] = + data[x][3] = b; + } + + image->GenerateImage( (byte *)data, 256, 1, + TF_LINEAR, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + + +/* +================ +R_Specular2DTableImage + +Create a 2D table that calculates ( reflection dot , specularity ) +================ +*/ +static void R_Specular2DTableImage( idImage *image ) { + int x, y; + byte data[256][256][4]; + + memset( data, 0, sizeof( data ) ); + for ( x = 0 ; x < 256 ; x++ ) { + float f = x / 255.0f; + for ( y = 0; y < 256; y++ ) { + + int b = (int)( pow( f, y ) * 255.0f ); + if ( b == 0 ) { + // as soon as b equals zero all remaining values in this column are going to be zero + // we early out to avoid pow() underflows + break; + } + + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = + data[y][x][3] = b; + } + } + + image->GenerateImage( (byte *)data, 256, 256, TF_LINEAR, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + + + +/* +================ +R_AlphaRampImage + +Creates a 0-255 ramp image +================ +*/ +static void R_AlphaRampImage( idImage *image ) { + int x; + byte data[256][4]; + + for (x=0 ; x<256 ; x++) { + data[x][0] = + data[x][1] = + data[x][2] = 255; + data[x][3] = x; + } + + image->GenerateImage( (byte *)data, 256, 1, + TF_NEAREST, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + + + +/* +================== +R_CreateDefaultImage + +the default image will be grey with a white box outline +to allow you to see the mapping coordinates on a surface +================== +*/ +#define DEFAULT_SIZE 16 +void idImage::MakeDefault() { + int x, y; + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + if ( com_developer.GetBool() ) { + // grey center + for ( y = 0 ; y < DEFAULT_SIZE ; y++ ) { + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + data[y][x][0] = 32; + data[y][x][1] = 32; + data[y][x][2] = 32; + data[y][x][3] = 255; + } + } + + // white border + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + data[0][x][0] = + data[0][x][1] = + data[0][x][2] = + data[0][x][3] = 255; + + data[x][0][0] = + data[x][0][1] = + data[x][0][2] = + data[x][0][3] = 255; + + data[DEFAULT_SIZE-1][x][0] = + data[DEFAULT_SIZE-1][x][1] = + data[DEFAULT_SIZE-1][x][2] = + data[DEFAULT_SIZE-1][x][3] = 255; + + data[x][DEFAULT_SIZE-1][0] = + data[x][DEFAULT_SIZE-1][1] = + data[x][DEFAULT_SIZE-1][2] = + data[x][DEFAULT_SIZE-1][3] = 255; + } + } else { + for ( y = 0 ; y < DEFAULT_SIZE ; y++ ) { + for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) { + data[y][x][0] = 0; + data[y][x][1] = 0; + data[y][x][2] = 0; + data[y][x][3] = 0; + } + } + } + + GenerateImage( (byte *)data, + DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, true, TR_REPEAT, TD_DEFAULT ); + + defaulted = true; +} + +static void R_DefaultImage( idImage *image ) { + image->MakeDefault(); +} + +static void R_WhiteImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + // solid white texture + memset( data, 255, sizeof( data ) ); + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, false, TR_REPEAT, TD_DEFAULT ); +} + +static void R_BlackImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + // solid black texture + memset( data, 0, sizeof( data ) ); + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, false, TR_REPEAT, TD_DEFAULT ); +} + + +// the size determines how far away from the edge the blocks start fading +static const int BORDER_CLAMP_SIZE = 32; +static void R_BorderClampImage( idImage *image ) { + byte data[BORDER_CLAMP_SIZE][BORDER_CLAMP_SIZE][4]; + + // solid white texture with a single pixel black border + memset( data, 255, sizeof( data ) ); + for ( int i = 0 ; i < BORDER_CLAMP_SIZE ; i++ ) { + data[i][0][0] = + data[i][0][1] = + data[i][0][2] = + data[i][0][3] = + + data[i][BORDER_CLAMP_SIZE-1][0] = + data[i][BORDER_CLAMP_SIZE-1][1] = + data[i][BORDER_CLAMP_SIZE-1][2] = + data[i][BORDER_CLAMP_SIZE-1][3] = + + data[0][i][0] = + data[0][i][1] = + data[0][i][2] = + data[0][i][3] = + + data[BORDER_CLAMP_SIZE-1][i][0] = + data[BORDER_CLAMP_SIZE-1][i][1] = + data[BORDER_CLAMP_SIZE-1][i][2] = + data[BORDER_CLAMP_SIZE-1][i][3] = 0; + } + + image->GenerateImage( (byte *)data, BORDER_CLAMP_SIZE, BORDER_CLAMP_SIZE, + TF_LINEAR /* TF_NEAREST */, false, TR_CLAMP_TO_BORDER, TD_DEFAULT ); + + if ( !glConfig.isInitialized ) { + // can't call qglTexParameterfv yet + return; + } + // explicit zero border + float color[4]; + color[0] = color[1] = color[2] = color[3] = 0; + qglTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color ); +} + +static void R_RGBA8Image( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + memset( data, 0, sizeof( data ) ); + data[0][0][0] = 16; + data[0][0][1] = 32; + data[0][0][2] = 48; + data[0][0][3] = 96; + + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, false, TR_REPEAT, TD_HIGH_QUALITY ); +} + +static void R_RGB8Image( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + + memset( data, 0, sizeof( data ) ); + data[0][0][0] = 16; + data[0][0][1] = 32; + data[0][0][2] = 48; + data[0][0][3] = 255; + + image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, + TF_DEFAULT, false, TR_REPEAT, TD_HIGH_QUALITY ); +} + +static void R_AlphaNotchImage( idImage *image ) { + byte data[2][4]; + + // this is used for alpha test clip planes + + data[0][0] = data[0][1] = data[0][2] = 255; + data[0][3] = 0; + data[1][0] = data[1][1] = data[1][2] = 255; + data[1][3] = 255; + + image->GenerateImage( (byte *)data, 2, 1, + TF_NEAREST, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + +static void R_FlatNormalImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + int i; + + int red = ( globalImages->image_useNormalCompression.GetInteger() == 1 ) ? 0 : 3; + int alpha = ( red == 0 ) ? 3 : 0; + // flat normal map for default bunp mapping + for ( i = 0 ; i < 4 ; i++ ) { + data[0][i][red] = 128; + data[0][i][1] = 128; + data[0][i][2] = 255; + data[0][i][alpha] = 255; + } + image->GenerateImage( (byte *)data, 2, 2, + TF_DEFAULT, true, TR_REPEAT, TD_HIGH_QUALITY ); +} + +static void R_AmbientNormalImage( idImage *image ) { + byte data[DEFAULT_SIZE][DEFAULT_SIZE][4]; + int i; + + int red = ( globalImages->image_useNormalCompression.GetInteger() == 1 ) ? 0 : 3; + int alpha = ( red == 0 ) ? 3 : 0; + // flat normal map for default bunp mapping + for ( i = 0 ; i < 4 ; i++ ) { + data[0][i][red] = (byte)(255 * tr.ambientLightVector[0]); + data[0][i][1] = (byte)(255 * tr.ambientLightVector[1]); + data[0][i][2] = (byte)(255 * tr.ambientLightVector[2]); + data[0][i][alpha] = 255; + } + const byte *pics[6]; + for ( i = 0 ; i < 6 ; i++ ) { + pics[i] = data[0][0]; + } + // this must be a cube map for fragment programs to simply substitute for the normalization cube map + image->GenerateCubeImage( pics, 2, TF_DEFAULT, true, TD_HIGH_QUALITY ); +} + + +static void CreateSquareLight( void ) { + byte *buffer; + int x, y; + int dx, dy; + int d; + int width, height; + + width = height = 128; + + buffer = (byte *)R_StaticAlloc( 128 * 128 * 4 ); + + for ( x = 0 ; x < 128 ; x++ ) { + if ( x < 32 ) { + dx = 32 - x; + } else if ( x > 96 ) { + dx = x - 96; + } else { + dx = 0; + } + for ( y = 0 ; y < 128 ; y++ ) { + if ( y < 32 ) { + dy = 32 - y; + } else if ( y > 96 ) { + dy = y - 96; + } else { + dy = 0; + } + d = (byte)idMath::Sqrt( dx * dx + dy * dy ); + if ( d > 32 ) { + d = 32; + } + d = 255 - d * 8; + if ( d < 0 ) { + d = 0; + } + buffer[(y*128+x)*4+0] = + buffer[(y*128+x)*4+1] = + buffer[(y*128+x)*4+2] = d; + buffer[(y*128+x)*4+3] = 255; + } + } + + R_WriteTGA( "lights/squarelight.tga", buffer, width, height ); + + R_StaticFree( buffer ); +} + +static void CreateFlashOff( void ) { + byte *buffer; + int x, y; + int d; + int width, height; + + width = 256; + height = 4; + + buffer = (byte *)R_StaticAlloc( width * height * 4 ); + + for ( x = 0 ; x < width ; x++ ) { + for ( y = 0 ; y < height ; y++ ) { + d = 255 - ( x * 256 / width ); + buffer[(y*width+x)*4+0] = + buffer[(y*width+x)*4+1] = + buffer[(y*width+x)*4+2] = d; + buffer[(y*width+x)*4+3] = 255; + } + } + + R_WriteTGA( "lights/flashoff.tga", buffer, width, height ); + + R_StaticFree( buffer ); +} + + +/* +=============== +CreatePitFogImage +=============== +*/ +void CreatePitFogImage( void ) { + byte data[16][16][4]; + int i, j; + + memset( data, 0, sizeof( data ) ); + for ( i = 0 ; i < 16 ; i++ ) { + int a; + +#if 0 + if ( i > 14 ) { + a = 0; + } else +#endif + { + a = i * 255 / 15; + if ( a > 255 ) { + a = 255; + } + } + + for ( j = 0 ; j < 16 ; j++ ) { + data[j][i][0] = + data[j][i][1] = + data[j][i][2] = 255; + data[j][i][3] = a; + } + } + + R_WriteTGA( "shapes/pitFalloff.tga", data[0][0], 16, 16 ); +} + +/* +=============== +CreatealphaSquareImage +=============== +*/ +void CreatealphaSquareImage( void ) { + byte data[16][16][4]; + int i, j; + + for ( i = 0 ; i < 16 ; i++ ) { + int a; + + for ( j = 0 ; j < 16 ; j++ ) { + if ( i == 0 || i == 15 || j == 0 || j == 15 ) { + a = 0; + } else { + a = 255; + } + data[j][i][0] = + data[j][i][1] = + data[j][i][2] = 255; + data[j][i][3] = a; + } + } + + R_WriteTGA( "shapes/alphaSquare.tga", data[0][0], 16, 16 ); +} + +#define NORMAL_MAP_SIZE 32 + +/*** NORMALIZATION CUBE MAP CONSTRUCTION ***/ + +/* Given a cube map face index, cube map size, and integer 2D face position, + * return the cooresponding normalized vector. + */ +static void getCubeVector(int i, int cubesize, int x, int y, float *vector) { + float s, t, sc, tc, mag; + + s = ((float)x + 0.5) / (float)cubesize; + t = ((float)y + 0.5) / (float)cubesize; + sc = s*2.0 - 1.0; + tc = t*2.0 - 1.0; + + switch (i) { + case 0: + vector[0] = 1.0; + vector[1] = -tc; + vector[2] = -sc; + break; + case 1: + vector[0] = -1.0; + vector[1] = -tc; + vector[2] = sc; + break; + case 2: + vector[0] = sc; + vector[1] = 1.0; + vector[2] = tc; + break; + case 3: + vector[0] = sc; + vector[1] = -1.0; + vector[2] = -tc; + break; + case 4: + vector[0] = sc; + vector[1] = -tc; + vector[2] = 1.0; + break; + case 5: + vector[0] = -sc; + vector[1] = -tc; + vector[2] = -1.0; + break; + } + + mag = idMath::InvSqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]); + vector[0] *= mag; + vector[1] *= mag; + vector[2] *= mag; +} + +/* Initialize a cube map texture object that generates RGB values + * that when expanded to a [-1,1] range in the register combiners + * form a normalized vector matching the per-pixel vector used to + * access the cube map. + */ +static void makeNormalizeVectorCubeMap( idImage *image ) { + float vector[3]; + int i, x, y; + byte *pixels[6]; + int size; + + size = NORMAL_MAP_SIZE; + + pixels[0] = (GLubyte*) Mem_Alloc(size*size*4*6); + + for (i = 0; i < 6; i++) { + pixels[i] = pixels[0] + i*size*size*4; + for (y = 0; y < size; y++) { + for (x = 0; x < size; x++) { + getCubeVector(i, size, x, y, vector); + pixels[i][4*(y*size+x) + 0] = (byte)(128 + 127*vector[0]); + pixels[i][4*(y*size+x) + 1] = (byte)(128 + 127*vector[1]); + pixels[i][4*(y*size+x) + 2] = (byte)(128 + 127*vector[2]); + pixels[i][4*(y*size+x) + 3] = 255; + } + } + } + + image->GenerateCubeImage( (const byte **)pixels, size, + TF_LINEAR, false, TD_HIGH_QUALITY ); + + Mem_Free(pixels[0]); +} + + + + +/* +================ +R_CreateNoFalloffImage + +This is a solid white texture that is zero clamped. +================ +*/ +static void R_CreateNoFalloffImage( idImage *image ) { + int x,y; + byte data[16][FALLOFF_TEXTURE_SIZE][4]; + + memset( data, 0, sizeof( data ) ); + for (x=1 ; xGenerateImage( (byte *)data, FALLOFF_TEXTURE_SIZE, 16, + TF_DEFAULT, false, TR_CLAMP_TO_ZERO, TD_HIGH_QUALITY ); +} + + +/* +================ +R_FogImage + +We calculate distance correctly in two planes, but the +third will still be projection based +================ +*/ +const int FOG_SIZE = 128; + +void R_FogImage( idImage *image ) { + int x,y; + byte data[FOG_SIZE][FOG_SIZE][4]; + int b; + +float step[256]; +int i; +float remaining = 1.0; +for ( i = 0 ; i < 256 ; i++ ) { + step[i] = remaining; + remaining *= 0.982f; +} + + for (x=0 ; x 255 ) { + b = 255; + } +b = (byte)(255 * ( 1.0 - step[b] )); + if ( x == 0 || x == FOG_SIZE-1 || y == 0 || y == FOG_SIZE-1 ) { + b = 255; // avoid clamping issues + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = 255; + data[y][x][3] = b; + } + } + + image->GenerateImage( (byte *)data, FOG_SIZE, FOG_SIZE, + TF_LINEAR, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + + +/* +================ +FogFraction + +Height values below zero are inside the fog volume +================ +*/ +static const float RAMP_RANGE = 8; +static const float DEEP_RANGE = -30; +static float FogFraction( float viewHeight, float targetHeight ) { + float total = idMath::Fabs( targetHeight - viewHeight ); + +// return targetHeight >= 0 ? 0 : 1.0; + + // only ranges that cross the ramp range are special + if ( targetHeight > 0 && viewHeight > 0 ) { + return 0.0; + } + if ( targetHeight < -RAMP_RANGE && viewHeight < -RAMP_RANGE ) { + return 1.0; + } + + float above; + if ( targetHeight > 0 ) { + above = targetHeight; + } else if ( viewHeight > 0 ) { + above = viewHeight; + } else { + above = 0; + } + + float rampTop, rampBottom; + + if ( viewHeight > targetHeight ) { + rampTop = viewHeight; + rampBottom = targetHeight; + } else { + rampTop = targetHeight; + rampBottom = viewHeight; + } + if ( rampTop > 0 ) { + rampTop = 0; + } + if ( rampBottom < -RAMP_RANGE ) { + rampBottom = -RAMP_RANGE; + } + + float rampSlope = 1.0 / RAMP_RANGE; + + if ( !total ) { + return -viewHeight * rampSlope; + } + + float ramp = ( 1.0 - ( rampTop * rampSlope + rampBottom * rampSlope ) * -0.5 ) * ( rampTop - rampBottom ); + + float frac = ( total - above - ramp ) / total; + + // after it gets moderately deep, always use full value + float deepest = viewHeight < targetHeight ? viewHeight : targetHeight; + + float deepFrac = deepest / DEEP_RANGE; + if ( deepFrac >= 1.0 ) { + return 1.0; + } + + frac = frac * ( 1.0 - deepFrac ) + deepFrac; + + return frac; +} + +/* +================ +R_FogEnterImage + +Modulate the fog alpha density based on the distance of the +start and end points to the terminator plane +================ +*/ +void R_FogEnterImage( idImage *image ) { + int x,y; + byte data[FOG_ENTER_SIZE][FOG_ENTER_SIZE][4]; + int b; + + for (x=0 ; x 255 ) { + b = 255; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = 255; + data[y][x][3] = b; + } + } + + // if mipmapped, acutely viewed surfaces fade wrong + image->GenerateImage( (byte *)data, FOG_ENTER_SIZE, FOG_ENTER_SIZE, + TF_LINEAR, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + + +/* +================ +R_QuadraticImage + +================ +*/ +static const int QUADRATIC_WIDTH = 32; +static const int QUADRATIC_HEIGHT = 4; + +void R_QuadraticImage( idImage *image ) { + int x,y; + byte data[QUADRATIC_HEIGHT][QUADRATIC_WIDTH][4]; + int b; + + + for (x=0 ; x 255 ) { + b = 255; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + + image->GenerateImage( (byte *)data, QUADRATIC_WIDTH, QUADRATIC_HEIGHT, + TF_DEFAULT, false, TR_CLAMP, TD_HIGH_QUALITY ); +} + +//===================================================================== + + +typedef struct { + char *name; + int minimize, maximize; +} filterName_t; + + + +/* +=============== +ChangeTextureFilter + +This resets filtering on all loaded images +New images will automatically pick up the current values. +=============== +*/ +void idImageManager::ChangeTextureFilter( void ) { + int i; + idImage *glt; + const char *string; +static filterName_t textureFilters[] = { + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}, + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST} +}; + + // if these are changed dynamically, it will force another ChangeTextureFilter + image_filter.ClearModified(); + image_anisotropy.ClearModified(); + image_lodbias.ClearModified(); + + string = image_filter.GetString(); + for ( i = 0; i < 6; i++ ) { + if ( !idStr::Icmp( textureFilters[i].name, string ) ) { + break; + } + } + + if ( i == 6 ) { + common->Warning( "bad r_textureFilter: '%s'", string); + // default to LINEAR_MIPMAP_NEAREST + i = 0; + } + + // set the values for future images + textureMinFilter = textureFilters[i].minimize; + textureMaxFilter = textureFilters[i].maximize; + textureAnisotropy = image_anisotropy.GetFloat(); + if ( textureAnisotropy < 1 ) { + textureAnisotropy = 1; + } else if ( textureAnisotropy > glConfig.maxTextureAnisotropy ) { + textureAnisotropy = glConfig.maxTextureAnisotropy; + } + textureLODBias = image_lodbias.GetFloat(); + + // change all the existing mipmap texture objects with default filtering + + for ( i = 0 ; i < images.Num() ; i++ ) { + unsigned int texEnum = GL_TEXTURE_2D; + + glt = images[ i ]; + + switch( glt->type ) { + case TT_2D: + texEnum = GL_TEXTURE_2D; + break; + case TT_3D: + texEnum = GL_TEXTURE_3D; + break; + case TT_CUBIC: + texEnum = GL_TEXTURE_CUBE_MAP_EXT; + break; + } + + // make sure we don't start a background load + if ( glt->texnum == idImage::TEXTURE_NOT_LOADED ) { + continue; + } + glt->Bind(); + if ( glt->filter == TF_DEFAULT ) { + qglTexParameterf(texEnum, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter ); + qglTexParameterf(texEnum, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter ); + } + if ( glConfig.anisotropicAvailable ) { + qglTexParameterf(texEnum, GL_TEXTURE_MAX_ANISOTROPY_EXT, globalImages->textureAnisotropy ); + } + if ( glConfig.textureLODBiasAvailable ) { + qglTexParameterf(texEnum, GL_TEXTURE_LOD_BIAS_EXT, globalImages->textureLODBias ); + } + } +} + +/* +=============== +idImage::Reload +=============== +*/ +void idImage::Reload( bool checkPrecompressed, bool force ) { + // always regenerate functional images + if ( generatorFunction ) { + common->DPrintf( "regenerating %s.\n", imgName.c_str() ); + generatorFunction( this ); + return; + } + + // check file times + if ( !force ) { + ID_TIME_T current; + + if ( cubeFiles != CF_2D ) { + R_LoadCubeImages( imgName, cubeFiles, NULL, NULL, ¤t ); + } else { + // get the current values + R_LoadImageProgram( imgName, NULL, NULL, NULL, ¤t ); + } + if ( current <= timestamp ) { + return; + } + } + + common->DPrintf( "reloading %s.\n", imgName.c_str() ); + + PurgeImage(); + + // force no precompressed image check, which will cause it to be reloaded + // from source, and another precompressed file generated. + // Load is from the front end, so the back end must be synced + ActuallyLoadImage( checkPrecompressed, false ); +} + +/* +=============== +R_ReloadImages_f + +Regenerate all images that came directly from files that have changed, so +any saved changes will show up in place. + +New r_texturesize/r_texturedepth variables will take effect on reload + +reloadImages +=============== +*/ +void R_ReloadImages_f( const idCmdArgs &args ) { + int i; + idImage *image; + bool all; + bool checkPrecompressed; + + // this probably isn't necessary... + globalImages->ChangeTextureFilter(); + + all = false; + checkPrecompressed = false; // if we are doing this as a vid_restart, look for precompressed like normal + + if ( args.Argc() == 2 ) { + if ( !idStr::Icmp( args.Argv(1), "all" ) ) { + all = true; + } else if ( !idStr::Icmp( args.Argv(1), "reload" ) ) { + all = true; + checkPrecompressed = true; + } else { + common->Printf( "USAGE: reloadImages \n" ); + return; + } + } + + for ( i = 0 ; i < globalImages->images.Num() ; i++ ) { + image = globalImages->images[ i ]; + image->Reload( checkPrecompressed, all ); + } +} + +typedef struct { + idImage *image; + int size; +} sortedImage_t; + +/* +======================= +R_QsortImageSizes + +======================= +*/ +static int R_QsortImageSizes( const void *a, const void *b ) { + const sortedImage_t *ea, *eb; + + ea = (sortedImage_t *)a; + eb = (sortedImage_t *)b; + + if ( ea->size > eb->size ) { + return -1; + } + if ( ea->size < eb->size ) { + return 1; + } + return idStr::Icmp( ea->image->imgName, eb->image->imgName ); +} + +/* +=============== +R_ListImages_f +=============== +*/ +void R_ListImages_f( const idCmdArgs &args ) { + int i, j, partialSize; + idImage *image; + int totalSize; + int count = 0; + int matchTag = 0; + bool uncompressedOnly = false; + bool unloaded = false; + bool partial = false; + bool cached = false; + bool uncached = false; + bool failed = false; + bool touched = false; + bool sorted = false; + bool duplicated = false; + bool byClassification = false; + bool overSized = false; + + if ( args.Argc() == 1 ) { + + } else if ( args.Argc() == 2 ) { + if ( idStr::Icmp( args.Argv( 1 ), "uncompressed" ) == 0 ) { + uncompressedOnly = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "sorted" ) == 0 ) { + sorted = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "partial" ) == 0 ) { + partial = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "unloaded" ) == 0 ) { + unloaded = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "cached" ) == 0 ) { + cached = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "uncached" ) == 0 ) { + uncached = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "tagged" ) == 0 ) { + matchTag = 1; + } else if ( idStr::Icmp( args.Argv( 1 ), "duplicated" ) == 0 ) { + duplicated = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "touched" ) == 0 ) { + touched = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "classify" ) == 0 ) { + byClassification = true; + sorted = true; + } else if ( idStr::Icmp( args.Argv( 1 ), "oversized" ) == 0 ) { + byClassification = true; + sorted = true; + overSized = true; + } else { + failed = true; + } + } else { + failed = true; + } + + if ( failed ) { + common->Printf( "usage: listImages [ sorted | partial | unloaded | cached | uncached | tagged | duplicated | touched | classify | showOverSized ]\n" ); + return; + } + + const char *header = " -w-- -h-- filt -fmt-- wrap size --name-------\n"; + common->Printf( "\n%s", header ); + + totalSize = 0; + + sortedImage_t *sortedArray = (sortedImage_t *)alloca( sizeof( sortedImage_t ) * globalImages->images.Num() ); + + for ( i = 0 ; i < globalImages->images.Num() ; i++ ) { + image = globalImages->images[ i ]; + + if ( uncompressedOnly ) { + if ( ( image->internalFormat >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT && image->internalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) + || image->internalFormat == GL_COLOR_INDEX8_EXT ) { + continue; + } + } + + if ( matchTag && image->classification != matchTag ) { + continue; + } + if ( unloaded && image->texnum != idImage::TEXTURE_NOT_LOADED ) { + continue; + } + if ( partial && !image->isPartialImage ) { + continue; + } + if ( cached && ( !image->partialImage || image->texnum == idImage::TEXTURE_NOT_LOADED ) ) { + continue; + } + if ( uncached && ( !image->partialImage || image->texnum != idImage::TEXTURE_NOT_LOADED ) ) { + continue; + } + + // only print duplicates (from mismatched wrap / clamp, etc) + if ( duplicated ) { + int j; + for ( j = i+1 ; j < globalImages->images.Num() ; j++ ) { + if ( idStr::Icmp( image->imgName, globalImages->images[ j ]->imgName ) == 0 ) { + break; + } + } + if ( j == globalImages->images.Num() ) { + continue; + } + } + + // "listimages touched" will list only images bound since the last "listimages touched" call + if ( touched ) { + if ( image->bindCount == 0 ) { + continue; + } + image->bindCount = 0; + } + + if ( sorted ) { + sortedArray[count].image = image; + sortedArray[count].size = image->StorageSize(); + } else { + common->Printf( "%4i:", i ); + image->Print(); + } + totalSize += image->StorageSize(); + count++; + } + + if ( sorted ) { + qsort( sortedArray, count, sizeof( sortedImage_t ), R_QsortImageSizes ); + partialSize = 0; + for ( i = 0 ; i < count ; i++ ) { + common->Printf( "%4i:", i ); + sortedArray[i].image->Print(); + partialSize += sortedArray[i].image->StorageSize(); + if ( ( (i+1) % 10 ) == 0 ) { + common->Printf( "-------- %5.1f of %5.1f megs --------\n", + partialSize / (1024*1024.0), totalSize / (1024*1024.0) ); + } + } + } + + common->Printf( "%s", header ); + common->Printf( " %i images (%i total)\n", count, globalImages->images.Num() ); + common->Printf( " %5.1f total megabytes of images\n\n\n", totalSize / (1024*1024.0) ); + + if ( byClassification ) { + + idList< int > classifications[IC_COUNT]; + + for ( i = 0 ; i < count ; i++ ) { + int cl = ClassifyImage( sortedArray[i].image->imgName ); + classifications[ cl ].Append( i ); + } + + for ( i = 0; i < IC_COUNT; i++ ) { + partialSize = 0; + idList< int > overSizedList; + for ( j = 0; j < classifications[ i ].Num(); j++ ) { + partialSize += sortedArray[ classifications[ i ][ j ] ].image->StorageSize(); + if ( overSized ) { + if ( sortedArray[ classifications[ i ][ j ] ].image->uploadWidth > IC_Info[i].maxWidth && sortedArray[ classifications[ i ][ j ] ].image->uploadHeight > IC_Info[i].maxHeight ) { + overSizedList.Append( classifications[ i ][ j ] ); + } + } + } + common->Printf ( " Classification %s contains %i images using %5.1f megabytes\n", IC_Info[i].desc, classifications[i].Num(), partialSize / ( 1024*1024.0 ) ); + if ( overSized && overSizedList.Num() ) { + common->Printf( " The following images may be oversized\n" ); + for ( j = 0; j < overSizedList.Num(); j++ ) { + common->Printf( " " ); + sortedArray[ overSizedList[ j ] ].image->Print(); + common->Printf( "\n" ); + } + } + } + } + +} + +/* +================== +SetNormalPalette + +Create a 256 color palette to be used by compressed normal maps +================== +*/ +void idImageManager::SetNormalPalette( void ) { + int i, j; + idVec3 v; + float t; + //byte temptable[768]; + byte *temptable = compressedPalette; + int compressedToOriginal[16]; + + // make an ad-hoc separable compression mapping scheme + for ( i = 0 ; i < 8 ; i++ ) { + float f, y; + + f = ( i + 1 ) / 8.5; + y = idMath::Sqrt( 1.0 - f * f ); + y = 1.0 - y; + + compressedToOriginal[7-i] = 127 - (int)( y * 127 + 0.5 ); + compressedToOriginal[8+i] = 128 + (int)( y * 127 + 0.5 ); + } + + for ( i = 0 ; i < 256 ; i++ ) { + if ( i <= compressedToOriginal[0] ) { + originalToCompressed[i] = 0; + } else if ( i >= compressedToOriginal[15] ) { + originalToCompressed[i] = 15; + } else { + for ( j = 0 ; j < 14 ; j++ ) { + if ( i <= compressedToOriginal[j+1] ) { + break; + } + } + if ( i - compressedToOriginal[j] < compressedToOriginal[j+1] - i ) { + originalToCompressed[i] = j; + } else { + originalToCompressed[i] = j + 1; + } + } + } + +#if 0 + for ( i = 0; i < 16; i++ ) { + for ( j = 0 ; j < 16 ; j++ ) { + + v[0] = ( i - 7.5 ) / 8; + v[1] = ( j - 7.5 ) / 8; + + t = 1.0 - ( v[0]*v[0] + v[1]*v[1] ); + if ( t < 0 ) { + t = 0; + } + v[2] = idMath::Sqrt( t ); + + temptable[(i*16+j)*3+0] = 128 + floor( 127 * v[0] + 0.5 ); + temptable[(i*16+j)*3+1] = 128 + floor( 127 * v[1] ); + temptable[(i*16+j)*3+2] = 128 + floor( 127 * v[2] ); + } + } +#else + for ( i = 0; i < 16; i++ ) { + for ( j = 0 ; j < 16 ; j++ ) { + + v[0] = ( compressedToOriginal[i] - 127.5 ) / 128; + v[1] = ( compressedToOriginal[j] - 127.5 ) / 128; + + t = 1.0 - ( v[0]*v[0] + v[1]*v[1] ); + if ( t < 0 ) { + t = 0; + } + v[2] = idMath::Sqrt( t ); + + temptable[(i*16+j)*3+0] = (byte)(128 + floor( 127 * v[0] + 0.5 )); + temptable[(i*16+j)*3+1] = (byte)(128 + floor( 127 * v[1] )); + temptable[(i*16+j)*3+2] = (byte)(128 + floor( 127 * v[2] )); + } + } +#endif + + // color 255 will be the "nullnormal" color for no reflection + temptable[255*3+0] = + temptable[255*3+1] = + temptable[255*3+2] = 128; + + if ( !glConfig.sharedTexturePaletteAvailable ) { + return; + } + + qglColorTableEXT( GL_SHARED_TEXTURE_PALETTE_EXT, + GL_RGB, + 256, + GL_RGB, + GL_UNSIGNED_BYTE, + temptable ); + + qglEnable( GL_SHARED_TEXTURE_PALETTE_EXT ); +} + +/* +============== +AllocImage + +Allocates an idImage, adds it to the list, +copies the name, and adds it to the hash chain. +============== +*/ +idImage *idImageManager::AllocImage( const char *name ) { + idImage *image; + int hash; + + if (strlen(name) >= MAX_IMAGE_NAME ) { + common->Error ("idImageManager::AllocImage: \"%s\" is too long\n", name); + } + + hash = idStr( name ).FileNameHash(); + + image = new idImage; + images.Append( image ); + + image->hashNext = imageHashTable[hash]; + imageHashTable[hash] = image; + + image->imgName = name; + + return image; +} + +idPBufferImage *idImageManager::AllocPBufferImage( const char *name ) { + if ( strlen( name ) >= MAX_IMAGE_NAME ) { + common->Error( "idImageManager::AllocPBufferImage: \"%s\" is too long\n", name ); + } + + idStr imageName = name; + const int hash = imageName.FileNameHash(); + for ( idImage *existing = imageHashTable[hash]; existing != NULL; existing = existing->hashNext ) { + if ( imageName.Icmp( existing->imgName ) == 0 ) { + idPBufferImage *pbufferImage = dynamic_cast( existing ); + if ( pbufferImage == NULL ) { + common->Error( "Trying to attach a pbuffer to an existing image '%s'", name ); + } + return pbufferImage; + } + } + + idPBufferImage *image = new idPBufferImage; + images.Append( image ); + image->hashNext = imageHashTable[hash]; + imageHashTable[hash] = image; + image->imgName = name; + qglGenTextures( 1, &image->texnum ); + image->mRenderTarget = new rvTexRenderTarget; + return image; +} + +/* +================== +ImageFromFunction + +Images that are procedurally generated are allways specified +with a callback which must work at any time, allowing the OpenGL +system to be completely regenerated if needed. +================== +*/ +idImage *idImageManager::ImageFromFunction( const char *_name, void (*generatorFunction)( idImage *image ) ) { + idStr name; + idImage *image; + int hash; + + if ( !name ) { + common->FatalError( "idImageManager::ImageFromFunction: NULL name" ); + } + + // strip any .tga file extensions from anywhere in the _name + name = _name; + name.Replace( ".tga", "" ); + name.BackSlashesToSlashes(); + + // see if the image already exists + hash = name.FileNameHash(); + for ( image = imageHashTable[hash] ; image; image = image->hashNext ) { + if ( name.Icmp( image->imgName ) == 0 ) { + if ( image->generatorFunction != generatorFunction ) { + common->DPrintf( "WARNING: reused image %s with mixed generators\n", name.c_str() ); + } + return image; + } + } + + // create the image and issue the callback + image = AllocImage( name ); + + image->generatorFunction = generatorFunction; + + if ( image_preload.GetBool() ) { + // check for precompressed, load is from the front end + image->referencedOutsideLevelLoad = true; + image->ActuallyLoadImage( true, false ); + } + + return image; +} + +/* +=============== +ImageFromFile + +Finds or loads the given image, always returning a valid image pointer. +Loading of the image may be deferred for dynamic loading. +============== +*/ +idImage *idImageManager::ImageFromFile( const char *_name, textureFilter_t filter, bool allowDownSize, + textureRepeat_t repeat, textureDepth_t depth, cubeFiles_t cubeMap ) { + idStr name; + idImage *image; + int hash; + + if ( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { + declManager->MediaPrint( "DEFAULTED\n" ); + return globalImages->defaultImage; + } + + // strip any .tga file extensions from anywhere in the _name, including image program parameters + name = _name; + name.Replace( ".tga", "" ); + name.BackSlashesToSlashes(); + + // + // see if the image is already loaded, unless we + // are in a reloadImages call + // + hash = name.FileNameHash(); + for ( image = imageHashTable[hash]; image; image = image->hashNext ) { + if ( name.Icmp( image->imgName ) == 0 ) { + // the built in's, like _white and _flat always match the other options + if ( name[0] == '_' ) { + return image; + } + if ( image->cubeFiles != cubeMap ) { + common->Error( "Image '%s' has been referenced with conflicting cube map states", _name ); + } + + if ( image->filter != filter || image->repeat != repeat ) { + // we might want to have the system reset these parameters on every bind and + // share the image data + continue; + } + + if ( image->allowDownSize == allowDownSize && image->depth == depth ) { + // note that it is used this level load + image->levelLoadReferenced = true; + if ( image->partialImage != NULL ) { + image->partialImage->levelLoadReferenced = true; + } + return image; + } + + // the same image is being requested, but with a different allowDownSize or depth + // so pick the highest of the two and reload the old image with those parameters + if ( !image->allowDownSize ) { + allowDownSize = false; + } + if ( image->depth > depth ) { + depth = image->depth; + } + if ( image->allowDownSize == allowDownSize && image->depth == depth ) { + // the already created one is already the highest quality + image->levelLoadReferenced = true; + if ( image->partialImage != NULL ) { + image->partialImage->levelLoadReferenced = true; + } + return image; + } + + image->allowDownSize = allowDownSize; + image->depth = depth; + image->levelLoadReferenced = true; + if ( image->partialImage != NULL ) { + image->partialImage->levelLoadReferenced = true; + } + if ( image_preload.GetBool() && !insideLevelLoad ) { + image->referencedOutsideLevelLoad = true; + image->ActuallyLoadImage( true, false ); // check for precompressed, load is from front end + declManager->MediaPrint( "%ix%i %s (reload for mixed referneces)\n", image->uploadWidth, image->uploadHeight, image->imgName.c_str() ); + } + return image; + } + } + + // + // create a new image + // + image = AllocImage( name ); + + // HACK: to allow keep fonts from being mip'd, as new ones will be introduced with localization + // this keeps us from having to make a material for each font tga + if ( name.Find( "fontImage_") >= 0 ) { + allowDownSize = false; + } + + image->allowDownSize = allowDownSize; + image->repeat = repeat; + image->depth = depth; + image->type = TT_2D; + image->cubeFiles = cubeMap; + image->filter = filter; + + image->levelLoadReferenced = true; + + // also create a shrunken version if we are going to dynamically cache the full size image + if ( image->ShouldImageBePartialCached() ) { + // if we only loaded part of the file, create a new idImage for the shrunken version + image->partialImage = new idImage; + + image->partialImage->allowDownSize = allowDownSize; + image->partialImage->repeat = repeat; + image->partialImage->depth = depth; + image->partialImage->type = TT_2D; + image->partialImage->cubeFiles = cubeMap; + image->partialImage->filter = filter; + + image->partialImage->levelLoadReferenced = true; + + // we don't bother hooking this into the hash table for lookup, but we do add it to the manager + // list for listImages + globalImages->images.Append( image->partialImage ); + image->partialImage->imgName = image->imgName; + image->partialImage->isPartialImage = true; + + // let the background file loader know that we can load + image->precompressedFile = true; + + if ( image_preload.GetBool() && !insideLevelLoad ) { + image->partialImage->ActuallyLoadImage( true, false ); // check for precompressed, load is from front end + declManager->MediaPrint( "%ix%i %s\n", image->partialImage->uploadWidth, image->partialImage->uploadHeight, image->imgName.c_str() ); + } else { + declManager->MediaPrint( "%s\n", image->imgName.c_str() ); + } + return image; + } + + // load it if we aren't in a level preload + if ( image_preload.GetBool() && !insideLevelLoad ) { + image->referencedOutsideLevelLoad = true; + image->ActuallyLoadImage( true, false ); // check for precompressed, load is from front end + declManager->MediaPrint( "%ix%i %s\n", image->uploadWidth, image->uploadHeight, image->imgName.c_str() ); + } else { + declManager->MediaPrint( "%s\n", image->imgName.c_str() ); + } + + return image; +} + +/* +=============== +idImageManager::GetImage +=============== +*/ +idImage *idImageManager::GetImage( const char *_name ) const { + idStr name; + idImage *image; + int hash; + + if ( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { + declManager->MediaPrint( "DEFAULTED\n" ); + return globalImages->defaultImage; + } + + // strip any .tga file extensions from anywhere in the _name, including image program parameters + name = _name; + name.Replace( ".tga", "" ); + name.BackSlashesToSlashes(); + + // + // look in loaded images + // + hash = name.FileNameHash(); + for ( image = imageHashTable[hash]; image; image = image->hashNext ) { + if ( name.Icmp( image->imgName ) == 0 ) { + return image; + } + } + + return NULL; +} + +/* +=============== +PurgeAllImages +=============== +*/ +void idImageManager::PurgeAllImages() { + int i; + idImage *image; + + for ( i = 0; i < images.Num() ; i++ ) { + image = images[i]; + image->PurgeImage(); + } +} + +/* +=============== +ReloadAllImages +=============== +*/ +void idImageManager::ReloadAllImages() { + idCmdArgs args; + + // build the compressed normal map palette + SetNormalPalette(); + + args.TokenizeString( "reloadImages reload", false ); + R_ReloadImages_f( args ); +} + +/* +=============== +R_CombineCubeImages_f + +Used to combine animations of six separate tga files into +a serials of 6x taller tga files, for preparation to roq compress +=============== +*/ +void R_CombineCubeImages_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Printf( "usage: combineCubeImages \n" ); + common->Printf( " combines basename[1-6][0001-9999].tga to basenameCM[0001-9999].tga\n" ); + common->Printf( " 1: forward 2:right 3:back 4:left 5:up 6:down\n" ); + return; + } + + idStr baseName = args.Argv( 1 ); + common->SetRefreshOnPrint( true ); + + for ( int frameNum = 1 ; frameNum < 10000 ; frameNum++ ) { + char filename[MAX_IMAGE_NAME]; + byte *pics[6]; + int width, height; + int side; + int orderRemap[6] = { 1,3,4,2,5,6 }; + for ( side = 0 ; side < 6 ; side++ ) { + sprintf( filename, "%s%i%04i.tga", baseName.c_str(), orderRemap[side], frameNum ); + + common->Printf( "reading %s\n", filename ); + R_LoadImage( filename, &pics[side], &width, &height, NULL, true ); + + if ( !pics[side] ) { + common->Printf( "not found.\n" ); + break; + } + + // convert from "camera" images to native cube map images + switch( side ) { + case 0: // forward + R_RotatePic( pics[side], width); + break; + case 1: // back + R_RotatePic( pics[side], width); + R_HorizontalFlip( pics[side], width, height ); + R_VerticalFlip( pics[side], width, height ); + break; + case 2: // left + R_VerticalFlip( pics[side], width, height ); + break; + case 3: // right + R_HorizontalFlip( pics[side], width, height ); + break; + case 4: // up + R_RotatePic( pics[side], width); + break; + case 5: // down + R_RotatePic( pics[side], width); + break; + } + } + + if ( side != 6 ) { + for ( int i = 0 ; i < side ; side++ ) { + Mem_Free( pics[side] ); + } + break; + } + + byte *combined = (byte *)Mem_Alloc( width*height*6*4 ); + for ( side = 0 ; side < 6 ; side++ ) { + memcpy( combined+width*height*4*side, pics[side], width*height*4 ); + Mem_Free( pics[side] ); + } + sprintf( filename, "%sCM%04i.tga", baseName.c_str(), frameNum ); + + common->Printf( "writing %s\n", filename ); + R_WriteTGA( filename, combined, width, height*6 ); + + Mem_Free( combined ); + } + common->SetRefreshOnPrint( false ); +} + + +/* +================== +idImage::StartBackgroundImageLoad +================== +*/ +void idImage::StartBackgroundImageLoad() { + if ( imageManager.numActiveBackgroundImageLoads >= idImageManager::MAX_BACKGROUND_IMAGE_LOADS ) { + return; + } + if ( globalImages->image_showBackgroundLoads.GetBool() ) { + common->Printf( "idImage::StartBackgroundImageLoad: %s\n", imgName.c_str() ); + } + backgroundLoadInProgress = true; + + if ( !precompressedFile ) { + common->Warning( "idImageManager::StartBackgroundImageLoad: %s wasn't a precompressed file", imgName.c_str() ); + return; + } + + bglNext = globalImages->backgroundImageLoads; + globalImages->backgroundImageLoads = this; + + char filename[MAX_IMAGE_NAME]; + ImageProgramStringToCompressedFileName( imgName, filename ); + + bgl.completed = false; + bgl.f = fileSystem->OpenFileRead( filename ); + if ( !bgl.f ) { + common->Warning( "idImageManager::StartBackgroundImageLoad: Couldn't load %s", imgName.c_str() ); + return; + } + bgl.file.position = 0; + bgl.file.length = bgl.f->Length(); + if ( bgl.file.length < sizeof( ddsFileHeader_t ) ) { + common->Warning( "idImageManager::StartBackgroundImageLoad: %s had a bad file length", imgName.c_str() ); + return; + } + + bgl.file.buffer = R_StaticAlloc( bgl.file.length ); + + fileSystem->BackgroundDownload( &bgl ); + + imageManager.numActiveBackgroundImageLoads++; + + // purge some images if necessary + int totalSize = 0; + for ( idImage *check = globalImages->cacheLRU.cacheUsageNext ; check != &globalImages->cacheLRU ; check = check->cacheUsageNext ) { + totalSize += check->StorageSize(); + } + int needed = this->StorageSize(); + + while ( ( totalSize + needed ) > globalImages->image_cacheMegs.GetFloat() * 1024 * 1024 ) { + // purge the least recently used + idImage *check = globalImages->cacheLRU.cacheUsagePrev; + if ( check->texnum != TEXTURE_NOT_LOADED ) { + totalSize -= check->StorageSize(); + if ( globalImages->image_showBackgroundLoads.GetBool() ) { + common->Printf( "purging %s\n", check->imgName.c_str() ); + } + check->PurgeImage(); + } + // remove it from the cached list + check->cacheUsageNext->cacheUsagePrev = check->cacheUsagePrev; + check->cacheUsagePrev->cacheUsageNext = check->cacheUsageNext; + check->cacheUsageNext = NULL; + check->cacheUsagePrev = NULL; + } +} + +/* +================== +R_CompleteBackgroundImageLoads + +Do we need to worry about vid_restarts here? +================== +*/ +void idImageManager::CompleteBackgroundImageLoads() { + idImage *remainingList = NULL; + idImage *next; + + for ( idImage *image = backgroundImageLoads ; image ; image = next ) { + next = image->bglNext; + if ( image->bgl.completed ) { + numActiveBackgroundImageLoads--; + fileSystem->CloseFile( image->bgl.f ); + // upload the image + image->UploadPrecompressedImage( (byte *)image->bgl.file.buffer, image->bgl.file.length ); + R_StaticFree( image->bgl.file.buffer ); + if ( image_showBackgroundLoads.GetBool() ) { + common->Printf( "R_CompleteBackgroundImageLoad: %s\n", image->imgName.c_str() ); + } + } else { + image->bglNext = remainingList; + remainingList = image; + } + } + if ( image_showBackgroundLoads.GetBool() ) { + static int prev; + if ( numActiveBackgroundImageLoads != prev ) { + prev = numActiveBackgroundImageLoads; + common->Printf( "background Loads: %i\n", numActiveBackgroundImageLoads ); + } + } + + backgroundImageLoads = remainingList; +} + +/* +=============== +CheckCvars +=============== +*/ +void idImageManager::CheckCvars() { + // textureFilter stuff + if ( image_filter.IsModified() || image_anisotropy.IsModified() || image_lodbias.IsModified() ) { + ChangeTextureFilter(); + image_filter.ClearModified(); + image_anisotropy.ClearModified(); + image_lodbias.ClearModified(); + } +} + +/* +=============== +SumOfUsedImages +=============== +*/ +int idImageManager::SumOfUsedImages() { + int total; + int i; + idImage *image; + + total = 0; + for ( i = 0; i < images.Num(); i++ ) { + image = images[i]; + if ( image->frameUsed == backEnd.frameCount ) { + total += image->StorageSize(); + } + } + + return total; +} + +/* +=============== +BindNull +=============== +*/ +void idImageManager::BindNull() { + tmu_t *tmu; + + tmu = &backEnd.glState.tmu[backEnd.glState.currenttmu]; + + RB_LogComment( "BindNull()\n" ); + if ( tmu->textureType == TT_CUBIC ) { + qglDisable( GL_TEXTURE_CUBE_MAP_EXT ); + } else if ( tmu->textureType == TT_3D ) { + qglDisable( GL_TEXTURE_3D ); + } else if ( tmu->textureType == TT_2D ) { + qglDisable( GL_TEXTURE_2D ); + } + tmu->textureType = TT_DISABLED; +} + +/* +=============== +Init +=============== +*/ +void idImageManager::Init() { + + memset(imageHashTable, 0, sizeof(imageHashTable)); + + images.Resize( 1024, 1024 ); + + // clear the cached LRU + cacheLRU.cacheUsageNext = &cacheLRU; + cacheLRU.cacheUsagePrev = &cacheLRU; + + // set default texture filter modes + ChangeTextureFilter(); + + // create built in images + defaultImage = ImageFromFunction( "_default", R_DefaultImage ); + whiteImage = ImageFromFunction( "_white", R_WhiteImage ); + blackImage = ImageFromFunction( "_black", R_BlackImage ); + borderClampImage = ImageFromFunction( "_borderClamp", R_BorderClampImage ); + flatNormalMap = ImageFromFunction( "_flat", R_FlatNormalImage ); + ambientNormalMap = ImageFromFunction( "_ambient", R_AmbientNormalImage ); + specularTableImage = ImageFromFunction( "_specularTable", R_SpecularTableImage ); + specular2DTableImage = ImageFromFunction( "_specular2DTable", R_Specular2DTableImage ); + rampImage = ImageFromFunction( "_ramp", R_RampImage ); + alphaRampImage = ImageFromFunction( "_alphaRamp", R_RampImage ); + alphaNotchImage = ImageFromFunction( "_alphaNotch", R_AlphaNotchImage ); + fogImage = ImageFromFunction( "_fog", R_FogImage ); + fogEnterImage = ImageFromFunction( "_fogEnter", R_FogEnterImage ); + normalCubeMapImage = ImageFromFunction( "_normalCubeMap", makeNormalizeVectorCubeMap ); + noFalloffImage = ImageFromFunction( "_noFalloff", R_CreateNoFalloffImage ); + ImageFromFunction( "_quadratic", R_QuadraticImage ); + + // cinematicImage is used for cinematic drawing + // scratchImage is used for screen wipes/doublevision etc.. + cinematicImage = ImageFromFunction("_cinematic", R_RGBA8Image ); + scratchImage = ImageFromFunction("_scratch", R_RGBA8Image ); + scratchImage2 = ImageFromFunction("_scratch2", R_RGBA8Image ); + accumImage = ImageFromFunction("_accum", R_RGBA8Image ); + scratchCubeMapImage = ImageFromFunction("_scratchCubeMap", makeNormalizeVectorCubeMap ); + currentRenderImage = ImageFromFunction("_currentRender", R_RGBA8Image ); + reflectionImage = ImageFromFunction("_reflectionRender", R_RGBA8Image ); + + cmdSystem->AddCommand( "reloadImages", R_ReloadImages_f, CMD_FL_RENDERER, "reloads images" ); + cmdSystem->AddCommand( "listImages", R_ListImages_f, CMD_FL_RENDERER, "lists images" ); + cmdSystem->AddCommand( "combineCubeImages", R_CombineCubeImages_f, CMD_FL_RENDERER, "combines six images for roq compression" ); + + // should forceLoadImages be here? +} + +/* +=============== +Shutdown +=============== +*/ +void idImageManager::Shutdown() { + images.DeleteContents( true ); +} + +/* +==================== +BeginLevelLoad + +Mark all file based images as currently unused, +but don't free anything. Calls to ImageFromFile() will +either mark the image as used, or create a new image without +loading the actual data. +==================== +*/ +void idImageManager::BeginLevelLoad() { + insideLevelLoad = true; + + for ( int i = 0 ; i < images.Num() ; i++ ) { + idImage *image = images[ i ]; + + // generator function images are always kept around + if ( image->generatorFunction ) { + continue; + } + + if ( com_purgeAll.GetBool() ) { + image->PurgeImage(); + } + + image->levelLoadReferenced = false; + } +} + +/* +==================== +EndLevelLoad + +Free all images marked as unused, and load all images that are necessary. +This architecture prevents us from having the union of two level's +worth of data present at one time. + +preload everything, never free +preload everything, free unused after level load +blocking load on demand +preload low mip levels, background load remainder on demand +==================== +*/ +void idImageManager::EndLevelLoad() { + int start = Sys_Milliseconds(); + + insideLevelLoad = false; + if ( idAsyncNetwork::serverDedicated.GetInteger() ) { + return; + } + + common->Printf( "----- idImageManager::EndLevelLoad -----\n" ); + + int purgeCount = 0; + int keepCount = 0; + int loadCount = 0; + + // purge the ones we don't need + for ( int i = 0 ; i < images.Num() ; i++ ) { + idImage *image = images[ i ]; + if ( image->generatorFunction ) { + continue; + } + + if ( !image->levelLoadReferenced && !image->referencedOutsideLevelLoad ) { +// common->Printf( "Purging %s\n", image->imgName.c_str() ); + purgeCount++; + image->PurgeImage(); + } else if ( image->texnum != idImage::TEXTURE_NOT_LOADED ) { +// common->Printf( "Keeping %s\n", image->imgName.c_str() ); + keepCount++; + } + } + + // load the ones we do need, if we are preloading + for ( int i = 0 ; i < images.Num() ; i++ ) { + idImage *image = images[ i ]; + if ( image->generatorFunction ) { + continue; + } + + if ( image->levelLoadReferenced && image->texnum == idImage::TEXTURE_NOT_LOADED && !image->partialImage ) { +// common->Printf( "Loading %s\n", image->imgName.c_str() ); + loadCount++; + image->ActuallyLoadImage( true, false ); + + if ( ( loadCount & 15 ) == 0 ) { + session->PacifierUpdate(); + } + } + } + + int end = Sys_Milliseconds(); + common->Printf( "%5i purged from previous\n", purgeCount ); + common->Printf( "%5i kept from previous\n", keepCount ); + common->Printf( "%5i new loaded\n", loadCount ); + common->Printf( "all images loaded in %5.1f seconds\n", (end-start) * 0.001 ); + common->Printf( "----------------------------------------\n" ); +} + +/* +=============== +idImageManager::StartBuild +=============== +*/ +void idImageManager::StartBuild() { + ddsList.Clear(); + ddsHash.Free(); +} + +/* +=============== +idImageManager::FinishBuild +=============== +*/ +void idImageManager::FinishBuild( bool removeDups ) { + idFile *batchFile; + if ( removeDups ) { + ddsList.Clear(); + char *buffer = NULL; + fileSystem->ReadFile( "makedds.bat", (void**)&buffer ); + if ( buffer ) { + idStr str = buffer; + while ( str.Length() ) { + int n = str.Find( '\n' ); + if ( n > 0 ) { + idStr line = str.Left( n + 1 ); + idStr right; + str.Right( str.Length() - n - 1, right ); + str = right; + ddsList.AddUnique( line ); + } else { + break; + } + } + } + } + batchFile = fileSystem->OpenFileWrite( ( removeDups ) ? "makedds2.bat" : "makedds.bat" ); + if ( batchFile ) { + int i; + int ddsNum = ddsList.Num(); + + for ( i = 0; i < ddsNum; i++ ) { + batchFile->WriteFloatString( "%s", ddsList[ i ].c_str() ); + batchFile->Printf( "@echo Finished compressing %d of %d. %.1f percent done.\n", i+1, ddsNum, ((float)(i+1)/(float)ddsNum)*100.f ); + } + fileSystem->CloseFile( batchFile ); + } + ddsList.Clear(); + ddsHash.Free(); +} + +/* +=============== +idImageManager::AddDDSCommand +=============== +*/ +void idImageManager::AddDDSCommand( const char *cmd ) { + int i, key; + + if ( !( cmd && *cmd ) ) { + return; + } + + key = ddsHash.GenerateKey( cmd, false ); + for ( i = ddsHash.First( key ); i != -1; i = ddsHash.Next( i ) ) { + if ( ddsList[i].Icmp( cmd ) == 0 ) { + break; + } + } + + if ( i == -1 ) { + ddsList.Append( cmd ); + } +} + +/* +=============== +idImageManager::PrintMemInfo +=============== +*/ +void idImageManager::PrintMemInfo( MemInfo *mi ) { + int i, j, total = 0; + int *sortIndex; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_images.txt" ); + if ( !f ) { + return; + } + + // sort first + sortIndex = new int[images.Num()]; + + for ( i = 0; i < images.Num(); i++ ) { + sortIndex[i] = i; + } + + for ( i = 0; i < images.Num() - 1; i++ ) { + for ( j = i + 1; j < images.Num(); j++ ) { + if ( images[sortIndex[i]]->StorageSize() < images[sortIndex[j]]->StorageSize() ) { + int temp = sortIndex[i]; + sortIndex[i] = sortIndex[j]; + sortIndex[j] = temp; + } + } + } + + // print next + for ( i = 0; i < images.Num(); i++ ) { + idImage *im = images[sortIndex[i]]; + int size; + + size = im->StorageSize(); + total += size; + + f->Printf( "%s %3i %s\n", idStr::FormatNumber( size ).c_str(), im->refCount, im->imgName.c_str() ); + } + + delete sortIndex; + mi->imageAssetsTotal = total; + + f->Printf( "\nTotal image bytes allocated: %s\n", idStr::FormatNumber( total ).c_str() ); + fileSystem->CloseFile( f ); +} diff --git a/src/renderer/image_load.cpp b/src/renderer/image_load.cpp new file mode 100644 index 0000000..114a47a --- /dev/null +++ b/src/renderer/image_load.cpp @@ -0,0 +1,2243 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvTexRenderTarget.h" + +void idPBufferImage::Bind() { + if ( mRenderTarget != NULL ) { + mRenderTarget->BeginTexture( texnum, GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE ); + } +} + +void idPBufferImage::UnBind() { + if ( mRenderTarget != NULL ) { + mRenderTarget->EndTexture(); + } +} + +void idPBufferImage::PurgeImage() { + if ( mRenderTarget != NULL ) { + mRenderTarget->Release(); + } + if ( texnum != TEXTURE_NOT_LOADED ) { + if ( qglDeleteTextures != NULL ) { + qglDeleteTextures( 1, &texnum ); + } + texnum = TEXTURE_NOT_LOADED; + } +} + +void idPBufferImage::Reload( bool checkPrecompressed, bool force ) { + if ( mRenderTarget != NULL ) { + mRenderTarget->Restore(); + mRenderTarget->DefaultD3GL(); + qglGenTextures( 1, &texnum ); + } +} + +idPBufferImage::~idPBufferImage() { + PurgeImage(); + delete mRenderTarget; + mRenderTarget = NULL; +} + +/* +PROBLEM: compressed textures may break the zero clamp rule! +*/ + +static bool FormatIsDXT( int internalFormat ) { + if ( internalFormat < GL_COMPRESSED_RGB_S3TC_DXT1_EXT + || internalFormat > GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) { + return false; + } + return true; +} + +int MakePowerOfTwo( int num ) { + int pot; + for (pot = 1 ; pot < num ; pot<<=1) { + } + return pot; +} + +/* +================ +BitsForInternalFormat + +Used for determining memory utilization +================ +*/ +int idImage::BitsForInternalFormat( int internalFormat ) const { + switch ( internalFormat ) { + case GL_INTENSITY8: + case 1: + return 8; + case 2: + case GL_LUMINANCE8_ALPHA8: + return 16; + case 3: + return 32; // on some future hardware, this may actually be 24, but be conservative + case 4: + return 32; + case GL_LUMINANCE8: + return 8; + case GL_ALPHA8: + return 8; + case GL_RGBA8: + return 32; + case GL_RGB8: + return 32; // on some future hardware, this may actually be 24, but be conservative + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + return 4; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + return 4; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + return 8; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return 8; + case GL_RGBA4: + return 16; + case GL_RGB5: + return 16; + case GL_COLOR_INDEX8_EXT: + return 8; + case GL_COLOR_INDEX: + return 8; + case GL_COMPRESSED_RGB_ARB: + return 4; // not sure + case GL_COMPRESSED_RGBA_ARB: + return 8; // not sure + default: + common->Error( "R_BitsForInternalFormat: BAD FORMAT:%i", internalFormat ); + } + return 0; +} + +/* +================== +UploadCompressedNormalMap + +Create a 256 color palette to be used by compressed normal maps +================== +*/ +void idImage::UploadCompressedNormalMap( int width, int height, const byte *rgba, int mipLevel ) { + byte *normals; + const byte *in; + byte *out; + int i, j; + int x, y, z; + int row; + + // OpenGL's pixel packing rule + row = width < 4 ? 4 : width; + + normals = (byte *)_alloca( row * height ); + if ( !normals ) { + common->Error( "R_UploadCompressedNormalMap: _alloca failed" ); + } + + in = rgba; + out = normals; + for ( i = 0 ; i < height ; i++, out += row, in += width * 4 ) { + for ( j = 0 ; j < width ; j++ ) { + x = in[ j * 4 + 0 ]; + y = in[ j * 4 + 1 ]; + z = in[ j * 4 + 2 ]; + + int c; + if ( x == 128 && y == 128 && z == 128 ) { + // the "nullnormal" color + c = 255; + } else { + c = ( globalImages->originalToCompressed[x] << 4 ) | globalImages->originalToCompressed[y]; + if ( c == 255 ) { + c = 254; // don't use the nullnormal color + } + } + out[j] = c; + } + } + + if ( mipLevel == 0 ) { + // Optionally write out the paletized normal map to a .tga + if ( globalImages->image_writeNormalTGAPalletized.GetBool() ) { + char filename[MAX_IMAGE_NAME]; + ImageProgramStringToCompressedFileName( imgName, filename ); + char *ext = strrchr(filename, '.'); + if ( ext ) { + strcpy(ext, "_pal.tga"); + R_WritePalTGA( filename, normals, globalImages->compressedPalette, width, height); + } + } + } + + if ( glConfig.sharedTexturePaletteAvailable ) { + qglTexImage2D( GL_TEXTURE_2D, + mipLevel, + GL_COLOR_INDEX8_EXT, + width, + height, + 0, + GL_COLOR_INDEX, + GL_UNSIGNED_BYTE, + normals ); + } +} + + +//======================================================================= + + +static byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + +/* +=============== +SelectInternalFormat + +This may need to scan six cube map images +=============== +*/ +GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, int width, int height, + textureDepth_t minimumDepth, bool *monochromeResult ) const { + int i, c; + const byte *scan; + int rgbOr, rgbAnd, aOr, aAnd; + int rgbDiffer, rgbaDiffer; + + // determine if the rgb channels are all the same + // and if either all rgb or all alpha are 255 + c = width*height; + rgbDiffer = 0; + rgbaDiffer = 0; + rgbOr = 0; + rgbAnd = -1; + aOr = 0; + aAnd = -1; + + *monochromeResult = true; // until shown otherwise + + for ( int side = 0 ; side < numDataPtrs ; side++ ) { + scan = dataPtrs[side]; + for ( i = 0; i < c; i++, scan += 4 ) { + int cor, cand; + + aOr |= scan[3]; + aAnd &= scan[3]; + + cor = scan[0] | scan[1] | scan[2]; + cand = scan[0] & scan[1] & scan[2]; + + // if rgb are all the same, the or and and will match + rgbDiffer |= ( cor ^ cand ); + + // our "isMonochrome" test is more lax than rgbDiffer, + // allowing the values to be off by several units and + // still use the NV20 mono path + if ( *monochromeResult ) { + if ( abs( scan[0] - scan[1] ) > 16 + || abs( scan[0] - scan[2] ) > 16 ) { + *monochromeResult = false; + } + } + + rgbOr |= cor; + rgbAnd &= cand; + + cor |= scan[3]; + cand &= scan[3]; + + rgbaDiffer |= ( cor ^ cand ); + } + } + + // we assume that all 0 implies that the alpha channel isn't needed, + // because some tools will spit out 32 bit images with a 0 alpha instead + // of 255 alpha, but if the alpha actually is referenced, there will be + // different behavior in the compressed vs uncompressed states. + bool needAlpha; + if ( aAnd == 255 || aOr == 0 ) { + needAlpha = false; + } else { + needAlpha = true; + } + + // catch normal maps first + if ( minimumDepth == TD_BUMP ) { + if ( globalImages->image_useCompression.GetBool() && globalImages->image_useNormalCompression.GetInteger() == 1 && glConfig.sharedTexturePaletteAvailable ) { + // image_useNormalCompression should only be set to 1 on nv_10 and nv_20 paths + return GL_COLOR_INDEX8_EXT; + } else if ( globalImages->image_useCompression.GetBool() && globalImages->image_useNormalCompression.GetInteger() ) { + // image_useNormalCompression == 2 uses rxgb format which produces really good quality for medium settings + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } else { + // we always need the alpha channel for bump maps for swizzling + return GL_RGBA8; + } + } + + // allow a complete override of image compression with a cvar + if ( !globalImages->image_useCompression.GetBool() ) { + minimumDepth = TD_HIGH_QUALITY; + } + + if ( minimumDepth == TD_SPECULAR ) { + // we are assuming that any alpha channel is unintentional + if ( true ) { + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + } else { + return GL_RGB5; + } + } + if ( minimumDepth == TD_DIFFUSE ) { + // we might intentionally have an alpha channel for alpha tested textures + if ( true ) { + if ( !needAlpha ) { + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + } else { + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + } + } else if ( ( aAnd == 255 || aOr == 0 ) ) { + return GL_RGB5; + } else { + return GL_RGBA4; + } + } + + // there will probably be some drivers that don't + // correctly handle the intensity/alpha/luminance/luminance+alpha + // formats, so provide a fallback that only uses the rgb/rgba formats + if ( !globalImages->image_useAllFormats.GetBool() ) { + // pretend rgb is varying and inconsistant, which + // prevents any of the more compact forms + rgbDiffer = 1; + rgbaDiffer = 1; + rgbAnd = 0; + } + + // cases without alpha + if ( !needAlpha ) { + if ( minimumDepth == TD_HIGH_QUALITY ) { + return GL_RGB8; // four bytes + } + if ( true ) { + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; // half byte + } + return GL_RGB5; // two bytes + } + + // cases with alpha + if ( !rgbaDiffer ) { + if ( minimumDepth != TD_HIGH_QUALITY ) { + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte + } + return GL_INTENSITY8; // single byte for all channels + } + +#if 0 + // we don't support alpha textures any more, because there + // is a discrepancy in the definition of TEX_ENV_COMBINE that + // causes them to be treated as 0 0 0 A, instead of 1 1 1 A as + // normal texture modulation treats them + if ( rgbAnd == 255 ) { + return GL_ALPHA8; // single byte, only alpha + } +#endif + + if ( minimumDepth == TD_HIGH_QUALITY ) { + return GL_RGBA8; // four bytes + } + if ( true ) { + return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte + } + if ( !rgbDiffer ) { + return GL_LUMINANCE8_ALPHA8; // two bytes, max quality + } + return GL_RGBA4; // two bytes +} + +/* +================== +SetImageFilterAndRepeat +================== +*/ +void idImage::SetImageFilterAndRepeat() const { + // set the minimize / maximize filtering + switch( filter ) { + case TF_DEFAULT: + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter ); + break; + case TF_LINEAR: + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case TF_NEAREST: + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + default: + common->FatalError( "R_CreateImage: bad texture filter" ); + } + + if ( glConfig.anisotropicAvailable ) { + // only do aniso filtering on mip mapped images + if ( filter == TF_DEFAULT ) { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, globalImages->textureAnisotropy ); + } else { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); + } + } + if ( glConfig.textureLODBiasAvailable ) { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS_EXT, globalImages->textureLODBias ); + } + + // set the wrap/clamp modes + switch( repeat ) { + case TR_REPEAT: + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + case TR_CLAMP_TO_BORDER: + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + break; + case TR_CLAMP_TO_ZERO: + case TR_CLAMP_TO_ZERO_ALPHA: + case TR_CLAMP: + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + break; + default: + common->FatalError( "R_CreateImage: bad texture repeat" ); + } +} + +/* +================ +idImage::Downsize +helper function that takes the current width/height and might make them smaller +================ +*/ +void idImage::GetDownsize( int &scaled_width, int &scaled_height ) const { + int size = 0; + + // perform optional picmip operation to save texture memory + if ( depth == TD_SPECULAR && globalImages->image_downSizeSpecular.GetInteger() ) { + size = globalImages->image_downSizeSpecularLimit.GetInteger(); + if ( size == 0 ) { + size = 64; + } + } else if ( depth == TD_BUMP && globalImages->image_downSizeBump.GetInteger() ) { + size = globalImages->image_downSizeBumpLimit.GetInteger(); + if ( size == 0 ) { + size = 64; + } + } else if ( ( allowDownSize || globalImages->image_forceDownSize.GetBool() ) && globalImages->image_downSize.GetInteger() ) { + size = globalImages->image_downSizeLimit.GetInteger(); + if ( size == 0 ) { + size = 256; + } + } + + if ( size > 0 ) { + while ( scaled_width > size || scaled_height > size ) { + if ( scaled_width > 1 ) { + scaled_width >>= 1; + } + if ( scaled_height > 1 ) { + scaled_height >>= 1; + } + } + } + + // clamp to minimum size + if ( scaled_width < 1 ) { + scaled_width = 1; + } + if ( scaled_height < 1 ) { + scaled_height = 1; + } + + // clamp size to the hardware specific upper limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // This causes a 512*256 texture to sample down to + // 256*128 on a voodoo3, even though it could be 256*256 + while ( scaled_width > glConfig.maxTextureSize + || scaled_height > glConfig.maxTextureSize ) { + scaled_width >>= 1; + scaled_height >>= 1; + } +} + +/* +================ +GenerateImage + +The alpha channel bytes should be 255 if you don't +want the channel. + +We need a material characteristic to ask for specific texture modes. + +Designed limitations of flexibility: + +No support for texture borders. + +No support for texture border color. + +No support for texture environment colors or GL_BLEND or GL_DECAL +texture environments, because the automatic optimization to single +or dual component textures makes those modes potentially undefined. + +No non-power-of-two images. + +No palettized textures. + +There is no way to specify separate wrap/clamp values for S and T + +There is no way to specify explicit mip map levels + +================ +*/ +void idImage::GenerateImage( const byte *pic, int width, int height, + textureFilter_t filterParm, bool allowDownSizeParm, + textureRepeat_t repeatParm, textureDepth_t depthParm ) { + bool preserveBorder; + byte *scaledBuffer; + int scaled_width, scaled_height; + byte *shrunk; + + PurgeImage(); + + filter = filterParm; + allowDownSize = allowDownSizeParm; + repeat = repeatParm; + depth = depthParm; + + // if we don't have a rendering context, just return after we + // have filled in the parms. We must have the values set, or + // an image match from a shader before OpenGL starts would miss + // the generated texture + if ( !glConfig.isInitialized ) { + return; + } + + // don't let mip mapping smear the texture into the clamped border + if ( repeat == TR_CLAMP_TO_ZERO ) { + preserveBorder = true; + } else { + preserveBorder = false; + } + + // make sure it is a power of 2 + scaled_width = MakePowerOfTwo( width ); + scaled_height = MakePowerOfTwo( height ); + + if ( scaled_width != width || scaled_height != height ) { + common->Error( "R_CreateImage: not a power of 2 image" ); + } + + // Optionally modify our width/height based on options/hardware + GetDownsize( scaled_width, scaled_height ); + + scaledBuffer = NULL; + + // generate the texture number + qglGenTextures( 1, &texnum ); + + // select proper internal format before we resample + internalFormat = SelectInternalFormat( &pic, 1, width, height, depth, &isMonochrome ); + + // copy or resample data as appropriate for first MIP level + if ( ( scaled_width == width ) && ( scaled_height == height ) ) { + // we must copy even if unchanged, because the border zeroing + // would otherwise modify const data + scaledBuffer = (byte *)R_StaticAlloc( sizeof( unsigned ) * scaled_width * scaled_height ); + memcpy (scaledBuffer, pic, width*height*4); + } else { + // resample down as needed (FIXME: this doesn't seem like it resamples anymore!) + // scaledBuffer = R_ResampleTexture( pic, width, height, width >>= 1, height >>= 1 ); + scaledBuffer = R_MipMap( pic, width, height, preserveBorder ); + width >>= 1; + height >>= 1; + if ( width < 1 ) { + width = 1; + } + if ( height < 1 ) { + height = 1; + } + + while ( width > scaled_width || height > scaled_height ) { + shrunk = R_MipMap( scaledBuffer, width, height, preserveBorder ); + R_StaticFree( scaledBuffer ); + scaledBuffer = shrunk; + + width >>= 1; + height >>= 1; + if ( width < 1 ) { + width = 1; + } + if ( height < 1 ) { + height = 1; + } + } + + // one might have shrunk down below the target size + scaled_width = width; + scaled_height = height; + } + + uploadHeight = scaled_height; + uploadWidth = scaled_width; + type = TT_2D; + + // zero the border if desired, allowing clamped projection textures + // even after picmip resampling or careless artists. + if ( repeat == TR_CLAMP_TO_ZERO ) { + byte rgba[4]; + + rgba[0] = rgba[1] = rgba[2] = 0; + rgba[3] = 255; + R_SetBorderTexels( (byte *)scaledBuffer, width, height, rgba ); + } + if ( repeat == TR_CLAMP_TO_ZERO_ALPHA ) { + byte rgba[4]; + + rgba[0] = rgba[1] = rgba[2] = 255; + rgba[3] = 0; + R_SetBorderTexels( (byte *)scaledBuffer, width, height, rgba ); + } + + if ( generatorFunction == NULL && ( depth == TD_BUMP && globalImages->image_writeNormalTGA.GetBool() || depth != TD_BUMP && globalImages->image_writeTGA.GetBool() ) ) { + // Optionally write out the texture to a .tga + char filename[MAX_IMAGE_NAME]; + ImageProgramStringToCompressedFileName( imgName, filename ); + char *ext = strrchr(filename, '.'); + if ( ext ) { + strcpy( ext, ".tga" ); + // swap the red/alpha for the write + /* + if ( depth == TD_BUMP ) { + for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) { + scaledBuffer[ i ] = scaledBuffer[ i + 3 ]; + scaledBuffer[ i + 3 ] = 0; + } + } + */ + R_WriteTGA( filename, scaledBuffer, scaled_width, scaled_height, false ); + + // put it back + /* + if ( depth == TD_BUMP ) { + for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) { + scaledBuffer[ i + 3 ] = scaledBuffer[ i ]; + scaledBuffer[ i ] = 0; + } + } + */ + } + } + + // swap the red and alpha for rxgb support + // do this even on tga normal maps so we only have to use + // one fragment program + // if the image is precompressed ( either in palletized mode or true rxgb mode ) + // then it is loaded above and the swap never happens here + if ( depth == TD_BUMP && globalImages->image_useNormalCompression.GetInteger() != 1 ) { + for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) { + scaledBuffer[ i + 3 ] = scaledBuffer[ i ]; + scaledBuffer[ i ] = 0; + } + } + // upload the main image level + Bind(); + + + if ( internalFormat == GL_COLOR_INDEX8_EXT ) { + /* + if ( depth == TD_BUMP ) { + for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) { + scaledBuffer[ i ] = scaledBuffer[ i + 3 ]; + scaledBuffer[ i + 3 ] = 0; + } + } + */ + UploadCompressedNormalMap( scaled_width, scaled_height, scaledBuffer, 0 ); + } else { + qglTexImage2D( GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } + + // create and upload the mip map levels, which we do in all cases, even if we don't think they are needed + int miplevel; + + miplevel = 0; + while ( scaled_width > 1 || scaled_height > 1 ) { + // preserve the border after mip map unless repeating + shrunk = R_MipMap( scaledBuffer, scaled_width, scaled_height, preserveBorder ); + R_StaticFree( scaledBuffer ); + scaledBuffer = shrunk; + + scaled_width >>= 1; + scaled_height >>= 1; + if ( scaled_width < 1 ) { + scaled_width = 1; + } + if ( scaled_height < 1 ) { + scaled_height = 1; + } + miplevel++; + + // this is a visualization tool that shades each mip map + // level with a different color so you can see the + // rasterizer's texture level selection algorithm + // Changing the color doesn't help with lumminance/alpha/intensity formats... + if ( depth == TD_DIFFUSE && globalImages->image_colorMipLevels.GetBool() ) { + R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] ); + } + + // upload the mip map + if ( internalFormat == GL_COLOR_INDEX8_EXT ) { + UploadCompressedNormalMap( scaled_width, scaled_height, scaledBuffer, miplevel ); + } else { + qglTexImage2D( GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } + } + + if ( scaledBuffer != 0 ) { + R_StaticFree( scaledBuffer ); + } + + SetImageFilterAndRepeat(); + + // see if we messed anything up + GL_CheckErrors(); +} + + +/* +================== +Generate3DImage +================== +*/ +void idImage::Generate3DImage( const byte *pic, int width, int height, int picDepth, + textureFilter_t filterParm, bool allowDownSizeParm, + textureRepeat_t repeatParm, textureDepth_t minDepthParm ) { + int scaled_width, scaled_height, scaled_depth; + + PurgeImage(); + + filter = filterParm; + allowDownSize = allowDownSizeParm; + repeat = repeatParm; + depth = minDepthParm; + + // if we don't have a rendering context, just return after we + // have filled in the parms. We must have the values set, or + // an image match from a shader before OpenGL starts would miss + // the generated texture + if ( !glConfig.isInitialized ) { + return; + } + + // make sure it is a power of 2 + scaled_width = MakePowerOfTwo( width ); + scaled_height = MakePowerOfTwo( height ); + scaled_depth = MakePowerOfTwo( picDepth ); + if ( scaled_width != width || scaled_height != height || scaled_depth != picDepth ) { + common->Error( "R_Create3DImage: not a power of 2 image" ); + } + + // FIXME: allow picmip here + + // generate the texture number + qglGenTextures( 1, &texnum ); + + // select proper internal format before we resample + // this function doesn't need to know it is 3D, so just make it very "tall" + internalFormat = SelectInternalFormat( &pic, 1, width, height * picDepth, minDepthParm, &isMonochrome ); + + uploadHeight = scaled_height; + uploadWidth = scaled_width; + uploadDepth = scaled_depth; + + + type = TT_3D; + + // upload the main image level + Bind(); + + qglTexImage3D(GL_TEXTURE_3D, 0, internalFormat, scaled_width, scaled_height, scaled_depth, + 0, GL_RGBA, GL_UNSIGNED_BYTE, pic ); + + // create and upload the mip map levels + int miplevel; + byte *scaledBuffer, *shrunk; + + scaledBuffer = (byte *)R_StaticAlloc( scaled_width * scaled_height * scaled_depth * 4 ); + memcpy( scaledBuffer, pic, scaled_width * scaled_height * scaled_depth * 4 ); + miplevel = 0; + while ( scaled_width > 1 || scaled_height > 1 || scaled_depth > 1 ) { + // preserve the border after mip map unless repeating + shrunk = R_MipMap3D( scaledBuffer, scaled_width, scaled_height, scaled_depth, + (bool)(repeat != TR_REPEAT) ); + R_StaticFree( scaledBuffer ); + scaledBuffer = shrunk; + + scaled_width >>= 1; + scaled_height >>= 1; + scaled_depth >>= 1; + if ( scaled_width < 1 ) { + scaled_width = 1; + } + if ( scaled_height < 1 ) { + scaled_height = 1; + } + if ( scaled_depth < 1 ) { + scaled_depth = 1; + } + miplevel++; + + // upload the mip map + qglTexImage3D(GL_TEXTURE_3D, miplevel, internalFormat, scaled_width, scaled_height, scaled_depth, + 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } + R_StaticFree( scaledBuffer ); + + // set the minimize / maximize filtering + switch( filter ) { + case TF_DEFAULT: + qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter ); + qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter ); + break; + case TF_LINEAR: + qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case TF_NEAREST: + qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + default: + common->FatalError( "R_CreateImage: bad texture filter" ); + } + + // set the wrap/clamp modes + switch( repeat ) { + case TR_REPEAT: + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT ); + break; + case TR_CLAMP_TO_BORDER: + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + break; + case TR_CLAMP_TO_ZERO: + case TR_CLAMP_TO_ZERO_ALPHA: + case TR_CLAMP: + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE ); + break; + default: + common->FatalError( "R_CreateImage: bad texture repeat" ); + } + + // see if we messed anything up + GL_CheckErrors(); +} + + +/* +==================== +GenerateCubeImage + +Non-square cube sides are not allowed +==================== +*/ +void idImage::GenerateCubeImage( const byte *pic[6], int size, + textureFilter_t filterParm, bool allowDownSizeParm, + textureDepth_t depthParm ) { + int scaled_width, scaled_height; + int width, height; + int i; + + PurgeImage(); + + filter = filterParm; + allowDownSize = allowDownSizeParm; + depth = depthParm; + + type = TT_CUBIC; + + // if we don't have a rendering context, just return after we + // have filled in the parms. We must have the values set, or + // an image match from a shader before OpenGL starts would miss + // the generated texture + if ( !glConfig.isInitialized ) { + return; + } + + if ( ! glConfig.cubeMapAvailable ) { + return; + } + + width = height = size; + + // generate the texture number + qglGenTextures( 1, &texnum ); + + // select proper internal format before we resample + internalFormat = SelectInternalFormat( pic, 6, width, height, depth, &isMonochrome ); + + // don't bother with downsample for now + scaled_width = width; + scaled_height = height; + + uploadHeight = scaled_height; + uploadWidth = scaled_width; + + Bind(); + + // no other clamp mode makes sense + qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // set the minimize / maximize filtering + switch( filter ) { + case TF_DEFAULT: + qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter ); + qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter ); + break; + case TF_LINEAR: + qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + break; + case TF_NEAREST: + qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + default: + common->FatalError( "R_CreateImage: bad texture filter" ); + } + + // upload the base level + // FIXME: support GL_COLOR_INDEX8_EXT? + for ( i = 0 ; i < 6 ; i++ ) { + qglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, 0, internalFormat, scaled_width, scaled_height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, pic[i] ); + } + + + // create and upload the mip map levels + int miplevel; + byte *shrunk[6]; + + for ( i = 0 ; i < 6 ; i++ ) { + shrunk[i] = R_MipMap( pic[i], scaled_width, scaled_height, false ); + } + + miplevel = 1; + while ( scaled_width > 1 ) { + for ( i = 0 ; i < 6 ; i++ ) { + byte *shrunken; + + qglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, miplevel, internalFormat, + scaled_width / 2, scaled_height / 2, 0, + GL_RGBA, GL_UNSIGNED_BYTE, shrunk[i] ); + + if ( scaled_width > 2 ) { + shrunken = R_MipMap( shrunk[i], scaled_width/2, scaled_height/2, false ); + } else { + shrunken = NULL; + } + + R_StaticFree( shrunk[i] ); + shrunk[i] = shrunken; + } + + scaled_width >>= 1; + scaled_height >>= 1; + miplevel++; + } + + // see if we messed anything up + GL_CheckErrors(); +} + + +/* +================ +ImageProgramStringToFileCompressedFileName +================ +*/ +void idImage::ImageProgramStringToCompressedFileName( const char *imageProg, char *fileName ) const { + const char *s; + char *f; + + strcpy( fileName, "dds/" ); + f = fileName + strlen( fileName ); + + int depth = 0; + + // convert all illegal characters to underscores + // this could conceivably produce a duplicated mapping, but we aren't going to worry about it + for ( s = imageProg ; *s ; s++ ) { + if ( *s == '/' || *s == '\\' || *s == '(') { + if ( depth < 4 ) { + *f = '/'; + depth ++; + } else { + *f = ' '; + } + f++; + } else if ( *s == '<' || *s == '>' || *s == ':' || *s == '|' || *s == '"' || *s == '.' ) { + *f = '_'; + f++; + } else if ( *s == ' ' && *(f-1) == '/' ) { // ignore a space right after a slash + } else if ( *s == ')' || *s == ',' ) { // always ignore these + } else { + *f = *s; + f++; + } + } + *f++ = 0; + strcat( fileName, ".dds" ); +} + +/* +================== +NumLevelsForImageSize +================== +*/ +int idImage::NumLevelsForImageSize( int width, int height ) const { + int numLevels = 1; + + while ( width > 1 || height > 1 ) { + numLevels++; + width >>= 1; + height >>= 1; + } + + return numLevels; +} + +/* +================ +WritePrecompressedImage + +When we are happy with our source data, we can write out precompressed +versions of everything to speed future load times. +================ +*/ +void idImage::WritePrecompressedImage() { + + // Always write the precompressed image if we're making a build + if ( !com_makingBuild.GetBool() ) { + if ( !globalImages->image_writePrecompressedTextures.GetBool() || !globalImages->image_usePrecompressedTextures.GetBool() ) { + return; + } + } + + if ( !glConfig.isInitialized ) { + return; + } + + char filename[MAX_IMAGE_NAME]; + ImageProgramStringToCompressedFileName( imgName, filename ); + + + + int numLevels = NumLevelsForImageSize( uploadWidth, uploadHeight ); + if ( numLevels > MAX_TEXTURE_LEVELS ) { + common->Warning( "R_WritePrecompressedImage: level > MAX_TEXTURE_LEVELS for image %s", filename ); + return; + } + + // glGetTexImage only supports a small subset of all the available internal formats + // We have to use BGRA because DDS is a windows based format + int altInternalFormat = 0; + int bitSize = 0; + switch ( internalFormat ) { + case GL_COLOR_INDEX8_EXT: + case GL_COLOR_INDEX: + // this will not work with dds viewers but we need it in this format to save disk + // load speed ( i.e. size ) + altInternalFormat = GL_COLOR_INDEX; + bitSize = 24; + break; + case 1: + case GL_INTENSITY8: + case GL_LUMINANCE8: + case 3: + case GL_RGB8: + altInternalFormat = GL_BGR_EXT; + bitSize = 24; + break; + case GL_LUMINANCE8_ALPHA8: + case 4: + case GL_RGBA8: + altInternalFormat = GL_BGRA_EXT; + bitSize = 32; + break; + case GL_ALPHA8: + altInternalFormat = GL_ALPHA; + bitSize = 8; + break; + default: + if ( FormatIsDXT( internalFormat ) ) { + altInternalFormat = internalFormat; + } else { + common->Warning("Unknown or unsupported format for %s", filename); + return; + } + } + + if ( globalImages->image_useOffLineCompression.GetBool() && FormatIsDXT( altInternalFormat ) ) { + idStr outFile = fileSystem->RelativePathToOSPath( filename, "fs_basepath" ); + idStr inFile = outFile; + inFile.StripFileExtension(); + inFile.SetFileExtension( "tga" ); + idStr format; + if ( depth == TD_BUMP ) { + format = "RXGB +red 0.0 +green 0.5 +blue 0.5"; + } else { + switch ( altInternalFormat ) { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + format = "DXT1"; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + format = "DXT1 -alpha_threshold"; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + format = "DXT3"; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + format = "DXT5"; + break; + } + } + globalImages->AddDDSCommand( va( "z:/d3xp/compressonator/thecompressonator -convert \"%s\" \"%s\" %s -mipmaps\n", inFile.c_str(), outFile.c_str(), format.c_str() ) ); + return; + } + + + ddsFileHeader_t header; + memset( &header, 0, sizeof(header) ); + header.dwSize = sizeof(header); + header.dwFlags = DDSF_CAPS | DDSF_PIXELFORMAT | DDSF_WIDTH | DDSF_HEIGHT; + header.dwHeight = uploadHeight; + header.dwWidth = uploadWidth; + + // hack in our monochrome flag for the NV20 optimization + if ( isMonochrome ) { + header.dwFlags |= DDSF_ID_MONOCHROME; + } + + if ( FormatIsDXT( altInternalFormat ) ) { + // size (in bytes) of the compressed base image + header.dwFlags |= DDSF_LINEARSIZE; + header.dwPitchOrLinearSize = ( ( uploadWidth + 3 ) / 4 ) * ( ( uploadHeight + 3 ) / 4 )* + (altInternalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16); + } + else { + // 4 Byte aligned line width (from nv_dds) + header.dwFlags |= DDSF_PITCH; + header.dwPitchOrLinearSize = ( ( uploadWidth * bitSize + 31 ) & -32 ) >> 3; + } + + header.dwCaps1 = DDSF_TEXTURE; + + if ( numLevels > 1 ) { + header.dwMipMapCount = numLevels; + header.dwFlags |= DDSF_MIPMAPCOUNT; + header.dwCaps1 |= DDSF_MIPMAP | DDSF_COMPLEX; + } + + header.ddspf.dwSize = sizeof(header.ddspf); + if ( FormatIsDXT( altInternalFormat ) ) { + header.ddspf.dwFlags = DDSF_FOURCC; + switch ( altInternalFormat ) { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','1'); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + header.ddspf.dwFlags |= DDSF_ALPHAPIXELS; + header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','1'); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','3'); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','5'); + break; + } + } else { + header.ddspf.dwFlags = ( internalFormat == GL_COLOR_INDEX8_EXT ) ? DDSF_RGB | DDSF_ID_INDEXCOLOR : DDSF_RGB; + header.ddspf.dwRGBBitCount = bitSize; + switch ( altInternalFormat ) { + case GL_BGRA_EXT: + case GL_LUMINANCE_ALPHA: + header.ddspf.dwFlags |= DDSF_ALPHAPIXELS; + header.ddspf.dwABitMask = 0xFF000000; + // Fall through + case GL_BGR_EXT: + case GL_LUMINANCE: + case GL_COLOR_INDEX: + header.ddspf.dwRBitMask = 0x00FF0000; + header.ddspf.dwGBitMask = 0x0000FF00; + header.ddspf.dwBBitMask = 0x000000FF; + break; + case GL_ALPHA: + header.ddspf.dwFlags = DDSF_ALPHAPIXELS; + header.ddspf.dwABitMask = 0xFF000000; + break; + default: + common->Warning( "Unknown or unsupported format for %s", filename ); + return; + } + } + + idFile *f = fileSystem->OpenFileWrite( filename ); + if ( f == NULL ) { + common->Warning( "Could not open %s trying to write precompressed image", filename ); + return; + } + common->Printf( "Writing precompressed image: %s\n", filename ); + + f->Write( "DDS ", 4 ); + f->Write( &header, sizeof(header) ); + + // bind to the image so we can read back the contents + Bind(); + + qglPixelStorei( GL_PACK_ALIGNMENT, 1 ); // otherwise small rows get padded to 32 bits + + int uw = uploadWidth; + int uh = uploadHeight; + + // Will be allocated first time through the loop + byte *data = NULL; + + for ( int level = 0 ; level < numLevels ; level++ ) { + + int size = 0; + if ( FormatIsDXT( altInternalFormat ) ) { + size = ( ( uw + 3 ) / 4 ) * ( ( uh + 3 ) / 4 ) * + (altInternalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16); + } else { + size = uw * uh * (bitSize / 8); + } + + if (data == NULL) { + data = (byte *)R_StaticAlloc( size ); + } + + if ( FormatIsDXT( altInternalFormat ) ) { + qglGetCompressedTexImageARB( GL_TEXTURE_2D, level, data ); + } else { + qglGetTexImage( GL_TEXTURE_2D, level, altInternalFormat, GL_UNSIGNED_BYTE, data ); + } + + f->Write( data, size ); + + uw /= 2; + uh /= 2; + if (uw < 1) { + uw = 1; + } + if (uh < 1) { + uh = 1; + } + } + + if (data != NULL) { + R_StaticFree( data ); + } + + fileSystem->CloseFile( f ); +} + +/* +================ +ShouldImageBePartialCached + +Returns true if there is a precompressed image, and it is large enough +to be worth caching +================ +*/ +bool idImage::ShouldImageBePartialCached() { + if ( !globalImages->image_useCache.GetBool() ) { + return false; + } + + // the allowDownSize flag does double-duty as don't-partial-load + if ( !allowDownSize ) { + return false; + } + + if ( globalImages->image_cacheMinK.GetInteger() <= 0 ) { + return false; + } + + // if we are doing a copyFiles, make sure the original images are referenced + if ( fileSystem->PerformingCopyFiles() ) { + return false; + } + + char filename[MAX_IMAGE_NAME]; + ImageProgramStringToCompressedFileName( imgName, filename ); + + // get the file timestamp + fileSystem->ReadFile( filename, NULL, ×tamp ); + + if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) { + return false; + } + + // open it and get the file size + idFile *f; + + f = fileSystem->OpenFileRead( filename ); + if ( !f ) { + return false; + } + + int len = f->Length(); + fileSystem->CloseFile( f ); + + if ( len <= globalImages->image_cacheMinK.GetInteger() * 1024 ) { + return false; + } + + // we do want to do a partial load + return true; +} + +/* +================ +CheckPrecompressedImage + +If fullLoad is false, only the small mip levels of the image will be loaded +================ +*/ +bool idImage::CheckPrecompressedImage( bool fullLoad ) { + if ( !glConfig.isInitialized ) { + return false; + } + +#if 1 // ( _D3XP had disabled ) - Allow grabbing of DDS's from original Doom pak files + // if we are doing a copyFiles, make sure the original images are referenced + if ( fileSystem->PerformingCopyFiles() ) { + return false; + } +#endif + + if ( depth == TD_BUMP && globalImages->image_useNormalCompression.GetInteger() != 2 ) { + return false; + } + + // god i love last minute hacks :-) + if ( com_machineSpec.GetInteger() >= 1 && com_videoRam.GetInteger() >= 128 && imgName.Icmpn( "lights/", 7 ) == 0 ) { + return false; + } + + char filename[MAX_IMAGE_NAME]; + ImageProgramStringToCompressedFileName( imgName, filename ); + + // get the file timestamp + ID_TIME_T precompTimestamp; + fileSystem->ReadFile( filename, NULL, &precompTimestamp ); + + + if ( precompTimestamp == FILE_NOT_FOUND_TIMESTAMP ) { + return false; + } + + if ( !generatorFunction && timestamp != FILE_NOT_FOUND_TIMESTAMP ) { + if ( precompTimestamp < timestamp ) { + // The image has changed after being precompressed + return false; + } + } + + timestamp = precompTimestamp; + + // open it and just read the header + idFile *f; + + f = fileSystem->OpenFileRead( filename ); + if ( !f ) { + return false; + } + + int len = f->Length(); + if ( len < sizeof( ddsFileHeader_t ) ) { + fileSystem->CloseFile( f ); + return false; + } + + if ( !fullLoad && len > globalImages->image_cacheMinK.GetInteger() * 1024 ) { + len = globalImages->image_cacheMinK.GetInteger() * 1024; + } + + byte *data = (byte *)R_StaticAlloc( len ); + + f->Read( data, len ); + + fileSystem->CloseFile( f ); + + unsigned long magic = LittleLong( *(unsigned long *)data ); + ddsFileHeader_t *_header = (ddsFileHeader_t *)(data + 4); + int ddspf_dwFlags = LittleLong( _header->ddspf.dwFlags ); + + if ( magic != DDS_MAKEFOURCC('D', 'D', 'S', ' ')) { + common->Printf( "CheckPrecompressedImage( %s ): magic != 'DDS '\n", imgName.c_str() ); + R_StaticFree( data ); + return false; + } + + // if we don't support color index textures, we must load the full image + // should we just expand the 256 color image to 32 bit for upload? + if ( ddspf_dwFlags & DDSF_ID_INDEXCOLOR && !glConfig.sharedTexturePaletteAvailable ) { + R_StaticFree( data ); + return false; + } + + // upload all the levels + UploadPrecompressedImage( data, len ); + + R_StaticFree( data ); + + return true; +} + +/* +=================== +UploadPrecompressedImage + +This can be called by the front end during nromal loading, +or by the backend after a background read of the file +has completed +=================== +*/ +void idImage::UploadPrecompressedImage( byte *data, int len ) { + ddsFileHeader_t *header = (ddsFileHeader_t *)(data + 4); + + // ( not byte swapping dwReserved1 dwReserved2 ) + header->dwSize = LittleLong( header->dwSize ); + header->dwFlags = LittleLong( header->dwFlags ); + header->dwHeight = LittleLong( header->dwHeight ); + header->dwWidth = LittleLong( header->dwWidth ); + header->dwPitchOrLinearSize = LittleLong( header->dwPitchOrLinearSize ); + header->dwDepth = LittleLong( header->dwDepth ); + header->dwMipMapCount = LittleLong( header->dwMipMapCount ); + header->dwCaps1 = LittleLong( header->dwCaps1 ); + header->dwCaps2 = LittleLong( header->dwCaps2 ); + + header->ddspf.dwSize = LittleLong( header->ddspf.dwSize ); + header->ddspf.dwFlags = LittleLong( header->ddspf.dwFlags ); + header->ddspf.dwFourCC = LittleLong( header->ddspf.dwFourCC ); + header->ddspf.dwRGBBitCount = LittleLong( header->ddspf.dwRGBBitCount ); + header->ddspf.dwRBitMask = LittleLong( header->ddspf.dwRBitMask ); + header->ddspf.dwGBitMask = LittleLong( header->ddspf.dwGBitMask ); + header->ddspf.dwBBitMask = LittleLong( header->ddspf.dwBBitMask ); + header->ddspf.dwABitMask = LittleLong( header->ddspf.dwABitMask ); + + // generate the texture number + qglGenTextures( 1, &texnum ); + + int externalFormat = 0; + + precompressedFile = true; + + uploadWidth = header->dwWidth; + uploadHeight = header->dwHeight; + if ( header->ddspf.dwFlags & DDSF_FOURCC ) { + switch ( header->ddspf.dwFourCC ) { + case DDS_MAKEFOURCC( 'D', 'X', 'T', '1' ): + if ( header->ddspf.dwFlags & DDSF_ALPHAPIXELS ) { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } else { + internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + } + break; + case DDS_MAKEFOURCC( 'D', 'X', 'T', '3' ): + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + break; + case DDS_MAKEFOURCC( 'D', 'X', 'T', '5' ): + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + case DDS_MAKEFOURCC( 'R', 'X', 'G', 'B' ): + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + default: + common->Warning( "Invalid compressed internal format\n" ); + return; + } + } else if ( ( header->ddspf.dwFlags & DDSF_RGBA ) && header->ddspf.dwRGBBitCount == 32 ) { + externalFormat = GL_BGRA_EXT; + internalFormat = GL_RGBA8; + } else if ( ( header->ddspf.dwFlags & DDSF_RGB ) && header->ddspf.dwRGBBitCount == 32 ) { + externalFormat = GL_BGRA_EXT; + internalFormat = GL_RGBA8; + } else if ( ( header->ddspf.dwFlags & DDSF_RGB ) && header->ddspf.dwRGBBitCount == 24 ) { + if ( header->ddspf.dwFlags & DDSF_ID_INDEXCOLOR ) { + externalFormat = GL_COLOR_INDEX; + internalFormat = GL_COLOR_INDEX8_EXT; + } else { + externalFormat = GL_BGR_EXT; + internalFormat = GL_RGB8; + } + } else if ( header->ddspf.dwRGBBitCount == 8 ) { + externalFormat = GL_ALPHA; + internalFormat = GL_ALPHA8; + } else { + common->Warning( "Invalid uncompressed internal format\n" ); + return; + } + + // we need the monochrome flag for the NV20 optimized path + if ( header->dwFlags & DDSF_ID_MONOCHROME ) { + isMonochrome = true; + } + + type = TT_2D; // FIXME: we may want to support pre-compressed cube maps in the future + + Bind(); + + int numMipmaps = 1; + if ( header->dwFlags & DDSF_MIPMAPCOUNT ) { + numMipmaps = header->dwMipMapCount; + } + + int uw = uploadWidth; + int uh = uploadHeight; + + // We may skip some mip maps if we are downsizing + int skipMip = 0; + GetDownsize( uploadWidth, uploadHeight ); + + byte *imagedata = data + sizeof(ddsFileHeader_t) + 4; + + for ( int i = 0 ; i < numMipmaps; i++ ) { + int size = 0; + if ( FormatIsDXT( internalFormat ) ) { + size = ( ( uw + 3 ) / 4 ) * ( ( uh + 3 ) / 4 ) * + (internalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16); + } else { + size = uw * uh * (header->ddspf.dwRGBBitCount / 8); + } + + if ( uw > uploadWidth || uh > uploadHeight ) { + skipMip++; + } else { + if ( FormatIsDXT( internalFormat ) ) { + qglCompressedTexImage2DARB( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, size, imagedata ); + } else { + qglTexImage2D( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, externalFormat, GL_UNSIGNED_BYTE, imagedata ); + } + } + + imagedata += size; + uw /= 2; + uh /= 2; + if (uw < 1) { + uw = 1; + } + if (uh < 1) { + uh = 1; + } + } + + SetImageFilterAndRepeat(); +} + +/* +=============== +ActuallyLoadImage + +Absolutely every image goes through this path +On exit, the idImage will have a valid OpenGL texture number that can be bound +=============== +*/ +void idImage::ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd ) { + int width, height; + byte *pic; + + // this is the ONLY place generatorFunction will ever be called + if ( generatorFunction ) { + generatorFunction( this ); + return; + } + + // if we are a partial image, we are only going to load from a compressed file + if ( isPartialImage ) { + if ( CheckPrecompressedImage( false ) ) { + return; + } + // this is an error -- the partial image failed to load + MakeDefault(); + return; + } + + // + // load the image from disk + // + if ( cubeFiles != CF_2D ) { + byte *pics[6]; + + // we don't check for pre-compressed cube images currently + R_LoadCubeImages( imgName, cubeFiles, pics, &width, ×tamp ); + + if ( pics[0] == NULL ) { + common->Warning( "Couldn't load cube image: %s", imgName.c_str() ); + MakeDefault(); + return; + } + + GenerateCubeImage( (const byte **)pics, width, filter, allowDownSize, depth ); + precompressedFile = false; + + for ( int i = 0 ; i < 6 ; i++ ) { + if ( pics[i] ) { + R_StaticFree( pics[i] ); + } + } + } else { + // see if we have a pre-generated image file that is + // already image processed and compressed + if ( checkForPrecompressed && globalImages->image_usePrecompressedTextures.GetBool() ) { + if ( CheckPrecompressedImage( true ) ) { + // we got the precompressed image + return; + } + // fall through to load the normal image + } + + R_LoadImageProgram( imgName, &pic, &width, &height, ×tamp, &depth ); + + if ( pic == NULL ) { + common->Warning( "Couldn't load image: %s", imgName.c_str() ); + MakeDefault(); + return; + } +/* + // swap the red and alpha for rxgb support + // do this even on tga normal maps so we only have to use + // one fragment program + // if the image is precompressed ( either in palletized mode or true rxgb mode ) + // then it is loaded above and the swap never happens here + if ( depth == TD_BUMP && globalImages->image_useNormalCompression.GetInteger() != 1 ) { + for ( int i = 0; i < width * height * 4; i += 4 ) { + pic[ i + 3 ] = pic[ i ]; + pic[ i ] = 0; + } + } +*/ + // build a hash for checking duplicate image files + // NOTE: takes about 10% of image load times (SD) + // may not be strictly necessary, but some code uses it, so let's leave it in + imageHash = MD4_BlockChecksum( pic, width * height * 4 ); + + GenerateImage( pic, width, height, filter, allowDownSize, repeat, depth ); + timestamp = timestamp; + precompressedFile = false; + + R_StaticFree( pic ); + + // write out the precompressed version of this file if needed + WritePrecompressedImage(); + } +} + +//========================================================================================================= + +/* +=============== +PurgeImage +=============== +*/ +void idImage::PurgeImage() { + if ( texnum != TEXTURE_NOT_LOADED ) { + // sometimes is NULL when exiting with an error + if ( qglDeleteTextures ) { + qglDeleteTextures( 1, &texnum ); // this should be the ONLY place it is ever called! + } + texnum = TEXTURE_NOT_LOADED; + } + + // clear all the current binding caches, so the next bind will do a real one + for ( int i = 0 ; i < MAX_MULTITEXTURE_UNITS ; i++ ) { + backEnd.glState.tmu[i].current2DMap = -1; + backEnd.glState.tmu[i].current3DMap = -1; + backEnd.glState.tmu[i].currentCubeMap = -1; + } +} + +/* +============== +Bind + +Automatically enables 2D mapping, cube mapping, or 3D texturing if needed +============== +*/ +void idImage::Bind() { + if ( qglLogFile ) { + RB_LogComment( "idImage::Bind( %s )\n", imgName.c_str() ); + } + + // if this is an image that we are caching, move it to the front of the LRU chain + if ( partialImage ) { + if ( cacheUsageNext ) { + // unlink from old position + cacheUsageNext->cacheUsagePrev = cacheUsagePrev; + cacheUsagePrev->cacheUsageNext = cacheUsageNext; + } + // link in at the head of the list + cacheUsageNext = globalImages->cacheLRU.cacheUsageNext; + cacheUsagePrev = &globalImages->cacheLRU; + + cacheUsageNext->cacheUsagePrev = this; + cacheUsagePrev->cacheUsageNext = this; + } + + // load the image if necessary (FIXME: not SMP safe!) + if ( texnum == TEXTURE_NOT_LOADED ) { + if ( partialImage ) { + // if we have a partial image, go ahead and use that + this->partialImage->Bind(); + + // start a background load of the full thing if it isn't already in the queue + if ( !backgroundLoadInProgress ) { + StartBackgroundImageLoad(); + } + return; + } + + // load the image on demand here, which isn't our normal game operating mode + ActuallyLoadImage( true, true ); // check for precompressed, load is from back end + } + + + // bump our statistic counters + frameUsed = backEnd.frameCount; + bindCount++; + + tmu_t *tmu = &backEnd.glState.tmu[backEnd.glState.currenttmu]; + + // enable or disable apropriate texture modes + if ( tmu->textureType != type && ( backEnd.glState.currenttmu < glConfig.maxTextureUnits ) ) { + if ( tmu->textureType == TT_CUBIC ) { + qglDisable( GL_TEXTURE_CUBE_MAP_EXT ); + } else if ( tmu->textureType == TT_3D ) { + qglDisable( GL_TEXTURE_3D ); + } else if ( tmu->textureType == TT_2D ) { + qglDisable( GL_TEXTURE_2D ); + } + + if ( type == TT_CUBIC ) { + qglEnable( GL_TEXTURE_CUBE_MAP_EXT ); + } else if ( type == TT_3D ) { + qglEnable( GL_TEXTURE_3D ); + } else if ( type == TT_2D ) { + qglEnable( GL_TEXTURE_2D ); + } + tmu->textureType = type; + } + + // bind the texture + if ( type == TT_2D ) { + if ( tmu->current2DMap != texnum ) { + tmu->current2DMap = texnum; + qglBindTexture( GL_TEXTURE_2D, texnum ); + } + } else if ( type == TT_CUBIC ) { + if ( tmu->currentCubeMap != texnum ) { + tmu->currentCubeMap = texnum; + qglBindTexture( GL_TEXTURE_CUBE_MAP_EXT, texnum ); + } + } else if ( type == TT_3D ) { + if ( tmu->current3DMap != texnum ) { + tmu->current3DMap = texnum; + qglBindTexture( GL_TEXTURE_3D, texnum ); + } + } + + if ( com_purgeAll.GetBool() ) { + GLclampf priority = 1.0f; + qglPrioritizeTextures( 1, &texnum, &priority ); + } +} + +/* +============== +BindFragment + +Fragment programs explicitly say which type of map they want, so we don't need to +do any enable / disable changes +============== +*/ +void idImage::BindFragment() { + if ( qglLogFile ) { + RB_LogComment( "idImage::BindFragment %s )\n", imgName.c_str() ); + } + + // if this is an image that we are caching, move it to the front of the LRU chain + if ( partialImage ) { + if ( cacheUsageNext ) { + // unlink from old position + cacheUsageNext->cacheUsagePrev = cacheUsagePrev; + cacheUsagePrev->cacheUsageNext = cacheUsageNext; + } + // link in at the head of the list + cacheUsageNext = globalImages->cacheLRU.cacheUsageNext; + cacheUsagePrev = &globalImages->cacheLRU; + + cacheUsageNext->cacheUsagePrev = this; + cacheUsagePrev->cacheUsageNext = this; + } + + // load the image if necessary (FIXME: not SMP safe!) + if ( texnum == TEXTURE_NOT_LOADED ) { + if ( partialImage ) { + // if we have a partial image, go ahead and use that + this->partialImage->BindFragment(); + + // start a background load of the full thing if it isn't already in the queue + if ( !backgroundLoadInProgress ) { + StartBackgroundImageLoad(); + } + return; + } + + // load the image on demand here, which isn't our normal game operating mode + ActuallyLoadImage( true, true ); // check for precompressed, load is from back end + } + + + // bump our statistic counters + frameUsed = backEnd.frameCount; + bindCount++; + + // bind the texture + if ( type == TT_2D ) { + qglBindTexture( GL_TEXTURE_2D, texnum ); + } else if ( type == TT_RECT ) { + qglBindTexture( GL_TEXTURE_RECTANGLE_NV, texnum ); + } else if ( type == TT_CUBIC ) { + qglBindTexture( GL_TEXTURE_CUBE_MAP_EXT, texnum ); + } else if ( type == TT_3D ) { + qglBindTexture( GL_TEXTURE_3D, texnum ); + } +} + + +/* +==================== +CopyFramebuffer +==================== +*/ +void idImage::CopyFramebuffer( int x, int y, int imageWidth, int imageHeight, bool useOversizedBuffer ) { + Bind(); + + if ( cvarSystem->GetCVarBool( "g_lowresFullscreenFX" ) ) { + imageWidth = 512; + imageHeight = 512; + } + + // if the size isn't a power of 2, the image must be increased in size + int potWidth, potHeight; + + potWidth = MakePowerOfTwo( imageWidth ); + potHeight = MakePowerOfTwo( imageHeight ); + + GetDownsize( imageWidth, imageHeight ); + GetDownsize( potWidth, potHeight ); + + qglReadBuffer( GL_BACK ); + + // only resize if the current dimensions can't hold it at all, + // otherwise subview renderings could thrash this + if ( ( useOversizedBuffer && ( uploadWidth < potWidth || uploadHeight < potHeight ) ) + || ( !useOversizedBuffer && ( uploadWidth != potWidth || uploadHeight != potHeight ) ) ) { + uploadWidth = potWidth; + uploadHeight = potHeight; + if ( potWidth == imageWidth && potHeight == imageHeight ) { + qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, x, y, imageWidth, imageHeight, 0 ); + } else { + byte *junk; + // we need to create a dummy image with power of two dimensions, + // then do a qglCopyTexSubImage2D of the data we want + // this might be a 16+ meg allocation, which could fail on _alloca + junk = (byte *)Mem_Alloc( potWidth * potHeight * 4 ); + memset( junk, 0, potWidth * potHeight * 4 ); //!@# +#if 0 // Disabling because it's unnecessary and introduces a green strip on edge of _currentRender + for ( int i = 0 ; i < potWidth * potHeight * 4 ; i+=4 ) { + junk[i+1] = 255; + } +#endif + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, potWidth, potHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, junk ); + Mem_Free( junk ); + + qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight ); + } + } else { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression or some other silliness + qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight ); + } + + // if the image isn't a full power of two, duplicate an extra row and/or column to fix bilerps + if ( imageWidth != potWidth ) { + qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, imageWidth, 0, x+imageWidth-1, y, 1, imageHeight ); + } + if ( imageHeight != potHeight ) { + qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, imageHeight, x, y+imageHeight-1, imageWidth, 1 ); + } + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + + backEnd.c_copyFrameBuffer++; +} + +/* +==================== +CopyDepthbuffer + +This should just be part of copyFramebuffer once we have a proper image type field +==================== +*/ +void idImage::CopyDepthbuffer( int x, int y, int imageWidth, int imageHeight ) { + Bind(); + + // if the size isn't a power of 2, the image must be increased in size + int potWidth, potHeight; + + potWidth = MakePowerOfTwo( imageWidth ); + potHeight = MakePowerOfTwo( imageHeight ); + + if ( uploadWidth != potWidth || uploadHeight != potHeight ) { + uploadWidth = potWidth; + uploadHeight = potHeight; + if ( potWidth == imageWidth && potHeight == imageHeight ) { + qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, x, y, imageWidth, imageHeight, 0 ); + } else { + // we need to create a dummy image with power of two dimensions, + // then do a qglCopyTexSubImage2D of the data we want + qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, potWidth, potHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight ); + } + } else { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression or some other silliness + qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight ); + } + +// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); +// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); +} + +/* +============= +RB_UploadScratchImage + +if rows = cols * 6, assume it is a cube map animation +============= +*/ +void idImage::UploadScratch( const byte *data, int cols, int rows ) { + int i; + + // if rows = cols * 6, assume it is a cube map animation + if ( rows == cols * 6 ) { + if ( type != TT_CUBIC ) { + type = TT_CUBIC; + uploadWidth = -1; // for a non-sub upload + } + + Bind(); + + rows /= 6; + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != uploadWidth || rows != uploadHeight ) { + uploadWidth = cols; + uploadHeight = rows; + + // upload the base level + for ( i = 0 ; i < 6 ; i++ ) { + qglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, 0, GL_RGB8, cols, rows, 0, + GL_RGBA, GL_UNSIGNED_BYTE, data + cols*rows*4*i ); + } + } else { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + for ( i = 0 ; i < 6 ; i++ ) { + qglTexSubImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, 0, 0, 0, cols, rows, + GL_RGBA, GL_UNSIGNED_BYTE, data + cols*rows*4*i ); + } + } + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + // no other clamp mode makes sense + qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else { + // otherwise, it is a 2D image + if ( type != TT_2D ) { + type = TT_2D; + uploadWidth = -1; // for a non-sub upload + } + + Bind(); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != uploadWidth || rows != uploadHeight ) { + uploadWidth = cols; + uploadHeight = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } else { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + // these probably should be clamp, but we have a lot of issues with editor + // geometry coming out with texcoords slightly off one side, resulting in + // a smear across the entire polygon +#if 1 + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); +#else + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); +#endif + } +} + + +void idImage::SetClassification( int tag ) { + classification = tag; +} + +/* +================== +StorageSize +================== +*/ +int idImage::StorageSize() const { + int baseSize; + + if ( texnum == TEXTURE_NOT_LOADED ) { + return 0; + } + + switch ( type ) { + default: + case TT_2D: + baseSize = uploadWidth*uploadHeight; + break; + case TT_3D: + baseSize = uploadWidth*uploadHeight*uploadDepth; + break; + case TT_CUBIC: + baseSize = 6 * uploadWidth*uploadHeight; + break; + } + + baseSize *= BitsForInternalFormat( internalFormat ); + + baseSize /= 8; + + // account for mip mapping + baseSize = baseSize * 4 / 3; + + return baseSize; +} + +/* +================== +Print +================== +*/ +void idImage::Print() const { + if ( precompressedFile ) { + common->Printf( "P" ); + } else if ( generatorFunction ) { + common->Printf( "F" ); + } else { + common->Printf( " " ); + } + + switch ( type ) { + case TT_2D: + common->Printf( " " ); + break; + case TT_3D: + common->Printf( "3" ); + break; + case TT_CUBIC: + common->Printf( "C" ); + break; + case TT_RECT: + common->Printf( "R" ); + break; + default: + common->Printf( "", type ); + break; + } + + common->Printf( "%4i %4i ", uploadWidth, uploadHeight ); + + switch( filter ) { + case TF_DEFAULT: + common->Printf( "dflt " ); + break; + case TF_LINEAR: + common->Printf( "linr " ); + break; + case TF_NEAREST: + common->Printf( "nrst " ); + break; + default: + common->Printf( "", filter ); + break; + } + + switch ( internalFormat ) { + case GL_INTENSITY8: + case 1: + common->Printf( "I " ); + break; + case 2: + case GL_LUMINANCE8_ALPHA8: + common->Printf( "LA " ); + break; + case 3: + common->Printf( "RGB " ); + break; + case 4: + common->Printf( "RGBA " ); + break; + case GL_LUMINANCE8: + common->Printf( "L " ); + break; + case GL_ALPHA8: + common->Printf( "A " ); + break; + case GL_RGBA8: + common->Printf( "RGBA8 " ); + break; + case GL_RGB8: + common->Printf( "RGB8 " ); + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + common->Printf( "DXT1 " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + common->Printf( "DXT1A " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + common->Printf( "DXT3 " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + common->Printf( "DXT5 " ); + break; + case GL_RGBA4: + common->Printf( "RGBA4 " ); + break; + case GL_RGB5: + common->Printf( "RGB5 " ); + break; + case GL_COLOR_INDEX8_EXT: + common->Printf( "CI8 " ); + break; + case GL_COLOR_INDEX: + common->Printf( "CI " ); + break; + case GL_COMPRESSED_RGB_ARB: + common->Printf( "RGBC " ); + break; + case GL_COMPRESSED_RGBA_ARB: + common->Printf( "RGBAC " ); + break; + case 0: + common->Printf( " " ); + break; + default: + common->Printf( "", internalFormat ); + break; + } + + switch ( repeat ) { + case TR_REPEAT: + common->Printf( "rept " ); + break; + case TR_CLAMP_TO_ZERO: + common->Printf( "zero " ); + break; + case TR_CLAMP_TO_ZERO_ALPHA: + common->Printf( "azro " ); + break; + case TR_CLAMP: + common->Printf( "clmp " ); + break; + default: + common->Printf( "", repeat ); + break; + } + + common->Printf( "%4ik ", StorageSize() / 1024 ); + + common->Printf( " %s\n", imgName.c_str() ); +} diff --git a/src/renderer/image_process.cpp b/src/renderer/image_process.cpp new file mode 100644 index 0000000..200ec6e --- /dev/null +++ b/src/renderer/image_process.cpp @@ -0,0 +1,616 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* +================ +R_ResampleTexture + +Used to resample images in a more general than quartering fashion. + +This will only have filter coverage if the resampled size +is greater than half the original size. + +If a larger shrinking is needed, use the mipmap function +after resampling to the next lower power of two. +================ +*/ +#define MAX_DIMENSION 4096 +byte *R_ResampleTexture( const byte *in, int inwidth, int inheight, + int outwidth, int outheight ) { + int i, j; + const byte *inrow, *inrow2; + unsigned int frac, fracstep; + unsigned int p1[MAX_DIMENSION], p2[MAX_DIMENSION]; + const byte *pix1, *pix2, *pix3, *pix4; + byte *out, *out_p; + + if ( outwidth > MAX_DIMENSION ) { + outwidth = MAX_DIMENSION; + } + if ( outheight > MAX_DIMENSION ) { + outheight = MAX_DIMENSION; + } + + out = (byte *)R_StaticAlloc( outwidth * outheight * 4 ); + out_p = out; + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for ( i=0 ; i>16); + frac += fracstep; + } + frac = 3*(fracstep>>2); + for ( i=0 ; i>16); + frac += fracstep; + } + + for (i=0 ; i> 1; + for (j=0 ; j>2; + out_p[j*4+1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + out_p[j*4+2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + out_p[j*4+3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } + + return out; +} + +/* +================ +R_Dropsample + +Used to resample images in a more general than quartering fashion. +Normal maps and such should not be bilerped. +================ +*/ +byte *R_Dropsample( const byte *in, int inwidth, int inheight, + int outwidth, int outheight ) { + int i, j, k; + const byte *inrow; + const byte *pix1; + byte *out, *out_p; + + out = (byte *)R_StaticAlloc( outwidth * outheight * 4 ); + out_p = out; + + for (i=0 ; iFatalError( "R_MipMapWithAlphaMin called with size %i,%i", width, height ); + } + + // convert the incoming texture to centered floating point + c = width * height; + fbuf = (float *)_alloca( c * 4 * sizeof( *fbuf ) ); + in_p = in; + fbuf_p = fbuf; + for ( i = 0 ; i < c ; i++, in_p+=4, fbuf_p += 4 ) { + fbuf_p[0] = ( in_p[0] / 255.0 ) * 2.0 - 1.0; // convert to a normal + fbuf_p[1] = ( in_p[1] / 255.0 ) * 2.0 - 1.0; + fbuf_p[2] = ( in_p[2] / 255.0 ) * 2.0 - 1.0; + fbuf_p[3] = ( in_p[3] / 255.0 ); // filtered divegence / specularity + } + + row = width * 4; + + newWidth = width >> 1; + newHeight = height >> 1; + if ( !newWidth ) { + newWidth = 1; + } + if ( !newHeight ) { + newHeight = 1; + } + out = (byte *)R_StaticAlloc( newWidth * newHeight * 4 ); + out_p = out; + + in_p = in; + + for ( i=0 ; iFatalError( "R_MipMap called with size %i,%i", width, height ); + } + + border[0] = in[0]; + border[1] = in[1]; + border[2] = in[2]; + border[3] = in[3]; + + row = width * 4; + + newWidth = width >> 1; + newHeight = height >> 1; + if ( !newWidth ) { + newWidth = 1; + } + if ( !newHeight ) { + newHeight = 1; + } + out = (byte *)R_StaticAlloc( newWidth * newHeight * 4 ); + out_p = out; + + in_p = in; + + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + if ( preserveBorder ) { + for (i=0 ; i>1; + out_p[1] = ( in_p[1] + in_p[5] )>>1; + out_p[2] = ( in_p[2] + in_p[6] )>>1; + out_p[3] = ( in_p[3] + in_p[7] )>>1; + } + } + return out; + } + + for (i=0 ; i>2; + out_p[1] = (in_p[1] + in_p[5] + in_p[row+1] + in_p[row+5])>>2; + out_p[2] = (in_p[2] + in_p[6] + in_p[row+2] + in_p[row+6])>>2; + out_p[3] = (in_p[3] + in_p[7] + in_p[row+3] + in_p[row+7])>>2; + } + } + + // copy the old border texel back around if desired + if ( preserveBorder ) { + R_SetBorderTexels( out, width, height, border ); + } + + return out; +} + +/* +================ +R_MipMap3D + +Returns a new copy of the texture, eigthed in size and filtered. + +If a texture is intended to be used in GL_CLAMP or GL_CLAMP_TO_EDGE mode with +a completely transparent border, we must prevent any blurring into the outer +ring of texels by filling it with the border from the previous level. This +will result in a slight shrinking of the texture as it mips, but better than +smeared clamps... +================ +*/ +byte *R_MipMap3D( const byte *in, int width, int height, int depth, bool preserveBorder ) { + int i, j, k; + const byte *in_p; + byte *out, *out_p; + int row, plane; + byte border[4]; + int newWidth, newHeight, newDepth; + + if ( depth == 1 ) { + return R_MipMap( in, width, height, preserveBorder ); + } + + // assume symetric for now + if ( width < 2 || height < 2 || depth < 2 ) { + common->FatalError( "R_MipMap3D called with size %i,%i,%i", width, height, depth ); + } + + border[0] = in[0]; + border[1] = in[1]; + border[2] = in[2]; + border[3] = in[3]; + + row = width * 4; + plane = row * height; + + newWidth = width >> 1; + newHeight = height >> 1; + newDepth = depth >> 1; + + out = (byte *)R_StaticAlloc( newWidth * newHeight * newDepth * 4 ); + out_p = out; + + in_p = in; + + width >>= 1; + height >>= 1; + depth >>= 1; + + for (k=0 ; k>3; + out_p[1] = (in_p[1] + in_p[5] + in_p[row+1] + in_p[row+5] + + in_p[plane+1] + in_p[plane+5] + in_p[plane+row+1] + in_p[plane+row+5] + )>>3; + out_p[2] = (in_p[2] + in_p[6] + in_p[row+2] + in_p[row+6] + + in_p[plane+2] + in_p[plane+6] + in_p[plane+row+2] + in_p[plane+row+6] + )>>3; + out_p[3] = (in_p[3] + in_p[7] + in_p[row+3] + in_p[row+7] + + in_p[plane+3] + in_p[plane+6] + in_p[plane+row+3] + in_p[plane+row+6] + )>>3; + } + } + } + + // copy the old border texel back around if desired + if ( preserveBorder ) { + R_SetBorderTexels3D( out, width, height, depth, border ); + } + + return out; +} + + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +void R_BlendOverTexture( byte *data, int pixelCount, const byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + + +/* +================== +R_HorizontalFlip + +Flip the image in place +================== +*/ +void R_HorizontalFlip( byte *data, int width, int height ) { + int i, j; + int temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width / 2 ; j++ ) { + temp = *( (int *)data + i * width + j ); + *( (int *)data + i * width + j ) = *( (int *)data + i * width + width - 1 - j ); + *( (int *)data + i * width + width - 1 - j ) = temp; + } + } +} + +void R_VerticalFlip( byte *data, int width, int height ) { + int i, j; + int temp; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height / 2 ; j++ ) { + temp = *( (int *)data + j * width + i ); + *( (int *)data + j * width + i ) = *( (int *)data + ( height - 1 - j ) * width + i ); + *( (int *)data + ( height - 1 - j ) * width + i ) = temp; + } + } +} + +void R_RotatePic( byte *data, int width ) { + int i, j; + int *temp; + + temp = (int *)R_StaticAlloc( width * width * 4 ); + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < width ; j++ ) { + *( temp + i * width + j ) = *( (int *)data + j * width + i ); + } + } + + SIMDProcessor->Memcpy( data, temp, width * width * 4 ); + + R_StaticFree( temp ); +} diff --git a/src/renderer/image_program.cpp b/src/renderer/image_program.cpp new file mode 100644 index 0000000..32377ce --- /dev/null +++ b/src/renderer/image_program.cpp @@ -0,0 +1,642 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + +all uncompressed +uncompressed normal maps + +downsample images + +16 meg Dynamic cache + +Anisotropic texturing + +Trilinear on all +Trilinear on normal maps, bilinear on others +Bilinear on all + + +Manager + +->List +->Print +->Reload( bool force ) + +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +// tr_imageprogram.c + +#include "tr_local.h" + +ID_TIME_T ProgramImagesTimestamp; +bool gWriteProgramFlag; + +/* + +Anywhere that an image name is used (diffusemaps, bumpmaps, specularmaps, lights, etc), +an imageProgram can be specified. + +This allows load time operations, like heightmap-to-normalmap conversion and image +composition, to be automatically handled in a way that supports timestamped reloads. + +*/ + +/* +================= +R_HeightmapToNormalMap + +it is not possible to convert a heightmap into a normal map +properly without knowing the texture coordinate stretching. +We can assume constant and equal ST vectors for walls, but not for characters. +================= +*/ +static void R_HeightmapToNormalMap( byte *data, int width, int height, float scale ) { + int i, j; + byte *depth; + + scale = scale / 256; + + // copy and convert to grey scale + j = width * height; + depth = (byte *)R_StaticAlloc( j ); + for ( i = 0 ; i < j ; i++ ) { + depth[i] = ( data[i*4] + data[i*4+1] + data[i*4+2] ) / 3; + } + + idVec3 dir, dir2; + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width ; j++ ) { + int d1, d2, d3, d4; + int a1, a2, a3, a4; + + // FIXME: look at five points? + + // look at three points to estimate the gradient + a1 = d1 = depth[ ( i * width + j ) ]; + a2 = d2 = depth[ ( i * width + ( ( j + 1 ) & ( width - 1 ) ) ) ]; + a3 = d3 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + j ) ]; + a4 = d4 = depth[ ( ( ( i + 1 ) & ( height - 1 ) ) * width + ( ( j + 1 ) & ( width - 1 ) ) ) ]; + + d2 -= d1; + d3 -= d1; + + dir[0] = -d2 * scale; + dir[1] = -d3 * scale; + dir[2] = 1; + dir.NormalizeFast(); + + a1 -= a3; + a4 -= a3; + + dir2[0] = -a4 * scale; + dir2[1] = a1 * scale; + dir2[2] = 1; + dir2.NormalizeFast(); + + dir += dir2; + dir.NormalizeFast(); + + a1 = ( i * width + j ) * 4; + data[ a1 + 0 ] = (byte)(dir[0] * 127 + 128); + data[ a1 + 1 ] = (byte)(dir[1] * 127 + 128); + data[ a1 + 2 ] = (byte)(dir[2] * 127 + 128); + data[ a1 + 3 ] = 255; + } + } + + + R_StaticFree( depth ); +} + + +/* +================= +R_ImageScale +================= +*/ +static void R_ImageScale( byte *data, int width, int height, float scale[4] ) { + int i, j; + int c; + + c = width * height * 4; + + for ( i = 0 ; i < c ; i++ ) { + j = (byte)(data[i] * scale[i&3]); + if ( j < 0 ) { + j = 0; + } else if ( j > 255 ) { + j = 255; + } + data[i] = j; + } +} + +/* +================= +R_InvertAlpha +================= +*/ +static void R_InvertAlpha( byte *data, int width, int height ) { + int i; + int c; + + c = width * height* 4; + + for ( i = 0 ; i < c ; i+=4 ) { + data[i+3] = 255 - data[i+3]; + } +} + +/* +================= +R_InvertColor +================= +*/ +static void R_InvertColor( byte *data, int width, int height ) { + int i; + int c; + + c = width * height* 4; + + for ( i = 0 ; i < c ; i+=4 ) { + data[i+0] = 255 - data[i+0]; + data[i+1] = 255 - data[i+1]; + data[i+2] = 255 - data[i+2]; + } +} + + +/* +=================== +R_AddNormalMaps + +=================== +*/ +static void R_AddNormalMaps( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) { + int i, j; + byte *newMap; + + // resample pic2 to the same size as pic1 + if ( width2 != width1 || height2 != height1 ) { + newMap = R_Dropsample( data2, width2, height2, width1, height1 ); + data2 = newMap; + } else { + newMap = NULL; + } + + // add the normal change from the second and renormalize + for ( i = 0 ; i < height1 ; i++ ) { + for ( j = 0 ; j < width1 ; j++ ) { + byte *d1, *d2; + idVec3 n; + float len; + + d1 = data1 + ( i * width1 + j ) * 4; + d2 = data2 + ( i * width1 + j ) * 4; + + n[0] = ( d1[0] - 128 ) / 127.0; + n[1] = ( d1[1] - 128 ) / 127.0; + n[2] = ( d1[2] - 128 ) / 127.0; + + // There are some normal maps that blend to 0,0,0 at the edges + // this screws up compression, so we try to correct that here by instead fading it to 0,0,1 + len = n.LengthFast(); + if ( len < 1.0f ) { + n[2] = idMath::Sqrt(1.0 - (n[0]*n[0]) - (n[1]*n[1])); + } + + n[0] += ( d2[0] - 128 ) / 127.0; + n[1] += ( d2[1] - 128 ) / 127.0; + n.Normalize(); + + d1[0] = (byte)(n[0] * 127 + 128); + d1[1] = (byte)(n[1] * 127 + 128); + d1[2] = (byte)(n[2] * 127 + 128); + d1[3] = 255; + } + } + + if ( newMap ) { + R_StaticFree( newMap ); + } +} + +/* +================ +R_SmoothNormalMap +================ +*/ +static void R_SmoothNormalMap( byte *data, int width, int height ) { + byte *orig; + int i, j, k, l; + idVec3 normal; + byte *out; + static float factors[3][3] = { + { 1, 1, 1 }, + { 1, 1, 1 }, + { 1, 1, 1 } + }; + + orig = (byte *)R_StaticAlloc( width * height * 4 ); + memcpy( orig, data, width * height * 4 ); + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + normal = vec3_origin; + for ( k = -1 ; k < 2 ; k++ ) { + for ( l = -1 ; l < 2 ; l++ ) { + byte *in; + + in = orig + ( ((j+l)&(height-1))*width + ((i+k)&(width-1)) ) * 4; + + // ignore 000 and -1 -1 -1 + if ( in[0] == 0 && in[1] == 0 && in[2] == 0 ) { + continue; + } + if ( in[0] == 128 && in[1] == 128 && in[2] == 128 ) { + continue; + } + + normal[0] += factors[k+1][l+1] * ( in[0] - 128 ); + normal[1] += factors[k+1][l+1] * ( in[1] - 128 ); + normal[2] += factors[k+1][l+1] * ( in[2] - 128 ); + } + } + normal.Normalize(); + out = data + ( j * width + i ) * 4; + out[0] = (byte)(128 + 127 * normal[0]); + out[1] = (byte)(128 + 127 * normal[1]); + out[2] = (byte)(128 + 127 * normal[2]); + } + } + + R_StaticFree( orig ); +} + + +/* +=================== +R_ImageAdd + +=================== +*/ +static void R_ImageAdd( byte *data1, int width1, int height1, byte *data2, int width2, int height2 ) { + int i, j; + int c; + byte *newMap; + + // resample pic2 to the same size as pic1 + if ( width2 != width1 || height2 != height1 ) { + newMap = R_Dropsample( data2, width2, height2, width1, height1 ); + data2 = newMap; + } else { + newMap = NULL; + } + + + c = width1 * height1 * 4; + + for ( i = 0 ; i < c ; i++ ) { + j = data1[i] + data2[i]; + if ( j > 255 ) { + j = 255; + } + data1[i] = j; + } + + if ( newMap ) { + R_StaticFree( newMap ); + } +} + + +// we build a canonical token form of the image program here +static char parseBuffer[MAX_IMAGE_NAME]; + +/* +=================== +AppendToken +=================== +*/ +static void AppendToken( idToken &token ) { + // add a leading space if not at the beginning + if ( parseBuffer[0] ) { + idStr::Append( parseBuffer, MAX_IMAGE_NAME, " " ); + } + idStr::Append( parseBuffer, MAX_IMAGE_NAME, token.c_str() ); +} + +/* +=================== +MatchAndAppendToken +=================== +*/ +static void MatchAndAppendToken( idLexer &src, const char *match ) { + if ( !src.ExpectTokenString( match ) ) { + return; + } + // a matched token won't need a leading space + idStr::Append( parseBuffer, MAX_IMAGE_NAME, match ); +} + +/* +=================== +R_ParseImageProgram_r + +If pic is NULL, the timestamps will be filled in, but no image will be generated +If both pic and timestamps are NULL, it will just advance past it, which can be +used to parse an image program from a text stream. +=================== +*/ +static bool R_ParseImageProgram_r( idLexer &src, byte **pic, int *width, int *height, + ID_TIME_T *timestamps, textureDepth_t *depth ) { + idToken token; + float scale; + ID_TIME_T timestamp; + + src.ReadToken( &token ); + AppendToken( token ); + + if ( !token.Icmp( "heightmap" ) ) { + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) { + return false; + } + + MatchAndAppendToken( src, "," ); + + src.ReadToken( &token ); + AppendToken( token ); + scale = token.GetFloatValue(); + + // process it + if ( pic ) { + R_HeightmapToNormalMap( *pic, *width, *height, scale ); + if ( depth ) { + *depth = TD_BUMP; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "addnormals" ) ) { + byte *pic2; + int width2, height2; + + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) { + return false; + } + + MatchAndAppendToken( src, "," ); + + if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) { + if ( pic ) { + R_StaticFree( *pic ); + *pic = NULL; + } + return false; + } + + // process it + if ( pic ) { + R_AddNormalMaps( *pic, *width, *height, pic2, width2, height2 ); + R_StaticFree( pic2 ); + if ( depth ) { + *depth = TD_BUMP; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "smoothnormals" ) ) { + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) { + return false; + } + + if ( pic ) { + R_SmoothNormalMap( *pic, *width, *height ); + if ( depth ) { + *depth = TD_BUMP; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "add" ) ) { + byte *pic2; + int width2, height2; + + MatchAndAppendToken( src, "(" ); + + if ( !R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ) ) { + return false; + } + + MatchAndAppendToken( src, "," ); + + if ( !R_ParseImageProgram_r( src, pic ? &pic2 : NULL, &width2, &height2, timestamps, depth ) ) { + if ( pic ) { + R_StaticFree( *pic ); + *pic = NULL; + } + return false; + } + + // process it + if ( pic ) { + R_ImageAdd( *pic, *width, *height, pic2, width2, height2 ); + R_StaticFree( pic2 ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "scale" ) ) { + float scale[4]; + int i; + + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ); + + for ( i = 0 ; i < 4 ; i++ ) { + MatchAndAppendToken( src, "," ); + src.ReadToken( &token ); + AppendToken( token ); + scale[i] = token.GetFloatValue(); + } + + // process it + if ( pic ) { + R_ImageScale( *pic, *width, *height, scale ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "invertAlpha" ) ) { + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ); + + // process it + if ( pic ) { + R_InvertAlpha( *pic, *width, *height ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "invertColor" ) ) { + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ); + + // process it + if ( pic ) { + R_InvertColor( *pic, *width, *height ); + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "makeIntensity" ) ) { + int i; + + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ); + + // copy red to green, blue, and alpha + if ( pic ) { + int c; + c = *width * *height * 4; + for ( i = 0 ; i < c ; i+=4 ) { + (*pic)[i+1] = + (*pic)[i+2] = + (*pic)[i+3] = (*pic)[i]; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + if ( !token.Icmp( "makeAlpha" ) ) { + int i; + + MatchAndAppendToken( src, "(" ); + + R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ); + + // average RGB into alpha, then set RGB to white + if ( pic ) { + int c; + c = *width * *height * 4; + for ( i = 0 ; i < c ; i+=4 ) { + (*pic)[i+3] = ( (*pic)[i+0] + (*pic)[i+1] + (*pic)[i+2] ) / 3; + (*pic)[i+0] = + (*pic)[i+1] = + (*pic)[i+2] = 255; + } + } + + MatchAndAppendToken( src, ")" ); + return true; + } + + // if we are just parsing instead of loading or checking, + // don't do the R_LoadImage + if ( !timestamps && !pic ) { + return true; + } + + // load it as an image + R_LoadImage( token.c_str(), pic, width, height, ×tamp, true ); + + if ( timestamp == -1 ) { + return false; + } + + // add this to the timestamp + if ( timestamps ) { + if ( timestamp > *timestamps ) { + *timestamps = timestamp; + } + } + + return true; +} + + +/* +=================== +R_LoadImageProgram +=================== +*/ +void R_LoadImageProgram( const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamps, textureDepth_t *depth ) { + idLexer src; + + src.LoadMemory( name, strlen(name), name ); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + + parseBuffer[0] = 0; + if ( timestamps ) { + *timestamps = 0; + } + + R_ParseImageProgram_r( src, pic, width, height, timestamps, depth ); + + src.FreeSource(); +} + +/* +=================== +R_ParsePastImageProgram +=================== +*/ +const char *R_ParsePastImageProgram( idLexer &src ) { + parseBuffer[0] = 0; + R_ParseImageProgram_r( src, NULL, NULL, NULL, NULL, NULL ); + return parseBuffer; +} diff --git a/src/renderer/interaction.cpp b/src/renderer/interaction.cpp new file mode 100644 index 0000000..2f5e829 --- /dev/null +++ b/src/renderer/interaction.cpp @@ -0,0 +1,1304 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* +=========================================================================== + +idInteraction implementation + +=========================================================================== +*/ + +// FIXME: use private allocator for srfCullInfo_t + +/* +================ +R_CalcInteractionFacing + +Determines which triangles of the surface are facing towards the light origin. + +The facing array should be allocated with one extra index than +the number of surface triangles, which will be used to handle dangling +edge silhouettes. +================ +*/ +void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { + idVec3 localLightOrigin; + + if ( cullInfo.facing != NULL ) { + return; + } + + R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin ); + + int numFaces = tri->numIndexes / 3; + + if ( !tri->facePlanes || !tri->facePlanesCalculated ) { + R_DeriveFacePlanes( const_cast(tri) ); + } + + cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ) ); + + // calculate back face culling + float *planeSide = (float *) _alloca16( numFaces * sizeof( float ) ); + + // exact geometric cull against face + SIMDProcessor->Dot( planeSide, localLightOrigin, tri->facePlanes, numFaces ); + SIMDProcessor->CmpGE( cullInfo.facing, planeSide, 0.0f, numFaces ); + + cullInfo.facing[ numFaces ] = 1; // for dangling edges to reference +} + +/* +===================== +R_CalcInteractionCullBits + +We want to cull a little on the sloppy side, because the pre-clipping +of geometry to the lights in dmap will give many cases that are right +at the border we throw things out on the border, because if any one +vertex is clearly inside, the entire triangle will be accepted. +===================== +*/ +void R_CalcInteractionCullBits( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { + int i, frontBits; + + if ( cullInfo.cullBits != NULL ) { + return; + } + + frontBits = 0; + + // cull the triangle surface bounding box + for ( i = 0; i < 6; i++ ) { + + R_GlobalPlaneToLocal( ent->modelMatrix, -light->frustum[i], cullInfo.localClipPlanes[i] ); + + // get front bits for the whole surface + if ( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON ) { + frontBits |= 1<numVerts * sizeof( cullInfo.cullBits[0] ) ); + SIMDProcessor->Memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) ); + + float *planeSide = (float *) _alloca16( tri->numVerts * sizeof( float ) ); + + for ( i = 0; i < 6; i++ ) { + // if completely infront of this clipping plane + if ( frontBits & ( 1 << i ) ) { + continue; + } + SIMDProcessor->Dot( planeSide, cullInfo.localClipPlanes[i], tri->verts, tri->numVerts ); + SIMDProcessor->CmpLT( cullInfo.cullBits, i, planeSide, LIGHT_CLIP_EPSILON, tri->numVerts ); + } +} + +/* +================ +R_FreeInteractionCullInfo +================ +*/ +void R_FreeInteractionCullInfo( srfCullInfo_t &cullInfo ) { + if ( cullInfo.facing != NULL ) { + R_StaticFree( cullInfo.facing ); + cullInfo.facing = NULL; + } + if ( cullInfo.cullBits != NULL ) { + if ( cullInfo.cullBits != LIGHT_CULL_ALL_FRONT ) { + R_StaticFree( cullInfo.cullBits ); + } + cullInfo.cullBits = NULL; + } +} + +#define MAX_CLIPPED_POINTS 20 +typedef struct { + int numVerts; + idVec3 verts[MAX_CLIPPED_POINTS]; +} clipTri_t; + +/* +============= +R_ChopWinding + +Clips a triangle from one buffer to another, setting edge flags +The returned buffer may be the same as inNum if no clipping is done +If entirely clipped away, clipTris[returned].numVerts == 0 + +I have some worries about edge flag cases when polygons are clipped +multiple times near the epsilon. +============= +*/ +static int R_ChopWinding( clipTri_t clipTris[2], int inNum, const idPlane plane ) { + clipTri_t *in, *out; + float dists[MAX_CLIPPED_POINTS]; + int sides[MAX_CLIPPED_POINTS]; + int counts[3]; + float dot; + int i, j; + idVec3 mid; + bool front; + + in = &clipTris[inNum]; + out = &clipTris[inNum^1]; + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + front = false; + for ( i = 0; i < in->numVerts; i++ ) { + dot = in->verts[i] * plane.Normal() + plane[3]; + dists[i] = dot; + if ( dot < LIGHT_CLIP_EPSILON ) { // slop onto the back + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_FRONT; + if ( dot > LIGHT_CLIP_EPSILON ) { + front = true; + } + } + counts[sides[i]]++; + } + + // if none in front, it is completely clipped away + if ( !front ) { + in->numVerts = 0; + return inNum; + } + if ( !counts[SIDE_BACK] ) { + return inNum; // inout stays the same + } + + // avoid wrapping checks by duplicating first value to end + sides[i] = sides[0]; + dists[i] = dists[0]; + in->verts[in->numVerts] = in->verts[0]; + + out->numVerts = 0; + for ( i = 0 ; i < in->numVerts ; i++ ) { + idVec3 &p1 = in->verts[i]; + + if ( sides[i] == SIDE_FRONT ) { + out->verts[out->numVerts] = p1; + out->numVerts++; + } + + if ( sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + idVec3 &p2 = in->verts[i+1]; + + dot = dists[i] / ( dists[i] - dists[i+1] ); + for ( j = 0; j < 3; j++ ) { + mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + out->verts[out->numVerts] = mid; + + out->numVerts++; + } + + return inNum ^ 1; +} + +/* +=================== +R_ClipTriangleToLight + +Returns false if nothing is left after clipping +=================== +*/ +static bool R_ClipTriangleToLight( const idVec3 &a, const idVec3 &b, const idVec3 &c, int planeBits, const idPlane frustum[6] ) { + int i; + clipTri_t pingPong[2]; + int p; + + pingPong[0].numVerts = 3; + pingPong[0].verts[0] = a; + pingPong[0].verts[1] = b; + pingPong[0].verts[2] = c; + + p = 0; + for ( i = 0 ; i < 6 ; i++ ) { + if ( planeBits & ( 1 << i ) ) { + p = R_ChopWinding( pingPong, p, frustum[i] ); + if ( pingPong[p].numVerts < 1 ) { + return false; + } + } + } + + return true; +} + +/* +==================== +R_CreateLightTris + +The resulting surface will be a subset of the original triangles, +it will never clip triangles, but it may cull on a per-triangle basis. +==================== +*/ +static srfTriangles_t *R_CreateLightTris( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + const idMaterial *shader, srfCullInfo_t &cullInfo ) { + int i; + int numIndexes; + glIndex_t *indexes; + srfTriangles_t *newTri; + int c_backfaced; + int c_distance; + idBounds bounds; + bool includeBackFaces; + int faceNum; + + tr.pc.c_createLightTris++; + c_backfaced = 0; + c_distance = 0; + + numIndexes = 0; + indexes = NULL; + + // it is debatable if non-shadowing lights should light back faces. we aren't at the moment + if ( r_lightAllBackFaces.GetBool() || light->lightShader->LightEffectsBackSides() + || shader->ReceivesLightingOnBackSides() + || ent->parms.noSelfShadow || ent->parms.noShadow ) { + includeBackFaces = true; + } else { + includeBackFaces = false; + } + + // allocate a new surface for the lit triangles + newTri = R_AllocStaticTriSurf(); + + // save a reference to the original surface + newTri->ambientSurface = const_cast(tri); + + // the light surface references the verts of the ambient surface + newTri->numVerts = tri->numVerts; + R_ReferenceStaticTriSurfVerts( newTri, tri ); + + // calculate cull information + if ( !includeBackFaces ) { + R_CalcInteractionFacing( ent, tri, light, cullInfo ); + } + R_CalcInteractionCullBits( ent, tri, light, cullInfo ); + + // if the surface is completely inside the light frustum + if ( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT ) { + + // if we aren't self shadowing, let back facing triangles get + // through so the smooth shaded bump maps light all the way around + if ( includeBackFaces ) { + + // the whole surface is lit so the light surface just references the indexes of the ambient surface + R_ReferenceStaticTriSurfIndexes( newTri, tri ); + numIndexes = tri->numIndexes; + bounds = tri->bounds; + + } else { + + // the light tris indexes are going to be a subset of the original indexes so we generally + // allocate too much memory here but we decrease the memory block when the number of indexes is known + R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); + + // back face cull the individual triangles + indexes = newTri->indexes; + const byte *facing = cullInfo.facing; + for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) { + if ( !facing[ faceNum ] ) { + c_backfaced++; + continue; + } + indexes[numIndexes+0] = tri->indexes[i+0]; + indexes[numIndexes+1] = tri->indexes[i+1]; + indexes[numIndexes+2] = tri->indexes[i+2]; + numIndexes += 3; + } + + // get bounds for the surface + SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes ); + + // decrease the size of the memory block to the size of the number of used indexes + R_ResizeStaticTriSurfIndexes( newTri, numIndexes ); + } + + } else { + + // the light tris indexes are going to be a subset of the original indexes so we generally + // allocate too much memory here but we decrease the memory block when the number of indexes is known + R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); + + // cull individual triangles + indexes = newTri->indexes; + const byte *facing = cullInfo.facing; + const byte *cullBits = cullInfo.cullBits; + for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) { + int i1, i2, i3; + + // if we aren't self shadowing, let back facing triangles get + // through so the smooth shaded bump maps light all the way around + if ( !includeBackFaces ) { + // back face cull + if ( !facing[ faceNum ] ) { + c_backfaced++; + continue; + } + } + + i1 = tri->indexes[i+0]; + i2 = tri->indexes[i+1]; + i3 = tri->indexes[i+2]; + + // fast cull outside the frustum + // if all three points are off one plane side, it definately isn't visible + if ( cullBits[i1] & cullBits[i2] & cullBits[i3] ) { + c_distance++; + continue; + } + + if ( r_usePreciseTriangleInteractions.GetBool() ) { + // do a precise clipped cull if none of the points is completely inside the frustum + // note that we do not actually use the clipped triangle, which would have Z fighting issues. + if ( cullBits[i1] && cullBits[i2] && cullBits[i3] ) { + int cull = cullBits[i1] | cullBits[i2] | cullBits[i3]; + if ( !R_ClipTriangleToLight( tri->verts[i1].xyz, tri->verts[i2].xyz, tri->verts[i3].xyz, cull, cullInfo.localClipPlanes ) ) { + continue; + } + } + } + + // add to the list + indexes[numIndexes+0] = i1; + indexes[numIndexes+1] = i2; + indexes[numIndexes+2] = i3; + numIndexes += 3; + } + + // get bounds for the surface + SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes ); + + // decrease the size of the memory block to the size of the number of used indexes + R_ResizeStaticTriSurfIndexes( newTri, numIndexes ); + } + + if ( !numIndexes ) { + R_ReallyFreeStaticTriSurf( newTri ); + return NULL; + } + + newTri->numIndexes = numIndexes; + + newTri->bounds = bounds; + + return newTri; +} + +/* +=============== +idInteraction::idInteraction +=============== +*/ +idInteraction::idInteraction( void ) { + numSurfaces = 0; + surfaces = NULL; + entityDef = NULL; + lightDef = NULL; + lightNext = NULL; + lightPrev = NULL; + entityNext = NULL; + entityPrev = NULL; + dynamicModelFrameCount = 0; + frustumState = FRUSTUM_UNINITIALIZED; + frustumAreas = NULL; +} + +/* +=============== +idInteraction::AllocAndLink +=============== +*/ +idInteraction *idInteraction::AllocAndLink( idRenderEntityLocal *edef, idRenderLightLocal *ldef ) { + if ( !edef || !ldef ) { + common->Error( "idInteraction::AllocAndLink: NULL parm" ); + } + + idRenderWorldLocal *renderWorld = edef->world; + + idInteraction *interaction = renderWorld->interactionAllocator.Alloc(); + + // link and initialize + interaction->dynamicModelFrameCount = 0; + + interaction->lightDef = ldef; + interaction->entityDef = edef; + + interaction->numSurfaces = -1; // not checked yet + interaction->surfaces = NULL; + + interaction->frustumState = idInteraction::FRUSTUM_UNINITIALIZED; + interaction->frustumAreas = NULL; + + // link at the start of the entity's list + interaction->lightNext = ldef->firstInteraction; + interaction->lightPrev = NULL; + ldef->firstInteraction = interaction; + if ( interaction->lightNext != NULL ) { + interaction->lightNext->lightPrev = interaction; + } else { + ldef->lastInteraction = interaction; + } + + // link at the start of the light's list + interaction->entityNext = edef->firstInteraction; + interaction->entityPrev = NULL; + edef->firstInteraction = interaction; + if ( interaction->entityNext != NULL ) { + interaction->entityNext->entityPrev = interaction; + } else { + edef->lastInteraction = interaction; + } + + // update the interaction table + if ( renderWorld->interactionTable ) { + int index = ldef->index * renderWorld->interactionTableWidth + edef->index; + if ( renderWorld->interactionTable[index] != NULL ) { + common->Error( "idInteraction::AllocAndLink: non NULL table entry" ); + } + renderWorld->interactionTable[ index ] = interaction; + } + + return interaction; +} + +/* +=============== +idInteraction::FreeSurfaces + +Frees the surfaces, but leaves the interaction linked in, so it +will be regenerated automatically +=============== +*/ +void idInteraction::FreeSurfaces( void ) { + if ( this->surfaces ) { + for ( int i = 0 ; i < this->numSurfaces ; i++ ) { + surfaceInteraction_t *sint = &this->surfaces[i]; + + if ( sint->lightTris ) { + if ( sint->lightTris != LIGHT_TRIS_DEFERRED ) { + R_FreeStaticTriSurf( sint->lightTris ); + } + sint->lightTris = NULL; + } + if ( sint->shadowTris ) { + // if it doesn't have an entityDef, it is part of a prelight + // model, not a generated interaction + if ( this->entityDef ) { + R_FreeStaticTriSurf( sint->shadowTris ); + sint->shadowTris = NULL; + } + } + R_FreeInteractionCullInfo( sint->cullInfo ); + } + + R_StaticFree( this->surfaces ); + this->surfaces = NULL; + } + this->numSurfaces = -1; +} + +/* +=============== +idInteraction::Unlink +=============== +*/ +void idInteraction::Unlink( void ) { + + // unlink from the entity's list + if ( this->entityPrev ) { + this->entityPrev->entityNext = this->entityNext; + } else { + this->entityDef->firstInteraction = this->entityNext; + } + if ( this->entityNext ) { + this->entityNext->entityPrev = this->entityPrev; + } else { + this->entityDef->lastInteraction = this->entityPrev; + } + this->entityNext = this->entityPrev = NULL; + + // unlink from the light's list + if ( this->lightPrev ) { + this->lightPrev->lightNext = this->lightNext; + } else { + this->lightDef->firstInteraction = this->lightNext; + } + if ( this->lightNext ) { + this->lightNext->lightPrev = this->lightPrev; + } else { + this->lightDef->lastInteraction = this->lightPrev; + } + this->lightNext = this->lightPrev = NULL; +} + +/* +=============== +idInteraction::UnlinkAndFree + +Removes links and puts it back on the free list. +=============== +*/ +void idInteraction::UnlinkAndFree( void ) { + + // clear the table pointer + idRenderWorldLocal *renderWorld = this->lightDef->world; + if ( renderWorld->interactionTable ) { + int index = this->lightDef->index * renderWorld->interactionTableWidth + this->entityDef->index; + if ( renderWorld->interactionTable[index] != this ) { + common->Error( "idInteraction::UnlinkAndFree: interactionTable wasn't set" ); + } + renderWorld->interactionTable[index] = NULL; + } + + Unlink(); + + FreeSurfaces(); + + // free the interaction area references + areaNumRef_t *area, *nextArea; + for ( area = frustumAreas; area; area = nextArea ) { + nextArea = area->next; + renderWorld->areaNumRefAllocator.Free( area ); + } + + // put it back on the free list + renderWorld->interactionAllocator.Free( this ); +} + +/* +=============== +idInteraction::MakeEmpty + +Makes the interaction empty and links it at the end of the entity's and light's interaction lists. +=============== +*/ +void idInteraction::MakeEmpty( void ) { + + // an empty interaction has no surfaces + numSurfaces = 0; + + Unlink(); + + // relink at the end of the entity's list + this->entityNext = NULL; + this->entityPrev = this->entityDef->lastInteraction; + this->entityDef->lastInteraction = this; + if ( this->entityPrev ) { + this->entityPrev->entityNext = this; + } else { + this->entityDef->firstInteraction = this; + } + + // relink at the end of the light's list + this->lightNext = NULL; + this->lightPrev = this->lightDef->lastInteraction; + this->lightDef->lastInteraction = this; + if ( this->lightPrev ) { + this->lightPrev->lightNext = this; + } else { + this->lightDef->firstInteraction = this; + } +} + +/* +=============== +idInteraction::HasShadows +=============== +*/ +ID_INLINE bool idInteraction::HasShadows( void ) const { + return ( !lightDef->parms.noShadows && !entityDef->parms.noShadow && lightDef->lightShader->LightCastsShadows() ); +} + +/* +=============== +idInteraction::MemoryUsed + +Counts up the memory used by all the surfaceInteractions, which +will be used to determine when we need to start purging old interactions. +=============== +*/ +int idInteraction::MemoryUsed( void ) { + int total = 0; + + for ( int i = 0 ; i < numSurfaces ; i++ ) { + surfaceInteraction_t *inter = &surfaces[i]; + + total += R_TriSurfMemory( inter->lightTris ); + total += R_TriSurfMemory( inter->shadowTris ); + } + + return total; +} + +/* +================== +idInteraction::CalcInteractionScissorRectangle +================== +*/ +idScreenRect idInteraction::CalcInteractionScissorRectangle( const idFrustum &viewFrustum ) { + idBounds projectionBounds; + idScreenRect portalRect; + idScreenRect scissorRect; + + if ( r_useInteractionScissors.GetInteger() == 0 ) { + return lightDef->viewLight->scissorRect; + } + + if ( r_useInteractionScissors.GetInteger() < 0 ) { + // this is the code from Cass at nvidia, it is more precise, but slower + return R_CalcIntersectionScissor( lightDef, entityDef, tr.viewDef ); + } + + // the following is Mr.E's code + + // frustum must be initialized and valid + if ( frustumState == idInteraction::FRUSTUM_UNINITIALIZED || frustumState == idInteraction::FRUSTUM_INVALID ) { + return lightDef->viewLight->scissorRect; + } + + // calculate scissors for the portals through which the interaction is visible + if ( r_useInteractionScissors.GetInteger() > 1 ) { + areaNumRef_t *area; + + if ( frustumState == idInteraction::FRUSTUM_VALID ) { + // retrieve all the areas the interaction frustum touches + for ( areaReference_t *ref = entityDef->entityRefs; ref; ref = ref->ownerNext ) { + area = entityDef->world->areaNumRefAllocator.Alloc(); + area->areaNum = ref->area->areaNum; + area->next = frustumAreas; + frustumAreas = area; + } + frustumAreas = tr.viewDef->renderWorld->FloodFrustumAreas( frustum, frustumAreas ); + frustumState = idInteraction::FRUSTUM_VALIDAREAS; + } + + portalRect.Clear(); + for ( area = frustumAreas; area; area = area->next ) { + portalRect.Union( entityDef->world->GetAreaScreenRect( area->areaNum ) ); + } + portalRect.Intersect( lightDef->viewLight->scissorRect ); + } else { + portalRect = lightDef->viewLight->scissorRect; + } + + // early out if the interaction is not visible through any portals + if ( portalRect.IsEmpty() ) { + return portalRect; + } + + // calculate bounds of the interaction frustum projected into the view frustum + if ( lightDef->parms.pointLight ) { + viewFrustum.ClippedProjectionBounds( frustum, idBox( lightDef->parms.origin, lightDef->parms.lightRadius, lightDef->parms.axis ), projectionBounds ); + } else { + viewFrustum.ClippedProjectionBounds( frustum, idBox( lightDef->frustumTris->bounds ), projectionBounds ); + } + + if ( projectionBounds.IsCleared() ) { + return portalRect; + } + + // derive a scissor rectangle from the projection bounds + scissorRect = R_ScreenRectFromViewFrustumBounds( projectionBounds ); + + // intersect with the portal crossing scissor rectangle + scissorRect.Intersect( portalRect ); + + if ( r_showInteractionScissors.GetInteger() > 0 ) { + R_ShowColoredScreenRect( scissorRect, lightDef->index ); + } + + return scissorRect; +} + +/* +=================== +idInteraction::CullInteractionByViewFrustum +=================== +*/ +bool idInteraction::CullInteractionByViewFrustum( const idFrustum &viewFrustum ) { + + if ( !r_useInteractionCulling.GetBool() ) { + return false; + } + + if ( frustumState == idInteraction::FRUSTUM_INVALID ) { + return false; + } + + if ( frustumState == idInteraction::FRUSTUM_UNINITIALIZED ) { + + frustum.FromProjection( idBox( entityDef->referenceBounds, entityDef->parms.origin, entityDef->parms.axis ), lightDef->globalLightOrigin, MAX_WORLD_SIZE ); + + if ( !frustum.IsValid() ) { + frustumState = idInteraction::FRUSTUM_INVALID; + return false; + } + + if ( lightDef->parms.pointLight ) { + frustum.ConstrainToBox( idBox( lightDef->parms.origin, lightDef->parms.lightRadius, lightDef->parms.axis ) ); + } else { + frustum.ConstrainToBox( idBox( lightDef->frustumTris->bounds ) ); + } + + frustumState = idInteraction::FRUSTUM_VALID; + } + + if ( !viewFrustum.IntersectsFrustum( frustum ) ) { + return true; + } + + if ( r_showInteractionFrustums.GetInteger() ) { + static idVec4 colors[] = { colorRed, colorGreen, colorBlue, colorYellow, colorMagenta, colorCyan, colorWhite, colorPurple }; + tr.viewDef->renderWorld->DebugFrustum( colors[lightDef->index & 7], frustum, ( r_showInteractionFrustums.GetInteger() > 1 ) ); + if ( r_showInteractionFrustums.GetInteger() > 2 ) { + tr.viewDef->renderWorld->DebugBox( colorWhite, idBox( entityDef->referenceBounds, entityDef->parms.origin, entityDef->parms.axis ) ); + } + } + + return false; +} + +/* +==================== +idInteraction::CreateInteraction + +Called when a entityDef and a lightDef are both present in a +portalArea, and might be visible. Performs cull checking before doing the expensive +computations. + +References tr.viewCount so lighting surfaces will only be created if the ambient surface is visible, +otherwise it will be marked as deferred. + +The results of this are cached and valid until the light or entity change. +==================== +*/ +void idInteraction::CreateInteraction( const idRenderModel *model ) { + const idMaterial * lightShader = lightDef->lightShader; + const idMaterial* shader; + bool interactionGenerated; + idBounds bounds; + + tr.pc.c_createInteractions++; + + bounds = model->Bounds( &entityDef->parms ); + + // if it doesn't contact the light frustum, none of the surfaces will + if ( R_CullLocalBox( bounds, entityDef->modelMatrix, 6, lightDef->frustum ) ) { + MakeEmpty(); + return; + } + + // use the turbo shadow path + shadowGen_t shadowGen = SG_DYNAMIC; + + // really large models, like outside terrain meshes, should use + // the more exactly culled static shadow path instead of the turbo shadow path. + // FIXME: this is a HACK, we should probably have a material flag. + if ( bounds[1][0] - bounds[0][0] > 3000 ) { + shadowGen = SG_STATIC; + } + + // + // create slots for each of the model's surfaces + // + numSurfaces = model->NumSurfaces(); + surfaces = (surfaceInteraction_t *)R_ClearedStaticAlloc( sizeof( *surfaces ) * numSurfaces ); + + interactionGenerated = false; + + // check each surface in the model + for ( int c = 0 ; c < model->NumSurfaces() ; c++ ) { + const modelSurface_t *surf; + srfTriangles_t *tri; + + surf = model->Surface( c ); + + tri = surf->geometry; + if ( !tri ) { + continue; + } + + // determine the shader for this surface, possibly by skinning + shader = surf->shader; + shader = R_RemapShaderBySkin( shader, entityDef->parms.customSkin, entityDef->parms.customShader ); + + if ( !shader ) { + continue; + } + + // try to cull each surface + if ( R_CullLocalBox( tri->bounds, entityDef->modelMatrix, 6, lightDef->frustum ) ) { + continue; + } + + surfaceInteraction_t *sint = &surfaces[c]; + + sint->shader = shader; + + // save the ambient tri pointer so we can reject lightTri interactions + // when the ambient surface isn't in view, and we can get shared vertex + // and shadow data from the source surface + sint->ambientTris = tri; + + // "invisible ink" lights and shaders + if ( shader->Spectrum() != lightShader->Spectrum() ) { + continue; + } + + // generate a lighted surface and add it + if ( shader->ReceivesLighting() ) { + if ( tri->ambientViewCount == tr.viewCount ) { + sint->lightTris = R_CreateLightTris( entityDef, tri, lightDef, shader, sint->cullInfo ); + } else { + // this will be calculated when sint->ambientTris is actually in view + sint->lightTris = LIGHT_TRIS_DEFERRED; + } + interactionGenerated = true; + } + + // if the interaction has shadows and this surface casts a shadow + if ( HasShadows() && shader->SurfaceCastsShadow() && tri->silEdges != NULL ) { + + // if the light has an optimized shadow volume, don't create shadows for any models that are part of the base areas + if ( lightDef->parms.prelightModel == NULL || !model->IsStaticWorldModel() || !r_useOptimizedShadows.GetBool() ) { + + // this is the only place during gameplay (outside the utilities) that R_CreateShadowVolume() is called + sint->shadowTris = R_CreateShadowVolume( entityDef, tri, lightDef, shadowGen, sint->cullInfo ); + if ( sint->shadowTris ) { + if ( shader->Coverage() != MC_OPAQUE || ( !r_skipSuppress.GetBool() && entityDef->parms.suppressSurfaceInViewID ) ) { + // if any surface is a shadow-casting perforated or translucent surface, or the + // base surface is suppressed in the view (world weapon shadows) we can't use + // the external shadow optimizations because we can see through some of the faces + sint->shadowTris->numShadowIndexesNoCaps = sint->shadowTris->numIndexes; + sint->shadowTris->numShadowIndexesNoFrontCaps = sint->shadowTris->numIndexes; + } + } + interactionGenerated = true; + } + } + + // free the cull information when it's no longer needed + if ( sint->lightTris != LIGHT_TRIS_DEFERRED ) { + R_FreeInteractionCullInfo( sint->cullInfo ); + } + } + + // if none of the surfaces generated anything, don't even bother checking? + if ( !interactionGenerated ) { + MakeEmpty(); + } +} + +/* +====================== +R_PotentiallyInsideInfiniteShadow + +If we know that we are "off to the side" of an infinite shadow volume, +we can draw it without caps in zpass mode +====================== +*/ +static bool R_PotentiallyInsideInfiniteShadow( const srfTriangles_t *occluder, + const idVec3 &localView, const idVec3 &localLight ) { + idBounds exp; + + // expand the bounds to account for the near clip plane, because the + // view could be mathematically outside, but if the near clip plane + // chops a volume edge, the zpass rendering would fail. + float znear = r_znear.GetFloat(); + if ( tr.viewDef->renderView.cramZNear ) { + znear *= 0.25f; + } + float stretch = znear * 2; // in theory, should vary with FOV + exp[0][0] = occluder->bounds[0][0] - stretch; + exp[0][1] = occluder->bounds[0][1] - stretch; + exp[0][2] = occluder->bounds[0][2] - stretch; + exp[1][0] = occluder->bounds[1][0] + stretch; + exp[1][1] = occluder->bounds[1][1] + stretch; + exp[1][2] = occluder->bounds[1][2] + stretch; + + if ( exp.ContainsPoint( localView ) ) { + return true; + } + if ( exp.ContainsPoint( localLight ) ) { + return true; + } + + // if the ray from localLight to localView intersects a face of the + // expanded bounds, we will be inside the projection + + idVec3 ray = localView - localLight; + + // intersect the ray from the view to the light with the near side of the bounds + for ( int axis = 0; axis < 3; axis++ ) { + float d, frac; + idVec3 hit; + + if ( localLight[axis] < exp[0][axis] ) { + if ( localView[axis] < exp[0][axis] ) { + continue; + } + d = exp[0][axis] - localLight[axis]; + frac = d / ray[axis]; + hit = localLight + frac * ray; + hit[axis] = exp[0][axis]; + } else if ( localLight[axis] > exp[1][axis] ) { + if ( localView[axis] > exp[1][axis] ) { + continue; + } + d = exp[1][axis] - localLight[axis]; + frac = d / ray[axis]; + hit = localLight + frac * ray; + hit[axis] = exp[1][axis]; + } else { + continue; + } + + if ( exp.ContainsPoint( hit ) ) { + return true; + } + } + + // the view is definitely not inside the projected shadow + return false; +} + +/* +================== +idInteraction::AddActiveInteraction + +Create and add any necessary light and shadow triangles + +If the model doesn't have any surfaces that need interactions +with this type of light, it can be skipped, but we might need to +instantiate the dynamic model to find out +================== +*/ +void idInteraction::AddActiveInteraction( void ) { + viewLight_t * vLight; + viewEntity_t * vEntity; + idScreenRect shadowScissor; + idScreenRect lightScissor; + idVec3 localLightOrigin; + idVec3 localViewOrigin; + + vLight = lightDef->viewLight; + vEntity = entityDef->viewEntity; + + // do not waste time culling the interaction frustum if there will be no shadows + if ( !HasShadows() ) { + + // use the entity scissor rectangle + shadowScissor = vEntity->scissorRect; + + // culling does not seem to be worth it for static world models + } else if ( entityDef->parms.hModel->IsStaticWorldModel() ) { + + // use the light scissor rectangle + shadowScissor = vLight->scissorRect; + + } else { + + // try to cull the interaction + // this will also cull the case where the light origin is inside the + // view frustum and the entity bounds are outside the view frustum + if ( CullInteractionByViewFrustum( tr.viewDef->viewFrustum ) ) { + return; + } + + // calculate the shadow scissor rectangle + shadowScissor = CalcInteractionScissorRectangle( tr.viewDef->viewFrustum ); + } + + // get out before making the dynamic model if the shadow scissor rectangle is empty + if ( shadowScissor.IsEmpty() ) { + return; + } + + // We will need the dynamic surface created to make interactions, even if the + // model itself wasn't visible. This just returns a cached value after it + // has been generated once in the view. + idRenderModel *model = R_EntityDefDynamicModel( entityDef ); + if ( model == NULL || model->NumSurfaces() <= 0 ) { + return; + } + + // the dynamic model may have changed since we built the surface list + if ( !IsDeferred() && entityDef->dynamicModelFrameCount != dynamicModelFrameCount ) { + FreeSurfaces(); + } + dynamicModelFrameCount = entityDef->dynamicModelFrameCount; + + // actually create the interaction if needed, building light and shadow surfaces as needed + if ( IsDeferred() ) { + CreateInteraction( model ); + } + + R_GlobalPointToLocal( vEntity->modelMatrix, lightDef->globalLightOrigin, localLightOrigin ); + R_GlobalPointToLocal( vEntity->modelMatrix, tr.viewDef->renderView.vieworg, localViewOrigin ); + + // calculate the scissor as the intersection of the light and model rects + // this is used for light triangles, but not for shadow triangles + lightScissor = vLight->scissorRect; + lightScissor.Intersect( vEntity->scissorRect ); + + bool lightScissorsEmpty = lightScissor.IsEmpty(); + + // for each surface of this entity / light interaction + for ( int i = 0; i < numSurfaces; i++ ) { + surfaceInteraction_t *sint = &surfaces[i]; + + // see if the base surface is visible, we may still need to add shadows even if empty + if ( !lightScissorsEmpty && sint->ambientTris && sint->ambientTris->ambientViewCount == tr.viewCount ) { + + // make sure we have created this interaction, which may have been deferred + // on a previous use that only needed the shadow + if ( sint->lightTris == LIGHT_TRIS_DEFERRED ) { + sint->lightTris = R_CreateLightTris( vEntity->entityDef, sint->ambientTris, vLight->lightDef, sint->shader, sint->cullInfo ); + R_FreeInteractionCullInfo( sint->cullInfo ); + } + + srfTriangles_t *lightTris = sint->lightTris; + + if ( lightTris ) { + + // try to cull before adding + // FIXME: this may not be worthwhile. We have already done culling on the ambient, + // but individual surfaces may still be cropped somewhat more + if ( !R_CullLocalBox( lightTris->bounds, vEntity->modelMatrix, 5, tr.viewDef->frustum ) ) { + + // make sure the original surface has its ambient cache created + srfTriangles_t *tri = sint->ambientTris; + if ( !tri->ambientCache ) { + if ( !R_CreateAmbientCache( tri, sint->shader->ReceivesLighting() ) ) { + // skip if we were out of vertex memory + continue; + } + } + + // reference the original surface's ambient cache + lightTris->ambientCache = tri->ambientCache; + + // touch the ambient surface so it won't get purged + vertexCache.Touch( lightTris->ambientCache ); + + // regenerate the lighting cache (for non-vertex program cards) if it has been purged + if ( !lightTris->lightingCache ) { + if ( !R_CreateLightingCache( entityDef, lightDef, lightTris ) ) { + // skip if we are out of vertex memory + continue; + } + } + // touch the light surface so it won't get purged + // (vertex program cards won't have a light cache at all) + if ( lightTris->lightingCache ) { + vertexCache.Touch( lightTris->lightingCache ); + } + + if ( !lightTris->indexCache && r_useIndexBuffers.GetBool() ) { + vertexCache.Alloc( lightTris->indexes, lightTris->numIndexes * sizeof( lightTris->indexes[0] ), &lightTris->indexCache, true ); + } + if ( lightTris->indexCache ) { + vertexCache.Touch( lightTris->indexCache ); + } + + // add the surface to the light list + + const idMaterial *shader = sint->shader; + R_GlobalShaderOverride( &shader ); + + // there will only be localSurfaces if the light casts shadows and + // there are surfaces with NOSELFSHADOW + if ( sint->shader->Coverage() == MC_TRANSLUCENT ) { + R_LinkLightSurf( &vLight->translucentInteractions, lightTris, + vEntity, lightDef, shader, lightScissor, false ); + } else if ( !lightDef->parms.noShadows && sint->shader->TestMaterialFlag(MF_NOSELFSHADOW) ) { + R_LinkLightSurf( &vLight->localInteractions, lightTris, + vEntity, lightDef, shader, lightScissor, false ); + } else { + R_LinkLightSurf( &vLight->globalInteractions, lightTris, + vEntity, lightDef, shader, lightScissor, false ); + } + } + } + } + + srfTriangles_t *shadowTris = sint->shadowTris; + + // the shadows will always have to be added, unless we can tell they + // are from a surface in an unconnected area + if ( shadowTris ) { + + // check for view specific shadow suppression (player shadows, etc) + if ( !r_skipSuppress.GetBool() ) { + if ( entityDef->parms.suppressShadowInViewID && + entityDef->parms.suppressShadowInViewID == tr.viewDef->renderView.viewID ) { + continue; + } + if ( entityDef->parms.suppressShadowInLightID && + entityDef->parms.suppressShadowInLightID == lightDef->parms.lightId ) { + continue; + } + } + + // cull static shadows that have a non-empty bounds + // dynamic shadows that use the turboshadow code will not have valid + // bounds, because the perspective projection extends them to infinity + if ( r_useShadowCulling.GetBool() && !shadowTris->bounds.IsCleared() ) { + if ( R_CullLocalBox( shadowTris->bounds, vEntity->modelMatrix, 5, tr.viewDef->frustum ) ) { + continue; + } + } + + // copy the shadow vertexes to the vertex cache if they have been purged + + // if we are using shared shadowVertexes and letting a vertex program fix them up, + // get the shadowCache from the parent ambient surface + if ( !shadowTris->shadowVertexes ) { + // the data may have been purged, so get the latest from the "home position" + shadowTris->shadowCache = sint->ambientTris->shadowCache; + } + + // if we have been purged, re-upload the shadowVertexes + if ( !shadowTris->shadowCache ) { + if ( shadowTris->shadowVertexes ) { + // each interaction has unique vertexes + R_CreatePrivateShadowCache( shadowTris ); + } else { + R_CreateVertexProgramShadowCache( sint->ambientTris ); + shadowTris->shadowCache = sint->ambientTris->shadowCache; + } + // if we are out of vertex cache space, skip the interaction + if ( !shadowTris->shadowCache ) { + continue; + } + } + + // touch the shadow surface so it won't get purged + vertexCache.Touch( shadowTris->shadowCache ); + + if ( !shadowTris->indexCache && r_useIndexBuffers.GetBool() ) { + vertexCache.Alloc( shadowTris->indexes, shadowTris->numIndexes * sizeof( shadowTris->indexes[0] ), &shadowTris->indexCache, true ); + vertexCache.Touch( shadowTris->indexCache ); + } + + // see if we can avoid using the shadow volume caps + bool inside = R_PotentiallyInsideInfiniteShadow( sint->ambientTris, localViewOrigin, localLightOrigin ); + + if ( sint->shader->TestMaterialFlag( MF_NOSELFSHADOW ) ) { + R_LinkLightSurf( &vLight->localShadows, + shadowTris, vEntity, lightDef, NULL, shadowScissor, inside ); + } else { + R_LinkLightSurf( &vLight->globalShadows, + shadowTris, vEntity, lightDef, NULL, shadowScissor, inside ); + } + } + } +} + +/* +=================== +R_ShowInteractionMemory_f +=================== +*/ +void R_ShowInteractionMemory_f( const idCmdArgs &args ) { + int total = 0; + int entities = 0; + int interactions = 0; + int deferredInteractions = 0; + int emptyInteractions = 0; + int lightTris = 0; + int lightTriVerts = 0; + int lightTriIndexes = 0; + int shadowTris = 0; + int shadowTriVerts = 0; + int shadowTriIndexes = 0; + + for ( int i = 0; i < tr.primaryWorld->entityDefs.Num(); i++ ) { + idRenderEntityLocal *def = tr.primaryWorld->entityDefs[i]; + if ( !def ) { + continue; + } + if ( def->firstInteraction == NULL ) { + continue; + } + entities++; + + for ( idInteraction *inter = def->firstInteraction; inter != NULL; inter = inter->entityNext ) { + interactions++; + total += inter->MemoryUsed(); + + if ( inter->IsDeferred() ) { + deferredInteractions++; + continue; + } + if ( inter->IsEmpty() ) { + emptyInteractions++; + continue; + } + + for ( int j = 0; j < inter->numSurfaces; j++ ) { + surfaceInteraction_t *srf = &inter->surfaces[j]; + + if ( srf->lightTris && srf->lightTris != LIGHT_TRIS_DEFERRED ) { + lightTris++; + lightTriVerts += srf->lightTris->numVerts; + lightTriIndexes += srf->lightTris->numIndexes; + } + if ( srf->shadowTris ) { + shadowTris++; + shadowTriVerts += srf->shadowTris->numVerts; + shadowTriIndexes += srf->shadowTris->numIndexes; + } + } + } + } + + common->Printf( "%i entities with %i total interactions totalling %ik\n", entities, interactions, total / 1024 ); + common->Printf( "%i deferred interactions, %i empty interactions\n", deferredInteractions, emptyInteractions ); + common->Printf( "%5i indexes %5i verts in %5i light tris\n", lightTriIndexes, lightTriVerts, lightTris ); + common->Printf( "%5i indexes %5i verts in %5i shadow tris\n", shadowTriIndexes, shadowTriVerts, shadowTris ); +} diff --git a/src/renderer/jpeg-6/jcapimin.c b/src/renderer/jpeg-6/jcapimin.c new file mode 100644 index 0000000..8edc932 --- /dev/null +++ b/src/renderer/jpeg-6/jcapimin.c @@ -0,0 +1,252 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-compression case or the transcoding-only + * case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jcapistd.c. But also see jcparam.c for + * parameter-setup helper routines, jcomapi.c for routines shared by + * compression and decompression, and jctrans.c for the transcoding case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG compression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_compress (j_compress_ptr cinfo) +{ + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO(cinfo, SIZEOF(struct jpeg_compress_struct)); + cinfo->err = err; + } + cinfo->is_decompressor = FALSE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->dest = NULL; + + cinfo->comp_info = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + cinfo->input_gamma = 1.0; /* in case application forgets */ + + /* OK, I'm ready */ + cinfo->global_state = CSTATE_START; +} + + +/* + * Destruction of a JPEG compression object + */ + +GLOBAL void +jpeg_destroy_compress (j_compress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG compression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_compress (j_compress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Forcibly suppress or un-suppress all quantization and Huffman tables. + * Marks all currently defined tables as already written (if suppress) + * or not written (if !suppress). This will control whether they get emitted + * by a subsequent jpeg_start_compress call. + * + * This routine is exported for use by applications that want to produce + * abbreviated JPEG datastreams. It logically belongs in jcparam.c, but + * since it is called by jpeg_start_compress, we put it here --- otherwise + * jcparam.o would be linked whether the application used it or not. + */ + +GLOBAL void +jpeg_suppress_tables (j_compress_ptr cinfo, boolean suppress) +{ + int i; + JQUANT_TBL * qtbl; + JHUFF_TBL * htbl; + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if ((qtbl = cinfo->quant_tbl_ptrs[i]) != NULL) + qtbl->sent_table = suppress; + } + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if ((htbl = cinfo->dc_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + if ((htbl = cinfo->ac_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + } +} + + +/* + * Finish JPEG compression. + * + * If a multipass operating mode was selected, this may do a great deal of + * work including most of the actual output. + */ + +GLOBAL void +jpeg_finish_compress (j_compress_ptr cinfo) +{ + JDIMENSION iMCU_row; + + if (cinfo->global_state == CSTATE_SCANNING || + cinfo->global_state == CSTATE_RAW_OK) { + /* Terminate first pass */ + if (cinfo->next_scanline < cinfo->image_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_pass) (cinfo); + } else if (cinfo->global_state != CSTATE_WRCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any remaining passes */ + while (! cinfo->master->is_last_pass) { + (*cinfo->master->prepare_for_pass) (cinfo); + for (iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++) { + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) iMCU_row; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* We bypass the main controller and invoke coef controller directly; + * all work is being done from the coefficient buffer. + */ + if (! (*cinfo->coef->compress_data) (cinfo, (JSAMPIMAGE) NULL)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } + (*cinfo->master->finish_pass) (cinfo); + } + /* Write EOI, do final cleanup */ + (*cinfo->marker->write_file_trailer) (cinfo); + (*cinfo->dest->term_destination) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); +} + + +/* + * Write a special marker. + * This is only recommended for writing COM or APPn markers. + * Must be called after jpeg_start_compress() and before + * first call to jpeg_write_scanlines() or jpeg_write_raw_data(). + */ + +GLOBAL void +jpeg_write_marker (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen) +{ + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_any_marker) (cinfo, marker, dataptr, datalen); +} + + +/* + * Alternate compression function: just write an abbreviated table file. + * Before calling this, all parameters and a data destination must be set up. + * + * To produce a pair of files containing abbreviated tables and abbreviated + * image data, one would proceed as follows: + * + * initialize JPEG object + * set JPEG parameters + * set destination to table file + * jpeg_write_tables(cinfo); + * set destination to image file + * jpeg_start_compress(cinfo, FALSE); + * write data... + * jpeg_finish_compress(cinfo); + * + * jpeg_write_tables has the side effect of marking all tables written + * (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress + * will not re-emit the tables unless it is passed write_all_tables=TRUE. + */ + +GLOBAL void +jpeg_write_tables (j_compress_ptr cinfo) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Initialize the marker writer ... bit of a crock to do it here. */ + jinit_marker_writer(cinfo); + /* Write them tables! */ + (*cinfo->marker->write_tables_only) (cinfo); + /* And clean up. */ + (*cinfo->dest->term_destination) (cinfo); + /* We can use jpeg_abort to release memory. */ + jpeg_abort((j_common_ptr) cinfo); +} diff --git a/src/renderer/jpeg-6/jcapistd.c b/src/renderer/jpeg-6/jcapistd.c new file mode 100644 index 0000000..eb68862 --- /dev/null +++ b/src/renderer/jpeg-6/jcapistd.c @@ -0,0 +1,185 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcapistd.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-compression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_compress, it will end up linking in the entire compressor. + * We thus must separate this file from jcapimin.c to avoid linking the + * whole compression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL void +jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (write_all_tables) + jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */ + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + jinit_compress_master(cinfo); + /* Set up for the first pass */ + (*cinfo->master->prepare_for_pass) (cinfo); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL JDIMENSION +jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) +{ + JDIMENSION row_ctr, rows_left; + + if (cinfo->global_state != CSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if (num_lines > rows_left) + num_lines = rows_left; + + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to write raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL JDIMENSION +jpeg_write_raw_data (j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != CSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_raw_data. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_raw_data. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Verify that at least one iMCU row has been passed. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE; + if (num_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Directly compress the row. */ + if (! (*cinfo->coef->compress_data) (cinfo, data)) { + /* If compressor did not consume the whole row, suspend processing. */ + return 0; + } + + /* OK, we processed one iMCU row. */ + cinfo->next_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} diff --git a/src/renderer/jpeg-6/jccoefct.c b/src/renderer/jpeg-6/jccoefct.c new file mode 100644 index 0000000..83a2c0e --- /dev/null +++ b/src/renderer/jpeg-6/jccoefct.c @@ -0,0 +1,472 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jccoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for compression. + * This controller is the top level of the JPEG compressor proper. + * The coefficient buffer lies between forward-DCT and entropy encoding steps. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* We use a full-image coefficient buffer when doing Huffman optimization, + * and also for writing multiple-scan JPEG files. In all cases, the DCT + * step is run during the first pass, and subsequent passes need only read + * the buffered coefficients. + */ +#ifdef ENTROPY_OPT_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#else +#ifdef C_MULTISCAN_FILES_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#endif +#endif + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* For single-pass compression, it's sufficient to buffer just one MCU + * (although this may prove a bit slow in practice). We allocate a + * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each + * MCU constructed and sent. (On 80x86, the workspace is FAR even though + * it's not really very big; this is to keep the module interfaces unchanged + * when a large coefficient buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays. + */ + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +/* Forward declarations */ +METHODDEF boolean compress_data + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#ifdef FULL_COEF_BUFFER_SUPPORTED +METHODDEF boolean compress_first_pass + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +METHODDEF boolean compress_output + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#endif + + +LOCAL void +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (coef->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_data; + break; +#ifdef FULL_COEF_BUFFER_SUPPORTED + case JBUF_SAVE_AND_PASS: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_first_pass; + break; + case JBUF_CRANK_DEST: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_output; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data in the single-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF boolean +compress_data (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, bi, ci, yindex, yoffset, blockcnt; + JDIMENSION ypos, xpos; + jpeg_component_info *compptr; + + /* Loop to write as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Determine where data comes from in input_buf and do the DCT thing. + * Each call on forward_DCT processes a horizontal row of DCT blocks + * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks + * sequentially. Dummy blocks at the right or bottom edge are filled in + * specially. The data in them does not matter for image reconstruction, + * so we fill them with values that will encode to the smallest amount of + * data, viz: all zeroes in the AC entries, DC entries equal to previous + * block's DC value. (Thanks to Thomas Kinsman for this idea.) + */ + blkn = 0; + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + xpos = MCU_col_num * compptr->MCU_sample_width; + ypos = yoffset * DCTSIZE; /* ypos == (yoffset+yindex) * DCTSIZE */ + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[ci], coef->MCU_buffer[blkn], + ypos, xpos, (JDIMENSION) blockcnt); + if (blockcnt < compptr->MCU_width) { + /* Create some dummy blocks at the right edge of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn + blockcnt], + (compptr->MCU_width - blockcnt) * SIZEOF(JBLOCK)); + for (bi = blockcnt; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn+bi-1][0][0]; + } + } + } else { + /* Create a row of dummy blocks at the bottom of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn], + compptr->MCU_width * SIZEOF(JBLOCK)); + for (bi = 0; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn-1][0][0]; + } + } + blkn += compptr->MCU_width; + ypos += DCTSIZE; + } + } + /* Try to write the MCU. In event of a suspension failure, we will + * re-DCT the MCU on restart (a bit inefficient, could be fixed...) + */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +#ifdef FULL_COEF_BUFFER_SUPPORTED + +/* + * Process some data in the first pass of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * This amount of data is read from the source buffer, DCT'd and quantized, + * and saved into the virtual arrays. We also generate suitable dummy blocks + * as needed at the right and lower edges. (The dummy blocks are constructed + * in the virtual arrays, which have been padded appropriately.) This makes + * it possible for subsequent passes not to worry about real vs. dummy blocks. + * + * We must also emit the data to the entropy encoder. This is conveniently + * done by calling compress_output() after we've loaded the current strip + * of the virtual arrays. + * + * NB: input_buf contains a plane for each component in image. All + * components are DCT'd and loaded into the virtual arrays in this pass. + * However, it may be that only a subset of the components are emitted to + * the entropy encoder during this first pass; be careful about looking + * at the scan-dependent variables (MCU dimensions, etc). + */ + +METHODDEF boolean +compress_first_pass (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION blocks_across, MCUs_across, MCUindex; + int bi, ci, h_samp_factor, block_row, block_rows, ndummy; + JCOEF lastDC; + jpeg_component_info *compptr; + JBLOCKARRAY buffer; + JBLOCKROW thisblockrow, lastblockrow; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (coef->iMCU_row_num < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here, since may not be set! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + blocks_across = compptr->width_in_blocks; + h_samp_factor = compptr->h_samp_factor; + /* Count number of dummy blocks to be added at the right margin. */ + ndummy = (int) (blocks_across % h_samp_factor); + if (ndummy > 0) + ndummy = h_samp_factor - ndummy; + /* Perform DCT for all non-dummy blocks in this iMCU row. Each call + * on forward_DCT processes a complete horizontal row of DCT blocks. + */ + for (block_row = 0; block_row < block_rows; block_row++) { + thisblockrow = buffer[block_row]; + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[ci], thisblockrow, + (JDIMENSION) (block_row * DCTSIZE), + (JDIMENSION) 0, blocks_across); + if (ndummy > 0) { + /* Create dummy blocks at the right edge of the image. */ + thisblockrow += blocks_across; /* => first dummy block */ + jzero_far((void FAR *) thisblockrow, ndummy * SIZEOF(JBLOCK)); + lastDC = thisblockrow[-1][0]; + for (bi = 0; bi < ndummy; bi++) { + thisblockrow[bi][0] = lastDC; + } + } + } + /* If at end of image, create dummy block rows as needed. + * The tricky part here is that within each MCU, we want the DC values + * of the dummy blocks to match the last real block's DC value. + * This squeezes a few more bytes out of the resulting file... + */ + if (coef->iMCU_row_num == last_iMCU_row) { + blocks_across += ndummy; /* include lower right corner */ + MCUs_across = blocks_across / h_samp_factor; + for (block_row = block_rows; block_row < compptr->v_samp_factor; + block_row++) { + thisblockrow = buffer[block_row]; + lastblockrow = buffer[block_row-1]; + jzero_far((void FAR *) thisblockrow, + (size_t) (blocks_across * SIZEOF(JBLOCK))); + for (MCUindex = 0; MCUindex < MCUs_across; MCUindex++) { + lastDC = lastblockrow[h_samp_factor-1][0]; + for (bi = 0; bi < h_samp_factor; bi++) { + thisblockrow[bi][0] = lastDC; + } + thisblockrow += h_samp_factor; /* advance to next MCU in row */ + lastblockrow += h_samp_factor; + } + } + } + } + /* NB: compress_output will increment iMCU_row_num if successful. + * A suspension return will result in redoing all the work above next time. + */ + + /* Emit data to the entropy encoder, sharing code with subsequent passes */ + return compress_output(cinfo, input_buf); +} + + +/* + * Process some data in subsequent passes of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF boolean +compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. + * NB: during first pass, this is safe only because the buffers will + * already be aligned properly, so jmemmgr.c won't need to do any I/O. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + +#endif /* FULL_COEF_BUFFER_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_c_coef_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef FULL_COEF_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + int ci; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->whole_image[0] = NULL; /* flag for no virtual arrays */ + } +} diff --git a/src/renderer/jpeg-6/jccolor.c b/src/renderer/jpeg-6/jccolor.c new file mode 100644 index 0000000..d774486 --- /dev/null +++ b/src/renderer/jpeg-6/jccolor.c @@ -0,0 +1,483 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jccolor.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_converter pub; /* public fields */ + + /* Private state for RGB->YCC conversion */ + INT32 * rgb_ycc_tab; /* => table for RGB to YCbCr conversion */ +} my_color_converter; + +typedef my_color_converter * my_cconvert_ptr; + + +/**************** RGB -> YCbCr conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B + * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE + * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2, + * rather than CENTERJSAMPLE, for Cb and Cr. This gave equal positive and + * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0) + * were not represented exactly. Now we sacrifice exact representation of + * maximum red and maximum blue in order to get exact grayscales. + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times R,G,B for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included + * in the tables to save adding them separately in the inner loop. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define CBCR_OFFSET ((INT32) CENTERJSAMPLE << SCALEBITS) +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L< Y section */ +#define G_Y_OFF (1*(MAXJSAMPLE+1)) /* offset to G => Y section */ +#define B_Y_OFF (2*(MAXJSAMPLE+1)) /* etc. */ +#define R_CB_OFF (3*(MAXJSAMPLE+1)) +#define G_CB_OFF (4*(MAXJSAMPLE+1)) +#define B_CB_OFF (5*(MAXJSAMPLE+1)) +#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */ +#define G_CR_OFF (6*(MAXJSAMPLE+1)) +#define B_CR_OFF (7*(MAXJSAMPLE+1)) +#define TABLE_SIZE (8*(MAXJSAMPLE+1)) + + +/* + * Initialize for RGB->YCC colorspace conversion. + */ + +METHODDEF void +rgb_ycc_start (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + INT32 * rgb_ycc_tab; + INT32 i; + + /* Allocate and fill in the conversion tables. */ + cconvert->rgb_ycc_tab = rgb_ycc_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (TABLE_SIZE * SIZEOF(INT32))); + + for (i = 0; i <= MAXJSAMPLE; i++) { + rgb_ycc_tab[i+R_Y_OFF] = FIX(0.29900) * i; + rgb_ycc_tab[i+G_Y_OFF] = FIX(0.58700) * i; + rgb_ycc_tab[i+B_Y_OFF] = FIX(0.11400) * i + ONE_HALF; + rgb_ycc_tab[i+R_CB_OFF] = (-FIX(0.16874)) * i; + rgb_ycc_tab[i+G_CB_OFF] = (-FIX(0.33126)) * i; + /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr. + * This ensures that the maximum output will round to MAXJSAMPLE + * not MAXJSAMPLE+1, and thus that we don't have to range-limit. + */ + rgb_ycc_tab[i+B_CB_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +/* B=>Cb and R=>Cr tables are the same + rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +*/ + rgb_ycc_tab[i+G_CR_OFF] = (-FIX(0.41869)) * i; + rgb_ycc_tab[i+B_CR_OFF] = (-FIX(0.08131)) * i; + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * + * Note that we change from the application's interleaved-pixel format + * to our internal noninterleaved, one-plane-per-component format. + * The input buffer is therefore three times as wide as the output buffer. + * + * A starting row offset is provided only for the output buffer. The caller + * can easily adjust the passed input_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +rgb_ycc_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/**************** Cases other than RGB -> YCbCr **************/ + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles RGB->grayscale conversion, which is the same + * as the RGB->Y portion of RGB->YCbCr. + * We assume rgb_ycc_start has been called (we only use the Y tables). + */ + +METHODDEF void +rgb_gray_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* Y */ + outptr[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles Adobe-style CMYK->YCCK conversion, + * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same + * conversion as above, while passing K (black) unchanged. + * We assume rgb_ycc_start has been called. + */ + +METHODDEF void +cmyk_ycck_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2, outptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + outptr3 = output_buf[3][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = MAXJSAMPLE - GETJSAMPLE(inptr[0]); + g = MAXJSAMPLE - GETJSAMPLE(inptr[1]); + b = MAXJSAMPLE - GETJSAMPLE(inptr[2]); + /* K passes through as-is */ + outptr3[col] = inptr[3]; /* don't need GETJSAMPLE here */ + inptr += 4; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles grayscale output with no conversion. + * The source can be either plain grayscale or YCbCr (since Y == gray). + */ + +METHODDEF void +grayscale_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + int instride = cinfo->input_components; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */ + inptr += instride; + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles multi-component colorspaces without conversion. + * We assume input_components == num_components. + */ + +METHODDEF void +null_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + register int ci; + int nc = cinfo->num_components; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + /* It seems fastest to make a separate pass for each component. */ + for (ci = 0; ci < nc; ci++) { + inptr = *input_buf; + outptr = output_buf[ci][output_row]; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */ + inptr += nc; + } + } + input_buf++; + output_row++; + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +null_method (j_compress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for input colorspace conversion. + */ + +GLOBAL void +jinit_color_converter (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_converter)); + cinfo->cconvert = (struct jpeg_color_converter *) cconvert; + /* set start_pass to null method until we find out differently */ + cconvert->pub.start_pass = null_method; + + /* Make sure input_components agrees with in_color_space */ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + if (cinfo->input_components != 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + if (cinfo->input_components != RGB_PIXELSIZE) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; +#endif /* else share code with YCbCr */ + + case JCS_YCbCr: + if (cinfo->input_components != 3) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->input_components != 4) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->input_components < 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + } + + /* Check num_components, set conversion method based on requested space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_GRAYSCALE) + cconvert->pub.color_convert = grayscale_convert; + else if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_gray_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = grayscale_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_ycc_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = cmyk_ycck_convert; + } else if (cinfo->in_color_space == JCS_YCCK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: /* allow null conversion of JCS_UNKNOWN */ + if (cinfo->jpeg_color_space != cinfo->in_color_space || + cinfo->num_components != cinfo->input_components) + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + cconvert->pub.color_convert = null_convert; + break; + } +} diff --git a/src/renderer/jpeg-6/jcdctmgr.c b/src/renderer/jpeg-6/jcdctmgr.c new file mode 100644 index 0000000..516a7d0 --- /dev/null +++ b/src/renderer/jpeg-6/jcdctmgr.c @@ -0,0 +1,412 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcdctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the forward-DCT management logic. + * This code selects a particular DCT implementation to be used, + * and it performs related housekeeping chores including coefficient + * quantization. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_forward_dct pub; /* public fields */ + + /* Pointer to the DCT routine actually in use */ + forward_DCT_method_ptr do_dct; + + /* The actual post-DCT divisors --- not identical to the quant table + * entries, because of scaling (especially for an unnormalized DCT). + * Each table is given in normal array order; note that this must + * be converted from the zigzag order of the quantization tables. + */ + DCTELEM * divisors[NUM_QUANT_TBLS]; + +#ifdef DCT_FLOAT_SUPPORTED + /* Same as above for the floating-point case. */ + float_DCT_method_ptr do_float_dct; + FAST_FLOAT * float_divisors[NUM_QUANT_TBLS]; +#endif +} my_fdct_controller; + +typedef my_fdct_controller * my_fdct_ptr; + + +/* + * Initialize for a processing pass. + * Verify that all referenced Q-tables are present, and set up + * the divisor table for each one. + * In the current implementation, DCT of all components is done during + * the first pass, even if only some components will be output in the + * first scan. Hence all components should be examined here. + */ + +METHODDEF void +start_pass_fdctmgr (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + int ci, qtblno, i; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + //DCTELEM * dtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + qtblno = compptr->quant_tbl_no; + /* Make sure specified quantization table is present */ + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + qtbl = cinfo->quant_tbl_ptrs[qtblno]; + /* Compute divisors for this quant table */ + /* We may do this more than once for same table, but it's not a big deal */ + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + /* For LL&M IDCT method, divisors are equal to raw quantization + * coefficients multiplied by 8 (to counteract scaling). + */ + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = ((DCTELEM) qtbl->quantval[jpeg_zigzag_order[i]]) << 3; + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + */ +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits: in natural order */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = (DCTELEM) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i]), + CONST_BITS-3); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + FAST_FLOAT * fdtbl; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + if (fdct->float_divisors[qtblno] == NULL) { + fdct->float_divisors[qtblno] = (FAST_FLOAT *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(FAST_FLOAT)); + } + fdtbl = fdct->float_divisors[qtblno]; + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fdtbl[i] = (FAST_FLOAT) + (1.0 / (((double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col] * 8.0))); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Perform forward DCT on one or more blocks of a component. + * + * The input samples are taken from the sample_data[] array starting at + * position start_row/start_col, and moving to the right for any additional + * blocks. The quantized coefficients are returned in coef_blocks[]. + */ + +METHODDEF void +forward_DCT (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for integer DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + forward_DCT_method_ptr do_dct = fdct->do_dct; + DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no]; + DCTELEM workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register DCTELEM *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register DCTELEM temp, qval; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + qval = divisors[i]; + temp = workspace[i]; + /* Divide the coefficient value by qval, ensuring proper rounding. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * + * In most files, at least half of the output values will be zero + * (at default quantization settings, more like three-quarters...) + * so we should ensure that this case is fast. On many machines, + * a comparison is enough cheaper than a divide to make a special test + * a win. Since both inputs will be nonnegative, we need only test + * for a < b to discover whether a/b is 0. + * If your machine's division is fast enough, define FAST_DIVIDE. + */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY(a,b) a /= b +#else +#define DIVIDE_BY(a,b) if (a >= b) a /= b; else a = 0 +#endif + if (temp < 0) { + temp = -temp; + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + temp = -temp; + } else { + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + } + output_ptr[i] = (JCOEF) temp; + } + } + } +} + + +#ifdef DCT_FLOAT_SUPPORTED + +METHODDEF void +forward_DCT_float (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for floating-point DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + float_DCT_method_ptr do_dct = fdct->do_float_dct; + FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no]; + FAST_FLOAT workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register FAST_FLOAT *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = (FAST_FLOAT) + (GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register FAST_FLOAT temp; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + /* Apply the quantization and scaling factor */ + temp = workspace[i] * divisors[i]; + /* Round to nearest integer. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * The maximum coefficient size is +-16K (for 12-bit data), so this + * code should work for either 16-bit or 32-bit ints. + */ + output_ptr[i] = (JCOEF) ((int) (temp + (FAST_FLOAT) 16384.5) - 16384); + } + } + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ + + +/* + * Initialize FDCT manager. + */ + +GLOBAL void +jinit_forward_dct (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct; + int i; + + fdct = (my_fdct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_fdct_controller)); + cinfo->fdct = (struct jpeg_forward_dct *) fdct; + fdct->pub.start_pass = start_pass_fdctmgr; + + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_islow; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_ifast; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + fdct->pub.forward_DCT = forward_DCT_float; + fdct->do_float_dct = jpeg_fdct_float; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + + /* Mark divisor tables unallocated */ + for (i = 0; i < NUM_QUANT_TBLS; i++) { + fdct->divisors[i] = NULL; +#ifdef DCT_FLOAT_SUPPORTED + fdct->float_divisors[i] = NULL; +#endif + } +} diff --git a/src/renderer/jpeg-6/jchuff.c b/src/renderer/jpeg-6/jchuff.c new file mode 100644 index 0000000..49ee15a --- /dev/null +++ b/src/renderer/jpeg-6/jchuff.c @@ -0,0 +1,870 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jchuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines. + * + * Much of the complexity here has to do with supporting output suspension. + * If the data destination module demands suspension, we want to be able to + * back up to the start of the current MCU. To do this, we copy state + * variables into local working storage, and update them back to the + * permanent JPEG objects only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jcphuff.c */ + + +/* Expanded entropy encoder object for Huffman encoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).put_buffer = (src).put_buffer, \ + (dest).put_bits = (src).put_bits, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + savable_state saved; /* Bit buffer & DC state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + +#ifdef ENTROPY_OPT_SUPPORTED /* Statistics tables for optimization */ + long * dc_count_ptrs[NUM_HUFF_TBLS]; + long * ac_count_ptrs[NUM_HUFF_TBLS]; +#endif +} huff_entropy_encoder; + +typedef huff_entropy_encoder * huff_entropy_ptr; + +/* Working state while writing an MCU. + * This struct contains all the fields that are needed by subroutines. + */ + +typedef struct { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + savable_state cur; /* Current bit buffer & DC state */ + j_compress_ptr cinfo; /* dump_buffer needs access to this */ +} working_state; + + +/* Forward declarations */ +METHODDEF boolean encode_mcu_huff JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF void finish_pass_huff JPP((j_compress_ptr cinfo)); +#ifdef ENTROPY_OPT_SUPPORTED +METHODDEF boolean encode_mcu_gather JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF void finish_pass_gather JPP((j_compress_ptr cinfo)); +#endif + + +/* + * Initialize for a Huffman-compressed scan. + * If gather_statistics is TRUE, we do not output anything during the scan, + * just count the Huffman symbols used and generate Huffman code tables. + */ + +METHODDEF void +start_pass_huff (j_compress_ptr cinfo, boolean gather_statistics) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + entropy->pub.encode_mcu = encode_mcu_gather; + entropy->pub.finish_pass = finish_pass_gather; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + entropy->pub.encode_mcu = encode_mcu_huff; + entropy->pub.finish_pass = finish_pass_huff; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + (cinfo->dc_huff_tbl_ptrs[dctbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS || + (cinfo->ac_huff_tbl_ptrs[actbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->dc_count_ptrs[dctbl] == NULL) + entropy->dc_count_ptrs[dctbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->dc_count_ptrs[dctbl], 257 * SIZEOF(long)); + if (entropy->ac_count_ptrs[actbl] == NULL) + entropy->ac_count_ptrs[actbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->ac_count_ptrs[actbl], 257 * SIZEOF(long)); +#endif + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_c_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + & entropy->ac_derived_tbls[actbl]); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bit buffer to empty */ + entropy->saved.put_buffer = 0; + entropy->saved.put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_make_c_derived_tbl (j_compress_ptr cinfo, JHUFF_TBL * htbl, + c_derived_tbl ** pdtbl) +{ + c_derived_tbl *dtbl; + int p, i, l, lastp, si; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (c_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(c_derived_tbl)); + dtbl = *pdtbl; + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + lastp = p; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure C.3: generate encoding tables */ + /* These are code and size indexed by symbol value */ + + /* Set any codeless symbols to have code length 0; + * this allows emit_bits to detect any attempt to emit such symbols. + */ + MEMZERO(dtbl->ehufsi, SIZEOF(dtbl->ehufsi)); + + for (p = 0; p < lastp; p++) { + dtbl->ehufco[htbl->huffval[p]] = huffcode[p]; + dtbl->ehufsi[htbl->huffval[p]] = huffsize[p]; + } +} + + +/* Outputting bytes to the file */ + +/* Emit a byte, taking 'action' if must suspend. */ +#define emit_byte(state,val,action) \ + { *(state)->next_output_byte++ = (JOCTET) (val); \ + if (--(state)->free_in_buffer == 0) \ + if (! dump_buffer(state)) \ + { action; } } + + +LOCAL boolean +dump_buffer (working_state * state) +/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */ +{ + struct jpeg_destination_mgr * dest = state->cinfo->dest; + + if (! (*dest->empty_output_buffer) (state->cinfo)) + return FALSE; + /* After a successful buffer dump, must reset buffer pointers */ + state->next_output_byte = dest->next_output_byte; + state->free_in_buffer = dest->free_in_buffer; + return TRUE; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL boolean +emit_bits (working_state * state, unsigned int code, int size) +/* Emit some bits; return TRUE if successful, FALSE if must suspend */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = state->cur.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(state->cinfo, JERR_HUFF_MISSING_CODE); + + put_buffer &= (((INT32) 1)<cur.put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(state, c, return FALSE); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(state, 0, return FALSE); + } + put_buffer <<= 8; + put_bits -= 8; + } + + state->cur.put_buffer = put_buffer; /* update state variables */ + state->cur.put_bits = put_bits; + + return TRUE; +} + + +LOCAL boolean +flush_bits (working_state * state) +{ + if (! emit_bits(state, 0x7F, 7)) /* fill any partial byte with ones */ + return FALSE; + state->cur.put_buffer = 0; /* and reset bit-buffer to empty */ + state->cur.put_bits = 0; + return TRUE; +} + + +/* Encode a single block's worth of coefficients */ + +LOCAL boolean +encode_one_block (working_state * state, JCOEFPTR block, int last_dc_val, + c_derived_tbl *dctbl, c_derived_tbl *actbl) +{ + register int temp, temp2; + register int nbits; + register int k, r, i; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = temp2 = block[0] - last_dc_val; + + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + + /* Emit the Huffman-coded symbol for the number of bits */ + if (! emit_bits(state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + if (! emit_bits(state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0])) + return FALSE; + r -= 16; + } + + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + + /* Emit Huffman symbol for run length / number of bits */ + i = (r << 4) + nbits; + if (! emit_bits(state, actbl->ehufco[i], actbl->ehufsi[i])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + if (! emit_bits(state, actbl->ehufco[0], actbl->ehufsi[0])) + return FALSE; + + return TRUE; +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL boolean +emit_restart (working_state * state, int restart_num) +{ + int ci; + + if (! flush_bits(state)) + return FALSE; + + emit_byte(state, 0xFF, return FALSE); + emit_byte(state, JPEG_RST0 + restart_num, return FALSE); + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < state->cinfo->comps_in_scan; ci++) + state->cur.last_dc_val[ci] = 0; + + /* The restart counter is not updated until we successfully write the MCU. */ + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of Huffman-compressed coefficients. + */ + +METHODDEF boolean +encode_mcu_huff (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + int blkn, ci; + jpeg_component_info * compptr; + + /* Load up working state */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! emit_restart(&state, entropy->next_restart_num)) + return FALSE; + } + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + if (! encode_one_block(&state, + MCU_data[blkn][0], state.cur.last_dc_val[ci], + entropy->dc_derived_tbls[compptr->dc_tbl_no], + entropy->ac_derived_tbls[compptr->ac_tbl_no])) + return FALSE; + /* Update last_dc_val */ + state.cur.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + /* Completed MCU, so update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed scan. + */ + +METHODDEF void +finish_pass_huff (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + + /* Load up working state ... flush_bits needs it */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Flush out the last data */ + if (! flush_bits(&state)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + + /* Update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); +} + + +/* + * Huffman coding optimization. + * + * This actually is optimization, in the sense that we find the best possible + * Huffman table(s) for the given data. We first scan the supplied data and + * count the number of uses of each symbol that is to be Huffman-coded. + * (This process must agree with the code above.) Then we build an + * optimal Huffman coding tree for the observed counts. + * + * The JPEG standard requires Huffman codes to be no more than 16 bits long. + * If some symbols have a very small but nonzero probability, the Huffman tree + * must be adjusted to meet the code length restriction. We currently use + * the adjustment method suggested in the JPEG spec. This method is *not* + * optimal; it may not choose the best possible limited-length code. But + * since the symbols involved are infrequently used, it's not clear that + * going to extra trouble is worthwhile. + */ + +#ifdef ENTROPY_OPT_SUPPORTED + + +/* Process a single block's worth of coefficients */ + +LOCAL void +htest_one_block (JCOEFPTR block, int last_dc_val, + long dc_counts[], long ac_counts[]) +{ + register int temp; + register int nbits; + register int k, r; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = block[0] - last_dc_val; + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + + /* Count the Huffman symbol for the number of bits */ + dc_counts[nbits]++; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + ac_counts[0xF0]++; + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + + /* Count Huffman symbol for run length / number of bits */ + ac_counts[(r << 4) + nbits]++; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + ac_counts[0]++; +} + + +/* + * Trial-encode one MCU's worth of Huffman-compressed coefficients. + * No data is actually output, so no suspension return is possible. + */ + +METHODDEF boolean +encode_mcu_gather (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn, ci; + jpeg_component_info * compptr; + + /* Take care of restart intervals if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Update restart state */ + entropy->restarts_to_go = cinfo->restart_interval; + } + entropy->restarts_to_go--; + } + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + htest_one_block(MCU_data[blkn][0], entropy->saved.last_dc_val[ci], + entropy->dc_count_ptrs[compptr->dc_tbl_no], + entropy->ac_count_ptrs[compptr->ac_tbl_no]); + entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + return TRUE; +} + + +/* + * Generate the optimal coding for the given counts, fill htbl. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_gen_optimal_table (j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[]) +{ +#define MAX_CLEN 32 /* assumed maximum initial code length */ + UINT8 bits[MAX_CLEN+1]; /* bits[k] = # of symbols with code length k */ + int codesize[257]; /* codesize[k] = code length of symbol k */ + int others[257]; /* next symbol in current branch of tree */ + int c1, c2; + int p, i, j; + long v; + + /* This algorithm is explained in section K.2 of the JPEG standard */ + + MEMZERO(bits, SIZEOF(bits)); + MEMZERO(codesize, SIZEOF(codesize)); + for (i = 0; i < 257; i++) + others[i] = -1; /* init links to empty */ + + freq[256] = 1; /* make sure there is a nonzero count */ + /* Including the pseudo-symbol 256 in the Huffman procedure guarantees + * that no real symbol is given code-value of all ones, because 256 + * will be placed in the largest codeword category. + */ + + /* Huffman's basic algorithm to assign optimal code lengths to symbols */ + + for (;;) { + /* Find the smallest nonzero frequency, set c1 = its symbol */ + /* In case of ties, take the larger symbol number */ + c1 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v) { + v = freq[i]; + c1 = i; + } + } + + /* Find the next smallest nonzero frequency, set c2 = its symbol */ + /* In case of ties, take the larger symbol number */ + c2 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v && i != c1) { + v = freq[i]; + c2 = i; + } + } + + /* Done if we've merged everything into one frequency */ + if (c2 < 0) + break; + + /* Else merge the two counts/trees */ + freq[c1] += freq[c2]; + freq[c2] = 0; + + /* Increment the codesize of everything in c1's tree branch */ + codesize[c1]++; + while (others[c1] >= 0) { + c1 = others[c1]; + codesize[c1]++; + } + + others[c1] = c2; /* chain c2 onto c1's tree branch */ + + /* Increment the codesize of everything in c2's tree branch */ + codesize[c2]++; + while (others[c2] >= 0) { + c2 = others[c2]; + codesize[c2]++; + } + } + + /* Now count the number of symbols of each code length */ + for (i = 0; i <= 256; i++) { + if (codesize[i]) { + /* The JPEG standard seems to think that this can't happen, */ + /* but I'm paranoid... */ + if (codesize[i] > MAX_CLEN) + ERREXIT(cinfo, JERR_HUFF_CLEN_OVERFLOW); + + bits[codesize[i]]++; + } + } + + /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure + * Huffman procedure assigned any such lengths, we must adjust the coding. + * Here is what the JPEG spec says about how this next bit works: + * Since symbols are paired for the longest Huffman code, the symbols are + * removed from this length category two at a time. The prefix for the pair + * (which is one bit shorter) is allocated to one of the pair; then, + * skipping the BITS entry for that prefix length, a code word from the next + * shortest nonzero BITS entry is converted into a prefix for two code words + * one bit longer. + */ + + for (i = MAX_CLEN; i > 16; i--) { + while (bits[i] > 0) { + j = i - 2; /* find length of new prefix to be used */ + while (bits[j] == 0) + j--; + + bits[i] -= 2; /* remove two symbols */ + bits[i-1]++; /* one goes in this length */ + bits[j+1] += 2; /* two new symbols in this length */ + bits[j]--; /* symbol of this length is now a prefix */ + } + } + + /* Remove the count for the pseudo-symbol 256 from the largest codelength */ + while (bits[i] == 0) /* find largest codelength still in use */ + i--; + bits[i]--; + + /* Return final symbol counts (only for lengths 0..16) */ + MEMCOPY(htbl->bits, bits, SIZEOF(htbl->bits)); + + /* Return a list of the symbols sorted by code length */ + /* It's not real clear to me why we don't need to consider the codelength + * changes made above, but the JPEG spec seems to think this works. + */ + p = 0; + for (i = 1; i <= MAX_CLEN; i++) { + for (j = 0; j <= 255; j++) { + if (codesize[j] == i) { + htbl->huffval[p] = (UINT8) j; + p++; + } + } + } + + /* Set sent_table FALSE so updated table will be written to JPEG file. */ + htbl->sent_table = FALSE; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did_dc[NUM_HUFF_TBLS]; + boolean did_ac[NUM_HUFF_TBLS]; + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did_dc, SIZEOF(did_dc)); + MEMZERO(did_ac, SIZEOF(did_ac)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if (! did_dc[dctbl]) { + htblptr = & cinfo->dc_huff_tbl_ptrs[dctbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->dc_count_ptrs[dctbl]); + did_dc[dctbl] = TRUE; + } + if (! did_ac[actbl]) { + htblptr = & cinfo->ac_huff_tbl_ptrs[actbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->ac_count_ptrs[actbl]); + did_ac[actbl] = TRUE; + } + } +} + + +#endif /* ENTROPY_OPT_SUPPORTED */ + + +/* + * Module initialization routine for Huffman entropy encoding. + */ + +GLOBAL void +jinit_huff_encoder (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_huff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; +#ifdef ENTROPY_OPT_SUPPORTED + entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL; +#endif + } +} diff --git a/src/renderer/jpeg-6/jchuff.h b/src/renderer/jpeg-6/jchuff.h new file mode 100644 index 0000000..2e65118 --- /dev/null +++ b/src/renderer/jpeg-6/jchuff.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jchuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy encoding routines + * that are shared between the sequential encoder (jchuff.c) and the + * progressive encoder (jcphuff.c). No other modules need to see these. + */ + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_c_derived_tbl jMkCDerived +#define jpeg_gen_optimal_table jGenOptTbl +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Expand a Huffman table definition into the derived format */ +EXTERN void jpeg_make_c_derived_tbl JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, c_derived_tbl ** pdtbl)); + +/* Generate an optimal table definition given the specified counts */ +EXTERN void jpeg_gen_optimal_table JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, long freq[])); diff --git a/src/renderer/jpeg-6/jcinit.c b/src/renderer/jpeg-6/jcinit.c new file mode 100644 index 0000000..d837d96 --- /dev/null +++ b/src/renderer/jpeg-6/jcinit.c @@ -0,0 +1,96 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcinit.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains initialization logic for the JPEG compressor. + * This routine is in charge of selecting the modules to be executed and + * making an initialization call to each one. + * + * Logically, this code belongs in jcmaster.c. It's split out because + * linking this routine implies linking the entire compression library. + * For a transcoding-only application, we want to be able to use jcmaster.c + * without linking in the whole library. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Master selection of compression modules. + * This is done once at the start of processing an image. We determine + * which modules will be used and give them appropriate initialization calls. + */ + +GLOBAL void +jinit_compress_master (j_compress_ptr cinfo) +{ + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, FALSE /* full compression */); + + /* Preprocessing */ + if (! cinfo->raw_data_in) { + jinit_color_converter(cinfo); + jinit_downsampler(cinfo); + jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */); + } + /* Forward DCT */ + jinit_forward_dct(cinfo); + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_encoder(cinfo); + } + + /* Need a full-image coefficient buffer in any multi-pass mode. */ + jinit_c_coef_controller(cinfo, + (cinfo->num_scans > 1 || cinfo->optimize_coding)); + jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} diff --git a/src/renderer/jpeg-6/jcmainct.c b/src/renderer/jpeg-6/jcmainct.c new file mode 100644 index 0000000..2c9baac --- /dev/null +++ b/src/renderer/jpeg-6/jcmainct.c @@ -0,0 +1,317 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for compression. + * The main buffer lies between the pre-processor and the JPEG + * compressor proper; it holds downsampled data in the JPEG colorspace. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Note: currently, there is no operating mode in which a full-image buffer + * is needed at this step. If there were, that mode could not be used with + * "raw data" input, since this module is bypassed in that case. However, + * we've left the code here for possible use in special applications. + */ +#undef FULL_MAIN_BUFFER_SUPPORTED + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_main_controller pub; /* public fields */ + + JDIMENSION cur_iMCU_row; /* number of current iMCU row */ + JDIMENSION rowgroup_ctr; /* counts row groups received in iMCU row */ + boolean suspended; /* remember if we suspended output */ + J_BUF_MODE pass_mode; /* current operating mode */ + + /* If using just a strip buffer, this points to the entire set of buffers + * (we allocate one for each component). In the full-image case, this + * points to the currently accessible strips of the virtual arrays. + */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* If using full-image storage, this array holds pointers to virtual-array + * control blocks for each component. Unused if not full-image storage. + */ + jvirt_sarray_ptr whole_image[MAX_COMPONENTS]; +#endif +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + + +/* Forward declarations */ +METHODDEF void process_data_simple_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#ifdef FULL_MAIN_BUFFER_SUPPORTED +METHODDEF void process_data_buffer_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + /* Do nothing in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + main->cur_iMCU_row = 0; /* initialize counters */ + main->rowgroup_ctr = 0; + main->suspended = FALSE; + main->pass_mode = pass_mode; /* save mode for use by process_data */ + + switch (pass_mode) { + case JBUF_PASS_THRU: +#ifdef FULL_MAIN_BUFFER_SUPPORTED + if (main->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + main->pub.process_data = process_data_simple_main; + break; +#ifdef FULL_MAIN_BUFFER_SUPPORTED + case JBUF_SAVE_SOURCE: + case JBUF_CRANK_DEST: + case JBUF_SAVE_AND_PASS: + if (main->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + main->pub.process_data = process_data_buffer_main; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This routine handles the simple pass-through mode, + * where we have only a strip buffer. + */ + +METHODDEF void +process_data_simple_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + while (main->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Read input data if we haven't filled the main buffer yet */ + if (main->rowgroup_ctr < DCTSIZE) + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + + /* If we don't have a full iMCU row buffered, return to application for + * more data. Note that preprocessor will always pad to fill the iMCU row + * at the bottom of the image. + */ + if (main->rowgroup_ctr != DCTSIZE) + return; + + /* Send the completed row to the compressor */ + if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main->suspended) { + (*in_row_ctr)--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main->suspended) { + (*in_row_ctr)++; + main->suspended = FALSE; + } + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + +/* + * Process some data. + * This routine handles all of the modes that use a full-size buffer. + */ + +METHODDEF void +process_data_buffer_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci; + jpeg_component_info *compptr; + boolean writing = (main->pass_mode != JBUF_CRANK_DEST); + + while (main->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Realign the virtual buffers if at the start of an iMCU row. */ + if (main->rowgroup_ctr == 0) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->buffer[ci] = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, main->whole_image[ci], + main->cur_iMCU_row * (compptr->v_samp_factor * DCTSIZE), + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE), writing); + } + /* In a read pass, pretend we just read some source data. */ + if (! writing) { + *in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE; + main->rowgroup_ctr = DCTSIZE; + } + } + + /* If a write pass, read input data until the current iMCU row is full. */ + /* Note: preprocessor will pad if necessary to fill the last iMCU row. */ + if (writing) { + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + /* Return to application if we need more data to fill the iMCU row. */ + if (main->rowgroup_ctr < DCTSIZE) + return; + } + + /* Emit data, unless this is a sink-only pass. */ + if (main->pass_mode != JBUF_SAVE_SOURCE) { + if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main->suspended) { + (*in_row_ctr)--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main->suspended) { + (*in_row_ctr)++; + main->suspended = FALSE; + } + } + + /* If get here, we are done with this iMCU row. Mark buffer empty. */ + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + +#endif /* FULL_MAIN_BUFFER_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_c_main_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main; + int ci; + jpeg_component_info *compptr; + + main = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_c_main_controller *) main; + main->pub.start_pass = start_pass_main; + + /* We don't need to create a buffer in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + /* Create the buffer. It holds downsampled data, so each component + * may be of a different size. + */ + if (need_full_buffer) { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component */ + /* Note we pad the bottom to a multiple of the iMCU height */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->whole_image[ci] = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor) * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + main->whole_image[0] = NULL; /* flag for no virtual arrays */ +#endif + /* Allocate a strip buffer for each component */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } + } +} diff --git a/src/renderer/jpeg-6/jcmarker.c b/src/renderer/jpeg-6/jcmarker.c new file mode 100644 index 0000000..a88a569 --- /dev/null +++ b/src/renderer/jpeg-6/jcmarker.c @@ -0,0 +1,663 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to write JPEG datastream markers. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Basic output routines. + * + * Note that we do not support suspension while writing a marker. + * Therefore, an application using suspension must ensure that there is + * enough buffer space for the initial markers (typ. 600-700 bytes) before + * calling jpeg_start_compress, and enough space to write the trailing EOI + * (a few bytes) before calling jpeg_finish_compress. Multipass compression + * modes are not supported at all with suspension, so those two are the only + * points where markers will be written. + */ + +LOCAL void +emit_byte (j_compress_ptr cinfo, int val) +/* Emit a byte */ +{ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *(dest->next_output_byte)++ = (JOCTET) val; + if (--dest->free_in_buffer == 0) { + if (! (*dest->empty_output_buffer) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } +} + + +LOCAL void +emit_marker (j_compress_ptr cinfo, JPEG_MARKER mark) +/* Emit a marker code */ +{ + emit_byte(cinfo, 0xFF); + emit_byte(cinfo, (int) mark); +} + + +LOCAL void +emit_2bytes (j_compress_ptr cinfo, int value) +/* Emit a 2-byte integer; these are always MSB first in JPEG files */ +{ + emit_byte(cinfo, (value >> 8) & 0xFF); + emit_byte(cinfo, value & 0xFF); +} + + +/* + * Routines to write specific marker types. + */ + +LOCAL int +emit_dqt (j_compress_ptr cinfo, int index) +/* Emit a DQT marker */ +/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */ +{ + JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index]; + int prec; + int i; + + if (qtbl == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, index); + + prec = 0; + for (i = 0; i < DCTSIZE2; i++) { + if (qtbl->quantval[i] > 255) + prec = 1; + } + + if (! qtbl->sent_table) { + emit_marker(cinfo, M_DQT); + + emit_2bytes(cinfo, prec ? DCTSIZE2*2 + 1 + 2 : DCTSIZE2 + 1 + 2); + + emit_byte(cinfo, index + (prec<<4)); + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + emit_byte(cinfo, qtbl->quantval[i] >> 8); + emit_byte(cinfo, qtbl->quantval[i] & 0xFF); + } + + qtbl->sent_table = TRUE; + } + + return prec; +} + + +LOCAL void +emit_dht (j_compress_ptr cinfo, int index, boolean is_ac) +/* Emit a DHT marker */ +{ + JHUFF_TBL * htbl; + int length, i; + + if (is_ac) { + htbl = cinfo->ac_huff_tbl_ptrs[index]; + index += 0x10; /* output index has AC bit set */ + } else { + htbl = cinfo->dc_huff_tbl_ptrs[index]; + } + + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, index); + + if (! htbl->sent_table) { + emit_marker(cinfo, M_DHT); + + length = 0; + for (i = 1; i <= 16; i++) + length += htbl->bits[i]; + + emit_2bytes(cinfo, length + 2 + 1 + 16); + emit_byte(cinfo, index); + + for (i = 1; i <= 16; i++) + emit_byte(cinfo, htbl->bits[i]); + + for (i = 0; i < length; i++) + emit_byte(cinfo, htbl->huffval[i]); + + htbl->sent_table = TRUE; + } +} + + +LOCAL void +emit_dac (j_compress_ptr cinfo) +/* Emit a DAC marker */ +/* Since the useful info is so small, we want to emit all the tables in */ +/* one DAC marker. Therefore this routine does its own scan of the table. */ +{ +#ifdef C_ARITH_CODING_SUPPORTED + char dc_in_use[NUM_ARITH_TBLS]; + char ac_in_use[NUM_ARITH_TBLS]; + int length, i; + jpeg_component_info *compptr; + + for (i = 0; i < NUM_ARITH_TBLS; i++) + dc_in_use[i] = ac_in_use[i] = 0; + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + dc_in_use[compptr->dc_tbl_no] = 1; + ac_in_use[compptr->ac_tbl_no] = 1; + } + + length = 0; + for (i = 0; i < NUM_ARITH_TBLS; i++) + length += dc_in_use[i] + ac_in_use[i]; + + emit_marker(cinfo, M_DAC); + + emit_2bytes(cinfo, length*2 + 2); + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + if (dc_in_use[i]) { + emit_byte(cinfo, i); + emit_byte(cinfo, cinfo->arith_dc_L[i] + (cinfo->arith_dc_U[i]<<4)); + } + if (ac_in_use[i]) { + emit_byte(cinfo, i + 0x10); + emit_byte(cinfo, cinfo->arith_ac_K[i]); + } + } +#endif /* C_ARITH_CODING_SUPPORTED */ +} + + +LOCAL void +emit_dri (j_compress_ptr cinfo) +/* Emit a DRI marker */ +{ + emit_marker(cinfo, M_DRI); + + emit_2bytes(cinfo, 4); /* fixed length */ + + emit_2bytes(cinfo, (int) cinfo->restart_interval); +} + + +LOCAL void +emit_sof (j_compress_ptr cinfo, JPEG_MARKER code) +/* Emit a SOF marker */ +{ + int ci; + jpeg_component_info *compptr; + + emit_marker(cinfo, code); + + emit_2bytes(cinfo, 3 * cinfo->num_components + 2 + 5 + 1); /* length */ + + /* Make sure image isn't bigger than SOF field can handle */ + if ((long) cinfo->image_height > 65535L || + (long) cinfo->image_width > 65535L) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535); + + emit_byte(cinfo, cinfo->data_precision); + emit_2bytes(cinfo, (int) cinfo->image_height); + emit_2bytes(cinfo, (int) cinfo->image_width); + + emit_byte(cinfo, cinfo->num_components); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + emit_byte(cinfo, compptr->component_id); + emit_byte(cinfo, (compptr->h_samp_factor << 4) + compptr->v_samp_factor); + emit_byte(cinfo, compptr->quant_tbl_no); + } +} + + +LOCAL void +emit_sos (j_compress_ptr cinfo) +/* Emit a SOS marker */ +{ + int i, td, ta; + jpeg_component_info *compptr; + + emit_marker(cinfo, M_SOS); + + emit_2bytes(cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3); /* length */ + + emit_byte(cinfo, cinfo->comps_in_scan); + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + emit_byte(cinfo, compptr->component_id); + td = compptr->dc_tbl_no; + ta = compptr->ac_tbl_no; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan; + * furthermore, Huffman coding of DC refinement uses no table at all. + * We emit 0 for unused field(s); this is recommended by the P&M text + * but does not seem to be specified in the standard. + */ + if (cinfo->Ss == 0) { + ta = 0; /* DC scan */ + if (cinfo->Ah != 0 && !cinfo->arith_code) + td = 0; /* no DC table either */ + } else { + td = 0; /* AC scan */ + } + } + emit_byte(cinfo, (td << 4) + ta); + } + + emit_byte(cinfo, cinfo->Ss); + emit_byte(cinfo, cinfo->Se); + emit_byte(cinfo, (cinfo->Ah << 4) + cinfo->Al); +} + + +LOCAL void +emit_jfif_app0 (j_compress_ptr cinfo) +/* Emit a JFIF-compliant APP0 marker */ +{ + /* + * Length of APP0 block (2 bytes) + * Block ID (4 bytes - ASCII "JFIF") + * Zero byte (1 byte to terminate the ID string) + * Version Major, Minor (2 bytes - 0x01, 0x01) + * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) + * Xdpu (2 bytes - dots per unit horizontal) + * Ydpu (2 bytes - dots per unit vertical) + * Thumbnail X size (1 byte) + * Thumbnail Y size (1 byte) + */ + + emit_marker(cinfo, M_APP0); + + emit_2bytes(cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */ + + emit_byte(cinfo, 0x4A); /* Identifier: ASCII "JFIF" */ + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0x49); + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0); + /* We currently emit version code 1.01 since we use no 1.02 features. + * This may avoid complaints from some older decoders. + */ + emit_byte(cinfo, 1); /* Major version */ + emit_byte(cinfo, 1); /* Minor version */ + emit_byte(cinfo, cinfo->density_unit); /* Pixel size information */ + emit_2bytes(cinfo, (int) cinfo->X_density); + emit_2bytes(cinfo, (int) cinfo->Y_density); + emit_byte(cinfo, 0); /* No thumbnail image */ + emit_byte(cinfo, 0); +} + + +LOCAL void +emit_adobe_app14 (j_compress_ptr cinfo) +/* Emit an Adobe APP14 marker */ +{ + /* + * Length of APP14 block (2 bytes) + * Block ID (5 bytes - ASCII "Adobe") + * Version Number (2 bytes - currently 100) + * Flags0 (2 bytes - currently 0) + * Flags1 (2 bytes - currently 0) + * Color transform (1 byte) + * + * Although Adobe TN 5116 mentions Version = 101, all the Adobe files + * now in circulation seem to use Version = 100, so that's what we write. + * + * We write the color transform byte as 1 if the JPEG color space is + * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with + * whether the encoder performed a transformation, which is pretty useless. + */ + + emit_marker(cinfo, M_APP14); + + emit_2bytes(cinfo, 2 + 5 + 2 + 2 + 2 + 1); /* length */ + + emit_byte(cinfo, 0x41); /* Identifier: ASCII "Adobe" */ + emit_byte(cinfo, 0x64); + emit_byte(cinfo, 0x6F); + emit_byte(cinfo, 0x62); + emit_byte(cinfo, 0x65); + emit_2bytes(cinfo, 100); /* Version */ + emit_2bytes(cinfo, 0); /* Flags0 */ + emit_2bytes(cinfo, 0); /* Flags1 */ + switch (cinfo->jpeg_color_space) { + case JCS_YCbCr: + emit_byte(cinfo, 1); /* Color transform = 1 */ + break; + case JCS_YCCK: + emit_byte(cinfo, 2); /* Color transform = 2 */ + break; + default: + emit_byte(cinfo, 0); /* Color transform = 0 */ + break; + } +} + + +/* + * This routine is exported for possible use by applications. + * The intended use is to emit COM or APPn markers after calling + * jpeg_start_compress() and before the first jpeg_write_scanlines() call + * (hence, after write_file_header but before write_frame_header). + * Other uses are not guaranteed to produce desirable results. + */ + +METHODDEF void +write_any_marker (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen) +/* Emit an arbitrary marker with parameters */ +{ + if (datalen <= (unsigned int) 65533) { /* safety check */ + emit_marker(cinfo, (JPEG_MARKER) marker); + + emit_2bytes(cinfo, (int) (datalen + 2)); /* total length */ + + while (datalen--) { + emit_byte(cinfo, *dataptr); + dataptr++; + } + } +} + + +/* + * Write datastream header. + * This consists of an SOI and optional APPn markers. + * We recommend use of the JFIF marker, but not the Adobe marker, + * when using YCbCr or grayscale data. The JFIF marker should NOT + * be used for any other JPEG colorspace. The Adobe marker is helpful + * to distinguish RGB, CMYK, and YCCK colorspaces. + * Note that an application can write additional header markers after + * jpeg_start_compress returns. + */ + +METHODDEF void +write_file_header (j_compress_ptr cinfo) +{ + emit_marker(cinfo, M_SOI); /* first the SOI */ + + if (cinfo->write_JFIF_header) /* next an optional JFIF APP0 */ + emit_jfif_app0(cinfo); + if (cinfo->write_Adobe_marker) /* next an optional Adobe APP14 */ + emit_adobe_app14(cinfo); +} + + +/* + * Write frame header. + * This consists of DQT and SOFn markers. + * Note that we do not emit the SOF until we have emitted the DQT(s). + * This avoids compatibility problems with incorrect implementations that + * try to error-check the quant table numbers as soon as they see the SOF. + */ + +METHODDEF void +write_frame_header (j_compress_ptr cinfo) +{ + int ci, prec; + boolean is_baseline; + jpeg_component_info *compptr; + + /* Emit DQT for each quantization table. + * Note that emit_dqt() suppresses any duplicate tables. + */ + prec = 0; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prec += emit_dqt(cinfo, compptr->quant_tbl_no); + } + /* now prec is nonzero iff there are any 16-bit quant tables. */ + + /* Check for a non-baseline specification. + * Note we assume that Huffman table numbers won't be changed later. + */ + if (cinfo->arith_code || cinfo->progressive_mode || + cinfo->data_precision != 8) { + is_baseline = FALSE; + } else { + is_baseline = TRUE; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1) + is_baseline = FALSE; + } + if (prec && is_baseline) { + is_baseline = FALSE; + /* If it's baseline except for quantizer size, warn the user */ + TRACEMS(cinfo, 0, JTRC_16BIT_TABLES); + } + } + + /* Emit the proper SOF marker */ + if (cinfo->arith_code) { + emit_sof(cinfo, M_SOF9); /* SOF code for arithmetic coding */ + } else { + if (cinfo->progressive_mode) + emit_sof(cinfo, M_SOF2); /* SOF code for progressive Huffman */ + else if (is_baseline) + emit_sof(cinfo, M_SOF0); /* SOF code for baseline implementation */ + else + emit_sof(cinfo, M_SOF1); /* SOF code for non-baseline Huffman file */ + } +} + + +/* + * Write scan header. + * This consists of DHT or DAC markers, optional DRI, and SOS. + * Compressed data will be written following the SOS. + */ + +METHODDEF void +write_scan_header (j_compress_ptr cinfo) +{ + int i; + jpeg_component_info *compptr; + + if (cinfo->arith_code) { + /* Emit arith conditioning info. We may have some duplication + * if the file has multiple scans, but it's so small it's hardly + * worth worrying about. + */ + emit_dac(cinfo); + } else { + /* Emit Huffman tables. + * Note that emit_dht() suppresses any duplicate tables. + */ + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan */ + if (cinfo->Ss == 0) { + if (cinfo->Ah == 0) /* DC needs no table for refinement scan */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + } else { + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } else { + /* Sequential mode: need both DC and AC tables */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } + } + + /* Emit DRI if required --- note that DRI value could change for each scan. + * If it doesn't, a tiny amount of space is wasted in multiple-scan files. + * We assume DRI will never be nonzero for one scan and zero for a later one. + */ + if (cinfo->restart_interval) + emit_dri(cinfo); + + emit_sos(cinfo); +} + + +/* + * Write datastream trailer. + */ + +METHODDEF void +write_file_trailer (j_compress_ptr cinfo) +{ + emit_marker(cinfo, M_EOI); +} + + +/* + * Write an abbreviated table-specification datastream. + * This consists of SOI, DQT and DHT tables, and EOI. + * Any table that is defined and not marked sent_table = TRUE will be + * emitted. Note that all tables will be marked sent_table = TRUE at exit. + */ + +METHODDEF void +write_tables_only (j_compress_ptr cinfo) +{ + int i; + + emit_marker(cinfo, M_SOI); + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if (cinfo->quant_tbl_ptrs[i] != NULL) + (void) emit_dqt(cinfo, i); + } + + if (! cinfo->arith_code) { + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if (cinfo->dc_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, FALSE); + if (cinfo->ac_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, TRUE); + } + } + + emit_marker(cinfo, M_EOI); +} + + +/* + * Initialize the marker writer module. + */ + +GLOBAL void +jinit_marker_writer (j_compress_ptr cinfo) +{ + /* Create the subobject */ + cinfo->marker = (struct jpeg_marker_writer *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(struct jpeg_marker_writer)); + /* Initialize method pointers */ + cinfo->marker->write_any_marker = write_any_marker; + cinfo->marker->write_file_header = write_file_header; + cinfo->marker->write_frame_header = write_frame_header; + cinfo->marker->write_scan_header = write_scan_header; + cinfo->marker->write_file_trailer = write_file_trailer; + cinfo->marker->write_tables_only = write_tables_only; +} diff --git a/src/renderer/jpeg-6/jcmaster.c b/src/renderer/jpeg-6/jcmaster.c new file mode 100644 index 0000000..f805b8b --- /dev/null +++ b/src/renderer/jpeg-6/jcmaster.c @@ -0,0 +1,602 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG compressor. + * These routines are concerned with parameter validation, initial setup, + * and inter-pass control (determining the number of passes and the work + * to be done in each pass). + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef enum { + main_pass, /* input data, also do first output step */ + huff_opt_pass, /* Huffman code optimization pass */ + output_pass /* data output pass */ +} c_pass_type; + +typedef struct { + struct jpeg_comp_master pub; /* public fields */ + + c_pass_type pass_type; /* the type of the current pass */ + + int pass_number; /* # of passes completed */ + int total_passes; /* total # of passes needed */ + + int scan_number; /* current index in scan_info[] */ +} my_comp_master; + +typedef my_comp_master * my_master_ptr; + + +/* + * Support routines that do various essential calculations. + */ + +LOCAL void +initial_setup (j_compress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ + int ci; + jpeg_component_info *compptr; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Sanity check on image dimensions */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0 || cinfo->input_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* Width of an input scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Fill in the correct component_index value; don't rely on application */ + compptr->component_index = ci; + /* For compression, we never do DCT scaling. */ + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed (this flag isn't actually used for compression) */ + compptr->component_needed = TRUE; + } + + /* Compute number of fully interleaved MCU rows (number of times that + * main controller will call coefficient controller). + */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); +} + + +#ifdef C_MULTISCAN_FILES_SUPPORTED + +LOCAL void +validate_script (j_compress_ptr cinfo) +/* Verify that the scan script in cinfo->scan_info[] is valid; also + * determine whether it uses progressive JPEG, and set cinfo->progressive_mode. + */ +{ + const jpeg_scan_info * scanptr; + int scanno, ncomps, ci, coefi, thisi; + int Ss, Se, Ah, Al; + boolean component_sent[MAX_COMPONENTS]; +#ifdef C_PROGRESSIVE_SUPPORTED + int * last_bitpos_ptr; + int last_bitpos[MAX_COMPONENTS][DCTSIZE2]; + /* -1 until that coefficient has been seen; then last Al for it */ +#endif + + if (cinfo->num_scans <= 0) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0); + + /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1; + * for progressive JPEG, no scan can have this. + */ + scanptr = cinfo->scan_info; + if (scanptr->Ss != 0 || scanptr->Se != DCTSIZE2-1) { +#ifdef C_PROGRESSIVE_SUPPORTED + cinfo->progressive_mode = TRUE; + last_bitpos_ptr = & last_bitpos[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (coefi = 0; coefi < DCTSIZE2; coefi++) + *last_bitpos_ptr++ = -1; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + for (ci = 0; ci < cinfo->num_components; ci++) + component_sent[ci] = FALSE; + } + + for (scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++) { + /* Validate component indexes */ + ncomps = scanptr->comps_in_scan; + if (ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN); + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (thisi < 0 || thisi >= cinfo->num_components) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + /* Components must appear in SOF order within each scan */ + if (ci > 0 && thisi <= scanptr->component_index[ci-1]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + } + /* Validate progression parameters */ + Ss = scanptr->Ss; + Se = scanptr->Se; + Ah = scanptr->Ah; + Al = scanptr->Al; + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + if (Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 || + Ah < 0 || Ah > 13 || Al < 0 || Al > 13) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + if (Ss == 0) { + if (Se != 0) /* DC and AC together not OK */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + if (ncomps != 1) /* AC scans must be for only one component */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + for (ci = 0; ci < ncomps; ci++) { + last_bitpos_ptr = & last_bitpos[scanptr->component_index[ci]][0]; + if (Ss != 0 && last_bitpos_ptr[0] < 0) /* AC without prior DC scan */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + for (coefi = Ss; coefi <= Se; coefi++) { + if (last_bitpos_ptr[coefi] < 0) { + /* first scan of this coefficient */ + if (Ah != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + /* not first scan */ + if (Ah != last_bitpos_ptr[coefi] || Al != Ah-1) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + last_bitpos_ptr[coefi] = Al; + } + } +#endif + } else { + /* For sequential JPEG, all progression parameters must be these: */ + if (Ss != 0 || Se != DCTSIZE2-1 || Ah != 0 || Al != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + /* Make sure components are not sent twice */ + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (component_sent[thisi]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + component_sent[thisi] = TRUE; + } + } + } + + /* Now verify that everything got sent. */ + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* For progressive mode, we only check that at least some DC data + * got sent for each component; the spec does not require that all bits + * of all coefficients be transmitted. Would it be wiser to enforce + * transmission of all coefficient bits?? + */ + for (ci = 0; ci < cinfo->num_components; ci++) { + if (last_bitpos[ci][0] < 0) + ERREXIT(cinfo, JERR_MISSING_DATA); + } +#endif + } else { + for (ci = 0; ci < cinfo->num_components; ci++) { + if (! component_sent[ci]) + ERREXIT(cinfo, JERR_MISSING_DATA); + } + } +} + +#endif /* C_MULTISCAN_FILES_SUPPORTED */ + + +LOCAL void +select_scan_parameters (j_compress_ptr cinfo) +/* Set up the scan parameters for the current scan */ +{ + int ci; + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if (cinfo->scan_info != NULL) { + /* Prepare for current scan --- the script is already validated */ + my_master_ptr master = (my_master_ptr) cinfo->master; + const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number; + + cinfo->comps_in_scan = scanptr->comps_in_scan; + for (ci = 0; ci < scanptr->comps_in_scan; ci++) { + cinfo->cur_comp_info[ci] = + &cinfo->comp_info[scanptr->component_index[ci]]; + } + cinfo->Ss = scanptr->Ss; + cinfo->Se = scanptr->Se; + cinfo->Ah = scanptr->Ah; + cinfo->Al = scanptr->Al; + } + else +#endif + { + /* Prepare for single sequential-JPEG scan containing all components */ + if (cinfo->num_components > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPS_IN_SCAN); + cinfo->comps_in_scan = cinfo->num_components; + for (ci = 0; ci < cinfo->num_components; ci++) { + cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci]; + } + cinfo->Ss = 0; + cinfo->Se = DCTSIZE2-1; + cinfo->Ah = 0; + cinfo->Al = 0; + } +} + + +LOCAL void +per_scan_setup (j_compress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = DCTSIZE; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * DCTSIZE; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } + + /* Convert restart specified in rows to actual MCU count. */ + /* Note that count must fit in 16 bits, so we provide limiting. */ + if (cinfo->restart_in_rows > 0) { + long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row; + cinfo->restart_interval = (unsigned int) MIN(nominal, 65535L); + } +} + + +/* + * Per-pass setup. + * This is called at the beginning of each pass. We determine which modules + * will be active during this pass and give them appropriate start_pass calls. + * We also set is_last_pass to indicate whether any more passes will be + * required. + */ + +METHODDEF void +prepare_for_pass (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + switch (master->pass_type) { + case main_pass: + /* Initial pass: will collect input data, and do either Huffman + * optimization or data output for the first scan. + */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (! cinfo->raw_data_in) { + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->downsample->start_pass) (cinfo); + (*cinfo->prep->start_pass) (cinfo, JBUF_PASS_THRU); + } + (*cinfo->fdct->start_pass) (cinfo); + (*cinfo->entropy->start_pass) (cinfo, cinfo->optimize_coding); + (*cinfo->coef->start_pass) (cinfo, + (master->total_passes > 1 ? + JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + if (cinfo->optimize_coding) { + /* No immediate data output; postpone writing frame/scan headers */ + master->pub.call_pass_startup = FALSE; + } else { + /* Will write frame/scan headers at first jpeg_write_scanlines call */ + master->pub.call_pass_startup = TRUE; + } + break; +#ifdef ENTROPY_OPT_SUPPORTED + case huff_opt_pass: + /* Do Huffman optimization for a scan after the first one. */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (cinfo->Ss != 0 || cinfo->Ah == 0 || cinfo->arith_code) { + (*cinfo->entropy->start_pass) (cinfo, TRUE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + master->pub.call_pass_startup = FALSE; + break; + } + /* Special case: Huffman DC refinement scans need no Huffman table + * and therefore we can skip the optimization pass for them. + */ + master->pass_type = output_pass; + master->pass_number++; + /*FALLTHROUGH*/ +#endif + case output_pass: + /* Do a data-output pass. */ + /* We need not repeat per-scan setup if prior optimization pass did it. */ + if (! cinfo->optimize_coding) { + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + } + (*cinfo->entropy->start_pass) (cinfo, FALSE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + /* We emit frame/scan headers now */ + if (master->scan_number == 0) + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); + master->pub.call_pass_startup = FALSE; + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + } + + master->pub.is_last_pass = (master->pass_number == master->total_passes-1); + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->total_passes; + } +} + + +/* + * Special start-of-pass hook. + * This is called by jpeg_write_scanlines if call_pass_startup is TRUE. + * In single-pass processing, we need this hook because we don't want to + * write frame/scan headers during jpeg_start_compress; we want to let the + * application write COM markers etc. between jpeg_start_compress and the + * jpeg_write_scanlines loop. + * In multi-pass processing, this routine is not used. + */ + +METHODDEF void +pass_startup (j_compress_ptr cinfo) +{ + cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */ + + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); +} + + +/* + * Finish up at end of pass. + */ + +METHODDEF void +finish_pass_master (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* The entropy coder always needs an end-of-pass call, + * either to analyze statistics or to flush its output buffer. + */ + (*cinfo->entropy->finish_pass) (cinfo); + + /* Update state for next pass */ + switch (master->pass_type) { + case main_pass: + /* next pass is either output of scan 0 (after optimization) + * or output of scan 1 (if no optimization). + */ + master->pass_type = output_pass; + if (! cinfo->optimize_coding) + master->scan_number++; + break; + case huff_opt_pass: + /* next pass is always output of current scan */ + master->pass_type = output_pass; + break; + case output_pass: + /* next pass is either optimization or output of next scan */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + master->scan_number++; + break; + } + + master->pass_number++; +} + + +/* + * Initialize master compression control. + */ + +GLOBAL void +jinit_c_master_control (j_compress_ptr cinfo, boolean transcode_only) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_comp_master)); + cinfo->master = (struct jpeg_comp_master *) master; + master->pub.prepare_for_pass = prepare_for_pass; + master->pub.pass_startup = pass_startup; + master->pub.finish_pass = finish_pass_master; + master->pub.is_last_pass = FALSE; + + /* Validate parameters, determine derived values */ + initial_setup(cinfo); + + if (cinfo->scan_info != NULL) { +#ifdef C_MULTISCAN_FILES_SUPPORTED + validate_script(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + cinfo->num_scans = 1; + } + + if (cinfo->progressive_mode) /* TEMPORARY HACK ??? */ + cinfo->optimize_coding = TRUE; /* assume default tables no good for progressive mode */ + + /* Initialize my private state */ + if (transcode_only) { + /* no main pass in transcoding */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + else + master->pass_type = output_pass; + } else { + /* for normal compression, first pass is always this type: */ + master->pass_type = main_pass; + } + master->scan_number = 0; + master->pass_number = 0; + if (cinfo->optimize_coding) + master->total_passes = cinfo->num_scans * 2; + else + master->total_passes = cinfo->num_scans; +} diff --git a/src/renderer/jpeg-6/jcomapi.c b/src/renderer/jpeg-6/jcomapi.c new file mode 100644 index 0000000..564e94e --- /dev/null +++ b/src/renderer/jpeg-6/jcomapi.c @@ -0,0 +1,118 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcomapi.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_abort (j_common_ptr cinfo) +{ + int pool; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) { + (*cinfo->mem->free_pool) (cinfo, pool); + } + + /* Reset overall state for possible reuse of object */ + cinfo->global_state = (cinfo->is_decompressor ? DSTATE_START : CSTATE_START); +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_destroy (j_common_ptr cinfo) +{ + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if (cinfo->mem != NULL) + (*cinfo->mem->self_destruct) (cinfo); + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL JQUANT_TBL * +jpeg_alloc_quant_table (j_common_ptr cinfo) +{ + JQUANT_TBL *tbl; + + tbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL JHUFF_TBL * +jpeg_alloc_huff_table (j_common_ptr cinfo) +{ + JHUFF_TBL *tbl; + + tbl = (JHUFF_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} diff --git a/src/renderer/jpeg-6/jconfig.h b/src/renderer/jpeg-6/jconfig.h new file mode 100644 index 0000000..81ac047 --- /dev/null +++ b/src/renderer/jpeg-6/jconfig.h @@ -0,0 +1,65 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* jconfig.wat --- jconfig.h for Watcom C/C++ on MS-DOS or OS/2. */ +/* see jconfig.doc for explanations */ + +#define HAVE_PROTOTYPES +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +/* #define void char */ +/* #define const */ +#define CHAR_IS_UNSIGNED +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_BSD_STRINGS +#undef NEED_SYS_TYPES_H +#undef NEED_FAR_POINTERS /* Watcom uses flat 32-bit addressing */ +#undef NEED_SHORT_EXTERNAL_NAMES +#undef INCOMPLETE_TYPES_BROKEN + +#define JDCT_DEFAULT JDCT_FLOAT +#define JDCT_FASTEST JDCT_FLOAT + +#ifdef JPEG_INTERNALS + +#undef RIGHT_SHIFT_IS_UNSIGNED + +#endif /* JPEG_INTERNALS */ + +#ifdef JPEG_CJPEG_DJPEG + +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +#undef RLE_SUPPORTED /* Utah RLE image file format */ +#define TARGA_SUPPORTED /* Targa image file format */ + +#undef TWO_FILE_COMMANDLINE /* optional */ +#define USE_SETMODE /* Needed to make one-file style work in Watcom */ +#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ +#undef DONT_USE_B_MODE +#undef PROGRESS_REPORT /* optional */ + +#endif /* JPEG_CJPEG_DJPEG */ diff --git a/src/renderer/jpeg-6/jcparam.c b/src/renderer/jpeg-6/jcparam.c new file mode 100644 index 0000000..f461e39 --- /dev/null +++ b/src/renderer/jpeg-6/jcparam.c @@ -0,0 +1,599 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcparam.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains optional default-setting code for the JPEG compressor. + * Applications do not have to use this file, but those that don't use it + * must know a lot more about the innards of the JPEG code. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Quantization table setup routines + */ + +GLOBAL void +jpeg_add_quant_table (j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline) +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ +{ + JQUANT_TBL ** qtblptr = & cinfo->quant_tbl_ptrs[which_tbl]; + int i; + long temp; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) cinfo); + + for (i = 0; i < DCTSIZE2; i++) { + temp = ((long) basic_table[i] * scale_factor + 50L) / 100L; + /* limit the values to the valid range */ + if (temp <= 0L) temp = 1L; + if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */ + if (force_baseline && temp > 255L) + temp = 255L; /* limit to baseline range if requested */ + (*qtblptr)->quantval[i] = (UINT16) temp; + } + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*qtblptr)->sent_table = FALSE; +} + + +GLOBAL void +jpeg_set_linear_quality (j_compress_ptr cinfo, int scale_factor, + boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables + * and a straight percentage-scaling quality scale. In most cases it's better + * to use jpeg_set_quality (below); this entry point is provided for + * applications that insist on a linear percentage scaling. + */ +{ + /* This is the sample quantization table given in the JPEG spec section K.1, + * but expressed in zigzag order (as are all of our quant. tables). + * The spec says that the values given produce "good" quality, and + * when divided by 2, "very good" quality. + */ + static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = { + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99 + }; + static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = { + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + }; + + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl, + scale_factor, force_baseline); + jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl, + scale_factor, force_baseline); +} + + +GLOBAL int +jpeg_quality_scaling (int quality) +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +{ + /* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if (quality <= 0) quality = 1; + if (quality > 100) quality = 100; + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause j_add_quant_table + * to make all the table entries 1 (hence, no quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if (quality < 50) + quality = 5000 / quality; + else + quality = 200 - quality*2; + + return quality; +} + + +GLOBAL void +jpeg_set_quality (j_compress_ptr cinfo, int quality, boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +{ + /* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling(quality); + + /* Set up standard quality tables */ + jpeg_set_linear_quality(cinfo, quality, force_baseline); +} + + +/* + * Huffman table setup routines + */ + +LOCAL void +add_huff_table (j_compress_ptr cinfo, + JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val) +/* Define a Huffman table */ +{ + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, val, SIZEOF((*htblptr)->huffval)); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*htblptr)->sent_table = FALSE; +} + + +LOCAL void +std_huff_tables (j_compress_ptr cinfo) +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ +{ + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + static const UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[0], + bits_dc_luminance, val_dc_luminance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[0], + bits_ac_luminance, val_ac_luminance); + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[1], + bits_dc_chrominance, val_dc_chrominance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[1], + bits_ac_chrominance, val_ac_chrominance); +} + + +/* + * Default parameter setup for compression. + * + * Applications that don't choose to use this routine must do their + * own setup of all these parameters. Alternately, you can call this + * to establish defaults and then alter parameters selectively. This + * is the recommended approach since, if we add any new parameters, + * your code will still work (they'll be set to reasonable defaults). + */ + +GLOBAL void +jpeg_set_defaults (j_compress_ptr cinfo) +{ + int i; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Allocate comp_info array large enough for maximum component count. + * Array is made permanent in case application wants to compress + * multiple images at same param settings. + */ + if (cinfo->comp_info == NULL) + cinfo->comp_info = (jpeg_component_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + MAX_COMPONENTS * SIZEOF(jpeg_component_info)); + + /* Initialize everything not dependent on the color space */ + + cinfo->data_precision = BITS_IN_JSAMPLE; + /* Set up two quantization tables using default quality of 75 */ + jpeg_set_quality(cinfo, 75, TRUE); + /* Set up two Huffman tables */ + std_huff_tables(cinfo); + + /* Initialize default arithmetic coding conditioning */ + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + + /* Default is no multiple-scan output */ + cinfo->scan_info = NULL; + cinfo->num_scans = 0; + + /* Expect normal source image, not raw downsampled data */ + cinfo->raw_data_in = FALSE; + + /* Use Huffman coding, not arithmetic coding, by default */ + cinfo->arith_code = FALSE; + + /* By default, don't do extra passes to optimize entropy coding */ + cinfo->optimize_coding = FALSE; + /* The standard Huffman tables are only valid for 8-bit data precision. + * If the precision is higher, force optimization on so that usable + * tables will be computed. This test can be removed if default tables + * are supplied that are valid for the desired precision. + */ + if (cinfo->data_precision > 8) + cinfo->optimize_coding = TRUE; + + /* By default, use the simpler non-cosited sampling alignment */ + cinfo->CCIR601_sampling = FALSE; + + /* No input smoothing */ + cinfo->smoothing_factor = 0; + + /* DCT algorithm preference */ + cinfo->dct_method = JDCT_DEFAULT; + + /* No restart markers */ + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + + /* Fill in default JFIF marker parameters. Note that whether the marker + * will actually be written is determined by jpeg_set_colorspace. + */ + cinfo->density_unit = 0; /* Pixel size is unknown by default */ + cinfo->X_density = 1; /* Pixel aspect ratio is square by default */ + cinfo->Y_density = 1; + + /* Choose JPEG colorspace based on input space, set defaults accordingly */ + + jpeg_default_colorspace(cinfo); +} + + +/* + * Select an appropriate JPEG colorspace for in_color_space. + */ + +GLOBAL void +jpeg_default_colorspace (j_compress_ptr cinfo) +{ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); + break; + case JCS_RGB: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_YCbCr: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_CMYK: + jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */ + break; + case JCS_YCCK: + jpeg_set_colorspace(cinfo, JCS_YCCK); + break; + case JCS_UNKNOWN: + jpeg_set_colorspace(cinfo, JCS_UNKNOWN); + break; + default: + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + } +} + + +/* + * Set the JPEG colorspace, and choose colorspace-dependent default values. + */ + +GLOBAL void +jpeg_set_colorspace (j_compress_ptr cinfo, J_COLOR_SPACE colorspace) +{ + jpeg_component_info * compptr; + int ci; + +#define SET_COMP(index,id,hsamp,vsamp,quant,dctbl,actbl) \ + (compptr = &cinfo->comp_info[index], \ + compptr->component_id = (id), \ + compptr->h_samp_factor = (hsamp), \ + compptr->v_samp_factor = (vsamp), \ + compptr->quant_tbl_no = (quant), \ + compptr->dc_tbl_no = (dctbl), \ + compptr->ac_tbl_no = (actbl) ) + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* For all colorspaces, we use Q and Huff tables 0 for luminance components, + * tables 1 for chrominance components. + */ + + cinfo->jpeg_color_space = colorspace; + + cinfo->write_JFIF_header = FALSE; /* No marker for non-JFIF colorspaces */ + cinfo->write_Adobe_marker = FALSE; /* write no Adobe marker by default */ + + switch (colorspace) { + case JCS_GRAYSCALE: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 1; + /* JFIF specifies component ID 1 */ + SET_COMP(0, 1, 1,1, 0, 0,0); + break; + case JCS_RGB: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag RGB */ + cinfo->num_components = 3; + SET_COMP(0, 0x52 /* 'R' */, 1,1, 0, 0,0); + SET_COMP(1, 0x47 /* 'G' */, 1,1, 0, 0,0); + SET_COMP(2, 0x42 /* 'B' */, 1,1, 0, 0,0); + break; + case JCS_YCbCr: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 3; + /* JFIF specifies component IDs 1,2,3 */ + /* We default to 2x2 subsamples of chrominance */ + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + break; + case JCS_CMYK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */ + cinfo->num_components = 4; + SET_COMP(0, 0x43 /* 'C' */, 1,1, 0, 0,0); + SET_COMP(1, 0x4D /* 'M' */, 1,1, 0, 0,0); + SET_COMP(2, 0x59 /* 'Y' */, 1,1, 0, 0,0); + SET_COMP(3, 0x4B /* 'K' */, 1,1, 0, 0,0); + break; + case JCS_YCCK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag YCCK */ + cinfo->num_components = 4; + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + SET_COMP(3, 4, 2,2, 0, 0,0); + break; + case JCS_UNKNOWN: + cinfo->num_components = cinfo->input_components; + if (cinfo->num_components < 1 || cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + for (ci = 0; ci < cinfo->num_components; ci++) { + SET_COMP(ci, ci, 1,1, 0, 0,0); + } + break; + default: + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + } +} + + +#ifdef C_PROGRESSIVE_SUPPORTED + +LOCAL jpeg_scan_info * +fill_a_scan (jpeg_scan_info * scanptr, int ci, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for specified component */ +{ + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_scans (jpeg_scan_info * scanptr, int ncomps, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for each component */ +{ + int ci; + + for (ci = 0; ci < ncomps; ci++) { + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al) +/* Support routine: generate interleaved DC scan if possible, else N scans */ +{ + int ci; + + if (ncomps <= MAX_COMPS_IN_SCAN) { + /* Single interleaved DC scan */ + scanptr->comps_in_scan = ncomps; + for (ci = 0; ci < ncomps; ci++) + scanptr->component_index[ci] = ci; + scanptr->Ss = scanptr->Se = 0; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } else { + /* Noninterleaved DC scan for each component */ + scanptr = fill_scans(scanptr, ncomps, 0, 0, Ah, Al); + } + return scanptr; +} + + +/* + * Create a recommended progressive-JPEG script. + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +GLOBAL void +jpeg_simple_progression (j_compress_ptr cinfo) +{ + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Figure space needed for script. Calculation must match code below! */ + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + nscans = 10; + } else { + /* All-purpose script for other color spaces. */ + if (ncomps > MAX_COMPS_IN_SCAN) + nscans = 6 * ncomps; /* 2 DC + 4 AC scans per component */ + else + nscans = 2 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } + + /* Allocate space for script. */ + /* We use permanent pool just in case application re-uses script. */ + scanptr = (jpeg_scan_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + nscans * SIZEOF(jpeg_scan_info)); + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + /* Initial DC scan */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + /* Initial AC scan: get some luma data out in a hurry */ + scanptr = fill_a_scan(scanptr, 0, 1, 5, 0, 2); + /* Chroma data is too small to be worth expending many scans on */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 0, 1); + /* Complete spectral selection for luma AC */ + scanptr = fill_a_scan(scanptr, 0, 6, 63, 0, 2); + /* Refine next bit of luma AC */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 2, 1); + /* Finish DC successive approximation */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + /* Finish AC successive approximation */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 1, 0); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 1, 0); + /* Luma bottom bit comes last since it's usually largest scan */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 1, 0); + } else { + /* All-purpose script for other color spaces. */ + /* Successive approximation first pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + scanptr = fill_scans(scanptr, ncomps, 1, 5, 0, 2); + scanptr = fill_scans(scanptr, ncomps, 6, 63, 0, 2); + /* Successive approximation second pass */ + scanptr = fill_scans(scanptr, ncomps, 1, 63, 2, 1); + /* Successive approximation final pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + scanptr = fill_scans(scanptr, ncomps, 1, 63, 1, 0); + } +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jcphuff.c b/src/renderer/jpeg-6/jcphuff.c new file mode 100644 index 0000000..28ea653 --- /dev/null +++ b/src/renderer/jpeg-6/jcphuff.c @@ -0,0 +1,853 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcphuff.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines for progressive JPEG. + * + * We do not support output suspension in this module, since the library + * currently does not allow multiple-scan files to be written with output + * suspension. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jchuff.c */ + +#ifdef C_PROGRESSIVE_SUPPORTED + +/* Expanded entropy encoder object for progressive Huffman encoding. */ + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + /* Mode flag: TRUE for optimization, FALSE for actual data output */ + boolean gather_statistics; + + /* Bit-level coding status. + * next_output_byte/free_in_buffer are local copies of cinfo->dest fields. + */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + j_compress_ptr cinfo; /* link to cinfo (needed for dump_buffer) */ + + /* Coding status for DC components */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + + /* Coding status for AC components */ + int ac_tbl_no; /* the table number of the single component */ + unsigned int EOBRUN; /* run length of EOBs */ + unsigned int BE; /* # of buffered correction bits before MCU */ + char * bit_buffer; /* buffer for correction bits (1 per char) */ + /* packing correction bits tightly would save some space but cost time... */ + + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan). + * Since any one scan codes only DC or only AC, we only need one set + * of tables, not one for DC and one for AC. + */ + c_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + /* Statistics tables for optimization; again, one set is enough */ + long * count_ptrs[NUM_HUFF_TBLS]; +} phuff_entropy_encoder; + +typedef phuff_entropy_encoder * phuff_entropy_ptr; + +/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit + * buffer can hold. Larger sizes may slightly improve compression, but + * 1000 is already well into the realm of overkill. + * The minimum safe size is 64 bits. + */ + +#define MAX_CORR_BITS 1000 /* Max # of correction bits I can buffer */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~0) << (16-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +/* Forward declarations */ +METHODDEF boolean encode_mcu_DC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean encode_mcu_AC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean encode_mcu_DC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean encode_mcu_AC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF void finish_pass_phuff JPP((j_compress_ptr cinfo)); +METHODDEF void finish_pass_gather_phuff JPP((j_compress_ptr cinfo)); + + +/* + * Initialize for a Huffman-compressed scan using progressive JPEG. + */ + +METHODDEF void +start_pass_phuff (j_compress_ptr cinfo, boolean gather_statistics) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + + entropy->cinfo = cinfo; + entropy->gather_statistics = gather_statistics; + + is_DC_band = (cinfo->Ss == 0); + + /* We assume jcmaster.c already validated the scan parameters. */ + + /* Select execution routines */ + if (cinfo->Ah == 0) { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_first; + else + entropy->pub.encode_mcu = encode_mcu_AC_first; + } else { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_refine; + else { + entropy->pub.encode_mcu = encode_mcu_AC_refine; + /* AC refinement needs a correction bit buffer */ + if (entropy->bit_buffer == NULL) + entropy->bit_buffer = (char *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + MAX_CORR_BITS * SIZEOF(char)); + } + } + if (gather_statistics) + entropy->pub.finish_pass = finish_pass_gather_phuff; + else + entropy->pub.finish_pass = finish_pass_phuff; + + /* Only DC coefficients may be interleaved, so cinfo->comps_in_scan = 1 + * for AC coefficients. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + if (tbl < 0 || tbl >= NUM_HUFF_TBLS || + (cinfo->dc_huff_tbl_ptrs[tbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo,JERR_NO_HUFF_TABLE, tbl); + } else { + entropy->ac_tbl_no = tbl = compptr->ac_tbl_no; + if (tbl < 0 || tbl >= NUM_HUFF_TBLS || + (cinfo->ac_huff_tbl_ptrs[tbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo,JERR_NO_HUFF_TABLE, tbl); + } + if (gather_statistics) { + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->count_ptrs[tbl] == NULL) + entropy->count_ptrs[tbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->count_ptrs[tbl], 257 * SIZEOF(long)); + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + if (is_DC_band) + jpeg_make_c_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[tbl], + & entropy->derived_tbls[tbl]); + else + jpeg_make_c_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[tbl], + & entropy->derived_tbls[tbl]); + } + } + + /* Initialize AC stuff */ + entropy->EOBRUN = 0; + entropy->BE = 0; + + /* Initialize bit buffer to empty */ + entropy->put_buffer = 0; + entropy->put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* Outputting bytes to the file. + * NB: these must be called only when actually outputting, + * that is, entropy->gather_statistics == FALSE. + */ + +/* Emit a byte */ +#define emit_byte(entropy,val) \ + { *(entropy)->next_output_byte++ = (JOCTET) (val); \ + if (--(entropy)->free_in_buffer == 0) \ + dump_buffer(entropy); } + + +LOCAL void +dump_buffer (phuff_entropy_ptr entropy) +/* Empty the output buffer; we do not support suspension in this module. */ +{ + struct jpeg_destination_mgr * dest = entropy->cinfo->dest; + + if (! (*dest->empty_output_buffer) (entropy->cinfo)) + ERREXIT(entropy->cinfo, JERR_CANT_SUSPEND); + /* After a successful buffer dump, must reset buffer pointers */ + entropy->next_output_byte = dest->next_output_byte; + entropy->free_in_buffer = dest->free_in_buffer; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL void +emit_bits (phuff_entropy_ptr entropy, unsigned int code, int size) +/* Emit some bits, unless we are in gather mode */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = entropy->put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + if (entropy->gather_statistics) + return; /* do nothing if we're only getting stats */ + + put_buffer &= (((INT32) 1)<put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(entropy, c); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(entropy, 0); + } + put_buffer <<= 8; + put_bits -= 8; + } + + entropy->put_buffer = put_buffer; /* update variables */ + entropy->put_bits = put_bits; +} + + +LOCAL void +flush_bits (phuff_entropy_ptr entropy) +{ + emit_bits(entropy, 0x7F, 7); /* fill any partial byte with ones */ + entropy->put_buffer = 0; /* and reset bit-buffer to empty */ + entropy->put_bits = 0; +} + + +/* + * Emit (or just count) a Huffman symbol. + */ + +INLINE +LOCAL void +emit_symbol (phuff_entropy_ptr entropy, int tbl_no, int symbol) +{ + if (entropy->gather_statistics) + entropy->count_ptrs[tbl_no][symbol]++; + else { + c_derived_tbl * tbl = entropy->derived_tbls[tbl_no]; + emit_bits(entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol]); + } +} + + +/* + * Emit bits from a correction bit buffer. + */ + +LOCAL void +emit_buffered_bits (phuff_entropy_ptr entropy, char * bufstart, + unsigned int nbits) +{ + if (entropy->gather_statistics) + return; /* no real work */ + + while (nbits > 0) { + emit_bits(entropy, (unsigned int) (*bufstart), 1); + bufstart++; + nbits--; + } +} + + +/* + * Emit any pending EOBRUN symbol. + */ + +LOCAL void +emit_eobrun (phuff_entropy_ptr entropy) +{ + register int temp, nbits; + + if (entropy->EOBRUN > 0) { /* if there is any pending EOBRUN */ + temp = entropy->EOBRUN; + nbits = 0; + while ((temp >>= 1)) + nbits++; + + emit_symbol(entropy, entropy->ac_tbl_no, nbits << 4); + if (nbits) + emit_bits(entropy, entropy->EOBRUN, nbits); + + entropy->EOBRUN = 0; + + /* Emit any buffered correction bits */ + emit_buffered_bits(entropy, entropy->bit_buffer, entropy->BE); + entropy->BE = 0; + } +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL void +emit_restart (phuff_entropy_ptr entropy, int restart_num) +{ + int ci; + + emit_eobrun(entropy); + + if (! entropy->gather_statistics) { + flush_bits(entropy); + emit_byte(entropy, 0xFF); + emit_byte(entropy, JPEG_RST0 + restart_num); + } + + if (entropy->cinfo->Ss == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < entropy->cinfo->comps_in_scan; ci++) + entropy->last_dc_val[ci] = 0; + } else { + /* Re-initialize all AC-related fields to 0 */ + entropy->EOBRUN = 0; + entropy->BE = 0; + } +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_DC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + int blkn, ci; + int Al = cinfo->Al; + JBLOCKROW block; + jpeg_component_info * compptr; + ISHIFT_TEMPS + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + temp2 = IRIGHT_SHIFT((int) ((*block)[0]), Al); + + /* DC differences are figured on the point-transformed values. */ + temp = temp2 - entropy->last_dc_val[ci]; + entropy->last_dc_val[ci] = temp2; + + /* Encode the DC coefficient difference per section G.1.2.1 */ + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + + /* Count/emit the Huffman-coded symbol for the number of bits */ + emit_symbol(entropy, compptr->dc_tbl_no, nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + emit_bits(entropy, (unsigned int) temp2, nbits); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_AC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + register int r, k; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ + + r = 0; /* r = run length of zeros */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = (*block)[jpeg_natural_order[k]]) == 0) { + r++; + continue; + } + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value; so the code is + * interwoven with finding the abs value (temp) and output bits (temp2). + */ + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ + temp2 = ~temp; + } else { + temp >>= Al; /* apply the point transform */ + temp2 = temp; + } + /* Watch out for case that nonzero coef is zero after point transform */ + if (temp == 0) { + r++; + continue; + } + + /* Emit any pending EOBRUN */ + if (entropy->EOBRUN > 0) + emit_eobrun(entropy); + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + emit_bits(entropy, (unsigned int) temp2, nbits); + + r = 0; /* reset zero run length */ + } + + if (r > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + if (entropy->EOBRUN == 0x7FFF) + emit_eobrun(entropy); /* force it out to avoid overflow */ + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF boolean +encode_mcu_DC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + int blkn; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* We simply emit the Al'th bit of the DC coefficient value. */ + temp = (*block)[0]; + emit_bits(entropy, (unsigned int) (temp >> Al), 1); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF boolean +encode_mcu_AC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + register int r, k; + int EOB; + char *BR_buffer; + unsigned int BR; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + int absvalues[DCTSIZE2]; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* It is convenient to make a pre-pass to determine the transformed + * coefficients' absolute values and the EOB position. + */ + EOB = 0; + for (k = cinfo->Ss; k <= Se; k++) { + temp = (*block)[jpeg_natural_order[k]]; + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if (temp < 0) + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + absvalues[k] = temp; /* save abs value for main pass */ + if (temp == 1) + EOB = k; /* EOB = index of last newly-nonzero coef */ + } + + /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ + + r = 0; /* r = run length of zeros */ + BR = 0; /* BR = count of buffered bits added now */ + BR_buffer = entropy->bit_buffer + entropy->BE; /* Append bits to buffer */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = absvalues[k]) == 0) { + r++; + continue; + } + + /* Emit any required ZRLs, but not if they can be folded into EOB */ + while (r > 15 && k <= EOB) { + /* emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + /* Emit ZRL */ + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + /* Emit buffered correction bits that must be associated with ZRL */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + } + + /* If the coef was previously nonzero, it only needs a correction bit. + * NOTE: a straight translation of the spec's figure G.7 would suggest + * that we also need to test r > 15. But if r > 15, we can only get here + * if k > EOB, which implies that this coefficient is not 1. + */ + if (temp > 1) { + /* The correction bit is the next bit of the absolute value. */ + BR_buffer[BR++] = (char) (temp & 1); + continue; + } + + /* Emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + 1); + + /* Emit output bit for newly-nonzero coef */ + temp = ((*block)[jpeg_natural_order[k]] < 0) ? 0 : 1; + emit_bits(entropy, (unsigned int) temp, 1); + + /* Emit buffered correction bits that must be associated with this code */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + r = 0; /* reset zero run length */ + } + + if (r > 0 || BR > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + entropy->BE += BR; /* concat my correction bits to older ones */ + /* We force out the EOB if we risk either: + * 1. overflow of the EOB counter; + * 2. overflow of the correction bit buffer during the next MCU. + */ + if (entropy->EOBRUN == 0x7FFF || entropy->BE > (MAX_CORR_BITS-DCTSIZE2+1)) + emit_eobrun(entropy); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed progressive scan. + */ + +METHODDEF void +finish_pass_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Flush out any buffered data */ + emit_eobrun(entropy); + flush_bits(entropy); + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did[NUM_HUFF_TBLS]; + + /* Flush out buffered data (all we care about is counting the EOB symbol) */ + emit_eobrun(entropy); + + is_DC_band = (cinfo->Ss == 0); + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did, SIZEOF(did)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + } else { + tbl = compptr->ac_tbl_no; + } + if (! did[tbl]) { + if (is_DC_band) + htblptr = & cinfo->dc_huff_tbl_ptrs[tbl]; + else + htblptr = & cinfo->ac_huff_tbl_ptrs[tbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->count_ptrs[tbl]); + did[tbl] = TRUE; + } + } +} + + +/* + * Module initialization routine for progressive Huffman entropy encoding. + */ + +GLOBAL void +jinit_phuff_encoder (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy; + int i; + + entropy = (phuff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(phuff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_phuff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + entropy->count_ptrs[i] = NULL; + } + entropy->bit_buffer = NULL; /* needed only in AC refinement scan */ +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jcprepct.c b/src/renderer/jpeg-6/jcprepct.c new file mode 100644 index 0000000..99ba0bd --- /dev/null +++ b/src/renderer/jpeg-6/jcprepct.c @@ -0,0 +1,395 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcprepct.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the compression preprocessing controller. + * This controller manages the color conversion, downsampling, + * and edge expansion steps. + * + * Most of the complexity here is associated with buffering input rows + * as required by the downsampler. See the comments at the head of + * jcsample.c for the downsampler's needs. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* At present, jcsample.c can request context rows only for smoothing. + * In the future, we might also need context rows for CCIR601 sampling + * or other more-complex downsampling procedures. The code to support + * context rows should be compiled only if needed. + */ +#ifdef INPUT_SMOOTHING_SUPPORTED +#define CONTEXT_ROWS_SUPPORTED +#endif + + +/* + * For the simple (no-context-row) case, we just need to buffer one + * row group's worth of pixels for the downsampling step. At the bottom of + * the image, we pad to a full row group by replicating the last pixel row. + * The downsampler's last output row is then replicated if needed to pad + * out to a full iMCU row. + * + * When providing context rows, we must buffer three row groups' worth of + * pixels. Three row groups are physically allocated, but the row pointer + * arrays are made five row groups high, with the extra pointers above and + * below "wrapping around" to point to the last and first real row groups. + * This allows the downsampler to access the proper context rows. + * At the top and bottom of the image, we create dummy context rows by + * copying the first or last real pixel row. This copying could be avoided + * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the + * trouble on the compression side. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_prep_controller pub; /* public fields */ + + /* Downsampling input buffer. This buffer holds color-converted data + * until we have enough to do a downsample step. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + JDIMENSION rows_to_go; /* counts rows remaining in source image */ + int next_buf_row; /* index of next row to store in color_buf */ + +#ifdef CONTEXT_ROWS_SUPPORTED /* only needed for context case */ + int this_row_group; /* starting row index of group to process */ + int next_buf_stop; /* downsample when we reach this index */ +#endif +} my_prep_controller; + +typedef my_prep_controller * my_prep_ptr; + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_prep (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + + if (pass_mode != JBUF_PASS_THRU) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Initialize total-height counter for detecting bottom of image */ + prep->rows_to_go = cinfo->image_height; + /* Mark the conversion buffer empty */ + prep->next_buf_row = 0; +#ifdef CONTEXT_ROWS_SUPPORTED + /* Preset additional state variables for context mode. + * These aren't used in non-context mode, so we needn't test which mode. + */ + prep->this_row_group = 0; + /* Set next_buf_stop to stop after two row groups have been read in. */ + prep->next_buf_stop = 2 * cinfo->max_v_samp_factor; +#endif +} + + +/* + * Expand an image vertically from height input_rows to height output_rows, + * by duplicating the bottom row. + */ + +LOCAL void +expand_bottom_edge (JSAMPARRAY image_data, JDIMENSION num_cols, + int input_rows, int output_rows) +{ + register int row; + + for (row = input_rows; row < output_rows; row++) { + jcopy_sample_rows(image_data, input_rows-1, image_data, row, + 1, num_cols); + } +} + + +/* + * Process some data in the simple no-context case. + * + * Preprocessor output data is counted in "row groups". A row group + * is defined to be v_samp_factor sample rows of each component. + * Downsampling will produce this much data from each max_v_samp_factor + * input rows. + */ + +METHODDEF void +pre_process_data (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while (*in_row_ctr < in_rows_avail && + *out_row_group_ctr < out_row_groups_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = cinfo->max_v_samp_factor - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + /* If at bottom of image, pad to fill the conversion buffer. */ + if (prep->rows_to_go == 0 && + prep->next_buf_row < cinfo->max_v_samp_factor) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, cinfo->max_v_samp_factor); + } + prep->next_buf_row = cinfo->max_v_samp_factor; + } + /* If we've filled the conversion buffer, empty it. */ + if (prep->next_buf_row == cinfo->max_v_samp_factor) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, (JDIMENSION) 0, + output_buf, *out_row_group_ctr); + prep->next_buf_row = 0; + (*out_row_group_ctr)++; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if (prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + expand_bottom_edge(output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) (*out_row_group_ctr * compptr->v_samp_factor), + (int) (out_row_groups_avail * compptr->v_samp_factor)); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +#ifdef CONTEXT_ROWS_SUPPORTED + +/* + * Process some data in the context case. + */ + +METHODDEF void +pre_process_context (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + int buf_height = cinfo->max_v_samp_factor * 3; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while (*out_row_group_ctr < out_row_groups_avail) { + if (*in_row_ctr < in_rows_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = prep->next_buf_stop - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + /* Pad at top of image, if first time through */ + if (prep->rows_to_go == cinfo->image_height) { + for (ci = 0; ci < cinfo->num_components; ci++) { + int row; + for (row = 1; row <= cinfo->max_v_samp_factor; row++) { + jcopy_sample_rows(prep->color_buf[ci], 0, + prep->color_buf[ci], -row, + 1, cinfo->image_width); + } + } + } + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + } else { + /* Return for more data, unless we are at the bottom of the image. */ + if (prep->rows_to_go != 0) + break; + } + /* If at bottom of image, pad to fill the conversion buffer. */ + if (prep->rows_to_go == 0 && + prep->next_buf_row < prep->next_buf_stop) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, prep->next_buf_stop); + } + prep->next_buf_row = prep->next_buf_stop; + } + /* If we've gotten enough data, downsample a row group. */ + if (prep->next_buf_row == prep->next_buf_stop) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, + (JDIMENSION) prep->this_row_group, + output_buf, *out_row_group_ctr); + (*out_row_group_ctr)++; + /* Advance pointers with wraparound as necessary. */ + prep->this_row_group += cinfo->max_v_samp_factor; + if (prep->this_row_group >= buf_height) + prep->this_row_group = 0; + if (prep->next_buf_row >= buf_height) + prep->next_buf_row = 0; + prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if (prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + expand_bottom_edge(output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) (*out_row_group_ctr * compptr->v_samp_factor), + (int) (out_row_groups_avail * compptr->v_samp_factor)); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +/* + * Create the wrapped-around downsampling input buffer needed for context mode. + */ + +LOCAL void +create_context_buffer (j_compress_ptr cinfo) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int rgroup_height = cinfo->max_v_samp_factor; + int ci, i; + jpeg_component_info * compptr; + JSAMPARRAY true_buffer, fake_buffer; + + /* Grab enough space for fake row pointers for all the components; + * we need five row groups' worth of pointers for each component. + */ + fake_buffer = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (cinfo->num_components * 5 * rgroup_height) * + SIZEOF(JSAMPROW)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate the actual buffer space (3 row groups) for this component. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + true_buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) (3 * rgroup_height)); + /* Copy true buffer row pointers into the middle of the fake row array */ + MEMCOPY(fake_buffer + rgroup_height, true_buffer, + 3 * rgroup_height * SIZEOF(JSAMPROW)); + /* Fill in the above and below wraparound pointers */ + for (i = 0; i < rgroup_height; i++) { + fake_buffer[i] = true_buffer[2 * rgroup_height + i]; + fake_buffer[4 * rgroup_height + i] = true_buffer[i]; + } + prep->color_buf[ci] = fake_buffer + rgroup_height; + fake_buffer += 5 * rgroup_height; /* point to space for next component */ + } +} + +#endif /* CONTEXT_ROWS_SUPPORTED */ + + +/* + * Initialize preprocessing controller. + */ + +GLOBAL void +jinit_c_prep_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_prep_ptr prep; + int ci; + jpeg_component_info * compptr; + + if (need_full_buffer) /* safety check */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + prep = (my_prep_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_prep_controller)); + cinfo->prep = (struct jpeg_c_prep_controller *) prep; + prep->pub.start_pass = start_pass_prep; + + /* Allocate the color conversion buffer. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + if (cinfo->downsample->need_context_rows) { + /* Set up to provide context rows */ +#ifdef CONTEXT_ROWS_SUPPORTED + prep->pub.pre_process_data = pre_process_context; + create_context_buffer(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* No context, just make it tall enough for one row group */ + prep->pub.pre_process_data = pre_process_data; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prep->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/src/renderer/jpeg-6/jcsample.c b/src/renderer/jpeg-6/jcsample.c new file mode 100644 index 0000000..168e947 --- /dev/null +++ b/src/renderer/jpeg-6/jcsample.c @@ -0,0 +1,543 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jcsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains downsampling routines. + * + * Downsampling input data is counted in "row groups". A row group + * is defined to be max_v_samp_factor pixel rows of each component, + * from which the downsampler produces v_samp_factor sample rows. + * A single row group is processed in each call to the downsampler module. + * + * The downsampler is responsible for edge-expansion of its output data + * to fill an integral number of DCT blocks horizontally. The source buffer + * may be modified if it is helpful for this purpose (the source buffer is + * allocated wide enough to correspond to the desired output width). + * The caller (the prep controller) is responsible for vertical padding. + * + * The downsampler may request "context rows" by setting need_context_rows + * during startup. In this case, the input arrays will contain at least + * one row group's worth of pixels above and below the passed-in data; + * the caller will create dummy rows at image top and bottom by replicating + * the first or last real pixel row. + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + * + * The downsampling algorithm used here is a simple average of the source + * pixels covered by the output pixel. The hi-falutin sampling literature + * refers to this as a "box filter". In general the characteristics of a box + * filter are not very good, but for the specific cases we normally use (1:1 + * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not + * nearly so bad. If you intend to use other sampling ratios, you'd be well + * advised to improve this code. + * + * A simple input-smoothing capability is provided. This is mainly intended + * for cleaning up color-dithered GIF input files (if you find it inadequate, + * we suggest using an external filtering program such as pnmconvol). When + * enabled, each input pixel P is replaced by a weighted sum of itself and its + * eight neighbors. P's weight is 1-8*SF and each neighbor's weight is SF, + * where SF = (smoothing_factor / 1024). + * Currently, smoothing is only supported for 2h2v sampling factors. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to downsample a single component */ +typedef JMETHOD(void, downsample1_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data)); + +/* Private subobject */ + +typedef struct { + struct jpeg_downsampler pub; /* public fields */ + + /* Downsampling method pointers, one per component */ + downsample1_ptr methods[MAX_COMPONENTS]; +} my_downsampler; + +typedef my_downsampler * my_downsample_ptr; + + +/* + * Initialize for a downsampling pass. + */ + +METHODDEF void +start_pass_downsample (j_compress_ptr cinfo) +{ + /* no work for now */ +} + + +/* + * Expand a component horizontally from width input_cols to width output_cols, + * by duplicating the rightmost samples. + */ + +LOCAL void +expand_right_edge (JSAMPARRAY image_data, int num_rows, + JDIMENSION input_cols, JDIMENSION output_cols) +{ + register JSAMPROW ptr; + register JSAMPLE pixval; + register int count; + int row; + int numcols = (int) (output_cols - input_cols); + + if (numcols > 0) { + for (row = 0; row < num_rows; row++) { + ptr = image_data[row] + input_cols; + pixval = ptr[-1]; /* don't need GETJSAMPLE() here */ + for (count = numcols; count > 0; count--) + *ptr++ = pixval; + } + } +} + + +/* + * Do downsampling for a whole row group (all components). + * + * In this version we simply downsample each component independently. + */ + +METHODDEF void +sep_downsample (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, JDIMENSION out_row_group_index) +{ + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int ci; + jpeg_component_info * compptr; + JSAMPARRAY in_ptr, out_ptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + in_ptr = input_buf[ci] + in_row_index; + out_ptr = output_buf[ci] + (out_row_group_index * compptr->v_samp_factor); + (*downsample->methods[ci]) (cinfo, compptr, in_ptr, out_ptr); + } +} + + +/* + * Downsample pixel values of a single component. + * One row group is processed per call. + * This version handles arbitrary integral sampling ratios, without smoothing. + * Note that this version is not actually used for customary sampling ratios. + */ + +METHODDEF void +int_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v; + JDIMENSION outcol, outcol_h; /* outcol_h == outcol*h_expand */ + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + JSAMPROW inptr, outptr; + INT32 outvalue; + + h_expand = cinfo->max_h_samp_factor / compptr->h_samp_factor; + v_expand = cinfo->max_v_samp_factor / compptr->v_samp_factor; + numpix = h_expand * v_expand; + numpix2 = numpix/2; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * h_expand); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + for (outcol = 0, outcol_h = 0; outcol < output_cols; + outcol++, outcol_h += h_expand) { + outvalue = 0; + for (v = 0; v < v_expand; v++) { + inptr = input_data[inrow+v] + outcol_h; + for (h = 0; h < h_expand; h++) { + outvalue += (INT32) GETJSAMPLE(*inptr++); + } + } + *outptr++ = (JSAMPLE) ((outvalue + numpix2) / numpix); + } + inrow += v_expand; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * without smoothing. + */ + +METHODDEF void +fullsize_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + /* Copy the data */ + jcopy_sample_rows(input_data, 0, output_data, 0, + cinfo->max_v_samp_factor, cinfo->image_width); + /* Edge-expand */ + expand_right_edge(output_data, cinfo->max_v_samp_factor, + cinfo->image_width, compptr->width_in_blocks * DCTSIZE); +} + + +/* + * Downsample pixel values of a single component. + * This version handles the common case of 2:1 horizontal and 1:1 vertical, + * without smoothing. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + bias = 0; /* bias = 0,1,0,1,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr) + GETJSAMPLE(inptr[1]) + + bias) >> 1); + bias ^= 1; /* 0=>1, 1=>0 */ + inptr += 2; + } + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * without smoothing. + */ + +METHODDEF void +h2v2_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + bias = 1; /* bias = 1,2,1,2,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]) + + bias) >> 2); + bias ^= 3; /* 1=>2, 2=>1 */ + inptr0 += 2; inptr1 += 2; + } + inrow += 2; + } +} + + +#ifdef INPUT_SMOOTHING_SUPPORTED + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * with smoothing. One row of context is required. + */ + +METHODDEF void +h2v2_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols * 2); + + /* We don't bother to form the individual "smoothed" input pixel values; + * we can directly compute the output which is the average of the four + * smoothed values. Each of the four member pixels contributes a fraction + * (1-8*SF) to its own smoothed image and a fraction SF to each of the three + * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final + * output. The four corner-adjacent neighbor pixels contribute a fraction + * SF to just one smoothed pixel, or SF/4 to the final output; while the + * eight edge-adjacent neighbors contribute SF to each of two smoothed + * pixels, or SF/2 overall. In order to use integer arithmetic, these + * factors are scaled by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 16384 - cinfo->smoothing_factor * 80; /* scaled (1-5*SF)/4 */ + neighscale = cinfo->smoothing_factor * 16; /* scaled SF/4 */ + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + above_ptr = input_data[inrow-1]; + below_ptr = input_data[inrow+2]; + + /* Special case for first column: pretend column -1 is same as column 0 */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[2]); + neighsum += neighsum; + neighsum += GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[2]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + /* sum of pixels directly mapped to this output element */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + /* sum of edge-neighbor pixels */ + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[2]); + /* The edge-neighbors count twice as much as corner-neighbors */ + neighsum += neighsum; + /* Add in the corner-neighbors */ + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[2]); + /* form final output scaled up by 2^16 */ + membersum = membersum * memberscale + neighsum * neighscale; + /* round, descale and output it */ + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[1]); + neighsum += neighsum; + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[1]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + inrow += 2; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * with smoothing. One row of context is required. + */ + +METHODDEF void +fullsize_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info *compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + int colsum, lastcolsum, nextcolsum; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols); + + /* Each of the eight neighbor pixels contributes a fraction SF to the + * smoothed pixel, while the main pixel contributes (1-8*SF). In order + * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 65536L - cinfo->smoothing_factor * 512L; /* scaled 1-8*SF */ + neighscale = cinfo->smoothing_factor * 64; /* scaled SF */ + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + above_ptr = input_data[outrow-1]; + below_ptr = input_data[outrow+1]; + + /* Special case for first column */ + colsum = GETJSAMPLE(*above_ptr++) + GETJSAMPLE(*below_ptr++) + + GETJSAMPLE(*inptr); + membersum = GETJSAMPLE(*inptr++); + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = colsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + membersum = GETJSAMPLE(*inptr++); + above_ptr++; below_ptr++; + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + colsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + } +} + +#endif /* INPUT_SMOOTHING_SUPPORTED */ + + +/* + * Module initialization routine for downsampling. + * Note that we must select a routine for each component. + */ + +GLOBAL void +jinit_downsampler (j_compress_ptr cinfo) +{ + my_downsample_ptr downsample; + int ci; + jpeg_component_info * compptr; + boolean smoothok = TRUE; + + downsample = (my_downsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_downsampler)); + cinfo->downsample = (struct jpeg_downsampler *) downsample; + downsample->pub.start_pass = start_pass_downsample; + downsample->pub.downsample = sep_downsample; + downsample->pub.need_context_rows = FALSE; + + if (cinfo->CCIR601_sampling) + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* Verify we can handle the sampling factors, and set up method pointers */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = fullsize_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = fullsize_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { + smoothok = FALSE; + downsample->methods[ci] = h2v1_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor * 2 == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = h2v2_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = h2v2_downsample; + } else if ((cinfo->max_h_samp_factor % compptr->h_samp_factor) == 0 && + (cinfo->max_v_samp_factor % compptr->v_samp_factor) == 0) { + smoothok = FALSE; + downsample->methods[ci] = int_downsample; + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + } + +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor && !smoothok) + TRACEMS(cinfo, 0, JTRC_SMOOTH_NOTIMPL); +#endif +} diff --git a/src/renderer/jpeg-6/jdapimin.c b/src/renderer/jpeg-6/jdapimin.c new file mode 100644 index 0000000..611d4f4 --- /dev/null +++ b/src/renderer/jpeg-6/jdapimin.c @@ -0,0 +1,422 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_decompress (j_decompress_ptr cinfo) +{ + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct)); + cinfo->err = err; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + jinit_marker_reader(cinfo); + + /* And initialize the overall input controller. */ + jinit_input_controller(cinfo); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL void +jpeg_destroy_decompress (j_decompress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_decompress (j_decompress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL void +jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) +{ + if (marker_code == JPEG_COM) + cinfo->marker->process_COM = routine; + else if (marker_code >= JPEG_APP0 && marker_code <= JPEG_APP0+15) + cinfo->marker->process_APPn[marker_code-JPEG_APP0] = routine; + else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} + + +/* + * Set default decompression parameters. + */ + +LOCAL void +default_decompress_parms (j_decompress_ptr cinfo) +{ + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch (cinfo->num_components) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if (cinfo->saw_JFIF_marker) { + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + } else if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if (cid0 == 1 && cid1 == 2 && cid2 == 3) + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + else if (cid0 == 82 && cid1 == 71 && cid2 == 66) + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + else { + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL int +jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) +{ + int retcode; + + if (cinfo->global_state != DSTATE_START && + cinfo->global_state != DSTATE_INHEADER) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + retcode = jpeg_consume_input(cinfo); + + switch (retcode) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if (require_image) /* Complain if application wanted an image */ + ERREXIT(cinfo, JERR_NO_IMAGE); + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL int +jpeg_consume_input (j_decompress_ptr cinfo) +{ + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch (cinfo->global_state) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + (*cinfo->inputctl->reset_input_controller) (cinfo); + /* Initialize application's data source module */ + (*cinfo->src->init_source) (cinfo); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms(cinfo); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + break; + default: + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL boolean +jpeg_input_complete (j_decompress_ptr cinfo) +{ + /* Check for valid jpeg object */ + if (cinfo->global_state < DSTATE_START || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL boolean +jpeg_has_multiple_scans (j_decompress_ptr cinfo) +{ + /* Only valid after jpeg_read_header completes */ + if (cinfo->global_state < DSTATE_READY || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_decompress (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) { + /* Terminate final pass of non-buffered mode */ + if (cinfo->output_scanline < cinfo->output_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state == DSTATE_BUFIMAGE) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state != DSTATE_STOPPING) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read until EOI */ + while (! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + /* Do final cleanup */ + (*cinfo->src->term_source) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); + return TRUE; +} diff --git a/src/renderer/jpeg-6/jdapistd.c b/src/renderer/jpeg-6/jdapistd.c new file mode 100644 index 0000000..6a9790c --- /dev/null +++ b/src/renderer/jpeg-6/jdapistd.c @@ -0,0 +1,299 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdapistd.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL boolean output_pass_setup JPP((j_decompress_ptr cinfo)); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_start_decompress (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress(cinfo); + if (cinfo->buffered_image) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if (cinfo->global_state == DSTATE_PRELOAD) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if (cinfo->inputctl->has_multiple_scans) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return FALSE; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if (cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup(cinfo); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL boolean +output_pass_setup (j_decompress_ptr cinfo) +{ + if (cinfo->global_state != DSTATE_PRESCAN) { + /* First call: do pass setup */ + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while (cinfo->master->is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while (cinfo->output_scanline < cinfo->output_height) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0); + if (cinfo->output_scanline == last_scanline) + return FALSE; /* No progress made, must suspend */ + } + /* Finish up dummy pass, and set up for another one */ + (*cinfo->master->finish_output_pass) (cinfo); + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL JDIMENSION +jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) +{ + JDIMENSION row_ctr; + + if (cinfo->global_state != DSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Process some data */ + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL JDIMENSION +jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != DSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + if (max_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Decompress directly into user's buffer. */ + if (! (*cinfo->coef->decompress_data) (cinfo, data)) + return 0; /* suspension forced, can do nothing more */ + + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL boolean +jpeg_start_output (j_decompress_ptr cinfo, int scan_number) +{ + if (cinfo->global_state != DSTATE_BUFIMAGE && + cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Limit scan number to valid range */ + if (scan_number <= 0) + scan_number = 1; + if (cinfo->inputctl->eoi_reached && + scan_number > cinfo->input_scan_number) + scan_number = cinfo->input_scan_number; + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup(cinfo); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_output (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_BUFPOST; + } else if (cinfo->global_state != DSTATE_BUFPOST) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read markers looking for SOS or EOI */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jdatadst.c b/src/renderer/jpeg-6/jdatadst.c new file mode 100644 index 0000000..1a6e804 --- /dev/null +++ b/src/renderer/jpeg-6/jdatadst.c @@ -0,0 +1,175 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdatadst.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains compression data destination routines for the case of + * emitting JPEG data to a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * destination manager. + * IMPORTANT: we assume that fwrite() will correctly transcribe an array of + * JOCTETs into 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + FILE * outfile; /* target stream */ + JOCTET * buffer; /* start of buffer */ +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +METHODDEF void +init_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + /* Allocate the output buffer --- it will be released when done with image */ + dest->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + OUTPUT_BUF_SIZE * SIZEOF(JOCTET)); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +METHODDEF boolean +empty_output_buffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) != + (size_t) OUTPUT_BUF_SIZE) + ERREXIT(cinfo, JERR_FILE_WRITE); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; + + return TRUE; +} + + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if (datacount > 0) { + if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount) + ERREXIT(cinfo, JERR_FILE_WRITE); + } + fflush(dest->outfile); + /* Make sure we wrote the output file OK */ + if (ferror(dest->outfile)) + ERREXIT(cinfo, JERR_FILE_WRITE); +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +GLOBAL void +jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; +} diff --git a/src/renderer/jpeg-6/jdatasrc.c b/src/renderer/jpeg-6/jdatasrc.c new file mode 100644 index 0000000..f53cd87 --- /dev/null +++ b/src/renderer/jpeg-6/jdatasrc.c @@ -0,0 +1,228 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdatasrc.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + unsigned char *infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF void +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF boolean +fill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + memcpy( src->buffer, src->infile, INPUT_BUF_SIZE ); + + src->infile += INPUT_BUF_SIZE; + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = INPUT_BUF_SIZE; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF void +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL void +jpeg_stdio_src (j_decompress_ptr cinfo, unsigned char *infile) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF(JOCTET)); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} diff --git a/src/renderer/jpeg-6/jdcoefct.c b/src/renderer/jpeg-6/jdcoefct.c new file mode 100644 index 0000000..d89f9eb --- /dev/null +++ b/src/renderer/jpeg-6/jdcoefct.c @@ -0,0 +1,749 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdcoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub; /* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + +/* Forward declarations */ +METHODDEF int decompress_onepass + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF int decompress_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL boolean smoothing_ok JPP((j_decompress_ptr cinfo)); +METHODDEF int decompress_smooth_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif + + +LOCAL void +start_iMCU_row (j_decompress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row (input side) */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF void +start_input_pass (j_decompress_ptr cinfo) +{ + cinfo->input_iMCU_row = 0; + start_iMCU_row(cinfo); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF void +start_output_pass (j_decompress_ptr cinfo) +{ +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if (coef->pub.coef_arrays != NULL) { + if (cinfo->do_block_smoothing && smoothing_ok(cinfo)) + coef->pub.decompress_data = decompress_smooth_data; + else + coef->pub.decompress_data = decompress_data; + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF int +decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far((void FAR *) coef->MCU_buffer[0], + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[ci] + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (cinfo->input_iMCU_row < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + output_col = start_col; + for (xindex = 0; xindex < useful_width; xindex++) { + (*inverse_DCT) (cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + output_ptr, output_col); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF int +dummy_consume_data (j_decompress_ptr cinfo) +{ + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF int +consume_data (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF int +decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number < cinfo->output_scan_number || + (cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL boolean +smoothing_ok (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info *compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if (! cinfo->progressive_mode || cinfo->coef_bits == NULL) + return FALSE; + + /* Allocate latch area if not already done */ + if (coef->coef_bits_latch == NULL) + coef->coef_bits_latch = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + (SAVED_COEFS * SIZEOF(int))); + coef_bits_latch = coef->coef_bits_latch; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* All components' quantization values must already be latched. */ + if ((qtable = compptr->quant_table) == NULL) + return FALSE; + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + for (coefi = 0; coefi <= 5; coefi++) { + if (qtable->quantval[coefi] == 0) + return FALSE; + } + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if (coef_bits[0] < 0) + return FALSE; + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for (coefi = 1; coefi <= 5; coefi++) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if (coef_bits[coefi] != 0) + smoothing_useful = TRUE; + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF int +decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int *coef_bits; + JQUANT_TBL *quanttbl; + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if (cinfo->input_scan_number == cinfo->output_scan_number) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; + if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) + break; + } + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2; /* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + access_rows = block_rows; /* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if (cinfo->output_iMCU_row > 0) { + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[1]; + Q10 = quanttbl->quantval[2]; + Q20 = quanttbl->quantval[3]; + Q11 = quanttbl->quantval[4]; + Q02 = quanttbl->quantval[5]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + if (first_row && block_row == 0) + prev_block_row = buffer_ptr; + else + prev_block_row = buffer[block_row-1]; + if (last_row && block_row == block_rows-1) + next_block_row = buffer_ptr; + else + next_block_row = buffer[block_row+1]; + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for (block_num = 0; block_num <= last_block_column; block_num++) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + /* Update DC values */ + if (block_num < last_block_column) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + num = 36 * Q00 * (DC4 - DC6); + if (num >= 0) { + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if (cinfo->progressive_mode) + access_rows *= 3; +#endif + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) access_rows); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + } +} diff --git a/src/renderer/jpeg-6/jdcolor.c b/src/renderer/jpeg-6/jdcolor.c new file mode 100644 index 0000000..0b2ce45 --- /dev/null +++ b/src/renderer/jpeg-6/jdcolor.c @@ -0,0 +1,391 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdcolor.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub; /* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter; + +typedef my_color_deconverter * my_cconvert_ptr; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + */ + +LOCAL void +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + cconvert->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +ycc_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS))]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF void +null_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while (--num_rows >= 0) { + for (ci = 0; ci < num_components; ci++) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for (count = num_cols; count > 0; count--) { + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF void +grayscale_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width); +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF void +ycck_cmyk_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS)))]; + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +start_pass_dcolor (j_decompress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL void +jinit_color_deconverter (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + int ci; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_deconverter)); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_RGB: + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->num_components < 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = grayscale_convert; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for (ci = 1; ci < cinfo->num_components; ci++) + cinfo->comp_info[ci].component_needed = FALSE; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if (cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if (cinfo->jpeg_color_space == JCS_YCCK) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: + /* Permit null conversion to same output space */ + if (cinfo->out_color_space == cinfo->jpeg_color_space) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert; + } else /* unsupported non-null conversion */ + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + } + + if (cinfo->quantize_colors) + cinfo->output_components = 1; /* single colormapped output component */ + else + cinfo->output_components = cinfo->out_color_components; +} diff --git a/src/renderer/jpeg-6/jdct.h b/src/renderer/jpeg-6/jdct.h new file mode 100644 index 0000000..9a984df --- /dev/null +++ b/src/renderer/jpeg-6/jdct.h @@ -0,0 +1,200 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdct.h + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + * implementations use an array of type FAST_FLOAT, instead.) + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_scaled_size * DCT_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN void jpeg_fdct_islow JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_ifast JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_float JPP((FAST_FLOAT * data)); + +EXTERN void jpeg_idct_islow + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_ifast + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_float + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_4x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_2x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_1x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ((INT32) 1) +#define CONST_SCALE (ONE << CONST_BITS) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16(var,const) ((var) * (const)) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) +#endif diff --git a/src/renderer/jpeg-6/jddctmgr.c b/src/renderer/jpeg-6/jddctmgr.c new file mode 100644 index 0000000..c2421a8 --- /dev/null +++ b/src/renderer/jpeg-6/jddctmgr.c @@ -0,0 +1,294 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jddctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub; /* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF void +start_pass (j_decompress_ptr cinfo) +{ + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info *compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Select the proper IDCT routine for this component's scaling */ + switch (compptr->DCT_scaled_size) { +#ifdef IDCT_SCALING_SUPPORTED + case 1: + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 2: + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 4: + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; +#endif + case DCTSIZE: + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + break; + default: + ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if (! compptr->component_needed || idct->cur_method[ci] == method) + continue; + qtbl = compptr->quant_table; + if (qtbl == NULL) /* happens if no data yet for component */ + continue; + idct->cur_method[ci] = method; + switch (method) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored in natural order as ints. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for (i = 0; i < DCTSIZE2; i++) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[jpeg_zigzag_order[i]]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. The multipliers are stored in natural order. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for (i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i]), + CONST_BITS-IFAST_SCALE_BITS); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * The multipliers are stored in natural order. + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ((double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col]); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL void +jinit_inverse_dct (j_decompress_ptr cinfo) +{ + my_idct_ptr idct; + int ci; + jpeg_component_info *compptr; + + idct = (my_idct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_idct_controller)); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(multiplier_table)); + MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/src/renderer/jpeg-6/jdhuff.c b/src/renderer/jpeg-6/jdhuff.c new file mode 100644 index 0000000..c5895ed --- /dev/null +++ b/src/renderer/jpeg-6/jdhuff.c @@ -0,0 +1,598 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdhuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2-1 || + cinfo->Ah != 0 || cinfo->Al != 0) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + cinfo->dc_huff_tbl_ptrs[dctbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS || + cinfo->ac_huff_tbl_ptrs[actbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_d_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + & entropy->ac_derived_tbls[actbl]); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jdphuff.c. + */ + +GLOBAL void +jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, JHUFF_TBL * htbl, + d_derived_tbl ** pdtbl) +{ + d_derived_tbl *dtbl; + int p, i, l, si; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (d_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(d_derived_tbl)); + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for (l = 1; l <= 16; l++) { + if (htbl->bits[l]) { + dtbl->valptr[l] = p; /* huffval[] index of 1st symbol of code length l */ + dtbl->mincode[l] = huffcode[p]; /* minimum code of length l */ + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits)); + + p = 0; + for (l = 1; l <= HUFF_LOOKAHEAD; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++, p++) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); + for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS (BIT_BUF_SIZE-7) +#endif + + +GLOBAL boolean +jpeg_fill_bit_buffer (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits) +/* Load up the bit buffer to a depth of at least nbits */ +{ + /* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + register int c; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + + while (bits_left < MIN_GET_BITS) { + /* Attempt to read a byte */ + if (state->unread_marker != 0) + goto no_more_data; /* can't advance past a marker */ + + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if (c == 0xFF) { + do { + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. */ + /* Better put it back for use later */ + state->unread_marker = c; + + no_more_data: + /* There should be enough bits still left in the data segment; */ + /* if so, just break out of the outer while loop. */ + if (bits_left >= nbits) + break; + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * Note that this code will be repeated for each byte demanded + * for the rest of the segment. We use a nonvolatile flag to ensure + * that only one warning message appears. + */ + if (! *(state->printed_eod_ptr)) { + WARNMS(state->cinfo, JWRN_HIT_MARKER); + *(state->printed_eod_ptr) = TRUE; + } + c = 0; /* insert a zero byte into bit buffer */ + } + } + + /* OK, load c into get_buffer */ + get_buffer = (get_buffer << 8) | c; + bits_left += 8; + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL int +jpeg_huff_decode (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits) +{ + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER(*state, l, return -1); + code = GET_BITS(l); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while (code > htbl->maxcode[l]) { + code <<= 1; + CHECK_BIT_BUFFER(*state, 1, return -1); + code |= GET_BITS(1); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if (l > 16) { + WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ htbl->valptr[l] + + ((int) (code - htbl->mincode[l])) ]; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF boolean +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * dctbl; + d_derived_tbl * actbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + dctbl = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + actbl = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, dctbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Shortcut if component's values are not interesting */ + if (! compptr->component_needed) + goto skip_ACs; + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + (*block)[0] = (JCOEF) s; + + /* Do we need to decode the AC coefficients for this component? */ + if (compptr->DCT_scaled_size > 1) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + break; + k += 15; + } + } + + } else { +skip_ACs: + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL void +jinit_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/src/renderer/jpeg-6/jdhuff.h b/src/renderer/jpeg-6/jdhuff.h new file mode 100644 index 0000000..3c1afb3 --- /dev/null +++ b/src/renderer/jpeg-6/jdhuff.h @@ -0,0 +1,226 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdhuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 mincode[17]; /* smallest code of length k */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + int valptr[17]; /* huffval[] index of 1st symbol of length k */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't do this with + * something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + boolean printed_eod; /* flag to suppress multiple warning msgs */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* current data source state */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + int unread_marker; /* nonzero if we have hit a marker */ + /* bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* pointers needed by jpeg_fill_bit_buffer */ + j_decompress_ptr cinfo; /* back link to decompress master record */ + boolean * printed_eod_ptr; /* => flag in permanent state */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + br_state.unread_marker = cinfop->unread_marker; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; \ + br_state.printed_eod_ptr = & permstate.printed_eod + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + cinfop->unread_marker = br_state.unread_marker; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN boolean jpeg_fill_bit_buffer JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits)); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + +/* Out-of-line case for Huffman code fetching */ +EXTERN int jpeg_huff_decode JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits)); diff --git a/src/renderer/jpeg-6/jdinput.c b/src/renderer/jpeg-6/jdinput.c new file mode 100644 index 0000000..0782c45 --- /dev/null +++ b/src/renderer/jpeg-6/jdinput.c @@ -0,0 +1,405 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdinput.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub; /* public fields */ + + boolean inheaders; /* TRUE until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF int consume_markers JPP((j_decompress_ptr cinfo)); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + +LOCAL void +initial_setup (j_decompress_ptr cinfo) +/* Called once, when first SOS marker is reached */ +{ + int ci; + jpeg_component_info *compptr; + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + * In the full decompressor, this will be overridden by jdmaster.c; + * but in the transcoder, jdmaster.c is not used, so we must do it here. + */ + cinfo->min_DCT_scaled_size = DCTSIZE; + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + /* Decide whether file contains multiple scans */ + if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) + cinfo->inputctl->has_multiple_scans = TRUE; + else + cinfo->inputctl->has_multiple_scans = FALSE; +} + + +LOCAL void +per_scan_setup (j_decompress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL void +latch_quant_tables (j_decompress_ptr cinfo) +{ + int ci, qtblno; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if (compptr->quant_table != NULL) + continue; + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + /* OK, save away the quantization table */ + qtbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(JQUANT_TBL)); + MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF void +start_input_pass (j_decompress_ptr cinfo) +{ + per_scan_setup(cinfo); + latch_quant_tables(cinfo); + (*cinfo->entropy->start_pass) (cinfo); + (*cinfo->coef->start_input_pass) (cinfo); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF void +finish_input_pass (j_decompress_ptr cinfo) +{ + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + */ + +METHODDEF int +consume_markers (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + + val = (*cinfo->marker->read_markers) (cinfo); + + switch (val) { + case JPEG_REACHED_SOS: /* Found SOS */ + if (inputctl->inheaders) { /* 1st SOS */ + initial_setup(cinfo); + inputctl->inheaders = FALSE; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapi.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if (! inputctl->pub.has_multiple_scans) + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + start_input_pass(cinfo); + } + break; + case JPEG_REACHED_EOI: /* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_NO_SOS); + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if (cinfo->output_scan_number > cinfo->input_scan_number) + cinfo->output_scan_number = cinfo->input_scan_number; + } + break; + case JPEG_SUSPENDED: + break; + } + + return val; +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF void +reset_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; + /* Reset other modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->marker->reset_marker_reader) (cinfo); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_input_controller)); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; +} diff --git a/src/renderer/jpeg-6/jdmainct.c b/src/renderer/jpeg-6/jdmainct.c new file mode 100644 index 0000000..39d0c42 --- /dev/null +++ b/src/renderer/jpeg-6/jdmainct.c @@ -0,0 +1,536 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub; /* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF void process_data_simple_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +METHODDEF void process_data_context_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void process_data_crank_post + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#endif + + +LOCAL void +alloc_funny_pointers (j_decompress_ptr cinfo) +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + main->xbuffer[0] = (JSAMPIMAGE) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + main->xbuffer[1] = main->xbuffer[0] + cinfo->num_components; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + xbuf += rgroup; /* want one row group at negative offsets */ + main->xbuffer[0][ci] = xbuf; + xbuf += rgroup * (M + 4); + main->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL void +make_funny_pointers (j_decompress_ptr cinfo) +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = main->buffer[ci]; + for (i = 0; i < rgroup * (M + 2); i++) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for (i = 0; i < rgroup * 2; i++) { + xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i]; + xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL void +set_wraparound_pointers (j_decompress_ptr cinfo) +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = main->xbuffer[0][ci]; + xbuf1 = main->xbuffer[1][ci]; + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; + xbuf0[rgroup*(M+2) + i] = xbuf0[i]; + xbuf1[rgroup*(M+2) + i] = xbuf1[i]; + } + } +} + + +LOCAL void +set_bottom_pointers (j_decompress_ptr cinfo) +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight); + if (rows_left == 0) rows_left = iMCUheight; + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if (ci == 0) { + main->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = main->xbuffer[main->whichptr][ci]; + for (i = 0; i < rgroup * 2; i++) { + xbuf[rows_left + i] = xbuf[rows_left-1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->upsample->need_context_rows) { + main->pub.process_data = process_data_context_main; + make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ + main->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + main->context_state = CTX_PREPARE_FOR_IMCU; + main->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + main->pub.process_data = process_data_simple_main; + } + main->buffer_full = FALSE; /* Mark buffer empty */ + main->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + main->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF void +process_data_simple_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, main->buffer)) + return; /* suspension forced, can do nothing more */ + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + (*cinfo->post->post_process_data) (cinfo, main->buffer, + &main->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if (main->rowgroup_ctr >= rowgroups_avail) { + main->buffer_full = FALSE; + main->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF void +process_data_context_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if (! main->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, + main->xbuffer[main->whichptr])) + return; /* suspension forced, can do nothing more */ + main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + main->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch (main->context_state) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main->rowgroup_ctr < main->rowgroups_avail) + return; /* Need to suspend */ + main->context_state = CTX_PREPARE_FOR_IMCU; + if (*out_row_ctr >= out_rows_avail) + return; /* Postprocessor exactly filled output buf */ + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + main->rowgroup_ctr = 0; + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if (main->iMCU_row_ctr == cinfo->total_iMCU_rows) + set_bottom_pointers(cinfo); + main->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], + &main->rowgroup_ctr, main->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main->rowgroup_ctr < main->rowgroups_avail) + return; /* Need to suspend */ + /* After the first iMCU, change wraparound pointers to normal state */ + if (main->iMCU_row_ctr == 1) + set_wraparound_pointers(cinfo); + /* Prepare to load new iMCU row using other xbuffer list */ + main->whichptr ^= 1; /* 0=>1 or 1=>0 */ + main->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + main->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); + main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); + main->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF void +process_data_crank_post (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_main_ptr main; + int ci, rgroup, ngroups; + jpeg_component_info *compptr; + + main = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_d_main_controller *) main; + main->pub.start_pass = start_pass_main; + + if (need_full_buffer) /* shouldn't happen */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if (cinfo->upsample->need_context_rows) { + if (cinfo->min_DCT_scaled_size < 2) /* unsupported, see comments above */ + ERREXIT(cinfo, JERR_NOTIMPL); + alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_scaled_size; + } + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + main->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) (rgroup * ngroups)); + } +} diff --git a/src/renderer/jpeg-6/jdmarker.c b/src/renderer/jpeg-6/jdmarker.c new file mode 100644 index 0000000..caf734b --- /dev/null +++ b/src/renderer/jpeg-6/jdmarker.c @@ -0,0 +1,1076 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS(cinfo) \ + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC(cinfo) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD(cinfo) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL(cinfo,action) \ + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + INPUT_RELOAD(cinfo); \ + } \ + bytes_in_buffer-- + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = GETJOCTET(*next_input_byte++); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + MAKE_BYTE_AVAIL(cinfo,action); \ + V += GETJOCTET(*next_input_byte++); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters can + * fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments, + * but we use skip_input_data to get past those, and thereby put the problem + * on the source manager's shoulders. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL boolean +get_soi (j_decompress_ptr cinfo) +/* Process an SOI marker */ +{ + int i; + + TRACEMS(cinfo, 1, JTRC_SOI); + + if (cinfo->marker->saw_SOI) + ERREXIT(cinfo, JERR_SOI_DUPLICATE); + + /* Reset all parameters that are defined to be reset by SOI */ + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->density_unit = 0; /* set default JFIF APP0 values */ + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL boolean +get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) +/* Process a SOFn marker */ +{ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE); + INPUT_BYTE(cinfo, cinfo->num_components, return FALSE); + + length -= 8; + + TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); + + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_DUPLICATE); + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + if (length != (cinfo->num_components * 3)) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->component_index = ci; + INPUT_BYTE(cinfo, compptr->component_id, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + compptr->h_samp_factor = (c >> 4) & 15; + compptr->v_samp_factor = (c ) & 15; + INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); + + TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_sos (j_decompress_ptr cinfo) +/* Process a SOS marker */ +{ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + if (! cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOS_NO_SOF); + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */ + + if (length != (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + TRACEMS1(cinfo, 1, JTRC_SOS, n); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for (i = 0; i < n; i++) { + INPUT_BYTE(cinfo, cc, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (cc == compptr->component_id) + goto id_found; + } + + ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); + + id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = (c >> 4) & 15; + compptr->ac_tbl_no = (c ) & 15; + + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ss = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Se = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ah = (c >> 4) & 15; + cinfo->Al = (c ) & 15; + + TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +get_app0 (j_decompress_ptr cinfo) +/* Process an APP0 marker */ +{ +#define JFIF_LEN 14 + INT32 length; + UINT8 b[JFIF_LEN]; + int buffp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if a JFIF APP0 marker is present */ + + if (length >= JFIF_LEN) { + for (buffp = 0; buffp < JFIF_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= JFIF_LEN; + + if (b[0]==0x4A && b[1]==0x46 && b[2]==0x49 && b[3]==0x46 && b[4]==0) { + /* Found JFIF APP0 marker: check version */ + /* Major version must be 1, anything else signals an incompatible change. + * We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec. + * Minor version should be 0..2, but process anyway if newer. + */ + if (b[5] != 1) + WARNMS2(cinfo, JWRN_JFIF_MAJOR, b[5], b[6]); + else if (b[6] > 2) + TRACEMS2(cinfo, 1, JTRC_JFIF_MINOR, b[5], b[6]); + /* Save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->density_unit = b[7]; + cinfo->X_density = (b[8] << 8) + b[9]; + cinfo->Y_density = (b[10] << 8) + b[11]; + TRACEMS3(cinfo, 1, JTRC_JFIF, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + if (b[12] | b[13]) + TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, b[12], b[13]); + if (length != ((INT32) b[12] * (INT32) b[13] * (INT32) 3)) + TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) length); + } else { + /* Start of APP0 does not match "JFIF" */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length + JFIF_LEN); + } + } else { + /* Too short to be JFIF marker */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +METHODDEF boolean +get_app14 (j_decompress_ptr cinfo) +/* Process an APP14 marker */ +{ +#define ADOBE_LEN 12 + INT32 length; + UINT8 b[ADOBE_LEN]; + int buffp; + unsigned int version, flags0, flags1, transform; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if an Adobe APP14 marker is present */ + + if (length >= ADOBE_LEN) { + for (buffp = 0; buffp < ADOBE_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= ADOBE_LEN; + + if (b[0]==0x41 && b[1]==0x64 && b[2]==0x6F && b[3]==0x62 && b[4]==0x65) { + /* Found Adobe APP14 marker */ + version = (b[5] << 8) + b[6]; + flags0 = (b[7] << 8) + b[8]; + flags1 = (b[9] << 8) + b[10]; + transform = b[11]; + TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe" */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length + ADOBE_LEN); + } + } else { + /* Too short to be Adobe marker */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +LOCAL boolean +get_dac (j_decompress_ptr cinfo) +/* Process a DAC marker */ +{ + INT32 length; + int index, val; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + INPUT_BYTE(cinfo, val, return FALSE); + + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_DAC, index, val); + + if (index < 0 || index >= (2*NUM_ARITH_TBLS)) + ERREXIT1(cinfo, JERR_DAC_INDEX, index); + + if (index >= NUM_ARITH_TBLS) { /* define AC table */ + cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); + cinfo->arith_dc_U[index] = (UINT8) (val >> 4); + if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) + ERREXIT1(cinfo, JERR_DAC_VALUE, val); + } + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dht (j_decompress_ptr cinfo) +/* Process a DHT marker */ +{ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DHT, index); + + bits[0] = 0; + count = 0; + for (i = 1; i <= 16; i++) { + INPUT_BYTE(cinfo, bits[i], return FALSE); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); + + if (count > 256 || ((INT32) count) > length) + ERREXIT(cinfo, JERR_DHT_COUNTS); + + for (i = 0; i < count; i++) + INPUT_BYTE(cinfo, huffval[i], return FALSE); + + length -= count; + + if (index & 0x10) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dqt (j_decompress_ptr cinfo) +/* Process a DQT marker */ +{ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, n, return FALSE); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2(cinfo, 1, JTRC_DQT, n, prec); + + if (n >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, n); + + if (cinfo->quant_tbl_ptrs[n] == NULL) + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + INPUT_2BYTES(cinfo, tmp, return FALSE); + else + INPUT_BYTE(cinfo, tmp, return FALSE); + quant_ptr->quantval[i] = (UINT16) tmp; + } + + for (i = 0; i < DCTSIZE2; i += 8) { + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i ], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + } + + length -= DCTSIZE2+1; + if (prec) length -= DCTSIZE2; + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dri (j_decompress_ptr cinfo) +/* Process a DRI marker */ +{ + INT32 length; + unsigned int tmp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + if (length != 4) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_2BYTES(cinfo, tmp, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DRI, tmp); + + cinfo->restart_interval = tmp; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +skip_variable (j_decompress_ptr cinfo) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + INT32 length; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); + + INPUT_SYNC(cinfo); /* do before skip_input_data */ + (*cinfo->src->skip_input_data) (cinfo, (long) length - 2L); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL boolean +next_marker (j_decompress_ptr cinfo) +{ + int c; + INPUT_VARS(cinfo); + + for (;;) { + INPUT_BYTE(cinfo, c, return FALSE); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while (c != 0xFF) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC(cinfo); + INPUT_BYTE(cinfo, c, return FALSE); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE(cinfo, c, return FALSE); + } while (c == 0xFF); + if (c != 0) + break; /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC(cinfo); + } + + if (cinfo->marker->discarded_bytes != 0) { + WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +first_marker (j_decompress_ptr cinfo) +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ +{ + int c, c2; + INPUT_VARS(cinfo); + + INPUT_BYTE(cinfo, c, return FALSE); + INPUT_BYTE(cinfo, c2, return FALSE); + if (c != 0xFF || c2 != (int) M_SOI) + ERREXIT2(cinfo, JERR_NO_SOI, c, c2); + + cinfo->unread_marker = c2; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF int +read_markers (j_decompress_ptr cinfo) +{ + /* Outer loop repeats once for each marker. */ + for (;;) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if (cinfo->unread_marker == 0) { + if (! cinfo->marker->saw_SOI) { + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; + } else { + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch (cinfo->unread_marker) { + case M_SOI: + if (! get_soi(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + if (! get_sof(cinfo, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF2: /* Progressive, Huffman */ + if (! get_sof(cinfo, TRUE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if (! get_sof(cinfo, FALSE, TRUE)) + return JPEG_SUSPENDED; + break; + + case M_SOF10: /* Progressive, arithmetic */ + if (! get_sof(cinfo, TRUE, TRUE)) + return JPEG_SUSPENDED; + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); + break; + + case M_SOS: + if (! get_sos(cinfo)) + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS(cinfo, 1, JTRC_EOI); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if (! get_dac(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DHT: + if (! get_dht(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DQT: + if (! get_dqt(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DRI: + if (! get_dri(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (! (*cinfo->marker->process_APPn[cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_COM: + if (! (*cinfo->marker->process_COM) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if (! skip_variable(cinfo)) + return JPEG_SUSPENDED; + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF boolean +read_restart_marker (j_decompress_ptr cinfo) +{ + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if (cinfo->unread_marker == 0) { + if (! next_marker(cinfo)) + return FALSE; + } + + if (cinfo->unread_marker == + ((int) M_RST0 + cinfo->marker->next_restart_num)) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1(cinfo, 2, JTRC_RST, cinfo->marker->next_restart_num); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if (! (*cinfo->src->resync_to_restart) (cinfo, + cinfo->marker->next_restart_num)) + return FALSE; + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL boolean +jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) +{ + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;;) { + if (marker < (int) M_SOF0) + action = 2; /* invalid marker */ + else if (marker < (int) M_RST0 || marker > (int) M_RST7) + action = 3; /* valid non-restart marker */ + else { + if (marker == ((int) M_RST0 + ((desired+1) & 7)) || + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ + else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ + else + action = 1; /* desired restart or too far away */ + } + TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); + switch (action) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if (! next_marker(cinfo)) + return FALSE; + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF void +reset_marker_reader (j_decompress_ptr cinfo) +{ + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + cinfo->marker->saw_SOI = FALSE; /* set internal state too */ + cinfo->marker->saw_SOF = FALSE; + cinfo->marker->discarded_bytes = 0; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_marker_reader (j_decompress_ptr cinfo) +{ + int i; + + /* Create subobject in permanent pool */ + cinfo->marker = (struct jpeg_marker_reader *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(struct jpeg_marker_reader)); + /* Initialize method pointers */ + cinfo->marker->reset_marker_reader = reset_marker_reader; + cinfo->marker->read_markers = read_markers; + cinfo->marker->read_restart_marker = read_restart_marker; + cinfo->marker->process_COM = skip_variable; + for (i = 0; i < 16; i++) + cinfo->marker->process_APPn[i] = skip_variable; + cinfo->marker->process_APPn[0] = get_app0; + cinfo->marker->process_APPn[14] = get_app14; + /* Reset marker processing state */ + reset_marker_reader(cinfo); +} diff --git a/src/renderer/jpeg-6/jdmaster.c b/src/renderer/jpeg-6/jdmaster.c new file mode 100644 index 0000000..dbb124f --- /dev/null +++ b/src/renderer/jpeg-6/jdmaster.c @@ -0,0 +1,581 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL boolean +use_merged_upsample (j_decompress_ptr cinfo) +{ +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling) + return FALSE; + /* jdmerge.c only supports YCC=>RGB color conversion */ + if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE) + return FALSE; + /* and it only handles 2h1v or 2h2v sampling ratios */ + if (cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1) + return FALSE; + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if (cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size) + return FALSE; + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL void +jpeg_calc_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ +#if 0 // JDC: commented out to remove warning + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if (cinfo->scale_num * 8 <= cinfo->scale_denom) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 8L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 8L); + cinfo->min_DCT_scaled_size = 1; + } else if (cinfo->scale_num * 4 <= cinfo->scale_denom) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 4L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 4L); + cinfo->min_DCT_scaled_size = 2; + } else if (cinfo->scale_num * 2 <= cinfo->scale_denom) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 2L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 2L); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + int ssize = cinfo->min_DCT_scaled_size; + while (ssize < DCTSIZE && + (compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && + (compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * + (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * + (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = (cinfo->quantize_colors ? 1 : + cinfo->out_color_components); + + /* See if upsampler will want to emit more than one row at a time */ + if (use_merged_upsample(cinfo)) + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + else + cinfo->rec_outbuf_height = 1; +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL void +prepare_range_limit_table (j_decompress_ptr cinfo) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO(table + (2 * (MAXJSAMPLE+1)), + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL void +master_selection (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions(cinfo); + prepare_range_limit_table(cinfo); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample(cinfo); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if (! cinfo->quantize_colors || ! cinfo->buffered_image) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if (cinfo->quantize_colors) { + if (cinfo->raw_data_out) + ERREXIT(cinfo, JERR_NOTIMPL); + /* 2-pass quantizer only works in 3-component color space. */ + if (cinfo->out_color_components != 3) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if (cinfo->colormap != NULL) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if (cinfo->enable_1pass_quant) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer(cinfo); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer(cinfo); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if (! cinfo->raw_data_out) { + if (master->using_merged_upsample) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler(cinfo); /* does color conversion too */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + jinit_color_deconverter(cinfo); + jinit_upsampler(cinfo); + } + jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant); + } + /* Inverse DCT */ + jinit_inverse_dct(cinfo); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller(cinfo, use_c_buffer); + + if (! cinfo->raw_data_out) + jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if (cinfo->progress != NULL && ! cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapi.c will crank the pass to completion.) + */ + +METHODDEF void +prepare_for_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (master->pub.is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + (*cinfo->cquantize->start_pass) (cinfo, FALSE); + (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if (cinfo->quantize_colors && cinfo->colormap == NULL) { + /* Select new quantization method */ + if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if (cinfo->enable_1pass_quant) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT(cinfo, JERR_MODE_CHANGE); + } + } + (*cinfo->idct->start_pass) (cinfo); + (*cinfo->coef->start_output_pass) (cinfo); + if (! cinfo->raw_data_out) { + if (! master->using_merged_upsample) + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->upsample->start_pass) (cinfo); + if (cinfo->quantize_colors) + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->post->start_pass) (cinfo, + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + } + } + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + (master->pub.is_dummy_pass ? 2 : 1); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) { + cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF void +finish_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (cinfo->quantize_colors) + (*cinfo->cquantize->finish_pass) (cinfo); + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL void +jpeg_new_colormap (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_BUFIMAGE) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + (*cinfo->cquantize->new_color_map) (cinfo); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL void +jinit_master_decompress (j_decompress_ptr cinfo) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_decomp_master)); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection(cinfo); +} diff --git a/src/renderer/jpeg-6/jdmerge.c b/src/renderer/jpeg-6/jdmerge.c new file mode 100644 index 0000000..f1c6501 --- /dev/null +++ b/src/renderer/jpeg-6/jdmerge.c @@ -0,0 +1,424 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdmerge.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains code for merged upsampling/color conversion. + * + * This file combines functions from jdsample.c and jdcolor.c; + * read those files first to understand what's going on. + * + * When the chroma components are to be upsampled by simple replication + * (ie, box filtering), we can save some work in color conversion by + * calculating all the output pixels corresponding to a pair of chroma + * samples at one time. In the conversion equations + * R = Y + K1 * Cr + * G = Y + K2 * Cb + K3 * Cr + * B = Y + K4 * Cb + * only the Y term varies among the group of pixels corresponding to a pair + * of chroma samples, so the rest of the terms can be calculated just once. + * At typical sampling ratios, this eliminates half or three-quarters of the + * multiplications needed for color conversion. + * + * This file currently provides implementations for the following cases: + * YCbCr => RGB color conversion only. + * Sampling ratios of 2h1v or 2h2v. + * No scaling needed at upsample time. + * Corner-aligned (non-CCIR601) sampling alignment. + * Other special cases could be added, but in most applications these are + * the only common cases. (For uncommon cases we fall back on the more + * general code in jdsample.c and jdcolor.c.) + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef UPSAMPLE_MERGING_SUPPORTED + + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Pointer to routine to do actual upsampling/conversion of one row group */ + JMETHOD(void, upmethod, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf)); + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ + + /* For 2:1 vertical sampling, we produce two output rows at a time. + * We need a "spare" row buffer to hold the second output row if the + * application provides just a one-row buffer; we also use the spare + * to discard the dummy last row if the image height is odd. + */ + JSAMPROW spare_row; + boolean spare_full; /* T if spare buffer is occupied */ + + JDIMENSION out_row_width; /* samples per output row */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + * This is taken directly from jdcolor.c; see that file for more info. + */ + +LOCAL void +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int i; + INT32 x; + SHIFT_TEMPS + + upsample->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + upsample->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + upsample->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + upsample->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + upsample->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + upsample->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + upsample->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF void +start_pass_merged_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the spare buffer empty */ + upsample->spare_full = FALSE; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * The control routine just handles the row buffering considerations. + */ + +METHODDEF void +merged_2v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +/* 2:1 vertical sampling case: may need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPROW work_ptrs[2]; + JDIMENSION num_rows; /* number of rows returned to caller */ + + if (upsample->spare_full) { + /* If we have a spare row saved from a previous cycle, just return it. */ + jcopy_sample_rows(& upsample->spare_row, 0, output_buf + *out_row_ctr, 0, + 1, upsample->out_row_width); + num_rows = 1; + upsample->spare_full = FALSE; + } else { + /* Figure number of rows to return to caller. */ + num_rows = 2; + /* Not more than the distance to the end of the image. */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + /* Create output pointer array for upsampler. */ + work_ptrs[0] = output_buf[*out_row_ctr]; + if (num_rows > 1) { + work_ptrs[1] = output_buf[*out_row_ctr + 1]; + } else { + work_ptrs[1] = upsample->spare_row; + upsample->spare_full = TRUE; + } + /* Now do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, work_ptrs); + } + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (! upsample->spare_full) + (*in_row_group_ctr)++; +} + + +METHODDEF void +merged_1v_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +/* 1:1 vertical sampling case: much easier, never need a spare row. */ +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Just do the upsampling. */ + (*upsample->upmethod) (cinfo, input_buf, *in_row_group_ctr, + output_buf + *out_row_ctr); + /* Adjust counts */ + (*out_row_ctr)++; + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by the control routines to do + * the actual upsampling/conversion. One row group is processed per call. + * + * Note: since we may be writing directly into application-supplied buffers, + * we have to be honest about the output width; we can't assume the buffer + * has been rounded up to an even width. + */ + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 1:1 vertical. + */ + +METHODDEF void +h2v1_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr; + JSAMPROW inptr0, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr0 = input_buf[0][in_row_group_ctr]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr = output_buf[0]; + /* Loop for each pair of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 2 Y values and emit 2 pixels */ + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr0++); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + outptr += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr0); + outptr[RGB_RED] = range_limit[y + cred]; + outptr[RGB_GREEN] = range_limit[y + cgreen]; + outptr[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Upsample and color convert for the case of 2:1 horizontal and 2:1 vertical. + */ + +METHODDEF void +h2v2_merged_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_group_ctr, + JSAMPARRAY output_buf) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + register int y, cred, cgreen, cblue; + int cb, cr; + register JSAMPROW outptr0, outptr1; + JSAMPROW inptr00, inptr01, inptr1, inptr2; + JDIMENSION col; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = upsample->Cr_r_tab; + int * Cbbtab = upsample->Cb_b_tab; + INT32 * Crgtab = upsample->Cr_g_tab; + INT32 * Cbgtab = upsample->Cb_g_tab; + SHIFT_TEMPS + + inptr00 = input_buf[0][in_row_group_ctr*2]; + inptr01 = input_buf[0][in_row_group_ctr*2 + 1]; + inptr1 = input_buf[1][in_row_group_ctr]; + inptr2 = input_buf[2][in_row_group_ctr]; + outptr0 = output_buf[0]; + outptr1 = output_buf[1]; + /* Loop for each group of output pixels */ + for (col = cinfo->output_width >> 1; col > 0; col--) { + /* Do the chroma part of the calculation */ + cb = GETJSAMPLE(*inptr1++); + cr = GETJSAMPLE(*inptr2++); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + /* Fetch 4 Y values and emit 4 pixels */ + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr00++); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + outptr0 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + y = GETJSAMPLE(*inptr01++); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + outptr1 += RGB_PIXELSIZE; + } + /* If image width is odd, do the last output column separately */ + if (cinfo->output_width & 1) { + cb = GETJSAMPLE(*inptr1); + cr = GETJSAMPLE(*inptr2); + cred = Crrtab[cr]; + cgreen = (int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], SCALEBITS); + cblue = Cbbtab[cb]; + y = GETJSAMPLE(*inptr00); + outptr0[RGB_RED] = range_limit[y + cred]; + outptr0[RGB_GREEN] = range_limit[y + cgreen]; + outptr0[RGB_BLUE] = range_limit[y + cblue]; + y = GETJSAMPLE(*inptr01); + outptr1[RGB_RED] = range_limit[y + cred]; + outptr1[RGB_GREEN] = range_limit[y + cgreen]; + outptr1[RGB_BLUE] = range_limit[y + cblue]; + } +} + + +/* + * Module initialization routine for merged upsampling/color conversion. + * + * NB: this is called under the conditions determined by use_merged_upsample() + * in jdmaster.c. That routine MUST correspond to the actual capabilities + * of this module; no safety checks are made here. + */ + +GLOBAL void +jinit_merged_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_merged_upsample; + upsample->pub.need_context_rows = FALSE; + + upsample->out_row_width = cinfo->output_width * cinfo->out_color_components; + + if (cinfo->max_v_samp_factor == 2) { + upsample->pub.upsample = merged_2v_upsample; + upsample->upmethod = h2v2_merged_upsample; + /* Allocate a spare row buffer */ + upsample->spare_row = (JSAMPROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) (upsample->out_row_width * SIZEOF(JSAMPLE))); + } else { + upsample->pub.upsample = merged_1v_upsample; + upsample->upmethod = h2v1_merged_upsample; + /* No spare row needed */ + upsample->spare_row = NULL; + } + + build_ycc_rgb_table(cinfo); +} + +#endif /* UPSAMPLE_MERGING_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jdphuff.c b/src/renderer/jpeg-6/jdphuff.c new file mode 100644 index 0000000..9489af6 --- /dev/null +++ b/src/renderer/jpeg-6/jdphuff.c @@ -0,0 +1,666 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdphuff.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines for progressive JPEG. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdhuff.c */ + + +#ifdef D_PROGRESSIVE_SUPPORTED + +/* + * Expanded entropy decoder object for progressive Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + unsigned int EOBRUN; /* remaining EOBs in EOBRUN */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).EOBRUN = (src).EOBRUN, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + d_derived_tbl * ac_derived_tbl; /* active table during an AC scan */ +} phuff_entropy_decoder; + +typedef phuff_entropy_decoder * phuff_entropy_ptr; + +/* Forward declarations */ +METHODDEF boolean decode_mcu_DC_first JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean decode_mcu_AC_first JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean decode_mcu_DC_refine JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean decode_mcu_AC_refine JPP((j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_phuff_decoder (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band, bad; + int ci, coefi, tbl; + int *coef_bit_ptr; + jpeg_component_info * compptr; + + is_DC_band = (cinfo->Ss == 0); + + /* Validate scan parameters */ + bad = FALSE; + if (is_DC_band) { + if (cinfo->Se != 0) + bad = TRUE; + } else { + /* need not check Ss/Se < 0 since they came from unsigned bytes */ + if (cinfo->Ss > cinfo->Se || cinfo->Se >= DCTSIZE2) + bad = TRUE; + /* AC scans may have only one component */ + if (cinfo->comps_in_scan != 1) + bad = TRUE; + } + if (cinfo->Ah != 0) { + /* Successive approximation refinement scan: must have Al = Ah-1. */ + if (cinfo->Al != cinfo->Ah-1) + bad = TRUE; + } + if (cinfo->Al > 13) /* need not check for < 0 */ + bad = TRUE; + if (bad) + ERREXIT4(cinfo, JERR_BAD_PROGRESSION, + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); + /* Update progression status, and verify that scan order is legal. + * Note that inter-scan inconsistencies are treated as warnings + * not fatal errors ... not clear if this is right way to behave. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + int cindex = cinfo->cur_comp_info[ci]->component_index; + coef_bit_ptr = & cinfo->coef_bits[cindex][0]; + if (!is_DC_band && coef_bit_ptr[0] < 0) /* AC without prior DC scan */ + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0); + for (coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++) { + int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi]; + if (cinfo->Ah != expected) + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi); + coef_bit_ptr[coefi] = cinfo->Al; + } + } + + /* Select MCU decoding routine */ + if (cinfo->Ah == 0) { + if (is_DC_band) + entropy->pub.decode_mcu = decode_mcu_DC_first; + else + entropy->pub.decode_mcu = decode_mcu_AC_first; + } else { + if (is_DC_band) + entropy->pub.decode_mcu = decode_mcu_DC_refine; + else + entropy->pub.decode_mcu = decode_mcu_AC_refine; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Make sure requested tables are present, and compute derived tables. + * We may build same derived table more than once, but it's not expensive. + */ + if (is_DC_band) { + if (cinfo->Ah == 0) { /* DC refinement needs no table */ + tbl = compptr->dc_tbl_no; + if (tbl < 0 || tbl >= NUM_HUFF_TBLS || + cinfo->dc_huff_tbl_ptrs[tbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tbl); + jpeg_make_d_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[tbl], + & entropy->derived_tbls[tbl]); + } + } else { + tbl = compptr->ac_tbl_no; + if (tbl < 0 || tbl >= NUM_HUFF_TBLS || + cinfo->ac_huff_tbl_ptrs[tbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tbl); + jpeg_make_d_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[tbl], + & entropy->derived_tbls[tbl]); + /* remember the single active table */ + entropy->ac_derived_tbl = entropy->derived_tbls[tbl]; + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize private state variables */ + entropy->saved.EOBRUN = 0; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Re-init EOB run count, too */ + entropy->saved.EOBRUN = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Huffman MCU decoding. + * Each of these routines decodes and returns one MCU's worth of + * Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA IS INITIALLY ZEROED BY THE CALLER. + * + * We return FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * spectral selection, since we'll just re-assign them on the next call. + * Successive approximation AC refinement has to be more careful, however.) + */ + +/* + * MCU decoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Al = cinfo->Al; + register int s, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * tbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + tbl = entropy->derived_tbls[compptr->dc_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, tbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Scale and output the DC coefficient (assumes jpeg_natural_order[0]=0) */ + (*block)[0] = (JCOEF) (s << Al); + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Se = cinfo->Se; + int Al = cinfo->Al; + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state. + * We can avoid loading/saving bitread state if in an EOB run. + */ + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we care about */ + + /* There is always only one block per MCU */ + + if (EOBRUN > 0) /* if it's a band of zeroes... */ + EOBRUN--; /* ...process it now (we do nothing) */ + else { + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + for (k = cinfo->Ss; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, return FALSE, label2); + r = s >> 4; + s &= 15; + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Scale and output coefficient in natural (dezigzagged) order */ + (*block)[jpeg_natural_order[k]] = (JCOEF) (s << Al); + } else { + if (r == 15) { /* ZRL */ + k += 15; /* skip 15 zeroes in band */ + } else { /* EOBr, run length is 2^r + appended bits */ + EOBRUN = 1 << r; + if (r) { /* EOBr, r > 0 */ + CHECK_BIT_BUFFER(br_state, r, return FALSE); + r = GET_BITS(r); + EOBRUN += r; + } + EOBRUN--; /* this band is processed at this moment */ + break; /* force end-of-band */ + } + } + } + + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + } + + /* Completed MCU, so update state */ + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we care about */ + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF boolean +decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int blkn; + JBLOCKROW block; + BITREAD_STATE_VARS; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* Encoded data is simply the next bit of the two's-complement DC value */ + CHECK_BIT_BUFFER(br_state, 1, return FALSE); + if (GET_BITS(1)) + (*block)[0] |= p1; + /* Note: since we use |=, repeating the assignment later is safe */ + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * MCU decoding for AC successive approximation refinement scan. + */ + +METHODDEF boolean +decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + int Se = cinfo->Se; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int m1 = (-1) << cinfo->Al; /* -1 in the bit position being coded */ + register int s, k, r; + unsigned int EOBRUN; + JBLOCKROW block; + JCOEFPTR thiscoef; + BITREAD_STATE_VARS; + d_derived_tbl * tbl; + int num_newnz; + int newnz_pos[DCTSIZE2]; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we care about */ + + /* There is always only one block per MCU */ + block = MCU_data[0]; + tbl = entropy->ac_derived_tbl; + + /* If we are forced to suspend, we must undo the assignments to any newly + * nonzero coefficients in the block, because otherwise we'd get confused + * next time about which coefficients were already nonzero. + * But we need not undo addition of bits to already-nonzero coefficients; + * instead, we can test the current bit position to see if we already did it. + */ + num_newnz = 0; + + /* initialize coefficient loop counter to start of band */ + k = cinfo->Ss; + + if (EOBRUN == 0) { + for (; k <= Se; k++) { + HUFF_DECODE(s, br_state, tbl, goto undoit, label3); + r = s >> 4; + s &= 15; + if (s) { + if (s != 1) /* size of new coef should always be 1 */ + WARNMS(cinfo, JWRN_HUFF_BAD_CODE); + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) + s = p1; /* newly nonzero coef is positive */ + else + s = m1; /* newly nonzero coef is negative */ + } else { + if (r != 15) { + EOBRUN = 1 << r; /* EOBr, run length is 2^r + appended bits */ + if (r) { + CHECK_BIT_BUFFER(br_state, r, goto undoit); + r = GET_BITS(r); + EOBRUN += r; + } + break; /* rest of block is handled by EOB logic */ + } + /* note s = 0 for processing ZRL */ + } + /* Advance over already-nonzero coefs and r still-zero coefs, + * appending correction bits to the nonzeroes. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + do { + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } else { + if (--r < 0) + break; /* reached target zero coefficient */ + } + k++; + } while (k <= Se); + if (s) { + int pos = jpeg_natural_order[k]; + /* Output newly nonzero coefficient */ + (*block)[pos] = (JCOEF) s; + /* Remember its position in case we have to suspend */ + newnz_pos[num_newnz++] = pos; + } + } + } + + if (EOBRUN > 0) { + /* Scan any remaining coefficient positions after the end-of-band + * (the last newly nonzero coefficient, if any). Append a correction + * bit to each already-nonzero coefficient. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + for (; k <= Se; k++) { + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } + } + /* Count one block completed in EOB run */ + EOBRUN--; + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we care about */ + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; + +undoit: + /* Re-zero any output coefficients that we made newly nonzero */ + while (num_newnz > 0) + (*block)[newnz_pos[--num_newnz]] = 0; + + return FALSE; +} + + +/* + * Module initialization routine for progressive Huffman entropy decoding. + */ + +GLOBAL void +jinit_phuff_decoder (j_decompress_ptr cinfo) +{ + phuff_entropy_ptr entropy; + int *coef_bit_ptr; + int ci, i; + + entropy = (phuff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(phuff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_phuff_decoder; + + /* Mark derived tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + } + + /* Create progression status table */ + cinfo->coef_bits = (int (*)[DCTSIZE2]) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components*DCTSIZE2*SIZEOF(int)); + coef_bit_ptr = & cinfo->coef_bits[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (i = 0; i < DCTSIZE2; i++) + *coef_bit_ptr++ = -1; +} + +#endif /* D_PROGRESSIVE_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jdpostct.c b/src/renderer/jpeg-6/jdpostct.c new file mode 100644 index 0000000..6c5c257 --- /dev/null +++ b/src/renderer/jpeg-6/jdpostct.c @@ -0,0 +1,314 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdpostct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub; /* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF void post_process_1pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void post_process_prepass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +METHODDEF void post_process_2pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->quantize_colors) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if (post->buffer == NULL) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF void +post_process_1pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if (max_rows > post->strip_height) + max_rows = post->strip_height; + num_rows = 0; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows); + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF void +post_process_prepass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if (post->next_row > old_next_row) { + num_rows = post->next_row - old_next_row; + (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF void +post_process_2pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row; /* available in strip */ + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + if (num_rows > max_rows) + num_rows = max_rows; + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if (num_rows > max_rows) + num_rows = max_rows; + + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL void +jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_post_ptr post; + + post = (my_post_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_post_controller)); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if (cinfo->quantize_colors) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if (need_full_buffer) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up((long) cinfo->output_height, + (long) post->strip_height), + post->strip_height); +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height); + } + } +} diff --git a/src/renderer/jpeg-6/jdsample.c b/src/renderer/jpeg-6/jdsample.c new file mode 100644 index 0000000..e71098b --- /dev/null +++ b/src/renderer/jpeg-6/jdsample.c @@ -0,0 +1,502 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD(void, upsample1_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF void +start_pass_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF void +sep_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + (*upsample->methods[ci]) (cinfo, compptr, + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + upsample->color_buf + ci); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + + (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF void +fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF void +noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = NULL; /* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF void +int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + for (h = h_expand; h > 0; h--) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if (v_expand > 1) { + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + v_expand-1, cinfo->output_width); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + 1, cinfo->output_width); + inrow++; + outrow += 2; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + * + * The upsampling algorithm is linear interpolation between pixel centers, + * also known as a "triangle filter". This is a good compromise between + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + * of the way between input pixel centers. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register int invalue; + register JDIMENSION colctr; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + /* Special case for first column */ + invalue = GETJSAMPLE(*inptr++); + *outptr++ = (JSAMPLE) invalue; + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(*inptr) + 2) >> 2); + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + invalue = GETJSAMPLE(*inptr++) * 3; + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(inptr[-2]) + 1) >> 2); + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(*inptr) + 2) >> 2); + } + + /* Special case for last column */ + invalue = GETJSAMPLE(*inptr); + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(inptr[-1]) + 1) >> 2); + *outptr++ = (JSAMPLE) invalue; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + * Again a triangle filter; see comments for h2v1 case, above. + * + * It is OK for us to reference the adjacent input rows because we demanded + * context from the main buffer controller (see initialization code). + */ + +METHODDEF void +h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr0, inptr1, outptr; +#if BITS_IN_JSAMPLE == 8 + register int thiscolsum, lastcolsum, nextcolsum; +#else + register INT32 thiscolsum, lastcolsum, nextcolsum; +#endif + register JDIMENSION colctr; + int inrow, outrow, v; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + for (v = 0; v < 2; v++) { + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + inptr0 = input_data[inrow]; + if (v == 0) /* next nearest is row above */ + inptr1 = input_data[inrow-1]; + else /* next nearest is row below */ + inptr1 = input_data[inrow+1]; + outptr = output_data[outrow++]; + + /* Special case for first column */ + thiscolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + } + + /* Special case for last column */ + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 7) >> 4); + } + inrow++; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL void +jinit_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer, do_fancy; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + if (cinfo->CCIR601_sampling) /* this isn't supported */ + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + * so don't ask for it. + */ + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + need_buffer = TRUE; + if (! compptr->component_needed) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if (h_in_group == h_out_group && v_in_group == v_out_group) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if (h_in_group * 2 == h_out_group && + v_in_group == v_out_group) { + /* Special cases for 2h1v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) + upsample->methods[ci] = h2v1_fancy_upsample; + else + upsample->methods[ci] = h2v1_upsample; + } else if (h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group) { + /* Special cases for 2h2v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) { + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; + } else + upsample->methods[ci] = h2v2_upsample; + } else if ((h_out_group % h_in_group) == 0 && + (v_out_group % v_in_group) == 0) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); + upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group); + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + if (need_buffer) { + upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up((long) cinfo->output_width, + (long) cinfo->max_h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/src/renderer/jpeg-6/jdtrans.c b/src/renderer/jpeg-6/jdtrans.c new file mode 100644 index 0000000..7da6cd8 --- /dev/null +++ b/src/renderer/jpeg-6/jdtrans.c @@ -0,0 +1,146 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jdtrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL void transdecode_master_selection JPP((j_decompress_ptr cinfo)); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL jvirt_barray_ptr * +jpeg_read_coefficients (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize active modules */ + transdecode_master_selection(cinfo); + cinfo->global_state = DSTATE_RDCOEFS; + } else if (cinfo->global_state != DSTATE_RDCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Absorb whole file into the coef buffer */ + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return NULL; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + return cinfo->coef->coef_arrays; +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL void +transdecode_master_selection (j_decompress_ptr cinfo) +{ + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller(cinfo, TRUE); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + + /* Initialize progress monitoring. */ + if (cinfo->progress != NULL) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if (cinfo->inputctl->has_multiple_scans) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/src/renderer/jpeg-6/jerror.c b/src/renderer/jpeg-6/jerror.c new file mode 100644 index 0000000..72fcae6 --- /dev/null +++ b/src/renderer/jpeg-6/jerror.c @@ -0,0 +1,258 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jerror.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * These routines are used by both the compression and decompression code. + */ + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ + +#include "jinclude.h" +#include "jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +extern jpg_Error( const char *fmt, ... ); +extern jpg_Printf( const char *fmt, ... ); + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE(code,string) string , + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF void +error_exit (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + jpg_Error( "%s\n", buffer ); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + */ + +METHODDEF void +output_message (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Send it to stderr, adding a newline */ + jpg_Printf( "%s\n", buffer ); +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF void +emit_message (j_common_ptr cinfo, int msg_level) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if (err->num_warnings == 0 || err->trace_level >= 3) + (*err->output_message) (cinfo); + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if (err->trace_level >= msg_level) + (*err->output_message) (cinfo); + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF void +format_message (j_common_ptr cinfo, char * buffer) +{ + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if (msg_code > 0 && msg_code <= err->last_jpeg_message) { + msgtext = err->jpeg_message_table[msg_code]; + } else if (err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if (msgtext == NULL) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ((ch = *msgptr++) != '\0') { + if (ch == '%') { + if (*msgptr == 's') isstring = TRUE; + break; + } + } + + /* Format the message into the passed buffer */ + if (isstring) + sprintf(buffer, msgtext, err->msg_parm.s); + else + sprintf(buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7]); +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF void +reset_error_mgr (j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL struct jpeg_error_mgr * +jpeg_std_error (struct jpeg_error_mgr * err) +{ + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/src/renderer/jpeg-6/jerror.h b/src/renderer/jpeg-6/jerror.h new file mode 100644 index 0000000..ddb178a --- /dev/null +++ b/src/renderer/jpeg-6/jerror.h @@ -0,0 +1,297 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jerror.h + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding") +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_COUNTS, "Bogus DHT counts") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_MINOR, "Unknown JFIF minor revision number %d.%02d") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Skipping marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/src/renderer/jpeg-6/jfdctflt.c b/src/renderer/jpeg-6/jfdctflt.c new file mode 100644 index 0000000..6d9fc6c --- /dev/null +++ b/src/renderer/jpeg-6/jfdctflt.c @@ -0,0 +1,192 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jfdctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_float (FAST_FLOAT * data) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jfdctfst.c b/src/renderer/jpeg-6/jfdctfst.c new file mode 100644 index 0000000..8283a81 --- /dev/null +++ b/src/renderer/jpeg-6/jfdctfst.c @@ -0,0 +1,248 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jfdctfst.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jfdctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * Again to save a few shifts, the intermediate results between pass 1 and + * pass 2 are not upscaled, but are represented only to integral precision. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#define CONST_BITS 8 + + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_0_382683433 ((INT32) 98) /* FIX(0.382683433) */ +#define FIX_0_541196100 ((INT32) 139) /* FIX(0.541196100) */ +#define FIX_0_707106781 ((INT32) 181) /* FIX(0.707106781) */ +#define FIX_1_306562965 ((INT32) 334) /* FIX(1.306562965) */ +#else +#define FIX_0_382683433 FIX(0.382683433) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_707106781 FIX(0.707106781) +#define FIX_1_306562965 FIX(1.306562965) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_ifast (DCTELEM * data) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z1, z2, z3, z4, z5, z11, z13; + DCTELEM *dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jfdctint.c b/src/renderer/jpeg-6/jfdctint.c new file mode 100644 index 0000000..740a04b --- /dev/null +++ b/src/renderer/jpeg-6/jfdctint.c @@ -0,0 +1,307 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jfdctint.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * forward DCT (Discrete Cosine Transform). + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D DCT step produces outputs which are a factor of sqrt(N) + * larger than the true DCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D DCT, + * because the y0 and y4 outputs need not be divided by sqrt(N). + * In the IJG code, this factor of 8 is removed by the quantization step + * (in jcdctmgr.c), NOT in this module. + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (For 12-bit sample data, the intermediate + * array is INT32 anyway.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_islow (DCTELEM * data) +{ + INT32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + DCTELEM *dataptr; + int ctr; + SHIFT_TEMPS + + /* Pass 1: process rows. */ + /* Note results are scaled up by sqrt(8) compared to a true DCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = (DCTELEM) ((tmp10 + tmp11) << PASS1_BITS); + dataptr[4] = (DCTELEM) ((tmp10 - tmp11) << PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865), + CONST_BITS-PASS1_BITS); + dataptr[6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065), + CONST_BITS-PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[7] = (DCTELEM) DESCALE(tmp4 + z1 + z3, CONST_BITS-PASS1_BITS); + dataptr[5] = (DCTELEM) DESCALE(tmp5 + z2 + z4, CONST_BITS-PASS1_BITS); + dataptr[3] = (DCTELEM) DESCALE(tmp6 + z2 + z3, CONST_BITS-PASS1_BITS); + dataptr[1] = (DCTELEM) DESCALE(tmp7 + z1 + z4, CONST_BITS-PASS1_BITS); + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. + * We remove the PASS1_BITS scaling, but leave the results scaled up + * by an overall factor of 8. + */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part per LL&M figure 1 --- note that published figure is faulty; + * rotator "sqrt(2)*c1" should be "sqrt(2)*c6". + */ + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = (DCTELEM) DESCALE(tmp10 + tmp11, PASS1_BITS); + dataptr[DCTSIZE*4] = (DCTELEM) DESCALE(tmp10 - tmp11, PASS1_BITS); + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_541196100); + dataptr[DCTSIZE*2] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp13, FIX_0_765366865), + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*6] = (DCTELEM) DESCALE(z1 + MULTIPLY(tmp12, - FIX_1_847759065), + CONST_BITS+PASS1_BITS); + + /* Odd part per figure 8 --- note paper omits factor of sqrt(2). + * cK represents cos(K*pi/16). + * i0..i3 in the paper are tmp4..tmp7 here. + */ + + z1 = tmp4 + tmp7; + z2 = tmp5 + tmp6; + z3 = tmp4 + tmp6; + z4 = tmp5 + tmp7; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp4 = MULTIPLY(tmp4, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp5 = MULTIPLY(tmp5, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp6 = MULTIPLY(tmp6, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp7 = MULTIPLY(tmp7, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + dataptr[DCTSIZE*7] = (DCTELEM) DESCALE(tmp4 + z1 + z3, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*5] = (DCTELEM) DESCALE(tmp5 + z2 + z4, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*3] = (DCTELEM) DESCALE(tmp6 + z2 + z3, + CONST_BITS+PASS1_BITS); + dataptr[DCTSIZE*1] = (DCTELEM) DESCALE(tmp7 + z1 + z4, + CONST_BITS+PASS1_BITS); + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jidctflt.c b/src/renderer/jpeg-6/jidctflt.c new file mode 100644 index 0000000..d7950c7 --- /dev/null +++ b/src/renderer/jpeg-6/jidctflt.c @@ -0,0 +1,265 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jidctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE(coef,quantval) (((FAST_FLOAT) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*2] | inptr[DCTSIZE*3] | + inptr[DCTSIZE*4] | inptr[DCTSIZE*5] | inptr[DCTSIZE*6] | + inptr[DCTSIZE*7]) == 0) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = tmp0 + tmp7; + wsptr[DCTSIZE*7] = tmp0 - tmp7; + wsptr[DCTSIZE*1] = tmp1 + tmp6; + wsptr[DCTSIZE*6] = tmp1 - tmp6; + wsptr[DCTSIZE*2] = tmp2 + tmp5; + wsptr[DCTSIZE*5] = tmp2 - tmp5; + wsptr[DCTSIZE*4] = tmp3 + tmp4; + wsptr[DCTSIZE*3] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + tmp10 = wsptr[0] + wsptr[4]; + tmp11 = wsptr[0] - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jidctfst.c b/src/renderer/jpeg-6/jidctfst.c new file mode 100644 index 0000000..401d45a --- /dev/null +++ b/src/renderer/jpeg-6/jidctfst.c @@ -0,0 +1,391 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jidctfst.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a fast, not so accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with fixed-point math, + * accuracy is lost due to imprecise representation of the scaled + * quantization values. The smaller the quantization table entry, the less + * precise the scaled value, so this implementation does worse with high- + * quality-setting files than with low-quality ones. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_IFAST_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling decisions are generally the same as in the LL&M algorithm; + * see jidctint.c for more details. However, we choose to descale + * (right shift) multiplication products as soon as they are formed, + * rather than carrying additional fractional bits into subsequent additions. + * This compromises accuracy slightly, but it lets us save a few shifts. + * More importantly, 16-bit arithmetic is then adequate (for 8-bit samples) + * everywhere except in the multiplications proper; this saves a good deal + * of work on 16-bit-int machines. + * + * The dequantized coefficients are not integers because the AA&N scaling + * factors have been incorporated. We represent them scaled up by PASS1_BITS, + * so that the first and second IDCT rounds have the same input scaling. + * For 8-bit JSAMPLEs, we choose IFAST_SCALE_BITS = PASS1_BITS so as to + * avoid a descaling shift; this compromises accuracy rather drastically + * for small quantization table entries, but it saves a lot of shifts. + * For 12-bit JSAMPLEs, there's no hope of using 16x16 multiplies anyway, + * so we use a much larger scaling factor to preserve accuracy. + * + * A final compromise is to represent the multiplicative constants to only + * 8 fractional bits, rather than 13. This saves some shifting work on some + * machines, and may also reduce the cost of multiplication (since there + * are fewer one-bits in the constants). + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 8 +#define PASS1_BITS 2 +#else +#define CONST_BITS 8 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 8 +#define FIX_1_082392200 ((INT32) 277) /* FIX(1.082392200) */ +#define FIX_1_414213562 ((INT32) 362) /* FIX(1.414213562) */ +#define FIX_1_847759065 ((INT32) 473) /* FIX(1.847759065) */ +#define FIX_2_613125930 ((INT32) 669) /* FIX(2.613125930) */ +#else +#define FIX_1_082392200 FIX(1.082392200) +#define FIX_1_414213562 FIX(1.414213562) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_2_613125930 FIX(2.613125930) +#endif + + +/* We can gain a little more speed, with a further compromise in accuracy, + * by omitting the addition in a descaling shift. This yields an incorrectly + * rounded result half the time... + */ + +#ifndef USE_ACCURATE_ROUNDING +#undef DESCALE +#define DESCALE(x,n) RIGHT_SHIFT(x, n) +#endif + + +/* Multiply a DCTELEM variable by an INT32 constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var,const) ((DCTELEM) DESCALE((var) * (const), CONST_BITS)) + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a DCTELEM result. For 8-bit data a 16x16->16 + * multiplication will do. For 12-bit data, the multiplier table is + * declared INT32, so a 32-bit multiply will be used. + */ + +#if BITS_IN_JSAMPLE == 8 +#define DEQUANTIZE(coef,quantval) (((IFAST_MULT_TYPE) (coef)) * (quantval)) +#else +#define DEQUANTIZE(coef,quantval) \ + DESCALE((coef)*(quantval), IFAST_SCALE_BITS-PASS1_BITS) +#endif + + +/* Like DESCALE, but applies to a DCTELEM and produces an int. + * We assume that int right shift is unsigned if INT32 right shift is. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS DCTELEM ishift_temp; +#if BITS_IN_JSAMPLE == 8 +#define DCTELEMBITS 16 /* DCTELEM may be 16 or 32 bits */ +#else +#define DCTELEMBITS 32 /* DCTELEM must be 32 bits */ +#endif +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~((DCTELEM) 0)) << (DCTELEMBITS-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +#ifdef USE_ACCURATE_ROUNDING +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT((x) + (1 << ((n)-1)), n)) +#else +#define IDESCALE(x,n) ((int) IRIGHT_SHIFT(x, n)) +#endif + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z5, z10, z11, z12, z13; + JCOEFPTR inptr; + IFAST_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS /* for DESCALE */ + ISHIFT_TEMPS /* for IDESCALE */ + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (IFAST_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*2] | inptr[DCTSIZE*3] | + inptr[DCTSIZE*4] | inptr[DCTSIZE*5] | inptr[DCTSIZE*6] | + inptr[DCTSIZE*7]) == 0) { + /* AC terms all zero */ + int dcval = (int) DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = (int) (tmp0 + tmp7); + wsptr[DCTSIZE*7] = (int) (tmp0 - tmp7); + wsptr[DCTSIZE*1] = (int) (tmp1 + tmp6); + wsptr[DCTSIZE*6] = (int) (tmp1 - tmp6); + wsptr[DCTSIZE*2] = (int) (tmp2 + tmp5); + wsptr[DCTSIZE*5] = (int) (tmp2 - tmp5); + wsptr[DCTSIZE*4] = (int) (tmp3 + tmp4); + wsptr[DCTSIZE*3] = (int) (tmp3 - tmp4); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if ((wsptr[1] | wsptr[2] | wsptr[3] | wsptr[4] | wsptr[5] | wsptr[6] | + wsptr[7]) == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[IDESCALE(wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((DCTELEM) wsptr[0] + (DCTELEM) wsptr[4]); + tmp11 = ((DCTELEM) wsptr[0] - (DCTELEM) wsptr[4]); + + tmp13 = ((DCTELEM) wsptr[2] + (DCTELEM) wsptr[6]); + tmp12 = MULTIPLY((DCTELEM) wsptr[2] - (DCTELEM) wsptr[6], FIX_1_414213562) + - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = (DCTELEM) wsptr[5] + (DCTELEM) wsptr[3]; + z10 = (DCTELEM) wsptr[5] - (DCTELEM) wsptr[3]; + z11 = (DCTELEM) wsptr[1] + (DCTELEM) wsptr[7]; + z12 = (DCTELEM) wsptr[1] - (DCTELEM) wsptr[7]; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[IDESCALE(tmp0 + tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[IDESCALE(tmp0 - tmp7, PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[IDESCALE(tmp1 + tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[IDESCALE(tmp1 - tmp6, PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[IDESCALE(tmp2 + tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[IDESCALE(tmp2 - tmp5, PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[IDESCALE(tmp3 + tmp4, PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[IDESCALE(tmp3 - tmp4, PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_IFAST_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jidctint.c b/src/renderer/jpeg-6/jidctint.c new file mode 100644 index 0000000..6a4eb33 --- /dev/null +++ b/src/renderer/jpeg-6/jidctint.c @@ -0,0 +1,412 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jidctint.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a slow-but-accurate integer implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on an algorithm described in + * C. Loeffler, A. Ligtenberg and G. Moschytz, "Practical Fast 1-D DCT + * Algorithms with 11 Multiplications", Proc. Int'l. Conf. on Acoustics, + * Speech, and Signal Processing 1989 (ICASSP '89), pp. 988-991. + * The primary algorithm described there uses 11 multiplies and 29 adds. + * We use their alternate method with 12 multiplies and 32 adds. + * The advantage of this method is that no data path contains more than one + * multiplication; this allows a very simple and accurate implementation in + * scaled fixed-point arithmetic, with a minimal number of shifts. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_ISLOW_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * The poop on this scaling stuff is as follows: + * + * Each 1-D IDCT step produces outputs which are a factor of sqrt(N) + * larger than the true IDCT outputs. The final outputs are therefore + * a factor of N larger than desired; since N=8 this can be cured by + * a simple right shift at the end of the algorithm. The advantage of + * this arrangement is that we save two multiplications per 1-D IDCT, + * because the y0 and y4 inputs need not be divided by sqrt(N). + * + * We have to do addition and subtraction of the integer inputs, which + * is no problem, and multiplication by fractional constants, which is + * a problem to do in integer arithmetic. We multiply all the constants + * by CONST_SCALE and convert them to integer constants (thus retaining + * CONST_BITS bits of precision in the constants). After doing a + * multiplication we have to divide the product by CONST_SCALE, with proper + * rounding, to produce the correct output. This division can be done + * cheaply as a right shift of CONST_BITS bits. We postpone shifting + * as long as possible so that partial sums can be added together with + * full fractional precision. + * + * The outputs of the first pass are scaled up by PASS1_BITS bits so that + * they are represented to better-than-integral precision. These outputs + * require BITS_IN_JSAMPLE + PASS1_BITS + 3 bits; this fits in a 16-bit word + * with the recommended scaling. (To scale up 12-bit sample data further, an + * intermediate INT32 array would be needed.) + * + * To avoid overflow of the 32-bit intermediate results in pass 2, we must + * have BITS_IN_JSAMPLE + CONST_BITS + PASS1_BITS <= 26. Error analysis + * shows that the values given below are the most effective. + */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#else +#define FIX_0_298631336 FIX(0.298631336) +#define FIX_0_390180644 FIX(0.390180644) +#define FIX_0_541196100 FIX(0.541196100) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_175875602 FIX(1.175875602) +#define FIX_1_501321110 FIX(1.501321110) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_1_961570560 FIX(1.961570560) +#define FIX_2_053119869 FIX(2.053119869) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_072711026 FIX(3.072711026) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE(coef,quantval) (((ISLOW_MULT_TYPE) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp1, tmp2, tmp3; + INT32 tmp10, tmp11, tmp12, tmp13; + INT32 z1, z2, z3, z4, z5; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + /* Note results are scaled up by sqrt(8) compared to a true IDCT; */ + /* furthermore, we scale the results by 2**PASS1_BITS. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*2] | inptr[DCTSIZE*3] | + inptr[DCTSIZE*4] | inptr[DCTSIZE*5] | inptr[DCTSIZE*6] | + inptr[DCTSIZE*7]) == 0) { + /* AC terms all zero */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + z2 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + z3 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + + tmp0 = (z2 + z3) << CONST_BITS; + tmp1 = (z2 - z3) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*7] = (int) DESCALE(tmp10 - tmp3, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp11 + tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*6] = (int) DESCALE(tmp11 - tmp2, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 + tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*5] = (int) DESCALE(tmp12 - tmp1, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*3] = (int) DESCALE(tmp13 + tmp0, CONST_BITS-PASS1_BITS); + wsptr[DCTSIZE*4] = (int) DESCALE(tmp13 - tmp0, CONST_BITS-PASS1_BITS); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if ((wsptr[1] | wsptr[2] | wsptr[3] | wsptr[4] | wsptr[5] | wsptr[6] | + wsptr[7]) == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part: reverse the even part of the forward DCT. */ + /* The rotator is sqrt(2)*c(-6). */ + + z2 = (INT32) wsptr[2]; + z3 = (INT32) wsptr[6]; + + z1 = MULTIPLY(z2 + z3, FIX_0_541196100); + tmp2 = z1 + MULTIPLY(z3, - FIX_1_847759065); + tmp3 = z1 + MULTIPLY(z2, FIX_0_765366865); + + tmp0 = ((INT32) wsptr[0] + (INT32) wsptr[4]) << CONST_BITS; + tmp1 = ((INT32) wsptr[0] - (INT32) wsptr[4]) << CONST_BITS; + + tmp10 = tmp0 + tmp3; + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + /* Odd part per figure 8; the matrix is unitary and hence its + * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. + */ + + tmp0 = (INT32) wsptr[7]; + tmp1 = (INT32) wsptr[5]; + tmp2 = (INT32) wsptr[3]; + tmp3 = (INT32) wsptr[1]; + + z1 = tmp0 + tmp3; + z2 = tmp1 + tmp2; + z3 = tmp0 + tmp2; + z4 = tmp1 + tmp3; + z5 = MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ + + tmp0 = MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ + tmp1 = MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ + tmp2 = MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ + tmp3 = MULTIPLY(tmp3, FIX_1_501321110); /* sqrt(2) * ( c1+c3-c5-c7) */ + z1 = MULTIPLY(z1, - FIX_0_899976223); /* sqrt(2) * (c7-c3) */ + z2 = MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ + z3 = MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ + z4 = MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ + + z3 += z5; + z4 += z5; + + tmp0 += z1 + z3; + tmp1 += z2 + z4; + tmp2 += z2 + z3; + tmp3 += z1 + z4; + + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE(tmp10 - tmp3, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp11 + tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE(tmp11 - tmp2, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE(tmp12 + tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE(tmp12 - tmp1, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE(tmp13 + tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE(tmp13 - tmp0, + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_ISLOW_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jidctred.c b/src/renderer/jpeg-6/jidctred.c new file mode 100644 index 0000000..4071b45 --- /dev/null +++ b/src/renderer/jpeg-6/jidctred.c @@ -0,0 +1,421 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jidctred.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains inverse-DCT routines that produce reduced-size output: + * either 4x4, 2x2, or 1x1 pixels from an 8x8 DCT block. + * + * The implementation is based on the Loeffler, Ligtenberg and Moschytz (LL&M) + * algorithm used in jidctint.c. We simply replace each 8-to-8 1-D IDCT step + * with an 8-to-4 step that produces the four averages of two adjacent outputs + * (or an 8-to-2 step producing two averages of four outputs, for 2x2 output). + * These steps were derived by computing the corresponding values at the end + * of the normal LL&M code, then simplifying as much as possible. + * + * 1x1 is trivial: just take the DC coefficient divided by 8. + * + * See jidctint.c for additional comments. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef IDCT_SCALING_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Scaling is the same as in jidctint.c. */ + +#if BITS_IN_JSAMPLE == 8 +#define CONST_BITS 13 +#define PASS1_BITS 2 +#else +#define CONST_BITS 13 +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#endif + +/* Some C compilers fail to reduce "FIX(constant)" at compile time, thus + * causing a lot of useless floating-point operations at run time. + * To get around this we use the following pre-calculated constants. + * If you change CONST_BITS you may want to add appropriate values. + * (With a reasonable C compiler, you can just rely on the FIX() macro...) + */ + +#if CONST_BITS == 13 +#define FIX_0_211164243 ((INT32) 1730) /* FIX(0.211164243) */ +#define FIX_0_509795579 ((INT32) 4176) /* FIX(0.509795579) */ +#define FIX_0_601344887 ((INT32) 4926) /* FIX(0.601344887) */ +#define FIX_0_720959822 ((INT32) 5906) /* FIX(0.720959822) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_850430095 ((INT32) 6967) /* FIX(0.850430095) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_061594337 ((INT32) 8697) /* FIX(1.061594337) */ +#define FIX_1_272758580 ((INT32) 10426) /* FIX(1.272758580) */ +#define FIX_1_451774981 ((INT32) 11893) /* FIX(1.451774981) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_2_172734803 ((INT32) 17799) /* FIX(2.172734803) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_624509785 ((INT32) 29692) /* FIX(3.624509785) */ +#else +#define FIX_0_211164243 FIX(0.211164243) +#define FIX_0_509795579 FIX(0.509795579) +#define FIX_0_601344887 FIX(0.601344887) +#define FIX_0_720959822 FIX(0.720959822) +#define FIX_0_765366865 FIX(0.765366865) +#define FIX_0_850430095 FIX(0.850430095) +#define FIX_0_899976223 FIX(0.899976223) +#define FIX_1_061594337 FIX(1.061594337) +#define FIX_1_272758580 FIX(1.272758580) +#define FIX_1_451774981 FIX(1.451774981) +#define FIX_1_847759065 FIX(1.847759065) +#define FIX_2_172734803 FIX(2.172734803) +#define FIX_2_562915447 FIX(2.562915447) +#define FIX_3_624509785 FIX(3.624509785) +#endif + + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * For 8-bit samples with the recommended scaling, all the variable + * and constant values involved are no more than 16 bits wide, so a + * 16x16->32 bit multiply can be used instead of a full 32x32 multiply. + * For 12-bit samples, a full 32-bit multiplication will be needed. + */ + +#if BITS_IN_JSAMPLE == 8 +#define MULTIPLY(var,const) MULTIPLY16C16(var,const) +#else +#define MULTIPLY(var,const) ((var) * (const)) +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce an int result. In this module, both inputs and result + * are 16 bits or less, so either int or short multiply will work. + */ + +#define DEQUANTIZE(coef,quantval) (((ISLOW_MULT_TYPE) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 4x4 output block. + */ + +GLOBAL void +jpeg_idct_4x4 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp2, tmp10, tmp12; + INT32 z1, z2, z3, z4; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE*4]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) { + /* Don't bother to process column 4, because second pass won't use it */ + if (ctr == DCTSIZE-4) + continue; + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*2] | inptr[DCTSIZE*3] | + inptr[DCTSIZE*5] | inptr[DCTSIZE*6] | inptr[DCTSIZE*7]) == 0) { + /* AC terms all zero; we need not examine term 4 for 4x4 output */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp0 <<= (CONST_BITS+1); + + z2 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + z3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp2 = MULTIPLY(z2, FIX_1_847759065) + MULTIPLY(z3, - FIX_0_765366865); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + z2 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + z3 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + z4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + + tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */ + + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */ + + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp2, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*3] = (int) DESCALE(tmp10 - tmp2, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp12 + tmp0, CONST_BITS-PASS1_BITS+1); + wsptr[DCTSIZE*2] = (int) DESCALE(tmp12 - tmp0, CONST_BITS-PASS1_BITS+1); + } + + /* Pass 2: process 4 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 4; ctr++) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if ((wsptr[1] | wsptr[2] | wsptr[3] | wsptr[5] | wsptr[6] | + wsptr[7]) == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp0 = ((INT32) wsptr[0]) << (CONST_BITS+1); + + tmp2 = MULTIPLY((INT32) wsptr[2], FIX_1_847759065) + + MULTIPLY((INT32) wsptr[6], - FIX_0_765366865); + + tmp10 = tmp0 + tmp2; + tmp12 = tmp0 - tmp2; + + /* Odd part */ + + z1 = (INT32) wsptr[7]; + z2 = (INT32) wsptr[5]; + z3 = (INT32) wsptr[3]; + z4 = (INT32) wsptr[1]; + + tmp0 = MULTIPLY(z1, - FIX_0_211164243) /* sqrt(2) * (c3-c1) */ + + MULTIPLY(z2, FIX_1_451774981) /* sqrt(2) * (c3+c7) */ + + MULTIPLY(z3, - FIX_2_172734803) /* sqrt(2) * (-c1-c5) */ + + MULTIPLY(z4, FIX_1_061594337); /* sqrt(2) * (c5+c7) */ + + tmp2 = MULTIPLY(z1, - FIX_0_509795579) /* sqrt(2) * (c7-c5) */ + + MULTIPLY(z2, - FIX_0_601344887) /* sqrt(2) * (c5-c1) */ + + MULTIPLY(z3, FIX_0_899976223) /* sqrt(2) * (c3-c7) */ + + MULTIPLY(z4, FIX_2_562915447); /* sqrt(2) * (c1+c3) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp2, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE(tmp10 - tmp2, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp12 + tmp0, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE(tmp12 - tmp0, + CONST_BITS+PASS1_BITS+3+1) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 2x2 output block. + */ + +GLOBAL void +jpeg_idct_2x2 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + INT32 tmp0, tmp10, z1; + JCOEFPTR inptr; + ISLOW_MULT_TYPE * quantptr; + int * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + int workspace[DCTSIZE*2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; inptr++, quantptr++, wsptr++, ctr--) { + /* Don't bother to process columns 2,4,6 */ + if (ctr == DCTSIZE-2 || ctr == DCTSIZE-4 || ctr == DCTSIZE-6) + continue; + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*3] | + inptr[DCTSIZE*5] | inptr[DCTSIZE*7]) == 0) { + /* AC terms all zero; we need not examine terms 2,4,6 for 2x2 output */ + int dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + + continue; + } + + /* Even part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp10 = z1 << (CONST_BITS+2); + + /* Odd part */ + + z1 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + tmp0 = MULTIPLY(z1, - FIX_0_720959822); /* sqrt(2) * (c7-c5+c3-c1) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp0 += MULTIPLY(z1, FIX_0_850430095); /* sqrt(2) * (-c1+c3+c5+c7) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp0 += MULTIPLY(z1, - FIX_1_272758580); /* sqrt(2) * (-c1+c3-c5-c7) */ + z1 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp0 += MULTIPLY(z1, FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp0, CONST_BITS-PASS1_BITS+2); + wsptr[DCTSIZE*1] = (int) DESCALE(tmp10 - tmp0, CONST_BITS-PASS1_BITS+2); + } + + /* Pass 2: process 2 rows from work array, store into output array. */ + + wsptr = workspace; + for (ctr = 0; ctr < 2; ctr++) { + outptr = output_buf[ctr] + output_col; + /* It's not clear whether a zero row test is worthwhile here ... */ + +#ifndef NO_ZERO_ROW_TEST + if ((wsptr[1] | wsptr[3] | wsptr[5] | wsptr[7]) == 0) { + /* AC terms all zero */ + JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) + & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((INT32) wsptr[0]) << (CONST_BITS+2); + + /* Odd part */ + + tmp0 = MULTIPLY((INT32) wsptr[7], - FIX_0_720959822) /* sqrt(2) * (c7-c5+c3-c1) */ + + MULTIPLY((INT32) wsptr[5], FIX_0_850430095) /* sqrt(2) * (-c1+c3+c5+c7) */ + + MULTIPLY((INT32) wsptr[3], - FIX_1_272758580) /* sqrt(2) * (-c1+c3-c5-c7) */ + + MULTIPLY((INT32) wsptr[1], FIX_3_624509785); /* sqrt(2) * (c1+c3+c5+c7) */ + + /* Final output stage */ + + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp0, + CONST_BITS+PASS1_BITS+3+2) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE(tmp10 - tmp0, + CONST_BITS+PASS1_BITS+3+2) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + + +/* + * Perform dequantization and inverse DCT on one block of coefficients, + * producing a reduced-size 1x1 output block. + */ + +GLOBAL void +jpeg_idct_1x1 (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + int dcval; + ISLOW_MULT_TYPE * quantptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + SHIFT_TEMPS + + /* We hardly need an inverse DCT routine for this: just take the + * average pixel value, which is one-eighth of the DC coefficient. + */ + quantptr = (ISLOW_MULT_TYPE *) compptr->dct_table; + dcval = DEQUANTIZE(coef_block[0], quantptr[0]); + dcval = (int) DESCALE((INT32) dcval, 3); + + output_buf[0][output_col] = range_limit[dcval & RANGE_MASK]; +} + +#endif /* IDCT_SCALING_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jinclude.h b/src/renderer/jpeg-6/jinclude.h new file mode 100644 index 0000000..a3a86f8 --- /dev/null +++ b/src/renderer/jpeg-6/jinclude.h @@ -0,0 +1,115 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +/* Include auto-config file to find out which system include files we need. */ + +#include "../jpeg-6/jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/src/renderer/jpeg-6/jmemmgr.c b/src/renderer/jpeg-6/jmemmgr.c new file mode 100644 index 0000000..2851444 --- /dev/null +++ b/src/renderer/jpeg-6/jmemmgr.c @@ -0,0 +1,1139 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jmemmgr.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP((const char * name)); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL void +print_mem_stats (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf(stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated); + + for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next) { + fprintf(stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used); + } + + for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next) { + fprintf(stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left); + } +} + +#endif /* MEM_STATS */ + + +LOCAL void +out_of_memory (j_common_ptr cinfo, int which) +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +{ +#ifdef MEM_STATS + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ +#endif + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +{ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +{ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF void * +alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "small" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* See if space is available in any existing pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while (hdr_ptr != NULL) { + if (hdr_ptr->hdr.bytes_left >= sizeofobject) + break; /* found pool with enough space */ + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if (hdr_ptr == NULL) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF(small_pool_hdr); + if (prev_hdr_ptr == NULL) /* first pool in class? */ + slop = first_pool_slop[pool_id]; + else + slop = extra_pool_slop[pool_id]; + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request)) + slop = (size_t) (MAX_ALLOC_CHUNK-min_request); + /* Try to get space, if fail reduce slop and try again */ + for (;;) { + hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); + if (hdr_ptr != NULL) + break; + slop /= 2; + if (slop < MIN_SLOP) /* give up when it gets real small */ + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if (prev_hdr_ptr == NULL) /* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + else + prev_hdr_ptr->hdr.next = hdr_ptr; + } + + /* OK, allocate the object from the current pool */ + data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF void FAR * +alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "large" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* Always make a new pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + + SIZEOF(large_pool_hdr)); + if (hdr_ptr == NULL) + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF JSAMPARRAY +alloc_sarray (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows) +/* Allocate a 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) samplesperrow * SIZEOF(JSAMPLE)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JSAMPROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JSAMPROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF(JSAMPLE))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF JBLOCKARRAY +alloc_barray (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows) +/* Allocate a 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) blocksperrow * SIZEOF(JBLOCK)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JBLOCKROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF(JBLOCK))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF jvirt_sarray_ptr +request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_sarray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF jvirt_barray_ptr +request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_barray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF void +realize_virt_arrays (j_common_ptr cinfo) +/* Allocate the in-memory buffers for any unrealized virtual arrays */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + } + } + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + } + } + + if (space_per_minheight <= 0) + return; /* no unrealized arrays, no work */ + + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if (avail_mem >= maximum_space) + max_minheights = 1000000000L; + else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if (max_minheights <= 0) + max_minheights = 1; + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF(JSAMPLE)); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF(JBLOCK)); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL void +do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual sample array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +LOCAL void +do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual coefficient-block array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +METHODDEF JSAMPARRAY +access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_sarray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +METHODDEF JBLOCKARRAY +access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_barray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF void +free_pool (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + +#ifdef MEM_STATS + if (cinfo->err->trace_level > 1) + print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */ +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if (pool_id == JPOOL_IMAGE) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->b_s_open) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + } + } + mem->virt_sarray_list = NULL; + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->b_s_open) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while (lhdr_ptr != NULL) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF(large_pool_hdr); + jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while (shdr_ptr != NULL) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF(small_pool_hdr); + jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF void +self_destruct (j_common_ptr cinfo) +{ + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + free_pool(cinfo, pool); + } + + /* Release the memory manager control block too. */ + jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL void +jinit_memory_mgr (j_common_ptr cinfo) +{ + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0) + ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE); + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ((long) test_mac != MAX_ALLOC_CHUNK || + (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0) + ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK); + + max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); + + if (mem == NULL) { + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF(my_memory_mgr); + + /* Declare ourselves open for business */ + cinfo->mem = & mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ((memenv = getenv("JPEGMEM")) != NULL) { + char ch = 'x'; + + if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { + if (ch == 'm' || ch == 'M') + max_to_use *= 1000L; + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/src/renderer/jpeg-6/jmemnobs.c b/src/renderer/jpeg-6/jmemnobs.c new file mode 100644 index 0000000..6e42fac --- /dev/null +++ b/src/renderer/jpeg-6/jmemnobs.c @@ -0,0 +1,127 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jmemnobs.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from ri.Malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +/* + * Memory allocation and ri.Freeing are controlled by the regular library + * routines ri.Malloc() and ri.Free(). + */ + +GLOBAL void * +jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void *) malloc(sizeofobject); +} + +GLOBAL void +jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL void FAR * +jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void FAR *) malloc(sizeofobject); +} + +GLOBAL void +jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject) +{ + free(object); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL long +jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated) +{ + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL void +jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed) +{ + ERREXIT(cinfo, JERR_NO_BACKING_STORE); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL long +jpeg_mem_init (j_common_ptr cinfo) +{ + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL void +jpeg_mem_term (j_common_ptr cinfo) +{ + /* no work */ +} diff --git a/src/renderer/jpeg-6/jmemsys.h b/src/renderer/jpeg-6/jmemsys.h new file mode 100644 index 0000000..1e18db8 --- /dev/null +++ b/src/renderer/jpeg-6/jmemsys.h @@ -0,0 +1,206 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jmemsys.h + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN void * jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); +EXTERN void jpeg_free_small JPP((j_common_ptr cinfo, void * object, + size_t sizeofobject)); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN void FAR * jpeg_get_large JPP((j_common_ptr cinfo,size_t sizeofobject)); +EXTERN void jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + size_t sizeofobject)); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN long jpeg_mem_available JPP((j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + backing_store_ptr info)); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +} backing_store_info; + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN void jpeg_open_backing_store JPP((j_common_ptr cinfo, + backing_store_ptr info, + long total_bytes_needed)); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN long jpeg_mem_init JPP((j_common_ptr cinfo)); +EXTERN void jpeg_mem_term JPP((j_common_ptr cinfo)); diff --git a/src/renderer/jpeg-6/jmorecfg.h b/src/renderer/jpeg-6/jmorecfg.h new file mode 100644 index 0000000..2f53b76 --- /dev/null +++ b/src/renderer/jpeg-6/jmorecfg.h @@ -0,0 +1,376 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jmorecfg.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE(value) ((int) (value)) +#else +#define GETJSAMPLE(value) ((int) (value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +#ifndef __MWERKS__ +#ifndef _BASETSD_H_ +typedef long INT32; +#endif +#endif + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +//#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +//typedef long INT32; +//#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These defines are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +#define METHODDEF static /* a function called through method pointers */ +#define LOCAL static /* a function used only in its module */ +#define GLOBAL /* a function referenced thru EXTERNs */ +#define EXTERN extern /* a reference to a GLOBAL function */ + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifdef NEED_FAR_POINTERS +#undef FAR +#define FAR far +#else +#undef FAR +#define FAR +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +//#ifndef HAVE_BOOLEAN +//typedef int boolean; +//#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + +/* Capability options common to encoder and decoder: */ + +#undef DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#undef DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#undef D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#undef D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#undef BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#undef IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#undef UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#undef QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#undef QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 4 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/src/renderer/jpeg-6/jpegint.h b/src/renderer/jpeg-6/jpegint.h new file mode 100644 index 0000000..87b1833 --- /dev/null +++ b/src/renderer/jpeg-6/jpegint.h @@ -0,0 +1,412 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jpegint.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + /* write_any_marker is exported for use by applications */ + /* Probably only COM and APPn markers should be written */ + JMETHOD(void, write_any_marker, (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN void jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN void jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN void jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN void jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN void jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN void jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN void jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN void jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN long jdiv_round_up JPP((long a, long b)); +EXTERN long jround_up JPP((long a, long b)); +EXTERN void jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN void jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN void jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/src/renderer/jpeg-6/jpeglib.h b/src/renderer/jpeg-6/jpeglib.h new file mode 100644 index 0000000..5000a23 --- /dev/null +++ b/src/renderer/jpeg-6/jpeglib.h @@ -0,0 +1,1075 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jpeglib.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +typedef unsigned char boolean; +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "../jpeg-6/jconfig.h" /* widely used configuration options */ +#endif +#include "../jpeg-6/jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 60 /* Version 6 */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This field directly represents the contents of a JPEG DQT marker. + * Note: the values are always given in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is not currently used by the compressor. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + boolean is_decompressor; /* so common code can tell which is which */\ + int global_state /* for checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker: */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_create_compress jCreaCompress +#define jpeg_create_decompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN struct jpeg_error_mgr *jpeg_std_error JPP((struct jpeg_error_mgr *err)); + +/* Initialization and destruction of JPEG compression objects */ +/* NB: you must set up the error-manager BEFORE calling jpeg_create_xxx */ +EXTERN void jpeg_create_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_create_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN void jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN void jpeg_stdio_src JPP((j_decompress_ptr cinfo, unsigned char *infile)); + +/* Default parameter setup for compression */ +EXTERN void jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN void jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN void jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN void jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN void jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN int jpeg_quality_scaling JPP((int quality)); +EXTERN void jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN JQUANT_TBL * jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN JHUFF_TBL * jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN void jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN JDIMENSION jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN void jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN JDIMENSION jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN void jpeg_write_marker JPP((j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN void jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN int jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN boolean jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN JDIMENSION jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN boolean jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN JDIMENSION jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN boolean jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN boolean jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN int jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN void jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN void jpeg_set_marker_processor JPP((j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN jvirt_barray_ptr * jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN void jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN void jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN void jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN void jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN boolean jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "../jpeg-6/jpegint.h" /* fetch private declarations */ +#include "../jpeg-6/jerror.h" /* fetch error codes too */ +#endif + +#endif /* JPEGLIB_H */ diff --git a/src/renderer/jpeg-6/jquant1.c b/src/renderer/jpeg-6/jquant1.c new file mode 100644 index 0000000..ec9ba8c --- /dev/null +++ b/src/renderer/jpeg-6/jquant1.c @@ -0,0 +1,880 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jquant1.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 1-pass color quantization (color mapping) routines. + * These routines provide mapping to a fixed color map using equally spaced + * color values. Optional Floyd-Steinberg or ordered dithering is available. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_1PASS_SUPPORTED + + +/* + * The main purpose of 1-pass quantization is to provide a fast, if not very + * high quality, colormapped output capability. A 2-pass quantizer usually + * gives better visual quality; however, for quantized grayscale output this + * quantizer is perfectly adequate. Dithering is highly recommended with this + * quantizer, though you can turn it off if you really want to. + * + * In 1-pass quantization the colormap must be chosen in advance of seeing the + * image. We use a map consisting of all combinations of Ncolors[i] color + * values for the i'th component. The Ncolors[] values are chosen so that + * their product, the total number of colors, is no more than that requested. + * (In most cases, the product will be somewhat less.) + * + * Since the colormap is orthogonal, the representative value for each color + * component can be determined without considering the other components; + * then these indexes can be combined into a colormap index by a standard + * N-dimensional-array-subscript calculation. Most of the arithmetic involved + * can be precalculated and stored in the lookup table colorindex[]. + * colorindex[i][j] maps pixel value j in component i to the nearest + * representative value (grid plane) for that component; this index is + * multiplied by the array stride for component i, so that the + * index of the colormap entry closest to a given pixel value is just + * sum( colorindex[component-number][pixel-component-value] ) + * Aside from being fast, this scheme allows for variable spacing between + * representative values with no additional lookup cost. + * + * If gamma correction has been applied in color conversion, it might be wise + * to adjust the color grid spacing so that the representative colors are + * equidistant in linear space. At this writing, gamma correction is not + * implemented by jdcolor, so nothing is done here. + */ + + +/* Declarations for ordered dithering. + * + * We use a standard 16x16 ordered dither array. The basic concept of ordered + * dithering is described in many references, for instance Dale Schumacher's + * chapter II.2 of Graphics Gems II (James Arvo, ed. Academic Press, 1991). + * In place of Schumacher's comparisons against a "threshold" value, we add a + * "dither" value to the input pixel and then round the result to the nearest + * output value. The dither value is equivalent to (0.5 - threshold) times + * the distance between output values. For ordered dithering, we assume that + * the output colors are equally spaced; if not, results will probably be + * worse, since the dither may be too much or too little at a given point. + * + * The normal calculation would be to form pixel value + dither, range-limit + * this to 0..MAXJSAMPLE, and then index into the colorindex table as usual. + * We can skip the separate range-limiting step by extending the colorindex + * table in both directions. + */ + +#define ODITHER_SIZE 16 /* dimension of dither matrix */ +/* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */ +#define ODITHER_CELLS (ODITHER_SIZE*ODITHER_SIZE) /* # cells in matrix */ +#define ODITHER_MASK (ODITHER_SIZE-1) /* mask for wrapping around counters */ + +typedef int ODITHER_MATRIX[ODITHER_SIZE][ODITHER_SIZE]; +typedef int (*ODITHER_MATRIX_PTR)[ODITHER_SIZE]; + +static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = { + /* Bayer's order-4 dither array. Generated by the code given in + * Stephen Hawley's article "Ordered Dithering" in Graphics Gems I. + * The values in this array must range from 0 to ODITHER_CELLS-1. + */ + { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, + { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, + { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, + { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, + { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, + { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, + { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, + { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, + { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, + { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, + { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, + { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, + { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, + { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, + { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, + { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } +}; + + +/* Declarations for Floyd-Steinberg dithering. + * + * Errors are accumulated into the array fserrors[], at a resolution of + * 1/16th of a pixel count. The error at a given pixel is propagated + * to its not-yet-processed neighbors using the standard F-S fractions, + * ... (here) 7/16 + * 3/16 5/16 1/16 + * We work left-to-right on even rows, right-to-left on odd rows. + * + * We can get away with a single array (holding one row's worth of errors) + * by using it to store the current row's errors at pixel columns not yet + * processed, but the next row's errors at columns already processed. We + * need only a few extra variables to hold the errors immediately around the + * current column. (If we are lucky, those variables are in registers, but + * even if not, they're probably cheaper to access than array elements are.) + * + * The fserrors[] array is indexed [component#][position]. + * We provide (#columns + 2) entries per component; the extra entry at each + * end saves us from special-casing the first and last pixels. + * + * Note: on a wide image, we might not have enough room in a PC's near data + * segment to hold the error array; so it is allocated with alloc_large. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef INT16 FSERROR; /* 16 bits should be enough */ +typedef int LOCFSERROR; /* use 'int' for calculation temps */ +#else +typedef INT32 FSERROR; /* may need more than 16 bits */ +typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ +#endif + +typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */ + + +/* Private subobject */ + +#define MAX_Q_COMPS 4 /* max components I can handle */ + +typedef struct { + struct jpeg_color_quantizer pub; /* public fields */ + + /* Initially allocated colormap is saved here */ + JSAMPARRAY sv_colormap; /* The color map as a 2-D pixel array */ + int sv_actual; /* number of entries in use */ + + JSAMPARRAY colorindex; /* Precomputed mapping for speed */ + /* colorindex[i][j] = index of color closest to pixel value j in component i, + * premultiplied as described above. Since colormap indexes must fit into + * JSAMPLEs, the entries of this array will too. + */ + boolean is_padded; /* is the colorindex padded for odither? */ + + int Ncolors[MAX_Q_COMPS]; /* # of values alloced to each component */ + + /* Variables for ordered dithering */ + int row_index; /* cur row's vertical index in dither matrix */ + ODITHER_MATRIX_PTR odither[MAX_Q_COMPS]; /* one dither array per component */ + + /* Variables for Floyd-Steinberg dithering */ + FSERRPTR fserrors[MAX_Q_COMPS]; /* accumulated errors */ + boolean on_odd_row; /* flag to remember which row we are on */ +} my_cquantizer; + +typedef my_cquantizer * my_cquantize_ptr; + + +/* + * Policy-making subroutines for create_colormap and create_colorindex. + * These routines determine the colormap to be used. The rest of the module + * only assumes that the colormap is orthogonal. + * + * * select_ncolors decides how to divvy up the available colors + * among the components. + * * output_value defines the set of representative values for a component. + * * largest_input_value defines the mapping from input values to + * representative values for a component. + * Note that the latter two routines may impose different policies for + * different components, though this is not currently done. + */ + + +LOCAL int +select_ncolors (j_decompress_ptr cinfo, int Ncolors[]) +/* Determine allocation of desired colors to components, */ +/* and fill in Ncolors[] array to indicate choice. */ +/* Return value is total number of colors (product of Ncolors[] values). */ +{ + int nc = cinfo->out_color_components; /* number of color components */ + int max_colors = cinfo->desired_number_of_colors; + int total_colors, iroot, i, j; + boolean changed; + long temp; + static const int RGB_order[3] = { RGB_GREEN, RGB_RED, RGB_BLUE }; + + /* We can allocate at least the nc'th root of max_colors per component. */ + /* Compute floor(nc'th root of max_colors). */ + iroot = 1; + do { + iroot++; + temp = iroot; /* set temp = iroot ** nc */ + for (i = 1; i < nc; i++) + temp *= iroot; + } while (temp <= (long) max_colors); /* repeat till iroot exceeds root */ + iroot--; /* now iroot = floor(root) */ + + /* Must have at least 2 color values per component */ + if (iroot < 2) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, (int) temp); + + /* Initialize to iroot color values for each component */ + total_colors = 1; + for (i = 0; i < nc; i++) { + Ncolors[i] = iroot; + total_colors *= iroot; + } + /* We may be able to increment the count for one or more components without + * exceeding max_colors, though we know not all can be incremented. + * Sometimes, the first component can be incremented more than once! + * (Example: for 16 colors, we start at 2*2*2, go to 3*2*2, then 4*2*2.) + * In RGB colorspace, try to increment G first, then R, then B. + */ + do { + changed = FALSE; + for (i = 0; i < nc; i++) { + j = (cinfo->out_color_space == JCS_RGB ? RGB_order[i] : i); + /* calculate new total_colors if Ncolors[j] is incremented */ + temp = total_colors / Ncolors[j]; + temp *= Ncolors[j]+1; /* done in long arith to avoid oflo */ + if (temp > (long) max_colors) + break; /* won't fit, done with this pass */ + Ncolors[j]++; /* OK, apply the increment */ + total_colors = (int) temp; + changed = TRUE; + } + } while (changed); + + return total_colors; +} + + +LOCAL int +output_value (j_decompress_ptr cinfo, int ci, int j, int maxj) +/* Return j'th output value, where j will range from 0 to maxj */ +/* The output values must fall in 0..MAXJSAMPLE in increasing order */ +{ + /* We always provide values 0 and MAXJSAMPLE for each component; + * any additional values are equally spaced between these limits. + * (Forcing the upper and lower values to the limits ensures that + * dithering can't produce a color outside the selected gamut.) + */ + return (int) (((INT32) j * MAXJSAMPLE + maxj/2) / maxj); +} + + +LOCAL int +largest_input_value (j_decompress_ptr cinfo, int ci, int j, int maxj) +/* Return largest input value that should map to j'th output value */ +/* Must have largest(j=0) >= 0, and largest(j=maxj) >= MAXJSAMPLE */ +{ + /* Breakpoints are halfway between values returned by output_value */ + return (int) (((INT32) (2*j + 1) * MAXJSAMPLE + maxj) / (2*maxj)); +} + + +/* + * Create the colormap. + */ + +LOCAL void +create_colormap (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colormap; /* Created colormap */ + int total_colors; /* Number of distinct output colors */ + int i,j,k, nci, blksize, blkdist, ptr, val; + + /* Select number of colors for each component */ + total_colors = select_ncolors(cinfo, cquantize->Ncolors); + + /* Report selected color counts */ + if (cinfo->out_color_components == 3) + TRACEMS4(cinfo, 1, JTRC_QUANT_3_NCOLORS, + total_colors, cquantize->Ncolors[0], + cquantize->Ncolors[1], cquantize->Ncolors[2]); + else + TRACEMS1(cinfo, 1, JTRC_QUANT_NCOLORS, total_colors); + + /* Allocate and fill in the colormap. */ + /* The colors are ordered in the map in standard row-major order, */ + /* i.e. rightmost (highest-indexed) color changes most rapidly. */ + + colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) total_colors, (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + /* blkdist is distance between groups of identical entries for a component */ + blkdist = total_colors; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colormap entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blkdist / nci; + for (j = 0; j < nci; j++) { + /* Compute j'th output value (out of nci) for component */ + val = output_value(cinfo, i, j, nci-1); + /* Fill in all colormap entries that have this value of this component */ + for (ptr = j * blksize; ptr < total_colors; ptr += blkdist) { + /* fill in blksize entries beginning at ptr */ + for (k = 0; k < blksize; k++) + colormap[i][ptr+k] = (JSAMPLE) val; + } + } + blkdist = blksize; /* blksize of this color is blkdist of next */ + } + + /* Save the colormap in private storage, + * where it will survive color quantization mode changes. + */ + cquantize->sv_colormap = colormap; + cquantize->sv_actual = total_colors; +} + + +/* + * Create the color index table. + */ + +LOCAL void +create_colorindex (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPROW indexptr; + int i,j,k, nci, blksize, val, pad; + + /* For ordered dither, we pad the color index tables by MAXJSAMPLE in + * each direction (input index values can be -MAXJSAMPLE .. 2*MAXJSAMPLE). + * This is not necessary in the other dithering modes. However, we + * flag whether it was done in case user changes dithering mode. + */ + if (cinfo->dither_mode == JDITHER_ORDERED) { + pad = MAXJSAMPLE*2; + cquantize->is_padded = TRUE; + } else { + pad = 0; + cquantize->is_padded = FALSE; + } + + cquantize->colorindex = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (MAXJSAMPLE+1 + pad), + (JDIMENSION) cinfo->out_color_components); + + /* blksize is number of adjacent repeated entries for a component */ + blksize = cquantize->sv_actual; + + for (i = 0; i < cinfo->out_color_components; i++) { + /* fill in colorindex entries for i'th color component */ + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + blksize = blksize / nci; + + /* adjust colorindex pointers to provide padding at negative indexes. */ + if (pad) + cquantize->colorindex[i] += MAXJSAMPLE; + + /* in loop, val = index of current output value, */ + /* and k = largest j that maps to current val */ + indexptr = cquantize->colorindex[i]; + val = 0; + k = largest_input_value(cinfo, i, 0, nci-1); + for (j = 0; j <= MAXJSAMPLE; j++) { + while (j > k) /* advance val if past boundary */ + k = largest_input_value(cinfo, i, ++val, nci-1); + /* premultiply so that no multiplication needed in main processing */ + indexptr[j] = (JSAMPLE) (val * blksize); + } + /* Pad at both ends if necessary */ + if (pad) + for (j = 1; j <= MAXJSAMPLE; j++) { + indexptr[-j] = indexptr[0]; + indexptr[MAXJSAMPLE+j] = indexptr[MAXJSAMPLE]; + } + } +} + + +/* + * Create an ordered-dither array for a component having ncolors + * distinct output values. + */ + +LOCAL ODITHER_MATRIX_PTR +make_odither_array (j_decompress_ptr cinfo, int ncolors) +{ + ODITHER_MATRIX_PTR odither; + int j,k; + INT32 num,den; + + odither = (ODITHER_MATRIX_PTR) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(ODITHER_MATRIX)); + /* The inter-value distance for this color is MAXJSAMPLE/(ncolors-1). + * Hence the dither value for the matrix cell with fill order f + * (f=0..N-1) should be (N-1-2*f)/(2*N) * MAXJSAMPLE/(ncolors-1). + * On 16-bit-int machine, be careful to avoid overflow. + */ + den = 2 * ODITHER_CELLS * ((INT32) (ncolors - 1)); + for (j = 0; j < ODITHER_SIZE; j++) { + for (k = 0; k < ODITHER_SIZE; k++) { + num = ((INT32) (ODITHER_CELLS-1 - 2*((int)base_dither_matrix[j][k]))) + * MAXJSAMPLE; + /* Ensure round towards zero despite C's lack of consistency + * about rounding negative values in integer division... + */ + odither[j][k] = (int) (num<0 ? -((-num)/den) : num/den); + } + } + return odither; +} + + +/* + * Create the ordered-dither tables. + * Components having the same number of representative colors may + * share a dither table. + */ + +LOCAL void +create_odither_tables (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + ODITHER_MATRIX_PTR odither; + int i, j, nci; + + for (i = 0; i < cinfo->out_color_components; i++) { + nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ + odither = NULL; /* search for matching prior component */ + for (j = 0; j < i; j++) { + if (nci == cquantize->Ncolors[j]) { + odither = cquantize->odither[j]; + break; + } + } + if (odither == NULL) /* need a new table? */ + odither = make_odither_array(cinfo, nci); + cquantize->odither[i] = odither; + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF void +color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + JSAMPARRAY colorindex = cquantize->colorindex; + register int pixcode, ci; + register JSAMPROW ptrin, ptrout; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + register int nc = cinfo->out_color_components; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = 0; + for (ci = 0; ci < nc; ci++) { + pixcode += GETJSAMPLE(colorindex[ci][GETJSAMPLE(*ptrin++)]); + } + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF void +color_quantize3 (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW ptrin, ptrout; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptrin = input_buf[row]; + ptrout = output_buf[row]; + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*ptrin++)]); + *ptrout++ = (JSAMPLE) pixcode; + } + } +} + + +METHODDEF void +quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + int * dither; /* points to active row of dither matrix */ + int row_index, col_index; /* current indexes into dither matrix */ + int nc = cinfo->out_color_components; + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + row_index = cquantize->row_index; + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + colorindex_ci = cquantize->colorindex[ci]; + dither = cquantize->odither[ci][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + /* Form pixel value + dither, range-limit to 0..MAXJSAMPLE, + * select output value, accumulate into output code for this pixel. + * Range-limiting need not be done explicitly, as we have extended + * the colorindex table to produce the right answers for out-of-range + * inputs. The maximum dither is +- MAXJSAMPLE; this sets the + * required amount of padding. + */ + *output_ptr += colorindex_ci[GETJSAMPLE(*input_ptr)+dither[col_index]]; + input_ptr += nc; + output_ptr++; + col_index = (col_index + 1) & ODITHER_MASK; + } + } + /* Advance row index for next row */ + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF void +quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* Fast path for out_color_components==3, with ordered dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register int pixcode; + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex0 = cquantize->colorindex[0]; + JSAMPROW colorindex1 = cquantize->colorindex[1]; + JSAMPROW colorindex2 = cquantize->colorindex[2]; + int * dither0; /* points to active row of dither matrix */ + int * dither1; + int * dither2; + int row_index, col_index; /* current indexes into dither matrix */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + row_index = cquantize->row_index; + input_ptr = input_buf[row]; + output_ptr = output_buf[row]; + dither0 = cquantize->odither[0][row_index]; + dither1 = cquantize->odither[1][row_index]; + dither2 = cquantize->odither[2][row_index]; + col_index = 0; + + for (col = width; col > 0; col--) { + pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*input_ptr++) + + dither0[col_index]]); + pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*input_ptr++) + + dither1[col_index]]); + pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*input_ptr++) + + dither2[col_index]]); + *output_ptr++ = (JSAMPLE) pixcode; + col_index = (col_index + 1) & ODITHER_MASK; + } + row_index = (row_index + 1) & ODITHER_MASK; + cquantize->row_index = row_index; + } +} + + +METHODDEF void +quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, + JSAMPARRAY output_buf, int num_rows) +/* General case, with Floyd-Steinberg dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + register LOCFSERROR cur; /* current error or pixel value */ + LOCFSERROR belowerr; /* error for pixel below cur */ + LOCFSERROR bpreverr; /* error for below/prev col */ + LOCFSERROR bnexterr; /* error for below/next col */ + LOCFSERROR delta; + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + register JSAMPROW input_ptr; + register JSAMPROW output_ptr; + JSAMPROW colorindex_ci; + JSAMPROW colormap_ci; + int pixcode; + int nc = cinfo->out_color_components; + int dir; /* 1 for left-to-right, -1 for right-to-left */ + int dirnc; /* dir * nc */ + int ci; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + /* Initialize output values to 0 so can process components separately */ + jzero_far((void FAR *) output_buf[row], + (size_t) (width * SIZEOF(JSAMPLE))); + for (ci = 0; ci < nc; ci++) { + input_ptr = input_buf[row] + ci; + output_ptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + input_ptr += (width-1) * nc; /* so point to rightmost pixel */ + output_ptr += width-1; + dir = -1; + dirnc = -nc; + errorptr = cquantize->fserrors[ci] + (width+1); /* => entry after last column */ + } else { + /* work left to right in this row */ + dir = 1; + dirnc = nc; + errorptr = cquantize->fserrors[ci]; /* => entry before first column */ + } + colorindex_ci = cquantize->colorindex[ci]; + colormap_ci = cquantize->sv_colormap[ci]; + /* Preset error values: no error propagated to first pixel from left */ + cur = 0; + /* and no error propagated to row below yet */ + belowerr = bpreverr = 0; + + for (col = width; col > 0; col--) { + /* cur holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur = RIGHT_SHIFT(cur + errorptr[dir] + 8, 4); + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE; this sets the required size + * of the range_limit array. + */ + cur += GETJSAMPLE(*input_ptr); + cur = GETJSAMPLE(range_limit[cur]); + /* Select output value, accumulate into output code for this pixel */ + pixcode = GETJSAMPLE(colorindex_ci[cur]); + *output_ptr += (JSAMPLE) pixcode; + /* Compute actual representation error at this pixel */ + /* Note: we can do this even though we don't have the final */ + /* pixel code, because the colormap is orthogonal. */ + cur -= GETJSAMPLE(colormap_ci[pixcode]); + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + bnexterr = cur; + delta = cur * 2; + cur += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr + cur); + cur += delta; /* form error * 5 */ + bpreverr = belowerr + cur; + belowerr = bnexterr; + cur += delta; /* form error * 7 */ + /* At this point cur contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + input_ptr += dirnc; /* advance input ptr to next column */ + output_ptr += dir; /* advance output ptr to next column */ + errorptr += dir; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error value into the + * final fserrors[] entry. Note we need not unload belowerr because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr; /* unload prev err into array */ + } + cquantize->on_odd_row = (cquantize->on_odd_row ? FALSE : TRUE); + } +} + + +/* + * Allocate workspace for Floyd-Steinberg errors. + */ + +LOCAL void +alloc_fs_workspace (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) { + cquantize->fserrors[i] = (FSERRPTR) + (*cinfo->mem->alloc_large)((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + } +} + + +/* + * Initialize for one-pass color quantization. + */ + +METHODDEF void +start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + size_t arraysize; + int i; + + /* Install my colormap. */ + cinfo->colormap = cquantize->sv_colormap; + cinfo->actual_number_of_colors = cquantize->sv_actual; + + /* Initialize for desired dithering mode. */ + switch (cinfo->dither_mode) { + case JDITHER_NONE: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = color_quantize3; + else + cquantize->pub.color_quantize = color_quantize; + break; + case JDITHER_ORDERED: + if (cinfo->out_color_components == 3) + cquantize->pub.color_quantize = quantize3_ord_dither; + else + cquantize->pub.color_quantize = quantize_ord_dither; + cquantize->row_index = 0; /* initialize state for ordered dither */ + /* If user changed to ordered dither from another mode, + * we must recreate the color index table with padding. + * This will cost extra space, but probably isn't very likely. + */ + if (! cquantize->is_padded) + create_colorindex(cinfo); + /* Create ordered-dither tables if we didn't already. */ + if (cquantize->odither[0] == NULL) + create_odither_tables(cinfo); + break; + case JDITHER_FS: + cquantize->pub.color_quantize = quantize_fs_dither; + cquantize->on_odd_row = FALSE; /* initialize state for F-S dither */ + /* Allocate Floyd-Steinberg workspace if didn't already. */ + if (cquantize->fserrors[0] == NULL) + alloc_fs_workspace(cinfo); + /* Initialize the propagated errors to zero. */ + arraysize = (size_t) ((cinfo->output_width + 2) * SIZEOF(FSERROR)); + for (i = 0; i < cinfo->out_color_components; i++) + jzero_far((void FAR *) cquantize->fserrors[i], arraysize); + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } +} + + +/* + * Finish up at the end of the pass. + */ + +METHODDEF void +finish_pass_1_quant (j_decompress_ptr cinfo) +{ + /* no work in 1-pass case */ +} + + +/* + * Switch to a new external colormap between output passes. + * Shouldn't get to this module! + */ + +METHODDEF void +new_color_map_1_quant (j_decompress_ptr cinfo) +{ + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + + +/* + * Module initialization routine for 1-pass color quantization. + */ + +GLOBAL void +jinit_1pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize; + + cquantize = (my_cquantize_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_1_quant; + cquantize->pub.finish_pass = finish_pass_1_quant; + cquantize->pub.new_color_map = new_color_map_1_quant; + cquantize->fserrors[0] = NULL; /* Flag FS workspace not allocated */ + cquantize->odither[0] = NULL; /* Also flag odither arrays not allocated */ + + /* Make sure my internal arrays won't overflow */ + if (cinfo->out_color_components > MAX_Q_COMPS) + ERREXIT1(cinfo, JERR_QUANT_COMPONENTS, MAX_Q_COMPS); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (cinfo->desired_number_of_colors > (MAXJSAMPLE+1)) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXJSAMPLE+1); + + /* Create the colormap and color index table. */ + create_colormap(cinfo); + create_colorindex(cinfo); + + /* Allocate Floyd-Steinberg workspace now if requested. + * We do this now since it is FAR storage and may affect the memory + * manager's space calculations. If the user changes to FS dither + * mode in a later pass, we will allocate the space then, and will + * possibly overrun the max_memory_to_use setting. + */ + if (cinfo->dither_mode == JDITHER_FS) + alloc_fs_workspace(cinfo); +} + +#endif /* QUANT_1PASS_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jquant2.c b/src/renderer/jpeg-6/jquant2.c new file mode 100644 index 0000000..301dea8 --- /dev/null +++ b/src/renderer/jpeg-6/jquant2.c @@ -0,0 +1,1334 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jquant2.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains 2-pass color quantization (color mapping) routines. + * These routines provide selection of a custom color map for an image, + * followed by mapping of the image to that color map, with optional + * Floyd-Steinberg dithering. + * It is also possible to use just the second pass to map to an arbitrary + * externally-given color map. + * + * Note: ordered dithering is not supported, since there isn't any fast + * way to compute intercolor distances; it's unclear that ordered dither's + * fundamental assumptions even hold with an irregularly spaced color map. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +#ifdef QUANT_2PASS_SUPPORTED + + +/* + * This module implements the well-known Heckbert paradigm for color + * quantization. Most of the ideas used here can be traced back to + * Heckbert's seminal paper + * Heckbert, Paul. "Color Image Quantization for Frame Buffer Display", + * Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304. + * + * In the first pass over the image, we accumulate a histogram showing the + * usage count of each possible color. To keep the histogram to a reasonable + * size, we reduce the precision of the input; typical practice is to retain + * 5 or 6 bits per color, so that 8 or 4 different input values are counted + * in the same histogram cell. + * + * Next, the color-selection step begins with a box representing the whole + * color space, and repeatedly splits the "largest" remaining box until we + * have as many boxes as desired colors. Then the mean color in each + * remaining box becomes one of the possible output colors. + * + * The second pass over the image maps each input pixel to the closest output + * color (optionally after applying a Floyd-Steinberg dithering correction). + * This mapping is logically trivial, but making it go fast enough requires + * considerable care. + * + * Heckbert-style quantizers vary a good deal in their policies for choosing + * the "largest" box and deciding where to cut it. The particular policies + * used here have proved out well in experimental comparisons, but better ones + * may yet be found. + * + * In earlier versions of the IJG code, this module quantized in YCbCr color + * space, processing the raw upsampled data without a color conversion step. + * This allowed the color conversion math to be done only once per colormap + * entry, not once per pixel. However, that optimization precluded other + * useful optimizations (such as merging color conversion with upsampling) + * and it also interfered with desired capabilities such as quantizing to an + * externally-supplied colormap. We have therefore abandoned that approach. + * The present code works in the post-conversion color space, typically RGB. + * + * To improve the visual quality of the results, we actually work in scaled + * RGB space, giving G distances more weight than R, and R in turn more than + * B. To do everything in integer math, we must use integer scale factors. + * The 2/3/1 scale factors used here correspond loosely to the relative + * weights of the colors in the NTSC grayscale equation. + * If you want to use this code to quantize a non-RGB color space, you'll + * probably need to change these scale factors. + */ + +#define R_SCALE 2 /* scale R distances by this much */ +#define G_SCALE 3 /* scale G distances by this much */ +#define B_SCALE 1 /* and B by this much */ + +/* Relabel R/G/B as components 0/1/2, respecting the RGB ordering defined + * in jmorecfg.h. As the code stands, it will do the right thing for R,G,B + * and B,G,R orders. If you define some other weird order in jmorecfg.h, + * you'll get compile errors until you extend this logic. In that case + * you'll probably want to tweak the histogram sizes too. + */ + +#if RGB_RED == 0 +#define C0_SCALE R_SCALE +#endif +#if RGB_BLUE == 0 +#define C0_SCALE B_SCALE +#endif +#if RGB_GREEN == 1 +#define C1_SCALE G_SCALE +#endif +#if RGB_RED == 2 +#define C2_SCALE R_SCALE +#endif +#if RGB_BLUE == 2 +#define C2_SCALE B_SCALE +#endif + + +/* + * First we have the histogram data structure and routines for creating it. + * + * The number of bits of precision can be adjusted by changing these symbols. + * We recommend keeping 6 bits for G and 5 each for R and B. + * If you have plenty of memory and cycles, 6 bits all around gives marginally + * better results; if you are short of memory, 5 bits all around will save + * some space but degrade the results. + * To maintain a fully accurate histogram, we'd need to allocate a "long" + * (preferably unsigned long) for each cell. In practice this is overkill; + * we can get by with 16 bits per cell. Few of the cell counts will overflow, + * and clamping those that do overflow to the maximum value will give close- + * enough results. This reduces the recommended histogram size from 256Kb + * to 128Kb, which is a useful savings on PC-class machines. + * (In the second pass the histogram space is re-used for pixel mapping data; + * in that capacity, each cell must be able to store zero to the number of + * desired colors. 16 bits/cell is plenty for that too.) + * Since the JPEG code is intended to run in small memory model on 80x86 + * machines, we can't just allocate the histogram in one chunk. Instead + * of a true 3-D array, we use a row of pointers to 2-D arrays. Each + * pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and + * each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries. Note that + * on 80x86 machines, the pointer row is in near memory but the actual + * arrays are in far memory (same arrangement as we use for image arrays). + */ + +#define MAXNUMCOLORS (MAXJSAMPLE+1) /* maximum size of colormap */ + +/* These will do the right thing for either R,G,B or B,G,R color order, + * but you may not like the results for other color orders. + */ +#define HIST_C0_BITS 5 /* bits of precision in R/B histogram */ +#define HIST_C1_BITS 6 /* bits of precision in G histogram */ +#define HIST_C2_BITS 5 /* bits of precision in B/R histogram */ + +/* Number of elements along histogram axes. */ +#define HIST_C0_ELEMS (1<cquantize; + register JSAMPROW ptr; + register histptr histp; + register hist3d histogram = cquantize->histogram; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + ptr = input_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the histogram */ + histp = & histogram[GETJSAMPLE(ptr[0]) >> C0_SHIFT] + [GETJSAMPLE(ptr[1]) >> C1_SHIFT] + [GETJSAMPLE(ptr[2]) >> C2_SHIFT]; + /* increment, check for overflow and undo increment if so. */ + if (++(*histp) <= 0) + (*histp)--; + ptr += 3; + } + } +} + + +/* + * Next we have the really interesting routines: selection of a colormap + * given the completed histogram. + * These routines work with a list of "boxes", each representing a rectangular + * subset of the input color space (to histogram precision). + */ + +typedef struct { + /* The bounds of the box (inclusive); expressed as histogram indexes */ + int c0min, c0max; + int c1min, c1max; + int c2min, c2max; + /* The volume (actually 2-norm) of the box */ + INT32 volume; + /* The number of nonzero histogram cells within this box */ + long colorcount; +} box; + +typedef box * boxptr; + + +LOCAL boxptr +find_biggest_color_pop (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest color population */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register long maxc = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->colorcount > maxc && boxp->volume > 0) { + which = boxp; + maxc = boxp->colorcount; + } + } + return which; +} + + +LOCAL boxptr +find_biggest_volume (boxptr boxlist, int numboxes) +/* Find the splittable box with the largest (scaled) volume */ +/* Returns NULL if no splittable boxes remain */ +{ + register boxptr boxp; + register int i; + register INT32 maxv = 0; + boxptr which = NULL; + + for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) { + if (boxp->volume > maxv) { + which = boxp; + maxv = boxp->volume; + } + } + return which; +} + + +LOCAL void +update_box (j_decompress_ptr cinfo, boxptr boxp) +/* Shrink the min/max bounds of a box to enclose only nonzero elements, */ +/* and recompute its volume and population */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + INT32 dist0,dist1,dist2; + long ccount; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + if (c0max > c0min) + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0min = c0min = c0; + goto have_c0min; + } + } + have_c0min: + if (c0max > c0min) + for (c0 = c0max; c0 >= c0min; c0--) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c0max = c0max = c0; + goto have_c0max; + } + } + have_c0max: + if (c1max > c1min) + for (c1 = c1min; c1 <= c1max; c1++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1min = c1min = c1; + goto have_c1min; + } + } + have_c1min: + if (c1max > c1min) + for (c1 = c1max; c1 >= c1min; c1--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) + if (*histp++ != 0) { + boxp->c1max = c1max = c1; + goto have_c1max; + } + } + have_c1max: + if (c2max > c2min) + for (c2 = c2min; c2 <= c2max; c2++) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2min = c2min = c2; + goto have_c2min; + } + } + have_c2min: + if (c2max > c2min) + for (c2 = c2max; c2 >= c2min; c2--) + for (c0 = c0min; c0 <= c0max; c0++) { + histp = & histogram[c0][c1min][c2]; + for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS) + if (*histp != 0) { + boxp->c2max = c2max = c2; + goto have_c2max; + } + } + have_c2max: + + /* Update box volume. + * We use 2-norm rather than real volume here; this biases the method + * against making long narrow boxes, and it has the side benefit that + * a box is splittable iff norm > 0. + * Since the differences are expressed in histogram-cell units, + * we have to shift back to JSAMPLE units to get consistent distances; + * after which, we scale according to the selected distance scale factors. + */ + dist0 = ((c0max - c0min) << C0_SHIFT) * C0_SCALE; + dist1 = ((c1max - c1min) << C1_SHIFT) * C1_SCALE; + dist2 = ((c2max - c2min) << C2_SHIFT) * C2_SCALE; + boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2; + + /* Now scan remaining volume of box and compute population */ + ccount = 0; + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++, histp++) + if (*histp != 0) { + ccount++; + } + } + boxp->colorcount = ccount; +} + + +LOCAL int +median_cut (j_decompress_ptr cinfo, boxptr boxlist, int numboxes, + int desired_colors) +/* Repeatedly select and split the largest box until we have enough boxes */ +{ + int n,lb; + int c0,c1,c2,cmax; + register boxptr b1,b2; + + while (numboxes < desired_colors) { + /* Select box to split. + * Current algorithm: by population for first half, then by volume. + */ + if (numboxes*2 <= desired_colors) { + b1 = find_biggest_color_pop(boxlist, numboxes); + } else { + b1 = find_biggest_volume(boxlist, numboxes); + } + if (b1 == NULL) /* no splittable boxes left! */ + break; + b2 = &boxlist[numboxes]; /* where new box will go */ + /* Copy the color bounds to the new box. */ + b2->c0max = b1->c0max; b2->c1max = b1->c1max; b2->c2max = b1->c2max; + b2->c0min = b1->c0min; b2->c1min = b1->c1min; b2->c2min = b1->c2min; + /* Choose which axis to split the box on. + * Current algorithm: longest scaled axis. + * See notes in update_box about scaling distances. + */ + c0 = ((b1->c0max - b1->c0min) << C0_SHIFT) * C0_SCALE; + c1 = ((b1->c1max - b1->c1min) << C1_SHIFT) * C1_SCALE; + c2 = ((b1->c2max - b1->c2min) << C2_SHIFT) * C2_SCALE; + /* We want to break any ties in favor of green, then red, blue last. + * This code does the right thing for R,G,B or B,G,R color orders only. + */ +#if RGB_RED == 0 + cmax = c1; n = 1; + if (c0 > cmax) { cmax = c0; n = 0; } + if (c2 > cmax) { n = 2; } +#else + cmax = c1; n = 1; + if (c2 > cmax) { cmax = c2; n = 2; } + if (c0 > cmax) { n = 0; } +#endif + /* Choose split point along selected axis, and update box bounds. + * Current algorithm: split at halfway point. + * (Since the box has been shrunk to minimum volume, + * any split will produce two nonempty subboxes.) + * Note that lb value is max for lower box, so must be < old max. + */ + switch (n) { + case 0: + lb = (b1->c0max + b1->c0min) / 2; + b1->c0max = lb; + b2->c0min = lb+1; + break; + case 1: + lb = (b1->c1max + b1->c1min) / 2; + b1->c1max = lb; + b2->c1min = lb+1; + break; + case 2: + lb = (b1->c2max + b1->c2min) / 2; + b1->c2max = lb; + b2->c2min = lb+1; + break; + } + /* Update stats for boxes */ + update_box(cinfo, b1); + update_box(cinfo, b2); + numboxes++; + } + return numboxes; +} + + +LOCAL void +compute_color (j_decompress_ptr cinfo, boxptr boxp, int icolor) +/* Compute representative color for a box, put it in colormap[icolor] */ +{ + /* Current algorithm: mean weighted by pixels (not colors) */ + /* Note it is important to get the rounding correct! */ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + histptr histp; + int c0,c1,c2; + int c0min,c0max,c1min,c1max,c2min,c2max; + long count; + long total = 0; + long c0total = 0; + long c1total = 0; + long c2total = 0; + + c0min = boxp->c0min; c0max = boxp->c0max; + c1min = boxp->c1min; c1max = boxp->c1max; + c2min = boxp->c2min; c2max = boxp->c2max; + + for (c0 = c0min; c0 <= c0max; c0++) + for (c1 = c1min; c1 <= c1max; c1++) { + histp = & histogram[c0][c1][c2min]; + for (c2 = c2min; c2 <= c2max; c2++) { + if ((count = *histp++) != 0) { + total += count; + c0total += ((c0 << C0_SHIFT) + ((1<>1)) * count; + c1total += ((c1 << C1_SHIFT) + ((1<>1)) * count; + c2total += ((c2 << C2_SHIFT) + ((1<>1)) * count; + } + } + } + + cinfo->colormap[0][icolor] = (JSAMPLE) ((c0total + (total>>1)) / total); + cinfo->colormap[1][icolor] = (JSAMPLE) ((c1total + (total>>1)) / total); + cinfo->colormap[2][icolor] = (JSAMPLE) ((c2total + (total>>1)) / total); +} + + +LOCAL void +select_colors (j_decompress_ptr cinfo, int desired_colors) +/* Master routine for color selection */ +{ + boxptr boxlist; + int numboxes; + int i; + + /* Allocate workspace for box list */ + boxlist = (boxptr) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, desired_colors * SIZEOF(box)); + /* Initialize one box containing whole space */ + numboxes = 1; + boxlist[0].c0min = 0; + boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT; + boxlist[0].c1min = 0; + boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT; + boxlist[0].c2min = 0; + boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT; + /* Shrink it to actually-used volume and set its statistics */ + update_box(cinfo, & boxlist[0]); + /* Perform median-cut to produce final box list */ + numboxes = median_cut(cinfo, boxlist, numboxes, desired_colors); + /* Compute the representative color for each box, fill colormap */ + for (i = 0; i < numboxes; i++) + compute_color(cinfo, & boxlist[i], i); + cinfo->actual_number_of_colors = numboxes; + TRACEMS1(cinfo, 1, JTRC_QUANT_SELECTED, numboxes); +} + + +/* + * These routines are concerned with the time-critical task of mapping input + * colors to the nearest color in the selected colormap. + * + * We re-use the histogram space as an "inverse color map", essentially a + * cache for the results of nearest-color searches. All colors within a + * histogram cell will be mapped to the same colormap entry, namely the one + * closest to the cell's center. This may not be quite the closest entry to + * the actual input color, but it's almost as good. A zero in the cache + * indicates we haven't found the nearest color for that cell yet; the array + * is cleared to zeroes before starting the mapping pass. When we find the + * nearest color for a cell, its colormap index plus one is recorded in the + * cache for future use. The pass2 scanning routines call fill_inverse_cmap + * when they need to use an unfilled entry in the cache. + * + * Our method of efficiently finding nearest colors is based on the "locally + * sorted search" idea described by Heckbert and on the incremental distance + * calculation described by Spencer W. Thomas in chapter III.1 of Graphics + * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that + * the distances from a given colormap entry to each cell of the histogram can + * be computed quickly using an incremental method: the differences between + * distances to adjacent cells themselves differ by a constant. This allows a + * fairly fast implementation of the "brute force" approach of computing the + * distance from every colormap entry to every histogram cell. Unfortunately, + * it needs a work array to hold the best-distance-so-far for each histogram + * cell (because the inner loop has to be over cells, not colormap entries). + * The work array elements have to be INT32s, so the work array would need + * 256Kb at our recommended precision. This is not feasible in DOS machines. + * + * To get around these problems, we apply Thomas' method to compute the + * nearest colors for only the cells within a small subbox of the histogram. + * The work array need be only as big as the subbox, so the memory usage + * problem is solved. Furthermore, we need not fill subboxes that are never + * referenced in pass2; many images use only part of the color gamut, so a + * fair amount of work is saved. An additional advantage of this + * approach is that we can apply Heckbert's locality criterion to quickly + * eliminate colormap entries that are far away from the subbox; typically + * three-fourths of the colormap entries are rejected by Heckbert's criterion, + * and we need not compute their distances to individual cells in the subbox. + * The speed of this approach is heavily influenced by the subbox size: too + * small means too much overhead, too big loses because Heckbert's criterion + * can't eliminate as many colormap entries. Empirically the best subbox + * size seems to be about 1/512th of the histogram (1/8th in each direction). + * + * Thomas' article also describes a refined method which is asymptotically + * faster than the brute-force method, but it is also far more complex and + * cannot efficiently be applied to small subboxes. It is therefore not + * useful for programs intended to be portable to DOS machines. On machines + * with plenty of memory, filling the whole histogram in one shot with Thomas' + * refined method might be faster than the present code --- but then again, + * it might not be any faster, and it's certainly more complicated. + */ + + +/* log2(histogram cells in update box) for each axis; this can be adjusted */ +#define BOX_C0_LOG (HIST_C0_BITS-3) +#define BOX_C1_LOG (HIST_C1_BITS-3) +#define BOX_C2_LOG (HIST_C2_BITS-3) + +#define BOX_C0_ELEMS (1<actual_number_of_colors; + int maxc0, maxc1, maxc2; + int centerc0, centerc1, centerc2; + int i, x, ncolors; + INT32 minmaxdist, min_dist, max_dist, tdist; + INT32 mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */ + + /* Compute true coordinates of update box's upper corner and center. + * Actually we compute the coordinates of the center of the upper-corner + * histogram cell, which are the upper bounds of the volume we care about. + * Note that since ">>" rounds down, the "center" values may be closer to + * min than to max; hence comparisons to them must be "<=", not "<". + */ + maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT)); + centerc0 = (minc0 + maxc0) >> 1; + maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT)); + centerc1 = (minc1 + maxc1) >> 1; + maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT)); + centerc2 = (minc2 + maxc2) >> 1; + + /* For each color in colormap, find: + * 1. its minimum squared-distance to any point in the update box + * (zero if color is within update box); + * 2. its maximum squared-distance to any point in the update box. + * Both of these can be found by considering only the corners of the box. + * We save the minimum distance for each color in mindist[]; + * only the smallest maximum distance is of interest. + */ + minmaxdist = 0x7FFFFFFFL; + + for (i = 0; i < numcolors; i++) { + /* We compute the squared-c0-distance term, then add in the other two. */ + x = GETJSAMPLE(cinfo->colormap[0][i]); + if (x < minc0) { + tdist = (x - minc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else if (x > maxc0) { + tdist = (x - maxc0) * C0_SCALE; + min_dist = tdist*tdist; + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + min_dist = 0; + if (x <= centerc0) { + tdist = (x - maxc0) * C0_SCALE; + max_dist = tdist*tdist; + } else { + tdist = (x - minc0) * C0_SCALE; + max_dist = tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[1][i]); + if (x < minc1) { + tdist = (x - minc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc1) { + tdist = (x - maxc1) * C1_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc1) { + tdist = (x - maxc1) * C1_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc1) * C1_SCALE; + max_dist += tdist*tdist; + } + } + + x = GETJSAMPLE(cinfo->colormap[2][i]); + if (x < minc2) { + tdist = (x - minc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else if (x > maxc2) { + tdist = (x - maxc2) * C2_SCALE; + min_dist += tdist*tdist; + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + /* within cell range so no contribution to min_dist */ + if (x <= centerc2) { + tdist = (x - maxc2) * C2_SCALE; + max_dist += tdist*tdist; + } else { + tdist = (x - minc2) * C2_SCALE; + max_dist += tdist*tdist; + } + } + + mindist[i] = min_dist; /* save away the results */ + if (max_dist < minmaxdist) + minmaxdist = max_dist; + } + + /* Now we know that no cell in the update box is more than minmaxdist + * away from some colormap entry. Therefore, only colors that are + * within minmaxdist of some part of the box need be considered. + */ + ncolors = 0; + for (i = 0; i < numcolors; i++) { + if (mindist[i] <= minmaxdist) + colorlist[ncolors++] = (JSAMPLE) i; + } + return ncolors; +} + + +LOCAL void +find_best_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2, + int numcolors, JSAMPLE colorlist[], JSAMPLE bestcolor[]) +/* Find the closest colormap entry for each cell in the update box, + * given the list of candidate colors prepared by find_nearby_colors. + * Return the indexes of the closest entries in the bestcolor[] array. + * This routine uses Thomas' incremental distance calculation method to + * find the distance from a colormap entry to successive cells in the box. + */ +{ + int ic0, ic1, ic2; + int i, icolor; + register INT32 * bptr; /* pointer into bestdist[] array */ + JSAMPLE * cptr; /* pointer into bestcolor[] array */ + INT32 dist0, dist1; /* initial distance values */ + register INT32 dist2; /* current distance in inner loop */ + INT32 xx0, xx1; /* distance increments */ + register INT32 xx2; + INT32 inc0, inc1, inc2; /* initial values for increments */ + /* This array holds the distance to the nearest-so-far color for each cell */ + INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Initialize best-distance for each cell of the update box */ + bptr = bestdist; + for (i = BOX_C0_ELEMS*BOX_C1_ELEMS*BOX_C2_ELEMS-1; i >= 0; i--) + *bptr++ = 0x7FFFFFFFL; + + /* For each color selected by find_nearby_colors, + * compute its distance to the center of each cell in the box. + * If that's less than best-so-far, update best distance and color number. + */ + + /* Nominal steps between cell centers ("x" in Thomas article) */ +#define STEP_C0 ((1 << C0_SHIFT) * C0_SCALE) +#define STEP_C1 ((1 << C1_SHIFT) * C1_SCALE) +#define STEP_C2 ((1 << C2_SHIFT) * C2_SCALE) + + for (i = 0; i < numcolors; i++) { + icolor = GETJSAMPLE(colorlist[i]); + /* Compute (square of) distance from minc0/c1/c2 to this color */ + inc0 = (minc0 - GETJSAMPLE(cinfo->colormap[0][icolor])) * C0_SCALE; + dist0 = inc0*inc0; + inc1 = (minc1 - GETJSAMPLE(cinfo->colormap[1][icolor])) * C1_SCALE; + dist0 += inc1*inc1; + inc2 = (minc2 - GETJSAMPLE(cinfo->colormap[2][icolor])) * C2_SCALE; + dist0 += inc2*inc2; + /* Form the initial difference increments */ + inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0; + inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1; + inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2; + /* Now loop over all cells in box, updating distance per Thomas method */ + bptr = bestdist; + cptr = bestcolor; + xx0 = inc0; + for (ic0 = BOX_C0_ELEMS-1; ic0 >= 0; ic0--) { + dist1 = dist0; + xx1 = inc1; + for (ic1 = BOX_C1_ELEMS-1; ic1 >= 0; ic1--) { + dist2 = dist1; + xx2 = inc2; + for (ic2 = BOX_C2_ELEMS-1; ic2 >= 0; ic2--) { + if (dist2 < *bptr) { + *bptr = dist2; + *cptr = (JSAMPLE) icolor; + } + dist2 += xx2; + xx2 += 2 * STEP_C2 * STEP_C2; + bptr++; + cptr++; + } + dist1 += xx1; + xx1 += 2 * STEP_C1 * STEP_C1; + } + dist0 += xx0; + xx0 += 2 * STEP_C0 * STEP_C0; + } + } +} + + +LOCAL void +fill_inverse_cmap (j_decompress_ptr cinfo, int c0, int c1, int c2) +/* Fill the inverse-colormap entries in the update box that contains */ +/* histogram cell c0/c1/c2. (Only that one cell MUST be filled, but */ +/* we can fill as many others as we wish.) */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int minc0, minc1, minc2; /* lower left corner of update box */ + int ic0, ic1, ic2; + register JSAMPLE * cptr; /* pointer into bestcolor[] array */ + register histptr cachep; /* pointer into main cache array */ + /* This array lists the candidate colormap indexes. */ + JSAMPLE colorlist[MAXNUMCOLORS]; + int numcolors; /* number of candidate colors */ + /* This array holds the actually closest colormap index for each cell. */ + JSAMPLE bestcolor[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS]; + + /* Convert cell coordinates to update box ID */ + c0 >>= BOX_C0_LOG; + c1 >>= BOX_C1_LOG; + c2 >>= BOX_C2_LOG; + + /* Compute true coordinates of update box's origin corner. + * Actually we compute the coordinates of the center of the corner + * histogram cell, which are the lower bounds of the volume we care about. + */ + minc0 = (c0 << BOX_C0_SHIFT) + ((1 << C0_SHIFT) >> 1); + minc1 = (c1 << BOX_C1_SHIFT) + ((1 << C1_SHIFT) >> 1); + minc2 = (c2 << BOX_C2_SHIFT) + ((1 << C2_SHIFT) >> 1); + + /* Determine which colormap entries are close enough to be candidates + * for the nearest entry to some cell in the update box. + */ + numcolors = find_nearby_colors(cinfo, minc0, minc1, minc2, colorlist); + + /* Determine the actually nearest colors. */ + find_best_colors(cinfo, minc0, minc1, minc2, numcolors, colorlist, + bestcolor); + + /* Save the best color numbers (plus 1) in the main cache array */ + c0 <<= BOX_C0_LOG; /* convert ID back to base cell indexes */ + c1 <<= BOX_C1_LOG; + c2 <<= BOX_C2_LOG; + cptr = bestcolor; + for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) { + for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) { + cachep = & histogram[c0+ic0][c1+ic1][c2]; + for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) { + *cachep++ = (histcell) (GETJSAMPLE(*cptr++) + 1); + } + } + } +} + + +/* + * Map some rows of pixels to the output colormapped representation. + */ + +METHODDEF void +pass2_no_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs no dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register JSAMPROW inptr, outptr; + register histptr cachep; + register int c0, c1, c2; + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + for (col = width; col > 0; col--) { + /* get pixel value and index into the cache */ + c0 = GETJSAMPLE(*inptr++) >> C0_SHIFT; + c1 = GETJSAMPLE(*inptr++) >> C1_SHIFT; + c2 = GETJSAMPLE(*inptr++) >> C2_SHIFT; + cachep = & histogram[c0][c1][c2]; + /* If we have not seen this color before, find nearest colormap entry */ + /* and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, c0,c1,c2); + /* Now emit the colormap index for this cell */ + *outptr++ = (JSAMPLE) (*cachep - 1); + } + } +} + + +METHODDEF void +pass2_fs_dither (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows) +/* This version performs Floyd-Steinberg dithering */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + register LOCFSERROR cur0, cur1, cur2; /* current error or pixel value */ + LOCFSERROR belowerr0, belowerr1, belowerr2; /* error for pixel below cur */ + LOCFSERROR bpreverr0, bpreverr1, bpreverr2; /* error for below/prev col */ + register FSERRPTR errorptr; /* => fserrors[] at column before current */ + JSAMPROW inptr; /* => current input pixel */ + JSAMPROW outptr; /* => current output pixel */ + histptr cachep; + int dir; /* +1 or -1 depending on direction */ + int dir3; /* 3*dir, for advancing inptr & errorptr */ + int row; + JDIMENSION col; + JDIMENSION width = cinfo->output_width; + JSAMPLE *range_limit = cinfo->sample_range_limit; + int *error_limit = cquantize->error_limiter; + JSAMPROW colormap0 = cinfo->colormap[0]; + JSAMPROW colormap1 = cinfo->colormap[1]; + JSAMPROW colormap2 = cinfo->colormap[2]; + SHIFT_TEMPS + + for (row = 0; row < num_rows; row++) { + inptr = input_buf[row]; + outptr = output_buf[row]; + if (cquantize->on_odd_row) { + /* work right to left in this row */ + inptr += (width-1) * 3; /* so point to rightmost pixel */ + outptr += width-1; + dir = -1; + dir3 = -3; + errorptr = cquantize->fserrors + (width+1)*3; /* => entry after last column */ + cquantize->on_odd_row = FALSE; /* flip for next time */ + } else { + /* work left to right in this row */ + dir = 1; + dir3 = 3; + errorptr = cquantize->fserrors; /* => entry before first real column */ + cquantize->on_odd_row = TRUE; /* flip for next time */ + } + /* Preset error values: no error propagated to first pixel from left */ + cur0 = cur1 = cur2 = 0; + /* and no error propagated to row below yet */ + belowerr0 = belowerr1 = belowerr2 = 0; + bpreverr0 = bpreverr1 = bpreverr2 = 0; + + for (col = width; col > 0; col--) { + /* curN holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur0 = RIGHT_SHIFT(cur0 + errorptr[dir3+0] + 8, 4); + cur1 = RIGHT_SHIFT(cur1 + errorptr[dir3+1] + 8, 4); + cur2 = RIGHT_SHIFT(cur2 + errorptr[dir3+2] + 8, 4); + /* Limit the error using transfer function set by init_error_limit. + * See comments with init_error_limit for rationale. + */ + cur0 = error_limit[cur0]; + cur1 = error_limit[cur1]; + cur2 = error_limit[cur2]; + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE (or less with error limiting); + * this sets the required size of the range_limit array. + */ + cur0 += GETJSAMPLE(inptr[0]); + cur1 += GETJSAMPLE(inptr[1]); + cur2 += GETJSAMPLE(inptr[2]); + cur0 = GETJSAMPLE(range_limit[cur0]); + cur1 = GETJSAMPLE(range_limit[cur1]); + cur2 = GETJSAMPLE(range_limit[cur2]); + /* Index into the cache with adjusted pixel value */ + cachep = & histogram[cur0>>C0_SHIFT][cur1>>C1_SHIFT][cur2>>C2_SHIFT]; + /* If we have not seen this color before, find nearest colormap */ + /* entry and update the cache */ + if (*cachep == 0) + fill_inverse_cmap(cinfo, cur0>>C0_SHIFT,cur1>>C1_SHIFT,cur2>>C2_SHIFT); + /* Now emit the colormap index for this cell */ + { register int pixcode = *cachep - 1; + *outptr = (JSAMPLE) pixcode; + /* Compute representation error for this pixel */ + cur0 -= GETJSAMPLE(colormap0[pixcode]); + cur1 -= GETJSAMPLE(colormap1[pixcode]); + cur2 -= GETJSAMPLE(colormap2[pixcode]); + } + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + { register LOCFSERROR bnexterr, delta; + + bnexterr = cur0; /* Process component 0 */ + delta = cur0 * 2; + cur0 += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr0 + cur0); + cur0 += delta; /* form error * 5 */ + bpreverr0 = belowerr0 + cur0; + belowerr0 = bnexterr; + cur0 += delta; /* form error * 7 */ + bnexterr = cur1; /* Process component 1 */ + delta = cur1 * 2; + cur1 += delta; /* form error * 3 */ + errorptr[1] = (FSERROR) (bpreverr1 + cur1); + cur1 += delta; /* form error * 5 */ + bpreverr1 = belowerr1 + cur1; + belowerr1 = bnexterr; + cur1 += delta; /* form error * 7 */ + bnexterr = cur2; /* Process component 2 */ + delta = cur2 * 2; + cur2 += delta; /* form error * 3 */ + errorptr[2] = (FSERROR) (bpreverr2 + cur2); + cur2 += delta; /* form error * 5 */ + bpreverr2 = belowerr2 + cur2; + belowerr2 = bnexterr; + cur2 += delta; /* form error * 7 */ + } + /* At this point curN contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + inptr += dir3; /* Advance pixel pointers to next column */ + outptr += dir; + errorptr += dir3; /* advance errorptr to current column */ + } + /* Post-loop cleanup: we must unload the final error values into the + * final fserrors[] entry. Note we need not unload belowerrN because + * it is for the dummy column before or after the actual array. + */ + errorptr[0] = (FSERROR) bpreverr0; /* unload prev errs into array */ + errorptr[1] = (FSERROR) bpreverr1; + errorptr[2] = (FSERROR) bpreverr2; + } +} + + +/* + * Initialize the error-limiting transfer function (lookup table). + * The raw F-S error computation can potentially compute error values of up to + * +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be + * much less, otherwise obviously wrong pixels will be created. (Typical + * effects include weird fringes at color-area boundaries, isolated bright + * pixels in a dark area, etc.) The standard advice for avoiding this problem + * is to ensure that the "corners" of the color cube are allocated as output + * colors; then repeated errors in the same direction cannot cause cascading + * error buildup. However, that only prevents the error from getting + * completely out of hand; Aaron Giles reports that error limiting improves + * the results even with corner colors allocated. + * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty + * well, but the smoother transfer function used below is even better. Thanks + * to Aaron Giles for this idea. + */ + +LOCAL void +init_error_limit (j_decompress_ptr cinfo) +/* Allocate and fill in the error_limiter table */ +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + int * table; + int in, out; + + table = (int *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, (MAXJSAMPLE*2+1) * SIZEOF(int)); + table += MAXJSAMPLE; /* so can index -MAXJSAMPLE .. +MAXJSAMPLE */ + cquantize->error_limiter = table; + +#define STEPSIZE ((MAXJSAMPLE+1)/16) + /* Map errors 1:1 up to +- MAXJSAMPLE/16 */ + out = 0; + for (in = 0; in < STEPSIZE; in++, out++) { + table[in] = out; table[-in] = -out; + } + /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */ + for (; in < STEPSIZE*3; in++, out += (in&1) ? 0 : 1) { + table[in] = out; table[-in] = -out; + } + /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */ + for (; in <= MAXJSAMPLE; in++) { + table[in] = out; table[-in] = -out; + } +#undef STEPSIZE +} + + +/* + * Finish up at the end of each pass. + */ + +METHODDEF void +finish_pass1 (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Select the representative colors and fill in cinfo->colormap */ + cinfo->colormap = cquantize->sv_colormap; + select_colors(cinfo, cquantize->desired); + /* Force next pass to zero the color index table */ + cquantize->needs_zeroed = TRUE; +} + + +METHODDEF void +finish_pass2 (j_decompress_ptr cinfo) +{ + /* no work */ +} + + +/* + * Initialize for each processing pass. + */ + +METHODDEF void +start_pass_2_quant (j_decompress_ptr cinfo, boolean is_pre_scan) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + hist3d histogram = cquantize->histogram; + int i; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + if (is_pre_scan) { + /* Set up method pointers */ + cquantize->pub.color_quantize = prescan_quantize; + cquantize->pub.finish_pass = finish_pass1; + cquantize->needs_zeroed = TRUE; /* Always zero histogram */ + } else { + /* Set up method pointers */ + if (cinfo->dither_mode == JDITHER_FS) + cquantize->pub.color_quantize = pass2_fs_dither; + else + cquantize->pub.color_quantize = pass2_no_dither; + cquantize->pub.finish_pass = finish_pass2; + + /* Make sure color count is acceptable */ + i = cinfo->actual_number_of_colors; + if (i < 1) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 1); + if (i > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + + if (cinfo->dither_mode == JDITHER_FS) { + size_t arraysize = (size_t) ((cinfo->output_width + 2) * + (3 * SIZEOF(FSERROR))); + /* Allocate Floyd-Steinberg workspace if we didn't already. */ + if (cquantize->fserrors == NULL) + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, arraysize); + /* Initialize the propagated errors to zero. */ + jzero_far((void FAR *) cquantize->fserrors, arraysize); + /* Make the error-limit table if we didn't already. */ + if (cquantize->error_limiter == NULL) + init_error_limit(cinfo); + cquantize->on_odd_row = FALSE; + } + + } + /* Zero the histogram or inverse color map, if necessary */ + if (cquantize->needs_zeroed) { + for (i = 0; i < HIST_C0_ELEMS; i++) { + jzero_far((void FAR *) histogram[i], + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = FALSE; + } +} + + +/* + * Switch to a new external colormap between output passes. + */ + +METHODDEF void +new_color_map_2_quant (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; + + /* Reset the inverse color map */ + cquantize->needs_zeroed = TRUE; +} + + +/* + * Module initialization routine for 2-pass color quantization. + */ + +GLOBAL void +jinit_2pass_quantizer (j_decompress_ptr cinfo) +{ + my_cquantize_ptr cquantize; + int i; + + cquantize = (my_cquantize_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_cquantizer)); + cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; + cquantize->pub.start_pass = start_pass_2_quant; + cquantize->pub.new_color_map = new_color_map_2_quant; + cquantize->fserrors = NULL; /* flag optional arrays not allocated */ + cquantize->error_limiter = NULL; + + /* Make sure jdmaster didn't give me a case I can't handle */ + if (cinfo->out_color_components != 3) + ERREXIT(cinfo, JERR_NOTIMPL); + + /* Allocate the histogram/inverse colormap storage */ + cquantize->histogram = (hist3d) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, HIST_C0_ELEMS * SIZEOF(hist2d)); + for (i = 0; i < HIST_C0_ELEMS; i++) { + cquantize->histogram[i] = (hist2d) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell)); + } + cquantize->needs_zeroed = TRUE; /* histogram is garbage now */ + + /* Allocate storage for the completed colormap, if required. + * We do this now since it is FAR storage and may affect + * the memory manager's space calculations. + */ + if (cinfo->enable_2pass_quant) { + /* Make sure color count is acceptable */ + int desired = cinfo->desired_number_of_colors; + /* Lower bound on # of colors ... somewhat arbitrary as long as > 0 */ + if (desired < 8) + ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 8); + /* Make sure colormap indexes can be represented by JSAMPLEs */ + if (desired > MAXNUMCOLORS) + ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS); + cquantize->sv_colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo,JPOOL_IMAGE, (JDIMENSION) desired, (JDIMENSION) 3); + cquantize->desired = desired; + } else + cquantize->sv_colormap = NULL; + + /* Only F-S dithering or no dithering is supported. */ + /* If user asks for ordered dither, give him F-S. */ + if (cinfo->dither_mode != JDITHER_NONE) + cinfo->dither_mode = JDITHER_FS; + + /* Allocate Floyd-Steinberg workspace if necessary. + * This isn't really needed until pass 2, but again it is FAR storage. + * Although we will cope with a later change in dither_mode, + * we do not promise to honor max_memory_to_use if dither_mode changes. + */ + if (cinfo->dither_mode == JDITHER_FS) { + cquantize->fserrors = (FSERRPTR) (*cinfo->mem->alloc_large) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (size_t) ((cinfo->output_width + 2) * (3 * SIZEOF(FSERROR)))); + /* Might as well create the error-limiting table too. */ + init_error_limit(cinfo); + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ diff --git a/src/renderer/jpeg-6/jutils.c b/src/renderer/jpeg-6/jutils.c new file mode 100644 index 0000000..439fde0 --- /dev/null +++ b/src/renderer/jpeg-6/jutils.c @@ -0,0 +1,199 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jutils.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL long +jdiv_round_up (long a, long b) +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ +{ + return (a + b - 1L) / b; +} + + +GLOBAL long +jround_up (long a, long b) +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ +{ + a += b - 1L; + return a - (a % b); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) +#define FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#endif +#endif + + +GLOBAL void +jcopy_sample_rows (JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols) +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ +{ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for (row = num_rows; row > 0; row--) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY(outptr, inptr, count); +#else + for (count = num_cols; count > 0; count--) + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL void +jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks) +/* Copy a row of coefficient blocks from one place to another. */ +{ +#ifdef FMEMCOPY + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL void +jzero_far (void FAR * target, size_t bytestozero) +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +{ +#ifdef FMEMZERO + FMEMZERO(target, bytestozero); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for (count = bytestozero; count > 0; count--) { + *ptr++ = 0; + } +#endif +} diff --git a/src/renderer/jpeg-6/jversion.h b/src/renderer/jpeg-6/jversion.h new file mode 100644 index 0000000..c2b6c37 --- /dev/null +++ b/src/renderer/jpeg-6/jversion.h @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* + * jversion.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "6 2-Aug-95" + +#define JCOPYRIGHT "Copyright (C) 1995, Thomas G. Lane" diff --git a/src/renderer/material.cpp b/src/renderer/material.cpp new file mode 100644 index 0000000..0d793b4 --- /dev/null +++ b/src/renderer/material.cpp @@ -0,0 +1,2746 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* + +Any errors during parsing just set MF_DEFAULTED and return, rather than throwing +a hard error. This will cause the material to fall back to default material, +but otherwise let things continue. + +Each material may have a set of calculations that must be evaluated before +drawing with it. + +Every expression that a material uses can be evaluated at one time, which +will allow for perfect common subexpression removal when I get around to +writing it. + +Without this, scrolling an entire surface could result in evaluating the +same texture matrix calculations a half dozen times. + + Open question: should I allow arbitrary per-vertex color, texCoord, and vertex + calculations to be specified in the material code? + + Every stage will definately have a valid image pointer. + + We might want the ability to change the sort value based on conditionals, + but it could be a hassle to implement, + +*/ + +// keep all of these on the stack, when they are static it makes material parsing non-reentrant +typedef struct mtrParsingData_s { + bool registerIsTemporary[MAX_EXPRESSION_REGISTERS]; + float shaderRegisters[MAX_EXPRESSION_REGISTERS]; + expOp_t shaderOps[MAX_EXPRESSION_OPS]; + shaderStage_t parseStages[MAX_SHADER_STAGES]; + + bool registersAreConstant; + bool forceOverlays; +} mtrParsingData_t; + + +/* +============= +idMaterial::CommonInit +============= +*/ +void idMaterial::CommonInit() { + desc = ""; + renderBump = ""; + contentFlags = CONTENTS_SOLID; + surfaceFlags = SURFTYPE_NONE; + materialFlags = 0; + sort = SS_BAD; + coverage = MC_BAD; + cullType = CT_FRONT_SIDED; + deform = DFRM_NONE; + numOps = 0; + ops = NULL; + numRegisters = 0; + expressionRegisters = NULL; + constantRegisters = NULL; + numStages = 0; + numAmbientStages = 0; + stages = NULL; + editorImage = NULL; + lightFalloffImage = NULL; + shouldCreateBackSides = false; + entityGui = 0; + fogLight = false; + blendLight = false; + ambientLight = false; + noFog = false; + hasSubview = false; + allowOverlays = true; + unsmoothedTangents = false; + gui = NULL; + memset( deformRegisters, 0, sizeof( deformRegisters ) ); + editorAlpha = 1.0; + spectrum = 0; + polygonOffset = 0; + suppressInSubview = false; + refCount = 0; + portalSky = false; + + decalInfo.stayTime = 10000; + decalInfo.maxAngle = 0.1f; + useCount = 0; +} + +/* +============= +idMaterial::idMaterial +============= +*/ +idMaterial::idMaterial() { + CommonInit(); + + // we put this here instead of in CommonInit, because + // we don't want it cleared when a material is purged + surfaceArea = 0; +} + +/* +============= +idMaterial::~idMaterial +============= +*/ +idMaterial::~idMaterial() { +} + +/* +=============== +idMaterial::FreeData +=============== +*/ +void idMaterial::FreeData() { + int i; + + if ( stages ) { + // delete any idCinematic textures + for ( i = 0; i < numStages; i++ ) { + if ( stages[i].texture.cinematic != NULL ) { + delete stages[i].texture.cinematic; + stages[i].texture.cinematic = NULL; + } + if ( stages[i].newStage != NULL ) { + Mem_Free( stages[i].newStage ); + stages[i].newStage = NULL; + } + } + R_StaticFree( stages ); + stages = NULL; + } + if ( expressionRegisters != NULL ) { + R_StaticFree( expressionRegisters ); + expressionRegisters = NULL; + } + if ( constantRegisters != NULL ) { + R_StaticFree( constantRegisters ); + constantRegisters = NULL; + } + if ( ops != NULL ) { + R_StaticFree( ops ); + ops = NULL; + } +} + +/* +============== +idMaterial::GetEditorImage +============== +*/ +idImage *idMaterial::GetEditorImage( void ) const { + if ( editorImage ) { + return editorImage; + } + + // if we don't have an editorImageName, use the first stage image + if ( !editorImageName.Length()) { + // _D3XP :: First check for a diffuse image, then use the first + if ( numStages && stages ) { + int i; + for( i = 0; i < numStages; i++ ) { + if ( stages[i].lighting == SL_DIFFUSE ) { + editorImage = stages[i].texture.image; + break; + } + } + if ( !editorImage ) { + editorImage = stages[0].texture.image; + } + } else { + editorImage = globalImages->defaultImage; + } + } else { + // look for an explicit one + editorImage = globalImages->ImageFromFile( editorImageName, TF_DEFAULT, true, TR_REPEAT, TD_DEFAULT ); + } + + if ( !editorImage ) { + editorImage = globalImages->defaultImage; + } + + return editorImage; +} + + +// info parms +typedef struct { + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + +static infoParm_t infoParms[] = { + // game relevant attributes + {"solid", 0, 0, CONTENTS_SOLID }, // may need to override a clearSolid + {"water", 1, 0, CONTENTS_WATER }, // used for water + {"playerclip", 0, 0, CONTENTS_PLAYERCLIP }, // solid to players + {"monsterclip", 0, 0, CONTENTS_MONSTERCLIP }, // solid to monsters + {"moveableclip",0, 0, CONTENTS_MOVEABLECLIP },// solid to moveable entities + {"ikclip", 0, 0, CONTENTS_IKCLIP }, // solid to IK + {"blood", 0, 0, CONTENTS_BLOOD }, // used to detect blood decals + {"trigger", 0, 0, CONTENTS_TRIGGER }, // used for triggers + {"aassolid", 0, 0, CONTENTS_AAS_SOLID }, // solid for AAS + {"aasobstacle", 0, 0, CONTENTS_AAS_OBSTACLE },// used to compile an obstacle into AAS that can be enabled/disabled + {"flashlight_trigger", 0, 0, CONTENTS_FLASHLIGHT_TRIGGER }, // used for triggers that are activated by the flashlight + {"nonsolid", 1, 0, 0 }, // clears the solid flag + {"nullNormal", 0, SURF_NULLNORMAL,0 }, // renderbump will draw as 0x80 0x80 0x80 + + // utility relevant attributes + {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas + {"qer_nocarve", 1, 0, CONTENTS_NOCSG}, // don't cut brushes in editor + + {"discrete", 1, SURF_DISCRETE, 0 }, // surfaces should not be automatically merged together or + // clipped to the world, + // because they represent discrete objects like gui shaders + // mirrors, or autosprites + {"noFragment", 0, SURF_NOFRAGMENT, 0 }, + + {"slick", 0, SURF_SLICK, 0 }, + {"collision", 0, SURF_COLLISION, 0 }, + {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nodamage", 0, SURF_NODAMAGE, 0 }, // no falling damage when hitting + {"ladder", 0, SURF_LADDER, 0 }, // climbable + {"nosteps", 0, SURF_NOSTEPS, 0 }, // no footsteps + + // material types for particle, sound, footstep feedback + {"metal", 0, SURFTYPE_METAL, 0 }, // metal + {"stone", 0, SURFTYPE_STONE, 0 }, // stone + {"flesh", 0, SURFTYPE_FLESH, 0 }, // flesh + {"wood", 0, SURFTYPE_WOOD, 0 }, // wood + {"cardboard", 0, SURFTYPE_CARDBOARD, 0 }, // cardboard + {"liquid", 0, SURFTYPE_LIQUID, 0 }, // liquid + {"glass", 0, SURFTYPE_GLASS, 0 }, // glass + {"plastic", 0, SURFTYPE_PLASTIC, 0 }, // plastic + {"ricochet", 0, SURFTYPE_RICOCHET, 0 }, // behaves like metal but causes a ricochet sound + + // unassigned surface types + {"surftype10", 0, SURFTYPE_10, 0 }, + {"surftype11", 0, SURFTYPE_11, 0 }, + {"surftype12", 0, SURFTYPE_12, 0 }, + {"surftype13", 0, SURFTYPE_13, 0 }, + {"surftype14", 0, SURFTYPE_14, 0 }, + {"surftype15", 0, SURFTYPE_15, 0 }, +}; + +static const int numInfoParms = sizeof(infoParms) / sizeof (infoParms[0]); + + +/* +=============== +idMaterial::CheckSurfaceParm + +See if the current token matches one of the surface parm bit flags +=============== +*/ +bool idMaterial::CheckSurfaceParm( idToken *token ) { + + for ( int i = 0 ; i < numInfoParms ; i++ ) { + if ( !token->Icmp( infoParms[i].name ) ) { + if ( infoParms[i].surfaceFlags & SURF_TYPE_MASK ) { + // ensure we only have one surface type set + surfaceFlags &= ~SURF_TYPE_MASK; + } + surfaceFlags |= infoParms[i].surfaceFlags; + contentFlags |= infoParms[i].contents; + if ( infoParms[i].clearSolid ) { + contentFlags &= ~CONTENTS_SOLID; + } + return true; + } + } + return false; +} + +/* +=============== +idMaterial::MatchToken + +Sets defaultShader and returns false if the next token doesn't match +=============== +*/ +bool idMaterial::MatchToken( idLexer &src, const char *match ) { + if ( !src.ExpectTokenString( match ) ) { + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + return true; +} + +/* +================= +idMaterial::ParseSort +================= +*/ +void idMaterial::ParseSort( idLexer &src ) { + idToken token; + + if ( !src.ReadTokenOnLine( &token ) ) { + src.Warning( "missing sort parameter" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + if ( !token.Icmp( "subview" ) ) { + sort = SS_SUBVIEW; + } else if ( !token.Icmp( "opaque" ) ) { + sort = SS_OPAQUE; + }else if ( !token.Icmp( "decal" ) ) { + sort = SS_DECAL; + } else if ( !token.Icmp( "far" ) ) { + sort = SS_FAR; + } else if ( !token.Icmp( "medium" ) ) { + sort = SS_MEDIUM; + } else if ( !token.Icmp( "close" ) ) { + sort = SS_CLOSE; + } else if ( !token.Icmp( "almostNearest" ) ) { + sort = SS_ALMOST_NEAREST; + } else if ( !token.Icmp( "nearest" ) ) { + sort = SS_NEAREST; + } else if ( !token.Icmp( "postProcess" ) ) { + sort = SS_POST_PROCESS; + } else if ( !token.Icmp( "portalSky" ) ) { + sort = SS_PORTAL_SKY; + } else { + sort = atof( token ); + } +} + +/* +================= +idMaterial::ParseDecalInfo +================= +*/ +void idMaterial::ParseDecalInfo( idLexer &src ) { + idToken token; + + decalInfo.stayTime = src.ParseFloat() * 1000; + if ( src.ReadToken( &token ) ) { + if ( token.Icmp( "," ) ) { + src.UnreadToken( &token ); + } else { + decalInfo.maxAngle = src.ParseFloat(); + } + } +} + +/* +============= +idMaterial::GetExpressionConstant +============= +*/ +int idMaterial::GetExpressionConstant( float f ) { + int i; + + for ( i = EXP_REG_NUM_PREDEFINED ; i < numRegisters ; i++ ) { + if ( !pd->registerIsTemporary[i] && pd->shaderRegisters[i] == f ) { + return i; + } + } + if ( numRegisters == MAX_EXPRESSION_REGISTERS ) { + common->Warning( "GetExpressionConstant: material '%s' hit MAX_EXPRESSION_REGISTERS", GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + pd->registerIsTemporary[i] = false; + pd->shaderRegisters[i] = f; + numRegisters++; + + return i; +} + +/* +============= +idMaterial::GetExpressionTemporary +============= +*/ +int idMaterial::GetExpressionTemporary( void ) { + if ( numRegisters == MAX_EXPRESSION_REGISTERS ) { + common->Warning( "GetExpressionTemporary: material '%s' hit MAX_EXPRESSION_REGISTERS", GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + pd->registerIsTemporary[numRegisters] = true; + numRegisters++; + return numRegisters - 1; +} + +/* +============= +idMaterial::GetExpressionOp +============= +*/ +expOp_t *idMaterial::GetExpressionOp( void ) { + if ( numOps == MAX_EXPRESSION_OPS ) { + common->Warning( "GetExpressionOp: material '%s' hit MAX_EXPRESSION_OPS", GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return &pd->shaderOps[0]; + } + + return &pd->shaderOps[numOps++]; +} + +/* +================= +idMaterial::EmitOp +================= +*/ +int idMaterial::EmitOp( int a, int b, expOpType_t opType ) { + expOp_t *op; + + // optimize away identity operations + if ( opType == OP_TYPE_ADD ) { + if ( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 0 ) { + return b; + } + if ( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 0 ) { + return a; + } + if ( !pd->registerIsTemporary[a] && !pd->registerIsTemporary[b] ) { + return GetExpressionConstant( pd->shaderRegisters[a] + pd->shaderRegisters[b] ); + } + } + if ( opType == OP_TYPE_MULTIPLY ) { + if ( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 1 ) { + return b; + } + if ( !pd->registerIsTemporary[a] && pd->shaderRegisters[a] == 0 ) { + return a; + } + if ( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 1 ) { + return a; + } + if ( !pd->registerIsTemporary[b] && pd->shaderRegisters[b] == 0 ) { + return b; + } + if ( !pd->registerIsTemporary[a] && !pd->registerIsTemporary[b] ) { + return GetExpressionConstant( pd->shaderRegisters[a] * pd->shaderRegisters[b] ); + } + } + + op = GetExpressionOp(); + op->opType = opType; + op->a = a; + op->b = b; + op->c = GetExpressionTemporary(); + + return op->c; +} + +/* +================= +idMaterial::ParseEmitOp +================= +*/ +int idMaterial::ParseEmitOp( idLexer &src, int a, expOpType_t opType, int priority ) { + int b; + + b = ParseExpressionPriority( src, priority ); + return EmitOp( a, b, opType ); +} + +/* +================= +idMaterial::ParseTerm + +Returns a register index +================= +*/ +int idMaterial::ParseTerm( idLexer &src ) { + idToken token; + int a, b; + + src.ReadToken( &token ); + + if ( token == "(" ) { + a = ParseExpression( src ); + MatchToken( src, ")" ); + return a; + } + + if ( !token.Icmp( "time" ) ) { + pd->registersAreConstant = false; + return EXP_REG_TIME; + } + if ( !token.Icmp( "parm0" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM0; + } + if ( !token.Icmp( "parm1" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM1; + } + if ( !token.Icmp( "parm2" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM2; + } + if ( !token.Icmp( "parm3" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM3; + } + if ( !token.Icmp( "parm4" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM4; + } + if ( !token.Icmp( "parm5" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM5; + } + if ( !token.Icmp( "parm6" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM6; + } + if ( !token.Icmp( "parm7" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM7; + } + if ( !token.Icmp( "parm8" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM8; + } + if ( !token.Icmp( "parm9" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM9; + } + if ( !token.Icmp( "parm10" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM10; + } + if ( !token.Icmp( "parm11" ) ) { + pd->registersAreConstant = false; + return EXP_REG_PARM11; + } + if ( !token.Icmp( "global0" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL0; + } + if ( !token.Icmp( "global1" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL1; + } + if ( !token.Icmp( "global2" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL2; + } + if ( !token.Icmp( "global3" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL3; + } + if ( !token.Icmp( "global4" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL4; + } + if ( !token.Icmp( "global5" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL5; + } + if ( !token.Icmp( "global6" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL6; + } + if ( !token.Icmp( "global7" ) ) { + pd->registersAreConstant = false; + return EXP_REG_GLOBAL7; + } + if ( !token.Icmp( "fragmentPrograms" ) ) { + return GetExpressionConstant( (float) glConfig.ARBFragmentProgramAvailable ); + } + + if ( !token.Icmp( "sound" ) ) { + pd->registersAreConstant = false; + return EmitOp( 0, 0, OP_TYPE_SOUND ); + } + + // parse negative numbers + if ( token == "-" ) { + src.ReadToken( &token ); + if ( token.type == TT_NUMBER || token == "." ) { + return GetExpressionConstant( -(float) token.GetFloatValue() ); + } + src.Warning( "Bad negative number '%s'", token.c_str() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + + if ( token.type == TT_NUMBER || token == "." || token == "-" ) { + return GetExpressionConstant( (float) token.GetFloatValue() ); + } + + // see if it is a table name + const idDeclTable *table = static_cast( declManager->FindType( DECL_TABLE, token.c_str(), false ) ); + if ( !table ) { + src.Warning( "Bad term '%s'", token.c_str() ); + SetMaterialFlag( MF_DEFAULTED ); + return 0; + } + + // parse a table expression + MatchToken( src, "[" ); + + b = ParseExpression( src ); + + MatchToken( src, "]" ); + + return EmitOp( table->Index(), b, OP_TYPE_TABLE ); +} + +/* +================= +idMaterial::ParseExpressionPriority + +Returns a register index +================= +*/ +#define TOP_PRIORITY 4 +int idMaterial::ParseExpressionPriority( idLexer &src, int priority ) { + idToken token; + int a; + + if ( priority == 0 ) { + return ParseTerm( src ); + } + + a = ParseExpressionPriority( src, priority - 1 ); + + if ( TestMaterialFlag( MF_DEFAULTED ) ) { // we have a parse error + return 0; + } + + if ( !src.ReadToken( &token ) ) { + // we won't get EOF in a real file, but we can + // when parsing from generated strings + return a; + } + + if ( priority == 1 && token == "*" ) { + return ParseEmitOp( src, a, OP_TYPE_MULTIPLY, priority ); + } + if ( priority == 1 && token == "/" ) { + return ParseEmitOp( src, a, OP_TYPE_DIVIDE, priority ); + } + if ( priority == 1 && token == "%" ) { // implied truncate both to integer + return ParseEmitOp( src, a, OP_TYPE_MOD, priority ); + } + if ( priority == 2 && token == "+" ) { + return ParseEmitOp( src, a, OP_TYPE_ADD, priority ); + } + if ( priority == 2 && token == "-" ) { + return ParseEmitOp( src, a, OP_TYPE_SUBTRACT, priority ); + } + if ( priority == 3 && token == ">" ) { + return ParseEmitOp( src, a, OP_TYPE_GT, priority ); + } + if ( priority == 3 && token == ">=" ) { + return ParseEmitOp( src, a, OP_TYPE_GE, priority ); + } + if ( priority == 3 && token == "<" ) { + return ParseEmitOp( src, a, OP_TYPE_LT, priority ); + } + if ( priority == 3 && token == "<=" ) { + return ParseEmitOp( src, a, OP_TYPE_LE, priority ); + } + if ( priority == 3 && token == "==" ) { + return ParseEmitOp( src, a, OP_TYPE_EQ, priority ); + } + if ( priority == 3 && token == "!=" ) { + return ParseEmitOp( src, a, OP_TYPE_NE, priority ); + } + if ( priority == 4 && token == "&&" ) { + return ParseEmitOp( src, a, OP_TYPE_AND, priority ); + } + if ( priority == 4 && token == "||" ) { + return ParseEmitOp( src, a, OP_TYPE_OR, priority ); + } + + // assume that anything else terminates the expression + // not too robust error checking... + + src.UnreadToken( &token ); + + return a; +} + +/* +================= +idMaterial::ParseExpression + +Returns a register index +================= +*/ +int idMaterial::ParseExpression( idLexer &src ) { + return ParseExpressionPriority( src, TOP_PRIORITY ); +} + + +/* +=============== +idMaterial::ClearStage +=============== +*/ +void idMaterial::ClearStage( shaderStage_t *ss ) { + ss->drawStateBits = 0; + ss->conditionRegister = GetExpressionConstant( 1 ); + ss->color.registers[0] = + ss->color.registers[1] = + ss->color.registers[2] = + ss->color.registers[3] = GetExpressionConstant( 1 ); +} + +/* +=============== +idMaterial::NameToSrcBlendMode +=============== +*/ +int idMaterial::NameToSrcBlendMode( const idStr &name ) { + if ( !name.Icmp( "GL_ONE" ) ) { + return GLS_SRCBLEND_ONE; + } else if ( !name.Icmp( "GL_ZERO" ) ) { + return GLS_SRCBLEND_ZERO; + } else if ( !name.Icmp( "GL_DST_COLOR" ) ) { + return GLS_SRCBLEND_DST_COLOR; + } else if ( !name.Icmp( "GL_ONE_MINUS_DST_COLOR" ) ) { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } else if ( !name.Icmp( "GL_SRC_ALPHA" ) ) { + return GLS_SRCBLEND_SRC_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_SRC_ALPHA" ) ) { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( !name.Icmp( "GL_DST_ALPHA" ) ) { + return GLS_SRCBLEND_DST_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_DST_ALPHA" ) ) { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } else if ( !name.Icmp( "GL_SRC_ALPHA_SATURATE" ) ) { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + + common->Warning( "unknown blend mode '%s' in material '%s'", name.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + + return GLS_SRCBLEND_ONE; +} + +/* +=============== +idMaterial::NameToDstBlendMode +=============== +*/ +int idMaterial::NameToDstBlendMode( const idStr &name ) { + if ( !name.Icmp( "GL_ONE" ) ) { + return GLS_DSTBLEND_ONE; + } else if ( !name.Icmp( "GL_ZERO" ) ) { + return GLS_DSTBLEND_ZERO; + } else if ( !name.Icmp( "GL_SRC_ALPHA" ) ) { + return GLS_DSTBLEND_SRC_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_SRC_ALPHA" ) ) { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( !name.Icmp( "GL_DST_ALPHA" ) ) { + return GLS_DSTBLEND_DST_ALPHA; + } else if ( !name.Icmp( "GL_ONE_MINUS_DST_ALPHA" ) ) { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } else if ( !name.Icmp( "GL_SRC_COLOR" ) ) { + return GLS_DSTBLEND_SRC_COLOR; + } else if ( !name.Icmp( "GL_ONE_MINUS_SRC_COLOR" ) ) { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + common->Warning( "unknown blend mode '%s' in material '%s'", name.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + + return GLS_DSTBLEND_ONE; +} + +/* +================ +idMaterial::ParseBlend +================ +*/ +void idMaterial::ParseBlend( idLexer &src, shaderStage_t *stage ) { + idToken token; + int srcBlend, dstBlend; + + if ( !src.ReadToken( &token ) ) { + return; + } + + // blending combinations + if ( !token.Icmp( "blend" ) ) { + stage->drawStateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + return; + } + if ( !token.Icmp( "add" ) ) { + stage->drawStateBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE; + return; + } + if ( !token.Icmp( "filter" ) || !token.Icmp( "modulate" ) ) { + stage->drawStateBits = GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + return; + } + if ( !token.Icmp( "none" ) ) { + // none is used when defining an alpha mask that doesn't draw + stage->drawStateBits = GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE; + return; + } + if ( !token.Icmp( "bumpmap" ) ) { + stage->lighting = SL_BUMP; + return; + } + if ( !token.Icmp( "diffusemap" ) ) { + stage->lighting = SL_DIFFUSE; + return; + } + if ( !token.Icmp( "specularmap" ) ) { + stage->lighting = SL_SPECULAR; + return; + } + + srcBlend = NameToSrcBlendMode( token ); + + MatchToken( src, "," ); + if ( !src.ReadToken( &token ) ) { + return; + } + dstBlend = NameToDstBlendMode( token ); + + stage->drawStateBits = srcBlend | dstBlend; +} + +/* +================ +idMaterial::ParseVertexParm + +If there is a single value, it will be repeated across all elements +If there are two values, 3 = 0.0, 4 = 1.0 +if there are three values, 4 = 1.0 +================ +*/ +void idMaterial::ParseVertexParm( idLexer &src, newShaderStage_t *newStage ) { + idToken token; + + src.ReadTokenOnLine( &token ); + int parm = token.GetIntValue(); + if ( !token.IsNumeric() || parm < 0 || parm >= MAX_VERTEX_PARMS ) { + common->Warning( "bad vertexParm number\n" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + if ( parm >= newStage->numVertexParms ) { + newStage->numVertexParms = parm+1; + } + + newStage->vertexParms[parm][0] = ParseExpression( src ); + + src.ReadTokenOnLine( &token ); + if ( !token[0] || token.Icmp( "," ) ) { + newStage->vertexParms[parm][1] = + newStage->vertexParms[parm][2] = + newStage->vertexParms[parm][3] = newStage->vertexParms[parm][0]; + return; + } + + newStage->vertexParms[parm][1] = ParseExpression( src ); + + src.ReadTokenOnLine( &token ); + if ( !token[0] || token.Icmp( "," ) ) { + newStage->vertexParms[parm][2] = GetExpressionConstant( 0 ); + newStage->vertexParms[parm][3] = GetExpressionConstant( 1 ); + return; + } + + newStage->vertexParms[parm][2] = ParseExpression( src ); + + src.ReadTokenOnLine( &token ); + if ( !token[0] || token.Icmp( "," ) ) { + newStage->vertexParms[parm][3] = GetExpressionConstant( 1 ); + return; + } + + newStage->vertexParms[parm][3] = ParseExpression( src ); +} + + +/* +================ +idMaterial::ParseFragmentMap +================ +*/ +void idMaterial::ParseFragmentMap( idLexer &src, newShaderStage_t *newStage ) { + const char *str; + textureFilter_t tf; + textureRepeat_t trp; + textureDepth_t td; + cubeFiles_t cubeMap; + bool allowPicmip; + idToken token; + + tf = TF_DEFAULT; + trp = TR_REPEAT; + td = TD_DEFAULT; + allowPicmip = true; + cubeMap = CF_2D; + + src.ReadTokenOnLine( &token ); + int unit = token.GetIntValue(); + if ( !token.IsNumeric() || unit < 0 || unit >= MAX_FRAGMENT_IMAGES ) { + common->Warning( "bad fragmentMap number\n" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + + // unit 1 is the normal map.. make sure it gets flagged as the proper depth + if ( unit == 1 ) { + td = TD_BUMP; + } + + if ( unit >= newStage->numFragmentProgramImages ) { + newStage->numFragmentProgramImages = unit+1; + } + + while( 1 ) { + src.ReadTokenOnLine( &token ); + + if ( !token.Icmp( "cubeMap" ) ) { + cubeMap = CF_NATIVE; + continue; + } + if ( !token.Icmp( "cameraCubeMap" ) ) { + cubeMap = CF_CAMERA; + continue; + } + if ( !token.Icmp( "nearest" ) ) { + tf = TF_NEAREST; + continue; + } + if ( !token.Icmp( "linear" ) ) { + tf = TF_LINEAR; + continue; + } + if ( !token.Icmp( "clamp" ) ) { + trp = TR_CLAMP; + continue; + } + if ( !token.Icmp( "noclamp" ) ) { + trp = TR_REPEAT; + continue; + } + if ( !token.Icmp( "zeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO; + continue; + } + if ( !token.Icmp( "alphazeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO_ALPHA; + continue; + } + if ( !token.Icmp( "forceHighQuality" ) ) { + td = TD_HIGH_QUALITY; + continue; + } + + if ( !token.Icmp( "uncompressed" ) || !token.Icmp( "highquality" ) ) { + if ( !globalImages->image_ignoreHighQuality.GetInteger() ) { + td = TD_HIGH_QUALITY; + } + continue; + } + if ( !token.Icmp( "nopicmip" ) ) { + allowPicmip = false; + continue; + } + + // assume anything else is the image name + src.UnreadToken( &token ); + break; + } + str = R_ParsePastImageProgram( src ); + + newStage->fragmentProgramImages[unit] = + globalImages->ImageFromFile( str, tf, allowPicmip, trp, td, cubeMap ); + if ( !newStage->fragmentProgramImages[unit] ) { + newStage->fragmentProgramImages[unit] = globalImages->defaultImage; + } +} + +/* +=============== +idMaterial::MultiplyTextureMatrix +=============== +*/ +void idMaterial::MultiplyTextureMatrix( textureStage_t *ts, int registers[2][3] ) { + int old[2][3]; + + if ( !ts->hasMatrix ) { + ts->hasMatrix = true; + memcpy( ts->matrix, registers, sizeof( ts->matrix ) ); + return; + } + + memcpy( old, ts->matrix, sizeof( old ) ); + + // multiply the two maticies + ts->matrix[0][0] = EmitOp( + EmitOp( old[0][0], registers[0][0], OP_TYPE_MULTIPLY ), + EmitOp( old[0][1], registers[1][0], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[0][1] = EmitOp( + EmitOp( old[0][0], registers[0][1], OP_TYPE_MULTIPLY ), + EmitOp( old[0][1], registers[1][1], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[0][2] = EmitOp( + EmitOp( + EmitOp( old[0][0], registers[0][2], OP_TYPE_MULTIPLY ), + EmitOp( old[0][1], registers[1][2], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + old[0][2], OP_TYPE_ADD ); + + ts->matrix[1][0] = EmitOp( + EmitOp( old[1][0], registers[0][0], OP_TYPE_MULTIPLY ), + EmitOp( old[1][1], registers[1][0], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[1][1] = EmitOp( + EmitOp( old[1][0], registers[0][1], OP_TYPE_MULTIPLY ), + EmitOp( old[1][1], registers[1][1], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ); + ts->matrix[1][2] = EmitOp( + EmitOp( + EmitOp( old[1][0], registers[0][2], OP_TYPE_MULTIPLY ), + EmitOp( old[1][1], registers[1][2], OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + old[1][2], OP_TYPE_ADD ); + +} + +/* +================= +idMaterial::ParseStage + +An open brace has been parsed + + +{ + if + map + "nearest" "linear" "clamp" "zeroclamp" "uncompressed" "highquality" "nopicmip" + scroll, scale, rotate +} + +================= +*/ +bool idMaterial::ParseStage( idLexer &src, const textureRepeat_t trpDefault ) { + idToken token; + const char *str; + shaderStage_t *ss; + textureStage_t *ts; + textureFilter_t tf; + textureRepeat_t trp; + textureDepth_t td; + cubeFiles_t cubeMap; + bool allowPicmip; + char imageName[MAX_IMAGE_NAME]; + int a, b; + int matrix[2][3]; + newShaderStage_t newStage; + + if ( numStages >= MAX_SHADER_STAGES ) { + SetMaterialFlag( MF_DEFAULTED ); + common->Warning( "material '%s' exceeded %i stages", GetName(), MAX_SHADER_STAGES ); + } + + tf = TF_DEFAULT; + trp = trpDefault; + td = TD_DEFAULT; + allowPicmip = true; + cubeMap = CF_2D; + + imageName[0] = 0; + + memset( &newStage, 0, sizeof( newStage ) ); + + ss = &pd->parseStages[numStages]; + ts = &ss->texture; + + ClearStage( ss ); + + while ( 1 ) { + if ( TestMaterialFlag( MF_DEFAULTED ) ) { // we have a parse error + return false; + } + if ( !src.ExpectAnyToken( &token ) ) { + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + + // the close brace for the entire material ends the draw block + if ( token == "}" ) { + break; + } + + //BSM Nerve: Added for stage naming in the material editor + if( !token.Icmp( "name") ) { + src.SkipRestOfLine(); + continue; + } + + // image options + if ( !token.Icmp( "blend" ) ) { + ParseBlend( src, ss ); + continue; + } + + if ( !token.Icmp( "map" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::Copynz( imageName, str, sizeof( imageName ) ); + continue; + } + + if ( !token.Icmp( "remoteRenderMap" ) ) { + ts->dynamic = DI_REMOTE_RENDER; + ts->width = src.ParseInt(); + ts->height = src.ParseInt(); + continue; + } + + if ( !token.Icmp( "mirrorRenderMap" ) ) { + ts->dynamic = DI_MIRROR_RENDER; + ts->width = src.ParseInt(); + ts->height = src.ParseInt(); + ts->texgen = TG_SCREEN; + continue; + } + + if ( !token.Icmp( "screen" ) ) { + ts->texgen = TG_SCREEN; + continue; + } + + if ( !token.Icmp( "videomap" ) ) { + // note that videomaps will always be in clamp mode, so texture + // coordinates had better be in the 0 to 1 range + if ( !src.ReadToken( &token ) ) { + common->Warning( "missing parameter for 'videoMap' keyword in material '%s'", GetName() ); + continue; + } + bool loop = false; + if ( !token.Icmp( "loop" ) ) { + loop = true; + if ( !src.ReadToken( &token ) ) { + common->Warning( "missing parameter for 'videoMap' keyword in material '%s'", GetName() ); + continue; + } + } + ts->cinematic = idCinematic::Alloc(); + ts->cinematic->InitFromFile( token.c_str(), loop ); + continue; + } + + if ( !token.Icmp( "soundmap" ) ) { + if ( !src.ReadToken( &token ) ) { + common->Warning( "missing parameter for 'soundmap' keyword in material '%s'", GetName() ); + continue; + } + ts->cinematic = new idSndWindow(); + ts->cinematic->InitFromFile( token.c_str(), true ); + continue; + } + + if ( !token.Icmp( "cubeMap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::Copynz( imageName, str, sizeof( imageName ) ); + cubeMap = CF_NATIVE; + continue; + } + + if ( !token.Icmp( "cameraCubeMap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::Copynz( imageName, str, sizeof( imageName ) ); + cubeMap = CF_CAMERA; + continue; + } + + if ( !token.Icmp( "ignoreAlphaTest" ) ) { + ss->ignoreAlphaTest = true; + continue; + } + if ( !token.Icmp( "nearest" ) ) { + tf = TF_NEAREST; + continue; + } + if ( !token.Icmp( "linear" ) ) { + tf = TF_LINEAR; + continue; + } + if ( !token.Icmp( "clamp" ) ) { + trp = TR_CLAMP; + continue; + } + if ( !token.Icmp( "noclamp" ) ) { + trp = TR_REPEAT; + continue; + } + if ( !token.Icmp( "zeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO; + continue; + } + if ( !token.Icmp( "alphazeroclamp" ) ) { + trp = TR_CLAMP_TO_ZERO_ALPHA; + continue; + } + if ( !token.Icmp( "uncompressed" ) || !token.Icmp( "highquality" ) ) { + if ( !globalImages->image_ignoreHighQuality.GetInteger() ) { + td = TD_HIGH_QUALITY; + } + continue; + } + if ( !token.Icmp( "forceHighQuality" ) ) { + td = TD_HIGH_QUALITY; + continue; + } + if ( !token.Icmp( "nopicmip" ) ) { + allowPicmip = false; + continue; + } + if ( !token.Icmp( "vertexColor" ) ) { + ss->vertexColor = SVC_MODULATE; + continue; + } + if ( !token.Icmp( "inverseVertexColor" ) ) { + ss->vertexColor = SVC_INVERSE_MODULATE; + continue; + } + + // privatePolygonOffset + else if ( !token.Icmp( "privatePolygonOffset" ) ) { + if ( !src.ReadTokenOnLine( &token ) ) { + ss->privatePolygonOffset = 1; + continue; + } + // explict larger (or negative) offset + src.UnreadToken( &token ); + ss->privatePolygonOffset = src.ParseFloat(); + continue; + } + + // texture coordinate generation + if ( !token.Icmp( "texGen" ) ) { + src.ExpectAnyToken( &token ); + if ( !token.Icmp( "normal" ) ) { + ts->texgen = TG_DIFFUSE_CUBE; + } else if ( !token.Icmp( "reflect" ) ) { + ts->texgen = TG_REFLECT_CUBE; + } else if ( !token.Icmp( "skybox" ) ) { + ts->texgen = TG_SKYBOX_CUBE; + } else if ( !token.Icmp( "wobbleSky" ) ) { + ts->texgen = TG_WOBBLESKY_CUBE; + texGenRegisters[0] = ParseExpression( src ); + texGenRegisters[1] = ParseExpression( src ); + texGenRegisters[2] = ParseExpression( src ); + } else { + common->Warning( "bad texGen '%s' in material %s", token.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + } + continue; + } + if ( !token.Icmp( "scroll" ) || !token.Icmp( "translate" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + matrix[0][0] = GetExpressionConstant( 1 ); + matrix[0][1] = GetExpressionConstant( 0 ); + matrix[0][2] = a; + matrix[1][0] = GetExpressionConstant( 0 ); + matrix[1][1] = GetExpressionConstant( 1 ); + matrix[1][2] = b; + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "scale" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + // this just scales without a centering + matrix[0][0] = a; + matrix[0][1] = GetExpressionConstant( 0 ); + matrix[0][2] = GetExpressionConstant( 0 ); + matrix[1][0] = GetExpressionConstant( 0 ); + matrix[1][1] = b; + matrix[1][2] = GetExpressionConstant( 0 ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "centerScale" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + // this subtracts 0.5, then scales, then adds 0.5 + matrix[0][0] = a; + matrix[0][1] = GetExpressionConstant( 0 ); + matrix[0][2] = EmitOp( GetExpressionConstant( 0.5 ), EmitOp( GetExpressionConstant( 0.5 ), a, OP_TYPE_MULTIPLY ), OP_TYPE_SUBTRACT ); + matrix[1][0] = GetExpressionConstant( 0 ); + matrix[1][1] = b; + matrix[1][2] = EmitOp( GetExpressionConstant( 0.5 ), EmitOp( GetExpressionConstant( 0.5 ), b, OP_TYPE_MULTIPLY ), OP_TYPE_SUBTRACT ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "shear" ) ) { + a = ParseExpression( src ); + MatchToken( src, "," ); + b = ParseExpression( src ); + // this subtracts 0.5, then shears, then adds 0.5 + matrix[0][0] = GetExpressionConstant( 1 ); + matrix[0][1] = a; + matrix[0][2] = EmitOp( GetExpressionConstant( -0.5 ), a, OP_TYPE_MULTIPLY ); + matrix[1][0] = b; + matrix[1][1] = GetExpressionConstant( 1 ); + matrix[1][2] = EmitOp( GetExpressionConstant( -0.5 ), b, OP_TYPE_MULTIPLY ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + if ( !token.Icmp( "rotate" ) ) { + const idDeclTable *table; + int sinReg, cosReg; + + // in cycles + a = ParseExpression( src ); + + table = static_cast( declManager->FindType( DECL_TABLE, "sinTable", false ) ); + if ( !table ) { + common->Warning( "no sinTable for rotate defined" ); + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + sinReg = EmitOp( table->Index(), a, OP_TYPE_TABLE ); + + table = static_cast( declManager->FindType( DECL_TABLE, "cosTable", false ) ); + if ( !table ) { + common->Warning( "no cosTable for rotate defined" ); + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + cosReg = EmitOp( table->Index(), a, OP_TYPE_TABLE ); + + // this subtracts 0.5, then rotates, then adds 0.5 + matrix[0][0] = cosReg; + matrix[0][1] = EmitOp( GetExpressionConstant( 0 ), sinReg, OP_TYPE_SUBTRACT ); + matrix[0][2] = EmitOp( EmitOp( EmitOp( GetExpressionConstant( -0.5 ), cosReg, OP_TYPE_MULTIPLY ), + EmitOp( GetExpressionConstant( 0.5 ), sinReg, OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + GetExpressionConstant( 0.5 ), OP_TYPE_ADD ); + + matrix[1][0] = sinReg; + matrix[1][1] = cosReg; + matrix[1][2] = EmitOp( EmitOp( EmitOp( GetExpressionConstant( -0.5 ), sinReg, OP_TYPE_MULTIPLY ), + EmitOp( GetExpressionConstant( -0.5 ), cosReg, OP_TYPE_MULTIPLY ), OP_TYPE_ADD ), + GetExpressionConstant( 0.5 ), OP_TYPE_ADD ); + + MultiplyTextureMatrix( ts, matrix ); + continue; + } + + // color mask options + if ( !token.Icmp( "maskRed" ) ) { + ss->drawStateBits |= GLS_REDMASK; + continue; + } + if ( !token.Icmp( "maskGreen" ) ) { + ss->drawStateBits |= GLS_GREENMASK; + continue; + } + if ( !token.Icmp( "maskBlue" ) ) { + ss->drawStateBits |= GLS_BLUEMASK; + continue; + } + if ( !token.Icmp( "maskAlpha" ) ) { + ss->drawStateBits |= GLS_ALPHAMASK; + continue; + } + if ( !token.Icmp( "maskColor" ) ) { + ss->drawStateBits |= GLS_COLORMASK; + continue; + } + if ( !token.Icmp( "maskDepth" ) ) { + ss->drawStateBits |= GLS_DEPTHMASK; + continue; + } + if ( !token.Icmp( "alphaTest" ) ) { + ss->hasAlphaTest = true; + ss->alphaTestRegister = ParseExpression( src ); + coverage = MC_PERFORATED; + continue; + } + + // shorthand for 2D modulated + if ( !token.Icmp( "colored" ) ) { + ss->color.registers[0] = EXP_REG_PARM0; + ss->color.registers[1] = EXP_REG_PARM1; + ss->color.registers[2] = EXP_REG_PARM2; + ss->color.registers[3] = EXP_REG_PARM3; + pd->registersAreConstant = false; + continue; + } + + if ( !token.Icmp( "color" ) ) { + ss->color.registers[0] = ParseExpression( src ); + MatchToken( src, "," ); + ss->color.registers[1] = ParseExpression( src ); + MatchToken( src, "," ); + ss->color.registers[2] = ParseExpression( src ); + MatchToken( src, "," ); + ss->color.registers[3] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "red" ) ) { + ss->color.registers[0] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "green" ) ) { + ss->color.registers[1] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "blue" ) ) { + ss->color.registers[2] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "alpha" ) ) { + ss->color.registers[3] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "rgb" ) ) { + ss->color.registers[0] = ss->color.registers[1] = + ss->color.registers[2] = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "rgba" ) ) { + ss->color.registers[0] = ss->color.registers[1] = + ss->color.registers[2] = ss->color.registers[3] = ParseExpression( src ); + continue; + } + + if ( !token.Icmp( "if" ) ) { + ss->conditionRegister = ParseExpression( src ); + continue; + } + if ( !token.Icmp( "program" ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + newStage.vertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, token.c_str() ); + newStage.fragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, token.c_str() ); + } + continue; + } + if ( !token.Icmp( "fragmentProgram" ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + newStage.fragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, token.c_str() ); + } + continue; + } + if ( !token.Icmp( "vertexProgram" ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + newStage.vertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, token.c_str() ); + } + continue; + } + if ( !token.Icmp( "megaTexture" ) ) { + if ( src.ReadTokenOnLine( &token ) ) { + newStage.megaTexture = new idMegaTexture; + if ( !newStage.megaTexture->InitFromMegaFile( token.c_str() ) ) { + delete newStage.megaTexture; + SetMaterialFlag( MF_DEFAULTED ); + continue; + } + newStage.vertexProgram = R_FindARBProgram( GL_VERTEX_PROGRAM_ARB, "megaTexture.vfp" ); + newStage.fragmentProgram = R_FindARBProgram( GL_FRAGMENT_PROGRAM_ARB, "megaTexture.vfp" ); + continue; + } + } + + + if ( !token.Icmp( "vertexParm" ) ) { + ParseVertexParm( src, &newStage ); + continue; + } + + if ( !token.Icmp( "fragmentMap" ) ) { + ParseFragmentMap( src, &newStage ); + continue; + } + + + common->Warning( "unknown token '%s' in material '%s'", token.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + + + // if we are using newStage, allocate a copy of it + if ( newStage.fragmentProgram || newStage.vertexProgram ) { + ss->newStage = (newShaderStage_t *)Mem_Alloc( sizeof( newStage ) ); + *(ss->newStage) = newStage; + } + + // successfully parsed a stage + numStages++; + + // select a compressed depth based on what the stage is + if ( td == TD_DEFAULT ) { + switch( ss->lighting ) { + case SL_BUMP: + td = TD_BUMP; + break; + case SL_DIFFUSE: + td = TD_DIFFUSE; + break; + case SL_SPECULAR: + td = TD_SPECULAR; + break; + default: + break; + } + } + + // now load the image with all the parms we parsed + if ( imageName[0] ) { + ts->image = globalImages->ImageFromFile( imageName, tf, allowPicmip, trp, td, cubeMap ); + if ( !ts->image ) { + ts->image = globalImages->defaultImage; + } + } else if ( !ts->cinematic && !ts->dynamic && !ss->newStage ) { + common->Warning( "material '%s' had stage with no image", GetName() ); + ts->image = globalImages->defaultImage; + } + return true; +} + +/* +=============== +idMaterial::ParseDeform +=============== +*/ +void idMaterial::ParseDeform( idLexer &src ) { + idToken token; + + if ( !src.ExpectAnyToken( &token ) ) { + return; + } + + if ( !token.Icmp( "sprite" ) ) { + deform = DFRM_SPRITE; + cullType = CT_TWO_SIDED; + SetMaterialFlag( MF_NOSHADOWS ); + return; + } + if ( !token.Icmp( "tube" ) ) { + deform = DFRM_TUBE; + cullType = CT_TWO_SIDED; + SetMaterialFlag( MF_NOSHADOWS ); + return; + } + if ( !token.Icmp( "flare" ) ) { + deform = DFRM_FLARE; + cullType = CT_TWO_SIDED; + deformRegisters[0] = ParseExpression( src ); + SetMaterialFlag( MF_NOSHADOWS ); + return; + } + if ( !token.Icmp( "expand" ) ) { + deform = DFRM_EXPAND; + deformRegisters[0] = ParseExpression( src ); + return; + } + if ( !token.Icmp( "move" ) ) { + deform = DFRM_MOVE; + deformRegisters[0] = ParseExpression( src ); + return; + } + if ( !token.Icmp( "turbulent" ) ) { + deform = DFRM_TURB; + + if ( !src.ExpectAnyToken( &token ) ) { + src.Warning( "deform particle missing particle name" ); + SetMaterialFlag( MF_DEFAULTED ); + return; + } + deformDecl = declManager->FindType( DECL_TABLE, token.c_str(), true ); + + deformRegisters[0] = ParseExpression( src ); + deformRegisters[1] = ParseExpression( src ); + deformRegisters[2] = ParseExpression( src ); + return; + } + if ( !token.Icmp( "eyeBall" ) ) { + deform = DFRM_EYEBALL; + return; + } + src.Warning( "Bad deform type '%s'", token.c_str() ); + SetMaterialFlag( MF_DEFAULTED ); +} + + +/* +============== +idMaterial::AddImplicitStages + +If a material has diffuse or specular stages without any +bump stage, add an implicit _flat bumpmap stage. + +If a material has a bump stage but no diffuse or specular +stage, add a _white diffuse stage. + +It is valid to have either a diffuse or specular without the other. + +It is valid to have a reflection map and a bump map for bumpy reflection +============== +*/ +bool idMaterial::AddImplicitStages( const textureRepeat_t trpDefault /* = TR_REPEAT */ ) { + char buffer[1024]; + idLexer newSrc; + bool hasDiffuse = false; + bool hasSpecular = false; + bool hasBump = false; + bool hasReflection = false; + + for ( int i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].lighting == SL_BUMP ) { + hasBump = true; + } + if ( pd->parseStages[i].lighting == SL_DIFFUSE ) { + hasDiffuse = true; + } + if ( pd->parseStages[i].lighting == SL_SPECULAR ) { + hasSpecular = true; + } + if ( pd->parseStages[i].texture.texgen == TG_REFLECT_CUBE ) { + hasReflection = true; + } + } + + // if it doesn't have an interaction at all, don't add anything + if ( !hasBump && !hasDiffuse && !hasSpecular ) { + return true; + } + + if ( numStages == MAX_SHADER_STAGES ) { + return true; + } + + if ( !hasBump ) { + idStr::snPrintf( buffer, sizeof( buffer ), "blend bumpmap\nmap _flat\n}\n" ); + newSrc.LoadMemory( buffer, strlen(buffer), "bumpmap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + if ( !ParseStage( newSrc, trpDefault ) ) { + return false; + } + newSrc.FreeSource(); + } + + if ( !hasDiffuse && !hasSpecular && !hasReflection ) { + idStr::snPrintf( buffer, sizeof( buffer ), "blend diffusemap\nmap _white\n}\n" ); + newSrc.LoadMemory( buffer, strlen(buffer), "diffusemap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + if ( !ParseStage( newSrc, trpDefault ) ) { + return false; + } + newSrc.FreeSource(); + } + return true; +} + +/* +=============== +idMaterial::SortInteractionStages + +The renderer expects bump, then diffuse, then specular +There can be multiple bump maps, followed by additional +diffuse and specular stages, which allows cross-faded bump mapping. + +Ambient stages can be interspersed anywhere, but they are +ignored during interactions, and all the interaction +stages are ignored during ambient drawing. +=============== +*/ +void idMaterial::SortInteractionStages() { + int j; + + for ( int i = 0 ; i < numStages ; i = j ) { + // find the next bump map + for ( j = i + 1 ; j < numStages ; j++ ) { + if ( pd->parseStages[j].lighting == SL_BUMP ) { + // if the very first stage wasn't a bumpmap, + // this bumpmap is part of the first group + if ( pd->parseStages[i].lighting != SL_BUMP ) { + continue; + } + break; + } + } + + // bubble sort everything bump / diffuse / specular + for ( int l = 1 ; l < j-i ; l++ ) { + for ( int k = i ; k < j-l ; k++ ) { + if ( pd->parseStages[k].lighting > pd->parseStages[k+1].lighting ) { + shaderStage_t temp; + + temp = pd->parseStages[k]; + pd->parseStages[k] = pd->parseStages[k+1]; + pd->parseStages[k+1] = temp; + } + } + } + } +} + +/* +================= +idMaterial::ParseMaterial + +The current text pointer is at the explicit text definition of the +Parse it into the global material variable. Later functions will optimize it. + +If there is any error during parsing, defaultShader will be set. +================= +*/ +bool idMaterial::ParseMaterial( idLexer &src ) { + idToken token; + int s; + char buffer[1024]; + const char *str; + idLexer newSrc; + int i; + + s = 0; + + numOps = 0; + numRegisters = EXP_REG_NUM_PREDEFINED; // leave space for the parms to be copied in + for ( i = 0 ; i < numRegisters ; i++ ) { + pd->registerIsTemporary[i] = true; // they aren't constants that can be folded + } + + numStages = 0; + + textureRepeat_t trpDefault = TR_REPEAT; // allow a global setting for repeat + + while ( 1 ) { + if ( TestMaterialFlag( MF_DEFAULTED ) ) { // we have a parse error + return false; + } + if ( !src.ExpectAnyToken( &token ) ) { + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + + // end of material definition + if ( token == "}" ) { + break; + } + else if ( !token.Icmp( "qer_editorimage") ) { + src.ReadTokenOnLine( &token ); + editorImageName = token.c_str(); + src.SkipRestOfLine(); + continue; + } + // description + else if ( !token.Icmp( "description") ) { + src.ReadTokenOnLine( &token ); + desc = token.c_str(); + continue; + } + // check for the surface / content bit flags + else if ( CheckSurfaceParm( &token ) ) { + continue; + } + + + // polygonOffset + else if ( !token.Icmp( "polygonOffset" ) ) { + SetMaterialFlag( MF_POLYGONOFFSET ); + if ( !src.ReadTokenOnLine( &token ) ) { + polygonOffset = 1; + continue; + } + // explict larger (or negative) offset + polygonOffset = token.GetFloatValue(); + continue; + } + // noshadow + else if ( !token.Icmp( "noShadows" ) ) { + SetMaterialFlag( MF_NOSHADOWS ); + continue; + } + else if ( !token.Icmp( "suppressInSubview" ) ) { + suppressInSubview = true; + continue; + } + else if ( !token.Icmp( "portalSky" ) ) { + portalSky = true; + continue; + } + // noSelfShadow + else if ( !token.Icmp( "noSelfShadow" ) ) { + SetMaterialFlag( MF_NOSELFSHADOW ); + continue; + } + // noPortalFog + else if ( !token.Icmp( "noPortalFog" ) ) { + SetMaterialFlag( MF_NOPORTALFOG ); + continue; + } + // forceShadows allows nodraw surfaces to cast shadows + else if ( !token.Icmp( "forceShadows" ) ) { + SetMaterialFlag( MF_FORCESHADOWS ); + continue; + } + // overlay / decal suppression + else if ( !token.Icmp( "noOverlays" ) ) { + allowOverlays = false; + continue; + } + // moster blood overlay forcing for alpha tested or translucent surfaces + else if ( !token.Icmp( "forceOverlays" ) ) { + pd->forceOverlays = true; + continue; + } + // translucent + else if ( !token.Icmp( "translucent" ) ) { + coverage = MC_TRANSLUCENT; + continue; + } + // global zero clamp + else if ( !token.Icmp( "zeroclamp" ) ) { + trpDefault = TR_CLAMP_TO_ZERO; + continue; + } + // global clamp + else if ( !token.Icmp( "clamp" ) ) { + trpDefault = TR_CLAMP; + continue; + } + // global clamp + else if ( !token.Icmp( "alphazeroclamp" ) ) { + trpDefault = TR_CLAMP_TO_ZERO; + continue; + } + // forceOpaque is used for skies-behind-windows + else if ( !token.Icmp( "forceOpaque" ) ) { + coverage = MC_OPAQUE; + continue; + } + // twoSided + else if ( !token.Icmp( "twoSided" ) ) { + cullType = CT_TWO_SIDED; + // twoSided implies no-shadows, because the shadow + // volume would be coplanar with the surface, giving depth fighting + // we could make this no-self-shadows, but it may be more important + // to receive shadows from no-self-shadow monsters + SetMaterialFlag( MF_NOSHADOWS ); + } + // backSided + else if ( !token.Icmp( "backSided" ) ) { + cullType = CT_BACK_SIDED; + // the shadow code doesn't handle this, so just disable shadows. + // We could fix this in the future if there was a need. + SetMaterialFlag( MF_NOSHADOWS ); + } + // foglight + else if ( !token.Icmp( "fogLight" ) ) { + fogLight = true; + continue; + } + // blendlight + else if ( !token.Icmp( "blendLight" ) ) { + blendLight = true; + continue; + } + // ambientLight + else if ( !token.Icmp( "ambientLight" ) ) { + ambientLight = true; + continue; + } + // mirror + else if ( !token.Icmp( "mirror" ) ) { + sort = SS_SUBVIEW; + coverage = MC_OPAQUE; + continue; + } + // noFog + else if ( !token.Icmp( "noFog" ) ) { + noFog = true; + continue; + } + // unsmoothedTangents + else if ( !token.Icmp( "unsmoothedTangents" ) ) { + unsmoothedTangents = true; + continue; + } + // lightFallofImage + // specifies the image to use for the third axis of projected + // light volumes + else if ( !token.Icmp( "lightFalloffImage" ) ) { + str = R_ParsePastImageProgram( src ); + idStr copy; + + copy = str; // so other things don't step on it + lightFalloffImage = globalImages->ImageFromFile( copy, TF_DEFAULT, false, TR_CLAMP /* TR_CLAMP_TO_ZERO */, TD_DEFAULT ); + continue; + } + // guisurf | guisurf entity + // an entity guisurf must have an idUserInterface + // specified in the renderEntity + else if ( !token.Icmp( "guisurf" ) ) { + src.ReadTokenOnLine( &token ); + if ( !token.Icmp( "entity" ) ) { + entityGui = 1; + } else if ( !token.Icmp( "entity2" ) ) { + entityGui = 2; + } else if ( !token.Icmp( "entity3" ) ) { + entityGui = 3; + } else { + gui = uiManager->FindGui( token.c_str(), true ); + } + continue; + } + // sort + else if ( !token.Icmp( "sort" ) ) { + ParseSort( src ); + continue; + } + // spectrum + else if ( !token.Icmp( "spectrum" ) ) { + src.ReadTokenOnLine( &token ); + spectrum = atoi( token.c_str() ); + continue; + } + // deform < sprite | tube | flare > + else if ( !token.Icmp( "deform" ) ) { + ParseDeform( src ); + continue; + } + // decalInfo ( ) ( ) + else if ( !token.Icmp( "decalInfo" ) ) { + ParseDecalInfo( src ); + continue; + } + // renderbump + else if ( !token.Icmp( "renderbump") ) { + src.ParseRestOfLine( renderBump ); + continue; + } + // diffusemap for stage shortcut + else if ( !token.Icmp( "diffusemap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::snPrintf( buffer, sizeof( buffer ), "blend diffusemap\nmap %s\n}\n", str ); + newSrc.LoadMemory( buffer, strlen(buffer), "diffusemap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + continue; + } + // specularmap for stage shortcut + else if ( !token.Icmp( "specularmap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::snPrintf( buffer, sizeof( buffer ), "blend specularmap\nmap %s\n}\n", str ); + newSrc.LoadMemory( buffer, strlen(buffer), "specularmap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + continue; + } + // normalmap for stage shortcut + else if ( !token.Icmp( "bumpmap" ) ) { + str = R_ParsePastImageProgram( src ); + idStr::snPrintf( buffer, sizeof( buffer ), "blend bumpmap\nmap %s\n}\n", str ); + newSrc.LoadMemory( buffer, strlen(buffer), "bumpmap" ); + newSrc.SetFlags( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ); + ParseStage( newSrc, trpDefault ); + newSrc.FreeSource(); + continue; + } + // DECAL_MACRO for backwards compatibility with the preprocessor macros + else if ( !token.Icmp( "DECAL_MACRO" ) ) { + // polygonOffset + SetMaterialFlag( MF_POLYGONOFFSET ); + polygonOffset = 1; + + // discrete + surfaceFlags |= SURF_DISCRETE; + contentFlags &= ~CONTENTS_SOLID; + + // sort decal + sort = SS_DECAL; + + // noShadows + SetMaterialFlag( MF_NOSHADOWS ); + continue; + } + else if ( token == "{" ) { + // create the new stage + ParseStage( src, trpDefault ); + continue; + } + else { + common->Warning( "unknown general material parameter '%s' in '%s'", token.c_str(), GetName() ); + SetMaterialFlag( MF_DEFAULTED ); + return false; + } + } + + // add _flat or _white stages if needed + AddImplicitStages(); + + // order the diffuse / bump / specular stages properly + SortInteractionStages(); + + // if we need to do anything with normals (lighting or environment mapping) + // and two sided lighting was asked for, flag + // shouldCreateBackSides() and change culling back to single sided, + // so we get proper tangent vectors on both sides + + // we can't just call ReceivesLighting(), because the stages are still + // in temporary form + if ( cullType == CT_TWO_SIDED ) { + for ( i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].lighting != SL_AMBIENT || pd->parseStages[i].texture.texgen != TG_EXPLICIT ) { + if ( cullType == CT_TWO_SIDED ) { + cullType = CT_FRONT_SIDED; + shouldCreateBackSides = true; + } + break; + } + } + } + + // currently a surface can only have one unique texgen for all the stages on old hardware + texgen_t firstGen = TG_EXPLICIT; + for ( i = 0; i < numStages; i++ ) { + if ( pd->parseStages[i].texture.texgen != TG_EXPLICIT ) { + if ( firstGen == TG_EXPLICIT ) { + firstGen = pd->parseStages[i].texture.texgen; + } else if ( firstGen != pd->parseStages[i].texture.texgen ) { + common->Warning( "material '%s' has multiple stages with a texgen", GetName() ); + break; + } + } + } + return true; +} + +/* +========================= +idMaterial::SetGui +========================= +*/ +void idMaterial::SetGui( const char *_gui ) const { + gui = uiManager->FindGui( _gui, true, false, true ); +} + +/* +========================= +idMaterial::Parse + +Parses the current material definition and finds all necessary images. +========================= +*/ +bool idMaterial::Parse( const char *text, const int textLength, bool noCaching ) { + idLexer src; + idToken token; + mtrParsingData_t parsingData; + (void)noCaching; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + // reset to the unparsed state + CommonInit(); + + memset( &parsingData, 0, sizeof( parsingData ) ); + + pd = &parsingData; // this is only valid during parse + + // parse it + ParseMaterial( src ); + + // if we are doing an fs_copyfiles, also reference the editorImage + if ( cvarSystem->GetCVarInteger( "fs_copyFiles" ) ) { + GetEditorImage(); + } + + // + // count non-lit stages + numAmbientStages = 0; + int i; + for ( i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].lighting == SL_AMBIENT ) { + numAmbientStages++; + } + } + + // see if there is a subview stage + if ( sort == SS_SUBVIEW ) { + hasSubview = true; + } else { + hasSubview = false; + for ( i = 0 ; i < numStages ; i++ ) { + if ( pd->parseStages[i].texture.dynamic ) { + hasSubview = true; + } + } + } + + // automatically determine coverage if not explicitly set + if ( coverage == MC_BAD ) { + // automatically set MC_TRANSLUCENT if we don't have any interaction stages and + // the first stage is blended and not an alpha test mask or a subview + if ( !numStages ) { + // non-visible + coverage = MC_TRANSLUCENT; + } else if ( numStages != numAmbientStages ) { + // we have an interaction draw + coverage = MC_OPAQUE; + } else if ( + ( pd->parseStages[0].drawStateBits & GLS_DSTBLEND_BITS ) != GLS_DSTBLEND_ZERO || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_COLOR || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_ONE_MINUS_DST_COLOR || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_DST_ALPHA || + ( pd->parseStages[0].drawStateBits & GLS_SRCBLEND_BITS ) == GLS_SRCBLEND_ONE_MINUS_DST_ALPHA + ) { + // blended with the destination + coverage = MC_TRANSLUCENT; + } else { + coverage = MC_OPAQUE; + } + } + + // translucent automatically implies noshadows + if ( coverage == MC_TRANSLUCENT ) { + SetMaterialFlag( MF_NOSHADOWS ); + } else { + // mark the contents as opaque + contentFlags |= CONTENTS_OPAQUE; + } + + // if we are translucent, draw with an alpha in the editor + if ( coverage == MC_TRANSLUCENT ) { + editorAlpha = 0.5; + } else { + editorAlpha = 1.0; + } + + // the sorts can make reasonable defaults + if ( sort == SS_BAD ) { + if ( TestMaterialFlag(MF_POLYGONOFFSET) ) { + sort = SS_DECAL; + } else if ( coverage == MC_TRANSLUCENT ) { + sort = SS_MEDIUM; + } else { + sort = SS_OPAQUE; + } + } + + // anything that references _currentRender will automatically get sort = SS_POST_PROCESS + // and coverage = MC_TRANSLUCENT + + for ( i = 0 ; i < numStages ; i++ ) { + shaderStage_t *pStage = &pd->parseStages[i]; + if ( pStage->texture.image == globalImages->currentRenderImage ) { + if ( sort != SS_PORTAL_SKY ) { + sort = SS_POST_PROCESS; + coverage = MC_TRANSLUCENT; + } + break; + } + if ( pStage->newStage ) { + for ( int j = 0 ; j < pStage->newStage->numFragmentProgramImages ; j++ ) { + if ( pStage->newStage->fragmentProgramImages[j] == globalImages->currentRenderImage ) { + if ( sort != SS_PORTAL_SKY ) { + sort = SS_POST_PROCESS; + coverage = MC_TRANSLUCENT; + } + i = numStages; + break; + } + } + } + } + + // set the drawStateBits depth flags + for ( i = 0 ; i < numStages ; i++ ) { + shaderStage_t *pStage = &pd->parseStages[i]; + if ( sort == SS_POST_PROCESS ) { + // post-process effects fill the depth buffer as they draw, so only the + // topmost post-process effect is rendered + pStage->drawStateBits |= GLS_DEPTHFUNC_LESS; + } else if ( coverage == MC_TRANSLUCENT || pStage->ignoreAlphaTest ) { + // translucent surfaces can extend past the exactly marked depth buffer + pStage->drawStateBits |= GLS_DEPTHFUNC_LESS | GLS_DEPTHMASK; + } else { + // opaque and perforated surfaces must exactly match the depth buffer, + // which gets alpha test correct + pStage->drawStateBits |= GLS_DEPTHFUNC_EQUAL | GLS_DEPTHMASK; + } + } + + // determine if this surface will accept overlays / decals + + if ( pd->forceOverlays ) { + // explicitly flaged in material definition + allowOverlays = true; + } else { + if ( !IsDrawn() ) { + allowOverlays = false; + } + if ( Coverage() != MC_OPAQUE ) { + allowOverlays = false; + } + if ( GetSurfaceFlags() & SURF_NOIMPACT ) { + allowOverlays = false; + } + } + + // add a tiny offset to the sort orders, so that different materials + // that have the same sort value will at least sort consistantly, instead + // of flickering back and forth +/* this messed up in-game guis + if ( sort != SS_SUBVIEW ) { + int hash, l; + + l = name.Length(); + hash = 0; + for ( int i = 0 ; i < l ; i++ ) { + hash ^= name[i]; + } + sort += hash * 0.01; + } +*/ + + if (numStages) { + stages = (shaderStage_t *)R_StaticAlloc( numStages * sizeof( stages[0] ) ); + memcpy( stages, pd->parseStages, numStages * sizeof( stages[0] ) ); + } + + if ( numOps ) { + ops = (expOp_t *)R_StaticAlloc( numOps * sizeof( ops[0] ) ); + memcpy( ops, pd->shaderOps, numOps * sizeof( ops[0] ) ); + } + + if ( numRegisters ) { + expressionRegisters = (float *)R_StaticAlloc( numRegisters * sizeof( expressionRegisters[0] ) ); + memcpy( expressionRegisters, pd->shaderRegisters, numRegisters * sizeof( expressionRegisters[0] ) ); + } + + // see if the registers are completely constant, and don't need to be evaluated + // per-surface + CheckForConstantRegisters(); + + pd = NULL; // the pointer will be invalid after exiting this function + + // finish things up + if ( TestMaterialFlag( MF_DEFAULTED ) ) { + MakeDefault(); + return false; + } + return true; +} + +/* +=================== +idMaterial::Print +=================== +*/ +char *opNames[] = { + "OP_TYPE_ADD", + "OP_TYPE_SUBTRACT", + "OP_TYPE_MULTIPLY", + "OP_TYPE_DIVIDE", + "OP_TYPE_MOD", + "OP_TYPE_TABLE", + "OP_TYPE_GT", + "OP_TYPE_GE", + "OP_TYPE_LT", + "OP_TYPE_LE", + "OP_TYPE_EQ", + "OP_TYPE_NE", + "OP_TYPE_AND", + "OP_TYPE_OR" +}; + +void idMaterial::Print() const { + int i; + + for ( i = EXP_REG_NUM_PREDEFINED ; i < GetNumRegisters() ; i++ ) { + common->Printf( "register %i: %f\n", i, expressionRegisters[i] ); + } + common->Printf( "\n" ); + for ( i = 0 ; i < numOps ; i++ ) { + const expOp_t *op = &ops[i]; + if ( op->opType == OP_TYPE_TABLE ) { + common->Printf( "%i = %s[ %i ]\n", op->c, declManager->DeclByIndex( DECL_TABLE, op->a )->GetName(), op->b ); + } else { + common->Printf( "%i = %i %s %i\n", op->c, op->a, opNames[ op->opType ], op->b ); + } + } +} + +/* +=============== +idMaterial::AddReference +=============== +*/ +void idMaterial::AddReference() { + refCount++; + + for ( int i = 0; i < numStages; i++ ) { + shaderStage_t *s = &stages[i]; + + if ( s->texture.image ) { + s->texture.image->AddReference(); + } + } +} + +int idMaterial::GetTexelCount() const { + int count = 0; + for ( int i = 0; stages && i < numStages; ++i ) { + if ( stages[i].texture.image ) { + const idImage *image = stages[i].texture.image; + count += image->uploadWidth * image->uploadHeight * Max( image->uploadDepth, 1 ); + } + } + return count; +} + +const rvDeclMatType *idMaterial::GetMaterialType( idVec2 &tc ) const { + if ( !materialTypeArray || MTAWidth <= 0 || MTAHeight <= 0 ) { + return materialType; + } + tc.x = fmodf( tc.x, 1.0f ); + tc.y = fmodf( tc.y, 1.0f ); + if ( tc.x < 0.0f ) tc.x += 1.0f; + if ( tc.y < 0.0f ) tc.y += 1.0f; + const int x = idMath::ClampInt( 0, MTAWidth - 1, idMath::Ftoi( tc.x * MTAWidth ) ); + const int y = idMath::ClampInt( 0, MTAHeight - 1, idMath::Ftoi( tc.y * MTAHeight ) ); + const int index = materialTypeArray[x + y * MTAWidth]; + const rvDeclMatType *type = static_cast( + declManager->DeclByIndex( DECL_MATERIALTYPE, index, false ) ); + return type ? type : materialType; +} + +bool idMaterial::Validate( const char *text, int textLength, idStr &report ) const { + (void)report; + if ( textLength <= 0 ) { + return false; + } + idDecl *decl = declManager->AllocateDecl( DECL_MATERIAL ); + const bool valid = decl && decl->Parse( text, textLength, false ); + if ( decl ) { + decl->FreeData(); + delete decl->base; + delete decl; + } + return valid; +} + +/* +=============== +idMaterial::EvaluateRegisters + +Parameters are taken from the localSpace and the renderView, +then all expressions are evaluated, leaving the material registers +set to their apropriate values. +=============== +*/ +void idMaterial::EvaluateRegisters( float *registers, const float shaderParms[MAX_ENTITY_SHADER_PARMS], + const viewDef_t *view, int soundEmitter, idVec3 *randomizer ) const { + int i, b; + expOp_t *op; + + // copy the material constants + for ( i = EXP_REG_NUM_PREDEFINED ; i < numRegisters ; i++ ) { + registers[i] = expressionRegisters[i]; + } + + // copy the local and global parameters + registers[EXP_REG_TIME] = view->floatTime; + registers[EXP_REG_PARM0] = shaderParms[0]; + registers[EXP_REG_PARM1] = shaderParms[1]; + registers[EXP_REG_PARM2] = shaderParms[2]; + registers[EXP_REG_PARM3] = shaderParms[3]; + registers[EXP_REG_PARM4] = shaderParms[4]; + registers[EXP_REG_PARM5] = shaderParms[5]; + registers[EXP_REG_PARM6] = shaderParms[6]; + registers[EXP_REG_PARM7] = shaderParms[7]; + registers[EXP_REG_PARM8] = shaderParms[8]; + registers[EXP_REG_PARM9] = shaderParms[9]; + registers[EXP_REG_PARM10] = shaderParms[10]; + registers[EXP_REG_PARM11] = shaderParms[11]; + registers[EXP_REG_GLOBAL0] = view->renderView.shaderParms[0]; + registers[EXP_REG_GLOBAL1] = view->renderView.shaderParms[1]; + registers[EXP_REG_GLOBAL2] = view->renderView.shaderParms[2]; + registers[EXP_REG_GLOBAL3] = view->renderView.shaderParms[3]; + registers[EXP_REG_GLOBAL4] = view->renderView.shaderParms[4]; + registers[EXP_REG_GLOBAL5] = view->renderView.shaderParms[5]; + registers[EXP_REG_GLOBAL6] = view->renderView.shaderParms[6]; + registers[EXP_REG_GLOBAL7] = view->renderView.shaderParms[7]; + registers[EXP_REG_VERTEX_RANDOMIZER] = randomizer ? randomizer->x + randomizer->y + randomizer->z : 0.0f; + + op = ops; + for ( i = 0 ; i < numOps ; i++, op++ ) { + switch( op->opType ) { + case OP_TYPE_ADD: + registers[op->c] = registers[op->a] + registers[op->b]; + break; + case OP_TYPE_SUBTRACT: + registers[op->c] = registers[op->a] - registers[op->b]; + break; + case OP_TYPE_MULTIPLY: + registers[op->c] = registers[op->a] * registers[op->b]; + break; + case OP_TYPE_DIVIDE: + registers[op->c] = registers[op->a] / registers[op->b]; + break; + case OP_TYPE_MOD: + b = (int)registers[op->b]; + b = b != 0 ? b : 1; + registers[op->c] = (int)registers[op->a] % b; + break; + case OP_TYPE_TABLE: + { + const idDeclTable *table = static_cast( declManager->DeclByIndex( DECL_TABLE, op->a ) ); + registers[op->c] = table->TableLookup( registers[op->b] ); + } + break; + case OP_TYPE_SOUND: + registers[op->c] = 0.0f; + if ( soundEmitter ) { + idSoundEmitter *emitter = soundSystem->EmitterForIndex( -1, soundEmitter ); + if ( emitter ) { + registers[op->c] = emitter->CurrentAmplitude(); + } + } + break; + case OP_TYPE_GT: + registers[op->c] = registers[ op->a ] > registers[op->b]; + break; + case OP_TYPE_GE: + registers[op->c] = registers[ op->a ] >= registers[op->b]; + break; + case OP_TYPE_LT: + registers[op->c] = registers[ op->a ] < registers[op->b]; + break; + case OP_TYPE_LE: + registers[op->c] = registers[ op->a ] <= registers[op->b]; + break; + case OP_TYPE_EQ: + registers[op->c] = registers[ op->a ] == registers[op->b]; + break; + case OP_TYPE_NE: + registers[op->c] = registers[ op->a ] != registers[op->b]; + break; + case OP_TYPE_AND: + registers[op->c] = registers[ op->a ] && registers[op->b]; + break; + case OP_TYPE_OR: + registers[op->c] = registers[ op->a ] || registers[op->b]; + break; + default: + common->FatalError( "R_EvaluateExpression: bad opcode" ); + } + } + +} + +void idMaterial::EvaluateStageRegisters( int stageIndex, float *registers, + const float shaderParms[MAX_ENTITY_SHADER_PARMS], float floatTime ) const { + if ( !registers || !shaderParms || stageIndex < 0 || stageIndex >= numStages ) { + return; + } + // The retail routine limits evaluation to this stage's operation span. + // Evaluating the complete expression program preserves shared dependencies + // and produces the same values for the requested stage registers. + viewDef_t localView; + memset( &localView, 0, sizeof( localView ) ); + localView.floatTime = floatTime; + EvaluateRegisters( registers, shaderParms, &localView, 0, NULL ); +} + +/* +============= +idMaterial::Texgen +============= +*/ +texgen_t idMaterial::Texgen() const { + if ( stages ) { + for ( int i = 0; i < numStages; i++ ) { + if ( stages[ i ].texture.texgen != TG_EXPLICIT ) { + return stages[ i ].texture.texgen; + } + } + } + + return TG_EXPLICIT; +} + +/* +============= +idMaterial::GetImageWidth +============= +*/ +int idMaterial::GetImageWidth( void ) const { + assert( GetStage(0) && GetStage(0)->texture.image ); + return GetStage(0)->texture.image->uploadWidth; +} + +/* +============= +idMaterial::GetImageHeight +============= +*/ +int idMaterial::GetImageHeight( void ) const { + assert( GetStage(0) && GetStage(0)->texture.image ); + return GetStage(0)->texture.image->uploadHeight; +} + +/* +============= +idMaterial::CinematicLength +============= +*/ +int idMaterial::CinematicLength() const { + if ( !stages || !stages[0].texture.cinematic ) { + return 0; + } + return stages[0].texture.cinematic->AnimationLength(); +} + +/* +============= +idMaterial::UpdateCinematic +============= +*/ +void idMaterial::UpdateCinematic( int time ) const { + if ( !stages || !stages[0].texture.cinematic || !backEnd.viewDef ) { + return; + } + stages[0].texture.cinematic->ImageForTime( tr.primaryRenderView.time ); +} + +/* +============= +idMaterial::CloseCinematic +============= +*/ +void idMaterial::CloseCinematic( void ) const { + for( int i = 0; i < numStages; i++ ) { + if ( stages[i].texture.cinematic ) { + stages[i].texture.cinematic->Close(); + delete stages[i].texture.cinematic; + stages[i].texture.cinematic = NULL; + } + } +} + +/* +============= +idMaterial::ResetCinematicTime +============= +*/ +void idMaterial::ResetCinematicTime( int time ) const { + for( int i = 0; i < numStages; i++ ) { + if ( stages[i].texture.cinematic ) { + stages[i].texture.cinematic->ResetTime( time ); + } + } +} + +/* +============= +idMaterial::ConstantRegisters +============= +*/ +const float *idMaterial::ConstantRegisters() const { + if ( !r_useConstantMaterials.GetBool() ) { + return NULL; + } + return constantRegisters; +} + +/* +================== +idMaterial::CheckForConstantRegisters + +As of 5/2/03, about half of the unique materials loaded on typical +maps are constant, but 2/3 of the surface references are. +This is probably an optimization of dubious value. +================== +*/ +static int c_constant, c_variable; +void idMaterial::CheckForConstantRegisters() { + if ( !pd->registersAreConstant ) { + return; + } + + // evaluate the registers once, and save them + constantRegisters = (float *)R_ClearedStaticAlloc( GetNumRegisters() * sizeof( float ) ); + + float shaderParms[MAX_ENTITY_SHADER_PARMS]; + memset( shaderParms, 0, sizeof( shaderParms ) ); + viewDef_t viewDef; + memset( &viewDef, 0, sizeof( viewDef ) ); + + EvaluateRegisters( constantRegisters, shaderParms, &viewDef, 0 ); +} + +/* +=================== +idMaterial::ImageName +=================== +*/ +const char *idMaterial::ImageName( void ) const { + if ( numStages == 0 ) { + return "_scratch"; + } + idImage *image = stages[0].texture.image; + if ( image ) { + return image->imgName; + } + return "_scratch"; +} + +/* +=================== +idMaterial::SetImageClassifications + +Just for image resource tracking. +=================== +*/ +void idMaterial::SetImageClassifications( int tag ) const { + for ( int i = 0 ; i < numStages ; i++ ) { + idImage *image = stages[i].texture.image; + if ( image ) { + image->SetClassification( tag ); + } + } +} + +/* +================= +idMaterial::Size +================= +*/ +size_t idMaterial::Size( void ) const { + return sizeof( idMaterial ); +} + +/* +=================== +idMaterial::SetDefaultText +=================== +*/ +bool idMaterial::SetDefaultText( void ) { + // if there exists an image with the same name + if ( 1 ) { //fileSystem->ReadFile( GetName(), NULL ) != -1 ) { + char generated[2048]; + idStr::snPrintf( generated, sizeof( generated ), + "material %s // IMPLICITLY GENERATED\n" + "{\n" + "{\n" + "blend blend\n" + "colored\n" + "map \"%s\"\n" + "clamp\n" + "}\n" + "}\n", GetName(), GetName() ); + SetText( generated ); + return true; + } else { + return false; + } +} + +/* +=================== +idMaterial::DefaultDefinition +=================== +*/ +const char *idMaterial::DefaultDefinition() const { + return + "{\n" + "\t" "{\n" + "\t\t" "blend\tblend\n" + "\t\t" "map\t\t_default\n" + "\t" "}\n" + "}"; +} + + +/* +=================== +idMaterial::GetBumpStage +=================== +*/ +const shaderStage_t *idMaterial::GetBumpStage( void ) const { + for ( int i = 0 ; i < numStages ; i++ ) { + if ( stages[i].lighting == SL_BUMP ) { + return &stages[i]; + } + } + return NULL; +} + +/* +=================== +idMaterial::ReloadImages +=================== +*/ +void idMaterial::ReloadImages( bool force ) const +{ + for ( int i = 0 ; i < numStages ; i++ ) { + if ( stages[i].newStage ) { + for ( int j = 0 ; j < stages[i].newStage->numFragmentProgramImages ; j++ ) { + if ( stages[i].newStage->fragmentProgramImages[j] ) { + stages[i].newStage->fragmentProgramImages[j]->Reload( false, force ); + } + } + } else if ( stages[i].texture.image ) { + stages[i].texture.image->Reload( false, force ); + } + } +} diff --git a/src/renderer/megatexture.cpp b/src/renderer/megatexture.cpp new file mode 100644 index 0000000..fe5e11f --- /dev/null +++ b/src/renderer/megatexture.cpp @@ -0,0 +1,909 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +idCVar idMegaTexture::r_megaTextureLevel( "r_megaTextureLevel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw only a specific level" ); +idCVar idMegaTexture::r_showMegaTexture( "r_showMegaTexture", "0", CVAR_RENDERER | CVAR_BOOL, "display all the level images" ); +idCVar idMegaTexture::r_showMegaTextureLabels( "r_showMegaTextureLabels", "0", CVAR_RENDERER | CVAR_BOOL, "draw colored blocks in each tile" ); +idCVar idMegaTexture::r_skipMegaTexture( "r_skipMegaTexture", "0", CVAR_RENDERER | CVAR_INTEGER, "only use the lowest level image" ); +idCVar idMegaTexture::r_terrainScale( "r_terrainScale", "3", CVAR_RENDERER | CVAR_INTEGER, "vertically scale USGS data" ); + +/* + +allow sparse population of the upper detail tiles + +*/ + +int RoundDownToPowerOfTwo( int num ) { + int pot; + for (pot = 1 ; (pot*2) <= num ; pot<<=1) { + } + return pot; +} + +static union { + int intVal; + byte color[4]; +} fillColor; + +static byte colors[8][4] = { + { 0, 0, 0, 255 }, + { 255, 0, 0, 255 }, + { 0, 255, 0, 255 }, + { 255, 255, 0, 255 }, + { 0, 0, 255, 255 }, + { 255, 0, 255, 255 }, + { 0, 255, 255, 255 }, + { 255, 255, 255, 255 } +}; + +static void R_EmptyLevelImage( idImage *image ) { + int c = MAX_LEVEL_WIDTH * MAX_LEVEL_WIDTH; + byte *data = (byte *)_alloca( c*4 ); + + for ( int i = 0 ; i < c ; i++ ) { + ((int *)data)[i] = fillColor.intVal; + } + + // FIXME: this won't live past vid mode changes + image->GenerateImage( data, MAX_LEVEL_WIDTH, MAX_LEVEL_WIDTH, + TF_DEFAULT, false, TR_REPEAT, TD_HIGH_QUALITY ); +} + + +/* +==================== +InitFromMegaFile +==================== +*/ +bool idMegaTexture::InitFromMegaFile( const char *fileBase ) { + idStr name = "megaTextures/"; + name += fileBase; + name.StripFileExtension(); + name += ".mega"; + + int width, height; + + fileHandle = fileSystem->OpenFileRead( name.c_str() ); + if ( !fileHandle ) { + common->Printf( "idMegaTexture: failed to open %s\n", name.c_str() ); + return false; + } + + fileHandle->Read( &header, sizeof( header ) ); + if ( header.tileSize < 64 || header.tilesWide < 1 || header.tilesHigh < 1 ) { + common->Printf( "idMegaTexture: bad header on %s\n", name.c_str() ); + return false; + } + + currentTriMapping = NULL; + + numLevels = 0; + width = header.tilesWide; + height = header.tilesHigh; + + int tileOffset = 1; // just past the header + + memset( levels, 0, sizeof( levels ) ); + while( 1 ) { + idTextureLevel *level = &levels[numLevels]; + + level->mega = this; + level->tileOffset = tileOffset; + level->tilesWide = width; + level->tilesHigh = height; + level->parms[0] = -1; // initially mask everything + level->parms[1] = 0; + level->parms[2] = 0; + level->parms[3] = (float)width / TILE_PER_LEVEL; + level->Invalidate(); + + tileOffset += level->tilesWide * level->tilesHigh; + + char str[1024]; + sprintf( str, "MEGA_%s_%i", fileBase, numLevels ); + + // give each level a default fill color + for (int i = 0 ; i < 4 ; i++ ) { + fillColor.color[i] = colors[numLevels+1][i]; + } + + levels[numLevels].image = globalImages->ImageFromFunction( str, R_EmptyLevelImage ); + numLevels++; + + if ( width <= TILE_PER_LEVEL && height <= TILE_PER_LEVEL ) { + break; + } + width = ( width + 1 ) >> 1; + height = ( height + 1 ) >> 1; + } + + // force first bind to load everything + currentViewOrigin[0] = -99999999.0f; + currentViewOrigin[1] = -99999999.0f; + currentViewOrigin[2] = -99999999.0f; + + return true; +} + +/* +==================== +SetMappingForSurface + +analyzes xyz and st to create a mapping +This is not very robust, but works for rectangular grids +==================== +*/ +void idMegaTexture::SetMappingForSurface( const srfTriangles_t *tri ) { + if ( tri == currentTriMapping ) { + return; + } + currentTriMapping = tri; + + if ( !tri->verts ) { + return; + } + + idDrawVert origin, axis[2]; + + origin.st[0] = 1.0; + origin.st[1] = 1.0; + + axis[0].st[0] = 0; + axis[0].st[1] = 1; + + axis[1].st[0] = 1; + axis[1].st[1] = 0; + + for ( int i = 0 ; i < tri->numVerts ; i++ ) { + idDrawVert *v = &tri->verts[i]; + + if ( v->st[0] <= origin.st[0] && v->st[1] <= origin.st[1] ) { + origin = *v; + } + if ( v->st[0] >= axis[0].st[0] && v->st[1] <= axis[0].st[1] ) { + axis[0] = *v; + } + if ( v->st[0] <= axis[1].st[0] && v->st[1] >= axis[1].st[1] ) { + axis[1] = *v; + } + } + + for ( int i = 0 ; i < 2 ; i++ ) { + idVec3 dir = axis[i].xyz - origin.xyz; + float texLen = axis[i].st[i] - origin.st[i]; + float spaceLen = (axis[i].xyz - origin.xyz).Length(); + + float scale = texLen / (spaceLen*spaceLen); + dir *= scale; + + float c = origin.xyz * dir - origin.st[i]; + + localViewToTextureCenter[i][0] = dir[0]; + localViewToTextureCenter[i][1] = dir[1]; + localViewToTextureCenter[i][2] = dir[2]; + localViewToTextureCenter[i][3] = -c; + } +} + +/* +==================== +BindForViewOrigin +==================== +*/ +void idMegaTexture::BindForViewOrigin( const idVec3 viewOrigin ) { + + SetViewOrigin( viewOrigin ); + + // borderClamp image goes in texture 0 + GL_SelectTexture( 0 ); + globalImages->borderClampImage->Bind(); + + // level images in higher textures, blurriest first + for ( int i = 0 ; i < 7 ; i++ ) { + GL_SelectTexture( 1+i ); + + if ( i >= numLevels ) { + globalImages->whiteImage->Bind(); + + static float parms[4] = { -2, -2, 0, 1 }; // no contribution + qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i, parms ); + } else { + idTextureLevel *level = &levels[ numLevels-1-i ]; + + if ( r_showMegaTexture.GetBool() ) { + if ( i & 1 ) { + globalImages->blackImage->Bind(); + } else { + globalImages->whiteImage->Bind(); + } + } else { + level->image->Bind(); + } + qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i, level->parms ); + } + } + + float parms[4]; + parms[0] = 0; + parms[1] = 0; + parms[2] = 0; + parms[3] = 1; + qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 7, parms ); + + parms[0] = 1; + parms[1] = 1; + parms[2] = r_terrainScale.GetFloat(); + parms[3] = 1; + qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 8, parms ); +} + +/* +==================== +Unbind + +This can go away once everything uses fragment programs so the enable states don't +need tracking +==================== +*/ +void idMegaTexture::Unbind( void ) { + for ( int i = 0 ; i < numLevels ; i++ ) { + GL_SelectTexture( 1+i ); + globalImages->BindNull(); + } +} + + +/* +==================== +SetViewOrigin +==================== +*/ +void idMegaTexture::SetViewOrigin( const idVec3 viewOrigin ) { + if ( r_showMegaTextureLabels.IsModified() ) { + r_showMegaTextureLabels.ClearModified(); + currentViewOrigin[0] = viewOrigin[0] + 0.1; // force a change + for ( int i = 0 ; i < numLevels ; i++ ) { + levels[i].Invalidate(); + } + } + + if ( viewOrigin == currentViewOrigin ) { + return; + } + if ( r_skipMegaTexture.GetBool() ) { + return; + } + + currentViewOrigin = viewOrigin; + + float texCenter[2]; + + // convert the viewOrigin to a texture center, which will + // be a different conversion for each megaTexture + for ( int i = 0 ; i < 2 ; i++ ) { + texCenter[i] = + viewOrigin[0] * localViewToTextureCenter[i][0] + + viewOrigin[1] * localViewToTextureCenter[i][1] + + viewOrigin[2] * localViewToTextureCenter[i][2] + + localViewToTextureCenter[i][3]; + } + + for ( int i = 0 ; i < numLevels ; i++ ) { + levels[i].UpdateForCenter( texCenter ); + } +} + + +/* +==================== +UpdateTile + +A local tile will only be mapped to globalTile[ localTile + X * TILE_PER_LEVEL ] for some x +==================== +*/ +void idTextureLevel::UpdateTile( int localX, int localY, int globalX, int globalY ) { + idTextureTile *tile = &tileMap[localX][localY]; + + if ( tile->x == globalX && tile->y == globalY ) { + return; + } + if ( (globalX & (TILE_PER_LEVEL-1)) != localX || (globalY & (TILE_PER_LEVEL-1)) != localY ) { + common->Error( "idTextureLevel::UpdateTile: bad coordinate mod" ); + } + + tile->x = globalX; + tile->y = globalY; + + byte data[ TILE_SIZE * TILE_SIZE * 4 ]; + + if ( globalX >= tilesWide || globalX < 0 || globalY >= tilesHigh || globalY < 0 ) { + // off the map + memset( data, 0, sizeof( data ) ); + } else { + // extract the data from the full image (FIXME: background load from disk) + int tileNum = tileOffset + tile->y * tilesWide + tile->x; + + int tileSize = TILE_SIZE * TILE_SIZE * 4; + + mega->fileHandle->Seek( tileNum * tileSize, FS_SEEK_SET ); + memset( data, 128, sizeof( data ) ); + mega->fileHandle->Read( data, tileSize ); + } + + if ( idMegaTexture::r_showMegaTextureLabels.GetBool() ) { + // put a color marker in it + byte color[4] = { 255 * localX / TILE_PER_LEVEL, 255 * localY / TILE_PER_LEVEL, 0, 0 }; + for ( int x = 0 ; x < 8 ; x++ ) { + for ( int y = 0 ; y < 8 ; y++ ) { + *(int *)&data[ ( ( y + TILE_SIZE/2 - 4 ) * TILE_SIZE + x + TILE_SIZE/2 - 4 ) * 4 ] = *(int *)color; + } + } + } + + // upload all the mip-map levels + int level = 0; + int size = TILE_SIZE; + while ( 1 ) { + qglTexSubImage2D( GL_TEXTURE_2D, level, localX * size, localY * size, size, size, GL_RGBA, GL_UNSIGNED_BYTE, data ); + size >>= 1; + level++; + + if ( size == 0 ) { + break; + } + + int byteSize = size * 4; + // mip-map in place + for ( int y = 0 ; y < size ; y++ ) { + byte *in, *in2, *out; + in = data + y * size * 16; + in2 = in + size * 8; + out = data + y * size * 4; + for ( int x = 0 ; x < size ; x++ ) { + out[x*4+0] = ( in[x*8+0] + in[x*8+4+0] + in2[x*8+0] + in2[x*8+4+0] ) >> 2; + out[x*4+1] = ( in[x*8+1] + in[x*8+4+1] + in2[x*8+1] + in2[x*8+4+1] ) >> 2; + out[x*4+2] = ( in[x*8+2] + in[x*8+4+2] + in2[x*8+2] + in2[x*8+4+2] ) >> 2; + out[x*4+3] = ( in[x*8+3] + in[x*8+4+3] + in2[x*8+3] + in2[x*8+4+3] ) >> 2; + } + } + } +} + +/* +==================== +UpdateForCenter + +Center is in the 0.0 to 1.0 range +==================== +*/ +void idTextureLevel::UpdateForCenter( float center[2] ) { + int globalTileCorner[2]; + int localTileOffset[2]; + + if ( tilesWide <= TILE_PER_LEVEL && tilesHigh <= TILE_PER_LEVEL ) { + globalTileCorner[0] = 0; + globalTileCorner[1] = 0; + localTileOffset[0] = 0; + localTileOffset[1] = 0; + // orient the mask so that it doesn't mask anything at all + parms[0] = 0.25; + parms[1] = 0.25; + parms[3] = 0.25; + } else { + for ( int i = 0 ; i < 2 ; i++ ) { + float global[2]; + + // this value will be outside the 0.0 to 1.0 range unless + // we are in the corner of the megaTexture + global[i] = ( center[i] * parms[3] - 0.5 ) * TILE_PER_LEVEL; + + globalTileCorner[i] = (int)( global[i] + 0.5 ); + + localTileOffset[i] = globalTileCorner[i] & (TILE_PER_LEVEL-1); + + // scaling for the mask texture to only allow the proper window + // of tiles to show through + parms[i] = -globalTileCorner[i] / (float)TILE_PER_LEVEL; + } + } + + image->Bind(); + + for ( int x = 0 ; x < TILE_PER_LEVEL ; x++ ) { + for ( int y = 0 ; y < TILE_PER_LEVEL ; y++ ) { + int globalTile[2]; + + globalTile[0] = globalTileCorner[0] + ( ( x - localTileOffset[0] ) & (TILE_PER_LEVEL-1) ); + globalTile[1] = globalTileCorner[1] + ( ( y - localTileOffset[1] ) & (TILE_PER_LEVEL-1) ); + + UpdateTile( x, y, globalTile[0], globalTile[1] ); + } + } +} + +/* +===================== +Invalidate + +Forces all tiles to be regenerated +===================== +*/ +void idTextureLevel::Invalidate() { + for ( int x = 0 ; x < TILE_PER_LEVEL ; x++ ) { + for ( int y = 0 ; y < TILE_PER_LEVEL ; y++ ) { + tileMap[x][y].x = + tileMap[x][y].y = -99999; + } + } +} + +//=================================================================================================== + + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + +static byte ReadByte( idFile *f ) { + byte b; + + f->Read( &b, 1 ); + return b; +} + +static short ReadShort( idFile *f ) { + byte b[2]; + + f->Read( &b, 2 ); + + return b[0] + ( b[1] << 8 ); +} + + +/* +==================== +GenerateMegaMipMaps +==================== +*/ +void idMegaTexture::GenerateMegaMipMaps( megaTextureHeader_t *header, idFile *outFile ) { + outFile->Flush(); + + // out fileSystem doesn't allow read / write access... + idFile *inFile = fileSystem->OpenFileRead( outFile->GetName() ); + + int tileOffset = 1; + int width = header->tilesWide; + int height = header->tilesHigh; + + int tileSize = header->tileSize * header->tileSize * 4; + byte *oldBlock = (byte *)_alloca( tileSize ); + byte *newBlock = (byte *)_alloca( tileSize ); + + while ( width > 1 || height > 1 ) { + int newHeight = (height+1) >> 1; + if ( newHeight < 1 ) { + newHeight = 1; + } + int newWidth = (width+1) >> 1; + if ( width < 1 ) { + width = 1; + } + common->Printf( "generating %i x %i block mip level\n", newWidth, newHeight ); + + int tileNum; + + for ( int y = 0 ; y < newHeight ; y++ ) { + common->Printf( "row %i\n", y ); + session->UpdateScreen(); + + for ( int x = 0 ; x < newWidth ; x++ ) { + // mip map four original blocks down into a single new block + for ( int yy = 0 ; yy < 2 ; yy++ ) { + for ( int xx = 0 ; xx< 2 ; xx++ ) { + int tx = x*2 + xx; + int ty = y*2 + yy; + + if ( tx > width || ty > height ) { + // off edge, zero fill + memset( newBlock, 0, sizeof( newBlock ) ); + } else { + tileNum = tileOffset + ty * width + tx; + inFile->Seek( tileNum * tileSize, FS_SEEK_SET ); + inFile->Read( oldBlock, tileSize ); + } + // mip map the new pixels + for ( int yyy = 0 ; yyy < TILE_SIZE / 2 ; yyy++ ) { + for ( int xxx = 0 ; xxx < TILE_SIZE / 2 ; xxx++ ) { + byte *in = &oldBlock[ ( yyy * 2 * TILE_SIZE + xxx * 2 ) * 4 ]; + byte *out = &newBlock[ ( ( ( TILE_SIZE/2 * yy ) + yyy ) * TILE_SIZE + ( TILE_SIZE/2 * xx ) + xxx ) * 4 ]; + out[0] = ( in[0] + in[4] + in[0+TILE_SIZE*4] + in[4+TILE_SIZE*4] ) >> 2; + out[1] = ( in[1] + in[5] + in[1+TILE_SIZE*4] + in[5+TILE_SIZE*4] ) >> 2; + out[2] = ( in[2] + in[6] + in[2+TILE_SIZE*4] + in[6+TILE_SIZE*4] ) >> 2; + out[3] = ( in[3] + in[7] + in[3+TILE_SIZE*4] + in[7+TILE_SIZE*4] ) >> 2; + } + } + + // write the block out + tileNum = tileOffset + width * height + y * newWidth + x; + outFile->Seek( tileNum * tileSize, FS_SEEK_SET ); + outFile->Write( newBlock, tileSize ); + + } + } + } + } + tileOffset += width * height; + width = newWidth; + height = newHeight; + } + + delete inFile; +} + +/* +==================== +GenerateMegaPreview + +Make a 2k x 2k preview image for a mega texture that can be used in modeling programs +==================== +*/ +void idMegaTexture::GenerateMegaPreview( const char *fileName ) { + idFile *fileHandle = fileSystem->OpenFileRead( fileName ); + if ( !fileHandle ) { + common->Printf( "idMegaTexture: failed to open %s\n", fileName ); + return; + } + + idStr outName = fileName; + outName.StripFileExtension(); + outName += "_preview.tga"; + + common->Printf( "Creating %s.\n", outName.c_str() ); + + megaTextureHeader_t header; + + fileHandle->Read( &header, sizeof( header ) ); + if ( header.tileSize < 64 || header.tilesWide < 1 || header.tilesHigh < 1 ) { + common->Printf( "idMegaTexture: bad header on %s\n", fileName ); + return; + } + + int tileSize = header.tileSize; + int width = header.tilesWide; + int height = header.tilesHigh; + int tileOffset = 1; + int tileBytes = tileSize * tileSize * 4; + // find the level that fits + while ( width * tileSize > 2048 || height * tileSize > 2048 ) { + tileOffset += width * height; + width >>= 1; + if ( width < 1 ) { + width = 1; + } + height >>= 1; + if ( height < 1 ) { + height = 1; + } + } + + byte *pic = (byte *)R_StaticAlloc( width * height * tileBytes ); + byte *oldBlock = (byte *)_alloca( tileBytes ); + for ( int y = 0 ; y < height ; y++ ) { + for ( int x = 0 ; x < width ; x++ ) { + int tileNum = tileOffset + y * width + x; + fileHandle->Seek( tileNum * tileBytes, FS_SEEK_SET ); + fileHandle->Read( oldBlock, tileBytes ); + + for ( int yy = 0 ; yy < tileSize ; yy++ ) { + memcpy( pic + ( ( y * tileSize + yy ) * width * tileSize + x * tileSize ) * 4, + oldBlock + yy * tileSize * 4, tileSize * 4 ); + } + } + } + + R_WriteTGA( outName.c_str(), pic, width * tileSize, height * tileSize, false ); + + R_StaticFree( pic ); + + delete fileHandle; +} + + +/* +==================== +MakeMegaTexture_f + +Incrementally load a giant tga file and process into the mega texture block format +==================== +*/ +void idMegaTexture::MakeMegaTexture_f( const idCmdArgs &args ) { + int columns, rows, fileSize, numBytes; + byte *pixbuf; + int row, column; + TargaHeader targa_header; + + if ( args.Argc() != 2 ) { + common->Printf( "USAGE: makeMegaTexture \n" ); + return; + } + + idStr name_s = "megaTextures/"; + name_s += args.Argv(1); + name_s.StripFileExtension(); + name_s += ".tga"; + + const char *name = name_s.c_str(); + + // + // open the file + // + common->Printf( "Opening %s.\n", name ); + fileSize = fileSystem->ReadFile( name, NULL, NULL ); + idFile *file = fileSystem->OpenFileRead( name ); + + if ( !file ) { + common->Printf( "Couldn't open %s\n", name ); + return; + } + + targa_header.id_length = ReadByte( file ); + targa_header.colormap_type = ReadByte( file ); + targa_header.image_type = ReadByte( file ); + + targa_header.colormap_index = ReadShort( file ); + targa_header.colormap_length = ReadShort( file ); + targa_header.colormap_size = ReadByte( file ); + targa_header.x_origin = ReadShort( file ); + targa_header.y_origin = ReadShort( file ); + targa_header.width = ReadShort( file ); + targa_header.height = ReadShort( file ); + targa_header.pixel_size = ReadByte( file ); + targa_header.attributes = ReadByte( file ); + + if ( targa_header.image_type != 2 && targa_header.image_type != 10 && targa_header.image_type != 3 ) { + common->Error( "LoadTGA( %s ): Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n", name ); + } + + if ( targa_header.colormap_type != 0 ) { + common->Error( "LoadTGA( %s ): colormaps not supported\n", name ); + } + + if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) { + common->Error( "LoadTGA( %s ): Only 32 or 24 bit images supported (no colormaps)\n", name ); + } + + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) { + numBytes = targa_header.width * targa_header.height * ( targa_header.pixel_size >> 3 ); + if ( numBytes > fileSize - 18 - targa_header.id_length ) { + common->Error( "LoadTGA( %s ): incomplete file\n", name ); + } + } + + columns = targa_header.width; + rows = targa_header.height; + + // skip TARGA image comment + if ( targa_header.id_length != 0 ) { + file->Seek( targa_header.id_length, FS_SEEK_CUR ); + } + + megaTextureHeader_t mtHeader; + + mtHeader.tileSize = TILE_SIZE; + mtHeader.tilesWide = RoundDownToPowerOfTwo( targa_header.width ) / TILE_SIZE; + mtHeader.tilesHigh = RoundDownToPowerOfTwo( targa_header.height ) / TILE_SIZE; + + idStr outName = name; + outName.StripFileExtension(); + outName += ".mega"; + + common->Printf( "Writing %i x %i size %i tiles to %s.\n", + mtHeader.tilesWide, mtHeader.tilesHigh, mtHeader.tileSize, outName.c_str() ); + + // open the output megatexture file + idFile *out = fileSystem->OpenFileWrite( outName.c_str() ); + + out->Write( &mtHeader, sizeof( mtHeader ) ); + out->Seek( TILE_SIZE * TILE_SIZE * 4, FS_SEEK_SET ); + + // we will process this one row of tiles at a time, since the entire thing + // won't fit in memory + byte *targa_rgba = (byte *)R_StaticAlloc( TILE_SIZE * targa_header.width * 4 ); + + int blockRowsRemaining = mtHeader.tilesHigh; + while ( blockRowsRemaining-- ) { + common->Printf( "%i blockRowsRemaining\n", blockRowsRemaining ); + session->UpdateScreen(); + + if ( targa_header.image_type == 2 || targa_header.image_type == 3 ) { + // Uncompressed RGB or gray scale image + for( row = 0 ; row < TILE_SIZE ; row++ ) { + pixbuf = targa_rgba + row*columns*4; + for( column = 0; column < columns; column++) { + unsigned char red,green,blue,alphabyte; + switch( targa_header.pixel_size ) { + case 8: + blue = ReadByte( file ); + green = blue; + red = blue; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + + case 24: + blue = ReadByte( file ); + green = ReadByte( file ); + red = ReadByte( file ); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = ReadByte( file ); + green = ReadByte( file ); + red = ReadByte( file ); + alphabyte = ReadByte( file ); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + } + } + } else if ( targa_header.image_type == 10 ) { // Runlength encoded RGB images + unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j; + + red = 0; + green = 0; + blue = 0; + alphabyte = 0xff; + + for( row = 0 ; row < TILE_SIZE ; row++ ) { + pixbuf = targa_rgba + row*columns*4; + for( column = 0; column < columns; ) { + packetHeader= ReadByte( file ); + packetSize = 1 + (packetHeader & 0x7f); + if ( packetHeader & 0x80 ) { // run-length packet + switch( targa_header.pixel_size ) { + case 24: + blue = ReadByte( file ); + green = ReadByte( file ); + red = ReadByte( file ); + alphabyte = 255; + break; + case 32: + blue = ReadByte( file ); + green = ReadByte( file ); + red = ReadByte( file ); + alphabyte = ReadByte( file ); + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + + for( j = 0; j < packetSize; j++ ) { + *pixbuf++=red; + *pixbuf++=green; + *pixbuf++=blue; + *pixbuf++=alphabyte; + column++; + if ( column == columns ) { // run spans across rows + common->Error( "TGA had RLE across columns, probably breaks block" ); + column = 0; + if ( row > 0) { + row--; + } + else { + goto breakOut; + } + pixbuf = targa_rgba + row*columns*4; + } + } + } else { // non run-length packet + for( j = 0; j < packetSize; j++ ) { + switch( targa_header.pixel_size ) { + case 24: + blue = ReadByte( file ); + green = ReadByte( file ); + red = ReadByte( file ); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = ReadByte( file ); + green = ReadByte( file ); + red = ReadByte( file ); + alphabyte = ReadByte( file ); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alphabyte; + break; + default: + common->Error( "LoadTGA( %s ): illegal pixel_size '%d'\n", name, targa_header.pixel_size ); + break; + } + column++; + if ( column == columns ) { // pixel packet run spans across rows + column = 0; + if ( row > 0 ) { + row--; + } + else { + goto breakOut; + } + pixbuf = targa_rgba + row*columns*4; + } + } + } + } + breakOut: ; + } + } + + // + // write out individual blocks from the full row block buffer + // + for ( int rowBlock = 0 ; rowBlock < mtHeader.tilesWide ; rowBlock++ ) { + for ( int y = 0 ; y < TILE_SIZE ; y++ ) { + out->Write( targa_rgba + ( y * targa_header.width + rowBlock * TILE_SIZE ) * 4, TILE_SIZE * 4 ); + } + } + } + + R_StaticFree( targa_rgba ); + + GenerateMegaMipMaps( &mtHeader, out ); + + delete out; + delete file; + + GenerateMegaPreview( outName.c_str() ); +#if 0 + if ( (targa_header.attributes & (1<<5)) ) { // image flp bit + R_VerticalFlip( *pic, *width, *height ); + } +#endif +} + + diff --git a/src/renderer/model.cpp b/src/renderer/model.cpp new file mode 100644 index 0000000..40f5776 --- /dev/null +++ b/src/renderer/model.cpp @@ -0,0 +1,2385 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Model_local.h" +#include "Model_ase.h" +#include "Model_lwo.h" +#include "Model_ma.h" + +idCVar idRenderModelStatic::r_mergeModelSurfaces( "r_mergeModelSurfaces", "1", CVAR_BOOL|CVAR_RENDERER, "combine model surfaces with the same material" ); +idCVar idRenderModelStatic::r_slopVertex( "r_slopVertex", "0.01", CVAR_RENDERER, "merge xyz coordinates this far apart" ); +idCVar idRenderModelStatic::r_slopTexCoord( "r_slopTexCoord", "0.001", CVAR_RENDERER, "merge texture coordinates this far apart" ); +idCVar idRenderModelStatic::r_slopNormal( "r_slopNormal", "0.02", CVAR_RENDERER, "merge normals that dot less than this" ); + +/* +================ +idRenderModelStatic::idRenderModelStatic +================ +*/ +idRenderModelStatic::idRenderModelStatic() { + name = ""; + bounds.Clear(); + lastModifiedFrame = 0; + lastArchivedFrame = 0; + overlaysAdded = 0; + shadowHull = NULL; + isStaticWorldModel = false; + defaulted = false; + purged = false; + fastLoad = false; + reloadable = true; + levelLoadReferenced = false; + hasSky = false; + timeStamp = 0; +} + +idRenderModel::~idRenderModel() { + // Retail asserts this condition through the model manager. Keep the check + // non-fatal so shutdown can unwind cleanly while reconstructed subsystems + // are being brought down. + if ( renderModelManager && renderModelManager->CheckModel( this ) ) { + common->Warning( "model freed while still registered" ); + } +} + +bool idRenderModel::HasSeparateSilTraceMeshes() const { + return false; +} + +/* +================ +idRenderModelStatic::~idRenderModelStatic +================ +*/ +idRenderModelStatic::~idRenderModelStatic() { + PurgeModel(); +} + +/* +============== +idRenderModelStatic::Print +============== +*/ +void idRenderModelStatic::Print() const { + common->Printf( "%s\n", name.c_str() ); + common->Printf( "Static model.\n" ); + common->Printf( "bounds: (%f %f %f) to (%f %f %f)\n", + bounds[0][0], bounds[0][1], bounds[0][2], + bounds[1][0], bounds[1][1], bounds[1][2] ); + + common->Printf( " verts tris material\n" ); + for ( int i = 0 ; i < NumSurfaces() ; i++ ) { + const modelSurface_t *surf = Surface( i ); + + srfTriangles_t *tri = surf->geometry; + const idMaterial *material = surf->shader; + + if ( !tri ) { + common->Printf( "%2i: %s, NULL surface geometry\n", i, material->GetName() ); + continue; + } + + common->Printf( "%2i: %5i %5i %s", i, tri->numVerts, tri->numIndexes / 3, material->GetName() ); + if ( tri->generateNormals ) { + common->Printf( " (smoothed)\n" ); + } else { + common->Printf( "\n" ); + } + } +} + +/* +============== +idRenderModelStatic::Memory +============== +*/ +int idRenderModelStatic::Memory() const { + int totalBytes = 0; + + totalBytes += sizeof( *this ); + totalBytes += name.DynamicMemoryUsed(); + totalBytes += surfaces.MemoryUsed(); + + if ( shadowHull ) { + totalBytes += R_TriSurfMemory( shadowHull ); + } + + for ( int j = 0 ; j < NumSurfaces() ; j++ ) { + const modelSurface_t *surf = Surface( j ); + if ( !surf->geometry ) { + continue; + } + totalBytes += R_TriSurfMemory( surf->geometry ); + } + + return totalBytes; +} + +/* +============== +idRenderModelStatic::List +============== +*/ +void idRenderModelStatic::List() const { + int totalTris = 0; + int totalVerts = 0; + int totalBytes = 0; + + totalBytes = Memory(); + + char closed = 'C'; + for ( int j = 0 ; j < NumSurfaces() ; j++ ) { + const modelSurface_t *surf = Surface( j ); + if ( !surf->geometry ) { + continue; + } + if ( !surf->geometry->perfectHull ) { + closed = ' '; + } + totalTris += surf->geometry->numIndexes / 3; + totalVerts += surf->geometry->numVerts; + } + common->Printf( "%c%4ik %3i %4i %4i %s", closed, totalBytes/1024, NumSurfaces(), totalVerts, totalTris, Name() ); + + if ( IsDynamicModel() == DM_CACHED ) { + common->Printf( " (DM_CACHED)" ); + } + if ( IsDynamicModel() == DM_CONTINUOUS ) { + common->Printf( " (DM_CONTINUOUS)" ); + } + if ( defaulted ) { + common->Printf( " (DEFAULTED)" ); + } + if ( bounds[0][0] >= bounds[1][0] ) { + common->Printf( " (EMPTY BOUNDS)" ); + } + if ( bounds[1][0] - bounds[0][0] > 100000 ) { + common->Printf( " (HUGE BOUNDS)" ); + } + + common->Printf( "\n" ); +} + +/* +================ +idRenderModelStatic::IsDefaultModel +================ +*/ +bool idRenderModelStatic::IsDefaultModel() const { + return defaulted; +} + +/* +================ +AddCubeFace +================ +*/ +static void AddCubeFace( srfTriangles_t *tri, idVec3 v1, idVec3 v2, idVec3 v3, idVec3 v4 ) { + tri->verts[tri->numVerts+0].Clear(); + tri->verts[tri->numVerts+0].xyz = v1 * 8; + tri->verts[tri->numVerts+0].st[0] = 0; + tri->verts[tri->numVerts+0].st[1] = 0; + + tri->verts[tri->numVerts+1].Clear(); + tri->verts[tri->numVerts+1].xyz = v2 * 8; + tri->verts[tri->numVerts+1].st[0] = 1; + tri->verts[tri->numVerts+1].st[1] = 0; + + tri->verts[tri->numVerts+2].Clear(); + tri->verts[tri->numVerts+2].xyz = v3 * 8; + tri->verts[tri->numVerts+2].st[0] = 1; + tri->verts[tri->numVerts+2].st[1] = 1; + + tri->verts[tri->numVerts+3].Clear(); + tri->verts[tri->numVerts+3].xyz = v4 * 8; + tri->verts[tri->numVerts+3].st[0] = 0; + tri->verts[tri->numVerts+3].st[1] = 1; + + tri->indexes[tri->numIndexes+0] = tri->numVerts + 0; + tri->indexes[tri->numIndexes+1] = tri->numVerts + 1; + tri->indexes[tri->numIndexes+2] = tri->numVerts + 2; + tri->indexes[tri->numIndexes+3] = tri->numVerts + 0; + tri->indexes[tri->numIndexes+4] = tri->numVerts + 2; + tri->indexes[tri->numIndexes+5] = tri->numVerts + 3; + + tri->numVerts += 4; + tri->numIndexes += 6; +} + +/* +================ +idRenderModelStatic::MakeDefaultModel +================ +*/ +void idRenderModelStatic::MakeDefaultModel() { + + defaulted = true; + + // throw out any surfaces we already have + PurgeModel(); + + // create one new surface + modelSurface_t surf; + + srfTriangles_t *tri = R_AllocStaticTriSurf(); + + surf.shader = tr.defaultMaterial; + surf.geometry = tri; + + R_AllocStaticTriSurfVerts( tri, 24 ); + R_AllocStaticTriSurfIndexes( tri, 36 ); + + AddCubeFace( tri, idVec3(-1, 1, 1), idVec3(1, 1, 1), idVec3(1, -1, 1), idVec3(-1, -1, 1) ); + AddCubeFace( tri, idVec3(-1, 1, -1), idVec3(-1, -1, -1), idVec3(1, -1, -1), idVec3(1, 1, -1) ); + + AddCubeFace( tri, idVec3(1, -1, 1), idVec3(1, 1, 1), idVec3(1, 1, -1), idVec3(1, -1, -1) ); + AddCubeFace( tri, idVec3(-1, -1, 1), idVec3(-1, -1, -1), idVec3(-1, 1, -1), idVec3(-1, 1, 1) ); + + AddCubeFace( tri, idVec3(-1, -1, 1), idVec3(1, -1, 1), idVec3(1, -1, -1), idVec3(-1, -1, -1) ); + AddCubeFace( tri, idVec3(-1, 1, 1), idVec3(-1, 1, -1), idVec3(1, 1, -1), idVec3(1, 1, 1) ); + + tri->generateNormals = true; + + AddSurface( surf ); + FinishSurfaces(); +} + +/* +================ +idRenderModelStatic::PartialInitFromFile +================ +*/ +void idRenderModelStatic::PartialInitFromFile( const char *fileName ) { + fastLoad = true; + InitFromFile( fileName ); +} + +/* +================ +idRenderModelStatic::InitFromFile +================ +*/ +void idRenderModelStatic::InitFromFile( const char *fileName ) { + bool loaded; + idStr extension; + + InitEmpty( fileName ); + + // FIXME: load new .proc map format + + name.ExtractFileExtension( extension ); + + if ( extension.Icmp( "ase" ) == 0 ) { + loaded = LoadASE( name ); + reloadable = true; + } else if ( extension.Icmp( "lwo" ) == 0 ) { + loaded = LoadLWO( name ); + reloadable = true; + } else if ( extension.Icmp( "flt" ) == 0 ) { + loaded = LoadFLT( name ); + reloadable = true; + } else if ( extension.Icmp( "ma" ) == 0 ) { + loaded = LoadMA( name ); + reloadable = true; + } else { + common->Warning( "idRenderModelStatic::InitFromFile: unknown type for model: \'%s\'", name.c_str() ); + loaded = false; + } + + if ( !loaded ) { + common->Warning( "Couldn't load model: '%s'", name.c_str() ); + MakeDefaultModel(); + return; + } + + // it is now available for use + purged = false; + + // create the bounds for culling and dynamic surface creation + FinishSurfaces(); +} + +/* +================ +idRenderModelStatic::LoadModel +================ +*/ +void idRenderModelStatic::LoadModel() { + PurgeModel(); + InitFromFile( name ); +} + +/* +================ +idRenderModelStatic::InitEmpty +================ +*/ +void idRenderModelStatic::InitEmpty( const char *fileName ) { + // model names of the form _area* are static parts of the + // world, and have already been considered for optimized shadows + // other model names are inline entity models, and need to be + // shadowed normally + if ( !idStr::Cmpn( fileName, "_area", 5 ) ) { + isStaticWorldModel = true; + } else { + isStaticWorldModel = false; + } + + name = fileName; + reloadable = false; // if it didn't come from a file, we can't reload it + PurgeModel(); + purged = false; + bounds.Zero(); +} + +void idRenderModelStatic::InitEmptyFromArgs( const char *modelName, idDict &args ) { + (void)args; + InitEmpty( modelName ); +} + +int idRenderModelStatic::GetSurfaceMask( const char *surfaceName ) const { + const idMaterial *material = declManager->FindMaterial( surfaceName, false ); + if ( !material ) { + return 0; + } + int mask = 0; + for ( int i = 0; i < surfaces.Num() && i < 32; ++i ) { + if ( surfaces[i].shader == material ) { + mask |= 1 << i; + } + } + return mask; +} + +void idRenderModelStatic::SetHasSky( bool on ) { + hasSky = on; +} + +bool idRenderModelStatic::GetHasSky() const { + return hasSky; +} + +void idRenderModelStatic::SetViewEntity( const viewEntity_t *ve ) { + (void)ve; +} + +bool idRenderModelStatic::HasCollisionSurface( const renderEntity_t *ent ) const { + for ( int i = 0; i < surfaces.Num(); ++i ) { + const modelSurface_t &surface = surfaces[i]; + if ( surface.id == -1 || !surface.shader ) { + continue; + } + const idMaterial *material = R_RemapShaderBySkin( surface.shader, + ent ? ent->customSkin : NULL, ent ? ent->customShader : NULL ); + if ( material && ( material->GetSurfaceFlags() & SURF_COLLISION ) && !material->IsDrawn() ) { + return true; + } + } + return false; +} + +/* +================ +idRenderModelStatic::AddSurface +================ +*/ +void idRenderModelStatic::AddSurface( modelSurface_t surface ) { + surfaces.Append( surface ); + if ( surface.geometry ) { + bounds += surface.geometry->bounds; + } +} + +/* +================ +idRenderModelStatic::Name +================ +*/ +const char *idRenderModelStatic::Name() const { + return name; +} + +/* +================ +idRenderModelStatic::Timestamp +================ +*/ +ID_TIME_T idRenderModelStatic::Timestamp() const { + return timeStamp; +} + +/* +================ +idRenderModelStatic::NumSurfaces +================ +*/ +int idRenderModelStatic::NumSurfaces() const { + return surfaces.Num(); +} + +/* +================ +idRenderModelStatic::NumBaseSurfaces +================ +*/ +int idRenderModelStatic::NumBaseSurfaces() const { + return surfaces.Num() - overlaysAdded; +} + +/* +================ +idRenderModelStatic::Surface +================ +*/ +const modelSurface_t *idRenderModelStatic::Surface( int surfaceNum ) const { + return &surfaces[surfaceNum]; +} + +/* +================ +idRenderModelStatic::AllocSurfaceTriangles +================ +*/ +srfTriangles_t *idRenderModelStatic::AllocSurfaceTriangles( int numVerts, int numIndexes ) const { + srfTriangles_t *tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, numVerts ); + R_AllocStaticTriSurfIndexes( tri, numIndexes ); + return tri; +} + +/* +================ +idRenderModelStatic::FreeSurfaceTriangles +================ +*/ +void idRenderModelStatic::FreeSurfaceTriangles( srfTriangles_t *tris ) const { + R_FreeStaticTriSurf( tris ); +} + +/* +================ +idRenderModelStatic::ShadowHull +================ +*/ +srfTriangles_t *idRenderModelStatic::ShadowHull() const { + return shadowHull; +} + +/* +================ +idRenderModelStatic::IsStaticWorldModel +================ +*/ +bool idRenderModelStatic::IsStaticWorldModel() const { + return isStaticWorldModel; +} + +/* +================ +idRenderModelStatic::IsDynamicModel +================ +*/ +dynamicModel_t idRenderModelStatic::IsDynamicModel() const { + // dynamic subclasses will override this + return DM_STATIC; +} + +/* +================ +idRenderModelStatic::IsReloadable +================ +*/ +bool idRenderModelStatic::IsReloadable() const { + return reloadable; +} + +/* +================ +idRenderModelStatic::Bounds +================ +*/ +idBounds idRenderModelStatic::Bounds( const struct renderEntity_s *mdef ) const { + return bounds; +} + +/* +================ +idRenderModelStatic::DepthHack +================ +*/ +float idRenderModelStatic::DepthHack() const { + return 0.0f; +} + +/* +================ +idRenderModelStatic::InstantiateDynamicModel +================ +*/ +idRenderModel *idRenderModelStatic::InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ) { + (void)surfMask; + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + common->Error( "InstantiateDynamicModel called on static model '%s'", name.c_str() ); + return NULL; +} + +/* +================ +idRenderModelStatic::NumJoints +================ +*/ +int idRenderModelStatic::NumJoints( void ) const { + return 0; +} + +/* +================ +idRenderModelStatic::GetJoints +================ +*/ +const idMD5Joint *idRenderModelStatic::GetJoints( void ) const { + return NULL; +} + +/* +================ +idRenderModelStatic::GetJointHandle +================ +*/ +jointHandle_t idRenderModelStatic::GetJointHandle( const char *name ) const { + return INVALID_JOINT; +} + +/* +================ +idRenderModelStatic::GetJointName +================ +*/ +const char * idRenderModelStatic::GetJointName( jointHandle_t handle ) const { + return ""; +} + +/* +================ +idRenderModelStatic::GetDefaultPose +================ +*/ +const idJointQuat *idRenderModelStatic::GetDefaultPose( void ) const { + return NULL; +} + +/* +================ +idRenderModelStatic::NearestJoint +================ +*/ +int idRenderModelStatic::NearestJoint( int surfaceNum, int a, int b, int c ) const { + return INVALID_JOINT; +} + + +//===================================================================== + + +/* +================ +idRenderModelStatic::FinishSurfaces + +The mergeShadows option allows surfaces with different textures to share +silhouette edges for shadow calculation, instead of leaving shared edges +hanging. + +If any of the original shaders have the noSelfShadow flag set, the surfaces +can't be merged, because they will need to be drawn in different order. + +If there is only one surface, a separate merged surface won't be generated. + +A model with multiple surfaces can't later have a skinned shader change the +state of the noSelfShadow flag. + +----------------- + +Creates mirrored copies of two sided surfaces with normal maps, which would +otherwise light funny. + +Extends the bounds of deformed surfaces so they don't cull incorrectly at screen edges. + +================ +*/ +void idRenderModelStatic::FinishSurfaces() { + int i; + int totalVerts, totalIndexes; + + purged = false; + + // make sure we don't have a huge bounds even if we don't finish everything + bounds.Zero(); + + if ( surfaces.Num() == 0 ) { + return; + } + + // renderBump doesn't care about most of this + if ( fastLoad ) { + bounds.Zero(); + for ( i = 0 ; i < surfaces.Num() ; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + R_BoundTriSurf( surf->geometry ); + bounds.AddBounds( surf->geometry->bounds ); + } + + return; + } + + // cleanup all the final surfaces, but don't create sil edges + totalVerts = 0; + totalIndexes = 0; + + // decide if we are going to merge all the surfaces into one shadower + int numOriginalSurfaces = surfaces.Num(); + + // make sure there aren't any NULL shaders or geometry + for ( i = 0 ; i < numOriginalSurfaces ; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + if ( surf->geometry == NULL || surf->shader == NULL ) { + MakeDefaultModel(); + common->Error( "Model %s, surface %i had NULL geometry", name.c_str(), i ); + } + if ( surf->shader == NULL ) { + MakeDefaultModel(); + common->Error( "Model %s, surface %i had NULL shader", name.c_str(), i ); + } + } + + // duplicate and reverse triangles for two sided bump mapped surfaces + // note that this won't catch surfaces that have their shaders dynamically + // changed, and won't work with animated models. + // It is better to create completely separate surfaces, rather than + // add vertexes and indexes to the existing surface, because the + // tangent generation wouldn't like the acute shared edges + for ( i = 0 ; i < numOriginalSurfaces ; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + if ( surf->shader->ShouldCreateBackSides() ) { + srfTriangles_t *newTri; + + newTri = R_CopyStaticTriSurf( surf->geometry ); + R_ReverseTriangles( newTri ); + + modelSurface_t newSurf; + + newSurf.shader = surf->shader; + newSurf.geometry = newTri; + + AddSurface( newSurf ); + } + } + + // clean the surfaces + for ( i = 0 ; i < surfaces.Num() ; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + R_CleanupTriangles( surf->geometry, surf->geometry->generateNormals, true, surf->shader->UseUnsmoothedTangents() ); + if ( surf->shader->SurfaceCastsShadow() ) { + totalVerts += surf->geometry->numVerts; + totalIndexes += surf->geometry->numIndexes; + } + } + + // add up the total surface area for development information + for ( i = 0 ; i < surfaces.Num() ; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + srfTriangles_t *tri = surf->geometry; + + for ( int j = 0 ; j < tri->numIndexes ; j += 3 ) { + float area = idWinding::TriangleArea( tri->verts[tri->indexes[j]].xyz, + tri->verts[tri->indexes[j+1]].xyz, tri->verts[tri->indexes[j+2]].xyz ); + const_cast(surf->shader)->AddToSurfaceArea( area ); + } + } + + // calculate the bounds + if ( surfaces.Num() == 0 ) { + bounds.Zero(); + } else { + bounds.Clear(); + for ( i = 0 ; i < surfaces.Num() ; i++ ) { + modelSurface_t *surf = &surfaces[i]; + + // if the surface has a deformation, increase the bounds + // the amount here is somewhat arbitrary, designed to handle + // autosprites and flares, but could be done better with exact + // deformation information. + // Note that this doesn't handle deformations that are skinned in + // at run time... + if ( surf->shader->Deform() != DFRM_NONE ) { + srfTriangles_t *tri = surf->geometry; + idVec3 mid = ( tri->bounds[1] + tri->bounds[0] ) * 0.5f; + float radius = ( tri->bounds[0] - mid ).Length(); + radius += 20.0f; + + tri->bounds[0][0] = mid[0] - radius; + tri->bounds[0][1] = mid[1] - radius; + tri->bounds[0][2] = mid[2] - radius; + + tri->bounds[1][0] = mid[0] + radius; + tri->bounds[1][1] = mid[1] + radius; + tri->bounds[1][2] = mid[2] + radius; + } + + // add to the model bounds + bounds.AddBounds( surf->geometry->bounds ); + + } + } +} + +/* +================= +idRenderModelStatic::ConvertASEToModelSurfaces +================= +*/ +typedef struct matchVert_s { + struct matchVert_s *next; + int v, tv; + byte color[4]; + idVec3 normal; +} matchVert_t; + +bool idRenderModelStatic::ConvertASEToModelSurfaces( const struct aseModel_s *ase ) { + aseObject_t * object; + aseMesh_t * mesh; + aseMaterial_t * material; + const idMaterial *im1, *im2; + srfTriangles_t *tri; + int objectNum; + int i, j, k; + int v, tv; + int * vRemap; + int * tvRemap; + matchVert_t * mvTable; // all of the match verts + matchVert_t ** mvHash; // points inside mvTable for each xyz index + matchVert_t * lastmv; + matchVert_t * mv; + idVec3 normal; + float uOffset, vOffset, textureSin, textureCos; + float uTiling, vTiling; + int * mergeTo; + byte * color; + static byte identityColor[4] = { 255, 255, 255, 255 }; + modelSurface_t surf, *modelSurf; + + if ( !ase ) { + return false; + } + if ( ase->objects.Num() < 1 ) { + return false; + } + + timeStamp = ase->timeStamp; + + // the modeling programs can save out multiple surfaces with a common + // material, but we would like to mege them together where possible + // meaning that this->NumSurfaces() <= ase->objects.currentElements + mergeTo = (int *)_alloca( ase->objects.Num() * sizeof( *mergeTo ) ); + surf.geometry = NULL; + if ( ase->materials.Num() == 0 ) { + // if we don't have any materials, dump everything into a single surface + surf.shader = tr.defaultMaterial; + surf.id = 0; + this->AddSurface( surf ); + for ( i = 0 ; i < ase->objects.Num() ; i++ ) { + mergeTo[i] = 0; + } + } else if ( !r_mergeModelSurfaces.GetBool() ) { + // don't merge any + for ( i = 0 ; i < ase->objects.Num() ; i++ ) { + mergeTo[i] = i; + object = ase->objects[i]; + material = ase->materials[object->materialRef]; + surf.shader = declManager->FindMaterial( material->name ); + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } else { + // search for material matches + for ( i = 0 ; i < ase->objects.Num() ; i++ ) { + object = ase->objects[i]; + material = ase->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + if ( im1->IsDiscrete() ) { + // flares, autosprites, etc + j = this->NumSurfaces(); + } else { + for ( j = 0 ; j < this->NumSurfaces() ; j++ ) { + modelSurf = &this->surfaces[j]; + im2 = modelSurf->shader; + if ( im1 == im2 ) { + // merge this + mergeTo[i] = j; + break; + } + } + } + if ( j == this->NumSurfaces() ) { + // didn't merge + mergeTo[i] = j; + surf.shader = im1; + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } + } + + idVectorSubset vertexSubset; + idVectorSubset texCoordSubset; + + // build the surfaces + for ( objectNum = 0 ; objectNum < ase->objects.Num() ; objectNum++ ) { + object = ase->objects[objectNum]; + mesh = &object->mesh; + material = ase->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + + bool normalsParsed = mesh->normalsParsed; + + // completely ignore any explict normals on surfaces with a renderbump command + // which will guarantee the best contours and least vertexes. + const char *rb = im1->GetRenderBump(); + if ( rb && rb[0] ) { + normalsParsed = false; + } + + // It seems like the tools our artists are using often generate + // verts and texcoords slightly separated that should be merged + // note that we really should combine the surfaces with common materials + // before doing this operation, because we can miss a slop combination + // if they are in different surfaces + + vRemap = (int *)R_StaticAlloc( mesh->numVertexes * sizeof( vRemap[0] ) ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = j; + } + } else { + float vertexEpsilon = r_slopVertex.GetFloat(); + float expand = 2 * 32 * vertexEpsilon; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->vertexes, mesh->numVertexes ); + mins -= idVec3( expand, expand, expand ); + maxs += idVec3( expand, expand, expand ); + vertexSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = vertexSubset.FindVector( mesh->vertexes, j, vertexEpsilon ); + } + } + + tvRemap = (int *)R_StaticAlloc( mesh->numTVertexes * sizeof( tvRemap[0] ) ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = j; + } + } else { + float texCoordEpsilon = r_slopTexCoord.GetFloat(); + float expand = 2 * 32 * texCoordEpsilon; + idVec2 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->tvertexes, mesh->numTVertexes ); + mins -= idVec2( expand, expand ); + maxs += idVec2( expand, expand ); + texCoordSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = texCoordSubset.FindVector( mesh->tvertexes, j, texCoordEpsilon ); + } + } + + // we need to find out how many unique vertex / texcoord combinations + // there are, because ASE tracks them separately but we need them unified + + // the maximum possible number of combined vertexes is the number of indexes + mvTable = (matchVert_t *)R_ClearedStaticAlloc( mesh->numFaces * 3 * sizeof( mvTable[0] ) ); + + // we will have a hash chain based on the xyz values + mvHash = (matchVert_t **)R_ClearedStaticAlloc( mesh->numVertexes * sizeof( mvHash[0] ) ); + + // allocate triangle surface + tri = R_AllocStaticTriSurf(); + tri->numVerts = 0; + tri->numIndexes = 0; + R_AllocStaticTriSurfIndexes( tri, mesh->numFaces * 3 ); + tri->generateNormals = !normalsParsed; + + // init default normal, color and tex coord index + normal.Zero(); + color = identityColor; + tv = 0; + + // find all the unique combinations + float normalEpsilon = 1.0f - r_slopNormal.GetFloat(); + for ( j = 0; j < mesh->numFaces; j++ ) { + for ( k = 0; k < 3; k++ ) { + v = mesh->faces[j].vertexNum[k]; + + if ( v < 0 || v >= mesh->numVertexes ) { + common->Error( "ConvertASEToModelSurfaces: bad vertex index in ASE file %s", name.c_str() ); + } + + // collapse the position if it was slightly offset + v = vRemap[v]; + + // we may or may not have texcoords to compare + if ( mesh->numTVFaces == mesh->numFaces && mesh->numTVertexes != 0 ) { + tv = mesh->faces[j].tVertexNum[k]; + if ( tv < 0 || tv >= mesh->numTVertexes ) { + common->Error( "ConvertASEToModelSurfaces: bad tex coord index in ASE file %s", name.c_str() ); + } + // collapse the tex coord if it was slightly offset + tv = tvRemap[tv]; + } + + // we may or may not have normals to compare + if ( normalsParsed ) { + normal = mesh->faces[j].vertexNormals[k]; + } + + // we may or may not have colors to compare + if ( mesh->colorsParsed ) { + color = mesh->faces[j].vertexColors[k]; + } + + // find a matching vert + for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) { + if ( mv->tv != tv ) { + continue; + } + if ( *(unsigned *)mv->color != *(unsigned *)color ) { + continue; + } + if ( !normalsParsed ) { + // if we are going to create the normals, just + // matching texcoords is enough + break; + } + if ( mv->normal * normal > normalEpsilon ) { + break; // we already have this one + } + } + if ( !mv ) { + // allocate a new match vert and link to hash chain + mv = &mvTable[ tri->numVerts ]; + mv->v = v; + mv->tv = tv; + mv->normal = normal; + *(unsigned *)mv->color = *(unsigned *)color; + mv->next = NULL; + if ( lastmv ) { + lastmv->next = mv; + } else { + mvHash[v] = mv; + } + tri->numVerts++; + } + + tri->indexes[tri->numIndexes] = mv - mvTable; + tri->numIndexes++; + } + } + + // allocate space for the indexes and copy them + if ( tri->numIndexes > mesh->numFaces * 3 ) { + common->FatalError( "ConvertASEToModelSurfaces: index miscount in ASE file %s", name.c_str() ); + } + if ( tri->numVerts > mesh->numFaces * 3 ) { + common->FatalError( "ConvertASEToModelSurfaces: vertex miscount in ASE file %s", name.c_str() ); + } + + // an ASE allows the texture coordinates to be scaled, translated, and rotated + if ( ase->materials.Num() == 0 ) { + uOffset = vOffset = 0.0f; + uTiling = vTiling = 1.0f; + textureSin = 0.0f; + textureCos = 1.0f; + } else { + material = ase->materials[object->materialRef]; + uOffset = -material->uOffset; + vOffset = material->vOffset; + uTiling = material->uTiling; + vTiling = material->vTiling; + textureSin = idMath::Sin( material->angle ); + textureCos = idMath::Cos( material->angle ); + } + + // now allocate and generate the combined vertexes + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( j = 0; j < tri->numVerts; j++ ) { + mv = &mvTable[j]; + tri->verts[ j ].Clear(); + tri->verts[ j ].xyz = mesh->vertexes[ mv->v ]; + tri->verts[ j ].normal = mv->normal; + *(unsigned *)tri->verts[j].color = *(unsigned *)mv->color; + if ( mesh->numTVFaces == mesh->numFaces && mesh->numTVertexes != 0 ) { + const idVec2 &tv = mesh->tvertexes[ mv->tv ]; + float u = tv.x * uTiling + uOffset; + float v = tv.y * vTiling + vOffset; + tri->verts[ j ].st[0] = u * textureCos + v * textureSin; + tri->verts[ j ].st[1] = u * -textureSin + v * textureCos; + } + } + + R_StaticFree( mvTable ); + R_StaticFree( mvHash ); + R_StaticFree( tvRemap ); + R_StaticFree( vRemap ); + + // see if we need to merge with a previous surface of the same material + modelSurf = &this->surfaces[mergeTo[ objectNum ]]; + srfTriangles_t *mergeTri = modelSurf->geometry; + if ( !mergeTri ) { + modelSurf->geometry = tri; + } else { + modelSurf->geometry = R_MergeTriangles( mergeTri, tri ); + R_FreeStaticTriSurf( tri ); + R_FreeStaticTriSurf( mergeTri ); + } + } + + return true; +} + +/* +================= +idRenderModelStatic::ConvertLWOToModelSurfaces +================= +*/ +bool idRenderModelStatic::ConvertLWOToModelSurfaces( const struct st_lwObject *lwo ) { + const idMaterial *im1, *im2; + srfTriangles_t *tri; + lwSurface * lwoSurf; + int numTVertexes; + int i, j, k; + int v, tv; + idVec3 * vList; + int * vRemap; + idVec2 * tvList; + int * tvRemap; + matchVert_t * mvTable; // all of the match verts + matchVert_t ** mvHash; // points inside mvTable for each xyz index + matchVert_t * lastmv; + matchVert_t * mv; + idVec3 normal; + int * mergeTo; + byte color[4]; + modelSurface_t surf, *modelSurf; + + if ( !lwo ) { + return false; + } + if ( lwo->surf == NULL ) { + return false; + } + + timeStamp = lwo->timeStamp; + + // count the number of surfaces + i = 0; + for ( lwoSurf = lwo->surf; lwoSurf; lwoSurf = lwoSurf->next ) { + i++; + } + + // the modeling programs can save out multiple surfaces with a common + // material, but we would like to merge them together where possible + mergeTo = (int *)_alloca( i * sizeof( mergeTo[0] ) ); + memset( &surf, 0, sizeof( surf ) ); + + if ( !r_mergeModelSurfaces.GetBool() ) { + // don't merge any + for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) { + mergeTo[i] = i; + surf.shader = declManager->FindMaterial( lwoSurf->name ); + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } else { + // search for material matches + for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) { + im1 = declManager->FindMaterial( lwoSurf->name ); + if ( im1->IsDiscrete() ) { + // flares, autosprites, etc + j = this->NumSurfaces(); + } else { + for ( j = 0 ; j < this->NumSurfaces() ; j++ ) { + modelSurf = &this->surfaces[j]; + im2 = modelSurf->shader; + if ( im1 == im2 ) { + // merge this + mergeTo[i] = j; + break; + } + } + } + if ( j == this->NumSurfaces() ) { + // didn't merge + mergeTo[i] = j; + surf.shader = im1; + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } + } + + idVectorSubset vertexSubset; + idVectorSubset texCoordSubset; + + // we only ever use the first layer + lwLayer *layer = lwo->layer; + + // vertex positions + if ( layer->point.count <= 0 ) { + common->Warning( "ConvertLWOToModelSurfaces: model \'%s\' has bad or missing vertex data", name.c_str() ); + return false; + } + + vList = (idVec3 *)R_StaticAlloc( layer->point.count * sizeof( vList[0] ) ); + for ( j = 0; j < layer->point.count; j++ ) { + vList[j].x = layer->point.pt[j].pos[0]; + vList[j].y = layer->point.pt[j].pos[2]; + vList[j].z = layer->point.pt[j].pos[1]; + } + + // vertex texture coords + numTVertexes = 0; + + if ( layer->nvmaps ) { + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + numTVertexes += vm->nverts; + } + } + } + + if ( numTVertexes ) { + tvList = (idVec2 *)Mem_Alloc( numTVertexes * sizeof( tvList[0] ) ); + int offset = 0; + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + vm->offset = offset; + for ( k = 0; k < vm->nverts; k++ ) { + tvList[k + offset].x = vm->val[k][0]; + tvList[k + offset].y = 1.0f - vm->val[k][1]; // invert the t + } + offset += vm->nverts; + } + } + } else { + common->Warning( "ConvertLWOToModelSurfaces: model \'%s\' has bad or missing uv data", name.c_str() ); + numTVertexes = 1; + tvList = (idVec2 *)Mem_ClearedAlloc( numTVertexes * sizeof( tvList[0] ) ); + } + + // It seems like the tools our artists are using often generate + // verts and texcoords slightly separated that should be merged + // note that we really should combine the surfaces with common materials + // before doing this operation, because we can miss a slop combination + // if they are in different surfaces + + vRemap = (int *)R_StaticAlloc( layer->point.count * sizeof( vRemap[0] ) ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < layer->point.count; j++ ) { + vRemap[j] = j; + } + } else { + float vertexEpsilon = r_slopVertex.GetFloat(); + float expand = 2 * 32 * vertexEpsilon; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, vList, layer->point.count ); + mins -= idVec3( expand, expand, expand ); + maxs += idVec3( expand, expand, expand ); + vertexSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < layer->point.count; j++ ) { + vRemap[j] = vertexSubset.FindVector( vList, j, vertexEpsilon ); + } + } + + tvRemap = (int *)R_StaticAlloc( numTVertexes * sizeof( tvRemap[0] ) ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < numTVertexes; j++ ) { + tvRemap[j] = j; + } + } else { + float texCoordEpsilon = r_slopTexCoord.GetFloat(); + float expand = 2 * 32 * texCoordEpsilon; + idVec2 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, tvList, numTVertexes ); + mins -= idVec2( expand, expand ); + maxs += idVec2( expand, expand ); + texCoordSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < numTVertexes; j++ ) { + tvRemap[j] = texCoordSubset.FindVector( tvList, j, texCoordEpsilon ); + } + } + + // build the surfaces + for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) { + im1 = declManager->FindMaterial( lwoSurf->name ); + + bool normalsParsed = true; + + // completely ignore any explict normals on surfaces with a renderbump command + // which will guarantee the best contours and least vertexes. + const char *rb = im1->GetRenderBump(); + if ( rb && rb[0] ) { + normalsParsed = false; + } + + // we need to find out how many unique vertex / texcoord combinations there are + + // the maximum possible number of combined vertexes is the number of indexes + mvTable = (matchVert_t *)R_ClearedStaticAlloc( layer->polygon.count * 3 * sizeof( mvTable[0] ) ); + + // we will have a hash chain based on the xyz values + mvHash = (matchVert_t **)R_ClearedStaticAlloc( layer->point.count * sizeof( mvHash[0] ) ); + + // allocate triangle surface + tri = R_AllocStaticTriSurf(); + tri->numVerts = 0; + tri->numIndexes = 0; + R_AllocStaticTriSurfIndexes( tri, layer->polygon.count * 3 ); + tri->generateNormals = !normalsParsed; + + // find all the unique combinations + float normalEpsilon; + if ( fastLoad ) { + normalEpsilon = 1.0f; // don't merge unless completely exact + } else { + normalEpsilon = 1.0f - r_slopNormal.GetFloat(); + } + for ( j = 0; j < layer->polygon.count; j++ ) { + lwPolygon *poly = &layer->polygon.pol[j]; + + if ( poly->surf != lwoSurf ) { + continue; + } + + if ( poly->nverts != 3 ) { + common->Warning( "ConvertLWOToModelSurfaces: model %s has too many verts for a poly! Make sure you triplet it down", name.c_str() ); + continue; + } + + for ( k = 0; k < 3; k++ ) { + + v = vRemap[poly->v[k].index]; + + normal.x = poly->v[k].norm[0]; + normal.y = poly->v[k].norm[2]; + normal.z = poly->v[k].norm[1]; + + // LWO models aren't all that pretty when it comes down to the floating point values they store + normal.FixDegenerateNormal(); + + tv = 0; + + color[0] = lwoSurf->color.rgb[0] * 255; + color[1] = lwoSurf->color.rgb[1] * 255; + color[2] = lwoSurf->color.rgb[2] * 255; + color[3] = 255; + + // first set attributes from the vertex + lwPoint *pt = &layer->point.pt[poly->v[k].index]; + int nvm; + for ( nvm = 0; nvm < pt->nvmaps; nvm++ ) { + lwVMapPt *vm = &pt->vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + tv = tvRemap[vm->index + vm->vmap->offset]; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + color[chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + + // then override with polygon attributes + for ( nvm = 0; nvm < poly->v[k].nvmaps; nvm++ ) { + lwVMapPt *vm = &poly->v[k].vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + tv = tvRemap[vm->index + vm->vmap->offset]; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + color[chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + + // find a matching vert + for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) { + if ( mv->tv != tv ) { + continue; + } + if ( *(unsigned *)mv->color != *(unsigned *)color ) { + continue; + } + if ( !normalsParsed ) { + // if we are going to create the normals, just + // matching texcoords is enough + break; + } + if ( mv->normal * normal > normalEpsilon ) { + break; // we already have this one + } + } + if ( !mv ) { + // allocate a new match vert and link to hash chain + mv = &mvTable[ tri->numVerts ]; + mv->v = v; + mv->tv = tv; + mv->normal = normal; + *(unsigned *)mv->color = *(unsigned *)color; + mv->next = NULL; + if ( lastmv ) { + lastmv->next = mv; + } else { + mvHash[v] = mv; + } + tri->numVerts++; + } + + tri->indexes[tri->numIndexes] = mv - mvTable; + tri->numIndexes++; + } + } + + // allocate space for the indexes and copy them + if ( tri->numIndexes > layer->polygon.count * 3 ) { + common->FatalError( "ConvertLWOToModelSurfaces: index miscount in LWO file %s", name.c_str() ); + } + if ( tri->numVerts > layer->polygon.count * 3 ) { + common->FatalError( "ConvertLWOToModelSurfaces: vertex miscount in LWO file %s", name.c_str() ); + } + + // now allocate and generate the combined vertexes + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( j = 0; j < tri->numVerts; j++ ) { + mv = &mvTable[j]; + tri->verts[ j ].Clear(); + tri->verts[ j ].xyz = vList[ mv->v ]; + tri->verts[ j ].st = tvList[ mv->tv ]; + tri->verts[ j ].normal = mv->normal; + *(unsigned *)tri->verts[j].color = *(unsigned *)mv->color; + } + + R_StaticFree( mvTable ); + R_StaticFree( mvHash ); + + // see if we need to merge with a previous surface of the same material + modelSurf = &this->surfaces[mergeTo[ i ]]; + srfTriangles_t *mergeTri = modelSurf->geometry; + if ( !mergeTri ) { + modelSurf->geometry = tri; + } else { + modelSurf->geometry = R_MergeTriangles( mergeTri, tri ); + R_FreeStaticTriSurf( tri ); + R_FreeStaticTriSurf( mergeTri ); + } + } + + R_StaticFree( tvRemap ); + R_StaticFree( vRemap ); + R_StaticFree( tvList ); + R_StaticFree( vList ); + + return true; +} + +/* +================= +idRenderModelStatic::ConvertLWOToASE +================= +*/ +struct aseModel_s *idRenderModelStatic::ConvertLWOToASE( const struct st_lwObject *obj, const char *fileName ) { + int j, k; + aseModel_t *ase; + + if ( !obj ) { + return NULL; + } + + // NOTE: using new operator because aseModel_t contains idList class objects + ase = new aseModel_t; + ase->timeStamp = obj->timeStamp; + ase->objects.Resize( obj->nlayers, obj->nlayers ); + + int materialRef = 0; + + for ( lwSurface *surf = obj->surf; surf; surf = surf->next ) { + + aseMaterial_t *mat = (aseMaterial_t *)Mem_ClearedAlloc( sizeof( *mat ) ); + strcpy( mat->name, surf->name ); + mat->uTiling = mat->vTiling = 1; + mat->angle = mat->uOffset = mat->vOffset = 0; + ase->materials.Append( mat ); + + lwLayer *layer = obj->layer; + + aseObject_t *object = (aseObject_t *)Mem_ClearedAlloc( sizeof( *object ) ); + object->materialRef = materialRef++; + + aseMesh_t *mesh = &object->mesh; + ase->objects.Append( object ); + + mesh->numFaces = layer->polygon.count; + mesh->numTVFaces = mesh->numFaces; + mesh->faces = (aseFace_t *)Mem_Alloc( mesh->numFaces * sizeof( mesh->faces[0] ) ); + + mesh->numVertexes = layer->point.count; + mesh->vertexes = (idVec3 *)Mem_Alloc( mesh->numVertexes * sizeof( mesh->vertexes[0] ) ); + + // vertex positions + if ( layer->point.count <= 0 ) { + common->Warning( "ConvertLWOToASE: model \'%s\' has bad or missing vertex data", name.c_str() ); + } + + for ( j = 0; j < layer->point.count; j++ ) { + mesh->vertexes[j].x = layer->point.pt[j].pos[0]; + mesh->vertexes[j].y = layer->point.pt[j].pos[2]; + mesh->vertexes[j].z = layer->point.pt[j].pos[1]; + } + + // vertex texture coords + mesh->numTVertexes = 0; + + if ( layer->nvmaps ) { + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + mesh->numTVertexes += vm->nverts; + } + } + } + + if ( mesh->numTVertexes ) { + mesh->tvertexes = (idVec2 *)Mem_Alloc( mesh->numTVertexes * sizeof( mesh->tvertexes[0] ) ); + int offset = 0; + for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) { + if ( vm->type == LWID_('T','X','U','V') ) { + vm->offset = offset; + for ( k = 0; k < vm->nverts; k++ ) { + mesh->tvertexes[k + offset].x = vm->val[k][0]; + mesh->tvertexes[k + offset].y = 1.0f - vm->val[k][1]; // invert the t + } + offset += vm->nverts; + } + } + } else { + common->Warning( "ConvertLWOToASE: model \'%s\' has bad or missing uv data", fileName ); + mesh->numTVertexes = 1; + mesh->tvertexes = (idVec2 *)Mem_ClearedAlloc( mesh->numTVertexes * sizeof( mesh->tvertexes[0] ) ); + } + + mesh->normalsParsed = true; + mesh->colorsParsed = true; // because we are falling back to the surface color + + // triangles + int faceIndex = 0; + for ( j = 0; j < layer->polygon.count; j++ ) { + lwPolygon *poly = &layer->polygon.pol[j]; + + if ( poly->surf != surf ) { + continue; + } + + if ( poly->nverts != 3 ) { + common->Warning( "ConvertLWOToASE: model %s has too many verts for a poly! Make sure you triplet it down", fileName ); + continue; + } + + mesh->faces[faceIndex].faceNormal.x = poly->norm[0]; + mesh->faces[faceIndex].faceNormal.y = poly->norm[2]; + mesh->faces[faceIndex].faceNormal.z = poly->norm[1]; + + for ( k = 0; k < 3; k++ ) { + + mesh->faces[faceIndex].vertexNum[k] = poly->v[k].index; + + mesh->faces[faceIndex].vertexNormals[k].x = poly->v[k].norm[0]; + mesh->faces[faceIndex].vertexNormals[k].y = poly->v[k].norm[2]; + mesh->faces[faceIndex].vertexNormals[k].z = poly->v[k].norm[1]; + + // complete fallbacks + mesh->faces[faceIndex].tVertexNum[k] = 0; + + mesh->faces[faceIndex].vertexColors[k][0] = surf->color.rgb[0] * 255; + mesh->faces[faceIndex].vertexColors[k][1] = surf->color.rgb[1] * 255; + mesh->faces[faceIndex].vertexColors[k][2] = surf->color.rgb[2] * 255; + mesh->faces[faceIndex].vertexColors[k][3] = 255; + + // first set attributes from the vertex + lwPoint *pt = &layer->point.pt[poly->v[k].index]; + int nvm; + for ( nvm = 0; nvm < pt->nvmaps; nvm++ ) { + lwVMapPt *vm = &pt->vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + mesh->faces[faceIndex].tVertexNum[k] = vm->index + vm->vmap->offset; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + mesh->faces[faceIndex].vertexColors[k][chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + + // then override with polygon attributes + for ( nvm = 0; nvm < poly->v[k].nvmaps; nvm++ ) { + lwVMapPt *vm = &poly->v[k].vm[nvm]; + + if ( vm->vmap->type == LWID_('T','X','U','V') ) { + mesh->faces[faceIndex].tVertexNum[k] = vm->index + vm->vmap->offset; + } + if ( vm->vmap->type == LWID_('R','G','B','A') ) { + for ( int chan = 0; chan < 4; chan++ ) { + mesh->faces[faceIndex].vertexColors[k][chan] = 255 * vm->vmap->val[vm->index][chan]; + } + } + } + } + + faceIndex++; + } + + mesh->numFaces = faceIndex; + mesh->numTVFaces = faceIndex; + + aseFace_t *newFaces = ( aseFace_t* )Mem_Alloc( mesh->numFaces * sizeof ( mesh->faces[0] ) ); + memcpy( newFaces, mesh->faces, sizeof( mesh->faces[0] ) * mesh->numFaces ); + Mem_Free( mesh->faces ); + mesh->faces = newFaces; + } + + return ase; +} + +/* +================= +idRenderModelStatic::ConvertMAToModelSurfaces +================= +*/ +bool idRenderModelStatic::ConvertMAToModelSurfaces (const struct maModel_s *ma ) { + + maObject_t * object; + maMesh_t * mesh; + maMaterial_t * material; + + const idMaterial *im1, *im2; + srfTriangles_t *tri; + int objectNum; + int i, j, k; + int v, tv; + int * vRemap; + int * tvRemap; + matchVert_t * mvTable; // all of the match verts + matchVert_t ** mvHash; // points inside mvTable for each xyz index + matchVert_t * lastmv; + matchVert_t * mv; + idVec3 normal; + float uOffset, vOffset, textureSin, textureCos; + float uTiling, vTiling; + int * mergeTo; + byte * color; + static byte identityColor[4] = { 255, 255, 255, 255 }; + modelSurface_t surf, *modelSurf; + + if ( !ma ) { + return false; + } + if ( ma->objects.Num() < 1 ) { + return false; + } + + timeStamp = ma->timeStamp; + + // the modeling programs can save out multiple surfaces with a common + // material, but we would like to mege them together where possible + // meaning that this->NumSurfaces() <= ma->objects.currentElements + mergeTo = (int *)_alloca( ma->objects.Num() * sizeof( *mergeTo ) ); + + surf.geometry = NULL; + if ( ma->materials.Num() == 0 ) { + // if we don't have any materials, dump everything into a single surface + surf.shader = tr.defaultMaterial; + surf.id = 0; + this->AddSurface( surf ); + for ( i = 0 ; i < ma->objects.Num() ; i++ ) { + mergeTo[i] = 0; + } + } else if ( !r_mergeModelSurfaces.GetBool() ) { + // don't merge any + for ( i = 0 ; i < ma->objects.Num() ; i++ ) { + mergeTo[i] = i; + object = ma->objects[i]; + if(object->materialRef >= 0) { + material = ma->materials[object->materialRef]; + surf.shader = declManager->FindMaterial( material->name ); + } else { + surf.shader = tr.defaultMaterial; + } + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } else { + // search for material matches + for ( i = 0 ; i < ma->objects.Num() ; i++ ) { + object = ma->objects[i]; + if(object->materialRef >= 0) { + material = ma->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + } else { + im1 = tr.defaultMaterial; + } + if ( im1->IsDiscrete() ) { + // flares, autosprites, etc + j = this->NumSurfaces(); + } else { + for ( j = 0 ; j < this->NumSurfaces() ; j++ ) { + modelSurf = &this->surfaces[j]; + im2 = modelSurf->shader; + if ( im1 == im2 ) { + // merge this + mergeTo[i] = j; + break; + } + } + } + if ( j == this->NumSurfaces() ) { + // didn't merge + mergeTo[i] = j; + surf.shader = im1; + surf.id = this->NumSurfaces(); + this->AddSurface( surf ); + } + } + } + + idVectorSubset vertexSubset; + idVectorSubset texCoordSubset; + + // build the surfaces + for ( objectNum = 0 ; objectNum < ma->objects.Num() ; objectNum++ ) { + object = ma->objects[objectNum]; + mesh = &object->mesh; + if(object->materialRef >= 0) { + material = ma->materials[object->materialRef]; + im1 = declManager->FindMaterial( material->name ); + } else { + im1 = tr.defaultMaterial; + } + + bool normalsParsed = mesh->normalsParsed; + + // completely ignore any explict normals on surfaces with a renderbump command + // which will guarantee the best contours and least vertexes. + const char *rb = im1->GetRenderBump(); + if ( rb && rb[0] ) { + normalsParsed = false; + } + + // It seems like the tools our artists are using often generate + // verts and texcoords slightly separated that should be merged + // note that we really should combine the surfaces with common materials + // before doing this operation, because we can miss a slop combination + // if they are in different surfaces + + vRemap = (int *)R_StaticAlloc( mesh->numVertexes * sizeof( vRemap[0] ) ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = j; + } + } else { + float vertexEpsilon = r_slopVertex.GetFloat(); + float expand = 2 * 32 * vertexEpsilon; + idVec3 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->vertexes, mesh->numVertexes ); + mins -= idVec3( expand, expand, expand ); + maxs += idVec3( expand, expand, expand ); + vertexSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numVertexes; j++ ) { + vRemap[j] = vertexSubset.FindVector( mesh->vertexes, j, vertexEpsilon ); + } + } + + tvRemap = (int *)R_StaticAlloc( mesh->numTVertexes * sizeof( tvRemap[0] ) ); + + if ( fastLoad ) { + // renderbump doesn't care about vertex count + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = j; + } + } else { + float texCoordEpsilon = r_slopTexCoord.GetFloat(); + float expand = 2 * 32 * texCoordEpsilon; + idVec2 mins, maxs; + + SIMDProcessor->MinMax( mins, maxs, mesh->tvertexes, mesh->numTVertexes ); + mins -= idVec2( expand, expand ); + maxs += idVec2( expand, expand ); + texCoordSubset.Init( mins, maxs, 32, 1024 ); + for ( j = 0; j < mesh->numTVertexes; j++ ) { + tvRemap[j] = texCoordSubset.FindVector( mesh->tvertexes, j, texCoordEpsilon ); + } + } + + // we need to find out how many unique vertex / texcoord / color combinations + // there are, because MA tracks them separately but we need them unified + + // the maximum possible number of combined vertexes is the number of indexes + mvTable = (matchVert_t *)R_ClearedStaticAlloc( mesh->numFaces * 3 * sizeof( mvTable[0] ) ); + + // we will have a hash chain based on the xyz values + mvHash = (matchVert_t **)R_ClearedStaticAlloc( mesh->numVertexes * sizeof( mvHash[0] ) ); + + // allocate triangle surface + tri = R_AllocStaticTriSurf(); + tri->numVerts = 0; + tri->numIndexes = 0; + R_AllocStaticTriSurfIndexes( tri, mesh->numFaces * 3 ); + tri->generateNormals = !normalsParsed; + + // init default normal, color and tex coord index + normal.Zero(); + color = identityColor; + tv = 0; + + // find all the unique combinations + float normalEpsilon = 1.0f - r_slopNormal.GetFloat(); + for ( j = 0; j < mesh->numFaces; j++ ) { + for ( k = 0; k < 3; k++ ) { + v = mesh->faces[j].vertexNum[k]; + + if ( v < 0 || v >= mesh->numVertexes ) { + common->Error( "ConvertMAToModelSurfaces: bad vertex index in MA file %s", name.c_str() ); + } + + // collapse the position if it was slightly offset + v = vRemap[v]; + + // we may or may not have texcoords to compare + if ( mesh->numTVertexes != 0 ) { + tv = mesh->faces[j].tVertexNum[k]; + if ( tv < 0 || tv >= mesh->numTVertexes ) { + common->Error( "ConvertMAToModelSurfaces: bad tex coord index in MA file %s", name.c_str() ); + } + // collapse the tex coord if it was slightly offset + tv = tvRemap[tv]; + } + + // we may or may not have normals to compare + if ( normalsParsed ) { + normal = mesh->faces[j].vertexNormals[k]; + } + + //BSM: Todo: Fix the vertex colors + // we may or may not have colors to compare + if ( mesh->faces[j].vertexColors[k] != -1 && mesh->faces[j].vertexColors[k] != -999 ) { + + color = &mesh->colors[mesh->faces[j].vertexColors[k]*4]; + } + + // find a matching vert + for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) { + if ( mv->tv != tv ) { + continue; + } + if ( *(unsigned *)mv->color != *(unsigned *)color ) { + continue; + } + if ( !normalsParsed ) { + // if we are going to create the normals, just + // matching texcoords is enough + break; + } + if ( mv->normal * normal > normalEpsilon ) { + break; // we already have this one + } + } + if ( !mv ) { + // allocate a new match vert and link to hash chain + mv = &mvTable[ tri->numVerts ]; + mv->v = v; + mv->tv = tv; + mv->normal = normal; + *(unsigned *)mv->color = *(unsigned *)color; + mv->next = NULL; + if ( lastmv ) { + lastmv->next = mv; + } else { + mvHash[v] = mv; + } + tri->numVerts++; + } + + tri->indexes[tri->numIndexes] = mv - mvTable; + tri->numIndexes++; + } + } + + // allocate space for the indexes and copy them + if ( tri->numIndexes > mesh->numFaces * 3 ) { + common->FatalError( "ConvertMAToModelSurfaces: index miscount in MA file %s", name.c_str() ); + } + if ( tri->numVerts > mesh->numFaces * 3 ) { + common->FatalError( "ConvertMAToModelSurfaces: vertex miscount in MA file %s", name.c_str() ); + } + + // an MA allows the texture coordinates to be scaled, translated, and rotated + //BSM: Todo: Does Maya support this and if so how + //if ( ase->materials.Num() == 0 ) { + uOffset = vOffset = 0.0f; + uTiling = vTiling = 1.0f; + textureSin = 0.0f; + textureCos = 1.0f; + //} else { + // material = ase->materials[object->materialRef]; + // uOffset = -material->uOffset; + // vOffset = material->vOffset; + // uTiling = material->uTiling; + // vTiling = material->vTiling; + // textureSin = idMath::Sin( material->angle ); + // textureCos = idMath::Cos( material->angle ); + //} + + // now allocate and generate the combined vertexes + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( j = 0; j < tri->numVerts; j++ ) { + mv = &mvTable[j]; + tri->verts[ j ].Clear(); + tri->verts[ j ].xyz = mesh->vertexes[ mv->v ]; + tri->verts[ j ].normal = mv->normal; + *(unsigned *)tri->verts[j].color = *(unsigned *)mv->color; + if ( mesh->numTVertexes != 0 ) { + const idVec2 &tv = mesh->tvertexes[ mv->tv ]; + float u = tv.x * uTiling + uOffset; + float v = tv.y * vTiling + vOffset; + tri->verts[ j ].st[0] = u * textureCos + v * textureSin; + tri->verts[ j ].st[1] = u * -textureSin + v * textureCos; + } + } + + R_StaticFree( mvTable ); + R_StaticFree( mvHash ); + R_StaticFree( tvRemap ); + R_StaticFree( vRemap ); + + // see if we need to merge with a previous surface of the same material + modelSurf = &this->surfaces[mergeTo[ objectNum ]]; + srfTriangles_t *mergeTri = modelSurf->geometry; + if ( !mergeTri ) { + modelSurf->geometry = tri; + } else { + modelSurf->geometry = R_MergeTriangles( mergeTri, tri ); + R_FreeStaticTriSurf( tri ); + R_FreeStaticTriSurf( mergeTri ); + } + } + + return true; +} + +/* +================= +idRenderModelStatic::LoadASE +================= +*/ +bool idRenderModelStatic::LoadASE( const char *fileName ) { + aseModel_t *ase; + + ase = ASE_Load( fileName ); + if ( ase == NULL ) { + return false; + } + + ConvertASEToModelSurfaces( ase ); + + ASE_Free( ase ); + + return true; +} + +/* +================= +idRenderModelStatic::LoadLWO +================= +*/ +bool idRenderModelStatic::LoadLWO( const char *fileName ) { + unsigned int failID; + int failPos; + lwObject *lwo; + + lwo = lwGetObject( fileName, &failID, &failPos ); + if ( lwo == NULL ) { + return false; + } + + ConvertLWOToModelSurfaces( lwo ); + + lwFreeObject( lwo ); + + return true; +} + +/* +================= +idRenderModelStatic::LoadMA +================= +*/ +bool idRenderModelStatic::LoadMA( const char *fileName ) { + maModel_t *ma; + + ma = MA_Load( fileName ); + if ( ma == NULL ) { + return false; + } + + ConvertMAToModelSurfaces( ma ); + + MA_Free( ma ); + + return true; +} + +/* +================= +idRenderModelStatic::LoadFLT + +USGS height map data for megaTexture experiments +================= +*/ +bool idRenderModelStatic::LoadFLT( const char *fileName ) { + float *data; + int len; + + len = fileSystem->ReadFile( fileName, (void **)&data ); + if ( len <= 0 ) { + return false; + } + int size = sqrt( len / 4.0f ); + + // bound the altitudes + float min = 9999999; + float max = -9999999; + for ( int i = 0 ; i < len/4 ; i++ ) { + data[i] = BigFloat( data[i] ); + if ( data[i] == -9999 ) { + data[i] = 0; // unscanned areas + } + + if ( data[i] < min ) { + min = data[i]; + } + if ( data[i] > max ) { + max = data[i]; + } + } +#if 1 + // write out a gray scale height map + byte *image = (byte *)R_StaticAlloc( len ); + byte *image_p = image; + for ( int i = 0 ; i < len/4 ; i++ ) { + float v = ( data[i] - min ) / ( max - min ); + image_p[0] = + image_p[1] = + image_p[2] = v * 255; + image_p[3] = 255; + image_p += 4; + } + idStr tgaName = fileName; + tgaName.StripFileExtension(); + tgaName += ".tga"; + R_WriteTGA( tgaName.c_str(), image, size, size, false ); + R_StaticFree( image ); +//return false; +#endif + + // find the island above sea level + int minX, maxX, minY, maxY; + { + int i; + for ( minX = 0 ; minX < size ; minX++ ) { + for ( i = 0 ; i < size ; i++ ) { + if ( data[i*size + minX] > 1.0 ) { + break; + } + } + if ( i != size ) { + break; + } + } + + for ( maxX = size-1 ; maxX > 0 ; maxX-- ) { + for ( i = 0 ; i < size ; i++ ) { + if ( data[i*size + maxX] > 1.0 ) { + break; + } + } + if ( i != size ) { + break; + } + } + + for ( minY = 0 ; minY < size ; minY++ ) { + for ( i = 0 ; i < size ; i++ ) { + if ( data[minY*size + i] > 1.0 ) { + break; + } + } + if ( i != size ) { + break; + } + } + + for ( maxY = size-1 ; maxY < size ; maxY-- ) { + for ( i = 0 ; i < size ; i++ ) { + if ( data[maxY*size + i] > 1.0 ) { + break; + } + } + if ( i != size ) { + break; + } + } + } + + int width = maxX - minX + 1; + int height = maxY - minY + 1; + +//width /= 2; + // allocate triangle surface + srfTriangles_t *tri = R_AllocStaticTriSurf(); + tri->numVerts = width * height; + tri->numIndexes = (width-1) * (height-1) * 6; + + fastLoad = true; // don't do all the sil processing + + R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + + for ( int i = 0 ; i < height ; i++ ) { + for ( int j = 0; j < width ; j++ ) { + int v = i * width + j; + tri->verts[ v ].Clear(); + tri->verts[ v ].xyz[0] = j * 10; // each sample is 10 meters + tri->verts[ v ].xyz[1] = -i * 10; + tri->verts[ v ].xyz[2] = data[(minY+i)*size+minX+j]; // height is in meters + tri->verts[ v ].st[0] = (float) j / (width-1); + tri->verts[ v ].st[1] = 1.0 - ( (float) i / (height-1) ); + } + } + + for ( int i = 0 ; i < height-1 ; i++ ) { + for ( int j = 0; j < width-1 ; j++ ) { + int v = ( i * (width-1) + j ) * 6; +#if 0 + tri->indexes[ v + 0 ] = i * width + j; + tri->indexes[ v + 1 ] = (i+1) * width + j; + tri->indexes[ v + 2 ] = (i+1) * width + j + 1; + tri->indexes[ v + 3 ] = i * width + j; + tri->indexes[ v + 4 ] = (i+1) * width + j + 1; + tri->indexes[ v + 5 ] = i * width + j + 1; +#else + tri->indexes[ v + 0 ] = i * width + j; + tri->indexes[ v + 1 ] = i * width + j + 1; + tri->indexes[ v + 2 ] = (i+1) * width + j + 1; + tri->indexes[ v + 3 ] = i * width + j; + tri->indexes[ v + 4 ] = (i+1) * width + j + 1; + tri->indexes[ v + 5 ] = (i+1) * width + j; +#endif + } + } + + fileSystem->FreeFile( data ); + + modelSurface_t surface; + + surface.geometry = tri; + surface.id = 0; + surface.shader = tr.defaultMaterial; // declManager->FindMaterial( "shaderDemos/megaTexture" ); + + this->AddSurface( surface ); + + return true; +} + + +//============================================================================= + +/* +================ +idRenderModelStatic::PurgeModel +================ +*/ +void idRenderModelStatic::PurgeModel() { + int i; + modelSurface_t *surf; + + for ( i = 0 ; i < surfaces.Num() ; i++ ) { + surf = &surfaces[i]; + + if ( surf->geometry ) { + R_FreeStaticTriSurf( surf->geometry ); + } + } + surfaces.Clear(); + + purged = true; +} + +/* +============== +idRenderModelStatic::FreeVertexCache + +We are about to restart the vertex cache, so dump everything +============== +*/ +void idRenderModelStatic::FreeVertexCache( void ) { + for ( int j = 0 ; j < surfaces.Num() ; j++ ) { + srfTriangles_t *tri = surfaces[j].geometry; + if ( !tri ) { + continue; + } + if ( tri->ambientCache ) { + vertexCache.Free( tri->ambientCache ); + tri->ambientCache = NULL; + } + // static shadows may be present + if ( tri->shadowCache ) { + vertexCache.Free( tri->shadowCache ); + tri->shadowCache = NULL; + } + } +} + +/* +================ +idRenderModelStatic::ReadFromDemoFile +================ +*/ +void idRenderModelStatic::ReadFromDemoFile( class idDemoFile *f ) { + PurgeModel(); + + InitEmpty( f->ReadHashString() ); + + int i, j, numSurfaces; + f->ReadInt( numSurfaces ); + + for ( i = 0 ; i < numSurfaces ; i++ ) { + modelSurface_t surf; + + surf.shader = declManager->FindMaterial( f->ReadHashString() ); + + srfTriangles_t *tri = R_AllocStaticTriSurf(); + + f->ReadInt( tri->numIndexes ); + R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); + for ( j = 0; j < tri->numIndexes; ++j ) + f->ReadInt( (int&)tri->indexes[j] ); + + f->ReadInt( tri->numVerts ); + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( j = 0; j < tri->numVerts; ++j ) { + f->ReadVec3( tri->verts[j].xyz ); + f->ReadVec2( tri->verts[j].st ); + f->ReadVec3( tri->verts[j].normal ); + f->ReadVec3( tri->verts[j].tangents[0] ); + f->ReadVec3( tri->verts[j].tangents[1] ); + f->ReadUnsignedChar( tri->verts[j].color[0] ); + f->ReadUnsignedChar( tri->verts[j].color[1] ); + f->ReadUnsignedChar( tri->verts[j].color[2] ); + f->ReadUnsignedChar( tri->verts[j].color[3] ); + } + + surf.geometry = tri; + + this->AddSurface( surf ); + } + this->FinishSurfaces(); +} + +/* +================ +idRenderModelStatic::WriteToDemoFile +================ +*/ +void idRenderModelStatic::WriteToDemoFile( class idDemoFile *f ) { + int data[1]; + + // note that it has been updated + lastArchivedFrame = tr.frameCount; + + data[0] = DC_DEFINE_MODEL; + f->WriteInt( data[0] ); + f->WriteHashString( this->Name() ); + + int i, j, iData = surfaces.Num(); + f->WriteInt( iData ); + + for ( i = 0 ; i < surfaces.Num() ; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + f->WriteHashString( surf->shader->GetName() ); + + srfTriangles_t *tri = surf->geometry; + f->WriteInt( tri->numIndexes ); + for ( j = 0; j < tri->numIndexes; ++j ) + f->WriteInt( (int&)tri->indexes[j] ); + f->WriteInt( tri->numVerts ); + for ( j = 0; j < tri->numVerts; ++j ) { + f->WriteVec3( tri->verts[j].xyz ); + f->WriteVec2( tri->verts[j].st ); + f->WriteVec3( tri->verts[j].normal ); + f->WriteVec3( tri->verts[j].tangents[0] ); + f->WriteVec3( tri->verts[j].tangents[1] ); + f->WriteUnsignedChar( tri->verts[j].color[0] ); + f->WriteUnsignedChar( tri->verts[j].color[1] ); + f->WriteUnsignedChar( tri->verts[j].color[2] ); + f->WriteUnsignedChar( tri->verts[j].color[3] ); + } + } +} + +/* +================ +idRenderModelStatic::IsLoaded +================ +*/ +bool idRenderModelStatic::IsLoaded( void ) { + return !purged; +} + +/* +================ +idRenderModelStatic::SetLevelLoadReferenced +================ +*/ +void idRenderModelStatic::SetLevelLoadReferenced( bool referenced ) { + levelLoadReferenced = referenced; +} + +/* +================ +idRenderModelStatic::IsLevelLoadReferenced +================ +*/ +bool idRenderModelStatic::IsLevelLoadReferenced( void ) { + return levelLoadReferenced; +} + +/* +================= +idRenderModelStatic::TouchData +================= +*/ +void idRenderModelStatic::TouchData( void ) { + for ( int i = 0 ; i < surfaces.Num() ; i++ ) { + const modelSurface_t *surf = &surfaces[i]; + + // re-find the material to make sure it gets added to the + // level keep list + declManager->FindMaterial( surf->shader->GetName() ); + } +} + +/* +================= +idRenderModelStatic::DeleteSurfaceWithId +================= +*/ +bool idRenderModelStatic::DeleteSurfaceWithId( int id ) { + int i; + + for ( i = 0; i < surfaces.Num(); i++ ) { + if ( surfaces[i].id == id ) { + R_FreeStaticTriSurf( surfaces[i].geometry ); + surfaces.RemoveIndex( i ); + return true; + } + } + return false; +} + +/* +================= +idRenderModelStatic::DeleteSurfacesWithNegativeId +================= +*/ +void idRenderModelStatic::DeleteSurfacesWithNegativeId( void ) { + int i; + + for ( i = 0; i < surfaces.Num(); i++ ) { + if ( surfaces[i].id < 0 ) { + R_FreeStaticTriSurf( surfaces[i].geometry ); + surfaces.RemoveIndex( i ); + i--; + } + } +} + +/* +================= +idRenderModelStatic::FindSurfaceWithId +================= +*/ +bool idRenderModelStatic::FindSurfaceWithId( int id, int &surfaceNum ) { + int i; + + for ( i = 0; i < surfaces.Num(); i++ ) { + if ( surfaces[i].id == id ) { + surfaceNum = i; + return true; + } + } + return false; +} diff --git a/src/renderer/model_ase.cpp b/src/renderer/model_ase.cpp new file mode 100644 index 0000000..ce675bf --- /dev/null +++ b/src/renderer/model_ase.cpp @@ -0,0 +1,908 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Model_ase.h" + +/* +====================================================================== + + Parses 3D Studio Max ASCII export files. + The goal is to parse the information into memory exactly as it is + represented in the file. Users of the data will then move it + into a form that is more convenient for them. + +====================================================================== +*/ + + +#define VERBOSE( x ) { if ( ase.verbose ) { common->Printf x ; } } + +// working variables used during parsing +typedef struct { + const char *buffer; + const char *curpos; + int len; + char token[1024]; + + bool verbose; + + aseModel_t *model; + aseObject_t *currentObject; + aseMesh_t *currentMesh; + aseMaterial_t *currentMaterial; + int currentFace; + int currentVertex; +} ase_t; + +static ase_t ase; + + +static aseMesh_t *ASE_GetCurrentMesh( void ) +{ + return ase.currentMesh; +} + +static int CharIsTokenDelimiter( int ch ) +{ + if ( ch <= 32 ) + return 1; + return 0; +} + +static int ASE_GetToken( bool restOfLine ) +{ + int i = 0; + + if ( ase.buffer == 0 ) + return 0; + + if ( ( ase.curpos - ase.buffer ) == ase.len ) + return 0; + + // skip over crap + while ( ( ( ase.curpos - ase.buffer ) < ase.len ) && + ( *ase.curpos <= 32 ) ) + { + ase.curpos++; + } + + while ( ( ase.curpos - ase.buffer ) < ase.len ) + { + ase.token[i] = *ase.curpos; + + ase.curpos++; + i++; + + if ( ( CharIsTokenDelimiter( ase.token[i-1] ) && !restOfLine ) || + ( ( ase.token[i-1] == '\n' ) || ( ase.token[i-1] == '\r' ) ) ) + { + ase.token[i-1] = 0; + break; + } + } + + ase.token[i] = 0; + + return 1; +} + +static void ASE_ParseBracedBlock( void (*parser)( const char *token ) ) +{ + int indent = 0; + + while ( ASE_GetToken( false ) ) + { + if ( !strcmp( ase.token, "{" ) ) + { + indent++; + } + else if ( !strcmp( ase.token, "}" ) ) + { + --indent; + if ( indent == 0 ) + break; + else if ( indent < 0 ) + common->Error( "Unexpected '}'" ); + } + else + { + if ( parser ) + parser( ase.token ); + } + } +} + +static void ASE_SkipEnclosingBraces( void ) +{ + int indent = 0; + + while ( ASE_GetToken( false ) ) + { + if ( !strcmp( ase.token, "{" ) ) + { + indent++; + } + else if ( !strcmp( ase.token, "}" ) ) + { + indent--; + if ( indent == 0 ) + break; + else if ( indent < 0 ) + common->Error( "Unexpected '}'" ); + } + } +} + +static void ASE_SkipRestOfLine( void ) +{ + ASE_GetToken( true ); +} + +static void ASE_KeyMAP_DIFFUSE( const char *token ) +{ + aseMaterial_t *material; + + if ( !strcmp( token, "*BITMAP" ) ) + { + idStr qpath; + idStr matname; + + ASE_GetToken( false ); + + // remove the quotes + char *s = strstr( ase.token + 1, "\"" ); + if ( s ) { + *s = 0; + } + matname = ase.token + 1; + + // convert the 3DSMax material pathname to a qpath + matname.BackSlashesToSlashes(); + qpath = fileSystem->OSPathToRelativePath( matname ); + idStr::Copynz( ase.currentMaterial->name, qpath, sizeof( ase.currentMaterial->name ) ); + } + else if ( !strcmp( token, "*UVW_U_OFFSET" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->uOffset = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_V_OFFSET" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->vOffset = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_U_TILING" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->uTiling = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_V_TILING" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->vTiling = atof( ase.token ); + } + else if ( !strcmp( token, "*UVW_ANGLE" ) ) + { + material = ase.model->materials[ase.model->materials.Num() - 1]; + ASE_GetToken( false ); + material->angle = atof( ase.token ); + } + else + { + } +} + +static void ASE_KeyMATERIAL( const char *token ) +{ + if ( !strcmp( token, "*MAP_DIFFUSE" ) ) + { + ASE_ParseBracedBlock( ASE_KeyMAP_DIFFUSE ); + } + else + { + } +} + +static void ASE_KeyMATERIAL_LIST( const char *token ) +{ + if ( !strcmp( token, "*MATERIAL_COUNT" ) ) + { + ASE_GetToken( false ); + VERBOSE( ( "..num materials: %s\n", ase.token ) ); + } + else if ( !strcmp( token, "*MATERIAL" ) ) + { + VERBOSE( ( "..material %d\n", ase.model->materials.Num() ) ); + + ase.currentMaterial = (aseMaterial_t *)Mem_Alloc( sizeof( aseMaterial_t ) ); + memset( ase.currentMaterial, 0, sizeof( aseMaterial_t ) ); + ase.currentMaterial->uTiling = 1; + ase.currentMaterial->vTiling = 1; + ase.model->materials.Append(ase.currentMaterial); + + ASE_ParseBracedBlock( ASE_KeyMATERIAL ); + } +} + +static void ASE_KeyNODE_TM( const char *token ) +{ + int i; + + if ( !strcmp( token, "*TM_ROW0" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[0][i] = atof( ase.token ); + } + } else if ( !strcmp( token, "*TM_ROW1" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[1][i] = atof( ase.token ); + } + } else if ( !strcmp( token, "*TM_ROW2" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[2][i] = atof( ase.token ); + } + } else if ( !strcmp( token, "*TM_ROW3" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + ase.currentObject->mesh.transform[3][i] = atof( ase.token ); + } + } +} + +static void ASE_KeyMESH_VERTEX_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_VERTEX" ) ) + { + ASE_GetToken( false ); // skip number + + ASE_GetToken( false ); + pMesh->vertexes[ase.currentVertex].x = atof( ase.token ); + + ASE_GetToken( false ); + pMesh->vertexes[ase.currentVertex].y = atof( ase.token ); + + ASE_GetToken( false ); + pMesh->vertexes[ase.currentVertex].z = atof( ase.token ); + + ase.currentVertex++; + + if ( ase.currentVertex > pMesh->numVertexes ) + { + common->Error( "ase.currentVertex >= pMesh->numVertexes" ); + } + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_VERTEX_LIST", token ); + } +} + +static void ASE_KeyMESH_FACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_FACE" ) ) + { + ASE_GetToken( false ); // skip face number + + // we are flipping the order here to change the front/back facing + // from 3DS to our standard (clockwise facing out) + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // first vertex + pMesh->faces[ase.currentFace].vertexNum[0] = atoi( ase.token ); + + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // second vertex + pMesh->faces[ase.currentFace].vertexNum[2] = atoi( ase.token ); + + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // third vertex + pMesh->faces[ase.currentFace].vertexNum[1] = atoi( ase.token ); + + ASE_GetToken( true ); + + // we could parse material id and smoothing groups here +/* + if ( ( p = strstr( ase.token, "*MESH_MTLID" ) ) != 0 ) + { + p += strlen( "*MESH_MTLID" ) + 1; + mtlID = atoi( p ); + } + else + { + common->Error( "No *MESH_MTLID found for face!" ); + } +*/ + + ase.currentFace++; + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_FACE_LIST", token ); + } +} + +static void ASE_KeyTFACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TFACE" ) ) + { + int a, b, c; + + ASE_GetToken( false ); + + ASE_GetToken( false ); + a = atoi( ase.token ); + ASE_GetToken( false ); + c = atoi( ase.token ); + ASE_GetToken( false ); + b = atoi( ase.token ); + + pMesh->faces[ase.currentFace].tVertexNum[0] = a; + pMesh->faces[ase.currentFace].tVertexNum[1] = b; + pMesh->faces[ase.currentFace].tVertexNum[2] = c; + + ase.currentFace++; + } + else + { + common->Error( "Unknown token '%s' in MESH_TFACE", token ); + } +} + +static void ASE_KeyCFACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_CFACE" ) ) + { + ASE_GetToken( false ); + + for ( int i = 0 ; i < 3 ; i++ ) { + ASE_GetToken( false ); + int a = atoi( ase.token ); + + // we flip the vertex order to change the face direction to our style + static int remap[3] = { 0, 2, 1 }; + pMesh->faces[ase.currentFace].vertexColors[remap[i]][0] = pMesh->cvertexes[a][0] * 255; + pMesh->faces[ase.currentFace].vertexColors[remap[i]][1] = pMesh->cvertexes[a][1] * 255; + pMesh->faces[ase.currentFace].vertexColors[remap[i]][2] = pMesh->cvertexes[a][2] * 255; + } + + ase.currentFace++; + } + else + { + common->Error( "Unknown token '%s' in MESH_CFACE", token ); + } +} + +static void ASE_KeyMESH_TVERTLIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*MESH_TVERT" ) ) + { + char u[80], v[80], w[80]; + + ASE_GetToken( false ); + + ASE_GetToken( false ); + strcpy( u, ase.token ); + + ASE_GetToken( false ); + strcpy( v, ase.token ); + + ASE_GetToken( false ); + strcpy( w, ase.token ); + + pMesh->tvertexes[ase.currentVertex].x = atof( u ); + // our OpenGL second texture axis is inverted from MAX's sense + pMesh->tvertexes[ase.currentVertex].y = 1.0f - atof( v ); + + ase.currentVertex++; + + if ( ase.currentVertex > pMesh->numTVertexes ) + { + common->Error( "ase.currentVertex > pMesh->numTVertexes" ); + } + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_TVERTLIST", token ); + } +} + +static void ASE_KeyMESH_CVERTLIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + pMesh->colorsParsed = true; + + if ( !strcmp( token, "*MESH_VERTCOL" ) ) + { + ASE_GetToken( false ); + + ASE_GetToken( false ); + pMesh->cvertexes[ase.currentVertex][0] = atof( token ); + + ASE_GetToken( false ); + pMesh->cvertexes[ase.currentVertex][1] = atof( token ); + + ASE_GetToken( false ); + pMesh->cvertexes[ase.currentVertex][2] = atof( token ); + + ase.currentVertex++; + + if ( ase.currentVertex > pMesh->numCVertexes ) + { + common->Error( "ase.currentVertex > pMesh->numCVertexes" ); + } + } + else { + common->Error( "Unknown token '%s' while parsing MESH_CVERTLIST", token ); + } +} + +static void ASE_KeyMESH_NORMALS( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + aseFace_t *f; + idVec3 n; + + pMesh->normalsParsed = true; + f = &pMesh->faces[ase.currentFace]; + + if ( !strcmp( token, "*MESH_FACENORMAL" ) ) + { + int num; + + ASE_GetToken( false ); + num = atoi( ase.token ); + + if ( num >= pMesh->numFaces || num < 0 ) { + common->Error( "MESH_NORMALS face index out of range: %i", num ); + } + + if ( num != ase.currentFace ) { + common->Error( "MESH_NORMALS face index != currentFace" ); + } + + ASE_GetToken( false ); + n[0] = atof( ase.token ); + ASE_GetToken( false ); + n[1] = atof( ase.token ); + ASE_GetToken( false ); + n[2]= atof( ase.token ); + + f->faceNormal[0] = n[0] * pMesh->transform[0][0] + n[1] * pMesh->transform[1][0] + n[2] * pMesh->transform[2][0]; + f->faceNormal[1] = n[0] * pMesh->transform[0][1] + n[1] * pMesh->transform[1][1] + n[2] * pMesh->transform[2][1]; + f->faceNormal[2] = n[0] * pMesh->transform[0][2] + n[1] * pMesh->transform[1][2] + n[2] * pMesh->transform[2][2]; + + f->faceNormal.Normalize(); + + ase.currentFace++; + } + else if ( !strcmp( token, "*MESH_VERTEXNORMAL" ) ) + { + int num; + int v; + + ASE_GetToken( false ); + num = atoi( ase.token ); + + if ( num >= pMesh->numVertexes || num < 0 ) { + common->Error( "MESH_NORMALS vertex index out of range: %i", num ); + } + + f = &pMesh->faces[ ase.currentFace - 1 ]; + + for ( v = 0 ; v < 3 ; v++ ) { + if ( num == f->vertexNum[ v ] ) { + break; + } + } + + if ( v == 3 ) { + common->Error( "MESH_NORMALS vertex index doesn't match face" ); + } + + ASE_GetToken( false ); + n[0] = atof( ase.token ); + ASE_GetToken( false ); + n[1] = atof( ase.token ); + ASE_GetToken( false ); + n[2]= atof( ase.token ); + + f->vertexNormals[ v ][0] = n[0] * pMesh->transform[0][0] + n[1] * pMesh->transform[1][0] + n[2] * pMesh->transform[2][0]; + f->vertexNormals[ v ][1] = n[0] * pMesh->transform[0][1] + n[1] * pMesh->transform[1][1] + n[2] * pMesh->transform[2][1]; + f->vertexNormals[ v ][2] = n[0] * pMesh->transform[0][2] + n[1] * pMesh->transform[1][2] + n[2] * pMesh->transform[2][2]; + + f->vertexNormals[v].Normalize(); + } +} + +static void ASE_KeyMESH( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if ( !strcmp( token, "*TIMEVALUE" ) ) + { + ASE_GetToken( false ); + + pMesh->timeValue = atoi( ase.token ); + VERBOSE( ( ".....timevalue: %d\n", pMesh->timeValue ) ); + } + else if ( !strcmp( token, "*MESH_NUMVERTEX" ) ) + { + ASE_GetToken( false ); + + pMesh->numVertexes = atoi( ase.token ); + VERBOSE( ( ".....num vertexes: %d\n", pMesh->numVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMTVERTEX" ) ) + { + ASE_GetToken( false ); + + pMesh->numTVertexes = atoi( ase.token ); + VERBOSE( ( ".....num tvertexes: %d\n", pMesh->numTVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMCVERTEX" ) ) + { + ASE_GetToken( false ); + + pMesh->numCVertexes = atoi( ase.token ); + VERBOSE( ( ".....num cvertexes: %d\n", pMesh->numCVertexes ) ); + } + else if ( !strcmp( token, "*MESH_NUMFACES" ) ) + { + ASE_GetToken( false ); + + pMesh->numFaces = atoi( ase.token ); + VERBOSE( ( ".....num faces: %d\n", pMesh->numFaces ) ); + } + else if ( !strcmp( token, "*MESH_NUMTVFACES" ) ) + { + ASE_GetToken( false ); + + pMesh->numTVFaces = atoi( ase.token ); + VERBOSE( ( ".....num tvfaces: %d\n", pMesh->numTVFaces ) ); + + if ( pMesh->numTVFaces != pMesh->numFaces ) + { + common->Error( "MESH_NUMTVFACES != MESH_NUMFACES" ); + } + } + else if ( !strcmp( token, "*MESH_NUMCVFACES" ) ) + { + ASE_GetToken( false ); + + pMesh->numCVFaces = atoi( ase.token ); + VERBOSE( ( ".....num cvfaces: %d\n", pMesh->numCVFaces ) ); + + if ( pMesh->numTVFaces != pMesh->numFaces ) + { + common->Error( "MESH_NUMCVFACES != MESH_NUMFACES" ); + } + } + else if ( !strcmp( token, "*MESH_VERTEX_LIST" ) ) + { + pMesh->vertexes = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numVertexes ); + ase.currentVertex = 0; + VERBOSE( ( ".....parsing MESH_VERTEX_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_VERTEX_LIST ); + } + else if ( !strcmp( token, "*MESH_TVERTLIST" ) ) + { + ase.currentVertex = 0; + pMesh->tvertexes = (idVec2 *)Mem_Alloc( sizeof( idVec2 ) * pMesh->numTVertexes ); + VERBOSE( ( ".....parsing MESH_TVERTLIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_TVERTLIST ); + } + else if ( !strcmp( token, "*MESH_CVERTLIST" ) ) + { + ase.currentVertex = 0; + pMesh->cvertexes = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numCVertexes ); + VERBOSE( ( ".....parsing MESH_CVERTLIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_CVERTLIST ); + } + else if ( !strcmp( token, "*MESH_FACE_LIST" ) ) + { + pMesh->faces = (aseFace_t *)Mem_Alloc( sizeof( aseFace_t ) * pMesh->numFaces ); + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_FACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_FACE_LIST ); + } + else if ( !strcmp( token, "*MESH_TFACELIST" ) ) + { + if ( !pMesh->faces ) { + common->Error( "*MESH_TFACELIST before *MESH_FACE_LIST" ); + } + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_TFACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyTFACE_LIST ); + } + else if ( !strcmp( token, "*MESH_CFACELIST" ) ) + { + if ( !pMesh->faces ) { + common->Error( "*MESH_CFACELIST before *MESH_FACE_LIST" ); + } + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_CFACE_LIST\n" ) ); + ASE_ParseBracedBlock( ASE_KeyCFACE_LIST ); + } + else if ( !strcmp( token, "*MESH_NORMALS" ) ) + { + if ( !pMesh->faces ) { + common->Warning( "*MESH_NORMALS before *MESH_FACE_LIST" ); + } + ase.currentFace = 0; + VERBOSE( ( ".....parsing MESH_NORMALS\n" ) ); + ASE_ParseBracedBlock( ASE_KeyMESH_NORMALS ); + } +} + +static void ASE_KeyMESH_ANIMATION( const char *token ) +{ + aseMesh_t *mesh; + + // loads a single animation frame + if ( !strcmp( token, "*MESH" ) ) + { + VERBOSE( ( "...found MESH\n" ) ); + + mesh = (aseMesh_t *)Mem_Alloc( sizeof( aseMesh_t ) ); + memset( mesh, 0, sizeof( aseMesh_t ) ); + ase.currentMesh = mesh; + + ase.currentObject->frames.Append( mesh ); + + ASE_ParseBracedBlock( ASE_KeyMESH ); + } + else + { + common->Error( "Unknown token '%s' while parsing MESH_ANIMATION", token ); + } +} + +static void ASE_KeyGEOMOBJECT( const char *token ) +{ + aseObject_t *object; + + object = ase.currentObject; + + if ( !strcmp( token, "*NODE_NAME" ) ) + { + ASE_GetToken( true ); + VERBOSE( ( " %s\n", ase.token ) ); + idStr::Copynz( object->name, ase.token, sizeof( object->name ) ); + } + else if ( !strcmp( token, "*NODE_PARENT" ) ) + { + ASE_SkipRestOfLine(); + } + // ignore unused data blocks + else if ( !strcmp( token, "*NODE_TM" ) || + !strcmp( token, "*TM_ANIMATION" ) ) + { + ASE_ParseBracedBlock( ASE_KeyNODE_TM ); + } + // ignore regular meshes that aren't part of animation + else if ( !strcmp( token, "*MESH" ) ) + { + ase.currentMesh = &ase.currentObject->mesh; + memset( ase.currentMesh, 0, sizeof( ase.currentMesh ) ); + + ASE_ParseBracedBlock( ASE_KeyMESH ); + } + // according to spec these are obsolete + else if ( !strcmp( token, "*MATERIAL_REF" ) ) + { + ASE_GetToken( false ); + + object->materialRef = atoi( ase.token ); + } + // loads a sequence of animation frames + else if ( !strcmp( token, "*MESH_ANIMATION" ) ) + { + VERBOSE( ( "..found MESH_ANIMATION\n" ) ); + + ASE_ParseBracedBlock( ASE_KeyMESH_ANIMATION ); + } + // skip unused info + else if ( !strcmp( token, "*PROP_MOTIONBLUR" ) || + !strcmp( token, "*PROP_CASTSHADOW" ) || + !strcmp( token, "*PROP_RECVSHADOW" ) ) + { + ASE_SkipRestOfLine(); + } + +} + +void ASE_ParseGeomObject( void ) { + aseObject_t *object; + + VERBOSE( ("GEOMOBJECT" ) ); + + object = (aseObject_t *)Mem_Alloc( sizeof( aseObject_t ) ); + memset( object, 0, sizeof( aseObject_t ) ); + ase.model->objects.Append( object ); + ase.currentObject = object; + + object->frames.Resize(32, 32); + + ASE_ParseBracedBlock( ASE_KeyGEOMOBJECT ); +} + +static void ASE_KeyGROUP( const char *token ) +{ + if ( !strcmp( token, "*GEOMOBJECT" ) ) { + ASE_ParseGeomObject(); + } +} + +/* +================= +ASE_Parse +================= +*/ +aseModel_t *ASE_Parse( const char *buffer, bool verbose ) { + memset( &ase, 0, sizeof( ase ) ); + + ase.verbose = verbose; + + ase.buffer = buffer; + ase.len = strlen( buffer ); + ase.curpos = ase.buffer; + ase.currentObject = NULL; + + // NOTE: using new operator because aseModel_t contains idList class objects + ase.model = new aseModel_t; + memset( ase.model, 0, sizeof( aseModel_t ) ); + ase.model->objects.Resize( 32, 32 ); + ase.model->materials.Resize( 32, 32 ); + + while ( ASE_GetToken( false ) ) { + if ( !strcmp( ase.token, "*3DSMAX_ASCIIEXPORT" ) || + !strcmp( ase.token, "*COMMENT" ) ) { + ASE_SkipRestOfLine(); + } else if ( !strcmp( ase.token, "*SCENE" ) ) { + ASE_SkipEnclosingBraces(); + } else if ( !strcmp( ase.token, "*GROUP" ) ) { + ASE_GetToken( false ); // group name + ASE_ParseBracedBlock( ASE_KeyGROUP ); + } else if ( !strcmp( ase.token, "*SHAPEOBJECT" ) ) { + ASE_SkipEnclosingBraces(); + } else if ( !strcmp( ase.token, "*CAMERAOBJECT" ) ) { + ASE_SkipEnclosingBraces(); + } else if ( !strcmp( ase.token, "*MATERIAL_LIST" ) ) { + VERBOSE( ("MATERIAL_LIST\n") ); + + ASE_ParseBracedBlock( ASE_KeyMATERIAL_LIST ); + } else if ( !strcmp( ase.token, "*GEOMOBJECT" ) ) { + ASE_ParseGeomObject(); + } else if ( ase.token[0] ) { + common->Printf( "Unknown token '%s'\n", ase.token ); + } + } + + return ase.model; +} + +/* +================= +ASE_Load +================= +*/ +aseModel_t *ASE_Load( const char *fileName ) { + char *buf; + ID_TIME_T timeStamp; + aseModel_t *ase; + + fileSystem->ReadFile( fileName, (void **)&buf, &timeStamp ); + if ( !buf ) { + return NULL; + } + + ase = ASE_Parse( buf, false ); + ase->timeStamp = timeStamp; + + fileSystem->FreeFile( buf ); + + return ase; +} + +/* +================= +ASE_Free +================= +*/ +void ASE_Free( aseModel_t *ase ) { + int i, j; + aseObject_t *obj; + aseMesh_t *mesh; + aseMaterial_t *material; + + if ( !ase ) { + return; + } + for ( i = 0; i < ase->objects.Num(); i++ ) { + obj = ase->objects[i]; + for ( j = 0; j < obj->frames.Num(); j++ ) { + mesh = obj->frames[j]; + if ( mesh->vertexes ) { + Mem_Free( mesh->vertexes ); + } + if ( mesh->tvertexes ) { + Mem_Free( mesh->tvertexes ); + } + if ( mesh->cvertexes ) { + Mem_Free( mesh->cvertexes ); + } + if ( mesh->faces ) { + Mem_Free( mesh->faces ); + } + Mem_Free( mesh ); + } + + obj->frames.Clear(); + + // free the base nesh + mesh = &obj->mesh; + if ( mesh->vertexes ) { + Mem_Free( mesh->vertexes ); + } + if ( mesh->tvertexes ) { + Mem_Free( mesh->tvertexes ); + } + if ( mesh->cvertexes ) { + Mem_Free( mesh->cvertexes ); + } + if ( mesh->faces ) { + Mem_Free( mesh->faces ); + } + Mem_Free( obj ); + } + ase->objects.Clear(); + + for ( i = 0; i < ase->materials.Num(); i++ ) { + material = ase->materials[i]; + Mem_Free( material ); + } + ase->materials.Clear(); + + delete ase; +} diff --git a/src/renderer/model_beam.cpp b/src/renderer/model_beam.cpp new file mode 100644 index 0000000..7d7f138 --- /dev/null +++ b/src/renderer/model_beam.cpp @@ -0,0 +1,210 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Model_local.h" + +/* + +This is a simple dynamic model that just creates a stretched quad between +two points that faces the view, like a dynamic deform tube. + +*/ + +static const char *beam_SnapshotName = "_beam_Snapshot_"; + +/* +=============== +idRenderModelBeam::IsDynamicModel +=============== +*/ +dynamicModel_t idRenderModelBeam::IsDynamicModel() const { + return DM_CONTINUOUS; // regenerate for every view +} + +/* +=============== +idRenderModelBeam::IsLoaded +=============== +*/ +bool idRenderModelBeam::IsLoaded() const { + return true; // don't ever need to load +} + +/* +=============== +idRenderModelBeam::InstantiateDynamicModel +=============== +*/ +idRenderModel *idRenderModelBeam::InstantiateDynamicModel( const struct renderEntity_s *renderEntity, const struct viewDef_s *viewDef, idRenderModel *cachedModel, dword surfMask ) { + (void)surfMask; + idRenderModelStatic *staticModel; + srfTriangles_t *tri; + modelSurface_t surf; + + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( renderEntity == NULL || viewDef == NULL ) { + delete cachedModel; + return NULL; + } + + if ( cachedModel != NULL ) { + + assert( dynamic_cast( cachedModel ) != NULL ); + assert( idStr::Icmp( cachedModel->Name(), beam_SnapshotName ) == 0 ); + + staticModel = static_cast( cachedModel ); + surf = *staticModel->Surface( 0 ); + tri = surf.geometry; + + } else { + + staticModel = NewRenderModel( this ); + staticModel->InitEmpty( beam_SnapshotName ); + + tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, 4 ); + R_AllocStaticTriSurfIndexes( tri, 6 ); + + tri->verts[0].Clear(); + tri->verts[0].st[0] = 0; + tri->verts[0].st[1] = 0; + + tri->verts[1].Clear(); + tri->verts[1].st[0] = 0; + tri->verts[1].st[1] = 1; + + tri->verts[2].Clear(); + tri->verts[2].st[0] = 1; + tri->verts[2].st[1] = 0; + + tri->verts[3].Clear(); + tri->verts[3].st[0] = 1; + tri->verts[3].st[1] = 1; + + tri->indexes[0] = 0; + tri->indexes[1] = 2; + tri->indexes[2] = 1; + tri->indexes[3] = 2; + tri->indexes[4] = 3; + tri->indexes[5] = 1; + + tri->numVerts = 4; + tri->numIndexes = 6; + + surf.geometry = tri; + surf.id = 0; + surf.shader = tr.defaultMaterial; + staticModel->AddSurface( surf ); + } + + idVec3 target = *reinterpret_cast( &renderEntity->shaderParms[SHADERPARM_BEAM_END_X] ); + + // we need the view direction to project the minor axis of the tube + // as the view changes + idVec3 localView, localTarget; + float modelMatrix[16]; + R_AxisToModelMatrix( renderEntity->axis, renderEntity->origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, viewDef->renderView.vieworg, localView ); + R_GlobalPointToLocal( modelMatrix, target, localTarget ); + + idVec3 major = localTarget; + idVec3 minor; + + idVec3 mid = 0.5f * localTarget; + idVec3 dir = mid - localView; + minor.Cross( major, dir ); + minor.Normalize(); + if ( renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] != 0.0f ) { + minor *= renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] * 0.5f; + } + + int red = idMath::FtoiFast( renderEntity->shaderParms[SHADERPARM_RED] * 255.0f ); + int green = idMath::FtoiFast( renderEntity->shaderParms[SHADERPARM_GREEN] * 255.0f ); + int blue = idMath::FtoiFast( renderEntity->shaderParms[SHADERPARM_BLUE] * 255.0f ); + int alpha = idMath::FtoiFast( renderEntity->shaderParms[SHADERPARM_ALPHA] * 255.0f ); + + tri->verts[0].xyz = minor; + tri->verts[0].color[0] = red; + tri->verts[0].color[1] = green; + tri->verts[0].color[2] = blue; + tri->verts[0].color[3] = alpha; + + tri->verts[1].xyz = -minor; + tri->verts[1].color[0] = red; + tri->verts[1].color[1] = green; + tri->verts[1].color[2] = blue; + tri->verts[1].color[3] = alpha; + + tri->verts[2].xyz = localTarget + minor; + tri->verts[2].color[0] = red; + tri->verts[2].color[1] = green; + tri->verts[2].color[2] = blue; + tri->verts[2].color[3] = alpha; + + tri->verts[3].xyz = localTarget - minor; + tri->verts[3].color[0] = red; + tri->verts[3].color[1] = green; + tri->verts[3].color[2] = blue; + tri->verts[3].color[3] = alpha; + + R_BoundTriSurf( tri ); + + staticModel->bounds = tri->bounds; + + return staticModel; +} + +/* +=============== +idRenderModelBeam::Bounds +=============== +*/ +idBounds idRenderModelBeam::Bounds( const struct renderEntity_s *renderEntity ) const { + idBounds b; + + b.Zero(); + if ( !renderEntity ) { + b.ExpandSelf( 8.0f ); + } else { + idVec3 target = *reinterpret_cast( &renderEntity->shaderParms[SHADERPARM_BEAM_END_X] ); + idVec3 localTarget; + float modelMatrix[16]; + R_AxisToModelMatrix( renderEntity->axis, renderEntity->origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, target, localTarget ); + + b.AddPoint( localTarget ); + if ( renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] != 0.0f ) { + b.ExpandSelf( renderEntity->shaderParms[SHADERPARM_BEAM_WIDTH] * 0.5f ); + } + } + return b; +} diff --git a/src/renderer/model_liquid.cpp b/src/renderer/model_liquid.cpp new file mode 100644 index 0000000..1161d0c --- /dev/null +++ b/src/renderer/model_liquid.cpp @@ -0,0 +1,529 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Model_local.h" + +#define LIQUID_MAX_SKIP_FRAMES 5 +#define LIQUID_MAX_TYPES 3 + +/* +==================== +idRenderModelLiquid::idRenderModelLiquid +==================== +*/ +idRenderModelLiquid::idRenderModelLiquid() { + verts_x = 32; + verts_y = 32; + scale_x = 256.0f; + scale_y = 256.0f; + liquid_type = 0; + density = 0.97f; + drop_height = 4; + drop_radius = 4; + drop_delay = 1000; + shader = declManager->FindMaterial( NULL ); + update_tics = 33; // ~30 hz + time = 0; + seed = 0; + + random.SetSeed( 0 ); +} + +/* +==================== +idRenderModelLiquid::GenerateSurface +==================== +*/ +modelSurface_t idRenderModelLiquid::GenerateSurface( float lerp ) { + srfTriangles_t *tri; + int i, base; + idDrawVert *vert; + modelSurface_t surf; + float inv_lerp; + + inv_lerp = 1.0f - lerp; + vert = verts.Ptr(); + for( i = 0; i < verts.Num(); i++, vert++ ) { + vert->xyz.z = page1[ i ] * lerp + page2[ i ] * inv_lerp; + } + + tr.pc.c_deformedSurfaces++; + tr.pc.c_deformedVerts += deformInfo->numOutputVerts; + tr.pc.c_deformedIndexes += deformInfo->numIndexes; + + tri = R_AllocStaticTriSurf(); + + // note that some of the data is references, and should not be freed + tri->deformedSurface = true; + + tri->numIndexes = deformInfo->numIndexes; + tri->indexes = deformInfo->indexes; + tri->silIndexes = deformInfo->silIndexes; + tri->numMirroredVerts = deformInfo->numMirroredVerts; + tri->mirroredVerts = deformInfo->mirroredVerts; + tri->numDupVerts = deformInfo->numDupVerts; + tri->dupVerts = deformInfo->dupVerts; + tri->numSilEdges = deformInfo->numSilEdges; + tri->silEdges = deformInfo->silEdges; + tri->dominantTris = deformInfo->dominantTris; + + tri->numVerts = deformInfo->numOutputVerts; + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + SIMDProcessor->Memcpy( tri->verts, verts.Ptr(), deformInfo->numSourceVerts * sizeof(tri->verts[0]) ); + + // replicate the mirror seam vertexes + base = deformInfo->numOutputVerts - deformInfo->numMirroredVerts; + for ( i = 0 ; i < deformInfo->numMirroredVerts ; i++ ) { + tri->verts[base + i] = tri->verts[deformInfo->mirroredVerts[i]]; + } + + R_BoundTriSurf( tri ); + + // If a surface is going to be have a lighting interaction generated, it will also have to call + // R_DeriveTangents() to get normals, tangents, and face planes. If it only + // needs shadows generated, it will only have to generate face planes. If it only + // has ambient drawing, or is culled, no additional work will be necessary + if ( !r_useDeferredTangents.GetBool() ) { + // set face planes, vertex normals, tangents + R_DeriveTangents( tri ); + } + + surf.geometry = tri; + surf.shader = shader; + + return surf; +} + +/* +==================== +idRenderModelLiquid::WaterDrop +==================== +*/ +void idRenderModelLiquid::WaterDrop( int x, int y, float *page ) { + int cx, cy; + int left,top,right,bottom; + int square; + int radsquare = drop_radius * drop_radius; + float invlength = 1.0f / ( float )radsquare; + float dist; + + if ( x < 0 ) { + x = 1 + drop_radius + random.RandomInt( verts_x - 2 * drop_radius - 1 ); + } + if ( y < 0 ) { + y = 1 + drop_radius + random.RandomInt( verts_y - 2 * drop_radius - 1 ); + } + + left=-drop_radius; right = drop_radius; + top=-drop_radius; bottom = drop_radius; + + // Perform edge clipping... + if ( x - drop_radius < 1 ) { + left -= (x-drop_radius-1); + } + if ( y - drop_radius < 1 ) { + top -= (y-drop_radius-1); + } + if ( x + drop_radius > verts_x - 1 ) { + right -= (x+drop_radius-verts_x+1); + } + if ( y + drop_radius > verts_y - 1 ) { + bottom-= (y+drop_radius-verts_y+1); + } + + for ( cy = top; cy < bottom; cy++ ) { + for ( cx = left; cx < right; cx++ ) { + square = cy*cy + cx*cx; + if ( square < radsquare ) { + dist = idMath::Sqrt( (float)square * invlength ); + page[verts_x*(cy+y) + cx+x] += idMath::Cos16( dist * idMath::PI * 0.5f ) * drop_height; + } + } + } +} + +/* +==================== +idRenderModelLiquid::IntersectBounds +==================== +*/ +void idRenderModelLiquid::IntersectBounds( const idBounds &bounds, float displacement ) { + int cx, cy; + int left,top,right,bottom; + float up, down; + float *pos; + + left = ( int )( bounds[ 0 ].x / scale_x ); + right = ( int )( bounds[ 1 ].x / scale_x ); + top = ( int )( bounds[ 0 ].y / scale_y ); + bottom = ( int )( bounds[ 1 ].y / scale_y ); + down = bounds[ 0 ].z; + up = bounds[ 1 ].z; + + if ( ( right < 1 ) || ( left >= verts_x ) || ( bottom < 1 ) || ( top >= verts_x ) ) { + return; + } + + // Perform edge clipping... + if ( left < 1 ) { + left = 1; + } + if ( right >= verts_x ) { + right = verts_x - 1; + } + if ( top < 1 ) { + top = 1; + } + if ( bottom >= verts_y ) { + bottom = verts_y - 1; + } + + for ( cy = top; cy < bottom; cy++ ) { + for ( cx = left; cx < right; cx++ ) { + pos = &page1[ verts_x * cy + cx ]; + if ( *pos > down ) {//&& ( *pos < up ) ) { + *pos = down; + } + } + } +} + +/* +==================== +idRenderModelLiquid::Update +==================== +*/ +void idRenderModelLiquid::Update( void ) { + int x, y; + float *p2; + float *p1; + float value; + + time += update_tics; + + idSwap( page1, page2 ); + + if ( time > nextDropTime ) { + WaterDrop( -1, -1, page2 ); + nextDropTime = time + drop_delay; + } else if ( time < nextDropTime - drop_delay ) { + nextDropTime = time + drop_delay; + } + + p1 = page1; + p2 = page2; + + switch( liquid_type ) { + case 0 : + for ( y = 1; y < verts_y - 1; y++ ) { + p2 += verts_x; + p1 += verts_x; + for ( x = 1; x < verts_x - 1; x++ ) { + value = + ( p2[ x + verts_x ] + + p2[ x - verts_x ] + + p2[ x + 1 ] + + p2[ x - 1 ] + + p2[ x - verts_x - 1 ] + + p2[ x - verts_x + 1 ] + + p2[ x + verts_x - 1 ] + + p2[ x + verts_x + 1 ] + + p2[ x ] ) * ( 2.0f / 9.0f ) - + p1[ x ]; + + p1[ x ] = value * density; + } + } + break; + + case 1 : + for ( y = 1; y < verts_y - 1; y++ ) { + p2 += verts_x; + p1 += verts_x; + for ( x = 1; x < verts_x - 1; x++ ) { + value = + ( p2[ x + verts_x ] + + p2[ x - verts_x ] + + p2[ x + 1 ] + + p2[ x - 1 ] + + p2[ x - verts_x - 1 ] + + p2[ x - verts_x + 1 ] + + p2[ x + verts_x - 1 ] + + p2[ x + verts_x + 1 ] ) * 0.25f - + p1[ x ]; + + p1[ x ] = value * density; + } + } + break; + + case 2 : + for ( y = 1; y < verts_y - 1; y++ ) { + p2 += verts_x; + p1 += verts_x; + for ( x = 1; x < verts_x - 1; x++ ) { + value = + ( p2[ x + verts_x ] + + p2[ x - verts_x ] + + p2[ x + 1 ] + + p2[ x - 1 ] + + p2[ x - verts_x - 1 ] + + p2[ x - verts_x + 1 ] + + p2[ x + verts_x - 1 ] + + p2[ x + verts_x + 1 ] + + p2[ x ] ) * ( 1.0f / 9.0f ); + + p1[ x ] = value * density; + } + } + break; + } +} + +/* +==================== +idRenderModelLiquid::Reset +==================== +*/ +void idRenderModelLiquid::Reset() { + int i, x, y; + + if ( pages.Num() < 2 * verts_x * verts_y ) { + return; + } + + nextDropTime = 0; + time = 0; + random.SetSeed( seed ); + + page1 = pages.Ptr(); + page2 = page1 + verts_x * verts_y; + + for ( i = 0, y = 0; y < verts_y; y++ ) { + for ( x = 0; x < verts_x; x++, i++ ) { + page1[ i ] = 0.0f; + page2[ i ] = 0.0f; + verts[ i ].xyz.z = 0.0f; + } + } +} + +/* +==================== +idRenderModelLiquid::InitFromFile +==================== +*/ +void idRenderModelLiquid::InitFromFile( const char *fileName ) { + int i, x, y; + idToken token; + idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + idList tris; + float size_x, size_y; + float rate; + + name = fileName; + + if ( !parser.LoadFile( fileName ) ) { + MakeDefaultModel(); + return; + } + + size_x = scale_x * verts_x; + size_y = scale_y * verts_y; + + while( parser.ReadToken( &token ) ) { + if ( !token.Icmp( "seed" ) ) { + seed = parser.ParseInt(); + } else if ( !token.Icmp( "size_x" ) ) { + size_x = parser.ParseFloat(); + } else if ( !token.Icmp( "size_y" ) ) { + size_y = parser.ParseFloat(); + } else if ( !token.Icmp( "verts_x" ) ) { + verts_x = parser.ParseFloat(); + if ( verts_x < 2 ) { + parser.Warning( "Invalid # of verts. Using default model." ); + MakeDefaultModel(); + return; + } + } else if ( !token.Icmp( "verts_y" ) ) { + verts_y = parser.ParseFloat(); + if ( verts_y < 2 ) { + parser.Warning( "Invalid # of verts. Using default model." ); + MakeDefaultModel(); + return; + } + } else if ( !token.Icmp( "liquid_type" ) ) { + liquid_type = parser.ParseInt() - 1; + if ( ( liquid_type < 0 ) || ( liquid_type >= LIQUID_MAX_TYPES ) ) { + parser.Warning( "Invalid liquid_type. Using default model." ); + MakeDefaultModel(); + return; + } + } else if ( !token.Icmp( "density" ) ) { + density = parser.ParseFloat(); + } else if ( !token.Icmp( "drop_height" ) ) { + drop_height = parser.ParseFloat(); + } else if ( !token.Icmp( "drop_radius" ) ) { + drop_radius = parser.ParseInt(); + } else if ( !token.Icmp( "drop_delay" ) ) { + drop_delay = SEC2MS( parser.ParseFloat() ); + } else if ( !token.Icmp( "shader" ) ) { + parser.ReadToken( &token ); + shader = declManager->FindMaterial( token ); + } else if ( !token.Icmp( "seed" ) ) { + seed = parser.ParseInt(); + } else if ( !token.Icmp( "update_rate" ) ) { + rate = parser.ParseFloat(); + if ( ( rate <= 0.0f ) || ( rate > 60.0f ) ) { + parser.Warning( "Invalid update_rate. Must be between 0 and 60. Using default model." ); + MakeDefaultModel(); + return; + } + update_tics = 1000 / rate; + } else { + parser.Warning( "Unknown parameter '%s'. Using default model.", token.c_str() ); + MakeDefaultModel(); + return; + } + } + + scale_x = size_x / ( verts_x - 1 ); + scale_y = size_y / ( verts_y - 1 ); + + pages.SetNum( 2 * verts_x * verts_y ); + page1 = pages.Ptr(); + page2 = page1 + verts_x * verts_y; + + verts.SetNum( verts_x * verts_y ); + for ( i = 0, y = 0; y < verts_y; y++ ) { + for ( x = 0; x < verts_x; x++, i++ ) { + page1[ i ] = 0.0f; + page2[ i ] = 0.0f; + verts[ i ].Clear(); + verts[ i ].xyz.Set( x * scale_x, y * scale_y, 0.0f ); + verts[ i ].st.Set( (float) x / (float)( verts_x - 1 ), (float) -y / (float)( verts_y - 1 ) ); + } + } + + tris.SetNum( ( verts_x - 1 ) * ( verts_y - 1 ) * 6 ); + for( i = 0, y = 0; y < verts_y - 1; y++ ) { + for( x = 1; x < verts_x; x++, i += 6 ) { + tris[ i + 0 ] = y * verts_x + x; + tris[ i + 1 ] = y * verts_x + x - 1; + tris[ i + 2 ] = ( y + 1 ) * verts_x + x - 1; + + tris[ i + 3 ] = ( y + 1 ) * verts_x + x - 1; + tris[ i + 4 ] = ( y + 1 ) * verts_x + x; + tris[ i + 5 ] = y * verts_x + x; + } + } + + // build the information that will be common to all animations of this mesh: + // sil edge connectivity and normal / tangent generation information + deformInfo = R_BuildDeformInfo( verts.Num(), verts.Ptr(), tris.Num(), tris.Ptr(), true ); + + bounds.Clear(); + bounds.AddPoint( idVec3( 0.0f, 0.0f, drop_height * -10.0f ) ); + bounds.AddPoint( idVec3( ( verts_x - 1 ) * scale_x, ( verts_y - 1 ) * scale_y, drop_height * 10.0f ) ); + + // set the timestamp for reloadmodels + fileSystem->ReadFile( name, NULL, &timeStamp ); + + Reset(); +} + +/* +==================== +idRenderModelLiquid::InstantiateDynamicModel +==================== +*/ +idRenderModel *idRenderModelLiquid::InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ) { + (void)surfMask; + idRenderModelStatic *staticModel; + int frames; + int t; + float lerp; + + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( !deformInfo ) { + return NULL; + } + + if ( !view ) { + t = 0; + } else { + t = view->renderView.time; + } + + // update the liquid model + frames = ( t - time ) / update_tics; + if ( frames > LIQUID_MAX_SKIP_FRAMES ) { + // don't let time accumalate when skipping frames + time += update_tics * ( frames - LIQUID_MAX_SKIP_FRAMES ); + + frames = LIQUID_MAX_SKIP_FRAMES; + } + + while( frames > 0 ) { + Update(); + frames--; + } + + // create the surface + lerp = ( float )( t - time ) / ( float )update_tics; + modelSurface_t surf = GenerateSurface( lerp ); + + staticModel = NewRenderModel( this ); + staticModel->AddSurface( surf ); + staticModel->bounds = surf.geometry->bounds; + + return staticModel; +} + +/* +==================== +idRenderModelLiquid::IsDynamicModel +==================== +*/ +dynamicModel_t idRenderModelLiquid::IsDynamicModel() const { + return DM_CONTINUOUS; +} + +/* +==================== +idRenderModelLiquid::Bounds +==================== +*/ +idBounds idRenderModelLiquid::Bounds(const struct renderEntity_s *ent) const { + // FIXME: need to do this better + return bounds; +} diff --git a/src/renderer/model_lwo.cpp b/src/renderer/model_lwo.cpp new file mode 100644 index 0000000..c8633a9 --- /dev/null +++ b/src/renderer/model_lwo.cpp @@ -0,0 +1,4134 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Model_lwo.h" + +/* +====================================================================== + + Converted from lwobject sample prog from LW 6.5 SDK. + +====================================================================== +*/ + +/* +====================================================================== +lwFreeClip() + +Free memory used by an lwClip. +====================================================================== */ + +void lwFreeClip( lwClip *clip ) +{ + if ( clip ) { + lwListFree( clip->ifilter, (void (__cdecl *)(void *))lwFreePlugin ); + lwListFree( clip->pfilter, (void (__cdecl *)(void *))lwFreePlugin ); + switch( clip->type ) { + case ID_STIL: { + if ( clip->source.still.name ) Mem_Free( clip->source.still.name ); + break; + } + case ID_ISEQ: { + if ( clip->source.seq.suffix ) Mem_Free( clip->source.seq.suffix ); + if ( clip->source.seq.prefix ) Mem_Free( clip->source.seq.prefix ); + break; + } + case ID_ANIM: { + if ( clip->source.anim.server ) Mem_Free( clip->source.anim.server ); + if ( clip->source.anim.name ) Mem_Free( clip->source.anim.name ); + break; + } + case ID_XREF: { + if ( clip->source.xref.string ) Mem_Free( clip->source.xref.string ); + break; + } + case ID_STCC: { + if ( clip->source.cycle.name ) Mem_Free( clip->source.cycle.name ); + break; + } + } + Mem_Free( clip ); + } +} + + +/* +====================================================================== +lwGetClip() + +Read image references from a CLIP chunk in an LWO2 file. +====================================================================== */ + +lwClip *lwGetClip( idFile *fp, int cksize ) +{ + lwClip *clip; + lwPlugin *filt; + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* allocate the Clip structure */ + + clip = (lwClip*)Mem_ClearedAlloc( sizeof( lwClip ) ); + if ( !clip ) goto Fail; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* index */ + + clip->index = getI4( fp ); + + /* first subchunk header */ + + clip->type = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + sz += sz & 1; + set_flen( 0 ); + + switch ( clip->type ) { + case ID_STIL: + clip->source.still.name = getS0( fp ); + break; + + case ID_ISEQ: + clip->source.seq.digits = getU1( fp ); + clip->source.seq.flags = getU1( fp ); + clip->source.seq.offset = getI2( fp ); + clip->source.seq.start = getI2( fp ); + clip->source.seq.end = getI2( fp ); + clip->source.seq.prefix = getS0( fp ); + clip->source.seq.suffix = getS0( fp ); + break; + + case ID_ANIM: + clip->source.anim.name = getS0( fp ); + clip->source.anim.server = getS0( fp ); + rlen = get_flen(); + clip->source.anim.data = getbytes( fp, sz - rlen ); + break; + + case ID_XREF: + clip->source.xref.index = getI4( fp ); + clip->source.xref.string = getS0( fp ); + break; + + case ID_STCC: + clip->source.cycle.lo = getI2( fp ); + clip->source.cycle.hi = getI2( fp ); + clip->source.cycle.name = getS0( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = fp->Tell() - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) + return clip; + + /* process subchunks as they're encountered */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TIME: + clip->start_time = getF4( fp ); + clip->duration = getF4( fp ); + clip->frame_rate = getF4( fp ); + break; + + case ID_CONT: + clip->contrast.val = getF4( fp ); + clip->contrast.eindex = getVX( fp ); + break; + + case ID_BRIT: + clip->brightness.val = getF4( fp ); + clip->brightness.eindex = getVX( fp ); + break; + + case ID_SATR: + clip->saturation.val = getF4( fp ); + clip->saturation.eindex = getVX( fp ); + break; + + case ID_HUE: + clip->hue.val = getF4( fp ); + clip->hue.eindex = getVX( fp ); + break; + + case ID_GAMM: + clip->gamma.val = getF4( fp ); + clip->gamma.eindex = getVX( fp ); + break; + + case ID_NEGA: + clip->negative = getU2( fp ); + break; + + case ID_IFLT: + case ID_PFLT: + filt = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ) ); + if ( !filt ) goto Fail; + + filt->name = getS0( fp ); + filt->flags = getU2( fp ); + rlen = get_flen(); + filt->data = getbytes( fp, sz - rlen ); + + if ( id == ID_IFLT ) { + lwListAdd( (void**)&clip->ifilter, filt ); + clip->nifilters++; + } + else { + lwListAdd( (void**)&clip->pfilter, filt ); + clip->npfilters++; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the CLIP chunk? */ + + rlen = fp->Tell() - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return clip; + +Fail: + lwFreeClip( clip ); + return NULL; +} + + +/* +====================================================================== +lwFindClip() + +Returns an lwClip pointer, given a clip index. +====================================================================== */ + +lwClip *lwFindClip( lwClip *list, int index ) +{ + lwClip *clip; + + clip = list; + while ( clip ) { + if ( clip->index == index ) break; + clip = clip->next; + } + return clip; +} + + +/* +====================================================================== +lwFreeEnvelope() + +Free the memory used by an lwEnvelope. +====================================================================== */ + +void lwFree( void *ptr ) { + Mem_Free( ptr ); +} + +void lwFreeEnvelope( lwEnvelope *env ) +{ + if ( env ) { + if ( env->name ) Mem_Free( env->name ); + lwListFree( env->key, lwFree ); + lwListFree( env->cfilter, (void (__cdecl *)(void *))lwFreePlugin ); + Mem_Free( env ); + } +} + + +static int compare_keys( lwKey *k1, lwKey *k2 ) +{ + return k1->time > k2->time ? 1 : k1->time < k2->time ? -1 : 0; +} + + +/* +====================================================================== +lwGetEnvelope() + +Read an ENVL chunk from an LWO2 file. +====================================================================== */ + +lwEnvelope *lwGetEnvelope( idFile *fp, int cksize ) +{ + lwEnvelope *env; + lwKey *key; + lwPlugin *plug; + unsigned int id; + unsigned short sz; + float f[ 4 ]; + int i, nparams, pos, rlen; + + + /* allocate the Envelope structure */ + + env = (lwEnvelope*)Mem_ClearedAlloc( sizeof( lwEnvelope ) ); + if ( !env ) goto Fail; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* index */ + + env->index = getVX( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TYPE: + env->type = getU2( fp ); + break; + + case ID_NAME: + env->name = getS0( fp ); + break; + + case ID_PRE: + env->behavior[ 0 ] = getU2( fp ); + break; + + case ID_POST: + env->behavior[ 1 ] = getU2( fp ); + break; + + case ID_KEY: + key = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ) ); + if ( !key ) goto Fail; + key->time = getF4( fp ); + key->value = getF4( fp ); + lwListInsert( (void**)&env->key, key, (int (__cdecl *)(void *,void *))compare_keys ); + env->nkeys++; + break; + + case ID_SPAN: + if ( !key ) goto Fail; + key->shape = getU4( fp ); + + nparams = ( sz - 4 ) / 4; + if ( nparams > 4 ) nparams = 4; + for ( i = 0; i < nparams; i++ ) + f[ i ] = getF4( fp ); + + switch ( key->shape ) { + case ID_TCB: + key->tension = f[ 0 ]; + key->continuity = f[ 1 ]; + key->bias = f[ 2 ]; + break; + + case ID_BEZI: + case ID_HERM: + case ID_BEZ2: + for ( i = 0; i < nparams; i++ ) + key->param[ i ] = f[ i ]; + break; + } + break; + + case ID_CHAN: + plug = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ) ); + if ( !plug ) goto Fail; + + plug->name = getS0( fp ); + plug->flags = getU2( fp ); + plug->data = getbytes( fp, sz - get_flen() ); + + lwListAdd( (void**)&env->cfilter, plug ); + env->ncfilters++; + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the ENVL chunk? */ + + rlen = fp->Tell() - pos; + if ( cksize < rlen ) goto Fail; + if ( cksize == rlen ) break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return env; + +Fail: + lwFreeEnvelope( env ); + return NULL; +} + + +/* +====================================================================== +lwFindEnvelope() + +Returns an lwEnvelope pointer, given an envelope index. +====================================================================== */ + +lwEnvelope *lwFindEnvelope( lwEnvelope *list, int index ) +{ + lwEnvelope *env; + + env = list; + while ( env ) { + if ( env->index == index ) break; + env = env->next; + } + return env; +} + + +/* +====================================================================== +range() + +Given the value v of a periodic function, returns the equivalent value +v2 in the principal interval [lo, hi]. If i isn't NULL, it receives +the number of wavelengths between v and v2. + + v2 = v - i * (hi - lo) + +For example, range( 3 pi, 0, 2 pi, i ) returns pi, with i = 1. +====================================================================== */ + +static float range( float v, float lo, float hi, int *i ) +{ + float v2, r = hi - lo; + + if ( r == 0.0 ) { + if ( i ) *i = 0; + return lo; + } + + v2 = lo + v - r * ( float ) floor(( double ) v / r ); + if ( i ) *i = -( int )(( v2 - v ) / r + ( v2 > v ? 0.5 : -0.5 )); + + return v2; +} + + +/* +====================================================================== +hermite() + +Calculate the Hermite coefficients. +====================================================================== */ + +static void hermite( float t, float *h1, float *h2, float *h3, float *h4 ) +{ + float t2, t3; + + t2 = t * t; + t3 = t * t2; + + *h2 = 3.0f * t2 - t3 - t3; + *h1 = 1.0f - *h2; + *h4 = t3 - t2; + *h3 = *h4 - t2 + t; +} + + +/* +====================================================================== +bezier() + +Interpolate the value of a 1D Bezier curve. +====================================================================== */ + +static float bezier( float x0, float x1, float x2, float x3, float t ) +{ + float a, b, c, t2, t3; + + t2 = t * t; + t3 = t2 * t; + + c = 3.0f * ( x1 - x0 ); + b = 3.0f * ( x2 - x1 ) - c; + a = x3 - x0 - c - b; + + return a * t3 + b * t2 + c * t + x0; +} + + +/* +====================================================================== +bez2_time() + +Find the t for which bezier() returns the input time. The handle +endpoints of a BEZ2 curve represent the control points, and these have +(time, value) coordinates, so time is used as both a coordinate and a +parameter for this curve type. +====================================================================== */ + +static float bez2_time( float x0, float x1, float x2, float x3, float time, + float *t0, float *t1 ) +{ + float v, t; + + t = *t0 + ( *t1 - *t0 ) * 0.5f; + v = bezier( x0, x1, x2, x3, t ); + if ( idMath::Fabs( time - v ) > .0001f ) { + if ( v > time ) + *t1 = t; + else + *t0 = t; + return bez2_time( x0, x1, x2, x3, time, t0, t1 ); + } + else + return t; +} + + +/* +====================================================================== +bez2() + +Interpolate the value of a BEZ2 curve. +====================================================================== */ + +static float bez2( lwKey *key0, lwKey *key1, float time ) +{ + float x, y, t, t0 = 0.0f, t1 = 1.0f; + + if ( key0->shape == ID_BEZ2 ) + x = key0->time + key0->param[ 2 ]; + else + x = key0->time + ( key1->time - key0->time ) / 3.0f; + + t = bez2_time( key0->time, x, key1->time + key1->param[ 0 ], key1->time, + time, &t0, &t1 ); + + if ( key0->shape == ID_BEZ2 ) + y = key0->value + key0->param[ 3 ]; + else + y = key0->value + key0->param[ 1 ] / 3.0f; + + return bezier( key0->value, y, key1->param[ 1 ] + key1->value, key1->value, t ); +} + + +/* +====================================================================== +outgoing() + +Return the outgoing tangent to the curve at key0. The value returned +for the BEZ2 case is used when extrapolating a linear pre behavior and +when interpolating a non-BEZ2 span. +====================================================================== */ + +static float outgoing( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, out; + + switch ( key0->shape ) + { + case ID_TCB: + a = ( 1.0f - key0->tension ) + * ( 1.0f + key0->continuity ) + * ( 1.0f + key0->bias ); + b = ( 1.0f - key0->tension ) + * ( 1.0f - key0->continuity ) + * ( 1.0f - key0->bias ); + d = key1->value - key0->value; + + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( a * ( key0->value - key0->prev->value ) + b * d ); + } + else + out = b * d; + break; + + case ID_LINE: + d = key1->value - key0->value; + if ( key0->prev ) { + t = ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + out = t * ( key0->value - key0->prev->value + d ); + } + else + out = d; + break; + + case ID_BEZI: + case ID_HERM: + out = key0->param[ 1 ]; + if ( key0->prev ) + out *= ( key1->time - key0->time ) / ( key1->time - key0->prev->time ); + break; + + case ID_BEZ2: + out = key0->param[ 3 ] * ( key1->time - key0->time ); + if ( idMath::Fabs( key0->param[ 2 ] ) > 1e-5f ) + out /= key0->param[ 2 ]; + else + out *= 1e5f; + break; + + case ID_STEP: + default: + out = 0.0f; + break; + } + + return out; +} + + +/* +====================================================================== +incoming() + +Return the incoming tangent to the curve at key1. The value returned +for the BEZ2 case is used when extrapolating a linear post behavior. +====================================================================== */ + +static float incoming( lwKey *key0, lwKey *key1 ) +{ + float a, b, d, t, in; + + switch ( key1->shape ) + { + case ID_LINE: + d = key1->value - key0->value; + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( key1->next->value - key1->value + d ); + } + else + in = d; + break; + + case ID_TCB: + a = ( 1.0f - key1->tension ) + * ( 1.0f - key1->continuity ) + * ( 1.0f + key1->bias ); + b = ( 1.0f - key1->tension ) + * ( 1.0f + key1->continuity ) + * ( 1.0f - key1->bias ); + d = key1->value - key0->value; + + if ( key1->next ) { + t = ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + in = t * ( b * ( key1->next->value - key1->value ) + a * d ); + } + else + in = a * d; + break; + + case ID_BEZI: + case ID_HERM: + in = key1->param[ 0 ]; + if ( key1->next ) + in *= ( key1->time - key0->time ) / ( key1->next->time - key0->time ); + break; + return in; + + case ID_BEZ2: + in = key1->param[ 1 ] * ( key1->time - key0->time ); + if ( idMath::Fabs( key1->param[ 0 ] ) > 1e-5f ) + in /= key1->param[ 0 ]; + else + in *= 1e5f; + break; + + case ID_STEP: + default: + in = 0.0f; + break; + } + + return in; +} + + +/* +====================================================================== +evalEnvelope() + +Given a list of keys and a time, returns the interpolated value of the +envelope at that time. +====================================================================== */ + +float evalEnvelope( lwEnvelope *env, float time ) +{ + lwKey *key0, *key1, *skey, *ekey; + float t, h1, h2, h3, h4, in, out, offset = 0.0f; + int noff; + + + /* if there's no key, the value is 0 */ + + if ( env->nkeys == 0 ) return 0.0f; + + /* if there's only one key, the value is constant */ + + if ( env->nkeys == 1 ) + return env->key->value; + + /* find the first and last keys */ + + skey = ekey = env->key; + while ( ekey->next ) ekey = ekey->next; + + /* use pre-behavior if time is before first key time */ + + if ( time < skey->time ) { + switch ( env->behavior[ 0 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return skey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + out = outgoing( skey, skey->next ) + / ( skey->next->time - skey->time ); + return out * ( time - skey->time ) + skey->value; + } + } + + /* use post-behavior if time is after last key time */ + + else if ( time > ekey->time ) { + switch ( env->behavior[ 1 ] ) + { + case BEH_RESET: + return 0.0f; + + case BEH_CONSTANT: + return ekey->value; + + case BEH_REPEAT: + time = range( time, skey->time, ekey->time, NULL ); + break; + + case BEH_OSCILLATE: + time = range( time, skey->time, ekey->time, &noff ); + if ( noff % 2 ) + time = ekey->time - skey->time - time; + break; + + case BEH_OFFSET: + time = range( time, skey->time, ekey->time, &noff ); + offset = noff * ( ekey->value - skey->value ); + break; + + case BEH_LINEAR: + in = incoming( ekey->prev, ekey ) + / ( ekey->time - ekey->prev->time ); + return in * ( time - ekey->time ) + ekey->value; + } + } + + /* get the endpoints of the interval being evaluated */ + + key0 = env->key; + while ( time > key0->next->time ) + key0 = key0->next; + key1 = key0->next; + + /* check for singularities first */ + + if ( time == key0->time ) + return key0->value + offset; + else if ( time == key1->time ) + return key1->value + offset; + + /* get interval length, time in [0, 1] */ + + t = ( time - key0->time ) / ( key1->time - key0->time ); + + /* interpolate */ + + switch ( key1->shape ) + { + case ID_TCB: + case ID_BEZI: + case ID_HERM: + out = outgoing( key0, key1 ); + in = incoming( key0, key1 ); + hermite( t, &h1, &h2, &h3, &h4 ); + return h1 * key0->value + h2 * key1->value + h3 * out + h4 * in + offset; + + case ID_BEZ2: + return bez2( key0, key1, time ) + offset; + + case ID_LINE: + return key0->value + t * ( key1->value - key0->value ) + offset; + + case ID_STEP: + return key0->value + offset; + + default: + return offset; + } +} + + + +/* +====================================================================== +lwListFree() + +Free the items in a list. +====================================================================== */ + +void lwListFree( void *list, void ( *freeNode )( void * )) +{ + lwNode *node, *next; + + node = ( lwNode * ) list; + while ( node ) { + next = node->next; + freeNode( node ); + node = next; + } +} + + +/* +====================================================================== +lwListAdd() + +Append a node to a list. +====================================================================== */ + +void lwListAdd( void **list, void *node ) +{ + lwNode *head, *tail; + + head = *(( lwNode ** ) list ); + if ( !head ) { + *list = node; + return; + } + while ( head ) { + tail = head; + head = head->next; + } + tail->next = ( lwNode * ) node; + (( lwNode * ) node )->prev = tail; +} + + +/* +====================================================================== +lwListInsert() + +Insert a node into a list in sorted order. +====================================================================== */ + +void lwListInsert( void **vlist, void *vitem, int ( *compare )( void *, void * )) +{ + lwNode **list, *item, *node, *prev; + + if ( !*vlist ) { + *vlist = vitem; + return; + } + + list = ( lwNode ** ) vlist; + item = ( lwNode * ) vitem; + node = *list; + prev = NULL; + + while ( node ) { + if ( 0 < compare( node, item )) break; + prev = node; + node = node->next; + } + + if ( !prev ) { + *list = item; + node->prev = item; + item->next = node; + } + else if ( !node ) { + prev->next = item; + item->prev = prev; + } + else { + item->next = node; + item->prev = prev; + prev->next = item; + node->prev = item; + } +} + +/* +====================================================================== +flen + +This accumulates a count of the number of bytes read. Callers can set +it at the beginning of a sequence of reads and then retrieve it to get +the number of bytes actually read. If one of the I/O functions fails, +flen is set to an error code, after which the I/O functions ignore +read requests until flen is reset. +====================================================================== */ + +#define FLEN_ERROR -9999 + +static int flen; + +void set_flen( int i ) { flen = i; } + +int get_flen( void ) { return flen; } + +void *getbytes( idFile *fp, int size ) +{ + void *data; + + if ( flen == FLEN_ERROR ) return NULL; + if ( size < 0 ) { + flen = FLEN_ERROR; + return NULL; + } + data = Mem_ClearedAlloc( size ); + if ( !data ) { + flen = FLEN_ERROR; + return NULL; + } + if ( size != fp->Read( data, size ) ) { + flen = FLEN_ERROR; + Mem_Free( data ); + return NULL; + } + + flen += size; + return data; +} + + +void skipbytes( idFile *fp, int n ) +{ + if ( flen == FLEN_ERROR ) return; + if ( fp->Seek( n, FS_SEEK_CUR )) + flen = FLEN_ERROR; + else + flen += n; +} + + +int getI1( idFile *fp ) +{ + int i, c; + + if ( flen == FLEN_ERROR ) return 0; + c = 0; + i = fp->Read(&c, 1); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + if ( c > 127 ) c -= 256; + flen += 1; + return c; +} + + +short getI2( idFile *fp ) +{ + short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 2 != fp->Read( &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +int getI4( idFile *fp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 4 != fp->Read( &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +unsigned char getU1( idFile *fp ) +{ + int i, c; + + if ( flen == FLEN_ERROR ) return 0; + c = 0; + i = fp->Read(&c, 1); + if ( i < 0 ) { + flen = FLEN_ERROR; + return 0; + } + flen += 1; + return c; +} + + +unsigned short getU2( idFile *fp ) +{ + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 2 != fp->Read( &i, 2 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 2, 1 ); + flen += 2; + return i; +} + + +unsigned int getU4( idFile *fp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + if ( 4 != fp->Read( &i, 4 )) { + flen = FLEN_ERROR; + return 0; + } + BigRevBytes( &i, 4, 1 ); + flen += 4; + return i; +} + + +int getVX( idFile *fp ) +{ + byte c; + int i; + + if ( flen == FLEN_ERROR ) return 0; + + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + + if ( c != 0xFF ) { + i = c << 8; + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i |= c; + flen += 2; + } + else { + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i = c << 16; + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i |= c << 8; + c = 0; + if (fp->Read(&c, 1) == -1) { + return 0; + } + i |= c; + flen += 4; + } + + return i; +} + + +float getF4( idFile *fp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + if ( 4 != fp->Read( &f, 4 ) ) { + flen = FLEN_ERROR; + return 0.0f; + } + BigRevBytes( &f, 4, 1 ); + flen += 4; + + if ( FLOAT_IS_DENORMAL( f ) ) { + f = 0.0f; + } + return f; +} + + +char *getS0( idFile *fp ) +{ + char *s; + int i, c, len, pos; + + if ( flen == FLEN_ERROR ) return NULL; + + pos = fp->Tell(); + for ( i = 1; ; i++ ) { + c = 0; + if (fp->Read(&c, 1) == -1) { + flen = FLEN_ERROR; + return NULL; + } + if ( c == 0 ) break; + } + + if ( i == 1 ) { + if ( fp->Seek( pos + 2, FS_SEEK_SET )) + flen = FLEN_ERROR; + else + flen += 2; + return NULL; + } + + len = i + ( i & 1 ); + s = (char*)Mem_ClearedAlloc( len ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + if ( fp->Seek( pos, FS_SEEK_SET )) { + flen = FLEN_ERROR; + return NULL; + } + if ( len != fp->Read( s, len )) { + flen = FLEN_ERROR; + return NULL; + } + + flen += len; + return s; +} + + +int sgetI1( unsigned char **bp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + i = **bp; + if ( i > 127 ) i -= 256; + flen += 1; + *bp++; + return i; +} + + +short sgetI2( unsigned char **bp ) +{ + short i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 2 ); + BigRevBytes( &i, 2, 1 ); + flen += 2; + *bp += 2; + return i; +} + + +int sgetI4( unsigned char **bp ) +{ + int i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 4 ); + BigRevBytes( &i, 4, 1 ); + flen += 4; + *bp += 4; + return i; +} + + +unsigned char sgetU1( unsigned char **bp ) +{ + unsigned char c; + + if ( flen == FLEN_ERROR ) return 0; + c = **bp; + flen += 1; + *bp++; + return c; +} + + +unsigned short sgetU2( unsigned char **bp ) +{ + unsigned char *buf = *bp; + unsigned short i; + + if ( flen == FLEN_ERROR ) return 0; + i = ( buf[ 0 ] << 8 ) | buf[ 1 ]; + flen += 2; + *bp += 2; + return i; +} + + +unsigned int sgetU4( unsigned char **bp ) +{ + unsigned int i; + + if ( flen == FLEN_ERROR ) return 0; + memcpy( &i, *bp, 4 ); + BigRevBytes( &i, 4, 1 ); + flen += 4; + *bp += 4; + return i; +} + + +int sgetVX( unsigned char **bp ) +{ + unsigned char *buf = *bp; + int i; + + if ( flen == FLEN_ERROR ) return 0; + + if ( buf[ 0 ] != 0xFF ) { + i = buf[ 0 ] << 8 | buf[ 1 ]; + flen += 2; + *bp += 2; + } + else { + i = ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ]; + flen += 4; + *bp += 4; + } + return i; +} + + +float sgetF4( unsigned char **bp ) +{ + float f; + + if ( flen == FLEN_ERROR ) return 0.0f; + memcpy( &f, *bp, 4 ); + BigRevBytes( &f, 4, 1 ); + flen += 4; + *bp += 4; + + if ( FLOAT_IS_DENORMAL( f ) ) { + f = 0.0f; + } + return f; +} + + +char *sgetS0( unsigned char **bp ) +{ + char *s; + unsigned char *buf = *bp; + int len; + + if ( flen == FLEN_ERROR ) return NULL; + + len = strlen( (const char*)buf ) + 1; + if ( len == 1 ) { + flen += 2; + *bp += 2; + return NULL; + } + len += len & 1; + s = (char*)Mem_ClearedAlloc( len ); + if ( !s ) { + flen = FLEN_ERROR; + return NULL; + } + + memcpy( s, buf, len ); + flen += len; + *bp += len; + return s; +} + +/* +====================================================================== +lwFreeLayer() + +Free memory used by an lwLayer. +====================================================================== */ + +void lwFreeLayer( lwLayer *layer ) +{ + if ( layer ) { + if ( layer->name ) Mem_Free( layer->name ); + lwFreePoints( &layer->point ); + lwFreePolygons( &layer->polygon ); + lwListFree( layer->vmap, (void (__cdecl *)(void *))lwFreeVMap ); + Mem_Free( layer ); + } +} + + +/* +====================================================================== +lwFreeObject() + +Free memory used by an lwObject. +====================================================================== */ + +void lwFreeObject( lwObject *object ) +{ + if ( object ) { + lwListFree( object->layer, (void (__cdecl *)(void *))lwFreeLayer ); + lwListFree( object->env, (void (__cdecl *)(void *))lwFreeEnvelope ); + lwListFree( object->clip, (void (__cdecl *)(void *))lwFreeClip ); + lwListFree( object->surf, (void (__cdecl *)(void *))lwFreeSurface ); + lwFreeTags( &object->taglist ); + Mem_Free( object ); + } +} + + +/* +====================================================================== +lwGetObject() + +Returns the contents of a LightWave object, given its filename, or +NULL if the file couldn't be loaded. On failure, failID and failpos +can be used to diagnose the cause. + +1. If the file isn't an LWO2 or an LWOB, failpos will contain 12 and + failID will be unchanged. + +2. If an error occurs while reading, failID will contain the most + recently read IFF chunk ID, and failpos will contain the value + returned by fp->Tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject( const char *filename, unsigned int *failID, int *failpos ) +{ + idFile *fp = NULL; + lwObject *object; + lwLayer *layer; + lwNode *node; + int id, formsize, type, cksize; + int i, rlen; + + fp = fileSystem->OpenFileRead( filename ); + if ( !fp ) { + return NULL; + } + + /* read the first 12 bytes */ + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + fileSystem->CloseFile( fp ); + return NULL; + } + + /* is this a LW object? */ + + if ( id != ID_FORM ) { + fileSystem->CloseFile( fp ); + if ( failpos ) *failpos = 12; + return NULL; + } + + if ( type != ID_LWO2 ) { + fileSystem->CloseFile( fp ); + if ( type == ID_LWOB ) + return lwGetObject5( filename, failID, failpos ); + else { + if ( failpos ) *failpos = 12; + return NULL; + } + } + + /* allocate an object and a default layer */ + + object = (lwObject*)Mem_ClearedAlloc( sizeof( lwObject ) ); + if ( !object ) goto Fail; + + layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ) ); + if ( !layer ) goto Fail; + object->layer = layer; + + object->timeStamp = fp->Timestamp(); + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_LAYR: + if ( object->nlayers > 0 ) { + layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ) ); + if ( !layer ) goto Fail; + lwListAdd( (void**)&object->layer, layer ); + } + object->nlayers++; + + set_flen( 0 ); + layer->index = getU2( fp ); + layer->flags = getU2( fp ); + layer->pivot[ 0 ] = getF4( fp ); + layer->pivot[ 1 ] = getF4( fp ); + layer->pivot[ 2 ] = getF4( fp ); + layer->name = getS0( fp ); + + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen <= cksize - 2 ) + layer->parent = getU2( fp ); + rlen = get_flen(); + if ( rlen < cksize ) + fp->Seek( cksize - rlen, FS_SEEK_CUR ); + break; + + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail; + break; + + case ID_POLS: + if ( !lwGetPolygons( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail; + break; + + case ID_VMAP: + case ID_VMAD: + node = ( lwNode * ) lwGetVMap( fp, cksize, layer->point.offset, + layer->polygon.offset, id == ID_VMAD ); + if ( !node ) goto Fail; + lwListAdd( (void**)&layer->vmap, node ); + layer->nvmaps++; + break; + + case ID_PTAG: + if ( !lwGetPolygonTags( fp, cksize, &object->taglist, + &layer->polygon )) + goto Fail; + break; + + case ID_BBOX: + set_flen( 0 ); + for ( i = 0; i < 6; i++ ) + layer->bbox[ i ] = getF4( fp ); + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) goto Fail; + if ( rlen < cksize ) + fp->Seek( cksize - rlen, FS_SEEK_CUR ); + break; + + case ID_TAGS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail; + break; + + case ID_ENVL: + node = ( lwNode * ) lwGetEnvelope( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void**)&object->env, node ); + object->nenvs++; + break; + + case ID_CLIP: + node = ( lwNode * ) lwGetClip( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void**)&object->clip, node ); + object->nclips++; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface( fp, cksize ); + if ( !node ) goto Fail; + lwListAdd( (void**)&object->surf, node ); + object->nsurfs++; + break; + + case ID_DESC: + case ID_TEXT: + case ID_ICON: + default: + fp->Seek( cksize, FS_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= fp->Tell() - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail; + } + + fileSystem->CloseFile( fp ); + fp = NULL; + + if ( object->nlayers == 0 ) + object->nlayers = 1; + + layer = object->layer; + while ( layer ) { + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail; + lwGetVertNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointVMaps( &layer->point, layer->vmap )) goto Fail; + if ( !lwGetPolyVMaps( &layer->polygon, layer->vmap )) goto Fail; + layer = layer->next; + } + + return object; + +Fail: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = fp->Tell(); + fileSystem->CloseFile( fp ); + } + lwFreeObject( object ); + return NULL; +} + + + + +/* IDs specific to LWOB */ + +#define ID_SRFS LWID_('S','R','F','S') +#define ID_FLAG LWID_('F','L','A','G') +#define ID_VLUM LWID_('V','L','U','M') +#define ID_VDIF LWID_('V','D','I','F') +#define ID_VSPC LWID_('V','S','P','C') +#define ID_RFLT LWID_('R','F','L','T') +#define ID_BTEX LWID_('B','T','E','X') +#define ID_CTEX LWID_('C','T','E','X') +#define ID_DTEX LWID_('D','T','E','X') +#define ID_LTEX LWID_('L','T','E','X') +#define ID_RTEX LWID_('R','T','E','X') +#define ID_STEX LWID_('S','T','E','X') +#define ID_TTEX LWID_('T','T','E','X') +#define ID_TFLG LWID_('T','F','L','G') +#define ID_TSIZ LWID_('T','S','I','Z') +#define ID_TCTR LWID_('T','C','T','R') +#define ID_TFAL LWID_('T','F','A','L') +#define ID_TVEL LWID_('T','V','E','L') +#define ID_TCLR LWID_('T','C','L','R') +#define ID_TVAL LWID_('T','V','A','L') +#define ID_TAMP LWID_('T','A','M','P') +#define ID_TIMG LWID_('T','I','M','G') +#define ID_TAAS LWID_('T','A','A','S') +#define ID_TREF LWID_('T','R','E','F') +#define ID_TOPC LWID_('T','O','P','C') +#define ID_SDAT LWID_('S','D','A','T') +#define ID_TFP0 LWID_('T','F','P','0') +#define ID_TFP1 LWID_('T','F','P','1') + + +/* +====================================================================== +add_clip() + +Add a clip to the clip list. Used to store the contents of an RIMG or +TIMG surface subchunk. +====================================================================== */ + +static int add_clip( char *s, lwClip **clist, int *nclips ) +{ + lwClip *clip; + char *p; + + clip = (lwClip*)Mem_ClearedAlloc( sizeof( lwClip ) ); + if ( !clip ) return 0; + + clip->contrast.val = 1.0f; + clip->brightness.val = 1.0f; + clip->saturation.val = 1.0f; + clip->gamma.val = 1.0f; + + if ( p = strstr( s, "(sequence)" )) { + p[ -1 ] = 0; + clip->type = ID_ISEQ; + clip->source.seq.prefix = s; + clip->source.seq.digits = 3; + } + else { + clip->type = ID_STIL; + clip->source.still.name = s; + } + + *nclips++; + clip->index = *nclips; + + lwListAdd( (void**)clist, clip ); + + return clip->index; +} + + +/* +====================================================================== +add_tvel() + +Add a triple of envelopes to simulate the old texture velocity +parameters. +====================================================================== */ + +static int add_tvel( float pos[], float vel[], lwEnvelope **elist, int *nenvs ) +{ + lwEnvelope *env; + lwKey *key0, *key1; + int i; + + for ( i = 0; i < 3; i++ ) { + env = (lwEnvelope*)Mem_ClearedAlloc( sizeof( lwEnvelope ) ); + key0 = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ) ); + key1 = (lwKey*)Mem_ClearedAlloc( sizeof( lwKey ) ); + if ( !env || !key0 || !key1 ) return 0; + + key0->next = key1; + key0->value = pos[ i ]; + key0->time = 0.0f; + key1->prev = key0; + key1->value = pos[ i ] + vel[ i ] * 30.0f; + key1->time = 1.0f; + key0->shape = key1->shape = ID_LINE; + + env->index = *nenvs + i + 1; + env->type = 0x0301 + i; + env->name = (char*)Mem_ClearedAlloc( 11 ); + if ( env->name ) { + strcpy( env->name, "Position.X" ); + env->name[ 9 ] += i; + } + env->key = key0; + env->nkeys = 2; + env->behavior[ 0 ] = BEH_LINEAR; + env->behavior[ 1 ] = BEH_LINEAR; + + lwListAdd( (void**)elist, env ); + } + + *nenvs += 3; + return env->index - 2; +} + + +/* +====================================================================== +get_texture() + +Create a new texture for BTEX, CTEX, etc. subchunks. +====================================================================== */ + +static lwTexture *get_texture( char *s ) +{ + lwTexture *tex; + + tex = (lwTexture*)Mem_ClearedAlloc( sizeof( lwTexture ) ); + if ( !tex ) return NULL; + + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + if ( strstr( s, "Image Map" )) { + tex->type = ID_IMAP; + if ( strstr( s, "Planar" )) tex->param.imap.projection = 0; + else if ( strstr( s, "Cylindrical" )) tex->param.imap.projection = 1; + else if ( strstr( s, "Spherical" )) tex->param.imap.projection = 2; + else if ( strstr( s, "Cubic" )) tex->param.imap.projection = 3; + else if ( strstr( s, "Front" )) tex->param.imap.projection = 4; + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.amplitude.val = 1.0f; + Mem_Free( s ); + } + else { + tex->type = ID_PROC; + tex->param.proc.name = s; + } + + return tex; +} + + +/* +====================================================================== +lwGetSurface5() + +Read an lwSurface from an LWOB file. +====================================================================== */ + +lwSurface *lwGetSurface5( idFile *fp, int cksize, lwObject *obj ) +{ + lwSurface *surf; + lwTexture *tex; + lwPlugin *shdr; + char *s; + float v[ 3 ]; + unsigned int id, flags; + unsigned short sz; + int pos, rlen, i; + + + /* allocate the Surface structure */ + + surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ) ); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* name */ + + surf->name = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 1 ] = getU1( fp ) / 255.0f; + surf->color.rgb[ 2 ] = getU1( fp ) / 255.0f; + break; + + case ID_FLAG: + flags = getU2( fp ); + if ( flags & 4 ) surf->smooth = 1.56207f; + if ( flags & 8 ) surf->color_hilite.val = 1.0f; + if ( flags & 16 ) surf->color_filter.val = 1.0f; + if ( flags & 128 ) surf->dif_sharp.val = 0.5f; + if ( flags & 256 ) surf->sideflags = 3; + if ( flags & 512 ) surf->add_trans.val = 1.0f; + break; + + case ID_LUMI: + surf->luminosity.val = getI2( fp ) / 256.0f; + break; + + case ID_VLUM: + surf->luminosity.val = getF4( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getI2( fp ) / 256.0f; + break; + + case ID_VDIF: + surf->diffuse.val = getF4( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getI2( fp ) / 256.0f; + break; + + case ID_VSPC: + surf->specularity.val = getF4( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = ( float ) logf( ( float) getU2( fp )) / 20.7944f; + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RFLT: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + s = getS0( fp ); + surf->reflection.cindex = add_clip( s, &obj->clip, &obj->nclips ); + surf->reflection.options = 3; + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getI2( fp ) / 256.0f; + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + break; + + case ID_BTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->bump.tex, tex ); + break; + + case ID_CTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->color.tex, tex ); + break; + + case ID_DTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->diffuse.tex, tex ); + break; + + case ID_LTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->luminosity.tex, tex ); + break; + + case ID_RTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->reflection.val.tex, tex ); + break; + + case ID_STEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->specularity.tex, tex ); + break; + + case ID_TTEX: + s = (char*)getbytes( fp, sz ); + tex = get_texture( s ); + lwListAdd( (void**)&surf->transparency.val.tex, tex ); + break; + + case ID_TFLG: + flags = getU2( fp ); + + if ( flags & 1 ) i = 0; + if ( flags & 2 ) i = 1; + if ( flags & 4 ) i = 2; + tex->axis = i; + if ( tex->type == ID_IMAP ) + tex->param.imap.axis = i; + else + tex->param.proc.axis = i; + + if ( flags & 8 ) tex->tmap.coord_sys = 1; + if ( flags & 16 ) tex->negative = 1; + if ( flags & 32 ) tex->param.imap.pblend = 1; + if ( flags & 64 ) { + tex->param.imap.aa_strength = 1.0f; + tex->param.imap.aas_flags = 1; + } + break; + + case ID_TSIZ: + for ( i = 0; i < 3; i++ ) + tex->tmap.size.val[ i ] = getF4( fp ); + break; + + case ID_TCTR: + for ( i = 0; i < 3; i++ ) + tex->tmap.center.val[ i ] = getF4( fp ); + break; + + case ID_TFAL: + for ( i = 0; i < 3; i++ ) + tex->tmap.falloff.val[ i ] = getF4( fp ); + break; + + case ID_TVEL: + for ( i = 0; i < 3; i++ ) + v[ i ] = getF4( fp ); + tex->tmap.center.eindex = add_tvel( tex->tmap.center.val, v, + &obj->env, &obj->nenvs ); + break; + + case ID_TCLR: + if ( tex->type == ID_PROC ) + for ( i = 0; i < 3; i++ ) + tex->param.proc.value[ i ] = getU1( fp ) / 255.0f; + break; + + case ID_TVAL: + tex->param.proc.value[ 0 ] = getI2( fp ) / 256.0f; + break; + + case ID_TAMP: + if ( tex->type == ID_IMAP ) + tex->param.imap.amplitude.val = getF4( fp ); + break; + + case ID_TIMG: + s = getS0( fp ); + tex->param.imap.cindex = add_clip( s, &obj->clip, &obj->nclips ); + break; + + case ID_TAAS: + tex->param.imap.aa_strength = getF4( fp ); + tex->param.imap.aas_flags = 1; + break; + + case ID_TREF: + tex->tmap.ref_object = (char*)getbytes( fp, sz ); + break; + + case ID_TOPC: + tex->opacity.val = getF4( fp ); + break; + + case ID_TFP0: + if ( tex->type == ID_IMAP ) + tex->param.imap.wrapw.val = getF4( fp ); + break; + + case ID_TFP1: + if ( tex->type == ID_IMAP ) + tex->param.imap.wraph.val = getF4( fp ); + break; + + case ID_SHDR: + shdr = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ) ); + if ( !shdr ) goto Fail; + shdr->name = (char*)getbytes( fp, sz ); + lwListAdd( (void**)&surf->shader, shdr ); + surf->nshaders++; + break; + + case ID_SDAT: + shdr->data = getbytes( fp, sz ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} + + +/* +====================================================================== +lwGetPolygons5() + +Read polygon records from a POLS chunk in an LWOB file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons5( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, nv, nverts, npols; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = (unsigned char*)getbytes( fp, cksize ); + if ( !buf ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize ) { + nv = sgetU2( &bp ); + nverts += nv; + npols++; + bp += 2 * nv; + i = sgetI2( &bp ); + if ( i < 0 ) bp += 2; /* detail polygons */ + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + + pp->nverts = nv; + pp->type = ID_FACE; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pv[ j ].index = sgetU2( &bp ) + ptoffset; + j = sgetI2( &bp ); + if ( j < 0 ) { + j = -j; + bp += 2; + } + j -= 1; + pp->surf = ( lwSurface * ) j; + + pp++; + pv += nv; + } + + Mem_Free( buf ); + return 1; + +Fail: + if ( buf ) Mem_Free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +getLWObject5() + +Returns the contents of an LWOB, given its filename, or NULL if the +file couldn't be loaded. On failure, failID and failpos can be used +to diagnose the cause. + +1. If the file isn't an LWOB, failpos will contain 12 and failID will + be unchanged. + +2. If an error occurs while reading an LWOB, failID will contain the + most recently read IFF chunk ID, and failpos will contain the + value returned by fp->Tell() at the time of the failure. + +3. If the file couldn't be opened, or an error occurs while reading + the first 12 bytes, both failID and failpos will be unchanged. + +If you don't need this information, failID and failpos can be NULL. +====================================================================== */ + +lwObject *lwGetObject5( const char *filename, unsigned int *failID, int *failpos ) +{ + idFile *fp = NULL; + lwObject *object; + lwLayer *layer; + lwNode *node; + int id, formsize, type, cksize; + + + /* open the file */ + + //fp = fopen( filename, "rb" ); + //if ( !fp ) return NULL; + + /* read the first 12 bytes */ + fp = fileSystem->OpenFileRead( filename ); + if ( !fp ) { + return NULL; + } + + set_flen( 0 ); + id = getU4( fp ); + formsize = getU4( fp ); + type = getU4( fp ); + if ( 12 != get_flen() ) { + fileSystem->CloseFile( fp ); + return NULL; + } + + /* LWOB? */ + + if ( id != ID_FORM || type != ID_LWOB ) { + fileSystem->CloseFile( fp ); + if ( failpos ) *failpos = 12; + return NULL; + } + + /* allocate an object and a default layer */ + + object = (lwObject*)Mem_ClearedAlloc( sizeof( lwObject ) ); + if ( !object ) goto Fail2; + + layer = (lwLayer*)Mem_ClearedAlloc( sizeof( lwLayer ) ); + if ( !layer ) goto Fail2; + object->layer = layer; + object->nlayers = 1; + + /* get the first chunk header */ + + id = getU4( fp ); + cksize = getU4( fp ); + if ( 0 > get_flen() ) goto Fail2; + + /* process chunks as they're encountered */ + + while ( 1 ) { + cksize += cksize & 1; + + switch ( id ) + { + case ID_PNTS: + if ( !lwGetPoints( fp, cksize, &layer->point )) + goto Fail2; + break; + + case ID_POLS: + if ( !lwGetPolygons5( fp, cksize, &layer->polygon, + layer->point.offset )) + goto Fail2; + break; + + case ID_SRFS: + if ( !lwGetTags( fp, cksize, &object->taglist )) + goto Fail2; + break; + + case ID_SURF: + node = ( lwNode * ) lwGetSurface5( fp, cksize, object ); + if ( !node ) goto Fail2; + lwListAdd( (void**)&object->surf, node ); + object->nsurfs++; + break; + + default: + fp->Seek( cksize, FS_SEEK_CUR ); + break; + } + + /* end of the file? */ + + if ( formsize <= fp->Tell() - 8 ) break; + + /* get the next chunk header */ + + set_flen( 0 ); + id = getU4( fp ); + cksize = getU4( fp ); + if ( 8 != get_flen() ) goto Fail2; + } + + fileSystem->CloseFile( fp ); + fp = NULL; + + lwGetBoundingBox( &layer->point, layer->bbox ); + lwGetPolyNormals( &layer->point, &layer->polygon ); + if ( !lwGetPointPolygons( &layer->point, &layer->polygon )) goto Fail2; + if ( !lwResolvePolySurfaces( &layer->polygon, &object->taglist, + &object->surf, &object->nsurfs )) goto Fail2; + lwGetVertNormals( &layer->point, &layer->polygon ); + + return object; + +Fail2: + if ( failID ) *failID = id; + if ( fp ) { + if ( failpos ) *failpos = fp->Tell(); + fileSystem->CloseFile( fp ); + } + lwFreeObject( object ); + return NULL; +} + +/* +====================================================================== +lwFreePoints() + +Free the memory used by an lwPointList. +====================================================================== */ + +void lwFreePoints( lwPointList *point ) +{ + int i; + + if ( point ) { + if ( point->pt ) { + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].pol ) Mem_Free( point->pt[ i ].pol ); + if ( point->pt[ i ].vm ) Mem_Free( point->pt[ i ].vm ); + } + Mem_Free( point->pt ); + } + memset( point, 0, sizeof( lwPointList )); + } +} + + +/* +====================================================================== +lwFreePolygons() + +Free the memory used by an lwPolygonList. +====================================================================== */ + +void lwFreePolygons( lwPolygonList *plist ) +{ + int i, j; + + if ( plist ) { + if ( plist->pol ) { + for ( i = 0; i < plist->count; i++ ) { + if ( plist->pol[ i ].v ) { + for ( j = 0; j < plist->pol[ i ].nverts; j++ ) + if ( plist->pol[ i ].v[ j ].vm ) + Mem_Free( plist->pol[ i ].v[ j ].vm ); + } + } + if ( plist->pol[ 0 ].v ) + Mem_Free( plist->pol[ 0 ].v ); + Mem_Free( plist->pol ); + } + memset( plist, 0, sizeof( lwPolygonList )); + } +} + + +/* +====================================================================== +lwGetPoints() + +Read point records from a PNTS chunk in an LWO2 file. The points are +added to the array in the lwPointList. +====================================================================== */ + +int lwGetPoints( idFile *fp, int cksize, lwPointList *point ) +{ + float *f; + int np, i, j; + + if ( cksize == 1 ) return 1; + + /* extend the point array to hold the new points */ + + np = cksize / 12; + point->offset = point->count; + point->count += np; + lwPoint *oldpt = point->pt; + point->pt = (lwPoint*)Mem_Alloc( point->count * sizeof( lwPoint ) ); + if ( !point->pt ) return 0; + if ( oldpt ) { + memcpy( point->pt, oldpt, point->offset * sizeof( lwPoint ) ); + Mem_Free( oldpt ); + } + memset( &point->pt[ point->offset ], 0, np * sizeof( lwPoint ) ); + + /* read the whole chunk */ + + f = ( float * ) getbytes( fp, cksize ); + if ( !f ) return 0; + BigRevBytes( f, 4, np * 3 ); + + /* assign position values */ + + for ( i = 0, j = 0; i < np; i++, j += 3 ) { + point->pt[ i ].pos[ 0 ] = f[ j ]; + point->pt[ i ].pos[ 1 ] = f[ j + 1 ]; + point->pt[ i ].pos[ 2 ] = f[ j + 2 ]; + } + + Mem_Free( f ); + return 1; +} + + +/* +====================================================================== +lwGetBoundingBox() + +Calculate the bounding box for a point list, but only if the bounding +box hasn't already been initialized. +====================================================================== */ + +void lwGetBoundingBox( lwPointList *point, float bbox[] ) +{ + int i, j; + + if ( point->count == 0 ) return; + + for ( i = 0; i < 6; i++ ) + if ( bbox[ i ] != 0.0f ) return; + + bbox[ 0 ] = bbox[ 1 ] = bbox[ 2 ] = 1e20f; + bbox[ 3 ] = bbox[ 4 ] = bbox[ 5 ] = -1e20f; + for ( i = 0; i < point->count; i++ ) { + for ( j = 0; j < 3; j++ ) { + if ( bbox[ j ] > point->pt[ i ].pos[ j ] ) + bbox[ j ] = point->pt[ i ].pos[ j ]; + if ( bbox[ j + 3 ] < point->pt[ i ].pos[ j ] ) + bbox[ j + 3 ] = point->pt[ i ].pos[ j ]; + } + } +} + + +/* +====================================================================== +lwAllocPolygons() + +Allocate or extend the polygon arrays to hold new records. +====================================================================== */ + +int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ) +{ + int i; + + plist->offset = plist->count; + plist->count += npols; + lwPolygon *oldpol = plist->pol; + plist->pol = (lwPolygon*)Mem_Alloc( plist->count * sizeof( lwPolygon ) ); + if ( !plist->pol ) return 0; + if ( oldpol ) { + memcpy( plist->pol, oldpol, plist->offset * sizeof( lwPolygon ) ); + Mem_Free( oldpol ); + } + memset( plist->pol + plist->offset, 0, npols * sizeof( lwPolygon ) ); + + plist->voffset = plist->vcount; + plist->vcount += nverts; + lwPolVert *oldpolv = plist->pol[0].v; + plist->pol[0].v = (lwPolVert*)Mem_Alloc( plist->vcount * sizeof( lwPolVert ) ); + if ( !plist->pol[ 0 ].v ) return 0; + if ( oldpolv ) { + memcpy( plist->pol[0].v, oldpolv, plist->voffset * sizeof( lwPolVert ) ); + Mem_Free( oldpolv ); + } + memset( plist->pol[ 0 ].v + plist->voffset, 0, nverts * sizeof( lwPolVert ) ); + + /* fix up the old vertex pointers */ + + for ( i = 1; i < plist->offset; i++ ) + plist->pol[ i ].v = plist->pol[ i - 1 ].v + plist->pol[ i - 1 ].nverts; + + return 1; +} + + +/* +====================================================================== +lwGetPolygons() + +Read polygon records from a POLS chunk in an LWO2 file. The polygons +are added to the array in the lwPolygonList. +====================================================================== */ + +int lwGetPolygons( idFile *fp, int cksize, lwPolygonList *plist, int ptoffset ) +{ + lwPolygon *pp; + lwPolVert *pv; + unsigned char *buf, *bp; + int i, j, flags, nv, nverts, npols; + unsigned int type; + + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + type = getU4( fp ); + buf = (unsigned char*)getbytes( fp, cksize - 4 ); + if ( cksize != get_flen() ) goto Fail; + + /* count the polygons and vertices */ + + nverts = 0; + npols = 0; + bp = buf; + + while ( bp < buf + cksize - 4 ) { + nv = sgetU2( &bp ); + nv &= 0x03FF; + nverts += nv; + npols++; + for ( i = 0; i < nv; i++ ) + j = sgetVX( &bp ); + } + + if ( !lwAllocPolygons( plist, npols, nverts )) + goto Fail; + + /* fill in the new polygons */ + + bp = buf; + pp = plist->pol + plist->offset; + pv = plist->pol[ 0 ].v + plist->voffset; + + for ( i = 0; i < npols; i++ ) { + nv = sgetU2( &bp ); + flags = nv & 0xFC00; + nv &= 0x03FF; + + pp->nverts = nv; + pp->flags = flags; + pp->type = type; + if ( !pp->v ) pp->v = pv; + for ( j = 0; j < nv; j++ ) + pp->v[ j ].index = sgetVX( &bp ) + ptoffset; + + pp++; + pv += nv; + } + + Mem_Free( buf ); + return 1; + +Fail: + if ( buf ) Mem_Free( buf ); + lwFreePolygons( plist ); + return 0; +} + + +/* +====================================================================== +lwGetPolyNormals() + +Calculate the polygon normals. By convention, LW's polygon normals +are found as the cross product of the first and last edges. It's +undefined for one- and two-point polygons. +====================================================================== */ + +void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j; + float p1[ 3 ], p2[ 3 ], pn[ 3 ], v1[ 3 ], v2[ 3 ]; + + for ( i = 0; i < polygon->count; i++ ) { + if ( polygon->pol[ i ].nverts < 3 ) continue; + for ( j = 0; j < 3; j++ ) { + + // FIXME: track down why indexes are way out of range + p1[ j ] = point->pt[ polygon->pol[ i ].v[ 0 ].index ].pos[ j ]; + p2[ j ] = point->pt[ polygon->pol[ i ].v[ 1 ].index ].pos[ j ]; + pn[ j ] = point->pt[ polygon->pol[ i ].v[ polygon->pol[ i ].nverts - 1 ].index ].pos[ j ]; + } + + for ( j = 0; j < 3; j++ ) { + v1[ j ] = p2[ j ] - p1[ j ]; + v2[ j ] = pn[ j ] - p1[ j ]; + } + + cross( v1, v2, polygon->pol[ i ].norm ); + normalize( polygon->pol[ i ].norm ); + } +} + + +/* +====================================================================== +lwGetPointPolygons() + +For each point, fill in the indexes of the polygons that share the +point. Returns 0 if any of the memory allocations fail, otherwise +returns 1. +====================================================================== */ + +int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ) +{ + int i, j, k; + + /* count the number of polygons per point */ + + for ( i = 0; i < polygon->count; i++ ) + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) + ++point->pt[ polygon->pol[ i ].v[ j ].index ].npols; + + /* alloc per-point polygon arrays */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].npols == 0 ) continue; + point->pt[ i ].pol = (int*)Mem_ClearedAlloc( point->pt[ i ].npols * sizeof( int ) ); + if ( !point->pt[ i ].pol ) return 0; + point->pt[ i ].npols = 0; + } + + /* fill in polygon array for each point */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + k = polygon->pol[ i ].v[ j ].index; + point->pt[ k ].pol[ point->pt[ k ].npols ] = i; + ++point->pt[ k ].npols; + } + } + + return 1; +} + + +/* +====================================================================== +lwResolvePolySurfaces() + +Convert tag indexes into actual lwSurface pointers. If any polygons +point to tags for which no corresponding surface can be found, a +default surface is created. +====================================================================== */ + +int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist, + lwSurface **surf, int *nsurfs ) +{ + lwSurface **s, *st; + int i, index; + + if ( tlist->count == 0 ) return 1; + + s = (lwSurface**)Mem_ClearedAlloc( tlist->count * sizeof( lwSurface * ) ); + if ( !s ) return 0; + + for ( i = 0; i < tlist->count; i++ ) { + st = *surf; + while ( st ) { + if ( !strcmp( st->name, tlist->tag[ i ] )) { + s[ i ] = st; + break; + } + st = st->next; + } + } + + for ( i = 0; i < polygon->count; i++ ) { + index = ( int ) polygon->pol[ i ].surf; + if ( index < 0 || index > tlist->count ) return 0; + if ( !s[ index ] ) { + s[ index ] = lwDefaultSurface(); + if ( !s[ index ] ) return 0; + s[ index ]->name = (char*)Mem_ClearedAlloc( strlen( tlist->tag[ index ] ) + 1 ); + if ( !s[ index ]->name ) return 0; + strcpy( s[ index ]->name, tlist->tag[ index ] ); + lwListAdd( (void**)surf, s[ index ] ); + *nsurfs = *nsurfs + 1; + } + polygon->pol[ i ].surf = s[ index ]; + } + + Mem_Free( s ); + return 1; +} + + +/* +====================================================================== +lwGetVertNormals() + +Calculate the vertex normals. For each polygon vertex, sum the +normals of the polygons that share the point. If the normals of the +current and adjacent polygons form an angle greater than the max +smoothing angle for the current polygon's surface, the normal of the +adjacent polygon is excluded from the sum. It's also excluded if the +polygons aren't in the same smoothing group. + +Assumes that lwGetPointPolygons(), lwGetPolyNormals() and +lwResolvePolySurfaces() have already been called. +====================================================================== */ + +void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ) +{ + int j, k, n, g, h, p; + float a; + + for ( j = 0; j < polygon->count; j++ ) { + for ( n = 0; n < polygon->pol[ j ].nverts; n++ ) { + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] = polygon->pol[ j ].norm[ k ]; + + if ( polygon->pol[ j ].surf->smooth <= 0 ) continue; + + p = polygon->pol[ j ].v[ n ].index; + + for ( g = 0; g < point->pt[ p ].npols; g++ ) { + h = point->pt[ p ].pol[ g ]; + if ( h == j ) continue; + + if ( polygon->pol[ j ].smoothgrp != polygon->pol[ h ].smoothgrp ) + continue; + a = vecangle( polygon->pol[ j ].norm, polygon->pol[ h ].norm ); + if ( a > polygon->pol[ j ].surf->smooth ) continue; + + for ( k = 0; k < 3; k++ ) + polygon->pol[ j ].v[ n ].norm[ k ] += polygon->pol[ h ].norm[ k ]; + } + + normalize( polygon->pol[ j ].v[ n ].norm ); + } + } +} + + +/* +====================================================================== +lwFreeTags() + +Free memory used by an lwTagList. +====================================================================== */ + +void lwFreeTags( lwTagList *tlist ) +{ + int i; + + if ( tlist ) { + if ( tlist->tag ) { + for ( i = 0; i < tlist->count; i++ ) + if ( tlist->tag[ i ] ) { + Mem_Free( tlist->tag[ i ] ); + } + Mem_Free( tlist->tag ); + } + memset( tlist, 0, sizeof( lwTagList )); + } +} + + +/* +====================================================================== +lwGetTags() + +Read tag strings from a TAGS chunk in an LWO2 file. The tags are +added to the lwTagList array. +====================================================================== */ + +int lwGetTags( idFile *fp, int cksize, lwTagList *tlist ) +{ + char *buf, *bp; + int i, len, ntags; + + if ( cksize == 0 ) return 1; + + /* read the whole chunk */ + + set_flen( 0 ); + buf = (char*)getbytes( fp, cksize ); + if ( !buf ) return 0; + + /* count the strings */ + + ntags = 0; + bp = buf; + while ( bp < buf + cksize ) { + len = strlen( bp ) + 1; + len += len & 1; + bp += len; + ++ntags; + } + + /* expand the string array to hold the new tags */ + + tlist->offset = tlist->count; + tlist->count += ntags; + char **oldtag = tlist->tag; + tlist->tag = (char**)Mem_Alloc( tlist->count * sizeof( char * ) ); + if ( !tlist->tag ) goto Fail; + if ( oldtag ) { + memcpy( tlist->tag, oldtag, tlist->offset * sizeof( char * ) ); + Mem_Free( oldtag ); + } + memset( &tlist->tag[ tlist->offset ], 0, ntags * sizeof( char * ) ); + + /* copy the new tags to the tag array */ + + bp = buf; + for ( i = 0; i < ntags; i++ ) + tlist->tag[ i + tlist->offset ] = sgetS0( (unsigned char**)&bp ); + + Mem_Free( buf ); + return 1; + +Fail: + if ( buf ) Mem_Free( buf ); + return 0; +} + + +/* +====================================================================== +lwGetPolygonTags() + +Read polygon tags from a PTAG chunk in an LWO2 file. +====================================================================== */ + +int lwGetPolygonTags( idFile *fp, int cksize, lwTagList *tlist, lwPolygonList *plist ) +{ + unsigned int type; + int rlen = 0, i, j; + + set_flen( 0 ); + type = getU4( fp ); + rlen = get_flen(); + if ( rlen < 0 ) return 0; + + if ( type != ID_SURF && type != ID_PART && type != ID_SMGP ) { + fp->Seek( cksize - 4, FS_SEEK_CUR ); + return 1; + } + + while ( rlen < cksize ) { + i = getVX( fp ) + plist->offset; + j = getVX( fp ) + tlist->offset; + rlen = get_flen(); + if ( rlen < 0 || rlen > cksize ) return 0; + + switch ( type ) { + case ID_SURF: plist->pol[ i ].surf = ( lwSurface * ) j; break; + case ID_PART: plist->pol[ i ].part = j; break; + case ID_SMGP: plist->pol[ i ].smoothgrp = j; break; + } + } + + return 1; +} + + +/* +====================================================================== +lwFreePlugin() + +Free the memory used by an lwPlugin. +====================================================================== */ + +void lwFreePlugin( lwPlugin *p ) +{ + if ( p ) { + if ( p->ord ) Mem_Free( p->ord ); + if ( p->name ) Mem_Free( p->name ); + if ( p->data ) Mem_Free( p->data ); + Mem_Free( p ); + } +} + + +/* +====================================================================== +lwFreeTexture() + +Free the memory used by an lwTexture. +====================================================================== */ + +void lwFreeTexture( lwTexture *t ) +{ + if ( t ) { + if ( t->ord ) Mem_Free( t->ord ); + switch ( t->type ) { + case ID_IMAP: + if ( t->param.imap.vmap_name ) Mem_Free( t->param.imap.vmap_name ); + break; + case ID_PROC: + if ( t->param.proc.name ) Mem_Free( t->param.proc.name ); + if ( t->param.proc.data ) Mem_Free( t->param.proc.data ); + break; + case ID_GRAD: + if ( t->param.grad.key ) Mem_Free( t->param.grad.key ); + if ( t->param.grad.ikey ) Mem_Free( t->param.grad.ikey ); + break; + } + if ( t->tmap.ref_object ) Mem_Free( t->tmap.ref_object ); + Mem_Free( t ); + } +} + + +/* +====================================================================== +lwFreeSurface() + +Free the memory used by an lwSurface. +====================================================================== */ + +void lwFreeSurface( lwSurface *surf ) +{ + if ( surf ) { + if ( surf->name ) Mem_Free( surf->name ); + if ( surf->srcname ) Mem_Free( surf->srcname ); + + lwListFree( surf->shader, (void (__cdecl *)(void *))lwFreePlugin ); + + lwListFree( surf->color.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->luminosity.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->diffuse.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->specularity.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->glossiness.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->reflection.val.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->transparency.val.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->eta.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->translucency.tex, (void (__cdecl *)(void *))lwFreeTexture ); + lwListFree( surf->bump.tex, (void (__cdecl *)(void *))lwFreeTexture ); + + Mem_Free( surf ); + } +} + + +/* +====================================================================== +lwGetTHeader() + +Read a texture map header from a SURF.BLOK in an LWO2 file. This is +the first subchunk in a BLOK, and its contents are common to all three +texture types. +====================================================================== */ + +int lwGetTHeader( idFile *fp, int hsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int pos, rlen; + + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* ordinal string */ + + tex->ord = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_CHAN: + tex->chan = getU4( fp ); + break; + + case ID_OPAC: + tex->opac_type = getU2( fp ); + tex->opacity.val = getF4( fp ); + tex->opacity.eindex = getVX( fp ); + break; + + case ID_ENAB: + tex->enabled = getU2( fp ); + break; + + case ID_NEGA: + tex->negative = getU2( fp ); + break; + + case ID_AXIS: + tex->axis = getU2( fp ); + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the texture header subchunk? */ + + if ( hsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTMap() + +Read a texture map from a SURF.BLOK in an LWO2 file. The TMAP +defines the mapping from texture to world or object coordinates. +====================================================================== */ + +int lwGetTMap( idFile *fp, int tmapsz, lwTMap *tmap ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_SIZE: + for ( i = 0; i < 3; i++ ) + tmap->size.val[ i ] = getF4( fp ); + tmap->size.eindex = getVX( fp ); + break; + + case ID_CNTR: + for ( i = 0; i < 3; i++ ) + tmap->center.val[ i ] = getF4( fp ); + tmap->center.eindex = getVX( fp ); + break; + + case ID_ROTA: + for ( i = 0; i < 3; i++ ) + tmap->rotate.val[ i ] = getF4( fp ); + tmap->rotate.eindex = getVX( fp ); + break; + + case ID_FALL: + tmap->fall_type = getU2( fp ); + for ( i = 0; i < 3; i++ ) + tmap->falloff.val[ i ] = getF4( fp ); + tmap->falloff.eindex = getVX( fp ); + break; + + case ID_OREF: + tmap->ref_object = getS0( fp ); + break; + + case ID_CSYS: + tmap->coord_sys = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the TMAP subchunk? */ + + if ( tmapsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetImageMap() + +Read an lwImageMap from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetImageMap( idFile *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PROJ: + tex->param.imap.projection = getU2( fp ); + break; + + case ID_VMAP: + tex->param.imap.vmap_name = getS0( fp ); + break; + + case ID_AXIS: + tex->param.imap.axis = getU2( fp ); + break; + + case ID_IMAG: + tex->param.imap.cindex = getVX( fp ); + break; + + case ID_WRAP: + tex->param.imap.wrapw_type = getU2( fp ); + tex->param.imap.wraph_type = getU2( fp ); + break; + + case ID_WRPW: + tex->param.imap.wrapw.val = getF4( fp ); + tex->param.imap.wrapw.eindex = getVX( fp ); + break; + + case ID_WRPH: + tex->param.imap.wraph.val = getF4( fp ); + tex->param.imap.wraph.eindex = getVX( fp ); + break; + + case ID_AAST: + tex->param.imap.aas_flags = getU2( fp ); + tex->param.imap.aa_strength = getF4( fp ); + break; + + case ID_PIXB: + tex->param.imap.pblend = getU2( fp ); + break; + + case ID_STCK: + tex->param.imap.stck.val = getF4( fp ); + tex->param.imap.stck.eindex = getVX( fp ); + break; + + case ID_TAMP: + tex->param.imap.amplitude.val = getF4( fp ); + tex->param.imap.amplitude.eindex = getVX( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the image map? */ + + if ( rsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetProcedural() + +Read an lwProcedural from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetProcedural( idFile *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_AXIS: + tex->param.proc.axis = getU2( fp ); + break; + + case ID_VALU: + tex->param.proc.value[ 0 ] = getF4( fp ); + if ( sz >= 8 ) tex->param.proc.value[ 1 ] = getF4( fp ); + if ( sz >= 12 ) tex->param.proc.value[ 2 ] = getF4( fp ); + break; + + case ID_FUNC: + tex->param.proc.name = getS0( fp ); + rlen = get_flen(); + tex->param.proc.data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the procedural block? */ + + if ( rsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetGradient() + +Read an lwGradient from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +int lwGetGradient( idFile *fp, int rsz, lwTexture *tex ) +{ + unsigned int id; + unsigned short sz; + int rlen, pos, i, j, nkeys; + + pos = fp->Tell(); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) return 0; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_TMAP: + if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0; + break; + + case ID_PNAM: + tex->param.grad.paramname = getS0( fp ); + break; + + case ID_INAM: + tex->param.grad.itemname = getS0( fp ); + break; + + case ID_GRST: + tex->param.grad.start = getF4( fp ); + break; + + case ID_GREN: + tex->param.grad.end = getF4( fp ); + break; + + case ID_GRPT: + tex->param.grad.repeat = getU2( fp ); + break; + + case ID_FKEY: + nkeys = sz / sizeof( lwGradKey ); + tex->param.grad.key = (lwGradKey*)Mem_ClearedAlloc( nkeys * sizeof( lwGradKey ) ); + if ( !tex->param.grad.key ) return 0; + for ( i = 0; i < nkeys; i++ ) { + tex->param.grad.key[ i ].value = getF4( fp ); + for ( j = 0; j < 4; j++ ) + tex->param.grad.key[ i ].rgba[ j ] = getF4( fp ); + } + break; + + case ID_IKEY: + nkeys = sz / 2; + tex->param.grad.ikey = (short*)Mem_ClearedAlloc( nkeys * sizeof( short ) ); + if ( !tex->param.grad.ikey ) return 0; + for ( i = 0; i < nkeys; i++ ) + tex->param.grad.ikey[ i ] = getU2( fp ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) return 0; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the gradient? */ + + if ( rsz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) return 0; + } + + set_flen( fp->Tell() - pos ); + return 1; +} + + +/* +====================================================================== +lwGetTexture() + +Read an lwTexture from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwTexture *lwGetTexture( idFile *fp, int bloksz, unsigned int type ) +{ + lwTexture *tex; + unsigned short sz; + int ok; + + tex = (lwTexture*)Mem_ClearedAlloc( sizeof( lwTexture ) ); + if ( !tex ) return NULL; + + tex->type = type; + tex->tmap.size.val[ 0 ] = + tex->tmap.size.val[ 1 ] = + tex->tmap.size.val[ 2 ] = 1.0f; + tex->opacity.val = 1.0f; + tex->enabled = 1; + + sz = getU2( fp ); + if ( !lwGetTHeader( fp, sz, tex )) { + Mem_Free( tex ); + return NULL; + } + + sz = bloksz - sz - 6; + switch ( type ) { + case ID_IMAP: ok = lwGetImageMap( fp, sz, tex ); break; + case ID_PROC: ok = lwGetProcedural( fp, sz, tex ); break; + case ID_GRAD: ok = lwGetGradient( fp, sz, tex ); break; + default: + ok = !fp->Seek( sz, FS_SEEK_CUR ); + } + + if ( !ok ) { + lwFreeTexture( tex ); + return NULL; + } + + set_flen( bloksz ); + return tex; +} + + +/* +====================================================================== +lwGetShader() + +Read a shader record from a SURF.BLOK in an LWO2 file. +====================================================================== */ + +lwPlugin *lwGetShader( idFile *fp, int bloksz ) +{ + lwPlugin *shdr; + unsigned int id; + unsigned short sz; + int hsz, rlen, pos; + + shdr = (lwPlugin*)Mem_ClearedAlloc( sizeof( lwPlugin ) ); + if ( !shdr ) return NULL; + + pos = fp->Tell(); + set_flen( 0 ); + hsz = getU2( fp ); + shdr->ord = getS0( fp ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( hsz > 0 ) { + sz += sz & 1; + hsz -= sz; + if ( id == ID_ENAB ) { + shdr->flags = getU2( fp ); + break; + } + else { + fp->Seek( sz, FS_SEEK_CUR ); + id = getU4( fp ); + sz = getU2( fp ); + } + } + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_FUNC: + shdr->name = getS0( fp ); + rlen = get_flen(); + shdr->data = getbytes( fp, sz - rlen ); + break; + + default: + break; + } + + /* error while reading the current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the shader block? */ + + if ( bloksz <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + set_flen( fp->Tell() - pos ); + return shdr; + +Fail: + lwFreePlugin( shdr ); + return NULL; +} + + +/* +====================================================================== +compare_textures() +compare_shaders() + +Callbacks for the lwListInsert() function, which is called to add +textures to surface channels and shaders to surfaces. +====================================================================== */ + +static int compare_textures( lwTexture *a, lwTexture *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +static int compare_shaders( lwPlugin *a, lwPlugin *b ) +{ + return strcmp( a->ord, b->ord ); +} + + +/* +====================================================================== +add_texture() + +Finds the surface channel (lwTParam or lwCParam) to which a texture is +applied, then calls lwListInsert(). +====================================================================== */ + +static int add_texture( lwSurface *surf, lwTexture *tex ) +{ + lwTexture **list; + + switch ( tex->chan ) { + case ID_COLR: list = &surf->color.tex; break; + case ID_LUMI: list = &surf->luminosity.tex; break; + case ID_DIFF: list = &surf->diffuse.tex; break; + case ID_SPEC: list = &surf->specularity.tex; break; + case ID_GLOS: list = &surf->glossiness.tex; break; + case ID_REFL: list = &surf->reflection.val.tex; break; + case ID_TRAN: list = &surf->transparency.val.tex; break; + case ID_RIND: list = &surf->eta.tex; break; + case ID_TRNL: list = &surf->translucency.tex; break; + case ID_BUMP: list = &surf->bump.tex; break; + default: return 0; + } + + lwListInsert( (void**)list, tex, (int (__cdecl *)(void *,void *))compare_textures ); + return 1; +} + + +/* +====================================================================== +lwDefaultSurface() + +Allocate and initialize a surface. +====================================================================== */ + +lwSurface *lwDefaultSurface( void ) +{ + lwSurface *surf; + + surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ) ); + if ( !surf ) return NULL; + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + return surf; +} + + +/* +====================================================================== +lwGetSurface() + +Read an lwSurface from an LWO2 file. +====================================================================== */ + +lwSurface *lwGetSurface( idFile *fp, int cksize ) +{ + lwSurface *surf; + lwTexture *tex; + lwPlugin *shdr; + unsigned int id, type; + unsigned short sz; + int pos, rlen; + + + /* allocate the Surface structure */ + + surf = (lwSurface*)Mem_ClearedAlloc( sizeof( lwSurface ) ); + if ( !surf ) goto Fail; + + /* non-zero defaults */ + + surf->color.rgb[ 0 ] = 0.78431f; + surf->color.rgb[ 1 ] = 0.78431f; + surf->color.rgb[ 2 ] = 0.78431f; + surf->diffuse.val = 1.0f; + surf->glossiness.val = 0.4f; + surf->bump.val = 1.0f; + surf->eta.val = 1.0f; + surf->sideflags = 1; + + /* remember where we started */ + + set_flen( 0 ); + pos = fp->Tell(); + + /* names */ + + surf->name = getS0( fp ); + surf->srcname = getS0( fp ); + + /* first subchunk header */ + + id = getU4( fp ); + sz = getU2( fp ); + if ( 0 > get_flen() ) goto Fail; + + /* process subchunks as they're encountered */ + + while ( 1 ) { + sz += sz & 1; + set_flen( 0 ); + + switch ( id ) { + case ID_COLR: + surf->color.rgb[ 0 ] = getF4( fp ); + surf->color.rgb[ 1 ] = getF4( fp ); + surf->color.rgb[ 2 ] = getF4( fp ); + surf->color.eindex = getVX( fp ); + break; + + case ID_LUMI: + surf->luminosity.val = getF4( fp ); + surf->luminosity.eindex = getVX( fp ); + break; + + case ID_DIFF: + surf->diffuse.val = getF4( fp ); + surf->diffuse.eindex = getVX( fp ); + break; + + case ID_SPEC: + surf->specularity.val = getF4( fp ); + surf->specularity.eindex = getVX( fp ); + break; + + case ID_GLOS: + surf->glossiness.val = getF4( fp ); + surf->glossiness.eindex = getVX( fp ); + break; + + case ID_REFL: + surf->reflection.val.val = getF4( fp ); + surf->reflection.val.eindex = getVX( fp ); + break; + + case ID_RFOP: + surf->reflection.options = getU2( fp ); + break; + + case ID_RIMG: + surf->reflection.cindex = getVX( fp ); + break; + + case ID_RSAN: + surf->reflection.seam_angle = getF4( fp ); + break; + + case ID_TRAN: + surf->transparency.val.val = getF4( fp ); + surf->transparency.val.eindex = getVX( fp ); + break; + + case ID_TROP: + surf->transparency.options = getU2( fp ); + break; + + case ID_TIMG: + surf->transparency.cindex = getVX( fp ); + break; + + case ID_RIND: + surf->eta.val = getF4( fp ); + surf->eta.eindex = getVX( fp ); + break; + + case ID_TRNL: + surf->translucency.val = getF4( fp ); + surf->translucency.eindex = getVX( fp ); + break; + + case ID_BUMP: + surf->bump.val = getF4( fp ); + surf->bump.eindex = getVX( fp ); + break; + + case ID_SMAN: + surf->smooth = getF4( fp ); + break; + + case ID_SIDE: + surf->sideflags = getU2( fp ); + break; + + case ID_CLRH: + surf->color_hilite.val = getF4( fp ); + surf->color_hilite.eindex = getVX( fp ); + break; + + case ID_CLRF: + surf->color_filter.val = getF4( fp ); + surf->color_filter.eindex = getVX( fp ); + break; + + case ID_ADTR: + surf->add_trans.val = getF4( fp ); + surf->add_trans.eindex = getVX( fp ); + break; + + case ID_SHRP: + surf->dif_sharp.val = getF4( fp ); + surf->dif_sharp.eindex = getVX( fp ); + break; + + case ID_GVAL: + surf->glow.val = getF4( fp ); + surf->glow.eindex = getVX( fp ); + break; + + case ID_LINE: + surf->line.enabled = 1; + if ( sz >= 2 ) surf->line.flags = getU2( fp ); + if ( sz >= 6 ) surf->line.size.val = getF4( fp ); + if ( sz >= 8 ) surf->line.size.eindex = getVX( fp ); + break; + + case ID_ALPH: + surf->alpha_mode = getU2( fp ); + surf->alpha = getF4( fp ); + break; + + case ID_AVAL: + surf->alpha = getF4( fp ); + break; + + case ID_BLOK: + type = getU4( fp ); + + switch ( type ) { + case ID_IMAP: + case ID_PROC: + case ID_GRAD: + tex = lwGetTexture( fp, sz - 4, type ); + if ( !tex ) goto Fail; + if ( !add_texture( surf, tex )) + lwFreeTexture( tex ); + set_flen( 4 + get_flen() ); + break; + case ID_SHDR: + shdr = lwGetShader( fp, sz - 4 ); + if ( !shdr ) goto Fail; + lwListInsert( (void**)&surf->shader, shdr, (int (__cdecl *)(void *,void *))compare_shaders ); + ++surf->nshaders; + set_flen( 4 + get_flen() ); + break; + } + break; + + default: + break; + } + + /* error while reading current subchunk? */ + + rlen = get_flen(); + if ( rlen < 0 || rlen > sz ) goto Fail; + + /* skip unread parts of the current subchunk */ + + if ( rlen < sz ) + fp->Seek( sz - rlen, FS_SEEK_CUR ); + + /* end of the SURF chunk? */ + + if ( cksize <= fp->Tell() - pos ) + break; + + /* get the next subchunk header */ + + set_flen( 0 ); + id = getU4( fp ); + sz = getU2( fp ); + if ( 6 != get_flen() ) goto Fail; + } + + return surf; + +Fail: + if ( surf ) lwFreeSurface( surf ); + return NULL; +} + + +float dot( float a[], float b[] ) +{ + return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; +} + + +void cross( float a[], float b[], float c[] ) +{ + c[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ]; + c[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ]; + c[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ]; +} + + +void normalize( float v[] ) +{ + float r; + + r = ( float ) idMath::Sqrt( dot( v, v )); + if ( r > 0 ) { + v[ 0 ] /= r; + v[ 1 ] /= r; + v[ 2 ] /= r; + } +} + +/* +====================================================================== +lwFreeVMap() + +Free memory used by an lwVMap. +====================================================================== */ + +void lwFreeVMap( lwVMap *vmap ) +{ + if ( vmap ) { + if ( vmap->name ) Mem_Free( vmap->name ); + if ( vmap->vindex ) Mem_Free( vmap->vindex ); + if ( vmap->pindex ) Mem_Free( vmap->pindex ); + if ( vmap->val ) { + if ( vmap->val[ 0 ] ) Mem_Free( vmap->val[ 0 ] ); + Mem_Free( vmap->val ); + } + Mem_Free( vmap ); + } +} + + +/* +====================================================================== +lwGetVMap() + +Read an lwVMap from a VMAP or VMAD chunk in an LWO2. +====================================================================== */ + +lwVMap *lwGetVMap( idFile *fp, int cksize, int ptoffset, int poloffset, + int perpoly ) +{ + unsigned char *buf, *bp; + lwVMap *vmap; + float *f; + int i, j, npts, rlen; + + + /* read the whole chunk */ + + set_flen( 0 ); + buf = (unsigned char*)getbytes( fp, cksize ); + if ( !buf ) return NULL; + + vmap = (lwVMap*)Mem_ClearedAlloc( sizeof( lwVMap ) ); + if ( !vmap ) { + Mem_Free( buf ); + return NULL; + } + + /* initialize the vmap */ + + vmap->perpoly = perpoly; + + bp = buf; + set_flen( 0 ); + vmap->type = sgetU4( &bp ); + vmap->dim = sgetU2( &bp ); + vmap->name = sgetS0( &bp ); + rlen = get_flen(); + + /* count the vmap records */ + + npts = 0; + while ( bp < buf + cksize ) { + i = sgetVX( &bp ); + if ( perpoly ) + i = sgetVX( &bp ); + bp += vmap->dim * sizeof( float ); + ++npts; + } + + /* allocate the vmap */ + + vmap->nverts = npts; + vmap->vindex = (int*)Mem_ClearedAlloc( npts * sizeof( int ) ); + if ( !vmap->vindex ) goto Fail; + if ( perpoly ) { + vmap->pindex = (int*)Mem_ClearedAlloc( npts * sizeof( int ) ); + if ( !vmap->pindex ) goto Fail; + } + + if ( vmap->dim > 0 ) { + vmap->val = (float**)Mem_ClearedAlloc( npts * sizeof( float * ) ); + if ( !vmap->val ) goto Fail; + f = (float*)Mem_ClearedAlloc( npts * vmap->dim * sizeof( float ) ); + if ( !f ) goto Fail; + for ( i = 0; i < npts; i++ ) + vmap->val[ i ] = f + i * vmap->dim; + } + + /* fill in the vmap values */ + + bp = buf + rlen; + for ( i = 0; i < npts; i++ ) { + vmap->vindex[ i ] = sgetVX( &bp ); + if ( perpoly ) + vmap->pindex[ i ] = sgetVX( &bp ); + for ( j = 0; j < vmap->dim; j++ ) + vmap->val[ i ][ j ] = sgetF4( &bp ); + } + + Mem_Free( buf ); + return vmap; + +Fail: + if ( buf ) Mem_Free( buf ); + lwFreeVMap( vmap ); + return NULL; +} + + +/* +====================================================================== +lwGetPointVMaps() + +Fill in the lwVMapPt structure for each point. +====================================================================== */ + +int lwGetPointVMaps( lwPointList *point, lwVMap *vmap ) +{ + lwVMap *vm; + int i, j, n; + + /* count the number of vmap values for each point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) + for ( i = 0; i < vm->nverts; i++ ) + ++point->pt[ vm->vindex[ i ]].nvmaps; + vm = vm->next; + } + + /* allocate vmap references for each mapped point */ + + for ( i = 0; i < point->count; i++ ) { + if ( point->pt[ i ].nvmaps ) { + point->pt[ i ].vm = (lwVMapPt*)Mem_ClearedAlloc( point->pt[ i ].nvmaps * sizeof( lwVMapPt ) ); + if ( !point->pt[ i ].vm ) return 0; + point->pt[ i ].nvmaps = 0; + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( !vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + j = vm->vindex[ i ]; + n = point->pt[ j ].nvmaps; + point->pt[ j ].vm[ n ].vmap = vm; + point->pt[ j ].vm[ n ].index = i; + ++point->pt[ j ].nvmaps; + } + } + vm = vm->next; + } + + return 1; +} + + +/* +====================================================================== +lwGetPolyVMaps() + +Fill in the lwVMapPt structure for each polygon vertex. +====================================================================== */ + +int lwGetPolyVMaps( lwPolygonList *polygon, lwVMap *vmap ) +{ + lwVMap *vm; + lwPolVert *pv; + int i, j; + + /* count the number of vmap values for each polygon vertex */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + /* allocate vmap references for each mapped vertex */ + + for ( i = 0; i < polygon->count; i++ ) { + for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) { + pv = &polygon->pol[ i ].v[ j ]; + if ( pv->nvmaps ) { + pv->vm = (lwVMapPt*)Mem_ClearedAlloc( pv->nvmaps * sizeof( lwVMapPt ) ); + if ( !pv->vm ) return 0; + pv->nvmaps = 0; + } + } + } + + /* fill in vmap references for each mapped point */ + + vm = vmap; + while ( vm ) { + if ( vm->perpoly ) { + for ( i = 0; i < vm->nverts; i++ ) { + for ( j = 0; j < polygon->pol[ vm->pindex[ i ]].nverts; j++ ) { + pv = &polygon->pol[ vm->pindex[ i ]].v[ j ]; + if ( vm->vindex[ i ] == pv->index ) { + pv->vm[ pv->nvmaps ].vmap = vm; + pv->vm[ pv->nvmaps ].index = i; + ++pv->nvmaps; + break; + } + } + } + } + vm = vm->next; + } + + return 1; +} diff --git a/src/renderer/model_ma.cpp b/src/renderer/model_ma.cpp new file mode 100644 index 0000000..b055c02 --- /dev/null +++ b/src/renderer/model_ma.cpp @@ -0,0 +1,1103 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Model_ma.h" + +/* +====================================================================== + + Parses Maya ASCII files. + +====================================================================== +*/ + + +#define MA_VERBOSE( x ) { if ( maGlobal.verbose ) { common->Printf x ; } } + +// working variables used during parsing +typedef struct { + bool verbose; + maModel_t *model; + maObject_t *currentObject; +} ma_t; + +static ma_t maGlobal; + + +void MA_ParseNodeHeader(idParser& parser, maNodeHeader_t* header) { + + memset(header, 0, sizeof(maNodeHeader_t)); + + idToken token; + while(parser.ReadToken(&token)) { + if(!token.Icmp("-")) { + parser.ReadToken(&token); + if (!token.Icmp("n")) { + parser.ReadToken(&token); + strcpy(header->name, token.c_str()); + } else if (!token.Icmp("p")) { + parser.ReadToken(&token); + strcpy(header->parent, token.c_str()); + } + } else if (!token.Icmp(";")) { + break; + } + } +} + +bool MA_ParseHeaderIndex(maAttribHeader_t* header, int& minIndex, int& maxIndex, const char* headerType, const char* skipString) { + + idParser miniParse; + idToken token; + + miniParse.LoadMemory(header->name, strlen(header->name), headerType); + if(skipString) { + miniParse.SkipUntilString(skipString); + } + + if(!miniParse.SkipUntilString("[")) { + //This was just a header + return false; + } + minIndex = miniParse.ParseInt(); + miniParse.ReadToken(&token); + if(!token.Icmp("]")) { + maxIndex = minIndex; + } else { + maxIndex = miniParse.ParseInt(); + } + return true; +} + +bool MA_ParseAttribHeader(idParser &parser, maAttribHeader_t* header) { + + idToken token; + + memset(header, 0, sizeof(maAttribHeader_t)); + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + parser.ReadToken(&token); + if (!token.Icmp("s")) { + header->size = parser.ParseInt(); + parser.ReadToken(&token); + } + } + strcpy(header->name, token.c_str()); + return true; +} + +bool MA_ReadVec3(idParser& parser, idVec3& vec) { + idToken token; + if(!parser.SkipUntilString("double3")) { + throw idException( va("Maya Loader '%s': Invalid Vec3", parser.GetFileName()) ); + return false; + } + + + //We need to flip y and z because of the maya coordinate system + vec.x = parser.ParseFloat(); + vec.z = parser.ParseFloat(); + vec.y = parser.ParseFloat(); + + return true; +} + +bool IsNodeComplete(idToken& token) { + if(!token.Icmp("createNode") || !token.Icmp("connectAttr") || !token.Icmp("select")) { + return true; + } + return false; +} + +bool MA_ParseTransform(idParser& parser) { + + maNodeHeader_t header; + maTransform_t* transform; + memset(&header, 0, sizeof(header)); + + //Allocate room for the transform + transform = (maTransform_t *)Mem_Alloc( sizeof( maTransform_t ) ); + memset(transform, 0, sizeof(maTransform_t)); + transform->scale.x = transform->scale.y = transform->scale.z = 1; + + //Get the header info from the transform + MA_ParseNodeHeader(parser, &header); + + //Read the transform attributes + idToken token; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + if(!token.Icmp("setAttr")) { + parser.ReadToken(&token); + if(!token.Icmp(".t")) { + if(!MA_ReadVec3(parser, transform->translate)) { + return false; + } + transform->translate.y *= -1; + } else if (!token.Icmp(".r")) { + if(!MA_ReadVec3(parser, transform->rotate)) { + return false; + } + } else if (!token.Icmp(".s")) { + if(!MA_ReadVec3(parser, transform->scale)) { + return false; + } + } else { + parser.SkipRestOfLine(); + } + } + } + + if(header.parent[0] != 0) { + //Find the parent + maTransform_t** parent; + maGlobal.model->transforms.Get(header.parent, &parent); + if(parent) { + transform->parent = *parent; + } + } + + //Add this transform to the list + maGlobal.model->transforms.Set(header.name, transform); + return true; +} + +bool MA_ParseVertex(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->vertexes) { + pMesh->numVertexes = header->size; + pMesh->vertexes = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numVertexes ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "VertexHeader", NULL)) { + //This was just a header + return true; + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->vertexes[i].x = parser.ParseFloat(); + pMesh->vertexes[i].z = parser.ParseFloat(); + pMesh->vertexes[i].y = -parser.ParseFloat(); + } + + return true; +} + +bool MA_ParseVertexTransforms(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->vertTransforms) { + if(header->size == 0) { + header->size = 1; + } + + pMesh->numVertTransforms = header->size; + pMesh->vertTransforms = (idVec4 *)Mem_Alloc( sizeof( idVec4 ) * pMesh->numVertTransforms ); + pMesh->nextVertTransformIndex = 0; + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "VertexTransformHeader", NULL)) { + //This was just a header + return true; + } + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + idToken tk2; + parser.ReadToken(&tk2); + if(!tk2.Icmp("type")) { + parser.SkipUntilString("float3"); + } else { + parser.UnreadToken(&tk2); + parser.UnreadToken(&token); + } + } else { + parser.UnreadToken(&token); + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->vertTransforms[pMesh->nextVertTransformIndex].x = parser.ParseFloat(); + pMesh->vertTransforms[pMesh->nextVertTransformIndex].z = parser.ParseFloat(); + pMesh->vertTransforms[pMesh->nextVertTransformIndex].y = -parser.ParseFloat(); + + //w hold the vert index + pMesh->vertTransforms[pMesh->nextVertTransformIndex].w = i; + + pMesh->nextVertTransformIndex++; + } + + return true; +} + +bool MA_ParseEdge(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->edges) { + pMesh->numEdges = header->size; + pMesh->edges = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numEdges ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "EdgeHeader", NULL)) { + //This was just a header + return true; + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->edges[i].x = parser.ParseFloat(); + pMesh->edges[i].y = parser.ParseFloat(); + pMesh->edges[i].z = parser.ParseFloat(); + } + + return true; +} + +bool MA_ParseNormal(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->normals) { + pMesh->numNormals = header->size; + pMesh->normals = (idVec3 *)Mem_Alloc( sizeof( idVec3 ) * pMesh->numNormals ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "NormalHeader", NULL)) { + //This was just a header + return true; + } + + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + idToken tk2; + parser.ReadToken(&tk2); + if(!tk2.Icmp("type")) { + parser.SkipUntilString("float3"); + } else { + parser.UnreadToken(&tk2); + parser.UnreadToken(&token); + } + } else { + parser.UnreadToken(&token); + } + + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->normals[i].x = parser.ParseFloat(); + + //Adjust the normals for the change in coordinate systems + pMesh->normals[i].z = parser.ParseFloat(); + pMesh->normals[i].y = -parser.ParseFloat(); + + pMesh->normals[i].Normalize(); + + } + + pMesh->normalsParsed = true; + pMesh->nextNormal = 0; + + return true; +} + + + +bool MA_ParseFace(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->faces) { + pMesh->numFaces = header->size; + pMesh->faces = (maFace_t *)Mem_Alloc( sizeof( maFace_t ) * pMesh->numFaces ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "FaceHeader", NULL)) { + //This was just a header + return true; + } + + //Read the face data + int currentFace = minIndex-1; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + + if(!token.Icmp("f")) { + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Face is not a triangle.", parser.GetFileName())); + return false; + } + //Increment the face number because a new face always starts with an "f" token + currentFace++; + + //We cannot reorder edges until later because the normal processing + //assumes the edges are in the original order + pMesh->faces[currentFace].edge[0] = parser.ParseInt(); + pMesh->faces[currentFace].edge[1] = parser.ParseInt(); + pMesh->faces[currentFace].edge[2] = parser.ParseInt(); + + //Some more init stuff + pMesh->faces[currentFace].vertexColors[0] = pMesh->faces[currentFace].vertexColors[1] = pMesh->faces[currentFace].vertexColors[2] = -1; + + } else if(!token.Icmp("mu")) { + int uvstIndex = parser.ParseInt(); + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Invalid texture coordinates.", parser.GetFileName())); + return false; + } + pMesh->faces[currentFace].tVertexNum[0] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[1] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[2] = parser.ParseInt(); + + } else if(!token.Icmp("mf")) { + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Invalid texture coordinates.", parser.GetFileName())); + return false; + } + pMesh->faces[currentFace].tVertexNum[0] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[1] = parser.ParseInt(); + pMesh->faces[currentFace].tVertexNum[2] = parser.ParseInt(); + + } else if(!token.Icmp("fc")) { + + int count = parser.ParseInt(); + if(count != 3) { + throw idException(va("Maya Loader '%s': Invalid vertex color.", parser.GetFileName())); + return false; + } + pMesh->faces[currentFace].vertexColors[0] = parser.ParseInt(); + pMesh->faces[currentFace].vertexColors[1] = parser.ParseInt(); + pMesh->faces[currentFace].vertexColors[2] = parser.ParseInt(); + + } + } + + return true; +} + +bool MA_ParseColor(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //Allocate enough space for all the verts if this is the first attribute for verticies + if(!pMesh->colors) { + pMesh->numColors = header->size; + pMesh->colors = (byte *)Mem_Alloc( sizeof( byte ) * pMesh->numColors * 4 ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "ColorHeader", NULL)) { + //This was just a header + return true; + } + + //Read each vert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->colors[i*4] = parser.ParseFloat() * 255; + pMesh->colors[i*4+1] = parser.ParseFloat() * 255; + pMesh->colors[i*4+2] = parser.ParseFloat() * 255; + pMesh->colors[i*4+3] = parser.ParseFloat() * 255; + } + + return true; +} + +bool MA_ParseTVert(idParser& parser, maAttribHeader_t* header) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + idToken token; + + //This is not the texture coordinates. It is just the name so ignore it + if(strstr(header->name, "uvsn")) { + return true; + } + + //Allocate enough space for all the data + if(!pMesh->tvertexes) { + pMesh->numTVertexes = header->size; + pMesh->tvertexes = (idVec2 *)Mem_Alloc( sizeof( idVec2 ) * pMesh->numTVertexes ); + } + + //Get the start and end index for this attribute + int minIndex, maxIndex; + if(!MA_ParseHeaderIndex(header, minIndex, maxIndex, "TextureCoordHeader", "uvsp")) { + //This was just a header + return true; + } + + parser.ReadToken(&token); + if(!token.Icmp("-")) { + idToken tk2; + parser.ReadToken(&tk2); + if(!tk2.Icmp("type")) { + parser.SkipUntilString("float2"); + } else { + parser.UnreadToken(&tk2); + parser.UnreadToken(&token); + } + } else { + parser.UnreadToken(&token); + } + + //Read each tvert + for(int i = minIndex; i <= maxIndex; i++) { + pMesh->tvertexes[i].x = parser.ParseFloat(); + pMesh->tvertexes[i].y = 1.0f - parser.ParseFloat(); + } + + return true; +} + + + +/* +* Quick check to see if the vert participates in a shared normal +*/ +bool MA_QuickIsVertShared(int faceIndex, int vertIndex) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + int vertNum = pMesh->faces[faceIndex].vertexNum[vertIndex]; + + for( int i = 0; i < 3; i++) { + int edge = pMesh->faces[faceIndex].edge[i]; + if(edge < 0) { + edge = idMath::Fabs(edge)-1; + } + if(pMesh->edges[edge].z == 1 && (pMesh->edges[edge].x == vertNum || pMesh->edges[edge].y == vertNum)) { + return true; + } + } + return false; +} + +void MA_GetSharedFace(int faceIndex, int vertIndex, int& sharedFace, int& sharedVert) { + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + int vertNum = pMesh->faces[faceIndex].vertexNum[vertIndex]; + + sharedFace = -1; + sharedVert = -1; + + //Find a shared edge on this face that contains the specified vert + for(int edgeIndex = 0; edgeIndex < 3; edgeIndex++) { + + int edge = pMesh->faces[faceIndex].edge[edgeIndex]; + if(edge < 0) { + edge = idMath::Fabs(edge)-1; + } + + if(pMesh->edges[edge].z == 1 && (pMesh->edges[edge].x == vertNum || pMesh->edges[edge].y == vertNum)) { + + for(int i = 0; i < faceIndex; i++) { + + for(int j = 0; j < 3; j++) { + if(pMesh->faces[i].vertexNum[j] == vertNum) { + sharedFace = i; + sharedVert = j; + break; + } + } + } + } + if(sharedFace != -1) + break; + + } +} + +void MA_ParseMesh(idParser& parser) { + + maObject_t *object; + object = (maObject_t *)Mem_Alloc( sizeof( maObject_t ) ); + memset( object, 0, sizeof( maObject_t ) ); + maGlobal.model->objects.Append( object ); + maGlobal.currentObject = object; + object->materialRef = -1; + + + //Get the header info from the mesh + maNodeHeader_t header; + MA_ParseNodeHeader(parser, &header); + + //Find my parent + if(header.parent[0] != 0) { + //Find the parent + maTransform_t** parent; + maGlobal.model->transforms.Get(header.parent, &parent); + if(parent) { + maGlobal.currentObject->mesh.transform = *parent; + } + } + + strcpy(object->name, header.name); + + //Read the transform attributes + idToken token; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + if(!token.Icmp("setAttr")) { + maAttribHeader_t header; + MA_ParseAttribHeader(parser, &header); + + if(strstr(header.name, ".vt")) { + MA_ParseVertex(parser, &header); + } else if (strstr(header.name, ".ed")) { + MA_ParseEdge(parser, &header); + } else if (strstr(header.name, ".pt")) { + MA_ParseVertexTransforms(parser, &header); + } else if (strstr(header.name, ".n")) { + MA_ParseNormal(parser, &header); + } else if (strstr(header.name, ".fc")) { + MA_ParseFace(parser, &header); + } else if (strstr(header.name, ".clr")) { + MA_ParseColor(parser, &header); + } else if (strstr(header.name, ".uvst")) { + MA_ParseTVert(parser, &header); + } else { + parser.SkipRestOfLine(); + } + } + } + + + maMesh_t* pMesh = &maGlobal.currentObject->mesh; + + //Get the verts from the edge + for(int i = 0; i < pMesh->numFaces; i++) { + for(int j = 0; j < 3; j++) { + int edge = pMesh->faces[i].edge[j]; + if(edge < 0) { + edge = idMath::Fabs(edge)-1; + pMesh->faces[i].vertexNum[j] = pMesh->edges[edge].y; + } else { + pMesh->faces[i].vertexNum[j] = pMesh->edges[edge].x; + } + } + } + + //Get the normals + if(pMesh->normalsParsed) { + for(int i = 0; i < pMesh->numFaces; i++) { + for(int j = 0; j < 3; j++) { + + //Is this vertex shared + int sharedFace = -1; + int sharedVert = -1; + + if(MA_QuickIsVertShared(i, j)) { + MA_GetSharedFace(i, j, sharedFace, sharedVert); + } + + if(sharedFace != -1) { + //Get the normal from the share + pMesh->faces[i].vertexNormals[j] = pMesh->faces[sharedFace].vertexNormals[sharedVert]; + + } else { + //The vertex is not shared so get the next normal + if(pMesh->nextNormal >= pMesh->numNormals) { + //We are using more normals than exist + throw idException(va("Maya Loader '%s': Invalid Normals Index.", parser.GetFileName())); + } + pMesh->faces[i].vertexNormals[j] = pMesh->normals[pMesh->nextNormal]; + pMesh->nextNormal++; + } + } + } + } + + //Now that the normals are good...lets reorder the verts to make the tris face the right way + for(int i = 0; i < pMesh->numFaces; i++) { + int tmp = pMesh->faces[i].vertexNum[1]; + pMesh->faces[i].vertexNum[1] = pMesh->faces[i].vertexNum[2]; + pMesh->faces[i].vertexNum[2] = tmp; + + idVec3 tmpVec = pMesh->faces[i].vertexNormals[1]; + pMesh->faces[i].vertexNormals[1] = pMesh->faces[i].vertexNormals[2]; + pMesh->faces[i].vertexNormals[2] = tmpVec; + + tmp = pMesh->faces[i].tVertexNum[1]; + pMesh->faces[i].tVertexNum[1] = pMesh->faces[i].tVertexNum[2]; + pMesh->faces[i].tVertexNum[2] = tmp; + + tmp = pMesh->faces[i].vertexColors[1]; + pMesh->faces[i].vertexColors[1] = pMesh->faces[i].vertexColors[2]; + pMesh->faces[i].vertexColors[2] = tmp; + } + + //Now apply the pt transformations + for(int i = 0; i < pMesh->numVertTransforms; i++) { + pMesh->vertexes[(int)pMesh->vertTransforms[i].w] += pMesh->vertTransforms[i].ToVec3(); + } + + MA_VERBOSE((va("MESH %s - parent %s\n", header.name, header.parent))); + MA_VERBOSE((va("\tverts:%d\n",maGlobal.currentObject->mesh.numVertexes))); + MA_VERBOSE((va("\tfaces:%d\n",maGlobal.currentObject->mesh.numFaces))); +} + +void MA_ParseFileNode(idParser& parser) { + + //Get the header info from the node + maNodeHeader_t header; + MA_ParseNodeHeader(parser, &header); + + //Read the transform attributes + idToken token; + while(parser.ReadToken(&token)) { + if(IsNodeComplete(token)) { + parser.UnreadToken(&token); + break; + } + if(!token.Icmp("setAttr")) { + maAttribHeader_t attribHeader; + MA_ParseAttribHeader(parser, &attribHeader); + + if(strstr(attribHeader.name, ".ftn")) { + parser.SkipUntilString("string"); + parser.ReadToken(&token); + if(!token.Icmp("(")) { + parser.ReadToken(&token); + } + + maFileNode_t* fileNode; + fileNode = (maFileNode_t*)Mem_Alloc( sizeof( maFileNode_t ) ); + strcpy(fileNode->name, header.name); + strcpy(fileNode->path, token.c_str()); + + maGlobal.model->fileNodes.Set(fileNode->name, fileNode); + } else { + parser.SkipRestOfLine(); + } + } + } +} + +void MA_ParseMaterialNode(idParser& parser) { + + //Get the header info from the node + maNodeHeader_t header; + MA_ParseNodeHeader(parser, &header); + + maMaterialNode_t* matNode; + matNode = (maMaterialNode_t*)Mem_Alloc( sizeof( maMaterialNode_t ) ); + memset(matNode, 0, sizeof(maMaterialNode_t)); + + strcpy(matNode->name, header.name); + + maGlobal.model->materialNodes.Set(matNode->name, matNode); +} + +void MA_ParseCreateNode(idParser& parser) { + + idToken token; + parser.ReadToken(&token); + + if(!token.Icmp("transform")) { + MA_ParseTransform(parser); + } else if(!token.Icmp("mesh")) { + MA_ParseMesh(parser); + } else if(!token.Icmp("file")) { + MA_ParseFileNode(parser); + } else if(!token.Icmp("shadingEngine") || !token.Icmp("lambert") || !token.Icmp("phong") || !token.Icmp("blinn") ) { + MA_ParseMaterialNode(parser); + } +} + + +int MA_AddMaterial(const char* materialName) { + + + maMaterialNode_t** destNode; + maGlobal.model->materialNodes.Get(materialName, &destNode); + if(destNode) { + maMaterialNode_t* matNode = *destNode; + + //Iterate down the tree until we get a file + while(matNode && !matNode->file) { + matNode = matNode->child; + } + if(matNode && matNode->file) { + + //Got the file + maMaterial_t *material; + material = (maMaterial_t *)Mem_Alloc( sizeof( maMaterial_t ) ); + memset( material, 0, sizeof( maMaterial_t ) ); + + //Remove the OS stuff + idStr qPath; + qPath = fileSystem->OSPathToRelativePath( matNode->file->path ); + + strcpy(material->name, qPath.c_str()); + + maGlobal.model->materials.Append( material ); + return maGlobal.model->materials.Num()-1; + } + } + return -1; +} + +bool MA_ParseConnectAttr(idParser& parser) { + + idStr temp; + idStr srcName; + idStr srcType; + idStr destName; + idStr destType; + + idToken token; + parser.ReadToken(&token); + temp = token; + int dot = temp.Find("."); + if(dot == -1) { + throw idException(va("Maya Loader '%s': Invalid Connect Attribute.", parser.GetFileName())); + return false; + } + srcName = temp.Left(dot); + srcType = temp.Right(temp.Length()-dot-1); + + parser.ReadToken(&token); + temp = token; + dot = temp.Find("."); + if(dot == -1) { + throw idException(va("Maya Loader '%s': Invalid Connect Attribute.", parser.GetFileName())); + return false; + } + destName = temp.Left(dot); + destType = temp.Right(temp.Length()-dot-1); + + if(srcType.Find("oc") != -1) { + + //Is this attribute a material node attribute + maMaterialNode_t** matNode; + maGlobal.model->materialNodes.Get(srcName, &matNode); + if(matNode) { + maMaterialNode_t** destNode; + maGlobal.model->materialNodes.Get(destName, &destNode); + if(destNode) { + (*destNode)->child = *matNode; + } + } + + //Is this attribute a file node + maFileNode_t** fileNode; + maGlobal.model->fileNodes.Get(srcName, &fileNode); + if(fileNode) { + maMaterialNode_t** destNode; + maGlobal.model->materialNodes.Get(destName, &destNode); + if(destNode) { + (*destNode)->file = *fileNode; + } + } + } + + if(srcType.Find("iog") != -1) { + //Is this an attribute for one of our meshes + for(int i = 0; i < maGlobal.model->objects.Num(); i++) { + if(!strcmp(maGlobal.model->objects[i]->name, srcName)) { + //maGlobal.model->objects[i]->materialRef = MA_AddMaterial(destName); + strcpy(maGlobal.model->objects[i]->materialName, destName); + break; + } + } + } + + return true; +} + + +void MA_BuildScale(idMat4& mat, float x, float y, float z) { + mat.Identity(); + mat[0][0] = x; + mat[1][1] = y; + mat[2][2] = z; +} + +void MA_BuildAxisRotation(idMat4& mat, float ang, int axis) { + + float sinAng = idMath::Sin(ang); + float cosAng = idMath::Cos(ang); + + mat.Identity(); + switch(axis) { + case 0: //x + mat[1][1] = cosAng; + mat[1][2] = sinAng; + mat[2][1] = -sinAng; + mat[2][2] = cosAng; + break; + case 1: //y + mat[0][0] = cosAng; + mat[0][2] = -sinAng; + mat[2][0] = sinAng; + mat[2][2] = cosAng; + break; + case 2://z + mat[0][0] = cosAng; + mat[0][1] = sinAng; + mat[1][0] = -sinAng; + mat[1][1] = cosAng; + break; + } +} + +void MA_ApplyTransformation(maModel_t *model) { + + for(int i = 0; i < model->objects.Num(); i++) { + maMesh_t* mesh = &model->objects[i]->mesh; + maTransform_t* transform = mesh->transform; + + + + while(transform) { + + idMat4 rotx, roty, rotz; + idMat4 scale; + + rotx.Identity(); + roty.Identity(); + rotz.Identity(); + + if(fabs(transform->rotate.x) > 0.0f) { + MA_BuildAxisRotation(rotx, DEG2RAD(-transform->rotate.x), 0); + } + if(fabs(transform->rotate.y) > 0.0f) { + MA_BuildAxisRotation(roty, DEG2RAD(transform->rotate.y), 1); + } + if(fabs(transform->rotate.z) > 0.0f) { + MA_BuildAxisRotation(rotz, DEG2RAD(-transform->rotate.z), 2); + } + + MA_BuildScale(scale, transform->scale.x, transform->scale.y, transform->scale.z); + + //Apply the transformation to each vert + for(int j = 0; j < mesh->numVertexes; j++) { + mesh->vertexes[j] = scale * mesh->vertexes[j]; + + mesh->vertexes[j] = rotx * mesh->vertexes[j]; + mesh->vertexes[j] = rotz * mesh->vertexes[j]; + mesh->vertexes[j] = roty * mesh->vertexes[j]; + + mesh->vertexes[j] = mesh->vertexes[j] + transform->translate; + } + + transform = transform->parent; + } + } +} + +/* +================= +MA_Parse +================= +*/ +maModel_t *MA_Parse( const char *buffer, const char* filename, bool verbose ) { + memset( &maGlobal, 0, sizeof( maGlobal ) ); + + maGlobal.verbose = verbose; + + + + + maGlobal.currentObject = NULL; + + // NOTE: using new operator because aseModel_t contains idList class objects + maGlobal.model = new maModel_t; + maGlobal.model->objects.Resize( 32, 32 ); + maGlobal.model->materials.Resize( 32, 32 ); + + + idParser parser; + parser.SetFlags(LEXFL_NOSTRINGCONCAT); + parser.LoadMemory(buffer, strlen(buffer), filename); + + idToken token; + while(parser.ReadToken(&token)) { + + if(!token.Icmp("createNode")) { + MA_ParseCreateNode(parser); + } else if(!token.Icmp("connectAttr")) { + MA_ParseConnectAttr(parser); + } + } + + //Resolve The Materials + for(int i = 0; i < maGlobal.model->objects.Num(); i++) { + maGlobal.model->objects[i]->materialRef = MA_AddMaterial(maGlobal.model->objects[i]->materialName); + } + + + + //Apply Transformation + MA_ApplyTransformation(maGlobal.model); + + return maGlobal.model; +} + +/* +================= +MA_Load +================= +*/ +maModel_t *MA_Load( const char *fileName ) { + char *buf; + ID_TIME_T timeStamp; + maModel_t *ma; + + fileSystem->ReadFile( fileName, (void **)&buf, &timeStamp ); + if ( !buf ) { + return NULL; + } + + try { + ma = MA_Parse( buf, fileName, false ); + ma->timeStamp = timeStamp; + } catch( idException &e ) { + common->Warning("%s", e.error); + if(maGlobal.model) { + MA_Free(maGlobal.model); + } + ma = NULL; + } + + fileSystem->FreeFile( buf ); + + return ma; +} + +/* +================= +MA_Free +================= +*/ +void MA_Free( maModel_t *ma ) { + int i; + maObject_t *obj; + maMesh_t *mesh; + maMaterial_t *material; + + if ( !ma ) { + return; + } + for ( i = 0; i < ma->objects.Num(); i++ ) { + obj = ma->objects[i]; + + // free the base nesh + mesh = &obj->mesh; + + if ( mesh->vertexes ) { + Mem_Free( mesh->vertexes ); + } + if ( mesh->vertTransforms ) { + Mem_Free( mesh->vertTransforms ); + } + if ( mesh->normals ) { + Mem_Free( mesh->normals ); + } + if ( mesh->tvertexes ) { + Mem_Free( mesh->tvertexes ); + } + if ( mesh->edges ) { + Mem_Free( mesh->edges ); + } + if ( mesh->colors ) { + Mem_Free( mesh->colors ); + } + if ( mesh->faces ) { + Mem_Free( mesh->faces ); + } + Mem_Free( obj ); + } + ma->objects.Clear(); + + for ( i = 0; i < ma->materials.Num(); i++ ) { + material = ma->materials[i]; + Mem_Free( material ); + } + ma->materials.Clear(); + + maTransform_t** trans; + for ( i = 0; i < ma->transforms.Num(); i++ ) { + trans = ma->transforms.GetIndex(i); + Mem_Free( *trans ); + } + ma->transforms.Clear(); + + + maFileNode_t** fileNode; + for ( i = 0; i < ma->fileNodes.Num(); i++ ) { + fileNode = ma->fileNodes.GetIndex(i); + Mem_Free( *fileNode ); + } + ma->fileNodes.Clear(); + + maMaterialNode_t** matNode; + for ( i = 0; i < ma->materialNodes.Num(); i++ ) { + matNode = ma->materialNodes.GetIndex(i); + Mem_Free( *matNode ); + } + ma->materialNodes.Clear(); + delete ma; +} diff --git a/src/renderer/model_md3.cpp b/src/renderer/model_md3.cpp new file mode 100644 index 0000000..d0126c1 --- /dev/null +++ b/src/renderer/model_md3.cpp @@ -0,0 +1,367 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Model_local.h" +#include "Model_md3.h" + +/*********************************************************************** + + idMD3Mesh + +***********************************************************************/ + +#define LL(x) x=LittleLong(x) + +/* +================= +idRenderModelMD3::InitFromFile +================= +*/ +void idRenderModelMD3::InitFromFile( const char *fileName ) { + int i, j; + md3Header_t *pinmodel; + md3Frame_t *frame; + md3Surface_t *surf; + md3Shader_t *shader; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; + void *buffer; + int version; + int size; + + + name = fileName; + + size = fileSystem->ReadFile( fileName, &buffer, NULL ); + if (!size || size<0 ) { + return; + } + + pinmodel = (md3Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD3_VERSION) { + fileSystem->FreeFile( buffer ); + common->Warning( "InitFromFile: %s has wrong version (%i should be %i)", + fileName, version, MD3_VERSION); + return; + } + + size = LittleLong(pinmodel->ofsEnd); + dataSize += size; + md3 = (md3Header_t *)Mem_Alloc( size ); + + memcpy (md3, buffer, LittleLong(pinmodel->ofsEnd) ); + + LL(md3->ident); + LL(md3->version); + LL(md3->numFrames); + LL(md3->numTags); + LL(md3->numSurfaces); + LL(md3->ofsFrames); + LL(md3->ofsTags); + LL(md3->ofsSurfaces); + LL(md3->ofsEnd); + + if ( md3->numFrames < 1 ) { + common->Warning( "InitFromFile: %s has no frames", fileName ); + fileSystem->FreeFile( buffer ); + return; + } + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)md3 + md3->ofsFrames ); + for ( i = 0 ; i < md3->numFrames ; i++, frame++) { + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + + // swap all the tags + tag = (md3Tag_t *) ( (byte *)md3 + md3->ofsTags ); + for ( i = 0 ; i < md3->numTags * md3->numFrames ; i++, tag++) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } + + // swap all the surfaces + surf = (md3Surface_t *) ( (byte *)md3 + md3->ofsSurfaces ); + for ( i = 0 ; i < md3->numSurfaces ; i++) { + + LL(surf->ident); + LL(surf->flags); + LL(surf->numFrames); + LL(surf->numShaders); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsShaders); + LL(surf->ofsSt); + LL(surf->ofsXyzNormals); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + common->Error( "InitFromFile: %s has more than %i verts on a surface (%i)", + fileName, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + common->Error( "InitFromFile: %s has more than %i triangles on a surface (%i)", + fileName, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = 0; //SF_MD3; + + // lowercase the surface name so skin compares are faster + int slen = (int)strlen( surf->name ); + for( j = 0; j < slen; j++ ) { + surf->name[j] = tolower( surf->name[j] ); + } + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j-2] == '_' ) { + surf->name[j-2] = 0; + } + + // register the shaders + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + const idMaterial *sh; + + sh = declManager->FindMaterial( shader->name ); + shader->shader = sh; + } + + // swap all the triangles + tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the ST + st = (md3St_t *) ( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } + + + // find the next surface + surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + fileSystem->FreeFile( buffer ); +} + +/* +================= +idRenderModelMD3::IsDynamicModel +================= +*/ +dynamicModel_t idRenderModelMD3::IsDynamicModel() const { + return DM_CACHED; +} + +/* +================= +idRenderModelMD3::LerpMeshVertexes +================= +*/ +void idRenderModelMD3::LerpMeshVertexes ( srfTriangles_t *tri, const struct md3Surface_s *surf, const float backlerp, const int frame, const int oldframe ) const { + short *oldXyz, *newXyz; + float oldXyzScale, newXyzScale; + int vertNum; + int numVerts; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + (frame * surf->numVerts * 4); + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, newXyz += 4 ) { + + idDrawVert *outvert = &tri->verts[tri->numVerts]; + + outvert->xyz.x = newXyz[0] * newXyzScale; + outvert->xyz.y = newXyz[1] * newXyzScale; + outvert->xyz.z = newXyz[2] * newXyzScale; + + tri->numVerts++; + } + } else { + // + // interpolate and copy the vertexes + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + (oldframe * surf->numVerts * 4); + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, oldXyz += 4, newXyz += 4 ) { + + idDrawVert *outvert = &tri->verts[tri->numVerts]; + + // interpolate the xyz + outvert->xyz.x = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outvert->xyz.y = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outvert->xyz.z = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + tri->numVerts++; + } + } +} + +/* +============= +idRenderModelMD3::InstantiateDynamicModel +============= +*/ +idRenderModel *idRenderModelMD3::InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ) { + (void)surfMask; + int i, j; + float backlerp; + int * triangles; + float * texCoords; + int indexes; + int numVerts; + md3Surface_t * surface; + int frame, oldframe; + idRenderModelStatic *staticModel; + + if ( cachedModel ) { + delete cachedModel; + cachedModel = NULL; + } + + staticModel = NewRenderModel( this ); + staticModel->bounds.Clear(); + + surface = (md3Surface_t *) ((byte *)md3 + md3->ofsSurfaces); + + // TODO: these need set by an entity + frame = ent->shaderParms[SHADERPARM_MD3_FRAME]; // probably want to keep frames < 1000 or so + oldframe = ent->shaderParms[SHADERPARM_MD3_LASTFRAME]; + backlerp = ent->shaderParms[SHADERPARM_MD3_BACKLERP]; + + for( i = 0; i < md3->numSurfaces; i++ ) { + + srfTriangles_t *tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, surface->numVerts ); + R_AllocStaticTriSurfIndexes( tri, surface->numTriangles * 3 ); + tri->bounds.Clear(); + + modelSurface_t surf; + + surf.geometry = tri; + + md3Shader_t* shaders = (md3Shader_t *) ((byte *)surface + surface->ofsShaders); + surf.shader = shaders->shader; + + LerpMeshVertexes( tri, surface, backlerp, frame, oldframe ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + for (j = 0 ; j < indexes ; j++) { + tri->indexes[j] = triangles[j]; + } + tri->numIndexes += indexes; + + texCoords = (float *) ((byte *)surface + surface->ofsSt); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + idDrawVert *stri = &tri->verts[j]; + stri->st[0] = texCoords[j*2+0]; + stri->st[1] = texCoords[j*2+1]; + } + + R_BoundTriSurf( tri ); + + staticModel->AddSurface( surf ); + staticModel->bounds.AddPoint( surf.geometry->bounds[0] ); + staticModel->bounds.AddPoint( surf.geometry->bounds[1] ); + + // find the next surface + surface = (md3Surface_t *)( (byte *)surface + surface->ofsEnd ); + } + + return staticModel; +} + +/* +===================== +idRenderModelMD3::Bounds +===================== +*/ + +idBounds idRenderModelMD3::Bounds(const struct renderEntity_s *ent) const { + idBounds ret; + + ret.Clear(); + + if (!ent || !md3) { + // just give it the editor bounds + ret.AddPoint(idVec3(-10,-10,-10)); + ret.AddPoint(idVec3( 10, 10, 10)); + return ret; + } + + md3Frame_t *frame = (md3Frame_t *)( (byte *)md3 + md3->ofsFrames ); + + ret.AddPoint( frame->bounds[0] ); + ret.AddPoint( frame->bounds[1] ); + + return ret; +} diff --git a/src/renderer/model_md5.cpp b/src/renderer/model_md5.cpp new file mode 100644 index 0000000..dd9a928 --- /dev/null +++ b/src/renderer/model_md5.cpp @@ -0,0 +1,1313 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Model_local.h" + +static const char *MD5_SnapshotName = "_MD5_Snapshot_"; + +idCVar r_deriveBiTangents( "r_deriveBiTangents", "0", CVAR_RENDERER | CVAR_BOOL, "derive the bitangent from the normal and tangent" ); +idCVar r_useNewSkinning( "r_useNewSkinning", "1", CVAR_RENDERER | CVAR_BOOL, "use new skinning" ); +idCVar r_useFastSkinning( "r_useFastSkinning", "0", CVAR_RENDERER | CVAR_BOOL, "use fast skinning with a single weight per vertex" ); + + +/*********************************************************************** + + idMD5Mesh + +***********************************************************************/ + +static int c_numVerts = 0; +static int c_numWeights = 0; +static int c_numWeightJoints = 0; + +typedef struct vertexWeight_s { + int vert; + int joint; + idVec3 offset; + float jointWeight; +} vertexWeight_t; + +/* +==================== +idMD5Mesh::idMD5Mesh +==================== +*/ +#if 0 +idMD5Mesh::idMD5Mesh() { + scaledWeights = NULL; + weightIndex = NULL; + shader = NULL; + numTris = 0; + deformInfo = NULL; + surfaceNum = 0; +} + +/* +==================== +idMD5Mesh::~idMD5Mesh +==================== +*/ +idMD5Mesh::~idMD5Mesh() { + Mem_Free16( scaledWeights ); + Mem_Free16( weightIndex ); + if ( deformInfo ) { + R_FreeDeformInfo( deformInfo ); + deformInfo = NULL; + } +} + +/* +==================== +idMD5Mesh::ParseMesh +==================== +*/ +void idMD5Mesh::ParseMesh( idLexer &parser, int numJoints, const idJointMat *joints ) { + idToken token; + idToken name; + int num; + int count; + int jointnum; + idStr shaderName; + int i, j; + idList tris; + idList firstWeightForVertex; + idList numWeightsForVertex; + int maxweight; + idList tempWeights; + + parser.ExpectTokenString( "{" ); + + // + // parse name + // + if ( parser.CheckTokenString( "name" ) ) { + parser.ReadToken( &name ); + } + + // + // parse shader + // + parser.ExpectTokenString( "shader" ); + + parser.ReadToken( &token ); + shaderName = token; + + shader = declManager->FindMaterial( shaderName ); + + // + // parse texture coordinates + // + parser.ExpectTokenString( "numverts" ); + count = parser.ParseInt(); + if ( count < 0 ) { + parser.Error( "Invalid size: %s", token.c_str() ); + } + + texCoords.SetNum( count ); + firstWeightForVertex.SetNum( count ); + numWeightsForVertex.SetNum( count ); + + numWeights = 0; + maxweight = 0; + for( i = 0; i < texCoords.Num(); i++ ) { + parser.ExpectTokenString( "vert" ); + parser.ParseInt(); + + parser.Parse1DMatrix( 2, texCoords[ i ].ToFloatPtr() ); + + firstWeightForVertex[ i ] = parser.ParseInt(); + numWeightsForVertex[ i ] = parser.ParseInt(); + + if ( !numWeightsForVertex[ i ] ) { + parser.Error( "Vertex without any joint weights." ); + } + + numWeights += numWeightsForVertex[ i ]; + if ( numWeightsForVertex[ i ] + firstWeightForVertex[ i ] > maxweight ) { + maxweight = numWeightsForVertex[ i ] + firstWeightForVertex[ i ]; + } + } + + // + // parse tris + // + parser.ExpectTokenString( "numtris" ); + count = parser.ParseInt(); + if ( count < 0 ) { + parser.Error( "Invalid size: %d", count ); + } + + tris.SetNum( count * 3 ); + numTris = count; + for( i = 0; i < count; i++ ) { + parser.ExpectTokenString( "tri" ); + parser.ParseInt(); + + tris[ i * 3 + 0 ] = parser.ParseInt(); + tris[ i * 3 + 1 ] = parser.ParseInt(); + tris[ i * 3 + 2 ] = parser.ParseInt(); + } + + // + // parse weights + // + parser.ExpectTokenString( "numweights" ); + count = parser.ParseInt(); + if ( count < 0 ) { + parser.Error( "Invalid size: %d", count ); + } + + if ( maxweight > count ) { + parser.Warning( "Vertices reference out of range weights in model (%d of %d weights).", maxweight, count ); + } + + tempWeights.SetNum( count ); + + for( i = 0; i < count; i++ ) { + parser.ExpectTokenString( "weight" ); + parser.ParseInt(); + + jointnum = parser.ParseInt(); + if ( ( jointnum < 0 ) || ( jointnum >= numJoints ) ) { + parser.Error( "Joint Index out of range(%d): %d", numJoints, jointnum ); + } + + tempWeights[ i ].joint = jointnum; + tempWeights[ i ].jointWeight = parser.ParseFloat(); + + parser.Parse1DMatrix( 3, tempWeights[ i ].offset.ToFloatPtr() ); + } + + // create pre-scaled weights and an index for the vertex/joint lookup + scaledWeights = (idVec4 *) Mem_Alloc16( numWeights * sizeof( scaledWeights[0] ) ); + weightIndex = (int *) Mem_Alloc16( numWeights * 2 * sizeof( weightIndex[0] ) ); + memset( weightIndex, 0, numWeights * 2 * sizeof( weightIndex[0] ) ); + + count = 0; + for( i = 0; i < texCoords.Num(); i++ ) { + num = firstWeightForVertex[i]; + for( j = 0; j < numWeightsForVertex[i]; j++, num++, count++ ) { + scaledWeights[count].ToVec3() = tempWeights[num].offset * tempWeights[num].jointWeight; + scaledWeights[count].w = tempWeights[num].jointWeight; + weightIndex[count * 2 + 0] = tempWeights[num].joint * sizeof( idJointMat ); + } + weightIndex[count * 2 - 1] = 1; + } + + tempWeights.Clear(); + numWeightsForVertex.Clear(); + firstWeightForVertex.Clear(); + + parser.ExpectTokenString( "}" ); + + // update counters + c_numVerts += texCoords.Num(); + c_numWeights += numWeights; + c_numWeightJoints++; + for ( i = 0; i < numWeights; i++ ) { + c_numWeightJoints += weightIndex[i*2+1]; + } + + // + // build the information that will be common to all animations of this mesh: + // silhouette edge connectivity and normal / tangent generation information + // + idDrawVert *verts = (idDrawVert *) _alloca16( texCoords.Num() * sizeof( idDrawVert ) ); + for ( i = 0; i < texCoords.Num(); i++ ) { + verts[i].Clear(); + verts[i].st = texCoords[i]; + } + TransformVerts( verts, joints ); + deformInfo = R_BuildDeformInfo( texCoords.Num(), verts, tris.Num(), tris.Ptr(), shader->UseUnsmoothedTangents() ); +} + +/* +==================== +idMD5Mesh::TransformVerts +==================== +*/ +void idMD5Mesh::TransformVerts( idDrawVert *verts, const idJointMat *entJoints ) { + SIMDProcessor->TransformVerts( verts, texCoords.Num(), entJoints, scaledWeights, weightIndex, numWeights ); +} + +/* +==================== +idMD5Mesh::TransformScaledVerts + +Special transform to make the mesh seem fat or skinny. May be used for zombie deaths +==================== +*/ +void idMD5Mesh::TransformScaledVerts( idDrawVert *verts, const idJointMat *entJoints, float scale ) { + idVec4 *scaledWeights = (idVec4 *) _alloca16( numWeights * sizeof( scaledWeights[0] ) ); + SIMDProcessor->Mul( scaledWeights[0].ToFloatPtr(), scale, scaledWeights[0].ToFloatPtr(), numWeights * 4 ); + SIMDProcessor->TransformVerts( verts, texCoords.Num(), entJoints, scaledWeights, weightIndex, numWeights ); +} + +/* +==================== +idMD5Mesh::UpdateSurface +==================== +*/ +void idMD5Mesh::UpdateSurface( const struct renderEntity_s *ent, const idJointMat *entJoints, modelSurface_t *surf ) { + int i, base; + srfTriangles_t *tri; + + tr.pc.c_deformedSurfaces++; + tr.pc.c_deformedVerts += deformInfo->numOutputVerts; + tr.pc.c_deformedIndexes += deformInfo->numIndexes; + + surf->shader = shader; + + if ( surf->geometry ) { + // if the number of verts and indexes are the same we can re-use the triangle surface + // the number of indexes must be the same to assure the correct amount of memory is allocated for the facePlanes + if ( surf->geometry->numVerts == deformInfo->numOutputVerts && surf->geometry->numIndexes == deformInfo->numIndexes ) { + R_FreeStaticTriSurfVertexCaches( surf->geometry ); + } else { + R_FreeStaticTriSurf( surf->geometry ); + surf->geometry = R_AllocStaticTriSurf(); + } + } else { + surf->geometry = R_AllocStaticTriSurf(); + } + + tri = surf->geometry; + + // note that some of the data is references, and should not be freed + tri->deformedSurface = true; + tri->tangentsCalculated = false; + tri->facePlanesCalculated = false; + + tri->numIndexes = deformInfo->numIndexes; + tri->indexes = deformInfo->indexes; + tri->silIndexes = deformInfo->silIndexes; + tri->numMirroredVerts = deformInfo->numMirroredVerts; + tri->mirroredVerts = deformInfo->mirroredVerts; + tri->numDupVerts = deformInfo->numDupVerts; + tri->dupVerts = deformInfo->dupVerts; + tri->numSilEdges = deformInfo->numSilEdges; + tri->silEdges = deformInfo->silEdges; + tri->dominantTris = deformInfo->dominantTris; + tri->numVerts = deformInfo->numOutputVerts; + + if ( tri->verts == NULL ) { + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( i = 0; i < deformInfo->numSourceVerts; i++ ) { + tri->verts[i].Clear(); + tri->verts[i].st = texCoords[i]; + } + } + + if ( ent->shaderParms[ SHADERPARM_MD5_SKINSCALE ] != 0.0f ) { + TransformScaledVerts( tri->verts, entJoints, ent->shaderParms[ SHADERPARM_MD5_SKINSCALE ] ); + } else { + TransformVerts( tri->verts, entJoints ); + } + + // replicate the mirror seam vertexes + base = deformInfo->numOutputVerts - deformInfo->numMirroredVerts; + for ( i = 0; i < deformInfo->numMirroredVerts; i++ ) { + tri->verts[base + i] = tri->verts[deformInfo->mirroredVerts[i]]; + } + + R_BoundTriSurf( tri ); + + // If a surface is going to be have a lighting interaction generated, it will also have to call + // R_DeriveTangents() to get normals, tangents, and face planes. If it only + // needs shadows generated, it will only have to generate face planes. If it only + // has ambient drawing, or is culled, no additional work will be necessary + if ( !r_useDeferredTangents.GetBool() ) { + // set face planes, vertex normals, tangents + R_DeriveTangents( tri ); + } +} + +/* +==================== +idMD5Mesh::CalcBounds +==================== +*/ +idBounds idMD5Mesh::CalcBounds( const idJointMat *entJoints ) { + idBounds bounds; + idDrawVert *verts = (idDrawVert *) _alloca16( texCoords.Num() * sizeof( idDrawVert ) ); + + TransformVerts( verts, entJoints ); + + SIMDProcessor->MinMax( bounds[0], bounds[1], verts, texCoords.Num() ); + + return bounds; +} + +/* +==================== +idMD5Mesh::NearestJoint +==================== +*/ +int idMD5Mesh::NearestJoint( int a, int b, int c ) const { + int i, bestJoint, vertNum, weightVertNum; + float bestWeight; + + // duplicated vertices might not have weights + if ( a >= 0 && a < texCoords.Num() ) { + vertNum = a; + } else if ( b >= 0 && b < texCoords.Num() ) { + vertNum = b; + } else if ( c >= 0 && c < texCoords.Num() ) { + vertNum = c; + } else { + // all vertices are duplicates which shouldn't happen + return 0; + } + + // find the first weight for this vertex + weightVertNum = 0; + for( i = 0; weightVertNum < vertNum; i++ ) { + weightVertNum += weightIndex[i*2+1]; + } + + // get the joint for the largest weight + bestWeight = scaledWeights[i].w; + bestJoint = weightIndex[i*2+0] / sizeof( idJointMat ); + for( ; weightIndex[i*2+1] == 0; i++ ) { + if ( scaledWeights[i].w > bestWeight ) { + bestWeight = scaledWeights[i].w; + bestJoint = weightIndex[i*2+0] / sizeof( idJointMat ); + } + } + return bestJoint; +} + +/* +==================== +idMD5Mesh::NumVerts +==================== +*/ +int idMD5Mesh::NumVerts( void ) const { + return texCoords.Num(); +} + +/* +==================== +idMD5Mesh::NumTris +==================== +*/ +int idMD5Mesh::NumTris( void ) const { + return numTris; +} + +/* +==================== +idMD5Mesh::NumWeights +==================== +*/ +int idMD5Mesh::NumWeights( void ) const { + return numWeights; +} +#endif + +idMD5Mesh::idMD5Mesh() { + weights = NULL; + baseVectors = NULL; + scaledBaseVectors = NULL; + shader = NULL; + numWeights = 0; + numTris = 0; + deformInfo = NULL; + surfaceNum = 0; + flags = 0; + currentTime = 0.0f; +} + +idMD5Mesh::~idMD5Mesh() { + Mem_Free16( scaledBaseVectors ); + Mem_Free16( weights ); + Mem_Free16( baseVectors ); + if ( deformInfo != NULL ) { + R_FreeDeformInfo( deformInfo ); + deformInfo = NULL; + } +} + +void idMD5Mesh::ParseMesh( idLexer &parser, int numJoints, const idJointMat *joints ) { + idToken token; + idToken meshName; + idList tris; + idList firstWeightForVertex; + idList numWeightsForVertex; + idList tempWeights; + + parser.ExpectTokenString( "{" ); + if ( parser.CheckTokenString( "name" ) ) { + parser.ReadToken( &meshName ); + } + + parser.ExpectTokenString( "shader" ); + parser.ReadToken( &token ); + shader = declManager->FindMaterial( token ); + + parser.ExpectTokenString( "numverts" ); + const int numVerts = parser.ParseInt(); + if ( numVerts < 0 ) { + parser.Error( "Invalid size: %d", numVerts ); + } + texCoords.SetNum( numVerts ); + firstWeightForVertex.SetNum( numVerts ); + numWeightsForVertex.SetNum( numVerts ); + + numWeights = 0; + int maxWeight = 0; + for ( int i = 0; i < numVerts; i++ ) { + parser.ExpectTokenString( "vert" ); + parser.ParseInt(); + parser.Parse1DMatrix( 2, texCoords[i].ToFloatPtr() ); + firstWeightForVertex[i] = parser.ParseInt(); + numWeightsForVertex[i] = parser.ParseInt(); + if ( numWeightsForVertex[i] <= 0 ) { + parser.Error( "Vertex without any joint weights." ); + } + numWeights += numWeightsForVertex[i]; + maxWeight = Max( maxWeight, firstWeightForVertex[i] + numWeightsForVertex[i] ); + } + + parser.ExpectTokenString( "numtris" ); + numTris = parser.ParseInt(); + if ( numTris < 0 ) { + parser.Error( "Invalid size: %d", numTris ); + } + tris.SetNum( numTris * 3 ); + for ( int i = 0; i < numTris; i++ ) { + parser.ExpectTokenString( "tri" ); + parser.ParseInt(); + tris[i * 3 + 0] = parser.ParseInt(); + tris[i * 3 + 1] = parser.ParseInt(); + tris[i * 3 + 2] = parser.ParseInt(); + } + + parser.ExpectTokenString( "numweights" ); + const int numFileWeights = parser.ParseInt(); + if ( numFileWeights < 0 ) { + parser.Error( "Invalid size: %d", numFileWeights ); + } + if ( maxWeight > numFileWeights ) { + parser.Warning( "Vertices reference out of range weights in model (%d of %d weights).", maxWeight, numFileWeights ); + } + tempWeights.SetNum( numFileWeights ); + for ( int i = 0; i < numFileWeights; i++ ) { + parser.ExpectTokenString( "weight" ); + parser.ParseInt(); + tempWeights[i].joint = parser.ParseInt(); + if ( tempWeights[i].joint < 0 || tempWeights[i].joint >= numJoints ) { + parser.Error( "Joint Index out of range(%d): %d", numJoints, tempWeights[i].joint ); + } + tempWeights[i].jointWeight = parser.ParseFloat(); + parser.Parse1DMatrix( 3, tempWeights[i].offset.ToFloatPtr() ); + } + + // Retail Quake 4 orders each vertex's weights from largest to smallest. + for ( int vertNum = 0; vertNum < numVerts; vertNum++ ) { + const int first = firstWeightForVertex[vertNum]; + const int count = numWeightsForVertex[vertNum]; + for ( int a = 0; a < count - 1; a++ ) { + for ( int b = a + 1; b < count; b++ ) { + if ( tempWeights[first + b].jointWeight > tempWeights[first + a].jointWeight ) { + idSwap( tempWeights[first + a], tempWeights[first + b] ); + } + } + } + } + + weights = (jointWeight_t *)Mem_Alloc16( numWeights * sizeof( weights[0] ) ); + scaledBaseVectors = (idVec4 *)Mem_Alloc16( numWeights * sizeof( scaledBaseVectors[0] ) ); + int outputWeight = 0; + for ( int vertNum = 0; vertNum < numVerts; vertNum++ ) { + const int first = firstWeightForVertex[vertNum]; + const int count = numWeightsForVertex[vertNum]; + for ( int weightNum = 0; weightNum < count; weightNum++, outputWeight++ ) { + const vertexWeight_t &source = tempWeights[first + weightNum]; + weights[outputWeight].weight = source.jointWeight; + weights[outputWeight].jointMatOffset = source.joint * sizeof( idJointMat ); + weights[outputWeight].nextVertexOffset = ( count - weightNum ) * sizeof( jointWeight_t ); + scaledBaseVectors[outputWeight].ToVec3() = source.offset * source.jointWeight; + scaledBaseVectors[outputWeight].w = source.jointWeight; + } + } + + parser.ExpectTokenString( "}" ); + + idDrawVert *initialVerts = (idDrawVert *)_alloca16( numVerts * sizeof( idDrawVert ) ); + for ( int i = 0; i < numVerts; i++ ) { + initialVerts[i].Clear(); + initialVerts[i].st = texCoords[i]; + } + idBounds initialBounds; + SIMDProcessor->TransformVertsNew( initialVerts, numVerts, initialBounds, joints, scaledBaseVectors, weights, numWeights ); + deformInfo = R_BuildDeformInfo( numVerts, initialVerts, tris.Num(), tris.Ptr(), shader->UseUnsmoothedTangents() ); + currentTime = 0.0f; + + int extraWeights = 0; + for ( int i = 0; i < deformInfo->numMirroredVerts; i++ ) { + extraWeights += numWeightsForVertex[deformInfo->mirroredVerts[i]]; + } + if ( extraWeights > 0 ) { + idVec4 *newScaledBaseVectors = (idVec4 *)Mem_Alloc16( ( numWeights + extraWeights ) * sizeof( idVec4 ) ); + jointWeight_t *newWeights = (jointWeight_t *)Mem_Alloc16( ( numWeights + extraWeights ) * sizeof( jointWeight_t ) ); + memcpy( newScaledBaseVectors, scaledBaseVectors, numWeights * sizeof( idVec4 ) ); + memcpy( newWeights, weights, numWeights * sizeof( jointWeight_t ) ); + Mem_Free16( scaledBaseVectors ); + Mem_Free16( weights ); + scaledBaseVectors = newScaledBaseVectors; + weights = newWeights; + + int appendWeight = numWeights; + for ( int i = 0; i < deformInfo->numMirroredVerts; i++ ) { + const int sourceVert = deformInfo->mirroredVerts[i]; + const int first = firstWeightForVertex[sourceVert]; + const int count = numWeightsForVertex[sourceVert]; + for ( int weightNum = 0; weightNum < count; weightNum++, appendWeight++ ) { + const vertexWeight_t &source = tempWeights[first + weightNum]; + weights[appendWeight].weight = source.jointWeight; + weights[appendWeight].jointMatOffset = source.joint * sizeof( idJointMat ); + weights[appendWeight].nextVertexOffset = ( count - weightNum ) * sizeof( jointWeight_t ); + scaledBaseVectors[appendWeight].ToVec3() = source.offset * source.jointWeight; + scaledBaseVectors[appendWeight].w = source.jointWeight; + } + } + } + + baseVectors = (idVec4 *)Mem_Alloc16( deformInfo->numOutputVerts * 4 * sizeof( idVec4 ) ); + modelSurface_t baseSurface; + memset( &baseSurface, 0, sizeof( baseSurface ) ); + UpdateSurface( NULL, joints, &baseSurface, false ); + R_DeriveTangents( baseSurface.geometry, true ); + for ( int i = 0; i < deformInfo->numOutputVerts; i++ ) { + const idDrawVert &vert = baseSurface.geometry->verts[i]; + baseVectors[i * 4 + 0].Set( vert.xyz.x, vert.xyz.y, vert.xyz.z, 1.0f ); + baseVectors[i * 4 + 1].Set( vert.normal.x, vert.normal.y, vert.normal.z, 0.0f ); + baseVectors[i * 4 + 2].Set( vert.tangents[0].x, vert.tangents[0].y, vert.tangents[0].z, 0.0f ); + baseVectors[i * 4 + 3].Set( vert.tangents[1].x, vert.tangents[1].y, vert.tangents[1].z, 0.0f ); + } + R_FreeStaticTriSurf( baseSurface.geometry ); + + c_numVerts += numVerts; + c_numWeights += numWeights; + c_numWeightJoints += numWeights; +} + +void idMD5Mesh::UpdateSurface( const renderEntity_t *ent, const idJointMat *entJoints, modelSurface_t *surf, bool calculateTangents ) { + tr.pc.c_deformedSurfaces++; + tr.pc.c_deformedVerts += deformInfo->numOutputVerts; + tr.pc.c_deformedIndexes += deformInfo->numIndexes; + surf->shader = shader; + + if ( surf->geometry != NULL ) { + if ( surf->geometry->numVerts == deformInfo->numOutputVerts && surf->geometry->numIndexes == deformInfo->numIndexes ) { + R_FreeStaticTriSurfVertexCaches( surf->geometry ); + } else { + R_FreeStaticTriSurf( surf->geometry ); + surf->geometry = NULL; + } + } + if ( surf->geometry == NULL ) { + surf->geometry = R_AllocStaticTriSurf(); + } + + srfTriangles_t *tri = surf->geometry; + tri->deformedSurface = true; + tri->tangentsCalculated = false; + tri->facePlanesCalculated = false; + tri->numIndexes = deformInfo->numIndexes; + tri->indexes = deformInfo->indexes; + tri->silIndexes = deformInfo->silIndexes; + tri->numMirroredVerts = deformInfo->numMirroredVerts; + tri->mirroredVerts = deformInfo->mirroredVerts; + tri->numDupVerts = deformInfo->numDupVerts; + tri->dupVerts = deformInfo->dupVerts; + tri->numSilEdges = deformInfo->numSilEdges; + tri->silEdges = deformInfo->silEdges; + tri->dominantTris = deformInfo->dominantTris; + tri->numVerts = deformInfo->numOutputVerts; + + if ( tri->verts == NULL ) { + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( int i = 0; i < deformInfo->numSourceVerts; i++ ) { + tri->verts[i].Clear(); + tri->verts[i].st = texCoords[i]; + } + const int mirrorBase = deformInfo->numOutputVerts - deformInfo->numMirroredVerts; + for ( int i = 0; i < deformInfo->numMirroredVerts; i++ ) { + tri->verts[mirrorBase + i] = tri->verts[deformInfo->mirroredVerts[i]]; + } + } + + if ( r_useNewSkinning.GetBool() && calculateTangents ) { + if ( r_useFastSkinning.GetBool() ) { + SIMDProcessor->TransformVertsAndTangentsFast( tri->verts, tri->numVerts, tri->bounds, entJoints, baseVectors, weights, numWeights ); + } else { + SIMDProcessor->TransformVertsAndTangents( tri->verts, tri->numVerts, tri->bounds, entJoints, baseVectors, weights, numWeights ); + } + tri->tangentsCalculated = true; + } else { + SIMDProcessor->TransformVertsNew( tri->verts, tri->numVerts, tri->bounds, entJoints, scaledBaseVectors, weights, numWeights ); + } + + if ( r_deriveBiTangents.GetBool() ) { + for ( int i = 0; i < tri->numVerts; i++ ) { + idVec3 bitangent = tri->verts[i].normal.Cross( tri->verts[i].tangents[0] ); + if ( bitangent * tri->verts[i].tangents[1] < 0.0f ) { + bitangent = -bitangent; + } + tri->verts[i].tangents[1] = bitangent; + } + } + + if ( !tri->tangentsCalculated && !r_useDeferredTangents.GetBool() ) { + R_DeriveTangents( tri, true ); + } +} + +idBounds idMD5Mesh::CalcBounds( const idJointMat *entJoints ) { + idBounds result; + idDrawVert *verts = (idDrawVert *)_alloca16( deformInfo->numOutputVerts * sizeof( idDrawVert ) ); + SIMDProcessor->TransformVertsNew( verts, deformInfo->numOutputVerts, result, entJoints, scaledBaseVectors, weights, numWeights ); + return result; +} + +int idMD5Mesh::NearestJoint( int a, int b, int c ) const { + int vertNum = a; + if ( vertNum < 0 || vertNum >= texCoords.Num() ) { + vertNum = b; + } + if ( vertNum < 0 || vertNum >= texCoords.Num() ) { + vertNum = c; + } + if ( vertNum < 0 || vertNum >= texCoords.Num() ) { + return 0; + } + int weightNum = 0; + for ( int i = 0; i < vertNum; i++ ) { + weightNum += weights[weightNum].nextVertexOffset / sizeof( jointWeight_t ); + } + return weights[weightNum].jointMatOffset / sizeof( idJointMat ); +} + +int idMD5Mesh::NumVerts() const { return texCoords.Num(); } +int idMD5Mesh::NumTris() const { return numTris; } +int idMD5Mesh::NumWeights() const { return numWeights; } + +/*********************************************************************** + + idRenderModelMD5 + +***********************************************************************/ + +/* +==================== +idRenderModelMD5::ParseJoint +==================== +*/ +void idRenderModelMD5::ParseJoint( idLexer &parser, idMD5Joint *joint, idJointQuat *defaultPose ) { + idToken token; + int num; + + // + // parse name + // + parser.ReadToken( &token ); + joint->name = token; + + // + // parse parent + // + num = parser.ParseInt(); + if ( num < 0 ) { + joint->parent = NULL; + } else { + if ( num >= joints.Num() - 1 ) { + parser.Error( "Invalid parent for joint '%s'", joint->name.c_str() ); + } + joint->parent = &joints[ num ]; + } + + // + // parse default pose + // + parser.Parse1DMatrix( 3, defaultPose->t.ToFloatPtr() ); + parser.Parse1DMatrix( 3, defaultPose->q.ToFloatPtr() ); + defaultPose->q.w = defaultPose->q.CalcW(); +} + +idRenderModelMD5::idRenderModelMD5() { + defaultPose = NULL; + skinSpaceToLocalMats = NULL; + viewEnt = NULL; +} + +idRenderModelMD5::~idRenderModelMD5() { + Mem_Free16( defaultPose ); + Mem_Free16( skinSpaceToLocalMats ); + defaultPose = NULL; + skinSpaceToLocalMats = NULL; +} + +/* +==================== +idRenderModelMD5::InitFromFile +==================== +*/ +void idRenderModelMD5::InitFromFile( const char *fileName ) { + name = fileName; + LoadModel(); +} + +/* +==================== +idRenderModelMD5::LoadModel + +used for initial loads, reloadModel, and reloading the data of purged models +Upon exit, the model will absolutely be valid, but possibly as a default model +==================== +*/ +void idRenderModelMD5::LoadModel() { + int version; + int i; + int num; + int parentNum; + idToken token; + idLexer parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS ); + idJointQuat *pose; + idMD5Joint *joint; + idJointMat *poseMat3; + + if ( !purged ) { + PurgeModel(); + } + purged = false; + + if ( !parser.LoadFile( name ) ) { + MakeDefaultModel(); + return; + } + + parser.ExpectTokenString( MD5_VERSION_STRING ); + version = parser.ParseInt(); + + if ( version != MD5_VERSION ) { + parser.Error( "Invalid version %d. Should be version %d\n", version, MD5_VERSION ); + } + + // + // skip commandline + // + parser.ExpectTokenString( "commandline" ); + parser.ReadToken( &token ); + + // parse num joints + parser.ExpectTokenString( "numJoints" ); + num = parser.ParseInt(); + joints.SetGranularity( 1 ); + joints.SetNum( num ); + defaultPose = ( idJointQuat * )Mem_Alloc16( num * sizeof( *defaultPose ) ); + skinSpaceToLocalMats = ( idJointMat * )Mem_Alloc16( num * sizeof( *skinSpaceToLocalMats ) ); + poseMat3 = skinSpaceToLocalMats; + + // parse num meshes + parser.ExpectTokenString( "numMeshes" ); + num = parser.ParseInt(); + if ( num < 0 ) { + parser.Error( "Invalid size: %d", num ); + } + meshes.SetGranularity( 1 ); + meshes.SetNum( num ); + + // + // parse joints + // + parser.ExpectTokenString( "joints" ); + parser.ExpectTokenString( "{" ); + pose = defaultPose; + joint = joints.Ptr(); + for( i = 0; i < joints.Num(); i++, joint++, pose++ ) { + ParseJoint( parser, joint, pose ); + poseMat3[ i ].SetRotation( pose->q.ToMat3() ); + poseMat3[ i ].SetTranslation( pose->t ); + if ( joint->parent ) { + parentNum = joint->parent - joints.Ptr(); + pose->q = ( poseMat3[ i ].ToMat3() * poseMat3[ parentNum ].ToMat3().Transpose() ).ToQuat(); + pose->t = ( poseMat3[ i ].ToVec3() - poseMat3[ parentNum ].ToVec3() ) * poseMat3[ parentNum ].ToMat3().Transpose(); + } + } + parser.ExpectTokenString( "}" ); + + for( i = 0; i < meshes.Num(); i++ ) { + parser.ExpectTokenString( "mesh" ); + meshes[ i ].ParseMesh( parser, joints.Num(), poseMat3 ); + } + + // + // calculate the bounds of the model + // + CalculateBounds( poseMat3 ); + for ( i = 0; i < joints.Num(); i++ ) { + skinSpaceToLocalMats[i].Invert(); + } + viewEnt = NULL; + + // set the timestamp for reloadmodels + fileSystem->ReadFile( name, NULL, &timeStamp ); +} + +/* +============== +idRenderModelMD5::Print +============== +*/ +void idRenderModelMD5::Print() const { + const idMD5Mesh *mesh; + int i; + + common->Printf( "%s\n", name.c_str() ); + common->Printf( "Dynamic model.\n" ); + common->Printf( "Generated smooth normals.\n" ); + common->Printf( " verts tris weights material\n" ); + int totalVerts = 0; + int totalTris = 0; + int totalWeights = 0; + for( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ ) { + totalVerts += mesh->NumVerts(); + totalTris += mesh->NumTris(); + totalWeights += mesh->NumWeights(); + common->Printf( "%2i: %5i %5i %7i %s\n", i, mesh->NumVerts(), mesh->NumTris(), mesh->NumWeights(), mesh->shader->GetName() ); + } + common->Printf( "-----\n" ); + common->Printf( "%4i verts.\n", totalVerts ); + common->Printf( "%4i tris.\n", totalTris ); + common->Printf( "%4i weights.\n", totalWeights ); + common->Printf( "%4i joints.\n", joints.Num() ); +} + +/* +============== +idRenderModelMD5::List +============== +*/ +void idRenderModelMD5::List() const { + int i; + const idMD5Mesh *mesh; + int totalTris = 0; + int totalVerts = 0; + + for( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ ) { + totalTris += mesh->numTris; + totalVerts += mesh->NumVerts(); + } + common->Printf( " %4ik %3i %4i %4i %s(MD5)", Memory()/1024, meshes.Num(), totalVerts, totalTris, Name() ); + + if ( defaulted ) { + common->Printf( " (DEFAULTED)" ); + } + + common->Printf( "\n" ); +} + +/* +==================== +idRenderModelMD5::CalculateBounds +==================== +*/ +void idRenderModelMD5::CalculateBounds( const idJointMat *entJoints ) { + int i; + idMD5Mesh *mesh; + + bounds.Clear(); + for( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ ) { + bounds.AddBounds( mesh->CalcBounds( entJoints ) ); + } +} + +/* +==================== +idRenderModelMD5::Bounds + +This calculates a rough bounds by using the joint radii without +transforming all the points +==================== +*/ +idBounds idRenderModelMD5::Bounds( const renderEntity_t *ent ) const { +#if 0 + // we can't calculate a rational bounds without an entity, + // because joints could be positioned to deform it into an + // arbitrarily large shape + if ( !ent ) { + common->Error( "idRenderModelMD5::Bounds: called without entity" ); + } +#endif + + if ( !ent ) { + // this is the bounds for the reference pose + return bounds; + } + + return ent->bounds; +} + +/* +==================== +idRenderModelMD5::DrawJoints +==================== +*/ +void idRenderModelMD5::DrawJoints( const renderEntity_t *ent, const struct viewDef_s *view, bool drawBones ) const { + int i; + int num; + idVec3 pos; + const idJointMat *joint; + const idMD5Joint *md5Joint; + int parentNum; + + num = ent->numJoints; + joint = ent->joints; + md5Joint = joints.Ptr(); + for( i = 0; i < num; i++, joint++, md5Joint++ ) { + pos = ent->origin + joint->ToVec3() * ent->axis; + if ( drawBones && md5Joint->parent ) { + parentNum = md5Joint->parent - joints.Ptr(); + session->rw->DebugLine( colorWhite, ent->origin + ent->joints[ parentNum ].ToVec3() * ent->axis, pos ); + } + + if ( drawBones ) { + session->rw->DebugLine( colorRed, pos, pos + joint->ToMat3()[ 0 ] * 2.0f * ent->axis ); + session->rw->DebugLine( colorGreen, pos, pos + joint->ToMat3()[ 1 ] * 2.0f * ent->axis ); + session->rw->DebugLine( colorBlue, pos, pos + joint->ToMat3()[ 2 ] * 2.0f * ent->axis ); + } + } + + idBounds bounds; + + bounds.FromTransformedBounds( ent->bounds, vec3_zero, ent->axis ); + session->rw->DebugBounds( colorMagenta, bounds, ent->origin ); + + if ( ( r_jointNameScale.GetFloat() != 0.0f ) && ( bounds.Expand( 128.0f ).ContainsPoint( view->renderView.vieworg - ent->origin ) ) ) { + idVec3 offset( 0, 0, r_jointNameOffset.GetFloat() ); + float scale; + + scale = r_jointNameScale.GetFloat(); + joint = ent->joints; + num = ent->numJoints; + for( i = 0; i < num; i++, joint++ ) { + pos = ent->origin + joint->ToVec3() * ent->axis; + session->rw->DrawText( joints[ i ].name, pos + offset, scale, colorWhite, view->renderView.viewaxis, 1 ); + } + } +} + +/* +==================== +idRenderModelMD5::InstantiateDynamicModel +==================== +*/ +idRenderModel *idRenderModelMD5::InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel, dword surfMask ) { + int i, surfaceNum; + idMD5Mesh *mesh; + idRenderModelStatic *staticModel; + + if ( cachedModel && !r_useCachedDynamicModels.GetBool() ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( purged ) { + common->DWarning( "model %s instantiated while purged", Name() ); + LoadModel(); + } + + if ( !ent->joints ) { + common->Printf( "idRenderModelMD5::InstantiateDynamicModel: NULL joints on renderEntity for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } else if ( ent->numJoints != joints.Num() ) { + common->Printf( "idRenderModelMD5::InstantiateDynamicModel: renderEntity has different number of joints than model for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } + + tr.pc.c_generateMd5++; + + if ( cachedModel ) { + assert( dynamic_cast(cachedModel) != NULL ); + assert( idStr::Icmp( cachedModel->Name(), MD5_SnapshotName ) == 0 ); + staticModel = static_cast(cachedModel); + } else { + staticModel = new idRenderModelStatic; + staticModel->InitEmpty( MD5_SnapshotName ); + } + + staticModel->bounds.Clear(); + + if ( r_showSkel.GetInteger() ) { + if ( ( view != NULL ) && ( !r_skipSuppress.GetBool() || !ent->suppressSurfaceInViewID || ( ent->suppressSurfaceInViewID != view->renderView.viewID ) ) ) { + // only draw the skeleton + DrawJoints( ent, view, r_showSkel.GetInteger() < 2 ); + } + + if ( r_showSkel.GetInteger() > 1 ) { + // turn off the model when showing the skeleton + staticModel->InitEmpty( MD5_SnapshotName ); + return staticModel; + } + } + + const idJointMat *entJoints = ent->joints; + if ( r_useNewSkinning.GetBool() && ( surfMask & SURF_COLLISION ) == 0 ) { + idJointMat *transformedJoints = (idJointMat *)_alloca16( joints.Num() * sizeof( idJointMat ) ); + SIMDProcessor->MultiplyJoints( transformedJoints, ent->joints, skinSpaceToLocalMats, joints.Num() ); + entJoints = transformedJoints; + } + + // create all the surfaces + for( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ ) { + // avoid deforming the surface if it will be a nodraw due to a skin remapping + // FIXME: may have to still deform clipping hulls + const idMaterial *shader = mesh->shader; + + shader = R_RemapShaderBySkin( shader, ent->customSkin, ent->customShader ); + + if ( !shader || ( !shader->IsDrawn() && !shader->SurfaceCastsShadow() ) ) { + staticModel->DeleteSurfaceWithId( i ); + mesh->surfaceNum = -1; + continue; + } + + modelSurface_t *surf; + + if ( staticModel->FindSurfaceWithId( i, surfaceNum ) ) { + mesh->surfaceNum = surfaceNum; + surf = &staticModel->surfaces[surfaceNum]; + } else { + + // Remove Overlays before adding new surfaces + idRenderModelOverlay::RemoveOverlaySurfacesFromModel( staticModel ); + + mesh->surfaceNum = staticModel->NumSurfaces(); + surf = &staticModel->surfaces.Alloc(); + surf->geometry = NULL; + surf->shader = NULL; + surf->id = i; + } + + mesh->UpdateSurface( ent, entJoints, surf, ( surfMask & SURF_COLLISION ) == 0 ); + + staticModel->bounds.AddPoint( surf->geometry->bounds[0] ); + staticModel->bounds.AddPoint( surf->geometry->bounds[1] ); + } + + return staticModel; +} + +/* +==================== +idRenderModelMD5::IsDynamicModel +==================== +*/ +dynamicModel_t idRenderModelMD5::IsDynamicModel() const { + return DM_CACHED; +} + +/* +==================== +idRenderModelMD5::NumJoints +==================== +*/ +int idRenderModelMD5::NumJoints( void ) const { + return joints.Num(); +} + +/* +==================== +idRenderModelMD5::GetJoints +==================== +*/ +const idMD5Joint *idRenderModelMD5::GetJoints( void ) const { + return joints.Ptr(); +} + +/* +==================== +idRenderModelMD5::GetDefaultPose +==================== +*/ +const idJointQuat *idRenderModelMD5::GetDefaultPose( void ) const { + return defaultPose; +} + +const idJointMat *idRenderModelMD5::GetSkinSpaceToLocalMats( void ) const { + return skinSpaceToLocalMats; +} + +/* +==================== +idRenderModelMD5::GetJointHandle +==================== +*/ +jointHandle_t idRenderModelMD5::GetJointHandle( const char *name ) const { + const idMD5Joint *joint; + int i; + + joint = joints.Ptr(); + for( i = 0; i < joints.Num(); i++, joint++ ) { + if ( idStr::Icmp( joint->name.c_str(), name ) == 0 ) { + return ( jointHandle_t )i; + } + } + + return INVALID_JOINT; +} + +/* +===================== +idRenderModelMD5::GetJointName +===================== +*/ +const char *idRenderModelMD5::GetJointName( jointHandle_t handle ) const { + if ( ( handle < 0 ) || ( handle >= joints.Num() ) ) { + return ""; + } + + return joints[ handle ].name; +} + +/* +==================== +idRenderModelMD5::NearestJoint +==================== +*/ +int idRenderModelMD5::NearestJoint( int surfaceNum, int a, int b, int c ) const { + int i; + const idMD5Mesh *mesh; + + if ( surfaceNum > meshes.Num() ) { + common->Error( "idRenderModelMD5::NearestJoint: surfaceNum > meshes.Num()" ); + } + + for ( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ ) { + if ( mesh->surfaceNum == surfaceNum ) { + return mesh->NearestJoint( a, b, c ); + } + } + return 0; +} + +/* +==================== +idRenderModelMD5::TouchData + +models that are already loaded at level start time +will still touch their materials to make sure they +are kept loaded +==================== +*/ +void idRenderModelMD5::TouchData() { + idMD5Mesh *mesh; + int i; + + for( mesh = meshes.Ptr(), i = 0; i < meshes.Num(); i++, mesh++ ) { + declManager->FindMaterial( mesh->shader->GetName() ); + } +} + +bool idRenderModelMD5::HasCollisionSurface( const renderEntity_t *ent ) const { + for ( int i = 0; i < meshes.Num(); i++ ) { + const idMaterial *material = R_RemapShaderBySkin( meshes[i].shader, ent->customSkin, ent->customShader ); + if ( material != NULL && ( material->GetSurfaceFlags() & SURF_COLLISION ) != 0 ) { + return true; + } + } + return false; +} + +int idRenderModelMD5::GetSurfaceMask( const char *surface ) const { + int mask = 0; + for ( int i = meshes.Num() - 1; i >= 0; i-- ) { + if ( idStr::Icmp( meshes[i].shader->GetName(), surface ) == 0 ) { + mask |= 1 << i; + } + } + return mask; +} + +void idRenderModelMD5::SetViewEntity( const viewEntity_t *ve ) { + viewEnt = ve; +} + +/* +=================== +idRenderModelMD5::PurgeModel + +frees all the data, but leaves the class around for dangling references, +which can regenerate the data with LoadModel() +=================== +*/ +void idRenderModelMD5::PurgeModel() { + purged = true; + joints.Clear(); + meshes.Clear(); + Mem_Free16( defaultPose ); + Mem_Free16( skinSpaceToLocalMats ); + defaultPose = NULL; + skinSpaceToLocalMats = NULL; + viewEnt = NULL; +} + +/* +=================== +idRenderModelMD5::Memory +=================== +*/ +int idRenderModelMD5::Memory() const { + int total, i; + + total = sizeof( *this ); + total += joints.MemoryUsed() + meshes.MemoryUsed(); + total += joints.Num() * ( sizeof( idJointQuat ) + sizeof( idJointMat ) ); + + // count up strings + for ( i = 0; i < joints.Num(); i++ ) { + total += joints[i].name.DynamicMemoryUsed(); + } + + // count up meshes + for ( i = 0 ; i < meshes.Num() ; i++ ) { + const idMD5Mesh *mesh = &meshes[i]; + + total += mesh->texCoords.MemoryUsed() + mesh->numWeights * ( sizeof( mesh->scaledBaseVectors[0] ) + sizeof( mesh->weights[0] ) ); + total += mesh->deformInfo->numOutputVerts * 4 * sizeof( idVec4 ); + + // sum up deform info + total += sizeof( mesh->deformInfo ); + total += R_DeformInfoMemoryUsed( mesh->deformInfo ); + } + return total; +} diff --git a/src/renderer/model_sprite.cpp b/src/renderer/model_sprite.cpp new file mode 100644 index 0000000..62e00e9 --- /dev/null +++ b/src/renderer/model_sprite.cpp @@ -0,0 +1,197 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Model_local.h" + + +/* + +A simple sprite model that always faces the view axis. + +*/ + +static const char *sprite_SnapshotName = "_sprite_Snapshot_"; + +/* +=============== +idRenderModelBeam::IsDynamicModel +=============== +*/ +dynamicModel_t idRenderModelSprite::IsDynamicModel() const { + return DM_CONTINUOUS; +} + +/* +=============== +idRenderModelBeam::IsLoaded +=============== +*/ +bool idRenderModelSprite::IsLoaded() const { + return true; +} + +/* +=============== +idRenderModelSprite::InstantiateDynamicModel +=============== +*/ +idRenderModel * idRenderModelSprite::InstantiateDynamicModel( const struct renderEntity_s *renderEntity, const struct viewDef_s *viewDef, idRenderModel *cachedModel, dword surfMask ) { + (void)surfMask; + idRenderModelStatic *staticModel; + srfTriangles_t *tri; + modelSurface_t surf; + + if ( cachedModel && !r_useCachedDynamicModels.GetBool() ) { + delete cachedModel; + cachedModel = NULL; + } + + if ( renderEntity == NULL || viewDef == NULL ) { + delete cachedModel; + return NULL; + } + + if ( cachedModel != NULL ) { + + assert( dynamic_cast( cachedModel ) != NULL ); + assert( idStr::Icmp( cachedModel->Name(), sprite_SnapshotName ) == 0 ); + + staticModel = static_cast( cachedModel ); + surf = *staticModel->Surface( 0 ); + tri = surf.geometry; + + } else { + + // Keep the transient snapshot and its surface allocations in the same + // renderer heap as the source model, as in Raven's NewRenderModel path. + staticModel = NewRenderModel( this ); + staticModel->InitEmpty( sprite_SnapshotName ); + + tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, 4 ); + R_AllocStaticTriSurfIndexes( tri, 6 ); + + tri->verts[ 0 ].Clear(); + tri->verts[ 0 ].normal.Set( 1.0f, 0.0f, 0.0f ); + tri->verts[ 0 ].tangents[0].Set( 0.0f, 1.0f, 0.0f ); + tri->verts[ 0 ].tangents[1].Set( 0.0f, 0.0f, 1.0f ); + tri->verts[ 0 ].st[ 0 ] = 0.0f; + tri->verts[ 0 ].st[ 1 ] = 0.0f; + + tri->verts[ 1 ].Clear(); + tri->verts[ 1 ].normal.Set( 1.0f, 0.0f, 0.0f ); + tri->verts[ 1 ].tangents[0].Set( 0.0f, 1.0f, 0.0f ); + tri->verts[ 1 ].tangents[1].Set( 0.0f, 0.0f, 1.0f ); + tri->verts[ 1 ].st[ 0 ] = 1.0f; + tri->verts[ 1 ].st[ 1 ] = 0.0f; + + tri->verts[ 2 ].Clear(); + tri->verts[ 2 ].normal.Set( 1.0f, 0.0f, 0.0f ); + tri->verts[ 2 ].tangents[0].Set( 0.0f, 1.0f, 0.0f ); + tri->verts[ 2 ].tangents[1].Set( 0.0f, 0.0f, 1.0f ); + tri->verts[ 2 ].st[ 0 ] = 1.0f; + tri->verts[ 2 ].st[ 1 ] = 1.0f; + + tri->verts[ 3 ].Clear(); + tri->verts[ 3 ].normal.Set( 1.0f, 0.0f, 0.0f ); + tri->verts[ 3 ].tangents[0].Set( 0.0f, 1.0f, 0.0f ); + tri->verts[ 3 ].tangents[1].Set( 0.0f, 0.0f, 1.0f ); + tri->verts[ 3 ].st[ 0 ] = 0.0f; + tri->verts[ 3 ].st[ 1 ] = 1.0f; + + tri->indexes[ 0 ] = 0; + tri->indexes[ 1 ] = 1; + tri->indexes[ 2 ] = 3; + tri->indexes[ 3 ] = 1; + tri->indexes[ 4 ] = 2; + tri->indexes[ 5 ] = 3; + + tri->numVerts = 4; + tri->numIndexes = 6; + + surf.geometry = tri; + surf.id = 0; + surf.shader = tr.defaultMaterial; + staticModel->AddSurface( surf ); + } + + int red = idMath::FtoiFast( renderEntity->shaderParms[ SHADERPARM_RED ] * 255.0f ); + int green = idMath::FtoiFast( renderEntity->shaderParms[ SHADERPARM_GREEN ] * 255.0f ); + int blue = idMath::FtoiFast( renderEntity->shaderParms[ SHADERPARM_BLUE ] * 255.0f ); + int alpha = idMath::FtoiFast( renderEntity->shaderParms[ SHADERPARM_ALPHA ] * 255.0f ); + + idVec3 right = idVec3( 0.0f, renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ] * 0.5f, 0.0f ); + idVec3 up = idVec3( 0.0f, 0.0f, renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] * 0.5f ); + + tri->verts[ 0 ].xyz = up + right; + tri->verts[ 0 ].color[ 0 ] = red; + tri->verts[ 0 ].color[ 1 ] = green; + tri->verts[ 0 ].color[ 2 ] = blue; + tri->verts[ 0 ].color[ 3 ] = alpha; + + tri->verts[ 1 ].xyz = up - right; + tri->verts[ 1 ].color[ 0 ] = red; + tri->verts[ 1 ].color[ 1 ] = green; + tri->verts[ 1 ].color[ 2 ] = blue; + tri->verts[ 1 ].color[ 3 ] = alpha; + + tri->verts[ 2 ].xyz = - right - up; + tri->verts[ 2 ].color[ 0 ] = red; + tri->verts[ 2 ].color[ 1 ] = green; + tri->verts[ 2 ].color[ 2 ] = blue; + tri->verts[ 2 ].color[ 3 ] = alpha; + + tri->verts[ 3 ].xyz = right - up; + tri->verts[ 3 ].color[ 0 ] = red; + tri->verts[ 3 ].color[ 1 ] = green; + tri->verts[ 3 ].color[ 2 ] = blue; + tri->verts[ 3 ].color[ 3 ] = alpha; + + R_BoundTriSurf( tri ); + + staticModel->bounds = tri->bounds; + + return staticModel; +} + +/* +=============== +idRenderModelSprite::Bounds +=============== +*/ +idBounds idRenderModelSprite::Bounds( const struct renderEntity_s *renderEntity ) const { + idBounds b; + + b.Zero(); + if ( renderEntity == NULL ) { + b.ExpandSelf( 8.0f ); + } else { + b.ExpandSelf( Max( renderEntity->shaderParms[ SHADERPARM_SPRITE_WIDTH ], renderEntity->shaderParms[ SHADERPARM_SPRITE_HEIGHT ] ) * 0.5f ); + } + return b; +} diff --git a/src/renderer/modeldecal.cpp b/src/renderer/modeldecal.cpp new file mode 100644 index 0000000..a025c13 --- /dev/null +++ b/src/renderer/modeldecal.cpp @@ -0,0 +1,504 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "Model_local.h" +#include "rvMesh.h" + +idCVar r_skipDecals( "r_skipDecals", "0", CVAR_RENDERER | CVAR_BOOL, "ignore the decals" ); +int idRenderModelDecal::allocatedDecalCount; +int AddCount; + +/* +================== +idRenderModelDecal::idRenderModelDecal +================== +*/ +idRenderModelDecal *idRenderModelDecal::Alloc( void ) { + allocatedDecalCount++; + idRenderModelDecal *decal = (idRenderModelDecal *)Memory::Allocate( sizeof( idRenderModelDecal ) ); + if ( decal != NULL ) { + memset( &decal->tri, 0, sizeof( decal->tri ) ); + decal->tri.verts = decal->verts; + decal->tri.indexes = decal->indexes; + decal->material = NULL; + decal->nextDecal = NULL; + } + return decal; +} + +/* +================== +idRenderModelDecal::idRenderModelDecal +================== +*/ +void idRenderModelDecal::Free( idRenderModelDecal *decal ) { + allocatedDecalCount--; + if ( decal != NULL ) { + Memory::Free( decal ); + } +} + +/* +================= +idRenderModelDecal::CreateProjectionInfo +================= +*/ +bool idRenderModelDecal::CreateProjectionInfo( decalProjectionInfo_t &info, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { + + if ( winding.GetNumPoints() != NUM_DECAL_BOUNDING_PLANES - 2 ) { + common->Printf( "idRenderModelDecal::CreateProjectionInfo: winding must have %d points\n", NUM_DECAL_BOUNDING_PLANES - 2 ); + return false; + } + + assert( material != NULL ); + + info.projectionOrigin = projectionOrigin; + info.material = material; + info.parallel = parallel; + info.fadeDepth = fadeDepth; + info.startTime = startTime; + info.maxAngle = material->GetDecalInfo().maxAngle; + info.force = false; + + // get the winding plane and the depth of the projection volume + idPlane windingPlane; + winding.GetPlane( windingPlane ); + float depth = windingPlane.Distance( projectionOrigin ); + + // find the bounds for the projection + winding.GetBounds( info.projectionBounds ); + if ( parallel ) { + const idVec3 transToProj = windingPlane.Normal() * depth; + const idVec3 transToFade = windingPlane.Normal() * fadeDepth; + for ( int i = 0; i < winding.GetNumPoints(); i++ ) { + info.projectionBounds.AddPoint( winding[i].ToVec3() + transToProj ); + info.projectionBounds.AddPoint( winding[i].ToVec3() - transToFade ); + } + } else { + info.projectionBounds.AddPoint( projectionOrigin ); + } + + // calculate the world space projection volume bounding planes, positive sides face outside the decal + if ( parallel ) { + for ( int i = 0; i < winding.GetNumPoints(); i++ ) { + idVec3 edge = winding[(i+1)%winding.GetNumPoints()].ToVec3() - winding[i].ToVec3(); + info.boundingPlanes[i].Normal().Cross( windingPlane.Normal(), edge ); + info.boundingPlanes[i].Normalize(); + info.boundingPlanes[i].FitThroughPoint( winding[i].ToVec3() ); + } + } else { + for ( int i = 0; i < winding.GetNumPoints(); i++ ) { + info.boundingPlanes[i].FromPoints( projectionOrigin, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3() ); + } + } + info.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 2] = windingPlane; + info.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 2][3] -= depth; + info.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 1] = -windingPlane; + + // fades will be from these plane + info.fadePlanes[0] = windingPlane; + info.fadePlanes[0][3] -= fadeDepth; + info.fadePlanes[1] = -windingPlane; + info.fadePlanes[1][3] += depth - fadeDepth; + + // calculate the texture vectors for the winding + float len, texArea, inva; + idVec3 temp; + idVec5 d0, d1; + + const idVec5 &a = winding[0]; + const idVec5 &b = winding[1]; + const idVec5 &c = winding[2]; + + d0 = b.ToVec3() - a.ToVec3(); + d0.s = b.s - a.s; + d0.t = b.t - a.t; + d1 = c.ToVec3() - a.ToVec3(); + d1.s = c.s - a.s; + d1.t = c.t - a.t; + + texArea = ( d0[3] * d1[4] ) - ( d0[4] * d1[3] ); + inva = 1.0f / texArea; + + temp[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ) * inva; + temp[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ) * inva; + temp[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ) * inva; + len = temp.Normalize(); + info.textureAxis[0].Normal() = temp * ( 1.0f / len ); + info.textureAxis[0][3] = winding[0].s - ( winding[0].ToVec3() * info.textureAxis[0].Normal() ); + + temp[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ) * inva; + temp[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ) * inva; + temp[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ) * inva; + len = temp.Normalize(); + info.textureAxis[1].Normal() = temp * ( 1.0f / len ); + info.textureAxis[1][3] = winding[0].t - ( winding[0].ToVec3() * info.textureAxis[1].Normal() ); + + return true; +} + +/* +================= +idRenderModelDecal::CreateProjectionInfo +================= +*/ +void idRenderModelDecal::GlobalProjectionInfoToLocal( decalProjectionInfo_t &localInfo, const decalProjectionInfo_t &info, const idVec3 &origin, const idMat3 &axis ) { + float modelMatrix[16]; + + R_AxisToModelMatrix( axis, origin, modelMatrix ); + + for ( int j = 0; j < NUM_DECAL_BOUNDING_PLANES; j++ ) { + R_GlobalPlaneToLocal( modelMatrix, info.boundingPlanes[j], localInfo.boundingPlanes[j] ); + } + R_GlobalPlaneToLocal( modelMatrix, info.fadePlanes[0], localInfo.fadePlanes[0] ); + R_GlobalPlaneToLocal( modelMatrix, info.fadePlanes[1], localInfo.fadePlanes[1] ); + R_GlobalPlaneToLocal( modelMatrix, info.textureAxis[0], localInfo.textureAxis[0] ); + R_GlobalPlaneToLocal( modelMatrix, info.textureAxis[1], localInfo.textureAxis[1] ); + R_GlobalPointToLocal( modelMatrix, info.projectionOrigin, localInfo.projectionOrigin ); + localInfo.projectionBounds = info.projectionBounds; + localInfo.projectionBounds.TranslateSelf( -origin ); + localInfo.projectionBounds.RotateSelf( axis.Transpose() ); + localInfo.material = info.material; + localInfo.parallel = info.parallel; + localInfo.fadeDepth = info.fadeDepth; + localInfo.startTime = info.startTime; + localInfo.maxAngle = info.maxAngle; + localInfo.force = info.force; +} + +/* +================= +idRenderModelDecal::AddWinding +================= +*/ +void idRenderModelDecal::AddWinding( const idWinding &w, const idMaterial *decalMaterial, const idPlane fadePlanes[2], float fadeDepth, int startTime ) { + idRenderModelDecal *decal = this; + while ( ( decal->material != NULL && decal->material != decalMaterial ) || + decal->tri.numVerts + w.GetNumPoints() >= MAX_DECAL_VERTS || + decal->tri.numIndexes + ( w.GetNumPoints() - 2 ) * 3 >= MAX_DECAL_INDEXES ) { + if ( decal->nextDecal == NULL ) { + decal->nextDecal = idRenderModelDecal::Alloc(); + } + decal = decal->nextDecal; + } + + decal->material = decalMaterial; + AddCount++; + + const float invFadeDepth = -1.0f / fadeDepth; + for ( int i = 0; i < w.GetNumPoints(); i++ ) { + float fade = fadePlanes[0].Distance( w[i].ToVec3() ) * invFadeDepth; + if ( fade < 0.0f ) { + fade = fadePlanes[1].Distance( w[i].ToVec3() ) * invFadeDepth; + } + if ( fade < 0.0f ) { + fade = 0.0f; + } else if ( fade > 0.99f ) { + fade = 1.0f; + } + fade = 1.0f - fade; + decal->vertDepthFade[decal->tri.numVerts + i] = fade; + decal->tri.verts[decal->tri.numVerts + i].xyz = w[i].ToVec3(); + decal->tri.verts[decal->tri.numVerts + i].st[0] = w[i].s; + decal->tri.verts[decal->tri.numVerts + i].st[1] = w[i].t; + decal->vertLifeSpan[decal->tri.numVerts + i] = 0.0f; + } + for ( int i = 2; i < w.GetNumPoints(); i++ ) { + decal->tri.indexes[decal->tri.numIndexes + 0] = decal->tri.numVerts; + decal->tri.indexes[decal->tri.numIndexes + 1] = decal->tri.numVerts + i - 1; + decal->tri.indexes[decal->tri.numIndexes + 2] = decal->tri.numVerts + i; + decal->indexStartTime[decal->tri.numIndexes] = + decal->indexStartTime[decal->tri.numIndexes + 1] = + decal->indexStartTime[decal->tri.numIndexes + 2] = startTime; + decal->tri.numIndexes += 3; + } + decal->tri.numVerts += w.GetNumPoints(); +} + +/* +================= +idRenderModelDecal::AddDepthFadedWinding +================= +*/ +void idRenderModelDecal::AddDepthFadedWinding( const idWinding &w, const idMaterial *decalMaterial, const idPlane fadePlanes[2], float fadeDepth, int startTime ) { + idFixedWinding front, back; + + front = w; + if ( front.Split( &back, fadePlanes[0], 0.1f ) == SIDE_CROSS ) { + AddWinding( back, decalMaterial, fadePlanes, fadeDepth, startTime ); + } + + if ( front.Split( &back, fadePlanes[1], 0.1f ) == SIDE_CROSS ) { + AddWinding( back, decalMaterial, fadePlanes, fadeDepth, startTime ); + } + + AddWinding( front, decalMaterial, fadePlanes, fadeDepth, startTime ); +} + +/* +================= +idRenderModelDecal::CreateDecal +================= +*/ +void idRenderModelDecal::CreateDecal( const idRenderModel *model, const decalProjectionInfo_t &localInfo ) { + + // check all model surfaces + for ( int surfNum = 0; surfNum < model->NumSurfaces(); surfNum++ ) { + const modelSurface_t *surf = model->Surface( surfNum ); + + // if no geometry or no shader + if ( !surf->geometry || !surf->shader ) { + continue; + } + + // decals and overlays use the same rules + if ( !localInfo.force && !surf->shader->AllowOverlays() ) { + continue; + } + + srfTriangles_t *stri = surf->geometry; + + // if the triangle bounds do not overlap with projection bounds + if ( !localInfo.projectionBounds.IntersectsBounds( stri->bounds ) ) { + continue; + } + + if ( stri->primBatchMesh != NULL ) { + stri->primBatchMesh->CreateDecalTriangles( *this, localInfo, + stri->facePlanesCalculated ? stri->facePlanes : NULL, + stri->silTraceVerts ); + continue; + } + + // allocate memory for the cull bits + byte *cullBits = (byte *)_alloca16( stri->numVerts * sizeof( cullBits[0] ) ); + + // catagorize all points by the planes + SIMDProcessor->DecalPointCull( cullBits, localInfo.boundingPlanes, stri->verts, stri->numVerts ); + + // find triangles inside the projection volume + for ( int triNum = 0, index = 0; index < stri->numIndexes; index += 3, triNum++ ) { + int v1 = stri->indexes[index+0]; + int v2 = stri->indexes[index+1]; + int v3 = stri->indexes[index+2]; + + // skip triangles completely off one side + if ( cullBits[v1] & cullBits[v2] & cullBits[v3] ) { + continue; + } + + // skip back facing triangles + if ( stri->facePlanes && stri->facePlanesCalculated && + stri->facePlanes[triNum].Normal() * localInfo.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 2].Normal() < localInfo.maxAngle ) { + continue; + } + + // create a winding with texture coordinates for the triangle + idFixedWinding fw; + fw.SetNumPoints( 3 ); + if ( localInfo.parallel ) { + for ( int j = 0; j < 3; j++ ) { + fw[j] = stri->verts[stri->indexes[index+j]].xyz; + fw[j].s = localInfo.textureAxis[0].Distance( fw[j].ToVec3() ); + fw[j].t = localInfo.textureAxis[1].Distance( fw[j].ToVec3() ); + } + } else { + for ( int j = 0; j < 3; j++ ) { + idVec3 dir; + float scale; + + fw[j] = stri->verts[stri->indexes[index+j]].xyz; + dir = fw[j].ToVec3() - localInfo.projectionOrigin; + localInfo.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 1].RayIntersection( fw[j].ToVec3(), dir, scale ); + dir = fw[j].ToVec3() + scale * dir; + fw[j].s = localInfo.textureAxis[0].Distance( dir ); + fw[j].t = localInfo.textureAxis[1].Distance( dir ); + } + } + + int orBits = cullBits[v1] | cullBits[v2] | cullBits[v3]; + + // clip the exact surface triangle to the projection volume + for ( int j = 0; j < NUM_DECAL_BOUNDING_PLANES; j++ ) { + if ( orBits & ( 1 << j ) ) { + if ( !fw.ClipInPlace( -localInfo.boundingPlanes[j] ) ) { + break; + } + } + } + + if ( fw.GetNumPoints() == 0 ) { + continue; + } + + AddDepthFadedWinding( fw, localInfo.material, localInfo.fadePlanes, localInfo.fadeDepth, localInfo.startTime ); + } + } +} + +/* +===================== +idRenderModelDecal::RemoveFadedDecals +===================== +*/ +idRenderModelDecal *idRenderModelDecal::RemoveFadedDecals( idRenderModelDecal *decals, int time ) { + int i, j, minTime, newNumIndexes, newNumVerts; + int inUse[MAX_DECAL_VERTS]; + decalInfo_t decalInfo; + idRenderModelDecal *nextDecal; + + if ( decals == NULL ) { + return NULL; + } + + // recursively free any next decals + decals->nextDecal = RemoveFadedDecals( decals->nextDecal, time ); + + // free the decals if no material set + if ( decals->material == NULL ) { + nextDecal = decals->nextDecal; + Free( decals ); + return nextDecal; + } + + decalInfo = decals->material->GetDecalInfo(); + minTime = time - decalInfo.stayTime; + for ( i = 0; i < decals->tri.numIndexes; i += 3 ) { + if ( decals->indexStartTime[i] <= minTime ) { + break; + } + } + if ( i == decals->tri.numIndexes ) { + return decals; + } + + newNumIndexes = 0; + for ( i = 0; i < decals->tri.numIndexes; i += 3 ) { + if ( decals->indexStartTime[i] > minTime ) { + // keep this triangle + if ( newNumIndexes != i ) { + for ( j = 0; j < 3; j++ ) { + decals->tri.indexes[newNumIndexes+j] = decals->tri.indexes[i+j]; + decals->indexStartTime[newNumIndexes+j] = decals->indexStartTime[i+j]; + } + } + newNumIndexes += 3; + } + } + + // free the decals if all trianges faded away + if ( newNumIndexes == 0 ) { + nextDecal = decals->nextDecal; + Free( decals ); + return nextDecal; + } + + decals->tri.numIndexes = newNumIndexes; + + memset( inUse, 0, sizeof( inUse ) ); + for ( i = 0; i < decals->tri.numIndexes; i++ ) { + inUse[decals->tri.indexes[i]] = 1; + } + + newNumVerts = 0; + for ( i = 0; i < decals->tri.numVerts; i++ ) { + if ( !inUse[i] ) { + continue; + } + decals->tri.verts[newNumVerts] = decals->tri.verts[i]; + decals->vertDepthFade[newNumVerts] = decals->vertDepthFade[i]; + inUse[i] = newNumVerts; + newNumVerts++; + } + decals->tri.numVerts = newNumVerts; + + for ( i = 0; i < decals->tri.numIndexes; i++ ) { + decals->tri.indexes[i] = inUse[decals->tri.indexes[i]]; + } + + return decals; +} + +/* +===================== +idRenderModelDecal::AddDecalDrawSurf +===================== +*/ +void idRenderModelDecal::AddDecalDrawSurf( viewEntity_t *space ) { + if ( r_skipDecals.GetBool() || tri.numIndexes == 0 ) { + return; + } + + const float maxTime = material->GetDecalInfo().stayTime; + const float renderTime = tr.viewDef->renderView.time; + for ( int i = 0; i < tri.numIndexes; i += 3 ) { + float life = renderTime - indexStartTime[i]; + if ( life <= maxTime ) { + life = idMath::ClampFloat( 0.0f, maxTime, life ) / maxTime; + vertLifeSpan[tri.indexes[i + 0]] = life; + vertLifeSpan[tri.indexes[i + 1]] = life; + vertLifeSpan[tri.indexes[i + 2]] = life; + } + } + + tr.pc.c_numDecalIndexes += tri.numIndexes; + + srfTriangles_t *newTri = (srfTriangles_t *)R_FrameAlloc( sizeof( *newTri ) ); + *newTri = tri; + + const int vertexBytes = tri.numVerts * sizeof( idDrawVert ); + const int colorBytes = material->GetNumStages() * tri.numVerts * 4; + byte *data = (byte *)R_FrameAlloc( vertexBytes + colorBytes ); + SIMDProcessor->Memcpy( data, tri.verts, vertexBytes ); + byte *colorCache = data + vertexBytes; + float shaderParms[MAX_ENTITY_SHADER_PARMS] = { 0.0f }; + float *registers = (float *)_alloca16( material->GetNumRegisters() * sizeof( float ) ); + + for ( int stageIndex = 0; stageIndex < material->GetNumStages(); stageIndex++ ) { + const shaderStage_t *stage = material->GetStage( stageIndex ); + for ( int i = 0; i < tri.numIndexes; i += 3 ) { + const int firstVert = tri.indexes[i]; + shaderParms[REG_DECAL_LIFE] = vertLifeSpan[firstVert]; + shaderParms[REG_DECAL_SPAWN] = indexStartTime[i]; + material->EvaluateStageRegisters( stageIndex, registers, shaderParms, tr.viewDef->floatTime ); + byte color[4]; + for ( int channel = 0; channel < 4; channel++ ) { + const int value = idMath::FtoiFast( vertDepthFade[firstVert] * registers[stage->color.registers[channel]] * 255.0f ); + color[channel] = (byte)value; + } + for ( int corner = 0; corner < 3; corner++ ) { + memcpy( colorCache + tri.indexes[i + corner] * 4, color, sizeof( color ) ); + } + } + colorCache += tri.numVerts * 4; + } + + newTri->ambientCache = vertexCache.AllocFrameTemp( data, vertexBytes + colorBytes ); + + R_AddDrawSurf( newTri, space, &space->entityDef->parms, material, space->scissorRect, 1 ); +} diff --git a/src/renderer/modelmanager.cpp b/src/renderer/modelmanager.cpp new file mode 100644 index 0000000..8aa4908 --- /dev/null +++ b/src/renderer/modelmanager.cpp @@ -0,0 +1,735 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Model_local.h" +#include "tr_local.h" // just for R_FreeWorldInteractions and R_CreateWorldInteractions + + +class idRenderModelManagerLocal : public idRenderModelManager { +public: + idRenderModelManagerLocal(); + virtual ~idRenderModelManagerLocal() {} + + virtual void Init(); + virtual void Shutdown(); + virtual void Reset(); + virtual idRenderModel * AllocModel(); + virtual void FreeModel( idRenderModel *model ); + virtual idRenderModel * FindModel( const char *modelName ); + virtual idRenderModel * CheckModel( const char *modelName ); + virtual srfTriangles_t *AllocStaticTriSurf( int verts, int indices ); + virtual void FreeStaticTriSurf( srfTriangles_t *tris ); + virtual srfTriangles_t *CopyStaticTriSurf( const srfTriangles_t *tri ); + virtual srfTriangles_t *PolytopeSurface( int numPlanes, const idPlane *planes, idWinding **windings ); + virtual void CreateSilIndexes( srfTriangles_t *tris ); + virtual void DeriveFacePlanes( srfTriangles_t *tris ); + virtual void BoundTriSurf( srfTriangles_t *tri ); + virtual void CleanupTriangles( srfTriangles_t *tris, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents, bool needSilMultiply ); + virtual void SimpleCleanupTriangles( srfTriangles_t *tri ); + virtual srfTriangles_t *CreateShadowVolume( const srfTriangles_t *tri, const idRenderLight *light, int optimize ); + virtual idRenderLight *CreateLightDef(); + virtual void FreeLightDef( idRenderLight *light ); + virtual bool CheckModel( idRenderModel *model ); + virtual idRenderModel * DefaultModel(); + virtual void AddModel( idRenderModel *model ); + virtual void RemoveModel( idRenderModel *model ); + virtual void ReloadModels( bool forceAll = false ); + virtual void FreeModelVertexCaches(); + virtual void WritePrecacheCommands( idFile *file ); + virtual void BeginLevelLoad(); + virtual void EndLevelLoad(); + + virtual void PrintMemInfo( MemInfo *mi ); + virtual size_t ListModelSummary(); + +private: + idList models; + idHashIndex hash; + idRenderModel * defaultModel; + idRenderModel * beamModel; + idRenderModel * spriteModel; + idRenderModel * trailModel; + bool insideLevelLoad; // don't actually load now + bool mShuttingDown; + + idRenderModel * GetModel( const char *modelName, bool createIfNotFound ); + + static void PrintModel_f( const idCmdArgs &args ); + static void ListModels_f( const idCmdArgs &args ); + static void ReloadModels_f( const idCmdArgs &args ); + static void TouchModel_f( const idCmdArgs &args ); +}; + + +idRenderModelManagerLocal localModelManager; +idRenderModelManager * renderModelManager = &localModelManager; + +rvRenderModelBSE::rvRenderModelBSE() { + bounds.Clear(); +} + +rvRenderModelBSE::~rvRenderModelBSE() { +} + +void rvRenderModelBSE::InitFromFile( const char *fileName ) { + name = fileName; +} + +dynamicModel_t rvRenderModelBSE::IsDynamicModel() const { + return DM_CONTINUOUS; +} + +/* +============== +idRenderModelManagerLocal::idRenderModelManagerLocal +============== +*/ +idRenderModelManagerLocal::idRenderModelManagerLocal() { + defaultModel = NULL; + beamModel = NULL; + spriteModel = NULL; + insideLevelLoad = false; + mShuttingDown = false; + trailModel = NULL; +} + +void idRenderModelManagerLocal::Reset() { + FreeModelVertexCaches(); +} + +srfTriangles_t *idRenderModelManagerLocal::AllocStaticTriSurf( int verts, int indices ) { + srfTriangles_t *tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, verts ); + R_AllocStaticTriSurfIndexes( tri, indices ); + return tri; +} + +void idRenderModelManagerLocal::FreeStaticTriSurf( srfTriangles_t *tris ) { + R_ReallyFreeStaticTriSurf( tris ); +} + +srfTriangles_t *idRenderModelManagerLocal::CopyStaticTriSurf( const srfTriangles_t *tri ) { + return R_CopyStaticTriSurf( tri ); +} + +srfTriangles_t *idRenderModelManagerLocal::PolytopeSurface( int numPlanes, const idPlane *planes, idWinding **windings ) { + return R_PolytopeSurface( numPlanes, planes, windings ); +} + +void idRenderModelManagerLocal::CreateSilIndexes( srfTriangles_t *tris ) { + R_CreateSilIndexes( tris ); +} + +void idRenderModelManagerLocal::DeriveFacePlanes( srfTriangles_t *tris ) { + R_DeriveFacePlanes( tris ); +} + +void idRenderModelManagerLocal::BoundTriSurf( srfTriangles_t *tri ) { + R_BoundTriSurf( tri ); +} + +void idRenderModelManagerLocal::CleanupTriangles( srfTriangles_t *tris, bool createNormals, + bool identifySilEdges, bool useUnsmoothedTangents, bool needSilMultiply ) { + (void)needSilMultiply; + R_CleanupTriangles( tris, createNormals, identifySilEdges, useUnsmoothedTangents ); +} + +void idRenderModelManagerLocal::SimpleCleanupTriangles( srfTriangles_t *tri ) { + R_RangeCheckIndexes( tri ); + R_CreateSilIndexes( tri ); + R_RemoveDegenerateTriangles( tri ); + R_FreeStaticTriSurfSilIndexes( tri ); +} + +idRenderLight *idRenderModelManagerLocal::CreateLightDef() { + return new idRenderLightLocal; +} + +void idRenderModelManagerLocal::FreeLightDef( idRenderLight *light ) { + if ( light ) { + light->FreeRenderLight(); + delete light; + } +} + +bool idRenderModelManagerLocal::CheckModel( idRenderModel *model ) { + if ( mShuttingDown ) { + return false; + } + return models.FindIndex( model ) >= 0; +} + +size_t idRenderModelManagerLocal::ListModelSummary() { + size_t total = 0; + for ( int i = 0; i < models.Num(); ++i ) { + if ( models[i] && models[i]->IsLoaded() ) { + total += models[i]->Memory(); + } + } + return total; +} + +srfTriangles_t *idRenderModelManagerLocal::CreateShadowVolume( const srfTriangles_t *tri, + const idRenderLight *light, int optimize ) { + idRenderEntityLocal entity; + entity.modelMatrix[0] = entity.modelMatrix[5] = entity.modelMatrix[10] = entity.modelMatrix[15] = 1.0f; + srfCullInfo_t cullInfo; + memset( &cullInfo, 0, sizeof( cullInfo ) ); + srfTriangles_t *result = R_CreateShadowVolume( &entity, tri, + static_cast( light ), optimize, cullInfo ); + R_FreeInteractionCullInfo( cullInfo ); + return result; +} + +/* +============== +idRenderModelManagerLocal::PrintModel_f +============== +*/ +void idRenderModelManagerLocal::PrintModel_f( const idCmdArgs &args ) { + idRenderModel *model; + + if ( args.Argc() != 2 ) { + common->Printf( "usage: printModel \n" ); + return; + } + + model = renderModelManager->CheckModel( args.Argv( 1 ) ); + if ( !model ) { + common->Printf( "model \"%s\" not found\n", args.Argv( 1 ) ); + return; + } + + model->Print(); +} + +/* +============== +idRenderModelManagerLocal::ListModels_f +============== +*/ +void idRenderModelManagerLocal::ListModels_f( const idCmdArgs &args ) { + int totalMem = 0; + int inUse = 0; + + common->Printf( " mem srf verts tris\n" ); + common->Printf( " --- --- ----- ----\n" ); + + for ( int i = 0 ; i < localModelManager.models.Num() ; i++ ) { + idRenderModel *model = localModelManager.models[i]; + + if ( !model->IsLoaded() ) { + continue; + } + model->List(); + totalMem += model->Memory(); + inUse++; + } + + common->Printf( " --- --- ----- ----\n" ); + common->Printf( " mem srf verts tris\n" ); + + common->Printf( "%i loaded models\n", inUse ); + common->Printf( "total memory: %4.1fM\n", (float)totalMem / (1024*1024) ); +} + +/* +============== +idRenderModelManagerLocal::ReloadModels_f +============== +*/ +void idRenderModelManagerLocal::ReloadModels_f( const idCmdArgs &args ) { + if ( idStr::Icmp( args.Argv(1), "all" ) == 0 ) { + localModelManager.ReloadModels( true ); + } else { + localModelManager.ReloadModels( false ); + } +} + +/* +============== +idRenderModelManagerLocal::TouchModel_f + +Precache a specific model +============== +*/ +void idRenderModelManagerLocal::TouchModel_f( const idCmdArgs &args ) { + const char *model = args.Argv( 1 ); + + if ( !model[0] ) { + common->Printf( "usage: touchModel \n" ); + return; + } + + common->Printf( "touchModel %s\n", model ); + session->UpdateScreen(); + idRenderModel *m = renderModelManager->CheckModel( model ); + if ( !m ) { + common->Printf( "...not found\n" ); + } +} + +/* +================= +idRenderModelManagerLocal::WritePrecacheCommands +================= +*/ +void idRenderModelManagerLocal::WritePrecacheCommands( idFile *f ) { + for ( int i = 0 ; i < models.Num() ; i++ ) { + idRenderModel *model = models[i]; + + if ( !model ) { + continue; + } + if ( !model->IsReloadable() ) { + continue; + } + + char str[1024]; + sprintf( str, "touchModel %s\n", model->Name() ); + common->Printf( "%s", str ); + f->Printf( "%s", str ); + } +} + +/* +================= +idRenderModelManagerLocal::Init +================= +*/ +void idRenderModelManagerLocal::Init() { + cmdSystem->AddCommand( "listModels", ListModels_f, CMD_FL_RENDERER, "lists all models" ); + cmdSystem->AddCommand( "printModel", PrintModel_f, CMD_FL_RENDERER, "prints model info", idCmdSystem::ArgCompletion_ModelName ); + cmdSystem->AddCommand( "reloadModels", ReloadModels_f, CMD_FL_RENDERER|CMD_FL_CHEAT, "reloads models" ); + cmdSystem->AddCommand( "touchModel", TouchModel_f, CMD_FL_RENDERER, "touches a model", idCmdSystem::ArgCompletion_ModelName ); + + insideLevelLoad = false; + + // create a default model + idRenderModelStatic *model = new idRenderModelStatic; + model->InitEmpty( "_DEFAULT" ); + model->MakeDefaultModel(); + model->SetLevelLoadReferenced( true ); + defaultModel = model; + AddModel( model ); + + // create the beam model + idRenderModelStatic *beam = new idRenderModelBeam; + beam->InitEmpty( "_BEAM" ); + beam->SetLevelLoadReferenced( true ); + beamModel = beam; + AddModel( beam ); + + idRenderModelStatic *sprite = new idRenderModelSprite; + sprite->InitEmpty( "_SPRITE" ); + sprite->SetLevelLoadReferenced( true ); + spriteModel = sprite; + AddModel( sprite ); +} + +/* +================= +idRenderModelManagerLocal::Shutdown +================= +*/ +void idRenderModelManagerLocal::Shutdown() { + models.DeleteContents( true ); + hash.Free(); +} + +/* +================= +idRenderModelManagerLocal::GetModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::GetModel( const char *modelName, bool createIfNotFound ) { + idStr canonical; + idStr extension; + + if ( !modelName || !modelName[0] ) { + return NULL; + } + + canonical = modelName; + canonical.ToLower(); + + // see if it is already present + int key = hash.GenerateKey( modelName, false ); + for ( int i = hash.First( key ); i != -1; i = hash.Next( i ) ) { + idRenderModel *model = models[i]; + + if ( canonical.Icmp( model->Name() ) == 0 ) { + if ( !model->IsLoaded() ) { + // reload it if it was purged + model->LoadModel(); + } else if ( insideLevelLoad && !model->IsLevelLoadReferenced() ) { + // we are reusing a model already in memory, but + // touch all the materials to make sure they stay + // in memory as well + model->TouchData(); + } + model->SetLevelLoadReferenced( true ); + return model; + } + } + + // see if we can load it + + // determine which subclass of idRenderModel to initialize + + idRenderModel *model; + + canonical.ExtractFileExtension( extension ); + + if ( ( extension.Icmp( "ase" ) == 0 ) || ( extension.Icmp( "lwo" ) == 0 ) || ( extension.Icmp( "flt" ) == 0 ) ) { + model = new idRenderModelStatic; + model->InitFromFile( modelName ); + } else if ( extension.Icmp( "ma" ) == 0 ) { + model = new idRenderModelStatic; + model->InitFromFile( modelName ); + } else if ( extension.Icmp( MD5_MESH_EXT ) == 0 ) { + model = new idRenderModelMD5; + model->InitFromFile( modelName ); + } else if ( extension.Icmp( "md3" ) == 0 ) { + model = new idRenderModelMD3; + model->InitFromFile( modelName ); + } else if ( extension.Icmp( "liquid" ) == 0 ) { + model = new idRenderModelLiquid; + model->InitFromFile( modelName ); + } else { + + if ( extension.Length() ) { + common->Warning( "unknown model type '%s'", canonical.c_str() ); + } + + if ( !createIfNotFound ) { + return NULL; + } + + idRenderModelStatic *smodel = new idRenderModelStatic; + smodel->InitEmpty( modelName ); + smodel->MakeDefaultModel(); + + model = smodel; + } + + model->SetLevelLoadReferenced( true ); + + if ( !createIfNotFound && model->IsDefaultModel() ) { + delete model; + model = NULL; + + return NULL; + } + + AddModel( model ); + + return model; +} + +/* +================= +idRenderModelManagerLocal::AllocModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::AllocModel() { + return new idRenderModelStatic(); +} + +/* +================= +idRenderModelManagerLocal::FreeModel +================= +*/ +void idRenderModelManagerLocal::FreeModel( idRenderModel *model ) { + if ( !model ) { + return; + } + if ( !dynamic_cast( model ) ) { + common->Error( "idRenderModelManager::FreeModel: model '%s' is not a static model", model->Name() ); + return; + } + if ( model == defaultModel ) { + common->Error( "idRenderModelManager::FreeModel: can't free the default model" ); + return; + } + if ( model == beamModel ) { + common->Error( "idRenderModelManager::FreeModel: can't free the beam model" ); + return; + } + if ( model == spriteModel ) { + common->Error( "idRenderModelManager::FreeModel: can't free the sprite model" ); + return; + } + + R_CheckForEntityDefsUsingModel( model ); + + delete model; +} + +/* +================= +idRenderModelManagerLocal::FindModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::FindModel( const char *modelName ) { + return GetModel( modelName, true ); +} + +/* +================= +idRenderModelManagerLocal::CheckModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::CheckModel( const char *modelName ) { + return GetModel( modelName, false ); +} + +/* +================= +idRenderModelManagerLocal::DefaultModel +================= +*/ +idRenderModel *idRenderModelManagerLocal::DefaultModel() { + return defaultModel; +} + +/* +================= +idRenderModelManagerLocal::AddModel +================= +*/ +void idRenderModelManagerLocal::AddModel( idRenderModel *model ) { + hash.Add( hash.GenerateKey( model->Name(), false ), models.Append( model ) ); +} + +/* +================= +idRenderModelManagerLocal::RemoveModel +================= +*/ +void idRenderModelManagerLocal::RemoveModel( idRenderModel *model ) { + int index = models.FindIndex( model ); + hash.RemoveIndex( hash.GenerateKey( model->Name(), false ), index ); + models.RemoveIndex( index ); +} + +/* +================= +idRenderModelManagerLocal::ReloadModels +================= +*/ +void idRenderModelManagerLocal::ReloadModels( bool forceAll ) { + if ( forceAll ) { + common->Printf( "Reloading all model files...\n" ); + } else { + common->Printf( "Checking for changed model files...\n" ); + } + + R_FreeDerivedData(); + + // skip the default model at index 0 + for ( int i = 1 ; i < models.Num() ; i++ ) { + idRenderModel *model = models[i]; + + // we may want to allow world model reloading in the future, but we don't now + if ( !model->IsReloadable() ) { + continue; + } + + if ( !forceAll ) { + // check timestamp + ID_TIME_T current; + + fileSystem->ReadFile( model->Name(), NULL, ¤t ); + if ( current <= model->Timestamp() ) { + continue; + } + } + + common->DPrintf( "reloading %s.\n", model->Name() ); + + model->LoadModel(); + } + + // we must force the world to regenerate, because models may + // have changed size, making their references invalid + R_ReCreateWorldReferences(); +} + +/* +================= +idRenderModelManagerLocal::FreeModelVertexCaches +================= +*/ +void idRenderModelManagerLocal::FreeModelVertexCaches() { + for ( int i = 0 ; i < models.Num() ; i++ ) { + idRenderModel *model = models[i]; + model->FreeVertexCache(); + } +} + +/* +================= +idRenderModelManagerLocal::BeginLevelLoad +================= +*/ +void idRenderModelManagerLocal::BeginLevelLoad() { + insideLevelLoad = true; + + for ( int i = 0 ; i < models.Num() ; i++ ) { + idRenderModel *model = models[i]; + + if ( com_purgeAll.GetBool() && model->IsReloadable() ) { + R_CheckForEntityDefsUsingModel( model ); + model->PurgeModel(); + } + + model->SetLevelLoadReferenced( false ); + } + + // purge unused triangle surface memory + R_PurgeTriSurfData( frameData ); +} + +/* +================= +idRenderModelManagerLocal::EndLevelLoad +================= +*/ +void idRenderModelManagerLocal::EndLevelLoad() { + common->Printf( "----- idRenderModelManagerLocal::EndLevelLoad -----\n" ); + + int start = Sys_Milliseconds(); + + insideLevelLoad = false; + int purgeCount = 0; + int keepCount = 0; + int loadCount = 0; + + // purge any models not touched + for ( int i = 0 ; i < models.Num() ; i++ ) { + idRenderModel *model = models[i]; + + if ( !model->IsLevelLoadReferenced() && model->IsLoaded() && model->IsReloadable() ) { + +// common->Printf( "purging %s\n", model->Name() ); + + purgeCount++; + + R_CheckForEntityDefsUsingModel( model ); + + model->PurgeModel(); + + } else { + +// common->Printf( "keeping %s\n", model->Name() ); + + keepCount++; + } + } + + // purge unused triangle surface memory + R_PurgeTriSurfData( frameData ); + + // load any new ones + for ( int i = 0 ; i < models.Num() ; i++ ) { + idRenderModel *model = models[i]; + + if ( model->IsLevelLoadReferenced() && !model->IsLoaded() && model->IsReloadable() ) { + + loadCount++; + model->LoadModel(); + + if ( ( loadCount & 15 ) == 0 ) { + session->PacifierUpdate(); + } + } + } + + // _D3XP added this + int end = Sys_Milliseconds(); + common->Printf( "%5i models purged from previous level, ", purgeCount ); + common->Printf( "%5i models kept.\n", keepCount ); + if ( loadCount ) { + common->Printf( "%5i new models loaded in %5.1f seconds\n", loadCount, (end-start) * 0.001 ); + } + common->Printf( "---------------------------------------------------\n" ); +} + +/* +================= +idRenderModelManagerLocal::PrintMemInfo +================= +*/ +void idRenderModelManagerLocal::PrintMemInfo( MemInfo *mi ) { + int i, j, totalMem = 0; + int *sortIndex; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_models.txt" ); + if ( !f ) { + return; + } + + // sort first + sortIndex = new int[ localModelManager.models.Num()]; + + for ( i = 0; i < localModelManager.models.Num(); i++ ) { + sortIndex[i] = i; + } + + for ( i = 0; i < localModelManager.models.Num() - 1; i++ ) { + for ( j = i + 1; j < localModelManager.models.Num(); j++ ) { + if ( localModelManager.models[sortIndex[i]]->Memory() < localModelManager.models[sortIndex[j]]->Memory() ) { + int temp = sortIndex[i]; + sortIndex[i] = sortIndex[j]; + sortIndex[j] = temp; + } + } + } + + // print next + for ( int i = 0 ; i < localModelManager.models.Num() ; i++ ) { + idRenderModel *model = localModelManager.models[sortIndex[i]]; + int mem; + + if ( !model->IsLoaded() ) { + continue; + } + + mem = model->Memory(); + totalMem += mem; + f->Printf( "%s %s\n", idStr::FormatNumber( mem ).c_str(), model->Name() ); + } + + delete sortIndex; + mi->modelAssetsTotal = totalMem; + + f->Printf( "\nTotal model bytes allocated: %s\n", idStr::FormatNumber( totalMem ).c_str() ); + fileSystem->CloseFile( f ); +} diff --git a/src/renderer/modeloverlay.cpp b/src/renderer/modeloverlay.cpp new file mode 100644 index 0000000..0afa130 --- /dev/null +++ b/src/renderer/modeloverlay.cpp @@ -0,0 +1,362 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Model_local.h" +#include "tr_local.h" +#include "rvMesh.h" + +#define FREE_OVERLAY_SURFACE( surface ) \ + do { \ + if ( ( surface )->verts ) { Mem_Free( ( surface )->verts ); } \ + if ( ( surface )->indexes ) { Mem_Free( ( surface )->indexes ); } \ + Mem_Free( surface ); \ + } while ( 0 ) + +/* +==================== +idRenderModelOverlay::~idRenderModelOverlay +==================== +*/ +idRenderModelOverlay::~idRenderModelOverlay() { + int i, k; + + for ( k = 0; k < materials.Num(); k++ ) { + for ( i = 0; i < materials[k]->surfaces.Num(); i++ ) { + FREE_OVERLAY_SURFACE( materials[k]->surfaces[i] ); + } + materials[k]->surfaces.Clear(); + delete materials[k]; + } + materials.Clear(); +} + +/* +==================== +idRenderModelOverlay::Alloc +==================== +*/ +idRenderModelOverlay *idRenderModelOverlay::Alloc( void ) { + return new idRenderModelOverlay; +} + +/* +==================== +idRenderModelOverlay::Free +==================== +*/ +void idRenderModelOverlay::Free( idRenderModelOverlay *overlay ) { + delete overlay; +} + +/* +===================== +idRenderModelOverlay::CreateOverlay + +This projects on both front and back sides to avoid seams +The material should be clamped, because entire triangles are added, some of which +may extend well past the 0.0 to 1.0 texture range +===================== +*/ +void idRenderModelOverlay::CreateOverlay( const idRenderModel *model, const idPlane localTextureAxis[2], const idMaterial *mtr ) { + int i, maxVerts, maxIndexes, surfNum; + idRenderModelOverlay *overlay = NULL; + + // count up the maximum possible vertices and indexes per surface + maxVerts = 0; + maxIndexes = 0; + for ( surfNum = 0; surfNum < model->NumSurfaces(); surfNum++ ) { + const modelSurface_t *surf = model->Surface( surfNum ); + if ( surf->geometry->numVerts > maxVerts ) { + maxVerts = surf->geometry->numVerts; + } + if ( surf->geometry->numIndexes > maxIndexes ) { + maxIndexes = surf->geometry->numIndexes; + } + } + + // make temporary buffers for the building process + overlayVertex_t *overlayVerts = (overlayVertex_t *)_alloca( maxVerts * sizeof( *overlayVerts ) ); + int *overlayIndexes = (int *)_alloca16( maxIndexes * sizeof( *overlayIndexes ) ); + + // pull out the triangles we need from the base surfaces + for ( surfNum = 0; surfNum < model->NumBaseSurfaces(); surfNum++ ) { + const modelSurface_t *surf = model->Surface( surfNum ); + float d; + + if ( !surf->geometry || !surf->shader ) { + continue; + } + + // some surfaces can explicitly disallow overlays + if ( !surf->shader->AllowOverlays() ) { + continue; + } + + const srfTriangles_t *stri = surf->geometry; + + // try to cull the whole surface along the first texture axis + d = stri->bounds.PlaneDistance( localTextureAxis[0] ); + if ( d < 0.0f || d > 1.0f ) { + continue; + } + + // try to cull the whole surface along the second texture axis + d = stri->bounds.PlaneDistance( localTextureAxis[1] ); + if ( d < 0.0f || d > 1.0f ) { + continue; + } + + int numVerts = 0; + int numIndexes = 0; + if ( stri->primBatchMesh ) { + stri->primBatchMesh->CreateOverlayTriangles( overlayVerts, numVerts, + overlayIndexes, numIndexes, localTextureAxis, stri->silTraceVerts ); + } else { + byte *cullBits = (byte *)_alloca16( stri->numVerts * sizeof( cullBits[0] ) ); + idVec2 *texCoords = (idVec2 *)_alloca16( stri->numVerts * sizeof( texCoords[0] ) ); + + SIMDProcessor->OverlayPointCull( cullBits, texCoords, localTextureAxis, stri->verts, stri->numVerts ); + + int *vertexRemap = (int *)_alloca16( sizeof( vertexRemap[0] ) * stri->numVerts ); + SIMDProcessor->Memset( vertexRemap, -1, sizeof( vertexRemap[0] ) * stri->numVerts ); + + // find triangles that need the overlay + for ( int index = 0; index < stri->numIndexes; index += 3 ) { + int v1 = stri->indexes[index+0]; + int v2 = stri->indexes[index+1]; + int v3 = stri->indexes[index+2]; + + // skip triangles completely off one side + if ( cullBits[v1] & cullBits[v2] & cullBits[v3] ) { + continue; + } + + for ( int vnum = 0; vnum < 3; vnum++ ) { + int ind = stri->indexes[index+vnum]; + if ( vertexRemap[ind] == -1 ) { + vertexRemap[ind] = numVerts; + + overlayVerts[numVerts].vertexNum = ind; + overlayVerts[numVerts].st[0] = texCoords[ind][0]; + overlayVerts[numVerts].st[1] = texCoords[ind][1]; + numVerts++; + } + overlayIndexes[numIndexes++] = vertexRemap[ind]; + } + } + } + + if ( !numIndexes ) { + continue; + } + + overlaySurface_t *s = (overlaySurface_t *) Mem_Alloc( sizeof( overlaySurface_t ) ); + s->surfaceNum = surfNum; + s->surfaceId = surf->id; + s->surfaceNumVerts = stri->numVerts; + s->verts = (overlayVertex_t *)Mem_Alloc( numVerts * sizeof( s->verts[0] ) ); + memcpy( s->verts, overlayVerts, numVerts * sizeof( s->verts[0] ) ); + s->numVerts = numVerts; + s->indexes = (int *)Mem_Alloc( numIndexes * sizeof( s->indexes[0] ) ); + memcpy( s->indexes, overlayIndexes, numIndexes * sizeof( s->indexes[0] ) ); + s->numIndexes = numIndexes; + + for ( i = 0; i < materials.Num(); i++ ) { + if ( materials[i]->material == mtr ) { + break; + } + } + if ( i < materials.Num() ) { + materials[i]->surfaces.Append( s ); + materials[i]->modified = true; + } else { + overlayMaterial_t *mat = new overlayMaterial_t; + mat->material = mtr; + mat->surfaces.Append( s ); + mat->modified = true; + materials.Append( mat ); + } + } + + // remove the oldest overlay surfaces if there are too many per material + for ( i = 0; i < materials.Num(); i++ ) { + while( materials[i]->surfaces.Num() > MAX_OVERLAY_SURFACES ) { + FREE_OVERLAY_SURFACE( materials[i]->surfaces[0] ); + materials[i]->surfaces.RemoveIndex( 0 ); + materials[i]->modified = true; + } + } +} + +/* +==================== +idRenderModelOverlay::AddOverlaySurfacesToModel +==================== +*/ +void idRenderModelOverlay::AddOverlaySurfacesToModel( idRenderModel *baseModel ) { + int i, j, k, numVerts, numIndexes, surfaceNum; + const modelSurface_t *baseSurf; + idRenderModelStatic *staticModel; + overlaySurface_t *surf; + srfTriangles_t *newTri; + modelSurface_t *newSurf; + + if ( baseModel == NULL || baseModel->IsDefaultModel() ) { + return; + } + + // md5 models won't have any surfaces when r_showSkel is set + if ( !baseModel->NumSurfaces() ) { + return; + } + + if ( baseModel->IsDynamicModel() != DM_STATIC ) { + common->Error( "idRenderModelOverlay::AddOverlaySurfacesToModel: baseModel is not a static model" ); + } + + assert( dynamic_cast(baseModel) != NULL ); + staticModel = static_cast(baseModel); + + staticModel->overlaysAdded = 0; + + if ( !materials.Num() ) { + staticModel->DeleteSurfacesWithNegativeId(); + return; + } + + for ( k = 0; k < materials.Num(); k++ ) { + + numVerts = numIndexes = 0; + for ( i = 0; i < materials[k]->surfaces.Num(); i++ ) { + numVerts += materials[k]->surfaces[i]->numVerts; + numIndexes += materials[k]->surfaces[i]->numIndexes; + } + + if ( staticModel->FindSurfaceWithId( -1 - k, surfaceNum ) ) { + newSurf = &staticModel->surfaces[surfaceNum]; + } else { + newSurf = &staticModel->surfaces.Alloc(); + newSurf->geometry = NULL; + newSurf->shader = materials[k]->material; + newSurf->id = -1 - k; + } + + if ( newSurf->geometry == NULL || newSurf->geometry->numVerts < numVerts || newSurf->geometry->numIndexes < numIndexes ) { + R_FreeStaticTriSurf( newSurf->geometry ); + newSurf->geometry = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( newSurf->geometry, numVerts ); + R_AllocStaticTriSurfIndexes( newSurf->geometry, numIndexes ); + SIMDProcessor->Memset( newSurf->geometry->verts, 0, numVerts * sizeof( newTri->verts[0] ) ); + materials[k]->modified = true; + } else { + R_FreeStaticTriSurfVertexCaches( newSurf->geometry ); + } + + newTri = newSurf->geometry; + numVerts = numIndexes = 0; + + for ( i = 0; i < materials[k]->surfaces.Num(); i++ ) { + surf = materials[k]->surfaces[i]; + + // get the model surface for this overlay surface + if ( surf->surfaceNum < staticModel->NumSurfaces() ) { + baseSurf = staticModel->Surface( surf->surfaceNum ); + } else { + baseSurf = NULL; + } + + // if the surface ids no longer match + if ( !baseSurf || baseSurf->id != surf->surfaceId ) { + // find the surface with the correct id + if ( staticModel->FindSurfaceWithId( surf->surfaceId, surf->surfaceNum ) ) { + baseSurf = staticModel->Surface( surf->surfaceNum ); + } else { + // the surface with this id no longer exists + FREE_OVERLAY_SURFACE( surf ); + materials[k]->surfaces.RemoveIndex( i ); + i--; + continue; + } + } + + if ( baseSurf->geometry->numVerts < surf->surfaceNumVerts ) { + common->Warning( "idRenderModelOverlay::AddOverlaySurfacesToModel: overlay vertex out of range. Model has probably changed since generating the overlay." ); + FREE_OVERLAY_SURFACE( surf ); + materials[k]->surfaces.RemoveIndex( i ); + staticModel->DeleteSurfaceWithId( newSurf->id ); + return; + } + + if ( materials[k]->modified ) { + for ( j = 0; j < surf->numIndexes; j++ ) { + newTri->indexes[numIndexes + j] = numVerts + surf->indexes[j]; + } + } + numIndexes += surf->numIndexes; + + for ( j = 0; j < surf->numVerts; j++ ) { + const overlayVertex_t *overlayVert = &surf->verts[j]; + idDrawVert &destVert = newTri->verts[numVerts++]; + + if ( materials[k]->modified ) { + destVert.st[0] = overlayVert->st[0]; + destVert.st[1] = overlayVert->st[1]; + } + + if ( baseSurf->geometry->silTraceVerts ) { + destVert.xyz = baseSurf->geometry->silTraceVerts[overlayVert->vertexNum].xyzw.ToVec3(); + } else { + destVert.xyz = baseSurf->geometry->verts[overlayVert->vertexNum].xyz; + } + } + } + + materials[k]->modified = false; + newTri->numVerts = numVerts; + newTri->numIndexes = numIndexes; + R_BoundTriSurf( newTri ); + + staticModel->overlaysAdded++; // so we don't create an overlay on an overlay surface + } +} + +#undef FREE_OVERLAY_SURFACE + +/* +==================== +idRenderModelOverlay::RemoveOverlaySurfacesFromModel +==================== +*/ +void idRenderModelOverlay::RemoveOverlaySurfacesFromModel( idRenderModel *baseModel ) { + idRenderModelStatic *staticModel; + + assert( dynamic_cast(baseModel) != NULL ); + staticModel = static_cast(baseModel); + + staticModel->DeleteSurfacesWithNegativeId(); + staticModel->overlaysAdded = 0; +} diff --git a/src/renderer/qgl.h b/src/renderer/qgl.h new file mode 100644 index 0000000..f07b356 --- /dev/null +++ b/src/renderer/qgl.h @@ -0,0 +1,655 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#if defined( _WINDOWS ) + +#include + +#elif defined( MACOS_X ) + +// magic flag to keep tiger gl.h from loading glext.h +#define GL_GLEXT_LEGACY +#include + +#elif defined( __linux__ ) + +// using our local glext.h +// http://oss.sgi.com/projects/ogl-sample/ABI/ +#define GL_GLEXT_LEGACY +#define GLX_GLXEXT_LEGACY +#include +#include + +// TTimo - X.h has a '#define Success 0' line.. +#undef Success + +#elif defined( _XENON ) + +#include "../sys/dx/gl_stub.h" +#include "../sys/dx/glext_stub.h" + +#else + +#include + +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef WINAPI +#define WINAPI +#endif + +// only use local glext.h if we are not using the system one already +// http://oss.sgi.com/projects/ogl-sample/ABI/ +#ifndef GL_GLEXT_VERSION + +#include "glext.h" + +#endif + +typedef void (*GLExtension_t)(void); + +#ifdef __cplusplus + extern "C" { +#endif + +GLExtension_t GLimp_ExtensionPointer( const char *name ); + +#ifdef __cplusplus + } +#endif + +// multitexture +extern void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern void ( APIENTRY * qglMultiTexCoord2fvARB )( GLenum texture, GLfloat * RESTRICT st ); +extern void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +extern void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +// ARB_vertex_buffer_object +extern PFNGLBINDBUFFERARBPROC qglBindBufferARB; +extern PFNGLDELETEBUFFERSARBPROC qglDeleteBuffersARB; +extern PFNGLGENBUFFERSARBPROC qglGenBuffersARB; +extern PFNGLISBUFFERARBPROC qglIsBufferARB; +extern PFNGLBUFFERDATAARBPROC qglBufferDataARB; +extern PFNGLBUFFERSUBDATAARBPROC qglBufferSubDataARB; +extern PFNGLGETBUFFERSUBDATAARBPROC qglGetBufferSubDataARB; +extern PFNGLMAPBUFFERARBPROC qglMapBufferARB; +extern PFNGLUNMAPBUFFERARBPROC qglUnmapBufferARB; +extern PFNGLGETBUFFERPARAMETERIVARBPROC qglGetBufferParameterivARB; +extern PFNGLGETBUFFERPOINTERVARBPROC qglGetBufferPointervARB; + + +// NV_register_combiners +extern void ( APIENTRY *qglCombinerParameterfvNV )( GLenum pname, const GLfloat * RESTRICT params ); +extern void ( APIENTRY *qglCombinerParameterivNV )( GLenum pname, const GLint * RESTRICT params ); +extern void ( APIENTRY *qglCombinerParameterfNV )( GLenum pname, const GLfloat param ); +extern void ( APIENTRY *qglCombinerParameteriNV )( GLenum pname, const GLint param ); +extern void ( APIENTRY *qglCombinerInputNV )( GLenum stage, GLenum portion, GLenum variable, GLenum input, + GLenum mapping, GLenum componentUsage ); +extern void ( APIENTRY *qglCombinerOutputNV )( GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, + GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, + GLboolean cdDotProduct, GLboolean muxSum ); +extern void ( APIENTRY *qglFinalCombinerInputNV )( GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage ); + +// RAVEN BEGIN +// dluetscher: added support for NV_vertex_program and NV_fragment_program extensions +extern void ( APIENTRY *qglBindProgramNV ) (GLenum, GLuint); +extern void ( APIENTRY *qglLoadProgramNV ) (GLenum, GLuint, GLsizei, const GLubyte * RESTRICT ); +extern void ( APIENTRY *qglProgramParameter4fvNV ) (GLenum, GLuint, const GLfloat * RESTRICT ); +// RAVEN END + +// 3D textures +extern void ( APIENTRY *qglTexImage3D)(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid * RESTRICT ); + +// shared texture palette +extern void ( APIENTRY *qglColorTableEXT)( int, int, int, int, int, const void * RESTRICT ); + +// RAVEN BEGIN +// dluetscher: added the BlendEquationEXT() from the blend minmax extension +extern void ( APIENTRY *qglBlendEquationEXT)( GLenum mode ); +// RAVEN END + +// RAVEN BEGIN +// dluetscher: added support for the EXT_draw_range_elements extension +extern void ( APIENTRY * qglDrawRangeElementsEXT )(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * RESTRICT indices); +// RAVEN END + +// ATI_fragment_shader +extern PFNGLGENFRAGMENTSHADERSATIPROC qglGenFragmentShadersATI; +extern PFNGLBINDFRAGMENTSHADERATIPROC qglBindFragmentShaderATI; +extern PFNGLDELETEFRAGMENTSHADERATIPROC qglDeleteFragmentShaderATI; +extern PFNGLBEGINFRAGMENTSHADERATIPROC qglBeginFragmentShaderATI; +extern PFNGLENDFRAGMENTSHADERATIPROC qglEndFragmentShaderATI; +extern PFNGLPASSTEXCOORDATIPROC qglPassTexCoordATI; +extern PFNGLSAMPLEMAPATIPROC qglSampleMapATI; +extern PFNGLCOLORFRAGMENTOP1ATIPROC qglColorFragmentOp1ATI; +extern PFNGLCOLORFRAGMENTOP2ATIPROC qglColorFragmentOp2ATI; +extern PFNGLCOLORFRAGMENTOP3ATIPROC qglColorFragmentOp3ATI; +extern PFNGLALPHAFRAGMENTOP1ATIPROC qglAlphaFragmentOp1ATI; +extern PFNGLALPHAFRAGMENTOP2ATIPROC qglAlphaFragmentOp2ATI; +extern PFNGLALPHAFRAGMENTOP3ATIPROC qglAlphaFragmentOp3ATI; +extern PFNGLSETFRAGMENTSHADERCONSTANTATIPROC qglSetFragmentShaderConstantATI; + +// EXT_stencil_two_side +extern PFNGLACTIVESTENCILFACEEXTPROC qglActiveStencilFaceEXT; + + +// ATI_separate_stencil +extern PFNGLSTENCILOPSEPARATEATIPROC qglStencilOpSeparateATI; +extern PFNGLSTENCILFUNCSEPARATEATIPROC qglStencilFuncSeparateATI; + +// ARB_texture_compression +extern PFNGLCOMPRESSEDTEXIMAGE2DARBPROC qglCompressedTexImage2DARB; +extern PFNGLGETCOMPRESSEDTEXIMAGEARBPROC qglGetCompressedTexImageARB; + +// ARB_vertex_program / ARB_fragment_program +extern PFNGLVERTEXATTRIBPOINTERARBPROC qglVertexAttribPointerARB; +extern PFNGLENABLEVERTEXATTRIBARRAYARBPROC qglEnableVertexAttribArrayARB; +extern PFNGLDISABLEVERTEXATTRIBARRAYARBPROC qglDisableVertexAttribArrayARB; +extern PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB; +extern PFNGLBINDPROGRAMARBPROC qglBindProgramARB; +extern PFNGLGENPROGRAMSARBPROC qglGenProgramsARB; +extern PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB; +extern PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB; + +// RAVEN BEGIN +// rjohnson: new shader stage system +// GLSL fragment +extern PFNGLCREATESHADEROBJECTARBPROC qglCreateShaderObjectARB; +extern PFNGLDELETEOBJECTARBPROC qglDeleteObjectARB; +extern PFNGLSHADERSOURCEARBPROC qglShaderSourceARB; +extern PFNGLCOMPILESHADERARBPROC qglCompileShaderARB; +extern PFNGLGETOBJECTPARAMETERIVARBPROC qglGetObjectParameterivARB; +extern PFNGLCREATEPROGRAMOBJECTARBPROC qglCreateProgramObjectARB; +extern PFNGLATTACHOBJECTARBPROC qglAttachObjectARB; +extern PFNGLDETACHOBJECTARBPROC qglDetachObjectARB; +extern PFNGLLINKPROGRAMARBPROC qglLinkProgramARB; +extern PFNGLUSEPROGRAMOBJECTARBPROC qglUseProgramObjectARB; +extern PFNGLGETUNIFORMLOCATIONARBPROC qglGetUniformLocationARB; +extern PFNGLUNIFORM1FARBPROC qglUniform1fARB; +extern PFNGLUNIFORM1IARBPROC qglUniform1iARB; +extern PFNGLUNIFORM1FVARBPROC qglUniform1fvARB; +extern PFNGLUNIFORM2FVARBPROC qglUniform2fvARB; +extern PFNGLUNIFORM3FVARBPROC qglUniform3fvARB; +extern PFNGLUNIFORM4FVARBPROC qglUniform4fvARB; +extern PFNGLGETINFOLOGARBPROC qglGetInfoLogARB; +// RAVEN END + +// GL_EXT_depth_bounds_test +extern PFNGLDEPTHBOUNDSEXTPROC qglDepthBoundsEXT; + +//=========================================================================== + +// non-windows systems will just redefine qgl* to gl* +#if defined( __APPLE__ ) || defined( ID_GL_HARDLINK ) + +#include "qgl_linked.h" + +#else + +// windows systems use a function pointer for each call so we can do our log file intercepts + +extern void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +extern void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint * RESTRICT textures, GLboolean * RESTRICT residences); +extern void ( APIENTRY * qglArrayElement )(GLint i); +extern void ( APIENTRY * qglBegin )(GLenum mode); +extern void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +extern void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte * RESTRICT bitmap); +extern void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( APIENTRY * qglCallList )(GLuint list); +extern void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid * RESTRICT lists); +extern void ( APIENTRY * qglClear )(GLbitfield mask); +extern void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( APIENTRY * qglClearDepth )(GLclampd depth); +extern void ( APIENTRY * qglClearIndex )(GLfloat c); +extern void ( APIENTRY * qglClearStencil )(GLint s); +extern void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble * RESTRICT equation); +extern void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( APIENTRY * qglColor3bv )(const GLbyte * RESTRICT v); +extern void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( APIENTRY * qglColor3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( APIENTRY * qglColor3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +extern void ( APIENTRY * qglColor3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( APIENTRY * qglColor3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( APIENTRY * qglColor3ubv )(const GLubyte * RESTRICT v); +extern void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( APIENTRY * qglColor3uiv )(const GLuint * RESTRICT v); +extern void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( APIENTRY * qglColor3usv )(const GLushort * RESTRICT v); +extern void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( APIENTRY * qglColor4bv )(const GLbyte * RESTRICT v); +extern void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( APIENTRY * qglColor4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglColor4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( APIENTRY * qglColor4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( APIENTRY * qglColor4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( APIENTRY * qglColor4ubv )(const GLubyte * RESTRICT v); +extern void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( APIENTRY * qglColor4uiv )(const GLuint * RESTRICT v); +extern void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( APIENTRY * qglColor4usv )(const GLushort * RESTRICT v); +extern void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglCullFace )(GLenum mode); +extern void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +extern void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint * RESTRICT textures); +extern void ( APIENTRY * qglDepthFunc )(GLenum func); +extern void ( APIENTRY * qglDepthMask )(GLboolean flag); +extern void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( APIENTRY * qglDisable )(GLenum cap); +extern void ( APIENTRY * qglDisableClientState )(GLenum array); +extern void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( APIENTRY * qglDrawBuffer )(GLenum mode); +extern void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid * RESTRICT indices); +extern void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +extern void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglEdgeFlagv )(const GLboolean * RESTRICT flag); +extern void ( APIENTRY * qglEnable )(GLenum cap); +extern void ( APIENTRY * qglEnableClientState )(GLenum array); +extern void ( APIENTRY * qglEnd )(void); +extern void ( APIENTRY * qglEndList )(void); +extern void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +extern void ( APIENTRY * qglEvalCoord1dv )(const GLdouble * RESTRICT u); +extern void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +extern void ( APIENTRY * qglEvalCoord1fv )(const GLfloat * RESTRICT u); +extern void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( APIENTRY * qglEvalCoord2dv )(const GLdouble * RESTRICT u); +extern void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( APIENTRY * qglEvalCoord2fv )(const GLfloat * RESTRICT u); +extern void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( APIENTRY * qglEvalPoint1 )(GLint i); +extern void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +extern void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat * RESTRICT buffer); +extern void ( APIENTRY * qglFinish )(void); +extern void ( APIENTRY * qglFlush )(void); +extern void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglFogiv )(GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglFrontFace )(GLenum mode); +extern void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( APIENTRY * qglGenLists )(GLsizei range); +extern void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint * RESTRICT textures); +extern void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean * RESTRICT params); +extern void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *RESTRICT equation); +extern void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble * RESTRICT params); +extern GLenum ( APIENTRY * qglGetError )(void); +extern void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble * RESTRICT v); +extern void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat * RESTRICT v); +extern void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint * RESTRICT v); +extern void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat * RESTRICT values); +extern void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint * RESTRICT values); +extern void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort * RESTRICT values); +extern void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* * RESTRICT params); +extern void ( APIENTRY * qglGetPolygonStipple )(GLubyte * RESTRICT mask); +extern const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +extern void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble * RESTRICT params); +extern void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat * RESTRICT params); +extern void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint * RESTRICT params); +extern void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +extern void ( APIENTRY * qglIndexMask )(GLuint mask); +extern void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglIndexd )(GLdouble c); +extern void ( APIENTRY * qglIndexdv )(const GLdouble * RESTRICT c); +extern void ( APIENTRY * qglIndexf )(GLfloat c); +extern void ( APIENTRY * qglIndexfv )(const GLfloat * RESTRICT c); +extern void ( APIENTRY * qglIndexi )(GLint c); +extern void ( APIENTRY * qglIndexiv )(const GLint * RESTRICT c); +extern void ( APIENTRY * qglIndexs )(GLshort c); +extern void ( APIENTRY * qglIndexsv )(const GLshort * RESTRICT c); +extern void ( APIENTRY * qglIndexub )(GLubyte c); +extern void ( APIENTRY * qglIndexubv )(const GLubyte * RESTRICT c); +extern void ( APIENTRY * qglInitNames )(void); +extern void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid * RESTRICT pointer); +extern GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +extern GLboolean ( APIENTRY * qglIsList )(GLuint list); +extern GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +extern void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +extern void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +extern void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +extern void ( APIENTRY * qglLineWidth )(GLfloat width); +extern void ( APIENTRY * qglListBase )(GLuint base); +extern void ( APIENTRY * qglLoadIdentity )(void); +extern void ( APIENTRY * qglLoadMatrixd )(const GLdouble * RESTRICT m); +extern void ( APIENTRY * qglLoadMatrixf )(const GLfloat * RESTRICT m); +extern void ( APIENTRY * qglLoadName )(GLuint name); +extern void ( APIENTRY * qglLogicOp )(GLenum opcode); +extern void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble * RESTRICT points); +extern void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat * RESTRICT points); +extern void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble * RESTRICT points); +extern void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat * RESTRICT points); +extern void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglMatrixMode )(GLenum mode); +extern void ( APIENTRY * qglMultMatrixd )(const GLdouble * RESTRICT m); +extern void ( APIENTRY * qglMultMatrixf )(const GLfloat * RESTRICT m); +extern void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +extern void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( APIENTRY * qglNormal3bv )(const GLbyte * RESTRICT v); +extern void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( APIENTRY * qglNormal3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( APIENTRY * qglNormal3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( APIENTRY * qglNormal3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( APIENTRY * qglNormal3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( APIENTRY * qglPassThrough )(GLfloat token); +extern void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat * RESTRICT values); +extern void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint * RESTRICT values); +extern void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort * RESTRICT values); +extern void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( APIENTRY * qglPointSize )(GLfloat size); +extern void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( APIENTRY * qglPolygonStipple )(const GLubyte * RESTRICT mask); +extern void ( APIENTRY * qglPopAttrib )(void); +extern void ( APIENTRY * qglPopClientAttrib )(void); +extern void ( APIENTRY * qglPopMatrix )(void); +extern void ( APIENTRY * qglPopName )(void); +extern void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint * RESTRICT textures, const GLclampf * RESTRICT priorities); +extern void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushMatrix )(void); +extern void ( APIENTRY * qglPushName )(GLuint name); +extern void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglRasterPos2dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglRasterPos2fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +extern void ( APIENTRY * qglRasterPos2iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglRasterPos2sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRasterPos3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglRasterPos3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglRasterPos3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglRasterPos3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglRasterPos4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglRasterPos4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglRasterPos4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglRasterPos4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglReadBuffer )(GLenum mode); +extern void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( APIENTRY * qglRectdv )(const GLdouble * RESTRICT v1, const GLdouble * RESTRICT v2); +extern void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( APIENTRY * qglRectfv )(const GLfloat * RESTRICT v1, const GLfloat * RESTRICT v2); +extern void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( APIENTRY * qglRectiv )(const GLint * RESTRICT v1, const GLint * RESTRICT v2); +extern void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( APIENTRY * qglRectsv )(const GLshort * RESTRICT v1, const GLshort * RESTRICT v2); +extern GLint ( APIENTRY * qglRenderMode )(GLenum mode); +extern void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint * RESTRICT buffer); +extern void ( APIENTRY * qglShadeModel )(GLenum mode); +extern void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( APIENTRY * qglStencilMask )(GLuint mask); +extern void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( APIENTRY * qglTexCoord1d )(GLdouble s); +extern void ( APIENTRY * qglTexCoord1dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord1f )(GLfloat s); +extern void ( APIENTRY * qglTexCoord1fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord1i )(GLint s); +extern void ( APIENTRY * qglTexCoord1iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord1s )(GLshort s); +extern void ( APIENTRY * qglTexCoord1sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +extern void ( APIENTRY * qglTexCoord2dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +extern void ( APIENTRY * qglTexCoord2fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +extern void ( APIENTRY * qglTexCoord2iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +extern void ( APIENTRY * qglTexCoord2sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( APIENTRY * qglTexCoord3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( APIENTRY * qglTexCoord3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( APIENTRY * qglTexCoord3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( APIENTRY * qglTexCoord3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( APIENTRY * qglTexCoord4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( APIENTRY * qglTexCoord4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( APIENTRY * qglTexCoord4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( APIENTRY * qglTexCoord4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble * RESTRICT params); +extern void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat * RESTRICT params); +extern void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint * RESTRICT params); +extern void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * RESTRICT pixels); +extern void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglVertex2dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglVertex2fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +extern void ( APIENTRY * qglVertex2iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglVertex2sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglVertex3dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex3fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglVertex3iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglVertex3sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglVertex4dv )(const GLdouble * RESTRICT v); +extern void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglVertex4fv )(const GLfloat * RESTRICT v); +extern void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglVertex4iv )(const GLint * RESTRICT v); +extern void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglVertex4sv )(const GLshort * RESTRICT v); +extern void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid * RESTRICT pointer); +extern void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +// RAVEN BEGIN +// dluetscher: added some wgl calls to the XENON version, as well as the Windows version +#if defined( _WINDOWS ) || defined( _XENON ) +typedef struct tagPIXELFORMATDESCRIPTOR PIXELFORMATDESCRIPTOR, *PPIXELFORMATDESCRIPTOR, FAR *LPPIXELFORMATDESCRIPTOR; +// RAVEN END + +extern int ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR * RESTRICT ); +extern int ( WINAPI * qwglDescribePixelFormat) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +extern int ( WINAPI * qwglGetPixelFormat)(HDC); +extern BOOL ( WINAPI * qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR * RESTRICT ); +extern BOOL ( WINAPI * qwglSwapBuffers)(HDC); + +// RAVEN BEGIN +// dluetscher: added block of functions from WGL ARB pbuffer and render texture extensions +typedef struct HPBUFFERARB__ *HPBUFFERARB; +extern BOOL ( WINAPI * qwglBindTexImageARB)(HPBUFFERARB, int); +extern BOOL ( WINAPI * qwglChoosePixelFormatARB)(HDC hdc, const int * RESTRICT piAttribIList, const FLOAT * RESTRICT pfAttribFList, UINT nMaxFormats, int * RESTRICT piFormats, UINT * RESTRICT nNumFormats); +extern HPBUFFERARB ( WINAPI * qwglCreatePbufferARB)(HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int * RESTRICT piAttribList); +extern BOOL ( WINAPI * qwglDestroyPbufferARB)(HPBUFFERARB); +extern HDC ( WINAPI * qwglGetPbufferDCARB)(HPBUFFERARB); +extern int ( WINAPI * qwglReleasePbufferDCARB)(HPBUFFERARB, HDC); +extern BOOL ( WINAPI * qwglReleaseTexImageARB)(HPBUFFERARB, int); +extern BOOL ( WINAPI * qwglSetPbufferAttribARB)(HPBUFFERARB, const int * RESTRICT ); +// RAVEN END + +extern BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +extern HGLRC ( WINAPI * qwglCreateContext)(HDC); +extern HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +extern BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +extern HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +extern HDC ( WINAPI * qwglGetCurrentDC)(VOID); +extern PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +extern BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +extern BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); + +// RAVEN BEGIN +// dluetscher: removed these wgl calls from the XENON version +#if !defined( _XENON ) +// RAVEN END +extern BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +extern BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +extern BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +extern int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF * RESTRICT ); +extern int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF * RESTRICT ); +extern BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +extern BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); +#endif + +#endif // _WINDOWS + +#if defined( __linux__ ) + +//GLX Functions +extern XVisualInfo * (*qglXChooseVisual)( Display * RESTRICT dpy, int screen, int * RESTRICT attribList ); +extern GLXContext (*qglXCreateContext)( Display * RESTRICT dpy, XVisualInfo * RESTRICT vis, GLXContext shareList, Bool direct ); +extern void (*qglXDestroyContext)( Display * RESTRICT dpy, GLXContext ctx ); +extern Bool (*qglXMakeCurrent)( Display * RESTRICT dpy, GLXDrawable drawable, GLXContext ctx); +extern void (*qglXSwapBuffers)( Display * RESTRICT dpy, GLXDrawable drawable ); +extern GLExtension_t (*qglXGetProcAddressARB)( const GLubyte * RESTRICT procname ); +extern GLXContext (*qglXGetCurrentContext)( void ); + +// make sure the code is correctly using qgl everywhere +// don't enable that when building glimp itself obviously.. +#if !defined( GLIMP ) + #include "../sys/linux/qgl_enforce.h" +#endif + +#endif // __linux__ + +// RAVEN BEGIN +// mwhitlock: loading from .xbr resource bundles +#if defined( _XENON ) +extern void ( APIENTRY * qglSetTexCacheDefault2DImageId ) (int id); +extern void ( APIENTRY * qglSetTexCacheDefaultCubeImageId ) (int id); +extern GLboolean ( APIENTRY * qglTexImageExistsInBundles ) ( unsigned long texNameCRC32 ); +extern void ( APIENTRY * qglTexImageFromCache )(int id, unsigned long texNameCRC32 ); + +// nrausch: capture backbuffer to memory +extern void xglCapture( int width, int height, void * RESTRICT pixels ); + +// mwhitlock: allow us to directly use the results of resolving from the EDRAM rendertarget as texture. +extern void ( APIENTRY * qglTexImageFromFrontBuffer )( void ); + +#endif //_XENON +// RAVEN END + +#endif // hardlinlk vs dlopen + +#endif diff --git a/src/renderer/qgl_linked.h b/src/renderer/qgl_linked.h new file mode 100644 index 0000000..5cfb10a --- /dev/null +++ b/src/renderer/qgl_linked.h @@ -0,0 +1,369 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define qglAccum glAccum +#define qglAlphaFunc glAlphaFunc +#define qglAreTexturesResident glAreTexturesResident +#define qglArrayElement glArrayElement +#define qglBegin glBegin +#define qglBindTexture glBindTexture +#define qglBitmap glBitmap +#define qglBlendFunc glBlendFunc +#define qglCallList glCallList +#define qglCallLists glCallLists +#define qglClear glClear +#define qglClearAccum glClearAccum +#define qglClearColor glClearColor +#define qglClearDepth glClearDepth +#define qglClearIndex glClearIndex +#define qglClearStencil glClearStencil +#define qglClipPlane glClipPlane +#define qglColor3b glColor3b +#define qglColor3bv glColor3bv +#define qglColor3d glColor3d +#define qglColor3dv glColor3dv +#define qglColor3f glColor3f +#define qglColor3fv glColor3fv +#define qglColor3i glColor3i +#define qglColor3iv glColor3iv +#define qglColor3s glColor3s +#define qglColor3sv glColor3sv +#define qglColor3ub glColor3ub +#define qglColor3ubv glColor3ubv +#define qglColor3ui glColor3ui +#define qglColor3uiv glColor3uiv +#define qglColor3us glColor3us +#define qglColor3usv glColor3usv +#define qglColor4b glColor4b +#define qglColor4bv glColor4bv +#define qglColor4d glColor4d +#define qglColor4dv glColor4dv +#define qglColor4f glColor4f +#define qglColor4fv glColor4fv +#define qglColor4i glColor4i +#define qglColor4iv glColor4iv +#define qglColor4s glColor4s +#define qglColor4sv glColor4sv +#define qglColor4ub glColor4ub +#define qglColor4ubv glColor4ubv +#define qglColor4ui glColor4ui +#define qglColor4uiv glColor4uiv +#define qglColor4us glColor4us +#define qglColor4usv glColor4usv +#define qglColorMask glColorMask +#define qglColorMaterial glColorMaterial +#define qglColorPointer glColorPointer +#define qglCopyPixels glCopyPixels +#define qglCopyTexImage1D glCopyTexImage1D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage1D glCopyTexSubImage1D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCullFace glCullFace +#define qglDeleteLists glDeleteLists +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRange glDepthRange +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDrawArrays glDrawArrays +#define qglDrawBuffer glDrawBuffer +#define qglDrawElements glDrawElements +#define qglDrawPixels glDrawPixels +#define qglEdgeFlag glEdgeFlag +#define qglEdgeFlagPointer glEdgeFlagPointer +#define qglEdgeFlagv glEdgeFlagv +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnd glEnd +#define qglEndList glEndList +#define qglEvalCoord1d glEvalCoord1d +#define qglEvalCoord1dv glEvalCoord1dv +#define qglEvalCoord1f glEvalCoord1f +#define qglEvalCoord1fv glEvalCoord1fv +#define qglEvalCoord2d glEvalCoord2d +#define qglEvalCoord2dv glEvalCoord2dv +#define qglEvalCoord2f glEvalCoord2f +#define qglEvalCoord2fv glEvalCoord2fv +#define qglEvalMesh1 glEvalMesh1 +#define qglEvalMesh2 glEvalMesh2 +#define qglEvalPoint1 glEvalPoint1 +#define qglEvalPoint2 glEvalPoint2 +#define qglFeedbackBuffer glFeedbackBuffer +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFogf glFogf +#define qglFogfv glFogfv +#define qglFogi glFogi +#define qglFogiv glFogiv +#define qglFrontFace glFrontFace +#define qglFrustum glFrustum +#define qglGenLists glGenLists +#define qglGenTextures glGenTextures +#define qglGetBooleanv glGetBooleanv +#define qglGetClipPlane glGetClipPlane +#define qglGetDoublev glGetDoublev +#define qglGetError glGetError +#define qglGetFloatv glGetFloatv +#define qglGetIntegerv glGetIntegerv +#define qglGetLightfv glGetLightfv +#define qglGetLightiv glGetLightiv +#define qglGetMapdv glGetMapdv +#define qglGetMapfv glGetMapfv +#define qglGetMapiv glGetMapiv +#define qglGetMaterialfv glGetMaterialfv +#define qglGetMaterialiv glGetMaterialiv +#define qglGetPixelMapfv glGetPixelMapfv +#define qglGetPixelMapuiv glGetPixelMapuiv +#define qglGetPixelMapusv glGetPixelMapusv +#define qglGetPointerv glGetPointerv +#define qglGetPolygonStipple glGetPolygonStipple +#define qglGetString glGetString +#define qglGetTexEnviv glGetTexEnviv +#define qglGetTexEnvfv glGetTexEnvfv +#define qglGetTexGendv glGetTexGendv +#define qglGetTexGenfv glGetTexGenfv +#define qglGetTexGeniv glGetTexGeniv +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglHint glHint +#define qglIndexMask glIndexMask +#define qglIndexPointer glIndexPointer +#define qglIndexd glIndexd +#define qglIndexdv glIndexdv +#define qglIndexf glIndexf +#define qglIndexfv glIndexfv +#define qglIndexi glIndexi +#define qglIndexiv glIndexiv +#define qglIndexs glIndexs +#define qglIndexsv glIndexsv +#define qglIndexub glIndexub +#define qglIndexubv glIndexubv +#define qglInitNames glInitNames +#define qglInterleavedArrays glInterleavedArrays +#define qglIsEnabled glIsEnabled +#define qglIsList glIsList +#define qglIsTexture glIsTexture +#define qglLightModelf glLightModelf +#define qglLightModelfv glLightModelfv +#define qglLightModeli glLightModeli +#define qglLightModeliv glLightModeliv +#define qglLightf glLightf +#define qglLightfv glLightfv +#define qglLighti glLighti +#define qglLightiv glLightiv +#define qglLineStipple glLineStipple +#define qglLineWidth glLineWidth +#define qglListBase glListBase +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixd glLoadMatrixd +#define qglLoadMatrixf glLoadMatrixf +#define qglLoadName glLoadName +#define qglLogicOp glLogicOp +#define qglMap1d glMap1d +#define qglMap1f glMap1f +#define qglMap2d glMap2d +#define qglMap2f glMap2f +#define qglMapGrid1d glMapGrid1d +#define qglMapGrid1f glMapGrid1f +#define qglMapGrid2d glMapGrid2d +#define qglMapGrid2f glMapGrid2f +#define qglMaterialf glMaterialf +#define qglMaterialfv glMaterialfv +#define qglMateriali glMateriali +#define qglMaterialiv glMaterialiv +#define qglMatrixMode glMatrixMode +#define qglMultMatrixd glMultMatrixd +#define qglMultMatrixf glMultMatrixf +#define qglNewList glNewList +#define qglNormal3b glNormal3b +#define qglNormal3bv glNormal3bv +#define qglNormal3d glNormal3d +#define qglNormal3dv glNormal3dv +#define qglNormal3f glNormal3f +#define qglNormal3fv glNormal3fv +#define qglNormal3i glNormal3i +#define qglNormal3iv glNormal3iv +#define qglNormal3s glNormal3s +#define qglNormal3sv glNormal3sv +#define qglNormalPointer glNormalPointer +#define qglOrtho glOrtho +#define qglPassThrough glPassThrough +#define qglPixelMapfv glPixelMapfv +#define qglPixelMapuiv glPixelMapuiv +#define qglPixelMapusv glPixelMapusv +#define qglPixelStoref glPixelStoref +#define qglPixelStorei glPixelStorei +#define qglPixelTransferf glPixelTransferf +#define qglPixelTransferi glPixelTransferi +#define qglPixelZoom glPixelZoom +#define qglPointSize glPointSize +#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +#define qglPolygonStipple glPolygonStipple +#define qglPopAttrib glPopAttrib +#define qglPopClientAttrib glPopClientAttrib +#define qglPopMatrix glPopMatrix +#define qglPopName glPopName +#define qglPrioritizeTextures glPrioritizeTextures +#define qglPushAttrib glPushAttrib +#define qglPushClientAttrib glPushClientAttrib +#define qglPushMatrix glPushMatrix +#define qglPushName glPushName +#define qglRasterPos2d glRasterPos2d +#define qglRasterPos2dv glRasterPos2dv +#define qglRasterPos2f glRasterPos2f +#define qglRasterPos2fv glRasterPos2fv +#define qglRasterPos2i glRasterPos2i +#define qglRasterPos2iv glRasterPos2iv +#define qglRasterPos2s glRasterPos2s +#define qglRasterPos2sv glRasterPos2sv +#define qglRasterPos3d glRasterPos3d +#define qglRasterPos3dv glRasterPos3dv +#define qglRasterPos3f glRasterPos3f +#define qglRasterPos3fv glRasterPos3fv +#define qglRasterPos3i glRasterPos3i +#define qglRasterPos3iv glRasterPos3iv +#define qglRasterPos3s glRasterPos3s +#define qglRasterPos3sv glRasterPos3sv +#define qglRasterPos4d glRasterPos4d +#define qglRasterPos4dv glRasterPos4dv +#define qglRasterPos4f glRasterPos4f +#define qglRasterPos4fv glRasterPos4fv +#define qglRasterPos4i glRasterPos4i +#define qglRasterPos4iv glRasterPos4iv +#define qglRasterPos4s glRasterPos4s +#define qglRasterPos4sv glRasterPos4sv +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRectd glRectd +#define qglRectdv glRectdv +#define qglRectf glRectf +#define qglRectfv glRectfv +#define qglRecti glRecti +#define qglRectiv glRectiv +#define qglRects glRects +#define qglRectsv glRectsv +#define qglRenderMode glRenderMode +#define qglRotated glRotated +#define qglRotatef glRotatef +#define qglScaled glScaled +#define qglScalef glScalef +#define qglScissor glScissor +#define qglSelectBuffer glSelectBuffer +#define qglShadeModel glShadeModel +#define qglStencilFunc glStencilFunc +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglTexCoord1d glTexCoord1d +#define qglTexCoord1dv glTexCoord1dv +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord1fv glTexCoord1fv +#define qglTexCoord1i glTexCoord1i +#define qglTexCoord1iv glTexCoord1iv +#define qglTexCoord1s glTexCoord1s +#define qglTexCoord1sv glTexCoord1sv +#define qglTexCoord2d glTexCoord2d +#define qglTexCoord2dv glTexCoord2dv +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord2fv glTexCoord2fv +#define qglTexCoord2i glTexCoord2i +#define qglTexCoord2iv glTexCoord2iv +#define qglTexCoord2s glTexCoord2s +#define qglTexCoord2sv glTexCoord2sv +#define qglTexCoord3d glTexCoord3d +#define qglTexCoord3dv glTexCoord3dv +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord3fv glTexCoord3fv +#define qglTexCoord3i glTexCoord3i +#define qglTexCoord3iv glTexCoord3iv +#define qglTexCoord3s glTexCoord3s +#define qglTexCoord3sv glTexCoord3sv +#define qglTexCoord4d glTexCoord4d +#define qglTexCoord4dv glTexCoord4dv +#define qglTexCoord4f glTexCoord4f +#define qglTexCoord4fv glTexCoord4fv +#define qglTexCoord4i glTexCoord4i +#define qglTexCoord4iv glTexCoord4iv +#define qglTexCoord4s glTexCoord4s +#define qglTexCoord4sv glTexCoord4sv +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexEnviv glTexEnviv +#define qglTexGend glTexGend +#define qglTexGendv glTexGendv +#define qglTexGenf glTexGenf +#define qglTexGenfv glTexGenfv +#define qglTexGeni glTexGeni +#define qglTexGeniv glTexGeniv +#define qglTexImage1D glTexImage1D +#define qglTexImage2D glTexImage2D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexParameteriv glTexParameteriv +#define qglTexSubImage1D glTexSubImage1D +#define qglTexSubImage2D glTexSubImage2D +#define qglTranslated glTranslated +#define qglTranslatef glTranslatef +#define qglVertex2d glVertex2d +#define qglVertex2dv glVertex2dv +#define qglVertex2f glVertex2f +#define qglVertex2fv glVertex2fv +#define qglVertex2i glVertex2i +#define qglVertex2iv glVertex2iv +#define qglVertex2s glVertex2s +#define qglVertex2sv glVertex2sv +#define qglVertex3d glVertex3d +#define qglVertex3dv glVertex3dv +#define qglVertex3f glVertex3f +#define qglVertex3fv glVertex3fv +#define qglVertex3i glVertex3i +#define qglVertex3iv glVertex3iv +#define qglVertex3s glVertex3s +#define qglVertex3sv glVertex3sv +#define qglVertex4d glVertex4d +#define qglVertex4dv glVertex4dv +#define qglVertex4f glVertex4f +#define qglVertex4fv glVertex4fv +#define qglVertex4i glVertex4i +#define qglVertex4iv glVertex4iv +#define qglVertex4s glVertex4s +#define qglVertex4sv glVertex4sv +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport + +#ifdef GLX_VERSION_1_1 // catch all for any GLX-aware situation +#define qglXChooseVisual glXChooseVisual +#define qglXCreateContext glXCreateContext +#define qglXDestroyContext glXDestroyContext +#define qglXMakeCurrent glXMakeCurrent +#define qglXSwapBuffers glXSwapBuffers +#define qglXGetProcAddressARB glXGetProcAddressARB +#endif diff --git a/src/renderer/renderentity.cpp b/src/renderer/renderentity.cpp new file mode 100644 index 0000000..1fbdb13 --- /dev/null +++ b/src/renderer/renderentity.cpp @@ -0,0 +1,159 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "RenderWorld_local.h" + +rvRenderEffectLocal::rvRenderEffectLocal() { + memset( &parms, 0, sizeof( parms ) ); + memset( modelMatrix, 0, sizeof( modelMatrix ) ); + model = NULL; + effect = NULL; + index = 0; + referenceBounds = bounds_zero; + world = NULL; + lastModifiedFrameNum = 0; + writeToDemo = false; + archived = false; + dynamicModel = NULL; + dynamicModelFrameCount = 0; + viewCount = 0; + viewEffect = NULL; + visibleCount = 0; + effectRefs = NULL; +} + +rvRenderEffectLocal::~rvRenderEffectLocal() { + if ( effect && bse ) { + bse->FreeEffect( this ); + } +} + +void rvRenderEffectLocal::FreeRenderEffect() { + if ( world ) { + world->FreeEffectDef( index ); + } +} + +void rvRenderEffectLocal::UpdateRenderEffect( const renderEffect_t *newParms, bool forceUpdate ) { + if ( world ) { + world->UpdateEffectDef( index, newParms, tr.viewDef ? tr.viewDef->renderView.time : 0 ); + } +} + +void rvRenderEffectLocal::ForceUpdate() { + lastModifiedFrameNum = -1; +} + +int rvRenderEffectLocal::GetIndex() { + return index; +} + +idRenderEntityLocal::idRenderEntityLocal() { + memset( &parms, 0, sizeof( parms ) ); + memset( modelMatrix, 0, sizeof( modelMatrix ) ); + + world = NULL; + index = 0; + lastModifiedFrameNum = 0; + archived = false; + dynamicModel = NULL; + dynamicModelFrameCount = 0; + cachedDynamicModel = NULL; + referenceBounds = bounds_zero; + viewCount = 0; + viewEntity = NULL; + visibleCount = 0; + decals = NULL; + overlay = NULL; + entityRefs = NULL; + firstInteraction = NULL; + lastInteraction = NULL; + needsPortalSky = false; +} + +void idRenderEntityLocal::FreeRenderEntity() { +} + +void idRenderEntityLocal::UpdateRenderEntity( const renderEntity_t *re, bool forceUpdate ) { +} + +void idRenderEntityLocal::GetRenderEntity( renderEntity_t *re ) { +} + +void idRenderEntityLocal::ForceUpdate() { +} + +int idRenderEntityLocal::GetIndex() { + return index; +} + +void idRenderEntityLocal::ProjectOverlay( const idPlane localTextureAxis[2], const idMaterial *material ) { +} +void idRenderEntityLocal::RemoveDecals() { +} + +//====================================================================== + +idRenderLightLocal::idRenderLightLocal() { + memset( &parms, 0, sizeof( parms ) ); + memset( modelMatrix, 0, sizeof( modelMatrix ) ); + memset( shadowFrustums, 0, sizeof( shadowFrustums ) ); + memset( lightProject, 0, sizeof( lightProject ) ); + memset( frustum, 0, sizeof( frustum ) ); + memset( frustumWindings, 0, sizeof( frustumWindings ) ); + + lightHasMoved = false; + world = NULL; + index = 0; + areaNum = 0; + lastModifiedFrameNum = 0; + archived = false; + lightShader = NULL; + falloffImage = NULL; + globalLightOrigin = vec3_zero; + frustumTris = NULL; + numShadowFrustums = 0; + viewCount = 0; + viewLight = NULL; + references = NULL; + foggedPortals = NULL; + firstInteraction = NULL; + lastInteraction = NULL; +} + +void idRenderLightLocal::FreeRenderLight() { +} +void idRenderLightLocal::UpdateRenderLight( const renderLight_t *re, bool forceUpdate ) { +} +void idRenderLightLocal::GetRenderLight( renderLight_t *re ) { +} +void idRenderLightLocal::ForceUpdate() { +} +int idRenderLightLocal::GetIndex() { + return index; +} diff --git a/src/renderer/rendersystem.cpp b/src/renderer/rendersystem.cpp new file mode 100644 index 0000000..4148655 --- /dev/null +++ b/src/renderer/rendersystem.cpp @@ -0,0 +1,1186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" +#include "rvRenderModelMD5R.h" + +idRenderSystemLocal tr; +idRenderSystem *renderSystem = &tr; + + +/* +===================== +R_PerformanceCounters + +This prints both front and back end counters, so it should +only be called when the back end thread is idle. +===================== +*/ +static void R_PerformanceCounters( void ) { + if ( r_showPrimitives.GetInteger() != 0 ) { + + float megaBytes = globalImages->SumOfUsedImages() / ( 1024*1024.0 ); + + if ( r_showPrimitives.GetInteger() > 1 ) { + common->Printf( "v:%i ds:%i t:%i/%i v:%i/%i st:%i sv:%i image:%5.1f MB\n", + tr.pc.c_numViews, + backEnd.pc.c_drawElements + backEnd.pc.c_shadowElements, + backEnd.pc.c_drawIndexes / 3, + ( backEnd.pc.c_drawIndexes - backEnd.pc.c_drawRefIndexes ) / 3, + backEnd.pc.c_drawVertexes, + ( backEnd.pc.c_drawVertexes - backEnd.pc.c_drawRefVertexes ), + backEnd.pc.c_shadowIndexes / 3, + backEnd.pc.c_shadowVertexes, + megaBytes + ); + } else { + common->Printf( "views:%i draws:%i tris:%i (shdw:%i) (vbo:%i) image:%5.1f MB\n", + tr.pc.c_numViews, + backEnd.pc.c_drawElements + backEnd.pc.c_shadowElements, + ( backEnd.pc.c_drawIndexes + backEnd.pc.c_shadowIndexes ) / 3, + backEnd.pc.c_shadowIndexes / 3, + backEnd.pc.c_vboIndexes / 3, + megaBytes + ); + } + } + + if ( r_showDynamic.GetBool() ) { + common->Printf( "callback:%i md5:%i dfrmVerts:%i dfrmTris:%i tangTris:%i guis:%i\n", + tr.pc.c_entityDefCallbacks, + tr.pc.c_generateMd5, + tr.pc.c_deformedVerts, + tr.pc.c_deformedIndexes/3, + tr.pc.c_tangentIndexes/3, + tr.pc.c_guiSurfs + ); + } + + if ( r_showCull.GetBool() ) { + common->Printf( "%i sin %i sclip %i sout %i bin %i bout\n", + tr.pc.c_sphere_cull_in, tr.pc.c_sphere_cull_clip, tr.pc.c_sphere_cull_out, + tr.pc.c_box_cull_in, tr.pc.c_box_cull_out ); + } + + if ( r_showAlloc.GetBool() ) { + common->Printf( "alloc:%i free:%i\n", tr.pc.c_alloc, tr.pc.c_free ); + } + + if ( r_showInteractions.GetBool() ) { + common->Printf( "createInteractions:%i createLightTris:%i createShadowVolumes:%i\n", + tr.pc.c_createInteractions, tr.pc.c_createLightTris, tr.pc.c_createShadowVolumes ); + } + if ( r_showDefs.GetBool() ) { + common->Printf( "viewEntities:%i shadowEntities:%i viewLights:%i\n", tr.pc.c_visibleViewEntities, + tr.pc.c_shadowViewEntities, tr.pc.c_viewLights ); + } + if ( r_showUpdates.GetBool() ) { + common->Printf( "entityUpdates:%i entityRefs:%i lightUpdates:%i lightRefs:%i\n", + tr.pc.c_entityUpdates, tr.pc.c_entityReferences, + tr.pc.c_lightUpdates, tr.pc.c_lightReferences ); + } + if ( r_showMemory.GetBool() ) { + int m1 = frameData ? frameData->memoryHighwater : 0; + common->Printf( "frameData: %i (%i)\n", R_CountFrameData(), m1 ); + } + if ( r_showLightScale.GetBool() ) { + common->Printf( "lightScale: %f\n", backEnd.pc.maxLightValue ); + } + + memset( &tr.pc, 0, sizeof( tr.pc ) ); + memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + + +/* +==================== +R_IssueRenderCommands + +Called by R_EndFrame each frame +==================== +*/ +static void R_IssueRenderCommands( void ) { + if ( frameData->cmdHead->commandId == RC_NOP + && !frameData->cmdHead->next ) { + // nothing to issue + return; + } + + // r_skipBackEnd allows the entire time of the back end + // to be removed from performance measurements, although + // nothing will be drawn to the screen. If the prints + // are going to a file, or r_skipBackEnd is later disabled, + // usefull data can be received. + + // r_skipRender is usually more usefull, because it will still + // draw 2D graphics + if ( !r_skipBackEnd.GetBool() ) { + RB_ExecuteBackEndCommands( frameData->cmdHead ); + } + + R_ClearCommandChain(); +} + +/* +============ +R_GetCommandBuffer + +Returns memory for a command buffer (stretchPicCommand_t, +drawSurfsCommand_t, etc) and links it to the end of the +current command chain. +============ +*/ +void *R_GetCommandBuffer( int bytes ) { + emptyCommand_t *cmd; + + cmd = (emptyCommand_t *)R_FrameAlloc( bytes ); + cmd->next = NULL; + frameData->cmdTail->next = &cmd->commandId; + frameData->cmdTail = cmd; + + return (void *)cmd; +} + + +/* +==================== +R_ClearCommandChain + +Called after every buffer submission +and by R_ToggleSmpFrame +==================== +*/ +void R_ClearCommandChain( void ) { + // clear the command chain + frameData->cmdHead = frameData->cmdTail = (emptyCommand_t *)R_FrameAlloc( sizeof( *frameData->cmdHead ) ); + frameData->cmdHead->commandId = RC_NOP; + frameData->cmdHead->next = NULL; +} + +/* +================= +R_ViewStatistics +================= +*/ +static void R_ViewStatistics( viewDef_t *parms ) { + // report statistics about this view + if ( !r_showSurfaces.GetBool() ) { + return; + } + common->Printf( "view:%p surfs:%i\n", parms, parms->numDrawSurfs ); +} + +/* +============= +R_AddDrawViewCmd + +This is the main 3D rendering command. A single scene may +have multiple views if a mirror, portal, or dynamic texture is present. +============= +*/ +void R_AddDrawViewCmd( viewDef_t *parms ) { + drawSurfsCommand_t *cmd; + + cmd = (drawSurfsCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + cmd->commandId = RC_DRAW_VIEW; + + cmd->viewDef = parms; + + if ( parms->viewEntitys ) { + // save the command for r_lockSurfaces debugging + tr.lockSurfacesCmd = *cmd; + } + + tr.pc.c_numViews++; + + R_ViewStatistics( parms ); +} + + +//================================================================================= + + +/* +====================== +R_LockSurfaceScene + +r_lockSurfaces allows a developer to move around +without changing the composition of the scene, including +culling. The only thing that is modified is the +view position and axis, no front end work is done at all + + +Add the stored off command again, so the new rendering will use EXACTLY +the same surfaces, including all the culling, even though the transformation +matricies have been changed. This allow the culling tightness to be +evaluated interactively. +====================== +*/ +void R_LockSurfaceScene( viewDef_t *parms ) { + drawSurfsCommand_t *cmd; + viewEntity_t *vModel; + + // set the matrix for world space to eye space + R_SetViewMatrix( parms ); + tr.lockSurfacesCmd.viewDef->worldSpace = parms->worldSpace; + + // update the view origin and axis, and all + // the entity matricies + for( vModel = tr.lockSurfacesCmd.viewDef->viewEntitys ; vModel ; vModel = vModel->next ) { + myGlMultMatrix( vModel->modelMatrix, + tr.lockSurfacesCmd.viewDef->worldSpace.modelViewMatrix, + vModel->modelViewMatrix ); + } + + // add the stored off surface commands again + cmd = (drawSurfsCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + *cmd = tr.lockSurfacesCmd; +} + +/* +============= +R_CheckCvars + +See if some cvars that we watch have changed +============= +*/ +static void R_CheckCvars( void ) { + globalImages->CheckCvars(); + + // gamma stuff + if ( r_gamma.IsModified() || r_brightness.IsModified() ) { + r_gamma.ClearModified(); + r_brightness.ClearModified(); + R_SetColorMappings(); + } + + // check for changes to logging state + GLimp_EnableLogging( r_logFile.GetInteger() != 0 ); +} + +/* +============= +idRenderSystemLocal::idRenderSystemLocal +============= +*/ +idRenderSystemLocal::idRenderSystemLocal( void ) { + Clear(); +} + +/* +============= +idRenderSystemLocal::~idRenderSystemLocal +============= +*/ +idRenderSystemLocal::~idRenderSystemLocal( void ) { +} + +void idRenderSystemLocal::GetValidModes( idStr &mode4x3Text, idStr &mode4x3Values, + idStr &mode16x9Text, idStr &mode16x9Values, + idStr &mode16x10Text, idStr &mode16x10Values ) { + mode4x3Text = "640x480;800x600;1024x768;1280x960;1600x1200"; + mode4x3Values = "3;4;5;6;7"; + mode16x9Text = "1280x720;1366x768;1600x900;1920x1080"; + mode16x9Values = "-1;-1;-1;-1"; + mode16x10Text = "1280x800;1440x900;1680x1050;1920x1200"; + mode16x10Values = "-1;-1;-1;-1"; +} + +void idRenderSystemLocal::RemoveAllModelReferences( idRenderModel *model ) { + for ( int i = 0; i < worlds.Num(); ++i ) { + if ( worlds[i] ) { + worlds[i]->RemoveAllModelReferences( model ); + } + } +} + +void idRenderSystemLocal::ExportMD5R( bool compressed ) { + rvRenderModelMD5R::WriteAll( compressed ); +} + +void idRenderSystemLocal::CopyPrimBatchTriangles( idDrawVert *destDrawVerts, + glIndex_t *destIndices, rvMesh *primBatchMesh, const rvSilTraceVertT *silTraceVerts ) { + if ( primBatchMesh ) { + primBatchMesh->CopyTriangles( destDrawVerts, destIndices, silTraceVerts ); + } +} + +void idRenderSystemLocal::TrackTextureUsage( TextureTrackCommand command, int frametime, const char *name ) { + (void)command; + (void)frametime; + (void)name; +} + +void idRenderSystemLocal::DrawStretchCopy( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, const idMaterial *material ) { + DrawStretchPic( x, y, w, h, s1, t1, s2, t2, material ); +} + +void idRenderSystemLocal::DrawTinyChar( int x, int y, int ch, const idMaterial *material ) { + ch &= 255; + if ( ch == ' ' || y < -8 ) { + return; + } + const float row = ( ch >> 4 ) * 0.0625f; + const float col = ( ch & 15 ) * 0.0625f; + DrawStretchPic( static_cast( x ), static_cast( y ), 4.0f, 8.0f, + col, row, col + 0.0625f, row + 0.0625f, material ); +} + +void idRenderSystemLocal::DrawTinyStringExt( int x, int y, const char *string, + const idVec4 &setColor, bool forceColor, const idMaterial *material ) { + SetColor( setColor ); + for ( const char *s = string; s && *s; ++s ) { + int escapeType = 0; + const int escapeLength = idStr::IsEscape( s, &escapeType ); + if ( escapeLength ) { + if ( !forceColor && escapeType == S_ESCAPE_COLORINDEX ) { + SetColor( idStr::ColorForIndex( s[1] ) ); + } + s += escapeLength - 1; + continue; + } + DrawTinyChar( x, y, *s, material ); + x += 4; + } + SetColor( setColor ); +} + +void idRenderSystemLocal::BeginFrame( viewDef_t *newViewDef, int windowWidth, int windowHeight ) { + viewDef = newViewDef; + BeginFrame( windowWidth, windowHeight ); +} + +/* +============= +SetColor + +This can be used to pass general information to the current material, not +just colors +============= +*/ +void idRenderSystemLocal::SetColor( const idVec4 &rgba ) { + guiModel->SetColor( rgba[0], rgba[1], rgba[2], rgba[3] ); +} + + +/* +============= +SetColor4 +============= +*/ +void idRenderSystemLocal::SetColor4( float r, float g, float b, float a ) { + guiModel->SetColor( r, g, b, a ); +} + +/* +============= +DrawStretchPic +============= +*/ +void idRenderSystemLocal::DrawStretchPic( const idDrawVert *verts, const glIndex_t *indexes, int vertCount, int indexCount, const idMaterial *material, + bool clip ) { + guiModel->DrawStretchPic( verts, indexes, vertCount, indexCount, material, + clip, 0.0f, 0.0f, 640.0f, 480.0f ); +} + +/* +============= +DrawStretchPic + +x/y/w/h are in the 0,0 to 640,480 range +============= +*/ +void idRenderSystemLocal::DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ) { + guiModel->DrawStretchPic( x, y, w, h, s1, t1, s2, t2, material ); +} + +/* +============= +DrawStretchTri + +x/y/w/h are in the 0,0 to 640,480 range +============= +*/ +void idRenderSystemLocal::DrawStretchTri( idVec2 p1, idVec2 p2, idVec2 p3, idVec2 t1, idVec2 t2, idVec2 t3, const idMaterial *material ) { + tr.guiModel->DrawStretchTri( p1, p2, p3, t1, t2, t3, material ); +} + +/* +============= +GlobalToNormalizedDeviceCoordinates +============= +*/ +void idRenderSystemLocal::GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ) { + if ( tr.viewDef ) { + R_GlobalToNormalizedDeviceCoordinates( tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, global, ndc ); + } else { + R_GlobalToNormalizedDeviceCoordinates( tr.primaryView->worldSpace.modelViewMatrix, tr.primaryView->projectionMatrix, global, ndc ); + } +} + +/* +============= +GlobalToNormalizedDeviceCoordinates +============= +*/ +void idRenderSystemLocal::GetGLSettings( int& width, int& height ) { + width = glConfig.vidWidth; + height = glConfig.vidHeight; +} + +/* +===================== +idRenderSystemLocal::DrawSmallChar + +small chars are drawn at native screen resolution +===================== +*/ +void idRenderSystemLocal::DrawSmallChar( int x, int y, int ch, const idMaterial *material ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -SMALLCHAR_HEIGHT ) { + return; + } + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625f; + fcol = col * 0.0625f; + size = 0.0625f; + + DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + material ); +} + +/* +================== +idRenderSystemLocal::DrawSmallString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void idRenderSystemLocal::DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) { + idVec4 color; + const unsigned char *s; + int xx; + + // draw the colored text + s = (const unsigned char*)string; + xx = x; + SetColor( setColor ); + while ( *s ) { + int escapeType = 0; + const int escapeLength = idStr::IsEscape( (const char *)s, &escapeType ); + if ( escapeLength ) { + if ( !forceColor && escapeType == S_ESCAPE_COLORINDEX ) { + if ( s[1] == C_COLOR_DEFAULT ) { + SetColor( setColor ); + } else { + color = idStr::ColorForIndex( s[1] ); + color[3] = setColor[3]; + SetColor( color ); + } + } else if ( !forceColor && escapeType == S_ESCAPE_COLOR && escapeLength == 5 ) { + color.Set( ( s[2] - '0' ) / 9.0f, ( s[3] - '0' ) / 9.0f, ( s[4] - '0' ) / 9.0f, setColor[3] ); + SetColor( color ); + } + s += escapeLength; + continue; + } + DrawSmallChar( xx, y, *s, material ); + xx += SMALLCHAR_WIDTH; + s++; + } + SetColor( colorWhite ); +} + +/* +===================== +idRenderSystemLocal::DrawBigChar +===================== +*/ +void idRenderSystemLocal::DrawBigChar( int x, int y, int ch, const idMaterial *material ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -BIGCHAR_HEIGHT ) { + return; + } + + row = ch >> 4; + col = ch & 15; + + frow = row * 0.0625f; + fcol = col * 0.0625f; + size = 0.0625f; + + DrawStretchPic( x, y, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + material ); +} + +/* +================== +idRenderSystemLocal::DrawBigString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void idRenderSystemLocal::DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ) { + idVec4 color; + const char *s; + int xx; + + // draw the colored text + s = string; + xx = x; + SetColor( setColor ); + while ( *s ) { + int escapeType = 0; + const int escapeLength = idStr::IsEscape( s, &escapeType ); + if ( escapeLength ) { + if ( !forceColor && escapeType == S_ESCAPE_COLORINDEX ) { + if ( s[1] == C_COLOR_DEFAULT ) { + SetColor( setColor ); + } else { + color = idStr::ColorForIndex( s[1] ); + color[3] = setColor[3]; + SetColor( color ); + } + } else if ( !forceColor && escapeType == S_ESCAPE_COLOR && escapeLength == 5 ) { + color.Set( ( s[2] - '0' ) / 9.0f, ( s[3] - '0' ) / 9.0f, ( s[4] - '0' ) / 9.0f, setColor[3] ); + SetColor( color ); + } + s += escapeLength; + continue; + } + DrawBigChar( xx, y, *s, material ); + xx += BIGCHAR_WIDTH; + s++; + } + SetColor( colorWhite ); +} + +//====================================================================================== + +/* +================== +SetBackEndRenderer + +Check for changes in the back end renderSystem, possibly invalidating cached data +================== +*/ +void idRenderSystemLocal::SetBackEndRenderer() { + if ( !r_renderer.IsModified() ) { + return; + } + + bool oldVPstate = backEndRendererHasVertexPrograms; + + backEndRenderer = BE_BAD; + + if ( idStr::Icmp( r_renderer.GetString(), "arb" ) == 0 ) { + backEndRenderer = BE_ARB; + } else if ( idStr::Icmp( r_renderer.GetString(), "arb2" ) == 0 ) { + if ( glConfig.allowARB2Path ) { + backEndRenderer = BE_ARB2; + } + } else if ( idStr::Icmp( r_renderer.GetString(), "nv10" ) == 0 ) { + if ( glConfig.allowNV10Path ) { + backEndRenderer = BE_NV10; + } + } else if ( idStr::Icmp( r_renderer.GetString(), "nv20" ) == 0 ) { + if ( glConfig.allowNV20Path ) { + backEndRenderer = BE_NV20; + } + } else if ( idStr::Icmp( r_renderer.GetString(), "r200" ) == 0 ) { + if ( glConfig.allowR200Path ) { + backEndRenderer = BE_R200; + } + } + + // fallback + if ( backEndRenderer == BE_BAD ) { + // choose the best + if ( glConfig.allowARB2Path ) { + backEndRenderer = BE_ARB2; + } else if ( glConfig.allowR200Path ) { + backEndRenderer = BE_R200; + } else if ( glConfig.allowNV20Path ) { + backEndRenderer = BE_NV20; + } else if ( glConfig.allowNV10Path ) { + backEndRenderer = BE_NV10; + } else { + // the others are considered experimental + backEndRenderer = BE_ARB; + } + } + + backEndRendererHasVertexPrograms = false; + backEndRendererMaxLight = 1.0; + + switch( backEndRenderer ) { + case BE_ARB: + common->Printf( "using ARB renderSystem\n" ); + break; + case BE_NV10: + common->Printf( "using NV10 renderSystem\n" ); + break; + case BE_NV20: + common->Printf( "using NV20 renderSystem\n" ); + backEndRendererHasVertexPrograms = true; + break; + case BE_R200: + common->Printf( "using R200 renderSystem\n" ); + backEndRendererHasVertexPrograms = true; + break; + case BE_ARB2: + common->Printf( "using ARB2 renderSystem\n" ); + backEndRendererHasVertexPrograms = true; + backEndRendererMaxLight = 999; + break; + default: + common->FatalError( "SetbackEndRenderer: bad back end" ); + } + + // clear the vertex cache if we are changing between + // using vertex programs and not, because specular and + // shadows will be different data + if ( oldVPstate != backEndRendererHasVertexPrograms ) { + vertexCache.PurgeAll(); + if ( primaryWorld ) { + primaryWorld->FreeInteractions(); + } + } + + r_renderer.ClearModified(); +} + +/* +==================== +BeginFrame +==================== +*/ +void idRenderSystemLocal::BeginFrame( int windowWidth, int windowHeight ) { + setBufferCommand_t *cmd; + + if ( !glConfig.isInitialized ) { + return; + } + + // determine which back end we will use + SetBackEndRenderer(); + + guiModel->Clear(); + + // for the larger-than-window tiled rendering screenshots + if ( tiledViewport[0] ) { + windowWidth = tiledViewport[0]; + windowHeight = tiledViewport[1]; + } + + glConfig.vidWidth = windowWidth; + glConfig.vidHeight = windowHeight; + + renderCrops[0].x = 0; + renderCrops[0].y = 0; + renderCrops[0].width = windowWidth; + renderCrops[0].height = windowHeight; + currentRenderCrop = 0; + + // screenFraction is just for quickly testing fill rate limitations + if ( r_screenFraction.GetInteger() != 100 ) { + int w = SCREEN_WIDTH * r_screenFraction.GetInteger() / 100.0f; + int h = SCREEN_HEIGHT * r_screenFraction.GetInteger() / 100.0f; + CropRenderSize( w, h ); + } + + + // this is the ONLY place this is modified + frameCount++; + + // just in case we did a common->Error while this + // was set + guiRecursionLevel = 0; + + // the first rendering will be used for commands like + // screenshot, rather than a possible subsequent remote + // or mirror render +// primaryWorld = NULL; + + // set the time for shader effects in 2D rendering + frameShaderTime = eventLoop->Milliseconds() * 0.001; + + // + // draw buffer stuff + // + cmd = (setBufferCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + cmd->commandId = RC_SET_BUFFER; + cmd->frameCount = frameCount; + + if ( r_frontBuffer.GetBool() ) { + cmd->buffer = (int)GL_FRONT; + } else { + cmd->buffer = (int)GL_BACK; + } +} + +void idRenderSystemLocal::WriteDemoPics() { + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_GUI_MODEL ); + guiModel->WriteToDemo( session->writeDemo ); +} + +void idRenderSystemLocal::DrawDemoPics() { + demoGuiModel->EmitFullScreen(); +} + +/* +============= +EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void idRenderSystemLocal::EndFrame( int *frontEndMsec, int *backEndMsec, int *numVerts, int *numIndexes ) { + emptyCommand_t *cmd; + + if ( !glConfig.isInitialized ) { + return; + } + + // close any gui drawing + guiModel->EmitFullScreen(); + guiModel->Clear(); + + // save out timing information + if ( frontEndMsec ) { + *frontEndMsec = pc.frontEndMsec; + } + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + if ( numVerts ) { + *numVerts = backEnd.pc.c_drawElements; + } + if ( numIndexes ) { + *numIndexes = backEnd.pc.c_drawIndexes; + } + + // print any other statistics and clear all of them + R_PerformanceCounters(); + + // check for dynamic changes that require some initialization + R_CheckCvars(); + + // check for errors + GL_CheckErrors(); + + // add the swapbuffers command + cmd = (emptyCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + cmd->commandId = RC_SWAP_BUFFERS; + + // start the back end up again with the new command list + R_IssueRenderCommands(); + + // use the other buffers next frame, because another CPU + // may still be rendering into the current buffers + R_ToggleSmpFrame(); + + // we can now release the vertexes used this frame + vertexCache.EndFrame(); + + if ( session->writeDemo ) { + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_END_FRAME ); + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_END_FRAME\n" ); + } + } + +} + +/* +===================== +RenderViewToViewport + +Converts from SCREEN_WIDTH / SCREEN_HEIGHT coordinates to current cropped pixel coordinates +===================== +*/ +void idRenderSystemLocal::RenderViewToViewport( const renderView_t *renderView, idScreenRect *viewport ) { + renderCrop_t *rc = &renderCrops[currentRenderCrop]; + + float wRatio = (float)rc->width / SCREEN_WIDTH; + float hRatio = (float)rc->height / SCREEN_HEIGHT; + + viewport->x1 = idMath::Ftoi( rc->x + renderView->x * wRatio ); + viewport->x2 = idMath::Ftoi( rc->x + floor( ( renderView->x + renderView->width ) * wRatio + 0.5f ) - 1 ); + viewport->y1 = idMath::Ftoi( ( rc->y + rc->height ) - floor( ( renderView->y + renderView->height ) * hRatio + 0.5f ) ); + viewport->y2 = idMath::Ftoi( ( rc->y + rc->height ) - floor( renderView->y * hRatio + 0.5f ) - 1 ); +} + +static int RoundDownToPowerOfTwo( int v ) { + int i; + + for ( i = 0 ; i < 20 ; i++ ) { + if ( ( 1 << i ) == v ) { + return v; + } + if ( ( 1 << i ) > v ) { + return 1 << ( i-1 ); + } + } + return 1<EmitFullScreen(); + guiModel->Clear(); + + if ( width < 1 || height < 1 ) { + common->Error( "CropRenderSize: bad sizes" ); + } + + if ( session->writeDemo ) { + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_CROP_RENDER ); + session->writeDemo->WriteInt( width ); + session->writeDemo->WriteInt( height ); + session->writeDemo->WriteInt( makePowerOfTwo ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_CROP_RENDER\n" ); + } + } + + // convert from virtual SCREEN_WIDTH/SCREEN_HEIGHT coordinates to physical OpenGL pixels + renderView_t renderView; + renderView.x = 0; + renderView.y = 0; + renderView.width = width; + renderView.height = height; + + idScreenRect r; + RenderViewToViewport( &renderView, &r ); + + width = r.x2 - r.x1 + 1; + height = r.y2 - r.y1 + 1; + + if ( forceDimensions ) { + // just give exactly what we ask for + width = renderView.width; + height = renderView.height; + } + + // if makePowerOfTwo, drop to next lower power of two after scaling to physical pixels + if ( makePowerOfTwo ) { + width = RoundDownToPowerOfTwo( width ); + height = RoundDownToPowerOfTwo( height ); + // FIXME: megascreenshots with offset viewports don't work right with this yet + } + + renderCrop_t *rc = &renderCrops[currentRenderCrop]; + + // we might want to clip these to the crop window instead + while ( width > glConfig.vidWidth ) { + width >>= 1; + } + while ( height > glConfig.vidHeight ) { + height >>= 1; + } + + if ( currentRenderCrop == MAX_RENDER_CROPS ) { + common->Error( "idRenderSystemLocal::CropRenderSize: currentRenderCrop == MAX_RENDER_CROPS" ); + } + + currentRenderCrop++; + + rc = &renderCrops[currentRenderCrop]; + + rc->x = 0; + rc->y = 0; + rc->width = width; + rc->height = height; +} + +/* +================ +UnCrop +================ +*/ +void idRenderSystemLocal::UnCrop() { + if ( !glConfig.isInitialized ) { + return; + } + + if ( currentRenderCrop < 1 ) { + common->Error( "idRenderSystemLocal::UnCrop: currentRenderCrop < 1" ); + } + + // close any gui drawing + guiModel->EmitFullScreen(); + guiModel->Clear(); + + currentRenderCrop--; + + if ( session->writeDemo ) { + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_UNCROP_RENDER ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_UNCROP\n" ); + } + } +} + +/* +================ +CaptureRenderToImage +================ +*/ +void idRenderSystemLocal::CaptureRenderToImage( const char *imageName ) { + if ( !glConfig.isInitialized ) { + return; + } + guiModel->EmitFullScreen(); + guiModel->Clear(); + + if ( session->writeDemo ) { + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_CAPTURE_RENDER ); + session->writeDemo->WriteHashString( imageName ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_CAPTURE_RENDER: %s\n", imageName ); + } + } + + // look up the image before we create the render command, because it + // may need to sync to create the image + idImage *image = globalImages->ImageFromFile(imageName, TF_DEFAULT, true, TR_REPEAT, TD_DEFAULT); + + renderCrop_t *rc = &renderCrops[currentRenderCrop]; + + copyRenderCommand_t *cmd = (copyRenderCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + cmd->commandId = RC_COPY_RENDER; + cmd->x = rc->x; + cmd->y = rc->y; + cmd->imageWidth = rc->width; + cmd->imageHeight = rc->height; + cmd->image = image; + + guiModel->Clear(); +} + +void idRenderSystemLocal::CaptureRenderToMemory( void *buffer ) { + if ( !glConfig.isInitialized || !buffer ) { + return; + } + renderCrop_t *rc = &renderCrops[currentRenderCrop]; + guiModel->EmitFullScreen(); + guiModel->Clear(); + R_IssueRenderCommands(); + qglReadBuffer( GL_BACK ); + const int paddedPixels = ( rc->width + 3 ) * rc->height; + byte *rgb = static_cast( R_StaticAlloc( paddedPixels * 3 ) ); + qglReadPixels( rc->x, rc->y, rc->width, rc->height, GL_RGB, GL_UNSIGNED_BYTE, rgb ); + byte *rgba = static_cast( buffer ); + for ( int i = 0; i < paddedPixels; ++i ) { + rgba[i * 4 + 0] = rgb[i * 3 + 0]; + rgba[i * 4 + 1] = rgb[i * 3 + 1]; + rgba[i * 4 + 2] = rgb[i * 3 + 2]; + rgba[i * 4 + 3] = 255; + } + R_StaticFree( rgb ); +} + +void idRenderSystemLocal::DebugGraph( float cur, float minValue, float maxValue, const idVec4 &color ) { + for ( int y = 0; y < 64; ++y ) { + memmove( &debugGraph[y][0][0], &debugGraph[y][1][0], 255 * 4 ); + } + const float range = maxValue - minValue; + const float normalized = range != 0.0f ? ( maxValue - cur ) / range : 0.0f; + const int split = idMath::ClampInt( 0, 64, idMath::Ftoi( normalized * 64.0f ) ); + for ( int y = 0; y < 64; ++y ) { + byte *pixel = debugGraph[y][255]; + if ( y < split ) { + memset( pixel, 0, 4 ); + } else { + pixel[0] = static_cast( idMath::ClampInt( 0, 255, idMath::Ftoi( color.x * 255.0f ) ) ); + pixel[1] = static_cast( idMath::ClampInt( 0, 255, idMath::Ftoi( color.y * 255.0f ) ) ); + pixel[2] = static_cast( idMath::ClampInt( 0, 255, idMath::Ftoi( color.z * 255.0f ) ) ); + pixel[3] = static_cast( idMath::ClampInt( 0, 255, idMath::Ftoi( color.w * 255.0f ) ) ); + } + } +} + +void idRenderSystemLocal::ShowDebugGraph() { + if ( !debugGraphMaterial ) { + return; + } + UploadImage( "textures/common/debuggraph.tga", &debugGraph[0][0][0], 256, 64 ); + SetColor4( 1, 1, 1, 1 ); + DrawStretchPic( 10, 400, 256, 64, 0, 0, 1, 1, debugGraphMaterial ); +} + +/* +============== +CaptureRenderToFile + +============== +*/ +void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) { + if ( !glConfig.isInitialized ) { + return; + } + + renderCrop_t *rc = &renderCrops[currentRenderCrop]; + + guiModel->EmitFullScreen(); + guiModel->Clear(); + R_IssueRenderCommands(); + + qglReadBuffer( GL_BACK ); + + // include extra space for OpenGL padding to word boundaries + int c = ( rc->width + 3 ) * rc->height; + byte *data = (byte *)R_StaticAlloc( c * 3 ); + + qglReadPixels( rc->x, rc->y, rc->width, rc->height, GL_RGB, GL_UNSIGNED_BYTE, data ); + + byte *data2 = (byte *)R_StaticAlloc( c * 4 ); + + for ( int i = 0 ; i < c ; i++ ) { + data2[ i * 4 ] = data[ i * 3 ]; + data2[ i * 4 + 1 ] = data[ i * 3 + 1 ]; + data2[ i * 4 + 2 ] = data[ i * 3 + 2 ]; + data2[ i * 4 + 3 ] = 0xff; + } + + R_WriteTGA( fileName, data2, rc->width, rc->height, true ); + + R_StaticFree( data ); + R_StaticFree( data2 ); +} + + +/* +============== +AllocRenderWorld +============== +*/ +idRenderWorld *idRenderSystemLocal::AllocRenderWorld() { + idRenderWorldLocal *rw; + rw = new idRenderWorldLocal; + worlds.Append( rw ); + return rw; +} + +/* +============== +FreeRenderWorld +============== +*/ +void idRenderSystemLocal::FreeRenderWorld( idRenderWorld *rw ) { + if ( primaryWorld == rw ) { + primaryWorld = NULL; + } + worlds.Remove( static_cast(rw) ); + delete rw; +} + +/* +============== +PrintMemInfo +============== +*/ +void idRenderSystemLocal::PrintMemInfo( MemInfo *mi ) { + // sum up image totals + globalImages->PrintMemInfo( mi ); + + // sum up model totals + renderModelManager->PrintMemInfo( mi ); + + // compute render totals + +} + +/* +=============== +idRenderSystemLocal::UploadImage +=============== +*/ +bool idRenderSystemLocal::UploadImage( const char *imageName, const byte *data, int width, int height ) { + idImage *image = globalImages->GetImage( imageName ); + if ( !image ) { + return false; + } + image->UploadScratch( data, width, height ); + image->SetImageFilterAndRepeat(); + return true; +} diff --git a/src/renderer/rendersystem_init.cpp b/src/renderer/rendersystem_init.cpp new file mode 100644 index 0000000..c5cbc5a --- /dev/null +++ b/src/renderer/rendersystem_init.cpp @@ -0,0 +1,2372 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +// Vista OpenGL wrapper check +#ifdef _WIN32 +#include "../sys/win32/win_local.h" +#endif + +// functions that are not called every frame + +glconfig_t glConfig; + +static void GfxInfo_f( void ); + +const char *r_rendererArgs[] = { "best", "arb", "arb2", "Cg", "exp", "nv10", "nv20", "r200", NULL }; + +idCVar r_inhibitFragmentProgram( "r_inhibitFragmentProgram", "0", CVAR_RENDERER | CVAR_BOOL, "ignore the fragment program extension" ); +idCVar r_glDriver( "r_glDriver", "", CVAR_RENDERER, "\"opengl32\", etc." ); +idCVar r_useLightPortalFlow( "r_useLightPortalFlow", "1", CVAR_RENDERER | CVAR_BOOL, "use a more precise area reference determination" ); +idCVar r_multiSamples( "r_multiSamples", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "number of antialiasing samples" ); +idCVar r_mode( "r_mode", "3", CVAR_ARCHIVE | CVAR_RENDERER | CVAR_INTEGER, "video mode number" ); +idCVar r_displayRefresh( "r_displayRefresh", "0", CVAR_RENDERER | CVAR_INTEGER | CVAR_NOCHEAT, "optional display refresh rate option for vid mode", 0.0f, 200.0f ); +idCVar r_fullscreen( "r_fullscreen", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "0 = windowed, 1 = full screen" ); +idCVar r_customWidth( "r_customWidth", "720", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "custom screen width. set r_mode to -1 to activate" ); +idCVar r_customHeight( "r_customHeight", "486", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "custom screen height. set r_mode to -1 to activate" ); +idCVar r_singleTriangle( "r_singleTriangle", "0", CVAR_RENDERER | CVAR_BOOL, "only draw a single triangle per primitive" ); +idCVar r_checkBounds( "r_checkBounds", "0", CVAR_RENDERER | CVAR_BOOL, "compare all surface bounds with precalculated ones" ); + +idCVar r_useNV20MonoLights( "r_useNV20MonoLights", "1", CVAR_RENDERER | CVAR_INTEGER, "use pass optimization for mono lights" ); +idCVar r_useConstantMaterials( "r_useConstantMaterials", "1", CVAR_RENDERER | CVAR_BOOL, "use pre-calculated material registers if possible" ); +idCVar r_useTripleTextureARB( "r_useTripleTextureARB", "1", CVAR_RENDERER | CVAR_BOOL, "cards with 3+ texture units do a two pass instead of three pass" ); +idCVar r_useSilRemap( "r_useSilRemap", "1", CVAR_RENDERER | CVAR_BOOL, "consider verts with the same XYZ, but different ST the same for shadows" ); +idCVar r_useNodeCommonChildren( "r_useNodeCommonChildren", "1", CVAR_RENDERER | CVAR_BOOL, "stop pushing reference bounds early when possible" ); +idCVar r_useShadowProjectedCull( "r_useShadowProjectedCull", "1", CVAR_RENDERER | CVAR_BOOL, "discard triangles outside light volume before shadowing" ); +idCVar r_useShadowVertexProgram( "r_useShadowVertexProgram", "1", CVAR_RENDERER | CVAR_BOOL, "do the shadow projection in the vertex program on capable cards" ); +idCVar r_useShadowSurfaceScissor( "r_useShadowSurfaceScissor", "1", CVAR_RENDERER | CVAR_BOOL, "scissor shadows by the scissor rect of the interaction surfaces" ); +idCVar r_useInteractionTable( "r_useInteractionTable", "1", CVAR_RENDERER | CVAR_BOOL, "create a full entityDefs * lightDefs table to make finding interactions faster" ); +idCVar r_useTurboShadow( "r_useTurboShadow", "1", CVAR_RENDERER | CVAR_BOOL, "use the infinite projection with W technique for dynamic shadows" ); +idCVar r_useTwoSidedStencil( "r_useTwoSidedStencil", "1", CVAR_RENDERER | CVAR_BOOL, "do stencil shadows in one pass with different ops on each side" ); +idCVar r_useDeferredTangents( "r_useDeferredTangents", "1", CVAR_RENDERER | CVAR_BOOL, "defer tangents calculations after deform" ); +idCVar r_useCachedDynamicModels( "r_useCachedDynamicModels", "1", CVAR_RENDERER | CVAR_BOOL, "cache snapshots of dynamic models" ); + +idCVar r_useVertexBuffers( "r_useVertexBuffers", "1", CVAR_RENDERER | CVAR_INTEGER, "use ARB_vertex_buffer_object for vertexes", 0, 1, idCmdSystem::ArgCompletion_Integer<0,1> ); +idCVar r_useIndexBuffers( "r_useIndexBuffers", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "use ARB_vertex_buffer_object for indexes", 0, 1, idCmdSystem::ArgCompletion_Integer<0,1> ); + +idCVar r_useStateCaching( "r_useStateCaching", "1", CVAR_RENDERER | CVAR_BOOL, "avoid redundant state changes in GL_*() calls" ); +idCVar r_useInfiniteFarZ( "r_useInfiniteFarZ", "1", CVAR_RENDERER | CVAR_BOOL, "use the no-far-clip-plane trick" ); + +idCVar r_znear( "r_znear", "3", CVAR_RENDERER | CVAR_FLOAT, "near Z clip plane distance", 0.001f, 200.0f ); + +idCVar r_ignoreGLErrors( "r_ignoreGLErrors", "1", CVAR_RENDERER | CVAR_BOOL, "ignore GL errors" ); +idCVar r_finish( "r_finish", "0", CVAR_RENDERER | CVAR_BOOL, "force a call to glFinish() every frame" ); +idCVar r_swapInterval( "r_swapInterval", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "changes wglSwapIntarval" ); + +idCVar r_gamma( "r_gamma", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 3.0f ); +idCVar r_brightness( "r_brightness", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_FLOAT, "changes gamma tables", 0.5f, 2.0f ); + +idCVar r_renderer( "r_renderer", "best", CVAR_RENDERER | CVAR_ARCHIVE, "hardware specific renderer path to use", r_rendererArgs, idCmdSystem::ArgCompletion_String ); + +idCVar r_jitter( "r_jitter", "0", CVAR_RENDERER | CVAR_BOOL, "randomly subpixel jitter the projection matrix" ); + +idCVar r_skipSuppress( "r_skipSuppress", "0", CVAR_RENDERER | CVAR_BOOL, "ignore the per-view suppressions" ); +idCVar r_skipPostProcess( "r_skipPostProcess", "0", CVAR_RENDERER | CVAR_BOOL, "skip all post-process renderings" ); +idCVar r_skipLightScale( "r_skipLightScale", "0", CVAR_RENDERER | CVAR_BOOL, "don't do any post-interaction light scaling, makes things dim on low-dynamic range cards" ); +idCVar r_skipInteractions( "r_skipInteractions", "0", CVAR_RENDERER | CVAR_BOOL, "skip all light/surface interaction drawing" ); +idCVar r_skipDynamicTextures( "r_skipDynamicTextures", "0", CVAR_RENDERER | CVAR_BOOL, "don't dynamically create textures" ); +idCVar r_skipTextures( "r_skipTextures", "0", CVAR_RENDERER, "fixme" ); +idCVar r_useSimpleInteraction( "r_useSimpleInteraction", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, + "use simple interaction program for faster rendering", 0, 1, idCmdSystem::ArgCompletion_Boolean ); +idCVar r_showEditorImages( "r_showEditorImages", "0", CVAR_RENDERER, + "renders the editor image rather than the texture" ); +idCVar r_portalsDistanceCull( "r_portalsDistanceCull", "1", CVAR_RENDERER | CVAR_BOOL, + "Allow culling of portals based on distance to viewer" ); +idCVar r_alphaToCoverage( "r_alphaToCoverage", "1", + CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, + "use alpha to coverage when multisampling" ); +idCVar r_skipCopyTexture( "r_skipCopyTexture", "0", CVAR_RENDERER | CVAR_BOOL, "do all rendering, but don't actually copyTexSubImage2D" ); +idCVar r_skipBackEnd( "r_skipBackEnd", "0", CVAR_RENDERER | CVAR_BOOL, "don't draw anything" ); +idCVar r_skipRender( "r_skipRender", "0", CVAR_RENDERER | CVAR_BOOL, "skip 3D rendering, but pass 2D" ); +idCVar r_skipRenderContext( "r_skipRenderContext", "0", CVAR_RENDERER | CVAR_BOOL, "NULL the rendering context during backend 3D rendering" ); +idCVar r_skipTranslucent( "r_skipTranslucent", "0", CVAR_RENDERER | CVAR_BOOL, "skip the translucent interaction rendering" ); +idCVar r_skipAmbient( "r_skipAmbient", "0", CVAR_RENDERER | CVAR_BOOL, "bypasses all non-interaction drawing" ); +idCVar r_skipNewAmbient( "r_skipNewAmbient", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "bypasses all vertex/fragment program ambient drawing" ); +idCVar r_skipBlendLights( "r_skipBlendLights", "0", CVAR_RENDERER | CVAR_BOOL, "skip all blend lights" ); +idCVar r_skipFogLights( "r_skipFogLights", "0", CVAR_RENDERER | CVAR_BOOL, "skip all fog lights" ); +idCVar r_skipDeforms( "r_skipDeforms", "0", CVAR_RENDERER | CVAR_BOOL, "leave all deform materials in their original state" ); +idCVar r_skipFrontEnd( "r_skipFrontEnd", "0", CVAR_RENDERER | CVAR_BOOL, "bypasses all front end work, but 2D gui rendering still draws" ); +idCVar r_skipUpdates( "r_skipUpdates", "0", CVAR_RENDERER | CVAR_BOOL, "1 = don't accept any entity or light updates, making everything static" ); +idCVar r_skipOverlays( "r_skipOverlays", "0", CVAR_RENDERER | CVAR_BOOL, "skip overlay surfaces" ); +idCVar r_skipSpecular( "r_skipSpecular", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_CHEAT | CVAR_ARCHIVE, "use black for specular1" ); +idCVar r_skipBump( "r_skipBump", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "uses a flat surface instead of the bump map" ); +idCVar r_skipDiffuse( "r_skipDiffuse", "0", CVAR_RENDERER | CVAR_BOOL, "use black for diffuse" ); +idCVar r_forceDiffuseOnly( "r_forceDiffuseOnly", "0", CVAR_RENDERER | CVAR_BOOL, "forces renderer to skip all but diffuse pass on non-arb2 renderers" ); +idCVar r_skipROQ( "r_skipROQ", "0", CVAR_RENDERER | CVAR_BOOL, "skip ROQ decoding" ); + +idCVar r_ignore( "r_ignore", "0", CVAR_RENDERER, "used for random debugging without defining new vars" ); +idCVar r_ignore2( "r_ignore2", "0", CVAR_RENDERER, "used for random debugging without defining new vars" ); +idCVar r_usePreciseTriangleInteractions( "r_usePreciseTriangleInteractions", "0", CVAR_RENDERER | CVAR_BOOL, "1 = do winding clipping to determine if each ambiguous tri should be lit" ); +idCVar r_useCulling( "r_useCulling", "2", CVAR_RENDERER | CVAR_INTEGER, "0 = none, 1 = sphere, 2 = sphere + box", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_useLightCulling( "r_useLightCulling", "3", CVAR_RENDERER | CVAR_INTEGER, "0 = none, 1 = box, 2 = exact clip of polyhedron faces, 3 = also areas", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_useLightScissors( "r_useLightScissors", "1", CVAR_RENDERER | CVAR_BOOL, "1 = use custom scissor rectangle for each light" ); +idCVar r_useClippedLightScissors( "r_useClippedLightScissors", "1", CVAR_RENDERER | CVAR_INTEGER, "0 = full screen when near clipped, 1 = exact when near clipped, 2 = exact always", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_useEntityCulling( "r_useEntityCulling", "1", CVAR_RENDERER | CVAR_BOOL, "0 = none, 1 = box" ); +idCVar r_useEntityScissors( "r_useEntityScissors", "0", CVAR_RENDERER | CVAR_BOOL, "1 = use custom scissor rectangle for each entity" ); +idCVar r_useInteractionCulling( "r_useInteractionCulling", "1", CVAR_RENDERER | CVAR_BOOL, "1 = cull interactions" ); +idCVar r_useInteractionScissors( "r_useInteractionScissors", "2", CVAR_RENDERER | CVAR_INTEGER, "1 = use a custom scissor rectangle for each shadow interaction, 2 = also crop using portal scissors", -2, 2, idCmdSystem::ArgCompletion_Integer<-2,2> ); +idCVar r_useShadowCulling( "r_useShadowCulling", "1", CVAR_RENDERER | CVAR_BOOL, "try to cull shadows from partially visible lights" ); +idCVar r_useFrustumFarDistance( "r_useFrustumFarDistance", "0", CVAR_RENDERER | CVAR_FLOAT, "if != 0 force the view frustum far distance to this distance" ); +idCVar r_logFile( "r_logFile", "0", CVAR_RENDERER | CVAR_INTEGER, "number of frames to emit GL logs" ); +FILE *qglLogFile; +idCVar r_clear( "r_clear", "2", CVAR_RENDERER, "force screen clear every frame, 1 = purple, 2 = black, 'r g b' = custom" ); +idCVar r_offsetFactor( "r_offsetfactor", "0", CVAR_RENDERER | CVAR_FLOAT, "polygon offset parameter" ); +idCVar r_offsetUnits( "r_offsetunits", "-600", CVAR_RENDERER | CVAR_FLOAT, "polygon offset parameter" ); +idCVar r_shadowPolygonOffset( "r_shadowPolygonOffset", "-1", CVAR_RENDERER | CVAR_FLOAT, "bias value added to depth test for stencil shadow drawing" ); +idCVar r_shadowPolygonFactor( "r_shadowPolygonFactor", "0", CVAR_RENDERER | CVAR_FLOAT, "scale value for stencil shadow drawing" ); +idCVar r_frontBuffer( "r_frontBuffer", "0", CVAR_RENDERER | CVAR_BOOL, "draw to front buffer for debugging" ); +idCVar r_skipSubviews( "r_skipSubviews", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = don't render any gui elements on surfaces" ); +idCVar r_skipGuiShaders( "r_skipGuiShaders", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = skip all gui elements on surfaces, 2 = skip drawing but still handle events, 3 = draw but skip events", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_skipParticles( "r_skipParticles", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = skip all particle systems", 0, 1, idCmdSystem::ArgCompletion_Integer<0,1> ); +idCVar r_subviewOnly( "r_subviewOnly", "0", CVAR_RENDERER | CVAR_BOOL, "1 = don't render main view, allowing subviews to be debugged" ); +idCVar r_shadows( "r_shadows", "1", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "enable shadows" ); +idCVar r_testARBProgram( "r_testARBProgram", "0", CVAR_RENDERER | CVAR_BOOL, "experiment with vertex/fragment programs" ); +idCVar r_testGamma( "r_testGamma", "0", CVAR_RENDERER | CVAR_FLOAT, "if > 0 draw a grid pattern to test gamma levels", 0, 195 ); +idCVar r_testGammaBias( "r_testGammaBias", "0", CVAR_RENDERER | CVAR_FLOAT, "if > 0 draw a grid pattern to test gamma levels" ); +idCVar r_testStepGamma( "r_testStepGamma", "0", CVAR_RENDERER | CVAR_FLOAT, "if > 0 draw a grid pattern to test gamma levels" ); +idCVar r_lightScale( "r_lightScale", "2", CVAR_RENDERER | CVAR_FLOAT, "all light intensities are multiplied by this" ); +idCVar r_lightSourceRadius( "r_lightSourceRadius", "0", CVAR_RENDERER | CVAR_FLOAT, "for soft-shadow sampling" ); +idCVar r_flareSize( "r_flareSize", "1", CVAR_RENDERER | CVAR_FLOAT, "scale the flare deforms from the material def" ); + +idCVar r_useExternalShadows( "r_useExternalShadows", "1", CVAR_RENDERER | CVAR_INTEGER, "1 = skip drawing caps when outside the light volume, 2 = force to no caps for testing", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_useOptimizedShadows( "r_useOptimizedShadows", "1", CVAR_RENDERER | CVAR_BOOL, "use the dmap generated static shadow volumes" ); +idCVar r_useScissor( "r_useScissor", "1", CVAR_RENDERER | CVAR_BOOL, "scissor clip as portals and lights are processed" ); +idCVar r_useCombinerDisplayLists( "r_useCombinerDisplayLists", "1", CVAR_RENDERER | CVAR_BOOL | CVAR_NOCHEAT, "put all nvidia register combiner programming in display lists" ); +idCVar r_useDepthBoundsTest( "r_useDepthBoundsTest", "1", CVAR_RENDERER | CVAR_BOOL, "use depth bounds test to reduce shadow fill" ); + +idCVar r_screenFraction( "r_screenFraction", "100", CVAR_RENDERER | CVAR_INTEGER, "for testing fill rate, the resolution of the entire screen can be changed" ); +idCVar r_demonstrateBug( "r_demonstrateBug", "0", CVAR_RENDERER | CVAR_BOOL, "used during development to show IHV's their problems" ); +idCVar r_usePortals( "r_usePortals", "1", CVAR_RENDERER | CVAR_BOOL, " 1 = use portals to perform area culling, otherwise draw everything" ); +idCVar r_singleLight( "r_singleLight", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one light" ); +idCVar r_singleEntity( "r_singleEntity", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one entity" ); +idCVar r_singleSurface( "r_singleSurface", "-1", CVAR_RENDERER | CVAR_INTEGER, "suppress all but one surface on each entity" ); +idCVar r_singleArea( "r_singleArea", "0", CVAR_RENDERER | CVAR_BOOL, "only draw the portal area the view is actually in" ); +idCVar r_forceLoadImages( "r_forceLoadImages", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "draw all images to screen after registration" ); +idCVar r_orderIndexes( "r_orderIndexes", "1", CVAR_RENDERER | CVAR_BOOL, "perform index reorganization to optimize vertex use" ); +idCVar r_lightAllBackFaces( "r_lightAllBackFaces", "0", CVAR_RENDERER | CVAR_BOOL, "light all the back faces, even when they would be shadowed" ); + +// visual debugging info +idCVar r_showPortals( "r_showPortals", "0", CVAR_RENDERER | CVAR_BOOL, "draw portal outlines in color based on passed / not passed" ); +idCVar r_showUnsmoothedTangents( "r_showUnsmoothedTangents", "0", CVAR_RENDERER | CVAR_BOOL, "if 1, put all nvidia register combiner programming in display lists" ); +idCVar r_showSilhouette( "r_showSilhouette", "0", CVAR_RENDERER | CVAR_BOOL, "highlight edges that are casting shadow planes" ); +idCVar r_showVertexColor( "r_showVertexColor", "0", CVAR_RENDERER | CVAR_BOOL, "draws all triangles with the solid vertex color" ); +idCVar r_showUpdates( "r_showUpdates", "0", CVAR_RENDERER | CVAR_BOOL, "report entity and light updates and ref counts" ); +idCVar r_showDemo( "r_showDemo", "0", CVAR_RENDERER | CVAR_BOOL, "report reads and writes to the demo file" ); +idCVar r_showDynamic( "r_showDynamic", "0", CVAR_RENDERER | CVAR_BOOL, "report stats on dynamic surface generation" ); +idCVar r_showLightScale( "r_showLightScale", "0", CVAR_RENDERER | CVAR_BOOL, "report the scale factor applied to drawing for overbrights" ); +idCVar r_showDefs( "r_showDefs", "0", CVAR_RENDERER | CVAR_BOOL, "report the number of modeDefs and lightDefs in view" ); +idCVar r_showTrace( "r_showTrace", "0", CVAR_RENDERER | CVAR_INTEGER, "show the intersection of an eye trace with the world", idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_showIntensity( "r_showIntensity", "0", CVAR_RENDERER | CVAR_BOOL, "draw the screen colors based on intensity, red = 0, green = 128, blue = 255" ); +idCVar r_showImages( "r_showImages", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = show all images instead of rendering, 2 = show in proportional size", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_showSmp( "r_showSmp", "0", CVAR_RENDERER | CVAR_BOOL, "show which end (front or back) is blocking" ); +idCVar r_showLights( "r_showLights", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = just print volumes numbers, highlighting ones covering the view, 2 = also draw planes of each volume, 3 = also draw edges of each volume", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showShadows( "r_showShadows", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = visualize the stencil shadow volumes, 2 = draw filled in", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showShadowCount( "r_showShadowCount", "0", CVAR_RENDERER | CVAR_INTEGER, "colors screen based on shadow volume depth complexity, >= 2 = print overdraw count based on stencil index values, 3 = only show turboshadows, 4 = only show static shadows", 0, 4, idCmdSystem::ArgCompletion_Integer<0,4> ); +idCVar r_showLightScissors( "r_showLightScissors", "0", CVAR_RENDERER | CVAR_BOOL, "show light scissor rectangles" ); +idCVar r_showEntityScissors( "r_showEntityScissors", "0", CVAR_RENDERER | CVAR_BOOL, "show entity scissor rectangles" ); +idCVar r_showInteractionFrustums( "r_showInteractionFrustums", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = show a frustum for each interaction, 2 = also draw lines to light origin, 3 = also draw entity bbox", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = show screen rectangle which contains the interaction frustum, 2 = also draw construction lines", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_showLightCount( "r_showLightCount", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = colors surfaces based on light count, 2 = also count everything through walls, 3 = also print overdraw", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showViewEntitys( "r_showViewEntitys", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = displays the bounding boxes of all view models, 2 = print index numbers" ); +idCVar r_showTris( "r_showTris", "0", CVAR_RENDERER | CVAR_INTEGER, "enables wireframe rendering of the world, 1 = only draw visible ones, 2 = draw all front facing, 3 = draw all", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showSurfaceInfo( "r_showSurfaceInfo", "0", CVAR_RENDERER | CVAR_BOOL, "show surface material name under crosshair" ); +idCVar r_showNormals( "r_showNormals", "0", CVAR_RENDERER | CVAR_FLOAT, "draws wireframe normals" ); +idCVar r_showMemory( "r_showMemory", "0", CVAR_RENDERER | CVAR_BOOL, "print frame memory utilization" ); +idCVar r_showCull( "r_showCull", "0", CVAR_RENDERER | CVAR_BOOL, "report sphere and box culling stats" ); +idCVar r_showInteractions( "r_showInteractions", "0", CVAR_RENDERER | CVAR_BOOL, "report interaction generation activity" ); +idCVar r_showDepth( "r_showDepth", "0", CVAR_RENDERER | CVAR_BOOL, "display the contents of the depth buffer and the depth range" ); +idCVar r_showSurfaces( "r_showSurfaces", "0", CVAR_RENDERER | CVAR_BOOL, "report surface/light/shadow counts" ); +idCVar r_showPrimitives( "r_showPrimitives", "0", CVAR_RENDERER | CVAR_INTEGER, "report drawsurf/index/vertex counts" ); +idCVar r_showEdges( "r_showEdges", "0", CVAR_RENDERER | CVAR_BOOL, "draw the sil edges" ); +idCVar r_showTexturePolarity( "r_showTexturePolarity", "0", CVAR_RENDERER | CVAR_BOOL, "shade triangles by texture area polarity" ); +idCVar r_showTangentSpace( "r_showTangentSpace", "0", CVAR_RENDERER | CVAR_INTEGER, "shade triangles by tangent space, 1 = use 1st tangent vector, 2 = use 2nd tangent vector, 3 = use normal vector", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); +idCVar r_showDominantTri( "r_showDominantTri", "0", CVAR_RENDERER | CVAR_BOOL, "draw lines from vertexes to center of dominant triangles" ); +idCVar r_showAlloc( "r_showAlloc", "0", CVAR_RENDERER | CVAR_BOOL, "report alloc/free counts" ); +idCVar r_showTextureVectors( "r_showTextureVectors", "0", CVAR_RENDERER | CVAR_FLOAT, " if > 0 draw each triangles texture (tangent) vectors" ); +idCVar r_showOverDraw( "r_showOverDraw", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = geometry overdraw, 2 = light interaction overdraw, 3 = geometry and light interaction overdraw", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); + +idCVar r_lockSurfaces( "r_lockSurfaces", "0", CVAR_RENDERER | CVAR_BOOL, "allow moving the view point without changing the composition of the scene, including culling" ); +idCVar r_useEntityCallbacks( "r_useEntityCallbacks", "1", CVAR_RENDERER | CVAR_BOOL, "if 0, issue the callback immediately at update time, rather than defering" ); + +idCVar r_showSkel( "r_showSkel", "0", CVAR_RENDERER | CVAR_INTEGER, "draw the skeleton when model animates, 1 = draw model with skeleton, 2 = draw skeleton only", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar r_jointNameScale( "r_jointNameScale", "0.02", CVAR_RENDERER | CVAR_FLOAT, "size of joint names when r_showskel is set to 1" ); +idCVar r_jointNameOffset( "r_jointNameOffset", "0.5", CVAR_RENDERER | CVAR_FLOAT, "offset of joint names when r_showskel is set to 1" ); + +idCVar r_cgVertexProfile( "r_cgVertexProfile", "best", CVAR_RENDERER | CVAR_ARCHIVE, "arbvp1, vp20, vp30" ); +idCVar r_cgFragmentProfile( "r_cgFragmentProfile", "best", CVAR_RENDERER | CVAR_ARCHIVE, "arbfp1, fp30" ); + +idCVar r_debugLineDepthTest( "r_debugLineDepthTest", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "perform depth test on debug lines" ); +idCVar r_debugLineWidth( "r_debugLineWidth", "1", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "width of debug lines" ); +idCVar r_debugArrowStep( "r_debugArrowStep", "120", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "step size of arrow cone line rotation in degrees", 0, 120 ); +idCVar r_debugPolygonFilled( "r_debugPolygonFilled", "1", CVAR_RENDERER | CVAR_BOOL, "draw a filled polygon" ); + +idCVar r_materialOverride( "r_materialOverride", "", CVAR_RENDERER, "overrides all materials", idCmdSystem::ArgCompletion_Decl ); + +idCVar r_debugRenderToTexture( "r_debugRenderToTexture", "0", CVAR_RENDERER | CVAR_INTEGER, "" ); + +void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +void ( APIENTRY * qglMultiTexCoord2fvARB )( GLenum texture, GLfloat *st ); +void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +void ( APIENTRY *qglCombinerParameterfvNV )( GLenum pname, const GLfloat *params ); +void ( APIENTRY *qglCombinerParameterivNV )( GLenum pname, const GLint *params ); +void ( APIENTRY *qglCombinerParameterfNV )( GLenum pname, const GLfloat param ); +void ( APIENTRY *qglCombinerParameteriNV )( GLenum pname, const GLint param ); +void ( APIENTRY *qglCombinerInputNV )( GLenum stage, GLenum portion, GLenum variable, GLenum input, + GLenum mapping, GLenum componentUsage ); +void ( APIENTRY *qglCombinerOutputNV )( GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, + GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, + GLboolean cdDotProduct, GLboolean muxSum ); +void ( APIENTRY *qglFinalCombinerInputNV )( GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage ); + +// Raven's NV program entry points. These data symbols are owned by +// RenderSystem_init.obj alongside the other GL extension pointers. +void ( APIENTRY *qglBindProgramNV )( GLenum target, GLuint id ); +void ( APIENTRY *qglLoadProgramNV )( GLenum target, GLuint id, GLsizei len, const GLubyte *program ); +void ( APIENTRY *qglProgramParameter4fvNV )( GLenum target, GLuint index, const GLfloat *params ); + +void ( APIENTRY *qglBlendEquationEXT )( GLenum mode ); +void ( APIENTRY *qglDrawRangeElementsEXT )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); + + +void (APIENTRY *qglVertexArrayRangeNV)( GLsizei length, void *pointer ); +// TTimo: wgl vs glX +// http://oss.sgi.com/projects/ogl-sample/registry/NV/vertex_array_range.txt +// since APIs are the same anyway, let's be wgl/glX agnostic +void *(APIENTRY *qAllocateMemoryNV)( GLsizei size, float readFrequency, float writeFrequency, float priority); +void (APIENTRY *qFreeMemoryNV)( void *pointer ); +#ifdef GLX_VERSION_1_1 +#define Q_ALLOCATE_MEMORY_NV "glXAllocateMemoryNV" +#define Q_FREE_MEMORY_NV "glXFreeMemoryNV" +#else +#define Q_ALLOCATE_MEMORY_NV "wglAllocateMemoryNV" +#define Q_FREE_MEMORY_NV "wglFreeMemoryNV" +#endif + +void (APIENTRY *qglTexImage3D)(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); + +void (APIENTRY * qglColorTableEXT)( int, int, int, int, int, const void * ); + + +// ATI_fragment_shader +PFNGLGENFRAGMENTSHADERSATIPROC qglGenFragmentShadersATI; +PFNGLBINDFRAGMENTSHADERATIPROC qglBindFragmentShaderATI; +PFNGLDELETEFRAGMENTSHADERATIPROC qglDeleteFragmentShaderATI; +PFNGLBEGINFRAGMENTSHADERATIPROC qglBeginFragmentShaderATI; +PFNGLENDFRAGMENTSHADERATIPROC qglEndFragmentShaderATI; +PFNGLPASSTEXCOORDATIPROC qglPassTexCoordATI; +PFNGLSAMPLEMAPATIPROC qglSampleMapATI; +PFNGLCOLORFRAGMENTOP1ATIPROC qglColorFragmentOp1ATI; +PFNGLCOLORFRAGMENTOP2ATIPROC qglColorFragmentOp2ATI; +PFNGLCOLORFRAGMENTOP3ATIPROC qglColorFragmentOp3ATI; +PFNGLALPHAFRAGMENTOP1ATIPROC qglAlphaFragmentOp1ATI; +PFNGLALPHAFRAGMENTOP2ATIPROC qglAlphaFragmentOp2ATI; +PFNGLALPHAFRAGMENTOP3ATIPROC qglAlphaFragmentOp3ATI; +PFNGLSETFRAGMENTSHADERCONSTANTATIPROC qglSetFragmentShaderConstantATI; + +// EXT_stencil_two_side +PFNGLACTIVESTENCILFACEEXTPROC qglActiveStencilFaceEXT; + +// ATI_separate_stencil +PFNGLSTENCILOPSEPARATEATIPROC qglStencilOpSeparateATI; +PFNGLSTENCILFUNCSEPARATEATIPROC qglStencilFuncSeparateATI; + +// ARB_texture_compression +PFNGLCOMPRESSEDTEXIMAGE2DARBPROC qglCompressedTexImage2DARB; +PFNGLGETCOMPRESSEDTEXIMAGEARBPROC qglGetCompressedTexImageARB; + +// ARB_vertex_buffer_object +PFNGLBINDBUFFERARBPROC qglBindBufferARB; +PFNGLDELETEBUFFERSARBPROC qglDeleteBuffersARB; +PFNGLGENBUFFERSARBPROC qglGenBuffersARB; +PFNGLISBUFFERARBPROC qglIsBufferARB; +PFNGLBUFFERDATAARBPROC qglBufferDataARB; +PFNGLBUFFERSUBDATAARBPROC qglBufferSubDataARB; +PFNGLGETBUFFERSUBDATAARBPROC qglGetBufferSubDataARB; +PFNGLMAPBUFFERARBPROC qglMapBufferARB; +PFNGLUNMAPBUFFERARBPROC qglUnmapBufferARB; +PFNGLGETBUFFERPARAMETERIVARBPROC qglGetBufferParameterivARB; +PFNGLGETBUFFERPOINTERVARBPROC qglGetBufferPointervARB; + +// ARB_vertex_program / ARB_fragment_program +PFNGLVERTEXATTRIBPOINTERARBPROC qglVertexAttribPointerARB; +PFNGLENABLEVERTEXATTRIBARRAYARBPROC qglEnableVertexAttribArrayARB; +PFNGLDISABLEVERTEXATTRIBARRAYARBPROC qglDisableVertexAttribArrayARB; +PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB; +PFNGLBINDPROGRAMARBPROC qglBindProgramARB; +PFNGLGENPROGRAMSARBPROC qglGenProgramsARB; +PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB; +PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB; + +// Raven GLSL shader-stage entry points. +PFNGLCREATESHADEROBJECTARBPROC qglCreateShaderObjectARB; +PFNGLDELETEOBJECTARBPROC qglDeleteObjectARB; +PFNGLSHADERSOURCEARBPROC qglShaderSourceARB; +PFNGLCOMPILESHADERARBPROC qglCompileShaderARB; +PFNGLGETOBJECTPARAMETERIVARBPROC qglGetObjectParameterivARB; +PFNGLCREATEPROGRAMOBJECTARBPROC qglCreateProgramObjectARB; +PFNGLATTACHOBJECTARBPROC qglAttachObjectARB; +PFNGLDETACHOBJECTARBPROC qglDetachObjectARB; +PFNGLLINKPROGRAMARBPROC qglLinkProgramARB; +PFNGLUSEPROGRAMOBJECTARBPROC qglUseProgramObjectARB; +PFNGLGETUNIFORMLOCATIONARBPROC qglGetUniformLocationARB; +PFNGLUNIFORM1FARBPROC qglUniform1fARB; +PFNGLUNIFORM1IARBPROC qglUniform1iARB; +PFNGLUNIFORM1FVARBPROC qglUniform1fvARB; +PFNGLUNIFORM2FVARBPROC qglUniform2fvARB; +PFNGLUNIFORM3FVARBPROC qglUniform3fvARB; +PFNGLUNIFORM4FVARBPROC qglUniform4fvARB; +PFNGLGETINFOLOGARBPROC qglGetInfoLogARB; + +// GL_EXT_depth_bounds_test +PFNGLDEPTHBOUNDSEXTPROC qglDepthBoundsEXT; + +/* +================= +R_CheckExtension +================= +*/ +bool R_CheckExtension( char *name ) { + if ( !strstr( glConfig.extensions_string, name ) ) { + common->Printf( "X..%s not found\n", name ); + return false; + } + + common->Printf( "...using %s\n", name ); + return true; +} + +/* +================== +R_CheckPortableExtensions + +================== +*/ +static void R_CheckPortableExtensions( void ) { + glConfig.glVersion = atof( glConfig.version_string ); + + // GL_ARB_multitexture + glConfig.multitextureAvailable = R_CheckExtension( "GL_ARB_multitexture" ); + if ( glConfig.multitextureAvailable ) { + qglMultiTexCoord2fARB = (void(APIENTRY *)(GLenum, GLfloat, GLfloat))GLimp_ExtensionPointer( "glMultiTexCoord2fARB" ); + qglMultiTexCoord2fvARB = (void(APIENTRY *)(GLenum, GLfloat *))GLimp_ExtensionPointer( "glMultiTexCoord2fvARB" ); + qglActiveTextureARB = (void(APIENTRY *)(GLenum))GLimp_ExtensionPointer( "glActiveTextureARB" ); + qglClientActiveTextureARB = (void(APIENTRY *)(GLenum))GLimp_ExtensionPointer( "glClientActiveTextureARB" ); + qglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, (GLint *)&glConfig.maxTextureUnits ); + if ( glConfig.maxTextureUnits > MAX_MULTITEXTURE_UNITS ) { + glConfig.maxTextureUnits = MAX_MULTITEXTURE_UNITS; + } + if ( glConfig.maxTextureUnits < 2 ) { + glConfig.multitextureAvailable = false; // shouldn't ever happen + } + qglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, (GLint *)&glConfig.maxTextureCoords ); + qglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, (GLint *)&glConfig.maxTextureImageUnits ); + } + + // GL_ARB_texture_env_combine + glConfig.textureEnvCombineAvailable = R_CheckExtension( "GL_ARB_texture_env_combine" ); + + // GL_ARB_texture_cube_map + glConfig.cubeMapAvailable = R_CheckExtension( "GL_ARB_texture_cube_map" ); + + // GL_ARB_texture_env_dot3 + glConfig.envDot3Available = R_CheckExtension( "GL_ARB_texture_env_dot3" ); + + // GL_ARB_texture_env_add + glConfig.textureEnvAddAvailable = R_CheckExtension( "GL_ARB_texture_env_add" ); + + // GL_ARB_texture_non_power_of_two + glConfig.textureNonPowerOfTwoAvailable = R_CheckExtension( "GL_ARB_texture_non_power_of_two" ); + + // GL_ARB_texture_compression + GL_S3_s3tc + // DRI drivers may have GL_ARB_texture_compression but no GL_EXT_texture_compression_s3tc + if ( R_CheckExtension( "GL_ARB_texture_compression" ) && R_CheckExtension( "GL_EXT_texture_compression_s3tc" ) ) { + qglCompressedTexImage2DARB = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)GLimp_ExtensionPointer( "glCompressedTexImage2DARB" ); + qglGetCompressedTexImageARB = (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)GLimp_ExtensionPointer( "glGetCompressedTexImageARB" ); + } else { + } + + // GL_EXT_texture_filter_anisotropic + glConfig.anisotropicAvailable = R_CheckExtension( "GL_EXT_texture_filter_anisotropic" ); + if ( glConfig.anisotropicAvailable ) { + qglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.maxTextureAnisotropy ); + common->Printf( " maxTextureAnisotropy: %f\n", glConfig.maxTextureAnisotropy ); + } else { + glConfig.maxTextureAnisotropy = 1; + } + + // GL_EXT_texture_lod_bias + // The actual extension is broken as specificed, storing the state in the texture unit instead + // of the texture object. The behavior in GL 1.4 is the behavior we use. + if ( glConfig.glVersion >= 1.4 || R_CheckExtension( "GL_EXT_texture_lod" ) ) { + common->Printf( "...using %s\n", "GL_1.4_texture_lod_bias" ); + glConfig.textureLODBiasAvailable = true; + } else { + common->Printf( "X..%s not found\n", "GL_1.4_texture_lod_bias" ); + glConfig.textureLODBiasAvailable = false; + } + + // GL_EXT_shared_texture_palette + glConfig.sharedTexturePaletteAvailable = R_CheckExtension( "GL_EXT_shared_texture_palette" ); + if ( glConfig.sharedTexturePaletteAvailable ) { + qglColorTableEXT = ( void ( APIENTRY * ) ( int, int, int, int, int, const void * ) ) GLimp_ExtensionPointer( "glColorTableEXT" ); + } + + // GL_EXT_texture3D (not currently used for anything) + glConfig.texture3DAvailable = R_CheckExtension( "GL_EXT_texture3D" ); + if ( glConfig.texture3DAvailable ) { + qglTexImage3D = + (void (APIENTRY *)(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *) ) + GLimp_ExtensionPointer( "glTexImage3D" ); + } + + // EXT_stencil_wrap + // This isn't very important, but some pathological case might cause a clamp error and give a shadow bug. + // Nvidia also believes that future hardware may be able to run faster with this enabled to avoid the + // serialization of clamping. + if ( R_CheckExtension( "GL_EXT_stencil_wrap" ) ) { + tr.stencilIncr = GL_INCR_WRAP_EXT; + tr.stencilDecr = GL_DECR_WRAP_EXT; + } else { + tr.stencilIncr = GL_INCR; + tr.stencilDecr = GL_DECR; + } + + // GL_NV_register_combiners + glConfig.registerCombinersAvailable = R_CheckExtension( "GL_NV_register_combiners" ); + if ( glConfig.registerCombinersAvailable ) { + qglCombinerParameterfvNV = (void (APIENTRY *)( GLenum pname, const GLfloat *params )) + GLimp_ExtensionPointer( "glCombinerParameterfvNV" ); + qglCombinerParameterivNV = (void (APIENTRY *)( GLenum pname, const GLint *params )) + GLimp_ExtensionPointer( "glCombinerParameterivNV" ); + qglCombinerParameterfNV = (void (APIENTRY *)( GLenum pname, const GLfloat param )) + GLimp_ExtensionPointer( "glCombinerParameterfNV" ); + qglCombinerParameteriNV = (void (APIENTRY *)( GLenum pname, const GLint param )) + GLimp_ExtensionPointer( "glCombinerParameteriNV" ); + qglCombinerInputNV = (void (APIENTRY *)( GLenum stage, GLenum portion, GLenum variable, GLenum input, + GLenum mapping, GLenum componentUsage )) + GLimp_ExtensionPointer( "glCombinerInputNV" ); + qglCombinerOutputNV = (void (APIENTRY *)( GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, + GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, + GLboolean cdDotProduct, GLboolean muxSum )) + GLimp_ExtensionPointer( "glCombinerOutputNV" ); + qglFinalCombinerInputNV = (void (APIENTRY *)( GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage )) + GLimp_ExtensionPointer( "glFinalCombinerInputNV" ); + } + + // GL_EXT_stencil_two_side + glConfig.twoSidedStencilAvailable = R_CheckExtension( "GL_EXT_stencil_two_side" ); + if ( glConfig.twoSidedStencilAvailable ) { + qglActiveStencilFaceEXT = (PFNGLACTIVESTENCILFACEEXTPROC)GLimp_ExtensionPointer( "glActiveStencilFaceEXT" ); + } else { + glConfig.atiTwoSidedStencilAvailable = R_CheckExtension( "GL_ATI_separate_stencil" ); + if ( glConfig.atiTwoSidedStencilAvailable ) { + qglStencilFuncSeparateATI = (PFNGLSTENCILFUNCSEPARATEATIPROC)GLimp_ExtensionPointer( "glStencilFuncSeparateATI" ); + qglStencilOpSeparateATI = (PFNGLSTENCILOPSEPARATEATIPROC)GLimp_ExtensionPointer( "glStencilOpSeparateATI" ); + } + } + + // GL_ATI_fragment_shader + glConfig.atiFragmentShaderAvailable = R_CheckExtension( "GL_ATI_fragment_shader" ); + if (! glConfig.atiFragmentShaderAvailable ) { + // only on OSX: ATI_fragment_shader is faked through ATI_text_fragment_shader (macosx_glimp.cpp) + glConfig.atiFragmentShaderAvailable = R_CheckExtension( "GL_ATI_text_fragment_shader" ); + } + if ( glConfig.atiFragmentShaderAvailable ) { + qglGenFragmentShadersATI = (PFNGLGENFRAGMENTSHADERSATIPROC)GLimp_ExtensionPointer( "glGenFragmentShadersATI" ); + qglBindFragmentShaderATI = (PFNGLBINDFRAGMENTSHADERATIPROC)GLimp_ExtensionPointer( "glBindFragmentShaderATI" ); + qglDeleteFragmentShaderATI = (PFNGLDELETEFRAGMENTSHADERATIPROC)GLimp_ExtensionPointer( "glDeleteFragmentShaderATI" ); + qglBeginFragmentShaderATI = (PFNGLBEGINFRAGMENTSHADERATIPROC)GLimp_ExtensionPointer( "glBeginFragmentShaderATI" ); + qglEndFragmentShaderATI = (PFNGLENDFRAGMENTSHADERATIPROC)GLimp_ExtensionPointer( "glEndFragmentShaderATI" ); + qglPassTexCoordATI = (PFNGLPASSTEXCOORDATIPROC)GLimp_ExtensionPointer( "glPassTexCoordATI" ); + qglSampleMapATI = (PFNGLSAMPLEMAPATIPROC)GLimp_ExtensionPointer( "glSampleMapATI" ); + qglColorFragmentOp1ATI = (PFNGLCOLORFRAGMENTOP1ATIPROC)GLimp_ExtensionPointer( "glColorFragmentOp1ATI" ); + qglColorFragmentOp2ATI = (PFNGLCOLORFRAGMENTOP2ATIPROC)GLimp_ExtensionPointer( "glColorFragmentOp2ATI" ); + qglColorFragmentOp3ATI = (PFNGLCOLORFRAGMENTOP3ATIPROC)GLimp_ExtensionPointer( "glColorFragmentOp3ATI" ); + qglAlphaFragmentOp1ATI = (PFNGLALPHAFRAGMENTOP1ATIPROC)GLimp_ExtensionPointer( "glAlphaFragmentOp1ATI" ); + qglAlphaFragmentOp2ATI = (PFNGLALPHAFRAGMENTOP2ATIPROC)GLimp_ExtensionPointer( "glAlphaFragmentOp2ATI" ); + qglAlphaFragmentOp3ATI = (PFNGLALPHAFRAGMENTOP3ATIPROC)GLimp_ExtensionPointer( "glAlphaFragmentOp3ATI" ); + qglSetFragmentShaderConstantATI = (PFNGLSETFRAGMENTSHADERCONSTANTATIPROC)GLimp_ExtensionPointer( "glSetFragmentShaderConstantATI" ); + } + + // ARB_vertex_buffer_object + glConfig.ARBVertexBufferObjectAvailable = R_CheckExtension( "GL_ARB_vertex_buffer_object" ); + if(glConfig.ARBVertexBufferObjectAvailable) { + qglBindBufferARB = (PFNGLBINDBUFFERARBPROC)GLimp_ExtensionPointer( "glBindBufferARB"); + qglDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)GLimp_ExtensionPointer( "glDeleteBuffersARB"); + qglGenBuffersARB = (PFNGLGENBUFFERSARBPROC)GLimp_ExtensionPointer( "glGenBuffersARB"); + qglIsBufferARB = (PFNGLISBUFFERARBPROC)GLimp_ExtensionPointer( "glIsBufferARB"); + qglBufferDataARB = (PFNGLBUFFERDATAARBPROC)GLimp_ExtensionPointer( "glBufferDataARB"); + qglBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)GLimp_ExtensionPointer( "glBufferSubDataARB"); + qglGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC)GLimp_ExtensionPointer( "glGetBufferSubDataARB"); + qglMapBufferARB = (PFNGLMAPBUFFERARBPROC)GLimp_ExtensionPointer( "glMapBufferARB"); + qglUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)GLimp_ExtensionPointer( "glUnmapBufferARB"); + qglGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)GLimp_ExtensionPointer( "glGetBufferParameterivARB"); + qglGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC)GLimp_ExtensionPointer( "glGetBufferPointervARB"); + } + + // ARB_vertex_program + glConfig.ARBVertexProgramAvailable = R_CheckExtension( "GL_ARB_vertex_program" ); + if (glConfig.ARBVertexProgramAvailable) { + qglVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC)GLimp_ExtensionPointer( "glVertexAttribPointerARB" ); + qglEnableVertexAttribArrayARB = (PFNGLENABLEVERTEXATTRIBARRAYARBPROC)GLimp_ExtensionPointer( "glEnableVertexAttribArrayARB" ); + qglDisableVertexAttribArrayARB = (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC)GLimp_ExtensionPointer( "glDisableVertexAttribArrayARB" ); + qglProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC)GLimp_ExtensionPointer( "glProgramStringARB" ); + qglBindProgramARB = (PFNGLBINDPROGRAMARBPROC)GLimp_ExtensionPointer( "glBindProgramARB" ); + qglGenProgramsARB = (PFNGLGENPROGRAMSARBPROC)GLimp_ExtensionPointer( "glGenProgramsARB" ); + qglProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)GLimp_ExtensionPointer( "glProgramEnvParameter4fvARB" ); + qglProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)GLimp_ExtensionPointer( "glProgramLocalParameter4fvARB" ); + } + + // ARB_fragment_program + if ( r_inhibitFragmentProgram.GetBool() ) { + glConfig.ARBFragmentProgramAvailable = false; + } else { + glConfig.ARBFragmentProgramAvailable = R_CheckExtension( "GL_ARB_fragment_program" ); + if (glConfig.ARBFragmentProgramAvailable) { + // these are the same as ARB_vertex_program + qglProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC)GLimp_ExtensionPointer( "glProgramStringARB" ); + qglBindProgramARB = (PFNGLBINDPROGRAMARBPROC)GLimp_ExtensionPointer( "glBindProgramARB" ); + qglProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)GLimp_ExtensionPointer( "glProgramEnvParameter4fvARB" ); + qglProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC)GLimp_ExtensionPointer( "glProgramLocalParameter4fvARB" ); + } + } + + // check for minimum set + if ( !glConfig.multitextureAvailable || !glConfig.textureEnvCombineAvailable || !glConfig.cubeMapAvailable + || !glConfig.envDot3Available ) { + common->Error( common->GetLocalizedString( "#str_06780" ) ); + } + + // GL_EXT_depth_bounds_test + glConfig.depthBoundsTestAvailable = R_CheckExtension( "EXT_depth_bounds_test" ); + if ( glConfig.depthBoundsTestAvailable ) { + qglDepthBoundsEXT = (PFNGLDEPTHBOUNDSEXTPROC)GLimp_ExtensionPointer( "glDepthBoundsEXT" ); + } + +} + + +/* +==================== +R_GetModeInfo + +r_mode is normally a small non-negative integer that +looks resolutions up in a table, but if it is set to -1, +the values from r_customWidth, amd r_customHeight +will be used instead. +==================== +*/ +typedef struct vidmode_s { + const char *description; + int width, height; +} vidmode_t; + +vidmode_t r_vidModes[] = { + { "Mode 0: 320x240", 320, 240 }, + { "Mode 1: 400x300", 400, 300 }, + { "Mode 2: 512x384", 512, 384 }, + { "Mode 3: 640x480", 640, 480 }, + { "Mode 4: 800x600", 800, 600 }, + { "Mode 5: 1024x768", 1024, 768 }, + { "Mode 6: 1152x864", 1152, 864 }, + { "Mode 7: 1280x1024", 1280, 1024 }, + { "Mode 8: 1600x1200", 1600, 1200 }, +}; +static int s_numVidModes = ( sizeof( r_vidModes ) / sizeof( r_vidModes[0] ) ); + +#if MACOS_X +bool R_GetModeInfo( int *width, int *height, int mode ) { +#else +static bool R_GetModeInfo( int *width, int *height, int mode ) { +#endif + vidmode_t *vm; + + if ( mode < -1 ) { + return false; + } + if ( mode >= s_numVidModes ) { + return false; + } + + if ( mode == -1 ) { + *width = r_customWidth.GetInteger(); + *height = r_customHeight.GetInteger(); + return true; + } + + vm = &r_vidModes[mode]; + + if ( width ) { + *width = vm->width; + } + if ( height ) { + *height = vm->height; + } + + return true; +} + + +/* +================== +R_InitOpenGL + +This function is responsible for initializing a valid OpenGL subsystem +for rendering. This is done by calling the system specific GLimp_Init, +which gives us a working OGL subsystem, then setting all necessary openGL +state, including images, vertex programs, and display lists. + +Changes to the vertex cache size or smp state require a vid_restart. + +If glConfig.isInitialized is false, no rendering can take place, but +all renderSystem functions will still operate properly, notably the material +and model information functions. +================== +*/ +void R_InitOpenGL( void ) { + GLint temp; + glimpParms_t parms; + int i; + + common->Printf( "----- R_InitOpenGL -----\n" ); + + if ( glConfig.isInitialized ) { + common->FatalError( "R_InitOpenGL called while active" ); + } + + // in case we had an error while doing a tiled rendering + tr.viewportOffset[0] = 0; + tr.viewportOffset[1] = 0; + + // + // initialize OS specific portions of the renderSystem + // + for ( i = 0 ; i < 2 ; i++ ) { + // set the parameters we are trying + R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, r_mode.GetInteger() ); + + parms.width = glConfig.vidWidth; + parms.height = glConfig.vidHeight; + parms.fullScreen = r_fullscreen.GetBool(); + parms.displayHz = r_displayRefresh.GetInteger(); + parms.multiSamples = r_multiSamples.GetInteger(); + parms.stereo = false; + + if ( GLimp_Init( parms ) ) { + // it worked + break; + } + + if ( i == 1 ) { + common->FatalError( "Unable to initialize OpenGL" ); + } + + // if we failed, set everything back to "safe mode" + // and try again + r_mode.SetInteger( 3 ); + r_fullscreen.SetInteger( 1 ); + r_displayRefresh.SetInteger( 0 ); + r_multiSamples.SetInteger( 0 ); + } + + // input and sound systems need to be tied to the new window + Sys_InitInput(); + soundSystem->InitHW(); + + // get our config strings + glConfig.vendor_string = (const char *)qglGetString(GL_VENDOR); + glConfig.renderer_string = (const char *)qglGetString(GL_RENDERER); + glConfig.version_string = (const char *)qglGetString(GL_VERSION); + glConfig.extensions_string = (const char *)qglGetString(GL_EXTENSIONS); + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp ); + glConfig.maxTextureSize = temp; + + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) { + glConfig.maxTextureSize = 256; + } + + glConfig.isInitialized = true; + + // recheck all the extensions (FIXME: this might be dangerous) + R_CheckPortableExtensions(); + + // parse our vertex and fragment programs, possibly disably support for + // one of the paths if there was an error + R_NV10_Init(); + R_NV20_Init(); + R_R200_Init(); + R_ARB2_Init(); + + cmdSystem->AddCommand( "reloadARBprograms", R_ReloadARBPrograms_f, CMD_FL_RENDERER, "reloads ARB programs" ); + R_ReloadARBPrograms_f( idCmdArgs() ); + + // allocate the vertex array range or vertex objects + vertexCache.Init(); + + // select which renderSystem we are going to use + r_renderer.SetModified(); + tr.SetBackEndRenderer(); + + // allocate the frame data, which may be more if smp is enabled + R_InitFrameData(); + + // Reset our gamma + R_SetColorMappings(); + +#ifdef _WIN32 + static bool glCheck = false; + if ( !glCheck && win32.osversion.dwMajorVersion == 6 ) { + glCheck = true; + if ( !idStr::Icmp( glConfig.vendor_string, "Microsoft" ) && idStr::FindText( glConfig.renderer_string, "OpenGL-D3D" ) != -1 ) { + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart partial windowed\n" ); + Sys_GrabMouseCursor( false ); + } + int ret = MessageBox( NULL, "Please install OpenGL drivers from your graphics hardware vendor to run " GAME_NAME ".\nYour OpenGL functionality is limited.", + "Insufficient OpenGL capabilities", MB_OKCANCEL | MB_ICONWARNING | MB_TASKMODAL ); + if ( ret == IDCANCEL ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + cmdSystem->ExecuteCommandBuffer(); + } + if ( cvarSystem->GetCVarBool( "r_fullscreen" ) ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); + } + } + } +#endif +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrors( void ) { + int err; + char s[64]; + int i; + + // check for up to 10 errors pending + for ( i = 0 ; i < 10 ; i++ ) { + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + idStr::snPrintf( s, sizeof(s), "%i", err); + break; + } + + if ( !r_ignoreGLErrors.GetBool() ) { + common->Printf( "GL_CheckErrors: %s\n", s ); + } + } +} + +/* +===================== +R_ReloadSurface_f + +Reload the material displayed by r_showSurfaceInfo +===================== +*/ +static void R_ReloadSurface_f( const idCmdArgs &args ) { + modelTrace_t mt; + idVec3 start, end; + + // start far enough away that we don't hit the player model + start = tr.primaryView->renderView.vieworg + tr.primaryView->renderView.viewaxis[0] * 16; + end = start + tr.primaryView->renderView.viewaxis[0] * 1000.0f; + if ( !tr.primaryWorld->Trace( mt, start, end, 0.0f, false ) ) { + return; + } + + common->Printf( "Reloading %s\n", mt.material->GetName() ); + + // reload the decl + mt.material->base->Reload(); + + // reload any images used by the decl + mt.material->ReloadImages( false ); +} + + + +/* +============== +R_ListModes_f +============== +*/ +static void R_ListModes_f( const idCmdArgs &args ) { + int i; + + common->Printf( "\n" ); + for ( i = 0; i < s_numVidModes; i++ ) { + common->Printf( "%s\n", r_vidModes[i].description ); + } + common->Printf( "\n" ); +} + + + +/* +============= +R_TestImage_f + +Display the given image centered on the screen. +testimage +testimage +============= +*/ +void R_TestImage_f( const idCmdArgs &args ) { + int imageNum; + + if ( tr.testVideo ) { + delete tr.testVideo; + tr.testVideo = NULL; + } + tr.testImage = NULL; + + if ( args.Argc() != 2 ) { + return; + } + + if ( idStr::IsNumeric( args.Argv(1) ) ) { + imageNum = atoi( args.Argv(1) ); + if ( imageNum >= 0 && imageNum < globalImages->images.Num() ) { + tr.testImage = globalImages->images[imageNum]; + } + } else { + tr.testImage = globalImages->ImageFromFile( args.Argv( 1 ), TF_DEFAULT, false, TR_REPEAT, TD_DEFAULT ); + } +} + +/* +============= +R_TestVideo_f + +Plays the cinematic file in a testImage +============= +*/ +void R_TestVideo_f( const idCmdArgs &args ) { + if ( tr.testVideo ) { + delete tr.testVideo; + tr.testVideo = NULL; + } + tr.testImage = NULL; + + if ( args.Argc() < 2 ) { + return; + } + + tr.testImage = globalImages->ImageFromFile( "_scratch", TF_DEFAULT, false, TR_REPEAT, TD_DEFAULT ); + tr.testVideo = idCinematic::Alloc(); + tr.testVideo->InitFromFile( args.Argv( 1 ), true ); + + cinData_t cin; + cin = tr.testVideo->ImageForTime( 0 ); + if ( !cin.image ) { + delete tr.testVideo; + tr.testVideo = NULL; + tr.testImage = NULL; + return; + } + + common->Printf( "%i x %i images\n", cin.imageWidth, cin.imageHeight ); + + int len = tr.testVideo->AnimationLength(); + common->Printf( "%5.1f seconds of video\n", len * 0.001 ); + + tr.testVideoStartTime = tr.primaryRenderView.time * 0.001; + + // try to play the matching wav file + idStr wavString = args.Argv( ( args.Argc() == 2 ) ? 1 : 2 ); + wavString.StripFileExtension(); + wavString = wavString + ".wav"; + soundSystem->PlayShaderDirectly( 1, wavString.c_str(), -1 ); +} + +static int R_QsortSurfaceAreas( const void *a, const void *b ) { + const idMaterial *ea, *eb; + int ac, bc; + + ea = *(idMaterial **)a; + if ( !ea->EverReferenced() ) { + ac = 0; + } else { + ac = ea->GetSurfaceArea(); + } + eb = *(idMaterial **)b; + if ( !eb->EverReferenced() ) { + bc = 0; + } else { + bc = eb->GetSurfaceArea(); + } + + if ( ac < bc ) { + return -1; + } + if ( ac > bc ) { + return 1; + } + + return idStr::Icmp( ea->GetName(), eb->GetName() ); +} + + +/* +=================== +R_ReportSurfaceAreas_f + +Prints a list of the materials sorted by surface area +=================== +*/ +void R_ReportSurfaceAreas_f( const idCmdArgs &args ) { + int i, count; + idMaterial **list; + + count = declManager->GetNumDecls( DECL_MATERIAL ); + list = (idMaterial **)_alloca( count * sizeof( *list ) ); + + for ( i = 0 ; i < count ; i++ ) { + list[i] = (idMaterial *)declManager->DeclByIndex( DECL_MATERIAL, i, false ); + } + + qsort( list, count, sizeof( list[0] ), R_QsortSurfaceAreas ); + + // skip over ones with 0 area + for ( i = 0 ; i < count ; i++ ) { + if ( list[i]->GetSurfaceArea() > 0 ) { + break; + } + } + + for ( ; i < count ; i++ ) { + // report size in "editor blocks" + int blocks = list[i]->GetSurfaceArea() / 4096.0; + common->Printf( "%7i %s\n", blocks, list[i]->GetName() ); + } +} + +/* +=================== +R_ReportImageDuplication_f + +Checks for images with the same hash value and does a better comparison +=================== +*/ +void R_ReportImageDuplication_f( const idCmdArgs &args ) { + int i, j; + + common->Printf( "Images with duplicated contents:\n" ); + + int count = 0; + + for ( i = 0 ; i < globalImages->images.Num() ; i++ ) { + idImage *image1 = globalImages->images[i]; + + if ( image1->isPartialImage ) { + // ignore background loading stubs + continue; + } + if ( image1->generatorFunction ) { + // ignore procedural images + continue; + } + if ( image1->cubeFiles != CF_2D ) { + // ignore cube maps + continue; + } + if ( image1->defaulted ) { + continue; + } + byte *data1; + int w1, h1; + + R_LoadImageProgram( image1->imgName, &data1, &w1, &h1, NULL ); + + for ( j = 0 ; j < i ; j++ ) { + idImage *image2 = globalImages->images[j]; + + if ( image2->isPartialImage ) { + continue; + } + if ( image2->generatorFunction ) { + continue; + } + if ( image2->cubeFiles != CF_2D ) { + continue; + } + if ( image2->defaulted ) { + continue; + } + if ( image1->imageHash != image2->imageHash ) { + continue; + } + if ( image2->uploadWidth != image1->uploadWidth + || image2->uploadHeight != image1->uploadHeight ) { + continue; + } + if ( !idStr::Icmp( image1->imgName, image2->imgName ) ) { + // ignore same image-with-different-parms + continue; + } + + byte *data2; + int w2, h2; + + R_LoadImageProgram( image2->imgName, &data2, &w2, &h2, NULL ); + + if ( w2 != w1 || h2 != h1 ) { + R_StaticFree( data2 ); + continue; + } + + if ( memcmp( data1, data2, w1*h1*4 ) ) { + R_StaticFree( data2 ); + continue; + } + + R_StaticFree( data2 ); + + common->Printf( "%s == %s\n", image1->imgName.c_str(), image2->imgName.c_str() ); + session->UpdateScreen( true ); + count++; + break; + } + + R_StaticFree( data1 ); + } + common->Printf( "%i / %i collisions\n", count, globalImages->images.Num() ); +} + +/* +============================================================================== + + THROUGHPUT BENCHMARKING + +============================================================================== +*/ + +/* +================ +R_RenderingFPS +================ +*/ +static float R_RenderingFPS( const renderView_t *renderView ) { + qglFinish(); + + int start = Sys_Milliseconds(); + static const int SAMPLE_MSEC = 1000; + int end; + int count = 0; + + while( 1 ) { + // render + renderSystem->BeginFrame( glConfig.vidWidth, glConfig.vidHeight ); + tr.primaryWorld->RenderScene( renderView ); + renderSystem->EndFrame( NULL, NULL ); + qglFinish(); + count++; + end = Sys_Milliseconds(); + if ( end - start > SAMPLE_MSEC ) { + break; + } + } + + float fps = count * 1000.0 / ( end - start ); + + return fps; +} + +/* +================ +R_Benchmark_f +================ +*/ +void R_Benchmark_f( const idCmdArgs &args ) { + float fps, msec; + renderView_t view; + + if ( !tr.primaryView ) { + common->Printf( "No primaryView for benchmarking\n" ); + return; + } + view = tr.primaryRenderView; + + for ( int size = 100 ; size >= 10 ; size -= 10 ) { + r_screenFraction.SetInteger( size ); + fps = R_RenderingFPS( &view ); + int kpix = glConfig.vidWidth * glConfig.vidHeight * ( size * 0.01 ) * ( size * 0.01 ) * 0.001; + msec = 1000.0 / fps; + common->Printf( "kpix: %4i msec:%5.1f fps:%5.1f\n", kpix, msec, fps ); + } + + // enable r_singleTriangle 1 while r_screenFraction is still at 10 + r_singleTriangle.SetBool( 1 ); + fps = R_RenderingFPS( &view ); + msec = 1000.0 / fps; + common->Printf( "single tri msec:%5.1f fps:%5.1f\n", msec, fps ); + r_singleTriangle.SetBool( 0 ); + r_screenFraction.SetInteger( 100 ); + + // enable r_skipRenderContext 1 + r_skipRenderContext.SetBool( true ); + fps = R_RenderingFPS( &view ); + msec = 1000.0 / fps; + common->Printf( "no context msec:%5.1f fps:%5.1f\n", msec, fps ); + r_skipRenderContext.SetBool( false ); +} + + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ + +/* +==================== +R_ReadTiledPixels + +Allows the rendering of an image larger than the actual window by +tiling it into window-sized chunks and rendering each chunk separately + +If ref isn't specified, the full session UpdateScreen will be done. +==================== +*/ +void R_ReadTiledPixels( int width, int height, byte *buffer, renderView_t *ref = NULL ) { + // include extra space for OpenGL padding to word boundaries + byte *temp = (byte *)R_StaticAlloc( (glConfig.vidWidth+3) * glConfig.vidHeight * 3 ); + + int oldWidth = glConfig.vidWidth; + int oldHeight = glConfig.vidHeight; + + tr.tiledViewport[0] = width; + tr.tiledViewport[1] = height; + + // disable scissor, so we don't need to adjust all those rects + r_useScissor.SetBool( false ); + + for ( int xo = 0 ; xo < width ; xo += oldWidth ) { + for ( int yo = 0 ; yo < height ; yo += oldHeight ) { + tr.viewportOffset[0] = -xo; + tr.viewportOffset[1] = -yo; + + if ( ref ) { + tr.BeginFrame( oldWidth, oldHeight ); + tr.primaryWorld->RenderScene( ref ); + tr.EndFrame( NULL, NULL ); + } else { + session->UpdateScreen(); + } + + int w = oldWidth; + if ( xo + w > width ) { + w = width - xo; + } + int h = oldHeight; + if ( yo + h > height ) { + h = height - yo; + } + + qglReadBuffer( GL_FRONT ); + qglReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, temp ); + + int row = ( w * 3 + 3 ) & ~3; // OpenGL pads to dword boundaries + + for ( int y = 0 ; y < h ; y++ ) { + memcpy( buffer + ( ( yo + y )* width + xo ) * 3, + temp + y * row, w * 3 ); + } + } + } + + r_useScissor.SetBool( true ); + + tr.viewportOffset[0] = 0; + tr.viewportOffset[1] = 0; + tr.tiledViewport[0] = 0; + tr.tiledViewport[1] = 0; + + R_StaticFree( temp ); + + glConfig.vidWidth = oldWidth; + glConfig.vidHeight = oldHeight; +} + + +/* +================== +TakeScreenshot + +Move to tr_imagefiles.c... + +Will automatically tile render large screen shots if necessary +Downsample is the number of steps to mipmap the image before saving it +If ref == NULL, session->updateScreen will be used +================== +*/ +void idRenderSystemLocal::TakeScreenshot( int width, int height, const char *fileName, int blends, renderView_t *ref, const char *basePath ) { + byte *buffer; + int i, j, c, temp; + + takingScreenshot = true; + + int pix = width * height; + + buffer = (byte *)R_StaticAlloc(pix*3 + 18); + memset (buffer, 0, 18); + + if ( blends <= 1 ) { + R_ReadTiledPixels( width, height, buffer + 18, ref ); + } else { + unsigned short *shortBuffer = (unsigned short *)R_StaticAlloc(pix*2*3); + memset (shortBuffer, 0, pix*2*3); + + // enable anti-aliasing jitter + r_jitter.SetBool( true ); + + for ( i = 0 ; i < blends ; i++ ) { + R_ReadTiledPixels( width, height, buffer + 18, ref ); + + for ( j = 0 ; j < pix*3 ; j++ ) { + shortBuffer[j] += buffer[18+j]; + } + } + + // divide back to bytes + for ( i = 0 ; i < pix*3 ; i++ ) { + buffer[18+i] = shortBuffer[i] / blends; + } + + R_StaticFree( shortBuffer ); + r_jitter.SetBool( false ); + } + + // fill in the header (this is vertically flipped, which qglReadPixels emits) + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 3; + for (i=18 ; iWriteFile( fileName, buffer, c, basePath ); + + R_StaticFree( buffer ); + + takingScreenshot = false; + +} + +void idRenderSystemLocal::TakeJPGScreenshot( int width, int height, const char *fileName, + int blends, renderView_t *ref, const char *basePath ) { + // Keep screenshot capture available while the retail in-memory JPEG + // destination manager is reconstructed. The pixel/readback path and blend + // behavior are identical; TakeScreenshot supplies the stable fallback. + TakeScreenshot( width, height, fileName, blends, ref, basePath ); +} + + +/* +================== +R_ScreenshotFilename + +Returns a filename with digits appended +if we have saved a previous screenshot, don't scan +from the beginning, because recording demo avis can involve +thousands of shots +================== +*/ +void R_ScreenshotFilename( int &lastNumber, const char *base, const char *extension, idStr &fileName ) { + int a,b,c,d, e; + + bool restrict = cvarSystem->GetCVarBool( "fs_restrict" ); + cvarSystem->SetCVarBool( "fs_restrict", false ); + + lastNumber++; + if ( lastNumber > 99999 ) { + lastNumber = 99999; + } + for ( ; lastNumber < 99999 ; lastNumber++ ) { + int frac = lastNumber; + + a = frac / 10000; + frac -= a*10000; + b = frac / 1000; + frac -= b*1000; + c = frac / 100; + frac -= c*100; + d = frac / 10; + frac -= d*10; + e = frac; + + sprintf( fileName, "%s%i%i%i%i%i%s", base, a, b, c, d, e, extension ); + if ( lastNumber == 99999 ) { + break; + } + int len = fileSystem->ReadFile( fileName, NULL, NULL ); + if ( len <= 0 ) { + break; + } + // check again... + } + cvarSystem->SetCVarBool( "fs_restrict", restrict ); +} + +/* +================== +R_BlendedScreenShot + +screenshot +screenshot [filename] +screenshot [width] [height] +screenshot [width] [height] [samples] +================== +*/ +#define MAX_BLENDS 256 // to keep the accumulation in shorts +void R_ScreenShot_f( const idCmdArgs &args ) { + static int lastNumber = 0; + idStr checkname; + + int width = glConfig.vidWidth; + int height = glConfig.vidHeight; + int x = 0; + int y = 0; + int blends = 0; + + switch ( args.Argc() ) { + case 1: + width = glConfig.vidWidth; + height = glConfig.vidHeight; + blends = 1; + R_ScreenshotFilename( lastNumber, "screenshots/shot", ".tga", checkname ); + break; + case 2: + width = glConfig.vidWidth; + height = glConfig.vidHeight; + blends = 1; + checkname = args.Argv( 1 ); + break; + case 3: + width = atoi( args.Argv( 1 ) ); + height = atoi( args.Argv( 2 ) ); + blends = 1; + R_ScreenshotFilename( lastNumber, "screenshots/shot", ".tga", checkname ); + break; + case 4: + width = atoi( args.Argv( 1 ) ); + height = atoi( args.Argv( 2 ) ); + blends = atoi( args.Argv( 3 ) ); + if ( blends < 1 ) { + blends = 1; + } + if ( blends > MAX_BLENDS ) { + blends = MAX_BLENDS; + } + R_ScreenshotFilename( lastNumber, "screenshots/shot", ".tga", checkname ); + break; + default: + common->Printf( "usage: screenshot\n screenshot \n screenshot \n screenshot \n" ); + return; + } + + // put the console away + console->Close(); + + tr.TakeScreenshot( width, height, checkname, blends, NULL ); + + common->Printf( "Wrote %s\n", checkname.c_str() ); +} + +/* +=============== +R_StencilShot +Save out a screenshot showing the stencil buffer expanded by 16x range +=============== +*/ +void R_StencilShot( void ) { + byte *buffer; + int i, c; + + int width = tr.GetScreenWidth(); + int height = tr.GetScreenHeight(); + + int pix = width * height; + + c = pix * 3 + 18; + buffer = (byte *)Mem_Alloc(c); + memset (buffer, 0, 18); + + byte *byteBuffer = (byte *)Mem_Alloc(pix); + + qglReadPixels( 0, 0, width, height, GL_STENCIL_INDEX , GL_UNSIGNED_BYTE, byteBuffer ); + + for ( i = 0 ; i < pix ; i++ ) { + buffer[18+i*3] = + buffer[18+i*3+1] = + // buffer[18+i*3+2] = ( byteBuffer[i] & 15 ) * 16; + buffer[18+i*3+2] = byteBuffer[i]; + } + + // fill in the header (this is vertically flipped, which qglReadPixels emits) + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + fileSystem->WriteFile( "screenshots/stencilShot.tga", buffer, c, "fs_savepath" ); + + Mem_Free( buffer ); + Mem_Free( byteBuffer ); +} + +/* +================== +R_EnvShot_f + +envshot + +Saves out env/_ft.tga, etc +================== +*/ +void R_EnvShot_f( const idCmdArgs &args ) { + idStr fullname; + const char *baseName; + int i; + idMat3 axis[6]; + renderView_t ref; + viewDef_t primary; + int blends; + char *extensions[6] = { "_px.tga", "_nx.tga", "_py.tga", "_ny.tga", + "_pz.tga", "_nz.tga" }; + int size; + + if ( args.Argc() != 2 && args.Argc() != 3 && args.Argc() != 4 ) { + common->Printf( "USAGE: envshot [size] [blends]\n" ); + return; + } + baseName = args.Argv( 1 ); + + blends = 1; + if ( args.Argc() == 4 ) { + size = atoi( args.Argv( 2 ) ); + blends = atoi( args.Argv( 3 ) ); + } else if ( args.Argc() == 3 ) { + size = atoi( args.Argv( 2 ) ); + blends = 1; + } else { + size = 256; + blends = 1; + } + + if ( !tr.primaryView ) { + common->Printf( "No primary view.\n" ); + return; + } + + primary = *tr.primaryView; + + memset( &axis, 0, sizeof( axis ) ); + axis[0][0][0] = 1; + axis[0][1][2] = 1; + axis[0][2][1] = 1; + + axis[1][0][0] = -1; + axis[1][1][2] = -1; + axis[1][2][1] = 1; + + axis[2][0][1] = 1; + axis[2][1][0] = -1; + axis[2][2][2] = -1; + + axis[3][0][1] = -1; + axis[3][1][0] = -1; + axis[3][2][2] = 1; + + axis[4][0][2] = 1; + axis[4][1][0] = -1; + axis[4][2][1] = 1; + + axis[5][0][2] = -1; + axis[5][1][0] = 1; + axis[5][2][1] = 1; + + for ( i = 0 ; i < 6 ; i++ ) { + ref = primary.renderView; + ref.x = ref.y = 0; + ref.fov_x = ref.fov_y = 90; + ref.width = glConfig.vidWidth; + ref.height = glConfig.vidHeight; + ref.viewaxis = axis[i]; + sprintf( fullname, "env/%s%s", baseName, extensions[i] ); + tr.TakeScreenshot( size, size, fullname, blends, &ref ); + } + + common->Printf( "Wrote %s, etc\n", fullname.c_str() ); +} + +//============================================================================ + +static idMat3 cubeAxis[6]; + + +/* +================== +R_SampleCubeMap +================== +*/ +void R_SampleCubeMap( const idVec3 &dir, int size, byte *buffers[6], byte result[4] ) { + float adir[3]; + int axis, x, y; + + adir[0] = fabs(dir[0]); + adir[1] = fabs(dir[1]); + adir[2] = fabs(dir[2]); + + if ( dir[0] >= adir[1] && dir[0] >= adir[2] ) { + axis = 0; + } else if ( -dir[0] >= adir[1] && -dir[0] >= adir[2] ) { + axis = 1; + } else if ( dir[1] >= adir[0] && dir[1] >= adir[2] ) { + axis = 2; + } else if ( -dir[1] >= adir[0] && -dir[1] >= adir[2] ) { + axis = 3; + } else if ( dir[2] >= adir[1] && dir[2] >= adir[2] ) { + axis = 4; + } else { + axis = 5; + } + + float fx = (dir * cubeAxis[axis][1]) / (dir * cubeAxis[axis][0]); + float fy = (dir * cubeAxis[axis][2]) / (dir * cubeAxis[axis][0]); + + fx = -fx; + fy = -fy; + x = size * 0.5 * (fx + 1); + y = size * 0.5 * (fy + 1); + if ( x < 0 ) { + x = 0; + } else if ( x >= size ) { + x = size-1; + } + if ( y < 0 ) { + y = 0; + } else if ( y >= size ) { + y = size-1; + } + + result[0] = buffers[axis][(y*size+x)*4+0]; + result[1] = buffers[axis][(y*size+x)*4+1]; + result[2] = buffers[axis][(y*size+x)*4+2]; + result[3] = buffers[axis][(y*size+x)*4+3]; +} + +/* +================== +R_MakeAmbientMap_f + +R_MakeAmbientMap_f [size] + +Saves out env/_amb_ft.tga, etc +================== +*/ +void R_MakeAmbientMap_f( const idCmdArgs &args ) { + idStr fullname; + const char *baseName; + int i; + renderView_t ref; + viewDef_t primary; + int downSample; + char *extensions[6] = { "_px.tga", "_nx.tga", "_py.tga", "_ny.tga", + "_pz.tga", "_nz.tga" }; + int outSize; + byte *buffers[6]; + int width, height; + + if ( args.Argc() != 2 && args.Argc() != 3 ) { + common->Printf( "USAGE: ambientshot [size]\n" ); + return; + } + baseName = args.Argv( 1 ); + + downSample = 0; + if ( args.Argc() == 3 ) { + outSize = atoi( args.Argv( 2 ) ); + } else { + outSize = 32; + } + + memset( &cubeAxis, 0, sizeof( cubeAxis ) ); + cubeAxis[0][0][0] = 1; + cubeAxis[0][1][2] = 1; + cubeAxis[0][2][1] = 1; + + cubeAxis[1][0][0] = -1; + cubeAxis[1][1][2] = -1; + cubeAxis[1][2][1] = 1; + + cubeAxis[2][0][1] = 1; + cubeAxis[2][1][0] = -1; + cubeAxis[2][2][2] = -1; + + cubeAxis[3][0][1] = -1; + cubeAxis[3][1][0] = -1; + cubeAxis[3][2][2] = 1; + + cubeAxis[4][0][2] = 1; + cubeAxis[4][1][0] = -1; + cubeAxis[4][2][1] = 1; + + cubeAxis[5][0][2] = -1; + cubeAxis[5][1][0] = 1; + cubeAxis[5][2][1] = 1; + + // read all of the images + for ( i = 0 ; i < 6 ; i++ ) { + sprintf( fullname, "env/%s%s", baseName, extensions[i] ); + common->Printf( "loading %s\n", fullname.c_str() ); + session->UpdateScreen(); + R_LoadImage( fullname, &buffers[i], &width, &height, NULL, true ); + if ( !buffers[i] ) { + common->Printf( "failed.\n" ); + for ( i-- ; i >= 0 ; i-- ) { + Mem_Free( buffers[i] ); + } + return; + } + } + + // resample with hemispherical blending + int samples = 1000; + + byte *outBuffer = (byte *)_alloca( outSize * outSize * 4 ); + + for ( int map = 0 ; map < 2 ; map++ ) { + for ( i = 0 ; i < 6 ; i++ ) { + for ( int x = 0 ; x < outSize ; x++ ) { + for ( int y = 0 ; y < outSize ; y++ ) { + idVec3 dir; + float total[3]; + + dir = cubeAxis[i][0] + -( -1 + 2.0*x/(outSize-1) ) * cubeAxis[i][1] + -( -1 + 2.0*y/(outSize-1) ) * cubeAxis[i][2]; + dir.Normalize(); + total[0] = total[1] = total[2] = 0; + //samples = 1; + float limit = map ? 0.95 : 0.25; // small for specular, almost hemisphere for ambient + + for ( int s = 0 ; s < samples ; s++ ) { + // pick a random direction vector that is inside the unit sphere but not behind dir, + // which is a robust way to evenly sample a hemisphere + idVec3 test; + while( 1 ) { + for ( int j = 0 ; j < 3 ; j++ ) { + test[j] = -1 + 2 * (rand()&0x7fff)/(float)0x7fff; + } + if ( test.Length() > 1.0 ) { + continue; + } + test.Normalize(); + if ( test * dir > limit ) { // don't do a complete hemisphere + break; + } + } + byte result[4]; + //test = dir; + R_SampleCubeMap( test, width, buffers, result ); + total[0] += result[0]; + total[1] += result[1]; + total[2] += result[2]; + } + outBuffer[(y*outSize+x)*4+0] = total[0] / samples; + outBuffer[(y*outSize+x)*4+1] = total[1] / samples; + outBuffer[(y*outSize+x)*4+2] = total[2] / samples; + outBuffer[(y*outSize+x)*4+3] = 255; + } + } + + if ( map == 0 ) { + sprintf( fullname, "env/%s_amb%s", baseName, extensions[i] ); + } else { + sprintf( fullname, "env/%s_spec%s", baseName, extensions[i] ); + } + common->Printf( "writing %s\n", fullname.c_str() ); + session->UpdateScreen(); + R_WriteTGA( fullname, outBuffer, outSize, outSize ); + } + } + + for ( i = 0 ; i < 6 ; i++ ) { + if ( buffers[i] ) { + Mem_Free( buffers[i] ); + } + } +} + +//============================================================================ + + +/* +=============== +R_SetColorMappings +=============== +*/ +void R_SetColorMappings( void ) { + int i, j; + float g, b; + int inf; + + b = r_brightness.GetFloat(); + g = r_gamma.GetFloat(); + + for ( i = 0; i < 256; i++ ) { + j = i * b; + if (j > 255) { + j = 255; + } + + if ( g == 1 ) { + inf = (j<<8) | j; + } else { + inf = 0xffff * pow ( j/255.0f, 1.0f / g ) + 0.5f; + } + if (inf < 0) { + inf = 0; + } + if (inf > 0xffff) { + inf = 0xffff; + } + + tr.gammaTable[i] = inf; + } + + GLimp_SetGamma( tr.gammaTable, tr.gammaTable, tr.gammaTable ); +} + + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( const idCmdArgs &args ) { + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + common->Printf( "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + common->Printf( "GL_RENDERER: %s\n", glConfig.renderer_string ); + common->Printf( "GL_VERSION: %s\n", glConfig.version_string ); + common->Printf( "GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + if ( glConfig.wgl_extensions_string ) { + common->Printf( "WGL_EXTENSIONS: %s\n", glConfig.wgl_extensions_string ); + } + common->Printf( "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + common->Printf( "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.maxTextureUnits ); + common->Printf( "GL_MAX_TEXTURE_COORDS_ARB: %d\n", glConfig.maxTextureCoords ); + common->Printf( "GL_MAX_TEXTURE_IMAGE_UNITS_ARB: %d\n", glConfig.maxTextureImageUnits ); + common->Printf( "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + common->Printf( "MODE: %d, %d x %d %s hz:", r_mode.GetInteger(), glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen.GetBool()] ); + + if ( glConfig.displayFrequency ) { + common->Printf( "%d\n", glConfig.displayFrequency ); + } else { + common->Printf( "N/A\n" ); + } + common->Printf( "CPU: %s\n", Sys_GetProcessorString() ); + + const char *active[2] = { "", " (ACTIVE)" }; + common->Printf( "ARB path ENABLED%s\n", active[tr.backEndRenderer == BE_ARB] ); + + if ( glConfig.allowNV10Path ) { + common->Printf( "NV10 path ENABLED%s\n", active[tr.backEndRenderer == BE_NV10] ); + } else { + common->Printf( "NV10 path disabled\n" ); + } + + if ( glConfig.allowNV20Path ) { + common->Printf( "NV20 path ENABLED%s\n", active[tr.backEndRenderer == BE_NV20] ); + } else { + common->Printf( "NV20 path disabled\n" ); + } + + if ( glConfig.allowR200Path ) { + common->Printf( "R200 path ENABLED%s\n", active[tr.backEndRenderer == BE_R200] ); + } else { + common->Printf( "R200 path disabled\n" ); + } + + if ( glConfig.allowARB2Path ) { + common->Printf( "ARB2 path ENABLED%s\n", active[tr.backEndRenderer == BE_ARB2] ); + } else { + common->Printf( "ARB2 path disabled\n" ); + } + + //============================= + + common->Printf( "-------\n" ); + + if ( r_finish.GetBool() ) { + common->Printf( "Forcing glFinish\n" ); + } else { + common->Printf( "glFinish not forced\n" ); + } + +#ifdef _WIN32 +// WGL_EXT_swap_interval +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); +extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + + if ( r_swapInterval.GetInteger() && wglSwapIntervalEXT ) { + common->Printf( "Forcing swapInterval %i\n", r_swapInterval.GetInteger() ); + } else { + common->Printf( "swapInterval not forced\n" ); + } +#endif + + bool tss = glConfig.twoSidedStencilAvailable || glConfig.atiTwoSidedStencilAvailable; + + if ( !r_useTwoSidedStencil.GetBool() && tss ) { + common->Printf( "Two sided stencil available but disabled\n" ); + } else if ( !tss ) { + common->Printf( "Two sided stencil not available\n" ); + } else if ( tss ) { + common->Printf( "Using two sided stencil\n" ); + } + + if ( vertexCache.IsFast() ) { + common->Printf( "Vertex cache is fast\n" ); + } else { + common->Printf( "Vertex cache is SLOW\n" ); + } +} + +/* +================= +R_VidRestart_f +================= +*/ +void R_VidRestart_f( const idCmdArgs &args ) { + int err; + + // if OpenGL isn't started, do nothing + if ( !glConfig.isInitialized ) { + return; + } + + bool full = true; + bool forceWindow = false; + for ( int i = 1 ; i < args.Argc() ; i++ ) { + if ( idStr::Icmp( args.Argv( i ), "partial" ) == 0 ) { + full = false; + continue; + } + if ( idStr::Icmp( args.Argv( i ), "windowed" ) == 0 ) { + forceWindow = true; + continue; + } + } + + // this could take a while, so give them the cursor back ASAP + Sys_GrabMouseCursor( false ); + + // dump ambient caches + renderModelManager->FreeModelVertexCaches(); + + // free any current world interaction surfaces and vertex caches + R_FreeDerivedData(); + + // make sure the defered frees are actually freed + R_ToggleSmpFrame(); + R_ToggleSmpFrame(); + + // free the vertex caches so they will be regenerated again + vertexCache.PurgeAll(); + + // sound and input are tied to the window we are about to destroy + + if ( full ) { + // free all of our texture numbers + soundSystem->ShutdownHW(); + Sys_ShutdownInput(); + globalImages->PurgeAllImages(); + // free the context and close the window + GLimp_Shutdown(); + glConfig.isInitialized = false; + + // create the new context and vertex cache + bool latch = cvarSystem->GetCVarBool( "r_fullscreen" ); + if ( forceWindow ) { + cvarSystem->SetCVarBool( "r_fullscreen", false ); + } + R_InitOpenGL(); + cvarSystem->SetCVarBool( "r_fullscreen", latch ); + + // regenerate all images + globalImages->ReloadAllImages(); + } else { + glimpParms_t parms; + parms.width = glConfig.vidWidth; + parms.height = glConfig.vidHeight; + parms.fullScreen = ( forceWindow ) ? false : r_fullscreen.GetBool(); + parms.displayHz = r_displayRefresh.GetInteger(); + parms.multiSamples = r_multiSamples.GetInteger(); + parms.stereo = false; + GLimp_SetScreenParms( parms ); + } + + + + // make sure the regeneration doesn't use anything no longer valid + tr.viewCount++; + tr.viewDef = NULL; + + // regenerate all necessary interactions + R_RegenerateWorld_f( idCmdArgs() ); + + // check for problems + err = qglGetError(); + if ( err != GL_NO_ERROR ) { + common->Printf( "glGetError() = 0x%x\n", err ); + } + + // start sound playing again + soundSystem->SetMute( false ); +} + + +/* +================= +R_InitMaterials +================= +*/ +void R_InitMaterials( void ) { + tr.defaultMaterial = declManager->FindMaterial( "_default", false ); + if ( !tr.defaultMaterial ) { + common->FatalError( "_default material not found" ); + } + declManager->FindMaterial( "_default", false ); + + // needed by R_DeriveLightData + declManager->FindMaterial( "lights/defaultPointLight" ); + declManager->FindMaterial( "lights/defaultProjectedLight" ); +} + + +/* +================= +R_SizeUp_f + +Keybinding command +================= +*/ +static void R_SizeUp_f( const idCmdArgs &args ) { + if ( r_screenFraction.GetInteger() + 10 > 100 ) { + r_screenFraction.SetInteger( 100 ); + } else { + r_screenFraction.SetInteger( r_screenFraction.GetInteger() + 10 ); + } +} + + +/* +================= +R_SizeDown_f + +Keybinding command +================= +*/ +static void R_SizeDown_f( const idCmdArgs &args ) { + if ( r_screenFraction.GetInteger() - 10 < 10 ) { + r_screenFraction.SetInteger( 10 ); + } else { + r_screenFraction.SetInteger( r_screenFraction.GetInteger() - 10 ); + } +} + + +/* +=============== +TouchGui_f + + this is called from the main thread +=============== +*/ +void R_TouchGui_f( const idCmdArgs &args ) { + const char *gui = args.Argv( 1 ); + + if ( !gui[0] ) { + common->Printf( "USAGE: touchGui \n" ); + return; + } + + common->Printf( "touchGui %s\n", gui ); + session->UpdateScreen(); + uiManager->Touch( gui ); +} + +/* +================= +R_InitCvars +================= +*/ +void R_InitCvars( void ) { + // update latched cvars here +} + +/* +================= +R_InitCommands +================= +*/ +void R_InitCommands( void ) { + cmdSystem->AddCommand( "MakeMegaTexture", idMegaTexture::MakeMegaTexture_f, CMD_FL_RENDERER|CMD_FL_CHEAT, "processes giant images" ); + cmdSystem->AddCommand( "sizeUp", R_SizeUp_f, CMD_FL_RENDERER, "makes the rendered view larger" ); + cmdSystem->AddCommand( "sizeDown", R_SizeDown_f, CMD_FL_RENDERER, "makes the rendered view smaller" ); + cmdSystem->AddCommand( "reloadGuis", R_ReloadGuis_f, CMD_FL_RENDERER, "reloads guis" ); + cmdSystem->AddCommand( "listGuis", R_ListGuis_f, CMD_FL_RENDERER, "lists guis" ); + cmdSystem->AddCommand( "touchGui", R_TouchGui_f, CMD_FL_RENDERER, "touches a gui" ); + cmdSystem->AddCommand( "screenshot", R_ScreenShot_f, CMD_FL_RENDERER, "takes a screenshot" ); + cmdSystem->AddCommand( "envshot", R_EnvShot_f, CMD_FL_RENDERER, "takes an environment shot" ); + cmdSystem->AddCommand( "makeAmbientMap", R_MakeAmbientMap_f, CMD_FL_RENDERER|CMD_FL_CHEAT, "makes an ambient map" ); + cmdSystem->AddCommand( "benchmark", R_Benchmark_f, CMD_FL_RENDERER, "benchmark" ); + cmdSystem->AddCommand( "gfxInfo", GfxInfo_f, CMD_FL_RENDERER, "show graphics info" ); + cmdSystem->AddCommand( "modulateLights", R_ModulateLights_f, CMD_FL_RENDERER | CMD_FL_CHEAT, "modifies shader parms on all lights" ); + cmdSystem->AddCommand( "testImage", R_TestImage_f, CMD_FL_RENDERER | CMD_FL_CHEAT, "displays the given image centered on screen", idCmdSystem::ArgCompletion_ImageName ); + cmdSystem->AddCommand( "testVideo", R_TestVideo_f, CMD_FL_RENDERER | CMD_FL_CHEAT, "displays the given cinematic", idCmdSystem::ArgCompletion_VideoName ); + cmdSystem->AddCommand( "reportSurfaceAreas", R_ReportSurfaceAreas_f, CMD_FL_RENDERER, "lists all used materials sorted by surface area" ); + cmdSystem->AddCommand( "reportImageDuplication", R_ReportImageDuplication_f, CMD_FL_RENDERER, "checks all referenced images for duplications" ); + cmdSystem->AddCommand( "regenerateWorld", R_RegenerateWorld_f, CMD_FL_RENDERER, "regenerates all interactions" ); + cmdSystem->AddCommand( "showInteractionMemory", R_ShowInteractionMemory_f, CMD_FL_RENDERER, "shows memory used by interactions" ); + cmdSystem->AddCommand( "showTriSurfMemory", R_ShowTriSurfMemory_f, CMD_FL_RENDERER, "shows memory used by triangle surfaces" ); + cmdSystem->AddCommand( "vid_restart", R_VidRestart_f, CMD_FL_RENDERER, "restarts renderSystem" ); + cmdSystem->AddCommand( "listRenderEntityDefs", R_ListRenderEntityDefs_f, CMD_FL_RENDERER, "lists the entity defs" ); + cmdSystem->AddCommand( "listRenderLightDefs", R_ListRenderLightDefs_f, CMD_FL_RENDERER, "lists the light defs" ); + cmdSystem->AddCommand( "listModes", R_ListModes_f, CMD_FL_RENDERER, "lists all video modes" ); + cmdSystem->AddCommand( "reloadSurface", R_ReloadSurface_f, CMD_FL_RENDERER, "reloads the decl and images for selected surface" ); +} + +/* +=============== +idRenderSystemLocal::Clear +=============== +*/ +void idRenderSystemLocal::Clear( void ) { + registered = false; + frameCount = 0; + viewCount = 0; + staticAllocCount = 0; + frameShaderTime = 0.0f; + smpFrame = 0; + deltaTime = 0.0f; + mBenchmarkMode = BENCHMARK_NONE; + memset( &mSavePC, 0, sizeof( mSavePC ) ); + lastLoadedImage.Clear(); + viewportOffset[0] = 0; + viewportOffset[1] = 0; + tiledViewport[0] = 0; + tiledViewport[1] = 0; + backEndRenderer = BE_BAD; + backEndRendererHasVertexPrograms = false; + backEndRendererMaxLight = 1.0f; + ambientLightVector.Zero(); + sortOffset = 0; + worlds.Clear(); + primaryWorld = NULL; + memset( &primaryRenderView, 0, sizeof( primaryRenderView ) ); + primaryView = NULL; + defaultMaterial = NULL; + testImage = NULL; + testVideo = NULL; + memset( debugGraph, 0, sizeof( debugGraph ) ); + debugGraphMaterial = NULL; + testStandaloneVideo = NULL; + testVideoStartTime = 0.0f; + viewDef = NULL; + memset( &pc, 0, sizeof( pc ) ); + memset( &lockSurfacesCmd, 0, sizeof( lockSurfacesCmd ) ); + memset( &identitySpace, 0, sizeof( identitySpace ) ); + qglLogFile = NULL; + stencilIncr = 0; + stencilDecr = 0; + memset( renderCrops, 0, sizeof( renderCrops ) ); + currentRenderCrop = 0; + guiRecursionLevel = 0; + guiModel = NULL; + demoGuiModel = NULL; + guiOverlayModel = NULL; + specialEffectsEnabled = 0; + memset( gammaTable, 0, sizeof( gammaTable ) ); + takingScreenshot = false; +} + +/* +=============== +idRenderSystemLocal::Init +=============== +*/ +void idRenderSystemLocal::Init( void ) { + + common->Printf( "------- Initializing renderSystem --------\n" ); + + // clear all our internal state + viewCount = 1; // so cleared structures never match viewCount + // we used to memset tr, but now that it is a class, we can't, so + // there may be other state we need to reset + + ambientLightVector[0] = 0.5f; + ambientLightVector[1] = 0.5f - 0.385f; + ambientLightVector[2] = 0.8925f; + ambientLightVector[3] = 1.0f; + + memset( &backEnd, 0, sizeof( backEnd ) ); + + R_InitCvars(); + + R_InitCommands(); + + guiModel = new idGuiModel; + guiModel->Clear(); + + demoGuiModel = new idGuiModel; + demoGuiModel->Clear(); + + R_InitTriSurfData(); + + globalImages->Init(); + + idCinematic::InitCinematic( ); + + // build brightness translation tables + R_SetColorMappings(); + + R_InitMaterials(); + + renderModelManager->Init(); + + // set the identity space + identitySpace.modelMatrix[0*4+0] = 1.0f; + identitySpace.modelMatrix[1*4+1] = 1.0f; + identitySpace.modelMatrix[2*4+2] = 1.0f; + + // determine which back end we will use + // ??? this is invalid here as there is not enough information to set it up correctly + SetBackEndRenderer(); + + common->Printf( "renderSystem initialized.\n" ); + common->Printf( "--------------------------------------\n" ); +} + +/* +=============== +idRenderSystemLocal::Shutdown +=============== +*/ +void idRenderSystemLocal::Shutdown( void ) { + common->Printf( "idRenderSystem::Shutdown()\n" ); + + R_DoneFreeType( ); + + if ( glConfig.isInitialized ) { + globalImages->PurgeAllImages(); + } + + renderModelManager->Shutdown(); + + idCinematic::ShutdownCinematic( ); + + globalImages->Shutdown(); + + // close the r_logFile + if ( qglLogFile ) { + fprintf( qglLogFile, "*** CLOSING LOG ***\n" ); + fclose( qglLogFile ); + qglLogFile = 0; + } + + // free frame memory + R_ShutdownFrameData(); + + // free the vertex cache, which should have nothing allocated now + vertexCache.Shutdown(); + + R_ShutdownTriSurfData(); + + RB_ShutdownDebugTools(); + + delete guiModel; + delete demoGuiModel; + + Clear(); + + ShutdownOpenGL(); +} + +/* +======================== +idRenderSystemLocal::BeginLevelLoad +======================== +*/ +void idRenderSystemLocal::BeginLevelLoad( void ) { + renderModelManager->BeginLevelLoad(); + globalImages->BeginLevelLoad(); +} + +/* +======================== +idRenderSystemLocal::EndLevelLoad +======================== +*/ +void idRenderSystemLocal::EndLevelLoad( void ) { + renderModelManager->EndLevelLoad(); + globalImages->EndLevelLoad(); + if ( r_forceLoadImages.GetBool() ) { + RB_ShowImages(); + } +} + +/* +======================== +idRenderSystemLocal::InitOpenGL +======================== +*/ +void idRenderSystemLocal::InitOpenGL( void ) { + // if OpenGL isn't started, start it now + if ( !glConfig.isInitialized ) { + int err; + + R_InitOpenGL(); + + globalImages->ReloadAllImages(); + + err = qglGetError(); + if ( err != GL_NO_ERROR ) { + common->Printf( "glGetError() = 0x%x\n", err ); + } + } +} + +/* +======================== +idRenderSystemLocal::ShutdownOpenGL +======================== +*/ +void idRenderSystemLocal::ShutdownOpenGL( void ) { + // free the context and close the window + R_ShutdownFrameData(); + GLimp_Shutdown(); + glConfig.isInitialized = false; +} + +/* +======================== +idRenderSystemLocal::IsOpenGLRunning +======================== +*/ +bool idRenderSystemLocal::IsOpenGLRunning( void ) const { + if ( !glConfig.isInitialized ) { + return false; + } + return true; +} + +/* +======================== +idRenderSystemLocal::IsFullScreen +======================== +*/ +bool idRenderSystemLocal::IsFullScreen( void ) const { + return glConfig.isFullscreen; +} + +/* +======================== +idRenderSystemLocal::GetScreenWidth +======================== +*/ +int idRenderSystemLocal::GetScreenWidth( void ) const { + return glConfig.vidWidth; +} + +/* +======================== +idRenderSystemLocal::GetScreenHeight +======================== +*/ +int idRenderSystemLocal::GetScreenHeight( void ) const { + return glConfig.vidHeight; +} + +/* +======================== +idRenderSystemLocal::GetCardCaps +======================== +*/ +void idRenderSystemLocal::GetCardCaps( bool &oldCard, bool &nv10or20 ) { + nv10or20 = ( tr.backEndRenderer == BE_NV10 || tr.backEndRenderer == BE_NV20 ); + oldCard = ( tr.backEndRenderer == BE_ARB || tr.backEndRenderer == BE_R200 || tr.backEndRenderer == BE_NV10 || tr.backEndRenderer == BE_NV20 ); +} diff --git a/src/renderer/renderworld.cpp b/src/renderer/renderworld.cpp new file mode 100644 index 0000000..783302e --- /dev/null +++ b/src/renderer/renderworld.cpp @@ -0,0 +1,2495 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* +=================== +R_ListRenderLightDefs_f +=================== +*/ +void R_ListRenderLightDefs_f( const idCmdArgs &args ) { + int i; + idRenderLightLocal *ldef; + + if ( !tr.primaryWorld ) { + return; + } + int active = 0; + int totalRef = 0; + int totalIntr = 0; + + for ( i = 0 ; i < tr.primaryWorld->lightDefs.Num() ; i++ ) { + ldef = tr.primaryWorld->lightDefs[i]; + if ( !ldef ) { + common->Printf( "%4i: FREED\n", i ); + continue; + } + + // count up the interactions + int iCount = 0; + for ( idInteraction *inter = ldef->firstInteraction; inter != NULL; inter = inter->lightNext ) { + iCount++; + } + totalIntr += iCount; + + // count up the references + int rCount = 0; + for ( areaReference_t *ref = ldef->references ; ref ; ref = ref->ownerNext ) { + rCount++; + } + totalRef += rCount; + + common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, ldef->lightShader->GetName()); + active++; + } + + common->Printf( "%i lightDefs, %i interactions, %i areaRefs\n", active, totalIntr, totalRef ); +} + +/* +=================== +R_ListRenderEntityDefs_f +=================== +*/ +void R_ListRenderEntityDefs_f( const idCmdArgs &args ) { + int i; + idRenderEntityLocal *mdef; + + if ( !tr.primaryWorld ) { + return; + } + int active = 0; + int totalRef = 0; + int totalIntr = 0; + + for ( i = 0 ; i < tr.primaryWorld->entityDefs.Num() ; i++ ) { + mdef = tr.primaryWorld->entityDefs[i]; + if ( !mdef ) { + common->Printf( "%4i: FREED\n", i ); + continue; + } + + // count up the interactions + int iCount = 0; + for ( idInteraction *inter = mdef->firstInteraction; inter != NULL; inter = inter->entityNext ) { + iCount++; + } + totalIntr += iCount; + + // count up the references + int rCount = 0; + for ( areaReference_t *ref = mdef->entityRefs ; ref ; ref = ref->ownerNext ) { + rCount++; + } + totalRef += rCount; + + common->Printf( "%4i: %3i intr %2i refs %s\n", i, iCount, rCount, mdef->parms.hModel->Name()); + active++; + } + + common->Printf( "total active: %i\n", active ); +} + +/* +=================== +idRenderWorldLocal::idRenderWorldLocal +=================== +*/ +idRenderWorldLocal::idRenderWorldLocal() { + mapName.Clear(); + mapTimeStamp = FILE_NOT_FOUND_TIMESTAMP; + + generateAllInteractionsCalled = false; + + areaNodes = NULL; + numAreaNodes = 0; + + portalAreas = NULL; + numPortalAreas = 0; + + doublePortals = NULL; + numInterAreaPortals = 0; + + interactionTable = 0; + interactionTableWidth = 0; + interactionTableHeight = 0; +} + +/* +=================== +idRenderWorldLocal::~idRenderWorldLocal +=================== +*/ +idRenderWorldLocal::~idRenderWorldLocal() { + // free all the entityDefs, lightDefs, portals, etc + FreeWorld(); + + // free up the debug lines, polys, and text + RB_ClearDebugPolygons( 0 ); + RB_ClearDebugLines( 0 ); + RB_ClearDebugText( 0 ); +} + +/* +=================== +ResizeInteractionTable +=================== +*/ +void idRenderWorldLocal::ResizeInteractionTable() { + // we overflowed the interaction table, so dump it + // we may want to resize this in the future if it turns out to be common + common->Printf( "idRenderWorldLocal::ResizeInteractionTable: overflowed interactionTableWidth, dumping\n" ); + R_StaticFree( interactionTable ); + interactionTable = NULL; +} + +/* +=================== +AddEntityDef +=================== +*/ +qhandle_t idRenderWorldLocal::AddEntityDef( const renderEntity_t *re ){ + // try and reuse a free spot + int entityHandle = entityDefs.FindNull(); + if ( entityHandle == -1 ) { + entityHandle = entityDefs.Append( NULL ); + if ( interactionTable && entityDefs.Num() > interactionTableWidth ) { + ResizeInteractionTable(); + } + } + + UpdateEntityDef( entityHandle, re ); + + return entityHandle; +} + +/* +============== +UpdateEntityDef + +Does not write to the demo file, which will only be updated for +visible entities +============== +*/ +int c_callbackUpdate; + +void idRenderWorldLocal::UpdateEntityDef( qhandle_t entityHandle, const renderEntity_t *re ) { + if ( r_skipUpdates.GetBool() ) { + return; + } + + tr.pc.c_entityUpdates++; + + if ( !re->hModel && !re->callback ) { + common->Error( "idRenderWorld::UpdateEntityDef: NULL hModel" ); + } + + // create new slots if needed + if ( entityHandle < 0 || entityHandle > LUDICROUS_INDEX ) { + common->Error( "idRenderWorld::UpdateEntityDef: index = %i", entityHandle ); + } + while ( entityHandle >= entityDefs.Num() ) { + entityDefs.Append( NULL ); + } + + idRenderEntityLocal *def = entityDefs[entityHandle]; + if ( def ) { + + if ( !re->forceUpdate ) { + + // check for exact match (OPTIMIZE: check through pointers more) + if ( !re->joints && !re->callbackData && !def->dynamicModel && !memcmp( re, &def->parms, sizeof( *re ) ) ) { + return; + } + + // if the only thing that changed was shaderparms, we can just leave things as they are + // after updating parms + + // if we have a callback function and the bounds, origin, axis and model match, + // then we can leave the references as they are + if ( re->callback ) { + + bool axisMatch = ( re->axis == def->parms.axis ); + bool originMatch = ( re->origin == def->parms.origin ); + bool boundsMatch = ( re->bounds == def->referenceBounds ); + bool modelMatch = ( re->hModel == def->parms.hModel ); + + if ( boundsMatch && originMatch && axisMatch && modelMatch ) { + // only clear the dynamic model and interaction surfaces if they exist + c_callbackUpdate++; + R_ClearEntityDefDynamicModel( def ); + def->parms = *re; + return; + } + } + } + + // save any decals if the model is the same, allowing marks to move with entities + if ( def->parms.hModel == re->hModel ) { + R_FreeEntityDefDerivedData( def, true, true ); + } else { + R_FreeEntityDefDerivedData( def, false, false ); + } + } else { + // creating a new one + def = new idRenderEntityLocal; + entityDefs[entityHandle] = def; + + def->world = this; + def->index = entityHandle; + } + + def->parms = *re; + + R_AxisToModelMatrix( def->parms.axis, def->parms.origin, def->modelMatrix ); + + def->lastModifiedFrameNum = tr.frameCount; + if ( session->writeDemo && def->archived ) { + WriteFreeEntity( entityHandle ); + def->archived = false; + } + + // optionally immediately issue any callbacks + if ( !r_useEntityCallbacks.GetBool() && def->parms.callback ) { + R_IssueEntityDefCallback( def ); + } + + // based on the model bounds, add references in each area + // that may contain the updated surface + R_CreateEntityRefs( def ); +} + +/* +=================== +FreeEntityDef + +Frees all references and lit surfaces from the model, and +NULL's out it's entry in the world list +=================== +*/ +void idRenderWorldLocal::FreeEntityDef( qhandle_t entityHandle ) { + idRenderEntityLocal *def; + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Printf( "idRenderWorld::FreeEntityDef: handle %i > %i\n", entityHandle, entityDefs.Num() ); + return; + } + + def = entityDefs[entityHandle]; + if ( !def ) { + common->Printf( "idRenderWorld::FreeEntityDef: handle %i is NULL\n", entityHandle ); + return; + } + + R_FreeEntityDefDerivedData( def, false, false ); + + if ( session->writeDemo && def->archived ) { + WriteFreeEntity( entityHandle ); + } + + // if we are playing a demo, these will have been freed + // in R_FreeEntityDefDerivedData(), otherwise the gui + // object still exists in the game + + def->parms.gui[ 0 ] = NULL; + def->parms.gui[ 1 ] = NULL; + def->parms.gui[ 2 ] = NULL; + + delete def; + entityDefs[ entityHandle ] = NULL; +} + +/* +================== +GetRenderEntity +================== +*/ +const renderEntity_t *idRenderWorldLocal::GetRenderEntity( qhandle_t entityHandle ) const { + idRenderEntityLocal *def; + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Printf( "idRenderWorld::GetRenderEntity: invalid handle %i [0, %i]\n", entityHandle, entityDefs.Num() ); + return NULL; + } + + def = entityDefs[entityHandle]; + if ( !def ) { + common->Printf( "idRenderWorld::GetRenderEntity: handle %i is NULL\n", entityHandle ); + return NULL; + } + + return &def->parms; +} + +/* +================== +AddLightDef +================== +*/ +qhandle_t idRenderWorldLocal::AddLightDef( const renderLight_t *rlight ) { + // try and reuse a free spot + int lightHandle = lightDefs.FindNull(); + + if ( lightHandle == -1 ) { + lightHandle = lightDefs.Append( NULL ); + if ( interactionTable && lightDefs.Num() > interactionTableHeight ) { + ResizeInteractionTable(); + } + } + UpdateLightDef( lightHandle, rlight ); + + return lightHandle; +} + +/* +================= +UpdateLightDef + +The generation of all the derived interaction data will +usually be deferred until it is visible in a scene + +Does not write to the demo file, which will only be done for visible lights +================= +*/ +void idRenderWorldLocal::UpdateLightDef( qhandle_t lightHandle, const renderLight_t *rlight ) { + if ( r_skipUpdates.GetBool() ) { + return; + } + + tr.pc.c_lightUpdates++; + + // create new slots if needed + if ( lightHandle < 0 || lightHandle > LUDICROUS_INDEX ) { + common->Error( "idRenderWorld::UpdateLightDef: index = %i", lightHandle ); + } + while ( lightHandle >= lightDefs.Num() ) { + lightDefs.Append( NULL ); + } + + bool justUpdate = false; + idRenderLightLocal *light = lightDefs[lightHandle]; + if ( light ) { + // if the shape of the light stays the same, we don't need to dump + // any of our derived data, because shader parms are calculated every frame + if ( rlight->axis == light->parms.axis && rlight->end == light->parms.end && + rlight->lightCenter == light->parms.lightCenter && rlight->lightRadius == light->parms.lightRadius && + rlight->noShadows == light->parms.noShadows && rlight->origin == light->parms.origin && + rlight->parallel == light->parms.parallel && rlight->pointLight == light->parms.pointLight && + rlight->right == light->parms.right && rlight->start == light->parms.start && + rlight->target == light->parms.target && rlight->up == light->parms.up && + rlight->shader == light->lightShader && rlight->prelightModel == light->parms.prelightModel ) { + justUpdate = true; + } else { + // if we are updating shadows, the prelight model is no longer valid + light->lightHasMoved = true; + R_FreeLightDefDerivedData( light ); + } + } else { + // create a new one + light = new idRenderLightLocal; + lightDefs[lightHandle] = light; + + light->world = this; + light->index = lightHandle; + } + + light->parms = *rlight; + light->lastModifiedFrameNum = tr.frameCount; + if ( session->writeDemo && light->archived ) { + WriteFreeLight( lightHandle ); + light->archived = false; + } + + if ( light->lightHasMoved ) { + light->parms.prelightModel = NULL; + } + + if (!justUpdate) { + R_DeriveLightData( light ); + R_CreateLightRefs( light ); + R_CreateLightDefFogPortals( light ); + } +} + +/* +==================== +FreeLightDef + +Frees all references and lit surfaces from the light, and +NULL's out it's entry in the world list +==================== +*/ +void idRenderWorldLocal::FreeLightDef( qhandle_t lightHandle ) { + idRenderLightLocal *light; + + if ( lightHandle < 0 || lightHandle >= lightDefs.Num() ) { + common->Printf( "idRenderWorld::FreeLightDef: invalid handle %i [0, %i]\n", lightHandle, lightDefs.Num() ); + return; + } + + light = lightDefs[lightHandle]; + if ( !light ) { + common->Printf( "idRenderWorld::FreeLightDef: handle %i is NULL\n", lightHandle ); + return; + } + + R_FreeLightDefDerivedData( light ); + + if ( session->writeDemo && light->archived ) { + WriteFreeLight( lightHandle ); + } + + delete light; + lightDefs[lightHandle] = NULL; +} + +/* +================== +GetRenderLight +================== +*/ +const renderLight_t *idRenderWorldLocal::GetRenderLight( qhandle_t lightHandle ) const { + idRenderLightLocal *def; + + if ( lightHandle < 0 || lightHandle >= lightDefs.Num() ) { + common->Printf( "idRenderWorld::GetRenderLight: handle %i > %i\n", lightHandle, lightDefs.Num() ); + return NULL; + } + + def = lightDefs[lightHandle]; + if ( !def ) { + common->Printf( "idRenderWorld::GetRenderLight: handle %i is NULL\n", lightHandle ); + return NULL; + } + + return &def->parms; +} + +/* +================== +EffectDefHasSound +================== +*/ +bool idRenderWorldLocal::EffectDefHasSound( const renderEffect_t *reffect ) { + return reffect != NULL && bse != NULL && bse->CheckDefForSound( reffect ); +} + +/* +================== +AddEffectDef +================== +*/ +qhandle_t idRenderWorldLocal::AddEffectDef( const renderEffect_t *reffect, int time ) { + if ( !bse_enabled.GetBool() || reffect == NULL ) { + return -1; + } + + int handle = effectDefs.FindNull(); + if ( handle == -1 ) { + handle = effectDefs.Append( NULL ); + } + + // The retail interface returns true from UpdateEffectDef when the + // effect has already completed or could not be created. + if ( UpdateEffectDef( handle, reffect, time ) ) { + FreeEffectDef( handle ); + return -1; + } + return handle; +} + +/* +================== +FreeEffectDefDerivedData +================== +*/ +void idRenderWorldLocal::FreeEffectDefDerivedData( rvRenderEffectLocal *def ) { + if ( def == NULL ) { + return; + } + for ( areaReference_t *ref = def->effectRefs; ref != NULL; ) { + areaReference_t *next = ref->ownerNext; + ref->areaNext->areaPrev = ref->areaPrev; + ref->areaPrev->areaNext = ref->areaNext; + areaReferenceAllocator.Free( ref ); + ref = next; + } + def->effectRefs = NULL; + def->viewEffect = NULL; + def->dynamicModel = NULL; +} + +/* +================== +AddEffectRefToArea +================== +*/ +void idRenderWorldLocal::AddEffectRefToArea( rvRenderEffectLocal *def, portalArea_t *area ) { + if ( def == NULL || area == NULL ) { + return; + } + areaReference_t *ref = areaReferenceAllocator.Alloc(); + ref->entity = NULL; + ref->light = NULL; + ref->effect = def; + ref->area = area; + ref->ownerNext = def->effectRefs; + def->effectRefs = ref; + + ref->areaNext = &area->effectRefs; + ref->areaPrev = area->effectRefs.areaPrev; + ref->areaNext->areaPrev = ref; + ref->areaPrev->areaNext = ref; +} + +/* +================== +PushEffectDef + +Place the effect in every portal area touched by its transformed current +bounds. Retail performs the same placement through the proc BSP. +================== +*/ +void idRenderWorldLocal::PushEffectDef( rvRenderEffectLocal *def ) { + if ( def == NULL || def->referenceBounds.IsCleared() || areaNodes == NULL ) { + return; + } + + idBounds worldBounds; + worldBounds.FromTransformedBounds( def->referenceBounds, def->parms.origin, def->parms.axis ); + int areas[128]; + const int numAreas = BoundsInAreas( worldBounds, areas, sizeof( areas ) / sizeof( areas[0] ) ); + for ( int i = 0; i < numAreas; ++i ) { + AddEffectRefToArea( def, &portalAreas[areas[i]] ); + } +} + +/* +================== +UpdateEffectDef +================== +*/ +bool idRenderWorldLocal::UpdateEffectDef( qhandle_t effectHandle, const renderEffect_t *reffect, int time ) { + if ( r_skipUpdates.GetBool() ) { + return false; + } + if ( !bse_enabled.GetBool() || bse == NULL || reffect == NULL ) { + return true; + } + if ( effectHandle < 0 || effectHandle > LUDICROUS_INDEX ) { + common->Error( "idRenderWorld::UpdateEffectDef: index = %i", effectHandle ); + } + while ( effectHandle >= effectDefs.Num() ) { + effectDefs.Append( NULL ); + } + + bool push = false; + rvRenderEffectLocal *def = effectDefs[effectHandle]; + if ( def != NULL ) { + // The retail executable compares the placement portion (0x70 bytes) + // before retaining the existing area references. + const int placementBytes = Min( static_cast( sizeof( renderEffect_t ) ), 0x70 ); + if ( memcmp( reffect, &def->parms, placementBytes ) != 0 ) { + FreeEffectDefDerivedData( def ); + push = true; + } + def->parms = *reffect; + } else { + def = new rvRenderEffectLocal; + effectDefs[effectHandle] = def; + def->index = effectHandle; + def->world = this; + def->parms = *reffect; + push = true; + if ( !bse->PlayEffect( def, time * 0.001f ) ) { + return true; + } + } + + def->lastModifiedFrameNum = tr.frameCount; + R_AxisToModelMatrix( def->parms.axis, def->parms.origin, def->modelMatrix ); + if ( bse->ServiceEffect( def, time * 0.001f ) ) { + return true; + } + if ( push ) { + PushEffectDef( def ); + } + return false; +} + +void idRenderWorldLocal::StopEffectDef( qhandle_t effectHandle ) { + if ( effectHandle < 0 || effectHandle >= effectDefs.Num() ) { + common->Printf( "idRenderWorld::StopEffectDef: invalid handle %i >= %i\n", effectHandle, effectDefs.Num() ); + return; + } + if ( effectDefs[effectHandle] != NULL && bse != NULL ) { + bse->StopEffect( effectDefs[effectHandle] ); + } +} + +const rvRenderEffectLocal *idRenderWorldLocal::GetEffectDef( qhandle_t effectHandle ) const { + if ( effectHandle < 0 || effectHandle >= effectDefs.Num() ) { + common->Printf( "idRenderWorld::GetEffectDef: invalid handle %i >= %i\n", effectHandle, effectDefs.Num() ); + return NULL; + } + return effectDefs[effectHandle]; +} + +void idRenderWorldLocal::FreeEffectDef( qhandle_t effectHandle ) { + if ( effectHandle < 0 || effectHandle >= effectDefs.Num() ) { + common->Printf( "idRenderWorld::FreeEffectDef: invalid handle %i >= %i\n", effectHandle, effectDefs.Num() ); + return; + } + rvRenderEffectLocal *def = effectDefs[effectHandle]; + if ( def == NULL ) { + return; + } + if ( bse != NULL ) { + bse->FreeEffect( def ); + } + FreeEffectDefDerivedData( def ); + delete def; + effectDefs[effectHandle] = NULL; +} + +void idRenderWorldLocal::PushMarkedDefs() { + for ( int i = 0; i < markedEffectDefs.Num(); ++i ) { + const int handle = markedEffectDefs[i]; + if ( handle >= 0 && handle < effectDefs.Num() && effectDefs[handle] != NULL ) { + PushEffectDef( effectDefs[handle] ); + } + } + ClearMarkedDefs(); +} + +void idRenderWorldLocal::ClearMarkedDefs() { + markedEntityDefs.Clear(); + markedLightDefs.Clear(); + markedEffectDefs.Clear(); +} + +void idRenderWorldLocal::RemoveAllModelReferences( idRenderModel *model ) { + for ( int i = localModels.Num() - 1; i >= 0; --i ) { + if ( localModels[i] == model ) { + localModels.RemoveIndex( i ); + } + } +} + +/* +================ +idRenderWorldLocal::ProjectDecalOntoWorld +================ +*/ +void idRenderWorldLocal::ProjectDecalOntoWorld( const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { + int i, areas[10], numAreas; + const areaReference_t *ref; + const portalArea_t *area; + const idRenderModel *model; + idRenderEntityLocal *def; + decalProjectionInfo_t info, localInfo; + + if ( !idRenderModelDecal::CreateProjectionInfo( info, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) ) { + return; + } + + // get the world areas touched by the projection volume + numAreas = BoundsInAreas( info.projectionBounds, areas, 10 ); + + // check all areas for models + for ( i = 0; i < numAreas; i++ ) { + + area = &portalAreas[ areas[i] ]; + + // check all models in this area + for ( ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) { + def = ref->entity; + + // completely ignore any dynamic or callback models + model = def->parms.hModel; + if ( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback ) { + continue; + } + + if ( def->parms.customShader != NULL && !def->parms.customShader->AllowOverlays() ) { + continue; + } + + idBounds bounds; + bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis ); + + // if the model bounds do not overlap with the projection bounds + if ( !info.projectionBounds.IntersectsBounds( bounds ) ) { + continue; + } + + // transform the bounding planes, fade planes and texture axis into local space + idRenderModelDecal::GlobalProjectionInfoToLocal( localInfo, info, def->parms.origin, def->parms.axis ); + localInfo.force = ( def->parms.customShader != NULL ); + + if ( !def->decals ) { + def->decals = idRenderModelDecal::Alloc(); + } + def->decals->CreateDecal( model, localInfo ); + } + } +} + +/* +==================== +idRenderWorldLocal::ProjectDecal +==================== +*/ +void idRenderWorldLocal::ProjectDecal( qhandle_t entityHandle, const idFixedWinding &winding, const idVec3 &projectionOrigin, const bool parallel, const float fadeDepth, const idMaterial *material, const int startTime ) { + decalProjectionInfo_t info, localInfo; + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle ); + return; + } + + idRenderEntityLocal *def = entityDefs[ entityHandle ]; + if ( !def ) { + return; + } + + const idRenderModel *model = def->parms.hModel; + + if ( model == NULL || model->IsDynamicModel() != DM_STATIC || def->parms.callback ) { + return; + } + + if ( !idRenderModelDecal::CreateProjectionInfo( info, winding, projectionOrigin, parallel, fadeDepth, material, startTime ) ) { + return; + } + + idBounds bounds; + bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis ); + + // if the model bounds do not overlap with the projection bounds + if ( !info.projectionBounds.IntersectsBounds( bounds ) ) { + return; + } + + // transform the bounding planes, fade planes and texture axis into local space + idRenderModelDecal::GlobalProjectionInfoToLocal( localInfo, info, def->parms.origin, def->parms.axis ); + localInfo.force = ( def->parms.customShader != NULL ); + + if ( def->decals == NULL ) { + def->decals = idRenderModelDecal::Alloc(); + } + def->decals->CreateDecal( model, localInfo ); +} + +/* +==================== +idRenderWorldLocal::ProjectOverlay +==================== +*/ +void idRenderWorldLocal::ProjectOverlay( qhandle_t entityHandle, const idPlane localTextureAxis[2], const idMaterial *material ) { + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle ); + return; + } + + idRenderEntityLocal *def = entityDefs[ entityHandle ]; + if ( !def ) { + return; + } + + const renderEntity_t *refEnt = &def->parms; + + idRenderModel *model = refEnt->hModel; + if ( model->IsDynamicModel() != DM_CACHED ) { // FIXME: probably should be MD5 only + return; + } + model = R_EntityDefDynamicModel( def ); + + if ( def->overlay == NULL ) { + def->overlay = idRenderModelOverlay::Alloc(); + } + def->overlay->CreateOverlay( model, localTextureAxis, material ); +} + +/* +==================== +idRenderWorldLocal::RemoveDecals +==================== +*/ +void idRenderWorldLocal::RemoveDecals( qhandle_t entityHandle ) { + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { + common->Error( "idRenderWorld::ProjectOverlay: index = %i", entityHandle ); + return; + } + + idRenderEntityLocal *def = entityDefs[ entityHandle ]; + if ( !def ) { + return; + } + + R_FreeEntityDefDecals( def ); + R_FreeEntityDefOverlay( def ); +} + +/* +==================== +SetRenderView + +Sets the current view so any calls to the render world will use the correct parms. +==================== +*/ +void idRenderWorldLocal::SetRenderView( const renderView_t *renderView ) { + tr.primaryRenderView = *renderView; +} + +/* +==================== +RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +==================== +*/ +void idRenderWorldLocal::RenderScene( const renderView_t *renderView, int renderFlags ) { +#ifndef ID_DEDICATED + renderView_t copy; + + if ( !glConfig.isInitialized ) { + return; + } + + copy = *renderView; + + // skip front end rendering work, which will result + // in only gui drawing + if ( r_skipFrontEnd.GetBool() ) { + return; + } + + if ( renderView->fov_x <= 0 || renderView->fov_y <= 0 ) { + common->Error( "idRenderWorld::RenderScene: bad FOVs: %f, %f", renderView->fov_x, renderView->fov_y ); + } + + // close any gui drawing + tr.guiModel->EmitFullScreen(); + tr.guiModel->Clear(); + + int startTime = Sys_Milliseconds(); + + // setup view parms for the initial view + // + viewDef_t *parms = (viewDef_t *)R_ClearedFrameAlloc( sizeof( *parms ) ); + parms->renderView = *renderView; + parms->renderFlags = renderFlags; + + if ( tr.takingScreenshot ) { + parms->renderView.forceUpdate = true; + } + + // set up viewport, adjusted for resolution and OpenGL style 0 at the bottom + tr.RenderViewToViewport( &parms->renderView, &parms->viewport ); + + // the scissor bounds may be shrunk in subviews even if + // the viewport stays the same + // this scissor range is local inside the viewport + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + + parms->isSubview = false; + parms->initialViewAreaOrigin = renderView->vieworg; + parms->floatTime = parms->renderView.time * 0.001f; + parms->renderWorld = this; + + // use this time for any subsequent 2D rendering, so damage blobs/etc + // can use level time + tr.frameShaderTime = parms->floatTime; + + // see if the view needs to reverse the culling sense in mirrors + // or environment cube sides + idVec3 cross; + cross = parms->renderView.viewaxis[1].Cross( parms->renderView.viewaxis[2] ); + if ( cross * parms->renderView.viewaxis[0] > 0 ) { + parms->isMirror = false; + } else { + parms->isMirror = true; + } + + if ( r_lockSurfaces.GetBool() ) { + R_LockSurfaceScene( parms ); + return; + } + + // save this world for use by some console commands + tr.primaryWorld = this; + tr.primaryRenderView = *renderView; + tr.primaryView = parms; + + // rendering this view may cause other views to be rendered + // for mirrors / portals / shadows / environment maps + // this will also cause any necessary entities and lights to be + // updated to the demo file + R_RenderView( parms ); + + // now write delete commands for any modified-but-not-visible entities, and + // add the renderView command to the demo + if ( session->writeDemo ) { + WriteRenderView( renderView ); + } + +#if 0 + for ( int i = 0 ; i < entityDefs.Num() ; i++ ) { + idRenderEntityLocal *def = entityDefs[i]; + if ( !def ) { + continue; + } + if ( def->parms.callback ) { + continue; + } + if ( def->parms.hModel->IsDynamicModel() == DM_CONTINUOUS ) { + } + } +#endif + + int endTime = Sys_Milliseconds(); + + tr.pc.frontEndMsec += endTime - startTime; + + // prepare for any 2D drawing after this + tr.guiModel->Clear(); +#endif +} + +void idRenderWorldLocal::RenderScene( const renderView_t *renderView ) { + RenderScene( renderView, 0 ); +} + +bool idRenderWorldLocal::HasSkybox( int areaNum ) { + return areaNum >= 0 && areaNum < numPortalAreas && portalAreas[areaNum].hasSkybox; +} + +static void R_WorldToScreenMatrices( const renderView_t *renderView, float modelView[16], float projection[16] ) { + viewDef_t viewDef; + memset( &viewDef, 0, sizeof( viewDef ) ); + viewDef.renderView = *renderView; + R_SetViewMatrix( &viewDef ); + memcpy( modelView, viewDef.worldSpace.modelViewMatrix, sizeof( viewDef.worldSpace.modelViewMatrix ) ); + + memset( projection, 0, sizeof( float ) * 16 ); + const float zNear = r_znear.GetFloat() * ( renderView->cramZNear ? 0.25f : 1.0f ); + const float xmax = zNear * idMath::Tan16( DEG2RAD( renderView->fov_x * 0.5f ) ); + const float ymax = zNear * idMath::Tan16( DEG2RAD( renderView->fov_y * 0.5f ) ); + projection[0] = zNear / xmax; + projection[5] = zNear / ymax; + projection[10] = -0.999f; + projection[14] = -2.0f * zNear; + projection[11] = -1.0f; +} + +idVec3 idRenderWorldLocal::WorldToScreen( renderView_t *renderView, idVec3 worldPoint ) { + float modelView[16], projection[16]; + idVec3 ndc; + R_WorldToScreenMatrices( renderView, modelView, projection ); + R_GlobalToNormalizedDeviceCoordinates( modelView, projection, worldPoint, ndc ); + return idVec3( SCREEN_WIDTH * ( ndc.x + 1.0f ) * 0.5f, + SCREEN_HEIGHT - SCREEN_HEIGHT * ( ndc.y + 1.0f ) * 0.5f, ndc.z ); +} + +idBounds idRenderWorldLocal::WorldToScreen( renderView_t *renderView, idBounds worldBounds ) { + idVec3 points[8]; + worldBounds.ToPoints( points ); + idBounds screenBounds; + screenBounds.Clear(); + for ( int i = 0; i < 8; ++i ) { + screenBounds.AddPoint( WorldToScreen( renderView, points[i] ) ); + } + return screenBounds; +} + +/* +=================== +NumAreas +=================== +*/ +int idRenderWorldLocal::NumAreas( void ) const { + return numPortalAreas; +} + +/* +=================== +NumPortalsInArea +=================== +*/ +int idRenderWorldLocal::NumPortalsInArea( int areaNum ) { + portalArea_t *area; + int count; + portal_t *portal; + + if ( areaNum >= numPortalAreas || areaNum < 0 ) { + common->Error( "idRenderWorld::NumPortalsInArea: bad areanum %i", areaNum ); + } + area = &portalAreas[areaNum]; + + count = 0; + for ( portal = area->portals ; portal ; portal = portal->next ) { + count++; + } + return count; +} + +/* +=================== +GetPortal +=================== +*/ +exitPortal_t idRenderWorldLocal::GetPortal( int areaNum, int portalNum ) { + portalArea_t *area; + int count; + portal_t *portal; + exitPortal_t ret; + + if ( areaNum > numPortalAreas ) { + common->Error( "idRenderWorld::GetPortal: areaNum > numAreas" ); + } + area = &portalAreas[areaNum]; + + count = 0; + for ( portal = area->portals ; portal ; portal = portal->next ) { + if ( count == portalNum ) { + ret.areas[0] = areaNum; + ret.areas[1] = portal->intoArea; + ret.w = portal->w; + ret.blockingBits = portal->doublePortal->blockingBits; + ret.portalHandle = portal->doublePortal - doublePortals + 1; + return ret; + } + count++; + } + + common->Error( "idRenderWorld::GetPortal: portalNum > numPortals" ); + + memset( &ret, 0, sizeof( ret ) ); + return ret; +} + +void idRenderWorldLocal::GetPortals( int areaNum, exitPortal_t *ret, int maxPortals ) { + if ( ret == NULL || maxPortals <= 0 || areaNum < 0 || areaNum >= numPortalAreas ) { + return; + } + int count = 0; + for ( portal_t *portal = portalAreas[areaNum].portals; portal != NULL && count < maxPortals; portal = portal->next, ++count ) { + ret[count].areas[0] = areaNum; + ret[count].areas[1] = portal->intoArea; + ret[count].w = portal->w; + ret[count].blockingBits = portal->doublePortal->blockingBits; + ret[count].portalHandle = static_cast( portal->doublePortal - doublePortals ) + 1; + } +} + +void idRenderWorldLocal::GetPortal( int areaNum, int portalNum, exitPortal_t *ret ) { + if ( ret == NULL ) { + return; + } + *ret = GetPortal( areaNum, portalNum ); +} + +/* +=============== +PointInAreaNum + +Will return -1 if the point is not in an area, otherwise +it will return 0 <= value < tr.world->numPortalAreas +=============== +*/ +int idRenderWorldLocal::PointInArea( const idVec3 &point ) const { + areaNode_t *node; + int nodeNum; + float d; + + node = areaNodes; + if ( !node ) { + return -1; + } + while( 1 ) { + d = point * node->plane.Normal() + node->plane[3]; + if (d > 0) { + nodeNum = node->children[0]; + } else { + nodeNum = node->children[1]; + } + if ( nodeNum == 0 ) { + return -1; // in solid + } + if ( nodeNum < 0 ) { + nodeNum = -1 - nodeNum; + if ( nodeNum >= numPortalAreas ) { + common->Error( "idRenderWorld::PointInArea: area out of range" ); + } + return nodeNum; + } + node = areaNodes + nodeNum; + } + + return -1; +} + +/* +=================== +BoundsInAreas_r +=================== +*/ +void idRenderWorldLocal::BoundsInAreas_r( int nodeNum, const idBounds &bounds, int *areas, int *numAreas, int maxAreas ) const { + int side, i; + areaNode_t *node; + + do { + if ( nodeNum < 0 ) { + nodeNum = -1 - nodeNum; + + for ( i = 0; i < (*numAreas); i++ ) { + if ( areas[i] == nodeNum ) { + break; + } + } + if ( i >= (*numAreas) && (*numAreas) < maxAreas ) { + areas[(*numAreas)++] = nodeNum; + } + return; + } + + node = areaNodes + nodeNum; + + side = bounds.PlaneSide( node->plane ); + if ( side == PLANESIDE_FRONT ) { + nodeNum = node->children[0]; + } + else if ( side == PLANESIDE_BACK ) { + nodeNum = node->children[1]; + } + else { + if ( node->children[1] != 0 ) { + BoundsInAreas_r( node->children[1], bounds, areas, numAreas, maxAreas ); + if ( (*numAreas) >= maxAreas ) { + return; + } + } + nodeNum = node->children[0]; + } + } while( nodeNum != 0 ); + + return; +} + +/* +=================== +BoundsInAreas + + fills the *areas array with the number of the areas the bounds are in + returns the total number of areas the bounds are in +=================== +*/ +int idRenderWorldLocal::BoundsInAreas( const idBounds &bounds, int *areas, int maxAreas ) const { + int numAreas = 0; + + assert( areas ); + assert( bounds[0][0] <= bounds[1][0] && bounds[0][1] <= bounds[1][1] && bounds[0][2] <= bounds[1][2] ); + assert( bounds[1][0] - bounds[0][0] < 1e4f && bounds[1][1] - bounds[0][1] < 1e4f && bounds[1][2] - bounds[0][2] < 1e4f ); + + if ( !areaNodes ) { + return numAreas; + } + BoundsInAreas_r( 0, bounds, areas, &numAreas, maxAreas ); + return numAreas; +} + +/* +================ +GuiTrace + +checks a ray trace against any gui surfaces in an entity, returning the +fraction location of the trace on the gui surface, or -1,-1 if no hit. +this doesn't do any occlusion testing, simply ignoring non-gui surfaces. +start / end are in global world coordinates. +================ +*/ +guiPoint_t idRenderWorldLocal::GuiTrace( qhandle_t entityHandle, const idVec3 start, const idVec3 end ) const { + localTrace_t local; + idVec3 localStart, localEnd, bestPoint; + int j; + idRenderModel *model; + srfTriangles_t *tri; + const idMaterial *shader; + guiPoint_t pt; + + pt.x = pt.y = -1; + pt.guiId = 0; + + if ( ( entityHandle < 0 ) || ( entityHandle >= entityDefs.Num() ) ) { + common->Printf( "idRenderWorld::GuiTrace: invalid handle %i\n", entityHandle ); + return pt; + } + + idRenderEntityLocal *def = entityDefs[entityHandle]; + if ( !def ) { + common->Printf( "idRenderWorld::GuiTrace: handle %i is NULL\n", entityHandle ); + return pt; + } + + model = def->parms.hModel; + if ( def->parms.callback || !def->parms.hModel || def->parms.hModel->IsDynamicModel() != DM_STATIC ) { + return pt; + } + + // transform the points into local space + R_GlobalPointToLocal( def->modelMatrix, start, localStart ); + R_GlobalPointToLocal( def->modelMatrix, end, localEnd ); + + + float best = 99999.0; + const modelSurface_t *bestSurf = NULL; + + for ( j = 0 ; j < model->NumSurfaces() ; j++ ) { + const modelSurface_t *surf = model->Surface( j ); + + tri = surf->geometry; + if ( !tri ) { + continue; + } + + shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + if ( !shader ) { + continue; + } + // only trace against gui surfaces + if (!shader->HasGui()) { + continue; + } + + local = R_LocalTrace( localStart, localEnd, 0.0f, tri, shader ); + if ( local.fraction < 1.0 ) { + idVec3 origin, axis[3]; + idVec3 cursor; + float axisLen[2]; + + R_SurfaceToTextureAxis( tri, origin, axis ); + cursor = local.point - origin; + + axisLen[0] = axis[0].Length(); + axisLen[1] = axis[1].Length(); + + pt.x = ( cursor * axis[0] ) / ( axisLen[0] * axisLen[0] ); + pt.y = ( cursor * axis[1] ) / ( axisLen[1] * axisLen[1] ); + pt.guiId = shader->GetEntityGui(); + + return pt; + } + } + + return pt; +} + +/* +=================== +idRenderWorldLocal::ModelTrace +=================== +*/ +bool idRenderWorldLocal::ModelTrace( modelTrace_t &trace, qhandle_t entityHandle, const idVec3 &start, const idVec3 &end, const float radius ) const { + int i; + bool collisionSurface; + const modelSurface_t *surf; + localTrace_t localTrace; + idRenderModel *model; + float modelMatrix[16]; + idVec3 localStart, localEnd; + const idMaterial *shader; + + trace.fraction = 1.0f; + + if ( entityHandle < 0 || entityHandle >= entityDefs.Num() ) { +// common->Error( "idRenderWorld::ModelTrace: index = %i", entityHandle ); + return false; + } + + idRenderEntityLocal *def = entityDefs[entityHandle]; + if ( !def ) { + return false; + } + + renderEntity_t *refEnt = &def->parms; + + model = R_EntityDefDynamicModel( def ); + if ( !model ) { + return false; + } + + // transform the points into local space + R_AxisToModelMatrix( refEnt->axis, refEnt->origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, start, localStart ); + R_GlobalPointToLocal( modelMatrix, end, localEnd ); + + // if we have explicit collision surfaces, only collide against them + // (FIXME, should probably have a parm to control this) + collisionSurface = false; + for ( i = 0; i < model->NumBaseSurfaces(); i++ ) { + surf = model->Surface( i ); + + shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + + if ( shader->GetSurfaceFlags() & SURF_COLLISION ) { + collisionSurface = true; + break; + } + } + + // only use baseSurfaces, not any overlays + for ( i = 0; i < model->NumBaseSurfaces(); i++ ) { + surf = model->Surface( i ); + + shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + + if ( !surf->geometry || !shader ) { + continue; + } + + if ( collisionSurface ) { + // only trace vs collision surfaces + if ( !( shader->GetSurfaceFlags() & SURF_COLLISION ) ) { + continue; + } + } else { + // skip if not drawn or translucent + if ( !shader->IsDrawn() || ( shader->Coverage() != MC_OPAQUE && shader->Coverage() != MC_PERFORATED ) ) { + continue; + } + } + + localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry, + surf->shader ); + + if ( localTrace.fraction < trace.fraction ) { + trace.fraction = localTrace.fraction; + trace.materialType = localTrace.materialType; + R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point ); + trace.normal = localTrace.normal * refEnt->axis; + trace.material = shader; + trace.entity = &def->parms; + trace.jointNumber = refEnt->hModel->NearestJoint( i, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] ); + } + } + + return ( trace.fraction < 1.0f ); +} + +/* +=================== +idRenderWorldLocal::Trace +=================== +*/ +// FIXME: _D3XP added those. +const char* playerModelExcludeList[] = { + "models/md5/characters/player/d3xp_spplayer.md5mesh", + "models/md5/characters/player/head/d3xp_head.md5mesh", + "models/md5/weapons/pistol_world/worldpistol.md5mesh", + NULL +}; + +const char* playerMaterialExcludeList[] = { + "muzzlesmokepuff", + NULL +}; + +bool idRenderWorldLocal::Trace( modelTrace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, bool skipDynamic, bool skipPlayer /*_D3XP*/ ) const { + areaReference_t * ref; + idRenderEntityLocal *def; + portalArea_t * area; + idRenderModel * model; + srfTriangles_t * tri; + localTrace_t localTrace; + int areas[128], numAreas, i, j, numSurfaces; + idBounds traceBounds, bounds; + float modelMatrix[16]; + idVec3 localStart, localEnd; + const idMaterial *shader; + + trace.fraction = 1.0f; + trace.point = end; + + // bounds for the whole trace + traceBounds.Clear(); + traceBounds.AddPoint( start ); + traceBounds.AddPoint( end ); + + // get the world areas the trace is in + numAreas = BoundsInAreas( traceBounds, areas, 128 ); + + numSurfaces = 0; + + // check all areas for models + for ( i = 0; i < numAreas; i++ ) { + + area = &portalAreas[ areas[i] ]; + + // check all models in this area + for ( ref = area->entityRefs.areaNext; ref != &area->entityRefs; ref = ref->areaNext ) { + def = ref->entity; + + model = def->parms.hModel; + if ( !model ) { + continue; + } + + if ( model->IsDynamicModel() != DM_STATIC ) { + if ( skipDynamic ) { + continue; + } + +#if 1 /* _D3XP addition. could use a cleaner approach */ + if ( skipPlayer ) { + idStr name = model->Name(); + const char *exclude; + int k; + + for ( k = 0; playerModelExcludeList[k]; k++ ) { + exclude = playerModelExcludeList[k]; + if ( name == exclude ) { + break; + } + } + + if ( playerModelExcludeList[k] ) { + continue; + } + } +#endif + + model = R_EntityDefDynamicModel( def ); + if ( !model ) { + continue; // can happen with particle systems, which don't instantiate without a valid view + } + } + + bounds.FromTransformedBounds( model->Bounds( &def->parms ), def->parms.origin, def->parms.axis ); + + // if the model bounds do not overlap with the trace bounds + if ( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) ) { + continue; + } + + // check all model surfaces + for ( j = 0; j < model->NumSurfaces(); j++ ) { + const modelSurface_t *surf = model->Surface( j ); + + shader = R_RemapShaderBySkin( surf->shader, def->parms.customSkin, def->parms.customShader ); + + // if no geometry or no shader + if ( !surf->geometry || !shader ) { + continue; + } + +#if 1 /* _D3XP addition. could use a cleaner approach */ + if ( skipPlayer ) { + idStr name = shader->GetName(); + const char *exclude; + int k; + + for ( k = 0; playerMaterialExcludeList[k]; k++ ) { + exclude = playerMaterialExcludeList[k]; + if ( name == exclude ) { + break; + } + } + + if ( playerMaterialExcludeList[k] ) { + continue; + } + } +#endif + + tri = surf->geometry; + + bounds.FromTransformedBounds( tri->bounds, def->parms.origin, def->parms.axis ); + + // if triangle bounds do not overlap with the trace bounds + if ( !traceBounds.IntersectsBounds( bounds ) || !bounds.LineIntersection( start, trace.point ) ) { + continue; + } + + numSurfaces++; + + // transform the points into local space + R_AxisToModelMatrix( def->parms.axis, def->parms.origin, modelMatrix ); + R_GlobalPointToLocal( modelMatrix, start, localStart ); + R_GlobalPointToLocal( modelMatrix, end, localEnd ); + + localTrace = R_LocalTrace( localStart, localEnd, radius, surf->geometry, + surf->shader ); + + if ( localTrace.fraction < trace.fraction ) { + trace.fraction = localTrace.fraction; + trace.materialType = localTrace.materialType; + R_LocalPointToGlobal( modelMatrix, localTrace.point, trace.point ); + trace.normal = localTrace.normal * def->parms.axis; + trace.material = shader; + trace.entity = &def->parms; + trace.jointNumber = model->NearestJoint( j, localTrace.indexes[0], localTrace.indexes[1], localTrace.indexes[2] ); + + traceBounds.Clear(); + traceBounds.AddPoint( start ); + traceBounds.AddPoint( start + trace.fraction * (end - start) ); + } + } + } + } + return ( trace.fraction < 1.0f ); +} + +/* +================== +idRenderWorldLocal::RecurseProcBSP +================== +*/ +void idRenderWorldLocal::RecurseProcBSP_r( modelTrace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 ) const { + float t1, t2; + float frac; + idVec3 mid; + int side; + float midf; + areaNode_t *node; + + if ( results->fraction <= p1f) { + return; // already hit something nearer + } + // empty leaf + if ( nodeNum < 0 ) { + return; + } + // if solid leaf node + if ( nodeNum == 0 ) { + if ( parentNodeNum != -1 ) { + + results->fraction = p1f; + results->point = p1; + node = &areaNodes[parentNodeNum]; + results->normal = node->plane.Normal(); + return; + } + } + node = &areaNodes[nodeNum]; + + // distance from plane for trace start and end + t1 = node->plane.Normal() * p1 + node->plane[3]; + t2 = node->plane.Normal() * p2 + node->plane[3]; + + if ( t1 >= 0.0f && t2 >= 0.0f ) { + RecurseProcBSP_r( results, nodeNum, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < 0.0f && t2 < 0.0f ) { + RecurseProcBSP_r( results, nodeNum, node->children[1], p1f, p2f, p1, p2 ); + return; + } + side = t1 < t2; + frac = t1 / (t1 - t2); + midf = p1f + frac*(p2f - p1f); + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + RecurseProcBSP_r( results, nodeNum, node->children[side], p1f, midf, p1, mid ); + RecurseProcBSP_r( results, nodeNum, node->children[side^1], midf, p2f, mid, p2 ); +} + +/* +================== +idRenderWorldLocal::FastWorldTrace +================== +*/ +bool idRenderWorldLocal::FastWorldTrace( modelTrace_t &results, const idVec3 &start, const idVec3 &end ) const { + memset( &results, 0, sizeof( modelTrace_t ) ); + results.fraction = 1.0f; + if ( areaNodes != NULL ) { + RecurseProcBSP_r( &results, -1, 0, 0.0f, 1.0f, start, end ); + return ( results.fraction < 1.0f ); + } + return false; +} + +/* +================================================================================= + +CREATE MODEL REFS + +================================================================================= +*/ + +/* +================= +AddEntityRefToArea + +This is called by R_PushVolumeIntoTree and also directly +for the world model references that are precalculated. +================= +*/ +void idRenderWorldLocal::AddEntityRefToArea( idRenderEntityLocal *def, portalArea_t *area ) { + areaReference_t *ref; + + if ( !def ) { + common->Error( "idRenderWorldLocal::AddEntityRefToArea: NULL def" ); + } + + ref = areaReferenceAllocator.Alloc(); + + tr.pc.c_entityReferences++; + + ref->entity = def; + + // link to entityDef + ref->ownerNext = def->entityRefs; + def->entityRefs = ref; + + // link to end of area list + ref->area = area; + ref->areaNext = &area->entityRefs; + ref->areaPrev = area->entityRefs.areaPrev; + ref->areaNext->areaPrev = ref; + ref->areaPrev->areaNext = ref; +} + +/* +=================== +AddLightRefToArea + +=================== +*/ +void idRenderWorldLocal::AddLightRefToArea( idRenderLightLocal *light, portalArea_t *area ) { + areaReference_t *lref; + + // add a lightref to this area + lref = areaReferenceAllocator.Alloc(); + lref->light = light; + lref->area = area; + lref->ownerNext = light->references; + light->references = lref; + tr.pc.c_lightReferences++; + + // doubly linked list so we can free them easily later + area->lightRefs.areaNext->areaPrev = lref; + lref->areaNext = area->lightRefs.areaNext; + lref->areaPrev = &area->lightRefs; + area->lightRefs.areaNext = lref; +} + +/* +=================== +GenerateAllInteractions + +Force the generation of all light / surface interactions at the start of a level +If this isn't called, they will all be dynamically generated + +This really isn't all that helpful anymore, because the calculation of shadows +and light interactions is deferred from idRenderWorldLocal::CreateLightDefInteractions(), but we +use it as an oportunity to size the interactionTable +=================== +*/ +void idRenderWorldLocal::GenerateAllInteractions() { + if ( !glConfig.isInitialized ) { + return; + } + + int start = Sys_Milliseconds(); + + generateAllInteractionsCalled = false; + + // watch how much memory we allocate + tr.staticAllocCount = 0; + + // let idRenderWorldLocal::CreateLightDefInteractions() know that it shouldn't + // try and do any view specific optimizations + tr.viewDef = NULL; + + for ( int i = 0 ; i < this->lightDefs.Num() ; i++ ) { + idRenderLightLocal *ldef = this->lightDefs[i]; + if ( !ldef ) { + continue; + } + this->CreateLightDefInteractions( ldef ); + } + + int end = Sys_Milliseconds(); + int msec = end - start; + + common->Printf( "idRenderWorld::GenerateAllInteractions, msec = %i, staticAllocCount = %i.\n", msec, tr.staticAllocCount ); + + + // build the interaction table + if ( r_useInteractionTable.GetBool() ) { + interactionTableWidth = entityDefs.Num() + 100; + interactionTableHeight = lightDefs.Num() + 100; + int size = interactionTableWidth * interactionTableHeight * sizeof( *interactionTable ); + interactionTable = (idInteraction **)R_ClearedStaticAlloc( size ); + + int count = 0; + for ( int i = 0 ; i < this->lightDefs.Num() ; i++ ) { + idRenderLightLocal *ldef = this->lightDefs[i]; + if ( !ldef ) { + continue; + } + idInteraction *inter; + for ( inter = ldef->firstInteraction; inter != NULL; inter = inter->lightNext ) { + idRenderEntityLocal *edef = inter->entityDef; + int index = ldef->index * interactionTableWidth + edef->index; + + interactionTable[ index ] = inter; + count++; + } + } + + common->Printf( "interactionTable size: %i bytes\n", size ); + common->Printf( "%i interaction take %i bytes\n", count, count * sizeof( idInteraction ) ); + } + + // entities flagged as noDynamicInteractions will no longer make any + generateAllInteractionsCalled = true; +} + +/* +=================== +idRenderWorldLocal::FreeInteractions +=================== +*/ +void idRenderWorldLocal::FreeInteractions() { + int i; + idRenderEntityLocal *def; + + for ( i = 0 ; i < entityDefs.Num(); i++ ) { + def = entityDefs[i]; + if ( !def ) { + continue; + } + // free all the interactions + while ( def->firstInteraction != NULL ) { + def->firstInteraction->UnlinkAndFree(); + } + } +} + +/* +================== +PushVolumeIntoTree + +Used for both light volumes and model volumes. + +This does not clip the points by the planes, so some slop +occurs. + +tr.viewCount should be bumped before calling, allowing it +to prevent double checking areas. + +We might alternatively choose to do this with an area flow. +================== +*/ +void idRenderWorldLocal::PushVolumeIntoTree_r( idRenderEntityLocal *def, idRenderLightLocal *light, const idSphere *sphere, int numPoints, const idVec3 (*points), + int nodeNum ) { + int i; + areaNode_t *node; + bool front, back; + + if ( nodeNum < 0 ) { + portalArea_t *area; + int areaNum = -1 - nodeNum; + + area = &portalAreas[ areaNum ]; + if ( area->viewCount == tr.viewCount ) { + return; // already added a reference here + } + area->viewCount = tr.viewCount; + + if ( def ) { + AddEntityRefToArea( def, area ); + } + if ( light ) { + AddLightRefToArea( light, area ); + } + + return; + } + + node = areaNodes + nodeNum; + + // if we know that all possible children nodes only touch an area + // we have already marked, we can early out + if ( r_useNodeCommonChildren.GetBool() && + node->commonChildrenArea != CHILDREN_HAVE_MULTIPLE_AREAS ) { + // note that we do NOT try to set a reference in this area + // yet, because the test volume may yet wind up being in the + // solid part, which would cause bounds slightly poked into + // a wall to show up in the next room + if ( portalAreas[ node->commonChildrenArea ].viewCount == tr.viewCount ) { + return; + } + } + + // if the bounding sphere is completely on one side, don't + // bother checking the individual points + float sd = node->plane.Distance( sphere->GetOrigin() ); + if ( sd >= sphere->GetRadius() ) { + nodeNum = node->children[0]; + if ( nodeNum ) { // 0 = solid + PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum ); + } + return; + } + if ( sd <= -sphere->GetRadius() ) { + nodeNum = node->children[1]; + if ( nodeNum ) { // 0 = solid + PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum ); + } + return; + } + + // exact check all the points against the node plane + front = back = false; +#ifdef MACOS_X //loop unrolling & pre-fetching for performance + const idVec3 norm = node->plane.Normal(); + const float plane3 = node->plane[3]; + float D0, D1, D2, D3; + + for ( i = 0 ; i < numPoints - 4; i+=4 ) { + D0 = points[i+0] * norm + plane3; + D1 = points[i+1] * norm + plane3; + if ( !front && D0 >= 0.0f ) { + front = true; + } else if ( !back && D0 <= 0.0f ) { + back = true; + } + D2 = points[i+1] * norm + plane3; + if ( !front && D1 >= 0.0f ) { + front = true; + } else if ( !back && D1 <= 0.0f ) { + back = true; + } + D3 = points[i+1] * norm + plane3; + if ( !front && D2 >= 0.0f ) { + front = true; + } else if ( !back && D2 <= 0.0f ) { + back = true; + } + + if ( !front && D3 >= 0.0f ) { + front = true; + } else if ( !back && D3 <= 0.0f ) { + back = true; + } + if ( back && front ) { + break; + } + } + if(!(back && front)) { + for (; i < numPoints ; i++ ) { + float d; + d = points[i] * node->plane.Normal() + node->plane[3]; + if ( d >= 0.0f ) { + front = true; + } else if ( d <= 0.0f ) { + back = true; + } + if ( back && front ) { + break; + } + } + } +#else + for ( i = 0 ; i < numPoints ; i++ ) { + float d; + + d = points[i] * node->plane.Normal() + node->plane[3]; + if ( d >= 0.0f ) { + front = true; + } else if ( d <= 0.0f ) { + back = true; + } + if ( back && front ) { + break; + } + } +#endif + if ( front ) { + nodeNum = node->children[0]; + if ( nodeNum ) { // 0 = solid + PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum ); + } + } + if ( back ) { + nodeNum = node->children[1]; + if ( nodeNum ) { // 0 = solid + PushVolumeIntoTree_r( def, light, sphere, numPoints, points, nodeNum ); + } + } +} + +/* +============== +PushVolumeIntoTree +============== +*/ +void idRenderWorldLocal::PushVolumeIntoTree( idRenderEntityLocal *def, idRenderLightLocal *light, int numPoints, const idVec3 (*points) ) { + int i; + float radSquared, lr; + idVec3 mid, dir; + + if ( areaNodes == NULL ) { + return; + } + + // calculate a bounding sphere for the points + mid.Zero(); + for ( i = 0; i < numPoints; i++ ) { + mid += points[i]; + } + mid *= ( 1.0f / numPoints ); + + radSquared = 0; + + for ( i = 0; i < numPoints; i++ ) { + dir = points[i] - mid; + lr = dir * dir; + if ( lr > radSquared ) { + radSquared = lr; + } + } + + idSphere sphere( mid, sqrt( radSquared ) ); + + PushVolumeIntoTree_r( def, light, &sphere, numPoints, points, 0 ); +} + +//=================================================================== + +/* +==================== +idRenderWorldLocal::DebugClearLines +==================== +*/ +void idRenderWorldLocal::DebugClearLines( int time ) { + RB_ClearDebugLines( time ); + RB_ClearDebugText( time ); +} + +void idRenderWorldLocal::DebugClear( int time ) { + RB_ClearDebugLines( time ); + RB_ClearDebugPolygons( time ); + RB_ClearDebugText( time ); +} + +/* +==================== +idRenderWorldLocal::DebugLine +==================== +*/ +void idRenderWorldLocal::DebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifetime, const bool depthTest ) { + RB_AddDebugLine( color, start, end, lifetime, depthTest ); +} + +/* +================ +idRenderWorldLocal::DebugArrow +================ +*/ +void idRenderWorldLocal::DebugArrow( const idVec4 &color, const idVec3 &start, const idVec3 &end, int size, const int lifetime ) { + idVec3 forward, right, up, v1, v2; + float a, s; + int i; + static float arrowCos[40]; + static float arrowSin[40]; + static int arrowStep; + + DebugLine( color, start, end, lifetime ); + + if ( r_debugArrowStep.GetInteger() <= 10 ) { + return; + } + // calculate sine and cosine when step size changes + if ( arrowStep != r_debugArrowStep.GetInteger() ) { + arrowStep = r_debugArrowStep.GetInteger(); + for (i = 0, a = 0; a < 360.0f; a += arrowStep, i++) { + arrowCos[i] = idMath::Cos16( DEG2RAD( a ) ); + arrowSin[i] = idMath::Sin16( DEG2RAD( a ) ); + } + arrowCos[i] = arrowCos[0]; + arrowSin[i] = arrowSin[0]; + } + // draw a nice arrow + forward = end - start; + forward.Normalize(); + forward.NormalVectors( right, up); + for (i = 0, a = 0; a < 360.0f; a += arrowStep, i++) { + s = 0.5f * size * arrowCos[i]; + v1 = end - size * forward; + v1 = v1 + s * right; + s = 0.5f * size * arrowSin[i]; + v1 = v1 + s * up; + + s = 0.5f * size * arrowCos[i+1]; + v2 = end - size * forward; + v2 = v2 + s * right; + s = 0.5f * size * arrowSin[i+1]; + v2 = v2 + s * up; + + DebugLine( color, v1, end, lifetime ); + DebugLine( color, v1, v2, lifetime ); + } +} + +/* +==================== +idRenderWorldLocal::DebugWinding +==================== +*/ +void idRenderWorldLocal::DebugWinding( const idVec4 &color, const idWinding &w, const idVec3 &origin, const idMat3 &axis, const int lifetime, const bool depthTest ) { + int i; + idVec3 point, lastPoint; + + if ( w.GetNumPoints() < 2 ) { + return; + } + + lastPoint = origin + w[w.GetNumPoints()-1].ToVec3() * axis; + for ( i = 0; i < w.GetNumPoints(); i++ ) { + point = origin + w[i].ToVec3() * axis; + DebugLine( color, lastPoint, point, lifetime, depthTest ); + lastPoint = point; + } +} + +/* +==================== +idRenderWorldLocal::DebugCircle +==================== +*/ +void idRenderWorldLocal::DebugCircle( const idVec4 &color, const idVec3 &origin, const idVec3 &dir, const float radius, const int numSteps, const int lifetime, const bool depthTest ) { + int i; + float a; + idVec3 left, up, point, lastPoint; + + dir.OrthogonalBasis( left, up ); + left *= radius; + up *= radius; + lastPoint = origin + up; + for ( i = 1; i <= numSteps; i++ ) { + a = idMath::TWO_PI * i / numSteps; + point = origin + idMath::Sin16( a ) * left + idMath::Cos16( a ) * up; + DebugLine( color, lastPoint, point, lifetime, depthTest ); + lastPoint = point; + } +} + +/* +============ +idRenderWorldLocal::DebugSphere +============ +*/ +void idRenderWorldLocal::DebugSphere( const idVec4 &color, const idSphere &sphere, const int lifetime, const bool depthTest /*_D3XP*/ ) { + int i, j, n, num; + float s, c; + idVec3 p, lastp, *lastArray; + + num = 360 / 15; + lastArray = (idVec3 *) _alloca16( num * sizeof( idVec3 ) ); + lastArray[0] = sphere.GetOrigin() + idVec3( 0, 0, sphere.GetRadius() ); + for ( n = 1; n < num; n++ ) { + lastArray[n] = lastArray[0]; + } + + for ( i = 15; i <= 360; i += 15 ) { + s = idMath::Sin16( DEG2RAD(i) ); + c = idMath::Cos16( DEG2RAD(i) ); + lastp[0] = sphere.GetOrigin()[0]; + lastp[1] = sphere.GetOrigin()[1] + sphere.GetRadius() * s; + lastp[2] = sphere.GetOrigin()[2] + sphere.GetRadius() * c; + for ( n = 0, j = 15; j <= 360; j += 15, n++ ) { + p[0] = sphere.GetOrigin()[0] + idMath::Sin16( DEG2RAD(j) ) * sphere.GetRadius() * s; + p[1] = sphere.GetOrigin()[1] + idMath::Cos16( DEG2RAD(j) ) * sphere.GetRadius() * s; + p[2] = lastp[2]; + + DebugLine( color, lastp, p, lifetime,depthTest ); + DebugLine( color, lastp, lastArray[n], lifetime, depthTest ); + + lastArray[n] = lastp; + lastp = p; + } + } +} + +/* +==================== +idRenderWorldLocal::DebugBounds +==================== +*/ +void idRenderWorldLocal::DebugBounds( const idVec4 &color, const idBounds &bounds, const idVec3 &org, const int lifetime ) { + int i; + idVec3 v[8]; + + if ( bounds.IsCleared() ) { + return; + } + + for ( i = 0; i < 8; i++ ) { + v[i][0] = org[0] + bounds[(i^(i>>1))&1][0]; + v[i][1] = org[1] + bounds[(i>>1)&1][1]; + v[i][2] = org[2] + bounds[(i>>2)&1][2]; + } + for ( i = 0; i < 4; i++ ) { + DebugLine( color, v[i], v[(i+1)&3], lifetime ); + DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime ); + DebugLine( color, v[i], v[4+i], lifetime ); + } +} + +void idRenderWorldLocal::DebugBounds( const idVec4 &color, const idBounds &bounds, + const idVec3 &org, const int lifetime, bool depthTest ) { + if ( bounds.IsCleared() ) { + return; + } + idVec3 v[8]; + for ( int i = 0; i < 8; ++i ) { + v[i][0] = org[0] + bounds[( i ^ ( i >> 1 ) ) & 1][0]; + v[i][1] = org[1] + bounds[( i >> 1 ) & 1][1]; + v[i][2] = org[2] + bounds[( i >> 2 ) & 1][2]; + } + for ( int i = 0; i < 4; ++i ) { + DebugLine( color, v[i], v[( i + 1 ) & 3], lifetime, depthTest ); + DebugLine( color, v[4 + i], v[4 + ( ( i + 1 ) & 3 )], lifetime, depthTest ); + DebugLine( color, v[i], v[4 + i], lifetime, depthTest ); + } +} + +size_t idRenderWorldLocal::MemorySummary( const idCmdArgs &args ) { + if ( !glConfig.isInitialized ) { + return 0; + } + size_t bytes = areaReferenceAllocator.Allocated() + interactionAllocator.Allocated() + + areaNumRefAllocator.Allocated() + entityDefs.Allocated() + lightDefs.Allocated() + + effectDefs.Allocated() + localModels.Allocated(); + for ( int i = 0; i < localModels.Num(); ++i ) { + if ( localModels[i] != NULL ) { + bytes += localModels[i]->Memory(); + } + } + const size_t kb = bytes >> 10; + common->Printf( "Renderer - %uK\n", static_cast( kb ) ); + return kb; +} + +void idRenderWorldLocal::DebugFOV( const idVec4 &color, const idVec3 &origin, + const idVec3 &direction, float farDot, float farDist, float nearDot, + float nearDist, float alpha, int lifetime ) { + idVec3 dir = direction; + if ( dir.Normalize() == 0.0f ) { + return; + } + idVec3 right, up; + dir.NormalVectors( right, up ); + farDot = idMath::ClampFloat( -1.0f, 1.0f, farDot ); + nearDot = idMath::ClampFloat( -1.0f, 1.0f, nearDot ); + const float farAngle = idMath::ACos( farDot ); + const float nearAngle = idMath::ACos( nearDot ); + const float farRadius = farDist * idMath::Tan16( farAngle ); + const float nearRadius = nearDist * idMath::Tan16( nearAngle ); + const idVec3 farCenter = origin + dir * farDist; + const idVec3 nearCenter = origin + dir * nearDist; + idVec4 drawColor = color; + if ( alpha < 1.0f ) { + drawColor.w = alpha; + } + DebugCircle( drawColor, farCenter, dir, farRadius, 32, lifetime, true ); + if ( nearDist > 0.0f ) { + DebugCircle( drawColor, nearCenter, dir, nearRadius, 32, lifetime, true ); + } + for ( int i = 0; i < 4; ++i ) { + const float a = idMath::HALF_PI * i; + const idVec3 radial = right * idMath::Cos16( a ) + up * idMath::Sin16( a ); + DebugLine( drawColor, nearCenter + radial * nearRadius, + farCenter + radial * farRadius, lifetime, true ); + } +} + +/* +==================== +idRenderWorldLocal::DebugBox +==================== +*/ +void idRenderWorldLocal::DebugBox( const idVec4 &color, const idBox &box, const int lifetime ) { + int i; + idVec3 v[8]; + + box.ToPoints( v ); + for ( i = 0; i < 4; i++ ) { + DebugLine( color, v[i], v[(i+1)&3], lifetime ); + DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime ); + DebugLine( color, v[i], v[4+i], lifetime ); + } +} + +/* +================ +idRenderWorldLocal::DebugFrustum +================ +*/ +void idRenderWorldLocal::DebugFrustum( const idVec4 &color, const idFrustum &frustum, const bool showFromOrigin, const int lifetime ) { + int i; + idVec3 v[8]; + + frustum.ToPoints( v ); + + if ( frustum.GetNearDistance() > 0.0f ) { + for ( i = 0; i < 4; i++ ) { + DebugLine( color, v[i], v[(i+1)&3], lifetime ); + } + if ( showFromOrigin ) { + for ( i = 0; i < 4; i++ ) { + DebugLine( color, frustum.GetOrigin(), v[i], lifetime ); + } + } + } + for ( i = 0; i < 4; i++ ) { + DebugLine( color, v[4+i], v[4+((i+1)&3)], lifetime ); + DebugLine( color, v[i], v[4+i], lifetime ); + } +} + +/* +============ +idRenderWorldLocal::DebugCone + + dir is the cone axis + radius1 is the radius at the apex + radius2 is the radius at apex+dir +============ +*/ +void idRenderWorldLocal::DebugCone( const idVec4 &color, const idVec3 &apex, const idVec3 &dir, float radius1, float radius2, const int lifetime ) { + int i; + idMat3 axis; + idVec3 top, p1, p2, lastp1, lastp2, d; + + axis[2] = dir; + axis[2].Normalize(); + axis[2].NormalVectors( axis[0], axis[1] ); + axis[1] = -axis[1]; + + top = apex + dir; + lastp2 = top + radius2 * axis[1]; + + if ( radius1 == 0.0f ) { + for ( i = 20; i <= 360; i += 20 ) { + d = idMath::Sin16( DEG2RAD(i) ) * axis[0] + idMath::Cos16( DEG2RAD(i) ) * axis[1]; + p2 = top + d * radius2; + DebugLine( color, lastp2, p2, lifetime ); + DebugLine( color, p2, apex, lifetime ); + lastp2 = p2; + } + } else { + lastp1 = apex + radius1 * axis[1]; + for ( i = 20; i <= 360; i += 20 ) { + d = idMath::Sin16( DEG2RAD(i) ) * axis[0] + idMath::Cos16( DEG2RAD(i) ) * axis[1]; + p1 = apex + d * radius1; + p2 = top + d * radius2; + DebugLine( color, lastp1, p1, lifetime ); + DebugLine( color, lastp2, p2, lifetime ); + DebugLine( color, p1, p2, lifetime ); + lastp1 = p1; + lastp2 = p2; + } + } +} + +/* +================ +idRenderWorldLocal::DebugAxis +================ +*/ +void idRenderWorldLocal::DebugAxis( const idVec3 &origin, const idMat3 &axis ) { + idVec3 start = origin; + idVec3 end = start + axis[0] * 20.0f; + DebugArrow( colorWhite, start, end, 2 ); + end = start + axis[0] * -20.0f; + DebugArrow( colorWhite, start, end, 2 ); + end = start + axis[1] * +20.0f; + DebugArrow( colorGreen, start, end, 2 ); + end = start + axis[1] * -20.0f; + DebugArrow( colorGreen, start, end, 2 ); + end = start + axis[2] * +20.0f; + DebugArrow( colorBlue, start, end, 2 ); + end = start + axis[2] * -20.0f; + DebugArrow( colorBlue, start, end, 2 ); +} + +/* +==================== +idRenderWorldLocal::DebugClearPolygons +==================== +*/ +void idRenderWorldLocal::DebugClearPolygons( int time ) { + RB_ClearDebugPolygons( time ); +} + +/* +==================== +idRenderWorldLocal::DebugPolygon +==================== +*/ +void idRenderWorldLocal::DebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime, const bool depthTest ) { + RB_AddDebugPolygon( color, winding, lifeTime, depthTest ); +} + +/* +================ +idRenderWorldLocal::DebugScreenRect +================ +*/ +void idRenderWorldLocal::DebugScreenRect( const idVec4 &color, const idScreenRect &rect, const viewDef_t *viewDef, const int lifetime ) { + int i; + float centerx, centery, dScale, hScale, vScale; + idBounds bounds; + idVec3 p[4]; + + centerx = ( viewDef->viewport.x2 - viewDef->viewport.x1 ) * 0.5f; + centery = ( viewDef->viewport.y2 - viewDef->viewport.y1 ) * 0.5f; + + dScale = r_znear.GetFloat() + 1.0f; + hScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_x * 0.5f ) ); + vScale = dScale * idMath::Tan16( DEG2RAD( viewDef->renderView.fov_y * 0.5f ) ); + + bounds[0][0] = bounds[1][0] = dScale; + bounds[0][1] = -( rect.x1 - centerx ) / centerx * hScale; + bounds[1][1] = -( rect.x2 - centerx ) / centerx * hScale; + bounds[0][2] = ( rect.y1 - centery ) / centery * vScale; + bounds[1][2] = ( rect.y2 - centery ) / centery * vScale; + + for ( i = 0; i < 4; i++ ) { + p[i].x = bounds[0][0]; + p[i].y = bounds[(i^(i>>1))&1].y; + p[i].z = bounds[(i>>1)&1].z; + p[i] = viewDef->renderView.vieworg + p[i] * viewDef->renderView.viewaxis; + } + for ( i = 0; i < 4; i++ ) { + DebugLine( color, p[i], p[(i+1)&3], false ); + } +} + +/* +================ +idRenderWorldLocal::DrawTextLength + + returns the length of the given text +================ +*/ +float idRenderWorldLocal::DrawTextLength( const char *text, float scale, int len ) { + return RB_DrawTextLength( text, scale, len ); +} + +/* +================ +idRenderWorldLocal::DrawText + + oriented on the viewaxis + align can be 0-left, 1-center (default), 2-right +================ +*/ +void idRenderWorldLocal::DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align, const int lifetime, const bool depthTest ) { + RB_AddDebugText( text, origin, scale, color, viewAxis, align, lifetime, depthTest ); +} + +/* +=============== +idRenderWorldLocal::RegenerateWorld +=============== +*/ +void idRenderWorldLocal::RegenerateWorld() { + R_RegenerateWorld_f( idCmdArgs() ); +} + +/* +=============== +R_GlobalShaderOverride +=============== +*/ +bool R_GlobalShaderOverride( const idMaterial **shader ) { + + if ( !(*shader)->IsDrawn() ) { + return false; + } + + if ( tr.primaryRenderView.globalMaterial ) { + *shader = tr.primaryRenderView.globalMaterial; + return true; + } + + if ( r_materialOverride.GetString()[0] != '\0' ) { + *shader = declManager->FindMaterial( r_materialOverride.GetString() ); + return true; + } + + return false; +} + +/* +=============== +R_RemapShaderBySkin +=============== +*/ +const idMaterial *R_RemapShaderBySkin( const idMaterial *shader, const idDeclSkin *skin, const idMaterial *customShader ) { + + if ( !shader ) { + return NULL; + } + + // never remap surfaces that were originally nodraw, like collision hulls + if ( !shader->IsDrawn() ) { + return shader; + } + + if ( customShader ) { + // this is sort of a hack, but cause deformed surfaces to map to empty surfaces, + // so the item highlight overlay doesn't highlight the autosprite surface + if ( shader->Deform() ) { + return NULL; + } + return const_cast(customShader); + } + + if ( !skin || !shader ) { + return const_cast(shader); + } + + return skin->RemapShaderBySkin( shader ); +} diff --git a/src/renderer/renderworld_demo.cpp b/src/renderer/renderworld_demo.cpp new file mode 100644 index 0000000..5c70a4b --- /dev/null +++ b/src/renderer/renderworld_demo.cpp @@ -0,0 +1,766 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +//#define WRITE_GUIS + +typedef struct { + int version; + int sizeofRenderEntity; + int sizeofRenderLight; + char mapname[256]; +} demoHeader_t; + + +/* +============== +StartWritingDemo +============== +*/ +void idRenderWorldLocal::StartWritingDemo( idDemoFile *demo ) { + int i; + + // FIXME: we should track the idDemoFile locally, instead of snooping into session for it + + WriteLoadMap(); + + // write the door portal state + for ( i = 0 ; i < numInterAreaPortals ; i++ ) { + if ( doublePortals[i].blockingBits ) { + SetPortalState( i+1, doublePortals[i].blockingBits ); + } + } + + // clear the archive counter on all defs + for ( i = 0 ; i < lightDefs.Num() ; i++ ) { + if ( lightDefs[i] ) { + lightDefs[i]->archived = false; + } + } + for ( i = 0 ; i < entityDefs.Num() ; i++ ) { + if ( entityDefs[i] ) { + entityDefs[i]->archived = false; + } + } +} + +void idRenderWorldLocal::StopWritingDemo() { +// writeDemo = NULL; +} + +/* +============== +ProcessDemoCommand +============== +*/ +bool idRenderWorldLocal::ProcessDemoCommand( idDemoFile *readDemo, renderView_t *renderView, int *demoTimeOffset ) { + bool newMap = false; + + if ( !readDemo ) { + return false; + } + + demoCommand_t dc; + qhandle_t h; + + if ( !readDemo->ReadInt( (int&)dc ) ) { + // a demoShot may not have an endFrame, but it is still valid + return false; + } + + switch( dc ) { + case DC_LOADMAP: + // read the initial data + demoHeader_t header; + + readDemo->ReadInt( header.version ); + readDemo->ReadInt( header.sizeofRenderEntity ); + readDemo->ReadInt( header.sizeofRenderLight ); + for ( int i = 0; i < 256; i++ ) + readDemo->ReadChar( header.mapname[i] ); + // the internal version value got replaced by DS_VERSION at toplevel + if ( header.version != 4 ) { + common->Error( "Demo version mismatch.\n" ); + } + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_LOADMAP: %s\n", header.mapname ); + } + InitFromMap( header.mapname ); + + newMap = true; // we will need to set demoTimeOffset + + break; + + case DC_RENDERVIEW: + readDemo->ReadInt( renderView->viewID ); + readDemo->ReadInt( renderView->x ); + readDemo->ReadInt( renderView->y ); + readDemo->ReadInt( renderView->width ); + readDemo->ReadInt( renderView->height ); + readDemo->ReadFloat( renderView->fov_x ); + readDemo->ReadFloat( renderView->fov_y ); + readDemo->ReadVec3( renderView->vieworg ); + readDemo->ReadMat3( renderView->viewaxis ); + readDemo->ReadBool( renderView->cramZNear ); + readDemo->ReadBool( renderView->forceUpdate ); + // binary compatibility with win32 padded structures + char tmp; + readDemo->ReadChar( tmp ); + readDemo->ReadChar( tmp ); + readDemo->ReadInt( renderView->time ); + for ( int i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) + readDemo->ReadFloat( renderView->shaderParms[i] ); + + if ( !readDemo->ReadInt( (int&)renderView->globalMaterial ) ) { + return false; + } + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_RENDERVIEW: %i\n", renderView->time ); + } + + // possibly change the time offset if this is from a new map + if ( newMap && demoTimeOffset ) { + *demoTimeOffset = renderView->time - eventLoop->Milliseconds(); + } + return false; + + case DC_UPDATE_ENTITYDEF: + ReadRenderEntity(); + break; + case DC_DELETE_ENTITYDEF: + if ( !readDemo->ReadInt( h ) ) { + return false; + } + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_DELETE_ENTITYDEF: %i\n", h ); + } + FreeEntityDef( h ); + break; + case DC_UPDATE_LIGHTDEF: + ReadRenderLight(); + break; + case DC_DELETE_LIGHTDEF: + if ( !readDemo->ReadInt( h ) ) { + return false; + } + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_DELETE_LIGHTDEF: %i\n", h ); + } + FreeLightDef( h ); + break; + + case DC_CAPTURE_RENDER: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_CAPTURE_RENDER\n" ); + } + renderSystem->CaptureRenderToImage( readDemo->ReadHashString() ); + break; + + case DC_CROP_RENDER: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_CROP_RENDER\n" ); + } + int size[3]; + readDemo->ReadInt( size[0] ); + readDemo->ReadInt( size[1] ); + readDemo->ReadInt( size[2] ); + renderSystem->CropRenderSize( size[0], size[1], size[2] != 0 ); + break; + + case DC_UNCROP_RENDER: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_UNCROP\n" ); + } + renderSystem->UnCrop(); + break; + + case DC_GUI_MODEL: + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_GUI_MODEL\n" ); + } + tr.demoGuiModel->ReadFromDemo( readDemo ); + break; + + case DC_DEFINE_MODEL: + { + idRenderModel *model = renderModelManager->AllocModel(); + model->ReadFromDemoFile( session->readDemo ); + // add to model manager, so we can find it + renderModelManager->AddModel( model ); + + // save it in the list to free when clearing this map + localModels.Append( model ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_DEFINE_MODEL\n" ); + } + break; + } + case DC_SET_PORTAL_STATE: + { + int data[2]; + readDemo->ReadInt( data[0] ); + readDemo->ReadInt( data[1] ); + SetPortalState( data[0], data[1] ); + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_SET_PORTAL_STATE: %i %i\n", data[0], data[1] ); + } + } + + break; + case DC_END_FRAME: + return true; + + default: + common->Error( "Bad token in demo stream" ); + } + + return false; +} + +bool idRenderWorldLocal::ProcessDemoCommand( idDemoFile *readDemo, renderView_t *renderView, + renderView_t *portalRenderView, int *demoTimeOffset ) { + const bool endFrame = ProcessDemoCommand( readDemo, renderView, demoTimeOffset ); + if ( portalRenderView != NULL && renderView != NULL ) { + *portalRenderView = *renderView; + } + return endFrame; +} + +/* +================ +WriteLoadMap +================ +*/ +void idRenderWorldLocal::WriteLoadMap() { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != session->rw ) { + return; + } + + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_LOADMAP ); + + demoHeader_t header; + strncpy( header.mapname, mapName.c_str(), sizeof( header.mapname ) - 1 ); + header.version = 4; + header.sizeofRenderEntity = sizeof( renderEntity_t ); + header.sizeofRenderLight = sizeof( renderLight_t ); + session->writeDemo->WriteInt( header.version ); + session->writeDemo->WriteInt( header.sizeofRenderEntity ); + session->writeDemo->WriteInt( header.sizeofRenderLight ); + for ( int i = 0; i < 256; i++ ) + session->writeDemo->WriteChar( header.mapname[i] ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_DELETE_LIGHTDEF: %s\n", mapName.c_str() ); + } +} + +/* +================ +WriteVisibleDefs + +================ +*/ +void idRenderWorldLocal::WriteVisibleDefs( const viewDef_t *viewDef ) { + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != session->rw ) { + return; + } + + // make sure all necessary entities and lights are updated + for ( viewEntity_t *viewEnt = viewDef->viewEntitys ; viewEnt ; viewEnt = viewEnt->next ) { + idRenderEntityLocal *ent = viewEnt->entityDef; + + if ( ent->archived ) { + // still up to date + continue; + } + + // write it out + WriteRenderEntity( ent->index, &ent->parms ); + ent->archived = true; + } + + for ( viewLight_t *viewLight = viewDef->viewLights ; viewLight ; viewLight = viewLight->next ) { + idRenderLightLocal *light = viewLight->lightDef; + + if ( light->archived ) { + // still up to date + continue; + } + // write it out + WriteRenderLight( light->index, &light->parms ); + light->archived = true; + } +} + + +/* +================ +WriteRenderView +================ +*/ +void idRenderWorldLocal::WriteRenderView( const renderView_t *renderView ) { + int i; + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != session->rw ) { + return; + } + + // write the actual view command + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_RENDERVIEW ); + session->writeDemo->WriteInt( renderView->viewID ); + session->writeDemo->WriteInt( renderView->x ); + session->writeDemo->WriteInt( renderView->y ); + session->writeDemo->WriteInt( renderView->width ); + session->writeDemo->WriteInt( renderView->height ); + session->writeDemo->WriteFloat( renderView->fov_x ); + session->writeDemo->WriteFloat( renderView->fov_y ); + session->writeDemo->WriteVec3( renderView->vieworg ); + session->writeDemo->WriteMat3( renderView->viewaxis ); + session->writeDemo->WriteBool( renderView->cramZNear ); + session->writeDemo->WriteBool( renderView->forceUpdate ); + // binary compatibility with old win32 version writing padded structures directly to disk + session->writeDemo->WriteUnsignedChar( 0 ); + session->writeDemo->WriteUnsignedChar( 0 ); + session->writeDemo->WriteInt( renderView->time ); + for ( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) + session->writeDemo->WriteFloat( renderView->shaderParms[i] ); + session->writeDemo->WriteInt( (int&)renderView->globalMaterial ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_RENDERVIEW: %i\n", renderView->time ); + } +} + +/* +================ +WriteFreeEntity +================ +*/ +void idRenderWorldLocal::WriteFreeEntity( qhandle_t handle ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != session->rw ) { + return; + } + + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_DELETE_ENTITYDEF ); + session->writeDemo->WriteInt( handle ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_DELETE_ENTITYDEF: %i\n", handle ); + } +} + +/* +================ +WriteFreeLightEntity +================ +*/ +void idRenderWorldLocal::WriteFreeLight( qhandle_t handle ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != session->rw ) { + return; + } + + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_DELETE_LIGHTDEF ); + session->writeDemo->WriteInt( handle ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_DELETE_LIGHTDEF: %i\n", handle ); + } +} + +/* +================ +WriteRenderLight +================ +*/ +void idRenderWorldLocal::WriteRenderLight( qhandle_t handle, const renderLight_t *light ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != session->rw ) { + return; + } + + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_UPDATE_LIGHTDEF ); + session->writeDemo->WriteInt( handle ); + + session->writeDemo->WriteMat3( light->axis ); + session->writeDemo->WriteVec3( light->origin ); + session->writeDemo->WriteInt( light->suppressLightInViewID ); + session->writeDemo->WriteInt( light->allowLightInViewID ); + session->writeDemo->WriteBool( light->noShadows ); + session->writeDemo->WriteBool( light->noSpecular ); + session->writeDemo->WriteBool( light->pointLight ); + session->writeDemo->WriteBool( light->parallel ); + session->writeDemo->WriteVec3( light->lightRadius ); + session->writeDemo->WriteVec3( light->lightCenter ); + session->writeDemo->WriteVec3( light->target ); + session->writeDemo->WriteVec3( light->right ); + session->writeDemo->WriteVec3( light->up ); + session->writeDemo->WriteVec3( light->start ); + session->writeDemo->WriteVec3( light->end ); + session->writeDemo->WriteInt( (int&)light->prelightModel ); + session->writeDemo->WriteInt( light->lightId ); + session->writeDemo->WriteInt( (int&)light->shader ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; i++) + session->writeDemo->WriteFloat( light->shaderParms[i] ); + session->writeDemo->WriteInt( light->referenceSoundHandle ); + + if ( light->prelightModel ) { + session->writeDemo->WriteHashString( light->prelightModel->Name() ); + } + if ( light->shader ) { + session->writeDemo->WriteHashString( light->shader->GetName() ); + } + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_UPDATE_LIGHTDEF: %i\n", handle ); + } +} + +void idRenderWorldLocal::WriteRenderLight( idDemoFile *demo, const renderLight_t *light ) { + if ( demo == NULL || light == NULL ) { + return; + } + demo->WriteMat3( light->axis ); + demo->WriteVec3( light->origin ); + demo->WriteInt( light->suppressLightInViewID ); + demo->WriteInt( light->allowLightInViewID ); + demo->WriteBool( light->noShadows ); + demo->WriteBool( light->noSpecular ); + demo->WriteBool( light->pointLight ); + demo->WriteBool( light->parallel ); + demo->WriteVec3( light->lightRadius ); + demo->WriteVec3( light->lightCenter ); + demo->WriteVec3( light->target ); + demo->WriteVec3( light->right ); + demo->WriteVec3( light->up ); + demo->WriteVec3( light->start ); + demo->WriteVec3( light->end ); + demo->WriteInt( light->lightId ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; ++i ) { + demo->WriteFloat( light->shaderParms[i] ); + } + demo->WriteInt( light->referenceSoundHandle ); + demo->WriteHashString( light->prelightModel ? light->prelightModel->Name() : "" ); + demo->WriteHashString( light->shader ? light->shader->GetName() : "" ); +} + +/* +================ +ReadRenderLight +================ +*/ +void idRenderWorldLocal::ReadRenderLight( ) { + renderLight_t light; + int index; + + session->readDemo->ReadInt( index ); + if ( index < 0 ) { + common->Error( "ReadRenderLight: index < 0 " ); + } + + session->readDemo->ReadMat3( light.axis ); + session->readDemo->ReadVec3( light.origin ); + session->readDemo->ReadInt( light.suppressLightInViewID ); + session->readDemo->ReadInt( light.allowLightInViewID ); + session->readDemo->ReadBool( light.noShadows ); + session->readDemo->ReadBool( light.noSpecular ); + session->readDemo->ReadBool( light.pointLight ); + session->readDemo->ReadBool( light.parallel ); + session->readDemo->ReadVec3( light.lightRadius ); + session->readDemo->ReadVec3( light.lightCenter ); + session->readDemo->ReadVec3( light.target ); + session->readDemo->ReadVec3( light.right ); + session->readDemo->ReadVec3( light.up ); + session->readDemo->ReadVec3( light.start ); + session->readDemo->ReadVec3( light.end ); + session->readDemo->ReadInt( (int&)light.prelightModel ); + session->readDemo->ReadInt( light.lightId ); + session->readDemo->ReadInt( (int&)light.shader ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; i++) + session->readDemo->ReadFloat( light.shaderParms[i] ); + session->readDemo->ReadInt( light.referenceSoundHandle ); + if ( light.prelightModel ) { + light.prelightModel = renderModelManager->FindModel( session->readDemo->ReadHashString() ); + } + if ( light.shader ) { + light.shader = declManager->FindMaterial( session->readDemo->ReadHashString() ); + } + + UpdateLightDef( index, &light ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_UPDATE_LIGHTDEF: %i\n", index ); + } +} + +void idRenderWorldLocal::ReadRenderLight( idDemoFile *demo, renderLight_t &light ) { + memset( &light, 0, sizeof( light ) ); + if ( demo == NULL ) { + return; + } + demo->ReadMat3( light.axis ); + demo->ReadVec3( light.origin ); + demo->ReadInt( light.suppressLightInViewID ); + demo->ReadInt( light.allowLightInViewID ); + demo->ReadBool( light.noShadows ); + demo->ReadBool( light.noSpecular ); + demo->ReadBool( light.pointLight ); + demo->ReadBool( light.parallel ); + demo->ReadVec3( light.lightRadius ); + demo->ReadVec3( light.lightCenter ); + demo->ReadVec3( light.target ); + demo->ReadVec3( light.right ); + demo->ReadVec3( light.up ); + demo->ReadVec3( light.start ); + demo->ReadVec3( light.end ); + demo->ReadInt( light.lightId ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; ++i ) { + demo->ReadFloat( light.shaderParms[i] ); + } + demo->ReadInt( light.referenceSoundHandle ); + const char *prelightName = demo->ReadHashString(); + if ( prelightName != NULL && prelightName[0] != '\0' ) { + light.prelightModel = renderModelManager->FindModel( prelightName ); + } + const char *shaderName = demo->ReadHashString(); + if ( shaderName != NULL && shaderName[0] != '\0' ) { + light.shader = declManager->FindMaterial( shaderName ); + } +} + +/* +================ +WriteRenderEntity +================ +*/ +void idRenderWorldLocal::WriteRenderEntity( qhandle_t handle, const renderEntity_t *ent ) { + + // only the main renderWorld writes stuff to demos, not the wipes or + // menu renders + if ( this != session->rw ) { + return; + } + + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_UPDATE_ENTITYDEF ); + session->writeDemo->WriteInt( handle ); + + session->writeDemo->WriteInt( (int&)ent->hModel ); + session->writeDemo->WriteInt( ent->entityNum ); + session->writeDemo->WriteInt( ent->bodyId ); + session->writeDemo->WriteVec3( ent->bounds[0] ); + session->writeDemo->WriteVec3( ent->bounds[1] ); + session->writeDemo->WriteInt( (int&)ent->callback ); + session->writeDemo->WriteInt( (int&)ent->callbackData ); + session->writeDemo->WriteInt( ent->suppressSurfaceInViewID ); + session->writeDemo->WriteInt( ent->suppressShadowInViewID ); + session->writeDemo->WriteInt( ent->suppressShadowInLightID ); + session->writeDemo->WriteInt( ent->allowSurfaceInViewID ); + session->writeDemo->WriteVec3( ent->origin ); + session->writeDemo->WriteMat3( ent->axis ); + session->writeDemo->WriteInt( (int&)ent->customShader ); + session->writeDemo->WriteInt( (int&)ent->referenceShader ); + session->writeDemo->WriteInt( (int&)ent->customSkin ); + session->writeDemo->WriteInt( ent->referenceSoundHandle ); + for ( int i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) + session->writeDemo->WriteFloat( ent->shaderParms[i] ); + for ( int i = 0; i < MAX_RENDERENTITY_GUI; i++ ) + session->writeDemo->WriteInt( (int&)ent->gui[i] ); + session->writeDemo->WriteInt( (int&)ent->remoteRenderView ); + session->writeDemo->WriteInt( ent->numJoints ); + session->writeDemo->WriteInt( (int&)ent->joints ); + session->writeDemo->WriteFloat( ent->modelDepthHack ); + session->writeDemo->WriteBool( ent->noSelfShadow ); + session->writeDemo->WriteBool( ent->noShadow ); + session->writeDemo->WriteBool( ent->noDynamicInteractions ); + session->writeDemo->WriteInt( ent->weaponDepthHackInViewID ); + session->writeDemo->WriteBool( ent->forceUpdate ); + + if ( ent->customShader ) { + session->writeDemo->WriteHashString( ent->customShader->GetName() ); + } + if ( ent->customSkin ) { + session->writeDemo->WriteHashString( ent->customSkin->GetName() ); + } + if ( ent->hModel ) { + session->writeDemo->WriteHashString( ent->hModel->Name() ); + } + if ( ent->referenceShader ) { + session->writeDemo->WriteHashString( ent->referenceShader->GetName() ); + } + if ( ent->numJoints ) { + for ( int i = 0; i < ent->numJoints; i++) { + float *data = ent->joints[i].ToFloatPtr(); + for ( int j = 0; j < 12; ++j) + session->writeDemo->WriteFloat( data[j] ); + } + } + + /* + if ( ent->decals ) { + ent->decals->WriteToDemoFile( session->readDemo ); + } + if ( ent->overlay ) { + ent->overlay->WriteToDemoFile( session->writeDemo ); + } + */ + +#ifdef WRITE_GUIS + if ( ent->gui ) { + ent->gui->WriteToDemoFile( session->writeDemo ); + } + if ( ent->gui2 ) { + ent->gui2->WriteToDemoFile( session->writeDemo ); + } + if ( ent->gui3 ) { + ent->gui3->WriteToDemoFile( session->writeDemo ); + } +#endif + + if ( r_showDemo.GetBool() ) { + common->Printf( "write DC_UPDATE_ENTITYDEF: %i = %s\n", handle, ent->hModel ? ent->hModel->Name() : "NULL" ); + } +} + +/* +================ +ReadRenderEntity +================ +*/ +void idRenderWorldLocal::ReadRenderEntity() { + renderEntity_t ent; + int index, i; + + session->readDemo->ReadInt( index ); + if ( index < 0 ) { + common->Error( "ReadRenderEntity: index < 0" ); + } + + session->readDemo->ReadInt( (int&)ent.hModel ); + session->readDemo->ReadInt( ent.entityNum ); + session->readDemo->ReadInt( ent.bodyId ); + session->readDemo->ReadVec3( ent.bounds[0] ); + session->readDemo->ReadVec3( ent.bounds[1] ); + session->readDemo->ReadInt( (int&)ent.callback ); + session->readDemo->ReadInt( (int&)ent.callbackData ); + session->readDemo->ReadInt( ent.suppressSurfaceInViewID ); + session->readDemo->ReadInt( ent.suppressShadowInViewID ); + session->readDemo->ReadInt( ent.suppressShadowInLightID ); + session->readDemo->ReadInt( ent.allowSurfaceInViewID ); + session->readDemo->ReadVec3( ent.origin ); + session->readDemo->ReadMat3( ent.axis ); + session->readDemo->ReadInt( (int&)ent.customShader ); + session->readDemo->ReadInt( (int&)ent.referenceShader ); + session->readDemo->ReadInt( (int&)ent.customSkin ); + session->readDemo->ReadInt( ent.referenceSoundHandle ); + for ( i = 0; i < MAX_ENTITY_SHADER_PARMS; i++ ) { + session->readDemo->ReadFloat( ent.shaderParms[i] ); + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + session->readDemo->ReadInt( (int&)ent.gui[i] ); + } + session->readDemo->ReadInt( (int&)ent.remoteRenderView ); + session->readDemo->ReadInt( ent.numJoints ); + session->readDemo->ReadInt( (int&)ent.joints ); + session->readDemo->ReadFloat( ent.modelDepthHack ); + session->readDemo->ReadBool( ent.noSelfShadow ); + session->readDemo->ReadBool( ent.noShadow ); + session->readDemo->ReadBool( ent.noDynamicInteractions ); + session->readDemo->ReadInt( ent.weaponDepthHackInViewID ); + session->readDemo->ReadBool( ent.forceUpdate ); + ent.callback = NULL; + if ( ent.customShader ) { + ent.customShader = declManager->FindMaterial( session->readDemo->ReadHashString() ); + } + if ( ent.customSkin ) { + ent.customSkin = declManager->FindSkin( session->readDemo->ReadHashString() ); + } + if ( ent.hModel ) { + ent.hModel = renderModelManager->FindModel( session->readDemo->ReadHashString() ); + } + if ( ent.referenceShader ) { + ent.referenceShader = declManager->FindMaterial( session->readDemo->ReadHashString() ); + } + if ( ent.numJoints ) { + ent.joints = (idJointMat *)Mem_Alloc16( ent.numJoints * sizeof( ent.joints[0] ) ); + for ( int i = 0; i < ent.numJoints; i++) { + float *data = ent.joints[i].ToFloatPtr(); + for ( int j = 0; j < 12; ++j) + session->readDemo->ReadFloat( data[j] ); + } + } + + ent.callbackData = NULL; + + /* + if ( ent.decals ) { + ent.decals = idRenderModelDecal::Alloc(); + ent.decals->ReadFromDemoFile( session->readDemo ); + } + if ( ent.overlay ) { + ent.overlay = idRenderModelOverlay::Alloc(); + ent.overlay->ReadFromDemoFile( session->readDemo ); + } + */ + + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( ent.gui[ i ] ) { + ent.gui[ i ] = uiManager->Alloc(); +#ifdef WRITE_GUIS + ent.gui[ i ]->ReadFromDemoFile( session->readDemo ); +#endif + } + } + + UpdateEntityDef( index, &ent ); + + if ( r_showDemo.GetBool() ) { + common->Printf( "DC_UPDATE_ENTITYDEF: %i = %s\n", index, ent.hModel ? ent.hModel->Name() : "NULL" ); + } +} diff --git a/src/renderer/renderworld_load.cpp b/src/renderer/renderworld_load.cpp new file mode 100644 index 0000000..a87fe4d --- /dev/null +++ b/src/renderer/renderworld_load.cpp @@ -0,0 +1,714 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + + +/* +================ +idRenderWorldLocal::FreeWorld +================ +*/ +void idRenderWorldLocal::FreeWorld() { + int i; + + // this will free all the lightDefs and entityDefs + FreeDefs(); + + // free all the portals and check light/model references + for ( i = 0 ; i < numPortalAreas ; i++ ) { + portalArea_t *area; + portal_t *portal, *nextPortal; + + area = &portalAreas[i]; + for ( portal = area->portals ; portal ; portal = nextPortal ) { + nextPortal = portal->next; + delete portal->w; + R_StaticFree( portal ); + } + + // there shouldn't be any remaining lightRefs or entityRefs + if ( area->lightRefs.areaNext != &area->lightRefs ) { + common->Error( "FreeWorld: unexpected remaining lightRefs" ); + } + if ( area->entityRefs.areaNext != &area->entityRefs ) { + common->Error( "FreeWorld: unexpected remaining entityRefs" ); + } + if ( area->effectRefs.areaNext != &area->effectRefs ) { + common->Error( "FreeWorld: unexpected remaining effectRefs" ); + } + } + + if ( portalAreas ) { + R_StaticFree( portalAreas ); + portalAreas = NULL; + numPortalAreas = 0; + R_StaticFree( areaScreenRect ); + areaScreenRect = NULL; + } + + if ( doublePortals ) { + R_StaticFree( doublePortals ); + doublePortals = NULL; + numInterAreaPortals = 0; + } + + if ( areaNodes ) { + R_StaticFree( areaNodes ); + areaNodes = NULL; + } + + // free all the inline idRenderModels + for ( i = 0 ; i < localModels.Num() ; i++ ) { + renderModelManager->RemoveModel( localModels[i] ); + delete localModels[i]; + } + localModels.Clear(); + + areaReferenceAllocator.Shutdown(); + interactionAllocator.Shutdown(); + areaNumRefAllocator.Shutdown(); + + mapName = ""; +} + +/* +================ +idRenderWorldLocal::TouchWorldModels +================ +*/ +void idRenderWorldLocal::TouchWorldModels( void ) { + int i; + + for ( i = 0 ; i < localModels.Num() ; i++ ) { + renderModelManager->CheckModel( localModels[i]->Name() ); + } +} + +/* +================ +idRenderWorldLocal::ParseModel +================ +*/ +idRenderModel *idRenderWorldLocal::ParseModel( idLexer *src ) { + idRenderModel *model; + idToken token; + int i, j; + srfTriangles_t *tri; + modelSurface_t surf; + + src->ExpectTokenString( "{" ); + + // parse the name + src->ExpectAnyToken( &token ); + + model = renderModelManager->AllocModel(); + model->InitEmpty( token ); + + int numSurfaces = src->ParseInt(); + if ( numSurfaces < 0 ) { + src->Error( "R_ParseModel: bad numSurfaces" ); + } + + for ( i = 0 ; i < numSurfaces ; i++ ) { + src->ExpectTokenString( "{" ); + + src->ExpectAnyToken( &token ); + + surf.shader = declManager->FindMaterial( token ); + + ((idMaterial*)surf.shader)->AddReference(); + + tri = R_AllocStaticTriSurf(); + surf.geometry = tri; + + tri->numVerts = src->ParseInt(); + tri->numIndexes = src->ParseInt(); + + R_AllocStaticTriSurfVerts( tri, tri->numVerts ); + for ( j = 0 ; j < tri->numVerts ; j++ ) { + float vec[8]; + + src->Parse1DMatrix( 8, vec ); + + tri->verts[j].xyz[0] = vec[0]; + tri->verts[j].xyz[1] = vec[1]; + tri->verts[j].xyz[2] = vec[2]; + tri->verts[j].st[0] = vec[3]; + tri->verts[j].st[1] = vec[4]; + tri->verts[j].normal[0] = vec[5]; + tri->verts[j].normal[1] = vec[6]; + tri->verts[j].normal[2] = vec[7]; + } + + R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); + for ( j = 0 ; j < tri->numIndexes ; j++ ) { + tri->indexes[j] = src->ParseInt(); + } + src->ExpectTokenString( "}" ); + + // add the completed surface to the model + model->AddSurface( surf ); + } + + src->ExpectTokenString( "}" ); + + model->FinishSurfaces(); + + return model; +} + +/* +================ +idRenderWorldLocal::ParseShadowModel +================ +*/ +idRenderModel *idRenderWorldLocal::ParseShadowModel( idLexer *src ) { + idRenderModel *model; + idToken token; + int j; + srfTriangles_t *tri; + modelSurface_t surf; + + src->ExpectTokenString( "{" ); + + // parse the name + src->ExpectAnyToken( &token ); + + model = renderModelManager->AllocModel(); + model->InitEmpty( token ); + + surf.shader = tr.defaultMaterial; + + tri = R_AllocStaticTriSurf(); + surf.geometry = tri; + + tri->numVerts = src->ParseInt(); + tri->numShadowIndexesNoCaps = src->ParseInt(); + tri->numShadowIndexesNoFrontCaps = src->ParseInt(); + tri->numIndexes = src->ParseInt(); + tri->shadowCapPlaneBits = src->ParseInt(); + + R_AllocStaticTriSurfShadowVerts( tri, tri->numVerts ); + tri->bounds.Clear(); + for ( j = 0 ; j < tri->numVerts ; j++ ) { + float vec[8]; + + src->Parse1DMatrix( 3, vec ); + tri->shadowVertexes[j].xyz[0] = vec[0]; + tri->shadowVertexes[j].xyz[1] = vec[1]; + tri->shadowVertexes[j].xyz[2] = vec[2]; + tri->shadowVertexes[j].xyz[3] = 1; // no homogenous value + + tri->bounds.AddPoint( tri->shadowVertexes[j].xyz.ToVec3() ); + } + + R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); + for ( j = 0 ; j < tri->numIndexes ; j++ ) { + tri->indexes[j] = src->ParseInt(); + } + + // add the completed surface to the model + model->AddSurface( surf ); + + src->ExpectTokenString( "}" ); + + // we do NOT do a model->FinishSurfaceces, because we don't need sil edges, planes, tangents, etc. +// model->FinishSurfaces(); + + return model; +} + +/* +================ +idRenderWorldLocal::SetupAreaRefs +================ +*/ +void idRenderWorldLocal::SetupAreaRefs() { + int i; + + connectedAreaNum = 0; + for ( i = 0 ; i < numPortalAreas ; i++ ) { + portalAreas[i].areaNum = i; + portalAreas[i].lightRefs.areaNext = + portalAreas[i].lightRefs.areaPrev = + &portalAreas[i].lightRefs; + portalAreas[i].entityRefs.areaNext = + portalAreas[i].entityRefs.areaPrev = + &portalAreas[i].entityRefs; + portalAreas[i].effectRefs.areaNext = + portalAreas[i].effectRefs.areaPrev = + &portalAreas[i].effectRefs; + portalAreas[i].hasSkybox = false; + } +} + +/* +================ +idRenderWorldLocal::ParseInterAreaPortals +================ +*/ +void idRenderWorldLocal::ParseInterAreaPortals( idLexer *src ) { + int i, j; + + src->ExpectTokenString( "{" ); + + numPortalAreas = src->ParseInt(); + if ( numPortalAreas < 0 ) { + src->Error( "R_ParseInterAreaPortals: bad numPortalAreas" ); + return; + } + portalAreas = (portalArea_t *)R_ClearedStaticAlloc( numPortalAreas * sizeof( portalAreas[0] ) ); + areaScreenRect = (idScreenRect *) R_ClearedStaticAlloc( numPortalAreas * sizeof( idScreenRect ) ); + + // set the doubly linked lists + SetupAreaRefs(); + + numInterAreaPortals = src->ParseInt(); + if ( numInterAreaPortals < 0 ) { + src->Error( "R_ParseInterAreaPortals: bad numInterAreaPortals" ); + return; + } + + doublePortals = (doublePortal_t *)R_ClearedStaticAlloc( numInterAreaPortals * + sizeof( doublePortals [0] ) ); + + for ( i = 0 ; i < numInterAreaPortals ; i++ ) { + int numPoints, a1, a2; + idWinding *w; + portal_t *p; + + numPoints = src->ParseInt(); + a1 = src->ParseInt(); + a2 = src->ParseInt(); + + w = new idWinding( numPoints ); + w->SetNumPoints( numPoints ); + for ( j = 0 ; j < numPoints ; j++ ) { + src->Parse1DMatrix( 3, (*w)[j].ToFloatPtr() ); + // no texture coordinates + (*w)[j][3] = 0; + (*w)[j][4] = 0; + } + + // add the portal to a1 + p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); + p->intoArea = a2; + p->doublePortal = &doublePortals[i]; + p->w = w; + p->w->GetPlane( p->plane ); + + p->next = portalAreas[a1].portals; + portalAreas[a1].portals = p; + + doublePortals[i].portals[0] = p; + + // reverse it for a2 + p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); + p->intoArea = a1; + p->doublePortal = &doublePortals[i]; + p->w = w->Reverse(); + p->w->GetPlane( p->plane ); + + p->next = portalAreas[a2].portals; + portalAreas[a2].portals = p; + + doublePortals[i].portals[1] = p; + } + + src->ExpectTokenString( "}" ); +} + +/* +================ +idRenderWorldLocal::ParseNodes +================ +*/ +void idRenderWorldLocal::ParseNodes( idLexer *src ) { + int i; + + src->ExpectTokenString( "{" ); + + numAreaNodes = src->ParseInt(); + if ( numAreaNodes < 0 ) { + src->Error( "R_ParseNodes: bad numAreaNodes" ); + } + areaNodes = (areaNode_t *)R_ClearedStaticAlloc( numAreaNodes * sizeof( areaNodes[0] ) ); + + for ( i = 0 ; i < numAreaNodes ; i++ ) { + areaNode_t *node; + + node = &areaNodes[i]; + + src->Parse1DMatrix( 4, node->plane.ToFloatPtr() ); + node->children[0] = src->ParseInt(); + node->children[1] = src->ParseInt(); + } + + src->ExpectTokenString( "}" ); +} + +/* +================ +idRenderWorldLocal::CommonChildrenArea_r +================ +*/ +int idRenderWorldLocal::CommonChildrenArea_r( areaNode_t *node ) { + int nums[2]; + + for ( int i = 0 ; i < 2 ; i++ ) { + if ( node->children[i] <= 0 ) { + nums[i] = -1 - node->children[i]; + } else { + nums[i] = CommonChildrenArea_r( &areaNodes[ node->children[i] ] ); + } + } + + // solid nodes will match any area + if ( nums[0] == AREANUM_SOLID ) { + nums[0] = nums[1]; + } + if ( nums[1] == AREANUM_SOLID ) { + nums[1] = nums[0]; + } + + int common; + if ( nums[0] == nums[1] ) { + common = nums[0]; + } else { + common = CHILDREN_HAVE_MULTIPLE_AREAS; + } + + node->commonChildrenArea = common; + + return common; +} + +/* +================= +idRenderWorldLocal::ClearWorld + +Sets up for a single area world +================= +*/ +void idRenderWorldLocal::ClearWorld() { + numPortalAreas = 1; + portalAreas = (portalArea_t *)R_ClearedStaticAlloc( sizeof( portalAreas[0] ) ); + areaScreenRect = (idScreenRect *) R_ClearedStaticAlloc( sizeof( idScreenRect ) ); + + SetupAreaRefs(); + + // even though we only have a single area, create a node + // that has both children pointing at it so we don't need to + // + areaNodes = (areaNode_t *)R_ClearedStaticAlloc( sizeof( areaNodes[0] ) ); + areaNodes[0].plane[3] = 1; + areaNodes[0].children[0] = -1; + areaNodes[0].children[1] = -1; +} + +/* +================= +idRenderWorldLocal::FreeDefs + +dump all the interactions +================= +*/ +void idRenderWorldLocal::FreeDefs() { + int i; + + generateAllInteractionsCalled = false; + + if ( interactionTable ) { + R_StaticFree( interactionTable ); + interactionTable = NULL; + } + + // free all lightDefs + for ( i = 0 ; i < lightDefs.Num() ; i++ ) { + idRenderLightLocal *light; + + light = lightDefs[i]; + if ( light && light->world == this ) { + FreeLightDef( i ); + lightDefs[i] = NULL; + } + } + + // free all entityDefs + for ( i = 0 ; i < entityDefs.Num() ; i++ ) { + idRenderEntityLocal *mod; + + mod = entityDefs[i]; + if ( mod && mod->world == this ) { + FreeEntityDef( i ); + entityDefs[i] = NULL; + } + } + + // Quake 4 adds BSE effect definitions to the same world lifetime as + // lights and entities. + for ( i = 0 ; i < effectDefs.Num() ; i++ ) { + rvRenderEffectLocal *effect = effectDefs[i]; + if ( effect && effect->world == this ) { + FreeEffectDef( i ); + effectDefs[i] = NULL; + } + } +} + +/* +================= +idRenderWorldLocal::InitFromMap + +A NULL or empty name will make a world without a map model, which +is still useful for displaying a bare model +================= +*/ +bool idRenderWorldLocal::InitFromMap( const char *name ) { + idLexer * src; + idToken token; + idStr filename; + idRenderModel * lastModel; + + // if this is an empty world, initialize manually + if ( !name || !name[0] ) { + FreeWorld(); + mapName.Clear(); + ClearWorld(); + return true; + } + + + // load it + filename = name; + filename.SetFileExtension( PROC_FILE_EXT ); + + // if we are reloading the same map, check the timestamp + // and try to skip all the work + ID_TIME_T currentTimeStamp; + fileSystem->ReadFile( filename, NULL, ¤tTimeStamp ); + + if ( name == mapName ) { + if ( currentTimeStamp != FILE_NOT_FOUND_TIMESTAMP && currentTimeStamp == mapTimeStamp ) { + common->Printf( "idRenderWorldLocal::InitFromMap: retaining existing map\n" ); + FreeDefs(); + TouchWorldModels(); + AddWorldModelEntities(); + ClearPortalStates(); + return true; + } + common->Printf( "idRenderWorldLocal::InitFromMap: timestamp has changed, reloading.\n" ); + } + + FreeWorld(); + + src = new idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); + if ( !src->IsLoaded() ) { + common->Printf( "idRenderWorldLocal::InitFromMap: %s not found\n", filename.c_str() ); + ClearWorld(); + return false; + } + + + mapName = name; + mapTimeStamp = currentTimeStamp; + + // if we are writing a demo, archive the load command + if ( session->writeDemo ) { + WriteLoadMap(); + } + + if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) { + common->Printf( "idRenderWorldLocal::InitFromMap: bad id '%s' instead of '%s'\n", token.c_str(), PROC_FILE_ID ); + delete src; + return false; + } + + // parse the file + while ( 1 ) { + if ( !src->ReadToken( &token ) ) { + break; + } + + if ( token == "model" ) { + lastModel = ParseModel( src ); + + // add it to the model manager list + renderModelManager->AddModel( lastModel ); + + // save it in the list to free when clearing this map + localModels.Append( lastModel ); + continue; + } + + if ( token == "shadowModel" ) { + lastModel = ParseShadowModel( src ); + + // add it to the model manager list + renderModelManager->AddModel( lastModel ); + + // save it in the list to free when clearing this map + localModels.Append( lastModel ); + continue; + } + + if ( token == "interAreaPortals" ) { + ParseInterAreaPortals( src ); + continue; + } + + if ( token == "nodes" ) { + ParseNodes( src ); + continue; + } + + src->Error( "idRenderWorldLocal::InitFromMap: bad token \"%s\"", token.c_str() ); + } + + delete src; + + // if it was a trivial map without any areas, create a single area + if ( !numPortalAreas ) { + ClearWorld(); + } + + // find the points where we can early-our of reference pushing into the BSP tree + CommonChildrenArea_r( &areaNodes[0] ); + + AddWorldModelEntities(); + ClearPortalStates(); + + // done! + return true; +} + +/* +===================== +idRenderWorldLocal::ClearPortalStates +===================== +*/ +void idRenderWorldLocal::ClearPortalStates() { + int i, j; + + // all portals start off open + for ( i = 0 ; i < numInterAreaPortals ; i++ ) { + doublePortals[i].blockingBits = PS_BLOCK_NONE; + } + + // flood fill all area connections + for ( i = 0 ; i < numPortalAreas ; i++ ) { + for ( j = 0 ; j < NUM_PORTAL_ATTRIBUTES ; j++ ) { + connectedAreaNum++; + FloodConnectedAreas( &portalAreas[i], j ); + } + } +} + +/* +===================== +idRenderWorldLocal::AddWorldModelEntities +===================== +*/ +void idRenderWorldLocal::AddWorldModelEntities() { + int i; + + // add the world model for each portal area + // we can't just call AddEntityDef, because that would place the references + // based on the bounding box, rather than explicitly into the correct area + for ( i = 0 ; i < numPortalAreas ; i++ ) { + idRenderEntityLocal *def; + int index; + + def = new idRenderEntityLocal; + + // try and reuse a free spot + index = entityDefs.FindNull(); + if ( index == -1 ) { + index = entityDefs.Append(def); + } else { + entityDefs[index] = def; + } + + def->index = index; + def->world = this; + + def->parms.hModel = renderModelManager->FindModel( va("_area%i", i ) ); + if ( def->parms.hModel->IsDefaultModel() || !def->parms.hModel->IsStaticWorldModel() ) { + common->Error( "idRenderWorldLocal::InitFromMap: bad area model lookup" ); + } + + idRenderModel *hModel = def->parms.hModel; + + for ( int j = 0; j < hModel->NumSurfaces(); j++ ) { + const modelSurface_t *surf = hModel->Surface( j ); + + if ( surf->shader->GetName() == idStr( "textures/smf/portal_sky" ) ) { + def->needsPortalSky = true; + portalAreas[i].hasSkybox = true; + } + } + + def->referenceBounds = def->parms.hModel->Bounds(); + + def->parms.axis[0][0] = 1; + def->parms.axis[1][1] = 1; + def->parms.axis[2][2] = 1; + + R_AxisToModelMatrix( def->parms.axis, def->parms.origin, def->modelMatrix ); + + // in case an explicit shader is used on the world, we don't + // want it to have a 0 alpha or color + def->parms.shaderParms[0] = + def->parms.shaderParms[1] = + def->parms.shaderParms[2] = + def->parms.shaderParms[3] = 1; + + AddEntityRefToArea( def, &portalAreas[i] ); + } +} + +/* +===================== +CheckAreaForPortalSky +===================== +*/ +bool idRenderWorldLocal::CheckAreaForPortalSky( int areaNum ) { + areaReference_t *ref; + + assert( areaNum >= 0 && areaNum < numPortalAreas ); + + for ( ref = portalAreas[areaNum].entityRefs.areaNext; ref->entity; ref = ref->areaNext ) { + assert( ref->area == &portalAreas[areaNum] ); + + if ( ref->entity && ref->entity->needsPortalSky ) { + return true; + } + } + + return false; +} diff --git a/src/renderer/renderworld_portals.cpp b/src/renderer/renderworld_portals.cpp new file mode 100644 index 0000000..24de7ba --- /dev/null +++ b/src/renderer/renderworld_portals.cpp @@ -0,0 +1,1113 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* + + +All that is done in these functions is the creation of viewLights +and viewEntitys for the lightDefs and entityDefs that are visible +in the portal areas that can be seen from the current viewpoint. + +*/ + + +// if we hit this many planes, we will just stop cropping the +// view down, which is still correct, just conservative +const int MAX_PORTAL_PLANES = 20; + +typedef struct portalStack_s { + portal_t *p; + const struct portalStack_s *next; + + idScreenRect rect; + + int numPortalPlanes; + idPlane portalPlanes[MAX_PORTAL_PLANES+1]; + // positive side is outside the visible frustum +} portalStack_t; + + +//==================================================================== + + +/* +=================== +idRenderWorldLocal::ScreenRectForWinding +=================== +*/ +idScreenRect idRenderWorldLocal::ScreenRectFromWinding( const idWinding *w, viewEntity_t *space ) { + idScreenRect r; + int i; + idVec3 v; + idVec3 ndc; + float windowX, windowY; + + r.Clear(); + for ( i = 0 ; i < w->GetNumPoints() ; i++ ) { + R_LocalPointToGlobal( space->modelMatrix, (*w)[i].ToVec3(), v ); + R_GlobalToNormalizedDeviceCoordinates( tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, v, ndc ); + + windowX = 0.5f * ( 1.0f + ndc[0] ) * ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ); + windowY = 0.5f * ( 1.0f + ndc[1] ) * ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ); + + r.AddPoint( windowX, windowY ); + } + + r.Expand(); + + return r; +} + +/* +=================== +PortalIsFoggedOut +=================== +*/ +bool idRenderWorldLocal::PortalIsFoggedOut( const portal_t *p ) { + idRenderLightLocal *ldef; + const idWinding *w; + int i; + idPlane forward; + + ldef = p->doublePortal->fogLight; + if ( !ldef ) { + return false; + } + + // find the current density of the fog + const idMaterial *lightShader = ldef->lightShader; + int size = sizeof( float ) *lightShader->GetNumRegisters(); + float *regs =(float *)_alloca( size ); + + lightShader->EvaluateRegisters( regs, ldef->parms.shaderParms, tr.viewDef, ldef->parms.referenceSoundHandle ); + + const shaderStage_t *stage = lightShader->GetStage(0); + + float alpha = regs[ stage->color.registers[3] ]; + + + // if they left the default value on, set a fog distance of 500 + float a; + + if ( alpha <= 1.0f ) { + a = -0.5f / DEFAULT_FOG_DISTANCE; + } else { + // otherwise, distance = alpha color + a = -0.5f / alpha; + } + + forward[0] = a * tr.viewDef->worldSpace.modelViewMatrix[2]; + forward[1] = a * tr.viewDef->worldSpace.modelViewMatrix[6]; + forward[2] = a * tr.viewDef->worldSpace.modelViewMatrix[10]; + forward[3] = a * tr.viewDef->worldSpace.modelViewMatrix[14]; + + w = p->w; + for ( i = 0 ; i < w->GetNumPoints() ; i++ ) { + float d; + + d = forward.Distance( (*w)[i].ToVec3() ); + if ( d < 0.5f ) { + return false; // a point not clipped off + } + } + + return true; +} + +/* +=================== +FloodViewThroughArea_r +=================== +*/ +void idRenderWorldLocal::FloodViewThroughArea_r( const idVec3 origin, int areaNum, + const struct portalStack_s *ps ) { + portal_t* p; + float d; + portalArea_t * area; + const portalStack_t *check; + portalStack_t newStack; + int i, j; + idVec3 v1, v2; + int addPlanes; + idFixedWinding w; // we won't overflow because MAX_PORTAL_PLANES = 20 + + area = &portalAreas[ areaNum ]; + + // cull models and lights to the current collection of planes + AddAreaRefs( areaNum, ps ); + + if ( areaScreenRect[areaNum].IsEmpty() ) { + areaScreenRect[areaNum] = ps->rect; + } else { + areaScreenRect[areaNum].Union( ps->rect ); + } + + // go through all the portals + for ( p = area->portals; p; p = p->next ) { + // an enclosing door may have sealed the portal off + if ( p->doublePortal->blockingBits & PS_BLOCK_VIEW ) { + continue; + } + + // make sure this portal is facing away from the view + d = p->plane.Distance( origin ); + if ( d < -0.1f ) { + continue; + } + + // make sure the portal isn't in our stack trace, + // which would cause an infinite loop + for ( check = ps; check; check = check->next ) { + if ( check->p == p ) { + break; // don't recursively enter a stack + } + } + if ( check ) { + continue; // already in stack + } + + // if we are very close to the portal surface, don't bother clipping + // it, which tends to give epsilon problems that make the area vanish + if ( d < 1.0f ) { + + // go through this portal + newStack = *ps; + newStack.p = p; + newStack.next = ps; + FloodViewThroughArea_r( origin, p->intoArea, &newStack ); + continue; + } + + // clip the portal winding to all of the planes + w = *p->w; + for ( j = 0; j < ps->numPortalPlanes; j++ ) { + if ( !w.ClipInPlace( -ps->portalPlanes[j], 0 ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; // portal not visible + } + + // see if it is fogged out + if ( PortalIsFoggedOut( p ) ) { + continue; + } + + // go through this portal + newStack.p = p; + newStack.next = ps; + + // find the screen pixel bounding box of the remaining portal + // so we can scissor things outside it + newStack.rect = ScreenRectFromWinding( &w, &tr.identitySpace ); + + // slop might have spread it a pixel outside, so trim it back + newStack.rect.Intersect( ps->rect ); + + // generate a set of clipping planes that will further restrict + // the visible view beyond just the scissor rect + + addPlanes = w.GetNumPoints(); + if ( addPlanes > MAX_PORTAL_PLANES ) { + addPlanes = MAX_PORTAL_PLANES; + } + + newStack.numPortalPlanes = 0; + for ( i = 0; i < addPlanes; i++ ) { + j = i+1; + if ( j == w.GetNumPoints() ) { + j = 0; + } + + v1 = origin - w[i].ToVec3(); + v2 = origin - w[j].ToVec3(); + + newStack.portalPlanes[newStack.numPortalPlanes].Normal().Cross( v2, v1 ); + + // if it is degenerate, skip the plane + if ( newStack.portalPlanes[newStack.numPortalPlanes].Normalize() < 0.01f ) { + continue; + } + newStack.portalPlanes[newStack.numPortalPlanes].FitThroughPoint( origin ); + + newStack.numPortalPlanes++; + } + + // the last stack plane is the portal plane + newStack.portalPlanes[newStack.numPortalPlanes] = p->plane; + newStack.numPortalPlanes++; + + FloodViewThroughArea_r( origin, p->intoArea, &newStack ); + } +} + +/* +======================= +FlowViewThroughPortals + +Finds viewLights and viewEntities by flowing from an origin through the visible portals. +origin point can see into. The planes array defines a volume (positive +sides facing in) that should contain the origin, such as a view frustum or a point light box. +Zero planes assumes an unbounded volume. +======================= +*/ +void idRenderWorldLocal::FlowViewThroughPortals( const idVec3 origin, int numPlanes, const idPlane *planes ) { + portalStack_t ps; + int i; + + ps.next = NULL; + ps.p = NULL; + + for ( i = 0 ; i < numPlanes ; i++ ) { + ps.portalPlanes[i] = planes[i]; + } + + ps.numPortalPlanes = numPlanes; + ps.rect = tr.viewDef->scissor; + + if ( tr.viewDef->areaNum < 0 ){ + + for ( i = 0; i < numPortalAreas; i++ ) { + areaScreenRect[i] = tr.viewDef->scissor; + } + + // if outside the world, mark everything + for ( i = 0 ; i < numPortalAreas ; i++ ) { + AddAreaRefs( i, &ps ); + } + } else { + + for ( i = 0; i < numPortalAreas; i++ ) { + areaScreenRect[i].Clear(); + } + + // flood out through portals, setting area viewCount + FloodViewThroughArea_r( origin, tr.viewDef->areaNum, &ps ); + } +} + +//================================================================================================== + + +/* +=================== +FloodLightThroughArea_r +=================== +*/ +void idRenderWorldLocal::FloodLightThroughArea_r( idRenderLightLocal *light, int areaNum, + const struct portalStack_s *ps ) { + portal_t* p; + float d; + portalArea_t * area; + const portalStack_t *check, *firstPortalStack; + portalStack_t newStack; + int i, j; + idVec3 v1, v2; + int addPlanes; + idFixedWinding w; // we won't overflow because MAX_PORTAL_PLANES = 20 + + area = &portalAreas[ areaNum ]; + + // add an areaRef + AddLightRefToArea( light, area ); + + // go through all the portals + for ( p = area->portals; p; p = p->next ) { + // make sure this portal is facing away from the view + d = p->plane.Distance( light->globalLightOrigin ); + if ( d < -0.1f ) { + continue; + } + + // make sure the portal isn't in our stack trace, + // which would cause an infinite loop + for ( check = ps; check; check = check->next ) { + firstPortalStack = check; + if ( check->p == p ) { + break; // don't recursively enter a stack + } + } + if ( check ) { + continue; // already in stack + } + + // if we are very close to the portal surface, don't bother clipping + // it, which tends to give epsilon problems that make the area vanish + if ( d < 1.0f ) { + // go through this portal + newStack = *ps; + newStack.p = p; + newStack.next = ps; + FloodLightThroughArea_r( light, p->intoArea, &newStack ); + continue; + } + + // clip the portal winding to all of the planes + w = *p->w; + for ( j = 0; j < ps->numPortalPlanes; j++ ) { + if ( !w.ClipInPlace( -ps->portalPlanes[j], 0 ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; // portal not visible + } + // also always clip to the original light planes, because they aren't + // necessarily extending to infinitiy like a view frustum + for ( j = 0; j < firstPortalStack->numPortalPlanes; j++ ) { + if ( !w.ClipInPlace( -firstPortalStack->portalPlanes[j], 0 ) ) { + break; + } + } + if ( !w.GetNumPoints() ) { + continue; // portal not visible + } + + // go through this portal + newStack.p = p; + newStack.next = ps; + + // generate a set of clipping planes that will further restrict + // the visible view beyond just the scissor rect + + addPlanes = w.GetNumPoints(); + if ( addPlanes > MAX_PORTAL_PLANES ) { + addPlanes = MAX_PORTAL_PLANES; + } + + newStack.numPortalPlanes = 0; + for ( i = 0; i < addPlanes; i++ ) { + j = i+1; + if ( j == w.GetNumPoints() ) { + j = 0; + } + + v1 = light->globalLightOrigin - w[i].ToVec3(); + v2 = light->globalLightOrigin - w[j].ToVec3(); + + newStack.portalPlanes[newStack.numPortalPlanes].Normal().Cross( v2, v1 ); + + // if it is degenerate, skip the plane + if ( newStack.portalPlanes[newStack.numPortalPlanes].Normalize() < 0.01f ) { + continue; + } + newStack.portalPlanes[newStack.numPortalPlanes].FitThroughPoint( light->globalLightOrigin ); + + newStack.numPortalPlanes++; + } + + FloodLightThroughArea_r( light, p->intoArea, &newStack ); + } +} + + +/* +======================= +FlowLightThroughPortals + +Adds an arearef in each area that the light center flows into. +This can only be used for shadow casting lights that have a generated +prelight, because shadows are cast from back side which may not be in visible areas. +======================= +*/ +void idRenderWorldLocal::FlowLightThroughPortals( idRenderLightLocal *light ) { + portalStack_t ps; + int i; + const idVec3 origin = light->globalLightOrigin; + + // if the light origin areaNum is not in a valid area, + // the light won't have any area refs + if ( light->areaNum == -1 ) { + return; + } + + memset( &ps, 0, sizeof( ps ) ); + + ps.numPortalPlanes = 6; + for ( i = 0 ; i < 6 ; i++ ) { + ps.portalPlanes[i] = light->frustum[i]; + } + + FloodLightThroughArea_r( light, light->areaNum, &ps ); +} + +//====================================================================================================== + +/* +=================== +idRenderWorldLocal::FloodFrustumAreas_r +=================== +*/ +areaNumRef_t *idRenderWorldLocal::FloodFrustumAreas_r( const idFrustum &frustum, const int areaNum, const idBounds &bounds, areaNumRef_t *areas ) { + portal_t *p; + portalArea_t *portalArea; + idBounds newBounds; + areaNumRef_t *a; + + portalArea = &portalAreas[ areaNum ]; + + // go through all the portals + for ( p = portalArea->portals; p; p = p->next ) { + + // check if we already visited the area the portal leads to + for ( a = areas; a; a = a->next ) { + if ( a->areaNum == p->intoArea ) { + break; + } + } + if ( a ) { + continue; + } + + // the frustum origin must be at the front of the portal plane + if ( p->plane.Side( frustum.GetOrigin(), 0.1f ) == SIDE_BACK ) { + continue; + } + + // the frustum must cross the portal plane + if ( frustum.PlaneSide( p->plane, 0.0f ) != PLANESIDE_CROSS ) { + continue; + } + + // get the bounds for the portal winding projected in the frustum + frustum.ProjectionBounds( *p->w, newBounds ); + + newBounds.IntersectSelf( bounds ); + + if ( newBounds[0][0] > newBounds[1][0] || newBounds[0][1] > newBounds[1][1] || newBounds[0][2] > newBounds[1][2] ) { + continue; + } + + newBounds[1][0] = frustum.GetFarDistance(); + + a = areaNumRefAllocator.Alloc(); + a->areaNum = p->intoArea; + a->next = areas; + areas = a; + + areas = FloodFrustumAreas_r( frustum, p->intoArea, newBounds, areas ); + } + + return areas; +} + +/* +=================== +idRenderWorldLocal::FloodFrustumAreas + + Retrieves all the portal areas the frustum floods into where the frustum starts in the given areas. + All portals are assumed to be open. +=================== +*/ +areaNumRef_t *idRenderWorldLocal::FloodFrustumAreas( const idFrustum &frustum, areaNumRef_t *areas ) { + idBounds bounds; + areaNumRef_t *a; + + // bounds that cover the whole frustum + bounds[0].Set( frustum.GetNearDistance(), -1.0f, -1.0f ); + bounds[1].Set( frustum.GetFarDistance(), 1.0f, 1.0f ); + + for ( a = areas; a; a = a->next ) { + areas = FloodFrustumAreas_r( frustum, a->areaNum, bounds, areas ); + } + + return areas; +} + + +/* +======================================================================= + +R_FindViewLightsAndEntities + +======================================================================= +*/ + +/* +================ +CullEntityByPortals + +Return true if the entity reference bounds do not intersect the current portal chain. +================ +*/ +bool idRenderWorldLocal::CullEntityByPortals( const idRenderEntityLocal *entity, const portalStack_t *ps ) { + + if ( !r_useEntityCulling.GetBool() ) { + return false; + } + + // try to cull the entire thing using the reference bounds. + // we do not yet do callbacks or dynamic model creation, + // because we want to do all touching of the model after + // we have determined all the lights that may effect it, + // which optimizes cache usage + if ( R_CullLocalBox( entity->referenceBounds, entity->modelMatrix, + ps->numPortalPlanes, ps->portalPlanes ) ) { + return true; + } + + return false; +} + +/* +=================== +AddAreaEntityRefs + +Any models that are visible through the current portalStack will +have their scissor +=================== +*/ +void idRenderWorldLocal::AddAreaEntityRefs( int areaNum, const portalStack_t *ps ) { + areaReference_t *ref; + idRenderEntityLocal *entity; + portalArea_t *area; + viewEntity_t *vEnt; + idBounds b; + + area = &portalAreas[ areaNum ]; + + for ( ref = area->entityRefs.areaNext ; ref != &area->entityRefs ; ref = ref->areaNext ) { + entity = ref->entity; + + // debug tool to allow viewing of only one entity at a time + if ( r_singleEntity.GetInteger() >= 0 && r_singleEntity.GetInteger() != entity->index ) { + continue; + } + + // remove decals that are completely faded away + R_FreeEntityDefFadedDecals( entity, tr.viewDef->renderView.time ); + + // check for completely suppressing the model + if ( !r_skipSuppress.GetBool() ) { + if ( entity->parms.suppressSurfaceInViewID + && entity->parms.suppressSurfaceInViewID == tr.viewDef->renderView.viewID ) { + continue; + } + if ( entity->parms.allowSurfaceInViewID + && entity->parms.allowSurfaceInViewID != tr.viewDef->renderView.viewID ) { + continue; + } + } + + // cull reference bounds + if ( CullEntityByPortals( entity, ps ) ) { + // we are culled out through this portal chain, but it might + // still be visible through others + continue; + } + + vEnt = R_SetEntityDefViewEntity( entity ); + + // possibly expand the scissor rect + vEnt->scissorRect.Union( ps->rect ); + } +} + +/* +================ +CullLightByPortals + +Return true if the light frustum does not intersect the current portal chain. +The last stack plane is not used because lights are not near clipped. +================ +*/ +bool idRenderWorldLocal::CullLightByPortals( const idRenderLightLocal *light, const portalStack_t *ps ) { + int i, j; + const srfTriangles_t *tri; + float d; + idFixedWinding w; // we won't overflow because MAX_PORTAL_PLANES = 20 + + if ( r_useLightCulling.GetInteger() == 0 ) { + return false; + } + + if ( r_useLightCulling.GetInteger() >= 2 ) { + // exact clip of light faces against all planes + for ( i = 0; i < 6; i++ ) { + // the light frustum planes face out from the light, + // so the planes that have the view origin on the negative + // side will be the "back" faces of the light, which must have + // some fragment inside the portalStack to be visible + if ( light->frustum[i].Distance( tr.viewDef->renderView.vieworg ) >= 0 ) { + continue; + } + + // get the exact winding for this side + const idWinding *ow = light->frustumWindings[i]; + + // projected lights may have one of the frustums degenerated + if ( !ow ) { + continue; + } + + w = *ow; + + // now check the winding against each of the portalStack planes + for ( j = 0; j < ps->numPortalPlanes - 1; j++ ) { + if ( !w.ClipInPlace( -ps->portalPlanes[j] ) ) { + break; + } + } + + if ( w.GetNumPoints() ) { + // part of the winding is visible through the portalStack, + // so the light is not culled + return false; + } + } + // none of the light surfaces were visible + return true; + + } else { + + // simple point check against each plane + tri = light->frustumTris; + + // check against frustum planes + for ( i = 0; i < ps->numPortalPlanes - 1; i++ ) { + for ( j = 0; j < tri->numVerts; j++ ) { + d = ps->portalPlanes[i].Distance( tri->verts[j].xyz ); + if ( d < 0.0f ) { + break; // point is inside this plane + } + } + if ( j == tri->numVerts ) { + // all points were outside one of the planes + tr.pc.c_box_cull_out++; + return true; + } + } + } + + return false; +} + +/* +=================== +AddAreaLightRefs + +This is the only point where lights get added to the viewLights list +=================== +*/ +void idRenderWorldLocal::AddAreaLightRefs( int areaNum, const portalStack_t *ps ) { + areaReference_t *lref; + portalArea_t *area; + idRenderLightLocal *light; + viewLight_t *vLight; + + area = &portalAreas[ areaNum ]; + + for ( lref = area->lightRefs.areaNext ; lref != &area->lightRefs ; lref = lref->areaNext ) { + light = lref->light; + + // debug tool to allow viewing of only one light at a time + if ( r_singleLight.GetInteger() >= 0 && r_singleLight.GetInteger() != light->index ) { + continue; + } + + // check for being closed off behind a door + // a light that doesn't cast shadows will still light even if it is behind a door + if ( r_useLightCulling.GetInteger() >= 3 && + !light->parms.noShadows && light->lightShader->LightCastsShadows() + && light->areaNum != -1 && !tr.viewDef->connectedAreas[ light->areaNum ] ) { + continue; + } + + // cull frustum + if ( CullLightByPortals( light, ps ) ) { + // we are culled out through this portal chain, but it might + // still be visible through others + continue; + } + + vLight = R_SetLightDefViewLight( light ); + + // expand the scissor rect + vLight->scissorRect.Union( ps->rect ); + } +} + +/* +=================== +AddAreaRefs + +This may be entered multiple times with different planes +if more than one portal sees into the area +=================== +*/ +void idRenderWorldLocal::AddAreaRefs( int areaNum, const portalStack_t *ps ) { + // mark the viewCount, so r_showPortals can display the + // considered portals + portalAreas[ areaNum ].viewCount = tr.viewCount; + + // add the models and lights, using more precise culling to the planes + AddAreaEntityRefs( areaNum, ps ); + AddAreaLightRefs( areaNum, ps ); +} + +/* +=================== +BuildConnectedAreas_r +=================== +*/ +void idRenderWorldLocal::BuildConnectedAreas_r( int areaNum ) { + portalArea_t *area; + portal_t *portal; + + if ( tr.viewDef->connectedAreas[areaNum] ) { + return; + } + + tr.viewDef->connectedAreas[areaNum] = true; + + // flood through all non-blocked portals + area = &portalAreas[ areaNum ]; + for ( portal = area->portals ; portal ; portal = portal->next ) { + if ( !(portal->doublePortal->blockingBits & PS_BLOCK_VIEW) ) { + BuildConnectedAreas_r( portal->intoArea ); + } + } +} + +/* +=================== +BuildConnectedAreas + +This is only valid for a given view, not all views in a frame +=================== +*/ +void idRenderWorldLocal::BuildConnectedAreas( void ) { + int i; + + tr.viewDef->connectedAreas = (bool *)R_FrameAlloc( numPortalAreas + * sizeof( tr.viewDef->connectedAreas[0] ) ); + + // if we are outside the world, we can see all areas + if ( tr.viewDef->areaNum == -1 ) { + for ( i = 0 ; i < numPortalAreas ; i++ ) { + tr.viewDef->connectedAreas[i] = true; + } + return; + } + + // start with none visible, and flood fill from the current area + memset( tr.viewDef->connectedAreas, 0, numPortalAreas * sizeof( tr.viewDef->connectedAreas[0] ) ); + BuildConnectedAreas_r( tr.viewDef->areaNum ); +} + +/* +============= +FindViewLightsAndEntites + +All the modelrefs and lightrefs that are in visible areas +will have viewEntitys and viewLights created for them. + +The scissorRects on the viewEntitys and viewLights may be empty if +they were considered, but not actually visible. +============= +*/ +void idRenderWorldLocal::FindViewLightsAndEntities( void ) { + // clear the visible lightDef and entityDef lists + tr.viewDef->viewLights = NULL; + tr.viewDef->viewEntitys = NULL; + + // find the area to start the portal flooding in + if ( !r_usePortals.GetBool() ) { + // debug tool to force no portal culling + tr.viewDef->areaNum = -1; + } else { + tr.viewDef->areaNum = PointInArea( tr.viewDef->initialViewAreaOrigin ); + } + + // determine all possible connected areas for + // light-behind-door culling + BuildConnectedAreas(); + + // bump the view count, invalidating all + // visible areas + tr.viewCount++; + + // flow through all the portals and add models / lights + if ( r_singleArea.GetBool() ) { + // if debugging, only mark this area + // if we are outside the world, don't draw anything + if ( tr.viewDef->areaNum >= 0 ) { + portalStack_t ps; + int i; + static int lastPrintedAreaNum; + + if ( tr.viewDef->areaNum != lastPrintedAreaNum ) { + lastPrintedAreaNum = tr.viewDef->areaNum; + common->Printf( "entering portal area %i\n", tr.viewDef->areaNum ); + } + + for ( i = 0 ; i < 5 ; i++ ) { + ps.portalPlanes[i] = tr.viewDef->frustum[i]; + } + ps.numPortalPlanes = 5; + ps.rect = tr.viewDef->scissor; + + AddAreaRefs( tr.viewDef->areaNum, &ps ); + } + } else { + // note that the center of projection for flowing through portals may + // be a different point than initialViewAreaOrigin for subviews that + // may have the viewOrigin in a solid/invalid area + FlowViewThroughPortals( tr.viewDef->renderView.vieworg, 5, tr.viewDef->frustum ); + } +} + +/* +============== +NumPortals +============== +*/ +int idRenderWorldLocal::NumPortals( void ) const { + return numInterAreaPortals; +} + +/* +============== +FindPortal + +Game code uses this to identify which portals are inside doors. +Returns 0 if no portal contacts the bounds +============== +*/ +qhandle_t idRenderWorldLocal::FindPortal( const idBounds &b ) const { + int i, j; + idBounds wb; + doublePortal_t *portal; + idWinding *w; + + for ( i = 0 ; i < numInterAreaPortals ; i++ ) { + portal = &doublePortals[i]; + w = portal->portals[0]->w; + + wb.Clear(); + for ( j = 0 ; j < w->GetNumPoints() ; j++ ) { + wb.AddPoint( (*w)[j].ToVec3() ); + } + if ( wb.IntersectsBounds( b ) ) { + return i + 1; + } + } + + return 0; +} + +/* +============= +FloodConnectedAreas +============= +*/ +void idRenderWorldLocal::FloodConnectedAreas( portalArea_t *area, int portalAttributeIndex ) { + if ( area->connectedAreaNum[portalAttributeIndex] == connectedAreaNum ) { + return; + } + area->connectedAreaNum[portalAttributeIndex] = connectedAreaNum; + + for ( portal_t *p = area->portals ; p ; p = p->next ) { + if ( !(p->doublePortal->blockingBits & (1<intoArea], portalAttributeIndex ); + } + } +} + +/* +============== +AreasAreConnected + +============== +*/ +bool idRenderWorldLocal::AreasAreConnected( int areaNum1, int areaNum2, portalConnection_t connection ) { + if ( areaNum1 == -1 || areaNum2 == -1 ) { + return false; + } + if ( areaNum1 > numPortalAreas || areaNum2 > numPortalAreas || areaNum1 < 0 || areaNum2 < 0 ) { + common->Error( "idRenderWorldLocal::AreAreasConnected: bad parms: %i, %i", areaNum1, areaNum2 ); + } + + int attribute = 0; + + int intConnection = (int)connection; + + while ( intConnection > 1 ) { + attribute++; + intConnection >>= 1; + } + if ( attribute >= NUM_PORTAL_ATTRIBUTES || ( 1 << attribute ) != (int)connection ) { + common->Error( "idRenderWorldLocal::AreasAreConnected: bad connection number: %i\n", (int)connection ); + } + + return portalAreas[areaNum1].connectedAreaNum[attribute] == portalAreas[areaNum2].connectedAreaNum[attribute]; +} + + +/* +============== +SetPortalState + +doors explicitly close off portals when shut +============== +*/ +void idRenderWorldLocal::SetPortalState( qhandle_t portal, int blockTypes ) { + if ( portal == 0 ) { + return; + } + + if ( portal < 1 || portal > numInterAreaPortals ) { + common->Error( "SetPortalState: bad portal number %i", portal ); + } + int old = doublePortals[portal-1].blockingBits; + if ( old == blockTypes ) { + return; + } + doublePortals[portal-1].blockingBits = blockTypes; + + // leave the connectedAreaGroup the same on one side, + // then flood fill from the other side with a new number for each changed attribute + for ( int i = 0 ; i < NUM_PORTAL_ATTRIBUTES ; i++ ) { + if ( ( old ^ blockTypes ) & ( 1 << i ) ) { + connectedAreaNum++; + FloodConnectedAreas( &portalAreas[doublePortals[portal-1].portals[1]->intoArea], i ); + } + } + + if ( session->writeDemo ) { + session->writeDemo->WriteInt( DS_RENDER ); + session->writeDemo->WriteInt( DC_SET_PORTAL_STATE ); + session->writeDemo->WriteInt( portal ); + session->writeDemo->WriteInt( blockTypes ); + } +} + +/* +============== +GetPortalState +============== +*/ +int idRenderWorldLocal::GetPortalState( qhandle_t portal ) { + if ( portal == 0 ) { + return 0; + } + + if ( portal < 1 || portal > numInterAreaPortals ) { + common->Error( "GetPortalState: bad portal number %i", portal ); + } + + return doublePortals[portal-1].blockingBits; +} + +/* +===================== +idRenderWorldLocal::ShowPortals + +Debugging tool, won't work correctly with SMP or when mirrors are present +===================== +*/ +void idRenderWorldLocal::ShowPortals() { + int i, j; + portalArea_t *area; + portal_t *p; + idWinding *w; + + // flood out through portals, setting area viewCount + for ( i = 0 ; i < numPortalAreas ; i++ ) { + area = &portalAreas[i]; + if ( area->viewCount != tr.viewCount ) { + continue; + } + for ( p = area->portals ; p ; p = p->next ) { + w = p->w; + if ( !w ) { + continue; + } + + if ( portalAreas[ p->intoArea ].viewCount != tr.viewCount ) { + // red = can't see + qglColor3f( 1, 0, 0 ); + } else { + // green = see through + qglColor3f( 0, 1, 0 ); + } + + qglBegin( GL_LINE_LOOP ); + for ( j = 0 ; j < w->GetNumPoints() ; j++ ) { + qglVertex3fv( (*w)[j].ToFloatPtr() ); + } + qglEnd(); + } + } +} + +/* +===================== +FindVisibleAreas_r +===================== +*/ +void idRenderWorldLocal::FindVisibleAreas_r( idVec3 origin, int areaNum, bool *visibleAreas ) { + visibleAreas[areaNum] = true; + for ( portal_t *portal = portalAreas[areaNum].portals; portal != NULL; portal = portal->next ) { + if ( !visibleAreas[portal->intoArea] && + !( portal->doublePortal->blockingBits & PS_BLOCK_VIEW ) && + portal->plane.Distance( origin ) >= -0.1f ) { + FindVisibleAreas_r( origin, portal->intoArea, visibleAreas ); + } + } +} + +void idRenderWorldLocal::FindVisibleAreas( idVec3 origin, int areaNum, bool *visibleAreas ) { + if ( visibleAreas == NULL ) { + return; + } + if ( areaNum >= 0 && areaNum < numPortalAreas ) { + FindVisibleAreas_r( origin, areaNum, visibleAreas ); + } else { + for ( int i = 0; i < numPortalAreas; ++i ) { + visibleAreas[i] = true; + } + } +} + +/* +===================== +RenderPortalFades + +Portal fade geometry is submitted by the back end from the portal/entity +references accumulated for the active view. Keeping this entry point in +the PDB owner also preserves the retail render-world ABI. +===================== +*/ +void idRenderWorldLocal::RenderPortalFades() { + // The standard portal/fog pass in the reconstructed back end consumes + // these references during R_RenderView; no additional immediate-mode + // submission is required here. +} diff --git a/src/renderer/rvGLSLShader.cpp b/src/renderer/rvGLSLShader.cpp new file mode 100644 index 0000000..d64391c --- /dev/null +++ b/src/renderer/rvGLSLShader.cpp @@ -0,0 +1,230 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "Shaders.h" + +// Retail compilation unit: rvGLSLShader.obj. + +rvShader::~rvShader() { +} + +bool rvShader::LoadProgram() { + return false; +} + +unsigned int rvShader::GetVariablePosition( const char *variableName ) { + return static_cast( -1 ); +} + +void rvShader::Bind() { +} + +void rvShader::SetTexture( int position, int unit, idImage *image ) { +} + +void rvShader::UnBindTexture( int position, int unit, idImage *image ) { +} + +void rvShader::UnBind() { +} + +void rvNewShaderStage::Resolve() { +} + +bool rvNewShaderStage::IsValid() { + return !invalidShader; +} + +static void ErrorWithInfoLog( unsigned int object, const char *name ) { + int infoLength = 0; + int charsWritten = 0; + int objectSubtype = 0; + qglGetObjectParameterivARB( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infoLength ); + char *info = infoLength > 0 ? static_cast( _alloca( infoLength + 1 ) ) : NULL; + if ( info != NULL ) { + info[0] = '\0'; + qglGetInfoLogARB( object, infoLength, &charsWritten, info ); + info[infoLength] = '\0'; + } + qglGetObjectParameterivARB( object, GL_OBJECT_SUBTYPE_ARB, &objectSubtype ); + const char *subtype = objectSubtype == GL_VERTEX_SHADER_ARB ? "vertex" : + ( objectSubtype == GL_FRAGMENT_SHADER_ARB ? "fragment" : "unknown" ); + if ( common->IsInitialized() ) { + common->Warning( "Failed to compile %s shader %s:\n%s", subtype, name, info != NULL ? info : "Unknown error" ); + } else { + common->FatalError( "Failed to compile %s shader %s:\n%s", subtype, name, info != NULL ? info : "Unknown error" ); + } +} + +rvGLSLShader::~rvGLSLShader() { + qglDetachObjectARB( program, vertexShader ); + qglDetachObjectARB( program, fragmentShader ); + qglDeleteObjectARB( vertexShader ); + qglDeleteObjectARB( fragmentShader ); + qglDeleteObjectARB( program ); +} + +unsigned int rvGLSLShader::GetVariablePosition( const char *variableName ) { + return qglGetUniformLocationARB( program, variableName ); +} + +void rvGLSLShader::Bind() { + qglUseProgramObjectARB( program ); +} + +void rvGLSLShader::SetTexture( int position, int unit, idImage *image ) { + GL_SelectTexture( unit ); + image->Bind(); + qglUniform1iARB( position, unit ); +} + +void rvGLSLShader::UnBindTexture( int position, int unit, idImage *image ) { + GL_SelectTexture( unit ); + if ( image != NULL ) { + image->UnBind(); + } + globalImages->BindNull(); +} + +void rvGLSLShader::UnBind() { + qglUseProgramObjectARB( 0 ); +} + +rvGLSLShaderStage::rvGLSLShaderStage() { + shaderName[0] = '\0'; + memset( shaderParmNames, 0, sizeof( shaderParmNames ) ); + memset( shaderParmLocations, 0, sizeof( shaderParmLocations ) ); + memset( shaderParmRegisters, 0xFF, sizeof( shaderParmRegisters ) ); + memset( shaderParmNumRegisters, 0, sizeof( shaderParmNumRegisters ) ); + memset( textureParmNames, 0, sizeof( textureParmNames ) ); + for ( int i = 0; i < 8; ++i ) { + textureParmLocations[i] = -1; + textureParmImages[i] = NULL; + } + shaderProgram = NULL; + invalidShader = false; +} + +rvGLSLShaderStage::~rvGLSLShaderStage() { +} + +bool rvGLSLShaderStage::IsSupported() { + return glConfig.GLSLProgramAvailable; +} + +void rvGLSLShaderStage::Resolve() { + if ( !IsSupported() ) { + shaderProgram = NULL; + invalidShader = true; + return; + } + shaderProgram = FindShaderProgram( shaderName ); + if ( shaderProgram == NULL ) { + rvShader *newShader = new rvGLSLShader( 0, shaderName ); + shaderProgram = newShader; + if ( !AddShaderProgram( newShader ) ) { + common->Warning( "Invalid GLSL shader '%s'\n", shaderName ); + invalidShader = true; + return; + } + } + invalidShader = false; + for ( int i = 0; i < 96 && shaderParmNames[i][0] != '\0'; ++i ) { + shaderParmLocations[i] = shaderProgram->GetVariablePosition( shaderParmNames[i] ); + } + for ( int i = 0; i < 8 && textureParmNames[i][0] != '\0'; ++i ) { + textureParmLocations[i] = shaderProgram->GetVariablePosition( textureParmNames[i] ); + } +} + +void rvGLSLShaderStage::BindShaderParameter( int slot, int numParms, const float *floatVector, int arraySize ) { + switch ( numParms ) { + case 1: qglUniform1fvARB( shaderParmLocations[slot], arraySize, floatVector ); break; + case 2: qglUniform2fvARB( shaderParmLocations[slot], arraySize, floatVector ); break; + case 3: qglUniform3fvARB( shaderParmLocations[slot], arraySize, floatVector ); break; + case 4: qglUniform4fvARB( shaderParmLocations[slot], arraySize, floatVector ); break; + } +} + +bool rvGLSLShader::LoadProgram() { + idStr basePath = "glprogs/"; + basePath += name; + idStr vertexName = basePath + "vp"; + idStr fragmentName = basePath + "fp"; + char *vertexBuffer = NULL; + char *fragmentBuffer = NULL; + const int vertexLength = fileSystem->ReadFile( vertexName, reinterpret_cast( &vertexBuffer ) ); + if ( vertexBuffer == NULL ) { + common->Printf( ": File not found\n" ); + return false; + } + const int fragmentLength = fileSystem->ReadFile( fragmentName, reinterpret_cast( &fragmentBuffer ) ); + if ( fragmentBuffer == NULL ) { + fileSystem->FreeFile( vertexBuffer ); + common->Printf( ": File not found\n" ); + return false; + } + + vertexShader = qglCreateShaderObjectARB( GL_VERTEX_SHADER_ARB ); + fragmentShader = qglCreateShaderObjectARB( GL_FRAGMENT_SHADER_ARB ); + const char *vertexSource = vertexBuffer; + const char *fragmentSource = fragmentBuffer; + qglShaderSourceARB( vertexShader, 1, &vertexSource, vertexLength != 0 ? &vertexLength : NULL ); + qglShaderSourceARB( fragmentShader, 1, &fragmentSource, fragmentLength != 0 ? &fragmentLength : NULL ); + fileSystem->FreeFile( vertexBuffer ); + fileSystem->FreeFile( fragmentBuffer ); + + int status = 0; + qglGetError(); + qglCompileShaderARB( vertexShader ); + unsigned int error = qglGetError(); + qglGetObjectParameterivARB( vertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &status ); + if ( error != GL_NO_ERROR || !status ) { + ErrorWithInfoLog( vertexShader, name ); + return false; + } + + qglCompileShaderARB( fragmentShader ); + error = qglGetError(); + qglGetObjectParameterivARB( fragmentShader, GL_OBJECT_COMPILE_STATUS_ARB, &status ); + if ( error != GL_NO_ERROR || !status ) { + ErrorWithInfoLog( fragmentShader, name ); + return false; + } + + program = qglCreateProgramObjectARB(); + qglAttachObjectARB( program, vertexShader ); + qglAttachObjectARB( program, fragmentShader ); + qglLinkProgramARB( program ); + error = qglGetError(); + qglGetObjectParameterivARB( program, GL_OBJECT_LINK_STATUS_ARB, &status ); + if ( error != GL_NO_ERROR || !status ) { + ErrorWithInfoLog( program, name ); + return false; + } + return true; +} diff --git a/src/renderer/rvIndexBuffer.cpp b/src/renderer/rvIndexBuffer.cpp new file mode 100644 index 0000000..05ad8d7 --- /dev/null +++ b/src/renderer/rvIndexBuffer.cpp @@ -0,0 +1,283 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvIndexBuffer.h" + +void rvIndexBuffer::CreateIndexStorage() { + const int indexSize = ( m_flags & rvIndexBufferFlag16Bit ) != 0 ? 2 : 4; + if ( ( m_flags & rvIndexBufferFlagSystemMemory ) != 0 ) { + m_systemMemStorage = static_cast( Mem_Alloc16( indexSize * m_numIndices, MA_RENDER ) ); + if ( m_systemMemStorage == NULL ) { + common->FatalError( "Ran out of memory trying to allocate system memory index storage" ); + return; + } + } + + if ( ( m_flags & rvIndexBufferFlagVideoMemory ) != 0 ) { + qglGenBuffersARB( 1, &m_ibID ); + if ( m_ibID == 0 ) { + common->FatalError( "rvIndexBuffer: Unable to gen index buffer id" ); + return; + } + if ( ( m_flags & rvIndexBufferFlagDynamicStream ) == 0 ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_ibID ); + qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, indexSize * m_numIndices, NULL, GL_STATIC_DRAW_ARB ); + if ( qglGetError() != GL_NO_ERROR ) { + common->FatalError( "Unable to allocate index storage" ); + } + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + } +} + +rvIndexBuffer::rvIndexBuffer() { + m_flags = 0; + m_lockStatus = 0; + m_numIndices = 0; + m_ibID = 0; + m_lockIndexOffset = 0; + m_lockIndexCount = 0; + m_lockedBase = NULL; + m_systemMemStorage = NULL; + m_numIndicesWritten = 0; +} + +bool rvIndexBuffer::Lock( int indexOffset, int numIndicesToLock, unsigned int lockFlags, void *&indexPtr ) { + m_lockIndexOffset = indexOffset; + m_lockIndexCount = numIndicesToLock != 0 ? numIndicesToLock : m_numIndices - indexOffset; + const int indexSize = ( m_flags & rvIndexBufferFlag16Bit ) != 0 ? 2 : 4; + + if ( ( m_flags & rvIndexBufferFlagSystemMemory ) != 0 ) { + m_lockedBase = m_systemMemStorage; + } else { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_ibID ); + unsigned int access = GL_READ_WRITE_ARB; + if ( ( lockFlags & rvIndexBufferLockFlagRead ) != 0 ) { + access = ( lockFlags & rvIndexBufferLockFlagWrite ) != 0 ? GL_READ_WRITE_ARB : GL_READ_ONLY_ARB; + } else if ( ( lockFlags & rvIndexBufferLockFlagWrite ) != 0 ) { + access = GL_WRITE_ONLY_ARB; + const unsigned int usage = ( m_flags & rvIndexBufferFlagDynamicStream ) != 0 ? GL_STREAM_DRAW_ARB : GL_STATIC_DRAW_ARB; + if ( ( m_flags & rvIndexBufferFlagDynamicStream ) != 0 ) { + lockFlags |= rvIndexBufferLockFlagEmpty; + } + if ( ( lockFlags & rvIndexBufferLockFlagEmpty ) != 0 ) { + m_lockIndexOffset = 0; + qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, indexSize * m_lockIndexCount, NULL, usage ); + } + } + m_lockedBase = static_cast( qglMapBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, access ) ); + if ( m_lockedBase == NULL ) { + return false; + } + } + + m_lockStatus = lockFlags; + indexPtr = m_lockedBase + indexSize * m_lockIndexOffset; + return true; +} + +void rvIndexBuffer::Unlock() { + const bool wrote = ( m_lockStatus & rvIndexBufferLockFlagWrite ) != 0; + if ( wrote ) { + if ( ( m_lockStatus & rvIndexBufferLockFlagEmpty ) != 0 ) { + m_numIndicesWritten = m_lockIndexCount; + } else { + m_numIndicesWritten += m_lockIndexCount; + } + } + + const int indexSize = ( m_flags & rvIndexBufferFlag16Bit ) != 0 ? 2 : 4; + if ( ( m_flags & rvIndexBufferFlagSystemMemory ) != 0 ) { + if ( wrote && ( m_flags & rvIndexBufferFlagVideoMemory ) != 0 ) { + const int byteOffset = indexSize * m_lockIndexOffset; + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_ibID ); + qglBufferSubDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, byteOffset, + indexSize * m_lockIndexCount, m_systemMemStorage + byteOffset ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + } else if ( ( m_flags & rvIndexBufferFlagVideoMemory ) != 0 ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_ibID ); + qglUnmapBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + + m_lockStatus = 0; + m_lockIndexOffset = 0; + m_lockIndexCount = 0; + m_lockedBase = NULL; +} + +void rvIndexBuffer::CopyRemappedData( int indexBufferOffset, int numIndices, unsigned int *indexMapping, + int *indices, unsigned int indexBase ) { + if ( indexBufferOffset + numIndices > m_numIndices ) { + common->Warning( "rvIndexBuffer: attempt to copy index data out-of-bounds" ); + return; + } + void *destination = NULL; + if ( !Lock( indexBufferOffset, numIndices, rvIndexBufferLockFlagWrite, destination ) ) { + return; + } + if ( ( m_flags & rvIndexBufferFlag16Bit ) != 0 ) { + unsigned short *out = static_cast( destination ); + for ( int i = 0; i < numIndices; ++i ) { out[i] = static_cast( indexBase + indexMapping[indices[i]] ); } + } else { + unsigned int *out = static_cast( destination ); + for ( int i = 0; i < numIndices; ++i ) { out[i] = indexBase + indexMapping[indices[i]]; } + } + Unlock(); +} + +void rvIndexBuffer::CopyData( int indexBufferOffset, int numIndices, int *indices, unsigned int indexBase ) { + if ( indexBufferOffset + numIndices > m_numIndices ) { + common->Warning( "rvIndexBuffer: attempt to copy index data out-of-bounds" ); + return; + } + void *destination = NULL; + if ( !Lock( indexBufferOffset, numIndices, rvIndexBufferLockFlagWrite, destination ) ) { + return; + } + if ( ( m_flags & rvIndexBufferFlag16Bit ) != 0 ) { + unsigned short *out = static_cast( destination ); + for ( int i = 0; i < numIndices; ++i ) { out[i] = static_cast( indexBase + indices[i] ); } + } else { + unsigned int *out = static_cast( destination ); + for ( int i = 0; i < numIndices; ++i ) { out[i] = indexBase + indices[i]; } + } + Unlock(); +} + +void rvIndexBuffer::Write( idFile &outFile, const char *prepend ) { + idStr indent = prepend; + outFile.WriteFloatString( "%sIndexBuffer\n%s{\n", prepend, prepend ); + indent += "\t"; + if ( ( m_flags & rvIndexBufferFlagSystemMemory ) != 0 ) { outFile.WriteFloatString( "%sSystemMemory\n", indent.c_str() ); } + if ( ( m_flags & rvIndexBufferFlagVideoMemory ) != 0 ) { outFile.WriteFloatString( "%sVideoMemory\n", indent.c_str() ); } + outFile.WriteFloatString( "%sIndex[ %d ]\n%s{\n%s\t", indent.c_str(), m_numIndicesWritten, indent.c_str(), indent.c_str() ); + void *base = NULL; + if ( !Lock( 0, m_numIndicesWritten, rvIndexBufferLockFlagRead, base ) ) { + common->FatalError( "Index buffer cannot be mapped for access" ); + return; + } + for ( int i = 0; i < m_numIndicesWritten; ++i ) { + const unsigned int value = ( m_flags & rvIndexBufferFlag16Bit ) != 0 ? + static_cast( base )[i] : static_cast( base )[i]; + outFile.WriteFloatString( "%d ", value ); + if ( ( i & 15 ) == 15 ) { outFile.WriteFloatString( "\n%s\t", indent.c_str() ); } + } + outFile.WriteFloatString( "\n" ); + Unlock(); + outFile.WriteFloatString( "%s}\n%s}\n", indent.c_str(), prepend ); +} + +void rvIndexBuffer::Shutdown() { + if ( m_lockStatus != 0 ) { Unlock(); } + if ( m_systemMemStorage != NULL ) { Mem_Free16( m_systemMemStorage ); } + if ( m_ibID != 0 ) { qglDeleteBuffersARB( 1, &m_ibID ); } + m_ibID = 0; + m_flags = 0; + m_lockStatus = 0; + m_numIndices = 0; + m_lockIndexOffset = 0; + m_lockIndexCount = 0; + m_lockedBase = NULL; + m_systemMemStorage = NULL; + m_numIndicesWritten = 0; +} + +void rvIndexBuffer::Resize( int numIndices ) { + if ( numIndices == m_numIndices || ( m_flags & rvIndexBufferFlagSystemMemory ) == 0 ) { + return; + } + const int indexSize = ( m_flags & rvIndexBufferFlag16Bit ) != 0 ? 2 : 4; + const int copyCount = Min( m_numIndices, numIndices ); + unsigned char *newStorage = static_cast( Mem_Alloc16( indexSize * numIndices, MA_RENDER ) ); + if ( newStorage == NULL ) { + common->FatalError( "Ran out of memory trying to allocate system memory index storage" ); + Shutdown(); + return; + } + SIMDProcessor->Memcpy( newStorage, m_systemMemStorage, indexSize * copyCount ); + Mem_Free16( m_systemMemStorage ); + m_systemMemStorage = newStorage; + if ( ( m_flags & rvIndexBufferFlagVideoMemory ) != 0 ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_ibID ); + qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, indexSize * numIndices, m_systemMemStorage, GL_STATIC_DRAW_ARB ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + m_numIndices = numIndices; +} + +rvIndexBuffer::~rvIndexBuffer() { + Shutdown(); +} + +void rvIndexBuffer::Init( int numIndices, unsigned int flagMask ) { + if ( m_flags != 0 ) { Shutdown(); } + m_numIndices = numIndices; + m_flags = flagMask; + CreateIndexStorage(); +} + +void rvIndexBuffer::Init( Lexer &lexer ) { + if ( m_flags != 0 ) { Shutdown(); } + lexer.ExpectTokenString( "{" ); + m_flags = 0; + idToken token; + while ( lexer.ReadToken( &token ) ) { + if ( !token.Icmp( "SystemMemory" ) ) { m_flags |= rvIndexBufferFlagSystemMemory; } + else if ( !token.Icmp( "VideoMemory" ) ) { m_flags |= rvIndexBufferFlagVideoMemory; } + else { break; } + } + if ( m_flags == 0 ) { m_flags = rvIndexBufferFlagVideoMemory; } + if ( !token.Icmp( "BitDepth" ) ) { + if ( lexer.ParseInt() == 16 ) { m_flags |= rvIndexBufferFlag16Bit; } + lexer.ReadToken( &token ); + } + if ( token.Icmp( "Index" ) ) { lexer.Error( "Expected index header" ); } + lexer.ExpectTokenString( "[" ); + m_numIndices = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + if ( m_numIndices <= 0 ) { lexer.Error( "Invalid index count" ); } + CreateIndexStorage(); + void *base = NULL; + if ( !Lock( 0, m_numIndices, rvIndexBufferLockFlagWrite | rvIndexBufferLockFlagEmpty, base ) ) { + lexer.Error( "Index buffer cannot be mapped for access" ); + Shutdown(); + return; + } + if ( ( m_flags & rvIndexBufferFlag16Bit ) != 0 ) { + unsigned short *out = static_cast( base ); + for ( int i = 0; i < m_numIndices; ++i ) { out[i] = static_cast( lexer.ParseInt() ); } + } else { + unsigned int *out = static_cast( base ); + for ( int i = 0; i < m_numIndices; ++i ) { out[i] = lexer.ParseInt(); } + } + Unlock(); + lexer.ExpectTokenString( "}" ); + lexer.ExpectTokenString( "}" ); +} diff --git a/src/renderer/rvIndexBuffer.h b/src/renderer/rvIndexBuffer.h new file mode 100644 index 0000000..d8e97a4 --- /dev/null +++ b/src/renderer/rvIndexBuffer.h @@ -0,0 +1,81 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software + +#ifndef __RENDERER_RVINDEXBUFFER_H__ +#define __RENDERER_RVINDEXBUFFER_H__ + +class Lexer; + +enum rvIndexBufferFlags { + rvIndexBufferFlagSystemMemory = BIT( 0 ), + rvIndexBufferFlagVideoMemory = BIT( 1 ), + rvIndexBufferFlag16Bit = BIT( 2 ), + rvIndexBufferFlagDynamicStream = BIT( 3 ) +}; + +enum rvIndexBufferLockFlags { + rvIndexBufferLockFlagRead = BIT( 0 ), + rvIndexBufferLockFlagWrite = BIT( 1 ), + rvIndexBufferLockFlagEmpty = BIT( 2 ) +}; + +class rvIndexBuffer { +public: + rvIndexBuffer(); + ~rvIndexBuffer(); + + void Init( int numIndices, unsigned int flagMask ); + void Init( Lexer &lexer ); + void Shutdown(); + void Resize( int numIndices ); + bool Lock( int indexOffset, int numIndicesToLock, unsigned int lockFlags, void *&indexPtr ); + void Unlock(); + void CopyRemappedData( int indexBufferOffset, int numIndices, unsigned int *indexMapping, + int *indices, unsigned int indexBase ); + void CopyData( int indexBufferOffset, int numIndices, int *indices, unsigned int indexBase ); + void Write( idFile &outFile, const char *prepend ); + + int GetNumIndices() const { return m_numIndices; } + int GetNumIndicesWritten() const { return m_numIndicesWritten; } + unsigned int GetBufferId() const { return m_ibID; } + bool Is16Bit() const { return ( m_flags & rvIndexBufferFlag16Bit ) != 0; } + bool IsVideoMemory() const { return ( m_flags & rvIndexBufferFlagVideoMemory ) != 0; } + +protected: + void CreateIndexStorage(); + + unsigned int m_flags; + unsigned int m_lockStatus; + int m_numIndices; + unsigned int m_ibID; + int m_lockIndexOffset; + int m_lockIndexCount; + unsigned char *m_lockedBase; + unsigned char *m_systemMemStorage; + int m_numIndicesWritten; +}; + +#endif diff --git a/src/renderer/rvMesh.cpp b/src/renderer/rvMesh.cpp new file mode 100644 index 0000000..792b2e3 --- /dev/null +++ b/src/renderer/rvMesh.cpp @@ -0,0 +1,1108 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvMesh.h" + +/* +=============================================================================== + + Quake 4 MD5R mesh front end + + The exact 96-byte layout and these function boundaries come from rvMesh.obj + in quake4.pdb; behavior follows the address-matched retail executable. + +=============================================================================== +*/ + +rvMesh::rvMesh() { + ResetValues(); +} + +rvMesh::~rvMesh() { + delete[] m_primBatches; + ResetValues(); +} + +void rvMesh::Init( rvRenderModelMD5R &renderModel, Lexer &lexer ) { + if ( m_renderModel != NULL ) { + delete[] m_primBatches; + ResetValues(); + } + + lexer.ExpectTokenString( "{" ); + idToken token; + if ( !lexer.ReadToken( &token ) ) { + lexer.Error( "Expected keyword." ); + } + if ( token.Icmp( "LevelOfDetail" ) == 0 ) { + m_levelOfDetail = static_cast( lexer.ParseInt() ); + lexer.ReadToken( &token ); + } + if ( token.Icmp( "Material" ) != 0 ) { + lexer.Error( "Expected Material keyword." ); + } + lexer.ReadToken( &token ); + m_material = declManager->FindMaterial( token.c_str() ); + if ( !lexer.ReadToken( &token ) ) { + lexer.Error( "Expected SilhouetteEdge, SilTracePrimBatch or DrawPrimBatch keyword." ); + } + + if ( token.Icmp( "SilTraceBuffers" ) == 0 ) { + m_silTraceVertexBuffer = static_cast( lexer.ParseInt() ); + m_silTraceIndexBuffer = static_cast( lexer.ParseInt() ); + if ( m_silTraceVertexBuffer >= renderModel.m_numVertexBuffers || + m_silTraceIndexBuffer >= renderModel.m_numIndexBuffers ) { + lexer.Error( "Invalid buffer reference by SilTraceBuffers statement" ); + } + lexer.ReadToken( &token ); + } + if ( token.Icmp( "DrawBuffers" ) == 0 ) { + m_drawVertexBuffer = static_cast( lexer.ParseInt() ); + m_drawIndexBuffer = static_cast( lexer.ParseInt() ); + if ( m_drawVertexBuffer >= renderModel.m_numVertexBuffers || + m_drawIndexBuffer >= renderModel.m_numIndexBuffers ) { + lexer.Error( "Invalid buffer reference by DrawBuffers statement" ); + } + lexer.ReadToken( &token ); + } + if ( token.Icmp( "ShadowVolumeBuffers" ) == 0 ) { + m_shadowVolVertexBuffer = static_cast( lexer.ParseInt() ); + m_shadowVolIndexBuffer = static_cast( lexer.ParseInt() ); + if ( m_shadowVolVertexBuffer >= renderModel.m_numVertexBuffers || + m_shadowVolIndexBuffer >= renderModel.m_numIndexBuffers ) { + lexer.Error( "Invalid buffer reference by ShadowVolumeBuffers statement" ); + } + lexer.ReadToken( &token ); + } + if ( token.Icmp( "PrimBatch" ) != 0 ) { + lexer.Error( "Expected PrimBatch keyword." ); + } + lexer.ExpectTokenString( "[" ); + m_numPrimBatches = static_cast( lexer.ParseInt() ); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + m_primBatches = new rvPrimBatch[m_numPrimBatches]; + if ( m_primBatches == NULL ) { + lexer.Error( "Out of memory" ); + return; + } + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + lexer.ExpectTokenString( "PrimBatch" ); + m_primBatches[batch].Init( lexer ); + } + CalcGeometryProfile(); + lexer.ExpectTokenString( "}" ); + lexer.ReadToken( &token ); + if ( token.Icmp( "Bounds" ) == 0 ) { + m_bounds[0].Set( lexer.ParseFloat(), lexer.ParseFloat(), lexer.ParseFloat() ); + m_bounds[1].Set( lexer.ParseFloat(), lexer.ParseFloat(), lexer.ParseFloat() ); + lexer.ReadToken( &token ); + } + if ( token.Icmp( "}" ) != 0 ) { + lexer.Error( "couldn't find expected '}'" ); + } + + m_renderModel = &renderModel; + if ( m_silTraceVertexBuffer != -1 && m_silTraceIndexBuffer != -1 ) { + if ( m_drawVertexBuffer != -1 && m_drawIndexBuffer != -1 ) { + rvVertexBuffer &silTraceVertexBuffer = + renderModel.m_vertexBuffers[m_silTraceVertexBuffer]; + rvVertexBuffer &drawVertexBuffer = renderModel.m_vertexBuffers[m_drawVertexBuffer]; + if ( silTraceVertexBuffer.IsInitiallyEmpty() && + !drawVertexBuffer.IsInitiallyEmpty() ) { + rvIndexBuffer &silTraceIndexBuffer = + renderModel.m_indexBuffers[m_silTraceIndexBuffer]; + rvIndexBuffer &drawIndexBuffer = renderModel.m_indexBuffers[m_drawIndexBuffer]; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + m_primBatches[batch].CopySilTraceVertices( silTraceVertexBuffer, + silTraceIndexBuffer, drawVertexBuffer, drawIndexBuffer ); + } + } + } + if ( m_shadowVolVertexBuffer != -1 ) { + rvVertexBuffer &shadowVertexBuffer = + renderModel.m_vertexBuffers[m_shadowVolVertexBuffer]; + if ( shadowVertexBuffer.IsInitiallyEmpty() ) { + rvVertexBuffer &silTraceVertexBuffer = + renderModel.m_vertexBuffers[m_silTraceVertexBuffer]; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + m_primBatches[batch].CopyShadowVertices( + shadowVertexBuffer, silTraceVertexBuffer ); + } + } + } + } +} + +void rvMesh::Init( rvRenderModelMD5R &renderModel, modelSurface_t &sourceSurface, + int *silRemap, int silTraceVertexBuffer, int drawVertexBuffer, + int shadowVertexBuffer, int silTraceIndexBuffer, int drawIndexBuffer, + int shadowIndexBuffer ) { + m_renderModel = &renderModel; + m_material = sourceSurface.shader; + srfTriangles_t &source = *sourceSurface.geometry; + m_numPrimBatches = 1; + m_primBatches = new rvPrimBatch[1]; + if ( m_primBatches == NULL ) { + common->FatalError( "Out of memory" ); + return; + } + m_primBatches[0].Init( NULL, 1 ); + m_silTraceVertexBuffer = static_cast( silTraceVertexBuffer ); + m_drawVertexBuffer = static_cast( drawVertexBuffer ); + m_shadowVolVertexBuffer = static_cast( shadowVertexBuffer ); + m_silTraceIndexBuffer = static_cast( silTraceIndexBuffer ); + m_drawIndexBuffer = static_cast( drawIndexBuffer ); + m_shadowVolIndexBuffer = source.shadowVertexes != NULL + ? static_cast( shadowIndexBuffer ) : -1; + + if ( source.numVerts != 0 && source.numIndexes != 0 ) { + unsigned int *silVertexMapOldToNew = NULL; + if ( source.verts != NULL && m_silTraceVertexBuffer >= 0 && + m_silTraceVertexBuffer < renderModel.m_numVertexBuffers && + m_silTraceIndexBuffer >= 0 && + m_silTraceIndexBuffer < renderModel.m_numIndexBuffers ) { + rvVertexBuffer &vertexBuffer = renderModel.m_vertexBuffers[m_silTraceVertexBuffer]; + rvIndexBuffer &indexBuffer = renderModel.m_indexBuffers[m_silTraceIndexBuffer]; + const int vertexStart = vertexBuffer.GetNumVerticesWritten(); + const int indexStart = indexBuffer.GetNumIndicesWritten(); + silVertexMapOldToNew = static_cast( _alloca16( + source.numVerts * sizeof( unsigned int ) ) ); + const int numSilVertices = vertexBuffer.CopySilTraceData( + silVertexMapOldToNew, vertexStart, source.numVerts, + silRemap, source.verts ); + indexBuffer.CopyRemappedData( indexStart, source.numIndexes, + silVertexMapOldToNew, source.indexes, 0 ); + m_primBatches[0].m_silTraceGeoSpec.m_vertexStart = vertexStart; + m_primBatches[0].m_silTraceGeoSpec.m_vertexCount = numSilVertices; + m_primBatches[0].m_silTraceGeoSpec.m_indexStart = indexStart; + m_primBatches[0].m_silTraceGeoSpec.m_primitiveCount = source.numIndexes / 3; + } + + if ( source.verts != NULL && m_drawVertexBuffer >= 0 && + m_drawVertexBuffer < renderModel.m_numVertexBuffers && + m_drawIndexBuffer >= 0 && + m_drawIndexBuffer < renderModel.m_numIndexBuffers ) { + rvVertexBuffer &vertexBuffer = renderModel.m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &indexBuffer = renderModel.m_indexBuffers[m_drawIndexBuffer]; + const int vertexStart = vertexBuffer.GetNumVerticesWritten(); + const int indexStart = indexBuffer.GetNumIndicesWritten(); + vertexBuffer.CopyData( vertexStart, source.numVerts, source.verts ); + indexBuffer.CopyData( indexStart, source.numIndexes, source.indexes, vertexStart ); + m_primBatches[0].m_drawGeoSpec.m_vertexStart = vertexStart; + m_primBatches[0].m_drawGeoSpec.m_vertexCount = source.numVerts; + m_primBatches[0].m_drawGeoSpec.m_indexStart = indexStart; + m_primBatches[0].m_drawGeoSpec.m_primitiveCount = source.numIndexes / 3; + } + + if ( m_shadowVolVertexBuffer >= 0 && + m_shadowVolVertexBuffer < renderModel.m_numVertexBuffers ) { + rvVertexBuffer &vertexBuffer = renderModel.m_vertexBuffers[m_shadowVolVertexBuffer]; + const int vertexStart = vertexBuffer.GetNumVerticesWritten(); + if ( source.verts != NULL ) { + vertexBuffer.CopyRemappedShadowVolData( vertexStart, source.numVerts, + silRemap, source.verts ); + m_primBatches[0].m_shadowVolGeoSpec.m_vertexStart = vertexStart; + m_primBatches[0].m_shadowVolGeoSpec.m_vertexCount = + m_primBatches[0].m_silTraceGeoSpec.m_vertexCount * 2; + } else if ( m_shadowVolIndexBuffer >= 0 && + m_shadowVolIndexBuffer < renderModel.m_numIndexBuffers ) { + rvIndexBuffer &indexBuffer = renderModel.m_indexBuffers[m_shadowVolIndexBuffer]; + const int indexStart = indexBuffer.GetNumIndicesWritten(); + vertexBuffer.CopyShadowVolData( vertexStart, source.numVerts, + source.shadowVertexes ); + indexBuffer.CopyData( indexStart, source.numIndexes, + source.indexes, vertexStart ); + m_primBatches[0].m_shadowVolGeoSpec.m_vertexStart = vertexStart; + m_primBatches[0].m_shadowVolGeoSpec.m_vertexCount = source.numVerts; + m_primBatches[0].m_shadowVolGeoSpec.m_indexStart = indexStart; + m_primBatches[0].m_shadowVolGeoSpec.m_primitiveCount = source.numIndexes / 3; + m_primBatches[0].m_numShadowPrimitivesNoCaps = + source.numShadowIndexesNoCaps / 3; + m_primBatches[0].m_shadowCapPlaneBits = source.shadowCapPlaneBits; + } + } + + if ( source.verts != NULL && source.numSilEdges != 0 && + silVertexMapOldToNew != NULL ) { + const int edgeStart = renderModel.m_numSilEdgesAdded; + for ( int edge = 0; edge < source.numSilEdges; ++edge ) { + silEdge_t remappedEdge = source.silEdges[edge]; + remappedEdge.v1 = silVertexMapOldToNew[remappedEdge.v1]; + remappedEdge.v2 = silVertexMapOldToNew[remappedEdge.v2]; + renderModel.m_silEdges[renderModel.m_numSilEdgesAdded++] = remappedEdge; + } + m_primBatches[0].m_silEdgeStart = edgeStart; + m_primBatches[0].m_silEdgeCount = source.numSilEdges; + } + CalcGeometryProfile(); + m_bounds = source.bounds; + } +} + +void rvMesh::Init( rvRenderModelMD5R &renderModel, const idMaterial *material, + int numTransforms, rvBlend4DrawVert *drawVertices, int numDrawVertices, + int *drawIndices, int numIndices, silEdge_t *silEdges, int numSilEdges, + int *silRemap, int silTraceVertexBuffer, int drawVertexBuffer, + int shadowVertexBuffer, int silTraceIndexBuffer, int drawIndexBuffer ) { + if ( m_renderModel != NULL ) { + delete[] m_primBatches; + ResetValues(); + } + m_material = material; + m_renderModel = &renderModel; + if ( numTransforms < 1 ) { + numTransforms = 1; + } + m_silTraceVertexBuffer = static_cast( silTraceVertexBuffer ); + m_silTraceIndexBuffer = static_cast( silTraceIndexBuffer ); + m_drawVertexBuffer = static_cast( drawVertexBuffer ); + m_drawIndexBuffer = static_cast( drawIndexBuffer ); + m_shadowVolVertexBuffer = static_cast( shadowVertexBuffer ); + m_shadowVolIndexBuffer = -1; + + int *batchStarts = static_cast( _alloca16( + ( numIndices / 3 + 2 ) * sizeof( int ) ) ); + unsigned char *transformUsed = static_cast( _alloca16( numTransforms ) ); + SIMDProcessor->Memset( transformUsed, 0, numTransforms ); + int numPrimBatches = 1; + int transformPaletteSize = 0; + int index = 0; + batchStarts[0] = 0; + while ( index < numIndices ) { + int addedTransforms[12]; + int numAddedTransforms = 0; + for ( int corner = 0; corner < 3; ++corner ) { + const rvBlend4DrawVert &vertex = drawVertices[drawIndices[index + corner]]; + for ( int weight = 0; weight < 4; ++weight ) { + const int transform = vertex.blendIndex[weight]; + if ( vertex.blendWeight[weight] <= 0.0f || transformUsed[transform] ) { + continue; + } + bool alreadyAdded = false; + for ( int added = 0; added < numAddedTransforms; ++added ) { + if ( addedTransforms[added] == transform ) { + alreadyAdded = true; + break; + } + } + if ( !alreadyAdded ) { + addedTransforms[numAddedTransforms++] = transform; + } + } + } + if ( transformPaletteSize + numAddedTransforms > 25 ) { + batchStarts[numPrimBatches++] = index; + SIMDProcessor->Memset( transformUsed, 0, numTransforms ); + transformPaletteSize = 0; + continue; + } + for ( int added = 0; added < numAddedTransforms; ++added ) { + transformUsed[addedTransforms[added]] = 1; + } + transformPaletteSize += numAddedTransforms; + index += 3; + } + batchStarts[numPrimBatches] = numIndices; + m_numPrimBatches = static_cast( numPrimBatches ); + m_primBatches = new rvPrimBatch[numPrimBatches]; + if ( m_primBatches == NULL ) { + common->FatalError( "Out of memory" ); + return; + } + + int *transformPalette = static_cast( _alloca16( numTransforms * sizeof( int ) ) ); + unsigned int *transformOldToNew = static_cast( _alloca16( + numTransforms * sizeof( unsigned int ) ) ); + unsigned int *drawVertexMapOldToNew = static_cast( _alloca16( + numDrawVertices * sizeof( unsigned int ) ) ); + unsigned int *drawVertexMapNewToOld = static_cast( _alloca16( + numDrawVertices * sizeof( unsigned int ) ) ); + unsigned int *silVertexMapOldToNew = static_cast( _alloca16( + numDrawVertices * sizeof( unsigned int ) ) ); + unsigned int *silVertexMapNewToOld = static_cast( _alloca16( + numDrawVertices * sizeof( unsigned int ) ) ); + unsigned char *silEdgeAdded = static_cast( _alloca16( numSilEdges ) ); + SIMDProcessor->Memset( silEdgeAdded, 0, numSilEdges ); + + for ( int batch = 0; batch < numPrimBatches; ++batch ) { + SIMDProcessor->Memset( transformOldToNew, -1, + numTransforms * sizeof( unsigned int ) ); + SIMDProcessor->Memset( drawVertexMapOldToNew, -1, + numDrawVertices * sizeof( unsigned int ) ); + SIMDProcessor->Memset( silVertexMapOldToNew, -1, + numDrawVertices * sizeof( unsigned int ) ); + int numBatchDrawVertices = 0; + int numBatchSilVertices = 0; + int numBatchTransforms = 0; + for ( int batchIndex = batchStarts[batch]; + batchIndex < batchStarts[batch + 1]; ++batchIndex ) { + const unsigned int oldVertex = drawIndices[batchIndex]; + if ( drawVertexMapOldToNew[oldVertex] != UINT_MAX ) { + continue; + } + const unsigned int oldSilVertex = silRemap[oldVertex]; + if ( silVertexMapOldToNew[oldSilVertex] == UINT_MAX ) { + silVertexMapOldToNew[oldSilVertex] = numBatchSilVertices; + silVertexMapNewToOld[numBatchSilVertices++] = oldSilVertex; + } + silVertexMapOldToNew[oldVertex] = silVertexMapOldToNew[oldSilVertex]; + drawVertexMapOldToNew[oldVertex] = numBatchDrawVertices; + drawVertexMapNewToOld[numBatchDrawVertices++] = oldVertex; + const rvBlend4DrawVert &vertex = drawVertices[oldVertex]; + for ( int weight = 0; weight < 4; ++weight ) { + const int transform = vertex.blendIndex[weight]; + if ( vertex.blendWeight[weight] > 0.0f && + transformOldToNew[transform] == UINT_MAX ) { + transformPalette[numBatchTransforms] = transform; + transformOldToNew[transform] = numBatchTransforms++; + } + } + } + m_primBatches[batch].Init( transformPalette, numBatchTransforms ); + for ( int transform = 0; transform < numTransforms; ++transform ) { + if ( transformOldToNew[transform] == UINT_MAX ) { + transformOldToNew[transform] = 0; + } + } + const int batchIndexStart = batchStarts[batch]; + const int batchIndexCount = batchStarts[batch + 1] - batchIndexStart; + + if ( m_silTraceVertexBuffer >= 0 && + m_silTraceVertexBuffer < renderModel.m_numVertexBuffers && + m_silTraceIndexBuffer >= 0 && + m_silTraceIndexBuffer < renderModel.m_numIndexBuffers ) { + rvVertexBuffer &vertexBuffer = renderModel.m_vertexBuffers[m_silTraceVertexBuffer]; + rvIndexBuffer &indexBuffer = renderModel.m_indexBuffers[m_silTraceIndexBuffer]; + const int vertexStart = vertexBuffer.GetNumVerticesWritten(); + const int indexStart = indexBuffer.GetNumIndicesWritten(); + vertexBuffer.CopyRemappedData( vertexStart, numBatchSilVertices, + silVertexMapNewToOld, transformOldToNew, drawVertices, true ); + indexBuffer.CopyRemappedData( indexStart, batchIndexCount, + silVertexMapOldToNew, drawIndices + batchIndexStart, 0 ); + m_primBatches[batch].m_silTraceGeoSpec.m_vertexStart = vertexStart; + m_primBatches[batch].m_silTraceGeoSpec.m_vertexCount = numBatchSilVertices; + m_primBatches[batch].m_silTraceGeoSpec.m_indexStart = indexStart; + m_primBatches[batch].m_silTraceGeoSpec.m_primitiveCount = batchIndexCount / 3; + } + if ( m_drawVertexBuffer >= 0 && + m_drawVertexBuffer < renderModel.m_numVertexBuffers && + m_drawIndexBuffer >= 0 && + m_drawIndexBuffer < renderModel.m_numIndexBuffers ) { + rvVertexBuffer &vertexBuffer = renderModel.m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &indexBuffer = renderModel.m_indexBuffers[m_drawIndexBuffer]; + const int vertexStart = vertexBuffer.GetNumVerticesWritten(); + const int indexStart = indexBuffer.GetNumIndicesWritten(); + vertexBuffer.CopyRemappedData( vertexStart, numBatchDrawVertices, + drawVertexMapNewToOld, transformOldToNew, drawVertices, false ); + indexBuffer.CopyRemappedData( indexStart, batchIndexCount, + drawVertexMapOldToNew, drawIndices + batchIndexStart, vertexStart ); + m_primBatches[batch].m_drawGeoSpec.m_vertexStart = vertexStart; + m_primBatches[batch].m_drawGeoSpec.m_vertexCount = numBatchDrawVertices; + m_primBatches[batch].m_drawGeoSpec.m_indexStart = indexStart; + m_primBatches[batch].m_drawGeoSpec.m_primitiveCount = batchIndexCount / 3; + } + if ( m_shadowVolVertexBuffer >= 0 && + m_shadowVolVertexBuffer < renderModel.m_numVertexBuffers ) { + rvVertexBuffer &vertexBuffer = renderModel.m_vertexBuffers[m_shadowVolVertexBuffer]; + const int vertexStart = vertexBuffer.GetNumVerticesWritten(); + vertexBuffer.CopyRemappedShadowVolData( vertexStart, numBatchSilVertices, + silVertexMapNewToOld, transformOldToNew, drawVertices ); + m_primBatches[batch].m_shadowVolGeoSpec.m_vertexStart = vertexStart; + m_primBatches[batch].m_shadowVolGeoSpec.m_vertexCount = numBatchSilVertices * 2; + } + if ( numSilEdges != 0 ) { + const int edgeStart = renderModel.m_numSilEdgesAdded; + for ( int edge = 0; edge < numSilEdges; ++edge ) { + if ( silEdgeAdded[edge] ) { + continue; + } + const silEdge_t &sourceEdge = silEdges[edge]; + if ( silVertexMapOldToNew[sourceEdge.v1] == UINT_MAX || + silVertexMapOldToNew[sourceEdge.v2] == UINT_MAX ) { + continue; + } + silEdge_t &destEdge = renderModel.m_silEdges[renderModel.m_numSilEdgesAdded++]; + destEdge.p1 = sourceEdge.p1; + destEdge.p2 = sourceEdge.p2; + destEdge.v1 = silVertexMapOldToNew[sourceEdge.v1]; + destEdge.v2 = silVertexMapOldToNew[sourceEdge.v2]; + silEdgeAdded[edge] = 1; + } + m_primBatches[batch].m_silEdgeStart = edgeStart; + m_primBatches[batch].m_silEdgeCount = + renderModel.m_numSilEdgesAdded - edgeStart; + } + } + CalcGeometryProfile(); +} + +void rvMesh::UpdateSurface( modelSurface_t &surface, const renderEntity_t &entity, + idJointMat *skinSpaceToLocalMats ) { + surface.shader = m_material; + if ( surface.geometry == NULL || + ( surface.geometry->verts == NULL && + ( surface.geometry->silTraceVerts == NULL || + surface.geometry->numVerts != m_numSilTraceVertices || + surface.geometry->numIndexes != m_numSilTraceIndices ) ) ) { + if ( surface.geometry != NULL ) { + R_FreeStaticTriSurf( surface.geometry ); + } + surface.geometry = R_AllocStaticTriSurf(); + } else { + R_FreeStaticTriSurfVertexCaches( surface.geometry ); + } + + srfTriangles_t &tri = *surface.geometry; + tri.deformedSurface = true; + tri.tangentsCalculated = true; + tri.facePlanesCalculated = false; + tri.numVerts = m_numSilTraceVertices; + tri.numIndexes = m_numSilTraceIndices; + tri.primBatchMesh = this; + tri.numSilEdges = m_numSilEdges; + tri.silEdges = &m_renderModel->m_silEdges[m_primBatches[0].m_silEdgeStart]; + if ( tri.silTraceVerts == NULL ) { + R_AllocStaticTriSurfSilTraceVerts( &tri, m_numSilTraceVertices ); + } + R_AllocStaticSkinToModelTransforms( &tri, m_numTransforms ); + tri.bounds.Clear(); + rvVertexBuffer &silTraceVertexBuffer = + m_renderModel->m_vertexBuffers[m_silTraceVertexBuffer]; + int vertexBase = 0; + int transformBase = 0; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + idVec3 batchMin; + idVec3 batchMax; + m_primBatches[batch].TransformVertsMinMax( tri.silTraceVerts + vertexBase, + batchMin, batchMax, silTraceVertexBuffer, skinSpaceToLocalMats, + entity.joints, tri.skinToModelTransforms + transformBase * 16 ); + tri.bounds.AddPoint( batchMin ); + tri.bounds.AddPoint( batchMax ); + vertexBase += m_primBatches[batch].m_silTraceGeoSpec.m_vertexCount; + transformBase += m_primBatches[batch].m_numTransforms; + } + m_drawSetUp = false; +} + +void rvMesh::UpdateSurface( modelSurface_t &surface ) { + surface.shader = m_material; + if ( surface.geometry == NULL || + ( surface.geometry->verts == NULL && + ( surface.geometry->silTraceVerts == NULL || + surface.geometry->numVerts != m_numSilTraceVertices || + surface.geometry->numIndexes != m_numSilTraceIndices ) ) ) { + if ( surface.geometry != NULL ) { + R_FreeStaticTriSurf( surface.geometry ); + } + surface.geometry = R_AllocStaticTriSurf(); + } else { + R_FreeStaticTriSurfVertexCaches( surface.geometry ); + } + + srfTriangles_t &tri = *surface.geometry; + tri.deformedSurface = false; + tri.tangentsCalculated = true; + tri.facePlanesCalculated = false; + tri.primBatchMesh = this; + if ( m_numSilTraceVertices != 0 ) { + tri.numVerts = m_numSilTraceVertices; + tri.numIndexes = m_numSilTraceIndices; + tri.numSilEdges = m_numSilEdges; + tri.silEdges = &m_renderModel->m_silEdges[m_primBatches[0].m_silEdgeStart]; + tri.silTraceVerts = m_renderModel->m_vertexBuffers[m_silTraceVertexBuffer] + .GetSilTraceVertexArray( m_primBatches[0].m_silTraceGeoSpec.m_vertexStart ); + if ( !m_bounds.IsCleared() ) { + tri.bounds = m_bounds; + } else { + SIMDProcessor->MinMax( tri.bounds[0], tri.bounds[1], + tri.silTraceVerts, m_numSilTraceVertices ); + } + } else { + int numVerts = 0; + int numIndexes = 0; + int numIndexesNoCaps = 0; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + numVerts += m_primBatches[batch].m_shadowVolGeoSpec.m_vertexCount; + numIndexes += m_primBatches[batch].m_shadowVolGeoSpec.m_primitiveCount * 3; + numIndexesNoCaps += m_primBatches[batch].m_numShadowPrimitivesNoCaps * 3; + } + tri.numVerts = numVerts; + tri.numIndexes = numIndexes; + tri.numShadowIndexesNoCaps = numIndexesNoCaps; + tri.numShadowIndexesNoFrontCaps = numIndexes; + tri.shadowCapPlaneBits = m_primBatches[0].m_shadowCapPlaneBits; + tri.bounds = m_bounds; + } +} + +void rvMesh::ResetValues() { + m_bounds.Clear(); + m_renderModel = NULL; + m_material = NULL; + m_nextInLOD = NULL; + m_primBatches = NULL; + m_numPrimBatches = 0; + m_levelOfDetail = -1; + m_surfaceNum = -1; + m_meshIdentifier = 0; + m_silTraceVertexBuffer = -1; + m_silTraceIndexBuffer = -1; + m_drawVertexBuffer = -1; + m_drawIndexBuffer = -1; + m_shadowVolVertexBuffer = -1; + m_shadowVolIndexBuffer = -1; + m_numSilTraceVertices = 0; + m_numSilTraceIndices = 0; + m_numSilTracePrimitives = 0; + m_numSilEdges = 0; + m_numDrawVertices = 0; + m_numDrawIndices = 0; + m_numDrawPrimitives = 0; + m_numTransforms = 0; + m_drawSetUp = false; +} + +int rvMesh::FlipOutsideBackFaces( srfCullInfo_t &cullInfo ) { + unsigned char *facing = cullInfo.facing; + const unsigned char *cullBits = cullInfo.cullBits; + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + int numRemaining = 0; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + numRemaining += m_primBatches[i].FlipOutsideBackFaces( + facing, cullBits, indexBuffer ); + facing += m_primBatches[i].m_silTraceGeoSpec.m_primitiveCount; + cullBits += m_primBatches[i].m_silTraceGeoSpec.m_vertexCount; + } + return numRemaining; +} + +void rvMesh::CalcGeometryProfile() { + m_numSilTraceVertices = 0; + m_numSilTraceIndices = 0; + m_numSilTracePrimitives = 0; + m_numSilEdges = 0; + m_numDrawVertices = 0; + m_numDrawIndices = 0; + m_numDrawPrimitives = 0; + m_numTransforms = 0; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + const rvPrimBatch &batch = m_primBatches[i]; + m_numSilTraceVertices += batch.m_silTraceGeoSpec.m_vertexCount; + m_numSilTraceIndices += batch.m_silTraceGeoSpec.m_primitiveCount * 3; + m_numSilTracePrimitives += batch.m_silTraceGeoSpec.m_primitiveCount; + m_numSilEdges += batch.m_silEdgeCount; + m_numDrawVertices += batch.m_drawGeoSpec.m_vertexCount; + m_numDrawIndices += batch.m_drawGeoSpec.m_primitiveCount * 3; + m_numDrawPrimitives += batch.m_drawGeoSpec.m_primitiveCount; + m_numTransforms += static_cast( batch.m_numTransforms ); + } +} + +void rvMesh::Draw( const float *skinToModelTransforms, + const rvVertexFormat *vertexComponentsNeeded ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + rvPrimBatch &batch = m_primBatches[i]; + batch.LoadMatrixPaletteIntoVPParams( skinToModelTransforms ); + batch.Draw( vertexBuffer, indexBuffer, vertexComponentsNeeded ); + skinToModelTransforms += batch.m_numTransforms * 16; + } + m_drawSetUp = false; +} + +void rvMesh::Draw( const float *skinToModelTransforms, int *indices, int, + const rvVertexFormat *vertexComponentsNeeded ) { + int *batchIndices = indices + m_numPrimBatches; + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + rvPrimBatch &batch = m_primBatches[i]; + batch.LoadMatrixPaletteIntoVPParams( skinToModelTransforms ); + batch.Draw( vertexBuffer, batchIndices, indices[i], vertexComponentsNeeded ); + skinToModelTransforms += batch.m_numTransforms * 16; + batchIndices += indices[i]; + } + m_drawSetUp = false; +} + +void rvMesh::DrawShadowVolume( const float *skinToModelTransforms, int *indices, + bool drawCaps, const rvVertexFormat *vertexComponentsNeeded ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_shadowVolVertexBuffer]; + if ( indices == NULL ) { + if ( m_shadowVolIndexBuffer < 0 ) { + return; + } + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_shadowVolIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + rvPrimBatch &batch = m_primBatches[i]; + batch.LoadMatrixPaletteIntoVPParams( skinToModelTransforms ); + batch.DrawShadowVolume( vertexBuffer, indexBuffer, drawCaps, vertexComponentsNeeded ); + skinToModelTransforms += batch.m_numTransforms * 16; + } + m_drawSetUp = false; + return; + } + + int *batchIndices = indices + m_numPrimBatches * 2; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + const int numIndices = indices[i * 2 + ( drawCaps ? 1 : 0 )]; + rvPrimBatch &batch = m_primBatches[i]; + if ( numIndices > 0 ) { + batch.LoadMatrixPaletteIntoVPParams( skinToModelTransforms ); + batch.DrawShadowVolume( vertexBuffer, batchIndices, numIndices, + vertexComponentsNeeded ); + } + skinToModelTransforms += batch.m_numTransforms * 16; + batchIndices += indices[i * 2 + 1]; + } + m_drawSetUp = false; +} + +void rvMesh::DeriveTriPlanes( idPlane *planes, + const rvSilTraceVertT *silTraceVerts ) { + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + m_primBatches[i].DeriveTriPlanes( planes, silTraceVerts, indexBuffer ); + silTraceVerts += m_primBatches[i].m_silTraceGeoSpec.m_vertexCount; + planes += m_primBatches[i].m_silTraceGeoSpec.m_primitiveCount; + } +} + +bool rvMesh::PreciseCullSurface( idBounds &ndcBounds, + const rvSilTraceVertT *silTraceVerts, const idVec3 &localView, + const float *modelMatrix ) { + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + if ( m_primBatches[i].PreciseCullSurface( ndcBounds, silTraceVerts, + localView, modelMatrix, indexBuffer ) ) { + return true; + } + silTraceVerts += m_primBatches[i].m_silTraceGeoSpec.m_vertexCount; + } + return false; +} + +int rvMesh::CreateSilShadowVolTris( int primBatch, int *shadowIndices, + const unsigned char *facing ) { + return m_primBatches[primBatch].CreateSilShadowVolTris( + shadowIndices, facing, m_renderModel->m_silEdges ); +} + +int rvMesh::CreateFrontBackShadowVolTris( int primBatch, int *shadowIndices, + const unsigned char *facing ) { + return m_primBatches[primBatch].CreateFrontBackShadowVolTris( + shadowIndices, facing, m_renderModel->m_indexBuffers[m_silTraceIndexBuffer] ); +} + +int rvMesh::GetNearestJoint( int silTraceIndex0, int silTraceIndex1, + int silTraceIndex2 ) const { + for ( int i = 0; i < m_numPrimBatches; ++i ) { + const primBatchGeoSpec_s &geo = m_primBatches[i].m_silTraceGeoSpec; + if ( silTraceIndex0 >= geo.m_indexStart && + silTraceIndex0 < geo.m_indexStart + geo.m_primitiveCount * 3 ) { + return m_primBatches[i].GetNearestJoint( silTraceIndex0, + silTraceIndex1, silTraceIndex2, + m_renderModel->m_vertexBuffers[m_silTraceVertexBuffer], + m_renderModel->m_indexBuffers[m_silTraceIndexBuffer] ); + } + } + return 0; +} + +void rvMesh::LocalTrace( localTrace_t &hit, int &testPlanes, int &testEdges, + int &intersections, const idVec3 &start, const idVec3 &end, + const unsigned char *cullBits, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts, float radius, + const idMaterial *material ) { + rvIndexBuffer &silTraceIndexBuffer = + m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + rvIndexBuffer &drawIndexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + testPlanes = 0; + testEdges = 0; + intersections = 0; + int lastIntersectBatch = -1; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + int batchIntersections = 0; + m_primBatches[i].LocalTrace( hit, testPlanes, testEdges, batchIntersections, + start, end, cullBits, facePlanes, silTraceVerts, radius, + silTraceIndexBuffer, drawIndexBuffer ); + if ( batchIntersections != 0 ) { + lastIntersectBatch = i; + intersections += batchIntersections; + } + const primBatchGeoSpec_s &geo = m_primBatches[i].m_silTraceGeoSpec; + cullBits += geo.m_vertexCount; + silTraceVerts += geo.m_vertexCount; + facePlanes += geo.m_primitiveCount; + } + if ( intersections != 0 ) { + hit.materialType = m_primBatches[lastIntersectBatch].GetMaterialType( + material, hit, m_renderModel->m_vertexBuffers[m_drawVertexBuffer] ); + } +} + +void rvMesh::CreateLightTris( srfTriangles_t &newTri, int &backFaced, + int &distanceCulled, srfCullInfo_t &cullInfo, + const rvSilTraceVertT *silTraceVerts, bool includeBackFaces ) { + rvIndexBuffer &silTraceIndexBuffer = + m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + rvIndexBuffer &drawIndexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + R_AllocStaticTriSurfIndexes( &newTri, m_numSilTraceIndices + m_numPrimBatches ); + newTri.bounds.Clear(); + + int numIndices = 0; + int *batchCounts = newTri.indexes; + int *destIndices = newTri.indexes + m_numPrimBatches; + const unsigned char *facing = cullInfo.facing; + const unsigned char *cullBits = cullInfo.cullBits; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + int batchIndexCount = 0; + int batchBackFaced = 0; + int batchDistanceCulled = 0; + idBounds batchBounds; + m_primBatches[batch].CreateLightTris( destIndices, batchIndexCount, + batchBounds, batchBackFaced, batchDistanceCulled, facing, cullBits, + cullInfo.localClipPlanes, silTraceVerts, includeBackFaces, + silTraceIndexBuffer, drawIndexBuffer ); + batchCounts[batch] = batchIndexCount; + numIndices += batchIndexCount; + backFaced += batchBackFaced; + distanceCulled += batchDistanceCulled; + if ( batchIndexCount != 0 ) { + newTri.bounds.AddBounds( batchBounds ); + } + destIndices += batchIndexCount; + facing += m_primBatches[batch].m_silTraceGeoSpec.m_primitiveCount; + cullBits += m_primBatches[batch].m_silTraceGeoSpec.m_vertexCount; + silTraceVerts += m_primBatches[batch].m_silTraceGeoSpec.m_vertexCount; + } + R_ResizeStaticTriSurfIndexes( &newTri, + numIndices != 0 ? numIndices + m_numPrimBatches : 0 ); + newTri.numIndexes = numIndices; +} + +void rvMesh::CreateFrontFaceTris( srfTriangles_t &newTri, int &backFaced, + srfCullInfo_t &cullInfo, const rvSilTraceVertT *silTraceVerts ) { + rvIndexBuffer &silTraceIndexBuffer = + m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + rvIndexBuffer &drawIndexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + R_AllocStaticTriSurfIndexes( &newTri, m_numSilTraceIndices + m_numPrimBatches ); + newTri.bounds.Clear(); + + int numIndices = 0; + int *batchCounts = newTri.indexes; + int *destIndices = newTri.indexes + m_numPrimBatches; + const unsigned char *facing = cullInfo.facing; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + int batchIndexCount = 0; + int batchBackFaced = 0; + idBounds batchBounds; + m_primBatches[batch].CreateFrontFaceTris( destIndices, batchIndexCount, + batchBounds, batchBackFaced, facing, silTraceVerts, + silTraceIndexBuffer, drawIndexBuffer ); + batchCounts[batch] = batchIndexCount; + numIndices += batchIndexCount; + backFaced += batchBackFaced; + if ( batchIndexCount != 0 ) { + newTri.bounds.AddBounds( batchBounds ); + } + destIndices += batchIndexCount; + facing += m_primBatches[batch].m_silTraceGeoSpec.m_primitiveCount; + silTraceVerts += m_primBatches[batch].m_silTraceGeoSpec.m_vertexCount; + } + R_ResizeStaticTriSurfIndexes( &newTri, + numIndices != 0 ? numIndices + m_numPrimBatches : 0 ); + newTri.numIndexes = numIndices; +} + +void rvMesh::CreateOverlayTriangles( overlayVertex_t *overlayVerts, + int &numVerts, int *overlayIndices, int &numIndices, + const idPlane *planes, const rvSilTraceVertT *silTraceVerts ) { + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + int vertexBase = 0; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + const int vertexCount = m_primBatches[i].m_silTraceGeoSpec.m_vertexCount; + unsigned char *cullBits = static_cast( + _alloca16( vertexCount * sizeof( unsigned char ) ) ); + idVec2 *textureCoordinates = static_cast( + _alloca16( vertexCount * sizeof( idVec2 ) ) ); + SIMDProcessor->OverlayPointCull( cullBits, textureCoordinates, + planes, silTraceVerts, vertexCount ); + m_primBatches[i].FindOverlayTriangles( overlayVerts, numVerts, + overlayIndices, numIndices, cullBits, textureCoordinates, + vertexBase, indexBuffer ); + vertexBase += vertexCount; + silTraceVerts += vertexCount; + } +} + +void rvMesh::CreateDecalTriangles( idRenderModelDecal &decalModel, + const decalProjectionInfo_t &localInfo, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts ) { + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + m_primBatches[i].FindDecalTriangles( decalModel, localInfo, + facePlanes, silTraceVerts, indexBuffer ); + const primBatchGeoSpec_s &geo = m_primBatches[i].m_silTraceGeoSpec; + silTraceVerts += geo.m_vertexCount; + if ( facePlanes != NULL ) { + facePlanes += geo.m_primitiveCount; + } + } +} + +void rvMesh::GenerateCollisionPolys( idCollisionModelManagerLocal &modelManager, + idCollisionModelLocal &collisionModel ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + m_primBatches[i].GenerateCollisionPolys( modelManager, collisionModel, + *m_material, vertexBuffer, indexBuffer ); + } +} + +void rvMesh::CopyTriangles( idDrawVert *destDrawVerts, int *destIndices, + const rvSilTraceVertT *silTraceVerts ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &drawIndexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + rvIndexBuffer &silTraceIndexBuffer = + m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + int destBase = 0; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + m_primBatches[i].CopyTriangles( destDrawVerts, destIndices, + vertexBuffer, drawIndexBuffer, silTraceVerts, silTraceIndexBuffer, + destBase ); + destBase += m_primBatches[i].m_drawGeoSpec.m_vertexCount; + destDrawVerts += m_primBatches[i].m_drawGeoSpec.m_vertexCount; + destIndices += m_primBatches[i].m_drawGeoSpec.m_primitiveCount * 3; + silTraceVerts += m_primBatches[i].m_silTraceGeoSpec.m_vertexCount; + } +} + +void rvMesh::CopyTriangles( idDrawVert *destDrawVerts, int *destIndices, + int destBase ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + m_primBatches[i].CopyDrawVertices( destDrawVerts, vertexBuffer ); + m_primBatches[i].CopyDrawIndices( destIndices, indexBuffer, destBase ); + destBase += m_primBatches[i].m_drawGeoSpec.m_vertexCount; + destDrawVerts += m_primBatches[i].m_drawGeoSpec.m_vertexCount; + destIndices += m_primBatches[i].m_drawGeoSpec.m_primitiveCount * 3; + } +} + +void rvMesh::TransformTriangles( idDrawVert *destDrawVerts, int *destIndices, + const idMat4 &transform, int colorShift, unsigned char *colorAdd, + int destBase ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + m_primBatches[i].TransformDrawVertices( destDrawVerts, vertexBuffer, + transform, colorShift, colorAdd ); + m_primBatches[i].CopyDrawIndices( destIndices, indexBuffer, destBase ); + destBase += m_primBatches[i].m_drawGeoSpec.m_vertexCount; + destDrawVerts += m_primBatches[i].m_drawGeoSpec.m_vertexCount; + destIndices += m_primBatches[i].m_drawGeoSpec.m_primitiveCount * 3; + } +} + +void rvMesh::TubeDeform( idDrawVert *destDrawVerts, int *destIndices, + const idVec3 &localView, const rvSilTraceVertT *silTraceVerts ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &drawIndexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + rvIndexBuffer &silTraceIndexBuffer = + m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + for ( int i = 0; i < m_numPrimBatches; ++i ) { + m_primBatches[i].TubeDeform( destDrawVerts, destIndices, localView, + vertexBuffer, drawIndexBuffer, silTraceVerts, silTraceIndexBuffer ); + destDrawVerts += m_primBatches[i].m_drawGeoSpec.m_vertexCount; + destIndices += m_primBatches[i].m_drawGeoSpec.m_primitiveCount * 3; + silTraceVerts += m_primBatches[i].m_silTraceGeoSpec.m_vertexCount; + } +} + +const rvVertexFormat *rvMesh::GetDrawVertexBufferFormat() const { + if ( m_drawVertexBuffer < 0 ) { + return NULL; + } + return &m_renderModel->m_vertexBuffers[m_drawVertexBuffer].GetFormat(); +} + +const rvVertexFormat *rvMesh::GetShadowVolVertexBufferFormat() const { + if ( m_shadowVolVertexBuffer < 0 ) { + return NULL; + } + return &m_renderModel->m_vertexBuffers[m_shadowVolVertexBuffer].GetFormat(); +} + +void rvMesh::PlaneForSurface( idPlane &destPlane, + const rvSilTraceVertT *silTraceVerts ) { + m_primBatches[0].PlaneForSurface( destPlane, silTraceVerts, + m_renderModel->m_indexBuffers[m_silTraceIndexBuffer] ); +} + +void rvMesh::SetupForDrawRender( const rvVertexFormat *vertexComponentsNeeded ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + vertexBuffer.SetupForRender( 0, + vertexComponentsNeeded != NULL ? *vertexComponentsNeeded : vertexBuffer.GetFormat() ); + if ( indexBuffer.IsVideoMemory() ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, indexBuffer.GetBufferId() ); + } + m_drawSetUp = true; +} + +void rvMesh::SetupForShadowVolRender( const rvVertexFormat *vertexComponentsNeeded ) { + rvVertexBuffer &vertexBuffer = m_renderModel->m_vertexBuffers[m_shadowVolVertexBuffer]; + vertexBuffer.SetupForRender( 0, + vertexComponentsNeeded != NULL ? *vertexComponentsNeeded : vertexBuffer.GetFormat() ); + if ( m_shadowVolIndexBuffer != -1 ) { + rvIndexBuffer &indexBuffer = m_renderModel->m_indexBuffers[m_shadowVolIndexBuffer]; + if ( indexBuffer.IsVideoMemory() ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, indexBuffer.GetBufferId() ); + } + } +} + +void rvMesh::ScreenRectFromDrawSurf( idScreenRect &destRect, + const float *modelMatrix, float *viewMatrix, float *projectionMatrix, + const idScreenRect &viewport, const rvSilTraceVertT *silTraceVerts ) const { + for ( int vertex = 0; vertex < m_numSilTraceVertices; ++vertex ) { + idVec3 global; + R_LocalPointToGlobal( modelMatrix, silTraceVerts[vertex].xyzw.ToVec3(), global ); + + idPlane view; + idPlane clip; + for ( int component = 0; component < 4; ++component ) { + view[component] = global[0] * viewMatrix[component + 0 * 4] + + global[1] * viewMatrix[component + 1 * 4] + + global[2] * viewMatrix[component + 2 * 4] + + viewMatrix[component + 3 * 4]; + } + for ( int component = 0; component < 4; ++component ) { + clip[component] = view[0] * projectionMatrix[component + 0 * 4] + + view[1] * projectionMatrix[component + 1 * 4] + + view[2] * projectionMatrix[component + 2 * 4] + + view[3] * projectionMatrix[component + 3 * 4]; + } + const idVec3 ndc( clip[0] / clip[3], clip[1] / clip[3], + ( clip[2] + clip[3] ) / ( 2.0f * clip[3] ) ); + const float x = ( viewport.x2 - viewport.x1 ) * ( ndc.x + 1.0f ) * 0.5f; + const float y = viewport.y2 - + ( viewport.y2 - viewport.y1 ) * ( ndc.y + 1.0f ) * 0.5f; + destRect.AddPoint( x, y ); + } +} + +void rvMesh::Write( idFile &outFile, const char *prepend ) { + idStr indent = prepend; + indent += "\t"; + outFile.WriteFloatString( "%sMesh\n%s{\n", prepend, prepend ); + if ( m_levelOfDetail != -1 ) { + outFile.WriteFloatString( "%sLevelOfDetail %d\n", indent.c_str(), m_levelOfDetail ); + } + if ( m_material != NULL ) { + outFile.WriteFloatString( "%sMaterial \"%s\"\n", indent.c_str(), m_material->GetName() ); + } + if ( m_levelOfDetail >= 0 ) { + outFile.WriteFloatString( "%sLevelOfDetail %d\n", indent.c_str(), m_levelOfDetail ); + } + if ( m_silTraceVertexBuffer >= 0 || m_silTraceIndexBuffer >= 0 ) { + outFile.WriteFloatString( "%sSilTraceBuffers %d %d\n", indent.c_str(), + m_silTraceVertexBuffer, m_silTraceIndexBuffer ); + } + if ( m_drawVertexBuffer >= 0 || m_drawIndexBuffer >= 0 ) { + outFile.WriteFloatString( "%sDrawBuffers %d %d\n", indent.c_str(), + m_drawVertexBuffer, m_drawIndexBuffer ); + } + if ( m_shadowVolVertexBuffer >= 0 || m_shadowVolIndexBuffer >= 0 ) { + outFile.WriteFloatString( "%sShadowVolumeBuffers %d %d\n", indent.c_str(), + m_shadowVolVertexBuffer, m_shadowVolIndexBuffer ); + } + outFile.WriteFloatString( "%sPrimBatch[ %d ]\n%s{\n", indent.c_str(), + m_numPrimBatches, indent.c_str() ); + idStr batchIndent = indent; + batchIndent += "\t"; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + m_primBatches[batch].Write( outFile, batchIndent.c_str() ); + } + outFile.WriteFloatString( "%s}\n", indent.c_str() ); + if ( !m_bounds.IsCleared() ) { + outFile.WriteFloatString( "%sBounds %f %f %f %f %f %f\n", indent.c_str(), + m_bounds[0].x, m_bounds[0].y, m_bounds[0].z, + m_bounds[1].x, m_bounds[1].y, m_bounds[1].z ); + } + outFile.WriteFloatString( "%s}\n", prepend ); +} + +void rvMesh::SurfaceToTextureAxis( idVec3 &origin, idVec3 axis[3], + const rvSilTraceVertT *silTraceVerts ) { + rvVertexBuffer &drawVertexBuffer = m_renderModel->m_vertexBuffers[m_drawVertexBuffer]; + rvIndexBuffer &drawIndexBuffer = m_renderModel->m_indexBuffers[m_drawIndexBuffer]; + rvIndexBuffer &silTraceIndexBuffer = m_renderModel->m_indexBuffers[m_silTraceIndexBuffer]; + + float bounds[2][2] = { + { 999999.0f, 999999.0f }, + { -999999.0f, -999999.0f } + }; + for ( int batch = 0; batch < m_numPrimBatches; ++batch ) { + m_primBatches[batch].GetTextureBounds( bounds, drawVertexBuffer ); + } + const float boundsOrigin[2] = { + idMath::Floor( ( bounds[0][0] + bounds[1][0] ) * 0.5f ), + idMath::Floor( ( bounds[0][1] + bounds[1][1] ) * 0.5f ) + }; + + idDrawVert a; + idDrawVert b; + idDrawVert c; + m_primBatches[0].GetTriangle( a, b, c, 0, drawVertexBuffer, + drawIndexBuffer, silTraceVerts, silTraceIndexBuffer ); + const idVec3 d0 = b.xyz - a.xyz; + const idVec3 d1 = c.xyz - a.xyz; + const float ds0 = b.st.x - a.st.x; + const float dt0 = b.st.y - a.st.y; + const float ds1 = c.st.x - a.st.x; + const float dt1 = c.st.y - a.st.y; + const float area = ds0 * dt1 - dt0 * ds1; + if ( area == 0.0f ) { + axis[0].Zero(); + axis[1].Zero(); + axis[2].Zero(); + return; + } + const float invArea = 1.0f / area; + axis[0] = ( d0 * dt1 - d1 * dt0 ) * invArea; + axis[1] = ( d1 * ds0 - d0 * ds1 ) * invArea; + idPlane plane; + plane.FromPoints( a.xyz, b.xyz, c.xyz ); + axis[2] = plane.Normal(); + origin = a.xyz + ( boundsOrigin[0] - a.st.x ) * axis[0]; + origin += ( boundsOrigin[1] - a.st.y ) * axis[1]; +} diff --git a/src/renderer/rvMesh.h b/src/renderer/rvMesh.h new file mode 100644 index 0000000..aff549c --- /dev/null +++ b/src/renderer/rvMesh.h @@ -0,0 +1,149 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software + +#ifndef __RENDERER_RVMESH_H__ +#define __RENDERER_RVMESH_H__ + +#include "rvPrimBatch.h" +#include "rvRenderModelMD5R.h" + +class rvMesh { +friend class rvRenderModelMD5R; +friend class rvModelParticle; +friend srfTriangles_t *R_CreateVertexBufferTurboShadowVolume( + const idRenderEntityLocal *ent, const srfTriangles_t *tri, + const idRenderLightLocal *light, srfCullInfo_t &cullInfo ); +public: + rvMesh(); + ~rvMesh(); + void Init( rvRenderModelMD5R &renderModel, Lexer &lexer ); + void Init( rvRenderModelMD5R &renderModel, modelSurface_t &sourceSurface, + int *silRemap, int silTraceVertexBuffer, int drawVertexBuffer, + int shadowVertexBuffer, int silTraceIndexBuffer, int drawIndexBuffer, + int shadowIndexBuffer ); + void Init( rvRenderModelMD5R &renderModel, const idMaterial *material, + int numTransforms, rvBlend4DrawVert *drawVertices, int numDrawVertices, + int *drawIndices, int numIndices, silEdge_t *silEdges, int numSilEdges, + int *silRemap, int silTraceVertexBuffer, int drawVertexBuffer, + int shadowVertexBuffer, int silTraceIndexBuffer, int drawIndexBuffer ); + void UpdateSurface( modelSurface_t &surface ); + + int FlipOutsideBackFaces( srfCullInfo_t &cullInfo ); + void CalcGeometryProfile(); + void Draw( const float *skinToModelTransforms, + const rvVertexFormat *vertexComponentsNeeded ); + void Draw( const float *skinToModelTransforms, int *indices, int numIndices, + const rvVertexFormat *vertexComponentsNeeded ); + void DrawShadowVolume( const float *skinToModelTransforms, int *indices, + bool drawCaps, const rvVertexFormat *vertexComponentsNeeded ); + void DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *silTraceVerts ); + bool PreciseCullSurface( idBounds &ndcBounds, + const rvSilTraceVertT *silTraceVerts, const idVec3 &localView, + const float *modelMatrix ); + int CreateSilShadowVolTris( int primBatch, int *shadowIndices, + const unsigned char *facing ); + int CreateFrontBackShadowVolTris( int primBatch, int *shadowIndices, + const unsigned char *facing ); + ID_INLINE int GetNumSilTracePrimitives( int primBatch ) const { + return m_primBatches[primBatch].m_silTraceGeoSpec.m_primitiveCount; + } + int GetNearestJoint( int silTraceIndex0, int silTraceIndex1, + int silTraceIndex2 ) const; + void LocalTrace( localTrace_t &hit, int &testPlanes, int &testEdges, + int &intersections, const idVec3 &start, const idVec3 &end, + const unsigned char *cullBits, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts, float radius, + const idMaterial *material ); + void CreateLightTris( srfTriangles_t &newTri, int &backFaced, + int &distanceCulled, srfCullInfo_t &cullInfo, + const rvSilTraceVertT *silTraceVerts, bool includeBackFaces ); + void CreateFrontFaceTris( srfTriangles_t &newTri, int &backFaced, + srfCullInfo_t &cullInfo, const rvSilTraceVertT *silTraceVerts ); + void CreateOverlayTriangles( overlayVertex_t *overlayVerts, int &numVerts, + int *overlayIndices, int &numIndices, const idPlane *planes, + const rvSilTraceVertT *silTraceVerts ); + void CreateDecalTriangles( idRenderModelDecal &decalModel, + const decalProjectionInfo_t &localInfo, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts ); + void GenerateCollisionPolys( idCollisionModelManagerLocal &modelManager, + idCollisionModelLocal &collisionModel ); + void CopyTriangles( idDrawVert *destDrawVerts, int *destIndices, + const rvSilTraceVertT *silTraceVerts ); + void CopyTriangles( idDrawVert *destDrawVerts, int *destIndices, + int destBase ); + void TransformTriangles( idDrawVert *destDrawVerts, int *destIndices, + const idMat4 &transform, int colorShift, unsigned char *colorAdd, + int destBase ); + void TubeDeform( idDrawVert *destDrawVerts, int *destIndices, + const idVec3 &localView, const rvSilTraceVertT *silTraceVerts ); + const rvVertexFormat *GetDrawVertexBufferFormat() const; + const rvVertexFormat *GetShadowVolVertexBufferFormat() const; + void PlaneForSurface( idPlane &destPlane, + const rvSilTraceVertT *silTraceVerts ); + void SetupForDrawRender( const rvVertexFormat *vertexComponentsNeeded ); + void SetupForShadowVolRender( const rvVertexFormat *vertexComponentsNeeded ); + void ScreenRectFromDrawSurf( idScreenRect &destRect, + const float *modelMatrix, float *viewMatrix, float *projectionMatrix, + const idScreenRect &viewport, const rvSilTraceVertT *silTraceVerts ) const; + void Write( idFile &outFile, const char *prepend ); + void SurfaceToTextureAxis( idVec3 &origin, idVec3 axis[3], + const rvSilTraceVertT *silTraceVerts ); + +protected: + void ResetValues(); + void UpdateSurface( modelSurface_t &surface, const renderEntity_t &entity, + idJointMat *skinSpaceToLocalMats ); + + idBounds m_bounds; + rvRenderModelMD5R *m_renderModel; + const idMaterial * m_material; + rvMesh * m_nextInLOD; + rvPrimBatch * m_primBatches; + int m_meshIdentifier; + int m_numSilTraceVertices; + int m_numSilTraceIndices; + int m_numSilTracePrimitives; + int m_numSilEdges; +public: + int m_numDrawVertices; + int m_numDrawIndices; +protected: + int m_numDrawPrimitives; + short m_silTraceVertexBuffer; + short m_silTraceIndexBuffer; + short m_drawVertexBuffer; + short m_drawIndexBuffer; + short m_shadowVolVertexBuffer; + short m_shadowVolIndexBuffer; + short m_levelOfDetail; + short m_numTransforms; + short m_numPrimBatches; + short m_surfaceNum; +public: + bool m_drawSetUp; +}; + +#endif diff --git a/src/renderer/rvPrimBatch.cpp b/src/renderer/rvPrimBatch.cpp new file mode 100644 index 0000000..303ca5a --- /dev/null +++ b/src/renderer/rvPrimBatch.cpp @@ -0,0 +1,1429 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvPrimBatch.h" +#include "../cm/CollisionModel_local.h" + +/* +=============================================================================== + + Quake 4 MD5R primitive batches + + The class layout, signatures, and compilation-unit ownership come from + rvPrimBatch.obj in quake4.pdb. These bodies follow the address-matched + retail executable. + +=============================================================================== +*/ + +static void RV_ClearGeoSpec( primBatchGeoSpec_s &geoSpec ) { + geoSpec.m_vertexStart = 0; + geoSpec.m_vertexCount = 0; + geoSpec.m_indexStart = -1; + geoSpec.m_primitiveCount = 0; +} + +rvPrimBatch::rvPrimBatch() { + m_transformPalette = NULL; + m_numTransforms = 0; + m_silEdgeStart = 0; + m_silEdgeCount = 0; + RV_ClearGeoSpec( m_silTraceGeoSpec ); + RV_ClearGeoSpec( m_drawGeoSpec ); + RV_ClearGeoSpec( m_shadowVolGeoSpec ); + m_numShadowPrimitivesNoCaps = 0; + m_shadowCapPlaneBits = 0; +} + +rvPrimBatch::~rvPrimBatch() { +} + +void rvPrimBatch::Shutdown() { + if ( m_transformPalette != NULL ) { + Mem_Free( m_transformPalette ); + } + m_transformPalette = NULL; + m_numTransforms = 0; + m_silEdgeStart = 0; + m_silEdgeCount = 0; + RV_ClearGeoSpec( m_silTraceGeoSpec ); + RV_ClearGeoSpec( m_drawGeoSpec ); + RV_ClearGeoSpec( m_shadowVolGeoSpec ); + m_numShadowPrimitivesNoCaps = 0; + m_shadowCapPlaneBits = 0; +} + +void rvPrimBatch::Init( int *transformPalette, int numTransforms ) { + if ( m_numTransforms != 0 ) { + Shutdown(); + } + if ( numTransforms > 25 ) { + common->FatalError( "Primitive batch initialization failed - too many transforms per batch" ); + return; + } + if ( transformPalette != NULL ) { + m_transformPalette = static_cast( Mem_Alloc( sizeof( int ) * numTransforms, MA_RENDER ) ); + if ( m_transformPalette == NULL ) { + common->FatalError( "Out of memory" ); + return; + } + for ( int i = 0; i < numTransforms; ++i ) { + m_transformPalette[i] = transformPalette[i]; + } + } + m_numTransforms = numTransforms; +} + +void rvPrimBatch::Init( Lexer &lexer ) { + if ( m_numTransforms != 0 ) { + Shutdown(); + } + lexer.ExpectTokenString( "{" ); + idToken token; + lexer.ReadToken( &token ); + if ( token.Icmp( "Transform" ) != 0 ) { + m_numTransforms = 1; + } else { + lexer.ExpectTokenString( "[" ); + m_numTransforms = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + if ( m_numTransforms > 25 ) { + lexer.Error( "Primitive batch initialization failed - too many transforms per batch" ); + return; + } + if ( m_numTransforms == 1 ) { + const int transform = lexer.ParseInt(); + if ( transform != 0 ) { + m_transformPalette = static_cast( Mem_Alloc( sizeof( int ), MA_RENDER ) ); + if ( m_transformPalette == NULL ) { + lexer.Error( "Out of memory error" ); + return; + } + m_transformPalette[0] = transform; + } + } else if ( m_numTransforms > 0 ) { + m_transformPalette = static_cast( Mem_Alloc( + m_numTransforms * sizeof( int ), MA_RENDER ) ); + if ( m_transformPalette == NULL ) { + lexer.Error( "Out of memory" ); + return; + } + for ( int i = 0; i < m_numTransforms; ++i ) { + m_transformPalette[i] = lexer.ParseInt(); + } + } + lexer.ExpectTokenString( "}" ); + lexer.ReadToken( &token ); + } + + if ( token.Icmp( "SilTraceIndexedTriList" ) == 0 ) { + m_silTraceGeoSpec.m_vertexStart = lexer.ParseInt(); + m_silTraceGeoSpec.m_vertexCount = lexer.ParseInt(); + m_silTraceGeoSpec.m_indexStart = lexer.ParseInt(); + m_silTraceGeoSpec.m_primitiveCount = lexer.ParseInt(); + lexer.ReadToken( &token ); + } + if ( token.Icmp( "DrawIndexedTriList" ) == 0 ) { + m_drawGeoSpec.m_vertexStart = lexer.ParseInt(); + m_drawGeoSpec.m_vertexCount = lexer.ParseInt(); + m_drawGeoSpec.m_indexStart = lexer.ParseInt(); + m_drawGeoSpec.m_primitiveCount = lexer.ParseInt(); + lexer.ReadToken( &token ); + } + if ( token.Icmp( "ShadowVerts" ) == 0 ) { + m_shadowVolGeoSpec.m_vertexStart = lexer.ParseInt(); + m_shadowVolGeoSpec.m_vertexCount = m_silTraceGeoSpec.m_vertexCount * 2; + if ( m_silTraceGeoSpec.m_vertexCount == 0 ) { + lexer.Error( "Primitive batch initialization failed - expected SilTraceIndexedTriList statement" ); + } + lexer.ReadToken( &token ); + } else if ( token.Icmp( "ShadowIndexedTriList" ) == 0 ) { + m_shadowVolGeoSpec.m_vertexStart = lexer.ParseInt(); + m_shadowVolGeoSpec.m_vertexCount = lexer.ParseInt(); + m_shadowVolGeoSpec.m_indexStart = lexer.ParseInt(); + m_shadowVolGeoSpec.m_primitiveCount = lexer.ParseInt(); + m_numShadowPrimitivesNoCaps = lexer.ParseInt(); + m_shadowCapPlaneBits = lexer.ParseInt(); + lexer.ReadToken( &token ); + } + if ( token.Icmp( "SilhouetteEdge" ) == 0 ) { + m_silEdgeStart = lexer.ParseInt(); + m_silEdgeCount = lexer.ParseInt(); + lexer.ReadToken( &token ); + } + if ( token.Icmp( "}" ) != 0 ) { + lexer.Error( "Expected }." ); + } +} + +void rvPrimBatch::LoadMatrixPaletteIntoVPParams( const float *skinToModelTransforms ) { + if ( m_transformPalette == NULL ) { + return; + } + for ( int i = 0; i < m_numTransforms; ++i ) { + const float *transform = skinToModelTransforms + i * 16; + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i * 3 + 0, transform + 0 ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i * 3 + 1, transform + 4 ); + qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, i * 3 + 2, transform + 8 ); + } +} + +int rvPrimBatch::FlipOutsideBackFaces( unsigned char *facing, + const unsigned char *cullBits, rvIndexBuffer &indexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *indexMemory = NULL; + indexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, indexMemory ); + + int numRemaining = 0; + if ( indexBuffer.Is16Bit() ) { + const unsigned short *indices = static_cast( indexMemory ); + for ( int triangle = 0; triangle < m_silTraceGeoSpec.m_primitiveCount; ++triangle ) { + if ( facing[triangle] == 0 ) { + if ( ( cullBits[indices[0]] & cullBits[indices[1]] & cullBits[indices[2]] ) != 0 ) { + facing[triangle] = 1; + } else { + ++numRemaining; + } + } + indices += 3; + } + } else { + const unsigned int *indices = static_cast( indexMemory ); + for ( int triangle = 0; triangle < m_silTraceGeoSpec.m_primitiveCount; ++triangle ) { + if ( facing[triangle] == 0 ) { + if ( ( cullBits[indices[0]] & cullBits[indices[1]] & cullBits[indices[2]] ) != 0 ) { + facing[triangle] = 1; + } else { + ++numRemaining; + } + } + indices += 3; + } + } + + indexBuffer.Unlock(); + return numRemaining; +} + +int rvPrimBatch::GetNearestJoint( int silTraceIndex0, int, int, + rvVertexBuffer &silTraceVertexBuffer, rvIndexBuffer &silTraceIndexBuffer ) const { + void *indexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, + m_silTraceGeoSpec.m_primitiveCount * 3, rvIndexBufferLockFlagRead, indexMemory ); + const int localIndex = silTraceIndex0 - m_silTraceGeoSpec.m_indexStart; + const int vertexIndex = silTraceIndexBuffer.Is16Bit() + ? static_cast( indexMemory )[localIndex] + : static_cast( indexMemory )[localIndex]; + + unsigned char *blendIndices = NULL; + int stride = 0; + silTraceVertexBuffer.LockBlendIndex( vertexIndex + m_silTraceGeoSpec.m_vertexStart, + 1, rvVertexBufferLockFlagRead, blendIndices, stride ); + const int nearestJoint = blendIndices[0]; + silTraceVertexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); + return nearestJoint; +} + +void rvPrimBatch::GetTextureBounds( float bounds[2][2], + rvVertexBuffer &drawVertexBuffer ) { + unsigned char *textureCoordinates = NULL; + int stride = 0; + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinates, stride ); + for ( int vertex = 0; vertex < m_drawGeoSpec.m_vertexCount; ++vertex ) { + const float *st = reinterpret_cast( textureCoordinates ); + if ( st[0] < bounds[0][0] ) { + bounds[0][0] = st[0]; + } + if ( st[0] > bounds[1][0] ) { + bounds[1][0] = st[0]; + } + if ( st[1] < bounds[0][1] ) { + bounds[0][1] = st[1]; + } + if ( st[1] > bounds[1][1] ) { + bounds[1][1] = st[1]; + } + textureCoordinates += stride; + } + drawVertexBuffer.Unlock(); +} + +void rvPrimBatch::CopyDrawVertices( idDrawVert *destDrawVerts, + rvVertexBuffer &drawVertexBuffer ) { + unsigned char *position = NULL; + unsigned char *normal = NULL; + unsigned char *tangent = NULL; + unsigned char *binormal = NULL; + unsigned char *textureCoordinate = NULL; + unsigned char *diffuseColor = NULL; + int positionStride = 0; + int normalStride = 0; + int tangentStride = 0; + int binormalStride = 0; + int textureCoordinateStride = 0; + int diffuseColorStride = 0; + + drawVertexBuffer.LockPosition( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + position, positionStride ); + drawVertexBuffer.LockNormal( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + normal, normalStride ); + drawVertexBuffer.LockBinormal( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + binormal, binormalStride ); + drawVertexBuffer.LockTangent( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + tangent, tangentStride ); + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinate, textureCoordinateStride ); + drawVertexBuffer.LockDiffuseColor( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + diffuseColor, diffuseColorStride ); + + for ( int i = 0; i < m_drawGeoSpec.m_vertexCount; ++i ) { + const float *positionValues = reinterpret_cast( position ); + const float *normalValues = reinterpret_cast( normal ); + const float *tangentValues = reinterpret_cast( tangent ); + const float *binormalValues = reinterpret_cast( binormal ); + const float *textureValues = reinterpret_cast( textureCoordinate ); + idDrawVert &dest = destDrawVerts[i]; + dest.xyz.Set( positionValues[0], positionValues[1], positionValues[2] ); + dest.normal.Set( normalValues[0], normalValues[1], normalValues[2] ); + dest.tangents[0].Set( tangentValues[0], tangentValues[1], tangentValues[2] ); + dest.tangents[1].Set( binormalValues[0], binormalValues[1], binormalValues[2] ); + dest.st.Set( textureValues[0], textureValues[1] ); + dest.color[0] = diffuseColor[0]; + dest.color[1] = diffuseColor[1]; + dest.color[2] = diffuseColor[2]; + dest.color[3] = diffuseColor[3]; + position += positionStride; + normal += normalStride; + tangent += tangentStride; + binormal += binormalStride; + textureCoordinate += textureCoordinateStride; + diffuseColor += diffuseColorStride; + } + drawVertexBuffer.Unlock(); +} + +void rvPrimBatch::CopyDrawIndices( int *destIndices, + rvIndexBuffer &drawIndexBuffer, int destBase ) { + const int indexAdjustment = destBase - m_drawGeoSpec.m_vertexStart; + const int numIndices = m_drawGeoSpec.m_primitiveCount * 3; + void *indexMemory = NULL; + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, indexMemory ); + if ( drawIndexBuffer.Is16Bit() ) { + const unsigned short *indices = static_cast( indexMemory ); + for ( int i = 0; i < numIndices; ++i ) { + destIndices[i] = indexAdjustment + indices[i]; + } + } else { + const unsigned int *indices = static_cast( indexMemory ); + for ( int i = 0; i < numIndices; ++i ) { + destIndices[i] = indexAdjustment + indices[i]; + } + } + drawIndexBuffer.Unlock(); +} + +void rvPrimBatch::CopySilTraceVertices( rvVertexBuffer &silTraceVertexBuffer, + rvIndexBuffer &silTraceIndexBuffer, rvVertexBuffer &drawVertexBuffer, + rvIndexBuffer &drawIndexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *silTraceIndexMemory = NULL; + void *drawIndexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, silTraceIndexMemory ); + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, drawIndexMemory ); + + unsigned int *copyMapping = static_cast( _alloca16( + m_drawGeoSpec.m_vertexCount * sizeof( unsigned int ) ) ); + SIMDProcessor->Memset( copyMapping, 0, + m_drawGeoSpec.m_vertexCount * sizeof( unsigned int ) ); + for ( int index = 0; index < numIndices; ++index ) { + const unsigned int silTraceIndex = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index] + : static_cast( silTraceIndexMemory )[index]; + const unsigned int drawIndex = drawIndexBuffer.Is16Bit() + ? static_cast( drawIndexMemory )[index] + : static_cast( drawIndexMemory )[index]; + copyMapping[silTraceIndex] = drawIndex - m_drawGeoSpec.m_vertexStart; + } + drawIndexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); + + unsigned char *silTraceVertices = NULL; + unsigned char *drawVertices = NULL; + int silTraceStride = 0; + int drawStride = 0; + silTraceVertexBuffer.LockPosition( m_silTraceGeoSpec.m_vertexStart, + m_silTraceGeoSpec.m_vertexCount, rvVertexBufferLockFlagWrite, + silTraceVertices, silTraceStride ); + drawVertexBuffer.LockPosition( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + drawVertices, drawStride ); + + const rvVertexFormat &silTraceFormat = silTraceVertexBuffer.GetFormat(); + const rvVertexFormat &drawFormat = drawVertexBuffer.GetFormat(); + float positionTail[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + rvVertexBuffer::ComponentCopy( + silTraceVertices + silTraceFormat.GetByteOffset( RV_VERTEX_COMPONENT_POSITION ), + silTraceStride, silTraceFormat.GetDataType( RV_VERTEX_COMPONENT_POSITION ), + silTraceFormat.GetPositionDimension(), + drawVertices + drawFormat.GetByteOffset( RV_VERTEX_COMPONENT_POSITION ), + drawStride, drawFormat.GetDataType( RV_VERTEX_COMPONENT_POSITION ), + drawFormat.GetPositionDimension(), m_silTraceGeoSpec.m_vertexCount, + copyMapping, positionTail, false ); + + if ( ( drawFormat.GetFlags() & rvVertexFormatFlagBlendIndex ) != 0 ) { + rvVertexBuffer::ComponentCopy( + silTraceVertices + silTraceFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_INDEX ), + silTraceStride, silTraceFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_INDEX ), 1, + drawVertices + drawFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_INDEX ), + drawStride, drawFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_INDEX ), 1, + m_silTraceGeoSpec.m_vertexCount, copyMapping, NULL, false ); + } + if ( ( drawFormat.GetFlags() & rvVertexFormatFlagBlendWeight ) != 0 ) { + rvVertexBuffer::ComponentCopy( + silTraceVertices + silTraceFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + silTraceStride, silTraceFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + silTraceFormat.GetBlendWeightDimension(), + drawVertices + drawFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + drawStride, drawFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + drawFormat.GetBlendWeightDimension(), m_silTraceGeoSpec.m_vertexCount, + copyMapping, NULL, true ); + } + drawVertexBuffer.Unlock(); + silTraceVertexBuffer.Unlock(); +} + +void rvPrimBatch::GetTriangle( idDrawVert &a, idDrawVert &b, idDrawVert &c, + int triangleOffset, rvVertexBuffer &drawVertexBuffer, + rvIndexBuffer &drawIndexBuffer, const rvSilTraceVertT *silTraceVerts, + rvIndexBuffer &silTraceIndexBuffer ) { + void *silTraceIndexMemory = NULL; + void *drawIndexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart + triangleOffset * 3, + 3, rvIndexBufferLockFlagRead, silTraceIndexMemory ); + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart + triangleOffset * 3, + 3, rvIndexBufferLockFlagRead, drawIndexMemory ); + unsigned char *textureCoordinates = NULL; + int textureCoordinateStride = 0; + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinates, textureCoordinateStride ); + + int silTraceIndices[3]; + int drawIndices[3]; + for ( int corner = 0; corner < 3; ++corner ) { + silTraceIndices[corner] = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[corner] + : static_cast( silTraceIndexMemory )[corner]; + drawIndices[corner] = drawIndexBuffer.Is16Bit() + ? static_cast( drawIndexMemory )[corner] + : static_cast( drawIndexMemory )[corner]; + } + + idDrawVert *vertices[3] = { &a, &b, &c }; + for ( int corner = 0; corner < 3; ++corner ) { + vertices[corner]->xyz = silTraceVerts[silTraceIndices[corner]].xyzw.ToVec3(); + const float *st = reinterpret_cast( textureCoordinates + + ( drawIndices[corner] - m_drawGeoSpec.m_vertexStart ) * textureCoordinateStride ); + vertices[corner]->st.Set( st[0], st[1] ); + } + + drawVertexBuffer.Unlock(); + drawIndexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); +} + +void rvPrimBatch::CopyTriangles( idDrawVert *destDrawVerts, int *destIndices, + rvVertexBuffer &drawVertexBuffer, rvIndexBuffer &drawIndexBuffer, + const rvSilTraceVertT *silTraceVerts, rvIndexBuffer &silTraceIndexBuffer, + int destBase ) { + const int numIndices = m_drawGeoSpec.m_primitiveCount * 3; + void *silTraceIndexMemory = NULL; + void *drawIndexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, silTraceIndexMemory ); + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, drawIndexMemory ); + unsigned char *textureCoordinates = NULL; + unsigned char *diffuseColors = NULL; + int textureCoordinateStride = 0; + int diffuseColorStride = 0; + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinates, textureCoordinateStride ); + drawVertexBuffer.LockDiffuseColor( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + diffuseColors, diffuseColorStride ); + + for ( int i = 0; i < numIndices; ++i ) { + const int silTraceIndex = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[i] + : static_cast( silTraceIndexMemory )[i]; + const int drawIndex = drawIndexBuffer.Is16Bit() + ? static_cast( drawIndexMemory )[i] + : static_cast( drawIndexMemory )[i]; + const int localDrawIndex = drawIndex - m_drawGeoSpec.m_vertexStart; + destIndices[i] = destBase + localDrawIndex; + idDrawVert &dest = destDrawVerts[localDrawIndex]; + dest.xyz = silTraceVerts[silTraceIndex].xyzw.ToVec3(); + const float *st = reinterpret_cast( textureCoordinates + + localDrawIndex * textureCoordinateStride ); + dest.st.Set( st[0], st[1] ); + const unsigned char *color = diffuseColors + localDrawIndex * diffuseColorStride; + dest.color[0] = color[0]; + dest.color[1] = color[1]; + dest.color[2] = color[2]; + dest.color[3] = color[3]; + } + + drawVertexBuffer.Unlock(); + drawIndexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); +} + +void rvPrimBatch::TransformDrawVertices( idDrawVert *destDrawVerts, + rvVertexBuffer &drawVertexBuffer, const idMat4 &transform, + int colorShift, unsigned char *colorAdd ) { + unsigned char *position = NULL; + unsigned char *normal = NULL; + unsigned char *tangent = NULL; + unsigned char *binormal = NULL; + unsigned char *textureCoordinate = NULL; + unsigned char *diffuseColor = NULL; + int positionStride = 0; + int normalStride = 0; + int tangentStride = 0; + int binormalStride = 0; + int textureCoordinateStride = 0; + int diffuseColorStride = 0; + drawVertexBuffer.LockPosition( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, position, positionStride ); + drawVertexBuffer.LockNormal( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, normal, normalStride ); + drawVertexBuffer.LockBinormal( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, binormal, binormalStride ); + drawVertexBuffer.LockTangent( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, tangent, tangentStride ); + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinate, textureCoordinateStride ); + drawVertexBuffer.LockDiffuseColor( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + diffuseColor, diffuseColorStride ); + + for ( int i = 0; i < m_drawGeoSpec.m_vertexCount; ++i ) { + const float *positionValues = reinterpret_cast( position ); + const float *normalValues = reinterpret_cast( normal ); + const float *tangentValues = reinterpret_cast( tangent ); + const float *binormalValues = reinterpret_cast( binormal ); + const float *textureValues = reinterpret_cast( textureCoordinate ); + idDrawVert &dest = destDrawVerts[i]; + dest.xyz = transform * idVec3( positionValues[0], positionValues[1], positionValues[2] ); + dest.normal.Set( normalValues[0], normalValues[1], normalValues[2] ); + dest.tangents[0].Set( tangentValues[0], tangentValues[1], tangentValues[2] ); + dest.tangents[1].Set( binormalValues[0], binormalValues[1], binormalValues[2] ); + dest.st.Set( textureValues[0], textureValues[1] ); + for ( int component = 0; component < 4; ++component ) { + dest.color[component] = static_cast( + colorAdd[component] + ( diffuseColor[component] >> colorShift ) ); + } + position += positionStride; + normal += normalStride; + tangent += tangentStride; + binormal += binormalStride; + textureCoordinate += textureCoordinateStride; + diffuseColor += diffuseColorStride; + } + drawVertexBuffer.Unlock(); +} + +void rvPrimBatch::TubeDeform( idDrawVert *destDrawVerts, int *destIndices, + const idVec3 &localView, rvVertexBuffer &drawVertexBuffer, + rvIndexBuffer &drawIndexBuffer, const rvSilTraceVertT *silTraceVerts, + rvIndexBuffer &silTraceIndexBuffer ) { + static const int edgeVertices[6][2] = { + { 0, 1 }, { 1, 2 }, { 2, 0 }, + { 3, 4 }, { 4, 5 }, { 5, 3 } + }; + + const int drawIndexCount = m_drawGeoSpec.m_primitiveCount * 3; + const int silTraceIndexCount = m_silTraceGeoSpec.m_primitiveCount * 3; + void *drawIndexMemory = NULL; + void *silTraceIndexMemory = NULL; + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, drawIndexCount, + rvIndexBufferLockFlagRead, drawIndexMemory ); + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, silTraceIndexCount, + rvIndexBufferLockFlagRead, silTraceIndexMemory ); + + unsigned char *textureCoordinateMemory = NULL; + unsigned char *diffuseColorMemory = NULL; + int textureCoordinateStride = 0; + int diffuseColorStride = 0; + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinateMemory, textureCoordinateStride ); + drawVertexBuffer.LockDiffuseColor( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + diffuseColorMemory, diffuseColorStride ); + + for ( int index = 0; index < drawIndexCount; ++index ) { + const int sourceIndex = drawIndexBuffer.Is16Bit() + ? static_cast( drawIndexMemory )[index] + : static_cast( drawIndexMemory )[index]; + destIndices[index] = sourceIndex - m_drawGeoSpec.m_vertexStart; + } + + for ( int vertex = 0; vertex < m_drawGeoSpec.m_vertexCount; vertex += 4 ) { + float lengths[2] = { 999999.0f, 999999.0f }; + int shortestEdges[2] = { 0, 0 }; + for ( int edge = 0; edge < 6; ++edge ) { + const int index0 = vertex + edgeVertices[edge][0]; + const int index1 = vertex + edgeVertices[edge][1]; + const int silVertex0 = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index0] + : static_cast( silTraceIndexMemory )[index0]; + const int silVertex1 = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index1] + : static_cast( silTraceIndexMemory )[index1]; + const float length = ( silTraceVerts[silVertex0].xyzw.ToVec3() - + silTraceVerts[silVertex1].xyzw.ToVec3() ).Length(); + if ( length < lengths[0] ) { + shortestEdges[1] = shortestEdges[0]; + lengths[1] = lengths[0]; + shortestEdges[0] = edge; + lengths[0] = length; + } else if ( length < lengths[1] ) { + shortestEdges[1] = edge; + lengths[1] = length; + } + } + + idVec3 midpoints[2]; + for ( int side = 0; side < 2; ++side ) { + const int edge = shortestEdges[side]; + const int index0 = vertex + edgeVertices[edge][0]; + const int index1 = vertex + edgeVertices[edge][1]; + const int silVertex0 = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index0] + : static_cast( silTraceIndexMemory )[index0]; + const int silVertex1 = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index1] + : static_cast( silTraceIndexMemory )[index1]; + midpoints[side] = 0.5f * ( silTraceVerts[silVertex0].xyzw.ToVec3() + + silTraceVerts[silVertex1].xyzw.ToVec3() ); + } + + const idVec3 major = midpoints[1] - midpoints[0]; + for ( int side = 0; side < 2; ++side ) { + const int edge = shortestEdges[side]; + const int drawVertex0 = destIndices[vertex + edgeVertices[edge][0]]; + const int drawVertex1 = destIndices[vertex + edgeVertices[edge][1]]; + idDrawVert &out0 = destDrawVerts[drawVertex0]; + idDrawVert &out1 = destDrawVerts[drawVertex1]; + + const unsigned char *color0 = diffuseColorMemory + + drawVertex0 * diffuseColorStride; + const unsigned char *color1 = diffuseColorMemory + + drawVertex1 * diffuseColorStride; + memcpy( out0.color, color0, sizeof( out0.color ) ); + memcpy( out1.color, color1, sizeof( out1.color ) ); + const float *st0 = reinterpret_cast( + textureCoordinateMemory + drawVertex0 * textureCoordinateStride ); + const float *st1 = reinterpret_cast( + textureCoordinateMemory + drawVertex1 * textureCoordinateStride ); + out0.st.Set( st0[0], st0[1] ); + out1.st.Set( st1[0], st1[1] ); + + idVec3 minor; + minor.Cross( major, midpoints[side] - localView ); + minor.Normalize(); + const float halfLength = 0.5f * lengths[side]; + if ( side != 0 ) { + out0.xyz = midpoints[side] - halfLength * minor; + out1.xyz = midpoints[side] + halfLength * minor; + } else { + out0.xyz = midpoints[side] + halfLength * minor; + out1.xyz = midpoints[side] - halfLength * minor; + } + } + } + + drawVertexBuffer.Unlock(); + drawIndexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); +} + +void rvPrimBatch::CopyShadowVertices( rvVertexBuffer &shadowVertexBuffer, + rvVertexBuffer &silTraceVertexBuffer ) { + unsigned char *shadowVertices = NULL; + unsigned char *silTraceVertices = NULL; + int shadowStride = 0; + int silTraceStride = 0; + shadowVertexBuffer.LockPosition( m_shadowVolGeoSpec.m_vertexStart, + m_shadowVolGeoSpec.m_vertexCount, rvVertexBufferLockFlagWrite, + shadowVertices, shadowStride ); + silTraceVertexBuffer.LockPosition( m_silTraceGeoSpec.m_vertexStart, + m_silTraceGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + silTraceVertices, silTraceStride ); + + const rvVertexFormat &shadowFormat = shadowVertexBuffer.GetFormat(); + const rvVertexFormat &silTraceFormat = silTraceVertexBuffer.GetFormat(); + float infiniteTail[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float finiteTail[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + rvVertexBuffer::ComponentCopy( + shadowVertices + shadowFormat.GetByteOffset( RV_VERTEX_COMPONENT_POSITION ), + shadowStride * 2, shadowFormat.GetDataType( RV_VERTEX_COMPONENT_POSITION ), + shadowFormat.GetPositionDimension(), + silTraceVertices + silTraceFormat.GetByteOffset( RV_VERTEX_COMPONENT_POSITION ), + silTraceStride, silTraceFormat.GetDataType( RV_VERTEX_COMPONENT_POSITION ), 3, + m_silTraceGeoSpec.m_vertexCount, NULL, finiteTail, false ); + rvVertexBuffer::ComponentCopy( + shadowVertices + shadowStride + shadowFormat.GetByteOffset( RV_VERTEX_COMPONENT_POSITION ), + shadowStride * 2, shadowFormat.GetDataType( RV_VERTEX_COMPONENT_POSITION ), + shadowFormat.GetPositionDimension(), + silTraceVertices + silTraceFormat.GetByteOffset( RV_VERTEX_COMPONENT_POSITION ), + silTraceStride, silTraceFormat.GetDataType( RV_VERTEX_COMPONENT_POSITION ), 3, + m_silTraceGeoSpec.m_vertexCount, NULL, infiniteTail, false ); + + if ( ( silTraceFormat.GetFlags() & rvVertexFormatFlagBlendIndex ) != 0 ) { + for ( int pair = 0; pair < 2; ++pair ) { + rvVertexBuffer::ComponentCopy( + shadowVertices + pair * shadowStride + + shadowFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_INDEX ), + shadowStride * 2, + shadowFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_INDEX ), 1, + silTraceVertices + silTraceFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_INDEX ), + silTraceStride, + silTraceFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_INDEX ), 1, + m_silTraceGeoSpec.m_vertexCount, NULL, NULL, false ); + } + } + if ( ( silTraceFormat.GetFlags() & rvVertexFormatFlagBlendWeight ) != 0 ) { + for ( int pair = 0; pair < 2; ++pair ) { + rvVertexBuffer::ComponentCopy( + shadowVertices + pair * shadowStride + + shadowFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + shadowStride * 2, + shadowFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + shadowFormat.GetBlendWeightDimension(), + silTraceVertices + silTraceFormat.GetByteOffset( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + silTraceStride, + silTraceFormat.GetDataType( RV_VERTEX_COMPONENT_BLEND_WEIGHT ), + silTraceFormat.GetBlendWeightDimension(), + m_silTraceGeoSpec.m_vertexCount, NULL, NULL, true ); + } + } + silTraceVertexBuffer.Unlock(); + shadowVertexBuffer.Unlock(); +} + +void rvPrimBatch::Write( idFile &outFile, const char *prepend ) { + idStr indent = prepend; + indent += "\t"; + outFile.WriteFloatString( "%sPrimBatch\n%s{\n", prepend, prepend ); + if ( m_transformPalette != NULL ) { + outFile.WriteFloatString( "%sTransform[ %d ]\n%s{\n", + indent.c_str(), m_numTransforms, indent.c_str() ); + for ( int i = 0; i < m_numTransforms; ++i ) { + outFile.WriteFloatString( "%s\t%d\n", indent.c_str(), m_transformPalette[i] ); + } + outFile.WriteFloatString( "%s}\n", indent.c_str() ); + } + if ( m_silTraceGeoSpec.m_primitiveCount > 0 ) { + outFile.WriteFloatString( "%sSilTraceIndexedTriList %d %d %d %d\n", + indent.c_str(), m_silTraceGeoSpec.m_vertexStart, + m_silTraceGeoSpec.m_vertexCount, m_silTraceGeoSpec.m_indexStart, + m_silTraceGeoSpec.m_primitiveCount ); + } + if ( m_drawGeoSpec.m_primitiveCount > 0 ) { + outFile.WriteFloatString( "%sDrawIndexedTriList %d %d %d %d\n", + indent.c_str(), m_drawGeoSpec.m_vertexStart, m_drawGeoSpec.m_vertexCount, + m_drawGeoSpec.m_indexStart, m_drawGeoSpec.m_primitiveCount ); + } + if ( m_shadowVolGeoSpec.m_primitiveCount > 0 ) { + outFile.WriteFloatString( "%sShadowIndexedTriList %d %d %d %d %d %d\n", + indent.c_str(), m_shadowVolGeoSpec.m_vertexStart, + m_shadowVolGeoSpec.m_vertexCount, m_shadowVolGeoSpec.m_indexStart, + m_shadowVolGeoSpec.m_primitiveCount, m_numShadowPrimitivesNoCaps, + m_shadowCapPlaneBits ); + } else if ( m_shadowVolGeoSpec.m_vertexCount > 0 ) { + outFile.WriteFloatString( "%sShadowVerts %d\n", indent.c_str(), + m_shadowVolGeoSpec.m_vertexStart ); + } + if ( m_silEdgeCount > 0 ) { + outFile.WriteFloatString( "%sSilhouetteEdge %d %d\n", indent.c_str(), + m_silEdgeStart, m_silEdgeCount ); + } + outFile.WriteFloatString( "%s}\n", prepend ); +} + +void rvPrimBatch::Draw( rvVertexBuffer &, rvIndexBuffer &indexBuffer, + const rvVertexFormat * ) { + const int numIndices = m_drawGeoSpec.m_primitiveCount * 3; + backEnd.pc.c_drawElements++; + backEnd.pc.c_drawIndexes += numIndices; + backEnd.pc.c_drawVertexes += m_drawGeoSpec.m_vertexCount; + + const GLenum indexType = indexBuffer.Is16Bit() ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; + const int indexSize = indexBuffer.Is16Bit() ? sizeof( unsigned short ) : sizeof( unsigned int ); + qglDrawElements( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : numIndices, + indexType, reinterpret_cast( m_drawGeoSpec.m_indexStart * indexSize ) ); + backEnd.pc.c_drawRefIndexes += numIndices; +} + +void rvPrimBatch::Draw( rvVertexBuffer &, int *indices, int numIndices, + const rvVertexFormat * ) { + if ( numIndices == 0 ) { + return; + } + backEnd.pc.c_drawElements++; + backEnd.pc.c_drawIndexes += numIndices; + backEnd.pc.c_drawVertexes += m_drawGeoSpec.m_vertexCount; + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + qglDrawElements( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : numIndices, + GL_UNSIGNED_INT, indices ); + backEnd.pc.c_drawRefIndexes += numIndices; +} + +void rvPrimBatch::DrawShadowVolume( rvVertexBuffer &, int *indices, int numIndices, + const rvVertexFormat * ) { + if ( numIndices == 0 ) { + return; + } + backEnd.pc.c_shadowElements++; + backEnd.pc.c_shadowIndexes += numIndices; + backEnd.pc.c_shadowVertexes += m_shadowVolGeoSpec.m_vertexCount; + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + qglDrawElements( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : numIndices, + GL_UNSIGNED_INT, indices ); +} + +void rvPrimBatch::DrawShadowVolume( rvVertexBuffer &, rvIndexBuffer &indexBuffer, + bool drawCaps, const rvVertexFormat * ) { + const int primitiveCount = drawCaps ? m_shadowVolGeoSpec.m_primitiveCount + : m_numShadowPrimitivesNoCaps; + const int numIndices = primitiveCount * 3; + if ( numIndices <= 0 ) { + return; + } + backEnd.pc.c_shadowElements++; + backEnd.pc.c_shadowIndexes += numIndices; + backEnd.pc.c_shadowVertexes += m_shadowVolGeoSpec.m_vertexCount; + + const GLenum indexType = indexBuffer.Is16Bit() ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; + const int indexSize = indexBuffer.Is16Bit() ? sizeof( unsigned short ) : sizeof( unsigned int ); + qglDrawElements( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : numIndices, + indexType, reinterpret_cast( m_shadowVolGeoSpec.m_indexStart * indexSize ) ); +} + +int rvPrimBatch::CreateSilShadowVolTris( int *shadowIndices, + const unsigned char *facing, silEdge_t *silEdges ) { + int *out = shadowIndices; + const int vertexStart = m_shadowVolGeoSpec.m_vertexStart; + const silEdge_t *edge = silEdges + m_silEdgeStart; + const silEdge_t *end = edge + m_silEdgeCount; + + for ( ; edge < end; ++edge ) { + const int f1 = facing[edge->p1]; + const int f2 = facing[edge->p2]; + if ( ( f1 ^ f2 ) == 0 ) { + continue; + } + + const int v1 = edge->v1 * 2; + const int v2 = edge->v2 * 2; + out[0] = vertexStart + v1; + out[1] = vertexStart + ( f1 ^ v2 ); + out[2] = vertexStart + ( f2 ^ v2 ); + out[3] = vertexStart + ( f2 ^ v1 ); + out[4] = vertexStart + ( f1 ^ v1 ); + out[5] = vertexStart + ( v2 ^ 1 ); + out += 6; + } + + return static_cast( out - shadowIndices ); +} + +int rvPrimBatch::CreateFrontBackShadowVolTris( int *shadowIndices, + const unsigned char *facing, rvIndexBuffer &silTraceIndexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *indexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, indexMemory ); + + int *out = shadowIndices; + const int vertexStart = m_shadowVolGeoSpec.m_vertexStart; + if ( silTraceIndexBuffer.Is16Bit() ) { + const unsigned short *indices = static_cast( indexMemory ); + for ( int triangle = 0; triangle < m_silTraceGeoSpec.m_primitiveCount; ++triangle ) { + if ( facing[triangle] != 0 ) { + indices += 3; + continue; + } + const int v0 = indices[0] * 2; + const int v1 = indices[1] * 2; + const int v2 = indices[2] * 2; + out[0] = vertexStart + v2; + out[1] = vertexStart + v1; + out[2] = vertexStart + v0; + out[3] = vertexStart + ( v0 ^ 1 ); + out[4] = vertexStart + ( v1 ^ 1 ); + out[5] = vertexStart + ( v2 ^ 1 ); + out += 6; + indices += 3; + } + } else { + const unsigned int *indices = static_cast( indexMemory ); + for ( int triangle = 0; triangle < m_silTraceGeoSpec.m_primitiveCount; ++triangle ) { + if ( facing[triangle] != 0 ) { + indices += 3; + continue; + } + const int v0 = indices[0] * 2; + const int v1 = indices[1] * 2; + const int v2 = indices[2] * 2; + out[0] = vertexStart + v2; + out[1] = vertexStart + v1; + out[2] = vertexStart + v0; + out[3] = vertexStart + ( v0 ^ 1 ); + out[4] = vertexStart + ( v1 ^ 1 ); + out[5] = vertexStart + ( v2 ^ 1 ); + out += 6; + indices += 3; + } + } + + silTraceIndexBuffer.Unlock(); + return static_cast( out - shadowIndices ); +} + +void rvPrimBatch::TransformVertsMinMax( rvSilTraceVertT *destSilTraceVerts, + idVec3 &boundsMin, idVec3 &boundsMax, rvVertexBuffer &silTraceVertexBuffer, + idJointMat *skinSpaceToLocalMats, idJointMat *localToModelMats, + float *skinToModelTransforms ) { + SIMDProcessor->JointMat_MultiplyMats( skinToModelTransforms, + skinSpaceToLocalMats, localToModelMats, m_transformPalette, m_numTransforms ); + + unsigned char *positions = NULL; + int stride = 0; + silTraceVertexBuffer.LockPosition( m_silTraceGeoSpec.m_vertexStart, + m_silTraceGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + positions, stride ); + if ( ( silTraceVertexBuffer.GetFormat().GetFlags() & rvVertexFormatFlagBlendWeight ) != 0 && + silTraceVertexBuffer.GetFormat().GetBlendWeightDimension() == 4 ) { + SIMDProcessor->TransformVertsMinMax4Bone( destSilTraceVerts, boundsMin, boundsMax, + positions, stride, m_silTraceGeoSpec.m_vertexCount, skinToModelTransforms ); + } else { + SIMDProcessor->TransformVertsMinMax1Bone( destSilTraceVerts, boundsMin, boundsMax, + positions, stride, m_silTraceGeoSpec.m_vertexCount, skinToModelTransforms ); + } + silTraceVertexBuffer.Unlock(); +} + +void rvPrimBatch::DeriveTriPlanes( idPlane *planes, + const rvSilTraceVertT *silTraceVerts, rvIndexBuffer &silTraceIndexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *indices = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, indices ); + if ( silTraceIndexBuffer.Is16Bit() ) { + SIMDProcessor->DeriveTriPlanes( planes, silTraceVerts, + m_silTraceGeoSpec.m_vertexCount, + static_cast( indices ), numIndices ); + } else { + SIMDProcessor->DeriveTriPlanes( planes, silTraceVerts, + m_silTraceGeoSpec.m_vertexCount, + static_cast( indices ), numIndices ); + } + silTraceIndexBuffer.Unlock(); +} + +static bool RV_TraceEdgeAcceptsPoint( const idVec3 &a, const idVec3 &b, + const idVec3 &opposite, const idVec3 &point, const idVec3 &normal, + float radiusSqr ) { + const idVec3 dirA = a - point; + const idVec3 dirB = b - point; + const idVec3 cross = dirA.Cross( dirB ); + if ( normal * cross <= 0.0f ) { + return true; + } + if ( radiusSqr <= 0.0f ) { + return false; + } + const idVec3 edge = a - b; + const float edgeLengthSqr = edge.LengthSqr(); + if ( cross.LengthSqr() > edgeLengthSqr * radiusSqr ) { + return false; + } + const float along = edge * dirA; + if ( along < 0.0f ) { + if ( ( a - opposite ) * dirA < 0.0f && dirA.LengthSqr() > radiusSqr ) { + return false; + } + } else if ( along > edgeLengthSqr ) { + if ( ( b - opposite ) * dirB < 0.0f && dirB.LengthSqr() > radiusSqr ) { + return false; + } + } + return true; +} + +void rvPrimBatch::LocalTrace( localTrace_t &hit, int &testPlanes, int &testEdges, + int &intersections, const idVec3 &start, const idVec3 &end, + const unsigned char *cullBits, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts, float radius, + rvIndexBuffer &silTraceIndexBuffer, rvIndexBuffer &drawIndexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *silTraceIndexMemory = NULL; + void *drawIndexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, silTraceIndexMemory ); + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, drawIndexMemory ); + const idVec3 traceDirection = end - start; + const float radiusSqr = radius * radius; + + for ( int index = 0, triangle = 0; index < numIndices; index += 3, ++triangle ) { + int vertexIndices[3]; + for ( int corner = 0; corner < 3; ++corner ) { + vertexIndices[corner] = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index + corner] + : static_cast( silTraceIndexMemory )[index + corner]; + } + const unsigned char triangleOr = cullBits[vertexIndices[0]] | + cullBits[vertexIndices[1]] | cullBits[vertexIndices[2]]; + if ( ( ( triangleOr ^ ( triangleOr >> 4 ) ) & 3 ) != 0 || + ( ( triangleOr ^ ( triangleOr >> 1 ) ) & 4 ) != 0 ) { + continue; + } + ++testPlanes; + const idPlane &plane = facePlanes[triangle]; + const float d1 = plane.Distance( start ); + const float d2 = plane.Distance( end ); + if ( d1 <= d2 || d1 < 0.0f || d2 > 0.0f ) { + continue; + } + const float fraction = d1 / ( d1 - d2 ); + if ( fraction < 0.0f || fraction >= hit.fraction ) { + continue; + } + ++testEdges; + const idVec3 point = start + fraction * traceDirection; + const idVec3 vertices[3] = { + silTraceVerts[vertexIndices[0]].xyzw.ToVec3(), + silTraceVerts[vertexIndices[1]].xyzw.ToVec3(), + silTraceVerts[vertexIndices[2]].xyzw.ToVec3() + }; + if ( !RV_TraceEdgeAcceptsPoint( vertices[0], vertices[1], vertices[2], + point, plane.Normal(), radiusSqr ) || + !RV_TraceEdgeAcceptsPoint( vertices[1], vertices[2], vertices[0], + point, plane.Normal(), radiusSqr ) || + !RV_TraceEdgeAcceptsPoint( vertices[2], vertices[0], vertices[1], + point, plane.Normal(), radiusSqr ) ) { + continue; + } + + ++intersections; + hit.fraction = fraction; + hit.normal = plane.Normal(); + hit.point = point; + for ( int corner = 0; corner < 3; ++corner ) { + hit.indexes[corner] = m_silTraceGeoSpec.m_indexStart + index + corner; + hit.vertices[corner] = vertices[corner]; + const int drawIndex = drawIndexBuffer.Is16Bit() + ? static_cast( drawIndexMemory )[index + corner] + : static_cast( drawIndexMemory )[index + corner]; + hit.drawIndices[corner] = drawIndex - m_drawGeoSpec.m_vertexStart; + } + } + drawIndexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); +} + +const rvDeclMatType *rvPrimBatch::GetMaterialType( const idMaterial *material, + const localTrace_t &hit, rvVertexBuffer &drawVertexBuffer ) { + if ( material->GetMaterialTypeArray() == NULL || + !drawVertexBuffer.IsSystemMemory() || + ( drawVertexBuffer.GetFormat().GetFlags() & + rvVertexFormatFlagTextureCoordinate ) == 0 ) { + return material->GetMaterialType(); + } + + unsigned char *textureCoordinateMemory = NULL; + int textureCoordinateStride = 0; + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinateMemory, textureCoordinateStride ); + idVec2 textureCoordinates[3]; + for ( int corner = 0; corner < 3; ++corner ) { + const float *st = reinterpret_cast( textureCoordinateMemory + + hit.drawIndices[corner] * textureCoordinateStride ); + textureCoordinates[corner].Set( st[0], st[1] ); + } + const float area = idMath::BarycentricTriangleArea( hit.normal, + hit.vertices[0], hit.vertices[1], hit.vertices[2] ); + if ( area == 0.0f ) { + drawVertexBuffer.Unlock(); + return material->GetMaterialType(); + } + idVec2 textureCoordinate; + idMath::BarycentricEvaluate( textureCoordinate, hit.point, hit.normal, + area, hit.vertices, textureCoordinates ); + drawVertexBuffer.Unlock(); + return material->GetMaterialType( textureCoordinate ); +} + +void rvPrimBatch::CreateLightTris( int *destDrawIndices, int &destIndexCount, + idBounds &bounds, int &backFaced, int &distanceCulled, + const unsigned char *facing, const unsigned char *cullBits, + const idPlane *, const rvSilTraceVertT *silTraceVerts, + bool includeBackFaces, rvIndexBuffer &silTraceIndexBuffer, + rvIndexBuffer &drawIndexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *silTraceIndexMemory = NULL; + void *drawIndexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, silTraceIndexMemory ); + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, drawIndexMemory ); + + int *selectedSilTraceIndices = static_cast( _alloca16( numIndices * sizeof( int ) ) ); + destIndexCount = 0; + for ( int triangle = 0; triangle < m_silTraceGeoSpec.m_primitiveCount; ++triangle ) { + if ( !includeBackFaces && facing[triangle] == 0 ) { + ++backFaced; + continue; + } + int silIndices[3]; + int drawIndices[3]; + for ( int corner = 0; corner < 3; ++corner ) { + const int index = triangle * 3 + corner; + silIndices[corner] = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index] + : static_cast( silTraceIndexMemory )[index]; + drawIndices[corner] = drawIndexBuffer.Is16Bit() + ? static_cast( drawIndexMemory )[index] + : static_cast( drawIndexMemory )[index]; + } + if ( ( cullBits[silIndices[0]] & cullBits[silIndices[1]] & + cullBits[silIndices[2]] ) != 0 ) { + ++distanceCulled; + continue; + } + for ( int corner = 0; corner < 3; ++corner ) { + selectedSilTraceIndices[destIndexCount] = silIndices[corner]; + destDrawIndices[destIndexCount++] = drawIndices[corner]; + } + } + drawIndexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); + SIMDProcessor->MinMax( bounds[0], bounds[1], silTraceVerts, + selectedSilTraceIndices, destIndexCount ); +} + +void rvPrimBatch::CreateFrontFaceTris( int *destDrawIndices, int &destIndexCount, + idBounds &bounds, int &backFaced, const unsigned char *facing, + const rvSilTraceVertT *silTraceVerts, rvIndexBuffer &silTraceIndexBuffer, + rvIndexBuffer &drawIndexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *silTraceIndexMemory = NULL; + void *drawIndexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, silTraceIndexMemory ); + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, drawIndexMemory ); + + int *selectedSilTraceIndices = static_cast( _alloca16( numIndices * sizeof( int ) ) ); + destIndexCount = 0; + for ( int triangle = 0; triangle < m_silTraceGeoSpec.m_primitiveCount; ++triangle ) { + if ( facing[triangle] == 0 ) { + ++backFaced; + continue; + } + for ( int corner = 0; corner < 3; ++corner ) { + const int index = triangle * 3 + corner; + selectedSilTraceIndices[destIndexCount] = silTraceIndexBuffer.Is16Bit() + ? static_cast( silTraceIndexMemory )[index] + : static_cast( silTraceIndexMemory )[index]; + destDrawIndices[destIndexCount++] = drawIndexBuffer.Is16Bit() + ? static_cast( drawIndexMemory )[index] + : static_cast( drawIndexMemory )[index]; + } + } + drawIndexBuffer.Unlock(); + silTraceIndexBuffer.Unlock(); + SIMDProcessor->MinMax( bounds[0], bounds[1], silTraceVerts, + selectedSilTraceIndices, destIndexCount ); +} + +void rvPrimBatch::FindOverlayTriangles( overlayVertex_t *overlayVerts, + int &numVerts, int *overlayIndices, int &numIndices, + const unsigned char *cullBits, const idVec2 *textureCoordinates, + int vertexBase, rvIndexBuffer &silTraceIndexBuffer ) { + int *vertexRemap = static_cast( _alloca16( + m_silTraceGeoSpec.m_vertexCount * sizeof( int ) ) ); + SIMDProcessor->Memset( vertexRemap, -1, + m_silTraceGeoSpec.m_vertexCount * sizeof( int ) ); + + const int indexCount = m_silTraceGeoSpec.m_primitiveCount * 3; + void *indexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, indexCount, + rvIndexBufferLockFlagRead, indexMemory ); + for ( int index = 0; index < indexCount; index += 3 ) { + int triangle[3]; + if ( silTraceIndexBuffer.Is16Bit() ) { + const unsigned short *indices = static_cast( indexMemory ) + index; + triangle[0] = indices[0]; + triangle[1] = indices[1]; + triangle[2] = indices[2]; + } else { + const unsigned int *indices = static_cast( indexMemory ) + index; + triangle[0] = indices[0]; + triangle[1] = indices[1]; + triangle[2] = indices[2]; + } + if ( ( cullBits[triangle[0]] & cullBits[triangle[1]] & cullBits[triangle[2]] ) != 0 ) { + continue; + } + for ( int corner = 0; corner < 3; ++corner ) { + const int vertex = triangle[corner]; + if ( vertexRemap[vertex] == -1 ) { + vertexRemap[vertex] = numVerts; + overlayVerts[numVerts].vertexNum = vertex + vertexBase; + overlayVerts[numVerts].st[0] = textureCoordinates[vertex].x; + overlayVerts[numVerts].st[1] = textureCoordinates[vertex].y; + ++numVerts; + } + overlayIndices[numIndices++] = vertexRemap[vertex]; + } + } + silTraceIndexBuffer.Unlock(); +} + +void rvPrimBatch::FindDecalTriangles( idRenderModelDecal &decalModel, + const decalProjectionInfo_t &localInfo, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts, + rvIndexBuffer &silTraceIndexBuffer ) { + byte *cullBits = static_cast( _alloca16( + m_silTraceGeoSpec.m_vertexCount * sizeof( byte ) ) ); + SIMDProcessor->DecalPointCull( cullBits, localInfo.boundingPlanes, + silTraceVerts, m_silTraceGeoSpec.m_vertexCount ); + + const int indexCount = m_silTraceGeoSpec.m_primitiveCount * 3; + void *indexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, indexCount, + rvIndexBufferLockFlagRead, indexMemory ); + for ( int index = 0, triangle = 0; index < indexCount; + index += 3, ++triangle ) { + int vertexIndices[3]; + for ( int corner = 0; corner < 3; ++corner ) { + vertexIndices[corner] = silTraceIndexBuffer.Is16Bit() + ? static_cast( indexMemory )[index + corner] + : static_cast( indexMemory )[index + corner]; + } + if ( ( cullBits[vertexIndices[0]] & cullBits[vertexIndices[1]] & + cullBits[vertexIndices[2]] ) != 0 ) { + continue; + } + if ( facePlanes != NULL && facePlanes[triangle].Normal() * + localInfo.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 2].Normal() < -0.1f ) { + continue; + } + + idFixedWinding winding; + winding.SetNumPoints( 3 ); + for ( int corner = 0; corner < 3; ++corner ) { + winding[corner] = silTraceVerts[vertexIndices[corner]].xyzw.ToVec3(); + idVec3 texturePosition = winding[corner].ToVec3(); + if ( !localInfo.parallel ) { + const idVec3 direction = texturePosition - localInfo.projectionOrigin; + float scale = 0.0f; + localInfo.boundingPlanes[NUM_DECAL_BOUNDING_PLANES - 1].RayIntersection( + texturePosition, direction, scale ); + texturePosition += scale * direction; + } + winding[corner].s = localInfo.textureAxis[0].Distance( texturePosition ); + winding[corner].t = localInfo.textureAxis[1].Distance( texturePosition ); + } + + const int orBits = cullBits[vertexIndices[0]] | + cullBits[vertexIndices[1]] | cullBits[vertexIndices[2]]; + for ( int plane = 0; plane < NUM_DECAL_BOUNDING_PLANES; ++plane ) { + if ( ( orBits & ( 1 << plane ) ) != 0 && + !winding.ClipInPlace( -localInfo.boundingPlanes[plane], 0.1f ) ) { + break; + } + } + if ( winding.GetNumPoints() != 0 ) { + decalModel.AddDepthFadedWinding( winding, localInfo.material, + localInfo.fadePlanes, localInfo.fadeDepth, localInfo.startTime ); + } + } + silTraceIndexBuffer.Unlock(); +} + +void rvPrimBatch::GenerateCollisionPolys( + idCollisionModelManagerLocal &modelManager, + idCollisionModelLocal &collisionModel, const idMaterial &material, + rvVertexBuffer &drawVertexBuffer, rvIndexBuffer &drawIndexBuffer ) { + const int indexCount = m_drawGeoSpec.m_primitiveCount * 3; + void *indexMemory = NULL; + drawIndexBuffer.Lock( m_drawGeoSpec.m_indexStart, indexCount, + rvIndexBufferLockFlagRead, indexMemory ); + + unsigned char *positionMemory = NULL; + unsigned char *textureCoordinateMemory = NULL; + int positionStride = 0; + int textureCoordinateStride = 0; + drawVertexBuffer.LockTextureCoordinate( 0, m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + textureCoordinateMemory, textureCoordinateStride ); + drawVertexBuffer.LockPosition( m_drawGeoSpec.m_vertexStart, + m_drawGeoSpec.m_vertexCount, rvVertexBufferLockFlagRead, + positionMemory, positionStride ); + + idFixedWinding winding; + for ( int index = 0; index < indexCount; index += 3 ) { + int sourceIndices[3]; + for ( int corner = 0; corner < 3; ++corner ) { + const int sourceIndex = drawIndexBuffer.Is16Bit() + ? static_cast( indexMemory )[index + corner] + : static_cast( indexMemory )[index + corner]; + sourceIndices[corner] = sourceIndex - m_drawGeoSpec.m_vertexStart; + } + + winding.SetNumPoints( 3 ); + for ( int corner = 0; corner < 3; ++corner ) { + const int sourceIndex = sourceIndices[2 - corner]; + const float *position = reinterpret_cast( + positionMemory + sourceIndex * positionStride ); + const float *textureCoordinate = reinterpret_cast( + textureCoordinateMemory + sourceIndex * textureCoordinateStride ); + winding[corner].x = position[0]; + winding[corner].y = position[1]; + winding[corner].z = position[2]; + winding[corner].s = textureCoordinate[0]; + winding[corner].t = textureCoordinate[1]; + } + + idPlane plane; + winding.GetPlane( plane ); + plane = -plane; + modelManager.PolygonFromWinding( &collisionModel, &winding, plane, + &material, 1 ); + } + + drawVertexBuffer.Unlock(); + drawIndexBuffer.Unlock(); +} + +bool rvPrimBatch::PreciseCullSurface( idBounds &ndcBounds, + const rvSilTraceVertT *silTraceVerts, const idVec3 &localView, + const float *modelMatrix, rvIndexBuffer &silTraceIndexBuffer ) { + const int numIndices = m_silTraceGeoSpec.m_primitiveCount * 3; + void *indexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, numIndices, + rvIndexBufferLockFlagRead, indexMemory ); + idFixedWinding winding; + for ( int index = 0; index < numIndices; index += 3 ) { + int vertexIndices[3]; + for ( int corner = 0; corner < 3; ++corner ) { + vertexIndices[corner] = silTraceIndexBuffer.Is16Bit() + ? static_cast( indexMemory )[index + corner] + : static_cast( indexMemory )[index + corner]; + } + const idVec3 &v1 = silTraceVerts[vertexIndices[0]].xyzw.ToVec3(); + const idVec3 &v2 = silTraceVerts[vertexIndices[1]].xyzw.ToVec3(); + const idVec3 &v3 = silTraceVerts[vertexIndices[2]].xyzw.ToVec3(); + if ( tr.guiRecursionLevel == 0 ) { + const idVec3 normal = ( v3 - v1 ).Cross( v2 - v1 ); + if ( normal * ( v1 - localView ) >= 0.0f ) { + silTraceIndexBuffer.Unlock(); + return true; + } + } + + winding.SetNumPoints( 3 ); + R_LocalPointToGlobal( modelMatrix, v1, winding[0].ToVec3() ); + R_LocalPointToGlobal( modelMatrix, v2, winding[1].ToVec3() ); + R_LocalPointToGlobal( modelMatrix, v3, winding[2].ToVec3() ); + winding[0].s = winding[0].t = 0.0f; + winding[1].s = winding[1].t = 0.0f; + winding[2].s = winding[2].t = 0.0f; + for ( int plane = 0; plane < 4; ++plane ) { + if ( !winding.ClipInPlace( -tr.viewDef->frustum[plane], 0.1f ) ) { + break; + } + } + for ( int point = 0; point < winding.GetNumPoints(); ++point ) { + idVec3 screen; + R_GlobalToNormalizedDeviceCoordinates( tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, winding[point].ToVec3(), screen ); + ndcBounds.AddPoint( screen ); + } + } + silTraceIndexBuffer.Unlock(); + return false; +} + +void rvPrimBatch::PlaneForSurface( idPlane &destPlane, + const rvSilTraceVertT *silTraceVerts, rvIndexBuffer &silTraceIndexBuffer ) { + void *indexMemory = NULL; + silTraceIndexBuffer.Lock( m_silTraceGeoSpec.m_indexStart, 3, + rvIndexBufferLockFlagRead, indexMemory ); + + int i0; + int i1; + int i2; + if ( silTraceIndexBuffer.Is16Bit() ) { + const unsigned short *indices = static_cast( indexMemory ); + i0 = indices[0]; + i1 = indices[1]; + i2 = indices[2]; + } else { + const unsigned int *indices = static_cast( indexMemory ); + i0 = indices[0]; + i1 = indices[1]; + i2 = indices[2]; + } + + destPlane.FromPoints( silTraceVerts[i0].xyzw.ToVec3(), + silTraceVerts[i1].xyzw.ToVec3(), silTraceVerts[i2].xyzw.ToVec3() ); + silTraceIndexBuffer.Unlock(); +} diff --git a/src/renderer/rvPrimBatch.h b/src/renderer/rvPrimBatch.h new file mode 100644 index 0000000..5dc85c5 --- /dev/null +++ b/src/renderer/rvPrimBatch.h @@ -0,0 +1,146 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software + +#ifndef __RENDERER_RVPRIMBATCH_H__ +#define __RENDERER_RVPRIMBATCH_H__ + +#include "rvIndexBuffer.h" +#include "rvVertexBuffer.h" + +class idCollisionModelLocal; +class idCollisionModelManagerLocal; + +struct primBatchGeoSpec_s { + int m_vertexStart; + int m_vertexCount; + int m_indexStart; + int m_primitiveCount; +}; + +class rvPrimBatch { +friend class rvMesh; +public: + rvPrimBatch(); + ~rvPrimBatch(); + + void Shutdown(); + void Init( int *transformPalette, int numTransforms ); + void Init( Lexer &lexer ); + void LoadMatrixPaletteIntoVPParams( const float *skinToModelTransforms ); + int FlipOutsideBackFaces( unsigned char *facing, const unsigned char *cullBits, + rvIndexBuffer &indexBuffer ); + int GetNearestJoint( int silTraceIndex0, int silTraceIndex1, int silTraceIndex2, + rvVertexBuffer &silTraceVertexBuffer, rvIndexBuffer &silTraceIndexBuffer ) const; + void GetTextureBounds( float bounds[2][2], rvVertexBuffer &drawVertexBuffer ); + void Draw( rvVertexBuffer &vertexBuffer, rvIndexBuffer &indexBuffer, + const rvVertexFormat *vertexComponentsNeeded ); + void Draw( rvVertexBuffer &vertexBuffer, int *indices, int numIndices, + const rvVertexFormat *vertexComponentsNeeded ); + void DrawShadowVolume( rvVertexBuffer &vertexBuffer, int *indices, int numIndices, + const rvVertexFormat *vertexComponentsNeeded ); + void DrawShadowVolume( rvVertexBuffer &vertexBuffer, rvIndexBuffer &indexBuffer, + bool drawCaps, const rvVertexFormat *vertexComponentsNeeded ); + int CreateSilShadowVolTris( int *shadowIndices, const unsigned char *facing, + silEdge_t *silEdges ); + int CreateFrontBackShadowVolTris( int *shadowIndices, const unsigned char *facing, + rvIndexBuffer &silTraceIndexBuffer ); + void TransformVertsMinMax( rvSilTraceVertT *destSilTraceVerts, + idVec3 &boundsMin, idVec3 &boundsMax, rvVertexBuffer &silTraceVertexBuffer, + idJointMat *skinSpaceToLocalMats, idJointMat *localToModelMats, + float *skinToModelTransforms ); + void DeriveTriPlanes( idPlane *planes, const rvSilTraceVertT *silTraceVerts, + rvIndexBuffer &silTraceIndexBuffer ); + void LocalTrace( localTrace_t &hit, int &testPlanes, int &testEdges, + int &intersections, const idVec3 &start, const idVec3 &end, + const unsigned char *cullBits, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts, float radius, + rvIndexBuffer &silTraceIndexBuffer, rvIndexBuffer &drawIndexBuffer ); + void CreateLightTris( int *destDrawIndices, int &destIndexCount, + idBounds &bounds, int &backFaced, int &distanceCulled, + const unsigned char *facing, const unsigned char *cullBits, + const idPlane *localClipPlanes, const rvSilTraceVertT *silTraceVerts, + bool includeBackFaces, rvIndexBuffer &silTraceIndexBuffer, + rvIndexBuffer &drawIndexBuffer ); + void CreateFrontFaceTris( int *destDrawIndices, int &destIndexCount, + idBounds &bounds, int &backFaced, const unsigned char *facing, + const rvSilTraceVertT *silTraceVerts, rvIndexBuffer &silTraceIndexBuffer, + rvIndexBuffer &drawIndexBuffer ); + const rvDeclMatType *GetMaterialType( const idMaterial *material, + const localTrace_t &hit, rvVertexBuffer &drawVertexBuffer ); + void FindOverlayTriangles( overlayVertex_t *overlayVerts, int &numVerts, + int *overlayIndices, int &numIndices, const unsigned char *cullBits, + const idVec2 *textureCoordinates, int vertexBase, + rvIndexBuffer &silTraceIndexBuffer ); + void FindDecalTriangles( idRenderModelDecal &decalModel, + const decalProjectionInfo_t &localInfo, const idPlane *facePlanes, + const rvSilTraceVertT *silTraceVerts, rvIndexBuffer &silTraceIndexBuffer ); + void GenerateCollisionPolys( idCollisionModelManagerLocal &modelManager, + idCollisionModelLocal &collisionModel, const idMaterial &material, + rvVertexBuffer &drawVertexBuffer, rvIndexBuffer &drawIndexBuffer ); + void CopyDrawVertices( idDrawVert *destDrawVerts, + rvVertexBuffer &drawVertexBuffer ); + void CopyDrawIndices( int *destIndices, rvIndexBuffer &drawIndexBuffer, + int destBase ); + void CopySilTraceVertices( rvVertexBuffer &silTraceVertexBuffer, + rvIndexBuffer &silTraceIndexBuffer, rvVertexBuffer &drawVertexBuffer, + rvIndexBuffer &drawIndexBuffer ); + void CopyShadowVertices( rvVertexBuffer &shadowVertexBuffer, + rvVertexBuffer &silTraceVertexBuffer ); + void CopyTriangles( idDrawVert *destDrawVerts, int *destIndices, + rvVertexBuffer &drawVertexBuffer, rvIndexBuffer &drawIndexBuffer, + const rvSilTraceVertT *silTraceVerts, rvIndexBuffer &silTraceIndexBuffer, + int destBase ); + void GetTriangle( idDrawVert &a, idDrawVert &b, idDrawVert &c, + int triangleOffset, rvVertexBuffer &drawVertexBuffer, + rvIndexBuffer &drawIndexBuffer, const rvSilTraceVertT *silTraceVerts, + rvIndexBuffer &silTraceIndexBuffer ); + void TransformDrawVertices( idDrawVert *destDrawVerts, + rvVertexBuffer &drawVertexBuffer, const idMat4 &transform, + int colorShift, unsigned char *colorAdd ); + void TubeDeform( idDrawVert *destDrawVerts, int *destIndices, + const idVec3 &localView, rvVertexBuffer &drawVertexBuffer, + rvIndexBuffer &drawIndexBuffer, const rvSilTraceVertT *silTraceVerts, + rvIndexBuffer &silTraceIndexBuffer ); + bool PreciseCullSurface( idBounds &ndcBounds, + const rvSilTraceVertT *silTraceVerts, const idVec3 &localView, + const float *modelMatrix, rvIndexBuffer &silTraceIndexBuffer ); + void PlaneForSurface( idPlane &destPlane, const rvSilTraceVertT *silTraceVerts, + rvIndexBuffer &silTraceIndexBuffer ); + void Write( idFile &outFile, const char *prepend ); + +protected: + int * m_transformPalette; + int m_numTransforms; + int m_silEdgeStart; + int m_silEdgeCount; + primBatchGeoSpec_s m_silTraceGeoSpec; + primBatchGeoSpec_s m_drawGeoSpec; + primBatchGeoSpec_s m_shadowVolGeoSpec; + int m_numShadowPrimitivesNoCaps; + int m_shadowCapPlaneBits; +}; + +#endif diff --git a/src/renderer/rvRenderModelMD5R.cpp b/src/renderer/rvRenderModelMD5R.cpp new file mode 100644 index 0000000..7cc765f --- /dev/null +++ b/src/renderer/rvRenderModelMD5R.cpp @@ -0,0 +1,1548 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvRenderModelMD5R.h" +#include "rvMesh.h" +#include "../cm/CollisionModel_local.h" + +/* +=============================================================================== + + Quake 4 MD5R render model + + The class layout, signatures, and compilation-unit ownership come from + rvRenderModelMD5R.obj in quake4.pdb. Bodies follow the address-matched + retail executable. + +=============================================================================== +*/ + +static const float MD5R_DefaultLODRanges[3] = { 500.0f, 1000.0f, 2000.0f }; +static const char *MD5R_SnapshotName = "_MD5R_Snapshot_"; +rvRenderModelMD5R *rvRenderModelMD5R::ms_modelList = NULL; + +static bool MD5R_FaceNegativePolarity( const srfTriangles_t &tri, + int firstIndex ) { + const idDrawVert &a = tri.verts[tri.indexes[firstIndex + 0]]; + const idDrawVert &b = tri.verts[tri.indexes[firstIndex + 1]]; + const idDrawVert &c = tri.verts[tri.indexes[firstIndex + 2]]; + const idVec2 d0 = b.st - a.st; + const idVec2 d1 = c.st - a.st; + return d0.x * d1.y - d0.y * d1.x < 0.0f; +} + +static int *MD5R_CreateSilRemap( const srfTriangles_t &tri ) { + int *remap = new int[tri.numVerts]; + if ( remap == NULL ) { + common->FatalError( "rvRenderModelMD5R: out of memory building silhouette remap" ); + } + if ( !r_useSilRemap.GetBool() ) { + for ( int i = 0; i < tri.numVerts; ++i ) { + remap[i] = i; + } + return remap; + } + + idHashIndex hash( 1024, tri.numVerts ); + for ( int i = 0; i < tri.numVerts; ++i ) { + const idDrawVert &vertex = tri.verts[i]; + const int hashKey = hash.GenerateKey( vertex.xyz ); + int previous; + for ( previous = hash.First( hashKey ); previous >= 0; + previous = hash.Next( previous ) ) { + if ( tri.verts[previous].xyz == vertex.xyz ) { + remap[i] = previous; + break; + } + } + if ( previous < 0 ) { + remap[i] = i; + hash.Add( hashKey, i ); + } + } + return remap; +} + +rvRenderModelMD5R::rvRenderModelMD5R() { + ResetValues(); + m_source = MD5R_SOURCE_NONE; + m_next = ms_modelList; + ms_modelList = this; +} + +rvRenderModelMD5R::~rvRenderModelMD5R() { + Shutdown(); + RemoveFromList( *this ); +} + +void rvRenderModelMD5R::ResetValues() { + purged = true; + m_joints = NULL; + m_skinSpaceToLocalMats = NULL; + m_defaultPose = NULL; + m_numJoints = 0; + m_vertexBuffers = NULL; + m_allocVertexBuffers = NULL; + m_numVertexBuffers = 0; + m_indexBuffers = NULL; + m_allocIndexBuffers = NULL; + m_numIndexBuffers = 0; + m_silEdges = NULL; + m_allocSilEdges = NULL; + m_numSilEdges = 0; + m_numSilEdgesAdded = 0; + m_meshes = NULL; + m_numMeshes = 0; + m_numLODs = 0; + m_lods = NULL; + m_allLODMeshes = NULL; +} + +void rvRenderModelMD5R::ParseVertexBuffers( Lexer &lexer ) { + lexer.ExpectTokenString( "[" ); + m_numVertexBuffers = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + m_allocVertexBuffers = new rvVertexBuffer[m_numVertexBuffers]; + m_vertexBuffers = m_allocVertexBuffers; + if ( m_vertexBuffers == NULL ) { + lexer.Error( "Out of memory" ); + } + for ( int i = 0; i < m_numVertexBuffers; ++i ) { + lexer.ExpectTokenString( "VertexBuffer" ); + m_vertexBuffers[i].Init( lexer ); + } + lexer.ExpectTokenString( "}" ); +} + +void rvRenderModelMD5R::ParseIndexBuffers( Lexer &lexer ) { + lexer.ExpectTokenString( "[" ); + m_numIndexBuffers = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + m_allocIndexBuffers = new rvIndexBuffer[m_numIndexBuffers]; + m_indexBuffers = m_allocIndexBuffers; + if ( m_indexBuffers == NULL ) { + lexer.Error( "Out of memory" ); + } + for ( int i = 0; i < m_numIndexBuffers; ++i ) { + lexer.ExpectTokenString( "IndexBuffer" ); + m_indexBuffers[i].Init( lexer ); + } + lexer.ExpectTokenString( "}" ); +} + +void rvRenderModelMD5R::ParseSilhouetteEdges( Lexer &lexer ) { + lexer.ExpectTokenString( "[" ); + m_numSilEdges = m_numSilEdgesAdded = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + m_allocSilEdges = static_cast( Mem_Alloc( + m_numSilEdges * sizeof( silEdge_t ), MA_RENDER ) ); + m_silEdges = m_allocSilEdges; + if ( m_silEdges == NULL && m_numSilEdges != 0 ) { + lexer.Error( "Out of memory" ); + } + for ( int i = 0; i < m_numSilEdges; ++i ) { + m_silEdges[i].p1 = lexer.ParseInt(); + m_silEdges[i].p2 = lexer.ParseInt(); + m_silEdges[i].v1 = lexer.ParseInt(); + m_silEdges[i].v2 = lexer.ParseInt(); + } + lexer.ExpectTokenString( "}" ); +} + +void rvRenderModelMD5R::ParseLevelOfDetail( Lexer &lexer ) { + lexer.ExpectTokenString( "[" ); + m_numLODs = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + m_lods = static_cast( Mem_Alloc( + m_numLODs * sizeof( levelOfDetailMD5R_s ), MA_RENDER ) ); + if ( m_lods == NULL && m_numLODs != 0 ) { + lexer.Error( "Out of memory" ); + } + for ( int i = 0; i < m_numLODs; ++i ) { + m_lods[i].m_rangeEnd = lexer.ParseFloat(); + m_lods[i].m_rangeEndSquared = m_lods[i].m_rangeEnd * m_lods[i].m_rangeEnd; + m_lods[i].m_meshList = NULL; + } + lexer.ExpectTokenString( "}" ); +} + +void rvRenderModelMD5R::ParseMeshes( Lexer &lexer ) { + lexer.ExpectTokenString( "[" ); + m_numMeshes = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + m_meshes = new rvMesh[m_numMeshes]; + if ( m_meshes == NULL && m_numMeshes != 0 ) { + lexer.Error( "Out of memory" ); + } + for ( int i = 0; i < m_numMeshes; ++i ) { + lexer.ExpectTokenString( "Mesh" ); + m_meshes[i].Init( *this, lexer ); + m_meshes[i].m_meshIdentifier = i; + } + lexer.ExpectTokenString( "}" ); +} + +void rvRenderModelMD5R::BuildLevelsOfDetail() { + int maximumLevel = -1; + for ( int i = 0; i < m_numMeshes; ++i ) { + maximumLevel = Max( maximumLevel, static_cast( m_meshes[i].m_levelOfDetail ) ); + } + if ( maximumLevel >= 0 && m_numLODs == 0 ) { + m_numLODs = maximumLevel + 1; + m_lods = static_cast( Mem_Alloc( + m_numLODs * sizeof( levelOfDetailMD5R_s ), MA_RENDER ) ); + if ( m_lods == NULL ) { + common->FatalError( "Out of memory" ); + } + for ( int i = 0; i < m_numLODs; ++i ) { + m_lods[i].m_rangeEnd = i < 3 ? MD5R_DefaultLODRanges[i] + : m_lods[i - 1].m_rangeEnd * 2.0f; + m_lods[i].m_rangeEndSquared = m_lods[i].m_rangeEnd * m_lods[i].m_rangeEnd; + m_lods[i].m_meshList = NULL; + } + } + m_allLODMeshes = NULL; + for ( int i = 0; i < m_numLODs; ++i ) { + m_lods[i].m_meshList = NULL; + } + for ( int i = 0; i < m_numMeshes; ++i ) { + rvMesh &mesh = m_meshes[i]; + if ( mesh.m_levelOfDetail < 0 || mesh.m_levelOfDetail >= m_numLODs ) { + mesh.m_nextInLOD = m_allLODMeshes; + m_allLODMeshes = &mesh; + } else { + mesh.m_nextInLOD = m_lods[mesh.m_levelOfDetail].m_meshList; + m_lods[mesh.m_levelOfDetail].m_meshList = &mesh; + } + } +} + +dynamicModel_t rvRenderModelMD5R::IsDynamicModel() const { + return m_numJoints != 0 ? DM_CACHED : DM_STATIC; +} + +idBounds rvRenderModelMD5R::Bounds( const renderEntity_t *entity ) const { + return entity != NULL && m_numJoints != 0 ? entity->bounds : bounds; +} + +srfTriangles_t *rvRenderModelMD5R::GenerateStaticTriSurface( int meshOffset ) { + rvMesh &mesh = m_meshes[meshOffset]; + srfTriangles_t *triangles = R_AllocStaticTriSurf(); + triangles->numVerts = mesh.m_numDrawVertices; + triangles->numIndexes = mesh.m_numDrawIndices; + R_AllocStaticTriSurfVerts( triangles, triangles->numVerts ); + R_AllocStaticTriSurfIndexes( triangles, triangles->numIndexes ); + mesh.CopyTriangles( triangles->verts, triangles->indexes, 0 ); + return triangles; +} + +int rvRenderModelMD5R::NumJoints() const { + return m_numJoints; +} + +const idMD5Joint *rvRenderModelMD5R::GetJoints() const { + return m_joints; +} + +const idJointQuat *rvRenderModelMD5R::GetDefaultPose() const { + return m_defaultPose; +} + +int rvRenderModelMD5R::NearestJoint( int surfaceNum, int a, int b, int c ) const { + if ( surfaceNum > m_numMeshes ) { + common->Error( "idRenderModelMD5R::NearestJoint: surfaceNum > m_numMeshes" ); + } + for ( int i = 0; i < m_numMeshes; ++i ) { + if ( m_meshes[i].m_surfaceNum == surfaceNum ) { + return m_meshes[i].GetNearestJoint( a, b, c ); + } + } + return 0; +} + +int rvRenderModelMD5R::CalcMaxBonesPerVertex( const jointWeight_t *jointWeights, + int numVerts, const srfTriangles_t &tri ) { + int maxBonesPerVertex = 0; + int currentWeight = 0; + for ( int vertex = 0; vertex < numVerts; ++vertex ) { + int bonesPerVertex = 0; + do { + ++bonesPerVertex; + } while ( jointWeights[currentWeight++].nextVertexOffset != + sizeof( jointWeight_t ) ); + maxBonesPerVertex = Max( maxBonesPerVertex, bonesPerVertex ); + if ( bonesPerVertex > 4 ) { + common->Warning( "Vertex %d in %s is weighted to %d transforms", + vertex, Name(), bonesPerVertex ); + } + } + return maxBonesPerVertex; +} + +void rvRenderModelMD5R::FixBadTangentSpaces( srfTriangles_t &tri ) { + for ( int i = 0; i < tri.numVerts; ++i ) { + idDrawVert &vert = tri.verts[i]; + if ( !FLOAT_IS_NAN( vert.tangents[0].x ) && + !FLOAT_IS_NAN( vert.tangents[0].y ) && + !FLOAT_IS_NAN( vert.tangents[0].z ) && + !FLOAT_IS_NAN( vert.tangents[1].x ) && + !FLOAT_IS_NAN( vert.tangents[1].y ) && + !FLOAT_IS_NAN( vert.tangents[1].z ) ) { + continue; + } + + if ( idMath::Fabs( vert.normal.z ) <= 0.7f ) { + const float length = idMath::Sqrt( + vert.normal.x * vert.normal.x + vert.normal.y * vert.normal.y ); + const float inverseLength = length > 0.0f ? 1.0f / length : 0.0f; + vert.tangents[0].Set( -vert.normal.y * inverseLength, + vert.normal.x * inverseLength, 0.0f ); + } else { + const float length = idMath::Sqrt( + vert.normal.y * vert.normal.y + vert.normal.z * vert.normal.z ); + const float inverseLength = length > 0.0f ? 1.0f / length : 0.0f; + vert.tangents[0].Set( length, + -vert.normal.x * vert.normal.y * inverseLength, + -vert.normal.x * vert.normal.z * inverseLength ); + } + vert.tangents[1] = vert.normal.Cross( vert.tangents[0] ); + } +} + +int rvRenderModelMD5R::BuildBlend4VertArray( idMD5Mesh &md5Mesh, + modelSurface_t &surface, rvBlend4DrawVert *drawVertices ) { + srfTriangles_t &tri = *surface.geometry; + R_DeriveTangents( &tri, true ); + FixBadTangentSpaces( tri ); + + int currentWeight = 0; + int maxBonesPerVertex = 0; + const int sourceVertexCount = md5Mesh.texCoords.Num(); + for ( int vertex = 0; vertex < sourceVertexCount; ++vertex ) { + int blendIndices[4] = { 0, 0, 0, 0 }; + float blendWeights[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + int bonesPerVertex = 0; + for ( ;; ) { + const jointWeight_t &jointWeight = md5Mesh.weights[currentWeight++]; + if ( bonesPerVertex < 4 ) { + blendIndices[bonesPerVertex] = jointWeight.jointMatOffset / + sizeof( idJointMat ); + blendWeights[bonesPerVertex] = jointWeight.weight; + if ( blendWeights[bonesPerVertex] > blendWeights[0] ) { + const int oldIndex = blendIndices[0]; + blendIndices[0] = blendIndices[bonesPerVertex]; + blendIndices[bonesPerVertex] = oldIndex; + const float oldWeight = blendWeights[0]; + blendWeights[0] = blendWeights[bonesPerVertex]; + blendWeights[bonesPerVertex] = oldWeight; + } + } + ++bonesPerVertex; + if ( jointWeight.nextVertexOffset == sizeof( jointWeight_t ) ) { + break; + } + } + maxBonesPerVertex = Max( maxBonesPerVertex, bonesPerVertex ); + + rvBlend4DrawVert &dest = drawVertices[vertex]; + for ( int i = 0; i < 4; ++i ) { + dest.blendIndex[i] = blendIndices[i]; + dest.blendWeight[i] = blendWeights[i]; + } + const idDrawVert &source = tri.verts[vertex]; + dest.xyz = source.xyz; + dest.normal = source.normal; + dest.tangent = source.tangents[0]; + dest.binormal = source.tangents[1]; + memcpy( dest.color, source.color, sizeof( dest.color ) ); + dest.st = source.st; + } + + for ( int vertex = sourceVertexCount; vertex < tri.numVerts; ++vertex ) { + const int mirroredVertex = + tri.mirroredVerts[vertex - sourceVertexCount]; + rvBlend4DrawVert &dest = drawVertices[vertex]; + const rvBlend4DrawVert &blendSource = drawVertices[mirroredVertex]; + memcpy( dest.blendIndex, blendSource.blendIndex, + sizeof( dest.blendIndex ) ); + memcpy( dest.blendWeight, blendSource.blendWeight, + sizeof( dest.blendWeight ) ); + const idDrawVert &source = tri.verts[vertex]; + dest.xyz = source.xyz; + dest.normal = source.normal; + dest.tangent = source.tangents[0]; + dest.binormal = source.tangents[1]; + memcpy( dest.color, source.color, sizeof( dest.color ) ); + dest.st = source.st; + } + + for ( int index = 0; index < tri.numIndexes; index += 3 ) { + if ( !MD5R_FaceNegativePolarity( tri, index ) ) { + continue; + } + for ( int corner = 0; corner < 3; ++corner ) { + float &weight = drawVertices[tri.indexes[index + corner]].blendWeight[0]; + weight = -idMath::Fabs( weight ); + } + } + return maxBonesPerVertex; +} + +void rvRenderModelMD5R::RemoveFromList( rvRenderModelMD5R &model ) { + rvRenderModelMD5R *previous = NULL; + for ( rvRenderModelMD5R *current = ms_modelList; current != NULL; + previous = current, current = current->m_next ) { + if ( current != &model ) { + continue; + } + if ( previous != NULL ) { + previous->m_next = model.m_next; + } else { + ms_modelList = model.m_next; + } + model.m_next = NULL; + return; + } +} + +void rvRenderModelMD5R::CompressVertexFormat( rvVertexFormat &compressedFormat, + const rvVertexFormat &format ) { + compressedFormat.Init( format ); + const Rv_Vertex_Component_t components[3] = { + RV_VERTEX_COMPONENT_NORMAL, + RV_VERTEX_COMPONENT_TANGENT, + RV_VERTEX_COMPONENT_BINORMAL + }; + for ( int i = 0; i < 3; ++i ) { + if ( ( compressedFormat.m_flags & BIT( components[i] ) ) != 0 ) { + compressedFormat.m_dataTypes[components[i]] = + RV_VERTEX_DATA_TYPE_DEC_10_10_10N; + compressedFormat.CalcSize(); + } + } +} + +void rvRenderModelMD5R::WriteSilhouetteEdges( idFile &outFile, + const char *prepend, const char *prependPlusTab ) { + if ( m_numSilEdgesAdded == 0 ) { + return; + } + outFile.WriteFloatString( "%sSilhouetteEdge[ %d ]\n%s{\n", + prepend, m_numSilEdgesAdded, prepend ); + for ( int i = 0; i < m_numSilEdgesAdded; ++i ) { + outFile.WriteFloatString( "%s%d %d %d %d\n", prependPlusTab, + m_silEdges[i].p1, m_silEdges[i].p2, + m_silEdges[i].v1, m_silEdges[i].v2 ); + } + outFile.WriteFloatString( "%s}\n", prepend ); +} + +void rvRenderModelMD5R::WriteLevelOfDetail( idFile &outFile, + const char *prepend, const char *prependPlusTab ) { + bool isDefault = m_numLODs <= 3; + for ( int i = 0; isDefault && i < m_numLODs; ++i ) { + const float defaultRange = i < 3 ? MD5R_DefaultLODRanges[i] + : m_lods[i - 1].m_rangeEnd * 2.0f; + isDefault = m_lods[i].m_rangeEnd == defaultRange; + } + if ( m_numLODs == 0 || isDefault ) { + return; + } + outFile.WriteFloatString( "%sLevelOfDetail[ %d ]\n%s{\n", + prepend, m_numLODs, prepend ); + for ( int i = 0; i < m_numLODs; ++i ) { + outFile.WriteFloatString( "%s%f\n", prependPlusTab, m_lods[i].m_rangeEnd ); + } + outFile.WriteFloatString( "%s}\n", prepend ); +} + +void rvRenderModelMD5R::WriteMeshes( idFile &outFile, const char *prepend ) { + if ( m_numMeshes == 0 ) { + return; + } + idStr indent = prepend; + indent += "\t"; + outFile.WriteFloatString( "%sMesh[ %d ]\n%s{\n", prepend, m_numMeshes, prepend ); + for ( int i = 0; i < m_numMeshes; ++i ) { + m_meshes[i].Write( outFile, indent.c_str() ); + } + outFile.WriteFloatString( "%s}\n", prepend ); +} + +void rvRenderModelMD5R::WriteJoints( idFile &outFile, const char *prepend, + const char *prependPlusTab ) { + if ( m_numJoints == 0 ) { + return; + } + outFile.WriteFloatString( "%sJoint[ %d ]\n%s{\n", + prepend, m_numJoints, prepend ); + for ( int i = 0; i < m_numJoints; ++i ) { + const int parentOffset = m_joints[i].parent != NULL + ? static_cast( m_joints[i].parent - m_joints ) : -1; + idJointMat jointMat = m_skinSpaceToLocalMats[i]; + jointMat.Invert(); + const idJointQuat jointQuat = jointMat.ToJointQuat(); + const idCQuat compressedQuat = jointQuat.q.ToCQuat(); + const idVec3 translation = jointMat.ToVec3(); + outFile.WriteFloatString( + "%s\"%s\" %d %f %f %f %f %f %f\n", + prependPlusTab, m_joints[i].name.c_str(), parentOffset, + translation.x, translation.y, translation.z, + compressedQuat.x, compressedQuat.y, compressedQuat.z ); + } + outFile.WriteFloatString( "%s}\n", prepend ); +} + +void rvRenderModelMD5R::Print() const { + int totalVertices = 0; + int totalTriangles = 0; + common->Printf( "%s\nDynamic MD5R model.\n verts tris material\n", name.c_str() ); + for ( int i = 0; i < m_numMeshes; ++i ) { + const rvMesh &mesh = m_meshes[i]; + totalVertices += mesh.m_numDrawVertices; + totalTriangles += mesh.m_numDrawPrimitives; + common->Printf( "%2i: %5i %5i %s\n", i, mesh.m_numDrawVertices, + mesh.m_numDrawPrimitives, mesh.m_material->GetName() ); + } + common->Printf( "-----\n%4i verts.\n%4i tris.\n%4i joints.\n", + totalVertices, totalTriangles, m_numJoints ); +} + +void rvRenderModelMD5R::TouchData() { + for ( int i = 0; i < m_numMeshes; ++i ) { + declManager->FindMaterial( m_meshes[i].m_material->GetName() ); + } +} + +jointHandle_t rvRenderModelMD5R::GetJointHandle( const char *jointName ) const { + for ( int i = 0; i < m_numJoints; ++i ) { + if ( m_joints[i].name.Icmp( jointName ) == 0 ) { + return static_cast( i ); + } + } + return INVALID_JOINT; +} + +const char *rvRenderModelMD5R::GetJointName( jointHandle_t jointHandle ) const { + if ( jointHandle < 0 || jointHandle >= m_numJoints ) { + return ""; + } + return m_joints[jointHandle].name.c_str(); +} + +int rvRenderModelMD5R::GetSurfaceMask( const char *surfaceName ) const { + int surfaceMask = 0; + for ( int i = m_numMeshes - 1; i >= 0; --i ) { + if ( idStr::Icmp( m_meshes[i].m_material->GetName(), surfaceName ) == 0 ) { + surfaceMask |= 1 << m_meshes[i].m_meshIdentifier; + } + } + return surfaceMask; +} + +void rvRenderModelMD5R::DrawJoints( const renderEntity_t *entity, + const viewDef_t *view ) const { + if ( entity == NULL || entity->joints == NULL ) { + return; + } + const int numJoints = Min( entity->numJoints, m_numJoints ); + for ( int i = 0; i < numJoints; ++i ) { + const idJointMat &joint = entity->joints[i]; + const idVec3 position = entity->origin + joint.ToVec3() * entity->axis; + if ( m_joints[i].parent != NULL ) { + const int parent = static_cast( m_joints[i].parent - m_joints ); + const idVec3 parentPosition = entity->origin + + entity->joints[parent].ToVec3() * entity->axis; + session->rw->DebugLine( colorWhite, parentPosition, position ); + } + const idMat3 jointAxis = joint.ToMat3(); + session->rw->DebugLine( colorRed, position, + position + jointAxis[0] * 2.0f * entity->axis ); + session->rw->DebugLine( colorGreen, position, + position + jointAxis[1] * 2.0f * entity->axis ); + session->rw->DebugLine( colorBlue, position, + position + jointAxis[2] * 2.0f * entity->axis ); + } + + idBounds worldBounds; + worldBounds.FromTransformedBounds( entity->bounds, vec3_origin, entity->axis ); + session->rw->DebugBounds( colorMagenta, worldBounds, entity->origin ); + if ( view == NULL || r_jointNameScale.GetFloat() == 0.0f || + !worldBounds.Expand( 128.0f ).ContainsPoint( + view->renderView.vieworg - entity->origin ) ) { + return; + } + const idVec3 offset( 0.0f, 0.0f, r_jointNameOffset.GetFloat() ); + for ( int i = 0; i < numJoints; ++i ) { + const idVec3 position = entity->origin + + entity->joints[i].ToVec3() * entity->axis; + session->rw->DrawText( m_joints[i].name.c_str(), position + offset, + r_jointNameScale.GetFloat(), colorCyan, + view->renderView.viewaxis, 1 ); + } +} + +void rvRenderModelMD5R::GenerateCollisionModel( + idCollisionModelManagerLocal &modelManager, + idCollisionModelLocal &collisionModel ) { + bool hasCollisionMesh = false; + for ( int i = 0; i < m_numMeshes; ++i ) { + if ( ( m_meshes[i].m_material->GetSurfaceFlags() & SURF_COLLISION ) != 0 ) { + hasCollisionMesh = true; + break; + } + } + for ( int i = 0; i < m_numMeshes; ++i ) { + const idMaterial *material = m_meshes[i].m_material; + if ( material->GetContentFlags() != 0 && + ( !hasCollisionMesh || + ( material->GetSurfaceFlags() & SURF_COLLISION ) != 0 ) ) { + m_meshes[i].GenerateCollisionPolys( modelManager, collisionModel ); + } + } +} + +void rvRenderModelMD5R::WriteSansBuffers( idFile &outFile, const char *prepend ) { + if ( m_numMeshes == 0 ) { + return; + } + WriteMeshes( outFile, prepend ); + outFile.WriteFloatString( "%sBounds %f %f %f %f %f %f\n", prepend, + bounds[0].x, bounds[0].y, bounds[0].z, + bounds[1].x, bounds[1].y, bounds[1].z ); + if ( GetHasSky() ) { + outFile.WriteFloatString( "%sHasSky\n", prepend ); + } +} + +void rvRenderModelMD5R::Shutdown() { + idRenderModelStatic::PurgeModel(); + delete[] m_joints; + if ( m_skinSpaceToLocalMats != NULL ) { + Mem_Free16( m_skinSpaceToLocalMats ); + } + if ( m_defaultPose != NULL ) { + Mem_Free16( m_defaultPose ); + } + delete[] m_allocVertexBuffers; + delete[] m_allocIndexBuffers; + if ( m_allocSilEdges != NULL ) { + Mem_Free( m_allocSilEdges ); + } + delete[] m_meshes; + if ( m_lods != NULL ) { + Mem_Free( m_lods ); + } + ResetValues(); +} + +void rvRenderModelMD5R::PurgeModel() { + Shutdown(); +} + +void rvRenderModelMD5R::ParseJoints( Lexer &lexer ) { + lexer.ExpectTokenString( "[" ); + m_numJoints = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + lexer.ExpectTokenString( "{" ); + + m_joints = new idMD5Joint[m_numJoints]; + m_skinSpaceToLocalMats = static_cast( + Mem_Alloc16( m_numJoints * sizeof( idJointMat ), MA_RENDER ) ); + m_defaultPose = static_cast( + Mem_Alloc16( m_numJoints * sizeof( idJointQuat ), MA_RENDER ) ); + if ( ( m_joints == NULL || m_skinSpaceToLocalMats == NULL || + m_defaultPose == NULL ) && m_numJoints != 0 ) { + lexer.Error( "Out of memory" ); + } + + for ( int i = 0; i < m_numJoints; ++i ) { + idToken token; + lexer.ReadToken( &token ); + m_joints[i].name = token; + const int parentOffset = lexer.ParseInt(); + + idVec3 translation; + translation.x = lexer.ParseFloat(); + translation.y = lexer.ParseFloat(); + translation.z = lexer.ParseFloat(); + idQuat rotation; + rotation.x = lexer.ParseFloat(); + rotation.y = lexer.ParseFloat(); + rotation.z = lexer.ParseFloat(); + rotation.w = rotation.CalcW(); + + m_skinSpaceToLocalMats[i].SetRotation( rotation.ToMat3() ); + m_skinSpaceToLocalMats[i].SetTranslation( translation ); + if ( parentOffset >= 0 ) { + if ( parentOffset >= m_numJoints - 1 ) { + lexer.Error( "Invalid parent for joint '%s'", + m_joints[i].name.c_str() ); + } + m_joints[i].parent = &m_joints[parentOffset]; + const idMat3 parentRotation = + m_skinSpaceToLocalMats[parentOffset].ToMat3(); + m_defaultPose[i].q = ( m_skinSpaceToLocalMats[i].ToMat3() * + parentRotation.Transpose() ).ToQuat(); + m_defaultPose[i].t = ( translation - + m_skinSpaceToLocalMats[parentOffset].ToVec3() ) * + parentRotation.Transpose(); + } else { + m_joints[i].parent = NULL; + m_defaultPose[i].q = rotation; + m_defaultPose[i].t = translation; + } + m_defaultPose[i].w = 0.0f; + } + + for ( int i = 0; i < m_numJoints; ++i ) { + m_skinSpaceToLocalMats[i].Invert(); + } + lexer.ExpectTokenString( "}" ); +} + +bool rvRenderModelMD5R::HasCollisionSurface( const renderEntity_t *entity ) const { + if ( entity == NULL ) { + return false; + } + for ( int i = 0; i < m_numMeshes; ++i ) { + const idMaterial *material = R_RemapShaderBySkin( m_meshes[i].m_material, + entity->customSkin, entity->customShader ); + if ( ( material->GetSurfaceFlags() & SURF_COLLISION ) != 0 && + !material->IsDrawn() && !material->SurfaceCastsShadow() ) { + return true; + } + } + return false; +} + +void rvRenderModelMD5R::Write( idFile &outFile, const char *prepend, + bool writeBuffers ) { + if ( IsDefaultModel() ) { + return; + } + idStr indent = prepend; + indent += "\t"; + WriteJoints( outFile, prepend, indent.c_str() ); + + if ( m_numVertexBuffers != 0 ) { + outFile.WriteFloatString( "%sVertexBuffer[ %d ]\n%s{\n", + prepend, m_numVertexBuffers, prepend ); + for ( int i = 0; i < m_numVertexBuffers; ++i ) { + if ( writeBuffers ) { + if ( ( m_vertexBuffers[i].m_flags & + ( rvVertexBufferFlagUsageShadow | + rvVertexBufferFlagUsageExplicitShadow ) ) != 0 ) { + rvVertexFormat compressedFormat; + CompressVertexFormat( compressedFormat, m_vertexBuffers[i].m_format ); + m_vertexBuffers[i].SetLoadFormat( compressedFormat ); + m_vertexBuffers[i].m_flags |= + rvVertexBufferFlagWriteLoadFormatAsPrimary; + } else { + m_vertexBuffers[i].m_flags |= + rvVertexBufferFlagInitiallyEmpty; + } + } else { + m_vertexBuffers[i].m_flags &= + ~rvVertexBufferFlagInitiallyEmpty; + m_vertexBuffers[i].m_flags &= + ~rvVertexBufferFlagWriteLoadFormatAsPrimary; + } + m_vertexBuffers[i].Write( outFile, indent.c_str() ); + } + outFile.WriteFloatString( "%s}\n", prepend ); + } + + if ( m_numIndexBuffers != 0 ) { + outFile.WriteFloatString( "%sIndexBuffer[ %d ]\n%s{\n", + prepend, m_numIndexBuffers, prepend ); + for ( int i = 0; i < m_numIndexBuffers; ++i ) { + m_indexBuffers[i].Write( outFile, indent.c_str() ); + } + outFile.WriteFloatString( "%s}\n", prepend ); + } + WriteSilhouetteEdges( outFile, prepend, indent.c_str() ); + WriteLevelOfDetail( outFile, prepend, indent.c_str() ); + WriteSansBuffers( outFile, prepend ); +} + +bool rvRenderModelMD5R::Init( idRenderModelMD5 &sourceModel ) { + if ( m_meshes != NULL ) { + Shutdown(); + } + name = sourceModel.Name(); + m_source = MD5R_SOURCE_MD5; + purged = false; + reloadable = true; + m_numJoints = sourceModel.NumJoints(); + m_joints = new idMD5Joint[m_numJoints]; + m_skinSpaceToLocalMats = static_cast( + Mem_Alloc16( m_numJoints * sizeof( idJointMat ), MA_RENDER ) ); + m_defaultPose = static_cast( + Mem_Alloc16( m_numJoints * sizeof( idJointQuat ), MA_RENDER ) ); + if ( ( m_joints == NULL || m_skinSpaceToLocalMats == NULL || + m_defaultPose == NULL ) && m_numJoints != 0 ) { + common->FatalError( "Out of memory" ); + } + + const idMD5Joint *sourceJoints = sourceModel.GetJoints(); + const idJointQuat *sourcePose = sourceModel.GetDefaultPose(); + const idJointMat *sourceSkinMats = sourceModel.GetSkinSpaceToLocalMats(); + idJointMat *jointMats = static_cast( + _alloca16( m_numJoints * sizeof( idJointMat ) ) ); + for ( int i = 0; i < m_numJoints; ++i ) { + m_joints[i].name = sourceJoints[i].name; + m_joints[i].parent = sourceJoints[i].parent != NULL + ? &m_joints[sourceJoints[i].parent - sourceJoints] : NULL; + m_defaultPose[i] = sourcePose[i]; + m_skinSpaceToLocalMats[i] = sourceSkinMats[i]; + jointMats[i].SetRotation( sourcePose[i].q.ToMat3() ); + jointMats[i].SetTranslation( sourcePose[i].t ); + if ( m_joints[i].parent != NULL ) { + jointMats[i] *= jointMats[m_joints[i].parent - m_joints]; + } + } + + idRenderModelStatic staticModel; + staticModel.InitEmpty( MD5R_SnapshotName ); + renderEntity_t renderEntity; + memset( &renderEntity, 0, sizeof( renderEntity ) ); + renderEntity.axis = mat3_identity; + renderEntity.bounds.Clear(); + renderEntity.numJoints = m_numJoints; + renderEntity.joints = jointMats; + renderEntity.hModel = &sourceModel; + for ( int i = 0; i < sourceModel.meshes.Num(); ++i ) { + modelSurface_t &surface = staticModel.surfaces.Alloc(); + surface.id = i; + surface.shader = NULL; + surface.geometry = NULL; + surface.mOriginalSurfaceName = NULL; + sourceModel.meshes[i].UpdateSurface( + &renderEntity, jointMats, &surface, true ); + staticModel.bounds.AddBounds( surface.geometry->bounds ); + } + + int oneBoneVertices = 0; + int fourBoneVertices = 0; + int numIndices = 0; + int numSilEdges = 0; + for ( int i = 0; i < staticModel.surfaces.Num(); ++i ) { + modelSurface_t &surface = staticModel.surfaces[i]; + idMD5Mesh &md5Mesh = sourceModel.meshes[surface.id]; + const int bonesPerVertex = CalcMaxBonesPerVertex( + md5Mesh.weights, md5Mesh.texCoords.Num(), *surface.geometry ); + if ( bonesPerVertex > 4 ) { + Shutdown(); + common->Warning( + "Unable to convert MD5 mesh %s - too many bones per vertex: %d", + sourceModel.Name(), bonesPerVertex ); + return false; + } + if ( bonesPerVertex <= 1 ) { + oneBoneVertices += surface.geometry->numVerts; + } else { + fourBoneVertices += surface.geometry->numVerts; + } + numIndices += surface.geometry->numIndexes; + numSilEdges += surface.geometry->numSilEdges; + } + if ( m_numJoints >= 25 ) { + oneBoneVertices *= 2; + fourBoneVertices *= 2; + numIndices *= 2; + numSilEdges *= 2; + } + + m_numVertexBuffers = oneBoneVertices != 0 && fourBoneVertices != 0 ? 6 : 3; + m_allocVertexBuffers = new rvVertexBuffer[m_numVertexBuffers]; + m_vertexBuffers = m_allocVertexBuffers; + if ( m_vertexBuffers == NULL ) { + common->FatalError( "Out of memory" ); + } + int textureDimensions[7] = { 2, 0, 0, 0, 0, 0, 0 }; + rvVertexFormat vertexFormat; + int fourBoneBufferStart = 0; + if ( oneBoneVertices != 0 ) { + vertexFormat.Init( rvVertexFormatFlagPosition | + rvVertexFormatFlagBlendIndex, 3, 0, 1, NULL ); + m_vertexBuffers[0].Init( vertexFormat, oneBoneVertices, + rvVertexBufferFlagSystemMemory | rvVertexBufferFlagUsageDraw ); + vertexFormat.Init( rvVertexFormatFlagPosition | + rvVertexFormatFlagBlendIndex | rvVertexFormatFlagNormal | + rvVertexFormatFlagTangent | rvVertexFormatFlagBinormal | + rvVertexFormatFlagDiffuseColor | + rvVertexFormatFlagTextureCoordinate, + 3, 0, 1, textureDimensions ); + m_vertexBuffers[1].Init( vertexFormat, oneBoneVertices, + rvVertexBufferFlagSystemMemory | rvVertexBufferFlagVideoMemory | + rvVertexBufferFlagUsageShadow ); + vertexFormat.Init( rvVertexFormatFlagPosition | + rvVertexFormatFlagBlendIndex, 3, 0, 1, NULL ); + m_vertexBuffers[2].Init( vertexFormat, oneBoneVertices * 2, + rvVertexBufferFlagVideoMemory | rvVertexBufferFlagUsageSilTrace ); + fourBoneBufferStart = 3; + } + if ( fourBoneVertices != 0 ) { + vertexFormat.Init( rvVertexFormatFlagPosition | + rvVertexFormatFlagBlendIndex | rvVertexFormatFlagBlendWeight, + 3, 4, 4, NULL ); + m_vertexBuffers[fourBoneBufferStart].Init( vertexFormat, + fourBoneVertices, + rvVertexBufferFlagSystemMemory | rvVertexBufferFlagUsageDraw ); + vertexFormat.Init( rvVertexFormatFlagPosition | + rvVertexFormatFlagBlendIndex | rvVertexFormatFlagBlendWeight | + rvVertexFormatFlagNormal | rvVertexFormatFlagTangent | + rvVertexFormatFlagDiffuseColor | + rvVertexFormatFlagTextureCoordinate, + 3, 3, 4, textureDimensions ); + m_vertexBuffers[fourBoneBufferStart + 1].Init( vertexFormat, + fourBoneVertices, + rvVertexBufferFlagSystemMemory | rvVertexBufferFlagVideoMemory | + rvVertexBufferFlagUsageShadow ); + vertexFormat.Init( rvVertexFormatFlagPosition | + rvVertexFormatFlagBlendIndex | rvVertexFormatFlagBlendWeight, + 4, 3, 4, NULL ); + m_vertexBuffers[fourBoneBufferStart + 2].Init( vertexFormat, + fourBoneVertices * 2, + rvVertexBufferFlagVideoMemory | rvVertexBufferFlagUsageSilTrace ); + } + + m_numIndexBuffers = 2; + m_allocIndexBuffers = new rvIndexBuffer[m_numIndexBuffers]; + m_indexBuffers = m_allocIndexBuffers; + if ( m_indexBuffers == NULL ) { + common->FatalError( "Out of memory" ); + } + m_indexBuffers[0].Init( numIndices, rvIndexBufferFlagSystemMemory ); + m_indexBuffers[1].Init( numIndices, + rvIndexBufferFlagSystemMemory | rvIndexBufferFlagVideoMemory ); + m_numSilEdges = numSilEdges; + m_numSilEdgesAdded = 0; + m_allocSilEdges = static_cast( + Mem_Alloc( numSilEdges * sizeof( silEdge_t ), MA_RENDER ) ); + m_silEdges = m_allocSilEdges; + if ( m_silEdges == NULL && numSilEdges != 0 ) { + common->FatalError( "Out of memory" ); + } + m_numMeshes = sourceModel.meshes.Num(); + m_meshes = new rvMesh[m_numMeshes]; + if ( m_meshes == NULL ) { + common->FatalError( "Out of memory" ); + } + + for ( int i = 0; i < m_numMeshes; ++i ) { + int surfaceNum; + if ( !staticModel.FindSurfaceWithId( i, surfaceNum ) ) { + continue; + } + modelSurface_t &surface = staticModel.surfaces[surfaceNum]; + rvBlend4DrawVert *drawVertices = static_cast( + Mem_Alloc( surface.geometry->numVerts * + sizeof( rvBlend4DrawVert ), MA_RENDER ) ); + const int bonesPerVertex = BuildBlend4VertArray( + sourceModel.meshes[i], surface, drawVertices ); + int *silRemap = MD5R_CreateSilRemap( *surface.geometry ); + const int vertexBufferStart = + bonesPerVertex <= 1 ? 0 : fourBoneBufferStart; + m_meshes[i].Init( *this, surface.shader, m_numJoints, + drawVertices, surface.geometry->numVerts, + surface.geometry->indexes, surface.geometry->numIndexes, + surface.geometry->silEdges, surface.geometry->numSilEdges, + silRemap, vertexBufferStart, vertexBufferStart + 1, + vertexBufferStart + 2, 0, 1 ); + m_meshes[i].m_meshIdentifier = i; + delete[] silRemap; + Mem_Free( drawVertices ); + } + BuildLevelsOfDetail(); + bounds = sourceModel.bounds; + for ( int i = 0; i < m_numVertexBuffers; ++i ) { + m_vertexBuffers[i].Resize( m_vertexBuffers[i].GetNumVerticesWritten() ); + } + for ( int i = 0; i < m_numIndexBuffers; ++i ) { + m_indexBuffers[i].Resize( m_indexBuffers[i].GetNumIndicesWritten() ); + } + SetHasSky( false ); + return true; +} + +bool rvRenderModelMD5R::Init( idRenderModelStatic &sourceModel, + rvVertexBuffer *vertexBuffers, int vertexBufferStart, + int numVertexBuffers, rvIndexBuffer *indexBuffers, + int indexBufferStart, int numIndexBuffers, silEdge_t *silEdges, + int silEdgeStart, int maxNumSilEdges, rvMD5RSource_t source ) { + if ( m_meshes != NULL ) { + Shutdown(); + } + name = sourceModel.Name(); + m_source = source; + purged = false; + + int numMeshes = 0; + for ( int i = 0; i < sourceModel.surfaces.Num(); ++i ) { + if ( sourceModel.surfaces[i].geometry != NULL ) { + ++numMeshes; + } + } + if ( numMeshes == 0 ) { + if ( source != MD5R_SOURCE_MANUAL ) { + MakeDefaultModel(); + } + return false; + } + + m_vertexBuffers = vertexBuffers; + m_numVertexBuffers = vertexBufferStart + numVertexBuffers; + m_indexBuffers = indexBuffers; + m_numIndexBuffers = indexBufferStart + numIndexBuffers; + m_silEdges = silEdges; + m_numSilEdges = maxNumSilEdges; + m_numSilEdgesAdded = silEdgeStart; + + int silTraceVertexBuffer = -1; + int drawVertexBuffer = -1; + int shadowVertexBuffer = -1; + if ( numVertexBuffers == 3 ) { + silTraceVertexBuffer = vertexBufferStart; + drawVertexBuffer = vertexBufferStart + 1; + shadowVertexBuffer = vertexBufferStart + 2; + } + int silTraceIndexBuffer = -1; + int drawIndexBuffer = -1; + int shadowIndexBuffer = -1; + if ( numIndexBuffers == 3 ) { + silTraceIndexBuffer = indexBufferStart; + drawIndexBuffer = indexBufferStart + 1; + shadowIndexBuffer = indexBufferStart + 2; + } else if ( numIndexBuffers == 2 ) { + silTraceIndexBuffer = indexBufferStart; + drawIndexBuffer = indexBufferStart + 1; + } + + m_numMeshes = numMeshes; + m_meshes = new rvMesh[m_numMeshes]; + if ( m_meshes == NULL ) { + common->FatalError( "Out of memory" ); + } + int meshIndex = 0; + for ( int i = 0; i < sourceModel.surfaces.Num(); ++i ) { + modelSurface_t &surface = sourceModel.surfaces[i]; + if ( surface.geometry == NULL ) { + continue; + } + int *silRemap = NULL; + if ( surface.geometry->verts != NULL ) { + FixBadTangentSpaces( *surface.geometry ); + silRemap = MD5R_CreateSilRemap( *surface.geometry ); + } + m_meshes[meshIndex].Init( *this, surface, silRemap, + silTraceVertexBuffer, drawVertexBuffer, shadowVertexBuffer, + silTraceIndexBuffer, drawIndexBuffer, shadowIndexBuffer ); + m_meshes[meshIndex].m_meshIdentifier = meshIndex; + delete[] silRemap; + ++meshIndex; + } + BuildLevelsOfDetail(); + bounds = sourceModel.bounds; + SetHasSky( sourceModel.GetHasSky() ); + return true; +} + +bool rvRenderModelMD5R::Init( idRenderModelStatic &sourceModel, + rvMD5RSource_t source ) { + if ( m_meshes != NULL ) { + Shutdown(); + } + name = sourceModel.Name(); + m_source = source; + purged = false; + reloadable = true; + + int numDrawSurfaces = 0; + for ( int i = 0; i < sourceModel.surfaces.Num(); ++i ) { + const modelSurface_t &surface = sourceModel.surfaces[i]; + if ( surface.geometry != NULL && + ( surface.shader->GetSurfaceFlags() & SURF_COLLISION ) == 0 ) { + ++numDrawSurfaces; + } + } + + int numVertices = 0; + int numIndices = 0; + int numSilEdges = 0; + int numMeshes = 0; + for ( int i = 0; i < sourceModel.surfaces.Num(); ++i ) { + const modelSurface_t &surface = sourceModel.surfaces[i]; + if ( surface.geometry == NULL || + ( numDrawSurfaces > 0 && + ( surface.shader->GetSurfaceFlags() & SURF_COLLISION ) != 0 ) ) { + continue; + } + numVertices += surface.geometry->numVerts; + numIndices += surface.geometry->numIndexes; + numSilEdges += surface.geometry->numSilEdges; + ++numMeshes; + } + if ( numMeshes == 0 ) { + if ( source != MD5R_SOURCE_MANUAL ) { + MakeDefaultModel(); + } + return false; + } + + int textureDimensions[7] = { 2, 0, 0, 0, 0, 0, 0 }; + rvVertexFormat vertexFormat; + m_numVertexBuffers = 3; + m_allocVertexBuffers = new rvVertexBuffer[m_numVertexBuffers]; + m_vertexBuffers = m_allocVertexBuffers; + if ( m_vertexBuffers == NULL ) { + common->FatalError( "Out of memory" ); + } + vertexFormat.Init( rvVertexFormatFlagPosition, 4, 0, 1, NULL ); + m_vertexBuffers[0].Init( vertexFormat, numVertices, + rvVertexBufferFlagSystemMemory | rvVertexBufferFlagUsageDraw ); + vertexFormat.Init( rvVertexFormatFlagPosition | rvVertexFormatFlagNormal | + rvVertexFormatFlagTangent | rvVertexFormatFlagBinormal | + rvVertexFormatFlagDiffuseColor | rvVertexFormatFlagTextureCoordinate, + 4, 0, 1, textureDimensions ); + m_vertexBuffers[1].Init( vertexFormat, numVertices, + rvVertexBufferFlagSystemMemory | rvVertexBufferFlagVideoMemory | + rvVertexBufferFlagUsageShadow ); + vertexFormat.Init( rvVertexFormatFlagPosition, 4, 0, 1, NULL ); + m_vertexBuffers[2].Init( vertexFormat, numVertices * 2, + rvVertexBufferFlagVideoMemory | rvVertexBufferFlagUsageSilTrace ); + + m_numIndexBuffers = 2; + m_allocIndexBuffers = new rvIndexBuffer[m_numIndexBuffers]; + m_indexBuffers = m_allocIndexBuffers; + if ( m_indexBuffers == NULL ) { + common->FatalError( "Out of memory" ); + } + m_indexBuffers[0].Init( numIndices, rvIndexBufferFlagSystemMemory ); + m_indexBuffers[1].Init( numIndices, + rvIndexBufferFlagSystemMemory | rvIndexBufferFlagVideoMemory ); + + m_numSilEdges = numSilEdges; + m_numSilEdgesAdded = 0; + m_allocSilEdges = static_cast( + Mem_Alloc( numSilEdges * sizeof( silEdge_t ), MA_RENDER ) ); + m_silEdges = m_allocSilEdges; + if ( m_silEdges == NULL && numSilEdges != 0 ) { + common->FatalError( "Out of memory" ); + } + m_numMeshes = numMeshes; + m_meshes = new rvMesh[m_numMeshes]; + if ( m_meshes == NULL ) { + common->FatalError( "Out of memory" ); + } + + int meshIndex = 0; + for ( int i = 0; i < sourceModel.surfaces.Num(); ++i ) { + modelSurface_t &surface = sourceModel.surfaces[i]; + if ( surface.geometry == NULL || + ( numDrawSurfaces > 0 && + ( surface.shader->GetSurfaceFlags() & SURF_COLLISION ) != 0 ) ) { + continue; + } + FixBadTangentSpaces( *surface.geometry ); + int *silRemap = MD5R_CreateSilRemap( *surface.geometry ); + m_meshes[meshIndex].Init( *this, surface, silRemap, + 0, 1, 2, 0, 1, -1 ); + m_meshes[meshIndex].m_meshIdentifier = meshIndex; + delete[] silRemap; + ++meshIndex; + } + BuildLevelsOfDetail(); + bounds = sourceModel.bounds; + for ( int i = 0; i < m_numVertexBuffers; ++i ) { + m_vertexBuffers[i].Resize( m_vertexBuffers[i].GetNumVerticesWritten() ); + } + for ( int i = 0; i < m_numIndexBuffers; ++i ) { + m_indexBuffers[i].Resize( m_indexBuffers[i].GetNumIndicesWritten() ); + } + GenerateStaticSurfaces(); + SetHasSky( source == MD5R_SOURCE_MANUAL ? sourceModel.GetHasSky() : false ); + return true; +} + +bool rvRenderModelMD5R::Init( Lexer &lexer, rvVertexBuffer *vertexBuffers, + int numVertexBuffers, rvIndexBuffer *indexBuffers, int numIndexBuffers, + silEdge_t *silEdges, int maxNumSilEdges, rvMD5RSource_t source ) { + if ( m_meshes != NULL ) { + Shutdown(); + } + m_source = source; + m_vertexBuffers = vertexBuffers; + m_numVertexBuffers = numVertexBuffers; + m_indexBuffers = indexBuffers; + m_numIndexBuffers = numIndexBuffers; + m_silEdges = silEdges; + m_numSilEdges = maxNumSilEdges; + m_numSilEdgesAdded = maxNumSilEdges; + purged = false; + + bool result = true; + idToken token; + lexer.ReadToken( &token ); + if ( token.Icmp( "Mesh" ) != 0 ) { + result = false; + } else { + ParseMeshes( lexer ); + BuildLevelsOfDetail(); + lexer.ReadToken( &token ); + } + if ( token.Icmp( "Bounds" ) == 0 ) { + bounds[0].x = lexer.ParseFloat(); + bounds[0].y = lexer.ParseFloat(); + bounds[0].z = lexer.ParseFloat(); + bounds[1].x = lexer.ParseFloat(); + bounds[1].y = lexer.ParseFloat(); + bounds[1].z = lexer.ParseFloat(); + lexer.ReadToken( &token ); + } + if ( result ) { + if ( token.Icmp( "HasSky" ) == 0 ) { + SetHasSky( true ); + } else { + lexer.UnreadToken( &token ); + } + if ( m_numJoints == 0 ) { + GenerateStaticSurfaces(); + } + } else { + lexer.UnreadToken( &token ); + } + return result; +} + +void rvRenderModelMD5R::InitFromFile( const char *fileName ) { + if ( m_meshes != NULL ) { + Shutdown(); + } + name = fileName; + m_source = MD5R_SOURCE_FILE; + LoadModel(); + reloadable = true; +} + +void rvRenderModelMD5R::LoadModel() { + idAutoPtr lexer( LexerFactory::MakeLexer( + LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES ) ); + + if ( m_source == MD5R_SOURCE_MD5 ) { + idAutoPtr md5Model( new idRenderModelMD5 ); + if ( md5Model.get() == NULL ) { + common->FatalError( "rvRenderModelMD5R: load failed, out of memory" ); + return; + } + md5Model->InitFromFile( name.c_str() ); + if ( md5Model->IsDefaultModel() ) { + MakeDefaultModel(); + } else { + Init( *md5Model ); + } + return; + } + + if ( m_source == MD5R_SOURCE_LWO_ASE_FLT ) { + idAutoPtr staticModel( new idRenderModelStatic ); + if ( staticModel.get() == NULL ) { + common->FatalError( "rvRenderModelMD5R: load failed, out of memory" ); + return; + } + staticModel->InitFromFile( name.c_str() ); + if ( staticModel->IsDefaultModel() ) { + MakeDefaultModel(); + } else { + Init( *staticModel, MD5R_SOURCE_LWO_ASE_FLT ); + } + return; + } + + if ( m_meshes != NULL ) { + Shutdown(); + } + purged = false; + if ( !lexer->LoadFile( name.c_str() ) ) { + MakeDefaultModel(); + return; + } + + lexer->ExpectTokenString( "MD5RVersion" ); + const int version = lexer->ParseInt(); + if ( version != 1 ) { + lexer->Error( "Invalid version %d. Should be version %d\n", version, 1 ); + } + + idToken token; + lexer->ReadToken( &token ); + if ( token.Icmp( "CommandLine" ) == 0 ) { + lexer->ReadToken( &token ); + } + if ( token.Icmp( "Joint" ) == 0 ) { + ParseJoints( *lexer ); + lexer->ReadToken( &token ); + } + if ( token.Icmp( "VertexBuffer" ) != 0 ) { + lexer->Error( "Expected VertexBuffer keyword" ); + } + ParseVertexBuffers( *lexer ); + lexer->ReadToken( &token ); + if ( token.Icmp( "IndexBuffer" ) == 0 ) { + ParseIndexBuffers( *lexer ); + lexer->ReadToken( &token ); + } + if ( token.Icmp( "SilhouetteEdge" ) == 0 ) { + ParseSilhouetteEdges( *lexer ); + lexer->ReadToken( &token ); + } + if ( token.Icmp( "LevelOfDetail" ) == 0 ) { + ParseLevelOfDetail( *lexer ); + lexer->ReadToken( &token ); + } + if ( token.Icmp( "Mesh" ) != 0 ) { + lexer->Error( "Expected Mesh keyword" ); + } + ParseMeshes( *lexer ); + BuildLevelsOfDetail(); + + lexer->ExpectTokenString( "Bounds" ); + bounds[0].x = lexer->ParseFloat(); + bounds[0].y = lexer->ParseFloat(); + bounds[0].z = lexer->ParseFloat(); + bounds[1].x = lexer->ParseFloat(); + bounds[1].y = lexer->ParseFloat(); + bounds[1].z = lexer->ParseFloat(); + if ( lexer->ReadToken( &token ) && token.Icmp( "HasSky" ) == 0 ) { + SetHasSky( true ); + } + fileSystem->ReadFile( name.c_str(), NULL, &timeStamp ); + if ( m_numJoints == 0 ) { + GenerateStaticSurfaces(); + } +} + +modelSurface_t *rvRenderModelMD5R::GenerateSurface( + idRenderModelStatic &staticModel, rvMesh &mesh, + const renderEntity_t &entity, unsigned int surfaceMask ) { + const idMaterial *material = R_RemapShaderBySkin( mesh.m_material, + entity.customSkin, entity.customShader ); + if ( ( surfaceMask & SURF_COLLISION ) != 0 ) { + if ( material == NULL || + ( material->GetSurfaceFlags() & SURF_COLLISION ) == 0 ) { + staticModel.DeleteSurfaceWithId( mesh.m_meshIdentifier ); + mesh.m_surfaceNum = -1; + return NULL; + } + } else if ( material == NULL || + ( !material->IsDrawn() && !material->SurfaceCastsShadow() ) ) { + staticModel.DeleteSurfaceWithId( mesh.m_meshIdentifier ); + mesh.m_surfaceNum = -1; + return NULL; + } + + int surfaceNum = -1; + modelSurface_t *surface = NULL; + if ( staticModel.FindSurfaceWithId( mesh.m_meshIdentifier, surfaceNum ) ) { + mesh.m_surfaceNum = surfaceNum; + surface = &staticModel.surfaces[surfaceNum]; + } else { + mesh.m_surfaceNum = staticModel.NumSurfaces(); + surface = &staticModel.surfaces.Alloc(); + surface->geometry = NULL; + surface->shader = NULL; + surface->id = mesh.m_meshIdentifier; + surface->mOriginalSurfaceName = NULL; + } + mesh.UpdateSurface( *surface, entity, m_skinSpaceToLocalMats ); + staticModel.bounds.AddBounds( surface->geometry->bounds ); + return surface; +} + +void rvRenderModelMD5R::GenerateSurface( rvMesh &mesh ) { + mesh.m_surfaceNum = NumSurfaces(); + modelSurface_t &surface = surfaces.Alloc(); + surface.geometry = NULL; + surface.shader = NULL; + surface.id = mesh.m_meshIdentifier; + surface.mOriginalSurfaceName = NULL; + mesh.UpdateSurface( surface ); +} + +idRenderModel *rvRenderModelMD5R::InstantiateDynamicModel( + const renderEntity_t *entity, const viewDef_t *view, + idRenderModel *cachedModel, dword surfaceMask ) { + if ( m_numJoints == 0 || entity == NULL ) { + return NULL; + } + if ( cachedModel != NULL && !r_useCachedDynamicModels.GetBool() ) { + delete cachedModel; + cachedModel = NULL; + } + if ( purged ) { + common->DWarning( "model %s instantiated while purged", Name() ); + LoadModel(); + } + if ( entity->joints == NULL ) { + common->Printf( "rvRenderModelMD5R::InstantiateDynamicModel: NULL joints on renderEntity for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } + if ( entity->numJoints != m_numJoints ) { + common->Printf( "rvRenderModelMD5R::InstantiateDynamicModel: renderEntity has different number of joints than model for '%s'\n", Name() ); + delete cachedModel; + return NULL; + } + + idRenderModelStatic *staticModel; + if ( cachedModel != NULL ) { + staticModel = static_cast( cachedModel ); + } else { + staticModel = new idRenderModelStatic; + staticModel->InitEmpty( MD5R_SnapshotName ); + } + staticModel->bounds.Clear(); + if ( r_showSkel.GetInteger() != 0 && view != NULL && + ( !r_skipSuppress.GetBool() || entity->suppressSurfaceInViewID == 0 || + entity->suppressSurfaceInViewID != view->renderView.viewID ) ) { + DrawJoints( entity, view ); + if ( r_showSkel.GetInteger() > 1 ) { + staticModel->InitEmpty( MD5R_SnapshotName ); + return staticModel; + } + } + + int level = 0; + if ( view != NULL ) { + const float distanceSquared = view->renderView.vieworg.Dist2( entity->origin ); + while ( level < m_numLODs && + distanceSquared >= m_lods[level].m_rangeEndSquared ) { + ++level; + } + } + if ( level != staticModel->levelOfDetail ) { + staticModel->InitEmpty( MD5R_SnapshotName ); + staticModel->levelOfDetail = level; + } + if ( level < m_numLODs ) { + for ( rvMesh *mesh = m_lods[level].m_meshList; mesh != NULL; + mesh = mesh->m_nextInLOD ) { + GenerateSurface( *staticModel, *mesh, *entity, surfaceMask ); + } + } + for ( rvMesh *mesh = m_allLODMeshes; mesh != NULL; mesh = mesh->m_nextInLOD ) { + GenerateSurface( *staticModel, *mesh, *entity, surfaceMask ); + } + return staticModel; +} + +void rvRenderModelMD5R::GenerateStaticSurfaces() { + if ( purged ) { + common->DWarning( "model %s instantiated while purged", Name() ); + LoadModel(); + } + for ( int i = 0; i < m_numMeshes; ++i ) { + GenerateSurface( m_meshes[i] ); + } +} + +void rvRenderModelMD5R::WriteAll( bool writeBuffers ) { + const bool oldFileLoadingAllowed = fileSystem->GetIsFileLoadingAllowed(); + fileSystem->SetIsFileLoadingAllowed( true ); + for ( rvRenderModelMD5R *model = ms_modelList; model != NULL; + model = model->m_next ) { + if ( model->m_meshes == NULL || model->purged || + ( model->m_source != MD5R_SOURCE_MD5 && + model->m_source != MD5R_SOURCE_LWO_ASE_FLT ) ) { + continue; + } + + idStr md5rName = model->name; + md5rName.StripAbsoluteFileExtension(); + if ( model->m_source == MD5R_SOURCE_LWO_ASE_FLT ) { + md5rName += "_static"; + } + md5rName += ".md5r"; + + unsigned int md5rTimestamp = 0; + idFile *input = fileSystem->OpenFileRead( md5rName.c_str() ); + if ( input != NULL ) { + md5rTimestamp = input->Timestamp(); + fileSystem->CloseFile( input ); + } + + unsigned int sourceTimestamp = 0; + idStr sourceName = model->name; + sourceName.StripAbsoluteFileExtension(); + if ( model->m_source == MD5R_SOURCE_MD5 ) { + sourceName += ".md5mesh"; + input = fileSystem->OpenFileRead( sourceName.c_str() ); + } else { + const char *extensions[3] = { ".lwo", ".ase", ".flt" }; + input = NULL; + for ( int i = 0; i < 3 && input == NULL; ++i ) { + sourceName = model->name; + sourceName.StripAbsoluteFileExtension(); + sourceName += extensions[i]; + input = fileSystem->OpenFileRead( sourceName.c_str() ); + } + } + if ( input != NULL ) { + sourceTimestamp = input->Timestamp(); + fileSystem->CloseFile( input ); + } + if ( md5rTimestamp != 0 && md5rTimestamp > sourceTimestamp ) { + continue; + } + + idFile *output = fileSystem->OpenFileWrite( + md5rName.c_str(), "fs_savepath", false ); + if ( output == NULL ) { + continue; + } + common->Printf( "writing %s\n", md5rName.c_str() ); + output->WriteFloatString( "MD5RVersion %d\n", 1 ); + model->Write( *output, "", writeBuffers ); + fileSystem->CloseFile( output ); + idLexer::WriteBinaryFile( md5rName.c_str() ); + } + fileSystem->SetIsFileLoadingAllowed( oldFileLoadingAllowed ); +} + +int rvRenderModelMD5R::Memory() const { + return 0; +} + +bool rvRenderModelMD5R::HasSeparateSilTraceMeshes() const { + return true; +} diff --git a/src/renderer/rvRenderModelMD5R.h b/src/renderer/rvRenderModelMD5R.h new file mode 100644 index 0000000..c2dff41 --- /dev/null +++ b/src/renderer/rvRenderModelMD5R.h @@ -0,0 +1,151 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software + +#ifndef __RENDERER_RVRENDERMODELMD5R_H__ +#define __RENDERER_RVRENDERMODELMD5R_H__ + +#include "Model_local.h" +#include "rvIndexBuffer.h" +#include "rvVertexBuffer.h" + +class rvMesh; +class idCollisionModelLocal; +class idCollisionModelManagerLocal; + +struct levelOfDetailMD5R_s { + float m_rangeEnd; + float m_rangeEndSquared; + rvMesh *m_meshList; +}; + +enum rvMD5RSource_t { + MD5R_SOURCE_NONE = 0, + MD5R_SOURCE_FILE, + MD5R_SOURCE_PROC, + MD5R_SOURCE_MD5, + MD5R_SOURCE_LWO_ASE_FLT, + MD5R_SOURCE_MANUAL +}; + +class rvRenderModelMD5R : public idRenderModelStatic { +friend class rvMesh; +public: + rvRenderModelMD5R(); + virtual ~rvRenderModelMD5R(); + + virtual void InitFromFile( const char *fileName ); + virtual void PurgeModel(); + virtual void LoadModel(); + virtual void TouchData(); + virtual void Print() const; + virtual void List() const {} + virtual int Memory() const; + virtual dynamicModel_t IsDynamicModel() const; + virtual idBounds Bounds( const renderEntity_t *entity ) const; + virtual bool HasCollisionSurface( const renderEntity_t *entity ) const; + virtual idRenderModel *InstantiateDynamicModel( const renderEntity_t *entity, + const viewDef_t *view, idRenderModel *cachedModel, dword surfaceMask ); + virtual int NumJoints() const; + virtual const idMD5Joint *GetJoints() const; + virtual jointHandle_t GetJointHandle( const char *jointName ) const; + virtual const char *GetJointName( jointHandle_t jointHandle ) const; + virtual const idJointQuat *GetDefaultPose() const; + virtual int NearestJoint( int surfaceNum, int a, int b, int c ) const; + virtual int GetSurfaceMask( const char *surfaceName ) const; + virtual bool HasSeparateSilTraceMeshes() const; + virtual srfTriangles_t *GenerateStaticTriSurface( int meshOffset ); + + bool Init( idRenderModelMD5 &model ); + bool Init( idRenderModelStatic &model, rvVertexBuffer *vertexBuffers, + int vertexBufferStart, int numVertexBuffers, rvIndexBuffer *indexBuffers, + int indexBufferStart, int numIndexBuffers, silEdge_t *silEdges, + int silEdgeStart, int maxNumSilEdges, rvMD5RSource_t source ); + bool Init( idRenderModelStatic &model, rvMD5RSource_t source ); + bool Init( Lexer &lexer, rvVertexBuffer *vertexBuffers, int numVertexBuffers, + rvIndexBuffer *indexBuffers, int numIndexBuffers, silEdge_t *silEdges, + int maxNumSilEdges, rvMD5RSource_t source ); + void Shutdown(); + void GenerateCollisionModel( idCollisionModelManagerLocal &modelManager, + idCollisionModelLocal &collisionModel ); + void GenerateStaticSurfaces(); + void Write( idFile &outFile, const char *prepend, bool writeBuffers ); + void WriteSansBuffers( idFile &outFile, const char *prepend ); + static void WriteAll( bool writeBuffers ); + static void CompressVertexFormat( rvVertexFormat &compressedFormat, + const rvVertexFormat &format ); + +protected: + void ResetValues(); + void ParseVertexBuffers( Lexer &lexer ); + void ParseIndexBuffers( Lexer &lexer ); + void ParseSilhouetteEdges( Lexer &lexer ); + void ParseLevelOfDetail( Lexer &lexer ); + void ParseMeshes( Lexer &lexer ); + void ParseJoints( Lexer &lexer ); + void BuildLevelsOfDetail(); + int CalcMaxBonesPerVertex( const jointWeight_t *jointWeights, int numVerts, + const srfTriangles_t &tri ); + void FixBadTangentSpaces( srfTriangles_t &tri ); + void DrawJoints( const renderEntity_t *entity, const viewDef_t *view ) const; + int BuildBlend4VertArray( idMD5Mesh &md5Mesh, modelSurface_t &surface, + rvBlend4DrawVert *drawVertices ); + modelSurface_t *GenerateSurface( idRenderModelStatic &staticModel, + rvMesh &mesh, const renderEntity_t &entity, unsigned int surfaceMask ); + void GenerateSurface( rvMesh &mesh ); + void WriteSilhouetteEdges( idFile &outFile, const char *prepend, + const char *prependPlusTab ); + void WriteLevelOfDetail( idFile &outFile, const char *prepend, + const char *prependPlusTab ); + void WriteMeshes( idFile &outFile, const char *prepend ); + void WriteJoints( idFile &outFile, const char *prepend, + const char *prependPlusTab ); + static void RemoveFromList( rvRenderModelMD5R &model ); + static rvRenderModelMD5R *ms_modelList; + + idMD5Joint * m_joints; + idJointMat * m_skinSpaceToLocalMats; + idJointQuat * m_defaultPose; + int m_numJoints; + rvVertexBuffer * m_vertexBuffers; + rvVertexBuffer * m_allocVertexBuffers; + int m_numVertexBuffers; + rvIndexBuffer * m_indexBuffers; + rvIndexBuffer * m_allocIndexBuffers; + int m_numIndexBuffers; + silEdge_t * m_silEdges; + silEdge_t * m_allocSilEdges; + int m_numSilEdges; + int m_numSilEdgesAdded; + rvMesh * m_meshes; + int m_numMeshes; + int m_numLODs; + levelOfDetailMD5R_s * m_lods; + rvMesh * m_allLODMeshes; + rvMD5RSource_t m_source; + rvRenderModelMD5R * m_next; +}; + +#endif diff --git a/src/renderer/rvSpecial.cpp b/src/renderer/rvSpecial.cpp new file mode 100644 index 0000000..040e5a6 --- /dev/null +++ b/src/renderer/rvSpecial.cpp @@ -0,0 +1,620 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software +// +// Quake 4 special-effect render targets reconstructed from quake4.pdb and +// the licensed retail executable. This is the retail rvSpecial.obj owner. + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "RenderWorld_local.h" +#include "rvSpecial.h" +#include "rvTexRenderTarget.h" +#include "Shaders.h" + +int numDrawn; +rvBlurTexture *DepthTexture; +rvAL *AL; +const idMaterial *ALMaterial; + +namespace { + +static void RB_SpecialFullScreenQuad() { + qglBegin( GL_QUADS ); + qglTexCoord2f( 0.0f, 1.0f ); qglVertex2f( 0.0f, 0.0f ); + qglTexCoord2f( 1.0f, 1.0f ); qglVertex2f( 640.0f, 0.0f ); + qglTexCoord2f( 1.0f, 0.0f ); qglVertex2f( 640.0f, 480.0f ); + qglTexCoord2f( 0.0f, 0.0f ); qglVertex2f( 0.0f, 480.0f ); + qglEnd(); +} + +static void RB_ResetSpecialTextureState() { + backEnd.glState.forceGlState = true; + for ( int unit = 0; unit < MAX_MULTITEXTURE_UNITS; ++unit ) { + GL_SelectTexture( unit ); + qglDisable( GL_TEXTURE_CUBE_MAP_ARB ); + qglDisable( GL_TEXTURE_3D ); + qglDisable( GL_TEXTURE_2D ); + backEnd.glState.tmu[unit].current2DMap = -1; + backEnd.glState.tmu[unit].current3DMap = -1; + backEnd.glState.tmu[unit].currentCubeMap = -1; + backEnd.glState.tmu[unit].textureType = TT_DISABLED; + globalImages->whiteImage->Bind(); + } + GL_SelectTexture( 0 ); +} + +static rvNewShaderStage *RB_SpecialStage( const idMaterial *material, int index ) { + if ( material == NULL || index < 0 || index >= material->GetNumStages() ) { + return NULL; + } + return material->GetStage( index )->newShaderStage; +} + +static bool RB_ProjectSpecialPoint( const idVec3 &global, idVec3 &ndc ) { + if ( backEnd.viewDef == NULL ) { + return false; + } + idVec4 view; + idVec4 clip; + const float *modelView = backEnd.viewDef->worldSpace.modelViewMatrix; + const float *projection = backEnd.viewDef->projectionMatrix; + for ( int i = 0; i < 4; ++i ) { + view[i] = global[0] * modelView[i + 0 * 4] + + global[1] * modelView[i + 1 * 4] + + global[2] * modelView[i + 2 * 4] + modelView[i + 3 * 4]; + } + for ( int i = 0; i < 4; ++i ) { + clip[i] = view[0] * projection[i + 0 * 4] + + view[1] * projection[i + 1 * 4] + + view[2] * projection[i + 2 * 4] + view[3] * projection[i + 3 * 4]; + } + if ( idMath::Fabs( clip[3] ) < 1.0e-6f ) { + return false; + } + ndc.Set( clip[0] / clip[3], clip[1] / clip[3], clip[2] / clip[3] ); + return true; +} + +static void RB_FillSpecialDepth( const drawSurf_t *surf, bool ambientLightImage ) { + const idMaterial *shader = surf->material; + const srfTriangles_t *tri = surf->geo; + if ( shader == NULL || tri == NULL || !shader->IsDrawn() || + tri->numIndexes == 0 || shader->Coverage() == MC_TRANSLUCENT ) { + return; + } + if ( tri->ambientCache == NULL ) { + common->Printf( "RB_T_FillDepthTexture: !tri->ambientCache\n" ); + return; + } + + const float *registers = surf->shaderRegisters; + int stageIndex; + for ( stageIndex = 0; stageIndex < shader->GetNumStages(); ++stageIndex ) { + if ( registers[shader->GetStage( stageIndex )->conditionRegister] != 0.0f ) { + break; + } + } + if ( stageIndex == shader->GetNumStages() ) { + return; + } + + GL_PolygonOffset( shader, true ); + float color[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + if ( shader->GetSort() == SS_SUBVIEW ) { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO | GLS_DEPTHFUNC_LESS ); + color[0] = color[1] = color[2] = 1.0f / backEnd.overBright; + } + + idDrawVert *ambient = static_cast( vertexCache.Position( tri->ambientCache ) ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ambient->xyz.ToFloatPtr() ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), &ambient->st ); + + bool drawSolid = shader->Coverage() == MC_OPAQUE; + if ( shader->Coverage() == MC_PERFORATED ) { + bool didDraw = false; + qglEnable( GL_ALPHA_TEST ); + for ( int i = 0; i < shader->GetNumStages(); ++i ) { + const shaderStage_t *stage = shader->GetStage( i ); + if ( !stage->hasAlphaTest || registers[stage->conditionRegister] == 0.0f ) { + continue; + } + didDraw = true; + color[3] = registers[stage->color.registers[3]]; + if ( color[3] <= 0.0f ) { + continue; + } + qglColor4fv( color ); + qglAlphaFunc( stage->hasAlphaFunc ? stage->alphaTestMode : GL_GREATER, + registers[stage->alphaTestRegister] ); + stage->texture.image->Bind(); + RB_PrepareStageTexturing( stage, surf, ambient, false ); + RB_DrawElementsWithCounters( tri ); + RB_FinishStageTexturing( stage, surf, ambient, false ); + } + qglDisable( GL_ALPHA_TEST ); + if ( !didDraw ) { + drawSolid = true; + } + } + + if ( drawSolid ) { + qglColor4fv( color ); + if ( ambientLightImage && ALMaterial != NULL && AL != NULL ) { + for ( int i = 0; i < shader->GetNumStages(); ++i ) { + const shaderStage_t *stage = shader->GetStage( i ); + if ( stage->lighting == SL_DIFFUSE ) { + rvNewShaderStage *alStage = RB_SpecialStage( ALMaterial, 0 ); + if ( alStage != NULL ) { + alStage->SetTextureParm( "Image", stage->texture.image ); + alStage->Bind( AL->regs, NULL ); + } + break; + } + } + } else { + globalImages->whiteImage->Bind(); + } + RB_DrawElementsWithCounters( tri ); + } + + GL_PolygonOffset( shader, false ); + if ( shader->GetSort() == SS_SUBVIEW ) { + GL_State( GLS_DEPTHFUNC_LESS ); + } +} + +} + +bool rvBlurTexture::CreateBuffer( const char *name, idPBufferImage **image ) { + *image = globalImages->AllocPBufferImage( name ); + if ( *image == NULL ) { + return false; + } + (*image)->useCount = 1; + (*image)->type = TT_2D; + (*image)->uploadWidth = 256; + (*image)->uploadHeight = 256; + (*image)->mRenderTarget->Init( 256, 256, 32, 8, 8, 8, 8, 24, 0, + rvTexRenderTargetFlagDepthStencil | rvTexRenderTargetFlagMipMap ); + (*image)->mRenderTarget->DefaultD3GL(); + return true; +} + +bool rvAL::CreateBuffer( const char *name, idPBufferImage **image ) { + *image = globalImages->AllocPBufferImage( name ); + if ( *image == NULL ) { + return false; + } + (*image)->useCount = 1; + (*image)->type = TT_2D; + (*image)->uploadWidth = 512; + (*image)->uploadHeight = 512; + (*image)->mRenderTarget->Init( 512, 512, 32, 8, 8, 8, 8, 24, 0, + rvTexRenderTargetFlagDepthStencil | rvTexRenderTargetFlagMipMap ); + (*image)->mRenderTarget->DefaultD3GL(); + return true; +} + +void RB_RestoreDrawingView() { + if ( backEnd.viewDef == NULL ) { + return; + } + qglMatrixMode( GL_PROJECTION ); + qglLoadMatrixf( backEnd.viewDef->projectionMatrix ); + qglMatrixMode( GL_MODELVIEW ); + qglLoadIdentity(); + backEnd.currentSpace = NULL; + qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1, + tr.viewportOffset[1] + backEnd.viewDef->viewport.y1, + backEnd.viewDef->viewport.x2 - backEnd.viewDef->viewport.x1 + 1, + backEnd.viewDef->viewport.y2 - backEnd.viewDef->viewport.y1 + 1 ); + qglScissor( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1 + backEnd.viewDef->scissor.x1, + tr.viewportOffset[1] + backEnd.viewDef->viewport.y1 + backEnd.viewDef->scissor.y1, + backEnd.viewDef->scissor.x2 - backEnd.viewDef->scissor.x1 + 1, + backEnd.viewDef->scissor.y2 - backEnd.viewDef->scissor.y1 + 1 ); + backEnd.currentScissor = backEnd.viewDef->scissor; + GL_State( GLS_DEFAULT ); + if ( backEnd.viewDef->viewEntitys != NULL ) { + qglEnable( GL_DEPTH_TEST ); + qglEnable( GL_STENCIL_TEST ); + } else { + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_STENCIL_TEST ); + } + backEnd.glState.faceCulling = -1; + GL_Cull( CT_FRONT_SIDED ); +} + +void R_AddSpecialEffects( viewDef_t *parms ) { + if ( ( tr.specialEffectsEnabled & ( SPECIAL_EFFECT_BLUR | SPECIAL_EFFECT_AL ) ) == 0 ) { + return; + } + drawSurfsCommand_t *cmd = static_cast( + R_GetCommandBuffer( sizeof( *cmd ) ) ); + cmd->commandId = RC_DRAW_DEPTH_TEXTURE; + cmd->viewDef = parms; +} + +void idRenderSystemLocal::SetSpecialEffectParm( ESpecialEffectType which, int parm, float value ) { + if ( parm < 0 || parm >= 12 ) { + return; + } + if ( which == SPECIAL_EFFECT_BLUR && DepthTexture != NULL ) { + DepthTexture->shaderParms[parm] = value; + } else if ( which == SPECIAL_EFFECT_AL && AL != NULL ) { + AL->shaderParms[parm] = value; + } +} + +rvBlurTexture::rvBlurTexture() : + mDepthImage( NULL ), mDepthMaterial( NULL ), regs( NULL ) { + mBlurImage[0] = mBlurImage[1] = NULL; + memset( shaderParms, 0, sizeof( shaderParms ) ); + CreateBuffer( "DepthTexture", &mDepthImage ); + CreateBuffer( "BlurTexture1", &mBlurImage[0] ); + mDepthMaterial = declManager->FindMaterial( "hs/MedLabs", true ); + regs = static_cast( Mem_ClearedAlloc( + mDepthMaterial->GetNumRegisters() * sizeof( float ), MA_RENDER ) ); + shaderParms[0] = 0.694f; + shaderParms[1] = 0.694f; + shaderParms[2] = 0.694f; + shaderParms[3] = 1.0f; + shaderParms[4] = 4.0f; + shaderParms[5] = 0.31f; + shaderParms[6] = 0.5f; + shaderParms[7] = 500.0f; +} + +void RB_T_FillDepthTexture( const drawSurf_t *surf ) { + RB_FillSpecialDepth( surf, false ); +} + +void rvBlurTexture::Render( drawSurfsCommand_t *drawSurfs ) { + if ( mDepthImage == NULL || mDepthImage->mRenderTarget == NULL || + drawSurfs == NULL || drawSurfs->viewDef == NULL ) { + return; + } + mDepthImage->mRenderTarget->BeginRender( 0 ); + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + qglClearDepth( 1.0 ); + qglDepthRange( 0.0, 1.0 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + backEnd.viewDef = drawSurfs->viewDef; + RB_BeginDrawingView(); + qglViewport( 0, 0, mDepthImage->uploadWidth, mDepthImage->uploadHeight ); + qglDisable( GL_BLEND ); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + GL_SelectTexture( 1 ); + GL_SelectTexture( 0 ); + globalImages->defaultImage->Bind(); + globalImages->whiteImage->Bind(); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnableClientState( GL_VERTEX_ARRAY ); + qglDepthFunc( GL_LEQUAL ); + qglDisable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + idVec3 randomizer( 0.0f, 0.0f, 0.0f ); + mDepthMaterial->EvaluateRegisters( regs, shaderParms, drawSurfs->viewDef, 0, &randomizer ); + rvNewShaderStage *stage = RB_SpecialStage( mDepthMaterial, 0 ); + if ( stage != NULL ) { + stage->Bind( regs, NULL ); + } + RB_RenderDrawSurfListWithFunction( drawSurfs->viewDef->drawSurfs, + drawSurfs->viewDef->numDrawSurfs, RB_T_FillDepthTexture ); + if ( stage != NULL ) { + stage->UnBind(); + } + mDepthImage->mRenderTarget->EndRender( true ); + RB_ResetSpecialTextureState(); +} + +void rvBlurTexture::Display( viewEntity_t *viewEnts, bool prePass ) { + if ( !prePass || viewEnts != NULL || mBlurImage[0] == NULL ) { + return; + } + mBlurImage[0]->mRenderTarget->BeginRender( 0 ); + RB_SetGL2D(); + qglViewport( 0, 0, mBlurImage[0]->uploadWidth, mBlurImage[0]->uploadHeight ); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + qglClearDepth( 1.0 ); + qglDepthRange( 0.0, 1.0 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + qglDisable( GL_BLEND ); + GL_SelectTexture( 1 ); + GL_SelectTexture( 0 ); + globalImages->defaultImage->Bind(); + globalImages->whiteImage->Bind(); + rvNewShaderStage *blurStage = RB_SpecialStage( mDepthMaterial, 1 ); + if ( blurStage != NULL ) { + blurStage->Bind( regs, NULL ); + RB_SpecialFullScreenQuad(); + blurStage->UnBind(); + } + mBlurImage[0]->mRenderTarget->EndRender( true ); + + RB_SetGL2D(); + qglBlendFunc( GL_ONE, GL_ZERO ); + rvNewShaderStage *displayStage = RB_SpecialStage( mDepthMaterial, 2 ); + if ( displayStage != NULL ) { + displayStage->Bind( regs, NULL ); + qglEnable( GL_BLEND ); + qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + RB_SpecialFullScreenQuad(); + displayStage->UnBind(); + } + backEnd.glState.forceGlState = true; +} + +rvAL::rvAL() : + mainStage( NULL ), mLightLocParm( -1 ), mLightColorParm( -1 ), + mLightSizeParm( -1 ), mLightMinDistanceParm( -1 ), mDepthImage( NULL ), + mDepthMaterial( NULL ), offset( 0.0f ), count( 0 ), regs( NULL ) { + mBlurImage[0] = mBlurImage[1] = NULL; + memset( shaderParms, 0, sizeof( shaderParms ) ); + CreateBuffer( "DepthTexture", &mDepthImage ); + CreateBuffer( "BlurTexture1", &mBlurImage[0] ); + mDepthMaterial = declManager->FindMaterial( "hs/ALSetup", true ); + ALMaterial = mDepthMaterial; + regs = static_cast( Mem_ClearedAlloc( + mDepthMaterial->GetNumRegisters() * sizeof( float ), MA_RENDER ) ); + + idRenderWorldLocal *world = tr.primaryWorld; + if ( world != NULL ) { + for ( int i = 0; i < world->entityDefs.Num() && count < 100; ++i ) { + const idRenderEntityLocal *entity = world->entityDefs[i]; + if ( entity == NULL ) { + continue; + } + lOrigin[count] = entity->parms.origin; + lColor[count].Set( entity->parms.shaderParms[0], + entity->parms.shaderParms[1], entity->parms.shaderParms[2] ); + if ( lColor[count].Normalize() == 0.0f ) { + lColor[count].Set( 1.0f, 1.0f, 1.0f ); + lColor[count].Normalize(); + } + lSize[count] = 300.0f; + ++count; + } + } +} + +void RB_T_FillDepthTextureAL( const drawSurf_t *surf ) { + RB_FillSpecialDepth( surf, true ); +} + +void rvAL::Render( drawSurfsCommand_t *drawSurfs ) { + if ( mDepthImage == NULL || mDepthImage->mRenderTarget == NULL || + drawSurfs == NULL || drawSurfs->viewDef == NULL ) { + return; + } + mDepthImage->mRenderTarget->BeginRender( 0 ); + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + qglClearDepth( 1.0 ); + qglDepthRange( 0.0, 1.0 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + backEnd.viewDef = drawSurfs->viewDef; + RB_BeginDrawingView(); + memcpy( backEnd.projectionMatrix, drawSurfs->viewDef->projectionMatrix, + sizeof( backEnd.projectionMatrix ) ); + qglViewport( 0, 0, mDepthImage->uploadWidth, mDepthImage->uploadHeight ); + qglDisable( GL_BLEND ); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + GL_SelectTexture( 1 ); + GL_SelectTexture( 0 ); + globalImages->defaultImage->Bind(); + globalImages->whiteImage->Bind(); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnableClientState( GL_VERTEX_ARRAY ); + qglDepthFunc( GL_LEQUAL ); + qglDisable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + idVec3 randomizer( 0.0f, 0.0f, 0.0f ); + mDepthMaterial->EvaluateRegisters( regs, shaderParms, drawSurfs->viewDef, 0, &randomizer ); + rvNewShaderStage *stage = RB_SpecialStage( mDepthMaterial, 0 ); + if ( stage != NULL ) { + stage->Bind( regs, NULL ); + } + RB_RenderDrawSurfListWithFunction( drawSurfs->viewDef->drawSurfs, + drawSurfs->viewDef->numDrawSurfs, RB_T_FillDepthTextureAL ); + if ( stage != NULL ) { + stage->UnBind(); + } + mDepthImage->mRenderTarget->EndRender( true ); + RB_ResetSpecialTextureState(); +} + +void RB_SetGL2D2() { + qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + if ( r_useScissor.GetBool() ) { + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + } + qglMatrixMode( GL_PROJECTION ); + qglLoadIdentity(); + qglOrtho( 0.0, 640.0, 480.0, 0.0, 0.0, 1.0 ); + qglMatrixMode( GL_MODELVIEW ); + qglLoadIdentity(); + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_STENCIL_TEST ); +} + +void R_ShutdownSpecialEffects() { + if ( DepthTexture != NULL ) { + Mem_Free( DepthTexture->regs ); + delete DepthTexture; + DepthTexture = NULL; + } + if ( AL != NULL ) { + Mem_Free( AL->regs ); + delete AL; + AL = NULL; + } + ALMaterial = NULL; + tr.specialEffectsEnabled = 0; +} + +void RB_DrawDepthTexture( const void *data ) { + const drawSurfsCommand_t *cmd = static_cast( data ); + if ( cmd == NULL || cmd->viewDef == NULL ) { + return; + } + backEnd.viewDef = cmd->viewDef; + if ( backEnd.viewDef->numDrawSurfs != 0 ) { + if ( DepthTexture != NULL ) { + DepthTexture->Render( const_cast( cmd ) ); + } + if ( AL != NULL ) { + AL->Render( const_cast( cmd ) ); + } + } +} + +void idRenderSystemLocal::SetSpecialEffect( ESpecialEffectType which, bool enabled ) { + if ( enabled ) { + specialEffectsEnabled |= which; + if ( which == SPECIAL_EFFECT_BLUR && DepthTexture == NULL && glConfig.allowARB2Path ) { + DepthTexture = new rvBlurTexture; + } else if ( which == SPECIAL_EFFECT_AL && AL == NULL && glConfig.allowARB2Path ) { + AL = new rvAL; + } + } else { + specialEffectsEnabled &= ~which; + } +} + +void idRenderSystemLocal::ShutdownSpecialEffects() { + R_ShutdownSpecialEffects(); +} + +void rvAL::DrawLight( const idVec3 &origin, float size, const idVec3 &color ) { + if ( mainStage == NULL || backEnd.viewDef == NULL ) { + return; + } + for ( int i = 0; i < 5; ++i ) { + if ( backEnd.viewDef->frustum[i].Distance( origin ) > size ) { + return; + } + } + + const idVec3 right = backEnd.viewDef->renderView.viewaxis[1] * size; + const idVec3 up = backEnd.viewDef->renderView.viewaxis[2] * size; + idVec3 points[4]; + points[0] = origin + right + up; + points[1] = origin - right + up; + points[2] = origin - right - up; + points[3] = origin + right - up; + idVec3 ndc0, ndc2; + if ( !RB_ProjectSpecialPoint( points[0], ndc0 ) || + !RB_ProjectSpecialPoint( points[2], ndc2 ) ) { + return; + } + + const idScreenRect &viewport = backEnd.viewDef->viewport; + float x1 = ( viewport.x2 - viewport.x1 ) * ( ndc0.x + 1.0f ) * 0.5f; + float y1 = viewport.y2 - ( viewport.y2 - viewport.y1 ) * ( ndc0.y + 1.0f ) * 0.5f; + float x2 = ( viewport.x2 - viewport.x1 ) * ( ndc2.x + 1.0f ) * 0.5f; + float y2 = viewport.y2 - ( viewport.y2 - viewport.y1 ) * ( ndc2.y + 1.0f ) * 0.5f; + if ( x1 > x2 ) { + idSwap( x1, x2 ); + } + if ( y1 > y2 ) { + idSwap( y1, y2 ); + } + + idVec3 globalOrigin; + R_LocalPointToGlobal( backEnd.viewDef->worldSpace.modelMatrix, origin, globalOrigin ); + idVec3 planeNormal = backEnd.viewDef->renderView.viewaxis[2].Cross( + backEnd.viewDef->renderView.viewaxis[1] ); + planeNormal.Normalize(); + const float minDistance = planeNormal * + ( origin - backEnd.viewDef->renderView.vieworg ); + mainStage->SetShaderParameter( mLightLocParm, regs, globalOrigin.ToFloatPtr(), 3 ); + mainStage->SetShaderParameter( mLightColorParm, regs, + const_cast( color.ToFloatPtr() ), 3 ); + mainStage->SetShaderParameter( mLightSizeParm, regs, &size, 1 ); + float distance = idMath::Fabs( minDistance ); + mainStage->SetShaderParameter( mLightMinDistanceParm, regs, &distance, 1 ); + mainStage->UpdateShaderParms( regs, NULL ); + + qglBegin( GL_QUADS ); + qglTexCoord2f( x1 / 639.0f, 1.0f - y1 / 479.0f ); + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0.0f, 0.0f ); qglVertex2f( x1, y1 ); + qglTexCoord2f( x2 / 639.0f, 1.0f - y1 / 479.0f ); + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 1.0f, 0.0f ); qglVertex2f( x2, y1 ); + qglTexCoord2f( x2 / 639.0f, 1.0f - y2 / 479.0f ); + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 1.0f, 1.0f ); qglVertex2f( x2, y2 ); + qglTexCoord2f( x1 / 639.0f, 1.0f - y2 / 479.0f ); + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0.0f, 1.0f ); qglVertex2f( x1, y2 ); + qglEnd(); + ++numDrawn; +} + +void rvAL::Display( viewEntity_t *viewEnts, bool prePass ) { + if ( prePass ) { + return; + } + mainStage = RB_SpecialStage( ALMaterial, 1 ); + if ( mainStage == NULL ) { + return; + } + mLightLocParm = mainStage->FindShaderParameter( "LightLoc" ); + mLightColorParm = mainStage->FindShaderParameter( "LightColor" ); + mLightSizeParm = mainStage->FindShaderParameter( "LightSize" ); + mLightMinDistanceParm = mainStage->FindShaderParameter( "LightMinDistance" ); + RB_SetGL2D2(); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + GL_State( GLS_DSTBLEND_ONE ); + numDrawn = 0; + if ( viewEnts != NULL && !cvarSystem->GetCVarBool( "rj" ) ) { + mainStage->Bind( regs, NULL ); + DrawLight( vec3_origin, 200.0f, idVec3( 1.0f, 1.0f, 0.0f ) ); + offset += 0.005f; + for ( int i = 0; i < count; ++i ) { + idVec3 animatedOrigin = lOrigin[i]; + animatedOrigin.x += idMath::Cos( 0.78423f * i + offset ) * 0.0f; + animatedOrigin.y += idMath::Cos( 0.234f * i + offset ) * 0.0f; + DrawLight( animatedOrigin, lSize[i], lColor[i] ); + } + mainStage->UnBind(); + } + backEnd.glState.forceGlState = true; + RB_RestoreDrawingView(); +} + +void RB_DisplaySpecialEffects( viewEntity_t *viewEnts, bool prePass ) { + if ( ( tr.specialEffectsEnabled & SPECIAL_EFFECT_BLUR ) != 0 && DepthTexture != NULL ) { + DepthTexture->Display( viewEnts, prePass ); + } + if ( ( tr.specialEffectsEnabled & SPECIAL_EFFECT_AL ) != 0 && AL != NULL ) { + AL->Display( viewEnts, prePass ); + } +} diff --git a/src/renderer/rvSpecial.h b/src/renderer/rvSpecial.h new file mode 100644 index 0000000..795d5cb --- /dev/null +++ b/src/renderer/rvSpecial.h @@ -0,0 +1,94 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software +// +// Quake 4 off-screen special effects reconstructed from quake4.pdb. + +#ifndef __RENDERER_RVSPECIAL_H__ +#define __RENDERER_RVSPECIAL_H__ + +class idMaterial; +class idPBufferImage; +class rvNewShaderStage; +struct drawSurf_s; +struct drawSurfsCommand_s; +typedef struct drawSurfsCommand_s drawSurfsCommand_t; +struct viewDef_s; +struct viewEntity_s; + +class rvBlurTexture { +public: + rvBlurTexture(); + + static bool CreateBuffer( const char *name, idPBufferImage **image ); + void Render( drawSurfsCommand_t *drawSurfs ); + void Display( viewEntity_s *viewEnts, bool prePass ); + + idPBufferImage *mDepthImage; + idPBufferImage *mBlurImage[2]; + const idMaterial *mDepthMaterial; + float * regs; + float shaderParms[12]; +}; + +class rvAL { +public: + rvAL(); + + static bool CreateBuffer( const char *name, idPBufferImage **image ); + void Render( drawSurfsCommand_t *drawSurfs ); + void DrawLight( const idVec3 &origin, float size, const idVec3 &color ); + void Display( viewEntity_s *viewEnts, bool prePass ); + +private: + rvNewShaderStage *mainStage; + int mLightLocParm; + int mLightColorParm; + int mLightSizeParm; + int mLightMinDistanceParm; + +public: + idPBufferImage *mDepthImage; + idPBufferImage *mBlurImage[2]; + const idMaterial *mDepthMaterial; + idVec3 lOrigin[100]; + idVec3 lColor[100]; + float lSize[100]; + float offset; + int count; + float * regs; + float shaderParms[12]; +}; + +void RB_RestoreDrawingView(); +void R_AddSpecialEffects( viewDef_s *parms ); +void RB_T_FillDepthTexture( const drawSurf_s *surf ); +void RB_T_FillDepthTextureAL( const drawSurf_s *surf ); +void RB_SetGL2D2(); +void R_ShutdownSpecialEffects(); +void RB_DrawDepthTexture( const void *data ); +void RB_DisplaySpecialEffects( viewEntity_s *viewEnts, bool prePass ); + +#endif diff --git a/src/renderer/rvTexRenderTarget.cpp b/src/renderer/rvTexRenderTarget.cpp new file mode 100644 index 0000000..c05589f --- /dev/null +++ b/src/renderer/rvTexRenderTarget.cpp @@ -0,0 +1,318 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvTexRenderTarget.h" + +// Retail compilation unit: rvTexRenderTarget.obj. + +// WGL_ARB_pixel_format, WGL_ARB_pbuffer, WGL_ARB_render_texture, and +// WGL_NV_float_buffer constants used by the retail implementation. qgl.h +// intentionally declares the entry points without importing wglext.h. +enum { + WGL_SUPPORT_OPENGL_ARB_Q4 = 0x2010, + WGL_COLOR_BITS_ARB_Q4 = 0x2014, + WGL_RED_BITS_ARB_Q4 = 0x2015, + WGL_GREEN_BITS_ARB_Q4 = 0x2017, + WGL_BLUE_BITS_ARB_Q4 = 0x2019, + WGL_ALPHA_BITS_ARB_Q4 = 0x201B, + WGL_DEPTH_BITS_ARB_Q4 = 0x2022, + WGL_STENCIL_BITS_ARB_Q4 = 0x2023, + WGL_BIND_TO_TEXTURE_RGB_ARB_Q4 = 0x2070, + WGL_BIND_TO_TEXTURE_RGBA_ARB_Q4 = 0x2071, + WGL_TEXTURE_FORMAT_ARB_Q4 = 0x2072, + WGL_TEXTURE_TARGET_ARB_Q4 = 0x2073, + WGL_MIPMAP_TEXTURE_ARB_Q4 = 0x2074, + WGL_TEXTURE_RGB_ARB_Q4 = 0x2075, + WGL_TEXTURE_RGBA_ARB_Q4 = 0x2076, + WGL_TEXTURE_2D_ARB_Q4 = 0x207A, + WGL_TEXTURE_CUBE_MAP_ARB_Q4 = 0x2078, + WGL_CUBE_MAP_FACE_ARB_Q4 = 0x207C, + WGL_FRONT_LEFT_ARB_Q4 = 0x2083, + WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV_Q4 = 0x20A0, + WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV_Q4 = 0x20A1, + WGL_TEXTURE_RECTANGLE_NV_Q4 = 0x20A2, + WGL_FLOAT_COMPONENTS_NV_Q4 = 0x20B0, + WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV_Q4 = 0x20B1, + WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV_Q4 = 0x20B2, + WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV_Q4 = 0x20B3, + WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV_Q4 = 0x20B4, + WGL_TEXTURE_FLOAT_R_NV_Q4 = 0x20B5, + WGL_TEXTURE_FLOAT_RG_NV_Q4 = 0x20B6, + WGL_TEXTURE_FLOAT_RGB_NV_Q4 = 0x20B7, + WGL_TEXTURE_FLOAT_RGBA_NV_Q4 = 0x20B8 +}; + +unsigned int rvTexRenderTarget::m_prevReadBuffer; +unsigned int rvTexRenderTarget::m_prevDrawBuffer; +int rvTexRenderTarget::m_prevViewport[4]; +HDC rvTexRenderTarget::m_hPrevDC; +HGLRC rvTexRenderTarget::m_hPrevGLRC; + +void rvTexRenderTarget::ResetValues() { + m_hPBuffer = NULL; + m_hDC = NULL; + m_hPrevDC = NULL; + m_textureObjName = 0; + m_resWidth = 0; + m_resHeight = 0; + m_flags = 0; + m_numRedBits = 0; + m_numGreenBits = 0; + m_numBlueBits = 0; + m_numAlphaBits = 0; + m_numDepthBits = 0; + m_numStencilBits = 0; + m_target = GL_TEXTURE_2D; +} + +rvTexRenderTarget::rvTexRenderTarget() { + ResetValues(); +} + +rvTexRenderTarget::~rvTexRenderTarget() { + Release(); +} + +bool rvTexRenderTarget::Init( int resWidth, int resHeight, int numColorBits, + int numRedBits, int numGreenBits, int numBlueBits, int numAlphaBits, + int numDepthBits, int numStencilBits, int flags ) { + HDC hdc = qwglGetCurrentDC(); + HGLRC glRC = qwglGetCurrentContext(); + int pixelFormatIndex = 0; + UINT formatCount = 0; + int attributePairs[24]; + int attributeIndex = 0; + + attributePairs[attributeIndex++] = WGL_SUPPORT_OPENGL_ARB_Q4; + attributePairs[attributeIndex++] = TRUE; + + if ( flags & rvTexRenderTargetFlagFloatingPoint ) { + attributePairs[attributeIndex++] = WGL_FLOAT_COMPONENTS_NV_Q4; + attributePairs[attributeIndex++] = TRUE; + attributePairs[attributeIndex++] = WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV_Q4 + + ( numAlphaBits ? 3 : numBlueBits ? 2 : numGreenBits ? 1 : 0 ); + attributePairs[attributeIndex++] = TRUE; + } else { + const int bindBase = ( flags & ( rvTexRenderTargetFlagCubeMap | rvTexRenderTargetFlagMipMap ) ) + ? WGL_BIND_TO_TEXTURE_RGB_ARB_Q4 + : WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV_Q4; + attributePairs[attributeIndex++] = bindBase + ( numAlphaBits != 0 ); + attributePairs[attributeIndex++] = TRUE; + } + + attributePairs[attributeIndex++] = WGL_COLOR_BITS_ARB_Q4; + attributePairs[attributeIndex++] = numColorBits; + attributePairs[attributeIndex++] = WGL_RED_BITS_ARB_Q4; + attributePairs[attributeIndex++] = numRedBits; + attributePairs[attributeIndex++] = WGL_GREEN_BITS_ARB_Q4; + attributePairs[attributeIndex++] = numGreenBits; + attributePairs[attributeIndex++] = WGL_BLUE_BITS_ARB_Q4; + attributePairs[attributeIndex++] = numBlueBits; + attributePairs[attributeIndex++] = WGL_ALPHA_BITS_ARB_Q4; + attributePairs[attributeIndex++] = numAlphaBits; + + if ( flags & rvTexRenderTargetFlagDepthStencil ) { + attributePairs[attributeIndex++] = WGL_DEPTH_BITS_ARB_Q4; + attributePairs[attributeIndex++] = numDepthBits; + attributePairs[attributeIndex++] = WGL_STENCIL_BITS_ARB_Q4; + attributePairs[attributeIndex++] = numStencilBits; + } + attributePairs[attributeIndex++] = 0; + attributePairs[attributeIndex] = 0; + + if ( !qwglChoosePixelFormatARB( hdc, attributePairs, NULL, 1, &pixelFormatIndex, &formatCount ) ) { + common->Warning( "qwglChoosePixelFormatARB() failed, last error %x\n", GetLastError() ); + return false; + } + if ( formatCount == 0 ) { + common->Warning( "qwglChoosePixelFormatARB() couldn't find any matches\n" ); + return false; + } + + attributeIndex = 0; + attributePairs[attributeIndex++] = WGL_TEXTURE_TARGET_ARB_Q4; + if ( flags & rvTexRenderTargetFlagCubeMap ) { + attributePairs[attributeIndex++] = WGL_TEXTURE_CUBE_MAP_ARB_Q4; + m_target = GL_TEXTURE_CUBE_MAP_ARB; + } else if ( flags & rvTexRenderTargetFlagMipMap ) { + attributePairs[attributeIndex++] = WGL_TEXTURE_2D_ARB_Q4; + m_target = GL_TEXTURE_2D; + } else { + attributePairs[attributeIndex++] = WGL_TEXTURE_RECTANGLE_NV_Q4; + m_target = GL_TEXTURE_RECTANGLE_NV; + } + + attributePairs[attributeIndex++] = WGL_TEXTURE_FORMAT_ARB_Q4; + if ( flags & rvTexRenderTargetFlagFloatingPoint ) { + attributePairs[attributeIndex++] = WGL_TEXTURE_FLOAT_R_NV_Q4 + + ( numAlphaBits ? 3 : numBlueBits ? 2 : numGreenBits ? 1 : 0 ); + } else { + attributePairs[attributeIndex++] = WGL_TEXTURE_RGB_ARB_Q4 + ( numAlphaBits != 0 ); + } + if ( flags & rvTexRenderTargetFlagMipMap ) { + attributePairs[attributeIndex++] = WGL_MIPMAP_TEXTURE_ARB_Q4; + attributePairs[attributeIndex++] = TRUE; + } + attributePairs[attributeIndex++] = 0; + attributePairs[attributeIndex] = 0; + + m_hPBuffer = qwglCreatePbufferARB( hdc, pixelFormatIndex, resWidth, resHeight, attributePairs ); + if ( !m_hPBuffer ) { + common->Warning( "qwglCreatePbufferARB() failed, last error %x\n", GetLastError() ); + return false; + } + + m_hDC = qwglGetPbufferDCARB( m_hPBuffer ); + if ( m_hDC ) { + m_hGLRC = qwglCreateContext( m_hDC ); + if ( m_hGLRC ) { + if ( !glRC || qwglShareLists( glRC, m_hGLRC ) ) { + m_flagsSave = m_flags = flags; + m_numColorBitsSave = m_numColorBits = static_cast( numColorBits ); + m_numRedBitsSave = m_numRedBits = static_cast( numRedBits ); + m_numGreenBitsSave = m_numGreenBits = static_cast( numGreenBits ); + m_numBlueBitsSave = m_numBlueBits = static_cast( numBlueBits ); + m_numAlphaBitsSave = m_numAlphaBits = static_cast( numAlphaBits ); + m_numDepthBitsSave = m_numDepthBits = static_cast( numDepthBits ); + m_numStencilBitsSave = m_numStencilBits = static_cast( numStencilBits ); + m_resWidthSave = m_resWidth = resWidth; + m_resHeightSave = m_resHeight = resHeight; + return true; + } + common->Warning( "qwglShareLists() couldn't share display list resources\n" ); + qwglDeleteContext( m_hGLRC ); + } + qwglReleasePbufferDCARB( m_hPBuffer, m_hDC ); + } + + qwglDestroyPbufferARB( m_hPBuffer ); + ResetValues(); + return false; +} + +void rvTexRenderTarget::Release() { + if ( m_hPBuffer ) { + if ( m_hDC ) { + if ( m_hGLRC ) { + qwglDeleteContext( m_hGLRC ); + } + qwglReleasePbufferDCARB( m_hPBuffer, m_hDC ); + } + qwglDestroyPbufferARB( m_hPBuffer ); + } + ResetValues(); +} + +bool rvTexRenderTarget::Restore() { + return Init( m_resWidthSave, m_resHeightSave, m_numColorBitsSave, + m_numRedBitsSave, m_numGreenBitsSave, m_numBlueBitsSave, + m_numAlphaBitsSave, m_numDepthBitsSave, m_numStencilBitsSave, m_flagsSave ); +} + +void rvTexRenderTarget::BeginRender( int cubeFace ) { + if ( m_textureObjName ) { + qwglReleaseTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB_Q4 ); + m_textureObjName = 0; + } + + if ( !m_hPrevDC && !m_hPrevGLRC ) { + m_hPrevDC = qwglGetCurrentDC(); + m_hPrevGLRC = qwglGetCurrentContext(); + qglGetIntegerv( GL_DRAW_BUFFER, reinterpret_cast( &m_prevDrawBuffer ) ); + qglGetIntegerv( GL_READ_BUFFER, reinterpret_cast( &m_prevReadBuffer ) ); + qglGetIntegerv( GL_VIEWPORT, m_prevViewport ); + } + + if ( m_flags & rvTexRenderTargetFlagCubeMap ) { + int attributePairs[4] = { WGL_CUBE_MAP_FACE_ARB_Q4, cubeFace, 0, 0 }; + qwglSetPbufferAttribARB( m_hPBuffer, attributePairs ); + } + + if ( !qwglMakeCurrent( m_hDC, m_hGLRC ) ) { + common->FatalError( "qwglMakeCurrent(m_hDC, m_hGLRC) failed, last error %x\n", GetLastError() ); + } + qglDrawBuffer( GL_FRONT_LEFT ); + qglReadBuffer( GL_FRONT_LEFT ); + qglViewport( 0, 0, m_resWidth, m_resHeight ); +} + +void rvTexRenderTarget::EndRender( bool restorePreviousDC ) { + if ( restorePreviousDC ) { + if ( !qwglMakeCurrent( m_hPrevDC, m_hPrevGLRC ) ) { + common->FatalError( "qwglMakeCurrent(m_hPrevDC, m_hPrevGLRC) failed, last error %x\n", GetLastError() ); + } + qglDrawBuffer( m_prevDrawBuffer ); + qglReadBuffer( m_prevReadBuffer ); + qglViewport( m_prevViewport[0], m_prevViewport[1], m_prevViewport[2], m_prevViewport[3] ); + m_hPrevDC = NULL; + m_hPrevGLRC = NULL; + } +} + +void rvTexRenderTarget::BeginTexture( unsigned int textureObjName, int minFilter, + int magFilter, int wrap ) { + if ( m_textureObjName ) { + qwglReleaseTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB_Q4 ); + m_textureObjName = 0; + } + qglBindTexture( m_target, textureObjName ); + qglTexParameteri( m_target, GL_TEXTURE_MIN_FILTER, minFilter ); + qglTexParameteri( m_target, GL_TEXTURE_MAG_FILTER, magFilter ); + qglTexParameteri( m_target, GL_TEXTURE_WRAP_S, wrap ); + qglTexParameteri( m_target, GL_TEXTURE_WRAP_T, wrap ); + qwglBindTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB_Q4 ); + m_textureObjName = textureObjName; +} + +void rvTexRenderTarget::EndTexture() { + qwglReleaseTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB_Q4 ); + m_textureObjName = 0; +} + +void rvTexRenderTarget::DefaultD3GL() { + BeginRender( 0 ); + qglClearDepth( 1.0 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + qglEnable( GL_DEPTH_TEST ); + qglDisable( GL_ALPHA_TEST ); + qglDepthFunc( GL_LEQUAL ); + qglMatrixMode( GL_MODELVIEW ); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglLoadIdentity(); + qglEnableClientState( GL_VERTEX_ARRAY ); + qglDisableClientState( GL_NORMAL_ARRAY ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglDisableClientState( GL_COLOR_ARRAY ); + qglShadeModel( GL_SMOOTH ); + qglCullFace( GL_BACK ); + qglEnable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); + qglDisable( GL_LIGHTING ); + qglDisable( GL_TEXTURE_2D ); + EndRender( true ); +} diff --git a/src/renderer/rvTexRenderTarget.h b/src/renderer/rvTexRenderTarget.h new file mode 100644 index 0000000..e551b1c --- /dev/null +++ b/src/renderer/rvTexRenderTarget.h @@ -0,0 +1,87 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __RV_TEX_RENDER_TARGET_H__ +#define __RV_TEX_RENDER_TARGET_H__ + +enum rvTexRenderTargetFlags { + rvTexRenderTargetFlagDepthStencil = 1, + rvTexRenderTargetFlagFloatingPoint = 2, + rvTexRenderTargetFlagCubeMap = 4, + rvTexRenderTargetFlagMipMap = 8 +}; + +class rvTexRenderTarget { +public: + rvTexRenderTarget(); + ~rvTexRenderTarget(); + + bool Init( int resWidth, int resHeight, int numColorBits, int numRedBits, + int numGreenBits, int numBlueBits, int numAlphaBits, + int numDepthBits, int numStencilBits, int flags ); + void Release(); + bool Restore(); + void BeginRender( int cubeFace ); + void EndRender( bool restorePreviousDC ); + void BeginTexture( unsigned int textureObjName, int minFilter, int magFilter, int wrap ); + void EndTexture(); + void DefaultD3GL(); + +private: + void ResetValues(); + + HPBUFFERARB m_hPBuffer; + HDC m_hDC; + HGLRC m_hGLRC; + unsigned int m_textureObjName; + int m_resWidth; + int m_resWidthSave; + int m_resHeight; + int m_resHeightSave; + int m_flags; + int m_flagsSave; + unsigned int m_target; + unsigned char m_numColorBits; + unsigned char m_numColorBitsSave; + unsigned char m_numRedBits; + unsigned char m_numRedBitsSave; + unsigned char m_numGreenBits; + unsigned char m_numGreenBitsSave; + unsigned char m_numBlueBits; + unsigned char m_numBlueBitsSave; + unsigned char m_numAlphaBits; + unsigned char m_numAlphaBitsSave; + unsigned char m_numDepthBits; + unsigned char m_numDepthBitsSave; + unsigned char m_numStencilBits; + unsigned char m_numStencilBitsSave; + + static unsigned int m_prevReadBuffer; + static unsigned int m_prevDrawBuffer; + static int m_prevViewport[4]; + static HDC m_hPrevDC; + static HGLRC m_hPrevGLRC; +}; + +#endif diff --git a/src/renderer/rvVertexBuffer.cpp b/src/renderer/rvVertexBuffer.cpp new file mode 100644 index 0000000..1d4205f --- /dev/null +++ b/src/renderer/rvVertexBuffer.cpp @@ -0,0 +1,646 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvVertexBuffer.h" + +// Retail compilation unit: rvVertexBuffer.obj. + +void rvVertexBuffer::ResetValues() { + m_flags = 0; + m_lockStatus = 0; + m_numVertices = 0; + m_vbID = 0; + m_lockVertexOffset = 0; + m_lockVertexCount = 0; + m_lockedBase = NULL; + m_numVerticesWritten = 0; + m_interleavedStorage = NULL; + m_positionArray = NULL; + m_swizzledPositionArray = NULL; + m_blendIndexArray = NULL; + m_blendWeightArray = NULL; + m_normalArray = NULL; + m_tangentArray = NULL; + m_binormalArray = NULL; + m_diffuseColorArray = NULL; + m_specularColorArray = NULL; + m_pointSizeArray = NULL; + memset( m_texCoordArrays, 0, sizeof( m_texCoordArrays ) ); +} + +rvVertexBuffer::rvVertexBuffer() { + ResetValues(); +} + +rvVertexBuffer::~rvVertexBuffer() { + Shutdown(); +} + +void rvVertexBuffer::CreateVertexStorage() { + bool allocationFailed = false; + if ( ( m_flags & rvVertexBufferFlagSystemMemory ) != 0 ) { + if ( ( m_flags & rvVertexBufferFlagSoA ) != 0 ) { + if ( ( m_format.m_flags & rvVertexFormatFlagPosition ) != 0 ) { + m_positionArray = static_cast( Mem_Alloc16( sizeof( float ) * m_numVertices * m_format.GetPositionDimension(), MA_RENDER ) ); + allocationFailed |= m_positionArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagSwizzledPosition ) != 0 ) { + m_swizzledPositionArray = static_cast( Mem_Alloc16( sizeof( float ) * 3 * ( ( m_numVertices + 3 ) & ~3 ), MA_RENDER ) ); + allocationFailed |= m_swizzledPositionArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendIndex ) != 0 ) { + m_blendIndexArray = static_cast( Mem_Alloc16( sizeof( unsigned int ) * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_blendIndexArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendWeight ) != 0 ) { + m_blendWeightArray = static_cast( Mem_Alloc16( sizeof( float ) * m_numVertices * m_format.GetBlendWeightDimension(), MA_RENDER ) ); + allocationFailed |= m_blendWeightArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagNormal ) != 0 ) { + m_normalArray = static_cast( Mem_Alloc16( sizeof( float ) * 3 * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_normalArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagTangent ) != 0 ) { + m_tangentArray = static_cast( Mem_Alloc16( sizeof( float ) * 3 * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_tangentArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagBinormal ) != 0 ) { + m_binormalArray = static_cast( Mem_Alloc16( sizeof( float ) * 3 * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_binormalArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagDiffuseColor ) != 0 ) { + m_diffuseColorArray = static_cast( Mem_Alloc16( sizeof( unsigned int ) * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_diffuseColorArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagSpecularColor ) != 0 ) { + m_specularColorArray = static_cast( Mem_Alloc16( sizeof( unsigned int ) * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_specularColorArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagPointSize ) != 0 ) { + m_pointSizeArray = static_cast( Mem_Alloc16( sizeof( float ) * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_pointSizeArray == NULL; + } + if ( ( m_format.m_flags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int texture = 0; texture < 7; ++texture ) { + const int dimension = m_format.GetTextureDimension( texture ); + if ( dimension != 0 ) { + m_texCoordArrays[texture] = static_cast( Mem_Alloc16( sizeof( float ) * dimension * m_numVertices, MA_RENDER ) ); + allocationFailed |= m_texCoordArrays[texture] == NULL; + } + } + } + } else { + m_interleavedStorage = static_cast( Mem_Alloc16( m_format.GetSize() * m_numVertices, MA_RENDER ) ); + allocationFailed = m_interleavedStorage == NULL; + } + } + if ( allocationFailed ) { + common->FatalError( "Ran out of memory trying to allocate system memory vertex storage" ); + return; + } + + if ( ( m_flags & rvVertexBufferFlagVideoMemory ) != 0 ) { + qglGetError(); + qglGenBuffersARB( 1, &m_vbID ); + if ( m_vbID == 0 ) { + common->FatalError( "rvVertexBuffer: Unable to gen buffer id" ); + return; + } + if ( ( m_flags & rvVertexBufferFlagDynamicStream ) == 0 ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, m_vbID ); + qglBufferDataARB( GL_ARRAY_BUFFER_ARB, m_format.GetSize() * m_numVertices, NULL, GL_STATIC_DRAW_ARB ); + const unsigned int error = qglGetError(); + if ( error != GL_NO_ERROR ) { + common->FatalError( "Unable to allocate vertex storage - %d", error ); + } + m_format.SetVertexDeclaration( 0 ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + } + } +} + +bool rvVertexBuffer::LockInterleaved( int vertexOffset, int numVerticesToLock, + unsigned int lockFlags, void *&startPtr, int &stride ) { + m_lockVertexOffset = vertexOffset; + m_lockVertexCount = numVerticesToLock; + if ( m_lockVertexCount == 0 ) { + m_lockVertexCount = m_numVertices - vertexOffset; + } + + if ( ( m_flags & rvVertexBufferFlagSystemMemory ) != 0 ) { + m_lockedBase = m_interleavedStorage; + } else if ( m_lockStatus == 0 ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, m_vbID ); + unsigned int access = GL_READ_WRITE_ARB; + if ( ( lockFlags & rvVertexBufferLockFlagRead ) != 0 ) { + access = ( lockFlags & rvVertexBufferLockFlagWrite ) != 0 ? GL_READ_WRITE_ARB : GL_READ_ONLY_ARB; + } else if ( ( lockFlags & rvVertexBufferLockFlagWrite ) != 0 ) { + access = GL_WRITE_ONLY_ARB; + const unsigned int usage = ( m_flags & rvVertexBufferFlagDynamicStream ) != 0 ? GL_STREAM_DRAW_ARB : GL_STATIC_DRAW_ARB; + if ( ( m_flags & rvVertexBufferFlagDynamicStream ) != 0 ) { + lockFlags |= rvVertexBufferLockFlagEmpty; + } + if ( ( lockFlags & rvVertexBufferLockFlagEmpty ) != 0 ) { + m_lockVertexOffset = 0; + qglBufferDataARB( GL_ARRAY_BUFFER_ARB, m_format.GetSize() * m_lockVertexCount, NULL, usage ); + } + } + m_lockedBase = static_cast( qglMapBufferARB( GL_ARRAY_BUFFER_ARB, access ) ); + if ( m_lockedBase == NULL ) { + return false; + } + } + + m_lockStatus = lockFlags; + startPtr = m_lockedBase + m_lockVertexOffset * m_format.GetSize(); + stride = m_format.GetSize(); + return true; +} + +void rvVertexBuffer::TransferSoAToAoS( unsigned char *vertexDest ) { + const int stride = m_format.GetSize(); + for ( int vertex = 0; vertex < m_numVertices; ++vertex ) { + unsigned char *dest = vertexDest + vertex * stride; + if ( ( m_format.m_flags & rvVertexFormatFlagSwizzledPosition ) != 0 ) { + const int block = vertex & ~3; + const int lane = vertex & 3; + float *out = reinterpret_cast( dest ); + out[0] = m_swizzledPositionArray[block * 3 + lane]; + out[1] = m_swizzledPositionArray[block * 3 + 4 + lane]; + out[2] = m_swizzledPositionArray[block * 3 + 8 + lane]; + } else if ( ( m_format.m_flags & rvVertexFormatFlagPosition ) != 0 ) { + memcpy( dest + m_format.m_byteOffset[0], m_positionArray + vertex * m_format.GetPositionDimension(), + sizeof( float ) * m_format.GetPositionDimension() ); + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendIndex ) != 0 ) { + memcpy( dest + m_format.m_byteOffset[2], m_blendIndexArray + vertex, 4 ); + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendWeight ) != 0 ) { + memcpy( dest + m_format.m_byteOffset[3], m_blendWeightArray + vertex * m_format.GetBlendWeightDimension(), + sizeof( float ) * m_format.GetBlendWeightDimension() ); + } + if ( ( m_format.m_flags & rvVertexFormatFlagNormal ) != 0 ) { memcpy( dest + m_format.m_byteOffset[4], m_normalArray + vertex * 3, 12 ); } + if ( ( m_format.m_flags & rvVertexFormatFlagTangent ) != 0 ) { memcpy( dest + m_format.m_byteOffset[5], m_tangentArray + vertex * 3, 12 ); } + if ( ( m_format.m_flags & rvVertexFormatFlagBinormal ) != 0 ) { memcpy( dest + m_format.m_byteOffset[6], m_binormalArray + vertex * 3, 12 ); } + if ( ( m_format.m_flags & rvVertexFormatFlagDiffuseColor ) != 0 ) { memcpy( dest + m_format.m_byteOffset[7], m_diffuseColorArray + vertex, 4 ); } + if ( ( m_format.m_flags & rvVertexFormatFlagSpecularColor ) != 0 ) { memcpy( dest + m_format.m_byteOffset[8], m_specularColorArray + vertex, 4 ); } + if ( ( m_format.m_flags & rvVertexFormatFlagPointSize ) != 0 ) { memcpy( dest + m_format.m_byteOffset[9], m_pointSizeArray + vertex, 4 ); } + for ( int texture = 0; texture < 7; ++texture ) { + const int dimension = m_format.GetTextureDimension( texture ); + if ( dimension != 0 ) { + memcpy( dest + m_format.m_byteOffset[10 + texture], m_texCoordArrays[texture] + vertex * dimension, + sizeof( float ) * dimension ); + } + } + } +} + +void rvVertexBuffer::SetupForRender( int vertexStartOffset, const rvVertexFormat &formatNeeded ) { + if ( ( m_flags & rvVertexBufferFlagVideoMemory ) != 0 ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, m_vbID ); + m_format.SetVertexDeclaration( vertexStartOffset ); + formatNeeded.EnableVertexDeclaration(); + } +} + +rvSilTraceVertT *rvVertexBuffer::GetSilTraceVertexArray( int vertexOffset ) { + return reinterpret_cast( m_interleavedStorage + sizeof( rvSilTraceVertT ) * vertexOffset ); +} + +void rvVertexBuffer::SetLoadFormat( const rvVertexFormat &loadFormat ) { + m_loadFormat.Init( loadFormat ); +} + +void rvVertexBuffer::Unlock() { + const bool wrote = ( m_lockStatus & rvVertexBufferLockFlagWrite ) != 0; + if ( wrote ) { + if ( ( m_lockStatus & rvVertexBufferLockFlagEmpty ) != 0 ) { m_numVerticesWritten = m_lockVertexCount; } + else { m_numVerticesWritten += m_lockVertexCount; } + } + + if ( ( m_flags & rvVertexBufferFlagSystemMemory ) != 0 ) { + if ( wrote && ( m_flags & rvVertexBufferFlagVideoMemory ) != 0 ) { + const int byteOffset = m_format.GetSize() * m_lockVertexOffset; + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, m_vbID ); + if ( ( m_flags & rvVertexBufferFlagSoA ) != 0 ) { + unsigned char *videoMemory = static_cast( qglMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB ) ); + if ( videoMemory != NULL ) { + TransferSoAToAoS( videoMemory + byteOffset ); + qglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + } + } else { + qglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, byteOffset, m_format.GetSize() * m_lockVertexCount, + m_interleavedStorage + byteOffset ); + } + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + } + } else if ( ( m_flags & rvVertexBufferFlagVideoMemory ) != 0 ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, m_vbID ); + qglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + } + m_lockStatus = 0; + m_lockVertexOffset = 0; + m_lockVertexCount = 0; + m_lockedBase = NULL; +} + +void rvVertexBuffer::CopyData( int destVertexOffset, int numVertices, const unsigned char *srcPtr, + int srcStride, const rvVertexFormat &srcFormat, const unsigned int *copyMapping ) { + unsigned char *destPtr = NULL; + int destStride = 0; + if ( !LockInterleaved( destVertexOffset, numVertices, rvVertexBufferLockFlagWrite, + reinterpret_cast( destPtr ), destStride ) ) { + common->FatalError( "Vertex buffer cannot be mapped for access" ); + return; + } + auto copy = [&]( int component, int destDimension, int sourceDimension ) { + if ( ( m_format.m_flags & BIT( component < 10 ? component : 10 ) ) != 0 && + ( srcFormat.m_flags & BIT( component < 10 ? component : 10 ) ) != 0 ) { + ComponentCopy( destPtr + m_format.m_byteOffset[component], destStride, + static_cast( m_format.m_dataTypes[component] ), destDimension, + srcPtr + srcFormat.m_byteOffset[component], srcStride, + static_cast( srcFormat.m_dataTypes[component] ), sourceDimension, + numVertices, copyMapping, NULL, false ); + } + }; + copy( 0, m_format.GetPositionDimension(), srcFormat.GetPositionDimension() ); + copy( 2, 1, 1 ); + copy( 3, m_format.GetBlendWeightDimension(), srcFormat.GetBlendWeightDimension() ); + for ( int component = 4; component <= 6; ++component ) { copy( component, 3, 3 ); } + for ( int component = 7; component <= 9; ++component ) { copy( component, 1, 1 ); } + for ( int texture = 0; texture < 7; ++texture ) { + copy( 10 + texture, m_format.GetTextureDimension( texture ), srcFormat.GetTextureDimension( texture ) ); + } + Unlock(); +} + +void rvVertexBuffer::CopyData( unsigned char *destPtr, int destStride, const rvVertexFormat &destFormat, + int srcVertexOffset, int numVertices, const unsigned int *copyMapping ) { + const unsigned char *srcPtr = NULL; + int srcStride = 0; + if ( !LockInterleaved( srcVertexOffset, numVertices, rvVertexBufferLockFlagRead, + reinterpret_cast( const_cast( srcPtr ) ), srcStride ) ) { + common->FatalError( "Vertex buffer cannot be mapped for access" ); + return; + } + auto copy = [&]( int component, int destDimension, int sourceDimension ) { + if ( ( destFormat.m_flags & BIT( component < 10 ? component : 10 ) ) != 0 && + ( m_format.m_flags & BIT( component < 10 ? component : 10 ) ) != 0 ) { + ComponentCopy( destPtr + destFormat.m_byteOffset[component], destStride, + static_cast( destFormat.m_dataTypes[component] ), destDimension, + srcPtr + m_format.m_byteOffset[component], srcStride, + static_cast( m_format.m_dataTypes[component] ), sourceDimension, + numVertices, copyMapping, NULL, false ); + } + }; + copy( 0, destFormat.GetPositionDimension(), m_format.GetPositionDimension() ); + copy( 2, 1, 1 ); + copy( 3, destFormat.GetBlendWeightDimension(), m_format.GetBlendWeightDimension() ); + for ( int component = 4; component <= 6; ++component ) { copy( component, 3, 3 ); } + for ( int component = 7; component <= 9; ++component ) { copy( component, 1, 1 ); } + for ( int texture = 0; texture < 7; ++texture ) { + copy( 10 + texture, destFormat.GetTextureDimension( texture ), m_format.GetTextureDimension( texture ) ); + } + Unlock(); +} + +void rvVertexBuffer::Write( idFile &outFile, const char *prepend ) { + idStr indent = prepend; + indent += "\t"; + outFile.WriteFloatString( "%sVertexBuffer\n%s{\n", prepend, prepend ); + outFile.WriteFloatString( "%sVertexFormat ", indent.c_str() ); + m_format.Write( outFile, indent.c_str() ); + if ( !m_loadFormat.HasSameComponents( m_format ) || !m_loadFormat.HasSameDataTypes( m_format ) ) { + outFile.WriteFloatString( "%sLoadVertexFormat ", indent.c_str() ); + m_loadFormat.Write( outFile, indent.c_str() ); + } + if ( ( m_flags & rvVertexBufferFlagSystemMemory ) != 0 ) { outFile.WriteFloatString( "%sSystemMemory\n", indent.c_str() ); } + if ( ( m_flags & rvVertexBufferFlagVideoMemory ) != 0 ) { outFile.WriteFloatString( "%sVideoMemory\n", indent.c_str() ); } + if ( ( m_flags & rvVertexBufferFlagSoA ) != 0 ) { outFile.WriteFloatString( "%sSoA\n", indent.c_str() ); } + outFile.WriteFloatString( "%sVertex[ %d ]\n%s{\n", indent.c_str(), m_numVerticesWritten, indent.c_str() ); + + unsigned char *storage = NULL; + bool allocatedStorage = false; + if ( ( m_flags & rvVertexBufferFlagSoA ) != 0 ) { + storage = static_cast( Mem_Alloc16( m_format.GetSize() * m_numVertices, MA_RENDER ) ); + if ( storage != NULL ) { TransferSoAToAoS( storage ); allocatedStorage = true; } + } else if ( ( m_flags & rvVertexBufferFlagSystemMemory ) != 0 ) { + storage = m_interleavedStorage; + } else { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, m_vbID ); + storage = static_cast( qglMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_READ_ONLY_ARB ) ); + } + if ( storage == NULL ) { + common->FatalError( "Vertex buffer cannot be mapped for access" ); + return; + } + int tokenSubTypes[72]; + for ( int i = 0; i < m_format.m_numValues; ++i ) { tokenSubTypes[i] = m_format.m_tokenSubTypes[i]; } + idStr dataIndent = indent; + dataIndent += "\t"; + outFile.WriteNumericStructArray( m_format.m_numValues, tokenSubTypes, m_numVerticesWritten, storage, dataIndent.c_str() ); + if ( allocatedStorage ) { Mem_Free16( storage ); } + else if ( ( m_flags & rvVertexBufferFlagSystemMemory ) == 0 ) { + qglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + } + outFile.WriteFloatString( "%s}\n%s}\n", indent.c_str(), prepend ); +} + +void rvVertexBuffer::Shutdown() { + if ( m_lockStatus != 0 ) { Unlock(); } + if ( m_interleavedStorage != NULL ) { Mem_Free16( m_interleavedStorage ); } + if ( m_positionArray != NULL ) { Mem_Free16( m_positionArray ); } + if ( m_swizzledPositionArray != NULL ) { Mem_Free16( m_swizzledPositionArray ); } + if ( m_blendIndexArray != NULL ) { Mem_Free16( m_blendIndexArray ); } + if ( m_blendWeightArray != NULL ) { Mem_Free16( m_blendWeightArray ); } + if ( m_normalArray != NULL ) { Mem_Free16( m_normalArray ); } + if ( m_tangentArray != NULL ) { Mem_Free16( m_tangentArray ); } + if ( m_binormalArray != NULL ) { Mem_Free16( m_binormalArray ); } + if ( m_diffuseColorArray != NULL ) { Mem_Free16( m_diffuseColorArray ); } + if ( m_specularColorArray != NULL ) { Mem_Free16( m_specularColorArray ); } + if ( m_pointSizeArray != NULL ) { Mem_Free16( m_pointSizeArray ); } + for ( int texture = 0; texture < 7; ++texture ) { if ( m_texCoordArrays[texture] != NULL ) { Mem_Free16( m_texCoordArrays[texture] ); } } + if ( m_vbID != 0 ) { qglDeleteBuffersARB( 1, &m_vbID ); } + m_format.Shutdown(); + m_loadFormat.Shutdown(); + ResetValues(); +} + +void rvVertexBuffer::Resize( int numVertices ) { + if ( numVertices == m_numVertices || ( m_flags & rvVertexBufferFlagSystemMemory ) == 0 ) { return; } + const int oldNumVertices = m_numVertices; + const int copyVertices = Min( oldNumVertices, numVertices ); + if ( ( m_flags & rvVertexBufferFlagSoA ) != 0 ) { + auto resizeArray = [&]( void *&array, int valuesPerVertex, int oldStorageVertices, int newStorageVertices ) -> bool { + if ( array == NULL ) { return true; } + void *replacement = Mem_Alloc16( sizeof( float ) * valuesPerVertex * newStorageVertices, MA_RENDER ); + if ( replacement == NULL ) { return false; } + SIMDProcessor->Memcpy( replacement, array, sizeof( float ) * valuesPerVertex * Min( oldStorageVertices, newStorageVertices ) ); + Mem_Free16( array ); array = replacement; return true; + }; + bool ok = true; + ok &= resizeArray( reinterpret_cast( m_positionArray ), m_format.GetPositionDimension(), oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_swizzledPositionArray ), 3, ( oldNumVertices + 3 ) & ~3, ( numVertices + 3 ) & ~3 ); + ok &= resizeArray( reinterpret_cast( m_blendIndexArray ), 1, oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_blendWeightArray ), m_format.GetBlendWeightDimension(), oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_normalArray ), 3, oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_tangentArray ), 3, oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_binormalArray ), 3, oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_diffuseColorArray ), 1, oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_specularColorArray ), 1, oldNumVertices, numVertices ); + ok &= resizeArray( reinterpret_cast( m_pointSizeArray ), 1, oldNumVertices, numVertices ); + for ( int texture = 0; texture < 7; ++texture ) { + ok &= resizeArray( reinterpret_cast( m_texCoordArrays[texture] ), m_format.GetTextureDimension( texture ), oldNumVertices, numVertices ); + } + if ( !ok ) { common->FatalError( "Ran out of memory trying to allocate system memory vertex storage" ); Shutdown(); return; } + } else { + unsigned char *replacement = static_cast( Mem_Alloc16( numVertices * m_format.GetSize(), MA_RENDER ) ); + if ( replacement == NULL ) { common->FatalError( "Ran out of memory trying to allocate system memory vertex storage" ); Shutdown(); return; } + SIMDProcessor->Memcpy( replacement, m_interleavedStorage, copyVertices * m_format.GetSize() ); + Mem_Free16( m_interleavedStorage ); + m_interleavedStorage = replacement; + } + m_numVertices = numVertices; + if ( ( m_flags & rvVertexBufferFlagVideoMemory ) != 0 ) { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, m_vbID ); + if ( ( m_flags & rvVertexBufferFlagSoA ) != 0 ) { + qglBufferDataARB( GL_ARRAY_BUFFER_ARB, numVertices * m_format.GetSize(), NULL, GL_STATIC_DRAW_ARB ); + unsigned char *mapped = static_cast( qglMapBufferARB( GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB ) ); + if ( mapped != NULL ) { TransferSoAToAoS( mapped ); qglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); } + } else { + qglBufferDataARB( GL_ARRAY_BUFFER_ARB, numVertices * m_format.GetSize(), m_interleavedStorage, GL_STATIC_DRAW_ARB ); + } + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + } +} + +#define RV_COMPONENT_LOCK_IMPL( name, member, component, componentStride ) \ + bool rvVertexBuffer::name( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, \ + unsigned char *&ptr, int &stride ) { \ + m_lockVertexOffset = vertexBufferOffset; m_lockVertexCount = numVerticesToLock; \ + if ( ( m_flags & rvVertexBufferFlagSoA ) == 0 ) { \ + if ( !LockInterleaved( vertexBufferOffset, numVerticesToLock, lockFlags, reinterpret_cast( ptr ), stride ) ) { return false; } \ + ptr += m_format.m_byteOffset[component]; return true; \ + } \ + m_lockStatus = lockFlags; ptr = reinterpret_cast( member ); stride = componentStride; return true; \ + } + +RV_COMPONENT_LOCK_IMPL( LockPosition, m_positionArray, 0, sizeof( float ) * m_format.GetPositionDimension() ) +RV_COMPONENT_LOCK_IMPL( LockBlendIndex, m_blendIndexArray, 2, sizeof( unsigned int ) ) +RV_COMPONENT_LOCK_IMPL( LockBlendWeight, m_blendWeightArray, 3, sizeof( float ) * m_format.GetBlendWeightDimension() ) +RV_COMPONENT_LOCK_IMPL( LockNormal, m_normalArray, 4, sizeof( float ) * 3 ) +RV_COMPONENT_LOCK_IMPL( LockTangent, m_tangentArray, 5, sizeof( float ) * 3 ) +RV_COMPONENT_LOCK_IMPL( LockBinormal, m_binormalArray, 6, sizeof( float ) * 3 ) +RV_COMPONENT_LOCK_IMPL( LockDiffuseColor, m_diffuseColorArray, 7, sizeof( unsigned int ) ) + +#undef RV_COMPONENT_LOCK_IMPL + +bool rvVertexBuffer::LockTextureCoordinate( int textureCoordinate, int vertexBufferOffset, + int numVerticesToLock, unsigned int lockFlags, unsigned char *&textureCoordinatePtr, int &stride ) { + m_lockVertexOffset = vertexBufferOffset; + m_lockVertexCount = numVerticesToLock; + if ( ( m_flags & rvVertexBufferFlagSoA ) == 0 ) { + if ( !LockInterleaved( vertexBufferOffset, numVerticesToLock, lockFlags, + reinterpret_cast( textureCoordinatePtr ), stride ) ) { return false; } + textureCoordinatePtr += m_format.m_byteOffset[10 + textureCoordinate]; + return true; + } + m_lockStatus = lockFlags; + textureCoordinatePtr = reinterpret_cast( m_texCoordArrays[textureCoordinate] ); + stride = sizeof( float ) * m_format.GetTextureDimension( textureCoordinate ); + return true; +} + +void rvVertexBuffer::CopyRemappedData( int vertexBufferOffset, int numVertices, + unsigned int *copyMapping, unsigned int *transformMapOldToNew, + rvBlend4DrawVert *srcVertData, bool absBlendWeights ) { + if ( vertexBufferOffset + numVertices > m_numVertices ) { common->FatalError( "rvVertexBuffer: attempt to copy vertex data out-of-bounds" ); return; } + unsigned char *ptr = NULL; int stride = 0; + if ( ( m_format.m_flags & rvVertexFormatFlagPosition ) != 0 && m_format.GetPositionDimension() >= 3 && + LockPosition( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { memcpy( ptr + i * stride, &srcVertData[copyMapping[i]].xyz, 12 ); } + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendIndex ) != 0 && + LockBlendIndex( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { unsigned char *out = ptr + i * stride; const rvBlend4DrawVert &src = srcVertData[copyMapping[i]]; for ( int j = 0; j < 4; ++j ) { out[j] = static_cast( transformMapOldToNew[src.blendIndex[j]] ); } } + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendWeight ) != 0 && + LockBlendWeight( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + const int dimension = m_format.GetBlendWeightDimension(); + for ( int i = 0; i < numVertices; ++i ) { float *out = reinterpret_cast( ptr + i * stride ); const float *src = srcVertData[copyMapping[i]].blendWeight; for ( int j = 0; j < dimension; ++j ) { out[j] = absBlendWeights ? idMath::Fabs( src[j] ) : src[j]; } } + } + auto copyVec3 = [&]( unsigned int flag, bool ( rvVertexBuffer::*lock )( int, int, unsigned int, unsigned char *&, int & ), idVec3 rvBlend4DrawVert::*member ) { + if ( ( m_format.m_flags & flag ) != 0 && ( this->*lock )( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { memcpy( ptr + i * stride, &( srcVertData[copyMapping[i]].*member ), 12 ); } + } + }; + copyVec3( rvVertexFormatFlagNormal, &rvVertexBuffer::LockNormal, &rvBlend4DrawVert::normal ); + copyVec3( rvVertexFormatFlagTangent, &rvVertexBuffer::LockTangent, &rvBlend4DrawVert::tangent ); + copyVec3( rvVertexFormatFlagBinormal, &rvVertexBuffer::LockBinormal, &rvBlend4DrawVert::binormal ); + if ( ( m_format.m_flags & rvVertexFormatFlagDiffuseColor ) != 0 && LockDiffuseColor( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { memcpy( ptr + i * stride, srcVertData[copyMapping[i]].color, 4 ); } + } + if ( m_format.GetTextureDimension( 0 ) >= 2 && LockTextureCoordinate( 0, vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { memcpy( ptr + i * stride, &srcVertData[copyMapping[i]].st, 8 ); } + } + if ( m_lockStatus != 0 ) { Unlock(); } +} + +void rvVertexBuffer::CopyRemappedShadowVolData( int vertexBufferOffset, int numVertices, + unsigned int *copyMapping, unsigned int *transformMapOldToNew, rvBlend4DrawVert *srcVertData ) { + if ( vertexBufferOffset + 2 * numVertices > m_numVertices ) { common->FatalError( "rvVertexBuffer: attempt to copy shadow volume vertex data out-of-bounds" ); return; } + unsigned char *ptr = NULL; int stride = 0; + if ( ( m_format.m_flags & rvVertexFormatFlagPosition ) != 0 && LockPosition( vertexBufferOffset, 2 * numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { const idVec3 &xyz = srcVertData[copyMapping[i]].xyz; memcpy( ptr + ( 2 * i ) * stride, &xyz, 12 ); memcpy( ptr + ( 2 * i + 1 ) * stride, &xyz, 12 ); if ( m_format.GetPositionDimension() == 4 ) { reinterpret_cast( ptr + ( 2 * i ) * stride )[3] = 1.0f; reinterpret_cast( ptr + ( 2 * i + 1 ) * stride )[3] = 0.0f; } } + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendIndex ) != 0 && LockBlendIndex( vertexBufferOffset, 2 * numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { const rvBlend4DrawVert &src = srcVertData[copyMapping[i]]; for ( int duplicate = 0; duplicate < 2; ++duplicate ) { unsigned char *out = ptr + ( 2 * i + duplicate ) * stride; for ( int j = 0; j < 4; ++j ) { out[j] = static_cast( abs( static_cast( transformMapOldToNew[src.blendIndex[j]] ) ) ); } if ( m_format.GetPositionDimension() == 3 ) { out[3] = duplicate ? 0 : 0xFF; } } } + } + if ( ( m_format.m_flags & rvVertexFormatFlagBlendWeight ) != 0 && LockBlendWeight( vertexBufferOffset, 2 * numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + const int dimension = m_format.GetBlendWeightDimension(); + for ( int i = 0; i < numVertices; ++i ) { for ( int duplicate = 0; duplicate < 2; ++duplicate ) { float *out = reinterpret_cast( ptr + ( 2 * i + duplicate ) * stride ); for ( int j = 0; j < dimension; ++j ) { out[j] = idMath::Fabs( srcVertData[copyMapping[i]].blendWeight[j] ); } } } + } + if ( m_lockStatus != 0 ) { Unlock(); } +} + +int rvVertexBuffer::CopySilTraceData( unsigned int *indexMapping, int vertexBufferOffset, + int numVertices, int *copyMapping, const idDrawVert *srcVertData ) { + if ( vertexBufferOffset + numVertices > m_numVertices ) { common->FatalError( "rvVertexBuffer: attempt to copy vertex data out-of-bounds" ); return 0; } + unsigned char *ptr = NULL; int stride = 0; int written = 0; + if ( ( m_format.m_flags & rvVertexFormatFlagPosition ) != 0 && m_format.GetPositionDimension() >= 3 && LockPosition( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { + if ( copyMapping[i] == i ) { memcpy( ptr + written * stride, &srcVertData[i].xyz, 12 ); if ( m_format.GetPositionDimension() == 4 ) { reinterpret_cast( ptr + written * stride )[3] = 1.0f; } indexMapping[i] = written++; } + else { indexMapping[i] = indexMapping[copyMapping[i]]; } + } + } + if ( m_lockStatus != 0 ) { Unlock(); } + m_numVerticesWritten = written; + return written; +} + +void rvVertexBuffer::CopyData( int vertexBufferOffset, int numVertices, const idDrawVert *srcVertData ) { + if ( vertexBufferOffset + numVertices > m_numVertices ) { common->FatalError( "rvVertexBuffer: attempt to copy vertex data out-of-bounds" ); return; } + unsigned char *ptr = NULL; int stride = 0; + auto copyField = [&]( unsigned int flag, bool ( rvVertexBuffer::*lock )( int, int, unsigned int, unsigned char *&, int & ), const unsigned char *field, int sourceStride, int bytes ) { + if ( ( m_format.m_flags & flag ) != 0 && ( this->*lock )( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { for ( int i = 0; i < numVertices; ++i ) { memcpy( ptr + i * stride, field + i * sourceStride, bytes ); } } + }; + copyField( rvVertexFormatFlagPosition, &rvVertexBuffer::LockPosition, reinterpret_cast( &srcVertData[0].xyz ), sizeof( idDrawVert ), 12 ); + copyField( rvVertexFormatFlagNormal, &rvVertexBuffer::LockNormal, reinterpret_cast( &srcVertData[0].normal ), sizeof( idDrawVert ), 12 ); + copyField( rvVertexFormatFlagTangent, &rvVertexBuffer::LockTangent, reinterpret_cast( &srcVertData[0].tangents[0] ), sizeof( idDrawVert ), 12 ); + copyField( rvVertexFormatFlagBinormal, &rvVertexBuffer::LockBinormal, reinterpret_cast( &srcVertData[0].tangents[1] ), sizeof( idDrawVert ), 12 ); + copyField( rvVertexFormatFlagDiffuseColor, &rvVertexBuffer::LockDiffuseColor, srcVertData[0].color, sizeof( idDrawVert ), 4 ); + if ( m_format.GetTextureDimension( 0 ) >= 2 && LockTextureCoordinate( 0, vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { for ( int i = 0; i < numVertices; ++i ) { memcpy( ptr + i * stride, &srcVertData[i].st, 8 ); } } + if ( m_lockStatus != 0 ) { Unlock(); } +} + +void rvVertexBuffer::CopyRemappedShadowVolData( int vertexBufferOffset, int numVertices, + int *copyMapping, const idDrawVert *srcVertData ) { + if ( vertexBufferOffset + 2 * numVertices > m_numVertices ) { common->FatalError( "rvVertexBuffer: attempt to copy shadow volume vertex data out-of-bounds" ); return; } + unsigned char *ptr = NULL; int stride = 0; + if ( ( m_format.m_flags & rvVertexFormatFlagPosition ) != 0 && LockPosition( vertexBufferOffset, 2 * numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + for ( int i = 0; i < numVertices; ++i ) { const idVec3 &xyz = srcVertData[copyMapping[i]].xyz; memcpy( ptr + ( 2 * i ) * stride, &xyz, 12 ); memcpy( ptr + ( 2 * i + 1 ) * stride, &xyz, 12 ); if ( m_format.GetPositionDimension() == 4 ) { reinterpret_cast( ptr + ( 2 * i ) * stride )[3] = 1.0f; reinterpret_cast( ptr + ( 2 * i + 1 ) * stride )[3] = 0.0f; } } + } + if ( m_lockStatus != 0 ) { Unlock(); } +} + +void rvVertexBuffer::CopyShadowVolData( int vertexBufferOffset, int numVertices, shadowCache_t *srcVertData ) { + if ( vertexBufferOffset + numVertices > m_numVertices ) { common->FatalError( "rvVertexBuffer: attempt to copy shadow volume vertex data out-of-bounds" ); return; } + unsigned char *ptr = NULL; int stride = 0; + if ( ( m_format.m_flags & rvVertexFormatFlagPosition ) != 0 && LockPosition( vertexBufferOffset, numVertices, rvVertexBufferLockFlagWrite, ptr, stride ) ) { + const int bytes = Min( 4, m_format.GetPositionDimension() ) * sizeof( float ); + for ( int i = 0; i < numVertices; ++i ) { memcpy( ptr + i * stride, &srcVertData[i].xyz, bytes ); } + } + if ( m_lockStatus != 0 ) { Unlock(); } +} + +void rvVertexBuffer::Init( const rvVertexFormat &vertexFormat, int numVertices, unsigned int flagMask ) { + if ( m_format.m_flags != 0 ) { Shutdown(); } + m_format.Init( vertexFormat ); + m_loadFormat.Init( vertexFormat ); + m_numVertices = numVertices; + m_flags = flagMask; + CreateVertexStorage(); +} + +void rvVertexBuffer::Init( Lexer &lexer ) { + if ( m_format.m_flags != 0 ) { Shutdown(); } + lexer.ExpectTokenString( "{" ); + lexer.ExpectTokenString( "VertexFormat" ); + m_format.Init( lexer ); + m_loadFormat.Init( m_format ); + m_flags = 0; + idToken token; + while ( lexer.ReadToken( &token ) ) { + if ( !token.Icmp( "LoadVertexFormat" ) ) { m_loadFormat.Init( lexer ); } + else if ( !token.Icmp( "SystemMemory" ) ) { m_flags |= rvVertexBufferFlagSystemMemory; } + else if ( !token.Icmp( "VideoMemory" ) ) { m_flags |= rvVertexBufferFlagVideoMemory; } + else { break; } + } + if ( m_flags == 0 ) { m_flags = rvVertexBufferFlagVideoMemory; } + if ( !token.Icmp( "SoA" ) ) { m_flags |= rvVertexBufferFlagSoA; lexer.ReadToken( &token ); } + if ( token.Icmp( "Vertex" ) ) { lexer.Error( "Expected vertex header" ); } + lexer.ExpectTokenString( "[" ); + m_numVertices = lexer.ParseInt(); + lexer.ExpectTokenString( "]" ); + if ( m_numVertices <= 0 ) { lexer.Error( "Invalid vertex count" ); return; } + CreateVertexStorage(); + lexer.ExpectTokenString( "{" ); + if ( !lexer.ReadToken( &token ) ) { return; } + if ( token.Icmp( "}" ) ) { + lexer.UnreadToken( &token ); + m_lockStatus = rvVertexBufferLockFlagWrite | rvVertexBufferLockFlagEmpty; + m_lockVertexOffset = 0; + m_lockVertexCount = m_numVertices; + int tokenSubTypes[72]; + if ( ( m_flags & rvVertexBufferFlagSoA ) != 0 ) { + auto parse = [&]( int component, int dimension, unsigned char *storage, int count ) { if ( storage != NULL ) { m_format.GetTokenSubTypes( static_cast( component ), tokenSubTypes ); lexer.ParseNumericStructArray( dimension, tokenSubTypes, count, storage ); } }; + if ( ( m_format.m_flags & rvVertexFormatFlagSwizzledPosition ) != 0 ) { parse( 1, 3, reinterpret_cast( m_swizzledPositionArray ), ( m_numVertices + 3 ) & ~3 ); } + else { parse( 0, m_format.GetPositionDimension(), reinterpret_cast( m_positionArray ), m_numVertices ); } + parse( 2, 1, reinterpret_cast( m_blendIndexArray ), m_numVertices ); + parse( 3, m_format.GetBlendWeightDimension(), reinterpret_cast( m_blendWeightArray ), m_numVertices ); + parse( 4, 3, reinterpret_cast( m_normalArray ), m_numVertices ); + parse( 5, 3, reinterpret_cast( m_tangentArray ), m_numVertices ); + parse( 6, 3, reinterpret_cast( m_binormalArray ), m_numVertices ); + parse( 7, 1, reinterpret_cast( m_diffuseColorArray ), m_numVertices ); + parse( 8, 1, reinterpret_cast( m_specularColorArray ), m_numVertices ); + parse( 9, 1, reinterpret_cast( m_pointSizeArray ), m_numVertices ); + for ( int texture = 0; texture < 7; ++texture ) { parse( 10 + texture, m_format.GetTextureDimension( texture ), reinterpret_cast( m_texCoordArrays[texture] ), m_numVertices ); } + } else { + unsigned char *storage = NULL; int stride = 0; + if ( !LockInterleaved( 0, m_numVertices, rvVertexBufferLockFlagWrite | rvVertexBufferLockFlagEmpty, reinterpret_cast( storage ), stride ) ) { lexer.Error( "Vertex buffer cannot be mapped for access" ); Shutdown(); return; } + for ( int i = 0; i < m_format.m_numValues; ++i ) { tokenSubTypes[i] = m_format.m_tokenSubTypes[i]; } + lexer.ParseNumericStructArray( m_format.m_numValues, tokenSubTypes, m_numVertices, storage ); + } + Unlock(); + lexer.ExpectTokenString( "}" ); + } else { + m_flags |= rvVertexBufferFlagInitiallyEmpty; + } + lexer.ExpectTokenString( "}" ); +} diff --git a/src/renderer/rvVertexBuffer.h b/src/renderer/rvVertexBuffer.h new file mode 100644 index 0000000..6571d1f --- /dev/null +++ b/src/renderer/rvVertexBuffer.h @@ -0,0 +1,148 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software + +#ifndef __RENDERER_RVVERTEXBUFFER_H__ +#define __RENDERER_RVVERTEXBUFFER_H__ + +#include "rvVertexFormat.h" + +class Lexer; + +enum rvVertexBufferFlags { + rvVertexBufferFlagSystemMemory = BIT( 0 ), + rvVertexBufferFlagVideoMemory = BIT( 1 ), + rvVertexBufferFlagSoA = BIT( 2 ), + rvVertexBufferFlagDynamicStream = BIT( 3 ), + rvVertexBufferFlagInitiallyEmpty = BIT( 4 ), + rvVertexBufferFlagWriteLoadFormatAsPrimary = BIT( 5 ), + rvVertexBufferFlagUsageDraw = BIT( 6 ), + rvVertexBufferFlagUsageShadow = BIT( 7 ), + rvVertexBufferFlagUsageSilTrace = BIT( 8 ), + rvVertexBufferFlagUsageExplicitShadow = BIT( 9 ) +}; + +enum rvVertexBufferLockFlags { + rvVertexBufferLockFlagRead = BIT( 0 ), + rvVertexBufferLockFlagWrite = BIT( 1 ), + rvVertexBufferLockFlagEmpty = BIT( 2 ) +}; + +class rvVertexBuffer { +friend class rvRenderModelMD5R; +public: + rvVertexBuffer(); + ~rvVertexBuffer(); + + void Init( const rvVertexFormat &vertexFormat, int numVertices, unsigned int flagMask ); + void Init( Lexer &lexer ); + void Shutdown(); + void Resize( int numVertices ); + void SetupForRender( int vertexStartOffset, const rvVertexFormat &formatNeeded ); + void SetLoadFormat( const rvVertexFormat &loadFormat ); + void Unlock(); + + bool LockPosition( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, + unsigned char *&positionPtr, int &stride ); + bool LockBlendIndex( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, + unsigned char *&blendIndexPtr, int &stride ); + bool LockBlendWeight( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, + unsigned char *&blendWeightPtr, int &stride ); + bool LockNormal( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, + unsigned char *&normalPtr, int &stride ); + bool LockTangent( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, + unsigned char *&tangentPtr, int &stride ); + bool LockBinormal( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, + unsigned char *&binormalPtr, int &stride ); + bool LockDiffuseColor( int vertexBufferOffset, int numVerticesToLock, unsigned int lockFlags, + unsigned char *&diffuseColorPtr, int &stride ); + bool LockTextureCoordinate( int textureCoordinate, int vertexBufferOffset, + int numVerticesToLock, unsigned int lockFlags, + unsigned char *&textureCoordinatePtr, int &stride ); + + void CopyData( int destVertexOffset, int numVertices, const unsigned char *srcPtr, + int srcStride, const rvVertexFormat &srcFormat, const unsigned int *copyMapping ); + void CopyData( unsigned char *destPtr, int destStride, const rvVertexFormat &destFormat, + int srcVertexOffset, int numVertices, const unsigned int *copyMapping ); + void CopyRemappedData( int vertexBufferOffset, int numVertices, unsigned int *copyMapping, + unsigned int *transformMapOldToNew, rvBlend4DrawVert *srcVertData, + bool absBlendWeights ); + void CopyRemappedShadowVolData( int vertexBufferOffset, int numVertices, + unsigned int *copyMapping, unsigned int *transformMapOldToNew, + rvBlend4DrawVert *srcVertData ); + int CopySilTraceData( unsigned int *indexMapping, int vertexBufferOffset, + int numVertices, int *copyMapping, const idDrawVert *srcVertData ); + void CopyData( int vertexBufferOffset, int numVertices, const idDrawVert *srcVertData ); + void CopyRemappedShadowVolData( int vertexBufferOffset, int numVertices, + int *copyMapping, const idDrawVert *srcVertData ); + void CopyShadowVolData( int vertexBufferOffset, int numVertices, shadowCache_t *srcVertData ); + void Write( idFile &outFile, const char *prepend ); + + static void ComponentCopy( unsigned char *destPtr, int destStride, + Rv_Vertex_Data_Type_t destDataType, int destDimension, + const unsigned char *srcPtr, int srcStride, Rv_Vertex_Data_Type_t srcDataType, + int srcDimension, int numVertices, const unsigned int *copyMapping, + float *transform, bool normalize ); + + const rvVertexFormat &GetFormat() const { return m_format; } + int GetNumVertices() const { return m_numVertices; } + int GetNumVerticesWritten() const { return m_numVerticesWritten; } + unsigned int GetBufferId() const { return m_vbID; } + bool IsInitiallyEmpty() const { return ( m_flags & rvVertexBufferFlagInitiallyEmpty ) != 0; } + bool IsSystemMemory() const { return ( m_flags & rvVertexBufferFlagSystemMemory ) != 0; } + rvSilTraceVertT *GetSilTraceVertexArray( int vertexOffset ); + +protected: + void ResetValues(); + void CreateVertexStorage(); + bool LockInterleaved( int vertexOffset, int numVerticesToLock, unsigned int lockFlags, + void *&startPtr, int &stride ); + void TransferSoAToAoS( unsigned char *vertexDest ); + + rvVertexFormat m_format; + rvVertexFormat m_loadFormat; + unsigned int m_flags; + unsigned int m_lockStatus; + int m_numVertices; + unsigned int m_vbID; + int m_lockVertexOffset; + int m_lockVertexCount; + unsigned char *m_lockedBase; + int m_numVerticesWritten; + unsigned char *m_interleavedStorage; + float *m_positionArray; + float *m_swizzledPositionArray; + unsigned int *m_blendIndexArray; + float *m_blendWeightArray; + float *m_normalArray; + float *m_tangentArray; + float *m_binormalArray; + unsigned int *m_diffuseColorArray; + unsigned int *m_specularColorArray; + float *m_pointSizeArray; + float *m_texCoordArrays[7]; +}; + +#endif diff --git a/src/renderer/rvVertexBufferCopy.cpp b/src/renderer/rvVertexBufferCopy.cpp new file mode 100644 index 0000000..2dbd983 --- /dev/null +++ b/src/renderer/rvVertexBufferCopy.cpp @@ -0,0 +1,373 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvVertexBuffer.h" + +// Retail compilation unit: rvVertexBufferCopy.obj. + +static ID_INLINE unsigned int SourceVertex( const unsigned int *copyMapping, int vertex ) { + return copyMapping != NULL ? copyMapping[vertex] : vertex; +} + +static unsigned short FloatToHalf( float value ) { + unsigned int bits; + memcpy( &bits, &value, sizeof( bits ) ); + const unsigned int sign = ( bits >> 16 ) & 0x8000; + int exponent = static_cast( ( bits >> 23 ) & 0xFF ) - 127 + 15; + unsigned int mantissa = bits & 0x7FFFFF; + if ( exponent <= 0 ) { + if ( exponent < -10 ) { return static_cast( sign ); } + mantissa = ( mantissa | 0x800000 ) >> ( 1 - exponent ); + return static_cast( sign | ( ( mantissa + 0x1000 ) >> 13 ) ); + } + if ( exponent >= 31 ) { return static_cast( sign | 0x7C00 ); } + return static_cast( sign | ( exponent << 10 ) | ( ( mantissa + 0x1000 ) >> 13 ) ); +} + +static float HalfToFloat( unsigned short value ) { + const unsigned int sign = static_cast( value & 0x8000 ) << 16; + unsigned int exponent = ( value >> 10 ) & 0x1F; + unsigned int mantissa = value & 0x3FF; + unsigned int bits; + if ( exponent == 0 ) { + if ( mantissa == 0 ) { bits = sign; } + else { + int unbiased = -14; + while ( ( mantissa & 0x400 ) == 0 ) { mantissa <<= 1; --unbiased; } + mantissa &= 0x3FF; + bits = sign | static_cast( unbiased + 127 ) << 23 | mantissa << 13; + } + } else if ( exponent == 31 ) { + bits = sign | 0x7F800000 | mantissa << 13; + } else { + bits = sign | ( exponent - 15 + 127 ) << 23 | mantissa << 13; + } + float result; + memcpy( &result, &bits, sizeof( result ) ); + return result; +} + +static void ConvertFloat16PairToFloat( float *destFloats, unsigned int float16Pair ) { + destFloats[0] = HalfToFloat( static_cast( float16Pair ) ); + destFloats[1] = HalfToFloat( static_cast( float16Pair >> 16 ) ); +} + +static void ConvertFloatToFloat16Pair( unsigned int &float16Pair, const float *srcFloats ) { + float16Pair = FloatToHalf( srcFloats[0] ) | static_cast( FloatToHalf( srcFloats[1] ) ) << 16; +} + +static void rvVtxCopyFloatToFloat( unsigned char *destPtr, int destStride, + int destNumValuesPerComponent, const unsigned char *srcPtr, int srcStride, + int srcNumValuesPerComponent, int numComponents, const unsigned int *copyMapping, + const float *srcTailPtr, bool absFlag ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + float *dest = reinterpret_cast( destPtr + vertex * destStride ); + const float *src = reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + const int commonValues = Min( destNumValuesPerComponent, srcNumValuesPerComponent ); + for ( int value = 0; value < commonValues; ++value ) { dest[value] = absFlag ? idMath::Fabs( src[value] ) : src[value]; } + for ( int value = commonValues; value < destNumValuesPerComponent; ++value ) { dest[value] = srcTailPtr != NULL ? srcTailPtr[value - commonValues] : 0.0f; } + } +} + +static void rvVtxCopyFloatToInt( unsigned char *destPtr, int destStride, + int destNumValuesPerComponent, const unsigned char *srcPtr, int srcStride, + int srcNumValuesPerComponent, int numComponents, const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + int *dest = reinterpret_cast( destPtr + vertex * destStride ); + const float *src = reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + for ( int value = 0; value < destNumValuesPerComponent; ++value ) { dest[value] = value < srcNumValuesPerComponent ? static_cast( src[value] ) : 0; } + } +} + +static void rvVtxCopyIntToFloat( unsigned char *destPtr, int destStride, + int destNumValuesPerComponent, const unsigned char *srcPtr, int srcStride, + int srcNumValuesPerComponent, int numComponents, const unsigned int *copyMapping, + const float *srcTailPtr ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + float *dest = reinterpret_cast( destPtr + vertex * destStride ); + const int *src = reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + for ( int value = 0; value < destNumValuesPerComponent; ++value ) { dest[value] = value < srcNumValuesPerComponent ? static_cast( src[value] ) : ( srcTailPtr != NULL ? srcTailPtr[value - srcNumValuesPerComponent] : 0.0f ); } + } +} + +static void rvVtxCopyIntToInt( unsigned char *destPtr, int destStride, + int destNumValuesPerComponent, const unsigned char *srcPtr, int srcStride, + int srcNumValuesPerComponent, int numComponents, const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + int *dest = reinterpret_cast( destPtr + vertex * destStride ); + const int *src = reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + for ( int value = 0; value < destNumValuesPerComponent; ++value ) { dest[value] = value < srcNumValuesPerComponent ? src[value] : 0; } + } +} + +static void rvVtxCopyDWordToDWord( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + *reinterpret_cast( destPtr + vertex * destStride ) = + *reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + } +} + +static void rvVtxCopyFloatToDec16Pair( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, const float *destScales, + const unsigned int *destMasks, const int *destShifts, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + const float *src = reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + unsigned int packed = 0; + for ( int value = 0; value < 2; ++value ) { packed |= ( static_cast( static_cast( idMath::Ftob( src[value] * destScales[value] ) ) ) << destShifts[value] ) & destMasks[value]; } + *reinterpret_cast( destPtr + vertex * destStride ) = packed; + } +} + +static void rvVtxCopyDec16PairToFloat( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, const float *srcScales, + const unsigned int *srcMasks, const int *srcShifts, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + const unsigned int packed = *reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + float *dest = reinterpret_cast( destPtr + vertex * destStride ); + for ( int value = 0; value < 2; ++value ) { dest[value] = static_cast( ( packed & srcMasks[value] ) >> srcShifts[value] ) * srcScales[value]; } + } +} + +static void rvVtxCopySignedDec16PairToFloat( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, const float *srcScales, + const unsigned int *srcMasks, const int *srcShifts, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + const unsigned int packed = *reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + float *dest = reinterpret_cast( destPtr + vertex * destStride ); + for ( int value = 0; value < 2; ++value ) { const short decoded = static_cast( ( packed & srcMasks[value] ) >> srcShifts[value] ); dest[value] = decoded * srcScales[value]; } + } +} + +static void rvVtxCopyFloatToFloat16Pair( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + ConvertFloatToFloat16Pair( *reinterpret_cast( destPtr + vertex * destStride ), + reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ) ); + } +} + +static void rvVtxCopyFloat16PairToFloat( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + ConvertFloat16PairToFloat( reinterpret_cast( destPtr + vertex * destStride ), + *reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ) ); + } +} + +static void rvVtxCopyFloatToDecTriple( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, const float *destScales, + const unsigned int *destMasks, const int *destShifts, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + const float *src = reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + unsigned int packed = 0; + for ( int value = 0; value < 3; ++value ) { packed |= ( static_cast( static_cast( src[value] * destScales[value] ) ) << destShifts[value] ) & destMasks[value]; } + *reinterpret_cast( destPtr + vertex * destStride ) = packed; + } +} + +static void rvVtxCopyDecTripleToFloat( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, const float *srcScales, + const unsigned int *srcMasks, const int *srcShifts, int numComponents, + const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + const unsigned int packed = *reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + float *dest = reinterpret_cast( destPtr + vertex * destStride ); + for ( int value = 0; value < 3; ++value ) { dest[value] = static_cast( ( packed & srcMasks[value] ) >> srcShifts[value] ) * srcScales[value]; } + } +} + +static void rvVtxCopySignedDecTripleToFloat( unsigned char *destPtr, int destStride, + const unsigned char *srcPtr, int srcStride, const float *srcScales, + const int *srcLeftShifts, const int *srcShifts, const unsigned int *srcMasks, + int numComponents, const unsigned int *copyMapping ) { + for ( int vertex = 0; vertex < numComponents; ++vertex ) { + const unsigned int packed = *reinterpret_cast( srcPtr + SourceVertex( copyMapping, vertex ) * srcStride ); + float *dest = reinterpret_cast( destPtr + vertex * destStride ); + for ( int value = 0; value < 3; ++value ) { + const int decoded = static_cast( ( packed & srcMasks[value] ) << srcLeftShifts[value] ) >> ( srcLeftShifts[value] + srcShifts[value] ); + dest[value] = decoded * srcScales[value]; + } + } +} + +static const unsigned int mask16Bit[2] = { 0x0000FFFFu, 0xFFFF0000u }; +static const int shift16Bit[2] = { 0, 16 }; +static const float scaleOne2[2] = { 1.0f, 1.0f }; +static const float scaleOne3[3] = { 1.0f, 1.0f, 1.0f }; +static const float scale32767[2] = { 32767.0f, 32767.0f }; +static const float scale65535[2] = { 65535.0f, 65535.0f }; +static const float scaleInv32767[2] = { 1.0f / 32767.0f, 1.0f / 32767.0f }; +static const float scaleInv65535[2] = { 1.0f / 65535.0f, 1.0f / 65535.0f }; + +struct PackedTripleDesc { + unsigned int masks[3]; + int shifts[3]; + int leftShifts[3]; + float signedScale[3]; + float unsignedScale[3]; + float signedInvScale[3]; + float unsignedInvScale[3]; +}; + +static const PackedTripleDesc packed10_10_10 = { + { 0x000003FFu, 0x000FFC00u, 0x3FF00000u }, { 0, 10, 20 }, { 22, 12, 2 }, + { 511.0f, 511.0f, 511.0f }, { 1023.0f, 1023.0f, 1023.0f }, + { 1.0f / 511.0f, 1.0f / 511.0f, 1.0f / 511.0f }, + { 1.0f / 1023.0f, 1.0f / 1023.0f, 1.0f / 1023.0f } +}; +static const PackedTripleDesc packed10_11_11 = { + { 0x000003FFu, 0x001FFC00u, 0xFFE00000u }, { 0, 10, 21 }, { 22, 11, 0 }, + { 511.0f, 1023.0f, 1023.0f }, { 1023.0f, 2047.0f, 2047.0f }, + { 1.0f / 511.0f, 1.0f / 1023.0f, 1.0f / 1023.0f }, + { 1.0f / 1023.0f, 1.0f / 2047.0f, 1.0f / 2047.0f } +}; +static const PackedTripleDesc packed11_11_10 = { + { 0x000007FFu, 0x003FF800u, 0xFFC00000u }, { 0, 11, 22 }, { 21, 10, 0 }, + { 1023.0f, 1023.0f, 511.0f }, { 2047.0f, 2047.0f, 1023.0f }, + { 1.0f / 1023.0f, 1.0f / 1023.0f, 1.0f / 511.0f }, + { 1.0f / 2047.0f, 1.0f / 2047.0f, 1.0f / 1023.0f } +}; + +static const PackedTripleDesc &PackedDescForType( Rv_Vertex_Data_Type_t type ) { + if ( type >= RV_VERTEX_DATA_TYPE_UDEC_11_11_10 ) { return packed11_11_10; } + if ( type >= RV_VERTEX_DATA_TYPE_UDEC_10_11_11 ) { return packed10_11_11; } + return packed10_10_10; +} + +static void CopyPackedByteComponent( unsigned char *destPtr, int destStride, + Rv_Vertex_Data_Type_t destType, const unsigned char *srcPtr, int srcStride, + Rv_Vertex_Data_Type_t srcType, int srcDimension, int numVertices, + const unsigned int *copyMapping ) { + const bool destSigned = destType == RV_VERTEX_DATA_TYPE_BYTE || destType == RV_VERTEX_DATA_TYPE_BYTEN; + const bool destNormalized = destType == RV_VERTEX_DATA_TYPE_COLOR || destType == RV_VERTEX_DATA_TYPE_UBYTEN || destType == RV_VERTEX_DATA_TYPE_BYTEN; + for ( int vertex = 0; vertex < numVertices; ++vertex ) { + unsigned char *dest = destPtr + vertex * destStride; + const unsigned char *source = srcPtr + SourceVertex( copyMapping, vertex ) * srcStride; + if ( srcType == RV_VERTEX_DATA_TYPE_FLOAT ) { + const float *src = reinterpret_cast( source ); + for ( int value = 0; value < 4; ++value ) { + const float input = value < srcDimension ? src[value] : ( value == 3 ? 1.0f : 0.0f ); + const float scale = destNormalized ? ( destSigned ? 127.0f : 255.0f ) : 1.0f; + if ( destSigned ) { reinterpret_cast( dest )[value] = static_cast( idMath::ClampInt( -128, 127, static_cast( input * scale ) ) ); } + else { dest[value] = static_cast( idMath::ClampInt( 0, 255, static_cast( input * scale ) ) ); } + } + } else { memcpy( dest, source, 4 ); } + } +} + +void rvVertexBuffer::ComponentCopy( unsigned char *destPtr, int destStride, + Rv_Vertex_Data_Type_t destDataType, int destNumValuesPerComponent, + const unsigned char *srcPtr, int srcStride, Rv_Vertex_Data_Type_t srcDataType, + int srcNumValuesPerComponent, int numComponents, const unsigned int *copyMapping, + float *srcTailPtr, bool absFlag ) { + if ( destDataType == RV_VERTEX_DATA_TYPE_FLOAT ) { + switch ( srcDataType ) { + case RV_VERTEX_DATA_TYPE_FLOAT: + rvVtxCopyFloatToFloat( destPtr, destStride, destNumValuesPerComponent, srcPtr, srcStride, srcNumValuesPerComponent, numComponents, copyMapping, srcTailPtr, absFlag ); return; + case RV_VERTEX_DATA_TYPE_FLOAT16: + rvVtxCopyFloat16PairToFloat( destPtr, destStride, srcPtr, srcStride, numComponents, copyMapping ); + if ( srcNumValuesPerComponent == 4 ) { rvVtxCopyFloat16PairToFloat( destPtr + 8, destStride, srcPtr + 4, srcStride, numComponents, copyMapping ); } return; + case RV_VERTEX_DATA_TYPE_INT: + case RV_VERTEX_DATA_TYPE_UINT: + case RV_VERTEX_DATA_TYPE_INTN: + case RV_VERTEX_DATA_TYPE_UINTN: + rvVtxCopyIntToFloat( destPtr, destStride, destNumValuesPerComponent, srcPtr, srcStride, srcNumValuesPerComponent, numComponents, copyMapping, srcTailPtr ); return; + case RV_VERTEX_DATA_TYPE_SHORT: + rvVtxCopySignedDec16PairToFloat( destPtr, destStride, srcPtr, srcStride, scaleOne2, mask16Bit, shift16Bit, numComponents, copyMapping ); break; + case RV_VERTEX_DATA_TYPE_USHORT: + rvVtxCopyDec16PairToFloat( destPtr, destStride, srcPtr, srcStride, scaleOne2, mask16Bit, shift16Bit, numComponents, copyMapping ); break; + case RV_VERTEX_DATA_TYPE_SHORTN: + rvVtxCopySignedDec16PairToFloat( destPtr, destStride, srcPtr, srcStride, scaleInv32767, mask16Bit, shift16Bit, numComponents, copyMapping ); break; + case RV_VERTEX_DATA_TYPE_USHORTN: + rvVtxCopyDec16PairToFloat( destPtr, destStride, srcPtr, srcStride, scaleInv65535, mask16Bit, shift16Bit, numComponents, copyMapping ); break; + default: + if ( srcDataType >= RV_VERTEX_DATA_TYPE_UDEC_10_10_10 ) { + const PackedTripleDesc &desc = PackedDescForType( srcDataType ); + const bool signedType = ( static_cast( srcDataType ) & 1 ) != 0; + const bool normalized = srcDataType == RV_VERTEX_DATA_TYPE_UDEC_10_10_10N || srcDataType == RV_VERTEX_DATA_TYPE_DEC_10_10_10N || srcDataType == RV_VERTEX_DATA_TYPE_UDEC_10_11_11N || srcDataType == RV_VERTEX_DATA_TYPE_DEC_10_11_11N || srcDataType == RV_VERTEX_DATA_TYPE_UDEC_11_11_10N || srcDataType == RV_VERTEX_DATA_TYPE_DEC_11_11_10N; + if ( signedType ) { rvVtxCopySignedDecTripleToFloat( destPtr, destStride, srcPtr, srcStride, normalized ? desc.signedInvScale : scaleOne3, desc.leftShifts, desc.shifts, desc.masks, numComponents, copyMapping ); } + else { rvVtxCopyDecTripleToFloat( destPtr, destStride, srcPtr, srcStride, normalized ? desc.unsignedInvScale : scaleOne3, desc.masks, desc.shifts, numComponents, copyMapping ); } + return; + } + CopyPackedByteComponent( destPtr, destStride, RV_VERTEX_DATA_TYPE_UBYTE, srcPtr, srcStride, srcDataType, srcNumValuesPerComponent, numComponents, copyMapping ); return; + } + if ( srcNumValuesPerComponent == 4 ) { + if ( srcDataType == RV_VERTEX_DATA_TYPE_SHORT || srcDataType == RV_VERTEX_DATA_TYPE_SHORTN ) { rvVtxCopySignedDec16PairToFloat( destPtr + 8, destStride, srcPtr + 4, srcStride, srcDataType == RV_VERTEX_DATA_TYPE_SHORTN ? scaleInv32767 : scaleOne2, mask16Bit, shift16Bit, numComponents, copyMapping ); } + else { rvVtxCopyDec16PairToFloat( destPtr + 8, destStride, srcPtr + 4, srcStride, srcDataType == RV_VERTEX_DATA_TYPE_USHORTN ? scaleInv65535 : scaleOne2, mask16Bit, shift16Bit, numComponents, copyMapping ); } + } + return; + } + + if ( destDataType == RV_VERTEX_DATA_TYPE_FLOAT16 ) { + if ( srcDataType == RV_VERTEX_DATA_TYPE_FLOAT ) { + rvVtxCopyFloatToFloat16Pair( destPtr, destStride, srcPtr, srcStride, numComponents, copyMapping ); + if ( srcNumValuesPerComponent == 4 ) { rvVtxCopyFloatToFloat16Pair( destPtr + 4, destStride, srcPtr + 8, srcStride, numComponents, copyMapping ); } + } else { rvVtxCopyDWordToDWord( destPtr, destStride, srcPtr, srcStride, numComponents, copyMapping ); } + return; + } + + if ( destDataType == RV_VERTEX_DATA_TYPE_INT || destDataType == RV_VERTEX_DATA_TYPE_UINT || destDataType == RV_VERTEX_DATA_TYPE_INTN || destDataType == RV_VERTEX_DATA_TYPE_UINTN ) { + if ( srcDataType == RV_VERTEX_DATA_TYPE_FLOAT ) { rvVtxCopyFloatToInt( destPtr, destStride, destNumValuesPerComponent, srcPtr, srcStride, srcNumValuesPerComponent, numComponents, copyMapping ); } + else { rvVtxCopyIntToInt( destPtr, destStride, destNumValuesPerComponent, srcPtr, srcStride, srcNumValuesPerComponent, numComponents, copyMapping ); } + return; + } + + if ( destDataType >= RV_VERTEX_DATA_TYPE_COLOR && destDataType <= RV_VERTEX_DATA_TYPE_BYTEN ) { + CopyPackedByteComponent( destPtr, destStride, destDataType, srcPtr, srcStride, srcDataType, srcNumValuesPerComponent, numComponents, copyMapping ); + return; + } + + if ( destDataType >= RV_VERTEX_DATA_TYPE_SHORT && destDataType <= RV_VERTEX_DATA_TYPE_USHORTN ) { + if ( srcDataType == RV_VERTEX_DATA_TYPE_FLOAT ) { + const float *scale = destDataType == RV_VERTEX_DATA_TYPE_SHORTN ? scale32767 : destDataType == RV_VERTEX_DATA_TYPE_USHORTN ? scale65535 : scaleOne2; + rvVtxCopyFloatToDec16Pair( destPtr, destStride, srcPtr, srcStride, scale, mask16Bit, shift16Bit, numComponents, copyMapping ); + if ( srcNumValuesPerComponent == 4 ) { rvVtxCopyFloatToDec16Pair( destPtr + 4, destStride, srcPtr + 8, srcStride, scale, mask16Bit, shift16Bit, numComponents, copyMapping ); } + } else { + rvVtxCopyDWordToDWord( destPtr, destStride, srcPtr, srcStride, numComponents, copyMapping ); + if ( srcNumValuesPerComponent == 4 ) { rvVtxCopyDWordToDWord( destPtr + 4, destStride, srcPtr + 4, srcStride, numComponents, copyMapping ); } + } + return; + } + + if ( destDataType >= RV_VERTEX_DATA_TYPE_UDEC_10_10_10 ) { + if ( srcDataType == RV_VERTEX_DATA_TYPE_FLOAT ) { + const PackedTripleDesc &desc = PackedDescForType( destDataType ); + const bool signedType = ( static_cast( destDataType ) & 1 ) != 0; + const bool normalized = destDataType == RV_VERTEX_DATA_TYPE_UDEC_10_10_10N || destDataType == RV_VERTEX_DATA_TYPE_DEC_10_10_10N || destDataType == RV_VERTEX_DATA_TYPE_UDEC_10_11_11N || destDataType == RV_VERTEX_DATA_TYPE_DEC_10_11_11N || destDataType == RV_VERTEX_DATA_TYPE_UDEC_11_11_10N || destDataType == RV_VERTEX_DATA_TYPE_DEC_11_11_10N; + const float *scale = normalized ? ( signedType ? desc.signedScale : desc.unsignedScale ) : scaleOne3; + rvVtxCopyFloatToDecTriple( destPtr, destStride, srcPtr, srcStride, scale, desc.masks, desc.shifts, numComponents, copyMapping ); + } else { rvVtxCopyDWordToDWord( destPtr, destStride, srcPtr, srcStride, numComponents, copyMapping ); } + } +} diff --git a/src/renderer/rvVertexFormat.cpp b/src/renderer/rvVertexFormat.cpp new file mode 100644 index 0000000..8b93473 --- /dev/null +++ b/src/renderer/rvVertexFormat.cpp @@ -0,0 +1,366 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "tr_local.h" +#include "rvVertexFormat.h" + +struct rvVertexStorageDesc { + unsigned char tokenSubType; + unsigned char numComponents; +}; + +struct rvVertexFormatStorageDesc { + unsigned int glStorage; + unsigned int countMask; + unsigned int countAdd; + unsigned int compressedCount; + bool normalized; +}; + +static const rvVertexStorageDesc vertexStorageDescArray[RV_NUM_VERTEX_DATA_TYPES] = { + { 0, 0 }, { 128, 1 }, { 65, 2 }, { 1, 1 }, { 65, 1 }, { 1, 1 }, { 65, 1 }, + { 65, 1 }, { 65, 1 }, { 65, 1 }, { 65, 1 }, { 65, 1 }, { 65, 2 }, { 65, 2 }, + { 65, 2 }, { 65, 2 }, { 65, 3 }, { 65, 3 }, { 65, 3 }, { 65, 3 }, { 65, 3 }, + { 65, 3 }, { 65, 3 }, { 65, 3 }, { 65, 3 }, { 65, 3 }, { 65, 3 }, { 65, 3 } +}; + +static const rvVertexFormatStorageDesc formatDescs[RV_NUM_VERTEX_DATA_TYPES] = { + { 0, 0, 0, 0, false }, + { GL_FLOAT, ~0u, 0, 0, false }, { GL_FLOAT, ~1u, 1, 0, false }, + { GL_INT, ~0u, 0, 0, false }, { GL_UNSIGNED_INT, ~0u, 0, 0, false }, + { GL_INT, ~0u, 0, 0, true }, { GL_UNSIGNED_INT, ~0u, 0, 0, true }, + { GL_UNSIGNED_BYTE, 0, 0, 4, true }, { GL_UNSIGNED_BYTE, 0, 0, 4, false }, + { GL_BYTE, 0, 0, 4, false }, { GL_UNSIGNED_BYTE, 0, 0, 4, true }, + { GL_BYTE, 0, 0, 4, true }, + { GL_SHORT, ~1u, 1, 0, false }, { GL_UNSIGNED_SHORT, ~1u, 1, 0, false }, + { GL_SHORT, ~1u, 1, 0, true }, { GL_UNSIGNED_SHORT, ~1u, 1, 0, true }, + { GL_UNSIGNED_BYTE, 0, 0, 3, false }, { GL_BYTE, 0, 0, 3, false }, + { GL_UNSIGNED_BYTE, 0, 0, 3, true }, { GL_BYTE, 0, 0, 3, true }, + { GL_UNSIGNED_SHORT, 0, 0, 3, false }, { GL_SHORT, 0, 0, 3, false }, + { GL_UNSIGNED_SHORT, 0, 0, 3, true }, { GL_SHORT, 0, 0, 3, true }, + { GL_UNSIGNED_INT, 0, 0, 3, false }, { GL_INT, 0, 0, 3, false }, + { GL_UNSIGNED_INT, 0, 0, 3, true }, { GL_INT, 0, 0, 3, true } +}; + +static const char *outputDataTypeStrings[RV_NUM_VERTEX_DATA_TYPES] = { + "", "Float ", "Float16 ", "Int ", "UInt ", "IntN ", "UIntN ", "Color ", + "UByte ", "Byte ", "UByteN ", "ByteN ", "Short ", "UShort ", "ShortN ", "UShortN ", + "UDec_10_10_10 ", "Dec_10_10_10 ", "UDec_10_10_10N ", "Dec_10_10_10N ", + "UDec_10_11_11 ", "Dec_10_11_11 ", "UDec_10_11_11N ", "Dec_10_11_11N ", + "UDec_11_11_10 ", "Dec_11_11_10 ", "UDec_11_11_10N ", "Dec_11_11_10N " +}; + +static int VertexElementCount( int dimension, const rvVertexFormatStorageDesc &desc ) { + return ( ~desc.countMask & desc.compressedCount ) + ( dimension & ( desc.countMask + desc.countAdd ) ); +} + +void rvVertexFormat::CalcSize() { + m_size = 0; + m_numValues = 0; + m_glVASMask = 0; + m_byteOffset[0] = m_byteOffset[1] = 0; + if ( ( m_flags & rvVertexFormatFlagSwizzledPosition ) != 0 ) { + m_size = 12; + } else if ( ( m_flags & rvVertexFormatFlagPosition ) != 0 ) { + const rvVertexStorageDesc &desc = vertexStorageDescArray[m_dataTypes[0]]; + for ( int remaining = GetPositionDimension(); remaining > 0; remaining -= desc.numComponents ) { + m_tokenSubTypes[m_numValues++] = desc.tokenSubType; + m_size += 4; + } + m_glVASMask |= BIT( 0 ); + } + + for ( int component = 2; component <= 9; ++component ) { + m_byteOffset[component] = m_size; + if ( ( m_flags & BIT( component ) ) == 0 ) { continue; } + const rvVertexStorageDesc &desc = vertexStorageDescArray[m_dataTypes[component]]; + int dimensions = 1; + if ( component == RV_VERTEX_COMPONENT_BLEND_INDEX || component == RV_VERTEX_COMPONENT_DIFFUSE_COLOR || + component == RV_VERTEX_COMPONENT_SPECULAR_COLOR ) { dimensions = 4; } + else if ( component == RV_VERTEX_COMPONENT_BLEND_WEIGHT ) { dimensions = GetBlendWeightDimension(); } + else if ( component == RV_VERTEX_COMPONENT_NORMAL || component == RV_VERTEX_COMPONENT_TANGENT || + component == RV_VERTEX_COMPONENT_BINORMAL ) { dimensions = 3; } + for ( int remaining = dimensions; remaining > 0; remaining -= desc.numComponents ) { + m_tokenSubTypes[m_numValues++] = desc.tokenSubType; + m_size += 4; + } + static const unsigned int componentMasks[10] = { + BIT( 0 ), 0, BIT( 17 ), BIT( 21 ), BIT( 1 ), BIT( 22 ), BIT( 23 ), BIT( 2 ), BIT( 20 ), BIT( 31 ) + }; + m_glVASMask |= componentMasks[component]; + } + + if ( ( m_flags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int texture = 0; texture < 7; ++texture ) { + const int component = RV_VERTEX_COMPONENT_TEXTURE_COORDINATE_0 + texture; + m_byteOffset[component] = m_size; + const rvVertexStorageDesc &desc = vertexStorageDescArray[m_dataTypes[component]]; + const int dimensions = GetTextureDimension( texture ); + for ( int remaining = dimensions; remaining > 0; remaining -= desc.numComponents ) { + m_tokenSubTypes[m_numValues++] = desc.tokenSubType; + m_size += 4; + } + if ( dimensions != 0 ) { m_glVASMask |= BIT( 24 + texture ); } + } + } +} + +void rvVertexFormat::BuildDataTypes( Rv_Vertex_Data_Type_t *dataTypes ) { + if ( ( m_flags & ( rvVertexFormatFlagPosition | rvVertexFormatFlagSwizzledPosition ) ) != 0 ) { + m_dataTypes[0] = dataTypes != NULL && dataTypes[0] != RV_VERTEX_DATA_TYPE_NONE ? dataTypes[0] : RV_VERTEX_DATA_TYPE_FLOAT; + } + const Rv_Vertex_Data_Type_t defaults[10] = { + RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_UBYTEN, + RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_FLOAT, + RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_COLOR, RV_VERTEX_DATA_TYPE_COLOR, + RV_VERTEX_DATA_TYPE_FLOAT + }; + for ( int component = 2; component <= 9; ++component ) { + if ( ( m_flags & BIT( component ) ) != 0 ) { + m_dataTypes[component] = dataTypes != NULL && dataTypes[component] != RV_VERTEX_DATA_TYPE_NONE ? + dataTypes[component] : defaults[component]; + } + } + if ( ( m_flags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int component = 10; component < 17; ++component ) { + m_dataTypes[component] = dataTypes != NULL && dataTypes[component] != RV_VERTEX_DATA_TYPE_NONE ? + dataTypes[component] : RV_VERTEX_DATA_TYPE_FLOAT; + } + } + CalcSize(); +} + +void rvVertexFormat::GetTokenSubTypes( Rv_Vertex_Component_t vertexComponent, int *tokenSubTypes ) { + const int tokenType = vertexStorageDescArray[m_dataTypes[vertexComponent]].tokenSubType; + for ( int i = 0; i < 4; ++i ) { tokenSubTypes[i] = tokenType; } +} + +void rvVertexFormat::SetVertexDeclaration( int vertexStartOffset ) const { + const int base = vertexStartOffset * m_size; + if ( ( m_flags & rvVertexFormatFlagBlendIndex ) != 0 ) { + const rvVertexFormatStorageDesc &desc = formatDescs[m_dataTypes[2]]; + qglVertexAttribPointerARB( 1, VertexElementCount( 4, desc ), desc.glStorage, desc.normalized, m_size, + reinterpret_cast( base + m_byteOffset[2] ) ); + } + if ( ( m_flags & rvVertexFormatFlagBlendWeight ) != 0 ) { + const rvVertexFormatStorageDesc &desc = formatDescs[m_dataTypes[3]]; + qglVertexAttribPointerARB( 5, VertexElementCount( GetBlendWeightDimension(), desc ), desc.glStorage, + desc.normalized, m_size, reinterpret_cast( base + m_byteOffset[3] ) ); + } + if ( ( m_flags & rvVertexFormatFlagNormal ) != 0 ) { + qglNormalPointer( formatDescs[m_dataTypes[4]].glStorage, m_size, reinterpret_cast( base + m_byteOffset[4] ) ); + } + const int attribComponents[] = { 5, 6, 7, 8, 9 }; + const unsigned int attribBindings[] = { 6, 7, 0, 4, 15 }; + const int attribDimensions[] = { 3, 3, 4, 4, 1 }; + for ( int i = 0; i < 5; ++i ) { + const int component = attribComponents[i]; + if ( ( m_flags & BIT( component ) ) == 0 ) { continue; } + const rvVertexFormatStorageDesc &desc = formatDescs[m_dataTypes[component]]; + if ( component == RV_VERTEX_COMPONENT_DIFFUSE_COLOR ) { + qglColorPointer( VertexElementCount( 4, desc ), desc.glStorage, m_size, + reinterpret_cast( base + m_byteOffset[component] ) ); + } else { + qglVertexAttribPointerARB( attribBindings[i], VertexElementCount( attribDimensions[i], desc ), + desc.glStorage, desc.normalized, m_size, reinterpret_cast( base + m_byteOffset[component] ) ); + } + } + if ( ( m_flags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int texture = 0; texture < 7; ++texture ) { + const int dimensions = GetTextureDimension( texture ); + if ( dimensions == 0 ) { continue; } + const int component = 10 + texture; + const rvVertexFormatStorageDesc &desc = formatDescs[m_dataTypes[component]]; + qglVertexAttribPointerARB( 8 + texture, VertexElementCount( dimensions, desc ), desc.glStorage, + desc.normalized, m_size, reinterpret_cast( base + m_byteOffset[component] ) ); + } + } + if ( ( m_flags & rvVertexFormatFlagPosition ) != 0 ) { + const rvVertexFormatStorageDesc &desc = formatDescs[m_dataTypes[0]]; + qglVertexPointer( VertexElementCount( GetPositionDimension(), desc ), desc.glStorage, m_size, + reinterpret_cast( base + m_byteOffset[0] ) ); + } +} + +void rvVertexFormat::EnableVertexDeclaration() const { + GL_VertexAttribState( m_glVASMask ); +} + +bool rvVertexFormat::HasSameComponents( const rvVertexFormat &format ) const { + if ( ( m_flags & format.m_flags ) != format.m_flags ) { return false; } + if ( ( format.m_flags & rvVertexFormatFlagPosition ) != 0 && GetPositionDimension() != format.GetPositionDimension() ) { return false; } + if ( ( format.m_flags & rvVertexFormatFlagBlendWeight ) != 0 && GetBlendWeightDimension() != format.GetBlendWeightDimension() ) { return false; } + if ( ( format.m_flags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int i = 0; i < 7; ++i ) { + if ( format.GetTextureDimension( i ) != 0 && GetTextureDimension( i ) != format.GetTextureDimension( i ) ) { return false; } + } + } + return true; +} + +bool rvVertexFormat::HasSameDataTypes( const rvVertexFormat &format ) const { + for ( int component = 0; component <= 9; ++component ) { + if ( ( m_flags & BIT( component ) ) != 0 && m_dataTypes[component] != format.m_dataTypes[component] ) { return false; } + } + if ( ( m_flags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int i = 0; i < 7; ++i ) { + if ( format.GetTextureDimension( i ) != 0 && m_dataTypes[10 + i] != format.m_dataTypes[10 + i] ) { return false; } + } + } + return true; +} + +rvVertexFormat::rvVertexFormat() { + m_flags = 0; m_dimensions = 0; m_size = 0; memset( m_byteOffset, 0, sizeof( m_byteOffset ) ); m_glVASMask = 0; +} + +void rvVertexFormat::Shutdown() { + m_flags = 0; m_dimensions = 0; m_size = 0; memset( m_byteOffset, 0, sizeof( m_byteOffset ) ); m_glVASMask = 0; +} + +rvVertexFormat::~rvVertexFormat() { + Shutdown(); +} + +static Rv_Vertex_Data_Type_t VertexDataTypeFromToken( const idToken &token, bool &matched ) { + matched = false; + for ( int i = 1; i < RV_NUM_VERTEX_DATA_TYPES; ++i ) { + idStr name = outputDataTypeStrings[i]; + name.StripTrailingWhitespace(); + if ( !token.Icmp( name ) ) { + matched = true; + return static_cast( i ); + } + } + return RV_VERTEX_DATA_TYPE_NONE; +} + +void rvVertexFormat::ParseComponentDataType( Rv_Vertex_Component_t vertexComponent, + Rv_Vertex_Data_Type_t defaultDataType, Lexer &lexer ) { + idToken token; + lexer.ReadToken( &token ); + bool matched; + Rv_Vertex_Data_Type_t type = VertexDataTypeFromToken( token, matched ); + if ( !matched ) { + lexer.UnreadToken( &token ); + type = defaultDataType; + } + m_dataTypes[vertexComponent] = static_cast( type ); +} + +void rvVertexFormat::Write( idFile &outFile, const char *prepend ) { + outFile.WriteFloatString( "{ " ); + if ( ( m_flags & rvVertexFormatFlagSwizzledPosition ) != 0 ) { outFile.WriteFloatString( "Pos3Swizzled " ); } + else if ( ( m_flags & rvVertexFormatFlagPosition ) != 0 ) { outFile.WriteFloatString( "Position %d ", GetPositionDimension() ); } + const char *componentNames[10] = { "", "", "BlendIndex ", "BlendWeight ", "Normal ", "Tangent ", "Binormal ", "DiffuseColor ", "SpecularColor ", "PointSize " }; + const Rv_Vertex_Data_Type_t defaults[10] = { RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_UBYTEN, + RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_FLOAT, RV_VERTEX_DATA_TYPE_FLOAT, + RV_VERTEX_DATA_TYPE_COLOR, RV_VERTEX_DATA_TYPE_COLOR, RV_VERTEX_DATA_TYPE_FLOAT }; + if ( ( m_flags & ( rvVertexFormatFlagPosition | rvVertexFormatFlagSwizzledPosition ) ) != 0 && m_dataTypes[0] != RV_VERTEX_DATA_TYPE_FLOAT ) { + outFile.WriteFloatString( outputDataTypeStrings[m_dataTypes[0]] ); + } + for ( int component = 2; component <= 9; ++component ) { + if ( ( m_flags & BIT( component ) ) == 0 ) { continue; } + if ( component == 3 ) { outFile.WriteFloatString( "BlendWeight %d %d ", GetBlendWeightDimension(), GetNumBlendTransforms() ); } + else { outFile.WriteFloatString( componentNames[component] ); } + if ( m_dataTypes[component] != defaults[component] ) { outFile.WriteFloatString( outputDataTypeStrings[m_dataTypes[component]] ); } + } + if ( ( m_flags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int texture = 0; texture < 7; ++texture ) { + if ( GetTextureDimension( texture ) != 0 ) { outFile.WriteFloatString( "TexCoord %d %d ", GetTextureDimension( texture ), texture ); } + if ( m_dataTypes[10 + texture] != RV_VERTEX_DATA_TYPE_FLOAT ) { outFile.WriteFloatString( outputDataTypeStrings[m_dataTypes[10 + texture]] ); } + } + } + outFile.WriteFloatString( "}\n" ); +} + +void rvVertexFormat::Init( unsigned int vtxFmtFlags, int posDim, int numWeights, int numTransforms, + const int *texDimArray, Rv_Vertex_Data_Type_t *dataTypes ) { + if ( m_flags != 0 ) { Shutdown(); } + m_flags = vtxFmtFlags; + if ( ( vtxFmtFlags & rvVertexFormatFlagSwizzledPosition ) != 0 ) { m_flags |= rvVertexFormatFlagPosition; posDim = 3; } + else if ( posDim > 4 ) { common->Warning( "Vertex format was initialized with an unsupported position dimension" ); } + if ( numWeights > 4 ) { common->Warning( "Vertex format was initialized with an unsupported number of blend weights" ); } + if ( numTransforms < 1 ) { numTransforms = 1; } + else if ( numTransforms > numWeights + 1 ) { common->Warning( "Vertex format was initialized with an unsupported number of blend transforms" ); } + m_dimensions = ( posDim & 7 ) | ( ( numWeights & 7 ) << 3 ) | ( ( numTransforms & 7 ) << 6 ); + if ( ( vtxFmtFlags & rvVertexFormatFlagTextureCoordinate ) != 0 ) { + for ( int i = 0; i < 7; ++i ) { + if ( texDimArray[i] > 4 ) { common->Warning( "Vertex format was initialized with an unsupported texture dimension" ); } + m_dimensions |= ( texDimArray[i] & 7 ) << ( 9 + i * 3 ); + } + } + BuildDataTypes( dataTypes ); +} + +void rvVertexFormat::Init( Lexer &lexer ) { + if ( m_flags != 0 ) { Shutdown(); } + lexer.ExpectTokenString( "{" ); + idToken token; + while ( lexer.ReadToken( &token ) && token.Icmp( "}" ) ) { + int component = -1; + Rv_Vertex_Data_Type_t defaultType = RV_VERTEX_DATA_TYPE_FLOAT; + if ( !token.Icmp( "Position" ) ) { m_flags |= BIT( 0 ); m_dimensions |= lexer.ParseInt(); component = 0; } + else if ( !token.Icmp( "PositionSwizzled" ) || !token.Icmp( "Pos3Swizzled" ) ) { m_flags |= BIT( 1 ); m_dimensions |= 3; component = 1; } + else if ( !token.Icmp( "BlendIndex" ) ) { m_flags |= BIT( 2 ); component = 2; defaultType = RV_VERTEX_DATA_TYPE_UBYTEN; } + else if ( !token.Icmp( "BlendWeight" ) ) { + m_flags |= BIT( 3 ); const int weights = lexer.ParseInt(); int transforms = lexer.ParseInt(); + if ( weights > 4 ) { lexer.Error( "Vertex format was initialized with an unsupported number of blend weights" ); } + if ( transforms < 1 ) { transforms = 1; } else if ( transforms > weights + 1 ) { lexer.Error( "Vertex format was initialized with an unsupported number of blend transforms" ); } + m_dimensions |= ( weights & 7 ) << 3 | ( transforms & 7 ) << 6; component = 3; + } + else if ( !token.Icmp( "Normal" ) ) { m_flags |= BIT( 4 ); component = 4; } + else if ( !token.Icmp( "Tangent" ) ) { m_flags |= BIT( 5 ); component = 5; } + else if ( !token.Icmp( "Binormal" ) ) { m_flags |= BIT( 6 ); component = 6; } + else if ( !token.Icmp( "DiffuseColor" ) ) { m_flags |= BIT( 7 ); component = 7; defaultType = RV_VERTEX_DATA_TYPE_COLOR; } + else if ( !token.Icmp( "SpecularColor" ) ) { m_flags |= BIT( 8 ); component = 8; defaultType = RV_VERTEX_DATA_TYPE_COLOR; } + else if ( !token.Icmp( "PointSize" ) ) { m_flags |= BIT( 9 ); component = 9; } + else if ( !token.Icmp( "TexCoord" ) ) { + m_flags |= BIT( 10 ); const int dimension = lexer.ParseInt(); const int set = lexer.ParseInt(); + if ( dimension > 4 ) { lexer.Error( "Vertex format was initialized with an unsupported texture coordinate dimension" ); } + if ( set >= 7 ) { lexer.Error( "Vertex format was initialized with an unsupported texture coordinate set" ); } + m_dimensions |= dimension << ( 9 + 3 * set ); component = 10 + set; + } else { lexer.Error( "Expected vertex format keyword" ); } + ParseComponentDataType( static_cast( component ), defaultType, lexer ); + } + CalcSize(); + if ( GetNumBlendTransforms() < 1 ) { m_dimensions |= 1 << 6; } +} + +void rvVertexFormat::Init( const rvVertexFormat &format ) { + if ( m_flags != 0 ) { Shutdown(); } + m_flags = format.m_flags; m_dimensions = format.m_dimensions; m_size = format.m_size; m_numValues = format.m_numValues; + memcpy( m_byteOffset, format.m_byteOffset, sizeof( m_byteOffset ) ); + memcpy( m_tokenSubTypes, format.m_tokenSubTypes, sizeof( m_tokenSubTypes ) ); + memcpy( m_dataTypes, format.m_dataTypes, sizeof( m_dataTypes ) ); + m_glVASMask = format.m_glVASMask; +} + +rvVertexFormat::rvVertexFormat( unsigned int vtxFmtFlags, int posDim, int numWeights, + int numTransforms, const int *texDimArray ) { + m_flags = 0; m_dimensions = 0; m_size = 0; memset( m_byteOffset, 0, sizeof( m_byteOffset ) ); m_glVASMask = 0; + Init( vtxFmtFlags, posDim, numWeights, numTransforms, texDimArray, NULL ); +} diff --git a/src/renderer/rvVertexFormat.h b/src/renderer/rvVertexFormat.h new file mode 100644 index 0000000..ccf5e20 --- /dev/null +++ b/src/renderer/rvVertexFormat.h @@ -0,0 +1,142 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2005 Raven Software + +#ifndef __RENDERER_RVVERTEXFORMAT_H__ +#define __RENDERER_RVVERTEXFORMAT_H__ + +class Lexer; + +enum Rv_Vertex_Data_Type_t { + RV_VERTEX_DATA_TYPE_NONE = 0, + RV_VERTEX_DATA_TYPE_FLOAT, + RV_VERTEX_DATA_TYPE_FLOAT16, + RV_VERTEX_DATA_TYPE_INT, + RV_VERTEX_DATA_TYPE_UINT, + RV_VERTEX_DATA_TYPE_INTN, + RV_VERTEX_DATA_TYPE_UINTN, + RV_VERTEX_DATA_TYPE_COLOR, + RV_VERTEX_DATA_TYPE_UBYTE, + RV_VERTEX_DATA_TYPE_BYTE, + RV_VERTEX_DATA_TYPE_UBYTEN, + RV_VERTEX_DATA_TYPE_BYTEN, + RV_VERTEX_DATA_TYPE_SHORT, + RV_VERTEX_DATA_TYPE_USHORT, + RV_VERTEX_DATA_TYPE_SHORTN, + RV_VERTEX_DATA_TYPE_USHORTN, + RV_VERTEX_DATA_TYPE_UDEC_10_10_10, + RV_VERTEX_DATA_TYPE_DEC_10_10_10, + RV_VERTEX_DATA_TYPE_UDEC_10_10_10N, + RV_VERTEX_DATA_TYPE_DEC_10_10_10N, + RV_VERTEX_DATA_TYPE_UDEC_10_11_11, + RV_VERTEX_DATA_TYPE_DEC_10_11_11, + RV_VERTEX_DATA_TYPE_UDEC_10_11_11N, + RV_VERTEX_DATA_TYPE_DEC_10_11_11N, + RV_VERTEX_DATA_TYPE_UDEC_11_11_10, + RV_VERTEX_DATA_TYPE_DEC_11_11_10, + RV_VERTEX_DATA_TYPE_UDEC_11_11_10N, + RV_VERTEX_DATA_TYPE_DEC_11_11_10N, + RV_NUM_VERTEX_DATA_TYPES +}; + +enum Rv_Vertex_Component_t { + RV_VERTEX_COMPONENT_POSITION = 0, + RV_VERTEX_COMPONENT_SWIZZLED_POSITION, + RV_VERTEX_COMPONENT_BLEND_INDEX, + RV_VERTEX_COMPONENT_BLEND_WEIGHT, + RV_VERTEX_COMPONENT_NORMAL, + RV_VERTEX_COMPONENT_TANGENT, + RV_VERTEX_COMPONENT_BINORMAL, + RV_VERTEX_COMPONENT_DIFFUSE_COLOR, + RV_VERTEX_COMPONENT_SPECULAR_COLOR, + RV_VERTEX_COMPONENT_POINT_SIZE, + RV_VERTEX_COMPONENT_TEXTURE_COORDINATE_0, + RV_NUM_VERTEX_COMPONENTS = 18 +}; + +enum rvVertexFormatFlags { + rvVertexFormatFlagPosition = BIT( 0 ), + rvVertexFormatFlagSwizzledPosition = BIT( 1 ), + rvVertexFormatFlagBlendIndex = BIT( 2 ), + rvVertexFormatFlagBlendWeight = BIT( 3 ), + rvVertexFormatFlagNormal = BIT( 4 ), + rvVertexFormatFlagTangent = BIT( 5 ), + rvVertexFormatFlagBinormal = BIT( 6 ), + rvVertexFormatFlagDiffuseColor = BIT( 7 ), + rvVertexFormatFlagSpecularColor = BIT( 8 ), + rvVertexFormatFlagPointSize = BIT( 9 ), + rvVertexFormatFlagTextureCoordinate = BIT( 10 ) +}; + +class rvVertexFormat { + friend class rvVertexBuffer; + friend class rvRenderModelMD5R; +public: + rvVertexFormat(); + rvVertexFormat( unsigned int vtxFmtFlags, int posDim, int numWeights, + int numTransforms, const int *texDimArray ); + ~rvVertexFormat(); + + void Shutdown(); + void Init( unsigned int vtxFmtFlags, int posDim, int numWeights, int numTransforms, + const int *texDimArray, Rv_Vertex_Data_Type_t *dataTypes = NULL ); + void Init( Lexer &lexer ); + void Init( const rvVertexFormat &format ); + void Write( idFile &outFile, const char *prepend ); + void GetTokenSubTypes( Rv_Vertex_Component_t vertexComponent, int *tokenSubTypes ); + void SetVertexDeclaration( int vertexStartOffset ) const; + void EnableVertexDeclaration() const; + bool HasSameComponents( const rvVertexFormat &format ) const; + bool HasSameDataTypes( const rvVertexFormat &format ) const; + + unsigned int GetFlags() const { return m_flags; } + int GetSize() const { return m_size; } + int GetNumValues() const { return m_numValues; } + int GetByteOffset( Rv_Vertex_Component_t component ) const { return m_byteOffset[component]; } + Rv_Vertex_Data_Type_t GetDataType( Rv_Vertex_Component_t component ) const { + return static_cast( m_dataTypes[component] ); + } + int GetPositionDimension() const { return m_dimensions & 7; } + int GetBlendWeightDimension() const { return ( m_dimensions >> 3 ) & 7; } + int GetNumBlendTransforms() const { return ( m_dimensions >> 6 ) & 7; } + int GetTextureDimension( int unit ) const { return ( m_dimensions >> ( 9 + unit * 3 ) ) & 7; } + +protected: + void CalcSize(); + void BuildDataTypes( Rv_Vertex_Data_Type_t *dataTypes ); + void ParseComponentDataType( Rv_Vertex_Component_t vertexComponent, + Rv_Vertex_Data_Type_t defaultDataType, Lexer &lexer ); + + unsigned int m_flags; + unsigned int m_dimensions; + int m_size; + int m_numValues; + int m_byteOffset[18]; + unsigned char m_tokenSubTypes[72]; + unsigned char m_dataTypes[18]; + unsigned int m_glVASMask; +}; + +#endif diff --git a/src/renderer/simplex.h b/src/renderer/simplex.h new file mode 100644 index 0000000..08a177b --- /dev/null +++ b/src/renderer/simplex.h @@ -0,0 +1,596 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +const int NUM_SIMPLEX_CHARS = 95; +const static int simplex[NUM_SIMPLEX_CHARS][112] = { + { 0,16, /* Ascii 32 */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,10, /* Ascii 33 */ + 5,21, 5, 7,-1,-1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,16, /* Ascii 34 */ + 4,21, 4,14,-1,-1,12,21,12,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,21, /* Ascii 35 */ + 11,25, 4,-7,-1,-1,17,25,10,-7,-1,-1, 4,12,18,12,-1,-1, 3, 6,17, 6,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 26,20, /* Ascii 36 */ + 8,25, 8,-4,-1,-1,12,25,12,-4,-1,-1,17,18,15,20,12,21, 8,21, 5,20, 3, + 18, 3,16, 4,14, 5,13, 7,12,13,10,15, 9,16, 8,17, 6,17, 3,15, 1,12, 0, + 8, 0, 5, 1, 3, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 31,24, /* Ascii 37 */ + 21,21, 3, 0,-1,-1, 8,21,10,19,10,17, 9,15, 7,14, 5,14, 3,16, 3,18, 4, + 20, 6,21, 8,21,10,20,13,19,16,19,19,20,21,21,-1,-1,17, 7,15, 6,14, 4, + 14, 2,16, 0,18, 0,20, 1,21, 3,21, 5,19, 7,17, 7,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 34,26, /* Ascii 38 */ + 23,12,23,13,22,14,21,14,20,13,19,11,17, 6,15, 3,13, 1,11, 0, 7, 0, 5, + 1, 4, 2, 3, 4, 3, 6, 4, 8, 5, 9,12,13,13,14,14,16,14,18,13,20,11,21, + 9,20, 8,18, 8,16, 9,13,11,10,16, 3,18, 1,20, 0,22, 0,23, 1,23, 2,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 7,10, /* Ascii 39 */ + 5,19, 4,20, 5,21, 6,20, 6,18, 5,16, 4,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,14, /* Ascii 40 */ + 11,25, 9,23, 7,20, 5,16, 4,11, 4, 7, 5, 2, 7,-2, 9,-5,11,-7,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,14, /* Ascii 41 */ + 3,25, 5,23, 7,20, 9,16,10,11,10, 7, 9, 2, 7,-2, 5,-5, 3,-7,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,16, /* Ascii 42 */ + 8,21, 8, 9,-1,-1, 3,18,13,12,-1,-1,13,18, 3,12,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,26, /* Ascii 43 */ + 13,18,13, 0,-1,-1, 4, 9,22, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,10, /* Ascii 44 */ + 6, 1, 5, 0, 4, 1, 5, 2, 6, 1, 6,-1, 5,-3, 4,-4,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,26, /* Ascii 45 */ + 4, 9,22, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,10, /* Ascii 46 */ + 5, 2, 4, 1, 5, 0, 6, 1, 5, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,22, /* Ascii 47 */ + 20,25, 2,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,20, /* Ascii 48 */ + 9,21, 6,20, 4,17, 3,12, 3, 9, 4, 4, 6, 1, 9, 0,11, 0,14, 1,16, 4,17, + 9,17,12,16,17,14,20,11,21, 9,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 4,20, /* Ascii 49 */ + 6,17, 8,18,11,21,11, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 14,20, /* Ascii 50 */ + 4,16, 4,17, 5,19, 6,20, 8,21,12,21,14,20,15,19,16,17,16,15,15,13,13, + 10, 3, 0,17, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 15,20, /* Ascii 51 */ + 5,21,16,21,10,13,13,13,15,12,16,11,17, 8,17, 6,16, 3,14, 1,11, 0, 8, + 0, 5, 1, 4, 2, 3, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 6,20, /* Ascii 52 */ + 13,21, 3, 7,18, 7,-1,-1,13,21,13, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,20, /* Ascii 53 */ + 15,21, 5,21, 4,12, 5,13, 8,14,11,14,14,13,16,11,17, 8,17, 6,16, 3,14, + 1,11, 0, 8, 0, 5, 1, 4, 2, 3, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,20, /* Ascii 54 */ + 16,18,15,20,12,21,10,21, 7,20, 5,17, 4,12, 4, 7, 5, 3, 7, 1,10, 0,11, + 0,14, 1,16, 3,17, 6,17, 7,16,10,14,12,11,13,10,13, 7,12, 5,10, 4, 7, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,20, /* Ascii 55 */ + 17,21, 7, 0,-1,-1, 3,21,17,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 29,20, /* Ascii 56 */ + 8,21, 5,20, 4,18, 4,16, 5,14, 7,13,11,12,14,11,16, 9,17, 7,17, 4,16, + 2,15, 1,12, 0, 8, 0, 5, 1, 4, 2, 3, 4, 3, 7, 4, 9, 6,11, 9,12,13,13, + 15,14,16,16,16,18,15,20,12,21, 8,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,20, /* Ascii 57 */ + 16,14,15,11,13, 9,10, 8, 9, 8, 6, 9, 4,11, 3,14, 3,15, 4,18, 6,20, 9, + 21,10,21,13,20,15,18,16,14,16, 9,15, 4,13, 1,10, 0, 8, 0, 5, 1, 4, 3, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,10, /* Ascii 58 */ + 5,14, 4,13, 5,12, 6,13, 5,14,-1,-1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 14,10, /* Ascii 59 */ + 5,14, 4,13, 5,12, 6,13, 5,14,-1,-1, 6, 1, 5, 0, 4, 1, 5, 2, 6, 1, 6, + -1, 5,-3, 4,-4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 3,24, /* Ascii 60 */ + 20,18, 4, 9,20, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,26, /* Ascii 61 */ + 4,12,22,12,-1,-1, 4, 6,22, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 3,24, /* Ascii 62 */ + 4,18,20, 9, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 20,18, /* Ascii 63 */ + 3,16, 3,17, 4,19, 5,20, 7,21,11,21,13,20,14,19,15,17,15,15,14,13,13, + 12, 9,10, 9, 7,-1,-1, 9, 2, 8, 1, 9, 0,10, 1, 9, 2,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 55,27, /* Ascii 64 */ + 18,13,17,15,15,16,12,16,10,15, 9,14, 8,11, 8, 8, 9, 6,11, 5,14, 5,16, + 6,17, 8,-1,-1,12,16,10,14, 9,11, 9, 8,10, 6,11, 5,-1,-1,18,16,17, 8, + 17, 6,19, 5,21, 5,23, 7,24,10,24,12,23,15,22,17,20,19,18,20,15,21,12, + 21, 9,20, 7,19, 5,17, 4,15, 3,12, 3, 9, 4, 6, 5, 4, 7, 2, 9, 1,12, 0, + 15, 0,18, 1,20, 2,21, 3,-1,-1,19,16,18, 8,18, 6,19, 5 }, + { 8,18, /* Ascii 65 */ + 9,21, 1, 0,-1,-1, 9,21,17, 0,-1,-1, 4, 7,14, 7,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,21, /* Ascii 66 */ + 4,21, 4, 0,-1,-1, 4,21,13,21,16,20,17,19,18,17,18,15,17,13,16,12,13, + 11,-1,-1, 4,11,13,11,16,10,17, 9,18, 7,18, 4,17, 2,16, 1,13, 0, 4, 0, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 18,21, /* Ascii 67 */ + 18,16,17,18,15,20,13,21, 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, + 3, 7, 1, 9, 0,13, 0,15, 1,17, 3,18, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 15,21, /* Ascii 68 */ + 4,21, 4, 0,-1,-1, 4,21,11,21,14,20,16,18,17,16,18,13,18, 8,17, 5,16, + 3,14, 1,11, 0, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,19, /* Ascii 69 */ + 4,21, 4, 0,-1,-1, 4,21,17,21,-1,-1, 4,11,12,11,-1,-1, 4, 0,17, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,18, /* Ascii 70 */ + 4,21, 4, 0,-1,-1, 4,21,17,21,-1,-1, 4,11,12,11,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 22,21, /* Ascii 71 */ + 18,16,17,18,15,20,13,21, 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, + 3, 7, 1, 9, 0,13, 0,15, 1,17, 3,18, 5,18, 8,-1,-1,13, 8,18, 8,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,22, /* Ascii 72 */ + 4,21, 4, 0,-1,-1,18,21,18, 0,-1,-1, 4,11,18,11,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2, 8, /* Ascii 73 */ + 4,21, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,16, /* Ascii 74 */ + 12,21,12, 5,11, 2,10, 1, 8, 0, 6, 0, 4, 1, 3, 2, 2, 5, 2, 7,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,21, /* Ascii 75 */ + 4,21, 4, 0,-1,-1,18,21, 4, 7,-1,-1, 9,12,18, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,17, /* Ascii 76 */ + 4,21, 4, 0,-1,-1, 4, 0,16, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,24, /* Ascii 77 */ + 4,21, 4, 0,-1,-1, 4,21,12, 0,-1,-1,20,21,12, 0,-1,-1,20,21,20, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,22, /* Ascii 78 */ + 4,21, 4, 0,-1,-1, 4,21,18, 0,-1,-1,18,21,18, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 21,22, /* Ascii 79 */ + 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0,13, 0,15, + 1,17, 3,18, 5,19, 8,19,13,18,16,17,18,15,20,13,21, 9,21,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 13,21, /* Ascii 80 */ + 4,21, 4, 0,-1,-1, 4,21,13,21,16,20,17,19,18,17,18,14,17,12,16,11,13, + 10, 4,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 24,22, /* Ascii 81 */ + 9,21, 7,20, 5,18, 4,16, 3,13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0,13, 0,15, + 1,17, 3,18, 5,19, 8,19,13,18,16,17,18,15,20,13,21, 9,21,-1,-1,12, 4, + 18,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 16,21, /* Ascii 82 */ + 4,21, 4, 0,-1,-1, 4,21,13,21,16,20,17,19,18,17,18,15,17,13,16,12,13, + 11, 4,11,-1,-1,11,11,18, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 20,20, /* Ascii 83 */ + 17,18,15,20,12,21, 8,21, 5,20, 3,18, 3,16, 4,14, 5,13, 7,12,13,10,15, + 9,16, 8,17, 6,17, 3,15, 1,12, 0, 8, 0, 5, 1, 3, 3,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,16, /* Ascii 84 */ + 8,21, 8, 0,-1,-1, 1,21,15,21,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,22, /* Ascii 85 */ + 4,21, 4, 6, 5, 3, 7, 1,10, 0,12, 0,15, 1,17, 3,18, 6,18,21,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,18, /* Ascii 86 */ + 1,21, 9, 0,-1,-1,17,21, 9, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,24, /* Ascii 87 */ + 2,21, 7, 0,-1,-1,12,21, 7, 0,-1,-1,12,21,17, 0,-1,-1,22,21,17, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,20, /* Ascii 88 */ + 3,21,17, 0,-1,-1,17,21, 3, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 6,18, /* Ascii 89 */ + 1,21, 9,11, 9, 0,-1,-1,17,21, 9,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,20, /* Ascii 90 */ + 17,21, 3, 0,-1,-1, 3,21,17,21,-1,-1, 3, 0,17, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,14, /* Ascii 91 */ + 4,25, 4,-7,-1,-1, 5,25, 5,-7,-1,-1, 4,25,11,25,-1,-1, 4,-7,11,-7,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,14, /* Ascii 92 */ + 0,21,14,-3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,14, /* Ascii 93 */ + 9,25, 9,-7,-1,-1,10,25,10,-7,-1,-1, 3,25,10,25,-1,-1, 3,-7,10,-7,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,16, /* Ascii 94 */ + 6,15, 8,18,10,15,-1,-1, 3,12, 8,17,13,12,-1,-1, 8,17, 8, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2,16, /* Ascii 95 */ + 0,-2,16,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 7,10, /* Ascii 96 */ + 6,21, 5,20, 4,18, 4,16, 5,15, 6,16, 5,17,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 97 */ + 15,14,15, 0,-1,-1,15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 98 */ + 4,21, 4, 0,-1,-1, 4,11, 6,13, 8,14,11,14,13,13,15,11,16, 8,16, 6,15, + 3,13, 1,11, 0, 8, 0, 6, 1, 4, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 14,18, /* Ascii 99 */ + 15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0,11, + 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 100 */ + 15,21,15, 0,-1,-1,15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,18, /* Ascii 101 */ + 3, 8,15, 8,15,10,14,12,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,12, /* Ascii 102 */ + 10,21, 8,21, 6,20, 5,17, 5, 0,-1,-1, 2,14, 9,14,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 22,19, /* Ascii 103 */ + 15,14,15,-2,14,-5,13,-6,11,-7, 8,-7, 6,-6,-1,-1,15,11,13,13,11,14, 8, + 14, 6,13, 4,11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,19, /* Ascii 104 */ + 4,21, 4, 0,-1,-1, 4,10, 7,13, 9,14,12,14,14,13,15,10,15, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8, 8, /* Ascii 105 */ + 3,21, 4,20, 5,21, 4,22, 3,21,-1,-1, 4,14, 4, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,10, /* Ascii 106 */ + 5,21, 6,20, 7,21, 6,22, 5,21,-1,-1, 6,14, 6,-3, 5,-6, 3,-7, 1,-7,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,17, /* Ascii 107 */ + 4,21, 4, 0,-1,-1,14,14, 4, 4,-1,-1, 8, 8,15, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2, 8, /* Ascii 108 */ + 4,21, 4, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 18,30, /* Ascii 109 */ + 4,14, 4, 0,-1,-1, 4,10, 7,13, 9,14,12,14,14,13,15,10,15, 0,-1,-1,15, + 10,18,13,20,14,23,14,25,13,26,10,26, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,19, /* Ascii 110 */ + 4,14, 4, 0,-1,-1, 4,10, 7,13, 9,14,12,14,14,13,15,10,15, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 111 */ + 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,16, + 6,16, 8,15,11,13,13,11,14, 8,14,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 112 */ + 4,14, 4,-7,-1,-1, 4,11, 6,13, 8,14,11,14,13,13,15,11,16, 8,16, 6,15, + 3,13, 1,11, 0, 8, 0, 6, 1, 4, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,19, /* Ascii 113 */ + 15,14,15,-7,-1,-1,15,11,13,13,11,14, 8,14, 6,13, 4,11, 3, 8, 3, 6, 4, + 3, 6, 1, 8, 0,11, 0,13, 1,15, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,13, /* Ascii 114 */ + 4,14, 4, 0,-1,-1, 4, 8, 5,11, 7,13, 9,14,12,14,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 17,17, /* Ascii 115 */ + 14,11,13,13,10,14, 7,14, 4,13, 3,11, 4, 9, 6, 8,11, 7,13, 6,14, 4,14, + 3,13, 1,10, 0, 7, 0, 4, 1, 3, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,12, /* Ascii 116 */ + 5,21, 5, 4, 6, 1, 8, 0,10, 0,-1,-1, 2,14, 9,14,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 10,19, /* Ascii 117 */ + 4,14, 4, 4, 5, 1, 7, 0,10, 0,12, 1,15, 4,-1,-1,15,14,15, 0,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,16, /* Ascii 118 */ + 2,14, 8, 0,-1,-1,14,14, 8, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 11,22, /* Ascii 119 */ + 3,14, 7, 0,-1,-1,11,14, 7, 0,-1,-1,11,14,15, 0,-1,-1,19,14,15, 0,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 5,17, /* Ascii 120 */ + 3,14,14, 0,-1,-1,14,14, 3, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 9,16, /* Ascii 121 */ + 2,14, 8, 0,-1,-1,14,14, 8, 0, 6,-4, 4,-6, 2,-7, 1,-7,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 8,17, /* Ascii 122 */ + 14,14, 3, 0,-1,-1, 3,14,14,14,-1,-1, 3, 0,14, 0,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 39,14, /* Ascii 123 */ + 9,25, 7,24, 6,23, 5,21, 5,19, 6,17, 7,16, 8,14, 8,12, 6,10,-1,-1, 7, + 24, 6,22, 6,20, 7,18, 8,17, 9,15, 9,13, 8,11, 4, 9, 8, 7, 9, 5, 9, 3, + 8, 1, 7, 0, 6,-2, 6,-4, 7,-6,-1,-1, 6, 8, 8, 6, 8, 4, 7, 2, 6, 1, 5, + -1, 5,-3, 6,-5, 7,-6, 9,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 2, 8, /* Ascii 124 */ + 4,25, 4,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 39,14, /* Ascii 125 */ + 5,25, 7,24, 8,23, 9,21, 9,19, 8,17, 7,16, 6,14, 6,12, 8,10,-1,-1, 7, + 24, 8,22, 8,20, 7,18, 6,17, 5,15, 5,13, 6,11,10, 9, 6, 7, 5, 5, 5, 3, + 6, 1, 7, 0, 8,-2, 8,-4, 7,-6,-1,-1, 8, 8, 6, 6, 6, 4, 7, 2, 8, 1, 9, + -1, 9,-3, 8,-5, 7,-6, 5,-7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }, + { 23,24, /* Ascii 126 */ + 3, 6, 3, 8, 4,11, 6,12, 8,12,10,11,14, 8,16, 7,18, 7,20, 8,21,10,-1, + -1, 3, 8, 4,10, 6,11, 8,11,10,10,14, 7,16, 6,18, 6,20, 7,21,10,21,12, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 } +}; diff --git a/src/renderer/tr_backend.cpp b/src/renderer/tr_backend.cpp new file mode 100644 index 0000000..35d316a --- /dev/null +++ b/src/renderer/tr_backend.cpp @@ -0,0 +1,694 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + + +frameData_t *frameData; +backEndState_t backEnd; + +void GL_PolygonOffsetState( bool enable, float factor, float units ) { + if ( enable ) { + if ( !backEnd.glState.polyOffset || + factor != backEnd.glState.polyOffsetFactor || + units != backEnd.glState.polyOffsetUnits ) { + qglPolygonOffset( factor, units ); + qglEnable( GL_POLYGON_OFFSET_FILL ); + backEnd.glState.polyOffset = true; + backEnd.glState.polyOffsetFactor = factor; + backEnd.glState.polyOffsetUnits = units; + } + } else if ( backEnd.glState.polyOffset ) { + qglDisable( GL_POLYGON_OFFSET_FILL ); + backEnd.glState.polyOffset = false; + } +} + +void GL_PolygonOffset( const idMaterial *material, bool enable ) { + if ( material->TestMaterialFlag( MF_POLYGONOFFSET ) ) { + GL_PolygonOffsetState( enable, r_offsetFactor.GetFloat(), + r_offsetUnits.GetFloat() * material->GetPolygonOffset() ); + } +} + +void GL_VertexAttribState( unsigned int vertexAttribStateBits ) { + unsigned int changed = r_useStateCaching.GetBool() ? + ( vertexAttribStateBits ^ backEnd.glState.vertexAttribStateBits ) : ~0u; + if ( changed == 0 ) { + return; + } + backEnd.glState.vertexAttribStateBits = vertexAttribStateBits; + + const unsigned int legacyBits[3] = { BIT( 0 ), BIT( 2 ), BIT( 1 ) }; + const unsigned int legacyStates[3] = { GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_NORMAL_ARRAY }; + for ( int i = 0; i < 3; ++i ) { + if ( ( changed & legacyBits[i] ) != 0 ) { + if ( ( vertexAttribStateBits & legacyBits[i] ) != 0 ) { qglEnableClientState( legacyStates[i] ); } + else { qglDisableClientState( legacyStates[i] ); } + changed &= ~legacyBits[i]; + } + } + + if ( ( changed & 0x7F8u ) != 0 ) { + const int activeTmu = backEnd.glState.currenttmu; + for ( int unit = 0; unit < 8; ++unit ) { + const unsigned int legacyTexCoord = BIT( 3 + unit ); + if ( ( changed & legacyTexCoord ) == 0 ) { continue; } + if ( unit != backEnd.glState.currenttmu ) { + qglClientActiveTextureARB( GL_TEXTURE0_ARB + unit ); + backEnd.glState.currenttmu = unit; + } + if ( ( vertexAttribStateBits & legacyTexCoord ) != 0 ) { + const unsigned int genericTexCoord = BIT( 24 + unit ); + if ( ( changed & genericTexCoord ) != 0 ) { + qglDisableVertexAttribArrayARB( 8 + unit ); + changed &= ~genericTexCoord; + } + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + } else { + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + } + changed &= ~legacyTexCoord; + } + if ( activeTmu != backEnd.glState.currenttmu ) { + qglClientActiveTextureARB( GL_TEXTURE0_ARB + activeTmu ); + backEnd.glState.currenttmu = activeTmu; + } + } + + const int genericAttribs[] = { 1, 5, 6, 7, 8, 9, 10, 11 }; + const unsigned int genericMasks[] = { + BIT( 17 ), BIT( 21 ), BIT( 22 ), BIT( 23 ), + BIT( 24 ), BIT( 25 ), BIT( 26 ), BIT( 27 ) + }; + for ( int i = 0; i < 8; ++i ) { + if ( ( changed & genericMasks[i] ) == 0 ) { continue; } + if ( ( vertexAttribStateBits & genericMasks[i] ) != 0 ) { qglEnableVertexAttribArrayARB( genericAttribs[i] ); } + else { qglDisableVertexAttribArrayARB( genericAttribs[i] ); } + } +} + +void GL_EnableVertexAttribState( unsigned int vertexAttribStateBits ) { + GL_VertexAttribState( backEnd.glState.vertexAttribStateBits | vertexAttribStateBits ); +} + +void GL_DisableVertexAttribState( unsigned int vertexAttribStateBits ) { + GL_VertexAttribState( backEnd.glState.vertexAttribStateBits & ~vertexAttribStateBits ); +} + +void GL_DisableTexCoordState() { + GL_VertexAttribState( backEnd.glState.vertexAttribStateBits & ~BIT( 3 + backEnd.glState.currenttmu ) ); +} + + +/* +====================== +RB_SetDefaultGLState + +This should initialize all GL state that any part of the entire program +may touch, including the editor. +====================== +*/ +void RB_SetDefaultGLState( void ) { + int i; + + qglClearDepth( 1.0f ); + qglColor4f (1,1,1,1); + + // Disable every tracked array before resetting the cached state. + GL_VertexAttribState( 0 ); + + // + // make sure our GL state vector is set correctly + // + memset( &backEnd.glState, 0, sizeof( backEnd.glState ) ); + backEnd.glState.forceGlState = true; + GL_SelectTexture( 0 ); + GL_VertexAttribState( BIT( 0 ) | BIT( 3 ) ); + + qglColorMask( 1, 1, 1, 1 ); + + qglEnable( GL_DEPTH_TEST ); + qglEnable( GL_BLEND ); + qglEnable( GL_SCISSOR_TEST ); + qglEnable( GL_CULL_FACE ); + qglDisable( GL_LIGHTING ); + qglDisable( GL_LINE_STIPPLE ); + qglDisable( GL_STENCIL_TEST ); + + qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + qglDepthMask( GL_TRUE ); + qglDepthFunc( GL_ALWAYS ); + + qglCullFace( GL_FRONT_AND_BACK ); + qglShadeModel( GL_SMOOTH ); + + if ( r_useScissor.GetBool() ) { + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + } + + for ( i = glConfig.maxTextureUnits - 1 ; i >= 0 ; i-- ) { + GL_SelectTexture( i ); + + // object linear texgen is our default + qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglTexGenf( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + + GL_TexEnv( GL_MODULATE ); + qglDisable( GL_TEXTURE_2D ); + if ( glConfig.texture3DAvailable ) { + qglDisable( GL_TEXTURE_3D ); + } + if ( glConfig.cubeMapAvailable ) { + qglDisable( GL_TEXTURE_CUBE_MAP_EXT ); + } + } +} + + +/* +==================== +RB_LogComment +==================== +*/ +//============================================================================= + + + +/* +==================== +GL_SelectTexture +==================== +*/ +void GL_SelectTexture( int unit ) { + if ( backEnd.glState.currenttmu == unit ) { + return; + } + + if ( unit < 0 || unit >= glConfig.maxTextureUnits && unit >= glConfig.maxTextureImageUnits ) { + common->Warning( "GL_SelectTexture: unit = %i", unit ); + return; + } + + qglActiveTextureARB( GL_TEXTURE0_ARB + unit ); + qglClientActiveTextureARB( GL_TEXTURE0_ARB + unit ); + + backEnd.glState.currenttmu = unit; +} + + +/* +==================== +GL_Cull + +This handles the flipping needed when the view being +rendered is a mirored view. +==================== +*/ +void GL_Cull( int cullType ) { + if ( backEnd.glState.faceCulling == cullType ) { + return; + } + + if ( cullType == CT_TWO_SIDED ) { + qglDisable( GL_CULL_FACE ); + } else { + if ( backEnd.glState.faceCulling == CT_TWO_SIDED ) { + qglEnable( GL_CULL_FACE ); + } + + if ( cullType == CT_BACK_SIDED ) { + if ( backEnd.viewDef->isMirror ) { + qglCullFace( GL_FRONT ); + } else { + qglCullFace( GL_BACK ); + } + } else { + if ( backEnd.viewDef->isMirror ) { + qglCullFace( GL_BACK ); + } else { + qglCullFace( GL_FRONT ); + } + } + } + + backEnd.glState.faceCulling = cullType; +} + +/* +==================== +GL_TexEnv +==================== +*/ +void GL_TexEnv( int env ) { + tmu_t *tmu; + + tmu = &backEnd.glState.tmu[backEnd.glState.currenttmu]; + if ( env == tmu->texEnv ) { + return; + } + + tmu->texEnv = env; + + switch ( env ) { + case GL_COMBINE_EXT: + case GL_MODULATE: + case GL_REPLACE: + case GL_DECAL: + case GL_ADD: + qglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env ); + break; + default: + common->Error( "GL_TexEnv: invalid env '%d' passed\n", env ); + break; + } +} + +/* +==================== +GL_State + +This routine is responsible for setting the most commonly changed state +==================== +*/ +void GL_State( int stateBits ) { + int diff; + + if ( !r_useStateCaching.GetBool() || backEnd.glState.forceGlState ) { + // make sure everything is set all the time, so we + // can see if our delta checking is screwing up + diff = -1; + backEnd.glState.forceGlState = false; + } else { + diff = stateBits ^ backEnd.glState.glStateBits; + if ( !diff ) { + return; + } + } + + // + // check depthFunc bits + // + if ( diff & ( GLS_DEPTHFUNC_EQUAL | GLS_DEPTHFUNC_LESS | GLS_DEPTHFUNC_ALWAYS ) ) { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) { + qglDepthFunc( GL_EQUAL ); + } else if ( stateBits & GLS_DEPTHFUNC_ALWAYS ) { + qglDepthFunc( GL_ALWAYS ); + } else { + qglDepthFunc( GL_LEQUAL ); + } + } + + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) { + GLenum srcFactor, dstFactor; + + switch ( stateBits & GLS_SRCBLEND_BITS ) { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + case GLS_SRCBLEND_SRC_COLOR: + srcFactor = GL_SRC_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_COLOR: + srcFactor = GL_ONE_MINUS_SRC_COLOR; + break; + default: + srcFactor = GL_ONE; // to get warning to shut up + common->Error( "GL_State: invalid src blend state bits\n" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + dstFactor = GL_ONE; // to get warning to shut up + common->Error( "GL_State: invalid dst blend state bits\n" ); + break; + } + + qglBlendFunc( srcFactor, dstFactor ); + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK ) { + if ( stateBits & GLS_DEPTHMASK ) { + qglDepthMask( GL_FALSE ); + } else { + qglDepthMask( GL_TRUE ); + } + } + + // + // check colormask + // + if ( diff & (GLS_REDMASK|GLS_GREENMASK|GLS_BLUEMASK|GLS_ALPHAMASK) ) { + GLboolean r, g, b, a; + r = ( stateBits & GLS_REDMASK ) ? 0 : 1; + g = ( stateBits & GLS_GREENMASK ) ? 0 : 1; + b = ( stateBits & GLS_BLUEMASK ) ? 0 : 1; + a = ( stateBits & GLS_ALPHAMASK ) ? 0 : 1; + qglColorMask( r, g, b, a ); + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) { + if ( stateBits & GLS_POLYMODE_LINE ) { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } else { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // alpha test + // + if ( diff & GLS_ATEST_BITS ) { + switch ( stateBits & GLS_ATEST_BITS ) { + case 0: + qglDisable( GL_ALPHA_TEST ); + break; + case GLS_ATEST_EQ_255: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_EQUAL, 1 ); + break; + case GLS_ATEST_LT_128: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_LESS, 0.5 ); + break; + case GLS_ATEST_GE_128: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.5 ); + break; + default: + assert( 0 ); + break; + } + } + + backEnd.glState.glStateBits = stateBits; +} + + + + +/* +============================================================================ + +RENDER BACK END THREAD FUNCTIONS + +============================================================================ +*/ + +/* +============= +RB_SetGL2D + +This is not used by the normal game paths, just by some tools +============= +*/ +void RB_SetGL2D( void ) { + // set 2D virtual screen size + qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + if ( r_useScissor.GetBool() ) { + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + } + qglMatrixMode( GL_PROJECTION ); + qglLoadIdentity(); + qglOrtho( 0, 640, 480, 0, 0, 1 ); // always assume 640x480 virtual coordinates + qglMatrixMode( GL_MODELVIEW ); + qglLoadIdentity(); + + GL_State( GLS_DEPTHFUNC_ALWAYS | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + GL_Cull( CT_TWO_SIDED ); + + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_STENCIL_TEST ); +} + + + +/* +============= +RB_SetBuffer + +============= +*/ +static void RB_SetBuffer( const void *data ) { + const setBufferCommand_t *cmd; + + // see which draw buffer we want to render the frame to + + cmd = (const setBufferCommand_t *)data; + + backEnd.frameCount = cmd->frameCount; + + qglDrawBuffer( cmd->buffer ); + + // clear screen for debugging + // automatically enable this with several other debug tools + // that might leave unrendered portions of the screen + if ( r_clear.GetFloat() || idStr::Length( r_clear.GetString() ) != 1 || r_lockSurfaces.GetBool() || r_singleArea.GetBool() || r_showOverDraw.GetBool() ) { + float c[3]; + if ( sscanf( r_clear.GetString(), "%f %f %f", &c[0], &c[1], &c[2] ) == 3 ) { + qglClearColor( c[0], c[1], c[2], 1 ); + } else if ( r_clear.GetInteger() == 2 ) { + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + } else if ( r_showOverDraw.GetBool() ) { + qglClearColor( 1.0f, 1.0f, 1.0f, 1.0f ); + } else { + qglClearColor( 0.4f, 0.0f, 0.25f, 1.0f ); + } + qglClear( GL_COLOR_BUFFER_BIT ); + } +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. +=============== +*/ +void RB_ShowImages( void ) { + int i; + idImage *image; + float x, y, w, h; + int start, end; + + RB_SetGL2D(); + + //qglClearColor( 0.2, 0.2, 0.2, 1 ); + //qglClear( GL_COLOR_BUFFER_BIT ); + + qglFinish(); + + start = Sys_Milliseconds(); + + for ( i = 0 ; i < globalImages->images.Num() ; i++ ) { + image = globalImages->images[i]; + + if ( image->texnum == idImage::TEXTURE_NOT_LOADED && image->partialImage == NULL ) { + continue; + } + + w = glConfig.vidWidth / 20; + h = glConfig.vidHeight / 15; + x = i % 20 * w; + y = i / 20 * h; + + // show in proportional size in mode 2 + if ( r_showImages.GetInteger() == 2 ) { + w *= image->uploadWidth / 512.0f; + h *= image->uploadHeight / 512.0f; + } + + image->Bind(); + qglBegin (GL_QUADS); + qglTexCoord2f( 0, 0 ); + qglVertex2f( x, y ); + qglTexCoord2f( 1, 0 ); + qglVertex2f( x + w, y ); + qglTexCoord2f( 1, 1 ); + qglVertex2f( x + w, y + h ); + qglTexCoord2f( 0, 1 ); + qglVertex2f( x, y + h ); + qglEnd(); + } + + qglFinish(); + + end = Sys_Milliseconds(); + common->Printf( "%i msec to draw all images\n", end - start ); +} + + +/* +==================== +RB_ExecuteBackEndCommands + +This function will be called syncronously if running without +smp extensions, or asyncronously by another thread. +==================== +*/ +int backEndStartTime, backEndFinishTime; +void RB_ExecuteBackEndCommands( const emptyCommand_t *cmds ) { + // r_debugRenderToTexture + int c_draw3d = 0, c_draw2d = 0, c_setBuffers = 0, c_swapBuffers = 0, c_copyRenders = 0; + + if ( cmds->commandId == RC_NOP && !cmds->next ) { + return; + } + + backEndStartTime = Sys_Milliseconds(); + + // needed for editor rendering + RB_SetDefaultGLState(); + + // upload any image loads that have completed + globalImages->CompleteBackgroundImageLoads(); + + for ( ; cmds ; cmds = (const emptyCommand_t *)cmds->next ) { + switch ( cmds->commandId ) { + case RC_NOP: + break; + case RC_DRAW_DEPTH_TEXTURE: + RB_DrawDepthTexture( cmds ); + break; + case RC_DRAW_VIEW: + RB_DrawView( cmds ); + if ( ((const drawSurfsCommand_t *)cmds)->viewDef->viewEntitys ) { + c_draw3d++; + } + else { + c_draw2d++; + } + break; + case RC_SET_BUFFER: + RB_SetBuffer( cmds ); + c_setBuffers++; + break; + case RC_SWAP_BUFFERS: + if ( r_showImages.GetInteger() != 0 ) { + RB_ShowImages(); + } + if ( r_finish.GetBool() ) { + qglFinish(); + } + if ( !r_frontBuffer.GetBool() ) { + GLimp_SwapBuffers(); + } + c_swapBuffers++; + break; + case RC_COPY_RENDER: + if ( !r_skipCopyTexture.GetBool() ) { + const copyRenderCommand_t *copy = (const copyRenderCommand_t *)cmds; + if ( copy->image ) { + copy->image->CopyFramebuffer( copy->x, copy->y, + copy->imageWidth, copy->imageHeight, false ); + } + } + c_copyRenders++; + break; + default: + common->Error( "RB_ExecuteBackEndCommands: bad commandId" ); + break; + } + } + + // go back to the default texture so the editor doesn't mess up a bound image + qglBindTexture( GL_TEXTURE_2D, 0 ); + backEnd.glState.tmu[0].current2DMap = -1; + + // stop rendering on this thread + backEndFinishTime = Sys_Milliseconds(); + backEnd.pc.msec = backEndFinishTime - backEndStartTime; + + if ( r_debugRenderToTexture.GetInteger() == 1 ) { + common->Printf( "3d: %i, 2d: %i, SetBuf: %i, SwpBuf: %i, CpyRenders: %i, CpyFrameBuf: %i\n", c_draw3d, c_draw2d, c_setBuffers, c_swapBuffers, c_copyRenders, backEnd.c_copyFrameBuffer ); + backEnd.c_copyFrameBuffer = 0; + } +} diff --git a/src/renderer/tr_deform.cpp b/src/renderer/tr_deform.cpp new file mode 100644 index 0000000..9704765 --- /dev/null +++ b/src/renderer/tr_deform.cpp @@ -0,0 +1,1332 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" + + +/* +================= +R_FinishDeform + +The ambientCache is on the stack, so we don't want to leave a reference +to it that would try to be freed later. Create the ambientCache immediately. +================= +*/ +#define R_FinishDeform( drawSurf, newTri, ac ) do { \ + if ( (drawSurf)->material->ReceivesLighting() ) { \ + (newTri)->verts = (ac); \ + R_DeriveTangents( (newTri), false ); \ + (newTri)->verts = NULL; \ + } \ + (newTri)->ambientCache = vertexCache.AllocFrameTemp( (ac), (newTri)->numVerts * sizeof( idDrawVert ), 2 ); \ + if ( (newTri)->ambientCache ) { \ + (drawSurf)->geo = (newTri); \ + } \ + if ( r_useIndexBuffers.GetBool() ) { \ + (newTri)->indexCache = vertexCache.AllocFrameTemp( (newTri)->indexes, \ + (newTri)->numIndexes * sizeof( (newTri)->indexes[0] ), 1 ); \ + } \ +} while ( 0 ) + +/* +===================== +R_SpriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void R_SpriteDeform( drawSurf_t *surf, bool radiusDimension ) { + int i; + idVec3 mid; + float radius, radius2; + idVec3 left, up; + idVec3 leftDir, upDir; + const srfTriangles_t *tri; + srfTriangles_t *newTri; + + tri = surf->geo; + + if ( tri->numVerts & 3 ) { + common->Warning( "R_AutospriteDeform: shader had odd vertex count" ); + return; + } + if ( tri->numIndexes != ( tri->numVerts >> 2 ) * 6 ) { + common->Warning( "R_AutospriteDeform: autosprite had odd index count" ); + return; + } + + R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[1], leftDir ); + R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[2], upDir ); + + if ( tr.viewDef->isMirror ) { + leftDir = vec3_origin - leftDir; + } + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + + idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + + for ( i = 0 ; i < tri->numVerts ; i+=4 ) { + idVec3 positions[4]; + if ( tri->silTraceVerts ) { + for ( int j = 0; j < 4; ++j ) { + positions[j].Set( tri->silTraceVerts[i+j].xyzw[0], + tri->silTraceVerts[i+j].xyzw[1], tri->silTraceVerts[i+j].xyzw[2] ); + } + } else { + for ( int j = 0; j < 4; ++j ) { + positions[j] = tri->verts[i+j].xyz; + } + } + + if ( radiusDimension ) { + mid = ( positions[0] + positions[1] + positions[2] + positions[3] ) * 0.25f; + radius = ( positions[0] - mid ).Length() * 0.707f; + radius2 = radius; + } else { + // The retail rectangular-sprite path intentionally leaves mid unchanged. + radius = ( positions[2] - positions[0] ).Length() * 0.5f; + radius2 = ( positions[1] - positions[0] ).Length() * 0.5f; + } + + left = leftDir * radius; + up = upDir * radius2; + + ac[i+0].xyz = mid + left + up; + ac[i+0].st[0] = 0; + ac[i+0].st[1] = 0; + ac[i+1].xyz = mid - left + up; + ac[i+1].st[0] = 1; + ac[i+1].st[1] = 0; + ac[i+2].xyz = mid - left - up; + ac[i+2].st[0] = 1; + ac[i+2].st[1] = 1; + ac[i+3].xyz = mid + left - up; + ac[i+3].st[0] = 0; + ac[i+3].st[1] = 1; + + newTri->indexes[6*(i>>2)+0] = i; + newTri->indexes[6*(i>>2)+1] = i+1; + newTri->indexes[6*(i>>2)+2] = i+2; + + newTri->indexes[6*(i>>2)+3] = i; + newTri->indexes[6*(i>>2)+4] = i+2; + newTri->indexes[6*(i>>2)+5] = i+3; + } + + R_FinishDeform( surf, newTri, ac ); +} + +/* +===================== +R_TubeDeform + +will pivot a rectangular quad along the center of its long axis + +Note that a geometric tube with even quite a few sides tube will almost certainly render much faster +than this, so this should only be for faked volumetric tubes. +Make sure this is used with twosided translucent shaders, because the exact side +order may not be correct. +===================== +*/ +static void R_TubeDeform( drawSurf_t *surf ) { + int i, j; + int indexes; + const srfTriangles_t *tri; +static int edgeVerts[6][2] = { + { 0, 1 }, + { 1, 2 }, + { 2, 0 }, + { 3, 4 }, + { 4, 5 }, + { 5, 3 } +}; + + tri = surf->geo; + + if ( tri->numVerts & 3 ) { + common->Error( "R_AutospriteDeform: shader had odd vertex count" ); + } + if ( tri->numIndexes != ( tri->numVerts >> 2 ) * 6 ) { + common->Error( "R_AutospriteDeform: autosprite had odd index count" ); + } + + // we need the view direction to project the minor axis of the tube + // as the view changes + idVec3 localView; + R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localView ); + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + srfTriangles_t *newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + idDrawVert *ac; + if ( tri->primBatchMesh ) { + newTri->numVerts = tri->primBatchMesh->m_numDrawVertices; + newTri->numIndexes = tri->primBatchMesh->m_numDrawIndices; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + memset( ac, 0, sizeof( idDrawVert ) * newTri->numVerts ); + tri->primBatchMesh->TubeDeform( ac, newTri->indexes, localView, tri->silTraceVerts ); + } else { + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + memcpy( newTri->indexes, tri->indexes, newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + memset( ac, 0, sizeof( idDrawVert ) * newTri->numVerts ); + + // this is a lot of work for two triangles... + // we could precalculate a lot if it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tri->numVerts ; i+=4, indexes+=6 ) { + float lengths[2]; + int nums[2]; + idVec3 mid[2]; + idVec3 major, minor; + const idDrawVert *v1, *v2; + + // identify the two shortest edges out of the six defined by the indexes + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = 999999; + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + + v1 = &tri->verts[tri->indexes[i+edgeVerts[j][0]]]; + v2 = &tri->verts[tri->indexes[i+edgeVerts[j][1]]]; + + l = ( v1->xyz - v2->xyz ).Length(); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + // find the midpoints of the two short edges, which + // will give us the major axis in object coordinates + for ( j = 0 ; j < 2 ; j++ ) { + v1 = &tri->verts[tri->indexes[i+edgeVerts[nums[j]][0]]]; + v2 = &tri->verts[tri->indexes[i+edgeVerts[nums[j]][1]]]; + + mid[j][0] = 0.5 * (v1->xyz[0] + v2->xyz[0]); + mid[j][1] = 0.5 * (v1->xyz[1] + v2->xyz[1]); + mid[j][2] = 0.5 * (v1->xyz[2] + v2->xyz[2]); + } + + // find the vector of the major axis + major = mid[1] - mid[0]; + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + int i1 = tri->indexes[i+edgeVerts[nums[j]][0]]; + int i2 = tri->indexes[i+edgeVerts[nums[j]][1]]; + + idDrawVert *av1 = &ac[i1]; + idDrawVert *av2 = &ac[i2]; + + *av1 = *(idDrawVert *)&tri->verts[i1]; + *av2 = *(idDrawVert *)&tri->verts[i2]; + + l = 0.5 * lengths[j]; + + // cross this with the view direction to get minor axis + idVec3 dir = mid[j] - localView; + minor.Cross( major, dir ); + minor.Normalize(); + + if ( j ) { + av1->xyz = mid[j] - l * minor; + av2->xyz = mid[j] + l * minor; + } else { + av1->xyz = mid[j] + l * minor; + av2->xyz = mid[j] - l * minor; + } + } + } + } + + R_FinishDeform( surf, newTri, ac ); +} + +/* +===================== +R_WindingFromTriangles + +===================== +*/ +#define MAX_TRI_WINDING_INDEXES 16 +int R_WindingFromTriangles( const srfTriangles_t *tri, glIndex_t indexes[MAX_TRI_WINDING_INDEXES] ) { + int i, j, k, l; + + indexes[0] = tri->indexes[0]; + int numIndexes = 1; + int numTris = tri->numIndexes / 3; + + do { + // find an edge that goes from the current index to another + // index that isn't already used, and isn't an internal edge + for ( i = 0 ; i < numTris ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + if ( tri->indexes[i*3+j] != indexes[numIndexes-1] ) { + continue; + } + int next = tri->indexes[i*3+(j+1)%3]; + + // make sure it isn't already used + if ( numIndexes == 1 ) { + if ( next == indexes[0] ) { + continue; + } + } else { + for ( k = 1 ; k < numIndexes ; k++ ) { + if ( indexes[k] == next ) { + break; + } + } + if ( k != numIndexes ) { + continue; + } + } + + // make sure it isn't an interior edge + for ( k = 0 ; k < numTris ; k++ ) { + if ( k == i ) { + continue; + } + for ( l = 0 ; l < 3 ; l++ ) { + int a, b; + + a = tri->indexes[k*3+l]; + if ( a != next ) { + continue; + } + b = tri->indexes[k*3+(l+1)%3]; + if ( b != indexes[numIndexes-1] ) { + continue; + } + + // this is an interior edge + break; + } + if ( l != 3 ) { + break; + } + } + if ( k != numTris ) { + continue; + } + + // add this to the list + indexes[numIndexes] = next; + numIndexes++; + break; + } + if ( j != 3 ) { + break; + } + } + if ( numIndexes == tri->numVerts ) { + break; + } + } while ( i != numTris ); + + return numIndexes; +} + +/* +===================== +R_FlareDeform + +===================== +*/ +/* +static void R_FlareDeform( drawSurf_t *surf ) { + const srfTriangles_t *tri; + srfTriangles_t *newTri; + idPlane plane; + float dot; + idVec3 localViewer; + int j; + + tri = surf->geo; + if ( tri->numVerts != 4 || tri->numIndexes != 6 ) { + //FIXME: temp hack for flares on tripleted models + common->Warning( "R_FlareDeform: not a single quad" ); + return; + } + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + newTri->numVerts = 4; + newTri->numIndexes = 2*3; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + + idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + + // find the plane + plane.FromPoints( tri->verts[tri->indexes[0]].xyz, tri->verts[tri->indexes[1]].xyz, tri->verts[tri->indexes[2]].xyz ); + + // if viewer is behind the plane, draw nothing + R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewer ); + float distFromPlane = localViewer * plane.Normal() + plane[3]; + if ( distFromPlane <= 0 ) { + newTri->numIndexes = 0; + surf->geo = newTri; + return; + } + + idVec3 center; + center = tri->verts[0].xyz; + for ( j = 1 ; j < tri->numVerts ; j++ ) { + center += tri->verts[j].xyz; + } + center *= 1.0/tri->numVerts; + + idVec3 dir = localViewer - center; + dir.Normalize(); + + dot = dir * plane.Normal(); + + // set vertex colors based on plane angle + int color = (int)(dot * 8 * 256); + if ( color > 255 ) { + color = 255; + } + for ( j = 0 ; j < newTri->numVerts ; j++ ) { + ac[j].color[0] = + ac[j].color[1] = + ac[j].color[2] = color; + ac[j].color[3] = 255; + } + + float spread = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ] * r_flareSize.GetFloat(); + idVec3 edgeDir[4][3]; + glIndex_t indexes[MAX_TRI_WINDING_INDEXES]; + int numIndexes = R_WindingFromTriangles( tri, indexes ); + + surf->material = declManager->FindMaterial( "textures/smf/anamorphicFlare" ); + + // only deal with quads + if ( numIndexes != 4 ) { + return; + } + + // compute centroid + idVec3 centroid, toeye, forward, up, left; + centroid.Set( 0, 0, 0 ); + for ( int i = 0; i < 4; i++ ) { + centroid += tri->verts[ indexes[i] ].xyz; + } + centroid /= 4; + + // compute basis vectors + up.Set( 0, 0, 1 ); + + toeye = centroid - localViewer; + toeye.Normalize(); + left = toeye.Cross( up ); + up = left.Cross( toeye ); + + left = left * 40 * 6; + up = up * 40; + + // compute flares + struct flare_t { + float angle; + float length; + }; + + static flare_t flares[] = { + { 0, 100 }, + { 90, 100 } + }; + + for ( int i = 0; i < 4; i++ ) { + memset( ac + i, 0, sizeof( ac[i] ) ); + } + + ac[0].xyz = centroid - left; + ac[0].st[0] = 0; ac[0].st[1] = 0; + + ac[1].xyz = centroid + up; + ac[1].st[0] = 1; ac[1].st[1] = 0; + + ac[2].xyz = centroid + left; + ac[2].st[0] = 1; ac[2].st[1] = 1; + + ac[3].xyz = centroid - up; + ac[3].st[0] = 0; ac[3].st[1] = 1; + + // setup colors + for ( j = 0 ; j < newTri->numVerts ; j++ ) { + ac[j].color[0] = + ac[j].color[1] = + ac[j].color[2] = 255; + ac[j].color[3] = 255; + } + + // setup indexes + static glIndex_t triIndexes[2*3] = { + 0,1,2, 0,2,3 + }; + + memcpy( newTri->indexes, triIndexes, sizeof( triIndexes ) ); + + R_FinishDeform( surf, newTri, ac ); +} +*/ + +static void R_FlareDeform( drawSurf_t *surf ) { + const srfTriangles_t *tri; + srfTriangles_t tempTri; + srfTriangles_t *newTri; + idPlane plane; + float dot; + idVec3 localViewer; + int j; + + tri = surf->geo; + if ( tri->primBatchMesh ) { + memset( &tempTri, 0, sizeof( tempTri ) ); + tempTri.numVerts = tri->primBatchMesh->m_numDrawVertices; + tempTri.numIndexes = tri->primBatchMesh->m_numDrawIndices; + tempTri.indexes = (glIndex_t *)R_FrameAlloc( tempTri.numIndexes * sizeof( tempTri.indexes[0] ) ); + tempTri.verts = (idDrawVert *)_alloca16( tempTri.numVerts * sizeof( idDrawVert ) ); + tri->primBatchMesh->CopyTriangles( tempTri.verts, tempTri.indexes, tri->silTraceVerts ); + tri = &tempTri; + } + + if ( tri->numVerts != 4 || tri->numIndexes != 6 ) { + //FIXME: temp hack for flares on tripleted models + common->Warning( "R_FlareDeform: not a single quad" ); + return; + } + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + newTri->numVerts = 16; + newTri->numIndexes = 18*3; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + + idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + + // find the plane + plane.FromPoints( tri->verts[tri->indexes[0]].xyz, tri->verts[tri->indexes[1]].xyz, tri->verts[tri->indexes[2]].xyz ); + + // if viewer is behind the plane, draw nothing + R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewer ); + float distFromPlane = localViewer * plane.Normal() + plane[3]; + if ( distFromPlane <= 0 ) { + newTri->numIndexes = 0; + surf->geo = newTri; + return; + } + + idVec3 center; + center = tri->verts[0].xyz; + for ( j = 1 ; j < tri->numVerts ; j++ ) { + center += tri->verts[j].xyz; + } + center *= 1.0/tri->numVerts; + + idVec3 dir = localViewer - center; + dir.Normalize(); + + dot = dir * plane.Normal(); + + // set vertex colors based on plane angle + int color = (int)(dot * 8 * 256); + if ( color > 255 ) { + color = 255; + } + for ( j = 0 ; j < newTri->numVerts ; j++ ) { + ac[j].color[0] = + ac[j].color[1] = + ac[j].color[2] = color; + ac[j].color[3] = 255; + } + + float spread = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ] * r_flareSize.GetFloat(); + idVec3 edgeDir[4][3]; + glIndex_t indexes[MAX_TRI_WINDING_INDEXES]; + int numIndexes = R_WindingFromTriangles( tri, indexes ); + + + // only deal with quads + if ( numIndexes != 4 ) { + return; + } + int i; + // calculate vector directions + for ( i = 0 ; i < 4 ; i++ ) { + ac[i].xyz = tri->verts[ indexes[i] ].xyz; + ac[i].st[0] = + ac[i].st[1] = 0.5; + + idVec3 toEye = tri->verts[ indexes[i] ].xyz - localViewer; + toEye.Normalize(); + + idVec3 d1 = tri->verts[ indexes[(i+1)%4] ].xyz - localViewer; + d1.Normalize(); + edgeDir[i][1].Cross( toEye, d1 ); + edgeDir[i][1].Normalize(); + edgeDir[i][1] = vec3_origin - edgeDir[i][1]; + + idVec3 d2 = tri->verts[ indexes[(i+3)%4] ].xyz - localViewer; + d2.Normalize(); + edgeDir[i][0].Cross( toEye, d2 ); + edgeDir[i][0].Normalize(); + + edgeDir[i][2] = edgeDir[i][0] + edgeDir[i][1]; + edgeDir[i][2].Normalize(); + } + + // build all the points + ac[4].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][0]; + ac[4].st[0] = 0; + ac[4].st[1] = 0.5; + + ac[5].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][2]; + ac[5].st[0] = 0; + ac[5].st[1] = 0; + + ac[6].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][1]; + ac[6].st[0] = 0.5; + ac[6].st[1] = 0; + + + ac[7].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][0]; + ac[7].st[0] = 0.5; + ac[7].st[1] = 0; + + ac[8].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][2]; + ac[8].st[0] = 1; + ac[8].st[1] = 0; + + ac[9].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][1]; + ac[9].st[0] = 1; + ac[9].st[1] = 0.5; + + + ac[10].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][0]; + ac[10].st[0] = 1; + ac[10].st[1] = 0.5; + + ac[11].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][2]; + ac[11].st[0] = 1; + ac[11].st[1] = 1; + + ac[12].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][1]; + ac[12].st[0] = 0.5; + ac[12].st[1] = 1; + + + ac[13].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][0]; + ac[13].st[0] = 0.5; + ac[13].st[1] = 1; + + ac[14].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][2]; + ac[14].st[0] = 0; + ac[14].st[1] = 1; + + ac[15].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][1]; + ac[15].st[0] = 0; + ac[15].st[1] = 0.5; + + for ( i = 4 ; i < 16 ; i++ ) { + idVec3 dir = ac[i].xyz - localViewer; + float len = dir.Normalize(); + + float ang = dir * plane.Normal(); + +// ac[i].xyz -= dir * spread * 2; + float newLen = -( distFromPlane / ang ); + + if ( newLen > 0 && newLen < len ) { + ac[i].xyz = localViewer + dir * newLen; + } + + ac[i].st[0] = 0; + ac[i].st[1] = 0.5; + } + +#if 1 + static glIndex_t triIndexes[18*3] = { + 0,4,5, 0,5,6, 0,6,7, 0,7,1, 1,7,8, 1,8,9, + 15,4,0, 15,0,3, 3,0,1, 3,1,2, 2,1,9, 2,9,10, + 14,15,3, 14,3,13, 13,3,2, 13,2,12, 12,2,11, 11,2,10 + }; +#else + newTri->numIndexes = 12; + static glIndex_t triIndexes[4*3] = { + 0,1,2, 0,2,3, 0,4,5,0,5,6 + }; +#endif + + memcpy( newTri->indexes, triIndexes, sizeof( triIndexes ) ); + + R_FinishDeform( surf, newTri, ac ); +} + + + +/* +===================== +R_ExpandDeform + +Expands the surface along it's normals by a shader amount +===================== +*/ +static void R_ExpandDeform( drawSurf_t *surf ) { + int i; + const srfTriangles_t *tri; + srfTriangles_t *newTri; + idDrawVert *ac; + + tri = surf->geo; + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + if ( tri->primBatchMesh ) { + newTri->numVerts = tri->primBatchMesh->m_numDrawVertices; + newTri->numIndexes = tri->primBatchMesh->m_numDrawIndices; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + tri->primBatchMesh->CopyTriangles( ac, newTri->indexes, tri->silTraceVerts ); + + // Static vertex buffers do not necessarily contain derived tangents/normals. + newTri->verts = ac; + R_DeriveTangents( newTri, false ); + newTri->verts = NULL; + + const float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; + for ( i = 0; i < newTri->numVerts; ++i ) { + ac[i].xyz += ac[i].normal * dist; + } + } else { + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + newTri->indexes = tri->indexes; + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + + const float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; + for ( i = 0 ; i < tri->numVerts ; i++ ) { + ac[i] = *(idDrawVert *)&tri->verts[i]; + ac[i].xyz = tri->verts[i].xyz + tri->verts[i].normal * dist; + } + } + + R_FinishDeform( surf, newTri, ac ); +} + +/* +===================== +R_MoveDeform + +Moves the surface along the X axis, mostly just for demoing the deforms +===================== +*/ +static void R_MoveDeform( drawSurf_t *surf ) { + int i; + const srfTriangles_t *tri; + srfTriangles_t *newTri; + idDrawVert *ac; + + tri = surf->geo; + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + if ( tri->primBatchMesh ) { + newTri->numVerts = tri->primBatchMesh->m_numDrawVertices; + newTri->numIndexes = tri->primBatchMesh->m_numDrawIndices; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + tri->primBatchMesh->CopyTriangles( ac, newTri->indexes, tri->silTraceVerts ); + } else { + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + newTri->indexes = tri->indexes; + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + for ( i = 0 ; i < tri->numVerts ; i++ ) { + ac[i] = *(idDrawVert *)&tri->verts[i]; + } + } + + const float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; + for ( i = 0 ; i < newTri->numVerts ; i++ ) { + ac[i].xyz[0] += dist; + } + + R_FinishDeform( surf, newTri, ac ); +} + +//===================================================================================== + +/* +===================== +R_TurbulentDeform + +Turbulently deforms the XYZ, S, and T values +===================== +*/ +static void R_TurbulentDeform( drawSurf_t *surf ) { + int i; + const srfTriangles_t *tri; + srfTriangles_t *newTri; + idDrawVert *ac; + + tri = surf->geo; + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + if ( tri->primBatchMesh ) { + newTri->numVerts = tri->primBatchMesh->m_numDrawVertices; + newTri->numIndexes = tri->primBatchMesh->m_numDrawIndices; + newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + tri->primBatchMesh->CopyTriangles( ac, newTri->indexes, tri->silTraceVerts ); + } else { + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + newTri->indexes = tri->indexes; + ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) ); + for ( i = 0; i < tri->numVerts; ++i ) { + ac[i] = *(idDrawVert *)&tri->verts[i]; + } + } + + idDeclTable *table = (idDeclTable *)surf->material->GetDeformDecl(); + float range = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ]; + float timeOfs = surf->shaderRegisters[ surf->material->GetDeformRegister(1) ]; + float domain = surf->shaderRegisters[ surf->material->GetDeformRegister(2) ]; + float tOfs = 0.5; + + for ( i = 0 ; i < newTri->numVerts ; i++ ) { + float f = ac[i].xyz[0] * 0.003 + ac[i].xyz[1] * 0.007 + ac[i].xyz[2] * 0.011; + + f = timeOfs + domain * f; + f += timeOfs; + + ac[i].st[0] += range * table->TableLookup( f ); + ac[i].st[1] += range * table->TableLookup( f + tOfs ); + } + + R_FinishDeform( surf, newTri, ac ); +} + +//===================================================================================== + +/* +===================== +AddTriangleToIsland_r + +===================== +*/ +#define MAX_EYEBALL_TRIS 10 +#define MAX_EYEBALL_ISLANDS 6 + +typedef struct { + int tris[MAX_EYEBALL_TRIS]; + int numTris; + idBounds bounds; + idVec3 mid; +} eyeIsland_t; + +static void AddTriangleToIsland_r( const srfTriangles_t *tri, int triangleNum, bool *usedList, eyeIsland_t *island ) { + int a, b, c; + + usedList[triangleNum] = true; + + // add to the current island + if ( island->numTris == MAX_EYEBALL_TRIS ) { + common->Error( "MAX_EYEBALL_TRIS" ); + } + island->tris[island->numTris] = triangleNum; + island->numTris++; + + // recurse into all neighbors + a = tri->indexes[triangleNum*3]; + b = tri->indexes[triangleNum*3+1]; + c = tri->indexes[triangleNum*3+2]; + + island->bounds.AddPoint( tri->verts[a].xyz ); + island->bounds.AddPoint( tri->verts[b].xyz ); + island->bounds.AddPoint( tri->verts[c].xyz ); + + int numTri = tri->numIndexes / 3; + for ( int i = 0 ; i < numTri ; i++ ) { + if ( usedList[i] ) { + continue; + } + if ( tri->indexes[i*3+0] == a + || tri->indexes[i*3+1] == a + || tri->indexes[i*3+2] == a + || tri->indexes[i*3+0] == b + || tri->indexes[i*3+1] == b + || tri->indexes[i*3+2] == b + || tri->indexes[i*3+0] == c + || tri->indexes[i*3+1] == c + || tri->indexes[i*3+2] == c ) { + AddTriangleToIsland_r( tri, i, usedList, island ); + } + } +} + +/* +===================== +R_EyeballDeform + +Each eyeball surface should have an separate upright triangle behind it, long end +pointing out the eye, and another single triangle in front of the eye for the focus point. +===================== +*/ +static void R_EyeballDeform( drawSurf_t *surf ) { + int i, j, k; + const srfTriangles_t *tri; + srfTriangles_t tempTri; + srfTriangles_t *newTri; + eyeIsland_t islands[MAX_EYEBALL_ISLANDS]; + int numIslands; + bool triUsed[MAX_EYEBALL_ISLANDS*MAX_EYEBALL_TRIS]; + + tri = surf->geo; + if ( tri->primBatchMesh ) { + memset( &tempTri, 0, sizeof( tempTri ) ); + tempTri.numVerts = tri->primBatchMesh->m_numDrawVertices; + tempTri.numIndexes = tri->primBatchMesh->m_numDrawIndices; + tempTri.indexes = (glIndex_t *)_alloca16( tempTri.numIndexes * sizeof( tempTri.indexes[0] ) ); + tempTri.verts = (idDrawVert *)_alloca16( tempTri.numVerts * sizeof( idDrawVert ) ); + tri->primBatchMesh->CopyTriangles( tempTri.verts, tempTri.indexes, tri->silTraceVerts ); + tri = &tempTri; + } + + // separate all the triangles into islands + int numTri = tri->numIndexes / 3; + if ( numTri > MAX_EYEBALL_ISLANDS*MAX_EYEBALL_TRIS ) { + common->Printf( "R_EyeballDeform: too many triangles in surface" ); + return; + } + memset( triUsed, 0, sizeof( triUsed ) ); + + for ( numIslands = 0 ; numIslands < MAX_EYEBALL_ISLANDS ; numIslands++ ) { + islands[numIslands].numTris = 0; + islands[numIslands].bounds.Clear(); + for ( i = 0 ; i < numTri ; i++ ) { + if ( !triUsed[i] ) { + AddTriangleToIsland_r( tri, i, triUsed, &islands[numIslands] ); + break; + } + } + if ( i == numTri ) { + break; + } + } + + // assume we always have two eyes, two origins, and two targets + if ( numIslands != 3 ) { + common->Printf( "R_EyeballDeform: %i triangle islands\n", numIslands ); + return; + } + + // this srfTriangles_t and all its indexes and caches are in frame + // memory, and will be automatically disposed of + + // the surface cannot have more indexes or verts than the original + newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) ); + memset( newTri, 0, sizeof( *newTri ) ); + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + newTri->indexes = (glIndex_t *)R_FrameAlloc( tri->numIndexes * sizeof( newTri->indexes[0] ) ); + idDrawVert *ac = (idDrawVert *)_alloca16( tri->numVerts * sizeof( idDrawVert ) ); + + newTri->numIndexes = 0; + + // decide which islands are the eyes and points + for ( i = 0 ; i < numIslands ; i++ ) { + islands[i].mid = islands[i].bounds.GetCenter(); + } + + for ( i = 0 ; i < numIslands ; i++ ) { + eyeIsland_t *island = &islands[i]; + + if ( island->numTris == 1 ) { + continue; + } + + // the closest single triangle point will be the eye origin + // and the next-to-farthest will be the focal point + idVec3 origin, focus; + int originIsland = 0; + float dist[MAX_EYEBALL_ISLANDS]; + int sortOrder[MAX_EYEBALL_ISLANDS]; + + for ( j = 0 ; j < numIslands ; j++ ) { + idVec3 dir = islands[j].mid - island->mid; + dist[j] = dir.Length(); + sortOrder[j] = j; + for ( k = j-1 ; k >= 0 ; k-- ) { + if ( dist[k] > dist[k+1] ) { + int temp = sortOrder[k]; + sortOrder[k] = sortOrder[k+1]; + sortOrder[k+1] = temp; + float ftemp = dist[k]; + dist[k] = dist[k+1]; + dist[k+1] = ftemp; + } + } + } + + originIsland = sortOrder[1]; + origin = islands[originIsland].mid; + + focus = islands[sortOrder[2]].mid; + + // determine the projection directions based on the origin island triangle + idVec3 dir = focus - origin; + dir.Normalize(); + + const idVec3 &p1 = tri->verts[tri->indexes[islands[originIsland].tris[0]+0]].xyz; + const idVec3 &p2 = tri->verts[tri->indexes[islands[originIsland].tris[0]+1]].xyz; + const idVec3 &p3 = tri->verts[tri->indexes[islands[originIsland].tris[0]+2]].xyz; + + idVec3 v1 = p2 - p1; + v1.Normalize(); + idVec3 v2 = p3 - p1; + v2.Normalize(); + + // texVec[0] will be the normal to the origin triangle + idVec3 texVec[2]; + + texVec[0].Cross( v1, v2 ); + + texVec[1].Cross( texVec[0], dir ); + + for ( j = 0 ; j < 2 ; j++ ) { + texVec[j] -= dir * ( texVec[j] * dir ); + texVec[j].Normalize(); + } + + // emit these triangles, generating the projected texcoords + + for ( j = 0 ; j < islands[i].numTris ; j++ ) { + for ( k = 0 ; k < 3 ; k++ ) { + int index = islands[i].tris[j] * 3; + + index = tri->indexes[index+k]; + newTri->indexes[newTri->numIndexes++] = index; + + ac[index].xyz = tri->verts[index].xyz; + + idVec3 local = tri->verts[index].xyz - origin; + + ac[index].st[0] = 0.5 + local * texVec[0]; + ac[index].st[1] = 0.5 + local * texVec[1]; + } + } + } + + R_FinishDeform( surf, newTri, ac ); +} + +//========================================================================================== + + +/* +===================== +R_ParticleDeform + +Emit particles from the surface instead of drawing it +===================== +*/ +#if 0 +static void R_ParticleDeform( drawSurf_t *surf, bool useArea ) { + const struct renderEntity_s *renderEntity = &surf->space->entityDef->parms; + const struct viewDef_s *viewDef = tr.viewDef; + const idDeclParticle *particleSystem = (idDeclParticle *)surf->material->GetDeformDecl(); + + if ( r_skipParticles.GetBool() ) { + return; + } + +#if 0 + if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] && + viewDef->renderView.time*0.001 >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] ) { + // the entire system has faded out + return NULL; + } +#endif + + // + // calculate the area of all the triangles + // + int numSourceTris = surf->geo->numIndexes / 3; + float totalArea = 0; + float *sourceTriAreas = NULL; + const srfTriangles_t *srcTri = surf->geo; + + if ( useArea ) { + sourceTriAreas = (float *)_alloca( sizeof( *sourceTriAreas ) * numSourceTris ); + int triNum = 0; + for ( int i = 0 ; i < srcTri->numIndexes ; i += 3, triNum++ ) { + float area; + area = idWinding::TriangleArea( srcTri->verts[srcTri->indexes[i]].xyz, srcTri->verts[srcTri->indexes[i+1]].xyz, srcTri->verts[srcTri->indexes[i+2]].xyz ); + sourceTriAreas[triNum] = totalArea; + totalArea += area; + } + } + + // + // create the particles almost exactly the way idRenderModelPrt does + // + particleGen_t g; + + g.renderEnt = renderEntity; + g.renderView = &viewDef->renderView; + g.origin.Zero(); + g.axis = mat3_identity; + + for ( int currentTri = 0; currentTri < ( ( useArea ) ? 1 : numSourceTris ); currentTri++ ) { + + for ( int stageNum = 0 ; stageNum < particleSystem->stages.Num() ; stageNum++ ) { + idParticleStage *stage = particleSystem->stages[stageNum]; + + if ( !stage->material ) { + continue; + } + if ( !stage->cycleMsec ) { + continue; + } + if ( stage->hidden ) { // just for gui particle editor use + continue; + } + + // we interpret stage->totalParticles as "particles per map square area" + // so the systems look the same on different size surfaces + int totalParticles = ( useArea ) ? stage->totalParticles * totalArea / 4096.0 : ( stage->totalParticles ); + + int count = totalParticles * stage->NumQuadsPerParticle(); + + // allocate a srfTriangles in temp memory that can hold all the particles + srfTriangles_t *tri; + + tri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *tri ) ); + tri->numVerts = 4 * count; + tri->numIndexes = 6 * count; + tri->verts = (idDrawVert *)R_FrameAlloc( tri->numVerts * sizeof( tri->verts[0] ) ); + tri->indexes = (glIndex_t *)R_FrameAlloc( tri->numIndexes * sizeof( tri->indexes[0] ) ); + + // just always draw the particles + tri->bounds = stage->bounds; + + tri->numVerts = 0; + + idRandom steppingRandom, steppingRandom2; + + int stageAge = g.renderView->time + renderEntity->shaderParms[SHADERPARM_TIMEOFFSET] * 1000 - stage->timeOffset * 1000; + int stageCycle = stageAge / stage->cycleMsec; + int inCycleTime = stageAge - stageCycle * stage->cycleMsec; + + // some particles will be in this cycle, some will be in the previous cycle + steppingRandom.SetSeed( (( stageCycle << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); + steppingRandom2.SetSeed( (( (stageCycle-1) << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) ); + + for ( int index = 0 ; index < totalParticles ; index++ ) { + g.index = index; + + // bump the random + steppingRandom.RandomInt(); + steppingRandom2.RandomInt(); + + // calculate local age for this index + int bunchOffset = stage->particleLife * 1000 * stage->spawnBunching * index / totalParticles; + + int particleAge = stageAge - bunchOffset; + int particleCycle = particleAge / stage->cycleMsec; + if ( particleCycle < 0 ) { + // before the particleSystem spawned + continue; + } + if ( stage->cycles && particleCycle >= stage->cycles ) { + // cycled systems will only run cycle times + continue; + } + + if ( particleCycle == stageCycle ) { + g.random = steppingRandom; + } else { + g.random = steppingRandom2; + } + + int inCycleTime = particleAge - particleCycle * stage->cycleMsec; + + if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] && + g.renderView->time - inCycleTime >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME]*1000 ) { + // don't fire any more particles + continue; + } + + // supress particles before or after the age clamp + g.frac = (float)inCycleTime / ( stage->particleLife * 1000 ); + if ( g.frac < 0 ) { + // yet to be spawned + continue; + } + if ( g.frac > 1.0 ) { + // this particle is in the deadTime band + continue; + } + + //--------------- + // locate the particle origin and axis somewhere on the surface + //--------------- + + int pointTri = currentTri; + + if ( useArea ) { + // select a triangle based on an even area distribution + pointTri = idBinSearch_LessEqual( sourceTriAreas, numSourceTris, g.random.RandomFloat() * totalArea ); + } + + // now pick a random point inside pointTri + const idDrawVert *v1 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 0 ] ]; + const idDrawVert *v2 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 1 ] ]; + const idDrawVert *v3 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 2 ] ]; + + float f1 = g.random.RandomFloat(); + float f2 = g.random.RandomFloat(); + float f3 = g.random.RandomFloat(); + + float ft = 1.0f / ( f1 + f2 + f3 + 0.0001f ); + + f1 *= ft; + f2 *= ft; + f3 *= ft; + + g.origin = v1->xyz * f1 + v2->xyz * f2 + v3->xyz * f3; + g.axis[0] = v1->tangents[0] * f1 + v2->tangents[0] * f2 + v3->tangents[0] * f3; + g.axis[1] = v1->tangents[1] * f1 + v2->tangents[1] * f2 + v3->tangents[1] * f3; + g.axis[2] = v1->normal * f1 + v2->normal * f2 + v3->normal * f3; + + //----------------------- + + // this is needed so aimed particles can calculate origins at different times + g.originalRandom = g.random; + + g.age = g.frac * stage->particleLife; + + // if the particle doesn't get drawn because it is faded out or beyond a kill region, + // don't increment the verts + tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts ); + } + + if ( tri->numVerts > 0 ) { + // build the index list + int indexes = 0; + for ( int i = 0 ; i < tri->numVerts ; i += 4 ) { + tri->indexes[indexes+0] = i; + tri->indexes[indexes+1] = i+2; + tri->indexes[indexes+2] = i+3; + tri->indexes[indexes+3] = i; + tri->indexes[indexes+4] = i+3; + tri->indexes[indexes+5] = i+1; + indexes += 6; + } + tri->numIndexes = indexes; + tri->ambientCache = vertexCache.AllocFrameTemp( tri->verts, tri->numVerts * sizeof( idDrawVert ) ); + if ( tri->ambientCache ) { + // add the drawsurf + R_AddDrawSurf( tri, surf->space, renderEntity, stage->material, surf->scissorRect ); + } + } + } + } +} +#endif + +//======================================================================================== + +/* +================= +R_DeformDrawSurf +================= +*/ +void R_DeformDrawSurf( drawSurf_t *drawSurf ) { + if ( !drawSurf->material ) { + return; + } + + if ( r_skipDeforms.GetBool() ) { + return; + } + switch ( drawSurf->material->Deform() ) { + case DFRM_NONE: + return; + case DFRM_SPRITE: + R_SpriteDeform( drawSurf, true ); + break; + case DFRM_TUBE: + R_TubeDeform( drawSurf ); + break; + case DFRM_FLARE: + R_FlareDeform( drawSurf ); + break; + case DFRM_EXPAND: + R_ExpandDeform( drawSurf ); + break; + case DFRM_MOVE: + R_MoveDeform( drawSurf ); + break; + case DFRM_EYEBALL: + R_EyeballDeform( drawSurf ); + break; + case DFRM_RECTSPRITE: + R_SpriteDeform( drawSurf, false ); + break; + case DFRM_TURB: + R_TurbulentDeform( drawSurf ); + break; + } +} diff --git a/src/renderer/tr_font.cpp b/src/renderer/tr_font.cpp new file mode 100644 index 0000000..e83600e --- /dev/null +++ b/src/renderer/tr_font.cpp @@ -0,0 +1,660 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +#ifdef BUILD_FREETYPE +#include "../ft2/fterrors.h" +#include "../ft2/ftsystem.h" +#include "../ft2/ftimage.h" +#include "../ft2/freetype.h" +#include "../ft2/ftoutln.h" + +#define _FLOOR(x) ((x) & -64) +#define _CEIL(x) (((x)+63) & -64) +#define _TRUNC(x) ((x) >> 6) + +FT_Library ftLibrary = NULL; +#endif + + +#ifdef BUILD_FREETYPE + +/* +============ +R_GetGlyphInfo +============ +*/ +void R_GetGlyphInfo(FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch) { + + *left = _FLOOR( glyph->metrics.horiBearingX ); + *right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width ); + *width = _TRUNC(*right - *left); + + *top = _CEIL( glyph->metrics.horiBearingY ); + *bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height ); + *height = _TRUNC( *top - *bottom ); + *pitch = ( qtrue ? (*width+3) & -4 : (*width+7) >> 3 ); +} + +/* +============ +R_RenderGlyph +============ +*/ +FT_Bitmap *R_RenderGlyph(FT_GlyphSlot glyph, glyphInfo_t* glyphOut) { + FT_Bitmap *bit2; + int left, right, width, top, bottom, height, pitch, size; + + R_GetGlyphInfo(glyph, &left, &right, &width, &top, &bottom, &height, &pitch); + + if ( glyph->format == ft_glyph_format_outline ) { + size = pitch*height; + + bit2 = Mem_Alloc(sizeof(FT_Bitmap)); + + bit2->width = width; + bit2->rows = height; + bit2->pitch = pitch; + bit2->pixel_mode = ft_pixel_mode_grays; + //bit2->pixel_mode = ft_pixel_mode_mono; + bit2->buffer = Mem_Alloc(pitch*height); + bit2->num_grays = 256; + + memset( bit2->buffer, 0, size ); + + FT_Outline_Translate( &glyph->outline, -left, -bottom ); + + FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 ); + + glyphOut->height = height; + glyphOut->pitch = pitch; + glyphOut->top = (glyph->metrics.horiBearingY >> 6) + 1; + glyphOut->bottom = bottom; + + return bit2; + } + else { + common->Printf( "Non-outline fonts are not supported\n" ); + } + return NULL; +} + +/* +============ +RE_ConstructGlyphInfo +============ +*/ +glyphInfo_t *RE_ConstructGlyphInfo( unsigned char *imageOut, int *xOut, int *yOut, int *maxHeight, FT_Face face, const unsigned char c, qboolean calcHeight ) { + int i; + static glyphInfo_t glyph; + unsigned char *src, *dst; + float scaled_width, scaled_height; + FT_Bitmap *bitmap = NULL; + + memset(&glyph, 0, sizeof(glyphInfo_t)); + // make sure everything is here + if (face != NULL) { + FT_Load_Glyph(face, FT_Get_Char_Index( face, c), FT_LOAD_DEFAULT ); + bitmap = R_RenderGlyph(face->glyph, &glyph); + if (bitmap) { + glyph.xSkip = (face->glyph->metrics.horiAdvance >> 6) + 1; + } else { + return &glyph; + } + + if (glyph.height > *maxHeight) { + *maxHeight = glyph.height; + } + + if (calcHeight) { + Mem_Free(bitmap->buffer); + Mem_Free(bitmap); + return &glyph; + } + +/* + // need to convert to power of 2 sizes so we do not get + // any scaling from the gl upload + for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1) + ; +*/ + + scaled_width = glyph.pitch; + scaled_height = glyph.height; + + // we need to make sure we fit + if (*xOut + scaled_width + 1 >= 255) { + if (*yOut + *maxHeight + 1 >= 255) { + *yOut = -1; + *xOut = -1; + Mem_Free(bitmap->buffer); + Mem_Free(bitmap); + return &glyph; + } else { + *xOut = 0; + *yOut += *maxHeight + 1; + } + } else if (*yOut + *maxHeight + 1 >= 255) { + *yOut = -1; + *xOut = -1; + Mem_Free(bitmap->buffer); + Mem_Free(bitmap); + return &glyph; + } + + src = bitmap->buffer; + dst = imageOut + (*yOut * 256) + *xOut; + + if (bitmap->pixel_mode == ft_pixel_mode_mono) { + for (i = 0; i < glyph.height; i++) { + int j; + unsigned char *_src = src; + unsigned char *_dst = dst; + unsigned char mask = 0x80; + unsigned char val = *_src; + for (j = 0; j < glyph.pitch; j++) { + if (mask == 0x80) { + val = *_src++; + } + if (val & mask) { + *_dst = 0xff; + } + mask >>= 1; + + if ( mask == 0 ) { + mask = 0x80; + } + _dst++; + } + + src += glyph.pitch; + dst += 256; + + } + } else { + for (i = 0; i < glyph.height; i++) { + memcpy( dst, src, glyph.pitch ); + src += glyph.pitch; + dst += 256; + } + } + + // we now have an 8 bit per pixel grey scale bitmap + // that is width wide and pf->ftSize->metrics.y_ppem tall + + glyph.imageHeight = scaled_height; + glyph.imageWidth = scaled_width; + glyph.s = (float)*xOut / 256; + glyph.t = (float)*yOut / 256; + glyph.s2 = glyph.s + (float)scaled_width / 256; + glyph.t2 = glyph.t + (float)scaled_height / 256; + + *xOut += scaled_width + 1; + } + + Mem_Free(bitmap->buffer); + Mem_Free(bitmap); + + return &glyph; +} + +#endif + +static int fdOffset; +static byte *fdFile; + +/* +============ +readInt +============ +*/ +int readInt( void ) { + int i = fdFile[fdOffset]+(fdFile[fdOffset+1]<<8)+(fdFile[fdOffset+2]<<16)+(fdFile[fdOffset+3]<<24); + fdOffset += 4; + return i; +} + +typedef union { + byte fred[4]; + float ffred; +} poor; + +/* +============ +readFloat +============ +*/ +float readFloat( void ) { + poor me; +#ifdef __ppc__ + me.fred[0] = fdFile[fdOffset+3]; + me.fred[1] = fdFile[fdOffset+2]; + me.fred[2] = fdFile[fdOffset+1]; + me.fred[3] = fdFile[fdOffset+0]; +#else + me.fred[0] = fdFile[fdOffset+0]; + me.fred[1] = fdFile[fdOffset+1]; + me.fred[2] = fdFile[fdOffset+2]; + me.fred[3] = fdFile[fdOffset+3]; +#endif + fdOffset += 4; + return me.ffred; +} + +/* +============ +RegisterFont + +Loads 3 point sizes, 12, 24, and 48 +============ +*/ +#if 0 +bool idRenderSystemLocal::RegisterFont( const char *fontName, fontInfoEx_t &font ) { +#ifdef BUILD_FREETYPE + FT_Face face; + int j, k, xOut, yOut, lastStart, imageNumber; + int scaledSize, newSize, maxHeight, left, satLevels; + unsigned char *out, *imageBuff; + glyphInfo_t *glyph; + idImage *image; + idMaterial *h; + float max; +#endif + void *faceData; + ID_TIME_T ftime; + int i, len, fontCount; + char name[1024]; + + int pointSize = 12; +/* + if ( registeredFontCount >= MAX_FONTS ) { + common->Warning( "RegisterFont: Too many fonts registered already." ); + return false; + } + + int pointSize = 12; + idStr::snPrintf( name, sizeof(name), "%s/fontImage_%i.dat", fontName, pointSize ); + for ( i = 0; i < registeredFontCount; i++ ) { + if ( idStr::Icmp(name, registeredFont[i].fontInfoSmall.name) == 0 ) { + memcpy( &font, ®isteredFont[i], sizeof( fontInfoEx_t ) ); + return true; + } + } +*/ + + memset( &font, 0, sizeof( font ) ); + + for ( fontCount = 0; fontCount < 3; fontCount++ ) { + + if ( fontCount == 0) { + pointSize = 12; + } else if ( fontCount == 1 ) { + pointSize = 24; + } else { + pointSize = 48; + } + // we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font + float glyphScale = 1.0f; // change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 ) + glyphScale *= 48.0f / pointSize; + + idStr::snPrintf( name, sizeof(name), "%s/fontImage_%i.dat", fontName, pointSize ); + + fontInfo_t *outFont; + if ( fontCount == 0 ) { + outFont = &font.fontInfoSmall; + } + else if ( fontCount == 1 ) { + outFont = &font.fontInfoMedium; + } + else { + outFont = &font.fontInfoLarge; + } + + idStr::Copynz( outFont->name, name, sizeof( outFont->name ) ); + + len = fileSystem->ReadFile( name, NULL, &ftime ); + if ( len != sizeof( fontInfo_t ) ) { + common->Warning( "RegisterFont: couldn't find font: '%s'", name ); + return false; + } + + fileSystem->ReadFile( name, &faceData, &ftime ); + fdOffset = 0; + fdFile = reinterpret_cast(faceData); + for( i = 0; i < GLYPHS_PER_FONT; i++ ) { + outFont->glyphs[i].height = readInt(); + outFont->glyphs[i].top = readInt(); + outFont->glyphs[i].bottom = readInt(); + outFont->glyphs[i].pitch = readInt(); + outFont->glyphs[i].xSkip = readInt(); + outFont->glyphs[i].imageWidth = readInt(); + outFont->glyphs[i].imageHeight = readInt(); + outFont->glyphs[i].s = readFloat(); + outFont->glyphs[i].t = readFloat(); + outFont->glyphs[i].s2 = readFloat(); + outFont->glyphs[i].t2 = readFloat(); + int junk /* font.glyphs[i].glyph */ = readInt(); + //FIXME: the +6, -6 skips the embedded fonts/ + memcpy( outFont->glyphs[i].shaderName, &fdFile[fdOffset + 6], 32 - 6 ); + fdOffset += 32; + } + outFont->glyphScale = readFloat(); + + int mw = 0; + int mh = 0; + for (i = GLYPH_START; i < GLYPH_END; i++) { + idStr::snPrintf(name, sizeof(name), "%s/%s", fontName, outFont->glyphs[i].shaderName); + outFont->glyphs[i].glyph = declManager->FindMaterial(name); + outFont->glyphs[i].glyph->SetSort( SS_GUI ); + if (mh < outFont->glyphs[i].height) { + mh = outFont->glyphs[i].height; + } + if (mw < outFont->glyphs[i].xSkip) { + mw = outFont->glyphs[i].xSkip; + } + } + if (fontCount == 0) { + font.maxWidthSmall = mw; + font.maxHeightSmall = mh; + } else if (fontCount == 1) { + font.maxWidthMedium = mw; + font.maxHeightMedium = mh; + } else { + font.maxWidthLarge = mw; + font.maxHeightLarge = mh; + } + fileSystem->FreeFile( faceData ); + } + + //memcpy( ®isteredFont[registeredFontCount++], &font, sizeof( fontInfoEx_t ) ); + + return true ; + +#ifndef BUILD_FREETYPE + common->Warning( "RegisterFont: couldn't load FreeType code %s", name ); +#else + + if (ftLibrary == NULL) { + common->Warning( "RegisterFont: FreeType not initialized." ); + return; + } + + len = fileSystem->ReadFile(fontName, &faceData, &ftime); + if ( len <= 0 ) { + common->Warning( "RegisterFont: Unable to read font file" ); + return; + } + + // allocate on the stack first in case we fail + if ( FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face ) ) { + common->Warning( "RegisterFont: FreeType2, unable to allocate new face." ); + return; + } + + + if ( FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi) ) { + common->Warning( "RegisterFont: FreeType2, Unable to set face char size." ); + return; + } + + // font = registeredFonts[registeredFontCount++]; + + // make a 256x256 image buffer, once it is full, register it, clean it and keep going + // until all glyphs are rendered + + out = Mem_Alloc( 1024*1024 ); + if ( out == NULL ) { + common->Warning( "RegisterFont: Mem_Alloc failure during output image creation." ); + return; + } + memset( out, 0, 1024*1024 ); + + maxHeight = 0; + + for (i = GLYPH_START; i < GLYPH_END; i++) { + glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue); + } + + xOut = 0; + yOut = 0; + i = GLYPH_START; + lastStart = i; + imageNumber = 0; + + while ( i <= GLYPH_END ) { + + glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse); + + if (xOut == -1 || yOut == -1 || i == GLYPH_END) { + // ran out of room + // we need to create an image from the bitmap, set all the handles in the glyphs to this point + // + + scaledSize = 256*256; + newSize = scaledSize * 4; + imageBuff = Mem_Alloc(newSize); + left = 0; + max = 0; + satLevels = 255; + for ( k = 0; k < (scaledSize) ; k++ ) { + if (max < out[k]) { + max = out[k]; + } + } + + if (max > 0) { + max = 255/max; + } + + for ( k = 0; k < (scaledSize) ; k++ ) { + imageBuff[left++] = 255; + imageBuff[left++] = 255; + imageBuff[left++] = 255; + imageBuff[left++] = ((float)out[k] * max); + } + + idStr::snprintf( name, sizeof(name), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize ); + if (r_saveFontData->integer) { + R_WriteTGA(name, imageBuff, 256, 256); + } + + //idStr::snprintf( name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize ); + image = R_CreateImage(name, imageBuff, 256, 256, qfalse, qfalse, GL_CLAMP); + h = RE_RegisterShaderFromImage(name, LIGHTMAP_2D, image, qfalse); + for (j = lastStart; j < i; j++) { + font.glyphs[j].glyph = h; + idStr::Copynz( font.glyphs[j].shaderName, name, sizeof( font.glyphs[j].shaderName ) ); + } + lastStart = i; + memset( out, 0, 1024*1024 ); + xOut = 0; + yOut = 0; + Mem_Free( imageBuff ); + i++; + } else { + memcpy( &font.glyphs[i], glyph, sizeof( glyphInfo_t ) ); + i++; + } + } + + registeredFont[registeredFontCount].glyphScale = glyphScale; + font.glyphScale = glyphScale; + memcpy( ®isteredFont[registeredFontCount++], &font, sizeof( fontInfo_t ) ); + + if ( r_saveFontData->integer ) { + fileSystem->WriteFile( va( "fonts/fontImage_%i.dat", pointSize), &font, sizeof( fontInfo_t ) ); + } + + Mem_Free( out ); + + fileSystem->FreeFile( faceData ); +#endif + return true; +} +#endif + +/* +==================== +idRenderSystemLocal::RegisterFont + +Quake 4 stores each point size in a single .fontdat record. The trailing +material pointer is deliberately not read from disk; it is resolved from the +same name after the platform-independent float fields have been decoded. +==================== +*/ +bool idRenderSystemLocal::RegisterFont( const char *fontName, fontInfoEx_t &font ) { + static const int pointSizes[3] = { 12, 24, 48 }; + int found = 0; + + memset( &font, 0, sizeof( font ) ); + + for ( int fontIndex = 0; fontIndex < 3; fontIndex++ ) { + fontInfo_t *outFont; + float *maxWidth; + float *maxHeight; + + if ( fontIndex == 0 ) { + outFont = &font.fontInfoSmall; + maxWidth = &font.maxWidthSmall; + maxHeight = &font.maxHeightSmall; + } else if ( fontIndex == 1 ) { + outFont = &font.fontInfoMedium; + maxWidth = &font.maxWidthMedium; + maxHeight = &font.maxHeightMedium; + } else { + outFont = &font.fontInfoLarge; + maxWidth = &font.maxWidthLarge; + maxHeight = &font.maxHeightLarge; + } + + char fileName[MAX_OSPATH]; + idStr::snPrintf( fileName, sizeof( fileName ), "%s_%i.fontdat", fontName, pointSizes[fontIndex] ); + if ( fileSystem->ReadFile( fileName, NULL, NULL ) != sizeof( fontInfo_t ) ) { + continue; + } + idFile *fontFile = fileSystem->OpenFileRead( fileName, true ); + if ( fontFile == NULL ) { + continue; + } + + for ( int glyphIndex = 0; glyphIndex < GLYPH_COUNT; glyphIndex++ ) { + glyphInfo_t &glyph = outFont->glyphs[glyphIndex]; + fontFile->ReadFloat( glyph.width ); + fontFile->ReadFloat( glyph.height ); + fontFile->ReadFloat( glyph.horiAdvance ); + fontFile->ReadFloat( glyph.horiBearingX ); + fontFile->ReadFloat( glyph.horiBearingY ); + fontFile->ReadFloat( glyph.s1 ); + fontFile->ReadFloat( glyph.t1 ); + fontFile->ReadFloat( glyph.s2 ); + fontFile->ReadFloat( glyph.t2 ); + *maxWidth = Max( *maxWidth, glyph.horiAdvance ); + *maxHeight = Max( *maxHeight, glyph.height ); + } + + fontFile->ReadFloat( outFont->pointSize ); + fontFile->ReadFloat( outFont->fontHeight ); + fontFile->ReadFloat( outFont->ascender ); + fontFile->ReadFloat( outFont->descender ); + fileSystem->CloseFile( fontFile ); + + outFont->material = const_cast( declManager->FindMaterial( fileName ) ); + outFont->material->SetSort( SS_GUI ); + found |= 1 << fontIndex; + } + + if ( found == 0 ) { + return false; + } + + switch ( found ) { + case 1: + font.fontInfoMedium = font.fontInfoSmall; + font.maxHeightMedium = font.maxHeightSmall; + font.fontInfoLarge = font.fontInfoSmall; + font.maxHeightLarge = font.maxHeightSmall; + break; + case 2: + font.fontInfoSmall = font.fontInfoMedium; + font.maxHeightSmall = font.maxHeightMedium; + font.fontInfoLarge = font.fontInfoMedium; + font.maxHeightLarge = font.maxHeightMedium; + break; + case 3: + font.fontInfoLarge = font.fontInfoMedium; + font.maxHeightLarge = font.maxHeightMedium; + break; + case 4: + font.fontInfoSmall = font.fontInfoLarge; + font.maxHeightSmall = font.maxHeightLarge; + font.fontInfoMedium = font.fontInfoLarge; + font.maxHeightMedium = font.maxHeightLarge; + break; + case 5: + font.fontInfoMedium = font.fontInfoLarge; + font.maxHeightMedium = font.maxHeightLarge; + break; + case 6: + font.fontInfoSmall = font.fontInfoMedium; + font.maxHeightSmall = font.maxHeightMedium; + break; + } + return true; +} + +/* +============ +R_InitFreeType +============ +*/ +void R_InitFreeType( void ) { +#ifdef BUILD_FREETYPE + if ( FT_Init_FreeType( &ftLibrary ) ) { + common->Printf( "R_InitFreeType: Unable to initialize FreeType.\n" ); + } +#endif +// registeredFontCount = 0; +} + +/* +============ +R_DoneFreeType +============ +*/ +void R_DoneFreeType( void ) { +#ifdef BUILD_FREETYPE + if ( ftLibrary ) { + FT_Done_FreeType( ftLibrary ); + ftLibrary = NULL; + } +#endif +// registeredFontCount = 0; +} diff --git a/src/renderer/tr_guisurf.cpp b/src/renderer/tr_guisurf.cpp new file mode 100644 index 0000000..ad2a00e --- /dev/null +++ b/src/renderer/tr_guisurf.cpp @@ -0,0 +1,272 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" + +/* +========================================================================================== + +GUI SHADERS + +========================================================================================== +*/ + +/* +================ +R_ScreenRectFromDrawSurf +================ +*/ +idScreenRect R_ScreenRectFromDrawSurf( drawSurf_t *drawSurf ) { + idScreenRect r; + r.Clear(); + + const srfTriangles_t *tri = drawSurf->geo; + if ( tri->primBatchMesh ) { + tri->primBatchMesh->ScreenRectFromDrawSurf( r, + drawSurf->space->modelMatrix, + tr.viewDef->worldSpace.modelViewMatrix, + tr.viewDef->projectionMatrix, + tr.viewDef->viewport, + tri->silTraceVerts ); + return r; + } + + for ( int i = 0; i < tri->numVerts; i++ ) { + idVec3 global; + idVec3 ndc; + R_LocalPointToGlobal( drawSurf->space->modelMatrix, tri->verts[i].xyz, global ); + R_GlobalToNormalizedDeviceCoordinates( tr.viewDef->worldSpace.modelViewMatrix, + tr.viewDef->projectionMatrix, global, ndc ); + + const float y = tr.viewDef->viewport.y2 - + ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ) * ( ndc.y + 1.0f ) * 0.5f; + const float x = ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ) * + ( ndc.x + 1.0f ) * 0.5f; + r.AddPoint( x, y ); + } + + return r; +} + +/* +================ +R_SurfaceToTextureAxis + +Calculates two axis for the surface sutch that a point dotted against +the axis will give a 0.0 to 1.0 range in S and T when inside the gui surface +================ +*/ +void R_SurfaceToTextureAxis( const srfTriangles_t *tri, idVec3 &origin, idVec3 axis[3] ) { + float area, inva; + float d0[5], d1[5]; + idDrawVert *a, *b, *c; + float bounds[2][2]; + float boundsOrg[2]; + int i, j; + float v; + + if ( tri->primBatchMesh ) { + tri->primBatchMesh->SurfaceToTextureAxis( origin, axis, tri->silTraceVerts ); + return; + } + + // find the bounds of the texture + bounds[0][0] = bounds[0][1] = 999999; + bounds[1][0] = bounds[1][1] = -999999; + for ( i = 0 ; i < tri->numVerts ; i++ ) { + for ( j = 0 ; j < 2 ; j++ ) { + v = tri->verts[i].st[j]; + if ( v < bounds[0][j] ) { + bounds[0][j] = v; + } + if ( v > bounds[1][j] ) { + bounds[1][j] = v; + } + } + } + + // use the floor of the midpoint as the origin of the + // surface, which will prevent a slight misalignment + // from throwing it an entire cycle off + boundsOrg[0] = floor( ( bounds[0][0] + bounds[1][0] ) * 0.5 ); + boundsOrg[1] = floor( ( bounds[0][1] + bounds[1][1] ) * 0.5 ); + + + // determine the world S and T vectors from the first drawSurf triangle + a = tri->verts + tri->indexes[0]; + b = tri->verts + tri->indexes[1]; + c = tri->verts + tri->indexes[2]; + + VectorSubtract( b->xyz, a->xyz, d0 ); + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + VectorSubtract( c->xyz, a->xyz, d1 ); + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( area == 0.0 ) { + axis[0].Zero(); + axis[1].Zero(); + axis[2].Zero(); + return; // degenerate + } + inva = 1.0 / area; + + axis[0][0] = (d0[0] * d1[4] - d0[4] * d1[0]) * inva; + axis[0][1] = (d0[1] * d1[4] - d0[4] * d1[1]) * inva; + axis[0][2] = (d0[2] * d1[4] - d0[4] * d1[2]) * inva; + + axis[1][0] = (d0[3] * d1[0] - d0[0] * d1[3]) * inva; + axis[1][1] = (d0[3] * d1[1] - d0[1] * d1[3]) * inva; + axis[1][2] = (d0[3] * d1[2] - d0[2] * d1[3]) * inva; + + idPlane plane; + plane.FromPoints( a->xyz, b->xyz, c->xyz ); + axis[2][0] = plane[0]; + axis[2][1] = plane[1]; + axis[2][2] = plane[2]; + + // take point 0 and project the vectors to the texture origin + VectorMA( a->xyz, boundsOrg[0] - a->st[0], axis[0], origin ); + VectorMA( origin, boundsOrg[1] - a->st[1], axis[1], origin ); +} + +/* +================= +R_RenderGuiSurf + +Create a texture space on the given surface and +call the GUI generator to create quads for it. +================= +*/ +void R_RenderGuiSurf( idUserInterface *gui, drawSurf_t *drawSurf ) { + idVec3 origin, axis[3]; + + // for testing the performance hit + if ( r_skipGuiShaders.GetInteger() == 1 ) { + return; + } + + // don't allow an infinite recursion loop + if ( tr.guiRecursionLevel == 4 ) { + return; + } + + tr.pc.c_guiSurfs++; + + // create the new matrix to draw on this surface + R_SurfaceToTextureAxis( drawSurf->geo, origin, axis ); + + float guiModelMatrix[16]; + float modelMatrix[16]; + + guiModelMatrix[0] = axis[0][0] / 640.0; + guiModelMatrix[4] = axis[1][0] / 480.0; + guiModelMatrix[8] = axis[2][0]; + guiModelMatrix[12] = origin[0]; + + guiModelMatrix[1] = axis[0][1] / 640.0; + guiModelMatrix[5] = axis[1][1] / 480.0; + guiModelMatrix[9] = axis[2][1]; + guiModelMatrix[13] = origin[1]; + + guiModelMatrix[2] = axis[0][2] / 640.0; + guiModelMatrix[6] = axis[1][2] / 480.0; + guiModelMatrix[10] = axis[2][2]; + guiModelMatrix[14] = origin[2]; + + guiModelMatrix[3] = 0; + guiModelMatrix[7] = 0; + guiModelMatrix[11] = 0; + guiModelMatrix[15] = 1; + + myGlMultMatrix( guiModelMatrix, drawSurf->space->modelMatrix, + modelMatrix ); + + tr.guiRecursionLevel++; + + // call the gui, which will call the 2D drawing functions + tr.guiModel->Clear(); + gui->Redraw( tr.viewDef->renderView.time ); + tr.guiModel->EmitToCurrentView( modelMatrix, + drawSurf->space->weaponDepthHackInViewID, + drawSurf->space->entityDef->parms.shaderParms ); + tr.guiModel->Clear(); + + if ( atoi( gui->State().GetString( "2d_calc", "0" ) ) ) { + const idScreenRect rect = R_ScreenRectFromDrawSurf( drawSurf ); + const float ratioX = 640.0f / ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ); + const float ratioY = 480.0f / ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ); + + gui->SetStateFloat( "2d_min_x", rect.x1 * ratioX ); + gui->SetStateFloat( "2d_min_y", rect.y1 * ratioY ); + gui->SetStateFloat( "2d_max_x", rect.x2 * ratioX ); + gui->SetStateFloat( "2d_max_y", rect.y2 * ratioY ); + gui->SetStateBool( "2d_calc", false ); + } + + tr.guiRecursionLevel--; +} + + + + +/* +================, +R_ReloadGuis_f + +Reloads any guis that have had their file timestamps changed. +An optional "all" parameter will cause all models to reload, even +if they are not out of date. + +Should we also reload the map models? +================ +*/ +void R_ReloadGuis_f( const idCmdArgs &args ) { + bool all; + + if ( !idStr::Icmp( args.Argv(1), "all" ) ) { + all = true; + common->Printf( "Reloading all gui files...\n" ); + } else { + all = false; + common->Printf( "Checking for changed gui files...\n" ); + } + + uiManager->Reload( all ); +} + +/* +================, +R_ListGuis_f + +================ +*/ +void R_ListGuis_f( const idCmdArgs &args ) { + uiManager->ListGuis(); +} diff --git a/src/renderer/tr_light.cpp b/src/renderer/tr_light.cpp new file mode 100644 index 0000000..3dbad97 --- /dev/null +++ b/src/renderer/tr_light.cpp @@ -0,0 +1,1540 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +static const float CHECK_BOUNDS_EPSILON = 1.0f; + + +/* +=========================================================================================== + +VERTEX CACHE GENERATORS + +=========================================================================================== +*/ + +/* +================== +R_CreateAmbientCache + +Create it if needed +================== +*/ +bool R_CreateAmbientCache( srfTriangles_t *tri, bool needsLighting ) { + if ( tri->ambientCache ) { + return true; + } + // we are going to use it for drawing, so make sure we have the tangents and normals + if ( needsLighting && !tri->tangentsCalculated ) { + R_DeriveTangents( tri ); + } + + vertexCache.Alloc( tri->verts, tri->numVerts * sizeof( tri->verts[0] ), &tri->ambientCache ); + if ( !tri->ambientCache ) { + return false; + } + return true; +} + +/* +================== +R_CreateLightingCache + +Returns false if the cache couldn't be allocated, in which case the surface should be skipped. +================== +*/ +bool R_CreateLightingCache( const idRenderEntityLocal *ent, const idRenderLightLocal *light, srfTriangles_t *tri ) { + idVec3 localLightOrigin; + + // fogs and blends don't need light vectors + if ( light->lightShader->IsFogLight() || light->lightShader->IsBlendLight() ) { + return true; + } + + // not needed if we have vertex programs + if ( tr.backEndRendererHasVertexPrograms ) { + return true; + } + + R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin ); + + int size = tri->ambientSurface->numVerts * sizeof( lightingCache_t ); + lightingCache_t *cache = (lightingCache_t *)_alloca16( size ); + +#if 1 + + SIMDProcessor->CreateTextureSpaceLightVectors( &cache[0].localLightVector, localLightOrigin, + tri->ambientSurface->verts, tri->ambientSurface->numVerts, tri->indexes, tri->numIndexes ); + +#else + + bool *used = (bool *)_alloca16( tri->ambientSurface->numVerts * sizeof( used[0] ) ); + memset( used, 0, tri->ambientSurface->numVerts * sizeof( used[0] ) ); + + // because the interaction may be a very small subset of the full surface, + // it makes sense to only deal with the verts used + for ( int j = 0; j < tri->numIndexes; j++ ) { + int i = tri->indexes[j]; + if ( used[i] ) { + continue; + } + used[i] = true; + + idVec3 lightDir; + const idDrawVert *v; + + v = &tri->ambientSurface->verts[i]; + + lightDir = localLightOrigin - v->xyz; + + cache[i].localLightVector[0] = lightDir * v->tangents[0]; + cache[i].localLightVector[1] = lightDir * v->tangents[1]; + cache[i].localLightVector[2] = lightDir * v->normal; + } + +#endif + + vertexCache.Alloc( cache, size, &tri->lightingCache ); + if ( !tri->lightingCache ) { + return false; + } + return true; +} + +/* +================== +R_CreatePrivateShadowCache + +This is used only for a specific light +================== +*/ +void R_CreatePrivateShadowCache( srfTriangles_t *tri ) { + if ( !tri->shadowVertexes ) { + return; + } + + vertexCache.Alloc( tri->shadowVertexes, tri->numVerts * sizeof( *tri->shadowVertexes ), &tri->shadowCache ); +} + +/* +================== +R_CreateVertexProgramShadowCache + +This is constant for any number of lights, the vertex program +takes care of projecting the verts to infinity. +================== +*/ +void R_CreateVertexProgramShadowCache( srfTriangles_t *tri ) { + if ( tri->verts == NULL ) { + return; + } + + shadowCache_t *temp = (shadowCache_t *)_alloca16( tri->numVerts * 2 * sizeof( shadowCache_t ) ); + +#if 1 + + SIMDProcessor->CreateVertexProgramShadowCache( &temp->xyz, tri->verts, tri->numVerts ); + +#else + + int numVerts = tri->numVerts; + const idDrawVert *verts = tri->verts; + for ( int i = 0; i < numVerts; i++ ) { + const float *v = verts[i].xyz.ToFloatPtr(); + temp[i*2+0].xyz[0] = v[0]; + temp[i*2+1].xyz[0] = v[0]; + temp[i*2+0].xyz[1] = v[1]; + temp[i*2+1].xyz[1] = v[1]; + temp[i*2+0].xyz[2] = v[2]; + temp[i*2+1].xyz[2] = v[2]; + temp[i*2+0].xyz[3] = 1.0f; // on the model surface + temp[i*2+1].xyz[3] = 0.0f; // will be projected to infinity + } + +#endif + + vertexCache.Alloc( temp, tri->numVerts * 2 * sizeof( shadowCache_t ), &tri->shadowCache ); +} + +/* +================== +R_SkyboxTexGen +================== +*/ +void R_SkyboxTexGen( drawSurf_t *surf, const idVec3 &viewOrg ) { + int i; + idVec3 localViewOrigin; + + R_GlobalPointToLocal( surf->space->modelMatrix, viewOrg, localViewOrigin ); + + int numVerts = surf->geo->numVerts; + int size = numVerts * sizeof( idVec3 ); + idVec3 *texCoords = (idVec3 *) _alloca16( size ); + + const idDrawVert *verts = surf->geo->verts; + for ( i = 0; i < numVerts; i++ ) { + texCoords[i][0] = verts[i].xyz[0] - localViewOrigin[0]; + texCoords[i][1] = verts[i].xyz[1] - localViewOrigin[1]; + texCoords[i][2] = verts[i].xyz[2] - localViewOrigin[2]; + } + + surf->dynamicTexCoords = vertexCache.AllocFrameTemp( texCoords, size ); +} + +/* +================== +R_WobbleskyTexGen +================== +*/ +void R_WobbleskyTexGen( drawSurf_t *surf, const idVec3 &viewOrg ) { + int i; + idVec3 localViewOrigin; + + const int *parms = surf->material->GetTexGenRegisters(); + + float wobbleDegrees = surf->shaderRegisters[ parms[0] ]; + float wobbleSpeed = surf->shaderRegisters[ parms[1] ]; + float rotateSpeed = surf->shaderRegisters[ parms[2] ]; + + wobbleDegrees = wobbleDegrees * idMath::PI / 180; + wobbleSpeed = wobbleSpeed * 2 * idMath::PI / 60; + rotateSpeed = rotateSpeed * 2 * idMath::PI / 60; + + // very ad-hoc "wobble" transform + float transform[16]; + float a = tr.viewDef->floatTime * wobbleSpeed; + float s = sin( a ) * sin( wobbleDegrees ); + float c = cos( a ) * sin( wobbleDegrees ); + float z = cos( wobbleDegrees ); + + idVec3 axis[3]; + + axis[2][0] = c; + axis[2][1] = s; + axis[2][2] = z; + + axis[1][0] = -sin( a * 2 ) * sin( wobbleDegrees ); + axis[1][2] = -s * sin( wobbleDegrees ); + axis[1][1] = sqrt( 1.0f - ( axis[1][0] * axis[1][0] + axis[1][2] * axis[1][2] ) ); + + // make the second vector exactly perpendicular to the first + axis[1] -= ( axis[2] * axis[1] ) * axis[2]; + axis[1].Normalize(); + + // construct the third with a cross + axis[0].Cross( axis[1], axis[2] ); + + // add the rotate + s = sin( rotateSpeed * tr.viewDef->floatTime ); + c = cos( rotateSpeed * tr.viewDef->floatTime ); + + transform[0] = axis[0][0] * c + axis[1][0] * s; + transform[4] = axis[0][1] * c + axis[1][1] * s; + transform[8] = axis[0][2] * c + axis[1][2] * s; + + transform[1] = axis[1][0] * c - axis[0][0] * s; + transform[5] = axis[1][1] * c - axis[0][1] * s; + transform[9] = axis[1][2] * c - axis[0][2] * s; + + transform[2] = axis[2][0]; + transform[6] = axis[2][1]; + transform[10] = axis[2][2]; + + transform[3] = transform[7] = transform[11] = 0.0f; + transform[12] = transform[13] = transform[14] = 0.0f; + + R_GlobalPointToLocal( surf->space->modelMatrix, viewOrg, localViewOrigin ); + + int numVerts = surf->geo->numVerts; + int size = numVerts * sizeof( idVec3 ); + idVec3 *texCoords = (idVec3 *) _alloca16( size ); + + const idDrawVert *verts = surf->geo->verts; + for ( i = 0; i < numVerts; i++ ) { + idVec3 v; + + v[0] = verts[i].xyz[0] - localViewOrigin[0]; + v[1] = verts[i].xyz[1] - localViewOrigin[1]; + v[2] = verts[i].xyz[2] - localViewOrigin[2]; + + R_LocalPointToGlobal( transform, v, texCoords[i] ); + } + + surf->dynamicTexCoords = vertexCache.AllocFrameTemp( texCoords, size ); +} + +/* +================= +R_SpecularTexGen + +Calculates the specular coordinates for cards without vertex programs. +================= +*/ +static void R_SpecularTexGen( drawSurf_t *surf, const idVec3 &globalLightOrigin, const idVec3 &viewOrg ) { + const srfTriangles_t *tri; + idVec3 localLightOrigin; + idVec3 localViewOrigin; + + R_GlobalPointToLocal( surf->space->modelMatrix, globalLightOrigin, localLightOrigin ); + R_GlobalPointToLocal( surf->space->modelMatrix, viewOrg, localViewOrigin ); + + tri = surf->geo; + + // FIXME: change to 3 component? + int size = tri->numVerts * sizeof( idVec4 ); + idVec4 *texCoords = (idVec4 *) _alloca16( size ); + +#if 1 + + SIMDProcessor->CreateSpecularTextureCoords( texCoords, localLightOrigin, localViewOrigin, + tri->verts, tri->numVerts, tri->indexes, tri->numIndexes ); + +#else + + bool *used = (bool *)_alloca16( tri->numVerts * sizeof( used[0] ) ); + memset( used, 0, tri->numVerts * sizeof( used[0] ) ); + + // because the interaction may be a very small subset of the full surface, + // it makes sense to only deal with the verts used + for ( int j = 0; j < tri->numIndexes; j++ ) { + int i = tri->indexes[j]; + if ( used[i] ) { + continue; + } + used[i] = true; + + float ilength; + + const idDrawVert *v = &tri->verts[i]; + + idVec3 lightDir = localLightOrigin - v->xyz; + idVec3 viewDir = localViewOrigin - v->xyz; + + ilength = idMath::RSqrt( lightDir * lightDir ); + lightDir[0] *= ilength; + lightDir[1] *= ilength; + lightDir[2] *= ilength; + + ilength = idMath::RSqrt( viewDir * viewDir ); + viewDir[0] *= ilength; + viewDir[1] *= ilength; + viewDir[2] *= ilength; + + lightDir += viewDir; + + texCoords[i][0] = lightDir * v->tangents[0]; + texCoords[i][1] = lightDir * v->tangents[1]; + texCoords[i][2] = lightDir * v->normal; + texCoords[i][3] = 1; + } + +#endif + + surf->dynamicTexCoords = vertexCache.AllocFrameTemp( texCoords, size ); +} + + +//======================================================================================================= + +/* +============= +R_SetEntityDefViewEntity + +If the entityDef isn't already on the viewEntity list, create +a viewEntity and add it to the list with an empty scissor rect. + +This does not instantiate dynamic models for the entity yet. +============= +*/ +viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def ) { + viewEntity_t *vModel; + + if ( def->viewCount == tr.viewCount ) { + return def->viewEntity; + } + def->viewCount = tr.viewCount; + + // set the model and modelview matricies + vModel = (viewEntity_t *)R_ClearedFrameAlloc( sizeof( *vModel ) ); + vModel->entityDef = def; + + // the scissorRect will be expanded as the model bounds is accepted into visible portal chains + vModel->scissorRect.Clear(); + + // copy the model and weapon depth hack for back-end use + vModel->modelDepthHack = def->parms.modelDepthHack; + vModel->weaponDepthHackInViewID = def->parms.weaponDepthHackInViewID; + vModel->distanceToCamera = 0.0f; + vModel->screenCoverage = 0.0f; + + R_AxisToModelMatrix( def->parms.axis, def->parms.origin, vModel->modelMatrix ); + + // we may not have a viewDef if we are just creating shadows at entity creation time + if ( tr.viewDef ) { + myGlMultMatrix( vModel->modelMatrix, tr.viewDef->worldSpace.modelViewMatrix, vModel->modelViewMatrix ); + + vModel->distanceToCamera = ( def->parms.origin - tr.viewDef->renderView.vieworg ).LengthSqr(); + vModel->next = tr.viewDef->viewEntitys; + tr.viewDef->viewEntitys = vModel; + } + + def->viewEntity = vModel; + + return vModel; +} + +/* +==================== +R_TestPointInViewLight +==================== +*/ +static const float INSIDE_LIGHT_FRUSTUM_SLOP = 32; +// this needs to be greater than the dist from origin to corner of near clip plane +static bool R_TestPointInViewLight( const idVec3 &org, const idRenderLightLocal *light ) { + int i; + idVec3 local; + + for ( i = 0 ; i < 6 ; i++ ) { + float d = light->frustum[i].Distance( org ); + if ( d > INSIDE_LIGHT_FRUSTUM_SLOP ) { + return false; + } + } + + return true; +} + +/* +=================== +R_PointInFrustum + +Assumes positive sides face outward +=================== +*/ +static bool R_PointInFrustum( idVec3 &p, idPlane *planes, int numPlanes ) { + for ( int i = 0 ; i < numPlanes ; i++ ) { + float d = planes[i].Distance( p ); + if ( d > 0 ) { + return false; + } + } + return true; +} + +/* +============= +R_SetLightDefViewLight + +If the lightDef isn't already on the viewLight list, create +a viewLight and add it to the list with an empty scissor rect. +============= +*/ +viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *light ) { + viewLight_t *vLight; + + if ( light->viewCount == tr.viewCount ) { + return light->viewLight; + } + light->viewCount = tr.viewCount; + + // add to the view light chain + vLight = (viewLight_t *)R_ClearedFrameAlloc( sizeof( *vLight ) ); + vLight->lightDef = light; + + // the scissorRect will be expanded as the light bounds is accepted into visible portal chains + vLight->scissorRect.Clear(); + + // calculate the shadow cap optimization states + vLight->viewInsideLight = R_TestPointInViewLight( tr.viewDef->renderView.vieworg, light ); + if ( !vLight->viewInsideLight ) { + vLight->viewSeesShadowPlaneBits = 0; + for ( int i = 0 ; i < light->numShadowFrustums ; i++ ) { + float d = light->shadowFrustums[i].planes[5].Distance( tr.viewDef->renderView.vieworg ); + if ( d < INSIDE_LIGHT_FRUSTUM_SLOP ) { + vLight->viewSeesShadowPlaneBits|= 1 << i; + } + } + } else { + // this should not be referenced in this case + vLight->viewSeesShadowPlaneBits = 63; + } + + // see if the light center is in view, which will allow us to cull invisible shadows + vLight->viewSeesGlobalLightOrigin = R_PointInFrustum( light->globalLightOrigin, tr.viewDef->frustum, 4 ); + + // copy data used by backend + vLight->globalLightOrigin = light->globalLightOrigin; + vLight->lightProject[0] = light->lightProject[0]; + vLight->lightProject[1] = light->lightProject[1]; + vLight->lightProject[2] = light->lightProject[2]; + vLight->lightProject[3] = light->lightProject[3]; + vLight->fogPlane = light->frustum[5]; + vLight->frustumTris = light->frustumTris; + vLight->falloffImage = light->falloffImage; + vLight->lightShader = light->lightShader; + vLight->shaderRegisters = NULL; // allocated and evaluated in R_AddLightSurfaces + + // link the view light + vLight->next = tr.viewDef->viewLights; + tr.viewDef->viewLights = vLight; + + light->viewLight = vLight; + + return vLight; +} + +/* +================= +idRenderWorldLocal::CreateLightDefInteractions + +When a lightDef is determined to effect the view (contact the frustum and non-0 light), it will check to +make sure that it has interactions for all the entityDefs that it might possibly contact. + +This does not guarantee that all possible interactions for this light are generated, only that +the ones that may effect the current view are generated. so it does need to be called every view. + +This does not cause entityDefs to create dynamic models, all work is done on the referenceBounds. + +All entities that have non-empty interactions with viewLights will +have viewEntities made for them and be put on the viewEntity list, +even if their surfaces aren't visible, because they may need to cast shadows. + +Interactions are usually removed when a entityDef or lightDef is modified, unless the change +is known to not effect them, so there is no danger of getting a stale interaction, we just need to +check that needed ones are created. + +An interaction can be at several levels: + +Don't interact (but share an area) (numSurfaces = 0) +Entity reference bounds touches light frustum, but surfaces haven't been generated (numSurfaces = -1) +Shadow surfaces have been generated, but light surfaces have not. The shadow surface may still be empty due to bounds being conservative. +Both shadow and light surfaces have been generated. Either or both surfaces may still be empty due to conservative bounds. + +================= +*/ +void idRenderWorldLocal::CreateLightDefInteractions( idRenderLightLocal *ldef ) { + areaReference_t *eref; + areaReference_t *lref; + idRenderEntityLocal *edef; + portalArea_t *area; + idInteraction *inter; + + for ( lref = ldef->references ; lref ; lref = lref->ownerNext ) { + area = lref->area; + + // check all the models in this area + for ( eref = area->entityRefs.areaNext ; eref != &area->entityRefs ; eref = eref->areaNext ) { + edef = eref->entity; + + // if the entity doesn't have any light-interacting surfaces, we could skip this, + // but we don't want to instantiate dynamic models yet, so we can't check that on + // most things + + // if the entity isn't viewed + if ( tr.viewDef && edef->viewCount != tr.viewCount ) { + // if the light doesn't cast shadows, skip + if ( !ldef->lightShader->LightCastsShadows() ) { + continue; + } + // if we are suppressing its shadow in this view, skip + if ( !r_skipSuppress.GetBool() ) { + if ( edef->parms.suppressShadowInViewID && edef->parms.suppressShadowInViewID == tr.viewDef->renderView.viewID ) { + continue; + } + if ( edef->parms.suppressShadowInLightID && edef->parms.suppressShadowInLightID == ldef->parms.lightId ) { + continue; + } + } + } + + // some big outdoor meshes are flagged to not create any dynamic interactions + // when the level designer knows that nearby moving lights shouldn't actually hit them + if ( edef->parms.noDynamicInteractions && edef->world->generateAllInteractionsCalled ) { + continue; + } + + // if any of the edef's interaction match this light, we don't + // need to consider it. + if ( r_useInteractionTable.GetBool() && this->interactionTable ) { + // allocating these tables may take several megs on big maps, but it saves 3% to 5% of + // the CPU time. The table is updated at interaction::AllocAndLink() and interaction::UnlinkAndFree() + int index = ldef->index * this->interactionTableWidth + edef->index; + inter = this->interactionTable[ index ]; + if ( inter ) { + // if this entity wasn't in view already, the scissor rect will be empty, + // so it will only be used for shadow casting + if ( !inter->IsEmpty() ) { + R_SetEntityDefViewEntity( edef ); + } + continue; + } + } else { + // scan the doubly linked lists, which may have several dozen entries + + // we could check either model refs or light refs for matches, but it is + // assumed that there will be less lights in an area than models + // so the entity chains should be somewhat shorter (they tend to be fairly close). + for ( inter = edef->firstInteraction; inter != NULL; inter = inter->entityNext ) { + if ( inter->lightDef == ldef ) { + break; + } + } + + // if we already have an interaction, we don't need to do anything + if ( inter != NULL ) { + // if this entity wasn't in view already, the scissor rect will be empty, + // so it will only be used for shadow casting + if ( !inter->IsEmpty() ) { + R_SetEntityDefViewEntity( edef ); + } + continue; + } + } + + // + // create a new interaction, but don't do any work other than bbox to frustum culling + // + idInteraction *inter = idInteraction::AllocAndLink( edef, ldef ); + + // do a check of the entity reference bounds against the light frustum, + // trying to avoid creating a viewEntity if it hasn't been already + float modelMatrix[16]; + float *m; + + if ( edef->viewCount == tr.viewCount ) { + m = edef->viewEntity->modelMatrix; + } else { + R_AxisToModelMatrix( edef->parms.axis, edef->parms.origin, modelMatrix ); + m = modelMatrix; + } + + if ( R_CullLocalBox( edef->referenceBounds, m, 6, ldef->frustum ) ) { + inter->MakeEmpty(); + continue; + } + + // we will do a more precise per-surface check when we are checking the entity + + // if this entity wasn't in view already, the scissor rect will be empty, + // so it will only be used for shadow casting + R_SetEntityDefViewEntity( edef ); + } + } +} + +//=============================================================================================================== + +/* +================= +R_LinkLightSurf +================= +*/ +void R_LinkLightSurf( const drawSurf_t **link, const srfTriangles_t *tri, const viewEntity_t *space, + const idRenderLightLocal *light, const idMaterial *shader, const idScreenRect &scissor, bool viewInsideShadow ) { + drawSurf_t *drawSurf; + + if ( !space ) { + space = &tr.viewDef->worldSpace; + } + + drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ) ); + + drawSurf->geo = tri; + drawSurf->space = space; + drawSurf->material = shader; + drawSurf->scissorRect = scissor; + drawSurf->dsFlags = 0; + if ( viewInsideShadow ) { + drawSurf->dsFlags |= DSF_VIEW_INSIDE_SHADOW; + } + + if ( !shader ) { + // shadows won't have a shader + drawSurf->shaderRegisters = NULL; + } else { + // process the shader expressions for conditionals / color / texcoords + const float *constRegs = shader->ConstantRegisters(); + if ( constRegs ) { + // this shader has only constants for parameters + drawSurf->shaderRegisters = constRegs; + } else { + // FIXME: share with the ambient surface? + float *regs = (float *)R_FrameAlloc( shader->GetNumRegisters() * sizeof( float ) ); + drawSurf->shaderRegisters = regs; + shader->EvaluateRegisters( regs, space->entityDef->parms.shaderParms, tr.viewDef, space->entityDef->parms.referenceSoundHandle ); + } + + // calculate the specular coordinates if we aren't using vertex programs + if ( !tr.backEndRendererHasVertexPrograms && !r_skipSpecular.GetBool() && tr.backEndRenderer != BE_ARB ) { + R_SpecularTexGen( drawSurf, light->globalLightOrigin, tr.viewDef->renderView.vieworg ); + // if we failed to allocate space for the specular calculations, drop the surface + if ( !drawSurf->dynamicTexCoords ) { + return; + } + } + } + + // actually link it in + drawSurf->nextOnLight = *link; + *link = drawSurf; +} + +/* +====================== +R_ClippedLightScissorRectangle +====================== +*/ +idScreenRect R_ClippedLightScissorRectangle( viewLight_t *vLight ) { + int i, j; + const idRenderLightLocal *light = vLight->lightDef; + idScreenRect r; + idFixedWinding w; + + r.Clear(); + + for ( i = 0 ; i < 6 ; i++ ) { + const idWinding *ow = light->frustumWindings[i]; + + // projected lights may have one of the frustums degenerated + if ( !ow ) { + continue; + } + + // the light frustum planes face out from the light, + // so the planes that have the view origin on the negative + // side will be the "back" faces of the light, which must have + // some fragment inside the portalStack to be visible + if ( light->frustum[i].Distance( tr.viewDef->renderView.vieworg ) >= 0 ) { + continue; + } + + w = *ow; + + // now check the winding against each of the frustum planes + for ( j = 0; j < 5; j++ ) { + if ( !w.ClipInPlace( -tr.viewDef->frustum[j] ) ) { + break; + } + } + + // project these points to the screen and add to bounds + for ( j = 0; j < w.GetNumPoints(); j++ ) { + idPlane eye, clip; + idVec3 ndc; + + R_TransformModelToClip( w[j].ToVec3(), tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, eye, clip ); + + if ( clip[3] <= 0.01f ) { + clip[3] = 0.01f; + } + + R_TransformClipToDevice( clip, tr.viewDef, ndc ); + + float windowX = 0.5f * ( 1.0f + ndc[0] ) * ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ); + float windowY = 0.5f * ( 1.0f + ndc[1] ) * ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ); + + if ( windowX > tr.viewDef->scissor.x2 ) { + windowX = tr.viewDef->scissor.x2; + } else if ( windowX < tr.viewDef->scissor.x1 ) { + windowX = tr.viewDef->scissor.x1; + } + if ( windowY > tr.viewDef->scissor.y2 ) { + windowY = tr.viewDef->scissor.y2; + } else if ( windowY < tr.viewDef->scissor.y1 ) { + windowY = tr.viewDef->scissor.y1; + } + + r.AddPoint( windowX, windowY ); + } + } + + // add the fudge boundary + r.Expand(); + + return r; +} + +/* +================== +R_CalcLightScissorRectangle + +The light screen bounds will be used to crop the scissor rect during +stencil clears and interaction drawing +================== +*/ +int c_clippedLight, c_unclippedLight; + +idScreenRect R_CalcLightScissorRectangle( viewLight_t *vLight ) { + idScreenRect r; + srfTriangles_t *tri; + idPlane eye, clip; + idVec3 ndc; + + if ( vLight->lightDef->parms.pointLight ) { + idBounds bounds; + idRenderLightLocal *lightDef = vLight->lightDef; + tr.viewDef->viewFrustum.ProjectionBounds( idBox( lightDef->parms.origin, lightDef->parms.lightRadius, lightDef->parms.axis ), bounds ); + return R_ScreenRectFromViewFrustumBounds( bounds ); + } + + if ( r_useClippedLightScissors.GetInteger() == 2 ) { + return R_ClippedLightScissorRectangle( vLight ); + } + + r.Clear(); + + tri = vLight->lightDef->frustumTris; + for ( int i = 0 ; i < tri->numVerts ; i++ ) { + R_TransformModelToClip( tri->verts[i].xyz, tr.viewDef->worldSpace.modelViewMatrix, + tr.viewDef->projectionMatrix, eye, clip ); + + // if it is near clipped, clip the winding polygons to the view frustum + if ( clip[3] <= 1 ) { + c_clippedLight++; + if ( r_useClippedLightScissors.GetInteger() ) { + return R_ClippedLightScissorRectangle( vLight ); + } else { + r.x1 = r.y1 = 0; + r.x2 = ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ) - 1; + r.y2 = ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ) - 1; + return r; + } + } + + R_TransformClipToDevice( clip, tr.viewDef, ndc ); + + float windowX = 0.5f * ( 1.0f + ndc[0] ) * ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ); + float windowY = 0.5f * ( 1.0f + ndc[1] ) * ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ); + + if ( windowX > tr.viewDef->scissor.x2 ) { + windowX = tr.viewDef->scissor.x2; + } else if ( windowX < tr.viewDef->scissor.x1 ) { + windowX = tr.viewDef->scissor.x1; + } + if ( windowY > tr.viewDef->scissor.y2 ) { + windowY = tr.viewDef->scissor.y2; + } else if ( windowY < tr.viewDef->scissor.y1 ) { + windowY = tr.viewDef->scissor.y1; + } + + r.AddPoint( windowX, windowY ); + } + + // add the fudge boundary + r.Expand(); + + c_unclippedLight++; + + return r; +} + +/* +================= +R_AddLightSurfaces + +Calc the light shader values, removing any light from the viewLight list +if it is determined to not have any visible effect due to being flashed off or turned off. + +Adds entities to the viewEntity list if they are needed for shadow casting. + +Add any precomputed shadow volumes. + +Removes lights from the viewLights list if they are completely +turned off, or completely off screen. + +Create any new interactions needed between the viewLights +and the viewEntitys due to game movement +================= +*/ +void R_AddLightSurfaces( void ) { + viewLight_t *vLight; + idRenderLightLocal *light; + viewLight_t **ptr; + + // go through each visible light, possibly removing some from the list + ptr = &tr.viewDef->viewLights; + while ( *ptr ) { + vLight = *ptr; + light = vLight->lightDef; + + const idMaterial *lightShader = light->lightShader; + if ( !lightShader ) { + common->Error( "R_AddLightSurfaces: NULL lightShader" ); + } + + // see if we are suppressing the light in this view + if ( !r_skipSuppress.GetBool() ) { + if ( light->parms.suppressLightInViewID + && light->parms.suppressLightInViewID == tr.viewDef->renderView.viewID ) { + *ptr = vLight->next; + light->viewCount = -1; + continue; + } + if ( light->parms.allowLightInViewID + && light->parms.allowLightInViewID != tr.viewDef->renderView.viewID ) { + *ptr = vLight->next; + light->viewCount = -1; + continue; + } + } + + // evaluate the light shader registers + float *lightRegs =(float *)R_FrameAlloc( lightShader->GetNumRegisters() * sizeof( float ) ); + vLight->shaderRegisters = lightRegs; + lightShader->EvaluateRegisters( lightRegs, light->parms.shaderParms, tr.viewDef, light->parms.referenceSoundHandle ); + + // if this is a purely additive light and no stage in the light shader evaluates + // to a positive light value, we can completely skip the light + if ( !lightShader->IsFogLight() && !lightShader->IsBlendLight() ) { + int lightStageNum; + for ( lightStageNum = 0 ; lightStageNum < lightShader->GetNumStages() ; lightStageNum++ ) { + const shaderStage_t *lightStage = lightShader->GetStage( lightStageNum ); + + // ignore stages that fail the condition + if ( !lightRegs[ lightStage->conditionRegister ] ) { + continue; + } + + const int *registers = lightStage->color.registers; + + // snap tiny values to zero to avoid lights showing up with the wrong color + if ( lightRegs[ registers[0] ] < 0.001f ) { + lightRegs[ registers[0] ] = 0.0f; + } + if ( lightRegs[ registers[1] ] < 0.001f ) { + lightRegs[ registers[1] ] = 0.0f; + } + if ( lightRegs[ registers[2] ] < 0.001f ) { + lightRegs[ registers[2] ] = 0.0f; + } + + // FIXME: when using the following values the light shows up bright red when using nvidia drivers/hardware + // this seems to have been fixed ? + //lightRegs[ registers[0] ] = 1.5143074e-005f; + //lightRegs[ registers[1] ] = 1.5483369e-005f; + //lightRegs[ registers[2] ] = 1.7014690e-005f; + + if ( lightRegs[ registers[0] ] > 0.0f || + lightRegs[ registers[1] ] > 0.0f || + lightRegs[ registers[2] ] > 0.0f ) { + break; + } + } + if ( lightStageNum == lightShader->GetNumStages() ) { + // we went through all the stages and didn't find one that adds anything + // remove the light from the viewLights list, and change its frame marker + // so interaction generation doesn't think the light is visible and + // create a shadow for it + *ptr = vLight->next; + light->viewCount = -1; + continue; + } + } + + if ( r_useLightScissors.GetBool() ) { + // calculate the screen area covered by the light frustum + // which will be used to crop the stencil cull + idScreenRect scissorRect = R_CalcLightScissorRectangle( vLight ); + // intersect with the portal crossing scissor rectangle + vLight->scissorRect.Intersect( scissorRect ); + + if ( r_showLightScissors.GetBool() ) { + R_ShowColoredScreenRect( vLight->scissorRect, light->index ); + } + } + +#if 0 + // this never happens, because CullLightByPortals() does a more precise job + if ( vLight->scissorRect.IsEmpty() ) { + // this light doesn't touch anything on screen, so remove it from the list + *ptr = vLight->next; + continue; + } +#endif + + // this one stays on the list + ptr = &vLight->next; + + // if we are doing a soft-shadow novelty test, regenerate the light with + // a random offset every time + if ( r_lightSourceRadius.GetFloat() != 0.0f ) { + for ( int i = 0 ; i < 3 ; i++ ) { + light->globalLightOrigin[i] += r_lightSourceRadius.GetFloat() * ( -1 + 2 * (rand()&0xfff)/(float)0xfff ); + } + } + + // create interactions with all entities the light may touch, and add viewEntities + // that may cast shadows, even if they aren't directly visible. Any real work + // will be deferred until we walk through the viewEntities + tr.viewDef->renderWorld->CreateLightDefInteractions( light ); + tr.pc.c_viewLights++; + + // fog lights will need to draw the light frustum triangles, so make sure they + // are in the vertex cache + if ( lightShader->IsFogLight() ) { + if ( !light->frustumTris->ambientCache ) { + if ( !R_CreateAmbientCache( light->frustumTris, false ) ) { + // skip if we are out of vertex memory + continue; + } + } + // touch the surface so it won't get purged + vertexCache.Touch( light->frustumTris->ambientCache ); + } + + // add the prelight shadows for the static world geometry + if ( light->parms.prelightModel && r_useOptimizedShadows.GetBool() ) { + + if ( !light->parms.prelightModel->NumSurfaces() ) { + common->Error( "no surfs in prelight model '%s'", light->parms.prelightModel->Name() ); + } + + srfTriangles_t *tri = light->parms.prelightModel->Surface( 0 )->geometry; + if ( !tri->shadowVertexes ) { + common->Error( "R_AddLightSurfaces: prelight model '%s' without shadowVertexes", light->parms.prelightModel->Name() ); + } + + // these shadows will all have valid bounds, and can be culled normally + if ( r_useShadowCulling.GetBool() ) { + if ( R_CullLocalBox( tri->bounds, tr.viewDef->worldSpace.modelMatrix, 5, tr.viewDef->frustum ) ) { + continue; + } + } + + // if we have been purged, re-upload the shadowVertexes + if ( !tri->shadowCache ) { + R_CreatePrivateShadowCache( tri ); + if ( !tri->shadowCache ) { + continue; + } + } + + // touch the shadow surface so it won't get purged + vertexCache.Touch( tri->shadowCache ); + + if ( !tri->indexCache && r_useIndexBuffers.GetBool() ) { + vertexCache.Alloc( tri->indexes, tri->numIndexes * sizeof( tri->indexes[0] ), &tri->indexCache, true ); + } + if ( tri->indexCache ) { + vertexCache.Touch( tri->indexCache ); + } + + R_LinkLightSurf( &vLight->globalShadows, tri, NULL, light, NULL, vLight->scissorRect, true /* FIXME? */ ); + } + } +} + +//=============================================================================================================== + +/* +================== +R_IssueEntityDefCallback +================== +*/ +bool R_IssueEntityDefCallback( idRenderEntityLocal *def ) { + bool update; + idBounds oldBounds; + + if ( r_checkBounds.GetBool() ) { + oldBounds = def->referenceBounds; + } + + def->archived = false; // will need to be written to the demo file + tr.pc.c_entityDefCallbacks++; + if ( tr.viewDef ) { + update = def->parms.callback( &def->parms, &tr.viewDef->renderView ); + } else { + update = def->parms.callback( &def->parms, NULL ); + } + + if ( !def->parms.hModel ) { + common->Error( "R_IssueEntityDefCallback: dynamic entity callback didn't set model" ); + } + + if ( r_checkBounds.GetBool() ) { + if ( oldBounds[0][0] > def->referenceBounds[0][0] + CHECK_BOUNDS_EPSILON || + oldBounds[0][1] > def->referenceBounds[0][1] + CHECK_BOUNDS_EPSILON || + oldBounds[0][2] > def->referenceBounds[0][2] + CHECK_BOUNDS_EPSILON || + oldBounds[1][0] < def->referenceBounds[1][0] - CHECK_BOUNDS_EPSILON || + oldBounds[1][1] < def->referenceBounds[1][1] - CHECK_BOUNDS_EPSILON || + oldBounds[1][2] < def->referenceBounds[1][2] - CHECK_BOUNDS_EPSILON ) { + common->Printf( "entity %i callback extended reference bounds\n", def->index ); + } + } + + return update; +} + +/* +=================== +R_EntityDefDynamicModel + +Issues a deferred entity callback if necessary. +If the model isn't dynamic, it returns the original. +Returns the cached dynamic model if present, otherwise creates +it and any necessary overlays +=================== +*/ +idRenderModel *R_EntityDefDynamicModel( idRenderEntityLocal *def ) { + bool callbackUpdate; + + // allow deferred entities to construct themselves + if ( def->parms.callback ) { + callbackUpdate = R_IssueEntityDefCallback( def ); + } else { + callbackUpdate = false; + } + + idRenderModel *model = def->parms.hModel; + + if ( !model ) { + common->Error( "R_EntityDefDynamicModel: NULL model" ); + } + + if ( model->IsDynamicModel() == DM_STATIC ) { + def->dynamicModel = NULL; + def->dynamicModelFrameCount = 0; + return model; + } + + // continously animating models (particle systems, etc) will have their snapshot updated every single view + if ( callbackUpdate || ( model->IsDynamicModel() == DM_CONTINUOUS && def->dynamicModelFrameCount != tr.frameCount ) ) { + R_ClearEntityDefDynamicModel( def ); + } + + // if we don't have a snapshot of the dynamic model, generate it now + if ( !def->dynamicModel ) { + + // instantiate the snapshot of the dynamic model, possibly reusing memory from the cached snapshot + def->cachedDynamicModel = model->InstantiateDynamicModel( &def->parms, tr.viewDef, def->cachedDynamicModel ); + + if ( def->cachedDynamicModel ) { + + // add any overlays to the snapshot of the dynamic model + if ( def->overlay && !r_skipOverlays.GetBool() ) { + def->overlay->AddOverlaySurfacesToModel( def->cachedDynamicModel ); + } else { + idRenderModelOverlay::RemoveOverlaySurfacesFromModel( def->cachedDynamicModel ); + } + + if ( r_checkBounds.GetBool() ) { + idBounds b = def->cachedDynamicModel->Bounds(); + if ( b[0][0] < def->referenceBounds[0][0] - CHECK_BOUNDS_EPSILON || + b[0][1] < def->referenceBounds[0][1] - CHECK_BOUNDS_EPSILON || + b[0][2] < def->referenceBounds[0][2] - CHECK_BOUNDS_EPSILON || + b[1][0] > def->referenceBounds[1][0] + CHECK_BOUNDS_EPSILON || + b[1][1] > def->referenceBounds[1][1] + CHECK_BOUNDS_EPSILON || + b[1][2] > def->referenceBounds[1][2] + CHECK_BOUNDS_EPSILON ) { + common->Printf( "entity %i dynamic model exceeded reference bounds\n", def->index ); + } + } + } + + def->dynamicModel = def->cachedDynamicModel; + def->dynamicModelFrameCount = tr.frameCount; + } + + // set model depth hack value + if ( def->dynamicModel && model->DepthHack() != 0.0f && tr.viewDef ) { + idPlane eye, clip; + idVec3 ndc; + R_TransformModelToClip( def->parms.origin, tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, eye, clip ); + R_TransformClipToDevice( clip, tr.viewDef, ndc ); + def->parms.modelDepthHack = model->DepthHack() * ( 1.0f - ndc.z ); + } + + // FIXME: if any of the surfaces have deforms, create a frame-temporary model with references to the + // undeformed surfaces. This would allow deforms to be light interacting. + + return def->dynamicModel; +} + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const renderEntity_t *renderEntity, + const idMaterial *shader, const idScreenRect &scissor, unsigned int flags ) { + drawSurf_t *drawSurf; + const float *shaderParms; + static float refRegs[MAX_EXPRESSION_REGISTERS]; // don't put on stack, or VC++ will do a page touch + float generatedShaderParms[MAX_ENTITY_SHADER_PARMS]; + + drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ) ); + drawSurf->geo = tri; + drawSurf->space = space; + drawSurf->material = shader; + drawSurf->scissorRect = scissor; + drawSurf->sort = shader->GetSort() + tr.sortOffset; + drawSurf->dsFlags = 0; + drawSurf->texGenTransformAndViewOrg = NULL; + drawSurf->mFlags = flags; + + // bumping this offset each time causes surfaces with equal sort orders to still + // deterministically draw in the order they are added + tr.sortOffset += 0.000001f; + + // if it doesn't fit, resize the list + if ( tr.viewDef->numDrawSurfs == tr.viewDef->maxDrawSurfs ) { + drawSurf_t **old = tr.viewDef->drawSurfs; + int count; + + if ( tr.viewDef->maxDrawSurfs == 0 ) { + tr.viewDef->maxDrawSurfs = INITIAL_DRAWSURFS; + count = 0; + } else { + count = tr.viewDef->maxDrawSurfs * sizeof( tr.viewDef->drawSurfs[0] ); + tr.viewDef->maxDrawSurfs *= 2; + } + tr.viewDef->drawSurfs = (drawSurf_t **)R_FrameAlloc( tr.viewDef->maxDrawSurfs * sizeof( tr.viewDef->drawSurfs[0] ) ); + memcpy( tr.viewDef->drawSurfs, old, count ); + } + tr.viewDef->drawSurfs[tr.viewDef->numDrawSurfs] = drawSurf; + tr.viewDef->numDrawSurfs++; + + // process the shader expressions for conditionals / color / texcoords + const float *constRegs = shader->ConstantRegisters(); + if ( constRegs ) { + // shader only uses constant values + drawSurf->shaderRegisters = constRegs; + } else { + float *regs = (float *)R_FrameAlloc( shader->GetNumRegisters() * sizeof( float ) ); + drawSurf->shaderRegisters = regs; + + // a reference shader will take the calculated stage color value from another shader + // and use that for the parm0-parm3 of the current shader, which allows a stage of + // a light model and light flares to pick up different flashing tables from + // different light shaders + if ( renderEntity->referenceShader ) { + // evaluate the reference shader to find our shader parms + const shaderStage_t *pStage; + + renderEntity->referenceShader->EvaluateRegisters( refRegs, renderEntity->shaderParms, tr.viewDef, renderEntity->referenceSoundHandle ); + pStage = renderEntity->referenceShader->GetStage(0); + + memcpy( generatedShaderParms, renderEntity->shaderParms, sizeof( generatedShaderParms ) ); + generatedShaderParms[0] = refRegs[ pStage->color.registers[0] ]; + generatedShaderParms[1] = refRegs[ pStage->color.registers[1] ]; + generatedShaderParms[2] = refRegs[ pStage->color.registers[2] ]; + + shaderParms = generatedShaderParms; + } else { + // evaluate with the entityDef's shader parms + shaderParms = renderEntity->shaderParms; + } + + shader->EvaluateRegisters( regs, shaderParms, tr.viewDef, renderEntity->referenceSoundHandle ); + } + + // check for deformations + R_DeformDrawSurf( drawSurf ); + + // skybox surfaces need a dynamic texgen + switch( shader->Texgen() ) { + case TG_SKYBOX_CUBE: + R_SkyboxTexGen( drawSurf, tr.viewDef->renderView.vieworg ); + break; + case TG_WOBBLESKY_CUBE: + R_WobbleskyTexGen( drawSurf, tr.viewDef->renderView.vieworg ); + break; + } + + // check for gui surfaces + idUserInterface *gui = NULL; + + if ( !space->entityDef ) { + gui = shader->GlobalGui(); + } else { + int guiNum = shader->GetEntityGui() - 1; + if ( guiNum >= 0 && guiNum < MAX_RENDERENTITY_GUI ) { + gui = renderEntity->gui[ guiNum ]; + } + if ( gui == NULL ) { + gui = shader->GlobalGui(); + } + } + + if ( gui ) { + idBounds ndcBounds; + + if ( !R_PreciseCullSurface( drawSurf, ndcBounds ) ) { + // did we ever use this to forward an entity color to a gui that didn't set color? +// memcpy( tr.guiShaderParms, shaderParms, sizeof( tr.guiShaderParms ) ); + R_RenderGuiSurf( gui, drawSurf ); + } + + } + + // we can't add subviews at this point, because that would + // increment tr.viewCount, messing up the rest of the surface + // adds for this view +} + +/* +=============== +R_AddAmbientDrawsurfs + +Adds surfaces for the given viewEntity +Walks through the viewEntitys list and creates drawSurf_t for each surface of +each viewEntity that has a non-empty scissorRect +=============== +*/ +static void R_AddAmbientDrawsurfs( viewEntity_t *vEntity ) { + int i, total; + idRenderEntityLocal *def; + srfTriangles_t *tri; + idRenderModel *model; + const idMaterial *shader; + + def = vEntity->entityDef; + + if ( def->dynamicModel ) { + model = def->dynamicModel; + } else { + model = def->parms.hModel; + } + + // add all the surfaces + total = model->NumSurfaces(); + for ( i = 0 ; i < total ; i++ ) { + const modelSurface_t *surf = model->Surface( i ); + + // for debugging, only show a single surface at a time + if ( r_singleSurface.GetInteger() >= 0 && i != r_singleSurface.GetInteger() ) { + continue; + } + + tri = surf->geometry; + if ( !tri ) { + continue; + } + if ( !tri->numIndexes ) { + continue; + } + shader = surf->shader; + shader = R_RemapShaderBySkin( shader, def->parms.customSkin, def->parms.customShader ); + + R_GlobalShaderOverride( &shader ); + + if ( !shader ) { + continue; + } + if ( !shader->IsDrawn() ) { + continue; + } + + // debugging tool to make sure we are have the correct pre-calculated bounds + if ( r_checkBounds.GetBool() ) { + int j, k; + for ( j = 0 ; j < tri->numVerts ; j++ ) { + for ( k = 0 ; k < 3 ; k++ ) { + if ( tri->verts[j].xyz[k] > tri->bounds[1][k] + CHECK_BOUNDS_EPSILON + || tri->verts[j].xyz[k] < tri->bounds[0][k] - CHECK_BOUNDS_EPSILON ) { + common->Printf( "bad tri->bounds on %s:%s\n", def->parms.hModel->Name(), shader->GetName() ); + break; + } + if ( tri->verts[j].xyz[k] > def->referenceBounds[1][k] + CHECK_BOUNDS_EPSILON + || tri->verts[j].xyz[k] < def->referenceBounds[0][k] - CHECK_BOUNDS_EPSILON ) { + common->Printf( "bad referenceBounds on %s:%s\n", def->parms.hModel->Name(), shader->GetName() ); + break; + } + } + if ( k != 3 ) { + break; + } + } + } + + if ( !R_CullLocalBox( tri->bounds, vEntity->modelMatrix, 5, tr.viewDef->frustum ) ) { + + def->visibleCount = tr.viewCount; + + // make sure we have an ambient cache + if ( !R_CreateAmbientCache( tri, shader->ReceivesLighting() ) ) { + // don't add anything if the vertex cache was too full to give us an ambient cache + return; + } + // touch it so it won't get purged + vertexCache.Touch( tri->ambientCache ); + + if ( r_useIndexBuffers.GetBool() && !tri->indexCache ) { + vertexCache.Alloc( tri->indexes, tri->numIndexes * sizeof( tri->indexes[0] ), &tri->indexCache, true ); + } + if ( tri->indexCache ) { + vertexCache.Touch( tri->indexCache ); + } + + // add the surface for drawing + R_AddDrawSurf( tri, vEntity, &vEntity->entityDef->parms, shader, vEntity->scissorRect ); + + // ambientViewCount is used to allow light interactions to be rejected + // if the ambient surface isn't visible at all + tri->ambientViewCount = tr.viewCount; + } + } + + // add the lightweight decal surfaces + for ( idRenderModelDecal *decal = def->decals; decal; decal = decal->Next() ) { + decal->AddDecalDrawSurf( vEntity ); + } +} + +/* +================== +R_CalcEntityScissorRectangle +================== +*/ +idScreenRect R_CalcEntityScissorRectangle( viewEntity_t *vEntity ) { + idBounds bounds; + idRenderEntityLocal *def = vEntity->entityDef; + + tr.viewDef->viewFrustum.ProjectionBounds( idBox( def->referenceBounds, def->parms.origin, def->parms.axis ), bounds ); + + return R_ScreenRectFromViewFrustumBounds( bounds ); +} + +/* +=================== +R_AddModelSurfaces + +Here is where dynamic models actually get instantiated, and necessary +interactions get created. This is all done on a sort-by-model basis +to keep source data in cache (most likely L2) as any interactions and +shadows are generated, since dynamic models will typically be lit by +two or more lights. +=================== +*/ +void R_AddModelSurfaces( void ) { + viewEntity_t *vEntity; + idInteraction *inter, *next; + idRenderModel *model; + + // clear the ambient surface list + tr.viewDef->numDrawSurfs = 0; + tr.viewDef->maxDrawSurfs = 0; // will be set to INITIAL_DRAWSURFS on R_AddDrawSurf + + // go through each entity that is either visible to the view, or to + // any light that intersects the view (for shadows) + for ( vEntity = tr.viewDef->viewEntitys; vEntity; vEntity = vEntity->next ) { + + if ( r_useEntityScissors.GetBool() ) { + // calculate the screen area covered by the entity + idScreenRect scissorRect = R_CalcEntityScissorRectangle( vEntity ); + // intersect with the portal crossing scissor rectangle + vEntity->scissorRect.Intersect( scissorRect ); + + if ( r_showEntityScissors.GetBool() ) { + R_ShowColoredScreenRect( vEntity->scissorRect, vEntity->entityDef->index ); + } + } + + // add the ambient surface if it has a visible rectangle + if ( !vEntity->scissorRect.IsEmpty() ) { + model = R_EntityDefDynamicModel( vEntity->entityDef ); + if ( model == NULL || model->NumSurfaces() <= 0 ) { + continue; + } + + R_AddAmbientDrawsurfs( vEntity ); + tr.pc.c_visibleViewEntities++; + } else { + tr.pc.c_shadowViewEntities++; + } + + // + // for all the entity / light interactions on this entity, add them to the view + // + // all empty interactions are at the end of the list so once the + // first is encountered all the remaining interactions are empty. + for ( inter = vEntity->entityDef->firstInteraction; inter != NULL && !inter->IsEmpty(); inter = next ) { + next = inter->entityNext; + if ( inter->lightDef->viewCount != tr.viewCount ) { + continue; + } + inter->AddActiveInteraction(); + } + + } +} + +/* +===================== +R_RemoveUnecessaryViewLights +===================== +*/ +void R_RemoveUnecessaryViewLights( void ) { + viewLight_t *vLight; + + // go through each visible light + for ( vLight = tr.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + // if the light didn't have any lit surfaces visible, there is no need to + // draw any of the shadows. We still keep the vLight for debugging + // draws + if ( !vLight->localInteractions && !vLight->globalInteractions && !vLight->translucentInteractions ) { + vLight->localShadows = NULL; + vLight->globalShadows = NULL; + } + } + + if ( r_useShadowSurfaceScissor.GetBool() ) { + // shrink the light scissor rect to only intersect the surfaces that will actually be drawn. + // This doesn't seem to actually help, perhaps because the surface scissor + // rects aren't actually the surface, but only the portal clippings. + for ( vLight = tr.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + const drawSurf_t *surf; + idScreenRect surfRect; + + if ( !vLight->lightShader->LightCastsShadows() ) { + continue; + } + + surfRect.Clear(); + + for ( surf = vLight->globalInteractions ; surf ; surf = surf->nextOnLight ) { + surfRect.Union( surf->scissorRect ); + } + for ( surf = vLight->localShadows ; surf ; surf = surf->nextOnLight ) { + const_cast(surf)->scissorRect.Intersect( surfRect ); + } + + for ( surf = vLight->localInteractions ; surf ; surf = surf->nextOnLight ) { + surfRect.Union( surf->scissorRect ); + } + for ( surf = vLight->globalShadows ; surf ; surf = surf->nextOnLight ) { + const_cast(surf)->scissorRect.Intersect( surfRect ); + } + + for ( surf = vLight->translucentInteractions ; surf ; surf = surf->nextOnLight ) { + surfRect.Union( surf->scissorRect ); + } + + vLight->scissorRect.Intersect( surfRect ); + } + } +} diff --git a/src/renderer/tr_lightrun.cpp b/src/renderer/tr_lightrun.cpp new file mode 100644 index 0000000..2cc4659 --- /dev/null +++ b/src/renderer/tr_lightrun.cpp @@ -0,0 +1,872 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* + + +Prelight models + +"_prelight_", ie "_prelight_light1" + +Static surfaces available to dmap will be processed to optimized +shadow and lit surface geometry + +Entity models are never prelighted. + +Light entity can have a "noPrelight 1" key set to avoid the preprocessing +and carving of the world. A light that will move should usually have this +set. + +Prelight models will usually have multiple surfaces + +Shadow volume surfaces will have the material "_shadowVolume" + +The exact same vertexes as the ambient surfaces will be used for the +non-shadow surfaces, so there is opportunity to share + + +Reference their parent surfaces? +Reference their parent area? + + +If we don't track parts that are in different areas, there will be huge +losses when an areaportal closed door has a light poking slightly +through it. + +There is potential benefit to splitting even the shadow volumes +at area boundaries, but it would involve the possibility of an +extra plane of shadow drawing at the area boundary. + + +interaction lightName numIndexes + +Shadow volume surface + +Surfaces in the world cannot have "no self shadow" properties, because all +the surfaces are considered together for the optimized shadow volume. If +you want no self shadow on a static surface, you must still make it into an +entity so it isn't considered in the prelight. + + +r_hidePrelights +r_hideNonPrelights + + + +each surface could include prelight indexes + +generation procedure in dmap: + +carve original surfaces into areas + +for each light + build shadow volume and beam tree + cut all potentially lit surfaces into the beam tree + move lit fragments into a new optimize group + +optimize groups + +build light models + + + + +*/ + +/* +================================================================================= + +LIGHT TESTING + +================================================================================= +*/ + + +/* +==================== +R_ModulateLights_f + +Modifies the shaderParms on all the lights so the level +designers can easily test different color schemes +==================== +*/ +void R_ModulateLights_f( const idCmdArgs &args ) { + if ( !tr.primaryWorld ) { + return; + } + if ( args.Argc() != 4 ) { + common->Printf( "usage: modulateLights \n" ); + return; + } + + float modulate[3]; + int i; + for ( i = 0 ; i < 3 ; i++ ) { + modulate[i] = atof( args.Argv( i+1 ) ); + } + + int count = 0; + for ( i = 0 ; i < tr.primaryWorld->lightDefs.Num() ; i++ ) { + idRenderLightLocal *light; + + light = tr.primaryWorld->lightDefs[i]; + if ( light ) { + count++; + for ( int j = 0 ; j < 3 ; j++ ) { + light->parms.shaderParms[j] *= modulate[j]; + } + } + } + common->Printf( "modulated %i lights\n", count ); +} + + + +//====================================================================================== + + +/* +=============== +R_CreateEntityRefs + +Creates all needed model references in portal areas, +chaining them to both the area and the entityDef. + +Bumps tr.viewCount. +=============== +*/ +void R_CreateEntityRefs( idRenderEntityLocal *def ) { + int i; + idVec3 transformed[8]; + idVec3 v; + + if ( !def->parms.hModel ) { + def->parms.hModel = renderModelManager->DefaultModel(); + } + + // if the entity hasn't been fully specified due to expensive animation calcs + // for md5 and particles, use the provided conservative bounds. + if ( def->parms.callback ) { + def->referenceBounds = def->parms.bounds; + } else { + def->referenceBounds = def->parms.hModel->Bounds( &def->parms ); + } + + // some models, like empty particles, may not need to be added at all + if ( def->referenceBounds.IsCleared() ) { + return; + } + + if ( r_showUpdates.GetBool() && + ( def->referenceBounds[1][0] - def->referenceBounds[0][0] > 1024 || + def->referenceBounds[1][1] - def->referenceBounds[0][1] > 1024 ) ) { + common->Printf( "big entityRef: %f,%f\n", def->referenceBounds[1][0] - def->referenceBounds[0][0], + def->referenceBounds[1][1] - def->referenceBounds[0][1] ); + } + + for (i = 0 ; i < 8 ; i++) { + v[0] = def->referenceBounds[i&1][0]; + v[1] = def->referenceBounds[(i>>1)&1][1]; + v[2] = def->referenceBounds[(i>>2)&1][2]; + + R_LocalPointToGlobal( def->modelMatrix, v, transformed[i] ); + } + + // bump the view count so we can tell if an + // area already has a reference + tr.viewCount++; + + // push these points down the BSP tree into areas + def->world->PushVolumeIntoTree( def, NULL, 8, transformed ); +} + + +/* +================================================================================= + +CREATE LIGHT REFS + +================================================================================= +*/ + +/* +===================== +R_SetLightProject + +All values are reletive to the origin +Assumes that right and up are not normalized +This is also called by dmap during map processing. +===================== +*/ +void R_SetLightProject( idPlane lightProject[4], const idVec3 origin, const idVec3 target, + const idVec3 rightVector, const idVec3 upVector, const idVec3 start, const idVec3 stop ) { + float dist; + float scale; + float rLen, uLen; + idVec3 normal; + float ofs; + idVec3 right, up; + idVec3 startGlobal; + idVec4 targetGlobal; + + right = rightVector; + rLen = right.Normalize(); + up = upVector; + uLen = up.Normalize(); + normal = up.Cross( right ); +//normal = right.Cross( up ); + normal.Normalize(); + + dist = target * normal; // - ( origin * normal ); + if ( dist < 0 ) { + dist = -dist; + normal = -normal; + } + + scale = ( 0.5f * dist ) / rLen; + right *= scale; + scale = -( 0.5f * dist ) / uLen; + up *= scale; + + lightProject[2] = normal; + lightProject[2][3] = -( origin * lightProject[2].Normal() ); + + lightProject[0] = right; + lightProject[0][3] = -( origin * lightProject[0].Normal() ); + + lightProject[1] = up; + lightProject[1][3] = -( origin * lightProject[1].Normal() ); + + // now offset to center + targetGlobal.ToVec3() = target + origin; + targetGlobal[3] = 1; + ofs = 0.5f - ( targetGlobal * lightProject[0].ToVec4() ) / ( targetGlobal * lightProject[2].ToVec4() ); + lightProject[0].ToVec4() += ofs * lightProject[2].ToVec4(); + ofs = 0.5f - ( targetGlobal * lightProject[1].ToVec4() ) / ( targetGlobal * lightProject[2].ToVec4() ); + lightProject[1].ToVec4() += ofs * lightProject[2].ToVec4(); + + // set the falloff vector + normal = stop - start; + dist = normal.Normalize(); + if ( dist <= 0 ) { + dist = 1; + } + lightProject[3] = normal * ( 1.0f / dist ); + startGlobal = start + origin; + lightProject[3][3] = -( startGlobal * lightProject[3].Normal() ); +} + +/* +=================== +R_SetLightFrustum + +Creates plane equations from the light projection, positive sides +face out of the light +=================== +*/ +void R_SetLightFrustum( const idPlane lightProject[4], idPlane frustum[6] ) { + int i; + + // we want the planes of s=0, s=q, t=0, and t=q + frustum[0] = lightProject[0]; + frustum[1] = lightProject[1]; + frustum[2] = lightProject[2] - lightProject[0]; + frustum[3] = lightProject[2] - lightProject[1]; + + // we want the planes of s=0 and s=1 for front and rear clipping planes + frustum[4] = lightProject[3]; + + frustum[5] = lightProject[3]; + frustum[5][3] -= 1.0f; + frustum[5] = -frustum[5]; + + for ( i = 0 ; i < 6 ; i++ ) { + float l; + + frustum[i] = -frustum[i]; + l = frustum[i].Normalize(); + frustum[i][3] /= l; + } +} + +/* +==================== +R_FreeLightDefFrustum +==================== +*/ +void R_FreeLightDefFrustum( idRenderLightLocal *ldef ) { + int i; + + // free the frustum tris + if ( ldef->frustumTris ) { + R_FreeStaticTriSurf( ldef->frustumTris ); + ldef->frustumTris = NULL; + } + // free frustum windings + for ( i = 0; i < 6; i++ ) { + if ( ldef->frustumWindings[i] ) { + delete ldef->frustumWindings[i]; + ldef->frustumWindings[i] = NULL; + } + } +} + +/* +================= +R_DeriveLightData + +Fills everything in based on light->parms +================= +*/ +void R_DeriveLightData( idRenderLightLocal *light ) { + int i; + + // decide which light shader we are going to use + if ( light->parms.shader ) { + light->lightShader = light->parms.shader; + } + if ( !light->lightShader ) { + if ( light->parms.pointLight ) { + light->lightShader = declManager->FindMaterial( "lights/defaultPointLight" ); + } else { + light->lightShader = declManager->FindMaterial( "lights/defaultProjectedLight" ); + } + } + + // get the falloff image + light->falloffImage = light->lightShader->LightFalloffImage(); + if ( !light->falloffImage ) { + // use the falloff from the default shader of the correct type + const idMaterial *defaultShader; + + if ( light->parms.pointLight ) { + defaultShader = declManager->FindMaterial( "lights/defaultPointLight" ); + light->falloffImage = defaultShader->LightFalloffImage(); + } else { + // projected lights by default don't diminish with distance + defaultShader = declManager->FindMaterial( "lights/defaultProjectedLight" ); + light->falloffImage = defaultShader->LightFalloffImage(); + } + } + + // set the projection + if ( !light->parms.pointLight ) { + // projected light + + R_SetLightProject( light->lightProject, vec3_origin /* light->parms.origin */, light->parms.target, + light->parms.right, light->parms.up, light->parms.start, light->parms.end); + } else { + // point light + memset( light->lightProject, 0, sizeof( light->lightProject ) ); + light->lightProject[0][0] = 0.5f / light->parms.lightRadius[0]; + light->lightProject[1][1] = 0.5f / light->parms.lightRadius[1]; + light->lightProject[3][2] = 0.5f / light->parms.lightRadius[2]; + light->lightProject[0][3] = 0.5f; + light->lightProject[1][3] = 0.5f; + light->lightProject[2][3] = 1.0f; + light->lightProject[3][3] = 0.5f; + } + + // set the frustum planes + R_SetLightFrustum( light->lightProject, light->frustum ); + + // rotate the light planes and projections by the axis + R_AxisToModelMatrix( light->parms.axis, light->parms.origin, light->modelMatrix ); + + for ( i = 0 ; i < 6 ; i++ ) { + idPlane temp; + temp = light->frustum[i]; + R_LocalPlaneToGlobal( light->modelMatrix, temp, light->frustum[i] ); + } + for ( i = 0 ; i < 4 ; i++ ) { + idPlane temp; + temp = light->lightProject[i]; + R_LocalPlaneToGlobal( light->modelMatrix, temp, light->lightProject[i] ); + } + + // adjust global light origin for off center projections and parallel projections + // we are just faking parallel by making it a very far off center for now + if ( light->parms.parallel ) { + idVec3 dir; + + dir = light->parms.lightCenter; + if ( !dir.Normalize() ) { + // make point straight up if not specified + dir[2] = 1; + } + light->globalLightOrigin = light->parms.origin + dir * 100000; + } else { + light->globalLightOrigin = light->parms.origin + light->parms.axis * light->parms.lightCenter; + } + + R_FreeLightDefFrustum( light ); + + light->frustumTris = R_PolytopeSurface( 6, light->frustum, light->frustumWindings ); + + // a projected light will have one shadowFrustum, a point light will have + // six unless the light center is outside the box + R_MakeShadowFrustums( light ); +} + +/* +================= +R_CreateLightRefs +================= +*/ +#define MAX_LIGHT_VERTS 40 +void R_CreateLightRefs( idRenderLightLocal *light ) { + idVec3 points[MAX_LIGHT_VERTS]; + int i; + srfTriangles_t *tri; + + tri = light->frustumTris; + + // because a light frustum is made of only six intersecting planes, + // we should never be able to get a stupid number of points... + if ( tri->numVerts > MAX_LIGHT_VERTS ) { + common->Error( "R_CreateLightRefs: %i points in frustumTris!", tri->numVerts ); + } + for ( i = 0 ; i < tri->numVerts ; i++ ) { + points[i] = tri->verts[i].xyz; + } + + if ( r_showUpdates.GetBool() && ( tri->bounds[1][0] - tri->bounds[0][0] > 1024 || + tri->bounds[1][1] - tri->bounds[0][1] > 1024 ) ) { + common->Printf( "big lightRef: %f,%f\n", tri->bounds[1][0] - tri->bounds[0][0] + ,tri->bounds[1][1] - tri->bounds[0][1] ); + } + + // determine the areaNum for the light origin, which may let us + // cull the light if it is behind a closed door + // it is debatable if we want to use the entity origin or the center offset origin, + // but we definitely don't want to use a parallel offset origin + light->areaNum = light->world->PointInArea( light->globalLightOrigin ); + if ( light->areaNum == -1 ) { + light->areaNum = light->world->PointInArea( light->parms.origin ); + } + + // bump the view count so we can tell if an + // area already has a reference + tr.viewCount++; + + // if we have a prelight model that includes all the shadows for the major world occluders, + // we can limit the area references to those visible through the portals from the light center. + // We can't do this in the normal case, because shadows are cast from back facing triangles, which + // may be in areas not directly visible to the light projection center. + if ( light->parms.prelightModel && r_useLightPortalFlow.GetBool() && light->lightShader->LightCastsShadows() ) { + light->world->FlowLightThroughPortals( light ); + } else { + // push these points down the BSP tree into areas + light->world->PushVolumeIntoTree( NULL, light, tri->numVerts, points ); + } +} + +/* +=============== +R_RenderLightFrustum + +Called by the editor and dmap to operate on light volumes +=============== +*/ +void R_RenderLightFrustum( const renderLight_t &renderLight, idPlane lightFrustum[6] ) { + idRenderLightLocal fakeLight; + + memset( &fakeLight, 0, sizeof( fakeLight ) ); + fakeLight.parms = renderLight; + + R_DeriveLightData( &fakeLight ); + + R_FreeStaticTriSurf( fakeLight.frustumTris ); + + for ( int i = 0 ; i < 6 ; i++ ) { + lightFrustum[i] = fakeLight.frustum[i]; + } +} + +void idRenderSystemLocal::RenderLightFrustum( const renderLight_t &renderLight, idPlane lightFrustum[6] ) { + R_RenderLightFrustum( renderLight, lightFrustum ); +} + + +//================================================================================= + +/* +=============== +WindingCompletelyInsideLight +=============== +*/ +bool WindingCompletelyInsideLight( const idWinding *w, const idRenderLightLocal *ldef ) { + int i, j; + + for ( i = 0 ; i < w->GetNumPoints() ; i++ ) { + for ( j = 0 ; j < 6 ; j++ ) { + float d; + + d = (*w)[i].ToVec3() * ldef->frustum[j].Normal() + ldef->frustum[j][3]; + if ( d > 0 ) { + return false; + } + } + } + return true; +} + +/* +====================== +R_CreateLightDefFogPortals + +When a fog light is created or moved, see if it completely +encloses any portals, which may allow them to be fogged closed. +====================== +*/ +void R_CreateLightDefFogPortals( idRenderLightLocal *ldef ) { + areaReference_t *lref; + portalArea_t *area; + + ldef->foggedPortals = NULL; + + if ( !ldef->lightShader->IsFogLight() ) { + return; + } + + // some fog lights will explicitly disallow portal fogging + if ( ldef->lightShader->TestMaterialFlag( MF_NOPORTALFOG ) ) { + return; + } + + for ( lref = ldef->references ; lref ; lref = lref->ownerNext ) { + // check all the models in this area + area = lref->area; + + portal_t *prt; + doublePortal_t *dp; + + for ( prt = area->portals ; prt ; prt = prt->next ) { + dp = prt->doublePortal; + + // we only handle a single fog volume covering a portal + // this will never cause incorrect drawing, but it may + // fail to cull a portal + if ( dp->fogLight ) { + continue; + } + + if ( WindingCompletelyInsideLight( prt->w, ldef ) ) { + dp->fogLight = ldef; + dp->nextFoggedPortal = ldef->foggedPortals; + ldef->foggedPortals = dp; + } + } + } +} + +/* +==================== +R_FreeLightDefDerivedData + +Frees all references and lit surfaces from the light +==================== +*/ +void R_FreeLightDefDerivedData( idRenderLightLocal *ldef ) { + areaReference_t *lref, *nextRef; + + // rmove any portal fog references + for ( doublePortal_t *dp = ldef->foggedPortals ; dp ; dp = dp->nextFoggedPortal ) { + dp->fogLight = NULL; + } + + // free all the interactions + while ( ldef->firstInteraction != NULL ) { + ldef->firstInteraction->UnlinkAndFree(); + } + + // free all the references to the light + for ( lref = ldef->references ; lref ; lref = nextRef ) { + nextRef = lref->ownerNext; + + // unlink from the area + lref->areaNext->areaPrev = lref->areaPrev; + lref->areaPrev->areaNext = lref->areaNext; + + // put it back on the free list for reuse + ldef->world->areaReferenceAllocator.Free( lref ); + } + ldef->references = NULL; + + R_FreeLightDefFrustum( ldef ); +} + +/* +=================== +R_FreeEntityDefDerivedData + +Used by both RE_FreeEntityDef and RE_UpdateEntityDef +Does not actually free the entityDef. +=================== +*/ +void R_FreeEntityDefDerivedData( idRenderEntityLocal *def, bool keepDecals, bool keepCachedDynamicModel ) { + int i; + areaReference_t *ref, *next; + + // demo playback needs to free the joints, while normal play + // leaves them in the control of the game + if ( session->readDemo ) { + if ( def->parms.joints ) { + Mem_Free16( def->parms.joints ); + def->parms.joints = NULL; + } + if ( def->parms.callbackData ) { + Mem_Free( def->parms.callbackData ); + def->parms.callbackData = NULL; + } + for ( i = 0; i < MAX_RENDERENTITY_GUI; i++ ) { + if ( def->parms.gui[ i ] ) { + delete def->parms.gui[ i ]; + def->parms.gui[ i ] = NULL; + } + } + } + + // free all the interactions + while ( def->firstInteraction != NULL ) { + def->firstInteraction->UnlinkAndFree(); + } + + // clear the dynamic model if present + if ( def->dynamicModel ) { + def->dynamicModel = NULL; + } + + if ( !keepDecals ) { + R_FreeEntityDefDecals( def ); + R_FreeEntityDefOverlay( def ); + } + + if ( !keepCachedDynamicModel ) { + delete def->cachedDynamicModel; + def->cachedDynamicModel = NULL; + } + + // free the entityRefs from the areas + for ( ref = def->entityRefs ; ref ; ref = next ) { + next = ref->ownerNext; + + // unlink from the area + ref->areaNext->areaPrev = ref->areaPrev; + ref->areaPrev->areaNext = ref->areaNext; + + // put it back on the free list for reuse + def->world->areaReferenceAllocator.Free( ref ); + } + def->entityRefs = NULL; +} + +/* +================== +R_ClearEntityDefDynamicModel + +If we know the reference bounds stays the same, we +only need to do this on entity update, not the full +R_FreeEntityDefDerivedData +================== +*/ +void R_ClearEntityDefDynamicModel( idRenderEntityLocal *def ) { + // free all the interaction surfaces + for( idInteraction *inter = def->firstInteraction; inter != NULL && !inter->IsEmpty(); inter = inter->entityNext ) { + inter->FreeSurfaces(); + } + + // clear the dynamic model if present + if ( def->dynamicModel ) { + def->dynamicModel = NULL; + } +} + +/* +=================== +R_FreeEntityDefDecals +=================== +*/ +void R_FreeEntityDefDecals( idRenderEntityLocal *def ) { + while( def->decals ) { + idRenderModelDecal *next = def->decals->Next(); + idRenderModelDecal::Free( def->decals ); + def->decals = next; + } +} + +/* +=================== +R_FreeEntityDefFadedDecals +=================== +*/ +void R_FreeEntityDefFadedDecals( idRenderEntityLocal *def, int time ) { + def->decals = idRenderModelDecal::RemoveFadedDecals( def->decals, time ); +} + +/* +=================== +R_FreeEntityDefOverlay +=================== +*/ +void R_FreeEntityDefOverlay( idRenderEntityLocal *def ) { + if ( def->overlay ) { + idRenderModelOverlay::Free( def->overlay ); + def->overlay = NULL; + } +} + +/* +=================== +R_FreeDerivedData + +ReloadModels and RegenerateWorld call this +// FIXME: need to do this for all worlds +=================== +*/ +void R_FreeDerivedData( void ) { + int i, j; + idRenderWorldLocal *rw; + idRenderEntityLocal *def; + idRenderLightLocal *light; + + for ( j = 0; j < tr.worlds.Num(); j++ ) { + rw = tr.worlds[j]; + + for ( i = 0; i < rw->entityDefs.Num(); i++ ) { + def = rw->entityDefs[i]; + if ( !def ) { + continue; + } + R_FreeEntityDefDerivedData( def, false, false ); + } + + for ( i = 0; i < rw->lightDefs.Num(); i++ ) { + light = rw->lightDefs[i]; + if ( !light ) { + continue; + } + R_FreeLightDefDerivedData( light ); + } + } +} + +/* +=================== +R_CheckForEntityDefsUsingModel +=================== +*/ +void R_CheckForEntityDefsUsingModel( idRenderModel *model ) { + int i, j; + idRenderWorldLocal *rw; + idRenderEntityLocal *def; + + for ( j = 0; j < tr.worlds.Num(); j++ ) { + rw = tr.worlds[j]; + + for ( i = 0 ; i < rw->entityDefs.Num(); i++ ) { + def = rw->entityDefs[i]; + if ( !def ) { + continue; + } + if ( def->parms.hModel == model ) { + //assert( 0 ); + // this should never happen but Radiant messes it up all the time so just free the derived data + R_FreeEntityDefDerivedData( def, false, false ); + } + } + } +} + +/* +=================== +R_ReCreateWorldReferences + +ReloadModels and RegenerateWorld call this +// FIXME: need to do this for all worlds +=================== +*/ +void R_ReCreateWorldReferences( void ) { + int i, j; + idRenderWorldLocal *rw; + idRenderEntityLocal *def; + idRenderLightLocal *light; + + // let the interaction generation code know this shouldn't be optimized for + // a particular view + tr.viewDef = NULL; + + for ( j = 0; j < tr.worlds.Num(); j++ ) { + rw = tr.worlds[j]; + + for ( i = 0 ; i < rw->entityDefs.Num() ; i++ ) { + def = rw->entityDefs[i]; + if ( !def ) { + continue; + } + // the world model entities are put specifically in a single + // area, instead of just pushing their bounds into the tree + if ( i < rw->numPortalAreas ) { + rw->AddEntityRefToArea( def, &rw->portalAreas[i] ); + } else { + R_CreateEntityRefs( def ); + } + } + + for ( i = 0 ; i < rw->lightDefs.Num() ; i++ ) { + light = rw->lightDefs[i]; + if ( !light ) { + continue; + } + renderLight_t parms = light->parms; + + light->world->FreeLightDef( i ); + rw->UpdateLightDef( i, &parms ); + } + } +} + +/* +=================== +R_RegenerateWorld_f + +Frees and regenerates all references and interactions, which +must be done when switching between display list mode and immediate mode +=================== +*/ +void R_RegenerateWorld_f( const idCmdArgs &args ) { + R_FreeDerivedData(); + + // watch how much memory we allocate + tr.staticAllocCount = 0; + + R_ReCreateWorldReferences(); + + common->Printf( "Regenerated world, staticAllocCount = %i.\n", tr.staticAllocCount ); +} diff --git a/src/renderer/tr_local.h b/src/renderer/tr_local.h new file mode 100644 index 0000000..734363b --- /dev/null +++ b/src/renderer/tr_local.h @@ -0,0 +1,1851 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __TR_LOCAL_H__ +#define __TR_LOCAL_H__ + +#include "Image.h" +#include "MegaTexture.h" + +class idRenderWorldLocal; +class rvBSE; +class rvRenderModelBSE; +struct viewEffect_s; + +// everything that is needed by the backend needs +// to be double buffered to allow it to run in +// parallel on a dual cpu machine +const int SMP_FRAMES = 1; + +const int FALLOFF_TEXTURE_SIZE = 64; + +const float DEFAULT_FOG_DISTANCE = 500.0f; + +const int FOG_ENTER_SIZE = 64; +const float FOG_ENTER = (FOG_ENTER_SIZE+1.0f)/(FOG_ENTER_SIZE*2); +// picky to get the bilerp correct at terminator + + +// idScreenRect gets carried around with each drawSurf, so it makes sense +// to keep it compact, instead of just using the idBounds class +class idScreenRect { +public: + short x1, y1, x2, y2; // inclusive pixel bounds inside viewport + float zmin, zmax; // for depth bounds test + + void Clear(); // clear to backwards values + void AddPoint( float x, float y ); // adds a point + void Expand(); // expand by one pixel each way to fix roundoffs + void Intersect( const idScreenRect &rect ); + void Union( const idScreenRect &rect ); + bool Equals( const idScreenRect &rect ) const; + bool IsEmpty() const; +}; + +idScreenRect R_ScreenRectFromViewFrustumBounds( const idBounds &bounds ); +void R_ShowColoredScreenRect( const idScreenRect &rect, int colorIndex ); + +typedef enum { + DC_BAD, + DC_RENDERVIEW, + DC_UPDATE_ENTITYDEF, + DC_DELETE_ENTITYDEF, + DC_UPDATE_LIGHTDEF, + DC_DELETE_LIGHTDEF, + DC_LOADMAP, + DC_CROP_RENDER, + DC_UNCROP_RENDER, + DC_CAPTURE_RENDER, + DC_END_FRAME, + DC_DEFINE_MODEL, + DC_SET_PORTAL_STATE, + DC_UPDATE_SOUNDOCCLUSION, + DC_GUI_MODEL +} demoCommand_t; + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ + +#include "ModelDecal.h" +#include "ModelOverlay.h" +#include "Interaction.h" + + +// drawSurf_t structures command the back end to render surfaces +// a given srfTriangles_t may be used with multiple viewEntity_t, +// as when viewed in a subview or multiple viewport render, or +// with multiple shaders when skinned, or, possibly with multiple +// lights, although currently each lighting interaction creates +// unique srfTriangles_t + +// drawSurf_t are always allocated and freed every frame, they are never cached +static const int DSF_VIEW_INSIDE_SHADOW = 1; + +typedef struct drawSurf_s { + const srfTriangles_t *geo; + const struct viewEntity_s *space; + const idMaterial *material; // may be NULL for shadow volumes + float sort; // material->sort, modified by gui / entity sort offsets + const float *shaderRegisters; // evaluated and adjusted for referenceShaders + const struct drawSurf_s *nextOnLight; // viewLight chains + idScreenRect scissorRect; // for scissor clipping, local inside renderView viewport + int dsFlags; // DSF_VIEW_INSIDE_SHADOW, etc + struct vertCache_s *dynamicTexCoords; // float * in vertex cache memory + float *texGenTransformAndViewOrg; + unsigned int mFlags; + // specular directions for non vertex program cards, skybox texcoords, etc +} drawSurf_t; + + +typedef struct { + int numPlanes; // this is always 6 for now + idPlane planes[6]; + // positive sides facing inward + // plane 5 is always the plane the projection is going to, the + // other planes are just clip planes + // all planes are in global coordinates + + bool makeClippedPlanes; + // a projected light with a single frustum needs to make sil planes + // from triangles that clip against side planes, but a point light + // that has adjacent frustums doesn't need to +} shadowFrustum_t; + + +// areas have references to hold all the lights and entities in them +typedef struct areaReference_s { + struct areaReference_s *areaNext; // chain in the area + struct areaReference_s *areaPrev; + struct areaReference_s *ownerNext; // chain on either the entityDef or lightDef + idRenderEntityLocal * entity; // only one of entity / light will be non-NULL + idRenderLightLocal * light; // only one of entity / light will be non-NULL + rvRenderEffectLocal * effect; // Quake 4 BSE effect reference + struct portalArea_s * area; // so owners can find all the areas they are in +} areaReference_t; + + +// idRenderLight should become the new public interface replacing the qhandle_t to light defs in the idRenderWorld interface +class idRenderLight { +public: + virtual ~idRenderLight() {} + + virtual void FreeRenderLight() = 0; + virtual void UpdateRenderLight( const renderLight_t *re, bool forceUpdate = false ) = 0; + virtual void GetRenderLight( renderLight_t *re ) = 0; + virtual void ForceUpdate() = 0; + virtual int GetIndex() = 0; +}; + + +// idRenderEntity should become the new public interface replacing the qhandle_t to entity defs in the idRenderWorld interface +class idRenderEntity { +public: + virtual ~idRenderEntity() {} + + virtual void FreeRenderEntity() = 0; + virtual void UpdateRenderEntity( const renderEntity_t *re, bool forceUpdate = false ) = 0; + virtual void GetRenderEntity( renderEntity_t *re ) = 0; + virtual void ForceUpdate() = 0; + virtual int GetIndex() = 0; + + // overlays are extra polygons that deform with animating models for blood and damage marks + virtual void ProjectOverlay( const idPlane localTextureAxis[2], const idMaterial *material ) = 0; + virtual void RemoveDecals() = 0; +}; + +class rvRenderEffect { +public: + virtual ~rvRenderEffect() {} + virtual void FreeRenderEffect() = 0; + virtual void UpdateRenderEffect( const renderEffect_t *effect, bool forceUpdate = false ) = 0; + virtual void ForceUpdate() = 0; + virtual int GetIndex() = 0; +}; + +class rvRenderEffectLocal : public rvRenderEffect { +public: + rvRenderEffectLocal(); + virtual ~rvRenderEffectLocal(); + virtual void FreeRenderEffect(); + virtual void UpdateRenderEffect( const renderEffect_t *effect, bool forceUpdate = false ); + virtual void ForceUpdate(); + virtual int GetIndex(); + + renderEffect_t parms; + rvRenderModelBSE * model; + rvBSE * effect; + int index; + idBounds referenceBounds; + float modelMatrix[16]; + idRenderWorldLocal * world; + int lastModifiedFrameNum; + bool writeToDemo; + bool archived; + idRenderModel * dynamicModel; + int dynamicModelFrameCount; + int viewCount; + viewEffect_s * viewEffect; + int visibleCount; + areaReference_t * effectRefs; +}; + + +class idRenderLightLocal : public idRenderLight { +public: + idRenderLightLocal(); + + virtual void FreeRenderLight(); + virtual void UpdateRenderLight( const renderLight_t *re, bool forceUpdate = false ); + virtual void GetRenderLight( renderLight_t *re ); + virtual void ForceUpdate(); + virtual int GetIndex(); + + renderLight_t parms; // specification + + bool lightHasMoved; // the light has changed its position since it was + // first added, so the prelight model is not valid + + float modelMatrix[16]; // this is just a rearrangement of parms.axis and parms.origin + + idRenderWorldLocal * world; + int index; // in world lightdefs + + int areaNum; // if not -1, we may be able to cull all the light's + // interactions if !viewDef->connectedAreas[areaNum] + + int lastModifiedFrameNum; // to determine if it is constantly changing, + // and should go in the dynamic frame memory, or kept + // in the cached memory + bool archived; // for demo writing + + + // derived information + idPlane lightProject[4]; + + const idMaterial * lightShader; // guaranteed to be valid, even if parms.shader isn't + idImage * falloffImage; + + idVec3 globalLightOrigin; // accounting for lightCenter and parallel + + + idPlane frustum[6]; // in global space, positive side facing out, last two are front/back + idWinding * frustumWindings[6]; // used for culling + srfTriangles_t * frustumTris; // triangulated frustumWindings[] + + int numShadowFrustums; // one for projected lights, usually six for point lights + shadowFrustum_t shadowFrustums[6]; + + int viewCount; // if == tr.viewCount, the light is on the viewDef->viewLights list + struct viewLight_s * viewLight; + + areaReference_t * references; // each area the light is present in will have a lightRef + idInteraction * firstInteraction; // doubly linked list + idInteraction * lastInteraction; + + struct doublePortal_s * foggedPortals; +}; + + +class idRenderEntityLocal : public idRenderEntity { +public: + idRenderEntityLocal(); + + virtual void FreeRenderEntity(); + virtual void UpdateRenderEntity( const renderEntity_t *re, bool forceUpdate = false ); + virtual void GetRenderEntity( renderEntity_t *re ); + virtual void ForceUpdate(); + virtual int GetIndex(); + + // overlays are extra polygons that deform with animating models for blood and damage marks + virtual void ProjectOverlay( const idPlane localTextureAxis[2], const idMaterial *material ); + virtual void RemoveDecals(); + + renderEntity_t parms; + + float modelMatrix[16]; // this is just a rearrangement of parms.axis and parms.origin + + idRenderWorldLocal * world; + int index; // in world entityDefs + + int lastModifiedFrameNum; // to determine if it is constantly changing, + // and should go in the dynamic frame memory, or kept + // in the cached memory + bool archived; // for demo writing + + idRenderModel * dynamicModel; // if parms.model->IsDynamicModel(), this is the generated data + int dynamicModelFrameCount; // continuously animating dynamic models will recreate + // dynamicModel if this doesn't == tr.viewCount + idRenderModel * cachedDynamicModel; + + idBounds referenceBounds; // the local bounds used to place entityRefs, either from parms or a model + + // a viewEntity_t is created whenever a idRenderEntityLocal is considered for inclusion + // in a given view, even if it turns out to not be visible + int viewCount; // if tr.viewCount == viewCount, viewEntity is valid, + // but the entity may still be off screen + struct viewEntity_s * viewEntity; // in frame temporary memory + + int visibleCount; + // if tr.viewCount == visibleCount, at least one ambient + // surface has actually been added by R_AddAmbientDrawsurfs + // note that an entity could still be in the view frustum and not be visible due + // to portal passing + + idRenderModelDecal * decals; // chain of decals that have been projected on this model + idRenderModelOverlay * overlay; // blood overlays on animated models + + areaReference_t * entityRefs; // chain of all references + idInteraction * firstInteraction; // doubly linked list + idInteraction * lastInteraction; + + bool needsPortalSky; +}; + + +// viewLights are allocated on the frame temporary stack memory +// a viewLight contains everything that the back end needs out of an idRenderLightLocal, +// which the front end may be modifying simultaniously if running in SMP mode. +// a viewLight may exist even without any surfaces, and may be relevent for fogging, +// but should never exist if its volume does not intersect the view frustum +typedef struct viewLight_s { + struct viewLight_s * next; + + // back end should NOT reference the lightDef, because it can change when running SMP + idRenderLightLocal * lightDef; + + // for scissor clipping, local inside renderView viewport + // scissorRect.Empty() is true if the viewEntity_t was never actually + // seen through any portals + idScreenRect scissorRect; + + // if the view isn't inside the light, we can use the non-reversed + // shadow drawing, avoiding the draws of the front and rear caps + bool viewInsideLight; + + // true if globalLightOrigin is inside the view frustum, even if it may + // be obscured by geometry. This allows us to skip shadows from non-visible objects + bool viewSeesGlobalLightOrigin; + + // if !viewInsideLight, the corresponding bit for each of the shadowFrustum + // projection planes that the view is on the negative side of will be set, + // allowing us to skip drawing the projected caps of shadows if we can't see the face + int viewSeesShadowPlaneBits; + + idVec3 globalLightOrigin; // global light origin used by backend + idPlane lightProject[4]; // light project used by backend + idPlane fogPlane; // fog plane for backend fog volume rendering + const srfTriangles_t * frustumTris; // light frustum for backend fog volume rendering + const idMaterial * lightShader; // light shader used by backend + const float * shaderRegisters; // shader registers used by backend + idImage * falloffImage; // falloff image used by backend + + const struct drawSurf_s *globalShadows; // shadow everything + const struct drawSurf_s *localInteractions; // don't get local shadows + const struct drawSurf_s *localShadows; // don't shadow local Surfaces + const struct drawSurf_s *globalInteractions; // get shadows from everything + const struct drawSurf_s *translucentInteractions; // get shadows from everything +} viewLight_t; + + +// a viewEntity is created whenever a idRenderEntityLocal is considered for inclusion +// in the current view, but it may still turn out to be culled. +// viewEntity are allocated on the frame temporary stack memory +// a viewEntity contains everything that the back end needs out of a idRenderEntityLocal, +// which the front end may be modifying simultaniously if running in SMP mode. +// A single entityDef can generate multiple viewEntity_t in a single frame, as when seen in a mirror +typedef struct viewEntity_s { + struct viewEntity_s *next; + + // back end should NOT reference the entityDef, because it can change when running SMP + idRenderEntityLocal *entityDef; + + // for scissor clipping, local inside renderView viewport + // scissorRect.Empty() is true if the viewEntity_t was never actually + // seen through any portals, but was created for shadow casting. + // a viewEntity can have a non-empty scissorRect, meaning that an area + // that it is in is visible, and still not be visible. + idScreenRect scissorRect; + + union { + int weaponDepthHackInViewID; + bool weaponDepthHack; // transitional source alias; occupies the target's four-byte slot + }; + float modelDepthHack; + + float modelMatrix[16]; // local coords to global coords + float modelViewMatrix[16]; // local coords to eye coords + float distanceToCamera; + float screenCoverage; +} viewEntity_t; + + +const int MAX_CLIP_PLANES = 1; // we may expand this to six for some subview issues + +// viewDefs are allocated on the frame temporary stack memory +typedef struct viewDef_s { + // specified in the call to DrawScene() + renderView_t renderView; + + float projectionMatrix[16]; + viewEntity_t worldSpace; + + idRenderWorldLocal *renderWorld; + + float floatTime; + + idVec3 initialViewAreaOrigin; + // Used to find the portalArea that view flooding will take place from. + // for a normal view, the initialViewOrigin will be renderView.viewOrg, + // but a mirror may put the projection origin outside + // of any valid area, or in an unconnected area of the map, so the view + // area must be based on a point just off the surface of the mirror / subview. + // It may be possible to get a failed portal pass if the plane of the + // mirror intersects a portal, and the initialViewAreaOrigin is on + // a different side than the renderView.viewOrg is. + + bool isSubview; // true if this view is not the main view + bool isMirror; // the portal is a mirror, invert the face culling + bool isXraySubview; + int renderFlags; + + int numClipPlanes; // mirrors will often use a single clip plane + idPlane clipPlanes[MAX_CLIP_PLANES]; // in world space, the positive side + // of the plane is the visible side + idScreenRect viewport; // in real pixels and proper Y flip + + idScreenRect scissor; + // for scissor clipping, local inside renderView viewport + // subviews may only be rendering part of the main view + // these are real physical pixel values, possibly scaled and offset from the + // renderView x/y/width/height + + struct viewDef_s * superView; // never go into an infinite subview loop + struct drawSurf_s * subviewSurface; + + // drawSurfs are the visible surfaces of the viewEntities, sorted + // by the material sort parameter + drawSurf_t ** drawSurfs; // we don't use an idList for this, because + int numDrawSurfs; // it is allocated in frame temporary memory + int maxDrawSurfs; // may be resized + + struct viewLight_s *viewLights; // chain of all viewLights effecting view + struct viewEntity_s *viewEntitys; // chain of all viewEntities effecting view, including off screen ones casting shadows + // we use viewEntities as a check to see if a given view consists solely + // of 2D rendering, which we can optimize in certain ways. A 2D view will + // not have any viewEntities + + idPlane frustum[5]; // positive sides face outward, [4] is the front clip plane + idFrustum viewFrustum; + + int areaNum; // -1 = not in a valid area + + bool * connectedAreas; + // An array in frame temporary memory that lists if an area can be reached without + // crossing a closed door. This is used to avoid drawing interactions + // when the light is behind a closed door. + +} viewDef_t; + + +// complex light / surface interactions are broken up into multiple passes of a +// simple interaction shader +typedef struct drawInteraction_s { + const drawSurf_t * surf; + + idImage * lightImage; + idImage * lightFalloffImage; + idImage * bumpImage; + idImage * diffuseImage; + idImage * specularImage; + + idVec4 diffuseColor; // may have a light color baked into it, will be < tr.backEndRendererMaxLight + idVec4 specularColor; // may have a light color baked into it, will be < tr.backEndRendererMaxLight + stageVertexColor_t vertexColor; // applies to both diffuse and specular + + int ambientLight; // use tr.ambientNormalMap instead of normalization cube map + // (not a bool just to avoid an uninitialized memory check of the pad region by valgrind) + + // these are loaded into the vertex program + idVec4 localLightOrigin; + idVec4 localViewOrigin; + idVec4 lightProjection[4]; // in local coordinates, possibly with a texture matrix baked in + idVec4 bumpMatrix[2]; + idVec4 diffuseMatrix[2]; + idVec4 specularMatrix[2]; +} drawInteraction_t; + + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +TR_CMDS + +============================================================= +*/ + +typedef enum { + RC_NOP, + RC_DRAW_DEPTH_TEXTURE, + RC_DRAW_VIEW, + RC_SET_BUFFER, + RC_COPY_RENDER, + RC_SWAP_BUFFERS // can't just assume swap at end of list because + // of forced list submission before syncs +} renderCommand_t; + +typedef struct { + renderCommand_t commandId, *next; +} emptyCommand_t; + +typedef struct { + renderCommand_t commandId, *next; + GLenum buffer; + int frameCount; +} setBufferCommand_t; + +typedef struct drawSurfsCommand_s { + renderCommand_t commandId, *next; + viewDef_t *viewDef; +} drawSurfsCommand_t; + +typedef struct { + renderCommand_t commandId, *next; + int x, y, imageWidth, imageHeight; + idImage *image; + int cubeFace; // when copying to a cubeMap +} copyRenderCommand_t; + + +//======================================================================= + +// this is the inital allocation for max number of drawsurfs +// in a given view, but it will automatically grow if needed +const int INITIAL_DRAWSURFS = 0x4000; + +// a request for frame memory will never fail +// (until malloc fails), but it may force the +// allocation of a new memory block that will +// be discontinuous with the existing memory +typedef struct frameMemoryBlock_s { + struct frameMemoryBlock_s *next; + int size; + int used; + int poop; // so that base is 16 byte aligned + byte base[4]; // dynamically allocated as [size] +} frameMemoryBlock_t; + +// all of the information needed by the back end must be +// contained in a frameData_t. This entire structure is +// duplicated so the front and back end can run in parallel +// on an SMP machine (OBSOLETE: this capability has been removed) +typedef struct { + // one or more blocks of memory for all frame + // temporary allocations + frameMemoryBlock_t *memory; + + // alloc will point somewhere into the memory chain + frameMemoryBlock_t *alloc; + + srfTriangles_t * firstDeferredFreeTriSurf; + srfTriangles_t * lastDeferredFreeTriSurf; + + int memoryHighwater; // max used on any frame + + // the currently building command list + // commands can be inserted at the front if needed, as for required + // dynamically generated textures + emptyCommand_t *cmdHead, *cmdTail; // may be of other command type based on commandId +} frameData_t; + +extern frameData_t *frameData; + +//======================================================================= + +void R_LockSurfaceScene( viewDef_t *parms ); +void R_ClearCommandChain( void ); +void R_AddDrawViewCmd( viewDef_t *parms ); + +void R_ReloadGuis_f( const idCmdArgs &args ); +void R_ListGuis_f( const idCmdArgs &args ); + +void *R_GetCommandBuffer( int bytes ); + +// this allows a global override of all materials +bool R_GlobalShaderOverride( const idMaterial **shader ); + +// this does various checks before calling the idDeclSkin +const idMaterial *R_RemapShaderBySkin( const idMaterial *shader, const idDeclSkin *customSkin, const idMaterial *customShader ); + + +//==================================================== + + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_in, c_sphere_cull_clip, c_sphere_cull_out; + int c_box_cull_in, c_box_cull_out; + int c_createInteractions; // number of calls to idInteraction::CreateInteraction + int c_createLightTris; + int c_createShadowVolumes; + int c_generateMd5; + int c_generateMd5r; + int c_entityDefCallbacks; + int c_alloc, c_free; // counts for R_StaticAllc/R_StaticFree + int c_visibleViewEntities; + int c_shadowViewEntities; + int c_viewLights; + int c_numViews; // number of total views rendered + int c_deformedSurfaces; // idMD5Mesh::GenerateSurface + int c_deformedVerts; // idMD5Mesh::GenerateSurface + int c_deformedIndexes; // idMD5Mesh::GenerateSurface + int c_tangentIndexes; // R_DeriveTangents() + int c_entityUpdates; + int c_entityReferences; + int c_entityPushes; + int c_entityPushesSuppressed; + int c_lightUpdates; + int c_lightReferences; + int c_lightPushes; + int c_lightPushesSuppressed; + int c_effectUpdates; + int c_effectReferences; + int c_effectPushes; + int c_effectPushesSuppressed; + int c_visibleViewEffects; + int c_culledViewEffects; + int c_guiSurfs; + int frontEndMsec; // sum of time in all RE_RenderScene's in a frame + int c_numDecalIndexes; + int c_numVolumePushes; +} performanceCounters_t; + + +typedef struct { + int current2DMap; + int current3DMap; + int currentCubeMap; + int texEnv; + textureType_t textureType; +} tmu_t; + +const int MAX_MULTITEXTURE_UNITS = 8; +typedef struct { + tmu_t tmu[MAX_MULTITEXTURE_UNITS]; + int currenttmu; + + int faceCulling; + int glStateBits; + bool forceGlState; // the next GL_State will ignore glStateBits and set everything + bool polyOffset; + float polyOffsetFactor; + float polyOffsetUnits; + unsigned int vertexAttribStateBits; +} glstate_t; + + +typedef struct { + int c_surfaces; + int c_shaders; + int c_vertexes; + int c_indexes; // one set per pass + int c_totalIndexes; // counting all passes + + int c_drawElements; + int c_drawIndexes; + int c_drawVertexes; + int c_drawRefIndexes; + int c_drawRefVertexes; + + int c_shadowElements; + int c_shadowIndexes; + int c_shadowVertexes; + + int c_vboIndexes; + float c_overDraw; + int c_renderPolysTraceTested; + + float maxLightValue; // for light scale + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is separated +// from the front end state +typedef struct { + int frameCount; // used to track all images used in a frame + const viewDef_t * viewDef; + float projectionMatrix[16]; + float modelViewProjection[16]; + const viewEntity_t *mvpSpace; + backEndCounters_t pc; + + const viewEntity_t *currentSpace; // for detecting when a matrix must change + idScreenRect currentScissor; + // for scissor clipping, local inside renderView viewport + + viewLight_t * vLight; + int depthFunc; // GLS_DEPTHFUNC_EQUAL, or GLS_DEPTHFUNC_LESS for translucent + float lightTextureMatrix[16]; // only if lightStage->texture.hasMatrix + float lightColor[4]; // evaluation of current light's color stage + + float lightScale; // Every light color calaculation will be multiplied by this, + // which will guarantee that the result is < tr.backEndRendererMaxLight + // A card with high dynamic range will have this set to 1.0 + float overBright; // The amount that all light interactions must be multiplied by + // with post processing to get the desired total light level. + // A high dynamic range card will have this set to 1.0. + + bool currentRenderNeeded; + bool currentRenderCopied; // true if any material has already referenced _currentRender + + // our OpenGL state deltas + glstate_t glState; + + int c_copyFrameBuffer; +} backEndState_t; + + +const int MAX_GUI_SURFACES = 1024; // default size of the drawSurfs list for guis, will + // be automatically expanded as needed + +typedef enum { + BE_ARB, + BE_NV10, + BE_NV20, + BE_R200, + BE_ARB2, + BE_BAD +} backEndName_t; + +typedef enum { + BENCHMARK_NONE, + BENCHMARK_LIGHTS +} EBenchmarkType; + +typedef struct { + int x, y, width, height; // these are in physical, OpenGL Y-at-bottom pixels +} renderCrop_t; +static const int MAX_RENDER_CROPS = 8; + +/* +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +class idRenderSystemLocal : public idRenderSystem { +public: + // external functions + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void InitOpenGL( void ); + virtual void ShutdownOpenGL( void ); + virtual bool IsOpenGLRunning( void ) const; + virtual void GetValidModes( idStr &mode4x3Text, idStr &mode4x3Values, idStr &mode16x9Text, idStr &mode16x9Values, + idStr &mode16x10Text, idStr &mode16x10Values ); + virtual bool IsFullScreen( void ) const; + virtual int GetScreenWidth( void ) const; + virtual int GetScreenHeight( void ) const; + virtual idRenderWorld * AllocRenderWorld( void ); + virtual void FreeRenderWorld( idRenderWorld *rw ); + virtual void RemoveAllModelReferences( idRenderModel *model ); + virtual void BeginLevelLoad( void ); + virtual void EndLevelLoad( void ); + virtual void ExportMD5R( bool compressed ); + virtual void CopyPrimBatchTriangles( idDrawVert *destDrawVerts, glIndex_t *destIndices, rvMesh *primBatchMesh, const rvSilTraceVertT *silTraceVerts ); + virtual void TrackTextureUsage( TextureTrackCommand command, int frametime = 0, const char *name = NULL ); + virtual bool RegisterFont( const char *fontName, fontInfoEx_t &font ); + virtual void SetColor( const idVec4 &rgba ); + virtual void SetColor4( float r, float g, float b, float a ); + virtual void DrawStretchPic ( const idDrawVert *verts, const glIndex_t *indexes, int vertCount, int indexCount, const idMaterial *material, + bool clip = true ); + virtual void DrawStretchPic ( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ); + virtual void DrawStretchCopy( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *material ); + + virtual void DrawStretchTri ( idVec2 p1, idVec2 p2, idVec2 p3, idVec2 t1, idVec2 t2, idVec2 t3, const idMaterial *material ); + virtual void GlobalToNormalizedDeviceCoordinates( const idVec3 &global, idVec3 &ndc ); + virtual void GetGLSettings( int& width, int& height ); + virtual void PrintMemInfo( MemInfo *mi ); + + virtual void DrawTinyChar( int x, int y, int ch, const idMaterial *material ); + virtual void DrawTinyStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ); + virtual void DrawSmallChar( int x, int y, int ch, const idMaterial *material ); + virtual void DrawSmallStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ); + virtual void DrawBigChar( int x, int y, int ch, const idMaterial *material ); + virtual void DrawBigStringExt( int x, int y, const char *string, const idVec4 &setColor, bool forceColor, const idMaterial *material ); + virtual void WriteDemoPics(); + virtual void DrawDemoPics(); + virtual void BeginFrame( int windowWidth, int windowHeight ); + virtual void BeginFrame( viewDef_t *viewDef, int windowWidth, int windowHeight ); + virtual void RenderLightFrustum( const renderLight_t &renderLight, idPlane lightFrustum[6] ); + virtual void LightProjectionMatrix( const idVec3 &origin, const idPlane &rearPlane, idVec4 mat[4] ); + virtual void ToggleSmpFrame( void ); + virtual void SetSpecialEffect( ESpecialEffectType which, bool enabled ); + virtual void SetSpecialEffectParm( ESpecialEffectType which, int parm, float value ); + virtual void ShutdownSpecialEffects( void ); + virtual void EndFrame( int *frontEndMsec = NULL, int *backEndMsec = NULL, int *numVerts = NULL, int *numIndexes = NULL ); + virtual void TakeJPGScreenshot( int width, int height, const char *fileName, int blends, renderView_t *ref, const char *basePath = "fs_savepath" ); + virtual void TakeScreenshot( int width, int height, const char *fileName, int blends, renderView_t *ref, const char *basePath = "fs_savepath" ); + virtual void CropRenderSize( int width, int height, bool makePowerOfTwo = false, bool forceDimensions = false ); + virtual void CaptureRenderToImage( const char *imageName ); + virtual void CaptureRenderToMemory( void *buffer ); + virtual void CaptureRenderToFile( const char *fileName, bool fixAlpha ); + virtual void UnCrop(); + virtual void GetCardCaps( bool &oldCard, bool &nv10or20 ); + virtual bool UploadImage( const char *imageName, const byte *data, int width, int height ); + virtual void DebugGraph( float cur, float min, float max, const idVec4 &color ); + virtual void ShowDebugGraph( void ); + +public: + // internal functions + idRenderSystemLocal( void ); + ~idRenderSystemLocal( void ); + + void Clear( void ); + void SetBackEndRenderer(); // sets tr.backEndRenderer based on cvars + void RenderViewToViewport( const renderView_t *renderView, idScreenRect *viewport ); + +public: + // renderer globals + bool registered; // cleared at shutdown, set at InitOpenGL + + bool takingScreenshot; + + int frameCount; // incremented every frame + int viewCount; // incremented every view (twice a scene if subviewed) + // and every R_MarkFragments call + + int staticAllocCount; // running total of bytes allocated + + float frameShaderTime; // shader time for all non-world 2D rendering + int smpFrame; + float deltaTime; + EBenchmarkType mBenchmarkMode; + performanceCounters_t mSavePC; + idStr lastLoadedImage; + + int viewportOffset[2]; // for doing larger-than-window tiled renderings + int tiledViewport[2]; + + // determines which back end to use, and if vertex programs are in use + backEndName_t backEndRenderer; + bool backEndRendererHasVertexPrograms; + float backEndRendererMaxLight; // 1.0 for standard, unlimited for floats + // determines how much overbrighting needs + // to be done post-process + + idVec4 ambientLightVector; // used for "ambient bump mapping" + + float sortOffset; // for determinist sorting of equal sort materials + + idListworlds; + + idRenderWorldLocal * primaryWorld; + renderView_t primaryRenderView; + viewDef_t * primaryView; + // many console commands need to know which world they should operate on + + const idMaterial * defaultMaterial; + idImage * testImage; + idCinematic * testVideo; + unsigned char debugGraph[4][256][64]; + const idMaterial * debugGraphMaterial; + idCinematic * testStandaloneVideo; + float testVideoStartTime; + + viewDef_t * viewDef; + + performanceCounters_t pc; // performance counters + + drawSurfsCommand_t lockSurfacesCmd; // use this when r_lockSurfaces = 1 + + viewEntity_t identitySpace; // can use if we don't know viewDef->worldSpace is valid + + int stencilIncr, stencilDecr; // GL_INCR / INCR_WRAP_EXT, GL_DECR / GL_DECR_EXT + + renderCrop_t renderCrops[MAX_RENDER_CROPS]; + int currentRenderCrop; + + // GUI drawing variables for surface creation + int guiRecursionLevel; // to prevent infinite overruns + class idGuiModel * guiModel; + class idGuiModel * demoGuiModel; + class idGuiModel * guiOverlayModel; + + unsigned int specialEffectsEnabled; + + unsigned short gammaTable[256]; // brightness / gamma modify this +}; + +extern backEndState_t backEnd; +extern idRenderSystemLocal tr; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init +extern FILE * qglLogFile; + + +// +// cvars +// +extern idCVar r_ext_vertex_array_range; + +extern idCVar r_glDriver; // "opengl32", etc +extern idCVar r_mode; // video mode number +extern idCVar r_displayRefresh; // optional display refresh rate option for vid mode +extern idCVar r_fullscreen; // 0 = windowed, 1 = full screen +extern idCVar r_multiSamples; // number of antialiasing samples + +extern idCVar r_ignore; // used for random debugging without defining new vars +extern idCVar r_ignore2; // used for random debugging without defining new vars +extern idCVar r_znear; // near Z clip plane + +extern idCVar r_finish; // force a call to glFinish() every frame +extern idCVar r_frontBuffer; // draw to front buffer for debugging +extern idCVar r_swapInterval; // changes wglSwapIntarval +extern idCVar r_offsetFactor; // polygon offset parameter +extern idCVar r_offsetUnits; // polygon offset parameter +extern idCVar r_singleTriangle; // only draw a single triangle per primitive +extern idCVar r_logFile; // number of frames to emit GL logs +extern idCVar r_clear; // force screen clear every frame +extern idCVar r_shadows; // enable shadows +extern idCVar r_subviewOnly; // 1 = don't render main view, allowing subviews to be debugged +extern idCVar r_lightScale; // all light intensities are multiplied by this, which is normally 2 +extern idCVar r_flareSize; // scale the flare deforms from the material def + +extern idCVar r_gamma; // changes gamma tables +extern idCVar r_brightness; // changes gamma tables + +extern idCVar r_renderer; // arb, nv10, nv20, r200, gl2, etc + +extern idCVar r_cgVertexProfile; // arbvp1, vp20, vp30 +extern idCVar r_cgFragmentProfile; // arbfp1, fp30 + +extern idCVar r_checkBounds; // compare all surface bounds with precalculated ones + +extern idCVar r_useNV20MonoLights; // 1 = allow an interaction pass optimization +extern idCVar r_useLightPortalFlow; // 1 = do a more precise area reference determination +extern idCVar r_useTripleTextureARB; // 1 = cards with 3+ texture units do a two pass instead of three pass +extern idCVar r_useShadowSurfaceScissor;// 1 = scissor shadows by the scissor rect of the interaction surfaces +extern idCVar r_useConstantMaterials; // 1 = use pre-calculated material registers if possible +extern idCVar r_useInteractionTable; // create a full entityDefs * lightDefs table to make finding interactions faster +extern idCVar r_useNodeCommonChildren; // stop pushing reference bounds early when possible +extern idCVar r_useSilRemap; // 1 = consider verts with the same XYZ, but different ST the same for shadows +extern idCVar r_useCulling; // 0 = none, 1 = sphere, 2 = sphere + box +extern idCVar r_useLightCulling; // 0 = none, 1 = box, 2 = exact clip of polyhedron faces +extern idCVar r_useLightScissors; // 1 = use custom scissor rectangle for each light +extern idCVar r_useClippedLightScissors;// 0 = full screen when near clipped, 1 = exact when near clipped, 2 = exact always +extern idCVar r_useEntityCulling; // 0 = none, 1 = box +extern idCVar r_useEntityScissors; // 1 = use custom scissor rectangle for each entity +extern idCVar r_useInteractionCulling; // 1 = cull interactions +extern idCVar r_useInteractionScissors; // 1 = use a custom scissor rectangle for each interaction +extern idCVar r_useFrustumFarDistance; // if != 0 force the view frustum far distance to this distance +extern idCVar r_useShadowCulling; // try to cull shadows from partially visible lights +extern idCVar r_usePreciseTriangleInteractions; // 1 = do winding clipping to determine if each ambiguous tri should be lit +extern idCVar r_useTurboShadow; // 1 = use the infinite projection with W technique for dynamic shadows +extern idCVar r_useExternalShadows; // 1 = skip drawing caps when outside the light volume +extern idCVar r_useOptimizedShadows; // 1 = use the dmap generated static shadow volumes +extern idCVar r_useShadowVertexProgram; // 1 = do the shadow projection in the vertex program on capable cards +extern idCVar r_useShadowProjectedCull; // 1 = discard triangles outside light volume before shadowing +extern idCVar r_useDeferredTangents; // 1 = don't always calc tangents after deform +extern idCVar r_useCachedDynamicModels; // 1 = cache snapshots of dynamic models +extern idCVar r_useTwoSidedStencil; // 1 = do stencil shadows in one pass with different ops on each side +extern idCVar r_useInfiniteFarZ; // 1 = use the no-far-clip-plane trick +extern idCVar r_useScissor; // 1 = scissor clip as portals and lights are processed +extern idCVar r_usePortals; // 1 = use portals to perform area culling, otherwise draw everything +extern idCVar r_useStateCaching; // avoid redundant state changes in GL_*() calls +extern idCVar r_useCombinerDisplayLists;// if 1, put all nvidia register combiner programming in display lists +extern idCVar r_useVertexBuffers; // if 0, don't use ARB_vertex_buffer_object for vertexes +extern idCVar r_useIndexBuffers; // if 0, don't use ARB_vertex_buffer_object for indexes +extern idCVar image_writeProgramImages; +extern idCVar r_useEntityCallbacks; // if 0, issue the callback immediately at update time, rather than defering +extern idCVar r_lightAllBackFaces; // light all the back faces, even when they would be shadowed +extern idCVar r_useDepthBoundsTest; // use depth bounds test to reduce shadow fill + +extern idCVar r_skipPostProcess; // skip all post-process renderings +extern idCVar r_skipSuppress; // ignore the per-view suppressions +extern idCVar r_skipInteractions; // skip all light/surface interaction drawing +extern idCVar r_skipFrontEnd; // bypasses all front end work, but 2D gui rendering still draws +extern idCVar r_skipBackEnd; // don't draw anything +extern idCVar r_skipCopyTexture; // do all rendering, but don't actually copyTexSubImage2D +extern idCVar r_skipRender; // skip 3D rendering, but pass 2D +extern idCVar r_skipRenderContext; // NULL the rendering context during backend 3D rendering +extern idCVar r_skipTranslucent; // skip the translucent interaction rendering +extern idCVar r_skipAmbient; // bypasses all non-interaction drawing +extern idCVar r_skipNewAmbient; // bypasses all vertex/fragment program ambients +extern idCVar r_skipBlendLights; // skip all blend lights +extern idCVar r_skipFogLights; // skip all fog lights +extern idCVar r_skipSubviews; // 1 = don't render any mirrors / cameras / etc +extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces +extern idCVar r_skipParticles; // 1 = don't render any particles +extern idCVar r_skipDecals; // 1 = don't render decals +extern idCVar r_skipUpdates; // 1 = don't accept any entity or light updates, making everything static +extern idCVar r_skipDeforms; // leave all deform materials in their original state +extern idCVar r_skipDynamicTextures; // don't dynamically create textures +extern idCVar r_skipTextures; // use default/black/flat images instead of stage textures +extern idCVar r_useSimpleInteraction; +extern idCVar r_showEditorImages; +extern idCVar r_portalsDistanceCull; +extern idCVar r_alphaToCoverage; +extern idCVar r_skipLightScale; // don't do any post-interaction light scaling, makes things dim on low-dynamic range cards +extern idCVar r_skipBump; // uses a flat surface instead of the bump map +extern idCVar r_skipSpecular; // use black for specular +extern idCVar r_skipDiffuse; // use black for diffuse +extern idCVar r_forceDiffuseOnly; // only draw diffuse passes on non-ARB2 paths +extern idCVar r_skipOverlays; // skip overlay surfaces +extern idCVar r_skipROQ; + +extern idCVar r_ignoreGLErrors; + +extern idCVar r_forceLoadImages; // draw all images to screen after registration +extern idCVar r_demonstrateBug; // used during development to show IHV's their problems +extern idCVar r_screenFraction; // for testing fill rate, the resolution of the entire screen can be changed + +extern idCVar r_showUnsmoothedTangents; // highlight geometry rendered with unsmoothed tangents +extern idCVar r_showSilhouette; // highlight edges that are casting shadow planes +extern idCVar r_showVertexColor; // draws all triangles with the solid vertex color +extern idCVar r_showUpdates; // report entity and light updates and ref counts +extern idCVar r_showDemo; // report reads and writes to the demo file +extern idCVar r_showDynamic; // report stats on dynamic surface generation +extern idCVar r_showLightScale; // report the scale factor applied to drawing for overbrights +extern idCVar r_showIntensity; // draw the screen colors based on intensity, red = 0, green = 128, blue = 255 +extern idCVar r_showDefs; // report the number of modeDefs and lightDefs in view +extern idCVar r_showTrace; // show the intersection of an eye trace with the world +extern idCVar r_showSmp; // show which end (front or back) is blocking +extern idCVar r_showDepth; // display the contents of the depth buffer and the depth range +extern idCVar r_showImages; // draw all images to screen instead of rendering +extern idCVar r_showTris; // enables wireframe rendering of the world +extern idCVar r_showSurfaceInfo; // show surface material name under crosshair +extern idCVar r_showNormals; // draws wireframe normals +extern idCVar r_showEdges; // draw the sil edges +extern idCVar r_showViewEntitys; // displays the bounding boxes of all view models and optionally the index +extern idCVar r_showTexturePolarity; // shade triangles by texture area polarity +extern idCVar r_showTangentSpace; // shade triangles by tangent space +extern idCVar r_showDominantTri; // draw lines from vertexes to center of dominant triangles +extern idCVar r_showTextureVectors; // draw each triangles texture (tangent) vectors +extern idCVar r_showLights; // 1 = print light info, 2 = also draw volumes +extern idCVar r_showLightCount; // colors surfaces based on light count +extern idCVar r_showShadows; // visualize the stencil shadow volumes +extern idCVar r_showShadowCount; // colors screen based on shadow volume depth complexity +extern idCVar r_showLightScissors; // show light scissor rectangles +extern idCVar r_showEntityScissors; // show entity scissor rectangles +extern idCVar r_showInteractionFrustums;// show a frustum for each interaction +extern idCVar r_showInteractionScissors;// show screen rectangle which contains the interaction frustum +extern idCVar r_showMemory; // print frame memory utilization +extern idCVar r_showCull; // report sphere and box culling stats +extern idCVar r_showInteractions; // report interaction generation activity +extern idCVar r_showSurfaces; // report surface/light/shadow counts +extern idCVar r_showPrimitives; // report vertex/index/draw counts +extern idCVar r_showPortals; // draw portal outlines in color based on passed / not passed +extern idCVar r_showAlloc; // report alloc/free counts +extern idCVar r_showSkel; // draw the skeleton when model animates +extern idCVar r_showOverDraw; // show overdraw +extern idCVar r_jointNameScale; // size of joint names when r_showskel is set to 1 +extern idCVar r_jointNameOffset; // offset of joint names when r_showskel is set to 1 + +extern idCVar r_testGamma; // draw a grid pattern to test gamma levels +extern idCVar r_testStepGamma; // draw a grid pattern to test gamma levels +extern idCVar r_testGammaBias; // draw a grid pattern to test gamma levels + +extern idCVar r_testARBProgram; // experiment with vertex/fragment programs + +extern idCVar r_singleLight; // suppress all but one light +extern idCVar r_singleEntity; // suppress all but one entity +extern idCVar r_singleArea; // only draw the portal area the view is actually in +extern idCVar r_singleSurface; // suppress all but one surface on each entity +extern idCVar r_shadowPolygonOffset; // bias value added to depth test for stencil shadow drawing +extern idCVar r_shadowPolygonFactor; // scale value for stencil shadow drawing + +extern idCVar r_jitter; // randomly subpixel jitter the projection matrix +extern idCVar r_lightSourceRadius; // for soft-shadow sampling +extern idCVar r_lockSurfaces; +extern idCVar r_orderIndexes; // perform index reorganization to optimize vertex use + +extern idCVar r_debugLineDepthTest; // perform depth test on debug lines +extern idCVar r_debugLineWidth; // width of debug lines +extern idCVar r_debugArrowStep; // step size of arrow cone line rotation in degrees +extern idCVar r_debugPolygonFilled; + +extern idCVar r_materialOverride; // override all materials + +extern idCVar r_debugRenderToTexture; + +/* +==================================================================== + +GL wrapper/helper functions + +==================================================================== +*/ + +void GL_SelectTexture( int unit ); +void GL_CheckErrors( void ); +void GL_State( int stateVector ); +void GL_PolygonOffsetState( bool enable, float factor, float units ); +void GL_PolygonOffset( const idMaterial *material, bool enable ); +void GL_VertexAttribState( unsigned int vertexAttribStateBits ); +void GL_EnableVertexAttribState( unsigned int vertexAttribStateBits ); +void GL_DisableVertexAttribState( unsigned int vertexAttribStateBits ); +void GL_DisableTexCoordState(); +void GL_TexEnv( int env ); +void GL_Cull( int cullType ); + +const int GLS_SRCBLEND_ZERO = 0x00000001; +const int GLS_SRCBLEND_ONE = 0x0; +const int GLS_SRCBLEND_DST_COLOR = 0x00000003; +const int GLS_SRCBLEND_ONE_MINUS_DST_COLOR = 0x00000004; +const int GLS_SRCBLEND_SRC_ALPHA = 0x00000005; +const int GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA = 0x00000006; +const int GLS_SRCBLEND_DST_ALPHA = 0x00000007; +const int GLS_SRCBLEND_ONE_MINUS_DST_ALPHA = 0x00000008; +const int GLS_SRCBLEND_ALPHA_SATURATE = 0x00000009; +const int GLS_SRCBLEND_SRC_COLOR = 0x0000000a; +const int GLS_SRCBLEND_ONE_MINUS_SRC_COLOR = 0x0000000b; +const int GLS_SRCBLEND_BITS = 0x0000000f; + +const int GLS_DSTBLEND_ZERO = 0x0; +const int GLS_DSTBLEND_ONE = 0x00000020; +const int GLS_DSTBLEND_SRC_COLOR = 0x00000030; +const int GLS_DSTBLEND_ONE_MINUS_SRC_COLOR = 0x00000040; +const int GLS_DSTBLEND_SRC_ALPHA = 0x00000050; +const int GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA = 0x00000060; +const int GLS_DSTBLEND_DST_ALPHA = 0x00000070; +const int GLS_DSTBLEND_ONE_MINUS_DST_ALPHA = 0x00000080; +const int GLS_DSTBLEND_BITS = 0x000000f0; + + +// these masks are the inverse, meaning when set the glColorMask value will be 0, +// preventing that channel from being written +const int GLS_DEPTHMASK = 0x00000100; +const int GLS_REDMASK = 0x00000200; +const int GLS_GREENMASK = 0x00000400; +const int GLS_BLUEMASK = 0x00000800; +const int GLS_ALPHAMASK = 0x00001000; +const int GLS_COLORMASK = (GLS_REDMASK|GLS_GREENMASK|GLS_BLUEMASK); + +const int GLS_POLYMODE_LINE = 0x00002000; + +const int GLS_DEPTHFUNC_ALWAYS = 0x00010000; +const int GLS_DEPTHFUNC_EQUAL = 0x00020000; +const int GLS_DEPTHFUNC_LESS = 0x0; + +const int GLS_ATEST_EQ_255 = 0x10000000; +const int GLS_ATEST_LT_128 = 0x20000000; +const int GLS_ATEST_GE_128 = 0x40000000; +const int GLS_ATEST_BITS = 0x70000000; + +const int GLS_DEFAULT = GLS_DEPTHFUNC_ALWAYS; + +void R_Init( void ); +void R_InitOpenGL( void ); + +void R_DoneFreeType( void ); + +void R_SetColorMappings( void ); + +void R_ScreenShot_f( const idCmdArgs &args ); +void R_StencilShot( void ); + +bool R_CheckExtension( char *name ); + + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +typedef struct { + int width; + int height; + bool fullScreen; + bool stereo; + int displayHz; + int multiSamples; +} glimpParms_t; + +bool GLimp_Init( glimpParms_t parms ); +// If the desired mode can't be set satisfactorily, false will be returned. +// The renderer will then reset the glimpParms to "safe mode" of 640x480 +// fullscreen and try again. If that also fails, the error will be fatal. + +bool GLimp_SetScreenParms( glimpParms_t parms ); +// will set up gl up with the new parms + +void GLimp_Shutdown( void ); +// Destroys the rendering context, closes the window, resets the resolution, +// and resets the gamma ramps. + +void GLimp_SwapBuffers( void ); +// Calls the system specific swapbuffers routine, and may also perform +// other system specific cvar checks that happen every frame. +// This will not be called if 'r_drawBuffer GL_FRONT' + +void GLimp_SetGamma( unsigned short red[256], + unsigned short green[256], + unsigned short blue[256] ); +// Sets the hardware gamma ramps for gamma and brightness adjustment. +// These are now taken as 16 bit values, so we can take full advantage +// of dacs with >8 bits of precision + + +bool GLimp_SpawnRenderThread( void (*function)( void ) ); +// Returns false if the system only has a single processor + +void * GLimp_BackEndSleep( void ); +void GLimp_FrontEndSleep( void ); +void GLimp_WakeBackEnd( void *data ); +// these functions implement the dual processor syncronization + +void GLimp_ActivateContext( void ); +void GLimp_DeactivateContext( void ); +// These are used for managing SMP handoffs of the OpenGL context +// between threads, and as a performance tunining aid. Setting +// 'r_skipRenderContext 1' will call GLimp_DeactivateContext() before +// the 3D rendering code, and GLimp_ActivateContext() afterwards. On +// most OpenGL implementations, this will result in all OpenGL calls +// being immediate returns, which lets us guage how much time is +// being spent inside OpenGL. + +void GLimp_EnableLogging( bool enable ); + + +/* +==================================================================== + +MAIN + +==================================================================== +*/ + +void R_RenderView( viewDef_t *parms ); + +// performs radius cull first, then corner cull +bool R_CullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ); +bool R_RadiusCullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ); +bool R_CornerCullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ); + +void R_AxisToModelMatrix( const idMat3 &axis, const idVec3 &origin, float modelMatrix[16] ); + +// note that many of these assume a normalized matrix, and will not work with scaled axis +void R_GlobalPointToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); +void R_GlobalVectorToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); +void R_GlobalPlaneToLocal( const float modelMatrix[16], const idPlane &in, idPlane &out ); +void R_PointTimesMatrix( const float modelMatrix[16], const idVec4 &in, idVec4 &out ); +void R_LocalPointToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); +void R_LocalVectorToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ); +void R_LocalPlaneToGlobal( const float modelMatrix[16], const idPlane &in, idPlane &out ); +void R_TransformEyeZToWin( float src_z, const float *projectionMatrix, float &dst_z ); + +void R_GlobalToNormalizedDeviceCoordinates( float *modelViewMatrix, float *projectionMatrix, const idVec3 &global, idVec3 &ndc ); + +void R_TransformModelToClip( const idVec3 &src, const float *modelMatrix, const float *projectionMatrix, idPlane &eye, idPlane &dst ); + +void R_TransformClipToDevice( const idPlane &clip, const viewDef_t *view, idVec3 &normalized ); + +void R_TransposeGLMatrix( const float in[16], float out[16] ); + +void R_SetViewMatrix( viewDef_t *viewDef ); + +void myGlMultMatrix( const float *a, const float *b, float *out ); + +/* +============================================================ + +LIGHT + +============================================================ +*/ + +void R_ListRenderLightDefs_f( const idCmdArgs &args ); +void R_ListRenderEntityDefs_f( const idCmdArgs &args ); + +bool R_IssueEntityDefCallback( idRenderEntityLocal *def ); +idRenderModel *R_EntityDefDynamicModel( idRenderEntityLocal *def ); + +viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def ); +viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *def ); + +void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const renderEntity_t *renderEntity, + const idMaterial *shader, const idScreenRect &scissor, unsigned int flags = 0 ); + +void R_LinkLightSurf( const drawSurf_t **link, const srfTriangles_t *tri, const viewEntity_t *space, + const idRenderLightLocal *light, const idMaterial *shader, const idScreenRect &scissor, bool viewInsideShadow ); + +bool R_CreateAmbientCache( srfTriangles_t *tri, bool needsLighting ); +bool R_CreateLightingCache( const idRenderEntityLocal *ent, const idRenderLightLocal *light, srfTriangles_t *tri ); +void R_CreatePrivateShadowCache( srfTriangles_t *tri ); +void R_CreateVertexProgramShadowCache( srfTriangles_t *tri ); + +/* +============================================================ + +LIGHTRUN + +============================================================ +*/ + +void R_RegenerateWorld_f( const idCmdArgs &args ); + +void R_ModulateLights_f( const idCmdArgs &args ); + +void R_SetLightProject( idPlane lightProject[4], const idVec3 origin, const idVec3 targetPoint, + const idVec3 rightVector, const idVec3 upVector, const idVec3 start, const idVec3 stop ); + +void R_AddLightSurfaces( void ); +void R_AddModelSurfaces( void ); +void R_RemoveUnecessaryViewLights( void ); + +void R_FreeDerivedData( void ); +void R_ReCreateWorldReferences( void ); + +void R_CreateEntityRefs( idRenderEntityLocal *def ); +void R_CreateLightRefs( idRenderLightLocal *light ); + +void R_DeriveLightData( idRenderLightLocal *light ); +void R_FreeLightDefDerivedData( idRenderLightLocal *light ); +void R_CheckForEntityDefsUsingModel( idRenderModel *model ); + +void R_ClearEntityDefDynamicModel( idRenderEntityLocal *def ); +void R_FreeEntityDefDerivedData( idRenderEntityLocal *def, bool keepDecals, bool keepCachedDynamicModel ); +void R_FreeEntityDefCachedDynamicModel( idRenderEntityLocal *def ); +void R_FreeEntityDefDecals( idRenderEntityLocal *def ); +void R_FreeEntityDefOverlay( idRenderEntityLocal *def ); +void R_FreeEntityDefFadedDecals( idRenderEntityLocal *def, int time ); + +void R_CreateLightDefFogPortals( idRenderLightLocal *ldef ); + +/* +============================================================ + +POLYTOPE + +============================================================ +*/ + +srfTriangles_t *R_PolytopeSurface( int numPlanes, const idPlane *planes, idWinding **windings ); + +/* +============================================================ + +RENDER + +============================================================ +*/ + +void RB_EnterWeaponDepthHack(); +void RB_EnterModelDepthHack( float depth ); +void RB_LeaveDepthHack(); +void RB_DrawElementsImmediate( const srfTriangles_t *tri ); +void RB_RenderTriangleSurface( const srfTriangles_t *tri ); +void RB_T_RenderTriangleSurface( const drawSurf_t *surf ); +void RB_T_ShowBatchSizeRenderTriangleSurface( const drawSurf_t *surf ); +void RB_T_ShowTriRenderTriangleSurface( const drawSurf_t *surf ); +void RB_T_RenderSimpleSurface( const drawSurf_t *surf ); +void RB_T_RenderTriangleSurfacePasses( const drawSurf_t *surf ); +void RB_RenderDrawSurfListWithFunction( drawSurf_t **drawSurfs, int numDrawSurfs, + void (*triFunc_)( const drawSurf_t *) ); +void RB_RenderDrawSurfChainWithFunction( const drawSurf_t *drawSurfs, + void (*triFunc_)( const drawSurf_t *) ); +void RB_DrawShaderPasses( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_LoadShaderTextureMatrix( const float *shaderRegisters, const textureStage_t *texture ); +void RB_GetShaderTextureMatrix( const float *shaderRegisters, const textureStage_t *texture, float matrix[16] ); +void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInteraction)(const drawInteraction_t *) ); + +const shaderStage_t *RB_SetLightTexture( const idRenderLightLocal *light ); + +void RB_DrawView( const void *data ); + +void RB_DetermineLightScale( void ); +void RB_STD_LightScale( void ); +void RB_BeginDrawingView (void); + +/* +============================================================ + +DRAW_STANDARD + +============================================================ +*/ + +void RB_DrawElementsWithCounters( const srfTriangles_t *tri ); +void RB_DrawShadowElementsWithCounters( const drawSurf_t *surf, int numIndexes ); +void RB_ARB2_MD5R_DrawShadowElements( const drawSurf_t *surf, int numIndexes ); +void RB_ARB2_MD5R_DrawDepthElements( const drawSurf_t *surf ); +void RB_ARB2_LoadMVPMatrixIntoVPParams( const drawSurf_t *surf ); +void RB_ARB2_LoadProjectionMatrixIntoVPParams( const drawSurf_t *surf ); +void RB_ARB2_LoadShaderTextureMatrixIntoVPParams( const float *shaderRegisters, const textureStage_t *texture ); +void RB_ARB2_LoadModelMatrixIntoVPParams( const drawSurf_t *surf ); +void RB_ARB2_LoadModelViewMatrixIntoVPParams( const drawSurf_t *surf ); +void RB_ARB2_LoadLocalViewOriginIntoVPParams( const drawSurf_t *surf ); +void RB_ARB2_PrepareStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf, bool fillingDepth ); +void RB_ARB2_DisableStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf ); +void RB_STD_FillDepthBuffer( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_PrepareStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf, idDrawVert *ac, bool fillingDepth ); +void RB_FinishStageTexturing( const shaderStage_t *pStage, const drawSurf_t *surf, idDrawVert *ac, bool fillingDepth ); +void RB_GetCurrentRender( bool force ); +void RB_BindVariableStageImage( const textureStage_t *texture ); +void RB_BindStageTexture( const float *shaderRegisters, const textureStage_t *texture, const drawSurf_t *surf ); +void RB_FinishStageTexture( const textureStage_t *texture, const drawSurf_t *surf ); +void RB_StencilShadowPass( const drawSurf_t *drawSurfs ); +int RB_STD_DrawShaderPasses( drawSurf_t **drawSurfs, int numDrawSurfs, materialSort_t minSort, materialSort_t maxSort ); +void RB_STD_DrawView( void ); +void RB_STD_FogAllLights( void ); +void RB_BakeTextureMatrixIntoTexgen( idPlane lightProject[3], const float textureMatrix[16] ); + +/* +============================================================ + +DRAW_* + +============================================================ +*/ + +void RB_ARB_DrawInteractions( void ); + +void R_R200_Init( void ); +void RB_R200_DrawInteractions( void ); + +void R_NV10_Init( void ); +void RB_NV10_DrawInteractions( void ); + +void R_NV20_Init( void ); +void RB_NV20_DrawInteractions( void ); + +void R_ARB2_Init( void ); +void RB_ARB2_DrawInteractions( void ); +void R_ReloadARBPrograms_f( const idCmdArgs &args ); +int R_FindARBProgram( GLenum target, const char *program ); + +typedef enum { + PROG_INVALID, + VPROG_INTERACTION, + VPROG_ENVIRONMENT, + VPROG_BUMPY_ENVIRONMENT, + VPROG_R200_INTERACTION, + VPROG_STENCIL_SHADOW, + VPROG_NV20_BUMP_AND_LIGHT, + VPROG_NV20_DIFFUSE_COLOR, + VPROG_NV20_SPECULAR_COLOR, + VPROG_NV20_DIFFUSE_AND_SPECULAR_COLOR, + VPROG_TEST, + FPROG_INTERACTION, + FPROG_ENVIRONMENT, + FPROG_BUMPY_ENVIRONMENT, + FPROG_TEST, + VPROG_AMBIENT, + FPROG_AMBIENT, + VPROG_GLASSWARP, + FPROG_GLASSWARP, + VPROG_SIMPLE_INTERACTION, + FPROG_SIMPLE_INTERACTION, + VPROG_MD5R_INTERACTION4, + VPROG_MD5R_INTERACTION1, + VPROG_MD5R_INTERACTION, + VPROG_MD5R_SIMPLE4, + VPROG_MD5R_SIMPLE1, + VPROG_MD5R_SIMPLE, + VPROG_MD5R_STDTEX4, + VPROG_MD5R_STDTEX1, + VPROG_MD5R_STDTEX, + VPROG_MD5R_SKYBOX4, + VPROG_MD5R_SKYBOX1, + VPROG_MD5R_SKYBOX, + VPROG_MD5R_ENVNORMAL4, + VPROG_MD5R_ENVNORMAL1, + VPROG_MD5R_ENVNORMAL, + VPROG_MD5R_ENVREFLECT4, + VPROG_MD5R_ENVREFLECT1, + VPROG_MD5R_ENVREFLECT, + VPROG_MD5R_ENVBUMP4, + VPROG_MD5R_ENVBUMP1, + VPROG_MD5R_ENVBUMP, + VPROG_MD5R_SHADOW4, + VPROG_MD5R_SHADOW1, + VPROG_MD5R_SHADOW, + VPROG_MD5R_BASICFOG4, + VPROG_MD5R_BASICFOG1, + VPROG_MD5R_BASICFOG, + VPROG_DEPTH, + FPROG_DEPTH, + FPROG_DEPTH_COC, + PROG_USER +} program_t; + +/* + + All vertex programs use the same constant register layout: + +c[4] localLightOrigin +c[5] localViewOrigin +c[6] lightProjection S +c[7] lightProjection T +c[8] lightProjection Q +c[9] lightFalloff S +c[10] bumpMatrix S +c[11] bumpMatrix T +c[12] diffuseMatrix S +c[13] diffuseMatrix T +c[14] specularMatrix S +c[15] specularMatrix T + + +c[20] light falloff tq constant + +// texture 0 was cube map +// texture 1 will be the per-surface bump map +// texture 2 will be the light falloff texture +// texture 3 will be the light projection texture +// texture 4 is the per-surface diffuse map +// texture 5 is the per-surface specular map +// texture 6 is the specular half angle cube map + +*/ + +typedef enum { + PP_LIGHT_ORIGIN = 4, + PP_VIEW_ORIGIN, + PP_LIGHT_PROJECT_S, + PP_LIGHT_PROJECT_T, + PP_LIGHT_PROJECT_Q, + PP_LIGHT_FALLOFF_S, + PP_BUMP_MATRIX_S, + PP_BUMP_MATRIX_T, + PP_DIFFUSE_MATRIX_S, + PP_DIFFUSE_MATRIX_T, + PP_SPECULAR_MATRIX_S, + PP_SPECULAR_MATRIX_T, + PP_COLOR_MODULATE, + PP_COLOR_ADD, + + PP_LIGHT_FALLOFF_TQ = 20 // only for NV programs +} programParameter_t; + + +/* +============================================================ + +TR_STENCILSHADOWS + +"facing" should have one more element than tri->numIndexes / 3, which should be set to 1 + +============================================================ +*/ + +void R_MakeShadowFrustums( idRenderLightLocal *def ); + +typedef enum { + SG_DYNAMIC, // use infinite projections + SG_STATIC, // clip to bounds + SG_OFFLINE // perform very time consuming optimizations +} shadowGen_t; + +srfTriangles_t *R_CreateShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + int optimize, srfCullInfo_t &cullInfo ); + +/* +============================================================ + +TR_TURBOSHADOW + +Fast, non-clipped overshoot shadow volumes + +"facing" should have one more element than tri->numIndexes / 3, which should be set to 1 +calling this function may modify "facing" based on culling + +============================================================ +*/ + +srfTriangles_t *R_CreateVertexProgramTurboShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + srfCullInfo_t &cullInfo ); + +srfTriangles_t *R_CreateVertexBufferTurboShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + srfCullInfo_t &cullInfo ); + +srfTriangles_t *R_CreateTurboShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + srfCullInfo_t &cullInfo ); + +/* +============================================================ + +util/shadowopt3 + +dmap time optimization of shadow volumes, called from R_CreateShadowVolume + +============================================================ +*/ + + +/* +============================================================ + +TRISURF + +============================================================ +*/ + +#define USE_TRI_DATA_ALLOCATOR + +void R_InitTriSurfData( void ); +void R_ShutdownTriSurfData( void ); +void R_PurgeTriSurfData( frameData_t *frame ); +void R_ShowTriSurfMemory_f( const idCmdArgs &args ); + +srfTriangles_t * R_AllocStaticTriSurf( void ); +srfTriangles_t * R_CopyStaticTriSurf( const srfTriangles_t *tri ); +void R_AllocStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ); +void R_AllocStaticTriSurfSilTraceVerts( srfTriangles_t *tri, int numVerts ); +void R_AllocStaticSkinToModelTransforms( srfTriangles_t *tri, int numTransforms ); +void R_AllocStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ); +void R_AllocStaticTriSurfShadowVerts( srfTriangles_t *tri, int numVerts ); +void R_AllocStaticTriSurfPlanes( srfTriangles_t *tri, int numIndexes ); +void R_ResizeStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ); +void R_ResizeStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ); +void R_ResizeStaticTriSurfShadowVerts( srfTriangles_t *tri, int numVerts ); +void R_ReferenceStaticTriSurfVerts( srfTriangles_t *tri, const srfTriangles_t *reference ); +void R_ReferenceStaticTriSurfIndexes( srfTriangles_t *tri, const srfTriangles_t *reference ); +void R_FreeStaticTriSurfSilIndexes( srfTriangles_t *tri ); +void R_FreeStaticTriSurf( srfTriangles_t *tri ); +void R_FreeStaticTriSurfVertexCaches( srfTriangles_t *tri ); +void R_ReallyFreeStaticTriSurf( srfTriangles_t *tri ); +void R_FreeDeferredTriSurfs( frameData_t *frame ); +int R_TriSurfMemory( const srfTriangles_t *tri ); + +void R_BoundTriSurf( srfTriangles_t *tri ); +void R_RemoveDuplicatedTriangles( srfTriangles_t *tri ); +void R_CreateSilIndexes( srfTriangles_t *tri ); +void R_RemoveDegenerateTriangles( srfTriangles_t *tri ); +void R_RemoveUnusedVerts( srfTriangles_t *tri ); +void R_RangeCheckIndexes( const srfTriangles_t *tri ); +void R_CreateVertexNormals( srfTriangles_t *tri ); // also called by dmap +void R_DeriveFacePlanes( srfTriangles_t *tri ); // also called by renderbump +void R_CleanupTriangles( srfTriangles_t *tri, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents ); +void R_ReverseTriangles( srfTriangles_t *tri ); + +// Only deals with vertexes and indexes, not silhouettes, planes, etc. +// Does NOT perform a cleanup triangles, so there may be duplicated verts in the result. +srfTriangles_t * R_MergeSurfaceList( const srfTriangles_t **surfaces, int numSurfaces ); +srfTriangles_t * R_MergeTriangles( const srfTriangles_t *tri1, const srfTriangles_t *tri2 ); + +// if the deformed verts have significant enough texture coordinate changes to reverse the texture +// polarity of a triangle, the tangents will be incorrect +void R_DeriveTangents( srfTriangles_t *tri, bool allocFacePlanes = true ); + +// deformable meshes precalculate as much as possible from a base frame, then generate +// complete srfTriangles_t from just a new set of vertexes +typedef struct deformInfo_s { + int numSourceVerts; + + // numOutputVerts may be smaller if the input had duplicated or degenerate triangles + // it will often be larger if the input had mirrored texture seams that needed + // to be busted for proper tangent spaces + int numOutputVerts; + + int numMirroredVerts; + int * mirroredVerts; + + int numIndexes; + glIndex_t * indexes; + + glIndex_t * silIndexes; + + int numDupVerts; + int * dupVerts; + + int numSilEdges; + silEdge_t * silEdges; + + dominantTri_t * dominantTris; +} deformInfo_t; + + +deformInfo_t * R_BuildDeformInfo( int numVerts, const idDrawVert *verts, int numIndexes, const int *indexes, bool useUnsmoothedTangents ); +void R_FreeDeformInfo( deformInfo_t *deformInfo ); +int R_DeformInfoMemoryUsed( deformInfo_t *deformInfo ); + +/* +============================================================ + +SUBVIEW + +============================================================ +*/ + +bool R_PreciseCullSurface( const drawSurf_t *drawSurf, idBounds &ndcBounds ); +bool R_GenerateSubViews( void ); + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_InitFrameData( void ); +void R_ShutdownFrameData( void ); +int R_CountFrameData( void ); +void R_ToggleSmpFrame( void ); +void *R_FrameAlloc( int bytes ); +void *R_ClearedFrameAlloc( int bytes ); +void R_FrameFree( void *data ); + +void *R_StaticAlloc( int bytes ); // just malloc with error checking +void *R_ClearedStaticAlloc( int bytes ); // with memset +void R_StaticFree( void *data ); + + +/* +============================================================= + +RENDERER DEBUG TOOLS + +============================================================= +*/ + +float RB_DrawTextLength( const char *text, float scale, int len ); +void RB_AddDebugText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align, const int lifetime, const bool depthTest ); +void RB_ClearDebugText( int time ); +void RB_AddDebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifeTime, const bool depthTest ); +void RB_ClearDebugLines( int time ); +void RB_AddDebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime, const bool depthTest ); +void RB_ClearDebugPolygons( int time ); +void RB_DrawBounds( const idBounds &bounds ); +void RB_ShowLights( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_ShowLightCount( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_PolygonClear( void ); +void RB_ScanStencilBuffer( void ); +void RB_ShowDestinationAlpha( void ); +void RB_ShowOverdraw( void ); +void RB_RenderDebugTools( drawSurf_t **drawSurfs, int numDrawSurfs ); +void RB_ShutdownDebugTools( void ); + +/* +============================================================= + +TR_BACKEND + +============================================================= +*/ + +void RB_SetDefaultGLState( void ); +void RB_SetGL2D( void ); +void R_AddSpecialEffects( viewDef_t *parms ); +void RB_DrawDepthTexture( const void *data ); +void RB_DisplaySpecialEffects( viewEntity_t *viewEnts, bool prePass ); + +// write a comment to the r_logFile if it is enabled +#define RB_LogComment( ... ) ((void)0) + +void RB_ShowImages( void ); + +void RB_ExecuteBackEndCommands( const emptyCommand_t *cmds ); + + +/* +============================================================= + +TR_GUISURF + +============================================================= +*/ + +void R_SurfaceToTextureAxis( const srfTriangles_t *tri, idVec3 &origin, idVec3 axis[3] ); +idScreenRect R_ScreenRectFromDrawSurf( drawSurf_t *drawSurf ); +void R_RenderGuiSurf( idUserInterface *gui, drawSurf_t *drawSurf ); + +/* +============================================================= + +TR_ORDERINDEXES + +============================================================= +*/ + + +/* +============================================================= + +TR_DEFORM + +============================================================= +*/ + +void R_DeformDrawSurf( drawSurf_t *drawSurf ); + +/* +============================================================= + +TR_TRACE + +============================================================= +*/ + +typedef struct localTrace_s { + float fraction; + const rvDeclMatType *materialType; + // only valid if fraction < 1.0 + idVec3 point; + idVec3 normal; + int indexes[3]; + idVec3 vertices[3]; + int drawIndices[3]; +} localTrace_t; + +const rvDeclMatType *R_GetMaterialType( const idMaterial *shader, + const srfTriangles_t *tri, int triangleNum, idVec3 &point ); +localTrace_t R_LocalTrace( const idVec3 &start, const idVec3 &end, + const float radius, const srfTriangles_t *tri, const idMaterial *shader ); +void RB_ShowTrace( drawSurf_t **drawSurfs, int numDrawSurfs ); + +/* +============================================================= + +TR_SHADOWBOUNDS + +============================================================= +*/ +idScreenRect R_CalcIntersectionScissor( const idRenderLightLocal * lightDef, + const idRenderEntityLocal * entityDef, + const viewDef_t * viewDef ); + +//============================================= + +#include "RenderWorld_local.h" +#include "GuiModel.h" +#include "VertexCache.h" + +#endif /* !__TR_LOCAL_H__ */ diff --git a/src/renderer/tr_main.cpp b/src/renderer/tr_main.cpp new file mode 100644 index 0000000..8644161 --- /dev/null +++ b/src/renderer/tr_main.cpp @@ -0,0 +1,1140 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#ifdef __ppc__ +#include +#endif +#if defined(MACOS_X) && defined(__i386__) +#include +#endif + +//==================================================================== + +/* +====================== +idScreenRect::Clear +====================== +*/ +void idScreenRect::Clear() { + x1 = y1 = 32000; + x2 = y2 = -32000; + zmin = 0.0f; zmax = 1.0f; +} + +/* +====================== +idScreenRect::AddPoint +====================== +*/ +void idScreenRect::AddPoint( float x, float y ) { + int ix = idMath::FtoiFast( x ); + int iy = idMath::FtoiFast( y ); + + if ( ix < x1 ) { + x1 = ix; + } + if ( ix > x2 ) { + x2 = ix; + } + if ( iy < y1 ) { + y1 = iy; + } + if ( iy > y2 ) { + y2 = iy; + } +} + +/* +====================== +idScreenRect::Expand +====================== +*/ +void idScreenRect::Expand() { + x1--; + y1--; + x2++; + y2++; +} + +/* +====================== +idScreenRect::Intersect +====================== +*/ +void idScreenRect::Intersect( const idScreenRect &rect ) { + if ( rect.x1 > x1 ) { + x1 = rect.x1; + } + if ( rect.x2 < x2 ) { + x2 = rect.x2; + } + if ( rect.y1 > y1 ) { + y1 = rect.y1; + } + if ( rect.y2 < y2 ) { + y2 = rect.y2; + } +} + +/* +====================== +idScreenRect::Union +====================== +*/ +void idScreenRect::Union( const idScreenRect &rect ) { + if ( rect.x1 < x1 ) { + x1 = rect.x1; + } + if ( rect.x2 > x2 ) { + x2 = rect.x2; + } + if ( rect.y1 < y1 ) { + y1 = rect.y1; + } + if ( rect.y2 > y2 ) { + y2 = rect.y2; + } +} + +/* +====================== +idScreenRect::Equals +====================== +*/ +bool idScreenRect::Equals( const idScreenRect &rect ) const { + return ( x1 == rect.x1 && x2 == rect.x2 && y1 == rect.y1 && y2 == rect.y2 ); +} + +/* +====================== +idScreenRect::IsEmpty +====================== +*/ +bool idScreenRect::IsEmpty() const { + return ( x1 > x2 || y1 > y2 ); +} + +/* +====================== +R_ScreenRectFromViewFrustumBounds +====================== +*/ +idScreenRect R_ScreenRectFromViewFrustumBounds( const idBounds &bounds ) { + idScreenRect screenRect; + + screenRect.x1 = idMath::FtoiFast( 0.5f * ( 1.0f - bounds[1].y ) * ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ) ); + screenRect.x2 = idMath::FtoiFast( 0.5f * ( 1.0f - bounds[0].y ) * ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 ) ); + screenRect.y1 = idMath::FtoiFast( 0.5f * ( 1.0f + bounds[0].z ) * ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ) ); + screenRect.y2 = idMath::FtoiFast( 0.5f * ( 1.0f + bounds[1].z ) * ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 ) ); + + if ( r_useDepthBoundsTest.GetInteger() ) { + R_TransformEyeZToWin( -bounds[0].x, tr.viewDef->projectionMatrix, screenRect.zmin ); + R_TransformEyeZToWin( -bounds[1].x, tr.viewDef->projectionMatrix, screenRect.zmax ); + } + + return screenRect; +} + +/* +====================== +R_ShowColoredScreenRect +====================== +*/ +void R_ShowColoredScreenRect( const idScreenRect &rect, int colorIndex ) { + if ( !rect.IsEmpty() ) { + static idVec4 colors[] = { colorRed, colorGreen, colorBlue, colorYellow, colorMagenta, colorCyan, colorWhite, colorPurple }; + tr.viewDef->renderWorld->DebugScreenRect( colors[colorIndex & 7], rect, tr.viewDef ); + } +} + +/* +==================== +R_ToggleSmpFrame +==================== +*/ +void R_ToggleSmpFrame( void ) { + if ( r_lockSurfaces.GetBool() ) { + return; + } + R_FreeDeferredTriSurfs( frameData ); + + // clear frame-temporary data + frameData_t *frame; + frameMemoryBlock_t *block; + + // update the highwater mark + R_CountFrameData(); + + frame = frameData; + + // reset the memory allocation to the first block + frame->alloc = frame->memory; + + // clear all the blocks + for ( block = frame->memory ; block ; block = block->next ) { + block->used = 0; + } + + R_ClearCommandChain(); +} + +void idRenderSystemLocal::ToggleSmpFrame() { + R_ToggleSmpFrame(); +} + + +//===================================================== + +#define MEMORY_BLOCK_SIZE 0x100000 + +/* +===================== +R_ShutdownFrameData +===================== +*/ +void R_ShutdownFrameData( void ) { + frameData_t *frame; + frameMemoryBlock_t *block; + + // free any current data + frame = frameData; + if ( !frame ) { + return; + } + + R_FreeDeferredTriSurfs( frame ); + + frameMemoryBlock_t *nextBlock; + for ( block = frame->memory ; block ; block = nextBlock ) { + nextBlock = block->next; + Mem_Free( block ); + } + Mem_Free( frame ); + frameData = NULL; +} + +/* +===================== +R_InitFrameData +===================== +*/ +void R_InitFrameData( void ) { + int size; + frameData_t *frame; + frameMemoryBlock_t *block; + + R_ShutdownFrameData(); + + frameData = (frameData_t *)Mem_ClearedAlloc( sizeof( *frameData )); + frame = frameData; + size = MEMORY_BLOCK_SIZE; + block = (frameMemoryBlock_t *)Mem_Alloc( size + sizeof( *block ) ); + if ( !block ) { + common->FatalError( "R_InitFrameData: Mem_Alloc() failed" ); + } + block->size = size; + block->used = 0; + block->next = NULL; + frame->memory = block; + frame->memoryHighwater = 0; + + R_ToggleSmpFrame(); +} + +/* +================ +R_CountFrameData +================ +*/ +int R_CountFrameData( void ) { + frameData_t *frame; + frameMemoryBlock_t *block; + int count; + + count = 0; + frame = frameData; + for ( block = frame->memory ; block ; block=block->next ) { + count += block->used; + if ( block == frame->alloc ) { + break; + } + } + + // note if this is a new highwater mark + if ( count > frame->memoryHighwater ) { + frame->memoryHighwater = count; + } + + return count; +} + +/* +================= +R_StaticAlloc +================= +*/ +void *R_StaticAlloc( int bytes ) { + void *buf; + + tr.pc.c_alloc++; + + tr.staticAllocCount += bytes; + + buf = Mem_Alloc( bytes ); + + // don't exit on failure on zero length allocations since the old code didn't + if ( !buf && ( bytes != 0 ) ) { + common->FatalError( "R_StaticAlloc failed on %i bytes", bytes ); + } + return buf; +} + +/* +================= +R_ClearedStaticAlloc +================= +*/ +void *R_ClearedStaticAlloc( int bytes ) { + void *buf; + + buf = R_StaticAlloc( bytes ); + SIMDProcessor->Memset( buf, 0, bytes ); + return buf; +} + +/* +================= +R_StaticFree +================= +*/ +void R_StaticFree( void *data ) { + tr.pc.c_free++; + Mem_Free( data ); +} + +/* +================ +R_FrameAlloc + +This data will be automatically freed when the +current frame's back end completes. + +This should only be called by the front end. The +back end shouldn't need to allocate memory. + +If we passed smpFrame in, the back end could +alloc memory, because it will always be a +different frameData than the front end is using. + +All temporary data, like dynamic tesselations +and local spaces are allocated here. + +The memory will not move, but it may not be +contiguous with previous allocations even +from this frame. + +The memory is NOT zero filled. +Should part of this be inlined in a macro? +================ +*/ +void *R_FrameAlloc( int bytes ) { + frameData_t *frame; + frameMemoryBlock_t *block; + void *buf; + + bytes = (bytes+16)&~15; + // see if it can be satisfied in the current block + frame = frameData; + block = frame->alloc; + + if ( block->size - block->used >= bytes ) { + buf = block->base + block->used; + block->used += bytes; + return buf; + } + + // advance to the next memory block if available + block = block->next; + // create a new block if we are at the end of + // the chain + if ( !block ) { + int size; + + size = MEMORY_BLOCK_SIZE; + block = (frameMemoryBlock_t *)Mem_Alloc( size + sizeof( *block ) ); + if ( !block ) { + common->FatalError( "R_FrameAlloc: Mem_Alloc() failed" ); + } + block->size = size; + block->used = 0; + block->next = NULL; + frame->alloc->next = block; + } + + // we could fix this if we needed to... + if ( bytes > block->size ) { + common->FatalError( "R_FrameAlloc of %i exceeded MEMORY_BLOCK_SIZE", + bytes ); + } + + frame->alloc = block; + + block->used = bytes; + + return block->base; +} + +/* +================== +R_ClearedFrameAlloc +================== +*/ +void *R_ClearedFrameAlloc( int bytes ) { + void *r; + + r = R_FrameAlloc( bytes ); + SIMDProcessor->Memset( r, 0, bytes ); + return r; +} + + +/* +================== +R_FrameFree + +This does nothing at all, as the frame data is reused every frame +and can only be stack allocated. + +The only reason for it's existance is so functions that can +use either static or frame memory can set function pointers +to both alloc and free. +================== +*/ +void R_FrameFree( void *data ) { +} + + + +//========================================================================== + +void R_AxisToModelMatrix( const idMat3 &axis, const idVec3 &origin, float modelMatrix[16] ) { + modelMatrix[0] = axis[0][0]; + modelMatrix[4] = axis[1][0]; + modelMatrix[8] = axis[2][0]; + modelMatrix[12] = origin[0]; + + modelMatrix[1] = axis[0][1]; + modelMatrix[5] = axis[1][1]; + modelMatrix[9] = axis[2][1]; + modelMatrix[13] = origin[1]; + + modelMatrix[2] = axis[0][2]; + modelMatrix[6] = axis[1][2]; + modelMatrix[10] = axis[2][2]; + modelMatrix[14] = origin[2]; + + modelMatrix[3] = 0; + modelMatrix[7] = 0; + modelMatrix[11] = 0; + modelMatrix[15] = 1; +} + + +// FIXME: these assume no skewing or scaling transforms + +void R_LocalPointToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { +#if defined(MACOS_X) && defined(__i386__) + __m128 m0, m1, m2, m3; + __m128 in0, in1, in2; + float i0,i1,i2; + i0 = in[0]; + i1 = in[1]; + i2 = in[2]; + + m0 = _mm_loadu_ps(&modelMatrix[0]); + m1 = _mm_loadu_ps(&modelMatrix[4]); + m2 = _mm_loadu_ps(&modelMatrix[8]); + m3 = _mm_loadu_ps(&modelMatrix[12]); + + in0 = _mm_load1_ps(&i0); + in1 = _mm_load1_ps(&i1); + in2 = _mm_load1_ps(&i2); + + m0 = _mm_mul_ps(m0, in0); + m1 = _mm_mul_ps(m1, in1); + m2 = _mm_mul_ps(m2, in2); + + m0 = _mm_add_ps(m0, m1); + m0 = _mm_add_ps(m0, m2); + m0 = _mm_add_ps(m0, m3); + + _mm_store_ss(&out[0], m0); + m1 = (__m128) _mm_shuffle_epi32((__m128i)m0, 0x55); + _mm_store_ss(&out[1], m1); + m2 = _mm_movehl_ps(m2, m0); + _mm_store_ss(&out[2], m2); +#else + out[0] = in[0] * modelMatrix[0] + in[1] * modelMatrix[4] + + in[2] * modelMatrix[8] + modelMatrix[12]; + out[1] = in[0] * modelMatrix[1] + in[1] * modelMatrix[5] + + in[2] * modelMatrix[9] + modelMatrix[13]; + out[2] = in[0] * modelMatrix[2] + in[1] * modelMatrix[6] + + in[2] * modelMatrix[10] + modelMatrix[14]; +#endif +} + +void R_PointTimesMatrix( const float modelMatrix[16], const idVec4 &in, idVec4 &out ) { + out[0] = in[0] * modelMatrix[0] + in[1] * modelMatrix[4] + + in[2] * modelMatrix[8] + modelMatrix[12]; + out[1] = in[0] * modelMatrix[1] + in[1] * modelMatrix[5] + + in[2] * modelMatrix[9] + modelMatrix[13]; + out[2] = in[0] * modelMatrix[2] + in[1] * modelMatrix[6] + + in[2] * modelMatrix[10] + modelMatrix[14]; + out[3] = in[0] * modelMatrix[3] + in[1] * modelMatrix[7] + + in[2] * modelMatrix[11] + modelMatrix[15]; +} + +void R_GlobalPointToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { + idVec3 temp; + + VectorSubtract( in, &modelMatrix[12], temp ); + + out[0] = DotProduct( temp, &modelMatrix[0] ); + out[1] = DotProduct( temp, &modelMatrix[4] ); + out[2] = DotProduct( temp, &modelMatrix[8] ); +} + +void R_LocalVectorToGlobal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { + out[0] = in[0] * modelMatrix[0] + in[1] * modelMatrix[4] + + in[2] * modelMatrix[8]; + out[1] = in[0] * modelMatrix[1] + in[1] * modelMatrix[5] + + in[2] * modelMatrix[9]; + out[2] = in[0] * modelMatrix[2] + in[1] * modelMatrix[6] + + in[2] * modelMatrix[10]; +} + +void R_GlobalVectorToLocal( const float modelMatrix[16], const idVec3 &in, idVec3 &out ) { + out[0] = DotProduct( in, &modelMatrix[0] ); + out[1] = DotProduct( in, &modelMatrix[4] ); + out[2] = DotProduct( in, &modelMatrix[8] ); +} + +void R_GlobalPlaneToLocal( const float modelMatrix[16], const idPlane &in, idPlane &out ) { + out[0] = DotProduct( in, &modelMatrix[0] ); + out[1] = DotProduct( in, &modelMatrix[4] ); + out[2] = DotProduct( in, &modelMatrix[8] ); + out[3] = in[3] + modelMatrix[12] * in[0] + modelMatrix[13] * in[1] + modelMatrix[14] * in[2]; +} + +void R_LocalPlaneToGlobal( const float modelMatrix[16], const idPlane &in, idPlane &out ) { + float offset; + + R_LocalVectorToGlobal( modelMatrix, in.Normal(), out.Normal() ); + + offset = modelMatrix[12] * out[0] + modelMatrix[13] * out[1] + modelMatrix[14] * out[2]; + out[3] = in[3] - offset; +} + +// transform Z in eye coordinates to window coordinates +void R_TransformEyeZToWin( float src_z, const float *projectionMatrix, float &dst_z ) { + float clip_z, clip_w; + + // projection + clip_z = src_z * projectionMatrix[ 2 + 2 * 4 ] + projectionMatrix[ 2 + 3 * 4 ]; + clip_w = src_z * projectionMatrix[ 3 + 2 * 4 ] + projectionMatrix[ 3 + 3 * 4 ]; + + if ( clip_w <= 0.0f ) { + dst_z = 0.0f; // clamp to near plane + } else { + dst_z = clip_z / clip_w; + dst_z = dst_z * 0.5f + 0.5f; // convert to window coords + } +} + +/* +================= +R_RadiusCullLocalBox + +A fast, conservative center-to-corner culling test +Returns true if the box is outside the given global frustum, (positive sides are out) +================= +*/ +bool R_RadiusCullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ) { + int i; + float d; + idVec3 worldOrigin; + float worldRadius; + const idPlane *frust; + + if ( r_useCulling.GetInteger() == 0 ) { + return false; + } + + // transform the surface bounds into world space + idVec3 localOrigin = ( bounds[0] + bounds[1] ) * 0.5; + + R_LocalPointToGlobal( modelMatrix, localOrigin, worldOrigin ); + + worldRadius = (bounds[0] - localOrigin).Length(); // FIXME: won't be correct for scaled objects + + for ( i = 0 ; i < numPlanes ; i++ ) { + frust = planes + i; + d = frust->Distance( worldOrigin ); + if ( d > worldRadius ) { + return true; // culled + } + } + + return false; // no culled +} + +/* +================= +R_CornerCullLocalBox + +Tests all corners against the frustum. +Can still generate a few false positives when the box is outside a corner. +Returns true if the box is outside the given global frustum, (positive sides are out) +================= +*/ +bool R_CornerCullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ) { + int i, j; + idVec3 transformed[8]; + float dists[8]; + idVec3 v; + const idPlane *frust; + + // we can disable box culling for experimental timing purposes + if ( r_useCulling.GetInteger() < 2 ) { + return false; + } + + // transform into world space + for ( i = 0 ; i < 8 ; i++ ) { + v[0] = bounds[i&1][0]; + v[1] = bounds[(i>>1)&1][1]; + v[2] = bounds[(i>>2)&1][2]; + + R_LocalPointToGlobal( modelMatrix, v, transformed[i] ); + } + + // check against frustum planes + for ( i = 0 ; i < numPlanes ; i++ ) { + frust = planes + i; + for ( j = 0 ; j < 8 ; j++ ) { + dists[j] = frust->Distance( transformed[j] ); + if ( dists[j] < 0 ) { + break; + } + } + if ( j == 8 ) { + // all points were behind one of the planes + tr.pc.c_box_cull_out++; + return true; + } + } + + tr.pc.c_box_cull_in++; + + return false; // not culled +} + +/* +================= +R_CullLocalBox + +Performs quick test before expensive test +Returns true if the box is outside the given global frustum, (positive sides are out) +================= +*/ +bool R_CullLocalBox( const idBounds &bounds, const float modelMatrix[16], int numPlanes, const idPlane *planes ) { + if ( R_RadiusCullLocalBox( bounds, modelMatrix, numPlanes, planes ) ) { + return true; + } + return R_CornerCullLocalBox( bounds, modelMatrix, numPlanes, planes ); +} + +/* +========================== +R_TransformModelToClip +========================== +*/ +void R_TransformModelToClip( const idVec3 &src, const float *modelMatrix, const float *projectionMatrix, idPlane &eye, idPlane &dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_GlobalToNormalizedDeviceCoordinates + +-1 to 1 range in x, y, and z +========================== +*/ +void R_GlobalToNormalizedDeviceCoordinates( float *modelViewMatrix, float *projectionMatrix, const idVec3 &global, idVec3 &ndc ) { + int i; + idPlane view; + idPlane clip; + + for ( i = 0 ; i < 4 ; i ++ ) { + view[i] = + global[0] * modelViewMatrix[ i + 0 * 4 ] + + global[1] * modelViewMatrix[ i + 1 * 4 ] + + global[2] * modelViewMatrix[ i + 2 * 4 ] + + modelViewMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i ++ ) { + clip[i] = + view[0] * projectionMatrix[ i + 0 * 4 ] + + view[1] * projectionMatrix[ i + 1 * 4 ] + + view[2] * projectionMatrix[ i + 2 * 4 ] + + view[3] * projectionMatrix[ i + 3 * 4 ]; + } + + ndc[0] = clip[0] / clip[3]; + ndc[1] = clip[1] / clip[3]; + ndc[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); +} + +/* +========================== +R_TransformClipToDevice + +Clip to normalized device coordinates +========================== +*/ +void R_TransformClipToDevice( const idPlane &clip, const viewDef_t *view, idVec3 &normalized ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = clip[2] / clip[3]; +} + + +/* +========================== +myGlMultMatrix +========================== +*/ +void myGlMultMatrix( const float a[16], const float b[16], float out[16] ) { +#if 0 + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +#else + out[0*4+0] = a[0*4+0]*b[0*4+0] + a[0*4+1]*b[1*4+0] + a[0*4+2]*b[2*4+0] + a[0*4+3]*b[3*4+0]; + out[0*4+1] = a[0*4+0]*b[0*4+1] + a[0*4+1]*b[1*4+1] + a[0*4+2]*b[2*4+1] + a[0*4+3]*b[3*4+1]; + out[0*4+2] = a[0*4+0]*b[0*4+2] + a[0*4+1]*b[1*4+2] + a[0*4+2]*b[2*4+2] + a[0*4+3]*b[3*4+2]; + out[0*4+3] = a[0*4+0]*b[0*4+3] + a[0*4+1]*b[1*4+3] + a[0*4+2]*b[2*4+3] + a[0*4+3]*b[3*4+3]; + out[1*4+0] = a[1*4+0]*b[0*4+0] + a[1*4+1]*b[1*4+0] + a[1*4+2]*b[2*4+0] + a[1*4+3]*b[3*4+0]; + out[1*4+1] = a[1*4+0]*b[0*4+1] + a[1*4+1]*b[1*4+1] + a[1*4+2]*b[2*4+1] + a[1*4+3]*b[3*4+1]; + out[1*4+2] = a[1*4+0]*b[0*4+2] + a[1*4+1]*b[1*4+2] + a[1*4+2]*b[2*4+2] + a[1*4+3]*b[3*4+2]; + out[1*4+3] = a[1*4+0]*b[0*4+3] + a[1*4+1]*b[1*4+3] + a[1*4+2]*b[2*4+3] + a[1*4+3]*b[3*4+3]; + out[2*4+0] = a[2*4+0]*b[0*4+0] + a[2*4+1]*b[1*4+0] + a[2*4+2]*b[2*4+0] + a[2*4+3]*b[3*4+0]; + out[2*4+1] = a[2*4+0]*b[0*4+1] + a[2*4+1]*b[1*4+1] + a[2*4+2]*b[2*4+1] + a[2*4+3]*b[3*4+1]; + out[2*4+2] = a[2*4+0]*b[0*4+2] + a[2*4+1]*b[1*4+2] + a[2*4+2]*b[2*4+2] + a[2*4+3]*b[3*4+2]; + out[2*4+3] = a[2*4+0]*b[0*4+3] + a[2*4+1]*b[1*4+3] + a[2*4+2]*b[2*4+3] + a[2*4+3]*b[3*4+3]; + out[3*4+0] = a[3*4+0]*b[0*4+0] + a[3*4+1]*b[1*4+0] + a[3*4+2]*b[2*4+0] + a[3*4+3]*b[3*4+0]; + out[3*4+1] = a[3*4+0]*b[0*4+1] + a[3*4+1]*b[1*4+1] + a[3*4+2]*b[2*4+1] + a[3*4+3]*b[3*4+1]; + out[3*4+2] = a[3*4+0]*b[0*4+2] + a[3*4+1]*b[1*4+2] + a[3*4+2]*b[2*4+2] + a[3*4+3]*b[3*4+2]; + out[3*4+3] = a[3*4+0]*b[0*4+3] + a[3*4+1]*b[1*4+3] + a[3*4+2]*b[2*4+3] + a[3*4+3]*b[3*4+3]; +#endif +} + +/* +================ +R_TransposeGLMatrix +================ +*/ +void R_TransposeGLMatrix( const float in[16], float out[16] ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[i*4+j] = in[j*4+i]; + } + } +} + +/* +================= +R_SetViewMatrix + +Sets up the world to view matrix for a given viewParm +================= +*/ +void R_SetViewMatrix( viewDef_t *viewDef ) { + idVec3 origin; + viewEntity_t *world; + float viewerMatrix[16]; + static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 + }; + + world = &viewDef->worldSpace; + + memset( world, 0, sizeof(*world) ); + + // the model matrix is an identity + world->modelMatrix[0*4+0] = 1; + world->modelMatrix[1*4+1] = 1; + world->modelMatrix[2*4+2] = 1; + + // transform by the camera placement + origin = viewDef->renderView.vieworg; + + viewerMatrix[0] = viewDef->renderView.viewaxis[0][0]; + viewerMatrix[4] = viewDef->renderView.viewaxis[0][1]; + viewerMatrix[8] = viewDef->renderView.viewaxis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = viewDef->renderView.viewaxis[1][0]; + viewerMatrix[5] = viewDef->renderView.viewaxis[1][1]; + viewerMatrix[9] = viewDef->renderView.viewaxis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = viewDef->renderView.viewaxis[2][0]; + viewerMatrix[6] = viewDef->renderView.viewaxis[2][1]; + viewerMatrix[10] = viewDef->renderView.viewaxis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, world->modelViewMatrix ); +} + +/* +=============== +R_SetupProjection + +This uses the "infinite far z" trick +=============== +*/ +void R_SetupProjection( void ) { + float xmin, xmax, ymin, ymax; + float width, height; + float zNear; + float jitterx, jittery; + static idRandom random; + + // random jittering is usefull when multiple + // frames are going to be blended together + // for motion blurred anti-aliasing + if ( r_jitter.GetBool() ) { + jitterx = random.RandomFloat(); + jittery = random.RandomFloat(); + } else { + jitterx = jittery = 0; + } + + // + // set up projection matrix + // + zNear = r_znear.GetFloat(); + if ( tr.viewDef->renderView.cramZNear ) { + zNear *= 0.25; + } + + ymax = zNear * tan( tr.viewDef->renderView.fov_y * idMath::PI / 360.0f ); + ymin = -ymax; + + xmax = zNear * tan( tr.viewDef->renderView.fov_x * idMath::PI / 360.0f ); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + + jitterx = jitterx * width / ( tr.viewDef->viewport.x2 - tr.viewDef->viewport.x1 + 1 ); + xmin += jitterx; + xmax += jitterx; + jittery = jittery * height / ( tr.viewDef->viewport.y2 - tr.viewDef->viewport.y1 + 1 ); + ymin += jittery; + ymax += jittery; + + tr.viewDef->projectionMatrix[0] = 2 * zNear / width; + tr.viewDef->projectionMatrix[4] = 0; + tr.viewDef->projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0 + tr.viewDef->projectionMatrix[12] = 0; + + tr.viewDef->projectionMatrix[1] = 0; + tr.viewDef->projectionMatrix[5] = 2 * zNear / height; + tr.viewDef->projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + tr.viewDef->projectionMatrix[13] = 0; + + // this is the far-plane-at-infinity formulation, and + // crunches the Z range slightly so w=0 vertexes do not + // rasterize right at the wraparound point + tr.viewDef->projectionMatrix[2] = 0; + tr.viewDef->projectionMatrix[6] = 0; + tr.viewDef->projectionMatrix[10] = -0.999f; + tr.viewDef->projectionMatrix[14] = -2.0f * zNear; + + tr.viewDef->projectionMatrix[3] = 0; + tr.viewDef->projectionMatrix[7] = 0; + tr.viewDef->projectionMatrix[11] = -1; + tr.viewDef->projectionMatrix[15] = 0; +} + +/* +================= +R_SetupViewFrustum + +Setup that culling frustum planes for the current view +FIXME: derive from modelview matrix times projection matrix +================= +*/ +static void R_SetupViewFrustum( void ) { + int i; + float xs, xc; + float ang; + + ang = DEG2RAD( tr.viewDef->renderView.fov_x ) * 0.5f; + idMath::SinCos( ang, xs, xc ); + + tr.viewDef->frustum[0] = xs * tr.viewDef->renderView.viewaxis[0] + xc * tr.viewDef->renderView.viewaxis[1]; + tr.viewDef->frustum[1] = xs * tr.viewDef->renderView.viewaxis[0] - xc * tr.viewDef->renderView.viewaxis[1]; + + ang = DEG2RAD( tr.viewDef->renderView.fov_y ) * 0.5f; + idMath::SinCos( ang, xs, xc ); + + tr.viewDef->frustum[2] = xs * tr.viewDef->renderView.viewaxis[0] + xc * tr.viewDef->renderView.viewaxis[2]; + tr.viewDef->frustum[3] = xs * tr.viewDef->renderView.viewaxis[0] - xc * tr.viewDef->renderView.viewaxis[2]; + + // plane four is the front clipping plane + tr.viewDef->frustum[4] = /* vec3_origin - */ tr.viewDef->renderView.viewaxis[0]; + + for ( i = 0; i < 5; i++ ) { + // flip direction so positive side faces out (FIXME: globally unify this) + tr.viewDef->frustum[i] = -tr.viewDef->frustum[i].Normal(); + tr.viewDef->frustum[i][3] = -( tr.viewDef->renderView.vieworg * tr.viewDef->frustum[i].Normal() ); + } + + // eventually, plane five will be the rear clipping plane for fog + + float dNear, dFar, dLeft, dUp; + + dNear = r_znear.GetFloat(); + if ( tr.viewDef->renderView.cramZNear ) { + dNear *= 0.25f; + } + + dFar = MAX_WORLD_SIZE; + dLeft = dFar * tan( DEG2RAD( tr.viewDef->renderView.fov_x * 0.5f ) ); + dUp = dFar * tan( DEG2RAD( tr.viewDef->renderView.fov_y * 0.5f ) ); + tr.viewDef->viewFrustum.SetOrigin( tr.viewDef->renderView.vieworg ); + tr.viewDef->viewFrustum.SetAxis( tr.viewDef->renderView.viewaxis ); + tr.viewDef->viewFrustum.SetSize( dNear, dFar, dLeft, dUp ); +} + +/* +=================== +R_ConstrainViewFrustum +=================== +*/ +static void R_ConstrainViewFrustum( void ) { + idBounds bounds; + + // constrain the view frustum to the total bounds of all visible lights and visible entities + bounds.Clear(); + for ( viewLight_t *vLight = tr.viewDef->viewLights; vLight; vLight = vLight->next ) { + bounds.AddBounds( vLight->lightDef->frustumTris->bounds ); + } + for ( viewEntity_t *vEntity = tr.viewDef->viewEntitys; vEntity; vEntity = vEntity->next ) { + bounds.AddBounds( vEntity->entityDef->referenceBounds ); + } + tr.viewDef->viewFrustum.ConstrainToBounds( bounds ); + + if ( r_useFrustumFarDistance.GetFloat() > 0.0f ) { + tr.viewDef->viewFrustum.MoveFarDistance( r_useFrustumFarDistance.GetFloat() ); + } +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + + +/* +======================= +R_QsortSurfaces + +======================= +*/ +static int R_QsortSurfaces( const void *a, const void *b ) { + const drawSurf_t *ea, *eb; + + ea = *(drawSurf_t **)a; + eb = *(drawSurf_t **)b; + + if ( ea->sort < eb->sort ) { + return -1; + } + if ( ea->sort > eb->sort ) { + return 1; + } + return 0; +} + + +/* +================= +R_SortDrawSurfs +================= +*/ +static void R_SortDrawSurfs( void ) { + // sort the drawsurfs by sort type, then orientation, then shader + qsort( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs, sizeof( tr.viewDef->drawSurfs[0] ), + R_QsortSurfaces ); +} + + + +//======================================================================== + + +//============================================================================== + + + +/* +================ +R_RenderView + +A view may be either the actual camera view, +a mirror / remote location, or a 3D view on a gui surface. + +Parms will typically be allocated with R_FrameAlloc +================ +*/ +void R_RenderView( viewDef_t *parms ) { + viewDef_t *oldView; + + if ( parms->renderView.width <= 0 || parms->renderView.height <= 0 ) { + return; + } + + tr.viewCount++; + + // save view in case we are a subview + oldView = tr.viewDef; + + tr.viewDef = parms; + + tr.sortOffset = 0; + + // set the matrix for world space to eye space + R_SetViewMatrix( tr.viewDef ); + + // the four sides of the view frustum are needed + // for culling and portal visibility + R_SetupViewFrustum(); + + // we need to set the projection matrix before doing + // portal-to-screen scissor box calculations + R_SetupProjection(); + + // identify all the visible portalAreas, and the entityDefs and + // lightDefs that are in them and pass culling. + static_cast(parms->renderWorld)->FindViewLightsAndEntities(); + + // constrain the view frustum to the view lights and entities + R_ConstrainViewFrustum(); + + // make sure that interactions exist for all light / entity combinations + // that are visible + // add any pre-generated light shadows, and calculate the light shader values + R_AddLightSurfaces(); + + // adds ambient surfaces and create any necessary interaction surfaces to add to the light + // lists + R_AddModelSurfaces(); + + // any viewLight that didn't have visible surfaces can have it's shadows removed + R_RemoveUnecessaryViewLights(); + + // sort all the ambient surfaces for translucency ordering + R_SortDrawSurfs(); + + // generate any subviews (mirrors, cameras, etc) before adding this view + if ( R_GenerateSubViews() ) { + // if we are debugging subviews, allow the skipping of the + // main view draw + if ( r_subviewOnly.GetBool() ) { + return; + } + } + + // write everything needed to the demo file + if ( session->writeDemo ) { + static_cast(parms->renderWorld)->WriteVisibleDefs( tr.viewDef ); + } + + // add the rendering commands for this viewDef + R_AddSpecialEffects( parms ); + R_AddDrawViewCmd( parms ); + + // restore view in case we are a subview + tr.viewDef = oldView; +} diff --git a/src/renderer/tr_orderindexes.cpp b/src/renderer/tr_orderindexes.cpp new file mode 100644 index 0000000..13b894d --- /dev/null +++ b/src/renderer/tr_orderindexes.cpp @@ -0,0 +1,214 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +#if 0 +// The Doom 3 seed implementation is retained only as provenance. The Quake 4 +// retail PDB contains tr_orderIndexes.obj but records no functions or data for +// it, and the sole call site in tr_trisurf.cpp is compiled out. + +/* +=============== +R_MeshCost +=============== +*/ + +#define CACHE_SIZE 24 +#define STALL_SIZE 8 +int R_MeshCost( int numIndexes, glIndex_t *indexes ) { + int inCache[CACHE_SIZE]; + int i, j, v; + int c_stalls; + int c_loads; + int fifo; + + for ( i = 0 ; i < CACHE_SIZE ; i++ ) { + inCache[i] = -1; + } + + c_loads = 0; + c_stalls = 0; + fifo = 0; + + for ( i = 0 ; i < numIndexes ; i++ ) { + v = indexes[i]; + for ( j = 0 ; j < CACHE_SIZE ; j++ ) { + if ( inCache[ ( fifo + j ) % CACHE_SIZE ] == v ) { + break; + } + } + if ( j == CACHE_SIZE ) { + c_loads++; + inCache[ fifo % CACHE_SIZE ] = v; + fifo++; + } else if ( j < STALL_SIZE ) { + c_stalls++; + } + } + + return c_loads; +} + + +typedef struct vertRef_s { + struct vertRef_s *next; + int tri; +} vertRef_t; + +/* +==================== +R_OrderIndexes + +Reorganizes the indexes so they will take best advantage +of the internal GPU vertex caches +==================== +*/ +void R_OrderIndexes( int numIndexes, glIndex_t *indexes ) { + bool *triangleUsed; + int numTris; + glIndex_t *oldIndexes; + glIndex_t *base; + int numOldIndexes; + int tri; + int i; + vertRef_t *vref, **vrefs, *vrefTable; + int numVerts; + int v1, v2; + int c_starts; + int c_cost; + + if ( !r_orderIndexes.GetBool() ) { + return; + } + + // save off the original indexes + oldIndexes = (glIndex_t *)_alloca( numIndexes * sizeof( *oldIndexes ) ); + memcpy( oldIndexes, indexes, numIndexes * sizeof( *oldIndexes ) ); + numOldIndexes = numIndexes; + + // make a table to mark the triangles when they are emited + numTris = numIndexes / 3; + triangleUsed = (bool *)_alloca( numTris * sizeof( *triangleUsed ) ); + memset( triangleUsed, 0, numTris * sizeof( *triangleUsed ) ); + + // find the highest vertex number + numVerts = 0; + for ( i = 0 ; i < numIndexes ; i++ ) { + if ( indexes[i] > numVerts ) { + numVerts = indexes[i]; + } + } + numVerts++; + + // create a table of triangles used by each vertex + vrefs = (vertRef_t **)_alloca( numVerts * sizeof( *vrefs ) ); + memset( vrefs, 0, numVerts * sizeof( *vrefs ) ); + + vrefTable = (vertRef_t *)_alloca( numIndexes * sizeof( *vrefTable ) ); + for ( i = 0 ; i < numIndexes ; i++ ) { + tri = i / 3; + + vrefTable[i].tri = tri; + vrefTable[i].next = vrefs[oldIndexes[i]]; + vrefs[oldIndexes[i]] = &vrefTable[i]; + } + + // generate new indexes + numIndexes = 0; + c_starts = 0; + while ( numIndexes != numOldIndexes ) { + // find a triangle that hasn't been used + for ( tri = 0 ; tri < numTris ; tri++ ) { + if ( !triangleUsed[tri] ) { + break; + } + } + if ( tri == numTris ) { + common->Error( "R_OrderIndexes: ran out of unused tris" ); + } + + c_starts++; + + do { + // emit this tri + base = oldIndexes + tri * 3; + indexes[numIndexes+0] = base[0]; + indexes[numIndexes+1] = base[1]; + indexes[numIndexes+2] = base[2]; + numIndexes += 3; + + triangleUsed[tri] = true; + + // try to find a shared edge to another unused tri + for ( i = 0 ; i < 3 ; i++ ) { + v1 = base[i]; + v2 = base[(i+1)%3]; + + for ( vref = vrefs[v1] ; vref ; vref = vref->next ) { + tri = vref->tri; + if ( triangleUsed[tri] ) { + continue; + } + + // if this triangle also uses v2, grab it + if ( oldIndexes[tri*3+0] == v2 + || oldIndexes[tri*3+1] == v2 + || oldIndexes[tri*3+2] == v2 ) { + break; + } + } + if ( vref ) { + break; + } + } + + // if we couldn't chain off of any verts, we need to find a new one + if ( i == 3 ) { + break; + } + } while ( 1 ); + } + + c_cost = R_MeshCost( numIndexes, indexes ); + +} + + +/* + + add all triangles that can be specified by the vertexes in the last 14 cache positions + + pick a new vert to add to the cache + don't pick one in the 24 previous cache positions + try to pick one that will enable the creation of as many triangles as possible + + look for a vert that shares an edge with the vert about to be evicted + + +*/ + +#endif diff --git a/src/renderer/tr_polytope.cpp b/src/renderer/tr_polytope.cpp new file mode 100644 index 0000000..6b54f3a --- /dev/null +++ b/src/renderer/tr_polytope.cpp @@ -0,0 +1,107 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +#define MAX_POLYTOPE_PLANES 6 + +/* +===================== +R_PolytopeSurface + +Generate vertexes and indexes for a polytope, and optionally returns the polygon windings. +The positive sides of the planes will be visible. +===================== +*/ +srfTriangles_t *R_PolytopeSurface( int numPlanes, const idPlane *planes, idWinding **windings ) { + int i, j; + srfTriangles_t *tri; + idFixedWinding planeWindings[MAX_POLYTOPE_PLANES]; + int numVerts, numIndexes; + + if ( numPlanes > MAX_POLYTOPE_PLANES ) { + common->Error( "R_PolytopeSurface: more than %d planes", MAX_POLYTOPE_PLANES ); + } + + numVerts = 0; + numIndexes = 0; + for ( i = 0; i < numPlanes; i++ ) { + const idPlane &plane = planes[i]; + idFixedWinding &w = planeWindings[i]; + + w.BaseForPlane( plane ); + for ( j = 0; j < numPlanes; j++ ) { + const idPlane &plane2 = planes[j]; + if ( j == i ) { + continue; + } + if ( !w.ClipInPlace( -plane2, ON_EPSILON ) ) { + break; + } + } + if ( w.GetNumPoints() <= 2 ) { + continue; + } + numVerts += w.GetNumPoints(); + numIndexes += ( w.GetNumPoints() - 2 ) * 3; + } + + // allocate the surface + tri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( tri, numVerts ); + R_AllocStaticTriSurfIndexes( tri, numIndexes ); + + // copy the data from the windings + for ( i = 0; i < numPlanes; i++ ) { + idFixedWinding &w = planeWindings[i]; + if ( !w.GetNumPoints() ) { + continue; + } + for ( j = 0 ; j < w.GetNumPoints() ; j++ ) { + tri->verts[tri->numVerts + j ].Clear(); + tri->verts[tri->numVerts + j ].xyz = w[j].ToVec3(); + } + + for ( j = 1 ; j < w.GetNumPoints() - 1 ; j++ ) { + tri->indexes[ tri->numIndexes + 0 ] = tri->numVerts; + tri->indexes[ tri->numIndexes + 1 ] = tri->numVerts + j; + tri->indexes[ tri->numIndexes + 2 ] = tri->numVerts + j + 1; + tri->numIndexes += 3; + } + tri->numVerts += w.GetNumPoints(); + + // optionally save the winding + if ( windings ) { + windings[i] = new idWinding( w.GetNumPoints() ); + *windings[i] = w; + } + } + + R_BoundTriSurf( tri ); + + return tri; +} diff --git a/src/renderer/tr_render.cpp b/src/renderer/tr_render.cpp new file mode 100644 index 0000000..f321f3a --- /dev/null +++ b/src/renderer/tr_render.cpp @@ -0,0 +1,965 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" + +/* + + back end scene + lights rendering functions + +*/ + +idVec4 showBatchSizeReallyBadColor( 1.0f, 0.0f, 1.0f, 1.0f ); +idVec4 showBatchSizeBadColor( 1.0f, 0.0f, 0.0f, 1.0f ); +idVec4 showBatchSizeModerateColor( 1.0f, 1.0f, 0.0f, 1.0f ); +idVec4 showBatchSizeGoodColor( 0.0f, 1.0f, 0.0f, 1.0f ); + + +/* +================= +RB_DrawElementsImmediate + +Draws with immediate mode commands, which is going to be very slow. +This should never happen if the vertex cache is operating properly. +================= +*/ +void RB_DrawElementsImmediate( const srfTriangles_t *tri ) { + + backEnd.pc.c_drawElements++; + backEnd.pc.c_drawIndexes += tri->numIndexes; + backEnd.pc.c_drawVertexes += tri->numVerts; + + if ( tri->ambientSurface != NULL ) { + if ( tri->indexes == tri->ambientSurface->indexes ) { + backEnd.pc.c_drawRefIndexes += tri->numIndexes; + } + if ( tri->verts == tri->ambientSurface->verts ) { + backEnd.pc.c_drawRefVertexes += tri->numVerts; + } + } + if ( !tri->numIndexes || !tri->numVerts || !tri->verts ) { + return; + } + + qglBegin( GL_TRIANGLES ); + for ( int i = 0 ; i < tri->numIndexes ; i++ ) { + qglTexCoord2fv( tri->verts[ tri->indexes[i] ].st.ToFloatPtr() ); + qglVertex3fv( tri->verts[ tri->indexes[i] ].xyz.ToFloatPtr() ); + } + qglEnd(); +} + + +/* +================ +RB_DrawElementsWithCounters +================ +*/ +void RB_DrawElementsWithCounters( const srfTriangles_t *tri ) { + if ( tri->primBatchMesh ) { + if ( !tri->primBatchMesh->m_drawSetUp ) { + tri->primBatchMesh->SetupForDrawRender( NULL ); + } + if ( tri->indexes ) { + tri->primBatchMesh->Draw( tri->skinToModelTransforms, tri->indexes, + tri->numIndexes, NULL ); + } else { + tri->primBatchMesh->Draw( tri->skinToModelTransforms, NULL ); + } + return; + } + + backEnd.pc.c_drawElements++; + backEnd.pc.c_drawIndexes += tri->numIndexes; + backEnd.pc.c_drawVertexes += tri->numVerts; + + if ( tri->ambientSurface != NULL ) { + if ( tri->indexes == tri->ambientSurface->indexes ) { + backEnd.pc.c_drawRefIndexes += tri->numIndexes; + } + if ( tri->verts == tri->ambientSurface->verts ) { + backEnd.pc.c_drawRefVertexes += tri->numVerts; + } + } + + if ( tri->indexCache && r_useIndexBuffers.GetBool() ) { + qglDrawElements( GL_TRIANGLES, + r_singleTriangle.GetBool() ? 3 : tri->numIndexes, + GL_INDEX_TYPE, + (int *)vertexCache.Position( tri->indexCache ) ); + backEnd.pc.c_vboIndexes += tri->numIndexes; + } else { + if ( r_useIndexBuffers.GetBool() ) { + vertexCache.UnbindIndex(); + } + qglDrawElements( GL_TRIANGLES, + r_singleTriangle.GetBool() ? 3 : tri->numIndexes, + GL_INDEX_TYPE, + tri->indexes ); + } +} + +/* +================ +RB_DrawShadowElementsWithCounters + +May not use all the indexes in the surface if caps are skipped +================ +*/ +void RB_DrawShadowElementsWithCounters( const drawSurf_t *surf, int numIndexes ) { + const srfTriangles_t *tri = surf->geo; + if ( tri->primBatchMesh ) { + RB_ARB2_MD5R_DrawShadowElements( surf, numIndexes ); + return; + } + GL_VertexAttribState( 1 ); + backEnd.pc.c_shadowElements++; + backEnd.pc.c_shadowIndexes += numIndexes; + backEnd.pc.c_shadowVertexes += tri->numVerts; + + if ( tri->indexCache && r_useIndexBuffers.GetBool() ) { + qglDrawElements( GL_TRIANGLES, + r_singleTriangle.GetBool() ? 3 : numIndexes, + GL_INDEX_TYPE, + (int *)vertexCache.Position( tri->indexCache ) ); + backEnd.pc.c_vboIndexes += numIndexes; + } else { + if ( r_useIndexBuffers.GetBool() ) { + vertexCache.UnbindIndex(); + } + qglDrawElements( GL_TRIANGLES, + r_singleTriangle.GetBool() ? 3 : numIndexes, + GL_INDEX_TYPE, + tri->indexes ); + } +} + + +/* +=============== +RB_RenderTriangleSurface + +Sets texcoord and vertex pointers +=============== +*/ +void RB_RenderTriangleSurface( const srfTriangles_t *tri ) { + if ( !tri->ambientCache ) { + RB_DrawElementsImmediate( tri ); + return; + } + + + idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), ac->st.ToFloatPtr() ); + + RB_DrawElementsWithCounters( tri ); +} + +/* +=============== +RB_T_RenderTriangleSurface + +=============== +*/ +void RB_T_RenderTriangleSurface( const drawSurf_t *surf ) { + RB_RenderTriangleSurface( surf->geo ); +} + +void RB_T_ShowBatchSizeRenderTriangleSurface( const drawSurf_t *surf ) { + idVec4 color; + const int numIndexes = surf->geo->numIndexes; + if ( numIndexes <= 30 ) { + color = showBatchSizeReallyBadColor; + } else if ( numIndexes <= 600 ) { + color.Lerp( showBatchSizeBadColor, showBatchSizeModerateColor, + (float)numIndexes / 600.0f ); + } else if ( numIndexes <= 1500 ) { + color.Lerp( showBatchSizeModerateColor, showBatchSizeGoodColor, + (float)numIndexes / 1500.0f ); + } else { + color = showBatchSizeGoodColor; + } + qglColor4f( color[0], color[1], color[2], 1.0f ); + RB_RenderTriangleSurface( surf->geo ); +} + +void RB_T_ShowTriRenderTriangleSurface( const drawSurf_t *surf ) { + if ( !( com_editors & EDITOR_MODVIEW ) ) { + RB_RenderTriangleSurface( surf->geo ); + return; + } + if ( r_showTris.GetBool() || surf->geo->modviewSelected ) { + if ( surf->geo->modviewSelected ) { + qglColor4f( 0.0f, 1.0f, 0.0f, 1.0f ); + } else { + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + } + RB_RenderTriangleSurface( surf->geo ); + } +} + +void RB_T_RenderSimpleSurface( const drawSurf_t *surf ) { + GL_State( 0 ); + qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + qglColor4f( 1.0f, 1.0f, 1.0f, surf->material->GetEditorAlpha() ); + surf->material->GetEditorImage()->Bind(); + RB_RenderTriangleSurface( surf->geo ); +} + +void RB_T_RenderTriangleSurfacePasses( const drawSurf_t *surf ) { + int passes = surf->material ? surf->material->GetNumStages() : 0; + if ( r_showOverDraw.GetInteger() > 1 && passes ) { + passes = 1; + } + for ( int i = 0; i < passes; ++i ) { + RB_RenderTriangleSurface( surf->geo ); + } +} + +/* +=============== +RB_EnterWeaponDepthHack +=============== +*/ +void RB_EnterWeaponDepthHack() { + qglDepthRange( 0, 0.5 ); + backEnd.projectionMatrix[14] *= 0.25f; + + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.projectionMatrix ); + backEnd.mvpSpace = NULL; + qglMatrixMode(GL_MODELVIEW); +} + +/* +=============== +RB_EnterModelDepthHack +=============== +*/ +void RB_EnterModelDepthHack( float depth ) { + qglDepthRange( 0.0f, 1.0f ); + backEnd.projectionMatrix[14] -= depth; + + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.projectionMatrix ); + backEnd.mvpSpace = NULL; + qglMatrixMode(GL_MODELVIEW); +} + +/* +=============== +RB_LeaveDepthHack +=============== +*/ +void RB_LeaveDepthHack() { + qglDepthRange( 0, 1 ); + backEnd.projectionMatrix[14] = backEnd.viewDef->projectionMatrix[14]; + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.projectionMatrix ); + backEnd.mvpSpace = NULL; + qglMatrixMode(GL_MODELVIEW); +} + +/* +==================== +RB_RenderDrawSurfListWithFunction + +The triangle functions can check backEnd.currentSpace != surf->space +to see if they need to perform any new matrix setup. The modelview +matrix will already have been loaded, and backEnd.currentSpace will +be updated after the triangle function completes. +==================== +*/ +void RB_RenderDrawSurfListWithFunction( drawSurf_t **drawSurfs, int numDrawSurfs, + void (*triFunc_)( const drawSurf_t *) ) { + int i; + const drawSurf_t *drawSurf; + + backEnd.currentSpace = NULL; + + for (i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + // change the matrix if needed + if ( drawSurf->space != backEnd.currentSpace ) { + qglLoadMatrixf( drawSurf->space->modelViewMatrix ); + } + + if ( drawSurf->space->weaponDepthHackInViewID && + drawSurf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) { + RB_EnterWeaponDepthHack(); + } + + if ( drawSurf->space->modelDepthHack != 0.0f ) { + RB_EnterModelDepthHack( drawSurf->space->modelDepthHack ); + } + + // change the scissor if needed + if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( drawSurf->scissorRect ) ) { + backEnd.currentScissor = drawSurf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + + // render it + triFunc_( drawSurf ); + + if ( ( drawSurf->space->weaponDepthHackInViewID && + drawSurf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) || + drawSurf->space->modelDepthHack != 0.0f ) { + RB_LeaveDepthHack(); + } + + backEnd.currentSpace = drawSurf->space; + } +} + +/* +====================== +RB_RenderDrawSurfChainWithFunction +====================== +*/ +void RB_RenderDrawSurfChainWithFunction( const drawSurf_t *drawSurfs, + void (*triFunc_)( const drawSurf_t *) ) { + const drawSurf_t *drawSurf; + + backEnd.currentSpace = NULL; + + for ( drawSurf = drawSurfs ; drawSurf ; drawSurf = drawSurf->nextOnLight ) { + // change the matrix if needed + if ( drawSurf->space != backEnd.currentSpace ) { + qglLoadMatrixf( drawSurf->space->modelViewMatrix ); + } + + if ( drawSurf->space->weaponDepthHackInViewID && + drawSurf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) { + RB_EnterWeaponDepthHack(); + } + + if ( drawSurf->space->modelDepthHack ) { + RB_EnterModelDepthHack( drawSurf->space->modelDepthHack ); + } + + // change the scissor if needed + if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( drawSurf->scissorRect ) ) { + backEnd.currentScissor = drawSurf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + + // render it + triFunc_( drawSurf ); + + if ( ( drawSurf->space->weaponDepthHackInViewID && + drawSurf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) || + drawSurf->space->modelDepthHack != 0.0f ) { + RB_LeaveDepthHack(); + } + + backEnd.currentSpace = drawSurf->space; + } +} + +/* +====================== +RB_GetShaderTextureMatrix +====================== +*/ +void RB_GetShaderTextureMatrix( const float *shaderRegisters, + const textureStage_t *texture, float matrix[16] ) { + matrix[0] = shaderRegisters[ texture->matrix[0][0] ]; + matrix[4] = shaderRegisters[ texture->matrix[0][1] ]; + matrix[8] = 0; + matrix[12] = shaderRegisters[ texture->matrix[0][2] ]; + + // we attempt to keep scrolls from generating incredibly large texture values, but + // center rotations and center scales can still generate offsets that need to be > 1 + if ( matrix[12] < -40 || matrix[12] > 40 ) { + matrix[12] -= (int)matrix[12]; + } + + matrix[1] = shaderRegisters[ texture->matrix[1][0] ]; + matrix[5] = shaderRegisters[ texture->matrix[1][1] ]; + matrix[9] = 0; + matrix[13] = shaderRegisters[ texture->matrix[1][2] ]; + if ( matrix[13] < -40 || matrix[13] > 40 ) { + matrix[13] -= (int)matrix[13]; + } + + matrix[2] = 0; + matrix[6] = 0; + matrix[10] = 1; + matrix[14] = 0; + + matrix[3] = 0; + matrix[7] = 0; + matrix[11] = 0; + matrix[15] = 1; +} + +/* +====================== +RB_LoadShaderTextureMatrix +====================== +*/ +void RB_LoadShaderTextureMatrix( const float *shaderRegisters, const textureStage_t *texture ) { + float matrix[16]; + + RB_GetShaderTextureMatrix( shaderRegisters, texture, matrix ); + qglMatrixMode( GL_TEXTURE ); + qglLoadMatrixf( matrix ); + qglMatrixMode( GL_MODELVIEW ); +} + +/* +====================== +RB_BindVariableStageImage + +Handles generating a cinematic frame if needed +====================== +*/ +void RB_BindVariableStageImage( const textureStage_t *texture ) { + if ( texture->cinematic ) { + cinData_t cin; + + if ( r_skipDynamicTextures.GetBool() ) { + globalImages->defaultImage->Bind(); + return; + } + + // offset time by shaderParm[7] (FIXME: make the time offset a parameter of the shader?) + // We make no attempt to optimize for multiple identical cinematics being in view, or + // for cinematics going at a lower framerate than the renderer. + cin = texture->cinematic->ImageForTime( (int)(1000 * ( backEnd.viewDef->floatTime + backEnd.viewDef->renderView.shaderParms[11] ) ) ); + + if ( cin.image ) { + globalImages->cinematicImage->UploadScratch( cin.image, cin.imageWidth, cin.imageHeight ); + } else { + globalImages->blackImage->Bind(); + } + } else { + if ( r_skipTextures.GetBool() ) { + globalImages->defaultImage->Bind(); + return; + } + if (texture->image) { + texture->image->Bind(); + } + } +} + +/* +====================== +RB_BindStageTexture +====================== +*/ +void RB_BindStageTexture( const float *shaderRegisters, const textureStage_t *texture, const drawSurf_t *surf ) { + // image + RB_BindVariableStageImage( texture ); + + // texgens + if ( texture->texgen == TG_DIFFUSE_CUBE ) { + qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ((idDrawVert *)vertexCache.Position( surf->geo->ambientCache ))->normal.ToFloatPtr() ); + } + if ( texture->texgen == TG_SKYBOX_CUBE || texture->texgen == TG_WOBBLESKY_CUBE ) { + qglTexCoordPointer( 3, GL_FLOAT, 0, vertexCache.Position( surf->dynamicTexCoords ) ); + } + if ( texture->texgen == TG_REFLECT_CUBE ) { + qglEnable( GL_TEXTURE_GEN_S ); + qglEnable( GL_TEXTURE_GEN_T ); + qglEnable( GL_TEXTURE_GEN_R ); + qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); + qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); + qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); + GL_EnableVertexAttribState( 2 ); + qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ((idDrawVert *)vertexCache.Position( surf->geo->ambientCache ))->normal.ToFloatPtr() ); + + qglMatrixMode( GL_TEXTURE ); + float mat[16]; + + R_TransposeGLMatrix( backEnd.viewDef->worldSpace.modelViewMatrix, mat ); + + qglLoadMatrixf( mat ); + qglMatrixMode( GL_MODELVIEW ); + } + + // matrix + if ( texture->hasMatrix ) { + RB_LoadShaderTextureMatrix( shaderRegisters, texture ); + } +} + +/* +====================== +RB_FinishStageTexture +====================== +*/ +void RB_FinishStageTexture( const textureStage_t *texture, const drawSurf_t *surf ) { + if ( texture->texgen == TG_DIFFUSE_CUBE || texture->texgen == TG_SKYBOX_CUBE + || texture->texgen == TG_WOBBLESKY_CUBE ) { + qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), + (void *)&(((idDrawVert *)vertexCache.Position( surf->geo->ambientCache ))->st) ); + } + + if ( texture->texgen == TG_REFLECT_CUBE ) { + qglDisable( GL_TEXTURE_GEN_S ); + qglDisable( GL_TEXTURE_GEN_T ); + qglDisable( GL_TEXTURE_GEN_R ); + qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); + GL_DisableVertexAttribState( 2 ); + + qglMatrixMode( GL_TEXTURE ); + qglLoadIdentity(); + qglMatrixMode( GL_MODELVIEW ); + } + + if ( texture->hasMatrix ) { + qglMatrixMode( GL_TEXTURE ); + qglLoadIdentity(); + qglMatrixMode( GL_MODELVIEW ); + } +} + + + +//============================================================================================= + + +/* +================= +RB_DetermineLightScale + +Sets: +backEnd.lightScale +backEnd.overBright + +Find out how much we are going to need to overscale the lighting, so we +can down modulate the pre-lighting passes. + +We only look at light calculations, but an argument could be made that +we should also look at surface evaluations, which would let surfaces +overbright past 1.0 +================= +*/ +void RB_DetermineLightScale( void ) { + viewLight_t *vLight; + const idMaterial *shader; + float max; + int i, j, numStages; + const shaderStage_t *stage; + + // the light scale will be based on the largest color component of any surface + // that will be drawn. + // should we consider separating rgb scales? + + // if there are no lights, this will remain at 1.0, so GUI-only + // rendering will not lose any bits of precision + max = 1.0; + + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + // lights with no surfaces or shaderparms may still be present + // for debug display + if ( !vLight->localInteractions && !vLight->globalInteractions + && !vLight->translucentInteractions ) { + continue; + } + + shader = vLight->lightShader; + numStages = shader->GetNumStages(); + for ( i = 0 ; i < numStages ; i++ ) { + stage = shader->GetStage( i ); + for ( j = 0 ; j < 3 ; j++ ) { + float v = r_lightScale.GetFloat() * vLight->shaderRegisters[ stage->color.registers[j] ]; + if ( v > max ) { + max = v; + } + } + } + } + + backEnd.pc.maxLightValue = max; + if ( max <= tr.backEndRendererMaxLight ) { + backEnd.lightScale = r_lightScale.GetFloat(); + backEnd.overBright = 1.0; + } else { + backEnd.lightScale = r_lightScale.GetFloat() * tr.backEndRendererMaxLight / max; + backEnd.overBright = max / tr.backEndRendererMaxLight; + } +} + + +/* +================= +RB_BeginDrawingView + +Any mirrored or portaled views have already been drawn, so prepare +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView (void) { + // set the modelview matrix for the viewer + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.viewDef->projectionMatrix ); + qglMatrixMode(GL_MODELVIEW); + + // set the window clipping + qglViewport( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1, + tr.viewportOffset[1] + backEnd.viewDef->viewport.y1, + backEnd.viewDef->viewport.x2 + 1 - backEnd.viewDef->viewport.x1, + backEnd.viewDef->viewport.y2 + 1 - backEnd.viewDef->viewport.y1 ); + + // the scissor may be smaller than the viewport for subviews + qglScissor( tr.viewportOffset[0] + backEnd.viewDef->viewport.x1 + backEnd.viewDef->scissor.x1, + tr.viewportOffset[1] + backEnd.viewDef->viewport.y1 + backEnd.viewDef->scissor.y1, + backEnd.viewDef->scissor.x2 + 1 - backEnd.viewDef->scissor.x1, + backEnd.viewDef->scissor.y2 + 1 - backEnd.viewDef->scissor.y1 ); + backEnd.currentScissor = backEnd.viewDef->scissor; + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + + // we don't have to clear the depth / stencil buffer for 2D rendering + if ( backEnd.viewDef->viewEntitys ) { + qglStencilMask( 0xff ); + // some cards may have 7 bit stencil buffers, so don't assume this + // should be 128 + qglClearStencil( 1<<(glConfig.stencilBits-1) ); + qglClear( GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + qglEnable( GL_DEPTH_TEST ); + } else { + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_STENCIL_TEST ); + } + + backEnd.glState.faceCulling = -1; // force face culling to set next time + GL_Cull( CT_FRONT_SIDED ); + +} + +/* +================== +R_SetDrawInteractions +================== +*/ +void R_SetDrawInteraction( const shaderStage_t *surfaceStage, const float *surfaceRegs, + idImage **image, idVec4 matrix[2], float color[4] ) { + *image = surfaceStage->texture.image; + if ( surfaceStage->texture.hasMatrix ) { + matrix[0][0] = surfaceRegs[surfaceStage->texture.matrix[0][0]]; + matrix[0][1] = surfaceRegs[surfaceStage->texture.matrix[0][1]]; + matrix[0][2] = 0; + matrix[0][3] = surfaceRegs[surfaceStage->texture.matrix[0][2]]; + + matrix[1][0] = surfaceRegs[surfaceStage->texture.matrix[1][0]]; + matrix[1][1] = surfaceRegs[surfaceStage->texture.matrix[1][1]]; + matrix[1][2] = 0; + matrix[1][3] = surfaceRegs[surfaceStage->texture.matrix[1][2]]; + + // we attempt to keep scrolls from generating incredibly large texture values, but + // center rotations and center scales can still generate offsets that need to be > 1 + if ( matrix[0][3] < -40 || matrix[0][3] > 40 ) { + matrix[0][3] -= (int)matrix[0][3]; + } + if ( matrix[1][3] < -40 || matrix[1][3] > 40 ) { + matrix[1][3] -= (int)matrix[1][3]; + } + } else { + matrix[0][0] = 1; + matrix[0][1] = 0; + matrix[0][2] = 0; + matrix[0][3] = 0; + + matrix[1][0] = 0; + matrix[1][1] = 1; + matrix[1][2] = 0; + matrix[1][3] = 0; + } + + if ( color ) { + for ( int i = 0 ; i < 4 ; i++ ) { + color[i] = surfaceRegs[surfaceStage->color.registers[i]]; + // clamp here, so card with greater range don't look different. + // we could perform overbrighting like we do for lights, but + // it doesn't currently look worth it. + if ( color[i] < 0 ) { + color[i] = 0; + } else if ( color[i] > 1.0 ) { + color[i] = 1.0; + } + } + } +} + +/* +================= +RB_SubmittInteraction +================= +*/ +static void RB_SubmittInteraction( drawInteraction_t *din, void (*DrawInteraction)(const drawInteraction_t *) ) { + if ( !din->bumpImage ) { + return; + } + + if ( !din->diffuseImage || r_skipDiffuse.GetBool() || r_skipTextures.GetBool() ) { + din->diffuseImage = globalImages->blackImage; + } + if ( !din->specularImage || r_skipSpecular.GetBool() || din->ambientLight || r_skipTextures.GetBool() ) { + din->specularImage = globalImages->blackImage; + } + if ( !din->bumpImage || r_skipBump.GetBool() || r_skipTextures.GetBool() ) { + din->bumpImage = globalImages->flatNormalMap; + } + + // if we wouldn't draw anything, don't call the Draw function + if ( + ( ( din->diffuseColor[0] > 0 || + din->diffuseColor[1] > 0 || + din->diffuseColor[2] > 0 ) && din->diffuseImage != globalImages->blackImage ) + || ( ( din->specularColor[0] > 0 || + din->specularColor[1] > 0 || + din->specularColor[2] > 0 ) && din->specularImage != globalImages->blackImage ) ) { + DrawInteraction( din ); + } +} + +/* +============= +RB_CreateSingleDrawInteractions + +This can be used by different draw_* backends to decompose a complex light / surface +interaction into primitive interactions +============= +*/ +void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInteraction)(const drawInteraction_t *) ) { + const idMaterial *surfaceShader = surf->material; + const float *surfaceRegs = surf->shaderRegisters; + const viewLight_t *vLight = backEnd.vLight; + const idMaterial *lightShader = vLight->lightShader; + const float *lightRegs = vLight->shaderRegisters; + drawInteraction_t inter; + + if ( r_skipInteractions.GetBool() || !surf->geo || + ( !surf->geo->ambientCache && !surf->geo->primBatchMesh ) ) { + return; + } + + // change the matrix and light projection vectors if needed + if ( surf->space != backEnd.currentSpace ) { + backEnd.currentSpace = surf->space; + qglLoadMatrixf( surf->space->modelViewMatrix ); + } + + // change the scissor if needed + if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( surf->scissorRect ) ) { + backEnd.currentScissor = surf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } + + // hack depth range if needed + if ( surf->space->weaponDepthHackInViewID && + surf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) { + RB_EnterWeaponDepthHack(); + } + + if ( surf->space->modelDepthHack ) { + RB_EnterModelDepthHack( surf->space->modelDepthHack ); + } + + inter.surf = surf; + inter.lightFalloffImage = vLight->falloffImage; + + R_GlobalPointToLocal( surf->space->modelMatrix, vLight->globalLightOrigin, inter.localLightOrigin.ToVec3() ); + R_GlobalPointToLocal( surf->space->modelMatrix, backEnd.viewDef->renderView.vieworg, inter.localViewOrigin.ToVec3() ); + inter.localLightOrigin[3] = 0; + inter.localViewOrigin[3] = 1; + inter.ambientLight = lightShader->IsAmbientLight(); + + // the base projections may be modified by texture matrix on light stages + idPlane lightProject[4]; + for ( int i = 0 ; i < 4 ; i++ ) { + R_GlobalPlaneToLocal( surf->space->modelMatrix, backEnd.vLight->lightProject[i], lightProject[i] ); + } + + for ( int lightStageNum = 0 ; lightStageNum < lightShader->GetNumStages() ; lightStageNum++ ) { + const shaderStage_t *lightStage = lightShader->GetStage( lightStageNum ); + + // ignore stages that fail the condition + if ( lightRegs && !lightRegs[ lightStage->conditionRegister ] ) { + continue; + } + + inter.lightImage = lightStage->texture.image; + + memcpy( inter.lightProjection, lightProject, sizeof( inter.lightProjection ) ); + // now multiply the texgen by the light texture matrix + if ( lightStage->texture.hasMatrix ) { + RB_GetShaderTextureMatrix( lightRegs, &lightStage->texture, backEnd.lightTextureMatrix ); + RB_BakeTextureMatrixIntoTexgen( reinterpret_cast(inter.lightProjection), backEnd.lightTextureMatrix ); + } + + inter.bumpImage = NULL; + inter.specularImage = NULL; + inter.diffuseImage = NULL; + inter.diffuseColor[0] = inter.diffuseColor[1] = inter.diffuseColor[2] = inter.diffuseColor[3] = 0; + inter.specularColor[0] = inter.specularColor[1] = inter.specularColor[2] = inter.specularColor[3] = 0; + + float lightColor[4]; + + // backEnd.lightScale is calculated so that lightColor[] will never exceed + // tr.backEndRendererMaxLight + lightColor[0] = backEnd.lightScale * lightRegs[ lightStage->color.registers[0] ]; + lightColor[1] = backEnd.lightScale * lightRegs[ lightStage->color.registers[1] ]; + lightColor[2] = backEnd.lightScale * lightRegs[ lightStage->color.registers[2] ]; + lightColor[3] = lightRegs[ lightStage->color.registers[3] ]; + + // go through the individual stages + for ( int surfaceStageNum = 0 ; surfaceStageNum < surfaceShader->GetNumStages() ; surfaceStageNum++ ) { + const shaderStage_t *surfaceStage = surfaceShader->GetStage( surfaceStageNum ); + + switch( surfaceStage->lighting ) { + case SL_AMBIENT: { + // ignore ambient stages while drawing interactions + break; + } + case SL_BUMP: { + // ignore stage that fails the condition + if ( !surfaceRegs[ surfaceStage->conditionRegister ] ) { + break; + } + // draw any previous interaction + RB_SubmittInteraction( &inter, DrawInteraction ); + inter.diffuseImage = NULL; + inter.specularImage = NULL; + R_SetDrawInteraction( surfaceStage, surfaceRegs, &inter.bumpImage, inter.bumpMatrix, NULL ); + break; + } + case SL_DIFFUSE: { + // ignore stage that fails the condition + if ( !surfaceRegs[ surfaceStage->conditionRegister ] ) { + break; + } + if ( inter.diffuseImage ) { + RB_SubmittInteraction( &inter, DrawInteraction ); + } + R_SetDrawInteraction( surfaceStage, surfaceRegs, &inter.diffuseImage, + inter.diffuseMatrix, inter.diffuseColor.ToFloatPtr() ); + inter.diffuseColor[0] *= lightColor[0]; + inter.diffuseColor[1] *= lightColor[1]; + inter.diffuseColor[2] *= lightColor[2]; + inter.diffuseColor[3] *= lightColor[3]; + inter.vertexColor = surfaceStage->vertexColor; + break; + } + case SL_SPECULAR: { + // ignore stage that fails the condition + if ( !surfaceRegs[ surfaceStage->conditionRegister ] ) { + break; + } + if ( inter.specularImage ) { + RB_SubmittInteraction( &inter, DrawInteraction ); + } + R_SetDrawInteraction( surfaceStage, surfaceRegs, &inter.specularImage, + inter.specularMatrix, inter.specularColor.ToFloatPtr() ); + inter.specularColor[0] *= lightColor[0]; + inter.specularColor[1] *= lightColor[1]; + inter.specularColor[2] *= lightColor[2]; + inter.specularColor[3] *= lightColor[3]; + inter.vertexColor = surfaceStage->vertexColor; + break; + } + } + } + + // draw the final interaction + RB_SubmittInteraction( &inter, DrawInteraction ); + } + + // unhack depth range if needed + if ( ( surf->space->weaponDepthHackInViewID && + surf->space->weaponDepthHackInViewID == backEnd.viewDef->renderView.viewID ) || + surf->space->modelDepthHack != 0.0f ) { + RB_LeaveDepthHack(); + } +} + +/* +============= +RB_DrawView +============= +*/ +void RB_DrawView( const void *data ) { + const drawSurfsCommand_t *cmd; + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.viewDef = cmd->viewDef; + memcpy( backEnd.projectionMatrix, backEnd.viewDef->projectionMatrix, + sizeof( backEnd.projectionMatrix ) ); + backEnd.mvpSpace = NULL; + + // if there aren't any drawsurfs, do nothing + if ( !backEnd.viewDef->numDrawSurfs ) { + return; + } + + // skip render bypasses everything that has models, assuming + // them to be 3D views, but leaves 2D rendering visible + if ( r_skipRender.GetBool() && backEnd.viewDef->viewEntitys ) { + return; + } + + // skip render context sets the wgl context to NULL, + // which should factor out the API cost, under the assumption + // that all gl calls just return if the context isn't valid + if ( r_skipRenderContext.GetBool() && backEnd.viewDef->viewEntitys ) { + GLimp_DeactivateContext(); + } + + backEnd.pc.c_surfaces += backEnd.viewDef->numDrawSurfs; + + RB_ShowOverdraw(); + + // render the scene, jumping to the hardware specific interaction renderers + RB_STD_DrawView(); + + // restore the context for 2D drawing if we were stubbing it out + if ( r_skipRenderContext.GetBool() && backEnd.viewDef->viewEntitys ) { + GLimp_ActivateContext(); + RB_SetDefaultGLState(); + } +} diff --git a/src/renderer/tr_rendertools.cpp b/src/renderer/tr_rendertools.cpp new file mode 100644 index 0000000..8024c0f --- /dev/null +++ b/src/renderer/tr_rendertools.cpp @@ -0,0 +1,2395 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "simplex.h" // line font definition + +#define MAX_DEBUG_LINES 16384 + +typedef struct debugLine_s { + idVec4 rgb; + idVec3 start; + idVec3 end; + bool depthTest; + int lifeTime; +} debugLine_t; + +debugLine_t rb_debugLines[ MAX_DEBUG_LINES ]; +int rb_numDebugLines = 0; +int rb_debugLineTime = 0; + +#define MAX_DEBUG_TEXT 512 + +typedef struct debugText_s { + idStr text; + idVec3 origin; + float scale; + idVec4 color; + idMat3 viewAxis; + int align; + int lifeTime; + bool depthTest; +} debugText_t; + +debugText_t rb_debugText[ MAX_DEBUG_TEXT ]; +int rb_numDebugText = 0; +int rb_debugTextTime = 0; + +#define MAX_DEBUG_POLYGONS 8192 + +typedef struct debugPolygon_s { + idVec4 rgb; + idWinding winding; + bool depthTest; + int lifeTime; +} debugPolygon_t; + +debugPolygon_t rb_debugPolygons[ MAX_DEBUG_POLYGONS ]; +int rb_numDebugPolygons = 0; +int rb_debugPolygonTime = 0; + +static void RB_DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align ); + +/* +================ +RB_DrawBounds +================ +*/ +void RB_DrawBounds( const idBounds &bounds ) { + if ( bounds.IsCleared() ) { + return; + } + + qglBegin( GL_LINE_LOOP ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[0][2] ); + qglVertex3f( bounds[0][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[0][1], bounds[0][2] ); + qglEnd(); + qglBegin( GL_LINE_LOOP ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[1][2] ); + qglVertex3f( bounds[0][0], bounds[1][1], bounds[1][2] ); + qglVertex3f( bounds[1][0], bounds[1][1], bounds[1][2] ); + qglVertex3f( bounds[1][0], bounds[0][1], bounds[1][2] ); + qglEnd(); + + qglBegin( GL_LINES ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[0][2] ); + qglVertex3f( bounds[0][0], bounds[0][1], bounds[1][2] ); + + qglVertex3f( bounds[0][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[0][0], bounds[1][1], bounds[1][2] ); + + qglVertex3f( bounds[1][0], bounds[0][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[0][1], bounds[1][2] ); + + qglVertex3f( bounds[1][0], bounds[1][1], bounds[0][2] ); + qglVertex3f( bounds[1][0], bounds[1][1], bounds[1][2] ); + qglEnd(); +} + + +/* +================ +RB_SimpleSurfaceSetup +================ +*/ +void RB_SimpleSurfaceSetup( const drawSurf_t *drawSurf ) { + // change the matrix if needed + if ( drawSurf->space != backEnd.currentSpace ) { + qglLoadMatrixf( drawSurf->space->modelViewMatrix ); + backEnd.currentSpace = drawSurf->space; + } + + // change the scissor if needed + if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( drawSurf->scissorRect ) ) { + backEnd.currentScissor = drawSurf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + } +} + +/* +================ +RB_SimpleWorldSetup +================ +*/ +void RB_SimpleWorldSetup( void ) { + backEnd.currentSpace = &backEnd.viewDef->worldSpace; + qglLoadMatrixf( backEnd.viewDef->worldSpace.modelViewMatrix ); + + backEnd.currentScissor = backEnd.viewDef->scissor; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); +} + +/* +================= +RB_PolygonClear + +This will cover the entire screen with normal rasterization. +Texturing is disabled, but the existing glColor, glDepthMask, +glColorMask, and the enabled state of depth buffering and +stenciling will matter. +================= +*/ +void RB_PolygonClear( void ) { + qglPushMatrix(); + qglPushAttrib( GL_ALL_ATTRIB_BITS ); + qglLoadIdentity(); + qglDisable( GL_TEXTURE_2D ); + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_SCISSOR_TEST ); + qglBegin( GL_POLYGON ); + qglVertex3f( -20, -20, -10 ); + qglVertex3f( 20, -20, -10 ); + qglVertex3f( 20, 20, -10 ); + qglVertex3f( -20, 20, -10 ); + qglEnd(); + qglPopAttrib(); + qglPopMatrix(); +} + +/* +==================== +RB_ShowDestinationAlpha +==================== +*/ +void RB_ShowDestinationAlpha( void ) { + GL_State( GLS_SRCBLEND_DST_ALPHA | GLS_DSTBLEND_ZERO | GLS_DEPTHMASK | GLS_DEPTHFUNC_ALWAYS ); + qglColor3f( 1, 1, 1 ); + RB_PolygonClear(); +} + +/* +=================== +RB_ScanStencilBuffer + +Debugging tool to see what values are in the stencil buffer +=================== +*/ +void RB_ScanStencilBuffer( void ) { + int counts[256]; + int i; + byte *stencilReadback; + + memset( counts, 0, sizeof( counts ) ); + + stencilReadback = (byte *)R_StaticAlloc( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + counts[ stencilReadback[i] ]++; + } + + R_StaticFree( stencilReadback ); + + // print some stats (not supposed to do from back end in SMP...) + common->Printf( "stencil values:\n" ); + for ( i = 0 ; i < 255 ; i++ ) { + if ( counts[i] ) { + common->Printf( "%i: %i\n", i, counts[i] ); + } + } +} + + +/* +=================== +RB_CountStencilBuffer + +Print an overdraw count based on stencil index values +=================== +*/ +void RB_CountStencilBuffer( void ) { + int count; + int i; + byte *stencilReadback; + + + stencilReadback = (byte *)R_StaticAlloc( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + count = 0; + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + count += stencilReadback[i]; + } + + R_StaticFree( stencilReadback ); + + // print some stats (not supposed to do from back end in SMP...) + common->Printf( "overdraw: %5.1f\n", (float)count/(glConfig.vidWidth * glConfig.vidHeight) ); +} + +/* +=================== +R_ColorByStencilBuffer + +Sets the screen colors based on the contents of the +stencil buffer. Stencil of 0 = black, 1 = red, 2 = green, +3 = blue, ..., 7+ = white +=================== +*/ +static void R_ColorByStencilBuffer( void ) { + int i; + static float colors[8][3] = { + {0,0,0}, + {1,0,0}, + {0,1,0}, + {0,0,1}, + {0,1,1}, + {1,0,1}, + {1,1,0}, + {1,1,1}, + }; + + // clear color buffer to white (>6 passes) + qglClearColor( 1, 1, 1, 1 ); + qglDisable( GL_SCISSOR_TEST ); + qglClear( GL_COLOR_BUFFER_BIT ); + + // now draw color for each stencil value + qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + for ( i = 0 ; i < 6 ; i++ ) { + qglColor3fv( colors[i] ); + qglStencilFunc( GL_EQUAL, i, 255 ); + RB_PolygonClear(); + } + + qglStencilFunc( GL_ALWAYS, 0, 255 ); +} + +//====================================================================== + +/* +================== +RB_ShowOverdraw +================== +*/ +void RB_ShowOverdraw( void ) { + const idMaterial * material; + int i; + drawSurf_t * * drawSurfs; + const drawSurf_t * surf; + int numDrawSurfs; + viewLight_t * vLight; + + if ( r_showOverDraw.GetInteger() == 0 ) { + return; + } + + material = declManager->FindMaterial( "textures/common/overdrawtest", false ); + if ( material == NULL ) { + return; + } + + drawSurfs = backEnd.viewDef->drawSurfs; + numDrawSurfs = backEnd.viewDef->numDrawSurfs; + + int interactions = 0; + for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { + for ( surf = vLight->localInteractions; surf; surf = surf->nextOnLight ) { + interactions++; + } + for ( surf = vLight->globalInteractions; surf; surf = surf->nextOnLight ) { + interactions++; + } + } + + drawSurf_t **newDrawSurfs = (drawSurf_t **)R_FrameAlloc( numDrawSurfs + interactions * sizeof( newDrawSurfs[0] ) ); + + for ( i = 0; i < numDrawSurfs; i++ ) { + surf = drawSurfs[i]; + if ( surf->material ) { + const_cast(surf)->material = material; + } + newDrawSurfs[i] = const_cast(surf); + } + + for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { + for ( surf = vLight->localInteractions; surf; surf = surf->nextOnLight ) { + const_cast(surf)->material = material; + newDrawSurfs[i++] = const_cast(surf); + } + for ( surf = vLight->globalInteractions; surf; surf = surf->nextOnLight ) { + const_cast(surf)->material = material; + newDrawSurfs[i++] = const_cast(surf); + } + vLight->localInteractions = NULL; + vLight->globalInteractions = NULL; + } + + switch( r_showOverDraw.GetInteger() ) { + case 1: // geometry overdraw + const_cast(backEnd.viewDef)->drawSurfs = newDrawSurfs; + const_cast(backEnd.viewDef)->numDrawSurfs = numDrawSurfs; + break; + case 2: // light interaction overdraw + const_cast(backEnd.viewDef)->drawSurfs = &newDrawSurfs[numDrawSurfs]; + const_cast(backEnd.viewDef)->numDrawSurfs = interactions; + break; + case 3: // geometry + light interaction overdraw + const_cast(backEnd.viewDef)->drawSurfs = newDrawSurfs; + const_cast(backEnd.viewDef)->numDrawSurfs += interactions; + break; + } +} + +/* +=================== +RB_ShowIntensity + +Debugging tool to see how much dynamic range a scene is using. +The greatest of the rgb values at each pixel will be used, with +the resulting color shading from red at 0 to green at 128 to blue at 255 +=================== +*/ +void RB_ShowIntensity( void ) { + byte *colorReadback; + int i, j, c; + + if ( !r_showIntensity.GetBool() ) { + return; + } + + colorReadback = (byte *)R_StaticAlloc( glConfig.vidWidth * glConfig.vidHeight * 4 ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_UNSIGNED_BYTE, colorReadback ); + + c = glConfig.vidWidth * glConfig.vidHeight * 4; + for ( i = 0; i < c ; i+=4 ) { + j = colorReadback[i]; + if ( colorReadback[i+1] > j ) { + j = colorReadback[i+1]; + } + if ( colorReadback[i+2] > j ) { + j = colorReadback[i+2]; + } + if ( j < 128 ) { + colorReadback[i+0] = 2*(128-j); + colorReadback[i+1] = 2*j; + colorReadback[i+2] = 0; + } else { + colorReadback[i+0] = 0; + colorReadback[i+1] = 2*(255-j); + colorReadback[i+2] = 2*(j-128); + } + } + + // draw it back to the screen + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + GL_State( GLS_DEPTHFUNC_ALWAYS ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0, 0 ); + qglPopMatrix(); + qglColor3f( 1, 1, 1 ); + globalImages->BindNull(); + qglMatrixMode( GL_MODELVIEW ); + + qglDrawPixels( glConfig.vidWidth, glConfig.vidHeight, GL_RGBA , GL_UNSIGNED_BYTE, colorReadback ); + + R_StaticFree( colorReadback ); +} + + +/* +=================== +RB_ShowDepthBuffer + +Draw the depth buffer as colors +=================== +*/ +void RB_ShowDepthBuffer( void ) { + void *depthReadback; + + if ( !r_showDepth.GetBool() ) { + return; + } + + qglPushMatrix(); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0, 0 ); + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); + qglPopMatrix(); + + GL_State( GLS_DEPTHFUNC_ALWAYS ); + qglColor3f( 1, 1, 1 ); + globalImages->BindNull(); + + depthReadback = R_StaticAlloc( glConfig.vidWidth * glConfig.vidHeight*4 ); + memset( depthReadback, 0, glConfig.vidWidth * glConfig.vidHeight*4 ); + + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_DEPTH_COMPONENT , GL_FLOAT, depthReadback ); + +#if 0 + for ( i = 0 ; i < glConfig.vidWidth * glConfig.vidHeight ; i++ ) { + ((byte *)depthReadback)[i*4] = + ((byte *)depthReadback)[i*4+1] = + ((byte *)depthReadback)[i*4+2] = 255 * ((float *)depthReadback)[i]; + ((byte *)depthReadback)[i*4+3] = 1; + } +#endif + + qglDrawPixels( glConfig.vidWidth, glConfig.vidHeight, GL_RGBA , GL_UNSIGNED_BYTE, depthReadback ); + R_StaticFree( depthReadback ); +} + +/* +================= +RB_ShowLightCount + +This is a debugging tool that will draw each surface with a color +based on how many lights are effecting it +================= +*/ +void RB_ShowLightCount( void ) { + int i; + const drawSurf_t *surf; + const viewLight_t *vLight; + + if ( !r_showLightCount.GetBool() ) { + return; + } + + GL_State( GLS_DEPTHFUNC_EQUAL ); + + RB_SimpleWorldSetup(); + qglClearStencil( 0 ); + qglClear( GL_STENCIL_BUFFER_BIT ); + + qglEnable( GL_STENCIL_TEST ); + + // optionally count everything through walls + if ( r_showLightCount.GetInteger() >= 2 ) { + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } else { + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + } + + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + globalImages->defaultImage->Bind(); + + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + for ( i = 0 ; i < 2 ; i++ ) { + for ( surf = i ? vLight->localInteractions: vLight->globalInteractions; surf; surf = (drawSurf_t *)surf->nextOnLight ) { + RB_SimpleSurfaceSetup( surf ); + if ( !surf->geo->ambientCache ) { + continue; + } + + const idDrawVert *ac = (idDrawVert *)vertexCache.Position( surf->geo->ambientCache ); + qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), &ac->xyz ); + RB_DrawElementsWithCounters( surf->geo ); + } + } + } + + // display the results + R_ColorByStencilBuffer(); + + if ( r_showLightCount.GetInteger() > 2 ) { + RB_CountStencilBuffer(); + } +} + + +/* +================= +RB_ShowSilhouette + +Blacks out all edges, then adds color for each edge that a shadow +plane extends from, allowing you to see doubled edges +================= +*/ +void RB_ShowSilhouette( void ) { + int i; + const drawSurf_t *surf; + const viewLight_t *vLight; + + if ( !r_showSilhouette.GetBool() ) { + return; + } + + // + // clear all triangle edges to black + // + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_TEXTURE_2D ); + qglDisable( GL_STENCIL_TEST ); + + qglColor3f( 0, 0, 0 ); + + GL_State( GLS_POLYMODE_LINE ); + + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_DEPTH_TEST ); + + RB_RenderDrawSurfListWithFunction( backEnd.viewDef->drawSurfs, backEnd.viewDef->numDrawSurfs, + RB_T_RenderTriangleSurface ); + + + // + // now blend in edges that cast silhouettes + // + RB_SimpleWorldSetup(); + qglColor3f( 0.5, 0, 0 ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + for ( i = 0 ; i < 2 ; i++ ) { + for ( surf = i ? vLight->localShadows : vLight->globalShadows + ; surf ; surf = (drawSurf_t *)surf->nextOnLight ) { + RB_SimpleSurfaceSetup( surf ); + + const srfTriangles_t *tri = surf->geo; + + qglVertexPointer( 3, GL_FLOAT, sizeof( shadowCache_t ), vertexCache.Position( tri->shadowCache ) ); + qglBegin( GL_LINES ); + + for ( int j = 0 ; j < tri->numIndexes ; j+=3 ) { + int i1 = tri->indexes[j+0]; + int i2 = tri->indexes[j+1]; + int i3 = tri->indexes[j+2]; + + if ( (i1 & 1) + (i2 & 1) + (i3 & 1) == 1 ) { + if ( (i1 & 1) + (i2 & 1) == 0 ) { + qglArrayElement( i1 ); + qglArrayElement( i2 ); + } else if ( (i1 & 1 ) + (i3 & 1) == 0 ) { + qglArrayElement( i1 ); + qglArrayElement( i3 ); + } + } + } + qglEnd(); + + } + } + } + + qglEnable( GL_DEPTH_TEST ); + + GL_State( GLS_DEFAULT ); + qglColor3f( 1,1,1 ); + GL_Cull( CT_FRONT_SIDED ); +} + + + +/* +================= +RB_ShowShadowCount + +This is a debugging tool that will draw only the shadow volumes +and count up the total fill usage +================= +*/ +static void RB_ShowShadowCount( void ) { + int i; + const drawSurf_t *surf; + const viewLight_t *vLight; + + if ( !r_showShadowCount.GetBool() ) { + return; + } + + GL_State( GLS_DEFAULT ); + + qglClearStencil( 0 ); + qglClear( GL_STENCIL_BUFFER_BIT ); + + qglEnable( GL_STENCIL_TEST ); + + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + globalImages->defaultImage->Bind(); + + // draw both sides + GL_Cull( CT_TWO_SIDED ); + + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + for ( i = 0 ; i < 2 ; i++ ) { + for ( surf = i ? vLight->localShadows : vLight->globalShadows + ; surf ; surf = (drawSurf_t *)surf->nextOnLight ) { + RB_SimpleSurfaceSetup( surf ); + const srfTriangles_t *tri = surf->geo; + if ( !tri->shadowCache ) { + continue; + } + + if ( r_showShadowCount.GetInteger() == 3 ) { + // only show turboshadows + if ( tri->numShadowIndexesNoCaps != tri->numIndexes ) { + continue; + } + } + if ( r_showShadowCount.GetInteger() == 4 ) { + // only show static shadows + if ( tri->numShadowIndexesNoCaps == tri->numIndexes ) { + continue; + } + } + + shadowCache_t *cache = (shadowCache_t *)vertexCache.Position( tri->shadowCache ); + qglVertexPointer( 4, GL_FLOAT, sizeof( *cache ), &cache->xyz ); + RB_DrawElementsWithCounters( tri ); + } + } + } + + // display the results + R_ColorByStencilBuffer(); + + if ( r_showShadowCount.GetInteger() == 2 ) { + common->Printf( "all shadows " ); + } else if ( r_showShadowCount.GetInteger() == 3 ) { + common->Printf( "turboShadows " ); + } else if ( r_showShadowCount.GetInteger() == 4 ) { + common->Printf( "static shadows " ); + } + + if ( r_showShadowCount.GetInteger() >= 2 ) { + RB_CountStencilBuffer(); + } + + GL_Cull( CT_FRONT_SIDED ); +} + + +/* +=============== +RB_T_RenderTriangleSurfaceAsLines + +=============== +*/ +void RB_T_RenderTriangleSurfaceAsLines( const drawSurf_t *surf ) { + const srfTriangles_t *tri = surf->geo; + + if ( !tri->verts ) { + return; + } + + qglBegin( GL_LINES ); + for ( int i = 0 ; i < tri->numIndexes ; i+= 3 ) { + for ( int j = 0 ; j < 3 ; j++ ) { + int k = ( j + 1 ) % 3; + qglVertex3fv( tri->verts[ tri->silIndexes[i+j] ].xyz.ToFloatPtr() ); + qglVertex3fv( tri->verts[ tri->silIndexes[i+k] ].xyz.ToFloatPtr() ); + } + } + qglEnd(); +} + + +/* +===================== +RB_ShowTris + +Debugging tool +===================== +*/ +static void RB_ShowTris( drawSurf_t **drawSurfs, int numDrawSurfs ) { + modelTrace_t mt; + idVec3 end; + + if ( !r_showTris.GetInteger() ) { + return; + } + + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_TEXTURE_2D ); + qglDisable( GL_STENCIL_TEST ); + + qglColor3f( 1, 1, 1 ); + + + GL_State( GLS_POLYMODE_LINE ); + + switch ( r_showTris.GetInteger() ) { + case 1: // only draw visible ones + qglPolygonOffset( -1, -2 ); + qglEnable( GL_POLYGON_OFFSET_LINE ); + break; + default: + case 2: // draw all front facing + GL_Cull( CT_FRONT_SIDED ); + qglDisable( GL_DEPTH_TEST ); + break; + case 3: // draw all + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_DEPTH_TEST ); + break; + } + + RB_RenderDrawSurfListWithFunction( drawSurfs, numDrawSurfs, RB_T_RenderTriangleSurface ); + + qglEnable( GL_DEPTH_TEST ); + qglDisable( GL_POLYGON_OFFSET_LINE ); + + qglDepthRange( 0, 1 ); + GL_State( GLS_DEFAULT ); + GL_Cull( CT_FRONT_SIDED ); +} + + +/* +===================== +RB_ShowSurfaceInfo + +Debugging tool +===================== +*/ +static void RB_ShowSurfaceInfo( drawSurf_t **drawSurfs, int numDrawSurfs ) { + modelTrace_t mt; + idVec3 start, end; + + if ( !r_showSurfaceInfo.GetBool() ) { + return; + } + + // start far enough away that we don't hit the player model + start = tr.primaryView->renderView.vieworg + tr.primaryView->renderView.viewaxis[0] * 16; + end = start + tr.primaryView->renderView.viewaxis[0] * 1000.0f; + if ( !tr.primaryWorld->Trace( mt, start, end, 0.0f, false ) ) { + return; + } + + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_TEXTURE_2D ); + qglDisable( GL_STENCIL_TEST ); + + qglColor3f( 1, 1, 1 ); + + GL_State( GLS_POLYMODE_LINE ); + + qglPolygonOffset( -1, -2 ); + qglEnable( GL_POLYGON_OFFSET_LINE ); + + idVec3 trans[3]; + float matrix[16]; + + // transform the object verts into global space + R_AxisToModelMatrix( mt.entity->axis, mt.entity->origin, matrix ); + + tr.primaryWorld->DrawText( mt.entity->hModel->Name(), mt.point + tr.primaryView->renderView.viewaxis[2] * 12, + 0.35f, colorRed, tr.primaryView->renderView.viewaxis ); + tr.primaryWorld->DrawText( mt.material->GetName(), mt.point, + 0.35f, colorBlue, tr.primaryView->renderView.viewaxis ); + + qglEnable( GL_DEPTH_TEST ); + qglDisable( GL_POLYGON_OFFSET_LINE ); + + qglDepthRange( 0, 1 ); + GL_State( GLS_DEFAULT ); + GL_Cull( CT_FRONT_SIDED ); +} + + +/* +===================== +RB_ShowViewEntitys + +Debugging tool +===================== +*/ +static void RB_ShowViewEntitys( viewEntity_t *vModels ) { + if ( !r_showViewEntitys.GetBool() ) { + return; + } + if ( r_showViewEntitys.GetInteger() == 2 ) { + common->Printf( "view entities: " ); + for ( ; vModels ; vModels = vModels->next ) { + common->Printf( "%i ", vModels->entityDef->index ); + } + common->Printf( "\n" ); + return; + } + + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_TEXTURE_2D ); + qglDisable( GL_STENCIL_TEST ); + + qglColor3f( 1, 1, 1 ); + + + GL_State( GLS_POLYMODE_LINE ); + + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_DEPTH_TEST ); + qglDisable( GL_SCISSOR_TEST ); + + for ( ; vModels ; vModels = vModels->next ) { + idBounds b; + + qglLoadMatrixf( vModels->modelViewMatrix ); + + if ( !vModels->entityDef ) { + continue; + } + + // draw the reference bounds in yellow + qglColor3f( 1, 1, 0 ); + RB_DrawBounds( vModels->entityDef->referenceBounds ); + + + // draw the model bounds in white + qglColor3f( 1, 1, 1 ); + + idRenderModel *model = R_EntityDefDynamicModel( vModels->entityDef ); + if ( !model ) { + continue; // particles won't instantiate without a current view + } + b = model->Bounds( &vModels->entityDef->parms ); + RB_DrawBounds( b ); + } + + qglEnable( GL_DEPTH_TEST ); + qglDisable( GL_POLYGON_OFFSET_LINE ); + + qglDepthRange( 0, 1 ); + GL_State( GLS_DEFAULT ); + GL_Cull( CT_FRONT_SIDED ); +} + +/* +===================== +RB_ShowTexturePolarity + +Shade triangle red if they have a positive texture area +green if they have a negative texture area, or blue if degenerate area +===================== +*/ +static void RB_ShowTexturePolarity( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showTexturePolarity.GetBool() ) { + return; + } + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_STENCIL_TEST ); + + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglColor3f( 1, 1, 1 ); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + tri = drawSurf->geo; + if ( !tri->verts ) { + continue; + } + + RB_SimpleSurfaceSetup( drawSurf ); + + qglBegin( GL_TRIANGLES ); + for ( j = 0 ; j < tri->numIndexes ; j+=3 ) { + idDrawVert *a, *b, *c; + float d0[5], d1[5]; + float area; + + a = tri->verts + tri->indexes[j]; + b = tri->verts + tri->indexes[j+1]; + c = tri->verts + tri->indexes[j+2]; + + // VectorSubtract( b->xyz, a->xyz, d0 ); + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + // VectorSubtract( c->xyz, a->xyz, d1 ); + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + area = d0[3] * d1[4] - d0[4] * d1[3]; + + if ( idMath::Fabs( area ) < 0.0001 ) { + qglColor4f( 0, 0, 1, 0.5 ); + } else if ( area < 0 ) { + qglColor4f( 1, 0, 0, 0.5 ); + } else { + qglColor4f( 0, 1, 0, 0.5 ); + } + qglVertex3fv( a->xyz.ToFloatPtr() ); + qglVertex3fv( b->xyz.ToFloatPtr() ); + qglVertex3fv( c->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + + +/* +===================== +RB_ShowUnsmoothedTangents + +Shade materials that are using unsmoothed tangents +===================== +*/ +static void RB_ShowUnsmoothedTangents( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showUnsmoothedTangents.GetBool() ) { + return; + } + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_STENCIL_TEST ); + + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglColor4f( 0, 1, 0, 0.5 ); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + if ( !drawSurf->material->UseUnsmoothedTangents() ) { + continue; + } + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->geo; + qglBegin( GL_TRIANGLES ); + for ( j = 0 ; j < tri->numIndexes ; j+=3 ) { + idDrawVert *a, *b, *c; + + a = tri->verts + tri->indexes[j]; + b = tri->verts + tri->indexes[j+1]; + c = tri->verts + tri->indexes[j+2]; + + qglVertex3fv( a->xyz.ToFloatPtr() ); + qglVertex3fv( b->xyz.ToFloatPtr() ); + qglVertex3fv( c->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + + +/* +===================== +RB_ShowTangentSpace + +Shade a triangle by the RGB colors of its tangent space +1 = tangents[0] +2 = tangents[1] +3 = normal +===================== +*/ +static void RB_ShowTangentSpace( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showTangentSpace.GetInteger() ) { + return; + } + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_STENCIL_TEST ); + + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->geo; + if ( !tri->verts ) { + continue; + } + qglBegin( GL_TRIANGLES ); + for ( j = 0 ; j < tri->numIndexes ; j++ ) { + const idDrawVert *v; + + v = &tri->verts[tri->indexes[j]]; + + if ( r_showTangentSpace.GetInteger() == 1 ) { + qglColor4f( 0.5 + 0.5 * v->tangents[0][0], 0.5 + 0.5 * v->tangents[0][1], + 0.5 + 0.5 * v->tangents[0][2], 0.5 ); + } else if ( r_showTangentSpace.GetInteger() == 2 ) { + qglColor4f( 0.5 + 0.5 * v->tangents[1][0], 0.5 + 0.5 * v->tangents[1][1], + 0.5 + 0.5 * v->tangents[1][2], 0.5 ); + } else { + qglColor4f( 0.5 + 0.5 * v->normal[0], 0.5 + 0.5 * v->normal[1], + 0.5 + 0.5 * v->normal[2], 0.5 ); + } + qglVertex3fv( v->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + +/* +===================== +RB_ShowVertexColor + +Draw each triangle with the solid vertex colors +===================== +*/ +static void RB_ShowVertexColor( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showVertexColor.GetBool() ) { + return; + } + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_STENCIL_TEST ); + + GL_State( GLS_DEPTHFUNC_LESS ); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->geo; + if ( !tri->verts ) { + continue; + } + qglBegin( GL_TRIANGLES ); + for ( j = 0 ; j < tri->numIndexes ; j++ ) { + const idDrawVert *v; + + v = &tri->verts[tri->indexes[j]]; + qglColor4ubv( v->color ); + qglVertex3fv( v->xyz.ToFloatPtr() ); + } + qglEnd(); + } + + GL_State( GLS_DEFAULT ); +} + + +/* +===================== +RB_ShowNormals + +Debugging tool +===================== +*/ +static void RB_ShowNormals( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + idVec3 end; + const srfTriangles_t *tri; + float size; + bool showNumbers; + idVec3 pos; + + if ( r_showNormals.GetFloat() == 0.0f ) { + return; + } + + GL_State( GLS_POLYMODE_LINE ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + globalImages->BindNull(); + qglDisable( GL_STENCIL_TEST ); + if ( !r_debugLineDepthTest.GetBool() ) { + qglDisable( GL_DEPTH_TEST ); + } else { + qglEnable( GL_DEPTH_TEST ); + } + + size = r_showNormals.GetFloat(); + if ( size < 0.0f ) { + size = -size; + showNumbers = true; + } else { + showNumbers = false; + } + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->geo; + if ( !tri->verts ) { + continue; + } + + qglBegin( GL_LINES ); + for ( j = 0 ; j < tri->numVerts ; j++ ) { + qglColor3f( 0, 0, 1 ); + qglVertex3fv( tri->verts[j].xyz.ToFloatPtr() ); + VectorMA( tri->verts[j].xyz, size, tri->verts[j].normal, end ); + qglVertex3fv( end.ToFloatPtr() ); + + qglColor3f( 1, 0, 0 ); + qglVertex3fv( tri->verts[j].xyz.ToFloatPtr() ); + VectorMA( tri->verts[j].xyz, size, tri->verts[j].tangents[0], end ); + qglVertex3fv( end.ToFloatPtr() ); + + qglColor3f( 0, 1, 0 ); + qglVertex3fv( tri->verts[j].xyz.ToFloatPtr() ); + VectorMA( tri->verts[j].xyz, size, tri->verts[j].tangents[1], end ); + qglVertex3fv( end.ToFloatPtr() ); + } + qglEnd(); + } + + if ( showNumbers ) { + RB_SimpleWorldSetup(); + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + tri = drawSurf->geo; + if ( !tri->verts ) { + continue; + } + + for ( j = 0 ; j < tri->numVerts ; j++ ) { + R_LocalPointToGlobal( drawSurf->space->modelMatrix, tri->verts[j].xyz + tri->verts[j].tangents[0] + tri->verts[j].normal * 0.2f, pos ); + RB_DrawText( va( "%d", j ), pos, 0.01f, colorWhite, backEnd.viewDef->renderView.viewaxis, 1 ); + } + + for ( j = 0 ; j < tri->numIndexes; j += 3 ) { + R_LocalPointToGlobal( drawSurf->space->modelMatrix, ( tri->verts[ tri->indexes[ j + 0 ] ].xyz + tri->verts[ tri->indexes[ j + 1 ] ].xyz + tri->verts[ tri->indexes[ j + 2 ] ].xyz ) * ( 1.0f / 3.0f ) + tri->verts[ tri->indexes[ j + 0 ] ].normal * 0.2f, pos ); + RB_DrawText( va( "%d", j / 3 ), pos, 0.01f, colorCyan, backEnd.viewDef->renderView.viewaxis, 1 ); + } + } + } + + qglEnable( GL_STENCIL_TEST ); +} + + +/* +===================== +RB_ShowNormals + +Debugging tool +===================== +*/ +static void RB_AltShowNormals( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j, k; + drawSurf_t *drawSurf; + idVec3 end; + const srfTriangles_t *tri; + + if ( r_showNormals.GetFloat() == 0.0f ) { + return; + } + + GL_State( GLS_DEFAULT ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + globalImages->BindNull(); + qglDisable( GL_STENCIL_TEST ); + qglDisable( GL_DEPTH_TEST ); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + RB_SimpleSurfaceSetup( drawSurf ); + + tri = drawSurf->geo; + qglBegin( GL_LINES ); + for ( j = 0 ; j < tri->numIndexes ; j += 3 ) { + const idDrawVert *v[3]; + idVec3 mid; + + v[0] = &tri->verts[tri->indexes[j+0]]; + v[1] = &tri->verts[tri->indexes[j+1]]; + v[2] = &tri->verts[tri->indexes[j+2]]; + + // make the midpoint slightly above the triangle + mid = ( v[0]->xyz + v[1]->xyz + v[2]->xyz ) * ( 1.0f / 3.0f ); + mid += 0.1f * tri->facePlanes[ j / 3 ].Normal(); + + for ( k = 0 ; k < 3 ; k++ ) { + idVec3 pos; + + pos = ( mid + v[k]->xyz * 3.0f ) * 0.25f; + + qglColor3f( 0, 0, 1 ); + qglVertex3fv( pos.ToFloatPtr() ); + VectorMA( pos, r_showNormals.GetFloat(), v[k]->normal, end ); + qglVertex3fv( end.ToFloatPtr() ); + + qglColor3f( 1, 0, 0 ); + qglVertex3fv( pos.ToFloatPtr() ); + VectorMA( pos, r_showNormals.GetFloat(), v[k]->tangents[0], end ); + qglVertex3fv( end.ToFloatPtr() ); + + qglColor3f( 0, 1, 0 ); + qglVertex3fv( pos.ToFloatPtr() ); + VectorMA( pos, r_showNormals.GetFloat(), v[k]->tangents[1], end ); + qglVertex3fv( end.ToFloatPtr() ); + + qglColor3f( 1, 1, 1 ); + qglVertex3fv( pos.ToFloatPtr() ); + qglVertex3fv( v[k]->xyz.ToFloatPtr() ); + } + } + qglEnd(); + } + + qglEnable( GL_DEPTH_TEST ); + qglEnable( GL_STENCIL_TEST ); +} + + + +/* +===================== +RB_ShowTextureVectors + +Draw texture vectors in the center of each triangle +===================== +*/ +static void RB_ShowTextureVectors( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( r_showTextureVectors.GetFloat() == 0.0f ) { + return; + } + + GL_State( GLS_DEPTHFUNC_LESS ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + globalImages->BindNull(); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + tri = drawSurf->geo; + + if ( !tri->verts ) { + continue; + } + if ( !tri->facePlanes ) { + continue; + } + RB_SimpleSurfaceSetup( drawSurf ); + + // draw non-shared edges in yellow + qglBegin( GL_LINES ); + + for ( j = 0 ; j < tri->numIndexes ; j+= 3 ) { + const idDrawVert *a, *b, *c; + float area, inva; + idVec3 temp; + float d0[5], d1[5]; + idVec3 mid; + idVec3 tangents[2]; + + a = &tri->verts[tri->indexes[j+0]]; + b = &tri->verts[tri->indexes[j+1]]; + c = &tri->verts[tri->indexes[j+2]]; + + // make the midpoint slightly above the triangle + mid = ( a->xyz + b->xyz + c->xyz ) * ( 1.0f / 3.0f ); + mid += 0.1f * tri->facePlanes[ j / 3 ].Normal(); + + // calculate the texture vectors + VectorSubtract( b->xyz, a->xyz, d0 ); + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + VectorSubtract( c->xyz, a->xyz, d1 ); + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( area == 0 ) { + continue; + } + inva = 1.0 / area; + + temp[0] = (d0[0] * d1[4] - d0[4] * d1[0]) * inva; + temp[1] = (d0[1] * d1[4] - d0[4] * d1[1]) * inva; + temp[2] = (d0[2] * d1[4] - d0[4] * d1[2]) * inva; + temp.Normalize(); + tangents[0] = temp; + + temp[0] = (d0[3] * d1[0] - d0[0] * d1[3]) * inva; + temp[1] = (d0[3] * d1[1] - d0[1] * d1[3]) * inva; + temp[2] = (d0[3] * d1[2] - d0[2] * d1[3]) * inva; + temp.Normalize(); + tangents[1] = temp; + + // draw the tangents + tangents[0] = mid + tangents[0] * r_showTextureVectors.GetFloat(); + tangents[1] = mid + tangents[1] * r_showTextureVectors.GetFloat(); + + qglColor3f( 1, 0, 0 ); + qglVertex3fv( mid.ToFloatPtr() ); + qglVertex3fv( tangents[0].ToFloatPtr() ); + + qglColor3f( 0, 1, 0 ); + qglVertex3fv( mid.ToFloatPtr() ); + qglVertex3fv( tangents[1].ToFloatPtr() ); + } + + qglEnd(); + } +} + +/* +===================== +RB_ShowDominantTris + +Draw lines from each vertex to the dominant triangle center +===================== +*/ +static void RB_ShowDominantTris( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + + if ( !r_showDominantTri.GetBool() ) { + return; + } + + GL_State( GLS_DEPTHFUNC_LESS ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + qglPolygonOffset( -1, -2 ); + qglEnable( GL_POLYGON_OFFSET_LINE ); + + globalImages->BindNull(); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + tri = drawSurf->geo; + + if ( !tri->verts ) { + continue; + } + if ( !tri->dominantTris ) { + continue; + } + RB_SimpleSurfaceSetup( drawSurf ); + + qglColor3f( 1, 1, 0 ); + qglBegin( GL_LINES ); + + for ( j = 0 ; j < tri->numVerts ; j++ ) { + const idDrawVert *a, *b, *c; + idVec3 mid; + + // find the midpoint of the dominant tri + + a = &tri->verts[j]; + b = &tri->verts[tri->dominantTris[j].v2]; + c = &tri->verts[tri->dominantTris[j].v3]; + + mid = ( a->xyz + b->xyz + c->xyz ) * ( 1.0f / 3.0f ); + + qglVertex3fv( mid.ToFloatPtr() ); + qglVertex3fv( a->xyz.ToFloatPtr() ); + } + + qglEnd(); + } + qglDisable( GL_POLYGON_OFFSET_LINE ); +} + +/* +===================== +RB_ShowEdges + +Debugging tool +===================== +*/ +static void RB_ShowEdges( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i, j, k, m, n, o; + drawSurf_t *drawSurf; + const srfTriangles_t *tri; + const silEdge_t *edge; + int danglePlane; + + if ( !r_showEdges.GetBool() ) { + return; + } + + GL_State( GLS_DEFAULT ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + globalImages->BindNull(); + qglDisable( GL_DEPTH_TEST ); + + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + drawSurf = drawSurfs[i]; + + tri = drawSurf->geo; + + idDrawVert *ac = (idDrawVert *)tri->verts; + if ( !ac ) { + continue; + } + + RB_SimpleSurfaceSetup( drawSurf ); + + // draw non-shared edges in yellow + qglColor3f( 1, 1, 0 ); + qglBegin( GL_LINES ); + + for ( j = 0 ; j < tri->numIndexes ; j+= 3 ) { + for ( k = 0 ; k < 3 ; k++ ) { + int l, i1, i2; + l = ( k == 2 ) ? 0 : k + 1; + i1 = tri->indexes[j+k]; + i2 = tri->indexes[j+l]; + + // if these are used backwards, the edge is shared + for ( m = 0 ; m < tri->numIndexes ; m += 3 ) { + for ( n = 0 ; n < 3 ; n++ ) { + o = ( n == 2 ) ? 0 : n + 1; + if ( tri->indexes[m+n] == i2 && tri->indexes[m+o] == i1 ) { + break; + } + } + if ( n != 3 ) { + break; + } + } + + // if we didn't find a backwards listing, draw it in yellow + if ( m == tri->numIndexes ) { + qglVertex3fv( ac[ i1 ].xyz.ToFloatPtr() ); + qglVertex3fv( ac[ i2 ].xyz.ToFloatPtr() ); + } + + } + } + + qglEnd(); + + // draw dangling sil edges in red + if ( !tri->silEdges ) { + continue; + } + + // the plane number after all real planes + // is the dangling edge + danglePlane = tri->numIndexes / 3; + + qglColor3f( 1, 0, 0 ); + + qglBegin( GL_LINES ); + for ( j = 0 ; j < tri->numSilEdges ; j++ ) { + edge = tri->silEdges + j; + + if ( edge->p1 != danglePlane && edge->p2 != danglePlane ) { + continue; + } + + qglVertex3fv( ac[ edge->v1 ].xyz.ToFloatPtr() ); + qglVertex3fv( ac[ edge->v2 ].xyz.ToFloatPtr() ); + } + qglEnd(); + } + + qglEnable( GL_DEPTH_TEST ); +} + +/* +============== +RB_ShowLights + +Visualize all light volumes used in the current scene +r_showLights 1 : just print volumes numbers, highlighting ones covering the view +r_showLights 2 : also draw planes of each volume +r_showLights 3 : also draw edges of each volume +============== +*/ +void RB_ShowLights( void ) { + const idRenderLightLocal *light; + int count; + srfTriangles_t *tri; + viewLight_t *vLight; + + if ( !r_showLights.GetInteger() ) { + return; + } + + // all volumes are expressed in world coordinates + RB_SimpleWorldSetup(); + + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + globalImages->BindNull(); + qglDisable( GL_STENCIL_TEST ); + + + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_DEPTH_TEST ); + + + common->Printf( "volumes: " ); // FIXME: not in back end! + + count = 0; + for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { + light = vLight->lightDef; + count++; + + tri = light->frustumTris; + + // depth buffered planes + if ( r_showLights.GetInteger() >= 2 ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHMASK ); + qglColor4f( 0, 0, 1, 0.25 ); + qglEnable( GL_DEPTH_TEST ); + RB_RenderTriangleSurface( tri ); + } + + // non-hidden lines + if ( r_showLights.GetInteger() >= 3 ) { + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK ); + qglDisable( GL_DEPTH_TEST ); + qglColor3f( 1, 1, 1 ); + RB_RenderTriangleSurface( tri ); + } + + int index; + + index = backEnd.viewDef->renderWorld->lightDefs.FindIndex( vLight->lightDef ); + if ( vLight->viewInsideLight ) { + // view is in this volume + common->Printf( "[%i] ", index ); + } else { + common->Printf( "%i ", index ); + } + } + + qglEnable( GL_DEPTH_TEST ); + qglDisable( GL_POLYGON_OFFSET_LINE ); + + qglDepthRange( 0, 1 ); + GL_State( GLS_DEFAULT ); + GL_Cull( CT_FRONT_SIDED ); + + common->Printf( " = %i total\n", count ); +} + +/* +===================== +RB_ShowPortals + +Debugging tool, won't work correctly with SMP or when mirrors are present +===================== +*/ +void RB_ShowPortals( void ) { + if ( !r_showPortals.GetBool() ) { + return; + } + + // all portals are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + qglDisable( GL_DEPTH_TEST ); + + GL_State( GLS_DEFAULT ); + + ((idRenderWorldLocal *)backEnd.viewDef->renderWorld)->ShowPortals(); + + qglEnable( GL_DEPTH_TEST ); +} + +/* +================ +RB_ClearDebugText +================ +*/ +void RB_ClearDebugText( int time ) { + int i; + int num; + debugText_t *text; + + rb_debugTextTime = time; + + if ( !time ) { + // free up our strings + text = rb_debugText; + for ( i = 0 ; i < MAX_DEBUG_TEXT; i++, text++ ) { + text->text.Clear(); + } + rb_numDebugText = 0; + return; + } + + // copy any text that still needs to be drawn + num = 0; + text = rb_debugText; + for ( i = 0 ; i < rb_numDebugText; i++, text++ ) { + if ( text->lifeTime > time ) { + if ( num != i ) { + rb_debugText[ num ] = *text; + } + num++; + } + } + rb_numDebugText = num; +} + +/* +================ +RB_AddDebugText +================ +*/ +void RB_AddDebugText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align, const int lifetime, const bool depthTest ) { + debugText_t *debugText; + + if ( rb_numDebugText < MAX_DEBUG_TEXT ) { + debugText = &rb_debugText[ rb_numDebugText++ ]; + debugText->text = text; + debugText->origin = origin; + debugText->scale = scale; + debugText->color = color; + debugText->viewAxis = viewAxis; + debugText->align = align; + debugText->lifeTime = rb_debugTextTime + lifetime; + debugText->depthTest = depthTest; + } +} + +/* +================ +RB_DrawTextLength + + returns the length of the given text +================ +*/ +float RB_DrawTextLength( const char *text, float scale, int len ) { + int i, num, index, charIndex; + float spacing, textLen = 0.0f; + + if ( text && *text ) { + if ( !len ) { + len = strlen(text); + } + for ( i = 0; i < len; i++ ) { + charIndex = text[i] - 32; + if ( charIndex < 0 || charIndex > NUM_SIMPLEX_CHARS ) { + continue; + } + num = simplex[charIndex][0] * 2; + spacing = simplex[charIndex][1]; + index = 2; + + while( index - 2 < num ) { + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + index += 2; + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + } + textLen += spacing * scale; + } + } + return textLen; +} + +/* +================ +RB_DrawText + + oriented on the viewaxis + align can be 0-left, 1-center (default), 2-right +================ +*/ +static void RB_DrawText( const char *text, const idVec3 &origin, float scale, const idVec4 &color, const idMat3 &viewAxis, const int align ) { + int i, j, len, num, index, charIndex, line; + float textLen, spacing; + idVec3 org, p1, p2; + + if ( text && *text ) { + qglBegin( GL_LINES ); + qglColor3fv( color.ToFloatPtr() ); + + if ( text[0] == '\n' ) { + line = 1; + } else { + line = 0; + } + + len = strlen( text ); + for ( i = 0; i < len; i++ ) { + + if ( i == 0 || text[i] == '\n' ) { + org = origin - viewAxis[2] * ( line * 36.0f * scale ); + if ( align != 0 ) { + for ( j = 1; i+j <= len; j++ ) { + if ( i+j == len || text[i+j] == '\n' ) { + textLen = RB_DrawTextLength( text+i, scale, j ); + break; + } + } + if ( align == 2 ) { + // right + org += viewAxis[1] * textLen; + } else { + // center + org += viewAxis[1] * ( textLen * 0.5f ); + } + } + line++; + } + + charIndex = text[i] - 32; + if ( charIndex < 0 || charIndex > NUM_SIMPLEX_CHARS ) { + continue; + } + num = simplex[charIndex][0] * 2; + spacing = simplex[charIndex][1]; + index = 2; + + while( index - 2 < num ) { + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + p1 = org + scale * simplex[charIndex][index] * -viewAxis[1] + scale * simplex[charIndex][index+1] * viewAxis[2]; + index += 2; + if ( simplex[charIndex][index] < 0) { + index++; + continue; + } + p2 = org + scale * simplex[charIndex][index] * -viewAxis[1] + scale * simplex[charIndex][index+1] * viewAxis[2]; + + qglVertex3fv( p1.ToFloatPtr() ); + qglVertex3fv( p2.ToFloatPtr() ); + } + org -= viewAxis[1] * ( spacing * scale ); + } + + qglEnd(); + } +} + +/* +================ +RB_ShowDebugText +================ +*/ +void RB_ShowDebugText( void ) { + int i; + int width; + debugText_t *text; + + if ( !rb_numDebugText ) { + return; + } + + // all lines are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + + width = r_debugLineWidth.GetInteger(); + if ( width < 1 ) { + width = 1; + } else if ( width > 10 ) { + width = 10; + } + + // draw lines + GL_State( GLS_POLYMODE_LINE ); + qglLineWidth( width ); + + if ( !r_debugLineDepthTest.GetBool() ) { + qglDisable( GL_DEPTH_TEST ); + } + + text = rb_debugText; + for ( i = 0 ; i < rb_numDebugText; i++, text++ ) { + if ( !text->depthTest ) { + RB_DrawText( text->text, text->origin, text->scale, text->color, text->viewAxis, text->align ); + } + } + + if ( !r_debugLineDepthTest.GetBool() ) { + qglEnable( GL_DEPTH_TEST ); + } + + text = rb_debugText; + for ( i = 0 ; i < rb_numDebugText; i++, text++ ) { + if ( text->depthTest ) { + RB_DrawText( text->text, text->origin, text->scale, text->color, text->viewAxis, text->align ); + } + } + + qglLineWidth( 1 ); + GL_State( GLS_DEFAULT ); +} + +/* +================ +RB_ClearDebugLines +================ +*/ +void RB_ClearDebugLines( int time ) { + int i; + int num; + debugLine_t *line; + + rb_debugLineTime = time; + + if ( !time ) { + rb_numDebugLines = 0; + return; + } + + // copy any lines that still need to be drawn + num = 0; + line = rb_debugLines; + for ( i = 0 ; i < rb_numDebugLines; i++, line++ ) { + if ( line->lifeTime > time ) { + if ( num != i ) { + rb_debugLines[ num ] = *line; + } + num++; + } + } + rb_numDebugLines = num; +} + +/* +================ +RB_AddDebugLine +================ +*/ +void RB_AddDebugLine( const idVec4 &color, const idVec3 &start, const idVec3 &end, const int lifeTime, const bool depthTest ) { + debugLine_t *line; + + if ( rb_numDebugLines < MAX_DEBUG_LINES ) { + line = &rb_debugLines[ rb_numDebugLines++ ]; + line->rgb = color; + line->start = start; + line->end = end; + line->depthTest = depthTest; + line->lifeTime = rb_debugLineTime + lifeTime; + } +} + +/* +================ +RB_ShowDebugLines +================ +*/ +void RB_ShowDebugLines( void ) { + int i; + int width; + debugLine_t *line; + + if ( !rb_numDebugLines ) { + return; + } + + // all lines are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + + width = r_debugLineWidth.GetInteger(); + if ( width < 1 ) { + width = 1; + } else if ( width > 10 ) { + width = 10; + } + + // draw lines + GL_State( GLS_POLYMODE_LINE );//| GLS_DEPTHMASK ); //| GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + qglLineWidth( width ); + + if ( !r_debugLineDepthTest.GetBool() ) { + qglDisable( GL_DEPTH_TEST ); + } + + qglBegin( GL_LINES ); + + line = rb_debugLines; + for ( i = 0 ; i < rb_numDebugLines; i++, line++ ) { + if ( !line->depthTest ) { + qglColor3fv( line->rgb.ToFloatPtr() ); + qglVertex3fv( line->start.ToFloatPtr() ); + qglVertex3fv( line->end.ToFloatPtr() ); + } + } + qglEnd(); + + if ( !r_debugLineDepthTest.GetBool() ) { + qglEnable( GL_DEPTH_TEST ); + } + + qglBegin( GL_LINES ); + + line = rb_debugLines; + for ( i = 0 ; i < rb_numDebugLines; i++, line++ ) { + if ( line->depthTest ) { + qglColor4fv( line->rgb.ToFloatPtr() ); + qglVertex3fv( line->start.ToFloatPtr() ); + qglVertex3fv( line->end.ToFloatPtr() ); + } + } + + qglEnd(); + + qglLineWidth( 1 ); + GL_State( GLS_DEFAULT ); +} + +/* +================ +RB_ClearDebugPolygons +================ +*/ +void RB_ClearDebugPolygons( int time ) { + int i; + int num; + debugPolygon_t *poly; + + rb_debugPolygonTime = time; + + if ( !time ) { + rb_numDebugPolygons = 0; + return; + } + + // copy any polygons that still need to be drawn + num = 0; + + poly = rb_debugPolygons; + for ( i = 0 ; i < rb_numDebugPolygons; i++, poly++ ) { + if ( poly->lifeTime > time ) { + if ( num != i ) { + rb_debugPolygons[ num ] = *poly; + } + num++; + } + } + rb_numDebugPolygons = num; +} + +/* +================ +RB_AddDebugPolygon +================ +*/ +void RB_AddDebugPolygon( const idVec4 &color, const idWinding &winding, const int lifeTime, const bool depthTest ) { + debugPolygon_t *poly; + + if ( rb_numDebugPolygons < MAX_DEBUG_POLYGONS ) { + poly = &rb_debugPolygons[ rb_numDebugPolygons++ ]; + poly->rgb = color; + poly->winding = winding; + poly->depthTest = depthTest; + poly->lifeTime = rb_debugPolygonTime + lifeTime; + } +} + +/* +================ +RB_ShowDebugPolygons +================ +*/ +void RB_ShowDebugPolygons( void ) { + int i, j; + debugPolygon_t *poly; + + if ( !rb_numDebugPolygons ) { + return; + } + + // all lines are expressed in world coordinates + RB_SimpleWorldSetup(); + + globalImages->BindNull(); + + qglDisable( GL_TEXTURE_2D ); + qglDisable( GL_STENCIL_TEST ); + + qglEnable( GL_DEPTH_TEST ); + + if ( r_debugPolygonFilled.GetBool() ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHMASK ); + qglPolygonOffset( -1, -2 ); + qglEnable( GL_POLYGON_OFFSET_FILL ); + } else { + GL_State( GLS_POLYMODE_LINE ); + qglPolygonOffset( -1, -2 ); + qglEnable( GL_POLYGON_OFFSET_LINE ); + } + + poly = rb_debugPolygons; + for ( i = 0 ; i < rb_numDebugPolygons; i++, poly++ ) { +// if ( !poly->depthTest ) { + + qglColor4fv( poly->rgb.ToFloatPtr() ); + + qglBegin( GL_POLYGON ); + + for ( j = 0; j < poly->winding.GetNumPoints(); j++) { + qglVertex3fv( poly->winding[j].ToFloatPtr() ); + } + + qglEnd(); +// } + } + + GL_State( GLS_DEFAULT ); + + if ( r_debugPolygonFilled.GetBool() ) { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } else { + qglDisable( GL_POLYGON_OFFSET_LINE ); + } + + qglDepthRange( 0, 1 ); + GL_State( GLS_DEFAULT ); +} + +/* +================ +RB_TestGamma +================ +*/ +#define G_WIDTH 512 +#define G_HEIGHT 512 +#define BAR_HEIGHT 64 + +void RB_TestGamma( void ) { + byte image[G_HEIGHT][G_WIDTH][4]; + int i, j; + int c, comp; + int v, dither; + int mask, y; + + if ( r_testGamma.GetInteger() <= 0 ) { + return; + } + + v = r_testGamma.GetInteger(); + if ( v <= 1 || v >= 196 ) { + v = 128; + } + + memset( image, 0, sizeof( image ) ); + + for ( mask = 0 ; mask < 8 ; mask++ ) { + y = mask * BAR_HEIGHT; + for ( c = 0 ; c < 4 ; c++ ) { + v = c * 64 + 32; + // solid color + for ( i = 0 ; i < BAR_HEIGHT/2 ; i++ ) { + for ( j = 0 ; j < G_WIDTH/4 ; j++ ) { + for ( comp = 0 ; comp < 3 ; comp++ ) { + if ( mask & ( 1 << comp ) ) { + image[y+i][c*G_WIDTH/4+j][comp] = v; + } + } + } + // dithered color + for ( j = 0 ; j < G_WIDTH/4 ; j++ ) { + if ( ( i ^ j ) & 1 ) { + dither = c * 64; + } else { + dither = c * 64 + 63; + } + for ( comp = 0 ; comp < 3 ; comp++ ) { + if ( mask & ( 1 << comp ) ) { + image[y+BAR_HEIGHT/2+i][c*G_WIDTH/4+j][comp] = dither; + } + } + } + } + } + } + + // draw geometrically increasing steps in the bottom row + y = 0 * BAR_HEIGHT; + float scale = 1; + for ( c = 0 ; c < 4 ; c++ ) { + v = (int)(64 * scale); + if ( v < 0 ) { + v = 0; + } else if ( v > 255 ) { + v = 255; + } + scale = scale * 1.5; + for ( i = 0 ; i < BAR_HEIGHT ; i++ ) { + for ( j = 0 ; j < G_WIDTH/4 ; j++ ) { + image[y+i][c*G_WIDTH/4+j][0] = v; + image[y+i][c*G_WIDTH/4+j][1] = v; + image[y+i][c*G_WIDTH/4+j][2] = v; + } + } + } + + + qglLoadIdentity(); + + qglMatrixMode( GL_PROJECTION ); + GL_State( GLS_DEPTHFUNC_ALWAYS ); + qglColor3f( 1, 1, 1 ); + qglPushMatrix(); + qglLoadIdentity(); + qglDisable( GL_TEXTURE_2D ); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0.01f, 0.01f ); + qglDrawPixels( G_WIDTH, G_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, image ); + qglPopMatrix(); + qglEnable( GL_TEXTURE_2D ); + qglMatrixMode( GL_MODELVIEW ); +} + + +/* +================== +RB_TestGammaBias +================== +*/ +static void RB_TestGammaBias( void ) { + byte image[G_HEIGHT][G_WIDTH][4]; + + if ( r_testGammaBias.GetInteger() <= 0 ) { + return; + } + + int y = 0; + for ( int bias = -40 ; bias < 40 ; bias+=10, y += BAR_HEIGHT ) { + float scale = 1; + for ( int c = 0 ; c < 4 ; c++ ) { + int v = (int)(64 * scale + bias); + scale = scale * 1.5; + if ( v < 0 ) { + v = 0; + } else if ( v > 255 ) { + v = 255; + } + for ( int i = 0 ; i < BAR_HEIGHT ; i++ ) { + for ( int j = 0 ; j < G_WIDTH/4 ; j++ ) { + image[y+i][c*G_WIDTH/4+j][0] = v; + image[y+i][c*G_WIDTH/4+j][1] = v; + image[y+i][c*G_WIDTH/4+j][2] = v; + } + } + } + } + + + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + GL_State( GLS_DEPTHFUNC_ALWAYS ); + qglColor3f( 1, 1, 1 ); + qglPushMatrix(); + qglLoadIdentity(); + qglDisable( GL_TEXTURE_2D ); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + qglRasterPos2f( 0.01f, 0.01f ); + qglDrawPixels( G_WIDTH, G_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, image ); + qglPopMatrix(); + qglEnable( GL_TEXTURE_2D ); + qglMatrixMode( GL_MODELVIEW ); +} + +/* +================ +RB_TestImage + +Display a single image over most of the screen +================ +*/ +void RB_TestImage( void ) { + idImage *image; + int max; + float w, h; + + image = tr.testImage; + if ( !image ) { + return; + } + + if ( tr.testVideo ) { + cinData_t cin; + + cin = tr.testVideo->ImageForTime( (int)(1000 * ( backEnd.viewDef->floatTime - tr.testVideoStartTime ) ) ); + if ( cin.image ) { + image->UploadScratch( cin.image, cin.imageWidth, cin.imageHeight ); + } else { + tr.testImage = NULL; + return; + } + w = 0.25; + h = 0.25; + } else { + max = image->uploadWidth > image->uploadHeight ? image->uploadWidth : image->uploadHeight; + + w = 0.25 * image->uploadWidth / max; + h = 0.25 * image->uploadHeight / max; + + w *= (float)glConfig.vidHeight / glConfig.vidWidth; + } + + qglLoadIdentity(); + + qglMatrixMode( GL_PROJECTION ); + GL_State( GLS_DEPTHFUNC_ALWAYS | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + qglColor3f( 1, 1, 1 ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( 0, 1, 0, 1, -1, 1 ); + + tr.testImage->Bind(); + qglBegin( GL_QUADS ); + + qglTexCoord2f( 0, 1 ); + qglVertex2f( 0.5 - w, 0 ); + + qglTexCoord2f( 0, 0 ); + qglVertex2f( 0.5 - w, h*2 ); + + qglTexCoord2f( 1, 0 ); + qglVertex2f( 0.5 + w, h*2 ); + + qglTexCoord2f( 1, 1 ); + qglVertex2f( 0.5 + w, 0 ); + + qglEnd(); + + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); +} + +/* +================= +RB_RenderDebugTools +================= +*/ +void RB_RenderDebugTools( drawSurf_t **drawSurfs, int numDrawSurfs ) { + // don't do anything if this was a 2D rendering + if ( !backEnd.viewDef->viewEntitys ) { + return; + } + + RB_LogComment( "---------- RB_RenderDebugTools ----------\n" ); + + GL_State( GLS_DEFAULT ); + backEnd.currentScissor = backEnd.viewDef->scissor; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + + + RB_ShowLightCount(); + RB_ShowShadowCount(); + RB_ShowTexturePolarity( drawSurfs, numDrawSurfs ); + RB_ShowTangentSpace( drawSurfs, numDrawSurfs ); + RB_ShowVertexColor( drawSurfs, numDrawSurfs ); + RB_ShowTris( drawSurfs, numDrawSurfs ); + RB_ShowUnsmoothedTangents( drawSurfs, numDrawSurfs ); + RB_ShowSurfaceInfo( drawSurfs, numDrawSurfs ); + RB_ShowEdges( drawSurfs, numDrawSurfs ); + RB_ShowNormals( drawSurfs, numDrawSurfs ); + RB_ShowViewEntitys( backEnd.viewDef->viewEntitys ); + RB_ShowLights(); + RB_ShowTextureVectors( drawSurfs, numDrawSurfs ); + RB_ShowDominantTris( drawSurfs, numDrawSurfs ); + if ( r_testGamma.GetInteger() > 0 ) { // test here so stack check isn't so damn slow on debug builds + RB_TestGamma(); + } + if ( r_testGammaBias.GetInteger() > 0 ) { + RB_TestGammaBias(); + } + RB_TestImage(); + RB_ShowPortals(); + RB_ShowSilhouette(); + RB_ShowDepthBuffer(); + RB_ShowIntensity(); + RB_ShowDebugLines(); + RB_ShowDebugText(); + RB_ShowDebugPolygons(); + RB_ShowTrace( drawSurfs, numDrawSurfs ); +} + +// These public render-world entry points are owned by tr_rendertools.obj in +// the retail PDB. They deliberately forward to the backend debug queues. +void idRenderWorldLocal::ShowDebugLines() { + RB_ShowDebugLines(); +} + +void idRenderWorldLocal::ShowDebugPolygons() { + RB_ShowDebugPolygons(); +} + +void idRenderWorldLocal::ShowDebugText() { + RB_ShowDebugText(); +} + +/* +================= +RB_ShutdownDebugTools +================= +*/ +void RB_ShutdownDebugTools( void ) { + for ( int i = 0; i < MAX_DEBUG_POLYGONS; i++ ) { + rb_debugPolygons[i].winding.Clear(); + } +} diff --git a/src/renderer/tr_shadowbounds.cpp b/src/renderer/tr_shadowbounds.cpp new file mode 100644 index 0000000..523143f --- /dev/null +++ b/src/renderer/tr_shadowbounds.cpp @@ -0,0 +1,634 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + + + +// Compute conservative shadow bounds as the intersection +// of the object's bounds' shadow volume and the light's bounds. +// +// --cass + + +template +struct MyArray +{ + MyArray() : s(0) {} + + MyArray( const MyArray & cpy ) : s(cpy.s) + { + for(int i=0; i < s; i++) + v[i] = cpy.v[i]; + } + + void push_back(const T & i) { + v[s] = i; + s++; + //if(s > max_size) + // max_size = int(s); + } + + T & operator[](int i) { + return v[i]; + } + + const T & operator[](int i) const { + return v[i]; + } + + unsigned int size() const { + return s; + } + + void empty() { + s = 0; + } + + T v[N]; + int s; +// static int max_size; +}; + +typedef MyArray MyArrayInt; +//int MyArrayInt::max_size = 0; +typedef MyArray MyArrayVec4; +//int MyArrayVec4::max_size = 0; + +struct poly +{ + MyArrayInt vi; + MyArrayInt ni; + idVec4 plane; +}; + +typedef MyArray MyArrayPoly; +//int MyArrayPoly::max_size = 0; + +struct edge +{ + int vi[2]; + int pi[2]; +}; + +typedef MyArray MyArrayEdge; +//int MyArrayEdge::max_size = 0; + +MyArrayInt four_ints(int a, int b, int c, int d) +{ + MyArrayInt vi; + vi.push_back(a); + vi.push_back(b); + vi.push_back(c); + vi.push_back(d); + return vi; +} + +idVec3 homogeneous_difference(idVec4 a, idVec4 b) +{ + idVec3 v; + v.x = b.x * a.w - a.x * b.w; + v.y = b.y * a.w - a.y * b.w; + v.z = b.z * a.w - a.z * b.w; + return v; +} + +// handles positive w only +idVec4 compute_homogeneous_plane(idVec4 a, idVec4 b, idVec4 c) +{ + idVec4 v, t; + + if(a[3] == 0) + { t = a; a = b; b = c; c = t; } + if(a[3] == 0) + { t = a; a = b; b = c; c = t; } + + // can't handle 3 infinite points + if( a[3] == 0 ) + return v; + + idVec3 vb = homogeneous_difference(a, b); + idVec3 vc = homogeneous_difference(a, c); + + idVec3 n = vb.Cross(vc); + n.Normalize(); + + v.x = n.x; + v.y = n.y; + v.z = n.z; + + v.w = - (n * idVec3(a.x, a.y, a.z)) / a.w ; + + return v; +} + +struct polyhedron +{ + MyArrayVec4 v; + MyArrayPoly p; + MyArrayEdge e; + + void add_quad( int va, int vb, int vc, int vd ) + { + poly pg; + pg.vi = four_ints(va, vb, vc, vd); + pg.ni = four_ints(-1, -1, -1, -1); + pg.plane = compute_homogeneous_plane(v[va], v[vb], v[vc]); + p.push_back(pg); + } + + void discard_neighbor_info() + { + for(unsigned int i = 0; i < p.size(); i++ ) + { + MyArrayInt & ni = p[i].ni; + for(unsigned int j = 0; j < ni.size(); j++) + ni[j] = -1; + } + } + + void compute_neighbors() + { + e.empty(); + + discard_neighbor_info(); + + bool found; + int P = p.size(); + // for each polygon + for(int i = 0; i < P-1; i++ ) + { + const MyArrayInt & vi = p[i].vi; + MyArrayInt & ni = p[i].ni; + int Si = vi.size(); + + // for each edge of that polygon + for(int ii=0; ii < Si; ii++) + { + int ii0 = ii; + int ii1 = (ii+1) % Si; + + // continue if we've already found this neighbor + if(ni[ii] != -1) + continue; + found = false; + // check all remaining polygons + for(int j = i+1; j < P; j++ ) + { + const MyArrayInt & vj = p[j].vi; + MyArrayInt & nj = p[j].ni; + int Sj = vj.size(); + + for( int jj = 0; jj < Sj; jj++ ) + { + int jj0 = jj; + int jj1 = (jj+1) % Sj; + if(vi[ii0] == vj[jj1] && vi[ii1] == vj[jj0]) + { + edge ed; + ed.vi[0] = vi[ii0]; + ed.vi[1] = vi[ii1]; + ed.pi[0] = i; + ed.pi[1] = j; + e.push_back(ed); + ni[ii] = j; + nj[jj] = i; + found = true; + break; + } + else if ( vi[ii0] == vj[jj0] && vi[ii1] == vj[jj1] ) + { + fprintf(stderr,"why am I here?\n"); + } + } + if( found ) + break; + } + } + } + } + + void recompute_planes() + { + // for each polygon + for(unsigned int i = 0; i < p.size(); i++ ) + { + p[i].plane = compute_homogeneous_plane(v[p[i].vi[0]], v[p[i].vi[1]], v[p[i].vi[2]]); + } + } + + void transform(const idMat4 & m) + { + for(unsigned int i=0; i < v.size(); i++ ) + v[i] = m * v[i]; + recompute_planes(); + } + +}; + +// make a unit cube +polyhedron PolyhedronFromBounds( const idBounds & b ) +{ + +// 3----------2 +// |\ /| +// | \ / | +// | 7--6 | +// | | | | +// | 4--5 | +// | / \ | +// | / \ | +// 0----------1 +// + + static polyhedron p; + + if( p.e.size() == 0 ) { + + p.v.push_back(idVec4( -1, -1, 1, 1)); + p.v.push_back(idVec4( 1, -1, 1, 1)); + p.v.push_back(idVec4( 1, 1, 1, 1)); + p.v.push_back(idVec4( -1, 1, 1, 1)); + p.v.push_back(idVec4( -1, -1, -1, 1)); + p.v.push_back(idVec4( 1, -1, -1, 1)); + p.v.push_back(idVec4( 1, 1, -1, 1)); + p.v.push_back(idVec4( -1, 1, -1, 1)); + + p.add_quad( 0, 1, 2, 3 ); + p.add_quad( 7, 6, 5, 4 ); + p.add_quad( 1, 0, 4, 5 ); + p.add_quad( 2, 1, 5, 6 ); + p.add_quad( 3, 2, 6, 7 ); + p.add_quad( 0, 3, 7, 4 ); + + p.compute_neighbors(); + p.recompute_planes(); + p.v.empty(); // no need to copy this data since it'll be replaced + } + + polyhedron p2(p); + + const idVec3 & min = b[0]; + const idVec3 & max = b[1]; + + p2.v.empty(); + p2.v.push_back(idVec4( min.x, min.y, max.z, 1)); + p2.v.push_back(idVec4( max.x, min.y, max.z, 1)); + p2.v.push_back(idVec4( max.x, max.y, max.z, 1)); + p2.v.push_back(idVec4( min.x, max.y, max.z, 1)); + p2.v.push_back(idVec4( min.x, min.y, min.z, 1)); + p2.v.push_back(idVec4( max.x, min.y, min.z, 1)); + p2.v.push_back(idVec4( max.x, max.y, min.z, 1)); + p2.v.push_back(idVec4( min.x, max.y, min.z, 1)); + + p2.recompute_planes(); + return p2; +} + + +polyhedron make_sv(const polyhedron & oc, idVec4 light) +{ + static polyhedron lut[64]; + int index = 0; + + for(unsigned int i = 0; i < 6; i++) { + if( ( oc.p[i].plane * light ) > 0 ) + index |= 1< 0) + { + ph.p.push_back(oc.p[i]); + } + } + + if(ph.p.size() == 0) + return ph = polyhedron(); + + ph.compute_neighbors(); + + MyArrayPoly vpg; + int I = ph.p.size(); + + for(int i=0; i < I; i++) + { + MyArrayInt & vi = ph.p[i].vi; + MyArrayInt & ni = ph.p[i].ni; + int S = vi.size(); + + for(int j = 0; j < S; j++) + { + if( ni[j] == -1 ) + { + poly pg; + int a = vi[(j+1)%S]; + int b = vi[j]; + pg.vi = four_ints( a, b, b+V, a+V); + pg.ni = four_ints(-1, -1, -1, -1); + vpg.push_back(pg); + } + } + } + for(unsigned int i = 0; i < vpg.size(); i++) + ph.p.push_back(vpg[i]); + + ph.compute_neighbors(); + ph.v.empty(); // no need to copy this data since it'll be replaced + } + + polyhedron ph2 = lut[index]; + + // initalize vertices + ph2.v = oc.v; + int V = ph2.v.size(); + for( int j = 0; j < V; j++ ) + { + idVec3 proj = homogeneous_difference( light, ph2.v[j] ); + ph2.v.push_back( idVec4(proj.x, proj.y, proj.z, 0) ); + } + + // need to compute planes for the shadow volume (sv) + ph2.recompute_planes(); + + return ph2; +} + +typedef MyArray MySegments; +//int MySegments::max_size = 0; + +void polyhedron_edges(polyhedron & a, MySegments & e) +{ + e.empty(); + if(a.e.size() == 0 && a.p.size() != 0) + a.compute_neighbors(); + + for(unsigned int i = 0; i < a.e.size(); i++) + { + e.push_back(a.v[a.e[i].vi[0]]); + e.push_back(a.v[a.e[i].vi[1]]); + } + +} + +// clip the segments of e by the planes of polyhedron a. +void clip_segments(const polyhedron & ph, MySegments & is, MySegments & os) +{ + const MyArrayPoly & p = ph.p; + + for(unsigned int i = 0; i < is.size(); i+=2 ) + { + idVec4 a = is[i ]; + idVec4 b = is[i+1]; + idVec4 c; + + bool discard = false; + + for(unsigned int j = 0; j < p.size(); j++ ) + { + float da = a * p[j].plane; + float db = b * p[j].plane; + float rdw = 1/(da - db); + + int code = 0; + if( da > 0 ) + code = 2; + if( db > 0 ) + code |= 1; + + + switch ( code ) + { + case 3: + discard = true; + break; + + case 2: + c = -db * rdw * a + da * rdw * b; + a = c; + break; + + case 1: + c = -db * rdw * a + da * rdw * b; + b = c; + break; + + case 0: + break; + + default: + common->Printf("bad clip code!\n"); + break; + } + + if( discard ) + break; + } + + if( ! discard ) + { + os.push_back(a); + os.push_back(b); + } + } + +} + +idMat4 make_idMat4(const float * m) +{ + return idMat4( m[ 0], m[ 4], m[ 8], m[12], + m[ 1], m[ 5], m[ 9], m[13], + m[ 2], m[ 6], m[10], m[14], + m[ 3], m[ 7], m[11], m[15] ); +} + +idVec3 v4to3(const idVec4 & v) +{ + return idVec3(v.x/v.w, v.y/v.w, v.z/v.w); +} + +void draw_polyhedron( const viewDef_t *viewDef, const polyhedron & p, idVec4 color ) +{ + for(unsigned int i = 0; i < p.e.size(); i++) + { + viewDef->renderWorld->DebugLine( color, v4to3(p.v[p.e[i].vi[0]]), v4to3(p.v[p.e[i].vi[1]])); + } +} + +void draw_segments( const viewDef_t *viewDef, const MySegments & s, idVec4 color ) +{ + for(unsigned int i = 0; i < s.size(); i+=2) + { + viewDef->renderWorld->DebugLine( color, v4to3(s[i]), v4to3(s[i+1])); + } +} + +void world_to_hclip( const viewDef_t *viewDef, const idVec4 &global, idVec4 &clip ) { + int i; + idVec4 view; + + for ( i = 0 ; i < 4 ; i ++ ) { + view[i] = + global[0] * viewDef->worldSpace.modelViewMatrix[ i + 0 * 4 ] + + global[1] * viewDef->worldSpace.modelViewMatrix[ i + 1 * 4 ] + + global[2] * viewDef->worldSpace.modelViewMatrix[ i + 2 * 4 ] + + global[3] * viewDef->worldSpace.modelViewMatrix[ i + 3 * 4 ]; + } + + + for ( i = 0 ; i < 4 ; i ++ ) { + clip[i] = + view[0] * viewDef->projectionMatrix[ i + 0 * 4 ] + + view[1] * viewDef->projectionMatrix[ i + 1 * 4 ] + + view[2] * viewDef->projectionMatrix[ i + 2 * 4 ] + + view[3] * viewDef->projectionMatrix[ i + 3 * 4 ]; + } +} + +idScreenRect R_CalcIntersectionScissor( const idRenderLightLocal * lightDef, + const idRenderEntityLocal * entityDef, + const viewDef_t * viewDef ) { + + idMat4 omodel = make_idMat4( entityDef->modelMatrix ); + idMat4 lmodel = make_idMat4( lightDef->modelMatrix ); + + // compute light polyhedron + polyhedron lvol = PolyhedronFromBounds( lightDef->frustumTris->bounds ); + // transform it into world space + //lvol.transform( lmodel ); + + // debug // + if ( r_useInteractionScissors.GetInteger() == -2 ) { + draw_polyhedron( viewDef, lvol, colorRed ); + } + + // compute object polyhedron + polyhedron vol = PolyhedronFromBounds( entityDef->referenceBounds ); + + //viewDef->renderWorld->DebugBounds( colorRed, lightDef->frustumTris->bounds ); + //viewDef->renderWorld->DebugBox( colorBlue, idBox( model->Bounds(), entityDef->parms.origin, entityDef->parms.axis ) ); + + // transform it into world space + vol.transform( omodel ); + + // debug // + if ( r_useInteractionScissors.GetInteger() == -2 ) { + draw_polyhedron( viewDef, vol, colorBlue ); + } + + // transform light position into world space + idVec4 lightpos = idVec4(lightDef->globalLightOrigin.x, + lightDef->globalLightOrigin.y, + lightDef->globalLightOrigin.z, + 1.0f ); + + // generate shadow volume "polyhedron" + polyhedron sv = make_sv(vol, lightpos); + + MySegments in_segs, out_segs; + + // get shadow volume edges + polyhedron_edges(sv, in_segs); + // clip them against light bounds planes + clip_segments(lvol, in_segs, out_segs); + + // get light bounds edges + polyhedron_edges(lvol, in_segs); + // clip them by the shadow volume + clip_segments(sv, in_segs, out_segs); + + // debug // + if ( r_useInteractionScissors.GetInteger() == -2 ) { + draw_segments( viewDef, out_segs, colorGreen ); + } + + idBounds outbounds; + outbounds.Clear(); + for( unsigned int i = 0; i < out_segs.size(); i++ ) { + + idVec4 v; + world_to_hclip( viewDef, out_segs[i], v ); + + if( v.w <= 0.0f ) { + return lightDef->viewLight->scissorRect; + } + + idVec3 rv(v.x, v.y, v.z); + rv /= v.w; + + outbounds.AddPoint( rv ); + } + + // limit the bounds to avoid an inside out scissor rectangle due to floating point to short conversion + if ( outbounds[0].x < -1.0f ) { + outbounds[0].x = -1.0f; + } + if ( outbounds[1].x > 1.0f ) { + outbounds[1].x = 1.0f; + } + if ( outbounds[0].y < -1.0f ) { + outbounds[0].y = -1.0f; + } + if ( outbounds[1].y > 1.0f ) { + outbounds[1].y = 1.0f; + } + + float w2 = ( viewDef->viewport.x2 - viewDef->viewport.x1 + 1 ) / 2.0f; + float x = viewDef->viewport.x1; + float h2 = ( viewDef->viewport.y2 - viewDef->viewport.y1 + 1 ) / 2.0f; + float y = viewDef->viewport.y1; + + idScreenRect rect; + rect.x1 = outbounds[0].x * w2 + w2 + x; + rect.x2 = outbounds[1].x * w2 + w2 + x; + rect.y1 = outbounds[0].y * h2 + h2 + y; + rect.y2 = outbounds[1].y * h2 + h2 + y; + rect.Expand(); + + rect.Intersect( lightDef->viewLight->scissorRect ); + + // debug // + if ( r_useInteractionScissors.GetInteger() == -2 && !rect.IsEmpty() ) { + viewDef->renderWorld->DebugScreenRect( colorYellow, rect, viewDef ); + } + + return rect; +} diff --git a/src/renderer/tr_stencilshadow.cpp b/src/renderer/tr_stencilshadow.cpp new file mode 100644 index 0000000..4267bc0 --- /dev/null +++ b/src/renderer/tr_stencilshadow.cpp @@ -0,0 +1,1336 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +// tr_stencilShadow.c -- creaton of stencil shadow volumes + +/* + + Should we split shadow volume surfaces when they exceed max verts + or max indexes? + + a problem is that the number of vertexes needed for the + shadow volume will be twice the number in the original, + and possibly up to 8/3 when near plane clipped. + + The maximum index count is 7x when not clipped and all + triangles are completely discrete. Near plane clipping + can increase this to 10x. + + The maximum expansions are always with discrete triangles. + Meshes of triangles will result in less index expansion because + there will be less silhouette edges, although it will always be + greater than the source if a cap is present. + + can't just project onto a plane if some surface points are + behind the light. + + The cases when a face is edge on to a light is robustly handled + with closed volumes, because only a single one of it's neighbors + will pass the edge test. It may be an issue with non-closed models. + + It is crucial that the shadow volumes be completely enclosed. + The triangles identified as shadow sources will be projected + directly onto the light far plane. + The sil edges must be handled carefully. + A partially clipped explicit sil edge will still generate a sil + edge. + EVERY new edge generated by clipping the triangles to the view + will generate a sil edge. + + If a triangle has no points inside the frustum, it is completely + culled away. If a sil edge is either in or on the frustum, it is + added. + If a triangle has no points outside the frustum, it does not + need to be clipped. + + + + USING THE STENCIL BUFFER FOR SHADOWING + + basic triangle property + + view plane inside shadow volume problem + + quad triangulation issue + + issues with silhouette optimizations + + the shapes of shadow projections are poor for sphere or box culling + + the gouraud shading problem + + + // epsilon culling rules: + +// the positive side of the frustum is inside +d = tri->verts[i].xyz * frustum[j].Normal() + frustum[j][3]; +if ( d < LIGHT_CLIP_EPSILON ) { + pointCull[i] |= ( 1 << j ); +} +if ( d > -LIGHT_CLIP_EPSILON ) { + pointCull[i] |= ( 1 << (6+j) ); +} + +If a low order bit is set, the point is on or outside the plane +If a high order bit is set, the point is on or inside the plane +If a low order bit is clear, the point is inside the plane (definately positive) +If a high order bit is clear, the point is outside the plane (definately negative) + + +*/ + +#define TRIANGLE_CULLED(p1,p2,p3) ( pointCull[p1] & pointCull[p2] & pointCull[p3] & 0x3f ) + +//#define TRIANGLE_CLIPPED(p1,p2,p3) ( ( pointCull[p1] | pointCull[p2] | pointCull[p3] ) & 0xfc0 ) +#define TRIANGLE_CLIPPED(p1,p2,p3) ( ( ( pointCull[p1] & pointCull[p2] & pointCull[p3] ) & 0xfc0 ) != 0xfc0 ) + +// an edge that is on the plane is NOT culled +#define EDGE_CULLED(p1,p2) ( ( pointCull[p1] ^ 0xfc0 ) & ( pointCull[p2] ^ 0xfc0 ) & 0xfc0 ) + +#define EDGE_CLIPPED(p1,p2) ( ( pointCull[p1] & pointCull[p2] & 0xfc0 ) != 0xfc0 ) + +// a point that is on the plane is NOT culled +//#define POINT_CULLED(p1) ( ( pointCull[p1] ^ 0xfc0 ) & 0xfc0 ) +#define POINT_CULLED(p1) ( ( pointCull[p1] & 0xfc0 ) != 0xfc0 ) + +//#define LIGHT_CLIP_EPSILON 0.001f +#define LIGHT_CLIP_EPSILON 0.1f + +#define MAX_CLIP_SIL_EDGES 2048 +static int numClipSilEdges; +static int clipSilEdges[MAX_CLIP_SIL_EDGES][2]; + +// facing will be 0 if forward facing, 1 if backwards facing +// grabbed with alloca +static byte *globalFacing; + +// faceCastsShadow will be 1 if the face is in the projection +// and facing the apropriate direction +static byte *faceCastsShadow; + +static int *remap; + +#define MAX_SHADOW_INDEXES 0x18000 +#define MAX_SHADOW_VERTS 0x18000 +static int numShadowIndexes; +static glIndex_t shadowIndexes[MAX_SHADOW_INDEXES]; +static int numShadowVerts; +static idVec4 shadowVerts[MAX_SHADOW_VERTS]; +static bool overflowed; + +idPlane pointLightFrustums[6][6] = { + { + idPlane( 1,0,0,0 ), + idPlane( 1,1,0,0 ), + idPlane( 1,-1,0,0 ), + idPlane( 1,0,1,0 ), + idPlane( 1,0,-1,0 ), + idPlane( -1,0,0,0 ), + }, + { + idPlane( -1,0,0,0 ), + idPlane( -1,1,0,0 ), + idPlane( -1,-1,0,0 ), + idPlane( -1,0,1,0 ), + idPlane( -1,0,-1,0 ), + idPlane( 1,0,0,0 ), + }, + + { + idPlane( 0,1,0,0 ), + idPlane( 0,1,1,0 ), + idPlane( 0,1,-1,0 ), + idPlane( 1,1,0,0 ), + idPlane( -1,1,0,0 ), + idPlane( 0,-1,0,0 ), + }, + { + idPlane( 0,-1,0,0 ), + idPlane( 0,-1,1,0 ), + idPlane( 0,-1,-1,0 ), + idPlane( 1,-1,0,0 ), + idPlane( -1,-1,0,0 ), + idPlane( 0,1,0,0 ), + }, + + { + idPlane( 0,0,1,0 ), + idPlane( 1,0,1,0 ), + idPlane( -1,0,1,0 ), + idPlane( 0,1,1,0 ), + idPlane( 0,-1,1,0 ), + idPlane( 0,0,-1,0 ), + }, + { + idPlane( 0,0,-1,0 ), + idPlane( 1,0,-1,0 ), + idPlane( -1,0,-1,0 ), + idPlane( 0,1,-1,0 ), + idPlane( 0,-1,-1,0 ), + idPlane( 0,0,1,0 ), + }, +}; + +int c_caps, c_sils; + +typedef struct { + int frontCapStart; + int rearCapStart; + int silStart; + int end; +} indexRef_t; +static indexRef_t indexRef[6]; +static int indexFrustumNumber; // which shadow generating side of a light the indexRef is for + +/* +=============== +PointsOrdered + +To make sure the triangulations of the sil edges is consistant, +we need to be able to order two points. We don't care about how +they compare with any other points, just that when the same two +points are passed in (in either order), they will always specify +the same one as leading. + +Currently we need to have separate faces in different surfaces +order the same way, so we must look at the actual coordinates. +If surfaces are ever guaranteed to not have to edge match with +other surfaces, we could just compare indexes. +=============== +*/ +static bool PointsOrdered( const idVec3 &a, const idVec3 &b ) { + float i, j; + + // vectors that wind up getting an equal hash value will + // potentially cause a misorder, which can show as a couple + // crack pixels in a shadow + + // scale by some odd numbers so -8, 8, 8 will not be equal + // to 8, -8, 8 + + // in the very rare case that these might be equal, all that would + // happen is an oportunity for a tiny rasterization shadow crack + i = a[0] + a[1]*127 + a[2]*1023; + j = b[0] + b[1]*127 + b[2]*1023; + + return (bool)(i < j); +} + +/* +==================== +idRenderSystemLocal::LightProjectionMatrix + +==================== +*/ +void idRenderSystemLocal::LightProjectionMatrix( const idVec3 &origin, const idPlane &rearPlane, idVec4 mat[4] ) { + idVec4 lv; + float lg; + + // calculate the homogenious light vector + lv.x = origin.x; + lv.y = origin.y; + lv.z = origin.z; + lv.w = 1; + + lg = rearPlane.ToVec4() * lv; + + // outer product + mat[0][0] = lg -rearPlane[0] * lv[0]; + mat[0][1] = -rearPlane[1] * lv[0]; + mat[0][2] = -rearPlane[2] * lv[0]; + mat[0][3] = -rearPlane[3] * lv[0]; + + mat[1][0] = -rearPlane[0] * lv[1]; + mat[1][1] = lg -rearPlane[1] * lv[1]; + mat[1][2] = -rearPlane[2] * lv[1]; + mat[1][3] = -rearPlane[3] * lv[1]; + + mat[2][0] = -rearPlane[0] * lv[2]; + mat[2][1] = -rearPlane[1] * lv[2]; + mat[2][2] = lg -rearPlane[2] * lv[2]; + mat[2][3] = -rearPlane[3] * lv[2]; + + mat[3][0] = -rearPlane[0] * lv[3]; + mat[3][1] = -rearPlane[1] * lv[3]; + mat[3][2] = -rearPlane[2] * lv[3]; + mat[3][3] = lg -rearPlane[3] * lv[3]; +} + +/* +=================== +R_ProjectPointsToFarPlane + +make a projected copy of the even verts into the odd spots +that is on the far light clip plane +=================== +*/ +static void R_ProjectPointsToFarPlane( const idRenderEntityLocal *ent, const idRenderLightLocal *light, + const idPlane &lightPlaneLocal, + int firstShadowVert, int numShadowVerts ) { + idVec3 lv; + idVec4 mat[4]; + int i; + idVec4 *in; + + R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, lv ); + renderSystem->LightProjectionMatrix( lv, lightPlaneLocal, mat ); + +#if 1 + // make a projected copy of the even verts into the odd spots + in = &shadowVerts[firstShadowVert]; + for ( i = firstShadowVert ; i < numShadowVerts ; i+= 2, in += 2 ) { + float w, oow; + + in[0].w = 1; + + w = in->ToVec3() * mat[3].ToVec3() + mat[3][3]; + if ( w == 0 ) { + in[1] = in[0]; + continue; + } + + oow = 1.0 / w; + in[1].x = ( in->ToVec3() * mat[0].ToVec3() + mat[0][3] ) * oow; + in[1].y = ( in->ToVec3() * mat[1].ToVec3() + mat[1][3] ) * oow; + in[1].z = ( in->ToVec3() * mat[2].ToVec3() + mat[2][3] ) * oow; + in[1].w = 1; + } + +#else + // messing with W seems to cause some depth precision problems + + // make a projected copy of the even verts into the odd spots + in = &shadowVerts[firstShadowVert]; + for ( i = firstShadowVert ; i < numShadowVerts ; i+= 2, in += 2 ) { + in[0].w = 1; + in[1].x = *in * mat[0].ToVec3() + mat[0][3]; + in[1].y = *in * mat[1].ToVec3() + mat[1][3]; + in[1].z = *in * mat[2].ToVec3() + mat[2][3]; + in[1].w = *in * mat[3].ToVec3() + mat[3][3]; + } +#endif +} + + + +#define MAX_CLIPPED_POINTS 20 +typedef struct { + int numVerts; + idVec3 verts[MAX_CLIPPED_POINTS]; + int edgeFlags[MAX_CLIPPED_POINTS]; +} clipTri_t; + +/* +============= +R_ChopWinding + +Clips a triangle from one buffer to another, setting edge flags +The returned buffer may be the same as inNum if no clipping is done +If entirely clipped away, clipTris[returned].numVerts == 0 + +I have some worries about edge flag cases when polygons are clipped +multiple times near the epsilon. +============= +*/ +static int R_ChopWinding( clipTri_t clipTris[2], int inNum, const idPlane &plane ) { + clipTri_t *in, *out; + float dists[MAX_CLIPPED_POINTS]; + int sides[MAX_CLIPPED_POINTS]; + int counts[3]; + float dot; + int i, j; + idVec3 *p1, *p2; + idVec3 mid; + + in = &clipTris[inNum]; + out = &clipTris[inNum^1]; + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < in->numVerts ; i++ ) { + dot = plane.Distance( in->verts[i] ); + dists[i] = dot; + if ( dot < -LIGHT_CLIP_EPSILON ) { + sides[i] = SIDE_BACK; + } else if ( dot > LIGHT_CLIP_EPSILON ) { + sides[i] = SIDE_FRONT; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + // if none in front, it is completely clipped away + if ( !counts[SIDE_FRONT] ) { + in->numVerts = 0; + return inNum; + } + if ( !counts[SIDE_BACK] ) { + return inNum; // inout stays the same + } + + // avoid wrapping checks by duplicating first value to end + sides[i] = sides[0]; + dists[i] = dists[0]; + in->verts[in->numVerts] = in->verts[0]; + in->edgeFlags[in->numVerts] = in->edgeFlags[0]; + + out->numVerts = 0; + for ( i = 0 ; i < in->numVerts ; i++ ) { + p1 = &in->verts[i]; + + if ( sides[i] != SIDE_BACK ) { + out->verts[out->numVerts] = *p1; + if ( sides[i] == SIDE_ON && sides[i+1] == SIDE_BACK ) { + out->edgeFlags[out->numVerts] = 1; + } else { + out->edgeFlags[out->numVerts] = in->edgeFlags[i]; + } + out->numVerts++; + } + + if ( (sides[i] == SIDE_FRONT && sides[i+1] == SIDE_BACK) + || (sides[i] == SIDE_BACK && sides[i+1] == SIDE_FRONT) ) { + // generate a split point + p2 = &in->verts[i+1]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for ( j=0 ; j<3 ; j++ ) { + mid[j] = (*p1)[j] + dot*((*p2)[j]-(*p1)[j]); + } + + out->verts[out->numVerts] = mid; + + // set the edge flag + if ( sides[i+1] != SIDE_FRONT ) { + out->edgeFlags[out->numVerts] = 1; + } else { + out->edgeFlags[out->numVerts] = in->edgeFlags[i]; + } + + out->numVerts++; + } + } + + return inNum ^ 1; +} + +/* +=================== +R_ClipTriangleToLight + +Returns false if nothing is left after clipping +=================== +*/ +static bool R_ClipTriangleToLight( const idVec3 &a, const idVec3 &b, const idVec3 &c, int planeBits, + const idPlane frustum[6] ) { + int i; + int base; + clipTri_t pingPong[2], *ct; + int p; + + pingPong[0].numVerts = 3; + pingPong[0].edgeFlags[0] = 0; + pingPong[0].edgeFlags[1] = 0; + pingPong[0].edgeFlags[2] = 0; + pingPong[0].verts[0] = a; + pingPong[0].verts[1] = b; + pingPong[0].verts[2] = c; + + p = 0; + for ( i = 0 ; i < 6 ; i++ ) { + if ( planeBits & ( 1 << i ) ) { + p = R_ChopWinding( pingPong, p, frustum[i] ); + if ( pingPong[p].numVerts < 1 ) { + return false; + } + } + } + ct = &pingPong[p]; + + // copy the clipped points out to shadowVerts + if ( numShadowVerts + ct->numVerts * 2 > MAX_SHADOW_VERTS ) { + overflowed = true; + return false; + } + + base = numShadowVerts; + for ( i = 0 ; i < ct->numVerts ; i++ ) { + shadowVerts[ base + i*2 ].ToVec3() = ct->verts[i]; + } + numShadowVerts += ct->numVerts * 2; + + if ( numShadowIndexes + 3 * ( ct->numVerts - 2 ) > MAX_SHADOW_INDEXES ) { + overflowed = true; + return false; + } + + for ( i = 2 ; i < ct->numVerts ; i++ ) { + shadowIndexes[numShadowIndexes++] = base + i * 2; + shadowIndexes[numShadowIndexes++] = base + ( i - 1 ) * 2; + shadowIndexes[numShadowIndexes++] = base; + } + + // any edges that were created by the clipping process will + // have a silhouette quad created for it, because it is one + // of the exterior bounds of the shadow volume + for ( i = 0 ; i < ct->numVerts ; i++ ) { + if ( ct->edgeFlags[i] ) { + if ( numClipSilEdges == MAX_CLIP_SIL_EDGES ) { + break; + } + clipSilEdges[ numClipSilEdges ][0] = base + i * 2; + if ( i == ct->numVerts - 1 ) { + clipSilEdges[ numClipSilEdges ][1] = base; + } else { + clipSilEdges[ numClipSilEdges ][1] = base + ( i + 1 ) * 2; + } + numClipSilEdges++; + } + } + + return true; +} + +/* +=================== +R_ClipLineToLight + +If neither point is clearly behind the clipping +plane, the edge will be passed unmodified. A sil edge that +is on a border plane must be drawn. + +If one point is clearly clipped by the plane and the +other point is on the plane, it will be completely removed. +=================== +*/ +static bool R_ClipLineToLight( const idVec3 &a, const idVec3 &b, const idPlane frustum[4], + idVec3 &p1, idVec3 &p2 ) { + float *clip; + int j; + float d1, d2; + float f; + + p1 = a; + p2 = b; + + // clip it + for ( j = 0 ; j < 6 ; j++ ) { + d1 = frustum[j].Distance( p1 ); + d2 = frustum[j].Distance( p2 ); + + // if both on or in front, not clipped to this plane + if ( d1 > -LIGHT_CLIP_EPSILON && d2 > -LIGHT_CLIP_EPSILON ) { + continue; + } + + // if one is behind and the other isn't clearly in front, the edge is clipped off + if ( d1 <= -LIGHT_CLIP_EPSILON && d2 < LIGHT_CLIP_EPSILON ) { + return false; + } + if ( d2 <= -LIGHT_CLIP_EPSILON && d1 < LIGHT_CLIP_EPSILON ) { + return false; + } + + // clip it, keeping the negative side + if ( d1 < 0 ) { + clip = p1.ToFloatPtr(); + } else { + clip = p2.ToFloatPtr(); + } + +#if 0 + if ( idMath::Fabs(d1 - d2) < 0.001 ) { + d2 = d1 - 0.1; + } +#endif + + f = d1 / ( d1 - d2 ); + clip[0] = p1[0] + f * ( p2[0] - p1[0] ); + clip[1] = p1[1] + f * ( p2[1] - p1[1] ); + clip[2] = p1[2] + f * ( p2[2] - p1[2] ); + } + + return true; // retain a fragment +} + + +/* +================== +R_AddClipSilEdges + +Add sil edges for each triangle clipped to the side of +the frustum. + +Only done for simple projected lights, not point lights. +================== +*/ +static void R_AddClipSilEdges( void ) { + int v1, v2; + int v1_back, v2_back; + int i; + + // don't allow it to overflow + if ( numShadowIndexes + numClipSilEdges * 6 > MAX_SHADOW_INDEXES ) { + overflowed = true; + return; + } + + for ( i = 0 ; i < numClipSilEdges ; i++ ) { + v1 = clipSilEdges[i][0]; + v2 = clipSilEdges[i][1]; + v1_back = v1 + 1; + v2_back = v2 + 1; + if ( PointsOrdered( shadowVerts[ v1 ].ToVec3(), shadowVerts[ v2 ].ToVec3() ) ) { + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v1_back; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v2_back; + shadowIndexes[numShadowIndexes++] = v1_back; + } else { + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v2_back; + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v2_back; + shadowIndexes[numShadowIndexes++] = v1_back; + } + } +} + +/* +================= +R_AddSilEdges + +Add quads from the front points to the projected points +for each silhouette edge in the light +================= +*/ +static void R_AddSilEdges( const srfTriangles_t *tri, unsigned short *pointCull, const idPlane frustum[6] ) { + int v1, v2; + int i; + silEdge_t *sil; + int numPlanes; + + numPlanes = tri->numIndexes / 3; + + // add sil edges for any true silhouette boundaries on the surface + for ( i = 0 ; i < tri->numSilEdges ; i++ ) { + sil = tri->silEdges + i; + if ( sil->p1 < 0 || sil->p1 > numPlanes || sil->p2 < 0 || sil->p2 > numPlanes ) { + common->Error( "Bad sil planes" ); + } + + // an edge will be a silhouette edge if the face on one side + // casts a shadow, but the face on the other side doesn't. + // "casts a shadow" means that it has some surface in the projection, + // not just that it has the correct facing direction + // This will cause edges that are exactly on the frustum plane + // to be considered sil edges if the face inside casts a shadow. + if ( !( faceCastsShadow[ sil->p1 ] ^ faceCastsShadow[ sil->p2 ] ) ) { + continue; + } + + // if the edge is completely off the negative side of + // a frustum plane, don't add it at all. This can still + // happen even if the face is visible and casting a shadow + // if it is partially clipped + if ( EDGE_CULLED( sil->v1, sil->v2 ) ) { + continue; + } + + // see if the edge needs to be clipped + if ( EDGE_CLIPPED( sil->v1, sil->v2 ) ) { + if ( numShadowVerts + 4 > MAX_SHADOW_VERTS ) { + overflowed = true; + return; + } + v1 = numShadowVerts; + v2 = v1 + 2; + if ( !R_ClipLineToLight( tri->verts[ sil->v1 ].xyz, tri->verts[ sil->v2 ].xyz, + frustum, shadowVerts[v1].ToVec3(), shadowVerts[v2].ToVec3() ) ) { + continue; // clipped away + } + + numShadowVerts += 4; + } else { + // use the entire edge + v1 = remap[ sil->v1 ]; + v2 = remap[ sil->v2 ]; + if ( v1 < 0 || v2 < 0 ) { + common->Error( "R_AddSilEdges: bad remap[]" ); + } + } + + // don't overflow + if ( numShadowIndexes + 6 > MAX_SHADOW_INDEXES ) { + overflowed = true; + return; + } + + // we need to choose the correct way of triangulating the silhouette quad + // consistantly between any two points, no matter which order they are specified. + // If this wasn't done, slight rasterization cracks would show in the shadow + // volume when two sil edges were exactly coincident + if ( faceCastsShadow[ sil->p2 ] ) { + if ( PointsOrdered( shadowVerts[ v1 ].ToVec3(), shadowVerts[ v2 ].ToVec3() ) ) { + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v1+1; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v1+1; + shadowIndexes[numShadowIndexes++] = v2+1; + } else { + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v2+1; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v1+1; + shadowIndexes[numShadowIndexes++] = v2+1; + } + } else { + if ( PointsOrdered( shadowVerts[ v1 ].ToVec3(), shadowVerts[ v2 ].ToVec3() ) ) { + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v1+1; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v2+1; + shadowIndexes[numShadowIndexes++] = v1+1; + } else { + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v2; + shadowIndexes[numShadowIndexes++] = v2+1; + shadowIndexes[numShadowIndexes++] = v1; + shadowIndexes[numShadowIndexes++] = v2+1; + shadowIndexes[numShadowIndexes++] = v1+1; + } + } + } +} + +/* +================ +R_CalcPointCull + +Also inits the remap[] array to all -1 +================ +*/ +static void R_CalcPointCull( const srfTriangles_t *tri, const idPlane frustum[6], unsigned short *pointCull ) { + int i; + int frontBits; + float *planeSide; + byte *side1, *side2; + + SIMDProcessor->Memset( remap, -1, tri->numVerts * sizeof( remap[0] ) ); + + for ( frontBits = 0, i = 0; i < 6; i++ ) { + // get front bits for the whole surface + if ( tri->bounds.PlaneDistance( frustum[i] ) >= LIGHT_CLIP_EPSILON ) { + frontBits |= 1<<(i+6); + } + } + + // initialize point cull + for ( i = 0; i < tri->numVerts; i++ ) { + pointCull[i] = frontBits; + } + + // if the surface is not completely inside the light frustum + if ( frontBits == ( ( ( 1 << 6 ) - 1 ) ) << 6 ) { + return; + } + + planeSide = (float *) _alloca16( tri->numVerts * sizeof( float ) ); + side1 = (byte *) _alloca16( tri->numVerts * sizeof( byte ) ); + side2 = (byte *) _alloca16( tri->numVerts * sizeof( byte ) ); + SIMDProcessor->Memset( side1, 0, tri->numVerts * sizeof( byte ) ); + SIMDProcessor->Memset( side2, 0, tri->numVerts * sizeof( byte ) ); + + for ( i = 0; i < 6; i++ ) { + + if ( frontBits & (1<<(i+6)) ) { + continue; + } + + SIMDProcessor->Dot( planeSide, frustum[i], tri->verts, tri->numVerts ); + SIMDProcessor->CmpLT( side1, i, planeSide, LIGHT_CLIP_EPSILON, tri->numVerts ); + SIMDProcessor->CmpGT( side2, i, planeSide, -LIGHT_CLIP_EPSILON, tri->numVerts ); + } + for ( i = 0; i < tri->numVerts; i++ ) { + pointCull[i] |= side1[i] | (side2[i] << 6); + } +} + +/* +================= +R_CreateShadowVolumeInFrustum + +Adds new verts and indexes to the shadow volume. + +If the frustum completely defines the projected light, +makeClippedPlanes should be true, which will cause sil quads to +be added along all clipped edges. + +If the frustum is just part of a point light, clipped planes don't +need to be added. +================= +*/ +static void R_CreateShadowVolumeInFrustum( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, + const idRenderLightLocal *light, + const idVec3 lightOrigin, + const idPlane frustum[6], + const idPlane &farPlane, + bool makeClippedPlanes ) { + int i; + int numTris; + unsigned short *pointCull; + int numCapIndexes; + int firstShadowIndex; + int firstShadowVert; + int cullBits; + + pointCull = (unsigned short *)_alloca16( tri->numVerts * sizeof( pointCull[0] ) ); + + // test the vertexes for inside the light frustum, which will allow + // us to completely cull away some triangles from consideration. + R_CalcPointCull( tri, frustum, pointCull ); + + // this may not be the first frustum added to the volume + firstShadowIndex = numShadowIndexes; + firstShadowVert = numShadowVerts; + + // decide which triangles front shadow volumes, clipping as needed + numClipSilEdges = 0; + numTris = tri->numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + + faceCastsShadow[i] = 0; // until shown otherwise + + // if it isn't facing the right way, don't add it + // to the shadow volume + if ( globalFacing[i] ) { + continue; + } + + i1 = tri->silIndexes[ i*3 + 0 ]; + i2 = tri->silIndexes[ i*3 + 1 ]; + i3 = tri->silIndexes[ i*3 + 2 ]; + + // if all the verts are off one side of the frustum, + // don't add any of them + if ( TRIANGLE_CULLED( i1, i2, i3 ) ) { + continue; + } + + // make sure the verts that are not on the negative sides + // of the frustum are copied over. + // we need to get the original verts even from clipped triangles + // so the edges reference correctly, because an edge may be unclipped + // even when a triangle is clipped. + if ( numShadowVerts + 6 > MAX_SHADOW_VERTS ) { + overflowed = true; + return; + } + + if ( !POINT_CULLED(i1) && remap[i1] == -1 ) { + remap[i1] = numShadowVerts; + shadowVerts[ numShadowVerts ].ToVec3() = tri->verts[i1].xyz; + numShadowVerts+=2; + } + if ( !POINT_CULLED(i2) && remap[i2] == -1 ) { + remap[i2] = numShadowVerts; + shadowVerts[ numShadowVerts ].ToVec3() = tri->verts[i2].xyz; + numShadowVerts+=2; + } + if ( !POINT_CULLED(i3) && remap[i3] == -1 ) { + remap[i3] = numShadowVerts; + shadowVerts[ numShadowVerts ].ToVec3() = tri->verts[i3].xyz; + numShadowVerts+=2; + } + + // clip the triangle if any points are on the negative sides + if ( TRIANGLE_CLIPPED( i1, i2, i3 ) ) { + cullBits = ( ( pointCull[ i1 ] ^ 0xfc0 ) | ( pointCull[ i2 ] ^ 0xfc0 ) | ( pointCull[ i3 ] ^ 0xfc0 ) ) >> 6; + // this will also define clip edges that will become + // silhouette planes + if ( R_ClipTriangleToLight( tri->verts[i1].xyz, tri->verts[i2].xyz, + tri->verts[i3].xyz, cullBits, frustum ) ) { + faceCastsShadow[i] = 1; + } + } else { + // instead of overflowing or drawing a streamer shadow, don't draw a shadow at all + if ( numShadowIndexes + 3 > MAX_SHADOW_INDEXES ) { + overflowed = true; + return; + } + if ( remap[i1] == -1 || remap[i2] == -1 || remap[i3] == -1 ) { + common->Error( "R_CreateShadowVolumeInFrustum: bad remap[]" ); + } + shadowIndexes[numShadowIndexes++] = remap[i3]; + shadowIndexes[numShadowIndexes++] = remap[i2]; + shadowIndexes[numShadowIndexes++] = remap[i1]; + faceCastsShadow[i] = 1; + } + } + + // add indexes for the back caps, which will just be reversals of the + // front caps using the back vertexes + numCapIndexes = numShadowIndexes - firstShadowIndex; + + // if no faces have been defined for the shadow volume, + // there won't be anything at all + if ( numCapIndexes == 0 ) { + return; + } + + // The retail runtime routes the expensive offline optimizer through + // rvToolsStub. With the tools build disabled that stub returns false, + // so all shadow volumes intentionally use this real-time path. + + // the dangling edge "face" is never considered to cast a shadow, + // so any face with dangling edges that casts a shadow will have + // it's dangling sil edge trigger a sil plane + faceCastsShadow[numTris] = 0; + + // instead of overflowing or drawing a streamer shadow, don't draw a shadow at all + // if we ran out of space + if ( numShadowIndexes + numCapIndexes > MAX_SHADOW_INDEXES ) { + overflowed = true; + return; + } + for ( i = 0 ; i < numCapIndexes ; i += 3 ) { + shadowIndexes[ numShadowIndexes + i + 0 ] = shadowIndexes[ firstShadowIndex + i + 2 ] + 1; + shadowIndexes[ numShadowIndexes + i + 1 ] = shadowIndexes[ firstShadowIndex + i + 1 ] + 1; + shadowIndexes[ numShadowIndexes + i + 2 ] = shadowIndexes[ firstShadowIndex + i + 0 ] + 1; + } + numShadowIndexes += numCapIndexes; + +c_caps += numCapIndexes * 2; + +int preSilIndexes = numShadowIndexes; + + // if any triangles were clipped, we will have a list of edges + // on the frustum which must now become sil edges + if ( makeClippedPlanes ) { + R_AddClipSilEdges(); + } + + // any edges that are a transition between a shadowing and + // non-shadowing triangle will cast a silhouette edge + R_AddSilEdges( tri, pointCull, frustum ); + +c_sils += numShadowIndexes - preSilIndexes; + + // project all of the vertexes to the shadow plane, generating + // an equal number of back vertexes + R_ProjectPointsToFarPlane( ent, light, farPlane, firstShadowVert, numShadowVerts ); + + // note the index distribution so we can sort all the caps after all the sils + indexRef[indexFrustumNumber].frontCapStart = firstShadowIndex; + indexRef[indexFrustumNumber].rearCapStart = firstShadowIndex+numCapIndexes; + indexRef[indexFrustumNumber].silStart = preSilIndexes; + indexRef[indexFrustumNumber].end = numShadowIndexes; + indexFrustumNumber++; +} + +/* +=================== +R_MakeShadowFrustums + +Called at definition derivation time +=================== +*/ +void R_MakeShadowFrustums( idRenderLightLocal *light ) { + int i, j; + + if ( light->parms.pointLight ) { +#if 0 + idVec3 adjustedRadius; + + // increase the light radius to cover any origin offsets. + // this will cause some shadows to extend out of the exact light + // volume, but is simpler than adjusting all the frustums + adjustedRadius[0] = light->parms.lightRadius[0] + idMath::Fabs( light->parms.lightCenter[0] ); + adjustedRadius[1] = light->parms.lightRadius[1] + idMath::Fabs( light->parms.lightCenter[1] ); + adjustedRadius[2] = light->parms.lightRadius[2] + idMath::Fabs( light->parms.lightCenter[2] ); + + light->numShadowFrustums = 0; + // a point light has to project against six planes + for ( i = 0 ; i < 6 ; i++ ) { + shadowFrustum_t *frust = &light->shadowFrustums[ light->numShadowFrustums ]; + + frust->numPlanes = 6; + frust->makeClippedPlanes = false; + for ( j = 0 ; j < 6 ; j++ ) { + idPlane &plane = frust->planes[j]; + plane[0] = pointLightFrustums[i][j][0] / adjustedRadius[0]; + plane[1] = pointLightFrustums[i][j][1] / adjustedRadius[1]; + plane[2] = pointLightFrustums[i][j][2] / adjustedRadius[2]; + plane.Normalize(); + plane[3] = -( plane.Normal() * light->globalLightOrigin ); + if ( j == 5 ) { + plane[3] += adjustedRadius[i>>1]; + } + } + + light->numShadowFrustums++; + } +#else + // exact projection,taking into account asymetric frustums when + // globalLightOrigin isn't centered + + static int faceCorners[6][4] = { + { 7, 5, 1, 3 }, // positive X side + { 4, 6, 2, 0 }, // negative X side + { 6, 7, 3, 2 }, // positive Y side + { 5, 4, 0, 1 }, // negative Y side + { 6, 4, 5, 7 }, // positive Z side + { 3, 1, 0, 2 } // negative Z side + }; + static int faceEdgeAdjacent[6][4] = { + { 4, 4, 2, 2 }, // positive X side + { 7, 7, 1, 1 }, // negative X side + { 5, 5, 0, 0 }, // positive Y side + { 6, 6, 3, 3 }, // negative Y side + { 0, 0, 3, 3 }, // positive Z side + { 5, 5, 6, 6 } // negative Z side + }; + + bool centerOutside = false; + + // if the light center of projection is outside the light bounds, + // we will need to build the planes a little differently + if ( fabs( light->parms.lightCenter[0] ) > light->parms.lightRadius[0] + || fabs( light->parms.lightCenter[1] ) > light->parms.lightRadius[1] + || fabs( light->parms.lightCenter[2] ) > light->parms.lightRadius[2] ) { + centerOutside = true; + } + + // make the corners + idVec3 corners[8]; + + for ( i = 0 ; i < 8 ; i++ ) { + idVec3 temp; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & ( 1 << j ) ) { + temp[j] = light->parms.lightRadius[j]; + } else { + temp[j] = -light->parms.lightRadius[j]; + } + } + + // transform to global space + corners[i] = light->parms.origin + light->parms.axis * temp; + } + + light->numShadowFrustums = 0; + for ( int side = 0 ; side < 6 ; side++ ) { + shadowFrustum_t *frust = &light->shadowFrustums[ light->numShadowFrustums ]; + idVec3 &p1 = corners[faceCorners[side][0]]; + idVec3 &p2 = corners[faceCorners[side][1]]; + idVec3 &p3 = corners[faceCorners[side][2]]; + idPlane backPlane; + + // plane will have positive side inward + backPlane.FromPoints( p1, p2, p3 ); + + // if center of projection is on the wrong side, skip + float d = backPlane.Distance( light->globalLightOrigin ); + if ( d < 0 ) { + continue; + } + + frust->numPlanes = 6; + frust->planes[5] = backPlane; + frust->planes[4] = backPlane; // we don't really need the extra plane + + // make planes with positive side facing inwards in light local coordinates + for ( int edge = 0 ; edge < 4 ; edge++ ) { + idVec3 &p1 = corners[faceCorners[side][edge]]; + idVec3 &p2 = corners[faceCorners[side][(edge+1)&3]]; + + // create a plane that goes through the center of projection + frust->planes[edge].FromPoints( p2, p1, light->globalLightOrigin ); + + // see if we should use an adjacent plane instead + if ( centerOutside ) { + idVec3 &p3 = corners[faceEdgeAdjacent[side][edge]]; + idPlane sidePlane; + + sidePlane.FromPoints( p2, p1, p3 ); + d = sidePlane.Distance( light->globalLightOrigin ); + if ( d < 0 ) { + // use this plane instead of the edged plane + frust->planes[edge] = sidePlane; + } + // we can't guarantee a neighbor, so add sill planes at edge + light->shadowFrustums[ light->numShadowFrustums ].makeClippedPlanes = true; + } + } + light->numShadowFrustums++; + } + +#endif + return; + } + + // projected light + + light->numShadowFrustums = 1; + shadowFrustum_t *frust = &light->shadowFrustums[ 0 ]; + + // flip and transform the frustum planes so the positive side faces + // inward in local coordinates + + // it is important to clip against even the near clip plane, because + // many projected lights that are faking area lights will have their + // origin behind solid surfaces. + for ( i = 0 ; i < 6 ; i++ ) { + idPlane &plane = frust->planes[i]; + + plane.SetNormal( -light->frustum[i].Normal() ); + plane.SetDist( -light->frustum[i].Dist() ); + } + + frust->numPlanes = 6; + + frust->makeClippedPlanes = true; + // projected lights don't have shared frustums, so any clipped edges + // right on the planes must have a sil plane created for them +} + +/* +================= +R_CreateShadowVolume + +The returned surface will have a valid bounds and radius for culling. + +Triangles are clipped to the light frustum before projecting. + +A single triangle can clip to as many as 7 vertexes, so +the worst case expansion is 2*(numindexes/3)*7 verts when counting both +the front and back caps, although it will usually only be a modest +increase in vertexes for closed modesl + +The worst case index count is much larger, when the 7 vertex clipped triangle +needs 15 indexes for the front, 15 for the back, and 42 (a quad on seven sides) +for the sides, for a total of 72 indexes from the original 3. Ouch. + +NULL may be returned if the surface doesn't create a shadow volume at all, +as with a single face that the light is behind. + +If an edge is within an epsilon of the border of the volume, it must be treated +as if it is clipped for triangles, generating a new sil edge, and act +as if it was culled for edges, because the sil edge will have been +generated by the triangle irregardless of if it actually was a sil edge. +================= +*/ +srfTriangles_t *R_CreateShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + int optimize, srfCullInfo_t &cullInfo ) { + int i, j; + idVec3 lightOrigin; + srfTriangles_t *newTri; + int capPlaneBits; + + if ( !r_shadows.GetBool() ) { + return NULL; + } + + if ( tri->numSilEdges == 0 || tri->numIndexes == 0 || tri->numVerts == 0 ) { + return NULL; + } + + if ( tri->numIndexes < 0 ) { + common->Error( "R_CreateShadowVolume: tri->numIndexes = %i", tri->numIndexes ); + } + + if ( tri->numVerts < 0 ) { + common->Error( "R_CreateShadowVolume: tri->numVerts = %i", tri->numVerts ); + } + + tr.pc.c_createShadowVolumes++; + + // use the fast infinite projection in dynamic situations, which + // trades somewhat more overdraw and no cap optimizations for + // a very simple generation process + if ( optimize == SG_DYNAMIC && r_useTurboShadow.GetBool() ) { + if ( tri->primBatchMesh ) { + return R_CreateVertexBufferTurboShadowVolume( ent, tri, light, cullInfo ); + } + if ( tr.backEndRendererHasVertexPrograms && r_useShadowVertexProgram.GetBool() ) { + return R_CreateVertexProgramTurboShadowVolume( ent, tri, light, cullInfo ); + } else { + return R_CreateTurboShadowVolume( ent, tri, light, cullInfo ); + } + } + if ( tri->primBatchMesh ) { + return NULL; + } + + R_CalcInteractionFacing( ent, tri, light, cullInfo ); + + int numFaces = tri->numIndexes / 3; + int allFront = 1; + for ( i = 0; i < numFaces && allFront; i++ ) { + allFront &= cullInfo.facing[i]; + } + if ( allFront ) { + // if no faces are the right direction, don't make a shadow at all + return NULL; + } + + // clear the shadow volume + numShadowIndexes = 0; + numShadowVerts = 0; + overflowed = false; + indexFrustumNumber = 0; + capPlaneBits = 0; + + // the facing information will be the same for all six projections + // from a point light, as well as for any directed lights + globalFacing = cullInfo.facing; + faceCastsShadow = (byte *)_alloca16( tri->numIndexes / 3 + 1 ); // + 1 for fake dangling edge face + remap = (int *)_alloca16( tri->numVerts * sizeof( remap[0] ) ); + + R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, lightOrigin ); + + // run through all the shadow frustums, which is one for a projected light, + // and usually six for a point light, but point lights with centers outside + // the box may have less + for ( int frustumNum = 0 ; frustumNum < light->numShadowFrustums ; frustumNum++ ) { + const shadowFrustum_t *frust = &light->shadowFrustums[frustumNum]; + ALIGN16( idPlane frustum[6] ); + + // transform the planes into entity space + // we could share and reverse some of the planes between frustums for a minor + // speed increase + + // the cull test is redundant for a single shadow frustum projected light, because + // the surface has already been checked against the main light frustums + + for ( j = 0 ; j < frust->numPlanes ; j++ ) { + R_GlobalPlaneToLocal( ent->modelMatrix, frust->planes[j], frustum[j] ); + + // try to cull the entire surface against this frustum + float d = tri->bounds.PlaneDistance( frustum[j] ); + if ( d < -LIGHT_CLIP_EPSILON ) { + break; + } + } + if ( j != frust->numPlanes ) { + continue; + } + // we need to check all the triangles + int oldFrustumNumber = indexFrustumNumber; + + R_CreateShadowVolumeInFrustum( ent, tri, light, lightOrigin, frustum, frustum[5], frust->makeClippedPlanes ); + + // if we couldn't make a complete shadow volume, it is better to + // not draw one at all, avoiding streamer problems + if ( overflowed ) { + return NULL; + } + + if ( indexFrustumNumber != oldFrustumNumber ) { + // note that we have caps projected against this frustum, + // which may allow us to skip drawing the caps if all projected + // planes face away from the viewer and the viewer is outside the light volume + capPlaneBits |= 1< MAX_SHADOW_VERTS || numShadowIndexes > MAX_SHADOW_INDEXES ) { + common->FatalError( "Shadow volume exceeded allocation" ); + } + + // allocate a new surface for the shadow volume + newTri = R_AllocStaticTriSurf(); + + // we might consider setting this, but it would only help for + // large lights that are partially off screen + newTri->bounds.Clear(); + + // copy off the verts and indexes + newTri->numVerts = numShadowVerts; + newTri->numIndexes = numShadowIndexes; + + // the shadow verts will go into a main memory buffer as well as a vertex + // cache buffer, so they can be copied back if they are purged + R_AllocStaticTriSurfShadowVerts( newTri, newTri->numVerts ); + SIMDProcessor->Memcpy( newTri->shadowVertexes, shadowVerts, newTri->numVerts * sizeof( newTri->shadowVertexes[0] ) ); + + R_AllocStaticTriSurfIndexes( newTri, newTri->numIndexes ); + + if ( 1 /* sortCapIndexes */ ) { + newTri->shadowCapPlaneBits = capPlaneBits; + + // copy the sil indexes first + newTri->numShadowIndexesNoCaps = 0; + for ( i = 0 ; i < indexFrustumNumber ; i++ ) { + int c = indexRef[i].end - indexRef[i].silStart; + SIMDProcessor->Memcpy( newTri->indexes+newTri->numShadowIndexesNoCaps, + shadowIndexes+indexRef[i].silStart, c * sizeof( newTri->indexes[0] ) ); + newTri->numShadowIndexesNoCaps += c; + } + // copy rear cap indexes next + newTri->numShadowIndexesNoFrontCaps = newTri->numShadowIndexesNoCaps; + for ( i = 0 ; i < indexFrustumNumber ; i++ ) { + int c = indexRef[i].silStart - indexRef[i].rearCapStart; + SIMDProcessor->Memcpy( newTri->indexes+newTri->numShadowIndexesNoFrontCaps, + shadowIndexes+indexRef[i].rearCapStart, c * sizeof( newTri->indexes[0] ) ); + newTri->numShadowIndexesNoFrontCaps += c; + } + // copy front cap indexes last + newTri->numIndexes = newTri->numShadowIndexesNoFrontCaps; + for ( i = 0 ; i < indexFrustumNumber ; i++ ) { + int c = indexRef[i].rearCapStart - indexRef[i].frontCapStart; + SIMDProcessor->Memcpy( newTri->indexes+newTri->numIndexes, + shadowIndexes+indexRef[i].frontCapStart, c * sizeof( newTri->indexes[0] ) ); + newTri->numIndexes += c; + } + + } else { + newTri->shadowCapPlaneBits = 63; // we don't have optimized index lists + SIMDProcessor->Memcpy( newTri->indexes, shadowIndexes, newTri->numIndexes * sizeof( newTri->indexes[0] ) ); + } + + return newTri; +} diff --git a/src/renderer/tr_subview.cpp b/src/renderer/tr_subview.cpp new file mode 100644 index 0000000..78f2860 --- /dev/null +++ b/src/renderer/tr_subview.cpp @@ -0,0 +1,571 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" + + +typedef struct { + idVec3 origin; + idMat3 axis; +} orientation_t; + + +/* +================= +R_MirrorPoint +================= +*/ +static void R_MirrorPoint( const idVec3 in, orientation_t *surface, orientation_t *camera, idVec3 &out ) { + int i; + idVec3 local; + idVec3 transformed; + float d; + + local = in - surface->origin; + + transformed = vec3_origin; + for ( i = 0 ; i < 3 ; i++ ) { + d = local * surface->axis[i]; + transformed += d * camera->axis[i]; + } + + out = transformed + camera->origin; +} + +/* +================= +R_MirrorVector +================= +*/ +static void R_MirrorVector( const idVec3 in, orientation_t *surface, orientation_t *camera, idVec3 &out ) { + int i; + float d; + + out = vec3_origin; + for ( i = 0 ; i < 3 ; i++ ) { + d = in * surface->axis[i]; + out += d * camera->axis[i]; + } +} + +/* +============= +R_PlaneForSurface + +Returns the plane for the first triangle in the surface +FIXME: check for degenerate triangle? +============= +*/ +static void R_PlaneForSurface( const srfTriangles_t *tri, idPlane &plane ) { + if ( tri->primBatchMesh ) { + tri->primBatchMesh->PlaneForSurface( plane, tri->silTraceVerts ); + return; + } + + idDrawVert *v1, *v2, *v3; + + v1 = tri->verts + tri->indexes[0]; + v2 = tri->verts + tri->indexes[1]; + v3 = tri->verts + tri->indexes[2]; + plane.FromPoints( v1->xyz, v2->xyz, v3->xyz ); +} + +/* +========================= +R_PreciseCullSurface + +Check the surface for visibility on a per-triangle basis +for cases when it is going to be VERY expensive to draw (subviews) + +If not culled, also returns the bounding box of the surface in +Normalized Device Coordinates, so it can be used to crop the scissor rect. + +OPTIMIZE: we could also take exact portal passing into consideration +========================= +*/ +bool R_PreciseCullSurface( const drawSurf_t *drawSurf, idBounds &ndcBounds ) { + const srfTriangles_t *tri; + int numTriangles; + idPlane clip, eye; + int i, j; + unsigned int pointOr; + unsigned int pointAnd; + idVec3 localView; + idFixedWinding w; + + tri = drawSurf->geo; + + pointOr = 0; + pointAnd = (unsigned int)~0; + + // get an exact bounds of the triangles for scissor cropping + ndcBounds.Clear(); + + for ( i = 0; i < tri->numVerts; i++ ) { + int j; + unsigned int pointFlags; + const idVec3 &point = tri->silTraceVerts + ? tri->silTraceVerts[i].xyzw.ToVec3() + : tri->verts[i].xyz; + + R_TransformModelToClip( point, drawSurf->space->modelViewMatrix, + tr.viewDef->projectionMatrix, eye, clip ); + + pointFlags = 0; + for ( j = 0; j < 3; j++ ) { + if ( clip[j] >= clip[3] ) { + pointFlags |= (1 << (j*2)); + } else if ( clip[j] <= -clip[3] ) { + pointFlags |= ( 1 << (j*2+1)); + } + } + + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) { + return true; + } + + // backface and frustum cull + numTriangles = tri->numIndexes / 3; + + R_GlobalPointToLocal( drawSurf->space->modelMatrix, tr.viewDef->renderView.vieworg, localView ); + + if ( tri->silTraceVerts ) { + if ( tri->primBatchMesh->PreciseCullSurface( ndcBounds, tri->silTraceVerts, + localView, drawSurf->space->modelMatrix ) ) { + return true; + } + return ndcBounds.IsCleared(); + } + + for ( i = 0; i < tri->numIndexes; i += 3 ) { + idVec3 dir, normal; + float dot; + idVec3 d1, d2; + + const idVec3 &v1 = tri->verts[tri->indexes[i]].xyz; + const idVec3 &v2 = tri->verts[tri->indexes[i+1]].xyz; + const idVec3 &v3 = tri->verts[tri->indexes[i+2]].xyz; + + // this is a hack, because R_GlobalPointToLocal doesn't work with the non-normalized + // axis that we get from the gui view transform. It doesn't hurt anything, because + // we know that all gui generated surfaces are front facing + if ( tr.guiRecursionLevel == 0 ) { + // we don't care that it isn't normalized, + // all we want is the sign + d1 = v2 - v1; + d2 = v3 - v1; + normal = d2.Cross( d1 ); + + dir = v1 - localView; + + dot = normal * dir; + if ( dot >= 0.0f ) { + return true; + } + } + + // now find the exact screen bounds of the clipped triangle + w.SetNumPoints( 3 ); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, v1, w[0].ToVec3() ); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, v2, w[1].ToVec3() ); + R_LocalPointToGlobal( drawSurf->space->modelMatrix, v3, w[2].ToVec3() ); + w[0].s = w[0].t = w[1].s = w[1].t = w[2].s = w[2].t = 0.0f; + + for ( j = 0; j < 4; j++ ) { + if ( !w.ClipInPlace( -tr.viewDef->frustum[j], 0.1f ) ) { + break; + } + } + for ( j = 0; j < w.GetNumPoints(); j++ ) { + idVec3 screen; + + R_GlobalToNormalizedDeviceCoordinates( tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, w[j].ToVec3(), screen ); + ndcBounds.AddPoint( screen ); + } + } + + // if we don't enclose any area, return + if ( ndcBounds.IsCleared() ) { + return true; + } + + return false; +} + +/* +======================== +R_MirrorViewBySurface +======================== +*/ +static viewDef_t *R_MirrorViewBySurface( drawSurf_t *drawSurf ) { + viewDef_t *parms; + orientation_t surface, camera; + idPlane originalPlane, plane; + + // copy the viewport size from the original + parms = (viewDef_t *)R_FrameAlloc( sizeof( *parms ) ); + *parms = *tr.viewDef; + parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons + + parms->isSubview = true; + parms->isMirror = true; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->geo, originalPlane ); + R_LocalPlaneToGlobal( drawSurf->space->modelMatrix, originalPlane, plane ); + + surface.origin = plane.Normal() * -plane[3]; + surface.axis[0] = plane.Normal(); + surface.axis[0].NormalVectors( surface.axis[1], surface.axis[2] ); + surface.axis[2] = -surface.axis[2]; + + camera.origin = surface.origin; + camera.axis[0] = -surface.axis[0]; + camera.axis[1] = surface.axis[1]; + camera.axis[2] = surface.axis[2]; + + // set the mirrored origin and axis + R_MirrorPoint( tr.viewDef->renderView.vieworg, &surface, &camera, parms->renderView.vieworg ); + + R_MirrorVector( tr.viewDef->renderView.viewaxis[0], &surface, &camera, parms->renderView.viewaxis[0] ); + R_MirrorVector( tr.viewDef->renderView.viewaxis[1], &surface, &camera, parms->renderView.viewaxis[1] ); + R_MirrorVector( tr.viewDef->renderView.viewaxis[2], &surface, &camera, parms->renderView.viewaxis[2] ); + + // make the view origin 16 units away from the center of the surface + idVec3 viewOrigin = ( drawSurf->geo->bounds[0] + drawSurf->geo->bounds[1] ) * 0.5; + viewOrigin += originalPlane.Normal(); + + R_LocalPointToGlobal( drawSurf->space->modelMatrix, viewOrigin, parms->initialViewAreaOrigin ); + + // set the mirror clip plane + parms->numClipPlanes = 1; + parms->clipPlanes[0] = -camera.axis[0]; + + parms->clipPlanes[0][3] = -( camera.origin * parms->clipPlanes[0].Normal() ); + + return parms; +} + +/* +=============== +R_RemoteRender +=============== +*/ +static void R_RemoteRender( drawSurf_t *surf, textureStage_t *stage ) { + viewDef_t *parms; + + // remote views can be reused in a single frame + if ( stage->dynamicFrameCount == tr.frameCount ) { + return; + } + + // if the entity doesn't have a remoteRenderView, do nothing + if ( !surf->space->entityDef->parms.remoteRenderView ) { + return; + } + + // copy the viewport size from the original + parms = (viewDef_t *)R_FrameAlloc( sizeof( *parms ) ); + *parms = *tr.viewDef; + + parms->isSubview = true; + parms->isMirror = false; + + parms->renderView = *surf->space->entityDef->parms.remoteRenderView; + parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons + parms->initialViewAreaOrigin = parms->renderView.vieworg; + + if ( idImageManager::image_downSize.GetInteger() ) { + const int limit = idImageManager::image_downSizeLimit.GetInteger(); + if ( stage->height <= stage->width ) { + tr.CropRenderSize( limit, limit, true ); + } else { + tr.CropRenderSize( stage->width / ( stage->height / limit ), limit, true ); + } + } else { + tr.CropRenderSize( stage->width, stage->height, true ); + } + + parms->renderView.x = 0; + parms->renderView.y = 0; + parms->renderView.width = SCREEN_WIDTH; + parms->renderView.height = SCREEN_HEIGHT; + + tr.RenderViewToViewport( &parms->renderView, &parms->viewport ); + + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + parms->superView = tr.viewDef; + parms->subviewSurface = surf; + + // generate render commands for it + R_RenderView(parms); + + // copy this rendering to the image + stage->dynamicFrameCount = tr.frameCount; + if (!stage->image) { + stage->image = globalImages->scratchImage; + } + + tr.CaptureRenderToImage( stage->image->imgName ); + tr.UnCrop(); +} + +/* +================= +R_MirrorRender +================= +*/ +void R_MirrorRender( drawSurf_t *surf, textureStage_t *stage, idScreenRect scissor ) { + viewDef_t *parms; + + // remote views can be reused in a single frame + if ( stage->dynamicFrameCount == tr.frameCount ) { + return; + } + + // issue a new view command + parms = R_MirrorViewBySurface( surf ); + if ( !parms ) { + return; + } + + tr.CropRenderSize( stage->width, stage->height, true ); + + parms->renderView.x = 0; + parms->renderView.y = 0; + parms->renderView.width = SCREEN_WIDTH; + parms->renderView.height = SCREEN_HEIGHT; + + tr.RenderViewToViewport( &parms->renderView, &parms->viewport ); + + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + parms->superView = tr.viewDef; + parms->subviewSurface = surf; + + // triangle culling order changes with mirroring + parms->isMirror = ( ( (int)parms->isMirror ^ (int)tr.viewDef->isMirror ) != 0 ); + + // generate render commands for it + R_RenderView( parms ); + + // copy this rendering to the image + stage->dynamicFrameCount = tr.frameCount; + stage->image = globalImages->scratchImage; + + tr.CaptureRenderToImage( stage->image->imgName ); + tr.UnCrop(); +} + +/* +================= +R_ReflectionRender +================= +*/ +void R_ReflectionRender( drawSurf_t *surf, textureStage_t *stage, idScreenRect scissor ) { + viewDef_t *parms; + + if ( stage->dynamicFrameCount == tr.frameCount ) { + return; + } + + parms = R_MirrorViewBySurface( surf ); + if ( !parms ) { + return; + } + + tr.CropRenderSize( stage->width, stage->height, true ); + + parms->renderView.x = 0; + parms->renderView.y = 0; + parms->renderView.width = SCREEN_WIDTH; + parms->renderView.height = SCREEN_HEIGHT; + + tr.RenderViewToViewport( &parms->renderView, &parms->viewport ); + + parms->scissor.x1 = 0; + parms->scissor.y1 = 0; + parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; + parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; + + parms->superView = tr.viewDef; + parms->subviewSurface = surf; + parms->isMirror = ( ( (int)parms->isMirror ^ (int)tr.viewDef->isMirror ) != 0 ); + + R_RenderView( parms ); + + stage->dynamicFrameCount = tr.frameCount; + tr.CaptureRenderToImage( globalImages->reflectionImage->imgName ); + tr.UnCrop(); +} + +/* +================== +R_GenerateSurfaceSubview +================== +*/ +bool R_GenerateSurfaceSubview( drawSurf_t *drawSurf ) { + idBounds ndcBounds; + viewDef_t *parms; + const idMaterial *shader; + + // for testing the performance hit + if ( r_skipSubviews.GetBool() ) { + return false; + } + + if ( R_PreciseCullSurface( drawSurf, ndcBounds ) ) { + return false; + } + + shader = drawSurf->material; + + // never recurse through a subview surface that we are + // already seeing through + for ( parms = tr.viewDef ; parms ; parms = parms->superView ) { + if ( parms->subviewSurface + && parms->subviewSurface->geo == drawSurf->geo + && parms->subviewSurface->space->entityDef == drawSurf->space->entityDef ) { + break; + } + } + if ( parms ) { + return false; + } + + // crop the scissor bounds based on the precise cull + idScreenRect scissor; + + idScreenRect *v = &tr.viewDef->viewport; + scissor.x1 = v->x1 + (int)( (v->x2 - v->x1 + 1 ) * 0.5f * ( ndcBounds[0][0] + 1.0f )); + scissor.y1 = v->y1 + (int)( (v->y2 - v->y1 + 1 ) * 0.5f * ( ndcBounds[0][1] + 1.0f )); + scissor.x2 = v->x1 + (int)( (v->x2 - v->x1 + 1 ) * 0.5f * ( ndcBounds[1][0] + 1.0f )); + scissor.y2 = v->y1 + (int)( (v->y2 - v->y1 + 1 ) * 0.5f * ( ndcBounds[1][1] + 1.0f )); + + // nudge a bit for safety + scissor.Expand(); + + scissor.Intersect( tr.viewDef->scissor ); + + if ( scissor.IsEmpty() ) { + // cropped out + return false; + } + + // see what kind of subview we are making + if ( shader->GetSort() != SS_SUBVIEW ) { + for ( int i = 0 ; i < shader->GetNumStages() ; i++ ) { + const shaderStage_t *stage = shader->GetStage( i ); + switch ( stage->texture.dynamic ) { + case DI_REMOTE_RENDER: + R_RemoteRender( drawSurf, const_cast(&stage->texture) ); + break; + case DI_MIRROR_RENDER: + R_MirrorRender( drawSurf, const_cast(&stage->texture), scissor ); + break; + case DI_REFLECTION_RENDER: + R_ReflectionRender( drawSurf, const_cast(&stage->texture), scissor ); + break; + } + } + return true; + } + + // issue a new view command + parms = R_MirrorViewBySurface( drawSurf ); + if ( !parms ) { + return false; + } + + parms->scissor = scissor; + parms->superView = tr.viewDef; + parms->subviewSurface = drawSurf; + + // triangle culling order changes with mirroring + parms->isMirror = ( ( (int)parms->isMirror ^ (int)tr.viewDef->isMirror ) != 0 ); + + // generate render commands for it + R_RenderView( parms ); + + return true; +} + +/* +================ +R_GenerateSubViews + +If we need to render another view to complete the current view, +generate it first. + +It is important to do this after all drawSurfs for the current +view have been generated, because it may create a subview which +would change tr.viewCount. +================ +*/ +bool R_GenerateSubViews( void ) { + drawSurf_t *drawSurf; + int i; + bool subviews; + const idMaterial *shader; + + // for testing the performance hit + if ( r_skipSubviews.GetBool() ) { + return false; + } + + subviews = false; + + // scan the surfaces until we either find a subview, or determine + // there are no more subview surfaces. + for ( i = 0 ; i < tr.viewDef->numDrawSurfs ; i++ ) { + drawSurf = tr.viewDef->drawSurfs[i]; + shader = drawSurf->material; + + if ( !shader || !shader->HasSubview() ) { + continue; + } + + if ( R_GenerateSurfaceSubview( drawSurf ) ) { + subviews = true; + } + } + + return subviews; +} diff --git a/src/renderer/tr_trace.cpp b/src/renderer/tr_trace.cpp new file mode 100644 index 0000000..f1655f8 --- /dev/null +++ b/src/renderer/tr_trace.cpp @@ -0,0 +1,461 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +//#define TEST_TRACE + +/* +================= +R_GetMaterialType + +Resolve a material-type image at the traced triangle's interpolated texture +coordinate. Materials without a type image use their default type. +================= +*/ +const rvDeclMatType *R_GetMaterialType( const idMaterial *shader, + const srfTriangles_t *tri, int triangleNum, idVec3 &point ) { + if ( shader->GetMaterialTypeArray() == NULL ) { + return shader->GetMaterialType(); + } + + const int firstIndex = triangleNum * 3; + idVec3 vertices[3]; + idVec2 textureCoordinates[3]; + for ( int corner = 0; corner < 3; ++corner ) { + const idDrawVert &vertex = tri->verts[tri->indexes[firstIndex + corner]]; + vertices[corner] = vertex.xyz; + textureCoordinates[corner] = vertex.st; + } + + const idVec3 normal = tri->facePlanes[triangleNum].Normal(); + const float area = idMath::BarycentricTriangleArea( normal, + vertices[0], vertices[1], vertices[2] ); + if ( area == 0.0f ) { + return shader->GetMaterialType(); + } + + idVec2 textureCoordinate; + idMath::BarycentricEvaluate( textureCoordinate, point, normal, area, + vertices, textureCoordinates ); + return shader->GetMaterialType( textureCoordinate ); +} + +/* +================= +R_LocalTrace + +If we resort the vertexes so all silverts come first, we can save some work here. +================= +*/ +localTrace_t R_LocalTrace( const idVec3 &start, const idVec3 &end, + const float radius, const srfTriangles_t *tri, const idMaterial *shader ) { + int i, j; + byte * cullBits; + idPlane planes[4]; + localTrace_t hit; + int c_testEdges, c_testPlanes, c_intersect; + idVec3 startDir; + byte totalOr; + float radiusSqr; + +#ifdef TEST_TRACE + idTimer trace_timer; + trace_timer.Start(); +#endif + + hit.fraction = 1.0f; + + // create two planes orthogonal to each other that intersect along the trace + startDir = end - start; + startDir.Normalize(); + startDir.NormalVectors( planes[0].Normal(), planes[1].Normal() ); + planes[0][3] = - start * planes[0].Normal(); + planes[1][3] = - start * planes[1].Normal(); + + // create front and end planes so the trace is on the positive sides of both + planes[2] = startDir; + planes[2][3] = - start * planes[2].Normal(); + planes[3] = -startDir; + planes[3][3] = - end * planes[3].Normal(); + + // catagorize each point against the four planes + cullBits = (byte *) _alloca16( tri->numVerts ); + SIMDProcessor->TracePointCull( cullBits, totalOr, radius, planes, tri->verts, tri->numVerts ); + + // if we don't have points on both sides of both the ray planes, no intersection + if ( ( totalOr ^ ( totalOr >> 4 ) ) & 3 ) { + //common->Printf( "nothing crossed the trace planes\n" ); + return hit; + } + + // if we don't have any points between front and end, no intersection + if ( ( totalOr ^ ( totalOr >> 1 ) ) & 4 ) { + //common->Printf( "trace didn't reach any triangles\n" ); + return hit; + } + + // scan for triangles that cross both planes + c_testPlanes = 0; + c_testEdges = 0; + c_intersect = 0; + + radiusSqr = Square( radius ); + startDir = end - start; + + if ( !tri->facePlanes || !tri->facePlanesCalculated ) { + R_DeriveFacePlanes( const_cast( tri ) ); + } + + for ( i = 0, j = 0; i < tri->numIndexes; i += 3, j++ ) { + float d1, d2, f, d; + float edgeLengthSqr; + idPlane * plane; + idVec3 point; + idVec3 dir[3]; + idVec3 cross; + idVec3 edge; + byte triOr; + + // get sidedness info for the triangle + triOr = cullBits[ tri->indexes[i+0] ]; + triOr |= cullBits[ tri->indexes[i+1] ]; + triOr |= cullBits[ tri->indexes[i+2] ]; + + // if we don't have points on both sides of both the ray planes, no intersection + if ( ( triOr ^ ( triOr >> 4 ) ) & 3 ) { + continue; + } + + // if we don't have any points between front and end, no intersection + if ( ( triOr ^ ( triOr >> 1 ) ) & 4 ) { + continue; + } + + c_testPlanes++; + + plane = &tri->facePlanes[j]; + d1 = plane->Distance( start ); + d2 = plane->Distance( end ); + + if ( d1 <= d2 ) { + continue; // comning at it from behind or parallel + } + + if ( d1 < 0.0f ) { + continue; // starts past it + } + + if ( d2 > 0.0f ) { + continue; // finishes in front of it + } + + f = d1 / ( d1 - d2 ); + + if ( f < 0.0f ) { + continue; // shouldn't happen + } + + if ( f >= hit.fraction ) { + continue; // have already hit something closer + } + + c_testEdges++; + + // find the exact point of impact with the plane + point = start + f * startDir; + + // see if the point is within the three edges + // if radius > 0 the triangle is expanded with a circle in the triangle plane + + dir[0] = tri->verts[ tri->indexes[i+0] ].xyz - point; + dir[1] = tri->verts[ tri->indexes[i+1] ].xyz - point; + + cross = dir[0].Cross( dir[1] ); + d = plane->Normal() * cross; + if ( d > 0.0f ) { + if ( radiusSqr <= 0.0f ) { + continue; + } + edge = tri->verts[ tri->indexes[i+0] ].xyz - tri->verts[ tri->indexes[i+1] ].xyz; + edgeLengthSqr = edge.LengthSqr(); + if ( cross.LengthSqr() > edgeLengthSqr * radiusSqr ) { + continue; + } + d = edge * dir[0]; + if ( d < 0.0f ) { + edge = tri->verts[ tri->indexes[i+0] ].xyz - tri->verts[ tri->indexes[i+2] ].xyz; + d = edge * dir[0]; + if ( d < 0.0f ) { + if ( dir[0].LengthSqr() > radiusSqr ) { + continue; + } + } + } else if ( d > edgeLengthSqr ) { + edge = tri->verts[ tri->indexes[i+1] ].xyz - tri->verts[ tri->indexes[i+2] ].xyz; + d = edge * dir[1]; + if ( d < 0.0f ) { + if ( dir[1].LengthSqr() > radiusSqr ) { + continue; + } + } + } + } + + dir[2] = tri->verts[ tri->indexes[i+2] ].xyz - point; + + cross = dir[1].Cross( dir[2] ); + d = plane->Normal() * cross; + if ( d > 0.0f ) { + if ( radiusSqr <= 0.0f ) { + continue; + } + edge = tri->verts[ tri->indexes[i+1] ].xyz - tri->verts[ tri->indexes[i+2] ].xyz; + edgeLengthSqr = edge.LengthSqr(); + if ( cross.LengthSqr() > edgeLengthSqr * radiusSqr ) { + continue; + } + d = edge * dir[1]; + if ( d < 0.0f ) { + edge = tri->verts[ tri->indexes[i+1] ].xyz - tri->verts[ tri->indexes[i+0] ].xyz; + d = edge * dir[1]; + if ( d < 0.0f ) { + if ( dir[1].LengthSqr() > radiusSqr ) { + continue; + } + } + } else if ( d > edgeLengthSqr ) { + edge = tri->verts[ tri->indexes[i+2] ].xyz - tri->verts[ tri->indexes[i+0] ].xyz; + d = edge * dir[2]; + if ( d < 0.0f ) { + if ( dir[2].LengthSqr() > radiusSqr ) { + continue; + } + } + } + } + + cross = dir[2].Cross( dir[0] ); + d = plane->Normal() * cross; + if ( d > 0.0f ) { + if ( radiusSqr <= 0.0f ) { + continue; + } + edge = tri->verts[ tri->indexes[i+2] ].xyz - tri->verts[ tri->indexes[i+0] ].xyz; + edgeLengthSqr = edge.LengthSqr(); + if ( cross.LengthSqr() > edgeLengthSqr * radiusSqr ) { + continue; + } + d = edge * dir[2]; + if ( d < 0.0f ) { + edge = tri->verts[ tri->indexes[i+2] ].xyz - tri->verts[ tri->indexes[i+1] ].xyz; + d = edge * dir[2]; + if ( d < 0.0f ) { + if ( dir[2].LengthSqr() > radiusSqr ) { + continue; + } + } + } else if ( d > edgeLengthSqr ) { + edge = tri->verts[ tri->indexes[i+0] ].xyz - tri->verts[ tri->indexes[i+1] ].xyz; + d = edge * dir[0]; + if ( d < 0.0f ) { + if ( dir[0].LengthSqr() > radiusSqr ) { + continue; + } + } + } + } + + // we hit it + c_intersect++; + + hit.fraction = f; + hit.normal = plane->Normal(); + hit.point = point; + hit.indexes[0] = tri->indexes[i]; + hit.indexes[1] = tri->indexes[i+1]; + hit.indexes[2] = tri->indexes[i+2]; + hit.materialType = R_GetMaterialType( shader, tri, j, point ); + } + + +#ifdef TEST_TRACE + trace_timer.Stop(); + common->Printf( "testVerts:%i c_testPlanes:%i c_testEdges:%i c_intersect:%i msec:%1.4f\n", + tri->numVerts, c_testPlanes, c_testEdges, c_intersect, trace_timer.Milliseconds() ); +#endif + + return hit; +} + +/* +================= +RB_DrawExpandedTriangles +================= +*/ +void RB_DrawExpandedTriangles( const srfTriangles_t *tri, const float radius, const idVec3 &vieworg ) { + int i, j, k; + idVec3 dir[6], normal, point; + + for ( i = 0; i < tri->numIndexes; i += 3 ) { + + idVec3 p[3] = { tri->verts[ tri->indexes[ i + 0 ] ].xyz, tri->verts[ tri->indexes[ i + 1 ] ].xyz, tri->verts[ tri->indexes[ i + 2 ] ].xyz }; + + dir[0] = p[0] - p[1]; + dir[1] = p[1] - p[2]; + dir[2] = p[2] - p[0]; + + normal = dir[0].Cross( dir[1] ); + + if ( normal * p[0] < normal * vieworg ) { + continue; + } + + dir[0] = normal.Cross( dir[0] ); + dir[1] = normal.Cross( dir[1] ); + dir[2] = normal.Cross( dir[2] ); + + dir[0].Normalize(); + dir[1].Normalize(); + dir[2].Normalize(); + + qglBegin( GL_LINE_LOOP ); + + for ( j = 0; j < 3; j++ ) { + k = ( j + 1 ) % 3; + + dir[4] = ( dir[j] + dir[k] ) * 0.5f; + dir[4].Normalize(); + + dir[3] = ( dir[j] + dir[4] ) * 0.5f; + dir[3].Normalize(); + + dir[5] = ( dir[4] + dir[k] ) * 0.5f; + dir[5].Normalize(); + + point = p[k] + dir[j] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[3] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[4] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[5] * radius; + qglVertex3f( point[0], point[1], point[2] ); + + point = p[k] + dir[k] * radius; + qglVertex3f( point[0], point[1], point[2] ); + } + + qglEnd(); + } +} + +/* +================ +RB_ShowTrace + +Debug visualization +================ +*/ +void RB_ShowTrace( drawSurf_t **drawSurfs, int numDrawSurfs ) { + int i; + const srfTriangles_t *tri; + const drawSurf_t *surf; + idVec3 start, end; + idVec3 localStart, localEnd; + localTrace_t hit; + float radius; + + if ( r_showTrace.GetInteger() == 0 ) { + return; + } + + if ( r_showTrace.GetInteger() == 2 ) { + radius = 5.0f; + } else { + radius = 0.0f; + } + + // determine the points of the trace + start = backEnd.viewDef->renderView.vieworg; + end = start + 4000 * backEnd.viewDef->renderView.viewaxis[0]; + + // check and draw the surfaces + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + GL_TexEnv( GL_MODULATE ); + + globalImages->whiteImage->Bind(); + + // find how many are ambient + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + surf = drawSurfs[i]; + tri = surf->geo; + + if ( tri == NULL || tri->verts == NULL ) { + continue; + } + + // transform the points into local space + R_GlobalPointToLocal( surf->space->modelMatrix, start, localStart ); + R_GlobalPointToLocal( surf->space->modelMatrix, end, localEnd ); + + // check the bounding box + if ( !tri->bounds.Expand( radius ).LineIntersection( localStart, localEnd ) ) { + continue; + } + + qglLoadMatrixf( surf->space->modelViewMatrix ); + + // highlight the surface + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglColor4f( 1, 0, 0, 0.25 ); + RB_DrawElementsImmediate( tri ); + + // draw the bounding box + GL_State( GLS_DEPTHFUNC_ALWAYS ); + + qglColor4f( 1, 1, 1, 1 ); + RB_DrawBounds( tri->bounds ); + + if ( radius != 0.0f ) { + // draw the expanded triangles + qglColor4f( 0.5f, 0.5f, 1.0f, 1.0f ); + RB_DrawExpandedTriangles( tri, radius, localStart ); + } + + // check the exact surfaces + hit = R_LocalTrace( localStart, localEnd, radius, tri, surf->material ); + if ( hit.fraction < 1.0 ) { + qglColor4f( 1, 1, 1, 1 ); + RB_DrawBounds( idBounds( hit.point ).Expand( 1 ) ); + } + } +} diff --git a/src/renderer/tr_trisurf.cpp b/src/renderer/tr_trisurf.cpp new file mode 100644 index 0000000..4b375a0 --- /dev/null +++ b/src/renderer/tr_trisurf.cpp @@ -0,0 +1,2319 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + +/* +============================================================================== + +TRIANGLE MESH PROCESSING + +The functions in this file have no vertex / index count limits. + +Truly identical vertexes that match in position, normal, and texcoord can +be merged away. + +Vertexes that match in position and texcoord, but have distinct normals will +remain distinct for all purposes. This is usually a poor choice for models, +as adding a bevel face will not add any more vertexes, and will tend to +look better. + +Match in position and normal, but differ in texcoords are referenced together +for calculating tangent vectors for bump mapping. +Artists should take care to have identical texels in all maps (bump/diffuse/specular) +in this case + +Vertexes that only match in position are merged for shadow edge finding. + +Degenerate triangles. + +Overlapped triangles, even if normals or texcoords differ, must be removed. +for the silhoette based stencil shadow algorithm to function properly. +Is this true??? +Is the overlapped triangle problem just an example of the trippled edge problem? + +Interpenetrating triangles are not currently clipped to surfaces. +Do they effect the shadows? + +if vertexes are intended to deform apart, make sure that no vertexes +are on top of each other in the base frame, or the sil edges may be +calculated incorrectly. + +We might be able to identify this from topology. + +Dangling edges are acceptable, but three way edges are not. + +Are any combinations of two way edges unacceptable, like one facing +the backside of the other? + + +Topology is determined by a collection of triangle indexes. + +The edge list can be built up from this, and stays valid even under +deformations. + +Somewhat non-intuitively, concave edges cannot be optimized away, or the +stencil shadow algorithm miscounts. + +Face normals are needed for generating shadow volumes and for calculating +the silhouette, but they will change with any deformation. + +Vertex normals and vertex tangents will change with each deformation, +but they may be able to be transformed instead of recalculated. + +bounding volume, both box and sphere will change with deformation. + +silhouette indexes +shade indexes +texture indexes + + shade indexes will only be > silhouette indexes if there is facet shading present + + lookups from texture to sil and texture to shade? + +The normal and tangent vector smoothing is simple averaging, no attempt is +made to better handle the cases where the distribution around the shared vertex +is highly uneven. + + + we may get degenerate triangles even with the uniquing and removal + if the vertexes have different texcoords. + +============================================================================== +*/ + +// this shouldn't change anything, but previously renderbumped models seem to need it +#define USE_INVA + +// instead of using the texture T vector, cross the normal and S vector for an orthogonal axis +#define DERIVE_UNSMOOTHED_BITANGENT + +const int MAX_SIL_EDGES = 0x10000; +const int SILEDGE_HASH_SIZE = 1024; + +static int numSilEdges; +static silEdge_t * silEdges; +static idHashIndex silEdgeHash( SILEDGE_HASH_SIZE, MAX_SIL_EDGES ); +static int numPlanes; + +static idBlockAlloc srfTrianglesAllocator; + +#ifdef USE_TRI_DATA_ALLOCATOR +static idDynamicBlockAlloc triVertexAllocator; +static idDynamicBlockAlloc triIndexAllocator; +static idDynamicBlockAlloc triShadowVertexAllocator; +static idDynamicBlockAlloc triPlaneAllocator; +static idDynamicBlockAlloc triSilIndexAllocator; +static idDynamicBlockAlloc triSilEdgeAllocator; +static idDynamicBlockAlloc triDominantTrisAllocator; +static idDynamicBlockAlloc triMirroredVertAllocator; +static idDynamicBlockAlloc triDupVertAllocator; +static idDynamicBlockAlloc silTraceVertexAllocator; +static idDynamicBlockAlloc skinToModelTransformAllocator; +#else +static idDynamicAlloc triVertexAllocator; +static idDynamicAlloc triIndexAllocator; +static idDynamicAlloc triShadowVertexAllocator; +static idDynamicAlloc triPlaneAllocator; +static idDynamicAlloc triSilIndexAllocator; +static idDynamicAlloc triSilEdgeAllocator; +static idDynamicAlloc triDominantTrisAllocator; +static idDynamicAlloc triMirroredVertAllocator; +static idDynamicAlloc triDupVertAllocator; +static idDynamicAlloc silTraceVertexAllocator; +static idDynamicAlloc skinToModelTransformAllocator; +#endif + + +/* +=============== +R_InitTriSurfData +=============== +*/ +void R_InitTriSurfData( void ) { + silEdges = (silEdge_t *)R_StaticAlloc( MAX_SIL_EDGES * sizeof( silEdges[0] ) ); + + // initialize allocators for triangle surfaces + triVertexAllocator.Init(); + triIndexAllocator.Init(); + triShadowVertexAllocator.Init(); + triPlaneAllocator.Init(); + triSilIndexAllocator.Init(); + triSilEdgeAllocator.Init(); + triDominantTrisAllocator.Init(); + triMirroredVertAllocator.Init(); + triDupVertAllocator.Init(); + silTraceVertexAllocator.Init(); + skinToModelTransformAllocator.Init(); + + // never swap out triangle surfaces + triVertexAllocator.SetLockMemory( true ); + triIndexAllocator.SetLockMemory( true ); + triShadowVertexAllocator.SetLockMemory( true ); + triPlaneAllocator.SetLockMemory( true ); + triSilIndexAllocator.SetLockMemory( true ); + triSilEdgeAllocator.SetLockMemory( true ); + triDominantTrisAllocator.SetLockMemory( true ); + triMirroredVertAllocator.SetLockMemory( true ); + triDupVertAllocator.SetLockMemory( true ); + silTraceVertexAllocator.SetLockMemory( true ); + skinToModelTransformAllocator.SetLockMemory( true ); +} + +/* +=============== +R_ShutdownTriSurfData +=============== +*/ +void R_ShutdownTriSurfData( void ) { + R_StaticFree( silEdges ); + silEdgeHash.Free(); + srfTrianglesAllocator.Shutdown(); + triVertexAllocator.Shutdown(); + triIndexAllocator.Shutdown(); + triShadowVertexAllocator.Shutdown(); + triPlaneAllocator.Shutdown(); + triSilIndexAllocator.Shutdown(); + triSilEdgeAllocator.Shutdown(); + triDominantTrisAllocator.Shutdown(); + triMirroredVertAllocator.Shutdown(); + triDupVertAllocator.Shutdown(); + silTraceVertexAllocator.Shutdown(); + skinToModelTransformAllocator.Shutdown(); +} + +/* +=============== +R_PurgeTriSurfData +=============== +*/ +void R_PurgeTriSurfData( frameData_t *frame ) { + // free deferred triangle surfaces + R_FreeDeferredTriSurfs( frame ); + + // free empty base blocks + triVertexAllocator.FreeEmptyBaseBlocks(); + triIndexAllocator.FreeEmptyBaseBlocks(); + triShadowVertexAllocator.FreeEmptyBaseBlocks(); + triPlaneAllocator.FreeEmptyBaseBlocks(); + triSilIndexAllocator.FreeEmptyBaseBlocks(); + triSilEdgeAllocator.FreeEmptyBaseBlocks(); + triDominantTrisAllocator.FreeEmptyBaseBlocks(); + triMirroredVertAllocator.FreeEmptyBaseBlocks(); + triDupVertAllocator.FreeEmptyBaseBlocks(); + silTraceVertexAllocator.FreeEmptyBaseBlocks(); + skinToModelTransformAllocator.FreeEmptyBaseBlocks(); +} + +/* +=============== +R_ShowTriMemory_f +=============== +*/ +void R_ShowTriSurfMemory_f( const idCmdArgs &args ) { + common->Printf( "%6d kB in %d triangle surfaces\n", + ( srfTrianglesAllocator.GetAllocCount() * sizeof( srfTriangles_t ) ) >> 10, + srfTrianglesAllocator.GetAllocCount() ); + + common->Printf( "%6d kB vertex memory (%d kB free in %d blocks, %d empty base blocks)\n", + triVertexAllocator.GetBaseBlockMemory() >> 10, triVertexAllocator.GetFreeBlockMemory() >> 10, + triVertexAllocator.GetNumFreeBlocks(), triVertexAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB index memory (%d kB free in %d blocks, %d empty base blocks)\n", + triIndexAllocator.GetBaseBlockMemory() >> 10, triIndexAllocator.GetFreeBlockMemory() >> 10, + triIndexAllocator.GetNumFreeBlocks(), triIndexAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB shadow vert memory (%d kB free in %d blocks, %d empty base blocks)\n", + triShadowVertexAllocator.GetBaseBlockMemory() >> 10, triShadowVertexAllocator.GetFreeBlockMemory() >> 10, + triShadowVertexAllocator.GetNumFreeBlocks(), triShadowVertexAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB tri plane memory (%d kB free in %d blocks, %d empty base blocks)\n", + triPlaneAllocator.GetBaseBlockMemory() >> 10, triPlaneAllocator.GetFreeBlockMemory() >> 10, + triPlaneAllocator.GetNumFreeBlocks(), triPlaneAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB sil index memory (%d kB free in %d blocks, %d empty base blocks)\n", + triSilIndexAllocator.GetBaseBlockMemory() >> 10, triSilIndexAllocator.GetFreeBlockMemory() >> 10, + triSilIndexAllocator.GetNumFreeBlocks(), triSilIndexAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB sil edge memory (%d kB free in %d blocks, %d empty base blocks)\n", + triSilEdgeAllocator.GetBaseBlockMemory() >> 10, triSilEdgeAllocator.GetFreeBlockMemory() >> 10, + triSilEdgeAllocator.GetNumFreeBlocks(), triSilEdgeAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB dominant tri memory (%d kB free in %d blocks, %d empty base blocks)\n", + triDominantTrisAllocator.GetBaseBlockMemory() >> 10, triDominantTrisAllocator.GetFreeBlockMemory() >> 10, + triDominantTrisAllocator.GetNumFreeBlocks(), triDominantTrisAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB mirror vert memory (%d kB free in %d blocks, %d empty base blocks)\n", + triMirroredVertAllocator.GetBaseBlockMemory() >> 10, triMirroredVertAllocator.GetFreeBlockMemory() >> 10, + triMirroredVertAllocator.GetNumFreeBlocks(), triMirroredVertAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB dup vert memory (%d kB free in %d blocks, %d empty base blocks)\n", + triDupVertAllocator.GetBaseBlockMemory() >> 10, triDupVertAllocator.GetFreeBlockMemory() >> 10, + triDupVertAllocator.GetNumFreeBlocks(), triDupVertAllocator.GetNumEmptyBaseBlocks() ); + + common->Printf( "%6d kB total triangle memory\n", + ( srfTrianglesAllocator.GetAllocCount() * sizeof( srfTriangles_t ) + + triVertexAllocator.GetBaseBlockMemory() + + triIndexAllocator.GetBaseBlockMemory() + + triShadowVertexAllocator.GetBaseBlockMemory() + + triPlaneAllocator.GetBaseBlockMemory() + + triSilIndexAllocator.GetBaseBlockMemory() + + triSilEdgeAllocator.GetBaseBlockMemory() + + triDominantTrisAllocator.GetBaseBlockMemory() + + triMirroredVertAllocator.GetBaseBlockMemory() + + triDupVertAllocator.GetBaseBlockMemory() ) >> 10 ); +} + +/* +================= +R_TriSurfMemory + +For memory profiling +================= +*/ +int R_TriSurfMemory( const srfTriangles_t *tri ) { + int total = 0; + + if ( !tri ) { + return total; + } + + // used as a flag in interations + if ( tri == LIGHT_TRIS_DEFERRED ) { + return total; + } + + if ( tri->shadowVertexes != NULL ) { + total += tri->numVerts * sizeof( tri->shadowVertexes[0] ); + } else if ( tri->verts != NULL ) { + if ( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts ) { + total += tri->numVerts * sizeof( tri->verts[0] ); + } + } + if ( tri->facePlanes != NULL ) { + total += tri->numIndexes / 3 * sizeof( tri->facePlanes[0] ); + } + if ( tri->indexes != NULL ) { + if ( tri->ambientSurface == NULL || tri->indexes != tri->ambientSurface->indexes ) { + total += tri->numIndexes * sizeof( tri->indexes[0] ); + } + } + if ( tri->silIndexes != NULL ) { + total += tri->numIndexes * sizeof( tri->silIndexes[0] ); + } + if ( tri->silEdges != NULL ) { + total += tri->numSilEdges * sizeof( tri->silEdges[0] ); + } + if ( tri->dominantTris != NULL ) { + total += tri->numVerts * sizeof( tri->dominantTris[0] ); + } + if ( tri->mirroredVerts != NULL ) { + total += tri->numMirroredVerts * sizeof( tri->mirroredVerts[0] ); + } + if ( tri->dupVerts != NULL ) { + total += tri->numDupVerts * sizeof( tri->dupVerts[0] ); + } + + total += sizeof( *tri ); + + return total; +} + +/* +============== +R_FreeStaticTriSurfVertexCaches +============== +*/ +void R_FreeStaticTriSurfVertexCaches( srfTriangles_t *tri ) { + if ( tri->ambientSurface == NULL ) { + // this is a real model surface + vertexCache.Free( tri->ambientCache ); + tri->ambientCache = NULL; + } else { + // this is a light interaction surface that references + // a different ambient model surface + vertexCache.Free( tri->lightingCache ); + tri->lightingCache = NULL; + } + if ( tri->indexCache ) { + vertexCache.Free( tri->indexCache ); + tri->indexCache = NULL; + } + if ( tri->shadowCache && ( tri->shadowVertexes != NULL || tri->verts != NULL ) ) { + // if we don't have tri->shadowVertexes, these are a reference to a + // shadowCache on the original surface, which a vertex program + // will take care of making unique for each light + vertexCache.Free( tri->shadowCache ); + tri->shadowCache = NULL; + } +} + +/* +============== +R_ReallyFreeStaticTriSurf + +This does the actual free +============== +*/ +void R_ReallyFreeStaticTriSurf( srfTriangles_t *tri ) { + if ( !tri ) { + return; + } + + R_FreeStaticTriSurfVertexCaches( tri ); + + if ( tri->verts != NULL ) { + // R_CreateLightTris points tri->verts at the verts of the ambient surface + if ( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts ) { + triVertexAllocator.Free( tri->verts ); + } + } + + if ( tri->silTraceVertsAlloc != NULL ) { + silTraceVertexAllocator.Free( tri->silTraceVertsAlloc ); + } + if ( tri->skinToModelTransformsAlloc != NULL ) { + skinToModelTransformAllocator.Free( tri->skinToModelTransformsAlloc ); + } + + if ( !tri->deformedSurface ) { + if ( tri->indexes != NULL ) { + // if a surface is completely inside a light volume R_CreateLightTris points tri->indexes at the indexes of the ambient surface + if ( tri->ambientSurface == NULL || tri->indexes != tri->ambientSurface->indexes ) { + triIndexAllocator.Free( tri->indexes ); + } + } + if ( tri->silIndexes != NULL ) { + triSilIndexAllocator.Free( tri->silIndexes ); + } + if ( tri->silEdges != NULL ) { + triSilEdgeAllocator.Free( tri->silEdges ); + } + if ( tri->dominantTris != NULL ) { + triDominantTrisAllocator.Free( tri->dominantTris ); + } + if ( tri->mirroredVerts != NULL ) { + triMirroredVertAllocator.Free( tri->mirroredVerts ); + } + if ( tri->dupVerts != NULL ) { + triDupVertAllocator.Free( tri->dupVerts ); + } + } + + if ( tri->facePlanes != NULL ) { + triPlaneAllocator.Free( tri->facePlanes ); + } + + if ( tri->shadowVertexes != NULL ) { + triShadowVertexAllocator.Free( tri->shadowVertexes ); + } + +#ifdef _DEBUG + memset( tri, 0, sizeof( srfTriangles_t ) ); +#endif + + srfTrianglesAllocator.Free( tri ); +} + +/* +============== +R_CheckStaticTriSurfMemory +============== +*/ +void R_CheckStaticTriSurfMemory( const srfTriangles_t *tri ) { + if ( !tri ) { + return; + } + + if ( tri->verts != NULL ) { + // R_CreateLightTris points tri->verts at the verts of the ambient surface + if ( tri->ambientSurface == NULL || tri->verts != tri->ambientSurface->verts ) { + const char *error = triVertexAllocator.CheckMemory( tri->verts ); + assert( error == NULL ); + } + } + + if ( !tri->deformedSurface ) { + if ( tri->indexes != NULL ) { + // if a surface is completely inside a light volume R_CreateLightTris points tri->indexes at the indexes of the ambient surface + if ( tri->ambientSurface == NULL || tri->indexes != tri->ambientSurface->indexes ) { + const char *error = triIndexAllocator.CheckMemory( tri->indexes ); + assert( error == NULL ); + } + } + } + + if ( tri->shadowVertexes != NULL ) { + const char *error = triShadowVertexAllocator.CheckMemory( tri->shadowVertexes ); + assert( error == NULL ); + } +} + +/* +================== +R_FreeDeferredTriSurfs +================== +*/ +void R_FreeDeferredTriSurfs( frameData_t *frame ) { + srfTriangles_t *tri, *next; + + if ( !frame ) { + return; + } + + for ( tri = frame->firstDeferredFreeTriSurf; tri; tri = next ) { + next = tri->nextDeferredFree; + R_ReallyFreeStaticTriSurf( tri ); + } + + frame->firstDeferredFreeTriSurf = NULL; + frame->lastDeferredFreeTriSurf = NULL; +} + +/* +============== +R_FreeStaticTriSurf + +This will defer the free until the current frame has run through the back end. +============== +*/ +void R_FreeStaticTriSurf( srfTriangles_t *tri ) { + frameData_t *frame; + + if ( !tri ) { + return; + } + + if ( tri->nextDeferredFree ) { + common->Error( "R_FreeStaticTriSurf: freed a freed triangle" ); + } + frame = frameData; + + if ( !frame ) { + // command line utility, or rendering in editor preview mode ( force ) + R_ReallyFreeStaticTriSurf( tri ); + } else { +#ifdef ID_DEBUG_MEMORY + R_CheckStaticTriSurfMemory( tri ); +#endif + tri->nextDeferredFree = NULL; + if ( frame->lastDeferredFreeTriSurf ) { + frame->lastDeferredFreeTriSurf->nextDeferredFree = tri; + } else { + frame->firstDeferredFreeTriSurf = tri; + } + frame->lastDeferredFreeTriSurf = tri; + } +} + +/* +============== +R_AllocStaticTriSurf +============== +*/ +srfTriangles_t *R_AllocStaticTriSurf( void ) { + srfTriangles_t *tris = srfTrianglesAllocator.Alloc(); + memset( tris, 0, sizeof( srfTriangles_t ) ); + return tris; +} + +/* +================= +R_CopyStaticTriSurf + +This only duplicates the indexes and verts, not any of the derived data. +================= +*/ +srfTriangles_t *R_CopyStaticTriSurf( const srfTriangles_t *tri ) { + srfTriangles_t *newTri; + + newTri = R_AllocStaticTriSurf(); + R_AllocStaticTriSurfVerts( newTri, tri->numVerts ); + R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); + newTri->numVerts = tri->numVerts; + newTri->numIndexes = tri->numIndexes; + memcpy( newTri->verts, tri->verts, tri->numVerts * sizeof( newTri->verts[0] ) ); + memcpy( newTri->indexes, tri->indexes, tri->numIndexes * sizeof( newTri->indexes[0] ) ); + + return newTri; +} + +/* +================= +R_AllocStaticTriSurfVerts +================= +*/ +void R_AllocStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ) { + assert( tri->verts == NULL ); + tri->verts = triVertexAllocator.Alloc( numVerts ); + tri->numAllocedVerts = numVerts; +} + +/* +================= +R_AllocStaticTriSurfSilTraceVerts +================= +*/ +void R_AllocStaticTriSurfSilTraceVerts( srfTriangles_t *tri, int numVerts ) { + assert( tri->silTraceVertsAlloc == NULL ); + tri->silTraceVertsAlloc = silTraceVertexAllocator.Alloc( numVerts ); + tri->silTraceVerts = tri->silTraceVertsAlloc; + tri->numAllocedVerts = numVerts; +} + +/* +================= +R_AllocStaticSkinToModelTransforms +================= +*/ +void R_AllocStaticSkinToModelTransforms( srfTriangles_t *tri, int numTransforms ) { + if ( tri->skinToModelTransformsAlloc != NULL ) { + if ( tri->numSkinToModelTransforms >= numTransforms ) { + return; + } + skinToModelTransformAllocator.Free( tri->skinToModelTransformsAlloc ); + } + tri->skinToModelTransformsAlloc = skinToModelTransformAllocator.Alloc( + numTransforms * 16 ); + tri->skinToModelTransforms = tri->skinToModelTransformsAlloc; + tri->numSkinToModelTransforms = numTransforms; +} + +/* +================= +R_AllocStaticTriSurfIndexes +================= +*/ +void R_AllocStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ) { + assert( tri->indexes == NULL ); + tri->indexes = triIndexAllocator.Alloc( numIndexes ); +} + +/* +================= +R_AllocStaticTriSurfShadowVerts +================= +*/ +void R_AllocStaticTriSurfShadowVerts( srfTriangles_t *tri, int numVerts ) { + assert( tri->shadowVertexes == NULL ); + tri->shadowVertexes = triShadowVertexAllocator.Alloc( numVerts ); +} + +/* +================= +R_AllocStaticTriSurfPlanes +================= +*/ +void R_AllocStaticTriSurfPlanes( srfTriangles_t *tri, int numIndexes ) { + if ( tri->facePlanes ) { + triPlaneAllocator.Free( tri->facePlanes ); + } + tri->facePlanes = triPlaneAllocator.Alloc( numIndexes / 3 ); +} + +/* +================= +R_ResizeStaticTriSurfVerts +================= +*/ +void R_ResizeStaticTriSurfVerts( srfTriangles_t *tri, int numVerts ) { +#ifdef USE_TRI_DATA_ALLOCATOR + tri->verts = triVertexAllocator.Resize( tri->verts, numVerts ); +#else + assert( false ); +#endif +} + +/* +================= +R_ResizeStaticTriSurfIndexes +================= +*/ +void R_ResizeStaticTriSurfIndexes( srfTriangles_t *tri, int numIndexes ) { +#ifdef USE_TRI_DATA_ALLOCATOR + tri->indexes = triIndexAllocator.Resize( tri->indexes, numIndexes ); +#else + assert( false ); +#endif +} + +/* +================= +R_ResizeStaticTriSurfShadowVerts +================= +*/ +void R_ResizeStaticTriSurfShadowVerts( srfTriangles_t *tri, int numVerts ) { +#ifdef USE_TRI_DATA_ALLOCATOR + tri->shadowVertexes = triShadowVertexAllocator.Resize( tri->shadowVertexes, numVerts ); +#else + assert( false ); +#endif +} + +/* +================= +R_ReferenceStaticTriSurfVerts +================= +*/ +void R_ReferenceStaticTriSurfVerts( srfTriangles_t *tri, const srfTriangles_t *reference ) { + tri->verts = reference->verts; +} + +/* +================= +R_ReferenceStaticTriSurfIndexes +================= +*/ +void R_ReferenceStaticTriSurfIndexes( srfTriangles_t *tri, const srfTriangles_t *reference ) { + tri->indexes = reference->indexes; +} + +/* +================= +R_FreeStaticTriSurfSilIndexes +================= +*/ +void R_FreeStaticTriSurfSilIndexes( srfTriangles_t *tri ) { + triSilIndexAllocator.Free( tri->silIndexes ); + tri->silIndexes = NULL; +} + +/* +=============== +R_RangeCheckIndexes + +Check for syntactically incorrect indexes, like out of range values. +Does not check for semantics, like degenerate triangles. + +No vertexes is acceptable if no indexes. +No indexes is acceptable. +More vertexes than are referenced by indexes are acceptable. +=============== +*/ +void R_RangeCheckIndexes( const srfTriangles_t *tri ) { + int i; + + if ( tri->numIndexes < 0 ) { + common->Error( "R_RangeCheckIndexes: numIndexes < 0" ); + } + if ( tri->numVerts < 0 ) { + common->Error( "R_RangeCheckIndexes: numVerts < 0" ); + } + + // must specify an integral number of triangles + if ( tri->numIndexes % 3 != 0 ) { + common->Error( "R_RangeCheckIndexes: numIndexes %% 3" ); + } + + for ( i = 0 ; i < tri->numIndexes ; i++ ) { + if ( tri->indexes[i] < 0 || tri->indexes[i] >= tri->numVerts ) { + common->Error( "R_RangeCheckIndexes: index out of range" ); + } + } + + // this should not be possible unless there are unused verts + if ( tri->numVerts > tri->numIndexes ) { + // FIXME: find the causes of these + // common->Printf( "R_RangeCheckIndexes: tri->numVerts > tri->numIndexes\n" ); + } +} + +/* +================= +R_BoundTriSurf +================= +*/ +void R_BoundTriSurf( srfTriangles_t *tri ) { + SIMDProcessor->MinMax( tri->bounds[0], tri->bounds[1], tri->verts, tri->numVerts ); +} + +/* +================= +R_CreateSilRemap +================= +*/ +static int *R_CreateSilRemap( const srfTriangles_t *tri ) { + int c_removed, c_unique; + int *remap; + int i, j, hashKey; + const idDrawVert *v1, *v2; + + remap = (int *)R_ClearedStaticAlloc( tri->numVerts * sizeof( remap[0] ) ); + + if ( !r_useSilRemap.GetBool() ) { + for ( i = 0 ; i < tri->numVerts ; i++ ) { + remap[i] = i; + } + return remap; + } + + idHashIndex hash( 1024, tri->numVerts ); + + c_removed = 0; + c_unique = 0; + for ( i = 0 ; i < tri->numVerts ; i++ ) { + v1 = &tri->verts[i]; + + // see if there is an earlier vert that it can map to + hashKey = hash.GenerateKey( v1->xyz ); + for ( j = hash.First( hashKey ); j >= 0; j = hash.Next( j ) ) { + v2 = &tri->verts[j]; + if ( v2->xyz[0] == v1->xyz[0] + && v2->xyz[1] == v1->xyz[1] + && v2->xyz[2] == v1->xyz[2] ) { + c_removed++; + remap[i] = j; + break; + } + } + if ( j < 0 ) { + c_unique++; + remap[i] = i; + hash.Add( hashKey, i ); + } + } + + return remap; +} + +/* +================= +R_CreateSilIndexes + +Uniquing vertexes only on xyz before creating sil edges reduces +the edge count by about 20% on Q3 models +================= +*/ +void R_CreateSilIndexes( srfTriangles_t *tri ) { + int i; + int *remap; + + if ( tri->silIndexes ) { + triSilIndexAllocator.Free( tri->silIndexes ); + tri->silIndexes = NULL; + } + + remap = R_CreateSilRemap( tri ); + + // remap indexes to the first one + tri->silIndexes = triSilIndexAllocator.Alloc( tri->numIndexes ); + for ( i = 0; i < tri->numIndexes; i++ ) { + tri->silIndexes[i] = remap[tri->indexes[i]]; + } + + R_StaticFree( remap ); +} + +/* +===================== +R_CreateDupVerts +===================== +*/ +void R_CreateDupVerts( srfTriangles_t *tri ) { + int i; + + int *remap = (int *) _alloca16( tri->numVerts * sizeof( remap[0] ) ); + + // initialize vertex remap in case there are unused verts + for ( i = 0; i < tri->numVerts; i++ ) { + remap[i] = i; + } + + // set the remap based on how the silhouette indexes are remapped + for ( i = 0; i < tri->numIndexes; i++ ) { + remap[tri->indexes[i]] = tri->silIndexes[i]; + } + + // create duplicate vertex index based on the vertex remap + int * tempDupVerts = (int *) _alloca16( tri->numVerts * 2 * sizeof( tempDupVerts[0] ) ); + tri->numDupVerts = 0; + for ( i = 0; i < tri->numVerts; i++ ) { + if ( remap[i] != i ) { + tempDupVerts[tri->numDupVerts*2+0] = i; + tempDupVerts[tri->numDupVerts*2+1] = remap[i]; + tri->numDupVerts++; + } + } + + tri->dupVerts = triDupVertAllocator.Alloc( tri->numDupVerts * 2 ); + memcpy( tri->dupVerts, tempDupVerts, tri->numDupVerts * 2 * sizeof( tri->dupVerts[0] ) ); +} + +/* +===================== +R_DeriveFacePlanes + +Writes the facePlanes values, overwriting existing ones if present +===================== +*/ +void R_DeriveFacePlanes( srfTriangles_t *tri ) { + idPlane * planes; + + if ( !tri->facePlanes ) { + R_AllocStaticTriSurfPlanes( tri, tri->numIndexes ); + } + planes = tri->facePlanes; + +#if 1 + + SIMDProcessor->DeriveTriPlanes( planes, tri->verts, tri->numVerts, tri->indexes, tri->numIndexes ); + +#else + + for ( int i = 0; i < tri->numIndexes; i+= 3, planes++ ) { + int i1, i2, i3; + idVec3 d1, d2, normal; + idVec3 *v1, *v2, *v3; + + i1 = tri->indexes[i + 0]; + i2 = tri->indexes[i + 1]; + i3 = tri->indexes[i + 2]; + + v1 = &tri->verts[i1].xyz; + v2 = &tri->verts[i2].xyz; + v3 = &tri->verts[i3].xyz; + + d1[0] = v2->x - v1->x; + d1[1] = v2->y - v1->y; + d1[2] = v2->z - v1->z; + + d2[0] = v3->x - v1->x; + d2[1] = v3->y - v1->y; + d2[2] = v3->z - v1->z; + + normal[0] = d2.y * d1.z - d2.z * d1.y; + normal[1] = d2.z * d1.x - d2.x * d1.z; + normal[2] = d2.x * d1.y - d2.y * d1.x; + + float sqrLength, invLength; + + sqrLength = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z; + invLength = idMath::RSqrt( sqrLength ); + + (*planes)[0] = normal[0] * invLength; + (*planes)[1] = normal[1] * invLength; + (*planes)[2] = normal[2] * invLength; + + planes->FitThroughPoint( *v1 ); + } + +#endif + + tri->facePlanesCalculated = true; +} + +/* +===================== +R_CreateVertexNormals + +Averages together the contributions of all faces that are +used by a vertex, creating drawVert->normal +===================== +*/ +void R_CreateVertexNormals( srfTriangles_t *tri ) { + int i, j; + const idPlane *planes; + + for ( i = 0 ; i < tri->numVerts ; i++ ) { + tri->verts[i].normal.Zero(); + } + + if ( !tri->facePlanes || !tri->facePlanesCalculated ) { + R_DeriveFacePlanes( tri ); + } + if ( !tri->silIndexes ) { + R_CreateSilIndexes( tri ); + } + planes = tri->facePlanes; + for ( i = 0 ; i < tri->numIndexes ; i += 3, planes++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + int index = tri->silIndexes[i+j]; + tri->verts[index].normal += planes->Normal(); + } + } + + // normalize and replicate from silIndexes to all indexes + for ( i = 0 ; i < tri->numIndexes ; i++ ) { + tri->verts[tri->indexes[i]].normal = tri->verts[tri->silIndexes[i]].normal; + tri->verts[tri->indexes[i]].normal.Normalize(); + } +} + +/* +=============== +R_DefineEdge +=============== +*/ +static int c_duplicatedEdges, c_tripledEdges; + +static void R_DefineEdge( int v1, int v2, int planeNum ) { + int i, hashKey; + + // check for degenerate edge + if ( v1 == v2 ) { + return; + } + hashKey = silEdgeHash.GenerateKey( v1, v2 ); + // search for a matching other side + for ( i = silEdgeHash.First( hashKey ); i >= 0 && i < MAX_SIL_EDGES; i = silEdgeHash.Next( i ) ) { + if ( silEdges[i].v1 == v1 && silEdges[i].v2 == v2 ) { + c_duplicatedEdges++; + // allow it to still create a new edge + continue; + } + if ( silEdges[i].v2 == v1 && silEdges[i].v1 == v2 ) { + if ( silEdges[i].p2 != numPlanes ) { + c_tripledEdges++; + // allow it to still create a new edge + continue; + } + // this is a matching back side + silEdges[i].p2 = planeNum; + return; + } + + } + + // define the new edge + if ( numSilEdges == MAX_SIL_EDGES ) { + common->DWarning( "MAX_SIL_EDGES" ); + return; + } + + silEdgeHash.Add( hashKey, numSilEdges ); + + silEdges[numSilEdges].p1 = planeNum; + silEdges[numSilEdges].p2 = numPlanes; + silEdges[numSilEdges].v1 = v1; + silEdges[numSilEdges].v2 = v2; + + numSilEdges++; +} + +/* +================= +SilEdgeSort +================= +*/ +static int SilEdgeSort( const void *a, const void *b ) { + if ( ((silEdge_t *)a)->p1 < ((silEdge_t *)b)->p1 ) { + return -1; + } + if ( ((silEdge_t *)a)->p1 > ((silEdge_t *)b)->p1 ) { + return 1; + } + if ( ((silEdge_t *)a)->p2 < ((silEdge_t *)b)->p2 ) { + return -1; + } + if ( ((silEdge_t *)a)->p2 > ((silEdge_t *)b)->p2 ) { + return 1; + } + return 0; +} + +/* +================= +R_IdentifySilEdges + +If the surface will not deform, coplanar edges (polygon interiors) +can never create silhouette plains, and can be omited +================= +*/ +int c_coplanarSilEdges; +int c_totalSilEdges; + +void R_IdentifySilEdges( srfTriangles_t *tri, bool omitCoplanarEdges ) { + int i; + int numTris; + int shared, single; + + omitCoplanarEdges = false; // optimization doesn't work for some reason + + numTris = tri->numIndexes / 3; + + numSilEdges = 0; + silEdgeHash.Clear(); + numPlanes = numTris; + + c_duplicatedEdges = 0; + c_tripledEdges = 0; + + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + + i1 = tri->silIndexes[ i*3 + 0 ]; + i2 = tri->silIndexes[ i*3 + 1 ]; + i3 = tri->silIndexes[ i*3 + 2 ]; + + // create the edges + R_DefineEdge( i1, i2, i ); + R_DefineEdge( i2, i3, i ); + R_DefineEdge( i3, i1, i ); + } + + if ( c_duplicatedEdges || c_tripledEdges ) { + common->DWarning( "%i duplicated edge directions, %i tripled edges", c_duplicatedEdges, c_tripledEdges ); + } + + // if we know that the vertexes aren't going + // to deform, we can remove interior triangulation edges + // on otherwise planar polygons. + // I earlier believed that I could also remove concave + // edges, because they are never silhouettes in the conventional sense, + // but they are still needed to balance out all the true sil edges + // for the shadow algorithm to function + int c_coplanarCulled; + + c_coplanarCulled = 0; + if ( omitCoplanarEdges ) { + for ( i = 0 ; i < numSilEdges ; i++ ) { + int i1, i2, i3; + idPlane plane; + int base; + int j; + float d; + + if ( silEdges[i].p2 == numPlanes ) { // the fake dangling edge + continue; + } + + base = silEdges[i].p1 * 3; + i1 = tri->silIndexes[ base + 0 ]; + i2 = tri->silIndexes[ base + 1 ]; + i3 = tri->silIndexes[ base + 2 ]; + + plane.FromPoints( tri->verts[i1].xyz, tri->verts[i2].xyz, tri->verts[i3].xyz ); + + // check to see if points of second triangle are not coplanar + base = silEdges[i].p2 * 3; + for ( j = 0 ; j < 3 ; j++ ) { + i1 = tri->silIndexes[ base + j ]; + d = plane.Distance( tri->verts[i1].xyz ); + if ( d != 0 ) { // even a small epsilon causes problems + break; + } + } + + if ( j == 3 ) { + // we can cull this sil edge + memmove( &silEdges[i], &silEdges[i+1], (numSilEdges-i-1) * sizeof( silEdges[i] ) ); + c_coplanarCulled++; + numSilEdges--; + i--; + } + } + if ( c_coplanarCulled ) { + c_coplanarSilEdges += c_coplanarCulled; +// common->Printf( "%i of %i sil edges coplanar culled\n", c_coplanarCulled, +// c_coplanarCulled + numSilEdges ); + } + } + c_totalSilEdges += numSilEdges; + + // sort the sil edges based on plane number + qsort( silEdges, numSilEdges, sizeof( silEdges[0] ), SilEdgeSort ); + + // count up the distribution. + // a perfectly built model should only have shared + // edges, but most models will have some interpenetration + // and dangling edges + shared = 0; + single = 0; + for ( i = 0 ; i < numSilEdges ; i++ ) { + if ( silEdges[i].p2 == numPlanes ) { + single++; + } else { + shared++; + } + } + + if ( !single ) { + tri->perfectHull = true; + } else { + tri->perfectHull = false; + } + + tri->numSilEdges = numSilEdges; + tri->silEdges = triSilEdgeAllocator.Alloc( numSilEdges ); + memcpy( tri->silEdges, silEdges, numSilEdges * sizeof( tri->silEdges[0] ) ); +} + +/* +=============== +R_FaceNegativePolarity + +Returns true if the texture polarity of the face is negative, false if it is positive or zero +=============== +*/ +static bool R_FaceNegativePolarity( const srfTriangles_t *tri, int firstIndex ) { + idDrawVert *a, *b, *c; + float area; + float d0[5], d1[5]; + + a = tri->verts + tri->indexes[firstIndex + 0]; + b = tri->verts + tri->indexes[firstIndex + 1]; + c = tri->verts + tri->indexes[firstIndex + 2]; + + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( area >= 0 ) { + return false; + } + return true; +} + +/* +================== +R_DeriveFaceTangents +================== +*/ +typedef struct { + idVec3 tangents[2]; + bool negativePolarity; + bool degenerate; +} faceTangents_t; + +static void R_DeriveFaceTangents( const srfTriangles_t *tri, faceTangents_t *faceTangents ) { + int i; + int c_textureDegenerateFaces; + int c_positive, c_negative; + faceTangents_t *ft; + idDrawVert *a, *b, *c; + + // + // calculate tangent vectors for each face in isolation + // + c_positive = 0; + c_negative = 0; + c_textureDegenerateFaces = 0; + for ( i = 0 ; i < tri->numIndexes ; i+=3 ) { + float area; + idVec3 temp; + float d0[5], d1[5]; + + ft = &faceTangents[i/3]; + + a = tri->verts + tri->indexes[i + 0]; + b = tri->verts + tri->indexes[i + 1]; + c = tri->verts + tri->indexes[i + 2]; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + area = d0[3] * d1[4] - d0[4] * d1[3]; + if ( fabs( area ) < 1e-20f ) { + ft->negativePolarity = false; + ft->degenerate = true; + ft->tangents[0].Zero(); + ft->tangents[1].Zero(); + c_textureDegenerateFaces++; + continue; + } + if ( area > 0.0f ) { + ft->negativePolarity = false; + c_positive++; + } else { + ft->negativePolarity = true; + c_negative++; + } + ft->degenerate = false; + +#ifdef USE_INVA + float inva = area < 0.0f ? -1 : 1; // was = 1.0f / area; + + temp[0] = (d0[0] * d1[4] - d0[4] * d1[0]) * inva; + temp[1] = (d0[1] * d1[4] - d0[4] * d1[1]) * inva; + temp[2] = (d0[2] * d1[4] - d0[4] * d1[2]) * inva; + temp.Normalize(); + ft->tangents[0] = temp; + + temp[0] = (d0[3] * d1[0] - d0[0] * d1[3]) * inva; + temp[1] = (d0[3] * d1[1] - d0[1] * d1[3]) * inva; + temp[2] = (d0[3] * d1[2] - d0[2] * d1[3]) * inva; + temp.Normalize(); + ft->tangents[1] = temp; +#else + temp[0] = (d0[0] * d1[4] - d0[4] * d1[0]); + temp[1] = (d0[1] * d1[4] - d0[4] * d1[1]); + temp[2] = (d0[2] * d1[4] - d0[4] * d1[2]); + temp.Normalize(); + ft->tangents[0] = temp; + + temp[0] = (d0[3] * d1[0] - d0[0] * d1[3]); + temp[1] = (d0[3] * d1[1] - d0[1] * d1[3]); + temp[2] = (d0[3] * d1[2] - d0[2] * d1[3]); + temp.Normalize(); + ft->tangents[1] = temp; +#endif + } +} + + + +/* +=================== +R_DuplicateMirroredVertexes + +Modifies the surface to bust apart any verts that are shared by both positive and +negative texture polarities, so tangent space smoothing at the vertex doesn't +degenerate. + +This will create some identical vertexes (which will eventually get different tangent +vectors), so never optimize the resulting mesh, or it will get the mirrored edges back. + +Reallocates tri->verts and changes tri->indexes in place +Silindexes are unchanged by this. + +sets mirroredVerts and mirroredVerts[] + +=================== +*/ +typedef struct { + bool polarityUsed[2]; + int negativeRemap; +} tangentVert_t; + +static void R_DuplicateMirroredVertexes( srfTriangles_t *tri ) { + tangentVert_t *tverts, *vert; + int i, j; + int totalVerts; + int numMirror; + + tverts = (tangentVert_t *)_alloca16( tri->numVerts * sizeof( *tverts ) ); + memset( tverts, 0, tri->numVerts * sizeof( *tverts ) ); + + // determine texture polarity of each surface + + // mark each vert with the polarities it uses + for ( i = 0 ; i < tri->numIndexes ; i+=3 ) { + int polarity; + + polarity = R_FaceNegativePolarity( tri, i ); + for ( j = 0 ; j < 3 ; j++ ) { + tverts[tri->indexes[i+j]].polarityUsed[ polarity ] = true; + } + } + + // now create new verts as needed + totalVerts = tri->numVerts; + for ( i = 0 ; i < tri->numVerts ; i++ ) { + vert = &tverts[i]; + if ( vert->polarityUsed[0] && vert->polarityUsed[1] ) { + vert->negativeRemap = totalVerts; + totalVerts++; + } + } + + tri->numMirroredVerts = totalVerts - tri->numVerts; + + // now create the new list + if ( totalVerts == tri->numVerts ) { + tri->mirroredVerts = NULL; + return; + } + + tri->mirroredVerts = triMirroredVertAllocator.Alloc( tri->numMirroredVerts ); + +#ifdef USE_TRI_DATA_ALLOCATOR + tri->verts = triVertexAllocator.Resize( tri->verts, totalVerts ); +#else + idDrawVert *oldVerts = tri->verts; + R_AllocStaticTriSurfVerts( tri, totalVerts ); + memcpy( tri->verts, oldVerts, tri->numVerts * sizeof( tri->verts[0] ) ); + triVertexAllocator.Free( oldVerts ); +#endif + + // create the duplicates + numMirror = 0; + for ( i = 0 ; i < tri->numVerts ; i++ ) { + j = tverts[i].negativeRemap; + if ( j ) { + tri->verts[j] = tri->verts[i]; + tri->mirroredVerts[numMirror] = i; + numMirror++; + } + } + + tri->numVerts = totalVerts; + // change the indexes + for ( i = 0 ; i < tri->numIndexes ; i++ ) { + if ( tverts[tri->indexes[i]].negativeRemap && + R_FaceNegativePolarity( tri, 3*(i/3) ) ) { + tri->indexes[i] = tverts[tri->indexes[i]].negativeRemap; + } + } + + tri->numVerts = totalVerts; +} + +/* +================= +R_DeriveTangentsWithoutNormals + +Build texture space tangents for bump mapping +If a surface is deformed, this must be recalculated + +This assumes that any mirrored vertexes have already been duplicated, so +any shared vertexes will have the tangent spaces smoothed across. + +Texture wrapping slightly complicates this, but as long as the normals +are shared, and the tangent vectors are projected onto the normals, the +separate vertexes should wind up with identical tangent spaces. + +mirroring a normalmap WILL cause a slightly visible seam unless the normals +are completely flat around the edge's full bilerp support. + +Vertexes which are smooth shaded must have their tangent vectors +in the same plane, which will allow a seamless +rendering as long as the normal map is even on both sides of the +seam. + +A smooth shaded surface may have multiple tangent vectors at a vertex +due to texture seams or mirroring, but it should only have a single +normal vector. + +Each triangle has a pair of tangent vectors in it's plane + +Should we consider having vertexes point at shared tangent spaces +to save space or speed transforms? + +this version only handles bilateral symetry +================= +*/ +void R_DeriveTangentsWithoutNormals( srfTriangles_t *tri ) { + int i, j; + faceTangents_t *faceTangents; + faceTangents_t *ft; + idDrawVert *vert; + + faceTangents = (faceTangents_t *)_alloca16( sizeof(faceTangents[0]) * tri->numIndexes/3 ); + R_DeriveFaceTangents( tri, faceTangents ); + + // clear the tangents + for ( i = 0 ; i < tri->numVerts ; i++ ) { + tri->verts[i].tangents[0].Zero(); + tri->verts[i].tangents[1].Zero(); + } + + // sum up the neighbors + for ( i = 0 ; i < tri->numIndexes ; i+=3 ) { + ft = &faceTangents[i/3]; + + // for each vertex on this face + for ( j = 0 ; j < 3 ; j++ ) { + vert = &tri->verts[tri->indexes[i+j]]; + + vert->tangents[0] += ft->tangents[0]; + vert->tangents[1] += ft->tangents[1]; + } + } + +#if 0 + // sum up both sides of the mirrored verts + // so the S vectors exactly mirror, and the T vectors are equal + for ( i = 0 ; i < tri->numMirroredVerts ; i++ ) { + idDrawVert *v1, *v2; + + v1 = &tri->verts[ tri->numVerts - tri->numMirroredVerts + i ]; + v2 = &tri->verts[ tri->mirroredVerts[i] ]; + + v1->tangents[0] -= v2->tangents[0]; + v1->tangents[1] += v2->tangents[1]; + + v2->tangents[0] = vec3_origin - v1->tangents[0]; + v2->tangents[1] = v1->tangents[1]; + } +#endif + + + // project the summed vectors onto the normal plane + // and normalize. The tangent vectors will not necessarily + // be orthogonal to each other, but they will be orthogonal + // to the surface normal. + for ( i = 0 ; i < tri->numVerts ; i++ ) { + vert = &tri->verts[i]; + for ( j = 0 ; j < 2 ; j++ ) { + float d; + + d = vert->tangents[j] * vert->normal; + vert->tangents[j] = vert->tangents[j] - d * vert->normal; + vert->tangents[j].Normalize(); + } + } + + tri->tangentsCalculated = true; +} + +static ID_INLINE void VectorNormalizeFast2( const idVec3 &v, idVec3 &out) { + float ilength; + + ilength = idMath::RSqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] ); + out[0] = v[0] * ilength; + out[1] = v[1] * ilength; + out[2] = v[2] * ilength; +} + +/* +=================== +R_BuildDominantTris + +Find the largest triangle that uses each vertex +=================== +*/ +typedef struct { + int vertexNum; + int faceNum; +} indexSort_t; + +static int IndexSort( const void *a, const void *b ) { + if ( ((indexSort_t *)a)->vertexNum < ((indexSort_t *)b)->vertexNum ) { + return -1; + } + if ( ((indexSort_t *)a)->vertexNum > ((indexSort_t *)b)->vertexNum ) { + return 1; + } + return 0; +} + +void R_BuildDominantTris( srfTriangles_t *tri ) { + int i, j; + dominantTri_t *dt; + indexSort_t *ind = (indexSort_t *)R_StaticAlloc( tri->numIndexes * sizeof( *ind ) ); + + for ( i = 0; i < tri->numIndexes; i++ ) { + ind[i].vertexNum = tri->indexes[i]; + ind[i].faceNum = i / 3; + } + qsort( ind, tri->numIndexes, sizeof( *ind ), IndexSort ); + + tri->dominantTris = dt = triDominantTrisAllocator.Alloc( tri->numVerts ); + memset( dt, 0, tri->numVerts * sizeof( dt[0] ) ); + + for ( i = 0; i < tri->numIndexes; i += j ) { + float maxArea = 0; + int vertNum = ind[i].vertexNum; + for ( j = 0; i + j < tri->numIndexes && ind[i+j].vertexNum == vertNum; j++ ) { + float d0[5], d1[5]; + idDrawVert *a, *b, *c; + idVec3 normal, tangent, bitangent; + + int i1 = tri->indexes[ind[i+j].faceNum * 3 + 0]; + int i2 = tri->indexes[ind[i+j].faceNum * 3 + 1]; + int i3 = tri->indexes[ind[i+j].faceNum * 3 + 2]; + + a = tri->verts + i1; + b = tri->verts + i2; + c = tri->verts + i3; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + normal[0] = ( d1[1] * d0[2] - d1[2] * d0[1] ); + normal[1] = ( d1[2] * d0[0] - d1[0] * d0[2] ); + normal[2] = ( d1[0] * d0[1] - d1[1] * d0[0] ); + + float area = normal.Length(); + + // if this is smaller than what we already have, skip it + if ( area < maxArea ) { + continue; + } + maxArea = area; + + if ( i1 == vertNum ) { + dt[vertNum].v2 = i2; + dt[vertNum].v3 = i3; + } else if ( i2 == vertNum ) { + dt[vertNum].v2 = i3; + dt[vertNum].v3 = i1; + } else { + dt[vertNum].v2 = i1; + dt[vertNum].v3 = i2; + } + + float len = area; + if ( len < 0.001f ) { + len = 0.001f; + } + dt[vertNum].normalizationScale[2] = 1.0f / len; // normal + + // texture area + area = d0[3] * d1[4] - d0[4] * d1[3]; + + tangent[0] = ( d0[0] * d1[4] - d0[4] * d1[0] ); + tangent[1] = ( d0[1] * d1[4] - d0[4] * d1[1] ); + tangent[2] = ( d0[2] * d1[4] - d0[4] * d1[2] ); + len = tangent.Length(); + if ( len < 0.001f ) { + len = 0.001f; + } + dt[vertNum].normalizationScale[0] = ( area > 0 ? 1 : -1 ) / len; // tangents[0] + + bitangent[0] = ( d0[3] * d1[0] - d0[0] * d1[3] ); + bitangent[1] = ( d0[3] * d1[1] - d0[1] * d1[3] ); + bitangent[2] = ( d0[3] * d1[2] - d0[2] * d1[3] ); + len = bitangent.Length(); + if ( len < 0.001f ) { + len = 0.001f; + } +#ifdef DERIVE_UNSMOOTHED_BITANGENT + dt[vertNum].normalizationScale[1] = ( area > 0 ? 1 : -1 ); +#else + dt[vertNum].normalizationScale[1] = ( area > 0 ? 1 : -1 ) / len; // tangents[1] +#endif + } + } + + R_StaticFree( ind ); +} + +/* +==================== +R_DeriveUnsmoothedTangents + +Uses the single largest area triangle for each vertex, instead of smoothing over all +==================== +*/ +void R_DeriveUnsmoothedTangents( srfTriangles_t *tri ) { + if ( tri->tangentsCalculated ) { + return; + } + +#if 1 + + SIMDProcessor->DeriveUnsmoothedTangents( tri->verts, tri->dominantTris, tri->numVerts ); + +#else + + for ( int i = 0 ; i < tri->numVerts ; i++ ) { + idVec3 temp; + float d0[5], d1[5]; + idDrawVert *a, *b, *c; + dominantTri_t *dt = &tri->dominantTris[i]; + + a = tri->verts + i; + b = tri->verts + dt->v2; + c = tri->verts + dt->v3; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + a->normal[0] = dt->normalizationScale[2] * ( d1[1] * d0[2] - d1[2] * d0[1] ); + a->normal[1] = dt->normalizationScale[2] * ( d1[2] * d0[0] - d1[0] * d0[2] ); + a->normal[2] = dt->normalizationScale[2] * ( d1[0] * d0[1] - d1[1] * d0[0] ); + + a->tangents[0][0] = dt->normalizationScale[0] * ( d0[0] * d1[4] - d0[4] * d1[0] ); + a->tangents[0][1] = dt->normalizationScale[0] * ( d0[1] * d1[4] - d0[4] * d1[1] ); + a->tangents[0][2] = dt->normalizationScale[0] * ( d0[2] * d1[4] - d0[4] * d1[2] ); + +#ifdef DERIVE_UNSMOOTHED_BITANGENT + // derive the bitangent for a completely orthogonal axis, + // instead of using the texture T vector + a->tangents[1][0] = dt->normalizationScale[1] * ( a->normal[2] * a->tangents[0][1] - a->normal[1] * a->tangents[0][2] ); + a->tangents[1][1] = dt->normalizationScale[1] * ( a->normal[0] * a->tangents[0][2] - a->normal[2] * a->tangents[0][0] ); + a->tangents[1][2] = dt->normalizationScale[1] * ( a->normal[1] * a->tangents[0][0] - a->normal[0] * a->tangents[0][1] ); +#else + // calculate the bitangent from the texture T vector + a->tangents[1][0] = dt->normalizationScale[1] * ( d0[3] * d1[0] - d0[0] * d1[3] ); + a->tangents[1][1] = dt->normalizationScale[1] * ( d0[3] * d1[1] - d0[1] * d1[3] ); + a->tangents[1][2] = dt->normalizationScale[1] * ( d0[3] * d1[2] - d0[2] * d1[3] ); +#endif + } + +#endif + + tri->tangentsCalculated = true; +} + +/* +================== +R_DeriveTangents + +This is called once for static surfaces, and every frame for deforming surfaces + +Builds tangents, normals, and face planes +================== +*/ +void R_DeriveTangents( srfTriangles_t *tri, bool allocFacePlanes ) { + int i; + idPlane *planes; + + if ( tri->dominantTris != NULL ) { + R_DeriveUnsmoothedTangents( tri ); + return; + } + + if ( tri->tangentsCalculated ) { + return; + } + + tr.pc.c_tangentIndexes += tri->numIndexes; + + if ( !tri->facePlanes && allocFacePlanes ) { + R_AllocStaticTriSurfPlanes( tri, tri->numIndexes ); + } + planes = tri->facePlanes; + +#if 1 + + if ( !planes ) { + planes = (idPlane *)_alloca16( ( tri->numIndexes / 3 ) * sizeof( planes[0] ) ); + } + + SIMDProcessor->DeriveTangents( planes, tri->verts, tri->numVerts, tri->indexes, tri->numIndexes ); + +#else + + for ( i = 0; i < tri->numVerts; i++ ) { + tri->verts[i].normal.Zero(); + tri->verts[i].tangents[0].Zero(); + tri->verts[i].tangents[1].Zero(); + } + + for ( i = 0; i < tri->numIndexes; i += 3 ) { + // make face tangents + float d0[5], d1[5]; + idDrawVert *a, *b, *c; + idVec3 temp, normal, tangents[2]; + + a = tri->verts + tri->indexes[i + 0]; + b = tri->verts + tri->indexes[i + 1]; + c = tri->verts + tri->indexes[i + 2]; + + d0[0] = b->xyz[0] - a->xyz[0]; + d0[1] = b->xyz[1] - a->xyz[1]; + d0[2] = b->xyz[2] - a->xyz[2]; + d0[3] = b->st[0] - a->st[0]; + d0[4] = b->st[1] - a->st[1]; + + d1[0] = c->xyz[0] - a->xyz[0]; + d1[1] = c->xyz[1] - a->xyz[1]; + d1[2] = c->xyz[2] - a->xyz[2]; + d1[3] = c->st[0] - a->st[0]; + d1[4] = c->st[1] - a->st[1]; + + // normal + temp[0] = d1[1] * d0[2] - d1[2] * d0[1]; + temp[1] = d1[2] * d0[0] - d1[0] * d0[2]; + temp[2] = d1[0] * d0[1] - d1[1] * d0[0]; + VectorNormalizeFast2( temp, normal ); + +#ifdef USE_INVA + float area = d0[3] * d1[4] - d0[4] * d1[3]; + float inva = area < 0.0f ? -1 : 1; // was = 1.0f / area; + + temp[0] = (d0[0] * d1[4] - d0[4] * d1[0]) * inva; + temp[1] = (d0[1] * d1[4] - d0[4] * d1[1]) * inva; + temp[2] = (d0[2] * d1[4] - d0[4] * d1[2]) * inva; + VectorNormalizeFast2( temp, tangents[0] ); + + temp[0] = (d0[3] * d1[0] - d0[0] * d1[3]) * inva; + temp[1] = (d0[3] * d1[1] - d0[1] * d1[3]) * inva; + temp[2] = (d0[3] * d1[2] - d0[2] * d1[3]) * inva; + VectorNormalizeFast2( temp, tangents[1] ); +#else + temp[0] = (d0[0] * d1[4] - d0[4] * d1[0]); + temp[1] = (d0[1] * d1[4] - d0[4] * d1[1]); + temp[2] = (d0[2] * d1[4] - d0[4] * d1[2]); + VectorNormalizeFast2( temp, tangents[0] ); + + temp[0] = (d0[3] * d1[0] - d0[0] * d1[3]); + temp[1] = (d0[3] * d1[1] - d0[1] * d1[3]); + temp[2] = (d0[3] * d1[2] - d0[2] * d1[3]); + VectorNormalizeFast2( temp, tangents[1] ); +#endif + + // sum up the tangents and normals for each vertex on this face + for ( int j = 0 ; j < 3 ; j++ ) { + vert = &tri->verts[tri->indexes[i+j]]; + vert->normal += normal; + vert->tangents[0] += tangents[0]; + vert->tangents[1] += tangents[1]; + } + + if ( planes ) { + planes->Normal() = normal; + planes->FitThroughPoint( a->xyz ); + planes++; + } + } + +#endif + +#if 0 + + if ( tri->silIndexes != NULL ) { + for ( i = 0; i < tri->numVerts; i++ ) { + tri->verts[i].normal.Zero(); + } + for ( i = 0; i < tri->numIndexes; i++ ) { + tri->verts[tri->silIndexes[i]].normal += planes[i/3].Normal(); + } + for ( i = 0 ; i < tri->numIndexes ; i++ ) { + tri->verts[tri->indexes[i]].normal = tri->verts[tri->silIndexes[i]].normal; + } + } + +#else + + int *dupVerts = tri->dupVerts; + idDrawVert *verts = tri->verts; + + // add the normal of a duplicated vertex to the normal of the first vertex with the same XYZ + for ( i = 0; i < tri->numDupVerts; i++ ) { + verts[dupVerts[i*2+0]].normal += verts[dupVerts[i*2+1]].normal; + } + + // copy vertex normals to duplicated vertices + for ( i = 0; i < tri->numDupVerts; i++ ) { + verts[dupVerts[i*2+1]].normal = verts[dupVerts[i*2+0]].normal; + } + +#endif + +#if 0 + // sum up both sides of the mirrored verts + // so the S vectors exactly mirror, and the T vectors are equal + for ( i = 0 ; i < tri->numMirroredVerts ; i++ ) { + idDrawVert *v1, *v2; + + v1 = &tri->verts[ tri->numVerts - tri->numMirroredVerts + i ]; + v2 = &tri->verts[ tri->mirroredVerts[i] ]; + + v1->tangents[0] -= v2->tangents[0]; + v1->tangents[1] += v2->tangents[1]; + + v2->tangents[0] = vec3_origin - v1->tangents[0]; + v2->tangents[1] = v1->tangents[1]; + } +#endif + + // project the summed vectors onto the normal plane + // and normalize. The tangent vectors will not necessarily + // be orthogonal to each other, but they will be orthogonal + // to the surface normal. +#if 1 + + SIMDProcessor->NormalizeTangents( tri->verts, tri->numVerts ); + +#else + + for ( i = 0 ; i < tri->numVerts ; i++ ) { + idDrawVert *vert = &tri->verts[i]; + + VectorNormalizeFast2( vert->normal, vert->normal ); + + // project the tangent vectors + for ( int j = 0 ; j < 2 ; j++ ) { + float d; + + d = vert->tangents[j] * vert->normal; + vert->tangents[j] = vert->tangents[j] - d * vert->normal; + VectorNormalizeFast2( vert->tangents[j], vert->tangents[j] ); + } + } + +#endif + + tri->tangentsCalculated = true; + tri->facePlanesCalculated = true; +} + +/* +================= +R_RemoveDuplicatedTriangles + +silIndexes must have already been calculated + +silIndexes are used instead of indexes, because duplicated +triangles could have different texture coordinates. +================= +*/ +void R_RemoveDuplicatedTriangles( srfTriangles_t *tri ) { + int c_removed; + int i, j, r; + int a, b, c; + + c_removed = 0; + + // check for completely duplicated triangles + // any rotation of the triangle is still the same, but a mirroring + // is considered different + for ( i = 0 ; i < tri->numIndexes ; i+=3 ) { + for ( r = 0 ; r < 3 ; r++ ) { + a = tri->silIndexes[i+r]; + b = tri->silIndexes[i+(r+1)%3]; + c = tri->silIndexes[i+(r+2)%3]; + for ( j = i + 3 ; j < tri->numIndexes ; j+=3 ) { + if ( tri->silIndexes[j] == a && tri->silIndexes[j+1] == b && tri->silIndexes[j+2] == c ) { + c_removed++; + memmove( tri->indexes + j, tri->indexes + j + 3, ( tri->numIndexes - j - 3 ) * sizeof( tri->indexes[0] ) ); + memmove( tri->silIndexes + j, tri->silIndexes + j + 3, ( tri->numIndexes - j - 3 ) * sizeof( tri->silIndexes[0] ) ); + tri->numIndexes -= 3; + j -= 3; + } + } + } + } + + if ( c_removed ) { + common->Printf( "removed %i duplicated triangles\n", c_removed ); + } + +} + +/* +================= +R_RemoveDegenerateTriangles + +silIndexes must have already been calculated +================= +*/ +void R_RemoveDegenerateTriangles( srfTriangles_t *tri ) { + int c_removed; + int i; + int a, b, c; + + // check for completely degenerate triangles + c_removed = 0; + for ( i = 0; i < tri->numIndexes; i += 3 ) { + a = tri->silIndexes[i]; + b = tri->silIndexes[i+1]; + c = tri->silIndexes[i+2]; + if ( a == b || a == c || b == c ) { + c_removed++; + memmove( tri->indexes + i, tri->indexes + i + 3, ( tri->numIndexes - i - 3 ) * sizeof( tri->indexes[0] ) ); + if ( tri->silIndexes ) { + memmove( tri->silIndexes + i, tri->silIndexes + i + 3, ( tri->numIndexes - i - 3 ) * sizeof( tri->silIndexes[0] ) ); + } + tri->numIndexes -= 3; + i -= 3; + } + } + + // this doesn't free the memory used by the unused verts + + if ( c_removed ) { + common->Printf( "removed %i degenerate triangles\n", c_removed ); + } +} + +/* +================= +R_TestDegenerateTextureSpace +================= +*/ +void R_TestDegenerateTextureSpace( srfTriangles_t *tri ) { + int c_degenerate; + int i; + + // check for triangles with a degenerate texture space + c_degenerate = 0; + for ( i = 0; i < tri->numIndexes; i += 3 ) { + const idDrawVert &a = tri->verts[tri->indexes[i+0]]; + const idDrawVert &b = tri->verts[tri->indexes[i+1]]; + const idDrawVert &c = tri->verts[tri->indexes[i+2]]; + + if ( a.st == b.st || b.st == c.st || c.st == a.st ) { + c_degenerate++; + } + } + + if ( c_degenerate ) { +// common->Printf( "%d triangles with a degenerate texture space\n", c_degenerate ); + } +} + +/* +================= +R_RemoveUnusedVerts +================= +*/ +void R_RemoveUnusedVerts( srfTriangles_t *tri ) { + int i; + int *mark; + int index; + int used; + + mark = (int *)R_ClearedStaticAlloc( tri->numVerts * sizeof( *mark ) ); + + for ( i = 0 ; i < tri->numIndexes ; i++ ) { + index = tri->indexes[i]; + if ( index < 0 || index >= tri->numVerts ) { + common->Error( "R_RemoveUnusedVerts: bad index" ); + } + mark[ index ] = 1; + + if ( tri->silIndexes ) { + index = tri->silIndexes[i]; + if ( index < 0 || index >= tri->numVerts ) { + common->Error( "R_RemoveUnusedVerts: bad index" ); + } + mark[ index ] = 1; + } + } + + used = 0; + for ( i = 0 ; i < tri->numVerts ; i++ ) { + if ( !mark[i] ) { + continue; + } + mark[i] = used + 1; + used++; + } + + if ( used != tri->numVerts ) { + for ( i = 0 ; i < tri->numIndexes ; i++ ) { + tri->indexes[i] = mark[ tri->indexes[i] ] - 1; + if ( tri->silIndexes ) { + tri->silIndexes[i] = mark[ tri->silIndexes[i] ] - 1; + } + } + tri->numVerts = used; + + for ( i = 0 ; i < tri->numVerts ; i++ ) { + index = mark[ i ]; + if ( !index ) { + continue; + } + tri->verts[ index - 1 ] = tri->verts[i]; + } + + // this doesn't realloc the arrays to save the memory used by the unused verts + } + + R_StaticFree( mark ); +} + +/* +================= +R_MergeSurfaceList + +Only deals with vertexes and indexes, not silhouettes, planes, etc. +Does NOT perform a cleanup triangles, so there may be duplicated verts in the result. +================= +*/ +srfTriangles_t *R_MergeSurfaceList( const srfTriangles_t **surfaces, int numSurfaces ) { + srfTriangles_t *newTri; + const srfTriangles_t *tri; + int i, j; + int totalVerts; + int totalIndexes; + + totalVerts = 0; + totalIndexes = 0; + for ( i = 0 ; i < numSurfaces ; i++ ) { + totalVerts += surfaces[i]->numVerts; + totalIndexes += surfaces[i]->numIndexes; + } + + newTri = R_AllocStaticTriSurf(); + newTri->numVerts = totalVerts; + newTri->numIndexes = totalIndexes; + R_AllocStaticTriSurfVerts( newTri, newTri->numVerts ); + R_AllocStaticTriSurfIndexes( newTri, newTri->numIndexes ); + + totalVerts = 0; + totalIndexes = 0; + for ( i = 0 ; i < numSurfaces ; i++ ) { + tri = surfaces[i]; + memcpy( newTri->verts + totalVerts, tri->verts, tri->numVerts * sizeof( *tri->verts ) ); + for ( j = 0 ; j < tri->numIndexes ; j++ ) { + newTri->indexes[ totalIndexes + j ] = totalVerts + tri->indexes[j]; + } + totalVerts += tri->numVerts; + totalIndexes += tri->numIndexes; + } + + return newTri; +} + +/* +================= +R_MergeTriangles + +Only deals with vertexes and indexes, not silhouettes, planes, etc. +Does NOT perform a cleanup triangles, so there may be duplicated verts in the result. +================= +*/ +srfTriangles_t *R_MergeTriangles( const srfTriangles_t *tri1, const srfTriangles_t *tri2 ) { + const srfTriangles_t *tris[2]; + + tris[0] = tri1; + tris[1] = tri2; + + return R_MergeSurfaceList( tris, 2 ); +} + +/* +================= +R_ReverseTriangles + +Lit two sided surfaces need to have the triangles actually duplicated, +they can't just turn on two sided lighting, because the normal and tangents +are wrong on the other sides. + +This should be called before R_CleanupTriangles +================= +*/ +void R_ReverseTriangles( srfTriangles_t *tri ) { + int i; + + // flip the normal on each vertex + // If the surface is going to have generated normals, this won't matter, + // but if it has explicit normals, this will keep it on the correct side + for ( i = 0 ; i < tri->numVerts ; i++ ) { + tri->verts[i].normal = vec3_origin - tri->verts[i].normal; + } + + // flip the index order to make them back sided + for ( i = 0 ; i < tri->numIndexes ; i+= 3 ) { + glIndex_t temp; + + temp = tri->indexes[ i + 0 ]; + tri->indexes[ i + 0 ] = tri->indexes[ i + 1 ]; + tri->indexes[ i + 1 ] = temp; + } +} + +/* +================= +R_CleanupTriangles + +FIXME: allow createFlat and createSmooth normals, as well as explicit +================= +*/ +void R_CleanupTriangles( srfTriangles_t *tri, bool createNormals, bool identifySilEdges, bool useUnsmoothedTangents ) { + R_RangeCheckIndexes( tri ); + + R_CreateSilIndexes( tri ); + +// R_RemoveDuplicatedTriangles( tri ); // this may remove valid overlapped transparent triangles + + R_RemoveDegenerateTriangles( tri ); + + R_TestDegenerateTextureSpace( tri ); + +// R_RemoveUnusedVerts( tri ); + + if ( identifySilEdges ) { + R_IdentifySilEdges( tri, true ); // assume it is non-deformable, and omit coplanar edges + } + + // bust vertexes that share a mirrored edge into separate vertexes + R_DuplicateMirroredVertexes( tri ); + + // optimize the index order (not working?) +// R_OrderIndexes( tri->numIndexes, tri->indexes ); + + R_CreateDupVerts( tri ); + + R_BoundTriSurf( tri ); + + if ( useUnsmoothedTangents ) { + R_BuildDominantTris( tri ); + R_DeriveUnsmoothedTangents( tri ); + } else if ( !createNormals ) { + R_DeriveFacePlanes( tri ); + R_DeriveTangentsWithoutNormals( tri ); + } else { + R_DeriveTangents( tri ); + } +} + +/* +=================================================================================== + +DEFORMED SURFACES + +=================================================================================== +*/ + +/* +=================== +R_BuildDeformInfo +=================== +*/ +deformInfo_t *R_BuildDeformInfo( int numVerts, const idDrawVert *verts, int numIndexes, const int *indexes, bool useUnsmoothedTangents ) { + deformInfo_t *deform; + srfTriangles_t tri; + int i; + + memset( &tri, 0, sizeof( tri ) ); + + tri.numVerts = numVerts; + R_AllocStaticTriSurfVerts( &tri, tri.numVerts ); + SIMDProcessor->Memcpy( tri.verts, verts, tri.numVerts * sizeof( tri.verts[0] ) ); + + tri.numIndexes = numIndexes; + R_AllocStaticTriSurfIndexes( &tri, tri.numIndexes ); + + // don't memcpy, so we can change the index type from int to short without changing the interface + for ( i = 0 ; i < tri.numIndexes ; i++ ) { + tri.indexes[i] = indexes[i]; + } + + R_RangeCheckIndexes( &tri ); + R_CreateSilIndexes( &tri ); + +// should we order the indexes here? + +// R_RemoveDuplicatedTriangles( &tri ); +// R_RemoveDegenerateTriangles( &tri ); +// R_RemoveUnusedVerts( &tri ); + R_IdentifySilEdges( &tri, false ); // we cannot remove coplanar edges, because + // they can deform to silhouettes + + R_DuplicateMirroredVertexes( &tri ); // split mirror points into multiple points + + R_CreateDupVerts( &tri ); + + if ( useUnsmoothedTangents ) { + R_BuildDominantTris( &tri ); + } + + deform = (deformInfo_t *)R_ClearedStaticAlloc( sizeof( *deform ) ); + + deform->numSourceVerts = numVerts; + deform->numOutputVerts = tri.numVerts; + + deform->numIndexes = numIndexes; + deform->indexes = tri.indexes; + + deform->silIndexes = tri.silIndexes; + + deform->numSilEdges = tri.numSilEdges; + deform->silEdges = tri.silEdges; + + deform->dominantTris = tri.dominantTris; + + deform->numMirroredVerts = tri.numMirroredVerts; + deform->mirroredVerts = tri.mirroredVerts; + + deform->numDupVerts = tri.numDupVerts; + deform->dupVerts = tri.dupVerts; + + if ( tri.verts ) { + triVertexAllocator.Free( tri.verts ); + } + + if ( tri.facePlanes ) { + triPlaneAllocator.Free( tri.facePlanes ); + } + + return deform; +} + +/* +=================== +R_FreeDeformInfo +=================== +*/ +void R_FreeDeformInfo( deformInfo_t *deformInfo ) { + if ( deformInfo->indexes != NULL ) { + triIndexAllocator.Free( deformInfo->indexes ); + } + if ( deformInfo->silIndexes != NULL ) { + triSilIndexAllocator.Free( deformInfo->silIndexes ); + } + if ( deformInfo->silEdges != NULL ) { + triSilEdgeAllocator.Free( deformInfo->silEdges ); + } + if ( deformInfo->dominantTris != NULL ) { + triDominantTrisAllocator.Free( deformInfo->dominantTris ); + } + if ( deformInfo->mirroredVerts != NULL ) { + triMirroredVertAllocator.Free( deformInfo->mirroredVerts ); + } + if ( deformInfo->dupVerts != NULL ) { + triDupVertAllocator.Free( deformInfo->dupVerts ); + } + R_StaticFree( deformInfo ); +} + +/* +=================== +R_DeformInfoMemoryUsed +=================== +*/ +int R_DeformInfoMemoryUsed( deformInfo_t *deformInfo ) { + int total = 0; + + if ( deformInfo->indexes != NULL ) { + total += deformInfo->numIndexes * sizeof( deformInfo->indexes[0] ); + } + if ( deformInfo->silIndexes != NULL ) { + total += deformInfo->numIndexes * sizeof( deformInfo->silIndexes[0] ); + } + if ( deformInfo->silEdges != NULL ) { + total += deformInfo->numSilEdges * sizeof( deformInfo->silEdges[0] ); + } + if ( deformInfo->dominantTris != NULL ) { + total += deformInfo->numSourceVerts * sizeof( deformInfo->dominantTris[0] ); + } + if ( deformInfo->mirroredVerts != NULL ) { + total += deformInfo->numMirroredVerts * sizeof( deformInfo->mirroredVerts[0] ); + } + if ( deformInfo->dupVerts != NULL ) { + total += deformInfo->numDupVerts * sizeof( deformInfo->dupVerts[0] ); + } + + total += sizeof( *deformInfo ); + return total; +} diff --git a/src/renderer/tr_turboshadow.cpp b/src/renderer/tr_turboshadow.cpp new file mode 100644 index 0000000..aef9116 --- /dev/null +++ b/src/renderer/tr_turboshadow.cpp @@ -0,0 +1,335 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" +#include "rvMesh.h" + +int c_turboUsedVerts; +int c_turboUnusedVerts; + + +/* +===================== +R_CreateVertexBufferTurboShadowVolume + +Creates the CPU index stream used with an MD5R mesh's resident shadow-volume +vertex buffer. Each primitive batch is preceded by a pair containing the +silhouette-only and silhouette-plus-cap index counts. +===================== +*/ +srfTriangles_t *R_CreateVertexBufferTurboShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + srfCullInfo_t &cullInfo ) { + rvMesh *mesh = tri->primBatchMesh; + const int numFaces = mesh->m_numSilTracePrimitives; + + R_CalcInteractionFacing( ent, tri, light, cullInfo ); + if ( r_useShadowProjectedCull.GetBool() ) { + R_CalcInteractionCullBits( ent, tri, light, cullInfo ); + } + + int numShadowingFaces; + if ( cullInfo.cullBits != LIGHT_CULL_ALL_FRONT && + r_useShadowProjectedCull.GetBool() ) { + numShadowingFaces = mesh->FlipOutsideBackFaces( cullInfo ); + } else { + int numFacing = 0; + for ( int i = 0; i < numFaces; ++i ) { + numFacing += cullInfo.facing[i]; + } + numShadowingFaces = numFaces - numFacing; + } + + if ( numShadowingFaces == 0 ) { + return NULL; + } + + srfTriangles_t *newTri = R_AllocStaticTriSurf(); + newTri->numVerts = mesh->m_numSilTraceVertices * 2; + newTri->primBatchMesh = tri->primBatchMesh; + newTri->skinToModelTransforms = tri->skinToModelTransforms; + newTri->skinToModelTransformsAlloc = NULL; + newTri->numSkinToModelTransforms = tri->numSkinToModelTransforms; + + const int batchHeaderIndexes = mesh->m_numPrimBatches * 2; + R_AllocStaticTriSurfIndexes( newTri, batchHeaderIndexes + + 6 * ( numShadowingFaces + mesh->m_numSilEdges ) ); + + int *batchCounts = newTri->indexes; + int *shadowIndexes = newTri->indexes + batchHeaderIndexes; + const byte *batchFacing = cullInfo.facing; + newTri->numIndexes = 0; + newTri->numShadowIndexesNoCaps = 0; + + for ( int batch = 0; batch < mesh->m_numPrimBatches; ++batch ) { + const int numSilIndexes = mesh->CreateSilShadowVolTris( + batch, shadowIndexes, batchFacing ); + shadowIndexes += numSilIndexes; + const int numCapIndexes = mesh->CreateFrontBackShadowVolTris( + batch, shadowIndexes, batchFacing ); + shadowIndexes += numCapIndexes; + + batchCounts[batch * 2] = numSilIndexes; + batchCounts[batch * 2 + 1] = numSilIndexes + numCapIndexes; + newTri->numShadowIndexesNoCaps += numSilIndexes; + newTri->numIndexes += numSilIndexes + numCapIndexes; + batchFacing += mesh->GetNumSilTracePrimitives( batch ); + } + + newTri->numShadowIndexesNoFrontCaps = newTri->numIndexes; + newTri->shadowCapPlaneBits = SHADOW_CAP_INFINITE; + R_ResizeStaticTriSurfIndexes( newTri, newTri->numIndexes + batchHeaderIndexes ); + newTri->bounds.Clear(); + return newTri; +} + + +/* +===================== +R_CreateVertexProgramTurboShadowVolume + +are dangling edges that are outside the light frustum still making planes? +===================== +*/ +srfTriangles_t *R_CreateVertexProgramTurboShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + srfCullInfo_t &cullInfo ) { + R_CalcInteractionFacing( ent, tri, light, cullInfo ); + if ( r_useShadowProjectedCull.GetBool() ) { + R_CalcInteractionCullBits( ent, tri, light, cullInfo ); + } + + const int numFaces = tri->numIndexes / 3; + int numFacing; + if ( cullInfo.cullBits != LIGHT_CULL_ALL_FRONT && + r_useShadowProjectedCull.GetBool() ) { + numFacing = SIMDProcessor->ShadowVolume_CountFacingCull( + cullInfo.facing, numFaces, tri->indexes, cullInfo.cullBits ); + } else { + numFacing = SIMDProcessor->ShadowVolume_CountFacing( + cullInfo.facing, numFaces ); + } + + const int numShadowingFaces = numFaces - numFacing; + if ( numShadowingFaces == 0 ) { + return NULL; + } + + srfTriangles_t *newTri = R_AllocStaticTriSurf(); + newTri->numVerts = tri->numVerts * 2; + R_AllocStaticTriSurfIndexes( newTri, + ( tri->numSilEdges + numShadowingFaces + 1 ) * 6 ); + + const int numShadowIndexes = SIMDProcessor->ShadowVolume_CreateSilTriangles( + newTri->indexes, cullInfo.facing, + reinterpret_cast( tri->silEdges ), tri->numSilEdges ); + newTri->numIndexes = newTri->numShadowIndexesNoFrontCaps = numShadowIndexes + numShadowingFaces * 6; + newTri->numShadowIndexesNoCaps = numShadowIndexes; + newTri->shadowCapPlaneBits = SHADOW_CAP_INFINITE; + R_ResizeStaticTriSurfIndexes( newTri, newTri->numIndexes + 6 ); + newTri->bounds.Clear(); + SIMDProcessor->ShadowVolume_CreateCapTriangles( newTri->indexes + numShadowIndexes, + cullInfo.facing, tri->indexes, tri->numIndexes ); + + return newTri; +} + +/* +===================== +R_CreateTurboShadowVolume +===================== +*/ +srfTriangles_t *R_CreateTurboShadowVolume( const idRenderEntityLocal *ent, + const srfTriangles_t *tri, const idRenderLightLocal *light, + srfCullInfo_t &cullInfo ) { + int i, j; + idVec3 localLightOrigin; + srfTriangles_t *newTri; + silEdge_t *sil; + const glIndex_t *indexes; + const byte *facing; + + R_CalcInteractionFacing( ent, tri, light, cullInfo ); + if ( r_useShadowProjectedCull.GetBool() ) { + R_CalcInteractionCullBits( ent, tri, light, cullInfo ); + } + + int numFaces = tri->numIndexes / 3; + int numShadowingFaces = 0; + facing = cullInfo.facing; + + // if all the triangles are inside the light frustum + if ( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT || !r_useShadowProjectedCull.GetBool() ) { + + // count the number of shadowing faces + for ( i = 0; i < numFaces; i++ ) { + numShadowingFaces += facing[i]; + } + numShadowingFaces = numFaces - numShadowingFaces; + + } else { + + // make all triangles that are outside the light frustum "facing", so they won't cast shadows + indexes = tri->indexes; + byte *modifyFacing = cullInfo.facing; + const byte *cullBits = cullInfo.cullBits; + for ( j = i = 0; i < tri->numIndexes; i += 3, j++ ) { + if ( !modifyFacing[j] ) { + int i1 = indexes[i+0]; + int i2 = indexes[i+1]; + int i3 = indexes[i+2]; + if ( cullBits[i1] & cullBits[i2] & cullBits[i3] ) { + modifyFacing[j] = 1; + } else { + numShadowingFaces++; + } + } + } + } + + if ( !numShadowingFaces ) { + // no faces are inside the light frustum and still facing the right way + return NULL; + } + + newTri = R_AllocStaticTriSurf(); + +#ifdef USE_TRI_DATA_ALLOCATOR + R_AllocStaticTriSurfShadowVerts( newTri, tri->numVerts * 2 ); + shadowCache_t *shadowVerts = newTri->shadowVertexes; +#else + shadowCache_t *shadowVerts = (shadowCache_t *)_alloca16( tri->numVerts * 2 * sizeof( shadowVerts[0] ) ); +#endif + + R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin ); + + int *vertRemap = (int *)_alloca16( tri->numVerts * sizeof( vertRemap[0] ) ); + + SIMDProcessor->Memset( vertRemap, -1, tri->numVerts * sizeof( vertRemap[0] ) ); + + for ( i = 0, j = 0; i < tri->numIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + // this may pull in some vertexes that are outside + // the frustum, because they connect to vertexes inside + vertRemap[tri->silIndexes[i+0]] = 0; + vertRemap[tri->silIndexes[i+1]] = 0; + vertRemap[tri->silIndexes[i+2]] = 0; + } + + newTri->numVerts = SIMDProcessor->CreateShadowCache( &shadowVerts->xyz, vertRemap, localLightOrigin, tri->verts, tri->numVerts ); + + c_turboUsedVerts += newTri->numVerts; + c_turboUnusedVerts += tri->numVerts * 2 - newTri->numVerts; + +#ifdef USE_TRI_DATA_ALLOCATOR + R_ResizeStaticTriSurfShadowVerts( newTri, newTri->numVerts ); +#else + R_AllocStaticTriSurfShadowVerts( newTri, newTri->numVerts ); + SIMDProcessor->Memcpy( newTri->shadowVertexes, shadowVerts, newTri->numVerts * sizeof( shadowVerts[0] ) ); +#endif + + // alloc the max possible size +#ifdef USE_TRI_DATA_ALLOCATOR + R_AllocStaticTriSurfIndexes( newTri, ( numShadowingFaces + tri->numSilEdges ) * 6 ); + glIndex_t *tempIndexes = newTri->indexes; + glIndex_t *shadowIndexes = newTri->indexes; +#else + glIndex_t *tempIndexes = (glIndex_t *)_alloca16( tri->numSilEdges * 6 * sizeof( tempIndexes[0] ) ); + glIndex_t *shadowIndexes = tempIndexes; +#endif + + // create new triangles along sil planes + for ( sil = tri->silEdges, i = tri->numSilEdges; i > 0; i--, sil++ ) { + + int f1 = facing[sil->p1]; + int f2 = facing[sil->p2]; + + if ( !( f1 ^ f2 ) ) { + continue; + } + + int v1 = vertRemap[sil->v1]; + int v2 = vertRemap[sil->v2]; + + // set the two triangle winding orders based on facing + // without using a poorly-predictable branch + + shadowIndexes[0] = v1; + shadowIndexes[1] = v2 ^ f1; + shadowIndexes[2] = v2 ^ f2; + shadowIndexes[3] = v1 ^ f2; + shadowIndexes[4] = v1 ^ f1; + shadowIndexes[5] = v2 ^ 1; + + shadowIndexes += 6; + } + + int numShadowIndexes = shadowIndexes - tempIndexes; + + // we aren't bothering to separate front and back caps on these + newTri->numIndexes = newTri->numShadowIndexesNoFrontCaps = numShadowIndexes + numShadowingFaces * 6; + newTri->numShadowIndexesNoCaps = numShadowIndexes; + newTri->shadowCapPlaneBits = SHADOW_CAP_INFINITE; + +#ifdef USE_TRI_DATA_ALLOCATOR + // decrease the size of the memory block to only store the used indexes + R_ResizeStaticTriSurfIndexes( newTri, newTri->numIndexes ); +#else + // allocate memory for the indexes + R_AllocStaticTriSurfIndexes( newTri, newTri->numIndexes ); + // copy the indexes we created for the sil planes + SIMDProcessor->Memcpy( newTri->indexes, tempIndexes, numShadowIndexes * sizeof( tempIndexes[0] ) ); +#endif + + // these have no effect, because they extend to infinity + newTri->bounds.Clear(); + + // put some faces on the model and some on the distant projection + indexes = tri->silIndexes; + shadowIndexes = newTri->indexes + numShadowIndexes; + for ( i = 0, j = 0; i < tri->numIndexes; i += 3, j++ ) { + if ( facing[j] ) { + continue; + } + + int i0 = vertRemap[indexes[i+0]]; + shadowIndexes[2] = i0; + shadowIndexes[3] = i0 ^ 1; + int i1 = vertRemap[indexes[i+1]]; + shadowIndexes[1] = i1; + shadowIndexes[4] = i1 ^ 1; + int i2 = vertRemap[indexes[i+2]]; + shadowIndexes[0] = i2; + shadowIndexes[5] = i2 ^ 1; + + shadowIndexes += 6; + } + + return newTri; +} diff --git a/src/renderer/vertexcache.cpp b/src/renderer/vertexcache.cpp new file mode 100644 index 0000000..9303925 --- /dev/null +++ b/src/renderer/vertexcache.cpp @@ -0,0 +1,560 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "tr_local.h" + + +static const int FRAME_MEMORY_BYTES = 0x200000; +static const int EXPAND_HEADERS = 1024; + +idCVar idVertexCache::r_showVertexCache( "r_showVertexCache", "0", CVAR_INTEGER|CVAR_RENDERER, "" ); +idCVar idVertexCache::r_vertexBufferMegs( "r_vertexBufferMegs", "32", CVAR_INTEGER|CVAR_RENDERER, "" ); + +idVertexCache vertexCache; + +/* +============== +R_ListVertexCache_f +============== +*/ +static void R_ListVertexCache_f( const idCmdArgs &args ) { + vertexCache.List(); +} + +/* +============== +idVertexCache::ActuallyFree +============== +*/ +void idVertexCache::ActuallyFree( vertCache_t *block ) { + if (!block) { + common->Error( "idVertexCache Free: NULL pointer" ); + } + + if ( block->user ) { + // let the owner know we have purged it + *block->user = NULL; + block->user = NULL; + } + + // temp blocks are in a shared space that won't be freed + if ( block->tag != TAG_TEMP ) { + staticAllocTotal -= block->size; + staticCountTotal--; + + if ( block->vbo ) { +#if 0 // this isn't really necessary, it will be reused soon enough + // filling with zero length data is the equivalent of freeing + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, block->vbo); + qglBufferDataARB(GL_ARRAY_BUFFER_ARB, 0, 0, GL_DYNAMIC_DRAW_ARB); +#endif + } else if ( block->virtMem ) { + Mem_Free( block->virtMem ); + block->virtMem = NULL; + } + } + block->tag = TAG_FREE; // mark as free + + // unlink stick it back on the free list + block->next->prev = block->prev; + block->prev->next = block->next; + +#if 1 + // stick it on the front of the free list so it will be reused immediately + block->next = freeStaticHeaders.next; + block->prev = &freeStaticHeaders; +#else + // stick it on the back of the free list so it won't be reused soon (just for debugging) + block->next = &freeStaticHeaders; + block->prev = freeStaticHeaders.prev; +#endif + + block->next->prev = block; + block->prev->next = block; +} + +/* +============== +idVertexCache::Position + +this will be a real pointer with virtual memory, +but it will be an int offset cast to a pointer with +ARB_vertex_buffer_object + +The ARB_vertex_buffer_object will be bound +============== +*/ +void *idVertexCache::Position( vertCache_t *buffer ) { + if ( !buffer || buffer->tag == TAG_FREE ) { + common->FatalError( "idVertexCache::Position: bad vertCache_t" ); + } + + // the ARB vertex object just uses an offset + if ( buffer->vbo ) { + if ( r_showVertexCache.GetInteger() == 2 ) { + if ( buffer->tag == TAG_TEMP ) { + common->Printf( "GL_ARRAY_BUFFER_ARB = %i + %i (%i bytes)\n", buffer->vbo, buffer->offset, buffer->size ); + } else { + common->Printf( "GL_ARRAY_BUFFER_ARB = %i (%i bytes)\n", buffer->vbo, buffer->size ); + } + } + if ( buffer->indexBuffer ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, buffer->vbo ); + } else { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, buffer->vbo ); + } + return (void *)buffer->offset; + } + + // virtual memory is a real pointer + return (void *)((byte *)buffer->virtMem + buffer->offset); +} + +void idVertexCache::UnbindIndex() { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); +} + + +//================================================================================ + +/* +=========== +idVertexCache::Init +=========== +*/ +void idVertexCache::Init() { + cmdSystem->AddCommand( "listVertexCache", R_ListVertexCache_f, CMD_FL_RENDERER, "lists vertex cache" ); + + if ( r_vertexBufferMegs.GetInteger() < 8 ) { + r_vertexBufferMegs.SetInteger( 8 ); + } + + virtualMemory = false; + + // use ARB_vertex_buffer_object unless explicitly disabled + if( r_useVertexBuffers.GetInteger() && glConfig.ARBVertexBufferObjectAvailable ) { + common->Printf( "using ARB_vertex_buffer_object memory\n" ); + } else { + virtualMemory = true; + r_useIndexBuffers.SetBool( false ); + common->Printf( "WARNING: vertex array range in virtual memory (SLOW)\n" ); + } + + // initialize the cache memory blocks + freeStaticHeaders.next = freeStaticHeaders.prev = &freeStaticHeaders; + staticHeaders.next = staticHeaders.prev = &staticHeaders; + freeDynamicHeaders.next = freeDynamicHeaders.prev = &freeDynamicHeaders; + dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders; + deferredFreeList.next = deferredFreeList.prev = &deferredFreeList; + + // set up the dynamic frame memory + frameBytes = FRAME_MEMORY_BYTES; + staticAllocTotal = 0; + + byte *junk = (byte *)Mem_Alloc( frameBytes ); + for ( int i = 0 ; i < NUM_VERTEX_FRAMES ; i++ ) { + allocatingTempBuffer = true; // force the alloc to use GL_STREAM_DRAW_ARB + Alloc( junk, frameBytes, &tempBuffers[i] ); + allocatingTempBuffer = false; + tempBuffers[i]->tag = TAG_FIXED; + // unlink these from the static list, so they won't ever get purged + tempBuffers[i]->next->prev = tempBuffers[i]->prev; + tempBuffers[i]->prev->next = tempBuffers[i]->next; + } + Mem_Free( junk ); + + EndFrame(); +} + +/* +=========== +idVertexCache::PurgeAll + +Used when toggling vertex programs on or off, because +the cached data isn't valid +=========== +*/ +void idVertexCache::PurgeAll() { + while( staticHeaders.next != &staticHeaders ) { + ActuallyFree( staticHeaders.next ); + } +} + +/* +=========== +idVertexCache::Shutdown +=========== +*/ +void idVertexCache::Shutdown() { +// PurgeAll(); // !@#: also purge the temp buffers + + headerAllocator.Shutdown(); +} + +/* +=========== +idVertexCache::Alloc +=========== +*/ +void idVertexCache::Alloc( void *data, int size, vertCache_t **buffer, bool indexBuffer ) { + vertCache_t *block; + + if ( size <= 0 ) { + common->Error( "idVertexCache::Alloc: size = %i\n", size ); + } + + // if we can't find anything, it will be NULL + *buffer = NULL; + + // if we don't have any remaining unused headers, allocate some more + if ( freeStaticHeaders.next == &freeStaticHeaders ) { + + for ( int i = 0; i < EXPAND_HEADERS; i++ ) { + block = headerAllocator.Alloc(); + block->next = freeStaticHeaders.next; + block->prev = &freeStaticHeaders; + block->next->prev = block; + block->prev->next = block; + + if( !virtualMemory ) { + qglGenBuffersARB( 1, & block->vbo ); + } + } + } + + // move it from the freeStaticHeaders list to the staticHeaders list + block = freeStaticHeaders.next; + block->next->prev = block->prev; + block->prev->next = block->next; + block->next = staticHeaders.next; + block->prev = &staticHeaders; + block->next->prev = block; + block->prev->next = block; + + block->size = size; + block->offset = 0; + block->tag = TAG_USED; + + // save data for debugging + staticAllocThisFrame += block->size; + staticCountThisFrame++; + staticCountTotal++; + staticAllocTotal += block->size; + + // this will be set to zero when it is purged + block->user = buffer; + *buffer = block; + + // allocation doesn't imply used-for-drawing, because at level + // load time lots of things may be created, but they aren't + // referenced by the GPU yet, and can be purged if needed. + block->frameUsed = currentFrame - NUM_VERTEX_FRAMES; + + block->indexBuffer = indexBuffer; + + // copy the data + if ( block->vbo ) { + if ( indexBuffer ) { + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, block->vbo ); + qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB ); + } else { + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, block->vbo ); + if ( allocatingTempBuffer ) { + qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STREAM_DRAW_ARB ); + } else { + qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB ); + } + } + } else { + block->virtMem = Mem_Alloc( size ); + SIMDProcessor->Memcpy( block->virtMem, data, size ); + } +} + +/* +=========== +idVertexCache::Touch +=========== +*/ +void idVertexCache::Touch( vertCache_t *block ) { + if ( !block ) { + common->Error( "idVertexCache Touch: NULL pointer" ); + } + + if ( block->tag == TAG_FREE ) { + common->FatalError( "idVertexCache Touch: freed pointer" ); + } + if ( block->tag == TAG_TEMP ) { + common->FatalError( "idVertexCache Touch: temporary pointer" ); + } + + block->frameUsed = currentFrame; + + // move to the head of the LRU list + block->next->prev = block->prev; + block->prev->next = block->next; + + block->next = staticHeaders.next; + block->prev = &staticHeaders; + staticHeaders.next->prev = block; + staticHeaders.next = block; +} + +/* +=========== +idVertexCache::Free +=========== +*/ +void idVertexCache::Free( vertCache_t *block ) { + if (!block) { + return; + } + + if ( block->tag == TAG_FREE ) { + common->FatalError( "idVertexCache Free: freed pointer" ); + } + if ( block->tag == TAG_TEMP ) { + common->FatalError( "idVertexCache Free: temporary pointer" ); + } + + // this block still can't be purged until the frame count has expired, + // but it won't need to clear a user pointer when it is + block->user = NULL; + + block->next->prev = block->prev; + block->prev->next = block->next; + + block->next = deferredFreeList.next; + block->prev = &deferredFreeList; + deferredFreeList.next->prev = block; + deferredFreeList.next = block; +} + +/* +=========== +idVertexCache::AllocFrameTemp + +A frame temp allocation must never be allowed to fail due to overflow. +We can't simply sync with the GPU and overwrite what we have, because +there may still be future references to dynamically created surfaces. +=========== +*/ +vertCache_t *idVertexCache::AllocFrameTemp( void *data, int size, unsigned int flags ) { + vertCache_t *block; + + if ( size <= 0 ) { + common->Error( "idVertexCache::AllocFrameTemp: size = %i\n", size ); + } + + if ( dynamicAllocThisFrame + size > frameBytes ) { + // if we don't have enough room in the temp block, allocate a static block, + // but immediately free it so it will get freed at the next frame + tempOverflow = true; + Alloc( data, size, &block, ( flags & 1 ) != 0 ); + Free( block); + return block; + } + + // this data is just going on the shared dynamic list + + // if we don't have any remaining unused headers, allocate some more + if ( freeDynamicHeaders.next == &freeDynamicHeaders ) { + + for ( int i = 0; i < EXPAND_HEADERS; i++ ) { + block = headerAllocator.Alloc(); + block->next = freeDynamicHeaders.next; + block->prev = &freeDynamicHeaders; + block->next->prev = block; + block->prev->next = block; + } + } + + // move it from the freeDynamicHeaders list to the dynamicHeaders list + block = freeDynamicHeaders.next; + block->next->prev = block->prev; + block->prev->next = block->next; + block->next = dynamicHeaders.next; + block->prev = &dynamicHeaders; + block->next->prev = block; + block->prev->next = block; + + block->size = size; + block->tag = TAG_TEMP; + block->indexBuffer = ( flags & 1 ) != 0; + block->offset = dynamicAllocThisFrame; + dynamicAllocThisFrame += block->size; + dynamicCountThisFrame++; + block->user = NULL; + block->frameUsed = 0; + + // copy the data + block->virtMem = tempBuffers[listNum]->virtMem; + block->vbo = tempBuffers[listNum]->vbo; + + if ( block->vbo ) { + const GLenum target = block->indexBuffer ? GL_ELEMENT_ARRAY_BUFFER_ARB : GL_ARRAY_BUFFER_ARB; + qglBindBufferARB( target, block->vbo ); + qglBufferSubDataARB( target, block->offset, (GLsizeiptrARB)size, data ); + } else { + SIMDProcessor->Memcpy( (byte *)block->virtMem + block->offset, data, size ); + } + + return block; +} + +/* +=========== +idVertexCache::EndFrame +=========== +*/ +void idVertexCache::EndFrame() { + // display debug information + if ( r_showVertexCache.GetBool() ) { + int staticUseCount = 0; + int staticUseSize = 0; + + for ( vertCache_t *block = staticHeaders.next ; block != &staticHeaders ; block = block->next ) { + if ( block->frameUsed == currentFrame ) { + staticUseCount++; + staticUseSize += block->size; + } + } + + const char *frameOverflow = tempOverflow ? "(OVERFLOW)" : ""; + + common->Printf( "vertex dynamic:%i=%ik%s, static alloc:%i=%ik used:%i=%ik total:%i=%ik\n", + dynamicCountThisFrame, dynamicAllocThisFrame/1024, frameOverflow, + staticCountThisFrame, staticAllocThisFrame/1024, + staticUseCount, staticUseSize/1024, + staticCountTotal, staticAllocTotal/1024 ); + } + +#if 0 + // if our total static count is above our working memory limit, start purging things + while ( staticAllocTotal > r_vertexBufferMegs.GetInteger() * 1024 * 1024 ) { + // free the least recently used + + } +#endif + + if( !virtualMemory ) { + // unbind vertex buffers so normal virtual memory will be used in case + // r_useVertexBuffers / r_useIndexBuffers + qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + + + currentFrame = tr.frameCount; + listNum = currentFrame % NUM_VERTEX_FRAMES; + staticAllocThisFrame = 0; + staticCountThisFrame = 0; + dynamicAllocThisFrame = 0; + dynamicCountThisFrame = 0; + tempOverflow = false; + + // free all the deferred free headers + while( deferredFreeList.next != &deferredFreeList ) { + ActuallyFree( deferredFreeList.next ); + } + + // free all the frame temp headers + vertCache_t *block = dynamicHeaders.next; + if ( block != &dynamicHeaders ) { + block->prev = &freeDynamicHeaders; + dynamicHeaders.prev->next = freeDynamicHeaders.next; + freeDynamicHeaders.next->prev = dynamicHeaders.prev; + freeDynamicHeaders.next = block; + + dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders; + } +} + +/* +============= +idVertexCache::List +============= +*/ +void idVertexCache::List( void ) { + int numActive = 0; + int numDeferred = 0; + int frameStatic = 0; + int totalStatic = 0; + int deferredSpace = 0; + + vertCache_t *block; + for ( block = staticHeaders.next ; block != &staticHeaders ; block = block->next) { + numActive++; + + totalStatic += block->size; + if ( block->frameUsed == currentFrame ) { + frameStatic += block->size; + } + } + + int numFreeStaticHeaders = 0; + for ( block = freeStaticHeaders.next ; block != &freeStaticHeaders ; block = block->next ) { + numFreeStaticHeaders++; + } + + int numFreeDynamicHeaders = 0; + for ( block = freeDynamicHeaders.next ; block != &freeDynamicHeaders ; block = block->next ) { + numFreeDynamicHeaders++; + } + + common->Printf( "%i megs working set\n", r_vertexBufferMegs.GetInteger() ); + common->Printf( "%i dynamic temp buffers of %ik\n", NUM_VERTEX_FRAMES, frameBytes / 1024 ); + common->Printf( "%5i active static headers\n", numActive ); + common->Printf( "%5i free static headers\n", numFreeStaticHeaders ); + common->Printf( "%5i free dynamic headers\n", numFreeDynamicHeaders ); + + if ( !virtualMemory ) { + common->Printf( "Vertex cache is in ARB_vertex_buffer_object memory (FAST).\n"); + } else { + common->Printf( "Vertex cache is in virtual memory (SLOW)\n" ); + } + + if ( r_useIndexBuffers.GetBool() ) { + common->Printf( "Index buffers are accelerated.\n" ); + } else { + common->Printf( "Index buffers are not used.\n" ); + } +} + +/* +============= +idVertexCache::IsFast + +just for gfxinfo printing +============= +*/ +bool idVertexCache::IsFast() { + if ( virtualMemory ) { + return false; + } + return true; +} diff --git a/src/renderer/wglext.h b/src/renderer/wglext.h new file mode 100644 index 0000000..2d9359b --- /dev/null +++ b/src/renderer/wglext.h @@ -0,0 +1,636 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __wglext_h_ +#define __wglext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2002 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +/* Header file version number */ +/* wglext.h last updated 2002/03/22 */ +/* Current version at http://oss.sgi.com/projects/ogl-sample/registry/ */ +#define WGL_WGLEXT_VERSION 4 + +#ifndef WGL_ARB_buffer_region +#define WGL_FRONT_COLOR_BUFFER_BIT_ARB 0x00000001 +#define WGL_BACK_COLOR_BUFFER_BIT_ARB 0x00000002 +#define WGL_DEPTH_BUFFER_BIT_ARB 0x00000004 +#define WGL_STENCIL_BUFFER_BIT_ARB 0x00000008 +#endif + +#ifndef WGL_ARB_multisample +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#endif + +#ifndef WGL_ARB_extensions_string +#endif + +#ifndef WGL_ARB_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#endif + +#ifndef WGL_ARB_make_current_read +#define ERROR_INVALID_PIXEL_TYPE_ARB 0x2043 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +#endif + +#ifndef WGL_ARB_pbuffer +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E +#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030 +#define WGL_PBUFFER_LARGEST_ARB 0x2033 +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_PBUFFER_LOST_ARB 0x2036 +#endif + +#ifndef WGL_ARB_render_texture +#define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_MIPMAP_TEXTURE_ARB 0x2074 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 +#define WGL_TEXTURE_1D_ARB 0x2079 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_MIPMAP_LEVEL_ARB 0x207B +#define WGL_CUBE_MAP_FACE_ARB 0x207C +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x207E +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x207F +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x2080 +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x2081 +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x2082 +#define WGL_FRONT_LEFT_ARB 0x2083 +#define WGL_FRONT_RIGHT_ARB 0x2084 +#define WGL_BACK_LEFT_ARB 0x2085 +#define WGL_BACK_RIGHT_ARB 0x2086 +#define WGL_AUX0_ARB 0x2087 +#define WGL_AUX1_ARB 0x2088 +#define WGL_AUX2_ARB 0x2089 +#define WGL_AUX3_ARB 0x208A +#define WGL_AUX4_ARB 0x208B +#define WGL_AUX5_ARB 0x208C +#define WGL_AUX6_ARB 0x208D +#define WGL_AUX7_ARB 0x208E +#define WGL_AUX8_ARB 0x208F +#define WGL_AUX9_ARB 0x2090 +#endif + +#ifndef WGL_EXT_make_current_read +#define ERROR_INVALID_PIXEL_TYPE_EXT 0x2043 +#endif + +#ifndef WGL_EXT_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000 +#define WGL_DRAW_TO_WINDOW_EXT 0x2001 +#define WGL_DRAW_TO_BITMAP_EXT 0x2002 +#define WGL_ACCELERATION_EXT 0x2003 +#define WGL_NEED_PALETTE_EXT 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006 +#define WGL_SWAP_METHOD_EXT 0x2007 +#define WGL_NUMBER_OVERLAYS_EXT 0x2008 +#define WGL_NUMBER_UNDERLAYS_EXT 0x2009 +#define WGL_TRANSPARENT_EXT 0x200A +#define WGL_TRANSPARENT_VALUE_EXT 0x200B +#define WGL_SHARE_DEPTH_EXT 0x200C +#define WGL_SHARE_STENCIL_EXT 0x200D +#define WGL_SHARE_ACCUM_EXT 0x200E +#define WGL_SUPPORT_GDI_EXT 0x200F +#define WGL_SUPPORT_OPENGL_EXT 0x2010 +#define WGL_DOUBLE_BUFFER_EXT 0x2011 +#define WGL_STEREO_EXT 0x2012 +#define WGL_PIXEL_TYPE_EXT 0x2013 +#define WGL_COLOR_BITS_EXT 0x2014 +#define WGL_RED_BITS_EXT 0x2015 +#define WGL_RED_SHIFT_EXT 0x2016 +#define WGL_GREEN_BITS_EXT 0x2017 +#define WGL_GREEN_SHIFT_EXT 0x2018 +#define WGL_BLUE_BITS_EXT 0x2019 +#define WGL_BLUE_SHIFT_EXT 0x201A +#define WGL_ALPHA_BITS_EXT 0x201B +#define WGL_ALPHA_SHIFT_EXT 0x201C +#define WGL_ACCUM_BITS_EXT 0x201D +#define WGL_ACCUM_RED_BITS_EXT 0x201E +#define WGL_ACCUM_GREEN_BITS_EXT 0x201F +#define WGL_ACCUM_BLUE_BITS_EXT 0x2020 +#define WGL_ACCUM_ALPHA_BITS_EXT 0x2021 +#define WGL_DEPTH_BITS_EXT 0x2022 +#define WGL_STENCIL_BITS_EXT 0x2023 +#define WGL_AUX_BUFFERS_EXT 0x2024 +#define WGL_NO_ACCELERATION_EXT 0x2025 +#define WGL_GENERIC_ACCELERATION_EXT 0x2026 +#define WGL_FULL_ACCELERATION_EXT 0x2027 +#define WGL_SWAP_EXCHANGE_EXT 0x2028 +#define WGL_SWAP_COPY_EXT 0x2029 +#define WGL_SWAP_UNDEFINED_EXT 0x202A +#define WGL_TYPE_RGBA_EXT 0x202B +#define WGL_TYPE_COLORINDEX_EXT 0x202C +#endif + +#ifndef WGL_EXT_pbuffer +#define WGL_DRAW_TO_PBUFFER_EXT 0x202D +#define WGL_MAX_PBUFFER_PIXELS_EXT 0x202E +#define WGL_MAX_PBUFFER_WIDTH_EXT 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_EXT 0x2030 +#define WGL_OPTIMAL_PBUFFER_WIDTH_EXT 0x2031 +#define WGL_OPTIMAL_PBUFFER_HEIGHT_EXT 0x2032 +#define WGL_PBUFFER_LARGEST_EXT 0x2033 +#define WGL_PBUFFER_WIDTH_EXT 0x2034 +#define WGL_PBUFFER_HEIGHT_EXT 0x2035 +#endif + +#ifndef WGL_EXT_depth_float +#define WGL_DEPTH_FLOAT_EXT 0x2040 +#endif + +#ifndef WGL_3DFX_multisample +#define WGL_SAMPLE_BUFFERS_3DFX 0x2060 +#define WGL_SAMPLES_3DFX 0x2061 +#endif + +#ifndef WGL_EXT_multisample +#define WGL_SAMPLE_BUFFERS_EXT 0x2041 +#define WGL_SAMPLES_EXT 0x2042 +#endif + +#ifndef WGL_I3D_digital_video_control +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_FRAMEBUFFER_I3D 0x2050 +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_VALUE_I3D 0x2051 +#define WGL_DIGITAL_VIDEO_CURSOR_INCLUDED_I3D 0x2052 +#define WGL_DIGITAL_VIDEO_GAMMA_CORRECTED_I3D 0x2053 +#endif + +#ifndef WGL_I3D_gamma +#define WGL_GAMMA_TABLE_SIZE_I3D 0x204E +#define WGL_GAMMA_EXCLUDE_DESKTOP_I3D 0x204F +#endif + +#ifndef WGL_I3D_genlock +#define WGL_GENLOCK_SOURCE_MULTIVIEW_I3D 0x2044 +#define WGL_GENLOCK_SOURCE_EXTENAL_SYNC_I3D 0x2045 +#define WGL_GENLOCK_SOURCE_EXTENAL_FIELD_I3D 0x2046 +#define WGL_GENLOCK_SOURCE_EXTENAL_TTL_I3D 0x2047 +#define WGL_GENLOCK_SOURCE_DIGITAL_SYNC_I3D 0x2048 +#define WGL_GENLOCK_SOURCE_DIGITAL_FIELD_I3D 0x2049 +#define WGL_GENLOCK_SOURCE_EDGE_FALLING_I3D 0x204A +#define WGL_GENLOCK_SOURCE_EDGE_RISING_I3D 0x204B +#define WGL_GENLOCK_SOURCE_EDGE_BOTH_I3D 0x204C +#endif + +#ifndef WGL_I3D_image_buffer +#define WGL_IMAGE_BUFFER_MIN_ACCESS_I3D 0x00000001 +#define WGL_IMAGE_BUFFER_LOCK_I3D 0x00000002 +#endif + +#ifndef WGL_I3D_swap_frame_lock +#endif + +#ifndef WGL_NV_render_depth_texture +#define WGL_BIND_TO_TEXTURE_DEPTH_NV 0x20A3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_DEPTH_NV 0x20A4 +#define WGL_DEPTH_TEXTURE_FORMAT_NV 0x20A5 +#define WGL_TEXTURE_DEPTH_COMPONENT_NV 0x20A6 +#define WGL_DEPTH_COMPONENT_NV 0x20A7 +#endif + +#ifndef WGL_NV_render_texture_rectangle +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 +#endif + +#ifndef WGL_NV_float_buffer +#define WGL_FLOAT_COMPONENTS_NV 0x20B0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 +#define WGL_TEXTURE_FLOAT_R_NV 0x20B5 +#define WGL_TEXTURE_FLOAT_RG_NV 0x20B6 +#define WGL_TEXTURE_FLOAT_RGB_NV 0x20B7 +#define WGL_TEXTURE_FLOAT_RGBA_NV 0x20B8 +#endif + + +/*************************************************************/ + +#ifndef WGL_ARB_pbuffer +DECLARE_HANDLE(HPBUFFERARB); +#endif +#ifndef WGL_EXT_pbuffer +DECLARE_HANDLE(HPBUFFEREXT); +#endif + +#ifndef WGL_ARB_buffer_region +#define WGL_ARB_buffer_region 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HANDLE WINAPI wglCreateBufferRegionARB (HDC, int, UINT); +extern VOID WINAPI wglDeleteBufferRegionARB (HANDLE); +extern BOOL WINAPI wglSaveBufferRegionARB (HANDLE, int, int, int, int); +extern BOOL WINAPI wglRestoreBufferRegionARB (HANDLE, int, int, int, int, int, int); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HANDLE (WINAPI * PFNWGLCREATEBUFFERREGIONARBPROC) (HDC hDC, int iLayerPlane, UINT uType); +typedef VOID (WINAPI * PFNWGLDELETEBUFFERREGIONARBPROC) (HANDLE hRegion); +typedef BOOL (WINAPI * PFNWGLSAVEBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height); +typedef BOOL (WINAPI * PFNWGLRESTOREBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); +#endif + +#ifndef WGL_ARB_multisample +#define WGL_ARB_multisample 1 +#endif + +#ifndef WGL_ARB_extensions_string +#define WGL_ARB_extensions_string 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern const char * WINAPI wglGetExtensionsStringARB (HDC); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc); +#endif + +#ifndef WGL_ARB_pixel_format +#define WGL_ARB_pixel_format 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetPixelFormatAttribivARB (HDC, int, int, UINT, const int *, int *); +extern BOOL WINAPI wglGetPixelFormatAttribfvARB (HDC, int, int, UINT, const int *, FLOAT *); +extern BOOL WINAPI wglChoosePixelFormatARB (HDC, const int *, const FLOAT *, UINT, int *, UINT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif + +#ifndef WGL_ARB_make_current_read +#define WGL_ARB_make_current_read 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglMakeContextCurrentARB (HDC, HDC, HGLRC); +extern HDC WINAPI wglGetCurrentReadDCARB (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTARBPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCARBPROC) (void); +#endif + +#ifndef WGL_ARB_pbuffer +#define WGL_ARB_pbuffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HPBUFFERARB WINAPI wglCreatePbufferARB (HDC, int, int, int, const int *); +extern HDC WINAPI wglGetPbufferDCARB (HPBUFFERARB); +extern int WINAPI wglReleasePbufferDCARB (HPBUFFERARB, HDC); +extern BOOL WINAPI wglDestroyPbufferARB (HPBUFFERARB); +extern BOOL WINAPI wglQueryPbufferARB (HPBUFFERARB, int, int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +#endif + +#ifndef WGL_ARB_render_texture +#define WGL_ARB_render_texture 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglBindTexImageARB (HPBUFFERARB, int); +extern BOOL WINAPI wglReleaseTexImageARB (HPBUFFERARB, int); +extern BOOL WINAPI wglSetPbufferAttribARB (HPBUFFERARB, const int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int *piAttribList); +#endif + +#ifndef WGL_EXT_display_color_table +#define WGL_EXT_display_color_table 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern GLboolean WINAPI wglCreateDisplayColorTableEXT (GLushort); +extern GLboolean WINAPI wglLoadDisplayColorTableEXT (const GLushort *, GLuint); +extern GLboolean WINAPI wglBindDisplayColorTableEXT (GLushort); +extern VOID WINAPI wglDestroyDisplayColorTableEXT (GLushort); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef GLboolean (WINAPI * PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef GLboolean (WINAPI * PFNWGLLOADDISPLAYCOLORTABLEEXTPROC) (const GLushort *table, GLuint length); +typedef GLboolean (WINAPI * PFNWGLBINDDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef VOID (WINAPI * PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC) (GLushort id); +#endif + +#ifndef WGL_EXT_extensions_string +#define WGL_EXT_extensions_string 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern const char * WINAPI wglGetExtensionsStringEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC) (void); +#endif + +#ifndef WGL_EXT_make_current_read +#define WGL_EXT_make_current_read 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglMakeContextCurrentEXT (HDC, HDC, HGLRC); +extern HDC WINAPI wglGetCurrentReadDCEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTEXTPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCEXTPROC) (void); +#endif + +#ifndef WGL_EXT_pbuffer +#define WGL_EXT_pbuffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern HPBUFFEREXT WINAPI wglCreatePbufferEXT (HDC, int, int, int, const int *); +extern HDC WINAPI wglGetPbufferDCEXT (HPBUFFEREXT); +extern int WINAPI wglReleasePbufferDCEXT (HPBUFFEREXT, HDC); +extern BOOL WINAPI wglDestroyPbufferEXT (HPBUFFEREXT); +extern BOOL WINAPI wglQueryPbufferEXT (HPBUFFEREXT, int, int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef HPBUFFEREXT (WINAPI * PFNWGLCREATEPBUFFEREXTPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); +#endif + +#ifndef WGL_EXT_pixel_format +#define WGL_EXT_pixel_format 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetPixelFormatAttribivEXT (HDC, int, int, UINT, int *, int *); +extern BOOL WINAPI wglGetPixelFormatAttribfvEXT (HDC, int, int, UINT, int *, FLOAT *); +extern BOOL WINAPI wglChoosePixelFormatEXT (HDC, const int *, const FLOAT *, UINT, int *, UINT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATEXTPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif + +#ifndef WGL_EXT_swap_control +#define WGL_EXT_swap_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglSwapIntervalEXT (int); +extern int WINAPI wglGetSwapIntervalEXT (void); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); +typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void); +#endif + +#ifndef WGL_EXT_depth_float +#define WGL_EXT_depth_float 1 +#endif + +#ifndef WGL_NV_vertex_array_range +#define WGL_NV_vertex_array_range 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern void* WINAPI wglAllocateMemoryNV (GLsizei, GLfloat, GLfloat, GLfloat); +extern void WINAPI wglFreeMemoryNV (void *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef void* (WINAPI * PFNWGLALLOCATEMEMORYNVPROC) (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); +typedef void (WINAPI * PFNWGLFREEMEMORYNVPROC) (void *pointer); +#endif + +#ifndef WGL_3DFX_multisample +#define WGL_3DFX_multisample 1 +#endif + +#ifndef WGL_EXT_multisample +#define WGL_EXT_multisample 1 +#endif + +#ifndef WGL_OML_sync_control +#define WGL_OML_sync_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetSyncValuesOML (HDC, INT64 *, INT64 *, INT64 *); +extern BOOL WINAPI wglGetMscRateOML (HDC, INT32 *, INT32 *); +extern INT64 WINAPI wglSwapBuffersMscOML (HDC, INT64, INT64, INT64); +extern INT64 WINAPI wglSwapLayerBuffersMscOML (HDC, int, INT64, INT64, INT64); +extern BOOL WINAPI wglWaitForMscOML (HDC, INT64, INT64, INT64, INT64 *, INT64 *, INT64 *); +extern BOOL WINAPI wglWaitForSbcOML (HDC, INT64, INT64 *, INT64 *, INT64 *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETSYNCVALUESOMLPROC) (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLGETMSCRATEOMLPROC) (HDC hdc, INT32 *numerator, INT32 *denominator); +typedef INT64 (WINAPI * PFNWGLSWAPBUFFERSMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef INT64 (WINAPI * PFNWGLSWAPLAYERBUFFERSMSCOMLPROC) (HDC hdc, int fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef BOOL (WINAPI * PFNWGLWAITFORMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLWAITFORSBCOMLPROC) (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); +#endif + +#ifndef WGL_I3D_digital_video_control +#define WGL_I3D_digital_video_control 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetDigitalVideoParametersI3D (HDC, int, int *); +extern BOOL WINAPI wglSetDigitalVideoParametersI3D (HDC, int, const int *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +#endif + +#ifndef WGL_I3D_gamma +#define WGL_I3D_gamma 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetGammaTableParametersI3D (HDC, int, int *); +extern BOOL WINAPI wglSetGammaTableParametersI3D (HDC, int, const int *); +extern BOOL WINAPI wglGetGammaTableI3D (HDC, int, USHORT *, USHORT *, USHORT *); +extern BOOL WINAPI wglSetGammaTableI3D (HDC, int, const USHORT *, const USHORT *, const USHORT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); +#endif + +#ifndef WGL_I3D_genlock +#define WGL_I3D_genlock 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglEnableGenlockI3D (HDC); +extern BOOL WINAPI wglDisableGenlockI3D (HDC); +extern BOOL WINAPI wglIsEnabledGenlockI3D (HDC, BOOL *); +extern BOOL WINAPI wglGenlockSourceI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSourceI3D (HDC, UINT *); +extern BOOL WINAPI wglGenlockSourceEdgeI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSourceEdgeI3D (HDC, UINT *); +extern BOOL WINAPI wglGenlockSampleRateI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSampleRateI3D (HDC, UINT *); +extern BOOL WINAPI wglGenlockSourceDelayI3D (HDC, UINT); +extern BOOL WINAPI wglGetGenlockSourceDelayI3D (HDC, UINT *); +extern BOOL WINAPI wglQueryGenlockMaxSourceDelayI3D (HDC, UINT *, UINT *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLENABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLDISABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLISENABLEDGENLOCKI3DPROC) (HDC hDC, BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEI3DPROC) (HDC hDC, UINT uSource); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEI3DPROC) (HDC hDC, UINT *uSource); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT uEdge); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT *uEdge); +typedef BOOL (WINAPI * PFNWGLGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT uRate); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT *uRate); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT uDelay); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT *uDelay); +typedef BOOL (WINAPI * PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC) (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); +#endif + +#ifndef WGL_I3D_image_buffer +#define WGL_I3D_image_buffer 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern LPVOID WINAPI wglCreateImageBufferI3D (HDC, DWORD, UINT); +extern BOOL WINAPI wglDestroyImageBufferI3D (HDC, LPVOID); +extern BOOL WINAPI wglAssociateImageBufferEventsI3D (HDC, const HANDLE *, const LPVOID *, const DWORD *, UINT); +extern BOOL WINAPI wglReleaseImageBufferEventsI3D (HDC, const LPVOID *, UINT); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef LPVOID (WINAPI * PFNWGLCREATEIMAGEBUFFERI3DPROC) (HDC hDC, DWORD dwSize, UINT uFlags); +typedef BOOL (WINAPI * PFNWGLDESTROYIMAGEBUFFERI3DPROC) (HDC hDC, LPVOID pAddress); +typedef BOOL (WINAPI * PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); +typedef BOOL (WINAPI * PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const LPVOID *pAddress, UINT count); +#endif + +#ifndef WGL_I3D_swap_frame_lock +#define WGL_I3D_swap_frame_lock 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglEnableFrameLockI3D (void); +extern BOOL WINAPI wglDisableFrameLockI3D (void); +extern BOOL WINAPI wglIsEnabledFrameLockI3D (BOOL *); +extern BOOL WINAPI wglQueryFrameLockMasterI3D (BOOL *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLENABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLDISABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLISENABLEDFRAMELOCKI3DPROC) (BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMELOCKMASTERI3DPROC) (BOOL *pFlag); +#endif + +#ifndef WGL_I3D_swap_frame_usage +#define WGL_I3D_swap_frame_usage 1 +#ifdef WGL_WGLEXT_PROTOTYPES +extern BOOL WINAPI wglGetFrameUsageI3D (float *); +extern BOOL WINAPI wglBeginFrameTrackingI3D (void); +extern BOOL WINAPI wglEndFrameTrackingI3D (void); +extern BOOL WINAPI wglQueryFrameTrackingI3D (DWORD *, DWORD *, float *); +#endif /* WGL_WGLEXT_PROTOTYPES */ +typedef BOOL (WINAPI * PFNWGLGETFRAMEUSAGEI3DPROC) (float *pUsage); +typedef BOOL (WINAPI * PFNWGLBEGINFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLENDFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMETRACKINGI3DPROC) (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/sound/default/snd_cache.cpp b/src/sound/default/snd_cache.cpp new file mode 100644 index 0000000..b7b8c91 --- /dev/null +++ b/src/sound/default/snd_cache.cpp @@ -0,0 +1,779 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +#ifdef Q4_RECONSTRUCTED_SOUND + +idSoundSample::idSoundSample() : + nChannels( 0 ), nSamplesPerSec( 0 ), objectSize( 0 ), objectMemSize( 0 ), + nonCacheData( NULL ), openalBuffer( 0 ), hardwareBuffer( false ), + wFormatTag( 0 ), wBitsPerSample( 0 ), nAvgBytesPerSec( 0 ), isOgg( false ) { +} + +idSoundSample::~idSoundSample() { PurgeSoundSample(); } +const byte *idSoundSample::GetSampleData() const { return nonCacheData; } +int idSoundSample::GetNumChannels() const { return nChannels; } +int idSoundSample::GetNumSamples() const { return objectSize; } +int idSoundSample::GetSampleRate() const { return nSamplesPerSec; } +int idSoundSample::GetMemoryUsed() const { return objectMemSize; } +int idSoundSample::GetDurationMS() const { return nSamplesPerSec > 0 ? (int)( (__int64)objectSize * 1000 / nSamplesPerSec ) : 0; } +float idSoundSample::GetDuration() const { return GetDurationMS() * 0.001f; } +bool idSoundSample::IsOgg() const { return isOgg; } +int idSoundSample::LengthIn44kHzSamples() const { return nSamplesPerSec > 0 ? (int)( (__int64)objectSize * PRIMARYFREQ / nSamplesPerSec ) : 0; } + +void idSoundSample::PurgeSoundSample() { + if ( nonCacheData != NULL ) Mem_Free( nonCacheData ); + nonCacheData = NULL; + objectSize = objectMemSize = 0; + purged = true; +} + +void idSoundSample::Load( int langIndex ) { + purged = false; + levelLoadReferenced = true; +} + +void idSoundSample::Expand( bool force ) { isOgg = false; } +void idSoundSample::MakeDefault() { defaultSound = true; purged = false; nChannels = 1; nSamplesPerSec = PRIMARYFREQ; } +void idSoundSample::CheckForDownSample() {} +void idSoundSample::Reload( bool force ) { if ( force || purged ) Load(); } +unsigned int idSoundSample::GetNewTimeStamp() const { return timestamp; } + +bool idSoundSample::FetchFromCache( int offset, const byte **output, int *position, int *size, bool allowIO ) { + if ( offset < 0 || nonCacheData == NULL || offset >= objectMemSize ) return false; + if ( output ) *output = nonCacheData + offset; + if ( position ) *position = offset; + if ( size ) *size = objectMemSize - offset; + return true; +} + +idSoundCache::idSoundCache() : insideLevelLoad( false ) {} +idSoundCache::~idSoundCache() { listCache.DeleteContents( true ); } + +idSoundSample *idSoundCache::FindSample( const idStr &filename ) { + for ( int i = 0; i < listCache.Num(); ++i ) if ( listCache[i] != NULL && !idStr::Icmp( listCache[i]->name, filename ) ) { listCache[i]->levelLoadReferenced = true; return listCache[i]; } + idSoundSample *sample = new idSoundSample; + sample->name = filename; + sample->Load(); + listCache.Append( sample ); + return sample; +} + +const idSoundSample *idSoundCache::GetObjectA( int index ) const { return index >= 0 && index < listCache.Num() ? listCache[index] : NULL; } +void idSoundCache::ReloadSounds( bool force ) { for ( int i = 0; i < listCache.Num(); ++i ) if ( listCache[i] ) listCache[i]->Reload( force ); } +void idSoundCache::PurgeSounds( bool force ) { for ( int i = 0; i < listCache.Num(); ++i ) if ( listCache[i] && ( force || !listCache[i]->levelLoadReferenced ) ) listCache[i]->PurgeSoundSample(); } +void idSoundCache::BeginLevelLoad( const char *mapName ) { insideLevelLoad = true; for ( int i = 0; i < listCache.Num(); ++i ) if ( listCache[i] ) listCache[i]->levelLoadReferenced = false; } +void idSoundCache::EndLevelLoad( const char *mapName ) { insideLevelLoad = false; PurgeSounds( false ); } +void idSoundCache::PrintMemInfo( MemInfo *mi ) {} +size_t idSoundCache::ListSoundSummary() { size_t total = 0; for ( int i = 0; i < listCache.Num(); ++i ) if ( listCache[i] ) total += listCache[i]->GetMemoryUsed(); return total; } + +void idSoundSystemLocal::CleanCache() { if ( soundCache ) soundCache->PurgeSounds( false ); } +void *idSoundSystemLocal::AllocSoundSample( int size ) { return size > 0 ? Mem_Alloc( size, MA_SOUND ) : NULL; } +void idSoundSystemLocal::FreeSoundSample( const byte *address ) { if ( address ) Mem_Free( const_cast( address ) ); } +int idSoundSystemLocal::GetCacheMemoryUsage() { return soundCache ? (int)soundCache->ListSoundSummary() : 0; } + +#else + +#define USE_SOUND_CACHE_ALLOCATOR + +#ifdef USE_SOUND_CACHE_ALLOCATOR +static idDynamicBlockAlloc soundCacheAllocator; +#else +static idDynamicAlloc soundCacheAllocator; +#endif + + +/* +=================== +idSoundCache::idSoundCache() +=================== +*/ +idSoundCache::idSoundCache() { + soundCacheAllocator.Init(); + soundCacheAllocator.SetLockMemory( true ); + listCache.AssureSize( 1024, NULL ); + listCache.SetGranularity( 256 ); + insideLevelLoad = false; +} + +/* +=================== +idSoundCache::~idSoundCache() +=================== +*/ +idSoundCache::~idSoundCache() { + listCache.DeleteContents( true ); + soundCacheAllocator.Shutdown(); +} + +/* +=================== +idSoundCache::::GetObject + +returns a single cached object pointer +=================== +*/ +const idSoundSample* idSoundCache::GetObject( const int index ) const { + if (index<0 || index>listCache.Num()) { + return NULL; + } + return listCache[index]; +} + +/* +=================== +idSoundCache::FindSound + +Adds a sound object to the cache and returns a handle for it. +=================== +*/ +idSoundSample *idSoundCache::FindSound( const idStr& filename, bool loadOnDemandOnly ) { + idStr fname; + + fname = filename; + fname.BackSlashesToSlashes(); + fname.ToLower(); + + declManager->MediaPrint( "%s\n", fname.c_str() ); + + // check to see if object is already in cache + for( int i = 0; i < listCache.Num(); i++ ) { + idSoundSample *def = listCache[i]; + if ( def && def->name == fname ) { + def->levelLoadReferenced = true; + if ( def->purged && !loadOnDemandOnly ) { + def->Load(); + } + return def; + } + } + + // create a new entry + idSoundSample *def = new idSoundSample; + + int shandle = listCache.FindNull(); + if ( shandle != -1 ) { + listCache[shandle] = def; + } else { + shandle = listCache.Append( def ); + } + + def->name = fname; + def->levelLoadReferenced = true; + def->onDemand = loadOnDemandOnly; + def->purged = true; + + if ( !loadOnDemandOnly ) { + // this may make it a default sound if it can't be loaded + def->Load(); + } + + return def; +} + +/* +=================== +idSoundCache::ReloadSounds + +Completely nukes the current cache +=================== +*/ +void idSoundCache::ReloadSounds( bool force ) { + int i; + + for( i = 0; i < listCache.Num(); i++ ) { + idSoundSample *def = listCache[i]; + if ( def ) { + def->Reload( force ); + } + } +} + +/* +==================== +BeginLevelLoad + +Mark all file based images as currently unused, +but don't free anything. Calls to ImageFromFile() will +either mark the image as used, or create a new image without +loading the actual data. +==================== +*/ +void idSoundCache::BeginLevelLoad() { + insideLevelLoad = true; + + for ( int i = 0 ; i < listCache.Num() ; i++ ) { + idSoundSample *sample = listCache[ i ]; + if ( !sample ) { + continue; + } + + if ( com_purgeAll.GetBool() ) { + sample->PurgeSoundSample(); + } + + sample->levelLoadReferenced = false; + } + + soundCacheAllocator.FreeEmptyBaseBlocks(); +} + +/* +==================== +EndLevelLoad + +Free all samples marked as unused +==================== +*/ +void idSoundCache::EndLevelLoad() { + int useCount, purgeCount; + common->Printf( "----- idSoundCache::EndLevelLoad -----\n" ); + + insideLevelLoad = false; + + // purge the ones we don't need + useCount = 0; + purgeCount = 0; + for ( int i = 0 ; i < listCache.Num() ; i++ ) { + idSoundSample *sample = listCache[ i ]; + if ( !sample ) { + continue; + } + if ( sample->purged ) { + continue; + } + if ( !sample->levelLoadReferenced ) { +// common->Printf( "Purging %s\n", sample->name.c_str() ); + purgeCount += sample->objectMemSize; + sample->PurgeSoundSample(); + } else { + useCount += sample->objectMemSize; + } + } + + soundCacheAllocator.FreeEmptyBaseBlocks(); + + common->Printf( "%5ik referenced\n", useCount / 1024 ); + common->Printf( "%5ik purged\n", purgeCount / 1024 ); + common->Printf( "----------------------------------------\n" ); +} + +/* +=================== +idSoundCache::PrintMemInfo +=================== +*/ +void idSoundCache::PrintMemInfo( MemInfo_t *mi ) { + int i, j, num = 0, total = 0; + int *sortIndex; + idFile *f; + + f = fileSystem->OpenFileWrite( mi->filebase + "_sounds.txt" ); + if ( !f ) { + return; + } + + // count + for ( i = 0; i < listCache.Num(); i++, num++ ) { + if ( !listCache[i] ) { + break; + } + } + + // sort first + sortIndex = new int[num]; + + for ( i = 0; i < num; i++ ) { + sortIndex[i] = i; + } + + for ( i = 0; i < num - 1; i++ ) { + for ( j = i + 1; j < num; j++ ) { + if ( listCache[sortIndex[i]]->objectMemSize < listCache[sortIndex[j]]->objectMemSize ) { + int temp = sortIndex[i]; + sortIndex[i] = sortIndex[j]; + sortIndex[j] = temp; + } + } + } + + // print next + for ( i = 0; i < num; i++ ) { + idSoundSample *sample = listCache[sortIndex[i]]; + + // this is strange + if ( !sample ) { + continue; + } + + total += sample->objectMemSize; + f->Printf( "%s %s\n", idStr::FormatNumber( sample->objectMemSize ).c_str(), sample->name.c_str() ); + } + + mi->soundAssetsTotal = total; + + f->Printf( "\nTotal sound bytes allocated: %s\n", idStr::FormatNumber( total ).c_str() ); + fileSystem->CloseFile( f ); +} + + +/* +========================================================================== + +idSoundSample + +========================================================================== +*/ + +/* +=================== +idSoundSample::idSoundSample +=================== +*/ +idSoundSample::idSoundSample() { + memset( &objectInfo, 0, sizeof(waveformatex_t) ); + objectSize = 0; + objectMemSize = 0; + nonCacheData = NULL; + amplitudeData = NULL; + openalBuffer = NULL; + hardwareBuffer = false; + defaultSound = false; + onDemand = false; + purged = false; + levelLoadReferenced = false; +} + +/* +=================== +idSoundSample::~idSoundSample +=================== +*/ +idSoundSample::~idSoundSample() { + PurgeSoundSample(); +} + +/* +=================== +idSoundSample::LengthIn44kHzSamples +=================== +*/ +int idSoundSample::LengthIn44kHzSamples( void ) const { + // objectSize is samples + if ( objectInfo.nSamplesPerSec == 11025 ) { + return objectSize << 2; + } else if ( objectInfo.nSamplesPerSec == 22050 ) { + return objectSize << 1; + } else { + return objectSize << 0; + } +} + +/* +=================== +idSoundSample::MakeDefault +=================== +*/ +void idSoundSample::MakeDefault( void ) { + int i; + float v; + int sample; + + memset( &objectInfo, 0, sizeof( objectInfo ) ); + + objectInfo.nChannels = 1; + objectInfo.wBitsPerSample = 16; + objectInfo.nSamplesPerSec = 44100; + + objectSize = MIXBUFFER_SAMPLES * 2; + objectMemSize = objectSize * sizeof( short ); + + nonCacheData = (byte *)soundCacheAllocator.Alloc( objectMemSize ); + + short *ncd = (short *)nonCacheData; + + for ( i = 0; i < MIXBUFFER_SAMPLES; i ++ ) { + v = sin( idMath::PI * 2 * i / 64 ); + sample = v * 0x4000; + ncd[i*2+0] = sample; + ncd[i*2+1] = sample; + } + + if ( idSoundSystemLocal::useOpenAL ) { + alGetError(); + alGenBuffers( 1, &openalBuffer ); + if ( alGetError() != AL_NO_ERROR ) { + common->Error( "idSoundCache: error generating OpenAL hardware buffer" ); + } + + alGetError(); + alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, nonCacheData, objectMemSize, objectInfo.nSamplesPerSec ); + if ( alGetError() != AL_NO_ERROR ) { + common->Error( "idSoundCache: error loading data into OpenAL hardware buffer" ); + } else { + hardwareBuffer = true; + } + } + + defaultSound = true; +} + +/* +=================== +idSoundSample::CheckForDownSample +=================== +*/ +void idSoundSample::CheckForDownSample( void ) { + if ( !idSoundSystemLocal::s_force22kHz.GetBool() ) { + return; + } + if ( objectInfo.wFormatTag != WAVE_FORMAT_TAG_PCM || objectInfo.nSamplesPerSec != 44100 ) { + return; + } + int shortSamples = objectSize >> 1; + short *converted = (short *)soundCacheAllocator.Alloc( shortSamples * sizeof( short ) ); + + if ( objectInfo.nChannels == 1 ) { + for ( int i = 0; i < shortSamples; i++ ) { + converted[i] = ((short *)nonCacheData)[i*2]; + } + } else { + for ( int i = 0; i < shortSamples; i += 2 ) { + converted[i+0] = ((short *)nonCacheData)[i*2+0]; + converted[i+1] = ((short *)nonCacheData)[i*2+1]; + } + } + soundCacheAllocator.Free( nonCacheData ); + nonCacheData = (byte *)converted; + objectSize >>= 1; + objectMemSize >>= 1; + objectInfo.nAvgBytesPerSec >>= 1; + objectInfo.nSamplesPerSec >>= 1; +} + +/* +=================== +idSoundSample::GetNewTimeStamp +=================== +*/ +ID_TIME_T idSoundSample::GetNewTimeStamp( void ) const { + ID_TIME_T timestamp; + + fileSystem->ReadFile( name, NULL, ×tamp ); + if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) { + idStr oggName = name; + oggName.SetFileExtension( ".ogg" ); + fileSystem->ReadFile( oggName, NULL, ×tamp ); + } + return timestamp; +} + +/* +=================== +idSoundSample::Load + +Loads based on name, possibly doing a MakeDefault if necessary +=================== +*/ +void idSoundSample::Load( void ) { + defaultSound = false; + purged = false; + hardwareBuffer = false; + + timestamp = GetNewTimeStamp(); + + if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) { + common->Warning( "Couldn't load sound '%s' using default", name.c_str() ); + MakeDefault(); + return; + } + + // load it + idWaveFile fh; + waveformatex_t info; + + if ( fh.Open( name, &info ) == -1 ) { + common->Warning( "Couldn't load sound '%s' using default", name.c_str() ); + MakeDefault(); + return; + } + + if ( info.nChannels != 1 && info.nChannels != 2 ) { + common->Warning( "idSoundSample: %s has %i channels, using default", name.c_str(), info.nChannels ); + fh.Close(); + MakeDefault(); + return; + } + + if ( info.wBitsPerSample != 16 ) { + common->Warning( "idSoundSample: %s is %dbits, expected 16bits using default", name.c_str(), info.wBitsPerSample ); + fh.Close(); + MakeDefault(); + return; + } + + if ( info.nSamplesPerSec != 44100 && info.nSamplesPerSec != 22050 && info.nSamplesPerSec != 11025 ) { + common->Warning( "idSoundCache: %s is %dHz, expected 11025, 22050 or 44100 Hz. Using default", name.c_str(), info.nSamplesPerSec ); + fh.Close(); + MakeDefault(); + return; + } + + objectInfo = info; + objectSize = fh.GetOutputSize(); + objectMemSize = fh.GetMemorySize(); + + nonCacheData = (byte *)soundCacheAllocator.Alloc( objectMemSize ); + fh.Read( nonCacheData, objectMemSize, NULL ); + + // optionally convert it to 22kHz to save memory + CheckForDownSample(); + + // create hardware audio buffers + if ( idSoundSystemLocal::useOpenAL ) { + // PCM loads directly + if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_PCM ) { + alGetError(); + alGenBuffers( 1, &openalBuffer ); + if ( alGetError() != AL_NO_ERROR ) + common->Error( "idSoundCache: error generating OpenAL hardware buffer" ); + if ( alIsBuffer( openalBuffer ) ) { + alGetError(); + alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, nonCacheData, objectMemSize, objectInfo.nSamplesPerSec ); + if ( alGetError() != AL_NO_ERROR ) { + common->Error( "idSoundCache: error loading data into OpenAL hardware buffer" ); + } else { + // Compute amplitude block size + int blockSize = 512 * objectInfo.nSamplesPerSec / 44100 ; + + // Allocate amplitude data array + amplitudeData = (byte *)soundCacheAllocator.Alloc( ( objectSize / blockSize + 1 ) * 2 * sizeof( short) ); + + // Creating array of min/max amplitude pairs per blockSize samples + int i; + for ( i = 0; i < objectSize; i+=blockSize ) { + short min = 32767; + short max = -32768; + + int j; + for ( j = 0; j < Min( objectSize - i, blockSize ); j++ ) { + min = ((short *)nonCacheData)[ i + j ] < min ? ((short *)nonCacheData)[ i + j ] : min; + max = ((short *)nonCacheData)[ i + j ] > max ? ((short *)nonCacheData)[ i + j ] : max; + } + + ((short *)amplitudeData)[ ( i / blockSize ) * 2 ] = min; + ((short *)amplitudeData)[ ( i / blockSize ) * 2 + 1 ] = max; + } + + hardwareBuffer = true; + } + } + } + + // OGG decompressed at load time (when smaller than s_decompressionLimit seconds, 6 seconds by default) + if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) { +#if defined(MACOS_X) + if ( ( objectSize < ( ( int ) objectInfo.nSamplesPerSec * idSoundSystemLocal::s_decompressionLimit.GetInteger() ) ) ) { +#else + if ( ( alIsExtensionPresent( ID_ALCHAR "EAX-RAM" ) == AL_TRUE ) && ( objectSize < ( ( int ) objectInfo.nSamplesPerSec * idSoundSystemLocal::s_decompressionLimit.GetInteger() ) ) ) { +#endif + alGetError(); + alGenBuffers( 1, &openalBuffer ); + if ( alGetError() != AL_NO_ERROR ) + common->Error( "idSoundCache: error generating OpenAL hardware buffer" ); + if ( alIsBuffer( openalBuffer ) ) { + idSampleDecoder *decoder = idSampleDecoder::Alloc(); + float *destData = (float *)soundCacheAllocator.Alloc( ( LengthIn44kHzSamples() + 1 ) * sizeof( float ) ); + + // Decoder *always* outputs 44 kHz data + decoder->Decode( this, 0, LengthIn44kHzSamples(), destData ); + + // Downsample back to original frequency (save memory) + if ( objectInfo.nSamplesPerSec == 11025 ) { + for ( int i = 0; i < objectSize; i++ ) { + if ( destData[i*4] < -32768.0f ) + ((short *)destData)[i] = -32768; + else if ( destData[i*4] > 32767.0f ) + ((short *)destData)[i] = 32767; + else + ((short *)destData)[i] = idMath::FtoiFast( destData[i*4] ); + } + } else if ( objectInfo.nSamplesPerSec == 22050 ) { + for ( int i = 0; i < objectSize; i++ ) { + if ( destData[i*2] < -32768.0f ) + ((short *)destData)[i] = -32768; + else if ( destData[i*2] > 32767.0f ) + ((short *)destData)[i] = 32767; + else + ((short *)destData)[i] = idMath::FtoiFast( destData[i*2] ); + } + } else { + for ( int i = 0; i < objectSize; i++ ) { + if ( destData[i] < -32768.0f ) + ((short *)destData)[i] = -32768; + else if ( destData[i] > 32767.0f ) + ((short *)destData)[i] = 32767; + else + ((short *)destData)[i] = idMath::FtoiFast( destData[i] ); + } + } + + alGetError(); + alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, destData, objectSize * sizeof( short ), objectInfo.nSamplesPerSec ); + if ( alGetError() != AL_NO_ERROR ) + common->Error( "idSoundCache: error loading data into OpenAL hardware buffer" ); + else { + // Compute amplitude block size + int blockSize = 512 * objectInfo.nSamplesPerSec / 44100 ; + + // Allocate amplitude data array + amplitudeData = (byte *)soundCacheAllocator.Alloc( ( objectSize / blockSize + 1 ) * 2 * sizeof( short ) ); + + // Creating array of min/max amplitude pairs per blockSize samples + int i; + for ( i = 0; i < objectSize; i+=blockSize ) { + short min = 32767; + short max = -32768; + + int j; + for ( j = 0; j < Min( objectSize - i, blockSize ); j++ ) { + min = ((short *)destData)[ i + j ] < min ? ((short *)destData)[ i + j ] : min; + max = ((short *)destData)[ i + j ] > max ? ((short *)destData)[ i + j ] : max; + } + + ((short *)amplitudeData)[ ( i / blockSize ) * 2 ] = min; + ((short *)amplitudeData)[ ( i / blockSize ) * 2 + 1 ] = max; + } + + hardwareBuffer = true; + } + + soundCacheAllocator.Free( (byte *)destData ); + idSampleDecoder::Free( decoder ); + } + } + } + + // Free memory if sample was loaded into hardware + if ( hardwareBuffer ) { + soundCacheAllocator.Free( nonCacheData ); + nonCacheData = NULL; + } + } + + fh.Close(); +} + +/* +=================== +idSoundSample::PurgeSoundSample +=================== +*/ +void idSoundSample::PurgeSoundSample() { + purged = true; + + if ( hardwareBuffer && idSoundSystemLocal::useOpenAL ) { + alGetError(); + alDeleteBuffers( 1, &openalBuffer ); + if ( alGetError() != AL_NO_ERROR ) { + common->Error( "idSoundCache: error unloading data from OpenAL hardware buffer" ); + } else { + openalBuffer = 0; + hardwareBuffer = false; + } + } + + if ( amplitudeData ) { + soundCacheAllocator.Free( amplitudeData ); + amplitudeData = NULL; + } + + if ( nonCacheData ) { + soundCacheAllocator.Free( nonCacheData ); + nonCacheData = NULL; + } +} + +/* +=================== +idSoundSample::Reload +=================== +*/ +void idSoundSample::Reload( bool force ) { + if ( !force ) { + ID_TIME_T newTimestamp; + + // check the timestamp + newTimestamp = GetNewTimeStamp(); + + if ( newTimestamp == FILE_NOT_FOUND_TIMESTAMP ) { + if ( !defaultSound ) { + common->Warning( "Couldn't load sound '%s' using default", name.c_str() ); + MakeDefault(); + } + return; + } + if ( newTimestamp == timestamp ) { + return; // don't need to reload it + } + } + + common->Printf( "reloading %s\n", name.c_str() ); + PurgeSoundSample(); + Load(); +} + +/* +=================== +idSoundSample::FetchFromCache + +Returns true on success. +=================== +*/ +bool idSoundSample::FetchFromCache( int offset, const byte **output, int *position, int *size, const bool allowIO ) { + offset &= 0xfffffffe; + + if ( objectSize == 0 || offset < 0 || offset > objectSize * (int)sizeof( short ) || !nonCacheData ) { + return false; + } + + if ( output ) { + *output = nonCacheData + offset; + } + if ( position ) { + *position = 0; + } + if ( size ) { + *size = objectSize * sizeof( short ) - offset; + if ( *size > SCACHE_SIZE ) { + *size = SCACHE_SIZE; + } + } + return true; +} + +#endif // Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/default/snd_common.cpp b/src/sound/default/snd_common.cpp new file mode 100644 index 0000000..0ca5ca9 --- /dev/null +++ b/src/sound/default/snd_common.cpp @@ -0,0 +1,41 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* +=========================================================================== + + Quake 4 common sound-sample implementation. + Compiland ownership recovered from quake4.pdb: snd_common.obj. + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +rvCommonSample::rvCommonSample() : + timestamp( 0 ), defaultSound( false ), purged( true ), levelLoadReferenced( false ) { +} diff --git a/src/sound/default/snd_decoder.cpp b/src/sound/default/snd_decoder.cpp new file mode 100644 index 0000000..58df820 --- /dev/null +++ b/src/sound/default/snd_decoder.cpp @@ -0,0 +1,596 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +#ifdef Q4_RECONSTRUCTED_SOUND + +// The bundled Ogg/Vorbis C objects are configured to route their allocations +// through these engine-owned entry points. Decoding itself remains disabled +// until the recovered streaming state has been validated, but the allocation +// ABI must still be present in the executable. +extern "C" { + void *_decoder_malloc( size_t size ) { + return malloc( size ); + } + + void *_decoder_calloc( size_t num, size_t size ) { + return calloc( num, size ); + } + + void *_decoder_realloc( void *memblock, size_t size ) { + return realloc( memblock, size ); + } + + void _decoder_free( void *memblock ) { + free( memblock ); + } +} + +#else +#include "OggVorbis/vorbis/codec.h" +#include "OggVorbis/vorbis/vorbisfile.h" + + +/* +=================================================================================== + + Thread safe decoder memory allocator. + + Each OggVorbis decoder consumes about 150kB of memory. + +=================================================================================== +*/ + +idDynamicBlockAlloc decoderMemoryAllocator; + +const int MIN_OGGVORBIS_MEMORY = 768 * 1024; + +extern "C" { + void *_decoder_malloc( size_t size ); + void *_decoder_calloc( size_t num, size_t size ); + void *_decoder_realloc( void *memblock, size_t size ); + void _decoder_free( void *memblock ); +} + +void *_decoder_malloc( size_t size ) { + void *ptr = decoderMemoryAllocator.Alloc( size ); + assert( size == 0 || ptr != NULL ); + return ptr; +} + +void *_decoder_calloc( size_t num, size_t size ) { + void *ptr = decoderMemoryAllocator.Alloc( num * size ); + assert( ( num * size ) == 0 || ptr != NULL ); + memset( ptr, 0, num * size ); + return ptr; +} + +void *_decoder_realloc( void *memblock, size_t size ) { + void *ptr = decoderMemoryAllocator.Resize( (byte *)memblock, size ); + assert( size == 0 || ptr != NULL ); + return ptr; +} + +void _decoder_free( void *memblock ) { + decoderMemoryAllocator.Free( (byte *)memblock ); +} + + +/* +=================================================================================== + + OggVorbis file loading/decoding. + +=================================================================================== +*/ + +/* +==================== +FS_ReadOGG +==================== +*/ +size_t FS_ReadOGG( void *dest, size_t size1, size_t size2, void *fh ) { + idFile *f = reinterpret_cast(fh); + return f->Read( dest, size1 * size2 ); +} + +/* +==================== +FS_SeekOGG +==================== +*/ +int FS_SeekOGG( void *fh, ogg_int64_t to, int type ) { + fsOrigin_t retype = FS_SEEK_SET; + + if ( type == SEEK_CUR ) { + retype = FS_SEEK_CUR; + } else if ( type == SEEK_END ) { + retype = FS_SEEK_END; + } else if ( type == SEEK_SET ) { + retype = FS_SEEK_SET; + } else { + common->FatalError( "fs_seekOGG: seek without type\n" ); + } + idFile *f = reinterpret_cast(fh); + return f->Seek( to, retype ); +} + +/* +==================== +FS_CloseOGG +==================== +*/ +int FS_CloseOGG( void *fh ) { + return 0; +} + +/* +==================== +FS_TellOGG +==================== +*/ +long FS_TellOGG( void *fh ) { + idFile *f = reinterpret_cast(fh); + return f->Tell(); +} + +/* +==================== +ov_openFile +==================== +*/ +int ov_openFile( idFile *f, OggVorbis_File *vf ) { + ov_callbacks callbacks; + + memset( vf, 0, sizeof( OggVorbis_File ) ); + + callbacks.read_func = FS_ReadOGG; + callbacks.seek_func = FS_SeekOGG; + callbacks.close_func = FS_CloseOGG; + callbacks.tell_func = FS_TellOGG; + return ov_open_callbacks((void *)f, vf, NULL, -1, callbacks); +} + +/* +==================== +idWaveFile::OpenOGG +==================== +*/ +int idWaveFile::OpenOGG( const char* strFileName, waveformatex_t *pwfx ) { + OggVorbis_File *ov; + + memset( pwfx, 0, sizeof( waveformatex_t ) ); + + mhmmio = fileSystem->OpenFileRead( strFileName ); + if ( !mhmmio ) { + return -1; + } + + Sys_EnterCriticalSection( CRITICAL_SECTION_ONE ); + + ov = new OggVorbis_File; + + if( ov_openFile( mhmmio, ov ) < 0 ) { + delete ov; + Sys_LeaveCriticalSection( CRITICAL_SECTION_ONE ); + fileSystem->CloseFile( mhmmio ); + mhmmio = NULL; + return -1; + } + + mfileTime = mhmmio->Timestamp(); + + vorbis_info *vi = ov_info( ov, -1 ); + + mpwfx.Format.nSamplesPerSec = vi->rate; + mpwfx.Format.nChannels = vi->channels; + mpwfx.Format.wBitsPerSample = sizeof(short) * 8; + mdwSize = ov_pcm_total( ov, -1 ) * vi->channels; // pcm samples * num channels + mbIsReadingFromMemory = false; + + if ( idSoundSystemLocal::s_realTimeDecoding.GetBool() ) { + + ov_clear( ov ); + fileSystem->CloseFile( mhmmio ); + mhmmio = NULL; + delete ov; + + mpwfx.Format.wFormatTag = WAVE_FORMAT_TAG_OGG; + mhmmio = fileSystem->OpenFileRead( strFileName ); + mMemSize = mhmmio->Length(); + + } else { + + ogg = ov; + + mpwfx.Format.wFormatTag = WAVE_FORMAT_TAG_PCM; + mMemSize = mdwSize * sizeof( short ); + } + + memcpy( pwfx, &mpwfx, sizeof( waveformatex_t ) ); + + Sys_LeaveCriticalSection( CRITICAL_SECTION_ONE ); + + isOgg = true; + + return 0; +} + +/* +==================== +idWaveFile::ReadOGG +==================== +*/ +int idWaveFile::ReadOGG( byte* pBuffer, int dwSizeToRead, int *pdwSizeRead ) { + int total = dwSizeToRead; + char *bufferPtr = (char *)pBuffer; + OggVorbis_File *ov = (OggVorbis_File *) ogg; + + do { + int ret = ov_read( ov, bufferPtr, total >= 4096 ? 4096 : total, Swap_IsBigEndian(), 2, 1, &ov->stream ); + if ( ret == 0 ) { + break; + } + if ( ret < 0 ) { + return -1; + } + bufferPtr += ret; + total -= ret; + } while( total > 0 ); + + dwSizeToRead = (byte *)bufferPtr - pBuffer; + + if ( pdwSizeRead != NULL ) { + *pdwSizeRead = dwSizeToRead; + } + + return dwSizeToRead; +} + +/* +==================== +idWaveFile::CloseOGG +==================== +*/ +int idWaveFile::CloseOGG( void ) { + OggVorbis_File *ov = (OggVorbis_File *) ogg; + if ( ov != NULL ) { + Sys_EnterCriticalSection( CRITICAL_SECTION_ONE ); + ov_clear( ov ); + delete ov; + Sys_LeaveCriticalSection( CRITICAL_SECTION_ONE ); + fileSystem->CloseFile( mhmmio ); + mhmmio = NULL; + ogg = NULL; + return 0; + } + return -1; +} + + +/* +=================================================================================== + + idSampleDecoderLocal + +=================================================================================== +*/ + +class idSampleDecoderLocal : public idSampleDecoder { +public: + virtual void Decode( idSoundSample *sample, int sampleOffset44k, int sampleCount44k, float *dest ); + virtual void ClearDecoder( void ); + virtual idSoundSample * GetSample( void ) const; + virtual int GetLastDecodeTime( void ) const; + + void Clear( void ); + int DecodePCM( idSoundSample *sample, int sampleOffset44k, int sampleCount44k, float *dest ); + int DecodeOGG( idSoundSample *sample, int sampleOffset44k, int sampleCount44k, float *dest ); + +private: + bool failed; // set if decoding failed + int lastFormat; // last format being decoded + idSoundSample * lastSample; // last sample being decoded + int lastSampleOffset; // last offset into the decoded sample + int lastDecodeTime; // last time decoding sound + idFile_Memory file; // encoded file in memory + + OggVorbis_File ogg; // OggVorbis file +}; + +idBlockAlloc sampleDecoderAllocator; + +/* +==================== +idSampleDecoder::Init +==================== +*/ +void idSampleDecoder::Init( void ) { + decoderMemoryAllocator.Init(); + decoderMemoryAllocator.SetLockMemory( true ); + decoderMemoryAllocator.SetFixedBlocks( idSoundSystemLocal::s_realTimeDecoding.GetBool() ? 10 : 1 ); +} + +/* +==================== +idSampleDecoder::Shutdown +==================== +*/ +void idSampleDecoder::Shutdown( void ) { + decoderMemoryAllocator.Shutdown(); + sampleDecoderAllocator.Shutdown(); +} + +/* +==================== +idSampleDecoder::Alloc +==================== +*/ +idSampleDecoder *idSampleDecoder::Alloc( void ) { + idSampleDecoderLocal *decoder = sampleDecoderAllocator.Alloc(); + decoder->Clear(); + return decoder; +} + +/* +==================== +idSampleDecoder::Free +==================== +*/ +void idSampleDecoder::Free( idSampleDecoder *decoder ) { + idSampleDecoderLocal *localDecoder = static_cast( decoder ); + localDecoder->ClearDecoder(); + sampleDecoderAllocator.Free( localDecoder ); +} + +/* +==================== +idSampleDecoder::GetNumUsedBlocks +==================== +*/ +int idSampleDecoder::GetNumUsedBlocks( void ) { + return decoderMemoryAllocator.GetNumUsedBlocks(); +} + +/* +==================== +idSampleDecoder::GetUsedBlockMemory +==================== +*/ +int idSampleDecoder::GetUsedBlockMemory( void ) { + return decoderMemoryAllocator.GetUsedBlockMemory(); +} + +/* +==================== +idSampleDecoderLocal::Clear +==================== +*/ +void idSampleDecoderLocal::Clear( void ) { + failed = false; + lastFormat = WAVE_FORMAT_TAG_PCM; + lastSample = NULL; + lastSampleOffset = 0; + lastDecodeTime = 0; +} + +/* +==================== +idSampleDecoderLocal::ClearDecoder +==================== +*/ +void idSampleDecoderLocal::ClearDecoder( void ) { + Sys_EnterCriticalSection( CRITICAL_SECTION_ONE ); + + switch( lastFormat ) { + case WAVE_FORMAT_TAG_PCM: { + break; + } + case WAVE_FORMAT_TAG_OGG: { + ov_clear( &ogg ); + memset( &ogg, 0, sizeof( ogg ) ); + break; + } + } + + Clear(); + + Sys_LeaveCriticalSection( CRITICAL_SECTION_ONE ); +} + +/* +==================== +idSampleDecoderLocal::GetSample +==================== +*/ +idSoundSample *idSampleDecoderLocal::GetSample( void ) const { + return lastSample; +} + +/* +==================== +idSampleDecoderLocal::GetLastDecodeTime +==================== +*/ +int idSampleDecoderLocal::GetLastDecodeTime( void ) const { + return lastDecodeTime; +} + +/* +==================== +idSampleDecoderLocal::Decode +==================== +*/ +void idSampleDecoderLocal::Decode( idSoundSample *sample, int sampleOffset44k, int sampleCount44k, float *dest ) { + int readSamples44k; + + if ( sample->objectInfo.wFormatTag != lastFormat || sample != lastSample ) { + ClearDecoder(); + } + + lastDecodeTime = soundSystemLocal.CurrentSoundTime; + + if ( failed ) { + memset( dest, 0, sampleCount44k * sizeof( dest[0] ) ); + return; + } + + // samples can be decoded both from the sound thread and the main thread for shakes + Sys_EnterCriticalSection( CRITICAL_SECTION_ONE ); + + switch( sample->objectInfo.wFormatTag ) { + case WAVE_FORMAT_TAG_PCM: { + readSamples44k = DecodePCM( sample, sampleOffset44k, sampleCount44k, dest ); + break; + } + case WAVE_FORMAT_TAG_OGG: { + readSamples44k = DecodeOGG( sample, sampleOffset44k, sampleCount44k, dest ); + break; + } + default: { + readSamples44k = 0; + break; + } + } + + Sys_LeaveCriticalSection( CRITICAL_SECTION_ONE ); + + if ( readSamples44k < sampleCount44k ) { + memset( dest + readSamples44k, 0, ( sampleCount44k - readSamples44k ) * sizeof( dest[0] ) ); + } +} + +/* +==================== +idSampleDecoderLocal::DecodePCM +==================== +*/ +int idSampleDecoderLocal::DecodePCM( idSoundSample *sample, int sampleOffset44k, int sampleCount44k, float *dest ) { + const byte *first; + int pos, size, readSamples; + + lastFormat = WAVE_FORMAT_TAG_PCM; + lastSample = sample; + + int shift = 22050 / sample->objectInfo.nSamplesPerSec; + int sampleOffset = sampleOffset44k >> shift; + int sampleCount = sampleCount44k >> shift; + + if ( sample->nonCacheData == NULL ) { + assert( false ); // this should never happen ( note: I've seen that happen with the main thread down in idGameLocal::MapClear clearing entities - TTimo ) + failed = true; + return 0; + } + + if ( !sample->FetchFromCache( sampleOffset * sizeof( short ), &first, &pos, &size, false ) ) { + failed = true; + return 0; + } + + if ( size - pos < sampleCount * sizeof( short ) ) { + readSamples = ( size - pos ) / sizeof( short ); + } else { + readSamples = sampleCount; + } + + // duplicate samples for 44kHz output + SIMDProcessor->UpSamplePCMTo44kHz( dest, (const short *)(first+pos), readSamples, sample->objectInfo.nSamplesPerSec, sample->objectInfo.nChannels ); + + return ( readSamples << shift ); +} + +/* +==================== +idSampleDecoderLocal::DecodeOGG +==================== +*/ +int idSampleDecoderLocal::DecodeOGG( idSoundSample *sample, int sampleOffset44k, int sampleCount44k, float *dest ) { + int readSamples, totalSamples; + + int shift = 22050 / sample->objectInfo.nSamplesPerSec; + int sampleOffset = sampleOffset44k >> shift; + int sampleCount = sampleCount44k >> shift; + + // open OGG file if not yet opened + if ( lastSample == NULL ) { + // make sure there is enough space for another decoder + if ( decoderMemoryAllocator.GetFreeBlockMemory() < MIN_OGGVORBIS_MEMORY ) { + return 0; + } + if ( sample->nonCacheData == NULL ) { + assert( false ); // this should never happen + failed = true; + return 0; + } + file.SetData( (const char *)sample->nonCacheData, sample->objectMemSize ); + if ( ov_openFile( &file, &ogg ) < 0 ) { + failed = true; + return 0; + } + lastFormat = WAVE_FORMAT_TAG_OGG; + lastSample = sample; + } + + // seek to the right offset if necessary + if ( sampleOffset != lastSampleOffset ) { + if ( ov_pcm_seek( &ogg, sampleOffset / sample->objectInfo.nChannels ) != 0 ) { + failed = true; + return 0; + } + } + + lastSampleOffset = sampleOffset; + + // decode OGG samples + totalSamples = sampleCount; + readSamples = 0; + do { + float **samples; + int ret = ov_read_float( &ogg, &samples, totalSamples / sample->objectInfo.nChannels, &ogg.stream ); + if ( ret == 0 ) { + failed = true; + break; + } + if ( ret < 0 ) { + failed = true; + return 0; + } + ret *= sample->objectInfo.nChannels; + + SIMDProcessor->UpSampleOGGTo44kHz( dest + ( readSamples << shift ), samples, ret, sample->objectInfo.nSamplesPerSec, sample->objectInfo.nChannels ); + + readSamples += ret; + totalSamples -= ret; + } while( totalSamples > 0 ); + + lastSampleOffset += readSamples; + + return ( readSamples << shift ); +} + +#endif // Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/default/snd_efxfile.cpp b/src/sound/default/snd_efxfile.cpp new file mode 100644 index 0000000..3444417 --- /dev/null +++ b/src/sound/default/snd_efxfile.cpp @@ -0,0 +1,228 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +/* +=============== +idEFXFile::idEFXFile +=============== +*/ +idEFXFile::idEFXFile( void ) { } + +/* +=============== +idEFXFile::Clear +=============== +*/ +void idEFXFile::Clear( void ) { + effects.DeleteContents( true ); +} + +/* +=============== +idEFXFile::~idEFXFile +=============== +*/ +idEFXFile::~idEFXFile( void ) { + Clear(); +} + +/* +=============== +idEFXFile::FindEffect +=============== +*/ +bool idEFXFile::FindEffect( idStr &name, idSoundEffect **effect, int *index ) { + int i; + + for ( i = 0; i < effects.Num(); i++ ) { + if ( ( effects[i] ) && ( effects[i]->name == name ) ) { + *effect = effects[i]; + *index = i; + return true; + } + } + return false; +} + +/* +=============== +idEFXFile::ReadEffect +=============== +*/ +bool idEFXFile::ReadEffect( idLexer &src, idSoundEffect *effect ) { + idToken name, token; + + if ( !src.ReadToken( &token ) ) + return false; + + // reverb effect + if ( token == "reverb" ) { + EAXREVERBPROPERTIES *reverb = ( EAXREVERBPROPERTIES * )Mem_Alloc( sizeof( EAXREVERBPROPERTIES ) ); + if ( reverb ) { + src.ReadTokenOnLine( &token ); + name = token; + + if ( !src.ReadToken( &token ) ) { + Mem_Free( reverb ); + return false; + } + + if ( token != "{" ) { + src.Error( "idEFXFile::ReadEffect: { not found, found %s", token.c_str() ); + Mem_Free( reverb ); + return false; + } + + do { + if ( !src.ReadToken( &token ) ) { + src.Error( "idEFXFile::ReadEffect: EOF without closing brace" ); + Mem_Free( reverb ); + return false; + } + + if ( token == "}" ) { + effect->name = name; + effect->data = ( void * )reverb; + effect->datasize = sizeof( EAXREVERBPROPERTIES ); + break; + } + + if ( token == "environment" ) { + src.ReadTokenOnLine( &token ); + reverb->ulEnvironment = token.GetUnsignedLongValue(); + } else if ( token == "environment size" ) { + reverb->flEnvironmentSize = src.ParseFloat(); + } else if ( token == "environment diffusion" ) { + reverb->flEnvironmentDiffusion = src.ParseFloat(); + } else if ( token == "room" ) { + reverb->lRoom = src.ParseInt(); + } else if ( token == "room hf" ) { + reverb->lRoomHF = src.ParseInt(); + } else if ( token == "room lf" ) { + reverb->lRoomLF = src.ParseInt(); + } else if ( token == "decay time" ) { + reverb->flDecayTime = src.ParseFloat(); + } else if ( token == "decay hf ratio" ) { + reverb->flDecayHFRatio = src.ParseFloat(); + } else if ( token == "decay lf ratio" ) { + reverb->flDecayLFRatio = src.ParseFloat(); + } else if ( token == "reflections" ) { + reverb->lReflections = src.ParseInt(); + } else if ( token == "reflections delay" ) { + reverb->flReflectionsDelay = src.ParseFloat(); + } else if ( token == "reflections pan" ) { + reverb->vReflectionsPan.x = src.ParseFloat(); + reverb->vReflectionsPan.y = src.ParseFloat(); + reverb->vReflectionsPan.z = src.ParseFloat(); + } else if ( token == "reverb" ) { + reverb->lReverb = src.ParseInt(); + } else if ( token == "reverb delay" ) { + reverb->flReverbDelay = src.ParseFloat(); + } else if ( token == "reverb pan" ) { + reverb->vReverbPan.x = src.ParseFloat(); + reverb->vReverbPan.y = src.ParseFloat(); + reverb->vReverbPan.z = src.ParseFloat(); + } else if ( token == "echo time" ) { + reverb->flEchoTime = src.ParseFloat(); + } else if ( token == "echo depth" ) { + reverb->flEchoDepth = src.ParseFloat(); + } else if ( token == "modulation time" ) { + reverb->flModulationTime = src.ParseFloat(); + } else if ( token == "modulation depth" ) { + reverb->flModulationDepth = src.ParseFloat(); + } else if ( token == "air absorption hf" ) { + reverb->flAirAbsorptionHF = src.ParseFloat(); + } else if ( token == "hf reference" ) { + reverb->flHFReference = src.ParseFloat(); + } else if ( token == "lf reference" ) { + reverb->flLFReference = src.ParseFloat(); + } else if ( token == "room rolloff factor" ) { + reverb->flRoomRolloffFactor = src.ParseFloat(); + } else if ( token == "flags" ) { + src.ReadTokenOnLine( &token ); + reverb->ulFlags = token.GetUnsignedLongValue(); + } else { + src.ReadTokenOnLine( &token ); + src.Error( "idEFXFile::ReadEffect: Invalid parameter in reverb definition" ); + Mem_Free( reverb ); + } + } while ( 1 ); + + return true; + } + } else { + // other effect (not supported at the moment) + src.Error( "idEFXFile::ReadEffect: Unknown effect definition" ); + } + + return false; +} + + +/* +=============== +idEFXFile::LoadFile +=============== +*/ +bool idEFXFile::LoadFile( const char *filename, bool OSPath ) { + idLexer src( LEXFL_NOSTRINGCONCAT ); + idToken token; + + src.LoadFile( filename, OSPath ); + if ( !src.IsLoaded() ) { + return false; + } + + if ( !src.ExpectTokenString( "Version" ) ) { + return NULL; + } + + if ( src.ParseInt() != 1 ) { + src.Error( "idEFXFile::LoadFile: Unknown file version" ); + return false; + } + + while ( !src.EndOfFile() ) { + idSoundEffect *effect = new idSoundEffect; + if ( ReadEffect( src, effect ) ) { + effects.Append( effect ); + } + }; + + return true; +} + + +/* +=============== +idEFXFile::UnloadFile +=============== +*/ +void idEFXFile::UnloadFile( void ) { + Clear(); +} diff --git a/src/sound/default/snd_emitter.cpp b/src/sound/default/snd_emitter.cpp new file mode 100644 index 0000000..c4176c3 --- /dev/null +++ b/src/sound/default/snd_emitter.cpp @@ -0,0 +1,1372 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +#ifdef Q4_RECONSTRUCTED_SOUND + +void idSoundFade::Clear() { + fadeStart44kHz = fadeEnd44kHz = 0; + fadeStartVolume = fadeEndVolume = 0.0f; +} + +float idSoundFade::FadeDbAt44kHz( int current44kHz ) { + if ( current44kHz <= fadeStart44kHz ) return fadeStartVolume; + if ( current44kHz >= fadeEnd44kHz || fadeEnd44kHz <= fadeStart44kHz ) return fadeEndVolume; + const float fraction = (float)( current44kHz - fadeStart44kHz ) / (float)( fadeEnd44kHz - fadeStart44kHz ); + return fadeStartVolume + fraction * ( fadeEndVolume - fadeStartVolume ); +} + +idSoundChannel::idSoundChannel() { Clear(); } +idSoundChannel::~idSoundChannel() { Stop(); } + +void idSoundChannel::Clear() { + triggerState = false; + trigger44kHzTime = 0; + memset( &parms, 0, sizeof( parms ) ); + leadinSample = NULL; + choice = 0; + triggerChannel = SCHANNEL_ANY; + soundShader = NULL; + decoder = NULL; + diversity = 0.0f; + lastVolume = 0.0f; + memset( lastV, 0, sizeof( lastV ) ); + channelFade.Clear(); + triggered = false; + openalSource = 0; + openalStreamingOffset = 0; + memset( openalStreamingBuffer, 0, sizeof( openalStreamingBuffer ) ); + lastShift44kHzTime = 0; + lastFrequencyShift = 1.0f; + elapsedShift44kHzTime = 0.0f; +} + +void idSoundChannel::Stop() { ALStop(); Clear(); } +void idSoundChannel::ALStop() { if ( openalSource ) soundSystemLocal.FreeOpenALSource( openalSource ); openalSource = 0; } +int idSoundChannel::GatherChannelSamples( int sampleOffset44k, int sampleCount44k, float *dest ) const { + if ( dest != NULL && sampleCount44k > 0 ) memset( dest, 0, sampleCount44k * sizeof( float ) ); + return 0; +} + +idSoundEmitterLocal::idSoundEmitterLocal() { Clear(); } +idSoundEmitterLocal::~idSoundEmitterLocal() { StopSound( SCHANNEL_ANY ); } + +void idSoundEmitterLocal::Clear() { + worldId = SOUNDWORLD_NONE; + index = 0; + removeStatus = REMOVE_STATUS_ALIVE; + origin.Zero(); + listenerId = 0; + memset( &parms, 0, sizeof( parms ) ); + parms.minDistance = 1.0f; + parms.maxDistance = 10.0f; + parms.frequencyShift = 1.0f; + maxDistance = 0.0f; + lastValidPortalArea = -1; + playing = false; + hasShakes = false; + spatializedOrigin.Zero(); + realDistance = distance = 0.0f; + numPortalChecks = 0; + ampTime = 0; + amplitude = 0.0f; +} + +void idSoundEmitterLocal::ClearPortalArea() { lastValidPortalArea = -1; } +void idSoundEmitterLocal::OverrideParms( const soundShaderParms_t *base, const soundShaderParms_t *over, soundShaderParms_t *out ) { + if ( out == NULL ) return; + if ( base != NULL ) *out = *base; else memset( out, 0, sizeof( *out ) ); + if ( over != NULL ) *out = *over; +} +void idSoundEmitterLocal::CheckForCompletion( int current44kHzTime ) { + playing = false; + for ( int i = 0; i < SOUND_MAX_CHANNELS; ++i ) if ( channels[i].triggerState ) { playing = true; break; } +} +void idSoundEmitterLocal::Spatialize( idVec3 listenerPos, int listenerArea, idRenderWorld *rw ) { + spatializedOrigin = origin; + realDistance = distance = ( origin - listenerPos ).Length(); +} +void idSoundEmitterLocal::UpdateEmitter( const idVec3 &newOrigin, const idVec3 &velocity, int newListenerId, const soundShaderParms_t *newParms ) { + origin = newOrigin; + listenerId = newListenerId; + if ( newParms != NULL ) parms = *newParms; +} + +int idSoundEmitterLocal::StartSound( const idSoundShader *shader, int channel, float newDiversity, int shaderFlags ) { + if ( shader == NULL ) return 0; + int slot = -1; + for ( int i = 0; i < SOUND_MAX_CHANNELS; ++i ) { + if ( channel != SCHANNEL_ANY && channels[i].triggerState && channels[i].triggerChannel == channel ) { slot = i; break; } + if ( slot < 0 && !channels[i].triggerState ) slot = i; + } + if ( slot < 0 ) slot = 0; + idSoundChannel &ch = channels[slot]; + ch.Clear(); + ch.triggerState = ch.triggered = true; + ch.trigger44kHzTime = soundSystemLocal.GetCurrent44kHzTime(); + ch.triggerChannel = channel; + ch.soundShader = shader; + ch.diversity = newDiversity; + shader->GetParms( &ch.parms ); + ch.parms.soundShaderFlags |= shaderFlags; + playing = true; + hasShakes = hasShakes || ch.parms.shakes > 0.0f; + maxDistance = Max( maxDistance, ch.parms.maxDistance ); + return (int)( shader->GetTimeLength() * 1000.0f ); +} + +void idSoundEmitterLocal::ModifySound( int channel, const soundShaderParms_t *newParms ) { + if ( newParms == NULL ) return; + for ( int i = 0; i < SOUND_MAX_CHANNELS; ++i ) if ( channels[i].triggerState && ( channel == SCHANNEL_ANY || channels[i].triggerChannel == channel ) ) channels[i].parms = *newParms; +} +void idSoundEmitterLocal::StopSound( int channel ) { + for ( int i = 0; i < SOUND_MAX_CHANNELS; ++i ) if ( channel == SCHANNEL_ANY || channels[i].triggerChannel == channel ) channels[i].Stop(); + CheckForCompletion( soundSystemLocal.GetCurrent44kHzTime() ); +} +void idSoundEmitterLocal::FadeSound( int channel, float to, float over ) { + const int now = soundSystemLocal.GetCurrent44kHzTime(); + const int end = now + (int)( over * PRIMARYFREQ ); + for ( int i = 0; i < SOUND_MAX_CHANNELS; ++i ) if ( channels[i].triggerState && ( channel == SCHANNEL_ANY || channels[i].triggerChannel == channel ) ) { + channels[i].channelFade.fadeStart44kHz = now; + channels[i].channelFade.fadeEnd44kHz = end; + channels[i].channelFade.fadeStartVolume = channels[i].lastVolume; + channels[i].channelFade.fadeEndVolume = to; + } +} +bool idSoundEmitterLocal::CurrentlyPlaying( int channel ) const { + for ( int i = 0; i < SOUND_MAX_CHANNELS; ++i ) if ( channels[i].triggerState && ( channel == SCHANNEL_ANY || channels[i].triggerChannel == channel ) ) return true; + return false; +} +float idSoundEmitterLocal::CurrentAmplitude( int channelFlags, bool factorDistance ) { return amplitude; } +bool idSoundEmitterLocal::AttachedToWorld( int id ) const { return id == SOUNDWORLD_ANY || worldId == id; } +int idSoundEmitterLocal::Handle() const { return index; } + +#else + + +/* +=================== +idSoundFade::Clear +=================== +*/ +void idSoundFade::Clear() { + fadeStart44kHz = 0; + fadeEnd44kHz = 0; + fadeStartVolume = 0; + fadeEndVolume = 0; +} + +/* +=================== +idSoundFade::FadeDbAt44kHz +=================== +*/ +float idSoundFade::FadeDbAt44kHz( int current44kHz ) { + float fadeDb; + + if ( current44kHz >= fadeEnd44kHz ) { + fadeDb = fadeEndVolume; + } else if ( current44kHz > fadeStart44kHz ) { + float fraction = ( fadeEnd44kHz - fadeStart44kHz ); + float over = ( current44kHz - fadeStart44kHz ); + fadeDb = fadeStartVolume + ( fadeEndVolume - fadeStartVolume ) * over / fraction; + } else { + fadeDb = fadeStartVolume; + } + return fadeDb; +} + +//======================================================================== + + +/* +======================= +GeneratePermutedList + +Fills in elements[0] .. elements[numElements-1] with a permutation of +0 .. numElements-1 based on the permute parameter + +numElements == 3 +maxPermute = 6 +permute 0 = 012 +permute 1 = 021 +permute 2 = 102 +permute 3 = 120 +permute 4 = 201 +permute 5 = 210 +======================= +*/ +void PermuteList_r( int *list, int listLength, int permute, int maxPermute ) { + if ( listLength < 2 ) { + return; + } + permute %= maxPermute; + int swap = permute * listLength / maxPermute; + int old = list[swap]; + list[swap] = list[0]; + list[0] = old; + + maxPermute /= listLength; + PermuteList_r( list + 1, listLength - 1, permute, maxPermute ); +} + +int Factorial( int val ) { + int fact = val; + while ( val > 1 ) { + val--; + fact *= val; + } + return fact; +} + +void GeneratePermutedList( int *list, int listLength, int permute ) { + for ( int i = 0 ; i < listLength ; i++ ) { + list[i] = i; + } + + // we can't calculate > 12 factorial, so we can't easily build a permuted list + if ( listLength > 12 ) { + return; + } + + // calculate listLength factorial + int maxPermute = Factorial( listLength ); + + // recursively permute + PermuteList_r( list, listLength, permute, maxPermute ); +} + +void TestPermutations( void ) { + int list[SOUND_MAX_LIST_WAVS]; + + for ( int len = 1 ; len < 5 ; len++ ) { + common->Printf( "list length: %i\n", len ); + + int max = Factorial( len ); + for ( int j = 0 ; j < max * 2 ; j++ ) { + GeneratePermutedList( list, len, j ); + common->Printf( "%4i : ", j ); + for ( int k = 0 ; k < len ; k++ ) { + common->Printf( "%i", list[k] ); + } + common->Printf( "\n" ); + } + } +} + +//===================================================================================== + +/* +=================== +idSoundChannel::idSoundChannel +=================== +*/ +idSoundChannel::idSoundChannel( void ) { + decoder = NULL; + Clear(); +} + +/* +=================== +idSoundChannel::~idSoundChannel +=================== +*/ +idSoundChannel::~idSoundChannel( void ) { + Clear(); +} + +/* +=================== +idSoundChannel::Clear +=================== +*/ +void idSoundChannel::Clear( void ) { + int j; + + Stop(); + soundShader = NULL; + lastVolume = 0.0f; + triggerChannel = SCHANNEL_ANY; + channelFade.Clear(); + diversity = 0.0f; + leadinSample = NULL; + trigger44kHzTime = 0; + for( j = 0; j < 6; j++ ) { + lastV[j] = 0.0f; + } + memset( &parms, 0, sizeof(parms) ); + + triggered = false; + openalSource = NULL; + openalStreamingOffset = 0; + openalStreamingBuffer[0] = openalStreamingBuffer[1] = openalStreamingBuffer[2] = 0; + lastopenalStreamingBuffer[0] = lastopenalStreamingBuffer[1] = lastopenalStreamingBuffer[2] = 0; +} + +/* +=================== +idSoundChannel::Start +=================== +*/ +void idSoundChannel::Start( void ) { + triggerState = true; + if ( decoder == NULL ) { + decoder = idSampleDecoder::Alloc(); + } +} + +/* +=================== +idSoundChannel::Stop +=================== +*/ +void idSoundChannel::Stop( void ) { + triggerState = false; + if ( decoder != NULL ) { + idSampleDecoder::Free( decoder ); + decoder = NULL; + } +} + +/* +=================== +idSoundChannel::ALStop +=================== +*/ +void idSoundChannel::ALStop( void ) { + if ( idSoundSystemLocal::useOpenAL ) { + + if ( alIsSource( openalSource ) ) { + alSourceStop( openalSource ); + alSourcei( openalSource, AL_BUFFER, 0 ); + soundSystemLocal.FreeOpenALSource( openalSource ); + } + + if ( openalStreamingBuffer[0] && openalStreamingBuffer[1] && openalStreamingBuffer[2] ) { + alGetError(); + alDeleteBuffers( 3, &openalStreamingBuffer[0] ); + if ( alGetError() == AL_NO_ERROR ) { + openalStreamingBuffer[0] = openalStreamingBuffer[1] = openalStreamingBuffer[2] = 0; + } + } + + if ( lastopenalStreamingBuffer[0] && lastopenalStreamingBuffer[1] && lastopenalStreamingBuffer[2] ) { + alGetError(); + alDeleteBuffers( 3, &lastopenalStreamingBuffer[0] ); + if ( alGetError() == AL_NO_ERROR ) { + lastopenalStreamingBuffer[0] = lastopenalStreamingBuffer[1] = lastopenalStreamingBuffer[2] = 0; + } + } + } +} + +/* +=================== +idSoundChannel::GatherChannelSamples + +Will always return 44kHz samples for the given range, even if it deeply looped or +out of the range of the unlooped samples. Handles looping between multiple different +samples and leadins +=================== +*/ +void idSoundChannel::GatherChannelSamples( int sampleOffset44k, int sampleCount44k, float *dest ) const { + float *dest_p = dest; + int len; + +//Sys_DebugPrintf( "msec:%i sample:%i : %i : %i\n", Sys_Milliseconds(), soundSystemLocal.GetCurrent44kHzTime(), sampleOffset44k, sampleCount44k ); //!@# + + // negative offset times will just zero fill + if ( sampleOffset44k < 0 ) { + len = -sampleOffset44k; + if ( len > sampleCount44k ) { + len = sampleCount44k; + } + memset( dest_p, 0, len * sizeof( dest_p[0] ) ); + dest_p += len; + sampleCount44k -= len; + sampleOffset44k += len; + } + + // grab part of the leadin sample + idSoundSample *leadin = leadinSample; + if ( !leadin || sampleOffset44k < 0 || sampleCount44k <= 0 ) { + memset( dest_p, 0, sampleCount44k * sizeof( dest_p[0] ) ); + return; + } + + if ( sampleOffset44k < leadin->LengthIn44kHzSamples() ) { + len = leadin->LengthIn44kHzSamples() - sampleOffset44k; + if ( len > sampleCount44k ) { + len = sampleCount44k; + } + + // decode the sample + decoder->Decode( leadin, sampleOffset44k, len, dest_p ); + + dest_p += len; + sampleCount44k -= len; + sampleOffset44k += len; + } + + // if not looping, zero fill any remaining spots + if ( !soundShader || !( parms.soundShaderFlags & SSF_LOOPING ) ) { + memset( dest_p, 0, sampleCount44k * sizeof( dest_p[0] ) ); + return; + } + + // fill the remainder with looped samples + idSoundSample *loop = soundShader->entries[0]; + + if ( !loop ) { + memset( dest_p, 0, sampleCount44k * sizeof( dest_p[0] ) ); + return; + } + + sampleOffset44k -= leadin->LengthIn44kHzSamples(); + + while( sampleCount44k > 0 ) { + int totalLen = loop->LengthIn44kHzSamples(); + + sampleOffset44k %= totalLen; + + len = totalLen - sampleOffset44k; + if ( len > sampleCount44k ) { + len = sampleCount44k; + } + + // decode the sample + decoder->Decode( loop, sampleOffset44k, len, dest_p ); + + dest_p += len; + sampleCount44k -= len; + sampleOffset44k += len; + } +} + + +//===================================================================================== + +/* +=============== +idSoundEmitterLocal::idSoundEmitterLocal + +=============== +*/ +idSoundEmitterLocal::idSoundEmitterLocal( void ) { + soundWorld = NULL; + Clear(); +} + +/* +=============== +idSoundEmitterLocal::~idSoundEmitterLocal +=============== +*/ +idSoundEmitterLocal::~idSoundEmitterLocal( void ) { + Clear(); +} + +/* +=============== +idSoundEmitterLocal::Clear +=============== +*/ +void idSoundEmitterLocal::Clear( void ) { + int i; + + for( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + channels[i].ALStop(); + channels[i].Clear(); + } + + removeStatus = REMOVE_STATUS_SAMPLEFINISHED; + distance = 0.0f; + + lastValidPortalArea = -1; + + playing = false; + hasShakes = false; + ampTime = 0; // last time someone queried + amplitude = 0; + maxDistance = 10.0f; // meters + spatializedOrigin.Zero(); + + memset( &parms, 0, sizeof( parms ) ); +} + +/* +================== +idSoundEmitterLocal::OverrideParms +================== +*/ +void idSoundEmitterLocal::OverrideParms( const soundShaderParms_t *base, + const soundShaderParms_t *over, soundShaderParms_t *out ) { + if ( !over ) { + *out = *base; + return; + } + if ( over->minDistance ) { + out->minDistance = over->minDistance; + } else { + out->minDistance = base->minDistance; + } + if ( over->maxDistance ) { + out->maxDistance = over->maxDistance; + } else { + out->maxDistance = base->maxDistance; + } + if ( over->shakes ) { + out->shakes = over->shakes; + } else { + out->shakes = base->shakes; + } + if ( over->volume ) { + out->volume = over->volume; + } else { + out->volume = base->volume; + } + if ( over->soundClass ) { + out->soundClass = over->soundClass; + } else { + out->soundClass = base->soundClass; + } + out->soundShaderFlags = base->soundShaderFlags | over->soundShaderFlags; +} + +/* +================== +idSoundEmitterLocal::CheckForCompletion + +Checks to see if all the channels have completed, clearing the playing flag if necessary. +Sets the playing and shakes bools. +================== +*/ +void idSoundEmitterLocal::CheckForCompletion( int current44kHzTime ) { + bool hasActive; + int i; + + hasActive = false; + hasShakes = false; + + if ( playing ) { + for ( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + + if ( !chan->triggerState ) { + continue; + } + const idSoundShader *shader = chan->soundShader; + if ( !shader ) { + continue; + } + + // see if this channel has completed + if ( !( chan->parms.soundShaderFlags & SSF_LOOPING ) ) { + ALint state = AL_PLAYING; + + if ( idSoundSystemLocal::useOpenAL && alIsSource( chan->openalSource ) ) { + alGetSourcei( chan->openalSource, AL_SOURCE_STATE, &state ); + } + idSlowChannel slow = GetSlowChannel( chan ); + + if ( soundWorld->slowmoActive && slow.IsActive() ) { + if ( slow.GetCurrentPosition().time >= chan->leadinSample->LengthIn44kHzSamples() / 2 ) { + chan->Stop(); + // if this was an onDemand sound, purge the sample now + if ( chan->leadinSample->onDemand ) { + chan->leadinSample->PurgeSoundSample(); + } + continue; + } + } else if ( ( chan->trigger44kHzTime + chan->leadinSample->LengthIn44kHzSamples() < current44kHzTime ) || ( state == AL_STOPPED ) ) { + chan->Stop(); + + // free hardware resources + chan->ALStop(); + + // if this was an onDemand sound, purge the sample now + if ( chan->leadinSample->onDemand ) { + chan->leadinSample->PurgeSoundSample(); + } + continue; + } + } + + // free decoder memory if no sound was decoded for a while + if ( chan->decoder != NULL && chan->decoder->GetLastDecodeTime() < current44kHzTime - SOUND_DECODER_FREE_DELAY ) { + chan->decoder->ClearDecoder(); + } + + hasActive = true; + + if ( chan->parms.shakes > 0.0f ) { + hasShakes = true; + } + } + } + + // mark the entire sound emitter as non-playing if there aren't any active channels + if ( !hasActive ) { + playing = false; + if ( removeStatus == REMOVE_STATUS_WAITSAMPLEFINISHED ) { + // this can now be reused by the next request for a new soundEmitter + removeStatus = REMOVE_STATUS_SAMPLEFINISHED; + } + } +} + +/* +=================== +idSoundEmitterLocal::Spatialize + +Called once each sound frame by the main thread from idSoundWorldLocal::PlaceOrigin +=================== +*/ +void idSoundEmitterLocal::Spatialize( idVec3 listenerPos, int listenerArea, idRenderWorld *rw ) { + int i; + bool hasActive = false; + + // + // work out the maximum distance of all the playing channels + // + maxDistance = 0; + + for ( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + + if ( !chan->triggerState ) { + continue; + } + if ( chan->parms.maxDistance > maxDistance ) { + maxDistance = chan->parms.maxDistance; + } + } + + // + // work out where the sound comes from + // + idVec3 realOrigin = origin * DOOM_TO_METERS; + idVec3 len = listenerPos - realOrigin; + realDistance = len.LengthFast(); + + if ( realDistance >= maxDistance ) { + // no way to possibly hear it + distance = realDistance; + return; + } + + // + // work out virtual origin and distance, which may be from a portal instead of the actual origin + // + distance = maxDistance * METERS_TO_DOOM; + if ( listenerArea == -1 ) { // listener is outside the world + return; + } + if ( rw ) { + // we have a valid renderWorld + int soundInArea = rw->PointInArea( origin ); + if ( soundInArea == -1 ) { + if ( lastValidPortalArea == -1 ) { // sound is outside the world + distance = realDistance; + spatializedOrigin = origin; // sound is in our area + return; + } + soundInArea = lastValidPortalArea; + } + lastValidPortalArea = soundInArea; + if ( soundInArea == listenerArea ) { + distance = realDistance; + spatializedOrigin = origin; // sound is in our area + return; + } + + soundWorld->ResolveOrigin( 0, NULL, soundInArea, 0.0f, origin, this ); + distance /= METERS_TO_DOOM; + } else { + // no portals available + distance = realDistance; + spatializedOrigin = origin; // sound is in our area + } +} + +/* +=========================================================================================== + +PUBLIC FUNCTIONS + +=========================================================================================== +*/ + +/* +===================== +idSoundEmitterLocal::UpdateEmitter +===================== +*/ +void idSoundEmitterLocal::UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms ) { + if ( !parms ) { + common->Error( "idSoundEmitterLocal::UpdateEmitter: NULL parms" ); + } + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_UPDATE ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteVec3( origin ); + soundWorld->writeDemo->WriteInt( listenerId ); + soundWorld->writeDemo->WriteFloat( parms->minDistance ); + soundWorld->writeDemo->WriteFloat( parms->maxDistance ); + soundWorld->writeDemo->WriteFloat( parms->volume ); + soundWorld->writeDemo->WriteFloat( parms->shakes ); + soundWorld->writeDemo->WriteInt( parms->soundShaderFlags ); + soundWorld->writeDemo->WriteInt( parms->soundClass ); + } + + this->origin = origin; + this->listenerId = listenerId; + this->parms = *parms; + + // FIXME: change values on all channels? +} + +/* +===================== +idSoundEmitterLocal::Free + +They are never truly freed, just marked so they can be reused by the soundWorld +===================== +*/ +void idSoundEmitterLocal::Free( bool immediate ) { + if ( removeStatus != REMOVE_STATUS_ALIVE ) { + return; + } + + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "FreeSound (%i,%i)\n", index, (int)immediate ); + } + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_FREE ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( immediate ); + } + + if ( !immediate ) { + removeStatus = REMOVE_STATUS_WAITSAMPLEFINISHED; + } else { + Clear(); + } +} + +/* +===================== +idSoundEmitterLocal::StartSound + +returns the length of the started sound in msec +===================== +*/ +int idSoundEmitterLocal::StartSound( const idSoundShader *shader, const s_channelType channel, float diversity, int soundShaderFlags, bool allowSlow ) { + int i; + + if ( !shader ) { + return 0; + } + + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "StartSound %ims (%i,%i,%s) = ", soundWorld->gameMsec, index, (int)channel, shader->GetName() ); + } + + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_START ); + soundWorld->writeDemo->WriteInt( index ); + + soundWorld->writeDemo->WriteHashString( shader->GetName() ); + + soundWorld->writeDemo->WriteInt( channel ); + soundWorld->writeDemo->WriteFloat( diversity ); + soundWorld->writeDemo->WriteInt( soundShaderFlags ); + } + + // build the channel parameters by taking the shader parms and optionally overriding + soundShaderParms_t chanParms; + + chanParms = shader->parms; + OverrideParms( &chanParms, &this->parms, &chanParms ); + chanParms.soundShaderFlags |= soundShaderFlags; + + if ( chanParms.shakes > 0.0f ) { + shader->CheckShakesAndOgg(); + } + + // this is the sample time it will be first mixed + int start44kHz; + + if ( soundWorld->fpa[0] ) { + // if we are recording an AVI demo, don't use hardware time + start44kHz = soundWorld->lastAVI44kHz + MIXBUFFER_SAMPLES; + } else { + start44kHz = soundSystemLocal.GetCurrent44kHzTime() + MIXBUFFER_SAMPLES; + } + + // + // pick which sound to play from the shader + // + if ( !shader->numEntries ) { + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "no samples in sound shader\n" ); + } + return 0; // no sounds + } + int choice; + + // pick a sound from the list based on the passed diversity + choice = (int)(diversity * shader->numEntries); + if ( choice < 0 || choice >= shader->numEntries ) { + choice = 0; + } + + // bump the choice if the exact sound was just played and we are NO_DUPS + if ( chanParms.soundShaderFlags & SSF_NO_DUPS ) { + idSoundSample *sample; + if ( shader->leadins[ choice ] ) { + sample = shader->leadins[ choice ]; + } else { + sample = shader->entries[ choice ]; + } + for( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + if ( chan->leadinSample == sample ) { + choice = ( choice + 1 ) % shader->numEntries; + break; + } + } + } + + // PLAY_ONCE sounds will never be restarted while they are running + if ( chanParms.soundShaderFlags & SSF_PLAY_ONCE ) { + for( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + if ( chan->triggerState && chan->soundShader == shader ) { + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "PLAY_ONCE not restarting\n" ); + } + return 0; + } + } + } + + // never play the same sound twice with the same starting time, even + // if they are on different channels + for( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + if ( chan->triggerState && chan->soundShader == shader && chan->trigger44kHzTime == start44kHz ) { + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "already started this frame\n" ); + } + return 0; + } + } + + Sys_EnterCriticalSection(); + + // kill any sound that is currently playing on this channel + if ( channel != SCHANNEL_ANY ) { + for( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + if ( chan->triggerState && chan->soundShader && chan->triggerChannel == channel ) { + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "(override %s)", chan->soundShader->base->GetName() ); + } + + chan->Stop(); + + // if this was an onDemand sound, purge the sample now + if ( chan->leadinSample->onDemand ) { + chan->ALStop(); + chan->leadinSample->PurgeSoundSample(); + } + break; + } + } + } + + // find a free channel to play the sound on + idSoundChannel *chan; + for( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + chan = &channels[i]; + if ( !chan->triggerState ) { + break; + } + } + + if ( i == SOUND_MAX_CHANNELS ) { + // we couldn't find a channel for it + Sys_LeaveCriticalSection(); + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "no channels available\n" ); + } + return 0; + } + + chan = &channels[i]; + + if ( shader->leadins[ choice ] ) { + chan->leadinSample = shader->leadins[ choice ]; + } else { + chan->leadinSample = shader->entries[ choice ]; + } + + // if the sample is onDemand (voice mails, etc), load it now + if ( chan->leadinSample->purged ) { + int start = Sys_Milliseconds(); + chan->leadinSample->Load(); + int end = Sys_Milliseconds(); + session->TimeHitch( end - start ); + // recalculate start44kHz, because loading may have taken a fair amount of time + if ( !soundWorld->fpa[0] ) { + start44kHz = soundSystemLocal.GetCurrent44kHzTime() + MIXBUFFER_SAMPLES; + } + } + + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "'%s'\n", chan->leadinSample->name.c_str() ); + } + + if ( idSoundSystemLocal::s_skipHelltimeFX.GetBool() ) { + chan->disallowSlow = true; + } else { + chan->disallowSlow = !allowSlow; + } + + ResetSlowChannel( chan ); + + // the sound will start mixing in the next async mix block + chan->triggered = true; + chan->openalStreamingOffset = 0; + chan->trigger44kHzTime = start44kHz; + chan->parms = chanParms; + chan->triggerGame44kHzTime = soundWorld->game44kHz; + chan->soundShader = shader; + chan->triggerChannel = channel; + chan->Start(); + + // we need to start updating the def and mixing it in + playing = true; + + // spatialize it immediately, so it will start the next mix block + // even if that happens before the next PlaceOrigin() + Spatialize( soundWorld->listenerPos, soundWorld->listenerArea, soundWorld->rw ); + + // return length of sound in milliseconds + int length = chan->leadinSample->LengthIn44kHzSamples(); + + if ( chan->leadinSample->objectInfo.nChannels == 2 ) { + length /= 2; // stereo samples + } + + // adjust the start time based on diversity for looping sounds, so they don't all start + // at the same point + if ( chan->parms.soundShaderFlags & SSF_LOOPING && !chan->leadinSample->LengthIn44kHzSamples() ) { + chan->trigger44kHzTime -= diversity * length; + chan->trigger44kHzTime &= ~7; // so we don't have to worry about the 22kHz and 11kHz expansions + // starting in fractional samples + chan->triggerGame44kHzTime -= diversity * length; + chan->triggerGame44kHzTime &= ~7; + } + + length *= 1000 / (float)PRIMARYFREQ; + + Sys_LeaveCriticalSection(); + + return length; +} + +/* +=================== +idSoundEmitterLocal::ModifySound +=================== +*/ +void idSoundEmitterLocal::ModifySound( const s_channelType channel, const soundShaderParms_t *parms ) { + if ( !parms ) { + common->Error( "idSoundEmitterLocal::ModifySound: NULL parms" ); + } + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "ModifySound(%i,%i)\n", index, channel ); + } + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_MODIFY ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( channel ); + soundWorld->writeDemo->WriteFloat( parms->minDistance ); + soundWorld->writeDemo->WriteFloat( parms->maxDistance ); + soundWorld->writeDemo->WriteFloat( parms->volume ); + soundWorld->writeDemo->WriteFloat( parms->shakes ); + soundWorld->writeDemo->WriteInt( parms->soundShaderFlags ); + soundWorld->writeDemo->WriteInt( parms->soundClass ); + } + + for ( int i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + + if ( !chan->triggerState ) { + continue; + } + if ( channel != SCHANNEL_ANY && chan->triggerChannel != channel ) { + continue; + } + + OverrideParms( &chan->parms, parms, &chan->parms ); + + if ( chan->parms.shakes > 0.0f && chan->soundShader != NULL ) { + chan->soundShader->CheckShakesAndOgg(); + } + } +} + +/* +=================== +idSoundEmitterLocal::StopSound + +can pass SCHANNEL_ANY +=================== +*/ +void idSoundEmitterLocal::StopSound( const s_channelType channel ) { + int i; + + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "StopSound(%i,%i)\n", index, channel ); + } + + if ( soundWorld && soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_STOP ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( channel ); + } + + Sys_EnterCriticalSection(); + + for( i = 0; i < SOUND_MAX_CHANNELS; i++ ) { + idSoundChannel *chan = &channels[i]; + + if ( !chan->triggerState ) { + continue; + } + if ( channel != SCHANNEL_ANY && chan->triggerChannel != channel ) { + continue; + } + + // stop it + chan->Stop(); + + // free hardware resources + chan->ALStop(); + + // if this was an onDemand sound, purge the sample now + if ( chan->leadinSample->onDemand ) { + chan->leadinSample->PurgeSoundSample(); + } + + chan->leadinSample = NULL; + chan->soundShader = NULL; + } + + Sys_LeaveCriticalSection(); +} + +/* +=================== +idSoundEmitterLocal::FadeSound + +to is in Db (sigh), over is in seconds +=================== +*/ +void idSoundEmitterLocal::FadeSound( const s_channelType channel, float to, float over ) { + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "FadeSound(%i,%i,%f,%f )\n", index, channel, to, over ); + } + if ( !soundWorld ) { + return; + } + if ( soundWorld->writeDemo ) { + soundWorld->writeDemo->WriteInt( DS_SOUND ); + soundWorld->writeDemo->WriteInt( SCMD_FADE ); + soundWorld->writeDemo->WriteInt( index ); + soundWorld->writeDemo->WriteInt( channel ); + soundWorld->writeDemo->WriteFloat( to ); + soundWorld->writeDemo->WriteFloat( over ); + } + + int start44kHz; + + if ( soundWorld->fpa[0] ) { + // if we are recording an AVI demo, don't use hardware time + start44kHz = soundWorld->lastAVI44kHz + MIXBUFFER_SAMPLES; + } else { + start44kHz = soundSystemLocal.GetCurrent44kHzTime() + MIXBUFFER_SAMPLES; + } + + int length44kHz = soundSystemLocal.MillisecondsToSamples( over * 1000 ); + + for( int i = 0; i < SOUND_MAX_CHANNELS ; i++ ) { + idSoundChannel *chan = &channels[i]; + + if ( !chan->triggerState ) { + continue; + } + if ( channel != SCHANNEL_ANY && chan->triggerChannel != channel ) { + continue; + } + + // if it is already fading to this volume at this rate, don't change it + if ( chan->channelFade.fadeEndVolume == to && + chan->channelFade.fadeEnd44kHz - chan->channelFade.fadeStart44kHz == length44kHz ) { + continue; + } + + // fade it + chan->channelFade.fadeStartVolume = chan->channelFade.FadeDbAt44kHz( start44kHz ); + chan->channelFade.fadeStart44kHz = start44kHz; + chan->channelFade.fadeEnd44kHz = start44kHz + length44kHz; + chan->channelFade.fadeEndVolume = to; + } +} + +/* +=================== +idSoundEmitterLocal::CurrentlyPlaying +=================== +*/ +bool idSoundEmitterLocal::CurrentlyPlaying( void ) const { + return playing; +} + +/* +=================== +idSoundEmitterLocal::Index +=================== +*/ +int idSoundEmitterLocal::Index( void ) const { + return index; +} + +/* +=================== +idSoundEmitterLocal::CurrentAmplitude + +this is called from the main thread by the material shader system +to allow lights and surface flares to vary with the sound amplitude +=================== +*/ +float idSoundEmitterLocal::CurrentAmplitude( void ) { + if ( idSoundSystemLocal::s_constantAmplitude.GetFloat() >= 0.0f ) { + return idSoundSystemLocal::s_constantAmplitude.GetFloat(); + } + + if ( removeStatus > REMOVE_STATUS_WAITSAMPLEFINISHED ) { + return 0.0; + } + + int localTime = soundSystemLocal.GetCurrent44kHzTime(); + + // see if we can use our cached value + if ( ampTime == localTime ) { + return amplitude; + } + + // calculate a new value + ampTime = localTime; + amplitude = soundWorld->FindAmplitude( this, localTime, NULL, SCHANNEL_ANY, false ); + + return amplitude; +} + +/* +=================== +idSoundEmitterLocal::GetSlowChannel +=================== +*/ +idSlowChannel idSoundEmitterLocal::GetSlowChannel( const idSoundChannel *chan ) { + return slowChannels[chan - channels]; +} + +/* +=================== +idSoundEmitterLocal::SetSlowChannel +=================== +*/ +void idSoundEmitterLocal::SetSlowChannel( const idSoundChannel *chan, idSlowChannel slow ) { + slowChannels[chan - channels] = slow; +} + +/* +=================== +idSoundEmitterLocal::ResetSlowChannel +=================== +*/ +void idSoundEmitterLocal::ResetSlowChannel( const idSoundChannel *chan ) { + int index = chan - channels; + slowChannels[index].Reset(); +} + +/* +=================== +idSlowChannel::Reset +=================== +*/ +void idSlowChannel::Reset() { + memset( this, 0, sizeof( *this ) ); + + this->chan = chan; + + curPosition.Set( 0 ); + newPosition.Set( 0 ); + + curSampleOffset = -10000; + newSampleOffset = -10000; + + triggerOffset = 0; +} + +/* +=================== +idSlowChannel::AttachSoundChannel +=================== +*/ +void idSlowChannel::AttachSoundChannel( const idSoundChannel *chan ) { + this->chan = chan; +} + +/* +=================== +idSlowChannel::GetSlowmoSpeed +=================== +*/ +float idSlowChannel::GetSlowmoSpeed() { + idSoundWorldLocal *sw = static_cast( soundSystemLocal.GetPlayingSoundWorld() ); + + if ( sw ) { + return sw->slowmoSpeed; + } else { + return 0; + } +} + +/* +=================== +idSlowChannel::GenerateSlowChannel +=================== +*/ +void idSlowChannel::GenerateSlowChannel( FracTime& playPos, int sampleCount44k, float* finalBuffer ) { + idSoundWorldLocal *sw = static_cast( soundSystemLocal.GetPlayingSoundWorld() ); + float in[MIXBUFFER_SAMPLES+3], out[MIXBUFFER_SAMPLES+3], *src, *spline, slowmoSpeed; + int i, neededSamples, orgTime, zeroedPos, count = 0; + + src = in + 2; + spline = out + 2; + + if ( sw ) { + slowmoSpeed = sw->slowmoSpeed; + } + else { + slowmoSpeed = 1; + } + + neededSamples = sampleCount44k * slowmoSpeed + 4; + orgTime = playPos.time; + + // get the channel's samples + chan->GatherChannelSamples( playPos.time * 2, neededSamples, src ); + for ( i = 0; i < neededSamples >> 1; i++ ) { + spline[i] = src[i*2]; + } + + // interpolate channel + zeroedPos = playPos.time; + playPos.time = 0; + + for ( i = 0; i < sampleCount44k >> 1; i++, count += 2 ) { + float val; + val = spline[playPos.time]; + src[i] = val; + playPos.Increment( slowmoSpeed ); + } + + // lowpass filter + float *in_p = in + 2, *out_p = out + 2; + int numSamples = sampleCount44k >> 1; + + lowpass.GetContinuitySamples( in_p[-1], in_p[-2], out_p[-1], out_p[-2] ); + lowpass.SetParms( slowmoSpeed * 15000, 1.2f ); + + for ( int i = 0, count = 0; i < numSamples; i++, count += 2 ) { + lowpass.ProcessSample( in_p + i, out_p + i ); + finalBuffer[count] = finalBuffer[count+1] = out[i]; + } + + lowpass.SetContinuitySamples( in_p[numSamples-2], in_p[numSamples-3], out_p[numSamples-2], out_p[numSamples-3] ); + + playPos.time += zeroedPos; +} + +/* +=================== +idSlowChannel::GatherChannelSamples +=================== +*/ +void idSlowChannel::GatherChannelSamples( int sampleOffset44k, int sampleCount44k, float *dest ) { + int state = 0; + + // setup chan + active = true; + newSampleOffset = sampleOffset44k >> 1; + + // set state + if ( newSampleOffset < curSampleOffset ) { + state = PLAYBACK_RESET; + } else if ( newSampleOffset > curSampleOffset ) { + state = PLAYBACK_ADVANCING; + } + + if ( state == PLAYBACK_RESET ) { + curPosition.Set( newSampleOffset ); + } + + // set current vars + curSampleOffset = newSampleOffset; + newPosition = curPosition; + + // do the slow processing + GenerateSlowChannel( newPosition, sampleCount44k, dest ); + + // finish off + if ( state == PLAYBACK_ADVANCING ) + curPosition = newPosition; +} + +#endif // Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/default/snd_reverb.cpp b/src/sound/default/snd_reverb.cpp new file mode 100644 index 0000000..1f9128d --- /dev/null +++ b/src/sound/default/snd_reverb.cpp @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* Quake 4 area-reverb compiland (snd_reverb.obj). */ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +const char *idSoundSystemLocal::GetReverbName( int reverb ) { return reverb == 0 ? "default" : ""; } +int idSoundSystemLocal::GetReverb( int area ) { return area >= 0 && area < areaReverbs.Num() ? areaReverbs[area] : 0; } +bool idSoundSystemLocal::SetReverb( int area, const char *reverbName, const char *fileName ) { + if ( area < 0 ) return false; + while ( areaReverbs.Num() <= area ) areaReverbs.Append( 0 ); + areaReverbs[area] = 0; + return true; +} +void idSoundSystemLocal::LoadReverbData( const char *mapName ) { areaReverbs.Clear(); } diff --git a/src/sound/default/snd_system.cpp b/src/sound/default/snd_system.cpp new file mode 100644 index 0000000..86ee409 --- /dev/null +++ b/src/sound/default/snd_system.cpp @@ -0,0 +1,1629 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +#ifdef Q4_RECONSTRUCTED_SOUND + +idCVar idSoundSystemLocal::s_noSound( "s_noSound", "0", CVAR_SOUND | CVAR_BOOL | CVAR_NOCHEAT, "disable sound output" ); +idCVar idSoundSystemLocal::s_maxSoundsPerShader( "s_maxSoundsPerShader", "0", CVAR_SOUND | CVAR_ARCHIVE | CVAR_INTEGER, "maximum simultaneous sounds for one shader" ); +idCVar idSoundSystemLocal::s_volume( "s_volume_dB", "0", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "volume in dB" ); +idCVar idSoundSystemLocal::s_useOpenAL( "s_useOpenAL", "0", CVAR_SOUND | CVAR_ARCHIVE | CVAR_BOOL, "use OpenAL" ); +idCVar idSoundSystemLocal::s_deviceName( "s_deviceName", "", CVAR_SOUND | CVAR_ARCHIVE, "OpenAL device name" ); + +idSoundSystemLocal soundSystemLocal; +idSoundSystem *soundSystem = &soundSystemLocal; + +s_stats::s_stats() : + rinuse( 0 ), runs( 1 ), timeinprocess( 0 ), missedWindow( 0 ), + missedUpdateWindow( 0 ), activeSounds( 0 ), numPortalChecks( 0 ) { +} + +idSoundSystemLocal::idSoundSystemLocal() : + rw( NULL ), writeDemo( NULL ), listenerPrivateId( 0 ), lastListenerArea( -1 ), + listenerArea( -1 ), listenerEnvironmentID( 0 ), gameMsec( 0 ), game44kHz( 0 ), + lastAVI44kHz( 0 ), activeSoundWorld( SOUNDWORLD_GAME ), snd_audio_hw( NULL ), + soundCache( NULL ), olddwCurrentWritePos( 0 ), buffers( 0 ), CurrentSoundTime( 0 ), + nextWriteBlock( 0 ), finalMixBuffer( NULL ), isInitialized( false ), muted( false ), + shutdown( false ), voiceCommInit( false ), graph( NULL ), openalDevice( NULL ), + openalContext( NULL ), openalSourceCount( 0 ), alEAXSet( NULL ), alEAXGet( NULL ), + alEAXSetBufferMode( NULL ), alEAXGetBufferMode( NULL ), efxloaded( false ), eaxVersion( 0 ) { + listenerAxis.Identity(); + listenerPos.Zero(); + memset( fpa, 0, sizeof( fpa ) ); + memset( localSound, 0, sizeof( localSound ) ); + memset( realAccum, 0, sizeof( realAccum ) ); + memset( meterTops, 0, sizeof( meterTops ) ); + memset( meterTopsTime, 0, sizeof( meterTopsTime ) ); + memset( openalSources, 0, sizeof( openalSources ) ); + memset( efxSlotAvailable, 0, sizeof( efxSlotAvailable ) ); + memset( efxSlotToArea, 0xff, sizeof( efxSlotToArea ) ); + memset( efxSlotId, 0, sizeof( efxSlotId ) ); + for ( int i = 0; i < SOUND_MAX_CLASSES; ++i ) { + soundClassFade[i].Clear(); + } +} + +idSoundSystemLocal::~idSoundSystemLocal() { + Shutdown(); +} + +void idSoundSystemLocal::Init() { + if ( isInitialized ) { + return; + } + finalMixBuffer = (float *)( ( (uintptr_t)realAccum + 15 ) & ~(uintptr_t)15 ); + soundCache = new idSoundCache; + emitters.Append( NULL ); + for ( int world = SOUNDWORLD_GAME; world < SOUNDWORLD_MAX; ++world ) { + localSound[world] = AllocLocalSoundEmitter( world, emitters.Num() ); + } + isInitialized = true; + shutdown = false; + common->Printf( "sound system initialized (reconstructed Q4 software backend)\n" ); +} + +void idSoundSystemLocal::Shutdown() { + if ( shutdown ) { + return; + } + ShutdownVoiceComms(); + ShutdownHW(); + ClearAllSoundEmitters(); + delete soundCache; + soundCache = NULL; + isInitialized = false; + shutdown = true; +} + +void idSoundSystemLocal::ClearBuffer() { + if ( finalMixBuffer != NULL ) { + memset( finalMixBuffer, 0, sizeof( float ) * 6 * MIXBUFFER_SAMPLES ); + } +} + +bool idSoundSystemLocal::InitHW() { return !s_noSound.GetBool(); } +bool idSoundSystemLocal::ShutdownHW() { openalDevice = NULL; openalContext = NULL; openalSourceCount = 0; return true; } +void idSoundSystemLocal::Frame() {} + +int idSoundSystemLocal::AsyncMix( int soundTime, float *mixBuffer ) { + CurrentSoundTime = soundTime; + if ( mixBuffer != NULL ) { + memset( mixBuffer, 0, sizeof( float ) * 6 * MIXBUFFER_SAMPLES ); + } + return soundTime; +} + +int idSoundSystemLocal::AsyncUpdate( int time ) { + CurrentSoundTime = MillisecondsToSamples( time ); + return CurrentSoundTime; +} + +int idSoundSystemLocal::AsyncUpdateWrite( int time ) { return AsyncUpdate( time ); } +void idSoundSystemLocal::SetMute( bool mute ) { muted = mute; if ( mute ) ClearBuffer(); } + +cinData_t idSoundSystemLocal::ImageForTime( int milliseconds, bool waveform ) { + cinData_t data; + memset( &data, 0, sizeof( data ) ); + return data; +} + +int idSoundSystemLocal::GetSoundDecoderInfo( int index, soundDecoderInfo_t &decoderInfo ) { + memset( &decoderInfo, 0, sizeof( decoderInfo ) ); + return 0; +} + +void idSoundSystemLocal::BeginLevelLoad( const char *mapName ) { if ( soundCache ) soundCache->BeginLevelLoad( mapName ); } +void idSoundSystemLocal::EndLevelLoad( const char *mapName ) { if ( soundCache ) soundCache->EndLevelLoad( mapName ); } +void idSoundSystemLocal::PrintMemInfo( MemInfo *mi ) { if ( soundCache ) soundCache->PrintMemInfo( mi ); } +int idSoundSystemLocal::IsEAXAvailable() { return 0; } +const char *idSoundSystemLocal::GetDeviceName( int index ) { return index == 0 ? "Reconstructed software backend" : NULL; } +const char *idSoundSystemLocal::GetDefaultDeviceName() { return "Reconstructed software backend"; } +void idSoundSystemLocal::DisableAllSounds() { StopAllSounds( SOUNDWORLD_ANY ); ClearBuffer(); } +void idSoundSystemLocal::FreeSoundEmitter( int worldId, int handle, bool immediate ) { + idSoundEmitterLocal *emitter = static_cast( EmitterForIndex( worldId, handle ) ); + if ( emitter == NULL ) return; + if ( immediate ) { + emitter->StopSound( SCHANNEL_ANY ); + emitter->removeStatus = REMOVE_STATUS_SAMPLEFINISHED; + } else { + emitter->removeStatus = REMOVE_STATUS_WAITSAMPLEFINISHED; + } +} + +void idSoundSystemLocal::PlayShaderDirectly( int worldId, const char *name, int channel ) { + if ( name == NULL || name[0] == '\0' ) { + if ( worldId >= 0 && worldId < SOUNDWORLD_MAX && localSound[worldId] ) localSound[worldId]->StopSound( channel ); + return; + } + const idSoundShader *shader = declManager->FindSound( name ); + if ( shader != NULL && worldId >= 0 && worldId < SOUNDWORLD_MAX && localSound[worldId] != NULL ) { + localSound[worldId]->StartSound( shader, channel, 0.0f, 0 ); + } +} + +int idSoundSystemLocal::SamplesToMilliseconds( int samples ) const { return (int)( ( (__int64)samples * 1000 ) / PRIMARYFREQ ); } +int idSoundSystemLocal::MillisecondsToSamples( int ms ) const { return (int)( ( (__int64)ms * PRIMARYFREQ ) / 1000 ); } +int idSoundSystemLocal::GetCurrent44kHzTime() const { return CurrentSoundTime; } +int idSoundSystemLocal::GetHardwareTime() const { return CurrentSoundTime; } + +ALuint idSoundSystemLocal::AllocOpenALSource( idSoundChannel *chan, bool looping, bool stereo ) { return 0; } +void idSoundSystemLocal::FreeOpenALSource( ALuint handle ) {} + +size_t idSoundSystemLocal::ListSoundSummary() { return soundCache ? soundCache->ListSoundSummary() : 0; } +bool idSoundSystemLocal::GetInsideLevelLoad() const { return soundCache != NULL && soundCache->insideLevelLoad; } +idSoundSample *idSoundSystemLocal::FindSample( const idStr &filename ) { return soundCache ? soundCache->FindSample( filename ) : NULL; } +bool idSoundSystemLocal::ValidateSoundShader( idSoundShader *shader ) { return shader != NULL; } +bool idSoundSystemLocal::HasCache() const { return soundCache != NULL; } +void idSoundSystemLocal::SetRenderWorld( idRenderWorld *renderWorld ) { rw = renderWorld; } +int idSoundSystemLocal::GetActiveSoundWorld() { return activeSoundWorld; } +int idSoundSystemLocal::GetNumAreas() { return areaReverbs.Num(); } +int idSoundSystemLocal::GetNumEmitters() const { return emitters.Num(); } +int idSoundSystemLocal::SetActiveSoundWorld( bool on ) { activeSoundWorld = on ? SOUNDWORLD_GAME : SOUNDWORLD_MENU; return activeSoundWorld; } +void idSoundSystemLocal::ListSoundDecoders( const idCmdArgs &args ) { common->Printf( "0 sound decoders\n" ); } +void idSoundSystemLocal::ActivateFilter( int filter ) {} +void idSoundSystemLocal::EndCinematic() { ClearBuffer(); } + +void S_InitSoundSystem() { + soundSystemLocal.Init(); +} + +void S_InitDefault() { +} + +#else + +#ifdef ID_DEDICATED +idCVar idSoundSystemLocal::s_noSound( "s_noSound", "1", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "" ); +#else +idCVar idSoundSystemLocal::s_noSound( "s_noSound", "0", CVAR_SOUND | CVAR_BOOL | CVAR_NOCHEAT, "" ); +#endif +idCVar idSoundSystemLocal::s_quadraticFalloff( "s_quadraticFalloff", "1", CVAR_SOUND | CVAR_BOOL, "" ); +idCVar idSoundSystemLocal::s_drawSounds( "s_drawSounds", "0", CVAR_SOUND | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); +idCVar idSoundSystemLocal::s_showStartSound( "s_showStartSound", "0", CVAR_SOUND | CVAR_BOOL, "" ); +idCVar idSoundSystemLocal::s_useOcclusion( "s_useOcclusion", "1", CVAR_SOUND | CVAR_BOOL, "" ); +idCVar idSoundSystemLocal::s_maxSoundsPerShader( "s_maxSoundsPerShader", "0", CVAR_SOUND | CVAR_ARCHIVE, "", 0, 10, idCmdSystem::ArgCompletion_Integer<0,10> ); +idCVar idSoundSystemLocal::s_showLevelMeter( "s_showLevelMeter", "0", CVAR_SOUND | CVAR_BOOL, "" ); +idCVar idSoundSystemLocal::s_constantAmplitude( "s_constantAmplitude", "-1", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_minVolume6( "s_minVolume6", "0", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_dotbias6( "s_dotbias6", "0.8", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_minVolume2( "s_minVolume2", "0.25", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_dotbias2( "s_dotbias2", "1.1", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_spatializationDecay( "s_spatializationDecay", "2", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_reverse( "s_reverse", "0", CVAR_SOUND | CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar idSoundSystemLocal::s_meterTopTime( "s_meterTopTime", "2000", CVAR_SOUND | CVAR_ARCHIVE | CVAR_INTEGER, "" ); +idCVar idSoundSystemLocal::s_volume( "s_volume_dB", "0", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "volume in dB" ); +idCVar idSoundSystemLocal::s_playDefaultSound( "s_playDefaultSound", "1", CVAR_SOUND | CVAR_ARCHIVE | CVAR_BOOL, "play a beep for missing sounds" ); +idCVar idSoundSystemLocal::s_subFraction( "s_subFraction", "0.75", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "volume to subwoofer in 5.1" ); +idCVar idSoundSystemLocal::s_globalFraction( "s_globalFraction", "0.8", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "volume to all speakers when not spatialized" ); +idCVar idSoundSystemLocal::s_doorDistanceAdd( "s_doorDistanceAdd", "150", CVAR_SOUND | CVAR_ARCHIVE | CVAR_FLOAT, "reduce sound volume with this distance when going through a door" ); +idCVar idSoundSystemLocal::s_singleEmitter( "s_singleEmitter", "0", CVAR_SOUND | CVAR_INTEGER, "mute all sounds but this emitter" ); +idCVar idSoundSystemLocal::s_numberOfSpeakers( "s_numberOfSpeakers", "2", CVAR_SOUND | CVAR_ARCHIVE, "number of speakers" ); +idCVar idSoundSystemLocal::s_force22kHz( "s_force22kHz", "0", CVAR_SOUND | CVAR_BOOL, "" ); +idCVar idSoundSystemLocal::s_clipVolumes( "s_clipVolumes", "1", CVAR_SOUND | CVAR_BOOL, "" ); +idCVar idSoundSystemLocal::s_realTimeDecoding( "s_realTimeDecoding", "1", CVAR_SOUND | CVAR_BOOL | CVAR_INIT, "" ); + +idCVar idSoundSystemLocal::s_slowAttenuate( "s_slowAttenuate", "1", CVAR_SOUND | CVAR_BOOL, "slowmo sounds attenuate over shorted distance" ); +idCVar idSoundSystemLocal::s_enviroSuitCutoffFreq( "s_enviroSuitCutoffFreq", "2000", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_enviroSuitCutoffQ( "s_enviroSuitCutoffQ", "2", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_reverbTime( "s_reverbTime", "1000", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_reverbFeedback( "s_reverbFeedback", "0.333", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_enviroSuitVolumeScale( "s_enviroSuitVolumeScale", "0.9", CVAR_SOUND | CVAR_FLOAT, "" ); +idCVar idSoundSystemLocal::s_skipHelltimeFX( "s_skipHelltimeFX", "0", CVAR_SOUND | CVAR_BOOL, "" ); + +#if ID_OPENAL +// off by default. OpenAL DLL gets loaded on-demand +idCVar idSoundSystemLocal::s_libOpenAL( "s_libOpenAL", "openal32.dll", CVAR_SOUND | CVAR_ARCHIVE, "OpenAL DLL name/path" ); +idCVar idSoundSystemLocal::s_useOpenAL( "s_useOpenAL", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ARCHIVE, "use OpenAL" ); +idCVar idSoundSystemLocal::s_useEAXReverb( "s_useEAXReverb", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ARCHIVE, "use EAX reverb" ); +idCVar idSoundSystemLocal::s_muteEAXReverb( "s_muteEAXReverb", "0", CVAR_SOUND | CVAR_BOOL, "mute eax reverb" ); +idCVar idSoundSystemLocal::s_decompressionLimit( "s_decompressionLimit", "6", CVAR_SOUND | CVAR_INTEGER | CVAR_ARCHIVE, "specifies maximum uncompressed sample length in seconds" ); +#else +idCVar idSoundSystemLocal::s_libOpenAL( "s_libOpenAL", "openal32.dll", CVAR_SOUND | CVAR_ARCHIVE, "OpenAL is not supported in this build" ); +idCVar idSoundSystemLocal::s_useOpenAL( "s_useOpenAL", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "OpenAL is not supported in this build" ); +idCVar idSoundSystemLocal::s_useEAXReverb( "s_useEAXReverb", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "EAX not available in this build" ); +idCVar idSoundSystemLocal::s_muteEAXReverb( "s_muteEAXReverb", "0", CVAR_SOUND | CVAR_BOOL | CVAR_ROM, "mute eax reverb" ); +idCVar idSoundSystemLocal::s_decompressionLimit( "s_decompressionLimit", "6", CVAR_SOUND | CVAR_INTEGER | CVAR_ROM, "specifies maximum uncompressed sample length in seconds" ); +#endif + +bool idSoundSystemLocal::useOpenAL = false; +bool idSoundSystemLocal::useEAXReverb = false; +int idSoundSystemLocal::EAXAvailable = -1; + +idSoundSystemLocal soundSystemLocal; +idSoundSystem *soundSystem = &soundSystemLocal; + +/* +=============== +SoundReloadSounds_f + + this is called from the main thread +=============== +*/ +void SoundReloadSounds_f( const idCmdArgs &args ) { + if ( !soundSystemLocal.soundCache ) { + return; + } + bool force = false; + if ( args.Argc() == 2 ) { + force = true; + } + soundSystem->SetMute( true ); + soundSystemLocal.soundCache->ReloadSounds( force ); + soundSystem->SetMute( false ); + common->Printf( "sound: changed sounds reloaded\n" ); +} + +/* +=============== +ListSounds_f + +Optional parameter to only list sounds containing that string +=============== +*/ +void ListSounds_f( const idCmdArgs &args ) { + int i; + const char *snd = args.Argv( 1 ); + + if ( !soundSystemLocal.soundCache ) { + common->Printf( "No sound.\n" ); + return; + } + + int totalSounds = 0; + int totalSamples = 0; + int totalMemory = 0; + int totalPCMMemory = 0; + for( i = 0; i < soundSystemLocal.soundCache->GetNumObjects(); i++ ) { + const idSoundSample *sample = soundSystemLocal.soundCache->GetObject(i); + if ( !sample ) { + continue; + } + if ( snd && sample->name.Find( snd, false ) < 0 ) { + continue; + } + + const waveformatex_t &info = sample->objectInfo; + + const char *stereo = ( info.nChannels == 2 ? "ST" : " " ); + const char *format = ( info.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV"; + const char *defaulted = ( sample->defaultSound ? "(DEFAULTED)" : sample->purged ? "(PURGED)" : "" ); + + common->Printf( "%s %dkHz %6dms %5dkB %4s %s%s\n", stereo, sample->objectInfo.nSamplesPerSec / 1000, + soundSystemLocal.SamplesToMilliseconds( sample->LengthIn44kHzSamples() ), + sample->objectMemSize >> 10, format, sample->name.c_str(), defaulted ); + + if ( !sample->purged ) { + totalSamples += sample->objectSize; + if ( info.wFormatTag != WAVE_FORMAT_TAG_OGG ) + totalPCMMemory += sample->objectMemSize; + if ( !sample->hardwareBuffer ) + totalMemory += sample->objectMemSize; + } + totalSounds++; + } + common->Printf( "%8d total sounds\n", totalSounds ); + common->Printf( "%8d total samples loaded\n", totalSamples ); + common->Printf( "%8d kB total system memory used\n", totalMemory >> 10 ); +#if ID_OPENAL + common->Printf( "%8d kB total OpenAL audio memory used\n", ( alGetInteger( alGetEnumValue( (ALubyte*)"AL_EAX_RAM_SIZE" ) ) - alGetInteger( alGetEnumValue( (ALubyte*)"AL_EAX_RAM_FREE" ) ) ) >> 10 ); +#endif +} + +/* +=============== +ListSoundDecoders_f +=============== +*/ +void ListSoundDecoders_f( const idCmdArgs &args ) { + int i, j, numActiveDecoders, numWaitingDecoders; + idSoundWorldLocal *sw = soundSystemLocal.currentSoundWorld; + + numActiveDecoders = numWaitingDecoders = 0; + + for ( i = 0; i < sw->emitters.Num(); i++ ) { + idSoundEmitterLocal *sound = sw->emitters[i]; + + if ( !sound ) { + continue; + } + + // run through all the channels + for ( j = 0; j < SOUND_MAX_CHANNELS; j++ ) { + idSoundChannel *chan = &sound->channels[j]; + + if ( chan->decoder == NULL ) { + continue; + } + + idSoundSample *sample = chan->decoder->GetSample(); + + if ( sample != NULL ) { + continue; + } + + const char *format = ( chan->leadinSample->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV"; + common->Printf( "%3d waiting %s: %s\n", numWaitingDecoders, format, chan->leadinSample->name.c_str() ); + + numWaitingDecoders++; + } + } + + for ( i = 0; i < sw->emitters.Num(); i++ ) { + idSoundEmitterLocal *sound = sw->emitters[i]; + + if ( !sound ) { + continue; + } + + // run through all the channels + for ( j = 0; j < SOUND_MAX_CHANNELS; j++ ) { + idSoundChannel *chan = &sound->channels[j]; + + if ( chan->decoder == NULL ) { + continue; + } + + idSoundSample *sample = chan->decoder->GetSample(); + + if ( sample == NULL ) { + continue; + } + + const char *format = ( sample->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV"; + + int localTime = soundSystemLocal.GetCurrent44kHzTime() - chan->trigger44kHzTime; + int sampleTime = sample->LengthIn44kHzSamples() * sample->objectInfo.nChannels; + int percent; + if ( localTime > sampleTime ) { + if ( chan->parms.soundShaderFlags & SSF_LOOPING ) { + percent = ( localTime % sampleTime ) * 100 / sampleTime; + } else { + percent = 100; + } + } else { + percent = localTime * 100 / sampleTime; + } + + common->Printf( "%3d decoding %3d%% %s: %s\n", numActiveDecoders, percent, format, sample->name.c_str() ); + + numActiveDecoders++; + } + } + + common->Printf( "%d decoders\n", numWaitingDecoders + numActiveDecoders ); + common->Printf( "%d waiting decoders\n", numWaitingDecoders ); + common->Printf( "%d active decoders\n", numActiveDecoders ); + common->Printf( "%d kB decoder memory in %d blocks\n", idSampleDecoder::GetUsedBlockMemory() >> 10, idSampleDecoder::GetNumUsedBlocks() ); +} + +/* +=============== +TestSound_f + + this is called from the main thread +=============== +*/ +void TestSound_f( const idCmdArgs &args ) { + if ( args.Argc() != 2 ) { + common->Printf( "Usage: testSound \n" ); + return; + } + if ( soundSystemLocal.currentSoundWorld ) { + soundSystemLocal.currentSoundWorld->PlayShaderDirectly( args.Argv( 1 ) ); + } +} + +/* +=============== +SoundSystemRestart_f + +restart the sound thread + + this is called from the main thread +=============== +*/ +void SoundSystemRestart_f( const idCmdArgs &args ) { + soundSystem->SetMute( true ); + soundSystemLocal.ShutdownHW(); + soundSystemLocal.InitHW(); + soundSystem->SetMute( false ); +} + +/* +=============== +idSoundSystemLocal::Init + +initialize the sound system +=============== +*/ +void idSoundSystemLocal::Init() { + + common->Printf( "----- Initializing Sound System ------\n" ); + + isInitialized = false; + muted = false; + shutdown = false; + + currentSoundWorld = NULL; + soundCache = NULL; + + olddwCurrentWritePos = 0; + buffers = 0; + CurrentSoundTime = 0; + + nextWriteBlock = 0xffffffff; + + memset( meterTops, 0, sizeof( meterTops ) ); + memset( meterTopsTime, 0, sizeof( meterTopsTime ) ); + + for( int i = -600; i < 600; i++ ) { + float pt = i * 0.1f; + volumesDB[i+600] = pow( 2.0f,( pt * ( 1.0f / 6.0f ) ) ); + } + + // make a 16 byte aligned finalMixBuffer + finalMixBuffer = (float *) ( ( ( (int)realAccum ) + 15 ) & ~15 ); + + graph = NULL; + + if ( !s_noSound.GetBool() ) { + idSampleDecoder::Init(); + soundCache = new idSoundCache(); + } + + // set up openal device and context + common->StartupVariable( "s_useOpenAL", true ); + common->StartupVariable( "s_useEAXReverb", true ); + + if ( idSoundSystemLocal::s_useOpenAL.GetBool() || idSoundSystemLocal::s_useEAXReverb.GetBool() ) { + if ( !Sys_LoadOpenAL() ) { + idSoundSystemLocal::s_useOpenAL.SetBool( false ); + } else { + common->Printf( "Setup OpenAL device and context... " ); + openalDevice = alcOpenDevice( NULL ); + openalContext = alcCreateContext( openalDevice, NULL ); + alcMakeContextCurrent( openalContext ); + common->Printf( "Done.\n" ); + + // try to obtain EAX extensions + if ( idSoundSystemLocal::s_useEAXReverb.GetBool() && alIsExtensionPresent( ID_ALCHAR "EAX4.0" ) ) { + idSoundSystemLocal::s_useOpenAL.SetBool( true ); // EAX presence causes AL enable + alEAXSet = (EAXSet)alGetProcAddress( ID_ALCHAR "EAXSet" ); + alEAXGet = (EAXGet)alGetProcAddress( ID_ALCHAR "EAXGet" ); + common->Printf( "OpenAL: found EAX 4.0 extension\n" ); + } else { + common->Printf( "OpenAL: EAX 4.0 extension not found\n" ); + idSoundSystemLocal::s_useEAXReverb.SetBool( false ); + alEAXSet = (EAXSet)NULL; + alEAXGet = (EAXGet)NULL; + } + + // try to obtain EAX-RAM extension - not required for operation + if ( alIsExtensionPresent( ID_ALCHAR "EAX-RAM" ) == AL_TRUE ) { + alEAXSetBufferMode = (EAXSetBufferMode)alGetProcAddress( ID_ALCHAR "EAXSetBufferMode" ); + alEAXGetBufferMode = (EAXGetBufferMode)alGetProcAddress( ID_ALCHAR "EAXGetBufferMode" ); + common->Printf( "OpenAL: found EAX-RAM extension, %dkB\\%dkB\n", alGetInteger( alGetEnumValue( ID_ALCHAR "AL_EAX_RAM_FREE" ) ) / 1024, alGetInteger( alGetEnumValue( ID_ALCHAR "AL_EAX_RAM_SIZE" ) ) / 1024 ); + } else { + alEAXSetBufferMode = (EAXSetBufferMode)NULL; + alEAXGetBufferMode = (EAXGetBufferMode)NULL; + common->Printf( "OpenAL: no EAX-RAM extension\n" ); + } + + if ( !idSoundSystemLocal::s_useOpenAL.GetBool() ) { + common->Printf( "OpenAL: disabling ( no EAX ). Using legacy mixer.\n" ); + + alcMakeContextCurrent( NULL ); + + alcDestroyContext( openalContext ); + openalContext = NULL; + + alcCloseDevice( openalDevice ); + openalDevice = NULL; + } else { + + ALuint handle; + openalSourceCount = 0; + + while ( openalSourceCount < 256 ) { + alGetError(); + alGenSources( 1, &handle ); + if ( alGetError() != AL_NO_ERROR ) { + break; + } else { + // store in source array + openalSources[openalSourceCount].handle = handle; + openalSources[openalSourceCount].startTime = 0; + openalSources[openalSourceCount].chan = NULL; + openalSources[openalSourceCount].inUse = false; + openalSources[openalSourceCount].looping = false; + + // initialise sources + alSourcef( handle, AL_ROLLOFF_FACTOR, 0.0f ); + + // found one source + openalSourceCount++; + } + } + + common->Printf( "OpenAL: found %s\n", alcGetString( openalDevice, ALC_DEVICE_SPECIFIER ) ); + common->Printf( "OpenAL: found %d hardware voices\n", openalSourceCount ); + + // adjust source count to allow for at least eight stereo sounds to play + openalSourceCount -= 8; + + EAXAvailable = 1; + } + } + } + + useOpenAL = idSoundSystemLocal::s_useOpenAL.GetBool(); + useEAXReverb = idSoundSystemLocal::s_useEAXReverb.GetBool(); + + cmdSystem->AddCommand( "listSounds", ListSounds_f, CMD_FL_SOUND, "lists all sounds" ); + cmdSystem->AddCommand( "listSoundDecoders", ListSoundDecoders_f, CMD_FL_SOUND, "list active sound decoders" ); + cmdSystem->AddCommand( "reloadSounds", SoundReloadSounds_f, CMD_FL_SOUND|CMD_FL_CHEAT, "reloads all sounds" ); + cmdSystem->AddCommand( "testSound", TestSound_f, CMD_FL_SOUND | CMD_FL_CHEAT, "tests a sound", idCmdSystem::ArgCompletion_SoundName ); + cmdSystem->AddCommand( "s_restart", SoundSystemRestart_f, CMD_FL_SOUND, "restarts the sound system" ); + + common->Printf( "sound system initialized.\n" ); + common->Printf( "--------------------------------------\n" ); +} + +/* +=============== +idSoundSystemLocal::Shutdown +=============== +*/ +void idSoundSystemLocal::Shutdown() { + ShutdownHW(); + + // EAX or not, the list needs to be cleared + EFXDatabase.Clear(); + + // destroy openal sources + if ( useOpenAL ) { + + efxloaded = false; + + // adjust source count back up to allow for freeing of all resources + openalSourceCount += 8; + + for ( ALsizei i = 0; i < openalSourceCount; i++ ) { + // stop source + alSourceStop( openalSources[i].handle ); + alSourcei( openalSources[i].handle, AL_BUFFER, 0 ); + + // delete source + alDeleteSources( 1, &openalSources[i].handle ); + + // clear entry in source array + openalSources[i].handle = NULL; + openalSources[i].startTime = 0; + openalSources[i].chan = NULL; + openalSources[i].inUse = false; + openalSources[i].looping = false; + + } + } + + // destroy all the sounds (hardware buffers as well) + delete soundCache; + soundCache = NULL; + + // destroy openal device and context + if ( useOpenAL ) { + alcMakeContextCurrent( NULL ); + + alcDestroyContext( openalContext ); + openalContext = NULL; + + alcCloseDevice( openalDevice ); + openalDevice = NULL; + } + + Sys_FreeOpenAL(); + + idSampleDecoder::Shutdown(); +} + +/* +=============== +idSoundSystemLocal::InitHW +=============== +*/ +bool idSoundSystemLocal::InitHW() { + + if ( s_noSound.GetBool() ) { + return false; + } + + delete snd_audio_hw; + snd_audio_hw = idAudioHardware::Alloc(); + + if ( snd_audio_hw == NULL ) { + return false; + } + + if ( !useOpenAL ) { + if ( !snd_audio_hw->Initialize() ) { + delete snd_audio_hw; + snd_audio_hw = NULL; + return false; + } + + if ( snd_audio_hw->GetNumberOfSpeakers() == 0 ) { + return false; + } + // put the real number in there + s_numberOfSpeakers.SetInteger( snd_audio_hw->GetNumberOfSpeakers() ); + } + + isInitialized = true; + shutdown = false; + + return true; +} + +/* +=============== +idSoundSystemLocal::ShutdownHW +=============== +*/ +bool idSoundSystemLocal::ShutdownHW() { + if ( !isInitialized ) { + return false; + } + + shutdown = true; // don't do anything at AsyncUpdate() time + Sys_Sleep( 100 ); // sleep long enough to make sure any async sound talking to hardware has returned + + common->Printf( "Shutting down sound hardware\n" ); + + delete snd_audio_hw; + snd_audio_hw = NULL; + + isInitialized = false; + + if ( graph ) { + Mem_Free( graph ); + graph = NULL; + } + + return true; +} + +/* +=============== +idSoundSystemLocal::GetCurrent44kHzTime +=============== +*/ +int idSoundSystemLocal::GetCurrent44kHzTime( void ) const { + if ( snd_audio_hw ) { + return CurrentSoundTime; + } else { + // NOTE: this would overflow 31bits within about 1h20 ( not that important since we get a snd_audio_hw right away pbly ) + //return ( ( Sys_Milliseconds()*441 ) / 10 ) * 4; + return idMath::FtoiFast( (float)Sys_Milliseconds() * 176.4f ); + } +} + +/* +=================== +idSoundSystemLocal::ClearBuffer +=================== +*/ +void idSoundSystemLocal::ClearBuffer( void ) { + + // check to make sure hardware actually exists + if ( !snd_audio_hw ) { + return; + } + + short *fBlock; + ulong fBlockLen; + + if ( !snd_audio_hw->Lock( (void **)&fBlock, &fBlockLen ) ) { + return; + } + + if ( fBlock ) { + SIMDProcessor->Memset( fBlock, 0, fBlockLen ); + snd_audio_hw->Unlock( fBlock, fBlockLen ); + } +} + +/* +=================== +idSoundSystemLocal::AsyncMix +Mac OSX version. The system uses it's own thread and an IOProc callback +=================== +*/ +int idSoundSystemLocal::AsyncMix( int soundTime, float *mixBuffer ) { + int inTime, numSpeakers; + + if ( !isInitialized || shutdown || !snd_audio_hw ) { + return 0; + } + + inTime = Sys_Milliseconds(); + numSpeakers = snd_audio_hw->GetNumberOfSpeakers(); + + // let the active sound world mix all the channels in unless muted or avi demo recording + if ( !muted && currentSoundWorld && !currentSoundWorld->fpa[0] ) { + currentSoundWorld->MixLoop( soundTime, numSpeakers, mixBuffer ); + } + + CurrentSoundTime = soundTime; + + return Sys_Milliseconds() - inTime; +} + +/* +=================== +idSoundSystemLocal::AsyncUpdate +called from async sound thread when com_asyncSound == 1 ( Windows ) +=================== +*/ +int idSoundSystemLocal::AsyncUpdate( int inTime ) { + + if ( !isInitialized || shutdown || !snd_audio_hw ) { + return 0; + } + + ulong dwCurrentWritePos; + dword dwCurrentBlock; + + // If not using openal, get actual playback position from sound hardware + if ( useOpenAL ) { + // here we do it in samples ( overflows in 27 hours or so ) + dwCurrentWritePos = idMath::Ftol( (float)Sys_Milliseconds() * 44.1f ) % ( MIXBUFFER_SAMPLES * ROOM_SLICES_IN_BUFFER ); + dwCurrentBlock = dwCurrentWritePos / MIXBUFFER_SAMPLES; + } else { + // and here in bytes + // get the current byte position in the buffer where the sound hardware is currently reading + if ( !snd_audio_hw->GetCurrentPosition( &dwCurrentWritePos ) ) { + return 0; + } + // mixBufferSize is in bytes + dwCurrentBlock = dwCurrentWritePos / snd_audio_hw->GetMixBufferSize(); + } + + if ( nextWriteBlock == 0xffffffff ) { + nextWriteBlock = dwCurrentBlock; + } + + if ( dwCurrentBlock != nextWriteBlock ) { + return 0; + } + + // lock the buffer so we can actually write to it + short *fBlock = NULL; + ulong fBlockLen = 0; + if ( !useOpenAL ) { + snd_audio_hw->Lock( (void **)&fBlock, &fBlockLen ); + if ( !fBlock ) { + return 0; + } + } + + int j; + soundStats.runs++; + soundStats.activeSounds = 0; + + int numSpeakers = snd_audio_hw->GetNumberOfSpeakers(); + + nextWriteBlock++; + nextWriteBlock %= ROOM_SLICES_IN_BUFFER; + + int newPosition = nextWriteBlock * MIXBUFFER_SAMPLES; + + if ( newPosition < olddwCurrentWritePos ) { + buffers++; // buffer wrapped + } + + // nextWriteSample is in multi-channel samples inside the buffer + int nextWriteSamples = nextWriteBlock * MIXBUFFER_SAMPLES; + + olddwCurrentWritePos = newPosition; + + // newSoundTime is in multi-channel samples since the sound system was started + int newSoundTime = ( buffers * MIXBUFFER_SAMPLES * ROOM_SLICES_IN_BUFFER ) + nextWriteSamples; + + // check for impending overflow + // FIXME: we don't handle sound wrap-around correctly yet + if ( newSoundTime > 0x6fffffff ) { + buffers = 0; + } + + if ( (newSoundTime - CurrentSoundTime) > (int)MIXBUFFER_SAMPLES ) { + soundStats.missedWindow++; + } + + if ( useOpenAL ) { + // enable audio hardware caching + alcSuspendContext( openalContext ); + } else { + // clear the buffer for all the mixing output + SIMDProcessor->Memset( finalMixBuffer, 0, MIXBUFFER_SAMPLES * sizeof(float) * numSpeakers ); + } + + // let the active sound world mix all the channels in unless muted or avi demo recording + if ( !muted && currentSoundWorld && !currentSoundWorld->fpa[0] ) { + currentSoundWorld->MixLoop( newSoundTime, numSpeakers, finalMixBuffer ); + } + + if ( useOpenAL ) { + // disable audio hardware caching (this updates ALL settings since last alcSuspendContext) + alcProcessContext( openalContext ); + } else { + short *dest = fBlock + nextWriteSamples * numSpeakers; + + SIMDProcessor->MixedSoundToSamples( dest, finalMixBuffer, MIXBUFFER_SAMPLES * numSpeakers ); + + // allow swapping the left / right speaker channels for people with miswired systems + if ( numSpeakers == 2 && s_reverse.GetBool() ) { + for( j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + short temp = dest[j*2]; + dest[j*2] = dest[j*2+1]; + dest[j*2+1] = temp; + } + } + snd_audio_hw->Unlock( fBlock, fBlockLen ); + } + + CurrentSoundTime = newSoundTime; + + soundStats.timeinprocess = Sys_Milliseconds() - inTime; + + return soundStats.timeinprocess; +} + +/* +=================== +idSoundSystemLocal::AsyncUpdateWrite +sound output using a write API. all the scheduling based on time +we mix MIXBUFFER_SAMPLES at a time, but we feed the audio device with smaller chunks (and more often) +called by the sound thread when com_asyncSound is 3 ( Linux ) +=================== +*/ +int idSoundSystemLocal::AsyncUpdateWrite( int inTime ) { + + if ( !isInitialized || shutdown || !snd_audio_hw ) { + return 0; + } + + if ( !useOpenAL ) { + snd_audio_hw->Flush(); + } + + unsigned int dwCurrentBlock = (unsigned int)( inTime * 44.1f / MIXBUFFER_SAMPLES ); + + if ( nextWriteBlock == 0xffffffff ) { + nextWriteBlock = dwCurrentBlock; + } + + if ( dwCurrentBlock < nextWriteBlock ) { + return 0; + } + + if ( nextWriteBlock != dwCurrentBlock ) { + Sys_Printf( "missed %d sound updates\n", dwCurrentBlock - nextWriteBlock ); + } + + int sampleTime = dwCurrentBlock * MIXBUFFER_SAMPLES; + int numSpeakers = snd_audio_hw->GetNumberOfSpeakers(); + + if ( useOpenAL ) { + // enable audio hardware caching + alcSuspendContext( openalContext ); + } else { + // clear the buffer for all the mixing output + SIMDProcessor->Memset( finalMixBuffer, 0, MIXBUFFER_SAMPLES * sizeof(float) * numSpeakers ); + } + + // let the active sound world mix all the channels in unless muted or avi demo recording + if ( !muted && currentSoundWorld && !currentSoundWorld->fpa[0] ) { + currentSoundWorld->MixLoop( sampleTime, numSpeakers, finalMixBuffer ); + } + + if ( useOpenAL ) { + // disable audio hardware caching (this updates ALL settings since last alcSuspendContext) + alcProcessContext( openalContext ); + } else { + short *dest = snd_audio_hw->GetMixBuffer(); + + SIMDProcessor->MixedSoundToSamples( dest, finalMixBuffer, MIXBUFFER_SAMPLES * numSpeakers ); + + // allow swapping the left / right speaker channels for people with miswired systems + if ( numSpeakers == 2 && s_reverse.GetBool() ) { + int j; + for( j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + short temp = dest[j*2]; + dest[j*2] = dest[j*2+1]; + dest[j*2+1] = temp; + } + } + snd_audio_hw->Write( false ); + } + + // only move to the next block if the write was successful + nextWriteBlock = dwCurrentBlock + 1; + CurrentSoundTime = sampleTime; + + return Sys_Milliseconds() - inTime; +} + +/* +=================== +idSoundSystemLocal::dB2Scale +=================== +*/ +float idSoundSystemLocal::dB2Scale( const float val ) const { + if ( val == 0.0f ) { + return 1.0f; // most common + } else if ( val <= -60.0f ) { + return 0.0f; + } else if ( val >= 60.0f ) { + return powf( 2.0f, val * ( 1.0f / 6.0f ) ); + } + int ival = (int)( ( val + 60.0f ) * 10.0f ); + return volumesDB[ival]; +} + +/* +=================== +idSoundSystemLocal::ImageForTime +=================== +*/ +cinData_t idSoundSystemLocal::ImageForTime( const int milliseconds, const bool waveform ) { + cinData_t ret; + int i, j; + + if ( !isInitialized || !snd_audio_hw ) { + memset( &ret, 0, sizeof( ret ) ); + return ret; + } + + Sys_EnterCriticalSection(); + + if ( !graph ) { + graph = (dword *)Mem_Alloc( 256*128 * 4); + } + memset( graph, 0, 256*128 * 4 ); + float *accum = finalMixBuffer; // unfortunately, these are already clamped + int time = Sys_Milliseconds(); + + int numSpeakers = snd_audio_hw->GetNumberOfSpeakers(); + + if ( !waveform ) { + for( j = 0; j < numSpeakers; j++ ) { + int meter = 0; + for( i = 0; i < MIXBUFFER_SAMPLES; i++ ) { + float result = idMath::Fabs(accum[i*numSpeakers+j]); + if ( result > meter ) { + meter = result; + } + } + + meter /= 256; // 32768 becomes 128 + if ( meter > 128 ) { + meter = 128; + } + int offset; + int xsize; + if ( numSpeakers == 6 ) { + offset = j * 40; + xsize = 20; + } else { + offset = j * 128; + xsize = 63; + } + int x,y; + dword color = 0xff00ff00; + for ( y = 0; y < 128; y++ ) { + for ( x = 0; x < xsize; x++ ) { + graph[(127-y)*256 + offset + x ] = color; + } +#if 0 + if ( y == 80 ) { + color = 0xff00ffff; + } else if ( y == 112 ) { + color = 0xff0000ff; + } +#endif + if ( y > meter ) { + break; + } + } + + if ( meter > meterTops[j] ) { + meterTops[j] = meter; + meterTopsTime[j] = time + s_meterTopTime.GetInteger(); + } else if ( time > meterTopsTime[j] && meterTops[j] > 0 ) { + meterTops[j]--; + if (meterTops[j]) { + meterTops[j]--; + } + } + } + + for( j = 0; j < numSpeakers; j++ ) { + int meter = meterTops[j]; + + int offset; + int xsize; + if ( numSpeakers == 6 ) { + offset = j*40; + xsize = 20; + } else { + offset = j*128; + xsize = 63; + } + int x,y; + dword color; + if ( meter <= 80 ) { + color = 0xff007f00; + } else if ( meter <= 112 ) { + color = 0xff007f7f; + } else { + color = 0xff00007f; + } + for ( y = meter; y < 128 && y < meter + 4; y++ ) { + for ( x = 0; x < xsize; x++ ) { + graph[(127-y)*256 + offset + x ] = color; + } + } + } + } else { + dword colors[] = { 0xff007f00, 0xff007f7f, 0xff00007f, 0xff00ff00, 0xff00ffff, 0xff0000ff }; + + for( j = 0; j < numSpeakers; j++ ) { + int xx = 0; + float fmeter; + int step = MIXBUFFER_SAMPLES / 256; + for( i = 0; i < MIXBUFFER_SAMPLES; i += step ) { + fmeter = 0.0f; + for( int x = 0; x < step; x++ ) { + float result = accum[(i+x)*numSpeakers+j]; + result = result / 32768.0f; + fmeter += result; + } + fmeter /= 4.0f; + if ( fmeter < -1.0f ) { + fmeter = -1.0f; + } else if ( fmeter > 1.0f ) { + fmeter = 1.0f; + } + int meter = (fmeter * 63.0f); + graph[ (meter + 64) * 256 + xx ] = colors[j]; + + if ( meter < 0 ) { + meter = -meter; + } + if ( meter > meterTops[xx] ) { + meterTops[xx] = meter; + meterTopsTime[xx] = time + 100; + } else if ( time>meterTopsTime[xx] && meterTops[xx] > 0 ) { + meterTops[xx]--; + if ( meterTops[xx] ) { + meterTops[xx]--; + } + } + xx++; + } + } + for( i = 0; i < 256; i++ ) { + int meter = meterTops[i]; + for ( int y = -meter; y < meter; y++ ) { + graph[ (y+64)*256 + i ] = colors[j]; + } + } + } + ret.imageHeight = 128; + ret.imageWidth = 256; + ret.image = (unsigned char *)graph; + + Sys_LeaveCriticalSection(); + + return ret; +} + +/* +=================== +idSoundSystemLocal::GetSoundDecoderInfo +=================== +*/ +int idSoundSystemLocal::GetSoundDecoderInfo( int index, soundDecoderInfo_t &decoderInfo ) { + int i, j, firstEmitter, firstChannel; + idSoundWorldLocal *sw = soundSystemLocal.currentSoundWorld; + + if ( index < 0 ) { + firstEmitter = 0; + firstChannel = 0; + } else { + firstEmitter = index / SOUND_MAX_CHANNELS; + firstChannel = index - firstEmitter * SOUND_MAX_CHANNELS + 1; + } + + for ( i = firstEmitter; i < sw->emitters.Num(); i++ ) { + idSoundEmitterLocal *sound = sw->emitters[i]; + + if ( !sound ) { + continue; + } + + // run through all the channels + for ( j = firstChannel; j < SOUND_MAX_CHANNELS; j++ ) { + idSoundChannel *chan = &sound->channels[j]; + + if ( chan->decoder == NULL ) { + continue; + } + + idSoundSample *sample = chan->decoder->GetSample(); + + if ( sample == NULL ) { + continue; + } + + decoderInfo.name = sample->name; + decoderInfo.format = ( sample->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) ? "OGG" : "WAV"; + decoderInfo.numChannels = sample->objectInfo.nChannels; + decoderInfo.numSamplesPerSecond = sample->objectInfo.nSamplesPerSec; + decoderInfo.num44kHzSamples = sample->LengthIn44kHzSamples(); + decoderInfo.numBytes = sample->objectMemSize; + decoderInfo.looping = ( chan->parms.soundShaderFlags & SSF_LOOPING ) != 0; + decoderInfo.lastVolume = chan->lastVolume; + decoderInfo.start44kHzTime = chan->trigger44kHzTime; + decoderInfo.current44kHzTime = soundSystemLocal.GetCurrent44kHzTime(); + + return ( i * SOUND_MAX_CHANNELS + j ); + } + + firstChannel = 0; + } + return -1; +} + +/* +=================== +idSoundSystemLocal::AllocSoundWorld +=================== +*/ +idSoundWorld *idSoundSystemLocal::AllocSoundWorld( idRenderWorld *rw ) { + idSoundWorldLocal *local = new idSoundWorldLocal; + + local->Init( rw ); + + return local; +} + +/* +=================== +idSoundSystemLocal::SetMute +=================== +*/ +void idSoundSystemLocal::SetMute( bool muteOn ) { + muted = muteOn; +} + +/* +=================== +idSoundSystemLocal::SamplesToMilliseconds +=================== +*/ +int idSoundSystemLocal::SamplesToMilliseconds( int samples ) const { + return ( samples / (PRIMARYFREQ/1000) ); +} + +/* +=================== +idSoundSystemLocal::SamplesToMilliseconds +=================== +*/ +int idSoundSystemLocal::MillisecondsToSamples( int ms ) const { + return ( ms * (PRIMARYFREQ/1000) ); +} + +/* +=================== +idSoundSystemLocal::SetPlayingSoundWorld + +specifying NULL will cause silence to be played +=================== +*/ +void idSoundSystemLocal::SetPlayingSoundWorld( idSoundWorld *soundWorld ) { + currentSoundWorld = static_cast(soundWorld); +} + +/* +=================== +idSoundSystemLocal::GetPlayingSoundWorld +=================== +*/ +idSoundWorld *idSoundSystemLocal::GetPlayingSoundWorld( void ) { + return currentSoundWorld; +} + +/* +=================== +idSoundSystemLocal::BeginLevelLoad +=================== +*/ + +void idSoundSystemLocal::BeginLevelLoad() { + if ( !isInitialized ) { + return; + } + soundCache->BeginLevelLoad(); + + if ( efxloaded ) { + EFXDatabase.UnloadFile(); + efxloaded = false; + } +} + +/* +=================== +idSoundSystemLocal::EndLevelLoad +=================== +*/ +void idSoundSystemLocal::EndLevelLoad( const char *mapstring ) { + if ( !isInitialized ) { + return; + } + soundCache->EndLevelLoad(); + + idStr efxname( "efxs/" ); + idStr mapname( mapstring ); + + mapname.SetFileExtension( ".efx" ); + mapname.StripPath(); + efxname += mapname; + + efxloaded = EFXDatabase.LoadFile( efxname ); + + if ( efxloaded ) { + common->Printf("sound: found %s\n", efxname.c_str() ); + } else { + common->Printf("sound: missing %s\n", efxname.c_str() ); + } +} + +/* +=================== +idSoundSystemLocal::AllocOpenALSource +=================== +*/ +ALuint idSoundSystemLocal::AllocOpenALSource( idSoundChannel *chan, bool looping, bool stereo ) { + int timeOldestZeroVolSingleShot = Sys_Milliseconds(); + int timeOldestZeroVolLooping = Sys_Milliseconds(); + int timeOldestSingle = Sys_Milliseconds(); + int iOldestZeroVolSingleShot = -1; + int iOldestZeroVolLooping = -1; + int iOldestSingle = -1; + int iUnused = -1; + int index = -1; + ALsizei i; + + // Grab current msec time + int time = Sys_Milliseconds(); + + // Cycle through all sources + for ( i = 0; i < openalSourceCount; i++ ) { + // Use any unused source first, + // Then find oldest single shot quiet source, + // Then find oldest looping quiet source and + // Lastly find oldest single shot non quiet source.. + if ( !openalSources[i].inUse ) { + iUnused = i; + break; + } else if ( !openalSources[i].looping && openalSources[i].chan->lastVolume < SND_EPSILON ) { + if ( openalSources[i].startTime < timeOldestZeroVolSingleShot ) { + timeOldestZeroVolSingleShot = openalSources[i].startTime; + iOldestZeroVolSingleShot = i; + } + } else if ( openalSources[i].looping && openalSources[i].chan->lastVolume < SND_EPSILON ) { + if ( openalSources[i].startTime < timeOldestZeroVolLooping ) { + timeOldestZeroVolLooping = openalSources[i].startTime; + iOldestZeroVolLooping = i; + } + } else if ( !openalSources[i].looping ) { + if ( openalSources[i].startTime < timeOldestSingle ) { + timeOldestSingle = openalSources[i].startTime; + iOldestSingle = i; + } + } + } + + if ( iUnused != -1 ) { + index = iUnused; + } else if ( iOldestZeroVolSingleShot != - 1 ) { + index = iOldestZeroVolSingleShot; + } else if ( iOldestZeroVolLooping != -1 ) { + index = iOldestZeroVolLooping; + } else if ( iOldestSingle != -1 ) { + index = iOldestSingle; + } + + if ( index != -1 ) { + // stop the channel that is being ripped off + if ( openalSources[index].chan ) { + // stop the channel only when not looping + if ( !openalSources[index].looping ) { + openalSources[index].chan->Stop(); + } else { + openalSources[index].chan->triggered = true; + } + + // Free hardware resources + openalSources[index].chan->ALStop(); + } + + // Initialize structure + openalSources[index].startTime = time; + openalSources[index].chan = chan; + openalSources[index].inUse = true; + openalSources[index].looping = looping; + openalSources[index].stereo = stereo; + + return openalSources[index].handle; + } else { + return NULL; + } +} + +/* +=================== +idSoundSystemLocal::FreeOpenALSource +=================== +*/ +void idSoundSystemLocal::FreeOpenALSource( ALuint handle ) { + ALsizei i; + for ( i = 0; i < openalSourceCount; i++ ) { + if ( openalSources[i].handle == handle ) { + if ( openalSources[i].chan ) { + openalSources[i].chan->openalSource = NULL; + } +#if ID_OPENAL + // Reset source EAX ROOM level when freeing stereo source + if ( openalSources[i].stereo && alEAXSet ) { + long Room = EAXSOURCE_DEFAULTROOM; + alEAXSet( &EAXPROPERTYID_EAX_Source, EAXSOURCE_ROOM, openalSources[i].handle, &Room, sizeof(Room)); + } +#endif + // Initialize structure + openalSources[i].startTime = 0; + openalSources[i].chan = NULL; + openalSources[i].inUse = false; + openalSources[i].looping = false; + openalSources[i].stereo = false; + } + } +} + +/* +============================================================ +SoundFX and misc effects +============================================================ +*/ + +/* +=================== +idSoundSystemLocal::ProcessSample +=================== +*/ +void SoundFX_Lowpass::ProcessSample( float* in, float* out ) { + float c, a1, a2, a3, b1, b2; + float resonance = idSoundSystemLocal::s_enviroSuitCutoffQ.GetFloat(); + float cutoffFrequency = idSoundSystemLocal::s_enviroSuitCutoffFreq.GetFloat(); + + Initialize(); + + c = 1.0 / idMath::Tan16( idMath::PI * cutoffFrequency / 44100 ); + + // compute coefs + a1 = 1.0 / ( 1.0 + resonance * c + c * c ); + a2 = 2* a1; + a3 = a1; + b1 = 2.0 * ( 1.0 - c * c) * a1; + b2 = ( 1.0 - resonance * c + c * c ) * a1; + + // compute output value + out[0] = a1 * in[0] + a2 * in[-1] + a3 * in[-2] - b1 * out[-1] - b2 * out[-2]; +} + +void SoundFX_LowpassFast::ProcessSample( float* in, float* out ) { + // compute output value + out[0] = a1 * in[0] + a2 * in[-1] + a3 * in[-2] - b1 * out[-1] - b2 * out[-2]; +} + +void SoundFX_LowpassFast::SetParms( float p1, float p2, float p3 ) { + float c; + + // set the vars + freq = p1; + res = p2; + + // precompute the coefs + c = 1.0 / idMath::Tan( idMath::PI * freq / 44100 ); + + // compute coefs + a1 = 1.0 / ( 1.0 + res * c + c * c ); + a2 = 2* a1; + a3 = a1; + + b1 = 2.0 * ( 1.0 - c * c) * a1; + b2 = ( 1.0 - res * c + c * c ) * a1; +} + +void SoundFX_Comb::Initialize() { + if ( initialized ) + return; + + initialized = true; + maxlen = 50000; + buffer = new float[maxlen]; + currentTime = 0; +} + +void SoundFX_Comb::ProcessSample( float* in, float* out ) { + float gain = idSoundSystemLocal::s_reverbFeedback.GetFloat(); + int len = idSoundSystemLocal::s_reverbTime.GetFloat() + param; + + Initialize(); + + // sum up and output + out[0] = buffer[currentTime]; + buffer[currentTime] = buffer[currentTime] * gain + in[0]; + + // increment current time + currentTime++; + if ( currentTime >= len ) + currentTime -= len; +} + +/* +=================== +idSoundSystemLocal::DoEnviroSuit +=================== +*/ +void idSoundSystemLocal::DoEnviroSuit( float* samples, int numSamples, int numSpeakers ) { + float out[10000], *out_p = out + 2; + float in[10000], *in_p = in + 2; + + assert( !idSoundSystemLocal::useOpenAL ); + + if ( !fxList.Num() ) { + for ( int i = 0; i < 6; i++ ) { + SoundFX* fx; + + // lowpass filter + fx = new SoundFX_Lowpass(); + fx->SetChannel( i ); + fxList.Append( fx ); + + // comb + fx = new SoundFX_Comb(); + fx->SetChannel( i ); + fx->SetParameter( i * 100 ); + fxList.Append( fx ); + + // comb + fx = new SoundFX_Comb(); + fx->SetChannel( i ); + fx->SetParameter( i * 100 + 5 ); + fxList.Append( fx ); + } + } + + for ( int i = 0; i < numSpeakers; i++ ) { + int j; + + // restore previous samples + memset( in, 0, 10000 * sizeof( float ) ); + memset( out, 0, 10000 * sizeof( float ) ); + + // fx loop + for ( int k = 0; k < fxList.Num(); k++ ) { + SoundFX* fx = fxList[k]; + + // skip if we're not the right channel + if ( fx->GetChannel() != i ) + continue; + + // get samples and continuity + fx->GetContinuitySamples( in_p[-1], in_p[-2], out_p[-1], out_p[-2] ); + for ( j = 0; j < numSamples; j++ ) { + in_p[j] = samples[j * numSpeakers + i] * s_enviroSuitVolumeScale.GetFloat(); + } + + // process fx loop + for ( j = 0; j < numSamples; j++ ) { + fx->ProcessSample( in_p + j, out_p + j ); + } + + // store samples and continuity + fx->SetContinuitySamples( in_p[numSamples-2], in_p[numSamples-3], out_p[numSamples-2], out_p[numSamples-3] ); + + for ( j = 0; j < numSamples; j++ ) { + samples[j * numSpeakers + i] = out_p[j]; + } + } + } +} + +/* +================= +idSoundSystemLocal::PrintMemInfo +================= +*/ +void idSoundSystemLocal::PrintMemInfo( MemInfo_t *mi ) { + soundCache->PrintMemInfo( mi ); +} + +/* +=============== +idSoundSystemLocal::EAXAvailable +=============== +*/ +int idSoundSystemLocal::IsEAXAvailable( void ) { +#if !ID_OPENAL + return -1; +#else + ALCdevice *device; + ALCcontext *context; + + if ( EAXAvailable != -1 ) { + return EAXAvailable; + } + + if ( !Sys_LoadOpenAL() ) { + EAXAvailable = 2; + return 2; + } + // when dynamically loading the OpenAL subsystem, we need to get a context before alIsExtensionPresent would work + device = alcOpenDevice( NULL ); + context = alcCreateContext( device, NULL ); + alcMakeContextCurrent( context ); + if ( alIsExtensionPresent( ID_ALCHAR "EAX4.0" ) ) { + alcMakeContextCurrent( NULL ); + alcDestroyContext( context ); + alcCloseDevice( device ); + EAXAvailable = 1; + return 1; + } + alcMakeContextCurrent( NULL ); + alcDestroyContext( context ); + alcCloseDevice( device ); + EAXAvailable = 0; + return 0; +#endif +} + +#endif // Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/default/snd_voice.cpp b/src/sound/default/snd_voice.cpp new file mode 100644 index 0000000..5a50036 --- /dev/null +++ b/src/sound/default/snd_voice.cpp @@ -0,0 +1,41 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/* Quake 4 voice-communication compiland (snd_voice.obj). */ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +void idSoundSystemLocal::InitVoiceComms() { voiceCommInit = true; } +void idSoundSystemLocal::ShutdownVoiceComms() { voiceCommInit = false; } +void idSoundSystemLocal::HandleVoiceComms() {} +bool idSoundSystemLocal::EnableRecording( bool enable, bool test, float &micLevel ) { micLevel = 0.0f; return false; } +int idSoundSystemLocal::GetVoiceData( byte *buffer, int maxSize ) { return 0; } +void idSoundSystemLocal::PlayVoiceData( int clientNum, const byte *buffer, int bytes ) {} +void idSoundSystemLocal::BufferVoiceData() {} +void idSoundSystemLocal::MixVoiceData( float *finalMixBuffer, int numSpeakers, int newTime ) {} +int idSoundSystemLocal::GetCommClientNum( int channel ) const { return -1; } +int idSoundSystemLocal::GetNumVoiceChannels() const { return 0; } diff --git a/src/sound/default/snd_wavefile.cpp b/src/sound/default/snd_wavefile.cpp new file mode 100644 index 0000000..88002db --- /dev/null +++ b/src/sound/default/snd_wavefile.cpp @@ -0,0 +1,354 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +#ifndef Q4_RECONSTRUCTED_SOUND + +//----------------------------------------------------------------------------- +// Name: idWaveFile::idWaveFile() +// Desc: Constructs the class. Call Open() to open a wave file for reading. +// Then call Read() as needed. Calling the destructor or Close() +// will close the file. +//----------------------------------------------------------------------------- +idWaveFile::idWaveFile( void ) { + memset( &mpwfx, 0, sizeof( waveformatextensible_t ) ); + mhmmio = NULL; + mdwSize = 0; + mseekBase = 0; + mbIsReadingFromMemory = false; + mpbData = NULL; + ogg = NULL; + isOgg = false; +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::~idWaveFile() +// Desc: Destructs the class +//----------------------------------------------------------------------------- +idWaveFile::~idWaveFile( void ) { + Close(); + + if ( mbIsReadingFromMemory && mpbData ) { + Mem_Free( mpbData ); + } + + memset( &mpwfx, 0, sizeof( waveformatextensible_t ) ); +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::Open() +// Desc: Opens a wave file for reading +//----------------------------------------------------------------------------- +int idWaveFile::Open( const char* strFileName, waveformatex_t* pwfx ) { + + mbIsReadingFromMemory = false; + + mpbData = NULL; + mpbDataCur = mpbData; + + if( strFileName == NULL ) { + return -1; + } + + idStr name = strFileName; + + // note: used to only check for .wav when making a build + name.SetFileExtension( ".ogg" ); + if ( fileSystem->ReadFile( name, NULL, NULL ) != -1 ) { + return OpenOGG( name, pwfx ); + } + + memset( &mpwfx, 0, sizeof( waveformatextensible_t ) ); + + mhmmio = fileSystem->OpenFileRead( strFileName ); + if ( !mhmmio ) { + mdwSize = 0; + return -1; + } + if ( mhmmio->Length() <= 0 ) { + mhmmio = NULL; + return -1; + } + if ( ReadMMIO() != 0 ) { + // ReadMMIO will fail if its an not a wave file + Close(); + return -1; + } + + mfileTime = mhmmio->Timestamp(); + + if ( ResetFile() != 0 ) { + Close(); + return -1; + } + + // After the reset, the size of the wav file is mck.cksize so store it now + mdwSize = mck.cksize / sizeof( short ); + mMemSize = mck.cksize; + + if ( mck.cksize != 0xffffffff ) { + if ( pwfx ) { + memcpy( pwfx, (waveformatex_t *)&mpwfx, sizeof(waveformatex_t)); + } + return 0; + } + return -1; +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::OpenFromMemory() +// Desc: copy data to idWaveFile member variable from memory +//----------------------------------------------------------------------------- +int idWaveFile::OpenFromMemory( short* pbData, int ulDataSize, waveformatextensible_t* pwfx ) { + mpwfx = *pwfx; + mulDataSize = ulDataSize; + mpbData = pbData; + mpbDataCur = mpbData; + mdwSize = ulDataSize / sizeof( short ); + mMemSize = ulDataSize; + mbIsReadingFromMemory = true; + + return 0; +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::ReadMMIO() +// Desc: Support function for reading from a multimedia I/O stream. +// mhmmio must be valid before calling. This function uses it to +// update mckRiff, and mpwfx. +//----------------------------------------------------------------------------- +int idWaveFile::ReadMMIO( void ) { + mminfo_t ckIn; // chunk info. for general use. + pcmwaveformat_t pcmWaveFormat; // Temp PCM structure to load in. + + memset( &mpwfx, 0, sizeof( waveformatextensible_t ) ); + + mhmmio->Read( &mckRiff, 12 ); + assert( !isOgg ); + mckRiff.ckid = LittleLong( mckRiff.ckid ); + mckRiff.cksize = LittleLong( mckRiff.cksize ); + mckRiff.fccType = LittleLong( mckRiff.fccType ); + mckRiff.dwDataOffset = 12; + + // Check to make sure this is a valid wave file + if( (mckRiff.ckid != fourcc_riff) || (mckRiff.fccType != mmioFOURCC('W', 'A', 'V', 'E') ) ) { + return -1; + } + + // Search the input file for for the 'fmt ' chunk. + ckIn.dwDataOffset = 12; + do { + if (8 != mhmmio->Read( &ckIn, 8 ) ) { + return -1; + } + assert( !isOgg ); + ckIn.ckid = LittleLong( ckIn.ckid ); + ckIn.cksize = LittleLong( ckIn.cksize ); + ckIn.dwDataOffset += ckIn.cksize-8; + } while (ckIn.ckid != mmioFOURCC('f', 'm', 't', ' ')); + + // Expect the 'fmt' chunk to be at least as large as ; + // if there are extra parameters at the end, we'll ignore them + if( ckIn.cksize < sizeof(pcmwaveformat_t) ) { + return -1; + } + + // Read the 'fmt ' chunk into . + if( mhmmio->Read( &pcmWaveFormat, sizeof(pcmWaveFormat) ) != sizeof(pcmWaveFormat) ) { + return -1; + } + assert( !isOgg ); + pcmWaveFormat.wf.wFormatTag = LittleShort( pcmWaveFormat.wf.wFormatTag ); + pcmWaveFormat.wf.nChannels = LittleShort( pcmWaveFormat.wf.nChannels ); + pcmWaveFormat.wf.nSamplesPerSec = LittleLong( pcmWaveFormat.wf.nSamplesPerSec ); + pcmWaveFormat.wf.nAvgBytesPerSec = LittleLong( pcmWaveFormat.wf.nAvgBytesPerSec ); + pcmWaveFormat.wf.nBlockAlign = LittleShort( pcmWaveFormat.wf.nBlockAlign ); + pcmWaveFormat.wBitsPerSample = LittleShort( pcmWaveFormat.wBitsPerSample ); + + // Copy the bytes from the pcm structure to the waveformatex_t structure + memcpy( &mpwfx, &pcmWaveFormat, sizeof(pcmWaveFormat) ); + + // Allocate the waveformatex_t, but if its not pcm format, read the next + // word, and thats how many extra bytes to allocate. + if( pcmWaveFormat.wf.wFormatTag == WAVE_FORMAT_TAG_PCM ) { + mpwfx.Format.cbSize = 0; + } else { + return -1; // we don't handle these (32 bit wavefiles, etc) +#if 0 + // Read in length of extra bytes. + word cbExtraBytes = 0L; + if( mhmmio->Read( (char*)&cbExtraBytes, sizeof(word) ) != sizeof(word) ) + return -1; + + mpwfx.Format.cbSize = cbExtraBytes; + + // Now, read those extra bytes into the structure, if cbExtraAlloc != 0. + if( mhmmio->Read( (char*)(((byte*)&(mpwfx.Format.cbSize))+sizeof(word)), cbExtraBytes ) != cbExtraBytes ) { + memset( &mpwfx, 0, sizeof( waveformatextensible_t ) ); + return -1; + } +#endif + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::ResetFile() +// Desc: Resets the internal mck pointer so reading starts from the +// beginning of the file again +//----------------------------------------------------------------------------- +int idWaveFile::ResetFile( void ) { + if( mbIsReadingFromMemory ) { + mpbDataCur = mpbData; + } else { + if( mhmmio == NULL ) { + return -1; + } + + // Seek to the data + if( -1 == mhmmio->Seek( mckRiff.dwDataOffset + sizeof(fourcc), FS_SEEK_SET ) ) { + return -1; + } + + // Search the input file for for the 'fmt ' chunk. + mck.ckid = 0; + do { + byte ioin; + if ( !mhmmio->Read( &ioin, 1 ) ) { + return -1; + } + mck.ckid = (mck.ckid>>8) | (ioin<<24); + } while (mck.ckid != mmioFOURCC('d', 'a', 't', 'a')); + + mhmmio->Read( &mck.cksize, 4 ); + assert( !isOgg ); + mck.cksize = LittleLong( mck.cksize ); + mseekBase = mhmmio->Tell(); + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::Read() +// Desc: Reads section of data from a wave file into pBuffer and returns +// how much read in pdwSizeRead, reading not more than dwSizeToRead. +// This uses mck to determine where to start reading from. So +// subsequent calls will be continue where the last left off unless +// Reset() is called. +//----------------------------------------------------------------------------- +int idWaveFile::Read( byte* pBuffer, int dwSizeToRead, int *pdwSizeRead ) { + + if ( ogg != NULL ) { + + return ReadOGG( pBuffer, dwSizeToRead, pdwSizeRead ); + + } else if ( mbIsReadingFromMemory ) { + + if( mpbDataCur == NULL ) { + return -1; + } + if( (byte*)(mpbDataCur + dwSizeToRead) > (byte*)(mpbData + mulDataSize) ) { + dwSizeToRead = mulDataSize - (int)(mpbDataCur - mpbData); + } + SIMDProcessor->Memcpy( pBuffer, mpbDataCur, dwSizeToRead ); + mpbDataCur += dwSizeToRead; + + if ( pdwSizeRead != NULL ) { + *pdwSizeRead = dwSizeToRead; + } + + return dwSizeToRead; + + } else { + + if( mhmmio == NULL ) { + return -1; + } + if( pBuffer == NULL ) { + return -1; + } + + dwSizeToRead = mhmmio->Read( pBuffer, dwSizeToRead ); + // this is hit by ogg code, which does it's own byte swapping internally + if ( !isOgg ) { + LittleRevBytes( pBuffer, 2, dwSizeToRead / 2 ); + } + + if ( pdwSizeRead != NULL ) { + *pdwSizeRead = dwSizeToRead; + } + + return dwSizeToRead; + } +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::Close() +// Desc: Closes the wave file +//----------------------------------------------------------------------------- +int idWaveFile::Close( void ) { + if ( ogg != NULL ) { + return CloseOGG(); + } + if( mhmmio != NULL ) { + fileSystem->CloseFile( mhmmio ); + mhmmio = NULL; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Name: idWaveFile::Seek() +//----------------------------------------------------------------------------- +int idWaveFile::Seek( int offset ) { + + if ( ogg != NULL ) { + + common->FatalError( "idWaveFile::Seek: cannot seek on an OGG file\n" ); + + } else if( mbIsReadingFromMemory ) { + + mpbDataCur = mpbData + offset; + + } else { + if( mhmmio == NULL ) { + return -1; + } + + if ((int)(offset+mseekBase) == mhmmio->Tell()) { + return 0; + } + mhmmio->Seek( offset + mseekBase, FS_SEEK_SET ); + return 0; + } + return -1; +} + +#endif // !Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/default/snd_world.cpp b/src/sound/default/snd_world.cpp new file mode 100644 index 0000000..b665f6e --- /dev/null +++ b/src/sound/default/snd_world.cpp @@ -0,0 +1,2247 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +#ifdef Q4_RECONSTRUCTED_SOUND + +idSoundEmitterLocal *idSoundSystemLocal::AllocLocalSoundEmitter( int worldId, int requestedIndex ) { + int index = requestedIndex; + if ( index < 1 ) { + for ( int i = 1; i < emitters.Num(); ++i ) { + if ( emitters[i] == NULL || emitters[i]->removeStatus == REMOVE_STATUS_SAMPLEFINISHED ) { index = i; break; } + } + } + if ( index < 1 ) index = emitters.Num(); + while ( emitters.Num() <= index ) emitters.Append( NULL ); + if ( emitters[index] == NULL ) emitters[index] = new idSoundEmitterLocal; + idSoundEmitterLocal *emitter = emitters[index]; + emitter->Clear(); + emitter->index = index; + emitter->worldId = worldId; + emitter->removeStatus = REMOVE_STATUS_ALIVE; + return emitter; +} + +int idSoundSystemLocal::AllocSoundEmitter( int worldId ) { + if ( !isInitialized || worldId <= SOUNDWORLD_NONE || worldId >= SOUNDWORLD_MAX ) return -1; + return AllocLocalSoundEmitter( worldId, -1 )->Handle(); +} + +idSoundEmitter *idSoundSystemLocal::EmitterForIndex( int worldId, int index ) { + if ( index <= 0 || index >= emitters.Num() ) return NULL; + idSoundEmitterLocal *emitter = emitters[index]; + return emitter != NULL && emitter->AttachedToWorld( worldId ) ? emitter : NULL; +} + +void idSoundSystemLocal::ClearAllSoundEmitters() { + for ( int i = 0; i < emitters.Num(); ++i ) delete emitters[i]; + emitters.Clear(); + memset( localSound, 0, sizeof( localSound ) ); +} + +void idSoundSystemLocal::StopAllSounds( int worldId ) { + for ( int i = 1; i < emitters.Num(); ++i ) if ( emitters[i] != NULL && emitters[i]->AttachedToWorld( worldId ) ) emitters[i]->StopSound( SCHANNEL_ANY ); +} + +void idSoundSystemLocal::ResetListener() { + lastListenerArea = listenerArea; + listenerArea = -1; + listenerAreaName.Clear(); +} + +void idSoundSystemLocal::PlaceListener( const idVec3 &origin, const idMat3 &axis, int newListenerId, int newGameTime, const idStr &areaName ) { + listenerPos = origin; + listenerAxis = axis; + listenerPrivateId = newListenerId; + gameMsec = newGameTime; + game44kHz = MillisecondsToSamples( newGameTime ); + listenerAreaName = areaName; + lastListenerArea = listenerArea; + listenerArea = rw != NULL ? rw->PointInArea( origin ) : -1; +} + +void idSoundSystemLocal::ForegroundUpdate() { + if ( !isInitialized || muted ) return; + for ( int i = 1; i < emitters.Num(); ++i ) { + idSoundEmitterLocal *emitter = emitters[i]; + if ( emitter == NULL || emitter->removeStatus == REMOVE_STATUS_SAMPLEFINISHED ) continue; + emitter->CheckForCompletion( CurrentSoundTime ); + if ( emitter->playing ) emitter->Spatialize( listenerPos, listenerArea, rw ); + if ( emitter->removeStatus == REMOVE_STATUS_WAITSAMPLEFINISHED && !emitter->playing ) emitter->removeStatus = REMOVE_STATUS_SAMPLEFINISHED; + } +} + +float idSoundSystemLocal::CurrentShakeAmplitudeForPosition( int worldId, int time, const idVec3 &listenerPosition ) { + float value = 0.0f; + for ( int i = 1; i < emitters.Num(); ++i ) if ( emitters[i] != NULL && emitters[i]->AttachedToWorld( worldId ) && emitters[i]->hasShakes ) value = Max( value, emitters[i]->CurrentAmplitude() ); + return value; +} + +void idSoundSystemLocal::FadeSoundClasses( int worldId, int soundClass, float to, float over ) { + if ( soundClass < 0 || soundClass >= SOUND_MAX_CLASSES ) return; + idSoundFade &fade = soundClassFade[soundClass]; + const int start = GetCurrent44kHzTime(); + fade.fadeStartVolume = fade.FadeDbAt44kHz( start ); + fade.fadeStart44kHz = start; + fade.fadeEnd44kHz = start + (int)( over * PRIMARYFREQ ); + fade.fadeEndVolume = to; +} + +void idSoundSystemLocal::StartWritingDemo( int worldId, idDemoFile *demo ) { writeDemo = demo; } +void idSoundSystemLocal::StopWritingDemo( int worldId ) { writeDemo = NULL; } +void idSoundSystemLocal::ProcessDemoCommand( int worldId, idDemoFile *demo ) {} +void idSoundSystemLocal::WriteToSaveGame( int worldId, idFile *savefile ) {} +void idSoundSystemLocal::ReadFromSaveGame( int worldId, idFile *savefile ) {} +void idSoundSystemLocal::OffsetSoundTime( int offset44kHz ) { CurrentSoundTime += offset44kHz; game44kHz += offset44kHz; } + +void idSoundSystemLocal::AVIOpen( int worldId, const char *path, const char *name ) { + aviDemoPath = path != NULL ? path : ""; + aviDemoName = name != NULL ? name : ""; + lastAVI44kHz = game44kHz; +} +void idSoundSystemLocal::AVIUpdate() {} +void idSoundSystemLocal::AVIClose( int worldId ) { aviDemoPath.Clear(); aviDemoName.Clear(); } + +void idSoundSystemLocal::ListActiveSounds( int worldId ) { + int count = 0; + for ( int i = 1; i < emitters.Num(); ++i ) if ( emitters[i] != NULL && emitters[i]->AttachedToWorld( worldId ) && emitters[i]->CurrentlyPlaying() ) ++count; + common->Printf( "%d active sounds in world %d\n", count, worldId ); +} + +#else + +/* +================== +idSoundWorldLocal::Init +================== +*/ +void idSoundWorldLocal::Init( idRenderWorld *renderWorld ) { + rw = renderWorld; + writeDemo = NULL; + + listenerAxis.Identity(); + listenerPos.Zero(); + listenerPrivateId = 0; + listenerQU.Zero(); + listenerArea = 0; + listenerAreaName = "Undefined"; + listenerEnvironmentID = -2; + + gameMsec = 0; + game44kHz = 0; + pause44kHz = -1; + lastAVI44kHz = 0; + + for ( int i = 0 ; i < SOUND_MAX_CLASSES ; i++ ) { + soundClassFade[i].Clear(); + } + + // fill in the 0 index spot + idSoundEmitterLocal *placeHolder = new idSoundEmitterLocal; + emitters.Append( placeHolder ); + + fpa[0] = fpa[1] = fpa[2] = fpa[3] = fpa[4] = fpa[5] = NULL; + + aviDemoPath = ""; + aviDemoName = ""; + + localSound = NULL; + + slowmoActive = false; + slowmoSpeed = 0; + enviroSuitActive = false; +} + +/* +=============== +idSoundWorldLocal::idSoundWorldLocal +=============== +*/ +idSoundWorldLocal::idSoundWorldLocal() { +} + +/* +=============== +idSoundWorldLocal::~idSoundWorldLocal +=============== +*/ +idSoundWorldLocal::~idSoundWorldLocal() { + Shutdown(); +} + +/* +=============== +idSoundWorldLocal::Shutdown + + this is called from the main thread +=============== +*/ +void idSoundWorldLocal::Shutdown() { + int i; + + if ( soundSystemLocal.currentSoundWorld == this ) { + soundSystemLocal.currentSoundWorld = NULL; + } + + AVIClose(); + + for ( i = 0; i < emitters.Num(); i++ ) { + if ( emitters[i] ) { + delete emitters[i]; + emitters[i] = NULL; + } + } + localSound = NULL; +} + +/* +=================== +idSoundWorldLocal::ClearAllSoundEmitters +=================== +*/ +void idSoundWorldLocal::ClearAllSoundEmitters() { + int i; + + Sys_EnterCriticalSection(); + + AVIClose(); + + for ( i = 0; i < emitters.Num(); i++ ) { + idSoundEmitterLocal *sound = emitters[i]; + sound->Clear(); + } + localSound = NULL; + + Sys_LeaveCriticalSection(); +} + +/* +=================== +idSoundWorldLocal::AllocLocalSoundEmitter +=================== +*/ +idSoundEmitterLocal *idSoundWorldLocal::AllocLocalSoundEmitter() { + int i, index; + idSoundEmitterLocal *def = NULL; + + index = -1; + + // never use the 0 index spot + + for ( i = 1 ; i < emitters.Num() ; i++ ) { + def = emitters[i]; + + // check for a completed and freed spot + if ( def->removeStatus >= REMOVE_STATUS_SAMPLEFINISHED ) { + index = i; + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "sound: recycling sound def %d\n", i ); + } + break; + } + } + + if ( index == -1 ) { + // append a brand new one + def = new idSoundEmitterLocal; + + // we need to protect this from the async thread + Sys_EnterCriticalSection(); + index = emitters.Append( def ); + Sys_LeaveCriticalSection(); + + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "sound: appended new sound def %d\n", index ); + } + } + + def->Clear(); + def->index = index; + def->removeStatus = REMOVE_STATUS_ALIVE; + def->soundWorld = this; + + return def; +} + +/* +=================== +idSoundWorldLocal::AllocSoundEmitter + + this is called from the main thread +=================== +*/ +idSoundEmitter *idSoundWorldLocal::AllocSoundEmitter() { + idSoundEmitterLocal *emitter = AllocLocalSoundEmitter(); + + if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) { + common->Printf( "AllocSoundEmitter = %i\n", emitter->index ); + } + if ( writeDemo ) { + writeDemo->WriteInt( DS_SOUND ); + writeDemo->WriteInt( SCMD_ALLOC_EMITTER ); + writeDemo->WriteInt( emitter->index ); + } + + return emitter; +} + +/* +=================== +idSoundWorldLocal::StartWritingDemo + + this is called from the main thread +=================== +*/ +void idSoundWorldLocal::StartWritingDemo( idDemoFile *demo ) { + writeDemo = demo; + + writeDemo->WriteInt( DS_SOUND ); + writeDemo->WriteInt( SCMD_STATE ); + + // use the normal save game code to archive all the emitters + WriteToSaveGame( writeDemo ); +} + +/* +=================== +idSoundWorldLocal::StopWritingDemo + + this is called from the main thread +=================== +*/ +void idSoundWorldLocal::StopWritingDemo() { + writeDemo = NULL; +} + +/* +=================== +idSoundWorldLocal::ProcessDemoCommand + + this is called from the main thread +=================== +*/ +void idSoundWorldLocal::ProcessDemoCommand( idDemoFile *readDemo ) { + int index; + idSoundEmitterLocal *def; + + if ( !readDemo ) { + return; + } + + soundDemoCommand_t dc; + + if ( !readDemo->ReadInt( (int&)dc ) ) { + return; + } + + switch( dc ) { + case SCMD_STATE: + // we need to protect this from the async thread + // other instances of calling idSoundWorldLocal::ReadFromSaveGame do this while the sound code is muted + // setting muted and going right in may not be good enough here, as we async thread may already be in an async tick (in which case we could still race to it) + Sys_EnterCriticalSection(); + ReadFromSaveGame( readDemo ); + Sys_LeaveCriticalSection(); + UnPause(); + break; + case SCMD_PLACE_LISTENER: + { + idVec3 origin; + idMat3 axis; + int listenerId; + int gameTime; + + readDemo->ReadVec3( origin ); + readDemo->ReadMat3( axis ); + readDemo->ReadInt( listenerId ); + readDemo->ReadInt( gameTime ); + + PlaceListener( origin, axis, listenerId, gameTime, "" ); + }; + break; + case SCMD_ALLOC_EMITTER: + readDemo->ReadInt( index ); + if ( index < 1 || index > emitters.Num() ) { + common->Error( "idSoundWorldLocal::ProcessDemoCommand: bad emitter number" ); + } + if ( index == emitters.Num() ) { + // append a brand new one + def = new idSoundEmitterLocal; + emitters.Append( def ); + } + def = emitters[ index ]; + def->Clear(); + def->index = index; + def->removeStatus = REMOVE_STATUS_ALIVE; + def->soundWorld = this; + break; + case SCMD_FREE: + { + int immediate; + + readDemo->ReadInt( index ); + readDemo->ReadInt( immediate ); + EmitterForIndex( index )->Free( immediate != 0 ); + } + break; + case SCMD_UPDATE: + { + idVec3 origin; + int listenerId; + soundShaderParms_t parms; + + readDemo->ReadInt( index ); + readDemo->ReadVec3( origin ); + readDemo->ReadInt( listenerId ); + readDemo->ReadFloat( parms.minDistance ); + readDemo->ReadFloat( parms.maxDistance ); + readDemo->ReadFloat( parms.volume ); + readDemo->ReadFloat( parms.shakes ); + readDemo->ReadInt( parms.soundShaderFlags ); + readDemo->ReadInt( parms.soundClass ); + EmitterForIndex( index )->UpdateEmitter( origin, listenerId, &parms ); + } + break; + case SCMD_START: + { + const idSoundShader *shader; + int channel; + float diversity; + int shaderFlags; + + readDemo->ReadInt( index ); + shader = declManager->FindSound( readDemo->ReadHashString() ); + readDemo->ReadInt( channel ); + readDemo->ReadFloat( diversity ); + readDemo->ReadInt( shaderFlags ); + EmitterForIndex( index )->StartSound( shader, (s_channelType)channel, diversity, shaderFlags ); + } + break; + case SCMD_MODIFY: + { + int channel; + soundShaderParms_t parms; + + readDemo->ReadInt( index ); + readDemo->ReadInt( channel ); + readDemo->ReadFloat( parms.minDistance ); + readDemo->ReadFloat( parms.maxDistance ); + readDemo->ReadFloat( parms.volume ); + readDemo->ReadFloat( parms.shakes ); + readDemo->ReadInt( parms.soundShaderFlags ); + readDemo->ReadInt( parms.soundClass ); + EmitterForIndex( index )->ModifySound( (s_channelType)channel, &parms ); + } + break; + case SCMD_STOP: + { + int channel; + + readDemo->ReadInt( index ); + readDemo->ReadInt( channel ); + EmitterForIndex( index )->StopSound( (s_channelType)channel ); + } + break; + case SCMD_FADE: + { + int channel; + float to, over; + + readDemo->ReadInt( index ); + readDemo->ReadInt( channel ); + readDemo->ReadFloat( to ); + readDemo->ReadFloat( over ); + EmitterForIndex( index )->FadeSound((s_channelType)channel, to, over ); + } + break; + } +} + +/* +=================== +idSoundWorldLocal::CurrentShakeAmplitudeForPosition + + this is called from the main thread +=================== +*/ +float idSoundWorldLocal::CurrentShakeAmplitudeForPosition( const int time, const idVec3 &listererPosition ) { + float amp = 0.0f; + int localTime; + + if ( idSoundSystemLocal::s_constantAmplitude.GetFloat() >= 0.0f ) { + return 0.0f; + } + + localTime = soundSystemLocal.GetCurrent44kHzTime(); + + for ( int i = 1; i < emitters.Num(); i++ ) { + idSoundEmitterLocal *sound = emitters[i]; + if ( !sound->hasShakes ) { + continue; + } + amp += FindAmplitude( sound, localTime, &listererPosition, SCHANNEL_ANY, true ); + } + return amp; +} + +/* +=================== +idSoundWorldLocal::MixLoop + +Sum all sound contributions into finalMixBuffer, an unclamped float buffer holding +all output channels. MIXBUFFER_SAMPLES samples will be created, with each sample consisting +of 2 or 6 floats depending on numSpeakers. + +this is normally called from the sound thread, but also from the main thread +for AVIdemo writing +=================== +*/ +void idSoundWorldLocal::MixLoop( int current44kHz, int numSpeakers, float *finalMixBuffer ) { + int i, j; + idSoundEmitterLocal *sound; + + // if noclip flying outside the world, leave silence + if ( listenerArea == -1 ) { + if ( idSoundSystemLocal::useOpenAL ) + alListenerf( AL_GAIN, 0.0f ); + return; + } + + // update the listener position and orientation + if ( idSoundSystemLocal::useOpenAL ) { + ALfloat listenerPosition[3]; + + listenerPosition[0] = -listenerPos.y; + listenerPosition[1] = listenerPos.z; + listenerPosition[2] = -listenerPos.x; + + ALfloat listenerOrientation[6]; + + listenerOrientation[0] = -listenerAxis[0].y; + listenerOrientation[1] = listenerAxis[0].z; + listenerOrientation[2] = -listenerAxis[0].x; + + listenerOrientation[3] = -listenerAxis[2].y; + listenerOrientation[4] = listenerAxis[2].z; + listenerOrientation[5] = -listenerAxis[2].x; + + alListenerf( AL_GAIN, 1.0f ); + alListenerfv( AL_POSITION, listenerPosition ); + alListenerfv( AL_ORIENTATION, listenerOrientation ); + +#if ID_OPENAL + if ( soundSystemLocal.s_useEAXReverb.GetBool() ) { + if ( soundSystemLocal.efxloaded ) { + idSoundEffect *effect = NULL; + int EnvironmentID = -1; + idStr defaultStr( "default" ); + idStr listenerAreaStr( listenerArea ); + + soundSystemLocal.EFXDatabase.FindEffect( listenerAreaStr, &effect, &EnvironmentID ); + if (!effect) + soundSystemLocal.EFXDatabase.FindEffect( listenerAreaName, &effect, &EnvironmentID ); + if (!effect) + soundSystemLocal.EFXDatabase.FindEffect( defaultStr, &effect, &EnvironmentID ); + + // only update if change in settings + if ( soundSystemLocal.s_muteEAXReverb.GetBool() || ( listenerEnvironmentID != EnvironmentID ) ) { + EAXREVERBPROPERTIES EnvironmentParameters; + + // get area reverb setting from EAX Manager + if ( ( effect ) && ( effect->data) && ( memcpy( &EnvironmentParameters, effect->data, effect->datasize ) ) ) { + if ( soundSystemLocal.s_muteEAXReverb.GetBool() ) { + EnvironmentParameters.lRoom = -10000; + EnvironmentID = -2; + } + if ( soundSystemLocal.alEAXSet ) { + soundSystemLocal.alEAXSet( &EAXPROPERTYID_EAX_FXSlot0, EAXREVERB_ALLPARAMETERS, 0, &EnvironmentParameters, sizeof( EnvironmentParameters ) ); + } + } + listenerEnvironmentID = EnvironmentID; + } + } + } +#endif + } + + // debugging option to mute all but a single soundEmitter + if ( idSoundSystemLocal::s_singleEmitter.GetInteger() > 0 && idSoundSystemLocal::s_singleEmitter.GetInteger() < emitters.Num() ) { + sound = emitters[idSoundSystemLocal::s_singleEmitter.GetInteger()]; + + if ( sound && sound->playing ) { + // run through all the channels + for ( j = 0; j < SOUND_MAX_CHANNELS ; j++ ) { + idSoundChannel *chan = &sound->channels[j]; + + // see if we have a sound triggered on this channel + if ( !chan->triggerState ) { + chan->ALStop(); + continue; + } + + AddChannelContribution( sound, chan, current44kHz, numSpeakers, finalMixBuffer ); + } + } + return; + } + + for ( i = 1; i < emitters.Num(); i++ ) { + sound = emitters[i]; + + if ( !sound ) { + continue; + } + // if no channels are active, do nothing + if ( !sound->playing ) { + continue; + } + // run through all the channels + for ( j = 0; j < SOUND_MAX_CHANNELS ; j++ ) { + idSoundChannel *chan = &sound->channels[j]; + + // see if we have a sound triggered on this channel + if ( !chan->triggerState ) { + chan->ALStop(); + continue; + } + + AddChannelContribution( sound, chan, current44kHz, numSpeakers, finalMixBuffer ); + } + } + + if ( !idSoundSystemLocal::useOpenAL && enviroSuitActive ) { + soundSystemLocal.DoEnviroSuit( finalMixBuffer, MIXBUFFER_SAMPLES, numSpeakers ); + } +} + +//============================================================================== + +/* +=================== +idSoundWorldLocal::AVIOpen + + this is called by the main thread +=================== +*/ +void idSoundWorldLocal::AVIOpen( const char *path, const char *name ) { + aviDemoPath = path; + aviDemoName = name; + + lastAVI44kHz = game44kHz - game44kHz % MIXBUFFER_SAMPLES; + + if ( soundSystemLocal.snd_audio_hw->GetNumberOfSpeakers() == 6 ) { + fpa[0] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_left.raw" ); + fpa[1] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_right.raw" ); + fpa[2] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_center.raw" ); + fpa[3] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_lfe.raw" ); + fpa[4] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_backleft.raw" ); + fpa[5] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_backright.raw" ); + } else { + fpa[0] = fileSystem->OpenFileWrite( aviDemoPath + "channel_left.raw" ); + fpa[1] = fileSystem->OpenFileWrite( aviDemoPath + "channel_right.raw" ); + } + + soundSystemLocal.SetMute( true ); +} + +/* +=================== +idSoundWorldLocal::AVIUpdate + +this is called by the main thread +writes one block of sound samples if enough time has passed +This can be used to write wave files even if no sound hardware exists +=================== +*/ +void idSoundWorldLocal::AVIUpdate() { + int numSpeakers; + + if ( game44kHz - lastAVI44kHz < MIXBUFFER_SAMPLES ) { + return; + } + + if ( !soundSystemLocal.snd_audio_hw ) { + numSpeakers = 2; + } else { + numSpeakers = soundSystemLocal.snd_audio_hw->GetNumberOfSpeakers(); + } + + float mix[MIXBUFFER_SAMPLES*6+16]; + float *mix_p = (float *)((( int)mix + 15 ) & ~15); // SIMD align + + SIMDProcessor->Memset( mix_p, 0, MIXBUFFER_SAMPLES*sizeof(float)*numSpeakers ); + + MixLoop( lastAVI44kHz, numSpeakers, mix_p ); + + for ( int i = 0; i < numSpeakers; i++ ) { + short outD[MIXBUFFER_SAMPLES]; + + for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) { + float s = mix_p[ j*numSpeakers + i]; + if ( s < -32768.0f ) { + outD[j] = -32768; + } else if ( s > 32767.0f ) { + outD[j] = 32767; + } else { + outD[j] = idMath::FtoiFast( s ); + } + } + // write to file + fpa[i]->Write( outD, MIXBUFFER_SAMPLES*sizeof(short) ); + } + + lastAVI44kHz += MIXBUFFER_SAMPLES; + + return; +} + +/* +=================== +idSoundWorldLocal::AVIClose +=================== +*/ +void idSoundWorldLocal::AVIClose( void ) { + int i; + + if ( !fpa[0] ) { + return; + } + + // make sure the final block is written + game44kHz += MIXBUFFER_SAMPLES; + AVIUpdate(); + game44kHz -= MIXBUFFER_SAMPLES; + + for ( i = 0; i < 6; i++ ) { + if ( fpa[i] != NULL ) { + fileSystem->CloseFile( fpa[i] ); + fpa[i] = NULL; + } + } + if ( soundSystemLocal.snd_audio_hw->GetNumberOfSpeakers() == 2 ) { + // convert it to a wave file + idFile *rL, *lL, *wO; + idStr name; + + name = aviDemoPath + aviDemoName + ".wav"; + wO = fileSystem->OpenFileWrite( name ); + if ( !wO ) { + common->Error( "Couldn't write %s", name.c_str() ); + } + + name = aviDemoPath + "channel_right.raw"; + rL = fileSystem->OpenFileRead( name ); + if ( !rL ) { + common->Error( "Couldn't open %s", name.c_str() ); + } + + name = aviDemoPath + "channel_left.raw"; + lL = fileSystem->OpenFileRead( name ); + if ( !lL ) { + common->Error( "Couldn't open %s", name.c_str() ); + } + + int numSamples = rL->Length()/2; + mminfo_t info; + pcmwaveformat_t format; + + info.ckid = fourcc_riff; + info.fccType = mmioFOURCC( 'W', 'A', 'V', 'E' ); + info.cksize = (rL->Length()*2) - 8 + 4 + 16 + 8 + 8; + info.dwDataOffset = 12; + + wO->Write( &info, 12 ); + + info.ckid = mmioFOURCC( 'f', 'm', 't', ' ' ); + info.cksize = 16; + + wO->Write( &info, 8 ); + + format.wBitsPerSample = 16; + format.wf.nAvgBytesPerSec = 44100*4; // sample rate * block align + format.wf.nChannels = 2; + format.wf.nSamplesPerSec = 44100; + format.wf.wFormatTag = WAVE_FORMAT_TAG_PCM; + format.wf.nBlockAlign = 4; // channels * bits/sample / 8 + + wO->Write( &format, 16 ); + + info.ckid = mmioFOURCC( 'd', 'a', 't', 'a' ); + info.cksize = rL->Length() * 2; + + wO->Write( &info, 8 ); + + short s0, s1; + for( i = 0; i < numSamples; i++ ) { + lL->Read( &s0, 2 ); + rL->Read( &s1, 2 ); + wO->Write( &s0, 2 ); + wO->Write( &s1, 2 ); + } + + fileSystem->CloseFile( wO ); + fileSystem->CloseFile( lL ); + fileSystem->CloseFile( rL ); + + fileSystem->RemoveFile( aviDemoPath + "channel_right.raw" ); + fileSystem->RemoveFile( aviDemoPath + "channel_left.raw" ); + } + + soundSystemLocal.SetMute( false ); +} + +//============================================================================== + + +/* +=================== +idSoundWorldLocal::ResolveOrigin + +Find out of the sound is completely occluded by a closed door portal, or +the virtual sound origin position at the portal closest to the listener. + this is called by the main thread + +dist is the distance from the orignial sound origin to the current portal that enters soundArea +def->distance is the distance we are trying to reduce. + +If there is no path through open portals from the sound to the listener, def->distance will remain +set at maxDistance +=================== +*/ +static const int MAX_PORTAL_TRACE_DEPTH = 10; + +void idSoundWorldLocal::ResolveOrigin( const int stackDepth, const soundPortalTrace_t *prevStack, const int soundArea, const float dist, const idVec3& soundOrigin, idSoundEmitterLocal *def ) { + + if ( dist >= def->distance ) { + // we can't possibly hear the sound through this chain of portals + return; + } + + if ( soundArea == listenerArea ) { + float fullDist = dist + (soundOrigin - listenerQU).LengthFast(); + if ( fullDist < def->distance ) { + def->distance = fullDist; + def->spatializedOrigin = soundOrigin; + } + return; + } + + if ( stackDepth == MAX_PORTAL_TRACE_DEPTH ) { + // don't spend too much time doing these calculations in big maps + return; + } + + soundPortalTrace_t newStack; + newStack.portalArea = soundArea; + newStack.prevStack = prevStack; + + int numPortals = rw->NumPortalsInArea( soundArea ); + for( int p = 0; p < numPortals; p++ ) { + exitPortal_t re = rw->GetPortal( soundArea, p ); + + float occlusionDistance = 0; + + // air blocking windows will block sound like closed doors + if ( (re.blockingBits & ( PS_BLOCK_VIEW | PS_BLOCK_AIR ) ) ) { + // we could just completely cut sound off, but reducing the volume works better + // continue; + occlusionDistance = idSoundSystemLocal::s_doorDistanceAdd.GetFloat(); + } + + // what area are we about to go look at + int otherArea = re.areas[0]; + if ( re.areas[0] == soundArea ) { + otherArea = re.areas[1]; + } + + // if this area is already in our portal chain, don't bother looking into it + const soundPortalTrace_t *prev; + for ( prev = prevStack ; prev ; prev = prev->prevStack ) { + if ( prev->portalArea == otherArea ) { + break; + } + } + if ( prev ) { + continue; + } + + // pick a point on the portal to serve as our virtual sound origin +#if 1 + idVec3 source; + + idPlane pl; + re.w->GetPlane( pl ); + + float scale; + idVec3 dir = listenerQU - soundOrigin; + if ( !pl.RayIntersection( soundOrigin, dir, scale ) ) { + source = re.w->GetCenter(); + } else { + source = soundOrigin + scale * dir; + + // if this point isn't inside the portal edges, slide it in + for ( int i = 0 ; i < re.w->GetNumPoints() ; i++ ) { + int j = ( i + 1 ) % re.w->GetNumPoints(); + idVec3 edgeDir = (*(re.w))[j].ToVec3() - (*(re.w))[i].ToVec3(); + idVec3 edgeNormal; + + edgeNormal.Cross( pl.Normal(), edgeDir ); + + idVec3 fromVert = source - (*(re.w))[j].ToVec3(); + + float d = edgeNormal * fromVert; + if ( d > 0 ) { + // move it in + float div = edgeNormal.Normalize(); + d /= div; + + source -= d * edgeNormal; + } + } + } +#else + // clip the ray from the listener to the center of the portal by + // all the portal edge planes, then project that point (or the original if not clipped) + // onto the portal plane to get the spatialized origin + + idVec3 start = listenerQU; + idVec3 mid = re.w->GetCenter(); + bool wasClipped = false; + + for ( int i = 0 ; i < re.w->GetNumPoints() ; i++ ) { + int j = ( i + 1 ) % re.w->GetNumPoints(); + idVec3 v1 = (*(re.w))[j].ToVec3() - soundOrigin; + idVec3 v2 = (*(re.w))[i].ToVec3() - soundOrigin; + + v1.Normalize(); + v2.Normalize(); + + idVec3 edgeNormal; + + edgeNormal.Cross( v1, v2 ); + + idVec3 fromVert = start - soundOrigin; + float d1 = edgeNormal * fromVert; + + if ( d1 > 0.0f ) { + fromVert = mid - (*(re.w))[j].ToVec3(); + float d2 = edgeNormal * fromVert; + + // move it in + float f = d1 / ( d1 - d2 ); + + idVec3 clipped = start * ( 1.0f - f ) + mid * f; + start = clipped; + wasClipped = true; + } + } + + idVec3 source; + if ( wasClipped ) { + // now project it onto the portal plane + idPlane pl; + re.w->GetPlane( pl ); + + float f1 = pl.Distance( start ); + float f2 = pl.Distance( soundOrigin ); + + float f = f1 / ( f1 - f2 ); + source = start * ( 1.0f - f ) + soundOrigin * f; + } else { + source = soundOrigin; + } +#endif + + idVec3 tlen = source - soundOrigin; + float tlenLength = tlen.LengthFast(); + + ResolveOrigin( stackDepth+1, &newStack, otherArea, dist+tlenLength+occlusionDistance, source, def ); + } +} + + +/* +=================== +idSoundWorldLocal::PlaceListener + + this is called by the main thread +=================== +*/ +void idSoundWorldLocal::PlaceListener( const idVec3& origin, const idMat3& axis, + const int listenerId, const int gameTime, const idStr& areaName ) { + + int current44kHzTime; + + if ( !soundSystemLocal.isInitialized ) { + return; + } + + if ( pause44kHz >= 0 ){ + return; + } + + if ( writeDemo ) { + writeDemo->WriteInt( DS_SOUND ); + writeDemo->WriteInt( SCMD_PLACE_LISTENER ); + writeDemo->WriteVec3( origin ); + writeDemo->WriteMat3( axis ); + writeDemo->WriteInt( listenerId ); + writeDemo->WriteInt( gameTime ); + } + + current44kHzTime = soundSystemLocal.GetCurrent44kHzTime(); + + // we usually expect gameTime to be increasing by 16 or 32 msec, but when + // a cinematic is fast-forward skipped through, it can jump by a significant + // amount, while the hardware 44kHz position will not have changed accordingly, + // which would make sounds (like long character speaches) continue from the + // old time. Fix this by killing all non-looping sounds + if ( gameTime > gameMsec + 500 ) { + OffsetSoundTime( - ( gameTime - gameMsec ) * 0.001f * 44100.0f ); + } + + gameMsec = gameTime; + if ( fpa[0] ) { + // exactly 30 fps so the wave file can be used for exact video frames + game44kHz = idMath::FtoiFast( gameMsec * ( ( 1000.0f / 60.0f ) / 16.0f ) * 0.001f * 44100.0f ); + } else { + // the normal 16 msec / frame + game44kHz = idMath::FtoiFast( gameMsec * 0.001f * 44100.0f ); + } + + listenerPrivateId = listenerId; + + listenerQU = origin; // Doom units + listenerPos = origin * DOOM_TO_METERS; // meters + listenerAxis = axis; + listenerAreaName = areaName; + listenerAreaName.ToLower(); + + if ( rw ) { + listenerArea = rw->PointInArea( listenerQU ); // where are we? + } else { + listenerArea = 0; + } + + if ( listenerArea < 0 ) { + return; + } + + ForegroundUpdate( current44kHzTime ); +} + +/* +================== +idSoundWorldLocal::ForegroundUpdate +================== +*/ +void idSoundWorldLocal::ForegroundUpdate( int current44kHzTime ) { + int j, k; + idSoundEmitterLocal *def; + + if ( !soundSystemLocal.isInitialized ) { + return; + } + + Sys_EnterCriticalSection(); + + // if we are recording an AVI demo, don't use hardware time + if ( fpa[0] ) { + current44kHzTime = lastAVI44kHz; + } + + // + // check to see if each sound is visible or not + // speed up by checking maxdistance to origin + // although the sound may still need to play if it has + // just become occluded so it can ramp down to 0 + // + for ( j = 1; j < emitters.Num(); j++ ) { + def = emitters[j]; + + if ( def->removeStatus >= REMOVE_STATUS_SAMPLEFINISHED ) { + continue; + } + + // see if our last channel just finished + def->CheckForCompletion( current44kHzTime ); + + if ( !def->playing ) { + continue; + } + + // update virtual origin / distance, etc + def->Spatialize( listenerPos, listenerArea, rw ); + + // per-sound debug options + if ( idSoundSystemLocal::s_drawSounds.GetInteger() && rw ) { + if ( def->distance < def->maxDistance || idSoundSystemLocal::s_drawSounds.GetInteger() > 1 ) { + idBounds ref; + ref.Clear(); + ref.AddPoint( idVec3( -10, -10, -10 ) ); + ref.AddPoint( idVec3( 10, 10, 10 ) ); + float vis = (1.0f - (def->distance / def->maxDistance)); + + // draw a box + rw->DebugBounds( idVec4( vis, 0.25f, vis, vis ), ref, def->origin ); + + // draw an arrow to the audible position, possible a portal center + if ( def->origin != def->spatializedOrigin ) { + rw->DebugArrow( colorRed, def->origin, def->spatializedOrigin, 4 ); + } + + // draw the index + idVec3 textPos = def->origin; + textPos[2] -= 8; + rw->DrawText( va("%i", def->index), textPos, 0.1f, idVec4(1,0,0,1), listenerAxis ); + textPos[2] += 8; + + // run through all the channels + for ( k = 0; k < SOUND_MAX_CHANNELS ; k++ ) { + idSoundChannel *chan = &def->channels[k]; + + // see if we have a sound triggered on this channel + if ( !chan->triggerState ) { + continue; + } + + char text[1024]; + float min = chan->parms.minDistance; + float max = chan->parms.maxDistance; + const char *defaulted = chan->leadinSample->defaultSound ? "(DEFAULTED)" : ""; + sprintf( text, "%s (%i/%i %i/%i)%s", chan->soundShader->GetName(), (int)def->distance, + (int)def->realDistance, (int)min, (int)max, defaulted ); + rw->DrawText( text, textPos, 0.1f, idVec4(1,0,0,1), listenerAxis ); + textPos[2] += 8; + } + } + } + } + + Sys_LeaveCriticalSection(); + + // + // the sound meter + // + if ( idSoundSystemLocal::s_showLevelMeter.GetInteger() ) { + const idMaterial *gui = declManager->FindMaterial( "guis/assets/soundmeter/audiobg", false ); + if ( gui ) { + const shaderStage_t *foo = gui->GetStage(0); + if ( !foo->texture.cinematic ) { + ((shaderStage_t *)foo)->texture.cinematic = new idSndWindow; + } + } + } + + // + // optionally dump out the generated sound + // + if ( fpa[0] ) { + AVIUpdate(); + } +} + +/* +=================== +idSoundWorldLocal::OffsetSoundTime +=================== +*/ +void idSoundWorldLocal::OffsetSoundTime( int offset44kHz ) { + int i, j; + + for ( i = 0; i < emitters.Num(); i++ ) { + if ( emitters[i] == NULL ) { + continue; + } + for ( j = 0; j < SOUND_MAX_CHANNELS; j++ ) { + idSoundChannel *chan = &emitters[i]->channels[ j ]; + + if ( !chan->triggerState ) { + continue; + } + + chan->trigger44kHzTime += offset44kHz; + } + } +} + +/* +=================== +idSoundWorldLocal::WriteToSaveGame +=================== +*/ +void idSoundWorldLocal::WriteToSaveGame( idFile *savefile ) { + int i, j, num, currentSoundTime; + const char *name; + + // the game soundworld is always paused at this point, save that time down + if ( pause44kHz > 0 ) { + currentSoundTime = pause44kHz; + } else { + currentSoundTime = soundSystemLocal.GetCurrent44kHzTime(); + } + + // write listener data + savefile->WriteVec3(listenerQU); + savefile->WriteMat3(listenerAxis); + savefile->WriteInt(listenerPrivateId); + savefile->WriteInt(gameMsec); + savefile->WriteInt(game44kHz); + savefile->WriteInt(currentSoundTime); + + num = emitters.Num(); + savefile->WriteInt(num); + + for ( i = 1; i < emitters.Num(); i++ ) { + idSoundEmitterLocal *def = emitters[i]; + + if ( def->removeStatus != REMOVE_STATUS_ALIVE ) { + int skip = -1; + savefile->Write( &skip, sizeof( skip ) ); + continue; + } + + savefile->WriteInt(i); + + // Write the emitter data + savefile->WriteVec3( def->origin ); + savefile->WriteInt( def->listenerId ); + WriteToSaveGameSoundShaderParams( savefile, &def->parms ); + savefile->WriteFloat( def->amplitude ); + savefile->WriteInt( def->ampTime ); + for (int k = 0; k < SOUND_MAX_CHANNELS; k++) + WriteToSaveGameSoundChannel( savefile, &def->channels[k] ); + savefile->WriteFloat( def->distance ); + savefile->WriteBool( def->hasShakes ); + savefile->WriteInt( def->lastValidPortalArea ); + savefile->WriteFloat( def->maxDistance ); + savefile->WriteBool( def->playing ); + savefile->WriteFloat( def->realDistance ); + savefile->WriteInt( def->removeStatus ); + savefile->WriteVec3( def->spatializedOrigin ); + + // write the channel data + for( j = 0; j < SOUND_MAX_CHANNELS; j++ ) { + idSoundChannel *chan = &def->channels[ j ]; + + // Write out any sound commands for this def + if ( chan->triggerState && chan->soundShader && chan->leadinSample ) { + + savefile->WriteInt( j ); + + // write the pointers out separately + name = chan->soundShader->GetName(); + savefile->WriteString( name ); + + name = chan->leadinSample->name; + savefile->WriteString( name ); + } + } + + // End active channels with -1 + int end = -1; + savefile->WriteInt( end ); + } + + // new in Doom3 v1.2 + savefile->Write( &slowmoActive, sizeof( slowmoActive ) ); + savefile->Write( &slowmoSpeed, sizeof( slowmoSpeed ) ); + savefile->Write( &enviroSuitActive, sizeof( enviroSuitActive ) ); +} + +/* + =================== + idSoundWorldLocal::WriteToSaveGameSoundShaderParams + =================== + */ +void idSoundWorldLocal::WriteToSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ) { + saveGame->WriteFloat(params->minDistance); + saveGame->WriteFloat(params->maxDistance); + saveGame->WriteFloat(params->volume); + saveGame->WriteFloat(params->shakes); + saveGame->WriteInt(params->soundShaderFlags); + saveGame->WriteInt(params->soundClass); +} + +/* + =================== + idSoundWorldLocal::WriteToSaveGameSoundChannel + =================== + */ +void idSoundWorldLocal::WriteToSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ) { + saveGame->WriteBool( ch->triggerState ); + saveGame->WriteUnsignedChar( 0 ); + saveGame->WriteUnsignedChar( 0 ); + saveGame->WriteUnsignedChar( 0 ); + saveGame->WriteInt( ch->trigger44kHzTime ); + saveGame->WriteInt( ch->triggerGame44kHzTime ); + WriteToSaveGameSoundShaderParams( saveGame, &ch->parms ); + saveGame->WriteInt( (int)ch->leadinSample ); + saveGame->WriteInt( ch->triggerChannel ); + saveGame->WriteInt( (int)ch->soundShader ); + saveGame->WriteInt( (int)ch->decoder ); + saveGame->WriteFloat(ch->diversity ); + saveGame->WriteFloat(ch->lastVolume ); + for (int m = 0; m < 6; m++) + saveGame->WriteFloat( ch->lastV[m] ); + saveGame->WriteInt( ch->channelFade.fadeStart44kHz ); + saveGame->WriteInt( ch->channelFade.fadeEnd44kHz ); + saveGame->WriteFloat( ch->channelFade.fadeStartVolume ); + saveGame->WriteFloat( ch->channelFade.fadeEndVolume ); +} + +/* +=================== +idSoundWorldLocal::ReadFromSaveGame +=================== +*/ +void idSoundWorldLocal::ReadFromSaveGame( idFile *savefile ) { + int i, num, handle, listenerId, gameTime, channel; + int savedSoundTime, currentSoundTime, soundTimeOffset; + idSoundEmitterLocal *def; + idVec3 origin; + idMat3 axis; + idStr soundShader; + + ClearAllSoundEmitters(); + + savefile->ReadVec3( origin ); + savefile->ReadMat3( axis ); + savefile->ReadInt( listenerId ); + savefile->ReadInt( gameTime ); + savefile->ReadInt( game44kHz ); + savefile->ReadInt( savedSoundTime ); + + // we will adjust the sound starting times from those saved with the demo + currentSoundTime = soundSystemLocal.GetCurrent44kHzTime(); + soundTimeOffset = currentSoundTime - savedSoundTime; + + // at the end of the level load we unpause the sound world and adjust the sound starting times once more + pause44kHz = currentSoundTime; + + // place listener + PlaceListener( origin, axis, listenerId, gameTime, "Undefined" ); + + // make sure there are enough + // slots to read the saveGame in. We don't shrink the list + // if there are extras. + savefile->ReadInt( num ); + + while( emitters.Num() < num ) { + def = new idSoundEmitterLocal; + def->index = emitters.Append( def ); + def->soundWorld = this; + } + + // read in the state + for ( i = 1; i < num; i++ ) { + + savefile->ReadInt( handle ); + if ( handle < 0 ) { + continue; + } + if ( handle != i ) { + common->Error( "idSoundWorldLocal::ReadFromSaveGame: index mismatch" ); + } + def = emitters[i]; + + def->removeStatus = REMOVE_STATUS_ALIVE; + def->playing = true; // may be reset by the first UpdateListener + + savefile->ReadVec3( def->origin ); + savefile->ReadInt( def->listenerId ); + ReadFromSaveGameSoundShaderParams( savefile, &def->parms ); + savefile->ReadFloat( def->amplitude ); + savefile->ReadInt( def->ampTime ); + for (int k = 0; k < SOUND_MAX_CHANNELS; k++) + ReadFromSaveGameSoundChannel( savefile, &def->channels[k] ); + savefile->ReadFloat( def->distance ); + savefile->ReadBool( def->hasShakes ); + savefile->ReadInt( def->lastValidPortalArea ); + savefile->ReadFloat( def->maxDistance ); + savefile->ReadBool( def->playing ); + savefile->ReadFloat( def->realDistance ); + savefile->ReadInt( (int&)def->removeStatus ); + savefile->ReadVec3( def->spatializedOrigin ); + + // read the individual channels + savefile->ReadInt( channel ); + + while ( channel >= 0 ) { + if ( channel > SOUND_MAX_CHANNELS ) { + common->Error( "idSoundWorldLocal::ReadFromSaveGame: channel > SOUND_MAX_CHANNELS" ); + } + + idSoundChannel *chan = &def->channels[channel]; + + if ( chan->decoder != NULL ) { + // The pointer in the save file is not valid, so we grab a new one + chan->decoder = idSampleDecoder::Alloc(); + } + + savefile->ReadString( soundShader ); + chan->soundShader = declManager->FindSound( soundShader ); + + savefile->ReadString( soundShader ); + // load savegames with s_noSound 1 + if ( soundSystemLocal.soundCache ) { + chan->leadinSample = soundSystemLocal.soundCache->FindSound( soundShader, false ); + } else { + chan->leadinSample = NULL; + } + + // adjust the hardware start time + chan->trigger44kHzTime += soundTimeOffset; + + // make sure we start up the hardware voice if needed + chan->triggered = chan->triggerState; + chan->openalStreamingOffset = currentSoundTime - chan->trigger44kHzTime; + + // adjust the hardware fade time + if ( chan->channelFade.fadeStart44kHz != 0 ) { + chan->channelFade.fadeStart44kHz += soundTimeOffset; + chan->channelFade.fadeEnd44kHz += soundTimeOffset; + } + + // next command + savefile->ReadInt( channel ); + } + } + + if ( session->GetSaveGameVersion() >= 17 ) { + savefile->Read( &slowmoActive, sizeof( slowmoActive ) ); + savefile->Read( &slowmoSpeed, sizeof( slowmoSpeed ) ); + savefile->Read( &enviroSuitActive, sizeof( enviroSuitActive ) ); + } else { + slowmoActive = false; + slowmoSpeed = 0; + enviroSuitActive = false; + } +} + +/* + =================== + idSoundWorldLocal::ReadFromSaveGameSoundShaderParams + =================== + */ +void idSoundWorldLocal::ReadFromSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ) { + saveGame->ReadFloat(params->minDistance); + saveGame->ReadFloat(params->maxDistance); + saveGame->ReadFloat(params->volume); + saveGame->ReadFloat(params->shakes); + saveGame->ReadInt(params->soundShaderFlags); + saveGame->ReadInt(params->soundClass); +} + +/* + =================== + idSoundWorldLocal::ReadFromSaveGameSoundChannel + =================== + */ +void idSoundWorldLocal::ReadFromSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ) { + saveGame->ReadBool( ch->triggerState ); + char tmp; + saveGame->ReadChar( tmp ); + saveGame->ReadChar( tmp ); + saveGame->ReadChar( tmp ); + saveGame->ReadInt( ch->trigger44kHzTime ); + saveGame->ReadInt( ch->triggerGame44kHzTime ); + ReadFromSaveGameSoundShaderParams( saveGame, &ch->parms ); + saveGame->ReadInt( (int&)ch->leadinSample ); + saveGame->ReadInt( ch->triggerChannel ); + saveGame->ReadInt( (int&)ch->soundShader ); + saveGame->ReadInt( (int&)ch->decoder ); + saveGame->ReadFloat(ch->diversity ); + saveGame->ReadFloat(ch->lastVolume ); + for (int m = 0; m < 6; m++) + saveGame->ReadFloat( ch->lastV[m] ); + saveGame->ReadInt( ch->channelFade.fadeStart44kHz ); + saveGame->ReadInt( ch->channelFade.fadeEnd44kHz ); + saveGame->ReadFloat( ch->channelFade.fadeStartVolume ); + saveGame->ReadFloat( ch->channelFade.fadeEndVolume ); +} + +/* +=================== +idSoundWorldLocal::EmitterForIndex +=================== +*/ +idSoundEmitter *idSoundWorldLocal::EmitterForIndex( int index ) { + if ( index == 0 ) { + return NULL; + } + if ( index >= emitters.Num() ) { + common->Error( "idSoundWorldLocal::EmitterForIndex: %i > %i", index, emitters.Num() ); + } + return emitters[index]; +} + +/* +=============== +idSoundWorldLocal::StopAllSounds + + this is called from the main thread +=============== +*/ +void idSoundWorldLocal::StopAllSounds() { + + for ( int i = 0; i < emitters.Num(); i++ ) { + idSoundEmitterLocal * def = emitters[i]; + def->StopSound( SCHANNEL_ANY ); + } +} + +/* +=============== +idSoundWorldLocal::Pause +=============== +*/ +void idSoundWorldLocal::Pause( void ) { + if ( pause44kHz >= 0 ) { + common->Warning( "idSoundWorldLocal::Pause: already paused" ); + return; + } + + pause44kHz = soundSystemLocal.GetCurrent44kHzTime(); +} + +/* +=============== +idSoundWorldLocal::UnPause +=============== +*/ +void idSoundWorldLocal::UnPause( void ) { + int offset44kHz; + + if ( pause44kHz < 0 ) { + common->Warning( "idSoundWorldLocal::UnPause: not paused" ); + return; + } + + offset44kHz = soundSystemLocal.GetCurrent44kHzTime() - pause44kHz; + OffsetSoundTime( offset44kHz ); + + pause44kHz = -1; +} + +/* +=============== +idSoundWorldLocal::IsPaused +=============== +*/ +bool idSoundWorldLocal::IsPaused( void ) { + return ( pause44kHz >= 0 ); +} + +/* +=============== +idSoundWorldLocal::PlayShaderDirectly + +start a music track + + this is called from the main thread +=============== +*/ +void idSoundWorldLocal::PlayShaderDirectly( const char *shaderName, int channel ) { + + if ( localSound && channel == -1 ) { + localSound->StopSound( SCHANNEL_ANY ); + } else if ( localSound ) { + localSound->StopSound( channel ); + } + + if ( !shaderName || !shaderName[0] ) { + return; + } + + const idSoundShader *shader = declManager->FindSound( shaderName ); + if ( !shader ) { + return; + } + + if ( !localSound ) { + localSound = AllocLocalSoundEmitter(); + } + + static idRandom rnd; + float diversity = rnd.RandomFloat(); + + localSound->StartSound( shader, ( channel == -1 ) ? SCHANNEL_ONE : channel , diversity, SSF_GLOBAL ); + + // in case we are at the console without a game doing updates, force an update + ForegroundUpdate( soundSystemLocal.GetCurrent44kHzTime() ); +} + +/* +=============== +idSoundWorldLocal::CalcEars + +Determine the volumes from each speaker for a given sound emitter +=============== +*/ +void idSoundWorldLocal::CalcEars( int numSpeakers, idVec3 spatializedOrigin, idVec3 listenerPos, + idMat3 listenerAxis, float ears[6], float spatialize ) { + idVec3 svec = spatializedOrigin - listenerPos; + idVec3 ovec; + + ovec[0] = svec * listenerAxis[0]; + ovec[1] = svec * listenerAxis[1]; + ovec[2] = svec * listenerAxis[2]; + + ovec.Normalize(); + + if ( numSpeakers == 6 ) { + static idVec3 speakerVector[6] = { + idVec3( 0.707f, 0.707f, 0.0f ), // front left + idVec3( 0.707f, -0.707f, 0.0f ), // front right + idVec3( 0.707f, 0.0f, 0.0f ), // front center + idVec3( 0.0f, 0.0f, 0.0f ), // sub + idVec3( -0.707f, 0.707f, 0.0f ), // rear left + idVec3( -0.707f, -0.707f, 0.0f ) // rear right + }; + for ( int i = 0 ; i < 6 ; i++ ) { + if ( i == 3 ) { + ears[i] = idSoundSystemLocal::s_subFraction.GetFloat(); // subwoofer + continue; + } + float dot = ovec * speakerVector[i]; + ears[i] = (idSoundSystemLocal::s_dotbias6.GetFloat() + dot) / ( 1.0f + idSoundSystemLocal::s_dotbias6.GetFloat() ); + if ( ears[i] < idSoundSystemLocal::s_minVolume6.GetFloat() ) { + ears[i] = idSoundSystemLocal::s_minVolume6.GetFloat(); + } + } + } else { + float dot = ovec.y; + float dotBias = idSoundSystemLocal::s_dotbias2.GetFloat(); + + // when we are inside the minDistance, start reducing the amount of spatialization + // so NPC voices right in front of us aren't quieter that off to the side + dotBias += ( idSoundSystemLocal::s_spatializationDecay.GetFloat() - dotBias ) * ( 1.0f - spatialize ); + + ears[0] = (idSoundSystemLocal::s_dotbias2.GetFloat() + dot) / ( 1.0f + dotBias ); + ears[1] = (idSoundSystemLocal::s_dotbias2.GetFloat() - dot) / ( 1.0f + dotBias ); + + if ( ears[0] < idSoundSystemLocal::s_minVolume2.GetFloat() ) { + ears[0] = idSoundSystemLocal::s_minVolume2.GetFloat(); + } + if ( ears[1] < idSoundSystemLocal::s_minVolume2.GetFloat() ) { + ears[1] = idSoundSystemLocal::s_minVolume2.GetFloat(); + } + + ears[2] = + ears[3] = + ears[4] = + ears[5] = 0.0f; + } +} + +/* +=============== +idSoundWorldLocal::AddChannelContribution + +Adds the contribution of a single sound channel to finalMixBuffer +this is called from the async thread + +Mixes MIXBUFFER_SAMPLES samples starting at current44kHz sample time into +finalMixBuffer +=============== +*/ +void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSoundChannel *chan, + int current44kHz, int numSpeakers, float *finalMixBuffer ) { + int j; + float volume; + + // + // get the sound definition and parameters from the entity + // + soundShaderParms_t *parms = &chan->parms; + + // assume we have a sound triggered on this channel + assert( chan->triggerState ); + + // fetch the actual wave file and see if it's valid + idSoundSample *sample = chan->leadinSample; + if ( sample == NULL ) { + return; + } + + // if you don't want to hear all the beeps from missing sounds + if ( sample->defaultSound && !idSoundSystemLocal::s_playDefaultSound.GetBool() ) { + return; + } + + // get the actual shader + const idSoundShader *shader = chan->soundShader; + + // this might happen if the foreground thread just deleted the sound emitter + if ( !shader ) { + return; + } + + float maxd = parms->maxDistance; + float mind = parms->minDistance; + + int mask = shader->speakerMask; + bool omni = ( parms->soundShaderFlags & SSF_OMNIDIRECTIONAL) != 0; + bool looping = ( parms->soundShaderFlags & SSF_LOOPING ) != 0; + bool global = ( parms->soundShaderFlags & SSF_GLOBAL ) != 0; + bool noOcclusion = ( parms->soundShaderFlags & SSF_NO_OCCLUSION ) || !idSoundSystemLocal::s_useOcclusion.GetBool(); + + // speed goes from 1 to 0.2 + if ( idSoundSystemLocal::s_slowAttenuate.GetBool() && slowmoActive && !chan->disallowSlow ) { + maxd *= slowmoSpeed; + } + + // stereo samples are always omni + if ( sample->objectInfo.nChannels == 2 ) { + omni = true; + } + + // if the sound is playing from the current listener, it will not be spatialized at all + if ( sound->listenerId == listenerPrivateId ) { + global = true; + } + + // + // see if it's in range + // + + // convert volumes from decibels to float scale + + // leadin volume scale for shattering lights + // this isn't exactly correct, because the modified volume will get applied to + // some initial chunk of the loop as well, because the volume is scaled for the + // entire mix buffer + if ( shader->leadinVolume && current44kHz - chan->trigger44kHzTime < sample->LengthIn44kHzSamples() ) { + volume = soundSystemLocal.dB2Scale( shader->leadinVolume ); + } else { + volume = soundSystemLocal.dB2Scale( parms->volume ); + } + + // global volume scale + volume *= soundSystemLocal.dB2Scale( idSoundSystemLocal::s_volume.GetFloat() ); + + + // volume fading + float fadeDb = chan->channelFade.FadeDbAt44kHz( current44kHz ); + volume *= soundSystemLocal.dB2Scale( fadeDb ); + + fadeDb = soundClassFade[parms->soundClass].FadeDbAt44kHz( current44kHz ); + volume *= soundSystemLocal.dB2Scale( fadeDb ); + + + // + // if it's a global sound then + // it's not affected by distance or occlusion + // + float spatialize = 1; + idVec3 spatializedOriginInMeters; + if ( !global ) { + float dlen; + + if ( noOcclusion ) { + // use the real origin and distance + spatializedOriginInMeters = sound->origin * DOOM_TO_METERS; + dlen = sound->realDistance; + } else { + // use the possibly portal-occluded origin and distance + spatializedOriginInMeters = sound->spatializedOrigin * DOOM_TO_METERS; + dlen = sound->distance; + } + + // reduce volume based on distance + if ( dlen >= maxd ) { + volume = 0.0f; + } else if ( dlen > mind ) { + float frac = idMath::ClampFloat( 0.0f, 1.0f, 1.0f - ((dlen - mind) / (maxd - mind))); + if ( idSoundSystemLocal::s_quadraticFalloff.GetBool() ) { + frac *= frac; + } + volume *= frac; + } else if ( mind > 0.0f ) { + // we tweak the spatialization bias when you are inside the minDistance + spatialize = dlen / mind; + } + } + + // + // if it is a private sound, set the volume to zero + // unless we match the listenerId + // + if ( parms->soundShaderFlags & SSF_PRIVATE_SOUND ) { + if ( sound->listenerId != listenerPrivateId ) { + volume = 0; + } + } + if ( parms->soundShaderFlags & SSF_ANTI_PRIVATE_SOUND ) { + if ( sound->listenerId == listenerPrivateId ) { + volume = 0; + } + } + + // + // do we have anything to add? + // + if ( volume < SND_EPSILON && chan->lastVolume < SND_EPSILON ) { + return; + } + chan->lastVolume = volume; + + // + // fetch the sound from the cache as 44kHz, 16 bit samples + // + int offset = current44kHz - chan->trigger44kHzTime; + float inputSamples[MIXBUFFER_SAMPLES*2+16]; + float *alignedInputSamples = (float *) ( ( ( (int)inputSamples ) + 15 ) & ~15 ); + + // + // allocate and initialize hardware source + // + if ( idSoundSystemLocal::useOpenAL && sound->removeStatus < REMOVE_STATUS_SAMPLEFINISHED ) { + if ( !alIsSource( chan->openalSource ) ) { + chan->openalSource = soundSystemLocal.AllocOpenALSource( chan, !chan->leadinSample->hardwareBuffer || !chan->soundShader->entries[0]->hardwareBuffer || looping, chan->leadinSample->objectInfo.nChannels == 2 ); + } + + if ( alIsSource( chan->openalSource ) ) { + + // stop source if needed.. + if ( chan->triggered ) { + alSourceStop( chan->openalSource ); + } + + // update source parameters + if ( global || omni ) { + alSourcei( chan->openalSource, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f( chan->openalSource, AL_POSITION, 0.0f, 0.0f, 0.0f ); + alSourcef( chan->openalSource, AL_GAIN, ( volume ) < ( 1.0f ) ? ( volume ) : ( 1.0f ) ); + } else { + alSourcei( chan->openalSource, AL_SOURCE_RELATIVE, AL_FALSE); + alSource3f( chan->openalSource, AL_POSITION, -spatializedOriginInMeters.y, spatializedOriginInMeters.z, -spatializedOriginInMeters.x ); + alSourcef( chan->openalSource, AL_GAIN, ( volume ) < ( 1.0f ) ? ( volume ) : ( 1.0f ) ); + } + alSourcei( chan->openalSource, AL_LOOPING, ( looping && chan->soundShader->entries[0]->hardwareBuffer ) ? AL_TRUE : AL_FALSE ); +#if !defined(MACOS_X) + alSourcef( chan->openalSource, AL_REFERENCE_DISTANCE, mind ); + alSourcef( chan->openalSource, AL_MAX_DISTANCE, maxd ); +#endif + alSourcef( chan->openalSource, AL_PITCH, ( slowmoActive && !chan->disallowSlow ) ? ( slowmoSpeed ) : ( 1.0f ) ); +#if ID_OPENAL + long lOcclusion = ( enviroSuitActive ? -1150 : 0); + if ( soundSystemLocal.alEAXSet ) { + soundSystemLocal.alEAXSet( &EAXPROPERTYID_EAX_Source, EAXSOURCE_OCCLUSION, chan->openalSource, &lOcclusion, sizeof(lOcclusion) ); + } +#endif + if ( ( !looping && chan->leadinSample->hardwareBuffer ) || ( looping && chan->soundShader->entries[0]->hardwareBuffer ) ) { + // handle uncompressed (non streaming) single shot and looping sounds + if ( chan->triggered ) { + alSourcei( chan->openalSource, AL_BUFFER, looping ? chan->soundShader->entries[0]->openalBuffer : chan->leadinSample->openalBuffer ); + } + } else { + ALint finishedbuffers; + ALuint buffers[3]; + + // handle streaming sounds (decode on the fly) both single shot AND looping + if ( chan->triggered ) { + alSourcei( chan->openalSource, AL_BUFFER, NULL ); + alDeleteBuffers( 3, &chan->lastopenalStreamingBuffer[0] ); + chan->lastopenalStreamingBuffer[0] = chan->openalStreamingBuffer[0]; + chan->lastopenalStreamingBuffer[1] = chan->openalStreamingBuffer[1]; + chan->lastopenalStreamingBuffer[2] = chan->openalStreamingBuffer[2]; + alGenBuffers( 3, &chan->openalStreamingBuffer[0] ); + if ( soundSystemLocal.alEAXSetBufferMode ) { + soundSystemLocal.alEAXSetBufferMode( 3, &chan->openalStreamingBuffer[0], alGetEnumValue( ID_ALCHAR "AL_STORAGE_ACCESSIBLE" ) ); + } + buffers[0] = chan->openalStreamingBuffer[0]; + buffers[1] = chan->openalStreamingBuffer[1]; + buffers[2] = chan->openalStreamingBuffer[2]; + finishedbuffers = 3; + } else { + alGetSourcei( chan->openalSource, AL_BUFFERS_PROCESSED, &finishedbuffers ); + alSourceUnqueueBuffers( chan->openalSource, finishedbuffers, &buffers[0] ); + if ( finishedbuffers == 3 ) { + chan->triggered = true; + } + } + + for ( j = 0; j < finishedbuffers; j++ ) { + chan->GatherChannelSamples( chan->openalStreamingOffset * sample->objectInfo.nChannels, MIXBUFFER_SAMPLES * sample->objectInfo.nChannels, alignedInputSamples ); + for ( int i = 0; i < ( MIXBUFFER_SAMPLES * sample->objectInfo.nChannels ); i++ ) { + if ( alignedInputSamples[i] < -32768.0f ) + ((short *)alignedInputSamples)[i] = -32768; + else if ( alignedInputSamples[i] > 32767.0f ) + ((short *)alignedInputSamples)[i] = 32767; + else + ((short *)alignedInputSamples)[i] = idMath::FtoiFast( alignedInputSamples[i] ); + } + alBufferData( buffers[j], chan->leadinSample->objectInfo.nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, alignedInputSamples, MIXBUFFER_SAMPLES * sample->objectInfo.nChannels * sizeof( short ), 44100 ); + chan->openalStreamingOffset += MIXBUFFER_SAMPLES; + } + + if ( finishedbuffers ) { + alSourceQueueBuffers( chan->openalSource, finishedbuffers, &buffers[0] ); + } + } + + // (re)start if needed.. + if ( chan->triggered ) { + alSourcePlay( chan->openalSource ); + chan->triggered = false; + } + } + } else { + + if ( slowmoActive && !chan->disallowSlow ) { + idSlowChannel slow = sound->GetSlowChannel( chan ); + + slow.AttachSoundChannel( chan ); + + if ( sample->objectInfo.nChannels == 2 ) { + // need to add a stereo path, but very few samples go through this + memset( alignedInputSamples, 0, sizeof( alignedInputSamples[0] ) * MIXBUFFER_SAMPLES * 2 ); + } else { + slow.GatherChannelSamples( offset, MIXBUFFER_SAMPLES, alignedInputSamples ); + } + + sound->SetSlowChannel( chan, slow ); + } else { + sound->ResetSlowChannel( chan ); + + // if we are getting a stereo sample adjust accordingly + if ( sample->objectInfo.nChannels == 2 ) { + // we should probably check to make sure any looping is also to a stereo sample... + chan->GatherChannelSamples( offset*2, MIXBUFFER_SAMPLES*2, alignedInputSamples ); + } else { + chan->GatherChannelSamples( offset, MIXBUFFER_SAMPLES, alignedInputSamples ); + } + } + + // + // work out the left / right ear values + // + float ears[6]; + if ( global || omni ) { + // same for all speakers + for ( int i = 0 ; i < 6 ; i++ ) { + ears[i] = idSoundSystemLocal::s_globalFraction.GetFloat() * volume; + } + ears[3] = idSoundSystemLocal::s_subFraction.GetFloat() * volume; // subwoofer + + } else { + CalcEars( numSpeakers, spatializedOriginInMeters, listenerPos, listenerAxis, ears, spatialize ); + + for ( int i = 0 ; i < 6 ; i++ ) { + ears[i] *= volume; + } + } + + // if the mask is 0, it really means do every channel + if ( !mask ) { + mask = 255; + } + // cleared mask bits set the mix volume to zero + for ( int i = 0 ; i < 6 ; i++ ) { + if ( !(mask & ( 1 << i ) ) ) { + ears[i] = 0; + } + } + + // if sounds are generally normalized, using a mixing volume over 1.0 will + // almost always cause clipping noise. If samples aren't normalized, there + // is a good call to allow overvolumes + if ( idSoundSystemLocal::s_clipVolumes.GetBool() && !( parms->soundShaderFlags & SSF_UNCLAMPED ) ) { + for ( int i = 0 ; i < 6 ; i++ ) { + if ( ears[i] > 1.0f ) { + ears[i] = 1.0f; + } + } + } + + // if this is the very first mixing block, set the lastV + // to the current volume + if ( current44kHz == chan->trigger44kHzTime ) { + for ( j = 0 ; j < 6 ; j++ ) { + chan->lastV[j] = ears[j]; + } + } + + if ( numSpeakers == 6 ) { + if ( sample->objectInfo.nChannels == 1 ) { + SIMDProcessor->MixSoundSixSpeakerMono( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); + } else { + SIMDProcessor->MixSoundSixSpeakerStereo( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); + } + } else { + if ( sample->objectInfo.nChannels == 1 ) { + SIMDProcessor->MixSoundTwoSpeakerMono( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); + } else { + SIMDProcessor->MixSoundTwoSpeakerStereo( finalMixBuffer, alignedInputSamples, MIXBUFFER_SAMPLES, chan->lastV, ears ); + } + } + + for ( j = 0 ; j < 6 ; j++ ) { + chan->lastV[j] = ears[j]; + } + + } + + soundSystemLocal.soundStats.activeSounds++; + +} + +/* +=============== +idSoundWorldLocal::FindAmplitude + + this is called from the main thread + + if listenerPosition is NULL, this is being used for shader parameters, + like flashing lights and glows based on sound level. Otherwise, it is being used for + the screen-shake on a player. + + This doesn't do the portal-occlusion currently, because it would have to reset all the defs + which would be problematic in multiplayer +=============== +*/ +float idSoundWorldLocal::FindAmplitude( idSoundEmitterLocal *sound, const int localTime, const idVec3 *listenerPosition, + const s_channelType channel, bool shakesOnly ) { + int i, j; + soundShaderParms_t *parms; + float volume; + int activeChannelCount; + static const int AMPLITUDE_SAMPLES = MIXBUFFER_SAMPLES/8; + float sourceBuffer[AMPLITUDE_SAMPLES]; + float sumBuffer[AMPLITUDE_SAMPLES]; + // work out the distance from the listener to the emitter + float dlen; + + if ( !sound->playing ) { + return 0; + } + + if ( listenerPosition ) { + // this doesn't do the portal spatialization + idVec3 dist = sound->origin - *listenerPosition; + dlen = dist.Length(); + dlen *= DOOM_TO_METERS; + } else { + dlen = 1; + } + + activeChannelCount = 0; + + for ( i = 0; i < SOUND_MAX_CHANNELS ; i++ ) { + idSoundChannel *chan = &sound->channels[ i ]; + + if ( !chan->triggerState ) { + continue; + } + + if ( channel != SCHANNEL_ANY && chan->triggerChannel != channel) { + continue; + } + + parms = &chan->parms; + + int localTriggerTimes = chan->trigger44kHzTime; + + bool looping = ( parms->soundShaderFlags & SSF_LOOPING ) != 0; + + // check for screen shakes + float shakes = parms->shakes; + if ( shakesOnly && shakes <= 0.0f ) { + continue; + } + + // + // calculate volume + // + if ( !listenerPosition ) { + // just look at the raw wav data for light shader evaluation + volume = 1.0; + } else { + volume = parms->volume; + volume = soundSystemLocal.dB2Scale( volume ); + if ( shakesOnly ) { + volume *= shakes; + } + + if ( listenerPosition && !( parms->soundShaderFlags & SSF_GLOBAL ) ) { + // check for overrides + float maxd = parms->maxDistance; + float mind = parms->minDistance; + + if ( dlen >= maxd ) { + volume = 0.0f; + } else if ( dlen > mind ) { + float frac = idMath::ClampFloat( 0, 1, 1.0f - ((dlen - mind) / (maxd - mind))); + if ( idSoundSystemLocal::s_quadraticFalloff.GetBool() ) { + frac *= frac; + } + volume *= frac; + } + } + } + + if ( volume <= 0 ) { + continue; + } + + // + // fetch the sound from the cache + // this doesn't handle stereo samples correctly... + // + if ( !listenerPosition && chan->parms.soundShaderFlags & SSF_NO_FLICKER ) { + // the NO_FLICKER option is to allow a light to still play a sound, but + // not have it effect the intensity + for ( j = 0 ; j < (AMPLITUDE_SAMPLES); j++ ) { + sourceBuffer[j] = j & 1 ? 32767.0f : -32767.0f; + } + } else { + int offset = (localTime - localTriggerTimes); // offset in samples + int size = ( looping ? chan->soundShader->entries[0]->LengthIn44kHzSamples() : chan->leadinSample->LengthIn44kHzSamples() ); + short *amplitudeData = (short *)( looping ? chan->soundShader->entries[0]->amplitudeData : chan->leadinSample->amplitudeData ); + + if ( amplitudeData ) { + // when the amplitudeData is present use that fill a dummy sourceBuffer + // this is to allow for amplitude based effect on hardware audio solutions + if ( looping ) offset %= size; + if ( offset < size ) { + for ( j = 0 ; j < (AMPLITUDE_SAMPLES); j++ ) { + sourceBuffer[j] = j & 1 ? amplitudeData[ ( offset / 512 ) * 2 ] : amplitudeData[ ( offset / 512 ) * 2 + 1 ]; + } + } + } else { + // get actual sample data + chan->GatherChannelSamples( offset, AMPLITUDE_SAMPLES, sourceBuffer ); + } + } + activeChannelCount++; + if ( activeChannelCount == 1 ) { + // store to the buffer + for( j = 0; j < AMPLITUDE_SAMPLES; j++ ) { + sumBuffer[ j ] = volume * sourceBuffer[ j ]; + } + } else { + // add to the buffer + for( j = 0; j < AMPLITUDE_SAMPLES; j++ ) { + sumBuffer[ j ] += volume * sourceBuffer[ j ]; + } + } + } + + if ( activeChannelCount == 0 ) { + return 0.0; + } + + float high = -32767.0f; + float low = 32767.0f; + + // use a 20th of a second + for( i = 0; i < (AMPLITUDE_SAMPLES); i++ ) { + float fabval = sumBuffer[i]; + if ( high < fabval ) { + high = fabval; + } + if ( low > fabval ) { + low = fabval; + } + } + + float sout; + sout = atan( (high - low) / 32767.0f) / DEG2RAD(45); + + return sout; +} + +/* +================= +idSoundWorldLocal::FadeSoundClasses + +fade all sounds in the world with a given shader soundClass +to is in Db (sigh), over is in seconds +================= +*/ +void idSoundWorldLocal::FadeSoundClasses( const int soundClass, const float to, const float over ) { + if ( soundClass < 0 || soundClass >= SOUND_MAX_CLASSES ) { + common->Error( "idSoundWorldLocal::FadeSoundClasses: bad soundClass %i", soundClass ); + } + + idSoundFade *fade = &soundClassFade[ soundClass ]; + + int length44kHz = soundSystemLocal.MillisecondsToSamples( over * 1000 ); + + // if it is already fading to this volume at this rate, don't change it + if ( fade->fadeEndVolume == to && + fade->fadeEnd44kHz - fade->fadeStart44kHz == length44kHz ) { + return; + } + + int start44kHz; + + if ( fpa[0] ) { + // if we are recording an AVI demo, don't use hardware time + start44kHz = lastAVI44kHz + MIXBUFFER_SAMPLES; + } else { + start44kHz = soundSystemLocal.GetCurrent44kHzTime() + MIXBUFFER_SAMPLES; + } + + // fade it + fade->fadeStartVolume = fade->FadeDbAt44kHz( start44kHz ); + fade->fadeStart44kHz = start44kHz; + fade->fadeEnd44kHz = start44kHz + length44kHz; + fade->fadeEndVolume = to; +} + +/* +================= +idSoundWorldLocal::SetSlowmo +================= +*/ +void idSoundWorldLocal::SetSlowmo( bool active ) { + slowmoActive = active; +} + +/* +================= +idSoundWorldLocal::SetSlowmoSpeed +================= +*/ +void idSoundWorldLocal::SetSlowmoSpeed( float speed ) { + slowmoSpeed = speed; +} + +/* +================= +idSoundWorldLocal::SetEnviroSuit +================= +*/ +void idSoundWorldLocal::SetEnviroSuit( bool active ) { + enviroSuitActive = active; +} + +#endif // Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/default/win_snd.cpp b/src/sound/default/win_snd.cpp new file mode 100644 index 0000000..1c1addb --- /dev/null +++ b/src/sound/default/win_snd.cpp @@ -0,0 +1,847 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#ifndef Q4_RECONSTRUCTED_SOUND + +// DirectX SDK +#include + +#include +#include +#include "../../sound/snd_local.h" +#include "win_local.h" + +#include "../../openal/idal.cpp" + +#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } +#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } } +#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } + +class idAudioBufferWIN32 : public idAudioBuffer { +public: + idAudioBufferWIN32( LPDIRECTSOUNDBUFFER apDSBuffer, dword dwDSBufferSize, idWaveFile* pWaveFile=NULL ); + ~idAudioBufferWIN32(); + + int FillBufferWithSound( LPDIRECTSOUNDBUFFER pDSB, bool bRepeatWavIfBufferLarger ); + + bool Lock( void **pDSLockedBuffer, ulong *dwDSLockedBufferSize ); + bool Unlock(void *pDSLockedBuffer, dword dwDSLockedBufferSize ); + bool GetCurrentPosition( ulong *pdwCurrentWriteCursor ); + + int Play( dword dwPriority=0, dword dwFlags=0 ); + int Stop( void ); + int Reset( void ); + bool IsSoundPlaying( void ); + void SetVolume( float x); + + idWaveFile* m_pWaveFile; +private: + LPDIRECTSOUNDBUFFER m_apDSBuffer; + dword m_dwDSBufferSize; + + int RestoreBuffer( LPDIRECTSOUNDBUFFER pDSB, bool* pbWasRestored ); +}; + +class idAudioHardwareWIN32 : public idAudioHardware { + +public: + idAudioHardwareWIN32(); + ~idAudioHardwareWIN32(); + + bool Initialize( ); + bool InitializeSpeakers( byte *buffer, int bufferSize, dword dwPrimaryFreq, dword dwPrimaryBitRate, dword dwSpeakers ); + + void SetPrimaryBufferFormat( dword dwPrimaryFreq, dword dwPrimaryBitRate, dword dwSpeakers ); + + int Create( idWaveFile* pWaveFile, idAudioBuffer** ppiab ); + int Create( idAudioBuffer** ppSound, const char* strWaveFileName, dword dwCreationFlags = 0 ); + int CreateFromMemory( idAudioBufferWIN32** ppSound, byte* pbData, ulong ulDataSize, waveformatextensible_t *pwfx, dword dwCreationFlags = 0 ); + + bool Lock( void **pDSLockedBuffer, ulong *dwDSLockedBufferSize ); + bool Unlock( void *pDSLockedBuffer, dword dwDSLockedBufferSize ); + bool GetCurrentPosition( ulong *pdwCurrentWriteCursor ); + + int GetNumberOfSpeakers() { return numSpeakers; } + int GetMixBufferSize() { return MIXBUFFER_SAMPLES * blockAlign; } + + // WIN32 driver doesn't support write API + bool Flush( void ) { return true; } + void Write( bool ) { } + short* GetMixBuffer( void ) { return NULL; } + +private: + LPDIRECTSOUND m_pDS; + LPDIRECTSOUNDBUFFER pDSBPrimary; + idAudioBufferWIN32 *speakers; + + int numSpeakers; + int bitsPerSample; + int bufferSize; // allocate buffer handed over to DirectSound + int blockAlign; // channels * bits per sample / 8: sound frame size +}; + +idAudioHardware *idAudioHardware::Alloc() { return new idAudioHardwareWIN32; } +idAudioHardware::~idAudioHardware() {} + +/* +================ +idAudioHardwareWIN32::idAudioHardware +================ +*/ +idAudioHardwareWIN32::idAudioHardwareWIN32() { + m_pDS = NULL; + pDSBPrimary = NULL; + speakers = NULL; +} + +/* +================ +idAudioHardwareWIN32::~idAudioHardware +================ +*/ +idAudioHardwareWIN32::~idAudioHardwareWIN32() { + SAFE_DELETE( speakers ); + SAFE_RELEASE( pDSBPrimary ); + SAFE_RELEASE( m_pDS ); +} + +/* +=============== +idAudioHardwareWIN32::Initialize +=============== +*/ +bool idAudioHardwareWIN32::Initialize( void ) { + int hr; + + bufferSize = 0; + numSpeakers = 0; + blockAlign = 0; + + SAFE_RELEASE( m_pDS ); + + // Create IDirectSound using the primary sound device + if( FAILED( hr = DirectSoundCreate( NULL, &m_pDS, NULL ) )) { + return false; + } + + // Set primary buffer format + SetPrimaryBufferFormat( PRIMARYFREQ, 16, idSoundSystemLocal::s_numberOfSpeakers.GetInteger() ); + return true; +} + +/* +=============== +idAudioHardwareWIN32::InitializeSpeakers +=============== +*/ +bool idAudioHardwareWIN32::InitializeSpeakers( byte *speakerData, int bufferSize, dword dwPrimaryFreq, dword dwPrimaryBitRate, dword dwSpeakers ) { + if ( dwSpeakers == 2 ) { + WAVEFORMATEXTENSIBLE wfx; + ZeroMemory( &wfx, sizeof(WAVEFORMATEXTENSIBLE) ); + wfx.Format.wFormatTag = WAVE_FORMAT_PCM; + wfx.Format.nChannels = 2; + wfx.Format.nSamplesPerSec = dwPrimaryFreq; + wfx.Format.wBitsPerSample = dwPrimaryBitRate; + wfx.Format.nBlockAlign = wfx.Format.wBitsPerSample / 8 * wfx.Format.nChannels; + wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; + wfx.Format.cbSize = sizeof(WAVEFORMATEX); + + CreateFromMemory( &speakers, speakerData, bufferSize, (waveformatextensible_t *)&wfx ); + + common->Printf("sound: STEREO\n"); + } else { + WAVEFORMATEXTENSIBLE waveFormatPCMEx; + ZeroMemory( &waveFormatPCMEx, sizeof(WAVEFORMATEXTENSIBLE) ); + + waveFormatPCMEx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + waveFormatPCMEx.Format.nChannels = 6; + waveFormatPCMEx.Format.nSamplesPerSec = dwPrimaryFreq; + waveFormatPCMEx.Format.wBitsPerSample = dwPrimaryBitRate; + waveFormatPCMEx.Format.nBlockAlign = waveFormatPCMEx.Format.wBitsPerSample / 8 * waveFormatPCMEx.Format.nChannels; + waveFormatPCMEx.Format.nAvgBytesPerSec = waveFormatPCMEx.Format.nSamplesPerSec * waveFormatPCMEx.Format.nBlockAlign; + waveFormatPCMEx.dwChannelMask = KSAUDIO_SPEAKER_5POINT1; + // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | + // SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | + // SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT + waveFormatPCMEx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; // Specify PCM + waveFormatPCMEx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE); + waveFormatPCMEx.Samples.wValidBitsPerSample = 16; + + CreateFromMemory( &speakers, speakerData, bufferSize, (waveformatextensible_t *)&waveFormatPCMEx ); + + common->Printf("sound: MULTICHANNEL\n"); + } + + if (!speakers) { + return false; + } + + speakers->Play(0,DSBPLAY_LOOPING); + + return true; +} + +/* +=============== +idAudioHardwareWIN32::SetPrimaryBufferFormat +Set primary buffer to a specified format +For example, to set the primary buffer format to 22kHz stereo, 16-bit +then: dwPrimaryChannels = 2 + dwPrimaryFreq = 22050, + dwPrimaryBitRate = 16 +=============== +*/ +void idAudioHardwareWIN32::SetPrimaryBufferFormat( dword dwPrimaryFreq, dword dwPrimaryBitRate, dword dwSpeakers ) { + HRESULT hr; + + if( m_pDS == NULL ) { + return; + } + + ulong cfgSpeakers; + m_pDS->GetSpeakerConfig( &cfgSpeakers ); + + DSCAPS dscaps; + dscaps.dwSize = sizeof(DSCAPS); + m_pDS->GetCaps(&dscaps); + + if (dscaps.dwFlags & DSCAPS_EMULDRIVER) { + return; + } + + // Get the primary buffer + DSBUFFERDESC dsbd; + ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) ); + dsbd.dwSize = sizeof(DSBUFFERDESC); + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwBufferBytes = 0; + dsbd.lpwfxFormat = NULL; + + // Obtain write-primary cooperative level. + if( FAILED( hr = m_pDS->SetCooperativeLevel(win32.hWnd, DSSCL_PRIORITY ) ) ) { + DXTRACE_ERR( TEXT("SetPrimaryBufferFormat"), hr ); + return; + } + + if( FAILED( hr = m_pDS->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL ) ) ) { + return; + } + + if ( dwSpeakers == 6 && (cfgSpeakers == DSSPEAKER_5POINT1 || cfgSpeakers == DSSPEAKER_SURROUND) ) { + WAVEFORMATEXTENSIBLE waveFormatPCMEx; + ZeroMemory( &waveFormatPCMEx, sizeof(WAVEFORMATEXTENSIBLE) ); + + waveFormatPCMEx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + waveFormatPCMEx.Format.nChannels = 6; + waveFormatPCMEx.Format.nSamplesPerSec = dwPrimaryFreq; + waveFormatPCMEx.Format.wBitsPerSample = (WORD) dwPrimaryBitRate; + waveFormatPCMEx.Format.nBlockAlign = waveFormatPCMEx.Format.wBitsPerSample / 8 * waveFormatPCMEx.Format.nChannels; + waveFormatPCMEx.Format.nAvgBytesPerSec = waveFormatPCMEx.Format.nSamplesPerSec * waveFormatPCMEx.Format.nBlockAlign; + waveFormatPCMEx.dwChannelMask = KSAUDIO_SPEAKER_5POINT1; + // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | + // SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | + // SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT + waveFormatPCMEx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; // Specify PCM + waveFormatPCMEx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE); + waveFormatPCMEx.Samples.wValidBitsPerSample = 16; + + if( FAILED( hr = pDSBPrimary->SetFormat((WAVEFORMATEX*)&waveFormatPCMEx) ) ) { + DXTRACE_ERR( TEXT("SetPrimaryBufferFormat"), hr ); + return; + } + numSpeakers = 6; // force it to think 5.1 + blockAlign = waveFormatPCMEx.Format.nBlockAlign; + } else { + if (dwSpeakers == 6) { + common->Printf("sound: hardware reported unable to use multisound, defaulted to stereo\n"); + } + WAVEFORMATEX wfx; + ZeroMemory( &wfx, sizeof(WAVEFORMATEX) ); + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = dwPrimaryFreq; + wfx.wBitsPerSample = (WORD) dwPrimaryBitRate; + wfx.nBlockAlign = wfx.wBitsPerSample / 8 * wfx.nChannels; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + wfx.cbSize = sizeof(WAVEFORMATEX); + + if( FAILED( hr = pDSBPrimary->SetFormat(&wfx) ) ) { + return; + } + numSpeakers = 2; // force it to think stereo + blockAlign = wfx.nBlockAlign; + } + + byte *speakerData; + bufferSize = MIXBUFFER_SAMPLES * sizeof(word) * numSpeakers * ROOM_SLICES_IN_BUFFER; + speakerData = (byte *)Mem_Alloc( bufferSize ); + memset( speakerData, 0, bufferSize ); + + InitializeSpeakers( speakerData, bufferSize, dwPrimaryFreq, dwPrimaryBitRate, numSpeakers ); +} + +/* +=============== +idAudioHardwareWIN32::Create +=============== +*/ +int idAudioHardwareWIN32::Create( idAudioBuffer** ppSound, + const char* strWaveFileName, + dword dwCreationFlags ) { + int hr; + LPDIRECTSOUNDBUFFER apDSBuffer = NULL; + dword dwDSBufferSize = NULL; + idWaveFile* pWaveFile = NULL; + + if( m_pDS == NULL ) + return -1; + if( strWaveFileName == NULL || ppSound == NULL ) + return -1; + + pWaveFile = new idWaveFile(); + + pWaveFile->Open( strWaveFileName, NULL ); + + if( pWaveFile->GetOutputSize() == 0 ) { + // Wave is blank, so don't create it. + hr = E_FAIL; + goto LFail; + } + + // Make the DirectSound buffer the same size as the wav file + dwDSBufferSize = pWaveFile->GetOutputSize(); + + // Create the direct sound buffer, and only request the flags needed + // since each requires some overhead and limits if the buffer can + // be hardware accelerated + DSBUFFERDESC dsbd; + memset( &dsbd, 0, sizeof(DSBUFFERDESC) ); + dsbd.dwSize = sizeof(DSBUFFERDESC); + dsbd.dwFlags = dwCreationFlags; + dsbd.dwBufferBytes = dwDSBufferSize; + dsbd.guid3DAlgorithm = GUID_NULL; + dsbd.lpwfxFormat = (WAVEFORMATEX*)&pWaveFile->mpwfx; + + // DirectSound is only guarenteed to play PCM data. Other + // formats may or may not work depending the sound card driver. + if( FAILED( hr = m_pDS->CreateSoundBuffer( &dsbd, &apDSBuffer, NULL ) ) ) + return -1; + + // Create the sound + *ppSound = new idAudioBufferWIN32( apDSBuffer, dwDSBufferSize, pWaveFile ); + + pWaveFile->Close(); + + return 0; + +LFail: + // Cleanup + SAFE_DELETE( pWaveFile ); + return -1; +} + +/* +=============== +idAudioHardwareWIN32::Create +=============== +*/ +int idAudioHardwareWIN32::Create( idWaveFile* pWaveFile, idAudioBuffer** ppiab ) { + int hr; + LPDIRECTSOUNDBUFFER apDSBuffer = NULL; + dword dwDSBufferSize = NULL; + + if( m_pDS == NULL ) + return -1; + + if( pWaveFile == NULL ) + return -1; + + *ppiab = NULL; + + if( pWaveFile->GetOutputSize() == 0 ) { + // Wave is blank, so don't create it. + hr = E_FAIL; + goto LFail; + } + + // Make the DirectSound buffer the same size as the wav file + dwDSBufferSize = pWaveFile->GetOutputSize(); + + // Create the direct sound buffer, and only request the flags needed + // since each requires some overhead and limits if the buffer can + // be hardware accelerated + DSBUFFERDESC dsbd; + memset( &dsbd, 0, sizeof(DSBUFFERDESC) ); + dsbd.dwSize = sizeof(DSBUFFERDESC); + dsbd.dwFlags = 0; + dsbd.dwBufferBytes = dwDSBufferSize; + dsbd.guid3DAlgorithm = GUID_NULL; + dsbd.lpwfxFormat = (WAVEFORMATEX*)&pWaveFile->mpwfx; + + // DirectSound is only guarenteed to play PCM data. Other + // formats may or may not work depending the sound card driver. + if( FAILED( hr = m_pDS->CreateSoundBuffer( &dsbd, &apDSBuffer, NULL ) ) ) + return -1; + + // Create the sound + *ppiab = new idAudioBufferWIN32( apDSBuffer, dwDSBufferSize, pWaveFile ); + + return 0; + +LFail: + // Cleanup + SAFE_DELETE( pWaveFile ); + return -1; +} + +//----------------------------------------------------------------------------- +// Name: idAudioHardwareWIN32::CreateFromMemory() +// Desc: +//----------------------------------------------------------------------------- +int idAudioHardwareWIN32::CreateFromMemory( idAudioBufferWIN32** ppSound, + byte* pbData, + ulong ulDataSize, + waveformatextensible_t* pwfx, + dword dwCreationFlags ) { + int hr; + LPDIRECTSOUNDBUFFER apDSBuffer = NULL; + dword dwDSBufferSize = NULL; + idWaveFile* pWaveFile = NULL; + + if( m_pDS == NULL ) + return -1; + if( pbData == NULL || ppSound == NULL ) + return -1; + + pWaveFile = new idWaveFile(); + + pWaveFile->OpenFromMemory( (short *)pbData, ulDataSize, (waveformatextensible_t *)pwfx); + + + // Make the DirectSound buffer the same size as the wav file + dwDSBufferSize = ulDataSize; + + // Create the direct sound buffer, and only request the flags needed + // since each requires some overhead and limits if the buffer can + // be hardware accelerated + DSBUFFERDESC dsbd; + memset( &dsbd, 0, sizeof(DSBUFFERDESC) ); + dsbd.dwSize = sizeof(DSBUFFERDESC); + dsbd.dwFlags = dwCreationFlags | DSBCAPS_GETCURRENTPOSITION2; + dsbd.dwBufferBytes = dwDSBufferSize; + dsbd.guid3DAlgorithm = GUID_NULL; + dsbd.lpwfxFormat = (WAVEFORMATEX *)pwfx; + + if( FAILED( hr = m_pDS->CreateSoundBuffer( &dsbd, &apDSBuffer, NULL ) ) ) + return -1; + + // Create the sound + *ppSound = new idAudioBufferWIN32( apDSBuffer, dwDSBufferSize, pWaveFile ); + + return S_OK; +} + +/* +=============== +idAudioHardwareWIN32::Lock +=============== +*/ +bool idAudioHardwareWIN32::Lock( void **pDSLockedBuffer, ulong *dwDSLockedBufferSize ) { + if (speakers) { + return speakers->Lock( pDSLockedBuffer, dwDSLockedBufferSize ); + } + return false; +} + +/* +=============== +idAudioHardwareWIN32::Unlock +=============== +*/ +bool idAudioHardwareWIN32::Unlock(void *pDSLockedBuffer, dword dwDSLockedBufferSize ) { + if (speakers) { + return speakers->Unlock( pDSLockedBuffer, dwDSLockedBufferSize ); + } + return false; +} + +/* +=============== +idAudioHardwareWIN32::GetCurrentPosition +=============== +*/ +bool idAudioHardwareWIN32::GetCurrentPosition( ulong *pdwCurrentWriteCursor ) { + if (speakers) { + return speakers->GetCurrentPosition( pdwCurrentWriteCursor ); + } + return false; +} + +static HMODULE hOpenAL = NULL; + +/* +=============== +Sys_LoadOpenAL +=============== +*/ +bool Sys_LoadOpenAL( void ) { +#if ID_OPENAL + const char *sym; + + if ( hOpenAL ) { + return true; + } + + hOpenAL = LoadLibrary( idSoundSystemLocal::s_libOpenAL.GetString() ); + if ( !hOpenAL ) { + common->Warning( "LoadLibrary %s failed.", idSoundSystemLocal::s_libOpenAL.GetString() ); + return false; + } + if ( ( sym = InitializeIDAL( hOpenAL ) ) ) { + common->Warning( "GetProcAddress %s failed.", sym ); + FreeLibrary( hOpenAL ); + hOpenAL = NULL; + return false; + } + return true; +#else + return false; +#endif +} + +/* +=============== +Sys_FreeOpenAL +=============== +*/ +void Sys_FreeOpenAL( void ) { + if ( hOpenAL ) { + FreeLibrary( hOpenAL ); + hOpenAL = NULL; + } +} + +/* +=============== +idAudioBufferWIN32::idAudioBuffer +=============== +*/ +idAudioBufferWIN32::idAudioBufferWIN32( LPDIRECTSOUNDBUFFER apDSBuffer, dword dwDSBufferSize, idWaveFile* pWaveFile ) { + + m_apDSBuffer = apDSBuffer; + + m_dwDSBufferSize = dwDSBufferSize; + m_pWaveFile = pWaveFile; + + if (pWaveFile) { + FillBufferWithSound( m_apDSBuffer, false ); + + m_apDSBuffer->SetCurrentPosition(0); + } +} + +/* +=============== +idAudioBufferWIN32::~idAudioBuffer +=============== +*/ +idAudioBufferWIN32::~idAudioBufferWIN32() { + SAFE_DELETE(m_pWaveFile); + SAFE_RELEASE( m_apDSBuffer ); + m_pWaveFile = NULL; + m_apDSBuffer = NULL; +} + +/* +=============== +idAudioBufferWIN32::FillBufferWithSound +=============== +*/ +int idAudioBufferWIN32::FillBufferWithSound( LPDIRECTSOUNDBUFFER pDSB, bool bRepeatWavIfBufferLarger ) { + int hr; + void* pDSLockedBuffer = NULL; // Pointer to locked buffer memory + ulong dwDSLockedBufferSize = 0; // Size of the locked DirectSound buffer + int dwWavDataRead = 0; // Amount of data read from the wav file + + if( pDSB == NULL ) + return -1; + + // we may not even have a wavefile + if (m_pWaveFile==NULL) { + return -1; + } + + // Make sure we have focus, and we didn't just switch in from + // an app which had a DirectSound device + if( FAILED( hr = RestoreBuffer( pDSB, NULL ) ) ) { + DXTRACE_ERR( TEXT("RestoreBuffer"), hr ); + return -1; + } + + // Lock the buffer down + if( FAILED( hr = pDSB->Lock( 0, m_dwDSBufferSize, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, NULL, 0L ) ) ) { + DXTRACE_ERR( TEXT("Lock"), hr ); + return -1; + } + + // Reset the wave file to the beginning + m_pWaveFile->ResetFile(); + + if( FAILED( hr = m_pWaveFile->Read( (byte*) pDSLockedBuffer, dwDSLockedBufferSize, &dwWavDataRead ) ) ) { + return DXTRACE_ERR( TEXT("Read"), hr ); + } + + if( dwWavDataRead == 0 ) { + // Wav is blank, so just fill with silence + memset( pDSLockedBuffer, (byte)(m_pWaveFile->mpwfx.Format.wBitsPerSample == 8 ? 128 : 0 ), dwDSLockedBufferSize ); + } else if( dwWavDataRead < (int)dwDSLockedBufferSize ) { + // If the wav file was smaller than the DirectSound buffer, + // we need to fill the remainder of the buffer with data + if( bRepeatWavIfBufferLarger ) { + // Reset the file and fill the buffer with wav data + int dwReadSoFar = dwWavDataRead; // From previous call above. + while( dwReadSoFar < (int)dwDSLockedBufferSize ) { + // This will keep reading in until the buffer is full + // for very short files + if( FAILED( hr = m_pWaveFile->ResetFile() ) ) { + return DXTRACE_ERR( TEXT("ResetFile"), hr ); + } + + hr = m_pWaveFile->Read( (byte*)pDSLockedBuffer + dwReadSoFar, dwDSLockedBufferSize - dwReadSoFar, &dwWavDataRead ); + if( FAILED(hr) ) { + return DXTRACE_ERR( TEXT("Read"), hr ); + } + + dwReadSoFar += dwWavDataRead; + } + } else { + // Don't repeat the wav file, just fill in silence + memset( (byte*) pDSLockedBuffer + dwWavDataRead, (byte)(m_pWaveFile->mpwfx.Format.wBitsPerSample == 8 ? 128 : 0 ), dwDSLockedBufferSize - dwWavDataRead); + } + } + + // Unlock the buffer, we don't need it anymore. + pDSB->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 ); + + return S_OK; +} + +/* +=============== +idAudioBufferWIN32::RestoreBuffer +Desc: Restores the lost buffer. *pbWasRestored returns true if the buffer was + restored. It can also NULL if the information is not needed. +=============== +*/ +int idAudioBufferWIN32::RestoreBuffer( LPDIRECTSOUNDBUFFER pDSB, bool* pbWasRestored ) { + int hr; + + if( pDSB == NULL ) { + return -1; + } + if( pbWasRestored ) { + *pbWasRestored = false; + } + + ulong dwStatus; + if( FAILED( hr = pDSB->GetStatus( &dwStatus ) ) ) { + return DXTRACE_ERR( TEXT("GetStatus"), hr ); + } + + if( dwStatus & DSBSTATUS_BUFFERLOST ) { + // Since the app could have just been activated, then + // DirectSound may not be giving us control yet, so + // the restoring the buffer may fail. + // If it does, sleep until DirectSound gives us control. + do { + hr = pDSB->Restore(); + if( hr == DSERR_BUFFERLOST ) { + Sleep( 10 ); + } + hr = pDSB->Restore(); + } while( hr ); + + if( pbWasRestored != NULL ) { + *pbWasRestored = true; + } + + return S_OK; + } else { + return S_FALSE; + } +} + +/* +=============== +idAudioBufferWIN32::Play +Desc: Plays the sound using voice management flags. Pass in DSBPLAY_LOOPING + in the dwFlags to loop the sound +=============== +*/ +int idAudioBufferWIN32::Play( dword dwPriority, dword dwFlags ) { + int hr; + bool bRestored; + + if( m_apDSBuffer == NULL ) { + return -1; + } + + // Restore the buffer if it was lost + if( FAILED( hr = RestoreBuffer( m_apDSBuffer, &bRestored ) ) ) { + common->Error( TEXT("RestoreBuffer"), hr ); + } + + if( bRestored ) { + // The buffer was restored, so we need to fill it with new data + if( FAILED( hr = FillBufferWithSound( m_apDSBuffer, false ) ) ) { + common->Error( TEXT("FillBufferWithSound"), hr ); + } + + // Make DirectSound do pre-processing on sound effects + Reset(); + } + + m_apDSBuffer->Play( 0, dwPriority, dwFlags ); + return 0; +} + +/* +=============== +idAudioBufferWIN32::Stop +Desc: Stops the sound from playing +=============== +*/ +int idAudioBufferWIN32::Stop() { + if( this == NULL || m_apDSBuffer == NULL ) { + return -1; + } + + m_apDSBuffer->Stop(); + + return 0; +} + +/* +=============== +idAudioBufferWIN32::Reset +Desc: Reset all of the sound buffers +=============== +*/ +int idAudioBufferWIN32::Reset() { + if( m_apDSBuffer == NULL ) { + return -1; + } + + m_apDSBuffer->SetCurrentPosition( 0 ); + + return 0; +} + +/* +=============== +idAudioBufferWIN32::IsSoundPlaying +Desc: Checks to see if a buffer is playing and returns true if it +=============== +*/ +bool idAudioBufferWIN32::IsSoundPlaying( ) { + if( m_apDSBuffer == NULL ) { + return false; + } + + if( m_apDSBuffer ) { + ulong dwStatus = 0; + m_apDSBuffer->GetStatus( &dwStatus ); + if ( dwStatus & DSBSTATUS_PLAYING ) { + return true; + } + } + return false; +} + +/* +=============== +idAudioBufferWIN32::Lock +=============== +*/ +bool idAudioBufferWIN32::Lock( void **pDSLockedBuffer, ulong *dwDSLockedBufferSize ) { + int hr; + // Restore the buffer if it was lost + bool bRestored; + if( FAILED( hr = RestoreBuffer( m_apDSBuffer, &bRestored ) ) ) { + return false; + } + + // Lock the DirectSound buffer + if( FAILED( hr = m_apDSBuffer->Lock( 0, m_dwDSBufferSize, pDSLockedBuffer, dwDSLockedBufferSize, NULL, NULL, 0 ) ) ) { + return false; + } + return true; +} + +/* +=============== +idAudioBufferWIN32::Unlock +=============== +*/ +bool idAudioBufferWIN32::Unlock(void *pDSLockedBuffer, dword dwDSLockedBufferSize ) { + // Unlock the DirectSound buffer + m_apDSBuffer->Unlock( pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0 ); + return true; +} + +/* +=============== +idAudioBufferWIN32::GetCurrentPosition +=============== +*/ +bool idAudioBufferWIN32::GetCurrentPosition( ulong *pdwCurrentWriteCursor ) { + int hr; + + // Make sure we have focus, and we didn't just switch in from + // an app which had a DirectSound device + if( FAILED( hr = RestoreBuffer( m_apDSBuffer, NULL ) ) ) { + DXTRACE_ERR( TEXT("RestoreBuffer"), hr ); + return false; + } + + if( FAILED( hr = m_apDSBuffer->GetCurrentPosition( NULL, pdwCurrentWriteCursor ) ) ) { + return false; + } + return true; +} + +/* +=============== +idAudioBufferWIN32::SetVolume +=============== +*/ +void idAudioBufferWIN32::SetVolume( float x) { + if (m_apDSBuffer) { + m_apDSBuffer->SetVolume(x); + } +} + +#endif // !Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/oggvorbis/ogg/ogg.h b/src/sound/oggvorbis/ogg/ogg.h new file mode 100644 index 0000000..116f292 --- /dev/null +++ b/src/sound/oggvorbis/ogg/ogg.h @@ -0,0 +1,226 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel libogg include + last mod: $Id: ogg.h,v 1.19 2002/09/15 23:48:02 xiphmont Exp $ + + ********************************************************************/ +#ifndef _OGG_H +#define _OGG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "os_types.h" + +typedef struct { + long endbyte; + int endbit; + + unsigned char *buffer; + unsigned char *ptr; + long storage; +} oggpack_buffer; + +/* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ + +typedef struct { + unsigned char *header; + long header_len; + unsigned char *body; + long body_len; +} ogg_page; + +/* ogg_stream_state contains the current encode/decode state of a logical + Ogg bitstream **********************************************************/ + +typedef struct { + unsigned char *body_data; /* bytes from packet bodies */ + long body_storage; /* storage elements allocated */ + long body_fill; /* elements stored; fill mark */ + long body_returned; /* elements of fill returned */ + + + int *lacing_vals; /* The values that will go to the segment table */ + ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + + unsigned char header[282]; /* working space for header encode */ + int header_fill; + + int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int b_o_s; /* set after we've written the initial page + of a logical bitstream */ + long serialno; + long pageno; + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a seperate abstraction + layer) also knows about the gap */ + ogg_int64_t granulepos; + +} ogg_stream_state; + +/* ogg_packet is used to encapsulate the data and metadata belonging + to a single raw Ogg/Vorbis packet *************************************/ + +typedef struct { + unsigned char *packet; + long bytes; + long b_o_s; + long e_o_s; + + ogg_int64_t granulepos; + + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a seperate abstraction + layer) also knows about the gap */ +} ogg_packet; + +typedef struct { + unsigned char *data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state; + +/* Ogg BITSTREAM PRIMITIVES: bitstream ************************/ + +extern void oggpack_writeinit(oggpack_buffer *b); +extern void oggpack_writetrunc(oggpack_buffer *b,long bits); +extern void oggpack_writealign(oggpack_buffer *b); +extern void oggpack_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpack_reset(oggpack_buffer *b); +extern void oggpack_writeclear(oggpack_buffer *b); +extern void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpack_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpack_look(oggpack_buffer *b,int bits); +extern long oggpack_look1(oggpack_buffer *b); +extern void oggpack_adv(oggpack_buffer *b,int bits); +extern void oggpack_adv1(oggpack_buffer *b); +extern long oggpack_read(oggpack_buffer *b,int bits); +extern long oggpack_read1(oggpack_buffer *b); +extern long oggpack_bytes(oggpack_buffer *b); +extern long oggpack_bits(oggpack_buffer *b); +extern unsigned char *oggpack_get_buffer(oggpack_buffer *b); + +extern void oggpackB_writeinit(oggpack_buffer *b); +extern void oggpackB_writetrunc(oggpack_buffer *b,long bits); +extern void oggpackB_writealign(oggpack_buffer *b); +extern void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpackB_reset(oggpack_buffer *b); +extern void oggpackB_writeclear(oggpack_buffer *b); +extern void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpackB_look(oggpack_buffer *b,int bits); +extern long oggpackB_look1(oggpack_buffer *b); +extern void oggpackB_adv(oggpack_buffer *b,int bits); +extern void oggpackB_adv1(oggpack_buffer *b); +extern long oggpackB_read(oggpack_buffer *b,int bits); +extern long oggpackB_read1(oggpack_buffer *b); +extern long oggpackB_bytes(oggpack_buffer *b); +extern long oggpackB_bits(oggpack_buffer *b); +extern unsigned char *oggpackB_get_buffer(oggpack_buffer *b); + +/* Ogg BITSTREAM PRIMITIVES: encoding **************************/ + +extern int ogg_stream_packetin(ogg_stream_state *os, ogg_packet *op); +extern int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_flush(ogg_stream_state *os, ogg_page *og); + +/* Ogg BITSTREAM PRIMITIVES: decoding **************************/ + +extern int ogg_sync_init(ogg_sync_state *oy); +extern int ogg_sync_clear(ogg_sync_state *oy); +extern int ogg_sync_reset(ogg_sync_state *oy); +extern int ogg_sync_destroy(ogg_sync_state *oy); + +extern char *ogg_sync_buffer(ogg_sync_state *oy, long size); +extern int ogg_sync_wrote(ogg_sync_state *oy, long bytes); +extern long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og); +extern int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og); +extern int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op); +extern int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op); + +/* Ogg BITSTREAM PRIMITIVES: general ***************************/ + +extern int ogg_stream_init(ogg_stream_state *os,int serialno); +extern int ogg_stream_clear(ogg_stream_state *os); +extern int ogg_stream_reset(ogg_stream_state *os); +extern int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno); +extern int ogg_stream_destroy(ogg_stream_state *os); +extern int ogg_stream_eos(ogg_stream_state *os); + +extern void ogg_page_checksum_set(ogg_page *og); + +extern int ogg_page_version(ogg_page *og); +extern int ogg_page_continued(ogg_page *og); +extern int ogg_page_bos(ogg_page *og); +extern int ogg_page_eos(ogg_page *og); +extern ogg_int64_t ogg_page_granulepos(ogg_page *og); +extern int ogg_page_serialno(ogg_page *og); +extern long ogg_page_pageno(ogg_page *og); +extern int ogg_page_packets(ogg_page *og); + +extern void ogg_packet_clear(ogg_packet *op); + + +#ifdef __cplusplus +} +#endif + +#endif /* _OGG_H */ + + + + + + diff --git a/src/sound/oggvorbis/ogg/os_types.h b/src/sound/oggvorbis/ogg/os_types.h new file mode 100644 index 0000000..00c2fab --- /dev/null +++ b/src/sound/oggvorbis/ogg/os_types.h @@ -0,0 +1,169 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: #ifdef jail to whip a few platforms into the UNIX ideal. + last mod: $Id: os_types.h,v 1.14 2003/09/02 05:09:14 xiphmont Exp $ + + ********************************************************************/ +#ifndef _OS_TYPES_H +#define _OS_TYPES_H + +/* make it easy on the folks that want to compile the libs with a + different malloc than stdlib */ + +// using private thread safe memory allocator for DOOM +#if 1 + +#include +#if !__MACH__ && __MWERKS__ +#include +#else +#include +#endif + +void *_decoder_malloc( size_t size ); +void *_decoder_calloc( size_t num, size_t size ); +void *_decoder_realloc( void *memblock, size_t size ); +void _decoder_free( void *memblock ); + +#define _ogg_malloc _decoder_malloc +#define _ogg_calloc _decoder_calloc +#define _ogg_realloc _decoder_realloc +#define _ogg_free _decoder_free + +#else + +#define _ogg_malloc malloc +#define _ogg_calloc calloc +#define _ogg_realloc realloc +#define _ogg_free free + +#endif + +#ifdef _WIN32 + +# ifndef __GNUC__ + /* MSVC/Borland */ + typedef __int64 ogg_int64_t; + typedef __int32 ogg_int32_t; + typedef unsigned __int32 ogg_uint32_t; + typedef __int16 ogg_int16_t; + typedef unsigned __int16 ogg_uint16_t; + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4146) // unary minus operator applied to unsigned type, result still unsigned +#pragma warning(disable : 4244) // conversion to smaller type, possible loss of data +#pragma warning(disable : 4305) // truncation from 'double' to 'float' + +# else + /* Cygwin */ + #include <_G_config.h> + typedef _G_int64_t ogg_int64_t; + typedef _G_int32_t ogg_int32_t; + typedef _G_uint32_t ogg_uint32_t; + typedef _G_int16_t ogg_int16_t; + typedef _G_uint16_t ogg_uint16_t; +# endif + +#elif defined(MACOS_X) /* MacOS X Framework build */ + +# include + typedef int16_t ogg_int16_t; + typedef u_int16_t ogg_uint16_t; + typedef int32_t ogg_int32_t; + typedef u_int32_t ogg_uint32_t; + typedef int64_t ogg_int64_t; + +#elif defined(__MACOS__) + +# include + typedef SInt16 ogg_int16_t; + typedef UInt16 ogg_uint16_t; + typedef SInt32 ogg_int32_t; + typedef UInt32 ogg_uint32_t; + typedef SInt64 ogg_int64_t; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16_t ogg_int16_t; + typedef u_int16_t ogg_uint16_t; + typedef int32_t ogg_int32_t; + typedef u_int32_t ogg_uint32_t; + typedef int64_t ogg_int64_t; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short ogg_int16_t; + typedef unsigned short ogg_uint16_t; + typedef int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long ogg_int64_t; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short ogg_int16_t; + typedef int ogg_int32_t; + typedef unsigned int ogg_uint32_t; + typedef long long ogg_int64_t; + +#elif defined(R5900) + + /* PS2 EE */ + typedef long ogg_int64_t; + typedef int ogg_int32_t; + typedef unsigned ogg_uint32_t; + typedef short ogg_int16_t; + +#else + +# include +//# include +// copied from a system install of config_types.h +/* these are filled in by configure */ +typedef int16_t ogg_int16_t; +typedef u_int16_t ogg_uint16_t; +typedef int32_t ogg_int32_t; +typedef u_int32_t ogg_uint32_t; +typedef int64_t ogg_int64_t; + + +#endif + +#endif /* _OS_TYPES_H */ diff --git a/src/sound/oggvorbis/oggsrc/bitwise.c b/src/sound/oggvorbis/oggsrc/bitwise.c new file mode 100644 index 0000000..d3f27ad --- /dev/null +++ b/src/sound/oggvorbis/oggsrc/bitwise.c @@ -0,0 +1,806 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: packing variable sized words into an octet stream + last mod: $Id: bitwise.c,v 1.17 2003/11/10 13:06:08 xiphmont Exp $ + + ********************************************************************/ + +/* We're 'LSb' endian; if we write a word but read individual bits, + then we'll read the lsb first */ + +#include +#include +#include "../ogg/ogg.h" + +#define BUFFER_INCREMENT 256 + +static unsigned long mask[]= +{0x00000000,0x00000001,0x00000003,0x00000007,0x0000000f, + 0x0000001f,0x0000003f,0x0000007f,0x000000ff,0x000001ff, + 0x000003ff,0x000007ff,0x00000fff,0x00001fff,0x00003fff, + 0x00007fff,0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff, + 0x000fffff,0x001fffff,0x003fffff,0x007fffff,0x00ffffff, + 0x01ffffff,0x03ffffff,0x07ffffff,0x0fffffff,0x1fffffff, + 0x3fffffff,0x7fffffff,0xffffffff }; + +static unsigned int mask8B[]= +{0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff}; + +void oggpack_writeinit(oggpack_buffer *b){ + memset(b,0,sizeof(*b)); + b->ptr=b->buffer=_ogg_malloc(BUFFER_INCREMENT); + b->buffer[0]='\0'; + b->storage=BUFFER_INCREMENT; +} + +void oggpackB_writeinit(oggpack_buffer *b){ + oggpack_writeinit(b); +} + +void oggpack_writetrunc(oggpack_buffer *b,long bits){ + long bytes=bits>>3; + bits-=bytes*8; + b->ptr=b->buffer+bytes; + b->endbit=bits; + b->endbyte=bytes; + *b->ptr&=mask[bits]; +} + +void oggpackB_writetrunc(oggpack_buffer *b,long bits){ + long bytes=bits>>3; + bits-=bytes*8; + b->ptr=b->buffer+bytes; + b->endbit=bits; + b->endbyte=bytes; + *b->ptr&=mask8B[bits]; +} + +/* Takes only up to 32 bits. */ +void oggpack_write(oggpack_buffer *b,unsigned long value,int bits){ + if(b->endbyte+4>=b->storage){ + b->buffer=_ogg_realloc(b->buffer,b->storage+BUFFER_INCREMENT); + b->storage+=BUFFER_INCREMENT; + b->ptr=b->buffer+b->endbyte; + } + + value&=mask[bits]; + bits+=b->endbit; + + b->ptr[0]|=value<endbit; + + if(bits>=8){ + b->ptr[1]=value>>(8-b->endbit); + if(bits>=16){ + b->ptr[2]=value>>(16-b->endbit); + if(bits>=24){ + b->ptr[3]=value>>(24-b->endbit); + if(bits>=32){ + if(b->endbit) + b->ptr[4]=value>>(32-b->endbit); + else + b->ptr[4]=0; + } + } + } + } + + b->endbyte+=bits/8; + b->ptr+=bits/8; + b->endbit=bits&7; +} + +/* Takes only up to 32 bits. */ +void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits){ + if(b->endbyte+4>=b->storage){ + b->buffer=_ogg_realloc(b->buffer,b->storage+BUFFER_INCREMENT); + b->storage+=BUFFER_INCREMENT; + b->ptr=b->buffer+b->endbyte; + } + + value=(value&mask[bits])<<(32-bits); + bits+=b->endbit; + + b->ptr[0]|=value>>(24+b->endbit); + + if(bits>=8){ + b->ptr[1]=value>>(16+b->endbit); + if(bits>=16){ + b->ptr[2]=value>>(8+b->endbit); + if(bits>=24){ + b->ptr[3]=value>>(b->endbit); + if(bits>=32){ + if(b->endbit) + b->ptr[4]=value<<(8-b->endbit); + else + b->ptr[4]=0; + } + } + } + } + + b->endbyte+=bits/8; + b->ptr+=bits/8; + b->endbit=bits&7; +} + +void oggpack_writealign(oggpack_buffer *b){ + int bits=8-b->endbit; + if(bits<8) + oggpack_write(b,0,bits); +} + +void oggpackB_writealign(oggpack_buffer *b){ + int bits=8-b->endbit; + if(bits<8) + oggpackB_write(b,0,bits); +} + +static void oggpack_writecopy_helper(oggpack_buffer *b, + void *source, + long bits, + void (*w)(oggpack_buffer *, + unsigned long, + int), + int msb){ + unsigned char *ptr=(unsigned char *)source; + + long bytes=bits/8; + bits-=bytes*8; + + if(b->endbit){ + int i; + /* unaligned copy. Do it the hard way. */ + for(i=0;iendbyte+bytes+1>=b->storage){ + b->storage=b->endbyte+bytes+BUFFER_INCREMENT; + b->buffer=_ogg_realloc(b->buffer,b->storage); + b->ptr=b->buffer+b->endbyte; + } + + memmove(b->ptr,source,bytes); + b->ptr+=bytes; + b->buffer+=bytes; + *b->ptr=0; + + } + if(bits){ + if(msb) + w(b,(unsigned long)(ptr[bytes]>>(8-bits)),bits); + else + w(b,(unsigned long)(ptr[bytes]),bits); + } +} + +void oggpack_writecopy(oggpack_buffer *b,void *source,long bits){ + oggpack_writecopy_helper(b,source,bits,oggpack_write,0); +} + +void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits){ + oggpack_writecopy_helper(b,source,bits,oggpackB_write,1); +} + +void oggpack_reset(oggpack_buffer *b){ + b->ptr=b->buffer; + b->buffer[0]=0; + b->endbit=b->endbyte=0; +} + +void oggpackB_reset(oggpack_buffer *b){ + oggpack_reset(b); +} + +void oggpack_writeclear(oggpack_buffer *b){ + _ogg_free(b->buffer); + memset(b,0,sizeof(*b)); +} + +void oggpackB_writeclear(oggpack_buffer *b){ + oggpack_writeclear(b); +} + +void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes){ + memset(b,0,sizeof(*b)); + b->buffer=b->ptr=buf; + b->storage=bytes; +} + +void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes){ + oggpack_readinit(b,buf,bytes); +} + +/* Read in bits without advancing the bitptr; bits <= 32 */ +long oggpack_look(oggpack_buffer *b,int bits){ + unsigned long ret; + unsigned long m=mask[bits]; + + bits+=b->endbit; + + if(b->endbyte+4>=b->storage){ + /* not the main path */ + if(b->endbyte*8+bits>b->storage*8)return(-1); + } + + ret=b->ptr[0]>>b->endbit; + if(bits>8){ + ret|=b->ptr[1]<<(8-b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(16-b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(24-b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]<<(32-b->endbit); + } + } + } + return(m&ret); +} + +/* Read in bits without advancing the bitptr; bits <= 32 */ +long oggpackB_look(oggpack_buffer *b,int bits){ + unsigned long ret; + int m=32-bits; + + bits+=b->endbit; + + if(b->endbyte+4>=b->storage){ + /* not the main path */ + if(b->endbyte*8+bits>b->storage*8)return(-1); + } + + ret=b->ptr[0]<<(24+b->endbit); + if(bits>8){ + ret|=b->ptr[1]<<(16+b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(8+b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]>>(8-b->endbit); + } + } + } + return (ret>>(m>>1))>>((m+1)>>1); +} + +long oggpack_look1(oggpack_buffer *b){ + if(b->endbyte>=b->storage)return(-1); + return((b->ptr[0]>>b->endbit)&1); +} + +long oggpackB_look1(oggpack_buffer *b){ + if(b->endbyte>=b->storage)return(-1); + return((b->ptr[0]>>(7-b->endbit))&1); +} + +void oggpack_adv(oggpack_buffer *b,int bits){ + bits+=b->endbit; + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; +} + +void oggpackB_adv(oggpack_buffer *b,int bits){ + oggpack_adv(b,bits); +} + +void oggpack_adv1(oggpack_buffer *b){ + if(++(b->endbit)>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } +} + +void oggpackB_adv1(oggpack_buffer *b){ + oggpack_adv1(b); +} + +/* bits <= 32 */ +long oggpack_read(oggpack_buffer *b,int bits){ + unsigned long ret; + unsigned long m=mask[bits]; + + bits+=b->endbit; + + if(b->endbyte+4>=b->storage){ + /* not the main path */ + ret=-1UL; + if(b->endbyte*8+bits>b->storage*8)goto overflow; + } + + ret=b->ptr[0]>>b->endbit; + if(bits>8){ + ret|=b->ptr[1]<<(8-b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(16-b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(24-b->endbit); + if(bits>32 && b->endbit){ + ret|=b->ptr[4]<<(32-b->endbit); + } + } + } + } + ret&=m; + + overflow: + + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return(ret); +} + +/* bits <= 32 */ +long oggpackB_read(oggpack_buffer *b,int bits){ + unsigned long ret; + long m=32-bits; + + bits+=b->endbit; + + if(b->endbyte+4>=b->storage){ + /* not the main path */ + ret=-1UL; + if(b->endbyte*8+bits>b->storage*8)goto overflow; + } + + ret=b->ptr[0]<<(24+b->endbit); + if(bits>8){ + ret|=b->ptr[1]<<(16+b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(8+b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]>>(8-b->endbit); + } + } + } + ret=(ret>>(m>>1))>>((m+1)>>1); + + overflow: + + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return(ret); +} + +long oggpack_read1(oggpack_buffer *b){ + unsigned long ret; + + if(b->endbyte>=b->storage){ + /* not the main path */ + ret=-1UL; + goto overflow; + } + + ret=(b->ptr[0]>>b->endbit)&1; + + overflow: + + b->endbit++; + if(b->endbit>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } + return(ret); +} + +long oggpackB_read1(oggpack_buffer *b){ + unsigned long ret; + + if(b->endbyte>=b->storage){ + /* not the main path */ + ret=-1UL; + goto overflow; + } + + ret=(b->ptr[0]>>(7-b->endbit))&1; + + overflow: + + b->endbit++; + if(b->endbit>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } + return(ret); +} + +long oggpack_bytes(oggpack_buffer *b){ + return(b->endbyte+(b->endbit+7)/8); +} + +long oggpack_bits(oggpack_buffer *b){ + return(b->endbyte*8+b->endbit); +} + +long oggpackB_bytes(oggpack_buffer *b){ + return oggpack_bytes(b); +} + +long oggpackB_bits(oggpack_buffer *b){ + return oggpack_bits(b); +} + +unsigned char *oggpack_get_buffer(oggpack_buffer *b){ + return(b->buffer); +} + +unsigned char *oggpackB_get_buffer(oggpack_buffer *b){ + return oggpack_get_buffer(b); +} + +/* Self test of the bitwise routines; everything else is based on + them, so they damned well better be solid. */ + +#ifdef _V_SELFTEST +#include + +static int ilog(unsigned int v){ + int ret=0; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +oggpack_buffer o; +oggpack_buffer r; + +void report(char *in){ + fprintf(stderr,"%s",in); + exit(1); +} + +void cliptest(unsigned long *b,int vals,int bits,int *comp,int compsize){ + long bytes,i; + unsigned char *buffer; + + oggpack_reset(&o); + for(i=0;i. + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: code raw [Vorbis] packets into framed OggSquish stream and + decode Ogg streams back into raw packets + last mod: $Id: framing.c,v 1.23 2002/09/29 07:10:37 giles Exp $ + + note: The CRC code is directly derived from public domain code by + Ross Williams (ross@guest.adelaide.edu.au). See docs/framing.html + for details. + + ********************************************************************/ + +#include +#include +#include "../ogg/ogg.h" + +/* A complete description of Ogg framing exists in docs/framing.html */ + +int ogg_page_version(ogg_page *og){ + return((int)(og->header[4])); +} + +int ogg_page_continued(ogg_page *og){ + return((int)(og->header[5]&0x01)); +} + +int ogg_page_bos(ogg_page *og){ + return((int)(og->header[5]&0x02)); +} + +int ogg_page_eos(ogg_page *og){ + return((int)(og->header[5]&0x04)); +} + +ogg_int64_t ogg_page_granulepos(ogg_page *og){ + unsigned char *page=og->header; + ogg_int64_t granulepos=page[13]&(0xff); + granulepos= (granulepos<<8)|(page[12]&0xff); + granulepos= (granulepos<<8)|(page[11]&0xff); + granulepos= (granulepos<<8)|(page[10]&0xff); + granulepos= (granulepos<<8)|(page[9]&0xff); + granulepos= (granulepos<<8)|(page[8]&0xff); + granulepos= (granulepos<<8)|(page[7]&0xff); + granulepos= (granulepos<<8)|(page[6]&0xff); + return(granulepos); +} + +int ogg_page_serialno(ogg_page *og){ + return(og->header[14] | + (og->header[15]<<8) | + (og->header[16]<<16) | + (og->header[17]<<24)); +} + +long ogg_page_pageno(ogg_page *og){ + return(og->header[18] | + (og->header[19]<<8) | + (og->header[20]<<16) | + (og->header[21]<<24)); +} + + + +/* returns the number of packets that are completed on this page (if + the leading packet is begun on a previous page, but ends on this + page, it's counted */ + +/* NOTE: +If a page consists of a packet begun on a previous page, and a new +packet begun (but not completed) on this page, the return will be: + ogg_page_packets(page) ==1, + ogg_page_continued(page) !=0 + +If a page happens to be a single packet that was begun on a +previous page, and spans to the next page (in the case of a three or +more page packet), the return will be: + ogg_page_packets(page) ==0, + ogg_page_continued(page) !=0 +*/ + +int ogg_page_packets(ogg_page *og){ + int i,n=og->header[26],count=0; + for(i=0;iheader[27+i]<255)count++; + return(count); +} + + +#if 0 +/* helper to initialize lookup for direct-table CRC (illustrative; we + use the static init below) */ + +static ogg_uint32_t _ogg_crc_entry(unsigned long index){ + int i; + unsigned long r; + + r = index << 24; + for (i=0; i<8; i++) + if (r & 0x80000000UL) + r = (r << 1) ^ 0x04c11db7; /* The same as the ethernet generator + polynomial, although we use an + unreflected alg and an init/final + of 0, not 0xffffffff */ + else + r<<=1; + return (r & 0xffffffffUL); +} +#endif + +static ogg_uint32_t crc_lookup[256]={ + 0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9, + 0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005, + 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61, + 0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd, + 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9, + 0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75, + 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011, + 0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd, + 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039, + 0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5, + 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81, + 0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d, + 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49, + 0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95, + 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1, + 0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d, + 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae, + 0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072, + 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16, + 0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca, + 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde, + 0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02, + 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066, + 0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba, + 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e, + 0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692, + 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6, + 0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a, + 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e, + 0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2, + 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686, + 0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a, + 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637, + 0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb, + 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f, + 0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53, + 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47, + 0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b, + 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff, + 0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623, + 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7, + 0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b, + 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f, + 0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3, + 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7, + 0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b, + 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f, + 0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3, + 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640, + 0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c, + 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8, + 0x68860bfd,0x6c47164a,0x61043093,0x65c52d24, + 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30, + 0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec, + 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088, + 0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654, + 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0, + 0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c, + 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18, + 0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4, + 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0, + 0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c, + 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668, + 0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4}; + +/* init the encode/decode logical stream state */ + +int ogg_stream_init(ogg_stream_state *os,int serialno){ + if(os){ + memset(os,0,sizeof(*os)); + // lowered for DOOM + os->body_storage=8*1024; + os->body_data=_ogg_malloc(os->body_storage*sizeof(*os->body_data)); + + os->lacing_storage=1024; + os->lacing_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->lacing_vals)); + os->granule_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->granule_vals)); + + os->serialno=serialno; + + return(0); + } + return(-1); +} + +/* _clear does not free os, only the non-flat storage within */ +int ogg_stream_clear(ogg_stream_state *os){ + if(os){ + if(os->body_data)_ogg_free(os->body_data); + if(os->lacing_vals)_ogg_free(os->lacing_vals); + if(os->granule_vals)_ogg_free(os->granule_vals); + + memset(os,0,sizeof(*os)); + } + return(0); +} + +int ogg_stream_destroy(ogg_stream_state *os){ + if(os){ + ogg_stream_clear(os); + _ogg_free(os); + } + return(0); +} + +/* Helpers for ogg_stream_encode; this keeps the structure and + what's happening fairly clear */ + +static void _os_body_expand(ogg_stream_state *os,int needed){ + if(os->body_storage<=os->body_fill+needed){ + os->body_storage+=(needed+1024); + os->body_data=_ogg_realloc(os->body_data,os->body_storage*sizeof(*os->body_data)); + } +} + +static void _os_lacing_expand(ogg_stream_state *os,int needed){ + if(os->lacing_storage<=os->lacing_fill+needed){ + os->lacing_storage+=(needed+32); + os->lacing_vals=_ogg_realloc(os->lacing_vals,os->lacing_storage*sizeof(*os->lacing_vals)); + os->granule_vals=_ogg_realloc(os->granule_vals,os->lacing_storage*sizeof(*os->granule_vals)); + } +} + +/* checksum the page */ +/* Direct table CRC; note that this will be faster in the future if we + perform the checksum silmultaneously with other copies */ + +void ogg_page_checksum_set(ogg_page *og){ + if(og){ + ogg_uint32_t crc_reg=0; + int i; + + /* safety; needed for API behavior, but not framing code */ + og->header[22]=0; + og->header[23]=0; + og->header[24]=0; + og->header[25]=0; + + for(i=0;iheader_len;i++) + crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg >> 24)&0xff)^og->header[i]]; + for(i=0;ibody_len;i++) + crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg >> 24)&0xff)^og->body[i]]; + + og->header[22]=crc_reg&0xff; + og->header[23]=(crc_reg>>8)&0xff; + og->header[24]=(crc_reg>>16)&0xff; + og->header[25]=(crc_reg>>24)&0xff; + } +} + +/* submit data to the internal buffer of the framing engine */ +int ogg_stream_packetin(ogg_stream_state *os,ogg_packet *op){ + int lacing_vals=op->bytes/255+1,i; + + if(os->body_returned){ + /* advance packet data according to the body_returned pointer. We + had to keep it around to return a pointer into the buffer last + call */ + + os->body_fill-=os->body_returned; + if(os->body_fill) + memmove(os->body_data,os->body_data+os->body_returned, + os->body_fill); + os->body_returned=0; + } + + /* make sure we have the buffer storage */ + _os_body_expand(os,op->bytes); + _os_lacing_expand(os,lacing_vals); + + /* Copy in the submitted packet. Yes, the copy is a waste; this is + the liability of overly clean abstraction for the time being. It + will actually be fairly easy to eliminate the extra copy in the + future */ + + memcpy(os->body_data+os->body_fill,op->packet,op->bytes); + os->body_fill+=op->bytes; + + /* Store lacing vals for this packet */ + for(i=0;ilacing_vals[os->lacing_fill+i]=255; + os->granule_vals[os->lacing_fill+i]=os->granulepos; + } + os->lacing_vals[os->lacing_fill+i]=(op->bytes)%255; + os->granulepos=os->granule_vals[os->lacing_fill+i]=op->granulepos; + + /* flag the first segment as the beginning of the packet */ + os->lacing_vals[os->lacing_fill]|= 0x100; + + os->lacing_fill+=lacing_vals; + + /* for the sake of completeness */ + os->packetno++; + + if(op->e_o_s)os->e_o_s=1; + + return(0); +} + +/* This will flush remaining packets into a page (returning nonzero), + even if there is not enough data to trigger a flush normally + (undersized page). If there are no packets or partial packets to + flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will + try to flush a normal sized page like ogg_stream_pageout; a call to + ogg_stream_flush does not guarantee that all packets have flushed. + Only a return value of 0 from ogg_stream_flush indicates all packet + data is flushed into pages. + + since ogg_stream_flush will flush the last page in a stream even if + it's undersized, you almost certainly want to use ogg_stream_pageout + (and *not* ogg_stream_flush) unless you specifically need to flush + an page regardless of size in the middle of a stream. */ + +int ogg_stream_flush(ogg_stream_state *os,ogg_page *og){ + int i; + int vals=0; + int maxvals=(os->lacing_fill>255?255:os->lacing_fill); + int bytes=0; + long acc=0; + ogg_int64_t granule_pos=os->granule_vals[0]; + + if(maxvals==0)return(0); + + /* construct a page */ + /* decide how many segments to include */ + + /* If this is the initial header case, the first page must only include + the initial header packet */ + if(os->b_o_s==0){ /* 'initial header page' case */ + granule_pos=0; + for(vals=0;valslacing_vals[vals]&0x0ff)<255){ + vals++; + break; + } + } + }else{ + for(vals=0;vals4096)break; + acc+=os->lacing_vals[vals]&0x0ff; + granule_pos=os->granule_vals[vals]; + } + } + + /* construct the header in temp storage */ + memcpy(os->header,"OggS",4); + + /* stream structure version */ + os->header[4]=0x00; + + /* continued packet flag? */ + os->header[5]=0x00; + if((os->lacing_vals[0]&0x100)==0)os->header[5]|=0x01; + /* first page flag? */ + if(os->b_o_s==0)os->header[5]|=0x02; + /* last page flag? */ + if(os->e_o_s && os->lacing_fill==vals)os->header[5]|=0x04; + os->b_o_s=1; + + /* 64 bits of PCM position */ + for(i=6;i<14;i++){ + os->header[i]=(granule_pos&0xff); + granule_pos>>=8; + } + + /* 32 bits of stream serial number */ + { + long serialno=os->serialno; + for(i=14;i<18;i++){ + os->header[i]=(serialno&0xff); + serialno>>=8; + } + } + + /* 32 bits of page counter (we have both counter and page header + because this val can roll over) */ + if(os->pageno==-1)os->pageno=0; /* because someone called + stream_reset; this would be a + strange thing to do in an + encode stream, but it has + plausible uses */ + { + long pageno=os->pageno++; + for(i=18;i<22;i++){ + os->header[i]=(pageno&0xff); + pageno>>=8; + } + } + + /* zero for computation; filled in later */ + os->header[22]=0; + os->header[23]=0; + os->header[24]=0; + os->header[25]=0; + + /* segment table */ + os->header[26]=vals&0xff; + for(i=0;iheader[i+27]=(os->lacing_vals[i]&0xff); + + /* set pointers in the ogg_page struct */ + og->header=os->header; + og->header_len=os->header_fill=vals+27; + og->body=os->body_data+os->body_returned; + og->body_len=bytes; + + /* advance the lacing data and set the body_returned pointer */ + + os->lacing_fill-=vals; + memmove(os->lacing_vals,os->lacing_vals+vals,os->lacing_fill*sizeof(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+vals,os->lacing_fill*sizeof(*os->granule_vals)); + os->body_returned+=bytes; + + /* calculate the checksum */ + + ogg_page_checksum_set(og); + + /* done */ + return(1); +} + + +/* This constructs pages from buffered packet segments. The pointers +returned are to static buffers; do not free. The returned buffers are +good only until the next call (using the same ogg_stream_state) */ + +int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og){ + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + os->body_fill-os->body_returned > 4096 ||/* 'page nominal size' case */ + os->lacing_fill>=255 || /* 'segment table full' case */ + (os->lacing_fill&&!os->b_o_s)){ /* 'initial header page' case */ + + return(ogg_stream_flush(os,og)); + } + + /* not enough data to construct a page and not end of stream */ + return(0); +} + +int ogg_stream_eos(ogg_stream_state *os){ + return os->e_o_s; +} + +/* DECODING PRIMITIVES: packet streaming layer **********************/ + +/* This has two layers to place more of the multi-serialno and paging + control in the application's hands. First, we expose a data buffer + using ogg_sync_buffer(). The app either copies into the + buffer, or passes it directly to read(), etc. We then call + ogg_sync_wrote() to tell how many bytes we just added. + + Pages are returned (pointers into the buffer in ogg_sync_state) + by ogg_sync_pageout(). The page is then submitted to + ogg_stream_pagein() along with the appropriate + ogg_stream_state* (ie, matching serialno). We then get raw + packets out calling ogg_stream_packetout() with a + ogg_stream_state. See the 'frame-prog.txt' docs for details and + example code. */ + +/* initialize the struct to a known state */ +int ogg_sync_init(ogg_sync_state *oy){ + if(oy){ + memset(oy,0,sizeof(*oy)); + } + return(0); +} + +/* clear non-flat storage within */ +int ogg_sync_clear(ogg_sync_state *oy){ + if(oy){ + if(oy->data)_ogg_free(oy->data); + ogg_sync_init(oy); + } + return(0); +} + +int ogg_sync_destroy(ogg_sync_state *oy){ + if(oy){ + ogg_sync_clear(oy); + _ogg_free(oy); + } + return(0); +} + +char *ogg_sync_buffer(ogg_sync_state *oy, long size){ + + /* first, clear out any space that has been previously returned */ + if(oy->returned){ + oy->fill-=oy->returned; + if(oy->fill>0) + memmove(oy->data,oy->data+oy->returned,oy->fill); + oy->returned=0; + } + + if(size>oy->storage-oy->fill){ + /* We need to extend the internal buffer */ + long newsize=size+oy->fill+4096; /* an extra page to be nice */ + + if(oy->data) + oy->data=_ogg_realloc(oy->data,newsize); + else + oy->data=_ogg_malloc(newsize); + oy->storage=newsize; + } + + /* expose a segment at least as large as requested at the fill mark */ + return((char *)oy->data+oy->fill); +} + +int ogg_sync_wrote(ogg_sync_state *oy, long bytes){ + if(oy->fill+bytes>oy->storage)return(-1); + oy->fill+=bytes; + return(0); +} + +/* sync the stream. This is meant to be useful for finding page + boundaries. + + return values for this: + -n) skipped n bytes + 0) page not ready; more data (no bytes skipped) + n) page synced at current location; page length n bytes + +*/ + +long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og){ + unsigned char *page=oy->data+oy->returned; + unsigned char *next; + long bytes=oy->fill-oy->returned; + + if(oy->headerbytes==0){ + int headerbytes,i; + if(bytes<27)return(0); /* not enough for a header */ + + /* verify capture pattern */ + if(memcmp(page,"OggS",4))goto sync_fail; + + headerbytes=page[26]+27; + if(bytesbodybytes+=page[27+i]; + oy->headerbytes=headerbytes; + } + + if(oy->bodybytes+oy->headerbytes>bytes)return(0); + + /* The whole test page is buffered. Verify the checksum */ + { + /* Grab the checksum bytes, set the header field to zero */ + char chksum[4]; + ogg_page log; + + memcpy(chksum,page+22,4); + memset(page+22,0,4); + + /* set up a temp page struct and recompute the checksum */ + log.header=page; + log.header_len=oy->headerbytes; + log.body=page+oy->headerbytes; + log.body_len=oy->bodybytes; + ogg_page_checksum_set(&log); + + /* Compare */ + if(memcmp(chksum,page+22,4)){ + /* D'oh. Mismatch! Corrupt page (or miscapture and not a page + at all) */ + /* replace the computed checksum with the one actually read in */ + memcpy(page+22,chksum,4); + + /* Bad checksum. Lose sync */ + goto sync_fail; + } + } + + /* yes, have a whole page all ready to go */ + { + unsigned char *page=oy->data+oy->returned; + long bytes; + + if(og){ + og->header=page; + og->header_len=oy->headerbytes; + og->body=page+oy->headerbytes; + og->body_len=oy->bodybytes; + } + + oy->unsynced=0; + oy->returned+=(bytes=oy->headerbytes+oy->bodybytes); + oy->headerbytes=0; + oy->bodybytes=0; + return(bytes); + } + + sync_fail: + + oy->headerbytes=0; + oy->bodybytes=0; + + /* search for possible capture */ + next=memchr(page+1,'O',bytes-1); + if(!next) + next=oy->data+oy->fill; + + oy->returned=next-oy->data; + return(-(next-page)); +} + +/* sync the stream and get a page. Keep trying until we find a page. + Supress 'sync errors' after reporting the first. + + return values: + -1) recapture (hole in data) + 0) need more data + 1) page returned + + Returns pointers into buffered data; invalidated by next call to + _stream, _clear, _init, or _buffer */ + +int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og){ + + /* all we need to do is verify a page at the head of the stream + buffer. If it doesn't verify, we look for the next potential + frame */ + + while(1){ + long ret=ogg_sync_pageseek(oy,og); + if(ret>0){ + /* have a page */ + return(1); + } + if(ret==0){ + /* need more data */ + return(0); + } + + /* head did not start a synced page... skipped some bytes */ + if(!oy->unsynced){ + oy->unsynced=1; + return(-1); + } + + /* loop. keep looking */ + + } +} + +/* add the incoming page to the stream state; we decompose the page + into packet segments here as well. */ + +int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og){ + unsigned char *header=og->header; + unsigned char *body=og->body; + long bodysize=og->body_len; + int segptr=0; + + int version=ogg_page_version(og); + int continued=ogg_page_continued(og); + int bos=ogg_page_bos(og); + int eos=ogg_page_eos(og); + ogg_int64_t granulepos=ogg_page_granulepos(og); + int serialno=ogg_page_serialno(og); + long pageno=ogg_page_pageno(og); + int segments=header[26]; + + /* clean up 'returned data' */ + { + long lr=os->lacing_returned; + long br=os->body_returned; + + /* body data */ + if(br){ + os->body_fill-=br; + if(os->body_fill) + memmove(os->body_data,os->body_data+br,os->body_fill); + os->body_returned=0; + } + + if(lr){ + /* segment table */ + if(os->lacing_fill-lr){ + memmove(os->lacing_vals,os->lacing_vals+lr, + (os->lacing_fill-lr)*sizeof(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+lr, + (os->lacing_fill-lr)*sizeof(*os->granule_vals)); + } + os->lacing_fill-=lr; + os->lacing_packet-=lr; + os->lacing_returned=0; + } + } + + /* check the serial number */ + if(serialno!=os->serialno)return(-1); + if(version>0)return(-1); + + _os_lacing_expand(os,segments+1); + + /* are we in sequence? */ + if(pageno!=os->pageno){ + int i; + + /* unroll previous partial packet (if any) */ + for(i=os->lacing_packet;ilacing_fill;i++) + os->body_fill-=os->lacing_vals[i]&0xff; + os->lacing_fill=os->lacing_packet; + + /* make a note of dropped data in segment table */ + if(os->pageno!=-1){ + os->lacing_vals[os->lacing_fill++]=0x400; + os->lacing_packet++; + } + + /* are we a 'continued packet' page? If so, we'll need to skip + some segments */ + if(continued){ + bos=0; + for(;segptrbody_data+os->body_fill,body,bodysize); + os->body_fill+=bodysize; + } + + { + int saved=-1; + while(segptrlacing_vals[os->lacing_fill]=val; + os->granule_vals[os->lacing_fill]=-1; + + if(bos){ + os->lacing_vals[os->lacing_fill]|=0x100; + bos=0; + } + + if(val<255)saved=os->lacing_fill; + + os->lacing_fill++; + segptr++; + + if(val<255)os->lacing_packet=os->lacing_fill; + } + + /* set the granulepos on the last granuleval of the last full packet */ + if(saved!=-1){ + os->granule_vals[saved]=granulepos; + } + + } + + if(eos){ + os->e_o_s=1; + if(os->lacing_fill>0) + os->lacing_vals[os->lacing_fill-1]|=0x200; + } + + os->pageno=pageno+1; + + return(0); +} + +/* clear things to an initial state. Good to call, eg, before seeking */ +int ogg_sync_reset(ogg_sync_state *oy){ + oy->fill=0; + oy->returned=0; + oy->unsynced=0; + oy->headerbytes=0; + oy->bodybytes=0; + return(0); +} + +int ogg_stream_reset(ogg_stream_state *os){ + os->body_fill=0; + os->body_returned=0; + + os->lacing_fill=0; + os->lacing_packet=0; + os->lacing_returned=0; + + os->header_fill=0; + + os->e_o_s=0; + os->b_o_s=0; + os->pageno=-1; + os->packetno=0; + os->granulepos=0; + + return(0); +} + +int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno){ + ogg_stream_reset(os); + os->serialno=serialno; + return(0); +} + +static int _packetout(ogg_stream_state *os,ogg_packet *op,int adv){ + + /* The last part of decode. We have the stream broken into packet + segments. Now we need to group them into packets (or return the + out of sync markers) */ + + int ptr=os->lacing_returned; + + if(os->lacing_packet<=ptr)return(0); + + if(os->lacing_vals[ptr]&0x400){ + /* we need to tell the codec there's a gap; it might need to + handle previous packet dependencies. */ + os->lacing_returned++; + os->packetno++; + return(-1); + } + + if(!op && !adv)return(1); /* just using peek as an inexpensive way + to ask if there's a whole packet + waiting */ + + /* Gather the whole packet. We'll have no holes or a partial packet */ + { + int size=os->lacing_vals[ptr]&0xff; + int bytes=size; + int eos=os->lacing_vals[ptr]&0x200; /* last packet of the stream? */ + int bos=os->lacing_vals[ptr]&0x100; /* first packet of the stream? */ + + while(size==255){ + int val=os->lacing_vals[++ptr]; + size=val&0xff; + if(val&0x200)eos=0x200; + bytes+=size; + } + + if(op){ + op->e_o_s=eos; + op->b_o_s=bos; + op->packet=os->body_data+os->body_returned; + op->packetno=os->packetno; + op->granulepos=os->granule_vals[ptr]; + op->bytes=bytes; + } + + if(adv){ + os->body_returned+=bytes; + os->lacing_returned=ptr+1; + os->packetno++; + } + } + return(1); +} + +int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op){ + return _packetout(os,op,1); +} + +int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op){ + return _packetout(os,op,0); +} + +void ogg_packet_clear(ogg_packet *op) { + _ogg_free(op->packet); + memset(op, 0, sizeof(*op)); +} + +#ifdef _V_SELFTEST +#include + +ogg_stream_state os_en, os_de; +ogg_sync_state oy; + +void checkpacket(ogg_packet *op,int len, int no, int pos){ + long j; + static int sequence=0; + static int lastno=0; + + if(op->bytes!=len){ + fprintf(stderr,"incorrect packet length!\n"); + exit(1); + } + if(op->granulepos!=pos){ + fprintf(stderr,"incorrect packet position!\n"); + exit(1); + } + + /* packet number just follows sequence/gap; adjust the input number + for that */ + if(no==0){ + sequence=0; + }else{ + sequence++; + if(no>lastno+1) + sequence++; + } + lastno=no; + if(op->packetno!=sequence){ + fprintf(stderr,"incorrect packet sequence %ld != %d\n", + (long)(op->packetno),sequence); + exit(1); + } + + /* Test data */ + for(j=0;jbytes;j++) + if(op->packet[j]!=((j+no)&0xff)){ + fprintf(stderr,"body data mismatch (1) at pos %ld: %x!=%lx!\n\n", + j,op->packet[j],(j+no)&0xff); + exit(1); + } +} + +void check_page(unsigned char *data,const int *header,ogg_page *og){ + long j; + /* Test data */ + for(j=0;jbody_len;j++) + if(og->body[j]!=data[j]){ + fprintf(stderr,"body data mismatch (2) at pos %ld: %x!=%x!\n\n", + j,data[j],og->body[j]); + exit(1); + } + + /* Test header */ + for(j=0;jheader_len;j++){ + if(og->header[j]!=header[j]){ + fprintf(stderr,"header content mismatch at pos %ld:\n",j); + for(j=0;jheader[j]); + fprintf(stderr,"\n"); + exit(1); + } + } + if(og->header_len!=header[26]+27){ + fprintf(stderr,"header length incorrect! (%ld!=%d)\n", + og->header_len,header[26]+27); + exit(1); + } +} + +void print_header(ogg_page *og){ + int j; + fprintf(stderr,"\nHEADER:\n"); + fprintf(stderr," capture: %c %c %c %c version: %d flags: %x\n", + og->header[0],og->header[1],og->header[2],og->header[3], + (int)og->header[4],(int)og->header[5]); + + fprintf(stderr," granulepos: %d serialno: %d pageno: %ld\n", + (og->header[9]<<24)|(og->header[8]<<16)| + (og->header[7]<<8)|og->header[6], + (og->header[17]<<24)|(og->header[16]<<16)| + (og->header[15]<<8)|og->header[14], + ((long)(og->header[21])<<24)|(og->header[20]<<16)| + (og->header[19]<<8)|og->header[18]); + + fprintf(stderr," checksum: %02x:%02x:%02x:%02x\n segments: %d (", + (int)og->header[22],(int)og->header[23], + (int)og->header[24],(int)og->header[25], + (int)og->header[26]); + + for(j=27;jheader_len;j++) + fprintf(stderr,"%d ",(int)og->header[j]); + fprintf(stderr,")\n\n"); +} + +void copy_page(ogg_page *og){ + unsigned char *temp=_ogg_malloc(og->header_len); + memcpy(temp,og->header,og->header_len); + og->header=temp; + + temp=_ogg_malloc(og->body_len); + memcpy(temp,og->body,og->body_len); + og->body=temp; +} + +void error(void){ + fprintf(stderr,"error!\n"); + exit(1); +} + +/* 17 only */ +const int head1_0[] = {0x4f,0x67,0x67,0x53,0,0x06, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x15,0xed,0xec,0x91, + 1, + 17}; + +/* 17, 254, 255, 256, 500, 510, 600 byte, pad */ +const int head1_1[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x59,0x10,0x6c,0x2c, + 1, + 17}; +const int head2_1[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x18,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x89,0x33,0x85,0xce, + 13, + 254,255,0,255,1,255,245,255,255,0, + 255,255,90}; + +/* nil packets; beginning,middle,end */ +const int head1_2[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; +const int head2_2[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x28,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x5c,0x3f,0x66,0xcb, + 17, + 17,254,255,0,0,255,1,0,255,245,255,255,0, + 255,255,90,0}; + +/* large initial packet */ +const int head1_3[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x01,0x27,0x31,0xaa, + 18, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,10}; + +const int head2_3[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x7f,0x4e,0x8a,0xd2, + 4, + 255,4,255,0}; + + +/* continuing packet test */ +const int head1_4[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_4[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x34,0x24,0xd5,0x29, + 17, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255}; + +const int head3_4[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xc8,0xc3,0xcb,0xed, + 5, + 10,255,4,255,0}; + + +/* page with the 255 segment limit */ +const int head1_5[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_5[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0xfc,0x03,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xed,0x2a,0x2e,0xa7, + 255, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10}; + +const int head3_5[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x6c,0x3b,0x82,0x3d, + 1, + 50}; + + +/* packet that overspans over an entire page */ +const int head1_6[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_6[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x3c,0xd9,0x4d,0x3f, + 17, + 100,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255}; + +const int head3_6[] = {0x4f,0x67,0x67,0x53,0,0x01, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xbd,0xd5,0xb5,0x8b, + 17, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255}; + +const int head4_6[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,3,0,0,0, + 0xef,0xdd,0x88,0xde, + 7, + 255,255,75,255,4,255,0}; + +/* packet that overspans over an entire page */ +const int head1_7[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_7[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x3c,0xd9,0x4d,0x3f, + 17, + 100,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255}; + +const int head3_7[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xd4,0xe0,0x60,0xe5, + 1,0}; + +void test_pack(const int *pl, const int **headers){ + unsigned char *data=_ogg_malloc(1024*1024); /* for scripted test cases only */ + long inptr=0; + long outptr=0; + long deptr=0; + long depacket=0; + long granule_pos=7,pageno=0; + int i,j,packets,pageout=0; + int eosflag=0; + int bosflag=0; + + ogg_stream_reset(&os_en); + ogg_stream_reset(&os_de); + ogg_sync_reset(&oy); + + for(packets=0;;packets++)if(pl[packets]==-1)break; + + for(i=0;i0){ + /* got a page. Happy happy. Verify that it's good. */ + + check_page(data+deptr,headers[pageout],&og_de); + deptr+=og_de.body_len; + pageout++; + + /* submit it to deconstitution */ + ogg_stream_pagein(&os_de,&og_de); + + /* packets out? */ + while(ogg_stream_packetpeek(&os_de,&op_de2)>0){ + ogg_stream_packetpeek(&os_de,NULL); + ogg_stream_packetout(&os_de,&op_de); /* just catching them all */ + + /* verify peek and out match */ + if(memcmp(&op_de,&op_de2,sizeof(op_de))){ + fprintf(stderr,"packetout != packetpeek! pos=%ld\n", + depacket); + exit(1); + } + + /* verify the packet! */ + /* check data */ + if(memcmp(data+depacket,op_de.packet,op_de.bytes)){ + fprintf(stderr,"packet data mismatch in decode! pos=%ld\n", + depacket); + exit(1); + } + /* check bos flag */ + if(bosflag==0 && op_de.b_o_s==0){ + fprintf(stderr,"b_o_s flag not set on packet!\n"); + exit(1); + } + if(bosflag && op_de.b_o_s){ + fprintf(stderr,"b_o_s flag incorrectly set on packet!\n"); + exit(1); + } + bosflag=1; + depacket+=op_de.bytes; + + /* check eos flag */ + if(eosflag){ + fprintf(stderr,"Multiple decoded packets with eos flag!\n"); + exit(1); + } + + if(op_de.e_o_s)eosflag=1; + + /* check granulepos flag */ + if(op_de.granulepos!=-1){ + fprintf(stderr," granule:%ld ",(long)op_de.granulepos); + } + } + } + } + } + } + } + _ogg_free(data); + if(headers[pageno]!=NULL){ + fprintf(stderr,"did not write last page!\n"); + exit(1); + } + if(headers[pageout]!=NULL){ + fprintf(stderr,"did not decode last page!\n"); + exit(1); + } + if(inptr!=outptr){ + fprintf(stderr,"encoded page data incomplete!\n"); + exit(1); + } + if(inptr!=deptr){ + fprintf(stderr,"decoded page data incomplete!\n"); + exit(1); + } + if(inptr!=depacket){ + fprintf(stderr,"decoded packet data incomplete!\n"); + exit(1); + } + if(!eosflag){ + fprintf(stderr,"Never got a packet with EOS set!\n"); + exit(1); + } + fprintf(stderr,"ok.\n"); +} + +int main(void){ + + ogg_stream_init(&os_en,0x04030201); + ogg_stream_init(&os_de,0x04030201); + ogg_sync_init(&oy); + + /* Exercise each code path in the framing code. Also verify that + the checksums are working. */ + + { + /* 17 only */ + const int packets[]={17, -1}; + const int *headret[]={head1_0,NULL}; + + fprintf(stderr,"testing single page encoding... "); + test_pack(packets,headret); + } + + { + /* 17, 254, 255, 256, 500, 510, 600 byte, pad */ + const int packets[]={17, 254, 255, 256, 500, 510, 600, -1}; + const int *headret[]={head1_1,head2_1,NULL}; + + fprintf(stderr,"testing basic page encoding... "); + test_pack(packets,headret); + } + + { + /* nil packets; beginning,middle,end */ + const int packets[]={0,17, 254, 255, 0, 256, 0, 500, 510, 600, 0, -1}; + const int *headret[]={head1_2,head2_2,NULL}; + + fprintf(stderr,"testing basic nil packets... "); + test_pack(packets,headret); + } + + { + /* large initial packet */ + const int packets[]={4345,259,255,-1}; + const int *headret[]={head1_3,head2_3,NULL}; + + fprintf(stderr,"testing initial-packet lacing > 4k... "); + test_pack(packets,headret); + } + + { + /* continuing packet test */ + const int packets[]={0,4345,259,255,-1}; + const int *headret[]={head1_4,head2_4,head3_4,NULL}; + + fprintf(stderr,"testing single packet page span... "); + test_pack(packets,headret); + } + + /* page with the 255 segment limit */ + { + + const int packets[]={0,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,50,-1}; + const int *headret[]={head1_5,head2_5,head3_5,NULL}; + + fprintf(stderr,"testing max packet segments... "); + test_pack(packets,headret); + } + + { + /* packet that overspans over an entire page */ + const int packets[]={0,100,9000,259,255,-1}; + const int *headret[]={head1_6,head2_6,head3_6,head4_6,NULL}; + + fprintf(stderr,"testing very large packets... "); + test_pack(packets,headret); + } + + { + /* term only page. why not? */ + const int packets[]={0,100,4080,-1}; + const int *headret[]={head1_7,head2_7,head3_7,NULL}; + + fprintf(stderr,"testing zero data page (1 nil packet)... "); + test_pack(packets,headret); + } + + + + { + /* build a bunch of pages for testing */ + unsigned char *data=_ogg_malloc(1024*1024); + int pl[]={0,100,4079,2956,2057,76,34,912,0,234,1000,1000,1000,300,-1}; + int inptr=0,i,j; + ogg_page og[5]; + + ogg_stream_reset(&os_en); + + for(i=0;pl[i]!=-1;i++){ + ogg_packet op; + int len=pl[i]; + + op.packet=data+inptr; + op.bytes=len; + op.e_o_s=(pl[i+1]<0?1:0); + op.granulepos=(i+1)*1000; + + for(j=0;j0)error(); + + /* Test fractional page inputs: incomplete fixed header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+3, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+23, + 5); + ogg_sync_wrote(&oy,5); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete body */ + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+28, + og[1].header_len-28); + ogg_sync_wrote(&oy,og[1].header_len-28); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body,1000); + ogg_sync_wrote(&oy,1000); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body+1000, + og[1].body_len-1000); + ogg_sync_wrote(&oy,og[1].body_len-1000); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test fractional page inputs: page + incomplete capture */ + { + ogg_page og_de; + fprintf(stderr,"Testing sync on 1+partial inputs... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+20, + og[1].header_len-20); + ogg_sync_wrote(&oy,og[1].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test recapture: garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing search for capture... "); + ogg_sync_reset(&oy); + + /* 'garbage' */ + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header+20, + og[2].header_len-20); + ogg_sync_wrote(&oy,og[2].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len); + ogg_sync_wrote(&oy,og[2].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test recapture: page + garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing recapture... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len-5); + ogg_sync_wrote(&oy,og[2].body_len-5); + + memcpy(ogg_sync_buffer(&oy,og[3].header_len),og[3].header, + og[3].header_len); + ogg_sync_wrote(&oy,og[3].header_len); + + memcpy(ogg_sync_buffer(&oy,og[3].body_len),og[3].body, + og[3].body_len); + ogg_sync_wrote(&oy,og[3].body_len); + + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + } + + return(0); +} + +#endif + + + + diff --git a/src/sound/oggvorbis/vorbis/codec.h b/src/sound/oggvorbis/vorbis/codec.h new file mode 100644 index 0000000..b458a9f --- /dev/null +++ b/src/sound/oggvorbis/vorbis/codec.h @@ -0,0 +1,264 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + + ******************************************************************** + + function: libvorbis codec headers + last mod: $Id: codec.h,v 1.45 2003/09/05 22:34:46 giles Exp $ + + ********************************************************************/ + +#ifndef _vorbis_codec_h_ +#define _vorbis_codec_h_ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#include "../ogg/ogg.h" + +typedef struct vorbis_info{ + int version; + int channels; + long rate; + + /* The below bitrate declarations are *hints*. + Combinations of the three values carry the following implications: + + all three set to the same value: + implies a fixed rate bitstream + only nominal set: + implies a VBR stream that averages the nominal bitrate. No hard + upper/lower limit + upper and or lower set: + implies a VBR bitstream that obeys the bitrate limits. nominal + may also be set to give a nominal rate. + none set: + the coder does not care to speculate. + */ + + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + + void *codec_setup; +} vorbis_info; + +/* vorbis_dsp_state buffers the current vorbis audio + analysis/synthesis state. The DSP state belongs to a specific + logical bitstream ****************************************************/ +typedef struct vorbis_dsp_state{ + int analysisp; + vorbis_info *vi; + + float **pcm; + float **pcmret; + int pcm_storage; + int pcm_current; + int pcm_returned; + + int preextrapolate; + int eofflag; + + long lW; + long W; + long nW; + long centerW; + + ogg_int64_t granulepos; + ogg_int64_t sequence; + + ogg_int64_t glue_bits; + ogg_int64_t time_bits; + ogg_int64_t floor_bits; + ogg_int64_t res_bits; + + void *backend_state; +} vorbis_dsp_state; + +typedef struct vorbis_block{ + /* necessary stream state for linking to the framing abstraction */ + float **pcm; /* this is a pointer into local storage */ + oggpack_buffer opb; + + long lW; + long W; + long nW; + int pcmend; + int mode; + + int eofflag; + ogg_int64_t granulepos; + ogg_int64_t sequence; + vorbis_dsp_state *vd; /* For read-only access of configuration */ + + /* local storage to avoid remallocing; it's up to the mapping to + structure it */ + void *localstore; + long localtop; + long localalloc; + long totaluse; + struct alloc_chain *reap; + + /* bitmetrics for the frame */ + long glue_bits; + long time_bits; + long floor_bits; + long res_bits; + + void *internal; + +} vorbis_block; + +/* vorbis_block is a single block of data to be processed as part of +the analysis/synthesis stream; it belongs to a specific logical +bitstream, but is independant from other vorbis_blocks belonging to +that logical bitstream. *************************************************/ + +struct alloc_chain{ + void *ptr; + struct alloc_chain *next; +}; + +/* vorbis_info contains all the setup information specific to the + specific compression/decompression mode in progress (eg, + psychoacoustic settings, channel setup, options, codebook + etc). vorbis_info and substructures are in backends.h. +*********************************************************************/ + +/* the comments are not part of vorbis_info so that vorbis_info can be + static storage */ +typedef struct vorbis_comment{ + /* unlimited user comment fields. libvorbis writes 'libvorbis' + whatever vendor is set to in encode */ + char **user_comments; + int *comment_lengths; + int comments; + char *vendor; + +} vorbis_comment; + + +/* libvorbis encodes in two abstraction layers; first we perform DSP + and produce a packet (see docs/analysis.txt). The packet is then + coded into a framed OggSquish bitstream by the second layer (see + docs/framing.txt). Decode is the reverse process; we sync/frame + the bitstream and extract individual packets, then decode the + packet back into PCM audio. + + The extra framing/packetizing is used in streaming formats, such as + files. Over the net (such as with UDP), the framing and + packetization aren't necessary as they're provided by the transport + and the streaming layer is not used */ + +/* Vorbis PRIMITIVES: general ***************************************/ + +extern void vorbis_info_init(vorbis_info *vi); +extern void vorbis_info_clear(vorbis_info *vi); +extern int vorbis_info_blocksize(vorbis_info *vi,int zo); +extern void vorbis_comment_init(vorbis_comment *vc); +extern void vorbis_comment_add(vorbis_comment *vc, char *comment); +extern void vorbis_comment_add_tag(vorbis_comment *vc, + char *tag, char *contents); +extern char *vorbis_comment_query(vorbis_comment *vc, char *tag, int count); +extern int vorbis_comment_query_count(vorbis_comment *vc, char *tag); +extern void vorbis_comment_clear(vorbis_comment *vc); + +extern int vorbis_block_init(vorbis_dsp_state *v, vorbis_block *vb); +extern int vorbis_block_clear(vorbis_block *vb); +extern void vorbis_dsp_clear(vorbis_dsp_state *v); +extern double vorbis_granule_time(vorbis_dsp_state *v, + ogg_int64_t granulepos); + +/* Vorbis PRIMITIVES: analysis/DSP layer ****************************/ + +extern int vorbis_analysis_init(vorbis_dsp_state *v,vorbis_info *vi); +extern int vorbis_commentheader_out(vorbis_comment *vc, ogg_packet *op); +extern int vorbis_analysis_headerout(vorbis_dsp_state *v, + vorbis_comment *vc, + ogg_packet *op, + ogg_packet *op_comm, + ogg_packet *op_code); +extern float **vorbis_analysis_buffer(vorbis_dsp_state *v,int vals); +extern int vorbis_analysis_wrote(vorbis_dsp_state *v,int vals); +extern int vorbis_analysis_blockout(vorbis_dsp_state *v,vorbis_block *vb); +extern int vorbis_analysis(vorbis_block *vb,ogg_packet *op); + +extern int vorbis_bitrate_addblock(vorbis_block *vb); +extern int vorbis_bitrate_flushpacket(vorbis_dsp_state *vd, + ogg_packet *op); + +/* Vorbis PRIMITIVES: synthesis layer *******************************/ +extern int vorbis_synthesis_headerin(vorbis_info *vi,vorbis_comment *vc, + ogg_packet *op); + +extern int vorbis_synthesis_init(vorbis_dsp_state *v,vorbis_info *vi); +extern int vorbis_synthesis_restart(vorbis_dsp_state *v); +extern int vorbis_synthesis(vorbis_block *vb,ogg_packet *op); +extern int vorbis_synthesis_trackonly(vorbis_block *vb,ogg_packet *op); +extern int vorbis_synthesis_blockin(vorbis_dsp_state *v,vorbis_block *vb); +extern int vorbis_synthesis_pcmout(vorbis_dsp_state *v,float ***pcm); +extern int vorbis_synthesis_lapout(vorbis_dsp_state *v,float ***pcm); +extern int vorbis_synthesis_read(vorbis_dsp_state *v,int samples); +extern long vorbis_packet_blocksize(vorbis_info *vi,ogg_packet *op); + +extern int vorbis_synthesis_halfrate(vorbis_info *v,int flag); +extern int vorbis_synthesis_halfrate_p(vorbis_info *v); + +/* Vorbis ERRORS and return codes ***********************************/ + +#define OV_FALSE -1 +#define OV_EOF -2 +#define OV_HOLE -3 + +#define OV_EREAD -128 +#define OV_EFAULT -129 +#define OV_EIMPL -130 +#define OV_EINVAL -131 +#define OV_ENOTVORBIS -132 +#define OV_EBADHEADER -133 +#define OV_EVERSION -134 +#define OV_ENOTAUDIO -135 +#define OV_EBADPACKET -136 +#define OV_EBADLINK -137 +#define OV_ENOSEEK -138 + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + diff --git a/src/sound/oggvorbis/vorbis/vorbisenc.h b/src/sound/oggvorbis/vorbis/vorbisenc.h new file mode 100644 index 0000000..67cb4bd --- /dev/null +++ b/src/sound/oggvorbis/vorbis/vorbisenc.h @@ -0,0 +1,117 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: vorbis encode-engine setup + last mod: $Id: vorbisenc.h,v 1.10 2002/07/01 11:20:10 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _OV_ENC_H_ +#define _OV_ENC_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#include "codec.h" + +extern int vorbis_encode_init(vorbis_info *vi, + long channels, + long rate, + + long max_bitrate, + long nominal_bitrate, + long min_bitrate); + +extern int vorbis_encode_setup_managed(vorbis_info *vi, + long channels, + long rate, + + long max_bitrate, + long nominal_bitrate, + long min_bitrate); + +extern int vorbis_encode_setup_vbr(vorbis_info *vi, + long channels, + long rate, + + float /* quality level from 0. (lo) to 1. (hi) */ + ); + +extern int vorbis_encode_init_vbr(vorbis_info *vi, + long channels, + long rate, + + float base_quality /* quality level from 0. (lo) to 1. (hi) */ + ); + +extern int vorbis_encode_setup_init(vorbis_info *vi); + +extern int vorbis_encode_ctl(vorbis_info *vi,int number,void *arg); + +#define OV_ECTL_RATEMANAGE_GET 0x10 + +#define OV_ECTL_RATEMANAGE_SET 0x11 +#define OV_ECTL_RATEMANAGE_AVG 0x12 +#define OV_ECTL_RATEMANAGE_HARD 0x13 + +#define OV_ECTL_LOWPASS_GET 0x20 +#define OV_ECTL_LOWPASS_SET 0x21 + +#define OV_ECTL_IBLOCK_GET 0x30 +#define OV_ECTL_IBLOCK_SET 0x31 + +struct ovectl_ratemanage_arg { + int management_active; + + long bitrate_hard_min; + long bitrate_hard_max; + double bitrate_hard_window; + + long bitrate_av_lo; + long bitrate_av_hi; + double bitrate_av_window; + double bitrate_av_window_center; +}; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + + diff --git a/src/sound/oggvorbis/vorbis/vorbisfile.h b/src/sound/oggvorbis/vorbis/vorbisfile.h new file mode 100644 index 0000000..30e2ed9 --- /dev/null +++ b/src/sound/oggvorbis/vorbis/vorbisfile.h @@ -0,0 +1,168 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: stdio-based convenience library for opening/seeking/decoding + last mod: $Id: vorbisfile.h,v 1.20 2003/08/18 05:34:01 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _OV_FILE_H_ +#define _OV_FILE_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +#include +#include "codec.h" + +/* The function prototypes for the callbacks are basically the same as for + * the stdio functions fread, fseek, fclose, ftell. + * The one difference is that the FILE * arguments have been replaced with + * a void * - this is to be used as a pointer to whatever internal data these + * functions might need. In the stdio case, it's just a FILE * cast to a void * + * + * If you use other functions, check the docs for these functions and return + * the right values. For seek_func(), you *MUST* return -1 if the stream is + * unseekable + */ +typedef struct { + size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource); + int (*seek_func) (void *datasource, ogg_int64_t offset, int whence); + int (*close_func) (void *datasource); + long (*tell_func) (void *datasource); +} ov_callbacks; + +#define NOTOPEN 0 +#define PARTOPEN 1 +#define OPENED 2 +#define STREAMSET 3 +#define INITSET 4 + +typedef struct OggVorbis_File { + void *datasource; /* Pointer to a FILE *, etc. */ + int seekable; + ogg_int64_t offset; + ogg_int64_t end; + ogg_sync_state oy; + + /* If the FILE handle isn't seekable (eg, a pipe), only the current + stream appears */ + int links; + ogg_int64_t *offsets; + ogg_int64_t *dataoffsets; + long *serialnos; + ogg_int64_t *pcmlengths; /* overloaded to maintain binary + compatability; x2 size, stores both + beginning and end values */ + vorbis_info *vi; + vorbis_comment *vc; + + /* Decoding working state local storage */ + ogg_int64_t pcm_offset; + int ready_state; + long current_serialno; + int current_link; + + double bittrack; + double samptrack; + + ogg_stream_state os; /* take physical pages, weld into a logical + stream of packets */ + vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ + vorbis_block vb; /* local working space for packet->PCM decode */ + + ov_callbacks callbacks; + + int stream; +} OggVorbis_File; + +extern int ov_clear(OggVorbis_File *vf); +extern int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes); +extern int ov_open_callbacks(void *datasource, OggVorbis_File *vf, + char *initial, long ibytes, ov_callbacks callbacks); + +extern int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes); +extern int ov_test_callbacks(void *datasource, OggVorbis_File *vf, + char *initial, long ibytes, ov_callbacks callbacks); +extern int ov_test_open(OggVorbis_File *vf); + +extern long ov_bitrate(OggVorbis_File *vf,int i); +extern long ov_bitrate_instant(OggVorbis_File *vf); +extern long ov_streams(OggVorbis_File *vf); +extern long ov_seekable(OggVorbis_File *vf); +extern long ov_serialnumber(OggVorbis_File *vf,int i); + +extern ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i); +extern ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i); +extern double ov_TIME_Total(OggVorbis_File *vf,int i); + +extern int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos); +extern int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos); +extern int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos); +extern int ov_time_seek(OggVorbis_File *vf,double pos); +extern int ov_time_seek_page(OggVorbis_File *vf,double pos); + +extern int ov_raw_seek_lap(OggVorbis_File *vf,ogg_int64_t pos); +extern int ov_pcm_seek_lap(OggVorbis_File *vf,ogg_int64_t pos); +extern int ov_pcm_seek_page_lap(OggVorbis_File *vf,ogg_int64_t pos); +extern int ov_time_seek_lap(OggVorbis_File *vf,double pos); +extern int ov_time_seek_page_lap(OggVorbis_File *vf,double pos); + +extern ogg_int64_t ov_raw_tell(OggVorbis_File *vf); +extern ogg_int64_t ov_pcm_tell(OggVorbis_File *vf); +extern double ov_TIME_Tell(OggVorbis_File *vf); + +extern vorbis_info *ov_info(OggVorbis_File *vf,int link); +extern vorbis_comment *ov_comment(OggVorbis_File *vf,int link); + +extern long ov_read_float(OggVorbis_File *vf,float ***pcm_channels,int samples, + int *bitstream); +extern long ov_read(OggVorbis_File *vf,char *buffer,int length, + int bigendianp,int word,int sgned,int *bitstream); +extern int ov_crosslap(OggVorbis_File *vf1,OggVorbis_File *vf2); + +extern int ov_halfrate(OggVorbis_File *vf,int flag); +extern int ov_halfrate_p(OggVorbis_File *vf); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + + diff --git a/src/sound/oggvorbis/vorbissrc/analysis.c b/src/sound/oggvorbis/vorbissrc/analysis.c new file mode 100644 index 0000000..c27d8c9 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/analysis.c @@ -0,0 +1,143 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: single-block PCM analysis mode dispatch + last mod: $Id: analysis.c,v 1.55 2002/07/11 06:40:48 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "registry.h" +#include "scales.h" +#include "os.h" +#include "misc.h" + +int analysis_noisy=1; + +/* decides between modes, dispatches to the appropriate mapping. */ +int vorbis_analysis(vorbis_block *vb, ogg_packet *op){ + int ret; + + vb->glue_bits=0; + vb->time_bits=0; + vb->floor_bits=0; + vb->res_bits=0; + + /* first things first. Make sure encode is ready */ + oggpack_reset(&vb->opb); + + /* we only have one mapping type (0), and we let the mapping code + itself figure out what soft mode to use. This allows easier + bitrate management */ + + if((ret=_mapping_P[0]->forward(vb))) + return(ret); + + if(op){ + if(vorbis_bitrate_managed(vb)) + /* The app is using a bitmanaged mode... but not using the + bitrate management interface. */ + return(OV_EINVAL); + + op->packet=oggpack_get_buffer(&vb->opb); + op->bytes=oggpack_bytes(&vb->opb); + op->b_o_s=0; + op->e_o_s=vb->eofflag; + op->granulepos=vb->granulepos; + op->packetno=vb->sequence; /* for sake of completeness */ + } + return(0); +} + +/* there was no great place to put this.... */ +void _analysis_output_always(char *base,int i,float *v,int n,int bark,int dB,ogg_int64_t off){ + int j; + FILE *of; + char buffer[80]; + + /* if(i==5870){*/ + sprintf(buffer,"%s_%d.m",base,i); + of=fopen(buffer,"w"); + + if(!of)perror("failed to open data dump file"); + + for(j=0;j. + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: libvorbis backend and mapping structures; needed for + static mode headers + last mod: $Id: backends.h,v 1.14 2002/07/11 06:40:48 xiphmont Exp $ + + ********************************************************************/ + +/* this is exposed up here because we need it for static modes. + Lookups for each backend aren't exposed because there's no reason + to do so */ + +#ifndef _vorbis_backend_h_ +#define _vorbis_backend_h_ + +#include "codec_internal.h" + +/* this would all be simpler/shorter with templates, but.... */ +/* Floor backend generic *****************************************/ +typedef struct{ + void (*pack) (vorbis_info_floor *,oggpack_buffer *); + vorbis_info_floor *(*unpack)(vorbis_info *,oggpack_buffer *); + vorbis_look_floor *(*look) (vorbis_dsp_state *,vorbis_info_floor *); + void (*free_info) (vorbis_info_floor *); + void (*free_look) (vorbis_look_floor *); + void *(*inverse1) (struct vorbis_block *,vorbis_look_floor *); + int (*inverse2) (struct vorbis_block *,vorbis_look_floor *, + void *buffer,float *); +} vorbis_func_floor; + +typedef struct{ + int order; + long rate; + long barkmap; + + int ampbits; + int ampdB; + + int numbooks; /* <= 16 */ + int books[16]; + + float lessthan; /* encode-only config setting hacks for libvorbis */ + float greaterthan; /* encode-only config setting hacks for libvorbis */ + +} vorbis_info_floor0; + + +#define VIF_POSIT 63 +#define VIF_CLASS 16 +#define VIF_PARTS 31 +typedef struct{ + int partitions; /* 0 to 31 */ + int partitionclass[VIF_PARTS]; /* 0 to 15 */ + + int class_dim[VIF_CLASS]; /* 1 to 8 */ + int class_subs[VIF_CLASS]; /* 0,1,2,3 (bits: 1<. + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: bitrate tracking and management + last mod: $Id: bitrate.c,v 1.21 2002/10/11 11:14:41 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "os.h" +#include "misc.h" +#include "bitrate.h" + + +static long BINBYTES(bitrate_manager_state *bm,long pos,long bin){ + int bins=bm->queue_bins; + return(bm->queue_binned[pos*bins+bin]); +} + +#define LIMITBYTES(pos,bin) (bm->minmax_binstack[(pos)*bins*2+((bin)+bins)]) + +static long LACING_ADJUST(long bytes){ + int addto=bytes/255+1; + return(bytes+addto); +} + +static int floater_interpolate(bitrate_manager_state *bm,vorbis_info *vi, + double desired_rate){ + int bin=rint(bm->avgfloat); + double lobitrate,hibitrate; + + + lobitrate=(double)(bm->avg_binacc[bin]*8)/bm->avg_sampleacc*vi->rate; + while(lobitrate>desired_rate && bin>0){ + bin--; + lobitrate=(double)(bm->avg_binacc[bin]*8)/bm->avg_sampleacc*vi->rate; + } + + if(bin+1queue_bins){ + hibitrate=(double)(bm->avg_binacc[bin+1]*8)/bm->avg_sampleacc*vi->rate; + if(fabs(hibitrate-desired_rate) < fabs(lobitrate-desired_rate))bin++; + } + return(bin); +} + +/* try out a new limit */ +static long limit_sum(bitrate_manager_state *bm,int limit){ + int i=bm->minmax_stackptr; + long acc=bm->minmax_acctotal; + long bins=bm->queue_bins; + + acc-=LIMITBYTES(i,0); + acc+=LIMITBYTES(i,limit); + + while(i-->0){ + if(bm->minmax_limitstack[i]<=limit)break; + acc-=LIMITBYTES(i,bm->minmax_limitstack[i]); + acc+=LIMITBYTES(i,limit); + } + return(acc); +} + +/* compute bitrate tracking setup, allocate circular packet size queue */ +void vorbis_bitrate_init(vorbis_info *vi,bitrate_manager_state *bm){ + int i; + codec_setup_info *ci=vi->codec_setup; + bitrate_manager_info *bi=&ci->bi; + long maxlatency; + + memset(bm,0,sizeof(*bm)); + + if(bi){ + + bm->avg_sampledesired=bi->queue_avg_time*vi->rate; + bm->avg_centerdesired=bi->queue_avg_time*vi->rate*bi->queue_avg_center; + bm->minmax_sampledesired=bi->queue_minmax_time*vi->rate; + + /* first find the max possible needed queue size */ + maxlatency=max(bm->avg_sampledesired-bm->avg_centerdesired, + bm->minmax_sampledesired)+bm->avg_centerdesired; + + if(maxlatency>0 && + (bi->queue_avgmin>0 || bi->queue_avgmax>0 || bi->queue_hardmax>0 || + bi->queue_hardmin>0)){ + long maxpackets=maxlatency/(ci->blocksizes[0]>>1)+3; + long bins=PACKETBLOBS; + + bm->queue_size=maxpackets; + bm->queue_bins=bins; + bm->queue_binned=_ogg_calloc(maxpackets,bins*sizeof(*bm->queue_binned)); + bm->queue_actual=_ogg_calloc(maxpackets,sizeof(*bm->queue_actual)); + + if((bi->queue_avgmin>0 || bi->queue_avgmax>0) && + bi->queue_avg_time>0){ + + bm->avg_binacc=_ogg_calloc(bins,sizeof(*bm->avg_binacc)); + bm->avgfloat=PACKETBLOBS/2; + + }else{ + bm->avg_tail= -1; + } + + if((bi->queue_hardmin>0 || bi->queue_hardmax>0) && + bi->queue_minmax_time>0){ + + bm->minmax_binstack=_ogg_calloc((bins*2+1)*bins*2, + sizeof(*bm->minmax_binstack)); + bm->minmax_posstack=_ogg_calloc((bins*2+1), + sizeof(*bm->minmax_posstack)); + bm->minmax_limitstack=_ogg_calloc((bins*2+1), + sizeof(*bm->minmax_limitstack)); + }else{ + bm->minmax_tail= -1; + } + + /* space for the packet queueing */ + bm->packetbuffers=_ogg_calloc(maxpackets,sizeof(*bm->packetbuffers)); + bm->packets=_ogg_calloc(maxpackets,sizeof(*bm->packets)); + for(i=0;ipacketbuffers+i); + + }else{ + bm->packetbuffers=_ogg_calloc(1,sizeof(*bm->packetbuffers)); + bm->packets=_ogg_calloc(1,sizeof(*bm->packets)); + oggpack_writeinit(bm->packetbuffers); + + } + } +} + +void vorbis_bitrate_clear(bitrate_manager_state *bm){ + int i; + if(bm){ + if(bm->queue_binned)_ogg_free(bm->queue_binned); + if(bm->queue_actual)_ogg_free(bm->queue_actual); + if(bm->avg_binacc)_ogg_free(bm->avg_binacc); + if(bm->minmax_binstack)_ogg_free(bm->minmax_binstack); + if(bm->minmax_posstack)_ogg_free(bm->minmax_posstack); + if(bm->minmax_limitstack)_ogg_free(bm->minmax_limitstack); + + if(bm->packetbuffers){ + if(bm->queue_size==0){ + oggpack_writeclear(bm->packetbuffers); + }else{ + for(i=0;iqueue_size;i++) + oggpack_writeclear(bm->packetbuffers+i); + } + _ogg_free(bm->packetbuffers); + } + if(bm->packets)_ogg_free(bm->packets); + + memset(bm,0,sizeof(*bm)); + } +} + +int vorbis_bitrate_managed(vorbis_block *vb){ + vorbis_dsp_state *vd=vb->vd; + private_state *b=vd->backend_state; + bitrate_manager_state *bm=&b->bms; + + if(bm->queue_binned)return(1); + return(0); +} + +/* finish taking in the block we just processed */ +int vorbis_bitrate_addblock(vorbis_block *vb){ + int i; + vorbis_block_internal *vbi=vb->internal; + vorbis_dsp_state *vd=vb->vd; + private_state *b=vd->backend_state; + bitrate_manager_state *bm=&b->bms; + vorbis_info *vi=vd->vi; + codec_setup_info *ci=vi->codec_setup; + bitrate_manager_info *bi=&ci->bi; + int eofflag=vb->eofflag; + int head=bm->queue_head; + int next_head=head+1; + int bins=bm->queue_bins; + int minmax_head,new_minmax_head; + + ogg_uint32_t *head_ptr; + oggpack_buffer temp; + + if(!bm->queue_binned){ + oggpack_buffer temp; + /* not a bitrate managed stream, but for API simplicity, we'll + buffer one packet to keep the code path clean */ + + if(bm->queue_head)return(-1); /* one has been submitted without + being claimed */ + bm->queue_head++; + + bm->packets[0].packet=oggpack_get_buffer(&vb->opb); + bm->packets[0].bytes=oggpack_bytes(&vb->opb); + bm->packets[0].b_o_s=0; + bm->packets[0].e_o_s=vb->eofflag; + bm->packets[0].granulepos=vb->granulepos; + bm->packets[0].packetno=vb->sequence; /* for sake of completeness */ + + memcpy(&temp,bm->packetbuffers,sizeof(vb->opb)); + memcpy(bm->packetbuffers,&vb->opb,sizeof(vb->opb)); + memcpy(&vb->opb,&temp,sizeof(vb->opb)); + + return(0); + } + + /* add encoded packet to head */ + if(next_head>=bm->queue_size)next_head=0; + head_ptr=bm->queue_binned+bins*head; + + /* is there room to add a block? In proper use of the API, this will + never come up... but guard it anyway */ + if(next_head==bm->avg_tail || next_head==bm->minmax_tail)return(-1); + + /* add the block to the toplevel queue */ + bm->queue_head=next_head; + bm->queue_actual[head]=(vb->W?0x80000000UL:0); + + /* buffer packet fields */ + bm->packets[head].packet=oggpack_get_buffer(&vb->opb); + bm->packets[head].bytes=oggpack_bytes(&vb->opb); + bm->packets[head].b_o_s=0; + bm->packets[head].e_o_s=vb->eofflag; + bm->packets[head].granulepos=vb->granulepos; + bm->packets[head].packetno=vb->sequence; /* for sake of completeness */ + + /* swap packet buffers */ + memcpy(&temp,bm->packetbuffers+head,sizeof(vb->opb)); + memcpy(bm->packetbuffers+head,&vb->opb,sizeof(vb->opb)); + memcpy(&vb->opb,&temp,sizeof(vb->opb)); + + /* save markers */ + head_ptr[0]=vbi->packetblob_markers[0]; + for(i=1;ipacketblob_markers[i]-vbi->packetblob_markers[i-1]; + } + + if(bm->avg_binacc) + new_minmax_head=minmax_head=bm->avg_center; + else + new_minmax_head=minmax_head=head; + + /* the average tracking queue is updated first; its results (if it's + in use) are taken into account by the min/max limiter (if min/max + is in use) */ + if(bm->avg_binacc){ + unsigned long desired_center=bm->avg_centerdesired; + if(eofflag)desired_center=0; + + /* update the avg head */ + for(i=0;iavg_binacc[i]+=LACING_ADJUST(head_ptr[i]); + bm->avg_sampleacc+=ci->blocksizes[vb->W]>>1; + bm->avg_centeracc+=ci->blocksizes[vb->W]>>1; + + if(bm->avg_sampleacc>bm->avg_sampledesired || eofflag){ + + /* update the avg center */ + if(bm->avg_centeracc>desired_center){ + /* choose the new average floater */ + int samples=ci->blocksizes[vb->W]>>1; + double upper=floater_interpolate(bm,vi,bi->queue_avgmax); + double lower=floater_interpolate(bm,vi,bi->queue_avgmin); + double new=PACKETBLOBS/2.,slew; + int bin; + + if(uppernew)new=lower; + + slew=(new-bm->avgfloat)/samples*vi->rate; + + if(slewavgfloat_downslew_max) + new=bm->avgfloat+bi->avgfloat_downslew_max/vi->rate*samples; + if(slew>bi->avgfloat_upslew_max) + new=bm->avgfloat+bi->avgfloat_upslew_max/vi->rate*samples; + + bm->avgfloat=new; + /* apply the average floater to new blocks */ + bin=rint(bm->avgfloat); + + /*fprintf(stderr,"%d ",bin);*/ + + while(bm->avg_centeracc>desired_center){ + samples=ci->blocksizes[bm->queue_actual[bm->avg_center]& + 0x80000000UL?1:0]>>1; + + bm->queue_actual[bm->avg_center]|=bin; + + bm->avg_centeracc-=samples; + bm->avg_center++; + if(bm->avg_center>=bm->queue_size)bm->avg_center=0; + } + new_minmax_head=bm->avg_center; + + } + + /* update the avg tail if needed */ + while(bm->avg_sampleacc>bm->avg_sampledesired){ + int samples= + ci->blocksizes[bm->queue_actual[bm->avg_tail]&0x80000000UL?1:0]>>1; + for(i=0;iqueue_bins;i++) + bm->avg_binacc[i]-=LACING_ADJUST(bm->queue_binned[bins*bm->avg_tail+i]); + bm->avg_sampleacc-=samples; + bm->avg_tail++; + if(bm->avg_tail>=bm->queue_size)bm->avg_tail=0; + } + + + } + }else{ + /* if we're not using an average tracker, the 'float' is nailed to + the avgfloat_initial value. It needs to be set for the min/max + to deal properly */ + long bin=PACKETBLOBS/2; + bm->queue_actual[head]|=bin; + new_minmax_head=next_head; + } + + /* update the min/max queues and enforce limits */ + if(bm->minmax_binstack){ + unsigned long sampledesired=eofflag?0:bm->minmax_sampledesired; + + /* add to stack recent */ + while(minmax_head!=new_minmax_head){ + unsigned int i; + int samples=ci->blocksizes[bm->queue_actual[minmax_head]& + 0x80000000UL?1:0]>>1; + int actual=bm->queue_actual[minmax_head]&0x7fffffffUL; + + for(i=0;i<(unsigned int)bins;i++){ + bm->minmax_binstack[bm->minmax_stackptr*bins*2+bins+i]+= + LACING_ADJUST(BINBYTES(bm,minmax_head, + actual>i?actual:i)); + + bm->minmax_binstack[bm->minmax_stackptr*bins*2+i]+= + LACING_ADJUST(BINBYTES(bm,minmax_head, + actualminmax_posstack[bm->minmax_stackptr]=minmax_head; /* not one + past + like + typical */ + bm->minmax_limitstack[bm->minmax_stackptr]=0; + bm->minmax_sampleacc+=samples; + bm->minmax_acctotal+= + LACING_ADJUST(BINBYTES(bm,minmax_head,actual)); + + minmax_head++; + if(minmax_head>=bm->queue_size)minmax_head=0; + + + } + + /* check limits, enforce changes */ + if(bm->minmax_sampleacc>sampledesired){ + double bitrate=(double)(bm->minmax_acctotal*8)/ + bm->minmax_sampleacc*vi->rate; + int limit=0; + + if((bi->queue_hardmax>0 && bitrate>bi->queue_hardmax) || + (bi->queue_hardmin>0 && bitratequeue_hardmin)){ + int newstack; + int stackctr; + long bitsum=bm->minmax_acctotal*8; + + bitrate=(double)bitsum/bm->minmax_sampleacc*vi->rate; + + /* we're off rate. Iteratively try out new hard floater + limits until we find one that brings us inside. Here's + where we see the whole point of the limit stacks. */ + if(bi->queue_hardmax>0 && bitrate>bi->queue_hardmax){ + for(limit=-1;limit>-bins+1;limit--){ + long bitsum=limit_sum(bm,limit)*8; + bitrate=(double)bitsum/bm->minmax_sampleacc*vi->rate; + if(bitrate<=bi->queue_hardmax)break; + } + }else if(bitratequeue_hardmin){ + for(limit=1;limitminmax_sampleacc*vi->rate; + if(bitrate>=bi->queue_hardmin)break; + } + if(bitrate>bi->queue_hardmax)limit--; + } + + /* trace the limit backward, stop when we see a lower limit */ + newstack=bm->minmax_stackptr-1; + while(newstack>=0){ + if(bm->minmax_limitstack[newstack]minmax_stackptr; + while(stackctr>newstack){ + bm->minmax_acctotal-= + LIMITBYTES(stackctr,bm->minmax_limitstack[stackctr]); + bm->minmax_acctotal+=LIMITBYTES(stackctr,limit); + + if(stackctrminmax_stackptr) + for(i=0;iminmax_binstack[stackctr*bins*2+i]+= + bm->minmax_binstack[(stackctr+1)*bins*2+i]; + + stackctr--; + } + stackctr++; + bm->minmax_posstack[stackctr]=bm->minmax_posstack[bm->minmax_stackptr]; + bm->minmax_limitstack[stackctr]=limit; + + /* set up new blank stack entry */ + stackctr++; + bm->minmax_stackptr=stackctr; + memset(&bm->minmax_binstack[stackctr*bins*2], + 0, + sizeof(*bm->minmax_binstack)*bins*2); + bm->minmax_limitstack[stackctr]=0; + bm->minmax_posstack[stackctr]=-1; + + } + } + + /* remove from tail */ + while(bm->minmax_sampleacc>sampledesired){ + int samples= + ci->blocksizes[bm->queue_actual[bm->minmax_tail]&0x80000000UL?1:0]>>1; + int actual=bm->queue_actual[bm->minmax_tail]&0x7fffffffUL; + + for(i=0;iminmax_binstack[bins+i]-= /* always comes off the stack bottom */ + LACING_ADJUST(BINBYTES(bm,bm->minmax_tail, + actual>i? + actual:i)); + bm->minmax_binstack[i]-= + LACING_ADJUST(BINBYTES(bm,bm->minmax_tail, + actualminmax_limitstack[0]>actual) + actual=bm->minmax_limitstack[0]; + if(bins+bm->minmax_limitstack[0]minmax_limitstack[0]; + + bm->minmax_acctotal-=LACING_ADJUST(BINBYTES(bm,bm->minmax_tail,actual)); + bm->minmax_sampleacc-=samples; + + /* revise queue_actual to reflect the limit */ + bm->queue_actual[bm->minmax_tail]&=0x80000000UL; + bm->queue_actual[bm->minmax_tail]|=actual; + + if(bm->minmax_tail==bm->minmax_posstack[0]){ + /* the stack becomes a FIFO; the first data has fallen off */ + memmove(bm->minmax_binstack,bm->minmax_binstack+bins*2, + sizeof(*bm->minmax_binstack)*bins*2*bm->minmax_stackptr); + memmove(bm->minmax_posstack,bm->minmax_posstack+1, + sizeof(*bm->minmax_posstack)*bm->minmax_stackptr); + memmove(bm->minmax_limitstack,bm->minmax_limitstack+1, + sizeof(*bm->minmax_limitstack)*bm->minmax_stackptr); + bm->minmax_stackptr--; + } + + bm->minmax_tail++; + if(bm->minmax_tail>=bm->queue_size)bm->minmax_tail=0; + + } + + + bm->last_to_flush=bm->minmax_tail; + }else{ + bm->last_to_flush=bm->avg_center; + } + if(eofflag) + bm->last_to_flush=bm->queue_head; + return(0); +} + +int vorbis_bitrate_flushpacket(vorbis_dsp_state *vd,ogg_packet *op){ + private_state *b=vd->backend_state; + bitrate_manager_state *bm=&b->bms; + + if(bm->queue_size==0){ + if(bm->queue_head==0)return(0); + + memcpy(op,bm->packets,sizeof(*op)); + bm->queue_head=0; + + }else{ + + if(bm->next_to_flush==bm->last_to_flush)return(0); + + { + long bin=bm->queue_actual[bm->next_to_flush]&0x7fffffff,i; + long bins=bm->queue_bins; + ogg_uint32_t *markers=bm->queue_binned+bins*bm->next_to_flush; + long bytes=markers[bin]; + + memcpy(op,bm->packets+bm->next_to_flush,sizeof(*op)); + + /* we have [PACKETBLOBS] possible packets all squished together in + the buffer, in sequence. count in to number [bin] */ + for(i=0;ipacket+=markers[i]; + op->bytes=bytes; + + } + + bm->next_to_flush++; + if(bm->next_to_flush>=bm->queue_size)bm->next_to_flush=0; + + } + + return(1); +} diff --git a/src/sound/oggvorbis/vorbissrc/bitrate.h b/src/sound/oggvorbis/vorbissrc/bitrate.h new file mode 100644 index 0000000..8ca3ae0 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/bitrate.h @@ -0,0 +1,108 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: bitrate tracking and management + last mod: $Id: bitrate.h,v 1.7 2002/07/11 06:40:48 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_BITRATE_H_ +#define _V_BITRATE_H_ + +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "os.h" + +/* encode side bitrate tracking */ +typedef struct bitrate_manager_state { + ogg_uint32_t *queue_binned; + ogg_uint32_t *queue_actual; + int queue_size; + + int queue_head; + int queue_bins; + + long *avg_binacc; + int avg_center; + int avg_tail; + ogg_uint32_t avg_centeracc; + ogg_uint32_t avg_sampleacc; + ogg_uint32_t avg_sampledesired; + ogg_uint32_t avg_centerdesired; + + long *minmax_binstack; + long *minmax_posstack; + long *minmax_limitstack; + long minmax_stackptr; + + long minmax_acctotal; + int minmax_tail; + ogg_uint32_t minmax_sampleacc; + ogg_uint32_t minmax_sampledesired; + + int next_to_flush; + int last_to_flush; + + double avgfloat; + + /* unfortunately, we need to hold queued packet data somewhere */ + oggpack_buffer *packetbuffers; + ogg_packet *packets; + +} bitrate_manager_state; + +typedef struct bitrate_manager_info{ + /* detailed bitrate management setup */ + double queue_avg_time; + double queue_avg_center; + double queue_minmax_time; + double queue_hardmin; + double queue_hardmax; + double queue_avgmin; + double queue_avgmax; + + double avgfloat_downslew_max; + double avgfloat_upslew_max; + +} bitrate_manager_info; + +extern void vorbis_bitrate_init(vorbis_info *vi,bitrate_manager_state *bs); +extern void vorbis_bitrate_clear(bitrate_manager_state *bs); +extern int vorbis_bitrate_managed(vorbis_block *vb); +extern int vorbis_bitrate_addblock(vorbis_block *vb); +extern int vorbis_bitrate_flushpacket(vorbis_dsp_state *vd, ogg_packet *op); + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/block.c b/src/sound/oggvorbis/vorbissrc/block.c new file mode 100644 index 0000000..513c76e --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/block.c @@ -0,0 +1,995 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2003 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: PCM data vector blocking, windowing and dis/reassembly + last mod: $Id: block.c,v 1.75 2003/09/02 04:39:26 xiphmont Exp $ + + Handle windowing, overlap-add, etc of the PCM vectors. This is made + more amusing by Vorbis' current two allowed block sizes. + + ********************************************************************/ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" + +#include "window.h" +#include "mdct.h" +#include "lpc.h" +#include "registry.h" +#include "misc.h" + +static int ilog2(unsigned int v){ + int ret=0; + if(v)--v; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +/* pcm accumulator examples (not exhaustive): + + <-------------- lW ----------------> + <--------------- W ----------------> +: .....|..... _______________ | +: .''' | '''_--- | |\ | +:.....''' |_____--- '''......| | \_______| +:.................|__________________|_______|__|______| + |<------ Sl ------>| > Sr < |endW + |beginSl |endSl | |endSr + |beginW |endlW |beginSr + + + |< lW >| + <--------------- W ----------------> + | | .. ______________ | + | | ' `/ | ---_ | + |___.'___/`. | ---_____| + |_______|__|_______|_________________| + | >|Sl|< |<------ Sr ----->|endW + | | |endSl |beginSr |endSr + |beginW | |endlW + mult[0] |beginSl mult[n] + + <-------------- lW -----------------> + |<--W-->| +: .............. ___ | | +: .''' |`/ \ | | +:.....''' |/`....\|...| +:.........................|___|___|___| + |Sl |Sr |endW + | | |endSr + | |beginSr + | |endSl + |beginSl + |beginW +*/ + +/* block abstraction setup *********************************************/ + +#ifndef WORD_ALIGN +#define WORD_ALIGN 8 +#endif + +int vorbis_block_init(vorbis_dsp_state *v, vorbis_block *vb){ + memset(vb,0,sizeof(*vb)); + vb->vd=v; + vb->localalloc=0; + vb->localstore=NULL; + if(v->analysisp){ + vorbis_block_internal *vbi= + vb->internal=_ogg_calloc(1,sizeof(vorbis_block_internal)); + oggpack_writeinit(&vb->opb); + vbi->ampmax=-9999; + } + + return(0); +} + +void *_vorbis_block_alloc(vorbis_block *vb,long bytes){ + bytes=(bytes+(WORD_ALIGN-1)) & ~(WORD_ALIGN-1); + if(bytes+vb->localtop>vb->localalloc){ + /* can't just _ogg_realloc... there are outstanding pointers */ + if(vb->localstore){ + struct alloc_chain *link=_ogg_malloc(sizeof(*link)); + vb->totaluse+=vb->localtop; + link->next=vb->reap; + link->ptr=vb->localstore; + vb->reap=link; + } + /* highly conservative */ + vb->localalloc=bytes; + vb->localstore=_ogg_malloc(vb->localalloc); + vb->localtop=0; + } + { + void *ret=(void *)(((char *)vb->localstore)+vb->localtop); + vb->localtop+=bytes; + return ret; + } +} + +/* reap the chain, pull the ripcord */ +void _vorbis_block_ripcord(vorbis_block *vb){ + /* reap the chain */ + struct alloc_chain *reap=vb->reap; + while(reap){ + struct alloc_chain *next=reap->next; + _ogg_free(reap->ptr); + memset(reap,0,sizeof(*reap)); + _ogg_free(reap); + reap=next; + } + /* consolidate storage */ + if(vb->totaluse){ + vb->localstore=_ogg_realloc(vb->localstore,vb->totaluse+vb->localalloc); + vb->localalloc+=vb->totaluse; + vb->totaluse=0; + } + + /* pull the ripcord */ + vb->localtop=0; + vb->reap=NULL; +} + +int vorbis_block_clear(vorbis_block *vb){ + if(vb->vd) + if(vb->vd->analysisp) + oggpack_writeclear(&vb->opb); + _vorbis_block_ripcord(vb); + if(vb->localstore)_ogg_free(vb->localstore); + + if(vb->internal) + _ogg_free(vb->internal); + + memset(vb,0,sizeof(*vb)); + return(0); +} + +/* Analysis side code, but directly related to blocking. Thus it's + here and not in analysis.c (which is for analysis transforms only). + The init is here because some of it is shared */ + +static int _vds_shared_init(vorbis_dsp_state *v,vorbis_info *vi,int encp){ + int i; + codec_setup_info *ci=vi->codec_setup; + private_state *b=NULL; + int hs; + + if(ci==NULL) return 1; + hs=ci->halfrate_flag; + + memset(v,0,sizeof(*v)); + b=v->backend_state=_ogg_calloc(1,sizeof(*b)); + + v->vi=vi; + b->modebits=ilog2(ci->modes); + + b->transform[0]=_ogg_calloc(VI_TRANSFORMB,sizeof(*b->transform[0])); + b->transform[1]=_ogg_calloc(VI_TRANSFORMB,sizeof(*b->transform[1])); + + /* MDCT is tranform 0 */ + + b->transform[0][0]=_ogg_calloc(1,sizeof(mdct_lookup)); + b->transform[1][0]=_ogg_calloc(1,sizeof(mdct_lookup)); + mdct_init(b->transform[0][0],ci->blocksizes[0]>>hs); + mdct_init(b->transform[1][0],ci->blocksizes[1]>>hs); + + /* Vorbis I uses only window type 0 */ + b->window[0]=ilog2(ci->blocksizes[0])-6; + b->window[1]=ilog2(ci->blocksizes[1])-6; + + if(encp){ /* encode/decode differ here */ + + /* analysis always needs an fft */ + drft_init(&b->fft_look[0],ci->blocksizes[0]); + drft_init(&b->fft_look[1],ci->blocksizes[1]); + + /* finish the codebooks */ + if(!ci->fullbooks){ + ci->fullbooks=_ogg_calloc(ci->books,sizeof(*ci->fullbooks)); + for(i=0;ibooks;i++) + vorbis_book_init_encode(ci->fullbooks+i,ci->book_param[i]); + } + + b->psy=_ogg_calloc(ci->psys,sizeof(*b->psy)); + for(i=0;ipsys;i++){ + _vp_psy_init(b->psy+i, + ci->psy_param[i], + &ci->psy_g_param, + ci->blocksizes[ci->psy_param[i]->blockflag]/2, + vi->rate); + } + + v->analysisp=1; + }else{ + /* finish the codebooks */ + if(!ci->fullbooks){ + ci->fullbooks=_ogg_calloc(ci->books,sizeof(*ci->fullbooks)); + for(i=0;ibooks;i++){ + vorbis_book_init_decode(ci->fullbooks+i,ci->book_param[i]); + /* decode codebooks are now standalone after init */ + vorbis_staticbook_destroy(ci->book_param[i]); + ci->book_param[i]=NULL; + } + } + } + + /* initialize the storage vectors. blocksize[1] is small for encode, + but the correct size for decode */ + v->pcm_storage=ci->blocksizes[1]; + v->pcm=_ogg_malloc(vi->channels*sizeof(*v->pcm)); + v->pcmret=_ogg_malloc(vi->channels*sizeof(*v->pcmret)); + { + int i; + for(i=0;ichannels;i++) + v->pcm[i]=_ogg_calloc(v->pcm_storage,sizeof(*v->pcm[i])); + } + + /* all 1 (large block) or 0 (small block) */ + /* explicitly set for the sake of clarity */ + v->lW=0; /* previous window size */ + v->W=0; /* current window size */ + + /* all vector indexes */ + v->centerW=ci->blocksizes[1]/2; + + v->pcm_current=v->centerW; + + /* initialize all the backend lookups */ + b->flr=_ogg_calloc(ci->floors,sizeof(*b->flr)); + b->residue=_ogg_calloc(ci->residues,sizeof(*b->residue)); + + for(i=0;ifloors;i++) + b->flr[i]=_floor_P[ci->floor_type[i]]-> + look(v,ci->floor_param[i]); + + for(i=0;iresidues;i++) + b->residue[i]=_residue_P[ci->residue_type[i]]-> + look(v,ci->residue_param[i]); + + return 0; +} + +/* arbitrary settings and spec-mandated numbers get filled in here */ +int vorbis_analysis_init(vorbis_dsp_state *v,vorbis_info *vi){ + private_state *b=NULL; + + if(_vds_shared_init(v,vi,1))return 1; + b=v->backend_state; + b->psy_g_look=_vp_global_look(vi); + + /* Initialize the envelope state storage */ + b->ve=_ogg_calloc(1,sizeof(*b->ve)); + _ve_envelope_init(b->ve,vi); + + vorbis_bitrate_init(vi,&b->bms); + + return(0); +} + +void vorbis_dsp_clear(vorbis_dsp_state *v){ + int i; + if(v){ + vorbis_info *vi=v->vi; + codec_setup_info *ci=(vi?vi->codec_setup:NULL); + private_state *b=v->backend_state; + + if(b){ + + if(b->ve){ + _ve_envelope_clear(b->ve); + _ogg_free(b->ve); + } + + if(b->transform[0]){ + mdct_clear(b->transform[0][0]); + _ogg_free(b->transform[0][0]); + _ogg_free(b->transform[0]); + } + if(b->transform[1]){ + mdct_clear(b->transform[1][0]); + _ogg_free(b->transform[1][0]); + _ogg_free(b->transform[1]); + } + + if(b->flr){ + for(i=0;ifloors;i++) + _floor_P[ci->floor_type[i]]-> + free_look(b->flr[i]); + _ogg_free(b->flr); + } + if(b->residue){ + for(i=0;iresidues;i++) + _residue_P[ci->residue_type[i]]-> + free_look(b->residue[i]); + _ogg_free(b->residue); + } + if(b->psy){ + for(i=0;ipsys;i++) + _vp_psy_clear(b->psy+i); + _ogg_free(b->psy); + } + + if(b->psy_g_look)_vp_global_free(b->psy_g_look); + vorbis_bitrate_clear(&b->bms); + + drft_clear(&b->fft_look[0]); + drft_clear(&b->fft_look[1]); + + } + + if(v->pcm){ + for(i=0;ichannels;i++) + if(v->pcm[i])_ogg_free(v->pcm[i]); + _ogg_free(v->pcm); + if(v->pcmret)_ogg_free(v->pcmret); + } + + if(b){ + /* free header, header1, header2 */ + if(b->header)_ogg_free(b->header); + if(b->header1)_ogg_free(b->header1); + if(b->header2)_ogg_free(b->header2); + _ogg_free(b); + } + + memset(v,0,sizeof(*v)); + } +} + +float **vorbis_analysis_buffer(vorbis_dsp_state *v, int vals){ + int i; + vorbis_info *vi=v->vi; + private_state *b=v->backend_state; + + /* free header, header1, header2 */ + if(b->header)_ogg_free(b->header);b->header=NULL; + if(b->header1)_ogg_free(b->header1);b->header1=NULL; + if(b->header2)_ogg_free(b->header2);b->header2=NULL; + + /* Do we have enough storage space for the requested buffer? If not, + expand the PCM (and envelope) storage */ + + if(v->pcm_current+vals>=v->pcm_storage){ + v->pcm_storage=v->pcm_current+vals*2; + + for(i=0;ichannels;i++){ + v->pcm[i]=_ogg_realloc(v->pcm[i],v->pcm_storage*sizeof(*v->pcm[i])); + } + } + + for(i=0;ichannels;i++) + v->pcmret[i]=v->pcm[i]+v->pcm_current; + + return(v->pcmret); +} + +static void _preextrapolate_helper(vorbis_dsp_state *v){ + int i; + int order=32; + float *lpc=alloca(order*sizeof(*lpc)); + float *work=alloca(v->pcm_current*sizeof(*work)); + long j; + v->preextrapolate=1; + + if(v->pcm_current-v->centerW>order*2){ /* safety */ + for(i=0;ivi->channels;i++){ + /* need to run the extrapolation in reverse! */ + for(j=0;jpcm_current;j++) + work[j]=v->pcm[i][v->pcm_current-j-1]; + + /* prime as above */ + vorbis_lpc_from_data(work,lpc,v->pcm_current-v->centerW,order); + + /* run the predictor filter */ + vorbis_lpc_predict(lpc,work+v->pcm_current-v->centerW-order, + order, + work+v->pcm_current-v->centerW, + v->centerW); + + for(j=0;jpcm_current;j++) + v->pcm[i][v->pcm_current-j-1]=work[j]; + + } + } +} + + +/* call with val<=0 to set eof */ + +int vorbis_analysis_wrote(vorbis_dsp_state *v, int vals){ + vorbis_info *vi=v->vi; + codec_setup_info *ci=vi->codec_setup; + + if(vals<=0){ + int order=32; + int i; + float *lpc=alloca(order*sizeof(*lpc)); + + /* if it wasn't done earlier (very short sample) */ + if(!v->preextrapolate) + _preextrapolate_helper(v); + + /* We're encoding the end of the stream. Just make sure we have + [at least] a few full blocks of zeroes at the end. */ + /* actually, we don't want zeroes; that could drop a large + amplitude off a cliff, creating spread spectrum noise that will + suck to encode. Extrapolate for the sake of cleanliness. */ + + vorbis_analysis_buffer(v,ci->blocksizes[1]*3); + v->eofflag=v->pcm_current; + v->pcm_current+=ci->blocksizes[1]*3; + + for(i=0;ichannels;i++){ + if(v->eofflag>order*2){ + /* extrapolate with LPC to fill in */ + long n; + + /* make a predictor filter */ + n=v->eofflag; + if(n>ci->blocksizes[1])n=ci->blocksizes[1]; + vorbis_lpc_from_data(v->pcm[i]+v->eofflag-n,lpc,n,order); + + /* run the predictor filter */ + vorbis_lpc_predict(lpc,v->pcm[i]+v->eofflag-order,order, + v->pcm[i]+v->eofflag,v->pcm_current-v->eofflag); + }else{ + /* not enough data to extrapolate (unlikely to happen due to + guarding the overlap, but bulletproof in case that + assumtion goes away). zeroes will do. */ + memset(v->pcm[i]+v->eofflag,0, + (v->pcm_current-v->eofflag)*sizeof(*v->pcm[i])); + + } + } + }else{ + + if(v->pcm_current+vals>v->pcm_storage) + return(OV_EINVAL); + + v->pcm_current+=vals; + + /* we may want to reverse extrapolate the beginning of a stream + too... in case we're beginning on a cliff! */ + /* clumsy, but simple. It only runs once, so simple is good. */ + if(!v->preextrapolate && v->pcm_current-v->centerW>ci->blocksizes[1]) + _preextrapolate_helper(v); + + } + return(0); +} + +/* do the deltas, envelope shaping, pre-echo and determine the size of + the next block on which to continue analysis */ +int vorbis_analysis_blockout(vorbis_dsp_state *v,vorbis_block *vb){ + int i; + vorbis_info *vi=v->vi; + codec_setup_info *ci=vi->codec_setup; + private_state *b=v->backend_state; + vorbis_look_psy_global *g=b->psy_g_look; + long beginW=v->centerW-ci->blocksizes[v->W]/2,centerNext; + vorbis_block_internal *vbi=(vorbis_block_internal *)vb->internal; + + /* check to see if we're started... */ + if(!v->preextrapolate)return(0); + + /* check to see if we're done... */ + if(v->eofflag==-1)return(0); + + /* By our invariant, we have lW, W and centerW set. Search for + the next boundary so we can determine nW (the next window size) + which lets us compute the shape of the current block's window */ + + /* we do an envelope search even on a single blocksize; we may still + be throwing more bits at impulses, and envelope search handles + marking impulses too. */ + { + long bp=_ve_envelope_search(v); + if(bp==-1){ + + if(v->eofflag==0)return(0); /* not enough data currently to search for a + full long block */ + v->nW=0; + }else{ + + if(ci->blocksizes[0]==ci->blocksizes[1]) + v->nW=0; + else + v->nW=bp; + } + } + + centerNext=v->centerW+ci->blocksizes[v->W]/4+ci->blocksizes[v->nW]/4; + + { + /* center of next block + next block maximum right side. */ + + long blockbound=centerNext+ci->blocksizes[v->nW]/2; + if(v->pcm_currentlW=v->lW; + vb->W=v->W; + vb->nW=v->nW; + + if(v->W){ + if(!v->lW || !v->nW){ + vbi->blocktype=BLOCKTYPE_TRANSITION; + /*fprintf(stderr,"-");*/ + }else{ + vbi->blocktype=BLOCKTYPE_LONG; + /*fprintf(stderr,"_");*/ + } + }else{ + if(_ve_envelope_mark(v)){ + vbi->blocktype=BLOCKTYPE_IMPULSE; + /*fprintf(stderr,"|");*/ + + }else{ + vbi->blocktype=BLOCKTYPE_PADDING; + /*fprintf(stderr,".");*/ + + } + } + + vb->vd=v; + vb->sequence=v->sequence++; + vb->granulepos=v->granulepos; + vb->pcmend=ci->blocksizes[v->W]; + + /* copy the vectors; this uses the local storage in vb */ + + /* this tracks 'strongest peak' for later psychoacoustics */ + /* moved to the global psy state; clean this mess up */ + if(vbi->ampmax>g->ampmax)g->ampmax=vbi->ampmax; + g->ampmax=_vp_ampmax_decay(g->ampmax,v); + vbi->ampmax=g->ampmax; + + vb->pcm=_vorbis_block_alloc(vb,sizeof(*vb->pcm)*vi->channels); + vbi->pcmdelay=_vorbis_block_alloc(vb,sizeof(*vbi->pcmdelay)*vi->channels); + for(i=0;ichannels;i++){ + vbi->pcmdelay[i]= + _vorbis_block_alloc(vb,(vb->pcmend+beginW)*sizeof(*vbi->pcmdelay[i])); + memcpy(vbi->pcmdelay[i],v->pcm[i],(vb->pcmend+beginW)*sizeof(*vbi->pcmdelay[i])); + vb->pcm[i]=vbi->pcmdelay[i]+beginW; + + /* before we added the delay + vb->pcm[i]=_vorbis_block_alloc(vb,vb->pcmend*sizeof(*vb->pcm[i])); + memcpy(vb->pcm[i],v->pcm[i]+beginW,ci->blocksizes[v->W]*sizeof(*vb->pcm[i])); + */ + + } + + /* handle eof detection: eof==0 means that we've not yet received EOF + eof>0 marks the last 'real' sample in pcm[] + eof<0 'no more to do'; doesn't get here */ + + if(v->eofflag){ + if(v->centerW>=v->eofflag){ + v->eofflag=-1; + vb->eofflag=1; + return(1); + } + } + + /* advance storage vectors and clean up */ + { + int new_centerNext=ci->blocksizes[1]/2; + int movementW=centerNext-new_centerNext; + + if(movementW>0){ + + _ve_envelope_shift(b->ve,movementW); + v->pcm_current-=movementW; + + for(i=0;ichannels;i++) + memmove(v->pcm[i],v->pcm[i]+movementW, + v->pcm_current*sizeof(*v->pcm[i])); + + + v->lW=v->W; + v->W=v->nW; + v->centerW=new_centerNext; + + if(v->eofflag){ + v->eofflag-=movementW; + if(v->eofflag<=0)v->eofflag=-1; + /* do not add padding to end of stream! */ + if(v->centerW>=v->eofflag){ + v->granulepos+=movementW-(v->centerW-v->eofflag); + }else{ + v->granulepos+=movementW; + } + }else{ + v->granulepos+=movementW; + } + } + } + + /* done */ + return(1); +} + +int vorbis_synthesis_restart(vorbis_dsp_state *v){ + vorbis_info *vi=v->vi; + codec_setup_info *ci; + int hs; + + if(!v->backend_state)return -1; + if(!vi)return -1; + ci=vi->codec_setup; + if(!ci)return -1; + hs=ci->halfrate_flag; + + v->centerW=ci->blocksizes[1]>>(hs+1); + v->pcm_current=v->centerW>>hs; + + v->pcm_returned=-1; + v->granulepos=-1; + v->sequence=-1; + v->eofflag=0; + ((private_state *)(v->backend_state))->sample_count=-1; + + return(0); +} + +int vorbis_synthesis_init(vorbis_dsp_state *v,vorbis_info *vi){ + if(_vds_shared_init(v,vi,0)) return 1; + vorbis_synthesis_restart(v); + + return 0; +} + +/* Unlike in analysis, the window is only partially applied for each + block. The time domain envelope is not yet handled at the point of + calling (as it relies on the previous block). */ + +int vorbis_synthesis_blockin(vorbis_dsp_state *v,vorbis_block *vb){ + vorbis_info *vi=v->vi; + codec_setup_info *ci=vi->codec_setup; + private_state *b=v->backend_state; + int hs=ci->halfrate_flag; + int i,j; + + if(!vb)return(OV_EINVAL); + if(v->pcm_current>v->pcm_returned && v->pcm_returned!=-1)return(OV_EINVAL); + + v->lW=v->W; + v->W=vb->W; + v->nW=-1; + + if((v->sequence==-1)|| + (v->sequence+1 != vb->sequence)){ + v->granulepos=-1; /* out of sequence; lose count */ + b->sample_count=-1; + } + + v->sequence=vb->sequence; + + if(vb->pcm){ /* no pcm to process if vorbis_synthesis_trackonly + was called on block */ + int n=ci->blocksizes[v->W]>>(hs+1); + int n0=ci->blocksizes[0]>>(hs+1); + int n1=ci->blocksizes[1]>>(hs+1); + + int thisCenter; + int prevCenter; + + v->glue_bits+=vb->glue_bits; + v->time_bits+=vb->time_bits; + v->floor_bits+=vb->floor_bits; + v->res_bits+=vb->res_bits; + + if(v->centerW){ + thisCenter=n1; + prevCenter=0; + }else{ + thisCenter=0; + prevCenter=n1; + } + + /* v->pcm is now used like a two-stage double buffer. We don't want + to have to constantly shift *or* adjust memory usage. Don't + accept a new block until the old is shifted out */ + + for(j=0;jchannels;j++){ + /* the overlap/add section */ + if(v->lW){ + if(v->W){ + /* large/large */ + float *w=_vorbis_window_get(b->window[1]-hs); + float *pcm=v->pcm[j]+prevCenter; + float *p=vb->pcm[j]; + for(i=0;iwindow[0]-hs); + float *pcm=v->pcm[j]+prevCenter+n1/2-n0/2; + float *p=vb->pcm[j]; + for(i=0;iW){ + /* small/large */ + float *w=_vorbis_window_get(b->window[0]-hs); + float *pcm=v->pcm[j]+prevCenter; + float *p=vb->pcm[j]+n1/2-n0/2; + for(i=0;iwindow[0]-hs); + float *pcm=v->pcm[j]+prevCenter; + float *p=vb->pcm[j]; + for(i=0;ipcm[j]+thisCenter; + float *p=vb->pcm[j]+n; + for(i=0;icenterW) + v->centerW=0; + else + v->centerW=n1; + + /* deal with initial packet state; we do this using the explicit + pcm_returned==-1 flag otherwise we're sensitive to first block + being short or long */ + + if(v->pcm_returned==-1){ + v->pcm_returned=thisCenter; + v->pcm_current=thisCenter; + }else{ + v->pcm_returned=prevCenter; + v->pcm_current=prevCenter+ + ((ci->blocksizes[v->lW]/4+ + ci->blocksizes[v->W]/4)>>hs); + } + + } + + /* track the frame number... This is for convenience, but also + making sure our last packet doesn't end with added padding. If + the last packet is partial, the number of samples we'll have to + return will be past the vb->granulepos. + + This is not foolproof! It will be confused if we begin + decoding at the last page after a seek or hole. In that case, + we don't have a starting point to judge where the last frame + is. For this reason, vorbisfile will always try to make sure + it reads the last two marked pages in proper sequence */ + + if(b->sample_count==-1){ + b->sample_count=0; + }else{ + b->sample_count+=ci->blocksizes[v->lW]/4+ci->blocksizes[v->W]/4; + } + + if(v->granulepos==-1){ + if(vb->granulepos!=-1){ /* only set if we have a position to set to */ + + v->granulepos=vb->granulepos; + + /* is this a short page? */ + if(b->sample_count>v->granulepos){ + /* corner case; if this is both the first and last audio page, + then spec says the end is cut, not beginning */ + if(vb->eofflag){ + /* trim the end */ + /* no preceeding granulepos; assume we started at zero (we'd + have to in a short single-page stream) */ + /* granulepos could be -1 due to a seek, but that would result + in a long count, not short count */ + + v->pcm_current-=(b->sample_count-v->granulepos)>>hs; + }else{ + /* trim the beginning */ + v->pcm_returned+=(b->sample_count-v->granulepos)>>hs; + if(v->pcm_returned>v->pcm_current) + v->pcm_returned=v->pcm_current; + } + + } + + } + }else{ + v->granulepos+=ci->blocksizes[v->lW]/4+ci->blocksizes[v->W]/4; + if(vb->granulepos!=-1 && v->granulepos!=vb->granulepos){ + + if(v->granulepos>vb->granulepos){ + long extra=v->granulepos-vb->granulepos; + + if(extra) + if(vb->eofflag){ + /* partial last frame. Strip the extra samples off */ + v->pcm_current-=extra>>hs; + } /* else {Shouldn't happen *unless* the bitstream is out of + spec. Either way, believe the bitstream } */ + } /* else {Shouldn't happen *unless* the bitstream is out of + spec. Either way, believe the bitstream } */ + v->granulepos=vb->granulepos; + } + } + + /* Update, cleanup */ + + if(vb->eofflag)v->eofflag=1; + return(0); + +} + +/* pcm==NULL indicates we just want the pending samples, no more */ +int vorbis_synthesis_pcmout(vorbis_dsp_state *v,float ***pcm){ + vorbis_info *vi=v->vi; + + if(v->pcm_returned>-1 && v->pcm_returnedpcm_current){ + if(pcm){ + int i; + for(i=0;ichannels;i++) + v->pcmret[i]=v->pcm[i]+v->pcm_returned; + *pcm=v->pcmret; + } + return(v->pcm_current-v->pcm_returned); + } + return(0); +} + +int vorbis_synthesis_read(vorbis_dsp_state *v,int n){ + if(n && v->pcm_returned+n>v->pcm_current)return(OV_EINVAL); + v->pcm_returned+=n; + return(0); +} + +/* intended for use with a specific vorbisfile feature; we want access + to the [usually synthetic/postextrapolated] buffer and lapping at + the end of a decode cycle, specifically, a half-short-block worth. + This funtion works like pcmout above, except it will also expose + this implicit buffer data not normally decoded. */ +int vorbis_synthesis_lapout(vorbis_dsp_state *v,float ***pcm){ + vorbis_info *vi=v->vi; + codec_setup_info *ci=vi->codec_setup; + int hs=ci->halfrate_flag; + + int n=ci->blocksizes[v->W]>>(hs+1); + int n0=ci->blocksizes[0]>>(hs+1); + int n1=ci->blocksizes[1]>>(hs+1); + int i,j; + + if(v->pcm_returned<0)return 0; + + /* our returned data ends at pcm_returned; because the synthesis pcm + buffer is a two-fragment ring, that means our data block may be + fragmented by buffering, wrapping or a short block not filling + out a buffer. To simplify things, we unfragment if it's at all + possibly needed. Otherwise, we'd need to call lapout more than + once as well as hold additional dsp state. Opt for + simplicity. */ + + /* centerW was advanced by blockin; it would be the center of the + *next* block */ + if(v->centerW==n1){ + /* the data buffer wraps; swap the halves */ + /* slow, sure, small */ + for(j=0;jchannels;j++){ + float *p=v->pcm[j]; + for(i=0;ipcm_current-=n1; + v->pcm_returned-=n1; + v->centerW=0; + } + + /* solidify buffer into contiguous space */ + if((v->lW^v->W)==1){ + /* long/short or short/long */ + for(j=0;jchannels;j++){ + float *s=v->pcm[j]; + float *d=v->pcm[j]+(n1-n0)/2; + for(i=(n1+n0)/2-1;i>=0;--i) + d[i]=s[i]; + } + v->pcm_returned+=(n1-n0)/2; + v->pcm_current+=(n1-n0)/2; + }else{ + if(v->lW==0){ + /* short/short */ + for(j=0;jchannels;j++){ + float *s=v->pcm[j]; + float *d=v->pcm[j]+n1-n0; + for(i=n0-1;i>=0;--i) + d[i]=s[i]; + } + v->pcm_returned+=n1-n0; + v->pcm_current+=n1-n0; + } + } + + if(pcm){ + int i; + for(i=0;ichannels;i++) + v->pcmret[i]=v->pcm[i]+v->pcm_returned; + *pcm=v->pcmret; + } + + return(n1+n-v->pcm_returned); + +} + +float *vorbis_window(vorbis_dsp_state *v,int W){ + vorbis_info *vi=v->vi; + codec_setup_info *ci=vi->codec_setup; + int hs=ci->halfrate_flag; + private_state *b=v->backend_state; + + if(b->window[W]-1<0)return NULL; + return _vorbis_window_get(b->window[W]-hs); +} + diff --git a/src/sound/oggvorbis/vorbissrc/books/coupled/res_books_stereo.h b/src/sound/oggvorbis/vorbissrc/books/coupled/res_books_stereo.h new file mode 100644 index 0000000..d6aa6c7 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/books/coupled/res_books_stereo.h @@ -0,0 +1,19104 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: static codebooks autogenerated by huff/huffbuld + last modified: $Id: res_books_stereo.h,v 1.2 2002/07/11 10:27:51 xiphmont Exp $ + + ********************************************************************/ + +#include "codebook.h" +static long _vq_quantlist__16c0_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16c0_s_p1_0[] = { + 1, 4, 4, 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 8, 8, 0, 0, 0, 0, 0, 0, 8, 9,10, 0, 0, 0, + 0, 0, 0, 7, 9,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 8, 8, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 8, 0, 0, 0, 0, + 0, 0, 8,10,10, 0, 0, 0, 0, 0, 0, 8,10,10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,10,10, 0, 0, 0, + 0, 0, 0, 9, 9,12, 0, 0, 0, 0, 0, 0,10,12,11, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,10,10, 0, 0, + 0, 0, 0, 0, 9,12,10, 0, 0, 0, 0, 0, 0,10,11,12, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 8, 8, 0, 0, 0, 0, 0, 0, 8,10,10, 0, 0, + 0, 0, 0, 0, 8,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7,10,10, 0, 0, 0, 0, 0, 0,10,12,11, 0, + 0, 0, 0, 0, 0, 9,10,12, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7,10,10, 0, 0, 0, 0, 0, 0,10,11,12, + 0, 0, 0, 0, 0, 0, 9,12, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c0_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16c0_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p1_0 = { + _vq_quantthresh__16c0_s_p1_0, + _vq_quantmap__16c0_s_p1_0, + 3, + 3 +}; + +static static_codebook _16c0_s_p1_0 = { + 8, 6561, + _vq_lengthlist__16c0_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16c0_s_p1_0, + NULL, + &_vq_auxt__16c0_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c0_s_p2_0[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c0_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c0_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p2_0 = { + _vq_quantthresh__16c0_s_p2_0, + _vq_quantmap__16c0_s_p2_0, + 5, + 5 +}; + +static static_codebook _16c0_s_p2_0 = { + 4, 625, + _vq_lengthlist__16c0_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c0_s_p2_0, + NULL, + &_vq_auxt__16c0_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c0_s_p3_0[] = { + 1, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 6, 6, 7, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 9, 9, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 6, 9, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c0_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c0_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p3_0 = { + _vq_quantthresh__16c0_s_p3_0, + _vq_quantmap__16c0_s_p3_0, + 5, + 5 +}; + +static static_codebook _16c0_s_p3_0 = { + 4, 625, + _vq_lengthlist__16c0_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c0_s_p3_0, + NULL, + &_vq_auxt__16c0_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16c0_s_p4_0[] = { + 1, 3, 2, 7, 8, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c0_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16c0_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p4_0 = { + _vq_quantthresh__16c0_s_p4_0, + _vq_quantmap__16c0_s_p4_0, + 9, + 9 +}; + +static static_codebook _16c0_s_p4_0 = { + 2, 81, + _vq_lengthlist__16c0_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16c0_s_p4_0, + NULL, + &_vq_auxt__16c0_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16c0_s_p5_0[] = { + 1, 3, 3, 6, 6, 6, 6, 8, 8, 0, 0, 0, 7, 7, 7, 7, + 8, 8, 0, 0, 0, 7, 7, 7, 7, 8, 8, 0, 0, 0, 7, 7, + 8, 8, 9, 9, 0, 0, 0, 7, 7, 8, 8, 9, 9, 0, 0, 0, + 8, 9, 8, 8,10,10, 0, 0, 0, 8, 8, 8, 8,10,10, 0, + 0, 0,10,10, 9, 9,10,10, 0, 0, 0, 0, 0, 9, 9,10, + 10, +}; + +static float _vq_quantthresh__16c0_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16c0_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p5_0 = { + _vq_quantthresh__16c0_s_p5_0, + _vq_quantmap__16c0_s_p5_0, + 9, + 9 +}; + +static static_codebook _16c0_s_p5_0 = { + 2, 81, + _vq_lengthlist__16c0_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16c0_s_p5_0, + NULL, + &_vq_auxt__16c0_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__16c0_s_p6_0[] = { + 1, 3, 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,10,11, + 11,11, 0, 0, 0, 6, 6, 8, 8, 9, 9, 9, 9,10,10,11, + 11,11,11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10,10, + 11,11,12,12,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10, + 10,11,11,12,12,12,13, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0,10,10,10, + 10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9, + 10,10,11,11,12,12,13,13,13,13, 0, 0, 0, 0, 0, 9, + 9,10,10,11,11,12,12,13,13,13,14, 0, 0, 0, 0, 0, + 10,10,10,11,11,11,12,12,13,13,13,14, 0, 0, 0, 0, + 0, 0, 0,10,10,11,11,12,12,13,13,14,14, 0, 0, 0, + 0, 0, 0, 0,11,11,12,12,13,13,13,13,14,14, 0, 0, + 0, 0, 0, 0, 0,11,11,12,12,12,13,13,14,15,14, 0, + 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,13,14,14,15, + 0, 0, 0, 0, 0, 0, 0, 0, 0,12,12,13,13,14,13,14, + 14, +}; + +static float _vq_quantthresh__16c0_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__16c0_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p6_0 = { + _vq_quantthresh__16c0_s_p6_0, + _vq_quantmap__16c0_s_p6_0, + 17, + 17 +}; + +static static_codebook _16c0_s_p6_0 = { + 2, 289, + _vq_lengthlist__16c0_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__16c0_s_p6_0, + NULL, + &_vq_auxt__16c0_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16c0_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,11,10,10,11, + 11,10, 4, 7, 7,10,10,10,11,10,10, 6,10,10,11,11, + 11,11,11,10, 6, 9, 9,11,12,12,11, 9, 9, 6, 9,10, + 11,12,12,11, 9,10, 7,11,11,11,11,11,12,13,12, 6, + 9,10,11,10,10,12,13,13, 6,10, 9,11,10,10,11,12, + 13, +}; + +static float _vq_quantthresh__16c0_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__16c0_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p7_0 = { + _vq_quantthresh__16c0_s_p7_0, + _vq_quantmap__16c0_s_p7_0, + 3, + 3 +}; + +static static_codebook _16c0_s_p7_0 = { + 4, 81, + _vq_lengthlist__16c0_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__16c0_s_p7_0, + NULL, + &_vq_auxt__16c0_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16c0_s_p7_1[] = { + 1, 3, 4, 6, 6, 7, 7, 8, 8, 8, 8,10,10,10, 7, 7, + 8, 8, 8, 9, 9, 9,10,10,10, 6, 7, 8, 8, 8, 8, 9, + 8,10,10,10, 7, 7, 8, 8, 9, 9, 9, 9,10,10,10, 7, + 7, 8, 8, 9, 9, 8, 9,10,10,10, 8, 8, 9, 9, 9, 9, + 9, 9,11,11,11, 8, 8, 9, 9, 9, 9, 9,10,10,11,11, + 9, 9, 9, 9, 9, 9, 9,10,11,11,11,10,11, 9, 9, 9, + 9,10, 9,11,11,11,10,11,10,10, 9, 9,10,10,11,11, + 11,11,11, 9, 9, 9, 9,10,10, +}; + +static float _vq_quantthresh__16c0_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16c0_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p7_1 = { + _vq_quantthresh__16c0_s_p7_1, + _vq_quantmap__16c0_s_p7_1, + 11, + 11 +}; + +static static_codebook _16c0_s_p7_1 = { + 2, 121, + _vq_lengthlist__16c0_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16c0_s_p7_1, + NULL, + &_vq_auxt__16c0_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16c0_s_p8_0[] = { + 1, 4, 4, 7, 7, 7, 7, 7, 6, 8, 8,10,10, 6, 5, 6, + 8, 8, 8, 8, 8, 8, 8, 9,10,10, 7, 6, 6, 8, 8, 8, + 8, 8, 8, 8, 8,10,10, 0, 8, 8, 8, 8, 9, 8, 9, 9, + 9,10,10,10, 0, 9, 8, 8, 8, 9, 9, 8, 8, 9, 9,10, + 10, 0,12,11, 8, 8, 9, 9, 9, 9,10,10,11,10, 0,12, + 13, 8, 8, 9,10, 9, 9,11,11,11,12, 0, 0, 0, 8, 8, + 8, 8,10, 9,12,13,12,14, 0, 0, 0, 8, 8, 8, 9,10, + 10,12,12,13,14, 0, 0, 0,13,13, 9, 9,11,11, 0, 0, + 14, 0, 0, 0, 0,14,14,10,10,12,11,12,14,14,14, 0, + 0, 0, 0, 0,11,11,13,13,14,13,14,14, 0, 0, 0, 0, + 0,12,13,13,12,13,14,14,14, +}; + +static float _vq_quantthresh__16c0_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__16c0_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p8_0 = { + _vq_quantthresh__16c0_s_p8_0, + _vq_quantmap__16c0_s_p8_0, + 13, + 13 +}; + +static static_codebook _16c0_s_p8_0 = { + 2, 169, + _vq_lengthlist__16c0_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__16c0_s_p8_0, + NULL, + &_vq_auxt__16c0_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c0_s_p8_1[] = { + 1, 4, 3, 5, 5, 7, 7, 7, 6, 6, 7, 7, 7, 5, 5, 7, + 7, 7, 6, 6, 7, 7, 7, 6, 6, +}; + +static float _vq_quantthresh__16c0_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c0_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p8_1 = { + _vq_quantthresh__16c0_s_p8_1, + _vq_quantmap__16c0_s_p8_1, + 5, + 5 +}; + +static static_codebook _16c0_s_p8_1 = { + 2, 25, + _vq_lengthlist__16c0_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c0_s_p8_1, + NULL, + &_vq_auxt__16c0_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p9_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16c0_s_p9_0[] = { + 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__16c0_s_p9_0[] = { + -157.5, 157.5, +}; + +static long _vq_quantmap__16c0_s_p9_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p9_0 = { + _vq_quantthresh__16c0_s_p9_0, + _vq_quantmap__16c0_s_p9_0, + 3, + 3 +}; + +static static_codebook _16c0_s_p9_0 = { + 4, 81, + _vq_lengthlist__16c0_s_p9_0, + 1, -518803456, 1628680192, 2, 0, + _vq_quantlist__16c0_s_p9_0, + NULL, + &_vq_auxt__16c0_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__16c0_s_p9_1[] = { + 1, 5, 5, 5, 5, 9,11,11,10,10,10,10,10,10,10, 7, + 6, 6, 6, 6,10,10,10,10,10,10,10,10,10,10, 7, 6, + 6, 6, 6,10, 9,10,10,10,10,10,10,10,10,10, 7, 7, + 8, 9,10,10,10,10,10,10,10,10,10,10,10, 8, 7,10, + 10,10, 9,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__16c0_s_p9_1[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__16c0_s_p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p9_1 = { + _vq_quantthresh__16c0_s_p9_1, + _vq_quantmap__16c0_s_p9_1, + 15, + 15 +}; + +static static_codebook _16c0_s_p9_1 = { + 2, 225, + _vq_lengthlist__16c0_s_p9_1, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__16c0_s_p9_1, + NULL, + &_vq_auxt__16c0_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c0_s_p9_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__16c0_s_p9_2[] = { + 1, 5, 5, 7, 8, 8, 7, 9, 9, 9,12,12,11,12,12,10, + 10,11,12,12,12,11,12,12, 8, 9, 8, 7, 9,10,10,11, + 11,10,11,12,10,12,10,12,12,12,11,12,11, 9, 8, 8, + 9,10, 9, 8, 9,10,12,12,11,11,12,11,10,11,12,11, + 12,12, 8, 9, 9, 9,10,11,12,11,12,11,11,11,11,12, + 12,11,11,12,12,11,11, 9, 9, 8, 9, 9,11, 9, 9,10, + 9,11,11,11,11,12,11,11,10,12,12,12, 9,12,11,10, + 11,11,11,11,12,12,12,11,11,11,12,10,12,12,12,10, + 10, 9,10, 9,10,10, 9, 9, 9,10,10,12,10,11,11, 9, + 11,11,10,11,11,11,10,10,10, 9, 9,10,10, 9, 9,10, + 11,11,10,11,10,11,10,11,11,10,11,11,11,10, 9,10, + 10, 9,10, 9, 9,11, 9, 9,11,10,10,11,11,10,10,11, + 10,11, 8, 9,11,11,10, 9,10,11,11,10,11,11,10,10, + 10,11,10, 9,10,10,11, 9,10,10, 9,11,10,10,10,10, + 11,10,11,11, 9,11,10,11,10,10,11,11,10,10,10, 9, + 10,10,11,11,11, 9,10,10,10,10,10,11,10,10,10, 9, + 10,10,11,10,10,10,10,10, 9,10,11,10,10,10,10,11, + 11,11,10,10,10,10,10,11,10,11,10,11,10,10,10, 9, + 11,11,10,10,10,11,11,10,10,10,10,10,10,10,10,11, + 11, 9,10,10,10,11,10,11,10,10,10,11, 9,10,11,10, + 11,10,10, 9,10,10,10,11,10,11,10,10,10,10,10,11, + 11,10,11,11,10,10,11,11,10, 9, 9,10,10,10,10,10, + 9,11, 9,10,10,10,11,11,10,10,10,10,11,11,11,10, + 9, 9,10,10,11,10,10,10,10,10,11,11,11,10,10,10, + 11,11,11, 9,10,10,10,10, 9,10, 9,10,11,10,11,10, + 10,11,11,10,11,11,11,11,11,10,11,10,10,10, 9,11, + 11,10,11,11,11,11,11,11,11,11,11,10,11,10,10,10, + 10,11,10,10,11, 9,10,10,10, +}; + +static float _vq_quantthresh__16c0_s_p9_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__16c0_s_p9_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__16c0_s_p9_2 = { + _vq_quantthresh__16c0_s_p9_2, + _vq_quantmap__16c0_s_p9_2, + 21, + 21 +}; + +static static_codebook _16c0_s_p9_2 = { + 2, 441, + _vq_lengthlist__16c0_s_p9_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__16c0_s_p9_2, + NULL, + &_vq_auxt__16c0_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__16c0_s_single[] = { + 3, 4,19, 7, 9, 7, 8,11, 9,12, 4, 1,19, 6, 7, 7, + 8,10,11,13,18,18,18,18,18,18,18,18,18,18, 8, 6, + 18, 8, 9, 9,11,12,14,18, 9, 6,18, 9, 7, 8, 9,11, + 12,18, 7, 6,18, 8, 7, 7, 7, 9,11,17, 8, 8,18, 9, + 7, 6, 6, 8,11,17,10,10,18,12, 9, 8, 7, 9,12,18, + 13,15,18,15,13,11,10,11,15,18,14,18,18,18,18,18, + 16,16,18,18, +}; + +static static_codebook _huff_book__16c0_s_single = { + 2, 100, + _huff_lengthlist__16c0_s_single, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__16c1_s_long[] = { + 2, 5,20, 7,10, 7, 8,10,11,11, 4, 2,20, 5, 8, 6, + 7, 9,10,10,20,20,20,20,19,19,19,19,19,19, 7, 5, + 19, 6,10, 7, 9,11,13,17,11, 8,19,10, 7, 7, 8,10, + 11,15, 7, 5,19, 7, 7, 5, 6, 9,11,16, 7, 6,19, 8, + 7, 6, 6, 7, 9,13, 9, 9,19,11, 9, 8, 6, 7, 8,13, + 12,14,19,16,13,10, 9, 8, 9,13,14,17,19,18,18,17, + 12,11,11,13, +}; + +static static_codebook _huff_book__16c1_s_long = { + 2, 100, + _huff_lengthlist__16c1_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16c1_s_p1_0[] = { + 1, 5, 5, 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 8, 7, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 8, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 7, 0, 0, 0, 0, + 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 9, 9,11, 0, 0, 0, 0, 0, 0, 9,11,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8,11, 9, 0, 0, 0, 0, 0, 0, 9,10,11, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,11,10, 0, + 0, 0, 0, 0, 0, 8, 9,11, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,11, + 0, 0, 0, 0, 0, 0, 9,11, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c1_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16c1_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p1_0 = { + _vq_quantthresh__16c1_s_p1_0, + _vq_quantmap__16c1_s_p1_0, + 3, + 3 +}; + +static static_codebook _16c1_s_p1_0 = { + 8, 6561, + _vq_lengthlist__16c1_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16c1_s_p1_0, + NULL, + &_vq_auxt__16c1_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c1_s_p2_0[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c1_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c1_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p2_0 = { + _vq_quantthresh__16c1_s_p2_0, + _vq_quantmap__16c1_s_p2_0, + 5, + 5 +}; + +static static_codebook _16c1_s_p2_0 = { + 4, 625, + _vq_lengthlist__16c1_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c1_s_p2_0, + NULL, + &_vq_auxt__16c1_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c1_s_p3_0[] = { + 1, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 7, 7, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 5, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 9, 9, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 7, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c1_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c1_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p3_0 = { + _vq_quantthresh__16c1_s_p3_0, + _vq_quantmap__16c1_s_p3_0, + 5, + 5 +}; + +static static_codebook _16c1_s_p3_0 = { + 4, 625, + _vq_lengthlist__16c1_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c1_s_p3_0, + NULL, + &_vq_auxt__16c1_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16c1_s_p4_0[] = { + 1, 2, 3, 7, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 0, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c1_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16c1_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p4_0 = { + _vq_quantthresh__16c1_s_p4_0, + _vq_quantmap__16c1_s_p4_0, + 9, + 9 +}; + +static static_codebook _16c1_s_p4_0 = { + 2, 81, + _vq_lengthlist__16c1_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16c1_s_p4_0, + NULL, + &_vq_auxt__16c1_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16c1_s_p5_0[] = { + 1, 3, 3, 5, 5, 6, 6, 8, 8, 0, 0, 0, 7, 7, 7, 7, + 9, 9, 0, 0, 0, 7, 7, 7, 7, 9, 9, 0, 0, 0, 8, 8, + 8, 8, 9, 9, 0, 0, 0, 8, 8, 8, 8,10,10, 0, 0, 0, + 9, 9, 8, 8,10,10, 0, 0, 0, 9, 9, 8, 8,10,10, 0, + 0, 0,10,10, 9, 9,10,10, 0, 0, 0, 0, 0, 9, 9,10, + 10, +}; + +static float _vq_quantthresh__16c1_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16c1_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p5_0 = { + _vq_quantthresh__16c1_s_p5_0, + _vq_quantmap__16c1_s_p5_0, + 9, + 9 +}; + +static static_codebook _16c1_s_p5_0 = { + 2, 81, + _vq_lengthlist__16c1_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16c1_s_p5_0, + NULL, + &_vq_auxt__16c1_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__16c1_s_p6_0[] = { + 1, 3, 3, 6, 6, 8, 8, 9, 9, 9, 9,10,10,11,11,12, + 12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11,11, + 12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11, + 11,12,12, 0, 0, 0, 8, 8, 8, 9,10, 9,10,10,10,10, + 11,11,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10,10,11, + 11,11,12,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10,10, + 11,11,12,12,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10, + 10,11,11,12,12,13,13, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9, + 10,10,11,11,12,12,12,12,13,13, 0, 0, 0, 0, 0, 9, + 9,10,10,11,11,12,12,12,12,13,13, 0, 0, 0, 0, 0, + 10,10,11,10,11,11,12,12,13,13,13,13, 0, 0, 0, 0, + 0, 0, 0,10,10,11,11,12,12,13,13,13,13, 0, 0, 0, + 0, 0, 0, 0,11,11,12,12,12,12,13,13,14,14, 0, 0, + 0, 0, 0, 0, 0,11,11,12,12,12,12,13,13,14,14, 0, + 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,13,13,14,14, + 0, 0, 0, 0, 0, 0, 0, 0, 0,12,12,13,13,13,13,14, + 14, +}; + +static float _vq_quantthresh__16c1_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__16c1_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p6_0 = { + _vq_quantthresh__16c1_s_p6_0, + _vq_quantmap__16c1_s_p6_0, + 17, + 17 +}; + +static static_codebook _16c1_s_p6_0 = { + 2, 289, + _vq_lengthlist__16c1_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__16c1_s_p6_0, + NULL, + &_vq_auxt__16c1_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16c1_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9,10,10, + 10, 9, 4, 7, 7,10,10,10,11,10,10, 6,10,10,11,11, + 11,11,10,10, 6,10, 9,11,11,11,11,10,10, 6,10,10, + 11,11,11,11,10,10, 7,11,11,11,11,11,12,12,11, 6, + 10,10,11,10,10,11,11,11, 6,10,10,10,11,10,11,11, + 11, +}; + +static float _vq_quantthresh__16c1_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__16c1_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p7_0 = { + _vq_quantthresh__16c1_s_p7_0, + _vq_quantmap__16c1_s_p7_0, + 3, + 3 +}; + +static static_codebook _16c1_s_p7_0 = { + 4, 81, + _vq_lengthlist__16c1_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__16c1_s_p7_0, + NULL, + &_vq_auxt__16c1_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16c1_s_p7_1[] = { + 2, 3, 3, 5, 6, 7, 7, 7, 7, 8, 8,10,10,10, 6, 6, + 7, 7, 8, 8, 8, 8,10,10,10, 6, 6, 7, 7, 8, 8, 8, + 8,10,10,10, 7, 7, 7, 7, 8, 8, 8, 8,10,10,10, 7, + 7, 7, 7, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 9, 9,10,10,10,10,10, 8, 8, 8, + 8, 9, 9,10,10,10,10,10, 9, 9, 8, 8, 9, 9,10,10, + 10,10,10, 8, 8, 8, 8, 9, 9, +}; + +static float _vq_quantthresh__16c1_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16c1_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p7_1 = { + _vq_quantthresh__16c1_s_p7_1, + _vq_quantmap__16c1_s_p7_1, + 11, + 11 +}; + +static static_codebook _16c1_s_p7_1 = { + 2, 121, + _vq_lengthlist__16c1_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16c1_s_p7_1, + NULL, + &_vq_auxt__16c1_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16c1_s_p8_0[] = { + 1, 4, 4, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 6, 5, 5, + 7, 8, 8, 9, 8, 8, 9, 9,10,11, 6, 5, 5, 8, 8, 9, + 9, 8, 8, 9,10,10,11, 0, 8, 8, 8, 9, 9, 9, 9, 9, + 10,10,11,11, 0, 9, 9, 9, 8, 9, 9, 9, 9,10,10,11, + 11, 0,13,13, 9, 9,10,10,10,10,11,11,12,12, 0,14, + 13, 9, 9,10,10,10,10,11,11,12,12, 0, 0, 0,10,10, + 9, 9,11,11,12,12,13,12, 0, 0, 0,10,10, 9, 9,10, + 10,12,12,13,13, 0, 0, 0,13,14,11,10,11,11,12,12, + 13,14, 0, 0, 0,14,14,10,10,11,11,12,12,13,13, 0, + 0, 0, 0, 0,12,12,12,12,13,13,14,15, 0, 0, 0, 0, + 0,12,12,12,12,13,13,14,15, +}; + +static float _vq_quantthresh__16c1_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__16c1_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p8_0 = { + _vq_quantthresh__16c1_s_p8_0, + _vq_quantmap__16c1_s_p8_0, + 13, + 13 +}; + +static static_codebook _16c1_s_p8_0 = { + 2, 169, + _vq_lengthlist__16c1_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__16c1_s_p8_0, + NULL, + &_vq_auxt__16c1_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c1_s_p8_1[] = { + 2, 3, 3, 5, 5, 6, 6, 6, 5, 5, 6, 6, 6, 5, 5, 6, + 6, 6, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__16c1_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c1_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p8_1 = { + _vq_quantthresh__16c1_s_p8_1, + _vq_quantmap__16c1_s_p8_1, + 5, + 5 +}; + +static static_codebook _16c1_s_p8_1 = { + 2, 25, + _vq_lengthlist__16c1_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c1_s_p8_1, + NULL, + &_vq_auxt__16c1_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16c1_s_p9_0[] = { + 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__16c1_s_p9_0[] = { + -1732.5, -1417.5, -1102.5, -787.5, -472.5, -157.5, 157.5, 472.5, + 787.5, 1102.5, 1417.5, 1732.5, +}; + +static long _vq_quantmap__16c1_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p9_0 = { + _vq_quantthresh__16c1_s_p9_0, + _vq_quantmap__16c1_s_p9_0, + 13, + 13 +}; + +static static_codebook _16c1_s_p9_0 = { + 2, 169, + _vq_lengthlist__16c1_s_p9_0, + 1, -513964032, 1628680192, 4, 0, + _vq_quantlist__16c1_s_p9_0, + NULL, + &_vq_auxt__16c1_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__16c1_s_p9_1[] = { + 1, 4, 4, 4, 4, 8, 8,12,13,14,14,14,14,14,14, 6, + 6, 6, 6, 6,10, 9,14,14,14,14,14,14,14,14, 7, 6, + 5, 6, 6,10, 9,12,13,13,13,13,13,13,13,13, 7, 7, + 9, 9,11,11,12,13,13,13,13,13,13,13,13, 7, 7, 8, + 8,11,12,13,13,13,13,13,13,13,13,13,12,12,10,10, + 13,12,13,13,13,13,13,13,13,13,13,12,12,10,10,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,12,13,12, + 13,13,13,13,13,13,13,13,13,13,13,13,12,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,12,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,12,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13, +}; + +static float _vq_quantthresh__16c1_s_p9_1[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__16c1_s_p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p9_1 = { + _vq_quantthresh__16c1_s_p9_1, + _vq_quantmap__16c1_s_p9_1, + 15, + 15 +}; + +static static_codebook _16c1_s_p9_1 = { + 2, 225, + _vq_lengthlist__16c1_s_p9_1, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__16c1_s_p9_1, + NULL, + &_vq_auxt__16c1_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c1_s_p9_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__16c1_s_p9_2[] = { + 1, 4, 4, 6, 6, 7, 7, 8, 7, 8, 8, 9, 9, 9, 9,10, + 10,10, 9,10,10,11,12,12, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,10,10,10,11,11,10,12,11,11,13,11, 7, 7, 8, + 8, 8, 8, 9, 9, 9,10,10,10,10, 9,10,10,11,11,12, + 11,11, 8, 8, 8, 8, 9, 9,10,10,10,10,11,11,11,11, + 11,11,11,12,11,12,12, 8, 8, 9, 9, 9, 9, 9,10,10, + 10,10,10,10,11,11,11,11,11,11,12,11, 9, 9, 9, 9, + 10,10,10,10,11,10,11,11,11,11,11,11,12,12,12,12, + 11, 9, 9, 9, 9,10,10,10,10,11,11,11,11,11,11,11, + 11,11,12,12,12,13, 9,10,10, 9,11,10,10,10,10,11, + 11,11,11,11,10,11,12,11,12,12,11,12,11,10, 9,10, + 10,11,10,11,11,11,11,11,11,11,11,11,12,12,11,12, + 12,12,10,10,10,11,10,11,11,11,11,11,11,11,11,11, + 11,11,12,13,12,12,11, 9,10,10,11,11,10,11,11,11, + 12,11,11,11,11,11,12,12,13,13,12,13,10,10,12,10, + 11,11,11,11,11,11,11,11,11,12,12,11,13,12,12,12, + 12,13,12,11,11,11,11,11,11,12,11,12,11,11,11,11, + 12,12,13,12,11,12,12,11,11,11,11,11,12,11,11,11, + 11,12,11,11,12,11,12,13,13,12,12,12,12,11,11,11, + 11,11,12,11,11,12,11,12,11,11,11,11,13,12,12,12, + 12,13,11,11,11,12,12,11,11,11,12,11,12,12,12,11, + 12,13,12,11,11,12,12,11,12,11,11,11,12,12,11,12, + 11,11,11,12,12,12,12,13,12,13,12,12,12,12,11,11, + 12,11,11,11,11,11,11,12,12,12,13,12,11,13,13,12, + 12,11,12,10,11,11,11,11,12,11,12,12,11,12,12,13, + 12,12,13,12,12,12,12,12,11,12,12,12,11,12,11,11, + 11,12,13,12,13,13,13,13,13,12,13,13,12,12,13,11, + 11,11,11,11,12,11,11,12,11, +}; + +static float _vq_quantthresh__16c1_s_p9_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__16c1_s_p9_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__16c1_s_p9_2 = { + _vq_quantthresh__16c1_s_p9_2, + _vq_quantmap__16c1_s_p9_2, + 21, + 21 +}; + +static static_codebook _16c1_s_p9_2 = { + 2, 441, + _vq_lengthlist__16c1_s_p9_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__16c1_s_p9_2, + NULL, + &_vq_auxt__16c1_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__16c1_s_short[] = { + 5, 6,17, 8,12, 9,10,10,12,13, 5, 2,17, 4, 9, 5, + 7, 8,11,13,16,16,16,16,16,16,16,16,16,16, 6, 4, + 16, 5,10, 5, 7,10,14,16,13, 9,16,11, 8, 7, 8, 9, + 13,16, 7, 4,16, 5, 7, 4, 6, 8,11,13, 8, 6,16, 7, + 8, 5, 5, 7, 9,13, 9, 8,16, 9, 8, 6, 6, 7, 9,13, + 11,11,16,10,10, 7, 7, 7, 9,13,13,13,16,13,13, 9, + 9, 9,10,13, +}; + +static static_codebook _huff_book__16c1_s_short = { + 2, 100, + _huff_lengthlist__16c1_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__16c2_s_long[] = { + 4, 7, 9, 9, 9, 8, 9,10,15,19, 5, 4, 5, 6, 7, 7, + 8, 9,14,16, 6, 5, 4, 5, 6, 7, 8,10,12,19, 7, 6, + 5, 4, 5, 6, 7, 9,11,18, 8, 7, 6, 5, 5, 5, 7, 9, + 10,17, 8, 7, 7, 5, 5, 5, 6, 7,12,18, 8, 8, 8, 7, + 7, 5, 5, 7,12,18, 8, 9,10, 9, 9, 7, 6, 7,12,17, + 14,18,16,16,15,12,11,10,12,18,15,17,18,18,18,15, + 14,14,16,18, +}; + +static static_codebook _huff_book__16c2_s_long = { + 2, 100, + _huff_lengthlist__16c2_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16c2_s_p1_0[] = { + 1, 3, 3, 0, 0, 0, 0, 0, 0, 4, 5, 5, 0, 0, 0, 0, + 0, 0, 4, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c2_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16c2_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p1_0 = { + _vq_quantthresh__16c2_s_p1_0, + _vq_quantmap__16c2_s_p1_0, + 3, + 3 +}; + +static static_codebook _16c2_s_p1_0 = { + 4, 81, + _vq_lengthlist__16c2_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16c2_s_p1_0, + NULL, + &_vq_auxt__16c2_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c2_s_p2_0[] = { + 2, 4, 3, 7, 7, 0, 0, 0, 7, 8, 0, 0, 0, 8, 8, 0, + 0, 0, 8, 8, 0, 0, 0, 8, 8, 4, 5, 4, 8, 8, 0, 0, + 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 9, 9, 0, 0, 0, + 9, 9, 4, 4, 5, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 8, + 8, 0, 0, 0, 9, 9, 0, 0, 0, 9, 9, 7, 8, 8,10,10, + 0, 0, 0,12,11, 0, 0, 0,11,11, 0, 0, 0,14,13, 0, + 0, 0,14,13, 7, 8, 8, 9,10, 0, 0, 0,11,12, 0, 0, + 0,11,11, 0, 0, 0,14,14, 0, 0, 0,13,14, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8,11,11, 0, 0, 0, + 11,11, 0, 0, 0,12,11, 0, 0, 0,12,12, 0, 0, 0,13, + 13, 8, 8, 8,11,11, 0, 0, 0,11,11, 0, 0, 0,11,12, + 0, 0, 0,12,13, 0, 0, 0,13,13, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 8, 8, 8,12,11, 0, 0, 0,12,11, 0, + 0, 0,11,11, 0, 0, 0,13,13, 0, 0, 0,13,12, 8, 8, + 8,11,12, 0, 0, 0,11,12, 0, 0, 0,11,11, 0, 0, 0, + 13,13, 0, 0, 0,12,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 8, 9, 9,14,13, 0, 0, 0,13,12, 0, 0, 0,13, + 13, 0, 0, 0,13,12, 0, 0, 0,13,13, 8, 9, 9,13,14, + 0, 0, 0,12,13, 0, 0, 0,13,13, 0, 0, 0,12,13, 0, + 0, 0,13,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, + 9, 9,14,13, 0, 0, 0,13,13, 0, 0, 0,13,12, 0, 0, + 0,13,13, 0, 0, 0,13,12, 8, 9, 9,14,14, 0, 0, 0, + 13,13, 0, 0, 0,12,13, 0, 0, 0,13,13, 0, 0, 0,12, + 13, +}; + +static float _vq_quantthresh__16c2_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c2_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p2_0 = { + _vq_quantthresh__16c2_s_p2_0, + _vq_quantmap__16c2_s_p2_0, + 5, + 5 +}; + +static static_codebook _16c2_s_p2_0 = { + 4, 625, + _vq_lengthlist__16c2_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c2_s_p2_0, + NULL, + &_vq_auxt__16c2_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16c2_s_p3_0[] = { + 1, 3, 3, 6, 6, 7, 7, 8, 8, 0, 0, 0, 6, 6, 7, 7, + 9, 9, 0, 0, 0, 6, 6, 7, 7, 9, 9, 0, 0, 0, 7, 7, + 8, 8,10,10, 0, 0, 0, 7, 7, 8, 8,10,10, 0, 0, 0, + 7, 7, 9, 9,10,10, 0, 0, 0, 7, 7, 9, 9,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c2_s_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16c2_s_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p3_0 = { + _vq_quantthresh__16c2_s_p3_0, + _vq_quantmap__16c2_s_p3_0, + 9, + 9 +}; + +static static_codebook _16c2_s_p3_0 = { + 2, 81, + _vq_lengthlist__16c2_s_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16c2_s_p3_0, + NULL, + &_vq_auxt__16c2_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__16c2_s_p4_0[] = { + 2, 3, 3, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9,10, + 10, 0, 0, 0, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10, + 11,11, 0, 0, 0, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10, + 10,10,11, 0, 0, 0, 6, 6, 8, 8, 8, 8, 9, 9,10,10, + 10,11,11,11, 0, 0, 0, 6, 6, 8, 8, 9, 9, 9, 9,10, + 10,11,11,11,11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9, + 10,10,11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, + 9,10,10,11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, + 10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 8, 8, 9, + 9,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__16c2_s_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__16c2_s_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p4_0 = { + _vq_quantthresh__16c2_s_p4_0, + _vq_quantmap__16c2_s_p4_0, + 17, + 17 +}; + +static static_codebook _16c2_s_p4_0 = { + 2, 289, + _vq_lengthlist__16c2_s_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__16c2_s_p4_0, + NULL, + &_vq_auxt__16c2_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16c2_s_p5_0[] = { + 1, 4, 4, 5, 7, 7, 6, 7, 7, 4, 6, 6,10,10,10,10, + 10,10, 4, 7, 6,10,10,10,10,10,10, 5, 9, 9, 9,12, + 11,10,11,12, 7,10,10,12,12,12,12,12,12, 7,10,10, + 11,12,12,12,12,13, 6,10,10,10,12,12,10,12,12, 7, + 10,10,11,13,12,12,12,12, 7,10,10,11,12,12,12,12, + 12, +}; + +static float _vq_quantthresh__16c2_s_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__16c2_s_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p5_0 = { + _vq_quantthresh__16c2_s_p5_0, + _vq_quantmap__16c2_s_p5_0, + 3, + 3 +}; + +static static_codebook _16c2_s_p5_0 = { + 4, 81, + _vq_lengthlist__16c2_s_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__16c2_s_p5_0, + NULL, + &_vq_auxt__16c2_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16c2_s_p5_1[] = { + 2, 3, 3, 6, 6, 7, 7, 7, 7, 8, 8,11,11,11, 6, 6, + 7, 7, 8, 8, 8, 8,11,11,11, 6, 6, 7, 7, 8, 8, 8, + 8,11,11,11, 6, 6, 8, 8, 8, 8, 9, 9,11,11,11, 6, + 6, 8, 8, 8, 8, 9, 9,11,11,11, 7, 7, 8, 8, 8, 8, + 8, 8,11,11,11, 7, 7, 8, 8, 8, 8, 8, 9,11,11,11, + 8, 8, 8, 8, 8, 8, 8, 8,11,11,11,11,11, 8, 8, 8, + 8, 8, 8,11,11,11,11,11, 8, 8, 8, 8, 8, 8,11,11, + 11,11,11, 7, 7, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__16c2_s_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16c2_s_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p5_1 = { + _vq_quantthresh__16c2_s_p5_1, + _vq_quantmap__16c2_s_p5_1, + 11, + 11 +}; + +static static_codebook _16c2_s_p5_1 = { + 2, 121, + _vq_lengthlist__16c2_s_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16c2_s_p5_1, + NULL, + &_vq_auxt__16c2_s_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16c2_s_p6_0[] = { + 1, 4, 4, 7, 6, 8, 8, 9, 9,10,10,11,11, 5, 5, 5, + 7, 7, 9, 9, 9, 9,11,11,12,12, 6, 5, 5, 7, 7, 9, + 9,10,10,11,11,12,12, 0, 6, 6, 7, 7, 9, 9,10,10, + 11,11,12,12, 0, 7, 7, 7, 7, 9, 9,10,10,11,12,12, + 12, 0,11,11, 8, 8,10,10,11,11,12,12,13,13, 0,11, + 12, 8, 8,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static float _vq_quantthresh__16c2_s_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__16c2_s_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p6_0 = { + _vq_quantthresh__16c2_s_p6_0, + _vq_quantmap__16c2_s_p6_0, + 13, + 13 +}; + +static static_codebook _16c2_s_p6_0 = { + 2, 169, + _vq_lengthlist__16c2_s_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__16c2_s_p6_0, + NULL, + &_vq_auxt__16c2_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16c2_s_p6_1[] = { + 2, 3, 3, 5, 5, 6, 6, 6, 5, 5, 6, 6, 6, 5, 5, 6, + 6, 6, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__16c2_s_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16c2_s_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p6_1 = { + _vq_quantthresh__16c2_s_p6_1, + _vq_quantmap__16c2_s_p6_1, + 5, + 5 +}; + +static static_codebook _16c2_s_p6_1 = { + 2, 25, + _vq_lengthlist__16c2_s_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16c2_s_p6_1, + NULL, + &_vq_auxt__16c2_s_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16c2_s_p7_0[] = { + 1, 4, 4, 7, 7, 8, 8, 9, 9,10,10,11,11, 5, 5, 5, + 8, 8, 9, 9,10,10,11,11,12,12, 6, 5, 5, 8, 8, 9, + 9,10,10,11,11,12,13,18, 6, 6, 7, 7, 9, 9,10,10, + 12,12,13,13,18, 6, 6, 7, 7, 9, 9,10,10,12,12,13, + 13,18,11,10, 8, 8,10,10,11,11,12,12,13,13,18,11, + 11, 8, 8,10,10,11,11,12,13,13,13,18,18,18,10,11, + 11,11,12,12,13,13,14,14,18,18,18,11,11,11,11,12, + 12,13,13,14,14,18,18,18,14,14,12,12,12,12,14,14, + 15,14,18,18,18,15,15,11,12,12,12,13,13,15,15,18, + 18,18,18,18,13,13,13,13,13,14,17,16,18,18,18,18, + 18,13,14,13,13,14,13,15,14, +}; + +static float _vq_quantthresh__16c2_s_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__16c2_s_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p7_0 = { + _vq_quantthresh__16c2_s_p7_0, + _vq_quantmap__16c2_s_p7_0, + 13, + 13 +}; + +static static_codebook _16c2_s_p7_0 = { + 2, 169, + _vq_lengthlist__16c2_s_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__16c2_s_p7_0, + NULL, + &_vq_auxt__16c2_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16c2_s_p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 7, 7, 7, 7, 9, 9, 9, 6, 6, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 6, 6, 7, 7, 8, 8, 8, + 8, 9, 9, 9, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 7, + 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 7, 7, 7, 7, 8, 8, + 8, 8, 9, 9, 9, 7, 7, 7, 7, 7, 7, 8, 8, 9, 9, 9, + 7, 7, 8, 8, 7, 7, 8, 8, 9, 9, 9, 9, 9, 7, 7, 7, + 7, 8, 8, 9, 9, 9, 9, 9, 8, 8, 7, 7, 8, 8, 9, 9, + 9, 9, 9, 7, 7, 7, 7, 8, 8, +}; + +static float _vq_quantthresh__16c2_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16c2_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p7_1 = { + _vq_quantthresh__16c2_s_p7_1, + _vq_quantmap__16c2_s_p7_1, + 11, + 11 +}; + +static static_codebook _16c2_s_p7_1 = { + 2, 121, + _vq_lengthlist__16c2_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16c2_s_p7_1, + NULL, + &_vq_auxt__16c2_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__16c2_s_p8_0[] = { + 1, 4, 4, 7, 6, 7, 7, 6, 6, 8, 8, 9, 9,10,10, 6, + 6, 6, 8, 8, 9, 8, 8, 8, 9, 9,11,10,11,11, 7, 6, + 6, 8, 8, 9, 8, 7, 7, 9, 9,10,10,12,11,14, 8, 8, + 8, 9, 9, 9, 9, 9,10, 9,10,10,11,13,14, 8, 8, 8, + 8, 9, 9, 8, 8, 9, 9,10,10,11,12,14,13,11, 9, 9, + 9, 9, 9, 9, 9,10,11,10,13,12,14,11,13, 8, 9, 9, + 9, 9, 9,10,10,11,10,13,12,14,14,14, 8, 9, 9, 9, + 11,11,11,11,11,12,13,13,14,14,14, 9, 8, 9, 9,10, + 10,12,10,11,12,12,14,14,14,14,11,12,10,10,12,12, + 12,12,13,14,12,12,14,14,14,12,12, 9,10,11,11,12, + 14,12,14,14,14,14,14,14,14,14,11,11,12,11,12,14, + 14,14,14,14,14,14,14,14,14,12,11,11,11,11,14,14, + 14,14,14,14,14,14,14,14,14,14,13,12,14,14,14,14, + 14,14,14,14,14,14,14,14,14,12,12,12,13,14,14,13, + 13, +}; + +static float _vq_quantthresh__16c2_s_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__16c2_s_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p8_0 = { + _vq_quantthresh__16c2_s_p8_0, + _vq_quantmap__16c2_s_p8_0, + 15, + 15 +}; + +static static_codebook _16c2_s_p8_0 = { + 2, 225, + _vq_lengthlist__16c2_s_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__16c2_s_p8_0, + NULL, + &_vq_auxt__16c2_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__16c2_s_p8_1[] = { + 2, 4, 4, 6, 6, 7, 7, 7, 7, 8, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8,11,12,11, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9, 9,11,11,10, 7, 7, 8, + 8, 8, 8, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,11, + 11,11, 8, 7, 8, 8, 9, 9, 9, 9, 9, 9,10,10, 9,10, + 10, 9,10,10,11,11,12, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9,10, 9,10,10,10,10,11,11,11, 8, 8, 9, 9, + 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,11,11, + 11, 8, 8, 9, 8, 9, 9, 9, 9,10, 9, 9, 9,10,10,10, + 10, 9,10,11,11,11, 9, 9, 9, 9,10, 9, 9, 9,10,10, + 9,10, 9,10,10,10,10,10,11,12,11,11,11, 9, 9, 9, + 9, 9,10,10, 9,10,10,10,10,10,10,10,10,12,11,13, + 13,11, 9, 9, 9, 9,10,10, 9,10,10,10,10,11,10,10, + 10,10,11,12,11,12,11, 9, 9, 9,10,10, 9,10,10,10, + 10,10,10,10,10,10,10,11,11,11,12,11, 9,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,11,12,12,12, + 11,11,11,10, 9,10,10,10,10,10,10,10,10,11,10,10, + 10,11,11,11,11,11,11,11,10,10,10,11,10,10,10,10, + 10,10,10,10,10,10,11,11,11,11,12,12,11,10,10,10, + 10,10,10,10,10,11,10,10,10,11,10,12,11,11,12,11, + 11,11,10,10,10,10,10,11,10,10,10,10,10,11,10,10, + 11,11,11,12,11,12,11,11,12,10,10,10,10,10,10,10, + 11,10,10,11,10,12,11,11,11,12,11,11,11,11,10,10, + 10,10,10,10,10,11,11,11,10,11,12,11,11,11,12,11, + 12,11,12,10,11,10,10,10,10,11,10,10,10,10,10,10, + 12,11,11,11,11,11,12,12,10,10,10,10,10,11,10,10, + 11,10,11,11,11,11,11,11,11,11,11,11,11,11,12,11, + 10,11,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__16c2_s_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__16c2_s_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p8_1 = { + _vq_quantthresh__16c2_s_p8_1, + _vq_quantmap__16c2_s_p8_1, + 21, + 21 +}; + +static static_codebook _16c2_s_p8_1 = { + 2, 441, + _vq_lengthlist__16c2_s_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__16c2_s_p8_1, + NULL, + &_vq_auxt__16c2_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16c2_s_p9_0[] = { + 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__16c2_s_p9_0[] = { + -5120.5, -4189.5, -3258.5, -2327.5, -1396.5, -465.5, 465.5, 1396.5, + 2327.5, 3258.5, 4189.5, 5120.5, +}; + +static long _vq_quantmap__16c2_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p9_0 = { + _vq_quantthresh__16c2_s_p9_0, + _vq_quantmap__16c2_s_p9_0, + 13, + 13 +}; + +static static_codebook _16c2_s_p9_0 = { + 2, 169, + _vq_lengthlist__16c2_s_p9_0, + 1, -510275072, 1631393792, 4, 0, + _vq_quantlist__16c2_s_p9_0, + NULL, + &_vq_auxt__16c2_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p9_1[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__16c2_s_p9_1[] = { + 1, 5, 5, 9, 8, 7, 7, 7, 6,10,11,11,11,11,11,11, + 11, 8, 7, 6, 8, 8,10, 9,10,10,10, 9,11,10,10,10, + 10,10, 8, 6, 6, 8, 8, 9, 8, 9, 8, 9,10,10,10,10, + 10,10,10,10, 8,10, 9, 9, 9, 9,10,10,10,10,10,10, + 10,10,10,10,10, 8, 9, 9, 9,10,10, 9,10,10,10,10, + 10,10,10,10,10,10,10,10, 9, 8, 9, 9,10,10,10,10, + 10,10,10,10,10,10,10,10, 9, 8, 8, 9, 9,10,10,10, + 10,10,10,10,10,10,10,10,10,10, 9,10, 9, 9,10,10, + 10,10,10,10,10,10,10,10,10,10,10, 9, 8, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 9, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10, 9,10, 9,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10, 9,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__16c2_s_p9_1[] = { + -367.5, -318.5, -269.5, -220.5, -171.5, -122.5, -73.5, -24.5, + 24.5, 73.5, 122.5, 171.5, 220.5, 269.5, 318.5, 367.5, +}; + +static long _vq_quantmap__16c2_s_p9_1[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p9_1 = { + _vq_quantthresh__16c2_s_p9_1, + _vq_quantmap__16c2_s_p9_1, + 17, + 17 +}; + +static static_codebook _16c2_s_p9_1 = { + 2, 289, + _vq_lengthlist__16c2_s_p9_1, + 1, -518488064, 1622704128, 5, 0, + _vq_quantlist__16c2_s_p9_1, + NULL, + &_vq_auxt__16c2_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__16c2_s_p9_2[] = { + 13, + 12, + 14, + 11, + 15, + 10, + 16, + 9, + 17, + 8, + 18, + 7, + 19, + 6, + 20, + 5, + 21, + 4, + 22, + 3, + 23, + 2, + 24, + 1, + 25, + 0, + 26, +}; + +static long _vq_lengthlist__16c2_s_p9_2[] = { + 1, 4, 4, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 8, 7, 8, 7, 7, 4, 4, +}; + +static float _vq_quantthresh__16c2_s_p9_2[] = { + -12.5, -11.5, -10.5, -9.5, -8.5, -7.5, -6.5, -5.5, + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5, + 11.5, 12.5, +}; + +static long _vq_quantmap__16c2_s_p9_2[] = { + 25, 23, 21, 19, 17, 15, 13, 11, + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, 12, 14, 16, 18, 20, + 22, 24, 26, +}; + +static encode_aux_threshmatch _vq_auxt__16c2_s_p9_2 = { + _vq_quantthresh__16c2_s_p9_2, + _vq_quantmap__16c2_s_p9_2, + 27, + 27 +}; + +static static_codebook _16c2_s_p9_2 = { + 1, 27, + _vq_lengthlist__16c2_s_p9_2, + 1, -528875520, 1611661312, 5, 0, + _vq_quantlist__16c2_s_p9_2, + NULL, + &_vq_auxt__16c2_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__16c2_s_short[] = { + 7,10,11,11,11,14,15,15,17,14, 8, 6, 7, 7, 8, 9, + 11,11,14,17, 9, 6, 6, 6, 7, 7,10,11,15,16, 9, 6, + 6, 4, 4, 5, 8, 9,12,16,10, 6, 6, 4, 4, 4, 6, 9, + 13,16,10, 7, 6, 5, 4, 3, 5, 7,13,16,11, 9, 8, 7, + 6, 5, 5, 6,12,15,10,10,10, 9, 7, 6, 6, 7,11,15, + 13,13,13,13,11,10,10, 9,12,16,16,16,16,14,16,15, + 15,12,14,14, +}; + +static static_codebook _huff_book__16c2_s_short = { + 2, 100, + _huff_lengthlist__16c2_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c0_s_long[] = { + 3, 4,18, 8, 8, 8, 9,10,11,13, 3, 2,18, 5, 5, 6, + 8,11,12,12,18,18,18,18,18,18,18,18,18,18, 8, 5, + 18, 7, 8, 8,10,14,14,16, 8, 5,18, 7, 5, 6, 7,11, + 12,13, 8, 6,18, 7, 5, 6, 7,10,12,14, 8, 7,18,10, + 7, 6, 6, 8,11,14, 9,10,18,13, 9, 7, 6, 8,10,13, + 11,13,18,16,13,10, 9, 8, 8,11,12,17,18,17,18,14, + 11, 9,10,14, +}; + +static static_codebook _huff_book__44c0_s_long = { + 2, 100, + _huff_lengthlist__44c0_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c0_s_p1_0[] = { + 1, 5, 5, 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 8, 7, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 8, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 7, 0, 0, 0, 0, + 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 9, 9,11, 0, 0, 0, 0, 0, 0, 9,11,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 9,10,11, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,11,10, 0, + 0, 0, 0, 0, 0, 8, 9,11, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 9,11,10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c0_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p1_0 = { + _vq_quantthresh__44c0_s_p1_0, + _vq_quantmap__44c0_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c0_s_p1_0 = { + 8, 6561, + _vq_lengthlist__44c0_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c0_s_p1_0, + NULL, + &_vq_auxt__44c0_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c0_s_p2_0[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c0_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p2_0 = { + _vq_quantthresh__44c0_s_p2_0, + _vq_quantmap__44c0_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c0_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c0_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c0_s_p2_0, + NULL, + &_vq_auxt__44c0_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c0_s_p3_0[] = { + 1, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 5, 7, 7, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 5, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 9, 9, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 7, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c0_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p3_0 = { + _vq_quantthresh__44c0_s_p3_0, + _vq_quantmap__44c0_s_p3_0, + 5, + 5 +}; + +static static_codebook _44c0_s_p3_0 = { + 4, 625, + _vq_lengthlist__44c0_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c0_s_p3_0, + NULL, + &_vq_auxt__44c0_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c0_s_p4_0[] = { + 1, 3, 2, 8, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c0_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p4_0 = { + _vq_quantthresh__44c0_s_p4_0, + _vq_quantmap__44c0_s_p4_0, + 9, + 9 +}; + +static static_codebook _44c0_s_p4_0 = { + 2, 81, + _vq_lengthlist__44c0_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c0_s_p4_0, + NULL, + &_vq_auxt__44c0_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c0_s_p5_0[] = { + 1, 3, 3, 6, 6, 6, 6, 8, 8, 0, 0, 0, 6, 7, 7, 7, + 9, 9, 0, 0, 0, 7, 7, 7, 7, 9, 9, 0, 0, 0, 7, 7, + 8, 8, 9, 9, 0, 0, 0, 7, 7, 8, 8,10, 9, 0, 0, 0, + 8, 8, 8, 8,10,10, 0, 0, 0, 8, 8, 8, 8,10,10, 0, + 0, 0,10, 9, 9, 9,11,11, 0, 0, 0, 0, 0, 9, 9,11, + 11, +}; + +static float _vq_quantthresh__44c0_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c0_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p5_0 = { + _vq_quantthresh__44c0_s_p5_0, + _vq_quantmap__44c0_s_p5_0, + 9, + 9 +}; + +static static_codebook _44c0_s_p5_0 = { + 2, 81, + _vq_lengthlist__44c0_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c0_s_p5_0, + NULL, + &_vq_auxt__44c0_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c0_s_p6_0[] = { + 1, 3, 4, 6, 6, 7, 8, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,10,10, + 11,11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,10, + 11,11,11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 8, 8, 9, 9, 9,10,10,10, + 10,11,11,11,12,12, 0, 0, 0, 8, 8, 9, 9, 9, 9,10, + 10,11,11,11,11,12,12, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,12,12,12,13, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 9, 9, + 9,10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, + 9, 9, 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 10, 9,10,10,11,11,11,12,12,13,13,13, 0, 0, 0, 0, + 0, 0, 0,10,10,11,11,12,12,12,12,13,13, 0, 0, 0, + 0, 0, 0, 0,11,11,11,11,12,12,13,13,13,13, 0, 0, + 0, 0, 0, 0, 0,11,11,11,11,12,12,13,12,13,13, 0, + 0, 0, 0, 0, 0, 0,11,11,12,12,12,12,13,13,13,14, + 0, 0, 0, 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,13, + 14, +}; + +static float _vq_quantthresh__44c0_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c0_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p6_0 = { + _vq_quantthresh__44c0_s_p6_0, + _vq_quantmap__44c0_s_p6_0, + 17, + 17 +}; + +static static_codebook _44c0_s_p6_0 = { + 2, 289, + _vq_lengthlist__44c0_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c0_s_p6_0, + NULL, + &_vq_auxt__44c0_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c0_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 4, 7, 7,10, 9, 9,11, 9, 9, 7,10,10,10,11, + 10,11,10,10, 6, 9, 9,11,10,10,11, 9,10, 6, 9, 9, + 11,10,10,11, 9, 9, 7,10,10,11,11,11,12,11,11, 7, + 9, 9,10, 9, 9,11,11,10, 7, 9, 9,10,10,10,12,10, + 11, +}; + +static float _vq_quantthresh__44c0_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c0_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p7_0 = { + _vq_quantthresh__44c0_s_p7_0, + _vq_quantmap__44c0_s_p7_0, + 3, + 3 +}; + +static static_codebook _44c0_s_p7_0 = { + 4, 81, + _vq_lengthlist__44c0_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c0_s_p7_0, + NULL, + &_vq_auxt__44c0_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c0_s_p7_1[] = { + 2, 3, 3, 6, 6, 7, 7, 7, 7, 7, 7,10, 9, 9, 6, 6, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 6, 6, 7, 7, 8, 8, 8, + 8,10, 9,10, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, + 7, 7, 7, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 8, 7, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 9, 9, 8, 8, 9, 8,10,10, + 10,10,10, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c0_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c0_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p7_1 = { + _vq_quantthresh__44c0_s_p7_1, + _vq_quantmap__44c0_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c0_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c0_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c0_s_p7_1, + NULL, + &_vq_auxt__44c0_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c0_s_p8_0[] = { + 1, 4, 4, 6, 6, 7, 7, 7, 7, 8, 8,10, 9, 7, 5, 5, + 7, 7, 8, 8, 8, 8, 9, 9,10,10, 7, 6, 6, 7, 7, 8, + 8, 8, 8, 9, 9,10,10, 0, 8, 8, 8, 8, 9, 8, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,12,12, 9, 8, 9, 9,10,10,10,11,12,11, 0,13, + 13, 9, 8, 9, 9,10,10,10,11,11,11, 0, 0, 0,10,10, + 9, 9,10,10,11,11,12,12, 0, 0, 0,10,10, 9, 8,10, + 10,11,11,12,12, 0, 0, 0,13,13,10,10,11,11,12,12, + 12,12, 0, 0, 0,15,15,10,10,11, 9,12,12,13,13, 0, + 0, 0, 0, 0,12,11,11,11,12,12,13,13, 0, 0, 0, 0, + 0,12,12,11,11,12,11,14,13, +}; + +static float _vq_quantthresh__44c0_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c0_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p8_0 = { + _vq_quantthresh__44c0_s_p8_0, + _vq_quantmap__44c0_s_p8_0, + 13, + 13 +}; + +static static_codebook _44c0_s_p8_0 = { + 2, 169, + _vq_lengthlist__44c0_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c0_s_p8_0, + NULL, + &_vq_auxt__44c0_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c0_s_p8_1[] = { + 2, 3, 4, 4, 4, 6, 6, 6, 5, 5, 6, 6, 6, 5, 5, 6, + 6, 6, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c0_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c0_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p8_1 = { + _vq_quantthresh__44c0_s_p8_1, + _vq_quantmap__44c0_s_p8_1, + 5, + 5 +}; + +static static_codebook _44c0_s_p8_1 = { + 2, 25, + _vq_lengthlist__44c0_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c0_s_p8_1, + NULL, + &_vq_auxt__44c0_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p9_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c0_s_p9_0[] = { + 1, 7, 7,11,11,10,11,11,11,11, 9,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11, 8,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11, 8,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__44c0_s_p9_0[] = { + -331.5, -110.5, 110.5, 331.5, +}; + +static long _vq_quantmap__44c0_s_p9_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p9_0 = { + _vq_quantthresh__44c0_s_p9_0, + _vq_quantmap__44c0_s_p9_0, + 5, + 5 +}; + +static static_codebook _44c0_s_p9_0 = { + 4, 625, + _vq_lengthlist__44c0_s_p9_0, + 1, -518283264, 1627103232, 3, 0, + _vq_quantlist__44c0_s_p9_0, + NULL, + &_vq_auxt__44c0_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c0_s_p9_1[] = { + 1, 4, 4, 6, 6, 7, 7, 9, 9,12,12,13,12, 6, 5, 5, + 7, 7, 8, 8,10,10,12,11,14,13, 6, 5, 5, 7, 7, 7, + 8, 9, 9,11,12,13,12,15, 7, 7, 7, 7, 9, 9,11,11, + 14,13,14,13,15, 7, 7, 8, 8, 8, 9,10,10,11,11,12, + 11,15,10,10, 8, 8,10,10,12,11,13,15,14,14,15,11, + 10, 8, 8,11,11,11,11,13,13,12,13,15,15,15,10,10, + 10,10,12,13,13,13,15,14,15,15,15,10,10,10,11,13, + 14,13,14,14,14,15,15,15,15,15,11,12,11,13,14,14, + 15,15,15,15,15,14,15,11,11,13,12,14,13,15,15,15, + 15,15,15,15,13,12,13,12,15,15,15,15,15,15,15,15, + 15,12,13,12,12,15,14,15,15, +}; + +static float _vq_quantthresh__44c0_s_p9_1[] = { + -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, 25.5, + 42.5, 59.5, 76.5, 93.5, +}; + +static long _vq_quantmap__44c0_s_p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p9_1 = { + _vq_quantthresh__44c0_s_p9_1, + _vq_quantmap__44c0_s_p9_1, + 13, + 13 +}; + +static static_codebook _44c0_s_p9_1 = { + 2, 169, + _vq_lengthlist__44c0_s_p9_1, + 1, -522616832, 1620115456, 4, 0, + _vq_quantlist__44c0_s_p9_1, + NULL, + &_vq_auxt__44c0_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_s_p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c0_s_p9_2[] = { + 2, 4, 4, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 8, + 9,10,10,10, 7, 7, 8, 7, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9,10,10,10, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9,10,10,10, 7, 7, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,11,10,10, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10,10,10, 8, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9,10, 9, 9,10,10,11, 9, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,11, 9, 8, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,11,11,10,11,11, 9, 9, + 9, 9, 9, 9,10, 9, 9, 9,10, 9,10,10,10,11,11, 9, + 9, 9, 9, 9, 9, 9,10, 9, 9, 9,10,11,10,10,10,11, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10, + 11,11,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, + 11,11,10,10, 9,10,10,10, 9,10, 9, 9, 9, 9,10,10, + 11,10,11,11,11, 9,10, 9, 9, 9, 9, 9, 9, 9,10,10, + 11,10,11,11,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,10,11,11,10,11,11, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44c0_s_p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c0_s_p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_s_p9_2 = { + _vq_quantthresh__44c0_s_p9_2, + _vq_quantmap__44c0_s_p9_2, + 17, + 17 +}; + +static static_codebook _44c0_s_p9_2 = { + 2, 289, + _vq_lengthlist__44c0_s_p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c0_s_p9_2, + NULL, + &_vq_auxt__44c0_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c0_s_short[] = { + 6, 8,17,12,12,12,14,15,17,16, 5, 1,16, 5, 6, 6, + 9,12,14,13,16,16,16,16,16,16,16,16,16,16, 9, 4, + 16, 6, 9, 7,10,14,14,14, 8, 5,16, 8, 6, 7,10,13, + 15,16, 9, 6,16, 7, 7, 7,10,12,15,14,10, 8,16, 9, + 9, 9,10,12,15,14,13, 7,16, 8, 7, 7,10,12,14,13, + 15, 6,16, 7, 5, 5, 7, 9,13,16,14, 7,16, 8, 6, 6, + 8, 9,11,14, +}; + +static static_codebook _huff_book__44c0_s_short = { + 2, 100, + _huff_lengthlist__44c0_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c0_sm_long[] = { + 3, 4,10, 8,11, 9,10,10,11,13, 3, 2,10, 5, 8, 6, + 8,11,12,13,10, 8, 7,10, 7, 8, 9,11,12,13, 8, 5, + 12, 6, 9, 7,10,13,14,16,11, 7, 7, 9, 5, 6, 7,10, + 13,13, 8, 6, 7, 7, 5, 5, 6, 9,12,13, 9, 8, 8, 8, + 7, 6, 6, 8,11,14,10,10,10,11, 9, 8, 7, 8,10,13, + 11,14,12,15,13,11, 9, 8, 9,12,12,18,15,18,17,15, + 12,10,10,14, +}; + +static static_codebook _huff_book__44c0_sm_long = { + 2, 100, + _huff_lengthlist__44c0_sm_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c0_sm_p1_0[] = { + 1, 5, 5, 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 8, 7, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 8, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 7, 0, 0, 0, 0, + 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 9, 9,10, 0, 0, 0, 0, 0, 0, 9,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, 0, + 0, 0, 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 9,10, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_sm_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c0_sm_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p1_0 = { + _vq_quantthresh__44c0_sm_p1_0, + _vq_quantmap__44c0_sm_p1_0, + 3, + 3 +}; + +static static_codebook _44c0_sm_p1_0 = { + 8, 6561, + _vq_lengthlist__44c0_sm_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c0_sm_p1_0, + NULL, + &_vq_auxt__44c0_sm_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c0_sm_p2_0[] = { + 1, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, 6, 6, 0, 0, 0, + 8, 8, 0, 0, 0, 0, 0, 0, 0, 5, 7, 6, 0, 0, 0, 8, + 8, 0, 0, 0, 8, 8, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 5, 6, 7, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, 0, 0, 8, 8, 0, 0, + 0, 8, 8, 0, 0, 0,10, 9, 0, 0, 0, 0, 0, 0, 0, 6, + 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 9, + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, + 0, 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, + 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0,10,10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 8,10,10, 0, 0, 0,10,10, 0, 0, 0, 9,10, 0, 0, 0, + 10,10, 0, 0, 0, 0, 0, 0, 0, 8,10,10, 0, 0, 0,10, + 10, 0, 0, 0,10, 9, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_sm_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c0_sm_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p2_0 = { + _vq_quantthresh__44c0_sm_p2_0, + _vq_quantmap__44c0_sm_p2_0, + 5, + 5 +}; + +static static_codebook _44c0_sm_p2_0 = { + 4, 625, + _vq_lengthlist__44c0_sm_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c0_sm_p2_0, + NULL, + &_vq_auxt__44c0_sm_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c0_sm_p3_0[] = { + 2, 3, 4, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 4, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 8, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 6, 8, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_sm_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c0_sm_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p3_0 = { + _vq_quantthresh__44c0_sm_p3_0, + _vq_quantmap__44c0_sm_p3_0, + 5, + 5 +}; + +static static_codebook _44c0_sm_p3_0 = { + 4, 625, + _vq_lengthlist__44c0_sm_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c0_sm_p3_0, + NULL, + &_vq_auxt__44c0_sm_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c0_sm_p4_0[] = { + 2, 3, 3, 6, 6, 0, 0, 0, 0, 0, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 4, 4, 6, 6, 0, 0, 0, 0, 0, 5, 5, 6, 6, + 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, + 7, 8, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c0_sm_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c0_sm_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p4_0 = { + _vq_quantthresh__44c0_sm_p4_0, + _vq_quantmap__44c0_sm_p4_0, + 9, + 9 +}; + +static static_codebook _44c0_sm_p4_0 = { + 2, 81, + _vq_lengthlist__44c0_sm_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c0_sm_p4_0, + NULL, + &_vq_auxt__44c0_sm_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c0_sm_p5_0[] = { + 1, 3, 3, 6, 6, 7, 7, 9, 9, 0, 6, 6, 7, 7, 8, 8, + 9, 9, 0, 6, 6, 7, 7, 8, 8,10,10, 0, 7, 7, 8, 8, + 8, 8,10,10, 0, 0, 0, 8, 8, 8, 8,10,10, 0, 0, 0, + 8, 8, 9, 9,11,11, 0, 0, 0, 9, 9, 9, 9,11,11, 0, + 0, 0,10,10, 9, 9,11,11, 0, 0, 0, 0, 0, 9, 9,11, + 11, +}; + +static float _vq_quantthresh__44c0_sm_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c0_sm_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p5_0 = { + _vq_quantthresh__44c0_sm_p5_0, + _vq_quantmap__44c0_sm_p5_0, + 9, + 9 +}; + +static static_codebook _44c0_sm_p5_0 = { + 2, 81, + _vq_lengthlist__44c0_sm_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c0_sm_p5_0, + NULL, + &_vq_auxt__44c0_sm_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c0_sm_p6_0[] = { + 1, 4, 4, 6, 6, 8, 8, 9, 9, 8, 8,10,10,10,10,11, + 11, 0, 6, 5, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11,11, + 12,12, 0, 6, 5, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11, + 11,12,12, 0, 7, 7, 8, 8, 8, 8,10,10,10,10,11,11, + 11,11,12,12, 0, 0, 0, 8, 7, 8, 8,10,10,10,10,11, + 11,11,11,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10,10, + 11,11,12,12,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10, + 10,11,11,12,12,12,13, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9, + 10,10,11,11,12,12,12,13,13,13, 0, 0, 0, 0, 0, 9, + 9,10, 9,11,11,12,12,13,13,13,13, 0, 0, 0, 0, 0, + 10,10,10,10,11,11,12,12,13,13,14,14, 0, 0, 0, 0, + 0, 0, 0,10,10,11,11,12,12,13,13,14,14, 0, 0, 0, + 0, 0, 0, 0,11,11,11,11,12,12,13,13,14,14, 0, 0, + 0, 0, 0, 0, 0,11,11,11,11,12,12,13,13,14,14, 0, + 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,13,13,14,14, + 0, 0, 0, 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,14, + 14, +}; + +static float _vq_quantthresh__44c0_sm_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c0_sm_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p6_0 = { + _vq_quantthresh__44c0_sm_p6_0, + _vq_quantmap__44c0_sm_p6_0, + 17, + 17 +}; + +static static_codebook _44c0_sm_p6_0 = { + 2, 289, + _vq_lengthlist__44c0_sm_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c0_sm_p6_0, + NULL, + &_vq_auxt__44c0_sm_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c0_sm_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 5, 7, 6,10, 9, 9,10, + 9, 9, 4, 7, 7,10, 9, 9,11, 9, 9, 6,10,10,10,10, + 10,11,10,10, 6, 9, 9,10,10,10,11, 9,10, 6, 9, 9, + 10,10,10,11,10, 9, 7,10,10,11,11,11,11,11,11, 6, + 9, 9,10, 9,10,11,11,10, 6, 9, 9,10,10,10,11,10, + 11, +}; + +static float _vq_quantthresh__44c0_sm_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c0_sm_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p7_0 = { + _vq_quantthresh__44c0_sm_p7_0, + _vq_quantmap__44c0_sm_p7_0, + 3, + 3 +}; + +static static_codebook _44c0_sm_p7_0 = { + 4, 81, + _vq_lengthlist__44c0_sm_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c0_sm_p7_0, + NULL, + &_vq_auxt__44c0_sm_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c0_sm_p7_1[] = { + 2, 4, 4, 5, 5, 7, 7, 7, 7, 7, 7, 9, 5, 5, 7, 7, + 7, 7, 8, 8, 8, 8, 9, 5, 5, 7, 7, 7, 7, 8, 8, 8, + 8,10, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8,10,10,10, 7, + 7, 7, 7, 8, 8, 8, 8,10,10,10, 8, 8, 8, 8, 8, 8, + 8, 8,10,10,10, 8, 8, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 9, 9, 8, 8, 8, 8,10,10, + 10,10,10, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c0_sm_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c0_sm_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p7_1 = { + _vq_quantthresh__44c0_sm_p7_1, + _vq_quantmap__44c0_sm_p7_1, + 11, + 11 +}; + +static static_codebook _44c0_sm_p7_1 = { + 2, 121, + _vq_lengthlist__44c0_sm_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c0_sm_p7_1, + NULL, + &_vq_auxt__44c0_sm_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c0_sm_p8_0[] = { + 1, 4, 4, 6, 6, 7, 7, 7, 7, 9, 9,10,10, 7, 5, 5, + 7, 7, 8, 8, 8, 8, 9, 9,10,10, 7, 5, 6, 7, 7, 8, + 8, 8, 8, 9, 9,10,10, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,12,12, 9, 9, 9, 9,10,10,11,11,12,11, 0,12, + 12, 9, 9, 9, 9,10,10,11,11,12,11, 0, 0, 0, 9, 9, + 9,10,11,11,11,11,12,12, 0, 0, 0,10,10, 9, 9,11, + 11,11,11,12,12, 0, 0, 0,13,13,10,10,11,11,12,12, + 13,13, 0, 0, 0,14,14,10,10,11,10,12,12,13,13, 0, + 0, 0, 0, 0,12,12,11,11,12,12,13,13, 0, 0, 0, 0, + 0,12,12,11,10,12,11,14,14, +}; + +static float _vq_quantthresh__44c0_sm_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c0_sm_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p8_0 = { + _vq_quantthresh__44c0_sm_p8_0, + _vq_quantmap__44c0_sm_p8_0, + 13, + 13 +}; + +static static_codebook _44c0_sm_p8_0 = { + 2, 169, + _vq_lengthlist__44c0_sm_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c0_sm_p8_0, + NULL, + &_vq_auxt__44c0_sm_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c0_sm_p8_1[] = { + 2, 4, 4, 4, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c0_sm_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c0_sm_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p8_1 = { + _vq_quantthresh__44c0_sm_p8_1, + _vq_quantmap__44c0_sm_p8_1, + 5, + 5 +}; + +static static_codebook _44c0_sm_p8_1 = { + 2, 25, + _vq_lengthlist__44c0_sm_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c0_sm_p8_1, + NULL, + &_vq_auxt__44c0_sm_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p9_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c0_sm_p9_0[] = { + 1, 3, 4,11,11,11,11,11,11, 4, 6, 6,10,10,10,10, + 10,10, 4, 6, 5,10,10,10,10,10,10,10, 6, 7,10, 9, + 10,10,10,10,10, 7, 9,10, 9,10,10,10,10,10, 9,10, + 7, 9,10,10,10,10,10,10,10, 9,10,10,10,10,10,10, + 10,10, 9,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__44c0_sm_p9_0[] = { + -773.5, -552.5, -331.5, -110.5, 110.5, 331.5, 552.5, 773.5, +}; + +static long _vq_quantmap__44c0_sm_p9_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p9_0 = { + _vq_quantthresh__44c0_sm_p9_0, + _vq_quantmap__44c0_sm_p9_0, + 9, + 9 +}; + +static static_codebook _44c0_sm_p9_0 = { + 2, 81, + _vq_lengthlist__44c0_sm_p9_0, + 1, -516186112, 1627103232, 4, 0, + _vq_quantlist__44c0_sm_p9_0, + NULL, + &_vq_auxt__44c0_sm_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c0_sm_p9_1[] = { + 1, 4, 4, 6, 6, 7, 7, 9, 9,12,12,13,13, 6, 5, 5, + 7, 7, 8, 8,10,10,12,12,14,13, 6, 5, 5, 6, 7, 8, + 8,10, 9,11,12,13,12,18, 7, 7, 7, 7, 9, 9,11,11, + 14,14,14,13,17, 7, 7, 8, 7, 9, 9,10,10,12,11,13, + 12,18,10,10, 8, 8,10,10,12,11,13,14,16,15,17,11, + 11, 8, 8,11,11,11,12,13,13,13,15,18,18,18,10,11, + 10,10,12,13,14,13,17,14,17,17,17,10,10,11,11,14, + 14,14,13,14,14,18,18,18,17,15,11,12,12,13,15,15, + 16,17,18,16,18,14,15,11,11,14,13,14,14,16,17,18, + 17,17,17,18,14,13,13,12,17,15,17,16,18,17,18,18, + 18,12,13,12,13,16,14,18,18, +}; + +static float _vq_quantthresh__44c0_sm_p9_1[] = { + -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, 25.5, + 42.5, 59.5, 76.5, 93.5, +}; + +static long _vq_quantmap__44c0_sm_p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p9_1 = { + _vq_quantthresh__44c0_sm_p9_1, + _vq_quantmap__44c0_sm_p9_1, + 13, + 13 +}; + +static static_codebook _44c0_sm_p9_1 = { + 2, 169, + _vq_lengthlist__44c0_sm_p9_1, + 1, -522616832, 1620115456, 4, 0, + _vq_quantlist__44c0_sm_p9_1, + NULL, + &_vq_auxt__44c0_sm_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c0_sm_p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c0_sm_p9_2[] = { + 2, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9,10, 6, 6, 7, 7, 8, 7, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9,10, 6, 6, 7, 7, 7, 7, 8, 8, 9, 8, 9, 9, 9, + 9, 9, 9,10, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,10,10,10, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,11,10,10, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,10, 9,10,11,10, 9, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,10,10,11,11, 9, 8, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,10,11,10,11,11, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10,10,11,11, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,11,10,10,11,11, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10,10,10, + 11,11,10, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,11,10, + 11,11,10,10, 9,10,10,10, 9, 9, 9, 9, 9, 9,10,10, + 10,11,11,11,11,10,10, 9, 9, 9, 9, 9, 9, 9, 9,10, + 11,10,11,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,11,11,11,10,11,11, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44c0_sm_p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c0_sm_p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c0_sm_p9_2 = { + _vq_quantthresh__44c0_sm_p9_2, + _vq_quantmap__44c0_sm_p9_2, + 17, + 17 +}; + +static static_codebook _44c0_sm_p9_2 = { + 2, 289, + _vq_lengthlist__44c0_sm_p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c0_sm_p9_2, + NULL, + &_vq_auxt__44c0_sm_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c0_sm_short[] = { + 5, 7,12,12,13,13,13,15,16,18, 4, 2,11, 5, 9, 7, + 9,12,13,14,13, 7, 8, 9, 8, 9,11,13,19,19, 9, 4, + 12, 5,10, 7, 9,12,15,14,11, 6, 7, 7, 5, 6, 8,11, + 15,17,10, 5, 8, 6, 6, 5, 6, 9,14,14,10, 5, 9, 7, + 7, 6, 6, 9,12,14,12, 6,10, 7, 7, 7, 8, 9,13,14, + 14, 7, 9, 7, 5, 5, 6, 9,11,13,14, 9,10, 9, 6, 6, + 7, 8,10,13, +}; + +static static_codebook _huff_book__44c0_sm_short = { + 2, 100, + _huff_lengthlist__44c0_sm_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c1_s_long[] = { + 3, 4,19, 9,10, 9, 9,10,11,12, 4, 2,19, 5, 6, 6, + 8,10,12,11,19,19,19,19,19,19,19,19,18,18, 8, 4, + 18, 6, 8, 7,10,13,14,13,10, 5,18, 7, 4, 6, 7,10, + 12,13, 9, 6,18, 7, 5, 6, 7,10,12,13, 9, 7,18, 9, + 7, 6, 6, 7,10,13, 9, 9,18,12, 9, 8, 6, 6, 9,12, + 10,12,18,15,12,11, 9, 8, 8,11,11,14,18,17,15,13, + 12, 9,10,12, +}; + +static static_codebook _huff_book__44c1_s_long = { + 2, 100, + _huff_lengthlist__44c1_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c1_s_p1_0[] = { + 2, 4, 4, 0, 0, 0, 0, 0, 0, 5, 7, 6, 0, 0, 0, 0, + 0, 0, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, + 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 7, 7, 0, 0, 0, 0, + 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, + 0, 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, 0, + 0, 0, 0, 0, 8, 9, 8, 0, 0, 0, 0, 0, 0, 8, 9, 9, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 8, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, + 0, 0, 0, 0, 0, 8, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 8, 9, 9, + 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c1_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p1_0 = { + _vq_quantthresh__44c1_s_p1_0, + _vq_quantmap__44c1_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c1_s_p1_0 = { + 8, 6561, + _vq_lengthlist__44c1_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c1_s_p1_0, + NULL, + &_vq_auxt__44c1_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c1_s_p2_0[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c1_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p2_0 = { + _vq_quantthresh__44c1_s_p2_0, + _vq_quantmap__44c1_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c1_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c1_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c1_s_p2_0, + NULL, + &_vq_auxt__44c1_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c1_s_p3_0[] = { + 2, 3, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 4, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c1_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p3_0 = { + _vq_quantthresh__44c1_s_p3_0, + _vq_quantmap__44c1_s_p3_0, + 5, + 5 +}; + +static static_codebook _44c1_s_p3_0 = { + 4, 625, + _vq_lengthlist__44c1_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c1_s_p3_0, + NULL, + &_vq_auxt__44c1_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c1_s_p4_0[] = { + 1, 3, 2, 7, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 8, 9, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c1_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p4_0 = { + _vq_quantthresh__44c1_s_p4_0, + _vq_quantmap__44c1_s_p4_0, + 9, + 9 +}; + +static static_codebook _44c1_s_p4_0 = { + 2, 81, + _vq_lengthlist__44c1_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c1_s_p4_0, + NULL, + &_vq_auxt__44c1_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c1_s_p5_0[] = { + 1, 3, 3, 5, 5, 6, 6, 8, 8, 0, 0, 0, 7, 7, 8, 7, + 9, 9, 0, 0, 0, 7, 7, 8, 8, 9, 9, 0, 0, 0, 7, 7, + 8, 8,10,10, 0, 0, 0, 7, 7, 8, 8,10,10, 0, 0, 0, + 8, 8, 9, 9,10,10, 0, 0, 0, 8, 8, 9, 9,10,10, 0, + 0, 0,10,10, 9, 9,11,11, 0, 0, 0, 0, 0, 9, 9,11, + 11, +}; + +static float _vq_quantthresh__44c1_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c1_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p5_0 = { + _vq_quantthresh__44c1_s_p5_0, + _vq_quantmap__44c1_s_p5_0, + 9, + 9 +}; + +static static_codebook _44c1_s_p5_0 = { + 2, 81, + _vq_lengthlist__44c1_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c1_s_p5_0, + NULL, + &_vq_auxt__44c1_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c1_s_p6_0[] = { + 1, 3, 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11,11, + 11,11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,10, + 11,11,11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 8, 8, 9, 9,10,10,10,10, + 10,10,11,11,12,12, 0, 0, 0, 8, 8, 9, 9, 9,10,10, + 10,11,11,11,11,12,12, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,12,12,13,12, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 9, 9, + 10,10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, + 9,10, 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 10,10,10,10,11,11,11,11,12,12,13,13, 0, 0, 0, 0, + 0, 0, 0,10,10,11,11,11,11,12,12,13,13, 0, 0, 0, + 0, 0, 0, 0,11,11,11,11,12,12,13,12,13,13, 0, 0, + 0, 0, 0, 0, 0,11,11,11,11,12,12,13,13,13,13, 0, + 0, 0, 0, 0, 0, 0,11,11,11,12,12,12,13,13,14,14, + 0, 0, 0, 0, 0, 0, 0, 0, 0,11,11,12,12,13,13,14, + 14, +}; + +static float _vq_quantthresh__44c1_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c1_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p6_0 = { + _vq_quantthresh__44c1_s_p6_0, + _vq_quantmap__44c1_s_p6_0, + 17, + 17 +}; + +static static_codebook _44c1_s_p6_0 = { + 2, 289, + _vq_lengthlist__44c1_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c1_s_p6_0, + NULL, + &_vq_auxt__44c1_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c1_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 5, 7, 7,10, 9, 9,10, 9, 9, 6,10,10,10,10, + 10,11,10,10, 6, 9, 9,10,11,10,10, 9, 9, 6, 9, 9, + 10,10,10,10, 9, 9, 7,10,10,11,10,10,11,11,11, 6, + 9, 9,10, 9, 9,11,10,10, 6, 9, 9,10, 9, 9,11,10, + 10, +}; + +static float _vq_quantthresh__44c1_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c1_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p7_0 = { + _vq_quantthresh__44c1_s_p7_0, + _vq_quantmap__44c1_s_p7_0, + 3, + 3 +}; + +static static_codebook _44c1_s_p7_0 = { + 4, 81, + _vq_lengthlist__44c1_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c1_s_p7_0, + NULL, + &_vq_auxt__44c1_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c1_s_p7_1[] = { + 2, 3, 3, 6, 6, 7, 7, 7, 7, 8, 8,10,10,10, 6, 6, + 7, 7, 8, 8, 8, 8,10,10,10, 6, 6, 7, 7, 8, 8, 8, + 8,10,10,10, 7, 7, 7, 7, 8, 8, 8, 8,10,10,10, 7, + 7, 7, 7, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 9, 9, 8, 8, 8, 8,10,10, + 10,10,10, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c1_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c1_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p7_1 = { + _vq_quantthresh__44c1_s_p7_1, + _vq_quantmap__44c1_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c1_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c1_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c1_s_p7_1, + NULL, + &_vq_auxt__44c1_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c1_s_p8_0[] = { + 1, 4, 4, 6, 6, 7, 7, 7, 7, 9, 8,10,10, 6, 5, 5, + 7, 7, 8, 8, 8, 8, 9, 9,10,10, 7, 6, 6, 7, 7, 8, + 8, 8, 8, 9, 9,10,10, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,12,12, 9, 9, 9, 9,10,10,10,11,12,11, 0,12, + 13, 9, 8, 9, 9,10,10,11,11,11,11, 0, 0, 0, 9, 9, + 9, 9,10,10,11,11,12,12, 0, 0, 0,10,10, 9, 9,10, + 10,11,11,12,12, 0, 0, 0,13,14,10,10,11,11,12,12, + 12,13, 0, 0, 0,14,14,10,10,11,10,12,12,13,13, 0, + 0, 0, 0, 0,12,11,11,11,12,12,13,13, 0, 0, 0, 0, + 0,12,12,11,10,12,11,14,13, +}; + +static float _vq_quantthresh__44c1_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c1_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p8_0 = { + _vq_quantthresh__44c1_s_p8_0, + _vq_quantmap__44c1_s_p8_0, + 13, + 13 +}; + +static static_codebook _44c1_s_p8_0 = { + 2, 169, + _vq_lengthlist__44c1_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c1_s_p8_0, + NULL, + &_vq_auxt__44c1_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c1_s_p8_1[] = { + 2, 3, 3, 5, 5, 6, 6, 6, 5, 5, 6, 6, 6, 5, 5, 6, + 6, 6, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c1_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c1_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p8_1 = { + _vq_quantthresh__44c1_s_p8_1, + _vq_quantmap__44c1_s_p8_1, + 5, + 5 +}; + +static static_codebook _44c1_s_p8_1 = { + 2, 25, + _vq_lengthlist__44c1_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c1_s_p8_1, + NULL, + &_vq_auxt__44c1_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c1_s_p9_0[] = { + 1, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 7, 6, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 6, 6, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 7, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 8, +}; + +static float _vq_quantthresh__44c1_s_p9_0[] = { + -1215.5, -994.5, -773.5, -552.5, -331.5, -110.5, 110.5, 331.5, + 552.5, 773.5, 994.5, 1215.5, +}; + +static long _vq_quantmap__44c1_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p9_0 = { + _vq_quantthresh__44c1_s_p9_0, + _vq_quantmap__44c1_s_p9_0, + 13, + 13 +}; + +static static_codebook _44c1_s_p9_0 = { + 2, 169, + _vq_lengthlist__44c1_s_p9_0, + 1, -514541568, 1627103232, 4, 0, + _vq_quantlist__44c1_s_p9_0, + NULL, + &_vq_auxt__44c1_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c1_s_p9_1[] = { + 1, 4, 4, 6, 6, 7, 7, 9, 9,12,12,13,12, 6, 5, 5, + 7, 7, 8, 8,10,10,11,12,13,13, 6, 5, 5, 7, 7, 8, + 8, 9, 9,11,11,12,12,15, 7, 7, 7, 7, 9, 9,11,11, + 13,13,14,13,15, 7, 7, 8, 7, 9, 9,10,10,12,11,14, + 12,15,10,10, 8, 8,10,10,12,11,12,13,14,13,15,10, + 10, 8, 8,11,10,11,11,13,12,13,15,14,15,15,10,10, + 10,10,12,13,13,12,15,14,15,15,13,10,10,11,11,13, + 13,13,12,13,13,15,15,15,14,15,11,11,13,13,13,13, + 14,15,15,15,15,13,13,11,11,12,13,13,14,13,15,15, + 15,14,15,15,13,12,12,11,13,13,15,15,15,15,15,15, + 14,13,12,13,12,15,13,15,15, +}; + +static float _vq_quantthresh__44c1_s_p9_1[] = { + -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, 25.5, + 42.5, 59.5, 76.5, 93.5, +}; + +static long _vq_quantmap__44c1_s_p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p9_1 = { + _vq_quantthresh__44c1_s_p9_1, + _vq_quantmap__44c1_s_p9_1, + 13, + 13 +}; + +static static_codebook _44c1_s_p9_1 = { + 2, 169, + _vq_lengthlist__44c1_s_p9_1, + 1, -522616832, 1620115456, 4, 0, + _vq_quantlist__44c1_s_p9_1, + NULL, + &_vq_auxt__44c1_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_s_p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c1_s_p9_2[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 8, 9, 8, 9, 9, + 9,10,10,10, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9,10,10,10, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9,10,10,10, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9,10,10,11,10, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9,10, 9, 9, 9,10,10,10, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,11, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10, 9,10,10,10,11,11, 9, 8, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10,10,11,11,11,11, 9, 9, + 9, 9, 9, 9,10, 9,10, 9, 9, 9,10,10,10,11,10, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,11,11,11, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10,10,10, + 11,11,10, 9, 9,10, 9, 9, 9, 9, 9, 9, 9,10,10,10, + 11,11,11,10, 9,10,10,10, 9, 9, 9, 9,10, 9,10,10, + 10,10,11,11,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, + 11,10,10,10,10,10,10, 9, 9, 9, 9,10, 9, 9, 9, 9, + 10,10,10,11,11,10,10,11,11, 9, 9,10, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44c1_s_p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c1_s_p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_s_p9_2 = { + _vq_quantthresh__44c1_s_p9_2, + _vq_quantmap__44c1_s_p9_2, + 17, + 17 +}; + +static static_codebook _44c1_s_p9_2 = { + 2, 289, + _vq_lengthlist__44c1_s_p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c1_s_p9_2, + NULL, + &_vq_auxt__44c1_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c1_s_short[] = { + 5, 7,17,13,12,12,14,16,15,16, 4, 2,17, 5, 7, 6, + 8,11,14,13,16,16,16,16,16,16,16,16,16,16,10, 4, + 16, 4, 8, 6, 7,11,14,14,10, 5,16, 6, 5, 6, 8,12, + 15,15,10, 5,16, 5, 6, 5, 7,10,14,15,11, 6,16, 7, + 8, 7, 7,10,14,14,13, 8,16, 8, 7, 7, 8,10,12,13, + 12, 8,16, 7, 5, 5, 6, 8,11,13,13, 9,16, 9, 6, 6, + 7, 8,10,13, +}; + +static static_codebook _huff_book__44c1_s_short = { + 2, 100, + _huff_lengthlist__44c1_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c1_sm_long[] = { + 3, 4,10, 9,11, 9,10,11,11,13, 4, 2,11, 5, 7, 7, + 8,10,12,13,10,10, 7,12, 8, 9, 9,10,12,13, 8, 5, + 13, 6, 9, 7,10,12,15,15,10, 6, 7, 8, 5, 5, 7,10, + 12,13, 9, 6, 8, 7, 5, 5, 6, 9,11,12, 9, 8, 9, 9, + 6, 5, 5, 7,10,13,10,10,10,12, 9, 7, 6, 7, 9,13, + 10,13,11,17,11,11, 9, 8, 9,12,12,16,14,17,15,14, + 12,10,10,12, +}; + +static static_codebook _huff_book__44c1_sm_long = { + 2, 100, + _huff_lengthlist__44c1_sm_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c1_sm_p1_0[] = { + 1, 5, 5, 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 8, 7, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 8, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 7, 0, 0, 0, 0, + 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 9, 9,10, 0, 0, 0, 0, 0, 0, 9,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, 0, + 0, 0, 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 9,10, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_sm_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c1_sm_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p1_0 = { + _vq_quantthresh__44c1_sm_p1_0, + _vq_quantmap__44c1_sm_p1_0, + 3, + 3 +}; + +static static_codebook _44c1_sm_p1_0 = { + 8, 6561, + _vq_lengthlist__44c1_sm_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c1_sm_p1_0, + NULL, + &_vq_auxt__44c1_sm_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c1_sm_p2_0[] = { + 1, 5, 5, 0, 0, 0, 5, 6, 0, 0, 0, 5, 6, 0, 0, 0, + 8, 8, 0, 0, 0, 0, 0, 0, 0, 5, 7, 6, 0, 0, 0, 8, + 8, 0, 0, 0, 8, 8, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 5, 6, 7, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, 0, 0, 8, 8, 0, 0, + 0, 8, 8, 0, 0, 0,10, 9, 0, 0, 0, 0, 0, 0, 0, 6, + 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 9, + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, + 0, 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, + 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0,10,10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 8,10,10, 0, 0, 0,10,10, 0, 0, 0, 9,10, 0, 0, 0, + 10,10, 0, 0, 0, 0, 0, 0, 0, 8,10,10, 0, 0, 0,10, + 10, 0, 0, 0,10, 9, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_sm_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c1_sm_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p2_0 = { + _vq_quantthresh__44c1_sm_p2_0, + _vq_quantmap__44c1_sm_p2_0, + 5, + 5 +}; + +static static_codebook _44c1_sm_p2_0 = { + 4, 625, + _vq_lengthlist__44c1_sm_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c1_sm_p2_0, + NULL, + &_vq_auxt__44c1_sm_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c1_sm_p3_0[] = { + 2, 3, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 7, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 8, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_sm_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c1_sm_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p3_0 = { + _vq_quantthresh__44c1_sm_p3_0, + _vq_quantmap__44c1_sm_p3_0, + 5, + 5 +}; + +static static_codebook _44c1_sm_p3_0 = { + 4, 625, + _vq_lengthlist__44c1_sm_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c1_sm_p3_0, + NULL, + &_vq_auxt__44c1_sm_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c1_sm_p4_0[] = { + 1, 3, 3, 7, 7, 0, 0, 0, 0, 0, 5, 5, 6, 6, 0, 0, + 0, 0, 0, 5, 5, 7, 7, 0, 0, 0, 0, 0, 7, 7, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 8, 9, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c1_sm_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c1_sm_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p4_0 = { + _vq_quantthresh__44c1_sm_p4_0, + _vq_quantmap__44c1_sm_p4_0, + 9, + 9 +}; + +static static_codebook _44c1_sm_p4_0 = { + 2, 81, + _vq_lengthlist__44c1_sm_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c1_sm_p4_0, + NULL, + &_vq_auxt__44c1_sm_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c1_sm_p5_0[] = { + 2, 3, 3, 5, 5, 6, 6, 8, 8, 0, 5, 5, 6, 6, 7, 7, + 9, 9, 0, 5, 5, 6, 6, 7, 7, 9, 9, 0, 6, 6, 7, 7, + 7, 7, 9, 9, 0, 0, 0, 7, 7, 7, 7, 9, 9, 0, 0, 0, + 7, 7, 8, 8,10,10, 0, 0, 0, 7, 7, 8, 8,10,10, 0, + 0, 0, 9, 9, 9, 9,10,10, 0, 0, 0, 0, 0, 8, 8,10, + 10, +}; + +static float _vq_quantthresh__44c1_sm_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c1_sm_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p5_0 = { + _vq_quantthresh__44c1_sm_p5_0, + _vq_quantmap__44c1_sm_p5_0, + 9, + 9 +}; + +static static_codebook _44c1_sm_p5_0 = { + 2, 81, + _vq_lengthlist__44c1_sm_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c1_sm_p5_0, + NULL, + &_vq_auxt__44c1_sm_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c1_sm_p6_0[] = { + 2, 3, 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 0, 5, 5, 6, 6, 8, 8, 9, 9, 9, 9,10,10,10,10, + 11,11, 0, 5, 5, 6, 6, 8, 8, 9, 9, 9, 9,10,10,10, + 10,11,11, 0, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9, + 10,10,11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, + 9,10,10,11,11,12,12, 0, 0, 0, 8, 8, 8, 8,10,10, + 10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 8, 8,10, + 10,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 8, 8, + 9, 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 8, + 8, 9, 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 9, 9,10,10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, + 0, 0, 0,10,10,10,10,11,11,12,12,13,13, 0, 0, 0, + 0, 0, 0, 0,10,10,11,11,11,11,13,12,13,13, 0, 0, + 0, 0, 0, 0, 0,10,10,11,11,11,11,13,13,13,13, 0, + 0, 0, 0, 0, 0, 0,11,11,11,11,12,12,13,13,14,14, + 0, 0, 0, 0, 0, 0, 0, 0, 0,11,11,12,12,13,12,14, + 14, +}; + +static float _vq_quantthresh__44c1_sm_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c1_sm_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p6_0 = { + _vq_quantthresh__44c1_sm_p6_0, + _vq_quantmap__44c1_sm_p6_0, + 17, + 17 +}; + +static static_codebook _44c1_sm_p6_0 = { + 2, 289, + _vq_lengthlist__44c1_sm_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c1_sm_p6_0, + NULL, + &_vq_auxt__44c1_sm_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c1_sm_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 5, 7, 7,10, 9, 9,10, 9, 9, 6,10,10,10,10, + 10,10,10,10, 6, 9, 9,10,10,10,10, 9, 9, 6, 9, 9, + 10,10,10,10,10, 9, 7,10,10,11,10,10,11,11,11, 6, + 9, 9,10, 9, 9,11,10,10, 6, 9, 9,10, 9, 9,11,10, + 10, +}; + +static float _vq_quantthresh__44c1_sm_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c1_sm_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p7_0 = { + _vq_quantthresh__44c1_sm_p7_0, + _vq_quantmap__44c1_sm_p7_0, + 3, + 3 +}; + +static static_codebook _44c1_sm_p7_0 = { + 4, 81, + _vq_lengthlist__44c1_sm_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c1_sm_p7_0, + NULL, + &_vq_auxt__44c1_sm_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c1_sm_p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 7, 7, 8, 8,10, 5, 5, 6, 6, + 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, 7, 7, 8, 8, 8, + 8,10, 7, 6, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, + 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, 9, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 9, 9, 8, 8, 8, 8,10,10, + 10,10,10, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c1_sm_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c1_sm_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p7_1 = { + _vq_quantthresh__44c1_sm_p7_1, + _vq_quantmap__44c1_sm_p7_1, + 11, + 11 +}; + +static static_codebook _44c1_sm_p7_1 = { + 2, 121, + _vq_lengthlist__44c1_sm_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c1_sm_p7_1, + NULL, + &_vq_auxt__44c1_sm_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c1_sm_p8_0[] = { + 1, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10, 6, 5, 5, + 7, 7, 8, 8, 8, 8, 9, 9,10,10, 7, 5, 6, 7, 7, 8, + 8, 8, 8, 9, 9,11,10, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,12,12, 9, 9, 9, 9,10,10,11,11,12,11, 0,13, + 13, 9, 9, 9, 9,10,10,11,11,12,12, 0, 0, 0, 9, 9, + 9, 9,10,10,11,12,12,12, 0, 0, 0,10,10, 9, 9,11, + 11,12,12,13,13, 0, 0, 0,13,13,10,10,11,11,12,12, + 13,13, 0, 0, 0,14,14,10,10,11,10,12,12,13,13, 0, + 0, 0, 0, 0,12,11,11,11,12,12,14,13, 0, 0, 0, 0, + 0,12,12,11,10,12,12,14,13, +}; + +static float _vq_quantthresh__44c1_sm_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c1_sm_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p8_0 = { + _vq_quantthresh__44c1_sm_p8_0, + _vq_quantmap__44c1_sm_p8_0, + 13, + 13 +}; + +static static_codebook _44c1_sm_p8_0 = { + 2, 169, + _vq_lengthlist__44c1_sm_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c1_sm_p8_0, + NULL, + &_vq_auxt__44c1_sm_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c1_sm_p8_1[] = { + 2, 4, 4, 4, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c1_sm_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c1_sm_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p8_1 = { + _vq_quantthresh__44c1_sm_p8_1, + _vq_quantmap__44c1_sm_p8_1, + 5, + 5 +}; + +static static_codebook _44c1_sm_p8_1 = { + 2, 25, + _vq_lengthlist__44c1_sm_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c1_sm_p8_1, + NULL, + &_vq_auxt__44c1_sm_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c1_sm_p9_0[] = { + 1, 5, 5,11,11,11,11,11,11,11,11,11,11, 4, 5, 5, + 11,10,11,11,11,11,11,11,11,11, 4, 5, 4,11,11,11, + 11,11,11,11,11,11,11,11, 6, 7,11, 9,11,11,11,11, + 11,11,11,11,11, 6, 7, 9, 8,11,11,11,11,11,11,11, + 11,11, 9, 9, 7, 8,11,11,11,11,11,11,11,11,11,10, + 10, 9,10,11,11,11,11,11,11,11,11,11,11,11, 8,11, + 10,10,11,11,11,11,11,11,11,11,11, 9,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11, 9,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,10,10,10,10,10, +}; + +static float _vq_quantthresh__44c1_sm_p9_0[] = { + -1215.5, -994.5, -773.5, -552.5, -331.5, -110.5, 110.5, 331.5, + 552.5, 773.5, 994.5, 1215.5, +}; + +static long _vq_quantmap__44c1_sm_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p9_0 = { + _vq_quantthresh__44c1_sm_p9_0, + _vq_quantmap__44c1_sm_p9_0, + 13, + 13 +}; + +static static_codebook _44c1_sm_p9_0 = { + 2, 169, + _vq_lengthlist__44c1_sm_p9_0, + 1, -514541568, 1627103232, 4, 0, + _vq_quantlist__44c1_sm_p9_0, + NULL, + &_vq_auxt__44c1_sm_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c1_sm_p9_1[] = { + 1, 4, 4, 6, 6, 7, 7,10,10,12,12,13,13, 6, 5, 5, + 7, 7, 8, 8,10,10,12,12,14,13, 6, 5, 5, 7, 7, 8, + 8,10,10,12,12,13,13,16, 7, 7, 7, 7, 9, 9,11,11, + 13,14,13,15,18, 7, 6, 8, 7, 9, 9,11,10,12,12,14, + 12,19,10,10, 8, 8,10,10,12,11,13,15,15,15,17,11, + 11, 8, 7,11,10,12,12,13,13,14,15,16,17,17,10,11, + 10,10,13,13,14,13,16,15,17,17,16,10,10,11,11,14, + 14,16,13,14,14,17,19,17,15,14,11,11,13,13,15,14, + 16,15,17,16,17,14,14,11,11,14,13,14,14,14,15,17, + 16,16,16,17,13,13,13,13,16,14,17,15,16,17,18,18, + 17,13,13,13,13,15,15,16,16, +}; + +static float _vq_quantthresh__44c1_sm_p9_1[] = { + -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, 25.5, + 42.5, 59.5, 76.5, 93.5, +}; + +static long _vq_quantmap__44c1_sm_p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p9_1 = { + _vq_quantthresh__44c1_sm_p9_1, + _vq_quantmap__44c1_sm_p9_1, + 13, + 13 +}; + +static static_codebook _44c1_sm_p9_1 = { + 2, 169, + _vq_lengthlist__44c1_sm_p9_1, + 1, -522616832, 1620115456, 4, 0, + _vq_quantlist__44c1_sm_p9_1, + NULL, + &_vq_auxt__44c1_sm_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c1_sm_p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c1_sm_p9_2[] = { + 2, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9,10, 6, 6, 7, 7, 8, 7, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9,10, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9,10, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 7, 7, 8, 8, 9, 8, 9, 9, 9, + 9, 9, 9, 9, 9,11,11,11, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10,10,10, 7, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,10, 9,10,10,10, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,10,10,11,11, 8, 8, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9,10,11,10,11,11, 9, 9, + 9, 9, 9, 9, 9,10,10, 9, 9, 9,10,11,10,11,11, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,11,11, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,11,10,11, + 11,10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10, + 11,11,11,11, 9,10,10,10, 9, 9, 9, 9,10, 9,10,10, + 10,11,11,11,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9,10, + 11,10,11,10,11,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,11,11,10,10,10,11, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44c1_sm_p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c1_sm_p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c1_sm_p9_2 = { + _vq_quantthresh__44c1_sm_p9_2, + _vq_quantmap__44c1_sm_p9_2, + 17, + 17 +}; + +static static_codebook _44c1_sm_p9_2 = { + 2, 289, + _vq_lengthlist__44c1_sm_p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c1_sm_p9_2, + NULL, + &_vq_auxt__44c1_sm_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c1_sm_short[] = { + 4, 7,13,13,13,13,14,16,16,19, 4, 2,13, 5, 8, 7, + 9,12,14,13,16, 9,10,10, 9,10,11,13,17,19, 9, 4, + 13, 5,10, 6, 8,11,15,15,11, 6, 9, 7, 6, 6, 8,11, + 14,18,11, 5, 9, 6, 6, 5, 6, 9,13,15,12, 6, 9, 7, + 6, 5, 5, 8,12,15,13, 7,10, 8, 6, 6, 7, 9,12,13, + 13, 9,11, 9, 6, 5, 6, 8,11,13,13,11,13,11, 7, 6, + 7, 8,10,12, +}; + +static static_codebook _huff_book__44c1_sm_short = { + 2, 100, + _huff_lengthlist__44c1_sm_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c2_s_long[] = { + 5, 5,12,10,11,10,10,10,11,13, 5, 1, 9, 5, 8, 7, + 8,10,13,13,12, 9, 8,11, 7, 8, 9,11,13,15, 9, 5, + 12, 6, 9, 8,10,12,15,14,12, 7, 6, 8, 5, 6, 7,10, + 12,13,10, 7, 8, 7, 6, 6, 7, 9,12,12,10, 8, 9, 9, + 7, 6, 6, 7,10,11,10, 9,10,11, 9, 8, 6, 6, 8,11, + 10,12,13,15,11,10, 8, 7, 8,11,11,13,16,16,14,13, + 11, 9, 9,10, +}; + +static static_codebook _huff_book__44c2_s_long = { + 2, 100, + _huff_lengthlist__44c2_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c2_s_p1_0[] = { + 2, 4, 4, 0, 0, 0, 0, 0, 0, 5, 7, 6, 0, 0, 0, 0, + 0, 0, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, + 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 7, 7, 0, 0, 0, 0, + 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, + 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, 0, + 0, 0, 0, 0, 8, 9, 8, 0, 0, 0, 0, 0, 0, 8, 9,10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 8, 0, 0, 0, 0, 0, 0, 8,10, 9, 0, + 0, 0, 0, 0, 0, 8, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 8, 9, 9, + 0, 0, 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c2_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c2_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p1_0 = { + _vq_quantthresh__44c2_s_p1_0, + _vq_quantmap__44c2_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c2_s_p1_0 = { + 8, 6561, + _vq_lengthlist__44c2_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c2_s_p1_0, + NULL, + &_vq_auxt__44c2_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c2_s_p2_0[] = { + 1, 4, 4, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 4, 6, 6, 0, 0, 0, 8, + 8, 0, 0, 0, 8, 8, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, + 0, 0, 4, 6, 6, 0, 0, 0, 8, 9, 0, 0, 0, 8, 8, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0,11,10, 0, 0, + 0,11,11, 0, 0, 0,11,11, 0, 0, 0, 0, 0, 0, 0, 7, + 8, 8, 0, 0, 0,10,11, 0, 0, 0,11,11, 0, 0, 0,11, + 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 8, 8, 0, 0, 0,11,11, 0, 0, 0,11,11, + 0, 0, 0,11,11, 0, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, + 0, 0,11,11, 0, 0, 0,11,11, 0, 0, 0,11,11, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 9, 9, 0, 0, 0,11,12, 0, 0, 0,11,12, 0, 0, 0, + 12,11, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0,12, + 11, 0, 0, 0,11,11, 0, 0, 0,11,11, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c2_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c2_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p2_0 = { + _vq_quantthresh__44c2_s_p2_0, + _vq_quantmap__44c2_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c2_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c2_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c2_s_p2_0, + NULL, + &_vq_auxt__44c2_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c2_s_p3_0[] = { + 2, 4, 3, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 5, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c2_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c2_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p3_0 = { + _vq_quantthresh__44c2_s_p3_0, + _vq_quantmap__44c2_s_p3_0, + 5, + 5 +}; + +static static_codebook _44c2_s_p3_0 = { + 4, 625, + _vq_lengthlist__44c2_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c2_s_p3_0, + NULL, + &_vq_auxt__44c2_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c2_s_p4_0[] = { + 1, 3, 3, 6, 6, 0, 0, 0, 0, 0, 6, 6, 6, 6, 0, 0, + 0, 0, 0, 6, 6, 6, 6, 0, 0, 0, 0, 0, 6, 6, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 7, 8, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c2_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c2_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p4_0 = { + _vq_quantthresh__44c2_s_p4_0, + _vq_quantmap__44c2_s_p4_0, + 9, + 9 +}; + +static static_codebook _44c2_s_p4_0 = { + 2, 81, + _vq_lengthlist__44c2_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c2_s_p4_0, + NULL, + &_vq_auxt__44c2_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c2_s_p5_0[] = { + 1, 3, 3, 6, 6, 6, 6, 8, 8, 0, 7, 7, 7, 7, 8, 8, + 9, 9, 0, 7, 7, 7, 7, 8, 8, 9, 9, 0, 7, 7, 8, 7, + 8, 8,10,10, 0, 0, 0, 8, 8, 8, 8,10,10, 0, 0, 0, + 8, 8, 9, 9,10,10, 0, 0, 0, 8, 8, 9, 9,10,10, 0, + 0, 0,10,10, 9, 9,11,11, 0, 0, 0, 0, 0, 9, 9,11, + 11, +}; + +static float _vq_quantthresh__44c2_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c2_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p5_0 = { + _vq_quantthresh__44c2_s_p5_0, + _vq_quantmap__44c2_s_p5_0, + 9, + 9 +}; + +static static_codebook _44c2_s_p5_0 = { + 2, 81, + _vq_lengthlist__44c2_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c2_s_p5_0, + NULL, + &_vq_auxt__44c2_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c2_s_p6_0[] = { + 1, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 0, 7, 7, 7, 7, 8, 8, 9, 9, 9, 9,10,10,10,10, + 11,11, 0, 7, 7, 7, 7, 8, 8, 9, 9, 9, 9,10,10,10, + 10,11,11, 0, 7, 7, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,11,11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 8, 8, 9, 9, 9, 9, 9, 9, + 10,10,11,11,12,12, 0, 0, 0, 8, 8, 9, 9, 9, 9, 9, + 9,10,11,11,11,12,12, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,11,12,12,12, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 9, 9, + 9, 9,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 9, + 9, 9, 9,10,10,11,11,12,12,12,13, 0, 0, 0, 0, 0, + 10,10,10,10,10,11,11,11,12,12,13,13, 0, 0, 0, 0, + 0, 0, 0,10,10,10,10,11,11,12,12,13,13, 0, 0, 0, + 0, 0, 0, 0,11,11,11,11,12,12,12,12,13,13, 0, 0, + 0, 0, 0, 0, 0,11,11,11,11,12,11,12,12,13,13, 0, + 0, 0, 0, 0, 0, 0,11,11,11,11,12,12,13,12,14,13, + 0, 0, 0, 0, 0, 0, 0, 0, 0,11,11,12,12,12,13,13, + 14, +}; + +static float _vq_quantthresh__44c2_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c2_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p6_0 = { + _vq_quantthresh__44c2_s_p6_0, + _vq_quantmap__44c2_s_p6_0, + 17, + 17 +}; + +static static_codebook _44c2_s_p6_0 = { + 2, 289, + _vq_lengthlist__44c2_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c2_s_p6_0, + NULL, + &_vq_auxt__44c2_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c2_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 5, 7, 7,10, 9, 9,10, 9, 9, 6,10, 9,10,10, + 10,11,10,10, 6, 9, 9,10,10,10,11, 9, 9, 6, 9, 9, + 10,10,10,11, 9, 9, 7,10,10,11,10,10,11,11,10, 6, + 9, 9,10, 9, 9,11,10,10, 6, 9, 9,10, 9,10,11,10, + 10, +}; + +static float _vq_quantthresh__44c2_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c2_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p7_0 = { + _vq_quantthresh__44c2_s_p7_0, + _vq_quantmap__44c2_s_p7_0, + 3, + 3 +}; + +static static_codebook _44c2_s_p7_0 = { + 4, 81, + _vq_lengthlist__44c2_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c2_s_p7_0, + NULL, + &_vq_auxt__44c2_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c2_s_p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 7, 7, 7, 7,10, 6, 6, 6, 6, + 7, 7, 8, 7, 8, 8, 9, 6, 6, 6, 6, 7, 7, 7, 7, 7, + 7,10, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8,10, 9, 9, 6, + 6, 7, 7, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10, 9, 8, 8, 8, + 8, 8, 8,10,10,10, 9,10, 8, 8, 8, 8, 8, 8,10,10, + 10,10, 9, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c2_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c2_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p7_1 = { + _vq_quantthresh__44c2_s_p7_1, + _vq_quantmap__44c2_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c2_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c2_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c2_s_p7_1, + NULL, + &_vq_auxt__44c2_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c2_s_p8_0[] = { + 1, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10, 6, 5, 5, + 7, 7, 8, 8, 9, 8, 9, 9,10,10, 7, 5, 5, 7, 7, 8, + 8, 8, 9,10, 9,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9,10,10,10,11, + 11, 0,12,12, 9, 9,10,10,10,10,11,11,12,12, 0,13, + 12, 9, 9,10, 9,10,10,11,11,11,12, 0, 0, 0,10,10, + 10,10,11,11,11,11,12,12, 0, 0, 0,10,10, 9, 9,11, + 11,12,12,12,13, 0, 0, 0,13,13,10,10,11,11,12,12, + 13,13, 0, 0, 0,14,14,10,10,11,11,12,12,13,13, 0, + 0, 0, 0, 0,12,12,11,12,12,12,14,13, 0, 0, 0, 0, + 0,12,12,12,12,12,12,14,13, +}; + +static float _vq_quantthresh__44c2_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c2_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p8_0 = { + _vq_quantthresh__44c2_s_p8_0, + _vq_quantmap__44c2_s_p8_0, + 13, + 13 +}; + +static static_codebook _44c2_s_p8_0 = { + 2, 169, + _vq_lengthlist__44c2_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c2_s_p8_0, + NULL, + &_vq_auxt__44c2_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c2_s_p8_1[] = { + 2, 4, 4, 4, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c2_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c2_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p8_1 = { + _vq_quantthresh__44c2_s_p8_1, + _vq_quantmap__44c2_s_p8_1, + 5, + 5 +}; + +static static_codebook _44c2_s_p8_1 = { + 2, 25, + _vq_lengthlist__44c2_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c2_s_p8_1, + NULL, + &_vq_auxt__44c2_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c2_s_p9_0[] = { + 1, 5, 4,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 5, 7, 7, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 9, 6, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44c2_s_p9_0[] = { + -1215.5, -994.5, -773.5, -552.5, -331.5, -110.5, 110.5, 331.5, + 552.5, 773.5, 994.5, 1215.5, +}; + +static long _vq_quantmap__44c2_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p9_0 = { + _vq_quantthresh__44c2_s_p9_0, + _vq_quantmap__44c2_s_p9_0, + 13, + 13 +}; + +static static_codebook _44c2_s_p9_0 = { + 2, 169, + _vq_lengthlist__44c2_s_p9_0, + 1, -514541568, 1627103232, 4, 0, + _vq_quantlist__44c2_s_p9_0, + NULL, + &_vq_auxt__44c2_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c2_s_p9_1[] = { + 1, 4, 4, 6, 6, 7, 7, 9, 9,11,11,12,12, 6, 5, 5, + 7, 7, 8, 8,10,10,12,11,13,13, 6, 5, 5, 7, 7, 8, + 8, 9, 9,11,11,12,13,16, 7, 7, 8, 8, 9, 9,10,10, + 13,13,16,14,16, 7, 7, 8, 8, 9, 9,10,10,13,13,15, + 13,16,10,10, 8, 8,10,10,11,11,12,12,15,14,16,11, + 11, 8, 8,10,10,11,11,12,12,14,13,16,16,16,10,11, + 10,10,12,12,14,13,13,13,16,16,16,10, 9,10, 7,13, + 12,13,13,13,13,16,16,16,14,15,11,11,12,12,13,13, + 15,14,16,16,16,16,14,11, 9,12, 9,15,13,13,14,16, + 16,16,16,16,13,13,12,12,14,15,16,15,16,16,15,16, + 16,13,12,12,11,15,13,14,15, +}; + +static float _vq_quantthresh__44c2_s_p9_1[] = { + -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, 25.5, + 42.5, 59.5, 76.5, 93.5, +}; + +static long _vq_quantmap__44c2_s_p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p9_1 = { + _vq_quantthresh__44c2_s_p9_1, + _vq_quantmap__44c2_s_p9_1, + 13, + 13 +}; + +static static_codebook _44c2_s_p9_1 = { + 2, 169, + _vq_lengthlist__44c2_s_p9_1, + 1, -522616832, 1620115456, 4, 0, + _vq_quantlist__44c2_s_p9_1, + NULL, + &_vq_auxt__44c2_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c2_s_p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c2_s_p9_2[] = { + 2, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, + 9,10, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9,10, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9,10, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 7, 7, 8, 8, 9, 8, 9, 9, 9, + 9, 9, 9, 9, 9,10,11,10, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10,10,10, 7, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,11,11, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10, 8, 8, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10,11,11,10,11,10, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,11,10, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9,10,10,10,10,10,11,11, + 9, 9, 9, 9, 9, 9, 9, 9,10, 9, 9, 9,11,10,10,11, + 10,10,11, 9, 9,10,10, 9, 9,10,10, 9, 9,10,10,10, + 11,10,11,10,10, 9, 9,10, 9, 9, 9, 9, 9, 9,10,10, + 10,10,11,10,10, 9,10, 9, 9, 9, 9,10, 9, 9, 9,10, + 10,10,11,11,10,10,10,10, 9, 9,10, 9, 9, 9,10, 9, + 10,10,10,10,11,10,11,11,10, 9, 9, 9, 9, 9, 9, 9, + 10, +}; + +static float _vq_quantthresh__44c2_s_p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c2_s_p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c2_s_p9_2 = { + _vq_quantthresh__44c2_s_p9_2, + _vq_quantmap__44c2_s_p9_2, + 17, + 17 +}; + +static static_codebook _44c2_s_p9_2 = { + 2, 289, + _vq_lengthlist__44c2_s_p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c2_s_p9_2, + NULL, + &_vq_auxt__44c2_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c2_s_short[] = { + 9, 9,12,11,11,11,12,12,12,13, 6, 2, 9, 4, 7, 6, + 8,11,15,17,12, 7, 8, 9, 7, 9,10,13,15,17,11, 4, + 12, 4, 9, 5, 8,11,16,17,12, 6, 7, 6, 6, 6, 8,11, + 15,17,11, 5, 9, 5, 6, 5, 6,10,15,15,12, 7,11, 7, + 7, 6, 7,10,13,16,13, 8,11, 9, 8, 6, 7,10,12,16, + 13, 9, 9, 8, 5, 5, 6, 9,12,14,16,10, 9, 9, 6, 5, + 6, 8,11,14, +}; + +static static_codebook _huff_book__44c2_s_short = { + 2, 100, + _huff_lengthlist__44c2_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c3_s_long[] = { + 4, 5,11,10,12,10,10,10,11,12, 4, 2,11, 5,11, 6, + 7, 9,13,16,11,11, 7,11, 6, 8, 8, 9,11,12,10, 5, + 11, 6,10, 7, 9,11,16,16,14, 9, 6, 9, 4, 5, 7, 8, + 11,13,10, 6, 7, 7, 5, 5, 6, 8,10,11,10, 7, 8, 8, + 6, 6, 5, 7, 9,10,10, 9, 8,11, 8, 7, 6, 6, 7,10, + 10,12,10,14, 9, 9, 7, 7, 7, 9,10,14,11,16,12,12, + 9, 8, 8, 9, +}; + +static static_codebook _huff_book__44c3_s_long = { + 2, 100, + _huff_lengthlist__44c3_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c3_s_p1_0[] = { + 2, 4, 4, 0, 0, 0, 0, 0, 0, 5, 7, 6, 0, 0, 0, 0, + 0, 0, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, + 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 7, 7, 0, 0, 0, 0, + 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, + 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, 0, + 0, 0, 0, 0, 8, 9, 8, 0, 0, 0, 0, 0, 0, 8, 9,10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 8, 0, 0, 0, 0, 0, 0, 8,10, 9, 0, + 0, 0, 0, 0, 0, 8, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 8, 9, 9, + 0, 0, 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c3_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c3_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p1_0 = { + _vq_quantthresh__44c3_s_p1_0, + _vq_quantmap__44c3_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c3_s_p1_0 = { + 8, 6561, + _vq_lengthlist__44c3_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c3_s_p1_0, + NULL, + &_vq_auxt__44c3_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c3_s_p2_0[] = { + 2, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, + 8, 7, 0, 0, 0, 0, 0, 0, 0, 5, 6, 6, 0, 0, 0, 8, + 7, 0, 0, 0, 7, 7, 0, 0, 0,10, 9, 0, 0, 0, 0, 0, + 0, 0, 5, 5, 6, 0, 0, 0, 7, 8, 0, 0, 0, 7, 7, 0, + 0, 0, 9,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 7, 7, 0, 0, + 0, 7, 8, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 5, + 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 9, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 5, 8, 7, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, + 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 5, 7, 8, 0, + 0, 0, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 8, 9,10, 0, 0, 0, 9, 9, 0, 0, 0, 9, 9, 0, 0, 0, + 10,10, 0, 0, 0, 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 9, + 9, 0, 0, 0, 9, 9, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c3_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c3_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p2_0 = { + _vq_quantthresh__44c3_s_p2_0, + _vq_quantmap__44c3_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c3_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c3_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c3_s_p2_0, + NULL, + &_vq_auxt__44c3_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c3_s_p3_0[] = { + 2, 3, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 4, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 8, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c3_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c3_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p3_0 = { + _vq_quantthresh__44c3_s_p3_0, + _vq_quantmap__44c3_s_p3_0, + 5, + 5 +}; + +static static_codebook _44c3_s_p3_0 = { + 4, 625, + _vq_lengthlist__44c3_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c3_s_p3_0, + NULL, + &_vq_auxt__44c3_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c3_s_p4_0[] = { + 2, 3, 3, 6, 6, 0, 0, 0, 0, 0, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 4, 4, 6, 6, 0, 0, 0, 0, 0, 5, 5, 6, 6, + 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c3_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c3_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p4_0 = { + _vq_quantthresh__44c3_s_p4_0, + _vq_quantmap__44c3_s_p4_0, + 9, + 9 +}; + +static static_codebook _44c3_s_p4_0 = { + 2, 81, + _vq_lengthlist__44c3_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c3_s_p4_0, + NULL, + &_vq_auxt__44c3_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c3_s_p5_0[] = { + 2, 3, 3, 5, 5, 7, 7, 9, 8, 0, 4, 4, 7, 7, 7, 7, + 9, 9, 0, 5, 5, 6, 7, 7, 7, 9, 9, 0, 6, 6, 7, 7, + 7, 7, 9, 9, 0, 0, 0, 7, 7, 7, 7, 9, 9, 0, 0, 0, + 8, 8, 8, 8,10,10, 0, 0, 0, 8, 8, 8, 8,10,10, 0, + 0, 0,10,10, 9, 9,10,10, 0, 0, 0, 0, 0, 9, 9,10, + 10, +}; + +static float _vq_quantthresh__44c3_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c3_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p5_0 = { + _vq_quantthresh__44c3_s_p5_0, + _vq_quantmap__44c3_s_p5_0, + 9, + 9 +}; + +static static_codebook _44c3_s_p5_0 = { + 2, 81, + _vq_lengthlist__44c3_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c3_s_p5_0, + NULL, + &_vq_auxt__44c3_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c3_s_p6_0[] = { + 2, 3, 3, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 0, 5, 5, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11,11, + 11,11, 0, 5, 5, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11, + 11,11,11, 0, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 8, 8, 8, 8, 9, 9,10, 9, + 10,10,11,11,12,12, 0, 0, 0, 8, 8, 8, 8, 9, 9,10, + 9,10,10,11,11,12,12, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,12,11,12,12, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 8, 8, + 9, 9,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 8, + 8, 9, 9,10,10,11,11,12,12,13,12, 0, 0, 0, 0, 0, + 9, 9,10,10,11,10,11,11,12,12,13,13, 0, 0, 0, 0, + 0, 0, 0,10,10,10,10,11,11,12,12,13,13, 0, 0, 0, + 0, 0, 0, 0,10,10,11,11,12,12,12,12,13,13, 0, 0, + 0, 0, 0, 0, 0,10,10,11,11,11,11,12,12,13,13, 0, + 0, 0, 0, 0, 0, 0,11,11,12,12,12,12,12,13,13,13, + 0, 0, 0, 0, 0, 0, 0, 0, 0,11,11,12,12,12,12,13, + 13, +}; + +static float _vq_quantthresh__44c3_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c3_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p6_0 = { + _vq_quantthresh__44c3_s_p6_0, + _vq_quantmap__44c3_s_p6_0, + 17, + 17 +}; + +static static_codebook _44c3_s_p6_0 = { + 2, 289, + _vq_lengthlist__44c3_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c3_s_p6_0, + NULL, + &_vq_auxt__44c3_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c3_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 5, 7, 7,10, 9, 9,10, 9, 9, 6,10,10,10,10, + 10,11,10,10, 6, 9, 9,10,10,10,10, 9, 9, 6, 9, 9, + 10,10,10,10, 9, 9, 7,10,10,10,11,11,11,10,11, 6, + 9, 9,10,10, 9,11,10,10, 6, 9, 9,10, 9, 9,10,10, + 10, +}; + +static float _vq_quantthresh__44c3_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c3_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p7_0 = { + _vq_quantthresh__44c3_s_p7_0, + _vq_quantmap__44c3_s_p7_0, + 3, + 3 +}; + +static static_codebook _44c3_s_p7_0 = { + 4, 81, + _vq_lengthlist__44c3_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c3_s_p7_0, + NULL, + &_vq_auxt__44c3_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c3_s_p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, + 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, 7, 7, 8, 8, 8, + 8,10, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, + 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 9, 9, 8, 8, 8, 8,10,10, + 10,10,10, 8, 8, 8, 8, 8, 9, +}; + +static float _vq_quantthresh__44c3_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c3_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p7_1 = { + _vq_quantthresh__44c3_s_p7_1, + _vq_quantmap__44c3_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c3_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c3_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c3_s_p7_1, + NULL, + &_vq_auxt__44c3_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c3_s_p8_0[] = { + 1, 4, 4, 6, 6, 7, 8, 8, 8, 9, 9,10,10, 6, 5, 5, + 7, 7, 8, 8, 9, 9, 9, 9,10,10, 7, 5, 5, 7, 7, 8, + 8, 9, 9, 9,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,11,12, 9, 9, 9,10,10,10,11,11,11,12, 0,13, + 13, 9, 9, 9, 9,10,10,11,11,12,12, 0, 0, 0,10, 9, + 9, 9,10,11,11,11,12,12, 0, 0, 0,10,10, 9, 9,11, + 11,11,11,12,13, 0, 0, 0,13,13,10,10,11,11,12,12, + 13,13, 0, 0, 0,14,14,10,10,11,11,12,12,13,13, 0, + 0, 0, 0, 0,12,12,11,11,12,12,13,13, 0, 0, 0, 0, + 0,12,12,11,11,12,12,13,13, +}; + +static float _vq_quantthresh__44c3_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c3_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p8_0 = { + _vq_quantthresh__44c3_s_p8_0, + _vq_quantmap__44c3_s_p8_0, + 13, + 13 +}; + +static static_codebook _44c3_s_p8_0 = { + 2, 169, + _vq_lengthlist__44c3_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c3_s_p8_0, + NULL, + &_vq_auxt__44c3_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c3_s_p8_1[] = { + 2, 4, 4, 5, 5, 6, 5, 5, 5, 5, 6, 4, 5, 5, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c3_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c3_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p8_1 = { + _vq_quantthresh__44c3_s_p8_1, + _vq_quantmap__44c3_s_p8_1, + 5, + 5 +}; + +static static_codebook _44c3_s_p8_1 = { + 2, 25, + _vq_lengthlist__44c3_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c3_s_p8_1, + NULL, + &_vq_auxt__44c3_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c3_s_p9_0[] = { + 1, 4, 4,10,10,10,10,10,10,10,10,10,10, 5,10, 7, + 10,10,10,10,10,10,10,10,10,10, 5, 8, 6,10,10,10, + 10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44c3_s_p9_0[] = { + -1402.5, -1147.5, -892.5, -637.5, -382.5, -127.5, 127.5, 382.5, + 637.5, 892.5, 1147.5, 1402.5, +}; + +static long _vq_quantmap__44c3_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p9_0 = { + _vq_quantthresh__44c3_s_p9_0, + _vq_quantmap__44c3_s_p9_0, + 13, + 13 +}; + +static static_codebook _44c3_s_p9_0 = { + 2, 169, + _vq_lengthlist__44c3_s_p9_0, + 1, -514332672, 1627381760, 4, 0, + _vq_quantlist__44c3_s_p9_0, + NULL, + &_vq_auxt__44c3_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44c3_s_p9_1[] = { + 1, 4, 4, 6, 6, 7, 7, 9, 9,11,11,11,12,12,12, 6, + 5, 5, 7, 7, 8, 8,10, 9,11,11,13,12,13,14, 6, 5, + 5, 7, 7, 8, 8,10,10,11,11,12,12,13,13,17, 7, 7, + 8, 8, 9, 9,10,10,12,12,14,13,14,14,17, 8, 7, 8, + 7, 9, 9,10,10,12,12,13,13,13,14,17,11,11, 8, 8, + 10,10,11,11,12,12,13,13,15,14,17,11,11, 8, 7,10, + 10,11,11,12,12,13,14,14,13,17,17,17,10,11,10,10, + 12,12,13,12,13,13,14,14,17,16,16,10,10,11, 9,13, + 12,13,13,13,13,14,14,16,16,15,13,15,11,12,12,12, + 14,14,14,14,14,15,16,16,16,14,14,11, 9,12,10,13, + 13,14,14,14,14,16,16,16,16,16,12,13,12,12,13,14, + 14,14,15,15,15,16,16,15,16,13,11,13,10,14,12,15, + 14,16,14,15,16,16,16,16,15,15,13,13,13,13,14,14, + 16,16,16,16,16,15,16,16,14,13,12,13,13,14,16,16, + 16, +}; + +static float _vq_quantthresh__44c3_s_p9_1[] = { + -110.5, -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, + 25.5, 42.5, 59.5, 76.5, 93.5, 110.5, +}; + +static long _vq_quantmap__44c3_s_p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p9_1 = { + _vq_quantthresh__44c3_s_p9_1, + _vq_quantmap__44c3_s_p9_1, + 15, + 15 +}; + +static static_codebook _44c3_s_p9_1 = { + 2, 225, + _vq_lengthlist__44c3_s_p9_1, + 1, -522338304, 1620115456, 4, 0, + _vq_quantlist__44c3_s_p9_1, + NULL, + &_vq_auxt__44c3_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c3_s_p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c3_s_p9_2[] = { + 3, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, + 8,10, 5, 5, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9,10, 5, 5, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, + 9, 9, 9,10, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,10,10,10, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10,10,10, 8, 7, 8, 8, 9, 8, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,10, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,11,10, 8, 8, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,11, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,11,10, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10, + 10,10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, + 11,10,11,10, 9, 9, 9,10, 9, 9, 9, 9, 9, 9,10,10, + 10,10,11,11,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9,10, 9, + 10,10,10,10,11,10,11,10,10, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44c3_s_p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c3_s_p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c3_s_p9_2 = { + _vq_quantthresh__44c3_s_p9_2, + _vq_quantmap__44c3_s_p9_2, + 17, + 17 +}; + +static static_codebook _44c3_s_p9_2 = { + 2, 289, + _vq_lengthlist__44c3_s_p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c3_s_p9_2, + NULL, + &_vq_auxt__44c3_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c3_s_short[] = { + 10,10,13,12,13,12,12,12,12,13, 8, 3,11, 5,10, 5, + 7,11,14,16,11, 6, 9, 8, 7, 7, 9,12,15,16,12, 4, + 12, 4,10, 5, 8,12,15,16,12, 6, 8, 7, 5, 5, 7,11, + 14,15,11, 4, 9, 4, 6, 4, 6, 9,13,15,10, 6,10, 7, + 7, 5, 6, 9,13,15,12, 9,11, 9, 8, 6, 7, 9,12,15, + 13,11,10, 9, 6, 5, 5, 8,11,14,16,12,11,10, 6, 5, + 6, 8,10,14, +}; + +static static_codebook _huff_book__44c3_s_short = { + 2, 100, + _huff_lengthlist__44c3_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c4_s_long[] = { + 3, 5,11,11,13,11,11,11,12,12, 5, 2,11, 6,10, 7, + 8,10,13,16,10, 9, 6,10, 6, 7, 8, 9,11,12,11, 5, + 11, 7,10, 8,10,12,15,17,12, 8, 5, 9, 4, 5, 7, 8, + 10,12,10, 6, 7, 8, 5, 5, 6, 8,10,11,10, 8, 8, 9, + 6, 6, 6, 7, 9,10,11,10, 9,11, 8, 7, 6, 6, 7, 9, + 11,13,10,15, 9, 9, 7, 7, 7, 8,10,15,11,17,11,11, + 9, 8, 7, 8, +}; + +static static_codebook _huff_book__44c4_s_long = { + 2, 100, + _huff_lengthlist__44c4_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c4_s_p1_0[] = { + 2, 4, 4, 0, 0, 0, 0, 0, 0, 5, 7, 6, 0, 0, 0, 0, + 0, 0, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, + 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 7, 7, 0, 0, 0, 0, + 0, 0, 7, 9, 8, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 8, 0, 0, 0, + 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 8, 8, 0, 0, + 0, 0, 0, 0, 8,10, 8, 0, 0, 0, 0, 0, 0, 8, 9,10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 7, 7, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 0, + 0, 0, 0, 0, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 8, 0, 0, 0, 0, 0, 0, 8,10, 9, 0, + 0, 0, 0, 0, 0, 8, 8,10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 8, 9, 9, + 0, 0, 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c4_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c4_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p1_0 = { + _vq_quantthresh__44c4_s_p1_0, + _vq_quantmap__44c4_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c4_s_p1_0 = { + 8, 6561, + _vq_lengthlist__44c4_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c4_s_p1_0, + NULL, + &_vq_auxt__44c4_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c4_s_p2_0[] = { + 2, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, 5, 5, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 5, 6, 6, 0, 0, 0, 8, + 7, 0, 0, 0, 7, 7, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 5, 6, 6, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 7, 7, 0, 0, + 0, 7, 7, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 5, + 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 9, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 5, 8, 7, 0, 0, 0, 7, 7, 0, 0, 0, 7, 7, + 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 5, 7, 8, 0, + 0, 0, 7, 7, 0, 0, 0, 7, 7, 0, 0, 0, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7,10,10, 0, 0, 0, 9, 9, 0, 0, 0, 9, 9, 0, 0, 0, + 10,10, 0, 0, 0, 0, 0, 0, 0, 7,10,10, 0, 0, 0, 9, + 9, 0, 0, 0, 9, 9, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c4_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c4_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p2_0 = { + _vq_quantthresh__44c4_s_p2_0, + _vq_quantmap__44c4_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c4_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c4_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c4_s_p2_0, + NULL, + &_vq_auxt__44c4_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c4_s_p3_0[] = { + 2, 3, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 4, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c4_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c4_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p3_0 = { + _vq_quantthresh__44c4_s_p3_0, + _vq_quantmap__44c4_s_p3_0, + 5, + 5 +}; + +static static_codebook _44c4_s_p3_0 = { + 4, 625, + _vq_lengthlist__44c4_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c4_s_p3_0, + NULL, + &_vq_auxt__44c4_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c4_s_p4_0[] = { + 2, 3, 3, 6, 6, 0, 0, 0, 0, 0, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 4, 4, 6, 6, 0, 0, 0, 0, 0, 5, 5, 6, 6, + 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, + 7, 8, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c4_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c4_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p4_0 = { + _vq_quantthresh__44c4_s_p4_0, + _vq_quantmap__44c4_s_p4_0, + 9, + 9 +}; + +static static_codebook _44c4_s_p4_0 = { + 2, 81, + _vq_lengthlist__44c4_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c4_s_p4_0, + NULL, + &_vq_auxt__44c4_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c4_s_p5_0[] = { + 2, 3, 3, 6, 6, 7, 7, 9, 9, 0, 4, 4, 7, 7, 7, 7, + 9, 9, 0, 4, 4, 7, 7, 7, 7, 9, 9, 0, 6, 6, 7, 7, + 8, 8,10,10, 0, 0, 0, 7, 7, 8, 8,10,10, 0, 0, 0, + 8, 8, 8, 8,10,10, 0, 0, 0, 8, 8, 8, 8,10,10, 0, + 0, 0,10,10, 9, 9,11,11, 0, 0, 0, 0, 0, 9, 9,11, + 11, +}; + +static float _vq_quantthresh__44c4_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c4_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p5_0 = { + _vq_quantthresh__44c4_s_p5_0, + _vq_quantmap__44c4_s_p5_0, + 9, + 9 +}; + +static static_codebook _44c4_s_p5_0 = { + 2, 81, + _vq_lengthlist__44c4_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c4_s_p5_0, + NULL, + &_vq_auxt__44c4_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c4_s_p6_0[] = { + 2, 4, 4, 6, 6, 8, 8, 8, 8, 9, 9,10, 9,10,10,11, + 11, 0, 4, 4, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11,11, + 11,11, 0, 4, 4, 6, 6, 8, 8, 9, 9, 9, 9,10,10,11, + 11,12,12, 0, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 8, 8, 8, 8, 9, 9,10, 9, + 10,11,11,11,12,12, 0, 0, 0, 8, 8, 8, 8, 9, 9,10, + 10,11,10,11,11,12,12, 0, 0, 0, 9, 9, 9, 9,10,10, + 10,10,11,11,11,11,12,12, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,11,12,12, 0, 0, 0, 0, 0, 8, 8, + 9, 9,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, 8, + 8, 9, 9,10,10,11,11,12,12,12,12, 0, 0, 0, 0, 0, + 9, 9,10,10,11,10,11,11,12,12,12,12, 0, 0, 0, 0, + 0, 0, 0,10,10,10,10,11,11,12,12,13,12, 0, 0, 0, + 0, 0, 0, 0,11,11,11,11,11,12,12,12,13,12, 0, 0, + 0, 0, 0, 0, 0,11,11,11,11,11,11,12,12,13,13, 0, + 0, 0, 0, 0, 0, 0,11,11,12,12,12,12,12,12,13,13, + 0, 0, 0, 0, 0, 0, 0, 0, 0,11,11,12,12,12,12,13, + 13, +}; + +static float _vq_quantthresh__44c4_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c4_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p6_0 = { + _vq_quantthresh__44c4_s_p6_0, + _vq_quantmap__44c4_s_p6_0, + 17, + 17 +}; + +static static_codebook _44c4_s_p6_0 = { + 2, 289, + _vq_lengthlist__44c4_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c4_s_p6_0, + NULL, + &_vq_auxt__44c4_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c4_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 5, 7, 7,10, 9, 9,10, 9, 9, 6,10,10,10,10, + 10,11,10,10, 6, 9, 9,10,10,10,10,10, 9, 6, 9, 9, + 10, 9,10,11, 9, 9, 7,10,10,11,11,11,11,10,10, 6, + 9, 9,10, 9, 9,10,10, 9, 6, 9, 9,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__44c4_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c4_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p7_0 = { + _vq_quantthresh__44c4_s_p7_0, + _vq_quantmap__44c4_s_p7_0, + 3, + 3 +}; + +static static_codebook _44c4_s_p7_0 = { + 4, 81, + _vq_lengthlist__44c4_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c4_s_p7_0, + NULL, + &_vq_auxt__44c4_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c4_s_p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, + 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, 7, 7, 8, 8, 8, + 8,10, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, + 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 9, 9, 8, 8, 8, 9,10,10, + 10,10,10, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c4_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c4_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p7_1 = { + _vq_quantthresh__44c4_s_p7_1, + _vq_quantmap__44c4_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c4_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c4_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c4_s_p7_1, + NULL, + &_vq_auxt__44c4_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c4_s_p8_0[] = { + 1, 4, 4, 6, 6, 8, 8, 8, 8, 9, 9,10,10, 6, 5, 5, + 7, 7, 8, 8, 9, 9,10,10,11,11, 7, 5, 5, 7, 7, 8, + 8, 9, 9,10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,12,12, 9, 9, 9, 9,10,10,10,10,11,11, 0,12, + 12, 9, 8, 9, 9,10,10,11,11,12,11, 0, 0, 0, 9,10, + 9, 9,10,10,11,11,12,12, 0, 0, 0,10,10, 9, 9,10, + 10,11,11,12,12, 0, 0, 0,13,13,10,10,10,11,12,12, + 12,12, 0, 0, 0,14,14,10,10,11,11,11,11,12,12, 0, + 0, 0, 0, 0,11,12,11,11,12,12,12,13, 0, 0, 0, 0, + 0,12,12,11,11,12,12,13,13, +}; + +static float _vq_quantthresh__44c4_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c4_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p8_0 = { + _vq_quantthresh__44c4_s_p8_0, + _vq_quantmap__44c4_s_p8_0, + 13, + 13 +}; + +static static_codebook _44c4_s_p8_0 = { + 2, 169, + _vq_lengthlist__44c4_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c4_s_p8_0, + NULL, + &_vq_auxt__44c4_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c4_s_p8_1[] = { + 2, 4, 4, 5, 5, 6, 5, 5, 5, 5, 6, 4, 5, 5, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c4_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c4_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p8_1 = { + _vq_quantthresh__44c4_s_p8_1, + _vq_quantmap__44c4_s_p8_1, + 5, + 5 +}; + +static static_codebook _44c4_s_p8_1 = { + 2, 25, + _vq_lengthlist__44c4_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c4_s_p8_1, + NULL, + &_vq_auxt__44c4_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c4_s_p9_0[] = { + 1, 3, 3,10,10,10,10,10,10,10,10,10,10, 5, 7, 7, + 10,10,10,10,10,10,10,10,10,10, 5, 7, 8,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44c4_s_p9_0[] = { + -1732.5, -1417.5, -1102.5, -787.5, -472.5, -157.5, 157.5, 472.5, + 787.5, 1102.5, 1417.5, 1732.5, +}; + +static long _vq_quantmap__44c4_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p9_0 = { + _vq_quantthresh__44c4_s_p9_0, + _vq_quantmap__44c4_s_p9_0, + 13, + 13 +}; + +static static_codebook _44c4_s_p9_0 = { + 2, 169, + _vq_lengthlist__44c4_s_p9_0, + 1, -513964032, 1628680192, 4, 0, + _vq_quantlist__44c4_s_p9_0, + NULL, + &_vq_auxt__44c4_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44c4_s_p9_1[] = { + 1, 4, 4, 5, 6, 7, 7, 9, 9,11,11,12,12,12,13, 6, + 5, 5, 6, 7, 8, 8,10,10,11,11,13,13,13,13, 6, 5, + 5, 7, 7, 8, 8,11,10,11,11,12,13,12,13,17, 7, 7, + 8, 8, 9, 9,11,11,12,12,12,13,15,14,17, 7, 7, 8, + 8, 9, 9,11,10,12,12,13,13,14,13,17,11,12, 9, 9, + 10,10,12,12,13,13,14,14,14,14,17,12,11, 9, 8,11, + 10,11,12,13,13,13,14,14,14,17,17,17,11,11,11,11, + 13,13,13,13,14,13,15,14,17,17,17,11,10,11, 9,13, + 12,13,14,15,14,15,14,17,17,17,14,14,11,12,12,13, + 14,14,16,15,15,15,17,17,17,15,15,12,11,13,11,13, + 13,13,15,16,14,17,17,17,17,17,13,13,14,13,14,14, + 15,15,16,15,17,17,17,17,17,14,14,14,12,14,12,16, + 14,15,14,17,17,17,17,17,16,17,13,14,14,15,15,15, + 17,15,17,17,17,17,17,16,17,13,14,14,14,15,17,15, + 15, +}; + +static float _vq_quantthresh__44c4_s_p9_1[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__44c4_s_p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p9_1 = { + _vq_quantthresh__44c4_s_p9_1, + _vq_quantmap__44c4_s_p9_1, + 15, + 15 +}; + +static static_codebook _44c4_s_p9_1 = { + 2, 225, + _vq_lengthlist__44c4_s_p9_1, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__44c4_s_p9_1, + NULL, + &_vq_auxt__44c4_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c4_s_p9_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44c4_s_p9_2[] = { + 3, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9,11, 5, 5, 7, 7, 7, 7, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9,10,10,11, 5, 5, 7, 7, 7, + 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,11, + 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9,10,10, + 10,10,10,10,11,11,11, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9,10,10,10,10,10,10,11,11,11, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,11,11, + 11, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9,10, 9,10,10,10, + 10,10,10,11,11,11, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,10,10,10,10,10,11,11,11,11,11, 8, 8, 9, + 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,11,11,11, + 11,11, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10, + 10,10,11,11,11,11,11, 9, 9, 9, 9, 9,10,10,10,10, + 10,10,10,10,10,10,10,11,11,11,11,11, 9, 9, 9, 9, + 10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11, + 11,11,11, 9, 9, 9,10,10,10,10,10,10,10,10,10,10, + 10,11,11,11,11,11,11,11,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,11,11,11,11,11,11,11,10,10, 9, + 10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11, + 11,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 11,11,11,11,11,11,11,11,11,10,10,10,10,10,10,10, + 10,10,10,10,10,11,11,11,11,11,11,11,11,11,10,10, + 10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 12,11,11,10,10,10,10,10,10,10,10,10,10,10,10,12, + 11,12,12,11,11,11,11,11,10,10,10,10,10,10,10,10, + 10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,10, + 10,10,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__44c4_s_p9_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44c4_s_p9_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44c4_s_p9_2 = { + _vq_quantthresh__44c4_s_p9_2, + _vq_quantmap__44c4_s_p9_2, + 21, + 21 +}; + +static static_codebook _44c4_s_p9_2 = { + 2, 441, + _vq_lengthlist__44c4_s_p9_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44c4_s_p9_2, + NULL, + &_vq_auxt__44c4_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c4_s_short[] = { + 4, 9,13,12,16,11,12,15,15,16, 4, 2,11, 5,10, 6, + 8,11,14,14,13,11, 8,11, 7, 8,10,13,17,17,10, 4, + 11, 5, 9, 6, 9,13,17,17,13, 9, 6, 9, 5, 5, 7,11, + 15,17,10, 5, 7, 6, 5, 4, 7,10,15,15,10, 7, 9, 8, + 7, 6, 7,10,14,13,13,10,11,10, 8, 7, 8,10,14,14, + 12,11,10, 9, 6, 5, 6, 9,13,17,14,13,11,10, 6, 6, + 6, 8,11,16, +}; + +static static_codebook _huff_book__44c4_s_short = { + 2, 100, + _huff_lengthlist__44c4_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c5_s_long[] = { + 3, 6, 9,14,11,13,12,12,12,12, 6, 3, 5, 8, 6, 8, + 10,11,12,14, 9, 5, 4,10, 5, 7, 8, 9,11,12,13, 8, + 10, 9, 9, 9,12,15,16,17,10, 6, 5, 9, 3, 5, 6, 8, + 10,12,10, 8, 7, 9, 5, 5, 6, 8,10,11,10, 9, 8,11, + 6, 6, 6, 7, 8,10,12,11, 9,13, 8, 7, 6, 6, 7, 9, + 11,13,10,15, 9, 9, 7, 7, 7, 8,10,15,10,17,11,10, + 9, 8, 7, 7, +}; + +static static_codebook _huff_book__44c5_s_long = { + 2, 100, + _huff_lengthlist__44c5_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c5_s_p1_0[] = { + 2, 4, 4, 0, 0, 0, 0, 0, 0, 4, 7, 6, 0, 0, 0, 0, + 0, 0, 4, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 7, 9, 8, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 7, 7, 0, 0, 0, 0, + 0, 0, 7, 9, 8, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 9,10,11, 0, 0, 0, 0, 0, 0, 9,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 9, 8, 0, 0, + 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 9,10,11, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 7, 7, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 9, 8, 0, 0, 0, 0, 0, 0, 9,11,10, 0, + 0, 0, 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 9,11,10, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c5_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c5_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p1_0 = { + _vq_quantthresh__44c5_s_p1_0, + _vq_quantmap__44c5_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c5_s_p1_0 = { + 8, 6561, + _vq_lengthlist__44c5_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c5_s_p1_0, + NULL, + &_vq_auxt__44c5_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c5_s_p2_0[] = { + 2, 4, 4, 0, 0, 0, 6, 5, 0, 0, 0, 5, 5, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 4, 6, 5, 0, 0, 0, 8, + 7, 0, 0, 0, 8, 8, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, + 0, 0, 4, 5, 6, 0, 0, 0, 7, 8, 0, 0, 0, 7, 8, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 5, 8, 7, 0, 0, 0, 8, 8, 0, 0, + 0, 8, 8, 0, 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 6, + 7, 8, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0,10, + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 7, 0, 0, 0, 8, 8, 0, 0, 0, 8, 8, + 0, 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 0, + 0, 0, 8, 8, 0, 0, 0, 8, 8, 0, 0, 0,10,10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 9, 9, 0, 0, 0,10,10, 0, 0, 0,10,10, 0, 0, 0, + 10,10, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0,10, + 10, 0, 0, 0,10,10, 0, 0, 0,10,10, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c5_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c5_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p2_0 = { + _vq_quantthresh__44c5_s_p2_0, + _vq_quantmap__44c5_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c5_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c5_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c5_s_p2_0, + NULL, + &_vq_auxt__44c5_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c5_s_p3_0[] = { + 2, 4, 3, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 5, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c5_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c5_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p3_0 = { + _vq_quantthresh__44c5_s_p3_0, + _vq_quantmap__44c5_s_p3_0, + 5, + 5 +}; + +static static_codebook _44c5_s_p3_0 = { + 4, 625, + _vq_lengthlist__44c5_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c5_s_p3_0, + NULL, + &_vq_auxt__44c5_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c5_s_p4_0[] = { + 2, 3, 3, 6, 6, 0, 0, 0, 0, 0, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 4, 4, 6, 6, 0, 0, 0, 0, 0, 5, 5, 6, 6, + 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, + 8, 7, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, + 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c5_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c5_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p4_0 = { + _vq_quantthresh__44c5_s_p4_0, + _vq_quantmap__44c5_s_p4_0, + 9, + 9 +}; + +static static_codebook _44c5_s_p4_0 = { + 2, 81, + _vq_lengthlist__44c5_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c5_s_p4_0, + NULL, + &_vq_auxt__44c5_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c5_s_p5_0[] = { + 2, 3, 4, 5, 6, 7, 7, 9, 9, 0, 4, 4, 6, 6, 7, 7, + 9, 9, 0, 4, 4, 6, 6, 7, 7, 9, 9, 0, 6, 6, 7, 7, + 8, 8,10,10, 0, 0, 0, 7, 7, 8, 8,10,10, 0, 0, 0, + 7, 7, 8, 8,10,10, 0, 0, 0, 7, 8, 8, 8,10,10, 0, + 0, 0, 9, 9, 9, 9,10,10, 0, 0, 0, 0, 0, 9, 9,10, + 10, +}; + +static float _vq_quantthresh__44c5_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c5_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p5_0 = { + _vq_quantthresh__44c5_s_p5_0, + _vq_quantmap__44c5_s_p5_0, + 9, + 9 +}; + +static static_codebook _44c5_s_p5_0 = { + 2, 81, + _vq_lengthlist__44c5_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c5_s_p5_0, + NULL, + &_vq_auxt__44c5_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c5_s_p6_0[] = { + 2, 4, 4, 6, 6, 8, 8, 9, 9, 9, 9,10,10,11,11,12, + 12, 0, 4, 4, 6, 6, 8, 8, 9, 9, 9, 9,10,10,11,11, + 12,12, 0, 4, 4, 6, 6, 8, 8, 9, 9, 9, 9,10,10,11, + 11,12,12, 0, 6, 6, 7, 7, 8, 8, 9,10,10,10,11,11, + 11,12,12,12, 0, 0, 0, 7, 7, 8, 8,10,10,10,10,11, + 11,12,12,12,12, 0, 0, 0, 7, 7, 9, 9,10,10,10,10, + 11,11,12,12,12,12, 0, 0, 0, 7, 7, 8, 9,10,10,10, + 10,11,11,12,12,12,13, 0, 0, 0, 8, 8, 9, 9,10,10, + 10,10,11,11,12,12,13,12, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 8, 8, + 9, 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 8, + 8, 9, 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 9, 9,10,10,11,11,11,12,12,12,13,13, 0, 0, 0, 0, + 0, 0, 0,10,10,11,11,11,12,12,12,13,13, 0, 0, 0, + 0, 0, 0, 0,11,11,11,11,12,12,12,13,13,13, 0, 0, + 0, 0, 0, 0, 0,11,11,11,11,12,12,13,12,13,13, 0, + 0, 0, 0, 0, 0, 0,11,11,12,12,12,12,13,13,13,13, + 0, 0, 0, 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,13, + 13, +}; + +static float _vq_quantthresh__44c5_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c5_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p6_0 = { + _vq_quantthresh__44c5_s_p6_0, + _vq_quantmap__44c5_s_p6_0, + 17, + 17 +}; + +static static_codebook _44c5_s_p6_0 = { + 2, 289, + _vq_lengthlist__44c5_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c5_s_p6_0, + NULL, + &_vq_auxt__44c5_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c5_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 5, 7, 7,10, 9, 9,10, 9, 9, 6,10,10,10,10, + 10,11,10,10, 6, 9, 9,10,10, 9,11,10,10, 6, 9, 9, + 10, 9,10,11,10, 9, 7,10,10,11,11,11,11,10,10, 6, + 9, 9,10,10, 9,10, 9, 9, 6, 9, 9,10,10,10,11, 9, + 9, +}; + +static float _vq_quantthresh__44c5_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c5_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p7_0 = { + _vq_quantthresh__44c5_s_p7_0, + _vq_quantmap__44c5_s_p7_0, + 3, + 3 +}; + +static static_codebook _44c5_s_p7_0 = { + 4, 81, + _vq_lengthlist__44c5_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c5_s_p7_0, + NULL, + &_vq_auxt__44c5_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c5_s_p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, + 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, 7, 7, 8, 8, 8, + 8,10, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, + 7, 8, 8, 8, 8, 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, + 8, 8,10,10,10, 7, 7, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 9, 9, 8, 8, 9, 8,10,10, + 10,10,10, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c5_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c5_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p7_1 = { + _vq_quantthresh__44c5_s_p7_1, + _vq_quantmap__44c5_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c5_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c5_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c5_s_p7_1, + NULL, + &_vq_auxt__44c5_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c5_s_p8_0[] = { + 1, 4, 4, 6, 6, 8, 8, 8, 8, 9, 9,10,10, 6, 5, 5, + 7, 7, 8, 8, 9, 9,10,10,10,10, 7, 5, 5, 7, 7, 8, + 8, 9, 9,10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,11,12, 9, 9, 9, 9, 9,10,10,10,11,11, 0,13, + 12, 9, 9, 9, 9,10,10,11,11,11,11, 0, 0, 0, 9,10, + 9, 9,10,10,11,11,12,11, 0, 0, 0,10,10, 9, 9,10, + 10,11,11,12,12, 0, 0, 0,13,13,10,10,11,11,11,12, + 12,12, 0, 0, 0,14,14,10,10,11,11,11,11,12,12, 0, + 0, 0, 0, 0,12,12,11,11,12,12,13,13, 0, 0, 0, 0, + 0,12,12,11,11,12,12,13,13, +}; + +static float _vq_quantthresh__44c5_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c5_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p8_0 = { + _vq_quantthresh__44c5_s_p8_0, + _vq_quantmap__44c5_s_p8_0, + 13, + 13 +}; + +static static_codebook _44c5_s_p8_0 = { + 2, 169, + _vq_lengthlist__44c5_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c5_s_p8_0, + NULL, + &_vq_auxt__44c5_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c5_s_p8_1[] = { + 2, 4, 4, 5, 5, 6, 5, 5, 5, 5, 6, 4, 5, 5, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c5_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c5_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p8_1 = { + _vq_quantthresh__44c5_s_p8_1, + _vq_quantmap__44c5_s_p8_1, + 5, + 5 +}; + +static static_codebook _44c5_s_p8_1 = { + 2, 25, + _vq_lengthlist__44c5_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c5_s_p8_1, + NULL, + &_vq_auxt__44c5_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p9_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44c5_s_p9_0[] = { + 1, 3, 3,11,11,11,11,11,11,11,11,11,11,11,11, 5, + 7, 7,11,11,11,11,11,11,11,11,11,11,11,11, 5, 9, + 7,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__44c5_s_p9_0[] = { + -2320.5, -1963.5, -1606.5, -1249.5, -892.5, -535.5, -178.5, 178.5, + 535.5, 892.5, 1249.5, 1606.5, 1963.5, 2320.5, +}; + +static long _vq_quantmap__44c5_s_p9_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p9_0 = { + _vq_quantthresh__44c5_s_p9_0, + _vq_quantmap__44c5_s_p9_0, + 15, + 15 +}; + +static static_codebook _44c5_s_p9_0 = { + 2, 225, + _vq_lengthlist__44c5_s_p9_0, + 1, -512522752, 1628852224, 4, 0, + _vq_quantlist__44c5_s_p9_0, + NULL, + &_vq_auxt__44c5_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p9_1[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c5_s_p9_1[] = { + 1, 4, 4, 6, 6, 8, 7, 9, 9,10,10,11,11,12,12,13, + 13, 6, 5, 5, 6, 6, 8, 8,10,10,11,11,12,12,13,13, + 13,13, 6, 5, 5, 7, 7, 8, 8,10,10,11,11,12,12,13, + 13,13,13,18, 7, 7, 8, 8, 9, 9,10,11,11,11,12,12, + 13,13,13,14,18, 7, 7, 8, 8, 9, 9,11,10,12,12,13, + 13,13,13,14,15,18,12,12, 9, 9,10,10,11,11,12,12, + 13,13,13,14,14,14,18,12,12, 9, 8,10,10,11,11,12, + 12,14,13,13,14,15,15,18,16,18,11,11,11,11,12,12, + 13,13,13,14,14,14,14,15,17,18,17,11,10,11, 9,12, + 13,13,13,14,14,13,14,14,14,18,18,18,13,14,11,12, + 12,12,13,14,13,13,14,15,16,15,18,18,18,15,13,12, + 9,12,11,13,14,14,15,14,14,16,14,18,18,18,18,18, + 12,13,13,13,13,14,15,14,15,15,15,15,18,18,18,18, + 17,14,12,13,11,14,12,15,14,14,15,16,15,18,18,18, + 17,18,15,18,13,13,14,13,15,14,16,15,17,16,18,18, + 17,18,18,15,17,14,13,14,12,14,14,15,15,15,15,18, + 18,18,17,17,18,18,14,15,14,14,14,14,15,14,16,16, + 17,18,18,18,18,17,17,15,15,13,13,15,13,15,13,15, + 15, +}; + +static float _vq_quantthresh__44c5_s_p9_1[] = { + -157.5, -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, + 10.5, 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, 157.5, +}; + +static long _vq_quantmap__44c5_s_p9_1[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p9_1 = { + _vq_quantthresh__44c5_s_p9_1, + _vq_quantmap__44c5_s_p9_1, + 17, + 17 +}; + +static static_codebook _44c5_s_p9_1 = { + 2, 289, + _vq_lengthlist__44c5_s_p9_1, + 1, -520814592, 1620377600, 5, 0, + _vq_quantlist__44c5_s_p9_1, + NULL, + &_vq_auxt__44c5_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c5_s_p9_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44c5_s_p9_2[] = { + 3, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,11, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9,10,11, 5, 5, 7, 7, 7, + 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,11, + 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,10,11,11,11, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,11,11,11, 8, 8, 8, 8, + 8, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,11,11, + 11, 8, 8, 8, 8, 9, 8, 9, 9, 9, 9, 9, 9,10,10,10, + 10,10,10,11,11,11, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9,10,10,10,10,10,10,10,11,11,11,11,11, 8, 8, 9, + 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,11,11,11, + 11,11, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10, + 10,10,11,11,11,11,11, 9, 9, 9, 9, 9, 9, 9,10,10, + 10,10,10,10,10,10,10,11,11,11,11,11, 9, 9, 9, 9, + 9, 9,10,10,10,10,10,10,10,10,10,10,11,11,11,11, + 11,11,11, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10, + 10,11,11,11,11,11,11,11,10, 9,10,10,10,10,10,10, + 10,10,10, 9,10,10,11,11,11,11,11,11,11, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11, + 11,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 11,11,11,11,11,11,11,11,11,10,10,10,10,10, 9,10, + 10,10,10,10,10,11,11,11,11,11,11,11,11,11,10,10, + 10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,10,10,10,10,10,10,10,10,10,10,10,10,11, + 11,11,11,11,11,11,11,11,10,10,10,10,10,10,10,10, + 10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,10, + 10,10,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__44c5_s_p9_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44c5_s_p9_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44c5_s_p9_2 = { + _vq_quantthresh__44c5_s_p9_2, + _vq_quantmap__44c5_s_p9_2, + 21, + 21 +}; + +static static_codebook _44c5_s_p9_2 = { + 2, 441, + _vq_lengthlist__44c5_s_p9_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44c5_s_p9_2, + NULL, + &_vq_auxt__44c5_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c5_s_short[] = { + 3, 9,10,15,10,10,11,15,15,17, 4, 5, 7, 8, 7, 7, + 9,13,15,16, 7, 6, 6,10, 6, 8, 9,12,12,16,10, 8, + 11, 8, 8, 7,11,15,17,17, 8, 5, 5, 8, 3, 4, 6,10, + 15,17,10, 7, 7, 7, 4, 4, 5,10,14,17,10, 9, 8, 9, + 6, 5, 6,10,14,17,12,12,11,12, 9, 8, 8,11,14,17, + 13,14,13,10, 7, 5, 6, 9,13,17,14,14,14,10, 7, 5, + 6, 7,10,15, +}; + +static static_codebook _huff_book__44c5_s_short = { + 2, 100, + _huff_lengthlist__44c5_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c6_s_long[] = { + 3, 8,11,13,13,13,12,12,13,18, 6, 3, 4, 7, 9, 9, + 11,11,13,16, 9, 4, 3, 5, 7, 7, 9,10,14,18,11, 7, + 4, 4, 6, 6, 8,10,14,15,11, 9, 6, 6, 6, 6, 8,10, + 13,15,10, 9, 7, 6, 6, 6, 7, 8,12,12,12,10, 9, 8, + 7, 6, 6, 7,11,12,11,10,10, 9, 9, 7, 7, 6, 9,12, + 12,12,13,13,13,10, 9, 8,10,12,13,14,16,16,17,14, + 12,11,11,13, +}; + +static static_codebook _huff_book__44c6_s_long = { + 2, 100, + _huff_lengthlist__44c6_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c6_s_p1_0[] = { + 1, 5, 5, 0, 5, 5, 0, 5, 5, 5, 8, 7, 0, 9, 8, 0, + 8, 8, 6, 7, 8, 0, 8, 9, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 5, 8, 8, 0, 8, 8, 0, 8, 8, 5, 8, 8, + 0, 8, 8, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 9, 8, 0, 8, 8, 0, 8, 8, 5, 8, 9, 0, 8, 8, 0, 8, + 8, +}; + +static float _vq_quantthresh__44c6_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c6_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p1_0 = { + _vq_quantthresh__44c6_s_p1_0, + _vq_quantmap__44c6_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c6_s_p1_0 = { + 4, 81, + _vq_lengthlist__44c6_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c6_s_p1_0, + NULL, + &_vq_auxt__44c6_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c6_s_p2_0[] = { + 3, 5, 5, 8, 8, 0, 5, 5, 9, 9, 0, 5, 5, 9, 9, 0, + 7, 7,10,10, 0, 0, 0,10,10, 5, 7, 7, 9, 9, 0, 8, + 7,10, 9, 0, 8, 8,10,10, 0,10,10,11,11, 0, 0, 0, + 11,11, 5, 7, 7, 9, 9, 0, 7, 8, 9,10, 0, 7, 8,10, + 10, 0,10,10,11,11, 0, 0, 0,11,11, 8, 9, 9,11,10, + 0,11,10,12,12, 0,11,11,12,12, 0,13,13,14,14, 0, + 0, 0,14,14, 8, 9, 9,10,11, 0,10,11,12,12, 0,11, + 11,12,12, 0,13,13,14,14, 0, 0, 0,14,14, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 5, 8, 8,11,11, 0, 7, 7,10,10, + 0, 7, 7,10,10, 0, 9, 8,11,10, 0, 0, 0,11,11, 5, + 7, 8,11,11, 0, 7, 7,10,10, 0, 7, 7,10,10, 0, 8, + 9,10,11, 0, 0, 0,11,11, 9,10,10,12,12, 0,10,10, + 12,11, 0,10,10,12,12, 0,12,12,13,13, 0, 0, 0,13, + 13, 9,10,10,12,12, 0,10,10,11,12, 0,10,10,12,12, + 0,12,12,13,13, 0, 0, 0,13,13, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 5, 8, 7,11,10, 0, 7, 7,10,10, 0, 7, 7, + 10,10, 0, 8, 9,11,11, 0, 0, 0,11,10, 5, 7, 8,10, + 11, 0, 7, 7,10,10, 0, 7, 7,10,10, 0, 9, 8,11,11, + 0, 0, 0,11,11, 9,10,10,12,12, 0,10,10,12,12, 0, + 10,10,12,12, 0,12,12,13,13, 0, 0, 0,13,13, 9, 9, + 10,12,12, 0,10,10,12,12, 0,10,10,12,12, 0,12,12, + 13,13, 0, 0, 0,13,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7,10,10,13,13, 0, 9, 8,12,12, 0, 8, 9,12,12, 0, + 10, 9,12,12, 0, 0, 0,12,12, 7,10,10,13,13, 0, 9, + 9,12,12, 0, 9, 8,12,12, 0, 9,10,12,12, 0, 0, 0, + 12,12,10,11,11,14,14, 0,11,10,13,13, 0,11,11,13, + 13, 0,12,12,13,13, 0, 0, 0,13,13,10,11,11,14,14, + 0,10,11,13,13, 0,11,11,13,13, 0,12,12,13,13, 0, + 0, 0,13,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,10, + 11,11,14,14, 0,11,11,13,13, 0,11,10,13,13, 0,12, + 12,13,13, 0, 0, 0,13,13,10,11,11,14,14, 0,11,11, + 13,13, 0,10,11,13,13, 0,12,12,13,13, 0, 0, 0,13, + 13, +}; + +static float _vq_quantthresh__44c6_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c6_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p2_0 = { + _vq_quantthresh__44c6_s_p2_0, + _vq_quantmap__44c6_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c6_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c6_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c6_s_p2_0, + NULL, + &_vq_auxt__44c6_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c6_s_p3_0[] = { + 2, 4, 4, 5, 5, 7, 7, 9, 9, 0, 4, 4, 6, 6, 7, 7, + 9, 9, 0, 4, 4, 6, 6, 7, 7, 9, 9, 0, 5, 5, 6, 6, + 8, 8,10,10, 0, 0, 0, 6, 6, 8, 8,10,10, 0, 0, 0, + 7, 7, 8, 8,10,10, 0, 0, 0, 7, 7, 9, 9,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c6_s_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c6_s_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p3_0 = { + _vq_quantthresh__44c6_s_p3_0, + _vq_quantmap__44c6_s_p3_0, + 9, + 9 +}; + +static static_codebook _44c6_s_p3_0 = { + 2, 81, + _vq_lengthlist__44c6_s_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c6_s_p3_0, + NULL, + &_vq_auxt__44c6_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c6_s_p4_0[] = { + 3, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 0, 4, 4, 6, 6, 7, 7, 8, 8, 8, 9,10,10,11,11, + 11,11, 0, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11, + 11,11,11, 0, 5, 5, 6, 6, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 6, 6, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9,10,10, + 11,11,12,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9,10, + 10,11,11,12,12,12,12, 0, 0, 0, 6, 6, 7, 7, 9, 9, + 10,10,11,11,12,12,12,13, 0, 0, 0, 0, 0, 7, 7, 9, + 9,10,10,11,11,12,12,12,13, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c6_s_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c6_s_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p4_0 = { + _vq_quantthresh__44c6_s_p4_0, + _vq_quantmap__44c6_s_p4_0, + 17, + 17 +}; + +static static_codebook _44c6_s_p4_0 = { + 2, 289, + _vq_lengthlist__44c6_s_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c6_s_p4_0, + NULL, + &_vq_auxt__44c6_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c6_s_p5_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 4, 7, 7, 9,10,10,10, + 10,10, 4, 7, 7, 9,10,10,10,10,10, 5, 9, 9, 9,11, + 11, 9,11,11, 7,10,10,11,12,11,12,12,12, 7,10,10, + 11,12,12,12,12,12, 6,10,10, 9,11,11,10,11,11, 7, + 10, 9,11,12,12,11,12,11, 7,10,10,11,12,12,11,12, + 12, +}; + +static float _vq_quantthresh__44c6_s_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c6_s_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p5_0 = { + _vq_quantthresh__44c6_s_p5_0, + _vq_quantmap__44c6_s_p5_0, + 3, + 3 +}; + +static static_codebook _44c6_s_p5_0 = { + 4, 81, + _vq_lengthlist__44c6_s_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c6_s_p5_0, + NULL, + &_vq_auxt__44c6_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c6_s_p5_1[] = { + 3, 4, 4, 6, 6, 8, 8, 8, 8, 9, 9,11, 4, 4, 6, 6, + 8, 8, 9, 9, 9, 9,11, 4, 4, 6, 6, 8, 8, 9, 8, 9, + 9,12, 5, 5, 6, 6, 8, 8, 9, 9, 9, 9,12,12,12, 6, + 6, 8, 8, 9, 9, 9, 9,11,11,11, 7, 7, 8, 8, 9, 9, + 9, 9,11,11,11, 7, 7, 8, 8, 8, 8, 9, 9,11,11,11, + 7, 7, 8, 8, 8, 8, 9, 9,11,11,11,11,11, 8, 8, 8, + 8, 9, 9,11,11,11,11,11, 7, 7, 8, 8, 8, 8,11,11, + 11,11,11, 7, 7, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c6_s_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c6_s_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p5_1 = { + _vq_quantthresh__44c6_s_p5_1, + _vq_quantmap__44c6_s_p5_1, + 11, + 11 +}; + +static static_codebook _44c6_s_p5_1 = { + 2, 121, + _vq_lengthlist__44c6_s_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c6_s_p5_1, + NULL, + &_vq_auxt__44c6_s_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c6_s_p6_0[] = { + 1, 4, 4, 6, 7, 8, 8, 8, 8, 9, 9,10,10, 5, 5, 5, + 7, 7, 9, 9, 9, 9,10,10,11,11, 6, 5, 5, 7, 7, 9, + 9, 9, 9,10,10,11,11, 0, 7, 7, 7, 7, 9, 9,10,10, + 11,11,11,11, 0, 7, 7, 7, 7, 9, 9,10,10,11,11,12, + 12, 0,11,11, 8, 8,10, 9,10,11,11,11,12,12, 0,12, + 12, 8, 8,10, 9,11,11,12,11,13,13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static float _vq_quantthresh__44c6_s_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c6_s_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p6_0 = { + _vq_quantthresh__44c6_s_p6_0, + _vq_quantmap__44c6_s_p6_0, + 13, + 13 +}; + +static static_codebook _44c6_s_p6_0 = { + 2, 169, + _vq_lengthlist__44c6_s_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c6_s_p6_0, + NULL, + &_vq_auxt__44c6_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c6_s_p6_1[] = { + 3, 4, 4, 5, 5, 6, 4, 4, 5, 5, 6, 4, 4, 5, 4, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c6_s_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c6_s_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p6_1 = { + _vq_quantthresh__44c6_s_p6_1, + _vq_quantmap__44c6_s_p6_1, + 5, + 5 +}; + +static static_codebook _44c6_s_p6_1 = { + 2, 25, + _vq_lengthlist__44c6_s_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c6_s_p6_1, + NULL, + &_vq_auxt__44c6_s_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c6_s_p7_0[] = { + 1, 4, 4, 7, 7, 8, 8, 9, 9,10,10,11,11, 5, 5, 5, + 7, 7, 8, 8, 9, 9,11,11,12,12, 6, 5, 5, 7, 7, 9, + 9, 9, 9,11,11,12,12,19, 7, 7, 7, 7, 9, 9,10,10, + 11,11,12,12,19, 7, 7, 7, 7, 9, 9,10,10,11,11,12, + 12,19,11,11, 8, 8,10,10,11,11,11,12,12,12,19,12, + 12, 8, 8,10, 9,11,11,12,12,13,12,19,19,19,11,11, + 10,10,11,11,12,12,13,13,19,19,19,11,11,10,10,11, + 11,12,12,13,13,19,19,19,14,14,11,11,11,12,13,13, + 13,13,19,19,19,15,15,11,11,12,12,13,12,14,14,19, + 19,19,19,18,13,13,12,12,13,13,14,14,18,18,18,18, + 18,13,12,12,12,13,13,14,14, +}; + +static float _vq_quantthresh__44c6_s_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__44c6_s_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p7_0 = { + _vq_quantthresh__44c6_s_p7_0, + _vq_quantmap__44c6_s_p7_0, + 13, + 13 +}; + +static static_codebook _44c6_s_p7_0 = { + 2, 169, + _vq_lengthlist__44c6_s_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__44c6_s_p7_0, + NULL, + &_vq_auxt__44c6_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c6_s_p7_1[] = { + 3, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7, 9, 5, 5, 7, 7, + 7, 7, 7, 7, 8, 8, 9, 5, 5, 6, 6, 7, 7, 7, 7, 7, + 7, 9, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 9, 9, 9, 7, + 7, 7, 7, 7, 8, 7, 8, 9, 9, 9, 7, 7, 7, 7, 8, 8, + 8, 8, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 9, 9, 9, + 7, 7, 8, 8, 7, 7, 8, 8, 9, 9, 9, 8, 9, 8, 8, 7, + 7, 7, 7, 9, 9, 8, 8, 9, 8, 8, 7, 7, 8, 8, 9, 9, + 9, 9, 8, 7, 7, 7, 7, 8, 8, +}; + +static float _vq_quantthresh__44c6_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c6_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p7_1 = { + _vq_quantthresh__44c6_s_p7_1, + _vq_quantmap__44c6_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c6_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c6_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c6_s_p7_1, + NULL, + &_vq_auxt__44c6_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44c6_s_p8_0[] = { + 1, 4, 4, 7, 7, 8, 8, 7, 7, 9, 9, 9,10,11,11, 6, + 5, 5, 8, 8, 9, 9, 8, 8, 9, 9,10,10,11,11, 6, 5, + 5, 8, 8, 9, 9, 8, 8, 9, 9,10,10,11,11,17, 8, 8, + 8, 8,10,10, 8, 9,10,10,11,11,12,11,17, 8, 8, 9, + 9,10,10, 9, 9,10,10,11,12,12,12,17,12,13, 9, 9, + 10,10, 9,10,10,10,11,11,13,12,17,13,13,10, 9,10, + 10,10,10,10,11,12,11,12,12,17,17,17, 9, 9, 9, 9, + 10,10,11,11,11,12,12,13,17,17,17, 9, 9, 9, 9,11, + 10,11,11,12,12,12,13,17,17,17,13,13,10,10,11,11, + 12,11,12,13,13,13,17,17,17,14,13,10, 9,11, 9,12, + 12,12,13,13,14,17,17,17,17,17,11,12,11,11,12,12, + 13,14,13,14,17,17,17,17,17,12,10,11, 8,12,11,13, + 14,14,14,17,17,16,16,16,13,15,11,12,12,13,13,13, + 14,14,16,16,16,16,16,14,13,12, 9,13,10,14,13,14, + 13, +}; + +static float _vq_quantthresh__44c6_s_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__44c6_s_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p8_0 = { + _vq_quantthresh__44c6_s_p8_0, + _vq_quantmap__44c6_s_p8_0, + 15, + 15 +}; + +static static_codebook _44c6_s_p8_0 = { + 2, 225, + _vq_lengthlist__44c6_s_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__44c6_s_p8_0, + NULL, + &_vq_auxt__44c6_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44c6_s_p8_1[] = { + 3, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, + 9, 8, 9, 9, 9,11, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 6, 6, 7, 7, 8, + 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,11, + 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,11,10,11, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9,11,11,11, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9, 9, 9, 9,11,11, + 11, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9,10,10, 9,10, 9, + 10,10, 9,11,11,11, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10, 9,10, 9, 9, 9, 9,11,11,11,11,11, 8, 8, 9, + 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,11,11,11, + 11,11, 9, 9, 9, 9, 9, 9,10, 9, 9, 9,10,10, 9, 9, + 9, 9,11,11,11,11,11, 9, 9, 9, 9, 9, 9, 9,10,10, + 9,10, 9,10,10,10,10,11,11,11,11,11, 9, 9, 9, 9, + 9, 9,10,10,10,10, 9,10,10, 9,10, 9,11,11,11,11, + 11,11,11, 9, 9, 9, 9,10, 9,10, 9, 9,10,10,10,10, + 10,10,11,11,11,11,11,11, 9, 9, 9, 9,10,10,10,10, + 9,10, 9,10,10, 9,11,11,11,11,11,11,10, 9, 9, 9, + 9, 9,10, 9,10,10,10,10,10,10,10,11,11,11,11,11, + 11,11, 9, 9,10,10,10,10,10,10,10,10,10,10,10,10, + 11,11,11,11,11,11,11,11,11, 9,10, 9,10, 9,10,10, + 10,10,10,10,10,11,11,11,11,11,11,11,11,11,10,10, + 10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,10,10, 9,10,10,10,10, 9,10, 9,10,10,11, + 11,11,11,11,11,11,11,11,10,10,10, 9,10,10,10,10, + 10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,10, + 10, 9,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__44c6_s_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44c6_s_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p8_1 = { + _vq_quantthresh__44c6_s_p8_1, + _vq_quantmap__44c6_s_p8_1, + 21, + 21 +}; + +static static_codebook _44c6_s_p8_1 = { + 2, 441, + _vq_lengthlist__44c6_s_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44c6_s_p8_1, + NULL, + &_vq_auxt__44c6_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c6_s_p9_0[] = { + 1, 4, 4, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44c6_s_p9_0[] = { + -3503.5, -2866.5, -2229.5, -1592.5, -955.5, -318.5, 318.5, 955.5, + 1592.5, 2229.5, 2866.5, 3503.5, +}; + +static long _vq_quantmap__44c6_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p9_0 = { + _vq_quantthresh__44c6_s_p9_0, + _vq_quantmap__44c6_s_p9_0, + 13, + 13 +}; + +static static_codebook _44c6_s_p9_0 = { + 2, 169, + _vq_lengthlist__44c6_s_p9_0, + 1, -511845376, 1630791680, 4, 0, + _vq_quantlist__44c6_s_p9_0, + NULL, + &_vq_auxt__44c6_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c6_s_p9_1[] = { + 1, 4, 4, 7, 7, 7, 7, 7, 7, 8, 9,10,11, 6, 6, 6, + 7, 8, 8, 8, 7, 8, 9,10,11,10, 6, 5, 6, 7, 8, 8, + 8, 8, 8, 9,10,10,11,14, 9, 8, 8, 8, 9, 8, 8, 9, + 10,10,12,11,14, 8, 8, 9, 8, 9, 8, 8, 8,11,10,11, + 11,14,14,13, 8, 9, 9, 9, 9,10,11,11,12,12,13,12, + 12, 8, 7,10, 9, 9, 9,11,11,11,10,13,13,13, 8, 9, + 9, 8,12,11,11,11,13,11,13,13,13, 9, 8, 9, 8,10, + 10,11,10,11,10,13,13,13,12,12, 9,10,11,11,11,12, + 13,12,13,13,13,13,12,10,10,10, 9,13,12,12,13,13, + 13,13,13,13,12,12,10,10,12,12,13,13,13,13,13,13, + 13,12,12,11,12,12,12,12,13, +}; + +static float _vq_quantthresh__44c6_s_p9_1[] = { + -269.5, -220.5, -171.5, -122.5, -73.5, -24.5, 24.5, 73.5, + 122.5, 171.5, 220.5, 269.5, +}; + +static long _vq_quantmap__44c6_s_p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p9_1 = { + _vq_quantthresh__44c6_s_p9_1, + _vq_quantmap__44c6_s_p9_1, + 13, + 13 +}; + +static static_codebook _44c6_s_p9_1 = { + 2, 169, + _vq_lengthlist__44c6_s_p9_1, + 1, -518889472, 1622704128, 4, 0, + _vq_quantlist__44c6_s_p9_1, + NULL, + &_vq_auxt__44c6_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c6_s_p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__44c6_s_p9_2[] = { + 2, 4, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 7, 6, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__44c6_s_p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__44c6_s_p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__44c6_s_p9_2 = { + _vq_quantthresh__44c6_s_p9_2, + _vq_quantmap__44c6_s_p9_2, + 49, + 49 +}; + +static static_codebook _44c6_s_p9_2 = { + 1, 49, + _vq_lengthlist__44c6_s_p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__44c6_s_p9_2, + NULL, + &_vq_auxt__44c6_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c6_s_short[] = { + 4, 9,11,11,13,13,17,16,17,17, 4, 4, 6, 7, 9, 9, + 12,15,17,17, 7, 5, 4, 5, 7, 8,11,12,17,17, 9, 6, + 4, 3, 5, 6,10,14,17,17,11, 8, 6, 4, 5, 6, 9,13, + 17,17,11,10, 7, 5, 5, 5, 8,12,17,17,13,12, 9, 8, + 7, 6, 8,11,17,17,13,13, 9, 6, 6, 5, 6, 9,17,17, + 17,16,10, 8, 7, 7, 8, 9,17,17,17,17,14,12,11,11, + 11,13,17,17, +}; + +static static_codebook _huff_book__44c6_s_short = { + 2, 100, + _huff_lengthlist__44c6_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c7_s_long[] = { + 3, 8,11,13,14,13,13,12,14,16, 6, 4, 5, 7, 9,10, + 11,11,13,15,10, 4, 3, 5, 7, 7,10,10,14,16,11, 7, + 4, 4, 5, 6, 8,10,13,15,12, 9, 6, 5, 5, 6, 8, 9, + 13,15,11, 9, 7, 6, 5, 5, 6, 8,11,13,11,10, 9, 8, + 7, 6, 6, 7,11,12,12,11,10, 9, 8, 7, 6, 6, 9,11, + 12,12,12,12,12,10, 9, 8,10,12,12,14,15,16,16,14, + 12,10,11,13, +}; + +static static_codebook _huff_book__44c7_s_long = { + 2, 100, + _huff_lengthlist__44c7_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c7_s_p1_0[] = { + 1, 5, 5, 0, 5, 5, 0, 5, 5, 6, 8, 7, 0, 9, 8, 0, + 8, 8, 5, 7, 8, 0, 8, 9, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 5, 8, 8, 0, 8, 8, 0, 8, 8, 5, 8, 8, + 0, 8, 8, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 9, 8, 0, 8, 8, 0, 8, 8, 5, 8, 9, 0, 8, 8, 0, 8, + 8, +}; + +static float _vq_quantthresh__44c7_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c7_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p1_0 = { + _vq_quantthresh__44c7_s_p1_0, + _vq_quantmap__44c7_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c7_s_p1_0 = { + 4, 81, + _vq_lengthlist__44c7_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c7_s_p1_0, + NULL, + &_vq_auxt__44c7_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c7_s_p2_0[] = { + 3, 5, 5, 8, 8, 0, 5, 5, 9, 9, 0, 5, 5, 9, 9, 0, + 7, 7,10, 9, 0, 0, 0, 9,10, 5, 7, 7, 9, 9, 0, 8, + 7,10, 9, 0, 8, 7,10, 9, 0,10, 9,11,11, 0, 0, 0, + 11,11, 6, 7, 7, 9, 9, 0, 7, 8, 9,10, 0, 7, 8,10, + 10, 0, 9, 9,11,11, 0, 0, 0,11,11, 8, 9, 9,11,10, + 0,11,10,12,12, 0,11,11,12,12, 0,13,13,14,14, 0, + 0, 0,14,14, 8, 9, 9,10,11, 0,10,11,11,12, 0,11, + 11,12,12, 0,13,13,14,14, 0, 0, 0,14,14, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 5, 8, 7,11,11, 0, 7, 7,10,10, + 0, 7, 7,10,10, 0, 9, 8,11,10, 0, 0, 0,11,11, 5, + 7, 8,11,11, 0, 7, 7,10,10, 0, 7, 7,10,10, 0, 8, + 9,10,11, 0, 0, 0,11,11, 9,10,10,12,12, 0,10,10, + 12,11, 0,10,10,12,12, 0,12,12,13,13, 0, 0, 0,13, + 13, 9,10,10,12,12, 0,10,10,11,12, 0,10,10,12,12, + 0,12,12,13,13, 0, 0, 0,13,13, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 5, 8, 7,11,10, 0, 7, 7,10,10, 0, 7, 7, + 10,10, 0, 9, 9,11,11, 0, 0, 0,11,10, 5, 7, 8,10, + 11, 0, 7, 7,10,10, 0, 7, 7,10,10, 0, 9, 9,11,11, + 0, 0, 0,10,11, 9,10, 9,12,11, 0,10,10,12,12, 0, + 10,10,12,11, 0,12,12,13,13, 0, 0, 0,13,13, 9, 9, + 10,11,12, 0,10,10,12,12, 0,10,10,11,12, 0,12,12, + 13,13, 0, 0, 0,13,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 9, 9,13,13, 0, 9, 8,12,12, 0, 8, 9,12,12, 0, + 10, 9,12,12, 0, 0, 0,12,12, 7,10, 9,13,13, 0, 9, + 9,12,12, 0, 9, 8,12,12, 0, 9,10,12,12, 0, 0, 0, + 12,12,10,11,11,14,14, 0,11,10,13,12, 0,11,11,13, + 13, 0,12,12,13,13, 0, 0, 0,13,13,10,11,11,14,14, + 0,10,11,12,13, 0,11,11,13,13, 0,12,12,13,13, 0, + 0, 0,13,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,10, + 11,11,14,14, 0,11,11,13,13, 0,11,10,13,13, 0,12, + 12,13,13, 0, 0, 0,13,13,10,11,11,14,14, 0,11,11, + 13,13, 0,10,11,13,13, 0,12,12,13,13, 0, 0, 0,13, + 13, +}; + +static float _vq_quantthresh__44c7_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c7_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p2_0 = { + _vq_quantthresh__44c7_s_p2_0, + _vq_quantmap__44c7_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c7_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c7_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c7_s_p2_0, + NULL, + &_vq_auxt__44c7_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c7_s_p3_0[] = { + 2, 4, 4, 5, 5, 7, 7, 8, 8, 0, 4, 4, 6, 6, 7, 7, + 9, 9, 0, 4, 4, 6, 6, 7, 7, 9, 9, 0, 5, 5, 6, 6, + 8, 8,10,10, 0, 0, 0, 6, 6, 8, 8,10,10, 0, 0, 0, + 7, 7, 9, 9,10,10, 0, 0, 0, 7, 7, 9, 9,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c7_s_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c7_s_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p3_0 = { + _vq_quantthresh__44c7_s_p3_0, + _vq_quantmap__44c7_s_p3_0, + 9, + 9 +}; + +static static_codebook _44c7_s_p3_0 = { + 2, 81, + _vq_lengthlist__44c7_s_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c7_s_p3_0, + NULL, + &_vq_auxt__44c7_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c7_s_p4_0[] = { + 3, 4, 4, 6, 5, 7, 7, 7, 7, 8, 8, 9, 9,10,10,11, + 11, 0, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 9,10,10,10, + 11,11, 0, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8,10,10,10, + 11,11,11, 0, 5, 5, 6, 6, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 6, 6, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9,10,10, + 11,11,12,12,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9,10, + 10,11,11,12,12,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 8, 8, 9, + 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c7_s_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c7_s_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p4_0 = { + _vq_quantthresh__44c7_s_p4_0, + _vq_quantmap__44c7_s_p4_0, + 17, + 17 +}; + +static static_codebook _44c7_s_p4_0 = { + 2, 289, + _vq_lengthlist__44c7_s_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c7_s_p4_0, + NULL, + &_vq_auxt__44c7_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c7_s_p5_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 4, 7, 7,10,11,10,10, + 11,11, 4, 7, 7,10,10,11,10,10,11, 5,10,10, 9,12, + 11,10,12,12, 7,11,10,12,12,12,12,13,13, 7,10,11, + 11,12,12,12,13,13, 5,10,10,10,12,12,10,12,12, 7, + 11,10,12,13,13,12,12,12, 7,10,11,12,13,13,12,12, + 12, +}; + +static float _vq_quantthresh__44c7_s_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c7_s_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p5_0 = { + _vq_quantthresh__44c7_s_p5_0, + _vq_quantmap__44c7_s_p5_0, + 3, + 3 +}; + +static static_codebook _44c7_s_p5_0 = { + 4, 81, + _vq_lengthlist__44c7_s_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c7_s_p5_0, + NULL, + &_vq_auxt__44c7_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c7_s_p5_1[] = { + 3, 5, 5, 6, 6, 7, 7, 8, 8, 8, 9,12, 4, 4, 6, 6, + 7, 7, 8, 8, 9, 9,11, 5, 5, 6, 6, 7, 7, 8, 8, 9, + 9,12, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,12,12,12, 6, + 6, 7, 7, 8, 8, 9, 9,12,12,12, 6, 6, 7, 7, 8, 8, + 9, 9,12,11,11, 6, 6, 7, 7, 8, 8, 9, 9,12,12,11, + 7, 7, 8, 8, 8, 8, 8, 8,12,12,12,11,11, 8, 8, 8, + 8, 8, 8,12,12,12,11,11, 7, 7, 7, 7, 8, 8,12,12, + 12,11,11, 7, 7, 7, 7, 8, 8, +}; + +static float _vq_quantthresh__44c7_s_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c7_s_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p5_1 = { + _vq_quantthresh__44c7_s_p5_1, + _vq_quantmap__44c7_s_p5_1, + 11, + 11 +}; + +static static_codebook _44c7_s_p5_1 = { + 2, 121, + _vq_lengthlist__44c7_s_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c7_s_p5_1, + NULL, + &_vq_auxt__44c7_s_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c7_s_p6_0[] = { + 1, 4, 4, 7, 7, 8, 8, 9, 9,10, 9,10,10, 5, 5, 5, + 7, 7, 9, 9, 9, 9,10,10,11,11, 6, 5, 5, 7, 7, 9, + 9, 9, 9,11,10,11,11, 0, 6, 6, 7, 7, 9, 9,10,10, + 11,11,12,12, 0, 7, 7, 7, 7, 9, 9,10,10,11,11,12, + 12, 0,11,10, 8, 8,10,10,11,11,11,12,12,12, 0,11, + 11, 8, 8,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static float _vq_quantthresh__44c7_s_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c7_s_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p6_0 = { + _vq_quantthresh__44c7_s_p6_0, + _vq_quantmap__44c7_s_p6_0, + 13, + 13 +}; + +static static_codebook _44c7_s_p6_0 = { + 2, 169, + _vq_lengthlist__44c7_s_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c7_s_p6_0, + NULL, + &_vq_auxt__44c7_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c7_s_p6_1[] = { + 3, 4, 4, 5, 5, 6, 4, 4, 5, 5, 6, 4, 4, 4, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c7_s_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c7_s_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p6_1 = { + _vq_quantthresh__44c7_s_p6_1, + _vq_quantmap__44c7_s_p6_1, + 5, + 5 +}; + +static static_codebook _44c7_s_p6_1 = { + 2, 25, + _vq_lengthlist__44c7_s_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c7_s_p6_1, + NULL, + &_vq_auxt__44c7_s_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c7_s_p7_0[] = { + 1, 4, 4, 7, 7, 8, 8, 9, 9,10,10,11,11, 5, 5, 5, + 7, 7, 9, 8, 9, 9,11,11,12,12, 6, 5, 5, 7, 7, 9, + 9, 9,10,11,11,12,12,20, 7, 7, 7, 7, 9, 9,10,10, + 11,11,12,12,20, 7, 7, 7, 7, 9, 9,10,10,11,11,12, + 12,20,11,11, 8, 8,10, 9,11,11,11,11,12,12,20,12, + 12, 8, 8, 9, 9,11,11,12,12,12,12,20,20,20,11,11, + 10,10,11,11,12,12,13,13,20,20,20,11,11,10,10,11, + 11,12,12,13,13,20,20,20,14,14,11,11,11,12,13,13, + 13,13,20,20,20,15,14,11,11,11,11,13,13,14,14,20, + 20,20,20,19,12,12,12,12,13,13,14,14,19,19,19,19, + 19,13,12,12,12,13,13,14,14, +}; + +static float _vq_quantthresh__44c7_s_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__44c7_s_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p7_0 = { + _vq_quantthresh__44c7_s_p7_0, + _vq_quantmap__44c7_s_p7_0, + 13, + 13 +}; + +static static_codebook _44c7_s_p7_0 = { + 2, 169, + _vq_lengthlist__44c7_s_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__44c7_s_p7_0, + NULL, + &_vq_auxt__44c7_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c7_s_p7_1[] = { + 3, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7, 9, 5, 5, 6, 6, + 7, 7, 7, 7, 8, 8, 9, 5, 5, 6, 6, 7, 7, 7, 7, 7, + 7, 9, 6, 6, 7, 7, 7, 7, 8, 8, 7, 8, 9, 9, 9, 7, + 7, 7, 7, 7, 7, 8, 8, 9, 9, 9, 7, 7, 7, 7, 8, 8, + 8, 8, 9, 9, 9, 7, 7, 7, 7, 7, 7, 8, 8, 9, 9, 9, + 7, 7, 8, 7, 7, 7, 8, 8, 9, 9, 9, 9, 9, 7, 7, 7, + 7, 8, 7, 9, 9, 9, 9, 9, 8, 8, 7, 7, 8, 8, 9, 9, + 9, 9, 9, 7, 7, 7, 7, 8, 8, +}; + +static float _vq_quantthresh__44c7_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c7_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p7_1 = { + _vq_quantthresh__44c7_s_p7_1, + _vq_quantmap__44c7_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c7_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c7_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c7_s_p7_1, + NULL, + &_vq_auxt__44c7_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44c7_s_p8_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 8, 9, 9, 9,10,11,11, 6, + 5, 5, 8, 8, 9, 9, 8, 8, 9, 9,10,10,11,11, 6, 5, + 5, 8, 8, 9, 9, 8, 8, 9, 9,10,10,11,11,17, 8, 8, + 8, 8,10, 9, 8, 9,10,10,11,11,11,11,17, 8, 8, 8, + 8,10,10, 9, 9,10,10,11,11,12,12,17,12,13, 9, 9, + 10,10, 9, 9,10,11,11,11,12,12,17,13,13, 9, 9,10, + 10,10,10,10,10,11,11,12,12,17,17,17, 9, 9, 9, 9, + 10,10,11,11,11,12,12,12,17,17,17, 9, 9, 9, 9,11, + 10,11,12,11,12,13,12,17,17,17,13,14,10,10,10,11, + 12,11,12,12,12,13,17,17,17,14,14,10, 9,10, 9,12, + 12,12,12,13,13,17,17,17,17,17,11,11,11,11,11,12, + 13,13,13,14,17,17,17,17,17,12,10,11, 9,12,11,13, + 15,14,14,17,17,17,17,17,14,15,11,12,12,13,13,12, + 14,14,17,16,16,16,16,15,13,12, 9,12,10,14,12,15, + 14, +}; + +static float _vq_quantthresh__44c7_s_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__44c7_s_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p8_0 = { + _vq_quantthresh__44c7_s_p8_0, + _vq_quantmap__44c7_s_p8_0, + 15, + 15 +}; + +static static_codebook _44c7_s_p8_0 = { + 2, 225, + _vq_lengthlist__44c7_s_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__44c7_s_p8_0, + NULL, + &_vq_auxt__44c7_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44c7_s_p8_1[] = { + 3, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, + 9, 9, 9, 9, 9,10, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 6, 6, 7, 7, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,11, + 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,11,11,11, 7, 7, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9,11,11,11, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,11,11, + 11, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9,10, 9, 9, 9, 9, + 10,10, 9,11,11,11, 8, 9, 9, 8, 9, 9, 9, 9, 9, 9, + 9, 9,10,10, 9, 9,10, 9,11,11,11,11,11, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,11,10,11, + 11,11, 9, 9, 9, 9, 9, 9,10, 9,10,10,10,10,10, 9, + 9,10,11,11,11,11,11, 9, 9, 9, 9, 9, 9, 9, 9,10, + 10, 9, 9,10,10,10,10,11,11,11,11,11, 9, 9, 9, 9, + 9, 9, 9,10,10,10, 9,10,10,10,10, 9,11,11,10,11, + 11,11,11, 9, 9, 9, 9,10,10,10,10,10,10,10, 9,10, + 10,10,11,11,11,11,11,11, 9, 9, 9, 9, 9,10,10,10, + 10,10, 9, 9,10, 9,11,10,11,11,11,11,11, 9, 9, 9, + 9, 9,10,10, 9,10,10,10,10,10,10,11,11,11,11,11, + 11,11,10, 9,10, 9,10,10,10,10,10,10,10,10,10, 9, + 10,11,10,11,11,11,11,11,10, 9, 9,10,10,10,10,10, + 10,10,10,10,10,11,11,11,11,11,11,11,10,11,10,10, + 10,10,10,10,10,10, 9,10,10, 9,11,11,11,11,11,10, + 11,11,11,10,10, 9, 9,10,10,10,10,10, 9,10,10,11, + 11,11,11,11,10,11,11,11,10,10, 9, 9,10,10,10,10, + 10,10,10,10,11,11,11,11,11,11,11,11,11,11,11, 9, + 9, 9, 9,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__44c7_s_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44c7_s_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p8_1 = { + _vq_quantthresh__44c7_s_p8_1, + _vq_quantmap__44c7_s_p8_1, + 21, + 21 +}; + +static static_codebook _44c7_s_p8_1 = { + 2, 441, + _vq_lengthlist__44c7_s_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44c7_s_p8_1, + NULL, + &_vq_auxt__44c7_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c7_s_p9_0[] = { + 1, 4, 4, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 9, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 8, 8, 8, +}; + +static float _vq_quantthresh__44c7_s_p9_0[] = { + -3503.5, -2866.5, -2229.5, -1592.5, -955.5, -318.5, 318.5, 955.5, + 1592.5, 2229.5, 2866.5, 3503.5, +}; + +static long _vq_quantmap__44c7_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p9_0 = { + _vq_quantthresh__44c7_s_p9_0, + _vq_quantmap__44c7_s_p9_0, + 13, + 13 +}; + +static static_codebook _44c7_s_p9_0 = { + 2, 169, + _vq_lengthlist__44c7_s_p9_0, + 1, -511845376, 1630791680, 4, 0, + _vq_quantlist__44c7_s_p9_0, + NULL, + &_vq_auxt__44c7_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c7_s_p9_1[] = { + 1, 4, 4, 7, 7, 7, 7, 7, 7, 9, 8,10,10, 6, 6, 6, + 7, 8, 8, 8, 8, 8, 9, 9,10,11, 6, 5, 6, 8, 7, 8, + 8, 8, 8, 9, 9,10,11,14, 9, 8, 9, 8, 9, 8, 8, 9, + 10,10,11,11,14, 8, 9, 8, 8, 8, 9, 9, 8,12,10,11, + 11,14,13,13, 8, 9, 9, 9, 9,10,10,12,12,12,14,14, + 13, 8, 7,10, 9, 9,10,10,11,11,10,14,14,14, 8, 9, + 9, 8,11,10,12,11,11,11,14,14,14, 9, 7, 9, 8,10, + 10,11,11,11,10,14,14,14,12,12,10, 9,11,11,11,13, + 12,13,14,14,14,12,12,10,10,11, 8,11,11,14,13,14, + 14,14,14,14,12,13,11,12,12,11,14,13,13,13,13,13, + 13,12,11,11, 9,12,12,12,13, +}; + +static float _vq_quantthresh__44c7_s_p9_1[] = { + -269.5, -220.5, -171.5, -122.5, -73.5, -24.5, 24.5, 73.5, + 122.5, 171.5, 220.5, 269.5, +}; + +static long _vq_quantmap__44c7_s_p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p9_1 = { + _vq_quantthresh__44c7_s_p9_1, + _vq_quantmap__44c7_s_p9_1, + 13, + 13 +}; + +static static_codebook _44c7_s_p9_1 = { + 2, 169, + _vq_lengthlist__44c7_s_p9_1, + 1, -518889472, 1622704128, 4, 0, + _vq_quantlist__44c7_s_p9_1, + NULL, + &_vq_auxt__44c7_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c7_s_p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__44c7_s_p9_2[] = { + 2, 4, 3, 4, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__44c7_s_p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__44c7_s_p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__44c7_s_p9_2 = { + _vq_quantthresh__44c7_s_p9_2, + _vq_quantmap__44c7_s_p9_2, + 49, + 49 +}; + +static static_codebook _44c7_s_p9_2 = { + 1, 49, + _vq_lengthlist__44c7_s_p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__44c7_s_p9_2, + NULL, + &_vq_auxt__44c7_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c7_s_short[] = { + 4,10,12,13,15,15,16,16,17,17, 5, 5, 7, 8, 9, 9, + 12,17,18,18, 7, 5, 4, 5, 7, 8,10,13,18,18, 8, 6, + 5, 4, 5, 6, 9,12,17,18,10, 9, 6, 4, 4, 5, 8,12, + 18,17,11, 9, 7, 5, 4, 4, 6,10,17,17,13,12,10, 8, + 6, 5, 6, 9,17,17,14,13,12, 7, 6, 5, 5, 8,16,17, + 16,15,14, 8, 8, 7, 7, 9,14,17,17,17,17,12,11,11, + 11,12,16,17, +}; + +static static_codebook _huff_book__44c7_s_short = { + 2, 100, + _huff_lengthlist__44c7_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c8_s_long[] = { + 3, 8,12,14,14,13,13,12,13,15, 6, 4, 6, 8,10,10, + 11,11,13,15, 9, 5, 4, 5, 7, 8, 9,10,13,15,11, 7, + 4, 4, 5, 6, 8, 9,13,14,12, 9, 6, 5, 5, 5, 7, 9, + 12,14,11,10, 7, 6, 5, 4, 6, 7,11,12,11,10, 9, 8, + 7, 5, 6, 6,10,11,12,11,10, 9, 8, 6, 6, 5, 8,10, + 12,12,12,11,11,10, 9, 7, 8,11,12,13,14,14,15,13, + 10, 9, 9,11, +}; + +static static_codebook _huff_book__44c8_s_long = { + 2, 100, + _huff_lengthlist__44c8_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c8_s_p1_0[] = { + 1, 5, 5, 0, 5, 5, 0, 5, 5, 6, 7, 7, 0, 9, 8, 0, + 8, 8, 6, 7, 8, 0, 8, 9, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 5, 8, 8, 0, 8, 8, 0, 8, 8, 5, 8, 8, + 0, 8, 8, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 8, 8, 0, 8, 8, 0, 8, 7, 5, 8, 8, 0, 8, 8, 0, 7, + 8, +}; + +static float _vq_quantthresh__44c8_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c8_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p1_0 = { + _vq_quantthresh__44c8_s_p1_0, + _vq_quantmap__44c8_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c8_s_p1_0 = { + 4, 81, + _vq_lengthlist__44c8_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c8_s_p1_0, + NULL, + &_vq_auxt__44c8_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c8_s_p2_0[] = { + 3, 5, 5, 8, 8, 0, 6, 6, 8, 8, 0, 5, 6, 8, 8, 0, + 7, 7, 9, 9, 0, 0, 0, 9, 9, 5, 7, 6, 9, 9, 0, 7, + 7,10, 9, 0, 7, 7,10, 9, 0, 9, 9,11,11, 0, 0, 0, + 11,11, 5, 6, 7, 9, 9, 0, 7, 7, 9,10, 0, 7, 7, 9, + 10, 0, 9, 9,11,11, 0, 0, 0,11,11, 8, 9, 9,11,10, + 0,11,10,12,11, 0,10,10,12,11, 0,13,13,14,13, 0, + 0, 0,14,13, 8, 9, 9,10,11, 0,10,11,11,12, 0,10, + 10,12,12, 0,13,13,13,14, 0, 0, 0,13,14, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 6, 7, 7,10,10, 0, 7, 7,10,10, + 0, 7, 7,10,10, 0, 9, 8,10,10, 0, 0, 0,10,10, 6, + 7, 7,10,10, 0, 7, 7,10,10, 0, 7, 7,10,10, 0, 8, + 9,10,10, 0, 0, 0,10,10, 8,10, 9,12,12, 0,10, 9, + 12,11, 0,10,10,11,12, 0,12,11,13,12, 0, 0, 0,13, + 13, 8, 9,10,11,12, 0, 9,10,11,12, 0,10,10,11,12, + 0,11,12,12,13, 0, 0, 0,13,13, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 8, 7,11,10, 0, 7, 7,10,10, 0, 7, 7, + 10, 9, 0, 8, 9,10,10, 0, 0, 0,10,10, 6, 7, 8,10, + 11, 0, 7, 7,10,10, 0, 7, 7,10,10, 0, 9, 8,10,10, + 0, 0, 0,10,10, 8,10, 9,12,11, 0,10,10,12,11, 0, + 10,10,12,11, 0,11,12,13,12, 0, 0, 0,13,12, 9, 9, + 10,11,12, 0,10,10,11,12, 0,10,10,11,12, 0,12,11, + 12,13, 0, 0, 0,12,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 9, 9,12,13, 0, 9, 8,12,11, 0, 8, 9,11,12, 0, + 10, 9,12,11, 0, 0, 0,11,12, 7, 9, 9,13,13, 0, 9, + 9,11,12, 0, 9, 8,12,11, 0, 9,10,11,12, 0, 0, 0, + 12,11, 9,11,11,14,13, 0,10,10,13,12, 0,10,10,13, + 13, 0,12,11,13,12, 0, 0, 0,13,13, 9,11,11,13,14, + 0,10,10,12,13, 0,10,10,13,13, 0,11,12,12,13, 0, + 0, 0,13,13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + 11,11,14,13, 0,10,10,13,12, 0,10,10,13,12, 0,11, + 12,13,13, 0, 0, 0,13,12, 9,11,11,13,14, 0,10,10, + 13,13, 0,10,10,12,13, 0,12,11,13,13, 0, 0, 0,12, + 13, +}; + +static float _vq_quantthresh__44c8_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c8_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p2_0 = { + _vq_quantthresh__44c8_s_p2_0, + _vq_quantmap__44c8_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c8_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c8_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c8_s_p2_0, + NULL, + &_vq_auxt__44c8_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c8_s_p3_0[] = { + 3, 3, 4, 5, 5, 7, 6, 8, 8, 0, 4, 4, 5, 5, 7, 7, + 9, 9, 0, 4, 4, 5, 6, 7, 7, 9, 9, 0, 5, 5, 6, 6, + 8, 8,10,10, 0, 0, 0, 6, 6, 8, 8,10,10, 0, 0, 0, + 7, 7, 8, 8,10,10, 0, 0, 0, 7, 7, 8, 8,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c8_s_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c8_s_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p3_0 = { + _vq_quantthresh__44c8_s_p3_0, + _vq_quantmap__44c8_s_p3_0, + 9, + 9 +}; + +static static_codebook _44c8_s_p3_0 = { + 2, 81, + _vq_lengthlist__44c8_s_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c8_s_p3_0, + NULL, + &_vq_auxt__44c8_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c8_s_p4_0[] = { + 3, 4, 4, 5, 5, 7, 7, 8, 8, 8, 8, 9, 9,10,10,10, + 10, 0, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10,10,10, + 11,11, 0, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11, + 10,11,11, 0, 5, 5, 6, 6, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 6, 6, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9,10,10, + 11,11,12,12,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9,10, + 10,11,11,12,12,12,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 8, 8, 9, + 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c8_s_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c8_s_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p4_0 = { + _vq_quantthresh__44c8_s_p4_0, + _vq_quantmap__44c8_s_p4_0, + 17, + 17 +}; + +static static_codebook _44c8_s_p4_0 = { + 2, 289, + _vq_lengthlist__44c8_s_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c8_s_p4_0, + NULL, + &_vq_auxt__44c8_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c8_s_p5_0[] = { + 1, 4, 4, 5, 7, 7, 6, 7, 7, 4, 6, 6,10,10,10,10, + 10,10, 4, 6, 6,10,10,10,10,10,10, 5,10,10, 9,12, + 12,10,12,12, 7,10,10,12,12,12,12,12,12, 7,10,10, + 12,12,12,12,12,13, 6,10,10,10,12,12,11,12,12, 8, + 10,10,12,13,12,12,12,12, 7,10,10,12,12,13,12,13, + 12, +}; + +static float _vq_quantthresh__44c8_s_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c8_s_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p5_0 = { + _vq_quantthresh__44c8_s_p5_0, + _vq_quantmap__44c8_s_p5_0, + 3, + 3 +}; + +static static_codebook _44c8_s_p5_0 = { + 4, 81, + _vq_lengthlist__44c8_s_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c8_s_p5_0, + NULL, + &_vq_auxt__44c8_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c8_s_p5_1[] = { + 4, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7,10, 4, 4, 6, 6, + 7, 7, 8, 8, 8, 8,10, 5, 5, 6, 6, 7, 7, 8, 8, 8, + 8,11, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8,11,11,11, 6, + 6, 7, 7, 8, 8, 8, 8,11,11,11, 6, 6, 7, 7, 8, 8, + 8, 8,11,11,11, 6, 6, 7, 7, 8, 8, 8, 8,11,11,11, + 7, 7, 7, 7, 8, 8, 8, 8,11,11,11,11,11, 7, 7, 8, + 8, 8, 8,11,11,11,11,11, 7, 7, 7, 7, 8, 8,11,11, + 11,11,11, 7, 7, 7, 7, 8, 8, +}; + +static float _vq_quantthresh__44c8_s_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c8_s_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p5_1 = { + _vq_quantthresh__44c8_s_p5_1, + _vq_quantmap__44c8_s_p5_1, + 11, + 11 +}; + +static static_codebook _44c8_s_p5_1 = { + 2, 121, + _vq_lengthlist__44c8_s_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c8_s_p5_1, + NULL, + &_vq_auxt__44c8_s_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c8_s_p6_0[] = { + 1, 4, 4, 7, 6, 8, 8, 9, 9,10,10,11,11, 5, 5, 5, + 7, 7, 9, 9,10, 9,11,11,12,12, 6, 5, 5, 7, 7, 9, + 9,10,10,11,11,12,12, 0, 6, 6, 7, 7, 9, 9,10,10, + 11,11,12,12, 0, 7, 7, 7, 7, 9, 9,10,10,12,12,12, + 12, 0,10,10, 8, 8,10,10,11,11,12,12,13,13, 0,11, + 11, 8, 8,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static float _vq_quantthresh__44c8_s_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c8_s_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p6_0 = { + _vq_quantthresh__44c8_s_p6_0, + _vq_quantmap__44c8_s_p6_0, + 13, + 13 +}; + +static static_codebook _44c8_s_p6_0 = { + 2, 169, + _vq_lengthlist__44c8_s_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c8_s_p6_0, + NULL, + &_vq_auxt__44c8_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c8_s_p6_1[] = { + 3, 4, 4, 5, 5, 5, 4, 4, 5, 5, 5, 4, 4, 5, 5, 6, + 5, 5, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__44c8_s_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c8_s_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p6_1 = { + _vq_quantthresh__44c8_s_p6_1, + _vq_quantmap__44c8_s_p6_1, + 5, + 5 +}; + +static static_codebook _44c8_s_p6_1 = { + 2, 25, + _vq_lengthlist__44c8_s_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c8_s_p6_1, + NULL, + &_vq_auxt__44c8_s_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c8_s_p7_0[] = { + 1, 4, 4, 7, 7, 8, 8, 9, 9,10,10,11,11, 5, 5, 5, + 7, 7, 9, 9,10,10,11,11,12,12, 6, 5, 5, 7, 7, 9, + 9,10,10,11,11,12,12,20, 6, 7, 7, 7, 9, 9,10,10, + 11,11,12,12,20, 7, 7, 7, 7, 9, 9,10,10,11,11,12, + 12,20,11,11, 8, 8,10,10,11,11,12,12,12,12,20,12, + 12, 8, 8,10, 9,11,11,12,12,13,13,20,20,20,11,10, + 10,10,11,11,12,12,13,13,20,20,20,10,11,10,10,11, + 11,12,12,13,13,20,20,20,14,15,11,11,12,12,13,13, + 14,13,20,20,20,15,15,11,11,12,12,13,13,14,14,20, + 20,20,20,19,13,13,12,12,13,13,14,14,19,19,19,19, + 19,13,13,12,12,13,13,14,14, +}; + +static float _vq_quantthresh__44c8_s_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__44c8_s_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p7_0 = { + _vq_quantthresh__44c8_s_p7_0, + _vq_quantmap__44c8_s_p7_0, + 13, + 13 +}; + +static static_codebook _44c8_s_p7_0 = { + 2, 169, + _vq_lengthlist__44c8_s_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__44c8_s_p7_0, + NULL, + &_vq_auxt__44c8_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c8_s_p7_1[] = { + 4, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 5, 5, 7, 7, + 7, 7, 7, 7, 7, 7, 8, 5, 5, 7, 7, 7, 7, 7, 7, 7, + 7, 8, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 7, + 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 7, 7, 7, 7, 7, 7, + 7, 7, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, + 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 8, 8, + 8, 8, 8, 7, 7, 7, 7, 7, 7, +}; + +static float _vq_quantthresh__44c8_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c8_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p7_1 = { + _vq_quantthresh__44c8_s_p7_1, + _vq_quantmap__44c8_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c8_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c8_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c8_s_p7_1, + NULL, + &_vq_auxt__44c8_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44c8_s_p8_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11,11, 6, + 5, 5, 7, 7, 9, 9, 8, 8,10,10,11,11,11,12, 6, 5, + 5, 7, 7, 9, 9, 9, 8,10,10,11,10,11,12,18, 8, 8, + 8, 8, 9, 9, 9, 9,10,10,11,11,12,12,18, 8, 8, 8, + 8, 9, 9, 9, 9,10,10,11,11,12,13,18,12,12, 9, 9, + 10,10, 9, 9,10,11,11,12,13,12,18,12,12, 9, 9,10, + 10,10,10,10,11,11,12,13,13,18,18,18, 9, 9, 9, 9, + 10,10,11,11,12,12,12,12,18,18,18, 9, 9, 9, 9,10, + 10,11,11,12,12,13,13,18,18,18,13,13,10,10,11,11, + 12,11,12,12,13,13,18,18,18,14,14,10, 9,11,10,12, + 12,12,12,13,13,18,18,18,18,18,11,12,11,11,12,12, + 13,13,14,13,18,18,18,18,18,12,11,11,10,12,11,13, + 13,13,14,18,18,18,18,18,15,16,12,12,12,13,13,13, + 14,14,18,17,17,17,17,16,14,12,11,12,11,13,12,15, + 14, +}; + +static float _vq_quantthresh__44c8_s_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__44c8_s_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p8_0 = { + _vq_quantthresh__44c8_s_p8_0, + _vq_quantmap__44c8_s_p8_0, + 15, + 15 +}; + +static static_codebook _44c8_s_p8_0 = { + 2, 225, + _vq_lengthlist__44c8_s_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__44c8_s_p8_0, + NULL, + &_vq_auxt__44c8_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44c8_s_p8_1[] = { + 4, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 9, 9, 9, 9,10, 6, 6, 7, 7, 8, 8, 8, 8, 9, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 6, 6, 7, 7, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, + 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, 8, 8, 8, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10, + 10, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9,10,10,11, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,11,11,10,11,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, + 10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10, + 9, 9,10,11,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,11,10,10, 9, 9, 9, 9, + 9, 9, 9, 9,10, 9, 9, 9, 9, 9, 9, 9,11,10,11,10, + 10,10,10, 9, 9, 9, 9, 9, 9,10, 9, 9, 9, 9, 9, 9, + 9,11,11,11,10,10,11,10, 9, 9, 9, 9, 9, 9,10,10, + 9, 9, 9, 9, 9, 9,10,11,10,10,10,11,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9,10, 9,11,11,10,10,10, + 10,10, 9, 9, 9,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10,11,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9,10,10,11,11,10,11,10,10,10,10,10,10, 9, + 10, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10, + 10,10,10, 9,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, + 9, 9,10, 9,10,10,10,10,10,10,10,10,10,10,10, 9, + 9, 9, 9,10, 9, 9,10, 9, 9, +}; + +static float _vq_quantthresh__44c8_s_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44c8_s_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p8_1 = { + _vq_quantthresh__44c8_s_p8_1, + _vq_quantmap__44c8_s_p8_1, + 21, + 21 +}; + +static static_codebook _44c8_s_p8_1 = { + 2, 441, + _vq_lengthlist__44c8_s_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44c8_s_p8_1, + NULL, + &_vq_auxt__44c8_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p9_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c8_s_p9_0[] = { + 1, 5, 5,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, 7,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10, 6, 8,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44c8_s_p9_0[] = { + -6982.5, -6051.5, -5120.5, -4189.5, -3258.5, -2327.5, -1396.5, -465.5, + 465.5, 1396.5, 2327.5, 3258.5, 4189.5, 5120.5, 6051.5, 6982.5, +}; + +static long _vq_quantmap__44c8_s_p9_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p9_0 = { + _vq_quantthresh__44c8_s_p9_0, + _vq_quantmap__44c8_s_p9_0, + 17, + 17 +}; + +static static_codebook _44c8_s_p9_0 = { + 2, 289, + _vq_lengthlist__44c8_s_p9_0, + 1, -509798400, 1631393792, 5, 0, + _vq_quantlist__44c8_s_p9_0, + NULL, + &_vq_auxt__44c8_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p9_1[] = { + 9, + 8, + 10, + 7, + 11, + 6, + 12, + 5, + 13, + 4, + 14, + 3, + 15, + 2, + 16, + 1, + 17, + 0, + 18, +}; + +static long _vq_lengthlist__44c8_s_p9_1[] = { + 1, 4, 4, 7, 7, 7, 7, 7, 7, 9, 9,10,10,11,10,13, + 12,11,12, 6, 6, 6, 8, 8, 8, 8, 8, 8, 9,10,10,10, + 12,12,12,12,13,15, 5, 5, 5, 8, 8, 8, 8, 8, 8,10, + 9,10,11,12,13,12,13,13,12,15, 9, 8, 9, 9, 9, 9, + 9, 9,10,10,10,11,14,12,13,15,12,14,15, 8, 9, 9, + 9, 9, 9, 9, 9,10,10,12,11,12,11,12,13,13,13,15, + 13,13, 9, 9,10, 9,10,10,11,10,11,12,12,12,14,13, + 14,15,15,13,13, 9, 8,10, 9,10,10,11,10,12,12,13, + 13,14,13,14,15,15,15,15, 9, 9, 9, 9,10,11,12,12, + 12,13,13,13,14,15,15,14,15,15,15, 9, 7, 9, 8,12, + 11,11,13,12,12,13,12,14,13,13,14,15,15,15,13,13, + 10,10,12,11,13,13,12,12,13,13,14,13,13,15,15,15, + 15,12,13,11, 9,11,10,12,12,15,13,13,13,14,13,14, + 13,15,15,15,15,15,12,12,11,11,12,13,15,13,13,14, + 14,14,15,14,15,15,15,15,15,13,11,12,11,12,11,13, + 14,13,13,14,14,13,14,15,15,15,15,15,15,15,12,12, + 12,13,15,13,15,14,15,14,13,15,15,15,15,15,15,14, + 15,13,13,12,11,14,12,15,13,14,14,14,13,15,15,15, + 15,15,15,15,14,13,14,13,15,13,15,15,15,14,15,14, + 15,15,15,15,15,15,15,14,14,14,13,13,13,15,15,15, + 15,14,15,15,15,15,15,15,15,15,15,15,12,13,13,13, + 14,15,15,13,15,15,15,15,15,15,15,15,15,15,15,15, + 15,14,14,15,15,15,14,14,15, +}; + +static float _vq_quantthresh__44c8_s_p9_1[] = { + -416.5, -367.5, -318.5, -269.5, -220.5, -171.5, -122.5, -73.5, + -24.5, 24.5, 73.5, 122.5, 171.5, 220.5, 269.5, 318.5, + 367.5, 416.5, +}; + +static long _vq_quantmap__44c8_s_p9_1[] = { + 17, 15, 13, 11, 9, 7, 5, 3, + 1, 0, 2, 4, 6, 8, 10, 12, + 14, 16, 18, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p9_1 = { + _vq_quantthresh__44c8_s_p9_1, + _vq_quantmap__44c8_s_p9_1, + 19, + 19 +}; + +static static_codebook _44c8_s_p9_1 = { + 2, 361, + _vq_lengthlist__44c8_s_p9_1, + 1, -518287360, 1622704128, 5, 0, + _vq_quantlist__44c8_s_p9_1, + NULL, + &_vq_auxt__44c8_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c8_s_p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__44c8_s_p9_2[] = { + 3, 4, 3, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 7, 6, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__44c8_s_p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__44c8_s_p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__44c8_s_p9_2 = { + _vq_quantthresh__44c8_s_p9_2, + _vq_quantmap__44c8_s_p9_2, + 49, + 49 +}; + +static static_codebook _44c8_s_p9_2 = { + 1, 49, + _vq_lengthlist__44c8_s_p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__44c8_s_p9_2, + NULL, + &_vq_auxt__44c8_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c8_s_short[] = { + 4,11,13,14,16,15,16,16,17,16, 5, 6, 8, 9,10, 9, + 11,17,16,17, 6, 6, 6, 6, 7, 8, 9,14,16,17, 8, 6, + 5, 4, 6, 7, 9,12,14,17, 9, 7, 6, 5, 5, 5, 8,11, + 14,17,10, 9, 7, 6, 5, 3, 5, 8,13,17,12,11, 9, 8, + 7, 4, 4, 6,14,17,13,13,12, 8, 7, 5, 4, 5,12,17, + 17,14,15,10, 8, 7, 7, 7,11,17,17,16,17,14,12,10, + 11,11,15,17, +}; + +static static_codebook _huff_book__44c8_s_short = { + 2, 100, + _huff_lengthlist__44c8_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44c9_s_long[] = { + 3, 8,13,14,15,15,13,13,14,14, 6, 5, 8,10,12,12, + 13,12,13,14,10, 6, 5, 6, 8, 9,11,11,13,14,13, 8, + 5, 4, 5, 6, 9,10,13,14,14,11, 7, 5, 4, 5, 8, 9, + 12,14,12,11, 8, 6, 5, 3, 5, 7,10,13,12,10,10, 8, + 7, 5, 4, 6, 9,12,13,12,11,10, 9, 6, 5, 5, 7,10, + 13,12,12,11,11, 9, 8, 7, 8,10,12,12,13,13,14,12, + 10, 9, 9,10, +}; + +static static_codebook _huff_book__44c9_s_long = { + 2, 100, + _huff_lengthlist__44c9_s_long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c9_s_p1_0[] = { + 1, 5, 5, 0, 5, 5, 0, 5, 5, 6, 8, 8, 0, 9, 8, 0, + 8, 8, 6, 8, 8, 0, 8, 9, 0, 8, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 5, 8, 8, 0, 8, 7, 0, 8, 8, 5, 8, 8, + 0, 7, 8, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 9, 8, 0, 8, 8, 0, 7, 7, 5, 8, 9, 0, 8, 8, 0, 7, + 7, +}; + +static float _vq_quantthresh__44c9_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44c9_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p1_0 = { + _vq_quantthresh__44c9_s_p1_0, + _vq_quantmap__44c9_s_p1_0, + 3, + 3 +}; + +static static_codebook _44c9_s_p1_0 = { + 4, 81, + _vq_lengthlist__44c9_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44c9_s_p1_0, + NULL, + &_vq_auxt__44c9_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c9_s_p2_0[] = { + 3, 6, 6, 8, 8, 0, 6, 6, 8, 8, 0, 5, 5, 8, 8, 0, + 7, 7, 9, 9, 0, 0, 0, 9, 9, 6, 7, 7, 9, 8, 0, 8, + 7, 9, 9, 0, 7, 7, 9, 9, 0, 9, 9,11,10, 0, 0, 0, + 10,10, 6, 7, 7, 8, 9, 0, 7, 8, 9, 9, 0, 7, 7, 9, + 9, 0, 9, 9,10,11, 0, 0, 0,10,10, 8, 9, 9,10,10, + 0,10,10,11,11, 0,10,10,11,11, 0,12,12,13,12, 0, + 0, 0,13,13, 8, 8, 9,10,10, 0,10,10,11,12, 0,10, + 10,11,11, 0,12,12,13,13, 0, 0, 0,13,13, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 6, 8, 7,10,10, 0, 7, 7,10, 9, + 0, 7, 7,10,10, 0, 9, 8,10,10, 0, 0, 0,10,10, 6, + 7, 7,10,10, 0, 7, 7, 9,10, 0, 7, 7,10,10, 0, 8, + 9,10,10, 0, 0, 0,10,10, 8, 9, 9,11,11, 0,10, 9, + 11,11, 0,10,10,11,11, 0,11,11,12,12, 0, 0, 0,12, + 12, 8, 9, 9,11,11, 0, 9,10,11,11, 0,10,10,11,11, + 0,11,11,12,12, 0, 0, 0,12,12, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 5, 8, 7,10,10, 0, 7, 7,10,10, 0, 7, 7, + 10, 9, 0, 8, 9,10,10, 0, 0, 0,10, 9, 5, 7, 8,10, + 10, 0, 7, 7,10,10, 0, 7, 7, 9,10, 0, 9, 8,10,10, + 0, 0, 0,10,10, 8, 9, 9,12,11, 0,10,10,11,11, 0, + 10, 9,11,11, 0,11,11,12,12, 0, 0, 0,12,12, 8, 9, + 9,11,11, 0,10,10,11,11, 0, 9,10,11,11, 0,11,11, + 12,12, 0, 0, 0,12,12, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 9, 9,12,12, 0, 9, 9,11,11, 0, 8, 9,11,11, 0, + 10, 9,12,11, 0, 0, 0,11,11, 7, 9, 9,12,12, 0, 8, + 9,11,11, 0, 9, 8,11,11, 0, 9,10,11,11, 0, 0, 0, + 11,11, 9,11,11,13,13, 0,10,10,12,12, 0,10,10,12, + 12, 0,12,11,12,12, 0, 0, 0,13,13, 9,10,11,13,13, + 0,10,10,12,12, 0,10,10,12,12, 0,11,11,12,12, 0, + 0, 0,12,12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, + 11,10,13,13, 0,10,10,12,12, 0,10, 9,12,12, 0,11, + 11,12,13, 0, 0, 0,12,11, 9,10,10,12,13, 0,10,10, + 12,12, 0,10,10,12,12, 0,11,11,13,12, 0, 0, 0,12, + 12, +}; + +static float _vq_quantthresh__44c9_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c9_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p2_0 = { + _vq_quantthresh__44c9_s_p2_0, + _vq_quantmap__44c9_s_p2_0, + 5, + 5 +}; + +static static_codebook _44c9_s_p2_0 = { + 4, 625, + _vq_lengthlist__44c9_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c9_s_p2_0, + NULL, + &_vq_auxt__44c9_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44c9_s_p3_0[] = { + 3, 4, 4, 5, 5, 6, 6, 8, 8, 0, 4, 4, 5, 5, 7, 6, + 8, 8, 0, 4, 4, 5, 5, 7, 7, 8, 8, 0, 5, 5, 6, 6, + 7, 7, 9, 9, 0, 0, 0, 6, 6, 7, 7, 9, 9, 0, 0, 0, + 7, 7, 8, 8, 9, 9, 0, 0, 0, 7, 7, 8, 8, 9, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c9_s_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44c9_s_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p3_0 = { + _vq_quantthresh__44c9_s_p3_0, + _vq_quantmap__44c9_s_p3_0, + 9, + 9 +}; + +static static_codebook _44c9_s_p3_0 = { + 2, 81, + _vq_lengthlist__44c9_s_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44c9_s_p3_0, + NULL, + &_vq_auxt__44c9_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44c9_s_p4_0[] = { + 4, 4, 4, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9,10, + 10, 0, 5, 5, 5, 5, 6, 6, 8, 7, 8, 8, 9, 9,10,10, + 11,11, 0, 5, 5, 5, 5, 7, 7, 8, 8, 8, 8, 9, 9,10, + 10,11,11, 0, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 6, 6, 7, 7, 8, 8, 9, 9,10, + 10,11,11,12,12, 0, 0, 0, 7, 7, 7, 7, 9, 9, 9, 9, + 11,11,11,11,12,12, 0, 0, 0, 7, 7, 7, 8, 9, 9, 9, + 9,11,10,11,11,12,12, 0, 0, 0, 7, 7, 7, 7, 9, 9, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 7, 7, 9, + 9,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__44c9_s_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44c9_s_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p4_0 = { + _vq_quantthresh__44c9_s_p4_0, + _vq_quantmap__44c9_s_p4_0, + 17, + 17 +}; + +static static_codebook _44c9_s_p4_0 = { + 2, 289, + _vq_lengthlist__44c9_s_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44c9_s_p4_0, + NULL, + &_vq_auxt__44c9_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44c9_s_p5_0[] = { + 1, 4, 4, 5, 7, 7, 6, 7, 7, 4, 6, 6,10,10,10,10, + 10,10, 4, 6, 6,10,10,10,10,10,10, 5,10,10, 9,12, + 12,10,12,12, 7,10,10,12,12,12,12,12,13, 7,10,10, + 12,12,12,12,13,13, 6,10,10,10,12,12,11,12,12, 8, + 10,10,12,12,12,12,12,12, 7,10,10,12,12,13,12,12, + 12, +}; + +static float _vq_quantthresh__44c9_s_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44c9_s_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p5_0 = { + _vq_quantthresh__44c9_s_p5_0, + _vq_quantmap__44c9_s_p5_0, + 3, + 3 +}; + +static static_codebook _44c9_s_p5_0 = { + 4, 81, + _vq_lengthlist__44c9_s_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44c9_s_p5_0, + NULL, + &_vq_auxt__44c9_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c9_s_p5_1[] = { + 4, 5, 5, 6, 6, 6, 7, 7, 7, 7, 7,10, 5, 5, 6, 6, + 7, 7, 7, 7, 8, 8,10, 5, 5, 6, 6, 7, 7, 7, 7, 8, + 8,11, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8,11,11,11, 6, + 6, 7, 7, 8, 8, 8, 8,11,11,11, 6, 6, 7, 7, 8, 8, + 8, 8,11,11,11, 6, 6, 7, 7, 7, 7, 8, 8,11,11,11, + 7, 7, 7, 7, 7, 7, 8, 8,11,11,11,11,11, 7, 7, 7, + 7, 8, 8,11,11,11,11,11, 7, 7, 7, 7, 7, 7,11,11, + 11,11,11, 7, 7, 7, 7, 7, 7, +}; + +static float _vq_quantthresh__44c9_s_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c9_s_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p5_1 = { + _vq_quantthresh__44c9_s_p5_1, + _vq_quantmap__44c9_s_p5_1, + 11, + 11 +}; + +static static_codebook _44c9_s_p5_1 = { + 2, 121, + _vq_lengthlist__44c9_s_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c9_s_p5_1, + NULL, + &_vq_auxt__44c9_s_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c9_s_p6_0[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10, 5, 4, 4, + 6, 6, 8, 8, 9, 9,10,10,11,11, 6, 4, 4, 6, 6, 8, + 8, 9, 9,10,10,12,12, 0, 6, 6, 6, 6, 8, 8,10,10, + 11,11,12,12, 0, 6, 6, 6, 6, 8, 8,10,10,11,11,12, + 12, 0,10,10, 8, 8, 9, 9,11,11,12,12,13,13, 0,11, + 11, 8, 8, 9,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static float _vq_quantthresh__44c9_s_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44c9_s_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p6_0 = { + _vq_quantthresh__44c9_s_p6_0, + _vq_quantmap__44c9_s_p6_0, + 13, + 13 +}; + +static static_codebook _44c9_s_p6_0 = { + 2, 169, + _vq_lengthlist__44c9_s_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44c9_s_p6_0, + NULL, + &_vq_auxt__44c9_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44c9_s_p6_1[] = { + 4, 4, 4, 5, 5, 5, 4, 4, 5, 5, 5, 4, 4, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, +}; + +static float _vq_quantthresh__44c9_s_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44c9_s_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p6_1 = { + _vq_quantthresh__44c9_s_p6_1, + _vq_quantmap__44c9_s_p6_1, + 5, + 5 +}; + +static static_codebook _44c9_s_p6_1 = { + 2, 25, + _vq_lengthlist__44c9_s_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44c9_s_p6_1, + NULL, + &_vq_auxt__44c9_s_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44c9_s_p7_0[] = { + 1, 4, 4, 7, 7, 8, 8, 9, 9,10,10,11,11, 6, 5, 5, + 7, 7, 9, 8,10,10,11,11,12,12, 6, 5, 5, 7, 7, 9, + 9,10,10,11,11,12,12,20, 6, 6, 7, 7, 9, 9,10,10, + 11,11,12,12,20, 7, 7, 7, 7, 9, 9,10,10,11,11,13, + 12,20,10,10, 8, 8, 9, 9,11,11,12,12,13,13,20,11, + 11, 8, 8, 9, 9,11,11,12,12,13,13,20,20,20,10,10, + 10,10,11,11,12,12,13,13,20,20,20,10,10,10,10,11, + 11,12,12,13,13,20,20,20,14,14,11,11,12,12,13,13, + 14,13,20,20,20,14,15,11,11,11,11,13,13,14,13,20, + 20,20,20,19,12,12,12,12,13,13,14,14,19,19,19,19, + 19,13,13,12,12,13,13,14,14, +}; + +static float _vq_quantthresh__44c9_s_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__44c9_s_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p7_0 = { + _vq_quantthresh__44c9_s_p7_0, + _vq_quantmap__44c9_s_p7_0, + 13, + 13 +}; + +static static_codebook _44c9_s_p7_0 = { + 2, 169, + _vq_lengthlist__44c9_s_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__44c9_s_p7_0, + NULL, + &_vq_auxt__44c9_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44c9_s_p7_1[] = { + 5, 6, 6, 6, 6, 7, 6, 7, 7, 7, 7, 8, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 8, 6, 6, 6, 6, 7, 7, 7, 7, 7, + 7, 8, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 6, + 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 7, 7, 7, 7, 7, 7, + 7, 7, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, + 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 8, 8, + 8, 8, 8, 7, 7, 7, 7, 7, 7, +}; + +static float _vq_quantthresh__44c9_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44c9_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p7_1 = { + _vq_quantthresh__44c9_s_p7_1, + _vq_quantmap__44c9_s_p7_1, + 11, + 11 +}; + +static static_codebook _44c9_s_p7_1 = { + 2, 121, + _vq_lengthlist__44c9_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44c9_s_p7_1, + NULL, + &_vq_auxt__44c9_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44c9_s_p8_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11,11, 6, + 5, 5, 7, 7, 9, 9, 8, 9,10,10,11,11,12,12, 6, 5, + 5, 7, 7, 9, 9, 9, 9,10,10,11,11,12,12,19, 7, 8, + 8, 8, 9, 9, 9, 9,10,10,11,11,12,13,19, 8, 8, 8, + 8, 9, 9, 9, 9,10,10,11,12,12,12,19,12,12, 9, 9, + 9,10, 9,10,10,10,12,12,12,12,19,12,12, 9, 9,10, + 9,10,10,11,11,12,11,13,13,19,19,19, 9, 9, 9, 9, + 10,10,11,11,12,12,12,12,19,19,19, 9, 9, 9, 9,10, + 10,11,11,12,12,13,13,19,19,19,13,13,10,10,10,10, + 12,12,12,12,13,13,19,19,19,13,13,10,10,10,10,12, + 12,12,12,13,13,19,19,19,19,19,11,12,11,11,12,12, + 13,12,13,13,19,19,19,18,18,12,11,11,10,12,11,13, + 13,13,14,18,18,18,18,18,15,16,12,12,13,12,13,13, + 14,14,18,18,18,18,18,16,15,12,11,12,11,13,13,14, + 14, +}; + +static float _vq_quantthresh__44c9_s_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__44c9_s_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p8_0 = { + _vq_quantthresh__44c9_s_p8_0, + _vq_quantmap__44c9_s_p8_0, + 15, + 15 +}; + +static static_codebook _44c9_s_p8_0 = { + 2, 225, + _vq_lengthlist__44c9_s_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__44c9_s_p8_0, + NULL, + &_vq_auxt__44c9_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44c9_s_p8_1[] = { + 4, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,10, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 6, 6, 7, 7, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10, 7, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10, + 10, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9,10,10,10, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, + 10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10, + 10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9,10,10,10,10,10,10,10, 9, 9, 9, 9,10,10,10,10, + 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10, + 10,10, 9, 9, 9, 9, 9,10, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,10,10, + 10,10, 9, 9, 9,10, 9, 9, 9, 9,10,10,10,10,10,10, + 10,10,10, 9,10, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10, + 10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,10,10, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44c9_s_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44c9_s_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p8_1 = { + _vq_quantthresh__44c9_s_p8_1, + _vq_quantmap__44c9_s_p8_1, + 21, + 21 +}; + +static static_codebook _44c9_s_p8_1 = { + 2, 441, + _vq_lengthlist__44c9_s_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44c9_s_p8_1, + NULL, + &_vq_auxt__44c9_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p9_0[] = { + 9, + 8, + 10, + 7, + 11, + 6, + 12, + 5, + 13, + 4, + 14, + 3, + 15, + 2, + 16, + 1, + 17, + 0, + 18, +}; + +static long _vq_lengthlist__44c9_s_p9_0[] = { + 1, 4, 4,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10, 7, 9,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10, 7, 9,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44c9_s_p9_0[] = { + -7913.5, -6982.5, -6051.5, -5120.5, -4189.5, -3258.5, -2327.5, -1396.5, + -465.5, 465.5, 1396.5, 2327.5, 3258.5, 4189.5, 5120.5, 6051.5, + 6982.5, 7913.5, +}; + +static long _vq_quantmap__44c9_s_p9_0[] = { + 17, 15, 13, 11, 9, 7, 5, 3, + 1, 0, 2, 4, 6, 8, 10, 12, + 14, 16, 18, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p9_0 = { + _vq_quantthresh__44c9_s_p9_0, + _vq_quantmap__44c9_s_p9_0, + 19, + 19 +}; + +static static_codebook _44c9_s_p9_0 = { + 2, 361, + _vq_lengthlist__44c9_s_p9_0, + 1, -508535424, 1631393792, 5, 0, + _vq_quantlist__44c9_s_p9_0, + NULL, + &_vq_auxt__44c9_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p9_1[] = { + 9, + 8, + 10, + 7, + 11, + 6, + 12, + 5, + 13, + 4, + 14, + 3, + 15, + 2, + 16, + 1, + 17, + 0, + 18, +}; + +static long _vq_lengthlist__44c9_s_p9_1[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 8, 9,10,10,10,12,11,12, + 13,13,12, 6, 5, 5, 8, 8, 8, 9, 8, 9,10,10,11,11, + 12,12,14,13,13,12, 5, 5, 5, 8, 8, 9, 8, 8, 9,10, + 10,11,11,12,12,14,12,13,13,16, 8, 8, 9, 9, 9, 9, + 9, 9,10,10,11,12,13,13,13,13,14,14,16, 8, 8, 9, + 9, 9, 9, 9, 9,11,11,12,11,13,13,14,13,13,13,16, + 14,13, 9, 9, 9, 9,10,10,11,12,12,13,13,13,14,13, + 15,14,16,13,13, 9, 8, 9, 9,10,10,12,11,13,13,14, + 14,14,14,15,14,16,16,16, 9, 9, 9, 9,10,10,12,12, + 12,13,13,13,15,13,15,15,16,16,16, 9, 7, 9, 8,10, + 11,11,12,12,13,13,16,15,14,14,14,16,16,16,13,13, + 10,10,11,11,13,15,13,14,13,14,14,13,14,14,16,16, + 16,13,12,10, 9,11,11,12,12,14,14,13,14,14,14,14, + 14,16,16,16,16,16,12,13,11,11,12,13,13,13,14,15, + 14,14,16,15,16,16,16,16,16,12,11,12,12,15,13,13, + 13,14,13,15,14,15,14,16,16,16,16,16,14,15,12,13, + 13,12,14,15,15,14,15,14,15,13,16,16,16,16,16,16, + 16,13,13,14,12,16,12,16,15,14,15,14,14,16,16,16, + 16,16,16,16,15,14,14,14,15,16,16,16,16,14,16,16, + 16,16,16,16,16,16,16,14,14,14,12,15,11,15,13,16, + 15,16,15,16,16,16,16,16,16,16,15,16,14,14,15,13, + 15,16,16,16,16,15,16,16,16,16,16,16,16,16,16,15, + 15,14,13,14,16,16,14,15,16, +}; + +static float _vq_quantthresh__44c9_s_p9_1[] = { + -416.5, -367.5, -318.5, -269.5, -220.5, -171.5, -122.5, -73.5, + -24.5, 24.5, 73.5, 122.5, 171.5, 220.5, 269.5, 318.5, + 367.5, 416.5, +}; + +static long _vq_quantmap__44c9_s_p9_1[] = { + 17, 15, 13, 11, 9, 7, 5, 3, + 1, 0, 2, 4, 6, 8, 10, 12, + 14, 16, 18, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p9_1 = { + _vq_quantthresh__44c9_s_p9_1, + _vq_quantmap__44c9_s_p9_1, + 19, + 19 +}; + +static static_codebook _44c9_s_p9_1 = { + 2, 361, + _vq_lengthlist__44c9_s_p9_1, + 1, -518287360, 1622704128, 5, 0, + _vq_quantlist__44c9_s_p9_1, + NULL, + &_vq_auxt__44c9_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44c9_s_p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__44c9_s_p9_2[] = { + 3, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 6, 6, 7, 7, 6, 7, 6, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__44c9_s_p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__44c9_s_p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__44c9_s_p9_2 = { + _vq_quantthresh__44c9_s_p9_2, + _vq_quantmap__44c9_s_p9_2, + 49, + 49 +}; + +static static_codebook _44c9_s_p9_2 = { + 1, 49, + _vq_lengthlist__44c9_s_p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__44c9_s_p9_2, + NULL, + &_vq_auxt__44c9_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44c9_s_short[] = { + 5,13,17,15,16,16,18,17,18,18, 5, 7,10,12,12,12, + 12,17,18,17, 7, 6, 7, 8, 9, 9, 9,13,17,17, 8, 6, + 6, 5, 6, 7, 8,11,16,18, 9, 7, 7, 5, 5, 6, 7,10, + 15,18, 9, 8, 7, 6, 5, 4, 5, 7,13,17,11,11, 9, 8, + 7, 4, 3, 5,12,18,13,13,12,10, 7, 5, 4, 3, 8,13, + 16,16,16,13, 8, 8, 7, 5, 8,12,15,18,17,15,11,10, + 9, 8,10,14, +}; + +static static_codebook _huff_book__44c9_s_short = { + 2, 100, + _huff_lengthlist__44c9_s_short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8c0_s_p1_0[] = { + 1, 5, 4, 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 8, 8, 0, 0, 0, 0, 0, 0, 7, 8, 9, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 8, 8, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 7, 9, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 8, 0, 0, 0, 0, + 0, 0, 8,10,10, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,10, 9, 0, 0, 0, + 0, 0, 0, 8, 9,11, 0, 0, 0, 0, 0, 0, 9,11,11, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9,10, 0, 0, + 0, 0, 0, 0, 9,11,10, 0, 0, 0, 0, 0, 0, 9,11,11, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 8, 8, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, + 0, 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,11,11, 0, + 0, 0, 0, 0, 0, 9,10,11, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 9,10, 0, 0, 0, 0, 0, 0, 9,11,11, + 0, 0, 0, 0, 0, 0, 8,11, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c0_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__8c0_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p1_0 = { + _vq_quantthresh__8c0_s_p1_0, + _vq_quantmap__8c0_s_p1_0, + 3, + 3 +}; + +static static_codebook _8c0_s_p1_0 = { + 8, 6561, + _vq_lengthlist__8c0_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__8c0_s_p1_0, + NULL, + &_vq_auxt__8c0_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8c0_s_p2_0[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c0_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8c0_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p2_0 = { + _vq_quantthresh__8c0_s_p2_0, + _vq_quantmap__8c0_s_p2_0, + 5, + 5 +}; + +static static_codebook _8c0_s_p2_0 = { + 4, 625, + _vq_lengthlist__8c0_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8c0_s_p2_0, + NULL, + &_vq_auxt__8c0_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8c0_s_p3_0[] = { + 1, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7, 7, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 5, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 7, 8, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 7, 7, 8, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c0_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8c0_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p3_0 = { + _vq_quantthresh__8c0_s_p3_0, + _vq_quantmap__8c0_s_p3_0, + 5, + 5 +}; + +static static_codebook _8c0_s_p3_0 = { + 4, 625, + _vq_lengthlist__8c0_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8c0_s_p3_0, + NULL, + &_vq_auxt__8c0_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__8c0_s_p4_0[] = { + 1, 2, 3, 7, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 0, 0, 0, 0, 0, 0, 0, 9, 8, 0, 0, 0, 0, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c0_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__8c0_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p4_0 = { + _vq_quantthresh__8c0_s_p4_0, + _vq_quantmap__8c0_s_p4_0, + 9, + 9 +}; + +static static_codebook _8c0_s_p4_0 = { + 2, 81, + _vq_lengthlist__8c0_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__8c0_s_p4_0, + NULL, + &_vq_auxt__8c0_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__8c0_s_p5_0[] = { + 1, 3, 3, 5, 5, 7, 6, 8, 8, 0, 0, 0, 7, 7, 7, 7, + 8, 8, 0, 0, 0, 7, 7, 7, 7, 8, 9, 0, 0, 0, 8, 8, + 8, 8, 9, 9, 0, 0, 0, 8, 8, 8, 8, 9, 9, 0, 0, 0, + 9, 9, 8, 8,10,10, 0, 0, 0, 9, 9, 8, 8,10,10, 0, + 0, 0,10,10, 9, 9,10,10, 0, 0, 0, 0, 0, 9, 9,10, + 10, +}; + +static float _vq_quantthresh__8c0_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__8c0_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p5_0 = { + _vq_quantthresh__8c0_s_p5_0, + _vq_quantmap__8c0_s_p5_0, + 9, + 9 +}; + +static static_codebook _8c0_s_p5_0 = { + 2, 81, + _vq_lengthlist__8c0_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__8c0_s_p5_0, + NULL, + &_vq_auxt__8c0_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__8c0_s_p6_0[] = { + 1, 3, 3, 6, 6, 8, 8, 9, 9, 8, 8,10, 9,10,10,11, + 11, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11,11, + 11,12, 0, 0, 0, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11, + 11,12,11, 0, 0, 0, 8, 8, 9, 9,10,10, 9, 9,10,10, + 11,11,12,12, 0, 0, 0, 8, 8, 9, 9,10,10, 9, 9,11, + 10,11,11,12,12, 0, 0, 0, 9, 9, 9, 9,10,10,10,10, + 11,11,11,12,12,12, 0, 0, 0, 9, 9, 9, 9,10,10,10, + 10,11,11,12,12,13,13, 0, 0, 0,10,10,10,10,11,11, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0,10, 9,10, + 11,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9, + 10, 9,10,11,12,12,13,13,14,13, 0, 0, 0, 0, 0, 9, + 9, 9,10,10,10,11,11,13,12,13,13, 0, 0, 0, 0, 0, + 10,10,10,10,11,11,12,12,13,13,14,14, 0, 0, 0, 0, + 0, 0, 0,10,10,11,11,12,12,13,13,13,14, 0, 0, 0, + 0, 0, 0, 0,11,11,11,11,12,12,13,14,14,14, 0, 0, + 0, 0, 0, 0, 0,11,11,11,11,12,12,13,13,14,13, 0, + 0, 0, 0, 0, 0, 0,11,11,12,12,13,13,14,14,14,14, + 0, 0, 0, 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,14, + 14, +}; + +static float _vq_quantthresh__8c0_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__8c0_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p6_0 = { + _vq_quantthresh__8c0_s_p6_0, + _vq_quantmap__8c0_s_p6_0, + 17, + 17 +}; + +static static_codebook _8c0_s_p6_0 = { + 2, 289, + _vq_lengthlist__8c0_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__8c0_s_p6_0, + NULL, + &_vq_auxt__8c0_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8c0_s_p7_0[] = { + 1, 4, 4, 7, 6, 6, 7, 6, 6, 4, 7, 7,11, 9,10,12, + 9,10, 4, 7, 7,10,10,10,11, 9, 9, 6,11,10,11,11, + 12,11,11,11, 6,10,10,11,11,12,11,10,10, 6, 9,10, + 11,11,11,11,10,10, 7,10,11,12,11,11,12,11,12, 6, + 9, 9,10, 9, 9,11,10,10, 6, 9, 9,10,10,10,11,10, + 10, +}; + +static float _vq_quantthresh__8c0_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__8c0_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p7_0 = { + _vq_quantthresh__8c0_s_p7_0, + _vq_quantmap__8c0_s_p7_0, + 3, + 3 +}; + +static static_codebook _8c0_s_p7_0 = { + 4, 81, + _vq_lengthlist__8c0_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__8c0_s_p7_0, + NULL, + &_vq_auxt__8c0_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__8c0_s_p7_1[] = { + 1, 3, 3, 6, 6, 8, 8, 9, 9, 9, 9,10,10,10, 7, 7, + 8, 8, 9, 9, 9, 9,10,10, 9, 7, 7, 8, 8, 9, 9, 9, + 9,10,10,10, 8, 8, 9, 9, 9, 9, 9, 9,10,10,10, 8, + 8, 9, 9, 9, 9, 8, 9,10,10,10, 8, 8, 9, 9, 9,10, + 10,10,10,10,10, 9, 9, 9, 9, 9, 9,10,10,11,10,11, + 9, 9, 9, 9,10,10,10,10,11,11,11,10,10, 9, 9,10, + 10,10, 9,11,10,10,10,10,10,10, 9, 9,10,10,11,11, + 10,10,10, 9, 9, 9,10,10,10, +}; + +static float _vq_quantthresh__8c0_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__8c0_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p7_1 = { + _vq_quantthresh__8c0_s_p7_1, + _vq_quantmap__8c0_s_p7_1, + 11, + 11 +}; + +static static_codebook _8c0_s_p7_1 = { + 2, 121, + _vq_lengthlist__8c0_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__8c0_s_p7_1, + NULL, + &_vq_auxt__8c0_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__8c0_s_p8_0[] = { + 1, 4, 4, 7, 6, 7, 7, 7, 7, 8, 8, 9, 9, 7, 6, 6, + 7, 7, 8, 8, 7, 7, 8, 9,10,10, 7, 6, 6, 7, 7, 8, + 7, 7, 7, 9, 9,10,12, 0, 8, 8, 8, 8, 8, 9, 8, 8, + 9, 9,10,10, 0, 8, 8, 8, 8, 8, 9, 8, 9, 9, 9,11, + 10, 0, 0,13, 9, 8, 9, 9, 9, 9,10,10,11,11, 0,13, + 0, 9, 9, 9, 9, 9, 9,11,10,11,11, 0, 0, 0, 8, 9, + 10, 9,10,10,13,11,12,12, 0, 0, 0, 8, 9, 9, 9,10, + 10,13,12,12,13, 0, 0, 0,12, 0,10,10,12,11,10,11, + 12,12, 0, 0, 0,13,13,10,10,10,11,12, 0,13, 0, 0, + 0, 0, 0, 0,13,11, 0,12,12,12,13,12, 0, 0, 0, 0, + 0, 0,13,13,11,13,13,11,12, +}; + +static float _vq_quantthresh__8c0_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__8c0_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p8_0 = { + _vq_quantthresh__8c0_s_p8_0, + _vq_quantmap__8c0_s_p8_0, + 13, + 13 +}; + +static static_codebook _8c0_s_p8_0 = { + 2, 169, + _vq_lengthlist__8c0_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__8c0_s_p8_0, + NULL, + &_vq_auxt__8c0_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8c0_s_p8_1[] = { + 1, 3, 4, 5, 5, 7, 6, 6, 6, 5, 7, 7, 7, 6, 6, 7, + 7, 7, 6, 6, 7, 7, 7, 6, 6, +}; + +static float _vq_quantthresh__8c0_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8c0_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p8_1 = { + _vq_quantthresh__8c0_s_p8_1, + _vq_quantmap__8c0_s_p8_1, + 5, + 5 +}; + +static static_codebook _8c0_s_p8_1 = { + 2, 25, + _vq_lengthlist__8c0_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8c0_s_p8_1, + NULL, + &_vq_auxt__8c0_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p9_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8c0_s_p9_0[] = { + 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__8c0_s_p9_0[] = { + -157.5, 157.5, +}; + +static long _vq_quantmap__8c0_s_p9_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p9_0 = { + _vq_quantthresh__8c0_s_p9_0, + _vq_quantmap__8c0_s_p9_0, + 3, + 3 +}; + +static static_codebook _8c0_s_p9_0 = { + 4, 81, + _vq_lengthlist__8c0_s_p9_0, + 1, -518803456, 1628680192, 2, 0, + _vq_quantlist__8c0_s_p9_0, + NULL, + &_vq_auxt__8c0_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__8c0_s_p9_1[] = { + 1, 4, 4, 5, 5,10, 8,11,11,11,11,11,11,11,11, 6, + 6, 6, 7, 6,11,10,11,11,11,11,11,11,11,11, 7, 5, + 6, 6, 6, 8, 7,11,11,11,11,11,11,11,11,11, 7, 8, + 8, 8, 9, 9,11,11,11,11,11,11,11,11,11, 9, 8, 7, + 8, 9,11,11,11,11,11,11,11,11,11,11,11,10,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,10,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11, +}; + +static float _vq_quantthresh__8c0_s_p9_1[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__8c0_s_p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p9_1 = { + _vq_quantthresh__8c0_s_p9_1, + _vq_quantmap__8c0_s_p9_1, + 15, + 15 +}; + +static static_codebook _8c0_s_p9_1 = { + 2, 225, + _vq_lengthlist__8c0_s_p9_1, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__8c0_s_p9_1, + NULL, + &_vq_auxt__8c0_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__8c0_s_p9_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__8c0_s_p9_2[] = { + 1, 5, 5, 7, 7, 8, 7, 8, 8,10,10, 9, 9,10,10,10, + 11,11,10,12,11,12,12,12, 9, 8, 8, 8, 8, 8, 9,10, + 10,10,10,11,11,11,10,11,11,12,12,11,12, 8, 8, 7, + 7, 8, 9,10,10,10, 9,10,10, 9,10,10,11,11,11,11, + 11,11, 9, 9, 9, 9, 8, 9,10,10,11,10,10,11,11,12, + 10,10,12,12,11,11,10, 9, 9,10, 8, 9,10,10,10, 9, + 10,10,11,11,10,11,10,10,10,12,12,12, 9,10, 9,10, + 9, 9,10,10,11,11,11,11,10,10,10,11,12,11,12,11, + 12,10,11,10,11, 9,10, 9,10, 9,10,10, 9,10,10,11, + 10,11,11,11,11,12,11, 9,10,10,10,10,11,11,11,11, + 11,10,11,11,11,11,10,12,10,12,12,11,12,10,10,11, + 10, 9,11,10,11, 9,10,11,10,10,10,11,11,11,11,12, + 12,10, 9, 9,11,10, 9,12,11,10,12,12,11,11,11,11, + 10,11,11,12,11,10,12, 9,11,10,11,10,10,11,10,11, + 9,10,10,10,11,12,11,11,12,11,10,10,11,11, 9,10, + 10,12,10,11,10,10,10, 9,10,10,10,10, 9,10,10,11, + 11,11,11,12,11,10,10,10,10,11,11,10,11,11, 9,11, + 10,12,10,12,11,10,11,10,10,10,11,10,10,11,11,10, + 11,10,10,10,10,11,11,12,10,10,10,11,10,11,12,11, + 10,11,10,10,11,11,10,12,10, 9,10,10,11,11,11,10, + 12,10,10,11,11,11,10,10,11,10,10,10,11,10,11,10, + 12,11,11,10,10,10,12,10,10,11, 9,10,11,11,11,10, + 10,11,10,10, 9,11,11,12,12,11,12,11,11,11,11,11, + 11, 9,10,11,10,12,10,10,10,10,11,10,10,11,10,10, + 12,10,10,10,10,10, 9,12,10,10,10,10,12, 9,11,10, + 10,11,10,12,12,10,12,12,12,10,10,10,10, 9,10,11, + 10,10,12,10,10,12,11,10,11,10,10,12,11,10,12,10, + 10,11, 9,11,10, 9,10, 9,10, +}; + +static float _vq_quantthresh__8c0_s_p9_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__8c0_s_p9_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__8c0_s_p9_2 = { + _vq_quantthresh__8c0_s_p9_2, + _vq_quantmap__8c0_s_p9_2, + 21, + 21 +}; + +static static_codebook _8c0_s_p9_2 = { + 2, 441, + _vq_lengthlist__8c0_s_p9_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__8c0_s_p9_2, + NULL, + &_vq_auxt__8c0_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__8c0_s_single[] = { + 4, 5,18, 7,10, 6, 7, 8, 9,10, 5, 2,18, 5, 7, 5, + 6, 7, 8,11,17,17,17,17,17,17,17,17,17,17, 7, 4, + 17, 6, 9, 6, 8,10,12,15,11, 7,17, 9, 6, 6, 7, 9, + 11,15, 6, 4,17, 6, 6, 4, 5, 8,11,16, 6, 6,17, 8, + 6, 5, 6, 9,13,16, 8, 9,17,11, 9, 8, 8,11,13,17, + 9,12,17,15,14,13,12,13,14,17,12,15,17,17,17,17, + 17,16,17,17, +}; + +static static_codebook _huff_book__8c0_s_single = { + 2, 100, + _huff_lengthlist__8c0_s_single, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8c1_s_p1_0[] = { + 1, 5, 5, 0, 0, 0, 0, 0, 0, 5, 7, 7, 0, 0, 0, 0, + 0, 0, 5, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 8, 7, 0, 0, 0, 0, 0, 0, 7, 8, 9, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 7, 8, 0, 0, 0, 0, 0, 0, 7, 9, 8, 0, 0, + 0, 0, 0, 0, 7, 9, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 8, 8, 0, 0, 0, 0, + 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, + 0, 0, 0, 8, 8,10, 0, 0, 0, 0, 0, 0, 9,10,10, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 9, 0, 0, + 0, 0, 0, 0, 8,10, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 8, 8, 0, 0, 0, 0, 0, 0, 8, 9, 9, 0, 0, + 0, 0, 0, 0, 8, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, 0, + 0, 0, 0, 0, 0, 8, 9,10, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 7, 9, 9, 0, 0, 0, 0, 0, 0, 9,10,10, + 0, 0, 0, 0, 0, 0, 8,10, 8, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c1_s_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__8c1_s_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p1_0 = { + _vq_quantthresh__8c1_s_p1_0, + _vq_quantmap__8c1_s_p1_0, + 3, + 3 +}; + +static static_codebook _8c1_s_p1_0 = { + 8, 6561, + _vq_lengthlist__8c1_s_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__8c1_s_p1_0, + NULL, + &_vq_auxt__8c1_s_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8c1_s_p2_0[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c1_s_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8c1_s_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p2_0 = { + _vq_quantthresh__8c1_s_p2_0, + _vq_quantmap__8c1_s_p2_0, + 5, + 5 +}; + +static static_codebook _8c1_s_p2_0 = { + 4, 625, + _vq_lengthlist__8c1_s_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8c1_s_p2_0, + NULL, + &_vq_auxt__8c1_s_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8c1_s_p3_0[] = { + 2, 4, 4, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 4, 4, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 6, 6, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c1_s_p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8c1_s_p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p3_0 = { + _vq_quantthresh__8c1_s_p3_0, + _vq_quantmap__8c1_s_p3_0, + 5, + 5 +}; + +static static_codebook _8c1_s_p3_0 = { + 4, 625, + _vq_lengthlist__8c1_s_p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8c1_s_p3_0, + NULL, + &_vq_auxt__8c1_s_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p4_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__8c1_s_p4_0[] = { + 1, 2, 3, 7, 7, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 0, + 0, 0, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 7, 7, + 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 0, 0, 0, 0, 0, 0, 0, 9, 8, 0, 0, 0, 0, 0, + 0, 0,10,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +}; + +static float _vq_quantthresh__8c1_s_p4_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__8c1_s_p4_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p4_0 = { + _vq_quantthresh__8c1_s_p4_0, + _vq_quantmap__8c1_s_p4_0, + 9, + 9 +}; + +static static_codebook _8c1_s_p4_0 = { + 2, 81, + _vq_lengthlist__8c1_s_p4_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__8c1_s_p4_0, + NULL, + &_vq_auxt__8c1_s_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__8c1_s_p5_0[] = { + 1, 3, 3, 4, 5, 6, 6, 8, 8, 0, 0, 0, 8, 8, 7, 7, + 9, 9, 0, 0, 0, 8, 8, 7, 7, 9, 9, 0, 0, 0, 9,10, + 8, 8, 9, 9, 0, 0, 0,10,10, 8, 8, 9, 9, 0, 0, 0, + 11,10, 8, 8,10,10, 0, 0, 0,11,11, 8, 8,10,10, 0, + 0, 0,12,12, 9, 9,10,10, 0, 0, 0, 0, 0, 9, 9,10, + 10, +}; + +static float _vq_quantthresh__8c1_s_p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__8c1_s_p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p5_0 = { + _vq_quantthresh__8c1_s_p5_0, + _vq_quantmap__8c1_s_p5_0, + 9, + 9 +}; + +static static_codebook _8c1_s_p5_0 = { + 2, 81, + _vq_lengthlist__8c1_s_p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__8c1_s_p5_0, + NULL, + &_vq_auxt__8c1_s_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p6_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__8c1_s_p6_0[] = { + 1, 3, 3, 5, 5, 8, 8, 8, 8, 9, 9,10,10,11,11,11, + 11, 0, 0, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11,11, + 12,12, 0, 0, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11,12,12, 0, 0, 0, 9, 9, 8, 8,10,10,10,10,11,11, + 12,12,12,12, 0, 0, 0, 9, 9, 8, 8,10,10,10,10,11, + 11,12,12,12,12, 0, 0, 0,10,10, 9, 9,10,10,10,10, + 11,11,12,12,13,13, 0, 0, 0,10,10, 9, 9,10,10,10, + 10,11,11,12,12,13,13, 0, 0, 0,11,11, 9, 9,10,10, + 10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9,10, + 10,10,10,11,11,12,12,13,13, 0, 0, 0, 0, 0, 9, 9, + 10,10,11,11,12,12,12,12,13,13, 0, 0, 0, 0, 0, 9, + 9,10,10,11,11,12,11,12,12,13,13, 0, 0, 0, 0, 0, + 10,10,11,11,11,11,12,12,13,12,13,13, 0, 0, 0, 0, + 0, 0, 0,11,10,11,11,12,12,13,13,13,13, 0, 0, 0, + 0, 0, 0, 0,11,11,12,12,12,12,13,13,13,14, 0, 0, + 0, 0, 0, 0, 0,11,11,12,12,12,12,13,13,14,13, 0, + 0, 0, 0, 0, 0, 0,12,12,12,12,13,13,13,13,14,14, + 0, 0, 0, 0, 0, 0, 0, 0, 0,12,12,13,13,13,13,14, + 14, +}; + +static float _vq_quantthresh__8c1_s_p6_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__8c1_s_p6_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p6_0 = { + _vq_quantthresh__8c1_s_p6_0, + _vq_quantmap__8c1_s_p6_0, + 17, + 17 +}; + +static static_codebook _8c1_s_p6_0 = { + 2, 289, + _vq_lengthlist__8c1_s_p6_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__8c1_s_p6_0, + NULL, + &_vq_auxt__8c1_s_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8c1_s_p7_0[] = { + 1, 4, 4, 6, 6, 6, 7, 6, 6, 4, 7, 7,10, 9, 9,10, + 9, 9, 5, 7, 7,10, 9, 9,10, 9, 9, 6,10,10,10,10, + 10,11,10,10, 6, 9, 9,10, 9,10,11,10,10, 6, 9, 9, + 10, 9, 9,11, 9,10, 7,10,10,11,11,11,11,10,10, 6, + 9, 9,10,10,10,11, 9, 9, 6, 9, 9,10,10,10,10, 9, + 9, +}; + +static float _vq_quantthresh__8c1_s_p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__8c1_s_p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p7_0 = { + _vq_quantthresh__8c1_s_p7_0, + _vq_quantmap__8c1_s_p7_0, + 3, + 3 +}; + +static static_codebook _8c1_s_p7_0 = { + 4, 81, + _vq_lengthlist__8c1_s_p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__8c1_s_p7_0, + NULL, + &_vq_auxt__8c1_s_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__8c1_s_p7_1[] = { + 2, 3, 3, 5, 5, 7, 7, 7, 7, 7, 7,10,10, 9, 7, 7, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 7, 7, 7, 7, 8, 8, 8, + 8,10,10,10, 7, 7, 7, 7, 8, 8, 8, 8,10,10,10, 7, + 7, 7, 7, 8, 8, 8, 8,10,10,10, 8, 8, 8, 8, 8, 8, + 8, 8,10,10,10, 8, 8, 8, 8, 8, 8, 8, 8,10,10,10, + 8, 8, 8, 8, 8, 8, 8, 8,10,10,10,10,10, 8, 8, 8, + 8, 8, 8,10,10,10,10,10, 8, 8, 8, 8, 8, 8,10,10, + 10,10,10, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__8c1_s_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__8c1_s_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p7_1 = { + _vq_quantthresh__8c1_s_p7_1, + _vq_quantmap__8c1_s_p7_1, + 11, + 11 +}; + +static static_codebook _8c1_s_p7_1 = { + 2, 121, + _vq_lengthlist__8c1_s_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__8c1_s_p7_1, + NULL, + &_vq_auxt__8c1_s_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p8_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__8c1_s_p8_0[] = { + 1, 4, 4, 6, 6, 8, 8, 8, 8, 9, 9,10,10, 7, 5, 5, + 7, 7, 8, 8, 8, 8, 9,10,11,11, 7, 5, 5, 7, 7, 8, + 8, 9, 9,10,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9, + 9,10,11,11, 0, 8, 8, 8, 8, 9, 9, 9, 9,10,10,11, + 11, 0,12,12, 9, 9, 9, 9,10, 9,10,11,11,11, 0,13, + 12, 9, 8, 9, 9,10,10,11,11,12,11, 0, 0, 0, 9, 9, + 9, 9,10,10,11,11,12,12, 0, 0, 0,10,10, 9, 9,10, + 10,11,11,12,12, 0, 0, 0,13,13,10,10,11,11,12,11, + 13,12, 0, 0, 0,14,14,10,10,11,10,11,11,12,12, 0, + 0, 0, 0, 0,12,12,11,11,12,12,13,13, 0, 0, 0, 0, + 0,12,12,11,10,12,11,13,12, +}; + +static float _vq_quantthresh__8c1_s_p8_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__8c1_s_p8_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p8_0 = { + _vq_quantthresh__8c1_s_p8_0, + _vq_quantmap__8c1_s_p8_0, + 13, + 13 +}; + +static static_codebook _8c1_s_p8_0 = { + 2, 169, + _vq_lengthlist__8c1_s_p8_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__8c1_s_p8_0, + NULL, + &_vq_auxt__8c1_s_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p8_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8c1_s_p8_1[] = { + 2, 3, 3, 5, 5, 6, 6, 6, 5, 5, 6, 6, 6, 5, 5, 6, + 6, 6, 5, 5, 6, 6, 6, 5, 5, +}; + +static float _vq_quantthresh__8c1_s_p8_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8c1_s_p8_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p8_1 = { + _vq_quantthresh__8c1_s_p8_1, + _vq_quantmap__8c1_s_p8_1, + 5, + 5 +}; + +static static_codebook _8c1_s_p8_1 = { + 2, 25, + _vq_lengthlist__8c1_s_p8_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8c1_s_p8_1, + NULL, + &_vq_auxt__8c1_s_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__8c1_s_p9_0[] = { + 1, 3, 3,10,10,10,10,10,10,10,10,10,10, 5, 6, 6, + 10,10,10,10,10,10,10,10,10,10, 6, 7, 8,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__8c1_s_p9_0[] = { + -1732.5, -1417.5, -1102.5, -787.5, -472.5, -157.5, 157.5, 472.5, + 787.5, 1102.5, 1417.5, 1732.5, +}; + +static long _vq_quantmap__8c1_s_p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p9_0 = { + _vq_quantthresh__8c1_s_p9_0, + _vq_quantmap__8c1_s_p9_0, + 13, + 13 +}; + +static static_codebook _8c1_s_p9_0 = { + 2, 169, + _vq_lengthlist__8c1_s_p9_0, + 1, -513964032, 1628680192, 4, 0, + _vq_quantlist__8c1_s_p9_0, + NULL, + &_vq_auxt__8c1_s_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__8c1_s_p9_1[] = { + 1, 4, 4, 5, 5, 7, 7, 9, 9,11,11,12,12,13,13, 6, + 5, 5, 6, 6, 9, 9,10,10,12,12,12,13,15,14, 6, 5, + 5, 7, 7, 9, 9,10,10,12,12,12,13,14,13,17, 7, 7, + 8, 8,10,10,11,11,12,13,13,13,13,13,17, 7, 7, 8, + 8,10,10,11,11,13,13,13,13,14,14,17,11,11, 9, 9, + 11,11,12,12,12,13,13,14,15,13,17,12,12, 9, 9,11, + 11,12,12,13,13,13,13,14,16,17,17,17,11,12,12,12, + 13,13,13,14,15,14,15,15,17,17,17,12,12,11,11,13, + 13,14,14,15,14,15,15,17,17,17,15,15,13,13,14,14, + 15,14,15,15,16,15,17,17,17,15,15,13,13,13,14,14, + 15,15,15,15,16,17,17,17,17,16,14,15,14,14,15,14, + 14,15,15,15,17,17,17,17,17,14,14,16,14,15,15,15, + 15,15,15,17,17,17,17,17,17,16,16,15,17,15,15,14, + 17,15,17,16,17,17,17,17,16,15,14,15,15,15,15,15, + 15, +}; + +static float _vq_quantthresh__8c1_s_p9_1[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__8c1_s_p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p9_1 = { + _vq_quantthresh__8c1_s_p9_1, + _vq_quantmap__8c1_s_p9_1, + 15, + 15 +}; + +static static_codebook _8c1_s_p9_1 = { + 2, 225, + _vq_lengthlist__8c1_s_p9_1, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__8c1_s_p9_1, + NULL, + &_vq_auxt__8c1_s_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__8c1_s_p9_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__8c1_s_p9_2[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 8, 9, 9, 9, + 9, 9, 9, 9, 9,11,11,12, 7, 7, 7, 7, 8, 8, 9, 9, + 9, 9,10,10,10,10,10,10,10,10,11,11,11, 7, 7, 7, + 7, 8, 8, 9, 8, 9, 9, 9, 9, 9, 9,10,10,10,10,11, + 11,12, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9,10,10,10,10, + 10,10,10,10,11,11,11, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9,10,10,10,10,10,10,10,10,11,11,11, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,11,11, + 11, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9,10,10,10,10,10, + 10,10,10,11,12,11, 9, 9, 8, 9, 9, 9, 9, 9,10,10, + 10,10,10,10,10,10,10,10,11,11,11,11,11, 8, 8, 9, + 9, 9, 9,10,10,10,10,10,10,10,10,10,10,11,12,11, + 12,11, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10, + 10,10,11,11,11,11,11, 9, 9, 9, 9,10,10,10,10,10, + 10,10,10,10,10,10,10,12,11,12,11,11, 9, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10,10,10,12,11,11,11, + 11,11,11,10,10,10,10,10,10,10,10,10,10,10,10,10, + 11,11,11,12,11,11,12,11,10,10,10,10,10,10,10,10, + 10,10,10,10,11,10,11,11,11,11,11,11,11,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,11,11,12,11,12, + 11,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 11,11,12,11,12,11,11,11,11,10,10,10,10,10,10,10, + 10,10,10,10,10,11,11,12,11,11,12,11,11,12,10,10, + 11,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,10,10,10,10,10,10,10,10,10,10,10,10,12, + 12,11,12,11,11,12,12,12,11,11,10,10,10,10,10,10, + 10,10,10,11,12,12,11,12,12,11,12,11,11,11,11,10, + 10,10,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__8c1_s_p9_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__8c1_s_p9_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__8c1_s_p9_2 = { + _vq_quantthresh__8c1_s_p9_2, + _vq_quantmap__8c1_s_p9_2, + 21, + 21 +}; + +static static_codebook _8c1_s_p9_2 = { + 2, 441, + _vq_lengthlist__8c1_s_p9_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__8c1_s_p9_2, + NULL, + &_vq_auxt__8c1_s_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__8c1_s_single[] = { + 4, 6,18, 8,11, 8, 8, 9, 9,10, 4, 4,18, 5, 9, 5, + 6, 7, 8,10,18,18,18,18,17,17,17,17,17,17, 7, 5, + 17, 6,11, 6, 7, 8, 9,12,12, 9,17,12, 8, 8, 9,10, + 10,13, 7, 5,17, 6, 8, 4, 5, 6, 8,10, 6, 5,17, 6, + 8, 5, 4, 5, 7, 9, 7, 7,17, 8, 9, 6, 5, 5, 6, 8, + 8, 8,17, 9,11, 8, 6, 6, 6, 7, 9,10,17,12,12,10, + 9, 7, 7, 8, +}; + +static static_codebook _huff_book__8c1_s_single = { + 2, 100, + _huff_lengthlist__8c1_s_single, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + diff --git a/src/sound/oggvorbis/vorbissrc/books/floor/floor_books.h b/src/sound/oggvorbis/vorbissrc/books/floor/floor_books.h new file mode 100644 index 0000000..3f8e016 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/books/floor/floor_books.h @@ -0,0 +1,1479 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: static codebooks autogenerated by huff/huffbuld + last modified: $Id: floor_books.h,v 1.3 2002/07/11 06:41:01 xiphmont Exp $ + + ********************************************************************/ + +#include "codebook.h" +static long _huff_lengthlist_line_1024x27_0sub0[] = { + 5, 5, 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, + 6, 5, 6, 5, 6, 5, 7, 5, 7, 5, 7, 5, 8, 5, 8, 6, + 8, 6, 9, 6, 9, 6, 9, 6,10, 6,10, 6,11, 6,11, 6, + 11, 6,12, 6,12, 7,12, 7,12, 7,12, 7,12, 7,12, 7, + 12, 7,12, 7,12, 7,12, 7,12, 8,12, 8,11, 8,11, 8, + 12, 9,11, 9, 9,10,11, 9,12, 9,12,12,14,13,13,14, + 13,13,13,12,14,16,20,20,21,14,14,15,21,21,21,20, + 21,21,21,21,21,21,21,21,21,21,20,20,20,20,20,20, +}; + +static static_codebook _huff_book_line_1024x27_0sub0 = { + 1, 128, + _huff_lengthlist_line_1024x27_0sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_1sub0[] = { + 2, 5, 5, 4, 5, 4, 5, 4, 5, 4, 6, 4, 6, 5, 6, 5, + 7, 5, 7, 6, 8, 6, 8, 6, 8, 6, 9, 6,10, 6,10, 6, +}; + +static static_codebook _huff_book_line_1024x27_1sub0 = { + 1, 32, + _huff_lengthlist_line_1024x27_1sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_1sub1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 5,10, 4,10, 4, 9, 4, 9, 3, 9, 4, 9, 4, 9, 4, + 9, 4, 9, 4, 9, 4, 8, 4, 8, 4, 8, 5, 9, 5, 9, 6, + 8, 6, 9, 7,10, 8,10, 9,10,10,10,12,11,13,12,13, + 13,15,13,14,13,14,12,15,13,15,14,15,13,16,14,16, + 14,15,14,14,14,16,15,18,15,18,16,18,18,18,18,18, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,17, +}; + +static static_codebook _huff_book_line_1024x27_1sub1 = { + 1, 128, + _huff_lengthlist_line_1024x27_1sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_2sub0[] = { + 1, 5, 5, 5, 5, 5, 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, + 6, 6, 7, 6, 7, 7, 8, 8, 9, 8, 9, 9, 9, 9,10,10, +}; + +static static_codebook _huff_book_line_1024x27_2sub0 = { + 1, 32, + _huff_lengthlist_line_1024x27_2sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_2sub1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 3, 4, 3, 4, 4, 5, 4, 5, 5, 5, 5, 6, 5, 6, 6, + 7, 6, 7, 7, 7, 7, 7, 7, 8, 9, 8, 9, 8,10, 8,11, + 8,12, 9,13, 9,14, 9,14, 8,12, 8,14, 9,14, 8,12, + 8,11, 8,11, 8,11, 9,11,10,11,10,12,10,12,11,12, + 12,12,12,12,11,12,11,13,11,13,12,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,13, +}; + +static static_codebook _huff_book_line_1024x27_2sub1 = { + 1, 128, + _huff_lengthlist_line_1024x27_2sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_3sub1[] = { + 0, 4, 5, 4, 5, 3, 5, 3, 5, 3, 5, 4, 4, 4, 5, 4, + 5, 5, +}; + +static static_codebook _huff_book_line_1024x27_3sub1 = { + 1, 18, + _huff_lengthlist_line_1024x27_3sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_3sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 3, 3, 3, 4, 4, 4, 5, 4, 6, 5, 6, 5, 7, + 5, 9, 5,10, 6,11, 6,12, 7,13, 8,15, 8,15, 9,15, + 9,15, +}; + +static static_codebook _huff_book_line_1024x27_3sub2 = { + 1, 50, + _huff_lengthlist_line_1024x27_3sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_3sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 9, 2, 9, 2, 9, 4, 9, 6, 9, 7, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static static_codebook _huff_book_line_1024x27_3sub3 = { + 1, 128, + _huff_lengthlist_line_1024x27_3sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_4sub1[] = { + 0, 4, 5, 4, 5, 4, 6, 3, 6, 3, 5, 3, 5, 3, 6, 4, + 6, 4, +}; + +static static_codebook _huff_book_line_1024x27_4sub1 = { + 1, 18, + _huff_lengthlist_line_1024x27_4sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_4sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 2, 4, 2, 5, 3, 5, 4, 6, 6, 6, 6, 7, 7, + 7, 8, 8, 8, 8, 8, 9, 8, 9, 9, 9, 9, 9,10,10,11, + 10,11, +}; + +static static_codebook _huff_book_line_1024x27_4sub2 = { + 1, 50, + _huff_lengthlist_line_1024x27_4sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_4sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 3, 3, 6, 4, 6, 4, 5, 5, 7, 4, 8, 5, 9, + 4, 9, 5, 9, 5, 9, 6, 9, 5, 9, 7, 9, 7, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static static_codebook _huff_book_line_1024x27_4sub3 = { + 1, 128, + _huff_lengthlist_line_1024x27_4sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_class1[] = { + 2, 9, 8,14, 7,13,11,14, 1, 5, 3, 7, 4,10, 7,12, +}; + +static static_codebook _huff_book_line_1024x27_class1 = { + 1, 16, + _huff_lengthlist_line_1024x27_class1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_class2[] = { + 1, 3, 2, 5, 4, 7, 6, 7, +}; + +static static_codebook _huff_book_line_1024x27_class2 = { + 1, 8, + _huff_lengthlist_line_1024x27_class2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_class3[] = { + 1, 5, 6,19, 5, 8,10,19, 9,10,15,19,19,19,19,19, + 4, 7, 9,19, 6, 7,10,19,11,11,15,19,19,19,19,19, + 8,11,13,19, 8,11,14,19,13,13,17,19,19,19,19,19, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 3, 7, 9,19, 6, 8,11,19,11,11,15,19,19,19,19,19, + 5, 7,11,19, 6, 7,11,19,11,10,14,19,19,19,19,19, + 8,11,15,19, 8,10,14,19,13,13,16,19,19,19,19,19, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 6, 9,11,19, 8,10,12,19,15,14,17,19,19,19,19,19, + 5, 8,11,19, 7, 9,12,19,14,11,16,19,19,19,19,19, + 9,10,18,19, 9,10,15,19,14,16,19,19,19,19,19,19, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, + 16,17,19,19,16,17,17,19,19,19,19,19,19,19,19,19, + 12,14,16,19,12,12,16,19,19,19,19,19,19,19,19,19, + 18,18,19,19,17,16,19,19,19,19,19,19,19,19,19,19, + 19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,18, +}; + +static static_codebook _huff_book_line_1024x27_class3 = { + 1, 256, + _huff_lengthlist_line_1024x27_class3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_1024x27_class4[] = { + 1, 4, 8,12, 4, 6, 8,21, 9, 8,10,21,20,16,18,20, + 2, 6, 8,20, 7, 6, 9,19,11, 9,10,20,17,15,16,20, + 5, 8,11,19, 8, 8,10,15,12,10,12,15,20,20,15,20, + 17,20,20,20,15,20,20,19,20,20,16,19,20,20,20,20, +}; + +static static_codebook _huff_book_line_1024x27_class4 = { + 1, 64, + _huff_lengthlist_line_1024x27_class4, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_0sub0[] = { + 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, + 6, 5, 6, 5, 6, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, + 8, 7, 8, 7, 8, 7, 8, 7, 9, 7, 9, 7, 9, 8,10, 8, + 10, 8,10, 8,10, 8,10, 9,11, 9,11, 9,10, 9,10,10, + 11,10,11,11,11,11,12,12,13,14,13,14,16,16,16,16, + 16,16,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,15,15,15, +}; + +static static_codebook _huff_book_line_128x11_0sub0 = { + 1, 128, + _huff_lengthlist_line_128x11_0sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_1sub0[] = { + 2, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 5, 6, 5, 6, 5, 7, 6, 7, 6, 7, 6, 8, 6, 8, 6, +}; + +static static_codebook _huff_book_line_128x11_1sub0 = { + 1, 32, + _huff_lengthlist_line_128x11_1sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_1sub1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 6, 3, 6, 3, 7, 3, 7, 4, 8, 4, 8, 4, 8, 4, 9, 4, + 10, 5, 9, 5,10, 5,10, 5,10, 5,12, 6,12, 6,10, 6, + 10, 7,10, 8,10, 8,10, 9,11, 9,12,11,10,11,11,13, + 12,12,12,13,10,13,10,13,10,13,10,13,11,13,10,13, + 10,13,10,13,10,13,10,13,11,12,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, +}; + +static static_codebook _huff_book_line_128x11_1sub1 = { + 1, 128, + _huff_lengthlist_line_128x11_1sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_2sub1[] = { + 0, 4, 5, 4, 5, 4, 5, 3, 4, 3, 4, 4, 4, 4, 4, 5, + 5, 5, +}; + +static static_codebook _huff_book_line_128x11_2sub1 = { + 1, 18, + _huff_lengthlist_line_128x11_2sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_2sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 3, 3, 4, 3, 5, 4, 5, 5, 6, 5, 6, 6, 6, + 6, 8, 6,10, 7,10, 8,10, 8,10,10,10,10,10,10,10, + 10,10, +}; + +static static_codebook _huff_book_line_128x11_2sub2 = { + 1, 50, + _huff_lengthlist_line_128x11_2sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_2sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +}; + +static static_codebook _huff_book_line_128x11_2sub3 = { + 1, 128, + _huff_lengthlist_line_128x11_2sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_3sub1[] = { + 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 4, + 5, 4, +}; + +static static_codebook _huff_book_line_128x11_3sub1 = { + 1, 18, + _huff_lengthlist_line_128x11_3sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_3sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 3, 5, 3, 6, 4, 7, 4, 7, 4, 7, 4, 8, 4, + 8, 4, 9, 4, 9, 4, 9, 5,10, 5,11, 5,12, 6,13, 6, + 13, 7, +}; + +static static_codebook _huff_book_line_128x11_3sub2 = { + 1, 50, + _huff_lengthlist_line_128x11_3sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_3sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 2, 6, 2, 7, 3, 8, 4, 7, 6, 9, 7, 9, 7, + 9, 9, 9, 8, 9, 8, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static static_codebook _huff_book_line_128x11_3sub3 = { + 1, 128, + _huff_lengthlist_line_128x11_3sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_class1[] = { + 1, 6, 3, 7, 2, 5, 4, 7, +}; + +static static_codebook _huff_book_line_128x11_class1 = { + 1, 8, + _huff_lengthlist_line_128x11_class1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_class2[] = { + 1, 5,11,14, 4,11,13,14,10,14,13,14,14,14,13,13, + 2, 6,11,13, 5,11,12,13,11,12,13,13,13,13,13,13, + 4, 8,12,13, 5, 9,11,13,12,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, +}; + +static static_codebook _huff_book_line_128x11_class2 = { + 1, 64, + _huff_lengthlist_line_128x11_class2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x11_class3[] = { + 6, 7,11,16, 6, 7,10,16,11, 9,13,15,15,15,15,15, + 4, 4, 7,14, 4, 4, 6,14, 8, 6, 8,15,15,15,15,15, + 4, 4, 6,15, 3, 2, 4,13, 6, 5, 6,14,15,12,11,14, + 11,11,13,15, 9, 8,10,15,11,10,11,15,15,15,15,15, +}; + +static static_codebook _huff_book_line_128x11_class3 = { + 1, 64, + _huff_lengthlist_line_128x11_class3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_0sub0[] = { + 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 6, 5, 6, 5, 6, 5, + 6, 5, 6, 5, 6, 5, 6, 5, 7, 5, 7, 5, 7, 6, 7, 6, + 7, 6, 8, 6, 8, 6, 8, 6, 8, 6, 8, 6, 8, 7, 9, 7, + 9, 7, 9, 7, 9, 7, 9, 7, 9, 7,10, 7,10, 8,10, 8, + 11, 8,11, 8,11, 8,12, 8,12, 8,12, 8,12, 9,12, 9, + 12, 9,12, 9,13, 9,13,10,13,10,13,11,14,12,14,13, + 14,14,16,15,17,17,19,18,19,19,19,19,19,19,19,19, + 19,19,19,19,19,19,19,19,19,19,19,18,18,18,18,18, +}; + +static static_codebook _huff_book_line_128x17_0sub0 = { + 1, 128, + _huff_lengthlist_line_128x17_0sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_1sub0[] = { + 2, 5, 5, 4, 5, 4, 5, 4, 5, 4, 5, 5, 5, 5, 6, 5, + 6, 5, 6, 6, 7, 6, 7, 6, 8, 6, 8, 7, 9, 7, 9, 8, +}; + +static static_codebook _huff_book_line_128x17_1sub0 = { + 1, 32, + _huff_lengthlist_line_128x17_1sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_1sub1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 2, 5, 3, 5, 3, 6, 4, 6, 4, 7, 4, 8, 5, 8, 5, + 8, 6, 9, 6, 9, 7, 9, 8,10, 8,10, 9,10,10,10,10, + 10,12,10,14,11,15,12,15,11,15,11,15,11,14,11,15, + 11,14,11,13,10,12,10,14,10,14,11,13,10,12,11,15, + 12,15,13,15,12,13,14,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,14,14,14, +}; + +static static_codebook _huff_book_line_128x17_1sub1 = { + 1, 128, + _huff_lengthlist_line_128x17_1sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_2sub1[] = { + 0, 4, 5, 4, 7, 3, 8, 3, 9, 3,10, 2,12, 3,12, 4, + 11, 6, +}; + +static static_codebook _huff_book_line_128x17_2sub1 = { + 1, 18, + _huff_lengthlist_line_128x17_2sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_2sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 1, 8, 2, 9, 3, 9, 7, 9, 7, 9, 7, 8, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, +}; + +static static_codebook _huff_book_line_128x17_2sub2 = { + 1, 50, + _huff_lengthlist_line_128x17_2sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_2sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +}; + +static static_codebook _huff_book_line_128x17_2sub3 = { + 1, 128, + _huff_lengthlist_line_128x17_2sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_3sub1[] = { + 0, 4, 4, 4, 5, 4, 5, 3, 5, 3, 5, 3, 5, 4, 6, 4, + 6, 4, +}; + +static static_codebook _huff_book_line_128x17_3sub1 = { + 1, 18, + _huff_lengthlist_line_128x17_3sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_3sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 3, 6, 3, 6, 3, 7, 4, 8, 4, 8, 4, 9, 4, + 9, 4,10, 4,10, 5,11, 5,11, 5,12, 5,12, 6,12, 6, + 12, 7, +}; + +static static_codebook _huff_book_line_128x17_3sub2 = { + 1, 50, + _huff_lengthlist_line_128x17_3sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_3sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 7, 1, 7, 3, 6, 3, 6, 4, 6, 5, 6, 8, 7,10, + 7,11, 7,11, 8,11, 9,11, 7,11, 8,11, 8,11,10,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, +}; + +static static_codebook _huff_book_line_128x17_3sub3 = { + 1, 128, + _huff_lengthlist_line_128x17_3sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_class1[] = { + 1, 3, 4, 7, 2, 6, 5, 7, +}; + +static static_codebook _huff_book_line_128x17_class1 = { + 1, 8, + _huff_lengthlist_line_128x17_class1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_class2[] = { + 1, 2, 7,14, 4, 9,13,14, 8,14,14,14,14,14,14,14, + 3, 5,10,14, 8,14,14,14,11,14,14,14,14,14,14,14, + 7,10,14,14,12,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,13, +}; + +static static_codebook _huff_book_line_128x17_class2 = { + 1, 64, + _huff_lengthlist_line_128x17_class2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x17_class3[] = { + 2, 6,11,19, 5, 9,12,19,10,11,13,19,19,19,19,19, + 2, 5, 8,19, 4, 6, 8,19, 8, 8, 9,19,19,16,19,19, + 3, 6, 8,19, 3, 5, 7,18, 8, 8, 9,16,16,11,16,19, + 14,14,14,19,10,10,11,19,16,12,14,19,19,19,19,19, +}; + +static static_codebook _huff_book_line_128x17_class3 = { + 1, 64, + _huff_lengthlist_line_128x17_class3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x4_0sub0[] = { + 2, 2, 2, 2, +}; + +static static_codebook _huff_book_line_128x4_0sub0 = { + 1, 4, + _huff_lengthlist_line_128x4_0sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x4_0sub1[] = { + 0, 0, 0, 0, 3, 2, 3, 2, 3, 3, +}; + +static static_codebook _huff_book_line_128x4_0sub1 = { + 1, 10, + _huff_lengthlist_line_128x4_0sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x4_0sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 3, 4, 4, 4, 4, + 4, 3, 4, 4, 5, 3, 6, 4, 6, +}; + +static static_codebook _huff_book_line_128x4_0sub2 = { + 1, 25, + _huff_lengthlist_line_128x4_0sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x4_0sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 2, 6, 3, 6, 3, + 7, 4, 7, 5, 7, 5, 7, 6, 8, 8, 9,10,10,16,10,16, + 10,14,10,14,11,15,15,15,15,15,15,15,15,15,15,15, +}; + +static static_codebook _huff_book_line_128x4_0sub3 = { + 1, 64, + _huff_lengthlist_line_128x4_0sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x4_class0[] = { + 8, 8, 8,14, 7, 7, 8,13, 7, 6, 7,11,11,11,10,13, + 9, 9,10,16, 8, 8, 9,12, 7, 7, 7,11,11,11,10,12, + 11,11,11,14,10,10,10,14, 9, 8, 9,12,13,14,12,14, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 8, 7, 8,11, 7, 7, 7,11, 6, 6, 6,10,10,10,10,14, + 8, 8, 8,12, 7, 7, 8,11, 6, 7, 7,10,10,10,10,13, + 10,10,10,12,10, 9, 9,12, 9, 9, 9,12,12,13,12,14, + 16,16,16,16,16,14,16,16,14,14,16,16,16,16,16,16, + 7, 6, 5, 7, 6, 6, 5, 6, 6, 5, 5, 5, 9,10, 9,10, + 8, 7, 6, 7, 7, 6, 5, 6, 6, 6, 5, 6,10,10, 9, 9, + 10, 9, 7, 8, 9, 8, 7, 7, 8, 7, 6, 7,11,11,10, 9, + 16,13,14,16,14,14,13,15,16,14,12,13,16,16,14,16, + 9, 8, 7, 8, 8, 8, 7, 8, 8, 7, 6, 7,10,10, 9,12, + 9, 9, 8, 8, 9, 8, 7, 8, 8, 8, 6, 7,10,10, 9,12, + 11,10, 9,10,10, 9, 7, 9, 9, 8, 6, 9,12,11,10,13, + 12,16,16,16,12,13,12,16,15,12,11,14,16,16,16,16, +}; + +static static_codebook _huff_book_line_128x4_class0 = { + 1, 256, + _huff_lengthlist_line_128x4_class0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_0sub1[] = { + 0, 3, 3, 3, 3, 3, 3, 3, 3, +}; + +static static_codebook _huff_book_line_128x7_0sub1 = { + 1, 9, + _huff_lengthlist_line_128x7_0sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_0sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 3, 4, 4, 4, + 5, 4, 5, 4, 5, 4, 6, 4, 6, +}; + +static static_codebook _huff_book_line_128x7_0sub2 = { + 1, 25, + _huff_lengthlist_line_128x7_0sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_0sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 5, 3, 5, 3, 6, 4, + 6, 4, 6, 4, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 5, 5, + 6, 7, 8,10,12,12,12,12,12,12,12,12,12,12,12,12, +}; + +static static_codebook _huff_book_line_128x7_0sub3 = { + 1, 64, + _huff_lengthlist_line_128x7_0sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_1sub1[] = { + 0, 3, 3, 3, 3, 2, 4, 3, 4, +}; + +static static_codebook _huff_book_line_128x7_1sub1 = { + 1, 9, + _huff_lengthlist_line_128x7_1sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_1sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 5, 3, 6, 3, 7, 3, + 8, 3, 9, 3,10, 3,11, 4,11, +}; + +static static_codebook _huff_book_line_128x7_1sub2 = { + 1, 25, + _huff_lengthlist_line_128x7_1sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_1sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,12, 2, 9, 3,10, 4, + 12, 5,12, 6,12,10,12,11,12,12,12,12,12,12,12,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, +}; + +static static_codebook _huff_book_line_128x7_1sub3 = { + 1, 64, + _huff_lengthlist_line_128x7_1sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_class0[] = { + 10, 7, 8,14,10, 7, 7,12,11, 8, 8,13,16,16,16,16, + 8, 5, 5,10, 7, 4, 4, 8, 8, 5, 5, 9,16,16,16,16, + 7, 4, 5, 7, 6, 3, 3, 6, 8, 5, 5, 7,16,14,13,16, + 9, 7, 7,10, 7, 4, 4, 7, 9, 6, 5, 6,15,13,11,14, +}; + +static static_codebook _huff_book_line_128x7_class0 = { + 1, 64, + _huff_lengthlist_line_128x7_class0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_128x7_class1[] = { + 8,12,16,16,10,14,16,16,12,15,16,16,16,16,16,16, + 7,11,15,16, 7,11,16,16,10,12,16,16,16,16,16,16, + 9,15,16,16, 9,12,16,16,11,15,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 6,10,13,16, 7,10,15,16, 9,11,16,16,16,16,16,16, + 4, 8,13,16, 5, 8,15,16, 7, 9,15,16,16,16,16,16, + 6,12,16,16, 6,10,15,16, 9,10,14,16,16,16,16,16, + 14,16,16,16,12,14,16,16,15,16,16,16,16,16,16,16, + 4, 9,11,16, 5, 9,13,16, 7, 9,15,16,16,16,16,16, + 2, 7,11,16, 3, 6,11,16, 5, 7,12,16,16,16,16,16, + 4, 9,14,16, 4, 7,13,16, 6, 8,13,16,16,16,16,16, + 11,14,16,16,10,16,14,16,11,16,16,16,16,16,16,16, + 7,13,16,16, 9,13,15,16,11,13,16,16,16,16,16,16, + 5,10,14,16, 6,10,15,16, 9,10,16,16,16,16,16,16, + 7,13,16,16, 6,10,16,16, 9,12,16,16,16,16,16,16, + 11,16,16,16,10,16,16,16,13,16,16,16,16,16,16,16, +}; + +static static_codebook _huff_book_line_128x7_class1 = { + 1, 256, + _huff_lengthlist_line_128x7_class1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x4_0sub0[] = { + 1, 3, 2, 3, +}; + +static static_codebook _huff_book_line_256x4_0sub0 = { + 1, 4, + _huff_lengthlist_line_256x4_0sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x4_0sub1[] = { + 0, 0, 0, 0, 2, 2, 3, 3, 3, 3, +}; + +static static_codebook _huff_book_line_256x4_0sub1 = { + 1, 10, + _huff_lengthlist_line_256x4_0sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x4_0sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 4, 3, 4, + 4, 4, 5, 4, 5, 5, 6, 4, 6, +}; + +static static_codebook _huff_book_line_256x4_0sub2 = { + 1, 25, + _huff_lengthlist_line_256x4_0sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x4_0sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 3, 4, 3, 4, 4, + 5, 4, 5, 5, 6, 5, 7, 6, 8, 7, 9, 8, 9, 9,10,12, + 15,15,15,15,15,15,15,15,14,14,14,14,14,14,14,14, +}; + +static static_codebook _huff_book_line_256x4_0sub3 = { + 1, 64, + _huff_lengthlist_line_256x4_0sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x4_class0[] = { + 4, 5, 6,11, 5, 5, 6,10, 7, 6, 5, 6,14,13,10,10, + 6, 6, 6,10, 6, 6, 6,10, 7, 7, 7, 9, 9,10, 9,12, + 9, 8, 8,11, 8, 8, 8,10, 8, 8, 9,11, 8, 9, 9,13, + 18,18,18,18,16,17,18,18,12,13,14,18,14,14,10,12, + 5, 5, 6,12, 6, 5, 6,10, 7, 7, 6, 7,13,12, 9,12, + 6, 6, 6,11, 6, 6, 6, 9, 7, 7, 7,10,10,10, 9,12, + 9, 8, 8,12, 8, 8, 7,11, 8, 8, 8,11, 9, 9, 8,10, + 18,18,17,18,18,18,15,18,16,14,12,18,14,12,10,11, + 8, 7, 7,12, 8, 7, 7,10, 8, 7, 6, 6,11,10, 9,10, + 8, 8, 7,11, 8, 7, 7, 9, 8, 8, 7, 9,10,10, 9, 9, + 10, 9, 8,12, 9, 9, 8,11,10, 9, 8, 9, 8, 8, 7, 9, + 18,18,18,18,18,18,18,18,17,16,14,18,13,12,11,13, + 12,12,13,16,11,10,10,16,12,12, 9, 7,15,12,11,11, + 14,14,14,14,12,11,11,18,14,12,10,11,18,13,11,13, + 18,18,18,17,17,16,15,17,18,15,16,18,16,12,11,11, + 18,18,18,18,18,18,18,18,18,18,17,18,18,13,12,15, +}; + +static static_codebook _huff_book_line_256x4_class0 = { + 1, 256, + _huff_lengthlist_line_256x4_class0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_0sub1[] = { + 0, 2, 3, 3, 3, 3, 4, 3, 4, +}; + +static static_codebook _huff_book_line_256x7_0sub1 = { + 1, 9, + _huff_lengthlist_line_256x7_0sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_0sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 3, 4, 3, 5, 3, + 6, 3, 6, 4, 6, 4, 7, 5, 7, +}; + +static static_codebook _huff_book_line_256x7_0sub2 = { + 1, 25, + _huff_lengthlist_line_256x7_0sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_0sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 5, 2, 5, 3, 5, 3, + 6, 3, 6, 4, 7, 6, 7, 8, 7, 9, 8, 9, 9, 9,10, 9, + 11,13,11,13,10,10,13,13,13,13,13,13,12,12,12,12, +}; + +static static_codebook _huff_book_line_256x7_0sub3 = { + 1, 64, + _huff_lengthlist_line_256x7_0sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_1sub1[] = { + 0, 3, 3, 3, 3, 2, 4, 3, 4, +}; + +static static_codebook _huff_book_line_256x7_1sub1 = { + 1, 9, + _huff_lengthlist_line_256x7_1sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_1sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 4, 3, 4, 4, + 5, 4, 6, 5, 6, 7, 6, 8, 8, +}; + +static static_codebook _huff_book_line_256x7_1sub2 = { + 1, 25, + _huff_lengthlist_line_256x7_1sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_1sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 4, 3, 6, 3, 7, + 3, 8, 5, 8, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, +}; + +static static_codebook _huff_book_line_256x7_1sub3 = { + 1, 64, + _huff_lengthlist_line_256x7_1sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_class0[] = { + 7, 5, 5, 9, 9, 6, 6, 9,12, 8, 7, 8,11, 8, 9,15, + 6, 3, 3, 7, 7, 4, 3, 6, 9, 6, 5, 6, 8, 6, 8,15, + 8, 5, 5, 9, 8, 5, 4, 6,10, 7, 5, 5,11, 8, 7,15, + 14,15,13,13,13,13, 8,11,15,10, 7, 6,11, 9,10,15, +}; + +static static_codebook _huff_book_line_256x7_class0 = { + 1, 64, + _huff_lengthlist_line_256x7_class0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_256x7_class1[] = { + 5, 6, 8,15, 6, 9,10,15,10,11,12,15,15,15,15,15, + 4, 6, 7,15, 6, 7, 8,15, 9, 8, 9,15,15,15,15,15, + 6, 8, 9,15, 7, 7, 8,15,10, 9,10,15,15,15,15,15, + 15,13,15,15,15,10,11,15,15,13,13,15,15,15,15,15, + 4, 6, 7,15, 6, 8, 9,15,10,10,12,15,15,15,15,15, + 2, 5, 6,15, 5, 6, 7,15, 8, 6, 7,15,15,15,15,15, + 5, 6, 8,15, 5, 6, 7,15, 9, 6, 7,15,15,15,15,15, + 14,12,13,15,12,10,11,15,15,15,15,15,15,15,15,15, + 7, 8, 9,15, 9,10,10,15,15,14,14,15,15,15,15,15, + 5, 6, 7,15, 7, 8, 9,15,12, 9,10,15,15,15,15,15, + 7, 7, 9,15, 7, 7, 8,15,12, 8, 9,15,15,15,15,15, + 13,13,14,15,12,11,12,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 13,13,13,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,12,13,15,15,12,13,15,15,14,15,15,15,15,15,15, + 15,15,15,15,15,15,13,15,15,15,15,15,15,15,15,15, +}; + +static static_codebook _huff_book_line_256x7_class1 = { + 1, 256, + _huff_lengthlist_line_256x7_class1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_0sub0[] = { + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 6, 5, 6, 6, 6, 6, 5, 6, 6, 7, 6, 7, 6, 7, 6, + 7, 6, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 9, 7, 9, 7, + 9, 7, 9, 8, 9, 8,10, 8,10, 8,10, 7,10, 6,10, 8, + 10, 8,11, 7,10, 7,11, 8,11,11,12,12,11,11,12,11, + 13,11,13,11,13,12,15,12,13,13,14,14,14,14,14,15, + 15,15,16,14,17,19,19,18,18,18,18,18,18,18,18,18, + 18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18, +}; + +static static_codebook _huff_book_line_512x17_0sub0 = { + 1, 128, + _huff_lengthlist_line_512x17_0sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_1sub0[] = { + 2, 4, 5, 4, 5, 4, 5, 4, 5, 5, 5, 5, 5, 5, 6, 5, + 6, 5, 6, 6, 7, 6, 7, 6, 8, 7, 8, 7, 8, 7, 8, 7, +}; + +static static_codebook _huff_book_line_512x17_1sub0 = { + 1, 32, + _huff_lengthlist_line_512x17_1sub0, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_1sub1[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 3, 5, 3, 5, 4, 5, 4, 5, 4, 5, 5, 5, 5, 6, 5, + 6, 5, 7, 5, 8, 6, 8, 6, 8, 6, 8, 6, 8, 7, 9, 7, + 9, 7,11, 9,11,11,12,11,14,12,14,16,14,16,13,16, + 14,16,12,15,13,16,14,16,13,14,12,15,13,15,13,13, + 13,15,12,14,14,15,13,15,12,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, +}; + +static static_codebook _huff_book_line_512x17_1sub1 = { + 1, 128, + _huff_lengthlist_line_512x17_1sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_2sub1[] = { + 0, 4, 5, 4, 4, 4, 5, 4, 4, 4, 5, 4, 5, 4, 5, 3, + 5, 3, +}; + +static static_codebook _huff_book_line_512x17_2sub1 = { + 1, 18, + _huff_lengthlist_line_512x17_2sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_2sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 3, 4, 3, 4, 4, 5, 4, 5, 4, 6, 4, 6, 5, + 6, 5, 7, 5, 7, 6, 8, 6, 8, 6, 8, 7, 8, 7, 9, 7, + 9, 8, +}; + +static static_codebook _huff_book_line_512x17_2sub2 = { + 1, 50, + _huff_lengthlist_line_512x17_2sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_2sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 3, 3, 3, 4, 3, 4, 4, 5, 5, 6, 6, 7, 7, + 7, 8, 8,11, 8, 9, 9, 9,10,11,11,11, 9,10,10,11, + 11,11,11,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, +}; + +static static_codebook _huff_book_line_512x17_2sub3 = { + 1, 128, + _huff_lengthlist_line_512x17_2sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_3sub1[] = { + 0, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 4, 4, 5, 4, 5, + 5, 5, +}; + +static static_codebook _huff_book_line_512x17_3sub1 = { + 1, 18, + _huff_lengthlist_line_512x17_3sub1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_3sub2[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 3, 3, 4, 3, 5, 4, 6, 4, 6, 5, 7, 6, 7, + 6, 8, 6, 8, 7, 9, 8,10, 8,12, 9,13,10,15,10,15, + 11,14, +}; + +static static_codebook _huff_book_line_512x17_3sub2 = { + 1, 50, + _huff_lengthlist_line_512x17_3sub2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_3sub3[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 4, 8, 4, 8, 4, 8, 4, 8, 5, 8, 5, 8, 6, 8, + 4, 8, 4, 8, 5, 8, 5, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +}; + +static static_codebook _huff_book_line_512x17_3sub3 = { + 1, 128, + _huff_lengthlist_line_512x17_3sub3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_class1[] = { + 1, 2, 3, 6, 5, 4, 7, 7, +}; + +static static_codebook _huff_book_line_512x17_class1 = { + 1, 8, + _huff_lengthlist_line_512x17_class1, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_class2[] = { + 3, 3, 3,14, 5, 4, 4,11, 8, 6, 6,10,17,12,11,17, + 6, 5, 5,15, 5, 3, 4,11, 8, 5, 5, 8,16, 9,10,14, + 10, 8, 9,17, 8, 6, 6,13,10, 7, 7,10,16,11,13,14, + 17,17,17,17,17,16,16,16,16,15,16,16,16,16,16,16, +}; + +static static_codebook _huff_book_line_512x17_class2 = { + 1, 64, + _huff_lengthlist_line_512x17_class2, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist_line_512x17_class3[] = { + 2, 4, 6,17, 4, 5, 7,17, 8, 7,10,17,17,17,17,17, + 3, 4, 6,15, 3, 3, 6,15, 7, 6, 9,17,17,17,17,17, + 6, 8,10,17, 6, 6, 8,16, 9, 8,10,17,17,15,16,17, + 17,17,17,17,12,15,15,16,12,15,15,16,16,16,16,16, +}; + +static static_codebook _huff_book_line_512x17_class3 = { + 1, 64, + _huff_lengthlist_line_512x17_class3, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + diff --git a/src/sound/oggvorbis/vorbissrc/books/uncoupled/res_books_uncoupled.h b/src/sound/oggvorbis/vorbissrc/books/uncoupled/res_books_uncoupled.h new file mode 100644 index 0000000..85e2037 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/books/uncoupled/res_books_uncoupled.h @@ -0,0 +1,10931 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: static codebooks autogenerated by huff/huffbuld + last modified: $Id: res_books_uncoupled.h,v 1.2 2002/07/11 10:27:53 xiphmont Exp $ + + ********************************************************************/ + +#include "codebook.h" +static long _vq_quantlist__16u0__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u0__p1_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 8, 5, 8, 8, 8,10,10, 8, + 10,11, 5, 8, 8, 8,10,10, 8,10,10, 4, 9, 9, 9,12, + 11, 8,11,11, 8,12,11,10,12,14,10,13,13, 7,11,11, + 10,14,12,11,14,14, 4, 9, 9, 8,11,11, 9,11,12, 7, + 11,11,10,13,14,10,12,14, 8,11,12,10,14,14,10,13, + 12, +}; + +static float _vq_quantthresh__16u0__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16u0__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p1_0 = { + _vq_quantthresh__16u0__p1_0, + _vq_quantmap__16u0__p1_0, + 3, + 3 +}; + +static static_codebook _16u0__p1_0 = { + 4, 81, + _vq_lengthlist__16u0__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16u0__p1_0, + NULL, + &_vq_auxt__16u0__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u0__p2_0[] = { + 2, 4, 4, 5, 6, 6, 5, 6, 6, 5, 7, 7, 7, 8, 9, 7, + 8, 9, 5, 7, 7, 7, 9, 8, 7, 9, 7, 4, 7, 7, 7, 9, + 9, 7, 8, 8, 6, 9, 8, 7, 8,11, 9,11,10, 6, 8, 9, + 8,11, 8, 9,10,11, 4, 7, 7, 7, 8, 8, 7, 9, 9, 6, + 9, 8, 9,11,10, 8, 8,11, 6, 8, 9, 9,10,11, 8,11, + 8, +}; + +static float _vq_quantthresh__16u0__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16u0__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p2_0 = { + _vq_quantthresh__16u0__p2_0, + _vq_quantmap__16u0__p2_0, + 3, + 3 +}; + +static static_codebook _16u0__p2_0 = { + 4, 81, + _vq_lengthlist__16u0__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16u0__p2_0, + NULL, + &_vq_auxt__16u0__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16u0__p3_0[] = { + 1, 5, 5, 7, 7, 6, 7, 7, 8, 8, 6, 7, 8, 8, 8, 8, + 9, 9,11,11, 8, 9, 9,11,11, 6, 9, 8,10,10, 8,10, + 10,11,11, 8,10,10,11,11,10,11,10,13,12, 9,11,10, + 13,13, 6, 8, 9,10,10, 8,10,10,11,11, 8,10,10,11, + 11, 9,10,11,13,12,10,10,11,12,12, 8,11,11,14,13, + 10,12,11,15,13, 9,12,11,15,14,12,14,13,16,14,12, + 13,13,17,14, 8,11,11,13,14, 9,11,12,14,15,10,11, + 12,13,15,11,13,13,14,16,12,13,14,14,16, 5, 9, 9, + 11,11, 9,11,11,12,12, 8,11,11,12,12,11,12,12,15, + 14,10,12,12,15,15, 8,11,11,13,12,10,12,12,13,13, + 10,12,12,14,13,12,12,13,14,15,11,13,13,17,16, 7, + 11,11,13,13,10,12,12,14,13,10,12,12,13,14,12,13, + 12,15,14,11,13,13,15,14, 9,12,12,16,15,11,13,13, + 17,16,10,13,13,16,16,13,14,15,15,16,13,15,14,19, + 17, 9,12,12,14,16,11,13,13,15,16,10,13,13,17,16, + 13,14,13,17,15,12,15,15,16,17, 5, 9, 9,11,11, 8, + 11,11,13,12, 9,11,11,12,12,10,12,12,14,15,11,12, + 12,14,14, 7,11,10,13,12,10,12,12,14,13,10,11,12, + 13,13,11,13,13,15,16,12,12,13,15,15, 7,11,11,13, + 13,10,13,13,14,14,10,12,12,13,13,11,13,13,16,15, + 12,13,13,15,14, 9,12,12,15,15,10,13,13,17,16,11, + 12,13,15,15,12,15,14,18,18,13,14,14,16,17, 9,12, + 12,15,16,10,13,13,15,16,11,13,13,15,16,13,15,15, + 17,17,13,15,14,16,15, 7,11,11,15,16,10,13,12,16, + 17,10,12,13,15,17,15,16,16,18,17,13,15,15,17,18, + 8,12,12,16,16,11,13,14,17,18,11,13,13,18,16,15, + 17,16,17,19,14,15,15,17,16, 8,12,12,16,15,11,14, + 13,18,17,11,13,14,18,17,15,16,16,18,17,13,16,16, + 18,18,11,15,14,18,17,13,14,15,18, 0,12,15,15, 0, + 17,17,16,17,17,18,14,16,18,18, 0,11,14,14,17, 0, + 12,15,14,17,19,12,15,14,18, 0,15,18,16, 0,17,14, + 18,16,18, 0, 7,11,11,16,15,10,12,12,18,16,10,13, + 13,16,15,13,15,14,17,17,14,16,16,19,18, 8,12,12, + 16,16,11,13,13,18,16,11,13,14,17,16,14,15,15,19, + 18,15,16,16, 0,19, 8,12,12,16,17,11,13,13,17,17, + 11,14,13,17,17,13,15,15,17,19,15,17,17,19, 0,11, + 14,15,19,17,12,15,16,18,18,12,14,15,19,17,14,16, + 17, 0,18,16,16,19,17, 0,11,14,14,18,19,12,15,14, + 17,17,13,16,14,17,16,14,17,16,18,18,15,18,15, 0, + 18, +}; + +static float _vq_quantthresh__16u0__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16u0__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p3_0 = { + _vq_quantthresh__16u0__p3_0, + _vq_quantmap__16u0__p3_0, + 5, + 5 +}; + +static static_codebook _16u0__p3_0 = { + 4, 625, + _vq_lengthlist__16u0__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16u0__p3_0, + NULL, + &_vq_auxt__16u0__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16u0__p4_0[] = { + 3, 5, 5, 8, 8, 6, 6, 6, 9, 9, 6, 6, 6, 9, 9, 9, + 10, 9,11,11, 9, 9, 9,11,11, 6, 7, 7,10,10, 7, 7, + 8,10,10, 7, 7, 8,10,10,10,10,10,11,12, 9,10,10, + 11,12, 6, 7, 7,10,10, 7, 8, 7,10,10, 7, 8, 7,10, + 10,10,11,10,12,11,10,10,10,13,10, 9,10,10,12,12, + 10,11,10,14,12, 9,11,11,13,13,11,12,13,13,13,11, + 12,12,15,13, 9,10,10,12,13, 9,11,10,12,13,10,10, + 11,12,13,11,12,12,12,13,11,12,12,13,13, 5, 7, 7, + 10,10, 7, 8, 8,10,10, 7, 8, 8,10,10,10,11,10,12, + 13,10,10,11,12,12, 6, 8, 8,11,10, 7, 8, 9,10,12, + 8, 9, 9,11,11,11,10,11,11,12,10,11,11,13,12, 7, + 8, 8,10,11, 8, 9, 8,11,10, 8, 9, 9,11,11,10,12, + 10,13,11,10,11,11,13,13,10,11,10,14,13,10,10,11, + 13,13,10,12,11,14,13,12,11,13,12,13,13,12,13,14, + 14,10,11,11,13,13,10,11,10,12,13,10,12,12,12,14, + 12,12,12,14,12,12,13,12,17,15, 5, 7, 7,10,10, 7, + 8, 8,10,10, 7, 8, 8,11,10,10,10,11,12,12,10,11, + 11,12,13, 6, 8, 8,11,10, 8, 9, 9,11,11, 7, 8, 9, + 10,11,11,11,11,12,12,10,10,11,12,13, 6, 8, 8,10, + 11, 8, 9, 9,11,11, 7, 9, 7,11,10,10,12,12,13,13, + 11,11,10,13,11, 9,11,10,14,13,11,11,11,15,13,10, + 10,11,13,13,12,13,13,14,14,12,11,12,12,13,10,11, + 11,12,13,10,11,12,13,13,10,11,10,13,12,12,12,13, + 14, 0,12,13,11,13,11, 8,10,10,13,13,10,11,11,14, + 13,10,11,11,13,12,13,14,14,14,15,12,12,12,15,14, + 9,11,10,13,12,10,10,11,13,14,11,11,11,15,12,13, + 12,14,15,16,13,13,13,14,13, 9,11,11,12,12,10,12, + 11,13,13,10,11,11,13,14,13,13,13,15,15,13,13,14, + 17,15,11,12,12,14,14,10,11,12,13,15,12,13,13, 0, + 15,13,11,14,12,16,14,16,14, 0,15,11,12,12,14,16, + 11,13,12,16,15,12,13,13,14,15,12,14,12,15,13,15, + 14,14,16,16, 8,10,10,13,13,10,11,10,13,14,10,11, + 11,13,13,13,13,12,14,14,14,13,13,16,17, 9,10,10, + 12,14,10,12,11,14,13,10,11,12,13,14,12,12,12,15, + 15,13,13,13,14,14, 9,10,10,13,13,10,11,12,12,14, + 10,11,10,13,13,13,13,13,14,16,13,13,13,14,14,11, + 12,13,15,13,12,14,13,14,16,12,12,13,13,14,13,14, + 14,17,15,13,12,17,13,16,11,12,13,14,15,12,13,14, + 14,17,11,12,11,14,14,13,16,14,16, 0,14,15,11,15, + 11, +}; + +static float _vq_quantthresh__16u0__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16u0__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p4_0 = { + _vq_quantthresh__16u0__p4_0, + _vq_quantmap__16u0__p4_0, + 5, + 5 +}; + +static static_codebook _16u0__p4_0 = { + 4, 625, + _vq_lengthlist__16u0__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16u0__p4_0, + NULL, + &_vq_auxt__16u0__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16u0__p5_0[] = { + 1, 4, 4, 7, 7, 7, 7, 9, 9, 4, 6, 6, 8, 8, 8, 8, + 9, 9, 4, 6, 6, 8, 8, 8, 8, 9, 9, 7, 8, 8, 9, 9, + 9, 9,11,10, 7, 8, 8, 9, 9, 9, 9,10,11, 7, 8, 8, + 9, 9,10,10,11,11, 7, 8, 8, 9, 9,10,10,11,11, 9, + 9, 9,10,10,11,11,12,12, 9, 9, 9,10,10,11,11,12, + 12, +}; + +static float _vq_quantthresh__16u0__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16u0__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p5_0 = { + _vq_quantthresh__16u0__p5_0, + _vq_quantmap__16u0__p5_0, + 9, + 9 +}; + +static static_codebook _16u0__p5_0 = { + 2, 81, + _vq_lengthlist__16u0__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16u0__p5_0, + NULL, + &_vq_auxt__16u0__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16u0__p6_0[] = { + 1, 4, 4, 7, 7,10,10,12,12,13,13,18,17, 3, 6, 6, + 9, 9,11,11,13,13,14,14,18,17, 3, 6, 6, 9, 9,11, + 11,13,13,14,14,17,18, 7, 9, 9,11,11,13,13,14,14, + 15,15, 0, 0, 7, 9, 9,11,11,13,13,14,14,15,16,19, + 18,10,11,11,13,13,14,14,16,15,17,18, 0, 0,10,11, + 11,13,13,14,14,15,15,16,18, 0, 0,11,13,13,14,14, + 15,15,17,17, 0,19, 0, 0,11,13,13,14,14,14,15,16, + 18, 0,19, 0, 0,13,14,14,15,15,18,17,18,18, 0,19, + 0, 0,13,14,14,15,16,16,16,18,18,19, 0, 0, 0,16, + 17,17, 0,17,19,19, 0,19, 0, 0, 0, 0,16,19,16,17, + 18, 0,19, 0, 0, 0, 0, 0, 0, +}; + +static float _vq_quantthresh__16u0__p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__16u0__p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p6_0 = { + _vq_quantthresh__16u0__p6_0, + _vq_quantmap__16u0__p6_0, + 13, + 13 +}; + +static static_codebook _16u0__p6_0 = { + 2, 169, + _vq_lengthlist__16u0__p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__16u0__p6_0, + NULL, + &_vq_auxt__16u0__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16u0__p6_1[] = { + 1, 4, 5, 6, 6, 4, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, + 6, 6, 7, 7, 6, 6, 6, 7, 7, +}; + +static float _vq_quantthresh__16u0__p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16u0__p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p6_1 = { + _vq_quantthresh__16u0__p6_1, + _vq_quantmap__16u0__p6_1, + 5, + 5 +}; + +static static_codebook _16u0__p6_1 = { + 2, 25, + _vq_lengthlist__16u0__p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16u0__p6_1, + NULL, + &_vq_auxt__16u0__p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u0__p7_0[] = { + 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__16u0__p7_0[] = { + -157.5, 157.5, +}; + +static long _vq_quantmap__16u0__p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p7_0 = { + _vq_quantthresh__16u0__p7_0, + _vq_quantmap__16u0__p7_0, + 3, + 3 +}; + +static static_codebook _16u0__p7_0 = { + 4, 81, + _vq_lengthlist__16u0__p7_0, + 1, -518803456, 1628680192, 2, 0, + _vq_quantlist__16u0__p7_0, + NULL, + &_vq_auxt__16u0__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p7_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__16u0__p7_1[] = { + 1, 5, 5, 6, 5, 9,10,11,11,10,10,10,10,10,10, 5, + 8, 8, 8,10,10,10,10,10,10,10,10,10,10,10, 5, 8, + 9, 9, 9,10,10,10,10,10,10,10,10,10,10, 5,10, 8, + 10,10,10,10,10,10,10,10,10,10,10,10, 4, 8, 9,10, + 10,10,10,10,10,10,10,10,10,10,10, 9,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10, 9,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__16u0__p7_1[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__16u0__p7_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p7_1 = { + _vq_quantthresh__16u0__p7_1, + _vq_quantmap__16u0__p7_1, + 15, + 15 +}; + +static static_codebook _16u0__p7_1 = { + 2, 225, + _vq_lengthlist__16u0__p7_1, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__16u0__p7_1, + NULL, + &_vq_auxt__16u0__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u0__p7_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__16u0__p7_2[] = { + 1, 6, 6, 7, 8, 7, 7,10, 9,10, 9,11,10, 9,11,10, + 9, 9, 9, 9,10, 6, 8, 7, 9, 9, 8, 8,10,10, 9,11, + 11,12,12,10, 9,11, 9,12,10, 9, 6, 9, 8, 9,12, 8, + 8,11, 9,11,11,12,11,12,12,10,11,11,10,10,11, 7, + 10, 9, 9, 9, 9, 9,10, 9,10, 9,10,10,12,10,10,10, + 11,12,10,10, 7, 9, 9, 9,10, 9, 9,10,10, 9, 9, 9, + 11,11,10,10,10,10, 9, 9,12, 7, 9,10, 9,11, 9,10, + 9,10,11,11,11,10,11,12, 9,12,11,10,10,10, 7, 9, + 9, 9, 9,10,12,10, 9,11,12,10,11,12,12,11, 9,10, + 11,10,11, 7, 9,10,10,11,10, 9,10,11,11,11,10,12, + 12,12,11,11,10,11,11,12, 8, 9,10,12,11,10,10,12, + 12,12,12,12,10,11,11, 9,11,10,12,11,11, 8, 9,10, + 10,11,12,11,11,10,10,10,12,12,12, 9,10,12,12,12, + 12,12, 8,10,11,10,10,12, 9,11,12,12,11,12,12,12, + 12,10,12,10,10,10,10, 8,12,11,11,11,10,10,11,12, + 12,12,12,11,12,12,12,11,11,11,12,10, 9,10,10,12, + 10,12,10,12,12,10,10,10,11,12,12,12,11,12,12,12, + 11,10,11,12,12,12,11,12,12,11,12,12,11,12,12,12, + 12,11,12,12,10,10,10,10,11,11,12,11,12,12,12,12, + 12,12,12,11,12,11,10,11,11,12,11,11, 9,10,10,10, + 12,10,10,11, 9,11,12,11,12,11,12,12,10,11,10,12, + 9, 9, 9,12,11,10,11,10,12,10,12,10,12,12,12,11, + 11,11,11,11,10, 9,10,10,11,10,11,11,12,11,10,11, + 12,12,12,11,11, 9,12,10,12, 9,10,12,10,10,11,10, + 11,11,12,11,10,11,10,11,11,11,11,12,11,11,10, 9, + 10,10,10, 9,11,11,10, 9,12,10,11,12,11,12,12,11, + 12,11,12,11,10,11,10,12,11,12,11,12,11,12,10,11, + 10,10,12,11,10,11,11,11,10, +}; + +static float _vq_quantthresh__16u0__p7_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__16u0__p7_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__16u0__p7_2 = { + _vq_quantthresh__16u0__p7_2, + _vq_quantmap__16u0__p7_2, + 21, + 21 +}; + +static static_codebook _16u0__p7_2 = { + 2, 441, + _vq_lengthlist__16u0__p7_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__16u0__p7_2, + NULL, + &_vq_auxt__16u0__p7_2, + NULL, + 0 +}; + +static long _huff_lengthlist__16u0__single[] = { + 3, 5, 8, 7,14, 8, 9,19, 5, 2, 5, 5, 9, 6, 9,19, + 8, 4, 5, 7, 8, 9,13,19, 7, 4, 6, 5, 9, 6, 9,19, + 12, 8, 7, 9,10,11,13,19, 8, 5, 8, 6, 9, 6, 7,19, + 8, 8,10, 7, 7, 4, 5,19,12,17,19,15,18,13,11,18, +}; + +static static_codebook _huff_book__16u0__single = { + 2, 64, + _huff_lengthlist__16u0__single, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__16u1__long[] = { + 3, 6,10, 8,12, 8,14, 8,14,19, 5, 3, 5, 5, 7, 6, + 11, 7,16,19, 7, 5, 6, 7, 7, 9,11,12,19,19, 6, 4, + 7, 5, 7, 6,10, 7,18,18, 8, 6, 7, 7, 7, 7, 8, 9, + 18,18, 7, 5, 8, 5, 7, 5, 8, 6,18,18,12, 9,10, 9, + 9, 9, 8, 9,18,18, 8, 7,10, 6, 8, 5, 6, 4,11,18, + 11,15,16,12,11, 8, 8, 6, 9,18,14,18,18,18,16,16, + 16,13,16,18, +}; + +static static_codebook _huff_book__16u1__long = { + 2, 100, + _huff_lengthlist__16u1__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u1__p1_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 8, 7, 7,10,10, 7, + 9,10, 5, 7, 8, 7,10, 9, 7,10,10, 5, 8, 8, 8,10, + 10, 8,10,10, 7,10,10,10,11,12,10,12,13, 7,10,10, + 9,13,11,10,12,13, 5, 8, 8, 8,10,10, 8,10,10, 7, + 10,10,10,12,12, 9,11,12, 7,10,11,10,12,12,10,13, + 11, +}; + +static float _vq_quantthresh__16u1__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16u1__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p1_0 = { + _vq_quantthresh__16u1__p1_0, + _vq_quantmap__16u1__p1_0, + 3, + 3 +}; + +static static_codebook _16u1__p1_0 = { + 4, 81, + _vq_lengthlist__16u1__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16u1__p1_0, + NULL, + &_vq_auxt__16u1__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u1__p2_0[] = { + 3, 4, 4, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 7, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 6, 8, 7, 5, 6, 6, 6, 8, + 8, 6, 8, 8, 6, 8, 8, 7, 7,10, 8, 9, 9, 6, 8, 8, + 7, 9, 8, 8, 9,10, 5, 6, 6, 6, 8, 8, 7, 8, 8, 6, + 8, 8, 8,10, 9, 7, 8, 9, 6, 8, 8, 8, 9, 9, 7,10, + 8, +}; + +static float _vq_quantthresh__16u1__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16u1__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p2_0 = { + _vq_quantthresh__16u1__p2_0, + _vq_quantmap__16u1__p2_0, + 3, + 3 +}; + +static static_codebook _16u1__p2_0 = { + 4, 81, + _vq_lengthlist__16u1__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16u1__p2_0, + NULL, + &_vq_auxt__16u1__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16u1__p3_0[] = { + 1, 5, 5, 8, 8, 6, 7, 7, 9, 9, 5, 7, 7, 9, 9, 9, + 10, 9,11,11, 9, 9,10,11,11, 6, 8, 8,10,10, 8, 9, + 10,11,11, 8, 9,10,11,11,10,11,11,12,13,10,11,11, + 13,13, 6, 8, 8,10,10, 8,10, 9,11,11, 8,10, 9,11, + 11,10,11,11,13,13,10,11,11,13,12, 9,11,11,14,13, + 10,12,12,15,14,10,12,11,14,13,12,13,13,15,15,12, + 13,13,16,14, 9,11,11,13,14,10,11,12,14,14,10,12, + 12,14,15,12,13,13,14,15,12,13,14,15,16, 5, 8, 8, + 11,11, 8,10,10,12,12, 8,10,10,12,12,11,12,12,14, + 14,11,12,12,14,14, 8,10,10,12,12, 9,11,12,12,13, + 10,12,12,13,13,12,12,13,14,15,11,13,13,15,15, 7, + 10,10,12,12, 9,12,11,13,12,10,11,12,13,13,12,13, + 12,15,14,11,12,13,15,15,10,12,12,15,14,11,13,13, + 16,15,11,13,13,16,15,14,13,14,15,16,13,15,15,17, + 17,10,12,12,14,15,11,12,12,15,15,11,13,13,15,16, + 13,15,13,16,15,13,15,15,16,17, 5, 8, 8,11,11, 8, + 10,10,12,12, 8,10,10,12,12,11,12,12,14,14,11,12, + 12,14,14, 7,10,10,12,12,10,12,12,14,13, 9,11,12, + 12,13,12,13,13,15,15,12,12,13,13,15, 7,10,10,12, + 13,10,11,12,13,13,10,12,11,13,13,11,13,13,15,15, + 12,13,12,15,14, 9,12,12,15,14,11,13,13,15,15,11, + 12,13,15,15,13,14,14,17,19,13,13,14,16,16,10,12, + 12,14,15,11,13,13,15,16,11,13,12,16,15,13,15,15, + 17,18,14,15,13,16,15, 8,11,11,15,14,10,12,12,16, + 15,10,12,12,16,16,14,15,15,18,17,13,14,15,16,18, + 9,12,12,15,15,11,12,14,16,17,11,13,13,16,15,15, + 15,15,17,18,14,15,16,17,17, 9,12,12,15,15,11,14, + 13,16,16,11,13,13,16,16,15,16,15,17,18,14,16,15, + 17,16,12,14,14,17,16,12,14,15,18,17,13,15,15,17, + 17,15,15,18,16,20,15,16,17,18,18,11,14,14,16,17, + 13,15,14,18,17,13,15,15,17,17,15,17,15,18,17,15, + 17,16,19,18, 8,11,11,14,15,10,12,12,15,15,10,12, + 12,16,16,13,14,14,17,16,14,15,15,17,17, 9,12,12, + 15,16,11,13,13,16,16,11,12,13,16,16,14,16,15,20, + 17,14,16,16,17,17, 9,12,12,15,16,11,13,13,16,17, + 11,13,13,17,16,14,15,15,17,18,15,15,15,18,18,11, + 14,14,17,16,13,15,15,17,17,13,14,14,18,17,15,16, + 16,18,19,15,15,17,17,19,11,14,14,16,17,13,15,14, + 17,19,13,15,14,18,17,15,17,16,18,18,15,17,15,18, + 16, +}; + +static float _vq_quantthresh__16u1__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16u1__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p3_0 = { + _vq_quantthresh__16u1__p3_0, + _vq_quantmap__16u1__p3_0, + 5, + 5 +}; + +static static_codebook _16u1__p3_0 = { + 4, 625, + _vq_lengthlist__16u1__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16u1__p3_0, + NULL, + &_vq_auxt__16u1__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16u1__p4_0[] = { + 4, 5, 5, 8, 8, 6, 6, 7, 9, 9, 6, 6, 6, 9, 9, 9, + 10, 9,11,11, 9, 9,10,11,11, 6, 7, 7,10, 9, 7, 7, + 8, 9,10, 7, 7, 8,10,10,10,10,10,10,12, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 7, 8, 7,10,10, 7, 8, 7,10, + 10, 9,10, 9,12,11,10,10, 9,12,10, 9,10,10,12,11, + 10,10,10,12,12, 9,10,10,12,12,12,11,12,13,13,11, + 11,12,12,13, 9,10,10,11,12, 9,10,10,12,12,10,10, + 10,12,12,11,12,11,14,13,11,12,12,14,13, 5, 7, 7, + 10,10, 7, 8, 8,10,10, 7, 8, 7,10,10,10,10,10,12, + 12,10,10,10,12,12, 6, 8, 7,10,10, 7, 7, 9,10,11, + 8, 9, 9,11,10,10,10,11,11,13,10,10,11,12,13, 6, + 8, 8,10,10, 7, 9, 8,11,10, 8, 9, 9,10,11,10,11, + 10,13,11,10,11,10,12,12,10,11,10,12,11,10,10,10, + 12,13,10,11,11,13,12,11,11,13,11,14,12,12,13,14, + 14, 9,10,10,12,13,10,11,10,13,12,10,11,11,12,13, + 11,12,11,14,12,12,13,13,15,14, 5, 7, 7,10,10, 7, + 7, 8,10,10, 7, 8, 8,10,10,10,10,10,11,12,10,10, + 10,12,12, 7, 8, 8,10,10, 8, 9, 8,11,10, 7, 8, 9, + 10,11,10,11,11,12,12,10,10,11,11,13, 7, 7, 8,10, + 10, 8, 8, 9,10,11, 7, 9, 7,11,10,10,11,11,13,12, + 11,11,10,13,11, 9,10,10,12,12,10,11,11,13,12,10, + 10,11,12,12,12,13,13,14,14,11,11,12,12,14,10,10, + 11,12,12,10,11,11,12,13,10,10,10,13,12,12,13,13, + 15,14,12,13,10,14,11, 8,10,10,12,12,10,11,10,13, + 13, 9,10,10,12,12,12,13,13,15,14,11,12,12,13,13, + 9,10,10,13,12,10,10,11,13,13,10,11,10,13,12,12, + 12,13,14,15,12,13,12,15,13, 9,10,10,12,13,10,11, + 10,13,12,10,10,11,12,13,12,14,12,15,13,12,12,13, + 14,15,11,12,11,14,13,11,11,12,14,15,12,13,12,15, + 14,13,11,15,11,16,13,14,14,16,15,11,12,12,14,14, + 11,12,11,14,13,12,12,13,14,15,13,14,12,16,12,14, + 14,14,15,15, 8,10,10,12,12, 9,10,10,12,12,10,10, + 11,13,13,11,12,12,13,13,12,13,13,14,15, 9,10,10, + 13,12,10,11,11,13,12,10,10,11,13,13,12,13,12,15, + 14,12,12,13,13,16, 9, 9,10,12,13,10,10,11,12,13, + 10,11,10,13,13,12,12,13,13,15,13,13,12,15,13,11, + 12,12,14,14,12,13,12,15,14,11,11,12,13,14,14,14, + 14,16,15,13,12,15,12,16,11,11,12,13,14,12,13,13, + 14,15,10,12,11,14,13,14,15,14,16,16,13,14,11,15, + 11, +}; + +static float _vq_quantthresh__16u1__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16u1__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p4_0 = { + _vq_quantthresh__16u1__p4_0, + _vq_quantmap__16u1__p4_0, + 5, + 5 +}; + +static static_codebook _16u1__p4_0 = { + 4, 625, + _vq_lengthlist__16u1__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16u1__p4_0, + NULL, + &_vq_auxt__16u1__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16u1__p5_0[] = { + 1, 4, 4, 7, 7, 7, 7, 9, 9, 4, 6, 6, 8, 8, 8, 8, + 10,10, 4, 5, 6, 8, 8, 8, 8,10,10, 7, 8, 8, 9, 9, + 9, 9,11,11, 7, 8, 8, 9, 9, 9, 9,11,11, 7, 8, 8, + 10, 9,11,11,12,11, 7, 8, 8, 9, 9,11,11,12,12, 9, + 10,10,11,11,12,12,13,12, 9,10,10,11,11,12,12,12, + 13, +}; + +static float _vq_quantthresh__16u1__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16u1__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p5_0 = { + _vq_quantthresh__16u1__p5_0, + _vq_quantmap__16u1__p5_0, + 9, + 9 +}; + +static static_codebook _16u1__p5_0 = { + 2, 81, + _vq_lengthlist__16u1__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16u1__p5_0, + NULL, + &_vq_auxt__16u1__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p6_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16u1__p6_0[] = { + 3, 4, 4, 6, 6, 7, 7, 9, 9, 4, 4, 4, 6, 6, 8, 8, + 9, 9, 4, 4, 4, 6, 6, 7, 7, 9, 9, 6, 6, 6, 7, 7, + 8, 8,10, 9, 6, 6, 6, 7, 7, 8, 8, 9,10, 7, 8, 7, + 8, 8, 9, 9,10,10, 7, 8, 8, 8, 8, 9, 9,10,10, 9, + 9, 9,10,10,10,10,11,11, 9, 9, 9,10,10,10,10,11, + 11, +}; + +static float _vq_quantthresh__16u1__p6_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16u1__p6_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p6_0 = { + _vq_quantthresh__16u1__p6_0, + _vq_quantmap__16u1__p6_0, + 9, + 9 +}; + +static static_codebook _16u1__p6_0 = { + 2, 81, + _vq_lengthlist__16u1__p6_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16u1__p6_0, + NULL, + &_vq_auxt__16u1__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u1__p7_0[] = { + 1, 4, 4, 4, 8, 8, 4, 8, 8, 5,11, 9, 8,12,11, 8, + 12,11, 5,10,11, 8,11,12, 8,11,12, 4,11,11,11,14, + 13,10,13,13, 8,14,13,12,14,16,12,16,15, 8,14,14, + 13,16,14,12,15,16, 4,11,11,10,14,13,11,14,14, 8, + 15,14,12,15,15,12,14,16, 8,14,14,11,16,15,12,15, + 13, +}; + +static float _vq_quantthresh__16u1__p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__16u1__p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p7_0 = { + _vq_quantthresh__16u1__p7_0, + _vq_quantmap__16u1__p7_0, + 3, + 3 +}; + +static static_codebook _16u1__p7_0 = { + 4, 81, + _vq_lengthlist__16u1__p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__16u1__p7_0, + NULL, + &_vq_auxt__16u1__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16u1__p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 4, 6, 5, 7, 7, + 8, 8, 8, 8, 8, 8, 4, 5, 6, 7, 7, 8, 8, 8, 8, 8, + 8, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 6, 7, 7, 8, + 8, 8, 8, 9, 9, 9, 9, 7, 8, 8, 8, 8, 9, 9, 9,10, + 9,10, 7, 8, 8, 8, 8, 9, 9, 9, 9,10, 9, 8, 8, 8, + 9, 9,10,10,10,10,10,10, 8, 8, 8, 9, 9, 9, 9,10, + 10,10,10, 8, 8, 8, 9, 9, 9,10,10,10,10,10, 8, 8, + 8, 9, 9,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__16u1__p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16u1__p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p7_1 = { + _vq_quantthresh__16u1__p7_1, + _vq_quantmap__16u1__p7_1, + 11, + 11 +}; + +static static_codebook _16u1__p7_1 = { + 2, 121, + _vq_lengthlist__16u1__p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16u1__p7_1, + NULL, + &_vq_auxt__16u1__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p8_0[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16u1__p8_0[] = { + 1, 4, 4, 5, 5, 8, 8,10,10,12,12, 4, 7, 7, 8, 8, + 9, 9,12,11,14,13, 4, 7, 7, 7, 8, 9,10,11,11,13, + 12, 5, 8, 8, 9, 9,11,11,12,13,15,14, 5, 7, 8, 9, + 9,11,11,13,13,17,15, 8, 9,10,11,11,12,13,17,14, + 17,16, 8,10, 9,11,11,12,12,13,15,15,17,10,11,11, + 12,13,14,15,15,16,16,17, 9,11,11,12,12,14,15,17, + 15,15,16,11,14,12,14,15,16,15,16,16,16,15,11,13, + 13,14,14,15,15,16,16,15,16, +}; + +static float _vq_quantthresh__16u1__p8_0[] = { + -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, 27.5, + 38.5, 49.5, +}; + +static long _vq_quantmap__16u1__p8_0[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p8_0 = { + _vq_quantthresh__16u1__p8_0, + _vq_quantmap__16u1__p8_0, + 11, + 11 +}; + +static static_codebook _16u1__p8_0 = { + 2, 121, + _vq_lengthlist__16u1__p8_0, + 1, -524582912, 1618345984, 4, 0, + _vq_quantlist__16u1__p8_0, + NULL, + &_vq_auxt__16u1__p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p8_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16u1__p8_1[] = { + 2, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 4, 6, 6, 7, 7, + 8, 7, 8, 8, 8, 8, 4, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 6, 7, 7, 7, + 7, 8, 8, 8, 8, 9, 9, 7, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 8, 8, 8, + 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 8, 8, 8, 9, 8, 9, 9, 9, 9, 9, 9, 8, 8, + 8, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__16u1__p8_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16u1__p8_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p8_1 = { + _vq_quantthresh__16u1__p8_1, + _vq_quantmap__16u1__p8_1, + 11, + 11 +}; + +static static_codebook _16u1__p8_1 = { + 2, 121, + _vq_lengthlist__16u1__p8_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16u1__p8_1, + NULL, + &_vq_auxt__16u1__p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p9_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__16u1__p9_0[] = { + 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, +}; + +static float _vq_quantthresh__16u1__p9_0[] = { + -1657.5, -1402.5, -1147.5, -892.5, -637.5, -382.5, -127.5, 127.5, + 382.5, 637.5, 892.5, 1147.5, 1402.5, 1657.5, +}; + +static long _vq_quantmap__16u1__p9_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p9_0 = { + _vq_quantthresh__16u1__p9_0, + _vq_quantmap__16u1__p9_0, + 15, + 15 +}; + +static static_codebook _16u1__p9_0 = { + 2, 225, + _vq_lengthlist__16u1__p9_0, + 1, -514071552, 1627381760, 4, 0, + _vq_quantlist__16u1__p9_0, + NULL, + &_vq_auxt__16u1__p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__16u1__p9_1[] = { + 1, 6, 5, 9, 9,10,10, 6, 7, 9, 9,10,10,10,10, 5, + 10, 8,10, 8,10,10, 8, 8,10, 9,10,10,10,10, 5, 8, + 9,10,10,10,10, 8,10,10,10,10,10,10,10, 9,10,10, + 10,10,10,10, 9, 9,10,10,10,10,10,10, 9, 9, 8, 9, + 10,10,10, 9,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10, 8,10,10,10,10, + 10,10,10,10,10,10,10,10,10, 6, 8, 8,10,10,10, 8, + 10,10,10,10,10,10,10,10, 5, 8, 8,10,10,10, 9, 9, + 10,10,10,10,10,10,10,10, 9,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__16u1__p9_1[] = { + -110.5, -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, + 25.5, 42.5, 59.5, 76.5, 93.5, 110.5, +}; + +static long _vq_quantmap__16u1__p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p9_1 = { + _vq_quantthresh__16u1__p9_1, + _vq_quantmap__16u1__p9_1, + 15, + 15 +}; + +static static_codebook _16u1__p9_1 = { + 2, 225, + _vq_lengthlist__16u1__p9_1, + 1, -522338304, 1620115456, 4, 0, + _vq_quantlist__16u1__p9_1, + NULL, + &_vq_auxt__16u1__p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u1__p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__16u1__p9_2[] = { + 1, 6, 6, 7, 8, 8,11,10, 9, 9,11, 9,10, 9,11,11, + 9, 6, 7, 6,11, 8,11, 9,10,10,11, 9,11,10,10,10, + 11, 9, 5, 7, 7, 8, 8,10,11, 8, 8,11, 9, 9,10,11, + 9,10,11, 8, 9, 6, 8, 8, 9, 9,10,10,11,11,11, 9, + 11,10, 9,11, 8, 8, 8, 9, 8, 9,10,11, 9, 9,11,11, + 10, 9, 9,11,10, 8,11, 8, 9, 8,11, 9,10, 9,10,11, + 11,10,10, 9,10,10, 8, 8, 9,10,10,10, 9,11, 9,10, + 11,11,11,11,10, 9,11, 9, 9,11,11,10, 8,11,11,11, + 9,10,10,11,10,11,11, 9,11,10, 9,11,10,10,10,10, + 9,11,10,11,10, 9, 9,10,11, 9, 8,10,11,11,10,10, + 11, 9,11,10,11,11,10,11, 9, 9, 8,10, 8, 9,11, 9, + 8,10,10, 9,11,10,11,10,11, 9,11, 8,10,11,11,11, + 11,10,10,11,11,11,11,10,11,11,10, 9, 8,10,10, 9, + 11,10,11,11,11, 9, 9, 9,11,11,11,10,10, 9, 9,10, + 9,11,11,11,11, 8,10,11,10,11,11,10,11,11, 9, 9, + 9,10, 9,11, 9,11,11,11,11,11,10,11,11,10,11,10, + 11,11, 9,11,10,11,10, 9,10, 9,10,10,11,11,11,11, + 9,10, 9,10,11,11,10,11,11,11,11,11,11,10,11,11, + 10, +}; + +static float _vq_quantthresh__16u1__p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__16u1__p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__16u1__p9_2 = { + _vq_quantthresh__16u1__p9_2, + _vq_quantmap__16u1__p9_2, + 17, + 17 +}; + +static static_codebook _16u1__p9_2 = { + 2, 289, + _vq_lengthlist__16u1__p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__16u1__p9_2, + NULL, + &_vq_auxt__16u1__p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__16u1__short[] = { + 5, 7,10, 9,11,10,15,11,13,16, 6, 4, 6, 6, 7, 7, + 10, 9,12,16,10, 6, 5, 6, 6, 7,10,11,16,16, 9, 6, + 7, 6, 7, 7,10, 8,14,16,11, 6, 5, 4, 5, 6, 8, 9, + 15,16, 9, 6, 6, 5, 6, 6, 9, 8,14,16,12, 7, 6, 6, + 5, 6, 6, 7,13,16, 8, 6, 7, 6, 5, 5, 4, 4,11,16, + 9, 8, 9, 9, 7, 7, 6, 5,13,16,14,14,16,15,16,15, + 16,16,16,16, +}; + +static static_codebook _huff_book__16u1__short = { + 2, 100, + _huff_lengthlist__16u1__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__16u2__long[] = { + 5, 7,10,10,10,11,11,13,18,19, 6, 5, 5, 6, 7, 8, + 9,12,19,19, 8, 5, 4, 4, 6, 7, 9,13,19,19, 8, 5, + 4, 4, 5, 6, 8,12,17,19, 7, 5, 5, 4, 4, 5, 7,12, + 18,18, 8, 7, 7, 6, 5, 5, 6,10,18,18, 9, 9, 9, 8, + 6, 5, 6, 9,18,18,11,13,13,13, 8, 7, 7, 9,16,18, + 13,17,18,16,11, 9, 9, 9,17,18,15,18,18,18,15,13, + 13,14,18,18, +}; + +static static_codebook _huff_book__16u2__long = { + 2, 100, + _huff_lengthlist__16u2__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__16u2__short[] = { + 8,11,12,12,14,15,16,16,16,16, 9, 7, 7, 8, 9,11, + 13,14,16,16,13, 7, 6, 6, 7, 9,12,13,15,16,15, 7, + 6, 5, 4, 6,10,11,14,16,12, 8, 7, 4, 2, 4, 7,10, + 14,16,11, 9, 7, 5, 3, 4, 6, 9,14,16,11,10, 9, 7, + 5, 5, 6, 9,16,16,10,10, 9, 8, 6, 6, 7,10,16,16, + 11,11,11,10,10,10,11,14,16,16,16,14,14,13,14,16, + 16,16,16,16, +}; + +static static_codebook _huff_book__16u2__short = { + 2, 100, + _huff_lengthlist__16u2__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u2_p1_0[] = { + 1, 5, 5, 5, 7, 7, 5, 7, 7, 5, 7, 7, 7, 9, 9, 7, + 9, 9, 5, 7, 7, 7, 9, 9, 7, 9, 9, 5, 7, 7, 8, 9, + 9, 7, 9, 9, 7, 9, 9, 9,10,10, 9,10,10, 7, 9, 9, + 9,10,10, 9,10,11, 5, 7, 8, 8, 9, 9, 8, 9, 9, 7, + 9, 9, 9,10,10, 9, 9,10, 7, 9, 9, 9,10,10, 9,11, + 10, +}; + +static float _vq_quantthresh__16u2_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__16u2_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p1_0 = { + _vq_quantthresh__16u2_p1_0, + _vq_quantmap__16u2_p1_0, + 3, + 3 +}; + +static static_codebook _16u2_p1_0 = { + 4, 81, + _vq_lengthlist__16u2_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__16u2_p1_0, + NULL, + &_vq_auxt__16u2_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16u2_p2_0[] = { + 3, 5, 5, 8, 8, 5, 7, 7, 9, 9, 5, 7, 7, 9, 9, 9, + 10, 9,11,11, 9, 9, 9,11,11, 5, 7, 7, 9, 9, 7, 8, + 8,10,10, 7, 8, 8,10,10,10,10,10,12,12, 9,10,10, + 11,12, 5, 7, 7, 9, 9, 7, 8, 8,10,10, 7, 8, 8,10, + 10, 9,10,10,12,11,10,10,10,12,12, 9,10,10,12,12, + 10,11,10,13,12, 9,10,10,12,12,12,12,12,14,14,11, + 12,12,13,14, 9,10,10,12,12, 9,10,10,12,12,10,10, + 10,12,12,11,12,12,14,13,12,13,12,14,14, 5, 7, 7, + 9, 9, 7, 8, 8,10,10, 7, 8, 8,10,10,10,11,10,12, + 12,10,10,11,12,12, 7, 8, 8,10,10, 8, 9, 9,11,11, + 8, 9, 9,11,11,11,11,11,12,13,10,11,11,12,13, 7, + 8, 8,10,10, 8, 9, 8,11,10, 8, 9, 9,11,11,10,11, + 10,13,12,10,11,11,13,13, 9,11,10,13,13,10,11,11, + 13,13,10,11,11,13,13,12,12,13,13,15,12,12,13,14, + 15, 9,10,10,12,12,10,11,10,13,12,10,11,11,13,13, + 11,13,11,14,13,12,13,13,15,15, 5, 7, 7, 9, 9, 7, + 8, 8,10,10, 7, 8, 8,10,10,10,10,10,12,12,10,10, + 11,12,12, 7, 8, 8,10,10, 8, 9, 9,11,11, 8, 8, 9, + 10,11,10,11,11,13,13,10,10,11,12,13, 7, 8, 8,10, + 11, 8, 9, 9,11,11, 8, 9, 9,11,11,10,11,11,13,12, + 11,11,11,13,12, 9,10,10,12,12,10,11,11,13,13,10, + 10,11,12,13,12,13,13,15,14,11,11,13,12,14,10,10, + 11,13,13,10,11,11,13,13,10,11,11,13,13,12,13,13, + 14,14,12,13,12,14,13, 8,10, 9,12,12, 9,11,10,13, + 13, 9,10,10,12,13,12,13,13,14,14,12,12,13,14,14, + 9,11,10,13,13,10,11,11,13,13,10,11,11,13,13,12, + 13,13,15,15,13,13,13,14,15, 9,10,10,12,13,10,11, + 10,13,12,10,11,11,13,13,12,13,12,15,14,13,13,13, + 14,15,11,12,12,15,14,12,12,13,15,15,12,13,13,15, + 14,14,13,15,14,16,13,14,15,16,16,11,12,12,14,14, + 11,12,12,15,14,12,13,13,15,15,13,14,13,16,14,14, + 14,14,16,16, 8, 9, 9,12,12, 9,10,10,13,12, 9,10, + 10,13,13,12,12,12,14,14,12,12,13,15,15, 9,10,10, + 13,12,10,11,11,13,13,10,10,11,13,14,12,13,13,15, + 15,12,12,13,14,15, 9,10,10,13,13,10,11,11,13,13, + 10,11,11,13,13,12,13,13,14,14,13,14,13,15,14,11, + 12,12,14,14,12,13,13,15,14,11,12,12,14,15,14,14, + 14,16,15,13,12,14,14,16,11,12,13,14,15,12,13,13, + 14,16,12,13,12,15,14,13,15,14,16,16,14,15,13,16, + 13, +}; + +static float _vq_quantthresh__16u2_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16u2_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p2_0 = { + _vq_quantthresh__16u2_p2_0, + _vq_quantmap__16u2_p2_0, + 5, + 5 +}; + +static static_codebook _16u2_p2_0 = { + 4, 625, + _vq_lengthlist__16u2_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16u2_p2_0, + NULL, + &_vq_auxt__16u2_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__16u2_p3_0[] = { + 2, 4, 4, 6, 6, 7, 7, 9, 9, 4, 5, 5, 6, 6, 8, 7, + 9, 9, 4, 5, 5, 6, 6, 7, 8, 9, 9, 6, 6, 6, 7, 7, + 8, 8,10,10, 6, 6, 6, 7, 7, 8, 8, 9,10, 7, 8, 7, + 8, 8, 9, 9,10,10, 7, 8, 8, 8, 8, 9, 9,10,10, 9, + 9, 9,10, 9,10,10,11,11, 9, 9, 9,10,10,10,10,11, + 11, +}; + +static float _vq_quantthresh__16u2_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__16u2_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p3_0 = { + _vq_quantthresh__16u2_p3_0, + _vq_quantmap__16u2_p3_0, + 9, + 9 +}; + +static static_codebook _16u2_p3_0 = { + 2, 81, + _vq_lengthlist__16u2_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__16u2_p3_0, + NULL, + &_vq_auxt__16u2_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__16u2_p4_0[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11,11, + 11, 5, 5, 5, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11,11, + 12,11, 5, 5, 5, 7, 7, 8, 8, 9, 9, 9, 9,10,10,11, + 11,12,12, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9, 9,10, + 10,11,11,12,12, 7, 8, 8, 8, 8, 9, 9, 9, 9,10,10, + 11,11,12,12,12,12, 7, 8, 8, 8, 8, 9, 9, 9, 9,10, + 10,11,11,11,12,12,12, 9, 9, 9, 9, 9, 9,10,10,10, + 10,10,11,11,12,12,13,13, 8, 9, 9, 9, 9,10, 9,10, + 10,10,10,11,11,12,12,13,13, 9, 9, 9, 9, 9,10,10, + 10,10,11,11,11,12,12,12,13,13, 9, 9, 9, 9, 9,10, + 10,10,10,11,11,12,11,12,12,13,13,10,10,10,10,10, + 11,11,11,11,11,12,12,12,12,13,13,14,10,10,10,10, + 10,11,11,11,11,12,11,12,12,13,12,13,13,11,11,11, + 11,11,12,12,12,12,12,12,13,13,13,13,14,14,11,11, + 11,11,11,12,12,12,12,12,12,13,12,13,13,14,14,11, + 12,12,12,12,12,12,13,13,13,13,13,13,14,14,14,14, + 11,12,12,12,12,12,12,13,13,13,13,14,13,14,14,14, + 14, +}; + +static float _vq_quantthresh__16u2_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__16u2_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p4_0 = { + _vq_quantthresh__16u2_p4_0, + _vq_quantmap__16u2_p4_0, + 17, + 17 +}; + +static static_codebook _16u2_p4_0 = { + 2, 289, + _vq_lengthlist__16u2_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__16u2_p4_0, + NULL, + &_vq_auxt__16u2_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__16u2_p5_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 9, 8, 7,10, 9, 7, + 10, 9, 5, 8, 9, 7, 9,10, 7, 9,10, 4, 9, 9, 9,11, + 11, 8,11,11, 7,11,11,10,10,13,10,14,13, 7,11,11, + 10,13,11,10,13,14, 5, 9, 9, 8,11,11, 9,11,11, 7, + 11,11,10,14,13,10,12,14, 7,11,11,10,13,13,10,13, + 10, +}; + +static float _vq_quantthresh__16u2_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__16u2_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p5_0 = { + _vq_quantthresh__16u2_p5_0, + _vq_quantmap__16u2_p5_0, + 3, + 3 +}; + +static static_codebook _16u2_p5_0 = { + 4, 81, + _vq_lengthlist__16u2_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__16u2_p5_0, + NULL, + &_vq_auxt__16u2_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16u2_p5_1[] = { + 2, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 5, 5, 5, 7, 7, + 7, 7, 8, 8, 8, 8, 5, 5, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 6, 7, 7, 7, + 7, 8, 8, 8, 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 8, 8, 8, + 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__16u2_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16u2_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p5_1 = { + _vq_quantthresh__16u2_p5_1, + _vq_quantmap__16u2_p5_1, + 11, + 11 +}; + +static static_codebook _16u2_p5_1 = { + 2, 121, + _vq_lengthlist__16u2_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16u2_p5_1, + NULL, + &_vq_auxt__16u2_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16u2_p6_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 8, 9, 9,10,10, 4, 6, 6, + 8, 8, 9, 9, 9, 9,10,10,12,11, 4, 6, 6, 8, 8, 9, + 9, 9, 9,10,10,11,12, 7, 8, 8, 9, 9,10,10,10,10, + 12,12,13,12, 7, 8, 8, 9, 9,10,10,10,10,11,12,12, + 12, 8, 9, 9,10,10,11,11,11,11,12,12,13,13, 8, 9, + 9,10,10,11,11,11,11,12,13,13,13, 8, 9, 9,10,10, + 11,11,12,12,13,13,14,14, 8, 9, 9,10,10,11,11,12, + 12,13,13,14,14, 9,10,10,11,12,13,12,13,14,14,14, + 14,14, 9,10,10,11,12,12,13,13,13,14,14,14,14,10, + 11,11,12,12,13,13,14,14,15,15,15,15,10,11,11,12, + 12,13,13,14,14,14,14,15,15, +}; + +static float _vq_quantthresh__16u2_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__16u2_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p6_0 = { + _vq_quantthresh__16u2_p6_0, + _vq_quantmap__16u2_p6_0, + 13, + 13 +}; + +static static_codebook _16u2_p6_0 = { + 2, 169, + _vq_lengthlist__16u2_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__16u2_p6_0, + NULL, + &_vq_auxt__16u2_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__16u2_p6_1[] = { + 2, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 6, 6, 5, 5, 5, 6, 6, +}; + +static float _vq_quantthresh__16u2_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__16u2_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p6_1 = { + _vq_quantthresh__16u2_p6_1, + _vq_quantmap__16u2_p6_1, + 5, + 5 +}; + +static static_codebook _16u2_p6_1 = { + 2, 25, + _vq_lengthlist__16u2_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__16u2_p6_1, + NULL, + &_vq_auxt__16u2_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__16u2_p7_0[] = { + 1, 4, 4, 7, 7, 7, 7, 8, 8, 9, 9,10,10, 4, 6, 6, + 9, 9, 9, 9, 9, 9,10,10,11,11, 4, 6, 6, 8, 9, 9, + 9, 9, 9,10,11,12,11, 7, 8, 9,10,10,10,10,11,10, + 11,12,12,13, 7, 9, 9,10,10,10,10,10,10,11,12,13, + 13, 7, 9, 8,10,10,11,11,11,12,12,13,13,14, 7, 9, + 9,10,10,11,11,11,12,13,13,13,13, 8, 9, 9,10,11, + 11,12,12,12,13,13,13,13, 8, 9, 9,10,11,11,11,12, + 12,13,13,14,14, 9,10,10,12,11,12,13,13,13,14,13, + 13,13, 9,10,10,11,11,12,12,13,14,13,13,14,13,10, + 11,11,12,13,14,14,14,15,14,14,14,14,10,11,11,12, + 12,13,13,13,14,14,14,15,14, +}; + +static float _vq_quantthresh__16u2_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__16u2_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p7_0 = { + _vq_quantthresh__16u2_p7_0, + _vq_quantmap__16u2_p7_0, + 13, + 13 +}; + +static static_codebook _16u2_p7_0 = { + 2, 169, + _vq_lengthlist__16u2_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__16u2_p7_0, + NULL, + &_vq_auxt__16u2_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__16u2_p7_1[] = { + 3, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7, 5, 6, 6, 7, 7, + 7, 7, 7, 7, 8, 8, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, + 8, 6, 6, 7, 7, 7, 8, 7, 8, 8, 8, 8, 6, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__16u2_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__16u2_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p7_1 = { + _vq_quantthresh__16u2_p7_1, + _vq_quantmap__16u2_p7_1, + 11, + 11 +}; + +static static_codebook _16u2_p7_1 = { + 2, 121, + _vq_lengthlist__16u2_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__16u2_p7_1, + NULL, + &_vq_auxt__16u2_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__16u2_p8_0[] = { + 1, 5, 5, 7, 7, 8, 8, 7, 7, 8, 8,10, 9,11,11, 4, + 6, 6, 8, 8,10, 9, 9, 8, 9, 9,10,10,12,14, 4, 6, + 7, 8, 9, 9,10, 9, 8, 9, 9,10,12,12,11, 7, 8, 8, + 10,10,10,10, 9, 9,10,10,11,13,13,12, 7, 8, 8, 9, + 11,11,10, 9, 9,11,10,12,11,11,14, 8, 9, 9,11,10, + 11,11,10,10,11,11,13,12,14,12, 8, 9, 9,11,12,11, + 11,10,10,12,11,12,12,12,14, 7, 8, 8, 9, 9,10,10, + 10,11,12,11,13,13,14,12, 7, 8, 9, 9, 9,10,10,11, + 11,11,12,12,14,14,14, 8,10, 9,10,11,11,11,11,14, + 12,12,13,14,14,13, 9, 9, 9,10,11,11,11,12,12,12, + 14,12,14,13,14,10,10,10,12,11,12,11,14,13,14,13, + 14,14,13,14, 9,10,10,11,12,11,13,12,13,13,14,14, + 14,13,14,10,13,13,12,12,11,12,14,13,14,13,14,12, + 14,13,10,11,11,12,11,12,12,14,14,14,13,14,14,14, + 14, +}; + +static float _vq_quantthresh__16u2_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__16u2_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p8_0 = { + _vq_quantthresh__16u2_p8_0, + _vq_quantmap__16u2_p8_0, + 15, + 15 +}; + +static static_codebook _16u2_p8_0 = { + 2, 225, + _vq_lengthlist__16u2_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__16u2_p8_0, + NULL, + &_vq_auxt__16u2_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__16u2_p8_1[] = { + 2, 5, 5, 7, 7, 8, 8, 8, 8, 9, 9,10, 9,10, 9, 9, + 9,10,10,10,10, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, + 10, 9,10,10,10,10,10,10,11,10, 5, 6, 6, 7, 7, 8, + 8, 8, 9, 9,10,10,10,10,10,10,10,10,10,10,10, 7, + 7, 7, 8, 8, 9, 8, 9, 9,10, 9,10,10,10,10,10,10, + 11,10,11,10, 7, 7, 7, 8, 8, 8, 9, 9, 9,10, 9,10, + 10,10,10,10,10,10,10,10,10, 8, 8, 8, 9, 9, 9, 9, + 10, 9,10,10,10,10,10,10,10,11,10,10,11,10, 8, 8, + 8, 8, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,11, + 11,10,10, 8, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10, + 11,10,11,10,11,10,11,10, 8, 9, 9, 9, 9, 9,10,10, + 10,10,10,10,10,10,10,10,11,11,10,10,10, 9,10, 9, + 9,10,10,10,11,10,10,10,10,10,10,10,10,11,11,11, + 11,11, 9, 9, 9,10, 9,10,10,10,10,10,10,11,10,11, + 10,11,11,11,11,10,10, 9,10, 9,10,10,10,10,11,10, + 10,10,10,10,11,10,11,10,11,10,10,11, 9,10,10,10, + 10,10,10,10,10,10,11,10,10,11,11,10,11,11,11,11, + 11, 9, 9,10,10,10,10,10,11,10,10,11,10,10,11,10, + 10,11,11,11,11,11, 9,10,10,10,10,10,10,10,11,10, + 11,10,11,10,11,11,11,11,11,10,11,10,10,10,10,10, + 10,10,10,10,11,11,11,11,11,11,11,11,11,10,11,11, + 10,10,10,10,10,11,10,10,10,11,10,11,11,11,11,10, + 12,11,11,11,10,10,10,10,10,10,11,10,10,10,11,11, + 12,11,11,11,11,11,11,11,11,11,10,10,10,11,10,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,10, + 10,10,11,10,11,10,10,11,11,11,11,11,11,11,11,11, + 11,11,11,10,10,10,10,10,10,10,11,11,10,11,11,10, + 11,11,10,11,11,11,10,11,11, +}; + +static float _vq_quantthresh__16u2_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__16u2_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p8_1 = { + _vq_quantthresh__16u2_p8_1, + _vq_quantmap__16u2_p8_1, + 21, + 21 +}; + +static static_codebook _16u2_p8_1 = { + 2, 441, + _vq_lengthlist__16u2_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__16u2_p8_1, + NULL, + &_vq_auxt__16u2_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p9_0[] = { + 5586, + 4655, + 6517, + 3724, + 7448, + 2793, + 8379, + 1862, + 9310, + 931, + 10241, + 0, + 11172, + 5521, + 5651, +}; + +static long _vq_lengthlist__16u2_p9_0[] = { + 1,10,10,10,10,10,10,10,10,10,10,10,10, 5, 4,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10, 4,10,10,10,10,10,10,10,10,10,10,10,10, + 6, 6, 5,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 5, + 5, +}; + +static float _vq_quantthresh__16u2_p9_0[] = { + -5120.5, -4189.5, -3258.5, -2327.5, -1396.5, -498, -32.5, 32.5, + 498, 1396.5, 2327.5, 3258.5, 4189.5, 5120.5, +}; + +static long _vq_quantmap__16u2_p9_0[] = { + 11, 9, 7, 5, 3, 1, 13, 0, + 14, 2, 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p9_0 = { + _vq_quantthresh__16u2_p9_0, + _vq_quantmap__16u2_p9_0, + 15, + 15 +}; + +static static_codebook _16u2_p9_0 = { + 2, 225, + _vq_lengthlist__16u2_p9_0, + 1, -510275072, 1611661312, 14, 0, + _vq_quantlist__16u2_p9_0, + NULL, + &_vq_auxt__16u2_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p9_1[] = { + 392, + 343, + 441, + 294, + 490, + 245, + 539, + 196, + 588, + 147, + 637, + 98, + 686, + 49, + 735, + 0, + 784, + 388, + 396, +}; + +static long _vq_lengthlist__16u2_p9_1[] = { + 1,12,10,12,10,12,10,12,11,12,12,12,12,12,12,12, + 12, 5, 5, 9,10,12,11,11,12,12,12,12,12,12,12,12, + 12,12,12,12,10, 9, 9,11, 9,11,11,12,11,12,12,12, + 12,12,12,12,12,12,12, 8, 8,10,11, 9,12,11,12,12, + 12,12,12,12,12,12,12,12,12,12, 9, 8,10,11,12,11, + 12,11,12,12,12,12,12,12,12,12,12,12,12, 8, 9,11, + 11,10,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 9,10,11,12,11,12,11,12,12,12,12,12,12,12,12,12, + 12,12,12, 9, 9,11,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11, 5, 8, 9, 9, 8,11, 9,11,11,11,11,11,11, + 11,11,11,11, 5, 5, 4, 8, 8, 8, 8,10, 9,10,10,11, + 11,11,11,11,11,11,11, 5, 4, +}; + +static float _vq_quantthresh__16u2_p9_1[] = { + -367.5, -318.5, -269.5, -220.5, -171.5, -122.5, -73.5, -26.5, + -2, 2, 26.5, 73.5, 122.5, 171.5, 220.5, 269.5, + 318.5, 367.5, +}; + +static long _vq_quantmap__16u2_p9_1[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 17, 0, 18, 2, 4, 6, 8, 10, + 12, 14, 16, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p9_1 = { + _vq_quantthresh__16u2_p9_1, + _vq_quantmap__16u2_p9_1, + 19, + 19 +}; + +static static_codebook _16u2_p9_1 = { + 2, 361, + _vq_lengthlist__16u2_p9_1, + 1, -518488064, 1611661312, 10, 0, + _vq_quantlist__16u2_p9_1, + NULL, + &_vq_auxt__16u2_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__16u2_p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__16u2_p9_2[] = { + 1, 3, 3, 4, 7, 7, 7, 8, 7, 7, 7, 7, 8, 8, 8, 8, + 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 9, 9, 8, 9, 9, + 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,12,12,10, + 11, +}; + +static float _vq_quantthresh__16u2_p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__16u2_p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__16u2_p9_2 = { + _vq_quantthresh__16u2_p9_2, + _vq_quantmap__16u2_p9_2, + 49, + 49 +}; + +static static_codebook _16u2_p9_2 = { + 1, 49, + _vq_lengthlist__16u2_p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__16u2_p9_2, + NULL, + &_vq_auxt__16u2_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u0__long[] = { + 3, 7,12,10,15,10, 9,18, 5, 2, 5, 6, 8, 7, 9,20, + 10, 4, 4, 6, 6, 8,11,20, 9, 5, 6, 5, 7, 6, 9,20, + 11, 7, 5, 7, 5, 7,10,20,10, 6, 7, 6, 6, 6, 8,17, + 9, 8,10, 7, 7, 5, 5,17,11,18,19,14,13, 9, 8,19, +}; + +static static_codebook _huff_book__44u0__long = { + 2, 64, + _huff_lengthlist__44u0__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u0__p1_0[] = { + 1, 4, 4, 5, 8, 7, 5, 7, 8, 5, 8, 8, 8,11,11, 8, + 10,11, 5, 8, 8, 8,10,10, 8,11,11, 4, 8, 8, 8,11, + 11, 8,11,11, 8,11,11,11,13,14,11,13,14, 7,11,11, + 10,14,12,11,13,14, 4, 8, 8, 8,11,11, 8,11,11, 8, + 11,11,11,14,13,10,12,13, 8,11,12,11,14,14,11,14, + 13, +}; + +static float _vq_quantthresh__44u0__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u0__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p1_0 = { + _vq_quantthresh__44u0__p1_0, + _vq_quantmap__44u0__p1_0, + 3, + 3 +}; + +static static_codebook _44u0__p1_0 = { + 4, 81, + _vq_lengthlist__44u0__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u0__p1_0, + NULL, + &_vq_auxt__44u0__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u0__p2_0[] = { + 2, 4, 4, 5, 6, 6, 5, 6, 6, 5, 7, 6, 7, 8, 8, 6, + 7, 8, 5, 6, 7, 6, 8, 7, 7, 8, 8, 5, 7, 7, 7, 8, + 8, 7, 8, 8, 7, 8, 8, 8, 9,10, 8,10,10, 6, 8, 8, + 7,10, 8, 8,10,10, 5, 7, 7, 7, 8, 8, 7, 8, 8, 6, + 8, 8, 8,10,10, 7, 8,10, 6, 8, 8, 8,10,10, 8,10, + 9, +}; + +static float _vq_quantthresh__44u0__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u0__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p2_0 = { + _vq_quantthresh__44u0__p2_0, + _vq_quantmap__44u0__p2_0, + 3, + 3 +}; + +static static_codebook _44u0__p2_0 = { + 4, 81, + _vq_lengthlist__44u0__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u0__p2_0, + NULL, + &_vq_auxt__44u0__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u0__p3_0[] = { + 1, 5, 5, 8, 8, 5, 7, 7, 9, 9, 5, 7, 8, 9, 9, 8, + 10, 9,12,11, 8, 9,10,11,12, 6, 8, 8,10,10, 8,10, + 10,11,11, 8, 9,10,11,11,10,12,11,13,13,10,11,11, + 13,13, 6, 8, 8,10,10, 8,10, 9,11,11, 8,10,10,11, + 11,10,11,11,13,13,10,11,11,13,13, 9,11,11,15,14, + 10,12,11,15,14,10,12,11,15,14,12,14,13,16,15,12, + 13,13,17,15, 9,11,11,14,14,10,11,12,14,15,10,11, + 12,14,17,12,13,14,15,16,13,13,14,15,17, 5, 8, 8, + 11,11, 8,10,10,12,12, 8,10,10,12,12,11,12,12,15, + 14,11,12,12,14,15, 8,10,10,13,12,10,12,12,13,13, + 10,12,12,14,14,12,13,13,15,15,11,13,13,15,16, 7, + 10,10,12,12, 9,12,11,14,13,10,12,12,13,14,12,13, + 12,15,15,12,13,13,16,17,10,12,12,15,16,12,13,13, + 17,15,11,13,13,17,16,15,15,15,17,17,13,15,16,18, + 18, 9,12,12,15,17,11,13,12,16,16,11,13,13,16,18, + 14,15,14,16,16,13,15,15,17,18, 5, 8, 8,11,11, 8, + 10,10,12,12, 8,10,10,12,12,10,12,12,15,15,11,12, + 12,14,15, 7,10,10,12,12,10,12,12,14,14, 9,11,12, + 13,13,12,13,13,16,16,12,12,13,15,15, 8,10,10,12, + 13,10,12,12,13,14,10,12,12,13,13,12,13,13,16,16, + 12,13,13,15,15,10,12,12,15,16,11,13,13,17,16,11, + 12,13,17,16,13,15,15,18,19,14,14,14,17,16,10,12, + 12,15,15,11,13,13,15,16,11,13,13,15,17,13,15,15, + 18,20,14,15,15,17,17, 8,11,11,15,16,10,13,12,17, + 16,10,12,13,16,17,15,16,15,20,19,14,15,16,18,19, + 9,12,12,15,17,11,13,14,18,18,11,13,14,17,17,16, + 17,18,19,19,15,16,18,19,20, 9,12,12,16,16,11,14, + 12,17,17,11,13,13,16,19,15,16,15,20,19,15,15,15, + 19,17,12,14,14,18,19,14,15,15,19,18,13,15,15,18, + 17,17,18,19,20, 0,15,16,17,20, 0,12,15,14,17,18, + 13,16,14,20,20,13,16,15,19,18,16,20,17,20,19,16, + 17,16, 0,19, 8,11,11,15,15,10,12,12,17,17,10,12, + 13,16,16,13,14,15,17,17,15,16,17,19,19, 9,12,12, + 16,17,11,13,13,18,16,11,13,13,16,17,14,15,16,19, + 18,15,16,17,17,18, 9,12,12,17,16,11,14,13,16,16, + 12,14,13,17,18,14,16,15,18,19,17,17,17,19,18,12, + 14,14,19,17,13,15,15,17, 0,13,14,16, 0, 0,15,16, + 16,19, 0,16,16,19,19, 0,12,15,14,19,18,13,15,15, + 19, 0,14,16,15, 0,19,15,17,17, 0,19,17,18,17, 0, + 19, +}; + +static float _vq_quantthresh__44u0__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u0__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p3_0 = { + _vq_quantthresh__44u0__p3_0, + _vq_quantmap__44u0__p3_0, + 5, + 5 +}; + +static static_codebook _44u0__p3_0 = { + 4, 625, + _vq_lengthlist__44u0__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u0__p3_0, + NULL, + &_vq_auxt__44u0__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u0__p4_0[] = { + 4, 5, 5, 8, 8, 5, 7, 6, 9, 9, 5, 6, 7, 9, 9, 9, + 9, 9,11,11, 9, 9, 9,11,11, 5, 7, 7, 9, 9, 7, 8, + 8,10,10, 6, 7, 8, 9,10,10,10,10,11,12, 9,10,10, + 11,12, 5, 7, 7, 9, 9, 6, 8, 7,10,10, 7, 8, 8,10, + 10, 9,10,10,12,11,10,10,10,12,11, 9,10,10,12,12, + 10,11,10,13,13, 9,10,10,13,13,12,12,12,14,14,11, + 12,12,14,14, 9,10,10,12,12, 9,10,10,13,13,10,10, + 10,12,13,11,12,12,14,14,12,13,12,14,14, 5, 7, 7, + 10,10, 7, 8, 8,10,10, 7, 8, 8,10,10,10,10,10,13, + 12,10,10,10,12,12, 7, 8, 8,11,10, 8, 8, 9,10,11, + 8, 9, 9,11,11,10,10,11,12,13,10,11,11,13,14, 6, + 8, 8,10,10, 7, 9, 8,11,10, 8, 9, 9,11,11,10,11, + 10,13,11,10,11,11,13,13,10,11,10,13,13,10,10,11, + 13,14,10,11,11,14,13,12,11,13,12,14,12,13,13,15, + 15, 9,10,10,13,13,10,11,10,13,13,10,11,11,13,14, + 12,13,11,15,12,12,13,13,14,15, 5, 7, 7,10, 9, 7, + 8, 8,10,10, 7, 8, 8,10,10,10,10,10,12,12,10,10, + 10,12,13, 6, 8, 8,10,10, 8, 9, 9,11,11, 7, 8, 9, + 10,11,10,11,11,13,13,10,10,11,11,14, 7, 8, 8,10, + 10, 8, 9, 9,11,11, 8, 9, 8,11,10,10,11,11,13,13, + 10,11,10,13,11, 9,10,10,13,13,10,11,11,14,13,10, + 10,11,12,13,13,13,13,15,14,12,11,13,12,15, 9,10, + 11,13,13,10,11,11,13,14,10,11,10,13,13,12,13,13, + 15,15,12,13,11,15,12, 8,10,10,13,12,10,11,11,13, + 13, 9,10,11,13,13,13,13,13,15,15,12,13,13,15,15, + 9,10,10,13,13,10,11,11,13,14,10,11,11,14,13,13, + 13,14,14,15,13,13,13,14,15, 9,10,10,13,13,10,11, + 10,14,13,10,11,11,13,14,13,14,13,15,14,12,13,13, + 14,15,11,13,13,15,14,11,11,13,14,15,12,14,13,15, + 15,13,12,15,12,16,14,14,15,17,16,11,12,12,14,15, + 11,13,11,15,14,12,13,13,15,15,14,14,12,17,13,14, + 15,15,18,16, 8,10,10,13,12, 9,10,10,13,13,10,10, + 11,13,13,12,13,13,14,14,12,13,13,15,15, 9,10,10, + 13,13,10,11,11,14,13,10,10,11,13,14,12,13,13,17, + 15,12,12,13,14,16, 9,10,10,13,13,10,11,11,13,13, + 10,11,10,14,13,13,13,13,14,15,13,14,13,15,15,11, + 13,12,14,14,12,13,13,16,14,11,12,13,15,15,14,15, + 16,17,18,14,12,15,14,16,11,12,13,14,15,12,13,13, + 15,16,11,13,11,15,14,14,16,14,16,17,14,15,12,16, + 12, +}; + +static float _vq_quantthresh__44u0__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u0__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p4_0 = { + _vq_quantthresh__44u0__p4_0, + _vq_quantmap__44u0__p4_0, + 5, + 5 +}; + +static static_codebook _44u0__p4_0 = { + 4, 625, + _vq_lengthlist__44u0__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u0__p4_0, + NULL, + &_vq_auxt__44u0__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u0__p5_0[] = { + 1, 4, 4, 7, 7, 8, 8, 9, 9, 4, 6, 5, 8, 8, 8, 8, + 10,10, 4, 5, 6, 8, 8, 8, 8,10,10, 7, 8, 8, 9, 9, + 9, 9,11,11, 7, 8, 8, 9, 9, 9, 9,11,11, 8, 8, 8, + 10, 9,10,11,12,12, 8, 8, 8, 9, 9,11,11,12,12, 9, + 10,10,11,11,12,12,13,13, 9,10,10,11,11,12,12,13, + 13, +}; + +static float _vq_quantthresh__44u0__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u0__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p5_0 = { + _vq_quantthresh__44u0__p5_0, + _vq_quantmap__44u0__p5_0, + 9, + 9 +}; + +static static_codebook _44u0__p5_0 = { + 2, 81, + _vq_lengthlist__44u0__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u0__p5_0, + NULL, + &_vq_auxt__44u0__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u0__p6_0[] = { + 1, 4, 4, 6, 6, 8, 8,10, 9,10,10,14,14, 4, 6, 5, + 8, 8, 9, 9,10,10,11,11,14,14, 4, 5, 6, 8, 8, 9, + 9,10,10,11,11,14,14, 7, 8, 8, 9, 9,10,10,11,11, + 12,12,15,15, 7, 8, 8, 9, 9,10,10,11,11,12,12,14, + 15, 9, 9, 9,10,10,11,11,12,11,12,12,15,16, 9, 9, + 9,10,10,11,11,11,11,12,12,15,15,10,10,10,11,11, + 12,11,12,12,13,13,16,16,10,10,10,11,11,12,12,12, + 12,13,13,16,17,11,11,12,12,12,13,13,14,13,14,14, + 18,17,11,11,11,12,12,12,12,13,14,14,14,18,18,14, + 14,14,15,15,15,16,16,16,17,17, 0,19,14,14,14,15, + 15,16,17,16,17,17,17,19, 0, +}; + +static float _vq_quantthresh__44u0__p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44u0__p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p6_0 = { + _vq_quantthresh__44u0__p6_0, + _vq_quantmap__44u0__p6_0, + 13, + 13 +}; + +static static_codebook _44u0__p6_0 = { + 2, 169, + _vq_lengthlist__44u0__p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44u0__p6_0, + NULL, + &_vq_auxt__44u0__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u0__p6_1[] = { + 2, 4, 4, 5, 5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 5, 6, 6, 6, 6, +}; + +static float _vq_quantthresh__44u0__p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u0__p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p6_1 = { + _vq_quantthresh__44u0__p6_1, + _vq_quantmap__44u0__p6_1, + 5, + 5 +}; + +static static_codebook _44u0__p6_1 = { + 2, 25, + _vq_lengthlist__44u0__p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u0__p6_1, + NULL, + &_vq_auxt__44u0__p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p7_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u0__p7_0[] = { + 1, 5, 5,11,11, 9,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11, 8,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11, 9,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11, 8,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,10,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__44u0__p7_0[] = { + -253.5, -84.5, 84.5, 253.5, +}; + +static long _vq_quantmap__44u0__p7_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p7_0 = { + _vq_quantthresh__44u0__p7_0, + _vq_quantmap__44u0__p7_0, + 5, + 5 +}; + +static static_codebook _44u0__p7_0 = { + 4, 625, + _vq_lengthlist__44u0__p7_0, + 1, -518709248, 1626677248, 3, 0, + _vq_quantlist__44u0__p7_0, + NULL, + &_vq_auxt__44u0__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p7_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u0__p7_1[] = { + 1, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10, 4, 7, 7, + 8, 8, 8, 8, 9, 9,11,10,12,11, 4, 6, 7, 8, 8, 7, + 8, 9, 9,10,11,12,11, 7, 8, 8, 9, 9,10,10,11,11, + 12,11,13,13, 7, 8, 8, 9,10,10,10,11,11,12,12,12, + 13, 7, 8, 8,10,10,12,12,13,12,16,13,13,14, 7, 8, + 8,10,11,12,12,13,12,13,13,14,14, 8, 9,10,12,12, + 14,13,16,15,16,16,16,15, 8,10,10,12,12,14,13,14, + 15,16,16,15,16,10,11,12,14,14,14,14,16,13,16,15, + 16,16,10,11,12,13,13,15,14,14,15,16,16,14,14,13, + 14,13,15,16,16,16,15,15,16,16,16,16,11,15,14,16, + 16,14,14,16,15,16,14,16,15, +}; + +static float _vq_quantthresh__44u0__p7_1[] = { + -71.5, -58.5, -45.5, -32.5, -19.5, -6.5, 6.5, 19.5, + 32.5, 45.5, 58.5, 71.5, +}; + +static long _vq_quantmap__44u0__p7_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p7_1 = { + _vq_quantthresh__44u0__p7_1, + _vq_quantmap__44u0__p7_1, + 13, + 13 +}; + +static static_codebook _44u0__p7_1 = { + 2, 169, + _vq_lengthlist__44u0__p7_1, + 1, -523010048, 1618608128, 4, 0, + _vq_quantlist__44u0__p7_1, + NULL, + &_vq_auxt__44u0__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u0__p7_2[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u0__p7_2[] = { + 2, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 5, 5, 6, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 5, 5, 5, 7, 7, 8, + 8, 8, 8, 9, 9, 9, 9, 6, 7, 7, 7, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 6, 7, 7, 8, 7, 8, 8, 9, 9, 9, 9, 9, + 9, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 8, 9, + 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44u0__p7_2[] = { + -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, + 2.5, 3.5, 4.5, 5.5, +}; + +static long _vq_quantmap__44u0__p7_2[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u0__p7_2 = { + _vq_quantthresh__44u0__p7_2, + _vq_quantmap__44u0__p7_2, + 13, + 13 +}; + +static static_codebook _44u0__p7_2 = { + 2, 169, + _vq_lengthlist__44u0__p7_2, + 1, -531103744, 1611661312, 4, 0, + _vq_quantlist__44u0__p7_2, + NULL, + &_vq_auxt__44u0__p7_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u0__short[] = { + 6,10,11,12,13,12,12,12, 4, 6, 6, 9, 9,10,11,13, + 3, 4, 3, 7, 6, 8,10,15, 5, 7, 7, 9, 8, 9,11,16, + 6, 7, 5, 8, 5, 7,10,16, 7, 8, 7, 9, 6, 7,10,16, + 10, 6, 3, 5, 4, 5, 7,16,13, 8, 5, 7, 6, 7,10,15, +}; + +static static_codebook _huff_book__44u0__short = { + 2, 64, + _huff_lengthlist__44u0__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u1__long[] = { + 4, 8,13,11,14,11,10,14, 6, 3, 5, 6, 8, 7, 9,14, + 12, 4, 3, 6, 5, 7,10,18,11, 6, 6, 6, 6, 6, 8,16, + 13, 7, 4, 6, 4, 6, 9,19,11, 6, 6, 5, 5, 5, 7,16, + 10, 8, 9, 7, 9, 6, 4,12,11,16,18,19,18,10, 7,11, +}; + +static static_codebook _huff_book__44u1__long = { + 2, 64, + _huff_lengthlist__44u1__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u1__p1_0[] = { + 1, 4, 4, 5, 8, 7, 5, 7, 8, 5, 8, 8, 8,11,11, 8, + 10,11, 5, 8, 8, 8,11,10, 8,11,11, 4, 8, 8, 8,11, + 11, 8,11,11, 8,11,11,11,13,14,11,13,14, 8,11,11, + 10,14,11,11,13,14, 4, 8, 8, 8,11,11, 8,11,11, 7, + 11,11,11,14,13,10,12,13, 8,11,11,11,14,14,11,14, + 13, +}; + +static float _vq_quantthresh__44u1__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u1__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p1_0 = { + _vq_quantthresh__44u1__p1_0, + _vq_quantmap__44u1__p1_0, + 3, + 3 +}; + +static static_codebook _44u1__p1_0 = { + 4, 81, + _vq_lengthlist__44u1__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u1__p1_0, + NULL, + &_vq_auxt__44u1__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u1__p2_0[] = { + 2, 5, 5, 5, 6, 6, 5, 6, 6, 5, 6, 6, 7, 8, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 7, 8, 8, 5, 6, 6, 7, 8, + 8, 6, 8, 8, 7, 8, 8, 8, 9,10, 8, 9, 9, 6, 8, 8, + 7, 9, 8, 8, 9,10, 5, 6, 6, 6, 8, 8, 7, 8, 8, 6, + 8, 8, 8,10, 9, 7, 8, 9, 7, 8, 8, 8, 9, 9, 8,10, + 9, +}; + +static float _vq_quantthresh__44u1__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u1__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p2_0 = { + _vq_quantthresh__44u1__p2_0, + _vq_quantmap__44u1__p2_0, + 3, + 3 +}; + +static static_codebook _44u1__p2_0 = { + 4, 81, + _vq_lengthlist__44u1__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u1__p2_0, + NULL, + &_vq_auxt__44u1__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u1__p3_0[] = { + 2, 5, 4, 7, 7, 5, 7, 7, 9, 8, 5, 7, 7, 8, 9, 8, + 9, 9,11,11, 8, 9, 9,11,11, 5, 7, 7,10,10, 7, 9, + 9,11,10, 7, 9, 9,10,10, 9,11,10,13,12, 9,10,10, + 12,13, 5, 7, 7,10, 9, 7, 9, 9,11,10, 7, 9, 9,10, + 11, 9,10,10,12,12,10,10,11,12,13, 8,10,10,14,13, + 9,11,11,15,13, 9,11,11,15,13,12,14,12,16,14,12, + 13,12,15,14, 8,10,10,13,14, 9,11,11,13,14,10,11, + 11,13,15,12,12,13,14,15,12,13,14,14,16, 5, 7, 7, + 10,10, 7, 9, 9,11,11, 7, 9, 9,11,12,10,11,11,14, + 14,10,11,11,14,14, 7, 9, 9,12,12, 9,11,11,13,12, + 9,11,11,13,13,12,12,12,14,14,11,12,13,15,15, 7, + 9, 9,12,11, 9,11,10,12,12, 9,11,11,12,13,11,12, + 11,14,14,11,12,12,15,16, 9,11,11,15,15,11,12,12, + 16,15,10,12,12,16,15,14,15,15,17,16,13,14,14,17, + 17, 9,11,11,14,15,10,12,11,15,15,10,12,12,15,17, + 13,14,13,16,15,13,14,15,17,17, 5, 7, 7,10,10, 7, + 9, 9,12,11, 7, 9, 9,11,12,10,11,11,14,14,10,11, + 11,13,14, 7, 9, 9,11,12, 9,11,11,13,13, 9,10,11, + 12,12,11,12,12,15,15,11,12,12,13,14, 7,10, 9,12, + 12, 9,11,11,13,13, 9,11,11,12,12,11,12,12,16,15, + 11,12,12,14,14, 9,11,11,15,14,10,12,12,16,15,10, + 11,12,15,15,13,14,14,17,18,13,13,14,15,16, 9,11, + 11,15,16,10,12,12,15,15,11,12,12,14,17,13,14,14, + 17,17,14,14,14,16,18, 7,10,10,14,15,10,12,12,16, + 15,10,11,12,16,16,14,16,15,18,17,13,15,14,17,19, + 9,11,12,16,15,11,13,13,17,16,10,13,13,16,16,15, + 16,16,17,19,13,15,15,17,17, 8,11,11,15,15,10,13, + 11,16,16,10,13,13,16,17,14,16,15,18,19,13,15,15, + 17,17,12,14,14,18,18,13,14,15,18,19,12,14,15,17, + 18,16,18,18,19, 0,14,15,16,18,18,11,14,13,17,18, + 12,16,14,19,19,12,15,14, 0,18,15,17,16,18,17,14, + 17,16,18,18, 7,10,10,15,14,10,12,11,16,16,10,11, + 12,16,16,13,15,14,19,18,14,15,16,18,18, 8,11,11, + 15,15,10,13,12,17,16,10,12,13,16,17,14,14,15,19, + 18,14,15,16,18, 0, 9,11,11,16,15,11,13,12,15,16, + 11,13,13,16,16,14,15,14, 0,17,15,16,16,19,18,11, + 14,14,18,17,12,14,15,17,17,12,13,15, 0,17,14,15, + 16,18,17,16,17,18,17,19,11,14,13,17,19,12,15,14, + 18,19,13,16,14, 0,19,14,17,15, 0,18,15,18,16, 0, + 0, +}; + +static float _vq_quantthresh__44u1__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u1__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p3_0 = { + _vq_quantthresh__44u1__p3_0, + _vq_quantmap__44u1__p3_0, + 5, + 5 +}; + +static static_codebook _44u1__p3_0 = { + 4, 625, + _vq_lengthlist__44u1__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u1__p3_0, + NULL, + &_vq_auxt__44u1__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u1__p4_0[] = { + 4, 5, 5, 8, 8, 6, 7, 6, 9, 9, 6, 6, 7, 9, 9, 9, + 9, 9,11,11, 9, 9, 9,11,11, 6, 7, 7, 9, 9, 7, 8, + 8,10,10, 6, 7, 8, 9,10,10,10,10,11,12, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 6, 8, 7,10, 9, 7, 8, 8,10, + 10, 9,10, 9,12,11,10,10,10,12,11, 9,10,10,12,12, + 10,10,10,13,12, 9,10,10,12,12,12,12,12,14,14,11, + 12,12,13,14, 9,10,10,12,12, 9,10,10,13,12,10,10, + 10,12,13,12,12,12,14,13,12,12,12,14,14, 5, 7, 7, + 9, 9, 7, 8, 7,10,10, 7, 7, 8,10,10,10,10,10,12, + 12,10,10,10,12,12, 7, 8, 8,10,10, 8, 8, 9,10,11, + 8, 8, 8,11,11,10,10,11,11,13,10,11,11,12,13, 6, + 7, 7,10,10, 7, 8, 8,11,10, 8, 8, 9,11,11,10,11, + 10,13,11,10,11,11,13,12,10,11,10,13,12,10,10,11, + 13,13,10,11,11,13,13,12,11,13,12,14,12,13,13,15, + 15, 9,10,10,12,13,10,11,10,13,13,10,11,11,13,14, + 12,13,11,14,12,12,13,13,14,15, 5, 7, 7, 9,10, 7, + 8, 7,10,10, 7, 7, 8,10,10,10,10,10,12,12,10,10, + 10,12,12, 6, 7, 7,10,10, 8, 9, 8,11,11, 7, 8, 8, + 10,11,10,11,11,12,13,10,10,11,11,13, 7, 8, 8,10, + 10, 8, 8, 8,11,11, 8, 9, 8,11,10,10,11,10,13,12, + 10,11,10,13,12, 9,10,10,13,12,10,11,11,13,13, 9, + 10,10,12,13,13,13,13,15,14,12,11,13,12,15,10,10, + 11,12,13,10,11,11,13,13,10,11,10,13,13,12,13,13, + 15,15,12,13,11,14,12, 8,10, 9,12,12, 9,10,10,13, + 13, 9,10,10,13,13,13,13,13,14,15,12,12,12,14,14, + 9,10,10,13,12,10,11,11,13,13,10,11,11,13,12,13, + 13,14,14,16,12,13,13,15,14, 9,10,10,13,13,10,11, + 10,13,13,10,11,11,13,13,13,14,12,15,14,12,13,13, + 14,15,12,12,12,14,14,11,12,12,14,15,12,13,13,15, + 14,14,12,15,12,16,14,14,15,17,16,11,12,12,14,14, + 11,12,11,15,14,12,13,13,15,15,13,14,12,16,13,14, + 14,15,17,16, 8,10,10,12,12, 9,10,10,13,12,10,10, + 10,13,13,12,13,12,14,14,12,13,13,15,14, 9,10,10, + 13,13,10,11,11,13,13,10,10,11,12,13,13,13,13,15, + 15,12,12,13,14,15, 9,10,10,12,13,10,11,11,12,13, + 10,11,10,13,13,12,13,13,14,15,13,14,13,15,14,11, + 12,12,15,14,12,13,13,15,14,11,12,12,14,15,14,14, + 14,17,15,13,12,15,13,16,12,12,12,14,15,12,13,13, + 14,15,11,12,12,15,14,14,15,14,16,17,13,15,12,16, + 12, +}; + +static float _vq_quantthresh__44u1__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u1__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p4_0 = { + _vq_quantthresh__44u1__p4_0, + _vq_quantmap__44u1__p4_0, + 5, + 5 +}; + +static static_codebook _44u1__p4_0 = { + 4, 625, + _vq_lengthlist__44u1__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u1__p4_0, + NULL, + &_vq_auxt__44u1__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u1__p5_0[] = { + 1, 4, 4, 7, 7, 8, 8,10,10, 4, 6, 5, 8, 8, 8, 8, + 10,10, 4, 5, 6, 8, 8, 8, 8,10,10, 7, 8, 7, 9, 9, + 9, 9,11,11, 7, 8, 8, 9, 9, 9, 9,11,11, 8, 8, 8, + 9, 9,10,11,12,12, 8, 8, 9, 9, 9,10,10,12,12,10, + 10,10,11,11,12,12,13,13,10,10,10,11,11,12,12,13, + 13, +}; + +static float _vq_quantthresh__44u1__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u1__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p5_0 = { + _vq_quantthresh__44u1__p5_0, + _vq_quantmap__44u1__p5_0, + 9, + 9 +}; + +static static_codebook _44u1__p5_0 = { + 2, 81, + _vq_lengthlist__44u1__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u1__p5_0, + NULL, + &_vq_auxt__44u1__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u1__p6_0[] = { + 1, 4, 4, 7, 7, 9, 8,10, 9,11,10,14,13, 4, 5, 5, + 8, 8, 9, 9,11,11,11,11,14,14, 4, 5, 5, 8, 8, 9, + 9,10,11,11,11,14,14, 7, 8, 8, 9, 9,11,10,11,11, + 12,12,16,15, 7, 8, 8, 9, 9,10,11,11,11,12,12,15, + 15, 9,10,10,11,11,11,11,12,12,13,13,16,16, 9,10, + 10,11,11,11,11,12,12,12,13,16,15,10,11,11,11,11, + 12,12,13,13,13,13,16,17,10,11,11,11,11,12,12,12, + 12,13,14,16,16,11,12,12,12,12,13,13,14,14,14,15, + 18,17,11,12,12,12,12,13,13,13,14,14,15,18,18,15, + 14,15,15,15,16,16,17,17,18,17,20,20,14,15,15,15, + 15,16,17,17,16,18, 0,19, 0, +}; + +static float _vq_quantthresh__44u1__p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44u1__p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p6_0 = { + _vq_quantthresh__44u1__p6_0, + _vq_quantmap__44u1__p6_0, + 13, + 13 +}; + +static static_codebook _44u1__p6_0 = { + 2, 169, + _vq_lengthlist__44u1__p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44u1__p6_0, + NULL, + &_vq_auxt__44u1__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u1__p6_1[] = { + 2, 4, 4, 5, 5, 4, 5, 5, 6, 5, 4, 5, 5, 5, 6, 5, + 6, 5, 6, 6, 5, 5, 6, 6, 6, +}; + +static float _vq_quantthresh__44u1__p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u1__p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p6_1 = { + _vq_quantthresh__44u1__p6_1, + _vq_quantmap__44u1__p6_1, + 5, + 5 +}; + +static static_codebook _44u1__p6_1 = { + 2, 25, + _vq_lengthlist__44u1__p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u1__p6_1, + NULL, + &_vq_auxt__44u1__p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p7_0[] = { + 3, + 2, + 4, + 1, + 5, + 0, + 6, +}; + +static long _vq_lengthlist__44u1__p7_0[] = { + 1, 3, 3, 9, 9, 9, 9, 5, 7, 7, 9, 9, 9, 9, 5, 6, + 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, +}; + +static float _vq_quantthresh__44u1__p7_0[] = { + -422.5, -253.5, -84.5, 84.5, 253.5, 422.5, +}; + +static long _vq_quantmap__44u1__p7_0[] = { + 5, 3, 1, 0, 2, 4, 6, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p7_0 = { + _vq_quantthresh__44u1__p7_0, + _vq_quantmap__44u1__p7_0, + 7, + 7 +}; + +static static_codebook _44u1__p7_0 = { + 2, 49, + _vq_lengthlist__44u1__p7_0, + 1, -518017024, 1626677248, 3, 0, + _vq_quantlist__44u1__p7_0, + NULL, + &_vq_auxt__44u1__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p7_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u1__p7_1[] = { + 1, 4, 4, 6, 6, 6, 7, 8, 8, 9, 9,11,10, 4, 7, 7, + 8, 8, 8, 8, 9, 9,11,10,12,11, 4, 6, 7, 8, 8, 8, + 8,10,10,10,11,12,11, 7, 8, 8, 9, 9,10,10,11,11, + 12,12,13,13, 7, 8, 8, 9, 9,10,10,11,11,12,12,13, + 13, 7, 8, 8,10,10,13,12,13,13,16,13,13,14, 7, 8, + 8,10,11,12,12,13,13,14,14,14,14, 8,10,10,13,12, + 13,13,16,14,15,15,16,15, 8,10,11,12,13,13,13,13, + 16,15,16,16,14,10,12,13,14,15,15,14,14,14,15,16, + 15,16,10,11,12,13,13,15,15,16,16,16,14,16,16,12, + 13,13,15,14,15,14,15,15,14,15,16,16,11,14,14,15, + 14,16,15,16,14,15,16,14,15, +}; + +static float _vq_quantthresh__44u1__p7_1[] = { + -71.5, -58.5, -45.5, -32.5, -19.5, -6.5, 6.5, 19.5, + 32.5, 45.5, 58.5, 71.5, +}; + +static long _vq_quantmap__44u1__p7_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p7_1 = { + _vq_quantthresh__44u1__p7_1, + _vq_quantmap__44u1__p7_1, + 13, + 13 +}; + +static static_codebook _44u1__p7_1 = { + 2, 169, + _vq_lengthlist__44u1__p7_1, + 1, -523010048, 1618608128, 4, 0, + _vq_quantlist__44u1__p7_1, + NULL, + &_vq_auxt__44u1__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u1__p7_2[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u1__p7_2[] = { + 2, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 5, 6, 6, + 6, 7, 8, 7, 8, 8, 8, 9, 9, 9, 5, 6, 6, 7, 7, 8, + 8, 8, 8, 8, 8, 8, 9, 6, 7, 7, 7, 7, 8, 8, 8, 9, + 9, 9, 9, 9, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9, 9, 9, + 9, 7, 8, 8, 8, 8, 8, 8, 9, 8, 9, 9, 9, 9, 7, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, + 8, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 9, 9, 9, 8, 9, + 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44u1__p7_2[] = { + -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, + 2.5, 3.5, 4.5, 5.5, +}; + +static long _vq_quantmap__44u1__p7_2[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u1__p7_2 = { + _vq_quantthresh__44u1__p7_2, + _vq_quantmap__44u1__p7_2, + 13, + 13 +}; + +static static_codebook _44u1__p7_2 = { + 2, 169, + _vq_lengthlist__44u1__p7_2, + 1, -531103744, 1611661312, 4, 0, + _vq_quantlist__44u1__p7_2, + NULL, + &_vq_auxt__44u1__p7_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u1__short[] = { + 7,12,12,14,16,13,12,15, 6, 9,10,13,11,11,12,12, + 4, 5, 5, 8, 5, 7, 9,12, 6, 7, 8,10, 8, 9,11,16, + 5, 5, 4, 7, 4, 5, 7,15, 6, 5, 5, 8, 5, 5, 6,15, + 8, 7, 4, 7, 3, 4, 5,16,15,11, 6, 8, 5, 6, 8,16, +}; + +static static_codebook _huff_book__44u1__short = { + 2, 64, + _huff_lengthlist__44u1__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u2__long[] = { + 8,14,15,15,17,15,12,13,13, 3, 4, 7, 8, 7, 8,11, + 20, 4, 3, 6, 5, 7, 9,16,15, 6, 5, 6, 6, 6, 8,15, + 20, 7, 4, 6, 4, 5, 8,18,16, 7, 6, 6, 5, 5, 6,14, + 11, 7, 8, 7, 7, 5, 4,10,10,13,15,16,19,10, 6,10, +}; + +static static_codebook _huff_book__44u2__long = { + 2, 64, + _huff_lengthlist__44u2__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u2__p1_0[] = { + 1, 4, 4, 5, 8, 7, 5, 7, 8, 5, 8, 8, 8,11,11, 8, + 10,11, 5, 8, 8, 8,11,10, 8,11,11, 4, 8, 8, 8,11, + 11, 8,11,11, 8,11,11,11,13,14,11,13,13, 8,11,11, + 10,13,12,11,13,14, 4, 8, 8, 8,11,11, 8,11,11, 7, + 11,11,11,14,13,10,12,13, 8,11,11,11,14,13,11,13, + 13, +}; + +static float _vq_quantthresh__44u2__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u2__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p1_0 = { + _vq_quantthresh__44u2__p1_0, + _vq_quantmap__44u2__p1_0, + 3, + 3 +}; + +static static_codebook _44u2__p1_0 = { + 4, 81, + _vq_lengthlist__44u2__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u2__p1_0, + NULL, + &_vq_auxt__44u2__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u2__p2_0[] = { + 3, 4, 4, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 8, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 6, 8, 8, 5, 6, 6, 6, 8, + 7, 6, 7, 8, 6, 8, 8, 8, 9, 9, 8, 9, 9, 6, 8, 8, + 7, 9, 8, 8, 9, 9, 5, 6, 6, 6, 8, 8, 6, 7, 8, 6, + 8, 8, 8, 9, 9, 7, 8, 9, 6, 8, 8, 8, 9, 9, 8, 9, + 9, +}; + +static float _vq_quantthresh__44u2__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u2__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p2_0 = { + _vq_quantthresh__44u2__p2_0, + _vq_quantmap__44u2__p2_0, + 3, + 3 +}; + +static static_codebook _44u2__p2_0 = { + 4, 81, + _vq_lengthlist__44u2__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u2__p2_0, + NULL, + &_vq_auxt__44u2__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u2__p3_0[] = { + 2, 4, 4, 8, 8, 5, 7, 7, 9, 9, 5, 7, 7, 9, 9, 8, + 9, 9,12,12, 8, 9,10,11,12, 5, 7, 7,10,10, 7, 9, + 9,11,10, 7, 8, 9,10,11,10,11,10,14,13, 9,10,10, + 12,13, 5, 7, 7,10,10, 7, 9, 8,11,10, 7, 9, 9,11, + 11, 9,10,10,13,12,10,10,11,13,13, 8,10,10,15,13, + 10,11,11,15,13, 9,11,11,14,13,13,14,13,17,15,12, + 13,13,15,15, 8,10,10,13,14, 9,11,11,13,14,10,11, + 12,13,16,12,13,13,15,15,13,13,14,15,17, 5, 7, 7, + 10,10, 7, 9, 9,11,11, 7, 9, 9,11,12,10,11,11,14, + 14,10,11,12,14,14, 7, 9, 9,12,12, 9,11,11,13,12, + 9,11,11,13,13,12,13,12,14,14,11,12,12,15,14, 7, + 9, 9,12,11, 9,11,10,13,11, 9,11,11,12,13,11,12, + 11,14,13,11,12,12,15,15,10,12,12,16,15,11,13,13, + 16,16,10,12,12,16,16,14,14,14,17,16,13,14,14,17, + 18, 9,11,11,14,15,10,12,11,15,15,10,12,12,15,17, + 13,15,13,17,16,13,14,15,17,19, 5, 7, 7,10,10, 7, + 9, 9,12,11, 7, 9, 9,11,11,10,11,11,15,14,10,11, + 12,13,14, 7, 9, 9,12,12, 9,11,11,13,12, 8,10,11, + 12,12,11,12,12,15,15,11,11,12,14,14, 7, 9, 9,12, + 12, 9,11,11,13,13, 9,11,11,12,12,11,12,12,16,15, + 11,12,13,14,14, 9,11,11,16,15,10,12,12,16,15,10, + 11,12,14,14,13,14,15,18,17,13,13,14,16,16,10,12, + 12,15,15,10,13,12,15,17,11,13,12,15,16,13,15,14, + 17,18,14,15,14,15,17, 8,10,10,15,15,10,12,12,17, + 15,10,12,12,17,16,14,16,15,17,17,13,14,15,16,16, + 9,11,12,16,16,11,13,13,16,17,11,13,13,16,16,15, + 16,16, 0, 0,14,15,15,19,17, 9,11,11,17,16,10,13, + 11,16,16,11,12,12,16,17,15,16,15,19,19,14,16,15, + 0,18,12,14,15, 0, 0,14,15,16,18,18,13,14,15,17, + 17,16,18,18, 0, 0,15,16,16,19,18,11,14,13, 0,18, + 13,16,13,17,17,13,15,14, 0, 0,17,17,15,19,18,14, + 16,16,19, 0, 8,10,10,15,15,10,12,11,16,16,10,11, + 12,17,16,14,15,15,17,18,15,15,16, 0, 0, 9,11,11, + 15,15,11,13,12,16,18,10,12,13,17,17,13,15,15,17, + 19,15,15,16,19,18, 9,12,11, 0,16,11,13,12,16,16, + 11,13,13,16,17,14,16,15,19,19,15,15,16,18,18,12, + 14,14,17,18,13,14,15,18, 0,13,14,15,18,19,15,17, + 16, 0,19,16,15,18,17,19,12,14,14,19,19,13,15,15, + 0,18,13,17,15,18,18,15,16,17, 0,19,17,19,18,18, + 0, +}; + +static float _vq_quantthresh__44u2__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u2__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p3_0 = { + _vq_quantthresh__44u2__p3_0, + _vq_quantmap__44u2__p3_0, + 5, + 5 +}; + +static static_codebook _44u2__p3_0 = { + 4, 625, + _vq_lengthlist__44u2__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u2__p3_0, + NULL, + &_vq_auxt__44u2__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u2__p4_0[] = { + 4, 5, 5, 9, 9, 6, 7, 6, 9, 9, 6, 6, 7, 9, 9, 9, + 10, 9,12,11, 9, 9,10,11,12, 6, 7, 7,10,10, 7, 8, + 7,10,10, 7, 7, 8,10,10,10,10,10,12,12, 9,10,10, + 11,12, 6, 7, 7,10,10, 7, 8, 7,10,10, 7, 7, 7,10, + 10, 9,10,10,12,11,10,10,10,12,12, 9,10,10,13,12, + 10,10,10,13,13,10,10,10,13,12,12,12,12,14,14,12, + 12,12,14,14, 9,10,10,12,13, 9,10,10,13,13,10,10, + 10,13,13,12,12,12,15,14,12,13,12,14,14, 5, 7, 7, + 10,10, 7, 8, 7,10,10, 7, 7, 8,10,10,10,10,10,12, + 12,10,10,10,12,12, 7, 8, 8,10,10, 8, 8, 8,10,11, + 8, 8, 8,11,10,10,10,11,11,13,10,10,11,12,13, 6, + 7, 7,10,10, 7, 8, 7,11,10, 8, 8, 8,10,11,10,11, + 10,13,11,10,10,10,13,12,10,11,10,13,13,10,10,10, + 12,13,10,11,11,13,13,12,11,13,11,14,12,13,13,14, + 14, 9,10,10,12,13,10,10,10,13,12,10,10,11,13,13, + 12,13,11,14,12,13,13,13,15,14, 5, 7, 7,10,10, 7, + 7, 7,10,10, 7, 7, 8,10,10,10,10,10,12,12,10,10, + 10,12,13, 6, 7, 7,10,10, 8, 8, 8,11,10, 7, 7, 8, + 10,11,10,10,10,12,12,10,10,11,11,13, 7, 8, 8,10, + 10, 7, 8, 8,10,11, 8, 8, 8,11,10,10,11,10,13,12, + 10,11,10,13,11, 9,10,10,13,13,10,11,11,13,13,10, + 10,10,12,13,13,13,13,14,14,12,11,13,12,14,10,10, + 11,13,13,10,11,11,13,13,10,10,10,13,12,12,13,13, + 14,14,12,13,11,15,12, 9,10,10,13,13,10,10,10,13, + 13,10,10,10,13,13,13,13,13,15,15,12,13,13,14,14, + 9,10,10,13,13,10,10,11,13,13,10,11,10,13,12,13, + 12,13,14,15,13,13,13,15,14, 9,10,10,13,13,10,10, + 10,13,12,10,10,11,12,13,13,13,12,15,14,13,13,13, + 14,14,12,13,12,15,14,12,11,12,14,14,12,13,12,15, + 14,14,12,15,12,16,14,14,15,16,16,12,12,12,14,15, + 11,12,11,15,14,12,13,13,14,15,14,14,12,16,13,14, + 14,15,16,16, 9,10,10,13,13,10,10,10,13,13,10,10, + 10,13,13,12,13,12,14,14,13,13,13,15,15, 9,10,10, + 13,13,10,11,10,13,12,10,10,10,12,13,13,13,13,14, + 14,12,12,13,14,15, 9,10,10,13,13,10,10,11,12,13, + 10,11,10,13,13,13,13,13,14,15,13,13,13,15,14,12, + 12,12,15,14,12,13,12,15,14,11,11,12,14,15,14,14, + 14,17,16,14,12,14,13,17,12,12,13,14,16,13,13,13, + 13,15,12,12,11,14,14,14,15,14,16,16,14,14,12,16, + 12, +}; + +static float _vq_quantthresh__44u2__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u2__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p4_0 = { + _vq_quantthresh__44u2__p4_0, + _vq_quantmap__44u2__p4_0, + 5, + 5 +}; + +static static_codebook _44u2__p4_0 = { + 4, 625, + _vq_lengthlist__44u2__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u2__p4_0, + NULL, + &_vq_auxt__44u2__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u2__p5_0[] = { + 2, 4, 4, 6, 6, 7, 7, 9, 9, 4, 5, 4, 7, 7, 8, 7, + 9, 9, 4, 4, 4, 7, 7, 7, 8, 9, 9, 6, 7, 7, 8, 8, + 8, 9,10,10, 6, 7, 7, 8, 8, 9, 8,10,10, 7, 8, 7, + 8, 9, 9,10,11,11, 7, 7, 8, 9, 9,10,10,11,11, 9, + 9, 9,10,10,11,11,12,12, 9, 9, 9,10,10,11,11,12, + 12, +}; + +static float _vq_quantthresh__44u2__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u2__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p5_0 = { + _vq_quantthresh__44u2__p5_0, + _vq_quantmap__44u2__p5_0, + 9, + 9 +}; + +static static_codebook _44u2__p5_0 = { + 2, 81, + _vq_lengthlist__44u2__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u2__p5_0, + NULL, + &_vq_auxt__44u2__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u2__p6_0[] = { + 1, 4, 4, 6, 6, 8, 8, 9,10,11,11,14,14, 4, 6, 5, + 8, 8, 9, 9,11,11,11,11,14,14, 4, 5, 5, 8, 8, 9, + 9,10,11,11,12,14,14, 7, 8, 8, 9, 9,11,10,11,11, + 12,12,15,15, 7, 8, 8, 9, 9,11,11,11,11,12,12,15, + 14, 9,10,10,11,11,11,11,12,12,13,12,16,16, 9,10, + 10,11,11,11,11,12,12,13,13,15,16,10,11,11,11,11, + 12,12,13,13,13,13,17,16,10,11,11,12,12,12,12,12, + 13,14,14,16,16,11,12,12,12,12,13,13,14,14,14,14, + 17,17,11,12,12,12,12,13,13,14,14,15,14,18,17,15, + 14,15,15,15,16,16,17,18,19,17,19, 0,15,15,15,15, + 15,16,17,16,16,18,17, 0, 0, +}; + +static float _vq_quantthresh__44u2__p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44u2__p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p6_0 = { + _vq_quantthresh__44u2__p6_0, + _vq_quantmap__44u2__p6_0, + 13, + 13 +}; + +static static_codebook _44u2__p6_0 = { + 2, 169, + _vq_lengthlist__44u2__p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44u2__p6_0, + NULL, + &_vq_auxt__44u2__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u2__p6_1[] = { + 2, 4, 4, 5, 5, 4, 5, 5, 6, 5, 4, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 5, 5, 6, 6, 6, +}; + +static float _vq_quantthresh__44u2__p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u2__p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p6_1 = { + _vq_quantthresh__44u2__p6_1, + _vq_quantmap__44u2__p6_1, + 5, + 5 +}; + +static static_codebook _44u2__p6_1 = { + 2, 25, + _vq_lengthlist__44u2__p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u2__p6_1, + NULL, + &_vq_auxt__44u2__p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p7_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u2__p7_0[] = { + 1, 3, 3,10,10,10,10,10,10, 4,10,10,10,10,10,10, + 10,10, 4,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44u2__p7_0[] = { + -591.5, -422.5, -253.5, -84.5, 84.5, 253.5, 422.5, 591.5, +}; + +static long _vq_quantmap__44u2__p7_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p7_0 = { + _vq_quantthresh__44u2__p7_0, + _vq_quantmap__44u2__p7_0, + 9, + 9 +}; + +static static_codebook _44u2__p7_0 = { + 2, 81, + _vq_lengthlist__44u2__p7_0, + 1, -516612096, 1626677248, 4, 0, + _vq_quantlist__44u2__p7_0, + NULL, + &_vq_auxt__44u2__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p7_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u2__p7_1[] = { + 1, 4, 4, 6, 6, 7, 7, 8, 7, 9, 9,10,10, 4, 7, 6, + 8, 8, 8, 8,10, 9,11,10,12,12, 4, 6, 7, 8, 8, 8, + 8,10,10,11,11,12,12, 7, 8, 8,10, 9,10,10,12,11, + 13,12,13,13, 7, 8, 8, 9, 9,10,10,11,11,13,13,14, + 15, 7, 8, 9,10,10,12,11,14,13,13,14,14,15, 7, 8, + 9,10,10,11,12,13,13,15,14,14,15, 8,10,10,12,11, + 13,13,15,15,15,17,15,15, 8,10,10,11,12,14,13,14, + 15,17,15,15,15,10,11,11,14,13,14,14,15,15,17,17, + 16,17,10,11,12,13,13,14,14,14,15,16,15,15,17,11, + 12,13,14,13,16,16,16,14,17,16,17,17,11,12,13,15, + 15,15,15,16,15,15,15,15,17, +}; + +static float _vq_quantthresh__44u2__p7_1[] = { + -71.5, -58.5, -45.5, -32.5, -19.5, -6.5, 6.5, 19.5, + 32.5, 45.5, 58.5, 71.5, +}; + +static long _vq_quantmap__44u2__p7_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p7_1 = { + _vq_quantthresh__44u2__p7_1, + _vq_quantmap__44u2__p7_1, + 13, + 13 +}; + +static static_codebook _44u2__p7_1 = { + 2, 169, + _vq_lengthlist__44u2__p7_1, + 1, -523010048, 1618608128, 4, 0, + _vq_quantlist__44u2__p7_1, + NULL, + &_vq_auxt__44u2__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u2__p7_2[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u2__p7_2[] = { + 2, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 5, 6, 6, + 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 5, 6, 6, 7, 7, 8, + 7, 8, 8, 8, 8, 8, 8, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 9, 9, 9, 9, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 7, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 7, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, 8, 9, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44u2__p7_2[] = { + -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, + 2.5, 3.5, 4.5, 5.5, +}; + +static long _vq_quantmap__44u2__p7_2[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u2__p7_2 = { + _vq_quantthresh__44u2__p7_2, + _vq_quantmap__44u2__p7_2, + 13, + 13 +}; + +static static_codebook _44u2__p7_2 = { + 2, 169, + _vq_lengthlist__44u2__p7_2, + 1, -531103744, 1611661312, 4, 0, + _vq_quantlist__44u2__p7_2, + NULL, + &_vq_auxt__44u2__p7_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u2__short[] = { + 16,15,15,16,14,16,16,16,16, 9, 8,11,10,10,10,15, + 16, 6, 3, 7, 5, 7, 9,16,16,11, 7,11, 9,10,12,16, + 16, 9, 4, 8, 3, 5, 8,16,16,11, 6, 9, 4, 4, 7,16, + 16,11, 5, 9, 3, 3, 5,12,16,15, 6,11, 5, 5, 6,14, +}; + +static static_codebook _huff_book__44u2__short = { + 2, 64, + _huff_lengthlist__44u2__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u3__long[] = { + 7,11,14,13,14,12,12,12,12, 2, 5, 8, 9, 8, 9,11, + 17, 4, 3, 6, 5, 7, 9,15,14, 6, 6, 7, 7, 7, 8,14, + 17, 8, 5, 7, 4, 5, 7,15,13, 7, 6, 7, 5, 5, 6,14, + 10, 8, 8, 7, 7, 5, 4, 9,10,12,15,14,16,10, 6, 9, +}; + +static static_codebook _huff_book__44u3__long = { + 2, 64, + _huff_lengthlist__44u3__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u3__p1_0[] = { + 1, 4, 4, 5, 8, 7, 5, 7, 8, 5, 8, 8, 8,11,11, 8, + 10,11, 5, 8, 8, 8,11,10, 8,11,11, 4, 8, 8, 8,11, + 11, 8,11,11, 8,11,11,11,13,14,11,13,14, 7,11,11, + 10,13,11,11,13,14, 4, 8, 8, 8,11,11, 8,11,11, 8, + 11,11,11,14,14,10,12,14, 8,11,11,11,14,13,11,14, + 13, +}; + +static float _vq_quantthresh__44u3__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u3__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p1_0 = { + _vq_quantthresh__44u3__p1_0, + _vq_quantmap__44u3__p1_0, + 3, + 3 +}; + +static static_codebook _44u3__p1_0 = { + 4, 81, + _vq_lengthlist__44u3__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u3__p1_0, + NULL, + &_vq_auxt__44u3__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u3__p2_0[] = { + 3, 4, 4, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 8, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 6, 8, 8, 5, 6, 6, 6, 8, + 7, 6, 8, 8, 6, 8, 8, 8, 8, 9, 8, 9, 9, 6, 8, 7, + 7, 9, 8, 8, 9, 9, 5, 6, 6, 6, 8, 8, 6, 8, 8, 6, + 8, 8, 8, 9, 9, 7, 8, 9, 6, 8, 8, 8, 9, 9, 8, 9, + 8, +}; + +static float _vq_quantthresh__44u3__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u3__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p2_0 = { + _vq_quantthresh__44u3__p2_0, + _vq_quantmap__44u3__p2_0, + 3, + 3 +}; + +static static_codebook _44u3__p2_0 = { + 4, 81, + _vq_lengthlist__44u3__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u3__p2_0, + NULL, + &_vq_auxt__44u3__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u3__p3_0[] = { + 2, 4, 4, 8, 8, 5, 7, 7, 9, 9, 5, 7, 7, 9, 9, 8, + 10, 9,12,12, 8, 9,10,12,12, 5, 7, 7,10,10, 7, 9, + 9,11,11, 7, 8, 9,10,11,10,11,10,14,13, 9,10,11, + 13,13, 5, 7, 7,10,10, 7, 9, 8,11,10, 7, 9, 9,11, + 11, 9,10,10,13,12,10,10,11,13,14, 8,10,10,14,13, + 10,11,11,15,13, 9,11,11,14,13,13,14,13,17,15,12, + 13,13,17,14, 8,10,10,14,14, 9,11,11,14,15,10,11, + 12,14,16,12,13,13,14,17,13,13,14,14,17, 5, 7, 7, + 10,10, 7, 9, 9,11,11, 7, 9, 9,11,11,10,11,11,15, + 14,10,11,11,15,14, 7, 9, 9,12,12, 9,11,11,13,13, + 9,11,11,13,13,11,12,12,15,14,11,12,12,15,16, 7, + 9, 9,12,11, 8,11,10,13,12, 9,11,11,12,13,11,12, + 11,16,14,11,12,13,15,16,10,12,12,17,15,11,12,13, + 16,15,11,12,12,16,16,15,15,15,16,16,13,14,15, 0, + 17, 9,11,11,15,15,10,12,11,16,15,11,12,12,15,17, + 13,15,13,16,15,13,15,14, 0,18, 5, 7, 7,10,10, 7, + 9, 9,12,11, 7, 9, 9,11,11,10,11,11,14,14,10,11, + 11,14,15, 7, 9, 9,12,11, 9,11,11,13,12, 8,10,11, + 11,12,11,12,12,16,15,11,11,12,13,14, 7, 9, 9,12, + 12, 9,11,11,13,13, 9,11,11,13,13,11,12,12,16,15, + 12,12,12,15,15, 9,11,11,17,15,11,12,12,17,16,10, + 11,12,15,15,13,14,15,18,17,13,13,14,15,15, 9,12, + 12,15,14,11,13,12,16,16,11,12,12,15,15,13,15,14, + 0, 0,14,15,14,16,18, 8,10,10,15,15,10,12,12,16, + 14,10,11,11,16,16,15,16,16,18,16,13,15,14,17,18, + 9,11,11,16,15,11,12,13,17,17,11,13,13,16,15,15, + 16,16,18,18,14,16,15,18,17, 9,11,11,16,16,10,13, + 12,16,16,11,12,12,16,16,14,16,16,17, 0,14,15,15, + 18,16,12,14,14, 0, 0,14,15,16, 0, 0,14,15,15, 0, + 0,17,17,16, 0, 0,15,16,18,17, 0,11,14,14,18, 0, + 12,15,13, 0,18,13,15,14,18, 0,15,16,15, 0,18,15, + 18,17, 0,18, 8,10,10,15,15,10,12,11,15,15,10,11, + 12,15,15,13,14,15,17, 0,14,15,16,17, 0, 9,11,11, + 15,15,11,12,12,17,15,10,12,13,16,15,13,15,15,18, + 18,15,16,17,18,18, 9,12,11,16,16,11,13,13,16,16, + 11,13,13,18,15,14,15,15,18, 0,16,16,17, 0,17,12, + 13,13,18,18,13,14,14,17,18,13,14,15,18,17,17,18, + 18, 0,18,16,16,18,16, 0,12,15,13,18,18,13,14,15, + 18,18,13,16,14,17,18,15,17,16, 0,18,17, 0,17, 0, + 0, +}; + +static float _vq_quantthresh__44u3__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u3__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p3_0 = { + _vq_quantthresh__44u3__p3_0, + _vq_quantmap__44u3__p3_0, + 5, + 5 +}; + +static static_codebook _44u3__p3_0 = { + 4, 625, + _vq_lengthlist__44u3__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u3__p3_0, + NULL, + &_vq_auxt__44u3__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u3__p4_0[] = { + 4, 6, 6, 9, 9, 6, 7, 6, 9, 9, 6, 6, 7, 9, 9, 9, + 10, 9,12,11, 9, 9,10,11,12, 6, 7, 7, 9, 9, 7, 7, + 7,10,10, 6, 7, 7, 9,10,10,10,10,12,12, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 7, 7, 7,10, 9, 7, 7, 7,10, + 10, 9,10, 9,12,11,10,10,10,12,12, 9,10,10,12,12, + 10,10,10,13,12, 9,10,10,12,12,12,12,12,14,14,12, + 12,12,13,14, 9,10,10,12,12, 9,10,10,12,12,10,10, + 10,12,13,11,12,12,14,13,12,12,12,14,14, 6, 7, 7, + 10, 9, 7, 8, 7,10,10, 7, 7, 7,10,10,10,10,10,12, + 12,10,10,10,12,12, 7, 8, 8,10,10, 8, 8, 8,10,10, + 7, 8, 8,10,10,10,10,11,11,13,10,10,11,12,13, 6, + 7, 7,10,10, 7, 8, 7,10,10, 8, 8, 8,10,10,10,11, + 10,13,11,10,10,10,12,12,10,10,10,13,12,10,10,10, + 12,13,10,10,10,13,12,12,11,13,12,14,12,12,13,14, + 14, 9,10,10,12,13, 9,10,10,12,12,10,10,11,12,13, + 12,12,11,14,12,12,12,13,14,14, 6, 7, 7, 9, 9, 7, + 7, 7,10,10, 7, 7, 8,10,10,10,10,10,12,12,10,10, + 10,12,12, 6, 7, 7,10,10, 8, 8, 8,10,10, 7, 7, 8, + 10,10,10,10,10,12,12,10,10,11,11,13, 7, 8, 8,10, + 10, 7, 8, 8,10,10, 8, 8, 8,10,10,10,10,10,12,12, + 10,11,10,13,11, 9,10,10,13,12,10,11,10,13,12, 9, + 10,10,12,13,12,13,12,14,14,12,11,12,12,14,10,10, + 10,12,13,10,10,11,12,13,10,10,10,12,12,12,13,12, + 14,14,12,12,11,14,12, 9,10, 9,12,12,10,10,10,13, + 13, 9,10,10,13,13,12,13,13,15,14,12,12,13,14,14, + 9,10,10,13,13,10,10,10,12,13,10,10,10,13,12,13, + 12,13,14,15,12,13,13,15,14, 9,10,10,13,12,10,10, + 10,13,12,10,10,10,12,13,12,13,12,14,14,13,12,12, + 14,14,12,12,12,15,14,12,11,12,14,14,12,13,12,14, + 14,14,12,14,12,16,14,14,14,16,16,11,12,12,14,14, + 11,12,11,15,13,12,12,12,14,15,13,14,12,16,13,14, + 14,15,16,16, 9,10,10,12,12, 9,10,10,13,12, 9,10, + 10,13,13,12,12,12,14,14,12,13,13,14,15, 9,10,10, + 13,12,10,11,10,13,12,10,10,10,12,13,12,13,12,14, + 14,12,12,13,14,15, 9,10,10,13,13,10,10,10,12,13, + 10,10,10,13,13,12,13,13,14,15,13,13,12,14,14,11, + 12,12,14,14,12,13,12,15,14,11,11,12,14,15,14,14, + 14,16,16,14,12,14,13,16,12,12,12,14,15,12,12,13, + 14,15,12,12,11,14,14,14,14,14,16,16,14,14,12,16, + 12, +}; + +static float _vq_quantthresh__44u3__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u3__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p4_0 = { + _vq_quantthresh__44u3__p4_0, + _vq_quantmap__44u3__p4_0, + 5, + 5 +}; + +static static_codebook _44u3__p4_0 = { + 4, 625, + _vq_lengthlist__44u3__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u3__p4_0, + NULL, + &_vq_auxt__44u3__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u3__p5_0[] = { + 2, 4, 4, 6, 6, 7, 7, 9, 9, 4, 5, 4, 7, 6, 8, 8, + 9, 9, 4, 4, 4, 6, 7, 8, 8, 9, 9, 6, 7, 6, 8, 8, + 9, 9,10,10, 6, 6, 7, 8, 8, 9, 9,10,10, 8, 8, 8, + 9, 9,10,10,11,11, 8, 8, 8, 9, 9,10,10,11,11, 9, + 9, 9,10,10,11,11,12,12, 9, 9,10,10,10,11,11,12, + 12, +}; + +static float _vq_quantthresh__44u3__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u3__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p5_0 = { + _vq_quantthresh__44u3__p5_0, + _vq_quantmap__44u3__p5_0, + 9, + 9 +}; + +static static_codebook _44u3__p5_0 = { + 2, 81, + _vq_lengthlist__44u3__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u3__p5_0, + NULL, + &_vq_auxt__44u3__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u3__p6_0[] = { + 1, 4, 4, 6, 6, 8, 8, 9,10,10,11,13,14, 4, 6, 5, + 8, 8, 9, 9,10,10,11,11,14,14, 4, 5, 6, 8, 8, 9, + 9,10,10,11,11,14,14, 7, 8, 8, 9, 9,10,10,11,11, + 12,12,15,15, 7, 8, 8, 9, 9,10,10,11,11,12,12,14, + 15, 8, 9, 9,10,10,11,11,12,12,13,12,16,16, 8, 9, + 9,10,10,11,11,12,12,12,12,16,16,10,11,10,11,11, + 12,12,13,13,13,13,17,16,10,11,11,11,11,12,12,12, + 12,13,13,17,17,11,11,12,12,12,13,13,13,14,14,14, + 16,17,11,12,11,12,12,13,13,14,14,14,14,19,17,14, + 14,14,16,15,16,16,16,17,17,17,20,19,14,15,15,15, + 15,15,16,16,17,17,17,20,19, +}; + +static float _vq_quantthresh__44u3__p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44u3__p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p6_0 = { + _vq_quantthresh__44u3__p6_0, + _vq_quantmap__44u3__p6_0, + 13, + 13 +}; + +static static_codebook _44u3__p6_0 = { + 2, 169, + _vq_lengthlist__44u3__p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44u3__p6_0, + NULL, + &_vq_auxt__44u3__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u3__p6_1[] = { + 2, 4, 4, 5, 5, 4, 5, 5, 6, 5, 4, 5, 5, 5, 6, 5, + 6, 5, 6, 6, 5, 5, 6, 6, 6, +}; + +static float _vq_quantthresh__44u3__p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u3__p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p6_1 = { + _vq_quantthresh__44u3__p6_1, + _vq_quantmap__44u3__p6_1, + 5, + 5 +}; + +static static_codebook _44u3__p6_1 = { + 2, 25, + _vq_lengthlist__44u3__p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u3__p6_1, + NULL, + &_vq_auxt__44u3__p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p7_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u3__p7_0[] = { + 1, 4, 4, 9, 9, 9, 9, 9, 9, 4, 9, 9, 9, 9, 9, 9, + 9, 9, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, +}; + +static float _vq_quantthresh__44u3__p7_0[] = { + -892.5, -637.5, -382.5, -127.5, 127.5, 382.5, 637.5, 892.5, +}; + +static long _vq_quantmap__44u3__p7_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p7_0 = { + _vq_quantthresh__44u3__p7_0, + _vq_quantmap__44u3__p7_0, + 9, + 9 +}; + +static static_codebook _44u3__p7_0 = { + 2, 81, + _vq_lengthlist__44u3__p7_0, + 1, -515907584, 1627381760, 4, 0, + _vq_quantlist__44u3__p7_0, + NULL, + &_vq_auxt__44u3__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p7_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u3__p7_1[] = { + 1, 4, 4, 6, 5, 7, 7, 9, 8,10,10,11,11,12,13, 4, + 7, 6, 7, 7, 9, 9,10,10,11,11,14,13,14,13, 4, 6, + 7, 7, 8, 9, 9,10,10,12,12,13,13,13,13, 6, 8, 8, + 10, 9,11,11,12,12,13,13,14,14,15,14, 6, 8, 8, 9, + 9,11,11,12,12,14,14,14,15,15,15, 8, 9, 9,11,10, + 13,12,14,14,14,14,15,16,15,15, 8, 9, 9,11,11,12, + 12,14,14,15,14,15,15,14,17, 9,10,10,13,12,14,14, + 15,14,14,17,15,15,16,15, 9,11,11,12,13,14,14,15, + 15,15,16,15,17,16,17,11,12,12,14,14,15,14,16,16, + 16,15,15,17,16,16,11,12,13,14,15,15,15,15,15,16, + 16,17,17,16,17,12,13,13,15,14,15,15,15,15,16,16, + 16,17,17,17,13,12,14,14,15,15,15,15,16,17,17,15, + 17,17,17,12,14,14,16,15,17,16,17,16,17,16,17,17, + 17,17,14,13,14,15,16,17,17,17,15,17,17,17,16,17, + 16, +}; + +static float _vq_quantthresh__44u3__p7_1[] = { + -110.5, -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, + 25.5, 42.5, 59.5, 76.5, 93.5, 110.5, +}; + +static long _vq_quantmap__44u3__p7_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p7_1 = { + _vq_quantthresh__44u3__p7_1, + _vq_quantmap__44u3__p7_1, + 15, + 15 +}; + +static static_codebook _44u3__p7_1 = { + 2, 225, + _vq_lengthlist__44u3__p7_1, + 1, -522338304, 1620115456, 4, 0, + _vq_quantlist__44u3__p7_1, + NULL, + &_vq_auxt__44u3__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u3__p7_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44u3__p7_2[] = { + 3, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9,10, 9, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9,10,10,10, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9,10,10,10,10, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9,10,10,10,10,10,10, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9,10,10,10,10,10,10, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9,10,10,10,10,10,10, 9, 9, 9, 9, 9, + 9, 9,10, 9,10,10,10,10,10,10,10,10, 9, 9, 9, 9, + 9, 9, 9,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, + 9, 9,10,10,10,10,10,10,10,10,10,10,10,10, 9, 9, + 9,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 9, + 9,10,10, 9,10, 9,10,10,10,10,10,10,10,10,10,10, + 9,10, 9,10, 9,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__44u3__p7_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44u3__p7_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44u3__p7_2 = { + _vq_quantthresh__44u3__p7_2, + _vq_quantmap__44u3__p7_2, + 17, + 17 +}; + +static static_codebook _44u3__p7_2 = { + 2, 289, + _vq_lengthlist__44u3__p7_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44u3__p7_2, + NULL, + &_vq_auxt__44u3__p7_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u3__short[] = { + 17,17,17,17,17,17,16,16,16,12,11,13,11,12,11,15, + 16, 9, 4, 8, 5, 7, 9,15,16,14, 9,12,10,10,12,16, + 16,11, 4, 9, 3, 4, 8,16,16,12, 6,10, 3, 4, 7,16, + 16,12, 6,11, 3, 3, 4,12,16,16, 7,13, 5, 5, 6,13, +}; + +static static_codebook _huff_book__44u3__short = { + 2, 64, + _huff_lengthlist__44u3__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u4__long[] = { + 3, 7,11,11,12,11,11,12, 5, 3, 5, 7, 8, 8, 9,11, + 9, 4, 4, 6, 6, 7, 9,15,10, 6, 6, 7, 7, 7, 8,14, + 12, 7, 5, 6, 4, 5, 7,15,10, 7, 6, 7, 5, 5, 6,13, + 9, 8, 8, 7, 7, 5, 4, 8,10,12,14,13,14,10, 5, 7, +}; + +static static_codebook _huff_book__44u4__long = { + 2, 64, + _huff_lengthlist__44u4__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u4__p1_0[] = { + 1, 4, 4, 5, 8, 7, 5, 7, 8, 5, 8, 8, 8,11,11, 8, + 10,11, 5, 8, 8, 8,11,10, 8,11,11, 4, 8, 8, 8,11, + 11, 8,11,11, 8,11,11,11,13,14,11,14,14, 7,11,10, + 10,14,12,11,14,14, 4, 8, 8, 8,11,11, 8,11,11, 8, + 11,11,11,14,14,10,12,14, 8,11,11,11,14,14,11,14, + 13, +}; + +static float _vq_quantthresh__44u4__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u4__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p1_0 = { + _vq_quantthresh__44u4__p1_0, + _vq_quantmap__44u4__p1_0, + 3, + 3 +}; + +static static_codebook _44u4__p1_0 = { + 4, 81, + _vq_lengthlist__44u4__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u4__p1_0, + NULL, + &_vq_auxt__44u4__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u4__p2_0[] = { + 2, 5, 5, 5, 6, 6, 5, 6, 6, 5, 6, 6, 7, 8, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 7, 8, 8, 5, 6, 6, 7, 8, + 8, 6, 8, 8, 7, 8, 8, 8, 9,10, 8,10,10, 6, 8, 8, + 7, 9, 8, 8, 9,10, 5, 6, 6, 6, 8, 8, 7, 8, 8, 6, + 8, 8, 8,10, 9, 8, 8, 9, 6, 8, 8, 8,10,10, 8,10, + 9, +}; + +static float _vq_quantthresh__44u4__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u4__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p2_0 = { + _vq_quantthresh__44u4__p2_0, + _vq_quantmap__44u4__p2_0, + 3, + 3 +}; + +static static_codebook _44u4__p2_0 = { + 4, 81, + _vq_lengthlist__44u4__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u4__p2_0, + NULL, + &_vq_auxt__44u4__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u4__p3_0[] = { + 2, 4, 4, 7, 7, 5, 7, 7, 9, 9, 5, 7, 7, 9, 9, 8, + 10, 9,12,12, 8, 9, 9,12,12, 5, 7, 7,10, 9, 7, 9, + 9,11,11, 7, 8, 9,10,11,10,11,11,13,13, 9,10,11, + 13,13, 5, 7, 7, 9,10, 7, 9, 9,11,11, 7, 9, 9,11, + 11, 9,11,10,13,12,10,11,11,13,13, 8,10,10,13,13, + 10,11,11,15,13, 9,11,11,14,13,13,15,13,16,15,12, + 13,13,15,15, 8,10,10,13,14, 9,11,11,14,14,10,11, + 12,14,16,12,13,13,14,16,13,14,14,15,17, 5, 7, 7, + 10,10, 7, 9, 9,11,11, 7, 9, 9,11,11,10,11,11,15, + 14,10,11,11,14,14, 7, 9, 9,12,12, 9,11,11,13,13, + 9,11,11,13,13,11,12,13,15,15,11,13,13,16,15, 7, + 9, 9,11,11, 9,11,10,13,12, 9,11,12,13,13,11,13, + 12,16,14,11,13,13,15,16,10,12,12,18,15,11,12,13, + 16,15,11,13,13,17,16,15,15,15,17,17,14,15,16,18, + 19, 9,11,11,15,15,10,12,11,15,16,11,13,13,15,16, + 13,15,13,18,15,14,15,15,17,19, 5, 7, 7,10,10, 7, + 9, 9,12,11, 7, 9, 9,11,12,10,12,11,14,14,10,11, + 12,14,15, 7, 9, 9,11,11, 9,11,11,13,12, 9,10,11, + 12,13,11,13,13,16,17,11,11,13,14,15, 7, 9, 9,12, + 12, 9,11,11,13,13, 9,11,11,13,13,11,13,12,15,16, + 11,13,13,16,15, 9,11,11,16,15,11,13,12,16,15,10, + 12,12,16,15,14,15,16,19,17,13,14,15,15,16,10,11, + 12,15,15,11,13,13,16,16,11,13,12,16,17,14,15,15, + 18,17,14,16,14,16,19, 7,10,10,15,14,10,12,12,16, + 15,10,11,11,16,15,14,16,16,19,18,13,15,14,17,17, + 9,11,11,17,16,11,13,14,18,17,11,13,13,16,16,15, + 16,17,19, 0,14,15,18,16,19, 9,11,11,16,15,11,13, + 12,18,16,11,13,13,17,16,14,16,16,17,19,15,16,15, + 18, 0,12,14,15,18,19,13,15,16,19,17,14,16,15,18, + 0,17,16,19, 0,18,16,17,18, 0, 0,11,13,14,18,19, + 13,15,13,19,19,14,15,15,17,17,15,17,15,19,17,16, + 18,18,19, 0, 8,10,10,14,14,10,12,11,15,15,10,11, + 12,15,17,13,15,15,17,16,14,15,15,18, 0, 9,11,11, + 15,15,11,13,13,18,16,10,12,13,16,17,14,15,16,17, + 18,14,15,17,19,19, 9,12,12,15,16,11,13,13,16,17, + 11,14,13,19,16,14,16,15,18,19,15,16,18,19,18,12, + 14,14,19,18,13,15,17,18,17,13,14,15,18, 0,16, 0, + 0,18,18,16,15, 0,17,19,12,15,14,17, 0,13,15,16, + 19,18,13,16,15, 0,19,16,18,16,19,18,17,19,16, 0, + 19, +}; + +static float _vq_quantthresh__44u4__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u4__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p3_0 = { + _vq_quantthresh__44u4__p3_0, + _vq_quantmap__44u4__p3_0, + 5, + 5 +}; + +static static_codebook _44u4__p3_0 = { + 4, 625, + _vq_lengthlist__44u4__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u4__p3_0, + NULL, + &_vq_auxt__44u4__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u4__p4_0[] = { + 4, 5, 5, 8, 8, 6, 7, 6, 9, 9, 6, 6, 7, 9, 9, 9, + 9, 9,11,11, 8, 9, 9,11,11, 6, 7, 7, 9, 9, 7, 8, + 8,10,10, 6, 7, 8, 9,10, 9,10,10,11,12, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 7, 8, 7,10, 9, 7, 8, 8,10, + 10, 9,10, 9,12,11, 9,10,10,12,11, 9, 9, 9,12,11, + 9,10,10,12,12, 9,10,10,12,12,11,12,12,13,14,11, + 11,12,13,14, 9, 9, 9,11,12, 9,10,10,12,12, 9,10, + 10,12,12,11,12,11,14,13,11,12,12,13,13, 5, 7, 7, + 9, 9, 7, 8, 8,10,10, 7, 7, 8,10,10, 9,10,10,12, + 12, 9,10,10,12,12, 7, 8, 8,10,10, 8, 8, 9,10,11, + 8, 9, 9,11,11,10,10,11,11,13,10,10,11,12,13, 6, + 7, 8,10,10, 7, 9, 8,11,10, 8, 8, 9,10,11,10,11, + 10,13,11,10,11,11,13,12, 9,10,10,12,12,10,10,11, + 12,13,10,11,11,13,13,12,11,13,12,15,12,13,13,14, + 14, 9,10,10,12,12, 9,11,10,13,12,10,11,11,13,13, + 11,13,11,14,12,12,13,13,14,15, 5, 7, 7, 9, 9, 7, + 8, 7,10,10, 7, 8, 8,10,10, 9,10,10,12,12, 9,10, + 10,12,12, 6, 8, 7,10,10, 8, 9, 8,11,10, 7, 8, 9, + 10,11,10,11,11,12,13,10,10,11,11,13, 7, 8, 8,10, + 10, 8, 9, 9,10,11, 8, 9, 8,11,10,10,11,11,13,12, + 10,11,10,13,11, 9,10,10,13,12,10,11,11,13,13, 9, + 10,10,12,13,12,13,13,15,14,11,11,13,12,14, 9,10, + 10,12,12,10,11,11,13,13,10,11,10,13,12,12,13,13, + 14,14,12,13,11,15,12, 8, 9, 9,12,12, 9,10,10,13, + 12, 9,10,10,12,12,12,13,12,14,14,11,12,12,14,13, + 9,10,10,12,12,10,10,11,13,13,10,11,11,13,12,12, + 13,13,14,15,12,13,13,15,14, 9,10,10,12,12, 9,11, + 10,13,12,10,10,11,12,13,12,13,12,15,14,12,13,13, + 14,14,11,12,12,14,13,11,11,12,13,14,12,13,13,15, + 14,13,12,14,12,16,14,15,14,16,16,11,12,12,14,14, + 11,12,11,15,13,12,13,13,14,15,13,14,12,16,13,14, + 14,15,16,16, 8, 9, 9,12,12, 9,10,10,12,12, 9,10, + 10,12,13,11,12,12,14,13,12,12,13,14,14, 9,10,10, + 12,12,10,11,11,13,12,10,10,11,12,13,12,13,13,14, + 14,12,12,13,14,15, 9,10,10,12,13,10,11,11,12,13, + 10,11,10,13,13,12,13,13,14,15,12,13,13,15,14,11, + 12,12,14,14,12,13,13,15,14,11,11,12,14,15,14,14, + 14,17,16,13,12,14,13,16,11,12,12,13,15,12,13,13, + 14,15,11,12,11,14,14,14,15,14,15,17,13,14,12,16, + 12, +}; + +static float _vq_quantthresh__44u4__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u4__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p4_0 = { + _vq_quantthresh__44u4__p4_0, + _vq_quantmap__44u4__p4_0, + 5, + 5 +}; + +static static_codebook _44u4__p4_0 = { + 4, 625, + _vq_lengthlist__44u4__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u4__p4_0, + NULL, + &_vq_auxt__44u4__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u4__p5_0[] = { + 2, 4, 4, 6, 6, 7, 7, 9, 9, 4, 5, 4, 7, 6, 8, 7, + 10, 9, 4, 4, 5, 6, 7, 7, 8, 9, 9, 6, 7, 6, 8, 8, + 8, 8,10,10, 6, 6, 7, 8, 8, 8, 9,10,10, 7, 8, 7, + 9, 8,10,10,11,11, 7, 7, 8, 8, 9,10,10,11,11, 9, + 10, 9,10,10,11,11,12,12, 9, 9,10,10,10,11,11,12, + 12, +}; + +static float _vq_quantthresh__44u4__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u4__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p5_0 = { + _vq_quantthresh__44u4__p5_0, + _vq_quantmap__44u4__p5_0, + 9, + 9 +}; + +static static_codebook _44u4__p5_0 = { + 2, 81, + _vq_lengthlist__44u4__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u4__p5_0, + NULL, + &_vq_auxt__44u4__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u4__p6_0[] = { + 1, 4, 4, 6, 6, 8, 8, 9,10,10,11,13,13, 4, 6, 5, + 8, 8, 9, 9,10,10,11,11,14,14, 4, 5, 6, 8, 8, 9, + 9,10,10,11,11,14,14, 7, 8, 8, 9, 9,10,10,11,11, + 12,12,15,15, 7, 8, 8, 9, 9,10,10,11,11,12,12,15, + 15, 8, 9, 9,10,10,11,11,12,12,13,13,16,16, 8, 9, + 9,10,10,11,11,12,12,13,13,16,16,10,10,10,11,11, + 12,12,13,13,14,13,16,16,10,10,11,11,12,12,12,13, + 13,13,14,16,17,11,12,11,12,12,13,13,13,14,14,14, + 17,16,11,11,12,12,12,13,13,14,14,15,14,17,17,14, + 14,14,15,15,16,16,17,17,17,19,19, 0,14,15,15,15, + 15,16,16,16,17,17,19,20,20, +}; + +static float _vq_quantthresh__44u4__p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44u4__p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p6_0 = { + _vq_quantthresh__44u4__p6_0, + _vq_quantmap__44u4__p6_0, + 13, + 13 +}; + +static static_codebook _44u4__p6_0 = { + 2, 169, + _vq_lengthlist__44u4__p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44u4__p6_0, + NULL, + &_vq_auxt__44u4__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u4__p6_1[] = { + 2, 4, 4, 5, 5, 4, 5, 5, 6, 5, 4, 5, 5, 5, 6, 5, + 6, 5, 6, 6, 5, 5, 6, 6, 6, +}; + +static float _vq_quantthresh__44u4__p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u4__p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p6_1 = { + _vq_quantthresh__44u4__p6_1, + _vq_quantmap__44u4__p6_1, + 5, + 5 +}; + +static static_codebook _44u4__p6_1 = { + 2, 25, + _vq_lengthlist__44u4__p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u4__p6_1, + NULL, + &_vq_auxt__44u4__p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u4__p7_0[] = { + 1, 3, 3,11,11,11,11,11,11,11,11,11,11, 4,10, 9, + 11,11,11,11,11,11,11,11,11,11, 4,10,10,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__44u4__p7_0[] = { + -1402.5, -1147.5, -892.5, -637.5, -382.5, -127.5, 127.5, 382.5, + 637.5, 892.5, 1147.5, 1402.5, +}; + +static long _vq_quantmap__44u4__p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p7_0 = { + _vq_quantthresh__44u4__p7_0, + _vq_quantmap__44u4__p7_0, + 13, + 13 +}; + +static static_codebook _44u4__p7_0 = { + 2, 169, + _vq_lengthlist__44u4__p7_0, + 1, -514332672, 1627381760, 4, 0, + _vq_quantlist__44u4__p7_0, + NULL, + &_vq_auxt__44u4__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p7_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u4__p7_1[] = { + 1, 4, 4, 6, 6, 7, 7, 9, 8,10,10,11,11,12,12, 4, + 7, 6, 8, 7, 9, 9,10,10,11,10,12,11,12,12, 4, 6, + 6, 7, 8, 9, 9,10,10,11,11,12,12,13,13, 6, 8, 8, + 10, 9,11,10,11,11,12,12,14,13,13,13, 6, 8, 8, 9, + 9,11,11,12,12,12,12,13,14,13,14, 8, 9, 9,11,10, + 12,12,13,12,13,14,14,14,14,14, 8, 9, 9,10,11,12, + 11,12,13,13,14,14,13,14,14, 9,10,10,12,12,14,13, + 14,14,15,15,16,15,15,15, 9,10,10,11,12,13,13,13, + 14,15,15,18,16,17,15,10,12,12,13,13,15,14,15,14, + 16,16,16,15,16,15,10,11,11,13,13,14,14,17,16,15, + 16,18,16,15,16,11,12,13,14,13,15,14,15,16,17,15, + 16,16,15,16,11,12,13,14,15,14,16,15,15,16,15,15, + 17,17,17,12,13,13,14,14,14,15,16,14,15,15,16,16, + 16,16,12,13,13,13,14,15,15,15,14,16,16,16,16,16, + 17, +}; + +static float _vq_quantthresh__44u4__p7_1[] = { + -110.5, -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, + 25.5, 42.5, 59.5, 76.5, 93.5, 110.5, +}; + +static long _vq_quantmap__44u4__p7_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p7_1 = { + _vq_quantthresh__44u4__p7_1, + _vq_quantmap__44u4__p7_1, + 15, + 15 +}; + +static static_codebook _44u4__p7_1 = { + 2, 225, + _vq_lengthlist__44u4__p7_1, + 1, -522338304, 1620115456, 4, 0, + _vq_quantlist__44u4__p7_1, + NULL, + &_vq_auxt__44u4__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u4__p7_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44u4__p7_2[] = { + 3, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 5, 6, 6, 7, 7, 8, 7, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9,10,10, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9,10,10, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9,10,10,10,10,10,10, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9,10,10,10,10,10,10, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10,10,10,10, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9,10,10,10,10,10,10, 9, 9, 9, 9, 9, + 9, 9,10, 9, 9,10,10,10,10,10,10,10, 9, 9, 9, 9, + 9, 9,10, 9,10,10,10,10,10,10,10,10,10, 9, 9, 9, + 9, 9,10,10,10,10,10,10,10,10,10,10,10,10, 9, 9, + 9, 9, 9,10, 9,10,10,10,10,10,10,10,10,10,10, 9, + 9, 9,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 9, 9, 9,10, 9,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__44u4__p7_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44u4__p7_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44u4__p7_2 = { + _vq_quantthresh__44u4__p7_2, + _vq_quantmap__44u4__p7_2, + 17, + 17 +}; + +static static_codebook _44u4__p7_2 = { + 2, 289, + _vq_lengthlist__44u4__p7_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44u4__p7_2, + NULL, + &_vq_auxt__44u4__p7_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u4__short[] = { + 3, 9,14,12,17,13,13,13, 4, 7, 8, 9,12,10,11,12, + 6, 5, 4, 7, 6, 7, 9,14, 8, 8, 7, 9, 8, 9,10,13, + 9, 8, 5, 7, 3, 5, 8,15, 9, 9, 6, 8, 4, 5, 7,16, + 11,10, 6, 9, 4, 3, 6,15,17,16, 8,11, 5, 5, 7,16, +}; + +static static_codebook _huff_book__44u4__short = { + 2, 64, + _huff_lengthlist__44u4__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u5__long[] = { + 3, 8,13,12,14,13,16,11,12,13, 5, 4, 5, 6, 8, 9, + 10, 9,12,12,10, 5, 4, 6, 6, 8, 9,10,14,16,10, 6, + 6, 6, 6, 7, 9, 8,12,13,13, 7, 5, 6, 4, 6, 6, 7, + 11,16,10, 7, 7, 7, 6, 6, 7, 7,11,14,14, 9, 8, 8, + 5, 6, 6, 7,11,16, 9, 8, 8, 8, 6, 6, 6, 4, 7,12, + 11,10,12,11,10, 9,10, 5, 6,10,10,13,15,15,15,15, + 14, 8, 7, 9, +}; + +static static_codebook _huff_book__44u5__long = { + 2, 100, + _huff_lengthlist__44u5__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u5__p1_0[] = { + 1, 4, 4, 5, 8, 7, 5, 7, 7, 5, 8, 8, 8,10,10, 7, + 9,10, 5, 8, 8, 7,10, 9, 8,10,10, 4, 8, 8, 8,11, + 10, 8,10,10, 8,11,11,10,12,13,10,13,13, 7,10,10, + 9,13,11,10,13,13, 5, 8, 8, 8,10,10, 8,10,10, 7, + 10,10,10,13,13,10,11,13, 8,10,11,10,13,13,10,13, + 12, +}; + +static float _vq_quantthresh__44u5__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u5__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p1_0 = { + _vq_quantthresh__44u5__p1_0, + _vq_quantmap__44u5__p1_0, + 3, + 3 +}; + +static static_codebook _44u5__p1_0 = { + 4, 81, + _vq_lengthlist__44u5__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u5__p1_0, + NULL, + &_vq_auxt__44u5__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u5__p2_0[] = { + 3, 4, 4, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 8, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 6, 8, 8, 5, 6, 6, 6, 8, + 7, 6, 8, 8, 6, 8, 8, 8, 9, 9, 8, 9, 9, 6, 7, 8, + 7, 9, 8, 8, 9, 9, 5, 6, 6, 6, 8, 7, 6, 8, 8, 6, + 8, 8, 8, 9, 9, 7, 8, 9, 6, 8, 8, 8, 9, 9, 8, 9, + 9, +}; + +static float _vq_quantthresh__44u5__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u5__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p2_0 = { + _vq_quantthresh__44u5__p2_0, + _vq_quantmap__44u5__p2_0, + 3, + 3 +}; + +static static_codebook _44u5__p2_0 = { + 4, 81, + _vq_lengthlist__44u5__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u5__p2_0, + NULL, + &_vq_auxt__44u5__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u5__p3_0[] = { + 2, 4, 5, 8, 8, 5, 7, 6, 9, 9, 5, 6, 7, 9, 9, 8, + 10, 9,12,12, 8, 9,10,12,13, 5, 7, 7,10, 9, 7, 9, + 9,11,11, 7, 8, 9,10,11,10,11,11,13,14, 9,10,11, + 13,13, 5, 7, 7, 9, 9, 7, 9, 8,11,10, 7, 9, 9,11, + 11, 9,11,10,14,13,10,11,11,13,14, 8,10,10,13,13, + 10,11,11,15,14, 9,11,11,14,14,13,15,14,18,16,12, + 13,14,16,16, 8,10,10,13,13, 9,11,11,14,14,10,11, + 12,14,15,12,13,13,16,16,13,14,14,15,17, 5, 7, 7, + 10,10, 7, 9, 9,11,11, 7, 9, 9,11,11,10,11,11,15, + 14,10,11,11,14,14, 7, 9, 9,12,11, 9,11,11,13,13, + 9,11,11,13,13,12,12,13,15,15,11,12,13,16,15, 6, + 9, 9,11,11, 8,11,10,13,12, 9,10,11,12,14,11,13, + 11,16,14,11,13,13,15,16,10,11,11,15,15,11,12,13, + 16,15,11,13,13,16,16,14,15,15,17,18,14,15,16,17, + 18, 9,11,11,14,15,10,12,11,15,15,11,12,13,15,16, + 13,15,13,17,15,14,15,16,18,19, 5, 7, 7,10,10, 7, + 9, 9,11,11, 7, 9, 9,11,11,10,11,11,15,14,10,11, + 11,14,15, 6, 9, 9,11,11, 9,11,10,13,13, 8,10,11, + 12,13,11,13,13,15,15,11,11,13,13,15, 7, 9, 9,11, + 12, 9,11,11,13,13, 9,11,11,13,13,11,13,12,17,16, + 11,13,12,16,15, 9,11,11,15,14,11,13,13,16,16,10, + 11,12,15,15,14,15,15,17,17,13,13,15,15,17,10,11, + 12,15,15,11,13,13,16,19,11,13,12,17,17,14,16,15, + 19,17,14,15,15,18,18, 8,10,10,14,14,10,12,11,16, + 15, 9,11,11,15,16,14,16,15,17,17,13,14,14,18,17, + 9,11,11,16,15,11,13,13,16,18,11,13,13,17,16,15, + 16,17,18, 0,15,15,16, 0,18, 9,11,11,16,15,10,13, + 12,17,15,11,13,13,16,17,14,18,15,19,18,15,16,16, + 19,18,13,15,15,19,17,13,15,15,18,18,14,15,15,19, + 0,17,18, 0,19,19,16,17,17, 0,18,12,14,13,18,17, + 13,15,13,19,18,14,15,15,19,19,16,17,15, 0,17,16, + 19,17,19,18, 8,10,10,14,14,10,11,11,15,15,10,11, + 11,16,16,13,15,15,17,16,14,15,15,18,19, 9,11,11, + 16,15,11,13,13,19,16,10,12,13,15,17,15,15,15,19, + 19,15,16,16,17, 0, 9,11,11,16,16,11,13,13,16,17, + 11,13,13,18,18,14,16,15,18,18,15,17,16,19, 0,12, + 14,14,17,18,13,16,16,18,18,13,14,15,17, 0,16,17, + 17, 0, 0,16,15, 0,19,17,12,15,14,17,18,14,15,16, + 0,18,14,16,16,18, 0,16,17,17, 0, 0,17,19,17,19, + 0, +}; + +static float _vq_quantthresh__44u5__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u5__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p3_0 = { + _vq_quantthresh__44u5__p3_0, + _vq_quantmap__44u5__p3_0, + 5, + 5 +}; + +static static_codebook _44u5__p3_0 = { + 4, 625, + _vq_lengthlist__44u5__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u5__p3_0, + NULL, + &_vq_auxt__44u5__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u5__p4_0[] = { + 4, 6, 6, 8, 8, 6, 7, 6, 9, 9, 6, 6, 7, 9, 9, 8, + 9, 9,11,11, 8, 9, 9,11,11, 6, 7, 7, 9, 9, 7, 8, + 8,10,10, 6, 7, 8, 9,10, 9,10,10,11,11, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 6, 8, 7,10, 9, 7, 8, 8,10, + 10, 9,10, 9,12,11, 9,10,10,12,11, 8, 9, 9,12,11, + 9,10,10,12,12, 9,10,10,12,12,11,12,12,13,13,11, + 11,12,12,13, 8, 9, 9,11,11, 9,10,10,12,12, 9,10, + 10,12,12,11,12,11,13,13,11,12,12,14,13, 6, 7, 7, + 9, 9, 7, 8, 7,10,10, 7, 7, 8, 9,10, 9,10,10,12, + 11, 9,10,10,11,11, 7, 8, 8,10,10, 8, 8, 9,10,11, + 8, 8, 8,11,10,10,10,11,11,12,10,10,11,12,12, 6, + 7, 7,10,10, 7, 8, 8,11,10, 8, 8, 9,10,11,10,11, + 10,13,11,10,10,11,12,12, 9,10,10,12,12,10,10,10, + 12,13,10,11,11,13,13,12,11,12,11,14,12,12,13,13, + 14, 9,10,10,12,12, 9,10,10,12,12,10,10,11,12,13, + 11,12,11,14,12,12,12,12,14,14, 6, 7, 7, 9, 9, 7, + 8, 7,10,10, 7, 7, 8,10,10, 9,10,10,11,11, 9,10, + 10,12,12, 6, 7, 7,10,10, 8, 9, 8,11,10, 7, 8, 8, + 10,11,10,10,11,12,12,10,10,11,11,13, 7, 8, 8,10, + 10, 8, 8, 8,10,11, 8, 9, 8,11,10,10,11,10,12,12, + 10,11,10,12,11, 9,10,10,12,12,10,11,11,13,12, 9, + 10,10,12,12,12,12,12,14,14,11,11,12,12,14, 9,10, + 10,12,12,10,11,11,12,12,10,10,10,12,12,12,13,12, + 14,14,11,12,11,14,11, 8, 9, 9,11,11, 9,10,10,12, + 12, 9,10,10,12,12,11,12,12,14,14,11,12,12,13,13, + 9,10,10,12,12,10,10,11,12,13,10,11,10,13,12,12, + 12,13,14,14,12,12,12,14,13, 9,10,10,12,12, 9,10, + 10,13,12,10,10,11,12,13,12,13,12,14,13,12,12,13, + 13,14,11,12,11,14,13,11,11,12,13,14,12,13,12,14, + 14,13,12,14,11,16,13,14,14,16,15,11,11,11,13,13, + 11,12,11,14,13,12,12,13,14,15,12,14,12,16,12,14, + 14,14,16,16, 8, 9, 9,11,11, 9,10,10,12,12, 9,10, + 10,12,12,11,12,12,13,13,12,12,12,14,14, 9,10,10, + 12,12,10,11,10,13,12, 9,10,10,12,13,12,12,12,14, + 13,12,12,13,13,15, 9,10,10,12,12,10,10,11,12,13, + 10,11,10,13,12,12,13,12,14,14,12,13,12,14,13,11, + 11,11,13,13,12,13,12,14,14,11,11,12,13,14,13,14, + 14,16,15,13,12,14,12,15,11,12,12,13,14,12,12,13, + 14,14,11,12,11,14,13,13,14,14,15,16,13,14,11,16, + 11, +}; + +static float _vq_quantthresh__44u5__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u5__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p4_0 = { + _vq_quantthresh__44u5__p4_0, + _vq_quantmap__44u5__p4_0, + 5, + 5 +}; + +static static_codebook _44u5__p4_0 = { + 4, 625, + _vq_lengthlist__44u5__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u5__p4_0, + NULL, + &_vq_auxt__44u5__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u5__p5_0[] = { + 2, 4, 3, 6, 6, 8, 7,10,10, 3, 5, 5, 8, 7, 8, 8, + 11,11, 3, 5, 5, 7, 8, 8, 8,10,11, 6, 8, 7, 9, 9, + 10, 9,12,11, 7, 7, 8, 9, 9, 9,10,11,12, 8, 8, 8, + 10, 9,11,11,13,12, 8, 8, 8, 9,10,11,12,12,13,10, + 11,10,12,11,13,12,14,14,10,10,11,11,12,12,13,14, + 14, +}; + +static float _vq_quantthresh__44u5__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u5__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p5_0 = { + _vq_quantthresh__44u5__p5_0, + _vq_quantmap__44u5__p5_0, + 9, + 9 +}; + +static static_codebook _44u5__p5_0 = { + 2, 81, + _vq_lengthlist__44u5__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u5__p5_0, + NULL, + &_vq_auxt__44u5__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p6_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u5__p6_0[] = { + 3, 4, 4, 5, 6, 7, 7, 9, 9, 4, 5, 4, 6, 6, 7, 7, + 9, 9, 4, 4, 4, 6, 6, 7, 7, 9, 9, 6, 6, 6, 7, 7, + 8, 8,10,10, 6, 6, 6, 7, 7, 8, 8,10,10, 7, 7, 7, + 8, 8, 9, 9,10,10, 7, 7, 7, 8, 8, 9, 9,10,11, 9, + 9, 9,10,10,11,10,11,11, 9, 9, 9,10,10,11,11,11, + 11, +}; + +static float _vq_quantthresh__44u5__p6_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u5__p6_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p6_0 = { + _vq_quantthresh__44u5__p6_0, + _vq_quantmap__44u5__p6_0, + 9, + 9 +}; + +static static_codebook _44u5__p6_0 = { + 2, 81, + _vq_lengthlist__44u5__p6_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u5__p6_0, + NULL, + &_vq_auxt__44u5__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u5__p7_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 9, 8, 8,11,10, 7, + 10,10, 5, 9, 9, 8,10,10, 8,10,11, 4, 9, 9, 9,11, + 11, 9,12,11, 8,11,11,11,12,12,10,12,12, 7,11,11, + 10,12,12,10,12,12, 4, 9, 9, 9,11,11, 9,11,11, 7, + 11,11,10,12,12,10,11,12, 8,11,11,10,12,12,11,12, + 12, +}; + +static float _vq_quantthresh__44u5__p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44u5__p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p7_0 = { + _vq_quantthresh__44u5__p7_0, + _vq_quantmap__44u5__p7_0, + 3, + 3 +}; + +static static_codebook _44u5__p7_0 = { + 4, 81, + _vq_lengthlist__44u5__p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44u5__p7_0, + NULL, + &_vq_auxt__44u5__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u5__p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 4, 5, 5, 7, 7, + 8, 8, 9, 8, 9, 8, 4, 5, 5, 7, 7, 8, 8, 8, 9, 9, + 9, 6, 7, 7, 8, 8, 9, 8, 9, 9, 9, 9, 6, 7, 7, 8, + 8, 8, 9, 9, 9, 9, 9, 8, 8, 8, 9, 8, 9, 9, 9, 9, + 9, 9, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 8, 9, 8, + 9, 9, 9, 9,10,10,10,10, 8, 9, 9, 9, 9, 9, 9,10, + 10,10,10, 8, 9, 9, 9, 9, 9, 9,10,10,10,10, 8, 9, + 9, 9, 9, 9, 9,10,10,10,10, +}; + +static float _vq_quantthresh__44u5__p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u5__p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p7_1 = { + _vq_quantthresh__44u5__p7_1, + _vq_quantmap__44u5__p7_1, + 11, + 11 +}; + +static static_codebook _44u5__p7_1 = { + 2, 121, + _vq_lengthlist__44u5__p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u5__p7_1, + NULL, + &_vq_auxt__44u5__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p8_0[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u5__p8_0[] = { + 1, 4, 4, 6, 6, 8, 8, 9, 9,10,10, 4, 6, 6, 7, 7, + 9, 9,10,10,11,11, 4, 6, 6, 7, 7, 9, 9,10,10,11, + 11, 6, 8, 8, 9, 9,10,10,12,11,13,12, 6, 7, 8, 9, + 9,10,10,11,11,12,12, 8, 9, 9,10,10,11,12,12,12, + 14,13, 8, 9, 9,10,10,11,11,12,13,14,13,10,11,10, + 12,12,13,13,14,13,15,14,10,11,11,12,12,12,13,13, + 13,15,15,11,12,12,13,12,13,14,14,14,15,14,11,11, + 12,13,13,13,14,14,14,15,15, +}; + +static float _vq_quantthresh__44u5__p8_0[] = { + -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, 27.5, + 38.5, 49.5, +}; + +static long _vq_quantmap__44u5__p8_0[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p8_0 = { + _vq_quantthresh__44u5__p8_0, + _vq_quantmap__44u5__p8_0, + 11, + 11 +}; + +static static_codebook _44u5__p8_0 = { + 2, 121, + _vq_lengthlist__44u5__p8_0, + 1, -524582912, 1618345984, 4, 0, + _vq_quantlist__44u5__p8_0, + NULL, + &_vq_auxt__44u5__p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p8_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u5__p8_1[] = { + 3, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 5, 6, 5, 6, 6, + 7, 7, 8, 8, 8, 8, 5, 6, 6, 6, 6, 7, 7, 8, 8, 8, + 8, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 6, 6, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44u5__p8_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u5__p8_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p8_1 = { + _vq_quantthresh__44u5__p8_1, + _vq_quantmap__44u5__p8_1, + 11, + 11 +}; + +static static_codebook _44u5__p8_1 = { + 2, 121, + _vq_lengthlist__44u5__p8_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u5__p8_1, + NULL, + &_vq_auxt__44u5__p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p9_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u5__p9_0[] = { + 1, 3, 3,12,10,12,12,12,12,12,12,12,12, 3, 8, 9, + 12,12,12,12,12,12,12,12,12,12, 4, 9, 9,12,12,12, + 12,12,12,12,12,12,12,11,12,12,12,12,12,12,12,12, + 12,12,12,12, 9,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11, +}; + +static float _vq_quantthresh__44u5__p9_0[] = { + -1402.5, -1147.5, -892.5, -637.5, -382.5, -127.5, 127.5, 382.5, + 637.5, 892.5, 1147.5, 1402.5, +}; + +static long _vq_quantmap__44u5__p9_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p9_0 = { + _vq_quantthresh__44u5__p9_0, + _vq_quantmap__44u5__p9_0, + 13, + 13 +}; + +static static_codebook _44u5__p9_0 = { + 2, 169, + _vq_lengthlist__44u5__p9_0, + 1, -514332672, 1627381760, 4, 0, + _vq_quantlist__44u5__p9_0, + NULL, + &_vq_auxt__44u5__p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u5__p9_1[] = { + 1, 4, 4, 7, 7, 8, 9, 7, 7, 8, 8, 9, 9,10,10, 5, + 6, 6, 9, 8,10,10, 9, 8, 9, 9,10, 9,11,10, 4, 6, + 6, 8, 9, 9,10, 8, 9, 9,10,10,10,11,10, 7, 8, 8, + 10,10,11,11,10,10,11,11,11,11,11,11, 7, 8, 8,10, + 10,11,11,10,10,11,11,11,11,11,12, 8, 9,10,11,10, + 12,12,11,11,11,11,12,12,12,13, 8, 9, 9,11,11,11, + 12,11,11,11,11,12,12,12,12, 8, 9, 9,10,10,11,11, + 12,11,12,12,12,13,13,13, 8, 9, 9,10,10,11,11,12, + 11,12,12,13,13,12,13, 9,10,10,11,11,12,11,12,13, + 14,14,15,15,15,14, 9,10,10,11,11,12,12,12,12,13, + 13,14,14,14,14, 9,10,11,12,11,12,13,14,13,14,13, + 14,14,14,14, 9,11,11,11,11,12,13,13,13,14,13,15, + 15,14,14,10,11,11,12,12,13,13,13,13,14,14,14,15, + 15,15,10,11,11,12,12,12,13,13,13,15,14,15,14,14, + 14, +}; + +static float _vq_quantthresh__44u5__p9_1[] = { + -110.5, -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, + 25.5, 42.5, 59.5, 76.5, 93.5, 110.5, +}; + +static long _vq_quantmap__44u5__p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p9_1 = { + _vq_quantthresh__44u5__p9_1, + _vq_quantmap__44u5__p9_1, + 15, + 15 +}; + +static static_codebook _44u5__p9_1 = { + 2, 225, + _vq_lengthlist__44u5__p9_1, + 1, -522338304, 1620115456, 4, 0, + _vq_quantlist__44u5__p9_1, + NULL, + &_vq_auxt__44u5__p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u5__p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44u5__p9_2[] = { + 3, 5, 5, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 7, 8, 8, 8, 8, 9, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10, 9, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10, 9, 9,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10, 9, 9, 9, 9, 9,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10,10,10,10, 9,10,10,10,10, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, 9, 9, 9, 9,10, + 9, 9, 9, 9, 9, 9, 9,10, 9,10, 9, 9, 9,10,10, 9, + 9, +}; + +static float _vq_quantthresh__44u5__p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44u5__p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44u5__p9_2 = { + _vq_quantthresh__44u5__p9_2, + _vq_quantmap__44u5__p9_2, + 17, + 17 +}; + +static static_codebook _44u5__p9_2 = { + 2, 289, + _vq_lengthlist__44u5__p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44u5__p9_2, + NULL, + &_vq_auxt__44u5__p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u5__short[] = { + 4,11,16,14,18,15,18,15,17,17, 4, 6, 9, 9,12, 9, + 14,10,14,17, 6, 6, 5, 7, 6, 8,10,10,16,17, 7, 8, + 7, 9, 9,10,12,12,14,17, 8, 6, 5, 7, 4, 7, 5, 7, + 14,17, 9, 9, 8, 9, 7,10, 8,10,14,17,11, 9, 7, 9, + 4, 7, 3, 6,15,17,11,10,10,10, 6, 9, 5, 6,14,17, + 17,17,10,15, 4, 8, 3, 5,12,17,17,17,12,16, 7,11, + 6, 8,16,17, +}; + +static static_codebook _huff_book__44u5__short = { + 2, 100, + _huff_lengthlist__44u5__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u6__long[] = { + 3, 9,14,13,15,13,16,12,12,12, 5, 4, 6, 7, 8, 9, + 10,10,13,12,10, 5, 5, 6, 6, 9, 9,10,14,14,10, 6, + 6, 7, 6, 8, 9, 9,13,12,13, 7, 5, 6, 4, 6, 6, 7, + 11,15,10, 8, 7, 7, 6, 7, 7, 7,11,13,16,10, 8, 8, + 5, 6, 4, 6,10,14, 9, 9, 9, 8, 6, 6, 5, 4, 7,11, + 11,11,12,11,10, 9, 9, 5, 5, 9,10,13,14,14,14,14, + 15, 8, 7, 8, +}; + +static static_codebook _huff_book__44u6__long = { + 2, 100, + _huff_lengthlist__44u6__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u6__p1_0[] = { + 1, 4, 4, 4, 8, 7, 5, 7, 7, 5, 8, 8, 8,10,10, 7, + 9,10, 5, 8, 8, 7,10, 9, 8,10,10, 5, 8, 8, 8,11, + 10, 8,10,10, 8,11,10,10,12,13,10,13,13, 7,10,10, + 10,13,11,10,13,13, 5, 8, 8, 8,11,10, 8,10,10, 7, + 10,10,10,13,13, 9,11,13, 8,10,11,10,13,13,10,13, + 12, +}; + +static float _vq_quantthresh__44u6__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u6__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p1_0 = { + _vq_quantthresh__44u6__p1_0, + _vq_quantmap__44u6__p1_0, + 3, + 3 +}; + +static static_codebook _44u6__p1_0 = { + 4, 81, + _vq_lengthlist__44u6__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u6__p1_0, + NULL, + &_vq_auxt__44u6__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u6__p2_0[] = { + 3, 5, 4, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 7, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 6, 8, 7, 5, 6, 6, 6, 8, + 7, 6, 7, 8, 6, 8, 8, 8, 8, 9, 8, 9, 9, 6, 7, 7, + 7, 9, 8, 8, 9, 9, 5, 6, 6, 6, 8, 7, 6, 7, 8, 6, + 7, 7, 8, 9, 9, 7, 8, 9, 6, 8, 8, 8, 9, 9, 8, 9, + 8, +}; + +static float _vq_quantthresh__44u6__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u6__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p2_0 = { + _vq_quantthresh__44u6__p2_0, + _vq_quantmap__44u6__p2_0, + 3, + 3 +}; + +static static_codebook _44u6__p2_0 = { + 4, 81, + _vq_lengthlist__44u6__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u6__p2_0, + NULL, + &_vq_auxt__44u6__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u6__p3_0[] = { + 2, 4, 5, 8, 8, 5, 7, 6, 9, 9, 5, 6, 7, 9, 9, 8, + 10, 9,13,12, 8, 9,10,12,13, 5, 7, 7,10, 9, 7, 9, + 9,11,11, 7, 8, 9,10,11,10,11,11,13,14, 9,10,11, + 13,13, 5, 7, 7, 9, 9, 7, 9, 8,11,10, 7, 9, 9,11, + 11, 9,11,10,14,13,10,11,11,14,13, 8,10,10,14,13, + 10,12,11,15,14, 9,11,11,15,14,13,15,14,20,16,13, + 13,14,16,16, 8,10,10,13,14, 9,11,11,14,15,10,11, + 12,14,15,13,13,14,16,16,13,14,14,16,17, 5, 7, 7, + 10,10, 7, 9, 9,11,11, 7, 9, 9,11,11,10,11,11,15, + 14,10,11,11,14,14, 7, 9, 9,12,11, 9,11,11,12,13, + 9,11,11,13,13,11,12,13,14,16,11,12,13,16,16, 6, + 9, 8,11,11, 8,11,10,13,12, 9,10,11,12,13,11,13, + 11,15,14,11,13,13,15,15,10,12,11,15,14,11,12,13, + 17,16,11,13,13,16,17,14,15,16,17,17,14,15,16,20, + 0, 9,11,11,14,15,10,13,12,16,16,11,13,13,17,17, + 13,15,14,17,15,15,16,16, 0, 0, 5, 7, 7,10,10, 7, + 9, 9,11,11, 7, 8, 9,11,11,10,11,11,14,14,10,11, + 11,14,14, 6, 9, 9,11,11, 9,11,11,14,12, 8,10,11, + 12,13,11,13,13,16,16,11,11,13,14,16, 7, 9, 9,11, + 12, 9,11,11,13,13, 9,11,11,13,12,11,13,12,16,16, + 12,13,12,16,14, 9,11,11,16,15,11,13,13,17,15,10, + 12,12,15,16,14,16,16, 0,17,13,14,15,15,17,10,11, + 12,15,15,11,13,13,17,17,11,13,13,15,16,14,15,15, + 19,18,14,15,15,19,16, 8,10,10,14,14,10,12,11,15, + 16,10,11,11,15,17,13,16,15,19,19,13,15,14,17,17, + 9,11,12,15,16,11,13,13,18,19,11,13,13,16,17,16, + 16,17, 0, 0,15,15,16, 0,19, 9,11,11,15,16,10,13, + 12,17,15,11,13,13,16,17,14,17,15,18,17,15,16,15, + 18, 0,13,14,15, 0,19,14,16,16, 0,17,15,16,16, 0, + 17,16,17, 0, 0, 0,16,17,19, 0, 0,12,14,14,17,17, + 13,15,13, 0,19,14,16,16,18,19,16,18,15,19,18,17, + 18,17,19, 0, 8,10,10,14,13,10,11,11,16,15,10,11, + 11,15,15,13,15,15,19,18,14,15,16, 0,18, 9,11,11, + 15,14,11,13,13, 0,16,10,12,13,15,17,14,16,16, 0, + 18,15,16,17,17, 0, 9,11,11,16,15,11,13,13,16,16, + 11,14,13,18,18,15,17,15,18,17,15,16,18, 0, 0,12, + 14,14,18,19,14,15,16, 0,18,13,14,15,18, 0,17,17, + 17, 0, 0,16,15,18,17, 0,12,14,15,17,19,14,16,16, + 19, 0,14,17,16, 0,18,17,19,17, 0, 0,16,18,17, 0, + 17, +}; + +static float _vq_quantthresh__44u6__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u6__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p3_0 = { + _vq_quantthresh__44u6__p3_0, + _vq_quantmap__44u6__p3_0, + 5, + 5 +}; + +static static_codebook _44u6__p3_0 = { + 4, 625, + _vq_lengthlist__44u6__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u6__p3_0, + NULL, + &_vq_auxt__44u6__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u6__p4_0[] = { + 4, 6, 6, 8, 8, 6, 7, 6, 9, 9, 6, 6, 7, 9, 9, 8, + 9, 9,11,11, 8, 9, 9,11,11, 6, 7, 7, 9, 9, 7, 8, + 8,10,10, 6, 7, 8, 9,10, 9,10,10,11,12, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 7, 8, 7,10, 9, 7, 8, 8,10, + 10, 9,10, 9,12,11, 9,10,10,12,11, 8, 9, 9,11,11, + 9,10,10,12,12, 9, 9,10,12,12,11,12,12,13,13,11, + 11,12,12,13, 8, 9, 9,11,11, 9,10,10,12,12, 9,10, + 10,12,12,11,12,11,13,13,11,12,12,13,13, 6, 7, 7, + 9, 9, 7, 8, 7,10, 9, 7, 7, 8, 9,10, 9,10,10,12, + 11, 9,10,10,11,12, 7, 8, 8,10,10, 8, 8, 9,10,10, + 8, 8, 8,10,10,10,10,11,12,12,10,10,11,12,12, 6, + 7, 7,10,10, 7, 8, 8,11,10, 8, 8, 9,10,11,10,11, + 10,12,11,10,10,11,12,12, 9,10,10,12,12,10,10,11, + 12,12,10,11,10,13,12,12,12,12,12,14,12,12,12,13, + 14, 9,10,10,12,12, 9,10,10,12,12,10,10,11,12,13, + 11,12,11,14,12,12,12,13,14,14, 6, 7, 7, 9, 9, 7, + 8, 7,10, 9, 7, 7, 8,10,10, 9,10,10,12,11, 9,10, + 10,11,12, 6, 7, 7,10,10, 8, 9, 8,11,10, 7, 8, 8, + 10,10,10,10,11,12,12,10,10,10,11,12, 7, 8, 8,10, + 10, 8, 8, 8,10,10, 8, 8, 8,10,10,10,11,10,12,12, + 10,11,10,12,11, 9,10,10,12,12,10,11,10,13,12, 9, + 10,10,12,12,12,12,12,14,14,11,11,12,12,13, 9,10, + 10,12,12,10,11,11,12,12,10,11,10,12,12,12,12,12, + 14,14,12,12,12,14,12, 8, 9, 9,11,11, 9,10,10,12, + 12, 9,10,10,12,12,11,12,12,13,13,11,12,12,13,13, + 9,10,10,12,12,10,10,11,12,13,10,11,10,12,12,12, + 12,13,13,14,12,12,12,14,14, 9,10,10,12,12, 9,10, + 10,12,12,10,10,10,12,12,12,12,12,14,13,12,12,12, + 14,14,11,12,12,13,13,11,12,12,14,14,12,12,12,14, + 14,13,12,14,12,15,13,14,14,15,15,11,11,11,13,13, + 11,12,11,14,13,12,12,12,14,14,12,14,12,15,12,13, + 14,14,15,16, 8, 9, 9,11,11, 9,10,10,12,12, 9,10, + 10,12,12,11,12,12,13,13,11,12,12,13,14, 9,10,10, + 12,12,10,11,10,13,12, 9,10,10,12,13,12,12,12,15, + 14,12,12,13,13,14, 9,10,10,12,12,10,10,11,12,12, + 10,11,10,12,12,12,13,12,14,14,12,13,12,14,13,11, + 11,11,13,13,12,12,12,14,13,11,11,12,13,14,13,14, + 14,15,15,12,12,14,12,15,11,12,12,13,14,12,12,12, + 14,14,11,12,12,14,14,13,14,14,15,15,13,14,12,16, + 12, +}; + +static float _vq_quantthresh__44u6__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u6__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p4_0 = { + _vq_quantthresh__44u6__p4_0, + _vq_quantmap__44u6__p4_0, + 5, + 5 +}; + +static static_codebook _44u6__p4_0 = { + 4, 625, + _vq_lengthlist__44u6__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u6__p4_0, + NULL, + &_vq_auxt__44u6__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u6__p5_0[] = { + 2, 3, 3, 6, 6, 8, 8,10,10, 4, 5, 5, 8, 7, 8, 8, + 11,11, 3, 5, 5, 7, 8, 8, 8,11,11, 6, 8, 7,10, 9, + 10, 9,12,12, 6, 7, 8, 9,10, 9,10,12,12, 8, 8, 8, + 10, 9,12,11,13,13, 8, 8, 8, 9,10,11,12,12,13,10, + 11,11,12,11,13,12,14,14,10,10,11,11,12,13,13,14, + 14, +}; + +static float _vq_quantthresh__44u6__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u6__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p5_0 = { + _vq_quantthresh__44u6__p5_0, + _vq_quantmap__44u6__p5_0, + 9, + 9 +}; + +static static_codebook _44u6__p5_0 = { + 2, 81, + _vq_lengthlist__44u6__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u6__p5_0, + NULL, + &_vq_auxt__44u6__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p6_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u6__p6_0[] = { + 3, 4, 4, 5, 5, 7, 7, 9, 9, 4, 5, 4, 6, 6, 7, 7, + 9, 9, 4, 4, 5, 6, 6, 7, 7, 9, 9, 5, 6, 6, 7, 7, + 8, 8,10,10, 6, 6, 6, 7, 7, 8, 8,10,10, 7, 7, 7, + 8, 8, 9, 9,10,10, 7, 7, 7, 8, 8, 9, 9,10,11, 9, + 9, 9,10,10,11,11,11,11, 9, 9, 9,10,10,10,11,11, + 11, +}; + +static float _vq_quantthresh__44u6__p6_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u6__p6_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p6_0 = { + _vq_quantthresh__44u6__p6_0, + _vq_quantmap__44u6__p6_0, + 9, + 9 +}; + +static static_codebook _44u6__p6_0 = { + 2, 81, + _vq_lengthlist__44u6__p6_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u6__p6_0, + NULL, + &_vq_auxt__44u6__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u6__p7_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 9, 8, 8,11,10, 8, + 11,10, 5, 8, 9, 7,10,10, 8,10,11, 4, 9, 9, 9,12, + 11, 9,11,12, 8,11,12,11,12,13,10,13,12, 7,12,11, + 10,12,12,10,13,13, 4, 9, 9, 8,11,11, 9,11,12, 7, + 12,12,10,13,13,10,12,13, 8,12,12,10,13,13,11,13, + 12, +}; + +static float _vq_quantthresh__44u6__p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44u6__p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p7_0 = { + _vq_quantthresh__44u6__p7_0, + _vq_quantmap__44u6__p7_0, + 3, + 3 +}; + +static static_codebook _44u6__p7_0 = { + 4, 81, + _vq_lengthlist__44u6__p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44u6__p7_0, + NULL, + &_vq_auxt__44u6__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u6__p7_1[] = { + 3, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 4, 5, 5, 6, 6, + 8, 7, 8, 8, 8, 8, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, + 8, 6, 7, 6, 7, 7, 8, 8, 9, 9, 9, 9, 6, 6, 7, 7, + 7, 8, 8, 9, 9, 9, 9, 7, 8, 7, 8, 8, 9, 9, 9, 9, + 9, 9, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 8, 8, 8, 9, 9, 9, 9, 9, 9,10,10, 8, 8, + 8, 9, 9, 9, 9, 9, 9,10,10, +}; + +static float _vq_quantthresh__44u6__p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u6__p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p7_1 = { + _vq_quantthresh__44u6__p7_1, + _vq_quantmap__44u6__p7_1, + 11, + 11 +}; + +static static_codebook _44u6__p7_1 = { + 2, 121, + _vq_lengthlist__44u6__p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u6__p7_1, + NULL, + &_vq_auxt__44u6__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p8_0[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u6__p8_0[] = { + 1, 4, 4, 6, 6, 8, 8, 9, 9,10,10, 4, 6, 6, 7, 7, + 9, 9,10,10,11,11, 4, 6, 6, 7, 7, 9, 9,10,10,11, + 11, 6, 8, 8, 9, 9,10,10,11,11,12,12, 6, 8, 8, 9, + 9,10,10,11,11,12,12, 8, 9, 9,10,10,11,11,12,12, + 13,13, 8, 9, 9,10,10,11,11,12,12,13,13,10,10,10, + 11,11,12,12,13,13,14,14,10,10,10,11,11,12,13,13, + 13,14,14,11,12,12,13,13,13,13,14,14,15,14,11,11, + 11,13,13,14,13,14,14,15,14, +}; + +static float _vq_quantthresh__44u6__p8_0[] = { + -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, 27.5, + 38.5, 49.5, +}; + +static long _vq_quantmap__44u6__p8_0[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p8_0 = { + _vq_quantthresh__44u6__p8_0, + _vq_quantmap__44u6__p8_0, + 11, + 11 +}; + +static static_codebook _44u6__p8_0 = { + 2, 121, + _vq_lengthlist__44u6__p8_0, + 1, -524582912, 1618345984, 4, 0, + _vq_quantlist__44u6__p8_0, + NULL, + &_vq_auxt__44u6__p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p8_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u6__p8_1[] = { + 3, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 5, 6, 6, 7, 6, + 7, 7, 7, 7, 8, 8, 5, 6, 6, 6, 7, 7, 7, 7, 7, 8, + 8, 6, 7, 6, 7, 7, 7, 7, 8, 8, 8, 8, 6, 6, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44u6__p8_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u6__p8_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p8_1 = { + _vq_quantthresh__44u6__p8_1, + _vq_quantmap__44u6__p8_1, + 11, + 11 +}; + +static static_codebook _44u6__p8_1 = { + 2, 121, + _vq_lengthlist__44u6__p8_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u6__p8_1, + NULL, + &_vq_auxt__44u6__p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p9_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u6__p9_0[] = { + 1, 3, 3,10,12,13,13,13,13,13,13,13,13,13,13, 3, + 9, 9,13,13,13,13,13,13,13,13,13,13,12,12, 4, 9, + 9,12,12,12,12,12,12,12,12,12,12,12,12, 9,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12, 9,11,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12, +}; + +static float _vq_quantthresh__44u6__p9_0[] = { + -1657.5, -1402.5, -1147.5, -892.5, -637.5, -382.5, -127.5, 127.5, + 382.5, 637.5, 892.5, 1147.5, 1402.5, 1657.5, +}; + +static long _vq_quantmap__44u6__p9_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p9_0 = { + _vq_quantthresh__44u6__p9_0, + _vq_quantmap__44u6__p9_0, + 15, + 15 +}; + +static static_codebook _44u6__p9_0 = { + 2, 225, + _vq_lengthlist__44u6__p9_0, + 1, -514071552, 1627381760, 4, 0, + _vq_quantlist__44u6__p9_0, + NULL, + &_vq_auxt__44u6__p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u6__p9_1[] = { + 1, 4, 4, 7, 7, 8, 8, 7, 7, 8, 8, 9, 9, 9, 9, 5, + 6, 6, 8, 8,10,10, 9, 9,10, 9,10, 9,10,10, 4, 6, + 6, 8, 9,10,10, 8, 9, 9,10,10,10,10,10, 7, 9, 8, + 10,10,11,11,10,10,11,11,11,11,12,12, 7, 8, 8,10, + 10,11,11,10,10,10,11,11,11,11,12, 8,10,10,11,11, + 12,12,11,11,11,11,12,12,12,13, 8, 9, 9,11,11,12, + 12,11,11,12,12,12,12,12,12, 8, 9, 9,10,10,11,11, + 13,12,13,12,13,13,13,13, 8, 9, 9,10,10,11,11,12, + 12,12,12,13,12,13,12, 9,10,10,11,11,12,12,13,13, + 14,12,13,13,14,13, 9,10,10,11,11,12,12,12,12,13, + 13,13,13,14,14, 9,11,10,11,11,12,13,13,12,13,13, + 15,13,14,14, 9,10,10,11,12,12,13,13,13,13,13,14, + 14,14,14,10,11,11,12,12,13,12,13,13,14,14,14,14, + 14,15,10,11,11,11,12,13,13,13,13,14,14,14,14,14, + 14, +}; + +static float _vq_quantthresh__44u6__p9_1[] = { + -110.5, -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, + 25.5, 42.5, 59.5, 76.5, 93.5, 110.5, +}; + +static long _vq_quantmap__44u6__p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p9_1 = { + _vq_quantthresh__44u6__p9_1, + _vq_quantmap__44u6__p9_1, + 15, + 15 +}; + +static static_codebook _44u6__p9_1 = { + 2, 225, + _vq_lengthlist__44u6__p9_1, + 1, -522338304, 1620115456, 4, 0, + _vq_quantlist__44u6__p9_1, + NULL, + &_vq_auxt__44u6__p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u6__p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44u6__p9_2[] = { + 3, 5, 5, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9,10,10, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10, 9,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9, 9, 9,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10, 9, 9, 9,10, + 9, +}; + +static float _vq_quantthresh__44u6__p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44u6__p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44u6__p9_2 = { + _vq_quantthresh__44u6__p9_2, + _vq_quantmap__44u6__p9_2, + 17, + 17 +}; + +static static_codebook _44u6__p9_2 = { + 2, 289, + _vq_lengthlist__44u6__p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44u6__p9_2, + NULL, + &_vq_auxt__44u6__p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u6__short[] = { + 5,11,15,14,17,14,17,15,17,17, 4, 6, 9, 9,14, 9, + 17,10,15,17, 6, 6, 5, 7, 7, 9,11,10,15,17, 6, 8, + 8,10, 9,11,14,13,16,17, 7, 6, 5, 7, 5, 7, 5, 8, + 15,17, 9, 8, 8, 9, 8,10,10,11,14,17,10, 8, 7, 9, + 4, 7, 3, 5,11,17,10,10,10,10, 6, 9, 4, 5,11,17, + 16,16,13,15, 5,10, 4, 3, 9,16,17,17,14,17, 7,11, + 6, 6,11,17, +}; + +static static_codebook _huff_book__44u6__short = { + 2, 100, + _huff_lengthlist__44u6__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u7__long[] = { + 3, 9,15,13,16,13,16,12,12,12, 5, 5, 7, 8, 8,10, + 11,10,13,13,10, 5, 5, 6, 6, 9, 9,10,14,14, 9, 6, + 6, 7, 6, 8, 9,10,13,12,13, 7, 5, 6, 4, 6, 6, 8, + 12,13,11, 8, 7, 8, 6, 7, 7, 7,11,13,15,10, 8, 8, + 5, 6, 4, 5, 9,13, 9, 9, 8, 9, 7, 7, 4, 4, 6,11, + 11,11,12,12,10,10, 8, 5, 5, 9,10,12,13,15,13,13, + 12, 7, 6, 8, +}; + +static static_codebook _huff_book__44u7__long = { + 2, 100, + _huff_lengthlist__44u7__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u7__p1_0[] = { + 1, 4, 4, 5, 7, 7, 4, 7, 7, 5, 8, 8, 8,10,10, 7, + 10,10, 5, 8, 8, 7,10, 9, 8,10,10, 5, 8, 8, 8,11, + 10, 8,10,10, 8,11,10,10,12,13,11,13,13, 7,10,10, + 10,13,11,11,13,13, 5, 8, 8, 8,11,10, 8,10,10, 7, + 10,10,11,13,13,10,11,13, 8,11,11,10,13,13,10,13, + 12, +}; + +static float _vq_quantthresh__44u7__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u7__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p1_0 = { + _vq_quantthresh__44u7__p1_0, + _vq_quantmap__44u7__p1_0, + 3, + 3 +}; + +static static_codebook _44u7__p1_0 = { + 4, 81, + _vq_lengthlist__44u7__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u7__p1_0, + NULL, + &_vq_auxt__44u7__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u7__p2_0[] = { + 3, 5, 4, 5, 6, 6, 5, 6, 6, 5, 6, 6, 6, 7, 8, 6, + 7, 7, 5, 6, 6, 6, 7, 7, 6, 8, 8, 5, 6, 6, 6, 8, + 7, 6, 7, 8, 6, 8, 8, 8, 9, 9, 8, 9, 9, 6, 7, 7, + 7, 9, 8, 8, 9, 9, 5, 6, 6, 6, 8, 7, 6, 7, 8, 6, + 7, 7, 8, 9, 9, 7, 8, 9, 6, 8, 8, 8, 9, 9, 8, 9, + 9, +}; + +static float _vq_quantthresh__44u7__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u7__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p2_0 = { + _vq_quantthresh__44u7__p2_0, + _vq_quantmap__44u7__p2_0, + 3, + 3 +}; + +static static_codebook _44u7__p2_0 = { + 4, 81, + _vq_lengthlist__44u7__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u7__p2_0, + NULL, + &_vq_auxt__44u7__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u7__p3_0[] = { + 2, 5, 5, 8, 8, 5, 7, 6, 9, 9, 5, 6, 7, 9, 9, 8, + 9, 9,12,12, 8, 9, 9,12,12, 5, 7, 7,10, 9, 7, 9, + 9,11,11, 6, 8, 9,10,11,10,11,11,13,14, 9,10,11, + 13,13, 5, 7, 7, 9, 9, 6, 9, 8,11,10, 7, 9, 9,11, + 11, 9,11,10,13,13,10,11,11,14,13, 8,10,10,14,13, + 10,11,11,16,14, 9,11,11,14,14,13,15,14,17,16,12, + 13,14,16,15, 8, 9,10,13,14, 9,11,11,14,14,10,11, + 11,14,15,13,13,14,17,17,13,14,15,16,17, 5, 7, 7, + 10,10, 7, 9, 8,11,11, 7, 8, 9,11,11,10,11,11,14, + 14,10,11,11,14,14, 7, 9, 9,11,11, 9,11,11,13,13, + 9,11,11,13,13,11,12,13,15,17,11,12,13,16,18, 6, + 8, 8,11,11, 8,10,10,13,12, 9,11,11,12,13,11,13, + 11,16,14,11,13,12,16,16,10,11,11,15,15,11,13,13, + 16,16,11,13,13,18,16,14,14,15,17,16,14,15,17,19, + 0, 9,11,11,14,15,10,12,12,15,17,11,13,13,15,17, + 14,15,13, 0,15,15,17,16,18, 0, 5, 7, 7, 9,10, 7, + 9, 9,11,11, 7, 8, 9,11,11,10,11,11,14,14,10,11, + 11,14,14, 6, 8, 9,11,11, 9,11,11,14,13, 8,10,10, + 11,14,11,13,13,17,15,11,12,13,14,16, 7, 9, 9,11, + 11, 9,11,11,13,12, 9,11,11,12,13,11,13,12,15,15, + 11,13,12,16,15, 9,11,11,15,15,11,12,13,16,15,10, + 11,12,16,15,15,16,16, 0, 0,14,13,15,16,19, 9,11, + 12,15,15,11,13,13,17,16,11,13,12,15,17,14,16,16, + 18, 0,15,15,16,18,17, 8,10,10,13,14,10,11,11,15, + 16,10,11,11,15,16,14,15,15,17,19,14,14,15,19,19, + 9,11,11,16,15,11,13,13,17,17,11,13,13,17,17,15, + 17,16, 0, 0,15,17,17, 0,17, 9,11,11,15,15,11,13, + 12,16,16,11,13,13,18,17,15,16,15,19,19,14,16,18, + 17,19,13,15,15, 0,18,14,16,17,17,18,14,16,16,19, + 19,18,19,19, 0, 0,16,17,18,19, 0,12,14,14,16,18, + 13,16,14, 0,19,14,16,17,19, 0,16,19,16,19,17,17, + 18,18, 0, 0, 8,10,10,14,13, 9,11,11,15,14,10,11, + 12,14,16,13,15,14,19,19,14,16,16,18,17, 9,11,11, + 16,15,11,13,13, 0,17,10,12,13,15,16,15,18,16,19, + 19,14,15,16,18,18, 9,11,12,15,15,11,13,13,17,19, + 11,13,13,17,17,15,17,16,19,19,15,17,16, 0, 0,12, + 14,14, 0,19,14,16,15, 0,19,13,14,16,19, 0,16,18, + 18, 0, 0,16,16,17,17, 0,13,14,14,17, 0,14,17,16, + 0,19,14,16,16,19,18,17,19,17, 0, 0,18,18,17, 0, + 0, +}; + +static float _vq_quantthresh__44u7__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u7__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p3_0 = { + _vq_quantthresh__44u7__p3_0, + _vq_quantmap__44u7__p3_0, + 5, + 5 +}; + +static static_codebook _44u7__p3_0 = { + 4, 625, + _vq_lengthlist__44u7__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u7__p3_0, + NULL, + &_vq_auxt__44u7__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u7__p4_0[] = { + 5, 6, 6, 8, 8, 6, 7, 6, 9, 9, 6, 6, 7, 9, 9, 8, + 9, 9,11,11, 8, 9, 9,11,11, 6, 7, 7, 9, 9, 7, 8, + 7,10,10, 6, 7, 8, 9,10, 9,10,10,11,12, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 6, 7, 7,10, 9, 7, 7, 8,10, + 10, 9,10, 9,12,11, 9,10,10,12,11, 8, 9, 9,11,11, + 9,10,10,12,12, 9,10,10,11,12,11,12,12,13,13,11, + 11,12,12,13, 8, 9, 9,11,11, 9,10,10,12,11, 9,10, + 10,12,12,11,12,11,13,12,11,12,12,13,14, 6, 7, 7, + 9, 9, 7, 8, 7,10, 9, 7, 7, 8, 9,10, 9,10,10,12, + 11, 9,10,10,11,12, 7, 8, 8,10,10, 8, 8, 8,10,10, + 8, 8, 8,10,10,10,10,11,12,12,10,10,11,12,12, 6, + 7, 7,10,10, 7, 8, 8,10,10, 8, 8, 8,10,11, 9,10, + 10,12,11,10,10,11,12,12, 9,10,10,12,12,10,10,10, + 12,12,10,11,10,12,12,12,12,12,13,14,12,12,12,13, + 14, 9,10,10,11,12, 9,10,10,12,12,10,10,11,12,12, + 11,12,11,14,12,12,12,12,14,14, 6, 7, 7, 9, 9, 7, + 8, 7,10, 9, 7, 7, 8, 9,10, 9,10,10,12,11, 9,10, + 10,11,12, 6, 7, 7,10,10, 8, 8, 8,11,10, 7, 8, 8, + 10,10,10,10,10,12,12,10,10,10,11,12, 7, 8, 8,10, + 10, 8, 8, 8,10,10, 8, 8, 8,10,10,10,11,10,12,12, + 10,11,10,12,12, 9,10,10,12,12,10,11,10,13,12, 9, + 10,10,12,12,12,12,12,14,14,11,11,12,12,14, 9,10, + 10,12,12,10,10,10,12,12,10,11,10,12,12,12,12,12, + 14,14,12,12,12,14,13, 8, 9, 9,11,11, 9,10,10,12, + 12, 9,10,10,12,12,11,12,12,14,13,11,12,12,13,13, + 9,10,10,12,12,10,10,10,12,12,10,11,11,12,12,12, + 12,13,14,14,12,12,12,14,14, 9,10, 9,12,12, 9,10, + 10,12,12,10,10,11,12,12,12,12,11,14,13,12,12,12, + 13,14,11,12,12,13,13,12,12,12,14,14,12,13,12,14, + 14,13,13,14,13,15,14,14,14,15,15,11,11,11,13,13, + 11,12,11,14,13,12,12,12,14,14,12,13,12,15,12,13, + 14,14,15,15, 8, 9, 9,11,11, 9,10,10,12,12, 9,10, + 10,12,12,11,12,12,13,13,11,12,12,13,14, 9,10,10, + 12,11,10,10,10,12,12, 9,10,10,12,12,12,13,12,14, + 13,11,12,12,13,14, 9,10,10,12,12,10,10,10,12,12, + 10,11,11,12,12,12,12,12,14,14,12,13,12,14,13,11, + 11,11,13,13,12,12,12,14,13,11,11,12,13,14,13,14, + 14,15,15,13,12,13,12,15,11,12,12,13,14,12,12,12, + 14,14,11,12,12,14,14,13,14,14,15,15,13,14,13,15, + 13, +}; + +static float _vq_quantthresh__44u7__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u7__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p4_0 = { + _vq_quantthresh__44u7__p4_0, + _vq_quantmap__44u7__p4_0, + 5, + 5 +}; + +static static_codebook _44u7__p4_0 = { + 4, 625, + _vq_lengthlist__44u7__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u7__p4_0, + NULL, + &_vq_auxt__44u7__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u7__p5_0[] = { + 2, 3, 3, 6, 6, 8, 8,10,10, 3, 5, 5, 8, 7, 8, 8, + 11,11, 4, 5, 5, 7, 8, 8, 8,11,11, 6, 8, 7, 9, 9, + 10, 9,12,12, 6, 7, 8, 9,10, 9,10,12,12, 8, 8, 8, + 10, 9,12,11,13,13, 8, 8, 8, 9,10,11,12,13,13,10, + 11,11,12,11,13,13,14,14,10,11,11,12,12,13,13,14, + 14, +}; + +static float _vq_quantthresh__44u7__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u7__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p5_0 = { + _vq_quantthresh__44u7__p5_0, + _vq_quantmap__44u7__p5_0, + 9, + 9 +}; + +static static_codebook _44u7__p5_0 = { + 2, 81, + _vq_lengthlist__44u7__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u7__p5_0, + NULL, + &_vq_auxt__44u7__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p6_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u7__p6_0[] = { + 4, 4, 4, 5, 5, 7, 7, 9, 9, 4, 4, 4, 6, 6, 7, 7, + 9, 9, 4, 4, 5, 6, 6, 7, 7, 9, 9, 5, 6, 6, 7, 7, + 8, 8, 9, 9, 5, 6, 6, 6, 7, 8, 8, 9, 9, 7, 7, 7, + 8, 8, 9, 9,10,10, 7, 7, 7, 8, 8, 9, 9,10,10, 9, + 9, 9,10, 9,10,10,11,11, 9, 9, 9, 9,10,10,10,11, + 11, +}; + +static float _vq_quantthresh__44u7__p6_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u7__p6_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p6_0 = { + _vq_quantthresh__44u7__p6_0, + _vq_quantmap__44u7__p6_0, + 9, + 9 +}; + +static static_codebook _44u7__p6_0 = { + 2, 81, + _vq_lengthlist__44u7__p6_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u7__p6_0, + NULL, + &_vq_auxt__44u7__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u7__p7_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 9, 8, 8,10,10, 8, + 10,10, 5, 8, 9, 8,10,10, 8,10,10, 4, 9, 9, 9,11, + 12, 8,12,11, 8,12,11,11,12,13,10,13,13, 7,12,11, + 10,13,12,10,13,13, 4, 9, 9, 8,11,12, 9,11,12, 7, + 11,12,10,13,13,10,12,13, 8,11,12,10,13,13,10,13, + 12, +}; + +static float _vq_quantthresh__44u7__p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44u7__p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p7_0 = { + _vq_quantthresh__44u7__p7_0, + _vq_quantmap__44u7__p7_0, + 3, + 3 +}; + +static static_codebook _44u7__p7_0 = { + 4, 81, + _vq_lengthlist__44u7__p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44u7__p7_0, + NULL, + &_vq_auxt__44u7__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u7__p7_1[] = { + 3, 5, 4, 6, 6, 7, 7, 8, 8, 8, 8, 5, 5, 5, 6, 6, + 7, 7, 8, 8, 8, 8, 5, 5, 5, 6, 6, 7, 7, 8, 8, 8, + 8, 6, 6, 6, 7, 7, 8, 7, 8, 8, 8, 8, 6, 6, 6, 7, + 7, 7, 8, 8, 8, 8, 8, 7, 7, 7, 8, 7, 8, 8, 9, 9, + 9, 9, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 8, 8, 8, + 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__44u7__p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u7__p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p7_1 = { + _vq_quantthresh__44u7__p7_1, + _vq_quantmap__44u7__p7_1, + 11, + 11 +}; + +static static_codebook _44u7__p7_1 = { + 2, 121, + _vq_lengthlist__44u7__p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u7__p7_1, + NULL, + &_vq_auxt__44u7__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p8_0[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u7__p8_0[] = { + 1, 4, 4, 6, 6, 8, 8, 9, 9,10,10, 4, 6, 6, 7, 7, + 9, 9,10,10,11,11, 4, 6, 6, 7, 7, 9, 9,10,10,11, + 11, 6, 8, 8, 9, 9,10,10,11,11,12,12, 6, 8, 8, 9, + 9,10,10,11,11,12,12, 8, 9, 9,10,10,11,11,12,12, + 14,13, 8, 9, 9,10,10,11,11,12,12,13,13,10,10,10, + 11,11,12,12,13,13,14,14,10,10,10,11,11,12,13,13, + 13,14,14,11,12,11,13,12,13,14,14,14,15,15,11,11, + 12,13,13,13,13,14,14,15,15, +}; + +static float _vq_quantthresh__44u7__p8_0[] = { + -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, 27.5, + 38.5, 49.5, +}; + +static long _vq_quantmap__44u7__p8_0[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p8_0 = { + _vq_quantthresh__44u7__p8_0, + _vq_quantmap__44u7__p8_0, + 11, + 11 +}; + +static static_codebook _44u7__p8_0 = { + 2, 121, + _vq_lengthlist__44u7__p8_0, + 1, -524582912, 1618345984, 4, 0, + _vq_quantlist__44u7__p8_0, + NULL, + &_vq_auxt__44u7__p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p8_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u7__p8_1[] = { + 3, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 5, 6, 6, 7, 6, + 7, 7, 7, 7, 8, 8, 5, 6, 6, 6, 7, 7, 7, 7, 7, 8, + 8, 6, 7, 6, 7, 7, 7, 7, 8, 8, 8, 8, 6, 6, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44u7__p8_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u7__p8_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p8_1 = { + _vq_quantthresh__44u7__p8_1, + _vq_quantmap__44u7__p8_1, + 11, + 11 +}; + +static static_codebook _44u7__p8_1 = { + 2, 121, + _vq_lengthlist__44u7__p8_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u7__p8_1, + NULL, + &_vq_auxt__44u7__p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p9_0[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u7__p9_0[] = { + 1, 4, 4, 9, 9, 9, 9, 9, 9, 9, 9, 5, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 4, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44u7__p9_0[] = { + -2866.5, -2229.5, -1592.5, -955.5, -318.5, 318.5, 955.5, 1592.5, + 2229.5, 2866.5, +}; + +static long _vq_quantmap__44u7__p9_0[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p9_0 = { + _vq_quantthresh__44u7__p9_0, + _vq_quantmap__44u7__p9_0, + 11, + 11 +}; + +static static_codebook _44u7__p9_0 = { + 2, 121, + _vq_lengthlist__44u7__p9_0, + 1, -512171520, 1630791680, 4, 0, + _vq_quantlist__44u7__p9_0, + NULL, + &_vq_auxt__44u7__p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p9_1[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u7__p9_1[] = { + 1, 4, 4, 5, 5, 7, 7,10, 9,11,11,12,12, 4, 7, 6, + 8, 8, 9, 9,11,10,13,12,13,13, 4, 6, 7, 8, 8, 9, + 9,10,11,13,13,12,13, 5, 8, 8,10, 9,12,11,12,12, + 13,13,15,14, 6, 8, 8,10,10,11,11,13,12,13,14,14, + 15, 8,10,10,12,11,13,13,14,15,15,16,15,16, 8, 9, + 10,12,12,13,13,16,15,15,15,15,15,10,11,11,14,13, + 14,14,16,16,15,16,16,16,10,12,12,12,14,14,14,15, + 16,15,16,15,16,11,12,12,14,14,16,16,15,16,16,16, + 16,16,12,12,13,13,15,15,14,15,16,16,16,16,16,12, + 14,14,15,14,16,16,16,16,16,16,16,16,13,14,13,14, + 15,16,15,16,16,16,16,16,16, +}; + +static float _vq_quantthresh__44u7__p9_1[] = { + -269.5, -220.5, -171.5, -122.5, -73.5, -24.5, 24.5, 73.5, + 122.5, 171.5, 220.5, 269.5, +}; + +static long _vq_quantmap__44u7__p9_1[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p9_1 = { + _vq_quantthresh__44u7__p9_1, + _vq_quantmap__44u7__p9_1, + 13, + 13 +}; + +static static_codebook _44u7__p9_1 = { + 2, 169, + _vq_lengthlist__44u7__p9_1, + 1, -518889472, 1622704128, 4, 0, + _vq_quantlist__44u7__p9_1, + NULL, + &_vq_auxt__44u7__p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u7__p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__44u7__p9_2[] = { + 2, 4, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, +}; + +static float _vq_quantthresh__44u7__p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__44u7__p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__44u7__p9_2 = { + _vq_quantthresh__44u7__p9_2, + _vq_quantmap__44u7__p9_2, + 49, + 49 +}; + +static static_codebook _44u7__p9_2 = { + 1, 49, + _vq_lengthlist__44u7__p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__44u7__p9_2, + NULL, + &_vq_auxt__44u7__p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u7__short[] = { + 6,14,18,18,18,18,17,17,17,17, 4, 8,11,12,13,11, + 13,11,16,17, 6, 7, 8, 9, 8, 9,12, 9,14,17, 6, 9, + 11,12,12,12,15,12,17,17, 6, 6, 6, 8, 5, 7, 7, 8, + 14,17, 7, 9, 9,11, 8,10,10,11,14,16, 8, 7, 7, 8, + 5, 7, 4, 5,11,17, 9, 8,10, 9, 7, 9, 4, 2, 9,16, + 15,14,16,14, 8,14, 4, 3, 7,16,17,17,16,17, 9,15, + 6, 5,10,17, +}; + +static static_codebook _huff_book__44u7__short = { + 2, 100, + _huff_lengthlist__44u7__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u8__long[] = { + 3, 9,13,13,14,13,13,13,13,14, 5, 4, 6, 8,10,12, + 13,15,13,14, 9, 5, 3, 5, 8,10,12,14,13,13,11, 7, + 4, 3, 5, 7,10,11,12,14,11, 9, 7, 4, 4, 6, 8,10, + 13,14,10,11, 9, 7, 6, 6, 7, 9,12,15,12,11,11, 8, + 7, 6, 6, 7,11,14,12,12,12,10, 8, 7, 6, 7, 9,13, + 11,12,13,12,11, 8, 8, 7, 9,12,11,14,16,16,15,11, + 10, 9, 9,11, +}; + +static static_codebook _huff_book__44u8__long = { + 2, 100, + _huff_lengthlist__44u8__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u8__short[] = { + 7,15,18,18,18,18,18,18,18,18, 4, 6, 9,10,10,11, + 16,15,18,18, 5, 6, 6, 6, 8,10,15,15,18,18, 5, 6, + 5, 4, 6, 9,12,15,17,18, 7, 6, 6, 5, 6, 5, 8,11, + 15,18,10, 9, 9, 7, 4, 3, 6,10,16,17,13,12,12, 6, + 6, 4, 5, 9,14,16,16,17,13, 5, 5, 4, 5, 8,13,16, + 17,17,14, 7, 7, 6, 7,10,15,17,17,17,17,11,12,12, + 12,14,17,17, +}; + +static static_codebook _huff_book__44u8__short = { + 2, 100, + _huff_lengthlist__44u8__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u8_p1_0[] = { + 1, 5, 5, 5, 7, 7, 5, 7, 7, 5, 7, 7, 8, 9, 9, 7, + 8, 9, 5, 7, 7, 7, 9, 8, 8, 9, 9, 5, 7, 7, 7, 9, + 9, 7, 9, 9, 8, 9, 9, 9,10,11, 9,10,10, 7, 9, 9, + 9,10, 9, 9,10,11, 5, 7, 7, 7, 9, 9, 8, 9, 9, 7, + 9, 9, 9,11,10, 9, 9,10, 8, 9, 9, 9,10,10, 9,11, + 10, +}; + +static float _vq_quantthresh__44u8_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u8_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p1_0 = { + _vq_quantthresh__44u8_p1_0, + _vq_quantmap__44u8_p1_0, + 3, + 3 +}; + +static static_codebook _44u8_p1_0 = { + 4, 81, + _vq_lengthlist__44u8_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u8_p1_0, + NULL, + &_vq_auxt__44u8_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u8_p2_0[] = { + 4, 5, 5, 8, 8, 6, 7, 6, 9, 9, 5, 6, 7, 9, 9, 8, + 9, 9,11,11, 8, 9, 9,11,11, 6, 7, 7, 9, 9, 7, 8, + 8,10,10, 7, 7, 8, 9,10, 9,10,10,12,12, 9, 9,10, + 11,12, 6, 7, 7, 9, 9, 7, 8, 7,10, 9, 7, 8, 8,10, + 10, 9,10, 9,12,11, 9,10,10,12,12, 8, 9, 9,11,11, + 9,10,10,12,12, 9,10,10,12,12,11,12,12,13,13,11, + 11,12,13,13, 8, 9, 9,11,11, 9,10,10,12,12, 9,10, + 10,12,12,11,12,11,13,13,11,12,12,14,13, 5, 7, 7, + 9, 9, 7, 8, 8,10,10, 7, 8, 8,10,10, 9,10,10,12, + 12, 9,10,10,11,12, 7, 8, 8,10,10, 8, 9, 9,11,11, + 8, 9, 9,10,11,10,11,11,12,12,10,10,11,12,13, 6, + 8, 8,10,10, 7, 8, 8,11,10, 8, 8, 9,10,11,10,11, + 10,12,11,10,11,11,12,12, 9,10,10,12,12,10,11,11, + 13,13,10,11,11,13,13,12,12,13,13,14,12,12,13,14, + 14, 9,10,10,12,12, 9,10,10,12,12,10,11,11,12,13, + 11,12,11,14,12,12,12,12,14,14, 5, 7, 7, 9, 9, 7, + 8, 8,10,10, 7, 8, 8,10,10, 9,10,10,12,11, 9,10, + 10,12,12, 6, 8, 8,10,10, 8, 9, 8,11,10, 7, 8, 8, + 10,11,10,11,11,13,12,10,10,11,11,13, 7, 8, 8,10, + 10, 8, 9, 9,11,10, 8, 9, 9,11,11,10,11,10,13,12, + 10,11,11,12,12, 9,10,10,12,12,10,11,11,13,12, 9, + 10,10,12,12,12,13,13,14,14,11,11,12,12,14, 9,10, + 10,12,12,10,11,11,13,13,10,11,11,13,12,12,13,12, + 14,14,12,13,12,14,13, 8, 9, 9,11,11, 9,10,10,12, + 12, 9,10,10,12,12,11,12,12,14,13,11,12,12,13,14, + 9,10,10,12,12,10,11,11,13,13,10,11,11,13,13,12, + 13,13,14,15,12,12,13,14,14, 9,10,10,12,12, 9,11, + 10,13,12,10,11,11,12,13,12,13,12,14,13,12,12,13, + 14,14,11,12,12,14,14,12,12,12,14,14,12,13,13,14, + 14,13,13,14,14,16,14,14,14,15,15,11,12,12,14,14, + 11,12,11,14,13,12,12,13,14,14,13,14,12,15,13,14, + 14,14,15,16, 8, 9, 9,11,11, 9,10,10,12,12, 9,10, + 10,12,12,11,12,12,14,13,11,12,12,13,14, 9,10,10, + 12,12,10,11,10,13,12, 9,10,11,12,13,12,13,12,14, + 14,12,12,13,13,14, 9,10,10,12,12,10,11,11,13,13, + 10,11,11,13,13,12,12,12,14,14,12,13,12,15,14,11, + 12,11,14,13,12,13,12,14,14,11,11,12,13,14,13,14, + 14,16,15,13,12,14,13,15,11,12,12,13,14,12,13,13, + 14,14,12,13,12,14,14,14,14,14,15,16,13,14,13,15, + 14, +}; + +static float _vq_quantthresh__44u8_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u8_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p2_0 = { + _vq_quantthresh__44u8_p2_0, + _vq_quantmap__44u8_p2_0, + 5, + 5 +}; + +static static_codebook _44u8_p2_0 = { + 4, 625, + _vq_lengthlist__44u8_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u8_p2_0, + NULL, + &_vq_auxt__44u8_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u8_p3_0[] = { + 3, 4, 4, 5, 5, 7, 7, 9, 9, 4, 5, 4, 6, 6, 7, 7, + 9, 9, 4, 4, 5, 6, 6, 7, 7, 9, 9, 5, 6, 6, 7, 7, + 8, 8,10,10, 6, 6, 6, 7, 7, 8, 8, 9,10, 7, 7, 7, + 8, 8, 9, 9,11,10, 7, 7, 7, 8, 8, 9, 9,10,11, 9, + 9, 9,10,10,11,10,12,12, 9, 9, 9,10,10,10,11,12, + 12, +}; + +static float _vq_quantthresh__44u8_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u8_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p3_0 = { + _vq_quantthresh__44u8_p3_0, + _vq_quantmap__44u8_p3_0, + 9, + 9 +}; + +static static_codebook _44u8_p3_0 = { + 2, 81, + _vq_lengthlist__44u8_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u8_p3_0, + NULL, + &_vq_auxt__44u8_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44u8_p4_0[] = { + 4, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8,10,10,11,11,11, + 11, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11,11, + 12,12, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,11, + 11,11,12, 6, 6, 6, 7, 7, 8, 7, 9, 9, 9, 9,10,10, + 11,11,12,12, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9,10, + 10,11,11,12,12, 7, 7, 7, 8, 7, 9, 8, 9, 9,10, 9, + 11,10,11,11,12,12, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, + 10,10,10,11,11,12,12, 8, 8, 8, 9, 9, 9, 9,10,10, + 10,10,11,11,11,11,12,12, 8, 8, 8, 8, 9, 9, 9,10, + 10,10,10,11,11,11,12,12,12, 9, 9, 9, 9, 9,10, 9, + 10,10,11,10,11,11,12,12,12,12, 9, 9, 9, 9, 9, 9, + 10,10,10,11,11,11,11,12,12,12,13,10,10,10,10,10, + 11,10,11,11,11,11,12,12,12,12,12,13,10,10,10,10, + 10,10,11,11,11,11,11,12,12,12,12,13,12,11,11,11, + 11,11,11,11,12,12,12,12,12,12,12,13,13,13,11,11, + 11,11,11,11,11,12,12,12,12,12,12,13,12,13,13,11, + 12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13, + 12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13, + 13, +}; + +static float _vq_quantthresh__44u8_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44u8_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p4_0 = { + _vq_quantthresh__44u8_p4_0, + _vq_quantmap__44u8_p4_0, + 17, + 17 +}; + +static static_codebook _44u8_p4_0 = { + 2, 289, + _vq_lengthlist__44u8_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44u8_p4_0, + NULL, + &_vq_auxt__44u8_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u8_p5_0[] = { + 1, 5, 5, 5, 7, 7, 5, 7, 7, 5, 8, 7, 7, 8, 9, 7, + 9, 9, 5, 7, 8, 7, 9, 9, 7, 9, 8, 5, 7, 7, 8, 9, + 9, 7, 9, 9, 7, 9, 9, 8, 9,11, 9,11,11, 7, 9, 9, + 9,11,10, 9,11,11, 5, 7, 8, 7, 9, 9, 8, 9, 9, 7, + 9, 9, 9,11,11, 9,10,11, 7, 9, 9, 9,11,11, 8,11, + 9, +}; + +static float _vq_quantthresh__44u8_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44u8_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p5_0 = { + _vq_quantthresh__44u8_p5_0, + _vq_quantmap__44u8_p5_0, + 3, + 3 +}; + +static static_codebook _44u8_p5_0 = { + 4, 81, + _vq_lengthlist__44u8_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44u8_p5_0, + NULL, + &_vq_auxt__44u8_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u8_p5_1[] = { + 4, 5, 5, 6, 6, 7, 7, 7, 7, 7, 8, 5, 6, 6, 6, 6, + 7, 7, 7, 7, 8, 8, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, + 8, 6, 6, 6, 7, 7, 7, 7, 8, 7, 8, 8, 6, 6, 6, 7, + 7, 7, 7, 7, 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 7, 7, 7, + 7, 7, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44u8_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u8_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p5_1 = { + _vq_quantthresh__44u8_p5_1, + _vq_quantmap__44u8_p5_1, + 11, + 11 +}; + +static static_codebook _44u8_p5_1 = { + 2, 121, + _vq_lengthlist__44u8_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u8_p5_1, + NULL, + &_vq_auxt__44u8_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u8_p6_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 8, 9,10,10,10, 5, 6, 6, + 7, 7, 8, 8, 9, 9,10,10,11,11, 5, 6, 6, 7, 7, 8, + 9, 9, 9,10,10,11,11, 7, 7, 7, 8, 8, 9, 9,10,10, + 11,11,11,11, 7, 7, 7, 8, 8, 9, 9,10,10,10,11,11, + 11, 8, 9, 9, 9, 9,10,10,10,10,11,11,12,12, 8, 9, + 9, 9, 9,10,10,10,11,11,11,12,12, 8, 9, 9,10,10, + 11,10,11,11,12,12,12,12, 9, 9, 9,10,10,11,11,11, + 11,12,12,12,12,10,10,10,11,11,11,11,12,12,12,12, + 13,13,10,10,10,11,11,11,11,12,12,12,12,13,13,11, + 11,11,12,12,12,12,12,12,13,13,13,13,11,11,11,12, + 12,12,12,12,12,13,13,13,13, +}; + +static float _vq_quantthresh__44u8_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44u8_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p6_0 = { + _vq_quantthresh__44u8_p6_0, + _vq_quantmap__44u8_p6_0, + 13, + 13 +}; + +static static_codebook _44u8_p6_0 = { + 2, 169, + _vq_lengthlist__44u8_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44u8_p6_0, + NULL, + &_vq_auxt__44u8_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u8_p6_1[] = { + 3, 4, 4, 5, 5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, +}; + +static float _vq_quantthresh__44u8_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u8_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p6_1 = { + _vq_quantthresh__44u8_p6_1, + _vq_quantmap__44u8_p6_1, + 5, + 5 +}; + +static static_codebook _44u8_p6_1 = { + 2, 25, + _vq_lengthlist__44u8_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u8_p6_1, + NULL, + &_vq_auxt__44u8_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u8_p7_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 8, 9, 9,10,10, 4, 6, 6, + 8, 8, 9, 8, 9, 9,10,10,11,11, 4, 6, 6, 8, 8, 8, + 9, 9, 9,10,10,11,11, 7, 8, 8, 9, 9,10,10,10,10, + 11,11,12,12, 7, 8, 8, 9, 9,10,10,10,10,11,11,12, + 12, 8, 9, 9,10,10,10,10,11,11,12,12,13,13, 8, 9, + 9,10,10,10,11,11,11,12,13,13,13, 9, 9, 9,10,10, + 11,11,12,12,13,13,14,14, 9, 9, 9,10,10,11,11,12, + 12,13,13,14,14,10,10,10,11,11,12,12,13,13,14,14, + 14,14,10,10,11,11,12,12,12,13,13,13,14,14,15,11, + 11,11,12,12,13,13,14,14,14,14,16,15,11,11,11,12, + 12,13,13,14,14,14,14,16,15, +}; + +static float _vq_quantthresh__44u8_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__44u8_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p7_0 = { + _vq_quantthresh__44u8_p7_0, + _vq_quantmap__44u8_p7_0, + 13, + 13 +}; + +static static_codebook _44u8_p7_0 = { + 2, 169, + _vq_lengthlist__44u8_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__44u8_p7_0, + NULL, + &_vq_auxt__44u8_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u8_p7_1[] = { + 4, 5, 5, 6, 6, 7, 7, 7, 7, 7, 7, 5, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 5, 6, 6, 7, 7, 7, 7, 7, 7, 7, + 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 7, 7, 7, 7, 7, 7, 7, 8, 8, + 8, 8, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 7, 7, 7, + 7, 7, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, + 7, 8, 8, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44u8_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u8_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p7_1 = { + _vq_quantthresh__44u8_p7_1, + _vq_quantmap__44u8_p7_1, + 11, + 11 +}; + +static static_codebook _44u8_p7_1 = { + 2, 121, + _vq_lengthlist__44u8_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u8_p7_1, + NULL, + &_vq_auxt__44u8_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u8_p8_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 7, 9, 8,10,10,11,11, 4, + 6, 6, 8, 8,10,10, 9, 9,10,10,11,10,12,12, 4, 6, + 6, 8, 8, 9,10, 9, 9,10,10,11,11,11,12, 7, 8, 8, + 10,10,11,11,11,10,11,11,13,12,13,12, 7, 8, 8,10, + 10,11,11,10,11,11,11,12,12,13,13, 8,10, 9,11,11, + 12,12,11,11,12,12,13,13,14,14, 8, 9, 9,11,11,12, + 12,11,11,12,12,14,13,13,13, 8, 9, 9,11,10,12,11, + 12,12,13,13,14,13,14,13, 8, 9, 9,11,11,11,12,12, + 12,13,13,13,14,14,14, 9,10,10,12,11,12,12,13,13, + 14,14,15,13,14,14, 9,10,10,11,12,12,12,13,13,14, + 14,15,14,14,14,10,11,11,12,12,13,13,14,13,14,14, + 15,14,15,15,10,11,11,12,12,13,13,13,14,14,14,14, + 15,16,15,11,12,12,13,12,14,14,14,13,15,14,16,15, + 16,15,11,12,12,13,13,13,14,14,15,15,15,15,16,15, + 15, +}; + +static float _vq_quantthresh__44u8_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__44u8_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p8_0 = { + _vq_quantthresh__44u8_p8_0, + _vq_quantmap__44u8_p8_0, + 15, + 15 +}; + +static static_codebook _44u8_p8_0 = { + 2, 225, + _vq_lengthlist__44u8_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__44u8_p8_0, + NULL, + &_vq_auxt__44u8_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44u8_p8_1[] = { + 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 6, 6, 7, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 7, 6, 7, 7, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 7, 7, 7, 8, 8, 8, 8, 9, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9,10, 9, 9,10, 9,10, 8, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9,10, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 10, 9,10,10, 9,10, 9,10, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9,10,10, 9,10,10, 9,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10,10, + 10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9,10, 9,10,10,10,10,10,10,10,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9,10, 9, 9,10,10,10,10,10,10,10,10, + 10, 9, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10, + 10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9,10,10, + 10,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9,10,10,10, + 10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, + 10,10, 9,10,10,10,10,10,10,10,10,10,10,10,10, 9, + 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10, 9, 9, 9, 9, 9,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__44u8_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44u8_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p8_1 = { + _vq_quantthresh__44u8_p8_1, + _vq_quantmap__44u8_p8_1, + 21, + 21 +}; + +static static_codebook _44u8_p8_1 = { + 2, 441, + _vq_lengthlist__44u8_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44u8_p8_1, + NULL, + &_vq_auxt__44u8_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p9_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u8_p9_0[] = { + 1, 4, 4, 9, 9, 9, 9, 9, 9, 5, 9, 9, 8, 8, 8, 8, + 8, 8, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, +}; + +static float _vq_quantthresh__44u8_p9_0[] = { + -3258.5, -2327.5, -1396.5, -465.5, 465.5, 1396.5, 2327.5, 3258.5, +}; + +static long _vq_quantmap__44u8_p9_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p9_0 = { + _vq_quantthresh__44u8_p9_0, + _vq_quantmap__44u8_p9_0, + 9, + 9 +}; + +static static_codebook _44u8_p9_0 = { + 2, 81, + _vq_lengthlist__44u8_p9_0, + 1, -511895552, 1631393792, 4, 0, + _vq_quantlist__44u8_p9_0, + NULL, + &_vq_auxt__44u8_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p9_1[] = { + 9, + 8, + 10, + 7, + 11, + 6, + 12, + 5, + 13, + 4, + 14, + 3, + 15, + 2, + 16, + 1, + 17, + 0, + 18, +}; + +static long _vq_lengthlist__44u8_p9_1[] = { + 1, 4, 4, 7, 7, 8, 8, 7, 8, 9, 9,10,10,11,11,12, + 12,12,12, 4, 7, 6, 9, 9, 9, 9, 9, 8, 9, 9,11,10, + 12,11,13,12,13,14, 4, 6, 6, 9, 9, 9, 9, 8, 9, 9, + 10,10,11,12,12,12,12,13,12, 7, 9, 8,11,10,10,10, + 10,10,11,11,12,11,14,12,13,14,14,13, 7, 8, 9,10, + 10,10,10,10,10,11,11,12,13,13,13,14,15,15,13, 8, + 9, 9,11,11,11,11,11,12,13,12,14,14,14,14,14,14, + 15,13, 8, 9, 9,10,11,11,11,12,12,13,12,13,14,13, + 15,14,15,15,15, 8, 9, 9,10,10,12,11,13,12,13,13, + 14,14,13,15,14,15,14,14, 8, 9, 9,10,11,12,12,13, + 13,14,14,14,14,15,15,15,12,14,14, 9,11,10,11,11, + 14,12,13,14,15,14,14,14,14,15,15,15,15,15, 9,10, + 11,11,12,12,13,13,14,14,14,14,15,15,14,15,15,15, + 15,10,11,11,12,12,14,14,13,14,14,15,15,15,15,15, + 15,15,15,15,10,11,11,12,13,13,13,14,14,15,15,14, + 14,15,15,15,15,14,15,11,12,13,15,13,14,15,15,15, + 15,14,15,15,15,15,15,15,15,15,11,12,12,14,14,14, + 13,14,15,15,14,15,15,15,15,15,15,15,15,13,13,14, + 13,13,14,14,15,14,15,15,15,15,15,15,15,15,15,15, + 11,14,13,14,14,15,14,14,15,15,15,15,15,15,15,15, + 15,15,15,12,12,13,14,13,13,14,15,14,15,15,15,15, + 15,15,15,15,15,15,13,13,14,14,13,15,14,14,15,15, + 14,15,15,15,15,15,15,15,15, +}; + +static float _vq_quantthresh__44u8_p9_1[] = { + -416.5, -367.5, -318.5, -269.5, -220.5, -171.5, -122.5, -73.5, + -24.5, 24.5, 73.5, 122.5, 171.5, 220.5, 269.5, 318.5, + 367.5, 416.5, +}; + +static long _vq_quantmap__44u8_p9_1[] = { + 17, 15, 13, 11, 9, 7, 5, 3, + 1, 0, 2, 4, 6, 8, 10, 12, + 14, 16, 18, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p9_1 = { + _vq_quantthresh__44u8_p9_1, + _vq_quantmap__44u8_p9_1, + 19, + 19 +}; + +static static_codebook _44u8_p9_1 = { + 2, 361, + _vq_lengthlist__44u8_p9_1, + 1, -518287360, 1622704128, 5, 0, + _vq_quantlist__44u8_p9_1, + NULL, + &_vq_auxt__44u8_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u8_p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__44u8_p9_2[] = { + 2, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__44u8_p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__44u8_p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__44u8_p9_2 = { + _vq_quantthresh__44u8_p9_2, + _vq_quantmap__44u8_p9_2, + 49, + 49 +}; + +static static_codebook _44u8_p9_2 = { + 1, 49, + _vq_lengthlist__44u8_p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__44u8_p9_2, + NULL, + &_vq_auxt__44u8_p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__44u9__long[] = { + 3, 9,13,13,14,14,13,14,14,13, 5, 5, 9,10,12,13, + 13,14,14,14, 9, 5, 6, 6, 8,11,12,14,14,14,11, 7, + 5, 3, 5, 8,10,12,13,12,12,10, 7, 4, 3, 5, 8,10, + 12,13,10,12, 9, 7, 4, 4, 6, 8,11,13,12,12,11, 9, + 7, 5, 6, 7, 9,13,12,12,12,11, 8, 7, 6, 6, 8,12, + 12,12,13,12,10, 9, 7, 7, 8,11,11,13,15,15,14,12, + 10, 9, 9,10, +}; + +static static_codebook _huff_book__44u9__long = { + 2, 100, + _huff_lengthlist__44u9__long, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _huff_lengthlist__44u9__short[] = { + 10,17,18,18,18,18,18,18,18,18, 5, 8,12,13,14,13, + 16,17,16,17, 5, 6, 8, 8,10, 9,12,16,16,17, 5, 6, + 7, 5, 6, 9,12,15,16,16, 7, 6, 6, 4, 5, 7, 9,14, + 15,17,10, 8, 8, 6, 6, 3, 5, 9,13,18,14,11,11,10, + 6, 3, 4, 7,13,17,15,16,16,10, 5, 6, 4, 4, 9,13, + 18,18,18,11, 6, 8, 5, 6,10,15,18,18,18,14,10,11, + 9,10,16,18, +}; + +static static_codebook _huff_book__44u9__short = { + 2, 100, + _huff_lengthlist__44u9__short, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u9_p1_0[] = { + 1, 5, 5, 5, 7, 7, 5, 7, 7, 5, 7, 7, 8, 9, 9, 7, + 9, 9, 5, 7, 7, 7, 9, 9, 7, 9, 9, 5, 7, 7, 7, 9, + 9, 7, 9, 9, 8, 9, 9, 9,10,11, 9,11,10, 7, 9, 9, + 9,11,10, 9,10,11, 5, 7, 7, 7, 9, 9, 7, 9, 9, 7, + 9, 9, 9,11,11, 9,10,11, 8, 9, 9, 9,10,10, 9,11, + 10, +}; + +static float _vq_quantthresh__44u9_p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__44u9_p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p1_0 = { + _vq_quantthresh__44u9_p1_0, + _vq_quantmap__44u9_p1_0, + 3, + 3 +}; + +static static_codebook _44u9_p1_0 = { + 4, 81, + _vq_lengthlist__44u9_p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__44u9_p1_0, + NULL, + &_vq_auxt__44u9_p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p2_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u9_p2_0[] = { + 3, 6, 5, 8, 8, 6, 7, 7, 9, 9, 6, 7, 7, 9, 9, 8, + 9, 9,11,10, 8, 9, 9,10,11, 6, 7, 7, 9, 9, 7, 8, + 8,10,10, 7, 8, 8, 9,10, 9,10,10,11,11, 9, 9,10, + 11,11, 6, 7, 7, 9, 9, 7, 8, 7,10, 9, 7, 8, 8,10, + 10, 9,10, 9,11,11, 9,10,10,11,11, 8, 9, 9,11,11, + 9,10,10,12,11, 9,10,10,11,11,11,12,12,13,13,11, + 11,11,12,13, 8, 9, 9,11,11, 9,10,10,11,11, 9,10, + 10,12,12,11,11,11,13,12,11,11,11,13,13, 6, 7, 7, + 9, 9, 7, 8, 8,10, 9, 7, 8, 8, 9,10, 9,10,10,11, + 11, 9,10,10,11,11, 7, 8, 8,10,10, 8, 9, 9,10,10, + 8, 9, 9,10,10,10,10,10,12,12,10,10,10,11,12, 7, + 8, 8,10,10, 8, 9, 8,10,10, 8, 9, 9,10,10, 9,10, + 10,12,11,10,10,10,12,12, 9,10,10,12,11,10,10,10, + 12,12,10,10,10,12,12,12,12,12,12,13,11,12,12,13, + 13, 9,10,10,11,11, 9,10,10,12,11,10,10,10,12,12, + 11,12,11,13,12,12,12,12,13,13, 6, 7, 7, 9, 9, 7, + 8, 8,10, 9, 7, 8, 8, 9,10, 9,10,10,12,11, 9,10, + 10,11,11, 7, 8, 8,10, 9, 8, 9, 9,11,10, 8, 8, 9, + 10,10,10,10,10,12,12,10,10,10,11,12, 7, 8, 8,10, + 10, 8, 9, 9,10,10, 8, 9, 9,10,10,10,10,10,12,12, + 10,10,10,12,12, 9,10,10,11,11,10,11,10,12,12, 9, + 10,10,11,12,11,12,12,13,13,11,11,12,11,13, 9,10, + 10,11,12,10,10,10,12,12,10,10,10,12,12,11,12,12, + 13,13,12,12,12,13,13, 8, 9, 9,11,11, 9,10,10,12, + 11, 9,10,10,11,12,11,12,12,13,13,11,11,12,13,13, + 9,10,10,12,12,10,10,10,12,12,10,11,10,12,12,12, + 12,12,13,13,12,12,12,13,13, 9,10,10,12,11,10,10, + 10,12,11,10,10,10,12,12,11,12,12,13,13,12,12,12, + 13,14,11,12,12,13,13,11,12,12,13,13,11,12,12,13, + 13,13,13,14,13,15,13,13,13,14,14,11,11,11,13,13, + 11,12,11,13,13,11,12,12,13,13,12,13,12,14,12,13, + 13,13,15,14, 8, 9, 9,11,11, 9,10,10,11,11, 9,10, + 10,11,12,11,12,11,13,13,11,12,12,13,13, 9,10,10, + 11,11,10,11,10,12,12, 9,10,10,12,12,12,12,12,13, + 13,11,11,12,12,13, 9,10,10,12,12,10,10,11,12,12, + 10,11,10,12,12,11,12,12,13,13,12,12,12,13,13,11, + 11,11,13,13,11,12,12,13,13,11,11,12,13,13,13,13, + 13,14,14,12,12,13,12,14,11,11,12,13,13,12,12,12, + 14,13,11,12,12,13,13,13,13,13,14,14,13,13,13,14, + 13, +}; + +static float _vq_quantthresh__44u9_p2_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u9_p2_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p2_0 = { + _vq_quantthresh__44u9_p2_0, + _vq_quantmap__44u9_p2_0, + 5, + 5 +}; + +static static_codebook _44u9_p2_0 = { + 4, 625, + _vq_lengthlist__44u9_p2_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u9_p2_0, + NULL, + &_vq_auxt__44u9_p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p3_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__44u9_p3_0[] = { + 3, 4, 4, 5, 5, 7, 7, 9, 8, 4, 5, 5, 6, 6, 7, 7, + 9, 9, 4, 5, 5, 6, 6, 7, 7, 9, 9, 5, 6, 6, 7, 6, + 8, 7, 9, 9, 5, 6, 6, 6, 7, 7, 8, 9, 9, 7, 7, 7, + 8, 7, 9, 8,10,10, 7, 7, 7, 7, 8, 8, 9,10,10, 9, + 9, 9, 9, 9,10,10,11,11, 9, 9, 9, 9, 9,10,10,11, + 11, +}; + +static float _vq_quantthresh__44u9_p3_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__44u9_p3_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p3_0 = { + _vq_quantthresh__44u9_p3_0, + _vq_quantmap__44u9_p3_0, + 9, + 9 +}; + +static static_codebook _44u9_p3_0 = { + 2, 81, + _vq_lengthlist__44u9_p3_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__44u9_p3_0, + NULL, + &_vq_auxt__44u9_p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p4_0[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__44u9_p4_0[] = { + 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9,10,10,11, + 11, 5, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8,10, 9,11,10, + 12,11, 5, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9,10,10, + 11,11,11, 6, 6, 6, 7, 6, 7, 7, 8, 8, 9, 9,10,10, + 11,11,12,12, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9,10, + 10,11,11,12,12, 7, 7, 7, 7, 7, 8, 8, 9, 8, 9, 9, + 10,10,11,11,12,12, 7, 7, 7, 7, 7, 8, 8, 8, 9, 9, + 9,10,10,11,11,12,12, 8, 8, 8, 8, 8, 9, 8, 9, 9, + 10,10,11,10,12,11,12,12, 8, 8, 8, 8, 8, 8, 9, 9, + 9,10,10,10,11,11,12,12,13, 8, 8, 8, 9, 9, 9, 9, + 10,10,11,10,11,11,12,12,13,12, 8, 8, 9, 9, 9, 9, + 9,10,10,10,11,11,11,12,12,12,13, 9,10, 9,10,10, + 10,10,11,10,11,11,12,11,13,12,13,13, 9, 9,10,10, + 10,10,10,10,11,11,11,11,12,12,13,13,13,10,11,10, + 11,11,11,11,12,11,12,12,13,12,13,13,14,13,10,10, + 11,11,11,11,11,11,12,12,12,12,13,13,13,13,14,11, + 12,11,12,12,12,12,12,12,13,13,13,13,14,13,14,14, + 11,11,12,12,12,12,12,12,12,12,13,13,13,13,14,14, + 14, +}; + +static float _vq_quantthresh__44u9_p4_0[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__44u9_p4_0[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p4_0 = { + _vq_quantthresh__44u9_p4_0, + _vq_quantmap__44u9_p4_0, + 17, + 17 +}; + +static static_codebook _44u9_p4_0 = { + 2, 289, + _vq_lengthlist__44u9_p4_0, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__44u9_p4_0, + NULL, + &_vq_auxt__44u9_p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p5_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__44u9_p5_0[] = { + 1, 5, 5, 5, 7, 7, 5, 7, 7, 5, 8, 7, 7, 8, 9, 7, + 8, 9, 5, 7, 8, 7, 9, 8, 7, 9, 8, 5, 8, 8, 8, 9, + 9, 7, 9, 9, 7, 9, 9, 8, 9,11, 9,11,10, 7, 9, 9, + 9,11, 9, 9,10,11, 5, 7, 8, 7, 9, 9, 8, 9, 9, 7, + 9, 9, 9,11,10, 9, 9,11, 7, 9, 9, 9,10,11, 8,11, + 9, +}; + +static float _vq_quantthresh__44u9_p5_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__44u9_p5_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p5_0 = { + _vq_quantthresh__44u9_p5_0, + _vq_quantmap__44u9_p5_0, + 3, + 3 +}; + +static static_codebook _44u9_p5_0 = { + 4, 81, + _vq_lengthlist__44u9_p5_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__44u9_p5_0, + NULL, + &_vq_auxt__44u9_p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p5_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u9_p5_1[] = { + 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, + 7, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, + 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 7, 7, 7, + 7, 7, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 8, 8, + 8, 8, 8, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 8, +}; + +static float _vq_quantthresh__44u9_p5_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u9_p5_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p5_1 = { + _vq_quantthresh__44u9_p5_1, + _vq_quantmap__44u9_p5_1, + 11, + 11 +}; + +static static_codebook _44u9_p5_1 = { + 2, 121, + _vq_lengthlist__44u9_p5_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u9_p5_1, + NULL, + &_vq_auxt__44u9_p5_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u9_p6_0[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10, 4, 5, 5, + 7, 7, 8, 8, 8, 8,10,10,11,11, 4, 5, 5, 7, 7, 8, + 8, 8, 8,10,10,11,11, 6, 7, 7, 8, 7, 8, 8, 9, 9, + 10,10,11,11, 6, 7, 7, 8, 7, 8, 8, 9, 9,10,10,11, + 11, 7, 8, 8, 8, 8, 9, 9, 9,10,11,11,12,12, 7, 8, + 8, 8, 8, 9, 9,10, 9,11,11,12,12, 8, 9, 8, 9, 9, + 10,10,10,10,11,11,12,12, 8, 8, 8, 9, 9,10, 9,10, + 10,11,11,12,12, 9,10,10,10,10,11,11,11,11,12,12, + 13,13, 9,10,10,10,10,11,11,11,11,12,12,13,12,10, + 11,11,11,11,12,12,12,12,12,12,13,13,10,11,11,11, + 11,12,12,12,12,13,12,13,13, +}; + +static float _vq_quantthresh__44u9_p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__44u9_p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p6_0 = { + _vq_quantthresh__44u9_p6_0, + _vq_quantmap__44u9_p6_0, + 13, + 13 +}; + +static static_codebook _44u9_p6_0 = { + 2, 169, + _vq_lengthlist__44u9_p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__44u9_p6_0, + NULL, + &_vq_auxt__44u9_p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__44u9_p6_1[] = { + 4, 4, 4, 5, 5, 4, 5, 4, 5, 5, 4, 4, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, +}; + +static float _vq_quantthresh__44u9_p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__44u9_p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p6_1 = { + _vq_quantthresh__44u9_p6_1, + _vq_quantmap__44u9_p6_1, + 5, + 5 +}; + +static static_codebook _44u9_p6_1 = { + 2, 25, + _vq_lengthlist__44u9_p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__44u9_p6_1, + NULL, + &_vq_auxt__44u9_p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p7_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__44u9_p7_0[] = { + 1, 5, 4, 6, 6, 7, 7, 8, 8, 9, 9,10,10, 5, 6, 6, + 7, 7, 8, 8, 9, 9,10,10,11,11, 5, 6, 6, 7, 7, 8, + 8, 9, 9,10,10,11,11, 6, 7, 7, 8, 8, 9, 9,10,10, + 11,11,12,12, 7, 7, 7, 8, 8, 9, 9,10,10,11,11,12, + 12, 8, 8, 8, 9, 9,10,10,11,10,12,12,13,12, 8, 8, + 8, 9, 9,10,10,11,11,12,12,12,13, 9, 9, 9,10,10, + 11,11,12,11,13,13,13,14, 9, 9, 9,10,10,11,11,11, + 12,13,12,13,13,10,10,10,11,11,12,12,13,12,13,13, + 14,14,10,10,10,11,11,11,12,12,12,13,13,14,14,11, + 11,11,12,12,13,13,13,13,14,14,15,14,11,11,11,12, + 12,13,13,13,14,14,15,15,15, +}; + +static float _vq_quantthresh__44u9_p7_0[] = { + -60.5, -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, + 27.5, 38.5, 49.5, 60.5, +}; + +static long _vq_quantmap__44u9_p7_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p7_0 = { + _vq_quantthresh__44u9_p7_0, + _vq_quantmap__44u9_p7_0, + 13, + 13 +}; + +static static_codebook _44u9_p7_0 = { + 2, 169, + _vq_lengthlist__44u9_p7_0, + 1, -523206656, 1618345984, 4, 0, + _vq_quantlist__44u9_p7_0, + NULL, + &_vq_auxt__44u9_p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__44u9_p7_1[] = { + 5, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 8, 8, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 8, 7, 7, + 7, 7, 7, 7, 7, 7, 8, 8, 8, +}; + +static float _vq_quantthresh__44u9_p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__44u9_p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p7_1 = { + _vq_quantthresh__44u9_p7_1, + _vq_quantmap__44u9_p7_1, + 11, + 11 +}; + +static static_codebook _44u9_p7_1 = { + 2, 121, + _vq_lengthlist__44u9_p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__44u9_p7_1, + NULL, + &_vq_auxt__44u9_p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p8_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u9_p8_0[] = { + 1, 4, 4, 7, 7, 8, 8, 8, 7, 9, 9,10,10,11,11, 4, + 6, 6, 8, 8, 9, 9, 9, 9,10,10,11,10,12,11, 4, 6, + 6, 8, 8, 9,10, 9, 9,10,10,11,11,12,12, 7, 8, 8, + 10,10,11,11,11,10,11,11,12,12,13,12, 7, 8, 8,10, + 10,11,11,10,10,11,12,12,12,13,13, 8,10, 9,11,11, + 12,12,11,12,12,12,13,13,14,14, 8, 9, 9,11,11,12, + 12,11,12,12,13,13,13,14,14, 8, 9, 9,10,10,11,11, + 13,12,13,13,14,14,15,14, 8, 9, 9,10,10,11,12,12, + 13,13,13,14,14,14,15, 9,10,10,11,11,13,12,13,13, + 14,14,15,15,15,15, 9,10,10,11,12,12,12,13,13,14, + 15,14,15,15,15,10,11,11,12,12,13,13,14,14,15,15, + 15,16,16,15,10,11,11,12,12,13,14,14,14,15,14,15, + 16,16,17,11,12,12,13,13,14,14,15,14,16,15,15,16, + 16,16,11,12,12,13,13,14,14,14,15,15,15,16,16,17, + 16, +}; + +static float _vq_quantthresh__44u9_p8_0[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__44u9_p8_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p8_0 = { + _vq_quantthresh__44u9_p8_0, + _vq_quantmap__44u9_p8_0, + 15, + 15 +}; + +static static_codebook _44u9_p8_0 = { + 2, 225, + _vq_lengthlist__44u9_p8_0, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__44u9_p8_0, + NULL, + &_vq_auxt__44u9_p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p8_1[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__44u9_p8_1[] = { + 4, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 7, 7, 7, 7, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9, 9, 8, 8, + 8, 8, 8, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9, + 9, 9, 9, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9,10,10,10,10, 8, 8, 8, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10, 9, 9,10,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9, 9,10, + 10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, + 10,10,10, 9,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9,10, 9,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9,10,10,10,10,10,10,10,10,10, + 10, 9, 9, 9, 9, 9, 9, 9, 9, 9,10, 9,10,10,10,10, + 10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, + 9,10, 9,10,10,10,10,10,10,10,10,10,10,10,10,10, + 9, 9, 9, 9, 9, 9, 9, 9,10,10, 9,10,10,10,10,10, + 10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, + 9,10,10,10,10,10,10,10,10,10,10,10,10,10,10, 9, + 9, 9, 9, 9,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10, 9, 9, 9, 9, 9, 9,10,10, 9,10,10,10, + 10,10,10,10,10,10,10,10,10, +}; + +static float _vq_quantthresh__44u9_p8_1[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__44u9_p8_1[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p8_1 = { + _vq_quantthresh__44u9_p8_1, + _vq_quantmap__44u9_p8_1, + 21, + 21 +}; + +static static_codebook _44u9_p8_1 = { + 2, 441, + _vq_lengthlist__44u9_p8_1, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__44u9_p8_1, + NULL, + &_vq_auxt__44u9_p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p9_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__44u9_p9_0[] = { + 1, 5, 5,10,10,10,10,10,10,10,10,10,10,10,10, 5, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10, 5,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, +}; + +static float _vq_quantthresh__44u9_p9_0[] = { + -6051.5, -5120.5, -4189.5, -3258.5, -2327.5, -1396.5, -465.5, 465.5, + 1396.5, 2327.5, 3258.5, 4189.5, 5120.5, 6051.5, +}; + +static long _vq_quantmap__44u9_p9_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p9_0 = { + _vq_quantthresh__44u9_p9_0, + _vq_quantmap__44u9_p9_0, + 15, + 15 +}; + +static static_codebook _44u9_p9_0 = { + 2, 225, + _vq_lengthlist__44u9_p9_0, + 1, -510036736, 1631393792, 4, 0, + _vq_quantlist__44u9_p9_0, + NULL, + &_vq_auxt__44u9_p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p9_1[] = { + 9, + 8, + 10, + 7, + 11, + 6, + 12, + 5, + 13, + 4, + 14, + 3, + 15, + 2, + 16, + 1, + 17, + 0, + 18, +}; + +static long _vq_lengthlist__44u9_p9_1[] = { + 1, 4, 4, 7, 7, 8, 8, 7, 7, 9, 9,10,10,12,11,12, + 11,12,12, 4, 7, 6, 9, 9, 9, 9, 9, 9,10, 9,11, 9, + 11,11,13,12,14,12, 4, 6, 6, 9, 9, 9, 9, 8, 9,10, + 10,11,11,12,12,12,13,13,13, 7, 9, 8,11,11,11,11, + 10,10,11,11,12,12,14,13,14,14,15,13, 7, 9, 9,10, + 10,10,10,10,10,11,11,12,13,13,13,14,14,15,13, 8, + 9, 9,12,10,11,11,12,11,12,12,13,13,14,14,15,16, + 15,14, 8, 9, 9,10,11,12,11,11,12,13,12,14,14,13, + 15,16,15,14,16, 7, 9, 9,10,10,12,11,12,13,16,13, + 14,16,14,15,15,15,15,15, 7, 9, 9,10,11,11,12,12, + 13,14,16,14,14,16,16,14,14,14,15, 9,10,11,12,12, + 12,13,13,13,16,15,16,15,14,15,15,15,16,16, 9,10, + 11,12,13,13,14,13,14,15,14,14,16,16,16,16,16,16, + 16,10,11,11,13,13,15,13,15,13,14,16,16,16,16,16, + 16,15,14,16,11,11,12,12,13,13,15,13,15,14,15,16, + 16,16,16,16,16,16,16,12,13,13,13,13,15,14,15,16, + 16,16,16,14,16,16,16,16,16,16,13,12,12,14,14,13, + 15,14,16,15,16,16,16,16,16,16,16,16,15,12,12,13, + 13,14,16,15,16,15,16,16,16,16,16,16,16,16,16,16, + 12,13,13,14,15,14,14,16,16,15,16,16,16,16,16,16, + 15,16,16,13,14,14,13,14,14,15,16,15,16,16,16,16, + 16,16,16,16,15,16,12,13,13,14,14,14,14,16,16,16, + 16,16,16,15,16,16,16,16,16, +}; + +static float _vq_quantthresh__44u9_p9_1[] = { + -416.5, -367.5, -318.5, -269.5, -220.5, -171.5, -122.5, -73.5, + -24.5, 24.5, 73.5, 122.5, 171.5, 220.5, 269.5, 318.5, + 367.5, 416.5, +}; + +static long _vq_quantmap__44u9_p9_1[] = { + 17, 15, 13, 11, 9, 7, 5, 3, + 1, 0, 2, 4, 6, 8, 10, 12, + 14, 16, 18, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p9_1 = { + _vq_quantthresh__44u9_p9_1, + _vq_quantmap__44u9_p9_1, + 19, + 19 +}; + +static static_codebook _44u9_p9_1 = { + 2, 361, + _vq_lengthlist__44u9_p9_1, + 1, -518287360, 1622704128, 5, 0, + _vq_quantlist__44u9_p9_1, + NULL, + &_vq_auxt__44u9_p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__44u9_p9_2[] = { + 24, + 23, + 25, + 22, + 26, + 21, + 27, + 20, + 28, + 19, + 29, + 18, + 30, + 17, + 31, + 16, + 32, + 15, + 33, + 14, + 34, + 13, + 35, + 12, + 36, + 11, + 37, + 10, + 38, + 9, + 39, + 8, + 40, + 7, + 41, + 6, + 42, + 5, + 43, + 4, + 44, + 3, + 45, + 2, + 46, + 1, + 47, + 0, + 48, +}; + +static long _vq_lengthlist__44u9_p9_2[] = { + 2, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__44u9_p9_2[] = { + -23.5, -22.5, -21.5, -20.5, -19.5, -18.5, -17.5, -16.5, + -15.5, -14.5, -13.5, -12.5, -11.5, -10.5, -9.5, -8.5, + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, + 8.5, 9.5, 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, + 16.5, 17.5, 18.5, 19.5, 20.5, 21.5, 22.5, 23.5, +}; + +static long _vq_quantmap__44u9_p9_2[] = { + 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 48, +}; + +static encode_aux_threshmatch _vq_auxt__44u9_p9_2 = { + _vq_quantthresh__44u9_p9_2, + _vq_quantmap__44u9_p9_2, + 49, + 49 +}; + +static static_codebook _44u9_p9_2 = { + 1, 49, + _vq_lengthlist__44u9_p9_2, + 1, -526909440, 1611661312, 6, 0, + _vq_quantlist__44u9_p9_2, + NULL, + &_vq_auxt__44u9_p9_2, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8u0__p1_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 8, 8, 8,10,10, 7, + 10,10, 5, 8, 8, 7,10,10, 8,10,10, 4, 9, 8, 8,11, + 11, 8,11,11, 7,11,11,10,11,13,10,13,13, 7,11,11, + 10,13,12,10,13,13, 5, 9, 8, 8,11,11, 8,11,11, 7, + 11,11, 9,13,13,10,12,13, 7,11,11,10,13,13,10,13, + 11, +}; + +static float _vq_quantthresh__8u0__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__8u0__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p1_0 = { + _vq_quantthresh__8u0__p1_0, + _vq_quantmap__8u0__p1_0, + 3, + 3 +}; + +static static_codebook _8u0__p1_0 = { + 4, 81, + _vq_lengthlist__8u0__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__8u0__p1_0, + NULL, + &_vq_auxt__8u0__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8u0__p2_0[] = { + 2, 4, 4, 5, 6, 6, 5, 6, 6, 5, 7, 7, 6, 7, 8, 6, + 7, 8, 5, 7, 7, 6, 8, 8, 7, 9, 7, 5, 7, 7, 7, 9, + 9, 7, 8, 8, 6, 9, 8, 7, 7,10, 8,10,10, 6, 8, 8, + 8,10, 8, 8,10,10, 5, 7, 7, 7, 8, 8, 7, 8, 9, 6, + 8, 8, 8,10,10, 8, 8,10, 6, 8, 9, 8,10,10, 7,10, + 8, +}; + +static float _vq_quantthresh__8u0__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__8u0__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p2_0 = { + _vq_quantthresh__8u0__p2_0, + _vq_quantmap__8u0__p2_0, + 3, + 3 +}; + +static static_codebook _8u0__p2_0 = { + 4, 81, + _vq_lengthlist__8u0__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__8u0__p2_0, + NULL, + &_vq_auxt__8u0__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8u0__p3_0[] = { + 1, 5, 5, 7, 7, 6, 7, 7, 9, 9, 6, 7, 7, 9, 9, 8, + 10, 9,11,11, 8, 9, 9,11,11, 6, 8, 8,10,10, 8,10, + 10,11,11, 8,10,10,11,11,10,11,11,12,12,10,11,11, + 12,13, 6, 8, 8,10,10, 8,10,10,11,11, 8,10,10,11, + 11, 9,10,11,12,12,10,11,11,12,12, 8,11,11,14,13, + 10,12,11,15,13,10,12,11,14,14,12,13,12,16,14,12, + 14,12,16,15, 8,11,11,13,14,10,11,12,13,15,10,11, + 12,13,15,11,12,13,14,15,12,12,14,14,16, 5, 8, 8, + 11,11, 9,11,11,12,12, 8,10,11,12,12,11,12,12,15, + 14,11,12,12,14,14, 7,11,10,13,12,10,11,12,13,14, + 10,12,12,14,13,12,13,13,14,15,12,13,13,15,15, 7, + 10,11,12,13,10,12,11,14,13,10,12,13,13,15,12,13, + 12,14,14,11,13,13,15,16, 9,12,12,15,14,11,13,13, + 15,16,11,13,13,16,16,13,14,15,15,15,12,14,15,17, + 16, 9,12,12,14,15,11,13,13,15,16,11,13,13,16,18, + 13,14,14,17,16,13,15,15,17,18, 5, 8, 9,11,11, 8, + 11,11,12,12, 8,10,11,12,12,11,12,12,14,14,11,12, + 12,14,15, 7,11,10,12,13,10,12,12,14,13,10,11,12, + 13,14,11,13,13,15,14,12,13,13,14,15, 7,10,11,13, + 13,10,12,12,13,14,10,12,12,13,13,11,13,13,16,16, + 12,13,13,15,14, 9,12,12,16,15,10,13,13,15,15,11, + 13,13,17,15,12,15,15,18,17,13,14,14,15,16, 9,12, + 12,15,15,11,13,13,15,16,11,13,13,15,15,12,15,15, + 16,16,13,15,14,17,15, 7,11,11,15,15,10,13,13,16, + 15,10,13,13,15,16,14,15,15,17,19,13,15,14,15,18, + 9,12,12,16,16,11,13,14,17,16,11,13,13,17,16,15, + 15,16,17,19,13,15,16, 0,18, 9,12,12,16,15,11,14, + 13,17,17,11,13,14,16,16,15,16,16,19,18,13,15,15, + 17,19,11,14,14,19,16,12,14,15, 0,18,12,16,15,18, + 17,15,15,18,16,19,14,15,17,19,19,11,14,14,18,19, + 13,15,14,19,19,12,16,15,18,17,15,17,15, 0,16,14, + 17,16,19, 0, 7,11,11,14,14,10,12,12,15,15,10,13, + 13,16,15,13,15,15,17, 0,14,15,15,16,19, 9,12,12, + 16,16,11,14,14,16,16,11,13,13,16,16,14,17,16,19, + 0,14,18,17,17,19, 9,12,12,15,16,11,13,13,15,17, + 12,14,13,19,16,13,15,15,17,19,15,17,16,17,19,11, + 14,14,19,16,12,15,15,19,17,13,14,15,17,19,14,16, + 17,19,19,16,15,16,17,19,11,15,14,16,16,12,15,15, + 19, 0,12,14,15,19,19,14,16,16, 0,18,15,19,14,18, + 16, +}; + +static float _vq_quantthresh__8u0__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8u0__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p3_0 = { + _vq_quantthresh__8u0__p3_0, + _vq_quantmap__8u0__p3_0, + 5, + 5 +}; + +static static_codebook _8u0__p3_0 = { + 4, 625, + _vq_lengthlist__8u0__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8u0__p3_0, + NULL, + &_vq_auxt__8u0__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8u0__p4_0[] = { + 3, 5, 5, 8, 8, 5, 6, 7, 9, 9, 6, 7, 6, 9, 9, 9, + 9, 9,10,11, 9, 9, 9,11,10, 6, 7, 7,10,10, 7, 7, + 8,10,10, 7, 8, 8,10,10,10,10,10,10,11, 9,10,10, + 11,12, 6, 7, 7,10,10, 7, 8, 8,10,10, 7, 8, 7,10, + 10, 9,10,10,12,11,10,10,10,11,10, 9,10,10,12,11, + 10,10,10,13,11, 9,10,10,12,12,11,11,12,12,13,11, + 11,11,12,13, 9,10,10,12,12,10,10,11,12,12,10,10, + 11,12,12,11,11,11,13,13,11,12,12,13,13, 5, 7, 7, + 10,10, 7, 8, 8,10,10, 7, 8, 8,10,10,10,11,11,12, + 12,10,11,10,12,12, 7, 8, 8,11,11, 7, 8, 9,10,11, + 8, 9, 9,11,11,11,10,11,10,12,10,11,11,12,13, 7, + 8, 8,10,11, 8, 9, 8,12,10, 8, 9, 9,11,12,10,11, + 10,13,11,10,11,11,13,12, 9,11,10,13,12,10,10,11, + 12,12,10,11,11,13,13,12,10,13,11,14,11,12,12,15, + 13, 9,11,11,13,13,10,11,11,13,12,10,11,11,12,14, + 12,13,11,14,12,12,12,12,14,14, 5, 7, 7,10,10, 7, + 8, 8,10,10, 7, 8, 8,11,10,10,11,11,12,12,10,11, + 10,12,12, 7, 8, 8,10,11, 8, 9, 9,12,11, 8, 8, 9, + 10,11,10,11,11,12,13,11,10,11,11,13, 6, 8, 8,10, + 11, 8, 9, 9,11,11, 7, 9, 7,11,10,10,11,11,12,12, + 10,11,10,13,10, 9,11,10,13,12,10,12,11,13,13,10, + 10,11,12,13,11,12,13,15,14,11,11,13,12,13, 9,10, + 11,12,13,10,11,11,12,13,10,11,10,13,12,12,13,13, + 13,14,12,12,11,14,11, 8,10,10,12,13,10,11,11,13, + 13,10,11,10,13,13,12,13,14,15,14,12,12,12,14,13, + 9,10,10,13,12,10,10,12,13,13,10,11,11,15,12,12, + 12,13,15,14,12,13,13,15,13, 9,10,11,12,13,10,12, + 10,13,12,10,11,11,12,13,12,14,12,15,13,12,12,12, + 15,14,11,12,11,14,13,11,11,12,14,14,12,13,13,14, + 13,13,11,15,11,15,14,14,14,16,15,11,12,12,13,14, + 11,13,11,14,14,12,12,13,14,15,12,14,12,15,12,13, + 15,14,16,15, 8,10,10,12,12,10,10,10,12,13,10,11, + 11,13,13,12,12,12,13,14,13,13,13,15,15, 9,10,10, + 12,12,10,11,11,13,12,10,10,11,13,13,12,12,12,14, + 14,12,12,13,15,14, 9,10,10,13,12,10,10,12,12,13, + 10,11,10,13,13,12,13,13,14,14,12,13,12,14,13,11, + 12,12,14,13,12,13,12,14,14,10,12,12,14,14,14,14, + 14,16,14,13,12,14,12,15,10,12,12,14,15,12,13,13, + 14,16,11,12,11,15,14,13,14,14,14,15,13,14,11,14, + 12, +}; + +static float _vq_quantthresh__8u0__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8u0__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p4_0 = { + _vq_quantthresh__8u0__p4_0, + _vq_quantmap__8u0__p4_0, + 5, + 5 +}; + +static static_codebook _8u0__p4_0 = { + 4, 625, + _vq_lengthlist__8u0__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8u0__p4_0, + NULL, + &_vq_auxt__8u0__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__8u0__p5_0[] = { + 1, 4, 4, 7, 7, 7, 7, 9, 9, 4, 6, 6, 8, 7, 8, 8, + 10,10, 4, 6, 6, 8, 8, 8, 8,10,10, 6, 8, 8, 9, 9, + 9, 9,11,11, 7, 8, 8, 9, 9, 9, 9,11,11, 7, 8, 8, + 9, 9,10,10,12,11, 7, 8, 8, 9, 9,10,10,11,11, 9, + 10,10,11,11,11,12,12,12, 9,10,10,11,11,12,12,12, + 12, +}; + +static float _vq_quantthresh__8u0__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__8u0__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p5_0 = { + _vq_quantthresh__8u0__p5_0, + _vq_quantmap__8u0__p5_0, + 9, + 9 +}; + +static static_codebook _8u0__p5_0 = { + 2, 81, + _vq_lengthlist__8u0__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__8u0__p5_0, + NULL, + &_vq_auxt__8u0__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p6_0[] = { + 6, + 5, + 7, + 4, + 8, + 3, + 9, + 2, + 10, + 1, + 11, + 0, + 12, +}; + +static long _vq_lengthlist__8u0__p6_0[] = { + 1, 4, 4, 7, 7, 9, 9,11,11,12,12,16,16, 3, 6, 6, + 9, 9,11,11,12,12,13,14,18,16, 3, 6, 7, 9, 9,11, + 11,13,12,14,14,17,16, 7, 9, 9,11,11,12,12,14,14, + 14,14,17,16, 7, 9, 9,11,11,13,12,13,13,14,14,17, + 0, 9,11,11,12,13,14,14,14,13,15,14,17,17, 9,11, + 11,12,12,14,14,13,14,14,15, 0, 0,11,12,12,15,14, + 15,14,15,14,15,16,17, 0,11,12,13,13,13,14,14,15, + 14,15,15, 0, 0,12,14,14,15,15,14,16,15,15,17,16, + 0,18,13,14,14,15,14,15,14,15,16,17,16, 0, 0,17, + 17,18, 0,16,18,16, 0, 0, 0,17, 0, 0,16, 0, 0,16, + 16, 0,15, 0,17, 0, 0, 0, 0, +}; + +static float _vq_quantthresh__8u0__p6_0[] = { + -27.5, -22.5, -17.5, -12.5, -7.5, -2.5, 2.5, 7.5, + 12.5, 17.5, 22.5, 27.5, +}; + +static long _vq_quantmap__8u0__p6_0[] = { + 11, 9, 7, 5, 3, 1, 0, 2, + 4, 6, 8, 10, 12, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p6_0 = { + _vq_quantthresh__8u0__p6_0, + _vq_quantmap__8u0__p6_0, + 13, + 13 +}; + +static static_codebook _8u0__p6_0 = { + 2, 169, + _vq_lengthlist__8u0__p6_0, + 1, -526516224, 1616117760, 4, 0, + _vq_quantlist__8u0__p6_0, + NULL, + &_vq_auxt__8u0__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p6_1[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8u0__p6_1[] = { + 1, 4, 4, 6, 6, 4, 6, 5, 7, 7, 4, 5, 6, 7, 7, 6, + 7, 7, 7, 7, 6, 7, 7, 7, 7, +}; + +static float _vq_quantthresh__8u0__p6_1[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8u0__p6_1[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p6_1 = { + _vq_quantthresh__8u0__p6_1, + _vq_quantmap__8u0__p6_1, + 5, + 5 +}; + +static static_codebook _8u0__p6_1 = { + 2, 25, + _vq_lengthlist__8u0__p6_1, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8u0__p6_1, + NULL, + &_vq_auxt__8u0__p6_1, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8u0__p7_0[] = { + 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, +}; + +static float _vq_quantthresh__8u0__p7_0[] = { + -157.5, 157.5, +}; + +static long _vq_quantmap__8u0__p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p7_0 = { + _vq_quantthresh__8u0__p7_0, + _vq_quantmap__8u0__p7_0, + 3, + 3 +}; + +static static_codebook _8u0__p7_0 = { + 4, 81, + _vq_lengthlist__8u0__p7_0, + 1, -518803456, 1628680192, 2, 0, + _vq_quantlist__8u0__p7_0, + NULL, + &_vq_auxt__8u0__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p7_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__8u0__p7_1[] = { + 1, 5, 5, 5, 5,10,10,11,11,11,11,11,11,11,11, 5, + 7, 6, 8, 8, 9,10,11,11,11,11,11,11,11,11, 6, 6, + 7, 9, 7,11,10,11,11,11,11,11,11,11,11, 5, 6, 6, + 11, 8,11,11,11,11,11,11,11,11,11,11, 5, 6, 6, 9, + 10,11,10,11,11,11,11,11,11,11,11, 7,10,10,11,11, + 11,11,11,11,11,11,11,11,11,11, 7,11, 8,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__8u0__p7_1[] = { + -136.5, -115.5, -94.5, -73.5, -52.5, -31.5, -10.5, 10.5, + 31.5, 52.5, 73.5, 94.5, 115.5, 136.5, +}; + +static long _vq_quantmap__8u0__p7_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p7_1 = { + _vq_quantthresh__8u0__p7_1, + _vq_quantmap__8u0__p7_1, + 15, + 15 +}; + +static static_codebook _8u0__p7_1 = { + 2, 225, + _vq_lengthlist__8u0__p7_1, + 1, -520986624, 1620377600, 4, 0, + _vq_quantlist__8u0__p7_1, + NULL, + &_vq_auxt__8u0__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__8u0__p7_2[] = { + 10, + 9, + 11, + 8, + 12, + 7, + 13, + 6, + 14, + 5, + 15, + 4, + 16, + 3, + 17, + 2, + 18, + 1, + 19, + 0, + 20, +}; + +static long _vq_lengthlist__8u0__p7_2[] = { + 1, 6, 5, 7, 7, 9, 9, 9, 9,10,12,12,10,11,11,10, + 11,11,11,10,11, 6, 8, 8, 9, 9,10,10, 9,10,11,11, + 10,11,11,11,11,10,11,11,11,11, 6, 7, 8, 9, 9, 9, + 10,11,10,11,12,11,10,11,11,11,11,11,11,12,10, 8, + 9, 9,10, 9,10,10, 9,10,10,10,10,10, 9,10,10,10, + 10, 9,10,10, 9, 9, 9, 9,10,10, 9, 9,10,10,11,10, + 9,12,10,11,10, 9,10,10,10, 8, 9, 9,10, 9,10, 9, + 9,10,10, 9,10, 9,11,10,10,10,10,10, 9,10, 8, 8, + 9, 9,10, 9,11, 9, 8, 9, 9,10,11,10,10,10,11,12, + 9, 9,11, 8, 9, 8,11,10,11,10,10, 9,11,10,10,10, + 10,10,10,10,11,11,11,11, 8, 9, 9, 9,10,10,10,11, + 11,12,11,12,11,10,10,10,12,11,11,11,10, 8,10, 9, + 11,10,10,11,12,10,11,12,11,11,12,11,12,12,10,11, + 11,10, 9, 9,10,11,12,10,10,10,11,10,11,11,10,12, + 12,10,11,10,11,12,10, 9,10,10,11,10,11,11,11,11, + 11,12,11,11,11, 9,11,10,11,10,11,10, 9, 9,10,11, + 11,11,10,10,11,12,12,11,12,11,11,11,12,12,12,12, + 11, 9,11,11,12,10,11,11,11,11,11,11,12,11,11,12, + 11,11,11,10,11,11, 9,11,10,11,11,11,10,10,10,11, + 11,11,12,10,11,10,11,11,11,11,12, 9,11,10,11,11, + 10,10,11,11, 9,11,11,12,10,10,10,10,10,11,11,10, + 9,10,11,11,12,11,10,10,12,11,11,12,11,12,11,11, + 10,10,11,11,10,12,11,10,11,10,11,10,10,10,11,11, + 10,10,11,11,11,11,10,10,10,12,11,11,11,11,10, 9, + 10,11,11,11,12,11,11,11,12,10,11,11,11, 9,10,11, + 11,11,11,11,11,10,10,11,11,12,11,10,11,12,11,10, + 10,11, 9,10,11,11,11,11,11,10,11,11,10,12,11,11, + 11,12,11,11,11,10,10,11,11, +}; + +static float _vq_quantthresh__8u0__p7_2[] = { + -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, + -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, + 6.5, 7.5, 8.5, 9.5, +}; + +static long _vq_quantmap__8u0__p7_2[] = { + 19, 17, 15, 13, 11, 9, 7, 5, + 3, 1, 0, 2, 4, 6, 8, 10, + 12, 14, 16, 18, 20, +}; + +static encode_aux_threshmatch _vq_auxt__8u0__p7_2 = { + _vq_quantthresh__8u0__p7_2, + _vq_quantmap__8u0__p7_2, + 21, + 21 +}; + +static static_codebook _8u0__p7_2 = { + 2, 441, + _vq_lengthlist__8u0__p7_2, + 1, -529268736, 1611661312, 5, 0, + _vq_quantlist__8u0__p7_2, + NULL, + &_vq_auxt__8u0__p7_2, + NULL, + 0 +}; + +static long _huff_lengthlist__8u0__single[] = { + 4, 7,11, 9,12, 8, 7,10, 6, 4, 5, 5, 7, 5, 6,16, + 9, 5, 5, 6, 7, 7, 9,16, 7, 4, 6, 5, 7, 5, 7,17, + 10, 7, 7, 8, 7, 7, 8,18, 7, 5, 6, 4, 5, 4, 5,15, + 7, 6, 7, 5, 6, 4, 5,15,12,13,18,12,17,11, 9,17, +}; + +static static_codebook _huff_book__8u0__single = { + 2, 64, + _huff_lengthlist__8u0__single, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p1_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8u1__p1_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 8, 8, 7, 9,10, 7, + 9, 9, 5, 8, 8, 7,10, 9, 7, 9, 9, 5, 8, 8, 8,10, + 10, 8,10,10, 7,10,10, 9,10,12,10,12,12, 7,10,10, + 9,12,11,10,12,12, 5, 8, 8, 8,10,10, 8,10,10, 7, + 10,10,10,12,12, 9,11,12, 7,10,10,10,12,12, 9,12, + 10, +}; + +static float _vq_quantthresh__8u1__p1_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__8u1__p1_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p1_0 = { + _vq_quantthresh__8u1__p1_0, + _vq_quantmap__8u1__p1_0, + 3, + 3 +}; + +static static_codebook _8u1__p1_0 = { + 4, 81, + _vq_lengthlist__8u1__p1_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__8u1__p1_0, + NULL, + &_vq_auxt__8u1__p1_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p2_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8u1__p2_0[] = { + 3, 4, 5, 5, 6, 6, 5, 6, 6, 5, 7, 6, 6, 7, 8, 6, + 7, 8, 5, 6, 6, 6, 8, 7, 6, 8, 7, 5, 6, 6, 7, 8, + 8, 6, 7, 7, 6, 8, 7, 7, 7, 9, 8, 9, 9, 6, 7, 8, + 7, 9, 7, 8, 9, 9, 5, 6, 6, 6, 7, 7, 7, 8, 8, 6, + 8, 7, 8, 9, 9, 7, 7, 9, 6, 7, 8, 8, 9, 9, 7, 9, + 7, +}; + +static float _vq_quantthresh__8u1__p2_0[] = { + -0.5, 0.5, +}; + +static long _vq_quantmap__8u1__p2_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p2_0 = { + _vq_quantthresh__8u1__p2_0, + _vq_quantmap__8u1__p2_0, + 3, + 3 +}; + +static static_codebook _8u1__p2_0 = { + 4, 81, + _vq_lengthlist__8u1__p2_0, + 1, -535822336, 1611661312, 2, 0, + _vq_quantlist__8u1__p2_0, + NULL, + &_vq_auxt__8u1__p2_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p3_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8u1__p3_0[] = { + 1, 5, 5, 7, 7, 6, 7, 7, 9, 9, 6, 7, 7, 9, 9, 8, + 10, 9,11,11, 9, 9, 9,11,11, 6, 8, 8,10,10, 8,10, + 10,11,11, 8, 9,10,11,11,10,11,11,12,12,10,11,11, + 12,13, 6, 8, 8,10,10, 8,10, 9,11,11, 8,10, 9,11, + 11,10,11,11,12,12,10,11,11,12,12, 9,11,11,14,13, + 10,12,11,14,14,10,12,11,14,13,12,13,13,15,14,12, + 13,13,15,14, 8,11,11,13,14,10,11,12,13,15,10,11, + 12,14,14,12,13,13,14,15,12,13,13,14,15, 5, 8, 8, + 11,11, 8,10,10,12,12, 8,10,10,12,12,11,12,12,14, + 13,11,12,12,13,14, 8,10,10,12,12, 9,11,12,13,14, + 10,12,12,13,13,12,12,13,14,14,11,13,13,15,15, 7, + 10,10,12,12, 9,12,11,14,12,10,11,12,13,14,12,13, + 12,14,14,12,13,13,15,16,10,12,12,15,14,11,12,13, + 15,15,11,13,13,15,16,14,14,15,15,16,13,14,15,17, + 15, 9,12,12,14,15,11,13,12,15,15,11,13,13,15,15, + 13,14,13,15,14,13,14,14,17, 0, 5, 8, 8,11,11, 8, + 10,10,12,12, 8,10,10,12,12,11,12,12,14,14,11,12, + 12,14,14, 7,10,10,12,12,10,12,12,13,13, 9,11,12, + 12,13,11,12,13,15,15,11,12,13,14,15, 8,10,10,12, + 12,10,12,11,13,13,10,12,11,13,13,11,13,13,15,14, + 12,13,12,15,13, 9,12,12,14,14,11,13,13,16,15,11, + 12,13,16,15,13,14,15,16,16,13,13,15,15,16,10,12, + 12,15,14,11,13,13,14,16,11,13,13,15,16,13,15,15, + 16,17,13,15,14,16,15, 8,11,11,14,15,10,12,12,15, + 15,10,12,12,15,16,14,15,15,16,17,13,14,14,16,16, + 9,12,12,15,15,11,13,14,15,17,11,13,13,15,16,14, + 15,16,19,17,13,15,15, 0,17, 9,12,12,15,15,11,14, + 13,16,15,11,13,13,15,16,15,15,15,18,17,13,15,15, + 17,17,11,15,14,18,16,12,14,15,17,17,12,15,15,18, + 18,15,15,16,15,19,14,16,16, 0, 0,11,14,14,16,17, + 12,15,14,18,17,12,15,15,18,18,15,17,15,18,16,14, + 16,16,18,18, 7,11,11,14,14,10,12,12,15,15,10,12, + 13,15,15,13,14,15,16,16,14,15,15,18,18, 9,12,12, + 15,15,11,13,13,16,15,11,12,13,16,16,14,15,15,17, + 16,15,16,16,17,17, 9,12,12,15,15,11,13,13,15,17, + 11,14,13,16,15,13,15,15,17,17,15,15,15,18,17,11, + 14,14,17,15,12,14,15,17,18,13,13,15,17,17,14,16, + 16,19,18,16,15,17,17, 0,11,14,14,17,17,12,15,15, + 18, 0,12,15,14,18,16,14,17,17,19, 0,16,18,15, 0, + 16, +}; + +static float _vq_quantthresh__8u1__p3_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8u1__p3_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p3_0 = { + _vq_quantthresh__8u1__p3_0, + _vq_quantmap__8u1__p3_0, + 5, + 5 +}; + +static static_codebook _8u1__p3_0 = { + 4, 625, + _vq_lengthlist__8u1__p3_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8u1__p3_0, + NULL, + &_vq_auxt__8u1__p3_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p4_0[] = { + 2, + 1, + 3, + 0, + 4, +}; + +static long _vq_lengthlist__8u1__p4_0[] = { + 4, 5, 5, 9, 9, 6, 7, 7, 9, 9, 6, 7, 7, 9, 9, 9, + 9, 9,11,11, 9, 9, 9,11,11, 6, 7, 7, 9, 9, 7, 7, + 8, 9,10, 7, 7, 8, 9,10, 9, 9,10,10,11, 9, 9,10, + 10,12, 6, 7, 7, 9, 9, 7, 8, 7,10, 9, 7, 8, 7,10, + 9, 9,10, 9,12,11,10,10, 9,12,10, 9,10,10,12,11, + 9,10,10,12,11, 9,10,10,12,12,11,11,12,12,13,11, + 11,12,12,13, 9, 9,10,12,11, 9,10,10,12,12,10,10, + 10,12,12,11,12,11,13,12,11,12,11,13,12, 6, 7, 7, + 9, 9, 7, 8, 8,10,10, 7, 8, 7,10, 9,10,10,10,12, + 12,10,10,10,12,11, 7, 8, 7,10,10, 7, 7, 9,10,11, + 8, 9, 9,11,10,10,10,11,10,12,10,10,11,12,12, 7, + 8, 8,10,10, 7, 9, 8,11,10, 8, 8, 9,11,11,10,11, + 10,12,11,10,11,11,12,12, 9,10,10,12,12, 9,10,10, + 12,12,10,11,11,13,12,11,10,12,10,14,12,12,12,13, + 14, 9,10,10,12,12, 9,11,10,12,12,10,11,11,12,12, + 11,12,11,14,12,12,12,12,14,14, 5, 7, 7, 9, 9, 7, + 7, 7, 9,10, 7, 8, 8,10,10,10,10,10,11,11,10,10, + 10,12,12, 7, 8, 8,10,10, 8, 9, 8,11,10, 7, 8, 9, + 10,11,10,10,10,11,12,10,10,11,11,13, 6, 7, 8,10, + 10, 8, 9, 9,10,10, 7, 9, 7,11,10,10,11,10,12,12, + 10,11,10,12,10, 9,10,10,12,12,10,11,11,13,12, 9, + 10,10,12,12,12,12,12,14,13,11,11,12,11,14, 9,10, + 10,11,12,10,11,11,12,13, 9,10,10,12,12,12,12,12, + 14,13,11,12,10,14,11, 9, 9,10,11,12, 9,10,10,12, + 12, 9,10,10,12,12,12,12,12,14,14,11,12,12,13,12, + 9,10, 9,12,12, 9,10,11,12,13,10,11,10,13,11,12, + 12,13,13,14,12,12,12,13,13, 9,10,10,12,12,10,11, + 10,13,12,10,10,11,12,13,12,13,12,14,13,12,12,12, + 13,14,11,12,11,14,13,10,10,11,13,13,12,12,12,14, + 13,12,10,14,10,15,13,14,14,14,14,11,11,12,13,14, + 10,12,11,13,13,12,12,12,13,15,12,13,11,15,12,13, + 13,14,14,14, 9,10, 9,12,12, 9,10,10,12,12,10,10, + 10,12,12,11,11,12,12,13,12,12,12,14,14, 9,10,10, + 12,12,10,11,10,13,12,10,10,11,12,13,12,12,12,14, + 13,12,12,13,13,14, 9,10,10,12,13,10,10,11,11,12, + 9,11,10,13,12,12,12,12,13,14,12,13,12,14,13,11, + 12,11,13,13,12,13,12,14,13,10,11,12,13,13,13,13, + 13,14,15,12,11,14,12,14,11,11,12,12,13,12,12,12, + 13,14,10,12,10,14,13,13,13,13,14,15,12,14,11,15, + 10, +}; + +static float _vq_quantthresh__8u1__p4_0[] = { + -1.5, -0.5, 0.5, 1.5, +}; + +static long _vq_quantmap__8u1__p4_0[] = { + 3, 1, 0, 2, 4, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p4_0 = { + _vq_quantthresh__8u1__p4_0, + _vq_quantmap__8u1__p4_0, + 5, + 5 +}; + +static static_codebook _8u1__p4_0 = { + 4, 625, + _vq_lengthlist__8u1__p4_0, + 1, -533725184, 1611661312, 3, 0, + _vq_quantlist__8u1__p4_0, + NULL, + &_vq_auxt__8u1__p4_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p5_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__8u1__p5_0[] = { + 1, 4, 4, 7, 7, 7, 7, 9, 9, 4, 6, 5, 8, 7, 8, 8, + 10,10, 4, 6, 6, 8, 8, 8, 8,10,10, 7, 8, 8, 9, 9, + 9, 9,11,11, 7, 8, 8, 9, 9, 9, 9,11,11, 8, 8, 8, + 9, 9,10,10,12,11, 8, 8, 8, 9, 9,10,10,11,11, 9, + 10,10,11,11,11,11,13,12, 9,10,10,11,11,12,12,12, + 13, +}; + +static float _vq_quantthresh__8u1__p5_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__8u1__p5_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p5_0 = { + _vq_quantthresh__8u1__p5_0, + _vq_quantmap__8u1__p5_0, + 9, + 9 +}; + +static static_codebook _8u1__p5_0 = { + 2, 81, + _vq_lengthlist__8u1__p5_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__8u1__p5_0, + NULL, + &_vq_auxt__8u1__p5_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p6_0[] = { + 4, + 3, + 5, + 2, + 6, + 1, + 7, + 0, + 8, +}; + +static long _vq_lengthlist__8u1__p6_0[] = { + 3, 4, 4, 6, 6, 7, 7, 9, 9, 4, 4, 5, 6, 6, 7, 7, + 9, 9, 4, 4, 4, 6, 6, 7, 7, 9, 9, 6, 6, 6, 7, 7, + 8, 8, 9, 9, 6, 6, 6, 7, 7, 8, 8, 9, 9, 7, 7, 7, + 8, 8, 8, 9,10,10, 7, 7, 7, 8, 8, 9, 8,10,10, 9, + 9, 9, 9, 9,10,10,10,10, 9, 9, 9, 9, 9,10,10,10, + 10, +}; + +static float _vq_quantthresh__8u1__p6_0[] = { + -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, +}; + +static long _vq_quantmap__8u1__p6_0[] = { + 7, 5, 3, 1, 0, 2, 4, 6, + 8, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p6_0 = { + _vq_quantthresh__8u1__p6_0, + _vq_quantmap__8u1__p6_0, + 9, + 9 +}; + +static static_codebook _8u1__p6_0 = { + 2, 81, + _vq_lengthlist__8u1__p6_0, + 1, -531628032, 1611661312, 4, 0, + _vq_quantlist__8u1__p6_0, + NULL, + &_vq_auxt__8u1__p6_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p7_0[] = { + 1, + 0, + 2, +}; + +static long _vq_lengthlist__8u1__p7_0[] = { + 1, 4, 4, 5, 7, 7, 5, 7, 7, 5, 9, 9, 8,10,10, 8, + 10,10, 5, 9, 9, 7,10,10, 8,10,10, 4,10,10, 9,12, + 12, 9,11,11, 7,12,11,10,11,13,10,13,13, 7,12,12, + 10,13,12,10,13,13, 4,10,10, 9,12,12, 9,12,12, 7, + 12,12,10,13,13,10,12,13, 7,11,12,10,13,13,10,13, + 11, +}; + +static float _vq_quantthresh__8u1__p7_0[] = { + -5.5, 5.5, +}; + +static long _vq_quantmap__8u1__p7_0[] = { + 1, 0, 2, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p7_0 = { + _vq_quantthresh__8u1__p7_0, + _vq_quantmap__8u1__p7_0, + 3, + 3 +}; + +static static_codebook _8u1__p7_0 = { + 4, 81, + _vq_lengthlist__8u1__p7_0, + 1, -529137664, 1618345984, 2, 0, + _vq_quantlist__8u1__p7_0, + NULL, + &_vq_auxt__8u1__p7_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p7_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__8u1__p7_1[] = { + 2, 4, 4, 6, 6, 7, 7, 8, 8, 8, 8, 4, 5, 5, 7, 7, + 8, 8, 9, 9, 9, 9, 4, 5, 5, 7, 7, 8, 8, 9, 9, 9, + 9, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 6, 7, 7, 8, + 8, 8, 8, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, 9, + 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 8, 9, 9, + 9, 9, 9, 9,10,10,10,10, 8, 9, 9, 9, 9, 9, 9,10, + 10,10,10, 8, 9, 9, 9, 9, 9, 9,10,10,10,10, 8, 9, + 9, 9, 9, 9, 9,10,10,10,10, +}; + +static float _vq_quantthresh__8u1__p7_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__8u1__p7_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p7_1 = { + _vq_quantthresh__8u1__p7_1, + _vq_quantmap__8u1__p7_1, + 11, + 11 +}; + +static static_codebook _8u1__p7_1 = { + 2, 121, + _vq_lengthlist__8u1__p7_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__8u1__p7_1, + NULL, + &_vq_auxt__8u1__p7_1, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p8_0[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__8u1__p8_0[] = { + 1, 4, 4, 6, 6, 8, 8,10,10,11,11, 4, 6, 6, 7, 7, + 9, 9,11,11,13,12, 4, 6, 6, 7, 7, 9, 9,11,11,12, + 12, 6, 7, 7, 9, 9,11,11,12,12,13,13, 6, 7, 7, 9, + 9,11,11,12,12,13,13, 8, 9, 9,11,11,12,12,13,13, + 14,14, 8, 9, 9,11,11,12,12,13,13,14,14, 9,11,11, + 12,12,13,13,14,14,15,15, 9,11,11,12,12,13,13,14, + 14,15,14,11,12,12,13,13,14,14,15,15,16,16,11,12, + 12,13,13,14,14,15,15,15,15, +}; + +static float _vq_quantthresh__8u1__p8_0[] = { + -49.5, -38.5, -27.5, -16.5, -5.5, 5.5, 16.5, 27.5, + 38.5, 49.5, +}; + +static long _vq_quantmap__8u1__p8_0[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p8_0 = { + _vq_quantthresh__8u1__p8_0, + _vq_quantmap__8u1__p8_0, + 11, + 11 +}; + +static static_codebook _8u1__p8_0 = { + 2, 121, + _vq_lengthlist__8u1__p8_0, + 1, -524582912, 1618345984, 4, 0, + _vq_quantlist__8u1__p8_0, + NULL, + &_vq_auxt__8u1__p8_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p8_1[] = { + 5, + 4, + 6, + 3, + 7, + 2, + 8, + 1, + 9, + 0, + 10, +}; + +static long _vq_lengthlist__8u1__p8_1[] = { + 2, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 5, 6, 6, 7, 7, + 7, 7, 8, 8, 8, 8, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 6, 7, 7, 7, + 7, 8, 8, 8, 8, 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, 8, + 8, 8, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 8, 9, 9, 7, 8, 8, 8, 8, 8, 8, 9, + 8, 9, 9, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, +}; + +static float _vq_quantthresh__8u1__p8_1[] = { + -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, + 3.5, 4.5, +}; + +static long _vq_quantmap__8u1__p8_1[] = { + 9, 7, 5, 3, 1, 0, 2, 4, + 6, 8, 10, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p8_1 = { + _vq_quantthresh__8u1__p8_1, + _vq_quantmap__8u1__p8_1, + 11, + 11 +}; + +static static_codebook _8u1__p8_1 = { + 2, 121, + _vq_lengthlist__8u1__p8_1, + 1, -531365888, 1611661312, 4, 0, + _vq_quantlist__8u1__p8_1, + NULL, + &_vq_auxt__8u1__p8_1, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p9_0[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__8u1__p9_0[] = { + 1, 4, 4,11,11,11,11,11,11,11,11,11,11,11,11, 3, + 11, 8,11,11,11,11,11,11,11,11,11,11,11,11, 3, 9, + 9,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__8u1__p9_0[] = { + -1657.5, -1402.5, -1147.5, -892.5, -637.5, -382.5, -127.5, 127.5, + 382.5, 637.5, 892.5, 1147.5, 1402.5, 1657.5, +}; + +static long _vq_quantmap__8u1__p9_0[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p9_0 = { + _vq_quantthresh__8u1__p9_0, + _vq_quantmap__8u1__p9_0, + 15, + 15 +}; + +static static_codebook _8u1__p9_0 = { + 2, 225, + _vq_lengthlist__8u1__p9_0, + 1, -514071552, 1627381760, 4, 0, + _vq_quantlist__8u1__p9_0, + NULL, + &_vq_auxt__8u1__p9_0, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p9_1[] = { + 7, + 6, + 8, + 5, + 9, + 4, + 10, + 3, + 11, + 2, + 12, + 1, + 13, + 0, + 14, +}; + +static long _vq_lengthlist__8u1__p9_1[] = { + 1, 4, 4, 7, 7, 9, 9, 7, 7, 8, 8,10,10,11,11, 4, + 7, 7, 9, 9,10,10, 8, 8,10,10,10,11,10,11, 4, 7, + 7, 9, 9,10,10, 8, 8,10, 9,11,11,11,11, 7, 9, 9, + 12,12,11,12,10,10,11,10,12,11,11,11, 7, 9, 9,11, + 11,13,12, 9, 9,11,10,11,11,12,11, 9,10,10,12,12, + 14,14,10,10,11,12,12,11,11,11, 9,10,11,11,13,14, + 13,10,11,11,11,12,11,12,12, 7, 8, 8,10, 9,11,10, + 11,12,12,11,12,14,12,13, 7, 8, 8, 9,10,10,11,12, + 12,12,11,12,12,12,13, 9, 9, 9,11,11,13,12,12,12, + 12,11,12,12,13,12, 8,10,10,11,10,11,12,12,12,12, + 12,12,14,12,12, 9,11,11,11,12,12,12,12,13,13,12, + 12,13,13,12,10,11,11,12,11,12,12,12,11,12,13,12, + 12,12,13,11,11,12,12,12,13,12,12,11,12,13,13,12, + 12,13,12,11,12,12,13,13,12,13,12,13,13,13,13,14, + 13, +}; + +static float _vq_quantthresh__8u1__p9_1[] = { + -110.5, -93.5, -76.5, -59.5, -42.5, -25.5, -8.5, 8.5, + 25.5, 42.5, 59.5, 76.5, 93.5, 110.5, +}; + +static long _vq_quantmap__8u1__p9_1[] = { + 13, 11, 9, 7, 5, 3, 1, 0, + 2, 4, 6, 8, 10, 12, 14, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p9_1 = { + _vq_quantthresh__8u1__p9_1, + _vq_quantmap__8u1__p9_1, + 15, + 15 +}; + +static static_codebook _8u1__p9_1 = { + 2, 225, + _vq_lengthlist__8u1__p9_1, + 1, -522338304, 1620115456, 4, 0, + _vq_quantlist__8u1__p9_1, + NULL, + &_vq_auxt__8u1__p9_1, + NULL, + 0 +}; + +static long _vq_quantlist__8u1__p9_2[] = { + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + 0, + 16, +}; + +static long _vq_lengthlist__8u1__p9_2[] = { + 2, 5, 4, 6, 6, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 5, 6, 6, 7, 7, 8, 8, 9, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 5, 6, 6, 7, 7, 8, 8, 8, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 7, 7, 7, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, + 9,10,10, 9, 7, 7, 7, 8, 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9,10,10, 8, 8, 8, 9, 9, 9, 9,10,10,10, 9, + 10,10,10,10,10,10, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, + 10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9,10,10,10, + 10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9,10, + 10,10,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, 9, + 9,10,10,10,10,10,10,10,10,10,10,10,10, 9, 9, 9, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10, 9,10, + 9, 9, 9,10,10,10,10,10,10,10,10,10,10,10,10, 9, + 10, 9,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 9, 9,10,10,10,10,10,10,10,10,10,10,10,10,10,10, + 10, +}; + +static float _vq_quantthresh__8u1__p9_2[] = { + -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, + 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, +}; + +static long _vq_quantmap__8u1__p9_2[] = { + 15, 13, 11, 9, 7, 5, 3, 1, + 0, 2, 4, 6, 8, 10, 12, 14, + 16, +}; + +static encode_aux_threshmatch _vq_auxt__8u1__p9_2 = { + _vq_quantthresh__8u1__p9_2, + _vq_quantmap__8u1__p9_2, + 17, + 17 +}; + +static static_codebook _8u1__p9_2 = { + 2, 289, + _vq_lengthlist__8u1__p9_2, + 1, -529530880, 1611661312, 5, 0, + _vq_quantlist__8u1__p9_2, + NULL, + &_vq_auxt__8u1__p9_2, + NULL, + 0 +}; + +static long _huff_lengthlist__8u1__single[] = { + 4, 7,13, 9,15, 9,16, 8,10,13, 7, 5, 8, 6, 9, 7, + 10, 7,10,11,11, 6, 7, 8, 8, 9, 9, 9,12,16, 8, 5, + 8, 6, 8, 6, 9, 7,10,12,11, 7, 7, 7, 6, 7, 7, 7, + 11,15, 7, 5, 8, 6, 7, 5, 7, 6, 9,13,13, 9, 9, 8, + 6, 6, 5, 5, 9,14, 8, 6, 8, 6, 6, 4, 5, 3, 5,13, + 9, 9,11, 8,10, 7, 8, 4, 5,12,11,16,17,15,17,12, + 13, 8, 8,15, +}; + +static static_codebook _huff_book__8u1__single = { + 2, 100, + _huff_lengthlist__8u1__single, + 0, 0, 0, 0, 0, + NULL, + NULL, + NULL, + NULL, + 0 +}; + diff --git a/src/sound/oggvorbis/vorbissrc/codebook.c b/src/sound/oggvorbis/vorbissrc/codebook.c new file mode 100644 index 0000000..d4b408d --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/codebook.c @@ -0,0 +1,639 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: basic codebook pack/unpack/code/decode operations + last mod: $Id: codebook.c,v 1.39 2002/06/28 22:19:35 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codebook.h" +#include "scales.h" +#include "misc.h" +#include "os.h" + +/* packs the given codebook into the bitstream **************************/ + +int vorbis_staticbook_pack(const static_codebook *c,oggpack_buffer *opb){ + long i,j; + int ordered=0; + + /* first the basic parameters */ + oggpack_write(opb,0x564342,24); + oggpack_write(opb,c->dim,16); + oggpack_write(opb,c->entries,24); + + /* pack the codewords. There are two packings; length ordered and + length random. Decide between the two now. */ + + for(i=1;ientries;i++) + if(c->lengthlist[i-1]==0 || c->lengthlist[i]lengthlist[i-1])break; + if(i==c->entries)ordered=1; + + if(ordered){ + /* length ordered. We only need to say how many codewords of + each length. The actual codewords are generated + deterministically */ + + long count=0; + oggpack_write(opb,1,1); /* ordered */ + oggpack_write(opb,c->lengthlist[0]-1,5); /* 1 to 32 */ + + for(i=1;ientries;i++){ + long this=c->lengthlist[i]; + long last=c->lengthlist[i-1]; + if(this>last){ + for(j=last;jentries-count)); + count=i; + } + } + } + oggpack_write(opb,i-count,_ilog(c->entries-count)); + + }else{ + /* length random. Again, we don't code the codeword itself, just + the length. This time, though, we have to encode each length */ + oggpack_write(opb,0,1); /* unordered */ + + /* algortihmic mapping has use for 'unused entries', which we tag + here. The algorithmic mapping happens as usual, but the unused + entry has no codeword. */ + for(i=0;ientries;i++) + if(c->lengthlist[i]==0)break; + + if(i==c->entries){ + oggpack_write(opb,0,1); /* no unused entries */ + for(i=0;ientries;i++) + oggpack_write(opb,c->lengthlist[i]-1,5); + }else{ + oggpack_write(opb,1,1); /* we have unused entries; thus we tag */ + for(i=0;ientries;i++){ + if(c->lengthlist[i]==0){ + oggpack_write(opb,0,1); + }else{ + oggpack_write(opb,1,1); + oggpack_write(opb,c->lengthlist[i]-1,5); + } + } + } + } + + /* is the entry number the desired return value, or do we have a + mapping? If we have a mapping, what type? */ + oggpack_write(opb,c->maptype,4); + switch(c->maptype){ + case 0: + /* no mapping */ + break; + case 1:case 2: + /* implicitly populated value mapping */ + /* explicitly populated value mapping */ + + if(!c->quantlist){ + /* no quantlist? error */ + return(-1); + } + + /* values that define the dequantization */ + oggpack_write(opb,c->q_min,32); + oggpack_write(opb,c->q_delta,32); + oggpack_write(opb,c->q_quant-1,4); + oggpack_write(opb,c->q_sequencep,1); + + { + int quantvals; + switch(c->maptype){ + case 1: + /* a single column of (c->entries/c->dim) quantized values for + building a full value list algorithmically (square lattice) */ + quantvals=_book_maptype1_quantvals(c); + break; + case 2: + /* every value (c->entries*c->dim total) specified explicitly */ + quantvals=c->entries*c->dim; + break; + default: /* NOT_REACHABLE */ + quantvals=-1; + } + + /* quantized values */ + for(i=0;iquantlist[i]),c->q_quant); + + } + break; + default: + /* error case; we don't have any other map types now */ + return(-1); + } + + return(0); +} + +/* unpacks a codebook from the packet buffer into the codebook struct, + readies the codebook auxiliary structures for decode *************/ +int vorbis_staticbook_unpack(oggpack_buffer *opb,static_codebook *s){ + long i,j; + memset(s,0,sizeof(*s)); + s->allocedp=1; + + /* make sure alignment is correct */ + if(oggpack_read(opb,24)!=0x564342)goto _eofout; + + /* first the basic parameters */ + s->dim=oggpack_read(opb,16); + s->entries=oggpack_read(opb,24); + if(s->entries==-1)goto _eofout; + + /* codeword ordering.... length ordered or unordered? */ + switch((int)oggpack_read(opb,1)){ + case 0: + /* unordered */ + s->lengthlist=_ogg_malloc(sizeof(*s->lengthlist)*s->entries); + + /* allocated but unused entries? */ + if(oggpack_read(opb,1)){ + /* yes, unused entries */ + + for(i=0;ientries;i++){ + if(oggpack_read(opb,1)){ + long num=oggpack_read(opb,5); + if(num==-1)goto _eofout; + s->lengthlist[i]=num+1; + }else + s->lengthlist[i]=0; + } + }else{ + /* all entries used; no tagging */ + for(i=0;ientries;i++){ + long num=oggpack_read(opb,5); + if(num==-1)goto _eofout; + s->lengthlist[i]=num+1; + } + } + + break; + case 1: + /* ordered */ + { + long length=oggpack_read(opb,5)+1; + s->lengthlist=_ogg_malloc(sizeof(*s->lengthlist)*s->entries); + + for(i=0;ientries;){ + long num=oggpack_read(opb,_ilog(s->entries-i)); + if(num==-1)goto _eofout; + for(j=0;jentries;j++,i++) + s->lengthlist[i]=length; + length++; + } + } + break; + default: + /* EOF */ + return(-1); + } + + /* Do we have a mapping to unpack? */ + switch((s->maptype=oggpack_read(opb,4))){ + case 0: + /* no mapping */ + break; + case 1: case 2: + /* implicitly populated value mapping */ + /* explicitly populated value mapping */ + + s->q_min=oggpack_read(opb,32); + s->q_delta=oggpack_read(opb,32); + s->q_quant=oggpack_read(opb,4)+1; + s->q_sequencep=oggpack_read(opb,1); + + { + int quantvals=0; + switch(s->maptype){ + case 1: + quantvals=_book_maptype1_quantvals(s); + break; + case 2: + quantvals=s->entries*s->dim; + break; + } + + /* quantized values */ + s->quantlist=_ogg_malloc(sizeof(*s->quantlist)*quantvals); + for(i=0;iquantlist[i]=oggpack_read(opb,s->q_quant); + + if(quantvals&&s->quantlist[quantvals-1]==-1)goto _eofout; + } + break; + default: + goto _errout; + } + + /* all set */ + return(0); + + _errout: + _eofout: + vorbis_staticbook_clear(s); + return(-1); +} + +/* returns the number of bits ************************************************/ +int vorbis_book_encode(codebook *book, int a, oggpack_buffer *b){ + oggpack_write(b,book->codelist[a],book->c->lengthlist[a]); + return(book->c->lengthlist[a]); +} + +/* One the encode side, our vector writers are each designed for a +specific purpose, and the encoder is not flexible without modification: + +The LSP vector coder uses a single stage nearest-match with no +interleave, so no step and no error return. This is specced by floor0 +and doesn't change. + +Residue0 encoding interleaves, uses multiple stages, and each stage +peels of a specific amount of resolution from a lattice (thus we want +to match by threshold, not nearest match). Residue doesn't *have* to +be encoded that way, but to change it, one will need to add more +infrastructure on the encode side (decode side is specced and simpler) */ + +/* floor0 LSP (single stage, non interleaved, nearest match) */ +/* returns entry number and *modifies a* to the quantization value *****/ +int vorbis_book_errorv(codebook *book,float *a){ + int dim=book->dim,k; + int best=_best(book,a,1); + for(k=0;kvaluelist+best*dim)[k]; + return(best); +} + +/* returns the number of bits and *modifies a* to the quantization value *****/ +int vorbis_book_encodev(codebook *book,int best,float *a,oggpack_buffer *b){ + int k,dim=book->dim; + for(k=0;kvaluelist+best*dim)[k]; + return(vorbis_book_encode(book,best,b)); +} + +/* the 'eliminate the decode tree' optimization actually requires the + codewords to be MSb first, not LSb. This is an annoying inelegancy + (and one of the first places where carefully thought out design + turned out to be wrong; Vorbis II and future Ogg codecs should go + to an MSb bitpacker), but not actually the huge hit it appears to + be. The first-stage decode table catches most words so that + bitreverse is not in the main execution path. */ + +static ogg_uint32_t bitreverse(ogg_uint32_t x){ + x= ((x>>16)&0x0000ffff) | ((x<<16)&0xffff0000); + x= ((x>> 8)&0x00ff00ff) | ((x<< 8)&0xff00ff00); + x= ((x>> 4)&0x0f0f0f0f) | ((x<< 4)&0xf0f0f0f0); + x= ((x>> 2)&0x33333333) | ((x<< 2)&0xcccccccc); + return((x>> 1)&0x55555555) | ((x<< 1)&0xaaaaaaaa); +} + +STIN long decode_packed_entry_number(codebook *book, oggpack_buffer *b){ + int read=book->dec_maxlength; + long lo,hi; + long lok = oggpack_look(b,book->dec_firsttablen); + + if (lok >= 0) { + long entry = book->dec_firsttable[lok]; + if(entry&0x80000000UL){ + lo=(entry>>15)&0x7fff; + hi=book->used_entries-(entry&0x7fff); + }else{ + oggpack_adv(b, book->dec_codelengths[entry-1]); + return(entry-1); + } + }else{ + lo=0; + hi=book->used_entries; + } + + lok = oggpack_look(b, read); + + while(lok<0 && read>1) + lok = oggpack_look(b, --read); + if(lok<0)return -1; + + /* bisect search for the codeword in the ordered list */ + { + ogg_uint32_t testword=bitreverse((ogg_uint32_t)lok); + + while(hi-lo>1){ + long p=(hi-lo)>>1; + long test=book->codelist[lo+p]>testword; + lo+=p&(test-1); + hi-=p&(-test); + } + + if(book->dec_codelengths[lo]<=read){ + oggpack_adv(b, book->dec_codelengths[lo]); + return(lo); + } + } + + oggpack_adv(b, read); + return(-1); +} + +/* Decode side is specced and easier, because we don't need to find + matches using different criteria; we simply read and map. There are + two things we need to do 'depending': + + We may need to support interleave. We don't really, but it's + convenient to do it here rather than rebuild the vector later. + + Cascades may be additive or multiplicitive; this is not inherent in + the codebook, but set in the code using the codebook. Like + interleaving, it's easiest to do it here. + addmul==0 -> declarative (set the value) + addmul==1 -> additive + addmul==2 -> multiplicitive */ + +/* returns the [original, not compacted] entry number or -1 on eof *********/ +long vorbis_book_decode(codebook *book, oggpack_buffer *b){ + long packed_entry=decode_packed_entry_number(book,b); + if(packed_entry>=0) + return(book->dec_index[packed_entry]); + + /* if there's no dec_index, the codebook unpacking isn't collapsed */ + return(packed_entry); +} + +/* returns 0 on OK or -1 on eof *************************************/ +long vorbis_book_decodevs_add(codebook *book,float *a,oggpack_buffer *b,int n){ + int step=n/book->dim; + long *entry = alloca(sizeof(*entry)*step); + float **t = alloca(sizeof(*t)*step); + int i,j,o; + + for (i = 0; i < step; i++) { + entry[i]=decode_packed_entry_number(book,b); + if(entry[i]==-1)return(-1); + t[i] = book->valuelist+entry[i]*book->dim; + } + for(i=0,o=0;idim;i++,o+=step) + for (j=0;jdim>8){ + for(i=0;ivaluelist+entry*book->dim; + for (j=0;jdim;) + a[i++]+=t[j++]; + } + }else{ + for(i=0;ivaluelist+entry*book->dim; + j=0; + switch((int)book->dim){ + case 8: + a[i++]+=t[j++]; + case 7: + a[i++]+=t[j++]; + case 6: + a[i++]+=t[j++]; + case 5: + a[i++]+=t[j++]; + case 4: + a[i++]+=t[j++]; + case 3: + a[i++]+=t[j++]; + case 2: + a[i++]+=t[j++]; + case 1: + a[i++]+=t[j++]; + case 0: + break; + } + } + } + return(0); +} + +long vorbis_book_decodev_set(codebook *book,float *a,oggpack_buffer *b,int n){ + int i,j,entry; + float *t; + + for(i=0;ivaluelist+entry*book->dim; + for (j=0;jdim;) + a[i++]=t[j++]; + } + return(0); +} + +long vorbis_book_decodevv_add(codebook *book,float **a,long offset,int ch, + oggpack_buffer *b,int n){ + long i,j,entry; + int chptr=0; + + for(i=offset/ch;i<(offset+n)/ch;){ + entry = decode_packed_entry_number(book,b); + if(entry==-1)return(-1); + { + const float *t = book->valuelist+entry*book->dim; + for (j=0;jdim;j++){ + a[chptr++][i]+=t[j]; + if(chptr==ch){ + chptr=0; + i++; + } + } + } + } + return(0); +} + +#ifdef _V_SELFTEST +/* Simple enough; pack a few candidate codebooks, unpack them. Code a + number of vectors through (keeping track of the quantized values), + and decode using the unpacked book. quantized version of in should + exactly equal out */ + +#include + +#include "vorbis/book/lsp20_0.vqh" +#include "vorbis/book/res0a_13.vqh" +#define TESTSIZE 40 + +float test1[TESTSIZE]={ + 0.105939f, + 0.215373f, + 0.429117f, + 0.587974f, + + 0.181173f, + 0.296583f, + 0.515707f, + 0.715261f, + + 0.162327f, + 0.263834f, + 0.342876f, + 0.406025f, + + 0.103571f, + 0.223561f, + 0.368513f, + 0.540313f, + + 0.136672f, + 0.395882f, + 0.587183f, + 0.652476f, + + 0.114338f, + 0.417300f, + 0.525486f, + 0.698679f, + + 0.147492f, + 0.324481f, + 0.643089f, + 0.757582f, + + 0.139556f, + 0.215795f, + 0.324559f, + 0.399387f, + + 0.120236f, + 0.267420f, + 0.446940f, + 0.608760f, + + 0.115587f, + 0.287234f, + 0.571081f, + 0.708603f, +}; + +float test3[TESTSIZE]={ + 0,1,-2,3,4,-5,6,7,8,9, + 8,-2,7,-1,4,6,8,3,1,-9, + 10,11,12,13,14,15,26,17,18,19, + 30,-25,-30,-1,-5,-32,4,3,-2,0}; + +static_codebook *testlist[]={&_vq_book_lsp20_0, + &_vq_book_res0a_13,NULL}; +float *testvec[]={test1,test3}; + +int main(){ + oggpack_buffer write; + oggpack_buffer read; + long ptr=0,i; + oggpack_writeinit(&write); + + fprintf(stderr,"Testing codebook abstraction...:\n"); + + while(testlist[ptr]){ + codebook c; + static_codebook s; + float *qv=alloca(sizeof(*qv)*TESTSIZE); + float *iv=alloca(sizeof(*iv)*TESTSIZE); + memcpy(qv,testvec[ptr],sizeof(*qv)*TESTSIZE); + memset(iv,0,sizeof(*iv)*TESTSIZE); + + fprintf(stderr,"\tpacking/coding %ld... ",ptr); + + /* pack the codebook, write the testvector */ + oggpack_reset(&write); + vorbis_book_init_encode(&c,testlist[ptr]); /* get it into memory + we can write */ + vorbis_staticbook_pack(testlist[ptr],&write); + fprintf(stderr,"Codebook size %ld bytes... ",oggpack_bytes(&write)); + for(i=0;i.000001){ + fprintf(stderr,"read (%g) != written (%g) at position (%ld)\n", + iv[i],qv[i],i); + exit(1); + } + + fprintf(stderr,"OK\n"); + ptr++; + } + + /* The above is the trivial stuff; now try unquantizing a log scale codebook */ + + exit(0); +} + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/codebook.h b/src/sound/oggvorbis/vorbissrc/codebook.h new file mode 100644 index 0000000..0ad2251 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/codebook.h @@ -0,0 +1,184 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: basic shared codebook operations + last mod: $Id: codebook.h,v 1.13 2002/06/28 22:19:35 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_CODEBOOK_H_ +#define _V_CODEBOOK_H_ + +#include "../ogg/ogg.h" + +/* This structure encapsulates huffman and VQ style encoding books; it + doesn't do anything specific to either. + + valuelist/quantlist are nonNULL (and q_* significant) only if + there's entry->value mapping to be done. + + If encode-side mapping must be done (and thus the entry needs to be + hunted), the auxiliary encode pointer will point to a decision + tree. This is true of both VQ and huffman, but is mostly useful + with VQ. + +*/ + +typedef struct static_codebook{ + long dim; /* codebook dimensions (elements per vector) */ + long entries; /* codebook entries */ + long *lengthlist; /* codeword lengths in bits */ + + /* mapping ***************************************************************/ + int maptype; /* 0=none + 1=implicitly populated values from map column + 2=listed arbitrary values */ + + /* The below does a linear, single monotonic sequence mapping. */ + long q_min; /* packed 32 bit float; quant value 0 maps to minval */ + long q_delta; /* packed 32 bit float; val 1 - val 0 == delta */ + int q_quant; /* bits: 0 < quant <= 16 */ + int q_sequencep; /* bitflag */ + + long *quantlist; /* map == 1: (int)(entries^(1/dim)) element column map + map == 2: list of dim*entries quantized entry vals + */ + + /* encode helpers ********************************************************/ + struct encode_aux_nearestmatch *nearest_tree; + struct encode_aux_threshmatch *thresh_tree; + struct encode_aux_pigeonhole *pigeon_tree; + + int allocedp; +} static_codebook; + +/* this structures an arbitrary trained book to quickly find the + nearest cell match */ +typedef struct encode_aux_nearestmatch{ + /* pre-calculated partitioning tree */ + long *ptr0; + long *ptr1; + + long *p; /* decision points (each is an entry) */ + long *q; /* decision points (each is an entry) */ + long aux; /* number of tree entries */ + long alloc; +} encode_aux_nearestmatch; + +/* assumes a maptype of 1; encode side only, so that's OK */ +typedef struct encode_aux_threshmatch{ + float *quantthresh; + long *quantmap; + int quantvals; + int threshvals; +} encode_aux_threshmatch; + +typedef struct encode_aux_pigeonhole{ + float min; + float del; + + int mapentries; + int quantvals; + long *pigeonmap; + + long fittotal; + long *fitlist; + long *fitmap; + long *fitlength; +} encode_aux_pigeonhole; + +typedef struct codebook{ + long dim; /* codebook dimensions (elements per vector) */ + long entries; /* codebook entries */ + long used_entries; /* populated codebook entries */ + const static_codebook *c; + + /* for encode, the below are entry-ordered, fully populated */ + /* for decode, the below are ordered by bitreversed codeword and only + used entries are populated */ + float *valuelist; /* list of dim*entries actual entry values */ + ogg_uint32_t *codelist; /* list of bitstream codewords for each entry */ + + int *dec_index; /* only used if sparseness collapsed */ + char *dec_codelengths; + ogg_uint32_t *dec_firsttable; + int dec_firsttablen; + int dec_maxlength; + +} codebook; + +extern void vorbis_staticbook_clear(static_codebook *b); +extern void vorbis_staticbook_destroy(static_codebook *b); +extern int vorbis_book_init_encode(codebook *dest,const static_codebook *source); +extern int vorbis_book_init_decode(codebook *dest,const static_codebook *source); +extern void vorbis_book_clear(codebook *b); + +extern float *_book_unquantize(const static_codebook *b,int n,int *map); +extern float *_book_logdist(const static_codebook *b,float *vals); +extern float _float32_unpack(long val); +extern long _float32_pack(float val); +extern int _best(codebook *book, float *a, int step); +extern int _ilog(unsigned int v); +extern long _book_maptype1_quantvals(const static_codebook *b); + +extern int vorbis_book_besterror(codebook *book,float *a,int step,int addmul); +extern long vorbis_book_codeword(codebook *book,int entry); +extern long vorbis_book_codelen(codebook *book,int entry); + + + +extern int vorbis_staticbook_pack(const static_codebook *c,oggpack_buffer *b); +extern int vorbis_staticbook_unpack(oggpack_buffer *b,static_codebook *c); + +extern int vorbis_book_encode(codebook *book, int a, oggpack_buffer *b); +extern int vorbis_book_errorv(codebook *book, float *a); +extern int vorbis_book_encodev(codebook *book, int best,float *a, + oggpack_buffer *b); + +extern long vorbis_book_decode(codebook *book, oggpack_buffer *b); +extern long vorbis_book_decodevs_add(codebook *book, float *a, + oggpack_buffer *b,int n); +extern long vorbis_book_decodev_set(codebook *book, float *a, + oggpack_buffer *b,int n); +extern long vorbis_book_decodev_add(codebook *book, float *a, + oggpack_buffer *b,int n); +extern long vorbis_book_decodevv_add(codebook *book, float **a, + long off,int ch, + oggpack_buffer *b,int n); + + + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/codec_internal.h b/src/sound/oggvorbis/vorbissrc/codec_internal.h new file mode 100644 index 0000000..02e79df --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/codec_internal.h @@ -0,0 +1,158 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: libvorbis codec headers + last mod: $Id: codec_internal.h,v 1.17 2003/08/18 05:34:01 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_CODECI_H_ +#define _V_CODECI_H_ + +#include "envelope.h" +#include "codebook.h" + +#define BLOCKTYPE_IMPULSE 0 +#define BLOCKTYPE_PADDING 1 +#define BLOCKTYPE_TRANSITION 0 +#define BLOCKTYPE_LONG 1 + +#define PACKETBLOBS 15 + +typedef struct vorbis_block_internal{ + float **pcmdelay; /* this is a pointer into local storage */ + float ampmax; + int blocktype; + + ogg_uint32_t packetblob_markers[PACKETBLOBS]; +} vorbis_block_internal; + +typedef void vorbis_look_floor; +typedef void vorbis_look_residue; +typedef void vorbis_look_transform; + +/* mode ************************************************************/ +typedef struct { + int blockflag; + int windowtype; + int transformtype; + int mapping; +} vorbis_info_mode; + +typedef void vorbis_info_floor; +typedef void vorbis_info_residue; +typedef void vorbis_info_mapping; + +#include "psy.h" +#include "bitrate.h" + +typedef struct private_state { + /* local lookup storage */ + envelope_lookup *ve; /* envelope lookup */ + int window[2]; + vorbis_look_transform **transform[2]; /* block, type */ + drft_lookup fft_look[2]; + + int modebits; + vorbis_look_floor **flr; + vorbis_look_residue **residue; + vorbis_look_psy *psy; + vorbis_look_psy_global *psy_g_look; + + /* local storage, only used on the encoding side. This way the + application does not need to worry about freeing some packets' + memory and not others'; packet storage is always tracked. + Cleared next call to a _dsp_ function */ + unsigned char *header; + unsigned char *header1; + unsigned char *header2; + + bitrate_manager_state bms; + + ogg_int64_t sample_count; +} private_state; + +/* codec_setup_info contains all the setup information specific to the + specific compression/decompression mode in progress (eg, + psychoacoustic settings, channel setup, options, codebook + etc). +*********************************************************************/ + +#include "highlevel.h" +typedef struct codec_setup_info { + + /* Vorbis supports only short and long blocks, but allows the + encoder to choose the sizes */ + + long blocksizes[2]; + + /* modes are the primary means of supporting on-the-fly different + blocksizes, different channel mappings (LR or M/A), + different residue backends, etc. Each mode consists of a + blocksize flag and a mapping (along with the mapping setup */ + + int modes; + int maps; + int floors; + int residues; + int books; + int psys; /* encode only */ + + vorbis_info_mode *mode_param[64]; + int map_type[64]; + vorbis_info_mapping *map_param[64]; + int floor_type[64]; + vorbis_info_floor *floor_param[64]; + int residue_type[64]; + vorbis_info_residue *residue_param[64]; + static_codebook *book_param[256]; + codebook *fullbooks; + + vorbis_info_psy *psy_param[4]; /* encode only */ + vorbis_info_psy_global psy_g_param; + + bitrate_manager_info bi; + highlevel_encode_setup hi; /* used only by vorbisenc.c. It's a + highly redundant structure, but + improves clarity of program flow. */ + int halfrate_flag; /* painless downsample for decode */ +} codec_setup_info; + +extern vorbis_look_psy_global *_vp_global_look(vorbis_info *vi); +extern void _vp_global_free(vorbis_look_psy_global *look); + +#endif + diff --git a/src/sound/oggvorbis/vorbissrc/envelope.c b/src/sound/oggvorbis/vorbissrc/envelope.c new file mode 100644 index 0000000..9e6c376 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/envelope.c @@ -0,0 +1,406 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: PCM data envelope analysis + last mod: $Id: envelope.c,v 1.54 2003/09/05 23:17:49 giles Exp $ + + ********************************************************************/ + +#include +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" + +#include "os.h" +#include "scales.h" +#include "envelope.h" +#include "mdct.h" +#include "misc.h" + +void _ve_envelope_init(envelope_lookup *e,vorbis_info *vi){ + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy_global *gi=&ci->psy_g_param; + int ch=vi->channels; + int i,j; + int n=e->winlength=128; + e->searchstep=64; /* not random */ + + e->minenergy=gi->preecho_minenergy; + e->ch=ch; + e->storage=128; + e->cursor=ci->blocksizes[1]/2; + e->mdct_win=_ogg_calloc(n,sizeof(*e->mdct_win)); + mdct_init(&e->mdct,n); + + for(i=0;imdct_win[i]=sin(i/(n-1.)*M_PI); + e->mdct_win[i]*=e->mdct_win[i]; + } + + /* magic follows */ + e->band[0].begin=2; e->band[0].end=4; + e->band[1].begin=4; e->band[1].end=5; + e->band[2].begin=6; e->band[2].end=6; + e->band[3].begin=9; e->band[3].end=8; + e->band[4].begin=13; e->band[4].end=8; + e->band[5].begin=17; e->band[5].end=8; + e->band[6].begin=22; e->band[6].end=8; + + for(j=0;jband[j].end; + e->band[j].window=_ogg_malloc(n*sizeof(*e->band[0].window)); + for(i=0;iband[j].window[i]=sin((i+.5)/n*M_PI); + e->band[j].total+=e->band[j].window[i]; + } + e->band[j].total=1./e->band[j].total; + } + + e->filter=_ogg_calloc(VE_BANDS*ch,sizeof(*e->filter)); + e->mark=_ogg_calloc(e->storage,sizeof(*e->mark)); + +} + +void _ve_envelope_clear(envelope_lookup *e){ + int i; + mdct_clear(&e->mdct); + for(i=0;iband[i].window); + _ogg_free(e->mdct_win); + _ogg_free(e->filter); + _ogg_free(e->mark); + memset(e,0,sizeof(*e)); +} + +/* fairly straight threshhold-by-band based until we find something + that works better and isn't patented. */ + +static int _ve_amp(envelope_lookup *ve, + vorbis_info_psy_global *gi, + float *data, + envelope_band *bands, + envelope_filter_state *filters, + long pos){ + long n=ve->winlength; + int ret=0; + long i,j; + float decay; + + /* we want to have a 'minimum bar' for energy, else we're just + basing blocks on quantization noise that outweighs the signal + itself (for low power signals) */ + + float minV=ve->minenergy; + float *vec=alloca(n*sizeof(*vec)); + + /* stretch is used to gradually lengthen the number of windows + considered prevoius-to-potential-trigger */ + int stretch=max(VE_MINSTRETCH,ve->stretch/2); + float penalty=gi->stretch_penalty-(ve->stretch/2-VE_MINSTRETCH); + if(penalty<0.f)penalty=0.f; + if(penalty>gi->stretch_penalty)penalty=gi->stretch_penalty; + + /*_analysis_output_always("lpcm",seq2,data,n,0,0, + totalshift+pos*ve->searchstep);*/ + + /* window and transform */ + for(i=0;imdct_win[i]; + mdct_forward(&ve->mdct,vec,vec); + + /*_analysis_output_always("mdct",seq2,vec,n/2,0,1,0); */ + + /* near-DC spreading function; this has nothing to do with + psychoacoustics, just sidelobe leakage and window size */ + { + float temp=vec[0]*vec[0]+.7*vec[1]*vec[1]+.2*vec[2]*vec[2]; + int ptr=filters->nearptr; + + /* the accumulation is regularly refreshed from scratch to avoid + floating point creep */ + if(ptr==0){ + decay=filters->nearDC_acc=filters->nearDC_partialacc+temp; + filters->nearDC_partialacc=temp; + }else{ + decay=filters->nearDC_acc+=temp; + filters->nearDC_partialacc+=temp; + } + filters->nearDC_acc-=filters->nearDC[ptr]; + filters->nearDC[ptr]=temp; + + decay*=(1./(VE_NEARDC+1)); + filters->nearptr++; + if(filters->nearptr>=VE_NEARDC)filters->nearptr=0; + decay=todB(&decay)*.5-15.f; + } + + /* perform spreading and limiting, also smooth the spectrum. yes, + the MDCT results in all real coefficients, but it still *behaves* + like real/imaginary pairs */ + for(i=0;i>1]=val; + decay-=8.; + } + + /*_analysis_output_always("spread",seq2++,vec,n/4,0,0,0);*/ + + /* perform preecho/postecho triggering by band */ + for(j=0;j=VE_AMP)filters[j].ampptr=0; + } + + /* look at min/max, decide trigger */ + if(valmax>gi->preecho_thresh[j]+penalty){ + ret|=1; + ret|=4; + } + if(valminpostecho_thresh[j]-penalty)ret|=2; + } + + return(ret); +} + +#if 0 +static int seq=0; +static ogg_int64_t totalshift=-1024; +#endif + +long _ve_envelope_search(vorbis_dsp_state *v){ + vorbis_info *vi=v->vi; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy_global *gi=&ci->psy_g_param; + envelope_lookup *ve=((private_state *)(v->backend_state))->ve; + long i,j; + + int first=ve->current/ve->searchstep; + int last=v->pcm_current/ve->searchstep-VE_WIN; + if(first<0)first=0; + + /* make sure we have enough storage to match the PCM */ + if(last+VE_WIN+VE_POST>ve->storage){ + ve->storage=last+VE_WIN+VE_POST; /* be sure */ + ve->mark=_ogg_realloc(ve->mark,ve->storage*sizeof(*ve->mark)); + } + + for(j=first;jstretch++; + if(ve->stretch>VE_MAXSTRETCH*2) + ve->stretch=VE_MAXSTRETCH*2; + + for(i=0;ich;i++){ + float *pcm=v->pcm[i]+ve->searchstep*(j); + ret|=_ve_amp(ve,gi,pcm,ve->band,ve->filter+i*VE_BANDS,j); + } + + ve->mark[j+VE_POST]=0; + if(ret&1){ + ve->mark[j]=1; + ve->mark[j+1]=1; + } + + if(ret&2){ + ve->mark[j]=1; + if(j>0)ve->mark[j-1]=1; + } + + if(ret&4)ve->stretch=-1; + } + + ve->current=last*ve->searchstep; + + { + long centerW=v->centerW; + long testW= + centerW+ + ci->blocksizes[v->W]/4+ + ci->blocksizes[1]/2+ + ci->blocksizes[0]/4; + + j=ve->cursor; + + while(jcurrent-(ve->searchstep)){/* account for postecho + working back one window */ + if(j>=testW)return(1); + + ve->cursor=j; + + if(ve->mark[j/ve->searchstep]){ + if(j>centerW){ + + #if 0 + if(j>ve->curmark){ + float *marker=alloca(v->pcm_current*sizeof(*marker)); + int l,m; + memset(marker,0,sizeof(*marker)*v->pcm_current); + fprintf(stderr,"mark! seq=%d, cursor:%fs time:%fs\n", + seq, + (totalshift+ve->cursor)/44100., + (totalshift+j)/44100.); + _analysis_output_always("pcmL",seq,v->pcm[0],v->pcm_current,0,0,totalshift); + _analysis_output_always("pcmR",seq,v->pcm[1],v->pcm_current,0,0,totalshift); + + _analysis_output_always("markL",seq,v->pcm[0],j,0,0,totalshift); + _analysis_output_always("markR",seq,v->pcm[1],j,0,0,totalshift); + + for(m=0;msearchstep]=ve->filter[m].markers[l]*.1; + _analysis_output_always(buf,seq,marker,v->pcm_current,0,0,totalshift); + } + + for(m=0;msearchstep]=ve->filter[m+VE_BANDS].markers[l]*.1; + _analysis_output_always(buf,seq,marker,v->pcm_current,0,0,totalshift); + } + + for(l=0;lsearchstep]=ve->mark[l]*.4; + _analysis_output_always("mark",seq,marker,v->pcm_current,0,0,totalshift); + + + seq++; + + } +#endif + + ve->curmark=j; + if(j>=testW)return(1); + return(0); + } + } + j+=ve->searchstep; + } + } + + return(-1); +} + +int _ve_envelope_mark(vorbis_dsp_state *v){ + envelope_lookup *ve=((private_state *)(v->backend_state))->ve; + vorbis_info *vi=v->vi; + codec_setup_info *ci=vi->codec_setup; + long centerW=v->centerW; + long beginW=centerW-ci->blocksizes[v->W]/4; + long endW=centerW+ci->blocksizes[v->W]/4; + if(v->W){ + beginW-=ci->blocksizes[v->lW]/4; + endW+=ci->blocksizes[v->nW]/4; + }else{ + beginW-=ci->blocksizes[0]/4; + endW+=ci->blocksizes[0]/4; + } + + if(ve->curmark>=beginW && ve->curmarksearchstep; + long last=endW/ve->searchstep; + long i; + for(i=first;imark[i])return(1); + } + return(0); +} + +void _ve_envelope_shift(envelope_lookup *e,long shift){ + int smallsize=e->current/e->searchstep+VE_POST; /* adjust for placing marks + ahead of ve->current */ + int smallshift=shift/e->searchstep; + + memmove(e->mark,e->mark+smallshift,(smallsize-smallshift)*sizeof(*e->mark)); + + #if 0 + for(i=0;ich;i++) + memmove(e->filter[i].markers, + e->filter[i].markers+smallshift, + (1024-smallshift)*sizeof(*(*e->filter).markers)); + totalshift+=shift; + #endif + + e->current-=shift; + if(e->curmark>=0) + e->curmark-=shift; + e->cursor-=shift; +} + + + + + + diff --git a/src/sound/oggvorbis/vorbissrc/envelope.h b/src/sound/oggvorbis/vorbissrc/envelope.h new file mode 100644 index 0000000..777d8cd --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/envelope.h @@ -0,0 +1,105 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: PCM data envelope analysis and manipulation + last mod: $Id: envelope.h,v 1.25 2002/07/11 06:40:48 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_ENVELOPE_ +#define _V_ENVELOPE_ + +#include "mdct.h" + +#define VE_PRE 16 +#define VE_WIN 4 +#define VE_POST 2 +#define VE_AMP (VE_PRE+VE_POST-1) + +#define VE_BANDS 7 +#define VE_NEARDC 15 + +#define VE_MINSTRETCH 2 /* a bit less than short block */ +#define VE_MAXSTRETCH 12 /* one-third full block */ + +typedef struct { + float ampbuf[VE_AMP]; + int ampptr; + + float nearDC[VE_NEARDC]; + float nearDC_acc; + float nearDC_partialacc; + int nearptr; + +} envelope_filter_state; + +typedef struct { + int begin; + int end; + float *window; + float total; +} envelope_band; + +typedef struct { + int ch; + int winlength; + int searchstep; + float minenergy; + + mdct_lookup mdct; + float *mdct_win; + + envelope_band band[VE_BANDS]; + envelope_filter_state *filter; + int stretch; + + int *mark; + + long storage; + long current; + long curmark; + long cursor; +} envelope_lookup; + +extern void _ve_envelope_init(envelope_lookup *e,vorbis_info *vi); +extern void _ve_envelope_clear(envelope_lookup *e); +extern long _ve_envelope_search(vorbis_dsp_state *v); +extern void _ve_envelope_shift(envelope_lookup *e,long shift); +extern int _ve_envelope_mark(vorbis_dsp_state *v); + + +#endif + diff --git a/src/sound/oggvorbis/vorbissrc/floor0.c b/src/sound/oggvorbis/vorbissrc/floor0.c new file mode 100644 index 0000000..28704a1 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/floor0.c @@ -0,0 +1,247 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: floor backend 0 implementation + last mod: $Id: floor0.c,v 1.55 2003/09/05 23:17:49 giles Exp $ + + ********************************************************************/ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "registry.h" +#include "lpc.h" +#include "lsp.h" +#include "codebook.h" +#include "scales.h" +#include "misc.h" +#include "os.h" + +#include "misc.h" +#include + +typedef struct { + int ln; + int m; + int **linearmap; + int n[2]; + + vorbis_info_floor0 *vi; + + long bits; + long frames; +} vorbis_look_floor0; + + +/***********************************************/ + +static void floor0_free_info(vorbis_info_floor *i){ + vorbis_info_floor0 *info=(vorbis_info_floor0 *)i; + if(info){ + memset(info,0,sizeof(*info)); + _ogg_free(info); + } +} + +static void floor0_free_look(vorbis_look_floor *i){ + vorbis_look_floor0 *look=(vorbis_look_floor0 *)i; + if(look){ + + if(look->linearmap){ + + if(look->linearmap[0])_ogg_free(look->linearmap[0]); + if(look->linearmap[1])_ogg_free(look->linearmap[1]); + + _ogg_free(look->linearmap); + } + memset(look,0,sizeof(*look)); + _ogg_free(look); + } +} + +static vorbis_info_floor *floor0_unpack (vorbis_info *vi,oggpack_buffer *opb){ + codec_setup_info *ci=vi->codec_setup; + int j; + + vorbis_info_floor0 *info=_ogg_malloc(sizeof(*info)); + info->order=oggpack_read(opb,8); + info->rate=oggpack_read(opb,16); + info->barkmap=oggpack_read(opb,16); + info->ampbits=oggpack_read(opb,6); + info->ampdB=oggpack_read(opb,8); + info->numbooks=oggpack_read(opb,4)+1; + + if(info->order<1)goto err_out; + if(info->rate<1)goto err_out; + if(info->barkmap<1)goto err_out; + if(info->numbooks<1)goto err_out; + + for(j=0;jnumbooks;j++){ + info->books[j]=oggpack_read(opb,8); + if(info->books[j]<0 || info->books[j]>=ci->books)goto err_out; + } + return(info); + + err_out: + floor0_free_info(info); + return(NULL); +} + +/* initialize Bark scale and normalization lookups. We could do this + with static tables, but Vorbis allows a number of possible + combinations, so it's best to do it computationally. + + The below is authoritative in terms of defining scale mapping. + Note that the scale depends on the sampling rate as well as the + linear block and mapping sizes */ + +static void floor0_map_lazy_init(vorbis_block *vb, + vorbis_info_floor *infoX, + vorbis_look_floor0 *look){ + if(!look->linearmap[vb->W]){ + vorbis_dsp_state *vd=vb->vd; + vorbis_info *vi=vd->vi; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_floor0 *info=(vorbis_info_floor0 *)infoX; + int W=vb->W; + int n=ci->blocksizes[W]/2,j; + + /* we choose a scaling constant so that: + floor(bark(rate/2-1)*C)=mapped-1 + floor(bark(rate/2)*C)=mapped */ + float scale=look->ln/toBARK(info->rate/2.f); + + /* the mapping from a linear scale to a smaller bark scale is + straightforward. We do *not* make sure that the linear mapping + does not skip bark-scale bins; the decoder simply skips them and + the encoder may do what it wishes in filling them. They're + necessary in some mapping combinations to keep the scale spacing + accurate */ + look->linearmap[W]=_ogg_malloc((n+1)*sizeof(**look->linearmap)); + for(j=0;jrate/2.f)/n*j) + *scale); /* bark numbers represent band edges */ + if(val>=look->ln)val=look->ln-1; /* guard against the approximation */ + look->linearmap[W][j]=val; + } + look->linearmap[W][j]=-1; + look->n[W]=n; + } +} + +static vorbis_look_floor *floor0_look(vorbis_dsp_state *vd, + vorbis_info_floor *i){ + vorbis_info_floor0 *info=(vorbis_info_floor0 *)i; + vorbis_look_floor0 *look=_ogg_calloc(1,sizeof(*look)); + look->m=info->order; + look->ln=info->barkmap; + look->vi=info; + + look->linearmap=_ogg_calloc(2,sizeof(*look->linearmap)); + + return look; +} + +static void *floor0_inverse1(vorbis_block *vb,vorbis_look_floor *i){ + vorbis_look_floor0 *look=(vorbis_look_floor0 *)i; + vorbis_info_floor0 *info=look->vi; + int j,k; + + int ampraw=oggpack_read(&vb->opb,info->ampbits); + if(ampraw>0){ /* also handles the -1 out of data case */ + long maxval=(1<ampbits)-1; + float amp=(float)ampraw/maxval*info->ampdB; + int booknum=oggpack_read(&vb->opb,_ilog(info->numbooks)); + + if(booknum!=-1 && booknumnumbooks){ /* be paranoid */ + codec_setup_info *ci=vb->vd->vi->codec_setup; + codebook *b=ci->fullbooks+info->books[booknum]; + float last=0.f; + + /* the additional b->dim is a guard against any possible stack + smash; b->dim is provably more than we can overflow the + vector */ + float *lsp=_vorbis_block_alloc(vb,sizeof(*lsp)*(look->m+b->dim+1)); + + for(j=0;jm;j+=b->dim) + if(vorbis_book_decodev_set(b,lsp+j,&vb->opb,b->dim)==-1)goto eop; + for(j=0;jm;){ + for(k=0;kdim;k++,j++)lsp[j]+=last; + last=lsp[j-1]; + } + + lsp[look->m]=amp; + return(lsp); + } + } + eop: + return(NULL); +} + +static int floor0_inverse2(vorbis_block *vb,vorbis_look_floor *i, + void *memo,float *out){ + vorbis_look_floor0 *look=(vorbis_look_floor0 *)i; + vorbis_info_floor0 *info=look->vi; + + floor0_map_lazy_init(vb,info,look); + + if(memo){ + float *lsp=(float *)memo; + float amp=lsp[look->m]; + + /* take the coefficients back to a spectral envelope curve */ + vorbis_lsp_to_curve(out, + look->linearmap[vb->W], + look->n[vb->W], + look->ln, + lsp,look->m,amp,(float)info->ampdB); + return(1); + } + memset(out,0,sizeof(*out)*look->n[vb->W]); + return(0); +} + +/* export hooks */ +vorbis_func_floor floor0_exportbundle={ + NULL,&floor0_unpack,&floor0_look,&floor0_free_info, + &floor0_free_look,&floor0_inverse1,&floor0_inverse2 +}; + + + diff --git a/src/sound/oggvorbis/vorbissrc/floor1.c b/src/sound/oggvorbis/vorbissrc/floor1.c new file mode 100644 index 0000000..0ed98d7 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/floor1.c @@ -0,0 +1,1113 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: floor backend 1 implementation + last mod: $Id: floor1.c,v 1.26 2003/02/15 07:10:07 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "registry.h" +#include "codebook.h" +#include "misc.h" +#include "scales.h" + +#include + +#define floor1_rangedB 140 /* floor 1 fixed at -140dB to 0dB range */ + +typedef struct { + int sorted_index[VIF_POSIT+2]; + int forward_index[VIF_POSIT+2]; + int reverse_index[VIF_POSIT+2]; + + int hineighbor[VIF_POSIT]; + int loneighbor[VIF_POSIT]; + int posts; + + int n; + int quant_q; + vorbis_info_floor1 *vi; + + long phrasebits; + long postbits; + long frames; +} vorbis_look_floor1; + +typedef struct lsfit_acc{ + long x0; + long x1; + + long xa; + long ya; + long x2a; + long y2a; + long xya; + long an; +} lsfit_acc; + +/***********************************************/ + +static void floor1_free_info(vorbis_info_floor *i){ + vorbis_info_floor1 *info=(vorbis_info_floor1 *)i; + if(info){ + memset(info,0,sizeof(*info)); + _ogg_free(info); + } +} + +static void floor1_free_look(vorbis_look_floor *i){ + vorbis_look_floor1 *look=(vorbis_look_floor1 *)i; + if(look){ + /*fprintf(stderr,"floor 1 bit usage %f:%f (%f total)\n", + (float)look->phrasebits/look->frames, + (float)look->postbits/look->frames, + (float)(look->postbits+look->phrasebits)/look->frames);*/ + + memset(look,0,sizeof(*look)); + _ogg_free(look); + } +} + +static int ilog(unsigned int v){ + int ret=0; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +static int ilog2(unsigned int v){ + int ret=0; + if(v)--v; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +static void floor1_pack (vorbis_info_floor *i,oggpack_buffer *opb){ + vorbis_info_floor1 *info=(vorbis_info_floor1 *)i; + int j,k; + int count=0; + int rangebits; + int maxposit=info->postlist[1]; + int maxclass=-1; + + /* save out partitions */ + oggpack_write(opb,info->partitions,5); /* only 0 to 31 legal */ + for(j=0;jpartitions;j++){ + oggpack_write(opb,info->partitionclass[j],4); /* only 0 to 15 legal */ + if(maxclasspartitionclass[j])maxclass=info->partitionclass[j]; + } + + /* save out partition classes */ + for(j=0;jclass_dim[j]-1,3); /* 1 to 8 */ + oggpack_write(opb,info->class_subs[j],2); /* 0 to 3 */ + if(info->class_subs[j])oggpack_write(opb,info->class_book[j],8); + for(k=0;k<(1<class_subs[j]);k++) + oggpack_write(opb,info->class_subbook[j][k]+1,8); + } + + /* save out the post list */ + oggpack_write(opb,info->mult-1,2); /* only 1,2,3,4 legal now */ + oggpack_write(opb,ilog2(maxposit),4); + rangebits=ilog2(maxposit); + + for(j=0,k=0;jpartitions;j++){ + count+=info->class_dim[info->partitionclass[j]]; + for(;kpostlist[k+2],rangebits); + } +} + + +static vorbis_info_floor *floor1_unpack (vorbis_info *vi,oggpack_buffer *opb){ + codec_setup_info *ci=vi->codec_setup; + int j,k,count=0,maxclass=-1,rangebits; + + vorbis_info_floor1 *info=_ogg_calloc(1,sizeof(*info)); + /* read partitions */ + info->partitions=oggpack_read(opb,5); /* only 0 to 31 legal */ + for(j=0;jpartitions;j++){ + info->partitionclass[j]=oggpack_read(opb,4); /* only 0 to 15 legal */ + if(maxclasspartitionclass[j])maxclass=info->partitionclass[j]; + } + + /* read partition classes */ + for(j=0;jclass_dim[j]=oggpack_read(opb,3)+1; /* 1 to 8 */ + info->class_subs[j]=oggpack_read(opb,2); /* 0,1,2,3 bits */ + if(info->class_subs[j]<0) + goto err_out; + if(info->class_subs[j])info->class_book[j]=oggpack_read(opb,8); + if(info->class_book[j]<0 || info->class_book[j]>=ci->books) + goto err_out; + for(k=0;k<(1<class_subs[j]);k++){ + info->class_subbook[j][k]=oggpack_read(opb,8)-1; + if(info->class_subbook[j][k]<-1 || info->class_subbook[j][k]>=ci->books) + goto err_out; + } + } + + /* read the post list */ + info->mult=oggpack_read(opb,2)+1; /* only 1,2,3,4 legal now */ + rangebits=oggpack_read(opb,4); + + for(j=0,k=0;jpartitions;j++){ + count+=info->class_dim[info->partitionclass[j]]; + for(;kpostlist[k+2]=oggpack_read(opb,rangebits); + if(t<0 || t>=(1<postlist[0]=0; + info->postlist[1]=1<vi=info; + look->n=info->postlist[1]; + + /* we drop each position value in-between already decoded values, + and use linear interpolation to predict each new value past the + edges. The positions are read in the order of the position + list... we precompute the bounding positions in the lookup. Of + course, the neighbors can change (if a position is declined), but + this is an initial mapping */ + + for(i=0;ipartitions;i++)n+=info->class_dim[info->partitionclass[i]]; + n+=2; + look->posts=n; + + /* also store a sorted position index */ + for(i=0;ipostlist+i; + qsort(sortpointer,n,sizeof(*sortpointer),icomp); + + /* points from sort order back to range number */ + for(i=0;iforward_index[i]=sortpointer[i]-info->postlist; + /* points from range order to sorted position */ + for(i=0;ireverse_index[look->forward_index[i]]=i; + /* we actually need the post values too */ + for(i=0;isorted_index[i]=info->postlist[look->forward_index[i]]; + + /* quantize values to multiplier spec */ + switch(info->mult){ + case 1: /* 1024 -> 256 */ + look->quant_q=256; + break; + case 2: /* 1024 -> 128 */ + look->quant_q=128; + break; + case 3: /* 1024 -> 86 */ + look->quant_q=86; + break; + case 4: /* 1024 -> 64 */ + look->quant_q=64; + break; + } + + /* discover our neighbors for decode where we don't use fit flags + (that would push the neighbors outward) */ + for(i=0;in; + int currentx=info->postlist[i+2]; + for(j=0;jpostlist[j]; + if(x>lx && xcurrentx){ + hi=j; + hx=x; + } + } + look->loneighbor[i]=lo; + look->hineighbor[i]=hi; + } + + return(look); +} + +static int render_point(int x0,int x1,int y0,int y1,int x){ + y0&=0x7fff; /* mask off flag */ + y1&=0x7fff; + + { + int dy=y1-y0; + int adx=x1-x0; + int ady=abs(dy); + int err=ady*(x-x0); + + int off=err/adx; + if(dy<0)return(y0-off); + return(y0+off); + } +} + +static int vorbis_dBquant(const float *x){ + int i= *x*7.3142857f+1023.5f; + if(i>1023)return(1023); + if(i<0)return(0); + return i; +} + +static float FLOOR1_fromdB_LOOKUP[256]={ + 1.0649863e-07F, 1.1341951e-07F, 1.2079015e-07F, 1.2863978e-07F, + 1.3699951e-07F, 1.4590251e-07F, 1.5538408e-07F, 1.6548181e-07F, + 1.7623575e-07F, 1.8768855e-07F, 1.9988561e-07F, 2.128753e-07F, + 2.2670913e-07F, 2.4144197e-07F, 2.5713223e-07F, 2.7384213e-07F, + 2.9163793e-07F, 3.1059021e-07F, 3.3077411e-07F, 3.5226968e-07F, + 3.7516214e-07F, 3.9954229e-07F, 4.2550680e-07F, 4.5315863e-07F, + 4.8260743e-07F, 5.1396998e-07F, 5.4737065e-07F, 5.8294187e-07F, + 6.2082472e-07F, 6.6116941e-07F, 7.0413592e-07F, 7.4989464e-07F, + 7.9862701e-07F, 8.5052630e-07F, 9.0579828e-07F, 9.6466216e-07F, + 1.0273513e-06F, 1.0941144e-06F, 1.1652161e-06F, 1.2409384e-06F, + 1.3215816e-06F, 1.4074654e-06F, 1.4989305e-06F, 1.5963394e-06F, + 1.7000785e-06F, 1.8105592e-06F, 1.9282195e-06F, 2.0535261e-06F, + 2.1869758e-06F, 2.3290978e-06F, 2.4804557e-06F, 2.6416497e-06F, + 2.8133190e-06F, 2.9961443e-06F, 3.1908506e-06F, 3.3982101e-06F, + 3.6190449e-06F, 3.8542308e-06F, 4.1047004e-06F, 4.3714470e-06F, + 4.6555282e-06F, 4.9580707e-06F, 5.2802740e-06F, 5.6234160e-06F, + 5.9888572e-06F, 6.3780469e-06F, 6.7925283e-06F, 7.2339451e-06F, + 7.7040476e-06F, 8.2047000e-06F, 8.7378876e-06F, 9.3057248e-06F, + 9.9104632e-06F, 1.0554501e-05F, 1.1240392e-05F, 1.1970856e-05F, + 1.2748789e-05F, 1.3577278e-05F, 1.4459606e-05F, 1.5399272e-05F, + 1.6400004e-05F, 1.7465768e-05F, 1.8600792e-05F, 1.9809576e-05F, + 2.1096914e-05F, 2.2467911e-05F, 2.3928002e-05F, 2.5482978e-05F, + 2.7139006e-05F, 2.8902651e-05F, 3.0780908e-05F, 3.2781225e-05F, + 3.4911534e-05F, 3.7180282e-05F, 3.9596466e-05F, 4.2169667e-05F, + 4.4910090e-05F, 4.7828601e-05F, 5.0936773e-05F, 5.4246931e-05F, + 5.7772202e-05F, 6.1526565e-05F, 6.5524908e-05F, 6.9783085e-05F, + 7.4317983e-05F, 7.9147585e-05F, 8.4291040e-05F, 8.9768747e-05F, + 9.5602426e-05F, 0.00010181521F, 0.00010843174F, 0.00011547824F, + 0.00012298267F, 0.00013097477F, 0.00013948625F, 0.00014855085F, + 0.00015820453F, 0.00016848555F, 0.00017943469F, 0.00019109536F, + 0.00020351382F, 0.00021673929F, 0.00023082423F, 0.00024582449F, + 0.00026179955F, 0.00027881276F, 0.00029693158F, 0.00031622787F, + 0.00033677814F, 0.00035866388F, 0.00038197188F, 0.00040679456F, + 0.00043323036F, 0.00046138411F, 0.00049136745F, 0.00052329927F, + 0.00055730621F, 0.00059352311F, 0.00063209358F, 0.00067317058F, + 0.00071691700F, 0.00076350630F, 0.00081312324F, 0.00086596457F, + 0.00092223983F, 0.00098217216F, 0.0010459992F, 0.0011139742F, + 0.0011863665F, 0.0012634633F, 0.0013455702F, 0.0014330129F, + 0.0015261382F, 0.0016253153F, 0.0017309374F, 0.0018434235F, + 0.0019632195F, 0.0020908006F, 0.0022266726F, 0.0023713743F, + 0.0025254795F, 0.0026895994F, 0.0028643847F, 0.0030505286F, + 0.0032487691F, 0.0034598925F, 0.0036847358F, 0.0039241906F, + 0.0041792066F, 0.0044507950F, 0.0047400328F, 0.0050480668F, + 0.0053761186F, 0.0057254891F, 0.0060975636F, 0.0064938176F, + 0.0069158225F, 0.0073652516F, 0.0078438871F, 0.0083536271F, + 0.0088964928F, 0.009474637F, 0.010090352F, 0.010746080F, + 0.011444421F, 0.012188144F, 0.012980198F, 0.013823725F, + 0.014722068F, 0.015678791F, 0.016697687F, 0.017782797F, + 0.018938423F, 0.020169149F, 0.021479854F, 0.022875735F, + 0.024362330F, 0.025945531F, 0.027631618F, 0.029427276F, + 0.031339626F, 0.033376252F, 0.035545228F, 0.037855157F, + 0.040315199F, 0.042935108F, 0.045725273F, 0.048696758F, + 0.051861348F, 0.055231591F, 0.058820850F, 0.062643361F, + 0.066714279F, 0.071049749F, 0.075666962F, 0.080584227F, + 0.085821044F, 0.091398179F, 0.097337747F, 0.10366330F, + 0.11039993F, 0.11757434F, 0.12521498F, 0.13335215F, + 0.14201813F, 0.15124727F, 0.16107617F, 0.17154380F, + 0.18269168F, 0.19456402F, 0.20720788F, 0.22067342F, + 0.23501402F, 0.25028656F, 0.26655159F, 0.28387361F, + 0.30232132F, 0.32196786F, 0.34289114F, 0.36517414F, + 0.38890521F, 0.41417847F, 0.44109412F, 0.46975890F, + 0.50028648F, 0.53279791F, 0.56742212F, 0.60429640F, + 0.64356699F, 0.68538959F, 0.72993007F, 0.77736504F, + 0.82788260F, 0.88168307F, 0.9389798F, 1.F, +}; + +static void render_line(int x0,int x1,int y0,int y1,float *d){ + int dy=y1-y0; + int adx=x1-x0; + int ady=abs(dy); + int base=dy/adx; + int sy=(dy<0?base-1:base+1); + int x=x0; + int y=y0; + int err=0; + + ady-=abs(base*adx); + + d[x]*=FLOOR1_fromdB_LOOKUP[y]; + while(++x=adx){ + err-=adx; + y+=sy; + }else{ + y+=base; + } + d[x]*=FLOOR1_fromdB_LOOKUP[y]; + } +} + +static void render_line0(int x0,int x1,int y0,int y1,int *d){ + int dy=y1-y0; + int adx=x1-x0; + int ady=abs(dy); + int base=dy/adx; + int sy=(dy<0?base-1:base+1); + int x=x0; + int y=y0; + int err=0; + + ady-=abs(base*adx); + + d[x]=y; + while(++x=adx){ + err-=adx; + y+=sy; + }else{ + y+=base; + } + d[x]=y; + } +} + +/* the floor has already been filtered to only include relevant sections */ +static int accumulate_fit(const float *flr,const float *mdct, + int x0, int x1,lsfit_acc *a, + int n,vorbis_info_floor1 *info){ + long i; + int quantized=vorbis_dBquant(flr+x0); + + long xa=0,ya=0,x2a=0,y2a=0,xya=0,na=0, xb=0,yb=0,x2b=0,y2b=0,xyb=0,nb=0; + + memset(a,0,sizeof(*a)); + a->x0=x0; + a->x1=x1; + if(x1>=n)x1=n-1; + + for(i=x0;i<=x1;i++){ + int quantized=vorbis_dBquant(flr+i); + if(quantized){ + if(mdct[i]+info->twofitatten>=flr[i]){ + xa += i; + ya += quantized; + x2a += i*i; + y2a += quantized*quantized; + xya += i*quantized; + na++; + }else{ + xb += i; + yb += quantized; + x2b += i*i; + y2b += quantized*quantized; + xyb += i*quantized; + nb++; + } + } + } + + xb+=xa; + yb+=ya; + x2b+=x2a; + y2b+=y2a; + xyb+=xya; + nb+=na; + + /* weight toward the actually used frequencies if we meet the threshhold */ + { + int weight=nb*info->twofitweight/(na+1); + + a->xa=xa*weight+xb; + a->ya=ya*weight+yb; + a->x2a=x2a*weight+x2b; + a->y2a=y2a*weight+y2b; + a->xya=xya*weight+xyb; + a->an=na*weight+nb; + } + + return(na); +} + +static void fit_line(lsfit_acc *a,int fits,int *y0,int *y1){ + long x=0,y=0,x2=0,y2=0,xy=0,an=0,i; + long x0=a[0].x0; + long x1=a[fits-1].x1; + + for(i=0;i=0){ + x+= x0; + y+= *y0; + x2+= x0 * x0; + y2+= *y0 * *y0; + xy+= *y0 * x0; + an++; + } + + if(*y1>=0){ + x+= x1; + y+= *y1; + x2+= x1 * x1; + y2+= *y1 * *y1; + xy+= *y1 * x1; + an++; + } + + if(an){ + /* need 64 bit multiplies, which C doesn't give portably as int */ + double fx=x; + double fy=y; + double fx2=x2; + double fxy=xy; + double denom=1./(an*fx2-fx*fx); + double a=(fy*fx2-fxy*fx)*denom; + double b=(an*fxy-fx*fy)*denom; + *y0=rint(a+b*x0); + *y1=rint(a+b*x1); + + /* limit to our range! */ + if(*y0>1023)*y0=1023; + if(*y1>1023)*y1=1023; + if(*y0<0)*y0=0; + if(*y1<0)*y1=0; + + }else{ + *y0=0; + *y1=0; + } +} + +/*static void fit_line_point(lsfit_acc *a,int fits,int *y0,int *y1){ + long y=0; + int i; + + for(i=0;itwofitatten>=mask[x]){ + if(y+info->maxovermaxunder>val)return(1); + } + + while(++x=adx){ + err-=adx; + y+=sy; + }else{ + y+=base; + } + + val=vorbis_dBquant(mask+x); + mse+=((y-val)*(y-val)); + n++; + if(mdct[x]+info->twofitatten>=mask[x]){ + if(val){ + if(y+info->maxovermaxunder>val)return(1); + } + } + } + + if(info->maxover*info->maxover/n>info->maxerr)return(0); + if(info->maxunder*info->maxunder/n>info->maxerr)return(0); + if(mse/n>info->maxerr)return(1); + return(0); +} + +static int post_Y(int *A,int *B,int pos){ + if(A[pos]<0) + return B[pos]; + if(B[pos]<0) + return A[pos]; + + return (A[pos]+B[pos])>>1; +} + +static int seq=0; + +int *floor1_fit(vorbis_block *vb,vorbis_look_floor1 *look, + const float *logmdct, /* in */ + const float *logmask){ + long i,j; + vorbis_info_floor1 *info=look->vi; + long n=look->n; + long posts=look->posts; + long nonzero=0; + lsfit_acc fits[VIF_POSIT+1]; + int fit_valueA[VIF_POSIT+2]; /* index by range list position */ + int fit_valueB[VIF_POSIT+2]; /* index by range list position */ + + int loneighbor[VIF_POSIT+2]; /* sorted index of range list position (+2) */ + int hineighbor[VIF_POSIT+2]; + int *output=NULL; + int memo[VIF_POSIT+2]; + + for(i=0;isorted_index[i], + look->sorted_index[i+1],fits+i, + n,info); + } + + if(nonzero){ + /* start by fitting the implicit base case.... */ + int y0=-200; + int y1=-200; + fit_line(fits,posts-1,&y0,&y1); + + fit_valueA[0]=y0; + fit_valueB[0]=y0; + fit_valueB[1]=y1; + fit_valueA[1]=y1; + + /* Non degenerate case */ + /* start progressive splitting. This is a greedy, non-optimal + algorithm, but simple and close enough to the best + answer. */ + for(i=2;ireverse_index[i]; + int ln=loneighbor[sortpos]; + int hn=hineighbor[sortpos]; + + /* eliminate repeat searches of a particular range with a memo */ + if(memo[ln]!=hn){ + /* haven't performed this error search yet */ + int lsortpos=look->reverse_index[ln]; + int hsortpos=look->reverse_index[hn]; + memo[ln]=hn; + + { + /* A note: we want to bound/minimize *local*, not global, error */ + int lx=info->postlist[ln]; + int hx=info->postlist[hn]; + int ly=post_Y(fit_valueA,fit_valueB,ln); + int hy=post_Y(fit_valueA,fit_valueB,hn); + + if(ly==-1 || hy==-1){ + exit(1); + } + + if(inspect_error(lx,hx,ly,hy,logmask,logmdct,info)){ + /* outside error bounds/begin search area. Split it. */ + int ly0=-200; + int ly1=-200; + int hy0=-200; + int hy1=-200; + fit_line(fits+lsortpos,sortpos-lsortpos,&ly0,&ly1); + fit_line(fits+sortpos,hsortpos-sortpos,&hy0,&hy1); + + /* store new edge values */ + fit_valueB[ln]=ly0; + if(ln==0)fit_valueA[ln]=ly0; + fit_valueA[i]=ly1; + fit_valueB[i]=hy0; + fit_valueA[hn]=hy1; + if(hn==1)fit_valueB[hn]=hy1; + + if(ly1>=0 || hy0>=0){ + /* store new neighbor values */ + for(j=sortpos-1;j>=0;j--) + if(hineighbor[j]==hn) + hineighbor[j]=i; + else + break; + for(j=sortpos+1;jloneighbor[i-2]; + int hn=look->hineighbor[i-2]; + int x0=info->postlist[ln]; + int x1=info->postlist[hn]; + int y0=output[ln]; + int y1=output[hn]; + + int predicted=render_point(x0,x1,y0,y1,info->postlist[i]); + int vx=post_Y(fit_valueA,fit_valueB,i); + + if(vx>=0 && predicted!=vx){ + output[i]=vx; + }else{ + output[i]= predicted|0x8000; + } + } + } + + return(output); + +} + +int *floor1_interpolate_fit(vorbis_block *vb,vorbis_look_floor1 *look, + int *A,int *B, + int del){ + + long i; + long posts=look->posts; + int *output=NULL; + + if(A && B){ + output=_vorbis_block_alloc(vb,sizeof(*output)*posts); + + for(i=0;i>16; + if(A[i]&0x8000 && B[i]&0x8000)output[i]|=0x8000; + } + } + + return(output); +} + + +int floor1_encode(vorbis_block *vb,vorbis_look_floor1 *look, + int *post,int *ilogmask){ + + long i,j; + vorbis_info_floor1 *info=look->vi; + long n=look->n; + long posts=look->posts; + codec_setup_info *ci=vb->vd->vi->codec_setup; + int out[VIF_POSIT+2]; + static_codebook **sbooks=ci->book_param; + codebook *books=ci->fullbooks; + static long seq=0; + + /* quantize values to multiplier spec */ + if(post){ + for(i=0;imult){ + case 1: /* 1024 -> 256 */ + val>>=2; + break; + case 2: /* 1024 -> 128 */ + val>>=3; + break; + case 3: /* 1024 -> 86 */ + val/=12; + break; + case 4: /* 1024 -> 64 */ + val>>=4; + break; + } + post[i]=val | (post[i]&0x8000); + } + + out[0]=post[0]; + out[1]=post[1]; + + /* find prediction values for each post and subtract them */ + for(i=2;iloneighbor[i-2]; + int hn=look->hineighbor[i-2]; + int x0=info->postlist[ln]; + int x1=info->postlist[hn]; + int y0=post[ln]; + int y1=post[hn]; + + int predicted=render_point(x0,x1,y0,y1,info->postlist[i]); + + if((post[i]&0x8000) || (predicted==post[i])){ + post[i]=predicted|0x8000; /* in case there was roundoff jitter + in interpolation */ + out[i]=0; + }else{ + int headroom=(look->quant_q-predictedquant_q-predicted:predicted); + + int val=post[i]-predicted; + + /* at this point the 'deviation' value is in the range +/- max + range, but the real, unique range can always be mapped to + only [0-maxrange). So we want to wrap the deviation into + this limited range, but do it in the way that least screws + an essentially gaussian probability distribution. */ + + if(val<0) + if(val<-headroom) + val=headroom-val-1; + else + val=-1-(val<<1); + else + if(val>=headroom) + val= val+headroom; + else + val<<=1; + + out[i]=val; + post[ln]&=0x7fff; + post[hn]&=0x7fff; + } + } + + /* we have everything we need. pack it out */ + /* mark nontrivial floor */ + oggpack_write(&vb->opb,1,1); + + /* beginning/end post */ + look->frames++; + look->postbits+=ilog(look->quant_q-1)*2; + oggpack_write(&vb->opb,out[0],ilog(look->quant_q-1)); + oggpack_write(&vb->opb,out[1],ilog(look->quant_q-1)); + + + /* partition by partition */ + for(i=0,j=2;ipartitions;i++){ + int class=info->partitionclass[i]; + int cdim=info->class_dim[class]; + int csubbits=info->class_subs[class]; + int csub=1<class_subbook[class][k]; + if(booknum<0){ + maxval[k]=1; + }else{ + maxval[k]=sbooks[info->class_subbook[class][k]]->entries; + } + } + for(k=0;kphrasebits+= + vorbis_book_encode(books+info->class_book[class],cval,&vb->opb); + +#ifdef TRAIN_FLOOR1 + { + FILE *of; + char buffer[80]; + sprintf(buffer,"line_%dx%ld_class%d.vqd", + vb->pcmend/2,posts-2,class); + of=fopen(buffer,"a"); + fprintf(of,"%d\n",cval); + fclose(of); + } +#endif + } + + /* write post values */ + for(k=0;kclass_subbook[class][bookas[k]]; + if(book>=0){ + /* hack to allow training with 'bad' books */ + if(out[j+k]<(books+book)->entries) + look->postbits+=vorbis_book_encode(books+book, + out[j+k],&vb->opb); + /*else + fprintf(stderr,"+!");*/ + +#ifdef TRAIN_FLOOR1 + { + FILE *of; + char buffer[80]; + sprintf(buffer,"line_%dx%ld_%dsub%d.vqd", + vb->pcmend/2,posts-2,class,bookas[k]); + of=fopen(buffer,"a"); + fprintf(of,"%d\n",out[j+k]); + fclose(of); + } +#endif + } + } + j+=cdim; + } + + { + /* generate quantized floor equivalent to what we'd unpack in decode */ + /* render the lines */ + int hx=0; + int lx=0; + int ly=post[0]*info->mult; + for(j=1;jposts;j++){ + int current=look->forward_index[j]; + int hy=post[current]&0x7fff; + if(hy==post[current]){ + + hy*=info->mult; + hx=info->postlist[current]; + + render_line0(lx,hx,ly,hy,ilogmask); + + lx=hx; + ly=hy; + } + } + for(j=hx;jpcmend/2;j++)ilogmask[j]=ly; /* be certain */ + seq++; + return(1); + } + }else{ + oggpack_write(&vb->opb,0,1); + memset(ilogmask,0,vb->pcmend/2*sizeof(*ilogmask)); + seq++; + return(0); + } +} + +static void *floor1_inverse1(vorbis_block *vb,vorbis_look_floor *in){ + vorbis_look_floor1 *look=(vorbis_look_floor1 *)in; + vorbis_info_floor1 *info=look->vi; + codec_setup_info *ci=vb->vd->vi->codec_setup; + + int i,j,k; + codebook *books=ci->fullbooks; + + /* unpack wrapped/predicted values from stream */ + if(oggpack_read(&vb->opb,1)==1){ + int *fit_value=_vorbis_block_alloc(vb,(look->posts)*sizeof(*fit_value)); + + fit_value[0]=oggpack_read(&vb->opb,ilog(look->quant_q-1)); + fit_value[1]=oggpack_read(&vb->opb,ilog(look->quant_q-1)); + + /* partition by partition */ + for(i=0,j=2;ipartitions;i++){ + int class=info->partitionclass[i]; + int cdim=info->class_dim[class]; + int csubbits=info->class_subs[class]; + int csub=1<class_book[class],&vb->opb); + + if(cval==-1)goto eop; + } + + for(k=0;kclass_subbook[class][cval&(csub-1)]; + cval>>=csubbits; + if(book>=0){ + if((fit_value[j+k]=vorbis_book_decode(books+book,&vb->opb))==-1) + goto eop; + }else{ + fit_value[j+k]=0; + } + } + j+=cdim; + } + + /* unwrap positive values and reconsitute via linear interpolation */ + for(i=2;iposts;i++){ + int predicted=render_point(info->postlist[look->loneighbor[i-2]], + info->postlist[look->hineighbor[i-2]], + fit_value[look->loneighbor[i-2]], + fit_value[look->hineighbor[i-2]], + info->postlist[i]); + int hiroom=look->quant_q-predicted; + int loroom=predicted; + int room=(hiroom=room){ + if(hiroom>loroom){ + val = val-loroom; + }else{ + val = -1-(val-hiroom); + } + }else{ + if(val&1){ + val= -((val+1)>>1); + }else{ + val>>=1; + } + } + + fit_value[i]=val+predicted; + fit_value[look->loneighbor[i-2]]&=0x7fff; + fit_value[look->hineighbor[i-2]]&=0x7fff; + + }else{ + fit_value[i]=predicted|0x8000; + } + + } + + return(fit_value); + } + eop: + return(NULL); +} + +static int floor1_inverse2(vorbis_block *vb,vorbis_look_floor *in,void *memo, + float *out){ + vorbis_look_floor1 *look=(vorbis_look_floor1 *)in; + vorbis_info_floor1 *info=look->vi; + + codec_setup_info *ci=vb->vd->vi->codec_setup; + int n=ci->blocksizes[vb->W]/2; + int j; + + if(memo){ + /* render the lines */ + int *fit_value=(int *)memo; + int hx=0; + int lx=0; + int ly=fit_value[0]*info->mult; + for(j=1;jposts;j++){ + int current=look->forward_index[j]; + int hy=fit_value[current]&0x7fff; + if(hy==fit_value[current]){ + + hy*=info->mult; + hx=info->postlist[current]; + + render_line(lx,hx,ly,hy,out); + + lx=hx; + ly=hy; + } + } + for(j=hx;j. + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: highlevel encoder setup struct seperated out for vorbisenc clarity + last mod: $Id: highlevel.h,v 1.4 2002/07/01 11:20:11 xiphmont Exp $ + + ********************************************************************/ + +typedef struct highlevel_byblocktype { + double tone_mask_setting; + double tone_peaklimit_setting; + double noise_bias_setting; + double noise_compand_setting; +} highlevel_byblocktype; + +typedef struct highlevel_encode_setup { + void *setup; + int set_in_stone; + + double base_setting; + double long_setting; + double short_setting; + double impulse_noisetune; + + int managed; + long bitrate_min; + long bitrate_av_lo; + long bitrate_av_hi; + long bitrate_max; + double bitrate_limit_window; + double bitrate_av_window; + double bitrate_av_window_center; + + int impulse_block_p; + int noise_normalize_p; + + double stereo_point_setting; + double lowpass_kHz; + + double ath_floating_dB; + double ath_absolute_dB; + + double amplitude_track_dBpersec; + double trigger_setting; + + highlevel_byblocktype block[4]; /* padding, impulse, transition, long */ + +} highlevel_encode_setup; + diff --git a/src/sound/oggvorbis/vorbissrc/info.c b/src/sound/oggvorbis/vorbissrc/info.c new file mode 100644 index 0000000..ea2ab31 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/info.c @@ -0,0 +1,627 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2003 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: maintain the info structure, info <-> header packets + last mod: $Id: info.c,v 1.62 2003/09/10 01:10:18 xiphmont Exp $ + + ********************************************************************/ + +/* general handling of the header and the vorbis_info structure (and + substructures) */ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "codebook.h" +#include "registry.h" +#include "window.h" +#include "psy.h" +#include "misc.h" +#include "os.h" + +/* helpers */ +static int ilog2(unsigned int v){ + int ret=0; + if(v)--v; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +static void _v_writestring(oggpack_buffer *o,char *s, int bytes){ + + while(bytes--){ + oggpack_write(o,*s++,8); + } +} + +static void _v_readstring(oggpack_buffer *o,char *buf,int bytes){ + while(bytes--){ + *buf++=oggpack_read(o,8); + } +} + +void vorbis_comment_init(vorbis_comment *vc){ + memset(vc,0,sizeof(*vc)); +} + +void vorbis_comment_add(vorbis_comment *vc,char *comment){ + vc->user_comments=_ogg_realloc(vc->user_comments, + (vc->comments+2)*sizeof(*vc->user_comments)); + vc->comment_lengths=_ogg_realloc(vc->comment_lengths, + (vc->comments+2)*sizeof(*vc->comment_lengths)); + vc->comment_lengths[vc->comments]=strlen(comment); + vc->user_comments[vc->comments]=_ogg_malloc(vc->comment_lengths[vc->comments]+1); + strcpy(vc->user_comments[vc->comments], comment); + vc->comments++; + vc->user_comments[vc->comments]=NULL; +} + +void vorbis_comment_add_tag(vorbis_comment *vc, char *tag, char *contents){ + char *comment=alloca(strlen(tag)+strlen(contents)+2); /* +2 for = and \0 */ + strcpy(comment, tag); + strcat(comment, "="); + strcat(comment, contents); + vorbis_comment_add(vc, comment); +} + +/* This is more or less the same as strncasecmp - but that doesn't exist + * everywhere, and this is a fairly trivial function, so we include it */ +static int tagcompare(const char *s1, const char *s2, int n){ + int c=0; + while(c < n){ + if(toupper(s1[c]) != toupper(s2[c])) + return !0; + c++; + } + return 0; +} + +char *vorbis_comment_query(vorbis_comment *vc, char *tag, int count){ + long i; + int found = 0; + int taglen = strlen(tag)+1; /* +1 for the = we append */ + char *fulltag = alloca(taglen+ 1); + + strcpy(fulltag, tag); + strcat(fulltag, "="); + + for(i=0;icomments;i++){ + if(!tagcompare(vc->user_comments[i], fulltag, taglen)){ + if(count == found) + /* We return a pointer to the data, not a copy */ + return vc->user_comments[i] + taglen; + else + found++; + } + } + return NULL; /* didn't find anything */ +} + +int vorbis_comment_query_count(vorbis_comment *vc, char *tag){ + int i,count=0; + int taglen = strlen(tag)+1; /* +1 for the = we append */ + char *fulltag = alloca(taglen+1); + strcpy(fulltag,tag); + strcat(fulltag, "="); + + for(i=0;icomments;i++){ + if(!tagcompare(vc->user_comments[i], fulltag, taglen)) + count++; + } + + return count; +} + +void vorbis_comment_clear(vorbis_comment *vc){ + if(vc){ + long i; + for(i=0;icomments;i++) + if(vc->user_comments[i])_ogg_free(vc->user_comments[i]); + if(vc->user_comments)_ogg_free(vc->user_comments); + if(vc->comment_lengths)_ogg_free(vc->comment_lengths); + if(vc->vendor)_ogg_free(vc->vendor); + } + memset(vc,0,sizeof(*vc)); +} + +/* blocksize 0 is guaranteed to be short, 1 is guarantted to be long. + They may be equal, but short will never ge greater than long */ +int vorbis_info_blocksize(vorbis_info *vi,int zo){ + codec_setup_info *ci = vi->codec_setup; + return ci ? ci->blocksizes[zo] : -1; +} + +/* used by synthesis, which has a full, alloced vi */ +void vorbis_info_init(vorbis_info *vi){ + memset(vi,0,sizeof(*vi)); + vi->codec_setup=_ogg_calloc(1,sizeof(codec_setup_info)); +} + +void vorbis_info_clear(vorbis_info *vi){ + codec_setup_info *ci=vi->codec_setup; + int i; + + if(ci){ + + for(i=0;imodes;i++) + if(ci->mode_param[i])_ogg_free(ci->mode_param[i]); + + for(i=0;imaps;i++) /* unpack does the range checking */ + _mapping_P[ci->map_type[i]]->free_info(ci->map_param[i]); + + for(i=0;ifloors;i++) /* unpack does the range checking */ + _floor_P[ci->floor_type[i]]->free_info(ci->floor_param[i]); + + for(i=0;iresidues;i++) /* unpack does the range checking */ + _residue_P[ci->residue_type[i]]->free_info(ci->residue_param[i]); + + for(i=0;ibooks;i++){ + if(ci->book_param[i]){ + /* knows if the book was not alloced */ + vorbis_staticbook_destroy(ci->book_param[i]); + } + if(ci->fullbooks) + vorbis_book_clear(ci->fullbooks+i); + } + if(ci->fullbooks) + _ogg_free(ci->fullbooks); + + for(i=0;ipsys;i++) + _vi_psy_free(ci->psy_param[i]); + + _ogg_free(ci); + } + + memset(vi,0,sizeof(*vi)); +} + +/* Header packing/unpacking ********************************************/ + +static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ + codec_setup_info *ci=vi->codec_setup; + if(!ci)return(OV_EFAULT); + + vi->version=oggpack_read(opb,32); + if(vi->version!=0)return(OV_EVERSION); + + vi->channels=oggpack_read(opb,8); + vi->rate=oggpack_read(opb,32); + + vi->bitrate_upper=oggpack_read(opb,32); + vi->bitrate_nominal=oggpack_read(opb,32); + vi->bitrate_lower=oggpack_read(opb,32); + + ci->blocksizes[0]=1<blocksizes[1]=1<rate<1)goto err_out; + if(vi->channels<1)goto err_out; + if(ci->blocksizes[0]<8)goto err_out; + if(ci->blocksizes[1]blocksizes[0])goto err_out; + + if(oggpack_read(opb,1)!=1)goto err_out; /* EOP check */ + + return(0); + err_out: + vorbis_info_clear(vi); + return(OV_EBADHEADER); +} + +static int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb){ + int i; + int vendorlen=oggpack_read(opb,32); + if(vendorlen<0)goto err_out; + vc->vendor=_ogg_calloc(vendorlen+1,1); + _v_readstring(opb,vc->vendor,vendorlen); + vc->comments=oggpack_read(opb,32); + if(vc->comments<0)goto err_out; + vc->user_comments=_ogg_calloc(vc->comments+1,sizeof(*vc->user_comments)); + vc->comment_lengths=_ogg_calloc(vc->comments+1, sizeof(*vc->comment_lengths)); + + for(i=0;icomments;i++){ + int len=oggpack_read(opb,32); + if(len<0)goto err_out; + vc->comment_lengths[i]=len; + vc->user_comments[i]=_ogg_calloc(len+1,1); + _v_readstring(opb,vc->user_comments[i],len); + } + if(oggpack_read(opb,1)!=1)goto err_out; /* EOP check */ + + return(0); + err_out: + vorbis_comment_clear(vc); + return(OV_EBADHEADER); +} + +/* all of the real encoding details are here. The modes, books, + everything */ +static int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb){ + codec_setup_info *ci=vi->codec_setup; + int i; + if(!ci)return(OV_EFAULT); + + /* codebooks */ + ci->books=oggpack_read(opb,8)+1; + /*ci->book_param=_ogg_calloc(ci->books,sizeof(*ci->book_param));*/ + for(i=0;ibooks;i++){ + ci->book_param[i]=_ogg_calloc(1,sizeof(*ci->book_param[i])); + if(vorbis_staticbook_unpack(opb,ci->book_param[i]))goto err_out; + } + + /* time backend settings; hooks are unused */ + { + int times=oggpack_read(opb,6)+1; + for(i=0;i=VI_TIMEB)goto err_out; + } + } + + /* floor backend settings */ + ci->floors=oggpack_read(opb,6)+1; + /*ci->floor_type=_ogg_malloc(ci->floors*sizeof(*ci->floor_type));*/ + /*ci->floor_param=_ogg_calloc(ci->floors,sizeof(void *));*/ + for(i=0;ifloors;i++){ + ci->floor_type[i]=oggpack_read(opb,16); + if(ci->floor_type[i]<0 || ci->floor_type[i]>=VI_FLOORB)goto err_out; + ci->floor_param[i]=_floor_P[ci->floor_type[i]]->unpack(vi,opb); + if(!ci->floor_param[i])goto err_out; + } + + /* residue backend settings */ + ci->residues=oggpack_read(opb,6)+1; + /*ci->residue_type=_ogg_malloc(ci->residues*sizeof(*ci->residue_type));*/ + /*ci->residue_param=_ogg_calloc(ci->residues,sizeof(void *));*/ + for(i=0;iresidues;i++){ + ci->residue_type[i]=oggpack_read(opb,16); + if(ci->residue_type[i]<0 || ci->residue_type[i]>=VI_RESB)goto err_out; + ci->residue_param[i]=_residue_P[ci->residue_type[i]]->unpack(vi,opb); + if(!ci->residue_param[i])goto err_out; + } + + /* map backend settings */ + ci->maps=oggpack_read(opb,6)+1; + /*ci->map_type=_ogg_malloc(ci->maps*sizeof(*ci->map_type));*/ + /*ci->map_param=_ogg_calloc(ci->maps,sizeof(void *));*/ + for(i=0;imaps;i++){ + ci->map_type[i]=oggpack_read(opb,16); + if(ci->map_type[i]<0 || ci->map_type[i]>=VI_MAPB)goto err_out; + ci->map_param[i]=_mapping_P[ci->map_type[i]]->unpack(vi,opb); + if(!ci->map_param[i])goto err_out; + } + + /* mode settings */ + ci->modes=oggpack_read(opb,6)+1; + /*vi->mode_param=_ogg_calloc(vi->modes,sizeof(void *));*/ + for(i=0;imodes;i++){ + ci->mode_param[i]=_ogg_calloc(1,sizeof(*ci->mode_param[i])); + ci->mode_param[i]->blockflag=oggpack_read(opb,1); + ci->mode_param[i]->windowtype=oggpack_read(opb,16); + ci->mode_param[i]->transformtype=oggpack_read(opb,16); + ci->mode_param[i]->mapping=oggpack_read(opb,8); + + if(ci->mode_param[i]->windowtype>=VI_WINDOWB)goto err_out; + if(ci->mode_param[i]->transformtype>=VI_WINDOWB)goto err_out; + if(ci->mode_param[i]->mapping>=ci->maps)goto err_out; + } + + if(oggpack_read(opb,1)!=1)goto err_out; /* top level EOP check */ + + return(0); + err_out: + vorbis_info_clear(vi); + return(OV_EBADHEADER); +} + +/* The Vorbis header is in three packets; the initial small packet in + the first page that identifies basic parameters, a second packet + with bitstream comments and a third packet that holds the + codebook. */ + +int vorbis_synthesis_headerin(vorbis_info *vi,vorbis_comment *vc,ogg_packet *op){ + oggpack_buffer opb; + + if(op){ + oggpack_readinit(&opb,op->packet,op->bytes); + + /* Which of the three types of header is this? */ + /* Also verify header-ness, vorbis */ + { + char buffer[6]; + int packtype=oggpack_read(&opb,8); + memset(buffer,0,6); + _v_readstring(&opb,buffer,6); + if(memcmp(buffer,"vorbis",6)){ + /* not a vorbis header */ + return(OV_ENOTVORBIS); + } + switch(packtype){ + case 0x01: /* least significant *bit* is read first */ + if(!op->b_o_s){ + /* Not the initial packet */ + return(OV_EBADHEADER); + } + if(vi->rate!=0){ + /* previously initialized info header */ + return(OV_EBADHEADER); + } + + return(_vorbis_unpack_info(vi,&opb)); + + case 0x03: /* least significant *bit* is read first */ + if(vi->rate==0){ + /* um... we didn't get the initial header */ + return(OV_EBADHEADER); + } + + return(_vorbis_unpack_comment(vc,&opb)); + + case 0x05: /* least significant *bit* is read first */ + if(vi->rate==0 || vc->vendor==NULL){ + /* um... we didn;t get the initial header or comments yet */ + return(OV_EBADHEADER); + } + + return(_vorbis_unpack_books(vi,&opb)); + + default: + /* Not a valid vorbis header type */ + return(OV_EBADHEADER); + break; + } + } + } + return(OV_EBADHEADER); +} + +/* pack side **********************************************************/ + +static int _vorbis_pack_info(oggpack_buffer *opb,vorbis_info *vi){ + codec_setup_info *ci=vi->codec_setup; + if(!ci)return(OV_EFAULT); + + /* preamble */ + oggpack_write(opb,0x01,8); + _v_writestring(opb,"vorbis", 6); + + /* basic information about the stream */ + oggpack_write(opb,0x00,32); + oggpack_write(opb,vi->channels,8); + oggpack_write(opb,vi->rate,32); + + oggpack_write(opb,vi->bitrate_upper,32); + oggpack_write(opb,vi->bitrate_nominal,32); + oggpack_write(opb,vi->bitrate_lower,32); + + oggpack_write(opb,ilog2(ci->blocksizes[0]),4); + oggpack_write(opb,ilog2(ci->blocksizes[1]),4); + oggpack_write(opb,1,1); + + return(0); +} + +static int _vorbis_pack_comment(oggpack_buffer *opb,vorbis_comment *vc){ + char temp[]="Xiph.Org libVorbis I 20030909"; + int bytes = strlen(temp); + + /* preamble */ + oggpack_write(opb,0x03,8); + _v_writestring(opb,"vorbis", 6); + + /* vendor */ + oggpack_write(opb,bytes,32); + _v_writestring(opb,temp, bytes); + + /* comments */ + + oggpack_write(opb,vc->comments,32); + if(vc->comments){ + int i; + for(i=0;icomments;i++){ + if(vc->user_comments[i]){ + oggpack_write(opb,vc->comment_lengths[i],32); + _v_writestring(opb,vc->user_comments[i], vc->comment_lengths[i]); + }else{ + oggpack_write(opb,0,32); + } + } + } + oggpack_write(opb,1,1); + + return(0); +} + +static int _vorbis_pack_books(oggpack_buffer *opb,vorbis_info *vi){ + codec_setup_info *ci=vi->codec_setup; + int i; + if(!ci)return(OV_EFAULT); + + oggpack_write(opb,0x05,8); + _v_writestring(opb,"vorbis", 6); + + /* books */ + oggpack_write(opb,ci->books-1,8); + for(i=0;ibooks;i++) + if(vorbis_staticbook_pack(ci->book_param[i],opb))goto err_out; + + /* times; hook placeholders */ + oggpack_write(opb,0,6); + oggpack_write(opb,0,16); + + /* floors */ + oggpack_write(opb,ci->floors-1,6); + for(i=0;ifloors;i++){ + oggpack_write(opb,ci->floor_type[i],16); + if(_floor_P[ci->floor_type[i]]->pack) + _floor_P[ci->floor_type[i]]->pack(ci->floor_param[i],opb); + else + goto err_out; + } + + /* residues */ + oggpack_write(opb,ci->residues-1,6); + for(i=0;iresidues;i++){ + oggpack_write(opb,ci->residue_type[i],16); + _residue_P[ci->residue_type[i]]->pack(ci->residue_param[i],opb); + } + + /* maps */ + oggpack_write(opb,ci->maps-1,6); + for(i=0;imaps;i++){ + oggpack_write(opb,ci->map_type[i],16); + _mapping_P[ci->map_type[i]]->pack(vi,ci->map_param[i],opb); + } + + /* modes */ + oggpack_write(opb,ci->modes-1,6); + for(i=0;imodes;i++){ + oggpack_write(opb,ci->mode_param[i]->blockflag,1); + oggpack_write(opb,ci->mode_param[i]->windowtype,16); + oggpack_write(opb,ci->mode_param[i]->transformtype,16); + oggpack_write(opb,ci->mode_param[i]->mapping,8); + } + oggpack_write(opb,1,1); + + return(0); +err_out: + return(-1); +} + +int vorbis_commentheader_out(vorbis_comment *vc, + ogg_packet *op){ + + oggpack_buffer opb; + + oggpack_writeinit(&opb); + if(_vorbis_pack_comment(&opb,vc)) return OV_EIMPL; + + op->packet = _ogg_malloc(oggpack_bytes(&opb)); + memcpy(op->packet, opb.buffer, oggpack_bytes(&opb)); + + op->bytes=oggpack_bytes(&opb); + op->b_o_s=0; + op->e_o_s=0; + op->granulepos=0; + + return 0; +} + +int vorbis_analysis_headerout(vorbis_dsp_state *v, + vorbis_comment *vc, + ogg_packet *op, + ogg_packet *op_comm, + ogg_packet *op_code){ + int ret=OV_EIMPL; + vorbis_info *vi=v->vi; + oggpack_buffer opb; + private_state *b=v->backend_state; + + if(!b){ + ret=OV_EFAULT; + goto err_out; + } + + /* first header packet **********************************************/ + + oggpack_writeinit(&opb); + if(_vorbis_pack_info(&opb,vi))goto err_out; + + /* build the packet */ + if(b->header)_ogg_free(b->header); + b->header=_ogg_malloc(oggpack_bytes(&opb)); + memcpy(b->header,opb.buffer,oggpack_bytes(&opb)); + op->packet=b->header; + op->bytes=oggpack_bytes(&opb); + op->b_o_s=1; + op->e_o_s=0; + op->granulepos=0; + + /* second header packet (comments) **********************************/ + + oggpack_reset(&opb); + if(_vorbis_pack_comment(&opb,vc))goto err_out; + + if(b->header1)_ogg_free(b->header1); + b->header1=_ogg_malloc(oggpack_bytes(&opb)); + memcpy(b->header1,opb.buffer,oggpack_bytes(&opb)); + op_comm->packet=b->header1; + op_comm->bytes=oggpack_bytes(&opb); + op_comm->b_o_s=0; + op_comm->e_o_s=0; + op_comm->granulepos=0; + + /* third header packet (modes/codebooks) ****************************/ + + oggpack_reset(&opb); + if(_vorbis_pack_books(&opb,vi))goto err_out; + + if(b->header2)_ogg_free(b->header2); + b->header2=_ogg_malloc(oggpack_bytes(&opb)); + memcpy(b->header2,opb.buffer,oggpack_bytes(&opb)); + op_code->packet=b->header2; + op_code->bytes=oggpack_bytes(&opb); + op_code->b_o_s=0; + op_code->e_o_s=0; + op_code->granulepos=0; + + oggpack_writeclear(&opb); + return(0); + err_out: + oggpack_writeclear(&opb); + memset(op,0,sizeof(*op)); + memset(op_comm,0,sizeof(*op_comm)); + memset(op_code,0,sizeof(*op_code)); + + if(b->header)_ogg_free(b->header); + if(b->header1)_ogg_free(b->header1); + if(b->header2)_ogg_free(b->header2); + b->header=NULL; + b->header1=NULL; + b->header2=NULL; + return(ret); +} + +double vorbis_granule_time(vorbis_dsp_state *v,ogg_int64_t granulepos){ + if(granulepos>=0) + return((double)granulepos/v->vi->rate); + return(-1); +} diff --git a/src/sound/oggvorbis/vorbissrc/lookup.c b/src/sound/oggvorbis/vorbissrc/lookup.c new file mode 100644 index 0000000..269ac4d --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/lookup.c @@ -0,0 +1,118 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: lookup based functions + last mod: $Id: lookup.c,v 1.9 2002/01/22 08:06:07 xiphmont Exp $ + + ********************************************************************/ + +#include +#include "lookup.h" +#include "lookup_data.h" +#include "os.h" +#include "misc.h" + +#ifdef FLOAT_LOOKUP + +/* interpolated lookup based cos function, domain 0 to PI only */ +float vorbis_coslook(float a){ + double d=a*(.31830989*(float)COS_LOOKUP_SZ); + int i=vorbis_ftoi(d-.5); + + return COS_LOOKUP[i]+ (d-i)*(COS_LOOKUP[i+1]-COS_LOOKUP[i]); +} + +/* interpolated 1./sqrt(p) where .5 <= p < 1. */ +float vorbis_invsqlook(float a){ + double d=a*(2.f*(float)INVSQ_LOOKUP_SZ)-(float)INVSQ_LOOKUP_SZ; + int i=vorbis_ftoi(d-.5f); + return INVSQ_LOOKUP[i]+ (d-i)*(INVSQ_LOOKUP[i+1]-INVSQ_LOOKUP[i]); +} + +/* interpolated 1./sqrt(p) where .5 <= p < 1. */ +float vorbis_invsq2explook(int a){ + return INVSQ2EXP_LOOKUP[a-INVSQ2EXP_LOOKUP_MIN]; +} + +#include +/* interpolated lookup based fromdB function, domain -140dB to 0dB only */ +float vorbis_fromdBlook(float a){ + int i=vorbis_ftoi(a*((float)(-(1<=(FROMdB_LOOKUP_SZ<>FROMdB_SHIFT]*FROMdB2_LOOKUP[i&FROMdB2_MASK]); +} + +#endif + +#ifdef INT_LOOKUP +/* interpolated 1./sqrt(p) where .5 <= a < 1. (.100000... to .111111...) in + 16.16 format + + returns in m.8 format */ +long vorbis_invsqlook_i(long a,long e){ + long i=(a&0x7fff)>>(INVSQ_LOOKUP_I_SHIFT-1); + long d=(a&INVSQ_LOOKUP_I_MASK)<<(16-INVSQ_LOOKUP_I_SHIFT); /* 0.16 */ + long val=INVSQ_LOOKUP_I[i]- /* 1.16 */ + (((INVSQ_LOOKUP_I[i]-INVSQ_LOOKUP_I[i+1])* /* 0.16 */ + d)>>16); /* result 1.16 */ + + e+=32; + if(e&1)val=(val*5792)>>13; /* multiply val by 1/sqrt(2) */ + e=(e>>1)-8; + + return(val>>e); +} + +/* interpolated lookup based fromdB function, domain -140dB to 0dB only */ +/* a is in n.12 format */ +float vorbis_fromdBlook_i(long a){ + int i=(-a)>>(12-FROMdB2_SHIFT); + return (i<0)?1.f: + ((i>=(FROMdB_LOOKUP_SZ<>FROMdB_SHIFT]*FROMdB2_LOOKUP[i&FROMdB2_MASK]); +} + +/* interpolated lookup based cos function, domain 0 to PI only */ +/* a is in 0.16 format, where 0==0, 2^^16-1==PI, return 0.14 */ +long vorbis_coslook_i(long a){ + int i=a>>COS_LOOKUP_I_SHIFT; + int d=a&COS_LOOKUP_I_MASK; + return COS_LOOKUP_I[i]- ((d*(COS_LOOKUP_I[i]-COS_LOOKUP_I[i+1]))>> + COS_LOOKUP_I_SHIFT); +} + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/lookup.h b/src/sound/oggvorbis/vorbissrc/lookup.h new file mode 100644 index 0000000..87a826b --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/lookup.h @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: lookup based functions + last mod: $Id: lookup.h,v 1.7 2002/07/11 06:40:49 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_LOOKUP_H_ + +#ifdef FLOAT_LOOKUP +extern float vorbis_coslook(float a); +extern float vorbis_invsqlook(float a); +extern float vorbis_invsq2explook(int a); +extern float vorbis_fromdBlook(float a); +#endif +#ifdef INT_LOOKUP +extern long vorbis_invsqlook_i(long a,long e); +extern long vorbis_coslook_i(long a); +extern float vorbis_fromdBlook_i(long a); +#endif + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/lookup_data.h b/src/sound/oggvorbis/vorbissrc/lookup_data.h new file mode 100644 index 0000000..066a348 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/lookup_data.h @@ -0,0 +1,213 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: lookup data; generated by lookups.pl; edit there + last mod: $Id: lookup_data.h,v 1.10 2003/01/18 08:09:28 msmith Exp $ + + ********************************************************************/ + +#ifndef _V_LOOKUP_DATA_H_ + +#ifdef FLOAT_LOOKUP +#define COS_LOOKUP_SZ 128 +static float COS_LOOKUP[COS_LOOKUP_SZ+1]={ + +1.0000000000000f,+0.9996988186962f,+0.9987954562052f,+0.9972904566787f, + +0.9951847266722f,+0.9924795345987f,+0.9891765099648f,+0.9852776423889f, + +0.9807852804032f,+0.9757021300385f,+0.9700312531945f,+0.9637760657954f, + +0.9569403357322f,+0.9495281805930f,+0.9415440651830f,+0.9329927988347f, + +0.9238795325113f,+0.9142097557035f,+0.9039892931234f,+0.8932243011955f, + +0.8819212643484f,+0.8700869911087f,+0.8577286100003f,+0.8448535652497f, + +0.8314696123025f,+0.8175848131516f,+0.8032075314806f,+0.7883464276266f, + +0.7730104533627f,+0.7572088465065f,+0.7409511253550f,+0.7242470829515f, + +0.7071067811865f,+0.6895405447371f,+0.6715589548470f,+0.6531728429538f, + +0.6343932841636f,+0.6152315905806f,+0.5956993044924f,+0.5758081914178f, + +0.5555702330196f,+0.5349976198871f,+0.5141027441932f,+0.4928981922298f, + +0.4713967368260f,+0.4496113296546f,+0.4275550934303f,+0.4052413140050f, + +0.3826834323651f,+0.3598950365350f,+0.3368898533922f,+0.3136817403989f, + +0.2902846772545f,+0.2667127574749f,+0.2429801799033f,+0.2191012401569f, + +0.1950903220161f,+0.1709618887603f,+0.1467304744554f,+0.1224106751992f, + +0.0980171403296f,+0.0735645635997f,+0.0490676743274f,+0.0245412285229f, + +0.0000000000000f,-0.0245412285229f,-0.0490676743274f,-0.0735645635997f, + -0.0980171403296f,-0.1224106751992f,-0.1467304744554f,-0.1709618887603f, + -0.1950903220161f,-0.2191012401569f,-0.2429801799033f,-0.2667127574749f, + -0.2902846772545f,-0.3136817403989f,-0.3368898533922f,-0.3598950365350f, + -0.3826834323651f,-0.4052413140050f,-0.4275550934303f,-0.4496113296546f, + -0.4713967368260f,-0.4928981922298f,-0.5141027441932f,-0.5349976198871f, + -0.5555702330196f,-0.5758081914178f,-0.5956993044924f,-0.6152315905806f, + -0.6343932841636f,-0.6531728429538f,-0.6715589548470f,-0.6895405447371f, + -0.7071067811865f,-0.7242470829515f,-0.7409511253550f,-0.7572088465065f, + -0.7730104533627f,-0.7883464276266f,-0.8032075314806f,-0.8175848131516f, + -0.8314696123025f,-0.8448535652497f,-0.8577286100003f,-0.8700869911087f, + -0.8819212643484f,-0.8932243011955f,-0.9039892931234f,-0.9142097557035f, + -0.9238795325113f,-0.9329927988347f,-0.9415440651830f,-0.9495281805930f, + -0.9569403357322f,-0.9637760657954f,-0.9700312531945f,-0.9757021300385f, + -0.9807852804032f,-0.9852776423889f,-0.9891765099648f,-0.9924795345987f, + -0.9951847266722f,-0.9972904566787f,-0.9987954562052f,-0.9996988186962f, + -1.0000000000000f, +}; + +#define INVSQ_LOOKUP_SZ 32 +static float INVSQ_LOOKUP[INVSQ_LOOKUP_SZ+1]={ + 1.414213562373f,1.392621247646f,1.371988681140f,1.352246807566f, + 1.333333333333f,1.315191898443f,1.297771369046f,1.281025230441f, + 1.264911064067f,1.249390095109f,1.234426799697f,1.219988562661f, + 1.206045378311f,1.192569588000f,1.179535649239f,1.166919931983f, + 1.154700538379f,1.142857142857f,1.131370849898f,1.120224067222f, + 1.109400392450f,1.098884511590f,1.088662107904f,1.078719779941f, + 1.069044967650f,1.059625885652f,1.050451462878f,1.041511287847f, + 1.032795558989f,1.024295039463f,1.016001016002f,1.007905261358f, + 1.000000000000f, +}; + +#define INVSQ2EXP_LOOKUP_MIN (-32) +#define INVSQ2EXP_LOOKUP_MAX 32 +static float INVSQ2EXP_LOOKUP[INVSQ2EXP_LOOKUP_MAX-\ + INVSQ2EXP_LOOKUP_MIN+1]={ + 65536.f, 46340.95001f, 32768.f, 23170.47501f, + 16384.f, 11585.2375f, 8192.f, 5792.618751f, + 4096.f, 2896.309376f, 2048.f, 1448.154688f, + 1024.f, 724.0773439f, 512.f, 362.038672f, + 256.f, 181.019336f, 128.f, 90.50966799f, + 64.f, 45.254834f, 32.f, 22.627417f, + 16.f, 11.3137085f, 8.f, 5.656854249f, + 4.f, 2.828427125f, 2.f, 1.414213562f, + 1.f, 0.7071067812f, 0.5f, 0.3535533906f, + 0.25f, 0.1767766953f, 0.125f, 0.08838834765f, + 0.0625f, 0.04419417382f, 0.03125f, 0.02209708691f, + 0.015625f, 0.01104854346f, 0.0078125f, 0.005524271728f, + 0.00390625f, 0.002762135864f, 0.001953125f, 0.001381067932f, + 0.0009765625f, 0.000690533966f, 0.00048828125f, 0.000345266983f, + 0.000244140625f,0.0001726334915f,0.0001220703125f,8.631674575e-05f, + 6.103515625e-05f,4.315837288e-05f,3.051757812e-05f,2.157918644e-05f, + 1.525878906e-05f, +}; + +#endif + +#define FROMdB_LOOKUP_SZ 35 +#define FROMdB2_LOOKUP_SZ 32 +#define FROMdB_SHIFT 5 +#define FROMdB2_SHIFT 3 +#define FROMdB2_MASK 31 +static float FROMdB_LOOKUP[FROMdB_LOOKUP_SZ]={ + 1.f, 0.6309573445f, 0.3981071706f, 0.2511886432f, + 0.1584893192f, 0.1f, 0.06309573445f, 0.03981071706f, + 0.02511886432f, 0.01584893192f, 0.01f, 0.006309573445f, + 0.003981071706f, 0.002511886432f, 0.001584893192f, 0.001f, + 0.0006309573445f,0.0003981071706f,0.0002511886432f,0.0001584893192f, + 0.0001f,6.309573445e-05f,3.981071706e-05f,2.511886432e-05f, + 1.584893192e-05f, 1e-05f,6.309573445e-06f,3.981071706e-06f, + 2.511886432e-06f,1.584893192e-06f, 1e-06f,6.309573445e-07f, + 3.981071706e-07f,2.511886432e-07f,1.584893192e-07f, +}; + +static float FROMdB2_LOOKUP[FROMdB2_LOOKUP_SZ]={ + 0.9928302478f, 0.9786445908f, 0.9646616199f, 0.9508784391f, + 0.9372921937f, 0.92390007f, 0.9106992942f, 0.8976871324f, + 0.8848608897f, 0.8722179097f, 0.8597555737f, 0.8474713009f, + 0.835362547f, 0.8234268041f, 0.8116616003f, 0.8000644989f, + 0.7886330981f, 0.7773650302f, 0.7662579617f, 0.755309592f, + 0.7445176537f, 0.7338799116f, 0.7233941627f, 0.7130582353f, + 0.7028699885f, 0.6928273125f, 0.6829281272f, 0.6731703824f, + 0.6635520573f, 0.6540711597f, 0.6447257262f, 0.6355138211f, +}; + +#ifdef INT_LOOKUP + +#define INVSQ_LOOKUP_I_SHIFT 10 +#define INVSQ_LOOKUP_I_MASK 1023 +static long INVSQ_LOOKUP_I[64+1]={ + 92682l, 91966l, 91267l, 90583l, + 89915l, 89261l, 88621l, 87995l, + 87381l, 86781l, 86192l, 85616l, + 85051l, 84497l, 83953l, 83420l, + 82897l, 82384l, 81880l, 81385l, + 80899l, 80422l, 79953l, 79492l, + 79039l, 78594l, 78156l, 77726l, + 77302l, 76885l, 76475l, 76072l, + 75674l, 75283l, 74898l, 74519l, + 74146l, 73778l, 73415l, 73058l, + 72706l, 72359l, 72016l, 71679l, + 71347l, 71019l, 70695l, 70376l, + 70061l, 69750l, 69444l, 69141l, + 68842l, 68548l, 68256l, 67969l, + 67685l, 67405l, 67128l, 66855l, + 66585l, 66318l, 66054l, 65794l, + 65536l, +}; + +#define COS_LOOKUP_I_SHIFT 9 +#define COS_LOOKUP_I_MASK 511 +#define COS_LOOKUP_I_SZ 128 +static long COS_LOOKUP_I[COS_LOOKUP_I_SZ+1]={ + 16384l, 16379l, 16364l, 16340l, + 16305l, 16261l, 16207l, 16143l, + 16069l, 15986l, 15893l, 15791l, + 15679l, 15557l, 15426l, 15286l, + 15137l, 14978l, 14811l, 14635l, + 14449l, 14256l, 14053l, 13842l, + 13623l, 13395l, 13160l, 12916l, + 12665l, 12406l, 12140l, 11866l, + 11585l, 11297l, 11003l, 10702l, + 10394l, 10080l, 9760l, 9434l, + 9102l, 8765l, 8423l, 8076l, + 7723l, 7366l, 7005l, 6639l, + 6270l, 5897l, 5520l, 5139l, + 4756l, 4370l, 3981l, 3590l, + 3196l, 2801l, 2404l, 2006l, + 1606l, 1205l, 804l, 402l, + 0l, -401l, -803l, -1204l, + -1605l, -2005l, -2403l, -2800l, + -3195l, -3589l, -3980l, -4369l, + -4755l, -5138l, -5519l, -5896l, + -6269l, -6638l, -7004l, -7365l, + -7722l, -8075l, -8422l, -8764l, + -9101l, -9433l, -9759l, -10079l, + -10393l, -10701l, -11002l, -11296l, + -11584l, -11865l, -12139l, -12405l, + -12664l, -12915l, -13159l, -13394l, + -13622l, -13841l, -14052l, -14255l, + -14448l, -14634l, -14810l, -14977l, + -15136l, -15285l, -15425l, -15556l, + -15678l, -15790l, -15892l, -15985l, + -16068l, -16142l, -16206l, -16260l, + -16304l, -16339l, -16363l, -16378l, + -16383l, +}; + +#endif + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/lpc.c b/src/sound/oggvorbis/vorbissrc/lpc.c new file mode 100644 index 0000000..7d81a10 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/lpc.c @@ -0,0 +1,173 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: LPC low level routines + last mod: $Id: lpc.c,v 1.37 2003/03/08 07:15:32 xiphmont Exp $ + + ********************************************************************/ + +/* Some of these routines (autocorrelator, LPC coefficient estimator) + are derived from code written by Jutta Degener and Carsten Bormann; + thus we include their copyright below. The entirety of this file + is freely redistributable on the condition that both of these + copyright notices are preserved without modification. */ + +/* Preserved Copyright: *********************************************/ + +/* Copyright 1992, 1993, 1994 by Jutta Degener and Carsten Bormann, +Technische Universita"t Berlin + +Any use of this software is permitted provided that this notice is not +removed and that neither the authors nor the Technische Universita"t +Berlin are deemed to have made any representations as to the +suitability of this software for any purpose nor are held responsible +for any defects of this software. THERE IS ABSOLUTELY NO WARRANTY FOR +THIS SOFTWARE. + +As a matter of courtesy, the authors request to be informed about uses +this software has found, about bugs in this software, and about any +improvements that may be of general interest. + +Berlin, 28.11.1994 +Jutta Degener +Carsten Bormann + +*********************************************************************/ + +#include +#include +#include +#include "os.h" +#include "smallft.h" +#include "lpc.h" +#include "scales.h" +#include "misc.h" + +/* Autocorrelation LPC coeff generation algorithm invented by + N. Levinson in 1947, modified by J. Durbin in 1959. */ + +/* Input : n elements of time doamin data + Output: m lpc coefficients, excitation energy */ + +float vorbis_lpc_from_data(float *data,float *lpci,int n,int m){ + double *aut=alloca(sizeof(*aut)*(m+1)); + double *lpc=alloca(sizeof(*lpc)*(m)); + double error; + int i,j; + + /* autocorrelation, p+1 lag coefficients */ + j=m+1; + while(j--){ + double d=0; /* double needed for accumulator depth */ + for(i=j;i. + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: LPC low level routines + last mod: $Id: lpc.h,v 1.20 2003/03/07 09:13:30 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_LPC_H_ +#define _V_LPC_H_ + +#include "../vorbis/codec.h" + +/* simple linear scale LPC code */ +extern float vorbis_lpc_from_data(float *data,float *lpc,int n,int m); + +extern void vorbis_lpc_predict(float *coeff,float *prime,int m, + float *data,long n); + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/lsp.c b/src/sound/oggvorbis/vorbissrc/lsp.c new file mode 100644 index 0000000..3643f74 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/lsp.c @@ -0,0 +1,479 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: LSP (also called LSF) conversion routines + last mod: $Id: lsp.c,v 1.24 2002/10/16 07:44:21 xiphmont Exp $ + + The LSP generation code is taken (with minimal modification and a + few bugfixes) from "On the Computation of the LSP Frequencies" by + Joseph Rothweiler (see http://www.rothweiler.us for contact info). + The paper is available at: + + http://www.myown1.com/joe/lsf + + ********************************************************************/ + +/* Note that the lpc-lsp conversion finds the roots of polynomial with + an iterative root polisher (CACM algorithm 283). It *is* possible + to confuse this algorithm into not converging; that should only + happen with absurdly closely spaced roots (very sharp peaks in the + LPC f response) which in turn should be impossible in our use of + the code. If this *does* happen anyway, it's a bug in the floor + finder; find the cause of the confusion (probably a single bin + spike or accidental near-float-limit resolution problems) and + correct it. */ + +#include +#include +#include +#include "lsp.h" +#include "os.h" +#include "misc.h" +#include "lookup.h" +#include "scales.h" + +/* three possible LSP to f curve functions; the exact computation + (float), a lookup based float implementation, and an integer + implementation. The float lookup is likely the optimal choice on + any machine with an FPU. The integer implementation is *not* fixed + point (due to the need for a large dynamic range and thus a + seperately tracked exponent) and thus much more complex than the + relatively simple float implementations. It's mostly for future + work on a fully fixed point implementation for processors like the + ARM family. */ + +/* undefine both for the 'old' but more precise implementation */ +#define FLOAT_LOOKUP +#undef INT_LOOKUP + +#ifdef FLOAT_LOOKUP +#include "lookup.c" /* catch this in the build system; we #include for + compilers (like gcc) that can't inline across + modules */ + +/* side effect: changes *lsp to cosines of lsp */ +void vorbis_lsp_to_curve(float *curve,int *map,int n,int ln,float *lsp,int m, + float amp,float ampoffset){ + int i; + float wdel=M_PI/ln; + vorbis_fpu_control fpu; + + vorbis_fpu_setround(&fpu); + for(i=0;i>1; + + do{ + q*=ftmp[0]-w; + p*=ftmp[1]-w; + ftmp+=2; + }while(--c); + + if(m&1){ + /* odd order filter; slightly assymetric */ + /* the last coefficient */ + q*=ftmp[0]-w; + q*=q; + p*=p*(1.f-w*w); + }else{ + /* even order filter; still symmetric */ + q*=q*(1.f+w); + p*=p*(1.f-w); + } + + q=frexp(p+q,&qexp); + q=vorbis_fromdBlook(amp* + vorbis_invsqlook(q)* + vorbis_invsq2explook(qexp+m)- + ampoffset); + + do{ + curve[i++]*=q; + }while(map[i]==k); + } + vorbis_fpu_restore(fpu); +} + +#else + +#ifdef INT_LOOKUP +#include "lookup.c" /* catch this in the build system; we #include for + compilers (like gcc) that can't inline across + modules */ + +static int MLOOP_1[64]={ + 0,10,11,11, 12,12,12,12, 13,13,13,13, 13,13,13,13, + 14,14,14,14, 14,14,14,14, 14,14,14,14, 14,14,14,14, + 15,15,15,15, 15,15,15,15, 15,15,15,15, 15,15,15,15, + 15,15,15,15, 15,15,15,15, 15,15,15,15, 15,15,15,15, +}; + +static int MLOOP_2[64]={ + 0,4,5,5, 6,6,6,6, 7,7,7,7, 7,7,7,7, + 8,8,8,8, 8,8,8,8, 8,8,8,8, 8,8,8,8, + 9,9,9,9, 9,9,9,9, 9,9,9,9, 9,9,9,9, + 9,9,9,9, 9,9,9,9, 9,9,9,9, 9,9,9,9, +}; + +static int MLOOP_3[8]={0,1,2,2,3,3,3,3}; + + +/* side effect: changes *lsp to cosines of lsp */ +void vorbis_lsp_to_curve(float *curve,int *map,int n,int ln,float *lsp,int m, + float amp,float ampoffset){ + + /* 0 <= m < 256 */ + + /* set up for using all int later */ + int i; + int ampoffseti=rint(ampoffset*4096.f); + int ampi=rint(amp*16.f); + long *ilsp=alloca(m*sizeof(*ilsp)); + for(i=0;i>25])) + if(!(shift=MLOOP_2[(pi|qi)>>19])) + shift=MLOOP_3[(pi|qi)>>16]; + qi=(qi>>shift)*labs(ilsp[j-1]-wi); + pi=(pi>>shift)*labs(ilsp[j]-wi); + qexp+=shift; + } + if(!(shift=MLOOP_1[(pi|qi)>>25])) + if(!(shift=MLOOP_2[(pi|qi)>>19])) + shift=MLOOP_3[(pi|qi)>>16]; + + /* pi,qi normalized collectively, both tracked using qexp */ + + if(m&1){ + /* odd order filter; slightly assymetric */ + /* the last coefficient */ + qi=(qi>>shift)*labs(ilsp[j-1]-wi); + pi=(pi>>shift)<<14; + qexp+=shift; + + if(!(shift=MLOOP_1[(pi|qi)>>25])) + if(!(shift=MLOOP_2[(pi|qi)>>19])) + shift=MLOOP_3[(pi|qi)>>16]; + + pi>>=shift; + qi>>=shift; + qexp+=shift-14*((m+1)>>1); + + pi=((pi*pi)>>16); + qi=((qi*qi)>>16); + qexp=qexp*2+m; + + pi*=(1<<14)-((wi*wi)>>14); + qi+=pi>>14; + + }else{ + /* even order filter; still symmetric */ + + /* p*=p(1-w), q*=q(1+w), let normalization drift because it isn't + worth tracking step by step */ + + pi>>=shift; + qi>>=shift; + qexp+=shift-7*m; + + pi=((pi*pi)>>16); + qi=((qi*qi)>>16); + qexp=qexp*2+m; + + pi*=(1<<14)-wi; + qi*=(1<<14)+wi; + qi=(qi+pi)>>14; + + } + + + /* we've let the normalization drift because it wasn't important; + however, for the lookup, things must be normalized again. We + need at most one right shift or a number of left shifts */ + + if(qi&0xffff0000){ /* checks for 1.xxxxxxxxxxxxxxxx */ + qi>>=1; qexp++; + }else + while(qi && !(qi&0x8000)){ /* checks for 0.0xxxxxxxxxxxxxxx or less*/ + qi<<=1; qexp--; + } + + amp=vorbis_fromdBlook_i(ampi* /* n.4 */ + vorbis_invsqlook_i(qi,qexp)- + /* m.8, m+n<=8 */ + ampoffseti); /* 8.12[0] */ + + curve[i]*=amp; + while(map[++i]==k)curve[i]*=amp; + } +} + +#else + +/* old, nonoptimized but simple version for any poor sap who needs to + figure out what the hell this code does, or wants the other + fraction of a dB precision */ + +/* side effect: changes *lsp to cosines of lsp */ +void vorbis_lsp_to_curve(float *curve,int *map,int n,int ln,float *lsp,int m, + float amp,float ampoffset){ + int i; + float wdel=M_PI/ln; + for(i=0;i= i; j--) { + g[j-2] -= g[j]; + g[j] += g[j]; + } + } +} + +static int comp(const void *a,const void *b){ + return (*(float *)a<*(float *)b)-(*(float *)a>*(float *)b); +} + +/* Newton-Raphson-Maehly actually functioned as a decent root finder, + but there are root sets for which it gets into limit cycles + (exacerbated by zero suppression) and fails. We can't afford to + fail, even if the failure is 1 in 100,000,000, so we now use + Laguerre and later polish with Newton-Raphson (which can then + afford to fail) */ + +#define EPSILON 10e-7 +static int Laguerre_With_Deflation(float *a,int ord,float *r){ + int i,m; + double lastdelta=0.f; + double *defl=alloca(sizeof(*defl)*(ord+1)); + for(i=0;i<=ord;i++)defl[i]=a[i]; + + for(m=ord;m>0;m--){ + double new=0.f,delta; + + /* iterate a root */ + while(1){ + double p=defl[m],pp=0.f,ppp=0.f,denom; + + /* eval the polynomial and its first two derivatives */ + for(i=m;i>0;i--){ + ppp = new*ppp + pp; + pp = new*pp + p; + p = new*p + defl[i-1]; + } + + /* Laguerre's method */ + denom=(m-1) * ((m-1)*pp*pp - m*p*ppp); + if(denom<0) + return(-1); /* complex root! The LPC generator handed us a bad filter */ + + if(pp>0){ + denom = pp + sqrt(denom); + if(denom-(EPSILON))denom=-(EPSILON); + } + + delta = m*p/denom; + new -= delta; + + if(delta<0.f)delta*=-1; + + if(fabs(delta/new)<10e-12)break; + lastdelta=delta; + } + + r[m-1]=new; + + /* forward deflation */ + + for(i=m;i>0;i--) + defl[i-1]+=new*defl[i]; + defl++; + + } + return(0); +} + + +/* for spit-and-polish only */ +static int Newton_Raphson(float *a,int ord,float *r){ + int i, k, count=0; + double error=1.f; + double *root=alloca(ord*sizeof(*root)); + + for(i=0; i1e-20){ + error=0; + + for(i=0; i= 0; k--) { + + pp= pp* rooti + p; + p = p * rooti + a[k]; + } + + delta = p/pp; + root[i] -= delta; + error+= delta*delta; + } + + if(count>40)return(-1); + + count++; + } + + /* Replaced the original bubble sort with a real sort. With your + help, we can eliminate the bubble sort in our lifetime. --Monty */ + + for(i=0; i>1; + int g1_order,g2_order; + float *g1=alloca(sizeof(*g1)*(order2+1)); + float *g2=alloca(sizeof(*g2)*(order2+1)); + float *g1r=alloca(sizeof(*g1r)*(order2+1)); + float *g2r=alloca(sizeof(*g2r)*(order2+1)); + int i; + + /* even and odd are slightly different base cases */ + g1_order=(m+1)>>1; + g2_order=(m) >>1; + + /* Compute the lengths of the x polynomials. */ + /* Compute the first half of K & R F1 & F2 polynomials. */ + /* Compute half of the symmetric and antisymmetric polynomials. */ + /* Remove the roots at +1 and -1. */ + + g1[g1_order] = 1.f; + for(i=1;i<=g1_order;i++) g1[g1_order-i] = lpc[i-1]+lpc[m-i]; + g2[g2_order] = 1.f; + for(i=1;i<=g2_order;i++) g2[g2_order-i] = lpc[i-1]-lpc[m-i]; + + if(g1_order>g2_order){ + for(i=2; i<=g2_order;i++) g2[g2_order-i] += g2[g2_order-i+2]; + }else{ + for(i=1; i<=g1_order;i++) g1[g1_order-i] -= g1[g1_order-i+1]; + for(i=1; i<=g2_order;i++) g2[g2_order-i] += g2[g2_order-i+1]; + } + + /* Convert into polynomials in cos(alpha) */ + cheby(g1,g1_order); + cheby(g2,g2_order); + + /* Find the roots of the 2 even polynomials.*/ + if(Laguerre_With_Deflation(g1,g1_order,g1r) || + Laguerre_With_Deflation(g2,g2_order,g2r)) + return(-1); + + Newton_Raphson(g1,g1_order,g1r); /* if it fails, it leaves g1r alone */ + Newton_Raphson(g2,g2_order,g2r); /* if it fails, it leaves g2r alone */ + + qsort(g1r,g1_order,sizeof(*g1r),comp); + qsort(g2r,g2_order,sizeof(*g2r),comp); + + for(i=0;i. + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: LSP (also called LSF) conversion routines + last mod: $Id: lsp.h,v 1.11 2002/07/11 06:40:49 xiphmont Exp $ + + ********************************************************************/ + + +#ifndef _V_LSP_H_ +#define _V_LSP_H_ + +extern int vorbis_lpc_to_lsp(float *lpc,float *lsp,int m); + +extern void vorbis_lsp_to_curve(float *curve,int *map,int n,int ln, + float *lsp,int m, + float amp,float ampoffset); + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/mapping0.c b/src/sound/oggvorbis/vorbissrc/mapping0.c new file mode 100644 index 0000000..7949106 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/mapping0.c @@ -0,0 +1,787 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: channel mapping 0 implementation + last mod: $Id: mapping0.c,v 1.60 2003/09/01 23:05:49 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "codebook.h" +#include "window.h" +#include "registry.h" +#include "psy.h" +#include "misc.h" + +/* simplistic, wasteful way of doing this (unique lookup for each + mode/submapping); there should be a central repository for + identical lookups. That will require minor work, so I'm putting it + off as low priority. + + Why a lookup for each backend in a given mode? Because the + blocksize is set by the mode, and low backend lookups may require + parameters from other areas of the mode/mapping */ + +static void mapping0_free_info(vorbis_info_mapping *i){ + vorbis_info_mapping0 *info=(vorbis_info_mapping0 *)i; + if(info){ + memset(info,0,sizeof(*info)); + _ogg_free(info); + } +} + +static int ilog(unsigned int v){ + int ret=0; + if(v)--v; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +static void mapping0_pack(vorbis_info *vi,vorbis_info_mapping *vm, + oggpack_buffer *opb){ + int i; + vorbis_info_mapping0 *info=(vorbis_info_mapping0 *)vm; + + /* another 'we meant to do it this way' hack... up to beta 4, we + packed 4 binary zeros here to signify one submapping in use. We + now redefine that to mean four bitflags that indicate use of + deeper features; bit0:submappings, bit1:coupling, + bit2,3:reserved. This is backward compatable with all actual uses + of the beta code. */ + + if(info->submaps>1){ + oggpack_write(opb,1,1); + oggpack_write(opb,info->submaps-1,4); + }else + oggpack_write(opb,0,1); + + if(info->coupling_steps>0){ + oggpack_write(opb,1,1); + oggpack_write(opb,info->coupling_steps-1,8); + + for(i=0;icoupling_steps;i++){ + oggpack_write(opb,info->coupling_mag[i],ilog(vi->channels)); + oggpack_write(opb,info->coupling_ang[i],ilog(vi->channels)); + } + }else + oggpack_write(opb,0,1); + + oggpack_write(opb,0,2); /* 2,3:reserved */ + + /* we don't write the channel submappings if we only have one... */ + if(info->submaps>1){ + for(i=0;ichannels;i++) + oggpack_write(opb,info->chmuxlist[i],4); + } + for(i=0;isubmaps;i++){ + oggpack_write(opb,0,8); /* time submap unused */ + oggpack_write(opb,info->floorsubmap[i],8); + oggpack_write(opb,info->residuesubmap[i],8); + } +} + +/* also responsible for range checking */ +static vorbis_info_mapping *mapping0_unpack(vorbis_info *vi,oggpack_buffer *opb){ + int i; + vorbis_info_mapping0 *info=_ogg_calloc(1,sizeof(*info)); + codec_setup_info *ci=vi->codec_setup; + memset(info,0,sizeof(*info)); + + if(oggpack_read(opb,1)) + info->submaps=oggpack_read(opb,4)+1; + else + info->submaps=1; + + if(oggpack_read(opb,1)){ + info->coupling_steps=oggpack_read(opb,8)+1; + + for(i=0;icoupling_steps;i++){ + int testM=info->coupling_mag[i]=oggpack_read(opb,ilog(vi->channels)); + int testA=info->coupling_ang[i]=oggpack_read(opb,ilog(vi->channels)); + + if(testM<0 || + testA<0 || + testM==testA || + testM>=vi->channels || + testA>=vi->channels) goto err_out; + } + + } + + if(oggpack_read(opb,2)>0)goto err_out; /* 2,3:reserved */ + + if(info->submaps>1){ + for(i=0;ichannels;i++){ + info->chmuxlist[i]=oggpack_read(opb,4); + if(info->chmuxlist[i]>=info->submaps)goto err_out; + } + } + for(i=0;isubmaps;i++){ + oggpack_read(opb,8); /* time submap unused */ + info->floorsubmap[i]=oggpack_read(opb,8); + if(info->floorsubmap[i]>=ci->floors)goto err_out; + info->residuesubmap[i]=oggpack_read(opb,8); + if(info->residuesubmap[i]>=ci->residues)goto err_out; + } + + return info; + + err_out: + mapping0_free_info(info); + return(NULL); +} + +#include "os.h" +#include "lpc.h" +#include "lsp.h" +#include "envelope.h" +#include "mdct.h" +#include "psy.h" +#include "scales.h" + +#if 0 +static long seq=0; +static ogg_int64_t total=0; +static float FLOOR1_fromdB_LOOKUP[256]={ + 1.0649863e-07F, 1.1341951e-07F, 1.2079015e-07F, 1.2863978e-07F, + 1.3699951e-07F, 1.4590251e-07F, 1.5538408e-07F, 1.6548181e-07F, + 1.7623575e-07F, 1.8768855e-07F, 1.9988561e-07F, 2.128753e-07F, + 2.2670913e-07F, 2.4144197e-07F, 2.5713223e-07F, 2.7384213e-07F, + 2.9163793e-07F, 3.1059021e-07F, 3.3077411e-07F, 3.5226968e-07F, + 3.7516214e-07F, 3.9954229e-07F, 4.2550680e-07F, 4.5315863e-07F, + 4.8260743e-07F, 5.1396998e-07F, 5.4737065e-07F, 5.8294187e-07F, + 6.2082472e-07F, 6.6116941e-07F, 7.0413592e-07F, 7.4989464e-07F, + 7.9862701e-07F, 8.5052630e-07F, 9.0579828e-07F, 9.6466216e-07F, + 1.0273513e-06F, 1.0941144e-06F, 1.1652161e-06F, 1.2409384e-06F, + 1.3215816e-06F, 1.4074654e-06F, 1.4989305e-06F, 1.5963394e-06F, + 1.7000785e-06F, 1.8105592e-06F, 1.9282195e-06F, 2.0535261e-06F, + 2.1869758e-06F, 2.3290978e-06F, 2.4804557e-06F, 2.6416497e-06F, + 2.8133190e-06F, 2.9961443e-06F, 3.1908506e-06F, 3.3982101e-06F, + 3.6190449e-06F, 3.8542308e-06F, 4.1047004e-06F, 4.3714470e-06F, + 4.6555282e-06F, 4.9580707e-06F, 5.2802740e-06F, 5.6234160e-06F, + 5.9888572e-06F, 6.3780469e-06F, 6.7925283e-06F, 7.2339451e-06F, + 7.7040476e-06F, 8.2047000e-06F, 8.7378876e-06F, 9.3057248e-06F, + 9.9104632e-06F, 1.0554501e-05F, 1.1240392e-05F, 1.1970856e-05F, + 1.2748789e-05F, 1.3577278e-05F, 1.4459606e-05F, 1.5399272e-05F, + 1.6400004e-05F, 1.7465768e-05F, 1.8600792e-05F, 1.9809576e-05F, + 2.1096914e-05F, 2.2467911e-05F, 2.3928002e-05F, 2.5482978e-05F, + 2.7139006e-05F, 2.8902651e-05F, 3.0780908e-05F, 3.2781225e-05F, + 3.4911534e-05F, 3.7180282e-05F, 3.9596466e-05F, 4.2169667e-05F, + 4.4910090e-05F, 4.7828601e-05F, 5.0936773e-05F, 5.4246931e-05F, + 5.7772202e-05F, 6.1526565e-05F, 6.5524908e-05F, 6.9783085e-05F, + 7.4317983e-05F, 7.9147585e-05F, 8.4291040e-05F, 8.9768747e-05F, + 9.5602426e-05F, 0.00010181521F, 0.00010843174F, 0.00011547824F, + 0.00012298267F, 0.00013097477F, 0.00013948625F, 0.00014855085F, + 0.00015820453F, 0.00016848555F, 0.00017943469F, 0.00019109536F, + 0.00020351382F, 0.00021673929F, 0.00023082423F, 0.00024582449F, + 0.00026179955F, 0.00027881276F, 0.00029693158F, 0.00031622787F, + 0.00033677814F, 0.00035866388F, 0.00038197188F, 0.00040679456F, + 0.00043323036F, 0.00046138411F, 0.00049136745F, 0.00052329927F, + 0.00055730621F, 0.00059352311F, 0.00063209358F, 0.00067317058F, + 0.00071691700F, 0.00076350630F, 0.00081312324F, 0.00086596457F, + 0.00092223983F, 0.00098217216F, 0.0010459992F, 0.0011139742F, + 0.0011863665F, 0.0012634633F, 0.0013455702F, 0.0014330129F, + 0.0015261382F, 0.0016253153F, 0.0017309374F, 0.0018434235F, + 0.0019632195F, 0.0020908006F, 0.0022266726F, 0.0023713743F, + 0.0025254795F, 0.0026895994F, 0.0028643847F, 0.0030505286F, + 0.0032487691F, 0.0034598925F, 0.0036847358F, 0.0039241906F, + 0.0041792066F, 0.0044507950F, 0.0047400328F, 0.0050480668F, + 0.0053761186F, 0.0057254891F, 0.0060975636F, 0.0064938176F, + 0.0069158225F, 0.0073652516F, 0.0078438871F, 0.0083536271F, + 0.0088964928F, 0.009474637F, 0.010090352F, 0.010746080F, + 0.011444421F, 0.012188144F, 0.012980198F, 0.013823725F, + 0.014722068F, 0.015678791F, 0.016697687F, 0.017782797F, + 0.018938423F, 0.020169149F, 0.021479854F, 0.022875735F, + 0.024362330F, 0.025945531F, 0.027631618F, 0.029427276F, + 0.031339626F, 0.033376252F, 0.035545228F, 0.037855157F, + 0.040315199F, 0.042935108F, 0.045725273F, 0.048696758F, + 0.051861348F, 0.055231591F, 0.058820850F, 0.062643361F, + 0.066714279F, 0.071049749F, 0.075666962F, 0.080584227F, + 0.085821044F, 0.091398179F, 0.097337747F, 0.10366330F, + 0.11039993F, 0.11757434F, 0.12521498F, 0.13335215F, + 0.14201813F, 0.15124727F, 0.16107617F, 0.17154380F, + 0.18269168F, 0.19456402F, 0.20720788F, 0.22067342F, + 0.23501402F, 0.25028656F, 0.26655159F, 0.28387361F, + 0.30232132F, 0.32196786F, 0.34289114F, 0.36517414F, + 0.38890521F, 0.41417847F, 0.44109412F, 0.46975890F, + 0.50028648F, 0.53279791F, 0.56742212F, 0.60429640F, + 0.64356699F, 0.68538959F, 0.72993007F, 0.77736504F, + 0.82788260F, 0.88168307F, 0.9389798F, 1.F, +}; + +#endif + +extern int *floor1_fit(vorbis_block *vb,vorbis_look_floor *look, + const float *logmdct, /* in */ + const float *logmask); +extern int *floor1_interpolate_fit(vorbis_block *vb,vorbis_look_floor *look, + int *A,int *B, + int del); +extern int floor1_encode(vorbis_block *vb,vorbis_look_floor *look, + int *post,int *ilogmask); + + +static int mapping0_forward(vorbis_block *vb){ + vorbis_dsp_state *vd=vb->vd; + vorbis_info *vi=vd->vi; + codec_setup_info *ci=vi->codec_setup; + private_state *b=vb->vd->backend_state; + vorbis_block_internal *vbi=(vorbis_block_internal *)vb->internal; + int n=vb->pcmend; + int i,j,k; + + int *nonzero = alloca(sizeof(*nonzero)*vi->channels); + float **gmdct = _vorbis_block_alloc(vb,vi->channels*sizeof(*gmdct)); + int **ilogmaskch= _vorbis_block_alloc(vb,vi->channels*sizeof(*ilogmaskch)); + int ***floor_posts = _vorbis_block_alloc(vb,vi->channels*sizeof(*floor_posts)); + + float global_ampmax=vbi->ampmax; + float *local_ampmax=alloca(sizeof(*local_ampmax)*vi->channels); + int blocktype=vbi->blocktype; + + int modenumber=vb->W; + vorbis_info_mapping0 *info=ci->map_param[modenumber]; + vorbis_look_psy *psy_look= + b->psy+blocktype+(vb->W?2:0); + + vb->mode=modenumber; + + for(i=0;ichannels;i++){ + float scale=4.f/n; + float scale_dB; + + float *pcm =vb->pcm[i]; + float *logfft =pcm; + + gmdct[i]=_vorbis_block_alloc(vb,n/2*sizeof(**gmdct)); + + scale_dB=todB(&scale); + +#if 0 + if(vi->channels==2) + if(i==0) + _analysis_output("pcmL",seq,pcm,n,0,0,total-n/2); + else + _analysis_output("pcmR",seq,pcm,n,0,0,total-n/2); +#endif + + /* window the PCM data */ + _vorbis_apply_window(pcm,b->window,ci->blocksizes,vb->lW,vb->W,vb->nW); + +#if 0 + if(vi->channels==2) + if(i==0) + _analysis_output("windowedL",seq,pcm,n,0,0,total-n/2); + else + _analysis_output("windowedR",seq,pcm,n,0,0,total-n/2); +#endif + + /* transform the PCM data */ + /* only MDCT right now.... */ + mdct_forward(b->transform[vb->W][0],pcm,gmdct[i]); + + /* FFT yields more accurate tonal estimation (not phase sensitive) */ + drft_forward(&b->fft_look[vb->W],pcm); + logfft[0]=scale_dB+todB(pcm); + local_ampmax[i]=logfft[0]; + for(j=1;j>1]=scale_dB+.5f*todB(&temp); + if(temp>local_ampmax[i])local_ampmax[i]=temp; + } + + if(local_ampmax[i]>0.f)local_ampmax[i]=0.f; + if(local_ampmax[i]>global_ampmax)global_ampmax=local_ampmax[i]; + +#if 0 + if(vi->channels==2) + if(i==0) + _analysis_output("fftL",seq,logfft,n/2,1,0,0); + else + _analysis_output("fftR",seq,logfft,n/2,1,0,0); +#endif + + } + + { + float *noise = _vorbis_block_alloc(vb,n/2*sizeof(*noise)); + float *tone = _vorbis_block_alloc(vb,n/2*sizeof(*tone)); + + for(i=0;ichannels;i++){ + /* the encoder setup assumes that all the modes used by any + specific bitrate tweaking use the same floor */ + + int submap=info->chmuxlist[i]; + + /* the following makes things clearer to *me* anyway */ + float *mdct =gmdct[i]; + float *logfft =vb->pcm[i]; + + float *logmdct =logfft+n/2; + float *logmask =logfft; + + vb->mode=modenumber; + + floor_posts[i]=_vorbis_block_alloc(vb,PACKETBLOBS*sizeof(**floor_posts)); + memset(floor_posts[i],0,sizeof(**floor_posts)*PACKETBLOBS); + + for(j=0;jchannels==2){ + if(i==0) + _analysis_output("mdctL",seq,logmdct,n/2,1,0,0); + else + _analysis_output("mdctR",seq,logmdct,n/2,1,0,0); + }else{ + _analysis_output("mdct",seq,logmdct,n/2,1,0,0); + } +#endif + + /* first step; noise masking. Not only does 'noise masking' + give us curves from which we can decide how much resolution + to give noise parts of the spectrum, it also implicitly hands + us a tonality estimate (the larger the value in the + 'noise_depth' vector, the more tonal that area is) */ + + _vp_noisemask(psy_look, + logmdct, + noise); /* noise does not have by-frequency offset + bias applied yet */ +#if 0 + if(vi->channels==2){ + if(i==0) + _analysis_output("noiseL",seq,noise,n/2,1,0,0); + else + _analysis_output("noiseR",seq,noise,n/2,1,0,0); + } +#endif + + /* second step: 'all the other crap'; all the stuff that isn't + computed/fit for bitrate management goes in the second psy + vector. This includes tone masking, peak limiting and ATH */ + + _vp_tonemask(psy_look, + logfft, + tone, + global_ampmax, + local_ampmax[i]); + +#if 0 + if(vi->channels==2){ + if(i==0) + _analysis_output("toneL",seq,tone,n/2,1,0,0); + else + _analysis_output("toneR",seq,tone,n/2,1,0,0); + } +#endif + + /* third step; we offset the noise vectors, overlay tone + masking. We then do a floor1-specific line fit. If we're + performing bitrate management, the line fit is performed + multiple times for up/down tweakage on demand. */ + + _vp_offset_and_mix(psy_look, + noise, + tone, + 1, + logmask); + +#if 0 + if(vi->channels==2){ + if(i==0) + _analysis_output("mask1L",seq,logmask,n/2,1,0,0); + else + _analysis_output("mask1R",seq,logmask,n/2,1,0,0); + } +#endif + + /* this algorithm is hardwired to floor 1 for now; abort out if + we're *not* floor1. This won't happen unless someone has + broken the encode setup lib. Guard it anyway. */ + if(ci->floor_type[info->floorsubmap[submap]]!=1)return(-1); + + floor_posts[i][PACKETBLOBS/2]= + floor1_fit(vb,b->flr[info->floorsubmap[submap]], + logmdct, + logmask); + + /* are we managing bitrate? If so, perform two more fits for + later rate tweaking (fits represent hi/lo) */ + if(vorbis_bitrate_managed(vb) && floor_posts[i][PACKETBLOBS/2]){ + /* higher rate by way of lower noise curve */ + + _vp_offset_and_mix(psy_look, + noise, + tone, + 2, + logmask); + +#if 0 + if(vi->channels==2){ + if(i==0) + _analysis_output("mask2L",seq,logmask,n/2,1,0,0); + else + _analysis_output("mask2R",seq,logmask,n/2,1,0,0); + } +#endif + + floor_posts[i][PACKETBLOBS-1]= + floor1_fit(vb,b->flr[info->floorsubmap[submap]], + logmdct, + logmask); + + /* lower rate by way of higher noise curve */ + _vp_offset_and_mix(psy_look, + noise, + tone, + 0, + logmask); + +#if 0 + if(vi->channels==2) + if(i==0) + _analysis_output("mask0L",seq,logmask,n/2,1,0,0); + else + _analysis_output("mask0R",seq,logmask,n/2,1,0,0); +#endif + + floor_posts[i][0]= + floor1_fit(vb,b->flr[info->floorsubmap[submap]], + logmdct, + logmask); + + /* we also interpolate a range of intermediate curves for + intermediate rates */ + for(k=1;kflr[info->floorsubmap[submap]], + floor_posts[i][0], + floor_posts[i][PACKETBLOBS/2], + k*65536/(PACKETBLOBS/2)); + for(k=PACKETBLOBS/2+1;kflr[info->floorsubmap[submap]], + floor_posts[i][PACKETBLOBS/2], + floor_posts[i][PACKETBLOBS-1], + (k-PACKETBLOBS/2)*65536/(PACKETBLOBS/2)); + } + } + } + vbi->ampmax=global_ampmax; + + /* + the next phases are performed once for vbr-only and PACKETBLOB + times for bitrate managed modes. + + 1) encode actual mode being used + 2) encode the floor for each channel, compute coded mask curve/res + 3) normalize and couple. + 4) encode residue + 5) save packet bytes to the packetblob vector + + */ + + /* iterate over the many masking curve fits we've created */ + + { + float **res_bundle=alloca(sizeof(*res_bundle)*vi->channels); + float **couple_bundle=alloca(sizeof(*couple_bundle)*vi->channels); + int *zerobundle=alloca(sizeof(*zerobundle)*vi->channels); + int **sortindex=alloca(sizeof(*sortindex)*vi->channels); + float **mag_memo; + int **mag_sort; + + if(info->coupling_steps){ + mag_memo=_vp_quantize_couple_memo(vb, + &ci->psy_g_param, + psy_look, + info, + gmdct); + + mag_sort=_vp_quantize_couple_sort(vb, + psy_look, + info, + mag_memo); + } + + memset(sortindex,0,sizeof(*sortindex)*vi->channels); + if(psy_look->vi->normal_channel_p){ + for(i=0;ichannels;i++){ + float *mdct =gmdct[i]; + sortindex[i]=alloca(sizeof(**sortindex)*n/2); + _vp_noise_normalize_sort(psy_look,mdct,sortindex[i]); + } + } + + for(k=(vorbis_bitrate_managed(vb)?0:PACKETBLOBS/2); + k<=(vorbis_bitrate_managed(vb)?PACKETBLOBS-1:PACKETBLOBS/2); + k++){ + + /* start out our new packet blob with packet type and mode */ + /* Encode the packet type */ + oggpack_write(&vb->opb,0,1); + /* Encode the modenumber */ + /* Encode frame mode, pre,post windowsize, then dispatch */ + oggpack_write(&vb->opb,modenumber,b->modebits); + if(vb->W){ + oggpack_write(&vb->opb,vb->lW,1); + oggpack_write(&vb->opb,vb->nW,1); + } + + /* encode floor, compute masking curve, sep out residue */ + for(i=0;ichannels;i++){ + int submap=info->chmuxlist[i]; + float *mdct =gmdct[i]; + float *res =vb->pcm[i]; + int *ilogmask=ilogmaskch[i]= + _vorbis_block_alloc(vb,n/2*sizeof(**gmdct)); + + nonzero[i]=floor1_encode(vb,b->flr[info->floorsubmap[submap]], + floor_posts[i][k], + ilogmask); +#if 0 + { + char buf[80]; + sprintf(buf,"maskI%c%d",i?'R':'L',k); + float work[n/2]; + for(j=0;jpsy_g_param.sliding_lowpass[vb->W][k]); + + _vp_noise_normalize(psy_look,res,res+n/2,sortindex[i]); + + +#if 0 + { + char buf[80]; + float work[n/2]; + for(j=0;jcoupling_steps){ + _vp_couple(k, + &ci->psy_g_param, + psy_look, + info, + vb->pcm, + mag_memo, + mag_sort, + ilogmaskch, + nonzero, + ci->psy_g_param.sliding_lowpass[vb->W][k]); + } + + /* classify and encode by submap */ + for(i=0;isubmaps;i++){ + int ch_in_bundle=0; + long **classifications; + int resnum=info->residuesubmap[i]; + + for(j=0;jchannels;j++){ + if(info->chmuxlist[j]==i){ + zerobundle[ch_in_bundle]=0; + if(nonzero[j])zerobundle[ch_in_bundle]=1; + res_bundle[ch_in_bundle]=vb->pcm[j]; + couple_bundle[ch_in_bundle++]=vb->pcm[j]+n/2; + } + } + + classifications=_residue_P[ci->residue_type[resnum]]-> + class(vb,b->residue[resnum],couple_bundle,zerobundle,ch_in_bundle); + + _residue_P[ci->residue_type[resnum]]-> + forward(vb,b->residue[resnum], + couple_bundle,NULL,zerobundle,ch_in_bundle,classifications); + } + + /* ok, done encoding. Mark this protopacket and prepare next. */ + oggpack_writealign(&vb->opb); + vbi->packetblob_markers[k]=oggpack_bytes(&vb->opb); + + } + + } + +#if 0 + seq++; + total+=ci->blocksizes[vb->W]/4+ci->blocksizes[vb->nW]/4; +#endif + return(0); +} + +static int mapping0_inverse(vorbis_block *vb,vorbis_info_mapping *l){ + vorbis_dsp_state *vd=vb->vd; + vorbis_info *vi=vd->vi; + codec_setup_info *ci=vi->codec_setup; + private_state *b=vd->backend_state; + vorbis_info_mapping0 *info=(vorbis_info_mapping0 *)l; + int hs=ci->halfrate_flag; + + int i,j; + long n=vb->pcmend=ci->blocksizes[vb->W]; + + float **pcmbundle=alloca(sizeof(*pcmbundle)*vi->channels); + int *zerobundle=alloca(sizeof(*zerobundle)*vi->channels); + + int *nonzero =alloca(sizeof(*nonzero)*vi->channels); + void **floormemo=alloca(sizeof(*floormemo)*vi->channels); + + /* recover the spectral envelope; store it in the PCM vector for now */ + for(i=0;ichannels;i++){ + int submap=info->chmuxlist[i]; + floormemo[i]=_floor_P[ci->floor_type[info->floorsubmap[submap]]]-> + inverse1(vb,b->flr[info->floorsubmap[submap]]); + if(floormemo[i]) + nonzero[i]=1; + else + nonzero[i]=0; + memset(vb->pcm[i],0,sizeof(*vb->pcm[i])*n/2); + } + + /* channel coupling can 'dirty' the nonzero listing */ + for(i=0;icoupling_steps;i++){ + if(nonzero[info->coupling_mag[i]] || + nonzero[info->coupling_ang[i]]){ + nonzero[info->coupling_mag[i]]=1; + nonzero[info->coupling_ang[i]]=1; + } + } + + /* recover the residue into our working vectors */ + for(i=0;isubmaps;i++){ + int ch_in_bundle=0; + for(j=0;jchannels;j++){ + if(info->chmuxlist[j]==i){ + if(nonzero[j]) + zerobundle[ch_in_bundle]=1; + else + zerobundle[ch_in_bundle]=0; + pcmbundle[ch_in_bundle++]=vb->pcm[j]; + } + } + + _residue_P[ci->residue_type[info->residuesubmap[i]]]-> + inverse(vb,b->residue[info->residuesubmap[i]], + pcmbundle,zerobundle,ch_in_bundle); + } + + /* channel coupling */ + for(i=info->coupling_steps-1;i>=0;i--){ + float *pcmM=vb->pcm[info->coupling_mag[i]]; + float *pcmA=vb->pcm[info->coupling_ang[i]]; + + for(j=0;j0) + if(ang>0){ + pcmM[j]=mag; + pcmA[j]=mag-ang; + }else{ + pcmA[j]=mag; + pcmM[j]=mag+ang; + } + else + if(ang>0){ + pcmM[j]=mag; + pcmA[j]=mag+ang; + }else{ + pcmA[j]=mag; + pcmM[j]=mag-ang; + } + } + } + + /* compute and apply spectral envelope */ + for(i=0;ichannels;i++){ + float *pcm=vb->pcm[i]; + int submap=info->chmuxlist[i]; + _floor_P[ci->floor_type[info->floorsubmap[submap]]]-> + inverse2(vb,b->flr[info->floorsubmap[submap]], + floormemo[i],pcm); + } + + /* transform the PCM data; takes PCM vector, vb; modifies PCM vector */ + /* only MDCT right now.... */ + for(i=0;ichannels;i++){ + float *pcm=vb->pcm[i]; + mdct_backward(b->transform[vb->W][0],pcm,pcm); + } + + /* all done! */ + return(0); +} + +/* export hooks */ +vorbis_func_mapping mapping0_exportbundle={ + &mapping0_pack, + &mapping0_unpack, + &mapping0_free_info, + &mapping0_forward, + &mapping0_inverse +}; + diff --git a/src/sound/oggvorbis/vorbissrc/masking.h b/src/sound/oggvorbis/vorbissrc/masking.h new file mode 100644 index 0000000..7cf1e5f --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/masking.h @@ -0,0 +1,809 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: masking curve data for psychoacoustics + last mod: $Id: masking.h,v 1.24 2002/07/01 11:20:11 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_MASKING_H_ +#define _V_MASKING_H_ + +/* more detailed ATH; the bass if flat to save stressing the floor + overly for only a bin or two of savings. */ + +#define MAX_ATH 88 +static float ATH[]={ + /*15*/ -51, -52, -53, -54, -55, -56, -57, -58, + /*31*/ -59, -60, -61, -62, -63, -64, -65, -66, + /*63*/ -67, -68, -69, -70, -71, -72, -73, -74, + /*125*/ -75, -76, -77, -78, -80, -81, -82, -83, + /*250*/ -84, -85, -86, -87, -88, -88, -89, -89, + /*500*/ -90, -91, -91, -92, -93, -94, -95, -96, + /*1k*/ -96, -97, -98, -98, -99, -99,-100,-100, + /*2k*/ -101,-102,-103,-104,-106,-107,-107,-107, + /*4k*/ -107,-105,-103,-102,-101, -99, -98, -96, + /*8k*/ -95, -95, -96, -97, -96, -95, -93, -90, + /*16k*/ -80, -70, -50, -40, -30, -30, -30, -30 +}; + +/* The tone masking curves from Ehmer's and Fielder's papers have been + replaced by an empirically collected data set. The previously + published values were, far too often, simply on crack. */ + +#define EHMER_OFFSET 16 +#define EHMER_MAX 56 + +/* masking tones from -50 to 0dB, 62.5 through 16kHz at half octaves + test tones from -2 octaves to +5 octaves sampled at eighth octaves */ +/* (Vorbis 0dB, the loudest possible tone, is assumed to be ~100dB SPL + for collection of these curves) */ + +static float tonemasks[P_BANDS][6][EHMER_MAX]={ + /* 62.5 Hz */ + {{ -60, -60, -60, -60, -60, -60, -60, -60, + -60, -60, -60, -60, -62, -62, -65, -73, + -69, -68, -68, -67, -70, -70, -72, -74, + -75, -79, -79, -80, -83, -88, -93, -100, + -110, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -53, -61, -66, + -66, -68, -67, -70, -76, -76, -72, -73, + -75, -76, -78, -79, -83, -88, -93, -100, + -110, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -37, -37, -37, -37, -37, -37, -37, -37, + -38, -40, -42, -46, -48, -53, -55, -62, + -65, -58, -56, -56, -61, -60, -65, -67, + -69, -71, -77, -77, -78, -80, -82, -84, + -88, -93, -98, -106, -112, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -25, -25, -25, -25, -25, -25, -25, -25, + -25, -26, -27, -29, -32, -38, -48, -52, + -52, -50, -48, -48, -51, -52, -54, -60, + -67, -67, -66, -68, -69, -73, -73, -76, + -80, -81, -81, -85, -85, -86, -88, -93, + -100, -110, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -16, -16, -16, -16, -16, -16, -16, -16, + -17, -19, -20, -22, -26, -28, -31, -40, + -47, -39, -39, -40, -42, -43, -47, -51, + -57, -52, -55, -55, -60, -58, -62, -63, + -70, -67, -69, -72, -73, -77, -80, -82, + -83, -87, -90, -94, -98, -104, -115, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -8, -8, -8, -8, -8, -8, -8, -8, + -8, -8, -10, -11, -15, -19, -25, -30, + -34, -31, -30, -31, -29, -32, -35, -42, + -48, -42, -44, -46, -50, -50, -51, -52, + -59, -54, -55, -55, -58, -62, -63, -66, + -72, -73, -76, -75, -78, -80, -80, -81, + -84, -88, -90, -94, -98, -101, -106, -110}}, + /* 88Hz */ + {{ -66, -66, -66, -66, -66, -66, -66, -66, + -66, -66, -66, -66, -66, -67, -67, -67, + -76, -72, -71, -74, -76, -76, -75, -78, + -79, -79, -81, -83, -86, -89, -93, -97, + -100, -105, -110, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -47, -47, -47, -47, -47, -47, -47, -47, + -47, -47, -47, -48, -51, -55, -59, -66, + -66, -66, -67, -66, -68, -69, -70, -74, + -79, -77, -77, -78, -80, -81, -82, -84, + -86, -88, -91, -95, -100, -108, -116, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -36, -36, -36, -36, -36, -36, -36, -36, + -36, -37, -37, -41, -44, -48, -51, -58, + -62, -60, -57, -59, -59, -60, -63, -65, + -72, -71, -70, -72, -74, -77, -76, -78, + -81, -81, -80, -83, -86, -91, -96, -100, + -105, -110, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -28, -28, -28, -28, -28, -28, -28, -28, + -28, -30, -32, -32, -33, -35, -41, -49, + -50, -49, -47, -48, -48, -52, -51, -57, + -65, -61, -59, -61, -64, -69, -70, -74, + -77, -77, -78, -81, -84, -85, -87, -90, + -92, -96, -100, -107, -112, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -19, -19, -19, -19, -19, -19, -19, -19, + -20, -21, -23, -27, -30, -35, -36, -41, + -46, -44, -42, -40, -41, -41, -43, -48, + -55, -53, -52, -53, -56, -59, -58, -60, + -67, -66, -69, -71, -72, -75, -79, -81, + -84, -87, -90, -93, -97, -101, -107, -114, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -9, -9, -9, -9, -9, -9, -9, -9, + -11, -12, -12, -15, -16, -20, -23, -30, + -37, -34, -33, -34, -31, -32, -32, -38, + -47, -44, -41, -40, -47, -49, -46, -46, + -58, -50, -50, -54, -58, -62, -64, -67, + -67, -70, -72, -76, -79, -83, -87, -91, + -96, -100, -104, -110, -999, -999, -999, -999}}, + /* 125 Hz */ + {{ -62, -62, -62, -62, -62, -62, -62, -62, + -62, -62, -63, -64, -66, -67, -66, -68, + -75, -72, -76, -75, -76, -78, -79, -82, + -84, -85, -90, -94, -101, -110, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -60, -60, -61, -63, -66, + -71, -68, -70, -70, -71, -72, -72, -75, + -81, -78, -79, -82, -83, -86, -90, -97, + -103, -113, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -53, -53, -53, -53, -53, -53, -53, -53, + -53, -54, -55, -57, -56, -57, -55, -61, + -65, -60, -60, -62, -63, -63, -66, -68, + -74, -73, -75, -75, -78, -80, -80, -82, + -85, -90, -96, -101, -108, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -46, -46, -46, -46, -46, -46, -46, -46, + -46, -46, -47, -47, -47, -47, -48, -51, + -57, -51, -49, -50, -51, -53, -54, -59, + -66, -60, -62, -67, -67, -70, -72, -75, + -76, -78, -81, -85, -88, -94, -97, -104, + -112, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -36, -36, -36, -36, -36, -36, -36, -36, + -39, -41, -42, -42, -39, -38, -41, -43, + -52, -44, -40, -39, -37, -37, -40, -47, + -54, -50, -48, -50, -55, -61, -59, -62, + -66, -66, -66, -69, -69, -73, -74, -74, + -75, -77, -79, -82, -87, -91, -95, -100, + -108, -115, -999, -999, -999, -999, -999, -999}, + { -28, -26, -24, -22, -20, -20, -23, -29, + -30, -31, -28, -27, -28, -28, -28, -35, + -40, -33, -32, -29, -30, -30, -30, -37, + -45, -41, -37, -38, -45, -47, -47, -48, + -53, -49, -48, -50, -49, -49, -51, -52, + -58, -56, -57, -56, -60, -61, -62, -70, + -72, -74, -78, -83, -88, -93, -100, -106}}, + /* 177 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -110, -105, -100, -95, -91, -87, -83, + -80, -78, -76, -78, -78, -81, -83, -85, + -86, -85, -86, -87, -90, -97, -107, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -110, -105, -100, -95, -90, + -85, -81, -77, -73, -70, -67, -67, -68, + -75, -73, -70, -69, -70, -72, -75, -79, + -84, -83, -84, -86, -88, -89, -89, -93, + -98, -105, -112, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-105, -100, -95, -90, -85, -80, -76, -71, + -68, -68, -65, -63, -63, -62, -62, -64, + -65, -64, -61, -62, -63, -64, -66, -68, + -73, -73, -74, -75, -76, -81, -83, -85, + -88, -89, -92, -95, -100, -108, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -80, -75, -71, -68, -65, -63, -62, -61, + -61, -61, -61, -59, -56, -57, -53, -50, + -58, -52, -50, -50, -52, -53, -54, -58, + -67, -63, -67, -68, -72, -75, -78, -80, + -81, -81, -82, -85, -89, -90, -93, -97, + -101, -107, -114, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + { -65, -61, -59, -57, -56, -55, -55, -56, + -56, -57, -55, -53, -52, -47, -44, -44, + -50, -44, -41, -39, -39, -42, -40, -46, + -51, -49, -50, -53, -54, -63, -60, -61, + -62, -66, -66, -66, -70, -73, -74, -75, + -76, -75, -79, -85, -89, -91, -96, -102, + -110, -999, -999, -999, -999, -999, -999, -999}, + { -52, -50, -49, -49, -48, -48, -48, -49, + -50, -50, -49, -46, -43, -39, -35, -33, + -38, -36, -32, -29, -32, -32, -32, -35, + -44, -39, -38, -38, -46, -50, -45, -46, + -53, -50, -50, -50, -54, -54, -53, -53, + -56, -57, -59, -66, -70, -72, -74, -79, + -83, -85, -90, -97, -114, -999, -999, -999}}, + /* 250 Hz */ + {{-999, -999, -999, -999, -999, -999, -110, -105, + -100, -95, -90, -86, -80, -75, -75, -79, + -80, -79, -80, -81, -82, -88, -95, -103, + -110, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -108, -103, -98, -93, + -88, -83, -79, -78, -75, -71, -67, -68, + -73, -73, -72, -73, -75, -77, -80, -82, + -88, -93, -100, -107, -114, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -110, -105, -101, -96, -90, + -86, -81, -77, -73, -69, -66, -61, -62, + -66, -64, -62, -65, -66, -70, -72, -76, + -81, -80, -84, -90, -95, -102, -110, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -107, -103, -97, -92, -88, + -83, -79, -74, -70, -66, -59, -53, -58, + -62, -55, -54, -54, -54, -58, -61, -62, + -72, -70, -72, -75, -78, -80, -81, -80, + -83, -83, -88, -93, -100, -107, -115, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -105, -100, -95, -90, -85, + -80, -75, -70, -66, -62, -56, -48, -44, + -48, -46, -46, -43, -46, -48, -48, -51, + -58, -58, -59, -60, -62, -62, -61, -61, + -65, -64, -65, -68, -70, -74, -75, -78, + -81, -86, -95, -110, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -105, -100, -95, -90, -85, -80, + -75, -70, -65, -61, -55, -49, -39, -33, + -40, -35, -32, -38, -40, -33, -35, -37, + -46, -41, -45, -44, -46, -42, -45, -46, + -52, -50, -50, -50, -54, -54, -55, -57, + -62, -64, -66, -68, -70, -76, -81, -90, + -100, -110, -999, -999, -999, -999, -999, -999}}, + /* 354 hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -105, -98, -90, -85, -82, -83, -80, -78, + -84, -79, -80, -83, -87, -89, -91, -93, + -99, -106, -117, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -105, -98, -90, -85, -80, -75, -70, -68, + -74, -72, -74, -77, -80, -82, -85, -87, + -92, -89, -91, -95, -100, -106, -112, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -105, -98, -90, -83, -75, -71, -63, -64, + -67, -62, -64, -67, -70, -73, -77, -81, + -84, -83, -85, -89, -90, -93, -98, -104, + -109, -114, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -103, -96, -88, -81, -75, -68, -58, -54, + -56, -54, -56, -56, -58, -60, -63, -66, + -74, -69, -72, -72, -75, -74, -77, -81, + -81, -82, -84, -87, -93, -96, -99, -104, + -110, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -108, -102, -96, + -91, -85, -80, -74, -68, -60, -51, -46, + -48, -46, -43, -45, -47, -47, -49, -48, + -56, -53, -55, -58, -57, -63, -58, -60, + -66, -64, -67, -70, -70, -74, -77, -84, + -86, -89, -91, -93, -94, -101, -109, -118, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -108, -103, -98, -93, -88, + -83, -78, -73, -68, -60, -53, -44, -35, + -38, -38, -34, -34, -36, -40, -41, -44, + -51, -45, -46, -47, -46, -54, -50, -49, + -50, -50, -50, -51, -54, -57, -58, -60, + -66, -66, -66, -64, -65, -68, -77, -82, + -87, -95, -110, -999, -999, -999, -999, -999}}, + /* 500 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -107, -102, -97, -92, -87, -83, -78, -75, + -82, -79, -83, -85, -89, -92, -95, -98, + -101, -105, -109, -113, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -106, + -100, -95, -90, -86, -81, -78, -74, -69, + -74, -74, -76, -79, -83, -84, -86, -89, + -92, -97, -93, -100, -103, -107, -110, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -106, -100, + -95, -90, -87, -83, -80, -75, -69, -60, + -66, -66, -68, -70, -74, -78, -79, -81, + -81, -83, -84, -87, -93, -96, -99, -103, + -107, -110, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -108, -103, -98, + -93, -89, -85, -82, -78, -71, -62, -55, + -58, -58, -54, -54, -55, -59, -61, -62, + -70, -66, -66, -67, -70, -72, -75, -78, + -84, -84, -84, -88, -91, -90, -95, -98, + -102, -103, -106, -110, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -108, -103, -98, -94, + -90, -87, -82, -79, -73, -67, -58, -47, + -50, -45, -41, -45, -48, -44, -44, -49, + -54, -51, -48, -47, -49, -50, -51, -57, + -58, -60, -63, -69, -70, -69, -71, -74, + -78, -82, -90, -95, -101, -105, -110, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -105, -101, -97, -93, -90, + -85, -80, -77, -72, -65, -56, -48, -37, + -40, -36, -34, -40, -50, -47, -38, -41, + -47, -38, -35, -39, -38, -43, -40, -45, + -50, -45, -44, -47, -50, -55, -48, -48, + -52, -66, -70, -76, -82, -90, -97, -105, + -110, -999, -999, -999, -999, -999, -999, -999}}, + /* 707 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -108, -103, -98, -93, -86, -79, -76, + -83, -81, -85, -87, -89, -93, -98, -102, + -107, -112, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -108, -103, -98, -93, -86, -79, -71, + -77, -74, -77, -79, -81, -84, -85, -90, + -92, -93, -92, -98, -101, -108, -112, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -108, -103, -98, -93, -87, -78, -68, -65, + -66, -62, -65, -67, -70, -73, -75, -78, + -82, -82, -83, -84, -91, -93, -98, -102, + -106, -110, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -105, -100, -95, -90, -82, -74, -62, -57, + -58, -56, -51, -52, -52, -54, -54, -58, + -66, -59, -60, -63, -66, -69, -73, -79, + -83, -84, -80, -81, -81, -82, -88, -92, + -98, -105, -113, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -107, + -102, -97, -92, -84, -79, -69, -57, -47, + -52, -47, -44, -45, -50, -52, -42, -42, + -53, -43, -43, -48, -51, -56, -55, -52, + -57, -59, -61, -62, -67, -71, -78, -83, + -86, -94, -98, -103, -110, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -105, -100, + -95, -90, -84, -78, -70, -61, -51, -41, + -40, -38, -40, -46, -52, -51, -41, -40, + -46, -40, -38, -38, -41, -46, -41, -46, + -47, -43, -43, -45, -41, -45, -56, -67, + -68, -83, -87, -90, -95, -102, -107, -113, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 1000 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -109, -105, -101, -96, -91, -84, -77, + -82, -82, -85, -89, -94, -100, -106, -110, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -106, -103, -98, -92, -85, -80, -71, + -75, -72, -76, -80, -84, -86, -89, -93, + -100, -107, -113, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -107, + -104, -101, -97, -92, -88, -84, -80, -64, + -66, -63, -64, -66, -69, -73, -77, -83, + -83, -86, -91, -98, -104, -111, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -107, + -104, -101, -97, -92, -90, -84, -74, -57, + -58, -52, -55, -54, -50, -52, -50, -52, + -63, -62, -69, -76, -77, -78, -78, -79, + -82, -88, -94, -100, -106, -111, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -106, -102, + -98, -95, -90, -85, -83, -78, -70, -50, + -50, -41, -44, -49, -47, -50, -50, -44, + -55, -46, -47, -48, -48, -54, -49, -49, + -58, -62, -71, -81, -87, -92, -97, -102, + -108, -114, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -106, -102, + -98, -95, -90, -85, -83, -78, -70, -45, + -43, -41, -47, -50, -51, -50, -49, -45, + -47, -41, -44, -41, -39, -43, -38, -37, + -40, -41, -44, -50, -58, -65, -73, -79, + -85, -92, -97, -101, -105, -109, -113, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 1414 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -107, -100, -95, -87, -81, + -85, -83, -88, -93, -100, -107, -114, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -107, -101, -95, -88, -83, -76, + -73, -72, -79, -84, -90, -95, -100, -105, + -110, -115, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -104, -98, -92, -87, -81, -70, + -65, -62, -67, -71, -74, -80, -85, -91, + -95, -99, -103, -108, -111, -114, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -103, -97, -90, -85, -76, -60, + -56, -54, -60, -62, -61, -56, -63, -65, + -73, -74, -77, -75, -78, -81, -86, -87, + -88, -91, -94, -98, -103, -110, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -105, + -100, -97, -92, -86, -81, -79, -70, -57, + -51, -47, -51, -58, -60, -56, -53, -50, + -58, -52, -50, -50, -53, -55, -64, -69, + -71, -85, -82, -78, -81, -85, -95, -102, + -112, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -105, + -100, -97, -92, -85, -83, -79, -72, -49, + -40, -43, -43, -54, -56, -51, -50, -40, + -43, -38, -36, -35, -37, -38, -37, -44, + -54, -60, -57, -60, -70, -75, -84, -92, + -103, -112, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 2000 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -102, -95, -89, -82, + -83, -84, -90, -92, -99, -107, -113, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -107, -101, -95, -89, -83, -72, + -74, -78, -85, -88, -88, -90, -92, -98, + -105, -111, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -109, -103, -97, -93, -87, -81, -70, + -70, -67, -75, -73, -76, -79, -81, -83, + -88, -89, -97, -103, -110, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -107, -100, -94, -88, -83, -75, -63, + -59, -59, -63, -66, -60, -62, -67, -67, + -77, -76, -81, -88, -86, -92, -96, -102, + -109, -116, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -105, -98, -92, -86, -81, -73, -56, + -52, -47, -55, -60, -58, -52, -51, -45, + -49, -50, -53, -54, -61, -71, -70, -69, + -78, -79, -87, -90, -96, -104, -112, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -103, -96, -90, -86, -78, -70, -51, + -42, -47, -48, -55, -54, -54, -53, -42, + -35, -28, -33, -38, -37, -44, -47, -49, + -54, -63, -68, -78, -82, -89, -94, -99, + -104, -109, -114, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 2828 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -110, -100, -90, -79, + -85, -81, -82, -82, -89, -94, -99, -103, + -109, -115, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -105, -97, -85, -72, + -74, -70, -70, -70, -76, -85, -91, -93, + -97, -103, -109, -115, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -112, -93, -81, -68, + -62, -60, -60, -57, -63, -70, -77, -82, + -90, -93, -98, -104, -109, -113, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -113, -100, -93, -84, -63, + -58, -48, -53, -54, -52, -52, -57, -64, + -66, -76, -83, -81, -85, -85, -90, -95, + -98, -101, -103, -106, -108, -111, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -105, -95, -86, -74, -53, + -50, -38, -43, -49, -43, -42, -39, -39, + -46, -52, -57, -56, -72, -69, -74, -81, + -87, -92, -94, -97, -99, -102, -105, -108, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -108, -99, -90, -76, -66, -45, + -43, -41, -44, -47, -43, -47, -40, -30, + -31, -31, -39, -33, -40, -41, -43, -53, + -59, -70, -73, -77, -79, -82, -84, -87, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 4000 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -110, -91, -76, + -75, -85, -93, -98, -104, -110, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -110, -91, -70, + -70, -75, -86, -89, -94, -98, -101, -106, + -110, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -110, -95, -80, -60, + -65, -64, -74, -83, -88, -91, -95, -99, + -103, -107, -110, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -110, -95, -80, -58, + -55, -49, -66, -68, -71, -78, -78, -80, + -88, -85, -89, -97, -100, -105, -110, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -110, -95, -80, -53, + -52, -41, -59, -59, -49, -58, -56, -63, + -86, -79, -90, -93, -98, -103, -107, -112, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -97, -91, -73, -45, + -40, -33, -53, -61, -49, -54, -50, -50, + -60, -52, -67, -74, -81, -92, -96, -100, + -105, -110, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 5657 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -113, -106, -99, -92, -77, + -80, -88, -97, -106, -115, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -116, -109, -102, -95, -89, -74, + -72, -88, -87, -95, -102, -109, -116, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -116, -109, -102, -95, -89, -75, + -66, -74, -77, -78, -86, -87, -90, -96, + -105, -115, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -115, -108, -101, -94, -88, -66, + -56, -61, -70, -65, -78, -72, -83, -84, + -93, -98, -105, -110, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -110, -105, -95, -89, -82, -57, + -52, -52, -59, -56, -59, -58, -69, -67, + -88, -82, -82, -89, -94, -100, -108, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -110, -101, -96, -90, -83, -77, -54, + -43, -38, -50, -48, -52, -48, -42, -42, + -51, -52, -53, -59, -65, -71, -78, -85, + -95, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 8000 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -120, -105, -86, -68, + -78, -79, -90, -100, -110, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -120, -105, -86, -66, + -73, -77, -88, -96, -105, -115, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -120, -105, -92, -80, -61, + -64, -68, -80, -87, -92, -100, -110, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -120, -104, -91, -79, -52, + -60, -54, -64, -69, -77, -80, -82, -84, + -85, -87, -88, -90, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -118, -100, -87, -77, -49, + -50, -44, -58, -61, -61, -67, -65, -62, + -62, -62, -65, -68, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -115, -98, -84, -62, -49, + -44, -38, -46, -49, -49, -46, -39, -37, + -39, -40, -42, -43, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 11314 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -110, -88, -74, + -77, -82, -82, -85, -90, -94, -99, -104, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -110, -88, -66, + -70, -81, -80, -81, -84, -88, -91, -93, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -110, -88, -61, + -63, -70, -71, -74, -77, -80, -83, -85, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -110, -86, -62, + -63, -62, -62, -58, -52, -50, -50, -52, + -54, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -118, -108, -84, -53, + -50, -50, -50, -55, -47, -45, -40, -40, + -40, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -118, -100, -73, -43, + -37, -42, -43, -53, -38, -37, -35, -35, + -38, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}}, + /* 16000 Hz */ + {{-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -100, -91, -84, -74, + -80, -80, -80, -80, -80, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -100, -91, -84, -74, + -68, -68, -68, -68, -68, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -100, -86, -78, -70, + -60, -45, -30, -21, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -100, -87, -78, -67, + -48, -38, -29, -21, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -100, -86, -69, -56, + -45, -35, -33, -29, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}, + {-999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -110, -100, -83, -71, -48, + -27, -38, -37, -34, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999, + -999, -999, -999, -999, -999, -999, -999, -999}} +}; + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/mdct.c b/src/sound/oggvorbis/vorbissrc/mdct.c new file mode 100644 index 0000000..9b9c0ab --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/mdct.c @@ -0,0 +1,588 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: normalized modified discrete cosine transform + power of two length transform only [64 <= n ] + last mod: $Id: mdct.c,v 1.32 2002/10/16 02:43:48 xiphmont Exp $ + + Original algorithm adapted long ago from _The use of multirate filter + banks for coding of high quality digital audio_, by T. Sporer, + K. Brandenburg and B. Edler, collection of the European Signal + Processing Conference (EUSIPCO), Amsterdam, June 1992, Vol.1, pp + 211-214 + + The below code implements an algorithm that no longer looks much like + that presented in the paper, but the basic structure remains if you + dig deep enough to see it. + + This module DOES NOT INCLUDE code to generate/apply the window + function. Everybody has their own weird favorite including me... I + happen to like the properties of y=sin(.5PI*sin^2(x)), but others may + vehemently disagree. + + ********************************************************************/ + +/* this can also be run as an integer transform by uncommenting a + define in mdct.h; the integerization is a first pass and although + it's likely stable for Vorbis, the dynamic range is constrained and + roundoff isn't done (so it's noisy). Consider it functional, but + only a starting point. There's no point on a machine with an FPU */ + +#include +#include +#include +#include +#include "../vorbis/codec.h" +#include "mdct.h" +#include "os.h" +#include "misc.h" + +/* build lookups for trig functions; also pre-figure scaling and + some window function algebra. */ + +void mdct_init(mdct_lookup *lookup,int n){ + int *bitrev=_ogg_malloc(sizeof(*bitrev)*(n/4)); + DATA_TYPE *T=_ogg_malloc(sizeof(*T)*(n+n/4)); + + int i; + int n2=n>>1; + int log2n=lookup->log2n=rint(log((float)n)/log(2.f)); + lookup->n=n; + lookup->trig=T; + lookup->bitrev=bitrev; + +/* trig lookups... */ + + for(i=0;i>j;j++) + if((msb>>j)&i)acc|=1<scale=FLOAT_CONV(4.f/n); +} + +/* 8 point butterfly (in place, 4 register) */ +STIN void mdct_butterfly_8(DATA_TYPE *x){ + REG_TYPE r0 = x[6] + x[2]; + REG_TYPE r1 = x[6] - x[2]; + REG_TYPE r2 = x[4] + x[0]; + REG_TYPE r3 = x[4] - x[0]; + + x[6] = r0 + r2; + x[4] = r0 - r2; + + r0 = x[5] - x[1]; + r2 = x[7] - x[3]; + x[0] = r1 + r0; + x[2] = r1 - r0; + + r0 = x[5] + x[1]; + r1 = x[7] + x[3]; + x[3] = r2 + r3; + x[1] = r2 - r3; + x[7] = r1 + r0; + x[5] = r1 - r0; + +} + +/* 16 point butterfly (in place, 4 register) */ +STIN void mdct_butterfly_16(DATA_TYPE *x){ + REG_TYPE r0 = x[1] - x[9]; + REG_TYPE r1 = x[0] - x[8]; + + x[8] += x[0]; + x[9] += x[1]; + x[0] = MULT_NORM((r0 + r1) * cPI2_8); + x[1] = MULT_NORM((r0 - r1) * cPI2_8); + + r0 = x[3] - x[11]; + r1 = x[10] - x[2]; + x[10] += x[2]; + x[11] += x[3]; + x[2] = r0; + x[3] = r1; + + r0 = x[12] - x[4]; + r1 = x[13] - x[5]; + x[12] += x[4]; + x[13] += x[5]; + x[4] = MULT_NORM((r0 - r1) * cPI2_8); + x[5] = MULT_NORM((r0 + r1) * cPI2_8); + + r0 = x[14] - x[6]; + r1 = x[15] - x[7]; + x[14] += x[6]; + x[15] += x[7]; + x[6] = r0; + x[7] = r1; + + mdct_butterfly_8(x); + mdct_butterfly_8(x+8); +} + +/* 32 point butterfly (in place, 4 register) */ +STIN void mdct_butterfly_32(DATA_TYPE *x){ + REG_TYPE r0 = x[30] - x[14]; + REG_TYPE r1 = x[31] - x[15]; + + x[30] += x[14]; + x[31] += x[15]; + x[14] = r0; + x[15] = r1; + + r0 = x[28] - x[12]; + r1 = x[29] - x[13]; + x[28] += x[12]; + x[29] += x[13]; + x[12] = MULT_NORM( r0 * cPI1_8 - r1 * cPI3_8 ); + x[13] = MULT_NORM( r0 * cPI3_8 + r1 * cPI1_8 ); + + r0 = x[26] - x[10]; + r1 = x[27] - x[11]; + x[26] += x[10]; + x[27] += x[11]; + x[10] = MULT_NORM(( r0 - r1 ) * cPI2_8); + x[11] = MULT_NORM(( r0 + r1 ) * cPI2_8); + + r0 = x[24] - x[8]; + r1 = x[25] - x[9]; + x[24] += x[8]; + x[25] += x[9]; + x[8] = MULT_NORM( r0 * cPI3_8 - r1 * cPI1_8 ); + x[9] = MULT_NORM( r1 * cPI3_8 + r0 * cPI1_8 ); + + r0 = x[22] - x[6]; + r1 = x[7] - x[23]; + x[22] += x[6]; + x[23] += x[7]; + x[6] = r1; + x[7] = r0; + + r0 = x[4] - x[20]; + r1 = x[5] - x[21]; + x[20] += x[4]; + x[21] += x[5]; + x[4] = MULT_NORM( r1 * cPI1_8 + r0 * cPI3_8 ); + x[5] = MULT_NORM( r1 * cPI3_8 - r0 * cPI1_8 ); + + r0 = x[2] - x[18]; + r1 = x[3] - x[19]; + x[18] += x[2]; + x[19] += x[3]; + x[2] = MULT_NORM(( r1 + r0 ) * cPI2_8); + x[3] = MULT_NORM(( r1 - r0 ) * cPI2_8); + + r0 = x[0] - x[16]; + r1 = x[1] - x[17]; + x[16] += x[0]; + x[17] += x[1]; + x[0] = MULT_NORM( r1 * cPI3_8 + r0 * cPI1_8 ); + x[1] = MULT_NORM( r1 * cPI1_8 - r0 * cPI3_8 ); + + mdct_butterfly_16(x); + mdct_butterfly_16(x+16); + +} + +/* N point first stage butterfly (in place, 2 register) */ +STIN void mdct_butterfly_first(DATA_TYPE *T, + DATA_TYPE *x, + int points){ + + DATA_TYPE *x1 = x + points - 8; + DATA_TYPE *x2 = x + (points>>1) - 8; + REG_TYPE r0; + REG_TYPE r1; + + do{ + + r0 = x1[6] - x2[6]; + r1 = x1[7] - x2[7]; + x1[6] += x2[6]; + x1[7] += x2[7]; + x2[6] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[7] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + r0 = x1[4] - x2[4]; + r1 = x1[5] - x2[5]; + x1[4] += x2[4]; + x1[5] += x2[5]; + x2[4] = MULT_NORM(r1 * T[5] + r0 * T[4]); + x2[5] = MULT_NORM(r1 * T[4] - r0 * T[5]); + + r0 = x1[2] - x2[2]; + r1 = x1[3] - x2[3]; + x1[2] += x2[2]; + x1[3] += x2[3]; + x2[2] = MULT_NORM(r1 * T[9] + r0 * T[8]); + x2[3] = MULT_NORM(r1 * T[8] - r0 * T[9]); + + r0 = x1[0] - x2[0]; + r1 = x1[1] - x2[1]; + x1[0] += x2[0]; + x1[1] += x2[1]; + x2[0] = MULT_NORM(r1 * T[13] + r0 * T[12]); + x2[1] = MULT_NORM(r1 * T[12] - r0 * T[13]); + + x1-=8; + x2-=8; + T+=16; + + }while(x2>=x); +} + +/* N/stage point generic N stage butterfly (in place, 2 register) */ +STIN void mdct_butterfly_generic(DATA_TYPE *T, + DATA_TYPE *x, + int points, + int trigint){ + + DATA_TYPE *x1 = x + points - 8; + DATA_TYPE *x2 = x + (points>>1) - 8; + REG_TYPE r0; + REG_TYPE r1; + + do{ + + r0 = x1[6] - x2[6]; + r1 = x1[7] - x2[7]; + x1[6] += x2[6]; + x1[7] += x2[7]; + x2[6] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[7] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T+=trigint; + + r0 = x1[4] - x2[4]; + r1 = x1[5] - x2[5]; + x1[4] += x2[4]; + x1[5] += x2[5]; + x2[4] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[5] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T+=trigint; + + r0 = x1[2] - x2[2]; + r1 = x1[3] - x2[3]; + x1[2] += x2[2]; + x1[3] += x2[3]; + x2[2] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[3] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T+=trigint; + + r0 = x1[0] - x2[0]; + r1 = x1[1] - x2[1]; + x1[0] += x2[0]; + x1[1] += x2[1]; + x2[0] = MULT_NORM(r1 * T[1] + r0 * T[0]); + x2[1] = MULT_NORM(r1 * T[0] - r0 * T[1]); + + T+=trigint; + x1-=8; + x2-=8; + + }while(x2>=x); +} + +STIN void mdct_butterflies(mdct_lookup *init, + DATA_TYPE *x, + int points){ + + DATA_TYPE *T=init->trig; + int stages=init->log2n-5; + int i,j; + + if(--stages>0){ + mdct_butterfly_first(T,x,points); + } + + for(i=1;--stages>0;i++){ + for(j=0;j<(1<>i)*j,points>>i,4<trig)_ogg_free(l->trig); + if(l->bitrev)_ogg_free(l->bitrev); + memset(l,0,sizeof(*l)); + } +} + +STIN void mdct_bitreverse(mdct_lookup *init, + DATA_TYPE *x){ + int n = init->n; + int *bit = init->bitrev; + DATA_TYPE *w0 = x; + DATA_TYPE *w1 = x = w0+(n>>1); + DATA_TYPE *T = init->trig+n; + + do{ + DATA_TYPE *x0 = x+bit[0]; + DATA_TYPE *x1 = x+bit[1]; + + REG_TYPE r0 = x0[1] - x1[1]; + REG_TYPE r1 = x0[0] + x1[0]; + REG_TYPE r2 = MULT_NORM(r1 * T[0] + r0 * T[1]); + REG_TYPE r3 = MULT_NORM(r1 * T[1] - r0 * T[0]); + + w1 -= 4; + + r0 = HALVE(x0[1] + x1[1]); + r1 = HALVE(x0[0] - x1[0]); + + w0[0] = r0 + r2; + w1[2] = r0 - r2; + w0[1] = r1 + r3; + w1[3] = r3 - r1; + + x0 = x+bit[2]; + x1 = x+bit[3]; + + r0 = x0[1] - x1[1]; + r1 = x0[0] + x1[0]; + r2 = MULT_NORM(r1 * T[2] + r0 * T[3]); + r3 = MULT_NORM(r1 * T[3] - r0 * T[2]); + + r0 = HALVE(x0[1] + x1[1]); + r1 = HALVE(x0[0] - x1[0]); + + w0[2] = r0 + r2; + w1[0] = r0 - r2; + w0[3] = r1 + r3; + w1[1] = r3 - r1; + + T += 4; + bit += 4; + w0 += 4; + + }while(w0n; + int n2=n>>1; + int n4=n>>2; + + /* rotate */ + + DATA_TYPE *iX = in+n2-7; + DATA_TYPE *oX = out+n2+n4; + DATA_TYPE *T = init->trig+n4; + + do{ + oX -= 4; + oX[0] = MULT_NORM(-iX[2] * T[3] - iX[0] * T[2]); + oX[1] = MULT_NORM (iX[0] * T[3] - iX[2] * T[2]); + oX[2] = MULT_NORM(-iX[6] * T[1] - iX[4] * T[0]); + oX[3] = MULT_NORM (iX[4] * T[1] - iX[6] * T[0]); + iX -= 8; + T += 4; + }while(iX>=in); + + iX = in+n2-8; + oX = out+n2+n4; + T = init->trig+n4; + + do{ + T -= 4; + oX[0] = MULT_NORM (iX[4] * T[3] + iX[6] * T[2]); + oX[1] = MULT_NORM (iX[4] * T[2] - iX[6] * T[3]); + oX[2] = MULT_NORM (iX[0] * T[1] + iX[2] * T[0]); + oX[3] = MULT_NORM (iX[0] * T[0] - iX[2] * T[1]); + iX -= 8; + oX += 4; + }while(iX>=in); + + mdct_butterflies(init,out+n2,n2); + mdct_bitreverse(init,out); + + /* roatate + window */ + + { + DATA_TYPE *oX1=out+n2+n4; + DATA_TYPE *oX2=out+n2+n4; + DATA_TYPE *iX =out; + T =init->trig+n2; + + do{ + oX1-=4; + + oX1[3] = MULT_NORM (iX[0] * T[1] - iX[1] * T[0]); + oX2[0] = -MULT_NORM (iX[0] * T[0] + iX[1] * T[1]); + + oX1[2] = MULT_NORM (iX[2] * T[3] - iX[3] * T[2]); + oX2[1] = -MULT_NORM (iX[2] * T[2] + iX[3] * T[3]); + + oX1[1] = MULT_NORM (iX[4] * T[5] - iX[5] * T[4]); + oX2[2] = -MULT_NORM (iX[4] * T[4] + iX[5] * T[5]); + + oX1[0] = MULT_NORM (iX[6] * T[7] - iX[7] * T[6]); + oX2[3] = -MULT_NORM (iX[6] * T[6] + iX[7] * T[7]); + + oX2+=4; + iX += 8; + T += 8; + }while(iXoX2); + } +} + +void mdct_forward(mdct_lookup *init, DATA_TYPE *in, DATA_TYPE *out){ + int n=init->n; + int n2=n>>1; + int n4=n>>2; + int n8=n>>3; + DATA_TYPE *w=alloca(n*sizeof(*w)); /* forward needs working space */ + DATA_TYPE *w2=w+n2; + + /* rotate */ + + /* window + rotate + step 1 */ + + REG_TYPE r0; + REG_TYPE r1; + DATA_TYPE *x0=in+n2+n4; + DATA_TYPE *x1=x0+1; + DATA_TYPE *T=init->trig+n2; + + int i=0; + + for(i=0;itrig+n2; + x0=out+n2; + + for(i=0;iscale); + x0[0] =MULT_NORM((w[0]*T[1]-w[1]*T[0])*init->scale); + w+=2; + T+=2; + } +} + diff --git a/src/sound/oggvorbis/vorbissrc/mdct.h b/src/sound/oggvorbis/vorbissrc/mdct.h new file mode 100644 index 0000000..e960887 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/mdct.h @@ -0,0 +1,107 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: modified discrete cosine transform prototypes + last mod: $Id: mdct.h,v 1.20 2002/01/22 11:59:00 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _OGG_mdct_H_ +#define _OGG_mdct_H_ + +#include "../vorbis/codec.h" + + + + + +/*#define MDCT_INTEGERIZED <- be warned there could be some hurt left here*/ +#ifdef MDCT_INTEGERIZED + +#define DATA_TYPE int +#define REG_TYPE register int +#define TRIGBITS 14 +#define cPI3_8 6270 +#define cPI2_8 11585 +#define cPI1_8 15137 + +#define FLOAT_CONV(x) ((int)((x)*(1<>TRIGBITS) +#define HALVE(x) ((x)>>1) + +#else + +#define DATA_TYPE float +#define REG_TYPE float +#define cPI3_8 .38268343236508977175F +#define cPI2_8 .70710678118654752441F +#define cPI1_8 .92387953251128675613F + +#define FLOAT_CONV(x) (x) +#define MULT_NORM(x) (x) +#define HALVE(x) ((x)*.5f) + +#endif + + +typedef struct { + int n; + int log2n; + + DATA_TYPE *trig; + int *bitrev; + + DATA_TYPE scale; +} mdct_lookup; + +extern void mdct_init(mdct_lookup *lookup,int n); +extern void mdct_clear(mdct_lookup *l); +extern void mdct_forward(mdct_lookup *init, DATA_TYPE *in, DATA_TYPE *out); +extern void mdct_backward(mdct_lookup *init, DATA_TYPE *in, DATA_TYPE *out); + +#endif + + + + + + + + + + + + diff --git a/src/sound/oggvorbis/vorbissrc/misc.h b/src/sound/oggvorbis/vorbissrc/misc.h new file mode 100644 index 0000000..6b11745 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/misc.h @@ -0,0 +1,76 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: miscellaneous prototypes + last mod: $Id: misc.h,v 1.12 2002/06/28 22:19:37 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_RANDOM_H_ +#define _V_RANDOM_H_ +#include "../vorbis/codec.h" + +extern int analysis_noisy; + +extern void *_vorbis_block_alloc(vorbis_block *vb,long bytes); +extern void _vorbis_block_ripcord(vorbis_block *vb); +extern void _analysis_output(char *base,int i,float *v,int n,int bark,int dB, + ogg_int64_t off); + +#ifdef DEBUG_MALLOC + +#define _VDBG_GRAPHFILE "malloc.m" +extern void *_VDBG_malloc(void *ptr,long bytes,char *file,long line); +extern void _VDBG_free(void *ptr,char *file,long line); + +#ifndef MISC_C +#undef _ogg_malloc +#undef _ogg_calloc +#undef _ogg_realloc +#undef _ogg_free + +#define _ogg_malloc(x) _VDBG_malloc(NULL,(x),__FILE__,__LINE__) +#define _ogg_calloc(x,y) _VDBG_malloc(NULL,(x)*(y),__FILE__,__LINE__) +#define _ogg_realloc(x,y) _VDBG_malloc((x),(y),__FILE__,__LINE__) +#define _ogg_free(x) _VDBG_free((x),__FILE__,__LINE__) +#endif +#endif + +#endif + + + + diff --git a/src/sound/oggvorbis/vorbissrc/modes/floor_all.h b/src/sound/oggvorbis/vorbissrc/modes/floor_all.h new file mode 100644 index 0000000..d122be4 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/floor_all.h @@ -0,0 +1,250 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: key floor settings + last mod: $Id: floor_all.h,v 1.1 2002/07/11 06:41:04 xiphmont Exp $ + + ********************************************************************/ + +#include "../vorbis/codec.h" +#include "backends.h" +#include "books/floor/floor_books.h" + +static static_codebook *_floor_128x4_books[]={ + &_huff_book_line_128x4_class0, + &_huff_book_line_128x4_0sub0, + &_huff_book_line_128x4_0sub1, + &_huff_book_line_128x4_0sub2, + &_huff_book_line_128x4_0sub3, +}; +static static_codebook *_floor_256x4_books[]={ + &_huff_book_line_256x4_class0, + &_huff_book_line_256x4_0sub0, + &_huff_book_line_256x4_0sub1, + &_huff_book_line_256x4_0sub2, + &_huff_book_line_256x4_0sub3, +}; +static static_codebook *_floor_128x7_books[]={ + &_huff_book_line_128x7_class0, + &_huff_book_line_128x7_class1, + + &_huff_book_line_128x7_0sub1, + &_huff_book_line_128x7_0sub2, + &_huff_book_line_128x7_0sub3, + &_huff_book_line_128x7_1sub1, + &_huff_book_line_128x7_1sub2, + &_huff_book_line_128x7_1sub3, +}; +static static_codebook *_floor_256x7_books[]={ + &_huff_book_line_256x7_class0, + &_huff_book_line_256x7_class1, + + &_huff_book_line_256x7_0sub1, + &_huff_book_line_256x7_0sub2, + &_huff_book_line_256x7_0sub3, + &_huff_book_line_256x7_1sub1, + &_huff_book_line_256x7_1sub2, + &_huff_book_line_256x7_1sub3, +}; +static static_codebook *_floor_128x11_books[]={ + &_huff_book_line_128x11_class1, + &_huff_book_line_128x11_class2, + &_huff_book_line_128x11_class3, + + &_huff_book_line_128x11_0sub0, + &_huff_book_line_128x11_1sub0, + &_huff_book_line_128x11_1sub1, + &_huff_book_line_128x11_2sub1, + &_huff_book_line_128x11_2sub2, + &_huff_book_line_128x11_2sub3, + &_huff_book_line_128x11_3sub1, + &_huff_book_line_128x11_3sub2, + &_huff_book_line_128x11_3sub3, +}; +static static_codebook *_floor_128x17_books[]={ + &_huff_book_line_128x17_class1, + &_huff_book_line_128x17_class2, + &_huff_book_line_128x17_class3, + + &_huff_book_line_128x17_0sub0, + &_huff_book_line_128x17_1sub0, + &_huff_book_line_128x17_1sub1, + &_huff_book_line_128x17_2sub1, + &_huff_book_line_128x17_2sub2, + &_huff_book_line_128x17_2sub3, + &_huff_book_line_128x17_3sub1, + &_huff_book_line_128x17_3sub2, + &_huff_book_line_128x17_3sub3, +}; +static static_codebook *_floor_512x17_books[]={ + &_huff_book_line_512x17_class1, + &_huff_book_line_512x17_class2, + &_huff_book_line_512x17_class3, + + &_huff_book_line_512x17_0sub0, + &_huff_book_line_512x17_1sub0, + &_huff_book_line_512x17_1sub1, + &_huff_book_line_512x17_2sub1, + &_huff_book_line_512x17_2sub2, + &_huff_book_line_512x17_2sub3, + &_huff_book_line_512x17_3sub1, + &_huff_book_line_512x17_3sub2, + &_huff_book_line_512x17_3sub3, +}; +static static_codebook *_floor_1024x27_books[]={ + &_huff_book_line_1024x27_class1, + &_huff_book_line_1024x27_class2, + &_huff_book_line_1024x27_class3, + &_huff_book_line_1024x27_class4, + + &_huff_book_line_1024x27_0sub0, + &_huff_book_line_1024x27_1sub0, + &_huff_book_line_1024x27_1sub1, + &_huff_book_line_1024x27_2sub0, + &_huff_book_line_1024x27_2sub1, + &_huff_book_line_1024x27_3sub1, + &_huff_book_line_1024x27_3sub2, + &_huff_book_line_1024x27_3sub3, + &_huff_book_line_1024x27_4sub1, + &_huff_book_line_1024x27_4sub2, + &_huff_book_line_1024x27_4sub3, +}; + +static static_codebook **_floor_books[10]={ + _floor_128x4_books, + _floor_256x4_books, + _floor_128x7_books, + _floor_256x7_books, + _floor_128x11_books, + _floor_128x17_books, + _floor_128x17_books, + _floor_1024x27_books, + _floor_1024x27_books, + _floor_512x17_books, +}; + +static vorbis_info_floor1 _floor[10]={ + /* 128 x 4 */ + { + 1,{0},{4},{2},{0}, + {{1,2,3,4}}, + 4,{0,128, 33,8,16,70}, + + 60,30,500, 1.,18., -1 + }, + /* 256 x 4 */ + { + 1,{0},{4},{2},{0}, + {{1,2,3,4}}, + 4,{0,256, 66,16,32,140}, + + 60,30,500, 1.,18., -1 + }, + /* 128 x 7 */ + { + 2,{0,1},{3,4},{2,2},{0,1}, + {{-1,2,3,4},{-1,5,6,7}}, + 4,{0,128, 14,4,58, 2,8,28,90}, + + 60,30,500, 1.,18., -1 + }, + /* 256 x 7 */ + { + 2,{0,1},{3,4},{2,2},{0,1}, + {{-1,2,3,4},{-1,5,6,7}}, + 4,{0,256, 28,8,116, 4,16,56,180}, + + 60,30,500, 1.,18., -1 + }, + + /* 128 x 11 */ + { + 4,{0,1,2,3},{2,3,3,3},{0,1,2,2},{-1,0,1,2}, + {{3},{4,5},{-1,6,7,8},{-1,9,10,11}}, + + 2,{0,128, 8,33, 4,16,70, 2,6,12, 23,46,90}, + + 60,30,500, 1,18., -1 + }, + + /* 128 x 17 */ + { + 6,{0,1,1,2,3,3},{2,3,3,3},{0,1,2,2},{-1,0,1,2}, + {{3},{4,5},{-1,6,7,8},{-1,9,10,11}}, + 2,{0,128, 12,46, 4,8,16, 23,33,70, 2,6,10, 14,19,28, 39,58,90}, + + 60,30,500, 1,18., -1 + }, + + /* 1024 x 17 */ + { + 6,{0,1,1,2,3,3},{2,3,3,3},{0,1,2,2},{-1,0,1,2}, + {{3},{4,5},{-1,6,7,8},{-1,9,10,11}}, + 2,{0,1024, 93,372, 33,65,130, 186,260,556, + 14,46,79, 111,158,220, 312,464,720}, + + 60,30,500, 1,18., -1 /* lowpass! */ + }, + /* 1024 x 27 */ + { + 8,{0,1,2,2,3,3,4,4},{3,4,3,4,3},{0,1,1,2,2},{-1,0,1,2,3}, + {{4},{5,6},{7,8},{-1,9,10,11},{-1,12,13,14}}, + 2,{0,1024, 93,23,372, 6,46,186,750, 14,33,65, 130,260,556, + 3,10,18,28, 39,55,79,111, 158,220,312, 464,650,850}, + + 60,30,500, 3,18., -1 /* lowpass */ + }, + /* 2048 x 27 */ + { + 8,{0,1,2,2,3,3,4,4},{3,4,3,4,3},{0,1,1,2,2},{-1,0,1,2,3}, + {{4},{5,6},{7,8},{-1,9,10,11},{-1,12,13,14}}, + 2,{0,2048, 186,46,744, 12,92,372,1500, 28,66,130, 260,520,1112, + 6,20,36,56, 78,110,158,222, 316,440,624, 928,1300,1700}, + + 60,30,500, 3,18., -1 /* lowpass */ + }, + /* 512 x 17 */ + { + 6,{0,1,1,2,3,3},{2,3,3,3},{0,1,2,2},{-1,0,1,2}, + {{3},{4,5},{-1,6,7,8},{-1,9,10,11}}, + 2,{0,512, 46,186, 16,33,65, 93,130,278, + 7,23,39, 55,79,110, 156,232,360}, + + 60,30,500, 1,18., -1 /* lowpass! */ + }, + +}; + diff --git a/src/sound/oggvorbis/vorbissrc/modes/psych_11.h b/src/sound/oggvorbis/vorbissrc/modes/psych_11.h new file mode 100644 index 0000000..c5c6e62 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/psych_11.h @@ -0,0 +1,75 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: 11kHz settings + last mod: $Id: psych_11.h,v 1.1 2002/07/10 03:04:22 xiphmont Exp $ + + ********************************************************************/ + +static double _psy_lowpass_11[3]={4.5,5.5,30.,}; + +static att3 _psy_tone_masteratt_11[3]={ + {{ 30, 25, 12}, 0, 0}, /* 0 */ + {{ 30, 25, 12}, 0, 0}, /* 0 */ + {{ 20, 0, -14}, 0, 1.}, /* 0 */ +}; + +static vp_adjblock _vp_tonemask_adj_11[3]={ + /* adjust for mode zero */ + /* 63 125 250 500 1 2 4 8 16 */ + {{-20,-20,-20,-20,-20,-16,-10, 0, 0, 0, 0,10, 2, 0,99,99,99}}, /* 0 */ + {{-20,-20,-20,-20,-20,-16,-10, 0, 0, 0, 0, 5, 0, 0,99,99,99}}, /* 1 */ + {{-20,-20,-20,-20,-20,-16,-10, 0, 0, 0, 0, 0, 0, 0,99,99,99}}, /* 2 */ +}; + + +static noise3 _psy_noisebias_11[3]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + {{{-10,-10,-10,-10, -5, -5, -5, 0, 4, 10, 10, 12, 12, 12, 99, 99, 99}, + {-15,-15,-15,-15,-10,-10, -5, 0, 0, 4, 4, 5, 5, 10, 99, 99, 99}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, 99, 99, 99}}}, + + {{{-10,-10,-10,-10, -5, -5, -5, 0, 4, 10, 10, 12, 12, 12, 99, 99, 99}, + {-15,-15,-15,-15,-10,-10, -5, -5, -5, 0, 0, 0, 0, 0, 99, 99, 99}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, 99, 99, 99}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 4, 4, 5, 5, 99, 99, 99}, + {-30,-30,-30,-30,-26,-22,-20,-14,-12,-12,-10,-10,-10,-10, 99, 99, 99}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24, 99, 99, 99}}}, +}; + +static double _noise_thresh_11[3]={ .3,.5,.5 }; + diff --git a/src/sound/oggvorbis/vorbissrc/modes/psych_16.h b/src/sound/oggvorbis/vorbissrc/modes/psych_16.h new file mode 100644 index 0000000..7c150c8 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/psych_16.h @@ -0,0 +1,153 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: 16kHz settings + last mod: $Id: psych_16.h,v 1.3 2003/09/02 06:05:47 xiphmont Exp $ + + ********************************************************************/ + +/* stereo mode by base quality level */ +static adj_stereo _psy_stereo_modes_16[4]={ + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 */ + {{ 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + { 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + {{ 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + { 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 4, 4, 4, 4}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + {{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + { 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, +}; + +static double _psy_lowpass_16[4]={6.5,8,30.,99.}; + +static att3 _psy_tone_masteratt_16[4]={ + {{ 30, 25, 12}, 0, 0}, /* 0 */ + {{ 25, 22, 12}, 0, 0}, /* 0 */ + {{ 20, 12, 0}, 0, 0}, /* 0 */ + {{ 15, 0, -14}, 0, 0}, /* 0 */ +}; + +static vp_adjblock _vp_tonemask_adj_16[4]={ + /* adjust for mode zero */ + /* 63 125 250 500 1 2 4 8 16 */ + {{-20,-20,-20,-20,-20,-16,-10, 0, 0, 0, 0,10, 0, 0, 0, 0, 0}}, /* 0 */ + {{-20,-20,-20,-20,-20,-16,-10, 0, 0, 0, 0,10, 0, 0, 0, 0, 0}}, /* 1 */ + {{-20,-20,-20,-20,-20,-16,-10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 2 */ + {{-30,-30,-30,-30,-30,-26,-20,-10, -5, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 2 */ +}; + + +static noise3 _psy_noisebias_16_short[4]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + {{{-15,-15,-15,-15,-15,-10,-10,-5, 4, 10, 10, 10, 10, 12, 12, 14, 20}, + {-15,-15,-15,-15,-15,-10,-10, -5, 0, 0, 4, 5, 5, 6, 8, 8, 15}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, -6, -6, -6}}}, + + {{{-15,-15,-15,-15,-15,-10,-10,-5, 4, 6, 6, 6, 6, 8, 10, 12, 20}, + {-15,-15,-15,-15,-15,-15,-15,-10, -5, -5, -5, 4, 5, 6, 8, 8, 15}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10,-10,-10,-10,-10,-10,-10,-10,-10}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 4, 4, 5, 5, 5, 8, 12}, + {-20,-20,-20,-20,-16,-12,-20,-14,-10,-10, -8, 0, 0, 0, 0, 2, 5}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24,-20,-20,-20}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, -5, -5, -5, -5, -5, 0, 0, 0, 6}, + {-30,-30,-30,-30,-26,-22,-20,-14,-12,-12,-10,-10,-10,-10,-10,-10, -6}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24,-20,-20,-20}}}, +}; + +static noise3 _psy_noisebias_16_impulse[4]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + {{{-15,-15,-15,-15,-15,-10,-10,-5, 4, 10, 10, 10, 10, 12, 12, 14, 20}, + {-15,-15,-15,-15,-15,-10,-10, -5, 0, 0, 4, 5, 5, 6, 8, 8, 15}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, -6, -6, -6}}}, + + {{{-15,-15,-15,-15,-15,-10,-10,-5, 4, 4, 4, 4, 5, 5, 6, 8, 15}, + {-15,-15,-15,-15,-15,-15,-15,-10, -5, -5, -5, 0, 0, 0, 0, 4, 10}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10,-10,-10,-10,-10,-10,-10,-10,-10}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 0, 0, 0, 0, 0, 0, 4, 10}, + {-20,-20,-20,-20,-16,-12,-20,-14,-10,-10,-10,-10,-10,-10,-10, -7, -5}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24,-20,-20,-20}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, -5, -5, -5, -5, -5, 0, 0, 0, 6}, + {-30,-30,-30,-30,-26,-22,-20,-18,-18,-18,-20,-20,-20,-20,-20,-20,-16}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24,-20,-20,-20}}}, +}; + +static noise3 _psy_noisebias_16[4]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + {{{-10,-10,-10,-10, -5, -5, -5, 0, 4, 6, 8, 8, 10, 10, 10, 14, 20}, + {-10,-10,-10,-10,-10, -5, -2, -2, 0, 0, 0, 4, 5, 6, 8, 8, 15}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, -6, -6, -6}}}, + + {{{-10,-10,-10,-10, -5, -5, -5, 0, 4, 6, 6, 6, 6, 8, 10, 12, 20}, + {-15,-15,-15,-15,-15,-10, -5, -5, 0, 0, 0, 4, 5, 6, 8, 8, 15}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, -6, -6, -6}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 4, 4, 5, 5, 5, 8, 12}, + {-20,-20,-20,-20,-16,-12,-20,-10, -5, -5, 0, 0, 0, 0, 0, 2, 5}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24,-20,-20,-20}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, -5, -5, -5, -5, -5, 0, 0, 0, 6}, + {-30,-30,-30,-30,-26,-22,-20,-14,-12,-12,-10,-10,-10,-10,-10,-10, -6}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24,-20,-20,-20}}}, +}; + +static double _noise_thresh_16[4]={ .3,.5,.5,.5 }; + +static int _noise_start_16[3]={ 256,256,9999 }; +static int _noise_part_16[4]={ 8,8,8,8 }; + +static int _psy_ath_floater_16[4]={ + -100,-100,-100,-105, +}; + +static int _psy_ath_abs_16[4]={ + -130,-130,-130,-140, +}; + + + diff --git a/src/sound/oggvorbis/vorbissrc/modes/psych_44.h b/src/sound/oggvorbis/vorbissrc/modes/psych_44.h new file mode 100644 index 0000000..bc7e66a --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/psych_44.h @@ -0,0 +1,556 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: key psychoacoustic settings for 44.1/48kHz + last mod: $Id: psych_44.h,v 1.29 2003/09/02 07:32:17 xiphmont Exp $ + + ********************************************************************/ + + +/* preecho trigger settings *****************************************/ + +static vorbis_info_psy_global _psy_global_44[5]={ + + {8, /* lines per eighth octave */ + {20.f,14.f,12.f,12.f,12.f,12.f,12.f}, + {-60.f,-30.f,-40.f,-40.f,-40.f,-40.f,-40.f}, 2,-75.f, + -6.f, + {99.},{{99.},{99.}},{0},{0},{{0.},{0.}} + }, + {8, /* lines per eighth octave */ + {14.f,10.f,10.f,10.f,10.f,10.f,10.f}, + {-40.f,-30.f,-25.f,-25.f,-25.f,-25.f,-25.f}, 2,-80.f, + -6.f, + {99.},{{99.},{99.}},{0},{0},{{0.},{0.}} + }, + {8, /* lines per eighth octave */ + {12.f,10.f,10.f,10.f,10.f,10.f,10.f}, + {-20.f,-20.f,-15.f,-15.f,-15.f,-15.f,-15.f}, 0,-80.f, + -6.f, + {99.},{{99.},{99.}},{0},{0},{{0.},{0.}} + }, + {8, /* lines per eighth octave */ + {10.f,8.f,8.f,8.f,8.f,8.f,8.f}, + {-20.f,-15.f,-12.f,-12.f,-12.f,-12.f,-12.f}, 0,-80.f, + -6.f, + {99.},{{99.},{99.}},{0},{0},{{0.},{0.}} + }, + {8, /* lines per eighth octave */ + {10.f,6.f,6.f,6.f,6.f,6.f,6.f}, + {-15.f,-15.f,-12.f,-12.f,-12.f,-12.f,-12.f}, 0,-85.f, + -6.f, + {99.},{{99.},{99.}},{0},{0},{{0.},{0.}} + }, +}; + +/* noise compander lookups * low, mid, high quality ****************/ +static compandblock _psy_compand_44[6]={ + /* sub-mode Z short */ + {{ + 0, 1, 2, 3, 4, 5, 6, 7, /* 7dB */ + 8, 9,10,11,12,13,14, 15, /* 15dB */ + 16,17,18,19,20,21,22, 23, /* 23dB */ + 24,25,26,27,28,29,30, 31, /* 31dB */ + 32,33,34,35,36,37,38, 39, /* 39dB */ + }}, + /* mode_Z nominal short*/ + {{ + 0, 1, 2, 3, 4, 5, 6, 6, /* 7dB */ + 7, 7, 7, 7, 6, 6, 6, 7, /* 15dB */ + 7, 8, 9,10,11,12,13, 14, /* 23dB */ + 15,16,17,17,17,18,18, 19, /* 31dB */ + 19,19,20,21,22,23,24, 25, /* 39dB */ + }}, + /* mode A short */ + {{ + 0, 1, 2, 3, 4, 5, 5, 5, /* 7dB */ + 6, 6, 6, 5, 4, 4, 4, 4, /* 15dB */ + 4, 4, 5, 5, 5, 6, 6, 6, /* 23dB */ + 7, 7, 7, 8, 8, 8, 9, 10, /* 31dB */ + 11,12,13,14,15,16,17, 18, /* 39dB */ + }}, + /* sub-mode Z long */ + {{ + 0, 1, 2, 3, 4, 5, 6, 7, /* 7dB */ + 8, 9,10,11,12,13,14, 15, /* 15dB */ + 16,17,18,19,20,21,22, 23, /* 23dB */ + 24,25,26,27,28,29,30, 31, /* 31dB */ + 32,33,34,35,36,37,38, 39, /* 39dB */ + }}, + /* mode_Z nominal long */ + {{ + 0, 1, 2, 3, 4, 5, 6, 7, /* 7dB */ + 8, 9,10,11,12,12,13, 13, /* 15dB */ + 13,14,14,14,15,15,15, 15, /* 23dB */ + 16,16,17,17,17,18,18, 19, /* 31dB */ + 19,19,20,21,22,23,24, 25, /* 39dB */ + }}, + /* mode A long */ + {{ + 0, 1, 2, 3, 4, 5, 6, 7, /* 7dB */ + 8, 8, 7, 6, 5, 4, 4, 4, /* 15dB */ + 4, 4, 5, 5, 5, 6, 6, 6, /* 23dB */ + 7, 7, 7, 8, 8, 8, 9, 10, /* 31dB */ + 11,12,13,14,15,16,17, 18, /* 39dB */ + }} +}; + +/* tonal masking curve level adjustments *************************/ +static vp_adjblock _vp_tonemask_adj_longblock[11]={ + /* adjust for mode zero */ + /* 63 125 250 500 1 2 4 8 16 */ + {{-15,-15,-15,-15,-10, -8, -4,-2, 0, 0, 0,10, 0, 0, 0, 0, 0}}, /* 0 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 5, 0, 0, 0, 0, 0}}, /* 1 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 2 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 3 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 4 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 5 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 6 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 7 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 8 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 9 */ + {{-15,-15,-15,-15,-15,-12,-10,-8, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 10 */ +}; +static vp_adjblock _vp_tonemask_adj_otherblock[11]={ + /* adjust for mode zero */ + /* 63 125 250 500 1 2 4 8 16 */ + {{-20,-20,-20,-20,-14,-12,-10, -8, -4, 0, 0,10, 0, 0, 0, 0, 0}}, /* 0 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 5, 0, 0, 0, 0, 0}}, /* 1 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 2 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 3 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 4 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 5 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 6 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 7 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 8 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 9 */ + {{-20,-20,-20,-20,-20,-18,-16,-14,-10, 0, 0, 0, 0, 0, 0, 0, 0}}, /* 10 */ +}; + +static noise3 _psy_noisebias_trans_low[2]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + /* 0 */ + {{{-10,-10,-10,-10,-10, -4, 0, 0, 4, 8, 8, 8, 8, 10, 12, 14, 20}, + {-30,-30,-30,-30,-26,-20,-16, -8, -6, -6, -2, 2, 2, 4, 8, 8, 15}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, -6, -4, -2}}}, + /* 1 */ + {{{-15,-15,-15,-15,-15,-10, -5, 0, 2, 2, 6, 6, 6, 8, 10, 12, 15}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, -2, 0, 0, 0, 2, 4, 10}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -6, -6, -6, -4, -4, -4, -2}}}, +}; +static noise3 _psy_noisebias_long_low[2]={ + /*63 125 250 500 1k 2k 4k 8k 16k*/ + /* 0 */ + {{{-10,-10,-10,-10,-10, -4, 0, 0, 0, 6, 6, 6, 6, 10, 10, 12, 20}, + {-20,-20,-20,-20,-20,-20,-10, -2, 0, 0, 0, 0, 0, 2, 4, 6, 15}, + {-20,-20,-20,-20,-20,-20,-20,-10, -6, -6, -6, -6, -6, -4, -4, -4, -2}}}, + /* 1 */ + {{{-10,-10,-10,-10,-10,-10, -8, -8, 0, 2, 4, 4, 5, 5, 5, 8, 10}, + {-20,-20,-20,-20,-20,-20,-20,-14, -8, -2, 0, 0, 0, 0, 2, 4, 10}, + {-20,-20,-20,-20,-20,-20,-20,-14, -6, -6, -6, -6, -6, -4, -4, -4, -2}}}, +}; + +static noise3 _psy_noisebias_trans[11]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + /* 0 */ + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 4, 4, 5, 5, 5, 8, 10}, + {-30,-30,-30,-30,-26,-22,-20,-14, -8, -4, 0, 0, 0, 0, 2, 4, 10}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -6, -6, -6, -4, -4, -4, -2}}}, + /* 1 */ + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 4, 4, 5, 5, 5, 8, 10}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, -2, -2, -2, -2, 0, 2, 8}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -8, -8, -8, -8, -6, -6, -6, -4}}}, + /* 2 */ + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 2, 2, 4, 4, 5, 6, 10}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, -2, -2, -2, -2, 0, 2, 6}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10,-10,-10,-10,-10, -8, -8, -8, -4}}}, + /* 3 */ + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 2, 2, 4, 4, 4, 5, 8}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, -3, -3, -3, -3, -1, 1, 6}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10,-10,-10,-10,-10, -8, -8, -8, -4}}}, + /* 4 */ + {{{-20,-20,-20,-20,-20,-18,-14, -8, -1, 1, 1, 1, 2, 3, 3, 4, 7}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, -3, -3, -3, -3, -1, 1, 5}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10,-10,-10,-10,-10, -8, -8, -8, -4}}}, + /* 5 */ + {{{-24,-24,-24,-24,-20,-18,-14, -8, -1, 1, 1, 1, 2, 3, 3, 4, 7}, + {-32,-32,-32,-32,-28,-24,-22,-16,-12, -6, -4, -4, -4, -4, -2, -1, 2}, + {-34,-34,-34,-34,-30,-24,-24,-18,-14,-12,-12,-12,-12,-10,-10, -9, -5}}}, + /* 6 */ + {{{-24,-24,-24,-24,-20,-18,-14, -8, -1, 1, 1, 1, 2, 3, 3, 4, 7}, + {-32,-32,-32,-32,-28,-24,-24,-18,-14, -8, -6, -6, -6, -6, -4, -2, 1}, + {-34,-34,-34,-34,-30,-26,-24,-18,-17,-15,-15,-15,-15,-13,-13,-12, -8}}}, + /* 7 */ + {{{-24,-24,-24,-24,-20,-18,-14, -8, -1, 1, 1, 1, 2, 3, 3, 4, 7}, + {-32,-32,-32,-32,-28,-24,-24,-18,-14,-12,-10, -8, -8, -8, -6, -4, 0}, + {-34,-34,-34,-34,-30,-26,-26,-24,-22,-19,-19,-19,-19,-18,-17,-16,-12}}}, + /* 8 */ + {{{-24,-24,-24,-24,-22,-20,-15,-10, -8, -2, 0, 0, 0, 1, 2, 3, 7}, + {-36,-36,-36,-36,-30,-30,-30,-24,-18,-14,-12,-10,-10,-10, -8, -6, -2}, + {-36,-36,-36,-36,-34,-30,-28,-26,-24,-24,-24,-24,-24,-24,-24,-20,-16}}}, + /* 9 */ + {{{-28,-28,-28,-28,-28,-28,-28,-20,-14, -8, -4, -4, -4, -4, -4, -2, 2}, + {-36,-36,-36,-36,-34,-32,-32,-28,-20,-16,-16,-16,-16,-14,-12,-10, -7}, + {-40,-40,-40,-40,-40,-40,-40,-32,-30,-30,-30,-30,-30,-30,-30,-24,-20}}}, + /* 10 */ + {{{-30,-30,-30,-30,-30,-30,-30,-28,-20,-14,-14,-14,-14,-14,-14,-12,-10}, + {-40,-40,-40,-40,-40,-40,-40,-40,-35,-30,-30,-30,-30,-30,-30,-30,-20}, + {-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40}}}, +}; + +static noise3 _psy_noisebias_long[11]={ + /*63 125 250 500 1k 2k 4k 8k 16k*/ + /* 0 */ + {{{-10,-10,-10,-10,-10,-10, -8, 2, 2, 2, 4, 4, 5, 5, 5, 8, 10}, + {-20,-20,-20,-20,-20,-20,-20,-14, -6, 0, 0, 0, 0, 0, 2, 4, 10}, + {-20,-20,-20,-20,-20,-20,-20,-14, -8, -6, -6, -6, -6, -4, -4, -4, -2}}}, + /* 1 */ + {{{-10,-10,-10,-10,-10,-10, -8, -4, 0, 2, 4, 4, 5, 5, 5, 8, 10}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10, -4, -2, -2, -2, -2, 0, 2, 8}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10, -8, -8, -8, -8, -6, -6, -6, -4}}}, + /* 2 */ + {{{-10,-10,-10,-10,-10,-10,-10, -8, 0, 2, 2, 2, 4, 4, 5, 6, 10}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10, -4, -2, -2, -2, -2, 0, 2, 6}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10,-10,-10,-10,-10, -8, -8, -8, -4}}}, + /* 3 */ + {{{-10,-10,-10,-10,-10,-10,-10, -8, 0, 2, 2, 2, 4, 4, 4, 5, 8}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10, -4, -3, -3, -3, -3, -1, 1, 6}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10,-10,-10,-10,-10, -8, -8, -8, -4}}}, + /* 4 */ + {{{-15,-15,-15,-15,-15,-15,-15,-10, -4, 1, 1, 1, 2, 3, 3, 4, 7}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10, -4, -3, -3, -3, -3, -1, 1, 5}, + {-20,-20,-20,-20,-20,-20,-20,-14,-10,-10,-10,-10,-10, -8, -8, -8, -4}}}, + /* 5 */ + {{{-15,-15,-15,-15,-15,-15,-15,-10, -4, 1, 1, 1, 2, 3, 3, 4, 7}, + {-22,-22,-22,-22,-22,-22,-22,-16,-12, -6, -4, -4, -4, -4, -2, -1, 2}, + {-24,-24,-24,-24,-24,-24,-24,-18,-14,-12,-12,-12,-12,-10,-10, -9, -5}}}, + /* 6 */ + {{{-15,-15,-15,-15,-15,-15,-15,-10, -4, 1, 1, 1, 2, 3, 3, 4, 7}, + {-24,-24,-24,-24,-24,-24,-24,-18,-14, -8, -6, -6, -6, -6, -4, -2, 1}, + {-26,-26,-26,-26,-26,-26,-26,-18,-16,-15,-15,-15,-15,-13,-13,-12, -8}}}, + /* 7 */ + {{{-15,-15,-15,-15,-15,-15,-15,-10, -4, 1, 1, 1, 2, 3, 3, 4, 7}, + {-24,-24,-24,-24,-24,-24,-24,-18,-14,-10, -8, -8, -8, -8, -6, -4, 0}, + {-26,-26,-26,-26,-26,-26,-26,-22,-20,-19,-19,-19,-19,-18,-17,-16,-12}}}, + /* 8 */ + {{{-15,-15,-15,-15,-15,-15,-15,-10, -4, 0, 0, 0, 0, 1, 2, 3, 7}, + {-26,-26,-26,-26,-26,-26,-26,-20,-16,-12,-10,-10,-10,-10, -8, -6, -2}, + {-28,-28,-28,-28,-28,-28,-28,-26,-24,-24,-24,-24,-24,-24,-24,-20,-16}}}, + /* 9 */ + {{{-22,-22,-22,-22,-22,-22,-22,-18,-14, -8, -4, -4, -4, -4, -4, -2, 2}, + {-26,-26,-26,-26,-26,-26,-26,-22,-18,-16,-16,-16,-16,-14,-12,-10, -7}, + {-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-24,-20}}}, + /* 10 */ + {{{-24,-24,-24,-24,-24,-24,-24,-24,-24,-18,-14,-14,-14,-14,-14,-12,-10}, + {-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-20}, + {-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40}}}, +}; + +static noise3 _psy_noisebias_impulse[11]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + /* 0 */ + {{{-10,-10,-10,-10,-10, -4, 0, 0, 4, 4, 8, 8, 8, 10, 12, 14, 20}, + {-30,-30,-30,-30,-26,-22,-20,-14, -6, -2, 0, 0, 0, 0, 2, 4, 10}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, -6, -4, -2}}}, + /* 1 */ + {{{-12,-12,-12,-12,-12, -8, -6, -4, 0, 4, 4, 4, 4, 10, 12, 14, 20}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -4, -4, -2, -2, -2, -2, 2}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -8,-10,-10, -8, -8, -8, -6, -4}}}, + /* 2 */ + {{{-14,-14,-14,-14,-14,-10, -8, -6, -2, 2, 2, 2, 2, 8, 10, 10, 16}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -6, -6, -4, -4, -4, -2, 0}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10,-10,-10,-10,-10,-10,-10, -8, -4}}}, + /* 3 */ + {{{-14,-14,-14,-14,-14,-10, -8, -6, -2, 2, 2, 2, 2, 6, 8, 8, 14}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -6, -6, -4, -4, -4, -2, 0}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10,-10,-10,-10,-10,-10,-10, -8, -4}}}, + /* 4 */ + {{{-16,-16,-16,-16,-16,-12,-10, -6, -2, 0, 0, 0, 0, 4, 6, 6, 12}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -6, -6, -4, -4, -4, -2, 0}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10,-10,-10,-10,-10,-10,-10, -8, -4}}}, + /* 5 */ + {{{-20,-20,-20,-20,-20,-18,-14,-10, -4, 0, 0, 0, 0, 4, 4, 6, 11}, + {-32,-32,-32,-32,-28,-24,-22,-16,-10, -6, -8, -8, -6, -6, -6, -4, -2}, + {-34,-34,-34,-34,-30,-26,-24,-18,-14,-12,-12,-12,-12,-12,-10, -9, -5}}}, + /* 6 */ + {{{-20,-20,-20,-20,-20,-18,-14,-10, -4, 0, 0, 0, 0, 4, 4, 6, 11}, + {-34,-34,-34,-34,-30,-30,-24,-20,-12,-12,-14,-14,-10, -9, -8, -6, -4}, + {-34,-34,-34,-34,-34,-30,-26,-20,-16,-15,-15,-15,-15,-15,-13,-12, -8}}}, + /* 7 */ + {{{-22,-22,-22,-22,-22,-20,-14,-10, -6, 0, 0, 0, 0, 4, 4, 6, 11}, + {-34,-34,-34,-34,-30,-30,-24,-20,-14,-14,-16,-16,-14,-12,-10,-10,-10}, + {-34,-34,-34,-34,-32,-32,-30,-24,-20,-19,-19,-19,-19,-19,-17,-16,-12}}}, + /* 8 */ + {{{-24,-24,-24,-24,-24,-22,-14,-10, -6, -1, -1, -1, -1, 3, 3, 5, 10}, + {-34,-34,-34,-34,-30,-30,-30,-24,-20,-20,-20,-20,-20,-18,-16,-16,-14}, + {-36,-36,-36,-36,-36,-34,-28,-24,-24,-24,-24,-24,-24,-24,-24,-20,-16}}}, + /* 9 */ + {{{-28,-28,-28,-28,-28,-28,-28,-20,-14, -8, -4, -4, -4, -4, -4, -2, 2}, + {-36,-36,-36,-36,-34,-32,-32,-30,-26,-26,-26,-26,-26,-22,-20,-20,-18}, + {-40,-40,-40,-40,-40,-40,-40,-32,-30,-30,-30,-30,-30,-30,-30,-24,-20}}}, + /* 10 */ + {{{-30,-30,-30,-30,-30,-26,-24,-24,-24,-20,-16,-16,-16,-16,-16,-14,-12}, + {-40,-40,-40,-40,-40,-40,-40,-40,-35,-30,-30,-30,-30,-30,-30,-30,-26}, + {-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40}}}, +}; + + +static noise3 _psy_noisebias_padding[11]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + /* 0 */ + {{{-10,-10,-10,-10,-10, -4, 0, 0, 4, 8, 8, 8, 8, 10, 12, 14, 20}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, -2, 2, 3, 6, 6, 8, 10}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, -4, -4, -4, -4, -2, 0, 2}}}, + /* 1 */ + {{{-12,-12,-12,-12,-12, -8, -6, -4, 0, 4, 4, 4, 4, 10, 12, 14, 20}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, 0, 0, 0, 2, 2, 4, 8}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -6, -6, -6, -6, -4, -2, 0}}}, + /* 2 */ + {{{-14,-14,-14,-14,-14,-10, -8, -6, -2, 2, 2, 2, 2, 8, 10, 10, 16}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -4, 0, 0, 0, 2, 2, 4, 8}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -8, -8, -8, -8, -8, -6, -4, -2}}}, + /* 3 */ + {{{-14,-14,-14,-14,-14,-10, -8, -6, -2, 2, 2, 2, 2, 6, 8, 8, 14}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -1, -1, -1, 0, 0, 2, 6}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -8, -8, -8, -8, -8, -6, -4, -2}}}, + /* 4 */ + {{{-16,-16,-16,-16,-16,-12,-10, -6, -2, 0, 0, 0, 0, 4, 6, 6, 12}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -6, -1, -1, -1, -1, 0, 2, 6}, + {-30,-30,-30,-30,-26,-22,-20,-14,-10, -8, -8, -8, -8, -8, -6, -4, -2}}}, + /* 5 */ + {{{-20,-20,-20,-20,-20,-18,-14,-10, -4, 0, 0, 0, 0, 4, 6, 6, 12}, + {-32,-32,-32,-32,-28,-24,-22,-16,-12, -6, -3, -3, -3, -3, -2, 0, 4}, + {-34,-34,-34,-34,-30,-26,-24,-18,-14,-10,-10,-10,-10,-10, -8, -5, -3}}}, + /* 6 */ + {{{-20,-20,-20,-20,-20,-18,-14,-10, -4, 0, 0, 0, 0, 4, 6, 6, 12}, + {-34,-34,-34,-34,-30,-30,-24,-20,-14, -8, -4, -4, -4, -4, -3, -1, 4}, + {-34,-34,-34,-34,-34,-30,-26,-20,-16,-13,-13,-13,-13,-13,-11, -8, -6}}}, + /* 7 */ + {{{-20,-20,-20,-20,-20,-18,-14,-10, -4, 0, 0, 0, 0, 4, 6, 6, 12}, + {-34,-34,-34,-34,-30,-30,-30,-24,-16,-10, -8, -6, -6, -6, -5, -3, 1}, + {-34,-34,-34,-34,-32,-32,-28,-22,-18,-16,-16,-16,-16,-16,-14,-12,-10}}}, + /* 8 */ + {{{-22,-22,-22,-22,-22,-20,-14,-10, -4, 0, 0, 0, 0, 3, 5, 5, 11}, + {-34,-34,-34,-34,-30,-30,-30,-24,-16,-12,-10, -8, -8, -8, -7, -5, -2}, + {-36,-36,-36,-36,-36,-34,-28,-22,-20,-20,-20,-20,-20,-20,-20,-16,-14}}}, + /* 9 */ + {{{-28,-28,-28,-28,-28,-28,-28,-20,-14, -8, -2, -2, -2, -2, 0, 2, 6}, + {-36,-36,-36,-36,-34,-32,-32,-24,-16,-12,-12,-12,-12,-12,-10, -8, -5}, + {-40,-40,-40,-40,-40,-40,-40,-32,-26,-24,-24,-24,-24,-24,-24,-20,-18}}}, + /* 10 */ + {{{-30,-30,-30,-30,-30,-26,-24,-24,-24,-20,-12,-12,-12,-12,-12,-10, -8}, + {-40,-40,-40,-40,-40,-40,-40,-40,-35,-30,-25,-25,-25,-25,-25,-25,-15}, + {-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40,-40}}}, +}; + +static noiseguard _psy_noiseguards_44[4]={ + {3,3,15}, + {3,3,15}, + {10,10,100}, + {10,10,100}, +}; + +static int _psy_tone_suppress[11]={ + -20,-20,-20,-20,-24,-30,-40,-40,-45,-45,-45, +}; + +static int _psy_tone_0dB[11]={ + 90,95,95,95,95,105,105,105,105,105,105, +}; + +static int _psy_noise_suppress[11]={ + -20,-24,-24,-24,-24,-30,-40,-40,-45,-45,-45, +}; + +static vorbis_info_psy _psy_info_template={ + -1, + -140.,-140., + /* tonemask att boost/decay,suppr,curves */ + {0.f,0.f,0.f}, 0.,0., -40.f, {0.}, + + /*noisemaskp,supp, low/high window, low/hi guard, minimum */ + 1, -0.f, .5f, .5f, 0,0,0, + {{-1},{-1},{-1}},{-1},105.f, + + 0,0,-1,-1,0., +}; + +/* ath ****************/ + +static int _psy_ath_floater[11]={ + -100,-100,-100,-100,-100,-105,-105,-105,-105,-110,-120, +}; + +static int _psy_ath_abs[11]={ + -130,-130,-130,-140,-140,-140,-140,-140,-140,-140,-150, +}; + +/* stereo setup. These don't map directly to quality level, there's + an additional indirection as several of the below may be used in a + single bitmanaged stream + +****************/ + +/* various stereo possibilities */ + +/* stereo mode by base quality level */ +static adj_stereo _psy_stereo_modes_44_low[2]={ + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 */ + {{ 4, 4, 4, 4, 4, 4, 4, 3, 2, 2, 1, 0, 0, 0, 0}, + { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 4, 3}, + { 1, 2, 3, 4, 4, 4, 4, 4, 4, 5, 6, 7, 8, 8, 8}, + { 12,12.5, 13,13.5, 14,14.5, 15, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 */ + {{ 4, 4, 4, 4, 4, 4, 4, 3, 2, 2, 1, 0, 0, 0, 0}, + { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 4, 3}, + { 1, 2, 3, 4, 5, 5, 6, 6, 6, 6, 6, 7, 8, 8, 8}, + { 12,12.5, 13,13.5, 14,14.5, 15, 99, 99, 99, 99, 99, 99, 99, 99}}, +}; + +static adj_stereo _psy_stereo_modes_44[11]={ + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 */ + {{ 4, 4, 4, 4, 4, 4, 4, 3, 2, 2, 1, 0, 0, 0, 0}, + { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 4, 3}, + { 1, 2, 3, 4, 5, 5, 6, 6, 6, 6, 6, 7, 8, 8, 8}, + { 12,12.5, 13,13.5, 14,14.5, 15, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 */ + {{ 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0, 0, 0, 0, 0}, + { 8, 8, 8, 8, 6, 6, 5, 5, 5, 5, 5, 5, 5, 4, 3}, + { 1, 2, 3, 4, 4, 5, 6, 6, 6, 6, 6, 8, 8, 8, 8}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 2 */ + {{ 3, 3, 3, 3, 3, 3, 2, 2, 2, 1, 0, 0, 0, 0, 0}, + { 8, 8, 8, 6, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2, 1}, + { 3, 4, 4, 4, 5, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 3 */ + {{ 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, + { 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 2, 1}, + { 4, 4, 5, 6, 6, 6, 6, 6, 8, 8, 10, 10, 10, 10, 10}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 4 */ + {{ 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 2, 1, 0}, + { 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 5 */ + {{ 2, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0}, + { 6, 6, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 6 */ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 3, 3, 3, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 7 */ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 3, 3, 3, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 8 */ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 9 */ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 10 */ + {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, +}; + +/* tone master attenuation by base quality mode and bitrate tweak */ + +static att3 _psy_tone_masteratt_44_low[2]={ + {{ 34, 20, 8}, 0, 1.25}, /* 0 */ + {{ 34, 20, 8}, -2, 1.25}, /* 1 */ +}; +static att3 _psy_tone_masteratt_44[11]={ + {{ 30, 20, 8}, -2, 1.25}, /* 0 */ + {{ 25, 14, 4}, 0, 0}, /* 1 */ + {{ 20, 10, -2}, 0, 0}, /* 2 */ + {{ 20, 9, -4}, 0, 0}, /* 3 */ + {{ 20, 9, -4}, 0, 0}, /* 4 */ + {{ 20, 6, -6}, 0, 0}, /* 5 */ + {{ 20, 3, -10}, 0, 0}, /* 6 */ + {{ 18, 1, -14}, 0, 0}, /* 7 */ + {{ 18, 0, -16}, 0, 0}, /* 8 */ + {{ 18, -2, -16}, 0, 0}, /* 9 */ + {{ 12, -2, -20}, 0, 0}, /* 10 */ +}; + +/* lowpass by mode **************/ +static double _psy_lowpass_44_low[2]={ + 15.1,15.1, +}; +static double _psy_lowpass_44[11]={ + 15.1,15.8,16.5,17.9,20.5,48.,999.,999.,999.,999.,999. +}; + +/* noise normalization **********/ + +static int _noise_start_short_44[10]={ + 16,16,16,16,32,32,9999,9999,9999,9999 +}; +static int _noise_start_long_44[10]={ + 128,128,128,256,512,512,9999,9999,9999,9999 +}; +static int _noise_part_short_44[10]={ + 8,8,8,8,8,8,8,8,8,8 +}; +static int _noise_part_long_44[10]={ + 32,32,32,32,32,32,32,32,32,32 +}; +static double _noise_thresh_44[10]={ + .2,.2,.3,.4,.5,.5,9999.,9999.,9999.,9999., +}; +static double _noise_thresh_44_2[2]={ + .5,.5, +}; + + +static int _noise_start_short_44_low[2]={ + 32,32 +}; +static int _noise_start_long_44_low[2]={ + 256,256 +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/psych_8.h b/src/sound/oggvorbis/vorbissrc/modes/psych_8.h new file mode 100644 index 0000000..beda7d1 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/psych_8.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: 8kHz psychoacoustic settings + last mod: $Id: psych_8.h,v 1.3 2003/09/02 06:05:47 xiphmont Exp $ + + ********************************************************************/ + +static att3 _psy_tone_masteratt_8[3]={ + {{ 32, 25, 12}, 0, 0}, /* 0 */ + {{ 30, 25, 12}, 0, 0}, /* 0 */ + {{ 20, 0, -14}, 0, 0}, /* 0 */ +}; + +static vp_adjblock _vp_tonemask_adj_8[3]={ + /* adjust for mode zero */ + /* 63 125 250 500 1 2 4 8 16 */ + {{-15,-15,-15,-15,-10,-10, -6, 0, 0, 0, 0,10, 0, 0,99,99,99}}, /* 1 */ + {{-15,-15,-15,-15,-10,-10, -6, 0, 0, 0, 0,10, 0, 0,99,99,99}}, /* 1 */ + {{-15,-15,-15,-15,-10,-10, -6, 0, 0, 0, 0, 0, 0, 0,99,99,99}}, /* 1 */ +}; + + +static noise3 _psy_noisebias_8[3]={ + /* 63 125 250 500 1k 2k 4k 8k 16k*/ + {{{-10,-10,-10,-10, -5, -5, -5, 0, 4, 8, 8, 8, 10, 10, 99, 99, 99}, + {-10,-10,-10,-10, -5, -5, -5, 0, 0, 4, 4, 4, 4, 4, 99, 99, 99}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, 99, 99, 99}}}, + + {{{-10,-10,-10,-10, -5, -5, -5, 0, 4, 8, 8, 8, 10, 10, 99, 99, 99}, + {-10,-10,-10,-10,-10,-10, -5, -5, -5, 0, 0, 0, 0, 0, 99, 99, 99}, + {-30,-30,-30,-30,-30,-24,-20,-14,-10, -6, -8, -8, -6, -6, 99, 99, 99}}}, + + {{{-15,-15,-15,-15,-15,-12,-10, -8, 0, 2, 4, 4, 5, 5, 99, 99, 99}, + {-30,-30,-30,-30,-26,-22,-20,-14,-12,-12,-10,-10,-10,-10, 99, 99, 99}, + {-30,-30,-30,-30,-26,-26,-26,-26,-26,-26,-26,-26,-26,-24, 99, 99, 99}}}, +}; + +/* stereo mode by base quality level */ +static adj_stereo _psy_stereo_modes_8[3]={ + /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 */ + {{ 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + { 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + {{ 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + { 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, + {{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + { 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}, +}; + +static noiseguard _psy_noiseguards_8[2]={ + {10,10,-1}, + {10,10,-1}, +}; + +static compandblock _psy_compand_8[2]={ + {{ + 0, 1, 2, 3, 4, 5, 6, 7, /* 7dB */ + 8, 8, 9, 9,10,10,11, 11, /* 15dB */ + 12,12,13,13,14,14,15, 15, /* 23dB */ + 16,16,17,17,17,18,18, 19, /* 31dB */ + 19,19,20,21,22,23,24, 25, /* 39dB */ + }}, + {{ + 0, 1, 2, 3, 4, 5, 6, 6, /* 7dB */ + 7, 7, 6, 6, 5, 5, 4, 4, /* 15dB */ + 3, 3, 3, 4, 5, 6, 7, 8, /* 23dB */ + 9,10,11,12,13,14,15, 16, /* 31dB */ + 17,18,19,20,21,22,23, 24, /* 39dB */ + }}, +}; + +static double _psy_lowpass_8[3]={3.,4.,4.}; +static int _noise_start_8[2]={ + 64,64, +}; +static int _noise_part_8[2]={ + 8,8, +}; + +static int _psy_ath_floater_8[3]={ + -100,-100,-105, +}; + +static int _psy_ath_abs_8[3]={ + -130,-130,-140, +}; + diff --git a/src/sound/oggvorbis/vorbissrc/modes/residue_16.h b/src/sound/oggvorbis/vorbissrc/modes/residue_16.h new file mode 100644 index 0000000..4590d63 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/residue_16.h @@ -0,0 +1,187 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel residue templates 16/22kHz + last mod: $Id: residue_16.h,v 1.3 2002/07/11 10:27:55 xiphmont Exp $ + + ********************************************************************/ + +/***** residue backends *********************************************/ + +static static_bookblock _resbook_16s_0={ + { + {0}, + {0,0,&_16c0_s_p1_0}, + {0,0,&_16c0_s_p2_0}, + {0,0,&_16c0_s_p3_0}, + {0,0,&_16c0_s_p4_0}, + {0,0,&_16c0_s_p5_0}, + {0,0,&_16c0_s_p6_0}, + {&_16c0_s_p7_0,&_16c0_s_p7_1}, + {&_16c0_s_p8_0,&_16c0_s_p8_1}, + {&_16c0_s_p9_0,&_16c0_s_p9_1,&_16c0_s_p9_2} + } +}; +static static_bookblock _resbook_16s_1={ + { + {0}, + {0,0,&_16c1_s_p1_0}, + {0,0,&_16c1_s_p2_0}, + {0,0,&_16c1_s_p3_0}, + {0,0,&_16c1_s_p4_0}, + {0,0,&_16c1_s_p5_0}, + {0,0,&_16c1_s_p6_0}, + {&_16c1_s_p7_0,&_16c1_s_p7_1}, + {&_16c1_s_p8_0,&_16c1_s_p8_1}, + {&_16c1_s_p9_0,&_16c1_s_p9_1,&_16c1_s_p9_2} + } +}; +static static_bookblock _resbook_16s_2={ + { + {0}, + {0,0,&_16c2_s_p1_0}, + {0,0,&_16c2_s_p2_0}, + {0,0,&_16c2_s_p3_0}, + {0,0,&_16c2_s_p4_0}, + {&_16c2_s_p5_0,&_16c2_s_p5_1}, + {&_16c2_s_p6_0,&_16c2_s_p6_1}, + {&_16c2_s_p7_0,&_16c2_s_p7_1}, + {&_16c2_s_p8_0,&_16c2_s_p8_1}, + {&_16c2_s_p9_0,&_16c2_s_p9_1,&_16c2_s_p9_2} + } +}; + +static vorbis_residue_template _res_16s_0[]={ + {2,0, &_residue_44_mid, + &_huff_book__16c0_s_single,&_huff_book__16c0_s_single, + &_resbook_16s_0,&_resbook_16s_0}, +}; +static vorbis_residue_template _res_16s_1[]={ + {2,0, &_residue_44_mid, + &_huff_book__16c1_s_short,&_huff_book__16c1_s_short, + &_resbook_16s_1,&_resbook_16s_1}, + + {2,0, &_residue_44_mid, + &_huff_book__16c1_s_long,&_huff_book__16c1_s_long, + &_resbook_16s_1,&_resbook_16s_1} +}; +static vorbis_residue_template _res_16s_2[]={ + {2,0, &_residue_44_high, + &_huff_book__16c2_s_short,&_huff_book__16c2_s_short, + &_resbook_16s_2,&_resbook_16s_2}, + + {2,0, &_residue_44_high, + &_huff_book__16c2_s_long,&_huff_book__16c2_s_long, + &_resbook_16s_2,&_resbook_16s_2} +}; + +static vorbis_mapping_template _mapres_template_16_stereo[3]={ + { _map_nominal, _res_16s_0 }, /* 0 */ + { _map_nominal, _res_16s_1 }, /* 1 */ + { _map_nominal, _res_16s_2 }, /* 2 */ +}; + +static static_bookblock _resbook_16u_0={ + { + {0}, + {0,0,&_16u0__p1_0}, + {0,0,&_16u0__p2_0}, + {0,0,&_16u0__p3_0}, + {0,0,&_16u0__p4_0}, + {0,0,&_16u0__p5_0}, + {&_16u0__p6_0,&_16u0__p6_1}, + {&_16u0__p7_0,&_16u0__p7_1,&_16u0__p7_2} + } +}; +static static_bookblock _resbook_16u_1={ + { + {0}, + {0,0,&_16u1__p1_0}, + {0,0,&_16u1__p2_0}, + {0,0,&_16u1__p3_0}, + {0,0,&_16u1__p4_0}, + {0,0,&_16u1__p5_0}, + {0,0,&_16u1__p6_0}, + {&_16u1__p7_0,&_16u1__p7_1}, + {&_16u1__p8_0,&_16u1__p8_1}, + {&_16u1__p9_0,&_16u1__p9_1,&_16u1__p9_2} + } +}; +static static_bookblock _resbook_16u_2={ + { + {0}, + {0,0,&_16u2_p1_0}, + {0,0,&_16u2_p2_0}, + {0,0,&_16u2_p3_0}, + {0,0,&_16u2_p4_0}, + {&_16u2_p5_0,&_16u2_p5_1}, + {&_16u2_p6_0,&_16u2_p6_1}, + {&_16u2_p7_0,&_16u2_p7_1}, + {&_16u2_p8_0,&_16u2_p8_1}, + {&_16u2_p9_0,&_16u2_p9_1,&_16u2_p9_2} + } +}; + +static vorbis_residue_template _res_16u_0[]={ + {1,0, &_residue_44_low_un, + &_huff_book__16u0__single,&_huff_book__16u0__single, + &_resbook_16u_0,&_resbook_16u_0}, +}; +static vorbis_residue_template _res_16u_1[]={ + {1,0, &_residue_44_mid_un, + &_huff_book__16u1__short,&_huff_book__16u1__short, + &_resbook_16u_1,&_resbook_16u_1}, + + {1,0, &_residue_44_mid_un, + &_huff_book__16u1__long,&_huff_book__16u1__long, + &_resbook_16u_1,&_resbook_16u_1} +}; +static vorbis_residue_template _res_16u_2[]={ + {1,0, &_residue_44_hi_un, + &_huff_book__16u2__short,&_huff_book__16u2__short, + &_resbook_16u_2,&_resbook_16u_2}, + + {1,0, &_residue_44_hi_un, + &_huff_book__16u2__long,&_huff_book__16u2__long, + &_resbook_16u_2,&_resbook_16u_2} +}; + + +static vorbis_mapping_template _mapres_template_16_uncoupled[3]={ + { _map_nominal_u, _res_16u_0 }, /* 0 */ + { _map_nominal_u, _res_16u_1 }, /* 1 */ + { _map_nominal_u, _res_16u_2 }, /* 2 */ +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/residue_44.h b/src/sound/oggvorbis/vorbissrc/modes/residue_44.h new file mode 100644 index 0000000..260d9b7 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/residue_44.h @@ -0,0 +1,278 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel residue templates for 32/44.1/48kHz + last mod: $Id: residue_44.h,v 1.16 2002/07/11 06:41:04 xiphmont Exp $ + + ********************************************************************/ + +#include "../vorbis/codec.h" +#include "backends.h" +#include "books/coupled/res_books_stereo.h" + +/***** residue backends *********************************************/ + +static vorbis_info_residue0 _residue_44_mid={ + 0,-1, -1, 10,-1, + /* 0 1 2 3 4 5 6 7 8 */ + {0}, + {-1}, + { .5, 1.5, 1.5, 2.5, 2.5, 4.5, 8.5, 16.5, 32.5}, + { .5, .5, 999., .5, 999., 4.5, 8.5, 16.5, 32.5}, +}; + +static vorbis_info_residue0 _residue_44_high={ + 0,-1, -1, 10,-1, + /* 0 1 2 3 4 5 6 7 8 */ + {0}, + {-1}, + { .5, 1.5, 2.5, 4.5, 8.5, 16.5, 32.5, 71.5,157.5}, + { .5, 1.5, 2.5, 3.5, 4.5, 8.5, 16.5, 71.5,157.5}, +}; + +static static_bookblock _resbook_44s_0={ + { + {0},{0,0,&_44c0_s_p1_0},{0,0,&_44c0_s_p2_0},{0,0,&_44c0_s_p3_0}, + {0,0,&_44c0_s_p4_0},{0,0,&_44c0_s_p5_0},{0,0,&_44c0_s_p6_0}, + {&_44c0_s_p7_0,&_44c0_s_p7_1},{&_44c0_s_p8_0,&_44c0_s_p8_1}, + {&_44c0_s_p9_0,&_44c0_s_p9_1,&_44c0_s_p9_2} + } +}; +static static_bookblock _resbook_44sm_0={ + { + {0},{0,0,&_44c0_sm_p1_0},{0,0,&_44c0_sm_p2_0},{0,0,&_44c0_sm_p3_0}, + {0,0,&_44c0_sm_p4_0},{0,0,&_44c0_sm_p5_0},{0,0,&_44c0_sm_p6_0}, + {&_44c0_sm_p7_0,&_44c0_sm_p7_1},{&_44c0_sm_p8_0,&_44c0_sm_p8_1}, + {&_44c0_sm_p9_0,&_44c0_sm_p9_1,&_44c0_sm_p9_2} + } +}; +static static_bookblock _resbook_44s_1={ + { + {0},{0,0,&_44c1_s_p1_0},{0,0,&_44c1_s_p2_0},{0,0,&_44c1_s_p3_0}, + {0,0,&_44c1_s_p4_0},{0,0,&_44c1_s_p5_0},{0,0,&_44c1_s_p6_0}, + {&_44c1_s_p7_0,&_44c1_s_p7_1},{&_44c1_s_p8_0,&_44c1_s_p8_1}, + {&_44c1_s_p9_0,&_44c1_s_p9_1,&_44c1_s_p9_2} + } +}; +static static_bookblock _resbook_44sm_1={ + { + {0},{0,0,&_44c1_sm_p1_0},{0,0,&_44c1_sm_p2_0},{0,0,&_44c1_sm_p3_0}, + {0,0,&_44c1_sm_p4_0},{0,0,&_44c1_sm_p5_0},{0,0,&_44c1_sm_p6_0}, + {&_44c1_sm_p7_0,&_44c1_sm_p7_1},{&_44c1_sm_p8_0,&_44c1_sm_p8_1}, + {&_44c1_sm_p9_0,&_44c1_sm_p9_1,&_44c1_sm_p9_2} + } +}; +static static_bookblock _resbook_44s_2={ + { + {0},{0,0,&_44c2_s_p1_0},{0,0,&_44c2_s_p2_0},{0,0,&_44c2_s_p3_0}, + {0,0,&_44c2_s_p4_0},{0,0,&_44c2_s_p5_0},{0,0,&_44c2_s_p6_0}, + {&_44c2_s_p7_0,&_44c2_s_p7_1},{&_44c2_s_p8_0,&_44c2_s_p8_1}, + {&_44c2_s_p9_0,&_44c2_s_p9_1,&_44c2_s_p9_2} + } +}; +static static_bookblock _resbook_44s_3={ + { + {0},{0,0,&_44c3_s_p1_0},{0,0,&_44c3_s_p2_0},{0,0,&_44c3_s_p3_0}, + {0,0,&_44c3_s_p4_0},{0,0,&_44c3_s_p5_0},{0,0,&_44c3_s_p6_0}, + {&_44c3_s_p7_0,&_44c3_s_p7_1},{&_44c3_s_p8_0,&_44c3_s_p8_1}, + {&_44c3_s_p9_0,&_44c3_s_p9_1,&_44c3_s_p9_2} + } +}; +static static_bookblock _resbook_44s_4={ + { + {0},{0,0,&_44c4_s_p1_0},{0,0,&_44c4_s_p2_0},{0,0,&_44c4_s_p3_0}, + {0,0,&_44c4_s_p4_0},{0,0,&_44c4_s_p5_0},{0,0,&_44c4_s_p6_0}, + {&_44c4_s_p7_0,&_44c4_s_p7_1},{&_44c4_s_p8_0,&_44c4_s_p8_1}, + {&_44c4_s_p9_0,&_44c4_s_p9_1,&_44c4_s_p9_2} + } +}; +static static_bookblock _resbook_44s_5={ + { + {0},{0,0,&_44c5_s_p1_0},{0,0,&_44c5_s_p2_0},{0,0,&_44c5_s_p3_0}, + {0,0,&_44c5_s_p4_0},{0,0,&_44c5_s_p5_0},{0,0,&_44c5_s_p6_0}, + {&_44c5_s_p7_0,&_44c5_s_p7_1},{&_44c5_s_p8_0,&_44c5_s_p8_1}, + {&_44c5_s_p9_0,&_44c5_s_p9_1,&_44c5_s_p9_2} + } +}; +static static_bookblock _resbook_44s_6={ + { + {0},{0,0,&_44c6_s_p1_0},{0,0,&_44c6_s_p2_0},{0,0,&_44c6_s_p3_0}, + {0,0,&_44c6_s_p4_0}, + {&_44c6_s_p5_0,&_44c6_s_p5_1}, + {&_44c6_s_p6_0,&_44c6_s_p6_1}, + {&_44c6_s_p7_0,&_44c6_s_p7_1}, + {&_44c6_s_p8_0,&_44c6_s_p8_1}, + {&_44c6_s_p9_0,&_44c6_s_p9_1,&_44c6_s_p9_2} + } +}; +static static_bookblock _resbook_44s_7={ + { + {0},{0,0,&_44c7_s_p1_0},{0,0,&_44c7_s_p2_0},{0,0,&_44c7_s_p3_0}, + {0,0,&_44c7_s_p4_0}, + {&_44c7_s_p5_0,&_44c7_s_p5_1}, + {&_44c7_s_p6_0,&_44c7_s_p6_1}, + {&_44c7_s_p7_0,&_44c7_s_p7_1}, + {&_44c7_s_p8_0,&_44c7_s_p8_1}, + {&_44c7_s_p9_0,&_44c7_s_p9_1,&_44c7_s_p9_2} + } +}; +static static_bookblock _resbook_44s_8={ + { + {0},{0,0,&_44c8_s_p1_0},{0,0,&_44c8_s_p2_0},{0,0,&_44c8_s_p3_0}, + {0,0,&_44c8_s_p4_0}, + {&_44c8_s_p5_0,&_44c8_s_p5_1}, + {&_44c8_s_p6_0,&_44c8_s_p6_1}, + {&_44c8_s_p7_0,&_44c8_s_p7_1}, + {&_44c8_s_p8_0,&_44c8_s_p8_1}, + {&_44c8_s_p9_0,&_44c8_s_p9_1,&_44c8_s_p9_2} + } +}; +static static_bookblock _resbook_44s_9={ + { + {0},{0,0,&_44c9_s_p1_0},{0,0,&_44c9_s_p2_0},{0,0,&_44c9_s_p3_0}, + {0,0,&_44c9_s_p4_0}, + {&_44c9_s_p5_0,&_44c9_s_p5_1}, + {&_44c9_s_p6_0,&_44c9_s_p6_1}, + {&_44c9_s_p7_0,&_44c9_s_p7_1}, + {&_44c9_s_p8_0,&_44c9_s_p8_1}, + {&_44c9_s_p9_0,&_44c9_s_p9_1,&_44c9_s_p9_2} + } +}; + + +static vorbis_residue_template _res_44s_0[]={ + {2,0, &_residue_44_mid, + &_huff_book__44c0_s_short,&_huff_book__44c0_sm_short, + &_resbook_44s_0,&_resbook_44sm_0}, + + {2,0, &_residue_44_mid, + &_huff_book__44c0_s_long,&_huff_book__44c0_sm_long, + &_resbook_44s_0,&_resbook_44sm_0} +}; +static vorbis_residue_template _res_44s_1[]={ + {2,0, &_residue_44_mid, + &_huff_book__44c1_s_short,&_huff_book__44c1_sm_short, + &_resbook_44s_1,&_resbook_44sm_1}, + + {2,0, &_residue_44_mid, + &_huff_book__44c1_s_long,&_huff_book__44c1_sm_long, + &_resbook_44s_1,&_resbook_44sm_1} +}; +static vorbis_residue_template _res_44s_2[]={ + {2,0, &_residue_44_mid, + &_huff_book__44c2_s_short,&_huff_book__44c2_s_short, + &_resbook_44s_2,&_resbook_44s_2}, + + {2,0, &_residue_44_mid, + &_huff_book__44c2_s_long,&_huff_book__44c2_s_long, + &_resbook_44s_2,&_resbook_44s_2} +}; +static vorbis_residue_template _res_44s_3[]={ + {2,0, &_residue_44_mid, + &_huff_book__44c3_s_short,&_huff_book__44c3_s_short, + &_resbook_44s_3,&_resbook_44s_3}, + + {2,0, &_residue_44_mid, + &_huff_book__44c3_s_long,&_huff_book__44c3_s_long, + &_resbook_44s_3,&_resbook_44s_3} +}; +static vorbis_residue_template _res_44s_4[]={ + {2,0, &_residue_44_mid, + &_huff_book__44c4_s_short,&_huff_book__44c4_s_short, + &_resbook_44s_4,&_resbook_44s_4}, + + {2,0, &_residue_44_mid, + &_huff_book__44c4_s_long,&_huff_book__44c4_s_long, + &_resbook_44s_4,&_resbook_44s_4} +}; +static vorbis_residue_template _res_44s_5[]={ + {2,0, &_residue_44_mid, + &_huff_book__44c5_s_short,&_huff_book__44c5_s_short, + &_resbook_44s_5,&_resbook_44s_5}, + + {2,0, &_residue_44_mid, + &_huff_book__44c5_s_long,&_huff_book__44c5_s_long, + &_resbook_44s_5,&_resbook_44s_5} +}; +static vorbis_residue_template _res_44s_6[]={ + {2,0, &_residue_44_high, + &_huff_book__44c6_s_short,&_huff_book__44c6_s_short, + &_resbook_44s_6,&_resbook_44s_6}, + + {2,0, &_residue_44_high, + &_huff_book__44c6_s_long,&_huff_book__44c6_s_long, + &_resbook_44s_6,&_resbook_44s_6} +}; +static vorbis_residue_template _res_44s_7[]={ + {2,0, &_residue_44_high, + &_huff_book__44c7_s_short,&_huff_book__44c7_s_short, + &_resbook_44s_7,&_resbook_44s_7}, + + {2,0, &_residue_44_high, + &_huff_book__44c7_s_long,&_huff_book__44c7_s_long, + &_resbook_44s_7,&_resbook_44s_7} +}; +static vorbis_residue_template _res_44s_8[]={ + {2,0, &_residue_44_high, + &_huff_book__44c8_s_short,&_huff_book__44c8_s_short, + &_resbook_44s_8,&_resbook_44s_8}, + + {2,0, &_residue_44_high, + &_huff_book__44c8_s_long,&_huff_book__44c8_s_long, + &_resbook_44s_8,&_resbook_44s_8} +}; +static vorbis_residue_template _res_44s_9[]={ + {2,0, &_residue_44_high, + &_huff_book__44c9_s_short,&_huff_book__44c9_s_short, + &_resbook_44s_9,&_resbook_44s_9}, + + {2,0, &_residue_44_high, + &_huff_book__44c9_s_long,&_huff_book__44c9_s_long, + &_resbook_44s_9,&_resbook_44s_9} +}; + +static vorbis_mapping_template _mapres_template_44_stereo[]={ + { _map_nominal, _res_44s_0 }, /* 0 */ + { _map_nominal, _res_44s_1 }, /* 1 */ + { _map_nominal, _res_44s_2 }, /* 2 */ + { _map_nominal, _res_44s_3 }, /* 3 */ + { _map_nominal, _res_44s_4 }, /* 4 */ + { _map_nominal, _res_44s_5 }, /* 5 */ + { _map_nominal, _res_44s_6 }, /* 6 */ + { _map_nominal, _res_44s_7 }, /* 7 */ + { _map_nominal, _res_44s_8 }, /* 8 */ + { _map_nominal, _res_44s_9 }, /* 9 */ +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/residue_44u.h b/src/sound/oggvorbis/vorbissrc/modes/residue_44u.h new file mode 100644 index 0000000..57994b4 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/residue_44u.h @@ -0,0 +1,320 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel residue templates for 32/44.1/48kHz uncoupled + last mod: $Id: residue_44u.h,v 1.3 2002/07/11 06:41:04 xiphmont Exp $ + + ********************************************************************/ + +#include "../vorbis/codec.h" +#include "backends.h" +#include "books/uncoupled/res_books_uncoupled.h" + +/***** residue backends *********************************************/ + + +static vorbis_info_residue0 _residue_44_low_un={ + 0,-1, -1, 8,-1, + {0}, + {-1}, + { .5, 1.5, 1.5, 2.5, 2.5, 4.5, 28.5}, + { -1, 25, -1, 45, -1, -1, -1} +}; + +static vorbis_info_residue0 _residue_44_mid_un={ + 0,-1, -1, 10,-1, + /* 0 1 2 3 4 5 6 7 8 9 */ + {0}, + {-1}, + { .5, 1.5, 1.5, 2.5, 2.5, 4.5, 4.5, 16.5, 60.5}, + { -1, 30, -1, 50, -1, 80, -1, -1, -1} +}; + +static vorbis_info_residue0 _residue_44_hi_un={ + 0,-1, -1, 10,-1, + /* 0 1 2 3 4 5 6 7 8 9 */ + {0}, + {-1}, + { .5, 1.5, 2.5, 4.5, 8.5, 16.5, 32.5, 71.5,157.5}, + { -1, -1, -1, -1, -1, -1, -1, -1, -1} +}; + +/* mapping conventions: + only one submap (this would change for efficient 5.1 support for example)*/ +/* Four psychoacoustic profiles are used, one for each blocktype */ +static vorbis_info_mapping0 _map_nominal_u[2]={ + {1, {0,0}, {0}, {0}, 0,{0},{0}}, + {1, {0,0}, {1}, {1}, 0,{0},{0}} +}; + +static static_bookblock _resbook_44u_0={ + { + {0}, + {0,0,&_44u0__p1_0}, + {0,0,&_44u0__p2_0}, + {0,0,&_44u0__p3_0}, + {0,0,&_44u0__p4_0}, + {0,0,&_44u0__p5_0}, + {&_44u0__p6_0,&_44u0__p6_1}, + {&_44u0__p7_0,&_44u0__p7_1,&_44u0__p7_2} + } +}; +static static_bookblock _resbook_44u_1={ + { + {0}, + {0,0,&_44u1__p1_0}, + {0,0,&_44u1__p2_0}, + {0,0,&_44u1__p3_0}, + {0,0,&_44u1__p4_0}, + {0,0,&_44u1__p5_0}, + {&_44u1__p6_0,&_44u1__p6_1}, + {&_44u1__p7_0,&_44u1__p7_1,&_44u1__p7_2} + } +}; +static static_bookblock _resbook_44u_2={ + { + {0}, + {0,0,&_44u2__p1_0}, + {0,0,&_44u2__p2_0}, + {0,0,&_44u2__p3_0}, + {0,0,&_44u2__p4_0}, + {0,0,&_44u2__p5_0}, + {&_44u2__p6_0,&_44u2__p6_1}, + {&_44u2__p7_0,&_44u2__p7_1,&_44u2__p7_2} + } +}; +static static_bookblock _resbook_44u_3={ + { + {0}, + {0,0,&_44u3__p1_0}, + {0,0,&_44u3__p2_0}, + {0,0,&_44u3__p3_0}, + {0,0,&_44u3__p4_0}, + {0,0,&_44u3__p5_0}, + {&_44u3__p6_0,&_44u3__p6_1}, + {&_44u3__p7_0,&_44u3__p7_1,&_44u3__p7_2} + } +}; +static static_bookblock _resbook_44u_4={ + { + {0}, + {0,0,&_44u4__p1_0}, + {0,0,&_44u4__p2_0}, + {0,0,&_44u4__p3_0}, + {0,0,&_44u4__p4_0}, + {0,0,&_44u4__p5_0}, + {&_44u4__p6_0,&_44u4__p6_1}, + {&_44u4__p7_0,&_44u4__p7_1,&_44u4__p7_2} + } +}; +static static_bookblock _resbook_44u_5={ + { + {0}, + {0,0,&_44u5__p1_0}, + {0,0,&_44u5__p2_0}, + {0,0,&_44u5__p3_0}, + {0,0,&_44u5__p4_0}, + {0,0,&_44u5__p5_0}, + {0,0,&_44u5__p6_0}, + {&_44u5__p7_0,&_44u5__p7_1}, + {&_44u5__p8_0,&_44u5__p8_1}, + {&_44u5__p9_0,&_44u5__p9_1,&_44u5__p9_2} + } +}; +static static_bookblock _resbook_44u_6={ + { + {0}, + {0,0,&_44u6__p1_0}, + {0,0,&_44u6__p2_0}, + {0,0,&_44u6__p3_0}, + {0,0,&_44u6__p4_0}, + {0,0,&_44u6__p5_0}, + {0,0,&_44u6__p6_0}, + {&_44u6__p7_0,&_44u6__p7_1}, + {&_44u6__p8_0,&_44u6__p8_1}, + {&_44u6__p9_0,&_44u6__p9_1,&_44u6__p9_2} + } +}; +static static_bookblock _resbook_44u_7={ + { + {0}, + {0,0,&_44u7__p1_0}, + {0,0,&_44u7__p2_0}, + {0,0,&_44u7__p3_0}, + {0,0,&_44u7__p4_0}, + {0,0,&_44u7__p5_0}, + {0,0,&_44u7__p6_0}, + {&_44u7__p7_0,&_44u7__p7_1}, + {&_44u7__p8_0,&_44u7__p8_1}, + {&_44u7__p9_0,&_44u7__p9_1,&_44u7__p9_2} + } +}; +static static_bookblock _resbook_44u_8={ + { + {0}, + {0,0,&_44u8_p1_0}, + {0,0,&_44u8_p2_0}, + {0,0,&_44u8_p3_0}, + {0,0,&_44u8_p4_0}, + {&_44u8_p5_0,&_44u8_p5_1}, + {&_44u8_p6_0,&_44u8_p6_1}, + {&_44u8_p7_0,&_44u8_p7_1}, + {&_44u8_p8_0,&_44u8_p8_1}, + {&_44u8_p9_0,&_44u8_p9_1,&_44u8_p9_2} + } +}; +static static_bookblock _resbook_44u_9={ + { + {0}, + {0,0,&_44u9_p1_0}, + {0,0,&_44u9_p2_0}, + {0,0,&_44u9_p3_0}, + {0,0,&_44u9_p4_0}, + {&_44u9_p5_0,&_44u9_p5_1}, + {&_44u9_p6_0,&_44u9_p6_1}, + {&_44u9_p7_0,&_44u9_p7_1}, + {&_44u9_p8_0,&_44u9_p8_1}, + {&_44u9_p9_0,&_44u9_p9_1,&_44u9_p9_2} + } +}; + +static vorbis_residue_template _res_44u_0[]={ + {1,0, &_residue_44_low_un, + &_huff_book__44u0__short,&_huff_book__44u0__short, + &_resbook_44u_0,&_resbook_44u_0}, + + {1,0, &_residue_44_low_un, + &_huff_book__44u0__long,&_huff_book__44u0__long, + &_resbook_44u_0,&_resbook_44u_0} +}; +static vorbis_residue_template _res_44u_1[]={ + {1,0, &_residue_44_low_un, + &_huff_book__44u1__short,&_huff_book__44u1__short, + &_resbook_44u_1,&_resbook_44u_1}, + + {1,0, &_residue_44_low_un, + &_huff_book__44u1__long,&_huff_book__44u1__long, + &_resbook_44u_1,&_resbook_44u_1} +}; +static vorbis_residue_template _res_44u_2[]={ + {1,0, &_residue_44_low_un, + &_huff_book__44u2__short,&_huff_book__44u2__short, + &_resbook_44u_2,&_resbook_44u_2}, + + {1,0, &_residue_44_low_un, + &_huff_book__44u2__long,&_huff_book__44u2__long, + &_resbook_44u_2,&_resbook_44u_2} +}; +static vorbis_residue_template _res_44u_3[]={ + {1,0, &_residue_44_low_un, + &_huff_book__44u3__short,&_huff_book__44u3__short, + &_resbook_44u_3,&_resbook_44u_3}, + + {1,0, &_residue_44_low_un, + &_huff_book__44u3__long,&_huff_book__44u3__long, + &_resbook_44u_3,&_resbook_44u_3} +}; +static vorbis_residue_template _res_44u_4[]={ + {1,0, &_residue_44_low_un, + &_huff_book__44u4__short,&_huff_book__44u4__short, + &_resbook_44u_4,&_resbook_44u_4}, + + {1,0, &_residue_44_low_un, + &_huff_book__44u4__long,&_huff_book__44u4__long, + &_resbook_44u_4,&_resbook_44u_4} +}; + +static vorbis_residue_template _res_44u_5[]={ + {1,0, &_residue_44_mid_un, + &_huff_book__44u5__short,&_huff_book__44u5__short, + &_resbook_44u_5,&_resbook_44u_5}, + + {1,0, &_residue_44_mid_un, + &_huff_book__44u5__long,&_huff_book__44u5__long, + &_resbook_44u_5,&_resbook_44u_5} +}; + +static vorbis_residue_template _res_44u_6[]={ + {1,0, &_residue_44_mid_un, + &_huff_book__44u6__short,&_huff_book__44u6__short, + &_resbook_44u_6,&_resbook_44u_6}, + + {1,0, &_residue_44_mid_un, + &_huff_book__44u6__long,&_huff_book__44u6__long, + &_resbook_44u_6,&_resbook_44u_6} +}; + +static vorbis_residue_template _res_44u_7[]={ + {1,0, &_residue_44_mid_un, + &_huff_book__44u7__short,&_huff_book__44u7__short, + &_resbook_44u_7,&_resbook_44u_7}, + + {1,0, &_residue_44_mid_un, + &_huff_book__44u7__long,&_huff_book__44u7__long, + &_resbook_44u_7,&_resbook_44u_7} +}; + +static vorbis_residue_template _res_44u_8[]={ + {1,0, &_residue_44_hi_un, + &_huff_book__44u8__short,&_huff_book__44u8__short, + &_resbook_44u_8,&_resbook_44u_8}, + + {1,0, &_residue_44_hi_un, + &_huff_book__44u8__long,&_huff_book__44u8__long, + &_resbook_44u_8,&_resbook_44u_8} +}; +static vorbis_residue_template _res_44u_9[]={ + {1,0, &_residue_44_hi_un, + &_huff_book__44u9__short,&_huff_book__44u9__short, + &_resbook_44u_9,&_resbook_44u_9}, + + {1,0, &_residue_44_hi_un, + &_huff_book__44u9__long,&_huff_book__44u9__long, + &_resbook_44u_9,&_resbook_44u_9} +}; + +static vorbis_mapping_template _mapres_template_44_uncoupled[]={ + { _map_nominal_u, _res_44u_0 }, /* 0 */ + { _map_nominal_u, _res_44u_1 }, /* 1 */ + { _map_nominal_u, _res_44u_2 }, /* 2 */ + { _map_nominal_u, _res_44u_3 }, /* 3 */ + { _map_nominal_u, _res_44u_4 }, /* 4 */ + { _map_nominal_u, _res_44u_5 }, /* 5 */ + { _map_nominal_u, _res_44u_6 }, /* 6 */ + { _map_nominal_u, _res_44u_7 }, /* 7 */ + { _map_nominal_u, _res_44u_8 }, /* 8 */ + { _map_nominal_u, _res_44u_9 }, /* 9 */ +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/residue_8.h b/src/sound/oggvorbis/vorbissrc/modes/residue_8.h new file mode 100644 index 0000000..ed141c1 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/residue_8.h @@ -0,0 +1,121 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel residue templates 8/11kHz + last mod: $Id: residue_8.h,v 1.2 2002/07/11 06:41:04 xiphmont Exp $ + + ********************************************************************/ + +#include "../vorbis/codec.h" +#include "backends.h" + +/***** residue backends *********************************************/ + +static static_bookblock _resbook_8s_0={ + { + {0},{0,0,&_8c0_s_p1_0},{0,0,&_8c0_s_p2_0},{0,0,&_8c0_s_p3_0}, + {0,0,&_8c0_s_p4_0},{0,0,&_8c0_s_p5_0},{0,0,&_8c0_s_p6_0}, + {&_8c0_s_p7_0,&_8c0_s_p7_1},{&_8c0_s_p8_0,&_8c0_s_p8_1}, + {&_8c0_s_p9_0,&_8c0_s_p9_1,&_8c0_s_p9_2} + } +}; +static static_bookblock _resbook_8s_1={ + { + {0},{0,0,&_8c1_s_p1_0},{0,0,&_8c1_s_p2_0},{0,0,&_8c1_s_p3_0}, + {0,0,&_8c1_s_p4_0},{0,0,&_8c1_s_p5_0},{0,0,&_8c1_s_p6_0}, + {&_8c1_s_p7_0,&_8c1_s_p7_1},{&_8c1_s_p8_0,&_8c1_s_p8_1}, + {&_8c1_s_p9_0,&_8c1_s_p9_1,&_8c1_s_p9_2} + } +}; + +static vorbis_residue_template _res_8s_0[]={ + {2,0, &_residue_44_mid, + &_huff_book__8c0_s_single,&_huff_book__8c0_s_single, + &_resbook_8s_0,&_resbook_8s_0}, +}; +static vorbis_residue_template _res_8s_1[]={ + {2,0, &_residue_44_mid, + &_huff_book__8c1_s_single,&_huff_book__8c1_s_single, + &_resbook_8s_1,&_resbook_8s_1}, +}; + +static vorbis_mapping_template _mapres_template_8_stereo[2]={ + { _map_nominal, _res_8s_0 }, /* 0 */ + { _map_nominal, _res_8s_1 }, /* 1 */ +}; + +static static_bookblock _resbook_8u_0={ + { + {0}, + {0,0,&_8u0__p1_0}, + {0,0,&_8u0__p2_0}, + {0,0,&_8u0__p3_0}, + {0,0,&_8u0__p4_0}, + {0,0,&_8u0__p5_0}, + {&_8u0__p6_0,&_8u0__p6_1}, + {&_8u0__p7_0,&_8u0__p7_1,&_8u0__p7_2} + } +}; +static static_bookblock _resbook_8u_1={ + { + {0}, + {0,0,&_8u1__p1_0}, + {0,0,&_8u1__p2_0}, + {0,0,&_8u1__p3_0}, + {0,0,&_8u1__p4_0}, + {0,0,&_8u1__p5_0}, + {0,0,&_8u1__p6_0}, + {&_8u1__p7_0,&_8u1__p7_1}, + {&_8u1__p8_0,&_8u1__p8_1}, + {&_8u1__p9_0,&_8u1__p9_1,&_8u1__p9_2} + } +}; + +static vorbis_residue_template _res_8u_0[]={ + {1,0, &_residue_44_low_un, + &_huff_book__8u0__single,&_huff_book__8u0__single, + &_resbook_8u_0,&_resbook_8u_0}, +}; +static vorbis_residue_template _res_8u_1[]={ + {1,0, &_residue_44_mid_un, + &_huff_book__8u1__single,&_huff_book__8u1__single, + &_resbook_8u_1,&_resbook_8u_1}, +}; + +static vorbis_mapping_template _mapres_template_8_uncoupled[2]={ + { _map_nominal_u, _res_8u_0 }, /* 0 */ + { _map_nominal_u, _res_8u_1 }, /* 1 */ +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_11.h b/src/sound/oggvorbis/vorbissrc/modes/setup_11.h new file mode 100644 index 0000000..2ff1e53 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_11.h @@ -0,0 +1,165 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: 11kHz settings + last mod: $Id: setup_11.h,v 1.2 2002/07/11 06:41:04 xiphmont Exp $ + + ********************************************************************/ + +#include "psych_11.h" + +static int blocksize_11[2]={ + 512,512 +}; + +static int _floor_mapping_11[2]={ + 1,1, +}; + +static double rate_mapping_11[3]={ + 8000.,13000.,44000., +}; + +static double rate_mapping_11_uncoupled[3]={ + 12000.,20000.,50000., +}; + +static double quality_mapping_11[3]={ + -.1,.0,1. +}; + +ve_setup_data_template ve_setup_11_stereo={ + 2, + rate_mapping_11, + quality_mapping_11, + 2, + 9000, + 15000, + + blocksize_11, + blocksize_11, + + _psy_tone_masteratt_11, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_11, + NULL, + _vp_tonemask_adj_11, + + _psy_noiseguards_8, + _psy_noisebias_11, + _psy_noisebias_11, + NULL, + NULL, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + NULL, + + {_noise_start_8,_noise_start_8}, + {_noise_part_8,_noise_part_8}, + _noise_thresh_11, + + _psy_ath_floater_8, + _psy_ath_abs_8, + + _psy_lowpass_11, + + _psy_global_44, + _global_mapping_8, + _psy_stereo_modes_8, + + _floor_books, + _floor, + _floor_mapping_11, + NULL, + + _mapres_template_8_stereo +}; + +ve_setup_data_template ve_setup_11_uncoupled={ + 2, + rate_mapping_11_uncoupled, + quality_mapping_11, + -1, + 9000, + 15000, + + blocksize_11, + blocksize_11, + + _psy_tone_masteratt_11, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_11, + NULL, + _vp_tonemask_adj_11, + + _psy_noiseguards_8, + _psy_noisebias_11, + _psy_noisebias_11, + NULL, + NULL, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + NULL, + + {_noise_start_8,_noise_start_8}, + {_noise_part_8,_noise_part_8}, + _noise_thresh_11, + + _psy_ath_floater_8, + _psy_ath_abs_8, + + _psy_lowpass_11, + + _psy_global_44, + _global_mapping_8, + _psy_stereo_modes_8, + + _floor_books, + _floor, + _floor_mapping_11, + NULL, + + _mapres_template_8_uncoupled +}; + diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_16.h b/src/sound/oggvorbis/vorbissrc/modes/setup_16.h new file mode 100644 index 0000000..409011c --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_16.h @@ -0,0 +1,173 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: 16kHz settings + last mod: $Id: setup_16.h,v 1.5 2002/10/11 11:14:42 xiphmont Exp $ + + ********************************************************************/ + +#include "psych_16.h" +#include "residue_16.h" + +static int blocksize_16_short[3]={ + 1024,512,512 +}; +static int blocksize_16_long[3]={ + 1024,1024,1024 +}; + +static int _floor_mapping_16_short[3]={ + 9,3,3 +}; +static int _floor_mapping_16[3]={ + 9,9,9 +}; + +static double rate_mapping_16[4]={ + 12000.,20000.,44000.,86000. +}; + +static double rate_mapping_16_uncoupled[4]={ + 16000.,28000.,64000.,100000. +}; + +static double _global_mapping_16[4]={ 1., 2., 3., 4. }; + +static double quality_mapping_16[4]={ -.1,.05,.5,1. }; + +static double _psy_compand_16_mapping[4]={ 0., .8, 1., 1.}; + +ve_setup_data_template ve_setup_16_stereo={ + 3, + rate_mapping_16, + quality_mapping_16, + 2, + 15000, + 19000, + + blocksize_16_short, + blocksize_16_long, + + _psy_tone_masteratt_16, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + + _psy_noiseguards_8, + _psy_noisebias_16_impulse, + _psy_noisebias_16_short, + _psy_noisebias_16_short, + _psy_noisebias_16, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_16_mapping, + _psy_compand_16_mapping, + + {_noise_start_16,_noise_start_16}, + { _noise_part_16, _noise_part_16}, + _noise_thresh_16, + + _psy_ath_floater_16, + _psy_ath_abs_16, + + _psy_lowpass_16, + + _psy_global_44, + _global_mapping_16, + _psy_stereo_modes_16, + + _floor_books, + _floor, + _floor_mapping_16_short, + _floor_mapping_16, + + _mapres_template_16_stereo +}; + +ve_setup_data_template ve_setup_16_uncoupled={ + 3, + rate_mapping_16_uncoupled, + quality_mapping_16, + -1, + 15000, + 19000, + + blocksize_16_short, + blocksize_16_long, + + _psy_tone_masteratt_16, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + + _psy_noiseguards_8, + _psy_noisebias_16_impulse, + _psy_noisebias_16_short, + _psy_noisebias_16_short, + _psy_noisebias_16, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_16_mapping, + _psy_compand_16_mapping, + + {_noise_start_16,_noise_start_16}, + { _noise_part_16, _noise_part_16}, + _noise_thresh_16, + + _psy_ath_floater_16, + _psy_ath_abs_16, + + _psy_lowpass_16, + + _psy_global_44, + _global_mapping_16, + _psy_stereo_modes_16, + + _floor_books, + _floor, + _floor_mapping_16_short, + _floor_mapping_16, + + _mapres_template_16_uncoupled +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_22.h b/src/sound/oggvorbis/vorbissrc/modes/setup_22.h new file mode 100644 index 0000000..9a14a65 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_22.h @@ -0,0 +1,152 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: 22kHz settings + last mod: $Id: setup_22.h,v 1.3 2002/07/11 10:02:29 xiphmont Exp $ + + ********************************************************************/ + +static double rate_mapping_22[4]={ + 15000.,20000.,44000.,86000. +}; + +static double rate_mapping_22_uncoupled[4]={ + 16000.,28000.,50000.,90000. +}; + +static double _psy_lowpass_22[4]={9.5,11.,30.,99.}; + +ve_setup_data_template ve_setup_22_stereo={ + 3, + rate_mapping_22, + quality_mapping_16, + 2, + 19000, + 26000, + + blocksize_16_short, + blocksize_16_long, + + _psy_tone_masteratt_16, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + + _psy_noiseguards_8, + _psy_noisebias_16_impulse, + _psy_noisebias_16_short, + _psy_noisebias_16_short, + _psy_noisebias_16, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + _psy_compand_8_mapping, + + {_noise_start_16,_noise_start_16}, + { _noise_part_16, _noise_part_16}, + _noise_thresh_16, + + _psy_ath_floater_16, + _psy_ath_abs_16, + + _psy_lowpass_22, + + _psy_global_44, + _global_mapping_16, + _psy_stereo_modes_16, + + _floor_books, + _floor, + _floor_mapping_16_short, + _floor_mapping_16, + + _mapres_template_16_stereo +}; + +ve_setup_data_template ve_setup_22_uncoupled={ + 3, + rate_mapping_22_uncoupled, + quality_mapping_16, + -1, + 19000, + 26000, + + blocksize_16_short, + blocksize_16_long, + + _psy_tone_masteratt_16, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + _vp_tonemask_adj_16, + + _psy_noiseguards_8, + _psy_noisebias_16_impulse, + _psy_noisebias_16_short, + _psy_noisebias_16_short, + _psy_noisebias_16, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + _psy_compand_8_mapping, + + {_noise_start_16,_noise_start_16}, + { _noise_part_16, _noise_part_16}, + _noise_thresh_16, + + _psy_ath_floater_16, + _psy_ath_abs_16, + + _psy_lowpass_22, + + _psy_global_44, + _global_mapping_16, + _psy_stereo_modes_16, + + _floor_books, + _floor, + _floor_mapping_16_short, + _floor_mapping_16, + + _mapres_template_16_uncoupled +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_32.h b/src/sound/oggvorbis/vorbissrc/modes/setup_32.h new file mode 100644 index 0000000..4c6e605 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_32.h @@ -0,0 +1,270 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel settings for 32kHz + last mod: $Id: setup_32.h,v 1.4 2002/07/13 06:12:49 xiphmont Exp $ + + ********************************************************************/ + +static double rate_mapping_32[11]={ + 28000.,35000.,45000.,56000.,60000., + 75000.,90000.,100000.,115000.,150000.,190000., +}; + +static double rate_mapping_32_un[11]={ + 42000.,52000.,64000.,72000.,78000., + 86000.,92000.,110000.,120000.,140000.,190000., +}; + +static double rate_mapping_32_low[2]={ + 20000.,28000. +}; + +static double rate_mapping_32_un_low[2]={ + 24000.,42000., +}; + +static double _psy_lowpass_32_low[2]={ + 13.,13., +}; +static double _psy_lowpass_32[11]={ + 13.,13.,14.,15.,99.,99.,99.,99.,99.,99.,99. +}; + +ve_setup_data_template ve_setup_32_stereo={ + 10, + rate_mapping_32, + quality_mapping_44, + 2, + 26000, + 40000, + + blocksize_short_44, + blocksize_long_44, + + _psy_tone_masteratt_44, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_impulse, + _psy_noisebias_padding, + _psy_noisebias_trans, + _psy_noisebias_long, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44,_noise_start_long_44}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_32, + + _psy_global_44, + _global_mapping_44, + _psy_stereo_modes_44, + + _floor_books, + _floor, + _floor_short_mapping_44, + _floor_long_mapping_44, + + _mapres_template_44_stereo +}; + +ve_setup_data_template ve_setup_32_uncoupled={ + 10, + rate_mapping_32_un, + quality_mapping_44, + -1, + 26000, + 40000, + + blocksize_short_44, + blocksize_long_44, + + _psy_tone_masteratt_44, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_impulse, + _psy_noisebias_padding, + _psy_noisebias_trans, + _psy_noisebias_long, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44,_noise_start_long_44}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44_2, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_32, + + _psy_global_44, + _global_mapping_44, + NULL, + + _floor_books, + _floor, + _floor_short_mapping_44, + _floor_long_mapping_44, + + _mapres_template_44_uncoupled +}; + +ve_setup_data_template ve_setup_32_stereo_low={ + 1, + rate_mapping_32_low, + quality_mapping_44_stereo_low, + 2, + 26000, + 40000, + + blocksize_short_44_low, + blocksize_long_44_low, + + _psy_tone_masteratt_44_low, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_long_low, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44_low,_noise_start_long_44_low}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_32_low, + + _psy_global_44, + _global_mapping_44, + _psy_stereo_modes_44_low, + + _floor_books, + _floor, + _floor_short_mapping_44_low, + _floor_long_mapping_44_low, + + _mapres_template_44_stereo +}; + + +ve_setup_data_template ve_setup_32_uncoupled_low={ + 1, + rate_mapping_32_un_low, + quality_mapping_44_stereo_low, + -1, + 26000, + 40000, + + blocksize_short_44_low, + blocksize_long_44_low, + + _psy_tone_masteratt_44_low, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_long_low, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44_low,_noise_start_long_44_low}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44_2, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_32_low, + + _psy_global_44, + _global_mapping_44, + NULL, + + _floor_books, + _floor, + _floor_short_mapping_44_low, + _floor_long_mapping_44_low, + + _mapres_template_44_uncoupled +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_44.h b/src/sound/oggvorbis/vorbissrc/modes/setup_44.h new file mode 100644 index 0000000..0f4d13e --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_44.h @@ -0,0 +1,200 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel settings for 44.1/48kHz + last mod: $Id: setup_44.h,v 1.9 2002/07/11 08:57:29 xiphmont Exp $ + + ********************************************************************/ + +#include "modes/floor_all.h" +#include "modes/residue_44.h" +#include "modes/psych_44.h" + +static double rate_mapping_44_stereo_low[2]={ + 22500.,32000. +}; + +static double rate_mapping_44_stereo[11]={ + 32000.,40000.,48000.,56000.,64000., + 80000.,96000.,112000.,128000.,160000.,250001. +}; +static double quality_mapping_44_stereo_low[2]={ + -.1,.0, +}; +static double quality_mapping_44[11]={ + .0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1.0 +}; + +static int blocksize_short_44_low[1]={ + 512 +}; +static int blocksize_long_44_low[1]={ + 4096 +}; + +static int blocksize_short_44[10]={ + 256,256,256,256,256,256,256,256,256,256 +}; +static int blocksize_long_44[10]={ + 2048,2048,2048,2048,2048,2048,2048,2048,2048,2048 +}; +static double _psy_compand_short_mapping[11]={ + 1., 1., 1.3, 1.6, 2., 2., 2., 2., 2., 2., 2. +}; +static double _psy_compand_long_mapping[11]={ + 4., 4., 4.3, 4.6, 5., 5., 5., 5., 5., 5., 5. +}; +static double _global_mapping_44[11]={ + 1., 1., 1.5, 2., 2., 2.5, 2.7, 3.0, 3.5, 4., 4. +}; +static double _global_mapping_44_low[2]={ + 0., 1., +}; +static int _floor_short_mapping_44_low[1]={ + 1, +}; +static int _floor_long_mapping_44_low[1]={ + 8 +}; +static int _floor_short_mapping_44[10]={ + 0,0,2,2,4,5,5,5,5,5 +}; +static int _floor_long_mapping_44[10]={ + 7,7,7,7,7,7,7,7,7,7 +}; + +ve_setup_data_template ve_setup_44_stereo={ + 10, + rate_mapping_44_stereo, + quality_mapping_44, + 2, + 40000, + 50000, + + blocksize_short_44, + blocksize_long_44, + + _psy_tone_masteratt_44, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_impulse, + _psy_noisebias_padding, + _psy_noisebias_trans, + _psy_noisebias_long, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44,_noise_start_long_44}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44, + + _psy_global_44, + _global_mapping_44, + _psy_stereo_modes_44, + + _floor_books, + _floor, + _floor_short_mapping_44, + _floor_long_mapping_44, + + _mapres_template_44_stereo +}; + +ve_setup_data_template ve_setup_44_stereo_low={ + 1, + rate_mapping_44_stereo_low, + quality_mapping_44_stereo_low, + 2, + 40000, + 50000, + + blocksize_short_44_low, + blocksize_long_44_low, + + _psy_tone_masteratt_44_low, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_long_low, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44_low,_noise_start_long_44_low}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44_low, + + _psy_global_44, + _global_mapping_44_low, + _psy_stereo_modes_44_low, + + _floor_books, + _floor, + _floor_short_mapping_44_low, + _floor_long_mapping_44_low, + + _mapres_template_44_stereo +}; + diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_44u.h b/src/sound/oggvorbis/vorbissrc/modes/setup_44u.h new file mode 100644 index 0000000..1be5b28 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_44u.h @@ -0,0 +1,153 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel settings for 44.1/48kHz uncoupled modes + last mod: $Id: setup_44u.h,v 1.3 2002/07/11 06:41:05 xiphmont Exp $ + + ********************************************************************/ + +#include "modes/residue_44u.h" + +static double rate_mapping_44_un[11]={ + 48000.,60000.,70000.,80000.,86000., + 96000.,110000.,120000.,140000.,160000.,240001. +}; + +static double rate_mapping_44_un_low[2]={ + 32000.,48000. +}; + +ve_setup_data_template ve_setup_44_uncoupled={ + 10, + rate_mapping_44_un, + quality_mapping_44, + -1, + 40000, + 50000, + + blocksize_short_44, + blocksize_long_44, + + _psy_tone_masteratt_44, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_impulse, + _psy_noisebias_padding, + _psy_noisebias_trans, + _psy_noisebias_long, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44,_noise_start_long_44}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44_2, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44, + + _psy_global_44, + _global_mapping_44, + NULL, + + _floor_books, + _floor, + _floor_short_mapping_44, + _floor_long_mapping_44, + + _mapres_template_44_uncoupled +}; + +ve_setup_data_template ve_setup_44_uncoupled_low={ + 1, + rate_mapping_44_un_low, + quality_mapping_44_stereo_low, + -1, + 40000, + 50000, + + blocksize_short_44_low, + blocksize_long_44_low, + + _psy_tone_masteratt_44_low, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_long_low, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44_low,_noise_start_long_44_low}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44_2, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44_low, + + _psy_global_44, + _global_mapping_44_low, + NULL, + + _floor_books, + _floor, + _floor_short_mapping_44_low, + _floor_long_mapping_44_low, + + _mapres_template_44_uncoupled +}; diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_8.h b/src/sound/oggvorbis/vorbissrc/modes/setup_8.h new file mode 100644 index 0000000..cfcc79b --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_8.h @@ -0,0 +1,170 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: 8kHz settings + last mod: $Id: setup_8.h,v 1.2 2002/07/11 06:41:05 xiphmont Exp $ + + ********************************************************************/ + +#include "psych_8.h" +#include "residue_8.h" + +static int blocksize_8[2]={ + 512,512 +}; + +static int _floor_mapping_8[2]={ + 1,1, +}; + +static double rate_mapping_8[3]={ + 6000.,9000.,32000., +}; + +static double rate_mapping_8_uncoupled[3]={ + 8000.,14000.,42000., +}; + +static double quality_mapping_8[3]={ + -.1,.0,1. +}; + +static double _psy_compand_8_mapping[3]={ 0., 1., 1.}; + +static double _global_mapping_8[3]={ 1., 2., 3. }; + +ve_setup_data_template ve_setup_8_stereo={ + 2, + rate_mapping_8, + quality_mapping_8, + 2, + 8000, + 9000, + + blocksize_8, + blocksize_8, + + _psy_tone_masteratt_8, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_8, + NULL, + _vp_tonemask_adj_8, + + _psy_noiseguards_8, + _psy_noisebias_8, + _psy_noisebias_8, + NULL, + NULL, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + NULL, + + {_noise_start_8,_noise_start_8}, + {_noise_part_8,_noise_part_8}, + _noise_thresh_44_2, + + _psy_ath_floater_8, + _psy_ath_abs_8, + + _psy_lowpass_8, + + _psy_global_44, + _global_mapping_8, + _psy_stereo_modes_8, + + _floor_books, + _floor, + _floor_mapping_8, + NULL, + + _mapres_template_8_stereo +}; + +ve_setup_data_template ve_setup_8_uncoupled={ + 2, + rate_mapping_8_uncoupled, + quality_mapping_8, + -1, + 8000, + 9000, + + blocksize_8, + blocksize_8, + + _psy_tone_masteratt_8, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_8, + NULL, + _vp_tonemask_adj_8, + + _psy_noiseguards_8, + _psy_noisebias_8, + _psy_noisebias_8, + NULL, + NULL, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + NULL, + + {_noise_start_8,_noise_start_8}, + {_noise_part_8,_noise_part_8}, + _noise_thresh_44_2, + + _psy_ath_floater_8, + _psy_ath_abs_8, + + _psy_lowpass_8, + + _psy_global_44, + _global_mapping_8, + _psy_stereo_modes_8, + + _floor_books, + _floor, + _floor_mapping_8, + NULL, + + _mapres_template_8_uncoupled +}; + diff --git a/src/sound/oggvorbis/vorbissrc/modes/setup_X.h b/src/sound/oggvorbis/vorbissrc/modes/setup_X.h new file mode 100644 index 0000000..5a3fd7e --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/modes/setup_X.h @@ -0,0 +1,353 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: catch-all toplevel settings for q modes only + last mod: $Id: setup_X.h,v 1.3 2002/07/11 06:41:05 xiphmont Exp $ + + ********************************************************************/ + +static double rate_mapping_X[11]={ + -1.,-1.,-1.,-1.,-1., + -1.,-1.,-1.,-1.,-1.,-1. +}; + +ve_setup_data_template ve_setup_X_stereo={ + 10, + rate_mapping_X, + quality_mapping_44, + 2, + 50000, + 200000, + + blocksize_short_44, + blocksize_long_44, + + _psy_tone_masteratt_44, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_impulse, + _psy_noisebias_padding, + _psy_noisebias_trans, + _psy_noisebias_long, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44,_noise_start_long_44}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44, + + _psy_global_44, + _global_mapping_44, + _psy_stereo_modes_44, + + _floor_books, + _floor, + _floor_short_mapping_44, + _floor_long_mapping_44, + + _mapres_template_44_stereo +}; + +ve_setup_data_template ve_setup_X_uncoupled={ + 10, + rate_mapping_X, + quality_mapping_44, + -1, + 50000, + 200000, + + blocksize_short_44, + blocksize_long_44, + + _psy_tone_masteratt_44, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_impulse, + _psy_noisebias_padding, + _psy_noisebias_trans, + _psy_noisebias_long, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44,_noise_start_long_44}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44_2, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44, + + _psy_global_44, + _global_mapping_44, + NULL, + + _floor_books, + _floor, + _floor_short_mapping_44, + _floor_long_mapping_44, + + _mapres_template_44_uncoupled +}; + +ve_setup_data_template ve_setup_X_stereo_low={ + 1, + rate_mapping_X, + quality_mapping_44_stereo_low, + 2, + 50000, + 200000, + + blocksize_short_44_low, + blocksize_long_44_low, + + _psy_tone_masteratt_44_low, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_long_low, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44_low,_noise_start_long_44_low}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44_low, + + _psy_global_44, + _global_mapping_44, + _psy_stereo_modes_44_low, + + _floor_books, + _floor, + _floor_short_mapping_44_low, + _floor_long_mapping_44_low, + + _mapres_template_44_stereo +}; + + +ve_setup_data_template ve_setup_X_uncoupled_low={ + 1, + rate_mapping_X, + quality_mapping_44_stereo_low, + -1, + 50000, + 200000, + + blocksize_short_44_low, + blocksize_long_44_low, + + _psy_tone_masteratt_44_low, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_otherblock, + _vp_tonemask_adj_longblock, + _vp_tonemask_adj_otherblock, + + _psy_noiseguards_44, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_trans_low, + _psy_noisebias_long_low, + _psy_noise_suppress, + + _psy_compand_44, + _psy_compand_short_mapping, + _psy_compand_long_mapping, + + {_noise_start_short_44_low,_noise_start_long_44_low}, + {_noise_part_short_44,_noise_part_long_44}, + _noise_thresh_44_2, + + _psy_ath_floater, + _psy_ath_abs, + + _psy_lowpass_44_low, + + _psy_global_44, + _global_mapping_44, + NULL, + + _floor_books, + _floor, + _floor_short_mapping_44_low, + _floor_long_mapping_44_low, + + _mapres_template_44_uncoupled +}; + +ve_setup_data_template ve_setup_XX_stereo={ + 2, + rate_mapping_X, + quality_mapping_8, + 2, + 0, + 8000, + + blocksize_8, + blocksize_8, + + _psy_tone_masteratt_8, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_8, + NULL, + _vp_tonemask_adj_8, + + _psy_noiseguards_8, + _psy_noisebias_8, + _psy_noisebias_8, + NULL, + NULL, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + NULL, + + {_noise_start_8,_noise_start_8}, + {_noise_part_8,_noise_part_8}, + _noise_thresh_44_2, + + _psy_ath_floater_8, + _psy_ath_abs_8, + + _psy_lowpass_8, + + _psy_global_44, + _global_mapping_8, + _psy_stereo_modes_8, + + _floor_books, + _floor, + _floor_mapping_8, + NULL, + + _mapres_template_8_stereo +}; + +ve_setup_data_template ve_setup_XX_uncoupled={ + 2, + rate_mapping_X, + quality_mapping_8, + -1, + 0, + 8000, + + blocksize_8, + blocksize_8, + + _psy_tone_masteratt_8, + _psy_tone_0dB, + _psy_tone_suppress, + + _vp_tonemask_adj_8, + NULL, + _vp_tonemask_adj_8, + + _psy_noiseguards_8, + _psy_noisebias_8, + _psy_noisebias_8, + NULL, + NULL, + _psy_noise_suppress, + + _psy_compand_8, + _psy_compand_8_mapping, + NULL, + + {_noise_start_8,_noise_start_8}, + {_noise_part_8,_noise_part_8}, + _noise_thresh_44_2, + + _psy_ath_floater_8, + _psy_ath_abs_8, + + _psy_lowpass_8, + + _psy_global_44, + _global_mapping_8, + _psy_stereo_modes_8, + + _floor_books, + _floor, + _floor_mapping_8, + NULL, + + _mapres_template_8_uncoupled +}; + diff --git a/src/sound/oggvorbis/vorbissrc/os.h b/src/sound/oggvorbis/vorbissrc/os.h new file mode 100644 index 0000000..531b1d5 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/os.h @@ -0,0 +1,171 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef _OS_H +#define _OS_H +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: #ifdef jail to whip a few platforms into the UNIX ideal. + last mod: $Id: os.h,v 1.33 2003/09/02 05:11:53 xiphmont Exp $ + + ********************************************************************/ + +#include +#include "../ogg/os_types.h" + +#include "misc.h" + +#ifndef _V_IFDEFJAIL_H_ +# define _V_IFDEFJAIL_H_ + +# ifdef __GNUC__ +# define STIN static __inline__ +# elif _WIN32 +# define STIN static __inline +# else +# define STIN static +# endif + +#ifdef DJGPP +# define rint(x) (floor((x)+0.5f)) +#endif + +#ifndef M_PI +# define M_PI (3.1415926536f) +#endif + +#ifdef _WIN32 +# include +# define rint(x) (floor((x)+0.5f)) +# define NO_FLOAT_MATH_LIB +# define FAST_HYPOT(a, b) sqrt((a)*(a) + (b)*(b)) +#endif + +#ifndef FAST_HYPOT +# define FAST_HYPOT hypot +#endif + +#endif + +#ifdef HAVE_ALLOCA_H +# include +#endif + +#ifdef USE_MEMORY_H +# include +#endif + +#ifndef min +# define min(x,y) ((x)>(y)?(y):(x)) +#endif + +#ifndef max +# define max(x,y) ((x)<(y)?(y):(x)) +#endif + +#if defined(__i386__) && defined(__GNUC__) && !defined(__BEOS__) +# define VORBIS_FPU_CONTROL +/* both GCC and MSVC are kinda stupid about rounding/casting to int. + Because of encapsulation constraints (GCC can't see inside the asm + block and so we end up doing stupid things like a store/load that + is collectively a noop), we do it this way */ + +/* we must set up the fpu before this works!! */ + +typedef ogg_int16_t vorbis_fpu_control; + +static inline void vorbis_fpu_setround(vorbis_fpu_control *fpu){ + ogg_int16_t ret; + ogg_int16_t temp; + __asm__ __volatile__("fnstcw %0\n\t" + "movw %0,%%dx\n\t" + "orw $62463,%%dx\n\t" + "movw %%dx,%1\n\t" + "fldcw %1\n\t":"=m"(ret):"m"(temp): "dx"); + *fpu=ret; +} + +static inline void vorbis_fpu_restore(vorbis_fpu_control fpu){ + __asm__ __volatile__("fldcw %0":: "m"(fpu)); +} + +/* assumes the FPU is in round mode! */ +static inline int vorbis_ftoi(double f){ /* yes, double! Otherwise, + we get extra fst/fld to + truncate precision */ + int i; + __asm__("fistl %0": "=m"(i) : "t"(f)); + return(i); +} +#endif + + +#if defined(_WIN32) && !defined(__GNUC__) && !defined(__BORLANDC__) +# define VORBIS_FPU_CONTROL + +typedef ogg_int16_t vorbis_fpu_control; + +static __inline int vorbis_ftoi(double f){ + int i; + __asm{ + fld f + fistp i + } + return i; +} + +static __inline void vorbis_fpu_setround(vorbis_fpu_control *fpu){ +} + +static __inline void vorbis_fpu_restore(vorbis_fpu_control fpu){ +} + +#endif + + +#ifndef VORBIS_FPU_CONTROL + +typedef int vorbis_fpu_control; + +static int vorbis_ftoi(double f){ + return (int)(f+.5); +} + +/* We don't have special code for this compiler/arch, so do it the slow way */ +# define vorbis_fpu_setround(vorbis_fpu_control) {} +# define vorbis_fpu_restore(vorbis_fpu_control) {} + +#endif + +#endif /* _OS_H */ diff --git a/src/sound/oggvorbis/vorbissrc/psy.c b/src/sound/oggvorbis/vorbissrc/psy.c new file mode 100644 index 0000000..b7fad60 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/psy.c @@ -0,0 +1,1172 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: psychoacoustics not including preecho + last mod: $Id: psy.c,v 1.81 2002/10/21 07:00:11 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include "../vorbis/codec.h" +#include "codec_internal.h" + +#include "masking.h" +#include "psy.h" +#include "os.h" +#include "lpc.h" +#include "smallft.h" +#include "scales.h" +#include "misc.h" + +#define NEGINF -9999.f +static double stereo_threshholds[]={0.0, .5, 1.0, 1.5, 2.5, 4.5, 8.5, 16.5, 9e10}; + +vorbis_look_psy_global *_vp_global_look(vorbis_info *vi){ + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy_global *gi=&ci->psy_g_param; + vorbis_look_psy_global *look=_ogg_calloc(1,sizeof(*look)); + + look->channels=vi->channels; + + look->ampmax=-9999.; + look->gi=gi; + return(look); +} + +void _vp_global_free(vorbis_look_psy_global *look){ + if(look){ + memset(look,0,sizeof(*look)); + _ogg_free(look); + } +} + +void _vi_gpsy_free(vorbis_info_psy_global *i){ + if(i){ + memset(i,0,sizeof(*i)); + _ogg_free(i); + } +} + +void _vi_psy_free(vorbis_info_psy *i){ + if(i){ + memset(i,0,sizeof(*i)); + _ogg_free(i); + } +} + +static void min_curve(float *c, + float *c2){ + int i; + for(i=0;ic[i])c[i]=c2[i]; +} + +static void attenuate_curve(float *c,float att){ + int i; + for(i=0;iATH[j+k+ath_offset])min=ATH[j+k+ath_offset]; + }else{ + if(min>ATH[MAX_ATH-1])min=ATH[MAX_ATH-1]; + } + ath[j]=min; + } + + /* copy curves into working space, replicate the 50dB curve to 30 + and 40, replicate the 100dB curve to 110 */ + for(j=0;j<6;j++) + memcpy(workc[i][j+2],tonemasks[i][j],EHMER_MAX*sizeof(*tonemasks[i][j])); + memcpy(workc[i][0],tonemasks[i][0],EHMER_MAX*sizeof(*tonemasks[i][0])); + memcpy(workc[i][1],tonemasks[i][0],EHMER_MAX*sizeof(*tonemasks[i][0])); + + /* apply centered curve boost/decay */ + for(j=0;j0)adj=0.; + if(adj>0. && center_boost<0)adj=0.; + workc[i][j][k]+=adj; + } + } + + /* normalize curves so the driving amplitude is 0dB */ + /* make temp curves with the ATH overlayed */ + for(j=0;j an eighth of an octave and that the eighth + octave values may also be composited. */ + + /* which octave curves will we be compositing? */ + bin=floor(fromOC(i*.5)/binHz); + lo_curve= ceil(toOC(bin*binHz+1)*2); + hi_curve= floor(toOC((bin+1)*binHz)*2); + if(lo_curve>i)lo_curve=i; + if(lo_curve<0)lo_curve=0; + if(hi_curve>=P_BANDS)hi_curve=P_BANDS-1; + + for(m=0;mn)lo_bin=n; + if(lo_binn)hi_bin=n; + + for(;lworkc[k][m][j]) + brute_buffer[l]=workc[k][m][j]; + } + + for(;lworkc[k][m][EHMER_MAX-1]) + brute_buffer[l]=workc[k][m][EHMER_MAX-1]; + + } + + /* be equally paranoid about being valid up to next half ocatve */ + if(i+1n)lo_bin=n; + if(lo_binn)hi_bin=n; + + for(;lworkc[k][m][j]) + brute_buffer[l]=workc[k][m][j]; + } + + for(;lworkc[k][m][EHMER_MAX-1]) + brute_buffer[l]=workc[k][m][EHMER_MAX-1]; + + } + + + for(j=0;j=n){ + ret[i][m][j+2]=-999.; + }else{ + ret[i][m][j+2]=brute_buffer[bin]; + } + } + } + + /* add fenceposts */ + for(j=0;j-200.f)break; + ret[i][m][0]=j; + + for(j=EHMER_MAX-1;j>EHMER_OFFSET+1;j--) + if(ret[i][m][j+2]>-200.f) + break; + ret[i][m][1]=j; + + } + } + + return(ret); +} + +void _vp_psy_init(vorbis_look_psy *p,vorbis_info_psy *vi, + vorbis_info_psy_global *gi,int n,long rate){ + long i,j,lo=-99,hi=1; + long maxoc; + memset(p,0,sizeof(*p)); + + p->eighth_octave_lines=gi->eighth_octave_lines; + p->shiftoc=rint(log(gi->eighth_octave_lines*8.f)/log(2.f))-1; + + p->firstoc=toOC(.25f*rate*.5/n)*(1<<(p->shiftoc+1))-gi->eighth_octave_lines; + maxoc=toOC((n+.25f)*rate*.5/n)*(1<<(p->shiftoc+1))+.5f; + p->total_octave_lines=maxoc-p->firstoc+1; + p->ath=_ogg_malloc(n*sizeof(*p->ath)); + + p->octave=_ogg_malloc(n*sizeof(*p->octave)); + p->bark=_ogg_malloc(n*sizeof(*p->bark)); + p->vi=vi; + p->n=n; + p->rate=rate; + + /* set up the lookups for a given blocksize and sample rate */ + + for(i=0,j=0;iath[j]=base+100.; + base+=delta; + } + } + } + + for(i=0;inoisewindowlominnoisewindowlo);lo++); + + for(;hi<=n && (hinoisewindowhimin || + toBARK(rate/(2*n)*hi)<(bark+vi->noisewindowhi));hi++); + + p->bark[i]=((lo-1)<<16)+(hi-1); + + } + + for(i=0;ioctave[i]=toOC((i+.25f)*.5*rate/n)*(1<<(p->shiftoc+1))+.5f; + + p->tonecurves=setup_tone_curves(vi->toneatt,rate*.5/n,n, + vi->tone_centerboost,vi->tone_decay); + + /* set up rolling noise median */ + p->noiseoffset=_ogg_malloc(P_NOISECURVES*sizeof(*p->noiseoffset)); + for(i=0;inoiseoffset[i]=_ogg_malloc(n*sizeof(**p->noiseoffset)); + + for(i=0;i=P_BANDS-1)halfoc=P_BANDS-1; + inthalfoc=(int)halfoc; + del=halfoc-inthalfoc; + + for(j=0;jnoiseoffset[j][i]= + p->vi->noiseoff[j][inthalfoc]*(1.-del) + + p->vi->noiseoff[j][inthalfoc+1]*del; + + } +#if 0 + { + static int ls=0; + _analysis_output_always("noiseoff0",ls,p->noiseoffset[0],n,1,0,0); + _analysis_output_always("noiseoff1",ls,p->noiseoffset[1],n,1,0,0); + _analysis_output_always("noiseoff2",ls++,p->noiseoffset[2],n,1,0,0); + } +#endif +} + +void _vp_psy_clear(vorbis_look_psy *p){ + int i,j; + if(p){ + if(p->ath)_ogg_free(p->ath); + if(p->octave)_ogg_free(p->octave); + if(p->bark)_ogg_free(p->bark); + if(p->tonecurves){ + for(i=0;itonecurves[i][j]); + } + _ogg_free(p->tonecurves[i]); + } + _ogg_free(p->tonecurves); + } + if(p->noiseoffset){ + for(i=0;inoiseoffset[i]); + } + _ogg_free(p->noiseoffset); + } + memset(p,0,sizeof(*p)); + } +} + +/* octave/(8*eighth_octave_lines) x scale and dB y scale */ +static void seed_curve(float *seed, + const float **curves, + float amp, + int oc, int n, + int linesper,float dBoffset){ + int i,post1; + int seedptr; + const float *posts,*curve; + + int choice=(int)((amp+dBoffset-P_LEVEL_0)*.1f); + choice=max(choice,0); + choice=min(choice,P_LEVELS-1); + posts=curves[choice]; + curve=posts+2; + post1=(int)posts[1]; + seedptr=oc+(posts[0]-EHMER_OFFSET)*linesper-(linesper>>1); + + for(i=posts[0];i0){ + float lin=amp+curve[i]; + if(seed[seedptr]=n)break; + } +} + +static void seed_loop(vorbis_look_psy *p, + const float ***curves, + const float *f, + const float *flr, + float *seed, + float specmax){ + vorbis_info_psy *vi=p->vi; + long n=p->n,i; + float dBoffset=vi->max_curve_dB-specmax; + + /* prime the working vector with peak values */ + + for(i=0;ioctave[i]; + while(i+1octave[i+1]==oc){ + i++; + if(f[i]>max)max=f[i]; + } + + if(max+6.f>flr[i]){ + oc=oc>>p->shiftoc; + + if(oc>=P_BANDS)oc=P_BANDS-1; + if(oc<0)oc=0; + + seed_curve(seed, + curves[oc], + max, + p->octave[i]-p->firstoc, + p->total_octave_lines, + p->eighth_octave_lines, + dBoffset); + } + } +} + +static void seed_chase(float *seeds, int linesper, long n){ + long *posstack=alloca(n*sizeof(*posstack)); + float *ampstack=alloca(n*sizeof(*ampstack)); + long stack=0; + long pos=0; + long i; + + for(i=0;i1 && ampstack[stack-1]<=ampstack[stack-2] && + iampstack[i]){ + endpos=posstack[i+1]; + }else{ + endpos=posstack[i]+linesper+1; /* +1 is important, else bin 0 is + discarded in short frames */ + } + if(endpos>n)endpos=n; + for(;pos +static void max_seeds(vorbis_look_psy *p, + float *seed, + float *flr){ + long n=p->total_octave_lines; + int linesper=p->eighth_octave_lines; + long linpos=0; + long pos; + + seed_chase(seed,linesper,n); /* for masking */ + + pos=p->octave[0]-p->firstoc-(linesper>>1); + + while(linpos+1n){ + float minV=seed[pos]; + long end=((p->octave[linpos]+p->octave[linpos+1])>>1)-p->firstoc; + if(minV>p->vi->tone_abs_limit)minV=p->vi->tone_abs_limit; + while(pos+1<=end){ + pos++; + if((seed[pos]>NEGINF && seed[pos]firstoc; + for(;linposn && p->octave[linpos]<=end;linpos++) + if(flr[linpos]total_octave_lines-1]; + for(;linposn;linpos++) + if(flr[linpos]> 16; + if( lo>=0 ) break; + hi = b[i] & 0xffff; + + tN = N[hi] + N[-lo]; + tX = X[hi] - X[-lo]; + tXX = XX[hi] + XX[-lo]; + tY = Y[hi] + Y[-lo]; + tXY = XY[hi] - XY[-lo]; + + A = tY * tXX - tX * tXY; + B = tN * tXY - tX * tY; + D = tN * tXX - tX * tX; + R = (A + x * B) / D; + if (R < 0.f) + R = 0.f; + + noise[i] = R - offset; + } + + for ( ;; i++, x += 1.f) { + + lo = b[i] >> 16; + hi = b[i] & 0xffff; + if(hi>=n)break; + + tN = N[hi] - N[lo]; + tX = X[hi] - X[lo]; + tXX = XX[hi] - XX[lo]; + tY = Y[hi] - Y[lo]; + tXY = XY[hi] - XY[lo]; + + A = tY * tXX - tX * tXY; + B = tN * tXY - tX * tY; + D = tN * tXX - tX * tX; + R = (A + x * B) / D; + if (R < 0.f) R = 0.f; + + noise[i] = R - offset; + } + for ( ; i < n; i++, x += 1.f) { + + R = (A + x * B) / D; + if (R < 0.f) R = 0.f; + + noise[i] = R - offset; + } + + if (fixed <= 0) return; + + for (i = 0, x = 0.f;; i++, x += 1.f) { + hi = i + fixed / 2; + lo = hi - fixed; + if(lo>=0)break; + + tN = N[hi] + N[-lo]; + tX = X[hi] - X[-lo]; + tXX = XX[hi] + XX[-lo]; + tY = Y[hi] + Y[-lo]; + tXY = XY[hi] - XY[-lo]; + + + A = tY * tXX - tX * tXY; + B = tN * tXY - tX * tY; + D = tN * tXX - tX * tX; + R = (A + x * B) / D; + + if (R - offset < noise[i]) noise[i] = R - offset; + } + for ( ;; i++, x += 1.f) { + + hi = i + fixed / 2; + lo = hi - fixed; + if(hi>=n)break; + + tN = N[hi] - N[lo]; + tX = X[hi] - X[lo]; + tXX = XX[hi] - XX[lo]; + tY = Y[hi] - Y[lo]; + tXY = XY[hi] - XY[lo]; + + A = tY * tXX - tX * tXY; + B = tN * tXY - tX * tY; + D = tN * tXX - tX * tX; + R = (A + x * B) / D; + + if (R - offset < noise[i]) noise[i] = R - offset; + } + for ( ; i < n; i++, x += 1.f) { + R = (A + x * B) / D; + if (R - offset < noise[i]) noise[i] = R - offset; + } +} + +static float FLOOR1_fromdB_INV_LOOKUP[256]={ + 0.F, 8.81683e+06F, 8.27882e+06F, 7.77365e+06F, + 7.29930e+06F, 6.85389e+06F, 6.43567e+06F, 6.04296e+06F, + 5.67422e+06F, 5.32798e+06F, 5.00286e+06F, 4.69759e+06F, + 4.41094e+06F, 4.14178e+06F, 3.88905e+06F, 3.65174e+06F, + 3.42891e+06F, 3.21968e+06F, 3.02321e+06F, 2.83873e+06F, + 2.66551e+06F, 2.50286e+06F, 2.35014e+06F, 2.20673e+06F, + 2.07208e+06F, 1.94564e+06F, 1.82692e+06F, 1.71544e+06F, + 1.61076e+06F, 1.51247e+06F, 1.42018e+06F, 1.33352e+06F, + 1.25215e+06F, 1.17574e+06F, 1.10400e+06F, 1.03663e+06F, + 973377.F, 913981.F, 858210.F, 805842.F, + 756669.F, 710497.F, 667142.F, 626433.F, + 588208.F, 552316.F, 518613.F, 486967.F, + 457252.F, 429351.F, 403152.F, 378551.F, + 355452.F, 333762.F, 313396.F, 294273.F, + 276316.F, 259455.F, 243623.F, 228757.F, + 214798.F, 201691.F, 189384.F, 177828.F, + 166977.F, 156788.F, 147221.F, 138237.F, + 129802.F, 121881.F, 114444.F, 107461.F, + 100903.F, 94746.3F, 88964.9F, 83536.2F, + 78438.8F, 73652.5F, 69158.2F, 64938.1F, + 60975.6F, 57254.9F, 53761.2F, 50480.6F, + 47400.3F, 44507.9F, 41792.0F, 39241.9F, + 36847.3F, 34598.9F, 32487.7F, 30505.3F, + 28643.8F, 26896.0F, 25254.8F, 23713.7F, + 22266.7F, 20908.0F, 19632.2F, 18434.2F, + 17309.4F, 16253.1F, 15261.4F, 14330.1F, + 13455.7F, 12634.6F, 11863.7F, 11139.7F, + 10460.0F, 9821.72F, 9222.39F, 8659.64F, + 8131.23F, 7635.06F, 7169.17F, 6731.70F, + 6320.93F, 5935.23F, 5573.06F, 5232.99F, + 4913.67F, 4613.84F, 4332.30F, 4067.94F, + 3819.72F, 3586.64F, 3367.78F, 3162.28F, + 2969.31F, 2788.13F, 2617.99F, 2458.24F, + 2308.24F, 2167.39F, 2035.14F, 1910.95F, + 1794.35F, 1684.85F, 1582.04F, 1485.51F, + 1394.86F, 1309.75F, 1229.83F, 1154.78F, + 1084.32F, 1018.15F, 956.024F, 897.687F, + 842.910F, 791.475F, 743.179F, 697.830F, + 655.249F, 615.265F, 577.722F, 542.469F, + 509.367F, 478.286F, 449.101F, 421.696F, + 395.964F, 371.803F, 349.115F, 327.812F, + 307.809F, 289.026F, 271.390F, 254.830F, + 239.280F, 224.679F, 210.969F, 198.096F, + 186.008F, 174.658F, 164.000F, 153.993F, + 144.596F, 135.773F, 127.488F, 119.708F, + 112.404F, 105.545F, 99.1046F, 93.0572F, + 87.3788F, 82.0469F, 77.0404F, 72.3394F, + 67.9252F, 63.7804F, 59.8885F, 56.2341F, + 52.8027F, 49.5807F, 46.5553F, 43.7144F, + 41.0470F, 38.5423F, 36.1904F, 33.9821F, + 31.9085F, 29.9614F, 28.1332F, 26.4165F, + 24.8045F, 23.2910F, 21.8697F, 20.5352F, + 19.2822F, 18.1056F, 17.0008F, 15.9634F, + 14.9893F, 14.0746F, 13.2158F, 12.4094F, + 11.6522F, 10.9411F, 10.2735F, 9.64662F, + 9.05798F, 8.50526F, 7.98626F, 7.49894F, + 7.04135F, 6.61169F, 6.20824F, 5.82941F, + 5.47370F, 5.13970F, 4.82607F, 4.53158F, + 4.25507F, 3.99542F, 3.75162F, 3.52269F, + 3.30774F, 3.10590F, 2.91638F, 2.73842F, + 2.57132F, 2.41442F, 2.26709F, 2.12875F, + 1.99885F, 1.87688F, 1.76236F, 1.65482F, + 1.55384F, 1.45902F, 1.36999F, 1.28640F, + 1.20790F, 1.13419F, 1.06499F, 1.F +}; + +void _vp_remove_floor(vorbis_look_psy *p, + float *mdct, + int *codedflr, + float *residue, + int sliding_lowpass){ + + int i,n=p->n; + + if(sliding_lowpass>n)sliding_lowpass=n; + + for(i=0;in; + float *work=alloca(n*sizeof(*work)); + + bark_noise_hybridmp(n,p->bark,logmdct,logmask, + 140.,-1); + + for(i=0;ibark,work,logmask,0., + p->vi->noisewindowfixed); + + for(i=0;i=NOISE_COMPAND_LEVELS)dB=NOISE_COMPAND_LEVELS-1; + if(dB<0)dB=0; + logmask[i]= work[i]+p->vi->noisecompand[dB]; + } + +} + +void _vp_tonemask(vorbis_look_psy *p, + float *logfft, + float *logmask, + float global_specmax, + float local_specmax){ + + int i,n=p->n; + + float *seed=alloca(sizeof(*seed)*p->total_octave_lines); + float att=local_specmax+p->vi->ath_adjatt; + for(i=0;itotal_octave_lines;i++)seed[i]=NEGINF; + + /* set the ATH (floating below localmax, not global max by a + specified att) */ + if(attvi->ath_maxatt)att=p->vi->ath_maxatt; + + for(i=0;iath[i]+att; + + /* tone masking */ + seed_loop(p,(const float ***)p->tonecurves,logfft,logmask,seed,global_specmax); + max_seeds(p,seed,logmask); + +} + +void _vp_offset_and_mix(vorbis_look_psy *p, + float *noise, + float *tone, + int offset_select, + float *logmask){ + int i,n=p->n; + float toneatt=p->vi->tone_masteratt[offset_select]; + + for(i=0;inoiseoffset[offset_select][i]; + if(val>p->vi->noisemaxsupp)val=p->vi->noisemaxsupp; + logmask[i]=max(val,tone[i]+toneatt); + } +} + +float _vp_ampmax_decay(float amp,vorbis_dsp_state *vd){ + vorbis_info *vi=vd->vi; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy_global *gi=&ci->psy_g_param; + + int n=ci->blocksizes[vd->W]/2; + float secs=(float)n/vi->rate; + + amp+=secs*gi->ampmax_att_per_sec; + if(amp<-9999)amp=-9999; + return(amp); +} + +static void couple_lossless(float A, float B, + float *qA, float *qB){ + int test1=fabs(*qA)>fabs(*qB); + test1-= fabs(*qA)fabs(B))<<1)-1; + if(test1==1){ + *qB=(*qA>0.f?*qA-*qB:*qB-*qA); + }else{ + float temp=*qB; + *qB=(*qB>0.f?*qA-*qB:*qB-*qA); + *qA=temp; + } + + if(*qB>fabs(*qA)*1.9999f){ + *qB= -fabs(*qA)*2.f; + *qA= -*qA; + } +} + +static float hypot_lookup[32]={ + -0.009935, -0.011245, -0.012726, -0.014397, + -0.016282, -0.018407, -0.020800, -0.023494, + -0.026522, -0.029923, -0.033737, -0.038010, + -0.042787, -0.048121, -0.054064, -0.060671, + -0.068000, -0.076109, -0.085054, -0.094892, + -0.105675, -0.117451, -0.130260, -0.144134, + -0.159093, -0.175146, -0.192286, -0.210490, + -0.229718, -0.249913, -0.271001, -0.292893}; + +static void precomputed_couple_point(float premag, + int floorA,int floorB, + float *mag, float *ang){ + + int test=(floorA>floorB)-1; + int offset=31-abs(floorA-floorB); + float floormag=hypot_lookup[((offset<0)-1)&offset]+1.f; + + floormag*=FLOOR1_fromdB_INV_LOOKUP[(floorB&test)|(floorA&(~test))]; + + *mag=premag*floormag; + *ang=0.f; +} + +/* just like below, this is currently set up to only do + single-step-depth coupling. Otherwise, we'd have to do more + copying (which will be inevitable later) */ + +/* doing the real circular magnitude calculation is audibly superior + to (A+B)/sqrt(2) */ +static float dipole_hypot(float a, float b){ + if(a>0.){ + if(b>0.)return sqrt(a*a+b*b); + if(a>-b)return sqrt(a*a-b*b); + return -sqrt(b*b-a*a); + } + if(b<0.)return -sqrt(a*a+b*b); + if(-a>b)return -sqrt(a*a-b*b); + return sqrt(b*b-a*a); +} +static float round_hypot(float a, float b){ + if(a>0.){ + if(b>0.)return sqrt(a*a+b*b); + if(a>-b)return sqrt(a*a+b*b); + return -sqrt(b*b+a*a); + } + if(b<0.)return -sqrt(a*a+b*b); + if(-a>b)return -sqrt(a*a+b*b); + return sqrt(b*b+a*a); +} + +/* revert to round hypot for now */ +float **_vp_quantize_couple_memo(vorbis_block *vb, + vorbis_info_psy_global *g, + vorbis_look_psy *p, + vorbis_info_mapping0 *vi, + float **mdct){ + + int i,j,n=p->n; + float **ret=_vorbis_block_alloc(vb,vi->coupling_steps*sizeof(*ret)); + int limit=g->coupling_pointlimit[p->vi->blockflag][PACKETBLOBS/2]; + + for(i=0;icoupling_steps;i++){ + float *mdctM=mdct[vi->coupling_mag[i]]; + float *mdctA=mdct[vi->coupling_ang[i]]; + ret[i]=_vorbis_block_alloc(vb,n*sizeof(**ret)); + for(j=0;jf2); +} + +int **_vp_quantize_couple_sort(vorbis_block *vb, + vorbis_look_psy *p, + vorbis_info_mapping0 *vi, + float **mags){ + + + if(p->vi->normal_point_p){ + int i,j,k,n=p->n; + int **ret=_vorbis_block_alloc(vb,vi->coupling_steps*sizeof(*ret)); + int partition=p->vi->normal_partition; + float **work=alloca(sizeof(*work)*partition); + + for(i=0;icoupling_steps;i++){ + ret[i]=_vorbis_block_alloc(vb,n*sizeof(**ret)); + + for(j=0;jn; + vorbis_info_psy *vi=p->vi; + int partition=vi->normal_partition; + float **work=alloca(sizeof(*work)*partition); + int start=vi->normal_start; + + for(j=start;jn)partition=n-j; + for(i=0;in; + vorbis_info_psy *vi=p->vi; + int partition=vi->normal_partition; + int start=vi->normal_start; + + if(start>n)start=n; + + if(vi->normal_channel_p){ + for(;j=.25f){ + out[k]=rint(in[k]); + acc-=in[k]*in[k]; + flag=1; + }else{ + if(accnormal_thresh)break; + out[k]=unitnorm(in[k]); + acc-=1.; + } + } + + for(;in; + + /* perform any requested channel coupling */ + /* point stereo can only be used in a first stage (in this encoder) + because of the dependency on floor lookups */ + for(i=0;icoupling_steps;i++){ + + /* once we're doing multistage coupling in which a channel goes + through more than one coupling step, the floor vector + magnitudes will also have to be recalculated an propogated + along with PCM. Right now, we're not (that will wait until 5.1 + most likely), so the code isn't here yet. The memory management + here is all assuming single depth couplings anyway. */ + + /* make sure coupling a zero and a nonzero channel results in two + nonzero channels. */ + if(nonzero[vi->coupling_mag[i]] || + nonzero[vi->coupling_ang[i]]){ + + + float *rM=res[vi->coupling_mag[i]]; + float *rA=res[vi->coupling_ang[i]]; + float *qM=rM+n; + float *qA=rA+n; + int *floorM=ifloor[vi->coupling_mag[i]]; + int *floorA=ifloor[vi->coupling_ang[i]]; + float prepoint=stereo_threshholds[g->coupling_prepointamp[blobno]]; + float postpoint=stereo_threshholds[g->coupling_postpointamp[blobno]]; + int partition=(p->vi->normal_point_p?p->vi->normal_partition:p->n); + int limit=g->coupling_pointlimit[p->vi->blockflag][blobno]; + int pointlimit=limit; + + nonzero[vi->coupling_mag[i]]=1; + nonzero[vi->coupling_ang[i]]=1; + + for(j=0;jn;j+=partition){ + float acc=0.f; + + for(k=0;k=limit && fabs(rM[l])vi->normal_point_p){ + for(k=0;k=p->vi->normal_thresh;k++){ + int l=mag_sort[i][j+k]; + if(l=pointlimit && rint(qM[l])==0.f){ + qM[l]=unitnorm(qM[l]); + acc-=1.f; + } + } + } + } + } + } +} + diff --git a/src/sound/oggvorbis/vorbissrc/psy.h b/src/sound/oggvorbis/vorbissrc/psy.h new file mode 100644 index 0000000..b0d5fc4 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/psy.h @@ -0,0 +1,199 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: random psychoacoustics (not including preecho) + last mod: $Id: psy.h,v 1.32 2002/07/13 06:12:47 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_PSY_H_ +#define _V_PSY_H_ +#include "smallft.h" + +#include "backends.h" +#include "envelope.h" + +#ifndef EHMER_MAX +#define EHMER_MAX 56 +#endif + +/* psychoacoustic setup ********************************************/ +#define P_BANDS 17 /* 62Hz to 16kHz */ +#define P_LEVELS 8 /* 30dB to 100dB */ +#define P_LEVEL_0 30. /* 30 dB */ +#define P_NOISECURVES 3 + +#define NOISE_COMPAND_LEVELS 40 +typedef struct vorbis_info_psy{ + int blockflag; + + float ath_adjatt; + float ath_maxatt; + + float tone_masteratt[P_NOISECURVES]; + float tone_centerboost; + float tone_decay; + float tone_abs_limit; + float toneatt[P_BANDS]; + + int noisemaskp; + float noisemaxsupp; + float noisewindowlo; + float noisewindowhi; + int noisewindowlomin; + int noisewindowhimin; + int noisewindowfixed; + float noiseoff[P_NOISECURVES][P_BANDS]; + float noisecompand[NOISE_COMPAND_LEVELS]; + + float max_curve_dB; + + int normal_channel_p; + int normal_point_p; + int normal_start; + int normal_partition; + double normal_thresh; +} vorbis_info_psy; + +typedef struct{ + int eighth_octave_lines; + + /* for block long/short tuning; encode only */ + float preecho_thresh[VE_BANDS]; + float postecho_thresh[VE_BANDS]; + float stretch_penalty; + float preecho_minenergy; + + float ampmax_att_per_sec; + + /* channel coupling config */ + int coupling_pkHz[PACKETBLOBS]; + int coupling_pointlimit[2][PACKETBLOBS]; + int coupling_prepointamp[PACKETBLOBS]; + int coupling_postpointamp[PACKETBLOBS]; + int sliding_lowpass[2][PACKETBLOBS]; + +} vorbis_info_psy_global; + +typedef struct { + float ampmax; + int channels; + + vorbis_info_psy_global *gi; + int coupling_pointlimit[2][P_NOISECURVES]; +} vorbis_look_psy_global; + + +typedef struct { + int n; + struct vorbis_info_psy *vi; + + float ***tonecurves; + float **noiseoffset; + + float *ath; + long *octave; /* in n.ocshift format */ + long *bark; + + long firstoc; + long shiftoc; + int eighth_octave_lines; /* power of two, please */ + int total_octave_lines; + long rate; /* cache it */ +} vorbis_look_psy; + +extern void _vp_psy_init(vorbis_look_psy *p,vorbis_info_psy *vi, + vorbis_info_psy_global *gi,int n,long rate); +extern void _vp_psy_clear(vorbis_look_psy *p); +extern void *_vi_psy_dup(void *source); + +extern void _vi_psy_free(vorbis_info_psy *i); +extern vorbis_info_psy *_vi_psy_copy(vorbis_info_psy *i); + +extern void _vp_remove_floor(vorbis_look_psy *p, + float *mdct, + int *icodedflr, + float *residue, + int sliding_lowpass); + +extern void _vp_noisemask(vorbis_look_psy *p, + float *logmdct, + float *logmask); + +extern void _vp_tonemask(vorbis_look_psy *p, + float *logfft, + float *logmask, + float global_specmax, + float local_specmax); + +extern void _vp_offset_and_mix(vorbis_look_psy *p, + float *noise, + float *tone, + int offset_select, + float *logmask); + +extern float _vp_ampmax_decay(float amp,vorbis_dsp_state *vd); + +extern float **_vp_quantize_couple_memo(vorbis_block *vb, + vorbis_info_psy_global *g, + vorbis_look_psy *p, + vorbis_info_mapping0 *vi, + float **mdct); + +extern void _vp_couple(int blobno, + vorbis_info_psy_global *g, + vorbis_look_psy *p, + vorbis_info_mapping0 *vi, + float **res, + float **mag_memo, + int **mag_sort, + int **ifloor, + int *nonzero, + int sliding_lowpass); + +extern void _vp_noise_normalize(vorbis_look_psy *p, + float *in,float *out,int *sortedindex); + +extern void _vp_noise_normalize_sort(vorbis_look_psy *p, + float *magnitudes,int *sortedindex); + +extern int **_vp_quantize_couple_sort(vorbis_block *vb, + vorbis_look_psy *p, + vorbis_info_mapping0 *vi, + float **mags); + +#endif + diff --git a/src/sound/oggvorbis/vorbissrc/registry.c b/src/sound/oggvorbis/vorbissrc/registry.c new file mode 100644 index 0000000..632ec9e --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/registry.c @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: registry for time, floor, res backends and channel mappings + last mod: $Id: registry.c,v 1.15 2002/07/11 06:40:50 xiphmont Exp $ + + ********************************************************************/ + +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "registry.h" +#include "misc.h" +/* seems like major overkill now; the backend numbers will grow into + the infrastructure soon enough */ + +extern vorbis_func_floor floor0_exportbundle; +extern vorbis_func_floor floor1_exportbundle; +extern vorbis_func_residue residue0_exportbundle; +extern vorbis_func_residue residue1_exportbundle; +extern vorbis_func_residue residue2_exportbundle; +extern vorbis_func_mapping mapping0_exportbundle; + +vorbis_func_floor *_floor_P[]={ + &floor0_exportbundle, + &floor1_exportbundle, +}; + +vorbis_func_residue *_residue_P[]={ + &residue0_exportbundle, + &residue1_exportbundle, + &residue2_exportbundle, +}; + +vorbis_func_mapping *_mapping_P[]={ + &mapping0_exportbundle, +}; + diff --git a/src/sound/oggvorbis/vorbissrc/registry.h b/src/sound/oggvorbis/vorbissrc/registry.h new file mode 100644 index 0000000..3c9a7a4 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/registry.h @@ -0,0 +1,56 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: registry for time, floor, res backends and channel mappings + last mod: $Id: registry.h,v 1.13 2002/07/11 06:40:50 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_REG_H_ +#define _V_REG_H_ + +#define VI_TRANSFORMB 1 +#define VI_WINDOWB 1 +#define VI_TIMEB 1 +#define VI_FLOORB 2 +#define VI_RESB 3 +#define VI_MAPB 1 + +extern vorbis_func_floor *_floor_P[]; +extern vorbis_func_residue *_residue_P[]; +extern vorbis_func_mapping *_mapping_P[]; + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/res0.c b/src/sound/oggvorbis/vorbissrc/res0.c new file mode 100644 index 0000000..7a3ef9b --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/res0.c @@ -0,0 +1,930 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: residue backend 0, 1 and 2 implementation + last mod: $Id: res0.c,v 1.49 2003/01/18 08:28:37 xiphmont Exp $ + + ********************************************************************/ + +/* Slow, slow, slow, simpleminded and did I mention it was slow? The + encode/decode loops are coded for clarity and performance is not + yet even a nagging little idea lurking in the shadows. Oh and BTW, + it's slow. */ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "registry.h" +#include "codebook.h" +#include "misc.h" +#include "os.h" + +#ifdef TRAIN_RES +#include +#endif + +typedef struct { + vorbis_info_residue0 *info; + + int parts; + int stages; + codebook *fullbooks; + codebook *phrasebook; + codebook ***partbooks; + + int partvals; + int **decodemap; + + long postbits; + long phrasebits; + long frames; + +#ifdef TRAIN_RES + int train_seq; + long *training_data[8][64]; + float training_max[8][64]; + float training_min[8][64]; + float tmin; + float tmax; +#endif + +} vorbis_look_residue0; + +void res0_free_info(vorbis_info_residue *i){ + vorbis_info_residue0 *info=(vorbis_info_residue0 *)i; + if(info){ + memset(info,0,sizeof(*info)); + _ogg_free(info); + } +} + +void res0_free_look(vorbis_look_residue *i){ + int j; + if(i){ + + vorbis_look_residue0 *look=(vorbis_look_residue0 *)i; + +#ifdef TRAIN_RES + { + int j,k,l; + for(j=0;jparts;j++){ + /*fprintf(stderr,"partition %d: ",j);*/ + for(k=0;k<8;k++) + if(look->training_data[k][j]){ + char buffer[80]; + FILE *of; + codebook *statebook=look->partbooks[j][k]; + + /* long and short into the same bucket by current convention */ + sprintf(buffer,"res_part%d_pass%d.vqd",j,k); + of=fopen(buffer,"a"); + + for(l=0;lentries;l++) + fprintf(of,"%d:%ld\n",l,look->training_data[k][j][l]); + + fclose(of); + + /*fprintf(stderr,"%d(%.2f|%.2f) ",k, + look->training_min[k][j],look->training_max[k][j]);*/ + + _ogg_free(look->training_data[k][j]); + } + /*fprintf(stderr,"\n");*/ + } + } + fprintf(stderr,"min/max residue: %g::%g\n",look->tmin,look->tmax); + + /*fprintf(stderr,"residue bit usage %f:%f (%f total)\n", + (float)look->phrasebits/look->frames, + (float)look->postbits/look->frames, + (float)(look->postbits+look->phrasebits)/look->frames);*/ +#endif + + + /*vorbis_info_residue0 *info=look->info; + + fprintf(stderr, + "%ld frames encoded in %ld phrasebits and %ld residue bits " + "(%g/frame) \n",look->frames,look->phrasebits, + look->resbitsflat, + (look->phrasebits+look->resbitsflat)/(float)look->frames); + + for(j=0;jparts;j++){ + long acc=0; + fprintf(stderr,"\t[%d] == ",j); + for(k=0;kstages;k++) + if((info->secondstages[j]>>k)&1){ + fprintf(stderr,"%ld,",look->resbits[j][k]); + acc+=look->resbits[j][k]; + } + + fprintf(stderr,":: (%ld vals) %1.2fbits/sample\n",look->resvals[j], + acc?(float)acc/(look->resvals[j]*info->grouping):0); + } + fprintf(stderr,"\n");*/ + + for(j=0;jparts;j++) + if(look->partbooks[j])_ogg_free(look->partbooks[j]); + _ogg_free(look->partbooks); + for(j=0;jpartvals;j++) + _ogg_free(look->decodemap[j]); + _ogg_free(look->decodemap); + + memset(look,0,sizeof(*look)); + _ogg_free(look); + } +} + +static int ilog(unsigned int v){ + int ret=0; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +static int icount(unsigned int v){ + int ret=0; + while(v){ + ret+=v&1; + v>>=1; + } + return(ret); +} + + +void res0_pack(vorbis_info_residue *vr,oggpack_buffer *opb){ + vorbis_info_residue0 *info=(vorbis_info_residue0 *)vr; + int j,acc=0; + oggpack_write(opb,info->begin,24); + oggpack_write(opb,info->end,24); + + oggpack_write(opb,info->grouping-1,24); /* residue vectors to group and + code with a partitioned book */ + oggpack_write(opb,info->partitions-1,6); /* possible partition choices */ + oggpack_write(opb,info->groupbook,8); /* group huffman book */ + + /* secondstages is a bitmask; as encoding progresses pass by pass, a + bitmask of one indicates this partition class has bits to write + this pass */ + for(j=0;jpartitions;j++){ + if(ilog(info->secondstages[j])>3){ + /* yes, this is a minor hack due to not thinking ahead */ + oggpack_write(opb,info->secondstages[j],3); + oggpack_write(opb,1,1); + oggpack_write(opb,info->secondstages[j]>>3,5); + }else + oggpack_write(opb,info->secondstages[j],4); /* trailing zero */ + acc+=icount(info->secondstages[j]); + } + for(j=0;jbooklist[j],8); + +} + +/* vorbis_info is for range checking */ +vorbis_info_residue *res0_unpack(vorbis_info *vi,oggpack_buffer *opb){ + int j,acc=0; + vorbis_info_residue0 *info=_ogg_calloc(1,sizeof(*info)); + codec_setup_info *ci=vi->codec_setup; + + info->begin=oggpack_read(opb,24); + info->end=oggpack_read(opb,24); + info->grouping=oggpack_read(opb,24)+1; + info->partitions=oggpack_read(opb,6)+1; + info->groupbook=oggpack_read(opb,8); + + for(j=0;jpartitions;j++){ + int cascade=oggpack_read(opb,3); + if(oggpack_read(opb,1)) + cascade|=(oggpack_read(opb,5)<<3); + info->secondstages[j]=cascade; + + acc+=icount(cascade); + } + for(j=0;jbooklist[j]=oggpack_read(opb,8); + + if(info->groupbook>=ci->books)goto errout; + for(j=0;jbooklist[j]>=ci->books)goto errout; + + return(info); + errout: + res0_free_info(info); + return(NULL); +} + +vorbis_look_residue *res0_look(vorbis_dsp_state *vd, + vorbis_info_residue *vr){ + vorbis_info_residue0 *info=(vorbis_info_residue0 *)vr; + vorbis_look_residue0 *look=_ogg_calloc(1,sizeof(*look)); + codec_setup_info *ci=vd->vi->codec_setup; + + int j,k,acc=0; + int dim; + int maxstage=0; + look->info=info; + + look->parts=info->partitions; + look->fullbooks=ci->fullbooks; + look->phrasebook=ci->fullbooks+info->groupbook; + dim=look->phrasebook->dim; + + look->partbooks=_ogg_calloc(look->parts,sizeof(*look->partbooks)); + + for(j=0;jparts;j++){ + int stages=ilog(info->secondstages[j]); + if(stages){ + if(stages>maxstage)maxstage=stages; + look->partbooks[j]=_ogg_calloc(stages,sizeof(*look->partbooks[j])); + for(k=0;ksecondstages[j]&(1<partbooks[j][k]=ci->fullbooks+info->booklist[acc++]; +#ifdef TRAIN_RES + look->training_data[k][j]=_ogg_calloc(look->partbooks[j][k]->entries, + sizeof(***look->training_data)); +#endif + } + } + } + + look->partvals=rint(pow((float)look->parts,(float)dim)); + look->stages=maxstage; + look->decodemap=_ogg_malloc(look->partvals*sizeof(*look->decodemap)); + for(j=0;jpartvals;j++){ + long val=j; + long mult=look->partvals/look->parts; + look->decodemap[j]=_ogg_malloc(dim*sizeof(*look->decodemap[j])); + for(k=0;kparts; + look->decodemap[j][k]=deco; + } + } +#ifdef TRAIN_RES + { + static int train_seq=0; + look->train_seq=train_seq++; + } +#endif + return(look); +} + +/* break an abstraction and copy some code for performance purposes */ +static int local_book_besterror(codebook *book,float *a){ + int dim=book->dim,i,k,o; + int best=0; + encode_aux_threshmatch *tt=book->c->thresh_tree; + + /* find the quant val of each scalar */ + for(k=0,o=dim;kthreshvals>>1; + + if(valquantthresh[i]){ + if(valquantthresh[i-1]){ + for(--i;i>0;--i) + if(val>=tt->quantthresh[i-1]) + break; + } + }else{ + + for(++i;ithreshvals-1;++i) + if(valquantthresh[i])break; + + } + + best=(best*tt->quantvals)+tt->quantmap[i]; + } + /* regular lattices are easy :-) */ + + if(book->c->lengthlist[best]<=0){ + const static_codebook *c=book->c; + int i,j; + float bestf=0.f; + float *e=book->valuelist; + best=-1; + for(i=0;ientries;i++){ + if(c->lengthlist[i]>0){ + float this=0.f; + for(j=0;jvaluelist+best*dim; + for(i=0;idim; + int step=n/dim; + + for(i=0;iinfo; + vorbis_info *vi=vb->vd->vi; + codec_setup_info *ci=vi->codec_setup; + + /* move all this setup out later */ + int samples_per_partition=info->grouping; + int possible_partitions=info->partitions; + int n=info->end-info->begin; + + int partvals=n/samples_per_partition; + long **partword=_vorbis_block_alloc(vb,ch*sizeof(*partword)); + float scale=100./samples_per_partition; + + /* we find the partition type for each partition of each + channel. We'll go back and do the interleaved encoding in a + bit. For now, clarity */ + + for(i=0;ibegin; + for(j=0;jmax)max=fabs(in[j][offset+k]); + ent+=fabs(rint(in[j][offset+k])); + } + ent*=scale; + + for(k=0;kclassmetric1[k] && + (info->classmetric2[k]<0 || (int)entclassmetric2[k])) + break; + + partword[j][i]=k; + } + } + +#ifdef TRAIN_RESAUX + { + FILE *of; + char buffer[80]; + + for(i=0;itrain_seq); + of=fopen(buffer,"a"); + for(j=0;jframes++; + + return(partword); +} + +/* designed for stereo or other modes where the partition size is an + integer multiple of the number of channels encoded in the current + submap */ +static long **_2class(vorbis_block *vb,vorbis_look_residue *vl,float **in, + int ch){ + long i,j,k,l; + vorbis_look_residue0 *look=(vorbis_look_residue0 *)vl; + vorbis_info_residue0 *info=look->info; + + /* move all this setup out later */ + int samples_per_partition=info->grouping; + int possible_partitions=info->partitions; + int n=info->end-info->begin; + + int partvals=n/samples_per_partition; + long **partword=_vorbis_block_alloc(vb,sizeof(*partword)); + +#ifdef TRAIN_RES + FILE *of; + char buffer[80]; +#endif + + partword[0]=_vorbis_block_alloc(vb,n*ch/samples_per_partition*sizeof(*partword[0])); + memset(partword[0],0,n*ch/samples_per_partition*sizeof(*partword[0])); + + for(i=0,l=info->begin/ch;imagmax)magmax=fabs(in[0][l]); + for(k=1;kangmax)angmax=fabs(in[k][l]); + l++; + } + + for(j=0;jclassmetric1[j] && + angmax<=info->classmetric2[j]) + break; + + partword[0][i]=j; + + } + +#ifdef TRAIN_RESAUX + sprintf(buffer,"resaux_%d.vqd",look->train_seq); + of=fopen(buffer,"a"); + for(i=0;iframes++; + + return(partword); +} + +static int _01forward(vorbis_block *vb,vorbis_look_residue *vl, + float **in,int ch, + long **partword, + int (*encode)(oggpack_buffer *,float *,int, + codebook *,long *)){ + long i,j,k,s; + vorbis_look_residue0 *look=(vorbis_look_residue0 *)vl; + vorbis_info_residue0 *info=look->info; + + vorbis_dsp_state *vd=vb->vd; + + /* move all this setup out later */ + int samples_per_partition=info->grouping; + int possible_partitions=info->partitions; + int partitions_per_word=look->phrasebook->dim; + int n=info->end-info->begin; + + int partvals=n/samples_per_partition; + long resbits[128]; + long resvals[128]; + +#ifdef TRAIN_RES + for(i=0;ibegin;jend;j++){ + if(in[i][j]>look->tmax)look->tmax=in[i][j]; + if(in[i][j]tmin)look->tmin=in[i][j]; + } +#endif + + memset(resbits,0,sizeof(resbits)); + memset(resvals,0,sizeof(resvals)); + + /* we code the partition words for each channel, then the residual + words for a partition per channel until we've written all the + residual words for that partition word. Then write the next + partition channel words... */ + + for(s=0;sstages;s++){ + + for(i=0;iphrasebook->entries) + look->phrasebits+=vorbis_book_encode(look->phrasebook,val,&vb->opb); +#if 0 /*def TRAIN_RES*/ + else + fprintf(stderr,"!"); +#endif + + } + } + + /* now we encode interleaved residual values for the partitions */ + for(k=0;kbegin; + + for(j=0;jsecondstages[partword[j][i]]&(1<partbooks[partword[j][i]][s]; + if(statebook){ + int ret; + long *accumulator=NULL; + +#ifdef TRAIN_RES + accumulator=look->training_data[s][partword[j][i]]; + { + int l; + float *samples=in[j]+offset; + for(l=0;ltraining_min[s][partword[j][i]]) + look->training_min[s][partword[j][i]]=samples[l]; + if(samples[l]>look->training_max[s][partword[j][i]]) + look->training_max[s][partword[j][i]]=samples[l]; + } + } +#endif + + ret=encode(&vb->opb,in[j]+offset,samples_per_partition, + statebook,accumulator); + + look->postbits+=ret; + resbits[partword[j][i]]+=ret; + } + } + } + } + } + } + + /*{ + long total=0; + long totalbits=0; + fprintf(stderr,"%d :: ",vb->mode); + for(k=0;kinfo; + + /* move all this setup out later */ + int samples_per_partition=info->grouping; + int partitions_per_word=look->phrasebook->dim; + int n=info->end-info->begin; + + int partvals=n/samples_per_partition; + int partwords=(partvals+partitions_per_word-1)/partitions_per_word; + int ***partword=alloca(ch*sizeof(*partword)); + + for(j=0;jstages;s++){ + + /* each loop decodes on partition codeword containing + partitions_pre_word partitions */ + for(i=0,l=0;iphrasebook,&vb->opb); + if(temp==-1)goto eopbreak; + partword[j][l]=look->decodemap[temp]; + if(partword[j][l]==NULL)goto errout; + } + } + + /* now we decode residual values for the partitions */ + for(k=0;kbegin+i*samples_per_partition; + if(info->secondstages[partword[j][l][k]]&(1<partbooks[partword[j][l][k]][s]; + if(stagebook){ + if(decodepart(stagebook,in[j]+offset,&vb->opb, + samples_per_partition)==-1)goto eopbreak; + } + } + } + } + } + + errout: + eopbreak: + return(0); +} + +#if 0 +/* residue 0 and 1 are just slight variants of one another. 0 is + interleaved, 1 is not */ +long **res0_class(vorbis_block *vb,vorbis_look_residue *vl, + float **in,int *nonzero,int ch){ + /* we encode only the nonzero parts of a bundle */ + int i,used=0; + for(i=0;ipcmend/2; + for(i=0;ipcmend/2; + for(i=0;ipcmend/2,used=0; + + /* don't duplicate the code; use a working vector hack for now and + reshape ourselves into a single channel res1 */ + /* ugly; reallocs for each coupling pass :-( */ + float *work=_vorbis_block_alloc(vb,ch*n*sizeof(*work)); + for(i=0;iinfo; + + /* move all this setup out later */ + int samples_per_partition=info->grouping; + int partitions_per_word=look->phrasebook->dim; + int n=info->end-info->begin; + + int partvals=n/samples_per_partition; + int partwords=(partvals+partitions_per_word-1)/partitions_per_word; + int **partword=_vorbis_block_alloc(vb,partwords*sizeof(*partword)); + + for(i=0;istages;s++){ + for(i=0,l=0;iphrasebook,&vb->opb); + if(temp==-1)goto eopbreak; + partword[l]=look->decodemap[temp]; + if(partword[l]==NULL)goto errout; + } + + /* now we decode residual values for the partitions */ + for(k=0;ksecondstages[partword[l][k]]&(1<partbooks[partword[l][k]][s]; + + if(stagebook){ + if(vorbis_book_decodevv_add(stagebook,in, + i*samples_per_partition+info->begin,ch, + &vb->opb,samples_per_partition)==-1) + goto eopbreak; + } + } + } + } + + errout: + eopbreak: + return(0); +} + + +vorbis_func_residue residue0_exportbundle={ + NULL, + &res0_unpack, + &res0_look, + &res0_free_info, + &res0_free_look, + NULL, + NULL, + &res0_inverse +}; + +vorbis_func_residue residue1_exportbundle={ + &res0_pack, + &res0_unpack, + &res0_look, + &res0_free_info, + &res0_free_look, + &res1_class, + &res1_forward, + &res1_inverse +}; + +vorbis_func_residue residue2_exportbundle={ + &res0_pack, + &res0_unpack, + &res0_look, + &res0_free_info, + &res0_free_look, + &res2_class, + &res2_forward, + &res2_inverse +}; + diff --git a/src/sound/oggvorbis/vorbissrc/scales.h b/src/sound/oggvorbis/vorbissrc/scales.h new file mode 100644 index 0000000..14aeadb --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/scales.h @@ -0,0 +1,112 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: linear scale -> dB, Bark and Mel scales + last mod: $Id: scales.h,v 1.26 2002/07/11 06:40:50 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_SCALES_H_ +#define _V_SCALES_H_ + +#include +#include "os.h" + +/* 20log10(x) */ +#define VORBIS_IEEE_FLOAT32 1 +#ifdef VORBIS_IEEE_FLOAT32 + +static float unitnorm(float x){ + ogg_uint32_t *ix=(ogg_uint32_t *)&x; + *ix=(*ix&0x80000000UL)|(0x3f800000UL); + return(x); +} + +static float FABS(float *x){ + ogg_uint32_t *ix=(ogg_uint32_t *)x; + *ix&=0x7fffffffUL; + return(*x); +} + +static float todB(const float *x){ + float calc; + ogg_int32_t *i=(ogg_int32_t *)x; + calc = ((*i) & 0x7fffffff); + calc *= 7.1771144e-7f; + calc += -764.27118f; + return calc; +} + +#define todB_nn(x) todB(x) + +#else + +static float unitnorm(float x){ + if(x<0)return(-1.f); + return(1.f); +} + +#define FABS(x) fabs(*(x)) + +#define todB(x) (*(x)==0?-400.f:log(*(x)**(x))*4.34294480f) +#define todB_nn(x) (*(x)==0.f?-400.f:log(*(x))*8.6858896f) + +#endif + +#define fromdB(x) (exp((x)*.11512925f)) + +/* The bark scale equations are approximations, since the original + table was somewhat hand rolled. The below are chosen to have the + best possible fit to the rolled tables, thus their somewhat odd + appearance (these are more accurate and over a longer range than + the oft-quoted bark equations found in the texts I have). The + approximations are valid from 0 - 30kHz (nyquist) or so. + + all f in Hz, z in Bark */ + +#define toBARK(n) (13.1f*atan(.00074f*(n))+2.24f*atan((n)*(n)*1.85e-8f)+1e-4f*(n)) +#define fromBARK(z) (102.f*(z)-2.f*pow(z,2.f)+.4f*pow(z,3.f)+pow(1.46f,z)-1.f) +#define toMEL(n) (log(1.f+(n)*.001f)*1442.695f) +#define fromMEL(m) (1000.f*exp((m)/1442.695f)-1000.f) + +/* Frequency to octave. We arbitrarily declare 63.5 Hz to be octave + 0.0 */ + +#define toOC(n) (log(n)*1.442695f-5.965784f) +#define fromOC(o) (exp(((o)+5.965784f)*.693147f)) + +#endif + diff --git a/src/sound/oggvorbis/vorbissrc/sharedbook.c b/src/sound/oggvorbis/vorbissrc/sharedbook.c new file mode 100644 index 0000000..45afc5d --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/sharedbook.c @@ -0,0 +1,758 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: basic shared codebook operations + last mod: $Id: sharedbook.c,v 1.29 2002/10/11 07:44:28 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include "../ogg/ogg.h" +#include "os.h" +#include "misc.h" +#include "../vorbis/codec.h" +#include "codebook.h" +#include "scales.h" + +/**** pack/unpack helpers ******************************************/ +int _ilog(unsigned int v){ + int ret=0; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +/* 32 bit float (not IEEE; nonnormalized mantissa + + biased exponent) : neeeeeee eeemmmmm mmmmmmmm mmmmmmmm + Why not IEEE? It's just not that important here. */ + +#define VQ_FEXP 10 +#define VQ_FMAN 21 +#define VQ_FEXP_BIAS 768 /* bias toward values smaller than 1. */ + +/* doesn't currently guard under/overflow */ +long _float32_pack(float val){ + int sign=0; + long exp; + long mant; + if(val<0){ + sign=0x80000000; + val= -val; + } + exp= floor(log(val)/log(2.f)); + mant=rint(ldexp(val,(VQ_FMAN-1)-exp)); + exp=(exp+VQ_FEXP_BIAS)<>VQ_FMAN; + if(sign)mant= -mant; + return(ldexp(mant,exp-(VQ_FMAN-1)-VQ_FEXP_BIAS)); +} + +/* given a list of word lengths, generate a list of codewords. Works + for length ordered or unordered, always assigns the lowest valued + codewords first. Extended to handle unused entries (length 0) */ +ogg_uint32_t *_make_words(long *l,long n,long sparsecount){ + long i,j,count=0; + ogg_uint32_t marker[33]; + ogg_uint32_t *r=_ogg_malloc((sparsecount?sparsecount:n)*sizeof(*r)); + memset(marker,0,sizeof(marker)); + + for(i=0;i0){ + ogg_uint32_t entry=marker[length]; + + /* when we claim a node for an entry, we also claim the nodes + below it (pruning off the imagined tree that may have dangled + from it) as well as blocking the use of any nodes directly + above for leaves */ + + /* update ourself */ + if(length<32 && (entry>>length)){ + /* error condition; the lengths must specify an overpopulated tree */ + _ogg_free(r); + return(NULL); + } + r[count++]=entry; + + /* Look to see if the next shorter marker points to the node + above. if so, update it and repeat. */ + { + for(j=length;j>0;j--){ + + if(marker[j]&1){ + /* have to jump branches */ + if(j==1) + marker[1]++; + else + marker[j]=marker[j-1]<<1; + break; /* invariant says next upper marker would already + have been moved if it was on the same path */ + } + marker[j]++; + } + } + + /* prune the tree; the implicit invariant says all the longer + markers were dangling from our just-taken node. Dangle them + from our *new* node. */ + for(j=length+1;j<33;j++) + if((marker[j]>>1) == entry){ + entry=marker[j]; + marker[j]=marker[j-1]<<1; + }else + break; + }else + if(sparsecount==0)count++; + } + + /* bitreverse the words because our bitwise packer/unpacker is LSb + endian */ + for(i=0,count=0;i>j)&1; + } + + if(sparsecount){ + if(l[i]) + r[count++]=temp; + }else + r[count++]=temp; + } + + return(r); +} + +/* there might be a straightforward one-line way to do the below + that's portable and totally safe against roundoff, but I haven't + thought of it. Therefore, we opt on the side of caution */ +long _book_maptype1_quantvals(const static_codebook *b){ + long vals=floor(pow((float)b->entries,1.f/b->dim)); + + /* the above *should* be reliable, but we'll not assume that FP is + ever reliable when bitstream sync is at stake; verify via integer + means that vals really is the greatest value of dim for which + vals^b->bim <= b->entries */ + /* treat the above as an initial guess */ + while(1){ + long acc=1; + long acc1=1; + int i; + for(i=0;idim;i++){ + acc*=vals; + acc1*=vals+1; + } + if(acc<=b->entries && acc1>b->entries){ + return(vals); + }else{ + if(acc>b->entries){ + vals--; + }else{ + vals++; + } + } + } +} + +/* unpack the quantized list of values for encode/decode ***********/ +/* we need to deal with two map types: in map type 1, the values are + generated algorithmically (each column of the vector counts through + the values in the quant vector). in map type 2, all the values came + in in an explicit list. Both value lists must be unpacked */ +float *_book_unquantize(const static_codebook *b,int n,int *sparsemap){ + long j,k,count=0; + if(b->maptype==1 || b->maptype==2){ + int quantvals; + float mindel=_float32_unpack(b->q_min); + float delta=_float32_unpack(b->q_delta); + float *r=_ogg_calloc(n*b->dim,sizeof(*r)); + + /* maptype 1 and 2 both use a quantized value vector, but + different sizes */ + switch(b->maptype){ + case 1: + /* most of the time, entries%dimensions == 0, but we need to be + well defined. We define that the possible vales at each + scalar is values == entries/dim. If entries%dim != 0, we'll + have 'too few' values (values*dimentries;j++){ + if((sparsemap && b->lengthlist[j]) || !sparsemap){ + float last=0.f; + int indexdiv=1; + for(k=0;kdim;k++){ + int index= (j/indexdiv)%quantvals; + float val=b->quantlist[index]; + val=fabs(val)*delta+mindel+last; + if(b->q_sequencep)last=val; + if(sparsemap) + r[sparsemap[count]*b->dim+k]=val; + else + r[count*b->dim+k]=val; + indexdiv*=quantvals; + } + count++; + } + + } + break; + case 2: + for(j=0;jentries;j++){ + if((sparsemap && b->lengthlist[j]) || !sparsemap){ + float last=0.f; + + for(k=0;kdim;k++){ + float val=b->quantlist[j*b->dim+k]; + val=fabs(val)*delta+mindel+last; + if(b->q_sequencep)last=val; + if(sparsemap) + r[sparsemap[count]*b->dim+k]=val; + else + r[count*b->dim+k]=val; + } + count++; + } + } + break; + } + + return(r); + } + return(NULL); +} + +void vorbis_staticbook_clear(static_codebook *b){ + if(b->allocedp){ + if(b->quantlist)_ogg_free(b->quantlist); + if(b->lengthlist)_ogg_free(b->lengthlist); + if(b->nearest_tree){ + _ogg_free(b->nearest_tree->ptr0); + _ogg_free(b->nearest_tree->ptr1); + _ogg_free(b->nearest_tree->p); + _ogg_free(b->nearest_tree->q); + memset(b->nearest_tree,0,sizeof(*b->nearest_tree)); + _ogg_free(b->nearest_tree); + } + if(b->thresh_tree){ + _ogg_free(b->thresh_tree->quantthresh); + _ogg_free(b->thresh_tree->quantmap); + memset(b->thresh_tree,0,sizeof(*b->thresh_tree)); + _ogg_free(b->thresh_tree); + } + + memset(b,0,sizeof(*b)); + } +} + +void vorbis_staticbook_destroy(static_codebook *b){ + if(b->allocedp){ + vorbis_staticbook_clear(b); + _ogg_free(b); + } +} + +void vorbis_book_clear(codebook *b){ + /* static book is not cleared; we're likely called on the lookup and + the static codebook belongs to the info struct */ + if(b->valuelist)_ogg_free(b->valuelist); + if(b->codelist)_ogg_free(b->codelist); + + if(b->dec_index)_ogg_free(b->dec_index); + if(b->dec_codelengths)_ogg_free(b->dec_codelengths); + if(b->dec_firsttable)_ogg_free(b->dec_firsttable); + + memset(b,0,sizeof(*b)); +} + +int vorbis_book_init_encode(codebook *c,const static_codebook *s){ + + memset(c,0,sizeof(*c)); + c->c=s; + c->entries=s->entries; + c->used_entries=s->entries; + c->dim=s->dim; + c->codelist=_make_words(s->lengthlist,s->entries,0); + c->valuelist=_book_unquantize(s,s->entries,NULL); + + return(0); +} + +static ogg_uint32_t bitreverse(ogg_uint32_t x){ + x= ((x>>16)&0x0000ffffUL) | ((x<<16)&0xffff0000UL); + x= ((x>> 8)&0x00ff00ffUL) | ((x<< 8)&0xff00ff00UL); + x= ((x>> 4)&0x0f0f0f0fUL) | ((x<< 4)&0xf0f0f0f0UL); + x= ((x>> 2)&0x33333333UL) | ((x<< 2)&0xccccccccUL); + return((x>> 1)&0x55555555UL) | ((x<< 1)&0xaaaaaaaaUL); +} + +static int sort32a(const void *a,const void *b){ + return ( **(ogg_uint32_t **)a>**(ogg_uint32_t **)b)- + ( **(ogg_uint32_t **)a<**(ogg_uint32_t **)b); +} + +/* decode codebook arrangement is more heavily optimized than encode */ +int vorbis_book_init_decode(codebook *c,const static_codebook *s){ + int i,j,n=0,tabn; + int *sortindex; + memset(c,0,sizeof(*c)); + + /* count actually used entries */ + for(i=0;ientries;i++) + if(s->lengthlist[i]>0) + n++; + + c->entries=s->entries; + c->used_entries=n; + c->dim=s->dim; + + /* two different remappings go on here. + + First, we collapse the likely sparse codebook down only to + actually represented values/words. This collapsing needs to be + indexed as map-valueless books are used to encode original entry + positions as integers. + + Second, we reorder all vectors, including the entry index above, + by sorted bitreversed codeword to allow treeless decode. */ + + { + /* perform sort */ + ogg_uint32_t *codes=_make_words(s->lengthlist,s->entries,c->used_entries); + ogg_uint32_t **codep=alloca(sizeof(*codep)*n); + + if(codes==NULL)goto err_out; + + for(i=0;icodelist=_ogg_malloc(n*sizeof(*c->codelist)); + /* the index is a reverse index */ + for(i=0;icodelist[sortindex[i]]=codes[i]; + _ogg_free(codes); + } + + c->valuelist=_book_unquantize(s,n,sortindex); + c->dec_index=_ogg_malloc(n*sizeof(*c->dec_index)); + + for(n=0,i=0;ientries;i++) + if(s->lengthlist[i]>0) + c->dec_index[sortindex[n++]]=i; + + c->dec_codelengths=_ogg_malloc(n*sizeof(*c->dec_codelengths)); + for(n=0,i=0;ientries;i++) + if(s->lengthlist[i]>0) + c->dec_codelengths[sortindex[n++]]=s->lengthlist[i]; + + c->dec_firsttablen=_ilog(c->used_entries)-4; /* this is magic */ + if(c->dec_firsttablen<5)c->dec_firsttablen=5; + if(c->dec_firsttablen>8)c->dec_firsttablen=8; + + tabn=1<dec_firsttablen; + c->dec_firsttable=_ogg_calloc(tabn,sizeof(*c->dec_firsttable)); + c->dec_maxlength=0; + + for(i=0;idec_maxlengthdec_codelengths[i]) + c->dec_maxlength=c->dec_codelengths[i]; + if(c->dec_codelengths[i]<=c->dec_firsttablen){ + ogg_uint32_t orig=bitreverse(c->codelist[i]); + for(j=0;j<(1<<(c->dec_firsttablen-c->dec_codelengths[i]));j++) + c->dec_firsttable[orig|(j<dec_codelengths[i])]=i+1; + } + } + + /* now fill in 'unused' entries in the firsttable with hi/lo search + hints for the non-direct-hits */ + { + ogg_uint32_t mask=0xfffffffeUL<<(31-c->dec_firsttablen); + long lo=0,hi=0; + + for(i=0;idec_firsttablen); + if(c->dec_firsttable[bitreverse(word)]==0){ + while((lo+1)codelist[lo+1]<=word)lo++; + while( hi=(c->codelist[hi]&mask))hi++; + + /* we only actually have 15 bits per hint to play with here. + In order to overflow gracefully (nothing breaks, efficiency + just drops), encode as the difference from the extremes. */ + { + unsigned long loval=lo; + unsigned long hival=n-hi; + + if(loval>0x7fff)loval=0x7fff; + if(hival>0x7fff)hival=0x7fff; + c->dec_firsttable[bitreverse(word)]= + 0x80000000UL | (loval<<15) | hival; + } + } + } + } + + + return(0); + err_out: + vorbis_book_clear(c); + return(-1); +} + +static float _dist(int el,float *ref, float *b,int step){ + int i; + float acc=0.f; + for(i=0;ic->thresh_tree; + +#if 0 + encode_aux_nearestmatch *nt=book->c->nearest_tree; + encode_aux_pigeonhole *pt=book->c->pigeon_tree; +#endif + int dim=book->dim; + int k,o; + /*int savebest=-1; + float saverr;*/ + + /* do we have a threshhold encode hint? */ + if(tt){ + int index=0,i; + /* find the quant val of each scalar */ + for(k=0,o=step*(dim-1);kthreshvals>>1; + if(a[o]quantthresh[i]){ + + for(;i>0;i--) + if(a[o]>=tt->quantthresh[i-1]) + break; + + }else{ + + for(i++;ithreshvals-1;i++) + if(a[o]quantthresh[i])break; + + } + + index=(index*tt->quantvals)+tt->quantmap[i]; + } + /* regular lattices are easy :-) */ + if(book->c->lengthlist[index]>0) /* is this unused? If so, we'll + use a decision tree after all + and fall through*/ + return(index); + } + +#if 0 + /* do we have a pigeonhole encode hint? */ + if(pt){ + const static_codebook *c=book->c; + int i,besti=-1; + float best=0.f; + int entry=0; + + /* dealing with sequentialness is a pain in the ass */ + if(c->q_sequencep){ + int pv; + long mul=1; + float qlast=0; + for(k=0,o=0;kmin)/pt->del); + if(pv<0 || pv>=pt->mapentries)break; + entry+=pt->pigeonmap[pv]*mul; + mul*=pt->quantvals; + qlast+=pv*pt->del+pt->min; + } + }else{ + for(k=0,o=step*(dim-1);kmin)/pt->del); + if(pv<0 || pv>=pt->mapentries)break; + entry=entry*pt->quantvals+pt->pigeonmap[pv]; + } + } + + /* must be within the pigeonholable range; if we quant outside (or + in an entry that we define no list for), brute force it */ + if(k==dim && pt->fitlength[entry]){ + /* search the abbreviated list */ + long *list=pt->fitlist+pt->fitmap[entry]; + for(i=0;ifitlength[entry];i++){ + float this=_dist(dim,book->valuelist+list[i]*dim,a,step); + if(besti==-1 || thisvaluelist+nt->p[ptr]; + float *q=book->valuelist+nt->q[ptr]; + + for(k=0,o=0;k0.f) /* in A */ + ptr= -nt->ptr0[ptr]; + else /* in B */ + ptr= -nt->ptr1[ptr]; + if(ptr<=0)break; + } + return(-ptr); + } +#endif + + /* brute force it! */ + { + const static_codebook *c=book->c; + int i,besti=-1; + float best=0.f; + float *e=book->valuelist; + for(i=0;ientries;i++){ + if(c->lengthlist[i]>0){ + float this=_dist(dim,e,a,step); + if(besti==-1 || thisvaluelist+savebest*dim)[i]); + fprintf(stderr,"\n" + "bruteforce (entry %d, err %g):",besti,best); + for(i=0;ivaluelist+besti*dim)[i]); + fprintf(stderr,"\n"); + }*/ + return(besti); + } +} + +long vorbis_book_codeword(codebook *book,int entry){ + if(book->c) /* only use with encode; decode optimizations are + allowed to break this */ + return book->codelist[entry]; + return -1; +} + +long vorbis_book_codelen(codebook *book,int entry){ + if(book->c) /* only use with encode; decode optimizations are + allowed to break this */ + return book->c->lengthlist[entry]; + return -1; +} + +#ifdef _V_SELFTEST + +/* Unit tests of the dequantizer; this stuff will be OK + cross-platform, I simply want to be sure that special mapping cases + actually work properly; a bug could go unnoticed for a while */ + +#include + +/* cases: + + no mapping + full, explicit mapping + algorithmic mapping + + nonsequential + sequential +*/ + +static long full_quantlist1[]={0,1,2,3, 4,5,6,7, 8,3,6,1}; +static long partial_quantlist1[]={0,7,2}; + +/* no mapping */ +static_codebook test1={ + 4,16, + NULL, + 0, + 0,0,0,0, + NULL, + NULL,NULL +}; +static float *test1_result=NULL; + +/* linear, full mapping, nonsequential */ +static_codebook test2={ + 4,3, + NULL, + 2, + -533200896,1611661312,4,0, + full_quantlist1, + NULL,NULL +}; +static float test2_result[]={-3,-2,-1,0, 1,2,3,4, 5,0,3,-2}; + +/* linear, full mapping, sequential */ +static_codebook test3={ + 4,3, + NULL, + 2, + -533200896,1611661312,4,1, + full_quantlist1, + NULL,NULL +}; +static float test3_result[]={-3,-5,-6,-6, 1,3,6,10, 5,5,8,6}; + +/* linear, algorithmic mapping, nonsequential */ +static_codebook test4={ + 3,27, + NULL, + 1, + -533200896,1611661312,4,0, + partial_quantlist1, + NULL,NULL +}; +static float test4_result[]={-3,-3,-3, 4,-3,-3, -1,-3,-3, + -3, 4,-3, 4, 4,-3, -1, 4,-3, + -3,-1,-3, 4,-1,-3, -1,-1,-3, + -3,-3, 4, 4,-3, 4, -1,-3, 4, + -3, 4, 4, 4, 4, 4, -1, 4, 4, + -3,-1, 4, 4,-1, 4, -1,-1, 4, + -3,-3,-1, 4,-3,-1, -1,-3,-1, + -3, 4,-1, 4, 4,-1, -1, 4,-1, + -3,-1,-1, 4,-1,-1, -1,-1,-1}; + +/* linear, algorithmic mapping, sequential */ +static_codebook test5={ + 3,27, + NULL, + 1, + -533200896,1611661312,4,1, + partial_quantlist1, + NULL,NULL +}; +static float test5_result[]={-3,-6,-9, 4, 1,-2, -1,-4,-7, + -3, 1,-2, 4, 8, 5, -1, 3, 0, + -3,-4,-7, 4, 3, 0, -1,-2,-5, + -3,-6,-2, 4, 1, 5, -1,-4, 0, + -3, 1, 5, 4, 8,12, -1, 3, 7, + -3,-4, 0, 4, 3, 7, -1,-2, 2, + -3,-6,-7, 4, 1, 0, -1,-4,-5, + -3, 1, 0, 4, 8, 7, -1, 3, 2, + -3,-4,-5, 4, 3, 2, -1,-2,-3}; + +void run_test(static_codebook *b,float *comp){ + float *out=_book_unquantize(b,b->entries,NULL); + int i; + + if(comp){ + if(!out){ + fprintf(stderr,"_book_unquantize incorrectly returned NULL\n"); + exit(1); + } + + for(i=0;ientries*b->dim;i++) + if(fabs(out[i]-comp[i])>.0001){ + fprintf(stderr,"disagreement in unquantized and reference data:\n" + "position %d, %g != %g\n",i,out[i],comp[i]); + exit(1); + } + + }else{ + if(out){ + fprintf(stderr,"_book_unquantize returned a value array: \n" + " correct result should have been NULL\n"); + exit(1); + } + } +} + +int main(){ + /* run the nine dequant tests, and compare to the hand-rolled results */ + fprintf(stderr,"Dequant test 1... "); + run_test(&test1,test1_result); + fprintf(stderr,"OK\nDequant test 2... "); + run_test(&test2,test2_result); + fprintf(stderr,"OK\nDequant test 3... "); + run_test(&test3,test3_result); + fprintf(stderr,"OK\nDequant test 4... "); + run_test(&test4,test4_result); + fprintf(stderr,"OK\nDequant test 5... "); + run_test(&test5,test5_result); + fprintf(stderr,"OK\n\n"); + + return(0); +} + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/smallft.c b/src/sound/oggvorbis/vorbissrc/smallft.c new file mode 100644 index 0000000..ec68344 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/smallft.c @@ -0,0 +1,1278 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: *unnormalized* fft transform + last mod: $Id: smallft.c,v 1.17 2002/07/11 06:40:50 xiphmont Exp $ + + ********************************************************************/ + +/* FFT implementation from OggSquish, minus cosine transforms, + * minus all but radix 2/4 case. In Vorbis we only need this + * cut-down version. + * + * To do more than just power-of-two sized vectors, see the full + * version I wrote for NetLib. + * + * Note that the packing is a little strange; rather than the FFT r/i + * packing following R_0, I_n, R_1, I_1, R_2, I_2 ... R_n-1, I_n-1, + * it follows R_0, R_1, I_1, R_2, I_2 ... R_n-1, I_n-1, I_n like the + * FORTRAN version + */ + +#include +#include +#include +#include "smallft.h" +#include "misc.h" + +static void drfti1(int n, float *wa, int *ifac){ + static int ntryh[4] = { 4,2,3,5 }; + static float tpi = 6.28318530717958648f; + float arg,argh,argld,fi; + int ntry=0,i,j=-1; + int k1, l1, l2, ib; + int ld, ii, ip, is, nq, nr; + int ido, ipm, nfm1; + int nl=n; + int nf=0; + + L101: + j++; + if (j < 4) + ntry=ntryh[j]; + else + ntry+=2; + + L104: + nq=nl/ntry; + nr=nl-ntry*nq; + if (nr!=0) goto L101; + + nf++; + ifac[nf+1]=ntry; + nl=nq; + if(ntry!=2)goto L107; + if(nf==1)goto L107; + + for (i=1;i>1; + ipp2=ip; + idp2=ido; + nbd=(ido-1)>>1; + t0=l1*ido; + t10=ip*ido; + + if(ido==1)goto L119; + for(ik=0;ikl1){ + for(j=1;j>1; + ipp2=ip; + ipph=(ip+1)>>1; + if(idol1)goto L139; + + is= -ido-1; + t1=0; + for(j=1;jn==1)return; + drftf1(l->n,data,l->trigcache,l->trigcache+l->n,l->splitcache); +} + +void drft_backward(drft_lookup *l,float *data){ + if (l->n==1)return; + drftb1(l->n,data,l->trigcache,l->trigcache+l->n,l->splitcache); +} + +void drft_init(drft_lookup *l,int n){ + l->n=n; + l->trigcache=_ogg_calloc(3*n,sizeof(*l->trigcache)); + l->splitcache=_ogg_calloc(32,sizeof(*l->splitcache)); + fdrffti(n, l->trigcache, l->splitcache); +} + +void drft_clear(drft_lookup *l){ + if(l){ + if(l->trigcache)_ogg_free(l->trigcache); + if(l->splitcache)_ogg_free(l->splitcache); + memset(l,0,sizeof(*l)); + } +} diff --git a/src/sound/oggvorbis/vorbissrc/smallft.h b/src/sound/oggvorbis/vorbissrc/smallft.h new file mode 100644 index 0000000..5f7cbd5 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/smallft.h @@ -0,0 +1,58 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: fft transform + last mod: $Id: smallft.h,v 1.12 2002/07/11 06:40:50 xiphmont Exp $ + + ********************************************************************/ + +#ifndef _V_SMFT_H_ +#define _V_SMFT_H_ + +#include "../vorbis/codec.h" + +typedef struct { + int n; + float *trigcache; + int *splitcache; +} drft_lookup; + +extern void drft_forward(drft_lookup *l,float *data); +extern void drft_backward(drft_lookup *l,float *data); +extern void drft_init(drft_lookup *l,int n); +extern void drft_clear(drft_lookup *l); + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/synthesis.c b/src/sound/oggvorbis/vorbissrc/synthesis.c new file mode 100644 index 0000000..6399541 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/synthesis.c @@ -0,0 +1,194 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: single-block PCM synthesis + last mod: $Id: synthesis.c,v 1.30 2003/08/18 05:34:01 xiphmont Exp $ + + ********************************************************************/ + +#include +#include "../ogg/ogg.h" +#include "../vorbis/codec.h" +#include "codec_internal.h" +#include "registry.h" +#include "misc.h" +#include "os.h" + +int vorbis_synthesis(vorbis_block *vb,ogg_packet *op){ + vorbis_dsp_state *vd=vb->vd; + private_state *b=vd->backend_state; + vorbis_info *vi=vd->vi; + codec_setup_info *ci=vi->codec_setup; + oggpack_buffer *opb=&vb->opb; + int type,mode,i; + + /* first things first. Make sure decode is ready */ + _vorbis_block_ripcord(vb); + oggpack_readinit(opb,op->packet,op->bytes); + + /* Check the packet type */ + if(oggpack_read(opb,1)!=0){ + /* Oops. This is not an audio data packet */ + return(OV_ENOTAUDIO); + } + + /* read our mode and pre/post windowsize */ + mode=oggpack_read(opb,b->modebits); + if(mode==-1)return(OV_EBADPACKET); + + vb->mode=mode; + vb->W=ci->mode_param[mode]->blockflag; + if(vb->W){ + + /* this doesn;t get mapped through mode selection as it's used + only for window selection */ + vb->lW=oggpack_read(opb,1); + vb->nW=oggpack_read(opb,1); + if(vb->nW==-1) return(OV_EBADPACKET); + }else{ + vb->lW=0; + vb->nW=0; + } + + /* more setup */ + vb->granulepos=op->granulepos; + vb->sequence=op->packetno; + vb->eofflag=op->e_o_s; + + /* alloc pcm passback storage */ + vb->pcmend=ci->blocksizes[vb->W]; + vb->pcm=_vorbis_block_alloc(vb,sizeof(*vb->pcm)*vi->channels); + for(i=0;ichannels;i++) + vb->pcm[i]=_vorbis_block_alloc(vb,vb->pcmend*sizeof(*vb->pcm[i])); + + /* unpack_header enforces range checking */ + type=ci->map_type[ci->mode_param[mode]->mapping]; + + return(_mapping_P[type]->inverse(vb,ci->map_param[ci->mode_param[mode]-> + mapping])); +} + +/* used to track pcm position without actually performing decode. + Useful for sequential 'fast forward' */ +int vorbis_synthesis_trackonly(vorbis_block *vb,ogg_packet *op){ + vorbis_dsp_state *vd=vb->vd; + private_state *b=vd->backend_state; + vorbis_info *vi=vd->vi; + codec_setup_info *ci=vi->codec_setup; + oggpack_buffer *opb=&vb->opb; + int mode; + + /* first things first. Make sure decode is ready */ + _vorbis_block_ripcord(vb); + oggpack_readinit(opb,op->packet,op->bytes); + + /* Check the packet type */ + if(oggpack_read(opb,1)!=0){ + /* Oops. This is not an audio data packet */ + return(OV_ENOTAUDIO); + } + + /* read our mode and pre/post windowsize */ + mode=oggpack_read(opb,b->modebits); + if(mode==-1)return(OV_EBADPACKET); + + vb->mode=mode; + vb->W=ci->mode_param[mode]->blockflag; + if(vb->W){ + vb->lW=oggpack_read(opb,1); + vb->nW=oggpack_read(opb,1); + if(vb->nW==-1) return(OV_EBADPACKET); + }else{ + vb->lW=0; + vb->nW=0; + } + + /* more setup */ + vb->granulepos=op->granulepos; + vb->sequence=op->packetno; + vb->eofflag=op->e_o_s; + + /* no pcm */ + vb->pcmend=0; + vb->pcm=NULL; + + return(0); +} + +long vorbis_packet_blocksize(vorbis_info *vi,ogg_packet *op){ + codec_setup_info *ci=vi->codec_setup; + oggpack_buffer opb; + int mode; + + oggpack_readinit(&opb,op->packet,op->bytes); + + /* Check the packet type */ + if(oggpack_read(&opb,1)!=0){ + /* Oops. This is not an audio data packet */ + return(OV_ENOTAUDIO); + } + + { + int modebits=0; + int v=ci->modes; + while(v>1){ + modebits++; + v>>=1; + } + + /* read our mode and pre/post windowsize */ + mode=oggpack_read(&opb,modebits); + } + if(mode==-1)return(OV_EBADPACKET); + return(ci->blocksizes[ci->mode_param[mode]->blockflag]); +} + +int vorbis_synthesis_halfrate(vorbis_info *vi,int flag){ + /* set / clear half-sample-rate mode */ + codec_setup_info *ci=vi->codec_setup; + + /* right now, our MDCT can't handle < 64 sample windows. */ + if(ci->blocksizes[0]<=64 && flag)return -1; + ci->halfrate_flag=(flag?1:0); + return 0; +} + +int vorbis_synthesis_halfrate_p(vorbis_info *vi){ + codec_setup_info *ci=vi->codec_setup; + return ci->halfrate_flag; +} + + diff --git a/src/sound/oggvorbis/vorbissrc/vorbisenc.c b/src/sound/oggvorbis/vorbissrc/vorbisenc.c new file mode 100644 index 0000000..82e6ae6 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/vorbisenc.c @@ -0,0 +1,1158 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: simple programmatic interface for encoder mode setup + last mod: $Id: vorbisenc.c,v 1.47 2002/07/11 06:40:50 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include + +#include "../vorbis/codec.h" +#include "../vorbis/vorbisenc.h" + +#include "codec_internal.h" + +#include "os.h" +#include "misc.h" + +/* careful with this; it's using static array sizing to make managing + all the modes a little less annoying. If we use a residue backend + with > 12 partition types, or a different division of iteration, + this needs to be updated. */ +typedef struct { + static_codebook *books[12][3]; +} static_bookblock; + +typedef struct { + int res_type; + int limit_type; /* 0 lowpass limited, 1 point stereo limited */ + vorbis_info_residue0 *res; + static_codebook *book_aux; + static_codebook *book_aux_managed; + static_bookblock *books_base; + static_bookblock *books_base_managed; +} vorbis_residue_template; + +typedef struct { + vorbis_info_mapping0 *map; + vorbis_residue_template *res; +} vorbis_mapping_template; + +typedef struct vp_adjblock{ + int block[P_BANDS]; +} vp_adjblock; + +typedef struct { + int data[NOISE_COMPAND_LEVELS]; +} compandblock; + +/* high level configuration information for setting things up + step-by-step with the detailed vorbis_encode_ctl interface. + There's a fair amount of redundancy such that interactive setup + does not directly deal with any vorbis_info or codec_setup_info + initialization; it's all stored (until full init) in this highlevel + setup, then flushed out to the real codec setup structs later. */ + +typedef struct { + int att[P_NOISECURVES]; + float boost; + float decay; +} att3; +typedef struct { int data[P_NOISECURVES]; } adj3; + +typedef struct { + int pre[PACKETBLOBS]; + int post[PACKETBLOBS]; + float kHz[PACKETBLOBS]; + float lowpasskHz[PACKETBLOBS]; +} adj_stereo; + +typedef struct { + int lo; + int hi; + int fixed; +} noiseguard; +typedef struct { + int data[P_NOISECURVES][17]; +} noise3; + +typedef struct { + int mappings; + double *rate_mapping; + double *quality_mapping; + int coupling_restriction; + long samplerate_min_restriction; + long samplerate_max_restriction; + + + int *blocksize_short; + int *blocksize_long; + + att3 *psy_tone_masteratt; + int *psy_tone_0dB; + int *psy_tone_dBsuppress; + + vp_adjblock *psy_tone_adj_impulse; + vp_adjblock *psy_tone_adj_long; + vp_adjblock *psy_tone_adj_other; + + noiseguard *psy_noiseguards; + noise3 *psy_noise_bias_impulse; + noise3 *psy_noise_bias_padding; + noise3 *psy_noise_bias_trans; + noise3 *psy_noise_bias_long; + int *psy_noise_dBsuppress; + + compandblock *psy_noise_compand; + double *psy_noise_compand_short_mapping; + double *psy_noise_compand_long_mapping; + + int *psy_noise_normal_start[2]; + int *psy_noise_normal_partition[2]; + double *psy_noise_normal_thresh; + + int *psy_ath_float; + int *psy_ath_abs; + + double *psy_lowpass; + + vorbis_info_psy_global *global_params; + double *global_mapping; + adj_stereo *stereo_modes; + + static_codebook ***floor_books; + vorbis_info_floor1 *floor_params; + int *floor_short_mapping; + int *floor_long_mapping; + + vorbis_mapping_template *maps; +} ve_setup_data_template; + +/* a few static coder conventions */ +static vorbis_info_mode _mode_template[2]={ + {0,0,0,0}, + {1,0,0,1} +}; + +static vorbis_info_mapping0 _map_nominal[2]={ + {1, {0,0}, {0}, {0}, 1,{0},{1}}, + {1, {0,0}, {1}, {1}, 1,{0},{1}} +}; + +#include "modes/setup_44.h" +#include "modes/setup_44u.h" +#include "modes/setup_32.h" +#include "modes/setup_8.h" +#include "modes/setup_11.h" +#include "modes/setup_16.h" +#include "modes/setup_22.h" +#include "modes/setup_X.h" + +static ve_setup_data_template *setup_list[]={ + &ve_setup_44_stereo, + &ve_setup_44_stereo_low, + &ve_setup_44_uncoupled, + &ve_setup_44_uncoupled_low, + + &ve_setup_32_stereo, + &ve_setup_32_stereo_low, + &ve_setup_32_uncoupled, + &ve_setup_32_uncoupled_low, + + &ve_setup_22_stereo, + &ve_setup_22_uncoupled, + &ve_setup_16_stereo, + &ve_setup_16_uncoupled, + + &ve_setup_11_stereo, + &ve_setup_11_uncoupled, + &ve_setup_8_stereo, + &ve_setup_8_uncoupled, + + &ve_setup_X_stereo, + &ve_setup_X_uncoupled, + &ve_setup_X_stereo_low, + &ve_setup_X_uncoupled_low, + &ve_setup_XX_stereo, + &ve_setup_XX_uncoupled, + 0 +}; + +static int vorbis_encode_toplevel_setup(vorbis_info *vi,int ch,long rate){ + if(vi && vi->codec_setup){ + + vi->version=0; + vi->channels=ch; + vi->rate=rate; + + return(0); + } + return(OV_EINVAL); +} + +static void vorbis_encode_floor_setup(vorbis_info *vi,double s,int block, + static_codebook ***books, + vorbis_info_floor1 *in, + int *x){ + int i,k,is=s; + vorbis_info_floor1 *f=_ogg_calloc(1,sizeof(*f)); + codec_setup_info *ci=vi->codec_setup; + + memcpy(f,in+x[is],sizeof(*f)); + /* fill in the lowpass field, even if it's temporary */ + f->n=ci->blocksizes[block]>>1; + + /* books */ + { + int partitions=f->partitions; + int maxclass=-1; + int maxbook=-1; + for(i=0;ipartitionclass[i]>maxclass)maxclass=f->partitionclass[i]; + for(i=0;i<=maxclass;i++){ + if(f->class_book[i]>maxbook)maxbook=f->class_book[i]; + f->class_book[i]+=ci->books; + for(k=0;k<(1<class_subs[i]);k++){ + if(f->class_subbook[i][k]>maxbook)maxbook=f->class_subbook[i][k]; + if(f->class_subbook[i][k]>=0)f->class_subbook[i][k]+=ci->books; + } + } + + for(i=0;i<=maxbook;i++) + ci->book_param[ci->books++]=books[x[is]][i]; + } + + /* for now, we're only using floor 1 */ + ci->floor_type[ci->floors]=1; + ci->floor_param[ci->floors]=f; + ci->floors++; + + return; +} + +static void vorbis_encode_global_psych_setup(vorbis_info *vi,double s, + vorbis_info_psy_global *in, + double *x){ + int i,is=s; + double ds=s-is; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy_global *g=&ci->psy_g_param; + + memcpy(g,in+(int)x[is],sizeof(*g)); + + ds=x[is]*(1.-ds)+x[is+1]*ds; + is=(int)ds; + ds-=is; + if(ds==0 && is>0){ + is--; + ds=1.; + } + + /* interpolate the trigger threshholds */ + for(i=0;i<4;i++){ + g->preecho_thresh[i]=in[is].preecho_thresh[i]*(1.-ds)+in[is+1].preecho_thresh[i]*ds; + g->postecho_thresh[i]=in[is].postecho_thresh[i]*(1.-ds)+in[is+1].postecho_thresh[i]*ds; + } + g->ampmax_att_per_sec=ci->hi.amplitude_track_dBpersec; + return; +} + +static void vorbis_encode_global_stereo(vorbis_info *vi, + highlevel_encode_setup *hi, + adj_stereo *p){ + float s=hi->stereo_point_setting; + int i,is=s; + double ds=s-is; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy_global *g=&ci->psy_g_param; + + if(p){ + memcpy(g->coupling_prepointamp,p[is].pre,sizeof(*p[is].pre)*PACKETBLOBS); + memcpy(g->coupling_postpointamp,p[is].post,sizeof(*p[is].post)*PACKETBLOBS); + + if(hi->managed){ + /* interpolate the kHz threshholds */ + for(i=0;icoupling_pointlimit[0][i]=kHz*1000./vi->rate*ci->blocksizes[0]; + g->coupling_pointlimit[1][i]=kHz*1000./vi->rate*ci->blocksizes[1]; + g->coupling_pkHz[i]=kHz; + + kHz=p[is].lowpasskHz[i]*(1.-ds)+p[is+1].lowpasskHz[i]*ds; + g->sliding_lowpass[0][i]=kHz*1000./vi->rate*ci->blocksizes[0]; + g->sliding_lowpass[1][i]=kHz*1000./vi->rate*ci->blocksizes[1]; + + } + }else{ + float kHz=p[is].kHz[PACKETBLOBS/2]*(1.-ds)+p[is+1].kHz[PACKETBLOBS/2]*ds; + for(i=0;icoupling_pointlimit[0][i]=kHz*1000./vi->rate*ci->blocksizes[0]; + g->coupling_pointlimit[1][i]=kHz*1000./vi->rate*ci->blocksizes[1]; + g->coupling_pkHz[i]=kHz; + } + + kHz=p[is].lowpasskHz[PACKETBLOBS/2]*(1.-ds)+p[is+1].lowpasskHz[PACKETBLOBS/2]*ds; + for(i=0;isliding_lowpass[0][i]=kHz*1000./vi->rate*ci->blocksizes[0]; + g->sliding_lowpass[1][i]=kHz*1000./vi->rate*ci->blocksizes[1]; + } + } + }else{ + for(i=0;isliding_lowpass[0][i]=ci->blocksizes[0]; + g->sliding_lowpass[1][i]=ci->blocksizes[1]; + } + } + return; +} + +static void vorbis_encode_psyset_setup(vorbis_info *vi,double s, + int *nn_start, + int *nn_partition, + double *nn_thresh, + int block){ + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy *p=ci->psy_param[block]; + highlevel_encode_setup *hi=&ci->hi; + int is=s; + + if(block>=ci->psys) + ci->psys=block+1; + if(!p){ + p=_ogg_calloc(1,sizeof(*p)); + ci->psy_param[block]=p; + } + + memcpy(p,&_psy_info_template,sizeof(*p)); + p->blockflag=block>>1; + + if(hi->noise_normalize_p){ + p->normal_channel_p=1; + p->normal_point_p=1; + p->normal_start=nn_start[is]; + p->normal_partition=nn_partition[is]; + p->normal_thresh=nn_thresh[is]; + } + + return; +} + +static void vorbis_encode_tonemask_setup(vorbis_info *vi,double s,int block, + att3 *att, + int *max, + vp_adjblock *in){ + int i,is=s; + double ds=s-is; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy *p=ci->psy_param[block]; + + /* 0 and 2 are only used by bitmanagement, but there's no harm to always + filling the values in here */ + p->tone_masteratt[0]=att[is].att[0]*(1.-ds)+att[is+1].att[0]*ds; + p->tone_masteratt[1]=att[is].att[1]*(1.-ds)+att[is+1].att[1]*ds; + p->tone_masteratt[2]=att[is].att[2]*(1.-ds)+att[is+1].att[2]*ds; + p->tone_centerboost=att[is].boost*(1.-ds)+att[is+1].boost*ds; + p->tone_decay=att[is].decay*(1.-ds)+att[is+1].decay*ds; + + p->max_curve_dB=max[is]*(1.-ds)+max[is+1]*ds; + + for(i=0;itoneatt[i]=in[is].block[i]*(1.-ds)+in[is+1].block[i]*ds; + return; +} + + +static void vorbis_encode_compand_setup(vorbis_info *vi,double s,int block, + compandblock *in, double *x){ + int i,is=s; + double ds=s-is; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy *p=ci->psy_param[block]; + + ds=x[is]*(1.-ds)+x[is+1]*ds; + is=(int)ds; + ds-=is; + if(ds==0 && is>0){ + is--; + ds=1.; + } + + /* interpolate the compander settings */ + for(i=0;inoisecompand[i]=in[is].data[i]*(1.-ds)+in[is+1].data[i]*ds; + return; +} + +static void vorbis_encode_peak_setup(vorbis_info *vi,double s,int block, + int *suppress){ + int is=s; + double ds=s-is; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy *p=ci->psy_param[block]; + + p->tone_abs_limit=suppress[is]*(1.-ds)+suppress[is+1]*ds; + + return; +} + +static void vorbis_encode_noisebias_setup(vorbis_info *vi,double s,int block, + int *suppress, + noise3 *in, + noiseguard *guard, + double userbias){ + int i,is=s,j; + double ds=s-is; + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy *p=ci->psy_param[block]; + + p->noisemaxsupp=suppress[is]*(1.-ds)+suppress[is+1]*ds; + p->noisewindowlomin=guard[block].lo; + p->noisewindowhimin=guard[block].hi; + p->noisewindowfixed=guard[block].fixed; + + for(j=0;jnoiseoff[j][i]=in[is].data[j][i]*(1.-ds)+in[is+1].data[j][i]*ds; + + /* impulse blocks may take a user specified bias to boost the + nominal/high noise encoding depth */ + for(j=0;jnoiseoff[j][0]+6; /* the lowest it can go */ + for(i=0;inoiseoff[j][i]+=userbias; + if(p->noiseoff[j][i]noiseoff[j][i]=min; + } + } + + return; +} + +static void vorbis_encode_ath_setup(vorbis_info *vi,int block){ + codec_setup_info *ci=vi->codec_setup; + vorbis_info_psy *p=ci->psy_param[block]; + + p->ath_adjatt=ci->hi.ath_floating_dB; + p->ath_maxatt=ci->hi.ath_absolute_dB; + return; +} + + +static int book_dup_or_new(codec_setup_info *ci,static_codebook *book){ + int i; + for(i=0;ibooks;i++) + if(ci->book_param[i]==book)return(i); + + return(ci->books++); +} + +static void vorbis_encode_blocksize_setup(vorbis_info *vi,double s, + int *shortb,int *longb){ + + codec_setup_info *ci=vi->codec_setup; + int is=s; + + int blockshort=shortb[is]; + int blocklong=longb[is]; + ci->blocksizes[0]=blockshort; + ci->blocksizes[1]=blocklong; + +} + +static void vorbis_encode_residue_setup(vorbis_info *vi, + int number, int block, + vorbis_residue_template *res){ + + codec_setup_info *ci=vi->codec_setup; + int i,n; + + vorbis_info_residue0 *r=ci->residue_param[number]= + _ogg_malloc(sizeof(*r)); + + memcpy(r,res->res,sizeof(*r)); + if(ci->residues<=number)ci->residues=number+1; + + switch(ci->blocksizes[block]){ + case 64:case 128:case 256: + r->grouping=16; + break; + default: + r->grouping=32; + break; + } + ci->residue_type[number]=res->res_type; + + /* to be adjusted by lowpass/pointlimit later */ + n=r->end=ci->blocksizes[block]>>1; + if(res->res_type==2) + n=r->end*=vi->channels; + + /* fill in all the books */ + { + int booklist=0,k; + + if(ci->hi.managed){ + for(i=0;ipartitions;i++) + for(k=0;k<3;k++) + if(res->books_base_managed->books[i][k]) + r->secondstages[i]|=(1<groupbook=book_dup_or_new(ci,res->book_aux_managed); + ci->book_param[r->groupbook]=res->book_aux_managed; + + for(i=0;ipartitions;i++){ + for(k=0;k<3;k++){ + if(res->books_base_managed->books[i][k]){ + int bookid=book_dup_or_new(ci,res->books_base_managed->books[i][k]); + r->booklist[booklist++]=bookid; + ci->book_param[bookid]=res->books_base_managed->books[i][k]; + } + } + } + + }else{ + + for(i=0;ipartitions;i++) + for(k=0;k<3;k++) + if(res->books_base->books[i][k]) + r->secondstages[i]|=(1<groupbook=book_dup_or_new(ci,res->book_aux); + ci->book_param[r->groupbook]=res->book_aux; + + for(i=0;ipartitions;i++){ + for(k=0;k<3;k++){ + if(res->books_base->books[i][k]){ + int bookid=book_dup_or_new(ci,res->books_base->books[i][k]); + r->booklist[booklist++]=bookid; + ci->book_param[bookid]=res->books_base->books[i][k]; + } + } + } + } + } + + /* lowpass setup/pointlimit */ + { + double freq=ci->hi.lowpass_kHz*1000.; + vorbis_info_floor1 *f=ci->floor_param[block]; /* by convention */ + double nyq=vi->rate/2.; + long blocksize=ci->blocksizes[block]>>1; + + /* lowpass needs to be set in the floor and the residue. */ + if(freq>nyq)freq=nyq; + /* in the floor, the granularity can be very fine; it doesn't alter + the encoding structure, only the samples used to fit the floor + approximation */ + f->n=freq/nyq*blocksize; + + /* this res may by limited by the maximum pointlimit of the mode, + not the lowpass. the floor is always lowpass limited. */ + if(res->limit_type){ + if(ci->hi.managed) + freq=ci->psy_g_param.coupling_pkHz[PACKETBLOBS-1]*1000.; + else + freq=ci->psy_g_param.coupling_pkHz[PACKETBLOBS/2]*1000.; + if(freq>nyq)freq=nyq; + } + + /* in the residue, we're constrained, physically, by partition + boundaries. We still lowpass 'wherever', but we have to round up + here to next boundary, or the vorbis spec will round it *down* to + previous boundary in encode/decode */ + if(ci->residue_type[block]==2) + r->end=(int)((freq/nyq*blocksize*2)/r->grouping+.9)* /* round up only if we're well past */ + r->grouping; + else + r->end=(int)((freq/nyq*blocksize)/r->grouping+.9)* /* round up only if we're well past */ + r->grouping; + } +} + +/* we assume two maps in this encoder */ +static void vorbis_encode_map_n_res_setup(vorbis_info *vi,double s, + vorbis_mapping_template *maps){ + + codec_setup_info *ci=vi->codec_setup; + int i,j,is=s,modes=2; + vorbis_info_mapping0 *map=maps[is].map; + vorbis_info_mode *mode=_mode_template; + vorbis_residue_template *res=maps[is].res; + + if(ci->blocksizes[0]==ci->blocksizes[1])modes=1; + + for(i=0;imap_param[i]=_ogg_calloc(1,sizeof(*map)); + ci->mode_param[i]=_ogg_calloc(1,sizeof(*mode)); + + memcpy(ci->mode_param[i],mode+i,sizeof(*_mode_template)); + if(i>=ci->modes)ci->modes=i+1; + + ci->map_type[i]=0; + memcpy(ci->map_param[i],map+i,sizeof(*map)); + if(i>=ci->maps)ci->maps=i+1; + + for(j=0;jcodec_setup; + highlevel_encode_setup *hi=&ci->hi; + ve_setup_data_template *setup=(ve_setup_data_template *)hi->setup; + int is=hi->base_setting; + double ds=hi->base_setting-is; + int ch=vi->channels; + double *r=setup->rate_mapping; + + if(r==NULL) + return(-1); + + return((r[is]*(1.-ds)+r[is+1]*ds)*ch); +} + +static void get_setup_template(vorbis_info *vi, + long ch,long srate, + double req,int q_or_bitrate){ + int i=0,j; + codec_setup_info *ci=vi->codec_setup; + highlevel_encode_setup *hi=&ci->hi; + if(q_or_bitrate)req/=ch; + + while(setup_list[i]){ + if(setup_list[i]->coupling_restriction==-1 || + setup_list[i]->coupling_restriction==ch){ + if(srate>=setup_list[i]->samplerate_min_restriction && + srate<=setup_list[i]->samplerate_max_restriction){ + int mappings=setup_list[i]->mappings; + double *map=(q_or_bitrate? + setup_list[i]->rate_mapping: + setup_list[i]->quality_mapping); + + /* the template matches. Does the requested quality mode + fall within this template's modes? */ + if(reqmap[setup_list[i]->mappings]){++i;continue;} + for(j=0;j=map[j] && reqsetup=setup_list[i]; + if(j==mappings) + hi->base_setting=j-.001; + else{ + float low=map[j]; + float high=map[j+1]; + float del=(req-low)/(high-low); + hi->base_setting=j+del; + } + return; + } + } + i++; + } + + hi->setup=NULL; +} + +/* encoders will need to use vorbis_info_init beforehand and call + vorbis_info clear when all done */ + +/* two interfaces; this, more detailed one, and later a convenience + layer on top */ + +/* the final setup call */ +int vorbis_encode_setup_init(vorbis_info *vi){ + int i0=0,singleblock=0; + codec_setup_info *ci=vi->codec_setup; + ve_setup_data_template *setup=NULL; + highlevel_encode_setup *hi=&ci->hi; + + if(ci==NULL)return(OV_EINVAL); + if(!hi->impulse_block_p)i0=1; + + /* too low/high an ATH floater is nonsensical, but doesn't break anything */ + if(hi->ath_floating_dB>-80)hi->ath_floating_dB=-80; + if(hi->ath_floating_dB<-200)hi->ath_floating_dB=-200; + + /* again, bound this to avoid the app shooting itself int he foot + too badly */ + if(hi->amplitude_track_dBpersec>0.)hi->amplitude_track_dBpersec=0.; + if(hi->amplitude_track_dBpersec<-99999.)hi->amplitude_track_dBpersec=-99999.; + + /* get the appropriate setup template; matches the fetch in previous + stages */ + setup=(ve_setup_data_template *)hi->setup; + if(setup==NULL)return(OV_EINVAL); + + hi->set_in_stone=1; + /* choose block sizes from configured sizes as well as paying + attention to long_block_p and short_block_p. If the configured + short and long blocks are the same length, we set long_block_p + and unset short_block_p */ + vorbis_encode_blocksize_setup(vi,hi->base_setting, + setup->blocksize_short, + setup->blocksize_long); + if(ci->blocksizes[0]==ci->blocksizes[1])singleblock=1; + + /* floor setup; choose proper floor params. Allocated on the floor + stack in order; if we alloc only long floor, it's 0 */ + vorbis_encode_floor_setup(vi,hi->short_setting,0, + setup->floor_books, + setup->floor_params, + setup->floor_short_mapping); + if(!singleblock) + vorbis_encode_floor_setup(vi,hi->long_setting,1, + setup->floor_books, + setup->floor_params, + setup->floor_long_mapping); + + /* setup of [mostly] short block detection and stereo*/ + vorbis_encode_global_psych_setup(vi,hi->trigger_setting, + setup->global_params, + setup->global_mapping); + vorbis_encode_global_stereo(vi,hi,setup->stereo_modes); + + /* basic psych setup and noise normalization */ + vorbis_encode_psyset_setup(vi,hi->short_setting, + setup->psy_noise_normal_start[0], + setup->psy_noise_normal_partition[0], + setup->psy_noise_normal_thresh, + 0); + vorbis_encode_psyset_setup(vi,hi->short_setting, + setup->psy_noise_normal_start[0], + setup->psy_noise_normal_partition[0], + setup->psy_noise_normal_thresh, + 1); + if(!singleblock){ + vorbis_encode_psyset_setup(vi,hi->long_setting, + setup->psy_noise_normal_start[1], + setup->psy_noise_normal_partition[1], + setup->psy_noise_normal_thresh, + 2); + vorbis_encode_psyset_setup(vi,hi->long_setting, + setup->psy_noise_normal_start[1], + setup->psy_noise_normal_partition[1], + setup->psy_noise_normal_thresh, + 3); + } + + /* tone masking setup */ + vorbis_encode_tonemask_setup(vi,hi->block[i0].tone_mask_setting,0, + setup->psy_tone_masteratt, + setup->psy_tone_0dB, + setup->psy_tone_adj_impulse); + vorbis_encode_tonemask_setup(vi,hi->block[1].tone_mask_setting,1, + setup->psy_tone_masteratt, + setup->psy_tone_0dB, + setup->psy_tone_adj_other); + if(!singleblock){ + vorbis_encode_tonemask_setup(vi,hi->block[2].tone_mask_setting,2, + setup->psy_tone_masteratt, + setup->psy_tone_0dB, + setup->psy_tone_adj_other); + vorbis_encode_tonemask_setup(vi,hi->block[3].tone_mask_setting,3, + setup->psy_tone_masteratt, + setup->psy_tone_0dB, + setup->psy_tone_adj_long); + } + + /* noise companding setup */ + vorbis_encode_compand_setup(vi,hi->block[i0].noise_compand_setting,0, + setup->psy_noise_compand, + setup->psy_noise_compand_short_mapping); + vorbis_encode_compand_setup(vi,hi->block[1].noise_compand_setting,1, + setup->psy_noise_compand, + setup->psy_noise_compand_short_mapping); + if(!singleblock){ + vorbis_encode_compand_setup(vi,hi->block[2].noise_compand_setting,2, + setup->psy_noise_compand, + setup->psy_noise_compand_long_mapping); + vorbis_encode_compand_setup(vi,hi->block[3].noise_compand_setting,3, + setup->psy_noise_compand, + setup->psy_noise_compand_long_mapping); + } + + /* peak guarding setup */ + vorbis_encode_peak_setup(vi,hi->block[i0].tone_peaklimit_setting,0, + setup->psy_tone_dBsuppress); + vorbis_encode_peak_setup(vi,hi->block[1].tone_peaklimit_setting,1, + setup->psy_tone_dBsuppress); + if(!singleblock){ + vorbis_encode_peak_setup(vi,hi->block[2].tone_peaklimit_setting,2, + setup->psy_tone_dBsuppress); + vorbis_encode_peak_setup(vi,hi->block[3].tone_peaklimit_setting,3, + setup->psy_tone_dBsuppress); + } + + /* noise bias setup */ + vorbis_encode_noisebias_setup(vi,hi->block[i0].noise_bias_setting,0, + setup->psy_noise_dBsuppress, + setup->psy_noise_bias_impulse, + setup->psy_noiseguards, + (i0==0?hi->impulse_noisetune:0.)); + vorbis_encode_noisebias_setup(vi,hi->block[1].noise_bias_setting,1, + setup->psy_noise_dBsuppress, + setup->psy_noise_bias_padding, + setup->psy_noiseguards,0.); + if(!singleblock){ + vorbis_encode_noisebias_setup(vi,hi->block[2].noise_bias_setting,2, + setup->psy_noise_dBsuppress, + setup->psy_noise_bias_trans, + setup->psy_noiseguards,0.); + vorbis_encode_noisebias_setup(vi,hi->block[3].noise_bias_setting,3, + setup->psy_noise_dBsuppress, + setup->psy_noise_bias_long, + setup->psy_noiseguards,0.); + } + + vorbis_encode_ath_setup(vi,0); + vorbis_encode_ath_setup(vi,1); + if(!singleblock){ + vorbis_encode_ath_setup(vi,2); + vorbis_encode_ath_setup(vi,3); + } + + vorbis_encode_map_n_res_setup(vi,hi->base_setting,setup->maps); + + /* set bitrate readonlies and management */ + vi->bitrate_nominal=setting_to_approx_bitrate(vi); + vi->bitrate_lower=hi->bitrate_min; + vi->bitrate_upper=hi->bitrate_max; + vi->bitrate_window=hi->bitrate_limit_window; + + if(hi->managed){ + ci->bi.queue_avg_time=hi->bitrate_av_window; + ci->bi.queue_avg_center=hi->bitrate_av_window_center; + ci->bi.queue_minmax_time=hi->bitrate_limit_window; + ci->bi.queue_hardmin=hi->bitrate_min; + ci->bi.queue_hardmax=hi->bitrate_max; + ci->bi.queue_avgmin=hi->bitrate_av_lo; + ci->bi.queue_avgmax=hi->bitrate_av_hi; + ci->bi.avgfloat_downslew_max=-999999.f; + ci->bi.avgfloat_upslew_max=999999.f; + } + + return(0); + +} + +static int vorbis_encode_setup_setting(vorbis_info *vi, + long channels, + long rate){ + int ret=0,i,is; + codec_setup_info *ci=vi->codec_setup; + highlevel_encode_setup *hi=&ci->hi; + ve_setup_data_template *setup=hi->setup; + double ds; + + ret=vorbis_encode_toplevel_setup(vi,channels,rate); + if(ret)return(ret); + + is=hi->base_setting; + ds=hi->base_setting-is; + + hi->short_setting=hi->base_setting; + hi->long_setting=hi->base_setting; + + hi->managed=0; + + hi->impulse_block_p=1; + hi->noise_normalize_p=1; + + hi->stereo_point_setting=hi->base_setting; + hi->lowpass_kHz= + setup->psy_lowpass[is]*(1.-ds)+setup->psy_lowpass[is+1]*ds; + + hi->ath_floating_dB=setup->psy_ath_float[is]*(1.-ds)+ + setup->psy_ath_float[is+1]*ds; + hi->ath_absolute_dB=setup->psy_ath_abs[is]*(1.-ds)+ + setup->psy_ath_abs[is+1]*ds; + + hi->amplitude_track_dBpersec=-6.; + hi->trigger_setting=hi->base_setting; + + for(i=0;i<4;i++){ + hi->block[i].tone_mask_setting=hi->base_setting; + hi->block[i].tone_peaklimit_setting=hi->base_setting; + hi->block[i].noise_bias_setting=hi->base_setting; + hi->block[i].noise_compand_setting=hi->base_setting; + } + + return(ret); +} + +int vorbis_encode_setup_vbr(vorbis_info *vi, + long channels, + long rate, + float quality){ + codec_setup_info *ci=vi->codec_setup; + highlevel_encode_setup *hi=&ci->hi; + + quality+=.00001; + if(quality>=1.)quality=.9999; + + get_setup_template(vi,channels,rate,quality,0); + if(!hi->setup)return OV_EIMPL; + + return vorbis_encode_setup_setting(vi,channels,rate); +} + +int vorbis_encode_init_vbr(vorbis_info *vi, + long channels, + long rate, + + float base_quality /* 0. to 1. */ + ){ + int ret=0; + + ret=vorbis_encode_setup_vbr(vi,channels,rate,base_quality); + + if(ret){ + vorbis_info_clear(vi); + return ret; + } + ret=vorbis_encode_setup_init(vi); + if(ret) + vorbis_info_clear(vi); + return(ret); +} + +int vorbis_encode_setup_managed(vorbis_info *vi, + long channels, + long rate, + + long max_bitrate, + long nominal_bitrate, + long min_bitrate){ + + codec_setup_info *ci=vi->codec_setup; + highlevel_encode_setup *hi=&ci->hi; + double tnominal=nominal_bitrate; + int ret=0; + + if(nominal_bitrate<=0.){ + if(max_bitrate>0.){ + nominal_bitrate=max_bitrate*.875; + }else{ + if(min_bitrate>0.){ + nominal_bitrate=min_bitrate; + }else{ + return(OV_EINVAL); + } + } + } + + get_setup_template(vi,channels,rate,nominal_bitrate,1); + if(!hi->setup)return OV_EIMPL; + + ret=vorbis_encode_setup_setting(vi,channels,rate); + if(ret){ + vorbis_info_clear(vi); + return ret; + } + + /* initialize management with sane defaults */ + /* initialize management with sane defaults */ + hi->managed=1; + hi->bitrate_av_window=4.; + hi->bitrate_av_window_center=.5; + hi->bitrate_limit_window=2.; + hi->bitrate_min=min_bitrate; + hi->bitrate_max=max_bitrate; + hi->bitrate_av_lo=tnominal; + hi->bitrate_av_hi=tnominal; + + return(ret); + +} + +int vorbis_encode_init(vorbis_info *vi, + long channels, + long rate, + + long max_bitrate, + long nominal_bitrate, + long min_bitrate){ + + int ret=vorbis_encode_setup_managed(vi,channels,rate, + max_bitrate, + nominal_bitrate, + min_bitrate); + if(ret){ + vorbis_info_clear(vi); + return(ret); + } + + ret=vorbis_encode_setup_init(vi); + if(ret) + vorbis_info_clear(vi); + return(ret); +} + +int vorbis_encode_ctl(vorbis_info *vi,int number,void *arg){ + if(vi){ + codec_setup_info *ci=vi->codec_setup; + highlevel_encode_setup *hi=&ci->hi; + int setp=(number&0xf); /* a read request has a low nibble of 0 */ + + if(setp && hi->set_in_stone)return(OV_EINVAL); + + switch(number){ + case OV_ECTL_RATEMANAGE_GET: + { + + struct ovectl_ratemanage_arg *ai= + (struct ovectl_ratemanage_arg *)arg; + + ai->management_active=hi->managed; + ai->bitrate_av_window=hi->bitrate_av_window; + ai->bitrate_av_window_center=hi->bitrate_av_window_center; + ai->bitrate_hard_window=hi->bitrate_limit_window; + ai->bitrate_hard_min=hi->bitrate_min; + ai->bitrate_hard_max=hi->bitrate_max; + ai->bitrate_av_lo=hi->bitrate_av_lo; + ai->bitrate_av_hi=hi->bitrate_av_hi; + + } + return(0); + + case OV_ECTL_RATEMANAGE_SET: + { + struct ovectl_ratemanage_arg *ai= + (struct ovectl_ratemanage_arg *)arg; + if(ai==NULL){ + hi->managed=0; + }else{ + hi->managed=ai->management_active; + vorbis_encode_ctl(vi,OV_ECTL_RATEMANAGE_AVG,arg); + vorbis_encode_ctl(vi,OV_ECTL_RATEMANAGE_HARD,arg); + } + } + return 0; + + case OV_ECTL_RATEMANAGE_AVG: + { + struct ovectl_ratemanage_arg *ai= + (struct ovectl_ratemanage_arg *)arg; + if(ai==NULL){ + hi->bitrate_av_lo=0; + hi->bitrate_av_hi=0; + hi->bitrate_av_window=0; + }else{ + hi->bitrate_av_window=ai->bitrate_av_window; + hi->bitrate_av_window_center=ai->bitrate_av_window_center; + hi->bitrate_av_lo=ai->bitrate_av_lo; + hi->bitrate_av_hi=ai->bitrate_av_hi; + } + + if(hi->bitrate_av_window<.25)hi->bitrate_av_window=.25; + if(hi->bitrate_av_window>10.)hi->bitrate_av_window=10.; + if(hi->bitrate_av_window_center<0.)hi->bitrate_av_window=0.; + if(hi->bitrate_av_window_center>1.)hi->bitrate_av_window=1.; + + if( ( (hi->bitrate_av_lo<=0 && hi->bitrate_av_hi<=0)|| + (hi->bitrate_av_window<=0) ) && + ( (hi->bitrate_min<=0 && hi->bitrate_max<=0)|| + (hi->bitrate_limit_window<=0) )) + hi->managed=0; + } + return(0); + case OV_ECTL_RATEMANAGE_HARD: + { + struct ovectl_ratemanage_arg *ai= + (struct ovectl_ratemanage_arg *)arg; + if(ai==NULL){ + hi->bitrate_min=0; + hi->bitrate_max=0; + hi->bitrate_limit_window=0; + }else{ + hi->bitrate_limit_window=ai->bitrate_hard_window; + hi->bitrate_min=ai->bitrate_hard_min; + hi->bitrate_max=ai->bitrate_hard_max; + } + if(hi->bitrate_limit_window<0.)hi->bitrate_limit_window=0.; + if(hi->bitrate_limit_window>10.)hi->bitrate_limit_window=10.; + + if( ( (hi->bitrate_av_lo<=0 && hi->bitrate_av_hi<=0)|| + (hi->bitrate_av_window<=0) ) && + ( (hi->bitrate_min<=0 && hi->bitrate_max<=0)|| + (hi->bitrate_limit_window<=0) )) + hi->managed=0; + } + return(0); + + case OV_ECTL_LOWPASS_GET: + { + double *farg=(double *)arg; + *farg=hi->lowpass_kHz; + } + return(0); + case OV_ECTL_LOWPASS_SET: + { + double *farg=(double *)arg; + hi->lowpass_kHz=*farg; + + if(hi->lowpass_kHz<2.)hi->lowpass_kHz=2.; + if(hi->lowpass_kHz>99.)hi->lowpass_kHz=99.; + } + return(0); + case OV_ECTL_IBLOCK_GET: + { + double *farg=(double *)arg; + *farg=hi->impulse_noisetune; + } + return(0); + case OV_ECTL_IBLOCK_SET: + { + double *farg=(double *)arg; + hi->impulse_noisetune=*farg; + + if(hi->impulse_noisetune>0.)hi->impulse_noisetune=0.; + if(hi->impulse_noisetune<-15.)hi->impulse_noisetune=-15.; + } + return(0); + } + + + return(OV_EIMPL); + } + return(OV_EINVAL); +} diff --git a/src/sound/oggvorbis/vorbissrc/vorbisfile.c b/src/sound/oggvorbis/vorbissrc/vorbisfile.c new file mode 100644 index 0000000..0a34470 --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/vorbisfile.c @@ -0,0 +1,2005 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: stdio-based convenience library for opening/seeking/decoding + last mod: $Id: vorbisfile.c,v 1.75 2003/09/16 20:28:14 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include +#include +#include + +#include "../vorbis/codec.h" +#include "../vorbis/vorbisfile.h" + +#include "os.h" +#include "misc.h" + +/* A 'chained bitstream' is a Vorbis bitstream that contains more than + one logical bitstream arranged end to end (the only form of Ogg + multiplexing allowed in a Vorbis bitstream; grouping [parallel + multiplexing] is not allowed in Vorbis) */ + +/* A Vorbis file can be played beginning to end (streamed) without + worrying ahead of time about chaining (see decoder_example.c). If + we have the whole file, however, and want random access + (seeking/scrubbing) or desire to know the total length/time of a + file, we need to account for the possibility of chaining. */ + +/* We can handle things a number of ways; we can determine the entire + bitstream structure right off the bat, or find pieces on demand. + This example determines and caches structure for the entire + bitstream, but builds a virtual decoder on the fly when moving + between links in the chain. */ + +/* There are also different ways to implement seeking. Enough + information exists in an Ogg bitstream to seek to + sample-granularity positions in the output. Or, one can seek by + picking some portion of the stream roughly in the desired area if + we only want coarse navigation through the stream. */ + +/************************************************************************* + * Many, many internal helpers. The intention is not to be confusing; + * rampant duplication and monolithic function implementation would be + * harder to understand anyway. The high level functions are last. Begin + * grokking near the end of the file */ + +/* read a little more data from the file/pipe into the ogg_sync framer +*/ +#define CHUNKSIZE 8500 /* a shade over 8k; anyone using pages well + over 8k gets what they deserve */ +static long _get_data(OggVorbis_File *vf){ + errno=0; + if(vf->datasource){ + char *buffer=ogg_sync_buffer(&vf->oy,CHUNKSIZE); + long bytes=(vf->callbacks.read_func)(buffer,1,CHUNKSIZE,vf->datasource); + if(bytes>0)ogg_sync_wrote(&vf->oy,bytes); + if(bytes==0 && errno)return(-1); + return(bytes); + }else + return(0); +} + +/* save a tiny smidge of verbosity to make the code more readable */ +static void _seek_helper(OggVorbis_File *vf,ogg_int64_t offset){ + if(vf->datasource){ + (vf->callbacks.seek_func)(vf->datasource, offset, SEEK_SET); + vf->offset=offset; + ogg_sync_reset(&vf->oy); + }else{ + /* shouldn't happen unless someone writes a broken callback */ + return; + } +} + +/* The read/seek functions track absolute position within the stream */ + +/* from the head of the stream, get the next page. boundary specifies + if the function is allowed to fetch more data from the stream (and + how much) or only use internally buffered data. + + boundary: -1) unbounded search + 0) read no additional data; use cached only + n) search for a new page beginning for n bytes + + return: <0) did not find a page (OV_FALSE, OV_EOF, OV_EREAD) + n) found a page at absolute offset n */ + +static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og, + ogg_int64_t boundary){ + if(boundary>0)boundary+=vf->offset; + while(1){ + long more; + + if(boundary>0 && vf->offset>=boundary)return(OV_FALSE); + more=ogg_sync_pageseek(&vf->oy,og); + + if(more<0){ + /* skipped n bytes */ + vf->offset-=more; + }else{ + if(more==0){ + /* send more paramedics */ + if(!boundary)return(OV_FALSE); + { + long ret=_get_data(vf); + if(ret==0)return(OV_EOF); + if(ret<0)return(OV_EREAD); + } + }else{ + /* got a page. Return the offset at the page beginning, + advance the internal offset past the page end */ + ogg_int64_t ret=vf->offset; + vf->offset+=more; + return(ret); + + } + } + } +} + +/* find the latest page beginning before the current stream cursor + position. Much dirtier than the above as Ogg doesn't have any + backward search linkage. no 'readp' as it will certainly have to + read. */ +/* returns offset or OV_EREAD, OV_FAULT */ +static ogg_int64_t _get_prev_page(OggVorbis_File *vf,ogg_page *og){ + ogg_int64_t begin=vf->offset; + ogg_int64_t end=begin; + ogg_int64_t ret; + ogg_int64_t offset=-1; + + while(offset==-1){ + begin-=CHUNKSIZE; + if(begin<0) + begin=0; + _seek_helper(vf,begin); + while(vf->offsetoffset); + if(ret==OV_EREAD)return(OV_EREAD); + if(ret<0){ + break; + }else{ + offset=ret; + } + } + } + + /* we have the offset. Actually snork and hold the page now */ + _seek_helper(vf,offset); + ret=_get_next_page(vf,og,CHUNKSIZE); + if(ret<0) + /* this shouldn't be possible */ + return(OV_EFAULT); + + return(offset); +} + +/* finds each bitstream link one at a time using a bisection search + (has to begin by knowing the offset of the lb's initial page). + Recurses for each link so it can alloc the link storage after + finding them all, then unroll and fill the cache at the same time */ +static int _bisect_forward_serialno(OggVorbis_File *vf, + ogg_int64_t begin, + ogg_int64_t searched, + ogg_int64_t end, + long currentno, + long m){ + ogg_int64_t endsearched=end; + ogg_int64_t next=end; + ogg_page og; + ogg_int64_t ret; + + /* the below guards against garbage seperating the last and + first pages of two links. */ + while(searched=0)next=ret; + }else{ + searched=ret+og.header_len+og.body_len; + } + } + + _seek_helper(vf,next); + ret=_get_next_page(vf,&og,-1); + if(ret==OV_EREAD)return(OV_EREAD); + + if(searched>=end || ret<0){ + vf->links=m+1; + vf->offsets=_ogg_malloc((vf->links+1)*sizeof(*vf->offsets)); + vf->serialnos=_ogg_malloc(vf->links*sizeof(*vf->serialnos)); + vf->offsets[m+1]=searched; + }else{ + ret=_bisect_forward_serialno(vf,next,vf->offset, + end,ogg_page_serialno(&og),m+1); + if(ret==OV_EREAD)return(OV_EREAD); + } + + vf->offsets[m]=begin; + vf->serialnos[m]=currentno; + return(0); +} + +/* uses the local ogg_stream storage in vf; this is important for + non-streaming input sources */ +static int _fetch_headers(OggVorbis_File *vf,vorbis_info *vi,vorbis_comment *vc, + long *serialno,ogg_page *og_ptr){ + ogg_page og; + ogg_packet op; + int i,ret; + + if(!og_ptr){ + ogg_int64_t llret=_get_next_page(vf,&og,CHUNKSIZE); + if(llret==OV_EREAD)return(OV_EREAD); + if(llret<0)return OV_ENOTVORBIS; + og_ptr=&og; + } + + ogg_stream_reset_serialno(&vf->os,ogg_page_serialno(og_ptr)); + if(serialno)*serialno=vf->os.serialno; + vf->ready_state=STREAMSET; + + /* extract the initial header from the first page and verify that the + Ogg bitstream is in fact Vorbis data */ + + vorbis_info_init(vi); + vorbis_comment_init(vc); + + i=0; + while(i<3){ + ogg_stream_pagein(&vf->os,og_ptr); + while(i<3){ + int result=ogg_stream_packetout(&vf->os,&op); + if(result==0)break; + if(result==-1){ + ret=OV_EBADHEADER; + goto bail_header; + } + if((ret=vorbis_synthesis_headerin(vi,vc,&op))){ + goto bail_header; + } + i++; + } + if(i<3) + if(_get_next_page(vf,og_ptr,CHUNKSIZE)<0){ + ret=OV_EBADHEADER; + goto bail_header; + } + } + return 0; + + bail_header: + vorbis_info_clear(vi); + vorbis_comment_clear(vc); + vf->ready_state=OPENED; + + return ret; +} + +/* last step of the OggVorbis_File initialization; get all the + vorbis_info structs and PCM positions. Only called by the seekable + initialization (local stream storage is hacked slightly; pay + attention to how that's done) */ + +/* this is void and does not propogate errors up because we want to be + able to open and use damaged bitstreams as well as we can. Just + watch out for missing information for links in the OggVorbis_File + struct */ +static void _prefetch_all_headers(OggVorbis_File *vf, ogg_int64_t dataoffset){ + ogg_page og; + int i; + ogg_int64_t ret; + + vf->vi=_ogg_realloc(vf->vi,vf->links*sizeof(*vf->vi)); + vf->vc=_ogg_realloc(vf->vc,vf->links*sizeof(*vf->vc)); + vf->dataoffsets=_ogg_malloc(vf->links*sizeof(*vf->dataoffsets)); + vf->pcmlengths=_ogg_malloc(vf->links*2*sizeof(*vf->pcmlengths)); + + for(i=0;ilinks;i++){ + if(i==0){ + /* we already grabbed the initial header earlier. Just set the offset */ + vf->dataoffsets[i]=dataoffset; + _seek_helper(vf,dataoffset); + + }else{ + + /* seek to the location of the initial header */ + + _seek_helper(vf,vf->offsets[i]); + if(_fetch_headers(vf,vf->vi+i,vf->vc+i,NULL,NULL)<0){ + vf->dataoffsets[i]=-1; + }else{ + vf->dataoffsets[i]=vf->offset; + } + } + + /* fetch beginning PCM offset */ + + if(vf->dataoffsets[i]!=-1){ + ogg_int64_t accumulated=0; + long lastblock=-1; + int result; + + ogg_stream_reset_serialno(&vf->os,vf->serialnos[i]); + + while(1){ + ogg_packet op; + + ret=_get_next_page(vf,&og,-1); + if(ret<0) + /* this should not be possible unless the file is + truncated/mangled */ + break; + + if(ogg_page_serialno(&og)!=vf->serialnos[i]) + break; + + /* count blocksizes of all frames in the page */ + ogg_stream_pagein(&vf->os,&og); + while((result=ogg_stream_packetout(&vf->os,&op))){ + if(result>0){ /* ignore holes */ + long thisblock=vorbis_packet_blocksize(vf->vi+i,&op); + if(lastblock!=-1) + accumulated+=(lastblock+thisblock)>>2; + lastblock=thisblock; + } + } + + if(ogg_page_granulepos(&og)!=-1){ + /* pcm offset of last packet on the first audio page */ + accumulated= ogg_page_granulepos(&og)-accumulated; + break; + } + } + + /* less than zero? This is a stream with samples trimmed off + the beginning, a normal occurrence; set the offset to zero */ + if(accumulated<0)accumulated=0; + + vf->pcmlengths[i*2]=accumulated; + } + + /* get the PCM length of this link. To do this, + get the last page of the stream */ + { + ogg_int64_t end=vf->offsets[i+1]; + _seek_helper(vf,end); + + while(1){ + ret=_get_prev_page(vf,&og); + if(ret<0){ + /* this should not be possible */ + vorbis_info_clear(vf->vi+i); + vorbis_comment_clear(vf->vc+i); + break; + } + if(ogg_page_granulepos(&og)!=-1){ + vf->pcmlengths[i*2+1]=ogg_page_granulepos(&og)-vf->pcmlengths[i*2]; + break; + } + vf->offset=ret; + } + } + } +} + +static int _make_decode_ready(OggVorbis_File *vf){ + if(vf->ready_state>STREAMSET)return 0; + if(vf->ready_stateseekable){ + if(vorbis_synthesis_init(&vf->vd,vf->vi+vf->current_link)) + return OV_EBADLINK; + }else{ + if(vorbis_synthesis_init(&vf->vd,vf->vi)) + return OV_EBADLINK; + } + vorbis_block_init(&vf->vd,&vf->vb); + vf->ready_state=INITSET; + vf->bittrack=0.f; + vf->samptrack=0.f; + return 0; +} + +static int _open_seekable2(OggVorbis_File *vf){ + long serialno=vf->current_serialno; + ogg_int64_t dataoffset=vf->offset, end; + ogg_page og; + + /* we're partially open and have a first link header state in + storage in vf */ + /* we can seek, so set out learning all about this file */ + (vf->callbacks.seek_func)(vf->datasource,0,SEEK_END); + vf->offset=vf->end=(vf->callbacks.tell_func)(vf->datasource); + + /* We get the offset for the last page of the physical bitstream. + Most OggVorbis files will contain a single logical bitstream */ + end=_get_prev_page(vf,&og); + if(end<0)return(end); + + /* more than one logical bitstream? */ + if(ogg_page_serialno(&og)!=serialno){ + + /* Chained bitstream. Bisect-search each logical bitstream + section. Do so based on serial number only */ + if(_bisect_forward_serialno(vf,0,0,end+1,serialno,0)<0)return(OV_EREAD); + + }else{ + + /* Only one logical bitstream */ + if(_bisect_forward_serialno(vf,0,end,end+1,serialno,0))return(OV_EREAD); + + } + + /* the initial header memory is referenced by vf after; don't free it */ + _prefetch_all_headers(vf,dataoffset); + return(ov_raw_seek(vf,0)); +} + +/* clear out the current logical bitstream decoder */ +static void _decode_clear(OggVorbis_File *vf){ + vorbis_dsp_clear(&vf->vd); + vorbis_block_clear(&vf->vb); + vf->ready_state=OPENED; +} + +/* fetch and process a packet. Handles the case where we're at a + bitstream boundary and dumps the decoding machine. If the decoding + machine is unloaded, it loads it. It also keeps pcm_offset up to + date (seek and read both use this. seek uses a special hack with + readp). + + return: <0) error, OV_HOLE (lost packet) or OV_EOF + 0) need more data (only if readp==0) + 1) got a packet +*/ + +static int _fetch_and_process_packet(OggVorbis_File *vf, + ogg_packet *op_in, + int readp, + int spanp){ + ogg_page og; + + /* handle one packet. Try to fetch it from current stream state */ + /* extract packets from page */ + while(1){ + + /* process a packet if we can. If the machine isn't loaded, + neither is a page */ + if(vf->ready_state==INITSET){ + while(1) { + ogg_packet op; + ogg_packet *op_ptr=(op_in?op_in:&op); + int result=ogg_stream_packetout(&vf->os,op_ptr); + ogg_int64_t granulepos; + + op_in=NULL; + if(result==-1)return(OV_HOLE); /* hole in the data. */ + if(result>0){ + /* got a packet. process it */ + granulepos=op_ptr->granulepos; + if(!vorbis_synthesis(&vf->vb,op_ptr)){ /* lazy check for lazy + header handling. The + header packets aren't + audio, so if/when we + submit them, + vorbis_synthesis will + reject them */ + + /* suck in the synthesis data and track bitrate */ + { + int oldsamples=vorbis_synthesis_pcmout(&vf->vd,NULL); + /* for proper use of libvorbis within libvorbisfile, + oldsamples will always be zero. */ + if(oldsamples)return(OV_EFAULT); + + vorbis_synthesis_blockin(&vf->vd,&vf->vb); + vf->samptrack+=vorbis_synthesis_pcmout(&vf->vd,NULL)-oldsamples; + vf->bittrack+=op_ptr->bytes*8; + } + + /* update the pcm offset. */ + if(granulepos!=-1 && !op_ptr->e_o_s){ + int link=(vf->seekable?vf->current_link:0); + int i,samples; + + /* this packet has a pcm_offset on it (the last packet + completed on a page carries the offset) After processing + (above), we know the pcm position of the *last* sample + ready to be returned. Find the offset of the *first* + + As an aside, this trick is inaccurate if we begin + reading anew right at the last page; the end-of-stream + granulepos declares the last frame in the stream, and the + last packet of the last page may be a partial frame. + So, we need a previous granulepos from an in-sequence page + to have a reference point. Thus the !op_ptr->e_o_s clause + above */ + + if(vf->seekable && link>0) + granulepos-=vf->pcmlengths[link*2]; + if(granulepos<0)granulepos=0; /* actually, this + shouldn't be possible + here unless the stream + is very broken */ + + samples=vorbis_synthesis_pcmout(&vf->vd,NULL); + + granulepos-=samples; + for(i=0;ipcmlengths[i*2+1]; + vf->pcm_offset=granulepos; + } + return(1); + } + } + else + break; + } + } + + if(vf->ready_state>=OPENED){ + int ret; + if(!readp)return(0); + if((ret=_get_next_page(vf,&og,-1))<0){ + return(OV_EOF); /* eof. + leave unitialized */ + } + + /* bitrate tracking; add the header's bytes here, the body bytes + are done by packet above */ + vf->bittrack+=og.header_len*8; + + /* has our decoding just traversed a bitstream boundary? */ + if(vf->ready_state==INITSET){ + if(vf->current_serialno!=ogg_page_serialno(&og)){ + if(!spanp) + return(OV_EOF); + + _decode_clear(vf); + + if(!vf->seekable){ + vorbis_info_clear(vf->vi); + vorbis_comment_clear(vf->vc); + } + } + } + } + + /* Do we need to load a new machine before submitting the page? */ + /* This is different in the seekable and non-seekable cases. + + In the seekable case, we already have all the header + information loaded and cached; we just initialize the machine + with it and continue on our merry way. + + In the non-seekable (streaming) case, we'll only be at a + boundary if we just left the previous logical bitstream and + we're now nominally at the header of the next bitstream + */ + + if(vf->ready_state!=INITSET){ + int link; + + if(vf->ready_stateseekable){ + vf->current_serialno=ogg_page_serialno(&og); + + /* match the serialno to bitstream section. We use this rather than + offset positions to avoid problems near logical bitstream + boundaries */ + for(link=0;linklinks;link++) + if(vf->serialnos[link]==vf->current_serialno)break; + if(link==vf->links)return(OV_EBADLINK); /* sign of a bogus + stream. error out, + leave machine + uninitialized */ + + vf->current_link=link; + + ogg_stream_reset_serialno(&vf->os,vf->current_serialno); + vf->ready_state=STREAMSET; + + }else{ + /* we're streaming */ + /* fetch the three header packets, build the info struct */ + + int ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,&og); + if(ret)return(ret); + vf->current_link++; + link=0; + } + } + + { + int ret=_make_decode_ready(vf); + if(ret<0)return ret; + } + } + ogg_stream_pagein(&vf->os,&og); + } +} + +/* if, eg, 64 bit stdio is configured by default, this will build with + fseek64 */ +static int _fseek64_wrap(FILE *f,ogg_int64_t off,int whence){ + if(f==NULL)return(-1); + return fseek(f,off,whence); +} + +static int _ov_open1(void *f,OggVorbis_File *vf,char *initial, + long ibytes, ov_callbacks callbacks){ + int offsettest=(f?callbacks.seek_func(f,0,SEEK_CUR):-1); + int ret; + + memset(vf,0,sizeof(*vf)); + vf->datasource=f; + vf->callbacks = callbacks; + + /* init the framing state */ + ogg_sync_init(&vf->oy); + + /* perhaps some data was previously read into a buffer for testing + against other stream types. Allow initialization from this + previously read data (as we may be reading from a non-seekable + stream) */ + if(initial){ + char *buffer=ogg_sync_buffer(&vf->oy,ibytes); + memcpy(buffer,initial,ibytes); + ogg_sync_wrote(&vf->oy,ibytes); + } + + /* can we seek? Stevens suggests the seek test was portable */ + if(offsettest!=-1)vf->seekable=1; + + /* No seeking yet; Set up a 'single' (current) logical bitstream + entry for partial open */ + vf->links=1; + vf->vi=_ogg_calloc(vf->links,sizeof(*vf->vi)); + vf->vc=_ogg_calloc(vf->links,sizeof(*vf->vc)); + ogg_stream_init(&vf->os,-1); /* fill in the serialno later */ + + /* Try to fetch the headers, maintaining all the storage */ + if((ret=_fetch_headers(vf,vf->vi,vf->vc,&vf->current_serialno,NULL))<0){ + vf->datasource=NULL; + ov_clear(vf); + }else + vf->ready_state=PARTOPEN; + return(ret); +} + +static int _ov_open2(OggVorbis_File *vf){ + if(vf->ready_state != PARTOPEN) return OV_EINVAL; + vf->ready_state=OPENED; + if(vf->seekable){ + int ret=_open_seekable2(vf); + if(ret){ + vf->datasource=NULL; + ov_clear(vf); + } + return(ret); + }else + vf->ready_state=STREAMSET; + + return 0; +} + + +/* clear out the OggVorbis_File struct */ +int ov_clear(OggVorbis_File *vf){ + if(vf){ + vorbis_block_clear(&vf->vb); + vorbis_dsp_clear(&vf->vd); + ogg_stream_clear(&vf->os); + + if(vf->vi && vf->links){ + int i; + for(i=0;ilinks;i++){ + vorbis_info_clear(vf->vi+i); + vorbis_comment_clear(vf->vc+i); + } + _ogg_free(vf->vi); + _ogg_free(vf->vc); + } + if(vf->dataoffsets)_ogg_free(vf->dataoffsets); + if(vf->pcmlengths)_ogg_free(vf->pcmlengths); + if(vf->serialnos)_ogg_free(vf->serialnos); + if(vf->offsets)_ogg_free(vf->offsets); + ogg_sync_clear(&vf->oy); + if(vf->datasource)(vf->callbacks.close_func)(vf->datasource); + memset(vf,0,sizeof(*vf)); + } +#ifdef DEBUG_LEAKS + _VDBG_dump(); +#endif + return(0); +} + +/* inspects the OggVorbis file and finds/documents all the logical + bitstreams contained in it. Tries to be tolerant of logical + bitstream sections that are truncated/woogie. + + return: -1) error + 0) OK +*/ + +int ov_open_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes, + ov_callbacks callbacks){ + int ret=_ov_open1(f,vf,initial,ibytes,callbacks); + if(ret)return ret; + return _ov_open2(vf); +} + +int ov_open(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ + ov_callbacks callbacks = { + (size_t (*)(void *, size_t, size_t, void *)) fread, + (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, + (int (*)(void *)) fclose, + (long (*)(void *)) ftell + }; + + return ov_open_callbacks((void *)f, vf, initial, ibytes, callbacks); +} + +/* cheap hack for game usage where downsampling is desirable; there's + no need for SRC as we can just do it cheaply in libvorbis. */ + +int ov_halfrate(OggVorbis_File *vf,int flag){ + int i; + if(vf->vi==NULL)return OV_EINVAL; + if(!vf->seekable)return OV_EINVAL; + if(vf->ready_state>=STREAMSET) + _decode_clear(vf); /* clear out stream state; later on libvorbis + will be able to swap this on the fly, but + for now dumping the decode machine is needed + to reinit the MDCT lookups. 1.1 libvorbis + is planned to be able to switch on the fly */ + + for(i=0;ilinks;i++){ + if(vorbis_synthesis_halfrate(vf->vi+i,flag)){ + ov_halfrate(vf,0); + return OV_EINVAL; + } + } + return 0; +} + +int ov_halfrate_p(OggVorbis_File *vf){ + if(vf->vi==NULL)return OV_EINVAL; + return vorbis_synthesis_halfrate_p(vf->vi); +} + +/* Only partially open the vorbis file; test for Vorbisness, and load + the headers for the first chain. Do not seek (although test for + seekability). Use ov_test_open to finish opening the file, else + ov_clear to close/free it. Same return codes as open. */ + +int ov_test_callbacks(void *f,OggVorbis_File *vf,char *initial,long ibytes, + ov_callbacks callbacks) +{ + return _ov_open1(f,vf,initial,ibytes,callbacks); +} + +int ov_test(FILE *f,OggVorbis_File *vf,char *initial,long ibytes){ + ov_callbacks callbacks = { + (size_t (*)(void *, size_t, size_t, void *)) fread, + (int (*)(void *, ogg_int64_t, int)) _fseek64_wrap, + (int (*)(void *)) fclose, + (long (*)(void *)) ftell + }; + + return ov_test_callbacks((void *)f, vf, initial, ibytes, callbacks); +} + +int ov_test_open(OggVorbis_File *vf){ + if(vf->ready_state!=PARTOPEN)return(OV_EINVAL); + return _ov_open2(vf); +} + +/* How many logical bitstreams in this physical bitstream? */ +long ov_streams(OggVorbis_File *vf){ + return vf->links; +} + +/* Is the FILE * associated with vf seekable? */ +long ov_seekable(OggVorbis_File *vf){ + return vf->seekable; +} + +/* returns the bitrate for a given logical bitstream or the entire + physical bitstream. If the file is open for random access, it will + find the *actual* average bitrate. If the file is streaming, it + returns the nominal bitrate (if set) else the average of the + upper/lower bounds (if set) else -1 (unset). + + If you want the actual bitrate field settings, get them from the + vorbis_info structs */ + +long ov_bitrate(OggVorbis_File *vf,int i){ + if(vf->ready_state=vf->links)return(OV_EINVAL); + if(!vf->seekable && i!=0)return(ov_bitrate(vf,0)); + if(i<0){ + ogg_int64_t bits=0; + int i; + float br; + for(i=0;ilinks;i++) + bits+=(vf->offsets[i+1]-vf->dataoffsets[i])*8; + /* This once read: return(rint(bits/ov_TIME_Total(vf,-1))); + * gcc 3.x on x86 miscompiled this at optimisation level 2 and above, + * so this is slightly transformed to make it work. + */ + br = bits/ov_TIME_Total(vf,-1); + return(rint(br)); + }else{ + if(vf->seekable){ + /* return the actual bitrate */ + return(rint((vf->offsets[i+1]-vf->dataoffsets[i])*8/ov_TIME_Total(vf,i))); + }else{ + /* return nominal if set */ + if(vf->vi[i].bitrate_nominal>0){ + return vf->vi[i].bitrate_nominal; + }else{ + if(vf->vi[i].bitrate_upper>0){ + if(vf->vi[i].bitrate_lower>0){ + return (vf->vi[i].bitrate_upper+vf->vi[i].bitrate_lower)/2; + }else{ + return vf->vi[i].bitrate_upper; + } + } + return(OV_FALSE); + } + } + } +} + +/* returns the actual bitrate since last call. returns -1 if no + additional data to offer since last call (or at beginning of stream), + EINVAL if stream is only partially open +*/ +long ov_bitrate_instant(OggVorbis_File *vf){ + int link=(vf->seekable?vf->current_link:0); + long ret; + if(vf->ready_statesamptrack==0)return(OV_FALSE); + ret=vf->bittrack/vf->samptrack*vf->vi[link].rate+.5; + vf->bittrack=0.f; + vf->samptrack=0.f; + return(ret); +} + +/* Guess */ +long ov_serialnumber(OggVorbis_File *vf,int i){ + if(i>=vf->links)return(ov_serialnumber(vf,vf->links-1)); + if(!vf->seekable && i>=0)return(ov_serialnumber(vf,-1)); + if(i<0){ + return(vf->current_serialno); + }else{ + return(vf->serialnos[i]); + } +} + +/* returns: total raw (compressed) length of content if i==-1 + raw (compressed) length of that logical bitstream for i==0 to n + OV_EINVAL if the stream is not seekable (we can't know the length) + or if stream is only partially open +*/ +ogg_int64_t ov_raw_total(OggVorbis_File *vf,int i){ + if(vf->ready_stateseekable || i>=vf->links)return(OV_EINVAL); + if(i<0){ + ogg_int64_t acc=0; + int i; + for(i=0;ilinks;i++) + acc+=ov_raw_total(vf,i); + return(acc); + }else{ + return(vf->offsets[i+1]-vf->offsets[i]); + } +} + +/* returns: total PCM length (samples) of content if i==-1 PCM length + (samples) of that logical bitstream for i==0 to n + OV_EINVAL if the stream is not seekable (we can't know the + length) or only partially open +*/ +ogg_int64_t ov_pcm_total(OggVorbis_File *vf,int i){ + if(vf->ready_stateseekable || i>=vf->links)return(OV_EINVAL); + if(i<0){ + ogg_int64_t acc=0; + int i; + for(i=0;ilinks;i++) + acc+=ov_pcm_total(vf,i); + return(acc); + }else{ + return(vf->pcmlengths[i*2+1]); + } +} + +/* returns: total seconds of content if i==-1 + seconds in that logical bitstream for i==0 to n + OV_EINVAL if the stream is not seekable (we can't know the + length) or only partially open +*/ +double ov_TIME_Total(OggVorbis_File *vf,int i){ + if(vf->ready_stateseekable || i>=vf->links)return(OV_EINVAL); + if(i<0){ + double acc=0; + int i; + for(i=0;ilinks;i++) + acc+=ov_TIME_Total(vf,i); + return(acc); + }else{ + return((double)(vf->pcmlengths[i*2+1])/vf->vi[i].rate); + } +} + +/* seek to an offset relative to the *compressed* data. This also + scans packets to update the PCM cursor. It will cross a logical + bitstream boundary, but only if it can't get any packets out of the + tail of the bitstream we seek to (so no surprises). + + returns zero on success, nonzero on failure */ + +int ov_raw_seek(OggVorbis_File *vf,ogg_int64_t pos){ + ogg_stream_state work_os; + + if(vf->ready_stateseekable) + return(OV_ENOSEEK); /* don't dump machine if we can't seek */ + + if(pos<0 || pos>vf->end)return(OV_EINVAL); + + /* don't yet clear out decoding machine (if it's initialized), in + the case we're in the same link. Restart the decode lapping, and + let _fetch_and_process_packet deal with a potential bitstream + boundary */ + vf->pcm_offset=-1; + ogg_stream_reset_serialno(&vf->os, + vf->current_serialno); /* must set serialno */ + vorbis_synthesis_restart(&vf->vd); + + _seek_helper(vf,pos); + + /* we need to make sure the pcm_offset is set, but we don't want to + advance the raw cursor past good packets just to get to the first + with a granulepos. That's not equivalent behavior to beginning + decoding as immediately after the seek position as possible. + + So, a hack. We use two stream states; a local scratch state and + the shared vf->os stream state. We use the local state to + scan, and the shared state as a buffer for later decode. + + Unfortuantely, on the last page we still advance to last packet + because the granulepos on the last page is not necessarily on a + packet boundary, and we need to make sure the granpos is + correct. + */ + + { + ogg_page og; + ogg_packet op; + int lastblock=0; + int accblock=0; + int thisblock; + int eosflag; + + ogg_stream_init(&work_os,vf->current_serialno); /* get the memory ready */ + ogg_stream_reset(&work_os); /* eliminate the spurious OV_HOLE + return from not necessarily + starting from the beginning */ + + while(1){ + if(vf->ready_state>=STREAMSET){ + /* snarf/scan a packet if we can */ + int result=ogg_stream_packetout(&work_os,&op); + + if(result>0){ + + if(vf->vi[vf->current_link].codec_setup){ + thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); + if(thisblock<0){ + ogg_stream_packetout(&vf->os,NULL); + thisblock=0; + }else{ + + if(eosflag) + ogg_stream_packetout(&vf->os,NULL); + else + if(lastblock)accblock+=(lastblock+thisblock)>>2; + } + + if(op.granulepos!=-1){ + int i,link=vf->current_link; + ogg_int64_t granulepos=op.granulepos-vf->pcmlengths[link*2]; + if(granulepos<0)granulepos=0; + + for(i=0;ipcmlengths[i*2+1]; + vf->pcm_offset=granulepos-accblock; + break; + } + lastblock=thisblock; + continue; + }else + ogg_stream_packetout(&vf->os,NULL); + } + } + + if(!lastblock){ + if(_get_next_page(vf,&og,-1)<0){ + vf->pcm_offset=ov_pcm_total(vf,-1); + break; + } + }else{ + /* huh? Bogus stream with packets but no granulepos */ + vf->pcm_offset=-1; + break; + } + + /* has our decoding just traversed a bitstream boundary? */ + if(vf->ready_state>=STREAMSET) + if(vf->current_serialno!=ogg_page_serialno(&og)){ + _decode_clear(vf); /* clear out stream state */ + ogg_stream_clear(&work_os); + } + + if(vf->ready_statecurrent_serialno=ogg_page_serialno(&og); + for(link=0;linklinks;link++) + if(vf->serialnos[link]==vf->current_serialno)break; + if(link==vf->links)goto seek_error; /* sign of a bogus stream. + error out, leave + machine uninitialized */ + vf->current_link=link; + + ogg_stream_reset_serialno(&vf->os,vf->current_serialno); + ogg_stream_reset_serialno(&work_os,vf->current_serialno); + vf->ready_state=STREAMSET; + + } + + ogg_stream_pagein(&vf->os,&og); + ogg_stream_pagein(&work_os,&og); + eosflag=ogg_page_eos(&og); + } + } + + ogg_stream_clear(&work_os); + vf->bittrack=0.f; + vf->samptrack=0.f; + return(0); + + seek_error: + /* dump the machine so we're in a known state */ + vf->pcm_offset=-1; + ogg_stream_clear(&work_os); + _decode_clear(vf); + return OV_EBADLINK; +} + +/* Page granularity seek (faster than sample granularity because we + don't do the last bit of decode to find a specific sample). + + Seek to the last [granule marked] page preceeding the specified pos + location, such that decoding past the returned point will quickly + arrive at the requested position. */ +int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){ + int link=-1; + ogg_int64_t result=0; + ogg_int64_t total=ov_pcm_total(vf,-1); + + if(vf->ready_stateseekable)return(OV_ENOSEEK); + + if(pos<0 || pos>total)return(OV_EINVAL); + + /* which bitstream section does this pcm offset occur in? */ + for(link=vf->links-1;link>=0;link--){ + total-=vf->pcmlengths[link*2+1]; + if(pos>=total)break; + } + + /* search within the logical bitstream for the page with the highest + pcm_pos preceeding (or equal to) pos. There is a danger here; + missing pages or incorrect frame number information in the + bitstream could make our task impossible. Account for that (it + would be an error condition) */ + + /* new search algorithm by HB (Nicholas Vinen) */ + { + ogg_int64_t end=vf->offsets[link+1]; + ogg_int64_t begin=vf->offsets[link]; + ogg_int64_t begintime = vf->pcmlengths[link*2]; + ogg_int64_t endtime = vf->pcmlengths[link*2+1]+begintime; + ogg_int64_t target=pos-total+begintime; + ogg_int64_t best=begin; + + ogg_page og; + while(beginoffset); + if(result==OV_EREAD) goto seek_error; + if(result<0){ + if(bisect<=begin+1) + end=begin; /* found it */ + else{ + if(bisect==0) goto seek_error; + bisect-=CHUNKSIZE; + if(bisect<=begin)bisect=begin+1; + _seek_helper(vf,bisect); + } + }else{ + ogg_int64_t granulepos=ogg_page_granulepos(&og); + if(granulepos==-1)continue; + if(granuleposoffset; /* raw offset of next page */ + begintime=granulepos; + + if(target-begintime>44100)break; + bisect=begin; /* *not* begin + 1 */ + }else{ + if(bisect<=begin+1) + end=begin; /* found it */ + else{ + if(end==vf->offset){ /* we're pretty close - we'd be stuck in */ + end=result; + bisect-=CHUNKSIZE; /* an endless loop otherwise. */ + if(bisect<=begin)bisect=begin+1; + _seek_helper(vf,bisect); + }else{ + end=result; + endtime=granulepos; + break; + } + } + } + } + } + } + + /* found our page. seek to it, update pcm offset. Easier case than + raw_seek, don't keep packets preceeding granulepos. */ + { + ogg_page og; + ogg_packet op; + + /* seek */ + _seek_helper(vf,best); + vf->pcm_offset=-1; + + if(_get_next_page(vf,&og,-1)<0)return(OV_EOF); /* shouldn't happen */ + + if(link!=vf->current_link){ + /* Different link; dump entire decode machine */ + _decode_clear(vf); + + vf->current_link=link; + vf->current_serialno=ogg_page_serialno(&og); + vf->ready_state=STREAMSET; + + }else{ + vorbis_synthesis_restart(&vf->vd); + } + + ogg_stream_reset_serialno(&vf->os,vf->current_serialno); + ogg_stream_pagein(&vf->os,&og); + + /* pull out all but last packet; the one with granulepos */ + while(1){ + result=ogg_stream_packetpeek(&vf->os,&op); + if(result==0){ + /* !!! the packet finishing this page originated on a + preceeding page. Keep fetching previous pages until we + get one with a granulepos or without the 'continued' flag + set. Then just use raw_seek for simplicity. */ + + _seek_helper(vf,best); + + while(1){ + result=_get_prev_page(vf,&og); + if(result<0) goto seek_error; + if(ogg_page_granulepos(&og)>-1 || + !ogg_page_continued(&og)){ + return ov_raw_seek(vf,result); + } + vf->offset=result; + } + } + if(result<0){ + result = OV_EBADPACKET; + goto seek_error; + } + if(op.granulepos!=-1){ + vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; + if(vf->pcm_offset<0)vf->pcm_offset=0; + vf->pcm_offset+=total; + break; + }else + result=ogg_stream_packetout(&vf->os,NULL); + } + } + } + + /* verify result */ + if(vf->pcm_offset>pos || pos>ov_pcm_total(vf,-1)){ + result=OV_EFAULT; + goto seek_error; + } + vf->bittrack=0.f; + vf->samptrack=0.f; + return(0); + + seek_error: + /* dump machine so we're in a known state */ + vf->pcm_offset=-1; + _decode_clear(vf); + return (int)result; +} + +/* seek to a sample offset relative to the decompressed pcm stream + returns zero on success, nonzero on failure */ + +int ov_pcm_seek(OggVorbis_File *vf,ogg_int64_t pos){ + int thisblock,lastblock=0; + int ret=ov_pcm_seek_page(vf,pos); + if(ret<0)return(ret); + if((ret=_make_decode_ready(vf)))return ret; + + /* discard leading packets we don't need for the lapping of the + position we want; don't decode them */ + + while(1){ + ogg_packet op; + ogg_page og; + + int ret=ogg_stream_packetpeek(&vf->os,&op); + if(ret>0){ + thisblock=vorbis_packet_blocksize(vf->vi+vf->current_link,&op); + if(thisblock<0){ + ogg_stream_packetout(&vf->os,NULL); + continue; /* non audio packet */ + } + if(lastblock)vf->pcm_offset+=(lastblock+thisblock)>>2; + + if(vf->pcm_offset+((thisblock+ + vorbis_info_blocksize(vf->vi,1))>>2)>=pos)break; + + /* remove the packet from packet queue and track its granulepos */ + ogg_stream_packetout(&vf->os,NULL); + vorbis_synthesis_trackonly(&vf->vb,&op); /* set up a vb with + only tracking, no + pcm_decode */ + vorbis_synthesis_blockin(&vf->vd,&vf->vb); + + /* end of logical stream case is hard, especially with exact + length positioning. */ + + if(op.granulepos>-1){ + int i; + /* always believe the stream markers */ + vf->pcm_offset=op.granulepos-vf->pcmlengths[vf->current_link*2]; + if(vf->pcm_offset<0)vf->pcm_offset=0; + for(i=0;icurrent_link;i++) + vf->pcm_offset+=vf->pcmlengths[i*2+1]; + } + + lastblock=thisblock; + + }else{ + if(ret<0 && ret!=OV_HOLE)break; + + /* suck in a new page */ + if(_get_next_page(vf,&og,-1)<0)break; + if(vf->current_serialno!=ogg_page_serialno(&og))_decode_clear(vf); + + if(vf->ready_statecurrent_serialno=ogg_page_serialno(&og); + for(link=0;linklinks;link++) + if(vf->serialnos[link]==vf->current_serialno)break; + if(link==vf->links)return(OV_EBADLINK); + vf->current_link=link; + + ogg_stream_reset_serialno(&vf->os,vf->current_serialno); + vf->ready_state=STREAMSET; + ret=_make_decode_ready(vf); + if(ret)return ret; + lastblock=0; + } + + ogg_stream_pagein(&vf->os,&og); + } + } + + vf->bittrack=0.f; + vf->samptrack=0.f; + /* discard samples until we reach the desired position. Crossing a + logical bitstream boundary with abandon is OK. */ + while(vf->pcm_offsetpcm_offset; + long samples=vorbis_synthesis_pcmout(&vf->vd,NULL); + + if(samples>target)samples=target; + vorbis_synthesis_read(&vf->vd,samples); + vf->pcm_offset+=samples; + + if(samplespcm_offset=ov_pcm_total(vf,-1); /* eof */ + } + return 0; +} + +/* seek to a playback time relative to the decompressed pcm stream + returns zero on success, nonzero on failure */ +int ov_time_seek(OggVorbis_File *vf,double seconds){ + /* translate time to PCM position and call ov_pcm_seek */ + + int link=-1; + ogg_int64_t pcm_total=ov_pcm_total(vf,-1); + double TIME_Total=ov_TIME_Total(vf,-1); + + if(vf->ready_stateseekable)return(OV_ENOSEEK); + if(seconds<0 || seconds>TIME_Total)return(OV_EINVAL); + + /* which bitstream section does this time offset occur in? */ + for(link=vf->links-1;link>=0;link--){ + pcm_total-=vf->pcmlengths[link*2+1]; + TIME_Total-=ov_TIME_Total(vf,link); + if(seconds>=TIME_Total)break; + } + + /* enough information to convert time offset to pcm offset */ + { + ogg_int64_t target=pcm_total+(seconds-TIME_Total)*vf->vi[link].rate; + return(ov_pcm_seek(vf,target)); + } +} + +/* page-granularity version of ov_time_seek + returns zero on success, nonzero on failure */ +int ov_time_seek_page(OggVorbis_File *vf,double seconds){ + /* translate time to PCM position and call ov_pcm_seek */ + + int link=-1; + ogg_int64_t pcm_total=ov_pcm_total(vf,-1); + double TIME_Total=ov_TIME_Total(vf,-1); + + if(vf->ready_stateseekable)return(OV_ENOSEEK); + if(seconds<0 || seconds>TIME_Total)return(OV_EINVAL); + + /* which bitstream section does this time offset occur in? */ + for(link=vf->links-1;link>=0;link--){ + pcm_total-=vf->pcmlengths[link*2+1]; + TIME_Total-=ov_TIME_Total(vf,link); + if(seconds>=TIME_Total)break; + } + + /* enough information to convert time offset to pcm offset */ + { + ogg_int64_t target=pcm_total+(seconds-TIME_Total)*vf->vi[link].rate; + return(ov_pcm_seek_page(vf,target)); + } +} + +/* tell the current stream offset cursor. Note that seek followed by + tell will likely not give the set offset due to caching */ +ogg_int64_t ov_raw_tell(OggVorbis_File *vf){ + if(vf->ready_stateoffset); +} + +/* return PCM offset (sample) of next PCM sample to be read */ +ogg_int64_t ov_pcm_tell(OggVorbis_File *vf){ + if(vf->ready_statepcm_offset); +} + +/* return time offset (seconds) of next PCM sample to be read */ +double ov_TIME_Tell(OggVorbis_File *vf){ + int link=0; + ogg_int64_t pcm_total=0; + double TIME_Total=0.f; + + if(vf->ready_stateseekable){ + pcm_total=ov_pcm_total(vf,-1); + TIME_Total=ov_TIME_Total(vf,-1); + + /* which bitstream section does this time offset occur in? */ + for(link=vf->links-1;link>=0;link--){ + pcm_total-=vf->pcmlengths[link*2+1]; + TIME_Total-=ov_TIME_Total(vf,link); + if(vf->pcm_offset>=pcm_total)break; + } + } + + return((double)TIME_Total+(double)(vf->pcm_offset-pcm_total)/vf->vi[link].rate); +} + +/* link: -1) return the vorbis_info struct for the bitstream section + currently being decoded + 0-n) to request information for a specific bitstream section + + In the case of a non-seekable bitstream, any call returns the + current bitstream. NULL in the case that the machine is not + initialized */ + +vorbis_info *ov_info(OggVorbis_File *vf,int link){ + if(vf->seekable){ + if(link<0) + if(vf->ready_state>=STREAMSET) + return vf->vi+vf->current_link; + else + return vf->vi; + else + if(link>=vf->links) + return NULL; + else + return vf->vi+link; + }else{ + return vf->vi; + } +} + +/* grr, strong typing, grr, no templates/inheritence, grr */ +vorbis_comment *ov_comment(OggVorbis_File *vf,int link){ + if(vf->seekable){ + if(link<0) + if(vf->ready_state>=STREAMSET) + return vf->vc+vf->current_link; + else + return vf->vc; + else + if(link>=vf->links) + return NULL; + else + return vf->vc+link; + }else{ + return vf->vc; + } +} + +static int host_is_big_endian() { + ogg_int32_t pattern = 0xfeedface; /* deadbeef */ + unsigned char *bytewise = (unsigned char *)&pattern; + if (bytewise[0] == 0xfe) return 1; + return 0; +} + +/* up to this point, everything could more or less hide the multiple + logical bitstream nature of chaining from the toplevel application + if the toplevel application didn't particularly care. However, at + the point that we actually read audio back, the multiple-section + nature must surface: Multiple bitstream sections do not necessarily + have to have the same number of channels or sampling rate. + + ov_read returns the sequential logical bitstream number currently + being decoded along with the PCM data in order that the toplevel + application can take action on channel/sample rate changes. This + number will be incremented even for streamed (non-seekable) streams + (for seekable streams, it represents the actual logical bitstream + index within the physical bitstream. Note that the accessor + functions above are aware of this dichotomy). + + input values: buffer) a buffer to hold packed PCM data for return + length) the byte length requested to be placed into buffer + bigendianp) should the data be packed LSB first (0) or + MSB first (1) + word) word size for output. currently 1 (byte) or + 2 (16 bit short) + + return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) + 0) EOF + n) number of bytes of PCM actually returned. The + below works on a packet-by-packet basis, so the + return length is not related to the 'length' passed + in, just guaranteed to fit. + + *section) set to the logical bitstream number */ + +long ov_read(OggVorbis_File *vf,char *buffer,int length, + int bigendianp,int word,int sgned,int *bitstream){ + int i,j; + int host_endian = host_is_big_endian(); + + float **pcm; + long samples; + + if(vf->ready_stateready_state==INITSET){ + samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); + if(samples)break; + } + + /* suck in another packet */ + { + int ret=_fetch_and_process_packet(vf,NULL,1,1); + if(ret==OV_EOF) + return(0); + if(ret<=0) + return(ret); + } + + } + + if(samples>0){ + + /* yay! proceed to pack data into the byte buffer */ + + long channels=ov_info(vf,-1)->channels; + long bytespersample=word * channels; + vorbis_fpu_control fpu; + if(samples>length/bytespersample)samples=length/bytespersample; + + if(samples <= 0) + return OV_EINVAL; + + /* a tight loop to pack each size */ + { + int val; + if(word==1){ + int off=(sgned?0:128); + vorbis_fpu_setround(&fpu); + for(j=0;j127)val=127; + else if(val<-128)val=-128; + *buffer++=val+off; + } + vorbis_fpu_restore(fpu); + }else{ + int off=(sgned?0:32768); + + if(host_endian==bigendianp){ + if(sgned){ + + vorbis_fpu_setround(&fpu); + for(i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + *dest=val; + dest+=channels; + } + } + vorbis_fpu_restore(fpu); + + }else{ + + vorbis_fpu_setround(&fpu); + for(i=0;i32767)val=32767; + else if(val<-32768)val=-32768; + *dest=val+off; + dest+=channels; + } + } + vorbis_fpu_restore(fpu); + + } + }else if(bigendianp){ + + vorbis_fpu_setround(&fpu); + for(j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + *buffer++=(val>>8); + *buffer++=(val&0xff); + } + vorbis_fpu_restore(fpu); + + }else{ + int val; + vorbis_fpu_setround(&fpu); + for(j=0;j32767)val=32767; + else if(val<-32768)val=-32768; + val+=off; + *buffer++=(val&0xff); + *buffer++=(val>>8); + } + vorbis_fpu_restore(fpu); + + } + } + } + + vorbis_synthesis_read(&vf->vd,samples); + vf->pcm_offset+=samples; + if(bitstream)*bitstream=vf->current_link; + return(samples*bytespersample); + }else{ + return(samples); + } +} + +/* input values: pcm_channels) a float vector per channel of output + length) the sample length being read by the app + + return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL) + 0) EOF + n) number of samples of PCM actually returned. The + below works on a packet-by-packet basis, so the + return length is not related to the 'length' passed + in, just guaranteed to fit. + + *section) set to the logical bitstream number */ + + + +long ov_read_float(OggVorbis_File *vf,float ***pcm_channels,int length, + int *bitstream){ + + if(vf->ready_stateready_state==INITSET){ + float **pcm; + long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm); + if(samples){ + if(pcm_channels)*pcm_channels=pcm; + if(samples>length)samples=length; + vorbis_synthesis_read(&vf->vd,samples); + vf->pcm_offset+=samples; + if(bitstream)*bitstream=vf->current_link; + return samples; + + } + } + + /* suck in another packet */ + { + int ret=_fetch_and_process_packet(vf,NULL,1,1); + if(ret==OV_EOF)return(0); + if(ret<=0)return(ret); + } + + } +} + +extern float *vorbis_window(vorbis_dsp_state *v,int W); +extern void _analysis_output_always(char *base,int i,float *v,int n,int bark,int dB, + ogg_int64_t off); + +static void _ov_splice(float **pcm,float **lappcm, + int n1, int n2, + int ch1, int ch2, + float *w1, float *w2){ + int i,j; + float *w=w1; + int n=n1; + + if(n1>n2){ + n=n2; + w=w2; + } + + /* splice */ + for(j=0;jready_state==INITSET)break; + /* suck in another packet */ + { + int ret=_fetch_and_process_packet(vf,NULL,1,0); + if(ret<0 && ret!=OV_HOLE)return(ret); + } + } + return 0; +} + +/* make sure vf is INITSET and that we have a primed buffer; if + we're crosslapping at a stream section boundary, this also makes + sure we're sanity checking against the right stream information */ +static int _ov_initprime(OggVorbis_File *vf){ + vorbis_dsp_state *vd=&vf->vd; + while(1){ + if(vf->ready_state==INITSET) + if(vorbis_synthesis_pcmout(vd,NULL))break; + + /* suck in another packet */ + { + int ret=_fetch_and_process_packet(vf,NULL,1,0); + if(ret<0 && ret!=OV_HOLE)return(ret); + } + } + return 0; +} + +/* grab enough data for lapping from vf; this may be in the form of + unreturned, already-decoded pcm, remaining PCM we will need to + decode, or synthetic postextrapolation from last packets. */ +static void _ov_getlap(OggVorbis_File *vf,vorbis_info *vi,vorbis_dsp_state *vd, + float **lappcm,int lapsize){ + int lapcount=0,i; + float **pcm; + + /* try first to decode the lapping data */ + while(lapcountlapsize-lapcount)samples=lapsize-lapcount; + for(i=0;ichannels;i++) + memcpy(lappcm[i]+lapcount,pcm[i],sizeof(**pcm)*samples); + lapcount+=samples; + vorbis_synthesis_read(vd,samples); + }else{ + /* suck in another packet */ + int ret=_fetch_and_process_packet(vf,NULL,1,0); /* do *not* span */ + if(ret==OV_EOF)break; + } + } + if(lapcountvd,&pcm); + if(samples==0){ + for(i=0;ichannels;i++) + memset(lappcm[i]+lapcount,0,sizeof(**pcm)*lapsize-lapcount); + lapcount=lapsize; + }else{ + if(samples>lapsize-lapcount)samples=lapsize-lapcount; + for(i=0;ichannels;i++) + memcpy(lappcm[i]+lapcount,pcm[i],sizeof(**pcm)*samples); + lapcount+=samples; + } + } +} + +/* this sets up crosslapping of a sample by using trailing data from + sample 1 and lapping it into the windowing buffer of sample 2 */ +int ov_crosslap(OggVorbis_File *vf1, OggVorbis_File *vf2){ + vorbis_info *vi1,*vi2; + float **lappcm; + float **pcm; + float *w1,*w2; + int n1,n2,i,ret,hs1,hs2; + + if(vf1==vf2)return(0); /* degenerate case */ + if(vf1->ready_stateready_statechannels); + n1=vorbis_info_blocksize(vi1,0)>>(1+hs1); + n2=vorbis_info_blocksize(vi2,0)>>(1+hs2); + w1=vorbis_window(&vf1->vd,0); + w2=vorbis_window(&vf2->vd,0); + + for(i=0;ichannels;i++) + lappcm[i]=alloca(sizeof(**lappcm)*n1); + + _ov_getlap(vf1,vi1,&vf1->vd,lappcm,n1); + + /* have a lapping buffer from vf1; now to splice it into the lapping + buffer of vf2 */ + /* consolidate and expose the buffer. */ + vorbis_synthesis_lapout(&vf2->vd,&pcm); + _analysis_output_always("pcmL",0,pcm[0],n1*2,0,0,0); + _analysis_output_always("pcmR",0,pcm[1],n1*2,0,0,0); + + /* splice */ + _ov_splice(pcm,lappcm,n1,n2,vi1->channels,vi2->channels,w1,w2); + + /* done */ + return(0); +} + +static int _ov_64_seek_lap(OggVorbis_File *vf,ogg_int64_t pos, + int (*localseek)(OggVorbis_File *,ogg_int64_t)){ + vorbis_info *vi; + float **lappcm; + float **pcm; + float *w1,*w2; + int n1,n2,ch1,ch2,hs; + int i,ret; + + if(vf->ready_statechannels; + n1=vorbis_info_blocksize(vi,0)>>(1+hs); + w1=vorbis_window(&vf->vd,0); /* window arrays from libvorbis are + persistent; even if the decode state + from this link gets dumped, this + window array continues to exist */ + + lappcm=alloca(sizeof(*lappcm)*ch1); + for(i=0;ivd,lappcm,n1); + + /* have lapping data; seek and prime the buffer */ + ret=localseek(vf,pos); + if(ret)return ret; + ret=_ov_initprime(vf); + if(ret)return(ret); + + /* Guard against cross-link changes; they're perfectly legal */ + vi=ov_info(vf,-1); + ch2=vi->channels; + n2=vorbis_info_blocksize(vi,0)>>(1+hs); + w2=vorbis_window(&vf->vd,0); + + /* consolidate and expose the buffer. */ + vorbis_synthesis_lapout(&vf->vd,&pcm); + + /* splice */ + _ov_splice(pcm,lappcm,n1,n2,ch1,ch2,w1,w2); + + /* done */ + return(0); +} + +int ov_raw_seek_lap(OggVorbis_File *vf,ogg_int64_t pos){ + return _ov_64_seek_lap(vf,pos,ov_raw_seek); +} + +int ov_pcm_seek_lap(OggVorbis_File *vf,ogg_int64_t pos){ + return _ov_64_seek_lap(vf,pos,ov_pcm_seek); +} + +int ov_pcm_seek_page_lap(OggVorbis_File *vf,ogg_int64_t pos){ + return _ov_64_seek_lap(vf,pos,ov_pcm_seek_page); +} + +static int _ov_d_seek_lap(OggVorbis_File *vf,double pos, + int (*localseek)(OggVorbis_File *,double)){ + vorbis_info *vi; + float **lappcm; + float **pcm; + float *w1,*w2; + int n1,n2,ch1,ch2,hs; + int i,ret; + + if(vf->ready_statechannels; + n1=vorbis_info_blocksize(vi,0)>>(1+hs); + w1=vorbis_window(&vf->vd,0); /* window arrays from libvorbis are + persistent; even if the decode state + from this link gets dumped, this + window array continues to exist */ + + lappcm=alloca(sizeof(*lappcm)*ch1); + for(i=0;ivd,lappcm,n1); + + /* have lapping data; seek and prime the buffer */ + ret=localseek(vf,pos); + if(ret)return ret; + ret=_ov_initprime(vf); + if(ret)return(ret); + + /* Guard against cross-link changes; they're perfectly legal */ + vi=ov_info(vf,-1); + ch2=vi->channels; + n2=vorbis_info_blocksize(vi,0)>>(1+hs); + w2=vorbis_window(&vf->vd,0); + + /* consolidate and expose the buffer. */ + vorbis_synthesis_lapout(&vf->vd,&pcm); + + /* splice */ + _ov_splice(pcm,lappcm,n1,n2,ch1,ch2,w1,w2); + + /* done */ + return(0); +} + +int ov_time_seek_lap(OggVorbis_File *vf,double pos){ + return _ov_d_seek_lap(vf,pos,ov_time_seek); +} + +int ov_time_seek_page_lap(OggVorbis_File *vf,double pos){ + return _ov_d_seek_lap(vf,pos,ov_time_seek_page); +} diff --git a/src/sound/oggvorbis/vorbissrc/window.h b/src/sound/oggvorbis/vorbissrc/window.h new file mode 100644 index 0000000..a16f48f --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/window.h @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: window functions + last mod: $Id: window.h,v 1.14 2003/09/05 22:17:41 giles Exp $ + + ********************************************************************/ + +#ifndef _V_WINDOW_ +#define _V_WINDOW_ + +extern float *_vorbis_window_get(int n); +extern void _vorbis_apply_window(float *d,int *winno,long *blocksizes, + int lW,int W,int nW); + + +#endif diff --git a/src/sound/oggvorbis/vorbissrc/windowvb.c b/src/sound/oggvorbis/vorbissrc/windowvb.c new file mode 100644 index 0000000..4c4280f --- /dev/null +++ b/src/sound/oggvorbis/vorbissrc/windowvb.c @@ -0,0 +1,2160 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * + * by the XIPHOPHORUS Company http://www.xiph.org/ * + * * + ******************************************************************** + + function: window functions + last mod: $Id: window.c,v 1.23 2003/09/01 22:59:54 xiphmont Exp $ + + ********************************************************************/ + +#include +#include +#include "os.h" +#include "misc.h" + +static float vwin64[32] = { + 0.0009460463F, 0.0085006468F, 0.0235352254F, 0.0458950567F, + 0.0753351908F, 0.1115073077F, 0.1539457973F, 0.2020557475F, + 0.2551056759F, 0.3122276645F, 0.3724270287F, 0.4346027792F, + 0.4975789974F, 0.5601459521F, 0.6211085051F, 0.6793382689F, + 0.7338252629F, 0.7837245849F, 0.8283939355F, 0.8674186656F, + 0.9006222429F, 0.9280614787F, 0.9500073081F, 0.9669131782F, + 0.9793740220F, 0.9880792941F, 0.9937636139F, 0.9971582668F, + 0.9989462667F, 0.9997230082F, 0.9999638688F, 0.9999995525F, +}; + +static float vwin128[64] = { + 0.0002365472F, 0.0021280687F, 0.0059065254F, 0.0115626550F, + 0.0190823442F, 0.0284463735F, 0.0396300935F, 0.0526030430F, + 0.0673285281F, 0.0837631763F, 0.1018564887F, 0.1215504095F, + 0.1427789367F, 0.1654677960F, 0.1895342001F, 0.2148867160F, + 0.2414252576F, 0.2690412240F, 0.2976177952F, 0.3270303960F, + 0.3571473350F, 0.3878306189F, 0.4189369387F, 0.4503188188F, + 0.4818259135F, 0.5133064334F, 0.5446086751F, 0.5755826278F, + 0.6060816248F, 0.6359640047F, 0.6650947483F, 0.6933470543F, + 0.7206038179F, 0.7467589810F, 0.7717187213F, 0.7954024542F, + 0.8177436264F, 0.8386902831F, 0.8582053981F, 0.8762669622F, + 0.8928678298F, 0.9080153310F, 0.9217306608F, 0.9340480615F, + 0.9450138200F, 0.9546851041F, 0.9631286621F, 0.9704194171F, + 0.9766389810F, 0.9818741197F, 0.9862151938F, 0.9897546035F, + 0.9925852598F, 0.9947991032F, 0.9964856900F, 0.9977308602F, + 0.9986155015F, 0.9992144193F, 0.9995953200F, 0.9998179155F, + 0.9999331503F, 0.9999825563F, 0.9999977357F, 0.9999999720F, +}; + +static float vwin256[128] = { + 0.0000591390F, 0.0005321979F, 0.0014780301F, 0.0028960636F, + 0.0047854363F, 0.0071449926F, 0.0099732775F, 0.0132685298F, + 0.0170286741F, 0.0212513119F, 0.0259337111F, 0.0310727950F, + 0.0366651302F, 0.0427069140F, 0.0491939614F, 0.0561216907F, + 0.0634851102F, 0.0712788035F, 0.0794969160F, 0.0881331402F, + 0.0971807028F, 0.1066323515F, 0.1164803426F, 0.1267164297F, + 0.1373318534F, 0.1483173323F, 0.1596630553F, 0.1713586755F, + 0.1833933062F, 0.1957555184F, 0.2084333404F, 0.2214142599F, + 0.2346852280F, 0.2482326664F, 0.2620424757F, 0.2761000481F, + 0.2903902813F, 0.3048975959F, 0.3196059553F, 0.3344988887F, + 0.3495595160F, 0.3647705766F, 0.3801144597F, 0.3955732382F, + 0.4111287047F, 0.4267624093F, 0.4424557009F, 0.4581897696F, + 0.4739456913F, 0.4897044744F, 0.5054471075F, 0.5211546088F, + 0.5368080763F, 0.5523887395F, 0.5678780103F, 0.5832575361F, + 0.5985092508F, 0.6136154277F, 0.6285587300F, 0.6433222619F, + 0.6578896175F, 0.6722449294F, 0.6863729144F, 0.7002589187F, + 0.7138889597F, 0.7272497662F, 0.7403288154F, 0.7531143679F, + 0.7655954985F, 0.7777621249F, 0.7896050322F, 0.8011158947F, + 0.8122872932F, 0.8231127294F, 0.8335866365F, 0.8437043850F, + 0.8534622861F, 0.8628575905F, 0.8718884835F, 0.8805540765F, + 0.8888543947F, 0.8967903616F, 0.9043637797F, 0.9115773078F, + 0.9184344360F, 0.9249394562F, 0.9310974312F, 0.9369141608F, + 0.9423961446F, 0.9475505439F, 0.9523851406F, 0.9569082947F, + 0.9611289005F, 0.9650563408F, 0.9687004405F, 0.9720714191F, + 0.9751798427F, 0.9780365753F, 0.9806527301F, 0.9830396204F, + 0.9852087111F, 0.9871715701F, 0.9889398207F, 0.9905250941F, + 0.9919389832F, 0.9931929973F, 0.9942985174F, 0.9952667537F, + 0.9961087037F, 0.9968351119F, 0.9974564312F, 0.9979827858F, + 0.9984239359F, 0.9987892441F, 0.9990876435F, 0.9993276081F, + 0.9995171241F, 0.9996636648F, 0.9997741654F, 0.9998550016F, + 0.9999119692F, 0.9999502656F, 0.9999744742F, 0.9999885497F, + 0.9999958064F, 0.9999989077F, 0.9999998584F, 0.9999999983F, +}; + +static float vwin512[256] = { + 0.0000147849F, 0.0001330607F, 0.0003695946F, 0.0007243509F, + 0.0011972759F, 0.0017882983F, 0.0024973285F, 0.0033242588F, + 0.0042689632F, 0.0053312973F, 0.0065110982F, 0.0078081841F, + 0.0092223540F, 0.0107533880F, 0.0124010466F, 0.0141650703F, + 0.0160451800F, 0.0180410758F, 0.0201524373F, 0.0223789233F, + 0.0247201710F, 0.0271757958F, 0.0297453914F, 0.0324285286F, + 0.0352247556F, 0.0381335972F, 0.0411545545F, 0.0442871045F, + 0.0475306997F, 0.0508847676F, 0.0543487103F, 0.0579219038F, + 0.0616036982F, 0.0653934164F, 0.0692903546F, 0.0732937809F, + 0.0774029356F, 0.0816170305F, 0.0859352485F, 0.0903567428F, + 0.0948806375F, 0.0995060259F, 0.1042319712F, 0.1090575056F, + 0.1139816300F, 0.1190033137F, 0.1241214941F, 0.1293350764F, + 0.1346429333F, 0.1400439046F, 0.1455367974F, 0.1511203852F, + 0.1567934083F, 0.1625545735F, 0.1684025537F, 0.1743359881F, + 0.1803534820F, 0.1864536069F, 0.1926349000F, 0.1988958650F, + 0.2052349715F, 0.2116506555F, 0.2181413191F, 0.2247053313F, + 0.2313410275F, 0.2380467105F, 0.2448206500F, 0.2516610835F, + 0.2585662164F, 0.2655342226F, 0.2725632448F, 0.2796513950F, + 0.2867967551F, 0.2939973773F, 0.3012512852F, 0.3085564739F, + 0.3159109111F, 0.3233125375F, 0.3307592680F, 0.3382489922F, + 0.3457795756F, 0.3533488602F, 0.3609546657F, 0.3685947904F, + 0.3762670121F, 0.3839690896F, 0.3916987634F, 0.3994537572F, + 0.4072317788F, 0.4150305215F, 0.4228476653F, 0.4306808783F, + 0.4385278181F, 0.4463861329F, 0.4542534630F, 0.4621274424F, + 0.4700057001F, 0.4778858615F, 0.4857655502F, 0.4936423891F, + 0.5015140023F, 0.5093780165F, 0.5172320626F, 0.5250737772F, + 0.5329008043F, 0.5407107971F, 0.5485014192F, 0.5562703465F, + 0.5640152688F, 0.5717338914F, 0.5794239366F, 0.5870831457F, + 0.5947092801F, 0.6023001235F, 0.6098534829F, 0.6173671907F, + 0.6248391059F, 0.6322671161F, 0.6396491384F, 0.6469831217F, + 0.6542670475F, 0.6614989319F, 0.6686768267F, 0.6757988210F, + 0.6828630426F, 0.6898676592F, 0.6968108799F, 0.7036909564F, + 0.7105061843F, 0.7172549043F, 0.7239355032F, 0.7305464154F, + 0.7370861235F, 0.7435531598F, 0.7499461068F, 0.7562635986F, + 0.7625043214F, 0.7686670148F, 0.7747504721F, 0.7807535410F, + 0.7866751247F, 0.7925141825F, 0.7982697296F, 0.8039408387F, + 0.8095266395F, 0.8150263196F, 0.8204391248F, 0.8257643590F, + 0.8310013848F, 0.8361496236F, 0.8412085555F, 0.8461777194F, + 0.8510567129F, 0.8558451924F, 0.8605428730F, 0.8651495278F, + 0.8696649882F, 0.8740891432F, 0.8784219392F, 0.8826633797F, + 0.8868135244F, 0.8908724888F, 0.8948404441F, 0.8987176157F, + 0.9025042831F, 0.9062007791F, 0.9098074886F, 0.9133248482F, + 0.9167533451F, 0.9200935163F, 0.9233459472F, 0.9265112712F, + 0.9295901680F, 0.9325833632F, 0.9354916263F, 0.9383157705F, + 0.9410566504F, 0.9437151618F, 0.9462922398F, 0.9487888576F, + 0.9512060252F, 0.9535447882F, 0.9558062262F, 0.9579914516F, + 0.9601016078F, 0.9621378683F, 0.9641014348F, 0.9659935361F, + 0.9678154261F, 0.9695683830F, 0.9712537071F, 0.9728727198F, + 0.9744267618F, 0.9759171916F, 0.9773453842F, 0.9787127293F, + 0.9800206298F, 0.9812705006F, 0.9824637665F, 0.9836018613F, + 0.9846862258F, 0.9857183066F, 0.9866995544F, 0.9876314227F, + 0.9885153662F, 0.9893528393F, 0.9901452948F, 0.9908941823F, + 0.9916009470F, 0.9922670279F, 0.9928938570F, 0.9934828574F, + 0.9940354423F, 0.9945530133F, 0.9950369595F, 0.9954886562F, + 0.9959094633F, 0.9963007242F, 0.9966637649F, 0.9969998925F, + 0.9973103939F, 0.9975965351F, 0.9978595598F, 0.9981006885F, + 0.9983211172F, 0.9985220166F, 0.9987045311F, 0.9988697776F, + 0.9990188449F, 0.9991527924F, 0.9992726499F, 0.9993794157F, + 0.9994740570F, 0.9995575079F, 0.9996306699F, 0.9996944099F, + 0.9997495605F, 0.9997969190F, 0.9998372465F, 0.9998712678F, + 0.9998996704F, 0.9999231041F, 0.9999421807F, 0.9999574732F, + 0.9999695157F, 0.9999788026F, 0.9999857885F, 0.9999908879F, + 0.9999944746F, 0.9999968817F, 0.9999984010F, 0.9999992833F, + 0.9999997377F, 0.9999999317F, 0.9999999911F, 0.9999999999F, +}; + +static float vwin1024[512] = { + 0.0000036962F, 0.0000332659F, 0.0000924041F, 0.0001811086F, + 0.0002993761F, 0.0004472021F, 0.0006245811F, 0.0008315063F, + 0.0010679699F, 0.0013339631F, 0.0016294757F, 0.0019544965F, + 0.0023090133F, 0.0026930125F, 0.0031064797F, 0.0035493989F, + 0.0040217533F, 0.0045235250F, 0.0050546946F, 0.0056152418F, + 0.0062051451F, 0.0068243817F, 0.0074729278F, 0.0081507582F, + 0.0088578466F, 0.0095941655F, 0.0103596863F, 0.0111543789F, + 0.0119782122F, 0.0128311538F, 0.0137131701F, 0.0146242260F, + 0.0155642855F, 0.0165333111F, 0.0175312640F, 0.0185581042F, + 0.0196137903F, 0.0206982797F, 0.0218115284F, 0.0229534910F, + 0.0241241208F, 0.0253233698F, 0.0265511886F, 0.0278075263F, + 0.0290923308F, 0.0304055484F, 0.0317471241F, 0.0331170013F, + 0.0345151222F, 0.0359414274F, 0.0373958560F, 0.0388783456F, + 0.0403888325F, 0.0419272511F, 0.0434935347F, 0.0450876148F, + 0.0467094213F, 0.0483588828F, 0.0500359261F, 0.0517404765F, + 0.0534724575F, 0.0552317913F, 0.0570183983F, 0.0588321971F, + 0.0606731048F, 0.0625410369F, 0.0644359070F, 0.0663576272F, + 0.0683061077F, 0.0702812571F, 0.0722829821F, 0.0743111878F, + 0.0763657775F, 0.0784466526F, 0.0805537129F, 0.0826868561F, + 0.0848459782F, 0.0870309736F, 0.0892417345F, 0.0914781514F, + 0.0937401128F, 0.0960275056F, 0.0983402145F, 0.1006781223F, + 0.1030411101F, 0.1054290568F, 0.1078418397F, 0.1102793336F, + 0.1127414119F, 0.1152279457F, 0.1177388042F, 0.1202738544F, + 0.1228329618F, 0.1254159892F, 0.1280227980F, 0.1306532471F, + 0.1333071937F, 0.1359844927F, 0.1386849970F, 0.1414085575F, + 0.1441550230F, 0.1469242403F, 0.1497160539F, 0.1525303063F, + 0.1553668381F, 0.1582254875F, 0.1611060909F, 0.1640084822F, + 0.1669324936F, 0.1698779549F, 0.1728446939F, 0.1758325362F, + 0.1788413055F, 0.1818708232F, 0.1849209084F, 0.1879913785F, + 0.1910820485F, 0.1941927312F, 0.1973232376F, 0.2004733764F, + 0.2036429541F, 0.2068317752F, 0.2100396421F, 0.2132663552F, + 0.2165117125F, 0.2197755102F, 0.2230575422F, 0.2263576007F, + 0.2296754753F, 0.2330109540F, 0.2363638225F, 0.2397338646F, + 0.2431208619F, 0.2465245941F, 0.2499448389F, 0.2533813719F, + 0.2568339669F, 0.2603023956F, 0.2637864277F, 0.2672858312F, + 0.2708003718F, 0.2743298135F, 0.2778739186F, 0.2814324472F, + 0.2850051576F, 0.2885918065F, 0.2921921485F, 0.2958059366F, + 0.2994329219F, 0.3030728538F, 0.3067254799F, 0.3103905462F, + 0.3140677969F, 0.3177569747F, 0.3214578205F, 0.3251700736F, + 0.3288934718F, 0.3326277513F, 0.3363726468F, 0.3401278914F, + 0.3438932168F, 0.3476683533F, 0.3514530297F, 0.3552469734F, + 0.3590499106F, 0.3628615659F, 0.3666816630F, 0.3705099239F, + 0.3743460698F, 0.3781898204F, 0.3820408945F, 0.3858990095F, + 0.3897638820F, 0.3936352274F, 0.3975127601F, 0.4013961936F, + 0.4052852405F, 0.4091796123F, 0.4130790198F, 0.4169831732F, + 0.4208917815F, 0.4248045534F, 0.4287211965F, 0.4326414181F, + 0.4365649248F, 0.4404914225F, 0.4444206167F, 0.4483522125F, + 0.4522859146F, 0.4562214270F, 0.4601584538F, 0.4640966984F, + 0.4680358644F, 0.4719756548F, 0.4759157726F, 0.4798559209F, + 0.4837958024F, 0.4877351199F, 0.4916735765F, 0.4956108751F, + 0.4995467188F, 0.5034808109F, 0.5074128550F, 0.5113425550F, + 0.5152696149F, 0.5191937395F, 0.5231146336F, 0.5270320028F, + 0.5309455530F, 0.5348549910F, 0.5387600239F, 0.5426603597F, + 0.5465557070F, 0.5504457754F, 0.5543302752F, 0.5582089175F, + 0.5620814145F, 0.5659474793F, 0.5698068262F, 0.5736591704F, + 0.5775042283F, 0.5813417176F, 0.5851713571F, 0.5889928670F, + 0.5928059689F, 0.5966103856F, 0.6004058415F, 0.6041920626F, + 0.6079687761F, 0.6117357113F, 0.6154925986F, 0.6192391705F, + 0.6229751612F, 0.6267003064F, 0.6304143441F, 0.6341170137F, + 0.6378080569F, 0.6414872173F, 0.6451542405F, 0.6488088741F, + 0.6524508681F, 0.6560799742F, 0.6596959469F, 0.6632985424F, + 0.6668875197F, 0.6704626398F, 0.6740236662F, 0.6775703649F, + 0.6811025043F, 0.6846198554F, 0.6881221916F, 0.6916092892F, + 0.6950809269F, 0.6985368861F, 0.7019769510F, 0.7054009085F, + 0.7088085484F, 0.7121996632F, 0.7155740484F, 0.7189315023F, + 0.7222718263F, 0.7255948245F, 0.7289003043F, 0.7321880760F, + 0.7354579530F, 0.7387097518F, 0.7419432921F, 0.7451583966F, + 0.7483548915F, 0.7515326059F, 0.7546913723F, 0.7578310265F, + 0.7609514077F, 0.7640523581F, 0.7671337237F, 0.7701953535F, + 0.7732371001F, 0.7762588195F, 0.7792603711F, 0.7822416178F, + 0.7852024259F, 0.7881426654F, 0.7910622097F, 0.7939609356F, + 0.7968387237F, 0.7996954579F, 0.8025310261F, 0.8053453193F, + 0.8081382324F, 0.8109096638F, 0.8136595156F, 0.8163876936F, + 0.8190941071F, 0.8217786690F, 0.8244412960F, 0.8270819086F, + 0.8297004305F, 0.8322967896F, 0.8348709171F, 0.8374227481F, + 0.8399522213F, 0.8424592789F, 0.8449438672F, 0.8474059356F, + 0.8498454378F, 0.8522623306F, 0.8546565748F, 0.8570281348F, + 0.8593769787F, 0.8617030779F, 0.8640064080F, 0.8662869477F, + 0.8685446796F, 0.8707795899F, 0.8729916682F, 0.8751809079F, + 0.8773473059F, 0.8794908626F, 0.8816115819F, 0.8837094713F, + 0.8857845418F, 0.8878368079F, 0.8898662874F, 0.8918730019F, + 0.8938569760F, 0.8958182380F, 0.8977568194F, 0.8996727552F, + 0.9015660837F, 0.9034368465F, 0.9052850885F, 0.9071108577F, + 0.9089142057F, 0.9106951869F, 0.9124538591F, 0.9141902832F, + 0.9159045233F, 0.9175966464F, 0.9192667228F, 0.9209148257F, + 0.9225410313F, 0.9241454187F, 0.9257280701F, 0.9272890704F, + 0.9288285075F, 0.9303464720F, 0.9318430576F, 0.9333183603F, + 0.9347724792F, 0.9362055158F, 0.9376175745F, 0.9390087622F, + 0.9403791881F, 0.9417289644F, 0.9430582055F, 0.9443670283F, + 0.9456555521F, 0.9469238986F, 0.9481721917F, 0.9494005577F, + 0.9506091252F, 0.9517980248F, 0.9529673894F, 0.9541173540F, + 0.9552480557F, 0.9563596334F, 0.9574522282F, 0.9585259830F, + 0.9595810428F, 0.9606175542F, 0.9616356656F, 0.9626355274F, + 0.9636172915F, 0.9645811114F, 0.9655271425F, 0.9664555414F, + 0.9673664664F, 0.9682600774F, 0.9691365355F, 0.9699960034F, + 0.9708386448F, 0.9716646250F, 0.9724741103F, 0.9732672685F, + 0.9740442683F, 0.9748052795F, 0.9755504729F, 0.9762800205F, + 0.9769940950F, 0.9776928703F, 0.9783765210F, 0.9790452223F, + 0.9796991504F, 0.9803384823F, 0.9809633954F, 0.9815740679F, + 0.9821706784F, 0.9827534063F, 0.9833224312F, 0.9838779332F, + 0.9844200928F, 0.9849490910F, 0.9854651087F, 0.9859683274F, + 0.9864589286F, 0.9869370940F, 0.9874030054F, 0.9878568447F, + 0.9882987937F, 0.9887290343F, 0.9891477481F, 0.9895551169F, + 0.9899513220F, 0.9903365446F, 0.9907109658F, 0.9910747662F, + 0.9914281260F, 0.9917712252F, 0.9921042433F, 0.9924273593F, + 0.9927407516F, 0.9930445982F, 0.9933390763F, 0.9936243626F, + 0.9939006331F, 0.9941680631F, 0.9944268269F, 0.9946770982F, + 0.9949190498F, 0.9951528537F, 0.9953786808F, 0.9955967011F, + 0.9958070836F, 0.9960099963F, 0.9962056061F, 0.9963940787F, + 0.9965755786F, 0.9967502693F, 0.9969183129F, 0.9970798704F, + 0.9972351013F, 0.9973841640F, 0.9975272151F, 0.9976644103F, + 0.9977959036F, 0.9979218476F, 0.9980423932F, 0.9981576901F, + 0.9982678862F, 0.9983731278F, 0.9984735596F, 0.9985693247F, + 0.9986605645F, 0.9987474186F, 0.9988300248F, 0.9989085193F, + 0.9989830364F, 0.9990537085F, 0.9991206662F, 0.9991840382F, + 0.9992439513F, 0.9993005303F, 0.9993538982F, 0.9994041757F, + 0.9994514817F, 0.9994959330F, 0.9995376444F, 0.9995767286F, + 0.9996132960F, 0.9996474550F, 0.9996793121F, 0.9997089710F, + 0.9997365339F, 0.9997621003F, 0.9997857677F, 0.9998076311F, + 0.9998277836F, 0.9998463156F, 0.9998633155F, 0.9998788692F, + 0.9998930603F, 0.9999059701F, 0.9999176774F, 0.9999282586F, + 0.9999377880F, 0.9999463370F, 0.9999539749F, 0.9999607685F, + 0.9999667820F, 0.9999720773F, 0.9999767136F, 0.9999807479F, + 0.9999842344F, 0.9999872249F, 0.9999897688F, 0.9999919127F, + 0.9999937009F, 0.9999951749F, 0.9999963738F, 0.9999973342F, + 0.9999980900F, 0.9999986724F, 0.9999991103F, 0.9999994297F, + 0.9999996543F, 0.9999998049F, 0.9999999000F, 0.9999999552F, + 0.9999999836F, 0.9999999957F, 0.9999999994F, 1.0000000000F, +}; + +static float vwin2048[1024] = { + 0.0000009241F, 0.0000083165F, 0.0000231014F, 0.0000452785F, + 0.0000748476F, 0.0001118085F, 0.0001561608F, 0.0002079041F, + 0.0002670379F, 0.0003335617F, 0.0004074748F, 0.0004887765F, + 0.0005774661F, 0.0006735427F, 0.0007770054F, 0.0008878533F, + 0.0010060853F, 0.0011317002F, 0.0012646969F, 0.0014050742F, + 0.0015528307F, 0.0017079650F, 0.0018704756F, 0.0020403610F, + 0.0022176196F, 0.0024022497F, 0.0025942495F, 0.0027936173F, + 0.0030003511F, 0.0032144490F, 0.0034359088F, 0.0036647286F, + 0.0039009061F, 0.0041444391F, 0.0043953253F, 0.0046535621F, + 0.0049191472F, 0.0051920781F, 0.0054723520F, 0.0057599664F, + 0.0060549184F, 0.0063572052F, 0.0066668239F, 0.0069837715F, + 0.0073080449F, 0.0076396410F, 0.0079785566F, 0.0083247884F, + 0.0086783330F, 0.0090391871F, 0.0094073470F, 0.0097828092F, + 0.0101655700F, 0.0105556258F, 0.0109529726F, 0.0113576065F, + 0.0117695237F, 0.0121887200F, 0.0126151913F, 0.0130489335F, + 0.0134899422F, 0.0139382130F, 0.0143937415F, 0.0148565233F, + 0.0153265536F, 0.0158038279F, 0.0162883413F, 0.0167800889F, + 0.0172790660F, 0.0177852675F, 0.0182986882F, 0.0188193231F, + 0.0193471668F, 0.0198822141F, 0.0204244594F, 0.0209738974F, + 0.0215305225F, 0.0220943289F, 0.0226653109F, 0.0232434627F, + 0.0238287784F, 0.0244212519F, 0.0250208772F, 0.0256276481F, + 0.0262415582F, 0.0268626014F, 0.0274907711F, 0.0281260608F, + 0.0287684638F, 0.0294179736F, 0.0300745833F, 0.0307382859F, + 0.0314090747F, 0.0320869424F, 0.0327718819F, 0.0334638860F, + 0.0341629474F, 0.0348690586F, 0.0355822122F, 0.0363024004F, + 0.0370296157F, 0.0377638502F, 0.0385050960F, 0.0392533451F, + 0.0400085896F, 0.0407708211F, 0.0415400315F, 0.0423162123F, + 0.0430993552F, 0.0438894515F, 0.0446864926F, 0.0454904698F, + 0.0463013742F, 0.0471191969F, 0.0479439288F, 0.0487755607F, + 0.0496140836F, 0.0504594879F, 0.0513117642F, 0.0521709031F, + 0.0530368949F, 0.0539097297F, 0.0547893979F, 0.0556758894F, + 0.0565691941F, 0.0574693019F, 0.0583762026F, 0.0592898858F, + 0.0602103410F, 0.0611375576F, 0.0620715250F, 0.0630122324F, + 0.0639596688F, 0.0649138234F, 0.0658746848F, 0.0668422421F, + 0.0678164838F, 0.0687973985F, 0.0697849746F, 0.0707792005F, + 0.0717800645F, 0.0727875547F, 0.0738016591F, 0.0748223656F, + 0.0758496620F, 0.0768835359F, 0.0779239751F, 0.0789709668F, + 0.0800244985F, 0.0810845574F, 0.0821511306F, 0.0832242052F, + 0.0843037679F, 0.0853898056F, 0.0864823050F, 0.0875812525F, + 0.0886866347F, 0.0897984378F, 0.0909166480F, 0.0920412513F, + 0.0931722338F, 0.0943095813F, 0.0954532795F, 0.0966033140F, + 0.0977596702F, 0.0989223336F, 0.1000912894F, 0.1012665227F, + 0.1024480185F, 0.1036357616F, 0.1048297369F, 0.1060299290F, + 0.1072363224F, 0.1084489014F, 0.1096676504F, 0.1108925534F, + 0.1121235946F, 0.1133607577F, 0.1146040267F, 0.1158533850F, + 0.1171088163F, 0.1183703040F, 0.1196378312F, 0.1209113812F, + 0.1221909370F, 0.1234764815F, 0.1247679974F, 0.1260654674F, + 0.1273688740F, 0.1286781995F, 0.1299934263F, 0.1313145365F, + 0.1326415121F, 0.1339743349F, 0.1353129866F, 0.1366574490F, + 0.1380077035F, 0.1393637315F, 0.1407255141F, 0.1420930325F, + 0.1434662677F, 0.1448452004F, 0.1462298115F, 0.1476200814F, + 0.1490159906F, 0.1504175195F, 0.1518246482F, 0.1532373569F, + 0.1546556253F, 0.1560794333F, 0.1575087606F, 0.1589435866F, + 0.1603838909F, 0.1618296526F, 0.1632808509F, 0.1647374648F, + 0.1661994731F, 0.1676668546F, 0.1691395880F, 0.1706176516F, + 0.1721010238F, 0.1735896829F, 0.1750836068F, 0.1765827736F, + 0.1780871610F, 0.1795967468F, 0.1811115084F, 0.1826314234F, + 0.1841564689F, 0.1856866221F, 0.1872218600F, 0.1887621595F, + 0.1903074974F, 0.1918578503F, 0.1934131947F, 0.1949735068F, + 0.1965387630F, 0.1981089393F, 0.1996840117F, 0.2012639560F, + 0.2028487479F, 0.2044383630F, 0.2060327766F, 0.2076319642F, + 0.2092359007F, 0.2108445614F, 0.2124579211F, 0.2140759545F, + 0.2156986364F, 0.2173259411F, 0.2189578432F, 0.2205943168F, + 0.2222353361F, 0.2238808751F, 0.2255309076F, 0.2271854073F, + 0.2288443480F, 0.2305077030F, 0.2321754457F, 0.2338475493F, + 0.2355239869F, 0.2372047315F, 0.2388897560F, 0.2405790329F, + 0.2422725350F, 0.2439702347F, 0.2456721043F, 0.2473781159F, + 0.2490882418F, 0.2508024539F, 0.2525207240F, 0.2542430237F, + 0.2559693248F, 0.2576995986F, 0.2594338166F, 0.2611719498F, + 0.2629139695F, 0.2646598466F, 0.2664095520F, 0.2681630564F, + 0.2699203304F, 0.2716813445F, 0.2734460691F, 0.2752144744F, + 0.2769865307F, 0.2787622079F, 0.2805414760F, 0.2823243047F, + 0.2841106637F, 0.2859005227F, 0.2876938509F, 0.2894906179F, + 0.2912907928F, 0.2930943447F, 0.2949012426F, 0.2967114554F, + 0.2985249520F, 0.3003417009F, 0.3021616708F, 0.3039848301F, + 0.3058111471F, 0.3076405901F, 0.3094731273F, 0.3113087266F, + 0.3131473560F, 0.3149889833F, 0.3168335762F, 0.3186811024F, + 0.3205315294F, 0.3223848245F, 0.3242409552F, 0.3260998886F, + 0.3279615918F, 0.3298260319F, 0.3316931758F, 0.3335629903F, + 0.3354354423F, 0.3373104982F, 0.3391881247F, 0.3410682882F, + 0.3429509551F, 0.3448360917F, 0.3467236642F, 0.3486136387F, + 0.3505059811F, 0.3524006575F, 0.3542976336F, 0.3561968753F, + 0.3580983482F, 0.3600020179F, 0.3619078499F, 0.3638158096F, + 0.3657258625F, 0.3676379737F, 0.3695521086F, 0.3714682321F, + 0.3733863094F, 0.3753063055F, 0.3772281852F, 0.3791519134F, + 0.3810774548F, 0.3830047742F, 0.3849338362F, 0.3868646053F, + 0.3887970459F, 0.3907311227F, 0.3926667998F, 0.3946040417F, + 0.3965428125F, 0.3984830765F, 0.4004247978F, 0.4023679403F, + 0.4043124683F, 0.4062583455F, 0.4082055359F, 0.4101540034F, + 0.4121037117F, 0.4140546246F, 0.4160067058F, 0.4179599190F, + 0.4199142277F, 0.4218695956F, 0.4238259861F, 0.4257833627F, + 0.4277416888F, 0.4297009279F, 0.4316610433F, 0.4336219983F, + 0.4355837562F, 0.4375462803F, 0.4395095337F, 0.4414734797F, + 0.4434380815F, 0.4454033021F, 0.4473691046F, 0.4493354521F, + 0.4513023078F, 0.4532696345F, 0.4552373954F, 0.4572055533F, + 0.4591740713F, 0.4611429123F, 0.4631120393F, 0.4650814151F, + 0.4670510028F, 0.4690207650F, 0.4709906649F, 0.4729606651F, + 0.4749307287F, 0.4769008185F, 0.4788708972F, 0.4808409279F, + 0.4828108732F, 0.4847806962F, 0.4867503597F, 0.4887198264F, + 0.4906890593F, 0.4926580213F, 0.4946266753F, 0.4965949840F, + 0.4985629105F, 0.5005304176F, 0.5024974683F, 0.5044640255F, + 0.5064300522F, 0.5083955114F, 0.5103603659F, 0.5123245790F, + 0.5142881136F, 0.5162509328F, 0.5182129997F, 0.5201742774F, + 0.5221347290F, 0.5240943178F, 0.5260530070F, 0.5280107598F, + 0.5299675395F, 0.5319233095F, 0.5338780330F, 0.5358316736F, + 0.5377841946F, 0.5397355596F, 0.5416857320F, 0.5436346755F, + 0.5455823538F, 0.5475287304F, 0.5494737691F, 0.5514174337F, + 0.5533596881F, 0.5553004962F, 0.5572398218F, 0.5591776291F, + 0.5611138821F, 0.5630485449F, 0.5649815818F, 0.5669129570F, + 0.5688426349F, 0.5707705799F, 0.5726967564F, 0.5746211290F, + 0.5765436624F, 0.5784643212F, 0.5803830702F, 0.5822998743F, + 0.5842146984F, 0.5861275076F, 0.5880382669F, 0.5899469416F, + 0.5918534968F, 0.5937578981F, 0.5956601107F, 0.5975601004F, + 0.5994578326F, 0.6013532732F, 0.6032463880F, 0.6051371429F, + 0.6070255039F, 0.6089114372F, 0.6107949090F, 0.6126758856F, + 0.6145543334F, 0.6164302191F, 0.6183035092F, 0.6201741706F, + 0.6220421700F, 0.6239074745F, 0.6257700513F, 0.6276298674F, + 0.6294868903F, 0.6313410873F, 0.6331924262F, 0.6350408745F, + 0.6368864001F, 0.6387289710F, 0.6405685552F, 0.6424051209F, + 0.6442386364F, 0.6460690702F, 0.6478963910F, 0.6497205673F, + 0.6515415682F, 0.6533593625F, 0.6551739194F, 0.6569852082F, + 0.6587931984F, 0.6605978593F, 0.6623991609F, 0.6641970728F, + 0.6659915652F, 0.6677826081F, 0.6695701718F, 0.6713542268F, + 0.6731347437F, 0.6749116932F, 0.6766850461F, 0.6784547736F, + 0.6802208469F, 0.6819832374F, 0.6837419164F, 0.6854968559F, + 0.6872480275F, 0.6889954034F, 0.6907389556F, 0.6924786566F, + 0.6942144788F, 0.6959463950F, 0.6976743780F, 0.6993984008F, + 0.7011184365F, 0.7028344587F, 0.7045464407F, 0.7062543564F, + 0.7079581796F, 0.7096578844F, 0.7113534450F, 0.7130448359F, + 0.7147320316F, 0.7164150070F, 0.7180937371F, 0.7197681970F, + 0.7214383620F, 0.7231042077F, 0.7247657098F, 0.7264228443F, + 0.7280755871F, 0.7297239147F, 0.7313678035F, 0.7330072301F, + 0.7346421715F, 0.7362726046F, 0.7378985069F, 0.7395198556F, + 0.7411366285F, 0.7427488034F, 0.7443563584F, 0.7459592717F, + 0.7475575218F, 0.7491510873F, 0.7507399471F, 0.7523240803F, + 0.7539034661F, 0.7554780839F, 0.7570479136F, 0.7586129349F, + 0.7601731279F, 0.7617284730F, 0.7632789506F, 0.7648245416F, + 0.7663652267F, 0.7679009872F, 0.7694318044F, 0.7709576599F, + 0.7724785354F, 0.7739944130F, 0.7755052749F, 0.7770111035F, + 0.7785118815F, 0.7800075916F, 0.7814982170F, 0.7829837410F, + 0.7844641472F, 0.7859394191F, 0.7874095408F, 0.7888744965F, + 0.7903342706F, 0.7917888476F, 0.7932382124F, 0.7946823501F, + 0.7961212460F, 0.7975548855F, 0.7989832544F, 0.8004063386F, + 0.8018241244F, 0.8032365981F, 0.8046437463F, 0.8060455560F, + 0.8074420141F, 0.8088331080F, 0.8102188253F, 0.8115991536F, + 0.8129740810F, 0.8143435957F, 0.8157076861F, 0.8170663409F, + 0.8184195489F, 0.8197672994F, 0.8211095817F, 0.8224463853F, + 0.8237777001F, 0.8251035161F, 0.8264238235F, 0.8277386129F, + 0.8290478750F, 0.8303516008F, 0.8316497814F, 0.8329424083F, + 0.8342294731F, 0.8355109677F, 0.8367868841F, 0.8380572148F, + 0.8393219523F, 0.8405810893F, 0.8418346190F, 0.8430825345F, + 0.8443248294F, 0.8455614974F, 0.8467925323F, 0.8480179285F, + 0.8492376802F, 0.8504517822F, 0.8516602292F, 0.8528630164F, + 0.8540601391F, 0.8552515928F, 0.8564373733F, 0.8576174766F, + 0.8587918990F, 0.8599606368F, 0.8611236868F, 0.8622810460F, + 0.8634327113F, 0.8645786802F, 0.8657189504F, 0.8668535195F, + 0.8679823857F, 0.8691055472F, 0.8702230025F, 0.8713347503F, + 0.8724407896F, 0.8735411194F, 0.8746357394F, 0.8757246489F, + 0.8768078479F, 0.8778853364F, 0.8789571146F, 0.8800231832F, + 0.8810835427F, 0.8821381942F, 0.8831871387F, 0.8842303777F, + 0.8852679127F, 0.8862997456F, 0.8873258784F, 0.8883463132F, + 0.8893610527F, 0.8903700994F, 0.8913734562F, 0.8923711263F, + 0.8933631129F, 0.8943494196F, 0.8953300500F, 0.8963050083F, + 0.8972742985F, 0.8982379249F, 0.8991958922F, 0.9001482052F, + 0.9010948688F, 0.9020358883F, 0.9029712690F, 0.9039010165F, + 0.9048251367F, 0.9057436357F, 0.9066565195F, 0.9075637946F, + 0.9084654678F, 0.9093615456F, 0.9102520353F, 0.9111369440F, + 0.9120162792F, 0.9128900484F, 0.9137582595F, 0.9146209204F, + 0.9154780394F, 0.9163296248F, 0.9171756853F, 0.9180162296F, + 0.9188512667F, 0.9196808057F, 0.9205048559F, 0.9213234270F, + 0.9221365285F, 0.9229441704F, 0.9237463629F, 0.9245431160F, + 0.9253344404F, 0.9261203465F, 0.9269008453F, 0.9276759477F, + 0.9284456648F, 0.9292100080F, 0.9299689889F, 0.9307226190F, + 0.9314709103F, 0.9322138747F, 0.9329515245F, 0.9336838721F, + 0.9344109300F, 0.9351327108F, 0.9358492275F, 0.9365604931F, + 0.9372665208F, 0.9379673239F, 0.9386629160F, 0.9393533107F, + 0.9400385220F, 0.9407185637F, 0.9413934501F, 0.9420631954F, + 0.9427278141F, 0.9433873208F, 0.9440417304F, 0.9446910576F, + 0.9453353176F, 0.9459745255F, 0.9466086968F, 0.9472378469F, + 0.9478619915F, 0.9484811463F, 0.9490953274F, 0.9497045506F, + 0.9503088323F, 0.9509081888F, 0.9515026365F, 0.9520921921F, + 0.9526768723F, 0.9532566940F, 0.9538316742F, 0.9544018300F, + 0.9549671786F, 0.9555277375F, 0.9560835241F, 0.9566345562F, + 0.9571808513F, 0.9577224275F, 0.9582593027F, 0.9587914949F, + 0.9593190225F, 0.9598419038F, 0.9603601571F, 0.9608738012F, + 0.9613828546F, 0.9618873361F, 0.9623872646F, 0.9628826591F, + 0.9633735388F, 0.9638599227F, 0.9643418303F, 0.9648192808F, + 0.9652922939F, 0.9657608890F, 0.9662250860F, 0.9666849046F, + 0.9671403646F, 0.9675914861F, 0.9680382891F, 0.9684807937F, + 0.9689190202F, 0.9693529890F, 0.9697827203F, 0.9702082347F, + 0.9706295529F, 0.9710466953F, 0.9714596828F, 0.9718685362F, + 0.9722732762F, 0.9726739240F, 0.9730705005F, 0.9734630267F, + 0.9738515239F, 0.9742360134F, 0.9746165163F, 0.9749930540F, + 0.9753656481F, 0.9757343198F, 0.9760990909F, 0.9764599829F, + 0.9768170175F, 0.9771702164F, 0.9775196013F, 0.9778651941F, + 0.9782070167F, 0.9785450909F, 0.9788794388F, 0.9792100824F, + 0.9795370437F, 0.9798603449F, 0.9801800080F, 0.9804960554F, + 0.9808085092F, 0.9811173916F, 0.9814227251F, 0.9817245318F, + 0.9820228343F, 0.9823176549F, 0.9826090160F, 0.9828969402F, + 0.9831814498F, 0.9834625674F, 0.9837403156F, 0.9840147169F, + 0.9842857939F, 0.9845535692F, 0.9848180654F, 0.9850793052F, + 0.9853373113F, 0.9855921062F, 0.9858437127F, 0.9860921535F, + 0.9863374512F, 0.9865796287F, 0.9868187085F, 0.9870547136F, + 0.9872876664F, 0.9875175899F, 0.9877445067F, 0.9879684396F, + 0.9881894112F, 0.9884074444F, 0.9886225619F, 0.9888347863F, + 0.9890441404F, 0.9892506468F, 0.9894543284F, 0.9896552077F, + 0.9898533074F, 0.9900486502F, 0.9902412587F, 0.9904311555F, + 0.9906183633F, 0.9908029045F, 0.9909848019F, 0.9911640779F, + 0.9913407550F, 0.9915148557F, 0.9916864025F, 0.9918554179F, + 0.9920219241F, 0.9921859437F, 0.9923474989F, 0.9925066120F, + 0.9926633054F, 0.9928176012F, 0.9929695218F, 0.9931190891F, + 0.9932663254F, 0.9934112527F, 0.9935538932F, 0.9936942686F, + 0.9938324012F, 0.9939683126F, 0.9941020248F, 0.9942335597F, + 0.9943629388F, 0.9944901841F, 0.9946153170F, 0.9947383593F, + 0.9948593325F, 0.9949782579F, 0.9950951572F, 0.9952100516F, + 0.9953229625F, 0.9954339111F, 0.9955429186F, 0.9956500062F, + 0.9957551948F, 0.9958585056F, 0.9959599593F, 0.9960595769F, + 0.9961573792F, 0.9962533869F, 0.9963476206F, 0.9964401009F, + 0.9965308483F, 0.9966198833F, 0.9967072261F, 0.9967928971F, + 0.9968769164F, 0.9969593041F, 0.9970400804F, 0.9971192651F, + 0.9971968781F, 0.9972729391F, 0.9973474680F, 0.9974204842F, + 0.9974920074F, 0.9975620569F, 0.9976306521F, 0.9976978122F, + 0.9977635565F, 0.9978279039F, 0.9978908736F, 0.9979524842F, + 0.9980127547F, 0.9980717037F, 0.9981293499F, 0.9981857116F, + 0.9982408073F, 0.9982946554F, 0.9983472739F, 0.9983986810F, + 0.9984488947F, 0.9984979328F, 0.9985458132F, 0.9985925534F, + 0.9986381711F, 0.9986826838F, 0.9987261086F, 0.9987684630F, + 0.9988097640F, 0.9988500286F, 0.9988892738F, 0.9989275163F, + 0.9989647727F, 0.9990010597F, 0.9990363938F, 0.9990707911F, + 0.9991042679F, 0.9991368404F, 0.9991685244F, 0.9991993358F, + 0.9992292905F, 0.9992584038F, 0.9992866914F, 0.9993141686F, + 0.9993408506F, 0.9993667526F, 0.9993918895F, 0.9994162761F, + 0.9994399273F, 0.9994628576F, 0.9994850815F, 0.9995066133F, + 0.9995274672F, 0.9995476574F, 0.9995671978F, 0.9995861021F, + 0.9996043841F, 0.9996220573F, 0.9996391352F, 0.9996556310F, + 0.9996715579F, 0.9996869288F, 0.9997017568F, 0.9997160543F, + 0.9997298342F, 0.9997431088F, 0.9997558905F, 0.9997681914F, + 0.9997800236F, 0.9997913990F, 0.9998023292F, 0.9998128261F, + 0.9998229009F, 0.9998325650F, 0.9998418296F, 0.9998507058F, + 0.9998592044F, 0.9998673362F, 0.9998751117F, 0.9998825415F, + 0.9998896358F, 0.9998964047F, 0.9999028584F, 0.9999090066F, + 0.9999148590F, 0.9999204253F, 0.9999257148F, 0.9999307368F, + 0.9999355003F, 0.9999400144F, 0.9999442878F, 0.9999483293F, + 0.9999521472F, 0.9999557499F, 0.9999591457F, 0.9999623426F, + 0.9999653483F, 0.9999681708F, 0.9999708175F, 0.9999732959F, + 0.9999756132F, 0.9999777765F, 0.9999797928F, 0.9999816688F, + 0.9999834113F, 0.9999850266F, 0.9999865211F, 0.9999879009F, + 0.9999891721F, 0.9999903405F, 0.9999914118F, 0.9999923914F, + 0.9999932849F, 0.9999940972F, 0.9999948336F, 0.9999954989F, + 0.9999960978F, 0.9999966349F, 0.9999971146F, 0.9999975411F, + 0.9999979185F, 0.9999982507F, 0.9999985414F, 0.9999987944F, + 0.9999990129F, 0.9999992003F, 0.9999993596F, 0.9999994939F, + 0.9999996059F, 0.9999996981F, 0.9999997732F, 0.9999998333F, + 0.9999998805F, 0.9999999170F, 0.9999999444F, 0.9999999643F, + 0.9999999784F, 0.9999999878F, 0.9999999937F, 0.9999999972F, + 0.9999999990F, 0.9999999997F, 1.0000000000F, 1.0000000000F, +}; + +static float vwin4096[2048] = { + 0.0000002310F, 0.0000020791F, 0.0000057754F, 0.0000113197F, + 0.0000187121F, 0.0000279526F, 0.0000390412F, 0.0000519777F, + 0.0000667623F, 0.0000833949F, 0.0001018753F, 0.0001222036F, + 0.0001443798F, 0.0001684037F, 0.0001942754F, 0.0002219947F, + 0.0002515616F, 0.0002829761F, 0.0003162380F, 0.0003513472F, + 0.0003883038F, 0.0004271076F, 0.0004677584F, 0.0005102563F, + 0.0005546011F, 0.0006007928F, 0.0006488311F, 0.0006987160F, + 0.0007504474F, 0.0008040251F, 0.0008594490F, 0.0009167191F, + 0.0009758351F, 0.0010367969F, 0.0010996044F, 0.0011642574F, + 0.0012307558F, 0.0012990994F, 0.0013692880F, 0.0014413216F, + 0.0015151998F, 0.0015909226F, 0.0016684898F, 0.0017479011F, + 0.0018291565F, 0.0019122556F, 0.0019971983F, 0.0020839845F, + 0.0021726138F, 0.0022630861F, 0.0023554012F, 0.0024495588F, + 0.0025455588F, 0.0026434008F, 0.0027430847F, 0.0028446103F, + 0.0029479772F, 0.0030531853F, 0.0031602342F, 0.0032691238F, + 0.0033798538F, 0.0034924239F, 0.0036068338F, 0.0037230833F, + 0.0038411721F, 0.0039610999F, 0.0040828664F, 0.0042064714F, + 0.0043319145F, 0.0044591954F, 0.0045883139F, 0.0047192696F, + 0.0048520622F, 0.0049866914F, 0.0051231569F, 0.0052614583F, + 0.0054015953F, 0.0055435676F, 0.0056873748F, 0.0058330166F, + 0.0059804926F, 0.0061298026F, 0.0062809460F, 0.0064339226F, + 0.0065887320F, 0.0067453738F, 0.0069038476F, 0.0070641531F, + 0.0072262899F, 0.0073902575F, 0.0075560556F, 0.0077236838F, + 0.0078931417F, 0.0080644288F, 0.0082375447F, 0.0084124891F, + 0.0085892615F, 0.0087678614F, 0.0089482885F, 0.0091305422F, + 0.0093146223F, 0.0095005281F, 0.0096882592F, 0.0098778153F, + 0.0100691958F, 0.0102624002F, 0.0104574281F, 0.0106542791F, + 0.0108529525F, 0.0110534480F, 0.0112557651F, 0.0114599032F, + 0.0116658618F, 0.0118736405F, 0.0120832387F, 0.0122946560F, + 0.0125078917F, 0.0127229454F, 0.0129398166F, 0.0131585046F, + 0.0133790090F, 0.0136013292F, 0.0138254647F, 0.0140514149F, + 0.0142791792F, 0.0145087572F, 0.0147401481F, 0.0149733515F, + 0.0152083667F, 0.0154451932F, 0.0156838304F, 0.0159242777F, + 0.0161665345F, 0.0164106001F, 0.0166564741F, 0.0169041557F, + 0.0171536443F, 0.0174049393F, 0.0176580401F, 0.0179129461F, + 0.0181696565F, 0.0184281708F, 0.0186884883F, 0.0189506084F, + 0.0192145303F, 0.0194802535F, 0.0197477772F, 0.0200171008F, + 0.0202882236F, 0.0205611449F, 0.0208358639F, 0.0211123801F, + 0.0213906927F, 0.0216708011F, 0.0219527043F, 0.0222364019F, + 0.0225218930F, 0.0228091769F, 0.0230982529F, 0.0233891203F, + 0.0236817782F, 0.0239762259F, 0.0242724628F, 0.0245704880F, + 0.0248703007F, 0.0251719002F, 0.0254752858F, 0.0257804565F, + 0.0260874117F, 0.0263961506F, 0.0267066722F, 0.0270189760F, + 0.0273330609F, 0.0276489263F, 0.0279665712F, 0.0282859949F, + 0.0286071966F, 0.0289301753F, 0.0292549303F, 0.0295814607F, + 0.0299097656F, 0.0302398442F, 0.0305716957F, 0.0309053191F, + 0.0312407135F, 0.0315778782F, 0.0319168122F, 0.0322575145F, + 0.0325999844F, 0.0329442209F, 0.0332902231F, 0.0336379900F, + 0.0339875208F, 0.0343388146F, 0.0346918703F, 0.0350466871F, + 0.0354032640F, 0.0357616000F, 0.0361216943F, 0.0364835458F, + 0.0368471535F, 0.0372125166F, 0.0375796339F, 0.0379485046F, + 0.0383191276F, 0.0386915020F, 0.0390656267F, 0.0394415008F, + 0.0398191231F, 0.0401984927F, 0.0405796086F, 0.0409624698F, + 0.0413470751F, 0.0417334235F, 0.0421215141F, 0.0425113457F, + 0.0429029172F, 0.0432962277F, 0.0436912760F, 0.0440880610F, + 0.0444865817F, 0.0448868370F, 0.0452888257F, 0.0456925468F, + 0.0460979992F, 0.0465051816F, 0.0469140931F, 0.0473247325F, + 0.0477370986F, 0.0481511902F, 0.0485670064F, 0.0489845458F, + 0.0494038074F, 0.0498247899F, 0.0502474922F, 0.0506719131F, + 0.0510980514F, 0.0515259060F, 0.0519554756F, 0.0523867590F, + 0.0528197550F, 0.0532544624F, 0.0536908800F, 0.0541290066F, + 0.0545688408F, 0.0550103815F, 0.0554536274F, 0.0558985772F, + 0.0563452297F, 0.0567935837F, 0.0572436377F, 0.0576953907F, + 0.0581488412F, 0.0586039880F, 0.0590608297F, 0.0595193651F, + 0.0599795929F, 0.0604415117F, 0.0609051202F, 0.0613704170F, + 0.0618374009F, 0.0623060704F, 0.0627764243F, 0.0632484611F, + 0.0637221795F, 0.0641975781F, 0.0646746555F, 0.0651534104F, + 0.0656338413F, 0.0661159469F, 0.0665997257F, 0.0670851763F, + 0.0675722973F, 0.0680610873F, 0.0685515448F, 0.0690436684F, + 0.0695374567F, 0.0700329081F, 0.0705300213F, 0.0710287947F, + 0.0715292269F, 0.0720313163F, 0.0725350616F, 0.0730404612F, + 0.0735475136F, 0.0740562172F, 0.0745665707F, 0.0750785723F, + 0.0755922207F, 0.0761075143F, 0.0766244515F, 0.0771430307F, + 0.0776632505F, 0.0781851092F, 0.0787086052F, 0.0792337371F, + 0.0797605032F, 0.0802889018F, 0.0808189315F, 0.0813505905F, + 0.0818838773F, 0.0824187903F, 0.0829553277F, 0.0834934881F, + 0.0840332697F, 0.0845746708F, 0.0851176899F, 0.0856623252F, + 0.0862085751F, 0.0867564379F, 0.0873059119F, 0.0878569954F, + 0.0884096867F, 0.0889639840F, 0.0895198858F, 0.0900773902F, + 0.0906364955F, 0.0911972000F, 0.0917595019F, 0.0923233995F, + 0.0928888909F, 0.0934559745F, 0.0940246485F, 0.0945949110F, + 0.0951667604F, 0.0957401946F, 0.0963152121F, 0.0968918109F, + 0.0974699893F, 0.0980497454F, 0.0986310773F, 0.0992139832F, + 0.0997984614F, 0.1003845098F, 0.1009721267F, 0.1015613101F, + 0.1021520582F, 0.1027443692F, 0.1033382410F, 0.1039336718F, + 0.1045306597F, 0.1051292027F, 0.1057292990F, 0.1063309466F, + 0.1069341435F, 0.1075388878F, 0.1081451776F, 0.1087530108F, + 0.1093623856F, 0.1099732998F, 0.1105857516F, 0.1111997389F, + 0.1118152597F, 0.1124323121F, 0.1130508939F, 0.1136710032F, + 0.1142926379F, 0.1149157960F, 0.1155404755F, 0.1161666742F, + 0.1167943901F, 0.1174236211F, 0.1180543652F, 0.1186866202F, + 0.1193203841F, 0.1199556548F, 0.1205924300F, 0.1212307078F, + 0.1218704860F, 0.1225117624F, 0.1231545349F, 0.1237988013F, + 0.1244445596F, 0.1250918074F, 0.1257405427F, 0.1263907632F, + 0.1270424667F, 0.1276956512F, 0.1283503142F, 0.1290064537F, + 0.1296640674F, 0.1303231530F, 0.1309837084F, 0.1316457312F, + 0.1323092193F, 0.1329741703F, 0.1336405820F, 0.1343084520F, + 0.1349777782F, 0.1356485582F, 0.1363207897F, 0.1369944704F, + 0.1376695979F, 0.1383461700F, 0.1390241842F, 0.1397036384F, + 0.1403845300F, 0.1410668567F, 0.1417506162F, 0.1424358061F, + 0.1431224240F, 0.1438104674F, 0.1444999341F, 0.1451908216F, + 0.1458831274F, 0.1465768492F, 0.1472719844F, 0.1479685308F, + 0.1486664857F, 0.1493658468F, 0.1500666115F, 0.1507687775F, + 0.1514723422F, 0.1521773031F, 0.1528836577F, 0.1535914035F, + 0.1543005380F, 0.1550110587F, 0.1557229631F, 0.1564362485F, + 0.1571509124F, 0.1578669524F, 0.1585843657F, 0.1593031499F, + 0.1600233024F, 0.1607448205F, 0.1614677017F, 0.1621919433F, + 0.1629175428F, 0.1636444975F, 0.1643728047F, 0.1651024619F, + 0.1658334665F, 0.1665658156F, 0.1672995067F, 0.1680345371F, + 0.1687709041F, 0.1695086050F, 0.1702476372F, 0.1709879978F, + 0.1717296843F, 0.1724726938F, 0.1732170237F, 0.1739626711F, + 0.1747096335F, 0.1754579079F, 0.1762074916F, 0.1769583819F, + 0.1777105760F, 0.1784640710F, 0.1792188642F, 0.1799749529F, + 0.1807323340F, 0.1814910049F, 0.1822509628F, 0.1830122046F, + 0.1837747277F, 0.1845385292F, 0.1853036062F, 0.1860699558F, + 0.1868375751F, 0.1876064613F, 0.1883766114F, 0.1891480226F, + 0.1899206919F, 0.1906946164F, 0.1914697932F, 0.1922462194F, + 0.1930238919F, 0.1938028079F, 0.1945829643F, 0.1953643583F, + 0.1961469868F, 0.1969308468F, 0.1977159353F, 0.1985022494F, + 0.1992897859F, 0.2000785420F, 0.2008685145F, 0.2016597005F, + 0.2024520968F, 0.2032457005F, 0.2040405084F, 0.2048365175F, + 0.2056337247F, 0.2064321269F, 0.2072317211F, 0.2080325041F, + 0.2088344727F, 0.2096376240F, 0.2104419547F, 0.2112474618F, + 0.2120541420F, 0.2128619923F, 0.2136710094F, 0.2144811902F, + 0.2152925315F, 0.2161050301F, 0.2169186829F, 0.2177334866F, + 0.2185494381F, 0.2193665340F, 0.2201847712F, 0.2210041465F, + 0.2218246565F, 0.2226462981F, 0.2234690680F, 0.2242929629F, + 0.2251179796F, 0.2259441147F, 0.2267713650F, 0.2275997272F, + 0.2284291979F, 0.2292597739F, 0.2300914518F, 0.2309242283F, + 0.2317581001F, 0.2325930638F, 0.2334291160F, 0.2342662534F, + 0.2351044727F, 0.2359437703F, 0.2367841431F, 0.2376255875F, + 0.2384681001F, 0.2393116776F, 0.2401563165F, 0.2410020134F, + 0.2418487649F, 0.2426965675F, 0.2435454178F, 0.2443953122F, + 0.2452462474F, 0.2460982199F, 0.2469512262F, 0.2478052628F, + 0.2486603262F, 0.2495164129F, 0.2503735194F, 0.2512316421F, + 0.2520907776F, 0.2529509222F, 0.2538120726F, 0.2546742250F, + 0.2555373760F, 0.2564015219F, 0.2572666593F, 0.2581327845F, + 0.2589998939F, 0.2598679840F, 0.2607370510F, 0.2616070916F, + 0.2624781019F, 0.2633500783F, 0.2642230173F, 0.2650969152F, + 0.2659717684F, 0.2668475731F, 0.2677243257F, 0.2686020226F, + 0.2694806601F, 0.2703602344F, 0.2712407419F, 0.2721221789F, + 0.2730045417F, 0.2738878265F, 0.2747720297F, 0.2756571474F, + 0.2765431760F, 0.2774301117F, 0.2783179508F, 0.2792066895F, + 0.2800963240F, 0.2809868505F, 0.2818782654F, 0.2827705647F, + 0.2836637447F, 0.2845578016F, 0.2854527315F, 0.2863485307F, + 0.2872451953F, 0.2881427215F, 0.2890411055F, 0.2899403433F, + 0.2908404312F, 0.2917413654F, 0.2926431418F, 0.2935457567F, + 0.2944492061F, 0.2953534863F, 0.2962585932F, 0.2971645230F, + 0.2980712717F, 0.2989788356F, 0.2998872105F, 0.3007963927F, + 0.3017063781F, 0.3026171629F, 0.3035287430F, 0.3044411145F, + 0.3053542736F, 0.3062682161F, 0.3071829381F, 0.3080984356F, + 0.3090147047F, 0.3099317413F, 0.3108495414F, 0.3117681011F, + 0.3126874163F, 0.3136074830F, 0.3145282972F, 0.3154498548F, + 0.3163721517F, 0.3172951841F, 0.3182189477F, 0.3191434385F, + 0.3200686525F, 0.3209945856F, 0.3219212336F, 0.3228485927F, + 0.3237766585F, 0.3247054271F, 0.3256348943F, 0.3265650560F, + 0.3274959081F, 0.3284274465F, 0.3293596671F, 0.3302925657F, + 0.3312261382F, 0.3321603804F, 0.3330952882F, 0.3340308574F, + 0.3349670838F, 0.3359039634F, 0.3368414919F, 0.3377796651F, + 0.3387184789F, 0.3396579290F, 0.3405980113F, 0.3415387216F, + 0.3424800556F, 0.3434220091F, 0.3443645779F, 0.3453077578F, + 0.3462515446F, 0.3471959340F, 0.3481409217F, 0.3490865036F, + 0.3500326754F, 0.3509794328F, 0.3519267715F, 0.3528746873F, + 0.3538231759F, 0.3547722330F, 0.3557218544F, 0.3566720357F, + 0.3576227727F, 0.3585740610F, 0.3595258964F, 0.3604782745F, + 0.3614311910F, 0.3623846417F, 0.3633386221F, 0.3642931280F, + 0.3652481549F, 0.3662036987F, 0.3671597548F, 0.3681163191F, + 0.3690733870F, 0.3700309544F, 0.3709890167F, 0.3719475696F, + 0.3729066089F, 0.3738661299F, 0.3748261285F, 0.3757866002F, + 0.3767475406F, 0.3777089453F, 0.3786708100F, 0.3796331302F, + 0.3805959014F, 0.3815591194F, 0.3825227796F, 0.3834868777F, + 0.3844514093F, 0.3854163698F, 0.3863817549F, 0.3873475601F, + 0.3883137810F, 0.3892804131F, 0.3902474521F, 0.3912148933F, + 0.3921827325F, 0.3931509650F, 0.3941195865F, 0.3950885925F, + 0.3960579785F, 0.3970277400F, 0.3979978725F, 0.3989683716F, + 0.3999392328F, 0.4009104516F, 0.4018820234F, 0.4028539438F, + 0.4038262084F, 0.4047988125F, 0.4057717516F, 0.4067450214F, + 0.4077186172F, 0.4086925345F, 0.4096667688F, 0.4106413155F, + 0.4116161703F, 0.4125913284F, 0.4135667854F, 0.4145425368F, + 0.4155185780F, 0.4164949044F, 0.4174715116F, 0.4184483949F, + 0.4194255498F, 0.4204029718F, 0.4213806563F, 0.4223585987F, + 0.4233367946F, 0.4243152392F, 0.4252939281F, 0.4262728566F, + 0.4272520202F, 0.4282314144F, 0.4292110345F, 0.4301908760F, + 0.4311709343F, 0.4321512047F, 0.4331316828F, 0.4341123639F, + 0.4350932435F, 0.4360743168F, 0.4370555794F, 0.4380370267F, + 0.4390186540F, 0.4400004567F, 0.4409824303F, 0.4419645701F, + 0.4429468716F, 0.4439293300F, 0.4449119409F, 0.4458946996F, + 0.4468776014F, 0.4478606418F, 0.4488438162F, 0.4498271199F, + 0.4508105483F, 0.4517940967F, 0.4527777607F, 0.4537615355F, + 0.4547454165F, 0.4557293991F, 0.4567134786F, 0.4576976505F, + 0.4586819101F, 0.4596662527F, 0.4606506738F, 0.4616351687F, + 0.4626197328F, 0.4636043614F, 0.4645890499F, 0.4655737936F, + 0.4665585880F, 0.4675434284F, 0.4685283101F, 0.4695132286F, + 0.4704981791F, 0.4714831570F, 0.4724681577F, 0.4734531766F, + 0.4744382089F, 0.4754232501F, 0.4764082956F, 0.4773933406F, + 0.4783783806F, 0.4793634108F, 0.4803484267F, 0.4813334237F, + 0.4823183969F, 0.4833033419F, 0.4842882540F, 0.4852731285F, + 0.4862579608F, 0.4872427462F, 0.4882274802F, 0.4892121580F, + 0.4901967751F, 0.4911813267F, 0.4921658083F, 0.4931502151F, + 0.4941345427F, 0.4951187863F, 0.4961029412F, 0.4970870029F, + 0.4980709667F, 0.4990548280F, 0.5000385822F, 0.5010222245F, + 0.5020057505F, 0.5029891553F, 0.5039724345F, 0.5049555834F, + 0.5059385973F, 0.5069214716F, 0.5079042018F, 0.5088867831F, + 0.5098692110F, 0.5108514808F, 0.5118335879F, 0.5128155277F, + 0.5137972956F, 0.5147788869F, 0.5157602971F, 0.5167415215F, + 0.5177225555F, 0.5187033945F, 0.5196840339F, 0.5206644692F, + 0.5216446956F, 0.5226247086F, 0.5236045035F, 0.5245840759F, + 0.5255634211F, 0.5265425344F, 0.5275214114F, 0.5285000474F, + 0.5294784378F, 0.5304565781F, 0.5314344637F, 0.5324120899F, + 0.5333894522F, 0.5343665461F, 0.5353433670F, 0.5363199102F, + 0.5372961713F, 0.5382721457F, 0.5392478287F, 0.5402232159F, + 0.5411983027F, 0.5421730845F, 0.5431475569F, 0.5441217151F, + 0.5450955548F, 0.5460690714F, 0.5470422602F, 0.5480151169F, + 0.5489876368F, 0.5499598155F, 0.5509316484F, 0.5519031310F, + 0.5528742587F, 0.5538450271F, 0.5548154317F, 0.5557854680F, + 0.5567551314F, 0.5577244174F, 0.5586933216F, 0.5596618395F, + 0.5606299665F, 0.5615976983F, 0.5625650302F, 0.5635319580F, + 0.5644984770F, 0.5654645828F, 0.5664302709F, 0.5673955370F, + 0.5683603765F, 0.5693247850F, 0.5702887580F, 0.5712522912F, + 0.5722153800F, 0.5731780200F, 0.5741402069F, 0.5751019362F, + 0.5760632034F, 0.5770240042F, 0.5779843341F, 0.5789441889F, + 0.5799035639F, 0.5808624549F, 0.5818208575F, 0.5827787673F, + 0.5837361800F, 0.5846930910F, 0.5856494961F, 0.5866053910F, + 0.5875607712F, 0.5885156324F, 0.5894699703F, 0.5904237804F, + 0.5913770586F, 0.5923298004F, 0.5932820016F, 0.5942336578F, + 0.5951847646F, 0.5961353179F, 0.5970853132F, 0.5980347464F, + 0.5989836131F, 0.5999319090F, 0.6008796298F, 0.6018267713F, + 0.6027733292F, 0.6037192993F, 0.6046646773F, 0.6056094589F, + 0.6065536400F, 0.6074972162F, 0.6084401833F, 0.6093825372F, + 0.6103242736F, 0.6112653884F, 0.6122058772F, 0.6131457359F, + 0.6140849604F, 0.6150235464F, 0.6159614897F, 0.6168987862F, + 0.6178354318F, 0.6187714223F, 0.6197067535F, 0.6206414213F, + 0.6215754215F, 0.6225087501F, 0.6234414028F, 0.6243733757F, + 0.6253046646F, 0.6262352654F, 0.6271651739F, 0.6280943862F, + 0.6290228982F, 0.6299507057F, 0.6308778048F, 0.6318041913F, + 0.6327298612F, 0.6336548105F, 0.6345790352F, 0.6355025312F, + 0.6364252945F, 0.6373473211F, 0.6382686070F, 0.6391891483F, + 0.6401089409F, 0.6410279808F, 0.6419462642F, 0.6428637869F, + 0.6437805452F, 0.6446965350F, 0.6456117524F, 0.6465261935F, + 0.6474398544F, 0.6483527311F, 0.6492648197F, 0.6501761165F, + 0.6510866174F, 0.6519963186F, 0.6529052162F, 0.6538133064F, + 0.6547205854F, 0.6556270492F, 0.6565326941F, 0.6574375162F, + 0.6583415117F, 0.6592446769F, 0.6601470079F, 0.6610485009F, + 0.6619491521F, 0.6628489578F, 0.6637479143F, 0.6646460177F, + 0.6655432643F, 0.6664396505F, 0.6673351724F, 0.6682298264F, + 0.6691236087F, 0.6700165157F, 0.6709085436F, 0.6717996889F, + 0.6726899478F, 0.6735793167F, 0.6744677918F, 0.6753553697F, + 0.6762420466F, 0.6771278190F, 0.6780126832F, 0.6788966357F, + 0.6797796728F, 0.6806617909F, 0.6815429866F, 0.6824232562F, + 0.6833025961F, 0.6841810030F, 0.6850584731F, 0.6859350031F, + 0.6868105894F, 0.6876852284F, 0.6885589168F, 0.6894316510F, + 0.6903034275F, 0.6911742430F, 0.6920440939F, 0.6929129769F, + 0.6937808884F, 0.6946478251F, 0.6955137837F, 0.6963787606F, + 0.6972427525F, 0.6981057560F, 0.6989677678F, 0.6998287845F, + 0.7006888028F, 0.7015478194F, 0.7024058309F, 0.7032628340F, + 0.7041188254F, 0.7049738019F, 0.7058277601F, 0.7066806969F, + 0.7075326089F, 0.7083834929F, 0.7092333457F, 0.7100821640F, + 0.7109299447F, 0.7117766846F, 0.7126223804F, 0.7134670291F, + 0.7143106273F, 0.7151531721F, 0.7159946602F, 0.7168350885F, + 0.7176744539F, 0.7185127534F, 0.7193499837F, 0.7201861418F, + 0.7210212247F, 0.7218552293F, 0.7226881526F, 0.7235199914F, + 0.7243507428F, 0.7251804039F, 0.7260089715F, 0.7268364426F, + 0.7276628144F, 0.7284880839F, 0.7293122481F, 0.7301353040F, + 0.7309572487F, 0.7317780794F, 0.7325977930F, 0.7334163868F, + 0.7342338579F, 0.7350502033F, 0.7358654202F, 0.7366795059F, + 0.7374924573F, 0.7383042718F, 0.7391149465F, 0.7399244787F, + 0.7407328655F, 0.7415401041F, 0.7423461920F, 0.7431511261F, + 0.7439549040F, 0.7447575227F, 0.7455589797F, 0.7463592723F, + 0.7471583976F, 0.7479563532F, 0.7487531363F, 0.7495487443F, + 0.7503431745F, 0.7511364244F, 0.7519284913F, 0.7527193726F, + 0.7535090658F, 0.7542975683F, 0.7550848776F, 0.7558709910F, + 0.7566559062F, 0.7574396205F, 0.7582221314F, 0.7590034366F, + 0.7597835334F, 0.7605624194F, 0.7613400923F, 0.7621165495F, + 0.7628917886F, 0.7636658072F, 0.7644386030F, 0.7652101735F, + 0.7659805164F, 0.7667496292F, 0.7675175098F, 0.7682841556F, + 0.7690495645F, 0.7698137341F, 0.7705766622F, 0.7713383463F, + 0.7720987844F, 0.7728579741F, 0.7736159132F, 0.7743725994F, + 0.7751280306F, 0.7758822046F, 0.7766351192F, 0.7773867722F, + 0.7781371614F, 0.7788862848F, 0.7796341401F, 0.7803807253F, + 0.7811260383F, 0.7818700769F, 0.7826128392F, 0.7833543230F, + 0.7840945263F, 0.7848334471F, 0.7855710833F, 0.7863074330F, + 0.7870424941F, 0.7877762647F, 0.7885087428F, 0.7892399264F, + 0.7899698137F, 0.7906984026F, 0.7914256914F, 0.7921516780F, + 0.7928763607F, 0.7935997375F, 0.7943218065F, 0.7950425661F, + 0.7957620142F, 0.7964801492F, 0.7971969692F, 0.7979124724F, + 0.7986266570F, 0.7993395214F, 0.8000510638F, 0.8007612823F, + 0.8014701754F, 0.8021777413F, 0.8028839784F, 0.8035888849F, + 0.8042924592F, 0.8049946997F, 0.8056956048F, 0.8063951727F, + 0.8070934020F, 0.8077902910F, 0.8084858381F, 0.8091800419F, + 0.8098729007F, 0.8105644130F, 0.8112545774F, 0.8119433922F, + 0.8126308561F, 0.8133169676F, 0.8140017251F, 0.8146851272F, + 0.8153671726F, 0.8160478598F, 0.8167271874F, 0.8174051539F, + 0.8180817582F, 0.8187569986F, 0.8194308741F, 0.8201033831F, + 0.8207745244F, 0.8214442966F, 0.8221126986F, 0.8227797290F, + 0.8234453865F, 0.8241096700F, 0.8247725781F, 0.8254341097F, + 0.8260942636F, 0.8267530385F, 0.8274104334F, 0.8280664470F, + 0.8287210782F, 0.8293743259F, 0.8300261889F, 0.8306766662F, + 0.8313257566F, 0.8319734591F, 0.8326197727F, 0.8332646963F, + 0.8339082288F, 0.8345503692F, 0.8351911167F, 0.8358304700F, + 0.8364684284F, 0.8371049907F, 0.8377401562F, 0.8383739238F, + 0.8390062927F, 0.8396372618F, 0.8402668305F, 0.8408949977F, + 0.8415217626F, 0.8421471245F, 0.8427710823F, 0.8433936354F, + 0.8440147830F, 0.8446345242F, 0.8452528582F, 0.8458697844F, + 0.8464853020F, 0.8470994102F, 0.8477121084F, 0.8483233958F, + 0.8489332718F, 0.8495417356F, 0.8501487866F, 0.8507544243F, + 0.8513586479F, 0.8519614568F, 0.8525628505F, 0.8531628283F, + 0.8537613897F, 0.8543585341F, 0.8549542611F, 0.8555485699F, + 0.8561414603F, 0.8567329315F, 0.8573229832F, 0.8579116149F, + 0.8584988262F, 0.8590846165F, 0.8596689855F, 0.8602519327F, + 0.8608334577F, 0.8614135603F, 0.8619922399F, 0.8625694962F, + 0.8631453289F, 0.8637197377F, 0.8642927222F, 0.8648642821F, + 0.8654344172F, 0.8660031272F, 0.8665704118F, 0.8671362708F, + 0.8677007039F, 0.8682637109F, 0.8688252917F, 0.8693854460F, + 0.8699441737F, 0.8705014745F, 0.8710573485F, 0.8716117953F, + 0.8721648150F, 0.8727164073F, 0.8732665723F, 0.8738153098F, + 0.8743626197F, 0.8749085021F, 0.8754529569F, 0.8759959840F, + 0.8765375835F, 0.8770777553F, 0.8776164996F, 0.8781538162F, + 0.8786897054F, 0.8792241670F, 0.8797572013F, 0.8802888082F, + 0.8808189880F, 0.8813477407F, 0.8818750664F, 0.8824009653F, + 0.8829254375F, 0.8834484833F, 0.8839701028F, 0.8844902961F, + 0.8850090636F, 0.8855264054F, 0.8860423218F, 0.8865568131F, + 0.8870698794F, 0.8875815212F, 0.8880917386F, 0.8886005319F, + 0.8891079016F, 0.8896138479F, 0.8901183712F, 0.8906214719F, + 0.8911231503F, 0.8916234067F, 0.8921222417F, 0.8926196556F, + 0.8931156489F, 0.8936102219F, 0.8941033752F, 0.8945951092F, + 0.8950854244F, 0.8955743212F, 0.8960618003F, 0.8965478621F, + 0.8970325071F, 0.8975157359F, 0.8979975490F, 0.8984779471F, + 0.8989569307F, 0.8994345004F, 0.8999106568F, 0.9003854005F, + 0.9008587323F, 0.9013306526F, 0.9018011623F, 0.9022702619F, + 0.9027379521F, 0.9032042337F, 0.9036691074F, 0.9041325739F, + 0.9045946339F, 0.9050552882F, 0.9055145376F, 0.9059723828F, + 0.9064288246F, 0.9068838638F, 0.9073375013F, 0.9077897379F, + 0.9082405743F, 0.9086900115F, 0.9091380503F, 0.9095846917F, + 0.9100299364F, 0.9104737854F, 0.9109162397F, 0.9113573001F, + 0.9117969675F, 0.9122352430F, 0.9126721275F, 0.9131076219F, + 0.9135417273F, 0.9139744447F, 0.9144057750F, 0.9148357194F, + 0.9152642787F, 0.9156914542F, 0.9161172468F, 0.9165416576F, + 0.9169646877F, 0.9173863382F, 0.9178066102F, 0.9182255048F, + 0.9186430232F, 0.9190591665F, 0.9194739359F, 0.9198873324F, + 0.9202993574F, 0.9207100120F, 0.9211192973F, 0.9215272147F, + 0.9219337653F, 0.9223389504F, 0.9227427713F, 0.9231452290F, + 0.9235463251F, 0.9239460607F, 0.9243444371F, 0.9247414557F, + 0.9251371177F, 0.9255314245F, 0.9259243774F, 0.9263159778F, + 0.9267062270F, 0.9270951264F, 0.9274826774F, 0.9278688814F, + 0.9282537398F, 0.9286372540F, 0.9290194254F, 0.9294002555F, + 0.9297797458F, 0.9301578976F, 0.9305347125F, 0.9309101919F, + 0.9312843373F, 0.9316571503F, 0.9320286323F, 0.9323987849F, + 0.9327676097F, 0.9331351080F, 0.9335012816F, 0.9338661320F, + 0.9342296607F, 0.9345918694F, 0.9349527596F, 0.9353123330F, + 0.9356705911F, 0.9360275357F, 0.9363831683F, 0.9367374905F, + 0.9370905042F, 0.9374422108F, 0.9377926122F, 0.9381417099F, + 0.9384895057F, 0.9388360014F, 0.9391811985F, 0.9395250989F, + 0.9398677043F, 0.9402090165F, 0.9405490371F, 0.9408877680F, + 0.9412252110F, 0.9415613678F, 0.9418962402F, 0.9422298301F, + 0.9425621392F, 0.9428931695F, 0.9432229226F, 0.9435514005F, + 0.9438786050F, 0.9442045381F, 0.9445292014F, 0.9448525971F, + 0.9451747268F, 0.9454955926F, 0.9458151963F, 0.9461335399F, + 0.9464506253F, 0.9467664545F, 0.9470810293F, 0.9473943517F, + 0.9477064238F, 0.9480172474F, 0.9483268246F, 0.9486351573F, + 0.9489422475F, 0.9492480973F, 0.9495527087F, 0.9498560837F, + 0.9501582243F, 0.9504591325F, 0.9507588105F, 0.9510572603F, + 0.9513544839F, 0.9516504834F, 0.9519452609F, 0.9522388186F, + 0.9525311584F, 0.9528222826F, 0.9531121932F, 0.9534008923F, + 0.9536883821F, 0.9539746647F, 0.9542597424F, 0.9545436171F, + 0.9548262912F, 0.9551077667F, 0.9553880459F, 0.9556671309F, + 0.9559450239F, 0.9562217272F, 0.9564972429F, 0.9567715733F, + 0.9570447206F, 0.9573166871F, 0.9575874749F, 0.9578570863F, + 0.9581255236F, 0.9583927890F, 0.9586588849F, 0.9589238134F, + 0.9591875769F, 0.9594501777F, 0.9597116180F, 0.9599719003F, + 0.9602310267F, 0.9604889995F, 0.9607458213F, 0.9610014942F, + 0.9612560206F, 0.9615094028F, 0.9617616433F, 0.9620127443F, + 0.9622627083F, 0.9625115376F, 0.9627592345F, 0.9630058016F, + 0.9632512411F, 0.9634955555F, 0.9637387471F, 0.9639808185F, + 0.9642217720F, 0.9644616100F, 0.9647003349F, 0.9649379493F, + 0.9651744556F, 0.9654098561F, 0.9656441534F, 0.9658773499F, + 0.9661094480F, 0.9663404504F, 0.9665703593F, 0.9667991774F, + 0.9670269071F, 0.9672535509F, 0.9674791114F, 0.9677035909F, + 0.9679269921F, 0.9681493174F, 0.9683705694F, 0.9685907506F, + 0.9688098636F, 0.9690279108F, 0.9692448948F, 0.9694608182F, + 0.9696756836F, 0.9698894934F, 0.9701022503F, 0.9703139569F, + 0.9705246156F, 0.9707342291F, 0.9709428000F, 0.9711503309F, + 0.9713568243F, 0.9715622829F, 0.9717667093F, 0.9719701060F, + 0.9721724757F, 0.9723738210F, 0.9725741446F, 0.9727734490F, + 0.9729717369F, 0.9731690109F, 0.9733652737F, 0.9735605279F, + 0.9737547762F, 0.9739480212F, 0.9741402656F, 0.9743315120F, + 0.9745217631F, 0.9747110216F, 0.9748992901F, 0.9750865714F, + 0.9752728681F, 0.9754581829F, 0.9756425184F, 0.9758258775F, + 0.9760082627F, 0.9761896768F, 0.9763701224F, 0.9765496024F, + 0.9767281193F, 0.9769056760F, 0.9770822751F, 0.9772579193F, + 0.9774326114F, 0.9776063542F, 0.9777791502F, 0.9779510023F, + 0.9781219133F, 0.9782918858F, 0.9784609226F, 0.9786290264F, + 0.9787962000F, 0.9789624461F, 0.9791277676F, 0.9792921671F, + 0.9794556474F, 0.9796182113F, 0.9797798615F, 0.9799406009F, + 0.9801004321F, 0.9802593580F, 0.9804173813F, 0.9805745049F, + 0.9807307314F, 0.9808860637F, 0.9810405046F, 0.9811940568F, + 0.9813467232F, 0.9814985065F, 0.9816494095F, 0.9817994351F, + 0.9819485860F, 0.9820968650F, 0.9822442750F, 0.9823908186F, + 0.9825364988F, 0.9826813184F, 0.9828252801F, 0.9829683868F, + 0.9831106413F, 0.9832520463F, 0.9833926048F, 0.9835323195F, + 0.9836711932F, 0.9838092288F, 0.9839464291F, 0.9840827969F, + 0.9842183351F, 0.9843530464F, 0.9844869337F, 0.9846199998F, + 0.9847522475F, 0.9848836798F, 0.9850142993F, 0.9851441090F, + 0.9852731117F, 0.9854013101F, 0.9855287073F, 0.9856553058F, + 0.9857811087F, 0.9859061188F, 0.9860303388F, 0.9861537717F, + 0.9862764202F, 0.9863982872F, 0.9865193756F, 0.9866396882F, + 0.9867592277F, 0.9868779972F, 0.9869959993F, 0.9871132370F, + 0.9872297131F, 0.9873454304F, 0.9874603918F, 0.9875746001F, + 0.9876880581F, 0.9878007688F, 0.9879127348F, 0.9880239592F, + 0.9881344447F, 0.9882441941F, 0.9883532104F, 0.9884614962F, + 0.9885690546F, 0.9886758883F, 0.9887820001F, 0.9888873930F, + 0.9889920697F, 0.9890960331F, 0.9891992859F, 0.9893018312F, + 0.9894036716F, 0.9895048100F, 0.9896052493F, 0.9897049923F, + 0.9898040418F, 0.9899024006F, 0.9900000717F, 0.9900970577F, + 0.9901933616F, 0.9902889862F, 0.9903839343F, 0.9904782087F, + 0.9905718122F, 0.9906647477F, 0.9907570180F, 0.9908486259F, + 0.9909395742F, 0.9910298658F, 0.9911195034F, 0.9912084899F, + 0.9912968281F, 0.9913845208F, 0.9914715708F, 0.9915579810F, + 0.9916437540F, 0.9917288928F, 0.9918134001F, 0.9918972788F, + 0.9919805316F, 0.9920631613F, 0.9921451707F, 0.9922265626F, + 0.9923073399F, 0.9923875052F, 0.9924670615F, 0.9925460114F, + 0.9926243577F, 0.9927021033F, 0.9927792508F, 0.9928558032F, + 0.9929317631F, 0.9930071333F, 0.9930819167F, 0.9931561158F, + 0.9932297337F, 0.9933027728F, 0.9933752362F, 0.9934471264F, + 0.9935184462F, 0.9935891985F, 0.9936593859F, 0.9937290112F, + 0.9937980771F, 0.9938665864F, 0.9939345418F, 0.9940019460F, + 0.9940688018F, 0.9941351118F, 0.9942008789F, 0.9942661057F, + 0.9943307950F, 0.9943949494F, 0.9944585717F, 0.9945216645F, + 0.9945842307F, 0.9946462728F, 0.9947077936F, 0.9947687957F, + 0.9948292820F, 0.9948892550F, 0.9949487174F, 0.9950076719F, + 0.9950661212F, 0.9951240679F, 0.9951815148F, 0.9952384645F, + 0.9952949196F, 0.9953508828F, 0.9954063568F, 0.9954613442F, + 0.9955158476F, 0.9955698697F, 0.9956234132F, 0.9956764806F, + 0.9957290746F, 0.9957811978F, 0.9958328528F, 0.9958840423F, + 0.9959347688F, 0.9959850351F, 0.9960348435F, 0.9960841969F, + 0.9961330977F, 0.9961815486F, 0.9962295521F, 0.9962771108F, + 0.9963242274F, 0.9963709043F, 0.9964171441F, 0.9964629494F, + 0.9965083228F, 0.9965532668F, 0.9965977840F, 0.9966418768F, + 0.9966855479F, 0.9967287998F, 0.9967716350F, 0.9968140559F, + 0.9968560653F, 0.9968976655F, 0.9969388591F, 0.9969796485F, + 0.9970200363F, 0.9970600250F, 0.9970996170F, 0.9971388149F, + 0.9971776211F, 0.9972160380F, 0.9972540683F, 0.9972917142F, + 0.9973289783F, 0.9973658631F, 0.9974023709F, 0.9974385042F, + 0.9974742655F, 0.9975096571F, 0.9975446816F, 0.9975793413F, + 0.9976136386F, 0.9976475759F, 0.9976811557F, 0.9977143803F, + 0.9977472521F, 0.9977797736F, 0.9978119470F, 0.9978437748F, + 0.9978752593F, 0.9979064029F, 0.9979372079F, 0.9979676768F, + 0.9979978117F, 0.9980276151F, 0.9980570893F, 0.9980862367F, + 0.9981150595F, 0.9981435600F, 0.9981717406F, 0.9981996035F, + 0.9982271511F, 0.9982543856F, 0.9982813093F, 0.9983079246F, + 0.9983342336F, 0.9983602386F, 0.9983859418F, 0.9984113456F, + 0.9984364522F, 0.9984612638F, 0.9984857825F, 0.9985100108F, + 0.9985339507F, 0.9985576044F, 0.9985809743F, 0.9986040624F, + 0.9986268710F, 0.9986494022F, 0.9986716583F, 0.9986936413F, + 0.9987153535F, 0.9987367969F, 0.9987579738F, 0.9987788864F, + 0.9987995366F, 0.9988199267F, 0.9988400587F, 0.9988599348F, + 0.9988795572F, 0.9988989278F, 0.9989180487F, 0.9989369222F, + 0.9989555501F, 0.9989739347F, 0.9989920780F, 0.9990099820F, + 0.9990276487F, 0.9990450803F, 0.9990622787F, 0.9990792460F, + 0.9990959841F, 0.9991124952F, 0.9991287812F, 0.9991448440F, + 0.9991606858F, 0.9991763084F, 0.9991917139F, 0.9992069042F, + 0.9992218813F, 0.9992366471F, 0.9992512035F, 0.9992655525F, + 0.9992796961F, 0.9992936361F, 0.9993073744F, 0.9993209131F, + 0.9993342538F, 0.9993473987F, 0.9993603494F, 0.9993731080F, + 0.9993856762F, 0.9993980559F, 0.9994102490F, 0.9994222573F, + 0.9994340827F, 0.9994457269F, 0.9994571918F, 0.9994684793F, + 0.9994795910F, 0.9994905288F, 0.9995012945F, 0.9995118898F, + 0.9995223165F, 0.9995325765F, 0.9995426713F, 0.9995526029F, + 0.9995623728F, 0.9995719829F, 0.9995814349F, 0.9995907304F, + 0.9995998712F, 0.9996088590F, 0.9996176954F, 0.9996263821F, + 0.9996349208F, 0.9996433132F, 0.9996515609F, 0.9996596656F, + 0.9996676288F, 0.9996754522F, 0.9996831375F, 0.9996906862F, + 0.9996981000F, 0.9997053804F, 0.9997125290F, 0.9997195474F, + 0.9997264371F, 0.9997331998F, 0.9997398369F, 0.9997463500F, + 0.9997527406F, 0.9997590103F, 0.9997651606F, 0.9997711930F, + 0.9997771089F, 0.9997829098F, 0.9997885973F, 0.9997941728F, + 0.9997996378F, 0.9998049936F, 0.9998102419F, 0.9998153839F, + 0.9998204211F, 0.9998253550F, 0.9998301868F, 0.9998349182F, + 0.9998395503F, 0.9998440847F, 0.9998485226F, 0.9998528654F, + 0.9998571146F, 0.9998612713F, 0.9998653370F, 0.9998693130F, + 0.9998732007F, 0.9998770012F, 0.9998807159F, 0.9998843461F, + 0.9998878931F, 0.9998913581F, 0.9998947424F, 0.9998980473F, + 0.9999012740F, 0.9999044237F, 0.9999074976F, 0.9999104971F, + 0.9999134231F, 0.9999162771F, 0.9999190601F, 0.9999217733F, + 0.9999244179F, 0.9999269950F, 0.9999295058F, 0.9999319515F, + 0.9999343332F, 0.9999366519F, 0.9999389088F, 0.9999411050F, + 0.9999432416F, 0.9999453196F, 0.9999473402F, 0.9999493044F, + 0.9999512132F, 0.9999530677F, 0.9999548690F, 0.9999566180F, + 0.9999583157F, 0.9999599633F, 0.9999615616F, 0.9999631116F, + 0.9999646144F, 0.9999660709F, 0.9999674820F, 0.9999688487F, + 0.9999701719F, 0.9999714526F, 0.9999726917F, 0.9999738900F, + 0.9999750486F, 0.9999761682F, 0.9999772497F, 0.9999782941F, + 0.9999793021F, 0.9999802747F, 0.9999812126F, 0.9999821167F, + 0.9999829878F, 0.9999838268F, 0.9999846343F, 0.9999854113F, + 0.9999861584F, 0.9999868765F, 0.9999875664F, 0.9999882287F, + 0.9999888642F, 0.9999894736F, 0.9999900577F, 0.9999906172F, + 0.9999911528F, 0.9999916651F, 0.9999921548F, 0.9999926227F, + 0.9999930693F, 0.9999934954F, 0.9999939015F, 0.9999942883F, + 0.9999946564F, 0.9999950064F, 0.9999953390F, 0.9999956547F, + 0.9999959541F, 0.9999962377F, 0.9999965062F, 0.9999967601F, + 0.9999969998F, 0.9999972260F, 0.9999974392F, 0.9999976399F, + 0.9999978285F, 0.9999980056F, 0.9999981716F, 0.9999983271F, + 0.9999984724F, 0.9999986081F, 0.9999987345F, 0.9999988521F, + 0.9999989613F, 0.9999990625F, 0.9999991562F, 0.9999992426F, + 0.9999993223F, 0.9999993954F, 0.9999994625F, 0.9999995239F, + 0.9999995798F, 0.9999996307F, 0.9999996768F, 0.9999997184F, + 0.9999997559F, 0.9999997895F, 0.9999998195F, 0.9999998462F, + 0.9999998698F, 0.9999998906F, 0.9999999088F, 0.9999999246F, + 0.9999999383F, 0.9999999500F, 0.9999999600F, 0.9999999684F, + 0.9999999754F, 0.9999999811F, 0.9999999858F, 0.9999999896F, + 0.9999999925F, 0.9999999948F, 0.9999999965F, 0.9999999978F, + 0.9999999986F, 0.9999999992F, 0.9999999996F, 0.9999999998F, + 0.9999999999F, 1.0000000000F, 1.0000000000F, 1.0000000000F, +}; + +static float vwin8192[4096] = { + 0.0000000578F, 0.0000005198F, 0.0000014438F, 0.0000028299F, + 0.0000046780F, 0.0000069882F, 0.0000097604F, 0.0000129945F, + 0.0000166908F, 0.0000208490F, 0.0000254692F, 0.0000305515F, + 0.0000360958F, 0.0000421021F, 0.0000485704F, 0.0000555006F, + 0.0000628929F, 0.0000707472F, 0.0000790635F, 0.0000878417F, + 0.0000970820F, 0.0001067842F, 0.0001169483F, 0.0001275744F, + 0.0001386625F, 0.0001502126F, 0.0001622245F, 0.0001746984F, + 0.0001876343F, 0.0002010320F, 0.0002148917F, 0.0002292132F, + 0.0002439967F, 0.0002592421F, 0.0002749493F, 0.0002911184F, + 0.0003077493F, 0.0003248421F, 0.0003423967F, 0.0003604132F, + 0.0003788915F, 0.0003978316F, 0.0004172335F, 0.0004370971F, + 0.0004574226F, 0.0004782098F, 0.0004994587F, 0.0005211694F, + 0.0005433418F, 0.0005659759F, 0.0005890717F, 0.0006126292F, + 0.0006366484F, 0.0006611292F, 0.0006860716F, 0.0007114757F, + 0.0007373414F, 0.0007636687F, 0.0007904576F, 0.0008177080F, + 0.0008454200F, 0.0008735935F, 0.0009022285F, 0.0009313250F, + 0.0009608830F, 0.0009909025F, 0.0010213834F, 0.0010523257F, + 0.0010837295F, 0.0011155946F, 0.0011479211F, 0.0011807090F, + 0.0012139582F, 0.0012476687F, 0.0012818405F, 0.0013164736F, + 0.0013515679F, 0.0013871235F, 0.0014231402F, 0.0014596182F, + 0.0014965573F, 0.0015339576F, 0.0015718190F, 0.0016101415F, + 0.0016489251F, 0.0016881698F, 0.0017278754F, 0.0017680421F, + 0.0018086698F, 0.0018497584F, 0.0018913080F, 0.0019333185F, + 0.0019757898F, 0.0020187221F, 0.0020621151F, 0.0021059690F, + 0.0021502837F, 0.0021950591F, 0.0022402953F, 0.0022859921F, + 0.0023321497F, 0.0023787679F, 0.0024258467F, 0.0024733861F, + 0.0025213861F, 0.0025698466F, 0.0026187676F, 0.0026681491F, + 0.0027179911F, 0.0027682935F, 0.0028190562F, 0.0028702794F, + 0.0029219628F, 0.0029741066F, 0.0030267107F, 0.0030797749F, + 0.0031332994F, 0.0031872841F, 0.0032417289F, 0.0032966338F, + 0.0033519988F, 0.0034078238F, 0.0034641089F, 0.0035208539F, + 0.0035780589F, 0.0036357237F, 0.0036938485F, 0.0037524331F, + 0.0038114775F, 0.0038709817F, 0.0039309456F, 0.0039913692F, + 0.0040522524F, 0.0041135953F, 0.0041753978F, 0.0042376599F, + 0.0043003814F, 0.0043635624F, 0.0044272029F, 0.0044913028F, + 0.0045558620F, 0.0046208806F, 0.0046863585F, 0.0047522955F, + 0.0048186919F, 0.0048855473F, 0.0049528619F, 0.0050206356F, + 0.0050888684F, 0.0051575601F, 0.0052267108F, 0.0052963204F, + 0.0053663890F, 0.0054369163F, 0.0055079025F, 0.0055793474F, + 0.0056512510F, 0.0057236133F, 0.0057964342F, 0.0058697137F, + 0.0059434517F, 0.0060176482F, 0.0060923032F, 0.0061674166F, + 0.0062429883F, 0.0063190183F, 0.0063955066F, 0.0064724532F, + 0.0065498579F, 0.0066277207F, 0.0067060416F, 0.0067848205F, + 0.0068640575F, 0.0069437523F, 0.0070239051F, 0.0071045157F, + 0.0071855840F, 0.0072671102F, 0.0073490940F, 0.0074315355F, + 0.0075144345F, 0.0075977911F, 0.0076816052F, 0.0077658768F, + 0.0078506057F, 0.0079357920F, 0.0080214355F, 0.0081075363F, + 0.0081940943F, 0.0082811094F, 0.0083685816F, 0.0084565108F, + 0.0085448970F, 0.0086337401F, 0.0087230401F, 0.0088127969F, + 0.0089030104F, 0.0089936807F, 0.0090848076F, 0.0091763911F, + 0.0092684311F, 0.0093609276F, 0.0094538805F, 0.0095472898F, + 0.0096411554F, 0.0097354772F, 0.0098302552F, 0.0099254894F, + 0.0100211796F, 0.0101173259F, 0.0102139281F, 0.0103109863F, + 0.0104085002F, 0.0105064700F, 0.0106048955F, 0.0107037766F, + 0.0108031133F, 0.0109029056F, 0.0110031534F, 0.0111038565F, + 0.0112050151F, 0.0113066289F, 0.0114086980F, 0.0115112222F, + 0.0116142015F, 0.0117176359F, 0.0118215252F, 0.0119258695F, + 0.0120306686F, 0.0121359225F, 0.0122416312F, 0.0123477944F, + 0.0124544123F, 0.0125614847F, 0.0126690116F, 0.0127769928F, + 0.0128854284F, 0.0129943182F, 0.0131036623F, 0.0132134604F, + 0.0133237126F, 0.0134344188F, 0.0135455790F, 0.0136571929F, + 0.0137692607F, 0.0138817821F, 0.0139947572F, 0.0141081859F, + 0.0142220681F, 0.0143364037F, 0.0144511927F, 0.0145664350F, + 0.0146821304F, 0.0147982791F, 0.0149148808F, 0.0150319355F, + 0.0151494431F, 0.0152674036F, 0.0153858168F, 0.0155046828F, + 0.0156240014F, 0.0157437726F, 0.0158639962F, 0.0159846723F, + 0.0161058007F, 0.0162273814F, 0.0163494142F, 0.0164718991F, + 0.0165948361F, 0.0167182250F, 0.0168420658F, 0.0169663584F, + 0.0170911027F, 0.0172162987F, 0.0173419462F, 0.0174680452F, + 0.0175945956F, 0.0177215974F, 0.0178490504F, 0.0179769545F, + 0.0181053098F, 0.0182341160F, 0.0183633732F, 0.0184930812F, + 0.0186232399F, 0.0187538494F, 0.0188849094F, 0.0190164200F, + 0.0191483809F, 0.0192807923F, 0.0194136539F, 0.0195469656F, + 0.0196807275F, 0.0198149394F, 0.0199496012F, 0.0200847128F, + 0.0202202742F, 0.0203562853F, 0.0204927460F, 0.0206296561F, + 0.0207670157F, 0.0209048245F, 0.0210430826F, 0.0211817899F, + 0.0213209462F, 0.0214605515F, 0.0216006057F, 0.0217411086F, + 0.0218820603F, 0.0220234605F, 0.0221653093F, 0.0223076066F, + 0.0224503521F, 0.0225935459F, 0.0227371879F, 0.0228812779F, + 0.0230258160F, 0.0231708018F, 0.0233162355F, 0.0234621169F, + 0.0236084459F, 0.0237552224F, 0.0239024462F, 0.0240501175F, + 0.0241982359F, 0.0243468015F, 0.0244958141F, 0.0246452736F, + 0.0247951800F, 0.0249455331F, 0.0250963329F, 0.0252475792F, + 0.0253992720F, 0.0255514111F, 0.0257039965F, 0.0258570281F, + 0.0260105057F, 0.0261644293F, 0.0263187987F, 0.0264736139F, + 0.0266288747F, 0.0267845811F, 0.0269407330F, 0.0270973302F, + 0.0272543727F, 0.0274118604F, 0.0275697930F, 0.0277281707F, + 0.0278869932F, 0.0280462604F, 0.0282059723F, 0.0283661287F, + 0.0285267295F, 0.0286877747F, 0.0288492641F, 0.0290111976F, + 0.0291735751F, 0.0293363965F, 0.0294996617F, 0.0296633706F, + 0.0298275231F, 0.0299921190F, 0.0301571583F, 0.0303226409F, + 0.0304885667F, 0.0306549354F, 0.0308217472F, 0.0309890017F, + 0.0311566989F, 0.0313248388F, 0.0314934211F, 0.0316624459F, + 0.0318319128F, 0.0320018220F, 0.0321721732F, 0.0323429663F, + 0.0325142013F, 0.0326858779F, 0.0328579962F, 0.0330305559F, + 0.0332035570F, 0.0333769994F, 0.0335508829F, 0.0337252074F, + 0.0338999728F, 0.0340751790F, 0.0342508259F, 0.0344269134F, + 0.0346034412F, 0.0347804094F, 0.0349578178F, 0.0351356663F, + 0.0353139548F, 0.0354926831F, 0.0356718511F, 0.0358514588F, + 0.0360315059F, 0.0362119924F, 0.0363929182F, 0.0365742831F, + 0.0367560870F, 0.0369383297F, 0.0371210113F, 0.0373041315F, + 0.0374876902F, 0.0376716873F, 0.0378561226F, 0.0380409961F, + 0.0382263077F, 0.0384120571F, 0.0385982443F, 0.0387848691F, + 0.0389719315F, 0.0391594313F, 0.0393473683F, 0.0395357425F, + 0.0397245537F, 0.0399138017F, 0.0401034866F, 0.0402936080F, + 0.0404841660F, 0.0406751603F, 0.0408665909F, 0.0410584576F, + 0.0412507603F, 0.0414434988F, 0.0416366731F, 0.0418302829F, + 0.0420243282F, 0.0422188088F, 0.0424137246F, 0.0426090755F, + 0.0428048613F, 0.0430010819F, 0.0431977371F, 0.0433948269F, + 0.0435923511F, 0.0437903095F, 0.0439887020F, 0.0441875285F, + 0.0443867889F, 0.0445864830F, 0.0447866106F, 0.0449871717F, + 0.0451881661F, 0.0453895936F, 0.0455914542F, 0.0457937477F, + 0.0459964738F, 0.0461996326F, 0.0464032239F, 0.0466072475F, + 0.0468117032F, 0.0470165910F, 0.0472219107F, 0.0474276622F, + 0.0476338452F, 0.0478404597F, 0.0480475056F, 0.0482549827F, + 0.0484628907F, 0.0486712297F, 0.0488799994F, 0.0490891998F, + 0.0492988306F, 0.0495088917F, 0.0497193830F, 0.0499303043F, + 0.0501416554F, 0.0503534363F, 0.0505656468F, 0.0507782867F, + 0.0509913559F, 0.0512048542F, 0.0514187815F, 0.0516331376F, + 0.0518479225F, 0.0520631358F, 0.0522787775F, 0.0524948475F, + 0.0527113455F, 0.0529282715F, 0.0531456252F, 0.0533634066F, + 0.0535816154F, 0.0538002515F, 0.0540193148F, 0.0542388051F, + 0.0544587222F, 0.0546790660F, 0.0548998364F, 0.0551210331F, + 0.0553426561F, 0.0555647051F, 0.0557871801F, 0.0560100807F, + 0.0562334070F, 0.0564571587F, 0.0566813357F, 0.0569059378F, + 0.0571309649F, 0.0573564168F, 0.0575822933F, 0.0578085942F, + 0.0580353195F, 0.0582624689F, 0.0584900423F, 0.0587180396F, + 0.0589464605F, 0.0591753049F, 0.0594045726F, 0.0596342635F, + 0.0598643774F, 0.0600949141F, 0.0603258735F, 0.0605572555F, + 0.0607890597F, 0.0610212862F, 0.0612539346F, 0.0614870049F, + 0.0617204968F, 0.0619544103F, 0.0621887451F, 0.0624235010F, + 0.0626586780F, 0.0628942758F, 0.0631302942F, 0.0633667331F, + 0.0636035923F, 0.0638408717F, 0.0640785710F, 0.0643166901F, + 0.0645552288F, 0.0647941870F, 0.0650335645F, 0.0652733610F, + 0.0655135765F, 0.0657542108F, 0.0659952636F, 0.0662367348F, + 0.0664786242F, 0.0667209316F, 0.0669636570F, 0.0672068000F, + 0.0674503605F, 0.0676943384F, 0.0679387334F, 0.0681835454F, + 0.0684287742F, 0.0686744196F, 0.0689204814F, 0.0691669595F, + 0.0694138536F, 0.0696611637F, 0.0699088894F, 0.0701570307F, + 0.0704055873F, 0.0706545590F, 0.0709039458F, 0.0711537473F, + 0.0714039634F, 0.0716545939F, 0.0719056387F, 0.0721570975F, + 0.0724089702F, 0.0726612565F, 0.0729139563F, 0.0731670694F, + 0.0734205956F, 0.0736745347F, 0.0739288866F, 0.0741836510F, + 0.0744388277F, 0.0746944166F, 0.0749504175F, 0.0752068301F, + 0.0754636543F, 0.0757208899F, 0.0759785367F, 0.0762365946F, + 0.0764950632F, 0.0767539424F, 0.0770132320F, 0.0772729319F, + 0.0775330418F, 0.0777935616F, 0.0780544909F, 0.0783158298F, + 0.0785775778F, 0.0788397349F, 0.0791023009F, 0.0793652755F, + 0.0796286585F, 0.0798924498F, 0.0801566492F, 0.0804212564F, + 0.0806862712F, 0.0809516935F, 0.0812175231F, 0.0814837597F, + 0.0817504031F, 0.0820174532F, 0.0822849097F, 0.0825527724F, + 0.0828210412F, 0.0830897158F, 0.0833587960F, 0.0836282816F, + 0.0838981724F, 0.0841684682F, 0.0844391688F, 0.0847102740F, + 0.0849817835F, 0.0852536973F, 0.0855260150F, 0.0857987364F, + 0.0860718614F, 0.0863453897F, 0.0866193211F, 0.0868936554F, + 0.0871683924F, 0.0874435319F, 0.0877190737F, 0.0879950175F, + 0.0882713632F, 0.0885481105F, 0.0888252592F, 0.0891028091F, + 0.0893807600F, 0.0896591117F, 0.0899378639F, 0.0902170165F, + 0.0904965692F, 0.0907765218F, 0.0910568740F, 0.0913376258F, + 0.0916187767F, 0.0919003268F, 0.0921822756F, 0.0924646230F, + 0.0927473687F, 0.0930305126F, 0.0933140545F, 0.0935979940F, + 0.0938823310F, 0.0941670653F, 0.0944521966F, 0.0947377247F, + 0.0950236494F, 0.0953099704F, 0.0955966876F, 0.0958838007F, + 0.0961713094F, 0.0964592136F, 0.0967475131F, 0.0970362075F, + 0.0973252967F, 0.0976147805F, 0.0979046585F, 0.0981949307F, + 0.0984855967F, 0.0987766563F, 0.0990681093F, 0.0993599555F, + 0.0996521945F, 0.0999448263F, 0.1002378506F, 0.1005312671F, + 0.1008250755F, 0.1011192757F, 0.1014138675F, 0.1017088505F, + 0.1020042246F, 0.1022999895F, 0.1025961450F, 0.1028926909F, + 0.1031896268F, 0.1034869526F, 0.1037846680F, 0.1040827729F, + 0.1043812668F, 0.1046801497F, 0.1049794213F, 0.1052790813F, + 0.1055791294F, 0.1058795656F, 0.1061803894F, 0.1064816006F, + 0.1067831991F, 0.1070851846F, 0.1073875568F, 0.1076903155F, + 0.1079934604F, 0.1082969913F, 0.1086009079F, 0.1089052101F, + 0.1092098975F, 0.1095149699F, 0.1098204270F, 0.1101262687F, + 0.1104324946F, 0.1107391045F, 0.1110460982F, 0.1113534754F, + 0.1116612359F, 0.1119693793F, 0.1122779055F, 0.1125868142F, + 0.1128961052F, 0.1132057781F, 0.1135158328F, 0.1138262690F, + 0.1141370863F, 0.1144482847F, 0.1147598638F, 0.1150718233F, + 0.1153841631F, 0.1156968828F, 0.1160099822F, 0.1163234610F, + 0.1166373190F, 0.1169515559F, 0.1172661714F, 0.1175811654F, + 0.1178965374F, 0.1182122874F, 0.1185284149F, 0.1188449198F, + 0.1191618018F, 0.1194790606F, 0.1197966960F, 0.1201147076F, + 0.1204330953F, 0.1207518587F, 0.1210709976F, 0.1213905118F, + 0.1217104009F, 0.1220306647F, 0.1223513029F, 0.1226723153F, + 0.1229937016F, 0.1233154615F, 0.1236375948F, 0.1239601011F, + 0.1242829803F, 0.1246062319F, 0.1249298559F, 0.1252538518F, + 0.1255782195F, 0.1259029586F, 0.1262280689F, 0.1265535501F, + 0.1268794019F, 0.1272056241F, 0.1275322163F, 0.1278591784F, + 0.1281865099F, 0.1285142108F, 0.1288422805F, 0.1291707190F, + 0.1294995259F, 0.1298287009F, 0.1301582437F, 0.1304881542F, + 0.1308184319F, 0.1311490766F, 0.1314800881F, 0.1318114660F, + 0.1321432100F, 0.1324753200F, 0.1328077955F, 0.1331406364F, + 0.1334738422F, 0.1338074129F, 0.1341413479F, 0.1344756472F, + 0.1348103103F, 0.1351453370F, 0.1354807270F, 0.1358164801F, + 0.1361525959F, 0.1364890741F, 0.1368259145F, 0.1371631167F, + 0.1375006805F, 0.1378386056F, 0.1381768917F, 0.1385155384F, + 0.1388545456F, 0.1391939129F, 0.1395336400F, 0.1398737266F, + 0.1402141724F, 0.1405549772F, 0.1408961406F, 0.1412376623F, + 0.1415795421F, 0.1419217797F, 0.1422643746F, 0.1426073268F, + 0.1429506358F, 0.1432943013F, 0.1436383231F, 0.1439827008F, + 0.1443274342F, 0.1446725229F, 0.1450179667F, 0.1453637652F, + 0.1457099181F, 0.1460564252F, 0.1464032861F, 0.1467505006F, + 0.1470980682F, 0.1474459888F, 0.1477942620F, 0.1481428875F, + 0.1484918651F, 0.1488411942F, 0.1491908748F, 0.1495409065F, + 0.1498912889F, 0.1502420218F, 0.1505931048F, 0.1509445376F, + 0.1512963200F, 0.1516484516F, 0.1520009321F, 0.1523537612F, + 0.1527069385F, 0.1530604638F, 0.1534143368F, 0.1537685571F, + 0.1541231244F, 0.1544780384F, 0.1548332987F, 0.1551889052F, + 0.1555448574F, 0.1559011550F, 0.1562577978F, 0.1566147853F, + 0.1569721173F, 0.1573297935F, 0.1576878135F, 0.1580461771F, + 0.1584048838F, 0.1587639334F, 0.1591233255F, 0.1594830599F, + 0.1598431361F, 0.1602035540F, 0.1605643131F, 0.1609254131F, + 0.1612868537F, 0.1616486346F, 0.1620107555F, 0.1623732160F, + 0.1627360158F, 0.1630991545F, 0.1634626319F, 0.1638264476F, + 0.1641906013F, 0.1645550926F, 0.1649199212F, 0.1652850869F, + 0.1656505892F, 0.1660164278F, 0.1663826024F, 0.1667491127F, + 0.1671159583F, 0.1674831388F, 0.1678506541F, 0.1682185036F, + 0.1685866872F, 0.1689552044F, 0.1693240549F, 0.1696932384F, + 0.1700627545F, 0.1704326029F, 0.1708027833F, 0.1711732952F, + 0.1715441385F, 0.1719153127F, 0.1722868175F, 0.1726586526F, + 0.1730308176F, 0.1734033121F, 0.1737761359F, 0.1741492886F, + 0.1745227698F, 0.1748965792F, 0.1752707164F, 0.1756451812F, + 0.1760199731F, 0.1763950918F, 0.1767705370F, 0.1771463083F, + 0.1775224054F, 0.1778988279F, 0.1782755754F, 0.1786526477F, + 0.1790300444F, 0.1794077651F, 0.1797858094F, 0.1801641771F, + 0.1805428677F, 0.1809218810F, 0.1813012165F, 0.1816808739F, + 0.1820608528F, 0.1824411530F, 0.1828217739F, 0.1832027154F, + 0.1835839770F, 0.1839655584F, 0.1843474592F, 0.1847296790F, + 0.1851122175F, 0.1854950744F, 0.1858782492F, 0.1862617417F, + 0.1866455514F, 0.1870296780F, 0.1874141211F, 0.1877988804F, + 0.1881839555F, 0.1885693461F, 0.1889550517F, 0.1893410721F, + 0.1897274068F, 0.1901140555F, 0.1905010178F, 0.1908882933F, + 0.1912758818F, 0.1916637828F, 0.1920519959F, 0.1924405208F, + 0.1928293571F, 0.1932185044F, 0.1936079625F, 0.1939977308F, + 0.1943878091F, 0.1947781969F, 0.1951688939F, 0.1955598998F, + 0.1959512141F, 0.1963428364F, 0.1967347665F, 0.1971270038F, + 0.1975195482F, 0.1979123990F, 0.1983055561F, 0.1986990190F, + 0.1990927873F, 0.1994868607F, 0.1998812388F, 0.2002759212F, + 0.2006709075F, 0.2010661974F, 0.2014617904F, 0.2018576862F, + 0.2022538844F, 0.2026503847F, 0.2030471865F, 0.2034442897F, + 0.2038416937F, 0.2042393982F, 0.2046374028F, 0.2050357071F, + 0.2054343107F, 0.2058332133F, 0.2062324145F, 0.2066319138F, + 0.2070317110F, 0.2074318055F, 0.2078321970F, 0.2082328852F, + 0.2086338696F, 0.2090351498F, 0.2094367255F, 0.2098385962F, + 0.2102407617F, 0.2106432213F, 0.2110459749F, 0.2114490220F, + 0.2118523621F, 0.2122559950F, 0.2126599202F, 0.2130641373F, + 0.2134686459F, 0.2138734456F, 0.2142785361F, 0.2146839168F, + 0.2150895875F, 0.2154955478F, 0.2159017972F, 0.2163083353F, + 0.2167151617F, 0.2171222761F, 0.2175296780F, 0.2179373670F, + 0.2183453428F, 0.2187536049F, 0.2191621529F, 0.2195709864F, + 0.2199801051F, 0.2203895085F, 0.2207991961F, 0.2212091677F, + 0.2216194228F, 0.2220299610F, 0.2224407818F, 0.2228518850F, + 0.2232632699F, 0.2236749364F, 0.2240868839F, 0.2244991121F, + 0.2249116204F, 0.2253244086F, 0.2257374763F, 0.2261508229F, + 0.2265644481F, 0.2269783514F, 0.2273925326F, 0.2278069911F, + 0.2282217265F, 0.2286367384F, 0.2290520265F, 0.2294675902F, + 0.2298834292F, 0.2302995431F, 0.2307159314F, 0.2311325937F, + 0.2315495297F, 0.2319667388F, 0.2323842207F, 0.2328019749F, + 0.2332200011F, 0.2336382988F, 0.2340568675F, 0.2344757070F, + 0.2348948166F, 0.2353141961F, 0.2357338450F, 0.2361537629F, + 0.2365739493F, 0.2369944038F, 0.2374151261F, 0.2378361156F, + 0.2382573720F, 0.2386788948F, 0.2391006836F, 0.2395227380F, + 0.2399450575F, 0.2403676417F, 0.2407904902F, 0.2412136026F, + 0.2416369783F, 0.2420606171F, 0.2424845185F, 0.2429086820F, + 0.2433331072F, 0.2437577936F, 0.2441827409F, 0.2446079486F, + 0.2450334163F, 0.2454591435F, 0.2458851298F, 0.2463113747F, + 0.2467378779F, 0.2471646389F, 0.2475916573F, 0.2480189325F, + 0.2484464643F, 0.2488742521F, 0.2493022955F, 0.2497305940F, + 0.2501591473F, 0.2505879549F, 0.2510170163F, 0.2514463311F, + 0.2518758989F, 0.2523057193F, 0.2527357916F, 0.2531661157F, + 0.2535966909F, 0.2540275169F, 0.2544585931F, 0.2548899193F, + 0.2553214948F, 0.2557533193F, 0.2561853924F, 0.2566177135F, + 0.2570502822F, 0.2574830981F, 0.2579161608F, 0.2583494697F, + 0.2587830245F, 0.2592168246F, 0.2596508697F, 0.2600851593F, + 0.2605196929F, 0.2609544701F, 0.2613894904F, 0.2618247534F, + 0.2622602586F, 0.2626960055F, 0.2631319938F, 0.2635682230F, + 0.2640046925F, 0.2644414021F, 0.2648783511F, 0.2653155391F, + 0.2657529657F, 0.2661906305F, 0.2666285329F, 0.2670666725F, + 0.2675050489F, 0.2679436616F, 0.2683825101F, 0.2688215940F, + 0.2692609127F, 0.2697004660F, 0.2701402532F, 0.2705802739F, + 0.2710205278F, 0.2714610142F, 0.2719017327F, 0.2723426830F, + 0.2727838644F, 0.2732252766F, 0.2736669191F, 0.2741087914F, + 0.2745508930F, 0.2749932235F, 0.2754357824F, 0.2758785693F, + 0.2763215837F, 0.2767648251F, 0.2772082930F, 0.2776519870F, + 0.2780959066F, 0.2785400513F, 0.2789844207F, 0.2794290143F, + 0.2798738316F, 0.2803188722F, 0.2807641355F, 0.2812096211F, + 0.2816553286F, 0.2821012574F, 0.2825474071F, 0.2829937773F, + 0.2834403673F, 0.2838871768F, 0.2843342053F, 0.2847814523F, + 0.2852289174F, 0.2856765999F, 0.2861244996F, 0.2865726159F, + 0.2870209482F, 0.2874694962F, 0.2879182594F, 0.2883672372F, + 0.2888164293F, 0.2892658350F, 0.2897154540F, 0.2901652858F, + 0.2906153298F, 0.2910655856F, 0.2915160527F, 0.2919667306F, + 0.2924176189F, 0.2928687171F, 0.2933200246F, 0.2937715409F, + 0.2942232657F, 0.2946751984F, 0.2951273386F, 0.2955796856F, + 0.2960322391F, 0.2964849986F, 0.2969379636F, 0.2973911335F, + 0.2978445080F, 0.2982980864F, 0.2987518684F, 0.2992058534F, + 0.2996600409F, 0.3001144305F, 0.3005690217F, 0.3010238139F, + 0.3014788067F, 0.3019339995F, 0.3023893920F, 0.3028449835F, + 0.3033007736F, 0.3037567618F, 0.3042129477F, 0.3046693306F, + 0.3051259102F, 0.3055826859F, 0.3060396572F, 0.3064968236F, + 0.3069541847F, 0.3074117399F, 0.3078694887F, 0.3083274307F, + 0.3087855653F, 0.3092438920F, 0.3097024104F, 0.3101611199F, + 0.3106200200F, 0.3110791103F, 0.3115383902F, 0.3119978592F, + 0.3124575169F, 0.3129173627F, 0.3133773961F, 0.3138376166F, + 0.3142980238F, 0.3147586170F, 0.3152193959F, 0.3156803598F, + 0.3161415084F, 0.3166028410F, 0.3170643573F, 0.3175260566F, + 0.3179879384F, 0.3184500023F, 0.3189122478F, 0.3193746743F, + 0.3198372814F, 0.3203000685F, 0.3207630351F, 0.3212261807F, + 0.3216895048F, 0.3221530069F, 0.3226166865F, 0.3230805430F, + 0.3235445760F, 0.3240087849F, 0.3244731693F, 0.3249377285F, + 0.3254024622F, 0.3258673698F, 0.3263324507F, 0.3267977045F, + 0.3272631306F, 0.3277287286F, 0.3281944978F, 0.3286604379F, + 0.3291265482F, 0.3295928284F, 0.3300592777F, 0.3305258958F, + 0.3309926821F, 0.3314596361F, 0.3319267573F, 0.3323940451F, + 0.3328614990F, 0.3333291186F, 0.3337969033F, 0.3342648525F, + 0.3347329658F, 0.3352012427F, 0.3356696825F, 0.3361382849F, + 0.3366070492F, 0.3370759749F, 0.3375450616F, 0.3380143087F, + 0.3384837156F, 0.3389532819F, 0.3394230071F, 0.3398928905F, + 0.3403629317F, 0.3408331302F, 0.3413034854F, 0.3417739967F, + 0.3422446638F, 0.3427154860F, 0.3431864628F, 0.3436575938F, + 0.3441288782F, 0.3446003158F, 0.3450719058F, 0.3455436478F, + 0.3460155412F, 0.3464875856F, 0.3469597804F, 0.3474321250F, + 0.3479046189F, 0.3483772617F, 0.3488500527F, 0.3493229914F, + 0.3497960774F, 0.3502693100F, 0.3507426887F, 0.3512162131F, + 0.3516898825F, 0.3521636965F, 0.3526376545F, 0.3531117559F, + 0.3535860003F, 0.3540603870F, 0.3545349157F, 0.3550095856F, + 0.3554843964F, 0.3559593474F, 0.3564344381F, 0.3569096680F, + 0.3573850366F, 0.3578605432F, 0.3583361875F, 0.3588119687F, + 0.3592878865F, 0.3597639402F, 0.3602401293F, 0.3607164533F, + 0.3611929117F, 0.3616695038F, 0.3621462292F, 0.3626230873F, + 0.3631000776F, 0.3635771995F, 0.3640544525F, 0.3645318360F, + 0.3650093496F, 0.3654869926F, 0.3659647645F, 0.3664426648F, + 0.3669206930F, 0.3673988484F, 0.3678771306F, 0.3683555390F, + 0.3688340731F, 0.3693127322F, 0.3697915160F, 0.3702704237F, + 0.3707494549F, 0.3712286091F, 0.3717078857F, 0.3721872840F, + 0.3726668037F, 0.3731464441F, 0.3736262047F, 0.3741060850F, + 0.3745860843F, 0.3750662023F, 0.3755464382F, 0.3760267915F, + 0.3765072618F, 0.3769878484F, 0.3774685509F, 0.3779493686F, + 0.3784303010F, 0.3789113475F, 0.3793925076F, 0.3798737809F, + 0.3803551666F, 0.3808366642F, 0.3813182733F, 0.3817999932F, + 0.3822818234F, 0.3827637633F, 0.3832458124F, 0.3837279702F, + 0.3842102360F, 0.3846926093F, 0.3851750897F, 0.3856576764F, + 0.3861403690F, 0.3866231670F, 0.3871060696F, 0.3875890765F, + 0.3880721870F, 0.3885554007F, 0.3890387168F, 0.3895221349F, + 0.3900056544F, 0.3904892748F, 0.3909729955F, 0.3914568160F, + 0.3919407356F, 0.3924247539F, 0.3929088702F, 0.3933930841F, + 0.3938773949F, 0.3943618021F, 0.3948463052F, 0.3953309035F, + 0.3958155966F, 0.3963003838F, 0.3967852646F, 0.3972702385F, + 0.3977553048F, 0.3982404631F, 0.3987257127F, 0.3992110531F, + 0.3996964838F, 0.4001820041F, 0.4006676136F, 0.4011533116F, + 0.4016390976F, 0.4021249710F, 0.4026109313F, 0.4030969779F, + 0.4035831102F, 0.4040693277F, 0.4045556299F, 0.4050420160F, + 0.4055284857F, 0.4060150383F, 0.4065016732F, 0.4069883899F, + 0.4074751879F, 0.4079620665F, 0.4084490252F, 0.4089360635F, + 0.4094231807F, 0.4099103763F, 0.4103976498F, 0.4108850005F, + 0.4113724280F, 0.4118599315F, 0.4123475107F, 0.4128351648F, + 0.4133228934F, 0.4138106959F, 0.4142985716F, 0.4147865201F, + 0.4152745408F, 0.4157626330F, 0.4162507963F, 0.4167390301F, + 0.4172273337F, 0.4177157067F, 0.4182041484F, 0.4186926583F, + 0.4191812359F, 0.4196698805F, 0.4201585915F, 0.4206473685F, + 0.4211362108F, 0.4216251179F, 0.4221140892F, 0.4226031241F, + 0.4230922221F, 0.4235813826F, 0.4240706050F, 0.4245598887F, + 0.4250492332F, 0.4255386379F, 0.4260281022F, 0.4265176256F, + 0.4270072075F, 0.4274968473F, 0.4279865445F, 0.4284762984F, + 0.4289661086F, 0.4294559743F, 0.4299458951F, 0.4304358704F, + 0.4309258996F, 0.4314159822F, 0.4319061175F, 0.4323963050F, + 0.4328865441F, 0.4333768342F, 0.4338671749F, 0.4343575654F, + 0.4348480052F, 0.4353384938F, 0.4358290306F, 0.4363196149F, + 0.4368102463F, 0.4373009241F, 0.4377916478F, 0.4382824168F, + 0.4387732305F, 0.4392640884F, 0.4397549899F, 0.4402459343F, + 0.4407369212F, 0.4412279499F, 0.4417190198F, 0.4422101305F, + 0.4427012813F, 0.4431924717F, 0.4436837010F, 0.4441749686F, + 0.4446662742F, 0.4451576169F, 0.4456489963F, 0.4461404118F, + 0.4466318628F, 0.4471233487F, 0.4476148690F, 0.4481064230F, + 0.4485980103F, 0.4490896302F, 0.4495812821F, 0.4500729654F, + 0.4505646797F, 0.4510564243F, 0.4515481986F, 0.4520400021F, + 0.4525318341F, 0.4530236942F, 0.4535155816F, 0.4540074959F, + 0.4544994365F, 0.4549914028F, 0.4554833941F, 0.4559754100F, + 0.4564674499F, 0.4569595131F, 0.4574515991F, 0.4579437074F, + 0.4584358372F, 0.4589279881F, 0.4594201595F, 0.4599123508F, + 0.4604045615F, 0.4608967908F, 0.4613890383F, 0.4618813034F, + 0.4623735855F, 0.4628658841F, 0.4633581984F, 0.4638505281F, + 0.4643428724F, 0.4648352308F, 0.4653276028F, 0.4658199877F, + 0.4663123849F, 0.4668047940F, 0.4672972143F, 0.4677896451F, + 0.4682820861F, 0.4687745365F, 0.4692669958F, 0.4697594634F, + 0.4702519387F, 0.4707444211F, 0.4712369102F, 0.4717294052F, + 0.4722219056F, 0.4727144109F, 0.4732069204F, 0.4736994336F, + 0.4741919498F, 0.4746844686F, 0.4751769893F, 0.4756695113F, + 0.4761620341F, 0.4766545571F, 0.4771470797F, 0.4776396013F, + 0.4781321213F, 0.4786246392F, 0.4791171544F, 0.4796096663F, + 0.4801021744F, 0.4805946779F, 0.4810871765F, 0.4815796694F, + 0.4820721561F, 0.4825646360F, 0.4830571086F, 0.4835495732F, + 0.4840420293F, 0.4845344763F, 0.4850269136F, 0.4855193407F, + 0.4860117569F, 0.4865041617F, 0.4869965545F, 0.4874889347F, + 0.4879813018F, 0.4884736551F, 0.4889659941F, 0.4894583182F, + 0.4899506268F, 0.4904429193F, 0.4909351952F, 0.4914274538F, + 0.4919196947F, 0.4924119172F, 0.4929041207F, 0.4933963046F, + 0.4938884685F, 0.4943806116F, 0.4948727335F, 0.4953648335F, + 0.4958569110F, 0.4963489656F, 0.4968409965F, 0.4973330032F, + 0.4978249852F, 0.4983169419F, 0.4988088726F, 0.4993007768F, + 0.4997926539F, 0.5002845034F, 0.5007763247F, 0.5012681171F, + 0.5017598801F, 0.5022516132F, 0.5027433157F, 0.5032349871F, + 0.5037266268F, 0.5042182341F, 0.5047098086F, 0.5052013497F, + 0.5056928567F, 0.5061843292F, 0.5066757664F, 0.5071671679F, + 0.5076585330F, 0.5081498613F, 0.5086411520F, 0.5091324047F, + 0.5096236187F, 0.5101147934F, 0.5106059284F, 0.5110970230F, + 0.5115880766F, 0.5120790887F, 0.5125700587F, 0.5130609860F, + 0.5135518700F, 0.5140427102F, 0.5145335059F, 0.5150242566F, + 0.5155149618F, 0.5160056208F, 0.5164962331F, 0.5169867980F, + 0.5174773151F, 0.5179677837F, 0.5184582033F, 0.5189485733F, + 0.5194388931F, 0.5199291621F, 0.5204193798F, 0.5209095455F, + 0.5213996588F, 0.5218897190F, 0.5223797256F, 0.5228696779F, + 0.5233595755F, 0.5238494177F, 0.5243392039F, 0.5248289337F, + 0.5253186063F, 0.5258082213F, 0.5262977781F, 0.5267872760F, + 0.5272767146F, 0.5277660932F, 0.5282554112F, 0.5287446682F, + 0.5292338635F, 0.5297229965F, 0.5302120667F, 0.5307010736F, + 0.5311900164F, 0.5316788947F, 0.5321677079F, 0.5326564554F, + 0.5331451366F, 0.5336337511F, 0.5341222981F, 0.5346107771F, + 0.5350991876F, 0.5355875290F, 0.5360758007F, 0.5365640021F, + 0.5370521327F, 0.5375401920F, 0.5380281792F, 0.5385160939F, + 0.5390039355F, 0.5394917034F, 0.5399793971F, 0.5404670159F, + 0.5409545594F, 0.5414420269F, 0.5419294179F, 0.5424167318F, + 0.5429039680F, 0.5433911261F, 0.5438782053F, 0.5443652051F, + 0.5448521250F, 0.5453389644F, 0.5458257228F, 0.5463123995F, + 0.5467989940F, 0.5472855057F, 0.5477719341F, 0.5482582786F, + 0.5487445387F, 0.5492307137F, 0.5497168031F, 0.5502028063F, + 0.5506887228F, 0.5511745520F, 0.5516602934F, 0.5521459463F, + 0.5526315103F, 0.5531169847F, 0.5536023690F, 0.5540876626F, + 0.5545728649F, 0.5550579755F, 0.5555429937F, 0.5560279189F, + 0.5565127507F, 0.5569974884F, 0.5574821315F, 0.5579666794F, + 0.5584511316F, 0.5589354875F, 0.5594197465F, 0.5599039080F, + 0.5603879716F, 0.5608719367F, 0.5613558026F, 0.5618395689F, + 0.5623232350F, 0.5628068002F, 0.5632902642F, 0.5637736262F, + 0.5642568858F, 0.5647400423F, 0.5652230953F, 0.5657060442F, + 0.5661888883F, 0.5666716272F, 0.5671542603F, 0.5676367870F, + 0.5681192069F, 0.5686015192F, 0.5690837235F, 0.5695658192F, + 0.5700478058F, 0.5705296827F, 0.5710114494F, 0.5714931052F, + 0.5719746497F, 0.5724560822F, 0.5729374023F, 0.5734186094F, + 0.5738997029F, 0.5743806823F, 0.5748615470F, 0.5753422965F, + 0.5758229301F, 0.5763034475F, 0.5767838480F, 0.5772641310F, + 0.5777442960F, 0.5782243426F, 0.5787042700F, 0.5791840778F, + 0.5796637654F, 0.5801433322F, 0.5806227778F, 0.5811021016F, + 0.5815813029F, 0.5820603814F, 0.5825393363F, 0.5830181673F, + 0.5834968737F, 0.5839754549F, 0.5844539105F, 0.5849322399F, + 0.5854104425F, 0.5858885179F, 0.5863664653F, 0.5868442844F, + 0.5873219746F, 0.5877995353F, 0.5882769660F, 0.5887542661F, + 0.5892314351F, 0.5897084724F, 0.5901853776F, 0.5906621500F, + 0.5911387892F, 0.5916152945F, 0.5920916655F, 0.5925679016F, + 0.5930440022F, 0.5935199669F, 0.5939957950F, 0.5944714861F, + 0.5949470396F, 0.5954224550F, 0.5958977317F, 0.5963728692F, + 0.5968478669F, 0.5973227244F, 0.5977974411F, 0.5982720163F, + 0.5987464497F, 0.5992207407F, 0.5996948887F, 0.6001688932F, + 0.6006427537F, 0.6011164696F, 0.6015900405F, 0.6020634657F, + 0.6025367447F, 0.6030098770F, 0.6034828621F, 0.6039556995F, + 0.6044283885F, 0.6049009288F, 0.6053733196F, 0.6058455606F, + 0.6063176512F, 0.6067895909F, 0.6072613790F, 0.6077330152F, + 0.6082044989F, 0.6086758295F, 0.6091470065F, 0.6096180294F, + 0.6100888977F, 0.6105596108F, 0.6110301682F, 0.6115005694F, + 0.6119708139F, 0.6124409011F, 0.6129108305F, 0.6133806017F, + 0.6138502139F, 0.6143196669F, 0.6147889599F, 0.6152580926F, + 0.6157270643F, 0.6161958746F, 0.6166645230F, 0.6171330088F, + 0.6176013317F, 0.6180694910F, 0.6185374863F, 0.6190053171F, + 0.6194729827F, 0.6199404828F, 0.6204078167F, 0.6208749841F, + 0.6213419842F, 0.6218088168F, 0.6222754811F, 0.6227419768F, + 0.6232083032F, 0.6236744600F, 0.6241404465F, 0.6246062622F, + 0.6250719067F, 0.6255373795F, 0.6260026799F, 0.6264678076F, + 0.6269327619F, 0.6273975425F, 0.6278621487F, 0.6283265800F, + 0.6287908361F, 0.6292549163F, 0.6297188201F, 0.6301825471F, + 0.6306460966F, 0.6311094683F, 0.6315726617F, 0.6320356761F, + 0.6324985111F, 0.6329611662F, 0.6334236410F, 0.6338859348F, + 0.6343480472F, 0.6348099777F, 0.6352717257F, 0.6357332909F, + 0.6361946726F, 0.6366558704F, 0.6371168837F, 0.6375777122F, + 0.6380383552F, 0.6384988123F, 0.6389590830F, 0.6394191668F, + 0.6398790631F, 0.6403387716F, 0.6407982916F, 0.6412576228F, + 0.6417167645F, 0.6421757163F, 0.6426344778F, 0.6430930483F, + 0.6435514275F, 0.6440096149F, 0.6444676098F, 0.6449254119F, + 0.6453830207F, 0.6458404356F, 0.6462976562F, 0.6467546820F, + 0.6472115125F, 0.6476681472F, 0.6481245856F, 0.6485808273F, + 0.6490368717F, 0.6494927183F, 0.6499483667F, 0.6504038164F, + 0.6508590670F, 0.6513141178F, 0.6517689684F, 0.6522236185F, + 0.6526780673F, 0.6531323146F, 0.6535863598F, 0.6540402024F, + 0.6544938419F, 0.6549472779F, 0.6554005099F, 0.6558535373F, + 0.6563063598F, 0.6567589769F, 0.6572113880F, 0.6576635927F, + 0.6581155906F, 0.6585673810F, 0.6590189637F, 0.6594703380F, + 0.6599215035F, 0.6603724598F, 0.6608232064F, 0.6612737427F, + 0.6617240684F, 0.6621741829F, 0.6626240859F, 0.6630737767F, + 0.6635232550F, 0.6639725202F, 0.6644215720F, 0.6648704098F, + 0.6653190332F, 0.6657674417F, 0.6662156348F, 0.6666636121F, + 0.6671113731F, 0.6675589174F, 0.6680062445F, 0.6684533538F, + 0.6689002450F, 0.6693469177F, 0.6697933712F, 0.6702396052F, + 0.6706856193F, 0.6711314129F, 0.6715769855F, 0.6720223369F, + 0.6724674664F, 0.6729123736F, 0.6733570581F, 0.6738015194F, + 0.6742457570F, 0.6746897706F, 0.6751335596F, 0.6755771236F, + 0.6760204621F, 0.6764635747F, 0.6769064609F, 0.6773491204F, + 0.6777915525F, 0.6782337570F, 0.6786757332F, 0.6791174809F, + 0.6795589995F, 0.6800002886F, 0.6804413477F, 0.6808821765F, + 0.6813227743F, 0.6817631409F, 0.6822032758F, 0.6826431785F, + 0.6830828485F, 0.6835222855F, 0.6839614890F, 0.6844004585F, + 0.6848391936F, 0.6852776939F, 0.6857159589F, 0.6861539883F, + 0.6865917815F, 0.6870293381F, 0.6874666576F, 0.6879037398F, + 0.6883405840F, 0.6887771899F, 0.6892135571F, 0.6896496850F, + 0.6900855733F, 0.6905212216F, 0.6909566294F, 0.6913917963F, + 0.6918267218F, 0.6922614055F, 0.6926958471F, 0.6931300459F, + 0.6935640018F, 0.6939977141F, 0.6944311825F, 0.6948644066F, + 0.6952973859F, 0.6957301200F, 0.6961626085F, 0.6965948510F, + 0.6970268470F, 0.6974585961F, 0.6978900980F, 0.6983213521F, + 0.6987523580F, 0.6991831154F, 0.6996136238F, 0.7000438828F, + 0.7004738921F, 0.7009036510F, 0.7013331594F, 0.7017624166F, + 0.7021914224F, 0.7026201763F, 0.7030486779F, 0.7034769268F, + 0.7039049226F, 0.7043326648F, 0.7047601531F, 0.7051873870F, + 0.7056143662F, 0.7060410902F, 0.7064675586F, 0.7068937711F, + 0.7073197271F, 0.7077454264F, 0.7081708684F, 0.7085960529F, + 0.7090209793F, 0.7094456474F, 0.7098700566F, 0.7102942066F, + 0.7107180970F, 0.7111417274F, 0.7115650974F, 0.7119882066F, + 0.7124110545F, 0.7128336409F, 0.7132559653F, 0.7136780272F, + 0.7140998264F, 0.7145213624F, 0.7149426348F, 0.7153636433F, + 0.7157843874F, 0.7162048668F, 0.7166250810F, 0.7170450296F, + 0.7174647124F, 0.7178841289F, 0.7183032786F, 0.7187221613F, + 0.7191407765F, 0.7195591239F, 0.7199772030F, 0.7203950135F, + 0.7208125550F, 0.7212298271F, 0.7216468294F, 0.7220635616F, + 0.7224800233F, 0.7228962140F, 0.7233121335F, 0.7237277813F, + 0.7241431571F, 0.7245582604F, 0.7249730910F, 0.7253876484F, + 0.7258019322F, 0.7262159422F, 0.7266296778F, 0.7270431388F, + 0.7274563247F, 0.7278692353F, 0.7282818700F, 0.7286942287F, + 0.7291063108F, 0.7295181160F, 0.7299296440F, 0.7303408944F, + 0.7307518669F, 0.7311625609F, 0.7315729763F, 0.7319831126F, + 0.7323929695F, 0.7328025466F, 0.7332118435F, 0.7336208600F, + 0.7340295955F, 0.7344380499F, 0.7348462226F, 0.7352541134F, + 0.7356617220F, 0.7360690478F, 0.7364760907F, 0.7368828502F, + 0.7372893259F, 0.7376955176F, 0.7381014249F, 0.7385070475F, + 0.7389123849F, 0.7393174368F, 0.7397222029F, 0.7401266829F, + 0.7405308763F, 0.7409347829F, 0.7413384023F, 0.7417417341F, + 0.7421447780F, 0.7425475338F, 0.7429500009F, 0.7433521791F, + 0.7437540681F, 0.7441556674F, 0.7445569769F, 0.7449579960F, + 0.7453587245F, 0.7457591621F, 0.7461593084F, 0.7465591631F, + 0.7469587259F, 0.7473579963F, 0.7477569741F, 0.7481556590F, + 0.7485540506F, 0.7489521486F, 0.7493499526F, 0.7497474623F, + 0.7501446775F, 0.7505415977F, 0.7509382227F, 0.7513345521F, + 0.7517305856F, 0.7521263229F, 0.7525217636F, 0.7529169074F, + 0.7533117541F, 0.7537063032F, 0.7541005545F, 0.7544945076F, + 0.7548881623F, 0.7552815182F, 0.7556745749F, 0.7560673323F, + 0.7564597899F, 0.7568519474F, 0.7572438046F, 0.7576353611F, + 0.7580266166F, 0.7584175708F, 0.7588082235F, 0.7591985741F, + 0.7595886226F, 0.7599783685F, 0.7603678116F, 0.7607569515F, + 0.7611457879F, 0.7615343206F, 0.7619225493F, 0.7623104735F, + 0.7626980931F, 0.7630854078F, 0.7634724171F, 0.7638591209F, + 0.7642455188F, 0.7646316106F, 0.7650173959F, 0.7654028744F, + 0.7657880459F, 0.7661729100F, 0.7665574664F, 0.7669417150F, + 0.7673256553F, 0.7677092871F, 0.7680926100F, 0.7684756239F, + 0.7688583284F, 0.7692407232F, 0.7696228080F, 0.7700045826F, + 0.7703860467F, 0.7707671999F, 0.7711480420F, 0.7715285728F, + 0.7719087918F, 0.7722886989F, 0.7726682938F, 0.7730475762F, + 0.7734265458F, 0.7738052023F, 0.7741835454F, 0.7745615750F, + 0.7749392906F, 0.7753166921F, 0.7756937791F, 0.7760705514F, + 0.7764470087F, 0.7768231508F, 0.7771989773F, 0.7775744880F, + 0.7779496827F, 0.7783245610F, 0.7786991227F, 0.7790733676F, + 0.7794472953F, 0.7798209056F, 0.7801941982F, 0.7805671729F, + 0.7809398294F, 0.7813121675F, 0.7816841869F, 0.7820558873F, + 0.7824272684F, 0.7827983301F, 0.7831690720F, 0.7835394940F, + 0.7839095957F, 0.7842793768F, 0.7846488373F, 0.7850179767F, + 0.7853867948F, 0.7857552914F, 0.7861234663F, 0.7864913191F, + 0.7868588497F, 0.7872260578F, 0.7875929431F, 0.7879595055F, + 0.7883257445F, 0.7886916601F, 0.7890572520F, 0.7894225198F, + 0.7897874635F, 0.7901520827F, 0.7905163772F, 0.7908803468F, + 0.7912439912F, 0.7916073102F, 0.7919703035F, 0.7923329710F, + 0.7926953124F, 0.7930573274F, 0.7934190158F, 0.7937803774F, + 0.7941414120F, 0.7945021193F, 0.7948624991F, 0.7952225511F, + 0.7955822752F, 0.7959416711F, 0.7963007387F, 0.7966594775F, + 0.7970178875F, 0.7973759685F, 0.7977337201F, 0.7980911422F, + 0.7984482346F, 0.7988049970F, 0.7991614292F, 0.7995175310F, + 0.7998733022F, 0.8002287426F, 0.8005838519F, 0.8009386299F, + 0.8012930765F, 0.8016471914F, 0.8020009744F, 0.8023544253F, + 0.8027075438F, 0.8030603298F, 0.8034127831F, 0.8037649035F, + 0.8041166906F, 0.8044681445F, 0.8048192647F, 0.8051700512F, + 0.8055205038F, 0.8058706222F, 0.8062204062F, 0.8065698556F, + 0.8069189702F, 0.8072677499F, 0.8076161944F, 0.8079643036F, + 0.8083120772F, 0.8086595151F, 0.8090066170F, 0.8093533827F, + 0.8096998122F, 0.8100459051F, 0.8103916613F, 0.8107370806F, + 0.8110821628F, 0.8114269077F, 0.8117713151F, 0.8121153849F, + 0.8124591169F, 0.8128025108F, 0.8131455666F, 0.8134882839F, + 0.8138306627F, 0.8141727027F, 0.8145144038F, 0.8148557658F, + 0.8151967886F, 0.8155374718F, 0.8158778154F, 0.8162178192F, + 0.8165574830F, 0.8168968067F, 0.8172357900F, 0.8175744328F, + 0.8179127349F, 0.8182506962F, 0.8185883164F, 0.8189255955F, + 0.8192625332F, 0.8195991295F, 0.8199353840F, 0.8202712967F, + 0.8206068673F, 0.8209420958F, 0.8212769820F, 0.8216115256F, + 0.8219457266F, 0.8222795848F, 0.8226131000F, 0.8229462721F, + 0.8232791009F, 0.8236115863F, 0.8239437280F, 0.8242755260F, + 0.8246069801F, 0.8249380901F, 0.8252688559F, 0.8255992774F, + 0.8259293544F, 0.8262590867F, 0.8265884741F, 0.8269175167F, + 0.8272462141F, 0.8275745663F, 0.8279025732F, 0.8282302344F, + 0.8285575501F, 0.8288845199F, 0.8292111437F, 0.8295374215F, + 0.8298633530F, 0.8301889382F, 0.8305141768F, 0.8308390688F, + 0.8311636141F, 0.8314878124F, 0.8318116637F, 0.8321351678F, + 0.8324583246F, 0.8327811340F, 0.8331035957F, 0.8334257098F, + 0.8337474761F, 0.8340688944F, 0.8343899647F, 0.8347106867F, + 0.8350310605F, 0.8353510857F, 0.8356707624F, 0.8359900904F, + 0.8363090696F, 0.8366276999F, 0.8369459811F, 0.8372639131F, + 0.8375814958F, 0.8378987292F, 0.8382156130F, 0.8385321472F, + 0.8388483316F, 0.8391641662F, 0.8394796508F, 0.8397947853F, + 0.8401095697F, 0.8404240037F, 0.8407380873F, 0.8410518204F, + 0.8413652029F, 0.8416782347F, 0.8419909156F, 0.8423032456F, + 0.8426152245F, 0.8429268523F, 0.8432381289F, 0.8435490541F, + 0.8438596279F, 0.8441698502F, 0.8444797208F, 0.8447892396F, + 0.8450984067F, 0.8454072218F, 0.8457156849F, 0.8460237959F, + 0.8463315547F, 0.8466389612F, 0.8469460154F, 0.8472527170F, + 0.8475590661F, 0.8478650625F, 0.8481707063F, 0.8484759971F, + 0.8487809351F, 0.8490855201F, 0.8493897521F, 0.8496936308F, + 0.8499971564F, 0.8503003286F, 0.8506031474F, 0.8509056128F, + 0.8512077246F, 0.8515094828F, 0.8518108872F, 0.8521119379F, + 0.8524126348F, 0.8527129777F, 0.8530129666F, 0.8533126015F, + 0.8536118822F, 0.8539108087F, 0.8542093809F, 0.8545075988F, + 0.8548054623F, 0.8551029712F, 0.8554001257F, 0.8556969255F, + 0.8559933707F, 0.8562894611F, 0.8565851968F, 0.8568805775F, + 0.8571756034F, 0.8574702743F, 0.8577645902F, 0.8580585509F, + 0.8583521566F, 0.8586454070F, 0.8589383021F, 0.8592308420F, + 0.8595230265F, 0.8598148556F, 0.8601063292F, 0.8603974473F, + 0.8606882098F, 0.8609786167F, 0.8612686680F, 0.8615583636F, + 0.8618477034F, 0.8621366874F, 0.8624253156F, 0.8627135878F, + 0.8630015042F, 0.8632890646F, 0.8635762690F, 0.8638631173F, + 0.8641496096F, 0.8644357457F, 0.8647215257F, 0.8650069495F, + 0.8652920171F, 0.8655767283F, 0.8658610833F, 0.8661450820F, + 0.8664287243F, 0.8667120102F, 0.8669949397F, 0.8672775127F, + 0.8675597293F, 0.8678415894F, 0.8681230929F, 0.8684042398F, + 0.8686850302F, 0.8689654640F, 0.8692455412F, 0.8695252617F, + 0.8698046255F, 0.8700836327F, 0.8703622831F, 0.8706405768F, + 0.8709185138F, 0.8711960940F, 0.8714733174F, 0.8717501840F, + 0.8720266939F, 0.8723028469F, 0.8725786430F, 0.8728540824F, + 0.8731291648F, 0.8734038905F, 0.8736782592F, 0.8739522711F, + 0.8742259261F, 0.8744992242F, 0.8747721653F, 0.8750447496F, + 0.8753169770F, 0.8755888475F, 0.8758603611F, 0.8761315177F, + 0.8764023175F, 0.8766727603F, 0.8769428462F, 0.8772125752F, + 0.8774819474F, 0.8777509626F, 0.8780196209F, 0.8782879224F, + 0.8785558669F, 0.8788234546F, 0.8790906854F, 0.8793575594F, + 0.8796240765F, 0.8798902368F, 0.8801560403F, 0.8804214870F, + 0.8806865768F, 0.8809513099F, 0.8812156863F, 0.8814797059F, + 0.8817433687F, 0.8820066749F, 0.8822696243F, 0.8825322171F, + 0.8827944532F, 0.8830563327F, 0.8833178556F, 0.8835790219F, + 0.8838398316F, 0.8841002848F, 0.8843603815F, 0.8846201217F, + 0.8848795054F, 0.8851385327F, 0.8853972036F, 0.8856555182F, + 0.8859134764F, 0.8861710783F, 0.8864283239F, 0.8866852133F, + 0.8869417464F, 0.8871979234F, 0.8874537443F, 0.8877092090F, + 0.8879643177F, 0.8882190704F, 0.8884734671F, 0.8887275078F, + 0.8889811927F, 0.8892345216F, 0.8894874948F, 0.8897401122F, + 0.8899923738F, 0.8902442798F, 0.8904958301F, 0.8907470248F, + 0.8909978640F, 0.8912483477F, 0.8914984759F, 0.8917482487F, + 0.8919976662F, 0.8922467284F, 0.8924954353F, 0.8927437871F, + 0.8929917837F, 0.8932394252F, 0.8934867118F, 0.8937336433F, + 0.8939802199F, 0.8942264417F, 0.8944723087F, 0.8947178210F, + 0.8949629785F, 0.8952077815F, 0.8954522299F, 0.8956963239F, + 0.8959400634F, 0.8961834486F, 0.8964264795F, 0.8966691561F, + 0.8969114786F, 0.8971534470F, 0.8973950614F, 0.8976363219F, + 0.8978772284F, 0.8981177812F, 0.8983579802F, 0.8985978256F, + 0.8988373174F, 0.8990764556F, 0.8993152405F, 0.8995536720F, + 0.8997917502F, 0.9000294751F, 0.9002668470F, 0.9005038658F, + 0.9007405317F, 0.9009768446F, 0.9012128048F, 0.9014484123F, + 0.9016836671F, 0.9019185693F, 0.9021531191F, 0.9023873165F, + 0.9026211616F, 0.9028546546F, 0.9030877954F, 0.9033205841F, + 0.9035530210F, 0.9037851059F, 0.9040168392F, 0.9042482207F, + 0.9044792507F, 0.9047099293F, 0.9049402564F, 0.9051702323F, + 0.9053998569F, 0.9056291305F, 0.9058580531F, 0.9060866248F, + 0.9063148457F, 0.9065427159F, 0.9067702355F, 0.9069974046F, + 0.9072242233F, 0.9074506917F, 0.9076768100F, 0.9079025782F, + 0.9081279964F, 0.9083530647F, 0.9085777833F, 0.9088021523F, + 0.9090261717F, 0.9092498417F, 0.9094731623F, 0.9096961338F, + 0.9099187561F, 0.9101410295F, 0.9103629540F, 0.9105845297F, + 0.9108057568F, 0.9110266354F, 0.9112471656F, 0.9114673475F, + 0.9116871812F, 0.9119066668F, 0.9121258046F, 0.9123445945F, + 0.9125630367F, 0.9127811314F, 0.9129988786F, 0.9132162785F, + 0.9134333312F, 0.9136500368F, 0.9138663954F, 0.9140824073F, + 0.9142980724F, 0.9145133910F, 0.9147283632F, 0.9149429890F, + 0.9151572687F, 0.9153712023F, 0.9155847900F, 0.9157980319F, + 0.9160109282F, 0.9162234790F, 0.9164356844F, 0.9166475445F, + 0.9168590595F, 0.9170702296F, 0.9172810548F, 0.9174915354F, + 0.9177016714F, 0.9179114629F, 0.9181209102F, 0.9183300134F, + 0.9185387726F, 0.9187471879F, 0.9189552595F, 0.9191629876F, + 0.9193703723F, 0.9195774136F, 0.9197841119F, 0.9199904672F, + 0.9201964797F, 0.9204021495F, 0.9206074767F, 0.9208124616F, + 0.9210171043F, 0.9212214049F, 0.9214253636F, 0.9216289805F, + 0.9218322558F, 0.9220351896F, 0.9222377821F, 0.9224400335F, + 0.9226419439F, 0.9228435134F, 0.9230447423F, 0.9232456307F, + 0.9234461787F, 0.9236463865F, 0.9238462543F, 0.9240457822F, + 0.9242449704F, 0.9244438190F, 0.9246423282F, 0.9248404983F, + 0.9250383293F, 0.9252358214F, 0.9254329747F, 0.9256297896F, + 0.9258262660F, 0.9260224042F, 0.9262182044F, 0.9264136667F, + 0.9266087913F, 0.9268035783F, 0.9269980280F, 0.9271921405F, + 0.9273859160F, 0.9275793546F, 0.9277724566F, 0.9279652221F, + 0.9281576513F, 0.9283497443F, 0.9285415014F, 0.9287329227F, + 0.9289240084F, 0.9291147586F, 0.9293051737F, 0.9294952536F, + 0.9296849987F, 0.9298744091F, 0.9300634850F, 0.9302522266F, + 0.9304406340F, 0.9306287074F, 0.9308164471F, 0.9310038532F, + 0.9311909259F, 0.9313776654F, 0.9315640719F, 0.9317501455F, + 0.9319358865F, 0.9321212951F, 0.9323063713F, 0.9324911155F, + 0.9326755279F, 0.9328596085F, 0.9330433577F, 0.9332267756F, + 0.9334098623F, 0.9335926182F, 0.9337750434F, 0.9339571380F, + 0.9341389023F, 0.9343203366F, 0.9345014409F, 0.9346822155F, + 0.9348626606F, 0.9350427763F, 0.9352225630F, 0.9354020207F, + 0.9355811498F, 0.9357599503F, 0.9359384226F, 0.9361165667F, + 0.9362943830F, 0.9364718716F, 0.9366490327F, 0.9368258666F, + 0.9370023733F, 0.9371785533F, 0.9373544066F, 0.9375299335F, + 0.9377051341F, 0.9378800087F, 0.9380545576F, 0.9382287809F, + 0.9384026787F, 0.9385762515F, 0.9387494993F, 0.9389224223F, + 0.9390950209F, 0.9392672951F, 0.9394392453F, 0.9396108716F, + 0.9397821743F, 0.9399531536F, 0.9401238096F, 0.9402941427F, + 0.9404641530F, 0.9406338407F, 0.9408032061F, 0.9409722495F, + 0.9411409709F, 0.9413093707F, 0.9414774491F, 0.9416452062F, + 0.9418126424F, 0.9419797579F, 0.9421465528F, 0.9423130274F, + 0.9424791819F, 0.9426450166F, 0.9428105317F, 0.9429757274F, + 0.9431406039F, 0.9433051616F, 0.9434694005F, 0.9436333209F, + 0.9437969232F, 0.9439602074F, 0.9441231739F, 0.9442858229F, + 0.9444481545F, 0.9446101691F, 0.9447718669F, 0.9449332481F, + 0.9450943129F, 0.9452550617F, 0.9454154945F, 0.9455756118F, + 0.9457354136F, 0.9458949003F, 0.9460540721F, 0.9462129292F, + 0.9463714719F, 0.9465297003F, 0.9466876149F, 0.9468452157F, + 0.9470025031F, 0.9471594772F, 0.9473161384F, 0.9474724869F, + 0.9476285229F, 0.9477842466F, 0.9479396584F, 0.9480947585F, + 0.9482495470F, 0.9484040243F, 0.9485581906F, 0.9487120462F, + 0.9488655913F, 0.9490188262F, 0.9491717511F, 0.9493243662F, + 0.9494766718F, 0.9496286683F, 0.9497803557F, 0.9499317345F, + 0.9500828047F, 0.9502335668F, 0.9503840209F, 0.9505341673F, + 0.9506840062F, 0.9508335380F, 0.9509827629F, 0.9511316810F, + 0.9512802928F, 0.9514285984F, 0.9515765982F, 0.9517242923F, + 0.9518716810F, 0.9520187646F, 0.9521655434F, 0.9523120176F, + 0.9524581875F, 0.9526040534F, 0.9527496154F, 0.9528948739F, + 0.9530398292F, 0.9531844814F, 0.9533288310F, 0.9534728780F, + 0.9536166229F, 0.9537600659F, 0.9539032071F, 0.9540460470F, + 0.9541885858F, 0.9543308237F, 0.9544727611F, 0.9546143981F, + 0.9547557351F, 0.9548967723F, 0.9550375100F, 0.9551779485F, + 0.9553180881F, 0.9554579290F, 0.9555974714F, 0.9557367158F, + 0.9558756623F, 0.9560143112F, 0.9561526628F, 0.9562907174F, + 0.9564284752F, 0.9565659366F, 0.9567031017F, 0.9568399710F, + 0.9569765446F, 0.9571128229F, 0.9572488061F, 0.9573844944F, + 0.9575198883F, 0.9576549879F, 0.9577897936F, 0.9579243056F, + 0.9580585242F, 0.9581924497F, 0.9583260824F, 0.9584594226F, + 0.9585924705F, 0.9587252264F, 0.9588576906F, 0.9589898634F, + 0.9591217452F, 0.9592533360F, 0.9593846364F, 0.9595156465F, + 0.9596463666F, 0.9597767971F, 0.9599069382F, 0.9600367901F, + 0.9601663533F, 0.9602956279F, 0.9604246143F, 0.9605533128F, + 0.9606817236F, 0.9608098471F, 0.9609376835F, 0.9610652332F, + 0.9611924963F, 0.9613194733F, 0.9614461644F, 0.9615725699F, + 0.9616986901F, 0.9618245253F, 0.9619500757F, 0.9620753418F, + 0.9622003238F, 0.9623250219F, 0.9624494365F, 0.9625735679F, + 0.9626974163F, 0.9628209821F, 0.9629442656F, 0.9630672671F, + 0.9631899868F, 0.9633124251F, 0.9634345822F, 0.9635564585F, + 0.9636780543F, 0.9637993699F, 0.9639204056F, 0.9640411616F, + 0.9641616383F, 0.9642818359F, 0.9644017549F, 0.9645213955F, + 0.9646407579F, 0.9647598426F, 0.9648786497F, 0.9649971797F, + 0.9651154328F, 0.9652334092F, 0.9653511095F, 0.9654685337F, + 0.9655856823F, 0.9657025556F, 0.9658191538F, 0.9659354773F, + 0.9660515263F, 0.9661673013F, 0.9662828024F, 0.9663980300F, + 0.9665129845F, 0.9666276660F, 0.9667420750F, 0.9668562118F, + 0.9669700766F, 0.9670836698F, 0.9671969917F, 0.9673100425F, + 0.9674228227F, 0.9675353325F, 0.9676475722F, 0.9677595422F, + 0.9678712428F, 0.9679826742F, 0.9680938368F, 0.9682047309F, + 0.9683153569F, 0.9684257150F, 0.9685358056F, 0.9686456289F, + 0.9687551853F, 0.9688644752F, 0.9689734987F, 0.9690822564F, + 0.9691907483F, 0.9692989750F, 0.9694069367F, 0.9695146337F, + 0.9696220663F, 0.9697292349F, 0.9698361398F, 0.9699427813F, + 0.9700491597F, 0.9701552754F, 0.9702611286F, 0.9703667197F, + 0.9704720490F, 0.9705771169F, 0.9706819236F, 0.9707864695F, + 0.9708907549F, 0.9709947802F, 0.9710985456F, 0.9712020514F, + 0.9713052981F, 0.9714082859F, 0.9715110151F, 0.9716134862F, + 0.9717156993F, 0.9718176549F, 0.9719193532F, 0.9720207946F, + 0.9721219794F, 0.9722229080F, 0.9723235806F, 0.9724239976F, + 0.9725241593F, 0.9726240661F, 0.9727237183F, 0.9728231161F, + 0.9729222601F, 0.9730211503F, 0.9731197873F, 0.9732181713F, + 0.9733163027F, 0.9734141817F, 0.9735118088F, 0.9736091842F, + 0.9737063083F, 0.9738031814F, 0.9738998039F, 0.9739961760F, + 0.9740922981F, 0.9741881706F, 0.9742837938F, 0.9743791680F, + 0.9744742935F, 0.9745691707F, 0.9746637999F, 0.9747581814F, + 0.9748523157F, 0.9749462029F, 0.9750398435F, 0.9751332378F, + 0.9752263861F, 0.9753192887F, 0.9754119461F, 0.9755043585F, + 0.9755965262F, 0.9756884496F, 0.9757801291F, 0.9758715650F, + 0.9759627575F, 0.9760537071F, 0.9761444141F, 0.9762348789F, + 0.9763251016F, 0.9764150828F, 0.9765048228F, 0.9765943218F, + 0.9766835802F, 0.9767725984F, 0.9768613767F, 0.9769499154F, + 0.9770382149F, 0.9771262755F, 0.9772140976F, 0.9773016815F, + 0.9773890275F, 0.9774761360F, 0.9775630073F, 0.9776496418F, + 0.9777360398F, 0.9778222016F, 0.9779081277F, 0.9779938182F, + 0.9780792736F, 0.9781644943F, 0.9782494805F, 0.9783342326F, + 0.9784187509F, 0.9785030359F, 0.9785870877F, 0.9786709069F, + 0.9787544936F, 0.9788378484F, 0.9789209714F, 0.9790038631F, + 0.9790865238F, 0.9791689538F, 0.9792511535F, 0.9793331232F, + 0.9794148633F, 0.9794963742F, 0.9795776561F, 0.9796587094F, + 0.9797395345F, 0.9798201316F, 0.9799005013F, 0.9799806437F, + 0.9800605593F, 0.9801402483F, 0.9802197112F, 0.9802989483F, + 0.9803779600F, 0.9804567465F, 0.9805353082F, 0.9806136455F, + 0.9806917587F, 0.9807696482F, 0.9808473143F, 0.9809247574F, + 0.9810019778F, 0.9810789759F, 0.9811557519F, 0.9812323064F, + 0.9813086395F, 0.9813847517F, 0.9814606433F, 0.9815363147F, + 0.9816117662F, 0.9816869981F, 0.9817620108F, 0.9818368047F, + 0.9819113801F, 0.9819857374F, 0.9820598769F, 0.9821337989F, + 0.9822075038F, 0.9822809920F, 0.9823542638F, 0.9824273195F, + 0.9825001596F, 0.9825727843F, 0.9826451940F, 0.9827173891F, + 0.9827893700F, 0.9828611368F, 0.9829326901F, 0.9830040302F, + 0.9830751574F, 0.9831460720F, 0.9832167745F, 0.9832872652F, + 0.9833575444F, 0.9834276124F, 0.9834974697F, 0.9835671166F, + 0.9836365535F, 0.9837057806F, 0.9837747983F, 0.9838436071F, + 0.9839122072F, 0.9839805990F, 0.9840487829F, 0.9841167591F, + 0.9841845282F, 0.9842520903F, 0.9843194459F, 0.9843865953F, + 0.9844535389F, 0.9845202771F, 0.9845868101F, 0.9846531383F, + 0.9847192622F, 0.9847851820F, 0.9848508980F, 0.9849164108F, + 0.9849817205F, 0.9850468276F, 0.9851117324F, 0.9851764352F, + 0.9852409365F, 0.9853052366F, 0.9853693358F, 0.9854332344F, + 0.9854969330F, 0.9855604317F, 0.9856237309F, 0.9856868310F, + 0.9857497325F, 0.9858124355F, 0.9858749404F, 0.9859372477F, + 0.9859993577F, 0.9860612707F, 0.9861229871F, 0.9861845072F, + 0.9862458315F, 0.9863069601F, 0.9863678936F, 0.9864286322F, + 0.9864891764F, 0.9865495264F, 0.9866096826F, 0.9866696454F, + 0.9867294152F, 0.9867889922F, 0.9868483769F, 0.9869075695F, + 0.9869665706F, 0.9870253803F, 0.9870839991F, 0.9871424273F, + 0.9872006653F, 0.9872587135F, 0.9873165721F, 0.9873742415F, + 0.9874317222F, 0.9874890144F, 0.9875461185F, 0.9876030348F, + 0.9876597638F, 0.9877163057F, 0.9877726610F, 0.9878288300F, + 0.9878848130F, 0.9879406104F, 0.9879962225F, 0.9880516497F, + 0.9881068924F, 0.9881619509F, 0.9882168256F, 0.9882715168F, + 0.9883260249F, 0.9883803502F, 0.9884344931F, 0.9884884539F, + 0.9885422331F, 0.9885958309F, 0.9886492477F, 0.9887024838F, + 0.9887555397F, 0.9888084157F, 0.9888611120F, 0.9889136292F, + 0.9889659675F, 0.9890181273F, 0.9890701089F, 0.9891219128F, + 0.9891735392F, 0.9892249885F, 0.9892762610F, 0.9893273572F, + 0.9893782774F, 0.9894290219F, 0.9894795911F, 0.9895299853F, + 0.9895802049F, 0.9896302502F, 0.9896801217F, 0.9897298196F, + 0.9897793443F, 0.9898286961F, 0.9898778755F, 0.9899268828F, + 0.9899757183F, 0.9900243823F, 0.9900728753F, 0.9901211976F, + 0.9901693495F, 0.9902173314F, 0.9902651436F, 0.9903127865F, + 0.9903602605F, 0.9904075659F, 0.9904547031F, 0.9905016723F, + 0.9905484740F, 0.9905951086F, 0.9906415763F, 0.9906878775F, + 0.9907340126F, 0.9907799819F, 0.9908257858F, 0.9908714247F, + 0.9909168988F, 0.9909622086F, 0.9910073543F, 0.9910523364F, + 0.9910971552F, 0.9911418110F, 0.9911863042F, 0.9912306351F, + 0.9912748042F, 0.9913188117F, 0.9913626580F, 0.9914063435F, + 0.9914498684F, 0.9914932333F, 0.9915364383F, 0.9915794839F, + 0.9916223703F, 0.9916650981F, 0.9917076674F, 0.9917500787F, + 0.9917923323F, 0.9918344286F, 0.9918763679F, 0.9919181505F, + 0.9919597769F, 0.9920012473F, 0.9920425621F, 0.9920837217F, + 0.9921247263F, 0.9921655765F, 0.9922062724F, 0.9922468145F, + 0.9922872030F, 0.9923274385F, 0.9923675211F, 0.9924074513F, + 0.9924472294F, 0.9924868557F, 0.9925263306F, 0.9925656544F, + 0.9926048275F, 0.9926438503F, 0.9926827230F, 0.9927214461F, + 0.9927600199F, 0.9927984446F, 0.9928367208F, 0.9928748486F, + 0.9929128285F, 0.9929506608F, 0.9929883459F, 0.9930258841F, + 0.9930632757F, 0.9931005211F, 0.9931376207F, 0.9931745747F, + 0.9932113836F, 0.9932480476F, 0.9932845671F, 0.9933209425F, + 0.9933571742F, 0.9933932623F, 0.9934292074F, 0.9934650097F, + 0.9935006696F, 0.9935361874F, 0.9935715635F, 0.9936067982F, + 0.9936418919F, 0.9936768448F, 0.9937116574F, 0.9937463300F, + 0.9937808629F, 0.9938152565F, 0.9938495111F, 0.9938836271F, + 0.9939176047F, 0.9939514444F, 0.9939851465F, 0.9940187112F, + 0.9940521391F, 0.9940854303F, 0.9941185853F, 0.9941516044F, + 0.9941844879F, 0.9942172361F, 0.9942498495F, 0.9942823283F, + 0.9943146729F, 0.9943468836F, 0.9943789608F, 0.9944109047F, + 0.9944427158F, 0.9944743944F, 0.9945059408F, 0.9945373553F, + 0.9945686384F, 0.9945997902F, 0.9946308112F, 0.9946617017F, + 0.9946924621F, 0.9947230926F, 0.9947535937F, 0.9947839656F, + 0.9948142086F, 0.9948443232F, 0.9948743097F, 0.9949041683F, + 0.9949338995F, 0.9949635035F, 0.9949929807F, 0.9950223315F, + 0.9950515561F, 0.9950806549F, 0.9951096282F, 0.9951384764F, + 0.9951671998F, 0.9951957987F, 0.9952242735F, 0.9952526245F, + 0.9952808520F, 0.9953089564F, 0.9953369380F, 0.9953647971F, + 0.9953925340F, 0.9954201491F, 0.9954476428F, 0.9954750153F, + 0.9955022670F, 0.9955293981F, 0.9955564092F, 0.9955833003F, + 0.9956100720F, 0.9956367245F, 0.9956632582F, 0.9956896733F, + 0.9957159703F, 0.9957421494F, 0.9957682110F, 0.9957941553F, + 0.9958199828F, 0.9958456937F, 0.9958712884F, 0.9958967672F, + 0.9959221305F, 0.9959473784F, 0.9959725115F, 0.9959975300F, + 0.9960224342F, 0.9960472244F, 0.9960719011F, 0.9960964644F, + 0.9961209148F, 0.9961452525F, 0.9961694779F, 0.9961935913F, + 0.9962175930F, 0.9962414834F, 0.9962652627F, 0.9962889313F, + 0.9963124895F, 0.9963359377F, 0.9963592761F, 0.9963825051F, + 0.9964056250F, 0.9964286361F, 0.9964515387F, 0.9964743332F, + 0.9964970198F, 0.9965195990F, 0.9965420709F, 0.9965644360F, + 0.9965866946F, 0.9966088469F, 0.9966308932F, 0.9966528340F, + 0.9966746695F, 0.9966964001F, 0.9967180260F, 0.9967395475F, + 0.9967609651F, 0.9967822789F, 0.9968034894F, 0.9968245968F, + 0.9968456014F, 0.9968665036F, 0.9968873037F, 0.9969080019F, + 0.9969285987F, 0.9969490942F, 0.9969694889F, 0.9969897830F, + 0.9970099769F, 0.9970300708F, 0.9970500651F, 0.9970699601F, + 0.9970897561F, 0.9971094533F, 0.9971290522F, 0.9971485531F, + 0.9971679561F, 0.9971872617F, 0.9972064702F, 0.9972255818F, + 0.9972445968F, 0.9972635157F, 0.9972823386F, 0.9973010659F, + 0.9973196980F, 0.9973382350F, 0.9973566773F, 0.9973750253F, + 0.9973932791F, 0.9974114392F, 0.9974295059F, 0.9974474793F, + 0.9974653599F, 0.9974831480F, 0.9975008438F, 0.9975184476F, + 0.9975359598F, 0.9975533806F, 0.9975707104F, 0.9975879495F, + 0.9976050981F, 0.9976221566F, 0.9976391252F, 0.9976560043F, + 0.9976727941F, 0.9976894950F, 0.9977061073F, 0.9977226312F, + 0.9977390671F, 0.9977554152F, 0.9977716759F, 0.9977878495F, + 0.9978039361F, 0.9978199363F, 0.9978358501F, 0.9978516780F, + 0.9978674202F, 0.9978830771F, 0.9978986488F, 0.9979141358F, + 0.9979295383F, 0.9979448566F, 0.9979600909F, 0.9979752417F, + 0.9979903091F, 0.9980052936F, 0.9980201952F, 0.9980350145F, + 0.9980497515F, 0.9980644067F, 0.9980789804F, 0.9980934727F, + 0.9981078841F, 0.9981222147F, 0.9981364649F, 0.9981506350F, + 0.9981647253F, 0.9981787360F, 0.9981926674F, 0.9982065199F, + 0.9982202936F, 0.9982339890F, 0.9982476062F, 0.9982611456F, + 0.9982746074F, 0.9982879920F, 0.9983012996F, 0.9983145304F, + 0.9983276849F, 0.9983407632F, 0.9983537657F, 0.9983666926F, + 0.9983795442F, 0.9983923208F, 0.9984050226F, 0.9984176501F, + 0.9984302033F, 0.9984426827F, 0.9984550884F, 0.9984674208F, + 0.9984796802F, 0.9984918667F, 0.9985039808F, 0.9985160227F, + 0.9985279926F, 0.9985398909F, 0.9985517177F, 0.9985634734F, + 0.9985751583F, 0.9985867727F, 0.9985983167F, 0.9986097907F, + 0.9986211949F, 0.9986325297F, 0.9986437953F, 0.9986549919F, + 0.9986661199F, 0.9986771795F, 0.9986881710F, 0.9986990946F, + 0.9987099507F, 0.9987207394F, 0.9987314611F, 0.9987421161F, + 0.9987527045F, 0.9987632267F, 0.9987736829F, 0.9987840734F, + 0.9987943985F, 0.9988046584F, 0.9988148534F, 0.9988249838F, + 0.9988350498F, 0.9988450516F, 0.9988549897F, 0.9988648641F, + 0.9988746753F, 0.9988844233F, 0.9988941086F, 0.9989037313F, + 0.9989132918F, 0.9989227902F, 0.9989322269F, 0.9989416021F, + 0.9989509160F, 0.9989601690F, 0.9989693613F, 0.9989784931F, + 0.9989875647F, 0.9989965763F, 0.9990055283F, 0.9990144208F, + 0.9990232541F, 0.9990320286F, 0.9990407443F, 0.9990494016F, + 0.9990580008F, 0.9990665421F, 0.9990750257F, 0.9990834519F, + 0.9990918209F, 0.9991001331F, 0.9991083886F, 0.9991165877F, + 0.9991247307F, 0.9991328177F, 0.9991408491F, 0.9991488251F, + 0.9991567460F, 0.9991646119F, 0.9991724232F, 0.9991801801F, + 0.9991878828F, 0.9991955316F, 0.9992031267F, 0.9992106684F, + 0.9992181569F, 0.9992255925F, 0.9992329753F, 0.9992403057F, + 0.9992475839F, 0.9992548101F, 0.9992619846F, 0.9992691076F, + 0.9992761793F, 0.9992832001F, 0.9992901701F, 0.9992970895F, + 0.9993039587F, 0.9993107777F, 0.9993175470F, 0.9993242667F, + 0.9993309371F, 0.9993375583F, 0.9993441307F, 0.9993506545F, + 0.9993571298F, 0.9993635570F, 0.9993699362F, 0.9993762678F, + 0.9993825519F, 0.9993887887F, 0.9993949785F, 0.9994011216F, + 0.9994072181F, 0.9994132683F, 0.9994192725F, 0.9994252307F, + 0.9994311434F, 0.9994370107F, 0.9994428327F, 0.9994486099F, + 0.9994543423F, 0.9994600303F, 0.9994656739F, 0.9994712736F, + 0.9994768294F, 0.9994823417F, 0.9994878105F, 0.9994932363F, + 0.9994986191F, 0.9995039592F, 0.9995092568F, 0.9995145122F, + 0.9995197256F, 0.9995248971F, 0.9995300270F, 0.9995351156F, + 0.9995401630F, 0.9995451695F, 0.9995501352F, 0.9995550604F, + 0.9995599454F, 0.9995647903F, 0.9995695953F, 0.9995743607F, + 0.9995790866F, 0.9995837734F, 0.9995884211F, 0.9995930300F, + 0.9995976004F, 0.9996021324F, 0.9996066263F, 0.9996110822F, + 0.9996155004F, 0.9996198810F, 0.9996242244F, 0.9996285306F, + 0.9996327999F, 0.9996370326F, 0.9996412287F, 0.9996453886F, + 0.9996495125F, 0.9996536004F, 0.9996576527F, 0.9996616696F, + 0.9996656512F, 0.9996695977F, 0.9996735094F, 0.9996773865F, + 0.9996812291F, 0.9996850374F, 0.9996888118F, 0.9996925523F, + 0.9996962591F, 0.9996999325F, 0.9997035727F, 0.9997071798F, + 0.9997107541F, 0.9997142957F, 0.9997178049F, 0.9997212818F, + 0.9997247266F, 0.9997281396F, 0.9997315209F, 0.9997348708F, + 0.9997381893F, 0.9997414767F, 0.9997447333F, 0.9997479591F, + 0.9997511544F, 0.9997543194F, 0.9997574542F, 0.9997605591F, + 0.9997636342F, 0.9997666797F, 0.9997696958F, 0.9997726828F, + 0.9997756407F, 0.9997785698F, 0.9997814703F, 0.9997843423F, + 0.9997871860F, 0.9997900016F, 0.9997927894F, 0.9997955494F, + 0.9997982818F, 0.9998009869F, 0.9998036648F, 0.9998063157F, + 0.9998089398F, 0.9998115373F, 0.9998141082F, 0.9998166529F, + 0.9998191715F, 0.9998216642F, 0.9998241311F, 0.9998265724F, + 0.9998289884F, 0.9998313790F, 0.9998337447F, 0.9998360854F, + 0.9998384015F, 0.9998406930F, 0.9998429602F, 0.9998452031F, + 0.9998474221F, 0.9998496171F, 0.9998517885F, 0.9998539364F, + 0.9998560610F, 0.9998581624F, 0.9998602407F, 0.9998622962F, + 0.9998643291F, 0.9998663394F, 0.9998683274F, 0.9998702932F, + 0.9998722370F, 0.9998741589F, 0.9998760591F, 0.9998779378F, + 0.9998797952F, 0.9998816313F, 0.9998834464F, 0.9998852406F, + 0.9998870141F, 0.9998887670F, 0.9998904995F, 0.9998922117F, + 0.9998939039F, 0.9998955761F, 0.9998972285F, 0.9998988613F, + 0.9999004746F, 0.9999020686F, 0.9999036434F, 0.9999051992F, + 0.9999067362F, 0.9999082544F, 0.9999097541F, 0.9999112354F, + 0.9999126984F, 0.9999141433F, 0.9999155703F, 0.9999169794F, + 0.9999183709F, 0.9999197449F, 0.9999211014F, 0.9999224408F, + 0.9999237631F, 0.9999250684F, 0.9999263570F, 0.9999276289F, + 0.9999288843F, 0.9999301233F, 0.9999313461F, 0.9999325529F, + 0.9999337437F, 0.9999349187F, 0.9999360780F, 0.9999372218F, + 0.9999383503F, 0.9999394635F, 0.9999405616F, 0.9999416447F, + 0.9999427129F, 0.9999437665F, 0.9999448055F, 0.9999458301F, + 0.9999468404F, 0.9999478365F, 0.9999488185F, 0.9999497867F, + 0.9999507411F, 0.9999516819F, 0.9999526091F, 0.9999535230F, + 0.9999544236F, 0.9999553111F, 0.9999561856F, 0.9999570472F, + 0.9999578960F, 0.9999587323F, 0.9999595560F, 0.9999603674F, + 0.9999611666F, 0.9999619536F, 0.9999627286F, 0.9999634917F, + 0.9999642431F, 0.9999649828F, 0.9999657110F, 0.9999664278F, + 0.9999671334F, 0.9999678278F, 0.9999685111F, 0.9999691835F, + 0.9999698451F, 0.9999704960F, 0.9999711364F, 0.9999717662F, + 0.9999723858F, 0.9999729950F, 0.9999735942F, 0.9999741834F, + 0.9999747626F, 0.9999753321F, 0.9999758919F, 0.9999764421F, + 0.9999769828F, 0.9999775143F, 0.9999780364F, 0.9999785495F, + 0.9999790535F, 0.9999795485F, 0.9999800348F, 0.9999805124F, + 0.9999809813F, 0.9999814417F, 0.9999818938F, 0.9999823375F, + 0.9999827731F, 0.9999832005F, 0.9999836200F, 0.9999840316F, + 0.9999844353F, 0.9999848314F, 0.9999852199F, 0.9999856008F, + 0.9999859744F, 0.9999863407F, 0.9999866997F, 0.9999870516F, + 0.9999873965F, 0.9999877345F, 0.9999880656F, 0.9999883900F, + 0.9999887078F, 0.9999890190F, 0.9999893237F, 0.9999896220F, + 0.9999899140F, 0.9999901999F, 0.9999904796F, 0.9999907533F, + 0.9999910211F, 0.9999912830F, 0.9999915391F, 0.9999917896F, + 0.9999920345F, 0.9999922738F, 0.9999925077F, 0.9999927363F, + 0.9999929596F, 0.9999931777F, 0.9999933907F, 0.9999935987F, + 0.9999938018F, 0.9999940000F, 0.9999941934F, 0.9999943820F, + 0.9999945661F, 0.9999947456F, 0.9999949206F, 0.9999950912F, + 0.9999952575F, 0.9999954195F, 0.9999955773F, 0.9999957311F, + 0.9999958807F, 0.9999960265F, 0.9999961683F, 0.9999963063F, + 0.9999964405F, 0.9999965710F, 0.9999966979F, 0.9999968213F, + 0.9999969412F, 0.9999970576F, 0.9999971707F, 0.9999972805F, + 0.9999973871F, 0.9999974905F, 0.9999975909F, 0.9999976881F, + 0.9999977824F, 0.9999978738F, 0.9999979624F, 0.9999980481F, + 0.9999981311F, 0.9999982115F, 0.9999982892F, 0.9999983644F, + 0.9999984370F, 0.9999985072F, 0.9999985750F, 0.9999986405F, + 0.9999987037F, 0.9999987647F, 0.9999988235F, 0.9999988802F, + 0.9999989348F, 0.9999989873F, 0.9999990379F, 0.9999990866F, + 0.9999991334F, 0.9999991784F, 0.9999992217F, 0.9999992632F, + 0.9999993030F, 0.9999993411F, 0.9999993777F, 0.9999994128F, + 0.9999994463F, 0.9999994784F, 0.9999995091F, 0.9999995384F, + 0.9999995663F, 0.9999995930F, 0.9999996184F, 0.9999996426F, + 0.9999996657F, 0.9999996876F, 0.9999997084F, 0.9999997282F, + 0.9999997469F, 0.9999997647F, 0.9999997815F, 0.9999997973F, + 0.9999998123F, 0.9999998265F, 0.9999998398F, 0.9999998524F, + 0.9999998642F, 0.9999998753F, 0.9999998857F, 0.9999998954F, + 0.9999999045F, 0.9999999130F, 0.9999999209F, 0.9999999282F, + 0.9999999351F, 0.9999999414F, 0.9999999472F, 0.9999999526F, + 0.9999999576F, 0.9999999622F, 0.9999999664F, 0.9999999702F, + 0.9999999737F, 0.9999999769F, 0.9999999798F, 0.9999999824F, + 0.9999999847F, 0.9999999868F, 0.9999999887F, 0.9999999904F, + 0.9999999919F, 0.9999999932F, 0.9999999943F, 0.9999999953F, + 0.9999999961F, 0.9999999969F, 0.9999999975F, 0.9999999980F, + 0.9999999985F, 0.9999999988F, 0.9999999991F, 0.9999999993F, + 0.9999999995F, 0.9999999997F, 0.9999999998F, 0.9999999999F, + 0.9999999999F, 1.0000000000F, 1.0000000000F, 1.0000000000F, + 1.0000000000F, 1.0000000000F, 1.0000000000F, 1.0000000000F, +}; + +static float *vwin[8] = { + vwin64, + vwin128, + vwin256, + vwin512, + vwin1024, + vwin2048, + vwin4096, + vwin8192, +}; + +float *_vorbis_window_get(int n){ + return vwin[n]; +} + +void _vorbis_apply_window(float *d,int *winno,long *blocksizes, + int lW,int W,int nW){ + lW=(W?lW:0); + nW=(W?nW:0); + + { + float *windowLW=vwin[winno[lW]]; + float *windowNW=vwin[winno[nW]]; + + long n=blocksizes[W]; + long ln=blocksizes[lW]; + long rn=blocksizes[nW]; + + long leftbegin=n/4-ln/4; + long leftend=leftbegin+ln/2; + + long rightbegin=n/2+n/4-rn/4; + long rightend=rightbegin+rn/2; + + int i,p; + + for(i=0;i. + +=========================================================================== +*/ + +#ifndef __SND_LOCAL_H__ +#define __SND_LOCAL_H__ + +// you need the OpenAL headers for build, even if AL is not enabled - http://www.openal.org/ +#ifdef _WIN32 +#include "../openal/include/al.h" +#include "../openal/include/alc.h" +#include "../openal/idal.h" +// broken OpenAL SDK ? +#define ID_ALCHAR (ALubyte *) +#elif defined( MACOS_X ) +#include +#include +#define ID_ALCHAR +#else +#include +#include +#define ID_ALCHAR +#endif +#include "../openal/include/efxlib.h" + +#ifdef Q4_RECONSTRUCTED_SOUND + +// Quake 4 keeps all sound worlds in a single sound-system object. These +// declarations follow the retail PDB layouts; the old Doom 3 world-based +// declarations remain below as a reconstruction seed. + +enum soundDemoCommand_t { + SCMD_STATE, + SCMD_PLACE_LISTENER, + SCMD_ALLOC_EMITTER, + SCMD_FREE, + SCMD_UPDATE, + SCMD_START, + SCMD_MODIFY, + SCMD_STOP, + SCMD_FADE +}; + +const int SOUND_MAX_CHANNELS = 8; +const int SOUND_DECODER_FREE_DELAY = 110250; +const int PRIMARYFREQ = 44100; +const float SND_EPSILON = 1.0f / 32768.0f; + +class idAudioHardware; +class idSoundCache; +class idSoundSample; +class idSampleDecoder; +class idSoundChannel; + +enum removeStatus_t { + REMOVE_STATUS_ALIVE = 0, + REMOVE_STATUS_WAITSAMPLEFINISHED, + REMOVE_STATUS_SAMPLEFINISHED +}; + +class idSoundFade { +public: + void Clear(); + float FadeDbAt44kHz( int current44kHz ); + + int fadeStart44kHz; + int fadeEnd44kHz; + float fadeStartVolume; + float fadeEndVolume; +}; + +class idSoundChannel { +public: + idSoundChannel(); + ~idSoundChannel(); + void Clear(); + void Stop(); + void ALStop(); + int GatherChannelSamples( int sampleOffset44k, int sampleCount44k, float *dest ) const; + + bool triggerState; + int trigger44kHzTime; + soundShaderParms_t parms; + idSoundSample *leadinSample; + int choice; + int triggerChannel; + const idSoundShader *soundShader; + idSampleDecoder *decoder; + float diversity; + float lastVolume; + float lastV[6]; + idSoundFade channelFade; + bool triggered; + ALuint openalSource; + ALuint openalStreamingOffset; + ALuint openalStreamingBuffer[3]; + int lastShift44kHzTime; + float lastFrequencyShift; + float elapsedShift44kHzTime; +}; + +class idSoundSample : public rvCommonSample { +public: + idSoundSample(); + virtual ~idSoundSample(); + + virtual const byte *GetSampleData() const; + virtual int GetNumChannels() const; + virtual int GetNumSamples() const; + virtual int GetSampleRate() const; + virtual int GetMemoryUsed() const; + virtual int GetDurationMS() const; + virtual float GetDuration() const; + virtual void Load( int langIndex = -1 ); + virtual void PurgeSoundSample(); + virtual bool IsOgg() const; + virtual void Expand( bool force ); + + int LengthIn44kHzSamples() const; + void MakeDefault(); + void CheckForDownSample(); + void Reload( bool force ); + unsigned int GetNewTimeStamp() const; + bool FetchFromCache( int offset, const byte **output, int *position, int *size, bool allowIO ); + + int nChannels; + int nSamplesPerSec; + int objectSize; + int objectMemSize; + byte *nonCacheData; + ALuint openalBuffer; + bool hardwareBuffer; + int wFormatTag; + int wBitsPerSample; + int nAvgBytesPerSec; + bool isOgg; +}; + +class idSoundCache { +public: + idSoundCache(); + ~idSoundCache(); + idSoundSample *FindSample( const idStr &filename ); + const idSoundSample *GetObjectA( int index ) const; + void ReloadSounds( bool force ); + void PurgeSounds( bool force ); + void BeginLevelLoad( const char *mapName ); + void EndLevelLoad( const char *mapName ); + void PrintMemInfo( MemInfo *mi ); + size_t ListSoundSummary(); + + bool insideLevelLoad; + idList listCache; +}; + +class idAudioSource { +public: + ALuint handle; + int startTime; + idSoundChannel *chan; + bool inUse; + bool looping; + bool stereo; +}; + +class s_stats { +public: + s_stats(); + int rinuse; + int runs; + int timeinprocess; + int missedWindow; + int missedUpdateWindow; + int activeSounds; + int numPortalChecks; +}; + +class idSoundEmitterLocal : public idSoundEmitter { +public: + idSoundEmitterLocal(); + virtual ~idSoundEmitterLocal(); + + virtual void UpdateEmitter( const idVec3 &origin, const idVec3 &velocity, int listenerId, const soundShaderParms_t *parms ); + virtual int StartSound( const idSoundShader *shader, int channel, float diversity, int shaderFlags ); + virtual void ModifySound( int channel, const soundShaderParms_t *parms ); + virtual void StopSound( int channel ); + virtual void FadeSound( int channel, float to, float over ); + virtual bool CurrentlyPlaying( int channel = SCHANNEL_ANY ) const; + virtual float CurrentAmplitude( int channelFlags = -1, bool factorDistance = false ); + virtual bool AttachedToWorld( int id ) const; + virtual int Handle() const; + + void Clear(); + void ClearPortalArea(); + void OverrideParms( const soundShaderParms_t *base, const soundShaderParms_t *over, soundShaderParms_t *out ); + void CheckForCompletion( int current44kHzTime ); + void Spatialize( idVec3 listenerPos, int listenerArea, idRenderWorld *rw ); + + int worldId; + int index; + removeStatus_t removeStatus; + idVec3 origin; + int listenerId; + soundShaderParms_t parms; + float maxDistance; + int lastValidPortalArea; + bool playing; + bool hasShakes; + idVec3 spatializedOrigin; + float realDistance; + float distance; + idSoundChannel channels[SOUND_MAX_CHANNELS]; + int numPortalChecks; + int ampTime; + float amplitude; +}; + +class idSoundSystemLocal : public idSoundSystem { +public: + idSoundSystemLocal(); + virtual ~idSoundSystemLocal(); + + virtual void Init(); + virtual void Shutdown(); + virtual void ClearBuffer(); + virtual bool InitHW(); + virtual void InitVoiceComms(); + virtual bool ShutdownHW(); + virtual void ShutdownVoiceComms(); + virtual void Frame(); + virtual void ForegroundUpdate(); + virtual int AsyncUpdate( int time ); + virtual int AsyncMix( int soundTime, float *mixBuffer ); + virtual int AsyncUpdateWrite( int time ); + virtual void SetMute( bool mute ); + virtual cinData_t ImageForTime( int milliseconds, bool waveform ); + virtual int GetSoundDecoderInfo( int index, soundDecoderInfo_t &decoderInfo ); + virtual void BeginLevelLoad( const char *mapName ); + virtual void EndLevelLoad( const char *mapName ); + virtual void CleanCache(); + virtual void PrintMemInfo( MemInfo *mi ); + virtual int IsEAXAvailable(); + virtual const char *GetDeviceName( int index ); + virtual const char *GetDefaultDeviceName(); + virtual void SetRenderWorld( idRenderWorld *rw ); + virtual void StopAllSounds( int worldId ); + virtual void DisableAllSounds(); + virtual int AllocSoundEmitter( int worldId ); + virtual void FreeSoundEmitter( int worldId, int handle, bool immediate ); + virtual idSoundEmitter *EmitterForIndex( int worldId, int index ); + virtual int GetNumEmitters() const; + virtual float CurrentShakeAmplitudeForPosition( int worldId, int time, const idVec3 &listenerPosition ); + virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, int listenerId, int gameTime, const idStr &areaName ); + virtual void ResetListener(); + virtual void FadeSoundClasses( int worldId, int soundClass, float to, float over ); + virtual void PlayShaderDirectly( int worldId, const char *name, int channel = -1 ); + virtual void StartWritingDemo( int worldId, idDemoFile *demo ); + virtual void StopWritingDemo( int worldId ); + virtual void ProcessDemoCommand( int worldId, idDemoFile *demo ); + virtual int GetHardwareTime() const; + virtual int SetActiveSoundWorld( bool on ); + virtual int GetActiveSoundWorld(); + virtual void AVIOpen( int worldId, const char *path, const char *name ); + virtual void AVIClose( int worldId ); + virtual void WriteToSaveGame( int worldId, idFile *savefile ); + virtual void ReadFromSaveGame( int worldId, idFile *savefile ); + virtual void ListActiveSounds( int worldId ); + virtual size_t ListSoundSummary(); + virtual bool HasCache() const; + virtual idSoundSample *FindSample( const idStr &filename ); + virtual int SamplesToMilliseconds( int samples ) const; + virtual void *AllocSoundSample( int size ); + virtual void FreeSoundSample( const byte *address ); + virtual bool GetInsideLevelLoad() const; + virtual bool ValidateSoundShader( idSoundShader *shader ); + virtual bool EnableRecording( bool enable, bool test, float &micLevel ); + virtual int GetVoiceData( byte *buffer, int maxSize ); + virtual void PlayVoiceData( int clientNum, const byte *buffer, int bytes ); + virtual void BufferVoiceData(); + virtual void MixVoiceData( float *finalMixBuffer, int numSpeakers, int newTime ); + virtual int GetCommClientNum( int channel ) const; + virtual int GetNumVoiceChannels() const; + virtual const char *GetReverbName( int reverb ); + virtual int GetNumAreas(); + virtual int GetReverb( int area ); + virtual bool SetReverb( int area, const char *reverbName, const char *fileName ); + virtual void EndCinematic(); + + int GetCurrent44kHzTime() const; + int MillisecondsToSamples( int ms ) const; + ALuint AllocOpenALSource( idSoundChannel *chan, bool looping, bool stereo ); + void FreeOpenALSource( ALuint handle ); + idSoundEmitterLocal *AllocLocalSoundEmitter( int worldId, int index ); + void ClearAllSoundEmitters(); + void OffsetSoundTime( int offset44kHz ); + void AVIUpdate(); + int GetCacheMemoryUsage(); + void ListSoundDecoders( const idCmdArgs &args ); + void LoadReverbData( const char *mapName ); + void HandleVoiceComms(); + void ActivateFilter( int filter ); + + static idCVar s_noSound; + static idCVar s_maxSoundsPerShader; + static idCVar s_volume; + static idCVar s_useOpenAL; + static idCVar s_deviceName; + + idRenderWorld *rw; + idDemoFile *writeDemo; + idMat3 listenerAxis; + idVec3 listenerPos; + int listenerPrivateId; + int lastListenerArea; + int listenerArea; + idStr listenerAreaName; + int listenerEnvironmentID; + int gameMsec; + int game44kHz; + int lastAVI44kHz; + idList emitters; + idSoundFade soundClassFade[SOUND_MAX_CLASSES]; + idFile *fpa[6]; + idStr aviDemoPath; + idStr aviDemoName; + idSoundEmitterLocal *localSound[SOUNDWORLD_MAX]; + int activeSoundWorld; + idAudioHardware *snd_audio_hw; + idSoundCache *soundCache; + int olddwCurrentWritePos; + int buffers; + int CurrentSoundTime; + unsigned int nextWriteBlock; + float realAccum[6 * MIXBUFFER_SAMPLES + 16]; + float *finalMixBuffer; + bool isInitialized; + bool muted; + bool shutdown; + bool voiceCommInit; + s_stats soundStats; + int meterTops[256]; + int meterTopsTime[256]; + dword *graph; + idList areaReverbs; + ALCdevice *openalDevice; + ALCcontext *openalContext; + ALsizei openalSourceCount; + idAudioSource openalSources[256]; + EAXSet alEAXSet; + EAXGet alEAXGet; + EAXSetBufferMode alEAXSetBufferMode; + EAXGetBufferMode alEAXGetBufferMode; + idEFXFile EFXDatabase; + bool efxloaded; + bool efxSlotAvailable[4]; + int efxSlotToArea[4]; + GUID efxSlotId[4]; + int eaxVersion; +}; + +class rvSoundShaderEditLocal : public rvSoundShaderEdit { +public: + virtual const char *GetSampleName( const idSoundShader *sound, int index ) const; + virtual int GetSamplesPerSec( const idSoundShader *sound, int index ) const; + virtual int GetNumChannels( const idSoundShader *sound, int index ) const; + virtual int GetNumSamples( const idSoundShader *sound, int index ) const { return sound != NULL ? sound->GetNumSamples( index ) : 0; } + virtual int GetMemorySize( const idSoundShader *sound, int index ) const; + virtual const byte *GetNonCacheData( const idSoundShader *sound, int index ) const; + virtual void LoadSampleData( idSoundShader *sound, int langIndex = -1 ); + virtual void ExpandSmallOggs( idSoundShader *sound, bool force ); + virtual const char *GetShakeData( idSoundShader *sound, int index ); + virtual void SetShakeData( idSoundShader *sound, int index, const char *ampData ); + virtual void Purge( idSoundShader *sound, bool freeBaseBlocks ); +}; + +extern idSoundSystemLocal soundSystemLocal; + +static_assert( sizeof( idSoundFade ) == 16, "Quake 4 PDB: idSoundFade" ); +static_assert( sizeof( idSoundChannel ) == 152, "Quake 4 PDB: idSoundChannel" ); +static_assert( sizeof( idSoundSample ) == 88, "Quake 4 PDB: idSoundSample" ); +static_assert( sizeof( idSoundCache ) == 20, "Quake 4 PDB: idSoundCache" ); +static_assert( sizeof( idSoundEmitterLocal ) == 1332, "Quake 4 PDB: idSoundEmitterLocal" ); +static_assert( sizeof( idSoundSystemLocal ) == 105036, "Quake 4 PDB: idSoundSystemLocal" ); + +#else + +// demo sound commands +typedef enum { + SCMD_STATE, // followed by a load game state + SCMD_PLACE_LISTENER, + SCMD_ALLOC_EMITTER, + + SCMD_FREE, + SCMD_UPDATE, + SCMD_START, + SCMD_MODIFY, + SCMD_STOP, + SCMD_FADE +} soundDemoCommand_t; + +const int SOUND_MAX_CHANNELS = 8; +const int SOUND_DECODER_FREE_DELAY = 1000 * MIXBUFFER_SAMPLES / USERCMD_MSEC; // four seconds + +const int PRIMARYFREQ = 44100; // samples per second +const float SND_EPSILON = 1.0f / 32768.0f; // if volume is below this, it will always multiply to zero + +const int ROOM_SLICES_IN_BUFFER = 10; + +class idAudioHardware; +class idAudioBuffer; +class idWaveFile; +class idSoundCache; +class idSoundSample; +class idSampleDecoder; +class idSoundWorldLocal; + + +/* +=================================================================================== + + General extended waveform format structure. + Use this for all NON PCM formats. + +=================================================================================== +*/ + +#ifdef WIN32 +#pragma pack(1) +#endif +#ifdef __MWERKS__ +#pragma pack (push, 1) +#endif +struct waveformatex_s { + word wFormatTag; /* format type */ + word nChannels; /* number of channels (i.e. mono, stereo...) */ + dword nSamplesPerSec; /* sample rate */ + dword nAvgBytesPerSec; /* for buffer estimation */ + word nBlockAlign; /* block size of data */ + word wBitsPerSample; /* Number of bits per sample of mono data */ + word cbSize; /* The count in bytes of the size of + extra information (after cbSize) */ +} PACKED; + +typedef waveformatex_s waveformatex_t; + +/* OLD general waveform format structure (information common to all formats) */ +struct waveformat_s { + word wFormatTag; /* format type */ + word nChannels; /* number of channels (i.e. mono, stereo, etc.) */ + dword nSamplesPerSec; /* sample rate */ + dword nAvgBytesPerSec; /* for buffer estimation */ + word nBlockAlign; /* block size of data */ +} PACKED; + +typedef waveformat_s waveformat_t; + +/* flags for wFormatTag field of WAVEFORMAT */ +enum { + WAVE_FORMAT_TAG_PCM = 1, + WAVE_FORMAT_TAG_OGG = 2 +}; + +/* specific waveform format structure for PCM data */ +struct pcmwaveformat_s { + waveformat_t wf; + word wBitsPerSample; +} PACKED; + +typedef pcmwaveformat_s pcmwaveformat_t; + +#ifndef mmioFOURCC +#define mmioFOURCC( ch0, ch1, ch2, ch3 ) \ + ( (dword)(byte)(ch0) | ( (dword)(byte)(ch1) << 8 ) | \ + ( (dword)(byte)(ch2) << 16 ) | ( (dword)(byte)(ch3) << 24 ) ) +#endif + +#define fourcc_riff mmioFOURCC('R', 'I', 'F', 'F') + +struct waveformatextensible_s { + waveformatex_t Format; + union { + word wValidBitsPerSample; /* bits of precision */ + word wSamplesPerBlock; /* valid if wBitsPerSample==0*/ + word wReserved; /* If neither applies, set to zero*/ + } Samples; + dword dwChannelMask; /* which channels are */ + /* present in stream */ + int SubFormat; +} PACKED; + +typedef waveformatextensible_s waveformatextensible_t; + +typedef dword fourcc; + +/* RIFF chunk information data structure */ +struct mminfo_s { + fourcc ckid; /* chunk ID */ + dword cksize; /* chunk size */ + fourcc fccType; /* form type or list type */ + dword dwDataOffset; /* offset of data portion of chunk */ +} PACKED; + +typedef mminfo_s mminfo_t; + +#ifdef WIN32 +#pragma pack() +#endif +#ifdef __MWERKS__ +#pragma pack (pop) +#endif + +/* +=================================================================================== + +idWaveFile + +=================================================================================== +*/ + +class idWaveFile { +public: + idWaveFile( void ); + ~idWaveFile( void ); + + int Open( const char* strFileName, waveformatex_t* pwfx = NULL ); + int OpenFromMemory( short* pbData, int ulDataSize, waveformatextensible_t* pwfx ); + int Read( byte* pBuffer, int dwSizeToRead, int *pdwSizeRead ); + int Seek( int offset ); + int Close( void ); + int ResetFile( void ); + + int GetOutputSize( void ) { return mdwSize; } + int GetMemorySize( void ) { return mMemSize; } + + waveformatextensible_t mpwfx; // Pointer to waveformatex structure + +private: + idFile * mhmmio; // I/O handle for the WAVE + mminfo_t mck; // Multimedia RIFF chunk + mminfo_t mckRiff; // used when opening a WAVE file + dword mdwSize; // size in samples + dword mMemSize; // size of the wave data in memory + dword mseekBase; + ID_TIME_T mfileTime; + + bool mbIsReadingFromMemory; + short * mpbData; + short * mpbDataCur; + dword mulDataSize; + + void * ogg; // only !NULL when !s_realTimeDecoding + bool isOgg; + +private: + int ReadMMIO( void ); + + int OpenOGG( const char* strFileName, waveformatex_t* pwfx = NULL ); + int ReadOGG( byte* pBuffer, int dwSizeToRead, int *pdwSizeRead ); + int CloseOGG( void ); +}; + + +/* +=================================================================================== + +idAudioHardware + +=================================================================================== +*/ + +class idAudioHardware { +public: + static idAudioHardware *Alloc(); + + virtual ~idAudioHardware(); + + virtual bool Initialize( ) = 0; + + virtual bool Lock( void **pDSLockedBuffer, ulong *dwDSLockedBufferSize ) = 0; + virtual bool Unlock( void *pDSLockedBuffer, dword dwDSLockedBufferSize ) = 0; + virtual bool GetCurrentPosition( ulong *pdwCurrentWriteCursor ) = 0; + + // try to write as many sound samples to the device as possible without blocking and prepare for a possible new mixing call + // returns wether there is *some* space for writing available + virtual bool Flush( void ) = 0; + + virtual void Write( bool flushing ) = 0; + + virtual int GetNumberOfSpeakers( void )= 0; + virtual int GetMixBufferSize( void ) = 0; + virtual short* GetMixBuffer( void ) = 0; +}; + + +/* +=================================================================================== + +Encapsulates functionality of a DirectSound buffer. + +=================================================================================== +*/ + +class idAudioBuffer { +public: + virtual int Play( dword dwPriority=0, dword dwFlags=0 ) = 0; + virtual int Stop( void ) = 0; + virtual int Reset( void ) = 0; + virtual bool IsSoundPlaying( void ) = 0; + virtual void SetVolume( float x ) = 0; +}; + + +/* +=================================================================================== + +idSoundEmitterLocal + +=================================================================================== +*/ + +typedef enum { + REMOVE_STATUS_INVALID = -1, + REMOVE_STATUS_ALIVE = 0, + REMOVE_STATUS_WAITSAMPLEFINISHED = 1, + REMOVE_STATUS_SAMPLEFINISHED = 2 +} removeStatus_t; + +class idSoundFade { +public: + int fadeStart44kHz; + int fadeEnd44kHz; + float fadeStartVolume; // in dB + float fadeEndVolume; // in dB + + void Clear(); + float FadeDbAt44kHz( int current44kHz ); +}; + +class SoundFX { +protected: + bool initialized; + + int channel; + int maxlen; + + float* buffer; + float continuitySamples[4]; + + float param; + +public: + SoundFX() { channel = 0; buffer = NULL; initialized = false; maxlen = 0; memset( continuitySamples, 0, sizeof( float ) * 4 ); }; + virtual ~SoundFX() { if ( buffer ) delete buffer; }; + + virtual void Initialize() { }; + virtual void ProcessSample( float* in, float* out ) = 0; + + void SetChannel( int chan ) { channel = chan; }; + int GetChannel() { return channel; }; + + void SetContinuitySamples( float in1, float in2, float out1, float out2 ) { continuitySamples[0] = in1; continuitySamples[1] = in2; continuitySamples[2] = out1; continuitySamples[3] = out2; }; // FIXME? + void GetContinuitySamples( float& in1, float& in2, float& out1, float& out2 ) { in1 = continuitySamples[0]; in2 = continuitySamples[1]; out1 = continuitySamples[2]; out2 = continuitySamples[3]; }; + + void SetParameter( float val ) { param = val; }; +}; + +class SoundFX_Lowpass : public SoundFX { +public: + virtual void ProcessSample( float* in, float* out ); +}; + +class SoundFX_LowpassFast : public SoundFX { + float freq; + float res; + float a1, a2, a3; + float b1, b2; + +public: + virtual void ProcessSample( float* in, float* out ); + void SetParms( float p1 = 0, float p2 = 0, float p3 = 0 ); +}; + +class SoundFX_Comb : public SoundFX { + int currentTime; + +public: + virtual void Initialize(); + virtual void ProcessSample( float* in, float* out ); +}; + +class FracTime { +public: + int time; + float frac; + + void Set( int val ) { time = val; frac = 0; }; + void Increment( float val ) { frac += val; while ( frac >= 1.f ) { time++; frac--; } }; +}; + +enum { + PLAYBACK_RESET, + PLAYBACK_ADVANCING +}; + +class idSoundChannel; + +class idSlowChannel { + bool active; + const idSoundChannel* chan; + + int playbackState; + int triggerOffset; + + FracTime newPosition; + int newSampleOffset; + + FracTime curPosition; + int curSampleOffset; + + SoundFX_LowpassFast lowpass; + + // functions + void GenerateSlowChannel( FracTime& playPos, int sampleCount44k, float* finalBuffer ); + + float GetSlowmoSpeed(); + +public: + + void AttachSoundChannel( const idSoundChannel *chan ); + void Reset(); + + void GatherChannelSamples( int sampleOffset44k, int sampleCount44k, float *dest ); + + bool IsActive() { return active; }; + FracTime GetCurrentPosition() { return curPosition; }; +}; + +class idSoundChannel { +public: + idSoundChannel( void ); + ~idSoundChannel( void ); + + void Clear( void ); + void Start( void ); + void Stop( void ); + void GatherChannelSamples( int sampleOffset44k, int sampleCount44k, float *dest ) const; + void ALStop( void ); // free OpenAL resources if any + + bool triggerState; + int trigger44kHzTime; // hardware time sample the channel started + int triggerGame44kHzTime; // game time sample time the channel started + soundShaderParms_t parms; // combines the shader parms and the per-channel overrides + idSoundSample * leadinSample; // if not looped, this is the only sample + s_channelType triggerChannel; + const idSoundShader *soundShader; + idSampleDecoder * decoder; + float diversity; + float lastVolume; // last calculated volume based on distance + float lastV[6]; // last calculated volume for each speaker, so we can smoothly fade + idSoundFade channelFade; + bool triggered; + ALuint openalSource; + ALuint openalStreamingOffset; + ALuint openalStreamingBuffer[3]; + ALuint lastopenalStreamingBuffer[3]; + + bool disallowSlow; + +}; + +class idSoundEmitterLocal : public idSoundEmitter { +public: + + idSoundEmitterLocal( void ); + virtual ~idSoundEmitterLocal( void ); + + //---------------------------------------------- + + // the "time" parameters should be game time in msec, which is used to make queries + // return deterministic values regardless of async buffer scheduling + + // a non-immediate free will let all currently playing sounds complete + virtual void Free( bool immediate ); + + // the parms specified will be the default overrides for all sounds started on this emitter. + // NULL is acceptable for parms + virtual void UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms ); + + // returns the length of the started sound in msec + virtual int StartSound( const idSoundShader *shader, const s_channelType channel, float diversity = 0, int shaderFlags = 0, bool allowSlow = true /* D3XP */ ); + + // can pass SCHANNEL_ANY + virtual void ModifySound( const s_channelType channel, const soundShaderParms_t *parms ); + virtual void StopSound( const s_channelType channel ); + virtual void FadeSound( const s_channelType channel, float to, float over ); + + virtual bool CurrentlyPlaying( void ) const; + + // can pass SCHANNEL_ANY + virtual float CurrentAmplitude( void ); + + // used for save games + virtual int Index( void ) const; + + //---------------------------------------------- + + void Clear( void ); + + void OverrideParms( const soundShaderParms_t *base, const soundShaderParms_t *over, soundShaderParms_t *out ); + void CheckForCompletion( int current44kHzTime ); + void Spatialize( idVec3 listenerPos, int listenerArea, idRenderWorld *rw ); + + idSoundWorldLocal * soundWorld; // the world that holds this emitter + + int index; // in world emitter list + removeStatus_t removeStatus; + + idVec3 origin; + int listenerId; + soundShaderParms_t parms; // default overrides for all channels + + + // the following are calculated in UpdateEmitter, and don't need to be archived + float maxDistance; // greatest of all playing channel distances + int lastValidPortalArea; // so an emitter that slides out of the world continues playing + bool playing; // if false, no channel is active + bool hasShakes; + idVec3 spatializedOrigin; // the virtual sound origin, either the real sound origin, + // or a point through a portal chain + float realDistance; // in meters + float distance; // in meters, this may be the straight-line distance, or + // it may go through a chain of portals. If there + // is not an open-portal path, distance will be > maxDistance + + // a single soundEmitter can have many channels playing from the same point + idSoundChannel channels[SOUND_MAX_CHANNELS]; + + idSlowChannel slowChannels[SOUND_MAX_CHANNELS]; + + idSlowChannel GetSlowChannel( const idSoundChannel *chan ); + void SetSlowChannel( const idSoundChannel *chan, idSlowChannel slow ); + void ResetSlowChannel( const idSoundChannel *chan ); + + // this is just used for feedback to the game or rendering system: + // flashing lights and screen shakes. Because the material expression + // evaluation doesn't do common subexpression removal, we cache the + // last generated value + int ampTime; + float amplitude; +}; + + +/* +=================================================================================== + +idSoundWorldLocal + +=================================================================================== +*/ + +class s_stats { +public: + s_stats( void ) { + rinuse = 0; + runs = 1; + timeinprocess = 0; + missedWindow = 0; + missedUpdateWindow = 0; + activeSounds = 0; + } + int rinuse; + int runs; + int timeinprocess; + int missedWindow; + int missedUpdateWindow; + int activeSounds; +}; + +typedef struct soundPortalTrace_s { + int portalArea; + const struct soundPortalTrace_s *prevStack; +} soundPortalTrace_t; + +class idSoundWorldLocal : public idSoundWorld { +public: + virtual ~idSoundWorldLocal( void ); + + // call at each map start + virtual void ClearAllSoundEmitters( void ); + virtual void StopAllSounds( void ); + + // get a new emitter that can play sounds in this world + virtual idSoundEmitter *AllocSoundEmitter( void ); + + // for load games + virtual idSoundEmitter *EmitterForIndex( int index ); + + // query data from all emitters in the world + virtual float CurrentShakeAmplitudeForPosition( const int time, const idVec3 &listererPosition ); + + // where is the camera/microphone + // listenerId allows listener-private sounds to be added + virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, const int listenerId, const int gameTime, const idStr& areaName ); + + // fade all sounds in the world with a given shader soundClass + // to is in Db (sigh), over is in seconds + virtual void FadeSoundClasses( const int soundClass, const float to, const float over ); + + // dumps the current state and begins archiving commands + virtual void StartWritingDemo( idDemoFile *demo ); + virtual void StopWritingDemo( void ); + + // read a sound command from a demo file + virtual void ProcessDemoCommand( idDemoFile *readDemo ); + + // background music + virtual void PlayShaderDirectly( const char *name, int channel = -1 ); + + // pause and unpause the sound world + virtual void Pause( void ); + virtual void UnPause( void ); + virtual bool IsPaused( void ); + + // avidump + virtual void AVIOpen( const char *path, const char *name ); + virtual void AVIClose( void ); + + // SaveGame Support + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual void ReadFromSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ); + virtual void ReadFromSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ); + virtual void WriteToSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ); + virtual void WriteToSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ); + + virtual void SetSlowmo( bool active ); + virtual void SetSlowmoSpeed( float speed ); + virtual void SetEnviroSuit( bool active ); + + //======================================= + + idSoundWorldLocal( void ); + + void Shutdown( void ); + void Init( idRenderWorld *rw ); + void ClearBuffer( void ); + + // update + void ForegroundUpdate( int currentTime ); + void OffsetSoundTime( int offset44kHz ); + + idSoundEmitterLocal * AllocLocalSoundEmitter(); + void CalcEars( int numSpeakers, idVec3 realOrigin, idVec3 listenerPos, idMat3 listenerAxis, float ears[6], float spatialize ); + void AddChannelContribution( idSoundEmitterLocal *sound, idSoundChannel *chan, + int current44kHz, int numSpeakers, float *finalMixBuffer ); + void MixLoop( int current44kHz, int numSpeakers, float *finalMixBuffer ); + void AVIUpdate( void ); + void ResolveOrigin( const int stackDepth, const soundPortalTrace_t *prevStack, const int soundArea, const float dist, const idVec3& soundOrigin, idSoundEmitterLocal *def ); + float FindAmplitude( idSoundEmitterLocal *sound, const int localTime, const idVec3 *listenerPosition, const s_channelType channel, bool shakesOnly ); + + //============================================ + + idRenderWorld * rw; // for portals and debug drawing + idDemoFile * writeDemo; // if not NULL, archive commands here + + idMat3 listenerAxis; + idVec3 listenerPos; // position in meters + int listenerPrivateId; + idVec3 listenerQU; // position in "quake units" + int listenerArea; + idStr listenerAreaName; + int listenerEnvironmentID; + + int gameMsec; + int game44kHz; + int pause44kHz; + int lastAVI44kHz; // determine when we need to mix and write another block + + idListemitters; + + idSoundFade soundClassFade[SOUND_MAX_CLASSES]; // for global sound fading + + // avi stuff + idFile * fpa[6]; + idStr aviDemoPath; + idStr aviDemoName; + + idSoundEmitterLocal * localSound; // just for playShaderDirectly() + + bool slowmoActive; + float slowmoSpeed; + bool enviroSuitActive; +}; + +/* +=================================================================================== + +idSoundSystemLocal + +=================================================================================== +*/ + +typedef struct { + ALuint handle; + int startTime; + idSoundChannel *chan; + bool inUse; + bool looping; + bool stereo; +} openalSource_t; + +class idSoundSystemLocal : public idSoundSystem { +public: + idSoundSystemLocal( ) { + isInitialized = false; + } + + // all non-hardware initialization + virtual void Init( void ); + + // shutdown routine + virtual void Shutdown( void ); + virtual void ClearBuffer( void ); + + // sound is attached to the window, and must be recreated when the window is changed + virtual bool ShutdownHW( void ); + virtual bool InitHW( void ); + + // async loop, called at 60Hz + virtual int AsyncUpdate( int time ); + // async loop, when the sound driver uses a write strategy + virtual int AsyncUpdateWrite( int time ); + // direct mixing called from the sound driver thread for OSes that support it + virtual int AsyncMix( int soundTime, float *mixBuffer ); + + virtual void SetMute( bool mute ); + + virtual cinData_t ImageForTime( const int milliseconds, const bool waveform ); + + int GetSoundDecoderInfo( int index, soundDecoderInfo_t &decoderInfo ); + + // if rw == NULL, no portal occlusion or rendered debugging is available + virtual idSoundWorld *AllocSoundWorld( idRenderWorld *rw ); + + // specifying NULL will cause silence to be played + virtual void SetPlayingSoundWorld( idSoundWorld *soundWorld ); + + // some tools, like the sound dialog, may be used in both the game and the editor + // This can return NULL, so check! + virtual idSoundWorld *GetPlayingSoundWorld( void ); + + virtual void BeginLevelLoad( void ); + virtual void EndLevelLoad( const char *mapString ); + + virtual void PrintMemInfo( MemInfo_t *mi ); + + virtual int IsEAXAvailable( void ); + + //------------------------- + + int GetCurrent44kHzTime( void ) const; + float dB2Scale( const float val ) const; + int SamplesToMilliseconds( int samples ) const; + int MillisecondsToSamples( int ms ) const; + + void DoEnviroSuit( float* samples, int numSamples, int numSpeakers ); + + ALuint AllocOpenALSource( idSoundChannel *chan, bool looping, bool stereo ); + void FreeOpenALSource( ALuint handle ); + + idAudioHardware * snd_audio_hw; + idSoundCache * soundCache; + + idSoundWorldLocal * currentSoundWorld; // the one to mix each async tic + + int olddwCurrentWritePos; // statistics + int buffers; // statistics + int CurrentSoundTime; // set by the async thread and only used by the main thread + + unsigned int nextWriteBlock; + + float realAccum[6*MIXBUFFER_SAMPLES+16]; + float * finalMixBuffer; // points inside realAccum at a 16 byte aligned boundary + + bool isInitialized; + bool muted; + bool shutdown; + + s_stats soundStats; // NOTE: updated throughout the code, not displayed anywhere + + int meterTops[256]; + int meterTopsTime[256]; + + dword * graph; + + float volumesDB[1200]; // dB to float volume conversion + + idList fxList; + + ALCdevice *openalDevice; + ALCcontext *openalContext; + ALsizei openalSourceCount; + openalSource_t openalSources[256]; + EAXSet alEAXSet; + EAXGet alEAXGet; + EAXSetBufferMode alEAXSetBufferMode; + EAXGetBufferMode alEAXGetBufferMode; + idEFXFile EFXDatabase; + bool efxloaded; + // latches + static bool useOpenAL; + static bool useEAXReverb; + // mark available during initialization, or through an explicit test + static int EAXAvailable; + + + static idCVar s_noSound; + static idCVar s_quadraticFalloff; + static idCVar s_drawSounds; + static idCVar s_minVolume6; + static idCVar s_dotbias6; + static idCVar s_minVolume2; + static idCVar s_dotbias2; + static idCVar s_spatializationDecay; + static idCVar s_showStartSound; + static idCVar s_maxSoundsPerShader; + static idCVar s_reverse; + static idCVar s_showLevelMeter; + static idCVar s_meterTopTime; + static idCVar s_volume; + static idCVar s_constantAmplitude; + static idCVar s_playDefaultSound; + static idCVar s_useOcclusion; + static idCVar s_subFraction; + static idCVar s_globalFraction; + static idCVar s_doorDistanceAdd; + static idCVar s_singleEmitter; + static idCVar s_numberOfSpeakers; + static idCVar s_force22kHz; + static idCVar s_clipVolumes; + static idCVar s_realTimeDecoding; + static idCVar s_libOpenAL; + static idCVar s_useOpenAL; + static idCVar s_useEAXReverb; + static idCVar s_muteEAXReverb; + static idCVar s_decompressionLimit; + + static idCVar s_slowAttenuate; + + static idCVar s_enviroSuitCutoffFreq; + static idCVar s_enviroSuitCutoffQ; + static idCVar s_enviroSuitSkipLowpass; + static idCVar s_enviroSuitSkipReverb; + + static idCVar s_reverbTime; + static idCVar s_reverbFeedback; + static idCVar s_enviroSuitVolumeScale; + static idCVar s_skipHelltimeFX; +}; + +extern idSoundSystemLocal soundSystemLocal; + + +/* +=================================================================================== + + This class holds the actual wavefile bitmap, size, and info. + +=================================================================================== +*/ + +const int SCACHE_SIZE = MIXBUFFER_SAMPLES*20; // 1/2 of a second (aroundabout) + +class idSoundSample { +public: + idSoundSample(); + ~idSoundSample(); + + idStr name; // name of the sample file + ID_TIME_T timestamp; // the most recent of all images used in creation, for reloadImages command + + waveformatex_t objectInfo; // what are we caching + int objectSize; // size of waveform in samples, excludes the header + int objectMemSize; // object size in memory + byte * nonCacheData; // if it's not cached + byte * amplitudeData; // precomputed min,max amplitude pairs + ALuint openalBuffer; // openal buffer + bool hardwareBuffer; + bool defaultSound; + bool onDemand; + bool purged; + bool levelLoadReferenced; // so we can tell which samples aren't needed any more + + int LengthIn44kHzSamples() const; + ID_TIME_T GetNewTimeStamp( void ) const; + void MakeDefault(); // turns it into a beep + void Load(); // loads the current sound based on name + void Reload( bool force ); // reloads if timestamp has changed, or always if force + void PurgeSoundSample(); // frees all data + void CheckForDownSample(); // down sample if required + bool FetchFromCache( int offset, const byte **output, int *position, int *size, const bool allowIO ); +}; + + +/* +=================================================================================== + + Sound sample decoder. + +=================================================================================== +*/ + +class idSampleDecoder { +public: + static void Init( void ); + static void Shutdown( void ); + static idSampleDecoder *Alloc( void ); + static void Free( idSampleDecoder *decoder ); + static int GetNumUsedBlocks( void ); + static int GetUsedBlockMemory( void ); + + virtual ~idSampleDecoder( void ) {} + virtual void Decode( idSoundSample *sample, int sampleOffset44k, int sampleCount44k, float *dest ) = 0; + virtual void ClearDecoder( void ) = 0; + virtual idSoundSample * GetSample( void ) const = 0; + virtual int GetLastDecodeTime( void ) const = 0; +}; + + +/* +=================================================================================== + + The actual sound cache. + +=================================================================================== +*/ + +class idSoundCache { +public: + idSoundCache(); + ~idSoundCache(); + + idSoundSample * FindSound( const idStr &fname, bool loadOnDemandOnly ); + + const int GetNumObjects( void ) { return listCache.Num(); } + const idSoundSample * GetObject( const int index ) const; + + void ReloadSounds( bool force ); + + void BeginLevelLoad(); + void EndLevelLoad(); + + void PrintMemInfo( MemInfo_t *mi ); + +private: + bool insideLevelLoad; + idList listCache; +}; + +#endif // Q4_RECONSTRUCTED_SOUND + +#endif /* !__SND_LOCAL_H__ */ diff --git a/src/sound/snd_shader.cpp b/src/sound/snd_shader.cpp new file mode 100644 index 0000000..b731145 --- /dev/null +++ b/src/sound/snd_shader.cpp @@ -0,0 +1,708 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "snd_local.h" + +#ifdef Q4_RECONSTRUCTED_SOUND + +static rvCommonSample *SoundShaderSampleForIndex( const idSoundShader *shader, int index ) { + if ( shader == NULL || index < 0 ) return NULL; + if ( index < shader->GetNumEntries() ) return shader->GetEntry( index ); + return NULL; +} + +void idSoundShader::Init() { + memset( &parms, 0, sizeof( parms ) ); + parms.minDistance = 1.0f; + parms.maxDistance = 10.0f; + parms.volume = 1.0f; + parms.attenuatedVolume = 1.0f; + parms.frequencyShift = 1.0f; + parms.wetLevel = 0.0f; + parms.dryLevel = 1.0f; + altSound = NULL; + desc = ""; + errorDuringParse = false; + noShakes = false; + frequentlyUsed = false; + leadinVolume = 0.0f; + memset( leadins, 0, sizeof( leadins ) ); + numLeadins = 0; + memset( entries, 0, sizeof( entries ) ); + shakes.Clear(); + numEntries = 0; + minFrequencyShift = 1.0f; + maxFrequencyShift = 1.0f; + playCount = 0; +} + +size_t idSoundShader::Size() const { return sizeof( *this ); } + +void idSoundShader::FreeData() { + Purge( false ); + memset( leadins, 0, sizeof( leadins ) ); + memset( entries, 0, sizeof( entries ) ); + numLeadins = numEntries = 0; + shakes.Clear(); +} + +const char *idSoundShader::DefaultDefinition() const { return "{\n\t_default.wav\n}"; } + +bool idSoundShader::SetDefaultText() { + idStr wavName = GetName(); + wavName.DefaultFileExtension( ".wav" ); + SetText( va( "sound %s // IMPLICITLY GENERATED\n{\n\t%s\n}\n", GetName(), wavName.c_str() ) ); + return true; +} + +bool idSoundShader::Validate( const char *text, int textLength, idStr &report ) const { + report.Clear(); + if ( text == NULL || textLength <= 0 ) { report = "empty sound shader"; return false; } + if ( idStr::FindChar( text, '{', 0, textLength ) < 0 || idStr::FindChar( text, '}', 0, textLength ) < 0 ) { + report = "sound shader requires matching braces"; + return false; + } + return true; +} + +bool idSoundShader::Parse( const char *text, int textLength, bool noCaching ) { + idLexer src; + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + if ( !src.SkipUntilString( "{" ) ) return false; + FreeData(); + Init(); + if ( !ParseShader( src ) || errorDuringParse ) { + MakeDefault(); + return false; + } + if ( !noCaching ) LoadSampleData(); + return true; +} + +bool idSoundShader::ParseShader( idLexer &src ) { + idToken token; + int maxSamples = idSoundSystemLocal::s_maxSoundsPerShader.GetInteger(); + if ( maxSamples <= 0 || maxSamples > SOUND_MAX_LIST_WAVS ) maxSamples = SOUND_MAX_LIST_WAVS; + + while ( src.ReadToken( &token ) ) { + if ( token == "}" ) return true; + if ( !token.Icmp( "description" ) ) { if ( src.ReadTokenOnLine( &token ) ) desc = token; continue; } + if ( !token.Icmp( "mindistance" ) ) { parms.minDistance = src.ParseFloat(); continue; } + if ( !token.Icmp( "maxdistance" ) ) { parms.maxDistance = src.ParseFloat(); continue; } + if ( !token.Icmp( "volume" ) ) { parms.volume = src.ParseFloat(); continue; } + if ( !token.Icmp( "attenuatedVolume" ) ) { parms.attenuatedVolume = src.ParseFloat(); continue; } + if ( !token.Icmp( "leadinVolume" ) ) { leadinVolume = src.ParseFloat(); continue; } + if ( !token.Icmp( "soundClass" ) ) { parms.soundClass = idMath::ClampInt( 0, SOUND_MAX_CLASSES - 1, src.ParseInt() ); continue; } + if ( !token.Icmp( "frequencyShift" ) ) { parms.frequencyShift = src.ParseFloat(); continue; } + if ( !token.Icmp( "minFrequencyShift" ) ) { minFrequencyShift = src.ParseFloat(); continue; } + if ( !token.Icmp( "maxFrequencyShift" ) ) { maxFrequencyShift = src.ParseFloat(); continue; } + if ( !token.Icmp( "wetLevel" ) ) { parms.wetLevel = src.ParseFloat(); continue; } + if ( !token.Icmp( "dryLevel" ) ) { parms.dryLevel = src.ParseFloat(); continue; } + if ( !token.Icmp( "shakes" ) ) { + if ( src.ReadToken( &token ) ) { + if ( token.type == TT_NUMBER ) parms.shakes = token.GetFloatValue(); else { parms.shakes = 1.0f; src.UnreadToken( &token ); } + } + continue; + } + if ( !token.Icmp( "altSound" ) ) { if ( src.ReadToken( &token ) ) altSound = declManager->FindSound( token ); continue; } + if ( !token.Icmp( "frequentlyUsed" ) ) { frequentlyUsed = true; continue; } + if ( !token.Icmp( "no_shakes" ) ) { noShakes = true; continue; } + if ( !token.Icmp( "no_dups" ) ) { parms.soundShaderFlags |= SSF_NO_DUPS; continue; } + if ( !token.Icmp( "no_flicker" ) ) { parms.soundShaderFlags |= SSF_NO_FLICKER; continue; } + if ( !token.Icmp( "looping" ) ) { parms.soundShaderFlags |= SSF_LOOPING; continue; } + if ( !token.Icmp( "no_occlusion" ) ) { parms.soundShaderFlags |= SSF_NO_OCCLUSION; continue; } + if ( !token.Icmp( "private" ) ) { parms.soundShaderFlags |= SSF_PRIVATE_SOUND; continue; } + if ( !token.Icmp( "antiPrivate" ) ) { parms.soundShaderFlags |= SSF_ANTI_PRIVATE_SOUND; continue; } + if ( !token.Icmp( "playonce" ) ) { parms.soundShaderFlags |= SSF_PLAY_ONCE; continue; } + if ( !token.Icmp( "global" ) ) { parms.soundShaderFlags |= SSF_GLOBAL; continue; } + if ( !token.Icmp( "unclamped" ) ) { parms.soundShaderFlags |= SSF_UNCLAMPED; continue; } + if ( !token.Icmp( "omnidirectional" ) ) { parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; continue; } + if ( !token.Icmp( "doppler" ) ) { parms.soundShaderFlags |= SSF_USEDOPPLER; continue; } + if ( !token.Icmp( "no_randomstart" ) ) { parms.soundShaderFlags |= SSF_NO_RANDOMSTART; continue; } + if ( !token.Icmp( "vo" ) ) { parms.soundShaderFlags |= SSF_IS_VO; continue; } + if ( !token.Icmp( "center" ) ) { parms.soundShaderFlags |= SSF_CENTER; continue; } + if ( !token.Icmp( "ordered" ) || !token.Icmp( "plain" ) || !token.Icmp( "onDemand" ) ) continue; + if ( !token.Icmp( "reverb" ) ) { src.ReadTokenOnLine( &token ); continue; } + + if ( !token.Icmp( "leadin" ) ) { + if ( !src.ReadToken( &token ) ) return false; + if ( numLeadins < maxSamples ) leadins[numLeadins++] = soundSystemLocal.FindSample( token ); + continue; + } + if ( token.Find( ".wav", false ) >= 0 || token.Find( ".ogg", false ) >= 0 ) { + token.BackSlashesToSlashes(); + if ( numEntries < maxSamples ) entries[numEntries++] = soundSystemLocal.FindSample( token ); + continue; + } + src.Warning( "unknown token '%s' in sound shader '%s'", token.c_str(), GetName() ); + } + return false; +} + +void idSoundShader::SetReferencedThisLevel() { + for ( int i = 0; i < numLeadins; ++i ) if ( leadins[i] ) leadins[i]->SetReferencedThisLevel(); + for ( int i = 0; i < numEntries; ++i ) if ( entries[i] ) entries[i]->SetReferencedThisLevel(); +} + +float idSoundShader::GetTimeLength() const { + float length = 0.0f; + for ( int i = 0; i < numLeadins; ++i ) if ( leadins[i] ) length = Max( length, leadins[i]->GetDuration() ); + for ( int i = 0; i < numEntries; ++i ) if ( entries[i] ) length = Max( length, entries[i]->GetDuration() ); + return length; +} + +void idSoundShader::Purge( bool freeBaseBlocks ) { + for ( int i = 0; i < numLeadins; ++i ) if ( leadins[i] ) leadins[i]->PurgeSoundSample(); + for ( int i = 0; i < numEntries; ++i ) if ( entries[i] ) entries[i]->PurgeSoundSample(); +} +void idSoundShader::LoadSampleData( int langIndex ) { + for ( int i = 0; i < numLeadins; ++i ) if ( leadins[i] ) leadins[i]->Load( langIndex ); + for ( int i = 0; i < numEntries; ++i ) if ( entries[i] ) entries[i]->Load( langIndex ); +} +void idSoundShader::ExpandSmallOggs( bool force ) { + for ( int i = 0; i < numLeadins; ++i ) if ( leadins[i] ) leadins[i]->Expand( force ); + for ( int i = 0; i < numEntries; ++i ) if ( entries[i] ) entries[i]->Expand( force ); +} + +const char *idSoundShader::GetShakeData( int index ) const { return index >= 0 && index < shakes.Num() ? shakes[index].c_str() : ""; } +void idSoundShader::SetShakeData( int index, const char *ampData ) { if ( index < 0 ) return; shakes.SetNum( index + 1 ); shakes[index] = ampData != NULL ? ampData : ""; } + +const char *idSoundShader::GetSampleName( int index ) const { rvCommonSample *sample = SoundShaderSampleForIndex( this, index ); return sample ? sample->name.c_str() : ""; } +int idSoundShader::GetSamplesPerSec( int index ) const { rvCommonSample *sample = SoundShaderSampleForIndex( this, index ); return sample ? sample->GetSampleRate() : 0; } +int idSoundShader::GetNumChannels( int index ) const { rvCommonSample *sample = SoundShaderSampleForIndex( this, index ); return sample ? sample->GetNumChannels() : 0; } +int idSoundShader::GetNumSamples( int index ) const { rvCommonSample *sample = SoundShaderSampleForIndex( this, index ); return sample ? sample->GetNumSamples() : 0; } +int idSoundShader::GetMemorySize( int index ) const { rvCommonSample *sample = SoundShaderSampleForIndex( this, index ); return sample ? sample->GetMemoryUsed() : 0; } +const byte *idSoundShader::GetNonCacheData( int index ) const { rvCommonSample *sample = SoundShaderSampleForIndex( this, index ); return sample ? sample->GetSampleData() : NULL; } + +bool idSoundShader::HasDefaultSound() const { for ( int i = 0; i < numEntries; ++i ) if ( entries[i] && entries[i]->defaultSound ) return true; return false; } +int idSoundShader::GetNumSounds() const { return numLeadins + numEntries; } +const char *idSoundShader::GetSound( int index ) const { + if ( index < 0 ) return ""; + if ( index < numLeadins ) return leadins[index] ? leadins[index]->name.c_str() : ""; + index -= numLeadins; + return index < numEntries && entries[index] ? entries[index]->name.c_str() : ""; +} + +bool idSoundShader::RebuildTextSource() { return false; } +void idSoundShader::List() const { common->Printf( "%4i: %s (%d samples)\n", Index(), GetName(), GetNumSounds() ); } + +const char *rvSoundShaderEditLocal::GetSampleName( const idSoundShader *sound, int index ) const { return sound ? sound->GetSampleName( index ) : ""; } +int rvSoundShaderEditLocal::GetSamplesPerSec( const idSoundShader *sound, int index ) const { return sound ? sound->GetSamplesPerSec( index ) : 0; } +int rvSoundShaderEditLocal::GetNumChannels( const idSoundShader *sound, int index ) const { return sound ? sound->GetNumChannels( index ) : 0; } +int rvSoundShaderEditLocal::GetMemorySize( const idSoundShader *sound, int index ) const { return sound ? sound->GetMemorySize( index ) : 0; } +const byte *rvSoundShaderEditLocal::GetNonCacheData( const idSoundShader *sound, int index ) const { return sound ? sound->GetNonCacheData( index ) : NULL; } +void rvSoundShaderEditLocal::LoadSampleData( idSoundShader *sound, int langIndex ) { if ( sound ) sound->LoadSampleData( langIndex ); } +void rvSoundShaderEditLocal::Purge( idSoundShader *sound, bool freeBaseBlocks ) { if ( sound ) sound->Purge( freeBaseBlocks ); } +void rvSoundShaderEditLocal::ExpandSmallOggs( idSoundShader *sound, bool force ) { if ( sound ) sound->ExpandSmallOggs( force ); } +const char *rvSoundShaderEditLocal::GetShakeData( idSoundShader *sound, int index ) { return sound ? sound->GetShakeData( index ) : ""; } +void rvSoundShaderEditLocal::SetShakeData( idSoundShader *sound, int index, const char *ampData ) { if ( sound ) sound->SetShakeData( index, ampData ); } + +static rvSoundShaderEditLocal soundShaderEditLocal; +rvSoundShaderEdit *soundShaderEdit = &soundShaderEditLocal; + +#else + + +/* +=============== +idSoundShader::Init +=============== +*/ +void idSoundShader::Init( void ) { + desc = ""; + errorDuringParse = false; + onDemand = false; + numEntries = 0; + numLeadins = 0; + leadinVolume = 0; + altSound = NULL; +} + +/* +=============== +idSoundShader::idSoundShader +=============== +*/ +idSoundShader::idSoundShader( void ) { + Init(); +} + +/* +=============== +idSoundShader::~idSoundShader +=============== +*/ +idSoundShader::~idSoundShader( void ) { +} + +/* +================= +idSoundShader::Size +================= +*/ +size_t idSoundShader::Size( void ) const { + return sizeof( idSoundShader ); +} + +/* +=============== +idSoundShader::idSoundShader::FreeData +=============== +*/ +void idSoundShader::FreeData() { + numEntries = 0; + numLeadins = 0; +} + +/* +=================== +idSoundShader::SetDefaultText +=================== +*/ +bool idSoundShader::SetDefaultText( void ) { + idStr wavname; + + wavname = GetName(); + wavname.DefaultFileExtension( ".wav" ); // if the name has .ogg in it, that will stay + + // if there exists a wav file with the same name + if ( 1 ) { //fileSystem->ReadFile( wavname, NULL ) != -1 ) { + char generated[2048]; + idStr::snPrintf( generated, sizeof( generated ), + "sound %s // IMPLICITLY GENERATED\n" + "{\n" + "%s\n" + "}\n", GetName(), wavname.c_str() ); + SetText( generated ); + return true; + } else { + return false; + } +} + +/* +=================== +DefaultDefinition +=================== +*/ +const char *idSoundShader::DefaultDefinition() const { + return + "{\n" + "\t" "_default.wav\n" + "}"; +} + +/* +=============== +idSoundShader::Parse + + this is called by the declManager +=============== +*/ +bool idSoundShader::Parse( const char *text, const int textLength ) { + idLexer src; + + src.LoadMemory( text, textLength, GetFileName(), GetLineNum() ); + src.SetFlags( DECL_LEXER_FLAGS ); + src.SkipUntilString( "{" ); + + // deeper functions can set this, which will cause MakeDefault() to be called at the end + errorDuringParse = false; + + if ( !ParseShader( src ) || errorDuringParse ) { + MakeDefault(); + return false; + } + return true; +} + +/* +=============== +idSoundShader::ParseShader +=============== +*/ +bool idSoundShader::ParseShader( idLexer &src ) { + int i; + idToken token; + + parms.minDistance = 1; + parms.maxDistance = 10; + parms.volume = 1; + parms.shakes = 0; + parms.soundShaderFlags = 0; + parms.soundClass = 0; + + speakerMask = 0; + altSound = NULL; + + for( i = 0; i < SOUND_MAX_LIST_WAVS; i++ ) { + leadins[i] = NULL; + entries[i] = NULL; + } + numEntries = 0; + numLeadins = 0; + + int maxSamples = idSoundSystemLocal::s_maxSoundsPerShader.GetInteger(); + if ( com_makingBuild.GetBool() || maxSamples <= 0 || maxSamples > SOUND_MAX_LIST_WAVS ) { + maxSamples = SOUND_MAX_LIST_WAVS; + } + + while ( 1 ) { + if ( !src.ExpectAnyToken( &token ) ) { + return false; + } + // end of definition + else if ( token == "}" ) { + break; + } + // minimum number of sounds + else if ( !token.Icmp( "minSamples" ) ) { + maxSamples = idMath::ClampInt( src.ParseInt(), SOUND_MAX_LIST_WAVS, maxSamples ); + } + // description + else if ( !token.Icmp( "description" ) ) { + src.ReadTokenOnLine( &token ); + desc = token.c_str(); + } + // mindistance + else if ( !token.Icmp( "mindistance" ) ) { + parms.minDistance = src.ParseFloat(); + } + // maxdistance + else if ( !token.Icmp( "maxdistance" ) ) { + parms.maxDistance = src.ParseFloat(); + } + // shakes screen + else if ( !token.Icmp( "shakes" ) ) { + src.ExpectAnyToken( &token ); + if ( token.type == TT_NUMBER ) { + parms.shakes = token.GetFloatValue(); + } else { + src.UnreadToken( &token ); + parms.shakes = 1.0f; + } + } + // reverb + else if ( !token.Icmp( "reverb" ) ) { + int reg0 = src.ParseFloat(); + if ( !src.ExpectTokenString( "," ) ) { + src.FreeSource(); + return false; + } + int reg1 = src.ParseFloat(); + // no longer supported + } + // volume + else if ( !token.Icmp( "volume" ) ) { + parms.volume = src.ParseFloat(); + } + // leadinVolume is used to allow light breaking leadin sounds to be much louder than the broken loop + else if ( !token.Icmp( "leadinVolume" ) ) { + leadinVolume = src.ParseFloat(); + } + // speaker mask + else if ( !token.Icmp( "mask_center" ) ) { + speakerMask |= 1<= SOUND_MAX_CLASSES ) { + src.Warning( "SoundClass out of range" ); + return false; + } + } + // altSound + else if ( !token.Icmp( "altSound" ) ) { + if ( !src.ExpectAnyToken( &token ) ) { + return false; + } + altSound = declManager->FindSound( token.c_str() ); + } + // ordered + else if ( !token.Icmp( "ordered" ) ) { + // no longer supported + } + // no_dups + else if ( !token.Icmp( "no_dups" ) ) { + parms.soundShaderFlags |= SSF_NO_DUPS; + } + // no_flicker + else if ( !token.Icmp( "no_flicker" ) ) { + parms.soundShaderFlags |= SSF_NO_FLICKER; + } + // plain + else if ( !token.Icmp( "plain" ) ) { + // no longer supported + } + // looping + else if ( !token.Icmp( "looping" ) ) { + parms.soundShaderFlags |= SSF_LOOPING; + } + // no occlusion + else if ( !token.Icmp( "no_occlusion" ) ) { + parms.soundShaderFlags |= SSF_NO_OCCLUSION; + } + // private + else if ( !token.Icmp( "private" ) ) { + parms.soundShaderFlags |= SSF_PRIVATE_SOUND; + } + // antiPrivate + else if ( !token.Icmp( "antiPrivate" ) ) { + parms.soundShaderFlags |= SSF_ANTI_PRIVATE_SOUND; + } + // once + else if ( !token.Icmp( "playonce" ) ) { + parms.soundShaderFlags |= SSF_PLAY_ONCE; + } + // global + else if ( !token.Icmp( "global" ) ) { + parms.soundShaderFlags |= SSF_GLOBAL; + } + // unclamped + else if ( !token.Icmp( "unclamped" ) ) { + parms.soundShaderFlags |= SSF_UNCLAMPED; + } + // omnidirectional + else if ( !token.Icmp( "omnidirectional" ) ) { + parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL; + } + // onDemand can't be a parms, because we must track all references and overrides would confuse it + else if ( !token.Icmp( "onDemand" ) ) { + // no longer loading sounds on demand + //onDemand = true; + } + + // the wave files + else if ( !token.Icmp( "leadin" ) ) { + // add to the leadin list + if ( !src.ReadToken( &token ) ) { + src.Warning( "Expected sound after leadin" ); + return false; + } + if ( soundSystemLocal.soundCache && numLeadins < maxSamples ) { + leadins[ numLeadins ] = soundSystemLocal.soundCache->FindSound( token.c_str(), onDemand ); + numLeadins++; + } + } else if ( token.Find( ".wav", false ) != -1 || token.Find( ".ogg", false ) != -1 ) { + // add to the wav list + if ( soundSystemLocal.soundCache && numEntries < maxSamples ) { + token.BackSlashesToSlashes(); + idStr lang = cvarSystem->GetCVarString( "sys_lang" ); + if ( lang.Icmp( "english" ) != 0 && token.Find( "sound/vo/", false ) >= 0 ) { + idStr work = token; + work.ToLower(); + work.StripLeading( "sound/vo/" ); + work = va( "sound/vo/%s/%s", lang.c_str(), work.c_str() ); + if ( fileSystem->ReadFile( work, NULL, NULL ) > 0 ) { + token = work; + } else { + // also try to find it with the .ogg extension + work.SetFileExtension( ".ogg" ); + if ( fileSystem->ReadFile( work, NULL, NULL ) > 0 ) { + token = work; + } + } + } + entries[ numEntries ] = soundSystemLocal.soundCache->FindSound( token.c_str(), onDemand ); + numEntries++; + } + } else { + src.Warning( "unknown token '%s'", token.c_str() ); + return false; + } + } + + if ( parms.shakes > 0.0f ) { + CheckShakesAndOgg(); + } + + return true; +} + +/* +=============== +idSoundShader::CheckShakesAndOgg +=============== +*/ +bool idSoundShader::CheckShakesAndOgg( void ) const { + int i; + bool ret = false; + + for ( i = 0; i < numLeadins; i++ ) { + if ( leadins[ i ]->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) { + common->Warning( "sound shader '%s' has shakes and uses OGG file '%s'", + GetName(), leadins[ i ]->name.c_str() ); + ret = true; + } + } + for ( i = 0; i < numEntries; i++ ) { + if ( entries[ i ]->objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) { + common->Warning( "sound shader '%s' has shakes and uses OGG file '%s'", + GetName(), entries[ i ]->name.c_str() ); + ret = true; + } + } + return ret; +} + +/* +=============== +idSoundShader::List +=============== +*/ +void idSoundShader::List() const { + idStrList shaders; + + common->Printf( "%4i: %s\n", Index(), GetName() ); + if ( idStr::Icmp( GetDescription(), "" ) != 0 ) { + common->Printf( " description: %s\n", GetDescription() ); + } + for( int k = 0; k < numLeadins ; k++ ) { + const idSoundSample *objectp = leadins[k]; + if ( objectp ) { + common->Printf( " %5dms %4dKb %s (LEADIN)\n", soundSystemLocal.SamplesToMilliseconds(objectp->LengthIn44kHzSamples()), (objectp->objectMemSize/1024) + ,objectp->name.c_str() ); + } + } + for( int k = 0; k < numEntries; k++ ) { + const idSoundSample *objectp = entries[k]; + if ( objectp ) { + common->Printf( " %5dms %4dKb %s\n", soundSystemLocal.SamplesToMilliseconds(objectp->LengthIn44kHzSamples()), (objectp->objectMemSize/1024) + ,objectp->name.c_str() ); + } + } +} + +/* +=============== +idSoundShader::GetAltSound +=============== +*/ +const idSoundShader *idSoundShader::GetAltSound( void ) const { + return altSound; +} + +/* +=============== +idSoundShader::GetMinDistance +=============== +*/ +float idSoundShader::GetMinDistance() const { + return parms.minDistance; +} + +/* +=============== +idSoundShader::GetMaxDistance +=============== +*/ +float idSoundShader::GetMaxDistance() const { + return parms.maxDistance; +} + +/* +=============== +idSoundShader::GetDescription +=============== +*/ +const char *idSoundShader::GetDescription() const { + return desc; +} + +/* +=============== +idSoundShader::HasDefaultSound +=============== +*/ +bool idSoundShader::HasDefaultSound() const { + for ( int i = 0; i < numEntries; i++ ) { + if ( entries[i] && entries[i]->defaultSound ) { + return true; + } + } + return false; +} + +/* +=============== +idSoundShader::GetParms +=============== +*/ +const soundShaderParms_t *idSoundShader::GetParms() const { + return &parms; +} + +/* +=============== +idSoundShader::GetNumSounds +=============== +*/ +int idSoundShader::GetNumSounds() const { + return numLeadins + numEntries; +} + +/* +=============== +idSoundShader::GetSound +=============== +*/ +const char *idSoundShader::GetSound( int index ) const { + if ( index >= 0 ) { + if ( index < numLeadins ) { + return leadins[index]->name.c_str(); + } + index -= numLeadins; + if ( index < numEntries ) { + return entries[index]->name.c_str(); + } + } + return ""; +} + +#endif // Q4_RECONSTRUCTED_SOUND diff --git a/src/sound/sound.h b/src/sound/sound.h new file mode 100644 index 0000000..aeba27b --- /dev/null +++ b/src/sound/sound.h @@ -0,0 +1,556 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SOUND__ +#define __SOUND__ + +// Resolution of sound shakes in fps +#define SHAKE_FPS 30 + +/* +=============================================================================== + + SOUND WORLD IDS + +=============================================================================== +*/ + +#define SOUNDWORLD_ANY -1 +#define SOUNDWORLD_NONE 0 +#define SOUNDWORLD_GAME 1 +#define SOUNDWORLD_MENU 2 +#define SOUNDWORLD_EDITOR 3 +#define SOUNDWORLD_MAX 4 + +/* +=============================================================================== + + SOUND SHADER DECL + +=============================================================================== +*/ + +// RAVEN BEGIN +class rvCommonSample +{ +public: + rvCommonSample( void ); + virtual ~rvCommonSample( void ) {} + + virtual const byte *GetSampleData( void ) const { return( NULL ); } + virtual int GetNumChannels( void ) const { return( 0 ); } + virtual int GetNumSamples( void ) const { return( 0 ); } + virtual int GetSampleRate( void ) const { return( 0 ); } + virtual int GetMemoryUsed( void ) const { return( 0 ); } + virtual int GetDurationMS( void ) const { return( 0 ); } + virtual float GetDuration( void ) const { return( 0.0f ); } + virtual void Load( int langIndex = -1 ) {} + virtual void PurgeSoundSample( void ) {} + virtual bool IsOgg( void ) const { return false; } // is false for an expanded ogg + virtual void Expand( bool force ) {} // expand oggs to pcm + + void SetReferencedThisLevel( void ) { levelLoadReferenced = true; } + + idStr name; // name of the sample file + unsigned int timestamp; // the most recent of all images used in creation, for reloadImages command + + bool defaultSound; // error during loading, now a beep + bool purged; + bool levelLoadReferenced; // so we can tell which samples aren't needed any more +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + bool referencedOutsideLevelLoad; +#endif +// RAVEN END +}; +// RAVEN END + +// sound shader flags +static const int SSF_PRIVATE_SOUND = BIT(0); // only plays for the current listenerId +static const int SSF_ANTI_PRIVATE_SOUND =BIT(1); // plays for everyone but the current listenerId +static const int SSF_NO_OCCLUSION = BIT(2); // don't flow through portals, only use straight line +static const int SSF_GLOBAL = BIT(3); // play full volume to all speakers and all listeners +static const int SSF_OMNIDIRECTIONAL = BIT(4); // fall off with distance, but play same volume in all speakers +static const int SSF_LOOPING = BIT(5); // repeat the sound continuously +static const int SSF_PLAY_ONCE = BIT(6); // never restart if already playing on any channel of a given emitter +static const int SSF_UNCLAMPED = BIT(7); // don't clamp calculated volumes at 1.0 +static const int SSF_NO_FLICKER = BIT(8); // always return 1.0 for volume queries +static const int SSF_NO_DUPS = BIT(9); // try not to play the same sound twice in a row +// RAVEN BEGIN +static const int SSF_USEDOPPLER = BIT(10); // allow doppler pitch shifting effects +static const int SSF_NO_RANDOMSTART = BIT(11); // don't offset the start position for looping sounds +static const int SSF_VO_FOR_PLAYER = BIT(12); // Notifies a funcRadioChatter that this shader is directed at the player +static const int SSF_IS_VO = BIT(13); // this sound is VO +static const int SSF_CAUSE_RUMBLE = BIT(14); // causes joystick rumble +static const int SSF_CENTER = BIT(15); // sound through center channel only +static const int SSF_HILITE = BIT(16); // display debug info for this emitter +// RAVEN END + +// these options can be overriden from sound shader defaults on a per-emitter and per-channel basis +typedef struct { + float minDistance; + float maxDistance; + float volume; + float attenuatedVolume; + float shakes; + int soundShaderFlags; // SSF_* bit flags + int soundClass; // for global fading of sounds +// RAVEN BEGIN +// bdube: frequency shift + float frequencyShift; + float wetLevel; + float dryLevel; +// RAVEN END +} soundShaderParms_t; + +const int SOUND_MAX_LIST_WAVS = 32; + +// sound classes are used to fade most sounds down inside cinematics, leaving dialog +// flagged with a non-zero class full volume +const int SOUND_CLASS_MUSICAL = 3; +const int SOUND_MAX_CLASSES = 4; + +// it is somewhat tempting to make this a virtual class to hide the private +// details here, but that doesn't fit easily with the decl manager at the moment. +// RAVEN BEGIN +// jsinger: added to support serialization/deserialization of binary decls +#ifdef RV_BINARYDECLS +class idSoundShader : public idDecl, public Serializable<'ISS '> { +public: + virtual void Write( SerialOutputStream &stream ) const; + virtual void AddReferences() const; + idSoundShader( SerialInputStream &stream ); +#else +class idSoundShader : public idDecl { +#endif +public: + idSoundShader( void ) { Init(); } + virtual ~idSoundShader( void ) {} + + virtual size_t Size( void ) const; + virtual bool SetDefaultText( void ); + virtual const char * DefaultDefinition( void ) const; + virtual bool Parse( const char *text, const int textLength, bool noCaching ); + virtual void FreeData( void ); +// RAVEN BEGIN + virtual void SetReferencedThisLevel( void ); +// RAVEN END + virtual void List( void ) const; + + virtual const char * GetDescription( void ) const { return( desc ); } + + // so the editor can draw correct default sound spheres + // this is currently defined as meters, which sucks, IMHO. + virtual float GetMinDistance( void ) const { return( parms.minDistance ); } + virtual float GetMaxDistance( void ) const { return( parms.maxDistance ); } + +// RAVEN BEGIN +// scork: for detailed error-reporting + virtual bool Validate( const char *psText, int iTextLength, idStr &strReportTo ) const; + +// jscott: implemented id's code + virtual bool RebuildTextSource( void ); + +// jscott: required access functions + bool IsPrivateSound( void ) const { return( !!( parms.soundShaderFlags & SSF_PRIVATE_SOUND ) ); } + bool IsAntiPrivateSound( void ) const { return( !!( parms.soundShaderFlags & SSF_ANTI_PRIVATE_SOUND ) ); } + bool IsNoOcclusion( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_OCCLUSION ) ); } + bool IsGlobal( void ) const { return( !!( parms.soundShaderFlags & SSF_GLOBAL ) ); } + bool IsOmnidirectional( void ) const { return( !!( parms.soundShaderFlags & SSF_OMNIDIRECTIONAL ) ); } + bool IsLooping( void ) const { return( !!( parms.soundShaderFlags & SSF_LOOPING ) ); } + bool IsPlayOnce( void ) const { return( !!( parms.soundShaderFlags & SSF_PLAY_ONCE ) ); } + bool IsUnclamped( void ) const { return( !!( parms.soundShaderFlags & SSF_UNCLAMPED ) ); } + bool IsNoFlicker( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_FLICKER ) ); } + bool IsNoDupes( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_DUPS ) ); } + bool IsDoppler( void ) const { return( !!( parms.soundShaderFlags & SSF_USEDOPPLER ) ); } + bool IsNoRandomStart( void ) const { return( !!( parms.soundShaderFlags & SSF_NO_RANDOMSTART ) ); } + bool IsVO_ForPlayer( void ) const { return( !!( parms.soundShaderFlags & SSF_VO_FOR_PLAYER ) ); } + bool IsVO( void ) const { return( !!( parms.soundShaderFlags & SSF_IS_VO ) ); } + bool IsCauseRumble( void ) const { return( !!( parms.soundShaderFlags & SSF_CAUSE_RUMBLE ) ); } + bool IsCenter( void ) const { return( !!( parms.soundShaderFlags & SSF_CENTER ) ); } + + float GetVolume( void ) const { return( parms.volume ); } + float GetShakes( void ) const { return( parms.shakes ); } + float GetTimeLength( void ) const; + void GetParms( soundShaderParms_t *out ) const { *out = parms; } + void SetNoShakes( bool in ) { noShakes = in; } + bool GetNoShakes( void ) const { return( noShakes ); } + + void IncPlayCount( void ) { playCount++; } + int GetPlayCount( void ) const { return( playCount ); } +// RAVEN END + + float GetLeadinVolume( void ) const { return( leadinVolume ); } + int GetNumEntries( void ) const { return( numEntries ); } + rvCommonSample *GetLeadin( int index ) const { return( leadins[index] ); } + rvCommonSample *GetEntry( int index ) const { return( entries[index] ); } + const char *GetShakeData( int index ) const; + void SetShakeData( int index, const char *ampData ); + void Purge( bool freeBaseBlocks ); + void LoadSampleData( int langIndex = -1 ); + + const char * GetSampleName( int index ) const; + int GetSamplesPerSec( int index ) const; + int GetNumChannels( int index ) const; + int GetNumSamples( int index ) const; + int GetMemorySize( int index ) const; + const byte * GetNonCacheData( int index ) const; + + void ExpandSmallOggs( bool force ); +// RAVEN END + + // returns NULL if an AltSound isn't defined in the shader. + // we use this for pairing a specific broken light sound with a normal light sound + virtual const idSoundShader *GetAltSound( void ) const { return( altSound ); } + + virtual bool HasDefaultSound( void ) const; + + virtual const soundShaderParms_t *GetParms( void ) const { return( &parms ); } + virtual int GetNumSounds( void ) const; + virtual const char * GetSound( int index ) const; + +private: + friend class idSoundEmitterLocal; + friend class idSoundChannel; + friend class idSoundCache; + + // options from sound shader text + soundShaderParms_t parms; // can be overriden on a per-channel basis + + const idSoundShader * altSound; + idStr desc; // description + bool errorDuringParse; +// RAVEN BEGIN + bool noShakes; // Don't generate shake data + bool frequentlyUsed; // Expand this to pcm data no matter how long it is +// RAVEN END + float leadinVolume; // allows light breaking leadin sounds to be much louder than the broken loop + +// RAVEN BEGIN + rvCommonSample * leadins[SOUND_MAX_LIST_WAVS]; +// RAVEN END + int numLeadins; +// RAVEN BEGIN + rvCommonSample * entries[SOUND_MAX_LIST_WAVS]; + idStrList shakes; +// RAVEN END + int numEntries; + +// RAVEN BEGIN +// bdube: frequency shift code from splash + float minFrequencyShift; + float maxFrequencyShift; + + int playCount; // For profiling +// RAVEN END + +private: + void Init( void ); + bool ParseShader( idLexer &src ); +}; + +class rvSoundShaderEdit +{ +public: + virtual ~rvSoundShaderEdit() {} + virtual const char * GetSampleName( const idSoundShader *sound, int index ) const = 0; + virtual int GetSamplesPerSec( const idSoundShader *sound, int index ) const = 0; + virtual int GetNumChannels( const idSoundShader *sound, int index ) const = 0; + virtual int GetNumSamples( const idSoundShader *sound, int index ) const = 0; + virtual int GetMemorySize( const idSoundShader *sound, int index ) const = 0; + virtual const byte * GetNonCacheData( const idSoundShader *sound, int index ) const = 0; + virtual void LoadSampleData( idSoundShader *sound, int langIndex = -1 ) = 0; + virtual void ExpandSmallOggs( idSoundShader *sound, bool force ) = 0; + virtual const char *GetShakeData( idSoundShader *sound, int index ) = 0; + virtual void SetShakeData( idSoundShader *sound, int index, const char *ampData ) = 0; + virtual void Purge( idSoundShader *sound, bool freeBaseBlocks ) = 0; +}; + +extern rvSoundShaderEdit *soundShaderEdit; + +/* +=============================================================================== + + SOUND EMITTER + +=============================================================================== +*/ + +// sound channels +static const int SCHANNEL_ANY = 0; // used in queries and commands to effect every channel at once, in + // startSound to have it not override any other channel +static const int SCHANNEL_ONE = 1; // any following integer can be used as a channel number +typedef int s_channelType; // the game uses its own series of enums, and we don't want to require casts + + +class idSoundEmitter { +public: + virtual ~idSoundEmitter( void ) {} + + // the parms specified will be the default overrides for all sounds started on this emitter. + // NULL is acceptable for parms + virtual void UpdateEmitter( const idVec3 &origin, const idVec3 &velocity, int listenerId, const soundShaderParms_t *parms ) = 0; + + // returns the length of the started sound in msec + virtual int StartSound( const idSoundShader *shader, const s_channelType channel, float diversity = 0.0f, int shaderFlags = 0 ) = 0; + + // pass SCHANNEL_ANY to effect all channels + virtual void ModifySound( const s_channelType channel, const soundShaderParms_t *parms ) = 0; + virtual void StopSound( const s_channelType channel ) = 0; + // to is in Db (sigh), over is in seconds + virtual void FadeSound( const s_channelType channel, float to, float over ) = 0; + + // returns true if there are any sounds playing from this emitter. There is some conservative + // slop at the end to remove inconsistent race conditions with the sound thread updates. + // FIXME: network game: on a dedicated server, this will always be false + virtual bool CurrentlyPlaying( const s_channelType channel = SCHANNEL_ANY ) const = 0; + + // returns a 0.0 to 1.0 value based on the current sound amplitude, allowing + // graphic effects to be modified in time with the audio. + // just samples the raw wav file, it doesn't account for volume overrides in the + virtual float CurrentAmplitude( int channelFlags = -1, bool factorDistance = false ) = 0; + + // Returns true if the emitter is in the passed in world + virtual bool AttachedToWorld( int id ) const = 0; + + // for save games. Index will always be > 0 + virtual int Handle( void ) const = 0; +}; + +/* +=============================================================================== + + SOUND SYSTEM + +=============================================================================== +*/ + +typedef struct { + idStr name; + idStr format; + int numChannels; + int numSamplesPerSecond; + int num44kHzSamples; + int numBytes; + bool looping; + float lastVolume; + int start44kHzTime; + int current44kHzTime; +} soundDecoderInfo_t; + +typedef struct soundPortalTrace_s { + int portalArea; + const struct soundPortalTrace_s *prevStack; +} soundPortalTrace_t; + +class idSoundSystem { +public: + virtual ~idSoundSystem( void ) {} + + // all non-hardware initialization + virtual void Init( void ) = 0; + + // shutdown routine + virtual void Shutdown( void ) = 0; + + // call ClearBuffer if there is a chance that the AsyncUpdate won't get called + // for 20+ msec, which would cause a stuttering repeat of the current + // buffer contents + virtual void ClearBuffer( void ) = 0; + + // sound is attached to the window, and must be recreated when the window is changed + virtual bool InitHW( void ) = 0; + virtual void InitVoiceComms( void ) = 0; + virtual bool ShutdownHW( void ) = 0; + virtual void ShutdownVoiceComms( void ) = 0; + + // Called once per common frame to check on changes to the sound system + virtual void Frame( void ) = 0; + // Service the sound system + virtual void ForegroundUpdate( void ) = 0; + + // asyn loop, called at 60Hz + virtual int AsyncUpdate( int time ) = 0; + + // direct mixing for OSes that support it + virtual int AsyncMix( int soundTime, float *mixBuffer ) = 0; + + // async loop, when the sound driver uses a write strategy + virtual int AsyncUpdateWrite( int time ) = 0; + + // it is a good idea to mute everything when starting a new level, + // because sounds may be started before a valid listener origin + // is specified + virtual void SetMute( bool mute ) = 0; + + // for the sound level meter window + virtual cinData_t ImageForTime( const int milliseconds, const bool waveform ) = 0; + + // get sound decoder info + virtual int GetSoundDecoderInfo( int index, soundDecoderInfo_t &decoderInfo ) = 0; + + // Mark all soundSamples as currently unused, + // but don't free anything. + virtual void BeginLevelLoad( const char *mapName ) = 0; + + // Free all soundSamples marked as unused + // We might want to defer the loading of new sounds to this point, + // as we do with images, to avoid having a union in memory at one time. + virtual void EndLevelLoad( const char *mapName ) = 0; + +// RAVEN BEGIN +// jnewquist: Free all sounds +#ifdef _XENON + virtual void FlushLevelSoundSamples( void ) = 0; +#endif +// RAVEN END + + // Frees the empty base blocks in the appropriate soundCache + virtual void CleanCache( void ) = 0; + + // prints memory info + virtual void PrintMemInfo( MemInfo *mi ) = 0; +#ifdef _USE_OPENAL + // is EAX support present - -1: disabled at compile time. 0: no suitable hardwre 1: ok + virtual int IsEAXAvailable( void ) = 0; + virtual const char * GetDeviceName( int index ) = 0; + virtual const char * GetDefaultDeviceName( void ) = 0; +#endif + + // SoundWorld stuff + + // call at each map start + virtual void SetRenderWorld( idRenderWorld *rw ) = 0; + virtual void StopAllSounds( int worldId ) = 0; + +// RAVEN BEGIN + // dissociate all virtual channels from hardware + virtual void DisableAllSounds( void ) = 0; + + // get a new emitter that can play sounds in this world + virtual int AllocSoundEmitter( int worldId ) = 0; + virtual void FreeSoundEmitter( int worldId, int handle, bool immediate ) = 0; +// RAVEN END + + // for load games, index 0 will return NULL + virtual idSoundEmitter *EmitterForIndex( int worldId, int index ) = 0; + virtual int GetNumEmitters( void ) const = 0; + + // query sound samples from all emitters reaching a given position + virtual float CurrentShakeAmplitudeForPosition( int worldId, const int time, const idVec3 &listenerPosition ) = 0; + + // where is the camera/microphone + // listenerId allows listener-private and antiPrivate sounds to be filtered + // gameTime is in msec, and is used to time sound queries and removals so that they are independent + // of any race conditions with the async update + virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, const int listenerId, const int gameTime, const idStr &areaName ) = 0; + + // reset the listener portal to invalid during level transitions + virtual void ResetListener( void ) = 0; + + // fade all sounds in the world with a given shader soundClass + // to is in Db (sigh), over is in seconds + virtual void FadeSoundClasses( int worldId, const int soundClass, float to, const float over ) = 0; + + // background music + virtual void PlayShaderDirectly( int worldId, const char *name, int channel = -1 ) = 0; + + // dumps the current state and begins archiving commands + virtual void StartWritingDemo( int worldId, idDemoFile *demo ) = 0; + virtual void StopWritingDemo( int worldId ) = 0; + + // read a sound command from a demo file + virtual void ProcessDemoCommand( int worldId, idDemoFile *demo ) = 0; + + virtual int GetHardwareTime( void ) const = 0; + + // unpauses the selected soundworld, pauses all others + virtual int SetActiveSoundWorld( bool on ) = 0; +// RAVEN BEGIN +// jnewquist: Accessor for active sound world + virtual int GetActiveSoundWorld( void ) = 0; +// RAVEN END + + // Write the sound output to multiple wav files. Note that this does not use the + // work done by AsyncUpdate, it mixes explicitly in the foreground every PlaceOrigin(), + // under the assumption that we are rendering out screenshots and the gameTime is going + // much slower than real time. + // path should not include an extension, and the generated filenames will be: + // _left.raw, _right.raw, or _51left.raw, _51right.raw, + // _51center.raw, _51lfe.raw, _51backleft.raw, _51backright.raw, + // If only two channel mixing is enabled, the left and right .raw files will also be + // combined into a stereo .wav file. + virtual void AVIOpen( int worldId, const char *path, const char *name ) = 0; + virtual void AVIClose( int worldId ) = 0; + + // SaveGame / demo Support + virtual void WriteToSaveGame( int worldId, idFile *savefile ) = 0; + virtual void ReadFromSaveGame( int worldId, idFile *savefile ) = 0; + +// RAVEN BEGIN +// rjohnson: added list active sounds + virtual void ListActiveSounds( int worldId ) = 0; +// RAVEN END + // End SoundWorld stuff + +// RAVEN BEGIN +// jscott: added + virtual size_t ListSoundSummary( void ) = 0; + + virtual bool HasCache( void ) const = 0; + virtual rvCommonSample *FindSample( const idStr &filename ) = 0; + virtual int SamplesToMilliseconds( int samples ) const = 0; + virtual void * AllocSoundSample( int size ) = 0; + virtual void FreeSoundSample( const byte *address ) = 0; + + virtual bool GetInsideLevelLoad( void ) const = 0; + virtual bool ValidateSoundShader( idSoundShader *shader ) = 0; + +// jscott: voice comm support + virtual bool EnableRecording( bool enable, bool test, float &micLevel ) = 0; + virtual int GetVoiceData( byte *buffer, int maxSize ) = 0; + virtual void PlayVoiceData( int clientNum, const byte *buffer, int bytes ) = 0; + virtual void BufferVoiceData( void ) = 0; + virtual void MixVoiceData( float *finalMixBuffer, int numSpeakers, int newTime ) = 0; +// ddynerman: voice comm utility + virtual int GetCommClientNum( int channel ) const = 0; + virtual int GetNumVoiceChannels( void ) const = 0; + +// jscott: reverb editor support + virtual const char *GetReverbName( int reverb ) = 0; + virtual int GetNumAreas( void ) = 0; + virtual int GetReverb( int area ) = 0; + virtual bool SetReverb( int area, const char *reverbName, const char *fileName ) = 0; + + virtual void EndCinematic() = 0; + +// RAVEN END +}; + +extern idSoundSystem *soundSystem; + +void S_InitSoundSystem( void ); + +#endif /* !__SOUND__ */ diff --git a/src/sys/AutoVersion.h b/src/sys/AutoVersion.h new file mode 100644 index 0000000..d01a23e --- /dev/null +++ b/src/sys/AutoVersion.h @@ -0,0 +1,37 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __AUTO_VERSION_HEADER +#define __AUTO_VERSION_HEADER + +// used by the .rc files +#define VERSION_MAJOR_RELEASE 1 +#define VERSION_MINOR_RELEASE 4 +#define VERSION_INTERNAL_BUILD 2 +#define VERSION_BUILD_NUMBER 0 + +#define VERSION_STRING "1, 4, 2, 0" +#define VERSION_STRING_DOTTED "1.4.2" + +#endif // __AUTO_VERSION_HEADER diff --git a/src/sys/linux/qgl_enforce.h b/src/sys/linux/qgl_enforce.h new file mode 100644 index 0000000..c631515 --- /dev/null +++ b/src/sys/linux/qgl_enforce.h @@ -0,0 +1,370 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define glAccum use_qglAccum +#define glAlphaFunc use_qglAlphaFunc +#define glAreTexturesResident use_qglAreTexturesResident +#define glArrayElement use_qglArrayElement +#define glBegin use_qglBegin +#define glBindTexture use_qglBindTexture +#define glBitmap use_qglBitmap +#define glBlendFunc use_qglBlendFunc +#define glCallList use_qglCallList +#define glCallLists use_qglCallLists +#define glClear use_qglClear +#define glClearAccum use_qglClearAccum +#define glClearColor use_qglClearColor +#define glClearDepth use_qglClearDepth +#define glClearIndex use_qglClearIndex +#define glClearStencil use_qglClearStencil +#define glClipPlane use_qglClipPlane +#define glColor3b use_qglColor3b +#define glColor3bv use_qglColor3bv +#define glColor3d use_qglColor3d +#define glColor3dv use_qglColor3dv +#define glColor3f use_qglColor3f +#define glColor3fv use_qglColor3fv +#define glColor3i use_qglColor3i +#define glColor3iv use_qglColor3iv +#define glColor3s use_qglColor3s +#define glColor3sv use_qglColor3sv +#define glColor3ub use_qglColor3ub +#define glColor3ubv use_qglColor3ubv +#define glColor3ui use_qglColor3ui +#define glColor3uiv use_qglColor3uiv +#define glColor3us use_qglColor3us +#define glColor3usv use_qglColor3usv +#define glColor4b use_qglColor4b +#define glColor4bv use_qglColor4bv +#define glColor4d use_qglColor4d +#define glColor4dv use_qglColor4dv +#define glColor4f use_qglColor4f +#define glColor4fv use_qglColor4fv +#define glColor4i use_qglColor4i +#define glColor4iv use_qglColor4iv +#define glColor4s use_qglColor4s +#define glColor4sv use_qglColor4sv +#define glColor4ub use_qglColor4ub +#define glColor4ubv use_qglColor4ubv +#define glColor4ui use_qglColor4ui +#define glColor4uiv use_qglColor4uiv +#define glColor4us use_qglColor4us +#define glColor4usv use_qglColor4usv +#define glColorMask use_qglColorMask +#define glColorMaterial use_qglColorMaterial +#define glColorPointer use_qglColorPointer +#define glCopyPixels use_qglCopyPixels +#define glCopyTexImage1D use_qglCopyTexImage1D +#define glCopyTexImage2D use_qglCopyTexImage2D +#define glCopyTexSubImage1D use_qglCopyTexSubImage1D +#define glCopyTexSubImage2D use_qglCopyTexSubImage2D +#define glCullFace use_qglCullFace +#define glDeleteLists use_qglDeleteLists +#define glDeleteTextures use_qglDeleteTextures +#define glDepthFunc use_qglDepthFunc +#define glDepthMask use_qglDepthMask +#define glDepthRange use_qglDepthRange +#define glDisable use_qglDisable +#define glDisableClientState use_qglDisableClientState +#define glDrawArrays use_qglDrawArrays +#define glDrawBuffer use_qglDrawBuffer +#define glDrawElements use_qglDrawElements +#define glDrawPixels use_qglDrawPixels +#define glEdgeFlag use_qglEdgeFlag +#define glEdgeFlagPointer use_qglEdgeFlagPointer +#define glEdgeFlagv use_qglEdgeFlagv +#define glEnable use_qglEnable +#define glEnableClientState use_qglEnableClientState +#define glEnd use_qglEnd +#define glEndList use_qglEndList +#define glEvalCoord1d use_qglEvalCoord1d +#define glEvalCoord1dv use_qglEvalCoord1dv +#define glEvalCoord1f use_qglEvalCoord1f +#define glEvalCoord1fv use_qglEvalCoord1fv +#define glEvalCoord2d use_qglEvalCoord2d +#define glEvalCoord2dv use_qglEvalCoord2dv +#define glEvalCoord2f use_qglEvalCoord2f +#define glEvalCoord2fv use_qglEvalCoord2fv +#define glEvalMesh1 use_qglEvalMesh1 +#define glEvalMesh2 use_qglEvalMesh2 +#define glEvalPoint1 use_qglEvalPoint1 +#define glEvalPoint2 use_qglEvalPoint2 +#define glFeedbackBuffer use_qglFeedbackBuffer +#define glFinish use_qglFinish +#define glFlush use_qglFlush +#define glFogf use_qglFogf +#define glFogfv use_qglFogfv +#define glFogi use_qglFogi +#define glFogiv use_qglFogiv +#define glFrontFace use_qglFrontFace +#define glFrustum use_qglFrustum +#define glGenLists use_qglGenLists +#define glGenTextures use_qglGenTextures +#define glGetBooleanv use_qglGetBooleanv +#define glGetClipPlane use_qglGetClipPlane +#define glGetDoublev use_qglGetDoublev +#define glGetError use_qglGetError +#define glGetFloatv use_qglGetFloatv +#define glGetIntegerv use_qglGetIntegerv +#define glGetLightfv use_qglGetLightfv +#define glGetLightiv use_qglGetLightiv +#define glGetMapdv use_qglGetMapdv +#define glGetMapfv use_qglGetMapfv +#define glGetMapiv use_qglGetMapiv +#define glGetMaterialfv use_qglGetMaterialfv +#define glGetMaterialiv use_qglGetMaterialiv +#define glGetPixelMapfv use_qglGetPixelMapfv +#define glGetPixelMapuiv use_qglGetPixelMapuiv +#define glGetPixelMapusv use_qglGetPixelMapusv +#define glGetPointerv use_qglGetPointerv +#define glGetPolygonStipple use_qglGetPolygonStipple +#define glGetString use_qglGetString +#define glGetTexEnvfv use_qglGetTexEnvfv +#define glGetTexEnviv use_qglGetTexEnviv +#define glGetTexGendv use_qglGetTexGendv +#define glGetTexGenfv use_qglGetTexGenfv +#define glGetTexGeniv use_qglGetTexGeniv +#define glGetTexImage use_qglGetTexImage +#define glGetTexLevelParameterfv use_qglGetTexLevelParameterfv +#define glGetTexLevelParameteriv use_qglGetTexLevelParameteriv +#define glGetTexParameterfv use_qglGetTexParameterfv +#define glGetTexParameteriv use_qglGetTexParameteriv +#define glHint use_qglHint +#define glIndexMask use_qglIndexMask +#define glIndexPointer use_qglIndexPointer +#define glIndexd use_qglIndexd +#define glIndexdv use_qglIndexdv +#define glIndexf use_qglIndexf +#define glIndexfv use_qglIndexfv +#define glIndexi use_qglIndexi +#define glIndexiv use_qglIndexiv +#define glIndexs use_qglIndexs +#define glIndexsv use_qglIndexsv +#define glIndexub use_qglIndexub +#define glIndexubv use_qglIndexubv +#define glInitNames use_qglInitNames +#define glInterleavedArrays use_qglInterleavedArrays +#define glIsEnabled use_qglIsEnabled +#define glIsList use_qglIsList +#define glIsTexture use_qglIsTexture +#define glLightModelf use_qglLightModelf +#define glLightModelfv use_qglLightModelfv +#define glLightModeli use_qglLightModeli +#define glLightModeliv use_qglLightModeliv +#define glLightf use_qglLightf +#define glLightfv use_qglLightfv +#define glLighti use_qglLighti +#define glLightiv use_qglLightiv +#define glLineStipple use_qglLineStipple +#define glLineWidth use_qglLineWidth +#define glListBase use_qglListBase +#define glLoadIdentity use_qglLoadIdentity +#define glLoadMatrixd use_qglLoadMatrixd +#define glLoadMatrixf use_qglLoadMatrixf +#define glLoadName use_qglLoadName +#define glLogicOp use_qglLogicOp +#define glMap1d use_qglMap1d +#define glMap1f use_qglMap1f +#define glMap2d use_qglMap2d +#define glMap2f use_qglMap2f +#define glMapGrid1d use_qglMapGrid1d +#define glMapGrid1f use_qglMapGrid1f +#define glMapGrid2d use_qglMapGrid2d +#define glMapGrid2f use_qglMapGrid2f +#define glMaterialf use_qglMaterialf +#define glMaterialfv use_qglMaterialfv +#define glMateriali use_qglMateriali +#define glMaterialiv use_qglMaterialiv +#define glMatrixMode use_qglMatrixMode +#define glMultMatrixd use_qglMultMatrixd +#define glMultMatrixf use_qglMultMatrixf +#define glNewList use_qglNewList +#define glNormal3b use_qglNormal3b +#define glNormal3bv use_qglNormal3bv +#define glNormal3d use_qglNormal3d +#define glNormal3dv use_qglNormal3dv +#define glNormal3f use_qglNormal3f +#define glNormal3fv use_qglNormal3fv +#define glNormal3i use_qglNormal3i +#define glNormal3iv use_qglNormal3iv +#define glNormal3s use_qglNormal3s +#define glNormal3sv use_qglNormal3sv +#define glNormalPointer use_qglNormalPointer +#define glOrtho use_qglOrtho +#define glPassThrough use_qglPassThrough +#define glPixelMapfv use_qglPixelMapfv +#define glPixelMapuiv use_qglPixelMapuiv +#define glPixelMapusv use_qglPixelMapusv +#define glPixelStoref use_qglPixelStoref +#define glPixelStorei use_qglPixelStorei +#define glPixelTransferf use_qglPixelTransferf +#define glPixelTransferi use_qglPixelTransferi +#define glPixelZoom use_qglPixelZoom +#define glPointSize use_qglPointSize +#define glPolygonMode use_qglPolygonMode +#define glPolygonOffset use_qglPolygonOffset +#define glPolygonStipple use_qglPolygonStipple +#define glPopAttrib use_qglPopAttrib +#define glPopClientAttrib use_qglPopClientAttrib +#define glPopMatrix use_qglPopMatrix +#define glPopName use_qglPopName +#define glPrioritizeTextures use_qglPrioritizeTextures +#define glPushAttrib use_qglPushAttrib +#define glPushClientAttrib use_qglPushClientAttrib +#define glPushMatrix use_qglPushMatrix +#define glPushName use_qglPushName +#define glRasterPos2d use_qglRasterPos2d +#define glRasterPos2dv use_qglRasterPos2dv +#define glRasterPos2f use_qglRasterPos2f +#define glRasterPos2fv use_qglRasterPos2fv +#define glRasterPos2i use_qglRasterPos2i +#define glRasterPos2iv use_qglRasterPos2iv +#define glRasterPos2s use_qglRasterPos2s +#define glRasterPos2sv use_qglRasterPos2sv +#define glRasterPos3d use_qglRasterPos3d +#define glRasterPos3dv use_qglRasterPos3dv +#define glRasterPos3f use_qglRasterPos3f +#define glRasterPos3fv use_qglRasterPos3fv +#define glRasterPos3i use_qglRasterPos3i +#define glRasterPos3iv use_qglRasterPos3iv +#define glRasterPos3s use_qglRasterPos3s +#define glRasterPos3sv use_qglRasterPos3sv +#define glRasterPos4d use_qglRasterPos4d +#define glRasterPos4dv use_qglRasterPos4dv +#define glRasterPos4f use_qglRasterPos4f +#define glRasterPos4fv use_qglRasterPos4fv +#define glRasterPos4i use_qglRasterPos4i +#define glRasterPos4iv use_qglRasterPos4iv +#define glRasterPos4s use_qglRasterPos4s +#define glRasterPos4sv use_qglRasterPos4sv +#define glReadBuffer use_qglReadBuffer +#define glReadPixels use_qglReadPixels +#define glRectd use_qglRectd +#define glRectdv use_qglRectdv +#define glRectf use_qglRectf +#define glRectfv use_qglRectfv +#define glRecti use_qglRecti +#define glRectiv use_qglRectiv +#define glRects use_qglRects +#define glRectsv use_qglRectsv +#define glRenderMode use_qglRenderMode +#define glRotated use_qglRotated +#define glRotatef use_qglRotatef +#define glScaled use_qglScaled +#define glScalef use_qglScalef +#define glScissor use_qglScissor +#define glSelectBuffer use_qglSelectBuffer +#define glShadeModel use_qglShadeModel +#define glStencilFunc use_qglStencilFunc +#define glStencilMask use_qglStencilMask +#define glStencilOp use_qglStencilOp +#define glTexCoord1d use_qglTexCoord1d +#define glTexCoord1dv use_qglTexCoord1dv +#define glTexCoord1f use_qglTexCoord1f +#define glTexCoord1fv use_qglTexCoord1fv +#define glTexCoord1i use_qglTexCoord1i +#define glTexCoord1iv use_qglTexCoord1iv +#define glTexCoord1s use_qglTexCoord1s +#define glTexCoord1sv use_qglTexCoord1sv +#define glTexCoord2d use_qglTexCoord2d +#define glTexCoord2dv use_qglTexCoord2dv +#define glTexCoord2f use_qglTexCoord2f +#define glTexCoord2fv use_qglTexCoord2fv +#define glTexCoord2i use_qglTexCoord2i +#define glTexCoord2iv use_qglTexCoord2iv +#define glTexCoord2s use_qglTexCoord2s +#define glTexCoord2sv use_qglTexCoord2sv +#define glTexCoord3d use_qglTexCoord3d +#define glTexCoord3dv use_qglTexCoord3dv +#define glTexCoord3f use_qglTexCoord3f +#define glTexCoord3fv use_qglTexCoord3fv +#define glTexCoord3i use_qglTexCoord3i +#define glTexCoord3iv use_qglTexCoord3iv +#define glTexCoord3s use_qglTexCoord3s +#define glTexCoord3sv use_qglTexCoord3sv +#define glTexCoord4d use_qglTexCoord4d +#define glTexCoord4dv use_qglTexCoord4dv +#define glTexCoord4f use_qglTexCoord4f +#define glTexCoord4fv use_qglTexCoord4fv +#define glTexCoord4i use_qglTexCoord4i +#define glTexCoord4iv use_qglTexCoord4iv +#define glTexCoord4s use_qglTexCoord4s +#define glTexCoord4sv use_qglTexCoord4sv +#define glTexCoordPointer use_qglTexCoordPointer +#define glTexEnvf use_qglTexEnvf +#define glTexEnvfv use_qglTexEnvfv +#define glTexEnvi use_qglTexEnvi +#define glTexEnviv use_qglTexEnviv +#define glTexGend use_qglTexGend +#define glTexGendv use_qglTexGendv +#define glTexGenf use_qglTexGenf +#define glTexGenfv use_qglTexGenfv +#define glTexGeni use_qglTexGeni +#define glTexGeniv use_qglTexGeniv +#define glTexImage1D use_qglTexImage1D +#define glTexImage2D use_qglTexImage2D +#define glTexParameterf use_qglTexParameterf +#define glTexParameterfv use_qglTexParameterfv +#define glTexParameteri use_qglTexParameteri +#define glTexParameteriv use_qglTexParameteriv +#define glTexSubImage1D use_qglTexSubImage1D +#define glTexSubImage2D use_qglTexSubImage2D +#define glTranslated use_qglTranslated +#define glTranslatef use_qglTranslatef +#define glVertex2d use_qglVertex2d +#define glVertex2dv use_qglVertex2dv +#define glVertex2f use_qglVertex2f +#define glVertex2fv use_qglVertex2fv +#define glVertex2i use_qglVertex2i +#define glVertex2iv use_qglVertex2iv +#define glVertex2s use_qglVertex2s +#define glVertex2sv use_qglVertex2sv +#define glVertex3d use_qglVertex3d +#define glVertex3dv use_qglVertex3dv +#define glVertex3f use_qglVertex3f +#define glVertex3fv use_qglVertex3fv +#define glVertex3i use_qglVertex3i +#define glVertex3iv use_qglVertex3iv +#define glVertex3s use_qglVertex3s +#define glVertex3sv use_qglVertex3sv +#define glVertex4d use_qglVertex4d +#define glVertex4dv use_qglVertex4dv +#define glVertex4f use_qglVertex4f +#define glVertex4fv use_qglVertex4fv +#define glVertex4i use_qglVertex4i +#define glVertex4iv use_qglVertex4iv +#define glVertex4s use_qglVertex4s +#define glVertex4sv use_qglVertex4sv +#define glVertexPointer use_qglVertexPointer +#define glViewport use_qglViewport + +#define glChooseVisual use_qglChooseVisual +#define glCreateContext use_qglCreateContext +#define glDestroyContext use_qglDestroyContext +#define glMakeCurrent use_qglMakeCurrent +#define glSwapBuffers use_qglSwapBuffers +#define glGetProcAddressARB use_qglGetProcAddressARB +#define glGetCurrentContext use_qglGetCurrentContext + + diff --git a/src/sys/osx/Resources/Game.plist b/src/sys/osx/Resources/Game.plist new file mode 100644 index 0000000..473d5ad --- /dev/null +++ b/src/sys/osx/Resources/Game.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + game.so + CFBundleIdentifier + com.aspyr.quakeiv.bundle + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleSignature + QKIV + CFBundleVersion + 1.0 + CSResourcesFileMapped + yes + + diff --git a/src/sys/osx/Resources/Info.plist b/src/sys/osx/Resources/Info.plist new file mode 100644 index 0000000..288cc6d --- /dev/null +++ b/src/sys/osx/Resources/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDisplayName + Quake 4 + CFBundleExecutable + Quake 4 + CFBundleIconFile + Quake4 + CFBundleIdentifier + com.aspyr.Quake4 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Quake 4 + CFBundlePackageType + APPL + CFBundleSignature + QKIV + CFBundleVersion + 1.4.2 ASPYR_BUILD_NUMBER + NSMainNibFile + SDLMain + NSPrincipalClass + NSApplication + + diff --git a/src/sys/osx/Subprojects/game.xcodeproj/project.pbxproj b/src/sys/osx/Subprojects/game.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f58c0f4 --- /dev/null +++ b/src/sys/osx/Subprojects/game.xcodeproj/project.pbxproj @@ -0,0 +1,1245 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 253A77B10A67159100A3CFB3 /* Buying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 253A77AD0A67159100A3CFB3 /* Buying.cpp */; }; + 253A77B40A6715A300A3CFB3 /* WeaponNapalmGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 253A77B20A6715A300A3CFB3 /* WeaponNapalmGun.cpp */; }; + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8114A1A2090D686B0025E084 /* libidlib_pic.a */; }; + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */; }; + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */; }; + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */; }; + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */; }; + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */; }; + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */; }; + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405A092A64D300B2BF95 /* Sound.cpp */; }; + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */; }; + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400E092A64D300B2BF95 /* StatWindow.cpp */; }; + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */; }; + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */; }; + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */; }; + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */; }; + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */; }; + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */; }; + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */; }; + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400C092A64D300B2BF95 /* StatManager.cpp */; }; + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */; }; + 81114FF8093E194300A5C91A /* AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F75092A64D300B2BF95 /* AF.cpp */; }; + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */; }; + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBF092A64D300B2BF95 /* Camera.cpp */; }; + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */; }; + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */; }; + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */; }; + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F89092A64D300B2BF95 /* AI_events.cpp */; }; + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */; }; + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */; }; + 81115001093E194300A5C91A /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCA092A64D300B2BF95 /* Effect.cpp */; }; + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */; }; + 81115003093E194300A5C91A /* Player_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4040092A64D300B2BF95 /* Player_States.cpp */; }; + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */; }; + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */; }; + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */; }; + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */; }; + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */; }; + 81115009093E194300A5C91A /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE8092A64D300B2BF95 /* State.cpp */; }; + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */; }; + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */; }; + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */; }; + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */; }; + 8111500E093E194300A5C91A /* Mover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4002092A64D300B2BF95 /* Mover.cpp */; }; + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */; }; + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */; }; + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4050092A64D300B2BF95 /* Script_Program.cpp */; }; + 81115012093E194300A5C91A /* Push.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403A092A64D300B2BF95 /* Push.cpp */; }; + 81115013093E194300A5C91A /* IconManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */; }; + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */; }; + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */; }; + 81115016093E194300A5C91A /* Moveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4000092A64D300B2BF95 /* Moveable.cpp */; }; + 81115017093E194300A5C91A /* Clip.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4016092A64D300B2BF95 /* Clip.cpp */; }; + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */; }; + 81115019093E194300A5C91A /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4060092A64D300B2BF95 /* Target.cpp */; }; + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4064092A64D300B2BF95 /* Trigger.cpp */; }; + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4088092A64D300B2BF95 /* Weapon.cpp */; }; + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */; }; + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */; }; + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */; }; + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */; }; + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */; }; + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */; }; + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */; }; + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */; }; + 81115024093E194300A5C91A /* Playback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403C092A64D300B2BF95 /* Playback.cpp */; }; + 81115025093E194300A5C91A /* Force.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4018092A64D300B2BF95 /* Force.cpp */; }; + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400A092A64D300B2BF95 /* StatEvent.cpp */; }; + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */; }; + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */; }; + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */; }; + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */; }; + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */; }; + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */; }; + 8111502D093E194300A5C91A /* Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403D092A64D300B2BF95 /* Player.cpp */; }; + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */; }; + 8111502F093E194300A5C91A /* IK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF4092A64D300B2BF95 /* IK.cpp */; }; + 81115030093E194300A5C91A /* Projectile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4043092A64D300B2BF95 /* Projectile.cpp */; }; + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4067092A64D300B2BF95 /* Vehicle.cpp */; }; + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */; }; + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */; }; + 81115034093E194300A5C91A /* Pvs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4045092A64D300B2BF95 /* Pvs.cpp */; }; + 81115035093E194300A5C91A /* Light.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFA092A64D300B2BF95 /* Light.cpp */; }; + 81115036093E194300A5C91A /* Entity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCE092A64D300B2BF95 /* Entity.cpp */; }; + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */; }; + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */; }; + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */; }; + 8111503A093E194300A5C91A /* AAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7A092A64D300B2BF95 /* AAS.cpp */; }; + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */; }; + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */; }; + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */; }; + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */; }; + 81115040093E194300A5C91A /* AI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F84092A64D300B2BF95 /* AI.cpp */; }; + 81115041093E194300A5C91A /* Tourney.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4010092A64D300B2BF95 /* Tourney.cpp */; }; + 81115042093E194300A5C91A /* TramGate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4062092A64D300B2BF95 /* TramGate.cpp */; }; + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */; }; + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */; }; + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406D092A64D300B2BF95 /* VehicleController.cpp */; }; + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */; }; + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */; }; + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */; }; + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */; }; + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */; }; + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */; }; + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F92092A64D300B2BF95 /* AI_States.cpp */; }; + 8111504D093E194300A5C91A /* Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE3092A64D300B2BF95 /* Event.cpp */; }; + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */; }; + 8111504F093E194300A5C91A /* CTF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4005092A64D300B2BF95 /* CTF.cpp */; }; + 81115050093E194300A5C91A /* GameState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4007092A64D300B2BF95 /* GameState.cpp */; }; + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */; }; + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */; }; + 81115053093E194300A5C91A /* Physics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4022092A64D300B2BF95 /* Physics.cpp */; }; + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */; }; + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */; }; + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */; }; + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */; }; + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */; }; + 81115059093E194300A5C91A /* Class.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDF092A64D300B2BF95 /* Class.cpp */; }; + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */; }; + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */; }; + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */; }; + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */; }; + 8111505E093E194300A5C91A /* Misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFE092A64D300B2BF95 /* Misc.cpp */; }; + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */; }; + 81115060093E194300A5C91A /* Icon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF0092A64D300B2BF95 /* Icon.cpp */; }; + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */; }; + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */; }; + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */; }; + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */; }; + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */; }; + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */; }; + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */; }; + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */; }; + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */; }; + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */; }; + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */; }; + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */; }; + 8111506D093E194300A5C91A /* Anim.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB7092A64D300B2BF95 /* Anim.cpp */; }; + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */; }; + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */; }; + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405E092A64D300B2BF95 /* SplineMover.cpp */; }; + 81115071093E194300A5C91A /* spawner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405C092A64D300B2BF95 /* spawner.cpp */; }; + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */; }; + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */; }; + 81115074093E194300A5C91A /* LipSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */; }; + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */; }; + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */; }; + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */; }; + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */; }; + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */; }; + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401E092A64D300B2BF95 /* Force_Field.cpp */; }; + 8111507B093E194300A5C91A /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F73092A64D300B2BF95 /* Actor.cpp */; }; + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4041092A64D300B2BF95 /* PlayerView.cpp */; }; + 8111507D093E194300A5C91A /* Item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF8092A64D300B2BF95 /* Item.cpp */; }; + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */; }; + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */; }; + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */; }; + 81115081093E194300A5C91A /* Instance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF6092A64D300B2BF95 /* Instance.cpp */; }; + 814E1D11097C19DD009E5F86 /* FreeView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 814E1D0D097C19DD009E5F86 /* FreeView.cpp */; }; + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */; }; + 816AD2E00981819D00CAC29C /* Game.plist in Resources */ = {isa = PBXBuildFile; fileRef = 816AD2DF0981819D00CAC29C /* Game.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 81114FE2093E189800A5C91A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = D2AAC045055464E500DB518D; + remoteInfo = idlib_pic; + }; + 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D2AAC046055464E500DB518D; + remoteInfo = idlib; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 253A77AD0A67159100A3CFB3 /* Buying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Buying.cpp; sourceTree = ""; }; + 253A77AE0A67159100A3CFB3 /* Buying.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Buying.h; sourceTree = ""; }; + 253A77B20A6715A300A3CFB3 /* WeaponNapalmGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNapalmGun.cpp; sourceTree = ""; }; + 81114DFB093E117D00A5C91A /* game.so.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = game.so.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 8114A19C090D686B0025E084 /* idlib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = idlib.xcodeproj; sourceTree = ""; }; + 814E1D0D097C19DD009E5F86 /* FreeView.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FreeView.cpp; sourceTree = ""; }; + 814E1D0E097C19DD009E5F86 /* FreeView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FreeView.h; sourceTree = ""; }; + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientAFEntity.cpp; sourceTree = ""; }; + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientAFEntity.h; sourceTree = ""; }; + 816AD2DF0981819D00CAC29C /* Game.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; name = Game.plist; path = ../Resources/Game.plist; sourceTree = SOURCE_ROOT; }; + 81CC3F73092A64D300B2BF95 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 81CC3F74092A64D300B2BF95 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 81CC3F75092A64D300B2BF95 /* AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AF.cpp; sourceTree = ""; }; + 81CC3F76092A64D300B2BF95 /* AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AF.h; sourceTree = ""; }; + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AFEntity.cpp; sourceTree = ""; }; + 81CC3F78092A64D300B2BF95 /* AFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AFEntity.h; sourceTree = ""; }; + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS.cpp; sourceTree = ""; }; + 81CC3F7B092A64D300B2BF95 /* AAS.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS.h; sourceTree = ""; }; + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_debug.cpp; sourceTree = ""; }; + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_Find.cpp; sourceTree = ""; }; + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_Find.h; sourceTree = ""; }; + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_local.h; sourceTree = ""; }; + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_pathing.cpp; sourceTree = ""; }; + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_routing.cpp; sourceTree = ""; }; + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_tactical.cpp; sourceTree = ""; }; + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_tactical.h; sourceTree = ""; }; + 81CC3F84092A64D300B2BF95 /* AI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI.cpp; sourceTree = ""; }; + 81CC3F85092A64D300B2BF95 /* AI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI.h; sourceTree = ""; }; + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Actions.cpp; sourceTree = ""; }; + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Announcements.cpp; sourceTree = ""; }; + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Debug.cpp; sourceTree = ""; }; + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_events.cpp; sourceTree = ""; }; + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Manager.cpp; sourceTree = ""; }; + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Manager.h; sourceTree = ""; }; + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Medic.cpp; sourceTree = ""; }; + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Medic.h; sourceTree = ""; }; + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Move.cpp; sourceTree = ""; }; + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Move.h; sourceTree = ""; }; + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_pathing.cpp; sourceTree = ""; }; + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_States.cpp; sourceTree = ""; }; + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Tactical.cpp; sourceTree = ""; }; + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Tactical.h; sourceTree = ""; }; + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Util.cpp; sourceTree = ""; }; + 81CC3F96092A64D300B2BF95 /* AI_Util.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Util.h; sourceTree = ""; }; + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Berserker.cpp; sourceTree = ""; }; + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossBuddy.cpp; sourceTree = ""; }; + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossMakron.cpp; sourceTree = ""; }; + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyGround.cpp; sourceTree = ""; }; + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyHover.cpp; sourceTree = ""; }; + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_FailedTransfer.cpp; sourceTree = ""; }; + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Fatty.cpp; sourceTree = ""; }; + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gladiator.cpp; sourceTree = ""; }; + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Grunt.cpp; sourceTree = ""; }; + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gunner.cpp; sourceTree = ""; }; + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Harvester.cpp; sourceTree = ""; }; + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HarvesterDispersal.cpp; sourceTree = ""; }; + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HeavyHoverTank.cpp; sourceTree = ""; }; + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_IronMaiden.cpp; sourceTree = ""; }; + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_LightTank.cpp; sourceTree = ""; }; + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_NetworkGuardian.cpp; sourceTree = ""; }; + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_RepairBot.cpp; sourceTree = ""; }; + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Scientist.cpp; sourceTree = ""; }; + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Sentry.cpp; sourceTree = ""; }; + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_SlimyTransfer.cpp; sourceTree = ""; }; + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StreamProtector.cpp; sourceTree = ""; }; + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggFlyer.cpp; sourceTree = ""; }; + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggHover.cpp; sourceTree = ""; }; + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggMarine.cpp; sourceTree = ""; }; + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TeleportDropper.cpp; sourceTree = ""; }; + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Turret.cpp; sourceTree = ""; }; + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TurretFlying.cpp; sourceTree = ""; }; + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAI.cpp; sourceTree = ""; }; + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAI.h; sourceTree = ""; }; + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim.cpp; sourceTree = ""; }; + 81CC3FB8092A64D300B2BF95 /* Anim.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim.h; sourceTree = ""; }; + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Blend.cpp; sourceTree = ""; }; + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Import.cpp; sourceTree = ""; }; + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Testmodel.cpp; sourceTree = ""; }; + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim_Testmodel.h; sourceTree = ""; }; + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = BrittleFracture.cpp; sourceTree = ""; }; + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BrittleFracture.h; sourceTree = ""; }; + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Camera.cpp; sourceTree = ""; }; + 81CC3FC0092A64D300B2BF95 /* Camera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Camera.h; sourceTree = ""; }; + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEffect.cpp; sourceTree = ""; }; + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEffect.h; sourceTree = ""; }; + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEntity.cpp; sourceTree = ""; }; + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEntity.h; sourceTree = ""; }; + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientModel.cpp; sourceTree = ""; }; + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientModel.h; sourceTree = ""; }; + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientMoveable.cpp; sourceTree = ""; }; + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientMoveable.h; sourceTree = ""; }; + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Effect.cpp; sourceTree = ""; }; + 81CC3FCB092A64D300B2BF95 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = ""; }; + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Entity.cpp; sourceTree = ""; }; + 81CC3FCF092A64D300B2BF95 /* Entity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Entity.h; sourceTree = ""; }; + 81CC3FD3092A64D300B2BF95 /* Game.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game.h; sourceTree = ""; }; + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Debug.cpp; sourceTree = ""; }; + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Debug.h; sourceTree = ""; }; + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_local.cpp; sourceTree = ""; }; + 81CC3FD7092A64D300B2BF95 /* Game_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_local.h; sourceTree = ""; }; + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Log.cpp; sourceTree = ""; }; + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Log.h; sourceTree = ""; }; + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_network.cpp; sourceTree = ""; }; + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameEdit.cpp; sourceTree = ""; }; + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameEdit.h; sourceTree = ""; }; + 81CC3FDF092A64D300B2BF95 /* Class.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Class.cpp; sourceTree = ""; }; + 81CC3FE0092A64D300B2BF95 /* Class.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Class.h; sourceTree = ""; }; + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DebugGraph.cpp; sourceTree = ""; }; + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DebugGraph.h; sourceTree = ""; }; + 81CC3FE3092A64D300B2BF95 /* Event.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Event.cpp; sourceTree = ""; }; + 81CC3FE4092A64D300B2BF95 /* Event.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Event.h; sourceTree = ""; }; + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NoGameTypeInfo.h; sourceTree = ""; }; + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SaveGame.cpp; sourceTree = ""; }; + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SaveGame.h; sourceTree = ""; }; + 81CC3FE8092A64D300B2BF95 /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = ""; }; + 81CC3FE9092A64D300B2BF95 /* State.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = State.h; sourceTree = ""; }; + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCmds.cpp; sourceTree = ""; }; + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCmds.h; sourceTree = ""; }; + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCvar.cpp; sourceTree = ""; }; + 81CC3FED092A64D300B2BF95 /* SysCvar.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCvar.h; sourceTree = ""; }; + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Healing_Station.cpp; sourceTree = ""; }; + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Healing_Station.h; sourceTree = ""; }; + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Icon.cpp; sourceTree = ""; }; + 81CC3FF1092A64D300B2BF95 /* Icon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Icon.h; sourceTree = ""; }; + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IconManager.cpp; sourceTree = ""; }; + 81CC3FF3092A64D300B2BF95 /* IconManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IconManager.h; sourceTree = ""; }; + 81CC3FF4092A64D300B2BF95 /* IK.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IK.cpp; sourceTree = ""; }; + 81CC3FF5092A64D300B2BF95 /* IK.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IK.h; sourceTree = ""; }; + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Instance.cpp; sourceTree = ""; }; + 81CC3FF7092A64D300B2BF95 /* Instance.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Instance.h; sourceTree = ""; }; + 81CC3FF8092A64D300B2BF95 /* Item.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Item.cpp; sourceTree = ""; }; + 81CC3FF9092A64D300B2BF95 /* Item.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Item.h; sourceTree = ""; }; + 81CC3FFA092A64D300B2BF95 /* Light.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Light.cpp; sourceTree = ""; }; + 81CC3FFB092A64D300B2BF95 /* Light.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Light.h; sourceTree = ""; }; + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LipSync.cpp; sourceTree = ""; }; + 81CC3FFD092A64D300B2BF95 /* LipSync.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LipSync.h; sourceTree = ""; }; + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Misc.cpp; sourceTree = ""; }; + 81CC3FFF092A64D300B2BF95 /* Misc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Misc.h; sourceTree = ""; }; + 81CC4000092A64D300B2BF95 /* Moveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Moveable.cpp; sourceTree = ""; }; + 81CC4001092A64D300B2BF95 /* Moveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Moveable.h; sourceTree = ""; }; + 81CC4002092A64D300B2BF95 /* Mover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Mover.cpp; sourceTree = ""; }; + 81CC4003092A64D300B2BF95 /* Mover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Mover.h; sourceTree = ""; }; + 81CC4005092A64D300B2BF95 /* CTF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CTF.cpp; sourceTree = ""; }; + 81CC4006092A64D300B2BF95 /* CTF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CTF.h; sourceTree = ""; }; + 81CC4007092A64D300B2BF95 /* GameState.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameState.cpp; sourceTree = ""; }; + 81CC4008092A64D300B2BF95 /* GameState.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameState.h; sourceTree = ""; }; + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatEvent.cpp; sourceTree = ""; }; + 81CC400B092A64D300B2BF95 /* StatEvent.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatEvent.h; sourceTree = ""; }; + 81CC400C092A64D300B2BF95 /* StatManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatManager.cpp; sourceTree = ""; }; + 81CC400D092A64D300B2BF95 /* StatManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatManager.h; sourceTree = ""; }; + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatWindow.cpp; sourceTree = ""; }; + 81CC400F092A64D300B2BF95 /* StatWindow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatWindow.h; sourceTree = ""; }; + 81CC4010092A64D300B2BF95 /* Tourney.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Tourney.cpp; sourceTree = ""; }; + 81CC4011092A64D300B2BF95 /* Tourney.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Tourney.h; sourceTree = ""; }; + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VoiceComms.cpp; sourceTree = ""; }; + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MultiplayerGame.cpp; sourceTree = ""; }; + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultiplayerGame.h; sourceTree = ""; }; + 81CC4016092A64D300B2BF95 /* Clip.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Clip.cpp; sourceTree = ""; }; + 81CC4017092A64D300B2BF95 /* Clip.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Clip.h; sourceTree = ""; }; + 81CC4018092A64D300B2BF95 /* Force.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force.cpp; sourceTree = ""; }; + 81CC4019092A64D300B2BF95 /* Force.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force.h; sourceTree = ""; }; + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Constant.cpp; sourceTree = ""; }; + 81CC401B092A64D300B2BF95 /* Force_Constant.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Constant.h; sourceTree = ""; }; + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Drag.cpp; sourceTree = ""; }; + 81CC401D092A64D300B2BF95 /* Force_Drag.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Drag.h; sourceTree = ""; }; + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Field.cpp; sourceTree = ""; }; + 81CC401F092A64D300B2BF95 /* Force_Field.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Field.h; sourceTree = ""; }; + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Spring.cpp; sourceTree = ""; }; + 81CC4021092A64D300B2BF95 /* Force_Spring.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Spring.h; sourceTree = ""; }; + 81CC4022092A64D300B2BF95 /* Physics.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics.cpp; sourceTree = ""; }; + 81CC4023092A64D300B2BF95 /* Physics.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics.h; sourceTree = ""; }; + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Actor.cpp; sourceTree = ""; }; + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Actor.h; sourceTree = ""; }; + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_AF.cpp; sourceTree = ""; }; + 81CC4027092A64D300B2BF95 /* Physics_AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_AF.h; sourceTree = ""; }; + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Base.cpp; sourceTree = ""; }; + 81CC4029092A64D300B2BF95 /* Physics_Base.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Base.h; sourceTree = ""; }; + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Monster.cpp; sourceTree = ""; }; + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Monster.h; sourceTree = ""; }; + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Parametric.cpp; sourceTree = ""; }; + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Parametric.h; sourceTree = ""; }; + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Particle.cpp; sourceTree = ""; }; + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Particle.h; sourceTree = ""; }; + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Player.cpp; sourceTree = ""; }; + 81CC4031092A64D300B2BF95 /* Physics_Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Player.h; sourceTree = ""; }; + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_RigidBody.cpp; sourceTree = ""; }; + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_RigidBody.h; sourceTree = ""; }; + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Static.cpp; sourceTree = ""; }; + 81CC4035092A64D300B2BF95 /* Physics_Static.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Static.h; sourceTree = ""; }; + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_StaticMulti.cpp; sourceTree = ""; }; + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_StaticMulti.h; sourceTree = ""; }; + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_VehicleMonster.cpp; sourceTree = ""; }; + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_VehicleMonster.h; sourceTree = ""; }; + 81CC403A092A64D300B2BF95 /* Push.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Push.cpp; sourceTree = ""; }; + 81CC403B092A64D300B2BF95 /* Push.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Push.h; sourceTree = ""; }; + 81CC403C092A64D300B2BF95 /* Playback.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Playback.cpp; sourceTree = ""; }; + 81CC403D092A64D300B2BF95 /* Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player.cpp; sourceTree = ""; }; + 81CC403E092A64D300B2BF95 /* Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Player.h; sourceTree = ""; }; + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_Cheats.cpp; sourceTree = ""; }; + 81CC4040092A64D300B2BF95 /* Player_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_States.cpp; sourceTree = ""; }; + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = PlayerView.cpp; sourceTree = ""; }; + 81CC4042092A64D300B2BF95 /* PlayerView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlayerView.h; sourceTree = ""; }; + 81CC4043092A64D300B2BF95 /* Projectile.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Projectile.cpp; sourceTree = ""; }; + 81CC4044092A64D300B2BF95 /* Projectile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Projectile.h; sourceTree = ""; }; + 81CC4045092A64D300B2BF95 /* Pvs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Pvs.cpp; sourceTree = ""; }; + 81CC4046092A64D300B2BF95 /* Pvs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pvs.h; sourceTree = ""; }; + 81CC404A092A64D300B2BF95 /* AI_public.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_public.h; sourceTree = ""; }; + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Compiler.cpp; sourceTree = ""; }; + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Compiler.h; sourceTree = ""; }; + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Interpreter.cpp; sourceTree = ""; }; + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Interpreter.h; sourceTree = ""; }; + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Program.cpp; sourceTree = ""; }; + 81CC4051092A64D300B2BF95 /* Script_Program.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Program.h; sourceTree = ""; }; + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Thread.cpp; sourceTree = ""; }; + 81CC4053092A64D300B2BF95 /* Script_Thread.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Thread.h; sourceTree = ""; }; + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptFuncUtility.cpp; sourceTree = ""; }; + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ScriptFuncUtility.h; sourceTree = ""; }; + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SecurityCamera.cpp; sourceTree = ""; }; + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SecurityCamera.h; sourceTree = ""; }; + 81CC405A092A64D300B2BF95 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = ""; }; + 81CC405B092A64D300B2BF95 /* Sound.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Sound.h; sourceTree = ""; }; + 81CC405C092A64D300B2BF95 /* spawner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = spawner.cpp; sourceTree = ""; }; + 81CC405D092A64D300B2BF95 /* spawner.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = spawner.h; sourceTree = ""; }; + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SplineMover.cpp; sourceTree = ""; }; + 81CC405F092A64D300B2BF95 /* SplineMover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SplineMover.h; sourceTree = ""; }; + 81CC4060092A64D300B2BF95 /* Target.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Target.cpp; sourceTree = ""; }; + 81CC4061092A64D300B2BF95 /* Target.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Target.h; sourceTree = ""; }; + 81CC4062092A64D300B2BF95 /* TramGate.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TramGate.cpp; sourceTree = ""; }; + 81CC4063092A64D300B2BF95 /* TramGate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TramGate.h; sourceTree = ""; }; + 81CC4064092A64D300B2BF95 /* Trigger.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Trigger.cpp; sourceTree = ""; }; + 81CC4065092A64D300B2BF95 /* Trigger.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Trigger.h; sourceTree = ""; }; + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle.cpp; sourceTree = ""; }; + 81CC4068092A64D300B2BF95 /* Vehicle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Vehicle.h; sourceTree = ""; }; + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_DropPod.cpp; sourceTree = ""; }; + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_Walker.cpp; sourceTree = ""; }; + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAnimated.cpp; sourceTree = ""; }; + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAnimated.h; sourceTree = ""; }; + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleController.cpp; sourceTree = ""; }; + 81CC406E092A64D300B2BF95 /* VehicleController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleController.h; sourceTree = ""; }; + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleDriver.cpp; sourceTree = ""; }; + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleDriver.h; sourceTree = ""; }; + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleMonster.cpp; sourceTree = ""; }; + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleMonster.h; sourceTree = ""; }; + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleParts.cpp; sourceTree = ""; }; + 81CC4074092A64D300B2BF95 /* VehicleParts.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleParts.h; sourceTree = ""; }; + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehiclePosition.cpp; sourceTree = ""; }; + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleRigid.cpp; sourceTree = ""; }; + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleRigid.h; sourceTree = ""; }; + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleSpline.cpp; sourceTree = ""; }; + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleSpline.h; sourceTree = ""; }; + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleStatic.cpp; sourceTree = ""; }; + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleStatic.h; sourceTree = ""; }; + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponBlaster.cpp; sourceTree = ""; }; + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponDarkMatterGun.cpp; sourceTree = ""; }; + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGauntlet.cpp; sourceTree = ""; }; + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGrenadeLauncher.cpp; sourceTree = ""; }; + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponHyperblaster.cpp; sourceTree = ""; }; + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponLightningGun.cpp; sourceTree = ""; }; + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponMachinegun.cpp; sourceTree = ""; }; + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNailgun.cpp; sourceTree = ""; }; + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRailgun.cpp; sourceTree = ""; }; + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRocketLauncher.cpp; sourceTree = ""; }; + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponShotgun.cpp; sourceTree = ""; }; + 81CC4088092A64D300B2BF95 /* Weapon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Weapon.cpp; sourceTree = ""; }; + 81CC4089092A64D300B2BF95 /* Weapon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Weapon.h; sourceTree = ""; }; + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WorldSpawn.cpp; sourceTree = ""; }; + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = WorldSpawn.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 81114DF9093E117D00A5C91A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* game */ = { + isa = PBXGroup; + children = ( + 81CC3F72092A64D300B2BF95 /* Source */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + 816AD2DF0981819D00CAC29C /* Game.plist */, + 8114A19C090D686B0025E084 /* idlib.xcodeproj */, + ); + name = game; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 81114DFB093E117D00A5C91A /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; + 8114A19D090D686B0025E084 /* Products */ = { + isa = PBXGroup; + children = ( + 8114A1A2090D686B0025E084 /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 81CC3F72092A64D300B2BF95 /* Source */ = { + isa = PBXGroup; + children = ( + 81CC3F73092A64D300B2BF95 /* Actor.cpp */, + 81CC3F74092A64D300B2BF95 /* Actor.h */, + 81CC3F75092A64D300B2BF95 /* AF.cpp */, + 81CC3F76092A64D300B2BF95 /* AF.h */, + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */, + 81CC3F78092A64D300B2BF95 /* AFEntity.h */, + 81CC3F79092A64D300B2BF95 /* ai */, + 81CC3FB6092A64D300B2BF95 /* anim */, + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */, + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */, + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */, + 81CC3FC0092A64D300B2BF95 /* Camera.h */, + 81CC3FC1092A64D300B2BF95 /* client */, + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */, + 81CC3FCB092A64D300B2BF95 /* Effect.h */, + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */, + 81CC3FCF092A64D300B2BF95 /* Entity.h */, + 814E1D0D097C19DD009E5F86 /* FreeView.cpp */, + 814E1D0E097C19DD009E5F86 /* FreeView.h */, + 81CC3FD3092A64D300B2BF95 /* Game.h */, + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */, + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */, + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */, + 81CC3FD7092A64D300B2BF95 /* Game_local.h */, + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */, + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */, + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */, + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */, + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */, + 81CC3FDD092A64D300B2BF95 /* gamesys */, + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */, + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */, + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */, + 81CC3FF1092A64D300B2BF95 /* Icon.h */, + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */, + 81CC3FF3092A64D300B2BF95 /* IconManager.h */, + 81CC3FF4092A64D300B2BF95 /* IK.cpp */, + 81CC3FF5092A64D300B2BF95 /* IK.h */, + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */, + 81CC3FF7092A64D300B2BF95 /* Instance.h */, + 81CC3FF8092A64D300B2BF95 /* Item.cpp */, + 81CC3FF9092A64D300B2BF95 /* Item.h */, + 81CC3FFA092A64D300B2BF95 /* Light.cpp */, + 81CC3FFB092A64D300B2BF95 /* Light.h */, + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */, + 81CC3FFD092A64D300B2BF95 /* LipSync.h */, + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */, + 81CC3FFF092A64D300B2BF95 /* Misc.h */, + 81CC4000092A64D300B2BF95 /* Moveable.cpp */, + 81CC4001092A64D300B2BF95 /* Moveable.h */, + 81CC4002092A64D300B2BF95 /* Mover.cpp */, + 81CC4003092A64D300B2BF95 /* Mover.h */, + 81CC4004092A64D300B2BF95 /* mp */, + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */, + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */, + 81CC4015092A64D300B2BF95 /* physics */, + 81CC403C092A64D300B2BF95 /* Playback.cpp */, + 81CC403D092A64D300B2BF95 /* Player.cpp */, + 81CC403E092A64D300B2BF95 /* Player.h */, + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */, + 81CC4040092A64D300B2BF95 /* Player_States.cpp */, + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */, + 81CC4042092A64D300B2BF95 /* PlayerView.h */, + 81CC4043092A64D300B2BF95 /* Projectile.cpp */, + 81CC4044092A64D300B2BF95 /* Projectile.h */, + 81CC4045092A64D300B2BF95 /* Pvs.cpp */, + 81CC4046092A64D300B2BF95 /* Pvs.h */, + 81CC4047092A64D300B2BF95 /* Resource */, + 81CC4049092A64D300B2BF95 /* rvAI */, + 81CC404B092A64D300B2BF95 /* script */, + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */, + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */, + 81CC405A092A64D300B2BF95 /* Sound.cpp */, + 81CC405B092A64D300B2BF95 /* Sound.h */, + 81CC405C092A64D300B2BF95 /* spawner.cpp */, + 81CC405D092A64D300B2BF95 /* spawner.h */, + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */, + 81CC405F092A64D300B2BF95 /* SplineMover.h */, + 81CC4060092A64D300B2BF95 /* Target.cpp */, + 81CC4061092A64D300B2BF95 /* Target.h */, + 81CC4062092A64D300B2BF95 /* TramGate.cpp */, + 81CC4063092A64D300B2BF95 /* TramGate.h */, + 81CC4064092A64D300B2BF95 /* Trigger.cpp */, + 81CC4065092A64D300B2BF95 /* Trigger.h */, + 81CC4066092A64D300B2BF95 /* vehicle */, + 81CC407C092A64D300B2BF95 /* weapon */, + 81CC4088092A64D300B2BF95 /* Weapon.cpp */, + 81CC4089092A64D300B2BF95 /* Weapon.h */, + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */, + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */, + ); + name = Source; + path = ../../../game; + sourceTree = SOURCE_ROOT; + }; + 81CC3F79092A64D300B2BF95 /* ai */ = { + isa = PBXGroup; + children = ( + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */, + 81CC3F7B092A64D300B2BF95 /* AAS.h */, + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */, + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */, + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */, + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */, + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */, + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */, + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */, + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */, + 81CC3F84092A64D300B2BF95 /* AI.cpp */, + 81CC3F85092A64D300B2BF95 /* AI.h */, + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */, + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */, + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */, + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */, + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */, + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */, + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */, + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */, + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */, + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */, + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */, + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */, + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */, + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */, + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */, + 81CC3F96092A64D300B2BF95 /* AI_Util.h */, + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */, + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */, + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */, + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */, + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */, + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */, + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */, + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */, + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */, + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */, + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */, + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */, + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */, + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */, + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */, + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */, + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */, + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */, + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */, + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */, + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */, + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */, + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */, + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */, + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */, + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */, + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */, + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */, + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */, + ); + path = ai; + sourceTree = ""; + }; + 81CC3FB6092A64D300B2BF95 /* anim */ = { + isa = PBXGroup; + children = ( + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */, + 81CC3FB8092A64D300B2BF95 /* Anim.h */, + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */, + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */, + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */, + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */, + ); + path = anim; + sourceTree = ""; + }; + 81CC3FC1092A64D300B2BF95 /* client */ = { + isa = PBXGroup; + children = ( + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */, + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */, + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */, + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */, + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */, + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */, + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */, + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */, + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */, + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */, + ); + path = client; + sourceTree = ""; + }; + 81CC3FDD092A64D300B2BF95 /* gamesys */ = { + isa = PBXGroup; + children = ( + 81CC3FDF092A64D300B2BF95 /* Class.cpp */, + 81CC3FE0092A64D300B2BF95 /* Class.h */, + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */, + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */, + 81CC3FE3092A64D300B2BF95 /* Event.cpp */, + 81CC3FE4092A64D300B2BF95 /* Event.h */, + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */, + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */, + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */, + 81CC3FE8092A64D300B2BF95 /* State.cpp */, + 81CC3FE9092A64D300B2BF95 /* State.h */, + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */, + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */, + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */, + 81CC3FED092A64D300B2BF95 /* SysCvar.h */, + ); + path = gamesys; + sourceTree = ""; + }; + 81CC4004092A64D300B2BF95 /* mp */ = { + isa = PBXGroup; + children = ( + 253A77AD0A67159100A3CFB3 /* Buying.cpp */, + 253A77AE0A67159100A3CFB3 /* Buying.h */, + 81CC4005092A64D300B2BF95 /* CTF.cpp */, + 81CC4006092A64D300B2BF95 /* CTF.h */, + 81CC4007092A64D300B2BF95 /* GameState.cpp */, + 81CC4008092A64D300B2BF95 /* GameState.h */, + 81CC4009092A64D300B2BF95 /* stats */, + 81CC4010092A64D300B2BF95 /* Tourney.cpp */, + 81CC4011092A64D300B2BF95 /* Tourney.h */, + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */, + ); + path = mp; + sourceTree = ""; + }; + 81CC4009092A64D300B2BF95 /* stats */ = { + isa = PBXGroup; + children = ( + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */, + 81CC400B092A64D300B2BF95 /* StatEvent.h */, + 81CC400C092A64D300B2BF95 /* StatManager.cpp */, + 81CC400D092A64D300B2BF95 /* StatManager.h */, + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */, + 81CC400F092A64D300B2BF95 /* StatWindow.h */, + ); + path = stats; + sourceTree = ""; + }; + 81CC4015092A64D300B2BF95 /* physics */ = { + isa = PBXGroup; + children = ( + 81CC4016092A64D300B2BF95 /* Clip.cpp */, + 81CC4017092A64D300B2BF95 /* Clip.h */, + 81CC4018092A64D300B2BF95 /* Force.cpp */, + 81CC4019092A64D300B2BF95 /* Force.h */, + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */, + 81CC401B092A64D300B2BF95 /* Force_Constant.h */, + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */, + 81CC401D092A64D300B2BF95 /* Force_Drag.h */, + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */, + 81CC401F092A64D300B2BF95 /* Force_Field.h */, + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */, + 81CC4021092A64D300B2BF95 /* Force_Spring.h */, + 81CC4022092A64D300B2BF95 /* Physics.cpp */, + 81CC4023092A64D300B2BF95 /* Physics.h */, + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */, + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */, + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */, + 81CC4027092A64D300B2BF95 /* Physics_AF.h */, + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */, + 81CC4029092A64D300B2BF95 /* Physics_Base.h */, + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */, + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */, + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */, + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */, + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */, + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */, + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */, + 81CC4031092A64D300B2BF95 /* Physics_Player.h */, + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */, + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */, + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */, + 81CC4035092A64D300B2BF95 /* Physics_Static.h */, + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */, + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */, + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */, + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */, + 81CC403A092A64D300B2BF95 /* Push.cpp */, + 81CC403B092A64D300B2BF95 /* Push.h */, + ); + path = physics; + sourceTree = ""; + }; + 81CC4047092A64D300B2BF95 /* Resource */ = { + isa = PBXGroup; + children = ( + ); + path = Resource; + sourceTree = ""; + }; + 81CC4049092A64D300B2BF95 /* rvAI */ = { + isa = PBXGroup; + children = ( + 81CC404A092A64D300B2BF95 /* AI_public.h */, + ); + path = rvAI; + sourceTree = ""; + }; + 81CC404B092A64D300B2BF95 /* script */ = { + isa = PBXGroup; + children = ( + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */, + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */, + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */, + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */, + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */, + 81CC4051092A64D300B2BF95 /* Script_Program.h */, + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */, + 81CC4053092A64D300B2BF95 /* Script_Thread.h */, + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */, + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */, + ); + path = script; + sourceTree = ""; + }; + 81CC4066092A64D300B2BF95 /* vehicle */ = { + isa = PBXGroup; + children = ( + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */, + 81CC4068092A64D300B2BF95 /* Vehicle.h */, + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */, + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */, + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */, + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */, + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */, + 81CC406E092A64D300B2BF95 /* VehicleController.h */, + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */, + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */, + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */, + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */, + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */, + 81CC4074092A64D300B2BF95 /* VehicleParts.h */, + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */, + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */, + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */, + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */, + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */, + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */, + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */, + ); + path = vehicle; + sourceTree = ""; + }; + 81CC407C092A64D300B2BF95 /* weapon */ = { + isa = PBXGroup; + children = ( + 253A77B20A6715A300A3CFB3 /* WeaponNapalmGun.cpp */, + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */, + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */, + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */, + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */, + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */, + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */, + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */, + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */, + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */, + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */, + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */, + ); + path = weapon; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 81114DFA093E117D00A5C91A /* game bundle */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */; + buildPhases = ( + 81114DF7093E117D00A5C91A /* Resources */, + 81114DF8093E117D00A5C91A /* Sources */, + 81114DF9093E117D00A5C91A /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 81114FE3093E189800A5C91A /* PBXTargetDependency */, + ); + name = "game bundle"; + productName = "game bundle"; + productReference = 81114DFB093E117D00A5C91A /* game.so.bundle */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "game" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* game */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 8114A19D090D686B0025E084 /* Products */; + ProjectRef = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + }, + ); + targets = ( + 81114DFA093E117D00A5C91A /* game bundle */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 8114A1A2090D686B0025E084 /* libidlib_pic.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libidlib_pic.a; + remoteRef = 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 81114DF7093E117D00A5C91A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 816AD2E00981819D00CAC29C /* Game.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 81114DF8093E117D00A5C91A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */, + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */, + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */, + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */, + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */, + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */, + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */, + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */, + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */, + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */, + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */, + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */, + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */, + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */, + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */, + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */, + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */, + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */, + 81114FF8093E194300A5C91A /* AF.cpp in Sources */, + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */, + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */, + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */, + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */, + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */, + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */, + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */, + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */, + 81115001093E194300A5C91A /* Effect.cpp in Sources */, + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */, + 81115003093E194300A5C91A /* Player_States.cpp in Sources */, + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */, + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */, + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */, + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */, + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */, + 81115009093E194300A5C91A /* State.cpp in Sources */, + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */, + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */, + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */, + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */, + 8111500E093E194300A5C91A /* Mover.cpp in Sources */, + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */, + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */, + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */, + 81115012093E194300A5C91A /* Push.cpp in Sources */, + 81115013093E194300A5C91A /* IconManager.cpp in Sources */, + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */, + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */, + 81115016093E194300A5C91A /* Moveable.cpp in Sources */, + 81115017093E194300A5C91A /* Clip.cpp in Sources */, + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */, + 81115019093E194300A5C91A /* Target.cpp in Sources */, + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */, + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */, + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */, + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */, + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */, + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */, + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */, + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */, + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */, + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */, + 81115024093E194300A5C91A /* Playback.cpp in Sources */, + 81115025093E194300A5C91A /* Force.cpp in Sources */, + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */, + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */, + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */, + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */, + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */, + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */, + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */, + 8111502D093E194300A5C91A /* Player.cpp in Sources */, + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */, + 8111502F093E194300A5C91A /* IK.cpp in Sources */, + 81115030093E194300A5C91A /* Projectile.cpp in Sources */, + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */, + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */, + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */, + 81115034093E194300A5C91A /* Pvs.cpp in Sources */, + 81115035093E194300A5C91A /* Light.cpp in Sources */, + 81115036093E194300A5C91A /* Entity.cpp in Sources */, + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */, + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */, + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */, + 8111503A093E194300A5C91A /* AAS.cpp in Sources */, + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */, + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */, + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */, + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */, + 81115040093E194300A5C91A /* AI.cpp in Sources */, + 81115041093E194300A5C91A /* Tourney.cpp in Sources */, + 81115042093E194300A5C91A /* TramGate.cpp in Sources */, + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */, + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */, + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */, + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */, + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */, + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */, + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */, + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */, + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */, + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */, + 8111504D093E194300A5C91A /* Event.cpp in Sources */, + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */, + 8111504F093E194300A5C91A /* CTF.cpp in Sources */, + 81115050093E194300A5C91A /* GameState.cpp in Sources */, + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */, + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */, + 81115053093E194300A5C91A /* Physics.cpp in Sources */, + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */, + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */, + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */, + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */, + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */, + 81115059093E194300A5C91A /* Class.cpp in Sources */, + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */, + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */, + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */, + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */, + 8111505E093E194300A5C91A /* Misc.cpp in Sources */, + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */, + 81115060093E194300A5C91A /* Icon.cpp in Sources */, + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */, + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */, + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */, + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */, + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */, + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */, + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */, + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */, + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */, + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */, + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */, + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */, + 8111506D093E194300A5C91A /* Anim.cpp in Sources */, + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */, + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */, + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */, + 81115071093E194300A5C91A /* spawner.cpp in Sources */, + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */, + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */, + 81115074093E194300A5C91A /* LipSync.cpp in Sources */, + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */, + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */, + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */, + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */, + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */, + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */, + 8111507B093E194300A5C91A /* Actor.cpp in Sources */, + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */, + 8111507D093E194300A5C91A /* Item.cpp in Sources */, + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */, + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */, + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */, + 81115081093E194300A5C91A /* Instance.cpp in Sources */, + 814E1D11097C19DD009E5F86 /* FreeView.cpp in Sources */, + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */, + 253A77B10A67159100A3CFB3 /* Buying.cpp in Sources */, + 253A77B40A6715A300A3CFB3 /* WeaponNapalmGun.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 81114FE3093E189800A5C91A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = idlib_pic; + targetProxy = 81114FE2093E189800A5C91A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 81114DFE093E117E00A5C91A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G4; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_CFLAGS = "-fvisibility=hidden"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Debug; + }; + 81114DFF093E117E00A5C91A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Release; + }; + 81149F3C090D5FDC0025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ONE_BYTE_BOOL = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _DEBUG, + GAME_DLL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ""; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Debug; + }; + 81149F3D090D5FDC0025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_MODEL_TUNING = G5; + GCC_ONE_BYTE_BOOL = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _FINAL, + GAME_DLL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ( + "-finline", + "-finline-limit=1000", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + "-fomit-frame-pointer", + "-fno-common", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81114DFE093E117E00A5C91A /* Debug */, + 81114DFF093E117E00A5C91A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "game" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149F3C090D5FDC0025E084 /* Debug */, + 81149F3D090D5FDC0025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/src/sys/osx/Subprojects/idlib.xcodeproj/project.pbxproj b/src/sys/osx/Subprojects/idlib.xcodeproj/project.pbxproj new file mode 100644 index 0000000..8ccd78a --- /dev/null +++ b/src/sys/osx/Subprojects/idlib.xcodeproj/project.pbxproj @@ -0,0 +1,905 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 81CC3D6F092A630E00B2BF95 /* MultifieldSort.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCA092A630E00B2BF95 /* MultifieldSort.h */; }; + 81CC3D70092A630E00B2BF95 /* AutoPtr.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCB092A630E00B2BF95 /* AutoPtr.h */; }; + 81CC3D71092A630E00B2BF95 /* Base64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CCC092A630E00B2BF95 /* Base64.cpp */; }; + 81CC3D72092A630E00B2BF95 /* Base64.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCD092A630E00B2BF95 /* Base64.h */; }; + 81CC3D73092A630E00B2BF95 /* BitMsg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CCE092A630E00B2BF95 /* BitMsg.cpp */; }; + 81CC3D74092A630E00B2BF95 /* BitMsg.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CCF092A630E00B2BF95 /* BitMsg.h */; }; + 81CC3D76092A630E00B2BF95 /* Bounds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD2092A630E00B2BF95 /* Bounds.cpp */; }; + 81CC3D77092A630E00B2BF95 /* Bounds.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD3092A630E00B2BF95 /* Bounds.h */; }; + 81CC3D78092A630E00B2BF95 /* Box.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD4092A630E00B2BF95 /* Box.cpp */; }; + 81CC3D79092A630E00B2BF95 /* Box.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD5092A630E00B2BF95 /* Box.h */; }; + 81CC3D7A092A630E00B2BF95 /* Frustum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD6092A630E00B2BF95 /* Frustum.cpp */; }; + 81CC3D7B092A630E00B2BF95 /* Frustum.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD7092A630E00B2BF95 /* Frustum.h */; }; + 81CC3D7C092A630E00B2BF95 /* Sphere.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CD8092A630E00B2BF95 /* Sphere.cpp */; }; + 81CC3D7D092A630E00B2BF95 /* Sphere.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CD9092A630E00B2BF95 /* Sphere.h */; }; + 81CC3D7E092A630E00B2BF95 /* CmdArgs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CDA092A630E00B2BF95 /* CmdArgs.cpp */; }; + 81CC3D7F092A630E00B2BF95 /* CmdArgs.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CDB092A630E00B2BF95 /* CmdArgs.h */; }; + 81CC3D80092A630E00B2BF95 /* BinSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CDD092A630E00B2BF95 /* BinSearch.h */; }; + 81CC3D81092A630E00B2BF95 /* BTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CDE092A630E00B2BF95 /* BTree.h */; }; + 81CC3D82092A630E00B2BF95 /* HashIndex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CDF092A630E00B2BF95 /* HashIndex.cpp */; }; + 81CC3D83092A630E00B2BF95 /* HashIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE0092A630E00B2BF95 /* HashIndex.h */; }; + 81CC3D84092A630E00B2BF95 /* HashTable.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE1092A630E00B2BF95 /* HashTable.h */; }; + 81CC3D85092A630E00B2BF95 /* Hierarchy.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE2092A630E00B2BF95 /* Hierarchy.h */; }; + 81CC3D86092A630E00B2BF95 /* LinkList.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE3092A630E00B2BF95 /* LinkList.h */; }; + 81CC3D87092A630E00B2BF95 /* List.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE4092A630E00B2BF95 /* List.h */; }; + 81CC3D88092A630E00B2BF95 /* ListGame.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE5092A630E00B2BF95 /* ListGame.h */; }; + 81CC3D89092A630E00B2BF95 /* Pair.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE6092A630E00B2BF95 /* Pair.h */; }; + 81CC3D8A092A630E00B2BF95 /* PlaneSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE7092A630E00B2BF95 /* PlaneSet.h */; }; + 81CC3D8B092A630E00B2BF95 /* Queue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE8092A630E00B2BF95 /* Queue.h */; }; + 81CC3D8C092A630E00B2BF95 /* rvBlockPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CE9092A630E00B2BF95 /* rvBlockPool.h */; }; + 81CC3D8D092A630E00B2BF95 /* Stack.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEA092A630E00B2BF95 /* Stack.h */; }; + 81CC3D8E092A630E00B2BF95 /* StaticList.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEB092A630E00B2BF95 /* StaticList.h */; }; + 81CC3D8F092A630E00B2BF95 /* StrList.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEC092A630E00B2BF95 /* StrList.h */; }; + 81CC3D90092A630E00B2BF95 /* StrPool.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CED092A630E00B2BF95 /* StrPool.h */; }; + 81CC3D91092A630E00B2BF95 /* VectorSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CEE092A630E00B2BF95 /* VectorSet.h */; }; + 81CC3D92092A630E00B2BF95 /* Dict.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CEF092A630E00B2BF95 /* Dict.cpp */; }; + 81CC3D93092A630E00B2BF95 /* Dict.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF0092A630E00B2BF95 /* Dict.h */; }; + 81CC3D95092A630E00B2BF95 /* DrawVert.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF3092A630E00B2BF95 /* DrawVert.h */; }; + 81CC3D96092A630E00B2BF95 /* JointTransform.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CF4092A630E00B2BF95 /* JointTransform.cpp */; }; + 81CC3D97092A630E00B2BF95 /* JointTransform.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF5092A630E00B2BF95 /* JointTransform.h */; }; + 81CC3D98092A630E00B2BF95 /* rvVertex.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF6092A630E00B2BF95 /* rvVertex.h */; }; + 81CC3D99092A630E00B2BF95 /* Surface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CF7092A630E00B2BF95 /* Surface.cpp */; }; + 81CC3D9A092A630E00B2BF95 /* Surface.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CF8092A630E00B2BF95 /* Surface.h */; }; + 81CC3D9B092A630E00B2BF95 /* Surface_Patch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CF9092A630E00B2BF95 /* Surface_Patch.cpp */; }; + 81CC3D9C092A630E00B2BF95 /* Surface_Patch.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CFA092A630E00B2BF95 /* Surface_Patch.h */; }; + 81CC3D9D092A630E00B2BF95 /* Surface_Polytope.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CFB092A630E00B2BF95 /* Surface_Polytope.cpp */; }; + 81CC3D9E092A630E00B2BF95 /* Surface_Polytope.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CFC092A630E00B2BF95 /* Surface_Polytope.h */; }; + 81CC3D9F092A630E00B2BF95 /* Surface_SweptSpline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CFD092A630E00B2BF95 /* Surface_SweptSpline.cpp */; }; + 81CC3DA0092A630E00B2BF95 /* Surface_SweptSpline.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3CFE092A630E00B2BF95 /* Surface_SweptSpline.h */; }; + 81CC3DA1092A630E00B2BF95 /* TraceModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3CFF092A630E00B2BF95 /* TraceModel.cpp */; }; + 81CC3DA2092A630E00B2BF95 /* TraceModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D00092A630E00B2BF95 /* TraceModel.h */; }; + 81CC3DA3092A630E00B2BF95 /* Winding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D01092A630E00B2BF95 /* Winding.cpp */; }; + 81CC3DA4092A630E00B2BF95 /* Winding.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D02092A630E00B2BF95 /* Winding.h */; }; + 81CC3DA5092A630E00B2BF95 /* Winding2D.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D03092A630E00B2BF95 /* Winding2D.cpp */; }; + 81CC3DA6092A630E00B2BF95 /* Winding2D.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D04092A630E00B2BF95 /* Winding2D.h */; }; + 81CC3DA7092A630E00B2BF95 /* CRC16.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D06092A630E00B2BF95 /* CRC16.cpp */; }; + 81CC3DA8092A630E00B2BF95 /* CRC16.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D07092A630E00B2BF95 /* CRC16.h */; }; + 81CC3DA9092A630E00B2BF95 /* CRC32.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D08092A630E00B2BF95 /* CRC32.cpp */; }; + 81CC3DAA092A630E00B2BF95 /* CRC32.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D09092A630E00B2BF95 /* CRC32.h */; }; + 81CC3DAB092A630E00B2BF95 /* CRC8.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D0A092A630E00B2BF95 /* CRC8.cpp */; }; + 81CC3DAC092A630E00B2BF95 /* CRC8.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D0B092A630E00B2BF95 /* CRC8.h */; }; + 81CC3DAD092A630E00B2BF95 /* Honeyman.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D0C092A630E00B2BF95 /* Honeyman.cpp */; }; + 81CC3DAE092A630E00B2BF95 /* Honeyman.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D0D092A630E00B2BF95 /* Honeyman.h */; }; + 81CC3DAF092A630E00B2BF95 /* MD4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D0E092A630E00B2BF95 /* MD4.cpp */; }; + 81CC3DB0092A630E00B2BF95 /* MD4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D0F092A630E00B2BF95 /* MD4.h */; }; + 81CC3DB1092A630E00B2BF95 /* MD5.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D10092A630E00B2BF95 /* MD5.cpp */; }; + 81CC3DB2092A630E00B2BF95 /* MD5.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D11092A630E00B2BF95 /* MD5.h */; }; + 81CC3DB3092A630E00B2BF95 /* Heap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D12092A630E00B2BF95 /* Heap.cpp */; }; + 81CC3DB4092A630E00B2BF95 /* Heap.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D13092A630E00B2BF95 /* Heap.h */; }; + 81CC3DB5092A630E00B2BF95 /* LangDict.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D14092A630E00B2BF95 /* LangDict.cpp */; }; + 81CC3DB6092A630E00B2BF95 /* LangDict.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D15092A630E00B2BF95 /* LangDict.h */; }; + 81CC3DB7092A630E00B2BF95 /* Lexer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D16092A630E00B2BF95 /* Lexer.cpp */; }; + 81CC3DB8092A630E00B2BF95 /* Lexer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D17092A630E00B2BF95 /* Lexer.h */; }; + 81CC3DB9092A630E00B2BF95 /* LexerFactory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D18092A630E00B2BF95 /* LexerFactory.cpp */; }; + 81CC3DBA092A630E00B2BF95 /* LexerFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D19092A630E00B2BF95 /* LexerFactory.h */; }; + 81CC3DBB092A630E00B2BF95 /* Lib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D1A092A630E00B2BF95 /* Lib.cpp */; }; + 81CC3DBC092A630E00B2BF95 /* Lib.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D1B092A630E00B2BF95 /* Lib.h */; }; + 81CC3DBD092A630E00B2BF95 /* mapfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D1C092A630E00B2BF95 /* mapfile.cpp */; }; + 81CC3DBE092A630E00B2BF95 /* MapFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D1D092A630E00B2BF95 /* MapFile.h */; }; + 81CC3DBF092A630E00B2BF95 /* Angles.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D1F092A630E00B2BF95 /* Angles.cpp */; }; + 81CC3DC0092A630E00B2BF95 /* Angles.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D20092A630E00B2BF95 /* Angles.h */; }; + 81CC3DC1092A630E00B2BF95 /* Complex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D21092A630E00B2BF95 /* Complex.cpp */; }; + 81CC3DC2092A630E00B2BF95 /* Complex.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D22092A630E00B2BF95 /* Complex.h */; }; + 81CC3DC3092A630E00B2BF95 /* Curve.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D23092A630E00B2BF95 /* Curve.h */; }; + 81CC3DC4092A630E00B2BF95 /* Extrapolate.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D24092A630E00B2BF95 /* Extrapolate.h */; }; + 81CC3DC5092A630E00B2BF95 /* FFT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D25092A630E00B2BF95 /* FFT.cpp */; }; + 81CC3DC6092A630E00B2BF95 /* FFT.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D26092A630E00B2BF95 /* FFT.h */; }; + 81CC3DC7092A630E00B2BF95 /* Interpolate.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D27092A630E00B2BF95 /* Interpolate.h */; }; + 81CC3DC8092A630E00B2BF95 /* Lcp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D28092A630E00B2BF95 /* Lcp.cpp */; }; + 81CC3DC9092A630E00B2BF95 /* Lcp.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D29092A630E00B2BF95 /* Lcp.h */; }; + 81CC3DCB092A630E00B2BF95 /* Mat3x4.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D2B092A630E00B2BF95 /* Mat3x4.h */; }; + 81CC3DCC092A630E00B2BF95 /* Math.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D2C092A630E00B2BF95 /* Math.cpp */; }; + 81CC3DCE092A630E00B2BF95 /* Matrix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D2E092A630E00B2BF95 /* Matrix.cpp */; }; + 81CC3DCF092A630E00B2BF95 /* Matrix.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D2F092A630E00B2BF95 /* Matrix.h */; }; + 81CC3DD0092A630E00B2BF95 /* Ode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D30092A630E00B2BF95 /* Ode.cpp */; }; + 81CC3DD1092A630E00B2BF95 /* Ode.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D31092A630E00B2BF95 /* Ode.h */; }; + 81CC3DD2092A630E00B2BF95 /* Plane.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D32092A630E00B2BF95 /* Plane.cpp */; }; + 81CC3DD3092A630E00B2BF95 /* Plane.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D33092A630E00B2BF95 /* Plane.h */; }; + 81CC3DD4092A630E00B2BF95 /* Pluecker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D34092A630E00B2BF95 /* Pluecker.cpp */; }; + 81CC3DD5092A630E00B2BF95 /* Pluecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D35092A630E00B2BF95 /* Pluecker.h */; }; + 81CC3DD6092A630E00B2BF95 /* Polynomial.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D36092A630E00B2BF95 /* Polynomial.cpp */; }; + 81CC3DD7092A630E00B2BF95 /* Polynomial.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D37092A630E00B2BF95 /* Polynomial.h */; }; + 81CC3DD8092A630E00B2BF95 /* Quat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D38092A630E00B2BF95 /* Quat.cpp */; }; + 81CC3DD9092A630E00B2BF95 /* Quat.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D39092A630E00B2BF95 /* Quat.h */; }; + 81CC3DDA092A630E00B2BF95 /* Radians.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D3A092A630E00B2BF95 /* Radians.cpp */; }; + 81CC3DDB092A630E00B2BF95 /* Radians.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D3B092A630E00B2BF95 /* Radians.h */; }; + 81CC3DDC092A630E00B2BF95 /* Random.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D3C092A630E00B2BF95 /* Random.h */; }; + 81CC3DDD092A630E00B2BF95 /* Rotation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D3D092A630E00B2BF95 /* Rotation.cpp */; }; + 81CC3DDE092A630E00B2BF95 /* Rotation.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D3E092A630E00B2BF95 /* Rotation.h */; }; + 81CC3DDF092A630E00B2BF95 /* Simd.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D3F092A630E00B2BF95 /* Simd.cpp */; }; + 81CC3DE0092A630E00B2BF95 /* Simd.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D40092A630E00B2BF95 /* Simd.h */; }; + 81CC3DE1092A630E00B2BF95 /* Simd_3DNow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D41092A630E00B2BF95 /* Simd_3DNow.cpp */; }; + 81CC3DE2092A630E00B2BF95 /* Simd_3DNow.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D42092A630E00B2BF95 /* Simd_3DNow.h */; }; + 81CC3DE3092A630E00B2BF95 /* Simd_AltiVec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D43092A630E00B2BF95 /* Simd_AltiVec.cpp */; }; + 81CC3DE4092A630E00B2BF95 /* Simd_AltiVec.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D44092A630E00B2BF95 /* Simd_AltiVec.h */; }; + 81CC3DE5092A630E00B2BF95 /* Simd_generic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D45092A630E00B2BF95 /* Simd_generic.cpp */; }; + 81CC3DE6092A630E00B2BF95 /* Simd_generic.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D46092A630E00B2BF95 /* Simd_generic.h */; }; + 81CC3DE7092A630E00B2BF95 /* Simd_InstructionMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D47092A630E00B2BF95 /* Simd_InstructionMacros.h */; }; + 81CC3DE8092A630E00B2BF95 /* Simd_MMX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D48092A630E00B2BF95 /* Simd_MMX.cpp */; }; + 81CC3DE9092A630E00B2BF95 /* Simd_MMX.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D49092A630E00B2BF95 /* Simd_MMX.h */; }; + 81CC3DEA092A630E00B2BF95 /* Simd_SSE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D4A092A630E00B2BF95 /* Simd_SSE.cpp */; }; + 81CC3DEB092A630E00B2BF95 /* Simd_SSE.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D4B092A630E00B2BF95 /* Simd_SSE.h */; }; + 81CC3DEC092A630E00B2BF95 /* Simd_SSE2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D4C092A630E00B2BF95 /* Simd_SSE2.cpp */; }; + 81CC3DED092A630E00B2BF95 /* Simd_SSE2.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D4D092A630E00B2BF95 /* Simd_SSE2.h */; }; + 81CC3DEE092A630E00B2BF95 /* Simd_SSE3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D4E092A630E00B2BF95 /* Simd_SSE3.cpp */; }; + 81CC3DEF092A630E00B2BF95 /* Simd_SSE3.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D4F092A630E00B2BF95 /* Simd_SSE3.h */; }; + 81CC3DF2092A630E00B2BF95 /* Vector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D52092A630E00B2BF95 /* Vector.cpp */; }; + 81CC3DF3092A630E00B2BF95 /* Vector.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D53092A630E00B2BF95 /* Vector.h */; }; + 81CC3DF4092A630E00B2BF95 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D54092A630E00B2BF95 /* Parser.cpp */; }; + 81CC3DF5092A630E00B2BF95 /* Parser.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D55092A630E00B2BF95 /* Parser.h */; }; + 81CC3DF6092A630E00B2BF95 /* precompiled.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D56092A630E00B2BF95 /* precompiled.h */; }; + 81CC3DF7092A630E00B2BF95 /* rvHeap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D57092A630E00B2BF95 /* rvHeap.cpp */; }; + 81CC3DF8092A630E00B2BF95 /* rvHeap.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D58092A630E00B2BF95 /* rvHeap.h */; }; + 81CC3DF9092A630E00B2BF95 /* rvHeapArena.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D59092A630E00B2BF95 /* rvHeapArena.cpp */; }; + 81CC3DFA092A630E00B2BF95 /* rvHeapArena.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D5A092A630E00B2BF95 /* rvHeapArena.h */; }; + 81CC3DFB092A630E00B2BF95 /* rvMemSys.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D5B092A630E00B2BF95 /* rvMemSys.cpp */; }; + 81CC3DFC092A630E00B2BF95 /* rvMemSys.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D5C092A630E00B2BF95 /* rvMemSys.h */; }; + 81CC3DFD092A630E00B2BF95 /* Str.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D5D092A630E00B2BF95 /* Str.cpp */; }; + 81CC3DFE092A630E00B2BF95 /* Str.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D5E092A630E00B2BF95 /* Str.h */; }; + 81CC3DFF092A630E00B2BF95 /* TextCompiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D5F092A630E00B2BF95 /* TextCompiler.cpp */; }; + 81CC3E00092A630E00B2BF95 /* TextCompiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D60092A630E00B2BF95 /* TextCompiler.h */; }; + 81CC3E03092A630E00B2BF95 /* AutoCrit.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D64092A630E00B2BF95 /* AutoCrit.h */; }; + 81CC3E08092A630E00B2BF95 /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D69092A630E00B2BF95 /* Timer.cpp */; }; + 81CC3E09092A630E00B2BF95 /* Timer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D6A092A630E00B2BF95 /* Timer.h */; }; + 81CC3E0A092A630E00B2BF95 /* TimingCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D6B092A630E00B2BF95 /* TimingCollection.cpp */; }; + 81CC3E0B092A630E00B2BF95 /* TimingCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D6C092A630E00B2BF95 /* TimingCollection.h */; }; + 81CC3E0C092A630E00B2BF95 /* Token.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3D6D092A630E00B2BF95 /* Token.cpp */; }; + 81CC3E0D092A630E00B2BF95 /* Token.h in Headers */ = {isa = PBXBuildFile; fileRef = 81CC3D6E092A630E00B2BF95 /* Token.h */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 81CC3CCA092A630E00B2BF95 /* MultifieldSort.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultifieldSort.h; sourceTree = ""; }; + 81CC3CCB092A630E00B2BF95 /* AutoPtr.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AutoPtr.h; sourceTree = ""; }; + 81CC3CCC092A630E00B2BF95 /* Base64.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Base64.cpp; sourceTree = ""; }; + 81CC3CCD092A630E00B2BF95 /* Base64.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Base64.h; sourceTree = ""; }; + 81CC3CCE092A630E00B2BF95 /* BitMsg.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = BitMsg.cpp; sourceTree = ""; }; + 81CC3CCF092A630E00B2BF95 /* BitMsg.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BitMsg.h; sourceTree = ""; }; + 81CC3CD2092A630E00B2BF95 /* Bounds.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Bounds.cpp; sourceTree = ""; }; + 81CC3CD3092A630E00B2BF95 /* Bounds.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Bounds.h; sourceTree = ""; }; + 81CC3CD4092A630E00B2BF95 /* Box.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Box.cpp; sourceTree = ""; }; + 81CC3CD5092A630E00B2BF95 /* Box.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Box.h; sourceTree = ""; }; + 81CC3CD6092A630E00B2BF95 /* Frustum.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Frustum.cpp; sourceTree = ""; }; + 81CC3CD7092A630E00B2BF95 /* Frustum.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Frustum.h; sourceTree = ""; }; + 81CC3CD8092A630E00B2BF95 /* Sphere.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Sphere.cpp; sourceTree = ""; }; + 81CC3CD9092A630E00B2BF95 /* Sphere.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Sphere.h; sourceTree = ""; }; + 81CC3CDA092A630E00B2BF95 /* CmdArgs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CmdArgs.cpp; sourceTree = ""; }; + 81CC3CDB092A630E00B2BF95 /* CmdArgs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CmdArgs.h; sourceTree = ""; }; + 81CC3CDD092A630E00B2BF95 /* BinSearch.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BinSearch.h; sourceTree = ""; }; + 81CC3CDE092A630E00B2BF95 /* BTree.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BTree.h; sourceTree = ""; }; + 81CC3CDF092A630E00B2BF95 /* HashIndex.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = HashIndex.cpp; sourceTree = ""; }; + 81CC3CE0092A630E00B2BF95 /* HashIndex.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HashIndex.h; sourceTree = ""; }; + 81CC3CE1092A630E00B2BF95 /* HashTable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HashTable.h; sourceTree = ""; }; + 81CC3CE2092A630E00B2BF95 /* Hierarchy.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Hierarchy.h; sourceTree = ""; }; + 81CC3CE3092A630E00B2BF95 /* LinkList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LinkList.h; sourceTree = ""; }; + 81CC3CE4092A630E00B2BF95 /* List.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = List.h; sourceTree = ""; }; + 81CC3CE5092A630E00B2BF95 /* ListGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ListGame.h; sourceTree = ""; }; + 81CC3CE6092A630E00B2BF95 /* Pair.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pair.h; sourceTree = ""; }; + 81CC3CE7092A630E00B2BF95 /* PlaneSet.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlaneSet.h; sourceTree = ""; }; + 81CC3CE8092A630E00B2BF95 /* Queue.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Queue.h; sourceTree = ""; }; + 81CC3CE9092A630E00B2BF95 /* rvBlockPool.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvBlockPool.h; sourceTree = ""; }; + 81CC3CEA092A630E00B2BF95 /* Stack.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Stack.h; sourceTree = ""; }; + 81CC3CEB092A630E00B2BF95 /* StaticList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StaticList.h; sourceTree = ""; }; + 81CC3CEC092A630E00B2BF95 /* StrList.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StrList.h; sourceTree = ""; }; + 81CC3CED092A630E00B2BF95 /* StrPool.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StrPool.h; sourceTree = ""; }; + 81CC3CEE092A630E00B2BF95 /* VectorSet.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VectorSet.h; sourceTree = ""; }; + 81CC3CEF092A630E00B2BF95 /* Dict.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Dict.cpp; sourceTree = ""; }; + 81CC3CF0092A630E00B2BF95 /* Dict.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Dict.h; sourceTree = ""; }; + 81CC3CF3092A630E00B2BF95 /* DrawVert.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DrawVert.h; sourceTree = ""; }; + 81CC3CF4092A630E00B2BF95 /* JointTransform.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = JointTransform.cpp; sourceTree = ""; }; + 81CC3CF5092A630E00B2BF95 /* JointTransform.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = JointTransform.h; sourceTree = ""; }; + 81CC3CF6092A630E00B2BF95 /* rvVertex.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvVertex.h; sourceTree = ""; }; + 81CC3CF7092A630E00B2BF95 /* Surface.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface.cpp; sourceTree = ""; }; + 81CC3CF8092A630E00B2BF95 /* Surface.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface.h; sourceTree = ""; }; + 81CC3CF9092A630E00B2BF95 /* Surface_Patch.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface_Patch.cpp; sourceTree = ""; }; + 81CC3CFA092A630E00B2BF95 /* Surface_Patch.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface_Patch.h; sourceTree = ""; }; + 81CC3CFB092A630E00B2BF95 /* Surface_Polytope.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface_Polytope.cpp; sourceTree = ""; }; + 81CC3CFC092A630E00B2BF95 /* Surface_Polytope.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface_Polytope.h; sourceTree = ""; }; + 81CC3CFD092A630E00B2BF95 /* Surface_SweptSpline.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Surface_SweptSpline.cpp; sourceTree = ""; }; + 81CC3CFE092A630E00B2BF95 /* Surface_SweptSpline.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Surface_SweptSpline.h; sourceTree = ""; }; + 81CC3CFF092A630E00B2BF95 /* TraceModel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TraceModel.cpp; sourceTree = ""; }; + 81CC3D00092A630E00B2BF95 /* TraceModel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TraceModel.h; sourceTree = ""; }; + 81CC3D01092A630E00B2BF95 /* Winding.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Winding.cpp; sourceTree = ""; }; + 81CC3D02092A630E00B2BF95 /* Winding.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Winding.h; sourceTree = ""; }; + 81CC3D03092A630E00B2BF95 /* Winding2D.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Winding2D.cpp; sourceTree = ""; }; + 81CC3D04092A630E00B2BF95 /* Winding2D.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Winding2D.h; sourceTree = ""; }; + 81CC3D06092A630E00B2BF95 /* CRC16.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CRC16.cpp; sourceTree = ""; }; + 81CC3D07092A630E00B2BF95 /* CRC16.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CRC16.h; sourceTree = ""; }; + 81CC3D08092A630E00B2BF95 /* CRC32.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CRC32.cpp; sourceTree = ""; }; + 81CC3D09092A630E00B2BF95 /* CRC32.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CRC32.h; sourceTree = ""; }; + 81CC3D0A092A630E00B2BF95 /* CRC8.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CRC8.cpp; sourceTree = ""; }; + 81CC3D0B092A630E00B2BF95 /* CRC8.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CRC8.h; sourceTree = ""; }; + 81CC3D0C092A630E00B2BF95 /* Honeyman.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Honeyman.cpp; sourceTree = ""; }; + 81CC3D0D092A630E00B2BF95 /* Honeyman.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Honeyman.h; sourceTree = ""; }; + 81CC3D0E092A630E00B2BF95 /* MD4.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MD4.cpp; sourceTree = ""; }; + 81CC3D0F092A630E00B2BF95 /* MD4.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MD4.h; sourceTree = ""; }; + 81CC3D10092A630E00B2BF95 /* MD5.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MD5.cpp; sourceTree = ""; }; + 81CC3D11092A630E00B2BF95 /* MD5.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MD5.h; sourceTree = ""; }; + 81CC3D12092A630E00B2BF95 /* Heap.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Heap.cpp; sourceTree = ""; }; + 81CC3D13092A630E00B2BF95 /* Heap.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Heap.h; sourceTree = ""; }; + 81CC3D14092A630E00B2BF95 /* LangDict.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LangDict.cpp; sourceTree = ""; }; + 81CC3D15092A630E00B2BF95 /* LangDict.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LangDict.h; sourceTree = ""; }; + 81CC3D16092A630E00B2BF95 /* Lexer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lexer.cpp; sourceTree = ""; }; + 81CC3D17092A630E00B2BF95 /* Lexer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Lexer.h; sourceTree = ""; }; + 81CC3D18092A630E00B2BF95 /* LexerFactory.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LexerFactory.cpp; sourceTree = ""; }; + 81CC3D19092A630E00B2BF95 /* LexerFactory.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LexerFactory.h; sourceTree = ""; }; + 81CC3D1A092A630E00B2BF95 /* Lib.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lib.cpp; sourceTree = ""; }; + 81CC3D1B092A630E00B2BF95 /* Lib.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Lib.h; sourceTree = ""; }; + 81CC3D1C092A630E00B2BF95 /* mapfile.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = mapfile.cpp; sourceTree = ""; }; + 81CC3D1D092A630E00B2BF95 /* MapFile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MapFile.h; sourceTree = ""; }; + 81CC3D1F092A630E00B2BF95 /* Angles.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Angles.cpp; sourceTree = ""; }; + 81CC3D20092A630E00B2BF95 /* Angles.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Angles.h; sourceTree = ""; }; + 81CC3D21092A630E00B2BF95 /* Complex.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Complex.cpp; sourceTree = ""; }; + 81CC3D22092A630E00B2BF95 /* Complex.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Complex.h; sourceTree = ""; }; + 81CC3D23092A630E00B2BF95 /* Curve.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Curve.h; sourceTree = ""; }; + 81CC3D24092A630E00B2BF95 /* Extrapolate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Extrapolate.h; sourceTree = ""; }; + 81CC3D25092A630E00B2BF95 /* FFT.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FFT.cpp; sourceTree = ""; }; + 81CC3D26092A630E00B2BF95 /* FFT.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FFT.h; sourceTree = ""; }; + 81CC3D27092A630E00B2BF95 /* Interpolate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Interpolate.h; sourceTree = ""; }; + 81CC3D28092A630E00B2BF95 /* Lcp.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lcp.cpp; sourceTree = ""; }; + 81CC3D29092A630E00B2BF95 /* Lcp.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Lcp.h; sourceTree = ""; }; + 81CC3D2B092A630E00B2BF95 /* Mat3x4.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Mat3x4.h; sourceTree = ""; }; + 81CC3D2C092A630E00B2BF95 /* Math.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Math.cpp; sourceTree = ""; }; + 81CC3D2E092A630E00B2BF95 /* Matrix.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Matrix.cpp; sourceTree = ""; }; + 81CC3D2F092A630E00B2BF95 /* Matrix.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Matrix.h; sourceTree = ""; }; + 81CC3D30092A630E00B2BF95 /* Ode.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Ode.cpp; sourceTree = ""; }; + 81CC3D31092A630E00B2BF95 /* Ode.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Ode.h; sourceTree = ""; }; + 81CC3D32092A630E00B2BF95 /* Plane.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Plane.cpp; sourceTree = ""; }; + 81CC3D33092A630E00B2BF95 /* Plane.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Plane.h; sourceTree = ""; }; + 81CC3D34092A630E00B2BF95 /* Pluecker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Pluecker.cpp; sourceTree = ""; }; + 81CC3D35092A630E00B2BF95 /* Pluecker.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pluecker.h; sourceTree = ""; }; + 81CC3D36092A630E00B2BF95 /* Polynomial.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Polynomial.cpp; sourceTree = ""; }; + 81CC3D37092A630E00B2BF95 /* Polynomial.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Polynomial.h; sourceTree = ""; }; + 81CC3D38092A630E00B2BF95 /* Quat.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Quat.cpp; sourceTree = ""; }; + 81CC3D39092A630E00B2BF95 /* Quat.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Quat.h; sourceTree = ""; }; + 81CC3D3A092A630E00B2BF95 /* Radians.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Radians.cpp; sourceTree = ""; }; + 81CC3D3B092A630E00B2BF95 /* Radians.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Radians.h; sourceTree = ""; }; + 81CC3D3C092A630E00B2BF95 /* Random.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Random.h; sourceTree = ""; }; + 81CC3D3D092A630E00B2BF95 /* Rotation.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Rotation.cpp; sourceTree = ""; }; + 81CC3D3E092A630E00B2BF95 /* Rotation.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Rotation.h; sourceTree = ""; }; + 81CC3D3F092A630E00B2BF95 /* Simd.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd.cpp; sourceTree = ""; }; + 81CC3D40092A630E00B2BF95 /* Simd.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd.h; sourceTree = ""; }; + 81CC3D41092A630E00B2BF95 /* Simd_3DNow.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_3DNow.cpp; sourceTree = ""; }; + 81CC3D42092A630E00B2BF95 /* Simd_3DNow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_3DNow.h; sourceTree = ""; }; + 81CC3D43092A630E00B2BF95 /* Simd_AltiVec.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_AltiVec.cpp; sourceTree = ""; }; + 81CC3D44092A630E00B2BF95 /* Simd_AltiVec.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_AltiVec.h; sourceTree = ""; }; + 81CC3D45092A630E00B2BF95 /* Simd_generic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_generic.cpp; sourceTree = ""; }; + 81CC3D46092A630E00B2BF95 /* Simd_generic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_generic.h; sourceTree = ""; }; + 81CC3D47092A630E00B2BF95 /* Simd_InstructionMacros.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_InstructionMacros.h; sourceTree = ""; }; + 81CC3D48092A630E00B2BF95 /* Simd_MMX.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_MMX.cpp; sourceTree = ""; }; + 81CC3D49092A630E00B2BF95 /* Simd_MMX.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_MMX.h; sourceTree = ""; }; + 81CC3D4A092A630E00B2BF95 /* Simd_SSE.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_SSE.cpp; sourceTree = ""; }; + 81CC3D4B092A630E00B2BF95 /* Simd_SSE.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_SSE.h; sourceTree = ""; }; + 81CC3D4C092A630E00B2BF95 /* Simd_SSE2.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_SSE2.cpp; sourceTree = ""; }; + 81CC3D4D092A630E00B2BF95 /* Simd_SSE2.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_SSE2.h; sourceTree = ""; }; + 81CC3D4E092A630E00B2BF95 /* Simd_SSE3.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Simd_SSE3.cpp; sourceTree = ""; }; + 81CC3D4F092A630E00B2BF95 /* Simd_SSE3.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Simd_SSE3.h; sourceTree = ""; }; + 81CC3D52092A630E00B2BF95 /* Vector.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vector.cpp; sourceTree = ""; }; + 81CC3D53092A630E00B2BF95 /* Vector.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Vector.h; sourceTree = ""; }; + 81CC3D54092A630E00B2BF95 /* Parser.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Parser.cpp; sourceTree = ""; }; + 81CC3D55092A630E00B2BF95 /* Parser.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Parser.h; sourceTree = ""; }; + 81CC3D56092A630E00B2BF95 /* precompiled.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = precompiled.h; sourceTree = ""; }; + 81CC3D57092A630E00B2BF95 /* rvHeap.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = rvHeap.cpp; sourceTree = ""; }; + 81CC3D58092A630E00B2BF95 /* rvHeap.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvHeap.h; sourceTree = ""; }; + 81CC3D59092A630E00B2BF95 /* rvHeapArena.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = rvHeapArena.cpp; sourceTree = ""; }; + 81CC3D5A092A630E00B2BF95 /* rvHeapArena.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvHeapArena.h; sourceTree = ""; }; + 81CC3D5B092A630E00B2BF95 /* rvMemSys.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = rvMemSys.cpp; sourceTree = ""; }; + 81CC3D5C092A630E00B2BF95 /* rvMemSys.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = rvMemSys.h; sourceTree = ""; }; + 81CC3D5D092A630E00B2BF95 /* Str.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Str.cpp; sourceTree = ""; }; + 81CC3D5E092A630E00B2BF95 /* Str.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Str.h; sourceTree = ""; }; + 81CC3D5F092A630E00B2BF95 /* TextCompiler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TextCompiler.cpp; sourceTree = ""; }; + 81CC3D60092A630E00B2BF95 /* TextCompiler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TextCompiler.h; sourceTree = ""; }; + 81CC3D64092A630E00B2BF95 /* AutoCrit.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AutoCrit.h; sourceTree = ""; }; + 81CC3D69092A630E00B2BF95 /* Timer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Timer.cpp; sourceTree = ""; }; + 81CC3D6A092A630E00B2BF95 /* Timer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Timer.h; sourceTree = ""; }; + 81CC3D6B092A630E00B2BF95 /* TimingCollection.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TimingCollection.cpp; sourceTree = ""; }; + 81CC3D6C092A630E00B2BF95 /* TimingCollection.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TimingCollection.h; sourceTree = ""; }; + 81CC3D6D092A630E00B2BF95 /* Token.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Token.cpp; sourceTree = ""; }; + 81CC3D6E092A630E00B2BF95 /* Token.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Token.h; sourceTree = ""; }; + D2AAC046055464E500DB518D /* libidlib_pic.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libidlib_pic.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D289987405E68DCB004EDB86 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* idlib */ = { + isa = PBXGroup; + children = ( + 81CC3CC8092A630E00B2BF95 /* Source */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = idlib; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC046055464E500DB518D /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 81CC3CC8092A630E00B2BF95 /* Source */ = { + isa = PBXGroup; + children = ( + 81CC3CC9092A630E00B2BF95 /* algorithms */, + 81CC3CCB092A630E00B2BF95 /* AutoPtr.h */, + 81CC3CCC092A630E00B2BF95 /* Base64.cpp */, + 81CC3CCD092A630E00B2BF95 /* Base64.h */, + 81CC3CCE092A630E00B2BF95 /* BitMsg.cpp */, + 81CC3CCF092A630E00B2BF95 /* BitMsg.h */, + 81CC3CD1092A630E00B2BF95 /* bv */, + 81CC3CDA092A630E00B2BF95 /* CmdArgs.cpp */, + 81CC3CDB092A630E00B2BF95 /* CmdArgs.h */, + 81CC3CDC092A630E00B2BF95 /* containers */, + 81CC3CEF092A630E00B2BF95 /* Dict.cpp */, + 81CC3CF0092A630E00B2BF95 /* Dict.h */, + 81CC3CF1092A630E00B2BF95 /* geometry */, + 81CC3D05092A630E00B2BF95 /* hashing */, + 81CC3D12092A630E00B2BF95 /* Heap.cpp */, + 81CC3D13092A630E00B2BF95 /* Heap.h */, + 81CC3D14092A630E00B2BF95 /* LangDict.cpp */, + 81CC3D15092A630E00B2BF95 /* LangDict.h */, + 81CC3D16092A630E00B2BF95 /* Lexer.cpp */, + 81CC3D17092A630E00B2BF95 /* Lexer.h */, + 81CC3D18092A630E00B2BF95 /* LexerFactory.cpp */, + 81CC3D19092A630E00B2BF95 /* LexerFactory.h */, + 81CC3D1A092A630E00B2BF95 /* Lib.cpp */, + 81CC3D1B092A630E00B2BF95 /* Lib.h */, + 81CC3D1C092A630E00B2BF95 /* mapfile.cpp */, + 81CC3D1D092A630E00B2BF95 /* MapFile.h */, + 81CC3D1E092A630E00B2BF95 /* math */, + 81CC3D54092A630E00B2BF95 /* Parser.cpp */, + 81CC3D55092A630E00B2BF95 /* Parser.h */, + 81CC3D56092A630E00B2BF95 /* precompiled.h */, + 81CC3D57092A630E00B2BF95 /* rvHeap.cpp */, + 81CC3D58092A630E00B2BF95 /* rvHeap.h */, + 81CC3D59092A630E00B2BF95 /* rvHeapArena.cpp */, + 81CC3D5A092A630E00B2BF95 /* rvHeapArena.h */, + 81CC3D5B092A630E00B2BF95 /* rvMemSys.cpp */, + 81CC3D5C092A630E00B2BF95 /* rvMemSys.h */, + 81CC3D5D092A630E00B2BF95 /* Str.cpp */, + 81CC3D5E092A630E00B2BF95 /* Str.h */, + 81CC3D5F092A630E00B2BF95 /* TextCompiler.cpp */, + 81CC3D60092A630E00B2BF95 /* TextCompiler.h */, + 81CC3D61092A630E00B2BF95 /* threads */, + 81CC3D69092A630E00B2BF95 /* Timer.cpp */, + 81CC3D6A092A630E00B2BF95 /* Timer.h */, + 81CC3D6B092A630E00B2BF95 /* TimingCollection.cpp */, + 81CC3D6C092A630E00B2BF95 /* TimingCollection.h */, + 81CC3D6D092A630E00B2BF95 /* Token.cpp */, + 81CC3D6E092A630E00B2BF95 /* Token.h */, + ); + name = Source; + path = ../../../idlib; + sourceTree = SOURCE_ROOT; + }; + 81CC3CC9092A630E00B2BF95 /* algorithms */ = { + isa = PBXGroup; + children = ( + 81CC3CCA092A630E00B2BF95 /* MultifieldSort.h */, + ); + path = algorithms; + sourceTree = ""; + }; + 81CC3CD1092A630E00B2BF95 /* bv */ = { + isa = PBXGroup; + children = ( + 81CC3CD2092A630E00B2BF95 /* Bounds.cpp */, + 81CC3CD3092A630E00B2BF95 /* Bounds.h */, + 81CC3CD4092A630E00B2BF95 /* Box.cpp */, + 81CC3CD5092A630E00B2BF95 /* Box.h */, + 81CC3CD6092A630E00B2BF95 /* Frustum.cpp */, + 81CC3CD7092A630E00B2BF95 /* Frustum.h */, + 81CC3CD8092A630E00B2BF95 /* Sphere.cpp */, + 81CC3CD9092A630E00B2BF95 /* Sphere.h */, + ); + path = bv; + sourceTree = ""; + }; + 81CC3CDC092A630E00B2BF95 /* containers */ = { + isa = PBXGroup; + children = ( + 81CC3CDD092A630E00B2BF95 /* BinSearch.h */, + 81CC3CDE092A630E00B2BF95 /* BTree.h */, + 81CC3CDF092A630E00B2BF95 /* HashIndex.cpp */, + 81CC3CE0092A630E00B2BF95 /* HashIndex.h */, + 81CC3CE1092A630E00B2BF95 /* HashTable.h */, + 81CC3CE2092A630E00B2BF95 /* Hierarchy.h */, + 81CC3CE3092A630E00B2BF95 /* LinkList.h */, + 81CC3CE4092A630E00B2BF95 /* List.h */, + 81CC3CE5092A630E00B2BF95 /* ListGame.h */, + 81CC3CE6092A630E00B2BF95 /* Pair.h */, + 81CC3CE7092A630E00B2BF95 /* PlaneSet.h */, + 81CC3CE8092A630E00B2BF95 /* Queue.h */, + 81CC3CE9092A630E00B2BF95 /* rvBlockPool.h */, + 81CC3CEA092A630E00B2BF95 /* Stack.h */, + 81CC3CEB092A630E00B2BF95 /* StaticList.h */, + 81CC3CEC092A630E00B2BF95 /* StrList.h */, + 81CC3CED092A630E00B2BF95 /* StrPool.h */, + 81CC3CEE092A630E00B2BF95 /* VectorSet.h */, + ); + path = containers; + sourceTree = ""; + }; + 81CC3CF1092A630E00B2BF95 /* geometry */ = { + isa = PBXGroup; + children = ( + 81CC3CF3092A630E00B2BF95 /* DrawVert.h */, + 81CC3CF4092A630E00B2BF95 /* JointTransform.cpp */, + 81CC3CF5092A630E00B2BF95 /* JointTransform.h */, + 81CC3CF6092A630E00B2BF95 /* rvVertex.h */, + 81CC3CF7092A630E00B2BF95 /* Surface.cpp */, + 81CC3CF8092A630E00B2BF95 /* Surface.h */, + 81CC3CF9092A630E00B2BF95 /* Surface_Patch.cpp */, + 81CC3CFA092A630E00B2BF95 /* Surface_Patch.h */, + 81CC3CFB092A630E00B2BF95 /* Surface_Polytope.cpp */, + 81CC3CFC092A630E00B2BF95 /* Surface_Polytope.h */, + 81CC3CFD092A630E00B2BF95 /* Surface_SweptSpline.cpp */, + 81CC3CFE092A630E00B2BF95 /* Surface_SweptSpline.h */, + 81CC3CFF092A630E00B2BF95 /* TraceModel.cpp */, + 81CC3D00092A630E00B2BF95 /* TraceModel.h */, + 81CC3D01092A630E00B2BF95 /* Winding.cpp */, + 81CC3D02092A630E00B2BF95 /* Winding.h */, + 81CC3D03092A630E00B2BF95 /* Winding2D.cpp */, + 81CC3D04092A630E00B2BF95 /* Winding2D.h */, + ); + path = geometry; + sourceTree = ""; + }; + 81CC3D05092A630E00B2BF95 /* hashing */ = { + isa = PBXGroup; + children = ( + 81CC3D06092A630E00B2BF95 /* CRC16.cpp */, + 81CC3D07092A630E00B2BF95 /* CRC16.h */, + 81CC3D08092A630E00B2BF95 /* CRC32.cpp */, + 81CC3D09092A630E00B2BF95 /* CRC32.h */, + 81CC3D0A092A630E00B2BF95 /* CRC8.cpp */, + 81CC3D0B092A630E00B2BF95 /* CRC8.h */, + 81CC3D0C092A630E00B2BF95 /* Honeyman.cpp */, + 81CC3D0D092A630E00B2BF95 /* Honeyman.h */, + 81CC3D0E092A630E00B2BF95 /* MD4.cpp */, + 81CC3D0F092A630E00B2BF95 /* MD4.h */, + 81CC3D10092A630E00B2BF95 /* MD5.cpp */, + 81CC3D11092A630E00B2BF95 /* MD5.h */, + ); + path = hashing; + sourceTree = ""; + }; + 81CC3D1E092A630E00B2BF95 /* math */ = { + isa = PBXGroup; + children = ( + 81CC3D1F092A630E00B2BF95 /* Angles.cpp */, + 81CC3D20092A630E00B2BF95 /* Angles.h */, + 81CC3D21092A630E00B2BF95 /* Complex.cpp */, + 81CC3D22092A630E00B2BF95 /* Complex.h */, + 81CC3D23092A630E00B2BF95 /* Curve.h */, + 81CC3D24092A630E00B2BF95 /* Extrapolate.h */, + 81CC3D25092A630E00B2BF95 /* FFT.cpp */, + 81CC3D26092A630E00B2BF95 /* FFT.h */, + 81CC3D27092A630E00B2BF95 /* Interpolate.h */, + 81CC3D28092A630E00B2BF95 /* Lcp.cpp */, + 81CC3D29092A630E00B2BF95 /* Lcp.h */, + 81CC3D2B092A630E00B2BF95 /* Mat3x4.h */, + 81CC3D2C092A630E00B2BF95 /* Math.cpp */, + 81CC3D2E092A630E00B2BF95 /* Matrix.cpp */, + 81CC3D2F092A630E00B2BF95 /* Matrix.h */, + 81CC3D30092A630E00B2BF95 /* Ode.cpp */, + 81CC3D31092A630E00B2BF95 /* Ode.h */, + 81CC3D32092A630E00B2BF95 /* Plane.cpp */, + 81CC3D33092A630E00B2BF95 /* Plane.h */, + 81CC3D34092A630E00B2BF95 /* Pluecker.cpp */, + 81CC3D35092A630E00B2BF95 /* Pluecker.h */, + 81CC3D36092A630E00B2BF95 /* Polynomial.cpp */, + 81CC3D37092A630E00B2BF95 /* Polynomial.h */, + 81CC3D38092A630E00B2BF95 /* Quat.cpp */, + 81CC3D39092A630E00B2BF95 /* Quat.h */, + 81CC3D3A092A630E00B2BF95 /* Radians.cpp */, + 81CC3D3B092A630E00B2BF95 /* Radians.h */, + 81CC3D3C092A630E00B2BF95 /* Random.h */, + 81CC3D3D092A630E00B2BF95 /* Rotation.cpp */, + 81CC3D3E092A630E00B2BF95 /* Rotation.h */, + 81CC3D3F092A630E00B2BF95 /* Simd.cpp */, + 81CC3D40092A630E00B2BF95 /* Simd.h */, + 81CC3D41092A630E00B2BF95 /* Simd_3DNow.cpp */, + 81CC3D42092A630E00B2BF95 /* Simd_3DNow.h */, + 81CC3D43092A630E00B2BF95 /* Simd_AltiVec.cpp */, + 81CC3D44092A630E00B2BF95 /* Simd_AltiVec.h */, + 81CC3D45092A630E00B2BF95 /* Simd_generic.cpp */, + 81CC3D46092A630E00B2BF95 /* Simd_generic.h */, + 81CC3D47092A630E00B2BF95 /* Simd_InstructionMacros.h */, + 81CC3D48092A630E00B2BF95 /* Simd_MMX.cpp */, + 81CC3D49092A630E00B2BF95 /* Simd_MMX.h */, + 81CC3D4A092A630E00B2BF95 /* Simd_SSE.cpp */, + 81CC3D4B092A630E00B2BF95 /* Simd_SSE.h */, + 81CC3D4C092A630E00B2BF95 /* Simd_SSE2.cpp */, + 81CC3D4D092A630E00B2BF95 /* Simd_SSE2.h */, + 81CC3D4E092A630E00B2BF95 /* Simd_SSE3.cpp */, + 81CC3D4F092A630E00B2BF95 /* Simd_SSE3.h */, + 81CC3D52092A630E00B2BF95 /* Vector.cpp */, + 81CC3D53092A630E00B2BF95 /* Vector.h */, + ); + path = math; + sourceTree = ""; + }; + 81CC3D61092A630E00B2BF95 /* threads */ = { + isa = PBXGroup; + children = ( + 81CC3D64092A630E00B2BF95 /* AutoCrit.h */, + ); + path = threads; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC043055464E500DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 81CC3D6F092A630E00B2BF95 /* MultifieldSort.h in Headers */, + 81CC3D70092A630E00B2BF95 /* AutoPtr.h in Headers */, + 81CC3D72092A630E00B2BF95 /* Base64.h in Headers */, + 81CC3D74092A630E00B2BF95 /* BitMsg.h in Headers */, + 81CC3D77092A630E00B2BF95 /* Bounds.h in Headers */, + 81CC3D79092A630E00B2BF95 /* Box.h in Headers */, + 81CC3D7B092A630E00B2BF95 /* Frustum.h in Headers */, + 81CC3D7D092A630E00B2BF95 /* Sphere.h in Headers */, + 81CC3D7F092A630E00B2BF95 /* CmdArgs.h in Headers */, + 81CC3D80092A630E00B2BF95 /* BinSearch.h in Headers */, + 81CC3D81092A630E00B2BF95 /* BTree.h in Headers */, + 81CC3D83092A630E00B2BF95 /* HashIndex.h in Headers */, + 81CC3D84092A630E00B2BF95 /* HashTable.h in Headers */, + 81CC3D85092A630E00B2BF95 /* Hierarchy.h in Headers */, + 81CC3D86092A630E00B2BF95 /* LinkList.h in Headers */, + 81CC3D87092A630E00B2BF95 /* List.h in Headers */, + 81CC3D88092A630E00B2BF95 /* ListGame.h in Headers */, + 81CC3D89092A630E00B2BF95 /* Pair.h in Headers */, + 81CC3D8A092A630E00B2BF95 /* PlaneSet.h in Headers */, + 81CC3D8B092A630E00B2BF95 /* Queue.h in Headers */, + 81CC3D8C092A630E00B2BF95 /* rvBlockPool.h in Headers */, + 81CC3D8D092A630E00B2BF95 /* Stack.h in Headers */, + 81CC3D8E092A630E00B2BF95 /* StaticList.h in Headers */, + 81CC3D8F092A630E00B2BF95 /* StrList.h in Headers */, + 81CC3D90092A630E00B2BF95 /* StrPool.h in Headers */, + 81CC3D91092A630E00B2BF95 /* VectorSet.h in Headers */, + 81CC3D93092A630E00B2BF95 /* Dict.h in Headers */, + 81CC3D95092A630E00B2BF95 /* DrawVert.h in Headers */, + 81CC3D97092A630E00B2BF95 /* JointTransform.h in Headers */, + 81CC3D98092A630E00B2BF95 /* rvVertex.h in Headers */, + 81CC3D9A092A630E00B2BF95 /* Surface.h in Headers */, + 81CC3D9C092A630E00B2BF95 /* Surface_Patch.h in Headers */, + 81CC3D9E092A630E00B2BF95 /* Surface_Polytope.h in Headers */, + 81CC3DA0092A630E00B2BF95 /* Surface_SweptSpline.h in Headers */, + 81CC3DA2092A630E00B2BF95 /* TraceModel.h in Headers */, + 81CC3DA4092A630E00B2BF95 /* Winding.h in Headers */, + 81CC3DA6092A630E00B2BF95 /* Winding2D.h in Headers */, + 81CC3DA8092A630E00B2BF95 /* CRC16.h in Headers */, + 81CC3DAA092A630E00B2BF95 /* CRC32.h in Headers */, + 81CC3DAC092A630E00B2BF95 /* CRC8.h in Headers */, + 81CC3DAE092A630E00B2BF95 /* Honeyman.h in Headers */, + 81CC3DB0092A630E00B2BF95 /* MD4.h in Headers */, + 81CC3DB2092A630E00B2BF95 /* MD5.h in Headers */, + 81CC3DB4092A630E00B2BF95 /* Heap.h in Headers */, + 81CC3DB6092A630E00B2BF95 /* LangDict.h in Headers */, + 81CC3DB8092A630E00B2BF95 /* Lexer.h in Headers */, + 81CC3DBA092A630E00B2BF95 /* LexerFactory.h in Headers */, + 81CC3DBC092A630E00B2BF95 /* Lib.h in Headers */, + 81CC3DBE092A630E00B2BF95 /* MapFile.h in Headers */, + 81CC3DC0092A630E00B2BF95 /* Angles.h in Headers */, + 81CC3DC2092A630E00B2BF95 /* Complex.h in Headers */, + 81CC3DC3092A630E00B2BF95 /* Curve.h in Headers */, + 81CC3DC4092A630E00B2BF95 /* Extrapolate.h in Headers */, + 81CC3DC6092A630E00B2BF95 /* FFT.h in Headers */, + 81CC3DC7092A630E00B2BF95 /* Interpolate.h in Headers */, + 81CC3DC9092A630E00B2BF95 /* Lcp.h in Headers */, + 81CC3DCB092A630E00B2BF95 /* Mat3x4.h in Headers */, + 81CC3DCF092A630E00B2BF95 /* Matrix.h in Headers */, + 81CC3DD1092A630E00B2BF95 /* Ode.h in Headers */, + 81CC3DD3092A630E00B2BF95 /* Plane.h in Headers */, + 81CC3DD5092A630E00B2BF95 /* Pluecker.h in Headers */, + 81CC3DD7092A630E00B2BF95 /* Polynomial.h in Headers */, + 81CC3DD9092A630E00B2BF95 /* Quat.h in Headers */, + 81CC3DDB092A630E00B2BF95 /* Radians.h in Headers */, + 81CC3DDC092A630E00B2BF95 /* Random.h in Headers */, + 81CC3DDE092A630E00B2BF95 /* Rotation.h in Headers */, + 81CC3DE0092A630E00B2BF95 /* Simd.h in Headers */, + 81CC3DE2092A630E00B2BF95 /* Simd_3DNow.h in Headers */, + 81CC3DE4092A630E00B2BF95 /* Simd_AltiVec.h in Headers */, + 81CC3DE6092A630E00B2BF95 /* Simd_generic.h in Headers */, + 81CC3DE7092A630E00B2BF95 /* Simd_InstructionMacros.h in Headers */, + 81CC3DE9092A630E00B2BF95 /* Simd_MMX.h in Headers */, + 81CC3DEB092A630E00B2BF95 /* Simd_SSE.h in Headers */, + 81CC3DED092A630E00B2BF95 /* Simd_SSE2.h in Headers */, + 81CC3DEF092A630E00B2BF95 /* Simd_SSE3.h in Headers */, + 81CC3DF3092A630E00B2BF95 /* Vector.h in Headers */, + 81CC3DF5092A630E00B2BF95 /* Parser.h in Headers */, + 81CC3DF6092A630E00B2BF95 /* precompiled.h in Headers */, + 81CC3DF8092A630E00B2BF95 /* rvHeap.h in Headers */, + 81CC3DFA092A630E00B2BF95 /* rvHeapArena.h in Headers */, + 81CC3DFC092A630E00B2BF95 /* rvMemSys.h in Headers */, + 81CC3DFE092A630E00B2BF95 /* Str.h in Headers */, + 81CC3E00092A630E00B2BF95 /* TextCompiler.h in Headers */, + 81CC3E03092A630E00B2BF95 /* AutoCrit.h in Headers */, + 81CC3E09092A630E00B2BF95 /* Timer.h in Headers */, + 81CC3E0B092A630E00B2BF95 /* TimingCollection.h in Headers */, + 81CC3E0D092A630E00B2BF95 /* Token.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D2AAC045055464E500DB518D /* idlib_pic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81149CB4090D3FD10025E084 /* Build configuration list for PBXNativeTarget "idlib_pic" */; + buildPhases = ( + D2AAC043055464E500DB518D /* Headers */, + D2AAC044055464E500DB518D /* Sources */, + D289987405E68DCB004EDB86 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = idlib_pic; + productName = idlib; + productReference = D2AAC046055464E500DB518D /* libidlib_pic.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 81149CB8090D3FD10025E084 /* Build configuration list for PBXProject "idlib" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* idlib */; + projectDirPath = ""; + targets = ( + D2AAC045055464E500DB518D /* idlib_pic */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + D2AAC044055464E500DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81CC3D71092A630E00B2BF95 /* Base64.cpp in Sources */, + 81CC3D73092A630E00B2BF95 /* BitMsg.cpp in Sources */, + 81CC3D76092A630E00B2BF95 /* Bounds.cpp in Sources */, + 81CC3D78092A630E00B2BF95 /* Box.cpp in Sources */, + 81CC3D7A092A630E00B2BF95 /* Frustum.cpp in Sources */, + 81CC3D7C092A630E00B2BF95 /* Sphere.cpp in Sources */, + 81CC3D7E092A630E00B2BF95 /* CmdArgs.cpp in Sources */, + 81CC3D82092A630E00B2BF95 /* HashIndex.cpp in Sources */, + 81CC3D92092A630E00B2BF95 /* Dict.cpp in Sources */, + 81CC3D96092A630E00B2BF95 /* JointTransform.cpp in Sources */, + 81CC3D99092A630E00B2BF95 /* Surface.cpp in Sources */, + 81CC3D9B092A630E00B2BF95 /* Surface_Patch.cpp in Sources */, + 81CC3D9D092A630E00B2BF95 /* Surface_Polytope.cpp in Sources */, + 81CC3D9F092A630E00B2BF95 /* Surface_SweptSpline.cpp in Sources */, + 81CC3DA1092A630E00B2BF95 /* TraceModel.cpp in Sources */, + 81CC3DA3092A630E00B2BF95 /* Winding.cpp in Sources */, + 81CC3DA5092A630E00B2BF95 /* Winding2D.cpp in Sources */, + 81CC3DA7092A630E00B2BF95 /* CRC16.cpp in Sources */, + 81CC3DA9092A630E00B2BF95 /* CRC32.cpp in Sources */, + 81CC3DAB092A630E00B2BF95 /* CRC8.cpp in Sources */, + 81CC3DAD092A630E00B2BF95 /* Honeyman.cpp in Sources */, + 81CC3DAF092A630E00B2BF95 /* MD4.cpp in Sources */, + 81CC3DB1092A630E00B2BF95 /* MD5.cpp in Sources */, + 81CC3DB3092A630E00B2BF95 /* Heap.cpp in Sources */, + 81CC3DB5092A630E00B2BF95 /* LangDict.cpp in Sources */, + 81CC3DB7092A630E00B2BF95 /* Lexer.cpp in Sources */, + 81CC3DB9092A630E00B2BF95 /* LexerFactory.cpp in Sources */, + 81CC3DBB092A630E00B2BF95 /* Lib.cpp in Sources */, + 81CC3DBD092A630E00B2BF95 /* mapfile.cpp in Sources */, + 81CC3DBF092A630E00B2BF95 /* Angles.cpp in Sources */, + 81CC3DC1092A630E00B2BF95 /* Complex.cpp in Sources */, + 81CC3DC5092A630E00B2BF95 /* FFT.cpp in Sources */, + 81CC3DC8092A630E00B2BF95 /* Lcp.cpp in Sources */, + 81CC3DCC092A630E00B2BF95 /* Math.cpp in Sources */, + 81CC3DCE092A630E00B2BF95 /* Matrix.cpp in Sources */, + 81CC3DD0092A630E00B2BF95 /* Ode.cpp in Sources */, + 81CC3DD2092A630E00B2BF95 /* Plane.cpp in Sources */, + 81CC3DD4092A630E00B2BF95 /* Pluecker.cpp in Sources */, + 81CC3DD6092A630E00B2BF95 /* Polynomial.cpp in Sources */, + 81CC3DD8092A630E00B2BF95 /* Quat.cpp in Sources */, + 81CC3DDA092A630E00B2BF95 /* Radians.cpp in Sources */, + 81CC3DDD092A630E00B2BF95 /* Rotation.cpp in Sources */, + 81CC3DDF092A630E00B2BF95 /* Simd.cpp in Sources */, + 81CC3DE1092A630E00B2BF95 /* Simd_3DNow.cpp in Sources */, + 81CC3DE3092A630E00B2BF95 /* Simd_AltiVec.cpp in Sources */, + 81CC3DE5092A630E00B2BF95 /* Simd_generic.cpp in Sources */, + 81CC3DE8092A630E00B2BF95 /* Simd_MMX.cpp in Sources */, + 81CC3DEA092A630E00B2BF95 /* Simd_SSE.cpp in Sources */, + 81CC3DEC092A630E00B2BF95 /* Simd_SSE2.cpp in Sources */, + 81CC3DEE092A630E00B2BF95 /* Simd_SSE3.cpp in Sources */, + 81CC3DF2092A630E00B2BF95 /* Vector.cpp in Sources */, + 81CC3DF4092A630E00B2BF95 /* Parser.cpp in Sources */, + 81CC3DF7092A630E00B2BF95 /* rvHeap.cpp in Sources */, + 81CC3DF9092A630E00B2BF95 /* rvHeapArena.cpp in Sources */, + 81CC3DFB092A630E00B2BF95 /* rvMemSys.cpp in Sources */, + 81CC3DFD092A630E00B2BF95 /* Str.cpp in Sources */, + 81CC3DFF092A630E00B2BF95 /* TextCompiler.cpp in Sources */, + 81CC3E08092A630E00B2BF95 /* Timer.cpp in Sources */, + 81CC3E0A092A630E00B2BF95 /* TimingCollection.cpp in Sources */, + 81CC3E0C092A630E00B2BF95 /* Token.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 81149CB5090D3FD10025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G4; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = NO; + INSTALL_PATH = /usr/local/lib; + LIBRARY_STYLE = STATIC; + MACOSX_DEPLOYMENT_TARGET = 10.3; + OTHER_CFLAGS = ( + "-fvisibility-inlines-hidden", + ); + PREBINDING = NO; + PRODUCT_NAME = idlib_pic; + REZ_PREPROCESSOR_DEFINITIONS = MACOS_X; + USE_SEPARATE_HEADERMAPS = YES; + ZERO_LINK = NO; + }; + name = Debug; + }; + 81149CB6090D3FD10025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G4; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INSTALL_PATH = /usr/local/lib; + LIBRARY_STYLE = STATIC; + MACOSX_DEPLOYMENT_TARGET = 10.3; + OTHER_CFLAGS = ( + "-fvisibility-inlines-hidden", + "-finline", + "-finline-limit=1000", + "-fomit-frame-pointer", + "-fno-common", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + PREBINDING = NO; + PRODUCT_NAME = idlib_pic; + REZ_PREPROCESSOR_DEFINITIONS = MACOS_X; + USE_SEPARATE_HEADERMAPS = YES; + ZERO_LINK = NO; + }; + name = Release; + }; + 81149CB9090D3FD10025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + GCC_ALTIVEC_EXTENSIONS = YES; + GCC_ONE_BYTE_BOOL = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _DEBUG, + Q4SDK, + ); + GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO; + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ""; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + USE_SEPARATE_HEADERMAPS = YES; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Debug; + }; + 81149CBA090D3FD10025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + GCC_ALTIVEC_EXTENSIONS = YES; + GCC_MODEL_TUNING = G5; + GCC_ONE_BYTE_BOOL = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + MACOS_X, + _FINAL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ( + "-finline", + "-finline-limit=1000", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + "-fomit-frame-pointer", + "-fno-common", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + USE_SEPARATE_HEADERMAPS = YES; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 81149CB4090D3FD10025E084 /* Build configuration list for PBXNativeTarget "idlib_pic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149CB5090D3FD10025E084 /* Debug */, + 81149CB6090D3FD10025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 81149CB8090D3FD10025E084 /* Build configuration list for PBXProject "idlib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149CB9090D3FD10025E084 /* Debug */, + 81149CBA090D3FD10025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/src/sys/osx/Subprojects/mpgame.xcodeproj/project.pbxproj b/src/sys/osx/Subprojects/mpgame.xcodeproj/project.pbxproj new file mode 100644 index 0000000..29e1414 --- /dev/null +++ b/src/sys/osx/Subprojects/mpgame.xcodeproj/project.pbxproj @@ -0,0 +1,1242 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 255BA9290BB85D6C00D112AC /* Lagometer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 255BA9270BB85D6C00D112AC /* Lagometer.cpp */; }; + 2CCDAFD60A2CD52800D4AD93 /* Buying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2CCDAFD40A2CD52800D4AD93 /* Buying.cpp */; }; + 2CE8A5580A2CD90C00929ACB /* WeaponNapalmGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2CE8A5560A2CD90B00929ACB /* WeaponNapalmGun.cpp */; }; + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8114A1A2090D686B0025E084 /* libidlib_pic.a */; }; + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */; }; + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */; }; + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */; }; + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */; }; + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */; }; + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */; }; + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405A092A64D300B2BF95 /* Sound.cpp */; }; + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */; }; + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400E092A64D300B2BF95 /* StatWindow.cpp */; }; + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */; }; + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */; }; + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */; }; + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */; }; + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */; }; + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */; }; + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */; }; + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400C092A64D300B2BF95 /* StatManager.cpp */; }; + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */; }; + 81114FF8093E194300A5C91A /* AF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F75092A64D300B2BF95 /* AF.cpp */; }; + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */; }; + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBF092A64D300B2BF95 /* Camera.cpp */; }; + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */; }; + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */; }; + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */; }; + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F89092A64D300B2BF95 /* AI_events.cpp */; }; + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */; }; + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */; }; + 81115001093E194300A5C91A /* Effect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCA092A64D300B2BF95 /* Effect.cpp */; }; + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */; }; + 81115003093E194300A5C91A /* Player_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4040092A64D300B2BF95 /* Player_States.cpp */; }; + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */; }; + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */; }; + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */; }; + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */; }; + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */; }; + 81115009093E194300A5C91A /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE8092A64D300B2BF95 /* State.cpp */; }; + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */; }; + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */; }; + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */; }; + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */; }; + 8111500E093E194300A5C91A /* Mover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4002092A64D300B2BF95 /* Mover.cpp */; }; + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */; }; + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */; }; + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4050092A64D300B2BF95 /* Script_Program.cpp */; }; + 81115012093E194300A5C91A /* Push.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403A092A64D300B2BF95 /* Push.cpp */; }; + 81115013093E194300A5C91A /* IconManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */; }; + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */; }; + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */; }; + 81115016093E194300A5C91A /* Moveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4000092A64D300B2BF95 /* Moveable.cpp */; }; + 81115017093E194300A5C91A /* Clip.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4016092A64D300B2BF95 /* Clip.cpp */; }; + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */; }; + 81115019093E194300A5C91A /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4060092A64D300B2BF95 /* Target.cpp */; }; + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4064092A64D300B2BF95 /* Trigger.cpp */; }; + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4088092A64D300B2BF95 /* Weapon.cpp */; }; + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */; }; + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */; }; + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */; }; + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */; }; + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */; }; + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */; }; + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */; }; + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */; }; + 81115024093E194300A5C91A /* Playback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403C092A64D300B2BF95 /* Playback.cpp */; }; + 81115025093E194300A5C91A /* Force.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4018092A64D300B2BF95 /* Force.cpp */; }; + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC400A092A64D300B2BF95 /* StatEvent.cpp */; }; + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */; }; + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */; }; + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */; }; + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */; }; + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */; }; + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */; }; + 8111502D093E194300A5C91A /* Player.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403D092A64D300B2BF95 /* Player.cpp */; }; + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */; }; + 8111502F093E194300A5C91A /* IK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF4092A64D300B2BF95 /* IK.cpp */; }; + 81115030093E194300A5C91A /* Projectile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4043092A64D300B2BF95 /* Projectile.cpp */; }; + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4067092A64D300B2BF95 /* Vehicle.cpp */; }; + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */; }; + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */; }; + 81115034093E194300A5C91A /* Pvs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4045092A64D300B2BF95 /* Pvs.cpp */; }; + 81115035093E194300A5C91A /* Light.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFA092A64D300B2BF95 /* Light.cpp */; }; + 81115036093E194300A5C91A /* Entity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FCE092A64D300B2BF95 /* Entity.cpp */; }; + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */; }; + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */; }; + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */; }; + 8111503A093E194300A5C91A /* AAS.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7A092A64D300B2BF95 /* AAS.cpp */; }; + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */; }; + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */; }; + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */; }; + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */; }; + 81115040093E194300A5C91A /* AI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F84092A64D300B2BF95 /* AI.cpp */; }; + 81115041093E194300A5C91A /* Tourney.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4010092A64D300B2BF95 /* Tourney.cpp */; }; + 81115042093E194300A5C91A /* TramGate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4062092A64D300B2BF95 /* TramGate.cpp */; }; + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */; }; + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */; }; + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406D092A64D300B2BF95 /* VehicleController.cpp */; }; + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */; }; + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */; }; + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */; }; + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */; }; + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */; }; + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */; }; + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F92092A64D300B2BF95 /* AI_States.cpp */; }; + 8111504D093E194300A5C91A /* Event.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE3092A64D300B2BF95 /* Event.cpp */; }; + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */; }; + 8111504F093E194300A5C91A /* CTF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4005092A64D300B2BF95 /* CTF.cpp */; }; + 81115050093E194300A5C91A /* GameState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4007092A64D300B2BF95 /* GameState.cpp */; }; + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */; }; + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */; }; + 81115053093E194300A5C91A /* Physics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4022092A64D300B2BF95 /* Physics.cpp */; }; + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */; }; + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */; }; + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */; }; + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */; }; + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */; }; + 81115059093E194300A5C91A /* Class.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDF092A64D300B2BF95 /* Class.cpp */; }; + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */; }; + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */; }; + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */; }; + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */; }; + 8111505E093E194300A5C91A /* Misc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFE092A64D300B2BF95 /* Misc.cpp */; }; + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */; }; + 81115060093E194300A5C91A /* Icon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF0092A64D300B2BF95 /* Icon.cpp */; }; + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */; }; + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */; }; + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */; }; + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */; }; + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */; }; + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */; }; + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */; }; + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */; }; + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */; }; + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */; }; + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */; }; + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */; }; + 8111506D093E194300A5C91A /* Anim.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB7092A64D300B2BF95 /* Anim.cpp */; }; + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */; }; + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */; }; + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405E092A64D300B2BF95 /* SplineMover.cpp */; }; + 81115071093E194300A5C91A /* spawner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC405C092A64D300B2BF95 /* spawner.cpp */; }; + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */; }; + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */; }; + 81115074093E194300A5C91A /* LipSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */; }; + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */; }; + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */; }; + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */; }; + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */; }; + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */; }; + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC401E092A64D300B2BF95 /* Force_Field.cpp */; }; + 8111507B093E194300A5C91A /* Actor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3F73092A64D300B2BF95 /* Actor.cpp */; }; + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4041092A64D300B2BF95 /* PlayerView.cpp */; }; + 8111507D093E194300A5C91A /* Item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF8092A64D300B2BF95 /* Item.cpp */; }; + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */; }; + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */; }; + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */; }; + 81115081093E194300A5C91A /* Instance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81CC3FF6092A64D300B2BF95 /* Instance.cpp */; }; + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */; }; + 816AD2E00981819D00CAC29C /* Game.plist in Resources */ = {isa = PBXBuildFile; fileRef = 816AD2DF0981819D00CAC29C /* Game.plist */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 81114FE2093E189800A5C91A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = D2AAC045055464E500DB518D; + remoteInfo = idlib_pic; + }; + 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D2AAC046055464E500DB518D; + remoteInfo = idlib; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 255BA9270BB85D6C00D112AC /* Lagometer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Lagometer.cpp; sourceTree = ""; }; + 2CCDAFD40A2CD52800D4AD93 /* Buying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = Buying.cpp; path = mp/Buying.cpp; sourceTree = ""; }; + 2CE8A5560A2CD90B00929ACB /* WeaponNapalmGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNapalmGun.cpp; sourceTree = ""; }; + 81114DFB093E117D00A5C91A /* game.so.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = game.so.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 8114A19C090D686B0025E084 /* idlib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = idlib.xcodeproj; sourceTree = ""; }; + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientAFEntity.cpp; sourceTree = ""; }; + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientAFEntity.h; sourceTree = ""; }; + 816AD2DF0981819D00CAC29C /* Game.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; name = Game.plist; path = ../Resources/Game.plist; sourceTree = SOURCE_ROOT; }; + 81CC3F73092A64D300B2BF95 /* Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Actor.cpp; sourceTree = ""; }; + 81CC3F74092A64D300B2BF95 /* Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Actor.h; sourceTree = ""; }; + 81CC3F75092A64D300B2BF95 /* AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AF.cpp; sourceTree = ""; }; + 81CC3F76092A64D300B2BF95 /* AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AF.h; sourceTree = ""; }; + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AFEntity.cpp; sourceTree = ""; }; + 81CC3F78092A64D300B2BF95 /* AFEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AFEntity.h; sourceTree = ""; }; + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS.cpp; sourceTree = ""; }; + 81CC3F7B092A64D300B2BF95 /* AAS.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS.h; sourceTree = ""; }; + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_debug.cpp; sourceTree = ""; }; + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_Find.cpp; sourceTree = ""; }; + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_Find.h; sourceTree = ""; }; + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_local.h; sourceTree = ""; }; + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_pathing.cpp; sourceTree = ""; }; + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_routing.cpp; sourceTree = ""; }; + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AAS_tactical.cpp; sourceTree = ""; }; + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AAS_tactical.h; sourceTree = ""; }; + 81CC3F84092A64D300B2BF95 /* AI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI.cpp; sourceTree = ""; }; + 81CC3F85092A64D300B2BF95 /* AI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI.h; sourceTree = ""; }; + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Actions.cpp; sourceTree = ""; }; + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Announcements.cpp; sourceTree = ""; }; + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Debug.cpp; sourceTree = ""; }; + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_events.cpp; sourceTree = ""; }; + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Manager.cpp; sourceTree = ""; }; + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Manager.h; sourceTree = ""; }; + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Medic.cpp; sourceTree = ""; }; + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Medic.h; sourceTree = ""; }; + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Move.cpp; sourceTree = ""; }; + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Move.h; sourceTree = ""; }; + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_pathing.cpp; sourceTree = ""; }; + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_States.cpp; sourceTree = ""; }; + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Tactical.cpp; sourceTree = ""; }; + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Tactical.h; sourceTree = ""; }; + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = AI_Util.cpp; sourceTree = ""; }; + 81CC3F96092A64D300B2BF95 /* AI_Util.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AI_Util.h; sourceTree = ""; }; + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Berserker.cpp; sourceTree = ""; }; + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossBuddy.cpp; sourceTree = ""; }; + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_BossMakron.cpp; sourceTree = ""; }; + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyGround.cpp; sourceTree = ""; }; + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_ConvoyHover.cpp; sourceTree = ""; }; + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_FailedTransfer.cpp; sourceTree = ""; }; + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Fatty.cpp; sourceTree = ""; }; + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gladiator.cpp; sourceTree = ""; }; + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Grunt.cpp; sourceTree = ""; }; + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Gunner.cpp; sourceTree = ""; }; + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Harvester.cpp; sourceTree = ""; }; + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HarvesterDispersal.cpp; sourceTree = ""; }; + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_HeavyHoverTank.cpp; sourceTree = ""; }; + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_IronMaiden.cpp; sourceTree = ""; }; + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_LightTank.cpp; sourceTree = ""; }; + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_NetworkGuardian.cpp; sourceTree = ""; }; + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_RepairBot.cpp; sourceTree = ""; }; + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Scientist.cpp; sourceTree = ""; }; + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Sentry.cpp; sourceTree = ""; }; + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_SlimyTransfer.cpp; sourceTree = ""; }; + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StreamProtector.cpp; sourceTree = ""; }; + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggFlyer.cpp; sourceTree = ""; }; + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggHover.cpp; sourceTree = ""; }; + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_StroggMarine.cpp; sourceTree = ""; }; + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TeleportDropper.cpp; sourceTree = ""; }; + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_Turret.cpp; sourceTree = ""; }; + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Monster_TurretFlying.cpp; sourceTree = ""; }; + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAI.cpp; sourceTree = ""; }; + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAI.h; sourceTree = ""; }; + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim.cpp; sourceTree = ""; }; + 81CC3FB8092A64D300B2BF95 /* Anim.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim.h; sourceTree = ""; }; + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Blend.cpp; sourceTree = ""; }; + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Import.cpp; sourceTree = ""; }; + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Anim_Testmodel.cpp; sourceTree = ""; }; + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Anim_Testmodel.h; sourceTree = ""; }; + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = BrittleFracture.cpp; sourceTree = ""; }; + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = BrittleFracture.h; sourceTree = ""; }; + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Camera.cpp; sourceTree = ""; }; + 81CC3FC0092A64D300B2BF95 /* Camera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Camera.h; sourceTree = ""; }; + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEffect.cpp; sourceTree = ""; }; + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEffect.h; sourceTree = ""; }; + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientEntity.cpp; sourceTree = ""; }; + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientEntity.h; sourceTree = ""; }; + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientModel.cpp; sourceTree = ""; }; + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientModel.h; sourceTree = ""; }; + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ClientMoveable.cpp; sourceTree = ""; }; + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ClientMoveable.h; sourceTree = ""; }; + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Effect.cpp; sourceTree = ""; }; + 81CC3FCB092A64D300B2BF95 /* Effect.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Effect.h; sourceTree = ""; }; + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Entity.cpp; sourceTree = ""; }; + 81CC3FCF092A64D300B2BF95 /* Entity.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Entity.h; sourceTree = ""; }; + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Debug.cpp; sourceTree = ""; }; + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Debug.h; sourceTree = ""; }; + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_local.cpp; sourceTree = ""; }; + 81CC3FD7092A64D300B2BF95 /* Game_local.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_local.h; sourceTree = ""; }; + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_Log.cpp; sourceTree = ""; }; + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Game_Log.h; sourceTree = ""; }; + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Game_network.cpp; sourceTree = ""; }; + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameEdit.cpp; sourceTree = ""; }; + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameEdit.h; sourceTree = ""; }; + 81CC3FDF092A64D300B2BF95 /* Class.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Class.cpp; sourceTree = ""; }; + 81CC3FE0092A64D300B2BF95 /* Class.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Class.h; sourceTree = ""; }; + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = DebugGraph.cpp; sourceTree = ""; }; + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = DebugGraph.h; sourceTree = ""; }; + 81CC3FE3092A64D300B2BF95 /* Event.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Event.cpp; sourceTree = ""; }; + 81CC3FE4092A64D300B2BF95 /* Event.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Event.h; sourceTree = ""; }; + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NoGameTypeInfo.h; sourceTree = ""; }; + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SaveGame.cpp; sourceTree = ""; }; + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SaveGame.h; sourceTree = ""; }; + 81CC3FE8092A64D300B2BF95 /* State.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = State.cpp; sourceTree = ""; }; + 81CC3FE9092A64D300B2BF95 /* State.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = State.h; sourceTree = ""; }; + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCmds.cpp; sourceTree = ""; }; + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCmds.h; sourceTree = ""; }; + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SysCvar.cpp; sourceTree = ""; }; + 81CC3FED092A64D300B2BF95 /* SysCvar.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SysCvar.h; sourceTree = ""; }; + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Healing_Station.cpp; sourceTree = ""; }; + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Healing_Station.h; sourceTree = ""; }; + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Icon.cpp; sourceTree = ""; }; + 81CC3FF1092A64D300B2BF95 /* Icon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Icon.h; sourceTree = ""; }; + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IconManager.cpp; sourceTree = ""; }; + 81CC3FF3092A64D300B2BF95 /* IconManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IconManager.h; sourceTree = ""; }; + 81CC3FF4092A64D300B2BF95 /* IK.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IK.cpp; sourceTree = ""; }; + 81CC3FF5092A64D300B2BF95 /* IK.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = IK.h; sourceTree = ""; }; + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Instance.cpp; sourceTree = ""; }; + 81CC3FF7092A64D300B2BF95 /* Instance.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Instance.h; sourceTree = ""; }; + 81CC3FF8092A64D300B2BF95 /* Item.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Item.cpp; sourceTree = ""; }; + 81CC3FF9092A64D300B2BF95 /* Item.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Item.h; sourceTree = ""; }; + 81CC3FFA092A64D300B2BF95 /* Light.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Light.cpp; sourceTree = ""; }; + 81CC3FFB092A64D300B2BF95 /* Light.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Light.h; sourceTree = ""; }; + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = LipSync.cpp; sourceTree = ""; }; + 81CC3FFD092A64D300B2BF95 /* LipSync.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LipSync.h; sourceTree = ""; }; + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Misc.cpp; sourceTree = ""; }; + 81CC3FFF092A64D300B2BF95 /* Misc.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Misc.h; sourceTree = ""; }; + 81CC4000092A64D300B2BF95 /* Moveable.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Moveable.cpp; sourceTree = ""; }; + 81CC4001092A64D300B2BF95 /* Moveable.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Moveable.h; sourceTree = ""; }; + 81CC4002092A64D300B2BF95 /* Mover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Mover.cpp; sourceTree = ""; }; + 81CC4003092A64D300B2BF95 /* Mover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Mover.h; sourceTree = ""; }; + 81CC4005092A64D300B2BF95 /* CTF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CTF.cpp; sourceTree = ""; }; + 81CC4006092A64D300B2BF95 /* CTF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CTF.h; sourceTree = ""; }; + 81CC4007092A64D300B2BF95 /* GameState.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = GameState.cpp; sourceTree = ""; }; + 81CC4008092A64D300B2BF95 /* GameState.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = GameState.h; sourceTree = ""; }; + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatEvent.cpp; sourceTree = ""; }; + 81CC400B092A64D300B2BF95 /* StatEvent.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatEvent.h; sourceTree = ""; }; + 81CC400C092A64D300B2BF95 /* StatManager.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatManager.cpp; sourceTree = ""; }; + 81CC400D092A64D300B2BF95 /* StatManager.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatManager.h; sourceTree = ""; }; + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = StatWindow.cpp; sourceTree = ""; }; + 81CC400F092A64D300B2BF95 /* StatWindow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatWindow.h; sourceTree = ""; }; + 81CC4010092A64D300B2BF95 /* Tourney.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Tourney.cpp; sourceTree = ""; }; + 81CC4011092A64D300B2BF95 /* Tourney.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Tourney.h; sourceTree = ""; }; + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VoiceComms.cpp; sourceTree = ""; }; + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = MultiplayerGame.cpp; sourceTree = ""; }; + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MultiplayerGame.h; sourceTree = ""; }; + 81CC4016092A64D300B2BF95 /* Clip.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Clip.cpp; sourceTree = ""; }; + 81CC4017092A64D300B2BF95 /* Clip.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Clip.h; sourceTree = ""; }; + 81CC4018092A64D300B2BF95 /* Force.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force.cpp; sourceTree = ""; }; + 81CC4019092A64D300B2BF95 /* Force.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force.h; sourceTree = ""; }; + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Constant.cpp; sourceTree = ""; }; + 81CC401B092A64D300B2BF95 /* Force_Constant.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Constant.h; sourceTree = ""; }; + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Drag.cpp; sourceTree = ""; }; + 81CC401D092A64D300B2BF95 /* Force_Drag.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Drag.h; sourceTree = ""; }; + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Field.cpp; sourceTree = ""; }; + 81CC401F092A64D300B2BF95 /* Force_Field.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Field.h; sourceTree = ""; }; + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Force_Spring.cpp; sourceTree = ""; }; + 81CC4021092A64D300B2BF95 /* Force_Spring.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Force_Spring.h; sourceTree = ""; }; + 81CC4022092A64D300B2BF95 /* Physics.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics.cpp; sourceTree = ""; }; + 81CC4023092A64D300B2BF95 /* Physics.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics.h; sourceTree = ""; }; + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Actor.cpp; sourceTree = ""; }; + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Actor.h; sourceTree = ""; }; + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_AF.cpp; sourceTree = ""; }; + 81CC4027092A64D300B2BF95 /* Physics_AF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_AF.h; sourceTree = ""; }; + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Base.cpp; sourceTree = ""; }; + 81CC4029092A64D300B2BF95 /* Physics_Base.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Base.h; sourceTree = ""; }; + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Monster.cpp; sourceTree = ""; }; + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Monster.h; sourceTree = ""; }; + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Parametric.cpp; sourceTree = ""; }; + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Parametric.h; sourceTree = ""; }; + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Particle.cpp; sourceTree = ""; }; + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Particle.h; sourceTree = ""; }; + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Player.cpp; sourceTree = ""; }; + 81CC4031092A64D300B2BF95 /* Physics_Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Player.h; sourceTree = ""; }; + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_RigidBody.cpp; sourceTree = ""; }; + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_RigidBody.h; sourceTree = ""; }; + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_Static.cpp; sourceTree = ""; }; + 81CC4035092A64D300B2BF95 /* Physics_Static.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_Static.h; sourceTree = ""; }; + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_StaticMulti.cpp; sourceTree = ""; }; + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_StaticMulti.h; sourceTree = ""; }; + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Physics_VehicleMonster.cpp; sourceTree = ""; }; + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Physics_VehicleMonster.h; sourceTree = ""; }; + 81CC403A092A64D300B2BF95 /* Push.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Push.cpp; sourceTree = ""; }; + 81CC403B092A64D300B2BF95 /* Push.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Push.h; sourceTree = ""; }; + 81CC403C092A64D300B2BF95 /* Playback.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Playback.cpp; sourceTree = ""; }; + 81CC403D092A64D300B2BF95 /* Player.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player.cpp; sourceTree = ""; }; + 81CC403E092A64D300B2BF95 /* Player.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Player.h; sourceTree = ""; }; + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_Cheats.cpp; sourceTree = ""; }; + 81CC4040092A64D300B2BF95 /* Player_States.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Player_States.cpp; sourceTree = ""; }; + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = PlayerView.cpp; sourceTree = ""; }; + 81CC4042092A64D300B2BF95 /* PlayerView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlayerView.h; sourceTree = ""; }; + 81CC4043092A64D300B2BF95 /* Projectile.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Projectile.cpp; sourceTree = ""; }; + 81CC4044092A64D300B2BF95 /* Projectile.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Projectile.h; sourceTree = ""; }; + 81CC4045092A64D300B2BF95 /* Pvs.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Pvs.cpp; sourceTree = ""; }; + 81CC4046092A64D300B2BF95 /* Pvs.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Pvs.h; sourceTree = ""; }; + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Compiler.cpp; sourceTree = ""; }; + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Compiler.h; sourceTree = ""; }; + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Interpreter.cpp; sourceTree = ""; }; + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Interpreter.h; sourceTree = ""; }; + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Program.cpp; sourceTree = ""; }; + 81CC4051092A64D300B2BF95 /* Script_Program.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Program.h; sourceTree = ""; }; + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Script_Thread.cpp; sourceTree = ""; }; + 81CC4053092A64D300B2BF95 /* Script_Thread.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Script_Thread.h; sourceTree = ""; }; + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptFuncUtility.cpp; sourceTree = ""; }; + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ScriptFuncUtility.h; sourceTree = ""; }; + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SecurityCamera.cpp; sourceTree = ""; }; + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SecurityCamera.h; sourceTree = ""; }; + 81CC405A092A64D300B2BF95 /* Sound.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Sound.cpp; sourceTree = ""; }; + 81CC405B092A64D300B2BF95 /* Sound.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Sound.h; sourceTree = ""; }; + 81CC405C092A64D300B2BF95 /* spawner.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = spawner.cpp; sourceTree = ""; }; + 81CC405D092A64D300B2BF95 /* spawner.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = spawner.h; sourceTree = ""; }; + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SplineMover.cpp; sourceTree = ""; }; + 81CC405F092A64D300B2BF95 /* SplineMover.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SplineMover.h; sourceTree = ""; }; + 81CC4060092A64D300B2BF95 /* Target.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Target.cpp; sourceTree = ""; }; + 81CC4061092A64D300B2BF95 /* Target.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Target.h; sourceTree = ""; }; + 81CC4062092A64D300B2BF95 /* TramGate.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TramGate.cpp; sourceTree = ""; }; + 81CC4063092A64D300B2BF95 /* TramGate.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TramGate.h; sourceTree = ""; }; + 81CC4064092A64D300B2BF95 /* Trigger.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Trigger.cpp; sourceTree = ""; }; + 81CC4065092A64D300B2BF95 /* Trigger.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Trigger.h; sourceTree = ""; }; + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle.cpp; sourceTree = ""; }; + 81CC4068092A64D300B2BF95 /* Vehicle.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Vehicle.h; sourceTree = ""; }; + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_DropPod.cpp; sourceTree = ""; }; + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Vehicle_Walker.cpp; sourceTree = ""; }; + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleAnimated.cpp; sourceTree = ""; }; + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleAnimated.h; sourceTree = ""; }; + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleController.cpp; sourceTree = ""; }; + 81CC406E092A64D300B2BF95 /* VehicleController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleController.h; sourceTree = ""; }; + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleDriver.cpp; sourceTree = ""; }; + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleDriver.h; sourceTree = ""; }; + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleMonster.cpp; sourceTree = ""; }; + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleMonster.h; sourceTree = ""; }; + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleParts.cpp; sourceTree = ""; }; + 81CC4074092A64D300B2BF95 /* VehicleParts.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleParts.h; sourceTree = ""; }; + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehiclePosition.cpp; sourceTree = ""; }; + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleRigid.cpp; sourceTree = ""; }; + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleRigid.h; sourceTree = ""; }; + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleSpline.cpp; sourceTree = ""; }; + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleSpline.h; sourceTree = ""; }; + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = VehicleStatic.cpp; sourceTree = ""; }; + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = VehicleStatic.h; sourceTree = ""; }; + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponBlaster.cpp; sourceTree = ""; }; + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponDarkMatterGun.cpp; sourceTree = ""; }; + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGauntlet.cpp; sourceTree = ""; }; + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponGrenadeLauncher.cpp; sourceTree = ""; }; + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponHyperblaster.cpp; sourceTree = ""; }; + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponLightningGun.cpp; sourceTree = ""; }; + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponMachinegun.cpp; sourceTree = ""; }; + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponNailgun.cpp; sourceTree = ""; }; + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRailgun.cpp; sourceTree = ""; }; + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponRocketLauncher.cpp; sourceTree = ""; }; + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WeaponShotgun.cpp; sourceTree = ""; }; + 81CC4088092A64D300B2BF95 /* Weapon.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Weapon.cpp; sourceTree = ""; }; + 81CC4089092A64D300B2BF95 /* Weapon.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Weapon.h; sourceTree = ""; }; + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = WorldSpawn.cpp; sourceTree = ""; }; + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = WorldSpawn.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 81114DF9093E117D00A5C91A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE4093E18A500A5C91A /* libidlib_pic.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* game */ = { + isa = PBXGroup; + children = ( + 81CC3F72092A64D300B2BF95 /* Source */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + 816AD2DF0981819D00CAC29C /* Game.plist */, + 8114A19C090D686B0025E084 /* idlib.xcodeproj */, + ); + name = game; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 81114DFB093E117D00A5C91A /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; + 8114A19D090D686B0025E084 /* Products */ = { + isa = PBXGroup; + children = ( + 8114A1A2090D686B0025E084 /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 81CC3F72092A64D300B2BF95 /* Source */ = { + isa = PBXGroup; + children = ( + 255BA9270BB85D6C00D112AC /* Lagometer.cpp */, + 81CC3F73092A64D300B2BF95 /* Actor.cpp */, + 81CC3F74092A64D300B2BF95 /* Actor.h */, + 81CC3F75092A64D300B2BF95 /* AF.cpp */, + 81CC3F76092A64D300B2BF95 /* AF.h */, + 81CC3F77092A64D300B2BF95 /* AFEntity.cpp */, + 81CC3F78092A64D300B2BF95 /* AFEntity.h */, + 81CC3F79092A64D300B2BF95 /* ai */, + 81CC3FB6092A64D300B2BF95 /* anim */, + 81CC3FBD092A64D300B2BF95 /* BrittleFracture.cpp */, + 81CC3FBE092A64D300B2BF95 /* BrittleFracture.h */, + 2CCDAFD40A2CD52800D4AD93 /* Buying.cpp */, + 81CC3FBF092A64D300B2BF95 /* Camera.cpp */, + 81CC3FC0092A64D300B2BF95 /* Camera.h */, + 81CC3FC1092A64D300B2BF95 /* client */, + 81CC3FCA092A64D300B2BF95 /* Effect.cpp */, + 81CC3FCB092A64D300B2BF95 /* Effect.h */, + 81CC3FCE092A64D300B2BF95 /* Entity.cpp */, + 81CC3FCF092A64D300B2BF95 /* Entity.h */, + 81CC3FD4092A64D300B2BF95 /* Game_Debug.cpp */, + 81CC3FD5092A64D300B2BF95 /* Game_Debug.h */, + 81CC3FD6092A64D300B2BF95 /* Game_local.cpp */, + 81CC3FD7092A64D300B2BF95 /* Game_local.h */, + 81CC3FD8092A64D300B2BF95 /* Game_Log.cpp */, + 81CC3FD9092A64D300B2BF95 /* Game_Log.h */, + 81CC3FDA092A64D300B2BF95 /* Game_network.cpp */, + 81CC3FDB092A64D300B2BF95 /* GameEdit.cpp */, + 81CC3FDC092A64D300B2BF95 /* GameEdit.h */, + 81CC3FDD092A64D300B2BF95 /* gamesys */, + 81CC3FEE092A64D300B2BF95 /* Healing_Station.cpp */, + 81CC3FEF092A64D300B2BF95 /* Healing_Station.h */, + 81CC3FF0092A64D300B2BF95 /* Icon.cpp */, + 81CC3FF1092A64D300B2BF95 /* Icon.h */, + 81CC3FF2092A64D300B2BF95 /* IconManager.cpp */, + 81CC3FF3092A64D300B2BF95 /* IconManager.h */, + 81CC3FF4092A64D300B2BF95 /* IK.cpp */, + 81CC3FF5092A64D300B2BF95 /* IK.h */, + 81CC3FF6092A64D300B2BF95 /* Instance.cpp */, + 81CC3FF7092A64D300B2BF95 /* Instance.h */, + 81CC3FF8092A64D300B2BF95 /* Item.cpp */, + 81CC3FF9092A64D300B2BF95 /* Item.h */, + 81CC3FFA092A64D300B2BF95 /* Light.cpp */, + 81CC3FFB092A64D300B2BF95 /* Light.h */, + 81CC3FFC092A64D300B2BF95 /* LipSync.cpp */, + 81CC3FFD092A64D300B2BF95 /* LipSync.h */, + 81CC3FFE092A64D300B2BF95 /* Misc.cpp */, + 81CC3FFF092A64D300B2BF95 /* Misc.h */, + 81CC4000092A64D300B2BF95 /* Moveable.cpp */, + 81CC4001092A64D300B2BF95 /* Moveable.h */, + 81CC4002092A64D300B2BF95 /* Mover.cpp */, + 81CC4003092A64D300B2BF95 /* Mover.h */, + 81CC4004092A64D300B2BF95 /* mp */, + 81CC4013092A64D300B2BF95 /* MultiplayerGame.cpp */, + 81CC4014092A64D300B2BF95 /* MultiplayerGame.h */, + 81CC4015092A64D300B2BF95 /* physics */, + 81CC403C092A64D300B2BF95 /* Playback.cpp */, + 81CC403D092A64D300B2BF95 /* Player.cpp */, + 81CC403E092A64D300B2BF95 /* Player.h */, + 81CC403F092A64D300B2BF95 /* Player_Cheats.cpp */, + 81CC4040092A64D300B2BF95 /* Player_States.cpp */, + 81CC4041092A64D300B2BF95 /* PlayerView.cpp */, + 81CC4042092A64D300B2BF95 /* PlayerView.h */, + 81CC4043092A64D300B2BF95 /* Projectile.cpp */, + 81CC4044092A64D300B2BF95 /* Projectile.h */, + 81CC4045092A64D300B2BF95 /* Pvs.cpp */, + 81CC4046092A64D300B2BF95 /* Pvs.h */, + 81CC4047092A64D300B2BF95 /* Resource */, + 81CC4049092A64D300B2BF95 /* rvAI */, + 81CC404B092A64D300B2BF95 /* script */, + 81CC4056092A64D300B2BF95 /* SecurityCamera.cpp */, + 81CC4057092A64D300B2BF95 /* SecurityCamera.h */, + 81CC405A092A64D300B2BF95 /* Sound.cpp */, + 81CC405B092A64D300B2BF95 /* Sound.h */, + 81CC405C092A64D300B2BF95 /* spawner.cpp */, + 81CC405D092A64D300B2BF95 /* spawner.h */, + 81CC405E092A64D300B2BF95 /* SplineMover.cpp */, + 81CC405F092A64D300B2BF95 /* SplineMover.h */, + 81CC4060092A64D300B2BF95 /* Target.cpp */, + 81CC4061092A64D300B2BF95 /* Target.h */, + 81CC4062092A64D300B2BF95 /* TramGate.cpp */, + 81CC4063092A64D300B2BF95 /* TramGate.h */, + 81CC4064092A64D300B2BF95 /* Trigger.cpp */, + 81CC4065092A64D300B2BF95 /* Trigger.h */, + 81CC4066092A64D300B2BF95 /* vehicle */, + 81CC407C092A64D300B2BF95 /* weapon */, + 81CC4088092A64D300B2BF95 /* Weapon.cpp */, + 81CC4089092A64D300B2BF95 /* Weapon.h */, + 81CC408A092A64D300B2BF95 /* WorldSpawn.cpp */, + 81CC408B092A64D300B2BF95 /* WorldSpawn.h */, + ); + name = Source; + path = ../../../mpgame; + sourceTree = SOURCE_ROOT; + }; + 81CC3F79092A64D300B2BF95 /* ai */ = { + isa = PBXGroup; + children = ( + 81CC3F7A092A64D300B2BF95 /* AAS.cpp */, + 81CC3F7B092A64D300B2BF95 /* AAS.h */, + 81CC3F7C092A64D300B2BF95 /* AAS_debug.cpp */, + 81CC3F7D092A64D300B2BF95 /* AAS_Find.cpp */, + 81CC3F7E092A64D300B2BF95 /* AAS_Find.h */, + 81CC3F7F092A64D300B2BF95 /* AAS_local.h */, + 81CC3F80092A64D300B2BF95 /* AAS_pathing.cpp */, + 81CC3F81092A64D300B2BF95 /* AAS_routing.cpp */, + 81CC3F82092A64D300B2BF95 /* AAS_tactical.cpp */, + 81CC3F83092A64D300B2BF95 /* AAS_tactical.h */, + 81CC3F84092A64D300B2BF95 /* AI.cpp */, + 81CC3F85092A64D300B2BF95 /* AI.h */, + 81CC3F86092A64D300B2BF95 /* AI_Actions.cpp */, + 81CC3F87092A64D300B2BF95 /* AI_Announcements.cpp */, + 81CC3F88092A64D300B2BF95 /* AI_Debug.cpp */, + 81CC3F89092A64D300B2BF95 /* AI_events.cpp */, + 81CC3F8A092A64D300B2BF95 /* AI_Manager.cpp */, + 81CC3F8B092A64D300B2BF95 /* AI_Manager.h */, + 81CC3F8C092A64D300B2BF95 /* AI_Medic.cpp */, + 81CC3F8D092A64D300B2BF95 /* AI_Medic.h */, + 81CC3F8E092A64D300B2BF95 /* AI_Move.cpp */, + 81CC3F8F092A64D300B2BF95 /* AI_Move.h */, + 81CC3F90092A64D300B2BF95 /* AI_pathing.cpp */, + 81CC3F92092A64D300B2BF95 /* AI_States.cpp */, + 81CC3F93092A64D300B2BF95 /* AI_Tactical.cpp */, + 81CC3F94092A64D300B2BF95 /* AI_Tactical.h */, + 81CC3F95092A64D300B2BF95 /* AI_Util.cpp */, + 81CC3F96092A64D300B2BF95 /* AI_Util.h */, + 81CC3F97092A64D300B2BF95 /* Monster_Berserker.cpp */, + 81CC3F98092A64D300B2BF95 /* Monster_BossBuddy.cpp */, + 81CC3F99092A64D300B2BF95 /* Monster_BossMakron.cpp */, + 81CC3F9A092A64D300B2BF95 /* Monster_ConvoyGround.cpp */, + 81CC3F9D092A64D300B2BF95 /* Monster_ConvoyHover.cpp */, + 81CC3F9E092A64D300B2BF95 /* Monster_FailedTransfer.cpp */, + 81CC3F9F092A64D300B2BF95 /* Monster_Fatty.cpp */, + 81CC3FA0092A64D300B2BF95 /* Monster_Gladiator.cpp */, + 81CC3FA1092A64D300B2BF95 /* Monster_Grunt.cpp */, + 81CC3FA2092A64D300B2BF95 /* Monster_Gunner.cpp */, + 81CC3FA3092A64D300B2BF95 /* Monster_Harvester.cpp */, + 81CC3FA4092A64D300B2BF95 /* Monster_HarvesterDispersal.cpp */, + 81CC3FA5092A64D300B2BF95 /* Monster_HeavyHoverTank.cpp */, + 81CC3FA6092A64D300B2BF95 /* Monster_IronMaiden.cpp */, + 81CC3FA7092A64D300B2BF95 /* Monster_LightTank.cpp */, + 81CC3FA8092A64D300B2BF95 /* Monster_NetworkGuardian.cpp */, + 81CC3FA9092A64D300B2BF95 /* Monster_RepairBot.cpp */, + 81CC3FAA092A64D300B2BF95 /* Monster_Scientist.cpp */, + 81CC3FAB092A64D300B2BF95 /* Monster_Sentry.cpp */, + 81CC3FAC092A64D300B2BF95 /* Monster_SlimyTransfer.cpp */, + 81CC3FAD092A64D300B2BF95 /* Monster_StreamProtector.cpp */, + 81CC3FAE092A64D300B2BF95 /* Monster_StroggFlyer.cpp */, + 81CC3FAF092A64D300B2BF95 /* Monster_StroggHover.cpp */, + 81CC3FB0092A64D300B2BF95 /* Monster_StroggMarine.cpp */, + 81CC3FB1092A64D300B2BF95 /* Monster_TeleportDropper.cpp */, + 81CC3FB2092A64D300B2BF95 /* Monster_Turret.cpp */, + 81CC3FB3092A64D300B2BF95 /* Monster_TurretFlying.cpp */, + 81CC3FB4092A64D300B2BF95 /* VehicleAI.cpp */, + 81CC3FB5092A64D300B2BF95 /* VehicleAI.h */, + ); + path = ai; + sourceTree = ""; + }; + 81CC3FB6092A64D300B2BF95 /* anim */ = { + isa = PBXGroup; + children = ( + 81CC3FB7092A64D300B2BF95 /* Anim.cpp */, + 81CC3FB8092A64D300B2BF95 /* Anim.h */, + 81CC3FB9092A64D300B2BF95 /* Anim_Blend.cpp */, + 81CC3FBA092A64D300B2BF95 /* Anim_Import.cpp */, + 81CC3FBB092A64D300B2BF95 /* Anim_Testmodel.cpp */, + 81CC3FBC092A64D300B2BF95 /* Anim_Testmodel.h */, + ); + path = anim; + sourceTree = ""; + }; + 81CC3FC1092A64D300B2BF95 /* client */ = { + isa = PBXGroup; + children = ( + 814E1D1F097C1A7B009E5F86 /* ClientAFEntity.cpp */, + 814E1D20097C1A7B009E5F86 /* ClientAFEntity.h */, + 81CC3FC2092A64D300B2BF95 /* ClientEffect.cpp */, + 81CC3FC3092A64D300B2BF95 /* ClientEffect.h */, + 81CC3FC4092A64D300B2BF95 /* ClientEntity.cpp */, + 81CC3FC5092A64D300B2BF95 /* ClientEntity.h */, + 81CC3FC6092A64D300B2BF95 /* ClientModel.cpp */, + 81CC3FC7092A64D300B2BF95 /* ClientModel.h */, + 81CC3FC8092A64D300B2BF95 /* ClientMoveable.cpp */, + 81CC3FC9092A64D300B2BF95 /* ClientMoveable.h */, + ); + path = client; + sourceTree = ""; + }; + 81CC3FDD092A64D300B2BF95 /* gamesys */ = { + isa = PBXGroup; + children = ( + 81CC3FDF092A64D300B2BF95 /* Class.cpp */, + 81CC3FE0092A64D300B2BF95 /* Class.h */, + 81CC3FE1092A64D300B2BF95 /* DebugGraph.cpp */, + 81CC3FE2092A64D300B2BF95 /* DebugGraph.h */, + 81CC3FE3092A64D300B2BF95 /* Event.cpp */, + 81CC3FE4092A64D300B2BF95 /* Event.h */, + 81CC3FE5092A64D300B2BF95 /* NoGameTypeInfo.h */, + 81CC3FE6092A64D300B2BF95 /* SaveGame.cpp */, + 81CC3FE7092A64D300B2BF95 /* SaveGame.h */, + 81CC3FE8092A64D300B2BF95 /* State.cpp */, + 81CC3FE9092A64D300B2BF95 /* State.h */, + 81CC3FEA092A64D300B2BF95 /* SysCmds.cpp */, + 81CC3FEB092A64D300B2BF95 /* SysCmds.h */, + 81CC3FEC092A64D300B2BF95 /* SysCvar.cpp */, + 81CC3FED092A64D300B2BF95 /* SysCvar.h */, + ); + path = gamesys; + sourceTree = ""; + }; + 81CC4004092A64D300B2BF95 /* mp */ = { + isa = PBXGroup; + children = ( + 81CC4005092A64D300B2BF95 /* CTF.cpp */, + 81CC4006092A64D300B2BF95 /* CTF.h */, + 81CC4007092A64D300B2BF95 /* GameState.cpp */, + 81CC4008092A64D300B2BF95 /* GameState.h */, + 81CC4009092A64D300B2BF95 /* stats */, + 81CC4010092A64D300B2BF95 /* Tourney.cpp */, + 81CC4011092A64D300B2BF95 /* Tourney.h */, + 81CC4012092A64D300B2BF95 /* VoiceComms.cpp */, + ); + path = mp; + sourceTree = ""; + }; + 81CC4009092A64D300B2BF95 /* stats */ = { + isa = PBXGroup; + children = ( + 81CC400A092A64D300B2BF95 /* StatEvent.cpp */, + 81CC400B092A64D300B2BF95 /* StatEvent.h */, + 81CC400C092A64D300B2BF95 /* StatManager.cpp */, + 81CC400D092A64D300B2BF95 /* StatManager.h */, + 81CC400E092A64D300B2BF95 /* StatWindow.cpp */, + 81CC400F092A64D300B2BF95 /* StatWindow.h */, + ); + path = stats; + sourceTree = ""; + }; + 81CC4015092A64D300B2BF95 /* physics */ = { + isa = PBXGroup; + children = ( + 81CC4016092A64D300B2BF95 /* Clip.cpp */, + 81CC4017092A64D300B2BF95 /* Clip.h */, + 81CC4018092A64D300B2BF95 /* Force.cpp */, + 81CC4019092A64D300B2BF95 /* Force.h */, + 81CC401A092A64D300B2BF95 /* Force_Constant.cpp */, + 81CC401B092A64D300B2BF95 /* Force_Constant.h */, + 81CC401C092A64D300B2BF95 /* Force_Drag.cpp */, + 81CC401D092A64D300B2BF95 /* Force_Drag.h */, + 81CC401E092A64D300B2BF95 /* Force_Field.cpp */, + 81CC401F092A64D300B2BF95 /* Force_Field.h */, + 81CC4020092A64D300B2BF95 /* Force_Spring.cpp */, + 81CC4021092A64D300B2BF95 /* Force_Spring.h */, + 81CC4022092A64D300B2BF95 /* Physics.cpp */, + 81CC4023092A64D300B2BF95 /* Physics.h */, + 81CC4024092A64D300B2BF95 /* Physics_Actor.cpp */, + 81CC4025092A64D300B2BF95 /* Physics_Actor.h */, + 81CC4026092A64D300B2BF95 /* Physics_AF.cpp */, + 81CC4027092A64D300B2BF95 /* Physics_AF.h */, + 81CC4028092A64D300B2BF95 /* Physics_Base.cpp */, + 81CC4029092A64D300B2BF95 /* Physics_Base.h */, + 81CC402A092A64D300B2BF95 /* Physics_Monster.cpp */, + 81CC402B092A64D300B2BF95 /* Physics_Monster.h */, + 81CC402C092A64D300B2BF95 /* Physics_Parametric.cpp */, + 81CC402D092A64D300B2BF95 /* Physics_Parametric.h */, + 81CC402E092A64D300B2BF95 /* Physics_Particle.cpp */, + 81CC402F092A64D300B2BF95 /* Physics_Particle.h */, + 81CC4030092A64D300B2BF95 /* Physics_Player.cpp */, + 81CC4031092A64D300B2BF95 /* Physics_Player.h */, + 81CC4032092A64D300B2BF95 /* Physics_RigidBody.cpp */, + 81CC4033092A64D300B2BF95 /* Physics_RigidBody.h */, + 81CC4034092A64D300B2BF95 /* Physics_Static.cpp */, + 81CC4035092A64D300B2BF95 /* Physics_Static.h */, + 81CC4036092A64D300B2BF95 /* Physics_StaticMulti.cpp */, + 81CC4037092A64D300B2BF95 /* Physics_StaticMulti.h */, + 81CC4038092A64D300B2BF95 /* Physics_VehicleMonster.cpp */, + 81CC4039092A64D300B2BF95 /* Physics_VehicleMonster.h */, + 81CC403A092A64D300B2BF95 /* Push.cpp */, + 81CC403B092A64D300B2BF95 /* Push.h */, + ); + path = physics; + sourceTree = ""; + }; + 81CC4047092A64D300B2BF95 /* Resource */ = { + isa = PBXGroup; + children = ( + ); + path = Resource; + sourceTree = ""; + }; + 81CC4049092A64D300B2BF95 /* rvAI */ = { + isa = PBXGroup; + children = ( + ); + path = rvAI; + sourceTree = ""; + }; + 81CC404B092A64D300B2BF95 /* script */ = { + isa = PBXGroup; + children = ( + 81CC404C092A64D300B2BF95 /* Script_Compiler.cpp */, + 81CC404D092A64D300B2BF95 /* Script_Compiler.h */, + 81CC404E092A64D300B2BF95 /* Script_Interpreter.cpp */, + 81CC404F092A64D300B2BF95 /* Script_Interpreter.h */, + 81CC4050092A64D300B2BF95 /* Script_Program.cpp */, + 81CC4051092A64D300B2BF95 /* Script_Program.h */, + 81CC4052092A64D300B2BF95 /* Script_Thread.cpp */, + 81CC4053092A64D300B2BF95 /* Script_Thread.h */, + 81CC4054092A64D300B2BF95 /* ScriptFuncUtility.cpp */, + 81CC4055092A64D300B2BF95 /* ScriptFuncUtility.h */, + ); + path = script; + sourceTree = ""; + }; + 81CC4066092A64D300B2BF95 /* vehicle */ = { + isa = PBXGroup; + children = ( + 81CC4067092A64D300B2BF95 /* Vehicle.cpp */, + 81CC4068092A64D300B2BF95 /* Vehicle.h */, + 81CC4069092A64D300B2BF95 /* Vehicle_DropPod.cpp */, + 81CC406A092A64D300B2BF95 /* Vehicle_Walker.cpp */, + 81CC406B092A64D300B2BF95 /* VehicleAnimated.cpp */, + 81CC406C092A64D300B2BF95 /* VehicleAnimated.h */, + 81CC406D092A64D300B2BF95 /* VehicleController.cpp */, + 81CC406E092A64D300B2BF95 /* VehicleController.h */, + 81CC406F092A64D300B2BF95 /* VehicleDriver.cpp */, + 81CC4070092A64D300B2BF95 /* VehicleDriver.h */, + 81CC4071092A64D300B2BF95 /* VehicleMonster.cpp */, + 81CC4072092A64D300B2BF95 /* VehicleMonster.h */, + 81CC4073092A64D300B2BF95 /* VehicleParts.cpp */, + 81CC4074092A64D300B2BF95 /* VehicleParts.h */, + 81CC4075092A64D300B2BF95 /* VehiclePosition.cpp */, + 81CC4076092A64D300B2BF95 /* VehicleRigid.cpp */, + 81CC4077092A64D300B2BF95 /* VehicleRigid.h */, + 81CC4078092A64D300B2BF95 /* VehicleSpline.cpp */, + 81CC4079092A64D300B2BF95 /* VehicleSpline.h */, + 81CC407A092A64D300B2BF95 /* VehicleStatic.cpp */, + 81CC407B092A64D300B2BF95 /* VehicleStatic.h */, + ); + path = vehicle; + sourceTree = ""; + }; + 81CC407C092A64D300B2BF95 /* weapon */ = { + isa = PBXGroup; + children = ( + 2CE8A5560A2CD90B00929ACB /* WeaponNapalmGun.cpp */, + 81CC407D092A64D300B2BF95 /* WeaponBlaster.cpp */, + 81CC407E092A64D300B2BF95 /* WeaponDarkMatterGun.cpp */, + 81CC407F092A64D300B2BF95 /* WeaponGauntlet.cpp */, + 81CC4080092A64D300B2BF95 /* WeaponGrenadeLauncher.cpp */, + 81CC4081092A64D300B2BF95 /* WeaponHyperblaster.cpp */, + 81CC4082092A64D300B2BF95 /* WeaponLightningGun.cpp */, + 81CC4083092A64D300B2BF95 /* WeaponMachinegun.cpp */, + 81CC4084092A64D300B2BF95 /* WeaponNailgun.cpp */, + 81CC4085092A64D300B2BF95 /* WeaponRailgun.cpp */, + 81CC4086092A64D300B2BF95 /* WeaponRocketLauncher.cpp */, + 81CC4087092A64D300B2BF95 /* WeaponShotgun.cpp */, + ); + path = weapon; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 81114DFA093E117D00A5C91A /* game bundle */ = { + isa = PBXNativeTarget; + buildConfigurationList = 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */; + buildPhases = ( + 81114DF7093E117D00A5C91A /* Resources */, + 81114DF8093E117D00A5C91A /* Sources */, + 81114DF9093E117D00A5C91A /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 81114FE3093E189800A5C91A /* PBXTargetDependency */, + ); + name = "game bundle"; + productName = "game bundle"; + productReference = 81114DFB093E117D00A5C91A /* game.so.bundle */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "mpgame" */; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* game */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 8114A19D090D686B0025E084 /* Products */; + ProjectRef = 8114A19C090D686B0025E084 /* idlib.xcodeproj */; + }, + ); + targets = ( + 81114DFA093E117D00A5C91A /* game bundle */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 8114A1A2090D686B0025E084 /* libidlib_pic.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libidlib_pic.a; + remoteRef = 8114A1A1090D686B0025E084 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 81114DF7093E117D00A5C91A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 816AD2E00981819D00CAC29C /* Game.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 81114DF8093E117D00A5C91A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81114FE5093E194300A5C91A /* Physics_Player.cpp in Sources */, + 81114FE6093E194300A5C91A /* Monster_ConvoyGround.cpp in Sources */, + 81114FE8093E194300A5C91A /* Monster_Harvester.cpp in Sources */, + 81114FE9093E194300A5C91A /* Monster_HeavyHoverTank.cpp in Sources */, + 81114FEA093E194300A5C91A /* Physics_AF.cpp in Sources */, + 81114FEB093E194300A5C91A /* VehicleRigid.cpp in Sources */, + 81114FEC093E194300A5C91A /* Sound.cpp in Sources */, + 81114FED093E194300A5C91A /* WeaponDarkMatterGun.cpp in Sources */, + 81114FEE093E194300A5C91A /* StatWindow.cpp in Sources */, + 81114FEF093E194300A5C91A /* Monster_HarvesterDispersal.cpp in Sources */, + 81114FF0093E194300A5C91A /* Script_Thread.cpp in Sources */, + 81114FF1093E194300A5C91A /* VehiclePosition.cpp in Sources */, + 81114FF2093E194300A5C91A /* Monster_TurretFlying.cpp in Sources */, + 81114FF3093E194300A5C91A /* ClientEffect.cpp in Sources */, + 81114FF4093E194300A5C91A /* AI_Actions.cpp in Sources */, + 81114FF5093E194300A5C91A /* Game_network.cpp in Sources */, + 81114FF6093E194300A5C91A /* StatManager.cpp in Sources */, + 81114FF7093E194300A5C91A /* Vehicle_Walker.cpp in Sources */, + 81114FF8093E194300A5C91A /* AF.cpp in Sources */, + 81114FF9093E194300A5C91A /* Monster_StreamProtector.cpp in Sources */, + 81114FFA093E194300A5C91A /* Camera.cpp in Sources */, + 81114FFB093E194300A5C91A /* WeaponLightningGun.cpp in Sources */, + 81114FFC093E194300A5C91A /* Monster_ConvoyHover.cpp in Sources */, + 81114FFD093E194300A5C91A /* Monster_Sentry.cpp in Sources */, + 81114FFE093E194300A5C91A /* AI_events.cpp in Sources */, + 81114FFF093E194300A5C91A /* AAS_routing.cpp in Sources */, + 81115000093E194300A5C91A /* ClientModel.cpp in Sources */, + 81115001093E194300A5C91A /* Effect.cpp in Sources */, + 81115002093E194300A5C91A /* SecurityCamera.cpp in Sources */, + 81115003093E194300A5C91A /* Player_States.cpp in Sources */, + 81115004093E194300A5C91A /* VehicleSpline.cpp in Sources */, + 81115005093E194300A5C91A /* Monster_Gunner.cpp in Sources */, + 81115006093E194300A5C91A /* AAS_pathing.cpp in Sources */, + 81115007093E194300A5C91A /* Monster_NetworkGuardian.cpp in Sources */, + 81115008093E194300A5C91A /* Physics_Monster.cpp in Sources */, + 81115009093E194300A5C91A /* State.cpp in Sources */, + 8111500A093E194300A5C91A /* Physics_RigidBody.cpp in Sources */, + 8111500B093E194300A5C91A /* Monster_FailedTransfer.cpp in Sources */, + 8111500C093E194300A5C91A /* Physics_Static.cpp in Sources */, + 8111500D093E194300A5C91A /* Monster_RepairBot.cpp in Sources */, + 8111500E093E194300A5C91A /* Mover.cpp in Sources */, + 8111500F093E194300A5C91A /* WeaponRocketLauncher.cpp in Sources */, + 81115010093E194300A5C91A /* SysCvar.cpp in Sources */, + 81115011093E194300A5C91A /* Script_Program.cpp in Sources */, + 81115012093E194300A5C91A /* Push.cpp in Sources */, + 81115013093E194300A5C91A /* IconManager.cpp in Sources */, + 81115014093E194300A5C91A /* AI_Tactical.cpp in Sources */, + 81115015093E194300A5C91A /* Monster_Grunt.cpp in Sources */, + 81115016093E194300A5C91A /* Moveable.cpp in Sources */, + 81115017093E194300A5C91A /* Clip.cpp in Sources */, + 81115018093E194300A5C91A /* Script_Compiler.cpp in Sources */, + 81115019093E194300A5C91A /* Target.cpp in Sources */, + 8111501A093E194300A5C91A /* Trigger.cpp in Sources */, + 8111501B093E194300A5C91A /* Weapon.cpp in Sources */, + 8111501C093E194300A5C91A /* Physics_Particle.cpp in Sources */, + 8111501D093E194300A5C91A /* Monster_StroggFlyer.cpp in Sources */, + 8111501E093E194300A5C91A /* VehicleParts.cpp in Sources */, + 8111501F093E194300A5C91A /* ScriptFuncUtility.cpp in Sources */, + 81115020093E194300A5C91A /* Force_Drag.cpp in Sources */, + 81115021093E194300A5C91A /* WeaponGauntlet.cpp in Sources */, + 81115022093E194300A5C91A /* Monster_IronMaiden.cpp in Sources */, + 81115023093E194300A5C91A /* Physics_Actor.cpp in Sources */, + 81115024093E194300A5C91A /* Playback.cpp in Sources */, + 81115025093E194300A5C91A /* Force.cpp in Sources */, + 81115026093E194300A5C91A /* StatEvent.cpp in Sources */, + 81115027093E194300A5C91A /* Monster_Fatty.cpp in Sources */, + 81115028093E194300A5C91A /* Physics_VehicleMonster.cpp in Sources */, + 81115029093E194300A5C91A /* BrittleFracture.cpp in Sources */, + 8111502A093E194300A5C91A /* AAS_tactical.cpp in Sources */, + 8111502B093E194300A5C91A /* AI_Announcements.cpp in Sources */, + 8111502C093E194300A5C91A /* VehicleMonster.cpp in Sources */, + 8111502D093E194300A5C91A /* Player.cpp in Sources */, + 8111502E093E194300A5C91A /* Game_Debug.cpp in Sources */, + 8111502F093E194300A5C91A /* IK.cpp in Sources */, + 81115030093E194300A5C91A /* Projectile.cpp in Sources */, + 81115031093E194300A5C91A /* Vehicle.cpp in Sources */, + 81115032093E194300A5C91A /* Healing_Station.cpp in Sources */, + 81115033093E194300A5C91A /* Monster_Turret.cpp in Sources */, + 81115034093E194300A5C91A /* Pvs.cpp in Sources */, + 81115035093E194300A5C91A /* Light.cpp in Sources */, + 81115036093E194300A5C91A /* Entity.cpp in Sources */, + 81115037093E194300A5C91A /* Anim_Import.cpp in Sources */, + 81115038093E194300A5C91A /* WeaponMachinegun.cpp in Sources */, + 81115039093E194300A5C91A /* WeaponShotgun.cpp in Sources */, + 8111503A093E194300A5C91A /* AAS.cpp in Sources */, + 8111503C093E194300A5C91A /* WeaponRailgun.cpp in Sources */, + 8111503D093E194300A5C91A /* AI_Util.cpp in Sources */, + 8111503E093E194300A5C91A /* AAS_Find.cpp in Sources */, + 8111503F093E194300A5C91A /* Game_Log.cpp in Sources */, + 81115040093E194300A5C91A /* AI.cpp in Sources */, + 81115041093E194300A5C91A /* Tourney.cpp in Sources */, + 81115042093E194300A5C91A /* TramGate.cpp in Sources */, + 81115043093E194300A5C91A /* Monster_StroggMarine.cpp in Sources */, + 81115044093E194300A5C91A /* Vehicle_DropPod.cpp in Sources */, + 81115045093E194300A5C91A /* VehicleController.cpp in Sources */, + 81115046093E194300A5C91A /* Monster_Gladiator.cpp in Sources */, + 81115047093E194300A5C91A /* DebugGraph.cpp in Sources */, + 81115048093E194300A5C91A /* Monster_Berserker.cpp in Sources */, + 81115049093E194300A5C91A /* Monster_SlimyTransfer.cpp in Sources */, + 8111504A093E194300A5C91A /* VehicleStatic.cpp in Sources */, + 8111504B093E194300A5C91A /* MultiplayerGame.cpp in Sources */, + 8111504C093E194300A5C91A /* AI_States.cpp in Sources */, + 8111504D093E194300A5C91A /* Event.cpp in Sources */, + 8111504E093E194300A5C91A /* AFEntity.cpp in Sources */, + 8111504F093E194300A5C91A /* CTF.cpp in Sources */, + 81115050093E194300A5C91A /* GameState.cpp in Sources */, + 81115051093E194300A5C91A /* WeaponNailgun.cpp in Sources */, + 81115052093E194300A5C91A /* AI_Medic.cpp in Sources */, + 81115053093E194300A5C91A /* Physics.cpp in Sources */, + 81115054093E194300A5C91A /* VehicleAI.cpp in Sources */, + 81115055093E194300A5C91A /* WeaponHyperblaster.cpp in Sources */, + 81115056093E194300A5C91A /* Physics_Parametric.cpp in Sources */, + 81115057093E194300A5C91A /* Physics_Base.cpp in Sources */, + 81115058093E194300A5C91A /* Anim_Testmodel.cpp in Sources */, + 81115059093E194300A5C91A /* Class.cpp in Sources */, + 8111505A093E194300A5C91A /* Force_Constant.cpp in Sources */, + 8111505B093E194300A5C91A /* Script_Interpreter.cpp in Sources */, + 8111505C093E194300A5C91A /* Player_Cheats.cpp in Sources */, + 8111505D093E194300A5C91A /* AI_Manager.cpp in Sources */, + 8111505E093E194300A5C91A /* Misc.cpp in Sources */, + 8111505F093E194300A5C91A /* WeaponBlaster.cpp in Sources */, + 81115060093E194300A5C91A /* Icon.cpp in Sources */, + 81115061093E194300A5C91A /* AI_pathing.cpp in Sources */, + 81115062093E194300A5C91A /* VehicleAnimated.cpp in Sources */, + 81115063093E194300A5C91A /* Physics_StaticMulti.cpp in Sources */, + 81115064093E194300A5C91A /* GameEdit.cpp in Sources */, + 81115065093E194300A5C91A /* WorldSpawn.cpp in Sources */, + 81115066093E194300A5C91A /* SaveGame.cpp in Sources */, + 81115067093E194300A5C91A /* ClientMoveable.cpp in Sources */, + 81115068093E194300A5C91A /* AI_Debug.cpp in Sources */, + 81115069093E194300A5C91A /* Monster_LightTank.cpp in Sources */, + 8111506A093E194300A5C91A /* Game_local.cpp in Sources */, + 8111506B093E194300A5C91A /* Monster_BossBuddy.cpp in Sources */, + 8111506C093E194300A5C91A /* AI_Move.cpp in Sources */, + 8111506D093E194300A5C91A /* Anim.cpp in Sources */, + 8111506E093E194300A5C91A /* Monster_Scientist.cpp in Sources */, + 8111506F093E194300A5C91A /* ClientEntity.cpp in Sources */, + 81115070093E194300A5C91A /* SplineMover.cpp in Sources */, + 81115071093E194300A5C91A /* spawner.cpp in Sources */, + 81115072093E194300A5C91A /* VoiceComms.cpp in Sources */, + 81115073093E194300A5C91A /* Monster_BossMakron.cpp in Sources */, + 81115074093E194300A5C91A /* LipSync.cpp in Sources */, + 81115075093E194300A5C91A /* VehicleDriver.cpp in Sources */, + 81115076093E194300A5C91A /* Force_Spring.cpp in Sources */, + 81115077093E194300A5C91A /* SysCmds.cpp in Sources */, + 81115078093E194300A5C91A /* Monster_StroggHover.cpp in Sources */, + 81115079093E194300A5C91A /* AAS_debug.cpp in Sources */, + 8111507A093E194300A5C91A /* Force_Field.cpp in Sources */, + 8111507B093E194300A5C91A /* Actor.cpp in Sources */, + 8111507C093E194300A5C91A /* PlayerView.cpp in Sources */, + 8111507D093E194300A5C91A /* Item.cpp in Sources */, + 8111507E093E194300A5C91A /* WeaponGrenadeLauncher.cpp in Sources */, + 8111507F093E194300A5C91A /* Anim_Blend.cpp in Sources */, + 81115080093E194300A5C91A /* Monster_TeleportDropper.cpp in Sources */, + 81115081093E194300A5C91A /* Instance.cpp in Sources */, + 814E1D23097C1A7B009E5F86 /* ClientAFEntity.cpp in Sources */, + 2CCDAFD60A2CD52800D4AD93 /* Buying.cpp in Sources */, + 2CE8A5580A2CD90C00929ACB /* WeaponNapalmGun.cpp in Sources */, + 255BA9290BB85D6C00D112AC /* Lagometer.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 81114FE3093E189800A5C91A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = idlib_pic; + targetProxy = 81114FE2093E189800A5C91A /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 81114DFE093E117E00A5C91A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)/q4mp"; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G4; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_CFLAGS = "-fvisibility=hidden"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Debug; + }; + 81114DFF093E117E00A5C91A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = ( + ppc, + i386, + ); + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)/q4mp"; + COPY_PHASE_STRIP = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PREFIX_HEADER = "$(PREFIX_HEADER)"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = ../Resources/Game.plist; + INSTALL_PATH = "$(HOME)/Library/Bundles"; + OTHER_LDFLAGS = ""; + PREBINDING = NO; + PRODUCT_NAME = game.so; + STRIP_STYLE = "non-global"; + WRAPPER_EXTENSION = bundle; + ZERO_LINK = NO; + }; + name = Release; + }; + 81149F3C090D5FDC0025E084 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_ONE_BYTE_BOOL = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + GAME_MPAPI, + MACOS_X, + _DEBUG, + GAME_DLL, + Q4SDK, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ""; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Debug; + }; + 81149F3D090D5FDC0025E084 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_MODEL_TUNING = G5; + GCC_ONE_BYTE_BOOL = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + GAME_MPAPI, + MACOS_X, + _FINAL, + GAME_DLL, + Q4SDK, + ); + GCC_SYMBOLS_PRIVATE_EXTERN = YES; + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ( + "-finline", + "-finline-limit=1000", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-fpermissive", + "-fomit-frame-pointer", + "-fno-common", + ); + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SHARED_PRECOMPS_DIR = "$(SYMROOT)/Precomps/"; + SYMROOT = ../Build; + WARNING_CFLAGS = "-Wno-invalid-offsetof"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 81114DFD093E117E00A5C91A /* Build configuration list for PBXNativeTarget "game bundle" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81114DFE093E117E00A5C91A /* Debug */, + 81114DFF093E117E00A5C91A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 81149F3B090D5FDC0025E084 /* Build configuration list for PBXProject "mpgame" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 81149F3C090D5FDC0025E084 /* Debug */, + 81149F3D090D5FDC0025E084 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/src/sys/osx/apple_bool.h b/src/sys/osx/apple_bool.h new file mode 100644 index 0000000..6469bb2 --- /dev/null +++ b/src/sys/osx/apple_bool.h @@ -0,0 +1,41 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// With GCC and xCode, a bool is 4 bytes. There are two approaches to +// making bool one byte. You could #define bool as an unsigned char. The downside +// is that a function that take bool and one that takes a char will no longer have a unique function +// signature. You could #define bool to custom class that defines a bool operator, which +// fixes the function signature but includes others problems such as bool bitfields, +// classes that have a bool operator, C function that accept ... as a param, the keyword +// volatile. + +// The following approach works for the best for Doom because of the above issues, bitfields +// especially + +#ifdef bool +#undef bool +#endif + +#define bool unsigned char + diff --git a/src/sys/osx/q4sdk.xcodeproj/project.pbxproj b/src/sys/osx/q4sdk.xcodeproj/project.pbxproj new file mode 100644 index 0000000..982aa04 --- /dev/null +++ b/src/sys/osx/q4sdk.xcodeproj/project.pbxproj @@ -0,0 +1,231 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXAggregateTarget section */ + 251B05430C186CC600504968 /* All */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 251B05440C186CEA00504968 /* Build configuration list for PBXAggregateTarget "All" */; + buildPhases = ( + ); + dependencies = ( + 251B05480C186CF700504968 /* PBXTargetDependency */, + 251B054A0C186CFB00504968 /* PBXTargetDependency */, + ); + name = All; + productName = All; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXContainerItemProxy section */ + 25136BA80A473DEA00D3D311 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 25136B9B0A473DEA00D3D311 /* game.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 81114DFB093E117D00A5C91A; + remoteInfo = "game bundle"; + }; + 25136BAC0A473DEA00D3D311 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D2AAC046055464E500DB518D; + remoteInfo = idlib_pic; + }; + 251B05410C186CBA00504968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 251B05360C186CBA00504968 /* mpgame.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 81114DFB093E117D00A5C91A; + remoteInfo = "game bundle"; + }; + 251B05470C186CF700504968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 251B05360C186CBA00504968 /* mpgame.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 81114DFA093E117D00A5C91A; + remoteInfo = "game bundle"; + }; + 251B05490C186CFB00504968 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 25136B9B0A473DEA00D3D311 /* game.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 81114DFA093E117D00A5C91A; + remoteInfo = "game bundle"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 25136B9B0A473DEA00D3D311 /* game.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = game.xcodeproj; path = Subprojects/game.xcodeproj; sourceTree = ""; }; + 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = idlib.xcodeproj; path = Subprojects/idlib.xcodeproj; sourceTree = ""; }; + 251B05360C186CBA00504968 /* mpgame.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = mpgame.xcodeproj; path = Subprojects/mpgame.xcodeproj; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 25136B850A473D9D00D3D311 = { + isa = PBXGroup; + children = ( + 251B05360C186CBA00504968 /* mpgame.xcodeproj */, + 25136B9B0A473DEA00D3D311 /* game.xcodeproj */, + 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */, + ); + sourceTree = ""; + }; + 25136B9C0A473DEA00D3D311 /* Products */ = { + isa = PBXGroup; + children = ( + 25136BA90A473DEA00D3D311 /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; + 25136B9F0A473DEA00D3D311 /* Products */ = { + isa = PBXGroup; + children = ( + 25136BAD0A473DEA00D3D311 /* libidlib_pic.a */, + ); + name = Products; + sourceTree = ""; + }; + 251B05370C186CBA00504968 /* Products */ = { + isa = PBXGroup; + children = ( + 251B05420C186CBA00504968 /* game.so.bundle */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXProject section */ + 25136B870A473D9D00D3D311 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 25136B880A473D9D00D3D311 /* Build configuration list for PBXProject "q4sdk" */; + hasScannedForEncodings = 0; + mainGroup = 25136B850A473D9D00D3D311; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 25136B9C0A473DEA00D3D311 /* Products */; + ProjectRef = 25136B9B0A473DEA00D3D311 /* game.xcodeproj */; + }, + { + ProductGroup = 25136B9F0A473DEA00D3D311 /* Products */; + ProjectRef = 25136B9E0A473DEA00D3D311 /* idlib.xcodeproj */; + }, + { + ProductGroup = 251B05370C186CBA00504968 /* Products */; + ProjectRef = 251B05360C186CBA00504968 /* mpgame.xcodeproj */; + }, + ); + targets = ( + 251B05430C186CC600504968 /* All */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 25136BA90A473DEA00D3D311 /* game.so.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = game.so.bundle; + remoteRef = 25136BA80A473DEA00D3D311 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 25136BAD0A473DEA00D3D311 /* libidlib_pic.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libidlib_pic.a; + remoteRef = 25136BAC0A473DEA00D3D311 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 251B05420C186CBA00504968 /* game.so.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = game.so.bundle; + remoteRef = 251B05410C186CBA00504968 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXTargetDependency section */ + 251B05480C186CF700504968 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "game bundle"; + targetProxy = 251B05470C186CF700504968 /* PBXContainerItemProxy */; + }; + 251B054A0C186CFB00504968 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "game bundle"; + targetProxy = 251B05490C186CFB00504968 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 25136B890A473D9D00D3D311 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Debug; + }; + 25136B8A0A473D9D00D3D311 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Release; + }; + 251B05450C186CEA00504968 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = All; + }; + name = Debug; + }; + 251B05460C186CEA00504968 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + PRODUCT_NAME = All; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 25136B880A473D9D00D3D311 /* Build configuration list for PBXProject "q4sdk" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 25136B890A473D9D00D3D311 /* Debug */, + 25136B8A0A473D9D00D3D311 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 251B05440C186CEA00504968 /* Build configuration list for PBXAggregateTarget "All" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 251B05450C186CEA00504968 /* Debug */, + 251B05460C186CEA00504968 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 25136B870A473D9D00D3D311 /* Project object */; +} diff --git a/src/sys/scons/SConscript.game b/src/sys/scons/SConscript.game new file mode 100644 index 0000000..f3fa6ec --- /dev/null +++ b/src/sys/scons/SConscript.game @@ -0,0 +1,44 @@ +# -*- mode: python -*- +# Quake4 build script +# TTimo +# http://scons.sourceforge.net + +import sys, os +import scons_utils + +Import( 'GLOBALS' ) +Import( GLOBALS ) + +if ( local_mpgame == 1 ): + proj_list = scons_utils.ExtractSource( File( '#mpgame.vcproj' ).abspath ) +else: + proj_list = scons_utils.ExtractSource( File( '#game.vcproj' ).abspath ) + +for i in range( len( proj_list ) ): + proj_list[ i ] = '../../' + proj_list[ i ] + +local_env = g_game_env.Copy() + +if ( local_mpgame == 1 ): + local_env.Append( CPPDEFINES = [ 'GAME_MPAPI' ] ) + +if ( local_demo == 1 ): + local_env.Append( CPPDEFINES = [ 'ID_DEMO_BUILD' ] ) + +if ( local_gamedll == 1 ): + local_env.Append( CPPDEFINES = [ 'GAME_DLL' ] ) + if ( local_mpgame == 1 ): + ret = local_env.SharedLibrarySafe( local_env, 'mpgame', proj_list + idlib_objects ) + else: + ret = local_env.SharedLibrarySafe( local_env, 'game', proj_list + idlib_objects ) + if ( OSX_BUILDSTYLE == '2' ): + local_env.Append( CPPFLAGS = [ '-fvisibility=hidden' ] ) + Return( 'ret' ) +else: + local_env.Append( CPPDEFINES = [ 'GAME_MONO' ] ) + ret_list = [] + for i in proj_list: + ret_list += local_env.StaticObject( source = i ) + Return( 'ret_list' ) + + diff --git a/src/sys/scons/SConscript.idlib b/src/sys/scons/SConscript.idlib new file mode 100644 index 0000000..cef5b7e --- /dev/null +++ b/src/sys/scons/SConscript.idlib @@ -0,0 +1,41 @@ +# -*- mode: python -*- +# Quake4 build script +# TTimo +# http://scons.sourceforge.net + +import os.path, scons_utils + +Import( 'GLOBALS' ) +Import( GLOBALS ) + +idlib_list = scons_utils.ExtractSource( File( '#idlib.vcproj' ).abspath ) +asm_files = [ 'idlib/math/Simd_MMX.cpp' ] + +for i in asm_files: + idlib_list.remove( i ) + +local_env = g_env.Copy() +if ( local_smp == 1 and local_idlibpic == 0 ): + # idlib compiled for SMP engine code needs the define + local_env.Append( CPPDEFINES = [ 'ENABLE_INTEL_SMP' ] ) +if ( GCC_X86_ASM == '1' ): + local_env.Append( CPPDEFINES = [ 'ID_GCC_X86_ASM' ] ) + +with_asm_env = g_env_noopt.Copy() +with_asm_env.Append( CPPFLAGS = [ '-masm=intel' ] ) + +ret_list = [] +for f in idlib_list: + if ( local_idlibpic == 0 ): + ret_list += local_env.StaticObject( source = os.path.join( '../..', f ) ) + else: + ret_list += local_env.SharedObject( source = os.path.join( '../..', f ) ) + +if ( GCC_X86_ASM == '1' ): + for f in asm_files: + if ( local_idlibpic == 0 ): + ret_list += with_asm_env.StaticObject( source = os.path.join( '../..', f ) ) + else: + ret_list += with_asm_env.SharedObject( source = os.path.join( '../..', f ) ) + +Return( 'ret_list' ) diff --git a/src/sys/scons/scons_utils.py b/src/sys/scons/scons_utils.py new file mode 100644 index 0000000..5d3c301 --- /dev/null +++ b/src/sys/scons/scons_utils.py @@ -0,0 +1,194 @@ +# -*- mode: python -*- +import sys, os, string, time, commands, re, pickle, StringIO, popen2, commands, pdb, zipfile +import SCons + +# need an Environment and a matching buffered_spawn API .. encapsulate +class idBuffering: + + silent = False + + def buffered_spawn( self, sh, escape, cmd, args, env ): + stderr = StringIO.StringIO() + stdout = StringIO.StringIO() + command_string = '' + for i in args: + if ( len( command_string ) ): + command_string += ' ' + command_string += i + try: + retval = self.env['PSPAWN']( sh, escape, cmd, args, env, stdout, stderr ) + except OSError, x: + if x.errno != 10: + raise x + print 'OSError ignored on command: %s' % command_string + retval = 0 + print command_string + if ( retval != 0 or not self.silent ): + sys.stdout.write( stdout.getvalue() ) + sys.stderr.write( stderr.getvalue() ) + return retval + +class idSetupBase: + + def SimpleCommand( self, cmd ): + print cmd + ret = commands.getstatusoutput( cmd ) + if ( len( ret[ 1 ] ) ): + sys.stdout.write( ret[ 1 ] ) + sys.stdout.write( '\n' ) + if ( ret[ 0 ] != 0 ): + raise 'command failed' + return ret[ 1 ] + + def TrySimpleCommand( self, cmd ): + print cmd + ret = commands.getstatusoutput( cmd ) + sys.stdout.write( ret[ 1 ] ) + + def M4Processing( self, file, d ): + file_out = file[:-3] + cmd = 'm4 ' + for ( key, val ) in d.items(): + cmd += '--define=%s="%s" ' % ( key, val ) + cmd += '%s > %s' % ( file, file_out ) + self.SimpleCommand( cmd ) + + def ExtractProtocolVersion( self ): + f = open( 'framework/Licensee.h' ) + l = f.readlines() + f.close() + + major = 'X' + p = re.compile( '^#define ASYNC_PROTOCOL_MAJOR\t*(.*)' ) + for i in l: + if ( p.match( i ) ): + major = p.match( i ).group(1) + break + + f = open( 'framework/async/AsyncNetwork.h' ) + l = f.readlines() + f.close() + + minor = 'X' + p = re.compile( '^const int ASYNC_PROTOCOL_MINOR\t*= (.*);' ) + for i in l: + if ( p.match( i ) ): + minor = p.match( i ).group(1) + break + + return '%s.%s' % ( major, minor ) + + def ExtractEngineVersion( self ): + f = open( 'framework/Licensee.h' ) + l = f.readlines() + f.close() + + version = 'X' + p = re.compile( '^#define.*ENGINE_VERSION\t*"DOOM (.*)"' ) + for i in l: + if ( p.match( i ) ): + version = p.match( i ).group(1) + break + + return version + + def ExtractBuildVersion( self ): + f = open( 'framework/BuildVersion.h' ) + l = f.readlines()[ 4 ] + f.close() + pat = re.compile( '.* = (.*);\n' ) + return pat.split( l )[ 1 ] + +def checkLDD( target, source, env ): + file = target[0] + if (not os.path.isfile(file.abspath)): + print('ERROR: CheckLDD: target %s not found\n' % target[0]) + Exit(1) + ( status, output ) = commands.getstatusoutput( 'ldd -r %s' % file ) + if ( status != 0 ): + print 'ERROR: ldd command returned with exit code %d' % ldd_ret + os.system( 'rm %s' % target[ 0 ] ) + sys.exit(1) + lines = string.split( output, '\n' ) + have_undef = 0 + for i_line in lines: + #print repr(i_line) + regex = re.compile('undefined symbol: (.*)\t\\((.*)\\)') + if ( regex.match(i_line) ): + symbol = regex.sub('\\1', i_line) + try: + env['ALLOWED_SYMBOLS'].index(symbol) + except: + have_undef = 1 + if ( have_undef ): + print output + print "ERROR: undefined symbols" + os.system('rm %s' % target[0]) + sys.exit(1) + +def SharedLibrarySafe( env, target, source ): + ret = env.SharedLibrary( target, source ) + if ( env['OS'] != 'Darwin' ): + env.AddPostAction( ret, checkLDD ) + return ret + +def NotImplementedStub( *whatever ): + print 'Not Implemented' + sys.exit( 1 ) + +# -------------------------------------------------------------------- + +# get a clean error output when running multiple jobs +def SetupBufferedOutput( env, silent ): + buf = idBuffering() + buf.silent = silent + buf.env = env + env['SPAWN'] = buf.buffered_spawn + +# setup utilities on an environement +def SetupUtils( env ): + env.SharedLibrarySafe = SharedLibrarySafe + if ( os.path.exists( 'sys/scons/SDK.py' ) ): + import SDK + sdk = SDK.idSDK() + env.BuildSDK = sdk.BuildSDK + else: + env.BuildSDK = NotImplementedStub + + if ( os.path.exists( 'sys/scons/Setup.py' ) ): + import Setup + setup = Setup.idSetup() + env.Prepare = setup.Prepare + env.BuildSetup = setup.BuildSetup + env.BuildGamePak = setup.BuildGamePak + else: + env.Prepare = NotImplementedStub + env.BuildSetup = NotImplementedStub + env.BuildGamePak = NotImplementedStub + + if ( os.path.exists( 'sys/scons/OSX.py' ) ): + import OSX + OSX = OSX.idOSX() + env.BuildBundle = OSX.BuildBundle + else: + env.BuildBundle = NotImplementedStub + +def BuildList( s_prefix, s_string ): + s_list = string.split( s_string ) + for i in range( len( s_list ) ): + s_list[ i ] = s_prefix + '/' + s_list[ i ] + return s_list + +def ExtractSource( file ): + from xml.dom.minidom import parse + dom = parse( file ) + files = dom.getElementsByTagName( 'File' ) + l = [] + for i in files: + s = i.getAttribute( 'RelativePath' ) + s = s.encode('ascii', 'ignore') + s = re.sub( '\\\\', '/', s ) + s = re.sub( '^\./', '', s ) + if ( string.lower( s[-4:] ) == '.cpp' or string.lower( s[-2:] ) == '.c' ): + l.append( s ) + return l diff --git a/src/sys/sys_local.cpp b/src/sys/sys_local.cpp new file mode 100644 index 0000000..53beb9e --- /dev/null +++ b/src/sys/sys_local.cpp @@ -0,0 +1,272 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop +#include "sys_local.h" +#include "win32/win_local.h" + +const char * sysLanguageNames[] = { + "english", "spanish", "italian", "german", "french", "russian", + "polish", "korean", "japanese", "chinese", NULL +}; + +idCVar sys_lang( "sys_lang", "english", CVAR_SYSTEM | CVAR_ARCHIVE, "", sysLanguageNames, idCmdSystem::ArgCompletion_String ); + +idSysLocal sysLocal; +idSys * sys = &sysLocal; + +idSysLocal::keyPress_t idSysLocal::keyPress[256]; +int idSysLocal::head = 0; +int idSysLocal::tail = 0; +int idSysLocal::free = 250; + +void idSysLocal::DebugPrintf( const char *fmt, ... ) { + va_list argptr; + + va_start( argptr, fmt ); + Sys_DebugVPrintf( fmt, argptr ); + va_end( argptr ); +} + +void idSysLocal::DebugVPrintf( const char *fmt, va_list arg ) { + Sys_DebugVPrintf( fmt, arg ); +} + +double idSysLocal::GetClockTicks( void ) { + return Sys_GetClockTicks(); +} + +double idSysLocal::ClockTicksPerSecond( void ) { + return Sys_ClockTicksPerSecond(); +} + +cpuid_t idSysLocal::GetProcessorId( void ) { + return Sys_GetProcessorId(); +} + +const char *idSysLocal::GetProcessorString( void ) { + return Sys_GetProcessorString(); +} + +const char *idSysLocal::FPU_GetState( void ) { + return Sys_FPU_GetState(); +} + +bool idSysLocal::FPU_StackIsEmpty( void ) { + return Sys_FPU_StackIsEmpty(); +} + +void idSysLocal::FPU_SetFTZ( bool enable ) { + Sys_FPU_SetFTZ( enable ); +} + +void idSysLocal::FPU_SetDAZ( bool enable ) { + Sys_FPU_SetDAZ( enable ); +} + +bool idSysLocal::LockMemory( void *ptr, int bytes ) { + return Sys_LockMemory( ptr, bytes ); +} + +bool idSysLocal::UnlockMemory( void *ptr, int bytes ) { + return Sys_UnlockMemory( ptr, bytes ); +} + +void idSysLocal::GetCallStack( address_t *callStack, const int callStackSize ) { + Sys_GetCallStack( callStack, callStackSize ); +} + +const char * idSysLocal::GetCallStackStr( const address_t *callStack, const int callStackSize ) { + return Sys_GetCallStackStr( callStack, callStackSize ); +} + +const char * idSysLocal::GetCallStackCurStr( int depth ) { + return Sys_GetCallStackCurStr( depth ); +} + +void idSysLocal::ShutdownSymbols( void ) { + Sys_ShutdownSymbols(); +} + +int idSysLocal::DLL_Load( const char *dllName ) { + return Sys_DLL_Load( dllName ); +} + +void *idSysLocal::DLL_GetProcAddress( int dllHandle, const char *procName ) { + return Sys_DLL_GetProcAddress( dllHandle, procName ); +} + +void idSysLocal::DLL_Unload( int dllHandle ) { + Sys_DLL_Unload( dllHandle ); +} + +void idSysLocal::DLL_GetFileName( const char *baseName, char *dllName, int maxLength ) { +#ifdef _WIN32 + idStr::snPrintf( dllName, maxLength, "%s" CPUSTRING ".dll", baseName ); +#elif defined( __linux__ ) + idStr::snPrintf( dllName, maxLength, "%s" CPUSTRING ".so", baseName ); +#elif defined( MACOS_X ) + idStr::snPrintf( dllName, maxLength, "%s" ".dylib", baseName ); +#else +#error OS define is required +#endif +} + +sysEvent_t idSysLocal::GenerateMouseButtonEvent( int button, bool down ) { + sysEvent_t ev; + ev.evType = SE_KEY; + ev.evValue = K_MOUSE1 + button - 1; + ev.evValue2 = down; + ev.evPtrLength = 0; + ev.evPtr = NULL; + return ev; +} + +sysEvent_t idSysLocal::GenerateMouseMoveEvent( int deltax, int deltay ) { + sysEvent_t ev; + ev.evType = SE_MOUSE; + ev.evValue = deltax; + ev.evValue2 = deltay; + ev.evPtrLength = 0; + ev.evPtr = NULL; + return ev; +} + +void idSysLocal::FPU_SetPrecision( int flags ) { + Sys_FPU_SetPrecision( flags ); +} + +int idSysLocal::MapKey( unsigned long lParam, unsigned short wParam ) { + return Sys_MapKey( lParam, wParam ); +} + +void idSysLocal::AddKeyPress( int key, bool state ) { + if ( free <= 0 ) { + return; + } + free--; + keyPress[head].keynum = (short)key; + keyPress[head].state = state; + head = ( head + 1 ) & 255; +} + +bool idSysLocal::GetKeyPress( const int n, int &key, bool &state ) { + (void)n; + if ( free >= 250 ) { + return false; + } + key = keyPress[tail].keynum; + state = keyPress[tail].state; + free++; + tail = ( tail + 1 ) & 255; + return true; +} + +int idSysLocal::GetNumKeyPresses( void ) { + return 250 - free; +} + +void idSysLocal::ShowConsole( int visLevel, bool quitOnClose ) { Sys_ShowConsole( visLevel, quitOnClose ); } +void idSysLocal::UpdateConsole( void ) {} +void idSysLocal::SetConsoleName( const char *consoleName ) { SetConsoleTitleA( consoleName ? consoleName : "Quake4" ); } +bool idSysLocal::IsAppActive( void ) const { return win32.activeApp; } +int idSysLocal::Milliseconds( void ) { return Sys_Milliseconds(); } +void idSysLocal::InitInput( void ) { Sys_InitInput(); } +void idSysLocal::ShutdownInput( void ) { Sys_ShutdownInput(); } +void idSysLocal::GenerateEvents( void ) { Sys_GenerateEvents(); } +void idSysLocal::GrabMouseCursor( bool grabIt ) { Sys_GrabMouseCursor( grabIt ); } + +FILE *idSysLocal::FOpen( const char *name, const char *mode ) { return fopen( name, mode ); } +void idSysLocal::FPrintf( FILE *file, const char *fmt ) { fprintf( file, "%s", fmt ); } +int idSysLocal::FTell( FILE *file ) { return ftell( file ); } +int idSysLocal::FSeek( FILE *file, long offset, int mode ) { return fseek( file, offset, mode ); } +void idSysLocal::FClose( FILE *file ) { fclose( file ); } +int idSysLocal::FRead( void *buffer, int size, int count, FILE *file ) { return fread( buffer, size, count, file ); } +int idSysLocal::FWrite( void *buffer, int size, int count, FILE *file ) { return fwrite( buffer, size, count, file ); } +long idSysLocal::FileTimeStamp( FILE *file ) { return (long)Sys_FileTimeStamp( file ); } +int idSysLocal::FEof( FILE *stream ) { return feof( stream ); } +char *idSysLocal::FGets( char *string, int n, FILE *stream ) { return fgets( string, n, stream ); } +void idSysLocal::FFlush( FILE *file ) { fflush( file ); } +int idSysLocal::SetVBuf( FILE *stream, char *buffer, int mode, size_t size ) { return setvbuf( stream, buffer, mode, size ); } + +int idSysLocal::GetGUID( char *buf, int buflen ) { + if ( buf && buflen > 0 ) { + buf[0] = '\0'; + } + return 0; +} + +/* +================= +Sys_TimeStampToStr +================= +*/ +const char *Sys_TimeStampToStr( long timeStamp ) { + static char timeString[MAX_STRING_CHARS]; + timeString[0] = '\0'; + + time_t localTimeStamp = (time_t)timeStamp; + tm* time = localtime( &localTimeStamp ); + idStr out; + + idStr lang = cvarSystem->GetCVarString( "sys_lang" ); + if ( lang.Icmp( "english" ) == 0 ) { + // english gets "month/day/year hour:min" + "am" or "pm" + out = va( "%02d", time->tm_mon + 1 ); + out += "/"; + out += va( "%02d", time->tm_mday ); + out += "/"; + out += va( "%d", time->tm_year + 1900 ); + out += "\t"; + if ( time->tm_hour > 12 ) { + out += va( "%02d", time->tm_hour - 12 ); + } else if ( time->tm_hour == 0 ) { + out += "12"; + } else { + out += va( "%02d", time->tm_hour ); + } + out += ":"; + out +=va( "%02d", time->tm_min ); + if ( time->tm_hour >= 12 ) { + out += "pm"; + } else { + out += "am"; + } + } else { + // europeans get "day/month/year 24hour:min" + out = va( "%02d", time->tm_mday ); + out += "/"; + out += va( "%02d", time->tm_mon + 1 ); + out += "/"; + out += va( "%d", time->tm_year + 1900 ); + out += "\t"; + out += va( "%02d", time->tm_hour ); + out += ":"; + out += va( "%02d", time->tm_min ); + } + idStr::Copynz( timeString, out, sizeof( timeString ) ); + + return timeString; +} diff --git a/src/sys/sys_local.h b/src/sys/sys_local.h new file mode 100644 index 0000000..b618b65 --- /dev/null +++ b/src/sys/sys_local.h @@ -0,0 +1,120 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_LOCAL__ +#define __SYS_LOCAL__ + +/* +============================================================== + + idSysLocal + +============================================================== +*/ + +class idSysLocal : public idSys { +public: + virtual void DebugPrintf( const char *fmt, ... )id_attribute((format(printf,2,3))); + virtual void DebugVPrintf( const char *fmt, va_list arg ); + + virtual double GetClockTicks( void ); + virtual double ClockTicksPerSecond( void ); + virtual cpuid_t GetProcessorId( void ); + virtual const char * GetProcessorString( void ); + virtual const char * FPU_GetState( void ); + virtual bool FPU_StackIsEmpty( void ); + virtual void FPU_SetFTZ( bool enable ); + virtual void FPU_SetDAZ( bool enable ); + virtual void FPU_SetPrecision( int flags ); + + virtual void GetCallStack( address_t *callStack, const int callStackSize ); + virtual const char * GetCallStackStr( const address_t *callStack, const int callStackSize ); + virtual const char * GetCallStackCurStr( int depth ); + virtual void ShutdownSymbols( void ); + + virtual bool LockMemory( void *ptr, int bytes ); + virtual bool UnlockMemory( void *ptr, int bytes ); + + virtual int DLL_Load( const char *dllName ); + virtual void * DLL_GetProcAddress( int dllHandle, const char *procName ); + virtual void DLL_Unload( int dllHandle ); + virtual void DLL_GetFileName( const char *baseName, char *dllName, int maxLength ); + + virtual sysEvent_t GenerateMouseButtonEvent( int button, bool down ); + virtual sysEvent_t GenerateMouseMoveEvent( int deltax, int deltay ); + + virtual int MapKey( unsigned long lParam, unsigned short wParam ); + virtual void AddKeyPress( int key, bool state ); + virtual int GetNumKeyPresses( void ); + virtual bool GetKeyPress( const int n, int &key, bool &state ); + + virtual void * CreateWindowEx( const char *className, const char *windowName, int style, int x, int y, int w, int h, void *parent, void *menu, void *instance, void *param, int extStyle = 0 ); + virtual void * GetDC( void *hWnd ); + virtual void ReleaseDC( void *hWnd, void *hDC ); + virtual void ShowWindow( void *hWnd, int show ); + virtual void UpdateWindow( void *hWnd ); + virtual bool IsWindowVisible( void *hWnd ); + virtual void SetForegroundWindow( void *hWnd ); + virtual void SetFocus( void *hWnd ); + virtual void DestroyWindow( void *hWnd ); + + virtual void ShowConsole( int visLevel, bool quitOnClose ); + virtual void UpdateConsole( void ); + virtual void SetConsoleName( const char *consoleName ); + virtual bool IsAppActive( void ) const; + virtual int Milliseconds( void ); + virtual void InitInput( void ); + virtual void ShutdownInput( void ); + virtual void GenerateEvents( void ); + virtual void GrabMouseCursor( bool grabIt ); + + virtual FILE * FOpen( const char *name, const char *mode ); + virtual void FPrintf( FILE *file, const char *fmt ); + virtual int FTell( FILE *file ); + virtual int FSeek( FILE *file, long offset, int mode ); + virtual void FClose( FILE *file ); + virtual int FRead( void *buffer, int size, int count, FILE *file ); + virtual int FWrite( void *buffer, int size, int count, FILE *file ); + virtual long FileTimeStamp( FILE *file ); + virtual int FEof( FILE *stream ); + virtual char * FGets( char *string, int n, FILE *stream ); + virtual void FFlush( FILE *file ); + virtual int SetVBuf( FILE *stream, char *buffer, int mode, size_t size ); + + virtual void OpenURL( const char *url, bool quit ); + virtual void StartProcess( const char *exeName, bool quit ); + virtual int GetGUID( char *buf, int buflen ); + +private: + struct keyPress_t { + short keynum; + bool state; + }; + static keyPress_t keyPress[256]; + static int head; + static int tail; + static int free; +}; + +#endif /* !__SYS_LOCAL__ */ diff --git a/src/sys/sys_public.h b/src/sys/sys_public.h new file mode 100644 index 0000000..a0a5b5b --- /dev/null +++ b/src/sys/sys_public.h @@ -0,0 +1,976 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SYS_PUBLIC__ +#define __SYS_PUBLIC__ + + +/* +=============================================================================== + + Non-portable system services. + +=============================================================================== +*/ + + +// Win32 +#ifdef _WINDOWS + +#define BUILD_STRING "win-x86" +#define BUILD_OS_ID 0 +#define CPUSTRING "x86" +#define CPU_EASYARGS 1 + +#define ALIGN16( x ) __declspec(align(16)) x +#define PACKED + +#define _alloca16( x ) ((void *)((((int)_alloca( (x)+15 )) + 15) & ~15)) + +#define ID_INLINE __forceinline +#define ID_STATIC_TEMPLATE static + +#define assertmem( x, y ) assert( _CrtIsValidPointer( x, y, true ) ) + +#endif + +#ifdef __GNUC__ +#define id_attribute(x) __attribute__(x) +#else +#define id_attribute(x) +#endif + +// Mac OSX +#if defined(MACOS_X) || defined(__APPLE__) + +#include + +#if __GNUC__ < 4 +#include "osx/apple_bool.h" +#endif + +#define BUILD_STRING "MacOSX-universal" +#define BUILD_OS_ID 1 +#ifdef __ppc__ + #define CPUSTRING "ppc" + #define CPU_EASYARGS 0 +#elif defined(__i386__) + #define CPUSTRING "x86" + #define CPU_EASYARGS 1 +#endif + +#define ALIGN16( x ) x __attribute__ ((aligned (16))) +#ifdef __MWERKS__ +#define PACKED +#else +#define PACKED __attribute__((packed)) +#endif + +#define _alloca alloca +#define _alloca16( x ) ((void *)((((int)alloca( (x)+15 )) + 15) & ~15)) + +#define __cdecl +#define ASSERT assert + +#define ID_STATIC_TEMPLATE + +#define assertmem( x, y ) + +#endif + + +// Linux +#ifdef __linux__ + +#ifdef __i386__ + #define BUILD_STRING "linux-x86" + #define BUILD_OS_ID 2 + #define CPUSTRING "x86" + #define CPU_EASYARGS 1 +#elif defined(__ppc__) + #define BUILD_STRING "linux-ppc" + #define CPUSTRING "ppc" + #define CPU_EASYARGS 0 +#endif + +#define _alloca alloca +#define _alloca16( x ) ((void *)((((int)alloca( (x)+15 )) + 15) & ~15)) + +#define ALIGN16( x ) x +#define PACKED __attribute__((packed)) + +#define __cdecl +#define ASSERT assert + +#define ID_INLINE inline +#define ID_STATIC_TEMPLATE + +#define assertmem( x, y ) + +#endif + +typedef enum { + CPUID_NONE = 0x00000, + CPUID_UNSUPPORTED = 0x00001, // unsupported (386/486) + CPUID_GENERIC = 0x00002, // unrecognized processor + CPUID_INTEL = 0x00004, // Intel + CPUID_AMD = 0x00008, // AMD + CPUID_MMX = 0x00010, // Multi Media Extensions + CPUID_3DNOW = 0x00020, // 3DNow! + CPUID_SSE = 0x00040, // Streaming SIMD Extensions + CPUID_SSE2 = 0x00080, // Streaming SIMD Extensions 2 + CPUID_SSE3 = 0x00100, // Streaming SIMD Extentions 3 aka Prescott's New Instructions + CPUID_ALTIVEC = 0x00200, // AltiVec + CPUID_HTT = 0x01000, // Hyper-Threading Technology + CPUID_CMOV = 0x02000, // Conditional Move (CMOV) and fast floating point comparison (FCOMI) instructions + CPUID_FTZ = 0x04000, // Flush-To-Zero mode (denormal results are flushed to zero) + CPUID_DAZ = 0x08000, // Denormals-Are-Zero mode (denormal source operands are set to zero) + CPUID_EM64T = 0x10000, // 64-bit Memory Extensions + CPUID_PENTIUMM = 0x20000, // Pentium M technology - high performance per MHz +#ifdef MACOS_X + CPUID_PPC = 0x40000, // PowerPC G4/G5 +#endif +// RAVEN BEGIN + CPUID_XENON = 0x80000 // Xenon PPC processor +// RAVEN END +} cpuid_t; + +typedef enum { + FPU_EXCEPTION_INVALID_OPERATION = 1, + FPU_EXCEPTION_DENORMALIZED_OPERAND = 2, + FPU_EXCEPTION_DIVIDE_BY_ZERO = 4, + FPU_EXCEPTION_NUMERIC_OVERFLOW = 8, + FPU_EXCEPTION_NUMERIC_UNDERFLOW = 16, + FPU_EXCEPTION_INEXACT_RESULT = 32 +} fpuExceptions_t; + +typedef enum { + FPU_PRECISION_SINGLE = 0, + FPU_PRECISION_DOUBLE = 1, + FPU_PRECISION_DOUBLE_EXTENDED = 2 +} fpuPrecision_t; + +typedef enum { + FPU_ROUNDING_TO_NEAREST = 0, + FPU_ROUNDING_DOWN = 1, + FPU_ROUNDING_UP = 2, + FPU_ROUNDING_TO_ZERO = 3 +} fpuRounding_t; + +typedef enum { + SE_NONE, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE // evPtr is a char*, from typing something at a non-game console +// RAVEN BEGIN +// rjohnson: debug event overflow stuff + , + SE_MAX +// RAVEN END +} sysEventType_t; + +typedef enum { + M_ACTION1, + M_ACTION2, + M_ACTION3, + M_ACTION4, + M_ACTION5, + M_ACTION6, + M_ACTION7, + M_ACTION8, + M_DELTAX, + M_DELTAY, + M_DELTAZ +} sys_mEvents; + +// RAVEN BEGIN +// rjohnson: new joystick code +#define MAX_AXIS_RANGE 127 +#define JOY_TO_CURSOR_SPEED ( idMath::M_MS2SEC * common->GetUserCmdMSec() * 1.5f * 140.f ) + +typedef enum { + AXIS_LEFT_HORIZONTAL, + AXIS_LEFT_VERTICAL, + AXIS_RIGHT_HORIZONTAL, + AXIS_RIGHT_VERTICAL, + MAX_JOYSTICK_AXIS +} joystickAxis_t; + +typedef enum { + J_ACTION_BUTTON_LEFT_SHOULDER, // K_JOY1 + J_ACTION_BUTTON_RIGHT_SHOULDER, // K_JOY2 + J_ACTION_BUTTON_A, // K_JOY3 + J_ACTION_BUTTON_B, // K_JOY4 + J_ACTION_BUTTON_Y, // K_JOY5 + J_ACTION_BUTTON_X, // K_JOY6 + J_ACTION_BUTTON_START, // K_JOY7 + J_ACTION_BUTTON_BACK, // K_JOY8 + J_ACTION_BUTTON_DPAD_UP, // K_JOY9 + J_ACTION_BUTTON_DPAD_DOWN, // K_JOY10 + J_ACTION_BUTTON_DPAD_RIGHT, // K_JOY11 + J_ACTION_BUTTON_DPAD_LEFT, // K_JOY12 + J_ACTION_BUTTON_AXIS_LEFT, // K_JOY13 + J_ACTION_BUTTON_AXIS_RIGHT, // K_JOY14 + J_ACTION_BUTTON_LEFT_TRIGGER, // K_JOY16 + J_ACTION_BUTTON_RIGHT_TRIGGER, // K_JOY15 + + J_DELTA_LEFT_HORIZONTAL, + J_DELTA_LEFT_VERTICAL, + J_DELTA_RIGHT_HORIZONTAL, + J_DELTA_RIGHT_VERTICAL, + + J_ACTION_BUTTON_GARBAGE, +} sys_jEvents; +// RAVEN END + +typedef struct sysEvent_s { + sysEventType_t evType; + int evValue; + int evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void * evPtr; // this must be manually freed if not NULL +} sysEvent_t; + +typedef struct sysMemoryStats_s { + int memoryLoad; + int totalPhysical; + int availPhysical; + int totalPageFile; + int availPageFile; + int totalVirtual; + int availVirtual; + int availExtendedVirtual; +} sysMemoryStats_t; + +typedef unsigned long address_t; + +template class idList; // for Sys_ListFiles + +struct sysTime_t { + int tm_sec; /* seconds after the minute - [0,59] */ + int tm_min; /* minutes after the hour - [0,59] */ + int tm_hour; /* hours since midnight - [0,23] */ + int tm_mday; /* day of the month - [1,31] */ + int tm_mon; /* months since January - [0,11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday - [0,6] */ + int tm_yday; /* days since January 1 - [0,365] */ + int tm_isdst; /* daylight savings time flag */ +}; + +void Sys_Init( void ); +void Sys_Shutdown( void ); +void Sys_Error( const char *error, ...); +void Sys_Quit( void ); + +bool Sys_AlreadyRunning( void ); + +// note that this isn't journaled... +char * Sys_GetClipboardData( void ); +void Sys_SetClipboardData( const char *string ); + +// will go to the various text consoles +// NOT thread safe - never use in the async paths +void Sys_Printf( const char *msg, ... )id_attribute((format(printf,1,2))); + +// guaranteed to be thread-safe +void Sys_DebugPrintf( const char *fmt, ... )id_attribute((format(printf,1,2))); +void Sys_DebugVPrintf( const char *fmt, va_list arg ); + +// a decent minimum sleep time to avoid going below the process scheduler speeds +#define SYS_MINSLEEP 20 + +// allow game to yield CPU time +// NOTE: due to SYS_MINSLEEP this is very bad portability karma, and should be completely removed +void Sys_Sleep( int msec ); + +// returns whether the main rendering window has focus +bool Sys_IsAppActive( void ); + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds( void ); + +// for accurate performance testing +double Sys_GetClockTicks( void ); +double Sys_ClockTicksPerSecond( void ); + +// returns a selection of the CPUID_* flags +cpuid_t Sys_GetProcessorId( void ); +const char * Sys_GetProcessorString( void ); + +// returns true if the FPU stack is empty +bool Sys_FPU_StackIsEmpty( void ); + +// empties the FPU stack +void Sys_FPU_ClearStack( void ); + +// returns the FPU state as a string +const char * Sys_FPU_GetState( void ); + +// enables the given FPU exceptions +void Sys_FPU_EnableExceptions( int exceptions ); + +// sets the FPU precision +void Sys_FPU_SetPrecision( int precision ); + +// sets the FPU rounding mode +void Sys_FPU_SetRounding( int rounding ); + +// sets Flush-To-Zero mode (only available when CPUID_FTZ is set) +void Sys_FPU_SetFTZ( bool enable ); + +// sets Denormals-Are-Zero mode (only available when CPUID_DAZ is set) +void Sys_FPU_SetDAZ( bool enable ); + +// returns amount of system ram +int Sys_GetSystemRam( void ); + +// returns amount of video ram +int Sys_GetVideoRam( void ); + +// returns amount of drive space in path +int Sys_GetDriveFreeSpace( const char *path ); + +// returns memory stats +void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ); +void Sys_GetExeLaunchMemoryStatus( sysMemoryStats_t &stats ); + +// lock and unlock memory +bool Sys_LockMemory( void *ptr, int bytes ); +bool Sys_UnlockMemory( void *ptr, int bytes ); + +// set amount of physical work memory +void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ); + +// allows retrieving the call stack at execution points +void Sys_GetCallStack( address_t *callStack, const int callStackSize ); +const char * Sys_GetCallStackStr( const address_t *callStack, const int callStackSize ); +const char * Sys_GetCallStackCurStr( int depth ); +const char * Sys_GetCallStackCurAddressStr( int depth ); +void Sys_ShutdownSymbols( void ); + +#if defined( _WIN32 ) +void Sys_ShowConsole( int visLevel, bool quitOnClose ); +void Sys_ShowWindow( bool show ); +void Sys_SetFatalError( const char *error ); +void Sys_DoPreferences( void ); +void Sys_InitScanTable( void ); +const unsigned char *Sys_GetScanTable( void ); +#endif + +// DLL loading, the path should be a fully qualified OS path to the DLL file to be loaded +int Sys_DLL_Load( const char *dllName ); +void * Sys_DLL_GetProcAddress( int dllHandle, const char *procName ); +void Sys_DLL_Unload( int dllHandle ); + +// event generation +void Sys_GenerateEvents( void ); +sysEvent_t Sys_GetEvent( void ); +void Sys_ClearEvents( void ); + +// input is tied to windows, so it needs to be started up and shut down whenever +// the main window is recreated +void Sys_InitInput( void ); +void Sys_ShutdownInput( void ); +unsigned char Sys_GetConsoleKey( bool shifted ); +// keyboard input polling +int Sys_PollKeyboardInputEvents( void ); +int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state ); +void Sys_EndKeyboardInputEvents( void ); +int Sys_MapKey( unsigned long key, unsigned short wParam ); + +// mouse input polling +int Sys_PollMouseInputEvents( void ); +int Sys_ReturnMouseInputEvent( const int n, int &action, int &value ); +void Sys_EndMouseInputEvents( void ); + +// RAVEN BEGIN +// ksergent: joystick input polling +int Sys_PollJoystickInputEvents( void ); +bool Sys_IsJoystickEnabled( void ); +bool Sys_IsJoystickConnected( int index ); +int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ); +void Sys_EndJoystickInputEvents( void ); +// RAVEN END + +// when the console is down, or the game is about to perform a lengthy +// operation like map loading, the system can release the mouse cursor +// when in windowed mode +void Sys_GrabMouseCursor( bool grabIt ); +bool Sys_IsWindowVisible( void ); +void Sys_Mkdir( const char *path ); +ID_TIME_T Sys_FileTimeStamp( FILE *fp ); + +// RAVEN BEGIN +// jscott: thread handling +void Sys_StartAsyncThread( void ); +void Sys_EndAsyncThread( void ); + +// jscott: VTune interface +#ifndef _FINAL +void Sys_StartProfiling( void ); +void Sys_StopProfiling( void ); +#endif +// RAVEN END + +// NOTE: do we need to guarantee the same output on all platforms? +const char * Sys_TimeStampToStr( long timeStamp ); +const char * Sys_DefaultCDPath( void ); +const char * Sys_DefaultBasePath( void ); +const char * Sys_DefaultSavePath( void ); +const char * Sys_EXEPath( void ); + +// for getting current system (real world) time +int Sys_RealTime( sysTime_t* sysTime ); + +// use fs_debug to verbose Sys_ListFiles +// returns -1 if directory was not found (the list is cleared) +int Sys_ListFiles( const char *directory, const char *extension, idList &list ); + +// RAVEN BEGIN +// rjohnson: added block +bool Sys_AppShouldSleep ( void ); +// RAVEN END + +/* +============================================================== + + Networking + +============================================================== +*/ + +typedef enum { + NA_BAD, // an address lookup failed + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, +// RAVEN BEGIN +// rjohnson: add fake clients + NA_FAKE, +// RAVEN END + NA_GAME, // bots, etc +} netadrtype_t; + +typedef struct { + netadrtype_t type; + unsigned char ip[4]; + unsigned short port; +} netadr_t; + +#define PORT_ANY -1 + +class idPort { +public: + idPort(); // this just zeros netSocket and port + ~idPort(); + + // if the InitForPort fails, the idPort.port field will remain 0 +// RAVEN BEGIN +// asalmon: option for xbox to create a VDP socket +#ifdef _XBOX + bool InitForPort( int portNumber, bool vdp = false ); +#else + bool InitForPort( int portNumber ); +#endif +// RAVEN END + int GetPort( void ) const { return port; } +// RAVEN BEGIN +// amccarthy: For Xbox this needs to be an unsigned int +#ifdef _XBOX + unsigned int GetSocket( void ) const { return netSocket; } +#endif +// RAVEN END + void Close(); + + bool GetPacket( netadr_t &from, void *data, int &size, int maxSize ); + bool GetPacketBlocking( netadr_t &from, void *data, int &size, int maxSize, int timeout ); + void SendPacket( const netadr_t to, const void *data, int size ); + +//RAVEN BEGIN +//asalmon: second version of sendPacket for Xbox avoids netadr_t +#ifdef _XBOX + bool SendPacketVDP( const struct sockaddr *to, const void *data, int size ); +#endif +//RAVEN END + + void GetTrafficStats( int &bytesSent, int &packetsSent, int &bytesReceived, int &packetsReceived ) const; + + void SetSilent( bool silent ); + bool GetSilent( void ) const; + +private: + int packetsRead; + int bytesRead; + + int packetsWritten; + int bytesWritten; + + int port; // UDP port +//RAVEN BEGIN +//amccarthy: For Xbox this needs to be an unsigned int +#ifdef _XBOX + unsigned int netSocket; // OS specific socket +#else + int netSocket; +#endif +//RAVEN END + + bool silent; // don't emit anything for a while +}; + +/* +=============== +idPort::GetTrafficStats +=============== +*/ +ID_INLINE void idPort::GetTrafficStats( int &_bytesSent, int &_packetsSent, int &_bytesReceived, int &_packetsReceived ) const { + _bytesSent = bytesWritten; + _packetsSent = packetsWritten; + _bytesReceived = bytesRead; + _packetsReceived = packetsRead; +} + +/* +=============== +idPort::SetSilent +=============== +*/ +ID_INLINE void idPort::SetSilent( bool _silent ) { silent = _silent; } + +/* +=============== +idPort::GetSilent +=============== +*/ +ID_INLINE bool idPort::GetSilent( void ) const { return silent; } + +class idTCP; +class idTCPServer; + +const int IDPOLL_READ = (1<<0); +const int IDPOLL_WRITE = (1<<1); +const int IDPOLL_ERROR = (1<<2); + +class idPoller { +public: + idPoller(); + + void Clear( void ); + + // will replace existing entries + void Add( int fd, int which = IDPOLL_READ ); + void Add( const idTCP &tcp, int which = IDPOLL_READ ); + void Add( const idTCPServer &tcp, int which = IDPOLL_READ ); + + void Remove( int fd ) { Add(fd, 0); } + void Remove( const idTCP &tcp ) { Add(tcp, 0); } + void Remove( const idTCPServer &serv ) { Add(serv, 0); } + + // returns IDPOLL_ flags + int Check( int fd ); + int Check( const idTCP &tcp ); + int Check( const idTCPServer &serv ); + + // returns the number of fds set, timeout is in ms, <0 is forever + int Poll( int timeout = -1 ); + +private: + int max_fd; + fd_set readfds, writefds, exceptfds; + fd_set rreadfds, rwritefds, rexceptfds; +}; + +class idTCPServer { +public: + idTCPServer(); + virtual ~idTCPServer(); + + bool Listen( const char *net_interface, short port ); + bool Accept( idTCP &client ); + void Close(); + + const netadr_t &GetAddress( void ) const { return address; } + +private: + netadr_t address; // local address + int fd; + + friend void idPoller::Add( const idTCPServer &serv, int which ); + friend void idPoller::Remove( const idTCPServer &serv ); + friend int idPoller::Check( const idTCPServer &serv ); +}; + +class idTCP { +public: + idTCP(); + idTCP( const netadr_t &address, int fd ); + idTCP( const idTCP &tcp ); + virtual ~idTCP(); + + idTCP & operator = (const idTCP &tcp); + + // if host is host:port, the value of port is ignored + bool Init( const char *host, short port ); + void Close(); + + // returns -1 on failure (and closes socket) + // those are non blocking, can be used for polling + // there is no buffering, you are not guaranteed to Read or Write everything in a single call + // (specially on win32, see recv and send documentation) + int Read( void *data, int size ); + int Write( const void *data, int size ); + + const netadr_t &GetAddress( void ) const { return address; } + +private: + netadr_t address; // remote address + int fd; // OS specific socket + + friend void idPoller::Add( const idTCP &tcp, int which ); + friend void idPoller::Remove( const idTCP &tcp ); + friend int idPoller::Check( const idTCP &tcp ); +}; + + // parses the port number + // can also do DNS resolve if you ask for it. + // NOTE: DNS resolve is a slow/blocking call, think before you use + // ( could be exploited for server DoS ) +bool Sys_StringToNetAdr( const char *s, netadr_t *a, bool doDNSResolve ); +const char * Sys_NetAdrToString( const netadr_t a ); +bool Sys_IsLANAddress( const netadr_t a ); +bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b ); + +void Sys_InitNetworking( void ); +void Sys_ShutdownNetworking( void ); + +// read proxy information from environment +#define MAX_PROXY_LENGTH 128 +bool Sys_GetHTTPProxyAddress( char proxy[ MAX_PROXY_LENGTH ] ); + +// RAVEN BEGIN +// ddynerman: utility functions +// TTimo: FIXME if exposed, call them Sys_ +int Net_GetNumInterfaces( void ); +netadr_t Net_GetInterface( int index ); + +// asalmon: Xbox live related functions +#ifdef _XBOX +#define NONCE_SIZE 8 +bool Sys_CreateLiveMatch( void ); +bool Sys_CreateSystemLinkMatch( void ); +bool Sys_VerifyString(const char *string); +#endif +// RAVEN END + + +/* +============================================================== + + Multi-threading + +============================================================== +*/ + +typedef unsigned int (*xthread_t)( void * ); + +typedef enum { + THREAD_NORMAL, + THREAD_ABOVE_NORMAL, + THREAD_HIGHEST +} xthreadPriority; + +typedef struct { + const char * name; + int threadHandle; + unsigned long threadId; +// RAVEN BEGIN +// ksergent: included to track multiprocessor system +#ifdef _XBOX + unsigned char cpuID; +#endif +// RAVEN END +} xthreadInfo; + +const int MAX_THREADS = 10; +extern xthreadInfo *g_threads[MAX_THREADS]; +extern int g_thread_count; + +void Sys_CreateThread( xthread_t function, void *parms, xthreadPriority priority, xthreadInfo &info, const char *name, xthreadInfo *threads[MAX_THREADS], int *thread_count ); +void Sys_DestroyThread( xthreadInfo& info ); // sets threadHandle back to 0 + +// find the name of the calling thread +// if index != NULL, set the index in g_threads array (use -1 for "main" thread) +const char * Sys_GetThreadName( int *index = 0 ); + +const int MAX_CRITICAL_SECTIONS = 4; + +enum { + CRITICAL_SECTION_ZERO = 0, + CRITICAL_SECTION_ONE, + CRITICAL_SECTION_TWO, + CRITICAL_SECTION_THREE +}; + +void Sys_EnterCriticalSection( int index = CRITICAL_SECTION_ZERO ); +void Sys_LeaveCriticalSection( int index = CRITICAL_SECTION_ZERO ); + +const int MAX_TRIGGER_EVENTS = 4; + +enum { + TRIGGER_EVENT_ZERO = 0, + TRIGGER_EVENT_ONE, + TRIGGER_EVENT_TWO, + TRIGGER_EVENT_THREE +}; + +void Sys_WaitForEvent( int index = TRIGGER_EVENT_ZERO ); +void Sys_TriggerEvent( int index = TRIGGER_EVENT_ZERO ); + +/* +============================================================== + + idSys + +============================================================== +*/ + +class idSys { +public: + virtual ~idSys() { } + virtual void DebugPrintf( const char *fmt, ... )id_attribute((format(printf,2,3))) = 0; + virtual void DebugVPrintf( const char *fmt, va_list arg ) = 0; + + virtual double GetClockTicks( void ) = 0; + virtual double ClockTicksPerSecond( void ) = 0; + virtual cpuid_t GetProcessorId( void ) = 0; + virtual const char * GetProcessorString( void ) = 0; + virtual const char * FPU_GetState( void ) = 0; + virtual bool FPU_StackIsEmpty( void ) = 0; + virtual void FPU_SetFTZ( bool enable ) = 0; + virtual void FPU_SetDAZ( bool enable ) = 0; +// RAVEN BEGIN + virtual void FPU_SetPrecision( int flags ) = 0; +// RAVEN END + + virtual bool LockMemory( void *ptr, int bytes ) = 0; + virtual bool UnlockMemory( void *ptr, int bytes ) = 0; + + virtual void GetCallStack( address_t *callStack, const int callStackSize ) = 0; + virtual const char * GetCallStackStr( const address_t *callStack, const int callStackSize ) = 0; + virtual const char * GetCallStackCurStr( int depth ) = 0; + virtual void ShutdownSymbols( void ) = 0; + + virtual int DLL_Load( const char *dllName ) = 0; + virtual void * DLL_GetProcAddress( int dllHandle, const char *procName ) = 0; + virtual void DLL_Unload( int dllHandle ) = 0; + virtual void DLL_GetFileName( const char *baseName, char *dllName, int maxLength ) = 0; + + virtual sysEvent_t GenerateMouseButtonEvent( int button, bool down ) = 0; + virtual sysEvent_t GenerateMouseMoveEvent( int deltax, int deltay ) = 0; + +// RAVEN BEGIN + virtual int MapKey( unsigned long lParam, unsigned short wParam ) = 0; + virtual void AddKeyPress( int key, bool state ) = 0; + virtual int GetNumKeyPresses( void ) = 0; + virtual bool GetKeyPress( const int n, int &key, bool &state ) = 0; + + virtual void * CreateWindowEx( const char *className, const char *windowName, int style, int x, int y, int w, int h, void *parent, void *menu, void *instance, void *param, int extStyle = 0 ) = 0; + virtual void * GetDC( void *hWnd ) = 0; + virtual void ReleaseDC( void *hWnd, void *hDC ) = 0; + virtual void ShowWindow( void *hWnd, int show ) = 0; + virtual void UpdateWindow( void *hWnd ) = 0; + virtual bool IsWindowVisible( void *hWnd ) = 0; + virtual void SetForegroundWindow( void *hWnd ) = 0; + virtual void SetFocus( void *hWnd ) = 0; + virtual void DestroyWindow( void *hWnd ) = 0; + + virtual void ShowConsole( int visLevel, bool quitOnClose ) = 0; + virtual void UpdateConsole( void ) = 0; + virtual void SetConsoleName( const char* consoleName ) = 0; + virtual bool IsAppActive( void ) const = 0; + virtual int Milliseconds( void ) = 0; + virtual void InitInput( void ) = 0; + virtual void ShutdownInput( void ) = 0; + virtual void GenerateEvents( void ) = 0; + virtual void GrabMouseCursor( bool grabIt ) = 0; + + virtual FILE *FOpen( const char *name, const char *mode ) = 0; + virtual void FPrintf( FILE *file, const char *fmt ) = 0; + virtual int FTell( FILE *file ) = 0; + virtual int FSeek( FILE *file, long offset, int mode ) = 0; + virtual void FClose( FILE *file ) = 0; + virtual int FRead( void *buffer, int size, int count, FILE *file ) = 0; + virtual int FWrite( void *buffer, int size, int count, FILE *file ) = 0; + virtual long FileTimeStamp( FILE *file ) = 0; + virtual int FEof( FILE *stream ) = 0; + virtual char *FGets( char *string, int n, FILE *stream ) = 0; + virtual void FFlush( FILE *f ) = 0; + virtual int SetVBuf( FILE *stream, char *buffer, int mode, size_t size ) = 0; +// RAVEN END + + virtual void OpenURL( const char *url, bool quit ) = 0; + virtual void StartProcess( const char *exePath, bool quit ) = 0; + + virtual int GetGUID( char *buf, int buflen ) = 0; +}; + +extern idSys * sys; + +// RAVEN BEGIN +// jnewquist: Scope timing tools +#if defined(_XENON) +class ScopeAutoMeasure { +public: + ID_INLINE ScopeAutoMeasure(const char *name) { + mName = name; + QueryPerformanceCounter( &mStartTime ); + } + ID_INLINE ~ScopeAutoMeasure() { + LARGE_INTEGER endTime; + QueryPerformanceCounter( &endTime ); + double time = (double)(endTime.QuadPart - mStartTime.QuadPart) / (Sys_ClockTicksPerSecond() * 0.000001); + printf("Time %s: %f us\n", mName, time); + } +protected: + LARGE_INTEGER mStartTime; + const char * mName; +}; + +#if defined(TIME_CAPTURE) //&& defined(_PROFILE) + +class TimedScope { +public: + ID_INLINE TimedScope(const char *name) { + mName = name; + mNext = mFirst; + mFirst = this; + mTime.QuadPart = 0; + } + static void ComputeCost() { + LARGE_INTEGER TicksPerSecond; + QueryPerformanceFrequency( &TicksPerSecond ); + sTicksPerMicrosecond = (double)TicksPerSecond.QuadPart * 0.000001; + + // get a rough time estimate for the cost of a call to Sys_Milliseconds so that we can remove it from the function costs + LARGE_INTEGER before; + QueryPerformanceCounter( &before ); + + LARGE_INTEGER test; + for(int i=0; i<1000000; i++) + { + QueryPerformanceCounter( &test ); + } + LARGE_INTEGER after; + QueryPerformanceCounter( &after ); + + __int64 Ticks = after.QuadPart - before.QuadPart; + sQueryPerformanceCounterCost.QuadPart = (double)Ticks/1000000.0f; + } + + // automatically deducts the cost of the Sys_Milliseconds() call used to gather the timing + ID_INLINE void AddTime(unsigned long long ticks) { + // add number of microseconds + mTime.QuadPart += (ticks - sQueryPerformanceCounterCost.QuadPart)/sTicksPerMicrosecond; + } + ID_INLINE void AddCall() { + mCalls++; + } + ID_INLINE static void PrintTimes(void) { + if (!mFirst) { + return; + } + printf("Start Frame\n"); + for (TimedScope* p=mFirst; p; p = p->mNext) { + printf("\t%20s: %d us\t%d calls\t %f us/call\n", p->mName, p->mTime, p->mCalls, (p->mCalls)?(double)p->mTime.QuadPart/(double)p->mCalls:0.0f); + } + printf("End Frame\n\n"); + } + ID_INLINE static void ClearTimes(void) { + if (!mFirst) { + return; + } + for (TimedScope* p=mFirst; p; p = p->mNext) { + p->mTime.QuadPart = 0; + } + } + ID_INLINE static void ClearCalls(void) { + if (!mFirst) { + return; + } + for (TimedScope* p=mFirst; p; p = p->mNext) { + p->mCalls = 0; + } + } +protected: + static TimedScope * mFirst; + TimedScope * mNext; + const char * mName; + LARGE_INTEGER mTime; + unsigned int mCalls; + static LARGE_INTEGER sQueryPerformanceCounterCost; + static double sTicksPerMicrosecond; +}; + +class ScopeAutoTimer { +public: + ID_INLINE ScopeAutoTimer(TimedScope *scope) { + QueryPerformanceCounter( &mStartTime ); + //mStartTime = Sys_Milliseconds(); + mScope = scope; + } + ID_INLINE ~ScopeAutoTimer() { + LARGE_INTEGER endTime; + QueryPerformanceCounter( &endTime ); + + //unsigned int time = (unsigned int)Sys_Milliseconds() - mStartTime; + // pass in number of ticks used and TimedScope will adjust it to a number of microseconds + mScope->AddTime(endTime.QuadPart - mStartTime.QuadPart); + mScope->AddCall(); + } +protected: + LARGE_INTEGER mStartTime; + TimedScope * mScope; +}; + +#define TIME_THIS_SCOPE(name) \ + static TimedScope scopeTime(name); \ + ScopeAutoTimer autoTimer(&scopeTime) +#else +#define TIME_THIS_SCOPE(name) +#endif +#endif + +#define STRINGIZE_INDIRECT(F, X) F(X) +#define STRINGIZE(X) #X +#define __LINESTR__ STRINGIZE_INDIRECT(STRINGIZE, __LINE__) +#define __FILELINEFUNC__ (__FILE__ " " __LINESTR__ " " __FUNCTION__) +#define __FUNCLINE__ ( __FUNCTION__ " " __LINESTR__ ) + +// RAVEN END + +#endif /* !__SYS_PUBLIC__ */ diff --git a/src/sys/win32/eax.h b/src/sys/win32/eax.h new file mode 100644 index 0000000..72d153a --- /dev/null +++ b/src/sys/win32/eax.h @@ -0,0 +1,557 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +/****************************************************************** +* +* EAX.H - Environmental Audio Extensions version 3.0 +* for OpenAL and DirectSound3D +* Updated May 22, 2001 by Jean-Marc Jot, Sam Dicker (version 1.0). +* +******************************************************************* +*/ + +#ifndef EAX_H_INCLUDED +#define EAX_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef OPENAL + #include + + /* + * EAX Wrapper Interface (using Direct X 7) {4FF53B81-1CE0-11d3-AAB8-00A0C95949D5} + */ + DEFINE_GUID(CLSID_EAXDirectSound, + 0x4ff53b81, + 0x1ce0, + 0x11d3, + 0xaa, 0xb8, 0x0, 0xa0, 0xc9, 0x59, 0x49, 0xd5); + + /* + * EAX Wrapper Interface (using Direct X 8) {CA503B60-B176-11d4-A094-D0C0BF3A560C} + */ + DEFINE_GUID(CLSID_EAXDirectSound8, + 0xca503b60, + 0xb176, + 0x11d4, + 0xa0, 0x94, 0xd0, 0xc0, 0xbf, 0x3a, 0x56, 0xc); + + + +#ifdef DIRECTSOUND_VERSION +#if DIRECTSOUND_VERSION == 0x0800 + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate8(GUID*, LPDIRECTSOUND8*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE8)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); +#endif +#endif + + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate(GUID*, LPDIRECTSOUND*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); + +#else // OPENAL + #include "..\Sdk\OpenAL\Include\al.h" + + #ifndef GUID_DEFINED + #define GUID_DEFINED + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; + #endif // !GUID_DEFINED + + #ifndef DEFINE_GUID + #ifndef INITGUID + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID FAR name + #else + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + #endif // INITGUID + #endif // DEFINE_GUID + + + /* + * EAX OpenAL Extension + */ + typedef ALenum (*EAXSet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALenum (*EAXGet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); +#endif + +#pragma pack(push, 4) + +/* + * EAX 3.0 listener property set {A8FA6880-B476-11d3-BDB9-00C0F02DDF87} + */ +DEFINE_GUID(DSPROPSETID_EAX30_ListenerProperties, + 0xa8fa6882, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x00, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_ListenerProperties DSPROPSETID_EAX30_ListenerProperties + +typedef enum +{ + DSPROPERTY_EAXLISTENER_NONE, + DSPROPERTY_EAXLISTENER_ALLPARAMETERS, + DSPROPERTY_EAXLISTENER_ENVIRONMENT, + DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAXLISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAXLISTENER_ROOM, + DSPROPERTY_EAXLISTENER_ROOMHF, + DSPROPERTY_EAXLISTENER_ROOMLF, + DSPROPERTY_EAXLISTENER_DECAYTIME, + DSPROPERTY_EAXLISTENER_DECAYHFRATIO, + DSPROPERTY_EAXLISTENER_DECAYLFRATIO, + DSPROPERTY_EAXLISTENER_REFLECTIONS, + DSPROPERTY_EAXLISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAXLISTENER_REFLECTIONSPAN, + DSPROPERTY_EAXLISTENER_REVERB, + DSPROPERTY_EAXLISTENER_REVERBDELAY, + DSPROPERTY_EAXLISTENER_REVERBPAN, + DSPROPERTY_EAXLISTENER_ECHOTIME, + DSPROPERTY_EAXLISTENER_ECHODEPTH, + DSPROPERTY_EAXLISTENER_MODULATIONTIME, + DSPROPERTY_EAXLISTENER_MODULATIONDEPTH, + DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF, + DSPROPERTY_EAXLISTENER_HFREFERENCE, + DSPROPERTY_EAXLISTENER_LFREFERENCE, + DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXLISTENER_FLAGS +} DSPROPERTY_EAX_LISTENERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXLISTENER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXLISTENER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXLISTENER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXLISTENER_NONE | \ + DSPROPERTY_EAXLISTENER_IMMEDIATE) + +typedef struct _EAXVECTOR { + float x; + float y; + float z; +} EAXVECTOR; + +// Use this structure for DSPROPERTY_EAXLISTENER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times and delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myListener.lRoom = -1000; +// myListener.lRoomHF = -100; +// ... +// myListener.dwFlags = myFlags /* see EAXLISTENERFLAGS below */ ; +// instead of: +// myListener = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +typedef struct _EAXLISTENERPROPERTIES +{ + unsigned long ulEnvironment; // sets all listener properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +} EAXLISTENERPROPERTIES, *LPEAXLISTENERPROPERTIES; + +// used by DSPROPERTY_EAXLISTENER_ENVIRONMENT +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_UNDEFINED, + + EAX_ENVIRONMENT_COUNT +}; + +// Used by DSPROPERTY_EAXLISTENER_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXLISTENERFLAGS_DECAYTIMESCALE | EAXLISTENERFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAXLISTENERFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAXLISTENERFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAXLISTENERFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAXLISTENERFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time +#define EAXLISTENERFLAGS_ECHOTIMESCALE 0x00000040 // echo time +#define EAXLISTENERFLAGS_MODULATIONTIMESCALE 0x00000080 // modulation time + +// This flag limits high-frequency decay time according to air absorption. +#define EAXLISTENERFLAGS_DECAYHFLIMIT 0x00000020 + +#define EAXLISTENERFLAGS_RESERVED 0xFFFFFF00 // reserved future use + +// Property ranges and defaults: + +#define EAXLISTENER_MINENVIRONMENT 0 +#define EAXLISTENER_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAXLISTENER_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAXLISTENER_MINENVIRONMENTSIZE 1.0f +#define EAXLISTENER_MAXENVIRONMENTSIZE 100.0f +#define EAXLISTENER_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAXLISTENER_MINENVIRONMENTDIFFUSION 0.0f +#define EAXLISTENER_MAXENVIRONMENTDIFFUSION 1.0f +#define EAXLISTENER_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAXLISTENER_MINROOM (-10000) +#define EAXLISTENER_MAXROOM 0 +#define EAXLISTENER_DEFAULTROOM (-1000) + +#define EAXLISTENER_MINROOMHF (-10000) +#define EAXLISTENER_MAXROOMHF 0 +#define EAXLISTENER_DEFAULTROOMHF (-100) + +#define EAXLISTENER_MINROOMLF (-10000) +#define EAXLISTENER_MAXROOMLF 0 +#define EAXLISTENER_DEFAULTROOMLF 0 + +#define EAXLISTENER_MINDECAYTIME 0.1f +#define EAXLISTENER_MAXDECAYTIME 20.0f +#define EAXLISTENER_DEFAULTDECAYTIME 1.49f + +#define EAXLISTENER_MINDECAYHFRATIO 0.1f +#define EAXLISTENER_MAXDECAYHFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYHFRATIO 0.83f + +#define EAXLISTENER_MINDECAYLFRATIO 0.1f +#define EAXLISTENER_MAXDECAYLFRATIO 2.0f +#define EAXLISTENER_DEFAULTDECAYLFRATIO 1.00f + +#define EAXLISTENER_MINREFLECTIONS (-10000) +#define EAXLISTENER_MAXREFLECTIONS 1000 +#define EAXLISTENER_DEFAULTREFLECTIONS (-2602) + +#define EAXLISTENER_MINREFLECTIONSDELAY 0.0f +#define EAXLISTENER_MAXREFLECTIONSDELAY 0.3f +#define EAXLISTENER_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAXLISTENER_MINREVERB (-10000) +#define EAXLISTENER_MAXREVERB 2000 +#define EAXLISTENER_DEFAULTREVERB 200 + +#define EAXLISTENER_MINREVERBDELAY 0.0f +#define EAXLISTENER_MAXREVERBDELAY 0.1f +#define EAXLISTENER_DEFAULTREVERBDELAY 0.011f + +#define EAXLISTENER_MINECHOTIME 0.075f +#define EAXLISTENER_MAXECHOTIME 0.25f +#define EAXLISTENER_DEFAULTECHOTIME 0.25f + +#define EAXLISTENER_MINECHODEPTH 0.0f +#define EAXLISTENER_MAXECHODEPTH 1.0f +#define EAXLISTENER_DEFAULTECHODEPTH 0.0f + +#define EAXLISTENER_MINMODULATIONTIME 0.04f +#define EAXLISTENER_MAXMODULATIONTIME 4.0f +#define EAXLISTENER_DEFAULTMODULATIONTIME 0.25f + +#define EAXLISTENER_MINMODULATIONDEPTH 0.0f +#define EAXLISTENER_MAXMODULATIONDEPTH 1.0f +#define EAXLISTENER_DEFAULTMODULATIONDEPTH 0.0f + +#define EAXLISTENER_MINAIRABSORPTIONHF (-100.0f) +#define EAXLISTENER_MAXAIRABSORPTIONHF 0.0f +#define EAXLISTENER_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXLISTENER_MINHFREFERENCE 1000.0f +#define EAXLISTENER_MAXHFREFERENCE 20000.0f +#define EAXLISTENER_DEFAULTHFREFERENCE 5000.0f + +#define EAXLISTENER_MINLFREFERENCE 20.0f +#define EAXLISTENER_MAXLFREFERENCE 1000.0f +#define EAXLISTENER_DEFAULTLFREFERENCE 250.0f + +#define EAXLISTENER_MINROOMROLLOFFFACTOR 0.0f +#define EAXLISTENER_MAXROOMROLLOFFFACTOR 10.0f +#define EAXLISTENER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXLISTENER_DEFAULTFLAGS (EAXLISTENERFLAGS_DECAYTIMESCALE | \ + EAXLISTENERFLAGS_REFLECTIONSSCALE | \ + EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE | \ + EAXLISTENERFLAGS_REVERBSCALE | \ + EAXLISTENERFLAGS_REVERBDELAYSCALE | \ + EAXLISTENERFLAGS_DECAYHFLIMIT) + + + +/* +* EAX 3.0 buffer property set {A8FA6881-B476-11d3-BDB9-00C0F02DDF87} +*/ +DEFINE_GUID(DSPROPSETID_EAX30_BufferProperties, + 0xa8fa6881, + 0xb476, + 0x11d3, + 0xbd, 0xb9, 0x0, 0xc0, 0xf0, 0x2d, 0xdf, 0x87); + +// For compatibility with future EAX versions: +#define DSPROPSETID_EAX_BufferProperties DSPROPSETID_EAX30_BufferProperties +#define DSPROPSETID_EAX_SourceProperties DSPROPSETID_EAX30_BufferProperties + +typedef enum +{ + DSPROPERTY_EAXBUFFER_NONE, + DSPROPERTY_EAXBUFFER_ALLPARAMETERS, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONPARAMETERS, + DSPROPERTY_EAXBUFFER_OCCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_EXCLUSIONPARAMETERS, + DSPROPERTY_EAXBUFFER_DIRECT, + DSPROPERTY_EAXBUFFER_DIRECTHF, + DSPROPERTY_EAXBUFFER_ROOM, + DSPROPERTY_EAXBUFFER_ROOMHF, + DSPROPERTY_EAXBUFFER_OBSTRUCTION, + DSPROPERTY_EAXBUFFER_OBSTRUCTIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSION, + DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, + DSPROPERTY_EAXBUFFER_OCCLUSIONDIRECTRATIO, + DSPROPERTY_EAXBUFFER_EXCLUSION, + DSPROPERTY_EAXBUFFER_EXCLUSIONLFRATIO, + DSPROPERTY_EAXBUFFER_OUTSIDEVOLUMEHF, + DSPROPERTY_EAXBUFFER_DOPPLERFACTOR, + DSPROPERTY_EAXBUFFER_ROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXBUFFER_AIRABSORPTIONFACTOR, + DSPROPERTY_EAXBUFFER_FLAGS +} DSPROPERTY_EAX_BUFFERPROPERTY; + +// OR these flags with property id +#define DSPROPERTY_EAXBUFFER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXBUFFER_DEFERRED 0x80000000 // changes take effect later +#define DSPROPERTY_EAXBUFFER_COMMITDEFERREDSETTINGS (DSPROPERTY_EAXBUFFER_NONE | \ + DSPROPERTY_EAXBUFFER_IMMEDIATE) + +// Use this structure for DSPROPERTY_EAXBUFFER_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXBUFFERFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +typedef struct _EAXBUFFERPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +} EAXBUFFERPROPERTIES, *LPEAXBUFFERPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_OBSTRUCTION, +typedef struct _EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +} EAXOBSTRUCTIONPROPERTIES, *LPEAXOBSTRUCTIONPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_OCCLUSION +typedef struct _EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXOCCLUSIONPROPERTIES, *LPEAXOCCLUSIONPROPERTIES; + +// Use this structure for DSPROPERTY_EAXBUFFER_EXCLUSION +typedef struct _EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +} EAXEXCLUSIONPROPERTIES, *LPEAXEXCLUSIONPROPERTIES; + +// Used by DSPROPERTY_EAXBUFFER_FLAGS +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXBUFFERFLAGS_DIRECTHFAUTO | EAXBUFFERFLAGS_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAXBUFFERFLAGS_DIRECTHFAUTO 0x00000001 // affects DSPROPERTY_EAXBUFFER_DIRECTHF +#define EAXBUFFERFLAGS_ROOMAUTO 0x00000002 // affects DSPROPERTY_EAXBUFFER_ROOM +#define EAXBUFFERFLAGS_ROOMHFAUTO 0x00000004 // affects DSPROPERTY_EAXBUFFER_ROOMHF + +#define EAXBUFFERFLAGS_RESERVED 0xFFFFFFF8 // reserved future use + +// Property ranges and defaults: + +#define EAXBUFFER_MINDIRECT (-10000) +#define EAXBUFFER_MAXDIRECT 1000 +#define EAXBUFFER_DEFAULTDIRECT 0 + +#define EAXBUFFER_MINDIRECTHF (-10000) +#define EAXBUFFER_MAXDIRECTHF 0 +#define EAXBUFFER_DEFAULTDIRECTHF 0 + +#define EAXBUFFER_MINROOM (-10000) +#define EAXBUFFER_MAXROOM 1000 +#define EAXBUFFER_DEFAULTROOM 0 + +#define EAXBUFFER_MINROOMHF (-10000) +#define EAXBUFFER_MAXROOMHF 0 +#define EAXBUFFER_DEFAULTROOMHF 0 + +#define EAXBUFFER_MINOBSTRUCTION (-10000) +#define EAXBUFFER_MAXOBSTRUCTION 0 +#define EAXBUFFER_DEFAULTOBSTRUCTION 0 + +#define EAXBUFFER_MINOBSTRUCTIONLFRATIO 0.0f +#define EAXBUFFER_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAXBUFFER_MINOCCLUSION (-10000) +#define EAXBUFFER_MAXOCCLUSION 0 +#define EAXBUFFER_DEFAULTOCCLUSION 0 + +#define EAXBUFFER_MINOCCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXBUFFER_MINOCCLUSIONROOMRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONROOMRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONROOMRATIO 1.5f + +#define EAXBUFFER_MINOCCLUSIONDIRECTRATIO 0.0f +#define EAXBUFFER_MAXOCCLUSIONDIRECTRATIO 10.0f +#define EAXBUFFER_DEFAULTOCCLUSIONDIRECTRATIO 1.0f + +#define EAXBUFFER_MINEXCLUSION (-10000) +#define EAXBUFFER_MAXEXCLUSION 0 +#define EAXBUFFER_DEFAULTEXCLUSION 0 + +#define EAXBUFFER_MINEXCLUSIONLFRATIO 0.0f +#define EAXBUFFER_MAXEXCLUSIONLFRATIO 1.0f +#define EAXBUFFER_DEFAULTEXCLUSIONLFRATIO 1.0f + +#define EAXBUFFER_MINOUTSIDEVOLUMEHF (-10000) +#define EAXBUFFER_MAXOUTSIDEVOLUMEHF 0 +#define EAXBUFFER_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAXBUFFER_MINDOPPLERFACTOR 0.0f +#define EAXBUFFER_MAXDOPPLERFACTOR 10.f +#define EAXBUFFER_DEFAULTDOPPLERFACTOR 0.0f + +#define EAXBUFFER_MINROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINROOMROLLOFFFACTOR 0.0f +#define EAXBUFFER_MAXROOMROLLOFFFACTOR 10.f +#define EAXBUFFER_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXBUFFER_MINAIRABSORPTIONFACTOR 0.0f +#define EAXBUFFER_MAXAIRABSORPTIONFACTOR 10.0f +#define EAXBUFFER_DEFAULTAIRABSORPTIONFACTOR 1.0f + +#define EAXBUFFER_DEFAULTFLAGS (EAXBUFFERFLAGS_DIRECTHFAUTO | \ + EAXBUFFERFLAGS_ROOMAUTO | \ + EAXBUFFERFLAGS_ROOMHFAUTO ) + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/sys/win32/rc/AFEditor_resource.h b/src/sys/win32/rc/AFEditor_resource.h new file mode 100644 index 0000000..516b3a9 --- /dev/null +++ b/src/sys/win32/rc/AFEditor_resource.h @@ -0,0 +1,365 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_DIALOG_AF 100 +#define IDD_DIALOG_AF_NAME 101 +#define IDD_DIALOG_AF_VIEW 102 +#define IDD_DIALOG_AF_PROPERTIES 103 +#define IDD_DIALOG_AF_BODY 104 +#define IDD_DIALOG_AF_CONSTRAINT 105 +#define IDD_DIALOG_AF_CONSTRAINT_FIXED 106 +#define IDD_DIALOG_AF_CONSTRAINT_BALLANDSOCKET 107 +#define IDD_DIALOG_AF_CONSTRAINT_UNIVERSAL 108 +#define IDD_DIALOG_AF_CONSTRAINT_HINGE 109 +#define IDD_DIALOG_AF_CONSTRAINT_SLIDER 110 +#define IDD_DIALOG_AF_CONSTRAINT_SPRING 111 +#define IDI_ICON2 112 + +#define IDC_DIALOG_AF_TAB_MODE 200 +#define IDC_DIALOG_AF_START 201 +#define IDC_COMBO_AF 202 +#define IDC_BUTTON_AF_NEW 203 +#define IDC_BUTTON_AF_DELETE 204 +#define IDC_BUTTON_AF_SPAWN 205 +#define IDC_BUTTON_AF_TPOSE 206 +#define IDC_BUTTON_AF_KILL 207 +#define IDC_BUTTON_AF_SAVE 208 +#define IDC_EDIT_AF_NAME 209 +#define IDC_STATIC_AF_NAME 210 +#define IDC_DIALOG_AF_VIEW_START 211 +#define IDC_AF_VIEW_AF 212 +#define IDC_AF_VIEW_MD5 213 +#define IDC_AF_VIEW_LINES 214 +#define IDC_AF_VIEW_PHYSICS 215 +#define IDC_CHECK_VIEW_BODIES 216 +#define IDC_CHECK_VIEW_BODYNAMES 217 +#define IDC_CHECK_VIEW_BODYMASS 218 +#define IDC_CHECK_VIEW_TOTALMASS 219 +#define IDC_CHECK_VIEW_INERTIATENSOR 220 +#define IDC_CHECK_VIEW_VELOCITY 221 +#define IDC_CHECK_VIEW_CONSTRAINTNAMES 222 +#define IDC_CHECK_VIEW_CONSTRAINTS 223 +#define IDC_CHECK_VIEW_PRIMARYONLY 224 +#define IDC_CHECK_VIEW_LIMITS 225 +#define IDC_CHECK_VIEW_CONSTRAINEDBODIES 226 +#define IDC_CHECK_VIEW_TREES 227 +#define IDC_CHECK_MD5_SKELETON 228 +#define IDC_CHECK_MD5_SKELETONONLY 229 +#define IDC_CHECK_LINES_DEPTHTEST 230 +#define IDC_CHECK_LINES_USEARROWS 231 +#define IDC_CHECK_PHYSICS_NOFRICTION 232 +#define IDC_CHECK_PHYSICS_NOLIMITS 233 +#define IDC_CHECK_PHYSICS_NOGRAVITY 234 +#define IDC_CHECK_PHYSICS_NOSELFCOLLISION 235 +#define IDC_CHECK_PHYSICS_TIMING 236 +#define IDC_CHECK_PHYSICS_DRAG_ENTITIES 237 +#define IDC_CHECK_PHYSICS_SHOW_DRAG_SELECTION 238 +#define IDC_DIALOG_AF_PROPERTIES_START 239 +#define IDC_STATIC_MD5 240 +#define IDC_STATIC_MODEL 241 +#define IDC_EDIT_MODEL 242 +#define IDC_BUTTON_BROWSE_MODEL 243 +#define IDC_STATIC_SKIN 244 +#define IDC_EDIT_SKIN 245 +#define IDC_BUTTON_BROWSE_SKIN 246 +#define IDC_STATIC_FRICTION 247 +#define IDC_STATIC_LINEARFRICTION 248 +#define IDC_EDIT_LINEARFRICTION 249 +#define IDC_SPIN_LINEARFRICTION 250 +#define IDC_STATIC_ANGULARFRICTION 251 +#define IDC_EDIT_ANGULARFRICTION 252 +#define IDC_SPIN_ANGULARFRICTION 253 +#define IDC_STATIC_CONTACTFRICTION 254 +#define IDC_EDIT_CONTACTFRICTION 255 +#define IDC_SPIN_CONTACTFRICTION 256 +#define IDC_STATIC_CONSTRAINT 257 +#define IDC_EDIT_CONSTRAINTFRICTION 258 +#define IDC_SPIN_CONSTRAINTFRICTION 259 +#define IDC_STATIC_SUSPENDSPEED 260 +#define IDC_STATIC_COLLISIONDETECTION 261 +#define IDC_CHECK_SELFCOLLISION 262 +#define IDC_STATIC_CONTENTS 263 +#define IDC_EDIT_CONTENTS 264 +#define IDC_STATIC_CLIPMASK 265 +#define IDC_EDIT_CLIPMASK 266 +#define IDC_STATIC_LINEARVELOCITY 267 +#define IDC_EDIT_LINEARVELOCITY 268 +#define IDC_STATIC_ANGULARVELOCITY 269 +#define IDC_EDIT_ANGULARVELOCITY 270 +#define IDC_STATIC_LINEARACCELERATION 271 +#define IDC_EDIT_LINEARACCELERATION 272 +#define IDC_STATIC_ANGULARACCELERATION 273 +#define IDC_EDIT_ANGULARACCELERATION 274 +#define IDC_STATIC_MASS 275 +#define IDC_STATIC_TOTALMASS 276 +#define IDC_EDIT_TOTALMASS 277 +#define IDC_SPIN_TOTALMASS 278 +#define IDC_STATIC_SUSPENDMOVEMENT 279 +#define IDC_STATIC_LINEAR_TOLERANCE 280 +#define IDC_EDIT_LINEAR_TOLERANCE 281 +#define IDC_STATIC_ANGULAR_TOLERANCE 282 +#define IDC_EDIT_ANGULAR_TOLERANCE 283 +#define IDC_STATIC_NO_MOVE_TIME 284 +#define IDC_EDIT_NO_MOVE_TIME 285 +#define IDC_STATIC_MAXIMUM_MOVE_TIME 286 +#define IDC_EDIT_MAXIMUM_MOVE_TIME 287 +#define IDC_STATIC_MINIMUM_MOVE_TIME 288 +#define IDC_EDIT_MINIMUM_MOVE_TIME 289 +#define IDC_DIALOG_AF_BODY_START 290 +#define IDC_COMBO_BODIES 291 +#define IDC_BUTTON_NEWBODY 292 +#define IDC_BUTTON_RENAMEBODY 293 +#define IDC_BUTTON_DELETEBODY 294 +#define IDC_BODY_COLLISIONMODEL 295 +#define IDC_STATIC_CM_TYPE 296 +#define IDC_COMBO_CM_TYPE 297 +#define IDC_EDIT_CM_NAME 298 +#define IDC_BUTTON_CM_BROWSE 299 +#define IDC_COMBO_BONE_JOINT1 300 +#define IDC_COMBO_BONE_JOINT2 301 +#define IDC_STATIC_CM_HEIGHT 302 +#define IDC_EDIT_CM_HEIGHT 303 +#define IDC_SPIN_CM_HEIGHT 304 +#define IDC_STATIC_CM_WIDTH 305 +#define IDC_EDIT_CM_WIDTH 306 +#define IDC_SPIN_CM_WIDTH 307 +#define IDC_STATIC_CM_LENGTH 308 +#define IDC_EDIT_CM_LENGTH 309 +#define IDC_SPIN_CM_LENGTH 310 +#define IDC_STATIC_CM_NUMSIDES 311 +#define IDC_EDIT_CM_NUMSIDES 312 +#define IDC_SPIN_CM_NUMSIDES 313 +#define IDC_STATIC_CM_DENSITY 314 +#define IDC_EDIT_CM_DENSITY 315 +#define IDC_SPIN_CM_DENSITY 316 +#define IDC_STATIC_CM_INERTIASCALE 317 +#define IDC_EDIT_CM_INERTIASCALE 318 +#define IDC_STATIC_BODY_ORIGIN_AND_ANGLES 319 +#define IDC_RADIO_ORIGIN_COORDINATES 320 +#define IDC_RADIO_ORIGIN_BONECENTER 321 +#define IDC_RADIO_ORIGIN_JOINT 322 +#define IDC_EDIT_AF_VECTOR_X 323 +#define IDC_EDIT_AF_VECTOR_Y 324 +#define IDC_EDIT_AF_VECTOR_Z 325 +#define IDC_SPIN_AF_VECTOR_X 326 +#define IDC_SPIN_AF_VECTOR_Y 327 +#define IDC_SPIN_AF_VECTOR_Z 328 +#define IDC_COMBO_ORIGIN_BONECENTER_JOINT1 329 +#define IDC_COMBO_ORIGIN_BONECENTER_JOINT2 330 +#define IDC_COMBO_ORIGIN_JOINT 331 +#define IDC_STATIC_CM_ANGLES 332 +#define IDC_STATIC_ANGLES_PITCH 333 +#define IDC_STATIC_ANGLES_YAW 334 +#define IDC_STATIC_ANGLES_ROLL 335 +#define IDC_EDIT_ANGLES_PITCH 336 +#define IDC_SPIN_ANGLES_PITCH 337 +#define IDC_EDIT_ANGLES_YAW 338 +#define IDC_SPIN_ANGLES_YAW 339 +#define IDC_EDIT_ANGLES_ROLL 340 +#define IDC_SPIN_ANGLES_ROLL 341 +#define IDC_STATIC_FRICTIONDIRECTION 342 +#define IDC_EDIT_FRICTIONDIRECTION 343 +#define IDC_STATIC_CONTACTMOTORDIRECTION 344 +#define IDC_EDIT_CONTACTMOTORDIRECTION 345 +#define IDC_STATIC_JOINTS 346 +#define IDC_STATIC_MODIFIEDJOINT 347 +#define IDC_COMBO_MODIFIEDJOINT 348 +#define IDC_STATIC_MODIFY 349 +#define IDC_RADIO_MODIFY_ORIENTATION 350 +#define IDC_RADIO_MODIFY_POSITION 351 +#define IDC_RADIO_MODIFY_BOTH 352 +#define IDC_STATIC_CONTAINEDJOINTS 353 +#define IDC_EDIT_CONTAINEDJOINTS 354 +#define IDC_DIALOG_AF_CONSTRAINT_START 355 +#define IDC_COMBO_CONSTRAINTS 356 +#define IDC_BUTTON_NEWCONSTRAINT 357 +#define IDC_BUTTON_RENAMECONSTRAINT 358 +#define IDC_BUTTON_DELETECONSTRAINT 359 +#define IDC_STATIC_CONSTRAINT_GENERAL 360 +#define IDC_STATIC_CONSTRAINT_TYPE 361 +#define IDC_COMBO_CONSTRAINT_TYPE 362 +#define IDC_STATIC_BODY1 363 +#define IDC_STATIC_BODY2 364 +#define IDC_COMBO_CONSTRAINT_BODY1 365 +#define IDC_COMBO_CONSTRAINT_BODY2 366 +#define IDC_STATIC_CONSTRAINT_FRICTION 367 +#define IDC_EDIT_CONSTRAINT_FRICTION 368 +#define IDC_SPIN_CONSTRAINT_FRICTION 369 +#define IDC_DIALOG_AF_CONSTRAINT_UNIVERSAL_START 370 +#define IDC_STATIC_ANCHOR 371 +#define IDC_RADIO_ANCHOR_JOINT 372 +#define IDC_COMBO_ANCHOR_JOINT 373 +#define IDC_RADIO_ANCHOR_COORDINATES 374 +#define IDC_EDIT_ANCHOR_X 375 +#define IDC_SPIN_ANCHOR_X 376 +#define IDC_EDIT_ANCHOR_Y 377 +#define IDC_SPIN_ANCHOR_Y 378 +#define IDC_EDIT_ANCHOR_Z 379 +#define IDC_SPIN_ANCHOR_Z 380 +#define IDC_STATIC_UNIVERSAL_SHAFT1 381 +#define IDC_RADIO_UNIVERSAL_BONE_SHAFT1 382 +#define IDC_RADIO_UNIVERSAL_ANGLES_SHAFT1 383 +#define IDC_COMBO_UNIVERSAL_JOINT1_SHAFT1 384 +#define IDC_COMBO_UNIVERSAL_JOINT2_SHAFT1 385 +#define IDC_EDIT_UNIVERSAL_PITCH_SHAFT1 386 +#define IDC_SPIN_UNIVERSAL_PITCH_SHAFT1 387 +#define IDC_EDIT_UNIVERSAL_YAW_SHAFT1 388 +#define IDC_SPIN_UNIVERSAL_YAW_SHAFT1 389 +#define IDC_STATIC_UNIVERSAL_SHAFT2 390 +#define IDC_RADIO_UNIVERSAL_BONE_SHAFT2 391 +#define IDC_RADIO_UNIVERSAL_ANGLES_SHAFT2 392 +#define IDC_COMBO_UNIVERSAL_JOINT1_SHAFT2 393 +#define IDC_COMBO_UNIVERSAL_JOINT2_SHAFT2 394 +#define IDC_EDIT_UNIVERSAL_PITCH_SHAFT2 395 +#define IDC_SPIN_UNIVERSAL_PITCH_SHAFT2 396 +#define IDC_EDIT_UNIVERSAL_YAW_SHAFT2 397 +#define IDC_SPIN_UNIVERSAL_YAW_SHAFT2 398 +#define IDC_STATIC_UNIVERSAL_LIMIT_TYPE 399 +#define IDC_RADIO_UNIVERSAL_LIMIT_NONE 400 +#define IDC_RADIO_UNIVERSAL_LIMIT_CONE 401 +#define IDC_RADIO_UNIVERSAL_LIMIT_PYRAMID 402 +#define IDC_STATIC_UNIVERSAL_LIMIT_ORIENTATION 403 +#define IDC_RADIO_UNIVERSAL_LIMIT_BONE 404 +#define IDC_RADIO_UNIVERSAL_LIMIT_ANGLES 405 +#define IDC_COMBO_UNIVERSAL_LIMIT_JOINT1 406 +#define IDC_COMBO_UNIVERSAL_LIMIT_JOINT2 407 +#define IDC_EDIT_UNIVERSAL_LIMIT_YAW 408 +#define IDC_SPIN_UNIVERSAL_LIMIT_YAW 409 +#define IDC_EDIT_UNIVERSAL_LIMIT_PITCH 410 +#define IDC_SPIN_UNIVERSAL_LIMIT_PITCH 411 +#define IDC_EDIT_UNIVERSAL_LIMIT_ROLL 412 +#define IDC_SPIN_UNIVERSAL_LIMIT_ROLL 413 +#define IDC_EDIT_UNIVERSAL_LIMIT_CONE_ANGLE 414 +#define IDC_SPIN_UNIVERSAL_LIMIT_CONE_ANGLE 415 +#define IDC_EDIT_UNIVERSAL_LIMIT_PYRAMID_ANGLE1 416 +#define IDC_SPIN_UNIVERSAL_LIMIT_PYRAMID_ANGLE1 417 +#define IDC_EDIT_UNIVERSAL_LIMIT_PYRAMID_ANGLE2 418 +#define IDC_SPIN_UNIVERSAL_LIMIT_PYRAMID_ANGLE2 419 +#define IDC_DIALOG_AF_CONSTRAINT_HINGE_START 420 +#define IDC_STATIC_HINGE_AXIS 421 +#define IDC_RADIO_HINGE_AXIS_BONE 422 +#define IDC_RADIO_HINGE_AXIS_ANGLES 423 +#define IDC_COMBO_HINGE_AXIS_JOINT1 424 +#define IDC_COMBO_HINGE_AXIS_JOINT2 425 +#define IDC_EDIT_HINGE_AXIS_PITCH 426 +#define IDC_SPIN_HINGE_AXIS_PITCH 427 +#define IDC_EDIT_HINGE_AXIS_YAW 428 +#define IDC_SPIN_HINGE_AXIS_YAW 429 +#define IDC_STATIC_HINGE_LIMIT 430 +#define IDC_RADIO_HINGE_LIMIT_NONE 431 +#define IDC_STATIC_HINGE_LIMIT2 432 +#define IDC_RADIO_HINGE_LIMIT_ANGLES 433 +#define IDC_SPIN_HINGE_LIMIT_ANGLE1 434 +#define IDC_EDIT_HINGE_LIMIT_ANGLE1 435 +#define IDC_SPIN_HINGE_LIMIT_ANGLE2 436 +#define IDC_EDIT_HINGE_LIMIT_ANGLE2 437 +#define IDC_EDIT_HINGE_LIMIT_ANGLE3 438 +#define IDC_SPIN_HINGE_LIMIT_ANGLE3 439 +#define IDC_DIALOG_AF_CONSTRAINT_BAS_START 440 +#define IDC_STATIC_BAS_LIMIT_TYPE 441 +#define IDC_RADIO_BAS_LIMIT_NONE 442 +#define IDC_RADIO_BAS_LIMIT_CONE 443 +#define IDC_RADIO_BAS_LIMIT_PYRAMID 444 +#define IDC_EDIT_BAS_LIMIT_CONE_ANGLE 445 +#define IDC_SPIN_BAS_LIMIT_CONE_ANGLE 446 +#define IDC_EDIT_BAS_LIMIT_PYRAMID_ANGLE1 447 +#define IDC_SPIN_BAS_LIMIT_PYRAMID_ANGLE1 448 +#define IDC_EDIT_BAS_LIMIT_PYRAMID_ANGLE2 449 +#define IDC_SPIN_BAS_LIMIT_PYRAMID_ANGLE2 450 +#define IDC_EDIT_BAS_LIMIT_ROLL 451 +#define IDC_SPIN_BAS_LIMIT_ROLL 452 +#define IDC_STATIC_BAS_LIMIT_ORIENTATION 453 +#define IDC_RADIO_BAS_LIMIT_BONE 454 +#define IDC_RADIO_BAS_LIMIT_ANGLES 455 +#define IDC_COMBO_BAS_LIMIT_JOINT1 456 +#define IDC_COMBO_BAS_LIMIT_JOINT2 457 +#define IDC_EDIT_BAS_LIMIT_PITCH 458 +#define IDC_SPIN_BAS_LIMIT_PITCH 459 +#define IDC_EDIT_BAS_LIMIT_YAW 460 +#define IDC_SPIN_BAS_LIMIT_YAW 461 +#define IDC_STATIC_BAS_LIMIT_AXIS 462 +#define IDC_RADIO_BAS_LIMIT_AXIS_BONE 463 +#define IDC_COMBO_BAS_LIMIT_AXIS_JOINT1 464 +#define IDC_COMBO_BAS_LIMIT_AXIS_JOINT2 465 +#define IDC_RADIO_BAS_LIMIT_AXIS_ANGLES 466 +#define IDC_EDIT_BAS_LIMIT_AXIS_PITCH 467 +#define IDC_SPIN_BAS_LIMIT_AXIS_PITCH 468 +#define IDC_EDIT_BAS_LIMIT_AXIS_YAW 469 +#define IDC_SPIN_BAS_LIMIT_AXIS_YAW 470 +#define IDC_DIALOG_AF_CONSTRAINT_SLIDER_START 471 +#define IDC_RADIO_SLIDER_AXIS_BONE 472 +#define IDC_COMBO_SLIDER_AXIS_JOINT1 473 +#define IDC_COMBO_SLIDER_AXIS_JOINT2 474 +#define IDC_RADIO_SLIDER_AXIS_ANGLES 475 +#define IDC_EDIT_SLIDER_AXIS_PITCH 476 +#define IDC_SPIN_SLIDER_AXIS_PITCH 477 +#define IDC_EDIT_SLIDER_AXIS_YAW 478 +#define IDC_SPIN_SLIDER_AXIS_YAW 479 +#define IDC_STATIC_SLIDER_AXIS 480 +#define IDC_DIALOG_AF_CONSTRAINT_SPRING_START 481 +#define IDC_STATIC_ANCHOR2 482 +#define IDC_RADIO_ANCHOR2_JOINT 483 +#define IDC_COMBO_ANCHOR2_JOINT 484 +#define IDC_RADIO_ANCHOR2_COORDINATES 485 +#define IDC_EDIT_ANCHOR2_X 486 +#define IDC_SPIN_ANCHOR2_X 487 +#define IDC_EDIT_ANCHOR2_Y 488 +#define IDC_SPIN_ANCHOR2_Y 489 +#define IDC_EDIT_ANCHOR2_Z 490 +#define IDC_SPIN_ANCHOR2_Z 491 +#define IDC_STATIC_SPRING_SETTINGS 492 +#define IDC_STATIC_SPRING_STRETCH 493 +#define IDC_STATIC_SPRING_COMPRESS 494 +#define IDC_STATIC_SPRING_DAMPING 495 +#define IDC_STATIC_SPRING_REST_LENGTH 496 +#define IDC_EDIT_SPRING_STRETCH 497 +#define IDC_SPIN_SPRING_STRETCH 498 +#define IDC_EDIT_SPRING_COMPRESS 499 +#define IDC_SPIN_SPRING_COMPRESS 500 +#define IDC_EDIT_SPRING_DAMPING 501 +#define IDC_SPIN_SPRING_DAMPING 502 +#define IDC_EDIT_SPRING_REST_LENGTH 503 +#define IDC_SPIN_SPRING_REST_LENGTH 504 +#define IDC_STATIC_SPRING_LIMIT 505 +#define IDC_RADIO_SPRING_NO_MIN_LENGTH 506 +#define IDC_RADIO_SPRING_MIN_LENGTH 507 +#define IDC_EDIT_SPRING_MIN_LENGTH 508 +#define IDC_SPIN_SPRING_MIN_LENGTH 509 +#define IDC_RADIO_SPRING_NO_MAX_LENGTH 510 +#define IDC_RADIO_SPRING_MAX_LENGTH 511 +#define IDC_EDIT_SPRING_MAX_LENGTH 512 +#define IDC_SPIN_SPRING_MAX_LENGTH 513 + + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 113 +#define _APS_NEXT_COMMAND_VALUE 20000 +#define _APS_NEXT_CONTROL_VALUE 514 +#define _APS_NEXT_SYMED_VALUE 113 +#endif +#endif diff --git a/src/sys/win32/rc/Common_resource.h b/src/sys/win32/rc/Common_resource.h new file mode 100644 index 0000000..bd4497a --- /dev/null +++ b/src/sys/win32/rc/Common_resource.h @@ -0,0 +1,54 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_NEWNAME 1000 +#define IDD_RENDERBUMPFLAT 1001 +#define IDD_TOOLS_OPEN 1002 +#define IDD_DIALOG_GOTOLINE 1003 +#define IDB_TOOLS_OPEN 1004 +#define IDB_TOOLS_BACK 1005 + +#define IDC_TOOLS_EDITNAME 1200 +#define IDC_TOOLS_BACK 1201 +#define IDC_TOOLS_LOOKIN 1202 +#define IDC_TOOLS_FILENAME 1203 +#define IDC_TOOLS_FILELIST 1204 +#define IDC_RBF_WIDTH 1205 +#define IDC_RBF_HEIGHT 1206 +#define IDC_RBF_FILENAME 1207 +#define IDC_GOTOLINE_STATIC 1208 +#define IDC_GOTOLINE_EDIT 1209 + + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 1006 +#define _APS_NEXT_COMMAND_VALUE 21000 +#define _APS_NEXT_CONTROL_VALUE 1210 +#define _APS_NEXT_SYMED_VALUE 1006 +#endif +#endif diff --git a/src/sys/win32/rc/CreateResourceIDs.h b/src/sys/win32/rc/CreateResourceIDs.h new file mode 100644 index 0000000..727868a --- /dev/null +++ b/src/sys/win32/rc/CreateResourceIDs.h @@ -0,0 +1,38 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __CREATERESOURCEIDS__ +#define __CREATERESOURCEIDS__ + +/* +=============================================================================== + + Create resource IDs without conflicts. + +=============================================================================== +*/ + +void CreateResourceIDs_f( const idCmdArgs &args ); + +#endif /* !__CREATERESOURCEIDS__ */ diff --git a/src/sys/win32/rc/Debugger_resource.h b/src/sys/win32/rc/Debugger_resource.h new file mode 100644 index 0000000..007c685 --- /dev/null +++ b/src/sys/win32/rc/Debugger_resource.h @@ -0,0 +1,86 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_DBG_FIND 2000 +#define IDD_DBG_QUICKWATCH 2001 +#define IDD_DBG_ABOUT 2002 +#define IDR_DBG_MAIN 2003 +#define IDR_DBG_ACCELERATORS 2004 +#define IDR_DBG_SCRIPT_POPUP 2005 +#define IDB_DBG_TOOLBAR 2006 +#define IDI_DBG_BREAKPOINT 2007 +#define IDI_DBG_CURRENTLINE 2008 +#define IDI_DBG_CURRENT 2009 +#define IDI_DBG_EMPTY 2010 + +#define IDC_DBG_CLOSE 2200 +#define IDC_DBG_FILELIST 2201 +#define IDC_DBG_NAME 2202 +#define IDC_DBG_PATH 2203 +#define IDC_DBG_BACK 2204 +#define IDC_DBG_LOOKIN 2205 +#define IDC_DBG_RECALC 2206 +#define IDC_DBG_ADDWATCH 2207 +#define IDC_DBG_VARIABLE 2208 +#define IDC_DBG_CURVALUE 2209 +#define IDC_DBG_CURVALUE_STATIC 2210 +#define IDC_DBG_FIND 2211 + +#define ID_DBG_FILE_MRU 22000 +#define ID_DBG_FILE_OPEN 22001 +#define ID_DBG_FILE_OPENSCRIPT 22002 +#define ID_DBG_FILE_EXIT 22003 +#define ID_DBG_WINDOW_CLOSEALL 22004 +#define ID_DBG_DEBUG_TOGGLEBREAKPOINT 22005 +#define ID_DBG_DEBUG_RUN 22006 +#define ID_DBG_DEBUG_BREAK 22007 +#define ID_DBG_FILE_NEXT 22008 +#define ID_DBG_DEBUG_STEPOVER 22009 +#define ID_DBG_FILE_CLOSE 22010 +#define ID_DBG_DEBUG_STEPINTO 22011 +#define ID_DBG_DEBUG_STEPOUT 22012 +#define ID_DBG_DEBUG_QUICKWATCH 22013 +#define ID_DBG_DEBUG_RUNTOCURSOR 22014 +#define ID_DBG_DEBUG_SHOWNEXTSTATEMENT 22015 +#define ID_DBG_EDIT_COPY 22016 +#define ID_DBG_EDIT_CUT 22017 +#define ID_DBG_EDIT_PASTE 22018 +#define ID_DBG_EDIT_FIND 22019 +#define ID_DBG_EDIT_FINDSELECTED 22020 +#define ID_DBG_EDIT_FINDNEXT 22021 +#define ID_DBG_EDIT_FINDPREV 22022 +#define ID_DBG_EDIT_FINDSELECTEDPREV 22023 +#define ID_DBG_HELP_ABOUT 22024 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 2011 +#define _APS_NEXT_COMMAND_VALUE 22025 +#define _APS_NEXT_CONTROL_VALUE 2212 +#define _APS_NEXT_SYMED_VALUE 2011 +#endif +#endif diff --git a/src/sys/win32/rc/DeclEditor_resource.h b/src/sys/win32/rc/DeclEditor_resource.h new file mode 100644 index 0000000..acff065 --- /dev/null +++ b/src/sys/win32/rc/DeclEditor_resource.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_DIALOG_DECLBROWSER 3000 +#define IDD_DIALOG_DECLEDITOR 3001 +#define IDD_DIALOG_DECLNEW 3002 +#define IDR_ACCELERATOR_DECLEDITOR 3003 + +#define IDC_DECLBROWSER_BASE_TREE 3200 +#define IDC_DECLBROWSER_TREE 3201 +#define IDC_DECLBROWSER_STATIC_SEARCH_NAMES 3202 +#define IDC_DECLBROWSER_STATIC_SEARCH_TEXT 3203 +#define IDC_DECLBROWSER_EDIT_SEARCH_NAMES 3204 +#define IDC_DECLBROWSER_EDIT_SEARCH_TEXT 3205 +#define IDC_DECLBROWSER_BUTTON_FIND 3206 +#define IDC_DECLBROWSER_BUTTON_EDIT 3207 +#define IDC_DECLBROWSER_BUTTON_NEW 3208 +#define IDC_DECLBROWSER_BUTTON_RELOAD 3209 +#define IDC_DECLNEW_STATIC_NEW_TYPE 3210 +#define IDC_DECLNEW_STATIC_NEW_NAME 3211 +#define IDC_DECLNEW_STATIC_NEW_FILE 3212 +#define IDC_DECLNEW_COMBO_NEW_TYPE 3213 +#define IDC_DECLNEW_EDIT_NEW_NAME 3214 +#define IDC_DECLNEW_EDIT_NEW_FILE 3215 +#define IDC_DECLNEW_BUTTON_NEW_FILE 3216 +#define IDC_DECLEDITOR_EDIT_TEXT 3217 +#define IDC_DECLEDITOR_BUTTON_TEST 3218 + +#define ID_DECLEDITOR_FIND_NEXT 23000 +#define ID_DECLEDITOR_GOTOLINE 23001 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 3004 +#define _APS_NEXT_COMMAND_VALUE 23002 +#define _APS_NEXT_CONTROL_VALUE 3219 +#define _APS_NEXT_SYMED_VALUE 3004 +#endif +#endif diff --git a/src/sys/win32/rc/GuiEd_resource.h b/src/sys/win32/rc/GuiEd_resource.h new file mode 100644 index 0000000..2f534f5 --- /dev/null +++ b/src/sys/win32/rc/GuiEd_resource.h @@ -0,0 +1,203 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_GUIED_ABOUT 5000 +#define IDD_GUIED_OPTIONS_GRID 5001 +#define IDD_GUIED_OPTIONS_GENERAL 5002 +#define IDD_GUIED_ITEMPROPS_GENERAL 5003 +#define IDD_GUIED_ITEMPROPS_ADVANCED 5004 +#define IDD_GUIED_ITEMKEY 5005 +#define IDD_GUIED_ALPHA 5006 +#define IDD_GUIED_SCRIPTS 5007 +#define IDD_GUIED_ITEMPROPS_KEYS 5008 +#define IDD_GUIED_ITEM_VARIABLES 5009 +#define IDD_GUIED_ITEMPROPS_TEXT 5010 +#define IDD_GUIED_ITEMPROPS_IMAGE 5011 +#define IDD_GUIED_TRANSFORMER 5012 +#define IDD_GUIED_CHECKIN 5013 +#define IDR_GUIED_MAIN 5014 +#define IDR_GUIED_ACCELERATORS 5015 +#define IDR_GUIED_ITEM_POPUP 5016 +#define IDR_GUIED_VIEWERTOOLBAR 5017 +#define IDI_GUIED 5018 +#define IDI_GUIED_NAV_VISIBLE 5019 +#define IDI_GUIED_NAV_SCRIPTS 5020 +#define IDI_GUIED_NAV_VISIBLEDISABLED 5021 +#define IDI_GUIED_NAV_SCRIPTSHI 5022 +#define IDI_GUIED_NAV_COLLAPSE 5023 +#define IDI_GUIED_NAV_EXPAND 5024 + +#define IDC_GUIED_OPTIONSTAB 5200 +#define IDC_GUIED_GRIDCOLOR 5201 +#define IDC_GUIED_SPACINGWIDTH 5202 +#define IDC_GUIED_SPACINGHEIGHT 5203 +#define IDC_GUIED_GRIDVISIBLE 5204 +#define IDC_GUIED_GRIDSNAP 5205 +#define IDC_GUIED_WINDOWTREE 5206 +#define IDC_GUIED_ITEMNAME 5207 +#define IDC_GUIED_ITEMBACKGROUND 5208 +#define IDC_GUIED_TYPE 5209 +#define IDC_GUIED_USEMATERIAL 5210 +#define IDC_GUIED_ITEMBORDERMATERIAL 5211 +#define IDC_GUIED_ITEMBACKCOLOR 5212 +#define IDC_GUIED_ITEMMATCOLOR 5213 +#define IDC_GUIED_ITEMFORECOLOR 5214 +#define IDC_GUIED_ITEMBORDERCOLOR 5215 +#define IDC_GUIED_ITEMBACKCOLORALPHA 5216 +#define IDC_GUIED_ITEMTEXT 5217 +#define IDC_GUIED_ITEMBORDERCOLORALPHA 5218 +#define IDC_GUIED_ITEMTEXTSCALE 5219 +#define IDC_GUIED_ITEMBORDERSIZE 5220 +#define IDC_GUIED_ITEMMATCOLORALPHA 5221 +#define IDC_GUIED_ITEMTEXTALIGNX 5222 +#define IDC_GUIED_ITEMTEXTALIGNY 5223 +#define IDC_GUIED_ITEMMATSCALEX 5224 +#define IDC_GUIED_ITEMMATSCALEY 5225 +#define IDC_GUIED_USEBACKCOLOR 5226 +#define IDC_GUIED_ITEMMATCOLORSTATIC 5227 +#define IDC_GUIED_ITEMKEYS 5228 +#define IDC_GUIED_ADDKEY 5229 +#define IDC_GUIED_EDITKEY 5230 +#define IDC_GUIED_DELETEKEY 5231 +#define IDC_GUIED_ITEMKEY 5232 +#define IDC_GUIED_ITEMVALUE 5233 +#define IDC_GUIED_ITEMVISIBLE 5234 +#define IDC_GUIED_SELECTIONCOLOR 5235 +#define IDC_GUIED_ALPHA 5236 +#define IDC_GUIED_ALPHASLIDER 5237 +#define IDC_GUIED_SCRIPT 5238 +#define IDC_GUIED_ADDVARIABLE 5239 +#define IDC_GUIED_EDITVARIABLE 5240 +#define IDC_GUIED_DELETEVARIABLE 5241 +#define IDC_GUIED_ITEMVARIABLES 5242 +#define IDC_GUIED_USETEXT 5243 +#define IDC_GUIED_ITEMTEXTALIGN 5244 +#define IDC_GUIED_STATIC_ALIGNMENT 5245 +#define IDC_GUIED_STATIC_X 5246 +#define IDC_GUIED_STATIC_Y 5247 +#define IDC_GUIED_ITEMFORECOLORALPHA 5248 +#define IDC_GUIED_STATIC_SCALE 5249 +#define IDC_GUIED_STATIC_COLOR 5250 +#define IDC_GUIED_USEBORDERCOLOR 5251 +#define IDC_GUIED_STATIC_FONT 5252 +#define IDC_GUIED_USEBORDERMATERIAL 5253 +#define IDC_GUIED_ITEMTEXTFONT 5254 +#define IDC_GUIED_STATIC_FONTSIZE 5255 +#define IDC_GUIED_ITEMVARIABLEBACKGROUND 5256 +#define IDC_GUIED_ITEMTEXTFONTSIZE 5257 +#define IDC_GUIED_XSCALE_STATIC 5258 +#define IDC_GUIED_YSCALE_STATIC 5259 +#define IDC_GUIED_ITEMNOEVENTS 5260 +#define IDC_GUIED_ITEMNOCLIP 5261 +#define IDC_GUIED_ITEMNOCLIP2 5262 +#define IDC_GUIED_ITEMNOCURSOR 5263 +#define IDC_GUIED_ITEMNOTIME 5264 +#define IDC_GUIED_ITEMRECTX 5265 +#define IDC_GUIED_ITEMRECTW 5266 +#define IDC_GUIED_IGNOREDESKTOP 5267 +#define IDC_GUIED_ITEMRECTH 5268 +#define IDC_GUIED_ITEMTEXTNOWRAP 5269 +#define IDC_GUIED_ITEMRECTY 5270 +#define IDC_GUIED_FILENAME 5271 +#define IDC_GUIED_COMMENT 5272 +#define IDC_GUIED_ITEMTEXTSCALE_SPIN 5273 +#define IDC_GUIED_HAND 5274 + +#define ID_GUIED_NEW_HTMLDEF 25000 +#define ID_GUIED_ITEM_NEWSLIDERDEF 25001 +#define ID_GUIED_FILE_EXIT 25002 +#define ID_GUIED_HELP_ABOUT 25003 +#define ID_GUIED_WINDOW_CLOSEALL 25004 +#define ID_GUIED_FILE_NEW 25005 +#define ID_GUIED_FILE_OPEN 25006 +#define ID_GUIED_FILE_CLOSE 25007 +#define ID_GUIED_VIEW_ZOOMIN 25008 +#define ID_GUIED_VIEW_ZOOMOUT 25009 +#define ID_GUIED_FILE_SAVE 25010 +#define ID_GUIED_FILE_SAVEAS 25011 +#define ID_GUIED_VIEW_SHOWGRID 25012 +#define ID_GUIED_VIEW_SNAPTOGRID 25013 +#define ID_GUIED_VIEW_GRIDSETTINGS 25014 +#define ID_GUIED_EDIT_CUT 25015 +#define ID_GUIED_EDIT_COPY 25016 +#define ID_GUIED_EDIT_PASTE 25017 +#define ID_GUIED_VIEW_OPTIONS 25018 +#define ID_GUIED_EDIT_REDO 25019 +#define ID_GUIED_VIEW_HIDESELECTED 25020 +#define ID_GUIED_VIEW_SHOWHIDDEN 25021 +#define ID_GUIED_EDIT_UNDO 25022 +#define ID_GUIED_EDIT_DELETE 25023 +#define ID_GUIED_WINDOW_TILE 25024 +#define ID_GUIED_WINDOW_SHOWNAVIGATOR 25025 +#define ID_GUIED_FILE_MRU 25026 +#define ID_GUIED_ITEM_NEWEDITDEF 25027 +#define ID_GUIED_ITEM_NEWSCRIPTDEF 25028 +#define ID_GUIED_ITEM_PROPERTIES 25029 +#define ID_GUIED_ITEM_ARRANGEBRINGFORWARD 25030 +#define ID_GUIED_ITEM_ARRANGESENDTOBACK 25031 +#define ID_GUIED_ITEM_ARRANGESENDBACKWARD 25032 +#define ID_GUIED_ITEM_ARRANGEBRINGTOFRONT 25033 +#define ID_GUIED_ITEM_ALIGNLEFTS 25034 +#define ID_GUIED_ITEM_ALIGNCENTERS 25035 +#define ID_GUIED_ITEM_ALIGNRIGHTS 25036 +#define ID_GUIED_ITEM_ALIGNTOPS 25037 +#define ID_GUIED_ITEM_ALIGNMIDDLES 25038 +#define ID_GUIED_ITEM_ALIGNBOTTOMS 25039 +#define ID_GUIED_ITEM_MAKESAMESIZEWIDTH 25040 +#define ID_GUIED_ITEM_MAKESAMESIZEHEIGHT 25041 +#define ID_GUIED_ITEM_MAKESAMESIZEBOTH 25042 +#define ID_GUIED_ITEM_SCRIPTS 25043 +#define ID_GUIED_ITEM_ARRANGEMAKECHILD 25044 +#define ID_GUIED_TOOLS_VIEWER 25045 +#define ID_GUIED_WINDOW_CASCADE 25046 +#define ID_GUIED_ITEM_NEWWINDOWDEF 25047 +#define ID_GUIED_VIEW_UNHIDESELECTED 25048 +#define ID_GUIED_WINDOW_SHOWTRANSFORMER 25049 +#define ID_GUIED_TOOLS_RELOADMATERIALS 25050 +#define ID_GUIED_SOURCECONTROL_CHECKIN 25051 +#define ID_GUIED_SOURCECONTROL_CHECKOUT 25052 +#define ID_GUIED_SOURCECONTROL_UNDOCHECKOUT 25053 +#define ID_GUIED_SOURCECONTROL_GETLATESTVERSION 25054 +#define ID_GUIED_ITEM_NEWHTMLDEF 25055 +#define ID_GUIED_VIEW_STATUSBAR 25056 +#define ID_GUIED_ITEM_NEWCHOICEDEF 25057 +#define ID_GUIED_ITEM_NEWBINDDEF 25058 +#define ID_GUIED_ITEM_NEWLISTDEF 25059 +#define ID_GUIED_WINDOW_SHOWPROPERTIES 25060 +#define ID_GUIED_ITEM_NEWRENDERDEF 25061 +#define ID_GUIED_VIEWER_PAUSE 25062 +#define ID_GUIED_VIEWER_PLAY 25063 +#define ID_GUIED_VIEWER_START 25064 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 5025 +#define _APS_NEXT_COMMAND_VALUE 25065 +#define _APS_NEXT_CONTROL_VALUE 5275 +#define _APS_NEXT_SYMED_VALUE 5025 +#endif +#endif diff --git a/src/sys/win32/rc/MaterialEditor_Resource.h b/src/sys/win32/rc/MaterialEditor_Resource.h new file mode 100644 index 0000000..99c7f96 --- /dev/null +++ b/src/sys/win32/rc/MaterialEditor_Resource.h @@ -0,0 +1,126 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#define IDD_ME_ABOUTBOX 6000 +#define IDD_TEST_DIALOG 6001 +#define IDD_CONSOLE_FORM 6002 +#define IDD_FIND 6003 +#define IDD_MATERIALEDIT_FORM 6004 +#define IDR_ME_MAINFRAME 6005 +#define IDR_ME_MATERIALTREE_POPUP 6006 +#define IDR_ME_FILETOOLBAR 6007 +#define IDR_ME_STAGELIST_POPUP 6008 +#define IDB_ME_TREEBITMAP 6009 +#define IDI_ME_ON_ICON 6010 +#define IDI_ME_OFF_ICON 6011 +#define IDI_ME_DISABLED_ICON 6012 +#define IDS_STRING7002 6013 +#define ID_EDITMENU_INSERTFILE 6015 +#define IDC_MATERIALEDITOR_EDIT_TEXT 6200 +#define IDC_TAB1 6201 +#define IDC_TAB2 6202 +#define IDC_CONSOLE_OUTPUT 6203 +#define IDC_EDIT_FINDTEXT 6204 +#define IDC_EDIT2 6205 +#define IDC_CONSOLE_EDIT 6206 +#define IDC_CHECK_MATCH_CASE 6207 +#define IDC_CHECK_NAME_ONLY 6208 +#define IDC_CHECK_MATCH_WORD 6209 +#define IDC_RADIO_SEARCHFILE 6210 +#define IDC_RADIO_SEARCHSCOPE 6211 +#define IDC_RADIO_SEARCHALL 6212 +#define IDC_MATERIAL_NAME 6213 +#define ID_ME_FILE_EXIT 26000 +#define ID_ME_FILE_OPEN 26001 +#define ID_ME_FILE_SAVE 26002 +#define ID_ME_FILE_NEW 26003 +#define ID_ME_FILE_SAVEAS 26004 +#define ID_VIEW_INCLUDEFILENAME 26005 +#define ID_PREVIEW_RELOADARBPROGRAMS 26006 +#define ID_ME_PREVIEW_APPLYCHANGES 26007 +#define ID_ME_PREVIEW_APPLYALL 26008 +#define ID_POPUP_APPLYCHANGES 26009 +#define ID_ME_FILE_SAVEMATERIAL 26010 +#define ID_ME_FILE_SAVEFILE 26011 +#define ID_POPUP_SAVEMATERIAL 26012 +#define ID_POPUP_SAVEFILE 26013 +#define ID_POPUP_SAVEALL 26014 +#define ID_POPUP_APPLYFILE 26015 +#define ID_POPUP2_SAVEFILE 26016 +#define ID_POPUP2_SAVEALL 26017 +#define ID_POPUP_APPLYALL 26018 +#define ID_POPUP3_SAVEALL 26019 +#define ID_POPUP_APPLYMATERIAL 26020 +#define ID_ME_PREVIEW_APPLYFILE 26021 +#define ID_STAGEPOPUP_ADDSTAGE 26022 +#define ID_STAGEPOPUP_ADDBUMPMAP 26023 +#define ID_STAGEPOPUP_ADDDIFFUSEMAP 26024 +#define ID_STAGEPOPUP_ADDSPECULAR 26025 +#define ID_STAGEPOPUP_DELETESTAGE 26026 +#define ID_Menu7052 26027 +#define ID_STAGEPOPUP_RENAMESTAGE 26028 +#define ID_STAGEPOPUP_DELETEALLSTAGES 26029 +#define ID_PREVIEW_RELOADIMAGES 26030 +#define ID_POPUP_ADDMATERIAL 26031 +#define ID_POPUP_DELETEMATERIAL 26032 +#define ID_POPUP_RENAMEMATERIAL 26033 +#define ID_POPUP_ADDFOLDER 26034 +#define ID_ME_EDIT_UNDO 26035 +#define ID_ME_EDIT_REDO 26036 +#define ID_ME_EDIT_CUT 26037 +#define ID_Menu7063 26038 +#define ID_ME_EDIT_COPY 26039 +#define ID_ME_EDIT_PASTE 26040 +#define ID_ME_EDIT_DELETE 26041 +#define ID_POPUP_CUT 26042 +#define ID_POPUP_COPY 26043 +#define ID_POPUP_PASTE 26044 +#define ID_STAGEPOPUP_CUT 26045 +#define ID_STAGEPOPUP_COPY 26046 +#define ID_STAGEPOPUP_PASTE 26047 +#define ID_STAGEPOPUP_ADD 26048 +#define ID_ME_EDIT_FIND 26049 +#define ID_ME_EDIT_RENAME 26050 +#define ID_POPUP_RELOADFILE 26051 +#define ID_FIND_NEXT 26052 +#define ID_BUTTON40001 26053 +#define ID_BUTTON40002 26054 +#define ID_BUTTON40003 26055 +#define ID_BUTTON40004 26056 +#define ID_BUTTON40005 26057 +#define ID_BUTTON40006 26058 +#define ID_BUTTON40007 26059 +#define ID_ME_FILE_SAVE_ACEL 26060 +#define ID_ME_EDIT_FIND_NEXT 26061 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 6016 +#define _APS_NEXT_COMMAND_VALUE 26062 +#define _APS_NEXT_CONTROL_VALUE 6214 +#define _APS_NEXT_SYMED_VALUE 6014 +#endif +#endif diff --git a/src/sys/win32/rc/PDAEditor_resource.h b/src/sys/win32/rc/PDAEditor_resource.h new file mode 100644 index 0000000..d7d3a57 --- /dev/null +++ b/src/sys/win32/rc/PDAEditor_resource.h @@ -0,0 +1,72 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_DIALOG_PDA_EDITOR 8000 +#define IDD_DIALOG_PDA_EDIT_EMAIL 8001 +#define IDD_DIALOG_PDA_ADD 8002 + +#define IDC_LIST_PDAS 8200 +#define IDC_EDIT_FULLNAME 8201 +#define IDC_EDIT_SHORTNAME 8202 +#define IDC_EDIT_POST 8203 +#define IDC_EDIT_TITLE 8204 +#define IDC_EDIT_SECURITY 8205 +#define IDC_EDIT_IDNUM 8206 +#define IDC_BUTTON_RANDOMID 8207 +#define IDC_LIST_EMAILS 8208 +#define IDC_LIST_VIDEO 8209 +#define IDC_BUTTON_VIDEO_DELETE 8210 +#define IDC_BUTTON_VIDEO_EDIT 8211 +#define IDC_BUTTON_VIDEO_ADD 8212 +#define IDC_LIST_AUDIO 8213 +#define IDC_BUTTON_AUDIO_DELETE 8214 +#define IDC_BUTTON_AUDIO_EDIT 8215 +#define IDC_BUTTON_AUDIO_ADD 8216 +#define IDC_LIST_EMAIL 8217 +#define IDC_BUTTON_EMAIL_DELETE 8218 +#define IDC_BUTTON_EMAIL_EDIT 8219 +#define IDC_BUTTON_EMAIL_ADD 8220 +#define IDC_EDIT_SUBJECT 8221 +#define IDC_EDIT_TO 8222 +#define IDC_EDIT_FROM 8223 +#define IDC_EDIT_DATE 8224 +#define IDC_EDIT_BODY 8225 +#define IDC_BUTTON_SAVE 8226 +#define IDC_BUTTON_PDA_ADD 8227 +#define IDC_BUTTON_PDA_ 8228 +#define IDC_BUTTON_PDA_DEL 8229 +#define IDC_EDIT1 8230 + + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 8003 +#define _APS_NEXT_COMMAND_VALUE 28000 +#define _APS_NEXT_CONTROL_VALUE 8231 +#define _APS_NEXT_SYMED_VALUE 8003 +#endif +#endif diff --git a/src/sys/win32/rc/ParticleEditor_resource.h b/src/sys/win32/rc/ParticleEditor_resource.h new file mode 100644 index 0000000..5ce46d0 --- /dev/null +++ b/src/sys/win32/rc/ParticleEditor_resource.h @@ -0,0 +1,147 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_DIALOG_PARTICLE_EDITOR 7000 + +#define IDC_BUTTON_UPDATE 7200 +#define IDC_BUTTON_SAVE_PARTICLE 7201 +#define IDC_COMBO_PARTICLES 7202 +#define IDC_BUTTON_NEW 7203 +#define IDC_LIST_STAGES 7204 +#define IDC_BUTTON_ADDSTAGE 7205 +#define IDC_BUTTON_REMOVESTAGE 7206 +#define IDC_EDIT_MATERIAL 7207 +#define IDC_BUTTON_BROWSEMATERIAL 7208 +#define IDC_EDIT_DEPTHHACK 7209 +#define IDC_CHECK_ONESHOT 7210 +#define IDC_EDIT_COUNT 7211 +#define IDC_SLIDER_COUNT 7212 +#define IDC_CHECK_WORLDGRAVITY 7213 +#define IDC_EDIT_TIME 7214 +#define IDC_SLIDER_TIME 7215 +#define IDC_EDIT_TIMEOFFSET 7216 +#define IDC_EDIT_BUNCHING 7217 +#define IDC_EDIT_DEADTIME 7218 +#define IDC_RADIO_RECT 7219 +#define IDC_RADIO_SPHERE 7220 +#define IDC_RADIO_CYLINDER 7221 +#define IDC_RADIO_SPHERE2 7222 +#define IDC_CHECK_WORLDGRAVITY2 7223 +#define IDC_CHECK_ENTITYCOLOR 7224 +#define IDC_EDIT_XSIZE 7225 +#define IDC_EDIT_ZSIZE 7226 +#define IDC_EDIT_YSIZE 7227 +#define IDC_EDIT_RINGOFFSET 7228 +#define IDC_RADIO_CONE 7229 +#define IDC_RADIO_OUTWARD 7230 +#define IDC_EDIT_DIRECTIONPARM 7231 +#define IDC_RADIO_VIEW 7232 +#define IDC_RADIO_AIMED 7233 +#define IDC_RADIO_X 7234 +#define IDC_RADIO_Y 7235 +#define IDC_RADIO_Z 7236 +#define IDC_EDIT_CUSTOMPATH 7237 +#define IDC_CHECK_RANDOMANGLE2 7238 +#define IDC_CHECK_RANDOMDISTRIBUTION 7239 +#define IDC_EDIT_CYCLES 7240 +#define IDC_EDIT_COLOR 7241 +#define IDC_BUTTON_BROWSECOLOR 7242 +#define IDC_EDIT_SPEEDFROM 7243 +#define IDC_EDIT_SPEEDTO 7244 +#define IDC_EDIT_ROTATIONFROM 7245 +#define IDC_EDIT_ROTATIONTO 7246 +#define IDC_EDIT_SIZEFROM 7247 +#define IDC_EDIT_SIZETO 7248 +#define IDC_EDIT_ASPECTFROM 7249 +#define IDC_EDIT_ASPECTTO 7250 +#define IDC_EDIT_FADECOLOR 7251 +#define IDC_BUTTON_BROWSEFADECOLOR 7252 +#define IDC_EDIT_FADEIN 7253 +#define IDC_EDIT_FADEOUT 7254 +#define IDC_EDIT_ANIMFRAMES 7255 +#define IDC_EDIT_ANIMRATE 7256 +#define IDC_EDIT_GRAVITY 7257 +#define IDC_EDIT_OFFSET 7258 +#define IDC_STATIC_DIRPARM 7259 +#define IDC_BUTTON_HIDESTAGE 7260 +#define IDC_RADIO_VIEWORIGIN 7261 +#define IDC_EDIT_CUSTOMPARMS 7262 +#define IDC_CHECK_EDITPARTICLEMODE 7263 +#define IDC_RADIO_VIEWPROJECTILE 7264 +#define IDC_EDIT_FADEFRACTION 7265 +#define IDC_BUTTON_SAVE_PARTICLE_AS 7266 +#define IDC_RADIO_VIEWIMPACT 7267 +#define IDC_BUTTON_BROWSECOLOR_ENTITY 7268 +#define IDC_BUTTON_SAVE_ONENTITY 7269 +#define IDC_BUTTON_SHOWSTAGE 7270 +#define IDC_RADIO_VIEWEXPLOSION 7271 +#define IDC_BUTTON_SAVE_PARTICLEENTITIES 7272 +#define IDC_EDIT_ORIENTATIONPARM1 7273 +#define IDC_EDIT_ORIENTATIONPARM2 7274 +#define IDC_SLIDER_BUNCHING 7275 +#define IDC_SLIDER_SPEEDFROM 7276 +#define IDC_SLIDER_SPEEDTO 7277 +#define IDC_SLIDER_SIZEFROM 7278 +#define IDC_SLIDER_SIZETO 7279 +#define IDC_SLIDER_ROTATIONFROM 7280 +#define IDC_SLIDER_ROTATIONTO 7281 +#define IDC_SLIDER_ASPECTFROM 7282 +#define IDC_SLIDER_ASPECTTO 7283 +#define IDC_SLIDER_GRAVITY 7284 +#define IDC_SLIDER_FADEIN 7285 +#define IDC_SLIDER_FADEOUT 7286 +#define IDC_COMBO_CUSTOMPATH 7287 +#define IDC_STATIC_INFILE 7288 +#define IDC_BUTTON_TESTMODEL 7289 +#define IDC_BUTTON_IMPACT 7290 +#define IDC_BUTTON_MUZZLE 7291 +#define IDC_BUTTON_FLIGHT 7292 +#define IDC_BUTTON_SELECTED 7293 +#define IDC_BUTTON_DOOM 7294 +#define IDC_BUTTON_DROPEMITTER 7295 +#define IDC_BUTTON1 7296 +#define IDC_BUTTON_VECTOR 7297 +#define IDC_SLIDER_FADEFRACTION 7298 +#define IDC_EDIT_BOUNDSEXPANSION 7299 +#define IDC_STATIC_DESC 7300 +#define IDC_EDIT_INITIALANGLE 7301 +#define IDC_BUTTON_XDN 7302 +#define IDC_BUTTON_XUP 7303 +#define IDC_BUTTON_YUP 7304 +#define IDC_BUTTON_YDN 7305 +#define IDC_BUTTON_ZUP 7306 +#define IDC_BUTTON_ZDN 7307 + + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 7001 +#define _APS_NEXT_COMMAND_VALUE 27000 +#define _APS_NEXT_CONTROL_VALUE 7308 +#define _APS_NEXT_SYMED_VALUE 7001 +#endif +#endif diff --git a/src/sys/win32/rc/PropTree_resource.h b/src/sys/win32/rc/PropTree_resource.h new file mode 100644 index 0000000..16dde93 --- /dev/null +++ b/src/sys/win32/rc/PropTree_resource.h @@ -0,0 +1,49 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#define IDR_ME_EDIT_MENU 6014 +#define ID_EDITMENU_INSERTFILE 6015 +#define IDS_TRUE 9000 +#define IDS_FALSE 9001 +#define IDS_NOITEMSEL 9002 +#define IDS_SELFORINFO 9003 +#define ID_UNDO 9005 +#define ID_CUT 9006 +#define ID_COPY 9007 +#define ID_PASTE 9008 +#define ID_EDIT_DELETE 9009 +#define ID_EDIT_SELECTALL 9010 +#define IDC_SPLITTER 9200 +#define IDC_FPOINT 9201 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 9011 +#define _APS_NEXT_COMMAND_VALUE 29000 +#define _APS_NEXT_CONTROL_VALUE 9202 +#define _APS_NEXT_SYMED_VALUE 9004 +#endif +#endif diff --git a/src/sys/win32/rc/Radiant_resource.h b/src/sys/win32/rc/Radiant_resource.h new file mode 100644 index 0000000..4e438b3 --- /dev/null +++ b/src/sys/win32/rc/Radiant_resource.h @@ -0,0 +1,884 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_FINDTEXTURE 10000 +#define IDD_ENTITY 10001 +#define IDD_GAMMA 10002 +#define IDD_FINDBRUSH 10003 +#define IDD_ROTATE 10004 +#define IDD_SIDES 10005 +#define IDD_ABOUT 10006 +#define IDD_SURFACE 10007 +#define IDD_PROJECT 10008 +#define IDD_TEXTUREBAR 10009 +#define IDD_ADDCMD 10010 +#define IDD_PLAYWAVE 10011 +#define IDD_TEXLIST 10012 +#define IDD_DLG_PREFS 10013 +#define IDD_DLG_MAPINFO 10014 +#define IDD_DLG_ENTITYLIST 10015 +#define IDD_DLG_SCRIPTS 10016 +#define IDD_DLG_NEWPROJECT 10017 +#define IDD_DLG_COMMANDLIST 10018 +#define IDD_DLG_INFORMATION 10019 +#define IDD_DLG_GROUP 10020 +#define IDD_DLG_CAMERA 10021 +#define IDD_DLG_CAMERAEVENT 10022 +#define IDD_DLG_CAMERATARGET 10023 +#define IDD_DLG_WAIT 10024 +#define IDD_DIALOG_SCALE 10025 +#define IDD_DIALOG_FINDREPLACE 10026 +#define IDD_DIALOG_STAIRS 10027 +#define IDD_DIALOG_INPUT 10028 +#define IDD_DIALOG_TEXTURELIST 10029 +#define IDD_DIALOG_NEWPATCH 10030 +#define IDD_DIALOG_TEXTURELAYOUT 10031 +#define IDD_DIALOG_CAP 10032 +#define IDD_DIALOG_THICKEN 10033 +#define IDD_DIALOG_PATCH 10034 +#define IDD_DIALOG_LIGHT 10035 +#define IDD_DIALOG_ENTITY 10036 +#define IDD_DIALOG_COLORS 10037 +#define IDD_DIALOG_INSPECTORS 10038 +#define IDD_DIALOG_TEXTURE 10039 +#define IDD_DIALOG_PREVIEW 10040 +#define IDD_DIALOG_CONSOLE 10041 +#define IDD_DIALOG_COMMENTS 10042 +#define IDD_DIALOG_EDITVIEW 10043 +#define IDD_DIALOG_EDITPREVIEW 10044 +#define IDD_DIALOG_NEW_INSPECTORS 10045 +#define IDD_DIALOG_NEWCURVE 10046 +#define IDD_DIALOG_GETSTRING 10047 +#define IDD_ENTFINDREPLACE 10048 +#define IDR_ACCELERATOR1 10049 +#define IDR_ACCEL_SURFACE 10050 +#define IDR_MINIACCEL 10051 +#define IDR_MAINFRAME 10052 +#define IDR_RADIANTYPE 10053 +#define IDR_SHADERFRAME 10054 +#define IDR_SHADERTYPE 10055 +#define IDR_TOOLBAR1 10056 +#define IDR_TOOLBAR_SCALELOCK 10057 +#define IDR_TOOLBAR_ADVANCED 10058 +#define IDR_MENU_DROP 10059 +#define IDR_MENU_QUAKE3 10060 +#define IDR_MENU_EV 10061 +#define IDR_POPUP_TEXTURE 10062 +#define IDR_POPUP_SELECTION 10063 +#define IDR_POPUP_VIEW 10064 +#define IDR_POPUP_GROUP 10065 +#define IDR_POPUP_SPLINE 10066 +#define IDR_POPUP_MATERIAL 10067 +#define IDR_POPUP_ENTITY 10068 +#define IDB_IENDCAP 10069 +#define IDB_ENDCAP 10070 +#define IDB_BEVEL 10071 +#define IDB_IBEVEL 10072 +#define IDB_VIEWQE4 10073 +#define IDB_VIEW4WAY 10074 +#define IDB_VIEWDEFAULT 10075 +#define IDB_VIEWDEFAULT2 10076 +#define IDB_VIEWDEFAULT3 10077 +#define IDB_VIEWDEFAULT_Z 10078 +#define IDB_BITMAP_GROUPS 10079 +#define IDB_BITMAP_MATERIAL 10080 +#define IDB_BITMAP_HSB 10081 +#define IDB_BITMAP_RGB 10082 + +#define IDC_VIEWDEFAULT 10200 +#define IDC_VIEWDEFAULT_Z 10201 +#define IDC_SELECT 10202 +#define IDC_APPLY 10203 +#define IDC_REFRESH 10204 +#define IDC_BUTTON_RELOAD 10205 +#define IDC_BUTTON_ADD 10206 +#define IDC_BUTTON_OPEN 10207 +#define IDC_APPLY2 10208 +#define IDC_APPLY_DIFFERENT 10209 +#define IDC_BUTTON_PLAY 10210 +#define IDC_CHECK1 10211 +#define IDC_TESTCAMERA 10212 +#define IDC_CHECK_LIVE 10213 +#define IDC_BTN_HIDE 10214 +#define IDC_SPIN1 10215 +#define IDC_SPIN2 10216 +#define IDC_SPIN3 10217 +#define IDC_RADIO2 10218 +#define IDC_RADIO3 10219 +#define IDC_RADIO_VIEWTYPE 10220 +#define IDC_RADIO_FALLOFF2 10221 +#define IDC_RADIO_VIEWTYPE2 10222 +#define IDC_RADIO_FALLOFF3 10223 +#define IDC_RADIO_VIEWTYPE3 10224 +#define IDC_LIST_ENTITIES 10225 +#define IDC_RADIO_VIEWTYPE4 10226 +#define IDC_EDIT_TOTALBRUSHES 10227 +#define IDC_CHECK_LOADLAST 10228 +#define IDC_CHECK_AUTOSAVE 10229 +#define IDC_CHECK_LOADLASTMAP 10230 +#define IDC_LIST_ENTITY 10231 +#define IDC_LIST_SCRIPTS 10232 +#define IDC_NEW 10233 +#define IDC_EDIT 10234 +#define IDC_EDIT_NAME 10235 +#define IDC_RUN 10236 +#define IDC_LIST_COMMANDS 10237 +#define IDC_CHECK_FACE 10238 +#define IDC_CHECK_RIGHTCLICK 10239 +#define IDC_CHECK_TEXTUREWINDOW 10240 +#define IDC_CHECK_TEXTURETOOLBAR 10241 +#define IDC_CHECK_LIGHTDRAW 10242 +#define IDC_CHECK_SNAPT 10243 +#define IDC_CHECK_TEXTURESCROLLBAR 10244 +#define IDC_CHECK_RUNQUAKE 10245 +#define IDC_CHECK_SETGAME 10246 +#define IDC_CHECK_DISPLAYLISTS 10247 +#define IDC_EDIT_AUTOSAVE 10248 +#define IDC_CHK_FLAT 10249 +#define IDC_CHECK_NEWMAPFORMAT 10250 +#define IDC_BTN_APPLYTEXTURESTUFF 10251 +#define IDC_CHECK_QE4PAINTING 10252 +#define IDC_BTN_PLAY 10253 +#define IDC_BTN_ADD 10254 +#define IDC_BTN_TEXTURE 10255 +#define IDC_BTN_PREVIEW 10256 +#define IDC_BTN_ADDTARGET 10257 +#define IDC_BTN_SET 10258 +#define IDC_BTN_OLDCOLOR 10259 +#define IDC_BUTTON_MODEL 10260 +#define IDC_SPIN_AUTOSAVE 10261 +#define IDC_BTN_DEL 10262 +#define IDC_BUTTON_SOUND 10263 +#define IDC_CHECK_SIZEPAINT 10264 +#define IDC_BTN_EDIT 10265 +#define IDC_BUTTON_GUI 10266 +#define IDC_EDIT_X 10267 +#define IDC_SPIN_UNDO 10268 +#define IDC_BTN_HIDEALL 10269 +#define IDC_BUTTON_PARTICLE 10270 +#define IDC_CHECK_SNAPSHOTS 10271 +#define IDC_BTN_SHOWALL 10272 +#define IDC_BUTTON_SKIN 10273 +#define IDC_EDIT_FIND 10274 +#define IDC_SPIN_POINTSIZE 10275 +#define IDC_BUTTON_CURVE 10276 +#define IDC_EDIT_REPLACE 10277 +#define IDC_EDIT_S 10278 +#define IDC_CHECK_SELECTED 10279 +#define IDC_EDIT_T 10280 +#define IDC_CHECK_FORCE 10281 +#define IDC_EDIT1 10282 +#define IDC_ROTATE_BOX 10283 +#define IDC_ROTZ 10284 +#define IDC_EDIT_TOTALENTITIES 10285 +#define IDC_EDIT_Y 10286 +#define IDC_EDIT_KEY 10287 +#define IDC_EDIT_INPUT 10288 +#define IDC_EDIT_PATH 10289 +#define IDC_PARTICLE_EDIT2 10290 +#define IDC_ROTY 10291 +#define IDC_EDIT_NET 10292 +#define IDC_EDIT_Z 10293 +#define IDC_EDIT_COMMENTS 10294 +#define IDC_E_VALUE_FIELD 10295 +#define IDC_E_LIST 10296 +#define IDC_E_COMMENT 10297 +#define IDC_E_PROPS 10298 +#define IDC_E_135 10299 +#define IDC_E_180 10300 +#define IDC_E_225 10301 +#define IDC_E_270 10302 +#define IDC_E_90 10303 +#define IDC_E_45 10304 +#define IDC_E_0 10305 +#define IDC_E_315 10306 +#define IDC_E_UP 10307 +#define IDC_E_DOWN 10308 +#define IDC_E_ADDPROP 10309 +#define IDC_E_DELPROP 10310 +#define IDC_E_CREATE 10311 +#define IDC_E_STATUS 10312 +#define IDC_SHIFT_BOX 10313 +#define IDC_HSHIFT 10314 +#define IDC_VSHIFT 10315 +#define IDC_ROTATEV 10316 +#define IDC_HSTRETCH 10317 +#define IDC_SCALEV 10318 +#define IDC_EDIT_STATUSPOINTSIZE 10319 +#define IDC_SCALEH 10320 +#define IDC_EDIT_ROTATION 10321 +#define IDC_STATIC_FIELD1 10322 +#define IDC_EDIT_UNDOLEVELS 10323 +#define IDC_HSCALE 10324 +#define IDC_VSCALE 10325 +#define IDC_EDIT_FIELD1 10326 +#define IDC_STATIC_FIELD2 10327 +#define IDC_ROTATE 10328 +#define IDC_EDIT_FIELD2 10329 +#define IDC_SLIDER_CAMSPEED 10330 +#define IDC_G_EDIT 10331 +#define IDC_STATIC_FIELD3 10332 +#define IDC_CHECK_CAMXYUPDATE 10333 +#define IDC_STATIC_KEY 10334 +#define IDC_FIND_BRUSH 10335 +#define IDC_EDIT_FIELD3 10336 +#define IDC_STATIC_VALUE 10337 +#define IDC_STATIC_FIELD4 10338 +#define IDC_E_KEY_FIELD 10339 +#define IDC_EDIT_FIELD4 10340 +#define IDC_FIND_ENTITY 10341 +#define IDC_STATIC_FIELD5 10342 +#define IDC_SIDES 10343 +#define IDC_EDIT_FIELD5 10344 +#define IDC_COMBO_WHATGAME 10345 +#define IDC_ROTX 10346 +#define IDC_BTN_COLOR 10347 +#define IDC_STATIC_COLOR 10348 +#define IDC_E_COLOR 10349 +#define IDC_ABOUT_GLVENDOR 10350 +#define IDC_ABOUT_GLVERSION 10351 +#define IDC_BTN_THECOLOR 10352 +#define IDC_ABOUT_GLRENDERER 10353 +#define IDC_STATIC_STUFF 10354 +#define IDC_TEXTURE 10355 +#define IDC_CHECK_ALTDRAG 10356 +#define IDC_PRJRSHCMD 10357 +#define IDC_SPIN_HSHIFT 10358 +#define IDC_CHECK_MOUSECHASE 10359 +#define IDC_PRJREMOTEBASE 10360 +#define IDC_SPIN_VSHIFT 10361 +#define IDC_CHECK_FACECOLOR 10362 +#define IDC_PRJENTITYPATH 10363 +#define IDC_SPIN_VSCALE 10364 +#define IDC_CHECK_NOCLAMP 10365 +#define IDC_PRJTEXPATH 10366 +#define IDC_SPIN_HSCALE 10367 +#define IDC_PRJAUTOSAVE 10368 +#define IDC_SPIN_ROTATE 10369 +#define IDC_ADDCMD 10370 +#define IDC_EDIT_ROTATEAMT 10371 +#define IDC_CMD_LIST 10372 +#define IDC_CHECK_DLLENTITIES 10373 +#define IDC_PRJBASEPATH 10374 +#define IDC_CHECK_WIDETOOLBAR 10375 +#define IDC_CMDCOMMAND 10376 +#define IDC_CHECK_BUGGYICD 10377 +#define IDC_CMDMENUTEXT 10378 +#define IDC_CHECK_SGIOPENGL 10379 +#define IDC_REMCMD 10380 +#define IDC_PRJINTERVAL 10381 +#define IDC_CHECK_HICOLOR 10382 +#define IDC_PRJMAPSPATH 10383 +#define IDC_CHECK_SHADERTEST 10384 +#define IDC_EDITCMD 10385 +#define IDC_CHECK_SHOWSHADERS 10386 +#define IDC_ABOUT_GLEXTENSIONS 10387 +#define IDC_LIST_TEXTURES 10388 +#define IDC_COMBO_WIDTH 10389 +#define IDC_COMBO_HEIGHT 10390 +#define IDC_BTN_PATCHDETAILS 10391 +#define IDC_BTN_PATCHRESET 10392 +#define IDC_BTN_PATCHNATURAL 10393 +#define IDC_RADIO_CAP 10394 +#define IDC_BTN_PATCHFIT 10395 +#define IDC_RADIO_CAP2 10396 +#define IDC_CHECK_SEAMS 10397 +#define IDC_BTN_FLIPX 10398 +#define IDC_RADIO_CAP3 10399 +#define IDC_EDIT_AMOUNT 10400 +#define IDC_BTN_FLIPY 10401 +#define IDC_RADIO_CAP4 10402 +#define IDC_BTN_ASSIGNSOUND 10403 +#define IDC_BTN_AXIAL 10404 +#define IDC_BTN_ASSIGNMODEL 10405 +#define IDC_BTN_FACEFIT 10406 +#define IDC_SLIDER_TEXTUREQUALITY 10407 +#define IDC_LIST1 10408 +#define IDC_BTN_BRUSHFIT 10409 +#define IDC_LIST_EVENTS 10410 +#define IDC_LIST_WAV 10411 +#define IDC_PARTICLE_LIST1 10412 +#define IDC_LIST_VARS 10413 +#define IDC_TREE1 10414 +#define IDC_TREE_TEXTURES 10415 +#define IDC_COMBO_ROW 10416 +#define IDC_COMBO_COL 10417 +#define IDC_COMBO_TYPE 10418 +#define IDC_EDIT_WIDTH 10419 +#define IDC_CHECK_BPRIMIT 10420 +#define IDC_EDIT_HEIGHT 10421 +#define IDC_SPIN_WIDTH 10422 +#define IDC_SPIN_HEIGHT 10423 +#define IDC_COMBO_SHADERS 10424 +#define IDC_TREE_GROUP 10425 +#define IDC_CHECK_NOSTIPPLE 10426 +#define IDC_TAB_MODE 10427 +#define IDC_CHECK_POINT 10428 +#define IDC_CHECK_EQUALRADIUS 10429 +#define IDC_RADIO_FALLOFF 10430 +#define IDC_CHECK_PROJECTED 10431 +#define IDC_CHECK_EXPLICITFALLOFF 10432 +#define IDC_CHECK_CENTER 10433 +#define IDC_SLIDER_BRIGHTNESS 10434 +#define IDC_CHECK_PARALLEL 10435 +#define IDC_CHECK_SHADOWS 10436 +#define IDC_CHECK_DIFFUSE 10437 +#define IDC_CHECK_SPECULAR 10438 +#define IDC_EDIT_RADIUSY 10439 +#define IDC_EDIT_RADIUSZ 10440 +#define IDC_EDIT_RADIUSX 10441 +#define IDC_EDIT_TARGETX 10442 +#define IDC_EDIT_TARGETY 10443 +#define IDC_EDIT_TARGETZ 10444 +#define IDC_EDIT_RIGHTX 10445 +#define IDC_EDIT_RIGHTY 10446 +#define IDC_EDIT_RIGHTZ 10447 +#define IDC_EDIT_UPX 10448 +#define IDC_EDIT_UPY 10449 +#define IDC_EDIT_UPZ 10450 +#define IDC_EDIT_STARTX 10451 +#define IDC_EDIT_STARTY 10452 +#define IDC_EDIT_STARTZ 10453 +#define IDC_EDIT_ENDX 10454 +#define IDC_EDIT_ENDY 10455 +#define IDC_EDIT_ENDZ 10456 +#define IDC_EDIT_CENTERX 10457 +#define IDC_COMBO_TEXTURE 10458 +#define IDC_EDIT_CENTERY 10459 +#define IDC_EDIT_MAPS 10460 +#define IDC_EDIT_CENTERZ 10461 +#define IDC_EDIT_CAM_NAME 10462 +#define IDC_COMBO_SPLINES 10463 +#define IDC_SCROLLBAR_SEGMENT 10464 +#define IDC_EDIT_LENGTH 10465 +#define IDC_EDIT_SEGMENT 10466 +#define IDC_BTN_ADDEVENT 10467 +#define IDC_BTN_DELEVENT 10468 +#define IDC_CHECK_TRACKCAMERA 10469 +#define IDC_RADIO_EVENT 10470 +#define IDC_EDIT_TOTALSEGMENTS 10471 +#define IDC_RADIO5 10472 +#define IDC_RADIO6 10473 +#define IDC_RADIO7 10474 +#define IDC_RADIO8 10475 +#define IDC_EDIT_PARAM 10476 +#define IDC_RADIO9 10477 +#define IDC_RADIO_FIXED 10478 +#define IDC_RADIO10 10479 +#define IDC_RADIO11 10480 +#define IDC_RADIO_EDITPOINTS 10481 +#define IDC_RADIO12 10482 +#define IDC_RADIO_EDITPOINTS2 10483 +#define IDC_BTN_DELETEPOINTS 10484 +#define IDC_RADIO13 10485 +#define IDC_EDIT_TYPE 10486 +#define IDC_RADIO14 10487 +#define IDC_BTN_SELECTALL 10488 +#define IDC_RADIO15 10489 +#define IDC_CHECK_SUBDIVIDE 10490 +#define IDC_RADIO16 10491 +#define IDC_SLIDER_HORZ 10492 +#define IDC_SLIDER_VERT 10493 +#define IDC_EDIT_HORZ 10494 +#define IDC_EDIT_VERT 10495 +#define IDC_LOAD 10496 +#define IDC_PREVIEW 10497 +#define IDC_CHECK_HIDEROOT 10498 +#define IDC_CHECK_ABSOLUTE 10499 +#define IDC_LIGHTPREVIEW 10500 +#define IDC_BUTTON_COLOR1 10501 +#define IDC_STATIC_RGB_RECT 10502 +#define IDC_STATIC_HSB_RECT 10503 +#define IDC_EDIT_RED 10504 +#define IDC_SPIN_RED 10505 +#define IDC_EDIT_GREEN 10506 +#define IDC_SPIN_GREEN 10507 +#define IDC_EDIT_BLUE 10508 +#define IDC_SPIN_BLUE 10509 +#define IDC_BUTTON_COLOR2 10510 +#define IDC_STATIC_NEWCOLOR 10511 +#define IDC_EDIT_HUE 10512 +#define IDC_SPIN_HUE 10513 +#define IDC_ANIMATION_SLIDER 10514 +#define IDC_EDIT_SAT 10515 +#define IDC_SPIN_SAT 10516 +#define IDC_BTN_APPLY 10517 +#define IDC_EDIT_VAL 10518 +#define IDC_SPIN_VAL 10519 +#define IDC_EDIT_OVERBRIGHT 10520 +#define IDC_SPIN_OVERBRIGHT 10521 +#define IDC_WAITSTR 10522 +#define IDC_COMBO_CLASS 10523 +#define IDC_EDIT_INFO 10524 +#define IDC_LIST_KEYVAL 10525 +#define IDC_BUTTON_BROWSE 10526 +#define IDC_BUTTON_CREATE 10527 +#define IDC_CUSTOM1 10528 +#define IDC_TAB_INSPECTOR 10529 +#define IDC_STATIC_TITLE 10530 +#define IDC_STATIC_VAL 10531 +#define IDC_TREE_MEDIA 10532 +#define IDC_EDIT_CONSOLE 10533 +#define IDC_BUTTON_SAVE 10534 +#define IDC_STATIC_LINE 10535 +#define IDC_BUTTON_GOTO 10536 +#define IDC_EDIT_GOTO 10537 +#define IDC_EDIT_LINE 10538 +#define IDC_ENTITY_PLAY_ANIM 10539 +#define IDC_ENTITY_ANIMATIONS 10540 +#define IDC_ENTITY_STOP_ANIM 10541 +#define IDC_ENTITY_CURRENT_ANIM 10542 +#define IDC_COMBO_CURVES 10543 +#define IDC_EDIT_FIND_KEY 10544 +#define IDC_EDIT_FIND_VALUE 10545 +#define IDC_EDIT_REPLACE_KEY 10546 +#define IDC_EDIT_REPLACE_VALUE 10547 +#define IDC_KEYCOPY 10548 +#define IDC_VALUECOPY 10549 +#define IDC_CHECK_FIND_WHOLESTRINGMATCHONLY 10550 +#define IDC_REPLACE 10551 +#define IDC_FIND 10552 +#define IDC_PROMPT 10553 +#define IDC_CHECK_SELECTALLMATCHING 10554 + +#define ID_COLOR_SUPERMAL 30000 +#define ID_Menu 30001 +#define ID_DROP_VIEWOPTIONS 30002 +#define ID_VIEWOPTIONS_WIREFRAME 30003 +#define ID_WIREFRAME_ON 30004 +#define ID_WIREFRAME_OFF 30005 +#define ID_VIEWOPTIONS_FORCEVISIBILITY 30006 +#define ID_FORCEVISIBILITY_ON 30007 +#define ID_FORCEVISIBILITY_OFF 30008 +#define ID_SELECT_COMPLETEENTITY 30009 +#define ID_PATCH_NURBEDITOR 30010 +#define ID_PRECISION_CURSOR_CYCLE 30011 +#define ID_THEMES_MAX 30012 +#define ID_MATERIALS_GENERATEMATERIALSLIST 30013 +#define ID_MATERIAL_EDIT 30014 +#define ID_SELECT_ALLTARGETS 30015 +#define ID_VIEW_SHOWCOMBATNODES 30016 +#define ID_FILE_IMPORT 30017 +#define ID_VIEW_XY 30018 +#define ID_VIEW_SIDE 30019 +#define ID_VIEW_FRONT 30020 +#define ID_CAMERATOGGLE 30021 +#define ID_VIEW_CAMERATOGGLE 30022 +#define ID_TEXTURES_POPUP 30023 +#define ID_POPUP_SELECTION 30024 +#define ID_VIEW_CHANGE 30025 +#define ID_VIEW_CAMERAUPDATE 30026 +#define ID_VIEW_CLIPPER 30027 +#define ID_PREFS 30028 +#define ID_TOGGLE_LOCK 30029 +#define ID_EDIT_MAPINFO 30030 +#define ID_EDIT_ENTITYINFO 30031 +#define ID_BRUSH_SCRIPTS 30032 +#define ID_VIEW_NEXTVIEW 30033 +#define ID_HELP_COMMANDLIST 30034 +#define ID_FILE_NEWPROJECT 30035 +#define ID_SNAPTOGRID 30036 +#define ID_SPLIT_SELECTED 30037 +#define ID_CLIP_SELECTED 30038 +#define ID_FLIP_CLIP 30039 +#define ID_TOGGLEVIEW_YZ 30040 +#define ID_TOGGLEVIEW_XZ 30041 +#define ID_COLORS_GRIDTEXT 30042 +#define ID_COLORS_BRUSH 30043 +#define ID_COLORS_SELECTEDBRUSH 30044 +#define ID_COLORS_CLIPPER 30045 +#define ID_COLORS_GRIDBLOCK 30046 +#define ID_COLORS_VIEWNAME 30047 +#define ID_COLOR_SETORIGINAL 30048 +#define ID_COLOR_SETQER 30049 +#define ID_COLOR_SETBLACK 30050 +#define ID_BYEBYE 30051 +#define ID_SELECT_SCALE 30052 +#define ID_SELECT_MOUSEROTATE 30053 +#define ID_TEXTURE_REPLACESELECTED 30054 +#define ID_TEXTURE_REPLACEALL 30055 +#define ID_SELECT_COMPLETE_ENTITY 30056 +#define ID_SCALELOCKX 30057 +#define ID_SCALELOCKY 30058 +#define ID_SCALELOCKZ 30059 +#define ID_VIEW_CUBICCLIPPING 30060 +#define ID_FILE_PROJECTSETTINGS 30061 +#define ID_VIEW_CUBEOUT 30062 +#define ID_VIEW_CUBEIN 30063 +#define ID_NODES_LOADNODES 30064 +#define ID_NODES_SHOWNODES 30065 +#define ID_NODES_SHOWLINKS 30066 +#define ID_NODES_REMOVEALLNODES 30067 +#define ID_NODES_COUNTNODES 30068 +#define ID_NODES_GIVEMONEYTONELNO 30069 +#define ID_FILE_SAVEREGION 30070 +#define ID_FILE_LOADREGION 30071 +#define ID_SELECTION_MOVEDOWN 30072 +#define ID_TOOLBAR_MAIN 30073 +#define ID_SELECTION_MOVEUP 30074 +#define ID_TOOLBAR_TEXTURE 30075 +#define ID_BRUSH_MAKECONE 30076 +#define ID_TEXTURES_LOAD 30077 +#define ID_TOGGLE_ROTATELOCK 30078 +#define ID_CURVE_CYLINDER 30079 +#define ID_CURVE_ENDCAP 30080 +#define ID_CURVE_BEVEL 30081 +#define ID_CURVE_SPHERE 30082 +#define ID_CURVE_HEMISPHERE 30083 +#define ID_CURVE_QUARTER 30084 +#define ID_CURVE_EIGHTHSPHERE 30085 +#define ID_CURVE_INVERTCURVE 30086 +#define ID_FILE_IMPORTMAP 30087 +#define ID_FILE_EXPORTMAP 30088 +#define ID_EDIT_LOADPREFAB 30089 +#define ID_VIEW_SHOWCURVES 30090 +#define ID_SELECTION_SELECT_NUDGELEFT 30091 +#define ID_SELECTION_SELECT_NUDGERIGHT 30092 +#define ID_SELECTION_SELECT_NUDGEUP 30093 +#define ID_SELECTION_SELECT_NUDGEDOWN 30094 +#define ID_TEXTURES_LOADLIST 30095 +#define ID_DONTSELECTCURVE 30096 +#define ID_CONVERTCURVES 30097 +#define ID_DYNAMIC_LIGHTING 30098 +#define ID_PATCH_SHOWBOUNDINGBOX 30099 +#define ID_CURVE_SIMPLEPATCHMESH 30100 +#define ID_PATCH_WIREFRAME 30101 +#define ID_PATCH_WELD 30102 +#define ID_CURVE_PATCHTUBE 30103 +#define ID_CURVE_PATCHCONE 30104 +#define ID_CURVE_PATCHENDCAP 30105 +#define ID_CURVE_PATCHBEVEL 30106 +#define ID_CURVE_PATCHINVERTEDENDCAP 30107 +#define ID_CURVE_PATCHINVERTEDBEVEL 30108 +#define ID_PATCH_DRILLDOWN 30109 +#define ID_CURVE_LOADPATCHFILE 30110 +#define ID_CURVE_INSERTROW 30111 +#define ID_CURVE_INSERTCOLUMN 30112 +#define ID_CURVE_DELETEROW 30113 +#define ID_CURVE_DELETECOLUMN 30114 +#define ID_PATCH_INSDEL 30115 +#define ID_CURVE_INSERT_ADDCOLUMN 30116 +#define ID_CURVE_INSERT_INSERTCOLUMN 30117 +#define ID_CURVE_INSERT_ADDROW 30118 +#define ID_CURVE_INSERT_INSERTROW 30119 +#define ID_CURVE_DELETE_FIRSTCOLUMN 30120 +#define ID_CURVE_DELETE_LASTCOLUMN 30121 +#define ID_CURVE_DELETE_FIRSTROW 30122 +#define ID_CURVE_DELETE_LASTROW 30123 +#define ID_CURVE_NEGATIVE 30124 +#define ID_PATCH_BEND 30125 +#define ID_CURVE_PATCHDENSETUBE 30126 +#define ID_CURVE_PATCHVERYDENSETUBE 30127 +#define ID_CURVE_CAP 30128 +#define ID_CURVE_CAP_INVERTEDBEVEL 30129 +#define ID_CURVE_CAP_INVERTEDENDCAP 30130 +#define ID_CURVE_REDISPERSE_ROWS 30131 +#define ID_CURVE_REDISPERSE_COLS 30132 +#define ID_PATCH_NATURALIZE 30133 +#define ID_CURVE_PATCHSQUARE 30134 +#define ID_BRUSH_PRIMITIVES_SPHERE 30135 +#define ID_BRUSH_PRIMITIVES_TORUS 30136 +#define ID_TEXTURES_TEXTUREWINDOWSCALE_200 30137 +#define ID_TEXTURES_TEXTUREWINDOWSCALE_100 30138 +#define ID_TEXTURES_TEXTUREWINDOWSCALE_50 30139 +#define ID_TEXTURES_TEXTUREWINDOWSCALE_25 30140 +#define ID_TEXTURES_TEXTUREWINDOWSCALE_10 30141 +#define ID_CURVE_NEGATIVETEXTUREX 30142 +#define ID_TEXTURES_FLUSH 30143 +#define ID_CURVE_OVERLAY_SET 30144 +#define ID_CURVE_OVERLAY_CLEAR 30145 +#define ID_CURVE_NEGATIVETEXTUREY 30146 +#define ID_CURVE_THICKEN 30147 +#define ID_CURVE_CYCLECAP 30148 +#define ID_CURVE_CYCLECAPALT 30149 +#define ID_CURVE_MATRIX_TRANSPOSE 30150 +#define ID_PLUGINS_REFRESH 30151 +#define ID_TEXTURES_RELOADSHADERS 30152 +#define ID_VIEW_ENTITIESAS_BOUNDINGBOX 30153 +#define ID_VIEW_ENTITIESAS_WRITEFRAME 30154 +#define ID_VIEW_ENTITIESAS_SELECTEDWIREFRAME 30155 +#define ID_VIEW_ENTITIESAS_SELECTEDSKINNED 30156 +#define ID_VIEW_ENTITIESAS_SKINNED 30157 +#define ID_VIEW_ENTITIESAS_SKINNEDANDBOXED 30158 +#define ID_SHOW_ENTITIES 30159 +#define ID_VIEW_ENTITIESAS_WIREFRAME 30160 +#define ID_VIEW_SHOWHINT 30161 +#define ID_VIEW_OPENGLLIGHTING 30162 +#define ID_VIEW_SHOWCAULK 30163 +#define ID_VIEW_SHOWANGLES 30164 +#define ID_EDIT_SAVEPREFAB 30165 +#define ID_CURVE_MOREENDCAPSBEVELS_SQUAREENDCAP 30166 +#define ID_CURVE_MOREENDCAPSBEVELS_SQUAREBEVEL 30167 +#define ID_CURVE_PRIMITIVES_SPHERE 30168 +#define ID_VIEW_HIDESHOW_HIDESELECTED 30169 +#define ID_VIEW_HIDESHOW_SHOWHIDDEN 30170 +#define ID_TEXTURES_SHADERS_SHOW 30171 +#define ID_SELECTION_CSGADD 30172 +#define ID_VIEW_HIDESHOW_HIDENOTSELECTED 30173 +#define ID_SELECTION_CSGMERGE 30174 +#define ID_TEXTURES_FLUSH_UNUSED 30175 +#define ID_DROP_GROUP_REMOVE 30176 +#define ID_DROP_GROUP_ADDTO_WORLD 30177 +#define ID_DROP_GROUP_NEWGROUP 30178 +#define ID_DROP_GROUP_NAME 30179 +#define ID_DROP_GROUP_ADDTO 30180 +#define ID_SHOW_LIGHTVOLUMES 30181 +#define ID_SHOW_LIGHTTEXTURES 30182 +#define ID_SHOW_DOOM 30183 +#define ID_SPLINES_MODE 30184 +#define ID_SPLINES_LOAD 30185 +#define ID_SPLINES_SAVE 30186 +#define ID_MATERIAL_INFO 30187 +#define ID_SPLINE_TEST 30188 +#define ID_POPUP_NEWCAMERA_INTERPOLATED 30189 +#define ID_POPUP_NEWCAMERA_SPLINE 30190 +#define ID_POPUP_NEWCAMERA_FIXED 30191 +#define ID_SELECTION_MOVEONLY 30192 +#define ID_SELECT_BRUSHESONLY 30193 +#define ID_SELECT_BYBOUNDINGBRUSH 30194 +#define ID_TEXTURES_HIDEALL 30195 +#define ID_SELECTION_COMBINE 30196 +#define ID_PATCH_COMBINE 30197 +#define ID_VIEW_RENDERMODE 30198 +#define ID_VIEW_REBUILDRENDERDATA 30199 +#define ID_VIEW_REALTIMEREBUILD 30200 +#define ID_VIEW_RENDERENTITYOUTLINES 30201 +#define ID_VIEW_MATERIALANIMATION 30202 +#define ID_DROP_NEWMODEL 30203 +#define ID_SELECT_AXIALTEXTURE_BYWIDTH 30204 +#define ID_SELECT_AXIALTEXTURE_BYHEIGHT 30205 +#define ID_SELECT_AXIALTEXTURE_ARBITRARY 30206 +#define ID_SELECTION_EXPORT_TOOBJ 30207 +#define ID_VIEW_RENDERSELECTION 30208 +#define ID_VIEW_SHOW_SHOWVISPORTALS 30209 +#define ID_SELECT_NOMODELS 30210 +#define ID_VIEW_RENDERSOUND 30211 +#define ID_SOUND_POPUP 30212 +#define ID_SOUND_SHOWSOUNDVOLUMES 30213 +#define ID_SOUND_SHOWSELECTEDSOUNDVOLUMES 30214 +#define ID_VIEW_SHOW_NODRAW 30215 +#define ID_SELECT_MOUSESCALE 30216 +#define ID_SELECTION_EXPORT_TOCM 30217 +#define ID_SPLINES_EDITPOINTS 30218 +#define ID_SPLINES_ADDPOINTS 30219 +#define ID_SPLINES_INSERTPOINTS 30220 +#define ID_SPLINES_DELETEPOINTS 30221 +#define ID_PLUGIN_START 30222 +#define ID_PLUGIN_END 30223 +#define ID_FILE_EXIT 30224 +#define ID_FILE_SAVEAS 30225 +#define ID_VIEW_CENTER 30226 +#define ID_VIEW_UPFLOOR 30227 +#define ID_VIEW_DOWNFLOOR 30228 +#define ID_BRUSH_FLIPX 30229 +#define ID_BRUSH_FLIPY 30230 +#define ID_BRUSH_FLIPZ 30231 +#define ID_BRUSH_ROTATEX 30232 +#define ID_BRUSH_ROTATEY 30233 +#define ID_BRUSH_ROTATEZ 30234 +#define ID_BSP_FULLVIS 30235 +#define ID_BSP_FASTVIS 30236 +#define ID_BSP_NOVIS 30237 +#define ID_BSP_RELIGHT 30238 +#define ID_BSP_ENTITIES 30239 +#define ID_FILE_POINTFILE 30240 +#define ID_VIEW_100 30241 +#define ID_VIEW_75 30242 +#define ID_VIEW_50 30243 +#define ID_VIEW_25 30244 +#define ID_VIEW_12 30245 +#define ID_TEXTURES_SHOWALL 30246 +#define ID_TEXTURES_SHOWINUSE 30247 +#define ID_TEXTURES_TOGGLEVIEW 30248 +#define ID_SELECTION_CREATEENTITY 30249 +#define ID_SELECTION_EDITENTITY 30250 +#define ID_MISC_BENCHMARK 30251 +#define ID_REGION_OFF 30252 +#define ID_REGION_SETXY 30253 +#define ID_REGION_SETBRUSH 30254 +#define ID_SELECTION_MAKEHOLLOW 30255 +#define ID_SELECTION_SELECTPARTIALTALL 30256 +#define ID_SELECTION_SELECTCOMPLETETALL 30257 +#define ID_SELECTION_CSGSUBTRACT 30258 +#define ID_SELECTION_SELECTTOUCHING 30259 +#define ID_VIEW_NEAREST 30260 +#define ID_VIEW_NEARESTMIPMAP 30261 +#define ID_VIEW_LINEAR 30262 +#define ID_VIEW_BILINEAR 30263 +#define ID_VIEW_BILINEARMIPMAP 30264 +#define ID_VIEW_TRILINEAR 30265 +#define ID_TEXTURES_WIREFRAME 30266 +#define ID_TEXTURES_FLATSHADE 30267 +#define ID_VIEW_SHOWNAMES 30268 +#define ID_VIEW_ZOOMIN 30269 +#define ID_VIEW_ZOOMOUT 30270 +#define ID_VIEW_SHOWCOORDINATES 30271 +#define ID_VIEW_Z100 30272 +#define ID_VIEW_ZZOOMIN 30273 +#define ID_VIEW_ZZOOMOUT 30274 +#define ID_SELECTION_CLONE 30275 +#define ID_SELECTION_DESELECT 30276 +#define ID_SELECTION_DELETE 30277 +#define ID_SELECTION_DRAGVERTECIES 30278 +#define ID_SELECTION_DRAGEDGES 30279 +#define ID_REGION_SETTALLBRUSH 30280 +#define ID_SELECTION_SELECTINSIDE 30281 +#define ID_PROJECT_RELEAD 30282 +#define ID_PROJECT_CHANGE 30283 +#define ID_MISC_GAMMA 30284 +#define ID_VIEW_SHOWENT 30285 +#define ID_VIEW_SHOWPATH 30286 +#define ID_VIEW_SHOWLIGHTS 30287 +#define ID_VIEW_SHOWCLIP 30288 +#define ID_VIEW_SHOWWATER 30289 +#define ID_VIEW_SHOWWORLD 30290 +#define ID_MISC_TEXTUREBACKGROUN 30291 +#define ID_TEXTUREBK 30292 +#define ID_COLORS_XYBK 30293 +#define ID_FILE_ABOUT 30294 +#define ID_VIEW_CONSOLE 30295 +#define ID_VIEW_ENTITY 30296 +#define ID_VIEW_TEXTURE 30297 +#define ID_COLORS_MAJOR 30298 +#define ID_COLORS_MINOR 30299 +#define ID_SELECTION_CONNECT 30300 +#define ID_FILE_LOADPROJECT 30301 +#define ID_MISC_FINDBRUSH 30302 +#define ID_MISC_NEXTLEAKSPOT 30303 +#define ID_MISC_PREVIOUSLEAKSPOT 30304 +#define ID_BRUSH_3SIDED 30305 +#define ID_BRUSH_4SIDED 30306 +#define ID_BRUSH_5SIDED 30307 +#define ID_BRUSH_6SIDED 30308 +#define ID_BRUSH_7SIDED 30309 +#define ID_BRUSH_8SIDED 30310 +#define ID_BRUSH_9SIDED 30311 +#define ID_SELECTION_ARBITRARYROTATION 30312 +#define ID_BRUSH_ARBITRARYSIDED 30313 +#define ID_SELECTION_UNGROUPENTITY 30314 +#define ID_MISC_SELECTENTITYCOLOR 30315 +#define ID_MISC_PRINTXY 30316 +#define ID_HELP_ABOUT 30317 +#define ID_EDIT_COPYBRUSH 30318 +#define ID_EDIT_PASTEBRUSH 30319 +#define ID_TEXTURES_INSPECTOR 30320 +#define ID_VIEW_SHOWDETAIL 30321 +#define ID_SELECTION_MAKE_DETAIL 30322 +#define ID_SELECTION_MAKE_STRUCTURAL 30323 +#define ID_REGION_SETSELECTION 30324 +#define ID_VIEW_SHOWBLOCKS 30325 +#define ID_CAMERA_UP 30326 +#define ID_CAMERA_DOWN 30327 +#define ID_CAMERA_LEFT 30328 +#define ID_CAMERA_RIGHT 30329 +#define ID_CAMERA_FORWARD 30330 +#define ID_CAMERA_BACK 30331 +#define ID_CAMERA_ANGLEUP 30332 +#define ID_CAMERA_ANGLEDOWN 30333 +#define ID_CAMERA_STRAFELEFT 30334 +#define ID_CAMERA_STRAFERIGHT 30335 +#define ID_GRID_TOGGLE 30336 +#define ID_ENTITYLIST 30337 +#define ID_MAPINFO 30338 +#define ID_TOGGLECONSOLE 30339 +#define ID_TOGGLECAMERA 30340 +#define ID_TOGGLEZ 30341 +#define ID_TOGGLEVIEW 30342 +#define ID_SELECTION_TEXTURE_DEC 30343 +#define ID_SELECTION_TEXTURE_INC 30344 +#define ID_SELECTION_TEXTURE_FIT 30345 +#define ID_SELECTION_TEXTURE_ROTATECLOCK 30346 +#define ID_SELECTION_TEXTURE_ROTATECOUNTER 30347 +#define ID_SELECTION_TEXTURE_SCALEUP 30348 +#define ID_SELECTION_TEXTURE_SCALEDOWN 30349 +#define ID_SELECTION_TEXTURE_SHIFTLEFT 30350 +#define ID_SELECTION_TEXTURE_SHIFTRIGHT 30351 +#define ID_SELECTION_TEXTURE_SHIFTUP 30352 +#define ID_SELECTION_TEXTURE_SHIFTDOWN 30353 +#define ID_GRID_NEXT 30354 +#define ID_GRID_PREV 30355 +#define ID_SELECTION_TEXTURE_SCALELEFT 30356 +#define ID_SELECTION_TEXTURE_SCALERIGHT 30357 +#define ID_SELECTION_PRINT 30358 +#define ID_SELECTION_TOGGLESIZEPAINT 30359 +#define ID_PATCH_TAB 30360 +#define ID_PATCH_ENTER 30361 +#define ID_SELECT_SNAPTOGRID 30362 +#define ID_PATCH_INSPECTOR 30363 +#define ID_SELECT_ALL 30364 +#define ID_CURVE_FREEZE 30365 +#define ID_CURVE_UNFREEZE 30366 +#define ID_CURVE_UNFREEZEALL 30367 +#define ID_SELECT_RESELECT 30368 +#define ID_FITBRUSH 30369 +#define ID_FITFACE 30370 +#define ID_VIEW_CROSSHAIR 30371 +#define ID_SELECTION_INVERT 30372 +#define ID_VIEW_GROUPS 30373 +#define ID_PROJECTED_LIGHT 30374 +#define ID_SPLINES_POPUP 30375 +#define ID_SELECTION_CENTER_ORIGIN 30376 +#define ID_CURVE_INCREASE_VERT 30377 +#define ID_CURVE_DECREASE_VERT 30378 +#define ID_CURVE_INCREASE_HORZ 30379 +#define ID_CURVE_DECREASE_HORZ 30380 +#define ID_GRID_START 30381 +#define ID_GRID_POINT0625 30382 +#define ID_GRID_POINT125 30383 +#define ID_GRID_POINT25 30384 +#define ID_GRID_POINT5 30385 +#define ID_GRID_1 30386 +#define ID_GRID_2 30387 +#define ID_GRID_4 30388 +#define ID_GRID_8 30389 +#define ID_GRID_16 30390 +#define ID_GRID_32 30391 +#define ID_GRID_64 30392 +#define ID_GRID_END 30393 +#define ID_VIEW_MEDIABROWSER 30394 +#define ID_VIEW_GAME 30395 +#define ID_PATCH_NATURALIZEALT 30396 +#define ID_VIEW_SHOWTRIGGERS 30397 +#define ID_AUTOCAULK 30398 +#define ID_MISC_FINDORREPLACEENTITY 30399 +#define ID_MISC_SETVIEWPOS 30400 +#define ID_MISC_FINDNEXTENT 30401 +#define ID_SELECTION_VIEW_WIREFRAMEON 30402 +#define ID_SELECTION_VIEW_WIREFRAMEOFF 30403 +#define ID_SELECTION_VIEW_VISIBLEON 30404 +#define ID_SELECTION_VIEW_VISIBLEOFF 30405 +#define ID_ENTITY_START 40000 +#define ID_ENTITY_END 45000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 10083 +#define _APS_NEXT_COMMAND_VALUE 30406 +#define _APS_NEXT_CONTROL_VALUE 10555 +#define _APS_NEXT_SYMED_VALUE 10083 +#endif +#endif diff --git a/src/sys/win32/rc/ScriptEditor_resource.h b/src/sys/win32/rc/ScriptEditor_resource.h new file mode 100644 index 0000000..df5c093 --- /dev/null +++ b/src/sys/win32/rc/ScriptEditor_resource.h @@ -0,0 +1,43 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_DIALOG_SCRIPTEDITOR 11000 +#define IDR_ACCELERATOR_SCRIPTEDITOR 11001 + +#define IDC_SCRIPTEDITOR_EDIT_TEXT 11200 + +#define ID_SCRIPTEDITOR_FIND_NEXT 31000 +#define ID_SCRIPTEDITOR_GOTOLINE 31001 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 11002 +#define _APS_NEXT_COMMAND_VALUE 31002 +#define _APS_NEXT_CONTROL_VALUE 11201 +#define _APS_NEXT_SYMED_VALUE 11002 +#endif +#endif diff --git a/src/sys/win32/rc/SoundEditor_resource.h b/src/sys/win32/rc/SoundEditor_resource.h new file mode 100644 index 0000000..1bfa902 --- /dev/null +++ b/src/sys/win32/rc/SoundEditor_resource.h @@ -0,0 +1,84 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDD_DIALOG_SOUND 12000 +#define IDD_DIALOG_SOUNDGROUP 12001 + +#define IDC_EDIT_SOUND_NAME 12200 +#define IDC_EDIT_VOLUME 12201 +#define IDC_EDIT_MINDIST 12202 +#define IDC_EDIT_MAXDIST 12203 +#define IDC_EDIT_LEADTHROUGH 12204 +#define IDC_BTN_SAVEMAP 12205 +#define IDC_BTN_SAVEMAPAS 12206 +#define IDC_BTN_EDIT_SOUND 12207 +#define IDC_LIST_GROUPS 12208 +#define IDC_EDIT_WAIT 12209 +#define IDC_TREE_SOUNDS 12210 +#define IDC_BTN_SWITCHTOGAME 12211 +#define IDC_BTN_APPLY_SOUND 12212 +#define IDC_BTN_REFRESHSPEAKERS 12213 +#define IDC_BTN_REFRESH 12214 +#define IDC_BTN_PLAYSOUND 12215 +#define IDC_EDIT_SHADER 12216 +#define IDC_CHECK_PLAY 12217 +#define IDC_CHECK_OMNI 12218 +#define IDC_CHECKP_TRIGGERED 12219 +#define IDC_CHECK_OCCLUSION 12220 +#define IDC_EDIT_GROUP 12221 +#define IDC_BTN_DROP 12222 +#define IDC_BTN_GROUP 12223 +#define IDC_CHECK_PLAIN 12224 +#define IDC_BTN_REFRESHWAVE 12225 +#define IDC_BTN_XDN 12226 +#define IDC_BTN_XUP 12227 +#define IDC_BTN_YUP 12228 +#define IDC_BTN_YDN 12229 +#define IDC_BTN_ZUP 12230 +#define IDC_BTN_ZDN 12231 +#define IDC_COMBO_SPEAKERS 12232 +#define IDC_CHECK_GROUPONLY 12233 +#define IDC_COMBO_GROUPS 12234 +#define IDC_BTN_UP 12235 +#define IDC_BTN_DOWN 12236 +#define IDC_STATIC_WAVESIZE 12237 +#define IDC_EDIT_RANDOM 12238 +#define IDC_BTN_TRIGGER 12239 +#define IDC_CHECK_LOOPING 12240 +#define IDC_CHECK_UNCLAMPED 12241 +#define IDC_EDIT_WAIT2 12242 +#define IDC_EDIT_SHAKES 12243 + + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 12002 +#define _APS_NEXT_COMMAND_VALUE 32000 +#define _APS_NEXT_CONTROL_VALUE 12244 +#define _APS_NEXT_SYMED_VALUE 12002 +#endif +#endif diff --git a/src/sys/win32/rc/doom_resource.h b/src/sys/win32/rc/doom_resource.h new file mode 100644 index 0000000..d552772 --- /dev/null +++ b/src/sys/win32/rc/doom_resource.h @@ -0,0 +1,40 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#define IDB_BITMAP_LOGO 4000 +#define IDI_ICON1 4001 + + + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_3D_CONTROLS 1 +#define _APS_NEXT_RESOURCE_VALUE 4002 +#define _APS_NEXT_COMMAND_VALUE 24000 +#define _APS_NEXT_CONTROL_VALUE 4200 +#define _APS_NEXT_SYMED_VALUE 4002 +#endif +#endif diff --git a/src/sys/win32/win_cpu.cpp b/src/sys/win32/win_cpu.cpp new file mode 100644 index 0000000..526df03 --- /dev/null +++ b/src/sys/win32/win_cpu.cpp @@ -0,0 +1,926 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "win_local.h" + + +/* +============================================================== + + Clock ticks + +============================================================== +*/ + +/* +================ +Sys_GetClockTicks +================ +*/ +double Sys_GetClockTicks( void ) { +#if 0 + + LARGE_INTEGER li; + + QueryPerformanceCounter( &li ); + return = (double ) li.LowPart + (double) 0xFFFFFFFF * li.HighPart; + +#else + + unsigned long lo, hi; + + __asm { + push ebx + xor eax, eax + cpuid + rdtsc + mov lo, eax + mov hi, edx + pop ebx + } + return (double ) lo + (double) 0xFFFFFFFF * hi; + +#endif +} + +/* +================ +Sys_ClockTicksPerSecond +================ +*/ +double Sys_ClockTicksPerSecond( void ) { + static double ticks = 0; +#if 0 + + if ( !ticks ) { + LARGE_INTEGER li; + QueryPerformanceFrequency( &li ); + ticks = li.QuadPart; + } + +#else + + if ( !ticks ) { + HKEY hKey; + LPBYTE ProcSpeed; + DWORD buflen, ret; + + if ( !RegOpenKeyEx( HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hKey ) ) { + ProcSpeed = 0; + buflen = sizeof( ProcSpeed ); + ret = RegQueryValueEx( hKey, "~MHz", NULL, NULL, (LPBYTE) &ProcSpeed, &buflen ); + // If we don't succeed, try some other spellings. + if ( ret != ERROR_SUCCESS ) { + ret = RegQueryValueEx( hKey, "~Mhz", NULL, NULL, (LPBYTE) &ProcSpeed, &buflen ); + } + if ( ret != ERROR_SUCCESS ) { + ret = RegQueryValueEx( hKey, "~mhz", NULL, NULL, (LPBYTE) &ProcSpeed, &buflen ); + } + RegCloseKey( hKey ); + if ( ret == ERROR_SUCCESS ) { + ticks = (double) ((unsigned long)ProcSpeed) * 1000000; + } + } + } + +#endif + return ticks; +} + + +/* +============================================================== + + CPU + +============================================================== +*/ + +/* +================ +HasCPUID +================ +*/ +static bool HasCPUID( void ) { + __asm + { + pushfd // save eflags + pop eax + test eax, 0x00200000 // check ID bit + jz set21 // bit 21 is not set, so jump to set_21 + and eax, 0xffdfffff // clear bit 21 + push eax // save new value in register + popfd // store new value in flags + pushfd + pop eax + test eax, 0x00200000 // check ID bit + jz good + jmp err // cpuid not supported +set21: + or eax, 0x00200000 // set ID bit + push eax // store new value + popfd // store new value in EFLAGS + pushfd + pop eax + test eax, 0x00200000 // if bit 21 is on + jnz good + jmp err + } + +err: + return false; +good: + return true; +} + +#define _REG_EAX 0 +#define _REG_EBX 1 +#define _REG_ECX 2 +#define _REG_EDX 3 + +/* +================ +CPUID +================ +*/ +static void CPUID( int func, unsigned regs[4] ) { + unsigned regEAX, regEBX, regECX, regEDX; + + __asm pusha + __asm mov eax, func + __asm __emit 00fh + __asm __emit 0a2h + __asm mov regEAX, eax + __asm mov regEBX, ebx + __asm mov regECX, ecx + __asm mov regEDX, edx + __asm popa + + regs[_REG_EAX] = regEAX; + regs[_REG_EBX] = regEBX; + regs[_REG_ECX] = regECX; + regs[_REG_EDX] = regEDX; +} + + +/* +================ +IsAMD +================ +*/ +static bool IsAMD( void ) { + char pstring[16]; + char processorString[13]; + + // get name of processor + CPUID( 0, ( unsigned int * ) pstring ); + processorString[0] = pstring[4]; + processorString[1] = pstring[5]; + processorString[2] = pstring[6]; + processorString[3] = pstring[7]; + processorString[4] = pstring[12]; + processorString[5] = pstring[13]; + processorString[6] = pstring[14]; + processorString[7] = pstring[15]; + processorString[8] = pstring[8]; + processorString[9] = pstring[9]; + processorString[10] = pstring[10]; + processorString[11] = pstring[11]; + processorString[12] = 0; + + if ( strcmp( processorString, "AuthenticAMD" ) == 0 ) { + return true; + } + return false; +} + +/* +================ +HasCMOV +================ +*/ +static bool HasCMOV( void ) { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 15 of EDX denotes CMOV existence + if ( regs[_REG_EDX] & ( 1 << 15 ) ) { + return true; + } + return false; +} + +/* +================ +Has3DNow +================ +*/ +static bool Has3DNow( void ) { + unsigned regs[4]; + + // check AMD-specific functions + CPUID( 0x80000000, regs ); + if ( regs[_REG_EAX] < 0x80000000 ) { + return false; + } + + // bit 31 of EDX denotes 3DNow! support + CPUID( 0x80000001, regs ); + if ( regs[_REG_EDX] & ( 1 << 31 ) ) { + return true; + } + + return false; +} + +/* +================ +HasMMX +================ +*/ +static bool HasMMX( void ) { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 23 of EDX denotes MMX existence + if ( regs[_REG_EDX] & ( 1 << 23 ) ) { + return true; + } + return false; +} + +/* +================ +HasSSE +================ +*/ +static bool HasSSE( void ) { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 25 of EDX denotes SSE existence + if ( regs[_REG_EDX] & ( 1 << 25 ) ) { + return true; + } + return false; +} + +/* +================ +HasSSE2 +================ +*/ +static bool HasSSE2( void ) { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 26 of EDX denotes SSE2 existence + if ( regs[_REG_EDX] & ( 1 << 26 ) ) { + return true; + } + return false; +} + +/* +================ +HasSSE3 +================ +*/ +static bool HasSSE3( void ) { + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 0 of ECX denotes SSE3 existence + if ( regs[_REG_ECX] & ( 1 << 0 ) ) { + return true; + } + return false; +} + +/* +================ +LogicalProcPerPhysicalProc +================ +*/ +#define NUM_LOGICAL_BITS 0x00FF0000 // EBX[23:16] Bit 16-23 in ebx contains the number of logical + // processors per physical processor when execute cpuid with + // eax set to 1 +static unsigned char LogicalProcPerPhysicalProc( void ) { + unsigned int regebx = 0; + __asm { + mov eax, 1 + cpuid + mov regebx, ebx + } + return (unsigned char) ((regebx & NUM_LOGICAL_BITS) >> 16); +} + +/* +================ +GetAPIC_ID +================ +*/ +#define INITIAL_APIC_ID_BITS 0xFF000000 // EBX[31:24] Bits 24-31 (8 bits) return the 8-bit unique + // initial APIC ID for the processor this code is running on. + // Default value = 0xff if HT is not supported +static unsigned char GetAPIC_ID( void ) { + unsigned int regebx = 0; + __asm { + mov eax, 1 + cpuid + mov regebx, ebx + } + return (unsigned char) ((regebx & INITIAL_APIC_ID_BITS) >> 24); +} + +/* +================ +CPUCount + + logicalNum is the number of logical CPU per physical CPU + physicalNum is the total number of physical processor + returns one of the HT_* flags +================ +*/ +#define HT_NOT_CAPABLE 0 +#define HT_ENABLED 1 +#define HT_DISABLED 2 +#define HT_SUPPORTED_NOT_ENABLED 3 +#define HT_CANNOT_DETECT 4 + +int CPUCount( int &logicalNum, int &physicalNum ) { + int statusFlag; + SYSTEM_INFO info; + + physicalNum = 1; + logicalNum = 1; + statusFlag = HT_NOT_CAPABLE; + + info.dwNumberOfProcessors = 0; + GetSystemInfo (&info); + + // Number of physical processors in a non-Intel system + // or in a 32-bit Intel system with Hyper-Threading technology disabled + physicalNum = info.dwNumberOfProcessors; + + unsigned char HT_Enabled = 0; + + logicalNum = LogicalProcPerPhysicalProc(); + + if ( logicalNum >= 1 ) { // > 1 doesn't mean HT is enabled in the BIOS + HANDLE hCurrentProcessHandle; + DWORD dwProcessAffinity; + DWORD dwSystemAffinity; + DWORD dwAffinityMask; + + // Calculate the appropriate shifts and mask based on the + // number of logical processors. + + unsigned char i = 1, PHY_ID_MASK = 0xFF, PHY_ID_SHIFT = 0; + + while( i < logicalNum ) { + i *= 2; + PHY_ID_MASK <<= 1; + PHY_ID_SHIFT++; + } + + hCurrentProcessHandle = GetCurrentProcess(); + GetProcessAffinityMask( hCurrentProcessHandle, &dwProcessAffinity, &dwSystemAffinity ); + + // Check if available process affinity mask is equal to the + // available system affinity mask + if ( dwProcessAffinity != dwSystemAffinity ) { + statusFlag = HT_CANNOT_DETECT; + physicalNum = -1; + return statusFlag; + } + + dwAffinityMask = 1; + while ( dwAffinityMask != 0 && dwAffinityMask <= dwProcessAffinity ) { + // Check if this CPU is available + if ( dwAffinityMask & dwProcessAffinity ) { + if ( SetProcessAffinityMask( hCurrentProcessHandle, dwAffinityMask ) ) { + unsigned char APIC_ID, LOG_ID, PHY_ID; + + Sleep( 0 ); // Give OS time to switch CPU + + APIC_ID = GetAPIC_ID(); + LOG_ID = APIC_ID & ~PHY_ID_MASK; + PHY_ID = APIC_ID >> PHY_ID_SHIFT; + + if ( LOG_ID != 0 ) { + HT_Enabled = 1; + } + } + } + dwAffinityMask = dwAffinityMask << 1; + } + + // Reset the processor affinity + SetProcessAffinityMask( hCurrentProcessHandle, dwProcessAffinity ); + + if ( logicalNum == 1 ) { // Normal P4 : HT is disabled in hardware + statusFlag = HT_DISABLED; + } else { + if ( HT_Enabled ) { + // Total physical processors in a Hyper-Threading enabled system. + physicalNum /= logicalNum; + statusFlag = HT_ENABLED; + } else { + statusFlag = HT_SUPPORTED_NOT_ENABLED; + } + } + } + return statusFlag; +} + +/* +================ +HasHTT +================ +*/ +static bool HasHTT( void ) { + unsigned regs[4]; + int logicalNum, physicalNum, HTStatusFlag; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 28 of EDX denotes HTT existence + if ( !( regs[_REG_EDX] & ( 1 << 28 ) ) ) { + return false; + } + + HTStatusFlag = CPUCount( logicalNum, physicalNum ); + if ( HTStatusFlag != HT_ENABLED ) { + return false; + } + return true; +} + +/* +================ +HasHTT +================ +*/ +static bool HasDAZ( void ) { + __declspec(align(16)) unsigned char FXSaveArea[512]; + unsigned char *FXArea = FXSaveArea; + DWORD dwMask = 0; + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 24 of EDX denotes support for FXSAVE + if ( !( regs[_REG_EDX] & ( 1 << 24 ) ) ) { + return false; + } + + memset( FXArea, 0, sizeof( FXSaveArea ) ); + + __asm { + mov eax, FXArea + FXSAVE [eax] + } + + dwMask = *(DWORD *)&FXArea[28]; // Read the MXCSR Mask + return ( ( dwMask & ( 1 << 6 ) ) == ( 1 << 6 ) ); // Return if the DAZ bit is set +} + +/* +================ +Sys_GetCPUId +================ +*/ +cpuid_t Sys_GetCPUId( void ) { + int flags; + + // verify we're at least a Pentium or 486 with CPUID support + if ( !HasCPUID() ) { + return CPUID_UNSUPPORTED; + } + + // check for an AMD + if ( IsAMD() ) { + flags = CPUID_AMD; + } else { + flags = CPUID_INTEL; + } + + // check for Multi Media Extensions + if ( HasMMX() ) { + flags |= CPUID_MMX; + } + + // check for 3DNow! + if ( Has3DNow() ) { + flags |= CPUID_3DNOW; + } + + // check for Streaming SIMD Extensions + if ( HasSSE() ) { + flags |= CPUID_SSE | CPUID_FTZ; + } + + // check for Streaming SIMD Extensions 2 + if ( HasSSE2() ) { + flags |= CPUID_SSE2; + } + + // check for Streaming SIMD Extensions 3 aka Prescott's New Instructions + if ( HasSSE3() ) { + flags |= CPUID_SSE3; + } + + // check for Hyper-Threading Technology + if ( HasHTT() ) { + flags |= CPUID_HTT; + } + + // check for Conditional Move (CMOV) and fast floating point comparison (FCOMI) instructions + if ( HasCMOV() ) { + flags |= CPUID_CMOV; + } + + // check for Denormals-Are-Zero mode + if ( HasDAZ() ) { + flags |= CPUID_DAZ; + } + + return (cpuid_t)flags; +} + + +/* +=============================================================================== + + FPU + +=============================================================================== +*/ + +typedef struct bitFlag_s { + char * name; + int bit; +} bitFlag_t; + +static byte fpuState[128], *statePtr = fpuState; +static char fpuString[2048]; +static bitFlag_t controlWordFlags[] = { + { "Invalid operation", 0 }, + { "Denormalized operand", 1 }, + { "Divide-by-zero", 2 }, + { "Numeric overflow", 3 }, + { "Numeric underflow", 4 }, + { "Inexact result (precision)", 5 }, + { "Infinity control", 12 }, + { "", 0 } +}; +static char *precisionControlField[] = { + "Single Precision (24-bits)", + "Reserved", + "Double Precision (53-bits)", + "Double Extended Precision (64-bits)" +}; +static char *roundingControlField[] = { + "Round to nearest", + "Round down", + "Round up", + "Round toward zero" +}; +static bitFlag_t statusWordFlags[] = { + { "Invalid operation", 0 }, + { "Denormalized operand", 1 }, + { "Divide-by-zero", 2 }, + { "Numeric overflow", 3 }, + { "Numeric underflow", 4 }, + { "Inexact result (precision)", 5 }, + { "Stack fault", 6 }, + { "Error summary status", 7 }, + { "FPU busy", 15 }, + { "", 0 } +}; + +/* +=============== +Sys_FPU_PrintStateFlags +=============== +*/ +int Sys_FPU_PrintStateFlags( char *ptr, int ctrl, int stat, int tags, int inof, int inse, int opof, int opse ) { + int i, length = 0; + + length += sprintf( ptr+length, "CTRL = %08x\n" + "STAT = %08x\n" + "TAGS = %08x\n" + "INOF = %08x\n" + "INSE = %08x\n" + "OPOF = %08x\n" + "OPSE = %08x\n" + "\n", + ctrl, stat, tags, inof, inse, opof, opse ); + + length += sprintf( ptr+length, "Control Word:\n" ); + for ( i = 0; controlWordFlags[i].name[0]; i++ ) { + length += sprintf( ptr+length, " %-30s = %s\n", controlWordFlags[i].name, ( ctrl & ( 1 << controlWordFlags[i].bit ) ) ? "true" : "false" ); + } + length += sprintf( ptr+length, " %-30s = %s\n", "Precision control", precisionControlField[(ctrl>>8)&3] ); + length += sprintf( ptr+length, " %-30s = %s\n", "Rounding control", roundingControlField[(ctrl>>10)&3] ); + + length += sprintf( ptr+length, "Status Word:\n" ); + for ( i = 0; statusWordFlags[i].name[0]; i++ ) { + ptr += sprintf( ptr+length, " %-30s = %s\n", statusWordFlags[i].name, ( stat & ( 1 << statusWordFlags[i].bit ) ) ? "true" : "false" ); + } + length += sprintf( ptr+length, " %-30s = %d%d%d%d\n", "Condition code", (stat>>8)&1, (stat>>9)&1, (stat>>10)&1, (stat>>14)&1 ); + length += sprintf( ptr+length, " %-30s = %d\n", "Top of stack pointer", (stat>>11)&7 ); + + return length; +} + +/* +=============== +Sys_FPU_StackIsEmpty +=============== +*/ +bool Sys_FPU_StackIsEmpty( void ) { + __asm { + mov eax, statePtr + fnstenv [eax] + mov eax, [eax+8] + xor eax, 0xFFFFFFFF + and eax, 0x0000FFFF + jz empty + } + return false; +empty: + return true; +} + +/* +=============== +Sys_FPU_ClearStack +=============== +*/ +void Sys_FPU_ClearStack( void ) { + __asm { + mov eax, statePtr + fnstenv [eax] + mov eax, [eax+8] + xor eax, 0xFFFFFFFF + mov edx, (3<<14) + emptyStack: + mov ecx, eax + and ecx, edx + jz done + fstp st + shr edx, 2 + jmp emptyStack + done: + } +} + +/* +=============== +Sys_FPU_GetState + + gets the FPU state without changing the state +=============== +*/ +const char *Sys_FPU_GetState( void ) { + double fpuStack[8] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + double *fpuStackPtr = fpuStack; + int i, numValues; + char *ptr; + + __asm { + mov esi, statePtr + mov edi, fpuStackPtr + fnstenv [esi] + mov esi, [esi+8] + xor esi, 0xFFFFFFFF + mov edx, (3<<14) + xor eax, eax + mov ecx, esi + and ecx, edx + jz done + fst qword ptr [edi+0] + inc eax + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(1) + fst qword ptr [edi+8] + inc eax + fxch st(1) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(2) + fst qword ptr [edi+16] + inc eax + fxch st(2) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(3) + fst qword ptr [edi+24] + inc eax + fxch st(3) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(4) + fst qword ptr [edi+32] + inc eax + fxch st(4) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(5) + fst qword ptr [edi+40] + inc eax + fxch st(5) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(6) + fst qword ptr [edi+48] + inc eax + fxch st(6) + shr edx, 2 + mov ecx, esi + and ecx, edx + jz done + fxch st(7) + fst qword ptr [edi+56] + inc eax + fxch st(7) + done: + mov numValues, eax + } + + int ctrl = *(int *)&fpuState[0]; + int stat = *(int *)&fpuState[4]; + int tags = *(int *)&fpuState[8]; + int inof = *(int *)&fpuState[12]; + int inse = *(int *)&fpuState[16]; + int opof = *(int *)&fpuState[20]; + int opse = *(int *)&fpuState[24]; + + ptr = fpuString; + ptr += sprintf( ptr,"FPU State:\n" + "num values on stack = %d\n", numValues ); + for ( i = 0; i < 8; i++ ) { + ptr += sprintf( ptr, "ST%d = %1.10e\n", i, fpuStack[i] ); + } + + Sys_FPU_PrintStateFlags( ptr, ctrl, stat, tags, inof, inse, opof, opse ); + + return fpuString; +} + +/* +=============== +Sys_FPU_EnableExceptions +=============== +*/ +void Sys_FPU_EnableExceptions( int exceptions ) { + __asm { + mov eax, statePtr + mov ecx, exceptions + and cx, 63 + not cx + fnstcw word ptr [eax] + mov bx, word ptr [eax] + or bx, 63 + and bx, cx + mov word ptr [eax], bx + fldcw word ptr [eax] + } +} + +/* +=============== +Sys_FPU_SetPrecision +=============== +*/ +void Sys_FPU_SetPrecision( int precision ) { + short precisionBitTable[4] = { 0, 1, 3, 0 }; + short precisionBits = precisionBitTable[precision & 3] << 8; + short precisionMask = ~( ( 1 << 9 ) | ( 1 << 8 ) ); + + __asm { + mov eax, statePtr + mov cx, precisionBits + fnstcw word ptr [eax] + mov bx, word ptr [eax] + and bx, precisionMask + or bx, cx + mov word ptr [eax], bx + fldcw word ptr [eax] + } +} + +/* +================ +Sys_FPU_SetRounding +================ +*/ +void Sys_FPU_SetRounding( int rounding ) { + short roundingBitTable[4] = { 0, 1, 2, 3 }; + short roundingBits = roundingBitTable[rounding & 3] << 10; + short roundingMask = ~( ( 1 << 11 ) | ( 1 << 10 ) ); + + __asm { + mov eax, statePtr + mov cx, roundingBits + fnstcw word ptr [eax] + mov bx, word ptr [eax] + and bx, roundingMask + or bx, cx + mov word ptr [eax], bx + fldcw word ptr [eax] + } +} + +/* +================ +Sys_FPU_SetDAZ +================ +*/ +void Sys_FPU_SetDAZ( bool enable ) { + DWORD dwData; + + _asm { + movzx ecx, byte ptr enable + and ecx, 1 + shl ecx, 6 + STMXCSR dword ptr dwData + mov eax, dwData + and eax, ~(1<<6) // clear DAX bit + or eax, ecx // set the DAZ bit + mov dwData, eax + LDMXCSR dword ptr dwData + } +} + +/* +================ +Sys_FPU_SetFTZ +================ +*/ +void Sys_FPU_SetFTZ( bool enable ) { + DWORD dwData; + + _asm { + movzx ecx, byte ptr enable + and ecx, 1 + shl ecx, 15 + STMXCSR dword ptr dwData + mov eax, dwData + and eax, ~(1<<15) // clear FTZ bit + or eax, ecx // set the FTZ bit + mov dwData, eax + LDMXCSR dword ptr dwData + } +} diff --git a/src/sys/win32/win_glimp.cpp b/src/sys/win32/win_glimp.cpp new file mode 100644 index 0000000..d0b7d7f --- /dev/null +++ b/src/sys/win32/win_glimp.cpp @@ -0,0 +1,1237 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +/* +** WIN_GLIMP.C +** +** This file contains ALL Win32 specific stuff having to do with the +** OpenGL refresh. When a port is being made the following functions +** must be implemented by the port: +** +** GLimp_SwapBuffers +** GLimp_Init +** GLimp_Shutdown +** GLimp_SetGamma +** +** Note that the GLW_xxx functions are Windows specific GL-subsystem +** related functions that are relevant ONLY to win_glimp.c +*/ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "win_local.h" +#include "rc/AFEditor_resource.h" +#include "rc/doom_resource.h" +#include "../../renderer/tr_local.h" + +static void GLW_InitExtensions( void ); + + +// WGL_ARB_extensions_string +PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB; + +// WGL_EXT_swap_interval +PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + +// WGL_ARB_pixel_format +PFNWGLGETPIXELFORMATATTRIBIVARBPROC wglGetPixelFormatAttribivARB; +PFNWGLGETPIXELFORMATATTRIBFVARBPROC wglGetPixelFormatAttribfvARB; +PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; + +// WGL_ARB_pbuffer +PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB; +PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB; +PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB; +PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB; +PFNWGLQUERYPBUFFERARBPROC wglQueryPbufferARB; + +// WGL_ARB_render_texture +PFNWGLBINDTEXIMAGEARBPROC wglBindTexImageARB; +PFNWGLRELEASETEXIMAGEARBPROC wglReleaseTexImageARB; +PFNWGLSETPBUFFERATTRIBARBPROC wglSetPbufferAttribARB; + + + +/* ARB_pixel_format */ +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B + +/* ARB_multisample */ +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 + + + +// +// function declaration +// +bool QGL_Init( const char *dllname ); +void QGL_Shutdown( void ); + + + +/* +======================== +GLimp_GetOldGammaRamp +======================== +*/ +static void GLimp_SaveGamma( void ) { + HDC hDC; + BOOL success; + + hDC = GetDC( GetDesktopWindow() ); + success = GetDeviceGammaRamp( hDC, win32.oldHardwareGamma ); + common->DPrintf( "...getting default gamma ramp: %s\n", success ? "success" : "failed" ); + ReleaseDC( GetDesktopWindow(), hDC ); +} + +/* +======================== +GLimp_RestoreGamma +======================== +*/ +static void GLimp_RestoreGamma( void ) { + HDC hDC; + BOOL success; + + // if we never read in a reasonable looking + // table, don't write it out + if ( win32.oldHardwareGamma[0][255] == 0 ) { + return; + } + + hDC = GetDC( GetDesktopWindow() ); + success = SetDeviceGammaRamp( hDC, win32.oldHardwareGamma ); + common->DPrintf ( "...restoring hardware gamma: %s\n", success ? "success" : "failed" ); + ReleaseDC( GetDesktopWindow(), hDC ); +} + + +/* +======================== +GLimp_SetGamma + +The renderer calls this when the user adjusts r_gamma or r_brightness +======================== +*/ +void GLimp_SetGamma( unsigned short red[256], unsigned short green[256], unsigned short blue[256] ) { + unsigned short table[3][256]; + int i; + + if ( !win32.hDC ) { + return; + } + + for ( i = 0; i < 256; i++ ) { + table[0][i] = red[i]; + table[1][i] = green[i]; + table[2][i] = blue[i]; + } + + if ( !SetDeviceGammaRamp( win32.hDC, table ) ) { + common->Printf( "WARNING: SetDeviceGammaRamp failed.\n" ); + } +} + +/* +============================================================================= + +WglExtension Grabbing + +This is gross -- creating a window just to get a context to get the wgl extensions + +============================================================================= +*/ + +/* +==================== +FakeWndProc + +Only used to get wglExtensions +==================== +*/ +LONG WINAPI FakeWndProc ( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) { + + if ( uMsg == WM_DESTROY ) { + PostQuitMessage(0); + } + + if ( uMsg != WM_CREATE ) { + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + const static PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, + 24, + 0, 0, 0, 0, 0, 0, + 8, 0, + 0, 0, 0, 0, + 24, 8, + 0, + PFD_MAIN_PLANE, + 0, + 0, + 0, + 0, + }; + int pixelFormat; + HDC hDC; + HGLRC hGLRC; + + hDC = GetDC(hWnd); + + // Set up OpenGL + pixelFormat = ChoosePixelFormat(hDC, &pfd); + SetPixelFormat(hDC, pixelFormat, &pfd); + hGLRC = qwglCreateContext(hDC); + qwglMakeCurrent(hDC, hGLRC); + + // free things + wglMakeCurrent(NULL, NULL); + wglDeleteContext(hGLRC); + ReleaseDC(hWnd, hDC); + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + + +/* +================== +GLW_GetWGLExtensionsWithFakeWindow +================== +*/ +void GLW_CheckWGLExtensions( HDC hDC ) { + wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC) + GLimp_ExtensionPointer("wglGetExtensionsStringARB"); + if ( wglGetExtensionsStringARB ) { + glConfig.wgl_extensions_string = (const char *) wglGetExtensionsStringARB(hDC); + } else { + glConfig.wgl_extensions_string = ""; + } + + // WGL_EXT_swap_control + wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) GLimp_ExtensionPointer( "wglSwapIntervalEXT" ); + r_swapInterval.SetModified(); // force a set next frame + + // WGL_ARB_pixel_format + wglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)GLimp_ExtensionPointer("wglGetPixelFormatAttribivARB"); + wglGetPixelFormatAttribfvARB = (PFNWGLGETPIXELFORMATATTRIBFVARBPROC)GLimp_ExtensionPointer("wglGetPixelFormatAttribfvARB"); + wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)GLimp_ExtensionPointer("wglChoosePixelFormatARB"); + + // WGL_ARB_pbuffer + wglCreatePbufferARB = (PFNWGLCREATEPBUFFERARBPROC)GLimp_ExtensionPointer("wglCreatePbufferARB"); + wglGetPbufferDCARB = (PFNWGLGETPBUFFERDCARBPROC)GLimp_ExtensionPointer("wglGetPbufferDCARB"); + wglReleasePbufferDCARB = (PFNWGLRELEASEPBUFFERDCARBPROC)GLimp_ExtensionPointer("wglReleasePbufferDCARB"); + wglDestroyPbufferARB = (PFNWGLDESTROYPBUFFERARBPROC)GLimp_ExtensionPointer("wglDestroyPbufferARB"); + wglQueryPbufferARB = (PFNWGLQUERYPBUFFERARBPROC)GLimp_ExtensionPointer("wglQueryPbufferARB"); + + // WGL_ARB_render_texture + wglBindTexImageARB = (PFNWGLBINDTEXIMAGEARBPROC)GLimp_ExtensionPointer("wglBindTexImageARB"); + wglReleaseTexImageARB = (PFNWGLRELEASETEXIMAGEARBPROC)GLimp_ExtensionPointer("wglReleaseTexImageARB"); + wglSetPbufferAttribARB = (PFNWGLSETPBUFFERATTRIBARBPROC)GLimp_ExtensionPointer("wglSetPbufferAttribARB"); +} + +/* +================== +GLW_GetWGLExtensionsWithFakeWindow +================== +*/ +static void GLW_GetWGLExtensionsWithFakeWindow( void ) { + HWND hWnd; + MSG msg; + + // Create a window for the sole purpose of getting + // a valid context to get the wglextensions + hWnd = CreateWindow(WIN32_FAKE_WINDOW_CLASS_NAME, GAME_NAME, + WS_OVERLAPPEDWINDOW, + 40, 40, + 640, + 480, + NULL, NULL, win32.hInstance, NULL ); + if ( !hWnd ) { + common->FatalError( "GLW_GetWGLExtensionsWithFakeWindow: Couldn't create fake window" ); + } + + HDC hDC = GetDC( hWnd ); + HGLRC gRC = wglCreateContext( hDC ); + wglMakeCurrent( hDC, gRC ); + GLW_CheckWGLExtensions( hDC ); + wglDeleteContext( gRC ); + ReleaseDC( hWnd, hDC ); + + DestroyWindow( hWnd ); + while ( GetMessage( &msg, NULL, 0, 0 ) ) { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } +} + +//============================================================================= + +/* +==================== +GLW_WM_CREATE +==================== +*/ +void GLW_WM_CREATE( HWND hWnd ) { +} + + + +/* +==================== +GLW_InitDriver + +Set the pixelformat for the window before it is +shown, and create the rendering context +==================== +*/ +static bool GLW_InitDriver( glimpParms_t parms ) { + PIXELFORMATDESCRIPTOR src = + { + sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd + 1, // version number + PFD_DRAW_TO_WINDOW | // support window + PFD_SUPPORT_OPENGL | // support OpenGL + PFD_DOUBLEBUFFER, // double buffered + PFD_TYPE_RGBA, // RGBA type + 32, // 32-bit color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 8, // 8 bit destination alpha + 0, // shift bit ignored + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits ignored + 24, // 24-bit z-buffer + 8, // 8-bit stencil buffer + 0, // no auxiliary buffer + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0 // layer masks ignored + }; + + common->Printf( "Initializing OpenGL driver\n" ); + + // + // get a DC for our window if we don't already have one allocated + // + if ( win32.hDC == NULL ) { + common->Printf( "...getting DC: " ); + + if ( ( win32.hDC = GetDC( win32.hWnd ) ) == NULL ) { + common->Printf( "^3failed^0\n" ); + return false; + } + common->Printf( "succeeded\n" ); + } + + // the multisample path uses the wgl + if ( wglChoosePixelFormatARB && parms.multiSamples > 1 ) { + int iAttributes[20]; + FLOAT fAttributes[] = {0, 0}; + UINT numFormats; + + // FIXME: specify all the other stuff + iAttributes[0] = WGL_SAMPLE_BUFFERS_ARB; + iAttributes[1] = 1; + iAttributes[2] = WGL_SAMPLES_ARB; + iAttributes[3] = parms.multiSamples; + iAttributes[4] = WGL_DOUBLE_BUFFER_ARB; + iAttributes[5] = TRUE; + iAttributes[6] = WGL_STENCIL_BITS_ARB; + iAttributes[7] = 8; + iAttributes[8] = WGL_DEPTH_BITS_ARB; + iAttributes[9] = 24; + iAttributes[10] = WGL_RED_BITS_ARB; + iAttributes[11] = 8; + iAttributes[12] = WGL_BLUE_BITS_ARB; + iAttributes[13] = 8; + iAttributes[14] = WGL_GREEN_BITS_ARB; + iAttributes[15] = 8; + iAttributes[16] = WGL_ALPHA_BITS_ARB; + iAttributes[17] = 8; + iAttributes[18] = 0; + iAttributes[19] = 0; + + wglChoosePixelFormatARB( win32.hDC, iAttributes, fAttributes, 1, &win32.pixelformat, &numFormats ); + } else { + // this is the "classic" choose pixel format path + + // eventually we may need to have more fallbacks, but for + // now, ask for everything + if ( parms.stereo ) { + common->Printf( "...attempting to use stereo\n" ); + src.dwFlags |= PFD_STEREO; + } + + // + // choose, set, and describe our desired pixel format. If we're + // using a minidriver then we need to bypass the GDI functions, + // otherwise use the GDI functions. + // + if ( ( win32.pixelformat = ChoosePixelFormat( win32.hDC, &src ) ) == 0 ) { + common->Printf( "...^3GLW_ChoosePFD failed^0\n"); + return false; + } + common->Printf( "...PIXELFORMAT %d selected\n", win32.pixelformat ); + } + + // get the full info + DescribePixelFormat( win32.hDC, win32.pixelformat, sizeof( win32.pfd ), &win32.pfd ); + glConfig.colorBits = win32.pfd.cColorBits; + glConfig.depthBits = win32.pfd.cDepthBits; + glConfig.stencilBits = win32.pfd.cStencilBits; + + // XP seems to set this incorrectly + if ( !glConfig.stencilBits ) { + glConfig.stencilBits = 8; + } + + // the same SetPixelFormat is used either way + if ( SetPixelFormat( win32.hDC, win32.pixelformat, &win32.pfd ) == FALSE ) { + common->Printf( "...^3SetPixelFormat failed^0\n", win32.hDC ); + return false; + } + + // + // startup the OpenGL subsystem by creating a context and making it current + // + common->Printf( "...creating GL context: " ); + if ( ( win32.hGLRC = qwglCreateContext( win32.hDC ) ) == 0 ) { + common->Printf( "^3failed^0\n" ); + return false; + } + common->Printf( "succeeded\n" ); + + common->Printf( "...making context current: " ); + if ( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) { + qwglDeleteContext( win32.hGLRC ); + win32.hGLRC = NULL; + common->Printf( "^3failed^0\n" ); + return false; + } + common->Printf( "succeeded\n" ); + + return true; +} + +/* +==================== +GLW_CreateWindowClasses +==================== +*/ +static void GLW_CreateWindowClasses( void ) { + WNDCLASS wc; + + // + // register the window class if necessary + // + if ( win32.windowClassRegistered ) { + return; + } + + memset( &wc, 0, sizeof( wc ) ); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) MainWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = win32.hInstance; + wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE(IDI_ICON1)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (struct HBRUSH__ *)COLOR_GRAYTEXT; + wc.lpszMenuName = 0; + wc.lpszClassName = WIN32_WINDOW_CLASS_NAME; + + if ( !RegisterClass( &wc ) ) { + common->FatalError( "GLW_CreateWindow: could not register window class" ); + } + common->Printf( "...registered window class\n" ); + + // now register the fake window class that is only used + // to get wgl extensions + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) FakeWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = win32.hInstance; + wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE(IDI_ICON1)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (struct HBRUSH__ *)COLOR_GRAYTEXT; + wc.lpszMenuName = 0; + wc.lpszClassName = WIN32_FAKE_WINDOW_CLASS_NAME; + + if ( !RegisterClass( &wc ) ) { + common->FatalError( "GLW_CreateWindow: could not register window class" ); + } + common->Printf( "...registered fake window class\n" ); + + win32.windowClassRegistered = true; +} + +/* +======================= +GLW_CreateWindow + +Responsible for creating the Win32 window. +If cdsFullscreen is true, it won't have a border +======================= +*/ +static bool GLW_CreateWindow( glimpParms_t parms ) { + int stylebits; + int x, y, w, h; + int exstyle; + + // + // compute width and height + // + if ( parms.fullScreen ) { + exstyle = WS_EX_TOPMOST; + stylebits = WS_POPUP|WS_VISIBLE|WS_SYSMENU; + + x = 0; + y = 0; + w = parms.width; + h = parms.height; + } else { + RECT r; + + // adjust width and height for window border + r.bottom = parms.height; + r.left = 0; + r.top = 0; + r.right = parms.width; + + exstyle = 0; + stylebits = WINDOW_STYLE|WS_SYSMENU; + AdjustWindowRect (&r, stylebits, FALSE); + + w = r.right - r.left; + h = r.bottom - r.top; + + x = win32.win_xpos.GetInteger(); + y = win32.win_ypos.GetInteger(); + + // adjust window coordinates if necessary + // so that the window is completely on screen + if ( x + w > win32.desktopWidth ) { + x = ( win32.desktopWidth - w ); + } + if ( y + h > win32.desktopHeight ) { + y = ( win32.desktopHeight - h ); + } + if ( x < 0 ) { + x = 0; + } + if ( y < 0 ) { + y = 0; + } + } + + win32.hWnd = CreateWindowEx ( + exstyle, + WIN32_WINDOW_CLASS_NAME, + GAME_NAME, + stylebits, + x, y, w, h, + NULL, + NULL, + win32.hInstance, + NULL); + + if ( !win32.hWnd ) { + common->Printf( "^3GLW_CreateWindow() - Couldn't create window^0\n" ); + return false; + } + + ::SetTimer( win32.hWnd, 0, 100, NULL ); + + ShowWindow( win32.hWnd, SW_SHOW ); + UpdateWindow( win32.hWnd ); + common->Printf( "...created window @ %d,%d (%dx%d)\n", x, y, w, h ); + + if ( !GLW_InitDriver( parms ) ) { + ShowWindow( win32.hWnd, SW_HIDE ); + DestroyWindow( win32.hWnd ); + win32.hWnd = NULL; + return false; + } + + SetForegroundWindow( win32.hWnd ); + SetFocus( win32.hWnd ); + + glConfig.isFullscreen = parms.fullScreen; + + return true; +} + + + +static void PrintCDSError( int value ) { + switch ( value ) { + case DISP_CHANGE_RESTART: + common->Printf( "restart required\n" ); + break; + case DISP_CHANGE_BADPARAM: + common->Printf( "bad param\n" ); + break; + case DISP_CHANGE_BADFLAGS: + common->Printf( "bad flags\n" ); + break; + case DISP_CHANGE_FAILED: + common->Printf( "DISP_CHANGE_FAILED\n" ); + break; + case DISP_CHANGE_BADMODE: + common->Printf( "bad mode\n" ); + break; + case DISP_CHANGE_NOTUPDATED: + common->Printf( "not updated\n" ); + break; + default: + common->Printf( "unknown error %d\n", value ); + break; + } +} + + +/* +=================== +GLW_SetFullScreen +=================== +*/ +static bool GLW_SetFullScreen( glimpParms_t parms ) { +#if 0 + // for some reason, bounds checker claims that windows is + // writing past the bounds of dm in the get display frequency call + union { + DEVMODE dm; + byte filler[1024]; + } hack; +#endif + DEVMODE dm; + int cdsRet; + + DEVMODE devmode; + int modeNum; + bool matched; + + // first make sure the user is not trying to select a mode that his card/monitor can't handle + matched = false; + for ( modeNum = 0 ; ; modeNum++ ) { + if ( !EnumDisplaySettings( NULL, modeNum, &devmode ) ) { + if ( matched ) { + // we got a resolution match, but not a frequency match + // so disable the frequency requirement + common->Printf( "...^3%dhz is unsupported at %dx%d^0\n", parms.displayHz, parms.width, parms.height ); + parms.displayHz = 0; + break; + } + common->Printf( "...^3%dx%d is unsupported in 32 bit^0\n", parms.width, parms.height ); + return false; + } + if ( (int)devmode.dmPelsWidth >= parms.width + && (int)devmode.dmPelsHeight >= parms.height + && devmode.dmBitsPerPel == 32 ) { + + matched = true; + + if ( parms.displayHz == 0 || devmode.dmDisplayFrequency == parms.displayHz ) { + break; + } + } + } + + memset( &dm, 0, sizeof( dm ) ); + dm.dmSize = sizeof( dm ); + + dm.dmPelsWidth = parms.width; + dm.dmPelsHeight = parms.height; + dm.dmBitsPerPel = 32; + dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + + if ( parms.displayHz != 0 ) { + dm.dmDisplayFrequency = parms.displayHz; + dm.dmFields |= DM_DISPLAYFREQUENCY; + } + + common->Printf( "...calling CDS: " ); + + // try setting the exact mode requested, because some drivers don't report + // the low res modes in EnumDisplaySettings, but still work + if ( ( cdsRet = ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) ) == DISP_CHANGE_SUCCESSFUL ) { + common->Printf( "ok\n" ); + win32.cdsFullscreen = true; + return true; + } + + // + // the exact mode failed, so scan EnumDisplaySettings for the next largest mode + // + common->Printf( "^3failed^0, " ); + + PrintCDSError( cdsRet ); + + common->Printf( "...trying next higher resolution:" ); + + // we could do a better matching job here... + for ( modeNum = 0 ; ; modeNum++ ) { + if ( !EnumDisplaySettings( NULL, modeNum, &devmode ) ) { + break; + } + if ( (int)devmode.dmPelsWidth >= parms.width + && (int)devmode.dmPelsHeight >= parms.height + && devmode.dmBitsPerPel == 32 ) { + + if ( ( cdsRet = ChangeDisplaySettings( &devmode, CDS_FULLSCREEN ) ) == DISP_CHANGE_SUCCESSFUL ) { + common->Printf( "ok\n" ); + win32.cdsFullscreen = true; + + return true; + } + break; + } + } + + common->Printf( "\n...^3no high res mode found^0\n" ); + return false; +} + + + +/* +=================== +GLimp_Init + +This is the platform specific OpenGL initialization function. It +is responsible for loading OpenGL, initializing it, +creating a window of the appropriate size, doing +fullscreen manipulations, etc. Its overall responsibility is +to make sure that a functional OpenGL subsystem is operating +when it returns to the ref. + +If there is any failure, the renderer will revert back to safe +parameters and try again. +=================== +*/ +bool GLimp_Init( glimpParms_t parms ) { + const char *driverName; + HDC hDC; + + common->Printf( "Initializing OpenGL subsystem\n" ); + + // check our desktop attributes + hDC = GetDC( GetDesktopWindow() ); + win32.desktopBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); + win32.desktopWidth = GetDeviceCaps( hDC, HORZRES ); + win32.desktopHeight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + // we can't run in a window unless it is 32 bpp + if ( win32.desktopBitsPixel < 32 && !parms.fullScreen ) { + common->Printf("^3Windowed mode requires 32 bit desktop depth^0\n"); + return false; + } + + // save the hardware gamma so it can be + // restored on exit + GLimp_SaveGamma(); + + // create our window classes if we haven't already + GLW_CreateWindowClasses(); + + // this will load the dll and set all our qgl* function pointers, + // but doesn't create a window + + // r_glDriver is only intended for using instrumented OpenGL + // dlls. Normal users should never have to use it, and it is + // not archived. + driverName = r_glDriver.GetString()[0] ? r_glDriver.GetString() : "opengl32"; + if ( !QGL_Init( driverName ) ) { + common->Printf( "^3GLimp_Init() could not load r_glDriver \"%s\"^0\n", driverName ); + return false; + } + + // getting the wgl extensions involves creating a fake window to get a context, + // which is pretty disgusting, and seems to mess with the AGP VAR allocation + GLW_GetWGLExtensionsWithFakeWindow(); + + // try to change to fullscreen + if ( parms.fullScreen ) { + if ( !GLW_SetFullScreen( parms ) ) { + GLimp_Shutdown(); + return false; + } + } + + // try to create a window with the correct pixel format + // and init the renderer context + if ( !GLW_CreateWindow( parms ) ) { + GLimp_Shutdown(); + return false; + } + + // wglSwapinterval, etc + GLW_CheckWGLExtensions( win32.hDC ); + + // check logging + GLimp_EnableLogging( ( r_logFile.GetInteger() != 0 ) ); + + return true; +} + + +/* +=================== +GLimp_SetScreenParms + +Sets up the screen based on passed parms.. +=================== +*/ +bool GLimp_SetScreenParms( glimpParms_t parms ) { + int exstyle; + int stylebits; + int x, y, w, h; + DEVMODE dm; + + memset( &dm, 0, sizeof( dm ) ); + dm.dmSize = sizeof( dm ); + dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; + if ( parms.displayHz != 0 ) { + dm.dmDisplayFrequency = parms.displayHz; + dm.dmFields |= DM_DISPLAYFREQUENCY; + } + + win32.cdsFullscreen = parms.fullScreen; + glConfig.isFullscreen = parms.fullScreen; + + if ( parms.fullScreen ) { + exstyle = WS_EX_TOPMOST; + stylebits = WS_POPUP|WS_VISIBLE|WS_SYSMENU; + SetWindowLong( win32.hWnd, GWL_STYLE, stylebits ); + SetWindowLong( win32.hWnd, GWL_EXSTYLE, exstyle ); + dm.dmPelsWidth = parms.width; + dm.dmPelsHeight = parms.height; + dm.dmBitsPerPel = 32; + x = y = w = h = 0; + } else { + RECT r; + + // adjust width and height for window border + r.bottom = parms.height; + r.left = 0; + r.top = 0; + r.right = parms.width; + + w = r.right - r.left; + h = r.bottom - r.top; + + x = win32.win_xpos.GetInteger(); + y = win32.win_ypos.GetInteger(); + + // adjust window coordinates if necessary + // so that the window is completely on screen + if ( x + w > win32.desktopWidth ) { + x = ( win32.desktopWidth - w ); + } + if ( y + h > win32.desktopHeight ) { + y = ( win32.desktopHeight - h ); + } + if ( x < 0 ) { + x = 0; + } + if ( y < 0 ) { + y = 0; + } + dm.dmPelsWidth = win32.desktopWidth; + dm.dmPelsHeight = win32.desktopHeight; + dm.dmBitsPerPel = win32.desktopBitsPixel; + exstyle = 0; + stylebits = WINDOW_STYLE|WS_SYSMENU; + AdjustWindowRect (&r, stylebits, FALSE); + SetWindowLong( win32.hWnd, GWL_STYLE, stylebits ); + SetWindowLong( win32.hWnd, GWL_EXSTYLE, exstyle ); + common->Printf( "%i %i %i %i\n", x, y, w, h ); + } + bool ret = ( ChangeDisplaySettings( &dm, parms.fullScreen ? CDS_FULLSCREEN : 0 ) == DISP_CHANGE_SUCCESSFUL ); + SetWindowPos( win32.hWnd, parms.fullScreen ? HWND_TOPMOST : HWND_NOTOPMOST, x, y, w, h, parms.fullScreen ? SWP_NOSIZE | SWP_NOMOVE : SWP_SHOWWINDOW ); + return ret; +} + +/* +=================== +GLimp_Shutdown + +This routine does all OS specific shutdown procedures for the OpenGL +subsystem. +=================== +*/ +void GLimp_Shutdown( void ) { + const char *success[] = { "failed", "success" }; + int retVal; + + common->Printf( "Shutting down OpenGL subsystem\n" ); + + // set current context to NULL + if ( qwglMakeCurrent ) { + retVal = qwglMakeCurrent( NULL, NULL ) != 0; + common->Printf( "...wglMakeCurrent( NULL, NULL ): %s\n", success[retVal] ); + } + + // delete HGLRC + if ( win32.hGLRC ) { + retVal = qwglDeleteContext( win32.hGLRC ) != 0; + common->Printf( "...deleting GL context: %s\n", success[retVal] ); + win32.hGLRC = NULL; + } + + // release DC + if ( win32.hDC ) { + retVal = ReleaseDC( win32.hWnd, win32.hDC ) != 0; + common->Printf( "...releasing DC: %s\n", success[retVal] ); + win32.hDC = NULL; + } + + // destroy window + if ( win32.hWnd ) { + common->Printf( "...destroying window\n" ); + ShowWindow( win32.hWnd, SW_HIDE ); + DestroyWindow( win32.hWnd ); + win32.hWnd = NULL; + } + + // reset display settings + if ( win32.cdsFullscreen ) { + common->Printf( "...resetting display\n" ); + ChangeDisplaySettings( 0, 0 ); + win32.cdsFullscreen = false; + } + + // close the thread so the handle doesn't dangle + if ( win32.renderThreadHandle ) { + common->Printf( "...closing smp thread\n" ); + CloseHandle( win32.renderThreadHandle ); + win32.renderThreadHandle = NULL; + } + + // restore gamma + GLimp_RestoreGamma(); + + // shutdown QGL subsystem + QGL_Shutdown(); +} + + +/* +===================== +GLimp_SwapBuffers +===================== +*/ +void GLimp_SwapBuffers( void ) { + // + // wglSwapinterval is a windows-private extension, + // so we must check for it here instead of portably + // + if ( r_swapInterval.IsModified() ) { + r_swapInterval.ClearModified(); + + if ( wglSwapIntervalEXT ) { + wglSwapIntervalEXT( r_swapInterval.GetInteger() ); + } + } + + qwglSwapBuffers( win32.hDC ); + +//Sys_DebugPrintf( "*** SwapBuffers() ***\n" ); +} + +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +//#define REALLOC_DC + +/* +=================== +GLimp_ActivateContext + +=================== +*/ +void GLimp_ActivateContext( void ) { + if ( !qwglMakeCurrent( win32.hDC, win32.hGLRC ) ) { + win32.wglErrors++; + } +} + +/* +=================== +GLimp_DeactivateContext + +=================== +*/ +void GLimp_DeactivateContext( void ) { + qglFinish(); + if ( !qwglMakeCurrent( win32.hDC, NULL ) ) { + win32.wglErrors++; + } +#ifdef REALLOC_DC + // makeCurrent NULL frees the DC, so get another + if ( ( win32.hDC = GetDC( win32.hWnd ) ) == NULL ) { + win32.wglErrors++; + } +#endif + +} + +/* +=================== +GLimp_RenderThreadWrapper + +=================== +*/ +static void GLimp_RenderThreadWrapper( void ) { + win32.glimpRenderThread(); + + // unbind the context before we die + qwglMakeCurrent( win32.hDC, NULL ); +} + +/* +======================= +GLimp_SpawnRenderThread + +Returns false if the system only has a single processor +======================= +*/ +bool GLimp_SpawnRenderThread( void (*function)( void ) ) { + SYSTEM_INFO info; + + // check number of processors + GetSystemInfo( &info ); + if ( info.dwNumberOfProcessors < 2 ) { + return false; + } + + // create the IPC elements + win32.renderCommandsEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + win32.renderCompletedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + win32.renderActiveEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + + win32.glimpRenderThread = function; + + win32.renderThreadHandle = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)GLimp_RenderThreadWrapper, // LPTHREAD_START_ROUTINE lpStartAddr, + 0, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &win32.renderThreadId ); + + if ( !win32.renderThreadHandle ) { + common->Error( "GLimp_SpawnRenderThread: failed" ); + } + + SetThreadPriority( win32.renderThreadHandle, THREAD_PRIORITY_ABOVE_NORMAL ); +#if 0 + // make sure they always run on different processors + SetThreadAffinityMask( GetCurrentThread, 1 ); + SetThreadAffinityMask( win32.renderThreadHandle, 2 ); +#endif + + return true; +} + + +//#define DEBUG_PRINTS + +/* +=================== +GLimp_BackEndSleep + +=================== +*/ +void *GLimp_BackEndSleep( void ) { + void *data; + +#ifdef DEBUG_PRINTS +OutputDebugString( "-->GLimp_BackEndSleep\n" ); +#endif + ResetEvent( win32.renderActiveEvent ); + + // after this, the front end can exit GLimp_FrontEndSleep + SetEvent( win32.renderCompletedEvent ); + + WaitForSingleObject( win32.renderCommandsEvent, INFINITE ); + + ResetEvent( win32.renderCompletedEvent ); + ResetEvent( win32.renderCommandsEvent ); + + data = win32.smpData; + + // after this, the main thread can exit GLimp_WakeRenderer + SetEvent( win32.renderActiveEvent ); + +#ifdef DEBUG_PRINTS +OutputDebugString( "<--GLimp_BackEndSleep\n" ); +#endif + return data; +} + +/* +=================== +GLimp_FrontEndSleep + +=================== +*/ +void GLimp_FrontEndSleep( void ) { +#ifdef DEBUG_PRINTS +OutputDebugString( "-->GLimp_FrontEndSleep\n" ); +#endif + WaitForSingleObject( win32.renderCompletedEvent, INFINITE ); + +#ifdef DEBUG_PRINTS +OutputDebugString( "<--GLimp_FrontEndSleep\n" ); +#endif +} + +volatile bool renderThreadActive; + +/* +=================== +GLimp_WakeBackEnd + +=================== +*/ +void GLimp_WakeBackEnd( void *data ) { + int r; + +#ifdef DEBUG_PRINTS +OutputDebugString( "-->GLimp_WakeBackEnd\n" ); +#endif + win32.smpData = data; + + if ( renderThreadActive ) { + common->FatalError( "GLimp_WakeBackEnd: already active" ); + } + + r = WaitForSingleObject( win32.renderActiveEvent, 0 ); + if ( r == WAIT_OBJECT_0 ) { + common->FatalError( "GLimp_WakeBackEnd: already signaled" ); + } + + r = WaitForSingleObject( win32.renderCommandsEvent, 0 ); + if ( r == WAIT_OBJECT_0 ) { + common->FatalError( "GLimp_WakeBackEnd: commands already signaled" ); + } + + // after this, the renderer can continue through GLimp_RendererSleep + SetEvent( win32.renderCommandsEvent ); + + r = WaitForSingleObject( win32.renderActiveEvent, 5000 ); + + if ( r == WAIT_TIMEOUT ) { + common->FatalError( "GLimp_WakeBackEnd: WAIT_TIMEOUT" ); + } + +#ifdef DEBUG_PRINTS +OutputDebugString( "<--GLimp_WakeBackEnd\n" ); +#endif +} + +//=================================================================== + +/* +=================== +GLimp_ExtensionPointer + +Returns a function pointer for an OpenGL extension entry point +=================== +*/ +GLExtension_t GLimp_ExtensionPointer( const char *name ) { + void (*proc)(void); + + proc = (GLExtension_t)qwglGetProcAddress( name ); + + if ( !proc ) { + common->Printf( "Couldn't find proc address for: %s\n", name ); + } + + return proc; +} + diff --git a/src/sys/win32/win_input.cpp b/src/sys/win32/win_input.cpp new file mode 100644 index 0000000..53f85fd --- /dev/null +++ b/src/sys/win32/win_input.cpp @@ -0,0 +1,1066 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "win_local.h" + + +#define DINPUT_BUFFERSIZE 256 + +#define CHAR_FIRSTREPEAT 200 +#define CHAR_REPEAT 100 + +typedef struct MYDATA { + LONG lX; // X axis goes here + LONG lY; // Y axis goes here + LONG lZ; // Z axis goes here + BYTE bButtonA; // One button goes here + BYTE bButtonB; // Another button goes here + BYTE bButtonC; // Another button goes here + BYTE bButtonD; // Another button goes here +} MYDATA; + +static DIOBJECTDATAFORMAT rgodf[] = { + { &GUID_XAxis, FIELD_OFFSET(MYDATA, lX), DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { &GUID_YAxis, FIELD_OFFSET(MYDATA, lY), DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { &GUID_ZAxis, FIELD_OFFSET(MYDATA, lZ), 0x80000000 | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonA), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonB), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonC), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonD), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, +}; + +//========================================================================== + +static const unsigned char s_scantokey[256] = { +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '`', K_SHIFT, '\\', 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '/', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, 0, K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 7 +// shifted + 0, 27, '!', '@', '#', '$', '%', '^', + '&', '*', '(', ')', '_', '+', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '~', K_SHIFT, '\\', 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '/', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, 0, K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 +}; + +static const unsigned char s_scantokey_german[256] = { +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '?', '\'', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'z', 'u', 'i', + 'o', 'p', '=', '+', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', '[', + ']', '`', K_SHIFT, '#', 'y', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '-', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 7 +// shifted + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '?', '\'', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'z', 'u', 'i', + 'o', 'p', '=', '+', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', '[', + ']', '`', K_SHIFT, '#', 'y', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '-', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 +}; + +static const unsigned char s_scantokey_french[256] = { +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', ')', '=', K_BACKSPACE, 9, // 0 + 'a', 'z', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '^', '$', K_ENTER,K_CTRL, 'q', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', + 'ù', '`', K_SHIFT, '*', 'w', 'x', 'c', 'v', // 2 + 'b', 'n', ',', ';', ':', '!', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 7 +// shifted + 0, 27, '&', 'é', '\"', '\'', '(', '-', + 'è', '_', 'ç', 'à', '°', '+', K_BACKSPACE, 9, // 0 + 'a', 'z', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '^', '$', K_ENTER,K_CTRL, 'q', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', + 'ù', 0, K_SHIFT, '*', 'w', 'x', 'c', 'v', // 2 + 'b', 'n', ',', ';', ':', '!', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 +}; + +static const unsigned char s_scantokey_spanish[256] = { +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '\'', '¡', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '`', '+', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ñ', + '´', 'º', K_SHIFT, 'ç', 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '-', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 7 +// shifted + 0, 27, '!', '\"', '·', '$', '%', '&', + '/', '(', ')', '=', '?', '¿', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '^', '*', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'Ñ', + '¨', 'ª', K_SHIFT, 'Ç', 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '-', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 +}; + +static const unsigned char s_scantokey_italian[256] = { +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '\'', 'ì', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', 'è', '+', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ò', + 'à', '\\', K_SHIFT, 'ù', 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '-', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 7 +// shifted + 0, 27, '!', '\"', '£', '$', '%', '&', + '/', '(', ')', '=', '?', '^', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', 'é', '*', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ç', + '°', '|', K_SHIFT, '§', 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '-', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, '<', K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 + + +}; + +static const unsigned char *keyScanTable = s_scantokey; + +// this should be part of the scantables and the scan tables should be 512 bytes +// (256 scan codes, shifted and unshifted). Changing everything to use 512 byte +// scan tables now might introduce bugs in tested code. Since we only need to fix +// the right-alt case for non-US keyboards, we're just using a special-case table +// for it. Eventually, the tables above should be fixed to handle all possible +// scan codes instead of just the first 128. +static unsigned char rightAltKey = K_ALT; + +#define NUM_OBJECTS (sizeof(rgodf) / sizeof(rgodf[0])) + +static DIDATAFORMAT df = { + sizeof(DIDATAFORMAT), // this structure + sizeof(DIOBJECTDATAFORMAT), // size of object data format + DIDF_RELAXIS, // absolute axis coordinates + sizeof(MYDATA), // device data size + NUM_OBJECTS, // number of objects + rgodf, // and here they are +}; + +/* +============================================================ + +DIRECT INPUT KEYBOARD CONTROL + +============================================================ +*/ + +bool IN_StartupKeyboard( void ) { + HRESULT hr; + bool bExclusive; + bool bForeground; + bool bImmediate; + bool bDisableWindowsKey; + DWORD dwCoopFlags; + + if (!win32.g_pdi) { + common->Printf("keyboard: DirectInput has not been started\n"); + return false; + } + + if (win32.g_pKeyboard) { + win32.g_pKeyboard->Release(); + win32.g_pKeyboard = NULL; + } + + // Detrimine where the buffer would like to be allocated + bExclusive = false; + bForeground = true; + bImmediate = false; + bDisableWindowsKey = true; + + if( bExclusive ) + dwCoopFlags = DISCL_EXCLUSIVE; + else + dwCoopFlags = DISCL_NONEXCLUSIVE; + + if( bForeground ) + dwCoopFlags |= DISCL_FOREGROUND; + else + dwCoopFlags |= DISCL_BACKGROUND; + + // Disabling the windows key is only allowed only if we are in foreground nonexclusive + if( bDisableWindowsKey && !bExclusive && bForeground ) + dwCoopFlags |= DISCL_NOWINKEY; + + // Obtain an interface to the system keyboard device. + if( FAILED( hr = win32.g_pdi->CreateDevice( GUID_SysKeyboard, &win32.g_pKeyboard, NULL ) ) ) { + common->Printf("keyboard: couldn't find a keyboard device\n"); + return false; + } + + // Set the data format to "keyboard format" - a predefined data format + // + // A data format specifies which controls on a device we + // are interested in, and how they should be reported. + // + // This tells DirectInput that we will be passing an array + // of 256 bytes to IDirectInputDevice::GetDeviceState. + if( FAILED( hr = win32.g_pKeyboard->SetDataFormat( &c_dfDIKeyboard ) ) ) + return false; + + // Set the cooperativity level to let DirectInput know how + // this device should interact with the system and with other + // DirectInput applications. + hr = win32.g_pKeyboard->SetCooperativeLevel( win32.hWnd, dwCoopFlags ); + if( hr == DIERR_UNSUPPORTED && !bForeground && bExclusive ) { + common->Printf("keyboard: SetCooperativeLevel() returned DIERR_UNSUPPORTED.\nFor security reasons, background exclusive keyboard access is not allowed.\n"); + return false; + } + + if( FAILED(hr) ) { + return false; + } + + if( !bImmediate ) { + // IMPORTANT STEP TO USE BUFFERED DEVICE DATA! + // + // DirectInput uses unbuffered I/O (buffer size = 0) by default. + // If you want to read buffered data, you need to set a nonzero + // buffer size. + // + // Set the buffer size to DINPUT_BUFFERSIZE (defined above) elements. + // + // The buffer size is a DWORD property associated with the device. + DIPROPDWORD dipdw; + + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DINPUT_BUFFERSIZE; // Arbitary buffer size + + if( FAILED( hr = win32.g_pKeyboard->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph ) ) ) + return false; + } + + // Acquire the newly created device + win32.g_pKeyboard->Acquire(); + + common->Printf( "keyboard: DirectInput initialized.\n"); + return true; +} + +/* +======= +MapKey + +Map from windows to quake keynums + +FIXME: scan code tables should include the upper 128 scan codes instead + of having to special-case them here. The current code makes it difficult + to special-case conversions for non-US keyboards. Currently the only + special-case is for right alt. +======= +*/ +int IN_DIMapKey (int key) { + if ( key>=128 ) { + switch ( key ) { + case DIK_HOME: + return K_HOME; + case DIK_UPARROW: + return K_UPARROW; + case DIK_PGUP: + return K_PGUP; + case DIK_LEFTARROW: + return K_LEFTARROW; + case DIK_RIGHTARROW: + return K_RIGHTARROW; + case DIK_END: + return K_END; + case DIK_DOWNARROW: + return K_DOWNARROW; + case DIK_PGDN: + return K_PGDN; + case DIK_INSERT: + return K_INS; + case DIK_DELETE: + return K_DEL; + case DIK_RMENU: + return rightAltKey; + case DIK_RCONTROL: + return K_CTRL; + case DIK_NUMPADENTER: + return K_KP_ENTER; + case DIK_NUMPADEQUALS: + return K_KP_EQUALS; + case DIK_PAUSE: + return K_PAUSE; + case DIK_DIVIDE: + return K_KP_SLASH; + case DIK_LWIN: + return K_LWIN; + case DIK_RWIN: + return K_RWIN; + case DIK_APPS: + return K_MENU; + case DIK_SYSRQ: + return K_PRINT_SCR; + default: + return 0; + } + } else { + switch (key) { + case DIK_NUMPAD7: + return K_KP_HOME; + case DIK_NUMPAD8: + return K_KP_UPARROW; + case DIK_NUMPAD9: + return K_KP_PGUP; + case DIK_NUMPAD4: + return K_KP_LEFTARROW; + case DIK_NUMPAD5: + return K_KP_5; + case DIK_NUMPAD6: + return K_KP_RIGHTARROW; + case DIK_NUMPAD1: + return K_KP_END; + case DIK_NUMPAD2: + return K_KP_DOWNARROW; + case DIK_NUMPAD3: + return K_KP_PGDN; + case DIK_NUMPAD0: + return K_KP_INS; + case DIK_DECIMAL: + return K_KP_DEL; + case DIK_SUBTRACT: + return K_KP_MINUS; + case DIK_ADD: + return K_KP_PLUS; + case DIK_NUMLOCK: + return K_KP_NUMLOCK; + case DIK_MULTIPLY: + return K_KP_STAR; + default: + return keyScanTable[key]; + } + } +} + + +/* +========================== +IN_DeactivateKeyboard +========================== +*/ +void IN_DeactivateKeyboard( void ) { + if (!win32.g_pKeyboard) { + return; + } + win32.g_pKeyboard->Unacquire( ); +} + +/* +============================================================ + +DIRECT INPUT MOUSE CONTROL + +============================================================ +*/ + +/* +======================== +IN_InitDirectInput +======================== +*/ + +void IN_InitDirectInput( void ) { + HRESULT hr; + + common->Printf( "Initializing DirectInput...\n" ); + + if ( win32.g_pdi != NULL ) { + win32.g_pdi->Release(); // if the previous window was destroyed we need to do this + win32.g_pdi = NULL; + } + + // Register with the DirectInput subsystem and get a pointer + // to a IDirectInput interface we can use. + // Create the base DirectInput object + if ( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&win32.g_pdi, NULL ) ) ) { + common->Printf ("DirectInputCreate failed\n"); + } +} + +/* +======================== +IN_InitDIMouse +======================== +*/ +bool IN_InitDIMouse( void ) { + HRESULT hr; + + if ( win32.g_pdi == NULL) { + return false; + } + + // obtain an interface to the system mouse device. + hr = win32.g_pdi->CreateDevice( GUID_SysMouse, &win32.g_pMouse, NULL); + + if (FAILED(hr)) { + common->Printf ("mouse: Couldn't open DI mouse device\n"); + return false; + } + + // Set the data format to "mouse format" - a predefined data format + // + // A data format specifies which controls on a device we + // are interested in, and how they should be reported. + // + // This tells DirectInput that we will be passing a + // DIMOUSESTATE2 structure to IDirectInputDevice::GetDeviceState. + if( FAILED( hr = win32.g_pMouse->SetDataFormat( &c_dfDIMouse2 ) ) ) { + common->Printf ("mouse: Couldn't set DI mouse format\n"); + return false; + } + + // set the cooperativity level. + hr = win32.g_pMouse->SetCooperativeLevel( win32.hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); + + if (FAILED(hr)) { + common->Printf ("mouse: Couldn't set DI coop level\n"); + return false; + } + + + // IMPORTANT STEP TO USE BUFFERED DEVICE DATA! + // + // DirectInput uses unbuffered I/O (buffer size = 0) by default. + // If you want to read buffered data, you need to set a nonzero + // buffer size. + // + // Set the buffer size to SAMPLE_BUFFER_SIZE (defined above) elements. + // + // The buffer size is a DWORD property associated with the device. + DIPROPDWORD dipdw; + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = DINPUT_BUFFERSIZE; // Arbitary buffer size + + if( FAILED( hr = win32.g_pMouse->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph ) ) ) { + common->Printf ("mouse: Couldn't set DI buffersize\n"); + return false; + } + + IN_ActivateMouse(); + + // clear any pending samples + Sys_PollMouseInputEvents(); + + common->Printf( "mouse: DirectInput initialized.\n"); + return true; +} + + +/* +========================== +IN_ActivateMouse +========================== +*/ +void IN_ActivateMouse( void ) { + int i; + HRESULT hr; + + if ( !win32.in_mouse.GetBool() || win32.mouseGrabbed || !win32.g_pMouse ) { + return; + } + + win32.mouseGrabbed = true; + for ( i = 0; i < 10; i++ ) { + if ( ::ShowCursor( false ) < 0 ) { + break; + } + } + + // we may fail to reacquire if the window has been recreated + hr = win32.g_pMouse->Acquire(); + if (FAILED(hr)) { + return; + } + + // set the cooperativity level. + hr = win32.g_pMouse->SetCooperativeLevel( win32.hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); +} + +/* +========================== +IN_DeactivateMouse +========================== +*/ +void IN_DeactivateMouse( void ) { + int i; + + if (!win32.g_pMouse || !win32.mouseGrabbed ) { + return; + } + + win32.g_pMouse->Unacquire(); + + for ( i = 0; i < 10; i++ ) { + if ( ::ShowCursor( true ) >= 0 ) { + break; + } + } + win32.mouseGrabbed = false; +} + +/* +========================== +IN_DeactivateMouseIfWindowed +========================== +*/ +void IN_DeactivateMouseIfWindowed( void ) { + if ( !win32.cdsFullscreen ) { + IN_DeactivateMouse(); + } +} + +/* +============================================================ + + MOUSE CONTROL + +============================================================ +*/ + + +/* +=========== +Sys_ShutdownInput +=========== +*/ +void Sys_ShutdownInput( void ) { + IN_DeactivateMouse(); + IN_DeactivateKeyboard(); + if ( win32.g_pKeyboard ) { + win32.g_pKeyboard->Release(); + win32.g_pKeyboard = NULL; + } + + if ( win32.g_pMouse ) { + win32.g_pMouse->Release(); + win32.g_pMouse = NULL; + } + + if ( win32.g_pdi ) { + win32.g_pdi->Release(); + win32.g_pdi = NULL; + } +} + +/* +=========== +Sys_InitInput +=========== +*/ +void Sys_InitInput( void ) { + common->Printf ("\n------- Input Initialization -------\n"); + IN_InitDirectInput(); + if ( win32.in_mouse.GetBool() ) { + IN_InitDIMouse(); + // don't grab the mouse on initialization + Sys_GrabMouseCursor( false ); + } else { + common->Printf ("Mouse control not active.\n"); + } + IN_StartupKeyboard(); + common->Printf ("------------------------------------\n"); + win32.in_mouse.ClearModified(); +} + +/* +=========== +Sys_InitScanTable +=========== +*/ +void Sys_InitScanTable( void ) { + idStr lang = cvarSystem->GetCVarString( "sys_lang" ); + if ( lang.Length() == 0 ) { + lang = "english"; + } + if ( lang.Icmp( "english" ) == 0 ) { + keyScanTable = s_scantokey; + // the only reason that english right alt binds as K_ALT is so that + // users who were using right-alt before the patch don't suddenly find + // that only left-alt is working. + rightAltKey = K_ALT; + } else if ( lang.Icmp( "spanish" ) == 0 ) { + keyScanTable = s_scantokey_spanish; + rightAltKey = K_RIGHT_ALT; + } else if ( lang.Icmp( "french" ) == 0 ) { + keyScanTable = s_scantokey_french; + rightAltKey = K_RIGHT_ALT; + } else if ( lang.Icmp( "german" ) == 0 ) { + keyScanTable = s_scantokey_german; + rightAltKey = K_RIGHT_ALT; + } else if ( lang.Icmp( "italian" ) == 0 ) { + keyScanTable = s_scantokey_italian; + rightAltKey = K_RIGHT_ALT; + } +} + +/* +================== +Sys_GetScanTable +================== +*/ +const unsigned char *Sys_GetScanTable( void ) { + return keyScanTable; +} + +/* +=============== +Sys_GetConsoleKey +=============== +*/ +unsigned char Sys_GetConsoleKey( bool shifted ) { + return keyScanTable[41 + ( shifted ? 128 : 0 )]; +} + +/* +================== +IN_Frame + +Called every frame, even if not generating commands +================== +*/ +void IN_Frame( void ) { + bool shouldGrab = true; + + if ( !win32.in_mouse.GetBool() ) { + shouldGrab = false; + } + // if fullscreen, we always want the mouse + if ( !win32.cdsFullscreen ) { + if ( win32.mouseReleased ) { + shouldGrab = false; + } + if ( win32.movingWindow ) { + shouldGrab = false; + } + if ( !win32.activeApp ) { + shouldGrab = false; + } + } + + if ( shouldGrab != win32.mouseGrabbed ) { + if ( win32.mouseGrabbed ) { + IN_DeactivateMouse(); + } else { + IN_ActivateMouse(); + +#if 0 // if we can't reacquire, try reinitializing + if ( !IN_InitDIMouse() ) { + win32.in_mouse.SetBool( false ); + return; + } +#endif + } + } +} + + +void Sys_GrabMouseCursor( bool grabIt ) { +#ifndef ID_DEDICATED + win32.mouseReleased = !grabIt; + if ( !grabIt ) { + // release it right now + IN_Frame(); + } +#endif +} + +//===================================================================================== + +static DIDEVICEOBJECTDATA polled_didod[ DINPUT_BUFFERSIZE ]; // Receives buffered data + +static int diFetch; +static byte toggleFetch[2][ 256 ]; + + +#if 1 +// I tried doing the full-state get to address a keyboard problem on one system, +// but it didn't make any difference + +/* +==================== +Sys_PollKeyboardInputEvents +==================== +*/ +int Sys_PollKeyboardInputEvents( void ) { + DWORD dwElements; + HRESULT hr; + + if( win32.g_pKeyboard == NULL ) { + return 0; + } + + dwElements = DINPUT_BUFFERSIZE; + hr = win32.g_pKeyboard->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), + polled_didod, &dwElements, 0 ); + if( hr != DI_OK ) + { + // We got an error or we got DI_BUFFEROVERFLOW. + // + // Either way, it means that continuous contact with the + // device has been lost, either due to an external + // interruption, or because the buffer overflowed + // and some events were lost. + hr = win32.g_pKeyboard->Acquire(); + + + + // nuke the garbage + if (!FAILED(hr)) { + //Bug 951: The following command really clears the garbage input. + //The original will still process keys in the buffer and was causing + //some problems. + win32.g_pKeyboard->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), NULL, &dwElements, 0 ); + dwElements = 0; + } + // hr may be DIERR_OTHERAPPHASPRIO or other errors. This + // may occur when the app is minimized or in the process of + // switching, so just try again later + } + + if( FAILED(hr) ) { + return 0; + } + + return dwElements; +} + +#else + +/* +==================== +Sys_PollKeyboardInputEvents + +Fake events by getting the entire device state +and checking transitions +==================== +*/ +int Sys_PollKeyboardInputEvents( void ) { + HRESULT hr; + + if( win32.g_pKeyboard == NULL ) { + return 0; + } + + hr = win32.g_pKeyboard->GetDeviceState( sizeof( toggleFetch[ diFetch ] ), toggleFetch[ diFetch ] ); + if( hr != DI_OK ) + { + // We got an error or we got DI_BUFFEROVERFLOW. + // + // Either way, it means that continuous contact with the + // device has been lost, either due to an external + // interruption, or because the buffer overflowed + // and some events were lost. + hr = win32.g_pKeyboard->Acquire(); + + // nuke the garbage + if (!FAILED(hr)) { + hr = win32.g_pKeyboard->GetDeviceState( sizeof( toggleFetch[ diFetch ] ), toggleFetch[ diFetch ] ); + } + // hr may be DIERR_OTHERAPPHASPRIO or other errors. This + // may occur when the app is minimized or in the process of + // switching, so just try again later + } + + if( FAILED(hr) ) { + return 0; + } + + // build faked events + int numChanges = 0; + + for ( int i = 0 ; i < 256 ; i++ ) { + if ( toggleFetch[0][i] != toggleFetch[1][i] ) { + polled_didod[ numChanges ].dwOfs = i; + polled_didod[ numChanges ].dwData = toggleFetch[ diFetch ][i] ? 0x80 : 0; + numChanges++; + } + } + + diFetch ^= 1; + + return numChanges; +} + +#endif + +/* +==================== +Sys_PollKeyboardInputEvents +==================== +*/ +int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state ) { + ch = IN_DIMapKey( polled_didod[ n ].dwOfs ); + state = (polled_didod[ n ].dwData & 0x80) == 0x80; + if ( ch == K_PRINT_SCR || ch == K_CTRL || ch == K_ALT || ch == K_RIGHT_ALT ) { + // for windows, add a keydown event for print screen here, since + // windows doesn't send keydown events to the WndProc for this key. + // ctrl and alt are handled here to get around windows sending ctrl and + // alt messages when the right-alt is pressed on non-US 102 keyboards. + Sys_QueEvent( GetTickCount(), SE_KEY, ch, state, 0, NULL ); + } + return ch; +} + + +void Sys_EndKeyboardInputEvents( void ) { +} + +void Sys_QueMouseEvents( int dwElements ) { + int i, value; + + for( i = 0; i < dwElements; i++ ) { + if ( polled_didod[i].dwOfs >= DIMOFS_BUTTON0 && polled_didod[i].dwOfs <= DIMOFS_BUTTON7 ) { + value = (polled_didod[i].dwData & 0x80) == 0x80; + Sys_QueEvent( polled_didod[i].dwTimeStamp, SE_KEY, K_MOUSE1 + ( polled_didod[i].dwOfs - DIMOFS_BUTTON0 ), value, 0, NULL ); + } else { + switch (polled_didod[i].dwOfs) { + case DIMOFS_X: + value = polled_didod[i].dwData; + Sys_QueEvent( polled_didod[i].dwTimeStamp, SE_MOUSE, value, 0, 0, NULL ); + break; + case DIMOFS_Y: + value = polled_didod[i].dwData; + Sys_QueEvent( polled_didod[i].dwTimeStamp, SE_MOUSE, 0, value, 0, NULL ); + break; + case DIMOFS_Z: + value = ( (int) polled_didod[i].dwData ) / WHEEL_DELTA; + int key = value < 0 ? K_MWHEELDOWN : K_MWHEELUP; + value = abs( value ); + while( value-- > 0 ) { + Sys_QueEvent( polled_didod[i].dwTimeStamp, SE_KEY, key, true, 0, NULL ); + Sys_QueEvent( polled_didod[i].dwTimeStamp, SE_KEY, key, false, 0, NULL ); + } + break; + } + } + } +} + +//===================================================================================== + +int Sys_PollMouseInputEvents( void ) { + DWORD dwElements; + HRESULT hr; + + if ( !win32.g_pMouse || !win32.mouseGrabbed ) { + return 0; + } + + dwElements = DINPUT_BUFFERSIZE; + hr = win32.g_pMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), polled_didod, &dwElements, 0 ); + + if( hr != DI_OK ) { + hr = win32.g_pMouse->Acquire(); + // clear the garbage + if (!FAILED(hr)) { + win32.g_pMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), polled_didod, &dwElements, 0 ); + } + } + + if( FAILED(hr) ) { + return 0; + } + + Sys_QueMouseEvents( dwElements ); + + return dwElements; +} + +int Sys_ReturnMouseInputEvent( const int n, int &action, int &value ) { + int diaction = polled_didod[n].dwOfs; + + if ( diaction >= DIMOFS_BUTTON0 && diaction <= DIMOFS_BUTTON7 ) { + value = (polled_didod[n].dwData & 0x80) == 0x80; + action = M_ACTION1 + ( diaction - DIMOFS_BUTTON0 ); + return 1; + } + + switch( diaction ) { + case DIMOFS_X: + value = polled_didod[n].dwData; + action = M_DELTAX; + return 1; + case DIMOFS_Y: + value = polled_didod[n].dwData; + action = M_DELTAY; + return 1; + case DIMOFS_Z: + // mouse wheel actions are impulses, without a specific up / down + value = ( (int) polled_didod[n].dwData ) / WHEEL_DELTA; + action = M_DELTAZ; + // a value of zero here should never happen + if ( value == 0 ) { + return 0; + } + return 1; + } + return 0; +} + +void Sys_EndMouseInputEvents( void ) { } + +/* +==================== +Sys_PollJoystickInputEvents + +The retail implementation delegates to the separately owned rvInput XInput +backend. That backend is intentionally absent while auxiliary/tool input is +disabled, so expose the exact win_input.obj API as an inert device. +==================== +*/ +int Sys_PollJoystickInputEvents( void ) { + return 0; +} + +int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ) { + (void)n; + action = 0; + value = 0; + return 0; +} + +void Sys_EndJoystickInputEvents( void ) { +} + +unsigned char Sys_MapCharForKey( int key ) { + return (unsigned char)key; +} diff --git a/src/sys/win32/win_local.h b/src/sys/win32/win_local.h new file mode 100644 index 0000000..2b11b94 --- /dev/null +++ b/src/sys/win32/win_local.h @@ -0,0 +1,182 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __WIN_LOCAL_H__ +#define __WIN_LOCAL_H__ + +#include +#ifndef DIRECTINPUT_VERSION +#define DIRECTINPUT_VERSION 0x0800 +#endif +#include +#include "../../renderer/wglext.h" // windows OpenGL extensions + +// WGL_ARB_extensions_string +extern PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB; + +// WGL_EXT_swap_interval +extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT; + +// WGL_ARB_pixel_format +extern PFNWGLGETPIXELFORMATATTRIBIVARBPROC wglGetPixelFormatAttribivARB; +extern PFNWGLGETPIXELFORMATATTRIBFVARBPROC wglGetPixelFormatAttribfvARB; +extern PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; + +// WGL_ARB_pbuffer +extern PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB; +extern PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB; +extern PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB; +extern PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB; +extern PFNWGLQUERYPBUFFERARBPROC wglQueryPbufferARB; + +// WGL_ARB_render_texture +extern PFNWGLBINDTEXIMAGEARBPROC wglBindTexImageARB; +extern PFNWGLRELEASETEXIMAGEARBPROC wglReleaseTexImageARB; +extern PFNWGLSETPBUFFERATTRIBARBPROC wglSetPbufferAttribARB; + + +#define MAX_OSPATH 256 + +#define WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_CAPTION|WS_VISIBLE | WS_THICKFRAME) + +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); + +void Sys_CreateConsole( void ); +void Sys_DestroyConsole( void ); +void Sys_ShowConsole( int visLevel, bool quitOnClose ); +void Sys_ShowWindow( bool show ); +void Sys_SetFatalError( const char *error ); +void Sys_DoPreferences( void ); + +void Sys_InitScanTable( void ); +const unsigned char *Sys_GetScanTable( void ); + +char *Sys_ConsoleInput (void); +char *Sys_GetCurrentUser( void ); + +void Win_SetErrorText( const char *text ); + +cpuid_t Sys_GetCPUId( void ); + +int MapKey (int key); + + +// Input subsystem + +void IN_Init (void); +void IN_Shutdown (void); +// add additional non keyboard / non mouse movement on top of the keyboard move cmd + +void IN_DeactivateMouseIfWindowed( void ); +void IN_DeactivateMouse( void ); +void IN_ActivateMouse( void ); + +void IN_Frame( void ); + +int IN_DIMapKey( int key ); + +void DisableTaskKeys( BOOL bDisable, BOOL bBeep, BOOL bTaskMgr ); + + +// window procedure +LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +void Conbuf_AppendText( const char *msg ); + +typedef struct { + HWND hWnd; + HINSTANCE hInstance; + + bool activeApp; // changed with WM_ACTIVATE messages + bool mouseReleased; // when the game has the console down or is doing a long operation + bool movingWindow; // inhibit mouse grab when dragging the window + bool mouseGrabbed; // current state of grab and hide + + OSVERSIONINFOEX osversion; + + cpuid_t cpuid; + + // when we get a windows message, we store the time off so keyboard processing + // can know the exact time of an event (not really needed now that we use async direct input) + int sysMsgTime; + + bool windowClassRegistered; + + WNDPROC wndproc; + + HDC hDC; // handle to device context + HGLRC hGLRC; // handle to GL rendering context + PIXELFORMATDESCRIPTOR pfd; + int pixelformat; + + HINSTANCE hinstOpenGL; // HINSTANCE for the OpenGL library + + int desktopBitsPixel; + int desktopWidth, desktopHeight; + + bool cdsFullscreen; + + FILE *log_fp; + + unsigned short oldHardwareGamma[3][256]; + // desktop gamma is saved here for restoration at exit + + static idCVar sys_arch; + static idCVar sys_cpustring; + static idCVar in_mouse; + static idCVar win_allowAltTab; + static idCVar win_notaskkeys; + static idCVar win_username; + static idCVar win_xpos; // archived X coordinate of window position + static idCVar win_ypos; // archived Y coordinate of window position + static idCVar win_outputDebugString; + static idCVar win_outputEditString; + static idCVar win_viewlog; + static idCVar win_timerUpdate; + static idCVar win_allowMultipleInstances; + + CRITICAL_SECTION criticalSections[MAX_CRITICAL_SECTIONS]; + HANDLE backgroundDownloadSemaphore; + + HINSTANCE hInstDI; // direct input + + LPDIRECTINPUT8 g_pdi; + LPDIRECTINPUTDEVICE8 g_pMouse; + LPDIRECTINPUTDEVICE8 g_pKeyboard; + + HANDLE renderCommandsEvent; + HANDLE renderCompletedEvent; + HANDLE renderActiveEvent; + HANDLE renderThreadHandle; + unsigned long renderThreadId; + void (*glimpRenderThread)( void ); + void *smpData; + int wglErrors; + // SMP acceleration vars + +} Win32Vars_t; + +extern Win32Vars_t win32; + +#endif /* !__WIN_LOCAL_H__ */ diff --git a/src/sys/win32/win_main.cpp b/src/sys/win32/win_main.cpp new file mode 100644 index 0000000..7577bb3 --- /dev/null +++ b/src/sys/win32/win_main.cpp @@ -0,0 +1,1631 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __MRC__ +#include +#include +#endif + +#include "../sys_local.h" +#include "win_local.h" +#include "rc/CreateResourceIDs.h" +#include "../../renderer/tr_local.h" + +idCVar Win32Vars_t::sys_arch( "sys_arch", "", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar Win32Vars_t::sys_cpustring( "sys_cpustring", "detect", CVAR_SYSTEM | CVAR_INIT, "" ); +idCVar Win32Vars_t::in_mouse( "in_mouse", "1", CVAR_SYSTEM | CVAR_BOOL, "enable mouse input" ); +idCVar Win32Vars_t::win_allowAltTab( "win_allowAltTab", "0", CVAR_SYSTEM | CVAR_BOOL, "allow Alt-Tab when fullscreen" ); +idCVar Win32Vars_t::win_notaskkeys( "win_notaskkeys", "0", CVAR_SYSTEM | CVAR_INTEGER, "disable windows task keys" ); +idCVar Win32Vars_t::win_username( "win_username", "", CVAR_SYSTEM | CVAR_INIT, "windows user name" ); +idCVar Win32Vars_t::win_xpos( "win_xpos", "3", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "horizontal position of window" ); +idCVar Win32Vars_t::win_ypos( "win_ypos", "22", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "vertical position of window" ); +idCVar Win32Vars_t::win_outputDebugString( "win_outputDebugString", "0", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar Win32Vars_t::win_outputEditString( "win_outputEditString", "1", CVAR_SYSTEM | CVAR_BOOL, "" ); +idCVar Win32Vars_t::win_viewlog( "win_viewlog", "0", CVAR_SYSTEM | CVAR_INTEGER, "" ); +idCVar Win32Vars_t::win_timerUpdate( "win_timerUpdate", "0", CVAR_SYSTEM | CVAR_BOOL, "allows the game to be updated while dragging the window" ); +idCVar Win32Vars_t::win_allowMultipleInstances( "win_allowMultipleInstances", "0", CVAR_SYSTEM | CVAR_BOOL, "allow multiple instances running concurrently" ); + +Win32Vars_t win32; + +static char sys_cmdline[MAX_STRING_CHARS]; + +// not a hard limit, just what we keep track of for debugging +xthreadInfo *g_threads[MAX_THREADS]; + +int g_thread_count = 0; + +static sysMemoryStats_t exeLaunchMemoryStats; + +static xthreadInfo threadInfo; +static HANDLE hTimer; + +/* +================ +Sys_GetExeLaunchMemoryStatus +================ +*/ +void Sys_GetExeLaunchMemoryStatus( sysMemoryStats_t &stats ) { + stats = exeLaunchMemoryStats; +} + +/* +================== +Sys_Createthread +================== +*/ +void Sys_CreateThread( xthread_t function, void *parms, xthreadPriority priority, xthreadInfo &info, const char *name, xthreadInfo *threads[MAX_THREADS], int *thread_count ) { + HANDLE temp = CreateThread( NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)function, // LPTHREAD_START_ROUTINE lpStartAddr, + parms, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &info.threadId); + info.threadHandle = (int) temp; + if (priority == THREAD_HIGHEST) { + SetThreadPriority( (HANDLE)info.threadHandle, THREAD_PRIORITY_HIGHEST ); // we better sleep enough to do this + } else if (priority == THREAD_ABOVE_NORMAL ) { + SetThreadPriority( (HANDLE)info.threadHandle, THREAD_PRIORITY_ABOVE_NORMAL ); + } + info.name = name; + if ( *thread_count < MAX_THREADS ) { + threads[(*thread_count)++] = &info; + } else { + common->DPrintf("WARNING: MAX_THREADS reached\n"); + } +} + +/* +================== +Sys_DestroyThread +================== +*/ +void Sys_DestroyThread( xthreadInfo& info ) { + WaitForSingleObject( (HANDLE)info.threadHandle, INFINITE); + CloseHandle( (HANDLE)info.threadHandle ); + info.threadHandle = 0; +} + +/* +================== +Sys_Sentry +================== +*/ +void Sys_Sentry() { + int j = 0; +} + +/* +================== +Sys_GetThreadName +================== +*/ +const char* Sys_GetThreadName(int *index) { + int id = GetCurrentThreadId(); + for( int i = 0; i < g_thread_count; i++ ) { + if ( id == g_threads[i]->threadId ) { + if ( index ) { + *index = i; + } + return g_threads[i]->name; + } + } + if ( index ) { + *index = -1; + } + return "main"; +} + + +/* +================== +Sys_EnterCriticalSection +================== +*/ +void Sys_EnterCriticalSection( int index ) { + assert( index >= 0 && index < MAX_CRITICAL_SECTIONS ); + if ( TryEnterCriticalSection( &win32.criticalSections[index] ) == 0 ) { + EnterCriticalSection( &win32.criticalSections[index] ); +// Sys_DebugPrintf( "busy lock '%s' in thread '%s'\n", lock->name, Sys_GetThreadName() ); + } +} + +/* +================== +Sys_LeaveCriticalSection +================== +*/ +void Sys_LeaveCriticalSection( int index ) { + assert( index >= 0 && index < MAX_CRITICAL_SECTIONS ); + LeaveCriticalSection( &win32.criticalSections[index] ); +} + +/* +================== +Sys_WaitForEvent +================== +*/ +void Sys_WaitForEvent( int index ) { + assert( index == 0 ); + if ( !win32.backgroundDownloadSemaphore ) { + win32.backgroundDownloadSemaphore = CreateEvent( NULL, TRUE, FALSE, NULL ); + } + WaitForSingleObject( win32.backgroundDownloadSemaphore, INFINITE ); + ResetEvent( win32.backgroundDownloadSemaphore ); +} + +/* +================== +Sys_TriggerEvent +================== +*/ +void Sys_TriggerEvent( int index ) { + assert( index == 0 ); + SetEvent( win32.backgroundDownloadSemaphore ); +} + + + +#pragma optimize( "", on ) + +#ifdef DEBUG + + +static unsigned int debug_total_alloc = 0; +static unsigned int debug_total_alloc_count = 0; +static unsigned int debug_current_alloc = 0; +static unsigned int debug_current_alloc_count = 0; +static unsigned int debug_frame_alloc = 0; +static unsigned int debug_frame_alloc_count = 0; + +idCVar sys_showMallocs( "sys_showMallocs", "0", CVAR_SYSTEM, "" ); + +// _HOOK_ALLOC, _HOOK_REALLOC, _HOOK_FREE + +typedef struct CrtMemBlockHeader +{ + struct _CrtMemBlockHeader *pBlockHeaderNext; // Pointer to the block allocated just before this one: + struct _CrtMemBlockHeader *pBlockHeaderPrev; // Pointer to the block allocated just after this one + char *szFileName; // File name + int nLine; // Line number + size_t nDataSize; // Size of user block + int nBlockUse; // Type of block + long lRequest; // Allocation number + byte gap[4]; // Buffer just before (lower than) the user's memory: +} CrtMemBlockHeader; + +#include + +/* +================== +Sys_AllocHook + + called for every malloc/new/free/delete +================== +*/ +int Sys_AllocHook( int nAllocType, void *pvData, size_t nSize, int nBlockUse, long lRequest, const unsigned char * szFileName, int nLine ) +{ + CrtMemBlockHeader *pHead; + byte *temp; + + if ( nBlockUse == _CRT_BLOCK ) + { + return( TRUE ); + } + + // get a pointer to memory block header + temp = ( byte * )pvData; + temp -= 32; + pHead = ( CrtMemBlockHeader * )temp; + + switch( nAllocType ) { + case _HOOK_ALLOC: + debug_total_alloc += nSize; + debug_current_alloc += nSize; + debug_frame_alloc += nSize; + debug_total_alloc_count++; + debug_current_alloc_count++; + debug_frame_alloc_count++; + break; + + case _HOOK_FREE: + assert( pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd ); + + debug_current_alloc -= pHead->nDataSize; + debug_current_alloc_count--; + debug_total_alloc_count++; + debug_frame_alloc_count++; + break; + + case _HOOK_REALLOC: + assert( pHead->gap[0] == 0xfd && pHead->gap[1] == 0xfd && pHead->gap[2] == 0xfd && pHead->gap[3] == 0xfd ); + + debug_current_alloc -= pHead->nDataSize; + debug_total_alloc += nSize; + debug_current_alloc += nSize; + debug_frame_alloc += nSize; + debug_total_alloc_count++; + debug_current_alloc_count--; + debug_frame_alloc_count++; + break; + } + return( TRUE ); +} + +/* +================== +Sys_DebugMemory_f +================== +*/ +void Sys_DebugMemory_f( void ) { + common->Printf( "Total allocation %8dk in %d blocks\n", debug_total_alloc / 1024, debug_total_alloc_count ); + common->Printf( "Current allocation %8dk in %d blocks\n", debug_current_alloc / 1024, debug_current_alloc_count ); +} + +/* +================== +Sys_MemFrame +================== +*/ +void Sys_MemFrame( void ) { + if( sys_showMallocs.GetInteger() ) { + common->Printf("Frame: %8dk in %5d blocks\n", debug_frame_alloc / 1024, debug_frame_alloc_count ); + } + + debug_frame_alloc = 0; + debug_frame_alloc_count = 0; +} + +#endif + +/* +================== +Sys_FlushCacheMemory + +On windows, the vertex buffers are write combined, so they +don't need to be flushed from the cache +================== +*/ +void Sys_FlushCacheMemory( void *base, int bytes ) { +} + +/* +============= +Sys_Error + +Show the early console as an error dialog +============= +*/ +void Sys_Error( const char *error, ... ) { + va_list argptr; + char text[4096]; + MSG msg; + + va_start( argptr, error ); + vsprintf( text, error, argptr ); + va_end( argptr); + + Conbuf_AppendText( text ); + Conbuf_AppendText( "\n" ); + + Win_SetErrorText( text ); + Sys_ShowConsole( 1, true ); + + timeEndPeriod( 1 ); + + Sys_ShutdownInput(); + + GLimp_Shutdown(); + + // wait for the user to quit + while ( 1 ) { + if ( !GetMessage( &msg, NULL, 0, 0 ) ) { + common->Quit(); + } + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + + Sys_DestroyConsole(); + + exit (1); +} + +/* +============== +Sys_Quit +============== +*/ +void Sys_Quit( void ) { + timeEndPeriod( 1 ); + Sys_ShutdownInput(); + Sys_DestroyConsole(); + ExitProcess( 0 ); +} + + +/* +============== +Sys_Printf +============== +*/ +#define MAXPRINTMSG 4096 +void Sys_Printf( const char *fmt, ... ) { + char msg[MAXPRINTMSG]; + + va_list argptr; + va_start(argptr, fmt); + idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, argptr ); + va_end(argptr); + msg[sizeof(msg)-1] = '\0'; + + if ( win32.win_outputDebugString.GetBool() ) { + OutputDebugString( msg ); + } + if ( win32.win_outputEditString.GetBool() ) { + Conbuf_AppendText( msg ); + } +} + +/* +============== +Sys_DebugPrintf +============== +*/ +#define MAXPRINTMSG 4096 +void Sys_DebugPrintf( const char *fmt, ... ) { + char msg[MAXPRINTMSG]; + + va_list argptr; + va_start( argptr, fmt ); + idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, argptr ); + msg[ sizeof(msg)-1 ] = '\0'; + va_end( argptr ); + + OutputDebugString( msg ); +} + +/* +============== +Sys_DebugVPrintf +============== +*/ +void Sys_DebugVPrintf( const char *fmt, va_list arg ) { + char msg[MAXPRINTMSG]; + + idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, arg ); + msg[ sizeof(msg)-1 ] = '\0'; + + OutputDebugString( msg ); +} + +/* +============== +Sys_Sleep +============== +*/ +void Sys_Sleep( int msec ) { + Sleep( msec ); +} + +/* +============== +Sys_ShowWindow +============== +*/ +void Sys_ShowWindow( bool show ) { + ::ShowWindow( win32.hWnd, show ? SW_SHOW : SW_HIDE ); +} + +/* +============== +Sys_IsWindowVisible +============== +*/ +bool Sys_IsWindowVisible( void ) { + return ( ::IsWindowVisible( win32.hWnd ) != 0 ); +} + +/* +============== +Sys_Mkdir +============== +*/ +void Sys_Mkdir( const char *path ) { + _mkdir (path); +} + +/* +================= +Sys_FileTimeStamp +================= +*/ +ID_TIME_T Sys_FileTimeStamp( FILE *fp ) { + struct _stat st; + _fstat( _fileno( fp ), &st ); + return (long) st.st_mtime; +} + +/* +============== +Sys_Cwd +============== +*/ +const char *Sys_Cwd( void ) { + static char cwd[MAX_OSPATH]; + + _getcwd( cwd, sizeof( cwd ) - 1 ); + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +/* +============== +Sys_DefaultCDPath +============== +*/ +const char *Sys_DefaultCDPath( void ) { + return ""; +} + +/* +============== +Sys_DefaultBasePath +============== +*/ +const char *Sys_DefaultBasePath( void ) { + return Sys_Cwd(); +} + +/* +============== +Sys_DefaultSavePath +============== +*/ +const char *Sys_DefaultSavePath( void ) { + return cvarSystem->GetCVarString( "fs_basepath" ); +} + +/* +============== +Sys_EXEPath +============== +*/ +const char *Sys_EXEPath( void ) { + static char exe[ MAX_OSPATH ]; + GetModuleFileName( NULL, exe, sizeof( exe ) - 1 ); + return exe; +} + +/* +============== +Sys_ListFiles +============== +*/ +int Sys_ListFiles( const char *directory, const char *extension, idStrList &list ) { + idStr search; + struct _finddata_t findinfo; + int findhandle; + int flag; + + if ( !extension) { + extension = ""; + } + + // passing a slash as extension will find directories + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + flag = 0; + } else { + flag = _A_SUBDIR; + } + + sprintf( search, "%s\\*%s", directory, extension ); + + // search + list.Clear(); + + findhandle = _findfirst( search, &findinfo ); + if ( findhandle == -1 ) { + return -1; + } + + do { + if ( flag ^ ( findinfo.attrib & _A_SUBDIR ) ) { + list.Append( findinfo.name ); + } + } while ( _findnext( findhandle, &findinfo ) != -1 ); + + _findclose( findhandle ); + + return list.Num(); +} + + +/* +================ +Sys_GetClipboardData +================ +*/ +char *Sys_GetClipboardData( void ) { + char *data = NULL; + char *cliptext; + + if ( OpenClipboard( NULL ) != 0 ) { + HANDLE hClipboardData; + + if ( ( hClipboardData = GetClipboardData( CF_TEXT ) ) != 0 ) { + if ( ( cliptext = (char *)GlobalLock( hClipboardData ) ) != 0 ) { + data = (char *)Mem_Alloc( GlobalSize( hClipboardData ) + 1 ); + strcpy( data, cliptext ); + GlobalUnlock( hClipboardData ); + + strtok( data, "\n\r\b" ); + } + } + CloseClipboard(); + } + return data; +} + +/* +================ +Sys_SetClipboardData +================ +*/ +void Sys_SetClipboardData( const char *string ) { + HGLOBAL HMem; + char *PMem; + + // allocate memory block + HMem = (char *)::GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE, strlen( string ) + 1 ); + if ( HMem == NULL ) { + return; + } + // lock allocated memory and obtain a pointer + PMem = (char *)::GlobalLock( HMem ); + if ( PMem == NULL ) { + return; + } + // copy text into allocated memory block + lstrcpy( PMem, string ); + // unlock allocated memory + ::GlobalUnlock( HMem ); + // open Clipboard + if ( !OpenClipboard( 0 ) ) { + ::GlobalFree( HMem ); + return; + } + // remove current Clipboard contents + EmptyClipboard(); + // supply the memory handle to the Clipboard + SetClipboardData( CF_TEXT, HMem ); + HMem = 0; + // close Clipboard + CloseClipboard(); +} + +/* +======================================================================== + +DLL Loading + +======================================================================== +*/ + +/* +===================== +Sys_DLL_Load +===================== +*/ +int Sys_DLL_Load( const char *dllName ) { + HINSTANCE libHandle; + libHandle = LoadLibrary( dllName ); + if ( libHandle ) { + // since we can't have LoadLibrary load only from the specified path, check it did the right thing + char loadedPath[ MAX_OSPATH ]; + GetModuleFileName( libHandle, loadedPath, sizeof( loadedPath ) - 1 ); + if ( idStr::IcmpPath( dllName, loadedPath ) ) { + Sys_Printf( "ERROR: LoadLibrary '%s' wants to load '%s'\n", dllName, loadedPath ); + Sys_DLL_Unload( (int)libHandle ); + return 0; + } + } + return (int)libHandle; +} + +/* +===================== +Sys_DLL_GetProcAddress +===================== +*/ +void *Sys_DLL_GetProcAddress( int dllHandle, const char *procName ) { + return GetProcAddress( (HINSTANCE)dllHandle, procName ); +} + +/* +===================== +Sys_DLL_Unload +===================== +*/ +void Sys_DLL_Unload( int dllHandle ) { + if ( !dllHandle ) { + return; + } + if ( FreeLibrary( (HINSTANCE)dllHandle ) == 0 ) { + int lastError = GetLastError(); + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, + lastError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + Sys_Error( "Sys_DLL_Unload: FreeLibrary failed - %s (%d)", lpMsgBuf, lastError ); + } +} + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +sysEvent_t eventQue[MAX_QUED_EVENTS]; +int eventHead = 0; +int eventTail = 0; + +/* +================ +Sys_QueEvent + +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) { + sysEvent_t *ev; + + ev = &eventQue[ eventHead & MASK_QUED_EVENTS ]; + + if ( eventHead - eventTail >= MAX_QUED_EVENTS ) { + common->Printf("Sys_QueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) { + Mem_Free( ev->evPtr ); + } + eventTail++; + } + + eventHead++; + + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + +/* +============= +Sys_PumpEvents + +This allows windows to be moved during renderbump +============= +*/ +void Sys_PumpEvents( void ) { + MSG msg; + + // pump the message loop + while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) ) { + if ( !GetMessage( &msg, NULL, 0, 0 ) ) { + common->Quit(); + } + + // save the msg time, because wndprocs don't have access to the timestamp + if ( win32.sysMsgTime && win32.sysMsgTime > (int)msg.time ) { + // don't ever let the event times run backwards +// common->Printf( "Sys_PumpEvents: win32.sysMsgTime (%i) > msg.time (%i)\n", win32.sysMsgTime, msg.time ); + } else { + win32.sysMsgTime = msg.time; + } + +#ifdef ID_ALLOW_TOOLS + if ( GUIEditorHandleMessage ( &msg ) ) { + continue; + } +#endif + + TranslateMessage (&msg); + DispatchMessage (&msg); + } +} + +/* +================ +Sys_GenerateEvents +================ +*/ +void Sys_GenerateEvents( void ) { + static int entered = false; + char *s; + + if ( entered ) { + return; + } + entered = true; + + // pump the message loop + Sys_PumpEvents(); + + // make sure mouse and joystick are only called once a frame + IN_Frame(); + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) { + char *b; + int len; + + len = strlen( s ) + 1; + b = (char *)Mem_Alloc( len ); + strcpy( b, s ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + entered = false; +} + +/* +================ +Sys_ClearEvents +================ +*/ +void Sys_ClearEvents( void ) { + eventHead = eventTail = 0; +} + +/* +================ +Sys_GetEvent +================ +*/ +sysEvent_t Sys_GetEvent( void ) { + sysEvent_t ev; + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // return the empty event + memset( &ev, 0, sizeof( ev ) ); + + return ev; +} + +//================================================================ + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( const idCmdArgs &args ) { + Sys_ShutdownInput(); + Sys_InitInput(); +} + + +/* +================== +Sys_AsyncThread +================== +*/ +static void Sys_AsyncThread( void *parm ) { + int wakeNumber; + int startTime; + + startTime = Sys_Milliseconds(); + wakeNumber = 0; + + while ( 1 ) { +#ifdef WIN32 + // this will trigger 60 times a second + int r = WaitForSingleObject( hTimer, 100 ); + if ( r != WAIT_OBJECT_0 ) { + OutputDebugString( "idPacketServer::PacketServerInterrupt: bad wait return" ); + } +#endif + +#if 0 + wakeNumber++; + int msec = Sys_Milliseconds(); + int deltaTime = msec - startTime; + startTime = msec; + + char str[1024]; + sprintf( str, "%i ", deltaTime ); + OutputDebugString( str ); +#endif + + + common->Async(); + } +} + +/* +============== +Sys_StartAsyncThread + +Start the thread that will call idCommon::Async() +============== +*/ +void Sys_StartAsyncThread( void ) { + // create an auto-reset event that happens 60 times a second + hTimer = CreateWaitableTimer( NULL, false, NULL ); + if ( !hTimer ) { + common->Error( "idPacketServer::Spawn: CreateWaitableTimer failed" ); + } + + LARGE_INTEGER t; + t.HighPart = t.LowPart = 0; + SetWaitableTimer( hTimer, &t, common->GetUserCmdMSec(), NULL, NULL, TRUE ); + + Sys_CreateThread( (xthread_t)Sys_AsyncThread, NULL, THREAD_ABOVE_NORMAL, threadInfo, "Async", g_threads, &g_thread_count ); + +#ifdef SET_THREAD_AFFINITY + // give the async thread an affinity for the second cpu + SetThreadAffinityMask( (HANDLE)threadInfo.threadHandle, 2 ); +#endif + + if ( !threadInfo.threadHandle ) { + common->Error( "Sys_StartAsyncThread: failed" ); + } +} + +/* +================ +Sys_AlreadyRunning + +returns true if there is a copy of D3 running already +================ +*/ +bool Sys_AlreadyRunning( void ) { +#ifndef DEBUG + if ( !win32.win_allowMultipleInstances.GetBool() ) { + HANDLE hMutexOneInstance = ::CreateMutex( NULL, FALSE, "DOOM3" ); + if ( ::GetLastError() == ERROR_ALREADY_EXISTS || ::GetLastError() == ERROR_ACCESS_DENIED ) { + return true; + } + } +#endif + return false; +} + +/* +================ +Sys_Init + +The cvar system must already be setup +================ +*/ +#define OSR2_BUILD_NUMBER 1111 +#define WIN98_BUILD_NUMBER 1998 + +void Sys_Init( void ) { + + CoInitialize( NULL ); + + // make sure the timer is high precision, otherwise + // NT gets 18ms resolution + timeBeginPeriod( 1 ); + + // get WM_TIMER messages pumped every millisecond +// SetTimer( NULL, 0, 100, NULL ); + + cmdSystem->AddCommand( "in_restart", Sys_In_Restart_f, CMD_FL_SYSTEM, "restarts the input system" ); +#ifdef DEBUG + cmdSystem->AddCommand( "createResourceIDs", CreateResourceIDs_f, CMD_FL_TOOL, "assigns resource IDs in _resouce.h files" ); +#endif +#if 0 + cmdSystem->AddCommand( "setAsyncSound", Sys_SetAsyncSound_f, CMD_FL_SYSTEM, "set the async sound option" ); +#endif + + // + // Windows user name + // + win32.win_username.SetString( Sys_GetCurrentUser() ); + + // + // Windows version + // + win32.osversion.dwOSVersionInfoSize = sizeof( win32.osversion ); + + if ( !GetVersionEx( (LPOSVERSIONINFO)&win32.osversion ) ) + Sys_Error( "Couldn't get OS info" ); + + if ( win32.osversion.dwMajorVersion < 4 ) { + Sys_Error( GAME_NAME " requires Windows version 4 (NT) or greater" ); + } + if ( win32.osversion.dwPlatformId == VER_PLATFORM_WIN32s ) { + Sys_Error( GAME_NAME " doesn't run on Win32s" ); + } + + if( win32.osversion.dwPlatformId == VER_PLATFORM_WIN32_NT ) { + if( win32.osversion.dwMajorVersion <= 4 ) { + win32.sys_arch.SetString( "WinNT (NT)" ); + } else if( win32.osversion.dwMajorVersion == 5 && win32.osversion.dwMinorVersion == 0 ) { + win32.sys_arch.SetString( "Win2K (NT)" ); + } else if( win32.osversion.dwMajorVersion == 5 && win32.osversion.dwMinorVersion == 1 ) { + win32.sys_arch.SetString( "WinXP (NT)" ); + } else if ( win32.osversion.dwMajorVersion == 6 ) { + win32.sys_arch.SetString( "Vista" ); + } else { + win32.sys_arch.SetString( "Unknown NT variant" ); + } + } else if( win32.osversion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) { + if( win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 0 ) { + // Win95 + if( win32.osversion.szCSDVersion[1] == 'C' ) { + win32.sys_arch.SetString( "Win95 OSR2 (95)" ); + } else { + win32.sys_arch.SetString( "Win95 (95)" ); + } + } else if( win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 10 ) { + // Win98 + if( win32.osversion.szCSDVersion[1] == 'A' ) { + win32.sys_arch.SetString( "Win98SE (95)" ); + } else { + win32.sys_arch.SetString( "Win98 (95)" ); + } + } else if( win32.osversion.dwMajorVersion == 4 && win32.osversion.dwMinorVersion == 90 ) { + // WinMe + win32.sys_arch.SetString( "WinMe (95)" ); + } else { + win32.sys_arch.SetString( "Unknown 95 variant" ); + } + } else { + win32.sys_arch.SetString( "unknown Windows variant" ); + } + + // + // CPU type + // + if ( !idStr::Icmp( win32.sys_cpustring.GetString(), "detect" ) ) { + idStr string; + + common->Printf( "%1.0f MHz ", Sys_ClockTicksPerSecond() / 1000000.0f ); + + win32.cpuid = Sys_GetCPUId(); + + string.Clear(); + + if ( win32.cpuid & CPUID_AMD ) { + string += "AMD CPU"; + } else if ( win32.cpuid & CPUID_INTEL ) { + string += "Intel CPU"; + } else if ( win32.cpuid & CPUID_UNSUPPORTED ) { + string += "unsupported CPU"; + } else { + string += "generic CPU"; + } + + string += " with "; + if ( win32.cpuid & CPUID_MMX ) { + string += "MMX & "; + } + if ( win32.cpuid & CPUID_3DNOW ) { + string += "3DNow! & "; + } + if ( win32.cpuid & CPUID_SSE ) { + string += "SSE & "; + } + if ( win32.cpuid & CPUID_SSE2 ) { + string += "SSE2 & "; + } + if ( win32.cpuid & CPUID_SSE3 ) { + string += "SSE3 & "; + } + if ( win32.cpuid & CPUID_HTT ) { + string += "HTT & "; + } + string.StripTrailing( " & " ); + string.StripTrailing( " with " ); + win32.sys_cpustring.SetString( string ); + } else { + common->Printf( "forcing CPU type to " ); + idLexer src( win32.sys_cpustring.GetString(), idStr::Length( win32.sys_cpustring.GetString() ), "sys_cpustring" ); + idToken token; + + int id = CPUID_NONE; + while( src.ReadToken( &token ) ) { + if ( token.Icmp( "generic" ) == 0 ) { + id |= CPUID_GENERIC; + } else if ( token.Icmp( "intel" ) == 0 ) { + id |= CPUID_INTEL; + } else if ( token.Icmp( "amd" ) == 0 ) { + id |= CPUID_AMD; + } else if ( token.Icmp( "mmx" ) == 0 ) { + id |= CPUID_MMX; + } else if ( token.Icmp( "3dnow" ) == 0 ) { + id |= CPUID_3DNOW; + } else if ( token.Icmp( "sse" ) == 0 ) { + id |= CPUID_SSE; + } else if ( token.Icmp( "sse2" ) == 0 ) { + id |= CPUID_SSE2; + } else if ( token.Icmp( "sse3" ) == 0 ) { + id |= CPUID_SSE3; + } else if ( token.Icmp( "htt" ) == 0 ) { + id |= CPUID_HTT; + } + } + if ( id == CPUID_NONE ) { + common->Printf( "WARNING: unknown sys_cpustring '%s'\n", win32.sys_cpustring.GetString() ); + id = CPUID_GENERIC; + } + win32.cpuid = (cpuid_t) id; + } + + common->Printf( "%s\n", win32.sys_cpustring.GetString() ); + common->Printf( "%d MB System Memory\n", Sys_GetSystemRam() ); + common->Printf( "%d MB Video Memory\n", Sys_GetVideoRam() ); +} + +/* +================ +Sys_Shutdown +================ +*/ +void Sys_Shutdown( void ) { + CoUninitialize(); +} + +/* +================ +Sys_GetProcessorId +================ +*/ +cpuid_t Sys_GetProcessorId( void ) { + return win32.cpuid; +} + +/* +================ +Sys_GetProcessorString +================ +*/ +const char *Sys_GetProcessorString( void ) { + return win32.sys_cpustring.GetString(); +} + +//======================================================================= + +//#define SET_THREAD_AFFINITY + + +/* +==================== +Win_Frame +==================== +*/ +void Win_Frame( void ) { + // if "viewlog" has been modified, show or hide the log console + if ( win32.win_viewlog.IsModified() ) { + if ( !com_skipRenderer.GetBool() && idAsyncNetwork::serverDedicated.GetInteger() != 1 ) { + Sys_ShowConsole( win32.win_viewlog.GetInteger(), false ); + } + win32.win_viewlog.ClearModified(); + } +} + +extern "C" { void _chkstk( int size ); }; +void clrstk( void ); + +/* +==================== +TestChkStk +==================== +*/ +void TestChkStk( void ) { + int buffer[0x1000]; + + buffer[0] = 1; +} + +/* +==================== +HackChkStk +==================== +*/ +void HackChkStk( void ) { + DWORD old; + VirtualProtect( _chkstk, 6, PAGE_EXECUTE_READWRITE, &old ); + *(byte *)_chkstk = 0xe9; + *(int *)((int)_chkstk+1) = (int)clrstk - (int)_chkstk - 5; + + TestChkStk(); +} + +/* +==================== +GetExceptionCodeInfo +==================== +*/ +const char *GetExceptionCodeInfo( UINT code ) { + switch( code ) { + case EXCEPTION_ACCESS_VIOLATION: return "The thread tried to read from or write to a virtual address for which it does not have the appropriate access."; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking."; + case EXCEPTION_BREAKPOINT: return "A breakpoint was encountered."; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on."; + case EXCEPTION_FLT_DENORMAL_OPERAND: return "One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value."; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "The thread tried to divide a floating-point value by a floating-point divisor of zero."; + case EXCEPTION_FLT_INEXACT_RESULT: return "The result of a floating-point operation cannot be represented exactly as a decimal fraction."; + case EXCEPTION_FLT_INVALID_OPERATION: return "This exception represents any floating-point exception not included in this list."; + case EXCEPTION_FLT_OVERFLOW: return "The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type."; + case EXCEPTION_FLT_STACK_CHECK: return "The stack overflowed or underflowed as the result of a floating-point operation."; + case EXCEPTION_FLT_UNDERFLOW: return "The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type."; + case EXCEPTION_ILLEGAL_INSTRUCTION: return "The thread tried to execute an invalid instruction."; + case EXCEPTION_IN_PAGE_ERROR: return "The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network."; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "The thread tried to divide an integer value by an integer divisor of zero."; + case EXCEPTION_INT_OVERFLOW: return "The result of an integer operation caused a carry out of the most significant bit of the result."; + case EXCEPTION_INVALID_DISPOSITION: return "An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception."; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "The thread tried to continue execution after a noncontinuable exception occurred."; + case EXCEPTION_PRIV_INSTRUCTION: return "The thread tried to execute an instruction whose operation is not allowed in the current machine mode."; + case EXCEPTION_SINGLE_STEP: return "A trace trap or other single-instruction mechanism signaled that one instruction has been executed."; + case EXCEPTION_STACK_OVERFLOW: return "The thread used up its stack."; + default: return "Unknown exception"; + } +} + +/* +==================== +EmailCrashReport + + emailer originally from Raven/Quake 4 +==================== +*/ +void EmailCrashReport( LPSTR messageText ) { + LPMAPISENDMAIL MAPISendMail; + MapiMessage message; + static int lastEmailTime = 0; + + if ( Sys_Milliseconds() < lastEmailTime + 10000 ) { + return; + } + + lastEmailTime = Sys_Milliseconds(); + + HINSTANCE mapi = LoadLibrary( "MAPI32.DLL" ); + if( mapi ) { + MAPISendMail = ( LPMAPISENDMAIL )GetProcAddress( mapi, "MAPISendMail" ); + if( MAPISendMail ) { + MapiRecipDesc toProgrammers = + { + 0, // ulReserved + MAPI_TO, // ulRecipClass + "DOOM 3 Crash", // lpszName + "SMTP:programmers@idsoftware.com", // lpszAddress + 0, // ulEIDSize + 0 // lpEntry + }; + + memset( &message, 0, sizeof( message ) ); + message.lpszSubject = "DOOM 3 Fatal Error"; + message.lpszNoteText = messageText; + message.nRecipCount = 1; + message.lpRecips = &toProgrammers; + + MAPISendMail( + 0, // LHANDLE lhSession + 0, // ULONG ulUIParam + &message, // lpMapiMessage lpMessage + MAPI_DIALOG, // FLAGS flFlags + 0 // ULONG ulReserved + ); + } + FreeLibrary( mapi ); + } +} + +int Sys_FPU_PrintStateFlags( char *ptr, int ctrl, int stat, int tags, int inof, int inse, int opof, int opse ); + +/* +==================== +_except_handler +==================== +*/ +EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, + struct _CONTEXT *ContextRecord, void * DispatcherContext ) { + + static char msg[ 8192 ]; + char FPUFlags[2048]; + + Sys_FPU_PrintStateFlags( FPUFlags, ContextRecord->FloatSave.ControlWord, + ContextRecord->FloatSave.StatusWord, + ContextRecord->FloatSave.TagWord, + ContextRecord->FloatSave.ErrorOffset, + ContextRecord->FloatSave.ErrorSelector, + ContextRecord->FloatSave.DataOffset, + ContextRecord->FloatSave.DataSelector ); + + + sprintf( msg, + "Please describe what you were doing when DOOM 3 crashed!\n" + "If this text did not pop into your email client please copy and email it to programmers@idsoftware.com\n" + "\n" + "-= FATAL EXCEPTION =-\n" + "\n" + "%s\n" + "\n" + "0x%x at address 0x%08x\n" + "\n" + "%s\n" + "\n" + "EAX = 0x%08x EBX = 0x%08x\n" + "ECX = 0x%08x EDX = 0x%08x\n" + "ESI = 0x%08x EDI = 0x%08x\n" + "EIP = 0x%08x ESP = 0x%08x\n" + "EBP = 0x%08x EFL = 0x%08x\n" + "\n" + "CS = 0x%04x\n" + "SS = 0x%04x\n" + "DS = 0x%04x\n" + "ES = 0x%04x\n" + "FS = 0x%04x\n" + "GS = 0x%04x\n" + "\n" + "%s\n", + com_version.GetString(), + ExceptionRecord->ExceptionCode, + ExceptionRecord->ExceptionAddress, + GetExceptionCodeInfo( ExceptionRecord->ExceptionCode ), + ContextRecord->Eax, ContextRecord->Ebx, + ContextRecord->Ecx, ContextRecord->Edx, + ContextRecord->Esi, ContextRecord->Edi, + ContextRecord->Eip, ContextRecord->Esp, + ContextRecord->Ebp, ContextRecord->EFlags, + ContextRecord->SegCs, + ContextRecord->SegSs, + ContextRecord->SegDs, + ContextRecord->SegEs, + ContextRecord->SegFs, + ContextRecord->SegGs, + FPUFlags + ); + + EmailCrashReport( msg ); + common->FatalError( msg ); + + // Tell the OS to restart the faulting instruction + return ExceptionContinueExecution; +} + +#define TEST_FPU_EXCEPTIONS /* FPU_EXCEPTION_INVALID_OPERATION | */ \ + /* FPU_EXCEPTION_DENORMALIZED_OPERAND | */ \ + /* FPU_EXCEPTION_DIVIDE_BY_ZERO | */ \ + /* FPU_EXCEPTION_NUMERIC_OVERFLOW | */ \ + /* FPU_EXCEPTION_NUMERIC_UNDERFLOW | */ \ + /* FPU_EXCEPTION_INEXACT_RESULT | */ \ + 0 + +/* +================== +WinMain +================== +*/ +int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { + +#ifdef RV_UNIFIED_ALLOCATOR + // The retail executable installs the CRT allocation callbacks before doing + // any Win32 or common-system startup work. Static constructors that run + // before WinMain safely use Memory's CRT fallback (_LOAD_DLL build mode). + Memory::InitAllocator( malloc, free, _msize ); +#endif + + const HCURSOR hcurSave = ::SetCursor( LoadCursor( 0, IDC_WAIT ) ); + + Sys_SetPhysicalWorkMemory( 192 << 20, 1024 << 20 ); + + Sys_GetCurrentMemoryStatus( exeLaunchMemoryStats ); + +#if 0 + DWORD handler = (DWORD)_except_handler; + __asm + { // Build EXCEPTION_REGISTRATION record: + push handler // Address of handler function + push FS:[0] // Address of previous handler + mov FS:[0],ESP // Install new EXECEPTION_REGISTRATION + } +#endif + + win32.hInstance = hInstance; + idStr::Copynz( sys_cmdline, lpCmdLine, sizeof( sys_cmdline ) ); + + // done before Com/Sys_Init since we need this for error output + Sys_CreateConsole(); + + // no abort/retry/fail errors + SetErrorMode( SEM_FAILCRITICALERRORS ); + + for ( int i = 0; i < MAX_CRITICAL_SECTIONS; i++ ) { + InitializeCriticalSection( &win32.criticalSections[i] ); + } + + // get the initial time base + Sys_Milliseconds(); + +#ifdef DEBUG + // disable the painfully slow MS heap check every 1024 allocs + _CrtSetDbgFlag( 0 ); +#endif + +// Sys_FPU_EnableExceptions( TEST_FPU_EXCEPTIONS ); + Sys_FPU_SetPrecision( FPU_PRECISION_DOUBLE_EXTENDED ); + + common->Init( 0, NULL, lpCmdLine ); + +#if TEST_FPU_EXCEPTIONS != 0 + common->Printf( Sys_FPU_GetState() ); +#endif + +#ifndef ID_DEDICATED + if ( win32.win_notaskkeys.GetInteger() ) { + DisableTaskKeys( TRUE, FALSE, /*( win32.win_notaskkeys.GetInteger() == 2 )*/ FALSE ); + } +#endif + + Sys_StartAsyncThread(); + + // hide or show the early console as necessary + if ( win32.win_viewlog.GetInteger() || com_skipRenderer.GetBool() || idAsyncNetwork::serverDedicated.GetInteger() ) { + Sys_ShowConsole( 1, true ); + } else { + Sys_ShowConsole( 0, false ); + } + +#ifdef SET_THREAD_AFFINITY + // give the main thread an affinity for the first cpu + SetThreadAffinityMask( GetCurrentThread(), 1 ); +#endif + + ::SetCursor( hcurSave ); + + // Launch the script debugger + if ( strstr( lpCmdLine, "+debugger" ) ) { + // DebuggerClientInit( lpCmdLine ); + return 0; + } + + ::SetFocus( win32.hWnd ); + + // main game loop + while( 1 ) { + + Win_Frame(); + +#ifdef DEBUG + Sys_MemFrame(); +#endif + + // set exceptions, even if some crappy syscall changes them! + Sys_FPU_EnableExceptions( TEST_FPU_EXCEPTIONS ); + +#ifdef ID_ALLOW_TOOLS + if ( com_editors ) { + if ( com_editors & EDITOR_GUI ) { + // GUI editor + GUIEditorRun(); + } else if ( com_editors & EDITOR_RADIANT ) { + // Level Editor + RadiantRun(); + } + else if (com_editors & EDITOR_MATERIAL ) { + //BSM Nerve: Add support for the material editor + MaterialEditorRun(); + } + else { + if ( com_editors & EDITOR_LIGHT ) { + // in-game Light Editor + LightEditorRun(); + } + if ( com_editors & EDITOR_SOUND ) { + // in-game Sound Editor + SoundEditorRun(); + } + if ( com_editors & EDITOR_DECL ) { + // in-game Declaration Browser + DeclBrowserRun(); + } + if ( com_editors & EDITOR_AF ) { + // in-game Articulated Figure Editor + AFEditorRun(); + } + if ( com_editors & EDITOR_PARTICLE ) { + // in-game Particle Editor + ParticleEditorRun(); + } + if ( com_editors & EDITOR_SCRIPT ) { + // in-game Script Editor + ScriptEditorRun(); + } + if ( com_editors & EDITOR_PDA ) { + // in-game PDA Editor + PDAEditorRun(); + } + } + } +#endif + // run the game + common->Frame(); + } + + // never gets here + return 0; +} + +/* +==================== +clrstk + +I tried to get the run time to call this at every function entry, but +==================== +*/ +static int parmBytes; +__declspec( naked ) void clrstk( void ) { + // eax = bytes to add to stack + __asm { + mov [parmBytes],eax + neg eax ; compute new stack pointer in eax + add eax,esp + add eax,4 + xchg eax,esp + mov eax,dword ptr [eax] ; copy the return address + push eax + + ; clear to zero + push edi + push ecx + mov edi,esp + add edi,12 + mov ecx,[parmBytes] + shr ecx,2 + xor eax,eax + cld + rep stosd + pop ecx + pop edi + + ret + } +} + +/* +================== +idSysLocal::OpenURL +================== +*/ +void idSysLocal::OpenURL( const char *url, bool doexit ) { + static bool doexit_spamguard = false; + HWND wnd; + + if (doexit_spamguard) { + common->DPrintf( "OpenURL: already in an exit sequence, ignoring %s\n", url ); + return; + } + + common->Printf("Open URL: %s\n", url); + + if ( !ShellExecute( NULL, "open", url, NULL, NULL, SW_RESTORE ) ) { + common->Error( "Could not open url: '%s' ", url ); + return; + } + + wnd = GetForegroundWindow(); + if ( wnd ) { + ShowWindow( wnd, SW_MAXIMIZE ); + } + + if ( doexit ) { + doexit_spamguard = true; + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + } +} + +/* +================== +idSysLocal::StartProcess +================== +*/ +void idSysLocal::StartProcess( const char *exePath, bool doexit ) { + TCHAR szPathOrig[_MAX_PATH]; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + + strncpy( szPathOrig, exePath, _MAX_PATH ); + + if( !CreateProcess( NULL, szPathOrig, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) ) { + common->Error( "Could not start process: '%s' ", szPathOrig ); + return; + } + + if ( doexit ) { + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); + } +} + +void *idSysLocal::CreateWindowEx( const char *className, const char *windowName, int style, int x, int y, int w, int h, void *parent, void *menu, void *instance, void *param, int extStyle ) { + return ::CreateWindowExA( extStyle, className, windowName, style, x, y, w, h, (HWND)parent, (HMENU)menu, (HINSTANCE)instance, param ); +} + +void *idSysLocal::GetDC( void *hWnd ) { return ::GetDC( (HWND)hWnd ); } +void idSysLocal::ReleaseDC( void *hWnd, void *hDC ) { ::ReleaseDC( (HWND)hWnd, (HDC)hDC ); } +void idSysLocal::ShowWindow( void *hWnd, int show ) { ::ShowWindow( (HWND)hWnd, show ); } +void idSysLocal::UpdateWindow( void *hWnd ) { ::UpdateWindow( (HWND)hWnd ); } +bool idSysLocal::IsWindowVisible( void *hWnd ) { return ::IsWindowVisible( (HWND)hWnd ) != FALSE; } +void idSysLocal::SetForegroundWindow( void *hWnd ) { ::SetForegroundWindow( (HWND)hWnd ); } +void idSysLocal::SetFocus( void *hWnd ) { ::SetFocus( (HWND)hWnd ); } +void idSysLocal::DestroyWindow( void *hWnd ) { ::DestroyWindow( (HWND)hWnd ); } + +/* +================== +Sys_SetFatalError +================== +*/ +void Sys_SetFatalError( const char *error ) { +} + +/* +================== +Sys_DoPreferences +================== +*/ +void Sys_DoPreferences( void ) { +} diff --git a/src/sys/win32/win_net.cpp b/src/sys/win32/win_net.cpp new file mode 100644 index 0000000..695c8a9 --- /dev/null +++ b/src/sys/win32/win_net.cpp @@ -0,0 +1,1194 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include +#include + +#include "win_local.h" + +static WSADATA winsockdata; +static bool winsockInitialized = false; +static bool usingSocks = false; + +idCVar net_ip( "net_ip", "localhost", CVAR_SYSTEM, "local IP address" ); +idCVar net_port( "net_port", "0", CVAR_SYSTEM | CVAR_INTEGER, "local IP port number" ); +idCVar net_forceLatency( "net_forceLatency", "0", CVAR_SYSTEM | CVAR_INTEGER, "milliseconds latency" ); +idCVar net_forceDrop( "net_forceDrop", "0", CVAR_SYSTEM | CVAR_INTEGER, "percentage packet loss" ); + +idCVar net_socksEnabled( "net_socksEnabled", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_BOOL, "" ); +idCVar net_socksServer( "net_socksServer", "", CVAR_SYSTEM | CVAR_ARCHIVE, "" ); +idCVar net_socksPort( "net_socksPort", "1080", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "" ); +idCVar net_socksUsername( "net_socksUsername", "", CVAR_SYSTEM | CVAR_ARCHIVE, "" ); +idCVar net_socksPassword( "net_socksPassword", "", CVAR_SYSTEM | CVAR_ARCHIVE, "" ); + + +static struct sockaddr socksRelayAddr; + +static SOCKET ip_socket; +static SOCKET socks_socket; +static char socksBuf[4096]; + +typedef struct { + unsigned long ip; + unsigned long mask; +} net_interface; + +#define MAX_INTERFACES 32 +int num_interfaces = 0; +net_interface netint[MAX_INTERFACES]; + +//============================================================================= + + +/* +==================== +NET_ErrorString +==================== +*/ +char *NET_ErrorString( void ) { + int code; + + code = WSAGetLastError(); + switch( code ) { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEDISCON: return "WSAEDISCON"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSASYSNOTREADY: return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED: return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN: return "WSATRY_AGAIN"; + case WSANO_RECOVERY: return "WSANO_RECOVERY"; + case WSANO_DATA: return "WSANO_DATA"; + default: return "NO ERROR"; + } +} + +/* +==================== +Net_NetadrToSockadr +==================== +*/ +void Net_NetadrToSockadr( const netadr_t *a, struct sockaddr *s ) { + memset( s, 0, sizeof(*s) ); + + if( a->type == NA_BROADCAST ) { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if( a->type == NA_IP || a->type == NA_LOOPBACK ) { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; + } + + ((struct sockaddr_in *)s)->sin_port = htons( (short)a->port ); +} + + +/* +==================== +Net_SockadrToNetadr +==================== +*/ +void Net_SockadrToNetadr( struct sockaddr *s, netadr_t *a ) { + unsigned int ip; + if (s->sa_family == AF_INET) { + ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + *(unsigned int *)&a->ip = ip; + a->port = htons( ((struct sockaddr_in *)s)->sin_port ); + // we store in network order, that loopback test is host order.. + ip = ntohl( ip ); + if ( ip == INADDR_LOOPBACK ) { + a->type = NA_LOOPBACK; + } else { + a->type = NA_IP; + } + } +} + +/* +============= +Net_ExtractPort +============= +*/ +static bool Net_ExtractPort( const char *src, char *buf, int bufsize, int *port ) { + char *p; + strncpy( buf, src, bufsize ); + p = buf; p += Min( bufsize - 1, (int)strlen( src ) ); *p = '\0'; + p = strchr( buf, ':' ); + if ( !p ) { + return false; + } + *p = '\0'; + *port = strtol( p+1, NULL, 10 ); + if ( errno == ERANGE ) { + return false; + } + return true; +} + +/* +============= +Net_StringToSockaddr +============= +*/ +static bool Net_StringToSockaddr( const char *s, struct sockaddr *sadr, bool doDNSResolve ) { + struct hostent *h; + char buf[256]; + int port; + + memset( sadr, 0, sizeof( *sadr ) ); + + ((struct sockaddr_in *)sadr)->sin_family = AF_INET; + ((struct sockaddr_in *)sadr)->sin_port = 0; + + if( s[0] >= '0' && s[0] <= '9' ) { + unsigned long ret = inet_addr(s); + if ( ret != INADDR_NONE ) { + *(int *)&((struct sockaddr_in *)sadr)->sin_addr = ret; + } else { + // check for port + if ( !Net_ExtractPort( s, buf, sizeof( buf ), &port ) ) { + return false; + } + ret = inet_addr( buf ); + if ( ret == INADDR_NONE ) { + return false; + } + *(int *)&((struct sockaddr_in *)sadr)->sin_addr = ret; + ((struct sockaddr_in *)sadr)->sin_port = htons( port ); + } + } else if ( doDNSResolve ) { + // try to remove the port first, otherwise the DNS gets confused into multiple timeouts + // failed or not failed, buf is expected to contain the appropriate host to resolve + if ( Net_ExtractPort( s, buf, sizeof( buf ), &port ) ) { + ((struct sockaddr_in *)sadr)->sin_port = htons( port ); + } + h = gethostbyname( buf ); + if ( h == 0 ) { + return false; + } + *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0]; + } + + return true; +} + +/* +==================== +NET_IPSocket +==================== +*/ +int NET_IPSocket( const char *net_interface, int port ) { + SOCKET newsocket; + struct sockaddr_in address; + unsigned long _true = 1; + int i = 1; + int err; + + if( net_interface ) { + common->DPrintf( "Opening IP socket: %s:%i\n", net_interface, port ); + } else { + common->DPrintf( "Opening IP socket: localhost:%i\n", port ); + } + + if( ( newsocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) { + err = WSAGetLastError(); + if( err != WSAEAFNOSUPPORT ) { + common->Printf( "WARNING: UDP_OpenSocket: socket: %s\n", NET_ErrorString() ); + } + return 0; + } + + // make it non-blocking + if( ioctlsocket( newsocket, FIONBIO, &_true ) == SOCKET_ERROR ) { + common->Printf( "WARNING: UDP_OpenSocket: ioctl FIONBIO: %s\n", NET_ErrorString() ); + return 0; + } + + // make it broadcast capable + if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i) ) == SOCKET_ERROR ) { + common->Printf( "WARNING: UDP_OpenSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString() ); + return 0; + } + + if( !net_interface || !net_interface[0] || !idStr::Icmp( net_interface, "localhost" ) ) { + address.sin_addr.s_addr = INADDR_ANY; + } + else { + Net_StringToSockaddr( net_interface, (struct sockaddr *)&address, true ); + } + + if( port == PORT_ANY ) { + address.sin_port = 0; + } + else { + address.sin_port = htons( (short)port ); + } + + address.sin_family = AF_INET; + + if( bind( newsocket, (const struct sockaddr *)&address, sizeof(address) ) == SOCKET_ERROR ) { + common->Printf( "WARNING: UDP_OpenSocket: bind: %s\n", NET_ErrorString() ); + closesocket( newsocket ); + return 0; + } + + return newsocket; +} + +/* +==================== +NET_OpenSocks +==================== +*/ +void NET_OpenSocks( int port ) { + struct sockaddr_in address; + int err; + struct hostent *h; + int len; + bool rfc1929; + unsigned char buf[64]; + + usingSocks = false; + + common->Printf( "Opening connection to SOCKS server.\n" ); + + if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) { + err = WSAGetLastError(); + common->Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() ); + return; + } + + h = gethostbyname( net_socksServer.GetString() ); + if ( h == NULL ) { + err = WSAGetLastError(); + common->Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() ); + return; + } + if ( h->h_addrtype != AF_INET ) { + common->Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" ); + return; + } + address.sin_family = AF_INET; + address.sin_addr.s_addr = *(int *)h->h_addr_list[0]; + address.sin_port = htons( (short)net_socksPort.GetInteger() ); + + if ( connect( socks_socket, (struct sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + common->Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() ); + return; + } + + // send socks authentication handshake + if ( *net_socksUsername.GetString() || *net_socksPassword.GetString() ) { + rfc1929 = true; + } + else { + rfc1929 = false; + } + + buf[0] = 5; // SOCKS version + // method count + if ( rfc1929 ) { + buf[1] = 2; + len = 4; + } + else { + buf[1] = 1; + len = 3; + } + buf[2] = 0; // method #1 - method id #00: no authentication + if ( rfc1929 ) { + buf[2] = 2; // method #2 - method id #02: username/password + } + if ( send( socks_socket, (const char *)buf, len, 0 ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + common->Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + err = WSAGetLastError(); + common->Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 5 ) { + common->Printf( "NET_OpenSocks: bad response\n" ); + return; + } + switch( buf[1] ) { + case 0: // no authentication + break; + case 2: // username/password authentication + break; + default: + common->Printf( "NET_OpenSocks: request denied\n" ); + return; + } + + // do username/password authentication if needed + if ( buf[1] == 2 ) { + int ulen; + int plen; + + // build the request + ulen = strlen( net_socksUsername.GetString() ); + plen = strlen( net_socksPassword.GetString() ); + + buf[0] = 1; // username/password authentication version + buf[1] = ulen; + if ( ulen ) { + memcpy( &buf[2], net_socksUsername.GetString(), ulen ); + } + buf[2 + ulen] = plen; + if ( plen ) { + memcpy( &buf[3 + ulen], net_socksPassword.GetString(), plen ); + } + + // send it + if ( send( socks_socket, (const char *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + common->Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + err = WSAGetLastError(); + common->Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 1 ) { + common->Printf( "NET_OpenSocks: bad response\n" ); + return; + } + if ( buf[1] != 0 ) { + common->Printf( "NET_OpenSocks: authentication failed\n" ); + return; + } + } + + // send the UDP associate request + buf[0] = 5; // SOCKS version + buf[1] = 3; // command: UDP associate + buf[2] = 0; // reserved + buf[3] = 1; // address type: IPV4 + *(int *)&buf[4] = INADDR_ANY; + *(short *)&buf[8] = htons( (short)port ); // port + if ( send( socks_socket, (const char *)buf, 10, 0 ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + common->Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if( len == SOCKET_ERROR ) { + err = WSAGetLastError(); + common->Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if( len < 2 || buf[0] != 5 ) { + common->Printf( "NET_OpenSocks: bad response\n" ); + return; + } + // check completion code + if( buf[1] != 0 ) { + common->Printf( "NET_OpenSocks: request denied: %i\n", buf[1] ); + return; + } + if( buf[3] != 1 ) { + common->Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] ); + return; + } + ((struct sockaddr_in *)&socksRelayAddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&socksRelayAddr)->sin_addr.s_addr = *(int *)&buf[4]; + ((struct sockaddr_in *)&socksRelayAddr)->sin_port = *(short *)&buf[8]; + memset( ((struct sockaddr_in *)&socksRelayAddr)->sin_zero, 0, 8 ); + + usingSocks = true; +} + +/* +================== +Net_WaitForUDPPacket +================== +*/ +bool Net_WaitForUDPPacket( int netSocket, int timeout ) { + int ret; + fd_set set; + struct timeval tv; + + if ( !netSocket ) { + return false; + } + + if ( timeout <= 0 ) { + return true; + } + + FD_ZERO( &set ); + FD_SET( netSocket, &set ); + + tv.tv_sec = 0; + tv.tv_usec = timeout * 1000; + + ret = select( netSocket + 1, &set, NULL, NULL, &tv ); + + if ( ret == -1 ) { + common->DPrintf( "Net_WaitForUPDPacket select(): %s\n", strerror( errno ) ); + return false; + } + + // timeout with no data + if ( ret == 0 ) { + return false; + } + + return true; +} + +/* +================== +Net_GetUDPPacket +================== +*/ +bool Net_GetUDPPacket( int netSocket, netadr_t &net_from, char *data, int &size, int maxSize ) { + int ret; + struct sockaddr from; + int fromlen; + int err; + + if( !netSocket ) { + return false; + } + + fromlen = sizeof(from); + ret = recvfrom( netSocket, data, maxSize, 0, (struct sockaddr *)&from, &fromlen ); + if ( ret == SOCKET_ERROR ) { + err = WSAGetLastError(); + + if( err == WSAEWOULDBLOCK || err == WSAECONNRESET ) { + return false; + } + char buf[1024]; + sprintf( buf, "Net_GetUDPPacket: %s\n", NET_ErrorString() ); + OutputDebugString( buf ); + return false; + } + + if ( netSocket == ip_socket ) { + memset( ((struct sockaddr_in *)&from)->sin_zero, 0, 8 ); + } + + if ( usingSocks && netSocket == ip_socket && memcmp( &from, &socksRelayAddr, fromlen ) == 0 ) { + if ( ret < 10 || data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 1 ) { + return false; + } + net_from.type = NA_IP; + net_from.ip[0] = data[4]; + net_from.ip[1] = data[5]; + net_from.ip[2] = data[6]; + net_from.ip[3] = data[7]; + net_from.port = *(short *)&data[8]; + memmove( data, &data[10], ret - 10 ); + } else { + Net_SockadrToNetadr( &from, &net_from ); + } + + if( ret == maxSize ) { + char buf[1024]; + sprintf( buf, "Net_GetUDPPacket: oversize packet from %s\n", Sys_NetAdrToString( net_from ) ); + OutputDebugString( buf ); + return false; + } + + size = ret; + + return true; +} + +/* +================== +Net_SendUDPPacket +================== +*/ +void Net_SendUDPPacket( int netSocket, int length, const void *data, const netadr_t to ) { + int ret; + struct sockaddr addr; + + if( !netSocket ) { + return; + } + + Net_NetadrToSockadr( &to, &addr ); + + if( usingSocks && to.type == NA_IP ) { + socksBuf[0] = 0; // reserved + socksBuf[1] = 0; + socksBuf[2] = 0; // fragment (not fragmented) + socksBuf[3] = 1; // address type: IPV4 + *(int *)&socksBuf[4] = ((struct sockaddr_in *)&addr)->sin_addr.s_addr; + *(short *)&socksBuf[8] = ((struct sockaddr_in *)&addr)->sin_port; + memcpy( &socksBuf[10], data, length ); + ret = sendto( netSocket, socksBuf, length+10, 0, &socksRelayAddr, sizeof(socksRelayAddr) ); + } else { + ret = sendto( netSocket, (const char *)data, length, 0, &addr, sizeof(addr) ); + } + if( ret == SOCKET_ERROR ) { + int err = WSAGetLastError(); + + // wouldblock is silent + if( err == WSAEWOULDBLOCK ) { + return; + } + + // some PPP links do not allow broadcasts and return an error + if( ( err == WSAEADDRNOTAVAIL ) && ( to.type == NA_BROADCAST ) ) { + return; + } + + char buf[1024]; + sprintf( buf, "Net_SendUDPPacket: %s\n", NET_ErrorString() ); + OutputDebugString( buf ); + } +} + +/* +==================== +Sys_InitNetworking +==================== +*/ +void Sys_InitNetworking( void ) { + int r; + + r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata ); + if( r ) { + common->Printf( "WARNING: Winsock initialization failed, returned %d\n", r ); + return; + } + + winsockInitialized = true; + common->Printf( "Winsock Initialized\n" ); + + PIP_ADAPTER_INFO pAdapterInfo; + PIP_ADAPTER_INFO pAdapter = NULL; + DWORD dwRetVal = 0; + PIP_ADDR_STRING pIPAddrString; + ULONG ulOutBufLen; + bool foundloopback; + + num_interfaces = 0; + foundloopback = false; + + pAdapterInfo = (IP_ADAPTER_INFO *)malloc( sizeof( IP_ADAPTER_INFO ) ); + if( !pAdapterInfo ) { + common->FatalError( "Sys_InitNetworking: Couldn't malloc( %d )", sizeof( IP_ADAPTER_INFO ) ); + } + ulOutBufLen = sizeof( IP_ADAPTER_INFO ); + + // Make an initial call to GetAdaptersInfo to get + // the necessary size into the ulOutBufLen variable + if( GetAdaptersInfo( pAdapterInfo, &ulOutBufLen ) == ERROR_BUFFER_OVERFLOW ) { + free( pAdapterInfo ); + pAdapterInfo = (IP_ADAPTER_INFO *)malloc( ulOutBufLen ); + if( !pAdapterInfo ) { + common->FatalError( "Sys_InitNetworking: Couldn't malloc( %ld )", ulOutBufLen ); + } + } + + if( ( dwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen) ) != NO_ERROR ) { + // happens if you have no network connection + common->Printf( "Sys_InitNetworking: GetAdaptersInfo failed (%ld).\n", dwRetVal ); + } else { + pAdapter = pAdapterInfo; + while( pAdapter ) { + common->Printf( "Found interface: %s %s - ", pAdapter->AdapterName, pAdapter->Description ); + pIPAddrString = &pAdapter->IpAddressList; + while( pIPAddrString ) { + unsigned long ip_a, ip_m; + if( !idStr::Icmp( "127.0.0.1", pIPAddrString->IpAddress.String ) ) { + foundloopback = true; + } + ip_a = ntohl( inet_addr( pIPAddrString->IpAddress.String ) ); + ip_m = ntohl( inet_addr( pIPAddrString->IpMask.String ) ); + //skip null netmasks + if( !ip_m ) { + common->Printf( "%s NULL netmask - skipped\n", pIPAddrString->IpAddress.String ); + pIPAddrString = pIPAddrString->Next; + continue; + } + common->Printf( "%s/%s\n", pIPAddrString->IpAddress.String, pIPAddrString->IpMask.String ); + netint[num_interfaces].ip = ip_a; + netint[num_interfaces].mask = ip_m; + num_interfaces++; + if( num_interfaces >= MAX_INTERFACES ) { + common->Printf( "Sys_InitNetworking: MAX_INTERFACES(%d) hit.\n", MAX_INTERFACES ); + free( pAdapterInfo ); + return; + } + pIPAddrString = pIPAddrString->Next; + } + pAdapter = pAdapter->Next; + } + } + // for some retarded reason, win32 doesn't count loopback as an adapter... + if( !foundloopback && num_interfaces < MAX_INTERFACES ) { + common->Printf( "Sys_InitNetworking: adding loopback interface\n" ); + netint[num_interfaces].ip = ntohl( inet_addr( "127.0.0.1" ) ); + netint[num_interfaces].mask = ntohl( inet_addr( "255.0.0.0" ) ); + num_interfaces++; + } + free( pAdapterInfo ); +} + + +/* +==================== +Sys_ShutdownNetworking +==================== +*/ +void Sys_ShutdownNetworking( void ) { + if ( !winsockInitialized ) { + return; + } + WSACleanup(); + winsockInitialized = false; +} + +/* +============= +Sys_StringToNetAdr +============= +*/ +bool Sys_StringToNetAdr( const char *s, netadr_t *a, bool doDNSResolve ) { + struct sockaddr sadr; + + if ( !Net_StringToSockaddr( s, &sadr, doDNSResolve ) ) { + return false; + } + + Net_SockadrToNetadr( &sadr, a ); + return true; +} + +/* +============= +Sys_NetAdrToString +============= +*/ +const char *Sys_NetAdrToString( const netadr_t a ) { + static int index = 0; + static char buf[ 4 ][ 64 ]; // flip/flop + char *s; + + s = buf[index]; + index = (index + 1) & 3; + + if ( a.type == NA_LOOPBACK ) { + if ( a.port ) { + idStr::snPrintf( s, 64, "localhost:%i", a.port ); + } else { + idStr::snPrintf( s, 64, "localhost" ); + } + } else if ( a.type == NA_IP ) { + idStr::snPrintf( s, 64, "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], a.port ); + } + return s; +} + +/* +================== +Sys_IsLANAddress +================== +*/ +bool Sys_IsLANAddress( const netadr_t adr ) { +#if ID_NOLANADDRESS + common->Printf( "Sys_IsLANAddress: ID_NOLANADDRESS\n" ); + return false; +#endif + if( adr.type == NA_LOOPBACK ) { + return true; + } + + if( adr.type != NA_IP ) { + return false; + } + + if( num_interfaces ) { + int i; + unsigned long *p_ip; + unsigned long ip; + p_ip = (unsigned long *)&adr.ip[0]; + ip = ntohl( *p_ip ); + + for( i=0; i < num_interfaces; i++ ) { + if( ( netint[i].ip & netint[i].mask ) == ( ip & netint[i].mask ) ) { + return true; + } + } + } + return false; +} + +/* +=================== +Sys_CompareNetAdrBase + +Compares without the port +=================== +*/ +bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b ) { + if ( a.type != b.type ) { + return false; + } + + if ( a.type == NA_LOOPBACK ) { + return true; + } + + if ( a.type == NA_IP ) { + if ( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] ) { + return true; + } + return false; + } + + common->Printf( "Sys_CompareNetAdrBase: bad address type\n" ); + return false; +} + +//============================================================================= + + +#define MAX_UDP_MSG_SIZE 1400 + +typedef struct udpMsg_s { + byte data[MAX_UDP_MSG_SIZE]; + netadr_t address; + int size; + int time; + struct udpMsg_s * next; +} udpMsg_t; + +class idUDPLag { +public: + idUDPLag( void ); + ~idUDPLag( void ); + + udpMsg_t * sendFirst; + udpMsg_t * sendLast; + udpMsg_t * recieveFirst; + udpMsg_t * recieveLast; + idBlockAlloc udpMsgAllocator; +}; + +idUDPLag::idUDPLag( void ) { + sendFirst = sendLast = recieveFirst = recieveLast = NULL; +} + +idUDPLag::~idUDPLag( void ) { + udpMsgAllocator.Shutdown(); +} + +idUDPLag *udpPorts[65536]; + +/* +================== +idPort::idPort +================== +*/ +idPort::idPort() { + netSocket = 0; + port = 0; + silent = false; +} + +/* +================== +idPort::~idPort +================== +*/ +idPort::~idPort() { + Close(); +} + +/* +================== +InitForPort +================== +*/ +bool idPort::InitForPort( int portNumber ) { + netSocket = NET_IPSocket( net_ip.GetString(), portNumber ); + if ( netSocket <= 0 ) { + netSocket = 0; + port = 0; + return false; + } + port = portNumber; + if ( portNumber == PORT_ANY ) { + struct sockaddr_in address; + int len = sizeof( address ); + getsockname( netSocket, (sockaddr *)&address, &len ); + port = ntohs( address.sin_port ); + } + +#if 0 + if ( net_socksEnabled.GetBool() ) { + NET_OpenSocks( portNumber ); + } +#endif + + udpPorts[ port ] = new idUDPLag; + + return true; +} + +/* +================== +idPort::Close +================== +*/ +void idPort::Close() { + if ( netSocket ) { + if ( udpPorts[ port ] ) { + delete udpPorts[ port ]; + udpPorts[ port ] = NULL; + } + closesocket( netSocket ); + netSocket = 0; + port = 0; + } +} + +/* +================== +idPort::GetPacket +================== +*/ +bool idPort::GetPacket( netadr_t &from, void *data, int &size, int maxSize ) { + udpMsg_t *msg; + bool ret; + + while( 1 ) { + + ret = Net_GetUDPPacket( netSocket, from, (char *)data, size, maxSize ); + if ( !ret ) { + break; + } + + if ( net_forceDrop.GetInteger() > 0 ) { + if ( rand() < net_forceDrop.GetInteger() * RAND_MAX / 100 ) { + continue; + } + } + + packetsRead++; + bytesRead += size; + + if ( net_forceLatency.GetInteger() > 0 ) { + + assert( size <= MAX_UDP_MSG_SIZE ); + msg = udpPorts[ port ]->udpMsgAllocator.Alloc(); + memcpy( msg->data, data, size ); + msg->size = size; + msg->address = from; + msg->time = Sys_Milliseconds(); + msg->next = NULL; + if ( udpPorts[ port ]->recieveLast ) { + udpPorts[ port ]->recieveLast->next = msg; + } else { + udpPorts[ port ]->recieveFirst = msg; + } + udpPorts[ port ]->recieveLast = msg; + } else { + break; + } + } + + if ( net_forceLatency.GetInteger() > 0 || ( udpPorts[ port ] && udpPorts[ port ]->recieveFirst ) ) { + + msg = udpPorts[ port ]->recieveFirst; + if ( msg && msg->time <= Sys_Milliseconds() - net_forceLatency.GetInteger() ) { + memcpy( data, msg->data, msg->size ); + size = msg->size; + from = msg->address; + udpPorts[ port ]->recieveFirst = udpPorts[ port ]->recieveFirst->next; + if ( !udpPorts[ port ]->recieveFirst ) { + udpPorts[ port ]->recieveLast = NULL; + } + udpPorts[ port ]->udpMsgAllocator.Free( msg ); + return true; + } + return false; + + } else { + return ret; + } +} + +/* +================== +idPort::GetPacketBlocking +================== +*/ +bool idPort::GetPacketBlocking( netadr_t &from, void *data, int &size, int maxSize, int timeout ) { + + Net_WaitForUDPPacket( netSocket, timeout ); + + if ( GetPacket( from, data, size, maxSize ) ) { + return true; + } + + return false; +} + +/* +================== +idPort::SendPacket +================== +*/ +void idPort::SendPacket( const netadr_t to, const void *data, int size ) { + udpMsg_t *msg; + + if ( to.type == NA_BAD ) { + common->Warning( "idPort::SendPacket: bad address type NA_BAD - ignored" ); + return; + } + + packetsWritten++; + bytesWritten += size; + + if ( net_forceDrop.GetInteger() > 0 ) { + if ( rand() < net_forceDrop.GetInteger() * RAND_MAX / 100 ) { + return; + } + } + + if ( net_forceLatency.GetInteger() > 0 || ( udpPorts[ port ] && udpPorts[ port ]->sendFirst ) ) { + + assert( size <= MAX_UDP_MSG_SIZE ); + msg = udpPorts[ port ]->udpMsgAllocator.Alloc(); + memcpy( msg->data, data, size ); + msg->size = size; + msg->address = to; + msg->time = Sys_Milliseconds(); + msg->next = NULL; + if ( udpPorts[ port ]->sendLast ) { + udpPorts[ port ]->sendLast->next = msg; + } else { + udpPorts[ port ]->sendFirst = msg; + } + udpPorts[ port ]->sendLast = msg; + + for ( msg = udpPorts[ port ]->sendFirst; msg && msg->time <= Sys_Milliseconds() - net_forceLatency.GetInteger(); msg = udpPorts[ port ]->sendFirst ) { + Net_SendUDPPacket( netSocket, msg->size, msg->data, msg->address ); + udpPorts[ port ]->sendFirst = udpPorts[ port ]->sendFirst->next; + if ( !udpPorts[ port ]->sendFirst ) { + udpPorts[ port ]->sendLast = NULL; + } + udpPorts[ port ]->udpMsgAllocator.Free( msg ); + } + + } else { + Net_SendUDPPacket( netSocket, size, data, to ); + } +} + + +//============================================================================= + +/* +================== +idTCP::idTCP +================== +*/ +idTCP::idTCP() { + fd = 0; + memset( &address, 0, sizeof( address ) ); +} + +/* +================== +idTCP::~idTCP +================== +*/ +idTCP::~idTCP() { + Close(); +} + +/* +================== +idTCP::Init +================== +*/ +bool idTCP::Init( const char *host, short port ) { + unsigned long _true = 1; + struct sockaddr sadr; + + if ( !Sys_StringToNetAdr( host, &address, true ) ) { + common->Printf( "Couldn't resolve server name \"%s\"\n", host ); + return false; + } + address.type = NA_IP; + if ( !address.port ) { + address.port = port; + } + common->Printf( "\"%s\" resolved to %i.%i.%i.%i:%i\n", host, + address.ip[0], address.ip[1], address.ip[2], address.ip[3], address.port ); + Net_NetadrToSockadr( &address, &sadr ); + + if ( fd ) { + common->Warning( "idTCP::Init: already initialized?" ); + } + + if ( ( fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == INVALID_SOCKET ) { + fd = 0; + common->Printf( "ERROR: idTCP::Init: socket: %s\n", NET_ErrorString() ); + return false; + } + + if ( connect( fd, &sadr, sizeof(sadr)) == SOCKET_ERROR ) { + common->Printf( "ERROR: idTCP::Init: connect: %s\n", NET_ErrorString() ); + closesocket( fd ); + fd = 0; + return false; + } + + // make it non-blocking + if( ioctlsocket( fd, FIONBIO, &_true ) == SOCKET_ERROR ) { + common->Printf( "ERROR: idTCP::Init: ioctl FIONBIO: %s\n", NET_ErrorString() ); + closesocket( fd ); + fd = 0; + return false; + } + + common->DPrintf( "Opened TCP connection\n" ); + return true; +} + +/* +================== +idTCP::Close +================== +*/ +void idTCP::Close() { + if ( fd ) { + closesocket( fd ); + } + fd = 0; +} + +/* +================== +idTCP::Read +================== +*/ +int idTCP::Read( void *data, int size ) { + int nbytes; + + if ( !fd ) { + common->Printf("idTCP::Read: not initialized\n"); + return -1; + } + + if ( ( nbytes = recv( fd, (char *)data, size, 0 ) ) == SOCKET_ERROR ) { + if ( WSAGetLastError() == WSAEWOULDBLOCK ) { + return 0; + } + common->Printf( "ERROR: idTCP::Read: %s\n", NET_ErrorString() ); + Close(); + return -1; + } + + // a successful read of 0 bytes indicates remote has closed the connection + if ( nbytes == 0 ) { + common->DPrintf( "idTCP::Read: read 0 bytes - assume connection closed\n" ); + return -1; + } + + return nbytes; +} + +/* +================== +idTCP::Write +================== +*/ +int idTCP::Write( const void *data, int size ) { + int nbytes; + + if ( !fd ) { + common->Printf("idTCP::Write: not initialized\n"); + return -1; + } + + if ( ( nbytes = send( fd, (char *)data, size, 0 ) ) == SOCKET_ERROR ) { + common->Printf( "ERROR: idTCP::Write: %s\n", NET_ErrorString() ); + Close(); + return -1; + } + + return nbytes; +} diff --git a/src/sys/win32/win_qgl.cpp b/src/sys/win32/win_qgl.cpp new file mode 100644 index 0000000..10a2f4b --- /dev/null +++ b/src/sys/win32/win_qgl.cpp @@ -0,0 +1,2825 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +/* +** QGL_WIN.C +** +** This file implements the operating system binding of GL to QGL function +** pointers. When doing a port of Doom you must implement the following +** two functions: +** +** QGL_Init() - loads libraries, assigns function pointers, etc. +** QGL_Shutdown() - unloads libraries, NULLs function pointers +*/ +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include +#include "win_local.h" +#include "../../renderer/tr_local.h" + + +int ( WINAPI * qwglChoosePixelFormat )(HDC, CONST PIXELFORMATDESCRIPTOR *); +int ( WINAPI * qwglDescribePixelFormat) (HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); +int ( WINAPI * qwglGetPixelFormat)(HDC); +BOOL ( WINAPI * qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR *); +BOOL ( WINAPI * qwglSwapBuffers)(HDC); + +// WGL_ARB_pbuffer / WGL_ARB_render_texture data symbols owned by win_qgl.obj. +BOOL ( WINAPI * qwglBindTexImageARB)(HPBUFFERARB, int); +BOOL ( WINAPI * qwglChoosePixelFormatARB)(HDC, const int *, const FLOAT *, UINT, int *, UINT *); +HPBUFFERARB ( WINAPI * qwglCreatePbufferARB)(HDC, int, int, int, const int *); +BOOL ( WINAPI * qwglDestroyPbufferARB)(HPBUFFERARB); +HDC ( WINAPI * qwglGetPbufferDCARB)(HPBUFFERARB); +int ( WINAPI * qwglReleasePbufferDCARB)(HPBUFFERARB, HDC); +BOOL ( WINAPI * qwglReleaseTexImageARB)(HPBUFFERARB, int); +BOOL ( WINAPI * qwglSetPbufferAttribARB)(HPBUFFERARB, const int *); + +BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +HGLRC ( WINAPI * qwglCreateContext)(HDC); +HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +HDC ( WINAPI * qwglGetCurrentDC)(VOID); +PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); +BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF *); +int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF *); +BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + +void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +void ( APIENTRY * qglArrayElement )(GLint i); +void ( APIENTRY * qglBegin )(GLenum mode); +void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +void ( APIENTRY * qglCallList )(GLuint list); +void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +void ( APIENTRY * qglClear )(GLbitfield mask); +void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +void ( APIENTRY * qglClearDepth )(GLclampd depth); +void ( APIENTRY * qglClearIndex )(GLfloat c); +void ( APIENTRY * qglClearStencil )(GLint s); +void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +void ( APIENTRY * qglColor3bv )(const GLbyte *v); +void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +void ( APIENTRY * qglColor3dv )(const GLdouble *v); +void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +void ( APIENTRY * qglColor3fv )(const GLfloat *v); +void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +void ( APIENTRY * qglColor3iv )(const GLint *v); +void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +void ( APIENTRY * qglColor3sv )(const GLshort *v); +void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +void ( APIENTRY * qglColor3uiv )(const GLuint *v); +void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +void ( APIENTRY * qglColor3usv )(const GLushort *v); +void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +void ( APIENTRY * qglColor4bv )(const GLbyte *v); +void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +void ( APIENTRY * qglColor4dv )(const GLdouble *v); +void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglColor4fv )(const GLfloat *v); +void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +void ( APIENTRY * qglColor4iv )(const GLint *v); +void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +void ( APIENTRY * qglColor4sv )(const GLshort *v); +void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +void ( APIENTRY * qglColor4uiv )(const GLuint *v); +void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +void ( APIENTRY * qglColor4usv )(const GLushort *v); +void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglCullFace )(GLenum mode); +void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +void ( APIENTRY * qglDepthFunc )(GLenum func); +void ( APIENTRY * qglDepthMask )(GLboolean flag); +void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +void ( APIENTRY * qglDisable )(GLenum cap); +void ( APIENTRY * qglDisableClientState )(GLenum array); +void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +void ( APIENTRY * qglDrawBuffer )(GLenum mode); +void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +void ( APIENTRY * qglEnable )(GLenum cap); +void ( APIENTRY * qglEnableClientState )(GLenum array); +void ( APIENTRY * qglEnd )(void); +void ( APIENTRY * qglEndList )(void); +void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +void ( APIENTRY * qglEvalPoint1 )(GLint i); +void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +void ( APIENTRY * qglFinish )(void); +void ( APIENTRY * qglFlush )(void); +void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglFrontFace )(GLenum mode); +void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * qglGenLists )(GLsizei range); +void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * qglGetError )(void); +void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat *values); +void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint *values); +void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort *values); +void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +void ( APIENTRY * qglIndexMask )(GLuint mask); +void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglIndexd )(GLdouble c); +void ( APIENTRY * qglIndexdv )(const GLdouble *c); +void ( APIENTRY * qglIndexf )(GLfloat c); +void ( APIENTRY * qglIndexfv )(const GLfloat *c); +void ( APIENTRY * qglIndexi )(GLint c); +void ( APIENTRY * qglIndexiv )(const GLint *c); +void ( APIENTRY * qglIndexs )(GLshort c); +void ( APIENTRY * qglIndexsv )(const GLshort *c); +void ( APIENTRY * qglIndexub )(GLubyte c); +void ( APIENTRY * qglIndexubv )(const GLubyte *c); +void ( APIENTRY * qglInitNames )(void); +void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * qglIsList )(GLuint list); +GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +void ( APIENTRY * qglLineWidth )(GLfloat width); +void ( APIENTRY * qglListBase )(GLuint base); +void ( APIENTRY * qglLoadIdentity )(void); +void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +void ( APIENTRY * qglLoadName )(GLuint name); +void ( APIENTRY * qglLogicOp )(GLenum opcode); +void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +void ( APIENTRY * qglMatrixMode )(GLenum mode); +void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +void ( APIENTRY * qglNormal3iv )(const GLint *v); +void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +void ( APIENTRY * qglNormal3sv )(const GLshort *v); +void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +void ( APIENTRY * qglPassThrough )(GLfloat token); +void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +void ( APIENTRY * qglPointSize )(GLfloat size); +void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +void ( APIENTRY * qglPopAttrib )(void); +void ( APIENTRY * qglPopClientAttrib )(void); +void ( APIENTRY * qglPopMatrix )(void); +void ( APIENTRY * qglPopName )(void); +void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushMatrix )(void); +void ( APIENTRY * qglPushName )(GLuint name); +void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +void ( APIENTRY * qglReadBuffer )(GLenum mode); +void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * qglRenderMode )(GLenum mode); +void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +void ( APIENTRY * qglShadeModel )(GLenum mode); +void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +void ( APIENTRY * qglStencilMask )(GLuint mask); +void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +void ( APIENTRY * qglTexCoord1d )(GLdouble s); +void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord1f )(GLfloat s); +void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord1i )(GLint s); +void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +void ( APIENTRY * qglTexCoord1s )(GLshort s); +void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +void ( APIENTRY * qglVertex2iv )(const GLint *v); +void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +void ( APIENTRY * qglVertex2sv )(const GLshort *v); +void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglVertex3iv )(const GLint *v); +void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglVertex3sv )(const GLshort *v); +void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglVertex4iv )(const GLint *v); +void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglVertex4sv )(const GLshort *v); +void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + + + +static void ( APIENTRY * dllAccum )(GLenum op, GLfloat value); +static void ( APIENTRY * dllAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * dllAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +static void ( APIENTRY * dllArrayElement )(GLint i); +static void ( APIENTRY * dllBegin )(GLenum mode); +static void ( APIENTRY * dllBindTexture )(GLenum target, GLuint texture); +static void ( APIENTRY * dllBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +static void ( APIENTRY * dllBlendFunc )(GLenum sfactor, GLenum dfactor); +static void ( APIENTRY * dllCallList )(GLuint list); +static void ( APIENTRY * dllCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +static void ( APIENTRY * dllClear )(GLbitfield mask); +static void ( APIENTRY * dllClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +static void ( APIENTRY * dllClearDepth )(GLclampd depth); +static void ( APIENTRY * dllClearIndex )(GLfloat c); +static void ( APIENTRY * dllClearStencil )(GLint s); +static void ( APIENTRY * dllClipPlane )(GLenum plane, const GLdouble *equation); +static void ( APIENTRY * dllColor3b )(GLbyte red, GLbyte green, GLbyte blue); +static void ( APIENTRY * dllColor3bv )(const GLbyte *v); +static void ( APIENTRY * dllColor3d )(GLdouble red, GLdouble green, GLdouble blue); +static void ( APIENTRY * dllColor3dv )(const GLdouble *v); +static void ( APIENTRY * dllColor3f )(GLfloat red, GLfloat green, GLfloat blue); +static void ( APIENTRY * dllColor3fv )(const GLfloat *v); +static void ( APIENTRY * dllColor3i )(GLint red, GLint green, GLint blue); +static void ( APIENTRY * dllColor3iv )(const GLint *v); +static void ( APIENTRY * dllColor3s )(GLshort red, GLshort green, GLshort blue); +static void ( APIENTRY * dllColor3sv )(const GLshort *v); +static void ( APIENTRY * dllColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +static void ( APIENTRY * dllColor3ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor3ui )(GLuint red, GLuint green, GLuint blue); +static void ( APIENTRY * dllColor3uiv )(const GLuint *v); +static void ( APIENTRY * dllColor3us )(GLushort red, GLushort green, GLushort blue); +static void ( APIENTRY * dllColor3usv )(const GLushort *v); +static void ( APIENTRY * dllColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +static void ( APIENTRY * dllColor4bv )(const GLbyte *v); +static void ( APIENTRY * dllColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +static void ( APIENTRY * dllColor4dv )(const GLdouble *v); +static void ( APIENTRY * dllColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllColor4fv )(const GLfloat *v); +static void ( APIENTRY * dllColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +static void ( APIENTRY * dllColor4iv )(const GLint *v); +static void ( APIENTRY * dllColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +static void ( APIENTRY * dllColor4sv )(const GLshort *v); +static void ( APIENTRY * dllColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +static void ( APIENTRY * dllColor4ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +static void ( APIENTRY * dllColor4uiv )(const GLuint *v); +static void ( APIENTRY * dllColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +static void ( APIENTRY * dllColor4usv )(const GLushort *v); +static void ( APIENTRY * dllColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +static void ( APIENTRY * dllColorMaterial )(GLenum face, GLenum mode); +static void ( APIENTRY * dllColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +static void ( APIENTRY * dllCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +static void ( APIENTRY * dllCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +static void ( APIENTRY * dllCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +static void ( APIENTRY * dllCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllCullFace )(GLenum mode); +static void ( APIENTRY * dllDeleteLists )(GLuint list, GLsizei range); +static void ( APIENTRY * dllDeleteTextures )(GLsizei n, const GLuint *textures); +static void ( APIENTRY * dllDepthFunc )(GLenum func); +static void ( APIENTRY * dllDepthMask )(GLboolean flag); +static void ( APIENTRY * dllDepthRange )(GLclampd zNear, GLclampd zFar); +static void ( APIENTRY * dllDisable )(GLenum cap); +static void ( APIENTRY * dllDisableClientState )(GLenum array); +static void ( APIENTRY * dllDrawArrays )(GLenum mode, GLint first, GLsizei count); +static void ( APIENTRY * dllDrawBuffer )(GLenum mode); +static void ( APIENTRY * dllDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +static void ( APIENTRY * dllDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllEdgeFlag )(GLboolean flag); +static void ( APIENTRY * dllEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllEdgeFlagv )(const GLboolean *flag); +static void ( APIENTRY * dllEnable )(GLenum cap); +static void ( APIENTRY * dllEnableClientState )(GLenum array); +static void ( APIENTRY * dllEnd )(void); +static void ( APIENTRY * dllEndList )(void); +static void ( APIENTRY * dllEvalCoord1d )(GLdouble u); +static void ( APIENTRY * dllEvalCoord1dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord1f )(GLfloat u); +static void ( APIENTRY * dllEvalCoord1fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalCoord2d )(GLdouble u, GLdouble v); +static void ( APIENTRY * dllEvalCoord2dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord2f )(GLfloat u, GLfloat v); +static void ( APIENTRY * dllEvalCoord2fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +static void ( APIENTRY * dllEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +static void ( APIENTRY * dllEvalPoint1 )(GLint i); +static void ( APIENTRY * dllEvalPoint2 )(GLint i, GLint j); +static void ( APIENTRY * dllFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +static void ( APIENTRY * dllFinish )(void); +static void ( APIENTRY * dllFlush )(void); +static void ( APIENTRY * dllFogf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllFogfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllFogi )(GLenum pname, GLint param); +static void ( APIENTRY * dllFogiv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllFrontFace )(GLenum mode); +static void ( APIENTRY * dllFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * dllGenLists )(GLsizei range); +static void ( APIENTRY * dllGenTextures )(GLsizei n, GLuint *textures); +static void ( APIENTRY * dllGetBooleanv )(GLenum pname, GLboolean *params); +static void ( APIENTRY * dllGetClipPlane )(GLenum plane, GLdouble *equation); +static void ( APIENTRY * dllGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * dllGetError )(void); +static void ( APIENTRY * dllGetFloatv )(GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetIntegerv )(GLenum pname, GLint *params); +static void ( APIENTRY * dllGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetLightiv )(GLenum light, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetMapdv )(GLenum target, GLenum query, GLdouble *v); +static void ( APIENTRY * dllGetMapfv )(GLenum target, GLenum query, GLfloat *v); +static void ( APIENTRY * dllGetMapiv )(GLenum target, GLenum query, GLint *v); +static void ( APIENTRY * dllGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetPixelMapfv )(GLenum map, GLfloat *values); +static void ( APIENTRY * dllGetPixelMapuiv )(GLenum map, GLuint *values); +static void ( APIENTRY * dllGetPixelMapusv )(GLenum map, GLushort *values); +static void ( APIENTRY * dllGetPointerv )(GLenum pname, GLvoid* *params); +static void ( APIENTRY * dllGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * dllGetString )(GLenum name); +static void ( APIENTRY * dllGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +static void ( APIENTRY * dllGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllHint )(GLenum target, GLenum mode); +static void ( APIENTRY * dllIndexMask )(GLuint mask); +static void ( APIENTRY * dllIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllIndexd )(GLdouble c); +static void ( APIENTRY * dllIndexdv )(const GLdouble *c); +static void ( APIENTRY * dllIndexf )(GLfloat c); +static void ( APIENTRY * dllIndexfv )(const GLfloat *c); +static void ( APIENTRY * dllIndexi )(GLint c); +static void ( APIENTRY * dllIndexiv )(const GLint *c); +static void ( APIENTRY * dllIndexs )(GLshort c); +static void ( APIENTRY * dllIndexsv )(const GLshort *c); +static void ( APIENTRY * dllIndexub )(GLubyte c); +static void ( APIENTRY * dllIndexubv )(const GLubyte *c); +static void ( APIENTRY * dllInitNames )(void); +static void ( APIENTRY * dllInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * dllIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * dllIsList )(GLuint list); +GLboolean ( APIENTRY * dllIsTexture )(GLuint texture); +static void ( APIENTRY * dllLightModelf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightModelfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLightModeli )(GLenum pname, GLint param); +static void ( APIENTRY * dllLightModeliv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllLightf )(GLenum light, GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightfv )(GLenum light, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLighti )(GLenum light, GLenum pname, GLint param); +static void ( APIENTRY * dllLightiv )(GLenum light, GLenum pname, const GLint *params); +static void ( APIENTRY * dllLineStipple )(GLint factor, GLushort pattern); +static void ( APIENTRY * dllLineWidth )(GLfloat width); +static void ( APIENTRY * dllListBase )(GLuint base); +static void ( APIENTRY * dllLoadIdentity )(void); +static void ( APIENTRY * dllLoadMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllLoadMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllLoadName )(GLuint name); +static void ( APIENTRY * dllLogicOp )(GLenum opcode); +static void ( APIENTRY * dllMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +static void ( APIENTRY * dllMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +static void ( APIENTRY * dllMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +static void ( APIENTRY * dllMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +static void ( APIENTRY * dllMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +static void ( APIENTRY * dllMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +static void ( APIENTRY * dllMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +static void ( APIENTRY * dllMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +static void ( APIENTRY * dllMaterialf )(GLenum face, GLenum pname, GLfloat param); +static void ( APIENTRY * dllMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllMateriali )(GLenum face, GLenum pname, GLint param); +static void ( APIENTRY * dllMaterialiv )(GLenum face, GLenum pname, const GLint *params); +static void ( APIENTRY * dllMatrixMode )(GLenum mode); +static void ( APIENTRY * dllMultMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllMultMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllNewList )(GLuint list, GLenum mode); +static void ( APIENTRY * dllNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +static void ( APIENTRY * dllNormal3bv )(const GLbyte *v); +static void ( APIENTRY * dllNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +static void ( APIENTRY * dllNormal3dv )(const GLdouble *v); +static void ( APIENTRY * dllNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +static void ( APIENTRY * dllNormal3fv )(const GLfloat *v); +static void ( APIENTRY * dllNormal3i )(GLint nx, GLint ny, GLint nz); +static void ( APIENTRY * dllNormal3iv )(const GLint *v); +static void ( APIENTRY * dllNormal3s )(GLshort nx, GLshort ny, GLshort nz); +static void ( APIENTRY * dllNormal3sv )(const GLshort *v); +static void ( APIENTRY * dllNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +static void ( APIENTRY * dllPassThrough )(GLfloat token); +static void ( APIENTRY * dllPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +static void ( APIENTRY * dllPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +static void ( APIENTRY * dllPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +static void ( APIENTRY * dllPixelStoref )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelStorei )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelTransferf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelTransferi )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelZoom )(GLfloat xfactor, GLfloat yfactor); +static void ( APIENTRY * dllPointSize )(GLfloat size); +static void ( APIENTRY * dllPolygonMode )(GLenum face, GLenum mode); +static void ( APIENTRY * dllPolygonOffset )(GLfloat factor, GLfloat units); +static void ( APIENTRY * dllPolygonStipple )(const GLubyte *mask); +static void ( APIENTRY * dllPopAttrib )(void); +static void ( APIENTRY * dllPopClientAttrib )(void); +static void ( APIENTRY * dllPopMatrix )(void); +static void ( APIENTRY * dllPopName )(void); +static void ( APIENTRY * dllPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +static void ( APIENTRY * dllPushAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushClientAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushMatrix )(void); +static void ( APIENTRY * dllPushName )(GLuint name); +static void ( APIENTRY * dllRasterPos2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllRasterPos2dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllRasterPos2fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos2i )(GLint x, GLint y); +static void ( APIENTRY * dllRasterPos2iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllRasterPos2sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRasterPos3dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllRasterPos3fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllRasterPos3iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllRasterPos3sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllRasterPos4dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllRasterPos4fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllRasterPos4iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllRasterPos4sv )(const GLshort *v); +static void ( APIENTRY * dllReadBuffer )(GLenum mode); +static void ( APIENTRY * dllReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +static void ( APIENTRY * dllRectdv )(const GLdouble *v1, const GLdouble *v2); +static void ( APIENTRY * dllRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +static void ( APIENTRY * dllRectfv )(const GLfloat *v1, const GLfloat *v2); +static void ( APIENTRY * dllRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +static void ( APIENTRY * dllRectiv )(const GLint *v1, const GLint *v2); +static void ( APIENTRY * dllRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +static void ( APIENTRY * dllRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * dllRenderMode )(GLenum mode); +static void ( APIENTRY * dllRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScaled )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllScalef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllSelectBuffer )(GLsizei size, GLuint *buffer); +static void ( APIENTRY * dllShadeModel )(GLenum mode); +static void ( APIENTRY * dllStencilFunc )(GLenum func, GLint ref, GLuint mask); +static void ( APIENTRY * dllStencilMask )(GLuint mask); +static void ( APIENTRY * dllStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +static void ( APIENTRY * dllTexCoord1d )(GLdouble s); +static void ( APIENTRY * dllTexCoord1dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord1f )(GLfloat s); +static void ( APIENTRY * dllTexCoord1fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord1i )(GLint s); +static void ( APIENTRY * dllTexCoord1iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord1s )(GLshort s); +static void ( APIENTRY * dllTexCoord1sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord2d )(GLdouble s, GLdouble t); +static void ( APIENTRY * dllTexCoord2dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord2f )(GLfloat s, GLfloat t); +static void ( APIENTRY * dllTexCoord2fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord2i )(GLint s, GLint t); +static void ( APIENTRY * dllTexCoord2iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord2s )(GLshort s, GLshort t); +static void ( APIENTRY * dllTexCoord2sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +static void ( APIENTRY * dllTexCoord3dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +static void ( APIENTRY * dllTexCoord3fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord3i )(GLint s, GLint t, GLint r); +static void ( APIENTRY * dllTexCoord3iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord3s )(GLshort s, GLshort t, GLshort r); +static void ( APIENTRY * dllTexCoord3sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +static void ( APIENTRY * dllTexCoord4dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +static void ( APIENTRY * dllTexCoord4fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +static void ( APIENTRY * dllTexCoord4iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +static void ( APIENTRY * dllTexCoord4sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllTexEnvf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexEnvi )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexEnviv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexGend )(GLenum coord, GLenum pname, GLdouble param); +static void ( APIENTRY * dllTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +static void ( APIENTRY * dllTexGenf )(GLenum coord, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexGeni )(GLenum coord, GLenum pname, GLint param); +static void ( APIENTRY * dllTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexParameterf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexParameteri )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTranslated )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllTranslatef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllVertex2dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllVertex2fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex2i )(GLint x, GLint y); +static void ( APIENTRY * dllVertex2iv )(const GLint *v); +static void ( APIENTRY * dllVertex2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllVertex2sv )(const GLshort *v); +static void ( APIENTRY * dllVertex3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllVertex3dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex3fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllVertex3iv )(const GLint *v); +static void ( APIENTRY * dllVertex3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllVertex3sv )(const GLshort *v); +static void ( APIENTRY * dllVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllVertex4dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllVertex4fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllVertex4iv )(const GLint *v); +static void ( APIENTRY * dllVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllVertex4sv )(const GLshort *v); +static void ( APIENTRY * dllVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +typedef struct { + GLenum e; + const char *name; +} glEnumName_t; + +#define DEF(x) { x, #x }, + +glEnumName_t glEnumNames[] = { +DEF(GL_FALSE) +DEF(GL_TRUE) + + { GL_BYTE, "GL_BYTE" }, + { GL_UNSIGNED_BYTE, "GL_UNSIGNED_BYTE" }, + { GL_SHORT, "GL_SHORT" }, + { GL_UNSIGNED_SHORT, "GL_UNSIGNED_SHORT" }, + { GL_INT, "GL_INT" }, + { GL_UNSIGNED_INT, "GL_UNSIGNED_INT" }, + { GL_FLOAT, "GL_FLOAT" }, + { GL_DOUBLE, "GL_DOUBLE" }, + + { GL_TEXTURE_CUBE_MAP_EXT, "GL_TEXTURE_CUBE_MAP_EXT" }, + { GL_TEXTURE_3D, "GL_TEXTURE_3D" }, + { GL_TEXTURE_2D, "GL_TEXTURE_2D" }, + { GL_BLEND, "GL_BLEND" }, + { GL_DEPTH_TEST, "GL_DEPTH_TEST" }, + { GL_CULL_FACE, "GL_CULL_FACE" }, + { GL_CLIP_PLANE0, "GL_CLIP_PLANE0" }, + { GL_COLOR_ARRAY, "GL_COLOR_ARRAY" }, + { GL_TEXTURE_COORD_ARRAY, "GL_TEXTURE_COORD_ARRAY" }, + { GL_VERTEX_ARRAY, "GL_VERTEX_ARRAY" }, + { GL_ALPHA_TEST, "GL_ALPHA_TEST" }, + { GL_TEXTURE_GEN_S, "GL_TEXTURE_GEN_S" }, + { GL_TEXTURE_GEN_T, "GL_TEXTURE_GEN_T" }, + { GL_TEXTURE_GEN_R, "GL_TEXTURE_GEN_R" }, + { GL_TEXTURE_GEN_Q, "GL_TEXTURE_GEN_Q" }, + { GL_STENCIL_TEST, "GL_STENCIL_TEST" }, + { GL_POLYGON_OFFSET_FILL, "GL_POLYGON_OFFSET_FILL" }, + + { GL_TRIANGLES, "GL_TRIANGLES" }, + { GL_TRIANGLE_STRIP, "GL_TRIANGLE_STRIP" }, + { GL_TRIANGLE_FAN, "GL_TRIANGLE_FAN" }, + { GL_QUADS, "GL_QUADS" }, + { GL_QUAD_STRIP, "GL_QUAD_STRIP" }, + { GL_POLYGON, "GL_POLYGON" }, + { GL_POINTS, "GL_POINTS" }, + { GL_LINES, "GL_LINES" }, + { GL_LINE_STRIP, "GL_LINE_STRIP" }, + { GL_LINE_LOOP, "GL_LINE_LOOP" }, + + { GL_ALWAYS, "GL_ALWAYS" }, + { GL_NEVER, "GL_NEVER" }, + { GL_LEQUAL, "GL_LEQUAL" }, + { GL_LESS, "GL_LESS" }, + { GL_EQUAL, "GL_EQUAL" }, + { GL_GREATER, "GL_GREATER" }, + { GL_GEQUAL, "GL_GEQUAL" }, + { GL_NOTEQUAL, "GL_NOTEQUAL" }, + + { GL_ONE, "GL_ONE" }, + { GL_ZERO, "GL_ZERO" }, + { GL_SRC_ALPHA, "GL_SRC_ALPHA" }, + { GL_ONE_MINUS_SRC_ALPHA, "GL_ONE_MINUS_SRC_ALPHA" }, + { GL_DST_COLOR, "GL_DST_COLOR" }, + { GL_ONE_MINUS_DST_COLOR, "GL_ONE_MINUS_DST_COLOR" }, + { GL_DST_ALPHA, "GL_DST_ALPHA" }, + + { GL_MODELVIEW, "GL_MODELVIEW" }, + { GL_PROJECTION, "GL_PROJECTION" }, + { GL_TEXTURE, "GL_TEXTURE" }, + +/* DrawBufferMode */ +DEF(GL_NONE) +DEF(GL_FRONT_LEFT) +DEF(GL_FRONT_RIGHT) +DEF(GL_BACK_LEFT) +DEF(GL_BACK_RIGHT) +DEF(GL_FRONT) +DEF(GL_BACK) +DEF(GL_LEFT) +DEF(GL_RIGHT) +DEF(GL_FRONT_AND_BACK) +DEF(GL_AUX0) +DEF(GL_AUX1) +DEF(GL_AUX2) +DEF(GL_AUX3) + +/* GetTarget */ +DEF(GL_CURRENT_COLOR) +DEF(GL_CURRENT_INDEX) +DEF(GL_CURRENT_NORMAL) +DEF(GL_CURRENT_TEXTURE_COORDS) +DEF(GL_CURRENT_RASTER_COLOR) +DEF(GL_CURRENT_RASTER_INDEX) +DEF(GL_CURRENT_RASTER_TEXTURE_COORDS) +DEF(GL_CURRENT_RASTER_POSITION) +DEF(GL_CURRENT_RASTER_POSITION_VALID) +DEF(GL_CURRENT_RASTER_DISTANCE) +DEF(GL_POINT_SMOOTH) +DEF(GL_POINT_SIZE) +DEF(GL_POINT_SIZE_RANGE) +DEF(GL_POINT_SIZE_GRANULARITY) +DEF(GL_LINE_SMOOTH) +DEF(GL_LINE_WIDTH) +DEF(GL_LINE_WIDTH_RANGE) +DEF(GL_LINE_WIDTH_GRANULARITY) +DEF(GL_LINE_STIPPLE) +DEF(GL_LINE_STIPPLE_PATTERN) +DEF(GL_LINE_STIPPLE_REPEAT) +DEF(GL_LIST_MODE) +DEF(GL_MAX_LIST_NESTING) +DEF(GL_LIST_BASE) +DEF(GL_LIST_INDEX) +DEF(GL_POLYGON_MODE) +DEF(GL_POLYGON_SMOOTH) +DEF(GL_POLYGON_STIPPLE) +DEF(GL_EDGE_FLAG) +DEF(GL_CULL_FACE) +DEF(GL_CULL_FACE_MODE) +DEF(GL_FRONT_FACE) +DEF(GL_LIGHTING) +DEF(GL_LIGHT_MODEL_LOCAL_VIEWER) +DEF(GL_LIGHT_MODEL_TWO_SIDE) +DEF(GL_LIGHT_MODEL_AMBIENT) +DEF(GL_SHADE_MODEL) +DEF(GL_COLOR_MATERIAL_FACE) +DEF(GL_COLOR_MATERIAL_PARAMETER) +DEF(GL_COLOR_MATERIAL) +DEF(GL_FOG) +DEF(GL_FOG_INDEX) +DEF(GL_FOG_DENSITY) +DEF(GL_FOG_START) +DEF(GL_FOG_END) +DEF(GL_FOG_MODE) +DEF(GL_FOG_COLOR) +DEF(GL_DEPTH_RANGE) +DEF(GL_DEPTH_TEST) +DEF(GL_DEPTH_WRITEMASK) +DEF(GL_DEPTH_CLEAR_VALUE) +DEF(GL_DEPTH_FUNC) +DEF(GL_ACCUM_CLEAR_VALUE) +DEF(GL_STENCIL_TEST) +DEF(GL_STENCIL_CLEAR_VALUE) +DEF(GL_STENCIL_FUNC) +DEF(GL_STENCIL_VALUE_MASK) +DEF(GL_STENCIL_FAIL) +DEF(GL_STENCIL_PASS_DEPTH_FAIL) +DEF(GL_STENCIL_PASS_DEPTH_PASS) +DEF(GL_STENCIL_REF) +DEF(GL_STENCIL_WRITEMASK) +DEF(GL_MATRIX_MODE) +DEF(GL_NORMALIZE) +DEF(GL_VIEWPORT) +DEF(GL_MODELVIEW_STACK_DEPTH) +DEF(GL_PROJECTION_STACK_DEPTH) +DEF(GL_TEXTURE_STACK_DEPTH) +DEF(GL_MODELVIEW_MATRIX) +DEF(GL_PROJECTION_MATRIX) +DEF(GL_TEXTURE_MATRIX) +DEF(GL_ATTRIB_STACK_DEPTH) +DEF(GL_CLIENT_ATTRIB_STACK_DEPTH) +DEF(GL_ALPHA_TEST) +DEF(GL_ALPHA_TEST_FUNC) +DEF(GL_ALPHA_TEST_REF) +DEF(GL_DITHER) +DEF(GL_BLEND_DST) +DEF(GL_BLEND_SRC) +DEF(GL_BLEND) +DEF(GL_LOGIC_OP_MODE) +DEF(GL_INDEX_LOGIC_OP) +DEF(GL_COLOR_LOGIC_OP) +DEF(GL_AUX_BUFFERS) +DEF(GL_DRAW_BUFFER) +DEF(GL_READ_BUFFER) +DEF(GL_SCISSOR_BOX) +DEF(GL_SCISSOR_TEST) +DEF(GL_INDEX_CLEAR_VALUE) +DEF(GL_INDEX_WRITEMASK) +DEF(GL_COLOR_CLEAR_VALUE) +DEF(GL_COLOR_WRITEMASK) +DEF(GL_INDEX_MODE) +DEF(GL_RGBA_MODE) +DEF(GL_DOUBLEBUFFER) +DEF(GL_STEREO) +DEF(GL_RENDER_MODE) +DEF(GL_PERSPECTIVE_CORRECTION_HINT) +DEF(GL_POINT_SMOOTH_HINT) +DEF(GL_LINE_SMOOTH_HINT) +DEF(GL_POLYGON_SMOOTH_HINT) +DEF(GL_FOG_HINT) +DEF(GL_TEXTURE_GEN_S) +DEF(GL_TEXTURE_GEN_T) +DEF(GL_TEXTURE_GEN_R) +DEF(GL_TEXTURE_GEN_Q) +DEF(GL_PIXEL_MAP_I_TO_I) +DEF(GL_PIXEL_MAP_S_TO_S) +DEF(GL_PIXEL_MAP_I_TO_R) +DEF(GL_PIXEL_MAP_I_TO_G) +DEF(GL_PIXEL_MAP_I_TO_B) +DEF(GL_PIXEL_MAP_I_TO_A) +DEF(GL_PIXEL_MAP_R_TO_R) +DEF(GL_PIXEL_MAP_G_TO_G) +DEF(GL_PIXEL_MAP_B_TO_B) +DEF(GL_PIXEL_MAP_A_TO_A) +DEF(GL_PIXEL_MAP_I_TO_I_SIZE) +DEF(GL_PIXEL_MAP_S_TO_S_SIZE) +DEF(GL_PIXEL_MAP_I_TO_R_SIZE) +DEF(GL_PIXEL_MAP_I_TO_G_SIZE) +DEF(GL_PIXEL_MAP_I_TO_B_SIZE) +DEF(GL_PIXEL_MAP_I_TO_A_SIZE) +DEF(GL_PIXEL_MAP_R_TO_R_SIZE) +DEF(GL_PIXEL_MAP_G_TO_G_SIZE) +DEF(GL_PIXEL_MAP_B_TO_B_SIZE) +DEF(GL_PIXEL_MAP_A_TO_A_SIZE) +DEF(GL_UNPACK_SWAP_BYTES) +DEF(GL_UNPACK_LSB_FIRST) +DEF(GL_UNPACK_ROW_LENGTH) +DEF(GL_UNPACK_SKIP_ROWS) +DEF(GL_UNPACK_SKIP_PIXELS) +DEF(GL_UNPACK_ALIGNMENT) +DEF(GL_PACK_SWAP_BYTES) +DEF(GL_PACK_LSB_FIRST) +DEF(GL_PACK_ROW_LENGTH) +DEF(GL_PACK_SKIP_ROWS) +DEF(GL_PACK_SKIP_PIXELS) +DEF(GL_PACK_ALIGNMENT) +DEF(GL_MAP_COLOR) +DEF(GL_MAP_STENCIL) +DEF(GL_INDEX_SHIFT) +DEF(GL_INDEX_OFFSET) +DEF(GL_RED_SCALE) +DEF(GL_RED_BIAS) +DEF(GL_ZOOM_X) +DEF(GL_ZOOM_Y) +DEF(GL_GREEN_SCALE) +DEF(GL_GREEN_BIAS) +DEF(GL_BLUE_SCALE) +DEF(GL_BLUE_BIAS) +DEF(GL_ALPHA_SCALE) +DEF(GL_ALPHA_BIAS) +DEF(GL_DEPTH_SCALE) +DEF(GL_DEPTH_BIAS) +DEF(GL_MAX_EVAL_ORDER) +DEF(GL_MAX_LIGHTS) +DEF(GL_MAX_CLIP_PLANES) +DEF(GL_MAX_TEXTURE_SIZE) +DEF(GL_MAX_PIXEL_MAP_TABLE) +DEF(GL_MAX_ATTRIB_STACK_DEPTH) +DEF(GL_MAX_MODELVIEW_STACK_DEPTH) +DEF(GL_MAX_NAME_STACK_DEPTH) +DEF(GL_MAX_PROJECTION_STACK_DEPTH) +DEF(GL_MAX_TEXTURE_STACK_DEPTH) +DEF(GL_MAX_VIEWPORT_DIMS) +DEF(GL_MAX_CLIENT_ATTRIB_STACK_DEPTH) +DEF(GL_SUBPIXEL_BITS) +DEF(GL_INDEX_BITS) +DEF(GL_RED_BITS) +DEF(GL_GREEN_BITS) +DEF(GL_BLUE_BITS) +DEF(GL_ALPHA_BITS) +DEF(GL_DEPTH_BITS) +DEF(GL_STENCIL_BITS) +DEF(GL_ACCUM_RED_BITS) +DEF(GL_ACCUM_GREEN_BITS) +DEF(GL_ACCUM_BLUE_BITS) +DEF(GL_ACCUM_ALPHA_BITS) +DEF(GL_NAME_STACK_DEPTH) +DEF(GL_AUTO_NORMAL) +DEF(GL_MAP1_COLOR_4) +DEF(GL_MAP1_INDEX) +DEF(GL_MAP1_NORMAL) +DEF(GL_MAP1_TEXTURE_COORD_1) +DEF(GL_MAP1_TEXTURE_COORD_2) +DEF(GL_MAP1_TEXTURE_COORD_3) +DEF(GL_MAP1_TEXTURE_COORD_4) +DEF(GL_MAP1_VERTEX_3) +DEF(GL_MAP1_VERTEX_4) +DEF(GL_MAP2_COLOR_4) +DEF(GL_MAP2_INDEX) +DEF(GL_MAP2_NORMAL) +DEF(GL_MAP2_TEXTURE_COORD_1) +DEF(GL_MAP2_TEXTURE_COORD_2) +DEF(GL_MAP2_TEXTURE_COORD_3) +DEF(GL_MAP2_TEXTURE_COORD_4) +DEF(GL_MAP2_VERTEX_3) +DEF(GL_MAP2_VERTEX_4) +DEF(GL_MAP1_GRID_DOMAIN) +DEF(GL_MAP1_GRID_SEGMENTS) +DEF(GL_MAP2_GRID_DOMAIN) +DEF(GL_MAP2_GRID_SEGMENTS) +DEF(GL_TEXTURE_1D) +DEF(GL_TEXTURE_2D) +DEF(GL_FEEDBACK_BUFFER_POINTER) +DEF(GL_FEEDBACK_BUFFER_SIZE) +DEF(GL_FEEDBACK_BUFFER_TYPE) +DEF(GL_SELECTION_BUFFER_POINTER) +DEF(GL_SELECTION_BUFFER_SIZE) + +/* PixelCopyType */ +DEF(GL_COLOR) +DEF(GL_DEPTH) +DEF(GL_STENCIL) + +/* PixelFormat */ +DEF(GL_COLOR_INDEX) +DEF(GL_STENCIL_INDEX) +DEF(GL_DEPTH_COMPONENT) +DEF(GL_RED) +DEF(GL_GREEN) +DEF(GL_BLUE) +DEF(GL_ALPHA) +DEF(GL_RGB) +DEF(GL_RGBA) +DEF(GL_LUMINANCE) +DEF(GL_LUMINANCE_ALPHA) + +/* PixelMap */ +/* GL_PIXEL_MAP_I_TO_I */ +/* GL_PIXEL_MAP_S_TO_S */ +/* GL_PIXEL_MAP_I_TO_R */ +/* GL_PIXEL_MAP_I_TO_G */ +/* GL_PIXEL_MAP_I_TO_B */ +/* GL_PIXEL_MAP_I_TO_A */ +/* GL_PIXEL_MAP_R_TO_R */ +/* GL_PIXEL_MAP_G_TO_G */ +/* GL_PIXEL_MAP_B_TO_B */ +/* GL_PIXEL_MAP_A_TO_A */ + +/* PixelStore */ +/* GL_UNPACK_SWAP_BYTES */ +/* GL_UNPACK_LSB_FIRST */ +/* GL_UNPACK_ROW_LENGTH */ +/* GL_UNPACK_SKIP_ROWS */ +/* GL_UNPACK_SKIP_PIXELS */ +/* GL_UNPACK_ALIGNMENT */ +/* GL_PACK_SWAP_BYTES */ +/* GL_PACK_LSB_FIRST */ +/* GL_PACK_ROW_LENGTH */ +/* GL_PACK_SKIP_ROWS */ +/* GL_PACK_SKIP_PIXELS */ +/* GL_PACK_ALIGNMENT */ + +/* PixelTransfer */ +/* GL_MAP_COLOR */ +/* GL_MAP_STENCIL */ +/* GL_INDEX_SHIFT */ +/* GL_INDEX_OFFSET */ +/* GL_RED_SCALE */ +/* GL_RED_BIAS */ +/* GL_GREEN_SCALE */ +/* GL_GREEN_BIAS */ +/* GL_BLUE_SCALE */ +/* GL_BLUE_BIAS */ +/* GL_ALPHA_SCALE */ +/* GL_ALPHA_BIAS */ +/* GL_DEPTH_SCALE */ +/* GL_DEPTH_BIAS */ + +/* PixelType */ +DEF(GL_BITMAP) +/* GL_BYTE */ +/* GL_UNSIGNED_BYTE */ +/* GL_SHORT */ +/* GL_UNSIGNED_SHORT */ +/* GL_INT */ +/* GL_UNSIGNED_INT */ +/* GL_FLOAT */ + +/* PolygonMode */ +DEF(GL_POINT) +DEF(GL_LINE) +DEF(GL_FILL) + +/* RenderingMode */ +DEF(GL_RENDER) +DEF(GL_FEEDBACK) +DEF(GL_SELECT) + +/* ShadingModel */ +DEF(GL_FLAT) +DEF(GL_SMOOTH) + +/* StencilOp */ +/* GL_ZERO */ +DEF(GL_KEEP) +DEF(GL_REPLACE) +DEF(GL_INCR) +DEF(GL_DECR) +/* GL_INVERT */ + +/* StringName */ +DEF(GL_VENDOR) +DEF(GL_RENDERER) +DEF(GL_VERSION) +DEF(GL_EXTENSIONS) + +/* TextureCoordName */ +DEF(GL_S) +DEF(GL_T) +DEF(GL_R) +DEF(GL_Q) + +/* TexCoordPointerType */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* TextureEnvMode */ +DEF(GL_MODULATE) +DEF(GL_DECAL) +/* GL_BLEND */ +/* GL_REPLACE */ + +/* TextureEnvParameter */ +DEF(GL_TEXTURE_ENV_MODE) +DEF(GL_TEXTURE_ENV_COLOR) + +/* TextureEnvTarget */ +DEF(GL_TEXTURE_ENV) + +/* TextureGenMode */ +DEF(GL_EYE_LINEAR) +DEF(GL_OBJECT_LINEAR) +DEF(GL_SPHERE_MAP) + +/* TextureGenParameter */ +DEF(GL_TEXTURE_GEN_MODE) +DEF(GL_OBJECT_PLANE) +DEF(GL_EYE_PLANE) + +/* TextureMagFilter */ +DEF(GL_NEAREST) +DEF(GL_LINEAR) + +/* TextureMinFilter */ +/* GL_NEAREST */ +/* GL_LINEAR */ +DEF(GL_NEAREST_MIPMAP_NEAREST) +DEF(GL_LINEAR_MIPMAP_NEAREST) +DEF(GL_NEAREST_MIPMAP_LINEAR) +DEF(GL_LINEAR_MIPMAP_LINEAR) + +/* TextureParameterName */ +DEF(GL_TEXTURE_MAG_FILTER) +DEF(GL_TEXTURE_MIN_FILTER) +DEF(GL_TEXTURE_WRAP_S) +DEF(GL_TEXTURE_WRAP_T) +/* GL_TEXTURE_BORDER_COLOR */ +/* GL_TEXTURE_PRIORITY */ + +/* TextureTarget */ +/* GL_TEXTURE_1D */ +/* GL_TEXTURE_2D */ +/* GL_PROXY_TEXTURE_1D */ +/* GL_PROXY_TEXTURE_2D */ + +/* TextureWrapMode */ +DEF(GL_CLAMP) +DEF(GL_REPEAT) + +/* VertexPointerType */ +/* GL_SHORT */ +/* GL_INT */ +/* GL_FLOAT */ +/* GL_DOUBLE */ + +/* ClientAttribMask */ +DEF(GL_CLIENT_PIXEL_STORE_BIT) +DEF(GL_CLIENT_VERTEX_ARRAY_BIT) +DEF(GL_CLIENT_ALL_ATTRIB_BITS) + +/* polygon_offset */ +DEF(GL_POLYGON_OFFSET_FACTOR) +DEF(GL_POLYGON_OFFSET_UNITS) +DEF(GL_POLYGON_OFFSET_POINT) +DEF(GL_POLYGON_OFFSET_LINE) +DEF(GL_POLYGON_OFFSET_FILL) + + { 0, NULL } +}; + +static const char *EnumString( GLenum t ) +{ + static char buffer[8][1024]; + static int index = 0; + + for ( glEnumName_t *n = glEnumNames ; n->name ; n++ ) { + if ( t == n->e ) { + return n->name; + } + } + + int oldIndex = index; + index = ( index + 1 ) & 7; + sprintf( buffer[oldIndex], "0x%x", t ); + + return buffer[oldIndex]; +} + +static const char *FloatData( const GLfloat *v, int count ) { + static char buffer[8][1024]; + static int index = 0; + char *name; + + name = buffer[index&7]; + sprintf( name, "f%i", index ); + index++; + + fprintf( qglLogFile, "static float %s[%i] = {", name, count ); + for( int i = 0 ; i < count ; i++ ) { + if ( i < count - 1 ) { + fprintf( qglLogFile, "%f,", v[i] ); + } else { + fprintf( qglLogFile, "%f};\n", v[i] ); + } + } + + return name; +} + +#if 0 + +// TODO: support GLbitfield + +static void APIENTRY logClear(GLbitfield mask) +{ + fprintf( qglLogFile, "glClear( 0 " ); + + if ( mask & GL_COLOR_BUFFER_BIT ) + fprintf( qglLogFile, "| GL_COLOR_BUFFER_BIT " ); + if ( mask & GL_DEPTH_BUFFER_BIT ) + fprintf( qglLogFile, "| GL_DEPTH_BUFFER_BIT " ); + if ( mask & GL_STENCIL_BUFFER_BIT ) + fprintf( qglLogFile, "| GL_STENCIL_BUFFER_BIT " ); + if ( mask & GL_ACCUM_BUFFER_BIT ) + fprintf( qglLogFile, "| GL_ACCUM_BUFFER_BIT " ); + + fprintf( qglLogFile, ");\n" ); + dllClear( mask ); +} + +#endif + +#if !defined( Q4_DISABLE_GL_LOGGING ) +#include "gl_logfuncs.cpp" +#endif + +/* +** QGL_Shutdown +** +** Unloads the specified DLL then nulls out all the proc pointers. This +** is only called during a hard shutdown of the OGL subsystem (e.g. vid_restart). +*/ +void QGL_Shutdown( void ) +{ + common->Printf( "...shutting down QGL\n" ); + + if ( win32.hinstOpenGL ) + { + common->Printf( "...unloading OpenGL DLL\n" ); + FreeLibrary( win32.hinstOpenGL ); + } + + win32.hinstOpenGL = NULL; + + qglAccum = NULL; + qglAlphaFunc = NULL; + qglAreTexturesResident = NULL; + qglArrayElement = NULL; + qglBegin = NULL; + qglBindTexture = NULL; + qglBitmap = NULL; + qglBlendFunc = NULL; + qglCallList = NULL; + qglCallLists = NULL; + qglClear = NULL; + qglClearAccum = NULL; + qglClearColor = NULL; + qglClearDepth = NULL; + qglClearIndex = NULL; + qglClearStencil = NULL; + qglClipPlane = NULL; + qglColor3b = NULL; + qglColor3bv = NULL; + qglColor3d = NULL; + qglColor3dv = NULL; + qglColor3f = NULL; + qglColor3fv = NULL; + qglColor3i = NULL; + qglColor3iv = NULL; + qglColor3s = NULL; + qglColor3sv = NULL; + qglColor3ub = NULL; + qglColor3ubv = NULL; + qglColor3ui = NULL; + qglColor3uiv = NULL; + qglColor3us = NULL; + qglColor3usv = NULL; + qglColor4b = NULL; + qglColor4bv = NULL; + qglColor4d = NULL; + qglColor4dv = NULL; + qglColor4f = NULL; + qglColor4fv = NULL; + qglColor4i = NULL; + qglColor4iv = NULL; + qglColor4s = NULL; + qglColor4sv = NULL; + qglColor4ub = NULL; + qglColor4ubv = NULL; + qglColor4ui = NULL; + qglColor4uiv = NULL; + qglColor4us = NULL; + qglColor4usv = NULL; + qglColorMask = NULL; + qglColorMaterial = NULL; + qglColorPointer = NULL; + qglCopyPixels = NULL; + qglCopyTexImage1D = NULL; + qglCopyTexImage2D = NULL; + qglCopyTexSubImage1D = NULL; + qglCopyTexSubImage2D = NULL; + qglCullFace = NULL; + qglDeleteLists = NULL; + qglDeleteTextures = NULL; + qglDepthFunc = NULL; + qglDepthMask = NULL; + qglDepthRange = NULL; + qglDisable = NULL; + qglDisableClientState = NULL; + qglDrawArrays = NULL; + qglDrawBuffer = NULL; + qglDrawElements = NULL; + qglDrawPixels = NULL; + qglEdgeFlag = NULL; + qglEdgeFlagPointer = NULL; + qglEdgeFlagv = NULL; + qglEnable = NULL; + qglEnableClientState = NULL; + qglEnd = NULL; + qglEndList = NULL; + qglEvalCoord1d = NULL; + qglEvalCoord1dv = NULL; + qglEvalCoord1f = NULL; + qglEvalCoord1fv = NULL; + qglEvalCoord2d = NULL; + qglEvalCoord2dv = NULL; + qglEvalCoord2f = NULL; + qglEvalCoord2fv = NULL; + qglEvalMesh1 = NULL; + qglEvalMesh2 = NULL; + qglEvalPoint1 = NULL; + qglEvalPoint2 = NULL; + qglFeedbackBuffer = NULL; + qglFinish = NULL; + qglFlush = NULL; + qglFogf = NULL; + qglFogfv = NULL; + qglFogi = NULL; + qglFogiv = NULL; + qglFrontFace = NULL; + qglFrustum = NULL; + qglGenLists = NULL; + qglGenTextures = NULL; + qglGetBooleanv = NULL; + qglGetClipPlane = NULL; + qglGetDoublev = NULL; + qglGetError = NULL; + qglGetFloatv = NULL; + qglGetIntegerv = NULL; + qglGetLightfv = NULL; + qglGetLightiv = NULL; + qglGetMapdv = NULL; + qglGetMapfv = NULL; + qglGetMapiv = NULL; + qglGetMaterialfv = NULL; + qglGetMaterialiv = NULL; + qglGetPixelMapfv = NULL; + qglGetPixelMapuiv = NULL; + qglGetPixelMapusv = NULL; + qglGetPointerv = NULL; + qglGetPolygonStipple = NULL; + qglGetString = NULL; + qglGetTexEnvfv = NULL; + qglGetTexEnviv = NULL; + qglGetTexGendv = NULL; + qglGetTexGenfv = NULL; + qglGetTexGeniv = NULL; + qglGetTexImage = NULL; + qglGetTexLevelParameterfv = NULL; + qglGetTexLevelParameteriv = NULL; + qglGetTexParameterfv = NULL; + qglGetTexParameteriv = NULL; + qglHint = NULL; + qglIndexMask = NULL; + qglIndexPointer = NULL; + qglIndexd = NULL; + qglIndexdv = NULL; + qglIndexf = NULL; + qglIndexfv = NULL; + qglIndexi = NULL; + qglIndexiv = NULL; + qglIndexs = NULL; + qglIndexsv = NULL; + qglIndexub = NULL; + qglIndexubv = NULL; + qglInitNames = NULL; + qglInterleavedArrays = NULL; + qglIsEnabled = NULL; + qglIsList = NULL; + qglIsTexture = NULL; + qglLightModelf = NULL; + qglLightModelfv = NULL; + qglLightModeli = NULL; + qglLightModeliv = NULL; + qglLightf = NULL; + qglLightfv = NULL; + qglLighti = NULL; + qglLightiv = NULL; + qglLineStipple = NULL; + qglLineWidth = NULL; + qglListBase = NULL; + qglLoadIdentity = NULL; + qglLoadMatrixd = NULL; + qglLoadMatrixf = NULL; + qglLoadName = NULL; + qglLogicOp = NULL; + qglMap1d = NULL; + qglMap1f = NULL; + qglMap2d = NULL; + qglMap2f = NULL; + qglMapGrid1d = NULL; + qglMapGrid1f = NULL; + qglMapGrid2d = NULL; + qglMapGrid2f = NULL; + qglMaterialf = NULL; + qglMaterialfv = NULL; + qglMateriali = NULL; + qglMaterialiv = NULL; + qglMatrixMode = NULL; + qglMultMatrixd = NULL; + qglMultMatrixf = NULL; + qglNewList = NULL; + qglNormal3b = NULL; + qglNormal3bv = NULL; + qglNormal3d = NULL; + qglNormal3dv = NULL; + qglNormal3f = NULL; + qglNormal3fv = NULL; + qglNormal3i = NULL; + qglNormal3iv = NULL; + qglNormal3s = NULL; + qglNormal3sv = NULL; + qglNormalPointer = NULL; + qglOrtho = NULL; + qglPassThrough = NULL; + qglPixelMapfv = NULL; + qglPixelMapuiv = NULL; + qglPixelMapusv = NULL; + qglPixelStoref = NULL; + qglPixelStorei = NULL; + qglPixelTransferf = NULL; + qglPixelTransferi = NULL; + qglPixelZoom = NULL; + qglPointSize = NULL; + qglPolygonMode = NULL; + qglPolygonOffset = NULL; + qglPolygonStipple = NULL; + qglPopAttrib = NULL; + qglPopClientAttrib = NULL; + qglPopMatrix = NULL; + qglPopName = NULL; + qglPrioritizeTextures = NULL; + qglPushAttrib = NULL; + qglPushClientAttrib = NULL; + qglPushMatrix = NULL; + qglPushName = NULL; + qglRasterPos2d = NULL; + qglRasterPos2dv = NULL; + qglRasterPos2f = NULL; + qglRasterPos2fv = NULL; + qglRasterPos2i = NULL; + qglRasterPos2iv = NULL; + qglRasterPos2s = NULL; + qglRasterPos2sv = NULL; + qglRasterPos3d = NULL; + qglRasterPos3dv = NULL; + qglRasterPos3f = NULL; + qglRasterPos3fv = NULL; + qglRasterPos3i = NULL; + qglRasterPos3iv = NULL; + qglRasterPos3s = NULL; + qglRasterPos3sv = NULL; + qglRasterPos4d = NULL; + qglRasterPos4dv = NULL; + qglRasterPos4f = NULL; + qglRasterPos4fv = NULL; + qglRasterPos4i = NULL; + qglRasterPos4iv = NULL; + qglRasterPos4s = NULL; + qglRasterPos4sv = NULL; + qglReadBuffer = NULL; + qglReadPixels = NULL; + qglRectd = NULL; + qglRectdv = NULL; + qglRectf = NULL; + qglRectfv = NULL; + qglRecti = NULL; + qglRectiv = NULL; + qglRects = NULL; + qglRectsv = NULL; + qglRenderMode = NULL; + qglRotated = NULL; + qglRotatef = NULL; + qglScaled = NULL; + qglScalef = NULL; + qglScissor = NULL; + qglSelectBuffer = NULL; + qglShadeModel = NULL; + qglStencilFunc = NULL; + qglStencilMask = NULL; + qglStencilOp = NULL; + qglTexCoord1d = NULL; + qglTexCoord1dv = NULL; + qglTexCoord1f = NULL; + qglTexCoord1fv = NULL; + qglTexCoord1i = NULL; + qglTexCoord1iv = NULL; + qglTexCoord1s = NULL; + qglTexCoord1sv = NULL; + qglTexCoord2d = NULL; + qglTexCoord2dv = NULL; + qglTexCoord2f = NULL; + qglTexCoord2fv = NULL; + qglTexCoord2i = NULL; + qglTexCoord2iv = NULL; + qglTexCoord2s = NULL; + qglTexCoord2sv = NULL; + qglTexCoord3d = NULL; + qglTexCoord3dv = NULL; + qglTexCoord3f = NULL; + qglTexCoord3fv = NULL; + qglTexCoord3i = NULL; + qglTexCoord3iv = NULL; + qglTexCoord3s = NULL; + qglTexCoord3sv = NULL; + qglTexCoord4d = NULL; + qglTexCoord4dv = NULL; + qglTexCoord4f = NULL; + qglTexCoord4fv = NULL; + qglTexCoord4i = NULL; + qglTexCoord4iv = NULL; + qglTexCoord4s = NULL; + qglTexCoord4sv = NULL; + qglTexCoordPointer = NULL; + qglTexEnvf = NULL; + qglTexEnvfv = NULL; + qglTexEnvi = NULL; + qglTexEnviv = NULL; + qglTexGend = NULL; + qglTexGendv = NULL; + qglTexGenf = NULL; + qglTexGenfv = NULL; + qglTexGeni = NULL; + qglTexGeniv = NULL; + qglTexImage1D = NULL; + qglTexImage2D = NULL; + qglTexParameterf = NULL; + qglTexParameterfv = NULL; + qglTexParameteri = NULL; + qglTexParameteriv = NULL; + qglTexSubImage1D = NULL; + qglTexSubImage2D = NULL; + qglTranslated = NULL; + qglTranslatef = NULL; + qglVertex2d = NULL; + qglVertex2dv = NULL; + qglVertex2f = NULL; + qglVertex2fv = NULL; + qglVertex2i = NULL; + qglVertex2iv = NULL; + qglVertex2s = NULL; + qglVertex2sv = NULL; + qglVertex3d = NULL; + qglVertex3dv = NULL; + qglVertex3f = NULL; + qglVertex3fv = NULL; + qglVertex3i = NULL; + qglVertex3iv = NULL; + qglVertex3s = NULL; + qglVertex3sv = NULL; + qglVertex4d = NULL; + qglVertex4dv = NULL; + qglVertex4f = NULL; + qglVertex4fv = NULL; + qglVertex4i = NULL; + qglVertex4iv = NULL; + qglVertex4s = NULL; + qglVertex4sv = NULL; + qglVertexPointer = NULL; + qglViewport = NULL; + + qwglCopyContext = NULL; + qwglCreateContext = NULL; + qwglCreateLayerContext = NULL; + qwglDeleteContext = NULL; + qwglDescribeLayerPlane = NULL; + qwglGetCurrentContext = NULL; + qwglGetCurrentDC = NULL; + qwglGetLayerPaletteEntries = NULL; + qwglGetProcAddress = NULL; + qwglMakeCurrent = NULL; + qwglRealizeLayerPalette = NULL; + qwglSetLayerPaletteEntries = NULL; + qwglShareLists = NULL; + qwglSwapLayerBuffers = NULL; + qwglUseFontBitmaps = NULL; + qwglUseFontOutlines = NULL; + + qwglChoosePixelFormat = NULL; + qwglDescribePixelFormat = NULL; + qwglGetPixelFormat = NULL; + qwglSetPixelFormat = NULL; + qwglSwapBuffers = NULL; +} + +#define GR_NUM_BOARDS 0x0f + + +#pragma warning (disable : 4113 4133 4047 ) +#define GPA( a ) GetProcAddress( win32.hinstOpenGL, a ) + +/* +** QGL_Init +** +** This is responsible for binding our qgl function pointers to +** the appropriate GL stuff. In Windows this means doing a +** LoadLibrary and a bunch of calls to GetProcAddress. On other +** operating systems we need to do the right thing, whatever that +** might be. +*/ +bool QGL_Init( const char *dllname ) +{ + assert( win32.hinstOpenGL == 0 ); + + common->Printf( "...initializing QGL\n" ); + + common->Printf( "...calling LoadLibrary( '%s' ): ", dllname ); + + if ( ( win32.hinstOpenGL = LoadLibrary( dllname ) ) == 0 ) + { + common->Printf( "failed\n" ); + return false; + } + common->Printf( "succeeded\n" ); + + qglAccum = dllAccum = glAccum; + qglAlphaFunc = dllAlphaFunc = glAlphaFunc; + qglAreTexturesResident = dllAreTexturesResident = glAreTexturesResident; + qglArrayElement = dllArrayElement = glArrayElement; + qglBegin = dllBegin = glBegin; + qglBindTexture = dllBindTexture = glBindTexture; + qglBitmap = dllBitmap = glBitmap; + qglBlendFunc = dllBlendFunc = glBlendFunc; + qglCallList = dllCallList = glCallList; + qglCallLists = dllCallLists = glCallLists; + qglClear = dllClear = glClear; + qglClearAccum = dllClearAccum = glClearAccum; + qglClearColor = dllClearColor = glClearColor; + qglClearDepth = dllClearDepth = glClearDepth; + qglClearIndex = dllClearIndex = glClearIndex; + qglClearStencil = dllClearStencil = glClearStencil; + qglClipPlane = dllClipPlane = glClipPlane; + qglColor3b = dllColor3b = glColor3b; + qglColor3bv = dllColor3bv = glColor3bv; + qglColor3d = dllColor3d = glColor3d; + qglColor3dv = dllColor3dv = glColor3dv; + qglColor3f = dllColor3f = glColor3f; + qglColor3fv = dllColor3fv = glColor3fv; + qglColor3i = dllColor3i = glColor3i; + qglColor3iv = dllColor3iv = glColor3iv; + qglColor3s = dllColor3s = glColor3s; + qglColor3sv = dllColor3sv = glColor3sv; + qglColor3ub = dllColor3ub = glColor3ub; + qglColor3ubv = dllColor3ubv = glColor3ubv; + qglColor3ui = dllColor3ui = glColor3ui; + qglColor3uiv = dllColor3uiv = glColor3uiv; + qglColor3us = dllColor3us = glColor3us; + qglColor3usv = dllColor3usv = glColor3usv; + qglColor4b = dllColor4b = glColor4b; + qglColor4bv = dllColor4bv = glColor4bv; + qglColor4d = dllColor4d = glColor4d; + qglColor4dv = dllColor4dv = glColor4dv; + qglColor4f = dllColor4f = glColor4f; + qglColor4fv = dllColor4fv = glColor4fv; + qglColor4i = dllColor4i = glColor4i; + qglColor4iv = dllColor4iv = glColor4iv; + qglColor4s = dllColor4s = glColor4s; + qglColor4sv = dllColor4sv = glColor4sv; + qglColor4ub = dllColor4ub = glColor4ub; + qglColor4ubv = dllColor4ubv = glColor4ubv; + qglColor4ui = dllColor4ui = glColor4ui; + qglColor4uiv = dllColor4uiv = glColor4uiv; + qglColor4us = dllColor4us = glColor4us; + qglColor4usv = dllColor4usv = glColor4usv; + qglColorMask = dllColorMask = glColorMask; + qglColorMaterial = dllColorMaterial = glColorMaterial; + qglColorPointer = dllColorPointer = glColorPointer; + qglCopyPixels = dllCopyPixels = glCopyPixels; + qglCopyTexImage1D = dllCopyTexImage1D = glCopyTexImage1D; + qglCopyTexImage2D = dllCopyTexImage2D = glCopyTexImage2D; + qglCopyTexSubImage1D = dllCopyTexSubImage1D = glCopyTexSubImage1D; + qglCopyTexSubImage2D = dllCopyTexSubImage2D = glCopyTexSubImage2D; + qglCullFace = dllCullFace = glCullFace; + qglDeleteLists = dllDeleteLists = glDeleteLists; + qglDeleteTextures = dllDeleteTextures = glDeleteTextures; + qglDepthFunc = dllDepthFunc = glDepthFunc; + qglDepthMask = dllDepthMask = glDepthMask; + qglDepthRange = dllDepthRange = glDepthRange; + qglDisable = dllDisable = glDisable; + qglDisableClientState = dllDisableClientState = glDisableClientState; + qglDrawArrays = dllDrawArrays = glDrawArrays; + qglDrawBuffer = dllDrawBuffer = glDrawBuffer; + qglDrawElements = dllDrawElements = glDrawElements; + qglDrawPixels = dllDrawPixels = glDrawPixels; + qglEdgeFlag = dllEdgeFlag = glEdgeFlag; + qglEdgeFlagPointer = dllEdgeFlagPointer = glEdgeFlagPointer; + qglEdgeFlagv = dllEdgeFlagv = glEdgeFlagv; + qglEnable = dllEnable = glEnable; + qglEnableClientState = dllEnableClientState = glEnableClientState; + qglEnd = dllEnd = glEnd; + qglEndList = dllEndList = glEndList; + qglEvalCoord1d = dllEvalCoord1d = glEvalCoord1d; + qglEvalCoord1dv = dllEvalCoord1dv = glEvalCoord1dv; + qglEvalCoord1f = dllEvalCoord1f = glEvalCoord1f; + qglEvalCoord1fv = dllEvalCoord1fv = glEvalCoord1fv; + qglEvalCoord2d = dllEvalCoord2d = glEvalCoord2d; + qglEvalCoord2dv = dllEvalCoord2dv = glEvalCoord2dv; + qglEvalCoord2f = dllEvalCoord2f = glEvalCoord2f; + qglEvalCoord2fv = dllEvalCoord2fv = glEvalCoord2fv; + qglEvalMesh1 = dllEvalMesh1 = glEvalMesh1; + qglEvalMesh2 = dllEvalMesh2 = glEvalMesh2; + qglEvalPoint1 = dllEvalPoint1 = glEvalPoint1; + qglEvalPoint2 = dllEvalPoint2 = glEvalPoint2; + qglFeedbackBuffer = dllFeedbackBuffer = glFeedbackBuffer; + qglFinish = dllFinish = glFinish; + qglFlush = dllFlush = glFlush; + qglFogf = dllFogf = glFogf; + qglFogfv = dllFogfv = glFogfv; + qglFogi = dllFogi = glFogi; + qglFogiv = dllFogiv = glFogiv; + qglFrontFace = dllFrontFace = glFrontFace; + qglFrustum = dllFrustum = glFrustum; + qglGenLists = dllGenLists = ( GLuint (__stdcall * )(int) ) glGenLists; + qglGenTextures = dllGenTextures = glGenTextures; + qglGetBooleanv = dllGetBooleanv = glGetBooleanv; + qglGetClipPlane = dllGetClipPlane = glGetClipPlane; + qglGetDoublev = dllGetDoublev = glGetDoublev; + qglGetError = dllGetError = ( GLenum (__stdcall * )(void) ) glGetError; + qglGetFloatv = dllGetFloatv = glGetFloatv; + qglGetIntegerv = dllGetIntegerv = glGetIntegerv; + qglGetLightfv = dllGetLightfv = glGetLightfv; + qglGetLightiv = dllGetLightiv = glGetLightiv; + qglGetMapdv = dllGetMapdv = glGetMapdv; + qglGetMapfv = dllGetMapfv = glGetMapfv; + qglGetMapiv = dllGetMapiv = glGetMapiv; + qglGetMaterialfv = dllGetMaterialfv = glGetMaterialfv; + qglGetMaterialiv = dllGetMaterialiv = glGetMaterialiv; + qglGetPixelMapfv = dllGetPixelMapfv = glGetPixelMapfv; + qglGetPixelMapuiv = dllGetPixelMapuiv = glGetPixelMapuiv; + qglGetPixelMapusv = dllGetPixelMapusv = glGetPixelMapusv; + qglGetPointerv = dllGetPointerv = glGetPointerv; + qglGetPolygonStipple = dllGetPolygonStipple = glGetPolygonStipple; + qglGetString = dllGetString = glGetString; + qglGetTexEnvfv = dllGetTexEnvfv = glGetTexEnvfv; + qglGetTexEnviv = dllGetTexEnviv = glGetTexEnviv; + qglGetTexGendv = dllGetTexGendv = glGetTexGendv; + qglGetTexGenfv = dllGetTexGenfv = glGetTexGenfv; + qglGetTexGeniv = dllGetTexGeniv = glGetTexGeniv; + qglGetTexImage = dllGetTexImage = glGetTexImage; + qglGetTexLevelParameterfv = dllGetTexLevelParameterfv = glGetTexLevelParameterfv; + qglGetTexLevelParameteriv = dllGetTexLevelParameteriv = glGetTexLevelParameteriv; + qglGetTexParameterfv = dllGetTexParameterfv = glGetTexParameterfv; + qglGetTexParameteriv = dllGetTexParameteriv = glGetTexParameteriv; + qglHint = dllHint = glHint; + qglIndexMask = dllIndexMask = glIndexMask; + qglIndexPointer = dllIndexPointer = glIndexPointer; + qglIndexd = dllIndexd = glIndexd; + qglIndexdv = dllIndexdv = glIndexdv; + qglIndexf = dllIndexf = glIndexf; + qglIndexfv = dllIndexfv = glIndexfv; + qglIndexi = dllIndexi = glIndexi; + qglIndexiv = dllIndexiv = glIndexiv; + qglIndexs = dllIndexs = glIndexs; + qglIndexsv = dllIndexsv = glIndexsv; + qglIndexub = dllIndexub = glIndexub; + qglIndexubv = dllIndexubv = glIndexubv; + qglInitNames = dllInitNames = glInitNames; + qglInterleavedArrays = dllInterleavedArrays = glInterleavedArrays; + qglIsEnabled = dllIsEnabled = glIsEnabled; + qglIsList = dllIsList = glIsList; + qglIsTexture = dllIsTexture = glIsTexture; + qglLightModelf = dllLightModelf = glLightModelf; + qglLightModelfv = dllLightModelfv = glLightModelfv; + qglLightModeli = dllLightModeli = glLightModeli; + qglLightModeliv = dllLightModeliv = glLightModeliv; + qglLightf = dllLightf = glLightf; + qglLightfv = dllLightfv = glLightfv; + qglLighti = dllLighti = glLighti; + qglLightiv = dllLightiv = glLightiv; + qglLineStipple = dllLineStipple = glLineStipple; + qglLineWidth = dllLineWidth = glLineWidth; + qglListBase = dllListBase = glListBase; + qglLoadIdentity = dllLoadIdentity = glLoadIdentity; + qglLoadMatrixd = dllLoadMatrixd = glLoadMatrixd; + qglLoadMatrixf = dllLoadMatrixf = glLoadMatrixf; + qglLoadName = dllLoadName = glLoadName; + qglLogicOp = dllLogicOp = glLogicOp; + qglMap1d = dllMap1d = glMap1d; + qglMap1f = dllMap1f = glMap1f; + qglMap2d = dllMap2d = glMap2d; + qglMap2f = dllMap2f = glMap2f; + qglMapGrid1d = dllMapGrid1d = glMapGrid1d; + qglMapGrid1f = dllMapGrid1f = glMapGrid1f; + qglMapGrid2d = dllMapGrid2d = glMapGrid2d; + qglMapGrid2f = dllMapGrid2f = glMapGrid2f; + qglMaterialf = dllMaterialf = glMaterialf; + qglMaterialfv = dllMaterialfv = glMaterialfv; + qglMateriali = dllMateriali = glMateriali; + qglMaterialiv = dllMaterialiv = glMaterialiv; + qglMatrixMode = dllMatrixMode = glMatrixMode; + qglMultMatrixd = dllMultMatrixd = glMultMatrixd; + qglMultMatrixf = dllMultMatrixf = glMultMatrixf; + qglNewList = dllNewList = glNewList; + qglNormal3b = dllNormal3b = glNormal3b; + qglNormal3bv = dllNormal3bv = glNormal3bv; + qglNormal3d = dllNormal3d = glNormal3d; + qglNormal3dv = dllNormal3dv = glNormal3dv; + qglNormal3f = dllNormal3f = glNormal3f; + qglNormal3fv = dllNormal3fv = glNormal3fv; + qglNormal3i = dllNormal3i = glNormal3i; + qglNormal3iv = dllNormal3iv = glNormal3iv; + qglNormal3s = dllNormal3s = glNormal3s; + qglNormal3sv = dllNormal3sv = glNormal3sv; + qglNormalPointer = dllNormalPointer = glNormalPointer; + qglOrtho = dllOrtho = glOrtho; + qglPassThrough = dllPassThrough = glPassThrough; + qglPixelMapfv = dllPixelMapfv = glPixelMapfv; + qglPixelMapuiv = dllPixelMapuiv = glPixelMapuiv; + qglPixelMapusv = dllPixelMapusv = glPixelMapusv; + qglPixelStoref = dllPixelStoref = glPixelStoref; + qglPixelStorei = dllPixelStorei = glPixelStorei; + qglPixelTransferf = dllPixelTransferf = glPixelTransferf; + qglPixelTransferi = dllPixelTransferi = glPixelTransferi; + qglPixelZoom = dllPixelZoom = glPixelZoom; + qglPointSize = dllPointSize = glPointSize; + qglPolygonMode = dllPolygonMode = glPolygonMode; + qglPolygonOffset = dllPolygonOffset = glPolygonOffset; + qglPolygonStipple = dllPolygonStipple = glPolygonStipple; + qglPopAttrib = dllPopAttrib = glPopAttrib; + qglPopClientAttrib = dllPopClientAttrib = glPopClientAttrib; + qglPopMatrix = dllPopMatrix = glPopMatrix; + qglPopName = dllPopName = glPopName; + qglPrioritizeTextures = dllPrioritizeTextures = glPrioritizeTextures; + qglPushAttrib = dllPushAttrib = glPushAttrib; + qglPushClientAttrib = dllPushClientAttrib = glPushClientAttrib; + qglPushMatrix = dllPushMatrix = glPushMatrix; + qglPushName = dllPushName = glPushName; + qglRasterPos2d = dllRasterPos2d = glRasterPos2d; + qglRasterPos2dv = dllRasterPos2dv = glRasterPos2dv; + qglRasterPos2f = dllRasterPos2f = glRasterPos2f; + qglRasterPos2fv = dllRasterPos2fv = glRasterPos2fv; + qglRasterPos2i = dllRasterPos2i = glRasterPos2i; + qglRasterPos2iv = dllRasterPos2iv = glRasterPos2iv; + qglRasterPos2s = dllRasterPos2s = glRasterPos2s; + qglRasterPos2sv = dllRasterPos2sv = glRasterPos2sv; + qglRasterPos3d = dllRasterPos3d = glRasterPos3d; + qglRasterPos3dv = dllRasterPos3dv = glRasterPos3dv; + qglRasterPos3f = dllRasterPos3f = glRasterPos3f; + qglRasterPos3fv = dllRasterPos3fv = glRasterPos3fv; + qglRasterPos3i = dllRasterPos3i = glRasterPos3i; + qglRasterPos3iv = dllRasterPos3iv = glRasterPos3iv; + qglRasterPos3s = dllRasterPos3s = glRasterPos3s; + qglRasterPos3sv = dllRasterPos3sv = glRasterPos3sv; + qglRasterPos4d = dllRasterPos4d = glRasterPos4d; + qglRasterPos4dv = dllRasterPos4dv = glRasterPos4dv; + qglRasterPos4f = dllRasterPos4f = glRasterPos4f; + qglRasterPos4fv = dllRasterPos4fv = glRasterPos4fv; + qglRasterPos4i = dllRasterPos4i = glRasterPos4i; + qglRasterPos4iv = dllRasterPos4iv = glRasterPos4iv; + qglRasterPos4s = dllRasterPos4s = glRasterPos4s; + qglRasterPos4sv = dllRasterPos4sv = glRasterPos4sv; + qglReadBuffer = dllReadBuffer = glReadBuffer; + qglReadPixels = dllReadPixels = glReadPixels; + qglRectd = dllRectd = glRectd; + qglRectdv = dllRectdv = glRectdv; + qglRectf = dllRectf = glRectf; + qglRectfv = dllRectfv = glRectfv; + qglRecti = dllRecti = glRecti; + qglRectiv = dllRectiv = glRectiv; + qglRects = dllRects = glRects; + qglRectsv = dllRectsv = glRectsv; + qglRenderMode = dllRenderMode = glRenderMode; + qglRotated = dllRotated = glRotated; + qglRotatef = dllRotatef = glRotatef; + qglScaled = dllScaled = glScaled; + qglScalef = dllScalef = glScalef; + qglScissor = dllScissor = glScissor; + qglSelectBuffer = dllSelectBuffer = glSelectBuffer; + qglShadeModel = dllShadeModel = glShadeModel; + qglStencilFunc = dllStencilFunc = glStencilFunc; + qglStencilMask = dllStencilMask = glStencilMask; + qglStencilOp = dllStencilOp = glStencilOp; + qglTexCoord1d = dllTexCoord1d = glTexCoord1d; + qglTexCoord1dv = dllTexCoord1dv = glTexCoord1dv; + qglTexCoord1f = dllTexCoord1f = glTexCoord1f; + qglTexCoord1fv = dllTexCoord1fv = glTexCoord1fv; + qglTexCoord1i = dllTexCoord1i = glTexCoord1i; + qglTexCoord1iv = dllTexCoord1iv = glTexCoord1iv; + qglTexCoord1s = dllTexCoord1s = glTexCoord1s; + qglTexCoord1sv = dllTexCoord1sv = glTexCoord1sv; + qglTexCoord2d = dllTexCoord2d = glTexCoord2d; + qglTexCoord2dv = dllTexCoord2dv = glTexCoord2dv; + qglTexCoord2f = dllTexCoord2f = glTexCoord2f; + qglTexCoord2fv = dllTexCoord2fv = glTexCoord2fv; + qglTexCoord2i = dllTexCoord2i = glTexCoord2i; + qglTexCoord2iv = dllTexCoord2iv = glTexCoord2iv; + qglTexCoord2s = dllTexCoord2s = glTexCoord2s; + qglTexCoord2sv = dllTexCoord2sv = glTexCoord2sv; + qglTexCoord3d = dllTexCoord3d = glTexCoord3d; + qglTexCoord3dv = dllTexCoord3dv = glTexCoord3dv; + qglTexCoord3f = dllTexCoord3f = glTexCoord3f; + qglTexCoord3fv = dllTexCoord3fv = glTexCoord3fv; + qglTexCoord3i = dllTexCoord3i = glTexCoord3i; + qglTexCoord3iv = dllTexCoord3iv = glTexCoord3iv; + qglTexCoord3s = dllTexCoord3s = glTexCoord3s; + qglTexCoord3sv = dllTexCoord3sv = glTexCoord3sv; + qglTexCoord4d = dllTexCoord4d = glTexCoord4d; + qglTexCoord4dv = dllTexCoord4dv = glTexCoord4dv; + qglTexCoord4f = dllTexCoord4f = glTexCoord4f; + qglTexCoord4fv = dllTexCoord4fv = glTexCoord4fv; + qglTexCoord4i = dllTexCoord4i = glTexCoord4i; + qglTexCoord4iv = dllTexCoord4iv = glTexCoord4iv; + qglTexCoord4s = dllTexCoord4s = glTexCoord4s; + qglTexCoord4sv = dllTexCoord4sv = glTexCoord4sv; + qglTexCoordPointer = dllTexCoordPointer = glTexCoordPointer; + qglTexEnvf = dllTexEnvf = glTexEnvf; + qglTexEnvfv = dllTexEnvfv = glTexEnvfv; + qglTexEnvi = dllTexEnvi = glTexEnvi; + qglTexEnviv = dllTexEnviv = glTexEnviv; + qglTexGend = dllTexGend = glTexGend; + qglTexGendv = dllTexGendv = glTexGendv; + qglTexGenf = dllTexGenf = glTexGenf; + qglTexGenfv = dllTexGenfv = glTexGenfv; + qglTexGeni = dllTexGeni = glTexGeni; + qglTexGeniv = dllTexGeniv = glTexGeniv; + qglTexImage1D = dllTexImage1D = glTexImage1D; + qglTexImage2D = dllTexImage2D = glTexImage2D; + qglTexParameterf = dllTexParameterf = glTexParameterf; + qglTexParameterfv = dllTexParameterfv = glTexParameterfv; + qglTexParameteri = dllTexParameteri = glTexParameteri; + qglTexParameteriv = dllTexParameteriv = glTexParameteriv; + qglTexSubImage1D = dllTexSubImage1D = glTexSubImage1D; + qglTexSubImage2D = dllTexSubImage2D = glTexSubImage2D; + qglTranslated = dllTranslated = glTranslated; + qglTranslatef = dllTranslatef = glTranslatef; + qglVertex2d = dllVertex2d = glVertex2d; + qglVertex2dv = dllVertex2dv = glVertex2dv; + qglVertex2f = dllVertex2f = glVertex2f; + qglVertex2fv = dllVertex2fv = glVertex2fv; + qglVertex2i = dllVertex2i = glVertex2i; + qglVertex2iv = dllVertex2iv = glVertex2iv; + qglVertex2s = dllVertex2s = glVertex2s; + qglVertex2sv = dllVertex2sv = glVertex2sv; + qglVertex3d = dllVertex3d = glVertex3d; + qglVertex3dv = dllVertex3dv = glVertex3dv; + qglVertex3f = dllVertex3f = glVertex3f; + qglVertex3fv = dllVertex3fv = glVertex3fv; + qglVertex3i = dllVertex3i = glVertex3i; + qglVertex3iv = dllVertex3iv = glVertex3iv; + qglVertex3s = dllVertex3s = glVertex3s; + qglVertex3sv = dllVertex3sv = glVertex3sv; + qglVertex4d = dllVertex4d = glVertex4d; + qglVertex4dv = dllVertex4dv = glVertex4dv; + qglVertex4f = dllVertex4f = glVertex4f; + qglVertex4fv = dllVertex4fv = glVertex4fv; + qglVertex4i = dllVertex4i = glVertex4i; + qglVertex4iv = dllVertex4iv = glVertex4iv; + qglVertex4s = dllVertex4s = glVertex4s; + qglVertex4sv = dllVertex4sv = glVertex4sv; + qglVertexPointer = dllVertexPointer = glVertexPointer; + qglViewport = dllViewport = glViewport; + + qwglCopyContext = wglCopyContext; + qwglCreateContext = wglCreateContext; + qwglCreateLayerContext = wglCreateLayerContext; + qwglDeleteContext = wglDeleteContext; + qwglDescribeLayerPlane = wglDescribeLayerPlane; + qwglGetCurrentContext = wglGetCurrentContext; + qwglGetCurrentDC = wglGetCurrentDC; + qwglGetLayerPaletteEntries = wglGetLayerPaletteEntries; + qwglGetProcAddress = wglGetProcAddress; + qwglMakeCurrent = wglMakeCurrent; + qwglRealizeLayerPalette = wglRealizeLayerPalette; + qwglSetLayerPaletteEntries = wglSetLayerPaletteEntries; + qwglShareLists = wglShareLists; + qwglSwapLayerBuffers = wglSwapLayerBuffers; + qwglUseFontBitmaps = wglUseFontBitmapsA; + qwglUseFontOutlines = wglUseFontOutlinesA; + + qwglChoosePixelFormat = ChoosePixelFormat; + qwglDescribePixelFormat = DescribePixelFormat; + qwglGetPixelFormat = GetPixelFormat; + qwglSetPixelFormat = SetPixelFormat; + qwglSwapBuffers = SwapBuffers; + + qglActiveTextureARB = 0; + qglClientActiveTextureARB = 0; + qglMultiTexCoord2fARB = 0; + + return true; +} + + +/* +================== +GLimp_EnableLogging + +================== +*/ +void GLimp_EnableLogging( bool enable ) { + static bool isEnabled; + static int initialFrames; + static char ospath[ MAX_OSPATH ]; + +#if defined( Q4_DISABLE_GL_LOGGING ) + enable = false; +#endif + + // return if we're already active + if ( isEnabled && enable ) { + // decrement log counter and stop if it has reached 0 + r_logFile.SetInteger( r_logFile.GetInteger() - 1 ); + if ( r_logFile.GetInteger() ) { + return; + } + common->Printf( "closing logfile '%s' after %i frames.\n", ospath, initialFrames ); + enable = false; + + fclose( qglLogFile ); + qglLogFile = NULL; + + } + + // return if we're already disabled + if ( !enable && !isEnabled ) { + return; + } + + isEnabled = enable; + +#if !defined( Q4_DISABLE_GL_LOGGING ) + if ( enable ) { + if ( !qglLogFile ) { + struct tm *newtime; + ID_TIME_T aclock; + idStr qpath; + int i; + const char *path; + + initialFrames = r_logFile.GetInteger(); + + // scan for an unused filename + for ( i = 0 ; i < 9999 ; i++ ) { + sprintf( qpath, "renderlog_%i.txt", i ); + if ( fileSystem->ReadFile( qpath, NULL, NULL ) == -1 ) { + break; // use this name + } + } + + path = fileSystem->RelativePathToOSPath( qpath, "fs_savepath" ); + idStr::Copynz( ospath, path, sizeof( ospath ) ); + qglLogFile = fopen( ospath, "wt" ); + + // write the time out to the top of the file + time( &aclock ); + newtime = localtime( &aclock ); + fprintf( qglLogFile, "// %s", asctime( newtime ) ); + fprintf( qglLogFile, "// %s\n\n", cvarSystem->GetCVarString( "si_version" ) ); + } + + qglAccum = logAccum; + qglAlphaFunc = logAlphaFunc; + qglAreTexturesResident = logAreTexturesResident; + qglArrayElement = logArrayElement; + qglBegin = logBegin; + qglBindTexture = logBindTexture; + qglBitmap = logBitmap; + qglBlendFunc = logBlendFunc; + qglCallList = logCallList; + qglCallLists = logCallLists; + qglClear = logClear; + qglClearAccum = logClearAccum; + qglClearColor = logClearColor; + qglClearDepth = logClearDepth; + qglClearIndex = logClearIndex; + qglClearStencil = logClearStencil; + qglClipPlane = logClipPlane; + qglColor3b = logColor3b; + qglColor3bv = logColor3bv; + qglColor3d = logColor3d; + qglColor3dv = logColor3dv; + qglColor3f = logColor3f; + qglColor3fv = logColor3fv; + qglColor3i = logColor3i; + qglColor3iv = logColor3iv; + qglColor3s = logColor3s; + qglColor3sv = logColor3sv; + qglColor3ub = logColor3ub; + qglColor3ubv = logColor3ubv; + qglColor3ui = logColor3ui; + qglColor3uiv = logColor3uiv; + qglColor3us = logColor3us; + qglColor3usv = logColor3usv; + qglColor4b = logColor4b; + qglColor4bv = logColor4bv; + qglColor4d = logColor4d; + qglColor4dv = logColor4dv; + qglColor4f = logColor4f; + qglColor4fv = logColor4fv; + qglColor4i = logColor4i; + qglColor4iv = logColor4iv; + qglColor4s = logColor4s; + qglColor4sv = logColor4sv; + qglColor4ub = logColor4ub; + qglColor4ubv = logColor4ubv; + qglColor4ui = logColor4ui; + qglColor4uiv = logColor4uiv; + qglColor4us = logColor4us; + qglColor4usv = logColor4usv; + qglColorMask = logColorMask; + qglColorMaterial = logColorMaterial; + qglColorPointer = logColorPointer; + qglCopyPixels = logCopyPixels; + qglCopyTexImage1D = logCopyTexImage1D; + qglCopyTexImage2D = logCopyTexImage2D; + qglCopyTexSubImage1D = logCopyTexSubImage1D; + qglCopyTexSubImage2D = logCopyTexSubImage2D; + qglCullFace = logCullFace; + qglDeleteLists = logDeleteLists ; + qglDeleteTextures = logDeleteTextures ; + qglDepthFunc = logDepthFunc ; + qglDepthMask = logDepthMask ; + qglDepthRange = logDepthRange ; + qglDisable = logDisable ; + qglDisableClientState = logDisableClientState ; + qglDrawArrays = logDrawArrays ; + qglDrawBuffer = logDrawBuffer ; + qglDrawElements = logDrawElements ; + qglDrawPixels = logDrawPixels ; + qglEdgeFlag = logEdgeFlag ; + qglEdgeFlagPointer = logEdgeFlagPointer ; + qglEdgeFlagv = logEdgeFlagv ; + qglEnable = logEnable ; + qglEnableClientState = logEnableClientState ; + qglEnd = logEnd ; + qglEndList = logEndList ; + qglEvalCoord1d = logEvalCoord1d ; + qglEvalCoord1dv = logEvalCoord1dv ; + qglEvalCoord1f = logEvalCoord1f ; + qglEvalCoord1fv = logEvalCoord1fv ; + qglEvalCoord2d = logEvalCoord2d ; + qglEvalCoord2dv = logEvalCoord2dv ; + qglEvalCoord2f = logEvalCoord2f ; + qglEvalCoord2fv = logEvalCoord2fv ; + qglEvalMesh1 = logEvalMesh1 ; + qglEvalMesh2 = logEvalMesh2 ; + qglEvalPoint1 = logEvalPoint1 ; + qglEvalPoint2 = logEvalPoint2 ; + qglFeedbackBuffer = logFeedbackBuffer ; + qglFinish = logFinish ; + qglFlush = logFlush ; + qglFogf = logFogf ; + qglFogfv = logFogfv ; + qglFogi = logFogi ; + qglFogiv = logFogiv ; + qglFrontFace = logFrontFace ; + qglFrustum = logFrustum ; + qglGenLists = logGenLists ; + qglGenTextures = logGenTextures ; + qglGetBooleanv = logGetBooleanv ; + qglGetClipPlane = logGetClipPlane ; + qglGetDoublev = logGetDoublev ; + qglGetError = logGetError ; + qglGetFloatv = logGetFloatv ; + qglGetIntegerv = logGetIntegerv ; + qglGetLightfv = logGetLightfv ; + qglGetLightiv = logGetLightiv ; + qglGetMapdv = logGetMapdv ; + qglGetMapfv = logGetMapfv ; + qglGetMapiv = logGetMapiv ; + qglGetMaterialfv = logGetMaterialfv ; + qglGetMaterialiv = logGetMaterialiv ; + qglGetPixelMapfv = logGetPixelMapfv ; + qglGetPixelMapuiv = logGetPixelMapuiv ; + qglGetPixelMapusv = logGetPixelMapusv ; + qglGetPointerv = logGetPointerv ; + qglGetPolygonStipple = logGetPolygonStipple ; + qglGetString = logGetString ; + qglGetTexEnvfv = logGetTexEnvfv ; + qglGetTexEnviv = logGetTexEnviv ; + qglGetTexGendv = logGetTexGendv ; + qglGetTexGenfv = logGetTexGenfv ; + qglGetTexGeniv = logGetTexGeniv ; + qglGetTexImage = logGetTexImage ; + qglGetTexLevelParameterfv = logGetTexLevelParameterfv ; + qglGetTexLevelParameteriv = logGetTexLevelParameteriv ; + qglGetTexParameterfv = logGetTexParameterfv ; + qglGetTexParameteriv = logGetTexParameteriv ; + qglHint = logHint ; + qglIndexMask = logIndexMask ; + qglIndexPointer = logIndexPointer ; + qglIndexd = logIndexd ; + qglIndexdv = logIndexdv ; + qglIndexf = logIndexf ; + qglIndexfv = logIndexfv ; + qglIndexi = logIndexi ; + qglIndexiv = logIndexiv ; + qglIndexs = logIndexs ; + qglIndexsv = logIndexsv ; + qglIndexub = logIndexub ; + qglIndexubv = logIndexubv ; + qglInitNames = logInitNames ; + qglInterleavedArrays = logInterleavedArrays ; + qglIsEnabled = logIsEnabled ; + qglIsList = logIsList ; + qglIsTexture = logIsTexture ; + qglLightModelf = logLightModelf ; + qglLightModelfv = logLightModelfv ; + qglLightModeli = logLightModeli ; + qglLightModeliv = logLightModeliv ; + qglLightf = logLightf ; + qglLightfv = logLightfv ; + qglLighti = logLighti ; + qglLightiv = logLightiv ; + qglLineStipple = logLineStipple ; + qglLineWidth = logLineWidth ; + qglListBase = logListBase ; + qglLoadIdentity = logLoadIdentity ; + qglLoadMatrixd = logLoadMatrixd ; + qglLoadMatrixf = logLoadMatrixf ; + qglLoadName = logLoadName ; + qglLogicOp = logLogicOp ; + qglMap1d = logMap1d ; + qglMap1f = logMap1f ; + qglMap2d = logMap2d ; + qglMap2f = logMap2f ; + qglMapGrid1d = logMapGrid1d ; + qglMapGrid1f = logMapGrid1f ; + qglMapGrid2d = logMapGrid2d ; + qglMapGrid2f = logMapGrid2f ; + qglMaterialf = logMaterialf ; + qglMaterialfv = logMaterialfv ; + qglMateriali = logMateriali ; + qglMaterialiv = logMaterialiv ; + qglMatrixMode = logMatrixMode ; + qglMultMatrixd = logMultMatrixd ; + qglMultMatrixf = logMultMatrixf ; + qglNewList = logNewList ; + qglNormal3b = logNormal3b ; + qglNormal3bv = logNormal3bv ; + qglNormal3d = logNormal3d ; + qglNormal3dv = logNormal3dv ; + qglNormal3f = logNormal3f ; + qglNormal3fv = logNormal3fv ; + qglNormal3i = logNormal3i ; + qglNormal3iv = logNormal3iv ; + qglNormal3s = logNormal3s ; + qglNormal3sv = logNormal3sv ; + qglNormalPointer = logNormalPointer ; + qglOrtho = logOrtho ; + qglPassThrough = logPassThrough ; + qglPixelMapfv = logPixelMapfv ; + qglPixelMapuiv = logPixelMapuiv ; + qglPixelMapusv = logPixelMapusv ; + qglPixelStoref = logPixelStoref ; + qglPixelStorei = logPixelStorei ; + qglPixelTransferf = logPixelTransferf ; + qglPixelTransferi = logPixelTransferi ; + qglPixelZoom = logPixelZoom ; + qglPointSize = logPointSize ; + qglPolygonMode = logPolygonMode ; + qglPolygonOffset = logPolygonOffset ; + qglPolygonStipple = logPolygonStipple ; + qglPopAttrib = logPopAttrib ; + qglPopClientAttrib = logPopClientAttrib ; + qglPopMatrix = logPopMatrix ; + qglPopName = logPopName ; + qglPrioritizeTextures = logPrioritizeTextures ; + qglPushAttrib = logPushAttrib ; + qglPushClientAttrib = logPushClientAttrib ; + qglPushMatrix = logPushMatrix ; + qglPushName = logPushName ; + qglRasterPos2d = logRasterPos2d ; + qglRasterPos2dv = logRasterPos2dv ; + qglRasterPos2f = logRasterPos2f ; + qglRasterPos2fv = logRasterPos2fv ; + qglRasterPos2i = logRasterPos2i ; + qglRasterPos2iv = logRasterPos2iv ; + qglRasterPos2s = logRasterPos2s ; + qglRasterPos2sv = logRasterPos2sv ; + qglRasterPos3d = logRasterPos3d ; + qglRasterPos3dv = logRasterPos3dv ; + qglRasterPos3f = logRasterPos3f ; + qglRasterPos3fv = logRasterPos3fv ; + qglRasterPos3i = logRasterPos3i ; + qglRasterPos3iv = logRasterPos3iv ; + qglRasterPos3s = logRasterPos3s ; + qglRasterPos3sv = logRasterPos3sv ; + qglRasterPos4d = logRasterPos4d ; + qglRasterPos4dv = logRasterPos4dv ; + qglRasterPos4f = logRasterPos4f ; + qglRasterPos4fv = logRasterPos4fv ; + qglRasterPos4i = logRasterPos4i ; + qglRasterPos4iv = logRasterPos4iv ; + qglRasterPos4s = logRasterPos4s ; + qglRasterPos4sv = logRasterPos4sv ; + qglReadBuffer = logReadBuffer ; + qglReadPixels = logReadPixels ; + qglRectd = logRectd ; + qglRectdv = logRectdv ; + qglRectf = logRectf ; + qglRectfv = logRectfv ; + qglRecti = logRecti ; + qglRectiv = logRectiv ; + qglRects = logRects ; + qglRectsv = logRectsv ; + qglRenderMode = logRenderMode ; + qglRotated = logRotated ; + qglRotatef = logRotatef ; + qglScaled = logScaled ; + qglScalef = logScalef ; + qglScissor = logScissor ; + qglSelectBuffer = logSelectBuffer ; + qglShadeModel = logShadeModel ; + qglStencilFunc = logStencilFunc ; + qglStencilMask = logStencilMask ; + qglStencilOp = logStencilOp ; + qglTexCoord1d = logTexCoord1d ; + qglTexCoord1dv = logTexCoord1dv ; + qglTexCoord1f = logTexCoord1f ; + qglTexCoord1fv = logTexCoord1fv ; + qglTexCoord1i = logTexCoord1i ; + qglTexCoord1iv = logTexCoord1iv ; + qglTexCoord1s = logTexCoord1s ; + qglTexCoord1sv = logTexCoord1sv ; + qglTexCoord2d = logTexCoord2d ; + qglTexCoord2dv = logTexCoord2dv ; + qglTexCoord2f = logTexCoord2f ; + qglTexCoord2fv = logTexCoord2fv ; + qglTexCoord2i = logTexCoord2i ; + qglTexCoord2iv = logTexCoord2iv ; + qglTexCoord2s = logTexCoord2s ; + qglTexCoord2sv = logTexCoord2sv ; + qglTexCoord3d = logTexCoord3d ; + qglTexCoord3dv = logTexCoord3dv ; + qglTexCoord3f = logTexCoord3f ; + qglTexCoord3fv = logTexCoord3fv ; + qglTexCoord3i = logTexCoord3i ; + qglTexCoord3iv = logTexCoord3iv ; + qglTexCoord3s = logTexCoord3s ; + qglTexCoord3sv = logTexCoord3sv ; + qglTexCoord4d = logTexCoord4d ; + qglTexCoord4dv = logTexCoord4dv ; + qglTexCoord4f = logTexCoord4f ; + qglTexCoord4fv = logTexCoord4fv ; + qglTexCoord4i = logTexCoord4i ; + qglTexCoord4iv = logTexCoord4iv ; + qglTexCoord4s = logTexCoord4s ; + qglTexCoord4sv = logTexCoord4sv ; + qglTexCoordPointer = logTexCoordPointer ; + qglTexEnvf = logTexEnvf ; + qglTexEnvfv = logTexEnvfv ; + qglTexEnvi = logTexEnvi ; + qglTexEnviv = logTexEnviv ; + qglTexGend = logTexGend ; + qglTexGendv = logTexGendv ; + qglTexGenf = logTexGenf ; + qglTexGenfv = logTexGenfv ; + qglTexGeni = logTexGeni ; + qglTexGeniv = logTexGeniv ; + qglTexImage1D = logTexImage1D ; + qglTexImage2D = logTexImage2D ; + qglTexParameterf = logTexParameterf ; + qglTexParameterfv = logTexParameterfv ; + qglTexParameteri = logTexParameteri ; + qglTexParameteriv = logTexParameteriv ; + qglTexSubImage1D = logTexSubImage1D ; + qglTexSubImage2D = logTexSubImage2D ; + qglTranslated = logTranslated ; + qglTranslatef = logTranslatef ; + qglVertex2d = logVertex2d ; + qglVertex2dv = logVertex2dv ; + qglVertex2f = logVertex2f ; + qglVertex2fv = logVertex2fv ; + qglVertex2i = logVertex2i ; + qglVertex2iv = logVertex2iv ; + qglVertex2s = logVertex2s ; + qglVertex2sv = logVertex2sv ; + qglVertex3d = logVertex3d ; + qglVertex3dv = logVertex3dv ; + qglVertex3f = logVertex3f ; + qglVertex3fv = logVertex3fv ; + qglVertex3i = logVertex3i ; + qglVertex3iv = logVertex3iv ; + qglVertex3s = logVertex3s ; + qglVertex3sv = logVertex3sv ; + qglVertex4d = logVertex4d ; + qglVertex4dv = logVertex4dv ; + qglVertex4f = logVertex4f ; + qglVertex4fv = logVertex4fv ; + qglVertex4i = logVertex4i ; + qglVertex4iv = logVertex4iv ; + qglVertex4s = logVertex4s ; + qglVertex4sv = logVertex4sv ; + qglVertexPointer = logVertexPointer ; + qglViewport = logViewport ; + } + else +#endif + { + qglAccum = dllAccum; + qglAlphaFunc = dllAlphaFunc; + qglAreTexturesResident = dllAreTexturesResident; + qglArrayElement = dllArrayElement; + qglBegin = dllBegin; + qglBindTexture = dllBindTexture; + qglBitmap = dllBitmap; + qglBlendFunc = dllBlendFunc; + qglCallList = dllCallList; + qglCallLists = dllCallLists; + qglClear = dllClear; + qglClearAccum = dllClearAccum; + qglClearColor = dllClearColor; + qglClearDepth = dllClearDepth; + qglClearIndex = dllClearIndex; + qglClearStencil = dllClearStencil; + qglClipPlane = dllClipPlane; + qglColor3b = dllColor3b; + qglColor3bv = dllColor3bv; + qglColor3d = dllColor3d; + qglColor3dv = dllColor3dv; + qglColor3f = dllColor3f; + qglColor3fv = dllColor3fv; + qglColor3i = dllColor3i; + qglColor3iv = dllColor3iv; + qglColor3s = dllColor3s; + qglColor3sv = dllColor3sv; + qglColor3ub = dllColor3ub; + qglColor3ubv = dllColor3ubv; + qglColor3ui = dllColor3ui; + qglColor3uiv = dllColor3uiv; + qglColor3us = dllColor3us; + qglColor3usv = dllColor3usv; + qglColor4b = dllColor4b; + qglColor4bv = dllColor4bv; + qglColor4d = dllColor4d; + qglColor4dv = dllColor4dv; + qglColor4f = dllColor4f; + qglColor4fv = dllColor4fv; + qglColor4i = dllColor4i; + qglColor4iv = dllColor4iv; + qglColor4s = dllColor4s; + qglColor4sv = dllColor4sv; + qglColor4ub = dllColor4ub; + qglColor4ubv = dllColor4ubv; + qglColor4ui = dllColor4ui; + qglColor4uiv = dllColor4uiv; + qglColor4us = dllColor4us; + qglColor4usv = dllColor4usv; + qglColorMask = dllColorMask; + qglColorMaterial = dllColorMaterial; + qglColorPointer = dllColorPointer; + qglCopyPixels = dllCopyPixels; + qglCopyTexImage1D = dllCopyTexImage1D; + qglCopyTexImage2D = dllCopyTexImage2D; + qglCopyTexSubImage1D = dllCopyTexSubImage1D; + qglCopyTexSubImage2D = dllCopyTexSubImage2D; + qglCullFace = dllCullFace; + qglDeleteLists = dllDeleteLists ; + qglDeleteTextures = dllDeleteTextures ; + qglDepthFunc = dllDepthFunc ; + qglDepthMask = dllDepthMask ; + qglDepthRange = dllDepthRange ; + qglDisable = dllDisable ; + qglDisableClientState = dllDisableClientState ; + qglDrawArrays = dllDrawArrays ; + qglDrawBuffer = dllDrawBuffer ; + qglDrawElements = dllDrawElements ; + qglDrawPixels = dllDrawPixels ; + qglEdgeFlag = dllEdgeFlag ; + qglEdgeFlagPointer = dllEdgeFlagPointer ; + qglEdgeFlagv = dllEdgeFlagv ; + qglEnable = dllEnable ; + qglEnableClientState = dllEnableClientState ; + qglEnd = dllEnd ; + qglEndList = dllEndList ; + qglEvalCoord1d = dllEvalCoord1d ; + qglEvalCoord1dv = dllEvalCoord1dv ; + qglEvalCoord1f = dllEvalCoord1f ; + qglEvalCoord1fv = dllEvalCoord1fv ; + qglEvalCoord2d = dllEvalCoord2d ; + qglEvalCoord2dv = dllEvalCoord2dv ; + qglEvalCoord2f = dllEvalCoord2f ; + qglEvalCoord2fv = dllEvalCoord2fv ; + qglEvalMesh1 = dllEvalMesh1 ; + qglEvalMesh2 = dllEvalMesh2 ; + qglEvalPoint1 = dllEvalPoint1 ; + qglEvalPoint2 = dllEvalPoint2 ; + qglFeedbackBuffer = dllFeedbackBuffer ; + qglFinish = dllFinish ; + qglFlush = dllFlush ; + qglFogf = dllFogf ; + qglFogfv = dllFogfv ; + qglFogi = dllFogi ; + qglFogiv = dllFogiv ; + qglFrontFace = dllFrontFace ; + qglFrustum = dllFrustum ; + qglGenLists = dllGenLists ; + qglGenTextures = dllGenTextures ; + qglGetBooleanv = dllGetBooleanv ; + qglGetClipPlane = dllGetClipPlane ; + qglGetDoublev = dllGetDoublev ; + qglGetError = dllGetError ; + qglGetFloatv = dllGetFloatv ; + qglGetIntegerv = dllGetIntegerv ; + qglGetLightfv = dllGetLightfv ; + qglGetLightiv = dllGetLightiv ; + qglGetMapdv = dllGetMapdv ; + qglGetMapfv = dllGetMapfv ; + qglGetMapiv = dllGetMapiv ; + qglGetMaterialfv = dllGetMaterialfv ; + qglGetMaterialiv = dllGetMaterialiv ; + qglGetPixelMapfv = dllGetPixelMapfv ; + qglGetPixelMapuiv = dllGetPixelMapuiv ; + qglGetPixelMapusv = dllGetPixelMapusv ; + qglGetPointerv = dllGetPointerv ; + qglGetPolygonStipple = dllGetPolygonStipple ; + qglGetString = dllGetString ; + qglGetTexEnvfv = dllGetTexEnvfv ; + qglGetTexEnviv = dllGetTexEnviv ; + qglGetTexGendv = dllGetTexGendv ; + qglGetTexGenfv = dllGetTexGenfv ; + qglGetTexGeniv = dllGetTexGeniv ; + qglGetTexImage = dllGetTexImage ; + qglGetTexLevelParameterfv = dllGetTexLevelParameterfv ; + qglGetTexLevelParameteriv = dllGetTexLevelParameteriv ; + qglGetTexParameterfv = dllGetTexParameterfv ; + qglGetTexParameteriv = dllGetTexParameteriv ; + qglHint = dllHint ; + qglIndexMask = dllIndexMask ; + qglIndexPointer = dllIndexPointer ; + qglIndexd = dllIndexd ; + qglIndexdv = dllIndexdv ; + qglIndexf = dllIndexf ; + qglIndexfv = dllIndexfv ; + qglIndexi = dllIndexi ; + qglIndexiv = dllIndexiv ; + qglIndexs = dllIndexs ; + qglIndexsv = dllIndexsv ; + qglIndexub = dllIndexub ; + qglIndexubv = dllIndexubv ; + qglInitNames = dllInitNames ; + qglInterleavedArrays = dllInterleavedArrays ; + qglIsEnabled = dllIsEnabled ; + qglIsList = dllIsList ; + qglIsTexture = dllIsTexture ; + qglLightModelf = dllLightModelf ; + qglLightModelfv = dllLightModelfv ; + qglLightModeli = dllLightModeli ; + qglLightModeliv = dllLightModeliv ; + qglLightf = dllLightf ; + qglLightfv = dllLightfv ; + qglLighti = dllLighti ; + qglLightiv = dllLightiv ; + qglLineStipple = dllLineStipple ; + qglLineWidth = dllLineWidth ; + qglListBase = dllListBase ; + qglLoadIdentity = dllLoadIdentity ; + qglLoadMatrixd = dllLoadMatrixd ; + qglLoadMatrixf = dllLoadMatrixf ; + qglLoadName = dllLoadName ; + qglLogicOp = dllLogicOp ; + qglMap1d = dllMap1d ; + qglMap1f = dllMap1f ; + qglMap2d = dllMap2d ; + qglMap2f = dllMap2f ; + qglMapGrid1d = dllMapGrid1d ; + qglMapGrid1f = dllMapGrid1f ; + qglMapGrid2d = dllMapGrid2d ; + qglMapGrid2f = dllMapGrid2f ; + qglMaterialf = dllMaterialf ; + qglMaterialfv = dllMaterialfv ; + qglMateriali = dllMateriali ; + qglMaterialiv = dllMaterialiv ; + qglMatrixMode = dllMatrixMode ; + qglMultMatrixd = dllMultMatrixd ; + qglMultMatrixf = dllMultMatrixf ; + qglNewList = dllNewList ; + qglNormal3b = dllNormal3b ; + qglNormal3bv = dllNormal3bv ; + qglNormal3d = dllNormal3d ; + qglNormal3dv = dllNormal3dv ; + qglNormal3f = dllNormal3f ; + qglNormal3fv = dllNormal3fv ; + qglNormal3i = dllNormal3i ; + qglNormal3iv = dllNormal3iv ; + qglNormal3s = dllNormal3s ; + qglNormal3sv = dllNormal3sv ; + qglNormalPointer = dllNormalPointer ; + qglOrtho = dllOrtho ; + qglPassThrough = dllPassThrough ; + qglPixelMapfv = dllPixelMapfv ; + qglPixelMapuiv = dllPixelMapuiv ; + qglPixelMapusv = dllPixelMapusv ; + qglPixelStoref = dllPixelStoref ; + qglPixelStorei = dllPixelStorei ; + qglPixelTransferf = dllPixelTransferf ; + qglPixelTransferi = dllPixelTransferi ; + qglPixelZoom = dllPixelZoom ; + qglPointSize = dllPointSize ; + qglPolygonMode = dllPolygonMode ; + qglPolygonOffset = dllPolygonOffset ; + qglPolygonStipple = dllPolygonStipple ; + qglPopAttrib = dllPopAttrib ; + qglPopClientAttrib = dllPopClientAttrib ; + qglPopMatrix = dllPopMatrix ; + qglPopName = dllPopName ; + qglPrioritizeTextures = dllPrioritizeTextures ; + qglPushAttrib = dllPushAttrib ; + qglPushClientAttrib = dllPushClientAttrib ; + qglPushMatrix = dllPushMatrix ; + qglPushName = dllPushName ; + qglRasterPos2d = dllRasterPos2d ; + qglRasterPos2dv = dllRasterPos2dv ; + qglRasterPos2f = dllRasterPos2f ; + qglRasterPos2fv = dllRasterPos2fv ; + qglRasterPos2i = dllRasterPos2i ; + qglRasterPos2iv = dllRasterPos2iv ; + qglRasterPos2s = dllRasterPos2s ; + qglRasterPos2sv = dllRasterPos2sv ; + qglRasterPos3d = dllRasterPos3d ; + qglRasterPos3dv = dllRasterPos3dv ; + qglRasterPos3f = dllRasterPos3f ; + qglRasterPos3fv = dllRasterPos3fv ; + qglRasterPos3i = dllRasterPos3i ; + qglRasterPos3iv = dllRasterPos3iv ; + qglRasterPos3s = dllRasterPos3s ; + qglRasterPos3sv = dllRasterPos3sv ; + qglRasterPos4d = dllRasterPos4d ; + qglRasterPos4dv = dllRasterPos4dv ; + qglRasterPos4f = dllRasterPos4f ; + qglRasterPos4fv = dllRasterPos4fv ; + qglRasterPos4i = dllRasterPos4i ; + qglRasterPos4iv = dllRasterPos4iv ; + qglRasterPos4s = dllRasterPos4s ; + qglRasterPos4sv = dllRasterPos4sv ; + qglReadBuffer = dllReadBuffer ; + qglReadPixels = dllReadPixels ; + qglRectd = dllRectd ; + qglRectdv = dllRectdv ; + qglRectf = dllRectf ; + qglRectfv = dllRectfv ; + qglRecti = dllRecti ; + qglRectiv = dllRectiv ; + qglRects = dllRects ; + qglRectsv = dllRectsv ; + qglRenderMode = dllRenderMode ; + qglRotated = dllRotated ; + qglRotatef = dllRotatef ; + qglScaled = dllScaled ; + qglScalef = dllScalef ; + qglScissor = dllScissor ; + qglSelectBuffer = dllSelectBuffer ; + qglShadeModel = dllShadeModel ; + qglStencilFunc = dllStencilFunc ; + qglStencilMask = dllStencilMask ; + qglStencilOp = dllStencilOp ; + qglTexCoord1d = dllTexCoord1d ; + qglTexCoord1dv = dllTexCoord1dv ; + qglTexCoord1f = dllTexCoord1f ; + qglTexCoord1fv = dllTexCoord1fv ; + qglTexCoord1i = dllTexCoord1i ; + qglTexCoord1iv = dllTexCoord1iv ; + qglTexCoord1s = dllTexCoord1s ; + qglTexCoord1sv = dllTexCoord1sv ; + qglTexCoord2d = dllTexCoord2d ; + qglTexCoord2dv = dllTexCoord2dv ; + qglTexCoord2f = dllTexCoord2f ; + qglTexCoord2fv = dllTexCoord2fv ; + qglTexCoord2i = dllTexCoord2i ; + qglTexCoord2iv = dllTexCoord2iv ; + qglTexCoord2s = dllTexCoord2s ; + qglTexCoord2sv = dllTexCoord2sv ; + qglTexCoord3d = dllTexCoord3d ; + qglTexCoord3dv = dllTexCoord3dv ; + qglTexCoord3f = dllTexCoord3f ; + qglTexCoord3fv = dllTexCoord3fv ; + qglTexCoord3i = dllTexCoord3i ; + qglTexCoord3iv = dllTexCoord3iv ; + qglTexCoord3s = dllTexCoord3s ; + qglTexCoord3sv = dllTexCoord3sv ; + qglTexCoord4d = dllTexCoord4d ; + qglTexCoord4dv = dllTexCoord4dv ; + qglTexCoord4f = dllTexCoord4f ; + qglTexCoord4fv = dllTexCoord4fv ; + qglTexCoord4i = dllTexCoord4i ; + qglTexCoord4iv = dllTexCoord4iv ; + qglTexCoord4s = dllTexCoord4s ; + qglTexCoord4sv = dllTexCoord4sv ; + qglTexCoordPointer = dllTexCoordPointer ; + qglTexEnvf = dllTexEnvf ; + qglTexEnvfv = dllTexEnvfv ; + qglTexEnvi = dllTexEnvi ; + qglTexEnviv = dllTexEnviv ; + qglTexGend = dllTexGend ; + qglTexGendv = dllTexGendv ; + qglTexGenf = dllTexGenf ; + qglTexGenfv = dllTexGenfv ; + qglTexGeni = dllTexGeni ; + qglTexGeniv = dllTexGeniv ; + qglTexImage1D = dllTexImage1D ; + qglTexImage2D = dllTexImage2D ; + qglTexParameterf = dllTexParameterf ; + qglTexParameterfv = dllTexParameterfv ; + qglTexParameteri = dllTexParameteri ; + qglTexParameteriv = dllTexParameteriv ; + qglTexSubImage1D = dllTexSubImage1D ; + qglTexSubImage2D = dllTexSubImage2D ; + qglTranslated = dllTranslated ; + qglTranslatef = dllTranslatef ; + qglVertex2d = dllVertex2d ; + qglVertex2dv = dllVertex2dv ; + qglVertex2f = dllVertex2f ; + qglVertex2fv = dllVertex2fv ; + qglVertex2i = dllVertex2i ; + qglVertex2iv = dllVertex2iv ; + qglVertex2s = dllVertex2s ; + qglVertex2sv = dllVertex2sv ; + qglVertex3d = dllVertex3d ; + qglVertex3dv = dllVertex3dv ; + qglVertex3f = dllVertex3f ; + qglVertex3fv = dllVertex3fv ; + qglVertex3i = dllVertex3i ; + qglVertex3iv = dllVertex3iv ; + qglVertex3s = dllVertex3s ; + qglVertex3sv = dllVertex3sv ; + qglVertex4d = dllVertex4d ; + qglVertex4dv = dllVertex4dv ; + qglVertex4f = dllVertex4f ; + qglVertex4fv = dllVertex4fv ; + qglVertex4i = dllVertex4i ; + qglVertex4iv = dllVertex4iv ; + qglVertex4s = dllVertex4s ; + qglVertex4sv = dllVertex4sv ; + qglVertexPointer = dllVertexPointer ; + qglViewport = dllViewport ; + } +} diff --git a/src/sys/win32/win_shared.cpp b/src/sys/win32/win_shared.cpp new file mode 100644 index 0000000..cf56c09 --- /dev/null +++ b/src/sys/win32/win_shared.cpp @@ -0,0 +1,805 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "win_local.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// idMath deliberately undefines the CRT integer limit macros. ATL's legacy +// allocation helpers still consume them as macros. +#ifndef INT_MIN +#define INT_MIN ( -2147483647 - 1 ) +#endif +#ifndef INT_MAX +#define INT_MAX 2147483647 +#endif + +#ifndef ID_DEDICATED +#include +#include +#include +#include +#include +#include + +#pragma comment (lib, "wbemuuid.lib") +#endif + +int Sys_MapKey( unsigned long key, unsigned short wParam ) { + const unsigned int scanCode = ( key >> 16 ) & 0xff; + if ( scanCode == 0x29 ) { + return 1; + } + if ( scanCode == 0x2a ) { + return K_SHIFT; + } + if ( scanCode == 0x36 ) { + return K_SHIFT; + } + const int mapped = MapKey( (int)key ); + if ( mapped ) { + return mapped; + } + return (int)MapVirtualKeyA( wParam, MAPVK_VK_TO_CHAR ); +} + +/* +================ +Sys_Milliseconds +================ +*/ +int Sys_Milliseconds( void ) { + int sys_curtime; + static int sys_timeBase; + static bool initialized = false; + + if ( !initialized ) { + sys_timeBase = timeGetTime(); + initialized = true; + } + sys_curtime = timeGetTime() - sys_timeBase; + + return sys_curtime; +} + +/* +================ +Sys_GetSystemRam + + returns amount of physical memory in MB +================ +*/ +int Sys_GetSystemRam( void ) { + MEMORYSTATUSEX statex; + statex.dwLength = sizeof ( statex ); + GlobalMemoryStatusEx (&statex); + int physRam = statex.ullTotalPhys / ( 1024 * 1024 ); + // HACK: For some reason, ullTotalPhys is sometimes off by a meg or two, so we round up to the nearest 16 megs + physRam = ( physRam + 8 ) & ~15; + return physRam; +} + + +/* +================ +Sys_GetDriveFreeSpace +returns in megabytes +================ +*/ +int Sys_GetDriveFreeSpace( const char *path ) { + DWORDLONG lpFreeBytesAvailable; + DWORDLONG lpTotalNumberOfBytes; + DWORDLONG lpTotalNumberOfFreeBytes; + int ret = 26; + //FIXME: see why this is failing on some machines + if ( ::GetDiskFreeSpaceEx( path, (PULARGE_INTEGER)&lpFreeBytesAvailable, (PULARGE_INTEGER)&lpTotalNumberOfBytes, (PULARGE_INTEGER)&lpTotalNumberOfFreeBytes ) ) { + ret = ( double )( lpFreeBytesAvailable ) / ( 1024.0 * 1024.0 ); + } + return ret; +} + + +/* +================ +Sys_GetVideoRam +returns in megabytes +================ +*/ +int Sys_GetVideoRam( void ) { +#ifdef ID_DEDICATED + return 0; +#else + unsigned int retSize = 64; + + CComPtr spLoc = NULL; + HRESULT hr = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, ( LPVOID * ) &spLoc ); + if ( hr != S_OK || spLoc == NULL ) { + return retSize; + } + + CComBSTR bstrNamespace( _T( "\\\\.\\root\\CIMV2" ) ); + CComPtr spServices; + + // Connect to CIM + hr = spLoc->ConnectServer( bstrNamespace, NULL, NULL, 0, NULL, 0, 0, &spServices ); + if ( hr != WBEM_S_NO_ERROR ) { + return retSize; + } + + // Switch the security level to IMPERSONATE so that provider will grant access to system-level objects. + hr = CoSetProxyBlanket( spServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE ); + if ( hr != S_OK ) { + return retSize; + } + + // Get the vid controller + CComPtr spEnumInst = NULL; + hr = spServices->CreateInstanceEnum( CComBSTR( "Win32_VideoController" ), WBEM_FLAG_SHALLOW, NULL, &spEnumInst ); + if ( hr != WBEM_S_NO_ERROR || spEnumInst == NULL ) { + return retSize; + } + + ULONG uNumOfInstances = 0; + CComPtr spInstance = NULL; + hr = spEnumInst->Next( 10000, 1, &spInstance, &uNumOfInstances ); + + if ( hr == S_OK && spInstance ) { + // Get properties from the object + CComVariant varSize; + hr = spInstance->Get( CComBSTR( _T( "AdapterRAM" ) ), 0, &varSize, 0, 0 ); + if ( hr == S_OK ) { + retSize = varSize.intVal / ( 1024 * 1024 ); + if ( retSize == 0 ) { + retSize = 64; + } + } + } + return retSize; +#endif +} + +/* +================ +Sys_GetCurrentMemoryStatus + + returns OS mem info + all values are in kB except the memoryload +================ +*/ +void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) { + MEMORYSTATUSEX statex; + unsigned __int64 work; + + memset( &statex, sizeof( statex ), 0 ); + statex.dwLength = sizeof( statex ); + GlobalMemoryStatusEx( &statex ); + + memset( &stats, 0, sizeof( stats ) ); + + stats.memoryLoad = statex.dwMemoryLoad; + + work = statex.ullTotalPhys >> 20; + stats.totalPhysical = *(int*)&work; + + work = statex.ullAvailPhys >> 20; + stats.availPhysical = *(int*)&work; + + work = statex.ullAvailPageFile >> 20; + stats.availPageFile = *(int*)&work; + + work = statex.ullTotalPageFile >> 20; + stats.totalPageFile = *(int*)&work; + + work = statex.ullTotalVirtual >> 20; + stats.totalVirtual = *(int*)&work; + + work = statex.ullAvailVirtual >> 20; + stats.availVirtual = *(int*)&work; + + work = statex.ullAvailExtendedVirtual >> 20; + stats.availExtendedVirtual = *(int*)&work; +} + +/* +================ +Sys_LockMemory +================ +*/ +bool Sys_LockMemory( void *ptr, int bytes ) { + return ( VirtualLock( ptr, (SIZE_T)bytes ) != FALSE ); +} + +/* +================ +Sys_UnlockMemory +================ +*/ +bool Sys_UnlockMemory( void *ptr, int bytes ) { + return ( VirtualUnlock( ptr, (SIZE_T)bytes ) != FALSE ); +} + +/* +================ +Sys_SetPhysicalWorkMemory +================ +*/ +void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ) { + ::SetProcessWorkingSetSize( GetCurrentProcess(), minBytes, maxBytes ); +} + +/* +================ +Sys_GetCurrentUser +================ +*/ +char *Sys_GetCurrentUser( void ) { + static char s_userName[1024]; + unsigned long size = sizeof( s_userName ); + + + if ( !GetUserName( s_userName, &size ) ) { + strcpy( s_userName, "player" ); + } + + if ( !s_userName[0] ) { + strcpy( s_userName, "player" ); + } + + return s_userName; +} + + +/* +=============================================================================== + + Call stack + +=============================================================================== +*/ + + +#define PROLOGUE_SIGNATURE 0x00EC8B55 + +#include + +const int UNDECORATE_FLAGS = UNDNAME_NO_MS_KEYWORDS | + UNDNAME_NO_ACCESS_SPECIFIERS | + UNDNAME_NO_FUNCTION_RETURNS | + UNDNAME_NO_ALLOCATION_MODEL | + UNDNAME_NO_ALLOCATION_LANGUAGE | + UNDNAME_NO_MEMBER_TYPE; + +#if defined(_DEBUG) && 1 + +typedef struct symbol_s { + int address; + char * name; + struct symbol_s * next; +} symbol_t; + +typedef struct module_s { + int address; + char * name; + symbol_t * symbols; + struct module_s * next; +} module_t; + +module_t *modules; + +/* +================== +SkipRestOfLine +================== +*/ +void SkipRestOfLine( const char **ptr ) { + while( (**ptr) != '\0' && (**ptr) != '\n' && (**ptr) != '\r' ) { + (*ptr)++; + } + while( (**ptr) == '\n' || (**ptr) == '\r' ) { + (*ptr)++; + } +} + +/* +================== +SkipWhiteSpace +================== +*/ +void SkipWhiteSpace( const char **ptr ) { + while( (**ptr) == ' ' ) { + (*ptr)++; + } +} + +/* +================== +ParseHexNumber +================== +*/ +int ParseHexNumber( const char **ptr ) { + int n = 0; + while( (**ptr) >= '0' && (**ptr) <= '9' || (**ptr) >= 'a' && (**ptr) <= 'f' ) { + n <<= 4; + if ( **ptr >= '0' && **ptr <= '9' ) { + n |= ( (**ptr) - '0' ); + } else { + n |= 10 + ( (**ptr) - 'a' ); + } + (*ptr)++; + } + return n; +} + +/* +================== +Sym_Init +================== +*/ +void Sym_Init( long addr ) { + TCHAR moduleName[MAX_STRING_CHARS]; + MEMORY_BASIC_INFORMATION mbi; + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) ); + + char *ext = moduleName + strlen( moduleName ); + while( ext > moduleName && *ext != '.' ) { + ext--; + } + if ( ext == moduleName ) { + strcat( moduleName, ".map" ); + } else { + strcpy( ext, ".map" ); + } + + module_t *module = (module_t *) malloc( sizeof( module_t ) ); + module->name = (char *) malloc( strlen( moduleName ) + 1 ); + strcpy( module->name, moduleName ); + module->address = (int)mbi.AllocationBase; + module->symbols = NULL; + module->next = modules; + modules = module; + + FILE *fp = fopen( moduleName, "rb" ); + if ( fp == NULL ) { + return; + } + + int pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + int length = ftell( fp ); + fseek( fp, pos, SEEK_SET ); + + char *text = (char *) malloc( length+1 ); + fread( text, 1, length, fp ); + text[length] = '\0'; + fclose( fp ); + + const char *ptr = text; + + // skip up to " Address" on a new line + while( *ptr != '\0' ) { + SkipWhiteSpace( &ptr ); + if ( idStr::Cmpn( ptr, "Address", 7 ) == 0 ) { + SkipRestOfLine( &ptr ); + break; + } + SkipRestOfLine( &ptr ); + } + + int symbolAddress; + int symbolLength; + char symbolName[MAX_STRING_CHARS]; + symbol_t *symbol; + + // parse symbols + while( *ptr != '\0' ) { + + SkipWhiteSpace( &ptr ); + + ParseHexNumber( &ptr ); + if ( *ptr == ':' ) { + ptr++; + } else { + break; + } + ParseHexNumber( &ptr ); + + SkipWhiteSpace( &ptr ); + + // parse symbol name + symbolLength = 0; + while( *ptr != '\0' && *ptr != ' ' ) { + symbolName[symbolLength++] = *ptr++; + if ( symbolLength >= sizeof( symbolName ) - 1 ) { + break; + } + } + symbolName[symbolLength++] = '\0'; + + SkipWhiteSpace( &ptr ); + + // parse symbol address + symbolAddress = ParseHexNumber( &ptr ); + + SkipRestOfLine( &ptr ); + + symbol = (symbol_t *) malloc( sizeof( symbol_t ) ); + symbol->name = (char *) malloc( symbolLength ); + strcpy( symbol->name, symbolName ); + symbol->address = symbolAddress; + symbol->next = module->symbols; + module->symbols = symbol; + } + + free( text ); +} + +/* +================== +Sym_Shutdown +================== +*/ +void Sym_Shutdown( void ) { + module_t *m; + symbol_t *s; + + for ( m = modules; m != NULL; m = modules ) { + modules = m->next; + for ( s = m->symbols; s != NULL; s = m->symbols ) { + m->symbols = s->next; + free( s->name ); + free( s ); + } + free( m->name ); + free( m ); + } + modules = NULL; +} + +/* +================== +Sym_GetFuncInfo +================== +*/ +void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) { + MEMORY_BASIC_INFORMATION mbi; + module_t *m; + symbol_t *s; + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + for ( m = modules; m != NULL; m = m->next ) { + if ( m->address == (int) mbi.AllocationBase ) { + break; + } + } + if ( !m ) { + Sym_Init( addr ); + m = modules; + } + + for ( s = m->symbols; s != NULL; s = s->next ) { + if ( s->address == addr ) { + + char undName[MAX_STRING_CHARS]; + if ( UnDecorateSymbolName( s->name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) { + funcName = undName; + } else { + funcName = s->name; + } + for ( int i = 0; i < funcName.Length(); i++ ) { + if ( funcName[i] == '(' ) { + funcName.CapLength( i ); + break; + } + } + module = m->name; + return; + } + } + + sprintf( funcName, "0x%08x", addr ); + module = ""; +} + +#elif defined(_DEBUG) + +DWORD lastAllocationBase = -1; +HANDLE processHandle; +idStr lastModule; + +/* +================== +Sym_Init +================== +*/ +void Sym_Init( long addr ) { + TCHAR moduleName[MAX_STRING_CHARS]; + TCHAR modShortNameBuf[MAX_STRING_CHARS]; + MEMORY_BASIC_INFORMATION mbi; + + if ( lastAllocationBase != -1 ) { + Sym_Shutdown(); + } + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) ); + _splitpath( moduleName, NULL, NULL, modShortNameBuf, NULL ); + lastModule = modShortNameBuf; + + processHandle = GetCurrentProcess(); + if ( !SymInitialize( processHandle, NULL, FALSE ) ) { + return; + } + if ( !SymLoadModule( processHandle, NULL, moduleName, NULL, (DWORD)mbi.AllocationBase, 0 ) ) { + SymCleanup( processHandle ); + return; + } + + SymSetOptions( SymGetOptions() & ~SYMOPT_UNDNAME ); + + lastAllocationBase = (DWORD) mbi.AllocationBase; +} + +/* +================== +Sym_Shutdown +================== +*/ +void Sym_Shutdown( void ) { + SymUnloadModule( GetCurrentProcess(), lastAllocationBase ); + SymCleanup( GetCurrentProcess() ); + lastAllocationBase = -1; +} + +/* +================== +Sym_GetFuncInfo +================== +*/ +void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) { + MEMORY_BASIC_INFORMATION mbi; + + VirtualQuery( (void*)addr, &mbi, sizeof(mbi) ); + + if ( (DWORD) mbi.AllocationBase != lastAllocationBase ) { + Sym_Init( addr ); + } + + BYTE symbolBuffer[ sizeof(IMAGEHLP_SYMBOL) + MAX_STRING_CHARS ]; + PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)&symbolBuffer[0]; + pSymbol->SizeOfStruct = sizeof(symbolBuffer); + pSymbol->MaxNameLength = 1023; + pSymbol->Address = 0; + pSymbol->Flags = 0; + pSymbol->Size =0; + + DWORD symDisplacement = 0; + if ( SymGetSymFromAddr( processHandle, addr, &symDisplacement, pSymbol ) ) { + // clean up name, throwing away decorations that don't affect uniqueness + char undName[MAX_STRING_CHARS]; + if ( UnDecorateSymbolName( pSymbol->Name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) { + funcName = undName; + } else { + funcName = pSymbol->Name; + } + module = lastModule; + } + else { + LPVOID lpMsgBuf; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + LocalFree( lpMsgBuf ); + + // Couldn't retrieve symbol (no debug info?, can't load dbghelp.dll?) + sprintf( funcName, "0x%08x", addr ); + module = ""; + } +} + +#else + +/* +================== +Sym_Init +================== +*/ +void Sym_Init( long addr ) { +} + +/* +================== +Sym_Shutdown +================== +*/ +void Sym_Shutdown( void ) { +} + +/* +================== +Sym_GetFuncInfo +================== +*/ +void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) { + module = ""; + sprintf( funcName, "0x%08x", addr ); +} + +#endif + +/* +================== +GetFuncAddr +================== +*/ +address_t GetFuncAddr( address_t midPtPtr ) { + long temp; + do { + temp = (long)(*(long*)midPtPtr); + if ( (temp&0x00FFFFFF) == PROLOGUE_SIGNATURE ) { + break; + } + midPtPtr--; + } while(true); + + return midPtPtr; +} + +/* +================== +GetCallerAddr +================== +*/ +address_t GetCallerAddr( long _ebp ) { + long midPtPtr; + long res = 0; + + __asm { + mov eax, _ebp + mov ecx, [eax] // check for end of stack frames list + test ecx, ecx // check for zero stack frame + jz label + mov eax, [eax+4] // get the ret address + test eax, eax // check for zero return address + jz label + mov midPtPtr, eax + } + res = GetFuncAddr( midPtPtr ); +label: + return res; +} + +/* +================== +Sys_GetCallStack + + use /Oy option +================== +*/ +void Sys_GetCallStack( address_t *callStack, const int callStackSize ) { +#if 1 //def _DEBUG + int i; + long m_ebp; + + __asm { + mov eax, ebp + mov m_ebp, eax + } + // skip last two functions + m_ebp = *((long*)m_ebp); + m_ebp = *((long*)m_ebp); + // list functions + for ( i = 0; i < callStackSize; i++ ) { + callStack[i] = GetCallerAddr( m_ebp ); + if ( callStack[i] == 0 ) { + break; + } + m_ebp = *((long*)m_ebp); + } +#else + int i = 0; +#endif + while( i < callStackSize ) { + callStack[i++] = 0; + } +} + +/* +================== +Sys_GetCallStackStr +================== +*/ +const char *Sys_GetCallStackStr( const address_t *callStack, const int callStackSize ) { + static char string[MAX_STRING_CHARS*2]; + int index, i; + idStr module, funcName; + + index = 0; + for ( i = callStackSize-1; i >= 0; i-- ) { + Sym_GetFuncInfo( callStack[i], module, funcName ); + index += sprintf( string+index, " -> %s", funcName.c_str() ); + } + return string; +} + +/* +================== +Sys_GetCallStackCurStr +================== +*/ +const char *Sys_GetCallStackCurStr( int depth ) { + address_t *callStack; + + callStack = (address_t *) _alloca( depth * sizeof( address_t ) ); + Sys_GetCallStack( callStack, depth ); + return Sys_GetCallStackStr( callStack, depth ); +} + +/* +================== +Sys_GetCallStackCurAddressStr +================== +*/ +const char *Sys_GetCallStackCurAddressStr( int depth ) { + static char string[MAX_STRING_CHARS*2]; + address_t *callStack; + int index, i; + + callStack = (address_t *) _alloca( depth * sizeof( address_t ) ); + Sys_GetCallStack( callStack, depth ); + + index = 0; + for ( i = depth-1; i >= 0; i-- ) { + index += sprintf( string+index, " -> 0x%08x", callStack[i] ); + } + return string; +} + +/* +================== +Sys_ShutdownSymbols +================== +*/ +void Sys_ShutdownSymbols( void ) { + Sym_Shutdown(); +} diff --git a/src/sys/win32/win_syscon.cpp b/src/sys/win32/win_syscon.cpp new file mode 100644 index 0000000..414808f --- /dev/null +++ b/src/sys/win32/win_syscon.cpp @@ -0,0 +1,553 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include +#include +#include +#include +#include +#include +#include + +#include "win_local.h" +#include "rc/AFEditor_resource.h" +#include "rc/doom_resource.h" + +#define COPY_ID 1 +#define QUIT_ID 2 +#define CLEAR_ID 3 + +#define ERRORBOX_ID 10 +#define ERRORTEXT_ID 11 + +#define EDIT_ID 100 +#define INPUT_ID 101 + +#define COMMAND_HISTORY 64 + +typedef struct { + HWND hWnd; + HWND hwndBuffer; + + HWND hwndButtonClear; + HWND hwndButtonCopy; + HWND hwndButtonQuit; + + HWND hwndErrorBox; + HWND hwndErrorText; + + HBITMAP hbmLogo; + HBITMAP hbmClearBitmap; + + HBRUSH hbrEditBackground; + HBRUSH hbrErrorBackground; + + HFONT hfBufferFont; + HFONT hfButtonFont; + + HWND hwndInputLine; + + char errorString[80]; + + char consoleText[512], returnedText[512]; + bool quitOnClose; + int windowWidth, windowHeight; + + WNDPROC SysInputLineWndProc; + + idEditField historyEditLines[COMMAND_HISTORY]; + + int nextHistoryLine;// the last line in the history buffer, not masked + int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + + idEditField consoleField; + +} WinConData; + +static WinConData s_wcd; + +static LONG WINAPI ConWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + char *cmdString; + static bool s_timePolarity; + + switch (uMsg) { + case WM_ACTIVATE: + if ( LOWORD( wParam ) != WA_INACTIVE ) { + SetFocus( s_wcd.hwndInputLine ); + } + break; + case WM_CLOSE: + if ( cvarSystem->IsInitialized() && com_skipRenderer.GetBool() ) { + cmdString = Mem_CopyString( "quit" ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, strlen( cmdString ) + 1, cmdString ); + } else if ( s_wcd.quitOnClose ) { + PostQuitMessage( 0 ); + } else { + Sys_ShowConsole( 0, false ); + win32.win_viewlog.SetBool( false ); + } + return 0; + case WM_CTLCOLORSTATIC: + if ( ( HWND ) lParam == s_wcd.hwndBuffer ) { + SetBkColor( ( HDC ) wParam, RGB( 0x00, 0x00, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0xff, 0xff, 0x00 ) ); + return ( long ) s_wcd.hbrEditBackground; + } else if ( ( HWND ) lParam == s_wcd.hwndErrorBox ) { + if ( s_timePolarity & 1 ) { + SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0xff, 0x0, 0x00 ) ); + } else { + SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0x00, 0x0, 0x00 ) ); + } + return ( long ) s_wcd.hbrErrorBackground; + } + break; + case WM_SYSCOMMAND: + if ( wParam == SC_CLOSE ) { + PostQuitMessage( 0 ); + } + break; + case WM_COMMAND: + if ( wParam == COPY_ID ) { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + SendMessage( s_wcd.hwndBuffer, WM_COPY, 0, 0 ); + } else if ( wParam == QUIT_ID ) { + if ( s_wcd.quitOnClose ) { + PostQuitMessage( 0 ); + } else { + cmdString = Mem_CopyString( "quit" ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, strlen( cmdString ) + 1, cmdString ); + } + } else if ( wParam == CLEAR_ID ) { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, FALSE, ( LPARAM ) "" ); + UpdateWindow( s_wcd.hwndBuffer ); + } + break; + case WM_CREATE: + s_wcd.hbrEditBackground = CreateSolidBrush( RGB( 0x00, 0x00, 0x80 ) ); + s_wcd.hbrErrorBackground = CreateSolidBrush( RGB( 0x80, 0x80, 0x80 ) ); + SetTimer( hWnd, 1, 1000, NULL ); + break; +/* + case WM_ERASEBKGND: + HGDIOBJ oldObject; + HDC hdcScaled; + hdcScaled = CreateCompatibleDC( ( HDC ) wParam ); + assert( hdcScaled != 0 ); + if ( hdcScaled ) { + oldObject = SelectObject( ( HDC ) hdcScaled, s_wcd.hbmLogo ); + assert( oldObject != 0 ); + if ( oldObject ) + { + StretchBlt( ( HDC ) wParam, 0, 0, s_wcd.windowWidth, s_wcd.windowHeight, + hdcScaled, 0, 0, 512, 384, + SRCCOPY ); + } + DeleteDC( hdcScaled ); + hdcScaled = 0; + } + return 1; +*/ + case WM_TIMER: + if ( wParam == 1 ) { + s_timePolarity = (bool)!s_timePolarity; + if ( s_wcd.hwndErrorBox ) { + InvalidateRect( s_wcd.hwndErrorBox, NULL, FALSE ); + } + } + break; + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} + +LONG WINAPI InputLineWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + int key, cursor; + switch ( uMsg ) { + case WM_KILLFOCUS: + if ( ( HWND ) wParam == s_wcd.hWnd || ( HWND ) wParam == s_wcd.hwndErrorBox ) { + SetFocus( hWnd ); + return 0; + } + break; + + case WM_KEYDOWN: + key = MapKey( lParam ); + + // command history + if ( ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) ) { + if ( s_wcd.nextHistoryLine - s_wcd.historyLine < COMMAND_HISTORY && s_wcd.historyLine > 0 ) { + s_wcd.historyLine--; + } + s_wcd.consoleField = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ]; + + SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() ); + SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() ); + return 0; + } + + if ( ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) ) { + if ( s_wcd.historyLine == s_wcd.nextHistoryLine ) { + return 0; + } + s_wcd.historyLine++; + s_wcd.consoleField = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ]; + + SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() ); + SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() ); + return 0; + } + break; + + case WM_CHAR: + key = MapKey( lParam ); + + GetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer(), MAX_EDIT_LINE ); + SendMessage( s_wcd.hwndInputLine, EM_GETSEL, (WPARAM) NULL, (LPARAM) &cursor ); + s_wcd.consoleField.SetCursor( cursor ); + + // enter the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + strncat( s_wcd.consoleText, s_wcd.consoleField.GetBuffer(), sizeof( s_wcd.consoleText ) - strlen( s_wcd.consoleText ) - 5 ); + strcat( s_wcd.consoleText, "\n" ); + SetWindowText( s_wcd.hwndInputLine, "" ); + + Sys_Printf( "]%s\n", s_wcd.consoleField.GetBuffer() ); + + // copy line to history buffer + s_wcd.historyEditLines[s_wcd.nextHistoryLine % COMMAND_HISTORY] = s_wcd.consoleField; + s_wcd.nextHistoryLine++; + s_wcd.historyLine = s_wcd.nextHistoryLine; + + s_wcd.consoleField.Clear(); + + return 0; + } + + // command completion + if ( key == K_TAB ) { + s_wcd.consoleField.AutoComplete(); + + SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() ); + //s_wcd.consoleField.SetWidthInChars( strlen( s_wcd.consoleField.GetBuffer() ) ); + SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() ); + + return 0; + } + + // clear autocompletion buffer on normal key input + if ( ( key >= K_SPACE && key <= K_BACKSPACE ) || + ( key >= K_KP_SLASH && key <= K_KP_PLUS ) || ( key >= K_KP_STAR && key <= K_KP_EQUALS ) ) { + s_wcd.consoleField.ClearAutoComplete(); + } + break; + } + + return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam ); +} + +/* +** Sys_CreateConsole +*/ +void Sys_CreateConsole( void ) { + HDC hDC; + WNDCLASS wc; + RECT rect; + const char *DEDCLASS = WIN32_CONSOLE_CLASS; + int nHeight; + int swidth, sheight; + int DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX; + int i; + + memset( &wc, 0, sizeof( wc ) ); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) ConWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = win32.hInstance; + wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE(IDI_ICON1)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (struct HBRUSH__ *)COLOR_WINDOW; + wc.lpszMenuName = 0; + wc.lpszClassName = DEDCLASS; + + if ( !RegisterClass (&wc) ) { + return; + } + + rect.left = 0; + rect.right = 540; + rect.top = 0; + rect.bottom = 450; + AdjustWindowRect( &rect, DEDSTYLE, FALSE ); + + hDC = GetDC( GetDesktopWindow() ); + swidth = GetDeviceCaps( hDC, HORZRES ); + sheight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + s_wcd.windowWidth = rect.right - rect.left + 1; + s_wcd.windowHeight = rect.bottom - rect.top + 1; + + //s_wcd.hbmLogo = LoadBitmap( win32.hInstance, MAKEINTRESOURCE( IDB_BITMAP_LOGO) ); + + s_wcd.hWnd = CreateWindowEx( 0, + DEDCLASS, + GAME_NAME, + DEDSTYLE, + ( swidth - 600 ) / 2, ( sheight - 450 ) / 2 , rect.right - rect.left + 1, rect.bottom - rect.top + 1, + NULL, + NULL, + win32.hInstance, + NULL ); + + if ( s_wcd.hWnd == NULL ) { + return; + } + + // + // create fonts + // + hDC = GetDC( s_wcd.hWnd ); + nHeight = -MulDiv( 8, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ); + + s_wcd.hfBufferFont = CreateFont( nHeight, 0, 0, 0, FW_LIGHT, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_MODERN | FIXED_PITCH, "Courier New" ); + + ReleaseDC( s_wcd.hWnd, hDC ); + + // + // create the input line + // + s_wcd.hwndInputLine = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | + ES_LEFT | ES_AUTOHSCROLL, + 6, 400, 528, 20, + s_wcd.hWnd, + ( HMENU ) INPUT_ID, // child window ID + win32.hInstance, NULL ); + + // + // create the buttons + // + s_wcd.hwndButtonCopy = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 5, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) COPY_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonCopy, WM_SETTEXT, 0, ( LPARAM ) "copy" ); + + s_wcd.hwndButtonClear = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 82, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) CLEAR_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonClear, WM_SETTEXT, 0, ( LPARAM ) "clear" ); + + s_wcd.hwndButtonQuit = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 462, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) QUIT_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonQuit, WM_SETTEXT, 0, ( LPARAM ) "quit" ); + + + // + // create the scrollbuffer + // + s_wcd.hwndBuffer = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER | + ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, + 6, 40, 526, 354, + s_wcd.hWnd, + ( HMENU ) EDIT_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndBuffer, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + + s_wcd.SysInputLineWndProc = ( WNDPROC ) SetWindowLong( s_wcd.hwndInputLine, GWL_WNDPROC, ( long ) InputLineWndProc ); + SendMessage( s_wcd.hwndInputLine, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + +// don't show it now that we have a splash screen up + if ( win32.win_viewlog.GetBool() ) { + ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT); + UpdateWindow( s_wcd.hWnd ); + SetForegroundWindow( s_wcd.hWnd ); + SetFocus( s_wcd.hwndInputLine ); + } + + + + s_wcd.consoleField.Clear(); + + for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) { + s_wcd.historyEditLines[i].Clear(); + } +} + +/* +** Sys_DestroyConsole +*/ +void Sys_DestroyConsole( void ) { + if ( s_wcd.hWnd ) { + ShowWindow( s_wcd.hWnd, SW_HIDE ); + CloseWindow( s_wcd.hWnd ); + DestroyWindow( s_wcd.hWnd ); + s_wcd.hWnd = 0; + } +} + +/* +** Sys_ShowConsole +*/ +void Sys_ShowConsole( int visLevel, bool quitOnClose ) { + + s_wcd.quitOnClose = quitOnClose; + + if ( !s_wcd.hWnd ) { + return; + } + + switch ( visLevel ) { + case 0: + ShowWindow( s_wcd.hWnd, SW_HIDE ); + break; + case 1: + ShowWindow( s_wcd.hWnd, SW_SHOWNORMAL ); + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + break; + case 2: + ShowWindow( s_wcd.hWnd, SW_MINIMIZE ); + break; + default: + Sys_Error( "Invalid visLevel %d sent to Sys_ShowConsole\n", visLevel ); + break; + } +} + +/* +** Sys_ConsoleInput +*/ +char *Sys_ConsoleInput( void ) { + + if ( s_wcd.consoleText[0] == 0 ) { + return NULL; + } + + strcpy( s_wcd.returnedText, s_wcd.consoleText ); + s_wcd.consoleText[0] = 0; + + return s_wcd.returnedText; +} + +/* +** Conbuf_AppendText +*/ +void Conbuf_AppendText( const char *pMsg ) +{ +#define CONSOLE_BUFFER_SIZE 16384 + + char buffer[CONSOLE_BUFFER_SIZE*2]; + char *b = buffer; + const char *msg; + int bufLen; + int i = 0; + static unsigned long s_totalChars; + + // + // if the message is REALLY long, use just the last portion of it + // + if ( strlen( pMsg ) > CONSOLE_BUFFER_SIZE - 1 ) { + msg = pMsg + strlen( pMsg ) - CONSOLE_BUFFER_SIZE + 1; + } else { + msg = pMsg; + } + + // + // copy into an intermediate buffer + // + while ( msg[i] && ( ( b - buffer ) < sizeof( buffer ) - 1 ) ) { + if ( msg[i] == '\n' && msg[i+1] == '\r' ) { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + i++; + } else if ( msg[i] == '\r' ) { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + } else if ( msg[i] == '\n' ) { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + } else if ( int escapeLength = idStr::IsEscape( &msg[i] ) ) { + i += escapeLength - 1; + } else { + *b= msg[i]; + b++; + } + i++; + } + *b = 0; + bufLen = b - buffer; + + s_totalChars += bufLen; + + // + // replace selection instead of appending if we're overflowing + // + if ( s_totalChars > 0x7000 ) { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + s_totalChars = bufLen; + } + + // + // put this text into the windows console + // + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + SendMessage( s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0 ); + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, 0, (LPARAM) buffer ); +} + +/* +** Win_SetErrorText +*/ +void Win_SetErrorText( const char *buf ) { + idStr::Copynz( s_wcd.errorString, buf, sizeof( s_wcd.errorString ) ); + if ( !s_wcd.hwndErrorBox ) { + s_wcd.hwndErrorBox = CreateWindow( "static", NULL, WS_CHILD | WS_VISIBLE | SS_SUNKEN, + 6, 5, 526, 30, + s_wcd.hWnd, + ( HMENU ) ERRORBOX_ID, // child window ID + win32.hInstance, NULL ); + SendMessage( s_wcd.hwndErrorBox, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + SetWindowText( s_wcd.hwndErrorBox, s_wcd.errorString ); + + DestroyWindow( s_wcd.hwndInputLine ); + s_wcd.hwndInputLine = NULL; + } +} diff --git a/src/sys/win32/win_taskkeyhook.cpp b/src/sys/win32/win_taskkeyhook.cpp new file mode 100644 index 0000000..ef0969d --- /dev/null +++ b/src/sys/win32/win_taskkeyhook.cpp @@ -0,0 +1,152 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#if defined( Q4_DISABLE_TASKKEY_HOOK ) + +#include "win_local.h" + +void DisableTaskKeys( BOOL bDisable, BOOL bBeep, BOOL bTaskMgr ) { +} + +#else + +// +// This file implements the low-level keyboard hook that traps the task keys. +// + +//#define _WIN32_WINNT 0x0500 // for KBDLLHOOKSTRUCT +#include // MFC core and standard components +#include "win_local.h" + +#define DLLEXPORT __declspec(dllexport) + +// Magic registry key/value for "Remove Task Manager" policy. +LPCTSTR KEY_DisableTaskMgr = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; +LPCTSTR VAL_DisableTaskMgr = "DisableTaskMgr"; + +// The section is SHARED among all instances of this DLL. +// A low-level keyboard hook is always a system-wide hook. +#pragma data_seg (".mydata") +HHOOK g_hHookKbdLL = NULL; // hook handle +BOOL g_bBeep = FALSE; // beep on illegal key +#pragma data_seg () +#pragma comment(linker, "/SECTION:.mydata,RWS") // tell linker: make it shared + +/* +================ +MyTaskKeyHookLL + + Low-level keyboard hook: + Trap task-switching keys by returning without passing along. +================ +*/ +LRESULT CALLBACK MyTaskKeyHookLL( int nCode, WPARAM wp, LPARAM lp ) { + KBDLLHOOKSTRUCT *pkh = (KBDLLHOOKSTRUCT *) lp; + + if ( nCode == HC_ACTION ) { + BOOL bCtrlKeyDown = GetAsyncKeyState( VK_CONTROL)>>((sizeof(SHORT) * 8) - 1 ); + + if ( ( pkh->vkCode == VK_ESCAPE && bCtrlKeyDown ) // Ctrl+Esc + || ( pkh->vkCode == VK_TAB && pkh->flags & LLKHF_ALTDOWN ) // Alt+TAB + || ( pkh->vkCode == VK_ESCAPE && pkh->flags & LLKHF_ALTDOWN ) // Alt+Esc + || ( pkh->vkCode == VK_LWIN || pkh->vkCode == VK_RWIN ) // Start Menu + ) { + + if ( g_bBeep && ( wp == WM_SYSKEYDOWN || wp == WM_KEYDOWN ) ) { + MessageBeep( 0 ); // beep on downstroke if requested + } + return 1; // return without processing the key strokes + } + } + return CallNextHookEx( g_hHookKbdLL, nCode, wp, lp ); +} + +/* +================ +AreTaskKeysDisabled + + Are task keys disabled--ie, is hook installed? + Note: This assumes there's no other hook that does the same thing! +================ +*/ +BOOL AreTaskKeysDisabled() { + return g_hHookKbdLL != NULL; +} + +/* +================ +IsTaskMgrDisabled +================ +*/ +BOOL IsTaskMgrDisabled() { + HKEY hk; + + if ( RegOpenKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ) != ERROR_SUCCESS ) { + return FALSE; // no key ==> not disabled + } + + DWORD val = 0; + DWORD len = 4; + return RegQueryValueEx( hk, VAL_DisableTaskMgr, NULL, NULL, (BYTE*)&val, &len ) == ERROR_SUCCESS && val == 1; +} + +/* +================ +DisableTaskKeys +================ +*/ +void DisableTaskKeys( BOOL bDisable, BOOL bBeep, BOOL bTaskMgr ) { + + // task keys (Ctrl+Esc, Alt-Tab, etc.) + if ( bDisable ) { + if ( !g_hHookKbdLL ) { + g_hHookKbdLL = SetWindowsHookEx( WH_KEYBOARD_LL, MyTaskKeyHookLL, win32.hInstance, 0 ); + } + } else if ( g_hHookKbdLL != NULL ) { + UnhookWindowsHookEx( g_hHookKbdLL ); + g_hHookKbdLL = NULL; + } + g_bBeep = bBeep; + + // task manager (Ctrl+Alt+Del) + if ( bTaskMgr ) { + HKEY hk; + if ( RegOpenKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ) != ERROR_SUCCESS ) { + RegCreateKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ); + } + if ( bDisable ) { + // disable TM: set policy = 1 + DWORD val = 1; + RegSetValueEx( hk, VAL_DisableTaskMgr, NULL, REG_DWORD, (BYTE*)&val, sizeof(val) ); + } else { + // enable TM: remove policy + RegDeleteValue( hk,VAL_DisableTaskMgr ); + } + } +} + +#endif diff --git a/src/sys/win32/win_wndproc.cpp b/src/sys/win32/win_wndproc.cpp new file mode 100644 index 0000000..2ae6f90 --- /dev/null +++ b/src/sys/win32/win_wndproc.cpp @@ -0,0 +1,452 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "win_local.h" +#include "../../renderer/tr_local.h" + +LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + +static bool s_alttab_disabled; + +static void WIN_DisableAltTab( void ) { + if ( s_alttab_disabled || win32.win_allowAltTab.GetBool() ) { + return; + } + if ( !idStr::Icmp( cvarSystem->GetCVarString( "sys_arch" ), "winnt" ) ) { + RegisterHotKey( 0, 0, MOD_ALT, VK_TAB ); + } else { + BOOL old; + + SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, &old, 0 ); + } + s_alttab_disabled = true; +} + +static void WIN_EnableAltTab( void ) { + if ( !s_alttab_disabled || win32.win_allowAltTab.GetBool() ) { + return; + } + if ( !idStr::Icmp( cvarSystem->GetCVarString( "sys_arch" ), "winnt" ) ) { + UnregisterHotKey( 0, 0 ); + } else { + BOOL old; + + SystemParametersInfo( SPI_SCREENSAVERRUNNING, 0, &old, 0 ); + } + + s_alttab_disabled = false; +} + +void WIN_Sizing(WORD side, RECT *rect) +{ + if ( !glConfig.isInitialized || glConfig.vidHeight <= 0 || glConfig.vidWidth <= 0 ) { + return; + } + + if ( idKeyInput::IsDown( K_CTRL ) ) { + return; + } + + int width = rect->right - rect->left; + int height = rect->bottom - rect->top; + + // Adjust width/height for window decoration + RECT decoRect = { 0, 0, 0, 0 }; + AdjustWindowRect( &decoRect, WINDOW_STYLE|WS_SYSMENU, FALSE ); + int decoWidth = decoRect.right - decoRect.left; + int decoHeight = decoRect.bottom - decoRect.top; + + width -= decoWidth; + height -= decoHeight; + + // Clamp to a minimum size + int minWidth = 160; + int minHeight = minWidth * SCREEN_HEIGHT / SCREEN_WIDTH; + + if ( width < minWidth ) { + width = minWidth; + } + if ( height < minHeight ) { + height = minHeight; + } + + // Set the new size + switch ( side ) { + case WMSZ_LEFT: + rect->left = rect->right - width - decoWidth; + rect->bottom = rect->top + ( width * SCREEN_HEIGHT / SCREEN_WIDTH ) + decoHeight; + break; + case WMSZ_RIGHT: + rect->right = rect->left + width + decoWidth; + rect->bottom = rect->top + ( width * SCREEN_HEIGHT / SCREEN_WIDTH ) + decoHeight; + break; + case WMSZ_BOTTOM: + case WMSZ_BOTTOMRIGHT: + rect->bottom = rect->top + height + decoHeight; + rect->right = rect->left + ( height * SCREEN_WIDTH / SCREEN_HEIGHT ) + decoWidth; + break; + case WMSZ_TOP: + case WMSZ_TOPRIGHT: + rect->top = rect->bottom - height - decoHeight; + rect->right = rect->left + ( height * SCREEN_WIDTH / SCREEN_HEIGHT ) + decoWidth; + break; + case WMSZ_BOTTOMLEFT: + rect->bottom = rect->top + height + decoHeight; + rect->left = rect->right - ( height * SCREEN_WIDTH / SCREEN_HEIGHT ) - decoWidth; + break; + case WMSZ_TOPLEFT: + rect->top = rect->bottom - height - decoHeight; + rect->left = rect->right - ( height * SCREEN_WIDTH / SCREEN_HEIGHT ) - decoWidth; + break; + } +} + +//========================================================================== + +// Keep this in sync with the one in win_input.cpp +// This one is used in the menu, the other one is used in game + +static byte s_scantokey[128] = +{ +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', K_BACKSPACE, 9, // 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', K_ENTER,K_CTRL, 'a', 's', // 1 + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', + '\'', '`', K_SHIFT, '\\', 'z', 'x', 'c', 'v', // 2 + 'b', 'n', 'm', ',', '.', '/', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, 0, K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 +}; + +static byte s_scantoshift[128] = +{ +// 0 1 2 3 4 5 6 7 +// 8 9 A B C D E F + 0, 27, '!', '@', '#', '$', '%', '^', + '&', '*', '(', ')', '_', '+', K_BACKSPACE, 9, // 0 + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', + 'O', 'P', '{', '}', K_ENTER,K_CTRL, 'A', 'S', // 1 + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', + '|' , '~', K_SHIFT, '\\', 'Z', 'X', 'C', 'V', // 2 + 'B', 'N', 'M', '<', '>', '?', K_SHIFT, K_KP_STAR, + K_ALT, ' ', K_CAPSLOCK,K_F1, K_F2, K_F3, K_F4, K_F5, // 3 + K_F6, K_F7, K_F8, K_F9, K_F10, K_PAUSE, K_SCROLL, K_HOME, + K_UPARROW, K_PGUP, K_KP_MINUS,K_LEFTARROW,K_KP_5, K_RIGHTARROW,K_KP_PLUS,K_END, // 4 + K_DOWNARROW,K_PGDN, K_INS, K_DEL, 0, 0, 0, K_F11, + K_F12, 0, 0, K_LWIN, K_RWIN, K_MENU, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 // 7 +}; + + +/* +======= +MapKey + +Map from windows to Doom keynums +======= +*/ +int MapKey (int key) +{ + int result; + int modified; + bool is_extended; + + modified = ( key >> 16 ) & 255; + + if ( modified > 127 ) + return 0; + + if ( key & ( 1 << 24 ) ) { + is_extended = true; + } + else { + is_extended = false; + } + + //Check for certain extended character codes. + //The specific case we are testing is the numpad / is not being translated + //properly for localized builds. + if(is_extended) { + switch(modified) { + case 0x35: //Numpad / + return K_KP_SLASH; + } + } + + const unsigned char *scanToKey = Sys_GetScanTable(); + result = scanToKey[modified]; + + // common->Printf( "Key: 0x%08x Modified: 0x%02x Extended: %s Result: 0x%02x\n", key, modified, (is_extended?"Y":"N"), result); + + if ( is_extended ) { + switch ( result ) + { + case K_PAUSE: + return K_KP_NUMLOCK; + case 0x0D: + return K_KP_ENTER; + case 0x2F: + return K_KP_SLASH; + case 0xAF: + return K_KP_PLUS; + case K_KP_STAR: + return K_PRINT_SCR; + case K_ALT: + return K_RIGHT_ALT; + } + } + else { + switch ( result ) + { + case K_HOME: + return K_KP_HOME; + case K_UPARROW: + return K_KP_UPARROW; + case K_PGUP: + return K_KP_PGUP; + case K_LEFTARROW: + return K_KP_LEFTARROW; + case K_RIGHTARROW: + return K_KP_RIGHTARROW; + case K_END: + return K_KP_END; + case K_DOWNARROW: + return K_KP_DOWNARROW; + case K_PGDN: + return K_KP_PGDN; + case K_INS: + return K_KP_INS; + case K_DEL: + return K_KP_DEL; + } + } + + return result; +} + + +/* +==================== +MainWndProc + +main window procedure +==================== +*/ +LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { + int key; + switch( uMsg ) { + case WM_WINDOWPOSCHANGED: + if (glConfig.isInitialized) { + RECT rect; + if (::GetClientRect(win32.hWnd, &rect)) { + glConfig.vidWidth = rect.right - rect.left; + glConfig.vidHeight = rect.bottom - rect.top; + } + } + break; + + case WM_CREATE: + + win32.hWnd = hWnd; + + if ( win32.cdsFullscreen ) { + WIN_DisableAltTab(); + } else { + WIN_EnableAltTab(); + } + + // do the OpenGL setup + void GLW_WM_CREATE( HWND hWnd ); + GLW_WM_CREATE( hWnd ); + + break; + + case WM_DESTROY: + // let sound and input know about this? + win32.hWnd = NULL; + if ( win32.cdsFullscreen ) { + WIN_EnableAltTab(); + } + break; + + case WM_CLOSE: + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit" ); + break; + + case WM_ACTIVATE: + // if we got here because of an alt-tab or maximize, + // we should activate immediately. If we are here because + // the mouse was clicked on a title bar or drag control, + // don't activate until the mouse button is released + { + int fActive, fMinimized; + + fActive = LOWORD(wParam); + fMinimized = (BOOL) HIWORD(wParam); + + win32.activeApp = (fActive != WA_INACTIVE); + if ( win32.activeApp ) { + idKeyInput::ClearStates(); + com_editorActive = false; + Sys_GrabMouseCursor( true ); + } + + if ( fActive == WA_INACTIVE ) { + win32.movingWindow = false; + } + + // start playing the game sound world + soundSystem->SetActiveSoundWorld( true ); + + // we do not actually grab or release the mouse here, + // that will be done next time through the main loop + } + break; + + case WM_MOVE: { + int xPos, yPos; + RECT r; + int style; + + if (!win32.cdsFullscreen ) + { + xPos = (short) LOWORD(lParam); // horizontal position + yPos = (short) HIWORD(lParam); // vertical position + + r.left = 0; + r.top = 0; + r.right = 1; + r.bottom = 1; + + style = GetWindowLong( hWnd, GWL_STYLE ); + AdjustWindowRect( &r, style, FALSE ); + + win32.win_xpos.SetInteger( xPos + r.left ); + win32.win_ypos.SetInteger( yPos + r.top ); + win32.win_xpos.ClearModified(); + win32.win_ypos.ClearModified(); + } + break; + } + case WM_TIMER: { + if ( win32.win_timerUpdate.GetBool() ) { + common->Frame(); + } + break; + } + case WM_SYSCOMMAND: + if ( wParam == SC_SCREENSAVE || wParam == SC_KEYMENU ) { + return 0; + } + break; + + case WM_SYSKEYDOWN: + if ( wParam == 13 ) { // alt-enter toggles full-screen + cvarSystem->SetCVarBool( "r_fullscreen", !renderSystem->IsFullScreen() ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" ); + return 0; + } + // fall through for other keys + case WM_KEYDOWN: + key = MapKey( lParam ); + if ( key == K_CTRL || key == K_ALT || key == K_RIGHT_ALT ) { + // let direct-input handle this because windows sends Alt-Gr + // as two events (ctrl then alt) + break; + } + Sys_QueEvent( win32.sysMsgTime, SE_KEY, key, true, 0, NULL ); + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + key = MapKey( lParam ); + if ( key == K_PRINT_SCR ) { + // don't queue printscreen keys. Since windows doesn't send us key + // down events for this, we handle queueing them with DirectInput + break; + } else if ( key == K_CTRL || key == K_ALT || key == K_RIGHT_ALT ) { + // let direct-input handle this because windows sends Alt-Gr + // as two events (ctrl then alt) + break; + } + Sys_QueEvent( win32.sysMsgTime, SE_KEY, key, false, 0, NULL ); + break; + + case WM_CHAR: + Sys_QueEvent( win32.sysMsgTime, SE_CHAR, wParam, 0, 0, NULL ); + break; + + case WM_NCLBUTTONDOWN: +// win32.movingWindow = true; + break; + + case WM_ENTERSIZEMOVE: + win32.movingWindow = true; + break; + + case WM_EXITSIZEMOVE: + win32.movingWindow = false; + break; + + case WM_SIZING: + WIN_Sizing(wParam, (RECT *)lParam); + break; + + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MOUSEMOVE: { + break; + } + case WM_MOUSEWHEEL: { + int delta = GET_WHEEL_DELTA_WPARAM( wParam ) / WHEEL_DELTA; + int key = delta < 0 ? K_MWHEELDOWN : K_MWHEELUP; + delta = abs( delta ); + while( delta-- > 0 ) { + Sys_QueEvent( win32.sysMsgTime, SE_KEY, key, true, 0, NULL ); + Sys_QueEvent( win32.sysMsgTime, SE_KEY, key, false, 0, NULL ); + } + break; + } + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} diff --git a/src/tests/abi/BSE_ABI.cpp b/src/tests/abi/BSE_ABI.cpp new file mode 100644 index 0000000..cacd81c --- /dev/null +++ b/src/tests/abi/BSE_ABI.cpp @@ -0,0 +1,53 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../bse/BSEInterface.h" +#include "../../bse/BSE_Envelope.h" +#include "../../bse/BSE_SpawnDomains.h" +#include "../../bse/BSE_Particle.h" +#include "../../bse/BSE.h" + +// Quake 4 retail Win32 layout checkpoints recovered from quake4.pdb. +assert_sizeof( rvParticleParms, 40 ); +assert_sizeof( rvEnvParms, 32 ); +assert_sizeof( rvEnvParms1, 20 ); +assert_sizeof( rvEnvParms2, 40 ); +assert_sizeof( rvEnvParms3, 56 ); +assert_sizeof( rvParticle, 340 ); +assert_sizeof( rvSpriteParticle, 400 ); +assert_sizeof( rvLineParticle, 416 ); +assert_sizeof( rvOrientedParticle, 456 ); +assert_sizeof( rvElectricityParticle, 476 ); +assert_sizeof( rvModelParticle, 456 ); +assert_sizeof( rvLightParticle, 616 ); +assert_sizeof( rvLinkedParticle, 360 ); +assert_sizeof( rvDebrisParticle, 396 ); +assert_sizeof( rvParticleTemplate, 1340 ); +assert_sizeof( rvSegment, 72 ); +assert_sizeof( rvSegmentTemplate, 1484 ); +assert_sizeof( rvDeclEffect, 52 ); +assert_sizeof( rvBSE, 388 ); diff --git a/src/tests/abi/RendererRaven_ABI.cpp b/src/tests/abi/RendererRaven_ABI.cpp new file mode 100644 index 0000000..9af97c4 --- /dev/null +++ b/src/tests/abi/RendererRaven_ABI.cpp @@ -0,0 +1,72 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../../idlib/precompiled.h" +#pragma hdrstop + +#include "../../renderer/tr_local.h" +#include "../../renderer/Shaders.h" +#include "../../renderer/rvIndexBuffer.h" +#include "../../renderer/rvSpecial.h" +#include "../../renderer/rvTexRenderTarget.h" +#include "../../renderer/rvVertexBuffer.h" +#include "../../renderer/rvVertexFormat.h" +#include "../../renderer/rvPrimBatch.h" +#include "../../renderer/rvMesh.h" +#include "../../renderer/rvRenderModelMD5R.h" + +#if defined(_M_IX86) +static_assert( sizeof( rvShader ) == 76, "quake4.pdb: rvShader must be 76 bytes" ); +static_assert( sizeof( rvNewShaderStage ) == 5580, "quake4.pdb: rvNewShaderStage must be 5580 bytes" ); +static_assert( sizeof( rvGLSLShader ) == 88, "quake4.pdb: rvGLSLShader must be 88 bytes" ); +static_assert( sizeof( rvGLSLShaderStage ) == 5964, "quake4.pdb: rvGLSLShaderStage must be 5964 bytes" ); +static_assert( sizeof( rvIndexBuffer ) == 36, "quake4.pdb: rvIndexBuffer must be 36 bytes" ); +static_assert( sizeof( rvTexRenderTarget ) == 60, "quake4.pdb: rvTexRenderTarget must be 60 bytes" ); +static_assert( sizeof( rvBlurTexture ) == 68, "quake4.pdb: rvBlurTexture must be 68 bytes" ); +static_assert( sizeof( rvAL ) == 2896, "quake4.pdb: rvAL must be 2896 bytes" ); +static_assert( sizeof( rvVertexBuffer ) == 472, "quake4.pdb: rvVertexBuffer must be 472 bytes" ); +static_assert( sizeof( rvVertexFormat ) == 184, "quake4.pdb: rvVertexFormat must be 184 bytes" ); +static_assert( sizeof( rvPrimBatch ) == 72, "quake4.pdb: rvPrimBatch must be 72 bytes" ); +static_assert( sizeof( rvMesh ) == 96, "quake4.pdb: rvMesh must be 96 bytes" ); +static_assert( sizeof( rvRenderModelMD5R ) == 196, "quake4.pdb: rvRenderModelMD5R must be 196 bytes" ); +static_assert( sizeof( rvRenderModelBSE ) == 112, "quake4.pdb: rvRenderModelBSE must be 112 bytes" ); +static_assert( sizeof( localTrace_t ) == 92, "quake4.pdb: localTrace_s must be 92 bytes" ); +static_assert( sizeof( srfCullInfo_t ) == 104, "quake4.pdb: srfCullInfo_s must be 104 bytes" ); +static_assert( sizeof( silEdge_t ) == 16, "quake4.pdb: silEdge_t must be 16 bytes" ); +static_assert( sizeof( overlayVertex_t ) == 12, "quake4.pdb: overlayVertex_t must be 12 bytes" ); +static_assert( sizeof( overlaySurface_t ) == 28, "quake4.pdb: overlaySurface_t must be 28 bytes" ); +static_assert( sizeof( overlayMaterial_t ) == 24, "quake4.pdb: overlayMaterial_t must be 24 bytes" ); +static_assert( sizeof( decalProjectionInfo_t ) == 220, "quake4.pdb: decalProjectionInfo_s must be 220 bytes" ); +static_assert( offsetof( decalProjectionInfo_t, maxAngle ) == 212, "quake4.pdb: decalProjectionInfo_s::maxAngle offset mismatch" ); +#if !defined( _DEBUG ) +static_assert( sizeof( idRenderModelDecal ) == 3548, "quake4.pdb: idRenderModelDecal must be 3548 bytes" ); +#endif +static_assert( sizeof( idImage ) == 1256, "quake4.pdb: idImage must be 1256 bytes" ); +static_assert( sizeof( idPBufferImage ) == 1260, "quake4.pdb: idPBufferImage must be 1260 bytes" ); +static_assert( sizeof( backEndCounters_t ) == 72, "quake4.pdb: retail backEndCounters_t must be 72 bytes" ); +static_assert( sizeof( glstate_t ) == 188, "quake4.pdb: glstate_t must be 188 bytes" ); +static_assert( sizeof( backEndState_t ) == 524, "quake4.exe/PDB: retail backEndState_t must be 524 bytes" ); +static_assert( sizeof( performanceCounters_t ) == 156, "quake4.pdb: performanceCounters_t must be 156 bytes" ); +static_assert( sizeof( idRenderSystemLocal ) == 67004, "quake4.exe/PDB: idRenderSystemLocal must be 67004 bytes" ); +#endif diff --git a/src/tools/reconstruction/CMakeLists.txt b/src/tools/reconstruction/CMakeLists.txt new file mode 100644 index 0000000..65855e5 --- /dev/null +++ b/src/tools/reconstruction/CMakeLists.txt @@ -0,0 +1,22 @@ +add_custom_target(reconstruction_manifests SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/Export-EvidenceManifest.ps1 + ${CMAKE_CURRENT_SOURCE_DIR}/Export-ReconstructionLedger.ps1 + ${CMAKE_CURRENT_SOURCE_DIR}/Seed-DoomImplementations.ps1 + ${CMAKE_CURRENT_SOURCE_DIR}/Seed-DoomHeaders.ps1 +) + +set_target_properties(reconstruction_manifests PROPERTIES FOLDER "Reconstruction") + +if(MSVC) + set(DIA_SDK_ROOT "${CMAKE_GENERATOR_INSTANCE}/DIA SDK") + if(EXISTS "${DIA_SDK_ROOT}/include/dia2.h") + add_executable(q4_pdb_inventory pdb_inventory.cpp) + target_include_directories(q4_pdb_inventory PRIVATE "${DIA_SDK_ROOT}/include") + target_link_directories(q4_pdb_inventory PRIVATE "${DIA_SDK_ROOT}/lib") + target_link_libraries(q4_pdb_inventory PRIVATE diaguids ole32 oleaut32) + target_compile_definitions(q4_pdb_inventory PRIVATE UNICODE _UNICODE) + set_target_properties(q4_pdb_inventory PROPERTIES FOLDER "Reconstruction") + else() + message(WARNING "DIA SDK not found; q4_pdb_inventory will not be built") + endif() +endif() diff --git a/src/tools/reconstruction/Export-CompilandSourceMap.ps1 b/src/tools/reconstruction/Export-CompilandSourceMap.ps1 new file mode 100644 index 0000000..692cd77 --- /dev/null +++ b/src/tools/reconstruction/Export-CompilandSourceMap.ps1 @@ -0,0 +1,72 @@ +param( + [string]$PdbInventoryPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\pdb-inventory.json"), + [string]$SourceRoot = (Join-Path $PSScriptRoot "..\.."), + [string]$OutputPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\compiland-source-map.csv") +) + +$ErrorActionPreference = "Stop" + +$inventory = Get-Content -LiteralPath $PdbInventoryPath -Raw | ConvertFrom-Json +$sourceRootPath = (Resolve-Path -LiteralPath $SourceRoot).Path + +$sourceByStem = @{} +Get-ChildItem -LiteralPath $sourceRootPath -Recurse -File | + Where-Object { + $_.Extension -in @(".c", ".cc", ".cpp", ".cxx") -and + $_.FullName -notmatch "[\\/]out[\\/]" + } | + ForEach-Object { + $stem = $_.BaseName.ToLowerInvariant() + if (-not $sourceByStem.ContainsKey($stem)) { + $sourceByStem[$stem] = [Collections.Generic.List[string]]::new() + } + $relative = $_.FullName.Substring($sourceRootPath.Length).TrimStart("\", "/").Replace("\", "/") + $sourceByStem[$stem].Add($relative) + } + +$pdbSourceByStem = @{} +foreach ($sourceFile in $inventory.sourceFiles) { + $path = if ($sourceFile -is [string]) { $sourceFile } else { $sourceFile.name } + if (-not $path) { continue } + $extension = [IO.Path]::GetExtension($path).ToLowerInvariant() + if ($extension -notin @(".c", ".cc", ".cpp", ".cxx")) { continue } + $stem = [IO.Path]::GetFileNameWithoutExtension($path).ToLowerInvariant() + if (-not $pdbSourceByStem.ContainsKey($stem)) { + $pdbSourceByStem[$stem] = [Collections.Generic.List[string]]::new() + } + if (-not $pdbSourceByStem[$stem].Contains($path)) { + $pdbSourceByStem[$stem].Add($path) + } +} + +$functionCounts = @{} +foreach ($function in $inventory.functions) { + if (-not $function.compiland) { continue } + if (-not $functionCounts.ContainsKey($function.compiland)) { + $functionCounts[$function.compiland] = 0 + } + $functionCounts[$function.compiland]++ +} + +$rows = foreach ($compilandEntry in $inventory.compilands) { + $compiland = if ($compilandEntry -is [string]) { $compilandEntry } else { $compilandEntry.name } + $stem = [IO.Path]::GetFileNameWithoutExtension($compiland).ToLowerInvariant() + $sources = if ($sourceByStem.ContainsKey($stem)) { @($sourceByStem[$stem] | Sort-Object -Unique) } else { @() } + $pdbSources = if ($pdbSourceByStem.ContainsKey($stem)) { @($pdbSourceByStem[$stem] | Sort-Object -Unique) } else { @() } + + [pscustomobject]@{ + compiland = $compiland + objectStem = $stem + pdbSource = $pdbSources -join " | " + reconstructedSource = $sources -join " | " + mappingStatus = if ($sources.Count -eq 0) { "missing" } elseif ($sources.Count -eq 1) { "mapped-one" } else { "mapped-many" } + pdbFunctionCount = if ($functionCounts.ContainsKey($compiland)) { $functionCounts[$compiland] } else { 0 } + } +} + +$rows | + Sort-Object compiland | + Export-Csv -LiteralPath $OutputPath -NoTypeInformation -Encoding UTF8 + +$summary = $rows | Group-Object mappingStatus | Sort-Object Name +$summary | ForEach-Object { "{0}: {1}" -f $_.Name, $_.Count } diff --git a/src/tools/reconstruction/Export-EvidenceManifest.ps1 b/src/tools/reconstruction/Export-EvidenceManifest.ps1 new file mode 100644 index 0000000..04c2e76 --- /dev/null +++ b/src/tools/reconstruction/Export-EvidenceManifest.ps1 @@ -0,0 +1,44 @@ +param( + [string]$RepositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..")), + [string]$OutputPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\evidence-manifest.json") +) + +$ErrorActionPreference = "Stop" + +$evidenceNames = @( + "quake4.exe", + "quake4.pdb", + "quake4.c", + "q4xbase\gamex86.dll" +) + +$files = foreach ($name in $evidenceNames) { + $path = Join-Path $RepositoryRoot $name + $item = Get-Item -LiteralPath $path + $hash = Get-FileHash -LiteralPath $path -Algorithm SHA256 + + [ordered]@{ + path = $name.Replace("\", "/") + bytes = $item.Length + modifiedUtc = $item.LastWriteTimeUtc.ToString("o") + sha256 = $hash.Hash.ToLowerInvariant() + } +} + +$manifest = [ordered]@{ + schemaVersion = 1 + generatedUtc = [DateTime]::UtcNow.ToString("o") + target = [ordered]@{ + architecture = "x86" + imageBase = "0x10000000" + compiler = "Microsoft Visual C++ 13.10.3077" + pdbGuid = "0F109363-54B5-494B-907D-0933AAE6B5D9" + pdbAge = 119 + } + files = $files +} + +$json = $manifest | ConvertTo-Json -Depth 5 +[IO.File]::WriteAllText($OutputPath, $json + [Environment]::NewLine, [Text.UTF8Encoding]::new($false)) +Write-Output $OutputPath + diff --git a/src/tools/reconstruction/Export-ReconstructionLedger.ps1 b/src/tools/reconstruction/Export-ReconstructionLedger.ps1 new file mode 100644 index 0000000..a9b4b9a --- /dev/null +++ b/src/tools/reconstruction/Export-ReconstructionLedger.ps1 @@ -0,0 +1,104 @@ +param( + [string]$RepositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..")), + [string]$PdbInventoryPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\pdb-inventory.json"), + [string]$ImplementationMapPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\implementation-map.csv"), + [string]$LedgerPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\function-ledger.csv"), + [string]$SummaryPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\function-ledger-summary.json") +) + +$ErrorActionPreference = "Stop" +$imageBase = [uint64]0x10000000 +$hexRaysPath = Join-Path $RepositoryRoot "quake4.c" + +$inventory = Get-Content -LiteralPath $PdbInventoryPath -Raw | ConvertFrom-Json + +$implementationByRva = @{} +if (Test-Path -LiteralPath $ImplementationMapPath) { + foreach ($implementation in (Import-Csv -LiteralPath $ImplementationMapPath)) { + $implementationByRva[$implementation.rva.ToUpperInvariant()] = $implementation + } +} + +$pdbByRva = @{} +foreach ($function in $inventory.functions) { + $rva = [uint64]$function.rva + $key = '{0:X8}' -f $rva + if (-not $pdbByRva.ContainsKey($key)) { + $pdbByRva[$key] = [Collections.Generic.List[object]]::new() + } + $pdbByRva[$key].Add($function) +} + +$hexByRva = @{} +$reader = [IO.File]::OpenText($hexRaysPath) +try { + $pendingRva = $null + while (($line = $reader.ReadLine()) -ne $null) { + if ($line -match '^//----- \(([0-9A-Fa-f]{8})\)') { + $virtualAddress = [Convert]::ToUInt64($matches[1], 16) + $pendingRva = $virtualAddress - $imageBase + continue + } + + if ($null -ne $pendingRva -and -not [string]::IsNullOrWhiteSpace($line)) { + $signature = $line.Trim() + if ($signature.StartsWith('//')) { + $signature = $signature.Substring(2).Trim() + } + $hexByRva[('{0:X8}' -f [uint64]$pendingRva)] = $signature + $pendingRva = $null + } + } +} +finally { + $reader.Dispose() +} + +$allRvas = @($pdbByRva.Keys + $hexByRva.Keys | Sort-Object -Unique) +$rows = foreach ($key in $allRvas) { + $rva = [Convert]::ToUInt64($key, 16) + $pdbFunctions = @( + if ($pdbByRva.ContainsKey($key)) { + $pdbByRva[$key] | ForEach-Object { $_ } + } + ) + $hasPdb = $pdbFunctions.Count -gt 0 + $hasHex = $hexByRva.ContainsKey($key) + $formattedRva = '0x{0:X8}' -f [uint64]$rva + $implementation = $implementationByRva[$formattedRva] + + [PSCustomObject][ordered]@{ + rva = $formattedRva + virtualAddress = '0x{0:X8}' -f ([uint64]$rva + $imageBase) + pdbName = ($pdbFunctions | ForEach-Object { $_.name } | Sort-Object -Unique) -join ' | ' + undecoratedName = ($pdbFunctions | ForEach-Object { $_.undecoratedName } | Sort-Object -Unique) -join ' | ' + compiland = ($pdbFunctions | ForEach-Object { $_.compiland } | Where-Object { $_ } | Sort-Object -Unique) -join ' | ' + length = ($pdbFunctions | Measure-Object -Property length -Maximum).Maximum + hexRaysSignature = if ($hasHex) { $hexByRva[$key] } else { '' } + status = if ($hasPdb -and $hasHex) { 'address-matched' } elseif ($hasPdb) { 'pdb-only' } else { 'hex-only' } + implementationPath = if ($null -ne $implementation) { $implementation.implementationPath } else { '' } + verification = if ($null -ne $implementation) { $implementation.verification } else { 'unreviewed' } + notes = if ($null -ne $implementation) { $implementation.notes } else { '' } + } +} + +$rows | Export-Csv -LiteralPath $LedgerPath -NoTypeInformation -Encoding UTF8 + +$summary = [ordered]@{ + schemaVersion = 2 + pdbFunctionRecords = $inventory.functions.Count + pdbUniqueRvas = $pdbByRva.Count + hexRaysFunctionRvas = $hexByRva.Count + unionRvas = $rows.Count + addressMatched = @($rows | Where-Object status -eq 'address-matched').Count + pdbOnly = @($rows | Where-Object status -eq 'pdb-only').Count + hexOnly = @($rows | Where-Object status -eq 'hex-only').Count + implemented = @($rows | Where-Object implementationPath).Count + compilesX86 = @($rows | Where-Object verification -like 'compiles-x86*').Count + compilesX86DebugRelease = @($rows | Where-Object verification -eq 'compiles-x86-debug-release').Count +} + +$json = $summary | ConvertTo-Json +[IO.File]::WriteAllText($SummaryPath, $json + [Environment]::NewLine, [Text.UTF8Encoding]::new($false)) + +$summary diff --git a/src/tools/reconstruction/Seed-DoomHeaders.ps1 b/src/tools/reconstruction/Seed-DoomHeaders.ps1 new file mode 100644 index 0000000..843ba73 --- /dev/null +++ b/src/tools/reconstruction/Seed-DoomHeaders.ps1 @@ -0,0 +1,53 @@ +param( + [string]$SourceRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")), + [string]$DoomRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..\DOOM-3-master\neo")), + [string]$ReportPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\doom-header-seed-report.csv") +) + +$ErrorActionPreference = "Stop" + +$sourceSets = @( + @{ Doom = 'cm'; Destination = 'cm'; Recurse = $true }, + @{ Doom = 'framework'; Destination = 'framework'; Recurse = $true }, + @{ Doom = 'renderer'; Destination = 'renderer'; Recurse = $true }, + @{ Doom = 'sound'; Destination = 'sound'; Recurse = $true }, + @{ Doom = 'ui'; Destination = 'ui'; Recurse = $true }, + @{ Doom = 'sys'; Destination = 'sys'; Recurse = $false }, + @{ Doom = 'sys\win32'; Destination = 'sys\win32'; Recurse = $true }, + @{ Doom = 'tools\compilers\aas'; Destination = 'aas'; Recurse = $true } +) + +$rows = foreach ($set in $sourceSets) { + $doomDirectory = Join-Path $DoomRoot $set.Doom + $parameters = @{ + LiteralPath = $doomDirectory + File = $true + Filter = '*.h' + } + if ($set.Recurse) { + $parameters.Recurse = $true + } + + foreach ($source in (Get-ChildItem @parameters)) { + $relative = $source.FullName.Substring($doomDirectory.Length).TrimStart('\', '/') + $destinationRelative = Join-Path $set.Destination $relative + $destination = Join-Path $SourceRoot $destinationRelative + $status = 'destination-exists' + + if (-not (Test-Path -LiteralPath $destination)) { + New-Item -ItemType Directory -Path (Split-Path -Parent $destination) -Force | Out-Null + Copy-Item -LiteralPath $source.FullName -Destination $destination + $status = 'copied' + } + + [PSCustomObject][ordered]@{ + source = $source.FullName.Substring($DoomRoot.Length).TrimStart('\', '/') + destination = $destinationRelative + status = $status + } + } +} + +$rows | Export-Csv -LiteralPath $ReportPath -NoTypeInformation -Encoding UTF8 +$rows | Group-Object status | Sort-Object Count -Descending | Select-Object Count, Name + diff --git a/src/tools/reconstruction/Seed-DoomImplementations.ps1 b/src/tools/reconstruction/Seed-DoomImplementations.ps1 new file mode 100644 index 0000000..e9a68f1 --- /dev/null +++ b/src/tools/reconstruction/Seed-DoomImplementations.ps1 @@ -0,0 +1,121 @@ +param( + [string]$SourceRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")), + [string]$DoomRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..\DOOM-3-master\neo")), + [string]$PdbInventoryPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\pdb-inventory.json"), + [string]$ReportPath = (Join-Path $PSScriptRoot "..\..\docs\reconstruction\doom-seed-report.csv") +) + +$ErrorActionPreference = "Stop" +$enginePrefix = '\Ritual\Q4x\Win32\Release\quake4\' +$originalSourcePrefix = 'c:\ritual\q4x\code\' + +function Normalize-RelativePath([string]$path) { + return $path.Replace('/', '\').TrimStart('\').ToLowerInvariant() +} + +$inventory = Get-Content -LiteralPath $PdbInventoryPath -Raw | ConvertFrom-Json + +$targetSourcesByBase = @{} +foreach ($sourceFile in $inventory.sourceFiles) { + $normalized = $sourceFile.Replace('/', '\').ToLowerInvariant() + if (-not $normalized.StartsWith($originalSourcePrefix)) { + continue + } + + $relative = $normalized.Substring($originalSourcePrefix.Length) + $extension = [IO.Path]::GetExtension($relative) + if ($extension -notin '.cpp', '.c', '.cc') { + continue + } + + $base = [IO.Path]::GetFileNameWithoutExtension($relative) + if (-not $targetSourcesByBase.ContainsKey($base)) { + $targetSourcesByBase[$base] = [Collections.Generic.List[string]]::new() + } + $targetSourcesByBase[$base].Add($relative) +} + +$doomByBase = @{} +foreach ($path in (rg --files $DoomRoot)) { + $extension = [IO.Path]::GetExtension($path).ToLowerInvariant() + if ($extension -notin '.cpp', '.c', '.cc') { + continue + } + + $base = [IO.Path]::GetFileNameWithoutExtension($path).ToLowerInvariant() + if (-not $doomByBase.ContainsKey($base)) { + $doomByBase[$base] = [Collections.Generic.List[string]]::new() + } + $doomByBase[$base].Add($path) +} + +$compilands = @( + $inventory.compilands | + ForEach-Object { $_.name } | + Where-Object { $_.StartsWith($enginePrefix, [StringComparison]::OrdinalIgnoreCase) -and $_.EndsWith('.obj', [StringComparison]::OrdinalIgnoreCase) } +) + +$rows = foreach ($compiland in $compilands) { + $moduleBase = [IO.Path]::GetFileNameWithoutExtension($compiland).ToLowerInvariant() + $targetCandidates = @( + if ($targetSourcesByBase.ContainsKey($moduleBase)) { + $targetSourcesByBase[$moduleBase] | ForEach-Object { $_ } + } + ) + $doomCandidates = @( + if ($doomByBase.ContainsKey($moduleBase)) { + $doomByBase[$moduleBase] | ForEach-Object { $_ } + } + ) + + $selectedSource = $null + $selectedRelative = $null + + foreach ($targetRelative in $targetCandidates) { + $exact = Join-Path $DoomRoot $targetRelative + if (Test-Path -LiteralPath $exact) { + $selectedSource = $exact + $selectedRelative = $targetRelative + break + } + } + + if ($null -eq $selectedSource -and $doomCandidates.Count -eq 1) { + $selectedSource = $doomCandidates[0] + if ($targetCandidates.Count -eq 1) { + $selectedRelative = $targetCandidates[0] + } else { + $selectedRelative = Normalize-RelativePath($selectedSource.Substring($DoomRoot.Length)) + } + } + + $status = 'no-doom-candidate' + $destination = '' + if ($null -ne $selectedSource) { + $destination = Join-Path $SourceRoot $selectedRelative + if (Test-Path -LiteralPath $destination) { + $status = 'destination-exists' + } else { + $destinationDirectory = Split-Path -Parent $destination + New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null + Copy-Item -LiteralPath $selectedSource -Destination $destination + $status = 'copied' + } + } elseif ($doomCandidates.Count -gt 1) { + $status = 'ambiguous-doom-candidate' + } + + [PSCustomObject][ordered]@{ + compiland = $compiland + module = $moduleBase + targetSourceCandidates = $targetCandidates -join ' | ' + doomCandidates = ($doomCandidates | ForEach-Object { $_.Substring($DoomRoot.Length).TrimStart('\', '/') }) -join ' | ' + selectedSource = if ($selectedSource) { $selectedSource.Substring($DoomRoot.Length).TrimStart('\', '/') } else { '' } + destination = if ($destination) { $destination.Substring($SourceRoot.Length).TrimStart('\', '/') } else { '' } + status = $status + } +} + +$rows | Export-Csv -LiteralPath $ReportPath -NoTypeInformation -Encoding UTF8 +$rows | Group-Object status | Sort-Object Count -Descending | Select-Object Count, Name + diff --git a/src/tools/reconstruction/pdb_inventory.cpp b/src/tools/reconstruction/pdb_inventory.cpp new file mode 100644 index 0000000..dd53786 --- /dev/null +++ b/src/tools/reconstruction/pdb_inventory.cpp @@ -0,0 +1,611 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using DllGetClassObjectProc = HRESULT(STDAPICALLTYPE *)(REFCLSID, REFIID, LPVOID *); + +template +void Release(T *&value) { + if (value != nullptr) { + value->Release(); + value = nullptr; + } +} + +std::string Utf8(const wchar_t *text) { + if (text == nullptr || *text == L'\0') { + return {}; + } + + const int size = WideCharToMultiByte(CP_UTF8, 0, text, -1, nullptr, 0, nullptr, nullptr); + std::string result(static_cast(size), '\0'); + WideCharToMultiByte(CP_UTF8, 0, text, -1, result.data(), size, nullptr, nullptr); + result.resize(static_cast(size - 1)); + return result; +} + +std::string SymbolString(IDiaSymbol *symbol, HRESULT(STDMETHODCALLTYPE IDiaSymbol::*getter)(BSTR *)) { + BSTR value = nullptr; + if (FAILED((symbol->*getter)(&value)) || value == nullptr) { + return {}; + } + const std::string result = Utf8(value); + SysFreeString(value); + return result; +} + +std::string JsonEscape(const std::string &value) { + std::ostringstream output; + for (const unsigned char ch : value) { + switch (ch) { + case '\\': output << "\\\\"; break; + case '"': output << "\\\""; break; + case '\b': output << "\\b"; break; + case '\f': output << "\\f"; break; + case '\n': output << "\\n"; break; + case '\r': output << "\\r"; break; + case '\t': output << "\\t"; break; + default: + if (ch < 0x20) { + output << "\\u" << std::hex << std::setw(4) << std::setfill('0') + << static_cast(ch) << std::dec; + } else { + output << static_cast(ch); + } + break; + } + } + return output.str(); +} + +std::string HexRva(DWORD rva) { + std::ostringstream output; + output << "0x" << std::uppercase << std::hex << std::setw(8) << std::setfill('0') << rva; + return output.str(); +} + +std::string GuidString(const GUID &guid) { + wchar_t value[40] = {}; + StringFromGUID2(guid, value, static_cast(std::size(value))); + return Utf8(value); +} + +std::string BasicTypeName(DWORD type, ULONGLONG length) { + switch (type) { + case btVoid: return "void"; + case btChar: return "char"; + case btWChar: return "wchar_t"; + case btInt: + if (length == 1) return "signed char"; + if (length == 2) return "short"; + if (length == 8) return "__int64"; + return "int"; + case btUInt: + if (length == 1) return "unsigned char"; + if (length == 2) return "unsigned short"; + if (length == 8) return "unsigned __int64"; + return "unsigned int"; + case btFloat: return length == 8 ? "double" : "float"; + case btBool: return "bool"; + case btLong: return "long"; + case btULong: return "unsigned long"; + case btCurrency: return "CURRENCY"; + case btDate: return "DATE"; + case btVariant: return "VARIANT"; + case btComplex: return "complex"; + case btBit: return "bit"; + case btBSTR: return "BSTR"; + case btHresult: return "HRESULT"; + default: return ""; + } +} + +std::string TypeName(IDiaSymbol *symbol, int depth = 0) { + if (symbol == nullptr || depth > 12) { + return ""; + } + + DWORD tag = SymTagNull; + symbol->get_symTag(&tag); + std::string result; + + if (tag == SymTagPointerType) { + IDiaSymbol *pointee = nullptr; + if (SUCCEEDED(symbol->get_type(&pointee)) && pointee != nullptr) { + result = TypeName(pointee, depth + 1) + " *"; + Release(pointee); + } else { + result = "void *"; + } + } else if (tag == SymTagArrayType) { + IDiaSymbol *element = nullptr; + DWORD count = 0; + symbol->get_count(&count); + if (SUCCEEDED(symbol->get_type(&element)) && element != nullptr) { + result = TypeName(element, depth + 1); + Release(element); + } else { + result = ""; + } + result += "[" + std::to_string(count) + "]"; + } else if (tag == SymTagBaseType) { + DWORD baseType = btNoType; + ULONGLONG length = 0; + symbol->get_baseType(&baseType); + symbol->get_length(&length); + result = BasicTypeName(baseType, length); + } else { + result = SymbolString(symbol, &IDiaSymbol::get_name); + if (result.empty()) { + result = ""; + } + } + + BOOL isConst = FALSE; + BOOL isVolatile = FALSE; + symbol->get_constType(&isConst); + symbol->get_volatileType(&isVolatile); + if (isConst) result = "const " + result; + if (isVolatile) result = "volatile " + result; + return result; +} + +std::string FindCompilandName(IDiaSymbol *symbol) { + IDiaSymbol *current = symbol; + current->AddRef(); + + for (;;) { + DWORD tag = SymTagNull; + if (SUCCEEDED(current->get_symTag(&tag)) && tag == SymTagCompiland) { + const std::string name = SymbolString(current, &IDiaSymbol::get_name); + Release(current); + return name; + } + + IDiaSymbol *parent = nullptr; + if (FAILED(current->get_lexicalParent(&parent)) || parent == nullptr) { + Release(current); + return {}; + } + Release(current); + current = parent; + } +} + +bool CreateDiaSource(const wchar_t *dllPath, HMODULE &module, IDiaDataSource *&source) { + module = LoadLibraryW(dllPath); + if (module == nullptr) { + std::wcerr << L"Unable to load DIA provider " << dllPath << L" (" << GetLastError() << L")\n"; + return false; + } + + const auto getClassObject = reinterpret_cast( + GetProcAddress(module, "DllGetClassObject")); + if (getClassObject == nullptr) { + std::cerr << "DIA provider does not export DllGetClassObject\n"; + return false; + } + + IClassFactory *factory = nullptr; + HRESULT hr = getClassObject(__uuidof(DiaSource), IID_IClassFactory, + reinterpret_cast(&factory)); + if (FAILED(hr)) { + std::cerr << "Unable to obtain DIA class factory: 0x" << std::hex << hr << "\n"; + return false; + } + + hr = factory->CreateInstance(nullptr, __uuidof(IDiaDataSource), + reinterpret_cast(&source)); + factory->Release(); + if (FAILED(hr)) { + std::cerr << "Unable to create DIA data source: 0x" << std::hex << hr << "\n"; + return false; + } + return true; +} + +void WriteCompilands(std::ostream &output, IDiaSymbol *global) { + IDiaEnumSymbols *symbols = nullptr; + global->findChildren(SymTagCompiland, nullptr, nsNone, &symbols); + + output << " \"compilands\": [\n"; + bool first = true; + if (symbols != nullptr) { + IDiaSymbol *symbol = nullptr; + ULONG fetched = 0; + while (symbols->Next(1, &symbol, &fetched) == S_OK && fetched == 1) { + if (!first) output << ",\n"; + first = false; + output << " {\"name\": \"" + << JsonEscape(SymbolString(symbol, &IDiaSymbol::get_name)) << "\"}"; + Release(symbol); + } + } + output << "\n ],\n"; + Release(symbols); +} + +void WriteSourceFiles(std::ostream &output, IDiaSession *session) { + IDiaEnumSourceFiles *files = nullptr; + session->findFile(nullptr, nullptr, nsNone, &files); + + std::set names; + if (files != nullptr) { + IDiaSourceFile *file = nullptr; + ULONG fetched = 0; + while (files->Next(1, &file, &fetched) == S_OK && fetched == 1) { + BSTR value = nullptr; + if (SUCCEEDED(file->get_fileName(&value)) && value != nullptr) { + names.insert(Utf8(value)); + SysFreeString(value); + } + Release(file); + } + } + Release(files); + + output << " \"sourceFiles\": [\n"; + bool first = true; + for (const auto &name : names) { + if (!first) output << ",\n"; + first = false; + output << " \"" << JsonEscape(name) << "\""; + } + output << "\n ],\n"; +} + +void WriteFunctions(std::ostream &output, IDiaSymbol *global) { + IDiaEnumSymbols *compilands = nullptr; + global->findChildren(SymTagCompiland, nullptr, nsNone, &compilands); + + output << " \"functions\": [\n"; + bool first = true; + if (compilands != nullptr) { + IDiaSymbol *compiland = nullptr; + ULONG compilandFetched = 0; + while (compilands->Next(1, &compiland, &compilandFetched) == S_OK && compilandFetched == 1) { + const std::string compilandName = SymbolString(compiland, &IDiaSymbol::get_name); + IDiaEnumSymbols *symbols = nullptr; + compiland->findChildren(SymTagFunction, nullptr, nsNone, &symbols); + + if (symbols != nullptr) { + IDiaSymbol *symbol = nullptr; + ULONG fetched = 0; + while (symbols->Next(1, &symbol, &fetched) == S_OK && fetched == 1) { + DWORD rva = 0; + if (FAILED(symbol->get_relativeVirtualAddress(&rva))) { + Release(symbol); + continue; + } + + DWORD id = 0; + ULONGLONG length = 0; + symbol->get_symIndexId(&id); + symbol->get_length(&length); + + if (!first) output << ",\n"; + first = false; + output << " {\"id\": " << id + << ", \"rva\": " << rva + << ", \"rvaHex\": \"" << HexRva(rva) << "\"" + << ", \"length\": " << length + << ", \"name\": \"" << JsonEscape(SymbolString(symbol, &IDiaSymbol::get_name)) << "\"" + << ", \"undecoratedName\": \"" + << JsonEscape(SymbolString(symbol, &IDiaSymbol::get_undecoratedName)) << "\"" + << ", \"compiland\": \"" << JsonEscape(compilandName) << "\"}"; + Release(symbol); + } + } + Release(symbols); + Release(compiland); + } + } + output << "\n ],\n"; + Release(compilands); +} + +void WriteData(std::ostream &output, IDiaSymbol *global) { + IDiaEnumSymbols *symbols = nullptr; + global->findChildren(SymTagData, nullptr, nsNone, &symbols); + + output << " \"data\": [\n"; + bool first = true; + if (symbols != nullptr) { + IDiaSymbol *symbol = nullptr; + ULONG fetched = 0; + while (symbols->Next(1, &symbol, &fetched) == S_OK && fetched == 1) { + DWORD rva = 0; + DWORD id = 0; + DWORD kind = 0; + symbol->get_symIndexId(&id); + symbol->get_dataKind(&kind); + const bool hasRva = SUCCEEDED(symbol->get_relativeVirtualAddress(&rva)); + + if (!first) output << ",\n"; + first = false; + output << " {\"id\": " << id + << ", \"name\": \"" << JsonEscape(SymbolString(symbol, &IDiaSymbol::get_name)) << "\"" + << ", \"dataKind\": " << kind; + if (hasRva) { + output << ", \"rva\": " << rva << ", \"rvaHex\": \"" << HexRva(rva) << "\""; + } + output << "}"; + Release(symbol); + } + } + output << "\n ],\n"; + Release(symbols); +} + +void WriteUserDefinedTypes(std::ostream &output, IDiaSymbol *global) { + IDiaEnumSymbols *symbols = nullptr; + global->findChildren(SymTagUDT, nullptr, nsNone, &symbols); + + std::set> types; + if (symbols != nullptr) { + IDiaSymbol *symbol = nullptr; + ULONG fetched = 0; + while (symbols->Next(1, &symbol, &fetched) == S_OK && fetched == 1) { + ULONGLONG length = 0; + symbol->get_length(&length); + types.emplace(SymbolString(symbol, &IDiaSymbol::get_name), length); + Release(symbol); + } + } + Release(symbols); + + output << " \"userDefinedTypes\": [\n"; + bool first = true; + for (const auto &[name, length] : types) { + if (!first) output << ",\n"; + first = false; + output << " {\"name\": \"" << JsonEscape(name) << "\", \"length\": " << length << "}"; + } + output << "\n ],\n"; +} + +struct TypeMemberRecord { + std::string name; + std::string type; + LONG offset = 0; + ULONGLONG length = 0; + DWORD access = 0; +}; + +struct BaseClassRecord { + std::string type; + LONG offset = 0; + DWORD access = 0; +}; + +struct TypeLayoutRecord { + std::string name; + ULONGLONG length = 0; + DWORD kind = 0; + std::vector bases; + std::vector members; +}; + +void WriteTypeLayouts(std::ostream &output, IDiaSymbol *global) { + IDiaEnumSymbols *symbols = nullptr; + global->findChildren(SymTagUDT, nullptr, nsNone, &symbols); + + std::map, TypeLayoutRecord> layouts; + if (symbols != nullptr) { + IDiaSymbol *symbol = nullptr; + ULONG fetched = 0; + while (symbols->Next(1, &symbol, &fetched) == S_OK && fetched == 1) { + ULONGLONG length = 0; + DWORD kind = 0; + symbol->get_length(&length); + symbol->get_udtKind(&kind); + const std::string name = SymbolString(symbol, &IDiaSymbol::get_name); + const auto key = std::make_pair(name, length); + auto [position, inserted] = layouts.emplace(key, TypeLayoutRecord{}); + TypeLayoutRecord &layout = position->second; + if (inserted) { + layout.name = name; + layout.length = length; + layout.kind = kind; + } + + if (layout.members.empty()) { + IDiaEnumSymbols *members = nullptr; + symbol->findChildren(SymTagData, nullptr, nsNone, &members); + if (members != nullptr) { + IDiaSymbol *member = nullptr; + ULONG memberFetched = 0; + while (members->Next(1, &member, &memberFetched) == S_OK && memberFetched == 1) { + DWORD dataKind = DataIsUnknown; + member->get_dataKind(&dataKind); + if (dataKind == DataIsMember) { + TypeMemberRecord record; + record.name = SymbolString(member, &IDiaSymbol::get_name); + member->get_offset(&record.offset); + member->get_access(&record.access); + IDiaSymbol *type = nullptr; + if (SUCCEEDED(member->get_type(&type)) && type != nullptr) { + record.type = TypeName(type); + type->get_length(&record.length); + Release(type); + } + layout.members.push_back(std::move(record)); + } + Release(member); + } + } + Release(members); + } + + if (layout.bases.empty()) { + IDiaEnumSymbols *bases = nullptr; + symbol->findChildren(SymTagBaseClass, nullptr, nsNone, &bases); + if (bases != nullptr) { + IDiaSymbol *base = nullptr; + ULONG baseFetched = 0; + while (bases->Next(1, &base, &baseFetched) == S_OK && baseFetched == 1) { + BaseClassRecord record; + base->get_offset(&record.offset); + base->get_access(&record.access); + IDiaSymbol *type = nullptr; + if (SUCCEEDED(base->get_type(&type)) && type != nullptr) { + record.type = TypeName(type); + Release(type); + } else { + record.type = SymbolString(base, &IDiaSymbol::get_name); + } + layout.bases.push_back(std::move(record)); + Release(base); + } + } + Release(bases); + } + Release(symbol); + } + } + Release(symbols); + + output << " \"typeLayouts\": [\n"; + bool firstLayout = true; + for (const auto &[key, layout] : layouts) { + if (layout.members.empty() && layout.bases.empty()) continue; + if (!firstLayout) output << ",\n"; + firstLayout = false; + output << " {\"name\": \"" << JsonEscape(layout.name) + << "\", \"length\": " << layout.length + << ", \"kind\": " << layout.kind + << ", \"bases\": ["; + bool firstBase = true; + for (const auto &base : layout.bases) { + if (!firstBase) output << ", "; + firstBase = false; + output << "{\"type\": \"" << JsonEscape(base.type) + << "\", \"offset\": " << base.offset + << ", \"access\": " << base.access << "}"; + } + output << "], \"members\": ["; + bool firstMember = true; + for (const auto &member : layout.members) { + if (!firstMember) output << ", "; + firstMember = false; + output << "{\"name\": \"" << JsonEscape(member.name) + << "\", \"type\": \"" << JsonEscape(member.type) + << "\", \"offset\": " << member.offset + << ", \"length\": " << member.length + << ", \"access\": " << member.access << "}"; + } + output << "]}"; + } + output << "\n ]\n"; +} + +} // namespace + +int wmain(int argc, wchar_t **argv) { + if (argc != 4) { + std::wcerr << L"usage: q4_pdb_inventory \n"; + return 2; + } + + HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr)) { + std::cerr << "CoInitializeEx failed: 0x" << std::hex << hr << "\n"; + return 1; + } + + HMODULE diaModule = nullptr; + IDiaDataSource *source = nullptr; + IDiaSession *session = nullptr; + IDiaSymbol *global = nullptr; + + if (!CreateDiaSource(argv[1], diaModule, source)) { + CoUninitialize(); + return 1; + } + + hr = source->loadDataFromPdb(argv[2]); + if (SUCCEEDED(hr)) hr = source->openSession(&session); + if (SUCCEEDED(hr)) hr = session->get_globalScope(&global); + if (FAILED(hr)) { + std::cerr << "Unable to open PDB: 0x" << std::hex << hr << "\n"; + Release(global); + Release(session); + Release(source); + FreeLibrary(diaModule); + CoUninitialize(); + return 1; + } + + std::ofstream output(std::filesystem::path(argv[3]), std::ios::binary | std::ios::trunc); + if (!output) { + std::wcerr << L"Unable to create " << argv[3] << L"\n"; + Release(global); + Release(session); + Release(source); + FreeLibrary(diaModule); + CoUninitialize(); + return 1; + } + + GUID guid = {}; + DWORD age = 0; + global->get_guid(&guid); + global->get_age(&age); + + output << "{\n" + << " \"schemaVersion\": 2,\n" + << " \"pdb\": {\"guid\": \"" << JsonEscape(GuidString(guid)) + << "\", \"age\": " << age << "},\n"; + WriteCompilands(output, global); + WriteSourceFiles(output, session); + WriteFunctions(output, global); + WriteData(output, global); + WriteUserDefinedTypes(output, global); + WriteTypeLayouts(output, global); + output << "}\n"; + + Release(global); + Release(session); + Release(source); + FreeLibrary(diaModule); + CoUninitialize(); + return 0; +} diff --git a/src/ui/BindWindow.h b/src/ui/BindWindow.h new file mode 100644 index 0000000..4c331f8 --- /dev/null +++ b/src/ui/BindWindow.h @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __BINDWINDOW_H +#define __BINDWINDOW_H + +class idUserInterfaceLocal; +class idBindWindow : public idWindow { +public: + idBindWindow(idUserInterfaceLocal *gui); + idBindWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idBindWindow(); + + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual size_t Allocated(){return idWindow::Allocated();}; +// +// + virtual idWinVar *GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL ); +// + virtual void Activate( bool activate, idStr &act ); + +private: + void CommonInit(); + idWinStr bindName; + bool waitingOnKey; +}; + +#endif // __BINDWINDOW_H diff --git a/src/ui/ChoiceWindow.h b/src/ui/ChoiceWindow.h new file mode 100644 index 0000000..1b52788 --- /dev/null +++ b/src/ui/ChoiceWindow.h @@ -0,0 +1,78 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __CHOICEWINDOW_H +#define __CHOICEWINDOW_H + +#include "Window.h" + +class idUserInterfaceLocal; +class idChoiceWindow : public idWindow { +public: + idChoiceWindow(idUserInterfaceLocal *gui); + idChoiceWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idChoiceWindow(); + + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual void Activate( bool activate, idStr &act ); + virtual size_t Allocated(){return idWindow::Allocated();}; + + virtual idWinVar *GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + void RunNamedEvent( const char* eventName ); + +private: + virtual bool ParseInternalVar(const char *name, idParser *src); + void CommonInit(); + void UpdateChoice(); + void ValidateChoice(); + + void InitVars(); + // true: read the updated cvar from cvar system, gui from dict + // false: write to the cvar system, to the gui dict + // force == true overrides liveUpdate 0 + void UpdateVars( bool read, bool force = false ); + + void UpdateChoicesAndVals( void ); + + int currentChoice; + int choiceType; + idStr latchedChoices; + idWinStr choicesStr; + idStr latchedVals; + idWinStr choiceVals; + idStrList choices; + idStrList values; + + idWinStr guiStr; + idWinStr cvarStr; + idCVar * cvar; + idMultiWinVar updateStr; + + idWinBool liveUpdate; + idWinStr updateGroup; +}; + +#endif // __CHOICEWINDOW_H diff --git a/src/ui/DeviceContext.h b/src/ui/DeviceContext.h new file mode 100644 index 0000000..08c5d3b --- /dev/null +++ b/src/ui/DeviceContext.h @@ -0,0 +1,186 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __DEVICECONTEXT_H__ +#define __DEVICECONTEXT_H__ + +// device context support for gui stuff +// + +#include "Rectangle.h" + +const int VIRTUAL_WIDTH = 640; +const int VIRTUAL_HEIGHT = 480; +const int BLINK_DIVISOR = 200; + +struct embeddedIcon_s { + const idMaterial *shader; + char code[4]; + int width; + int height; + float s1; + float s2; + float t1; + float t2; +}; + +class idDeviceContext { +public: + idDeviceContext(); + ~idDeviceContext() { } + + void Init(); + void Shutdown(); + bool Initialized() { return initialized; } + void EnableLocalization(); + + void GetTransformInfo(idVec3& origin, idMat3& mat ); + + void SetTransformInfo(const idVec3 &origin, const idMat3 &mat); + void DrawMaterial(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex = 1.0, float scaley = 1.0); + void DrawRect(float x, float y, float width, float height, float size, const idVec4 &color); + void DrawFilledRect(float x, float y, float width, float height, const idVec4 &color); + int DrawText(const char *text, float textScale, int textAlign, idVec4 color, idRectangle rectDraw, bool wrap, int cursor = -1, bool calcOnly = false, idList *breaks = NULL, int limit = 0 ); + void DrawMaterialRect( float x, float y, float w, float h, float size, const idMaterial *mat, const idVec4 &color); + void DrawStretchPic(float x, float y, float w, float h, float s0, float t0, float s1, float t1, const idMaterial *mat); + + void DrawMaterialRotated(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex = 1.0, float scaley = 1.0, float angle = 0.0f); + void DrawStretchPicRotated(float x, float y, float w, float h, float s0, float t0, float s1, float t1, const idMaterial *mat, float angle = 0.0f); + + int CharWidth( const char c, float scale, int adjust = 0 ); + int TextWidth(const char *text, float scale, int limit, int adjust = 0); + int TextHeight(const char *text, float scale, int limit); + int MaxCharHeight(float scale); + int MaxCharWidth(float scale); + bool GetMaxTextIndex( const char *text, int width, float scale, wrapInfo_t& wrapInfo ); + + int FindFont( const char *name ); + void SetupFonts(); + + idRegion *GetTextRegion(const char *text, float textScale, idRectangle rectDraw, float xStart, float yStart); + + void SetSize(float width, float height); + + const idMaterial *GetScrollBarImage(int index); + + void DrawCursor(float *x, float *y, float size); + void SetCursor(int n); + + void AdjustCoords(float *x, float *y, float *w, float *h); + bool ClippedCoords(float *x, float *y, float *w, float *h); + bool ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2); + + void PushClipRect(float x, float y, float w, float h); + void PushClipRect(idRectangle r); + void PopClipRect(); + + void EnableClipping(bool b) { enableClipping = b; }; + void SetFont( int num ); + void RegisterIcon( const char *code, const char *shader, int x, int y, int w, int h ); + void SizeIcons( void ); + + void SetOverStrike(bool b) { overStrikeMode = b; } + + bool GetOverStrike() { return overStrikeMode; } + + void DrawEditCursor(float x, float y, float scale); + + enum { + CURSOR_ARROW, + CURSOR_HAND, + CURSOR_COUNT + }; + + enum { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + }; + + enum { + SCROLLBAR_HBACK, + SCROLLBAR_VBACK, + SCROLLBAR_THUMB, + SCROLLBAR_RIGHT, + SCROLLBAR_LEFT, + SCROLLBAR_UP, + SCROLLBAR_DOWN, + SCROLLBAR_COUNT + }; + + static idVec4 colorPurple; + static idVec4 colorOrange; + static idVec4 colorYellow; + static idVec4 colorGreen; + static idVec4 colorBlue; + static idVec4 colorRed; + static idVec4 colorWhite; + static idVec4 colorBlack; + static idVec4 colorNone; + +private: + int DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor = -1, bool resetEscapes = true); + void PaintChar(float x,float y,float width,float height,float scale,float s,float t,float s2,float t2,const idMaterial *hShader); + void SetFontByScale( float scale ); + void Clear( void ); + void SizeIcon( embeddedIcon_s *icon ); + + const idMaterial *cursorImages[CURSOR_COUNT]; + const idMaterial *scrollBarImages[SCROLLBAR_COUNT]; + const idMaterial *whiteImage; + fontInfoEx_t *activeFont; + fontInfo_t *useFont; + idStr fontName; + float xScale; + float yScale; + + float vidHeight; + float vidWidth; + + int cursor; + + idList clipRects; + + static idList fonts; + idStr fontLang; + + bool enableClipping; + + bool overStrikeMode; + + idMat3 mat; + idVec3 origin; + bool initialized; + bool isMatIdentity; + bool mbcs; + idVec4 drawTextColor; + float drawTextColorAdjust; + idHashTable icons; +}; + +#if defined( _WIN32 ) && defined( Q4_RECON_ENGINE_PRIVATE ) +static_assert( sizeof( idDeviceContext ) == 0xF0, "idDeviceContext ABI drift" ); +#endif + +#endif /* !__DEVICECONTEXT_H__ */ diff --git a/src/ui/EditWindow.h b/src/ui/EditWindow.h new file mode 100644 index 0000000..4afd067 --- /dev/null +++ b/src/ui/EditWindow.h @@ -0,0 +1,92 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __EDITWINDOW_H__ +#define __EDITWINDOW_H__ + +#include "Window.h" + +const int MAX_EDITFIELD = 4096; + +class idUserInterfaceLocal; +class idSliderWindow; + +class idEditWindow : public idWindow { +public: + idEditWindow(idUserInterfaceLocal *gui); + idEditWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idEditWindow(); + + virtual void Draw( int time, float x, float y ); + virtual const char *HandleEvent( const sysEvent_t *event, bool *updateVisuals ); + virtual void PostParse(); + virtual void GainFocus(); + virtual size_t Allocated(){return idWindow::Allocated();}; + + virtual idWinVar * GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL ); + + virtual void HandleBuddyUpdate(idWindow *buddy); + virtual void Activate(bool activate, idStr &act); + + void RunNamedEvent( const char* eventName ); + +private: + + virtual bool ParseInternalVar(const char *name, idParser *src); + + void InitCvar(); + // true: read the updated cvar from cvar system + // false: write to the cvar system + // force == true overrides liveUpdate 0 + void UpdateCvar( bool read, bool force = false ); + + void CommonInit(); + void EnsureCursorVisible(); + void InitScroller( bool horizontal ); + + int maxChars; + int paintOffset; + int cursorPos; + int cursorLine; + int cvarMax; + bool wrap; + bool readonly; + bool numeric; + idStr sourceFile; + idSliderWindow * scroller; + idList breaks; + float sizeBias; + int textIndex; + int lastTextLength; + bool forceScroll; + idWinBool password; + + idWinStr cvarStr; + idCVar * cvar; + + idWinBool liveUpdate; + idWinStr cvarGroup; +}; + +#endif /* !__EDITWINDOW_H__ */ diff --git a/src/ui/FieldWindow.h b/src/ui/FieldWindow.h new file mode 100644 index 0000000..1218667 --- /dev/null +++ b/src/ui/FieldWindow.h @@ -0,0 +1,50 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __FIELDWINDOW_H +#define __FIELDWINDOW_H + +#include "Window.h" + + +class idFieldWindow : public idWindow { +public: + idFieldWindow(idUserInterfaceLocal *gui); + idFieldWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idFieldWindow(); + + virtual void Draw(int time, float x, float y); + +private: + virtual bool ParseInternalVar(const char *name, idParser *src); + void CommonInit(); + void CalcPaintOffset(int len); + int cursorPos; + int lastTextLength; + int lastCursorPos; + int paintOffset; + bool showCursor; + idStr cursorVar; +}; + +#endif // __FIELDWINDOW_H diff --git a/src/ui/GameBearShootWindow.h b/src/ui/GameBearShootWindow.h new file mode 100644 index 0000000..5077bdd --- /dev/null +++ b/src/ui/GameBearShootWindow.h @@ -0,0 +1,129 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __GAME_BEARSHOOT_WINDOW_H__ +#define __GAME_BEARSHOOT_WINDOW_H__ + +class idGameBearShootWindow; + +class BSEntity { +public: + const idMaterial * material; + idStr materialName; + float width, height; + bool visible; + + idVec4 entColor; + idVec2 position; + float rotation; + float rotationSpeed; + idVec2 velocity; + + bool fadeIn; + bool fadeOut; + + idGameBearShootWindow * game; + +public: + BSEntity(idGameBearShootWindow* _game); + virtual ~BSEntity(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameBearShootWindow* _game ); + + void SetMaterial(const char* name); + void SetSize( float _width, float _height ); + void SetVisible( bool isVisible ); + + virtual void Update( float timeslice ); + virtual void Draw(idDeviceContext *dc); + +private: +}; + + +class idGameBearShootWindow : public idWindow { +public: + idGameBearShootWindow(idUserInterfaceLocal *gui); + idGameBearShootWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + ~idGameBearShootWindow(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual const char* Activate(bool activate); + virtual idWinVar * GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + +private: + void CommonInit(); + void ResetGameState(); + + void UpdateBear(); + void UpdateHelicopter(); + void UpdateTurret(); + void UpdateButtons(); + void UpdateGame(); + void UpdateScore(); + + virtual bool ParseInternalVar(const char *name, idParser *src); + +private: + + idWinBool gamerunning; + idWinBool onFire; + idWinBool onContinue; + idWinBool onNewGame; + + float timeSlice; + float timeRemaining; + bool gameOver; + + int currentLevel; + int goalsHit; + bool updateScore; + bool bearHitTarget; + + float bearScale; + bool bearIsShrinking; + int bearShrinkStartTime; + + float turretAngle; + float turretForce; + + float windForce; + int windUpdateTime; + + idList entities; + + BSEntity *turret; + BSEntity *bear; + BSEntity *helicopter; + BSEntity *goal; + BSEntity *wind; + BSEntity *gunblast; +}; + +#endif //__GAME_BEARSHOOT_WINDOW_H__ diff --git a/src/ui/GameBustOutWindow.h b/src/ui/GameBustOutWindow.h new file mode 100644 index 0000000..a2065d4 --- /dev/null +++ b/src/ui/GameBustOutWindow.h @@ -0,0 +1,182 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __GAME_BUSTOUT_WINDOW_H__ +#define __GAME_BUSTOUT_WINDOW_H__ + +class idGameBustOutWindow; + +typedef enum { + POWERUP_NONE = 0, + POWERUP_BIGPADDLE, + POWERUP_MULTIBALL +} powerupType_t; + +class BOEntity { +public: + bool visible; + + idStr materialName; + const idMaterial * material; + float width, height; + idVec4 color; + idVec2 position; + idVec2 velocity; + + powerupType_t powerup; + + bool removed; + bool fadeOut; + + idGameBustOutWindow * game; + +public: + BOEntity(idGameBustOutWindow* _game); + virtual ~BOEntity(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameBustOutWindow* _game ); + + void SetMaterial(const char* name); + void SetSize( float _width, float _height ); + void SetColor( float r, float g, float b, float a ); + void SetVisible( bool isVisible ); + + virtual void Update( float timeslice, int guiTime ); + virtual void Draw(idDeviceContext *dc); + +private: +}; + +typedef enum { + COLLIDE_NONE = 0, + COLLIDE_DOWN, + COLLIDE_UP, + COLLIDE_LEFT, + COLLIDE_RIGHT +} collideDir_t; + +class BOBrick { +public: + float x; + float y; + float width; + float height; + powerupType_t powerup; + + bool isBroken; + + BOEntity *ent; + +public: + BOBrick(); + BOBrick( BOEntity *_ent, float _x, float _y, float _width, float _height ); + ~BOBrick(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameBustOutWindow *game ); + + void SetColor( idVec4 bcolor ); + collideDir_t checkCollision( idVec2 pos, idVec2 vel ); + +private: +}; + +#define BOARD_ROWS 12 + +class idGameBustOutWindow : public idWindow { +public: + idGameBustOutWindow(idUserInterfaceLocal *gui); + idGameBustOutWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + ~idGameBustOutWindow(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual const char* Activate(bool activate); + virtual idWinVar * GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + idList entities; + +private: + void CommonInit(); + void ResetGameState(); + + void ClearBoard(); + void ClearPowerups(); + void ClearBalls(); + + void LoadBoardFiles(); + void SetCurrentBoard(); + void UpdateGame(); + void UpdatePowerups(); + void UpdatePaddle(); + void UpdateBall(); + void UpdateScore(); + + BOEntity * CreateNewBall(); + BOEntity * CreatePowerup( BOBrick *brick ); + + virtual bool ParseInternalVar(const char *name, idParser *src); + +private: + + idWinBool gamerunning; + idWinBool onFire; + idWinBool onContinue; + idWinBool onNewGame; + idWinBool onNewLevel; + + float timeSlice; + bool gameOver; + + int numLevels; + byte * levelBoardData; + bool boardDataLoaded; + + int numBricks; + int currentLevel; + + bool updateScore; + int gameScore; + int nextBallScore; + + int bigPaddleTime; + float paddleVelocity; + + float ballSpeed; + int ballsRemaining; + int ballsInPlay; + bool ballHitCeiling; + + idList balls; + idList powerUps; + + BOBrick *paddle; + idList board[BOARD_ROWS]; +}; + +#endif //__GAME_BUSTOUT_WINDOW_H__ diff --git a/src/ui/GameSSDWindow.h b/src/ui/GameSSDWindow.h new file mode 100644 index 0000000..286ae9f --- /dev/null +++ b/src/ui/GameSSDWindow.h @@ -0,0 +1,612 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __GAME_SSD_WINDOW_H__ +#define __GAME_SSD_WINDOW_H__ + +class idGameSSDWindow; + +class SSDCrossHair { + +public: + enum { + CROSSHAIR_STANDARD = 0, + CROSSHAIR_SUPER, + CROSSHAIR_COUNT + }; + const idMaterial* crosshairMaterial[CROSSHAIR_COUNT]; + int currentCrosshair; + float crosshairWidth, crosshairHeight; + +public: + SSDCrossHair(); + ~SSDCrossHair(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + void InitCrosshairs(); + void Draw(idDeviceContext *dc, const idVec2& cursor); +}; + +enum { + SSD_ENTITY_BASE = 0, + SSD_ENTITY_ASTEROID, + SSD_ENTITY_ASTRONAUT, + SSD_ENTITY_EXPLOSION, + SSD_ENTITY_POINTS, + SSD_ENTITY_PROJECTILE, + SSD_ENTITY_POWERUP +}; + +class SSDEntity { + +public: + //SSDEntity Information + int type; + int id; + idStr materialName; + const idMaterial* material; + idVec3 position; + idVec2 size; + float radius; + float hitRadius; + float rotation; + + idVec4 matColor; + + idStr text; + float textScale; + idVec4 foreColor; + + idGameSSDWindow* game; + int currentTime; + int lastUpdate; + int elapsed; + + bool destroyed; + bool noHit; + bool noPlayerDamage; + + bool inUse; + + +public: + SSDEntity(); + virtual ~SSDEntity(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void EntityInit(); + + void SetGame(idGameSSDWindow* _game); + void SetMaterial(const char* _name); + void SetPosition(const idVec3& _position); + void SetSize(const idVec2& _size); + void SetRadius(float _radius, float _hitFactor = 1.0f); + void SetRotation(float _rotation); + + void Update(); + bool HitTest(const idVec2& pt); + + + virtual void EntityUpdate() {}; + virtual void Draw(idDeviceContext *dc); + virtual void DestroyEntity(); + + virtual void OnHit(int key) {}; + virtual void OnStrikePlayer() {}; + + idBounds WorldToScreen(const idBounds worldBounds); + idVec3 WorldToScreen(const idVec3& worldPos); + + idVec3 ScreenToWorld(const idVec3& screenPos); + +}; + +/* +***************************************************************************** +* SSDMover +**************************************************************************** +*/ + +class SSDMover : public SSDEntity { + +public: + idVec3 speed; + float rotationSpeed; + +public: + SSDMover(); + virtual ~SSDMover(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void MoverInit(const idVec3& _speed, float _rotationSpeed); + + virtual void EntityUpdate(); + + +}; + +/* +***************************************************************************** +* SSDAsteroid +**************************************************************************** +*/ + +#define MAX_ASTEROIDS 64 + +class SSDAsteroid : public SSDMover { + +public: + + int health; + +public: + SSDAsteroid(); + ~SSDAsteroid(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& startPosition, const idVec2& _size, float _speed, float rotate, int _health); + + virtual void EntityUpdate(); + static SSDAsteroid* GetNewAsteroid(idGameSSDWindow* _game, const idVec3& startPosition, const idVec2& _size, float _speed, float rotate, int _health); + static SSDAsteroid* GetSpecificAsteroid(int id); + static void WriteAsteroids(idFile* savefile); + static void ReadAsteroids(idFile* savefile, idGameSSDWindow* _game); + + + +protected: + static SSDAsteroid asteroidPool[MAX_ASTEROIDS]; + +}; + +/* +***************************************************************************** +* SSDAstronaut +**************************************************************************** +*/ +#define MAX_ASTRONAUT 8 + +class SSDAstronaut : public SSDMover { + +public: + + int health; + +public: + SSDAstronaut(); + ~SSDAstronaut(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& startPosition, float _speed, float rotate, int _health); + + static SSDAstronaut* GetNewAstronaut(idGameSSDWindow* _game, const idVec3& startPosition, float _speed, float rotate, int _health); + static SSDAstronaut* GetSpecificAstronaut(int id); + static void WriteAstronauts(idFile* savefile); + static void ReadAstronauts(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDAstronaut astronautPool[MAX_ASTRONAUT]; + +}; + +/* +***************************************************************************** +* SSDExplosion +**************************************************************************** +*/ +#define MAX_EXPLOSIONS 64 + +class SSDExplosion : public SSDEntity { + +public: + idVec2 finalSize; + int length; + int beginTime; + int endTime; + int explosionType; + + //The entity that is exploding + SSDEntity* buddy; + bool killBuddy; + bool followBuddy; + + enum { + EXPLOSION_NORMAL = 0, + EXPLOSION_TELEPORT = 1 + }; + +public: + SSDExplosion(); + ~SSDExplosion(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& _position, const idVec2& _size, int _length, int _type, SSDEntity* _buddy, bool _killBuddy = true, bool _followBuddy = true); + + virtual void EntityUpdate(); + static SSDExplosion* GetNewExplosion(idGameSSDWindow* _game, const idVec3& _position, const idVec2& _size, int _length, int _type, SSDEntity* _buddy, bool _killBuddy = true, bool _followBuddy = true); + static SSDExplosion* GetSpecificExplosion(int id); + static void WriteExplosions(idFile* savefile); + static void ReadExplosions(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDExplosion explosionPool[MAX_EXPLOSIONS]; +}; + +#define MAX_POINTS 16 + +class SSDPoints : public SSDEntity { + + int length; + int distance; + int beginTime; + int endTime; + + idVec3 beginPosition; + idVec3 endPosition; + + idVec4 beginColor; + idVec4 endColor; + + +public: + SSDPoints(); + ~SSDPoints(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, SSDEntity* _ent, int _points, int _length, int _distance, const idVec4& color); + virtual void EntityUpdate(); + + static SSDPoints* GetNewPoints(idGameSSDWindow* _game, SSDEntity* _ent, int _points, int _length, int _distance, const idVec4& color); + static SSDPoints* GetSpecificPoints(int id); + static void WritePoints(idFile* savefile); + static void ReadPoints(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDPoints pointsPool[MAX_POINTS]; +}; + +#define MAX_PROJECTILES 64 + +class SSDProjectile : public SSDEntity { + + idVec3 dir; + idVec3 speed; + int beginTime; + int endTime; + + idVec3 endPosition; + +public: + SSDProjectile(); + ~SSDProjectile(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + void Init(idGameSSDWindow* _game, const idVec3& _beginPosition, const idVec3& _endPosition, float _speed, float _size); + virtual void EntityUpdate(); + + static SSDProjectile* GetNewProjectile(idGameSSDWindow* _game, const idVec3& _beginPosition, const idVec3& _endPosition, float _speed, float _size); + static SSDProjectile* GetSpecificProjectile(int id); + static void WriteProjectiles(idFile* savefile); + static void ReadProjectiles(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDProjectile projectilePool[MAX_PROJECTILES]; +}; + + +#define MAX_POWERUPS 64 + +/** +* Powerups work in two phases: +* 1.) Closed container hurls at you +* If you shoot the container it open +* 3.) If an opened powerup hits the player he aquires the powerup +* Powerup Types: +* Health - Give a specific amount of health +* Super Blaster - Increases the power of the blaster (lasts a specific amount of time) +* Asteroid Nuke - Destroys all asteroids on screen as soon as it is aquired +* Rescue Powerup - Rescues all astronauts as soon as it is acquited +* Bonus Points - Gives some bonus points when acquired +*/ +class SSDPowerup : public SSDMover { + + enum { + POWERUP_STATE_CLOSED = 0, + POWERUP_STATE_OPEN + }; + + enum { + POWERUP_TYPE_HEALTH = 0, + POWERUP_TYPE_SUPER_BLASTER, + POWERUP_TYPE_ASTEROID_NUKE, + POWERUP_TYPE_RESCUE_ALL, + POWERUP_TYPE_BONUS_POINTS, + POWERUP_TYPE_DAMAGE, + POWERUP_TYPE_MAX + }; + + int powerupState; + int powerupType; + + +public: + + +public: + SSDPowerup(); + virtual ~SSDPowerup(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile, idGameSSDWindow* _game ); + + virtual void OnHit(int key); + virtual void OnStrikePlayer(); + + void OnOpenPowerup(); + void OnActivatePowerup(); + + + + void Init(idGameSSDWindow* _game, float _speed, float _rotation); + + static SSDPowerup* GetNewPowerup(idGameSSDWindow* _game, float _speed, float _rotation); + static SSDPowerup* GetSpecificPowerup(int id); + static void WritePowerups(idFile* savefile); + static void ReadPowerups(idFile* savefile, idGameSSDWindow* _game); + +protected: + static SSDPowerup powerupPool[MAX_POWERUPS]; + +}; + + +typedef struct { + float spawnBuffer; + int needToWin; +} SSDLevelData_t; + +typedef struct { + float speedMin, speedMax; + float sizeMin, sizeMax; + float rotateMin, rotateMax; + int spawnMin, spawnMax; + int asteroidHealth; + int asteroidPoints; + int asteroidDamage; +} SSDAsteroidData_t; + +typedef struct { + float speedMin, speedMax; + float rotateMin, rotateMax; + int spawnMin, spawnMax; + int health; + int points; + int penalty; +} SSDAstronautData_t; + +typedef struct { + float speedMin, speedMax; + float rotateMin, rotateMax; + int spawnMin, spawnMax; +} SSDPowerupData_t; + +typedef struct { + float speed; + int damage; + int size; +} SSDWeaponData_t; + +/** +* SSDLevelStats_t +* Data that is used for each level. This data is reset +* each new level. +*/ +typedef struct { + int shotCount; + int hitCount; + int destroyedAsteroids; + int nextAsteroidSpawnTime; + + int killedAstronauts; + int savedAstronauts; + + //Astronaut Level Data + int nextAstronautSpawnTime; + + //Powerup Level Data + int nextPowerupSpawnTime; + + SSDEntity* targetEnt; +} SSDLevelStats_t; + +/** +* SSDGameStats_t +* Data that is used for the game that is currently running. Memset this +* to completely reset the game +*/ +typedef struct { + bool gameRunning; + + int score; + int prebonusscore; + + int health; + + int currentWeapon; + int currentLevel; + int nextLevel; + + SSDLevelStats_t levelStats; +} SSDGameStats_t; + + +class idGameSSDWindow : public idWindow { +public: + idGameSSDWindow(idUserInterfaceLocal *gui); + idGameSSDWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + ~idGameSSDWindow(); + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual idWinVar* GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + + virtual void Draw(int time, float x, float y); + + void AddHealth(int health); + void AddScore(SSDEntity* ent, int points); + void AddDamage(int damage); + + void OnNuke(); + void OnRescueAll(); + void OnSuperBlaster(); + + SSDEntity* GetSpecificEntity(int type, int id); + + void PlaySound(const char* sound); + + + + + static idRandom random; + int ssdTime; + +private: + + //Initialization + virtual bool ParseInternalVar(const char *name, idParser *src); + void ParseLevelData(int level, const idStr& levelDataString); + void ParseAsteroidData(int level, const idStr& asteroidDataString); + void ParseWeaponData(int weapon, const idStr& weaponDataString); + void ParseAstronautData(int level, const idStr& astronautDataString); + void ParsePowerupData(int level, const idStr& powerupDataString); + + void CommonInit(); + void ResetGameStats(); + void ResetLevelStats(); + void ResetEntities(); + + //Game Running Methods + void StartGame(); + void StopGame(); + void GameOver(); + + //Starting the Game + void BeginLevel(int level); + void ContinueGame(); + + //Stopping the Game + void LevelComplete(); + void GameComplete(); + + + + void UpdateGame(); + void CheckForHits(); + void ZOrderEntities(); + + void SpawnAsteroid(); + + void FireWeapon(int key); + SSDEntity* EntityHitTest(const idVec2& pt); + + void HitAsteroid(SSDAsteroid* asteroid, int key); + void AsteroidStruckPlayer(SSDAsteroid* asteroid); + + + + + + void RefreshGuiData(); + + idVec2 GetCursorWorld(); + + //Astronaut Methods + void SpawnAstronaut(); + void HitAstronaut(SSDAstronaut* astronaut, int key); + void AstronautStruckPlayer(SSDAstronaut* astronaut); + + //Powerup Methods + void SpawnPowerup(); + + + void StartSuperBlaster(); + void StopSuperBlaster(); + + //void FreeSoundEmitter( bool immediate ); + + + + +public: + + //WinVars used to call functions from the guis + idWinBool beginLevel; + idWinBool resetGame; + idWinBool continueGame; + idWinBool refreshGuiData; + + SSDCrossHair crosshair; + idBounds screenBounds; + + //Level Data + int levelCount; + idList levelData; + idList asteroidData; + idList astronautData; + idList powerupData; + + + //Weapon Data + int weaponCount; + idList weaponData; + + int superBlasterTimeout; + + //All current game data is stored in this structure (except the entity list) + SSDGameStats_t gameStats; + idList entities; + + int currentSound; + +}; + +#endif //__GAME_SSD_WINDOW_H__ diff --git a/src/ui/GameWindow.h b/src/ui/GameWindow.h new file mode 100644 index 0000000..0448d84 --- /dev/null +++ b/src/ui/GameWindow.h @@ -0,0 +1,34 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __GAMEWINDOW_H__ +#define __GAMEWINDOW_H__ + +class idGameWindowProxy : public idWindow { +public: + idGameWindowProxy( idDeviceContext *d, idUserInterfaceLocal *gui ); + void Draw( int time, float x, float y ); +}; + +#endif diff --git a/src/ui/GuiScript.h b/src/ui/GuiScript.h new file mode 100644 index 0000000..641daa0 --- /dev/null +++ b/src/ui/GuiScript.h @@ -0,0 +1,101 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __GUISCRIPT_H +#define __GUISCRIPT_H + +#include "Window.h" +#include "Winvar.h" + +struct idGSWinVar { + idGSWinVar() { + var = NULL; + own = false; + } + idWinVar* var; + bool own; +}; + +class idGuiScriptList; + +class idGuiScript { + friend class idGuiScriptList; + friend class idWindow; + +public: + idGuiScript(); + ~idGuiScript(); + + bool Parse(idParser *src); + void Execute(idWindow *win) { + if (handler) { + handler(win, &parms); + } + } + void FixupParms(idWindow *win); + size_t Size() { + int sz = sizeof(*this); + for (int i = 0; i < parms.Num(); i++) { + sz += parms[i].var->Size(); + } + return sz; + } + + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); + +protected: + int conditionReg; + idGuiScriptList *ifList; + idGuiScriptList *elseList; + idList parms; + void (*handler) (idWindow *window, idList *src); + +}; + + +class idGuiScriptList { + idList list; +public: + idGuiScriptList() { list.SetGranularity( 4 ); }; + ~idGuiScriptList() { list.DeleteContents(true); }; + void Execute(idWindow *win); + void Append(idGuiScript* gs) { + list.Append(gs); + } + size_t Size() { + int sz = sizeof(*this); + for (int i = 0; i < list.Num(); i++) { + sz += list[i]->Size(); + } + return sz; + } + void FixupParms(idWindow *win); + void ReadFromDemoFile( class idDemoFile *f ) {}; + void WriteToDemoFile( class idDemoFile *f ) {}; + + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); +}; + +#endif // __GUISCRIPT_H diff --git a/src/ui/ListGUI.h b/src/ui/ListGUI.h new file mode 100644 index 0000000..3313156 --- /dev/null +++ b/src/ui/ListGUI.h @@ -0,0 +1,63 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __LISTGUI_H__ +#define __LISTGUI_H__ + +/* +=============================================================================== + + feed data to a listDef + each item has an id and a display string + +=============================================================================== +*/ + +class idListGUI { +public: + virtual ~idListGUI() { } + + virtual void Config( idUserInterface *pGUI, const char *name ) = 0; +// RAVEN BEGIN +// shouchard: added greyed support + virtual void Add( int id, const idStr& s, bool greyed = false ) = 0; + // use the element count as index for the ids + virtual void Push( const idStr& s, bool greyed = false ) = 0; +// RAVEN END + virtual bool Del( int id ) = 0; + virtual void Clear( void ) = 0; + virtual int Num( void ) = 0; + virtual int GetSelection( char *s, int size, int sel = 0 ) const = 0; // returns the id, not the list index (or -1) + virtual void SetSelection( int sel ) = 0; + virtual int GetNumSelections() = 0; + virtual bool IsConfigured( void ) const = 0; + // by default, any modification to the list will trigger a full GUI refresh immediately + virtual void SetStateChanges( bool enable ) = 0; + virtual void Shutdown( void ) = 0; +}; + +#endif /* !__LISTGUI_H__ */ diff --git a/src/ui/ListGUILocal.h b/src/ui/ListGUILocal.h new file mode 100644 index 0000000..05d42ca --- /dev/null +++ b/src/ui/ListGUILocal.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __LISTGUILOCAL_H__ +#define __LISTGUILOCAL_H__ + +/* +=============================================================================== + + feed data to a listDef + each item has an id and a display string + +=============================================================================== +*/ + +class idListGUILocal : protected idList, public idListGUI { +public: + idListGUILocal() { m_pGUI = NULL; m_water = 0; m_stateUpdates = true; } + + // idListGUI interface + void Config( idUserInterface *pGUI, const char *name ) { m_pGUI = pGUI; m_name = name; } + void Add( int id, const idStr& s, bool greyed = false ); + // use the element count as index for the ids + void Push( const idStr& s, bool greyed = false ); + bool Del( int id ); + void Clear( void ); + int Num( void ) { return idList::Num(); } + int GetSelection( char *s, int size, int sel = 0 ) const; // returns the id, not the list index (or -1) + void SetSelection( int sel ); + int GetNumSelections(); + bool IsConfigured( void ) const; + void SetStateChanges( bool enable ); + void Shutdown( void ); + +private: + idUserInterface * m_pGUI; + idStr m_name; + int m_water; + idList m_ids; + idList m_greyed; + bool m_stateUpdates; + + void StateChanged(); +}; + +#if defined( _WIN32 ) && defined( Q4_RECON_ENGINE_PRIVATE ) +static_assert( sizeof( idListGUILocal ) == 0x60, "idListGUILocal ABI drift" ); +#endif + +#endif /* !__LISTGUILOCAL_H__ */ diff --git a/src/ui/ListWindow.h b/src/ui/ListWindow.h new file mode 100644 index 0000000..d080f5e --- /dev/null +++ b/src/ui/ListWindow.h @@ -0,0 +1,96 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __LISTWINDOW_H +#define __LISTWINDOW_H + +class idSliderWindow; + +enum { + TAB_TYPE_TEXT = 0, + TAB_TYPE_ICON = 1 +}; + +struct idTabRect { + int x; + int w; + int align; + int valign; + int type; + idVec2 iconSize; + float iconVOffset; + float textScale; +}; + +class idListWindow : public idWindow { +public: + idListWindow(idUserInterfaceLocal *gui); + idListWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + + virtual const char* HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual void Activate(bool activate, idStr &act); + virtual void HandleBuddyUpdate(idWindow *buddy); + virtual void StateChanged( bool redraw = false ); + virtual size_t Allocated(){return idWindow::Allocated();}; + virtual idWinVar* GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + void UpdateList(); + +private: + virtual bool ParseInternalVar(const char *name, idParser *src); + void CommonInit(); + void InitScroller( bool horizontal ); + void SetCurrentSel( int sel ); + void AddCurrentSel( int sel ); + int GetCurrentSel(); + bool IsSelected( int index ); + void ClearSelection( int sel ); + + idList tabInfo; + int top; + float sizeBias; + bool horizontal; + idStr tabStopStr; + idStr tabAlignStr; + idStr tabVAlignStr; + idStr tabTypeStr; + idStr tabIconSizeStr; + idStr tabIconVOffsetStr; + idStr tabTextScalesStr; + idHashTable iconMaterials; + bool multipleSel; + + idStrList listItems; + idSliderWindow* scroller; + idList currentSel; + idStr listName; + + int clickTime; + + int typedTime; + idStr typed; +}; + +#endif // __LISTWINDOW_H diff --git a/src/ui/MarkerWindow.h b/src/ui/MarkerWindow.h new file mode 100644 index 0000000..4cf2402 --- /dev/null +++ b/src/ui/MarkerWindow.h @@ -0,0 +1,70 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __MARKERWINDOW_H +#define __MARKERWINDOW_H + +class idUserInterfaceLocal; + +typedef struct { + int time; + const idMaterial *mat; + idRectangle rect; +} markerData_t; + +class idMarkerWindow : public idWindow { +public: + idMarkerWindow(idUserInterfaceLocal *gui); + idMarkerWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idMarkerWindow(); + virtual size_t Allocated(){return idWindow::Allocated();}; + virtual idWinVar *GetWinVarByName(const char *_name, bool winLookup = false); + + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual const char *RouteMouseCoords(float xd, float yd); + virtual void Activate(bool activate, idStr &act); + virtual void MouseExit(); + virtual void MouseEnter(); + + +private: + virtual bool ParseInternalVar(const char *name, idParser *src); + void CommonInit(); + void Line(int x1, int y1, int x2, int y2, dword* out, dword color); + void Point(int x, int y, dword *out, dword color); + logStats_t loggedStats[MAX_LOGGED_STATS]; + idList markerTimes; + idStr statData; + int numStats; + dword *imageBuff; + const idMaterial *markerMat; + const idMaterial *markerStop; + idVec4 markerColor; + int currentMarker; + int currentTime; + int stopTime; +}; + +#endif // __MARKERWINDOW_H diff --git a/src/ui/Rectangle.h b/src/ui/Rectangle.h new file mode 100644 index 0000000..cd52c8b --- /dev/null +++ b/src/ui/Rectangle.h @@ -0,0 +1,218 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef IDRECTANGLE_H_ +#define IDRECTANGLE_H_ +// +// simple rectangle +// +extern void RotateVector(idVec3 &v, idVec3 origin, float a, float c, float s); +class idRectangle { +public: + float x; // horiz position + float y; // vert position + float w; // width + float h; // height; + idRectangle() { x = y = w= h = 0.0; } + idRectangle(float ix, float iy, float iw, float ih) { x = ix; y = iy; w = iw; h = ih; } + float Bottom() const { return y + h; } + float Right() const { return x + w; } + void Offset (float x, float y) { + this->x += x; + this->y += y; + } + bool Contains(float xt, float yt) { + if (w == 0.0 && h == 0.0) { + return false; + } + if (xt >= x && xt <= Right() && yt >= y && yt <= Bottom()) { + return true; + } + return false; + } + void Empty() { x = y = w = h = 0.0; }; + + void ClipAgainst(idRectangle r, bool sizeOnly) { + if (!sizeOnly) { + if (x < r.x) { + x = r.x; + } + if (y < r.y) { + y = r.y; + } + } + if (x + w > r.x + r.w) { + w = (r.x + r.w) - x; + } + if (y + h > r.y + r.h) { + h = (r.y + r.h) - y; + } + } + + + + void Rotate(float a, idRectangle &out) { + idVec3 p1, p2, p3, p4, p5; + float c, s; + idVec3 center; + center.Set((x + w) / 2.0, (y + h) / 2.0, 0); + p1.Set(x, y, 0); + p2.Set(Right(), y, 0); + p4.Set(x, Bottom(), 0); + if (a) { + s = sin( DEG2RAD( a ) ); + c = cos( DEG2RAD( a ) ); + } + else { + s = c = 0; + } + RotateVector(p1, center, a, c, s); + RotateVector(p2, center, a, c, s); + RotateVector(p4, center, a, c, s); + out.x = p1.x; + out.y = p1.y; + out.w = (p2 - p1).Length(); + out.h = (p4 - p1).Length(); + } + + idRectangle & operator+=( const idRectangle &a ); + idRectangle & operator-=( const idRectangle &a ); + idRectangle & operator/=( const idRectangle &a ); + idRectangle & operator/=( const float a ); + idRectangle & operator*=( const float a ); + idRectangle & operator=( const idVec4 v ); + int operator==(const idRectangle &a) const; + float & operator[]( const int index ); + char * String( void ) const; + const idVec4& ToVec4() const; + +}; + +ID_INLINE const idVec4 &idRectangle::ToVec4() const { + return *reinterpret_cast(&x); +} + + +ID_INLINE idRectangle &idRectangle::operator+=( const idRectangle &a ) { + x += a.x; + y += a.y; + w += a.w; + h += a.h; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator/=( const idRectangle &a ) { + x /= a.x; + y /= a.y; + w /= a.w; + h /= a.h; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator/=( const float a ) { + float inva = 1.0f / a; + x *= inva; + y *= inva; + w *= inva; + h *= inva; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator-=( const idRectangle &a ) { + x -= a.x; + y -= a.y; + w -= a.w; + h -= a.h; + + return *this; +} + +ID_INLINE idRectangle &idRectangle::operator*=( const float a ) { + x *= a; + y *= a; + w *= a; + h *= a; + + return *this; +} + + +ID_INLINE idRectangle &idRectangle::operator=( const idVec4 v ) { + x = v.x; + y = v.y; + w = v.z; + h = v.w; + return *this; +} + +ID_INLINE int idRectangle::operator==( const idRectangle &a ) const { + return (x == a.x && y == a.y && w == a.w && a.h); +} + +ID_INLINE float& idRectangle::operator[]( int index ) { + return ( &x )[ index ]; +} + +class idRegion { +public: + idRegion() { }; + + void Empty() { + rects.Clear(); + } + + bool Contains(float xt, float yt) { + int c = rects.Num(); + for (int i = 0; i < c; i++) { + if (rects[i].Contains(xt, yt)) { + return true; + } + } + return false; + } + + void AddRect(float x, float y, float w, float h) { + rects.Append(idRectangle(x, y, w, h)); + } + + int GetRectCount() { + return rects.Num(); + } + + idRectangle *GetRect(int index) { + if (index >= 0 && index < rects.Num()) { + return &rects[index]; + } + return NULL; + } + +protected: + + idList rects; +}; + + +#endif diff --git a/src/ui/RegExp.h b/src/ui/RegExp.h new file mode 100644 index 0000000..721b486 --- /dev/null +++ b/src/ui/RegExp.h @@ -0,0 +1,107 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __REGEXP_H__ +#define __REGEXP_H__ + +class idWindow; +class idWinVar; + +class idRegister { +public: + idRegister(); + idRegister( const char *p, int t ); + + enum REGTYPE { VEC4 = 0, FLOAT, BOOL, INT, STRING, VEC2, VEC3, RECTANGLE, NUMTYPES } ; + static int REGCOUNT[NUMTYPES]; + + bool enabled; + short type; + idStr name; + int regCount; + unsigned short regs[4]; + idWinVar * var; + + void SetToRegs( float *registers ); + void GetFromRegs( float *registers ); + void CopyRegs( idRegister *src ); + void Enable( bool b ) { enabled = b; } + void ReadFromDemoFile( idDemoFile *f ); + void WriteToDemoFile( idDemoFile *f ); + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); +}; + +ID_INLINE idRegister::idRegister( void ) { +} + +ID_INLINE idRegister::idRegister( const char *p, int t ) { + name = p; + type = t; + assert( t >= 0 && t < NUMTYPES ); + regCount = REGCOUNT[t]; + enabled = ( type == STRING ) ? false : true; + var = NULL; +}; + +ID_INLINE void idRegister::CopyRegs( idRegister *src ) { + regs[0] = src->regs[0]; + regs[1] = src->regs[1]; + regs[2] = src->regs[2]; + regs[3] = src->regs[3]; +} + +class idRegisterList { +public: + + idRegisterList(); + ~idRegisterList(); + + void AddReg( const char *name, int type, idParser *src, idWindow *win, idWinVar *var ); + void AddReg( const char *name, int type, idVec4 data, idWindow *win, idWinVar *var ); + + idRegister * FindReg( const char *name ); + void SetToRegs( float *registers ); + void GetFromRegs( float *registers ); + void Reset(); + void ReadFromDemoFile( idDemoFile *f ); + void WriteToDemoFile( idDemoFile *f ); + void WriteToSaveGame( idFile *savefile ); + void ReadFromSaveGame( idFile *savefile ); + +private: + idList regs; + idHashIndex regHash; +}; + +ID_INLINE idRegisterList::idRegisterList() { + regs.SetGranularity( 4 ); + regHash.SetGranularity( 4 ); + regHash.Clear( 32, 4 ); +} + +ID_INLINE idRegisterList::~idRegisterList() { +} + +#endif /* !__REGEXP_H__ */ diff --git a/src/ui/RegExp_old.h b/src/ui/RegExp_old.h new file mode 100644 index 0000000..2fb0a50 --- /dev/null +++ b/src/ui/RegExp_old.h @@ -0,0 +1,83 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef REGEXP_H_ +#define REGEXP_H_ + +class idWindow; + +class idRegister { +public: + idRegister() {}; + idRegister(const char *p, int t) { + name = p; + type = t; + assert(t >= 0 && t < NUMTYPES); + regCount = REGCOUNT[t]; + enabled = (type == STRING) ? false : true; + }; + bool enabled; + int type; + int regCount; + enum REGTYPE { VEC4 = 0, FLOAT, BOOL, INT, STRING, VEC2, VEC3, NUMTYPES } ; + static int REGCOUNT[NUMTYPES]; + idStr name; + int regs[4]; + void SetToRegs(float *registers, idTypedDict *state); + void SetToRegList(idList *registers, idTypedDict *state); + void GetFromRegs(float *registers, idTypedDict *state); + void CopyRegs(idRegister *src) { + regs[0] = src->regs[0]; + regs[1] = src->regs[1]; + regs[2] = src->regs[2]; + regs[3] = src->regs[3]; + } + void Enable(bool b) { + enabled = b; + } + void ReadFromDemoFile(idDemoFile *f); + void WriteToDemoFile(idDemoFile *f); + +}; + +class idRegisterList { + idList regs; +public: + + // + void RemoveReg ( const char* name ); + // + + void AddReg(const char *name, int type, idParser *src, idWindow *win); + void AddReg(const char *name, int type, idVec4 data, idWindow *win); + idRegister *FindReg(const char *name); + int FindRegIndex ( const char* name ); + void SetToRegs(float *registers, idTypedDict *state); + void GetFromRegs(float *registers, idTypedDict *state); + void Reset(); + void ReadFromDemoFile(idDemoFile *f); + void WriteToDemoFile(idDemoFile *f); + +}; + +#endif \ No newline at end of file diff --git a/src/ui/RenderWindow.h b/src/ui/RenderWindow.h new file mode 100644 index 0000000..898ba42 --- /dev/null +++ b/src/ui/RenderWindow.h @@ -0,0 +1,71 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ +#ifndef __RENDERWINDOW_H +#define __RENDERWINDOW_H + +class idUserInterfaceLocal; +class idRenderWindow : public idWindow { +public: + idRenderWindow(idUserInterfaceLocal *gui); + idRenderWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idRenderWindow(); + + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual size_t Allocated(){return idWindow::Allocated();}; +// +// + virtual idWinVar *GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); +// + +private: + void CommonInit(); + virtual bool ParseInternalVar(const char *name, idParser *src); + void Render(int time); + void PreRender(); + void BuildAnimation(int time); + renderView_t refdef; + idRenderWorld *world; + renderEntity_t worldEntity; + renderLight_t rLight; + const idMD5Anim *modelAnim; + + qhandle_t worldModelDef; + qhandle_t lightDef; + qhandle_t modelDef; + idWinStr modelName; + idWinStr animName; + idStr animClass; + idWinVec4 lightOrigin; + idWinVec4 lightColor; + idWinVec4 modelOrigin; + idWinVec4 modelRotate; + idWinVec4 viewOffset; + idWinBool needsRender; + int animLength; + int animEndTime; + bool updateAnimation; +}; + +#endif // __RENDERWINDOW_H diff --git a/src/ui/SimpleWindow.h b/src/ui/SimpleWindow.h new file mode 100644 index 0000000..44e91a6 --- /dev/null +++ b/src/ui/SimpleWindow.h @@ -0,0 +1,102 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SIMPLEWIN_H__ +#define __SIMPLEWIN_H__ + +class idUserInterfaceLocal; +class idDeviceContext; +class idSimpleWindow; + +typedef struct { + idWindow *win; + idSimpleWindow *simp; +} drawWin_t; + +class idSimpleWindow { + friend class idWindow; +public: + idSimpleWindow(idWindow* win); + virtual ~idSimpleWindow(); + void Redraw(float x, float y); + void StateChanged( bool redraw ); + void ResetCinematics(); + + idStr name; + + idWinVar * GetWinVarByName(const char *_name); + int GetWinVarOffset( idWinVar *wv, drawWin_t* owner); + size_t Size(); + bool GetMaxTextIndex( const char *text, wrapInfo_t& wrapInfo ) const; + + idWindow* GetParent ( void ) { return mParent; } + + virtual void WriteToSaveGame( idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + +protected: + void CalcClientRect(float xofs, float yofs); + void SetupTransforms(float x, float y); + void DrawBackground(const idRectangle &drawRect); + void DrawBorderAndCaption(const idRectangle &drawRect); + + idUserInterfaceLocal *gui; + idDeviceContext *dc; + int flags; + idRectangle drawRect; // overall rect + idRectangle clientRect; // client area + idRectangle textRect; + idVec2 origin; + int fontNum; + float matScalex; + float matScaley; + float borderSize; + int textAlign; + float textAlignx; + float textAligny; + float textSpacing; + int textStyle; + int textShadow; + + idWinStr text; + idWinBool visible; + idWinRectangle rect; // overall rect + idWinVec4 backColor; + idWinVec4 matColor; + idWinVec4 foreColor; + idWinVec4 borderColor; + idWinFloat textScale; + idWinFloat rotate; + idWinVec2 shear; + idWinBackground backGroundName; + + const idMaterial* background; + + idWindow * mParent; + + idWinBool hideCursor; + idList definedVars; +}; + +#endif /* !__SIMPLEWIN_H__ */ diff --git a/src/ui/SliderWindow.h b/src/ui/SliderWindow.h new file mode 100644 index 0000000..b48549f --- /dev/null +++ b/src/ui/SliderWindow.h @@ -0,0 +1,89 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __SLIDERWINDOW_H__ +#define __SLIDERWINDOW_H__ + +class idUserInterfaceLocal; + +class idSliderWindow : public idWindow { +public: + idSliderWindow(idUserInterfaceLocal *gui); + idSliderWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idSliderWindow(); + + void InitWithDefaults(const char *_name, const idRectangle &rect, const idVec4 &foreColor, const idVec4 &matColor, const char *_background, const char *thumbShader, bool _vertical, bool _scrollbar); + + void SetRange(float _low, float _high, float _step); + float GetLow() { return low; } + float GetHigh() { return high; } + + void SetValue(float _value); + float GetValue() { return value; }; + + virtual size_t Allocated(){return idWindow::Allocated();}; + virtual idWinVar * GetWinVarByName(const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + virtual void PostParse(); + virtual void Draw(int time, float x, float y); + virtual void DrawBackground(const idRectangle &drawRect); + virtual const char *RouteMouseCoords(float xd, float yd); + virtual void Activate(bool activate, idStr &act); + virtual void SetBuddy(idWindow *buddy); + + void RunNamedEvent( const char* eventName ); + +private: + virtual bool ParseInternalVar(const char *name, idParser *src); + void CommonInit(); + void InitCvar(); + // true: read the updated cvar from cvar system + // false: write to the cvar system + // force == true overrides liveUpdate 0 + void UpdateCvar( bool read, bool force = false ); + + idWinFloat value; + float low; + float high; + float thumbWidth; + float thumbHeight; + float stepSize; + float lastValue; + idRectangle thumbRect; + const idMaterial * thumbMat; + bool vertical; + bool verticalFlip; + bool scrollbar; + idWindow * buddyWin; + idStr thumbShader; + + idWinStr cvarStr; + idCVar * cvar; + bool cvar_init; + idWinBool liveUpdate; + idWinStr cvarGroup; +}; + +#endif /* !__SLIDERWINDOW_H__ */ + diff --git a/src/ui/UserInterface.h b/src/ui/UserInterface.h new file mode 100644 index 0000000..ec1c770 --- /dev/null +++ b/src/ui/UserInterface.h @@ -0,0 +1,239 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +// Copyright (C) 2004 Id Software, Inc. +// + +#ifndef __USERINTERFACE_H__ +#define __USERINTERFACE_H__ + +struct wrapInfo_t { + int lastWhitespace; + int maxIndex; + wrapInfo_t ( void ) { + lastWhitespace = -1; + maxIndex = -1; + } +}; + +/* +=============================================================================== + + Draws an interactive 2D surface. + Used for all user interaction with the game. + +=============================================================================== +*/ + +class idFile; +class idDemoFile; + + +class idUserInterface { +public: + virtual ~idUserInterface( void ) {} + + // Returns the name of the gui. + virtual const char * Name( void ) const = 0; + + // Returns a comment on the gui. + virtual const char * Comment( void ) const = 0; + + // Returns true if the gui is interactive. + virtual bool IsInteractive() const = 0; + +// RAVEN BEGIN +// bdube: added + // Changes the interactive of the gui + virtual void SetInteractive ( bool interactive ) = 0 ; +// RAVEN END + + virtual bool IsUniqued() const = 0; + + virtual void SetUniqued( bool b ) = 0; + // returns false if it failed to load + virtual bool InitFromFile( const char *qpath, bool rebuild = true, bool cache = true ) = 0; + + // handles an event, can return an action string, the caller interprets + // any return and acts accordingly + virtual const char * HandleEvent( const sysEvent_t *event, int time, bool *updateVisuals = NULL ) = 0; + + // handles a named event + virtual void HandleNamedEvent( const char *eventName ) = 0; + + // repaints the ui + virtual void Redraw( int time ) = 0; + + // repaints the cursor + virtual void DrawCursor( void ) = 0; + + // Provides read access to the idDict that holds this gui's state. + virtual const idDict & State( void ) const = 0; + + // Removes a gui state variable + virtual void DeleteStateVar( const char *varName ) = 0; + + // Sets a gui state variable. + virtual void SetStateString( const char *varName, const char *value ) = 0; + virtual void SetStateBool( const char *varName, const bool value ) = 0; + virtual void SetStateInt( const char *varName, const int value ) = 0; + virtual void SetStateFloat( const char *varName, const float value ) = 0; + virtual void SetStateVector( const char *varName, const idVec3& vector ) = 0; + virtual void SetStateVec4( const char *varName, const idVec4& vector ) = 0; +// RAVEN BEGIN +// bdube: added way to clear state + virtual void ClearState( void ) = 0; +// rjohnson: added + virtual void DeleteState( const char *varName ) = 0; + + virtual idVec4 GetLightColor ( void ) = 0; + + // Gets a gui state variable + virtual const char* GetStateString( const char *varName, const char* defaultString = "" ) const = 0; + virtual bool GetStateBool( const char *varName, const char* defaultString = "0" ) const = 0; + virtual int GetStateInt( const char *varName, const char* defaultString = "0" ) const = 0; + virtual float GetStateFloat( const char *varName, const char* defaultString = "0" ) const = 0; + virtual idVec3 GetStateVector( const char *varName, const char* defaultString = "0 0 0" ) const = 0; + virtual idVec4 GetStateVec4( const char *varName, const char* defaultString = "0 0 0 0" ) const = 0; + +// jscott: added + virtual class idWindow * GetDesktop( void ) const = 0; +// RAVEN END + + // The state has changed and the gui needs to update from the state idDict. + virtual void StateChanged( int time, bool redraw = false ) = 0; + + // Activated the gui. + virtual const char * Activate( bool activate, int time ) = 0; + + // Triggers the gui and runs the onTrigger scripts. + virtual void Trigger( int time ) = 0; + +#ifdef Q4_RECON_ENGINE_PRIVATE + // Retail 1.3 interface names and vtable slots recovered from quake4.pdb. + virtual void ReadFromDemoFile( class idDemoFile *f ) = 0; + virtual void WriteToDemoFile( class idDemoFile *f ) = 0; +#else + virtual void ReadFromDemo( class idDemoFile *f ) = 0; + virtual void WriteToDemo( class idDemoFile *f ) = 0; +#endif + + virtual bool WriteToSaveGame( idFile *savefile ) const = 0; + virtual bool ReadFromSaveGame( idFile *savefile ) = 0; + virtual void SetKeyBindingNames( void ) = 0; + + virtual void SetCursor( float x, float y ) = 0; + virtual float CursorX( void ) = 0; + virtual float CursorY( void ) = 0; + +// RAVEN BEGIN +// mekberg: Returns the index of the string where width in pixels <= specified val. Can return index of last whitespace. + virtual bool GetMaxTextIndex( const char *windowName, const char *text, wrapInfo_t& wrapInfo ) const = 0; + +// mwhitlock: Xenon texture streaming +#if defined(_XENON) + virtual const idList& GetMaterialsList(void) = 0; +#endif +// RAVEN END + +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual bool IsLevelLoadReferenced( void ) = 0; + virtual void SetLevelLoadReferenced( bool refd ) = 0; +#endif +// RAVEN END +}; + + +class idUserInterfaceManager { +public: + virtual ~idUserInterfaceManager( void ) {} + + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + virtual void Touch( const char *name ) = 0; + virtual void WritePrecacheCommands( idFile *f ) = 0; + + // Sets the size for 640x480 adjustment. + virtual void SetSize( float width, float height ) = 0; + + virtual void BeginLevelLoad( void ) = 0; + virtual void EndLevelLoad( void ) = 0; +// RAVEN BEGIN +// mwhitlock: Dynamic memory consolidation +#if defined(_RV_MEM_SYS_SUPPORT) + virtual void FlushGUIs( void ) = 0; +#endif +// RAVEN END + + // Reloads changed guis, or all guis. + virtual void Reload( bool all ) = 0; + + // lists all guis + virtual void ListGuis( void ) const = 0; + + // Returns true if gui exists. + virtual bool CheckGui( const char *qpath ) const = 0; + + // Allocates a new gui. + virtual idUserInterface * Alloc( void ) const = 0; + + // De-allocates a gui.. ONLY USE FOR PRECACHING + virtual void DeAlloc( idUserInterface *gui ) = 0; + + // Returns NULL if gui by that name does not exist. + virtual idUserInterface * FindGui( const char *qpath, bool autoLoad = false, bool needUnique = false, bool forceUnique = false ) = 0; + +#ifdef Q4_RECON_ENGINE_PRIVATE + // Retail engine slot; the three index helpers below are later SDK additions. + virtual idUserInterface * FindDemoGui( const char *qpath ) = 0; +#else + // Returns the index into the global gui list + virtual int GuiIndex( idUserInterface *gui ) = 0; + + // Returns the gui at location index, or allocates a new one at location index + virtual idUserInterface * FindGuiByIndex( int index ) = 0; + + // Clears out the in game guis before loading a renderdemo + virtual void ClearGameGuis( void ) = 0; +#endif + + // Allocates a new GUI list handler + virtual idListGUI * AllocListGUI( void ) const = 0; + + // De-allocates a list gui + virtual void FreeListGUI( idListGUI *listgui ) = 0; + +// RAVEN BEGIN +// rjohnson: added option for guis to always think + virtual void RunAlwaysThinkGUIs ( int time ) = 0; +// bdube: embedded icons + virtual void RegisterIcon ( const char* code, const char* shader, int x = -1, int y = -1, int w = -1, int h = -1 ) = 0; +// RAVEN END +}; + +extern idUserInterfaceManager * uiManager; + +#endif /* !__USERINTERFACE_H__ */ diff --git a/src/ui/UserInterfaceLocal.h b/src/ui/UserInterfaceLocal.h new file mode 100644 index 0000000..6e0f3bb --- /dev/null +++ b/src/ui/UserInterfaceLocal.h @@ -0,0 +1,162 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +class idWindow; + +class idUserInterfaceLocal : public idUserInterface { + friend class idUserInterfaceManagerLocal; +public: + idUserInterfaceLocal(); + virtual ~idUserInterfaceLocal(); + + virtual const char * Name() const; + virtual const char * Comment() const; + virtual bool IsInteractive() const; + virtual void SetInteractive( bool interactive ); + virtual bool InitFromFile( const char *qpath, bool rebuild = true, bool cache = true ); + virtual const char * HandleEvent( const sysEvent_t *event, int time, bool *updateVisuals ); + virtual void HandleNamedEvent( const char* namedEvent ); + virtual void Redraw( int time ); + virtual void DrawCursor(); + virtual const idDict & State() const; + virtual void DeleteStateVar( const char *varName ); + virtual void SetStateString( const char *varName, const char *value ); + virtual void SetStateBool( const char *varName, const bool value ); + virtual void SetStateInt( const char *varName, const int value ); + virtual void SetStateFloat( const char *varName, const float value ); + virtual void SetStateVector( const char *varName, const idVec3& vector ); + virtual void SetStateVec4( const char *varName, const idVec4& vector ); + virtual void ClearState( void ); + virtual void DeleteState( const char *varName ); + virtual idVec4 GetLightColor( void ); + + // Gets a gui state variable + virtual const char* GetStateString( const char *varName, const char* defaultString = "" ) const; + virtual bool GetStateBool( const char *varName, const char* defaultString = "0" ) const; + virtual int GetStateInt( const char *varName, const char* defaultString = "0" ) const; + virtual float GetStateFloat( const char *varName, const char* defaultString = "0" ) const; + virtual idVec3 GetStateVector( const char *varName, const char* defaultString = "0 0 0" ) const; + virtual idVec4 GetStateVec4( const char *varName, const char* defaultString = "0 0 0 0" ) const; + + virtual void StateChanged( int time, bool redraw ); + virtual const char * Activate( bool activate, int time ); + virtual void Trigger( int time ); + virtual void ReadFromDemoFile( class idDemoFile *f ); + virtual void WriteToDemoFile( class idDemoFile *f ); + virtual bool WriteToSaveGame( idFile *savefile ) const; + virtual bool ReadFromSaveGame( idFile *savefile ); + virtual void SetKeyBindingNames( void ); + virtual bool IsUniqued() const { return uniqued; }; + virtual void SetUniqued( bool b ) { uniqued = b; }; + virtual void SetCursor( float x, float y ); + + virtual float CursorX() { return cursorX; } + virtual float CursorY() { return cursorY; } + virtual bool GetMaxTextIndex( const char *windowName, const char *text, wrapInfo_t& wrapInfo ) const; + + size_t Size(); + + idDict * GetStateDict() { return &state; } + + const char * GetSourceFile( void ) const { return source; } + ID_TIME_T GetTimeStamp( void ) const { return timeStamp; } + + idWindow * GetDesktop() const { return desktop; } + void SetBindHandler( idWindow *win ) { bindHandler = win; } + bool Active() const { return active; } + int GetTime() const { return time; } + void SetTime( int _time ) { time = _time; } + void SetLightColorVar( idWinVec4 *var ) { lightColorVar = var; } + + void ClearRefs() { refs = 0; } + void AddRef() { refs++; } + int GetRefs() { return refs; } + + void RecurseSetKeyBindingNames( idWindow *window ); + idStr &GetPendingCmd() { return pendingCmd; }; + idStr &GetReturnCmd() { return returnCmd; }; + +private: + bool active; + bool loading; + bool interactive; + bool uniqued; + + idDict state; + idWindow * desktop; + idWindow * bindHandler; + + idStr source; + idStr activateStr; + idStr pendingCmd; + idStr returnCmd; + ID_TIME_T timeStamp; + bool initialized; + + float cursorX; + float cursorY; + + int time; + + int refs; + idWinVec4 * lightColorVar; +}; + +class idUserInterfaceManagerLocal : public idUserInterfaceManager { + friend class idUserInterfaceLocal; + +public: + virtual void Init(); + virtual void Shutdown(); + virtual void Touch( const char *name ); + virtual void WritePrecacheCommands( idFile *f ); + virtual void SetSize( float width, float height ); + virtual void BeginLevelLoad(); + virtual void EndLevelLoad(); + virtual void Reload( bool all ); + virtual void ListGuis() const; + virtual bool CheckGui( const char *qpath ) const; + virtual idUserInterface * Alloc( void ) const; + virtual void DeAlloc( idUserInterface *gui ); + virtual idUserInterface * FindGui( const char *qpath, bool autoLoad = false, bool needInteractive = false, bool forceUnique = false ); + virtual idUserInterface * FindDemoGui( const char *qpath ); + virtual idListGUI * AllocListGUI( void ) const; + virtual void FreeListGUI( idListGUI *listgui ); + virtual void RunAlwaysThinkGUIs( int time ); + virtual void RegisterIcon( const char *code, const char *shader, int x = -1, int y = -1, int w = -1, int h = -1 ); + +private: + idRectangle screenRect; + idDeviceContext dc; + + idList guis; + idList demoGuis; + idList alwaysThinkGUIs; + +}; + +#if defined( _WIN32 ) && defined( Q4_RECON_ENGINE_PRIVATE ) +static_assert( sizeof( idUserInterfaceLocal ) == 0xD8, "idUserInterfaceLocal ABI drift" ); +static_assert( sizeof( idUserInterfaceManagerLocal ) == 0x134, "idUserInterfaceManagerLocal ABI drift" ); +#endif diff --git a/src/ui/Window.h b/src/ui/Window.h new file mode 100644 index 0000000..2809e6b --- /dev/null +++ b/src/ui/Window.h @@ -0,0 +1,476 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __WINDOW_H__ +#define __WINDOW_H__ + +#include "Rectangle.h" +#include "DeviceContext.h" +#include "RegExp.h" +#include "Winvar.h" +#include "GuiScript.h" +#include "SimpleWindow.h" + +const int WIN_CHILD = 0x00000001; +const int WIN_CAPTION = 0x00000002; +const int WIN_BORDER = 0x00000004; +const int WIN_SIZABLE = 0x00000008; +const int WIN_MOVABLE = 0x00000010; +const int WIN_FOCUS = 0x00000020; +const int WIN_CAPTURE = 0x00000040; +const int WIN_HCENTER = 0x00000080; +const int WIN_VCENTER = 0x00000100; +const int WIN_MODAL = 0x00000200; +const int WIN_INTRANSITION = 0x00000400; +const int WIN_CANFOCUS = 0x00000800; +const int WIN_SELECTED = 0x00001000; +const int WIN_TRANSFORM = 0x00002000; +const int WIN_HOLDCAPTURE = 0x00004000; +const int WIN_NOWRAP = 0x00008000; +const int WIN_NOCLIP = 0x00010000; +const int WIN_INVERTRECT = 0x00020000; +const int WIN_NATURALMAT = 0x00040000; +const int WIN_NOCURSOR = 0x00080000; +const int WIN_MENUGUI = 0x00100000; +const int WIN_ACTIVE = 0x00200000; +const int WIN_SHOWCOORDS = 0x00400000; +const int WIN_SHOWTIME = 0x00800000; +const int WIN_WANTENTER = 0x01000000; +const int WIN_ALWAYSTHINK = 0x08000000; + +const int WIN_DESKTOP = 0x10000000; + +const char CAPTION_HEIGHT[] = "16.0"; +const char SCROLLER_SIZE[] = "16.0"; +const int SCROLLBAR_SIZE = 16; + +const int MAX_WINDOW_NAME = 32; +const int MAX_LIST_ITEMS = 1024; + +const char DEFAULT_BACKCOLOR[] = "1 1 1 1"; +const char DEFAULT_FORECOLOR[] = "0 0 0 1"; +const char DEFAULT_BORDERCOLOR[] = "0 0 0 1"; +const char DEFAULT_TEXTSCALE[] = "0.4"; + +typedef enum { + WOP_TYPE_ADD, + WOP_TYPE_SUBTRACT, + WOP_TYPE_MULTIPLY, + WOP_TYPE_DIVIDE, + WOP_TYPE_MOD, + WOP_TYPE_TABLE, + WOP_TYPE_GT, + WOP_TYPE_GE, + WOP_TYPE_LT, + WOP_TYPE_LE, + WOP_TYPE_EQ, + WOP_TYPE_NE, + WOP_TYPE_AND, + WOP_TYPE_OR, + WOP_TYPE_VAR, + WOP_TYPE_VARS, + WOP_TYPE_VARF, + WOP_TYPE_VARI, + WOP_TYPE_VARB, + WOP_TYPE_COND, + WOP_TYPE_VARFM +} wexpOpType_t; + +typedef enum { + WEXP_REG_TIME, + WEXP_REG_NUM_PREDEFINED +} wexpRegister_t; + +typedef struct { + wexpOpType_t opType; + int a, b, c, d; +} wexpOp_t; + +struct idRegEntry { + const char *name; + idRegister::REGTYPE type; + int index; +}; + +class rvGEWindowWrapper; +class idWindow; + +struct idTimeLineEvent { + idTimeLineEvent() { + event = new idGuiScriptList; + } + ~idTimeLineEvent() { + delete event; + } + int time; + idGuiScriptList *event; + bool pending; + size_t Size() { + return sizeof(*this) + event->Size(); + } +}; + +class rvNamedEvent +{ +public: + + rvNamedEvent(const char* name) + { + mEvent = new idGuiScriptList; + mName = name; + } + ~rvNamedEvent(void) + { + delete mEvent; + } + size_t Size() + { + return sizeof(*this) + mEvent->Size(); + } + + idStr mName; + idGuiScriptList* mEvent; +}; + +struct idTransitionData { + idWinVar *data; + int offset; + idInterpolateAccelDecelLinear interp; +}; + + +class idUserInterfaceLocal; +class idWindow { + friend class idUserInterfaceManagerLocal; +public: + idWindow(idUserInterfaceLocal *gui); + idWindow(idDeviceContext *d, idUserInterfaceLocal *gui); + virtual ~idWindow(); + + enum { + ON_MOUSEENTER = 0, + ON_MOUSEEXIT, + ON_ACTION, + ON_BACKACTION, + ON_ACTIVATE, + ON_DEACTIVATE, + ON_ESC, + ON_EVENT, + ON_FRAME = ON_EVENT, + ON_TRIGGER, + ON_ACTIONRELEASE, + ON_ENTER, + ON_ENTERRELEASE, + ON_TABRELEASE, + ON_GAINFOCUS, + ON_LOSEFOCUS, + ON_SELCHANGE, + ON_INIT, + ON_JOYSTART, + ON_JOYSELECT, + ON_JOYBACK, + ON_JOYLShoulder, + ON_JOYRShoulder, + ON_JOYUP, + ON_JOYDOWN, + ON_JOYLEFT, + ON_JOYRIGHT, + ON_JOYBUTTON1, + ON_JOYBUTTON2, + SCRIPT_COUNT + }; + + enum { + ADJUST_MOVE = 0, + ADJUST_TOP, + ADJUST_RIGHT, + ADJUST_BOTTOM, + ADJUST_LEFT, + ADJUST_TOPLEFT, + ADJUST_BOTTOMRIGHT, + ADJUST_TOPRIGHT, + ADJUST_BOTTOMLEFT + }; + + static const char *ScriptNames[SCRIPT_COUNT]; + + static const idRegEntry RegisterVars[]; + static const int NumRegisterVars; + + void SetDC(idDeviceContext *d); + + idDeviceContext* GetDC ( void ) { return dc; } + + idWindow *SetFocus(idWindow *w, bool scripts = true); + + idWindow *SetCapture(idWindow *w); + void SetParent(idWindow *w); + void SetFlag(unsigned int f); + void ClearFlag(unsigned int f); + unsigned GetFlags() {return flags;}; + void Move(float x, float y); + void BringToTop(idWindow *w); + void Adjust(float xd, float yd); + void SetAdjustMode(idWindow *child); + void Size(float x, float y, float w, float h); + void SetupFromState(); + void SetupBackground(); + drawWin_t *FindChildByName(const char *name); + bool GetMaxTextIndex( const char *text, wrapInfo_t& wrapInfo ) const; + idSimpleWindow *FindSimpleWinByName(const char *_name); + idWindow *GetParent() { return parent; } + idUserInterfaceLocal *GetGui() {return gui;}; + bool Contains(float x, float y); + size_t Size(); + virtual size_t Allocated(); + idStr* GetStrPtrByName(const char *_name); + + virtual idWinVar *GetWinVarByName (const char *_name, bool winLookup = false, drawWin_t** owner = NULL); + + int GetWinVarOffset( idWinVar *wv, drawWin_t *dw ); + float GetMaxCharHeight(); + float GetMaxCharWidth(); + void SetFont(); + void SetInitialState(const char *_name); + void AddChild(idWindow *win); + void DebugDraw(int time, float x, float y); + void CalcClientRect(float xofs, float yofs); + void CommonInit(); + void CleanUp(); + void DrawBorderAndCaption(const idRectangle &drawRect); + void DrawCaption(int time, float x, float y); + void SetupTransforms(float x, float y); + bool Contains(const idRectangle &sr, float x, float y); + const char *GetName() { return name; }; + + virtual bool Parse(idParser *src, bool rebuild = true); + virtual const char *HandleEvent(const sysEvent_t *event, bool *updateVisuals); + void CalcRects(float x, float y); + virtual void Redraw(float x, float y); + + virtual void ArchiveToDictionary(idDict *dict, bool useNames = true); + virtual void InitFromDictionary(idDict *dict, bool byName = true); + virtual void PostParse(); + virtual void Activate( bool activate, idStr &act ); + virtual void Trigger(); + virtual void GainFocus(); + virtual void LoseFocus(); + virtual void GainCapture(); + virtual void LoseCapture(); + virtual void Sized(); + virtual void Moved(); + virtual void Draw(int time, float x, float y); + virtual void MouseExit(); + virtual void MouseEnter(); + virtual void DrawBackground(const idRectangle &drawRect); + virtual const char *RouteMouseCoords(float xd, float yd); + virtual void SetBuddy(idWindow *buddy) {}; + virtual void HandleBuddyUpdate(idWindow *buddy) {}; + virtual void StateChanged( bool redraw ); + virtual void ReadFromDemoFile( class idDemoFile *f, bool rebuild = true ); + virtual void WriteToDemoFile( class idDemoFile *f ); + + // SaveGame support + void WriteSaveGameString( const char *string, idFile *savefile ); + void WriteSaveGameTransition( idTransitionData &trans, idFile *savefile ); + virtual void WriteToSaveGame( idFile *savefile ); + void ReadSaveGameString( idStr &string, idFile *savefile ); + void ReadSaveGameTransition( idTransitionData & trans, idFile *savefile ); + virtual void ReadFromSaveGame( idFile *savefile ); + void FixupTransitions(); + virtual void HasAction(){}; + virtual void HasScripts(){}; + + void FixupParms(); + void GetScriptString(const char *name, idStr &out); + void SetScriptParams(); + bool HasOps() { return (ops.Num() > 0); }; + float EvalRegs(int test = -1, bool force = false); + void StartTransition(); + void ClearTransitions(); + void AddTransition(idWinVar *dest, idVec4 from, idVec4 to, int time, float accelTime, float decelTime); + void ResetTime(int time); + void ResetCinematics(); + + int NumTransitions(); + + bool ParseScript(idParser *src, idGuiScriptList &list, int *timeParm = NULL, bool allowIf = false); + bool RunScript(int n); + bool RunScriptList(idGuiScriptList *src); + void SetRegs(const char *key, const char *val); + int ParseExpression( idParser *src, idWinVar *var = NULL, int component = 0 ); + int ExpressionConstant(float f); + idRegisterList *RegList() { return ®List; } + void AddCommand(const char *cmd); + void AddUpdateVar(idWinVar *var); + bool Interactive(); + bool ContainsStateVars(); + void SetChildWinVarVal(const char *name, const char *var, const char *val); + idWindow *GetFocusedChild(); + idWindow *GetCaptureChild(); + const char *GetComment() { return comment; } + void SetComment( const char * p) { comment = p; } + + idStr cmd; + + virtual void RunNamedEvent ( const char* eventName ); + + void AddDefinedVar ( idWinVar* var ); + + idWindow* FindChildByPoint ( float x, float y, idWindow* below = NULL ); + int GetChildIndex ( idWindow* window ); + int GetChildCount ( void ); + idWindow* GetChild ( int index ); + void RemoveChild ( idWindow *win ); + bool InsertChild ( idWindow *win, idWindow* before ); + + void ScreenToClient ( idRectangle* rect ); + void ClientToScreen ( idRectangle* rect ); + + bool UpdateFromDictionary ( idDict& dict ); + +protected: + + friend class rvGEWindowWrapper; + + idWindow* FindChildByPoint ( float x, float y, idWindow** below ); + void SetDefaults ( void ); + + friend class idSimpleWindow; + friend class idUserInterfaceLocal; + bool IsSimple(); + void UpdateWinVars(); + void DisableRegister(const char *_name); + void Transition(); + void Time(); + bool RunTimeEvents(int time); + void Dump(); + + int ExpressionTemporary(); + wexpOp_t *ExpressionOp(); + int EmitOp( int a, int b, wexpOpType_t opType, wexpOp_t **opp = NULL ); + int ParseEmitOp( idParser *src, int a, wexpOpType_t opType, int priority, wexpOp_t **opp = NULL ); + int ParseTerm( idParser *src, idWinVar *var = NULL, int component = 0 ); + int ParseExpressionPriority( idParser *src, int priority, idWinVar *var = NULL, int component = 0 ); + void EvaluateRegisters(float *registers); + void SaveExpressionParseState(); + void RestoreExpressionParseState(); + void ParseBracedExpression(idParser *src); + bool ParseScriptEntry(const char *name, idParser *src); + bool ParseRegEntry(const char *name, idParser *src); + virtual bool ParseInternalVar(const char *name, idParser *src); + void ParseString(idParser *src, idStr &out); + void ParseVec4(idParser *src, idVec4 &out); + void ConvertRegEntry(const char *name, idParser *src, idStr &out, int tabs); + + float actualX; // physical coords + float actualY; // '' + int childID; // this childs id + unsigned int flags; // visible, focus, mouseover, cursor, border, etc.. + int lastTimeRun; // + idRectangle drawRect; // overall rect + idRectangle clientRect; // client area + idVec2 origin; + + int timeLine; // time stamp used for various fx + float xOffset; + float yOffset; + float forceAspectWidth; + float forceAspectHeight; + float matScalex; + float matScaley; + float borderSize; + float textAlignx; + float textAligny; + float textSpacing; + int textStyle; + idStr name; + idStr comment; + idVec2 shear; + + signed char textShadow; + unsigned char fontNum; + unsigned char cursor; // + signed char textAlign; + + idWinBool noTime; // + idWinBool visible; // + idWinBool noEvents; + idWinRectangle rect; // overall rect + idWinVec4 backColor; + idWinVec4 matColor; + idWinVec4 foreColor; + idWinVec4 hoverColor; + idWinVec4 borderColor; + idWinFloat textScale; + idWinFloat rotate; + idWinStr text; + idWinBackground backGroundName; // + + idList definedVars; + idList updateVars; + + idRectangle textRect; // text extented rect + const idMaterial *background; // background asset + + idWindow *parent; // parent window + idList children; // child windows + idList drawWindows; + + idWindow *focusedChild; // if a child window has the focus + idWindow *captureChild; // if a child window has mouse capture + idWindow *overChild; // if a child window has mouse capture + bool hover; + + idDeviceContext *dc; + + idUserInterfaceLocal *gui; + + static idCVar gui_debug; + static idCVar gui_edit; + + idGuiScriptList *scripts[SCRIPT_COUNT]; + bool *saveTemps; + + idList timeLineEvents; + idList transitions; + + static bool registerIsTemporary[MAX_EXPRESSION_REGISTERS]; // statics to assist during parsing + + idList ops; // evaluate to make expressionRegisters + idList expressionRegisters; + idList *saveOps; // evaluate to make expressionRegisters + idList namedEvents; // added named events + idList *saveRegs; + + idRegisterList regList; + + idWinBool hideCursor; +}; + +ID_INLINE void idWindow::AddDefinedVar( idWinVar* var ) { + definedVars.AddUnique( var ); +} + +#endif /* !__WINDOW_H__ */ diff --git a/src/ui/Winvar.h b/src/ui/Winvar.h new file mode 100644 index 0000000..5a421d0 --- /dev/null +++ b/src/ui/Winvar.h @@ -0,0 +1,936 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#ifndef __WINVAR_H__ +#define __WINVAR_H__ + +#include "Rectangle.h" + +static const char *VAR_GUIPREFIX = "gui::"; +static const int VAR_GUIPREFIX_LEN = strlen(VAR_GUIPREFIX); + +class idWindow; +class idWinVar { +public: + idWinVar(); + virtual ~idWinVar(); + + void SetGuiInfo(idDict *gd, const char *_name); + const char *GetName() const { + if (name) { + if (guiDict && *name == '*') { + return guiDict->GetString(&name[1]); + } + return name; + } + return ""; + } + void SetName(const char *_name) { + delete []name; + name = NULL; + if (_name) { + name = new char[strlen(_name)+1]; + strcpy(name, _name); + } + } + + idWinVar &operator=( const idWinVar &other ) { + guiDict = other.guiDict; + SetName(other.name); + return *this; + } + + idDict *GetDict() const { return guiDict; } + bool NeedsUpdate() { return (guiDict != NULL); } + + virtual void Init(const char *_name, idWindow* win) = 0; + virtual void Set(const char *val) = 0; + virtual void Update() = 0; + virtual const char *c_str() const = 0; + virtual size_t Size() { size_t sz = (name) ? strlen(name) : 0; return sz + sizeof(*this); } + virtual void SetMember( int index, const float val ) {} + virtual float GetMember( int index ) const { return 0.0f; } + + virtual void WriteToSaveGame( idFile *savefile ) = 0; + virtual void ReadFromSaveGame( idFile *savefile ) = 0; + + virtual float x( void ) const = 0; + + virtual void SetEval(bool b) { + eval = b; + } + bool GetEval() { + return eval; + } + +protected: + idDict *guiDict; + char *name; + bool eval; +}; + +class idWinBool : public idWinVar { +public: + idWinBool() : idWinVar() {}; + ~idWinBool() {}; + virtual void Init(const char *_name, idWindow *win) { idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetBool(GetName()); + } + } + int operator==( const bool &other ) { return (other == data); } + bool &operator=( const bool &other ) { + data = other; + if (guiDict) { + guiDict->SetBool(GetName(), data); + } + return data; + } + idWinBool &operator=( const idWinBool &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + + operator bool() const { return data; } + + virtual void Set(const char *val) { + data = ( atoi( val ) != 0 ); + if (guiDict) { + guiDict->SetBool(GetName(), data); + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetBool( s ); + } + } + + virtual const char *c_str() const {return va("%i", data); } + + // SaveGames + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + + virtual float x( void ) const { return data ? 1.0f : 0.0f; }; + +protected: + bool data; +}; + +class idWinStr : public idWinVar { +public: + idWinStr() : idWinVar() {}; + ~idWinStr() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetString(GetName()); + } + } + int operator==( const idStr &other ) const { + return (other == data); + } + int operator==( const char *other ) const { + return (data == other); + } + idStr &operator=( const idStr &other ) { + data = other; + if (guiDict) { + guiDict->Set(GetName(), data); + } + return data; + } + idWinStr &operator=( const idWinStr &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + operator const char *() const { + return data.c_str(); + } + operator const idStr &() const { + return data; + } + int LengthWithoutColors() { + if (guiDict && name && *name) { + data = guiDict->GetString(GetName()); + } + return data.LengthWithoutEscapes(); + } + int Length() { + if (guiDict && name && *name) { + data = guiDict->GetString(GetName()); + } + return data.Length(); + } + void RemoveColors() { + if (guiDict && name && *name) { + data = guiDict->GetString(GetName()); + } + data.RemoveEscapes(); + } + virtual const char *c_str() const { + return data.c_str(); + } + + virtual void Set(const char *val) { + data = val; + if ( guiDict ) { + guiDict->Set(GetName(), data); + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetString( s ); + } + } + + virtual size_t Size() { + size_t sz = idWinVar::Size(); + return sz +data.Allocated(); + } + + // SaveGames + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + + int len = data.Length(); + savefile->Write( &len, sizeof( len ) ); + if ( len > 0 ) { + savefile->Write( data.c_str(), len ); + } + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + + int len; + savefile->Read( &len, sizeof( len ) ); + if ( len > 0 ) { + data.Fill( ' ', len ); + savefile->Read( &data[0], len ); + } + } + + // return wether string is emtpy + virtual float x( void ) const { return data[0] ? 1.0f : 0.0f; }; + +protected: + idStr data; +}; + +class idWinInt : public idWinVar { +public: + idWinInt() : idWinVar() {}; + ~idWinInt() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetInt(GetName()); + } + } + int &operator=( const int &other ) { + data = other; + if (guiDict) { + guiDict->SetInt(GetName(), data); + } + return data; + } + idWinInt &operator=( const idWinInt &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + operator int () const { + return data; + } + virtual void Set(const char *val) { + data = atoi(val);; + if (guiDict) { + guiDict->SetInt(GetName(), data); + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetInt( s ); + } + } + virtual const char *c_str() const { + return va("%i", data); + } + + // SaveGames + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + + // no suitable conversion + virtual float x( void ) const { assert( false ); return 0.0f; }; + +protected: + int data; +}; + +class idWinFloat : public idWinVar { +public: + idWinFloat() : idWinVar() {}; + ~idWinFloat() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetFloat(GetName()); + } + } + idWinFloat &operator=( const idWinFloat &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + float &operator=( const float &other ) { + data = other; + if (guiDict) { + guiDict->SetFloat(GetName(), data); + } + return data; + } + operator float() const { + return data; + } + virtual void Set(const char *val) { + data = atof(val); + if (guiDict) { + guiDict->SetFloat(GetName(), data); + } + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetFloat( s ); + } + } + virtual const char *c_str() const { + return va("%f", data); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + + virtual float x( void ) const { return data; }; +protected: + float data; +}; + +class idWinRectangle : public idWinVar { +public: + idWinRectangle() : idWinVar() {}; + ~idWinRectangle() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + idVec4 v = guiDict->GetVec4(GetName()); + data.x = v.x; + data.y = v.y; + data.w = v.z; + data.h = v.w; + } + } + + int operator==( const idRectangle &other ) const { + return (other == data); + } + + idWinRectangle &operator=( const idWinRectangle &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + idRectangle &operator=( const idVec4 &other ) { + data = other; + if (guiDict) { + guiDict->SetVec4(GetName(), other); + } + return data; + } + + idRectangle &operator=( const idRectangle &other ) { + data = other; + if (guiDict) { + idVec4 v = data.ToVec4(); + guiDict->SetVec4(GetName(), v); + } + return data; + } + + operator const idRectangle&() const { + return data; + } + + float x() const { + return data.x; + } + float y() const { + return data.y; + } + float w() const { + return data.w; + } + float h() const { + return data.h; + } + float Right() const { + return data.Right(); + } + float Bottom() const { + return data.Bottom(); + } + idVec4 &ToVec4() { + static idVec4 ret; + ret = data.ToVec4(); + return ret; + } + virtual void Set(const char *val) { + if ( strchr ( val, ',' ) ) { + sscanf( val, "%f,%f,%f,%f", &data.x, &data.y, &data.w, &data.h ); + } else { + sscanf( val, "%f %f %f %f", &data.x, &data.y, &data.w, &data.h ); + } + if (guiDict) { + idVec4 v = data.ToVec4(); + guiDict->SetVec4(GetName(), v); + } + } + virtual void SetMember( int index, const float val ) { + if ( index < 0 || index > 3 ) { + return; + } + idVec4 value = data.ToVec4(); + value[index] = val; + *this = value; + } + virtual float GetMember( int index ) const { + if ( index < 0 || index > 3 ) { + return 0.0f; + } + const idVec4 value = data.ToVec4(); + return value[index]; + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + idVec4 v = guiDict->GetVec4( s ); + data.x = v.x; + data.y = v.y; + data.w = v.z; + data.h = v.w; + } + } + + virtual const char *c_str() const { + return data.ToVec4().ToString(); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idRectangle data; +}; + +class idWinVec2 : public idWinVar { +public: + idWinVec2() : idWinVar() {}; + ~idWinVec2() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetVec2(GetName()); + } + } + int operator==( const idVec2 &other ) const { + return (other == data); + } + idWinVec2 &operator=( const idWinVec2 &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + + idVec2 &operator=( const idVec2 &other ) { + data = other; + if (guiDict) { + guiDict->SetVec2(GetName(), data); + } + return data; + } + float x() const { + return data.x; + } + float y() const { + return data.y; + } + virtual void Set(const char *val) { + if ( strchr ( val, ',' ) ) { + sscanf( val, "%f,%f", &data.x, &data.y ); + } else { + sscanf( val, "%f %f", &data.x, &data.y); + } + if (guiDict) { + guiDict->SetVec2(GetName(), data); + } + } + operator const idVec2&() const { + return data; + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetVec2( s ); + } + } + virtual const char *c_str() const { + return data.ToString(); + } + void Zero() { + data.Zero(); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idVec2 data; +}; + +class idWinVec4 : public idWinVar { +public: + idWinVec4() : idWinVar() {}; + ~idWinVec4() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetVec4(GetName()); + } + } + int operator==( const idVec4 &other ) const { + return (other == data); + } + idWinVec4 &operator=( const idWinVec4 &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + idVec4 &operator=( const idVec4 &other ) { + data = other; + if (guiDict) { + guiDict->SetVec4(GetName(), data); + } + return data; + } + operator const idVec4&() const { + return data; + } + + float x() const { + return data.x; + } + + float y() const { + return data.y; + } + + float z() const { + return data.z; + } + + float w() const { + return data.w; + } + virtual void Set(const char *val) { + if ( strchr ( val, ',' ) ) { + sscanf( val, "%f,%f,%f,%f", &data.x, &data.y, &data.z, &data.w ); + } else { + sscanf( val, "%f %f %f %f", &data.x, &data.y, &data.z, &data.w); + } + if ( guiDict ) { + guiDict->SetVec4( GetName(), data ); + } + } + virtual void SetMember( int index, const float val ) { + if ( index < 0 || index > 3 ) { + return; + } + data[index] = val; + if ( guiDict ) { + guiDict->SetVec4( GetName(), data ); + } + } + virtual float GetMember( int index ) const { + return ( index >= 0 && index < 4 ) ? data[index] : 0.0f; + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetVec4( s ); + } + } + virtual const char *c_str() const { + return data.ToString(); + } + + void Zero() { + data.Zero(); + if ( guiDict ) { + guiDict->SetVec4(GetName(), data); + } + } + + const idVec3 &ToVec3() const { + return data.ToVec3(); + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idVec4 data; +}; + +// Quake 4 exposes vector and rectangle components to GUI scripts through +// synthetic variables such as matColor_w and rect_x. +class idWinFloatMember : public idWinVar { +public: + idWinFloatMember( idWinVar *parent = NULL, int member = 0 ) : + idWinVar(), parentVar( parent ), memberIndex( member ) {} + virtual ~idWinFloatMember() {} + + virtual void Init( const char *_name, idWindow *win ) { + idWinVar::Init( _name, win ); + } + virtual void Set( const char *val ) { + Set( static_cast( atof( val ) ) ); + } + void Set( const float val ) { + if ( parentVar ) { + parentVar->SetMember( memberIndex, val ); + } + } + virtual void Update() { + if ( parentVar ) { + parentVar->Update(); + } + } + virtual const char *c_str() const { + return va( "%f", x() ); + } + virtual void SetEval( bool b ) { + idWinVar::SetEval( b ); + if ( parentVar ) { + parentVar->SetEval( b ); + } + } + virtual void WriteToSaveGame( idFile *savefile ) { + const float value = x(); + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &value, sizeof( value ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + float value; + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &value, sizeof( value ) ); + Set( value ); + } + virtual float x() const { + return parentVar ? parentVar->GetMember( memberIndex ) : 0.0f; + } + + idWinVar *GetParentVar() const { return parentVar; } + int GetMemberIndex() const { return memberIndex; } + +private: + idWinVar *parentVar; + int memberIndex; +}; + +class idWinVec3 : public idWinVar { +public: + idWinVec3() : idWinVar() {}; + ~idWinVec3() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinVar::Init(_name, win); + if (guiDict) { + data = guiDict->GetVector(GetName()); + } + } + int operator==( const idVec3 &other ) const { + return (other == data); + } + idWinVec3 &operator=( const idWinVec3 &other ) { + idWinVar::operator=(other); + data = other.data; + return *this; + } + idVec3 &operator=( const idVec3 &other ) { + data = other; + if (guiDict) { + guiDict->SetVector(GetName(), data); + } + return data; + } + operator const idVec3&() const { + return data; + } + + float x() const { + return data.x; + } + + float y() const { + return data.y; + } + + float z() const { + return data.z; + } + + virtual void Set(const char *val) { + sscanf( val, "%f %f %f", &data.x, &data.y, &data.z); + if (guiDict) { + guiDict->SetVector(GetName(), data); + } + } + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetVector( s ); + } + } + virtual const char *c_str() const { + return data.ToString(); + } + + void Zero() { + data.Zero(); + if (guiDict) { + guiDict->SetVector(GetName(), data); + } + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + savefile->Write( &data, sizeof( data ) ); + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + savefile->Read( &data, sizeof( data ) ); + } + +protected: + idVec3 data; +}; + +class idWinBackground : public idWinStr { +public: + idWinBackground() : idWinStr() { + mat = NULL; + }; + ~idWinBackground() {}; + virtual void Init(const char *_name, idWindow *win) { + idWinStr::Init(_name, win); + if (guiDict) { + data = guiDict->GetString(GetName()); + } + } + int operator==( const idStr &other ) const { + return (other == data); + } + int operator==( const char *other ) const { + return (data == other); + } + idStr &operator=( const idStr &other ) { + data = other; + if (guiDict) { + guiDict->Set(GetName(), data); + } + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + return data; + } + idWinBackground &operator=( const idWinBackground &other ) { + idWinVar::operator=(other); + data = other.data; + mat = other.mat; + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + return *this; + } + operator const char *() const { + return data.c_str(); + } + operator const idStr &() const { + return data; + } + int Length() { + if (guiDict) { + data = guiDict->GetString(GetName()); + } + return data.Length(); + } + virtual const char *c_str() const { + return data.c_str(); + } + + virtual void Set(const char *val) { + data = val; + if (guiDict) { + guiDict->Set(GetName(), data); + } + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + } + + virtual void Update() { + const char *s = GetName(); + if ( guiDict && s[0] != '\0' ) { + data = guiDict->GetString( s ); + if (mat) { + if ( data == "" ) { + (*mat) = NULL; + } else { + (*mat) = declManager->FindMaterial(data); + } + } + } + } + + virtual size_t Size() { + size_t sz = idWinVar::Size(); + return sz +data.Allocated(); + } + + void SetMaterialPtr( const idMaterial **m ) { + mat = m; + } + + virtual void WriteToSaveGame( idFile *savefile ) { + savefile->Write( &eval, sizeof( eval ) ); + + int len = data.Length(); + savefile->Write( &len, sizeof( len ) ); + if ( len > 0 ) { + savefile->Write( data.c_str(), len ); + } + } + virtual void ReadFromSaveGame( idFile *savefile ) { + savefile->Read( &eval, sizeof( eval ) ); + + int len; + savefile->Read( &len, sizeof( len ) ); + if ( len > 0 ) { + data.Fill( ' ', len ); + savefile->Read( &data[0], len ); + } + if ( mat ) { + if ( len > 0 ) { + (*mat) = declManager->FindMaterial( data ); + } else { + (*mat) = NULL; + } + } + } + +protected: + idStr data; + const idMaterial **mat; +}; + +/* +================ +idMultiWinVar +multiplexes access to a list if idWinVar* +================ +*/ +class idMultiWinVar : public idList< idWinVar * > { +public: + void Set( const char *val ); + void Update( void ); + void SetGuiInfo( idDict *dict ); +}; + +#endif /* !__WINVAR_H__ */ diff --git a/src/ui/bindwindow.cpp b/src/ui/bindwindow.cpp new file mode 100644 index 0000000..d8bf9a4 --- /dev/null +++ b/src/ui/bindwindow.cpp @@ -0,0 +1,124 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "BindWindow.h" + + +void idBindWindow::CommonInit() { + bindName = ""; + waitingOnKey = false; +} + +idBindWindow::idBindWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idBindWindow::idBindWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idBindWindow::~idBindWindow() { + +} + + +const char *idBindWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + static char ret[ 256 ]; + + if (!(event->evType == SE_KEY && event->evValue2)) { + return ""; + } + + int key = event->evValue; + + if (waitingOnKey) { + waitingOnKey = false; + if (key == K_ESCAPE) { + idStr::snPrintf( ret, sizeof( ret ), "clearbind \"%s\"", bindName.GetName()); + } else { + idStr::snPrintf( ret, sizeof( ret ), "bind %i \"%s\"", key, bindName.GetName()); + } + return ret; + } else { + if (key == K_MOUSE1) { + waitingOnKey = true; + gui->SetBindHandler(this); + return ""; + } + } + + return ""; +} + +idWinVar *idBindWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + + if (idStr::Icmp(_name, "bind") == 0) { + return &bindName; + } + + return idWindow::GetWinVarByName(_name, fixup,owner); +} + +void idBindWindow::PostParse() { + idWindow::PostParse(); + bindName.SetGuiInfo( gui->GetStateDict(), bindName ); + bindName.Update(); + //bindName = state.GetString("bind"); + flags |= (WIN_HOLDCAPTURE | WIN_CANFOCUS); +} + +void idBindWindow::Draw(int time, float x, float y) { + idVec4 color = foreColor; + + idStr str; + if ( waitingOnKey ) { + str = common->GetLocalizedString( "#str_07000" ); + } else if ( bindName.Length() ) { + str = bindName.c_str(); + } else { + str = common->GetLocalizedString( "#str_07001" ); + } + + if ( waitingOnKey || ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) ) { + color = hoverColor; + } else { + hover = false; + } + + dc->DrawText(str, textScale, textAlign, color, textRect, false, -1); +} + +void idBindWindow::Activate( bool activate, idStr &act ) { + idWindow::Activate( activate, act ); + bindName.Update(); +} diff --git a/src/ui/choicewindow.cpp b/src/ui/choicewindow.cpp new file mode 100644 index 0000000..9ef1222 --- /dev/null +++ b/src/ui/choicewindow.cpp @@ -0,0 +1,393 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "ChoiceWindow.h" + +/* +============ +idChoiceWindow::InitVars +============ +*/ +void idChoiceWindow::InitVars( ) { + if ( cvarStr.Length() ) { + cvar = cvarSystem->Find( cvarStr ); + if ( !cvar ) { + common->Warning( "idChoiceWindow::InitVars: gui '%s' window '%s' references undefined cvar '%s'", gui->GetSourceFile(), name.c_str(), cvarStr.c_str() ); + return; + } + updateStr.Append( &cvarStr ); + } + if ( guiStr.Length() ) { + updateStr.Append( &guiStr ); + } + updateStr.SetGuiInfo( gui->GetStateDict() ); + updateStr.Update(); +} + +/* +============ +idChoiceWindow::CommonInit +============ +*/ +void idChoiceWindow::CommonInit() { + currentChoice = 0; + choiceType = 0; + cvar = NULL; + liveUpdate = true; + choices.Clear(); +} + +idChoiceWindow::idChoiceWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idChoiceWindow::idChoiceWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idChoiceWindow::~idChoiceWindow() { + +} + +void idChoiceWindow::RunNamedEvent( const char* eventName ) { + idStr event, group; + + if ( !idStr::Cmpn( eventName, "cvar read ", 10 ) ) { + event = eventName; + group = event.Mid( 10, event.Length() - 10 ); + if ( !group.Cmp( updateGroup ) ) { + UpdateVars( true, true ); + } + } else if ( !idStr::Cmpn( eventName, "cvar write ", 11 ) ) { + event = eventName; + group = event.Mid( 11, event.Length() - 11 ); + if ( !group.Cmp( updateGroup ) ) { + UpdateVars( false, true ); + } + } +} + +void idChoiceWindow::UpdateVars( bool read, bool force ) { + if ( force || liveUpdate ) { + if ( cvar && cvarStr.NeedsUpdate() ) { + if ( read ) { + cvarStr.Set( cvar->GetString() ); + } else { + cvar->SetString( cvarStr.c_str() ); + } + } + if ( !read && guiStr.NeedsUpdate() ) { + guiStr.Set( va( "%i", currentChoice ) ); + } + } +} + +const char *idChoiceWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + int key; + bool runAction = false; + bool runAction2 = false; + + if ( event->evType == SE_KEY ) { + key = event->evValue; + + if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW || key == K_MOUSE1) { + // never affects the state, but we want to execute script handlers anyway + if ( !event->evValue2 ) { + RunScript( ON_ACTIONRELEASE ); + return cmd; + } + currentChoice++; + if (currentChoice >= choices.Num()) { + currentChoice = 0; + } + runAction = true; + } + + if ( key == K_LEFTARROW || key == K_KP_LEFTARROW || key == K_MOUSE2) { + // never affects the state, but we want to execute script handlers anyway + if ( !event->evValue2 ) { + RunScript( ON_ACTIONRELEASE ); + return cmd; + } + currentChoice--; + if (currentChoice < 0) { + currentChoice = choices.Num() - 1; + } + runAction = true; + } + + if ( !event->evValue2 ) { + // is a key release with no action catch + return ""; + } + + } else if ( event->evType == SE_CHAR ) { + + key = event->evValue; + + int potentialChoice = -1; + for ( int i = 0; i < choices.Num(); i++ ) { + if ( toupper(key) == toupper(choices[i][0]) ) { + if ( i < currentChoice && potentialChoice < 0 ) { + potentialChoice = i; + } else if ( i > currentChoice ) { + potentialChoice = -1; + currentChoice = i; + break; + } + } + } + if ( potentialChoice >= 0 ) { + currentChoice = potentialChoice; + } + + runAction = true; + runAction2 = true; + + } else { + return ""; + } + + if ( runAction ) { + RunScript( ON_ACTION ); + } + + if ( choiceType == 0 ) { + cvarStr.Set( va( "%i", currentChoice ) ); + } else if ( values.Num() ) { + cvarStr.Set( values[ currentChoice ] ); + } else { + cvarStr.Set( choices[ currentChoice ] ); + } + + UpdateVars( false ); + + if ( runAction2 ) { + RunScript( ON_ACTIONRELEASE ); + } + + return cmd; +} + +void idChoiceWindow::ValidateChoice() { + if ( currentChoice < 0 || currentChoice >= choices.Num() ) { + currentChoice = 0; + } + if ( choices.Num() == 0 ) { + choices.Append( common->GetLocalizedString( "#str_107731" ) ); + } +} + +void idChoiceWindow::UpdateChoice() { + if ( !updateStr.Num() ) { + return; + } + UpdateVars( true ); + updateStr.Update(); + if ( choiceType == 0 ) { + // ChoiceType 0 stores current as an integer in either cvar or gui + // If both cvar and gui are defined then cvar wins, but they are both updated + if ( updateStr[ 0 ]->NeedsUpdate() ) { + currentChoice = atoi( updateStr[ 0 ]->c_str() ); + } + ValidateChoice(); + } else { + // ChoiceType 1 stores current as a cvar string + int c = ( values.Num() ) ? values.Num() : choices.Num(); + int i; + for ( i = 0; i < c; i++ ) { + if ( idStr::Icmp( cvarStr.c_str(), ( values.Num() ) ? values[i] : choices[i] ) == 0 ) { + break; + } + } + if (i == c) { + i = 0; + } + currentChoice = i; + ValidateChoice(); + } +} + +bool idChoiceWindow::ParseInternalVar(const char *_name, idParser *src) { + if (idStr::Icmp(_name, "choicetype") == 0) { + choiceType = src->ParseInt(); + return true; + } + if (idStr::Icmp(_name, "currentchoice") == 0) { + currentChoice = src->ParseInt(); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} + + +idWinVar *idChoiceWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + if ( idStr::Icmp( _name, "choices" ) == 0 ) { + return &choicesStr; + } + if ( idStr::Icmp( _name, "values" ) == 0 ) { + return &choiceVals; + } + if ( idStr::Icmp( _name, "cvar" ) == 0 ) { + return &cvarStr; + } + if ( idStr::Icmp( _name, "gui" ) == 0 ) { + return &guiStr; + } + if ( idStr::Icmp( _name, "liveUpdate" ) == 0 ) { + return &liveUpdate; + } + if ( idStr::Icmp( _name, "updateGroup" ) == 0 ) { + return &updateGroup; + } + + return idWindow::GetWinVarByName(_name, fixup, owner); +} + +// update the lists whenever the WinVar have changed +void idChoiceWindow::UpdateChoicesAndVals( void ) { + idToken token; + idStr str2, str3; + idLexer src; + + if ( latchedChoices.Icmp( choicesStr ) ) { + choices.Clear(); + src.FreeSource(); + src.SetFlags( LEXFL_NOFATALERRORS | LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( choicesStr, choicesStr.Length(), "" ); + if ( src.IsLoaded() ) { + while( src.ReadToken( &token ) ) { + if ( token == ";" ) { + if ( str2.Length() ) { + str2.StripTrailingWhitespace(); + str2 = common->GetLocalizedString( str2 ); + choices.Append(str2); + str2 = ""; + } + continue; + } + str2 += token; + str2 += " "; + } + if ( str2.Length() ) { + str2.StripTrailingWhitespace(); + choices.Append( str2 ); + } + } + latchedChoices = choicesStr.c_str(); + } + if ( choiceVals.Length() && latchedVals.Icmp( choiceVals ) ) { + values.Clear(); + src.FreeSource(); + src.SetFlags( LEXFL_ALLOWPATHNAMES | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + src.LoadMemory( choiceVals, choiceVals.Length(), "" ); + str2 = ""; + bool negNum = false; + if ( src.IsLoaded() ) { + while( src.ReadToken( &token ) ) { + if (token == "-") { + negNum = true; + continue; + } + if (token == ";") { + if (str2.Length()) { + str2.StripTrailingWhitespace(); + values.Append( str2 ); + str2 = ""; + } + continue; + } + if ( negNum ) { + str2 += "-"; + negNum = false; + } + str2 += token; + str2 += " "; + } + if ( str2.Length() ) { + str2.StripTrailingWhitespace(); + values.Append( str2 ); + } + } + if ( choices.Num() != values.Num() ) { + common->Warning( "idChoiceWindow:: gui '%s' window '%s' has value count unequal to choices count", gui->GetSourceFile(), name.c_str()); + } + latchedVals = choiceVals.c_str(); + } +} + +void idChoiceWindow::PostParse() { + idWindow::PostParse(); + UpdateChoicesAndVals(); + + InitVars(); + UpdateChoice(); + UpdateVars(false); + + flags |= WIN_CANFOCUS; +} + +void idChoiceWindow::Draw(int time, float x, float y) { + idVec4 color = foreColor; + + UpdateChoicesAndVals(); + UpdateChoice(); + + // FIXME: It'd be really cool if textAlign worked, but a lot of the guis have it set wrong because it used to not work + textAlign = 0; + + // The retail Q4 implementation leaves an empty choice window undrawn. A + // dynamic GUI expression can temporarily clear choices after PostParse. + if ( choices.Num() == 0 ) { + return; + } + + if ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + hover = false; + } + if ( flags & WIN_FOCUS ) { + color = hoverColor; + } + + dc->DrawText( choices[currentChoice], textScale, textAlign, color, textRect, false, -1 ); +} + +void idChoiceWindow::Activate( bool activate, idStr &act ) { + idWindow::Activate( activate, act ); + if ( activate ) { + // sets the gui state based on the current choice the window contains + UpdateChoice(); + } +} diff --git a/src/ui/devicecontext.cpp b/src/ui/devicecontext.cpp new file mode 100644 index 0000000..7cee8f0 --- /dev/null +++ b/src/ui/devicecontext.cpp @@ -0,0 +1,1195 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" + +idVec4 idDeviceContext::colorPurple; +idVec4 idDeviceContext::colorOrange; +idVec4 idDeviceContext::colorYellow; +idVec4 idDeviceContext::colorGreen; +idVec4 idDeviceContext::colorBlue; +idVec4 idDeviceContext::colorRed; +idVec4 idDeviceContext::colorBlack; +idVec4 idDeviceContext::colorWhite; +idVec4 idDeviceContext::colorNone; + + +idCVar gui_smallFontLimit( "gui_smallFontLimit", "0.30", CVAR_GUI | CVAR_ARCHIVE, "" ); +idCVar gui_mediumFontLimit( "gui_mediumFontLimit", "0.60", CVAR_GUI | CVAR_ARCHIVE, "" ); + + +idList idDeviceContext::fonts; + +int idDeviceContext::FindFont( const char *name ) { + int c = fonts.Num(); + for (int i = 0; i < c; i++) { + if (idStr::Icmp(name, fonts[i].name) == 0) { + return i; + } + } + + // If the font was not found, try to register it + idStr fileName = name; + fileName.Replace("fonts", va("fonts/%s", fontLang.c_str()) ); + + fontInfoEx_t fontInfo; + int index = fonts.Append( fontInfo ); + if ( renderSystem->RegisterFont( fileName, fonts[index] ) ){ + idStr::Copynz( fonts[index].name, name, sizeof( fonts[index].name ) ); + return index; + } else { + common->Printf( "Could not register font %s [%s]\n", name, fileName.c_str() ); + return -1; +} +} + +void idDeviceContext::SetupFonts() { + fonts.SetGranularity( 1 ); + + fontLang = cvarSystem->GetCVarString( "sys_lang" ); + + // western european languages can use the english font + if ( fontLang == "french" || fontLang == "german" || fontLang == "spanish" || fontLang == "italian" ) { + fontLang = "english"; + } + + // Default font has to be added first. Quake 4's default face is the + // language-specific chain font (fonts//chain_*.fontdat). + FindFont( "fonts/chain" ); +} + +void idDeviceContext::SetFont( int num ) { + if ( num >= 0 && num < fonts.Num() ) { + activeFont = &fonts[num]; + } else { + activeFont = &fonts[0]; + } +} + + +void idDeviceContext::Init() { + xScale = 0.0; + SetSize(VIRTUAL_WIDTH, VIRTUAL_HEIGHT); + whiteImage = declManager->FindMaterial("gfx/guis/white.tga"); + whiteImage->SetSort( SS_GUI ); + mbcs = false; + SetupFonts(); + activeFont = &fonts[0]; + colorPurple = idVec4(1, 0, 1, 1); + colorOrange = idVec4(1, 1, 0, 1); + colorYellow = idVec4(0, 1, 1, 1); + colorGreen = idVec4(0, 1, 0, 1); + colorBlue = idVec4(0, 0, 1, 1); + colorRed = idVec4(1, 0, 0, 1); + colorWhite = idVec4(1, 1, 1, 1); + colorBlack = idVec4(0, 0, 0, 1); + colorNone = idVec4(0, 0, 0, 0); + cursorImages[CURSOR_ARROW] = declManager->FindMaterial("gfx/guis/guicursor_arrow.tga"); + cursorImages[CURSOR_HAND] = declManager->FindMaterial("gfx/guis/guicursor_hand.tga"); + scrollBarImages[SCROLLBAR_HBACK] = declManager->FindMaterial("gfx/guis/scrollbarh.tga"); + scrollBarImages[SCROLLBAR_VBACK] = declManager->FindMaterial("gfx/guis/scrollbarv.tga"); + scrollBarImages[SCROLLBAR_THUMB] = declManager->FindMaterial("gfx/guis/scrollbar_thumb.tga"); + scrollBarImages[SCROLLBAR_RIGHT] = declManager->FindMaterial("gfx/guis/scrollbar_right.tga"); + scrollBarImages[SCROLLBAR_LEFT] = declManager->FindMaterial("gfx/guis/scrollbar_left.tga"); + scrollBarImages[SCROLLBAR_UP] = declManager->FindMaterial("gfx/guis/scrollbar_up.tga"); + scrollBarImages[SCROLLBAR_DOWN] = declManager->FindMaterial("gfx/guis/scrollbar_down.tga"); + cursorImages[CURSOR_ARROW]->SetSort( SS_GUI ); + cursorImages[CURSOR_HAND]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_HBACK]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_VBACK]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_THUMB]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_RIGHT]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_LEFT]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_UP]->SetSort( SS_GUI ); + scrollBarImages[SCROLLBAR_DOWN]->SetSort( SS_GUI ); + cursor = CURSOR_ARROW; + enableClipping = true; + overStrikeMode = true; + mat.Identity(); + origin.Zero(); + initialized = true; +} + +void idDeviceContext::Shutdown() { + fontName.Clear(); + clipRects.Clear(); + fonts.Clear(); + Clear(); +} + +void idDeviceContext::Clear() { + initialized = false; + useFont = NULL; + activeFont = NULL; + mbcs = false; +} + +idDeviceContext::idDeviceContext() { + Clear(); +} + +void idDeviceContext::SetTransformInfo(const idVec3 &org, const idMat3 &m) { + origin = org; + mat = m; +} + +// +// added method +void idDeviceContext::GetTransformInfo(idVec3& org, idMat3& m ) +{ + m = mat; + org = origin; +} +// + +void idDeviceContext::PopClipRect() { + if (clipRects.Num()) { + clipRects.RemoveIndex(clipRects.Num()-1); + } +} + +void idDeviceContext::PushClipRect(idRectangle r) { + clipRects.Append(r); +} + +void idDeviceContext::PushClipRect(float x, float y, float w, float h) { + clipRects.Append(idRectangle(x, y, w, h)); +} + +bool idDeviceContext::ClippedCoords(float *x, float *y, float *w, float *h, float *s1, float *t1, float *s2, float *t2) { + + if ( enableClipping == false || clipRects.Num() == 0 ) { + return false; + } + + int c = clipRects.Num(); + while( --c > 0 ) { + idRectangle *clipRect = &clipRects[c]; + + float ox = *x; + float oy = *y; + float ow = *w; + float oh = *h; + + if ( ow <= 0.0f || oh <= 0.0f ) { + break; + } + + if (*x < clipRect->x) { + *w -= clipRect->x - *x; + *x = clipRect->x; + } else if (*x > clipRect->x + clipRect->w) { + *x = *w = *y = *h = 0; + } + if (*y < clipRect->y) { + *h -= clipRect->y - *y; + *y = clipRect->y; + } else if (*y > clipRect->y + clipRect->h) { + *x = *w = *y = *h = 0; + } + if (*w > clipRect->w) { + *w = clipRect->w - *x + clipRect->x; + } else if (*x + *w > clipRect->x + clipRect->w) { + *w = clipRect->Right() - *x; + } + if (*h > clipRect->h) { + *h = clipRect->h - *y + clipRect->y; + } else if (*y + *h > clipRect->y + clipRect->h) { + *h = clipRect->Bottom() - *y; + } + + if ( s1 && s2 && t1 && t2 && ow > 0.0f ) { + float ns1, ns2, nt1, nt2; + // upper left + float u = ( *x - ox ) / ow; + ns1 = *s1 * ( 1.0f - u ) + *s2 * ( u ); + + // upper right + u = ( *x + *w - ox ) / ow; + ns2 = *s1 * ( 1.0f - u ) + *s2 * ( u ); + + // lower left + u = ( *y - oy ) / oh; + nt1 = *t1 * ( 1.0f - u ) + *t2 * ( u ); + + // lower right + u = ( *y + *h - oy ) / oh; + nt2 = *t1 * ( 1.0f - u ) + *t2 * ( u ); + + // set values + *s1 = ns1; + *s2 = ns2; + *t1 = nt1; + *t2 = nt2; + } + } + + return (*w == 0 || *h == 0) ? true : false; +} + + +void idDeviceContext::AdjustCoords(float *x, float *y, float *w, float *h) { + if (x) { + *x *= xScale; + } + if (y) { + *y *= yScale; + } + if (w) { + *w *= xScale; + } + if (h) { + *h *= yScale; + } +} + +void idDeviceContext::DrawStretchPic(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader) { + idDrawVert verts[4]; + glIndex_t indexes[6]; + indexes[0] = 3; + indexes[1] = 0; + indexes[2] = 2; + indexes[3] = 2; + indexes[4] = 0; + indexes[5] = 1; + verts[0].xyz[0] = x; + verts[0].xyz[1] = y; + verts[0].xyz[2] = 0; + verts[0].st[0] = s1; + verts[0].st[1] = t1; + verts[0].normal[0] = 0; + verts[0].normal[1] = 0; + verts[0].normal[2] = 1; + verts[0].tangents[0][0] = 1; + verts[0].tangents[0][1] = 0; + verts[0].tangents[0][2] = 0; + verts[0].tangents[1][0] = 0; + verts[0].tangents[1][1] = 1; + verts[0].tangents[1][2] = 0; + verts[1].xyz[0] = x + w; + verts[1].xyz[1] = y; + verts[1].xyz[2] = 0; + verts[1].st[0] = s2; + verts[1].st[1] = t1; + verts[1].normal[0] = 0; + verts[1].normal[1] = 0; + verts[1].normal[2] = 1; + verts[1].tangents[0][0] = 1; + verts[1].tangents[0][1] = 0; + verts[1].tangents[0][2] = 0; + verts[1].tangents[1][0] = 0; + verts[1].tangents[1][1] = 1; + verts[1].tangents[1][2] = 0; + verts[2].xyz[0] = x + w; + verts[2].xyz[1] = y + h; + verts[2].xyz[2] = 0; + verts[2].st[0] = s2; + verts[2].st[1] = t2; + verts[2].normal[0] = 0; + verts[2].normal[1] = 0; + verts[2].normal[2] = 1; + verts[2].tangents[0][0] = 1; + verts[2].tangents[0][1] = 0; + verts[2].tangents[0][2] = 0; + verts[2].tangents[1][0] = 0; + verts[2].tangents[1][1] = 1; + verts[2].tangents[1][2] = 0; + verts[3].xyz[0] = x; + verts[3].xyz[1] = y + h; + verts[3].xyz[2] = 0; + verts[3].st[0] = s1; + verts[3].st[1] = t2; + verts[3].normal[0] = 0; + verts[3].normal[1] = 0; + verts[3].normal[2] = 1; + verts[3].tangents[0][0] = 1; + verts[3].tangents[0][1] = 0; + verts[3].tangents[0][2] = 0; + verts[3].tangents[1][0] = 0; + verts[3].tangents[1][1] = 1; + verts[3].tangents[1][2] = 0; + + bool ident = !mat.IsIdentity(); + if ( ident ) { + verts[0].xyz -= origin; + verts[0].xyz *= mat; + verts[0].xyz += origin; + verts[1].xyz -= origin; + verts[1].xyz *= mat; + verts[1].xyz += origin; + verts[2].xyz -= origin; + verts[2].xyz *= mat; + verts[2].xyz += origin; + verts[3].xyz -= origin; + verts[3].xyz *= mat; + verts[3].xyz += origin; + } + + renderSystem->DrawStretchPic( &verts[0], &indexes[0], 4, 6, shader, ident ); + +} + + +void idDeviceContext::DrawMaterial(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex, float scaley) { + + renderSystem->SetColor(color); + + float s0, s1, t0, t1; +// +// handle negative scales as well + if ( scalex < 0 ) + { + w *= -1; + scalex *= -1; + } + if ( scaley < 0 ) + { + h *= -1; + scaley *= -1; + } +// + if( w < 0 ) { // flip about vertical + w = -w; + s0 = 1 * scalex; + s1 = 0; + } + else { + s0 = 0; + s1 = 1 * scalex; + } + + if( h < 0 ) { // flip about horizontal + h = -h; + t0 = 1 * scaley; + t1 = 0; + } + else { + t0 = 0; + t1 = 1 * scaley; + } + + if ( ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) ) { + return; + } + + AdjustCoords(&x, &y, &w, &h); + + DrawStretchPic( x, y, w, h, s0, t0, s1, t1, mat); +} + +void idDeviceContext::DrawMaterialRotated(float x, float y, float w, float h, const idMaterial *mat, const idVec4 &color, float scalex, float scaley, float angle) { + + renderSystem->SetColor(color); + + float s0, s1, t0, t1; + // + // handle negative scales as well + if ( scalex < 0 ) + { + w *= -1; + scalex *= -1; + } + if ( scaley < 0 ) + { + h *= -1; + scaley *= -1; + } + // + if( w < 0 ) { // flip about vertical + w = -w; + s0 = 1 * scalex; + s1 = 0; + } + else { + s0 = 0; + s1 = 1 * scalex; + } + + if( h < 0 ) { // flip about horizontal + h = -h; + t0 = 1 * scaley; + t1 = 0; + } + else { + t0 = 0; + t1 = 1 * scaley; + } + + if ( angle == 0.0f && ClippedCoords( &x, &y, &w, &h, &s0, &t0, &s1, &t1 ) ) { + return; + } + + AdjustCoords(&x, &y, &w, &h); + + DrawStretchPicRotated( x, y, w, h, s0, t0, s1, t1, mat, angle); +} + +void idDeviceContext::DrawStretchPicRotated(float x, float y, float w, float h, float s1, float t1, float s2, float t2, const idMaterial *shader, float angle) { + + idDrawVert verts[4]; + glIndex_t indexes[6]; + indexes[0] = 3; + indexes[1] = 0; + indexes[2] = 2; + indexes[3] = 2; + indexes[4] = 0; + indexes[5] = 1; + verts[0].xyz[0] = x; + verts[0].xyz[1] = y; + verts[0].xyz[2] = 0; + verts[0].st[0] = s1; + verts[0].st[1] = t1; + verts[0].normal[0] = 0; + verts[0].normal[1] = 0; + verts[0].normal[2] = 1; + verts[0].tangents[0][0] = 1; + verts[0].tangents[0][1] = 0; + verts[0].tangents[0][2] = 0; + verts[0].tangents[1][0] = 0; + verts[0].tangents[1][1] = 1; + verts[0].tangents[1][2] = 0; + verts[1].xyz[0] = x + w; + verts[1].xyz[1] = y; + verts[1].xyz[2] = 0; + verts[1].st[0] = s2; + verts[1].st[1] = t1; + verts[1].normal[0] = 0; + verts[1].normal[1] = 0; + verts[1].normal[2] = 1; + verts[1].tangents[0][0] = 1; + verts[1].tangents[0][1] = 0; + verts[1].tangents[0][2] = 0; + verts[1].tangents[1][0] = 0; + verts[1].tangents[1][1] = 1; + verts[1].tangents[1][2] = 0; + verts[2].xyz[0] = x + w; + verts[2].xyz[1] = y + h; + verts[2].xyz[2] = 0; + verts[2].st[0] = s2; + verts[2].st[1] = t2; + verts[2].normal[0] = 0; + verts[2].normal[1] = 0; + verts[2].normal[2] = 1; + verts[2].tangents[0][0] = 1; + verts[2].tangents[0][1] = 0; + verts[2].tangents[0][2] = 0; + verts[2].tangents[1][0] = 0; + verts[2].tangents[1][1] = 1; + verts[2].tangents[1][2] = 0; + verts[3].xyz[0] = x; + verts[3].xyz[1] = y + h; + verts[3].xyz[2] = 0; + verts[3].st[0] = s1; + verts[3].st[1] = t2; + verts[3].normal[0] = 0; + verts[3].normal[1] = 0; + verts[3].normal[2] = 1; + verts[3].tangents[0][0] = 1; + verts[3].tangents[0][1] = 0; + verts[3].tangents[0][2] = 0; + verts[3].tangents[1][0] = 0; + verts[3].tangents[1][1] = 1; + verts[3].tangents[1][2] = 0; + + bool ident = !mat.IsIdentity(); + if ( ident ) { + verts[0].xyz -= origin; + verts[0].xyz *= mat; + verts[0].xyz += origin; + verts[1].xyz -= origin; + verts[1].xyz *= mat; + verts[1].xyz += origin; + verts[2].xyz -= origin; + verts[2].xyz *= mat; + verts[2].xyz += origin; + verts[3].xyz -= origin; + verts[3].xyz *= mat; + verts[3].xyz += origin; + } + + //Generate a translation so we can translate to the center of the image rotate and draw + idVec3 origTrans; + origTrans.x = x+(w/2); + origTrans.y = y+(h/2); + origTrans.z = 0; + + + //Rotate the verts about the z axis before drawing them + idMat4 rotz; + rotz.Identity(); + float sinAng = idMath::Sin(angle); + float cosAng = idMath::Cos(angle); + rotz[0][0] = cosAng; + rotz[0][1] = sinAng; + rotz[1][0] = -sinAng; + rotz[1][1] = cosAng; + for(int i = 0; i < 4; i++) { + //Translate to origin + verts[i].xyz -= origTrans; + + //Rotate + verts[i].xyz = rotz * verts[i].xyz; + + //Translate back + verts[i].xyz += origTrans; + } + + + renderSystem->DrawStretchPic( &verts[0], &indexes[0], 4, 6, shader, (angle == 0.0) ? false : true ); +} + +void idDeviceContext::DrawFilledRect( float x, float y, float w, float h, const idVec4 &color) { + + if ( color.w == 0.0f ) { + return; + } + + renderSystem->SetColor(color); + + if (ClippedCoords(&x, &y, &w, &h, NULL, NULL, NULL, NULL)) { + return; + } + + AdjustCoords(&x, &y, &w, &h); + DrawStretchPic( x, y, w, h, 0, 0, 0, 0, whiteImage); +} + + +void idDeviceContext::DrawRect( float x, float y, float w, float h, float size, const idVec4 &color) { + + if ( color.w == 0.0f ) { + return; + } + + renderSystem->SetColor(color); + + if (ClippedCoords(&x, &y, &w, &h, NULL, NULL, NULL, NULL)) { + return; + } + + AdjustCoords(&x, &y, &w, &h); + DrawStretchPic( x, y, size, h, 0, 0, 0, 0, whiteImage ); + DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, whiteImage ); + DrawStretchPic( x, y, w, size, 0, 0, 0, 0, whiteImage ); + DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, whiteImage ); +} + +void idDeviceContext::DrawMaterialRect( float x, float y, float w, float h, float size, const idMaterial *mat, const idVec4 &color) { + + if ( color.w == 0.0f ) { + return; + } + + renderSystem->SetColor(color); + DrawMaterial( x, y, size, h, mat, color ); + DrawMaterial( x + w - size, y, size, h, mat, color ); + DrawMaterial( x, y, w, size, mat, color ); + DrawMaterial( x, y + h - size, w, size, mat, color ); +} + + +void idDeviceContext::SetCursor(int n) { + cursor = (n < CURSOR_ARROW || n >= CURSOR_COUNT) ? CURSOR_ARROW : n; +} + +void idDeviceContext::DrawCursor(float *x, float *y, float size) { + if (*x < 0) { + *x = 0; + } + + if (*x >= vidWidth) { + *x = vidWidth; + } + + if (*y < 0) { + *y = 0; + } + + if (*y >= vidHeight) { + *y = vidHeight; + } + + renderSystem->SetColor(colorWhite); + AdjustCoords(x, y, &size, &size); + DrawStretchPic( *x, *y, size, size, 0, 0, 1, 1, cursorImages[cursor]); +} +/* + ======================================================================================================================= + ======================================================================================================================= + */ + +void idDeviceContext::PaintChar(float x,float y,float width,float height,float scale,float s,float t,float s2,float t2,const idMaterial *hShader) { + float w, h; + w = width * scale; + h = height * scale; + + if (ClippedCoords(&x, &y, &w, &h, &s, &t, &s2, &t2)) { + return; + } + + AdjustCoords(&x, &y, &w, &h); + DrawStretchPic(x, y, w, h, s, t, s2, t2, hShader); +} + + +void idDeviceContext::SetFontByScale(float scale) { + if (scale <= gui_smallFontLimit.GetFloat()) { + useFont = &activeFont->fontInfoSmall; + activeFont->maxHeight = activeFont->maxHeightSmall; + activeFont->maxWidth = activeFont->maxWidthSmall; + } else if (scale <= gui_mediumFontLimit.GetFloat()) { + useFont = &activeFont->fontInfoMedium; + activeFont->maxHeight = activeFont->maxHeightMedium; + activeFont->maxWidth = activeFont->maxWidthMedium; + } else { + useFont = &activeFont->fontInfoLarge; + activeFont->maxHeight = activeFont->maxHeightLarge; + activeFont->maxWidth = activeFont->maxWidthLarge; + } +} + +int idDeviceContext::DrawText(float x, float y, float scale, idVec4 color, const char *text, float adjust, int limit, int style, int cursor, bool resetEscapes) { + SetFontByScale( scale ); + if ( !useFont || useFont->pointSize == 0.0f ) { + return 0; + } + + const float useScale = scale / useFont->pointSize * 48.0f; + if ( resetEscapes ) { + drawTextColor = color; + drawTextColorAdjust = 0.0f; + } + idVec4 newColor = drawTextColor; + renderSystem->SetColor( newColor ); + + if ( !text || color.w == 0.0f ) { + return 0; + } + const int len = ( limit > 0 ) ? Min( static_cast( strlen( text ) ), limit ) : static_cast( strlen( text ) ); + int count = 0; + while ( *text && count < len ) { + int escapeType = 0; + const int escapeLength = idStr::IsEscape( text, &escapeType ); + if ( escapeLength ) { + if ( escapeType == S_ESCAPE_COLORINDEX ) { + if ( text[1] == C_COLOR_DEFAULT ) { + drawTextColor = color; + } else { + drawTextColor = idStr::ColorForIndex( text[1] ); + drawTextColor.w = color.w; + } + drawTextColorAdjust = 0.0f; + newColor = drawTextColor; + renderSystem->SetColor( newColor ); + } else if ( escapeType == S_ESCAPE_COLOR && escapeLength == 5 ) { + drawTextColor.x = ( text[2] - '0' ) / 9.0f; + drawTextColor.y = ( text[3] - '0' ) / 9.0f; + drawTextColor.z = ( text[4] - '0' ) / 9.0f; + drawTextColor.w = color.w; + drawTextColorAdjust = 0.0f; + newColor = drawTextColor; + renderSystem->SetColor( newColor ); + } else if ( escapeType == S_ESCAPE_ICON ) { + char code[4] = { text[2], text[3], text[4], '\0' }; + embeddedIcon_s *icon = NULL; + if ( icons.Get( code, &icon ) && icon && icon->height > 0 ) { + const glyphInfo_t& reference = useFont->glyphs['W']; + const float iconScale = reference.height / icon->height; + PaintChar( x, y - useScale * reference.horiBearingY - 1.0f, + icon->width * iconScale, icon->height * iconScale, useScale, + icon->s1, icon->t1, icon->s2, icon->t2, icon->shader ); + x += icon->width * iconScale; + } + } + text += escapeLength; + count += escapeLength; + continue; + } + + const glyphInfo_t& glyph = useFont->glyphs[static_cast( *text )]; + const float glyphX = x + glyph.horiBearingX * useScale; + const float glyphY = y - glyph.horiBearingY * useScale - 1.0f; + if ( style == 1 ) { + idVec4 shadowColor( 0.0f, 0.0f, 0.0f, newColor.w ); + renderSystem->SetColor( shadowColor ); + PaintChar( glyphX + 1.0f, glyphY + 1.0f, glyph.width, glyph.height, useScale, + glyph.s1, glyph.t1, glyph.s2, glyph.t2, useFont->material ); + renderSystem->SetColor( newColor ); + } + PaintChar( glyphX, glyphY, glyph.width, glyph.height, useScale, + glyph.s1, glyph.t1, glyph.s2, glyph.t2, useFont->material ); + if ( cursor != -1 && cursor <= count ) { + DrawEditCursor( x, y, scale ); + cursor = -1; + } + x += idMath::Ceil( ( glyph.horiAdvance + adjust ) * useScale ); + text++; + count++; + } + if ( cursor != -1 && cursor <= count ) { + DrawEditCursor( x, y, scale ); + } + return count; +} + +void idDeviceContext::SetSize(float width, float height) { + vidWidth = VIRTUAL_WIDTH; + vidHeight = VIRTUAL_HEIGHT; + xScale = yScale = 0.0f; + if ( width != 0.0f && height != 0.0f ) { + xScale = vidWidth * ( 1.0f / width ); + yScale = vidHeight * ( 1.0f / height ); + } +} + +void idDeviceContext::SizeIcon( embeddedIcon_s *icon ) { + if ( !icon || !icon->shader ) { + return; + } + const int imageWidth = icon->shader->GetImageWidth(); + const int imageHeight = icon->shader->GetImageHeight(); + if ( imageWidth <= 0 || imageHeight <= 0 ) { + return; + } + const int x = static_cast( icon->s1 ); + const int y = static_cast( icon->t1 ); + const int width = icon->width; + const int height = icon->height; + icon->s1 = ( x == -1 ) ? 0.0f : static_cast( x ) / imageWidth; + icon->t1 = ( y == -1 ) ? 0.0f : static_cast( y ) / imageHeight; + icon->s2 = ( x == -1 ) ? 1.0f : static_cast( x + width ) / imageWidth; + icon->t2 = ( y == -1 ) ? 1.0f : static_cast( y + height ) / imageHeight; + icon->width = ( width == -1 ) ? imageWidth : width; + icon->height = ( height == -1 ) ? imageHeight : height; +} + +void idDeviceContext::SizeIcons() { + for ( int i = 0; i < icons.Num(); i++ ) { + SizeIcon( icons.GetIndex( i ) ); + } +} + +void idDeviceContext::RegisterIcon( const char *code, const char *shader, int x, int y, int w, int h ) { + embeddedIcon_s icon; + memset( &icon, 0, sizeof( icon ) ); + idStr::Copynz( icon.code, code, sizeof( icon.code ) ); + icon.shader = declManager->FindMaterial( shader ); + if ( !icon.shader ) { + return; + } + const_cast( icon.shader )->EnsureNotPurged(); + icon.shader->SetSort( SS_GUI ); + icon.width = w; + icon.height = h; + icon.s1 = icon.s2 = static_cast( x ); + icon.t1 = icon.t2 = static_cast( y ); + if ( icon.shader->GetImageWidth() && icon.shader->GetImageHeight() ) { + SizeIcon( &icon ); + } + icons.Set( icon.code, icon ); +} + +int idDeviceContext::CharWidth( const char c, float scale, int adjust ) { + SetFontByScale( scale ); + if ( !useFont || useFont->pointSize == 0.0f ) { + return 0; + } + const float advance = idMath::Ceil( useFont->glyphs[static_cast( c )].horiAdvance ); + return static_cast( ( advance + adjust ) / useFont->pointSize * scale * 48.0f ); +} + +int idDeviceContext::TextWidth( const char *text, float scale, int limit, int adjust ) { + SetFontByScale( scale ); + if ( !text || !useFont || useFont->pointSize == 0.0f ) { + return 0; + } + float width = 0.0f; + for ( int i = 0; text[i] && ( limit <= 0 || i < limit ); ) { + int escapeType = 0; + const int escapeLength = idStr::IsEscape( text + i, &escapeType ); + if ( escapeLength ) { + if ( escapeType == S_ESCAPE_ICON ) { + char code[4] = { text[i + 2], text[i + 3], text[i + 4], '\0' }; + embeddedIcon_s *icon = NULL; + if ( icons.Get( code, &icon ) && icon && icon->height > 0 ) { + width += useFont->glyphs['W'].height / icon->height * icon->width; + } + } + i += escapeLength; + continue; + } + width += idMath::Ceil( useFont->glyphs[static_cast( text[i] )].horiAdvance ) + adjust; + i++; + } + return static_cast( width / useFont->pointSize * scale * 48.0f ); +} + +bool idDeviceContext::GetMaxTextIndex( const char *text, int limit, float textScale, wrapInfo_t& wrapInfo ) { + wrapInfo.lastWhitespace = -1; + wrapInfo.maxIndex = -1; + SetFontByScale( textScale ); + if ( !text || !useFont || useFont->pointSize == 0.0f ) { + return false; + } + float width = 0.0f; + for ( int i = 0; text[i]; ) { + int escapeType = 0; + const int escapeLength = idStr::IsEscape( text + i, &escapeType ); + if ( escapeLength ) { + if ( escapeType == S_ESCAPE_ICON ) { + char code[4] = { text[i + 2], text[i + 3], text[i + 4], '\0' }; + embeddedIcon_s *icon = NULL; + if ( icons.Get( code, &icon ) && icon && icon->height > 0 ) { + width += useFont->glyphs['W'].height / icon->height * icon->width; + } + } + i += escapeLength; + } else { + width += idMath::Ceil( useFont->glyphs[static_cast( text[i] )].horiAdvance ); + const int pixelWidth = static_cast( width / useFont->pointSize * textScale * 48.0f ); + if ( pixelWidth > limit ) { + wrapInfo.maxIndex = i - 1; + return true; + } + if ( text[i] == ' ' ) { + wrapInfo.lastWhitespace = i; + } + i++; + } + } + return false; +} + +int idDeviceContext::TextHeight(const char *text, float scale, int limit) { + int len, count; + float max; + glyphInfo_t *glyph; + const char *s = text; + SetFontByScale(scale); + fontInfo_t *font = useFont; + if ( !font || font->pointSize == 0.0f ) { + return 0; + } + const float useScale = scale / font->pointSize * 48.0f; + max = 0; + if (text) { + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + + count = 0; + while (s && *s && count < len) { + const int escapeLength = idStr::IsEscape( s ); + if ( escapeLength ) { + s += escapeLength; + continue; + } + else { + glyph = &font->glyphs[*(const unsigned char*)s]; + if (max < glyph->height) { + max = glyph->height; + } + + s++; + count++; + } + } + } + + return idMath::FtoiFast( max * useScale ); +} + +int idDeviceContext::MaxCharWidth(float scale) { + SetFontByScale(scale); + if ( !useFont || useFont->pointSize == 0.0f ) { + return 0; + } + return static_cast( scale / useFont->pointSize * 48.0f * activeFont->maxWidth ); +} + +int idDeviceContext::MaxCharHeight(float scale) { + SetFontByScale(scale); + if ( !useFont || useFont->pointSize == 0.0f ) { + return 0; + } + return static_cast( scale / useFont->pointSize * 48.0f * activeFont->maxHeight ); +} + +const idMaterial *idDeviceContext::GetScrollBarImage(int index) { + if (index >= SCROLLBAR_HBACK && index < SCROLLBAR_COUNT) { + return scrollBarImages[index]; + } + return scrollBarImages[SCROLLBAR_HBACK]; +} + +// this only supports left aligned text +idRegion *idDeviceContext::GetTextRegion(const char *text, float textScale, idRectangle rectDraw, float xStart, float yStart) { +#if 0 + const char *p, *textPtr, *newLinePtr; + char buff[1024]; + int len, textWidth, newLine, newLineWidth; + float y; + + float charSkip = MaxCharWidth(textScale) + 1; + float lineSkip = MaxCharHeight(textScale); + + textWidth = 0; + newLinePtr = NULL; +#endif + return NULL; +/* + if (text == NULL) { + return; + } + + textPtr = text; + if (*textPtr == '\0') { + return; + } + + y = lineSkip + rectDraw.y + yStart; + len = 0; + buff[0] = '\0'; + newLine = 0; + newLineWidth = 0; + p = textPtr; + + textWidth = 0; + while (p) { + if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\0') { + newLine = len; + newLinePtr = p + 1; + newLineWidth = textWidth; + } + + if ((newLine && textWidth > rectDraw.w) || *p == '\n' || *p == '\0') { + if (len) { + + float x = rectDraw.x ; + + buff[newLine] = '\0'; + DrawText(x, y, textScale, color, buff, 0, 0, 0); + if (!wrap) { + return; + } + } + + if (*p == '\0') { + break; + } + + y += lineSkip + 5; + p = newLinePtr; + len = 0; + newLine = 0; + newLineWidth = 0; + continue; + } + + buff[len++] = *p++; + buff[len] = '\0'; + textWidth = TextWidth( buff, textScale, -1 ); + } +*/ +} + +void idDeviceContext::DrawEditCursor( float x, float y, float scale ) { + if ( com_ticNumber & 0x10 ) { + return; + } + SetFontByScale(scale); + if ( !useFont || useFont->pointSize == 0.0f ) { + return; + } + float useScale = scale / useFont->pointSize * 48.0f; + const glyphInfo_t *glyph2 = &useFont->glyphs[(overStrikeMode) ? '_' : '|']; + float yadj = useScale * glyph2->horiBearingY - 1.0f; + PaintChar( x, y - yadj, glyph2->width, glyph2->height, useScale, + glyph2->s1, glyph2->t1, glyph2->s2, glyph2->t2, useFont->material ); +} + +int idDeviceContext::DrawText( const char *text, float textScale, int textAlign, idVec4 color, idRectangle rectDraw, bool wrap, int cursor, bool calcOnly, idList *breaks, int limit ) { + const char *p, *textPtr, *newLinePtr; + char buff[1024]; + int len, newLine, newLineWidth, count; + float y; + float textWidth; + + float charSkip = MaxCharWidth( textScale ) + 1; + float lineSkip = MaxCharHeight( textScale ); + + float cursorSkip = ( cursor >= 0 ? charSkip : 0 ); + + bool lineBreak, wordBreak; + + SetFontByScale( textScale ); + + textWidth = 0; + newLinePtr = NULL; + + if (!calcOnly && !(text && *text)) { + if (cursor == 0) { + renderSystem->SetColor(color); + DrawEditCursor(rectDraw.x, lineSkip + rectDraw.y, textScale); + } + return idMath::FtoiFast( rectDraw.w / charSkip ); + } + + textPtr = text; + + y = lineSkip + rectDraw.y; + len = 0; + buff[0] = '\0'; + newLine = 0; + newLineWidth = 0; + p = textPtr; + + if ( breaks ) { + breaks->Append(0); + } + count = 0; + textWidth = 0; + lineBreak = false; + wordBreak = false; + while (p) { + + if ( *p == '\n' || *p == '\r' || *p == '\0' ) { + lineBreak = true; + if ((*p == '\n' && *(p + 1) == '\r') || (*p == '\r' && *(p + 1) == '\n')) { + p++; + } + } + + int nextCharWidth = ( idStr::CharIsPrintable(*p) ? CharWidth( *p, textScale ) : cursorSkip ); + // FIXME: this is a temp hack until the guis can be fixed not not overflow the bounding rectangles + // the side-effect is that list boxes and edit boxes will draw over their scroll bars + // The following line and the !linebreak in the if statement below should be removed + nextCharWidth = 0; + + if ( !lineBreak && ( textWidth + nextCharWidth ) > rectDraw.w ) { + // The next character will cause us to overflow, if we haven't yet found a suitable + // break spot, set it to be this character + if ( len > 0 && newLine == 0 ) { + newLine = len; + newLinePtr = p; + newLineWidth = textWidth; + } + wordBreak = true; + } else if ( lineBreak || ( wrap && (*p == ' ' || *p == '\t') ) ) { + // The next character is in view, so if we are a break character, store our position + newLine = len; + newLinePtr = p + 1; + newLineWidth = textWidth; + } + + if ( lineBreak || wordBreak ) { + float x = rectDraw.x; + + if (textAlign == ALIGN_RIGHT) { + x = rectDraw.x + rectDraw.w - newLineWidth; + } else if (textAlign == ALIGN_CENTER) { + x = rectDraw.x + (rectDraw.w - newLineWidth) / 2; + } + + if ( wrap || newLine > 0 ) { + buff[newLine] = '\0'; + + // This is a special case to handle breaking in the middle of a word. + // if we didn't do this, the cursor would appear on the end of this line + // and the beginning of the next. + if ( wordBreak && cursor >= newLine && newLine == len ) { + cursor++; + } + } + + if (!calcOnly) { + count += DrawText(x, y, textScale, color, buff, 0, 0, 0, cursor); + } + + if ( cursor < newLine ) { + cursor = -1; + } else if ( cursor >= 0 ) { + cursor -= ( newLine + 1 ); + } + + if ( !wrap ) { + return newLine; + } + + if ( ( limit && count > limit ) || *p == '\0' ) { + break; + } + + y += lineSkip + 5; + + if ( !calcOnly && y > rectDraw.Bottom() ) { + break; + } + + p = newLinePtr; + + if (breaks) { + breaks->Append(p - text); + } + + len = 0; + newLine = 0; + newLineWidth = 0; + textWidth = 0; + lineBreak = false; + wordBreak = false; + continue; + } + + buff[len++] = *p++; + buff[len] = '\0'; + // Escape sequences and embedded icons are measured as a unit. + textWidth = static_cast( TextWidth( buff, textScale, -1 ) ); + } + + return idMath::FtoiFast( rectDraw.w / charSkip ); +} + +/* +============= +idRectangle::String +============= +*/ +char *idRectangle::String( void ) const { + static int index = 0; + static char str[ 8 ][ 48 ]; + char *s; + + // use an array so that multiple toString's won't collide + s = str[ index ]; + index = (index + 1)&7; + + sprintf( s, "%.2f %.2f %.2f %.2f", x, y, w, h ); + + return s; +} diff --git a/src/ui/editwindow.cpp b/src/ui/editwindow.cpp new file mode 100644 index 0000000..5e38a44 --- /dev/null +++ b/src/ui/editwindow.cpp @@ -0,0 +1,645 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SliderWindow.h" +#include "EditWindow.h" + + +bool idEditWindow::ParseInternalVar( const char *_name, idParser *src ) { + if ( idStr::Icmp( _name, "maxchars" ) == 0) { + maxChars = src->ParseInt(); + return true; + } + if ( idStr::Icmp( _name, "numeric" ) == 0) { + numeric = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "wrap" ) == 0) { + wrap = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "readonly" ) == 0) { + readonly = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "forceScroll" ) == 0) { + forceScroll = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "source" ) == 0) { + ParseString( src, sourceFile ); + return true; + } + if ( idStr::Icmp( _name, "password" ) == 0 ) { + password = src->ParseBool(); + return true; + } + if ( idStr::Icmp( _name, "cvarMax" ) == 0) { + cvarMax = src->ParseInt(); + return true; + } + + return idWindow::ParseInternalVar( _name, src ); +} + +idWinVar *idEditWindow::GetWinVarByName( const char *_name, bool fixup, drawWin_t** owner ) { + if ( idStr::Icmp( _name, "cvar" ) == 0 ) { + return &cvarStr; + } + if ( idStr::Icmp( _name, "password" ) == 0 ) { + return &password; + } + if ( idStr::Icmp( _name, "liveUpdate" ) == 0 ) { + return &liveUpdate; + } + if ( idStr::Icmp( _name, "cvarGroup" ) == 0 ) { + return &cvarGroup; + } + return idWindow::GetWinVarByName( _name, fixup, owner ); +} + +void idEditWindow::CommonInit() { + maxChars = 128; + numeric = false; + paintOffset = 0; + cursorPos = 0; + cursorLine = 0; + cvarMax = 0; + wrap = false; + sourceFile = ""; + scroller = NULL; + sizeBias = 0; + lastTextLength = 0; + forceScroll = false; + password = false; + cvar = NULL; + liveUpdate = true; + readonly = false; + + scroller = new idSliderWindow(dc, gui); +} + + +idEditWindow::idEditWindow( idDeviceContext *d, idUserInterfaceLocal *g ) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idEditWindow::idEditWindow( idUserInterfaceLocal *g ) : idWindow(g) { + gui = g; + CommonInit(); +} + +idEditWindow::~idEditWindow() { + +} + +void idEditWindow::GainFocus() { + cursorPos = text.Length(); + EnsureCursorVisible(); +} + +void idEditWindow::Draw( int time, float x, float y ) { + idVec4 color = foreColor; + + UpdateCvar( true ); + + int len = text.Length(); + if ( len != lastTextLength ) { + scroller->SetValue( 0.0f ); + EnsureCursorVisible(); + lastTextLength = len; + } + float scale = textScale; + + idStr pass; + const char* buffer; + if ( password ) { + const char* temp = text; + for ( ; *temp; temp++ ) { + pass += "*"; + } + buffer = pass; + } else { + buffer = text; + } + + if ( cursorPos > len ) { + cursorPos = len; + } + + idRectangle rect = textRect; + + rect.x -= paintOffset; + rect.w += paintOffset; + + if ( wrap && scroller->GetHigh() > 0.0f ) { + float lineHeight = GetMaxCharHeight( ) + 5; + rect.y -= scroller->GetValue() * lineHeight; + rect.w -= sizeBias; + rect.h = ( breaks.Num() + 1 ) * lineHeight; + } + + if ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + hover = false; + } + if ( flags & WIN_FOCUS ) { + color = hoverColor; + } + + dc->DrawText( buffer, scale, 0, color, rect, wrap, (flags & WIN_FOCUS) ? cursorPos : -1); +} + +/* +============= +idEditWindow::HandleEvent +============= +*/ +const char *idEditWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + static char buffer[ MAX_EDITFIELD ]; + const char *ret = ""; + + if ( wrap ) { + // need to call this to allow proper focus and capturing on embedded children + ret = idWindow::HandleEvent( event, updateVisuals ); + if ( ret && *ret ) { + return ret; + } + } + + if ( ( event->evType != SE_CHAR && event->evType != SE_KEY ) ) { + return ret; + } + + idStr::Copynz( buffer, text.c_str(), sizeof( buffer ) ); + int key = event->evValue; + int len = text.Length(); + + if ( event->evType == SE_CHAR ) { + if ( event->evValue == Sys_GetConsoleKey( false ) || event->evValue == Sys_GetConsoleKey( true ) ) { + return ""; + } + + if ( updateVisuals ) { + *updateVisuals = true; + } + + if ( maxChars && len > maxChars ) { + len = maxChars; + } + + if ( ( key == K_ENTER || key == K_KP_ENTER ) && event->evValue2 ) { + RunScript( ON_ACTION ); + RunScript( ON_ENTER ); + return cmd; + } + + if ( key == K_ESCAPE ) { + RunScript( ON_ESC ); + return cmd; + } + + if ( readonly ) { + return ""; + } + + if ( key == 'h' - 'a' + 1 || key == K_BACKSPACE ) { // ctrl-h is backspace + if ( cursorPos > 0 ) { + if ( cursorPos >= len ) { + buffer[len - 1] = 0; + cursorPos = len - 1; + } else { + memmove( &buffer[ cursorPos - 1 ], &buffer[ cursorPos ], len + 1 - cursorPos); + cursorPos--; + } + + text = buffer; + UpdateCvar( false ); + RunScript( ON_ACTION ); + } + + return ""; + } + + // + // ignore any non printable chars (except enter when wrap is enabled) + // + if ( wrap && (key == K_ENTER || key == K_KP_ENTER) ) { + } else if ( !idStr::CharIsPrintable( key ) ) { + return ""; + } + + if ( numeric ) { + if ( ( key < '0' || key > '9' ) && key != '.' ) { + return ""; + } + } + + if ( dc->GetOverStrike() ) { + if ( maxChars && cursorPos >= maxChars ) { + return ""; + } + } else { + if ( ( len == MAX_EDITFIELD - 1 ) || ( maxChars && len >= maxChars ) ) { + return ""; + } + memmove( &buffer[ cursorPos + 1 ], &buffer[ cursorPos ], len + 1 - cursorPos ); + } + + buffer[ cursorPos ] = key; + + text = buffer; + UpdateCvar( false ); + RunScript( ON_ACTION ); + + if ( cursorPos < len + 1 ) { + cursorPos++; + } + EnsureCursorVisible(); + + } else if ( event->evType == SE_KEY && event->evValue2 ) { + + if ( updateVisuals ) { + *updateVisuals = true; + } + + if ( key == K_DEL ) { + if ( readonly ) { + return ret; + } + if ( cursorPos < len ) { + memmove( &buffer[cursorPos], &buffer[cursorPos + 1], len - cursorPos); + text = buffer; + UpdateCvar( false ); + RunScript( ON_ACTION ); + } + return ret; + } + + if ( key == K_RIGHTARROW ) { + if ( cursorPos < len ) { + if ( idKeyInput::IsDown( K_CTRL ) ) { + // skip to next word + while( ( cursorPos < len ) && ( buffer[ cursorPos ] != ' ' ) ) { + cursorPos++; + } + + while( ( cursorPos < len ) && ( buffer[ cursorPos ] == ' ' ) ) { + cursorPos++; + } + } else { + if ( cursorPos < len ) { + cursorPos++; + } + } + } + + EnsureCursorVisible(); + + return ret; + } + + if ( key == K_LEFTARROW ) { + if ( idKeyInput::IsDown( K_CTRL ) ) { + // skip to previous word + while( ( cursorPos > 0 ) && ( buffer[ cursorPos - 1 ] == ' ' ) ) { + cursorPos--; + } + + while( ( cursorPos > 0 ) && ( buffer[ cursorPos - 1 ] != ' ' ) ) { + cursorPos--; + } + } else { + if ( cursorPos > 0 ) { + cursorPos--; + } + } + + EnsureCursorVisible(); + + return ret; + } + + if ( key == K_HOME ) { + if ( idKeyInput::IsDown( K_CTRL ) || cursorLine <= 0 || ( cursorLine >= breaks.Num() ) ) { + cursorPos = 0; + } else { + cursorPos = breaks[cursorLine]; + } + EnsureCursorVisible(); + return ret; + } + + if ( key == K_END ) { + if ( idKeyInput::IsDown( K_CTRL ) || (cursorLine < -1) || ( cursorLine >= breaks.Num() - 1 ) ) { + cursorPos = len; + } else { + cursorPos = breaks[cursorLine + 1] - 1; + } + EnsureCursorVisible(); + return ret; + } + + if ( key == K_INS ) { + if ( !readonly ) { + dc->SetOverStrike( !dc->GetOverStrike() ); + } + return ret; + } + + if ( key == K_DOWNARROW ) { + if ( idKeyInput::IsDown( K_CTRL ) ) { + scroller->SetValue( scroller->GetValue() + 1.0f ); + } else { + if ( cursorLine < breaks.Num() - 1 ) { + int offset = cursorPos - breaks[cursorLine]; + cursorPos = breaks[cursorLine + 1] + offset; + EnsureCursorVisible(); + } + } + } + + if (key == K_UPARROW ) { + if ( idKeyInput::IsDown( K_CTRL ) ) { + scroller->SetValue( scroller->GetValue() - 1.0f ); + } else { + if ( cursorLine > 0 ) { + int offset = cursorPos - breaks[cursorLine]; + cursorPos = breaks[cursorLine - 1] + offset; + EnsureCursorVisible(); + } + } + } + + if ( key == K_ENTER || key == K_KP_ENTER ) { + RunScript( ON_ACTION ); + RunScript( ON_ENTER ); + return cmd; + } + + if ( key == K_ESCAPE ) { + RunScript( ON_ESC ); + return cmd; + } + + } else if ( event->evType == SE_KEY && !event->evValue2 ) { + if ( key == K_ENTER || key == K_KP_ENTER ) { + RunScript( ON_ENTERRELEASE ); + return cmd; + } else { + RunScript( ON_ACTIONRELEASE ); + } + } + + return ret; +} + +void idEditWindow::PostParse() { + idWindow::PostParse(); + + if ( maxChars == 0 ) { + maxChars = 10; + } + if ( sourceFile.Length() ) { + void *buffer; + fileSystem->ReadFile( sourceFile, &buffer ); + text = (char *) buffer; + fileSystem->FreeFile( buffer ); + } + + InitCvar(); + InitScroller(false); + + EnsureCursorVisible(); + + flags |= WIN_CANFOCUS; +} + +/* +================ +idEditWindow::InitScroller + +This is the same as in idListWindow +================ +*/ +void idEditWindow::InitScroller( bool horizontal ) +{ + const char *thumbImage = "gfx/guis/scrollbar_thumb.tga"; + const char *barImage = "gfx/guis/scrollbarv.tga"; + const char *scrollerName = "_scrollerWinV"; + + if (horizontal) { + barImage = "gfx/guis/scrollbarh.tga"; + scrollerName = "_scrollerWinH"; + } + + const idMaterial *mat = declManager->FindMaterial( barImage ); + mat->SetSort( SS_GUI ); + sizeBias = mat->GetImageWidth(); + + idRectangle scrollRect; + if (horizontal) { + sizeBias = mat->GetImageHeight(); + scrollRect.x = 0; + scrollRect.y = (clientRect.h - sizeBias); + scrollRect.w = clientRect.w; + scrollRect.h = sizeBias; + } else { + scrollRect.x = (clientRect.w - sizeBias); + scrollRect.y = 0; + scrollRect.w = sizeBias; + scrollRect.h = clientRect.h; + } + + scroller->InitWithDefaults(scrollerName, scrollRect, foreColor, matColor, mat->GetName(), thumbImage, !horizontal, true); + InsertChild(scroller, NULL); + scroller->SetBuddy(this); +} + +void idEditWindow::HandleBuddyUpdate( idWindow *buddy ) { +} + +void idEditWindow::EnsureCursorVisible() +{ + if ( readonly ) { + cursorPos = -1; + } else if ( maxChars == 1 ) { + cursorPos = 0; + } + + if ( !dc ) { + return; + } + + SetFont(); + if ( !wrap ) { + int cursorX = 0; + if ( password ) { + cursorX = cursorPos * dc->CharWidth( '*', textScale ); + } else { + int i = 0; + while ( i < text.Length() && i < cursorPos ) { + const int escapeLength = idStr::IsEscape( &text[i] ); + if ( escapeLength ) { + i += escapeLength; + } else { + cursorX += dc->CharWidth( text[i], textScale ); + i++; + } + } + } + int maxWidth = GetMaxCharWidth( ); + int left = cursorX - maxWidth; + int right = ( cursorX - textRect.w ) + maxWidth; + + if ( paintOffset > left ) { + // When we go past the left side, we want the text to jump 6 characters + paintOffset = left - maxWidth * 6; + } + if ( paintOffset < right) { + paintOffset = right; + } + if ( paintOffset < 0 ) { + paintOffset = 0; + } + scroller->SetRange(0.0f, 0.0f, 1.0f); + + } else { + // Word wrap + + breaks.Clear(); + idRectangle rect = textRect; + rect.w -= sizeBias; + dc->DrawText(text, textScale, textAlign, colorWhite, rect, true, (flags & WIN_FOCUS) ? cursorPos : -1, true, &breaks ); + + int fit = textRect.h / (GetMaxCharHeight() + 5); + if ( fit < breaks.Num() + 1 ) { + scroller->SetRange(0, breaks.Num() + 1 - fit, 1); + } else { + // The text fits completely in the box + scroller->SetRange(0.0f, 0.0f, 1.0f); + } + + if ( forceScroll ) { + scroller->SetValue( breaks.Num() - fit ); + } else if ( readonly ) { + } else { + cursorLine = 0; + for ( int i = 1; i < breaks.Num(); i++ ) { + if ( cursorPos >= breaks[i] ) { + cursorLine = i; + } else { + break; + } + } + int topLine = idMath::FtoiFast( scroller->GetValue() ); + if ( cursorLine < topLine ) { + scroller->SetValue( cursorLine ); + } else if ( cursorLine >= topLine + fit) { + scroller->SetValue( ( cursorLine - fit ) + 1 ); + } + } + } +} + +void idEditWindow::Activate(bool activate, idStr &act) { + idWindow::Activate(activate, act); + if ( activate ) { + UpdateCvar( true, true ); + EnsureCursorVisible(); + } +} + +/* +============ +idEditWindow::InitCvar +============ +*/ +void idEditWindow::InitCvar( ) { + if ( cvarStr[0] == '\0' ) { + if ( text.GetName() == NULL ) { + common->Warning( "idEditWindow::InitCvar: gui '%s' window '%s' has an empty cvar string", gui->GetSourceFile(), name.c_str() ); + } + cvar = NULL; + return; + } + + cvar = cvarSystem->Find( cvarStr ); + if ( !cvar ) { + common->Warning( "idEditWindow::InitCvar: gui '%s' window '%s' references undefined cvar '%s'", gui->GetSourceFile(), name.c_str(), cvarStr.c_str() ); + return; + } +} + +/* +============ +idEditWindow::UpdateCvar +============ +*/ +void idEditWindow::UpdateCvar( bool read, bool force ) { + if ( force || liveUpdate ) { + if ( cvar ) { + if ( read ) { + text = cvar->GetString(); + } else { + cvar->SetString( text ); + if ( cvarMax && ( cvar->GetInteger() > cvarMax ) ) { + cvar->SetInteger( cvarMax ); + } + } + } + } +} + +/* +============ +idEditWindow::RunNamedEvent +============ +*/ +void idEditWindow::RunNamedEvent( const char* eventName ) { + idStr event, group; + + if ( !idStr::Cmpn( eventName, "cvar read ", 10 ) ) { + event = eventName; + group = event.Mid( 10, event.Length() - 10 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( true, true ); + } + } else if ( !idStr::Cmpn( eventName, "cvar write ", 11 ) ) { + event = eventName; + group = event.Mid( 11, event.Length() - 11 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( false, true ); + } + } +} diff --git a/src/ui/fieldwindow.cpp b/src/ui/fieldwindow.cpp new file mode 100644 index 0000000..06bb518 --- /dev/null +++ b/src/ui/fieldwindow.cpp @@ -0,0 +1,101 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "FieldWindow.h" + + +void idFieldWindow::CommonInit() { + cursorPos = 0; + lastTextLength = 0; + lastCursorPos = 0; + paintOffset = 0; + showCursor = false; +} + +idFieldWindow::idFieldWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idFieldWindow::idFieldWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idFieldWindow::~idFieldWindow() { + +} + +bool idFieldWindow::ParseInternalVar(const char *_name, idParser *src) { + if (idStr::Icmp(_name, "cursorvar") == 0) { + ParseString(src, cursorVar); + return true; + } + if (idStr::Icmp(_name, "showcursor") == 0) { + showCursor = src->ParseBool(); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} + + +void idFieldWindow::CalcPaintOffset(int len) { + lastCursorPos = cursorPos; + lastTextLength = len; + paintOffset = 0; + int tw = dc->TextWidth(text, textScale, -1); + if (tw < textRect.w) { + return; + } + while (tw > textRect.w && len > 0) { + tw = dc->TextWidth(text, textScale, --len); + paintOffset++; + } +} + + +void idFieldWindow::Draw(int time, float x, float y) { + float scale = textScale; + int len = text.Length(); + cursorPos = gui->State().GetInt( cursorVar ); + if (len != lastTextLength || cursorPos != lastCursorPos) { + CalcPaintOffset(len); + } + idRectangle rect = textRect; + if (paintOffset >= len) { + paintOffset = 0; + } + if (cursorPos > len) { + cursorPos = len; + } + dc->DrawText(&text[paintOffset], scale, 0, foreColor, rect, false, ((flags & WIN_FOCUS) || showCursor) ? cursorPos - paintOffset : -1); +} + diff --git a/src/ui/gamewindow.cpp b/src/ui/gamewindow.cpp new file mode 100644 index 0000000..5942b99 --- /dev/null +++ b/src/ui/gamewindow.cpp @@ -0,0 +1,48 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "GameWindow.h" + +/* +================ +idGameWindowProxy::idGameWindowProxy +================ +*/ +idGameWindowProxy::idGameWindowProxy( idDeviceContext *d, idUserInterfaceLocal *g ) : idWindow( d, g ) { } + +/* +================ +idGameWindowProxy::Draw +================ +*/ +void idGameWindowProxy::Draw( int time, float x, float y ) { + common->Printf( "TODO: idGameWindowProxy::Draw\n" ); +} + diff --git a/src/ui/guiscript.cpp b/src/ui/guiscript.cpp new file mode 100644 index 0000000..c7bcc08 --- /dev/null +++ b/src/ui/guiscript.cpp @@ -0,0 +1,709 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Window.h" +#include "Winvar.h" +#include "GuiScript.h" +#include "UserInterfaceLocal.h" + + +/* +========================= +Script_Set +========================= +*/ +void Script_Set(idWindow *window, idList *src) { + idStr key, val; + idWinStr *dest = dynamic_cast((*src)[0].var); + if (dest) { + if (idStr::Icmp(*dest, "cmd") == 0) { + dest = dynamic_cast((*src)[1].var); + int parmCount = src->Num(); + if (parmCount > 2) { + val = dest->c_str(); + int i = 2; + while (i < parmCount) { + val += " \""; + val += (*src)[i].var->c_str(); + val += "\""; + i++; + } + window->AddCommand(val); + } else { + window->AddCommand(*dest); + } + return; + } + } + (*src)[0].var->Set((*src)[1].var->c_str()); + (*src)[0].var->SetEval(false); +} + +/* +========================= +Script_SetFocus +========================= +*/ +void Script_SetFocus(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if (parm) { + drawWin_t *win = window->GetGui()->GetDesktop()->FindChildByName(*parm); + if (win && win->win) { + window->SetFocus(win->win); + } + } +} + +/* +========================= +Script_ShowCursor +========================= +*/ +void Script_ShowCursor(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if ( parm ) { + if ( atoi( *parm ) ) { + window->GetGui()->GetDesktop()->ClearFlag( WIN_NOCURSOR ); + } else { + window->GetGui()->GetDesktop()->SetFlag( WIN_NOCURSOR ); + } + } +} + +/* +========================= +Script_RunScript + + run scripts must come after any set cmd set's in the script +========================= +*/ +void Script_RunScript(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if (parm) { + idStr str = window->cmd; + str += " ; runScript "; + str += parm->c_str(); + window->cmd = str; + } +} + +/* +========================= +Script_LocalSound +========================= +*/ +void Script_LocalSound(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if (parm) { + soundSystem->PlayShaderDirectly( 1, *parm, -1 ); + } +} + +void Script_SetLightColor(idWindow *window, idList *src) { + idWinVec4 *color = dynamic_cast((*src)[0].var); + window->GetGui()->SetLightColorVar( color ); +} + +void Script_NonInteractive(idWindow *window, idList *src) { + bool interactive = false; + if ( src->Num() > 0 ) { + idWinStr *parm = dynamic_cast((*src)[0].var); + interactive = parm && atoi( parm->c_str() ) == 0; + } + window->GetGui()->SetInteractive( interactive ); +} + +void Script_ResetVideo(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + drawWin_t *drawWin = parm ? window->GetGui()->GetDesktop()->FindChildByName( parm->c_str() ) : NULL; + if ( drawWin && drawWin->win ) { + drawWin->win->ResetCinematics(); + drawWin->win->EvalRegs( -1, true ); + } else if ( drawWin && drawWin->simp ) { + drawWin->simp->ResetCinematics(); + } else { + window->ResetCinematics(); + window->EvalRegs( -1, true ); + } +} + +void Script_NamedEvent(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if ( !parm ) { + return; + } + idStr event = parm->c_str(); + const int separator = event.Find( "::" ); + if ( separator <= 0 ) { + window->GetGui()->HandleNamedEvent( event ); + return; + } + idStr windowName = event.Left( separator ); + idStr windowEvent = event.Right( event.Length() - separator - 2 ); + drawWin_t *drawWin = window->GetGui()->GetDesktop()->FindChildByName( windowName ); + if ( drawWin && drawWin->win ) { + drawWin->win->RunNamedEvent( windowEvent ); + } +} + +void Script_StopTransitions(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + if ( !parm ) { + return; + } + drawWin_t *drawWin = window->GetGui()->GetDesktop()->FindChildByName( parm->c_str() ); + if ( drawWin && drawWin->win ) { + drawWin->win->ClearTransitions(); + } +} + +void Script_ConsoleCmd(idWindow *window, idList *src) { + idStr command; + for ( int i = 0; i < src->Num(); ++i ) { + if ( i > 0 ) { + command += " "; + } + command += (*src)[i].var->c_str(); + } + command += " \n"; + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, command ); +} + +/* +========================= +Script_EvalRegs +========================= +*/ +void Script_EvalRegs(idWindow *window, idList *src) { + window->EvalRegs(-1, true); +} + +/* +========================= +Script_EndGame +========================= +*/ +void Script_EndGame( idWindow *window, idList *src ) { + cvarSystem->SetCVarBool( "g_nightmare", true ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" ); +} + +/* +========================= +Script_ResetTime +========================= +*/ +void Script_ResetTime(idWindow *window, idList *src) { + idWinStr *parm = dynamic_cast((*src)[0].var); + drawWin_t *win = NULL; + if (parm && src->Num() > 1) { + win = window->GetGui()->GetDesktop()->FindChildByName(*parm); + parm = dynamic_cast((*src)[1].var); + } + if (win && win->win) { + win->win->ResetTime(atoi(*parm)); + win->win->EvalRegs(-1, true); + } else { + window->ResetTime(atoi(*parm)); + window->EvalRegs(-1, true); + } +} + +/* +========================= +Script_ResetCinematics +========================= +*/ +void Script_ResetCinematics(idWindow *window, idList *src) { + window->ResetCinematics(); +} + +/* +========================= +Script_Transition +========================= +*/ +void Script_Transition(idWindow *window, idList *src) { + // transitions always affect rect or vec4 vars + if (src->Num() >= 4) { + idWinRectangle *rect = NULL; + idWinVec4 *vec4 = dynamic_cast((*src)[0].var); + // + // added float variable + idWinFloat* val = NULL; + idWinFloatMember *member = NULL; + // + if (vec4 == NULL) { + rect = dynamic_cast((*src)[0].var); + // + // added float variable + if ( NULL == rect ) { + val = dynamic_cast((*src)[0].var); + if ( NULL == val ) { + member = dynamic_cast((*src)[0].var); + } + } + // + } + idWinVec4 *from = dynamic_cast((*src)[1].var); + idWinVec4 *to = dynamic_cast((*src)[2].var); + idWinStr *timeStr = dynamic_cast((*src)[3].var); + // + // added float variable + if (!((vec4 || rect || val || member) && from && to && timeStr)) { + // + common->Warning("Bad transition in gui %s in window %s\n", window->GetGui()->GetSourceFile(), window->GetName()); + return; + } + int time = atoi(*timeStr); + float ac = 0.0f; + float dc = 0.0f; + if (src->Num() > 4) { + idWinStr *acv = dynamic_cast((*src)[4].var); + idWinStr *dcv = dynamic_cast((*src)[5].var); + assert(acv && dcv); + ac = atof(*acv); + dc = atof(*dcv); + } + + if (vec4) { + vec4->SetEval(false); + window->AddTransition(vec4, *from, *to, time, ac, dc); + // + // added float variable + } else if ( val ) { + val->SetEval ( false ); + window->AddTransition(val, *from, *to, time, ac, dc); + } else if ( member ) { + member->SetEval( false ); + window->AddTransition( member, *from, *to, time, ac, dc ); + // + } else { + rect->SetEval(false); + window->AddTransition(rect, *from, *to, time, ac, dc); + } + window->StartTransition(); + } +} + +typedef struct { + const char *name; + void (*handler) (idWindow *window, idList *src); + int mMinParms; + int mMaxParms; +} guiCommandDef_t; + +guiCommandDef_t commandList[] = { + { "set", Script_Set, 2, 999 }, + { "setFocus", Script_SetFocus, 1, 2 }, + { "endGame", Script_EndGame, 0, 0 }, + { "consoleCmd", Script_ConsoleCmd, 0, 10 }, + { "resetTime", Script_ResetTime, 1, 2 }, + { "showCursor", Script_ShowCursor, 1, 1 }, + { "resetCinematics", Script_ResetCinematics, 0, 2 }, + { "resetVideo", Script_ResetVideo, 1, 1 }, + { "transition", Script_Transition, 4, 6 }, + { "localSound", Script_LocalSound, 1, 1 }, + { "runScript", Script_RunScript, 1, 1 }, + { "evalRegs", Script_EvalRegs, 0, 0 }, + { "setlightcolor", Script_SetLightColor, 1, 1 }, + { "namedEvent", Script_NamedEvent, 1, 1 }, + { "stopTransitions", Script_StopTransitions, 1, 1 }, + { "nonInteractive", Script_NonInteractive, 0, 1 } +}; + +int scriptCommandCount = sizeof(commandList) / sizeof(guiCommandDef_t); + + +/* +========================= +idGuiScript::idGuiScript +========================= +*/ +idGuiScript::idGuiScript() { + ifList = NULL; + elseList = NULL; + conditionReg = -1; + handler = NULL; + parms.SetGranularity( 2 ); +} + +/* +========================= +idGuiScript::~idGuiScript +========================= +*/ +idGuiScript::~idGuiScript() { + delete ifList; + delete elseList; + int c = parms.Num(); + for ( int i = 0; i < c; i++ ) { + if ( parms[i].own ) { + delete parms[i].var; + } + } +} + +/* +========================= +idGuiScript::WriteToSaveGame +========================= +*/ +void idGuiScript::WriteToSaveGame( idFile *savefile ) { + int i; + + if ( ifList ) { + ifList->WriteToSaveGame( savefile ); + } + if ( elseList ) { + elseList->WriteToSaveGame( savefile ); + } + + savefile->Write( &conditionReg, sizeof( conditionReg ) ); + + for ( i = 0; i < parms.Num(); i++ ) { + if ( parms[i].own ) { + parms[i].var->WriteToSaveGame( savefile ); + } + } +} + +/* +========================= +idGuiScript::ReadFromSaveGame +========================= +*/ +void idGuiScript::ReadFromSaveGame( idFile *savefile ) { + int i; + + if ( ifList ) { + ifList->ReadFromSaveGame( savefile ); + } + if ( elseList ) { + elseList->ReadFromSaveGame( savefile ); + } + + savefile->Read( &conditionReg, sizeof( conditionReg ) ); + + for ( i = 0; i < parms.Num(); i++ ) { + if ( parms[i].own ) { + parms[i].var->ReadFromSaveGame( savefile ); + } + } +} + +/* +========================= +idGuiScript::Parse +========================= +*/ +bool idGuiScript::Parse(idParser *src) { + int i; + + // first token should be function call + // then a potentially variable set of parms + // ended with a ; + idToken token; + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + + handler = NULL; + + for ( i = 0; i < scriptCommandCount ; i++ ) { + if ( idStr::Icmp(token, commandList[i].name) == 0 ) { + handler = commandList[i].handler; + break; + } + } + + if (handler == NULL) { + src->Error("Uknown script call %s", token.c_str()); + } + // now read parms til ; + // all parms are read as idWinStr's but will be fixed up later + // to be proper types + while (1) { + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + + if (idStr::Icmp(token, ";") == 0) { + break; + } + + if (idStr::Icmp(token, "}") == 0) { + src->UnreadToken(&token); + break; + } + + idWinStr *str = new idWinStr(); + *str = token; + idGSWinVar wv; + wv.own = true; + wv.var = str; + parms.Append( wv ); + } + + // + // verify min/max params + if ( handler && (parms.Num() < commandList[i].mMinParms || parms.Num() > commandList[i].mMaxParms ) ) { + src->Error("incorrect number of parameters for script %s", commandList[i].name ); + } + // + + return true; +} + +/* +========================= +idGuiScriptList::Execute +========================= +*/ +void idGuiScriptList::Execute(idWindow *win) { + int c = list.Num(); + for (int i = 0; i < c; i++) { + idGuiScript *gs = list[i]; + assert(gs); + if (gs->conditionReg >= 0) { + if (win->HasOps()) { + float f = win->EvalRegs(gs->conditionReg); + if (f) { + if (gs->ifList) { + win->RunScriptList(gs->ifList); + } + } else if (gs->elseList) { + win->RunScriptList(gs->elseList); + } + } + } + gs->Execute(win); + } +} + +/* +========================= +idGuiScriptList::FixupParms +========================= +*/ +void idGuiScript::FixupParms(idWindow *win) { + if (handler == &Script_Set) { + bool precacheBackground = false; + bool precacheSounds = false; + idWinStr *str = dynamic_cast(parms[0].var); + assert(str); + idWinVar *dest = win->GetWinVarByName(*str, true); + // Quake 4 binds every valid winvar destination here. Restricting this + // to transition-compatible numeric types leaves bool/string/int set + // commands pointing at their temporary parser strings. + if ( dest ) { + delete parms[0].var; + parms[0].var = dest; + parms[0].own = false; + + if ( dynamic_cast(dest) != NULL ) { + precacheBackground = true; + } + } else if ( idStr::Icmp( str->c_str(), "cmd" ) == 0 ) { + precacheSounds = true; + } + int parmCount = parms.Num(); + for (int i = 1; i < parmCount; i++) { + idWinStr *str = dynamic_cast(parms[i].var); + if (idStr::Icmpn(*str, "gui::", 5) == 0) { + + // always use a string here, no point using a float if it is one + // FIXME: This creates duplicate variables, while not technically a problem since they + // are all bound to the same guiDict, it does consume extra memory and is generally a bad thing + idWinStr* defvar = new idWinStr(); + defvar->Init ( *str, win ); + win->AddDefinedVar ( defvar ); + delete parms[i].var; + parms[i].var = defvar; + parms[i].own = false; + + //dest = win->GetWinVarByName(*str, true); + //if (dest) { + // delete parms[i].var; + // parms[i].var = dest; + // parms[i].own = false; + //} + // + } else if ((*str[0]) == '$') { + // + // dont include the $ when asking for variable + dest = win->GetGui()->GetDesktop()->GetWinVarByName((const char*)(*str) + 1, true); + // + if (dest) { + delete parms[i].var; + parms[i].var = dest; + parms[i].own = false; + } + } else if ( idStr::Cmpn( str->c_str(), STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { + str->Set( common->GetLocalizedString( str->c_str() ) ); + } else if ( precacheBackground ) { + const idMaterial *mat = declManager->FindMaterial( str->c_str() ); + mat->SetSort( SS_GUI ); + } else if ( precacheSounds ) { + // Search for "play <...>" + idToken token; + idParser parser( LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + parser.LoadMemory(str->c_str(), str->Length(), "command"); + + while ( parser.ReadToken(&token) ) { + if ( token.Icmp("play") == 0 ) { + if ( parser.ReadToken(&token) && ( token != "" ) ) { + declManager->FindSound( token.c_str() ); + } + } + } + } + } + } else if (handler == &Script_Transition) { + if (parms.Num() < 4) { + common->Warning("Window %s in gui %s has a bad transition definition", win->GetName(), win->GetGui()->GetSourceFile()); + } + idWinStr *str = dynamic_cast(parms[0].var); + assert(str); + + // + drawWin_t *destowner; + idWinVar *dest = win->GetWinVarByName(*str, true, &destowner ); + // + + if (dest) { + delete parms[0].var; + parms[0].var = dest; + parms[0].own = false; + } else { + common->Warning("Window %s in gui %s: a transition does not have a valid destination var %s", win->GetName(), win->GetGui()->GetSourceFile(),str->c_str()); + handler = NULL; + return; + } + + // + // support variables as parameters + int c; + for ( c = 1; c < 3; c ++ ) { + str = dynamic_cast(parms[c].var); + + idWinVec4 *v4 = new idWinVec4; + parms[c].var = v4; + parms[c].own = true; + + drawWin_t* owner; + + if ( (*str[0]) == '$' ) { + dest = win->GetWinVarByName ( (const char*)(*str) + 1, true, &owner ); + } else { + dest = NULL; + } + + if ( dest ) { + idWindow* ownerparent; + idWindow* destparent; + if ( owner ) { + ownerparent = owner->simp?owner->simp->GetParent():owner->win->GetParent(); + destparent = destowner->simp?destowner->simp->GetParent():destowner->win->GetParent(); + + // If its the rectangle they are referencing then adjust it + if ( ownerparent && destparent && + (dest == (owner->simp?owner->simp->GetWinVarByName ( "rect" ):owner->win->GetWinVarByName ( "rect" ) ) ) ) + { + idRectangle rect; + rect = *(dynamic_cast(dest)); + ownerparent->ClientToScreen ( &rect ); + destparent->ScreenToClient ( &rect ); + *v4 = rect.ToVec4 ( ); + } else { + v4->Set ( dest->c_str ( ) ); + } + } else { + v4->Set ( dest->c_str ( ) ); + } + } else { + v4->Set(*str); + } + + delete str; + } + // + + } else { + int c = parms.Num(); + for (int i = 0; i < c; i++) { + parms[i].var->Init(parms[i].var->c_str(), win); + } + } +} + +/* +========================= +idGuiScriptList::FixupParms +========================= +*/ +void idGuiScriptList::FixupParms(idWindow *win) { + int c = list.Num(); + for (int i = 0; i < c; i++) { + idGuiScript *gs = list[i]; + gs->FixupParms(win); + if (gs->ifList) { + gs->ifList->FixupParms(win); + } + if (gs->elseList) { + gs->elseList->FixupParms(win); + } + } +} + +/* +========================= +idGuiScriptList::WriteToSaveGame +========================= +*/ +void idGuiScriptList::WriteToSaveGame( idFile *savefile ) { + int i; + + for ( i = 0; i < list.Num(); i++ ) { + list[i]->WriteToSaveGame( savefile ); + } +} + +/* +========================= +idGuiScriptList::ReadFromSaveGame +========================= +*/ +void idGuiScriptList::ReadFromSaveGame( idFile *savefile ) { + int i; + + for ( i = 0; i < list.Num(); i++ ) { + list[i]->ReadFromSaveGame( savefile ); + } +} diff --git a/src/ui/listgui.cpp b/src/ui/listgui.cpp new file mode 100644 index 0000000..083e297 --- /dev/null +++ b/src/ui/listgui.cpp @@ -0,0 +1,197 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "ListGUILocal.h" + +/* +==================== +idListGUILocal::StateChanged +==================== +*/ +void idListGUILocal::StateChanged() { + int i; + + if ( !m_stateUpdates ) { + return; + } + + const int count = Num(); + if ( count > 0 && m_pGUI->GetStateInt( va( "%s_sel_0", m_name.c_str() ), "0" ) >= count ) { + m_pGUI->SetStateInt( va( "%s_sel_0", m_name.c_str() ), count - 1 ); + } + + for( i = 0; i < count; i++ ) { + m_pGUI->SetStateString( va( "%s_item_%i", m_name.c_str(), i ), (*this)[i].c_str() ); + } + for( i = count; i < m_water; i++ ) { + m_pGUI->SetStateString( va( "%s_item_%i", m_name.c_str(), i ), "" ); + } + for ( i = 0; i < count; i++ ) { + const char *greyedName = va( "%s_item_%i_greyed", m_name.c_str(), i ); + if ( m_greyed[i] ) { + m_pGUI->SetStateBool( greyedName, true ); + } else { + m_pGUI->DeleteStateVar( greyedName ); + } + } + m_water = count; + m_pGUI->StateChanged( com_frameTime ); +} + +/* +==================== +idListGUILocal::GetNumSelections +==================== +*/ +int idListGUILocal::GetNumSelections() { + return m_pGUI->State().GetInt( va( "%s_numsel", m_name.c_str() ) ); +} + +/* +==================== +idListGUILocal::GetSelection +==================== +*/ +int idListGUILocal::GetSelection( char *s, int size, int _sel ) const { + if ( s ) { + s[ 0 ] = '\0'; + } + int sel = m_pGUI->State().GetInt( va( "%s_sel_%i", m_name.c_str(), _sel ), "-1" ); + if ( sel == -1 || sel >= m_ids.Num() ) { + return -1; + } + if ( s ) { + idStr::snPrintf( s, size, m_pGUI->State().GetString( va( "%s_item_%i", m_name.c_str(), sel ), "" ) ); + } + // don't let overflow + if ( sel >= m_ids.Num() ) { + sel = 0; + } + m_pGUI->SetStateInt( va( "%s_selid_0", m_name.c_str() ), m_ids[ sel ] ); + return m_ids[ sel ]; +} + +/* +==================== +idListGUILocal::SetSelection +==================== +*/ +void idListGUILocal::SetSelection( int sel ) { + m_pGUI->SetStateInt( va( "%s_sel_0", m_name.c_str() ), sel ); + StateChanged(); +} + +/* +==================== +idListGUILocal::Add +==================== +*/ +void idListGUILocal::Add( int id, const idStr &s, bool greyed ) { + int i = m_ids.FindIndex( id ); + if ( i == -1 ) { + Append( s ); + m_ids.Append( id ); + m_greyed.Append( greyed ); + } else { + (*this)[ i ] = s; + } + StateChanged(); +} + +/* +==================== +idListGUILocal::Push +==================== +*/ +void idListGUILocal::Push( const idStr& s, bool greyed ) { + Append( s ); + m_ids.Append( m_ids.Num() ); + m_greyed.Append( greyed ); + StateChanged(); +} + +/* +==================== +idListGUILocal::Del +==================== +*/ +bool idListGUILocal::Del(int id) { + int i = m_ids.FindIndex(id); + if ( i == -1 ) { + return false; + } + m_ids.RemoveIndex( i ); + m_greyed.RemoveIndex( i ); + this->RemoveIndex( i ); + StateChanged(); + return true; +} + +/* +==================== +idListGUILocal::Clear +==================== +*/ +void idListGUILocal::Clear() { + m_ids.Clear(); + m_greyed.Clear(); + idList::Clear(); + if ( m_pGUI ) { + // will clear all the GUI variables and will set m_water back to 0 + StateChanged(); + } +} + +/* +==================== +idListGUILocal::IsConfigured +==================== +*/ +bool idListGUILocal::IsConfigured( void ) const { + return m_pGUI != NULL; +} + +/* +==================== +idListGUILocal::SetStateChanges +==================== +*/ +void idListGUILocal::SetStateChanges( bool enable ) { + m_stateUpdates = enable; + StateChanged(); +} + +/* +==================== +idListGUILocal::Shutdown +==================== +*/ +void idListGUILocal::Shutdown( void ) { + m_pGUI = NULL; + m_name.Clear(); + Clear(); +} diff --git a/src/ui/listwindow.cpp b/src/ui/listwindow.cpp new file mode 100644 index 0000000..2dcfac8 --- /dev/null +++ b/src/ui/listwindow.cpp @@ -0,0 +1,644 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SliderWindow.h" +#include "ListWindow.h" + +// Number of pixels above the text that the rect starts +static const int pixelOffset = 3; + +// number of pixels between columns +static const int tabBorder = 4; + +// Time in milliseconds between clicks to register as a double-click +static const int doubleClickSpeed = 300; + +void idListWindow::CommonInit() { + typed = ""; + typedTime = 0; + clickTime = 0; + currentSel.Clear(); + top = 0; + sizeBias = 0; + horizontal = false; + scroller = new idSliderWindow(dc, gui); + multipleSel = false; +} + +idListWindow::idListWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idListWindow::idListWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +void idListWindow::SetCurrentSel( int sel ) { + currentSel.Clear(); + currentSel.Append( sel ); +} + +void idListWindow::ClearSelection( int sel ) { + int cur = currentSel.FindIndex( sel ); + if ( cur >= 0 ) { + currentSel.RemoveIndex( cur ); + } +} + +void idListWindow::AddCurrentSel( int sel ) { + currentSel.Append( sel ); +} + +int idListWindow::GetCurrentSel() { + return ( currentSel.Num() ) ? currentSel[0] : 0; +} + +bool idListWindow::IsSelected( int index ) { + return ( currentSel.FindIndex( index ) >= 0 ); +} + +const char *idListWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + // need to call this to allow proper focus and capturing on embedded children + const char *ret = idWindow::HandleEvent(event, updateVisuals); + + float vert = GetMaxCharHeight(); + int numVisibleLines = textRect.h / vert; + + int key = event->evValue; + + if ( event->evType == SE_KEY ) { + if ( !event->evValue2 ) { + // We only care about key down, not up + return ret; + } + + if ( key == K_MOUSE1 || key == K_MOUSE2 ) { + // If the user clicked in the scroller, then ignore it + if ( scroller->Contains(gui->CursorX(), gui->CursorY()) ) { + return ret; + } + } + + if ( ( key == K_ENTER || key == K_KP_ENTER ) ) { + RunScript( ON_ENTER ); + return cmd; + } + + if ( key == K_MWHEELUP ) { + key = K_UPARROW; + } else if ( key == K_MWHEELDOWN ) { + key = K_DOWNARROW; + } + + if ( key == K_MOUSE1) { + if (Contains(gui->CursorX(), gui->CursorY())) { + int cur = ( int )( ( gui->CursorY() - actualY - pixelOffset ) / vert ) + top; + if ( cur >= 0 && cur < listItems.Num() ) { + if ( multipleSel && idKeyInput::IsDown( K_CTRL ) ) { + if ( IsSelected( cur ) ) { + ClearSelection( cur ); + } else { + AddCurrentSel( cur ); + } + } else { + if ( IsSelected( cur ) && ( gui->GetTime() < clickTime + doubleClickSpeed ) ) { + // Double-click causes ON_ENTER to get run + RunScript(ON_ENTER); + return cmd; + } + SetCurrentSel( cur ); + + clickTime = gui->GetTime(); + } + } else { + SetCurrentSel( listItems.Num() - 1 ); + } + } + } else if ( key == K_UPARROW || key == K_PGUP || key == K_DOWNARROW || key == K_PGDN ) { + int numLines = 1; + + if ( key == K_PGUP || key == K_PGDN ) { + numLines = numVisibleLines / 2; + } + + if ( key == K_UPARROW || key == K_PGUP ) { + numLines = -numLines; + } + + if ( idKeyInput::IsDown( K_CTRL ) ) { + top += numLines; + } else { + SetCurrentSel( GetCurrentSel() + numLines ); + } + } else { + return ret; + } + } else if ( event->evType == SE_CHAR ) { + if ( !idStr::CharIsPrintable(key) ) { + return ret; + } + + if ( gui->GetTime() > typedTime + 1000 ) { + typed = ""; + } + typedTime = gui->GetTime(); + typed.Append( key ); + + for ( int i=0; i= listItems.Num() ) { + SetCurrentSel( listItems.Num() - 1 ); + } + + if ( scroller->GetHigh() > 0.0f ) { + if ( !idKeyInput::IsDown( K_CTRL ) ) { + if ( top > GetCurrentSel() - 1 ) { + top = GetCurrentSel() - 1; + } + if ( top < GetCurrentSel() - numVisibleLines + 2 ) { + top = GetCurrentSel() - numVisibleLines + 2; + } + } + + if ( top > listItems.Num() - 2 ) { + top = listItems.Num() - 2; + } + if ( top < 0 ) { + top = 0; + } + scroller->SetValue(top); + } else { + top = 0; + scroller->SetValue(0.0f); + } + + if ( key != K_MOUSE1 ) { + // Send a fake mouse click event so onAction gets run in our parents + const sysEvent_t ev = sys->GenerateMouseButtonEvent( 1, true ); + idWindow::HandleEvent(&ev, updateVisuals); + } + + if ( currentSel.Num() > 0 ) { + for ( int i = 0; i < currentSel.Num(); i++ ) { + gui->SetStateInt( va( "%s_sel_%i", listName.c_str(), i ), currentSel[i] ); + } + } else { + gui->SetStateInt( va( "%s_sel_0", listName.c_str() ), 0 ); + } + gui->SetStateInt( va( "%s_numsel", listName.c_str() ), currentSel.Num() ); + + return ret; +} + + +bool idListWindow::ParseInternalVar(const char *_name, idParser *src) { + if (idStr::Icmp(_name, "horizontal") == 0) { + horizontal = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "listname") == 0) { + ParseString(src, listName); + return true; + } + if (idStr::Icmp(_name, "tabstops") == 0) { + ParseString(src, tabStopStr); + return true; + } + if (idStr::Icmp(_name, "tabaligns") == 0) { + ParseString(src, tabAlignStr); + return true; + } + if (idStr::Icmp(_name, "multipleSel") == 0) { + multipleSel = src->ParseBool(); + return true; + } + if(idStr::Icmp(_name, "tabvaligns") == 0) { + ParseString(src, tabVAlignStr); + return true; + } + if(idStr::Icmp(_name, "tabTypes") == 0) { + ParseString(src, tabTypeStr); + return true; + } + if(idStr::Icmp(_name, "tabIconSizes") == 0) { + ParseString(src, tabIconSizeStr); + return true; + } + if(idStr::Icmp(_name, "tabIconVOffset") == 0) { + ParseString(src, tabIconVOffsetStr); + return true; + } + if(idStr::Icmp(_name, "tabTextScales") == 0) { + ParseString(src, tabTextScalesStr); + return true; + } + + idStr strName = _name; + if(idStr::Icmp(strName.Left(4), "mtr_") == 0) { + idStr matName; + const idMaterial* mat; + + ParseString(src, matName); + mat = declManager->FindMaterial(matName); + mat->SetImageClassifications( 1 ); // just for resource tracking + if ( mat && !mat->TestMaterialFlag( MF_DEFAULTED ) ) { + mat->SetSort(SS_GUI ); + } + iconMaterials.Set(_name, mat); + return true; + } + + return idWindow::ParseInternalVar(_name, src); +} + +idWinVar *idListWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + return idWindow::GetWinVarByName(_name, fixup, owner); +} + +void idListWindow::PostParse() { + idWindow::PostParse(); + + InitScroller(horizontal); + + idList tabStops; + idList tabAligns; + if (tabStopStr.Length()) { + idParser src(tabStopStr, tabStopStr.Length(), "tabstops", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabStops.Append(atoi(tok)); + } + } + if (tabAlignStr.Length()) { + idParser src(tabAlignStr, tabAlignStr.Length(), "tabaligns", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabAligns.Append(atoi(tok)); + } + } + idList tabVAligns; + if (tabVAlignStr.Length()) { + idParser src(tabVAlignStr, tabVAlignStr.Length(), "tabvaligns", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabVAligns.Append(atoi(tok)); + } + } + + idList tabTypes; + if (tabTypeStr.Length()) { + idParser src(tabTypeStr, tabTypeStr.Length(), "tabtypes", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabTypes.Append(atoi(tok)); + } + } + idList tabSizes; + if (tabIconSizeStr.Length()) { + idParser src(tabIconSizeStr, tabIconSizeStr.Length(), "tabiconsizes", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + idVec2 size; + size.x = atoi(tok); + + src.ReadToken(&tok); //"," + src.ReadToken(&tok); + + size.y = atoi(tok); + tabSizes.Append(size); + } + } + + idList tabIconVOffsets; + if (tabIconVOffsetStr.Length()) { + idParser src(tabIconVOffsetStr, tabIconVOffsetStr.Length(), "tabiconvoffsets", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabIconVOffsets.Append(atof(tok)); + } + } + + idList tabTextScales; + if (tabTextScalesStr.Length()) { + idParser src(tabTextScalesStr, tabTextScalesStr.Length(), "tabtextscales", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); + idToken tok; + while (src.ReadToken(&tok)) { + if (tok == ",") { + continue; + } + tabTextScales.Append(atof(tok)); + } + } + + int c = tabStops.Num(); + bool doAligns = (tabAligns.Num() == tabStops.Num()); + for (int i = 0; i < c; i++) { + idTabRect r; + r.x = tabStops[i]; + r.w = (i < c - 1) ? tabStops[i+1] - r.x - tabBorder : -1; + r.align = (doAligns) ? tabAligns[i] : 0; + if(tabVAligns.Num() > i) { + r.valign = tabVAligns[i]; + } else { + r.valign = 0; + } + if(tabTypes.Num() > i) { + r.type = tabTypes[i]; + } else { + r.type = TAB_TYPE_TEXT; + } + if(tabSizes.Num() > i) { + r.iconSize = tabSizes[i]; + } else { + r.iconSize.Zero(); + } + if(tabIconVOffsets.Num() > i ) { + r.iconVOffset = tabIconVOffsets[i]; + } else { + r.iconVOffset = 0; + } + if(tabTextScales.Num() > i) { + r.textScale = tabTextScales[i]; + } else { + r.textScale = textScale; + } + tabInfo.Append(r); + } + flags |= WIN_CANFOCUS; +} + +/* +================ +idListWindow::InitScroller + +This is the same as in idEditWindow +================ +*/ +void idListWindow::InitScroller( bool horizontal ) +{ + const char *thumbImage = "gfx/guis/scrollbar_thumb.tga"; + const char *barImage = "gfx/guis/scrollbarv.tga"; + const char *scrollerName = "_scrollerWinV"; + + if (horizontal) { + barImage = "gfx/guis/scrollbarh.tga"; + scrollerName = "_scrollerWinH"; + } + + const idMaterial *mat = declManager->FindMaterial( barImage ); + mat->SetSort( SS_GUI ); + sizeBias = mat->GetImageWidth(); + + idRectangle scrollRect; + if (horizontal) { + sizeBias = mat->GetImageHeight(); + scrollRect.x = 0; + scrollRect.y = (clientRect.h - sizeBias); + scrollRect.w = clientRect.w; + scrollRect.h = sizeBias; + } else { + scrollRect.x = (clientRect.w - sizeBias); + scrollRect.y = 0; + scrollRect.w = sizeBias; + scrollRect.h = clientRect.h; + } + + scroller->InitWithDefaults(scrollerName, scrollRect, foreColor, matColor, mat->GetName(), thumbImage, !horizontal, true); + InsertChild(scroller, NULL); + scroller->SetBuddy(this); +} + +void idListWindow::Draw(int time, float x, float y) { + idVec4 color; + idStr work; + int count = listItems.Num(); + idRectangle rect = textRect; + float scale = textScale; + float lineHeight = GetMaxCharHeight(); + + float bottom = textRect.Bottom(); + float width = textRect.w; + + if ( scroller->GetHigh() > 0.0f ) { + if ( horizontal ) { + bottom -= sizeBias; + } else { + width -= sizeBias; + rect.w = width; + } + } + + if ( noEvents || !Contains(gui->CursorX(), gui->CursorY()) ) { + hover = false; + } + + for (int i = top; i < count; i++) { + if ( IsSelected( i ) ) { + rect.h = lineHeight; + dc->DrawFilledRect(rect.x, rect.y + pixelOffset, rect.w, rect.h, borderColor); + if ( flags & WIN_FOCUS ) { + idVec4 color = borderColor; + color.w = 1.0f; + dc->DrawRect(rect.x, rect.y + pixelOffset, rect.w, rect.h, 1.0f, color ); + } + } + rect.y ++; + rect.h = lineHeight - 1; + if ( hover && !noEvents && Contains(rect, gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + color = foreColor; + } + rect.h = lineHeight + pixelOffset; + rect.y --; + + if ( tabInfo.Num() > 0 ) { + int start = 0; + int tab = 0; + int stop = listItems[i].Find('\t', 0); + while ( start < listItems[i].Length() ) { + if ( tab >= tabInfo.Num() ) { + common->Warning( "idListWindow::Draw: gui '%s' window '%s' tabInfo.Num() exceeded", gui->GetSourceFile(), name.c_str() ); + break; + } + listItems[i].Mid(start, stop - start, work); + + rect.x = textRect.x + tabInfo[tab].x; + rect.w = (tabInfo[tab].w == -1) ? width - tabInfo[tab].x : tabInfo[tab].w; + dc->PushClipRect( rect ); + + if ( tabInfo[tab].type == TAB_TYPE_TEXT ) { + dc->DrawText(work, tabInfo[tab].textScale, tabInfo[tab].align, color, rect, false, -1); + } else if (tabInfo[tab].type == TAB_TYPE_ICON) { + + const idMaterial **hashMat; + const idMaterial *iconMat; + + // leaving the icon name empty doesn't draw anything + if ( work[0] != '\0' ) { + + if ( iconMaterials.Get(work, &hashMat) == false ) { + iconMat = declManager->FindMaterial("_default"); + } else { + iconMat = *hashMat; + } + + idRectangle iconRect; + iconRect.w = tabInfo[tab].iconSize.x; + iconRect.h = tabInfo[tab].iconSize.y; + + if(tabInfo[tab].align == idDeviceContext::ALIGN_LEFT) { + iconRect.x = rect.x; + } else if (tabInfo[tab].align == idDeviceContext::ALIGN_CENTER) { + iconRect.x = rect.x + rect.w/2.0f - iconRect.w/2.0f; + } else if (tabInfo[tab].align == idDeviceContext::ALIGN_RIGHT) { + iconRect.x = rect.x + rect.w - iconRect.w; + } + + if(tabInfo[tab].valign == 0) { //Top + iconRect.y = rect.y + tabInfo[tab].iconVOffset; + } else if(tabInfo[tab].valign == 1) { //Center + iconRect.y = rect.y + rect.h/2.0f - iconRect.h/2.0f + tabInfo[tab].iconVOffset; + } else if(tabInfo[tab].valign == 2) { //Bottom + iconRect.y = rect.y + rect.h - iconRect.h + tabInfo[tab].iconVOffset; + } + + dc->DrawMaterial(iconRect.x, iconRect.y, iconRect.w, iconRect.h, iconMat, idVec4(1.0f,1.0f,1.0f,1.0f), 1.0f, 1.0f); + + } + } + + dc->PopClipRect(); + + start = stop + 1; + stop = listItems[i].Find('\t', start); + if ( stop < 0 ) { + stop = listItems[i].Length(); + } + tab++; + } + rect.x = textRect.x; + rect.w = width; + } else { + dc->DrawText(listItems[i], scale, 0, color, rect, false, -1); + } + rect.y += lineHeight; + if ( rect.y > bottom ) { + break; + } + } +} + +void idListWindow::Activate(bool activate, idStr &act) { + idWindow::Activate(activate, act); + + if ( activate ) { + UpdateList(); + } +} + +void idListWindow::HandleBuddyUpdate(idWindow *buddy) { + top = scroller->GetValue(); +} + +void idListWindow::UpdateList() { + idStr str, strName; + listItems.Clear(); + for (int i = 0; i < MAX_LIST_ITEMS; i++) { + if (gui->State().GetString( va("%s_item_%i", listName.c_str(), i), "", str) ) { + if ( str.Length() ) { + listItems.Append(str); + } + } else { + break; + } + } + float vert = GetMaxCharHeight(); + int fit = textRect.h / vert; + if ( listItems.Num() < fit ) { + scroller->SetRange(0.0f, 0.0f, 1.0f); + } else { + scroller->SetRange(0.0f, (listItems.Num() - fit) + 1.0f, 1.0f); + } + + SetCurrentSel( gui->State().GetInt( va( "%s_sel_0", listName.c_str() ) ) ); + + float value = scroller->GetValue(); + if ( value > listItems.Num() - 1 ) { + value = listItems.Num() - 1; + } + if ( value < 0.0f ) { + value = 0.0f; + } + scroller->SetValue(value); + top = value; + + typedTime = 0; + clickTime = 0; + typed = ""; +} + +void idListWindow::StateChanged( bool redraw ) { + UpdateList(); +} diff --git a/src/ui/markerwindow.cpp b/src/ui/markerwindow.cpp new file mode 100644 index 0000000..445122a --- /dev/null +++ b/src/ui/markerwindow.cpp @@ -0,0 +1,355 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +// included for image uploading for player stat graph +#include "../renderer/Image.h" + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "MarkerWindow.h" + +class idImage; +void idMarkerWindow::CommonInit() { + numStats = 0; + currentTime = -1; + currentMarker = -1; + stopTime = -1; + imageBuff = NULL; + markerMat = NULL; + markerStop = NULL; +} + +idMarkerWindow::idMarkerWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idMarkerWindow::idMarkerWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idMarkerWindow::~idMarkerWindow() { +} + +bool idMarkerWindow::ParseInternalVar(const char *_name, idParser *src) { + if (idStr::Icmp(_name, "markerMat") == 0) { + idStr str; + ParseString(src, str); + markerMat = declManager->FindMaterial(str); + markerMat->SetSort( SS_GUI ); + return true; + } + if (idStr::Icmp(_name, "markerStop") == 0) { + idStr str; + ParseString(src, str); + markerStop = declManager->FindMaterial(str); + markerStop->SetSort( SS_GUI ); + return true; + } + if (idStr::Icmp(_name, "markerColor") == 0) { + ParseVec4(src, markerColor); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} + +idWinVar *idMarkerWindow::GetWinVarByName(const char *_name, bool fixup) { + return idWindow::GetWinVarByName(_name, fixup); +} + +const char *idMarkerWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + + if (!(event->evType == SE_KEY && event->evValue2)) { + return ""; + } + + int key = event->evValue; + if (event->evValue2 && key == K_MOUSE1) { + gui->GetDesktop()->SetChildWinVarVal("markerText", "text", ""); + idRectangle r; + int c = markerTimes.Num(); + int i; + for (i = 0; i < c; i++) { + markerData_t &md = markerTimes[i]; + if (md.rect.Contains(gui->CursorX(), gui->CursorY())) { + currentMarker = i; + gui->SetStateInt( "currentMarker", md.time ); + stopTime = md.time; + gui->GetDesktop()->SetChildWinVarVal("markerText", "text", va("Marker set at %.2i:%.2i", md.time / 60 / 60, (md.time / 60) % 60)); + gui->GetDesktop()->SetChildWinVarVal("markerText", "visible", "1"); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "matcolor", "1 1 1 1"); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "text", ""); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "background", md.mat->GetName()); + break; + } + } + if ( i == c ) { + // no marker selected; + currentMarker = -1; + gui->SetStateInt( "currentMarker", currentTime ); + stopTime = currentTime; + gui->GetDesktop()->SetChildWinVarVal("markerText", "text", va("Marker set at %.2i:%.2i", currentTime / 60 / 60, (currentTime / 60) % 60)); + gui->GetDesktop()->SetChildWinVarVal("markerText", "visible", "1"); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "matcolor", "0 0 0 0"); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "text", "No Preview"); + } + float pct = gui->State().GetFloat( "loadPct" ); + int len = gui->State().GetInt( "loadLength" ); + if (stopTime > len * pct) { + return "cmdDemoGotoMarker"; + } + } else if (key == K_MOUSE2) { + stopTime = -1; + gui->GetDesktop()->SetChildWinVarVal("markerText", "text", ""); + gui->SetStateInt( "currentMarker", -1 ); + return "cmdDemoGotoMarker"; + } else if (key == K_SPACE) { + return "cmdDemoPauseFrame"; + } + + return ""; +} + +void idMarkerWindow::PostParse() { + idWindow::PostParse(); +} + +static const int HEALTH_MAX = 100; +static const int COMBAT_MAX = 100; +static const int RATE_MAX = 125; +static const int STAMINA_MAX = 12; +void idMarkerWindow::Draw(int time, float x, float y) { + float pct; + idRectangle r = clientRect; + int len = gui->State().GetInt( "loadLength" ); + if (len == 0) { + len = 1; + } + if (numStats > 1) { + int c = markerTimes.Num(); + if (c > 0) { + for (int i = 0; i < c; i++) { + markerData_t &md = markerTimes[i]; + if (md.rect.w == 0) { + md.rect.x = (r.x + r.w * ((float)md.time / len)) - 8; + md.rect.y = r.y + r.h - 20; + md.rect.w = 16; + md.rect.h = 16; + } + dc->DrawMaterial(md.rect.x, md.rect.y, md.rect.w, md.rect.h, markerMat, markerColor); + } + } + } + + r.y += 10; + if (r.w > 0 && r.Contains(gui->CursorX(), gui->CursorY())) { + pct = (gui->CursorX() - r.x) / r.w; + currentTime = len * pct; + r.x = (gui->CursorX() > r.x + r.w - 40) ? gui->CursorX() - 40 : gui->CursorX(); + r.y = gui->CursorY() - 15; + r.w = 40; + r.h = 20; + dc->DrawText(va("%.2i:%.2i", currentTime / 60 / 60, (currentTime / 60) % 60), 0.25, 0, idDeviceContext::colorWhite, r, false); + } + + if (stopTime >= 0 && markerStop) { + r = clientRect; + r.y += (r.h - 32) / 2; + pct = (float)stopTime / len; + r.x += (r.w * pct) - 16; + idVec4 color(1, 1, 1, 0.65f); + dc->DrawMaterial(r.x, r.y, 32, 32, markerStop, color); + } + +} + + + +const char *idMarkerWindow::RouteMouseCoords(float xd, float yd) { + const char * ret = idWindow::RouteMouseCoords(xd, yd); + idRectangle r; + int i, c = markerTimes.Num(); + int len = gui->State().GetInt( "loadLength" ); + if (len == 0) { + len = 1; + } + for (i = 0; i < c; i++) { + markerData_t &md = markerTimes[i]; + if (md.rect.Contains(gui->CursorY(), gui->CursorX())) { + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "background", md.mat->GetName()); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "matcolor", "1 1 1 1"); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "text", ""); + break; + } + } + + if (i >= c) { + if (currentMarker == -1) { + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "matcolor", "0 0 0 0"); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "text", "No Preview"); + } else { + markerData_t &md = markerTimes[currentMarker]; + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "background", md.mat->GetName()); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "matcolor", "1 1 1 1"); + gui->GetDesktop()->SetChildWinVarVal("markerBackground", "text", ""); + } + } + return ret; +} + +void idMarkerWindow::Point(int x, int y, dword *out, dword color) { + int index = (63-y) * 512 + x; + if (index >= 0 && index < 512 * 64) { + out[index] = color; + } else { + common->Warning("Out of bounds on point %i : %i", x, y); + } +} + +void idMarkerWindow::Line(int x1, int y1, int x2, int y2, dword* out, dword color) { + int deltax = abs(x2 - x1); + int deltay = abs(y2 - y1); + int incx = (x1 > x2) ? -1 : 1; + int incy = (y1 > y2) ? -1 : 1; + int right, up, dir; + if (deltax > deltay) { + right = deltay * 2; + up = right - deltax * 2; + dir = right - deltax; + while (deltax-- >= 0) { + Point(x1, y1, out, color); + x1 += incx; + y1 += (dir > 0) ? incy : 0; + dir += (dir > 0) ? up : right; + } + } else { + right = deltax * 2; + up = right - deltay * 2; + dir = right - deltay; + while ( deltay-- >= 0) { + Point(x1, y1, out, color); + x1 += (dir > 0) ? incx : 0; + y1 += incy; + dir += (dir > 0) ? up : right; + } + } +} + + +void idMarkerWindow::Activate(bool activate, idStr &act) { + idWindow::Activate(activate, act); + if (activate) { + int i; + gui->GetDesktop()->SetChildWinVarVal("markerText", "text", ""); + imageBuff = (dword*)Mem_Alloc(512*64*4); + markerTimes.Clear(); + currentMarker = -1; + currentTime = -1; + stopTime = -1; + statData = gui->State().GetString( "statData" ); + numStats = 0; + if (statData.Length()) { + idFile *file = fileSystem->OpenFileRead(statData); + if (file) { + file->Read(&numStats, sizeof(numStats)); + file->Read(loggedStats, numStats * sizeof(loggedStats[0])); + for (i = 0; i < numStats; i++) { + if (loggedStats[i].health < 0) { + loggedStats[i].health = 0; + } + if (loggedStats[i].stamina < 0) { + loggedStats[i].stamina = 0; + } + if (loggedStats[i].heartRate < 0) { + loggedStats[i].heartRate = 0; + } + if (loggedStats[i].combat < 0) { + loggedStats[i].combat = 0; + } + } + fileSystem->CloseFile(file); + } + } + + if (numStats > 1 && background) { + idStr markerPath = statData; + markerPath.StripFilename(); + idFileList *markers; + markers = fileSystem->ListFiles( markerPath, ".tga", false, true ); + idStr name; + for ( i = 0; i < markers->GetNumFiles(); i++ ) { + name = markers->GetFile( i ); + markerData_t md; + md.mat = declManager->FindMaterial( name ); + md.mat->SetSort( SS_GUI ); + name.StripPath(); + name.StripFileExtension(); + md.time = atoi(name); + markerTimes.Append(md); + } + fileSystem->FreeFileList( markers ); + memset(imageBuff, 0, 512*64*4); + float step = 511.0f / (numStats - 1); + float startX = 0; + float x1, y1, x2, y2; + x1 = 0 - step; + for (i = 0; i < numStats-1; i++) { + x1 += step; + x2 = x1 + step; + y1 = 63 * ((float)loggedStats[i].health / HEALTH_MAX); + y2 = 63 * ((float)loggedStats[i+1].health / HEALTH_MAX); + Line(x1, y1, x2, y2, imageBuff, 0xff0000ff); + y1 = 63 * ((float)loggedStats[i].heartRate / RATE_MAX); + y2 = 63 * ((float)loggedStats[i+1].heartRate / RATE_MAX); + Line(x1, y1, x2, y2, imageBuff, 0xff00ff00); + // stamina not quite as high on graph so health does not get obscured with both at 100% + y1 = 62 * ((float)loggedStats[i].stamina / STAMINA_MAX); + y2 = 62 * ((float)loggedStats[i+1].stamina / STAMINA_MAX); + Line(x1, y1, x2, y2, imageBuff, 0xffff0000); + y1 = 63 * ((float)loggedStats[i].combat / COMBAT_MAX); + y2 = 63 * ((float)loggedStats[i+1].combat / COMBAT_MAX); + Line(x1, y1, x2, y2, imageBuff, 0xff00ffff); + } + const shaderStage_t *stage = background->GetStage(0); + if (stage) { + stage->texture.image->UploadScratch((byte*)imageBuff, 512, 64); + } + Mem_Free(imageBuff); + } + } +} + +void idMarkerWindow::MouseExit() { + idWindow::MouseExit(); +} + +void idMarkerWindow::MouseEnter() { + idWindow::MouseEnter(); +} diff --git a/src/ui/regexp.cpp b/src/ui/regexp.cpp new file mode 100644 index 0000000..54f6651 --- /dev/null +++ b/src/ui/regexp.cpp @@ -0,0 +1,398 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "RegExp.h" +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" + +int idRegister::REGCOUNT[NUMTYPES] = {4, 1, 1, 1, 0, 2, 3, 4}; + +/* +==================== +idRegister::SetToRegs +==================== +*/ +void idRegister::SetToRegs( float *registers ) { + int i; + idVec4 v; + idVec2 v2; + idVec3 v3; + idRectangle rect; + + if ( !enabled || var == NULL || ( var && ( var->GetDict() || !var->GetEval() ) ) ) { + return; + } + + switch( type ) { + case VEC4: { + v = *static_cast(var); + break; + } + case RECTANGLE: { + rect = *static_cast(var); + v = rect.ToVec4(); + break; + } + case VEC2: { + v2 = *static_cast(var); + v[0] = v2[0]; + v[1] = v2[1]; + break; + } + case VEC3: { + v3 = *static_cast(var); + v[0] = v3[0]; + v[1] = v3[1]; + v[2] = v3[2]; + break; + } + case FLOAT: { + v[0] = *static_cast(var); + break; + } + case INT: { + v[0] = *static_cast(var); + break; + } + case BOOL: { + v[0] = *static_cast(var); + break; + } + default: { + common->FatalError( "idRegister::SetToRegs: bad reg type" ); + break; + } + } + for ( i = 0; i < regCount; i++ ) { + registers[ regs[ i ] ] = v[i]; + } +} + +/* +================= +idRegister::GetFromRegs +================= +*/ +void idRegister::GetFromRegs( float *registers ) { + idVec4 v; + idRectangle rect; + + if (!enabled || var == NULL || (var && (var->GetDict() || !var->GetEval()))) { + return; + } + + for ( int i = 0; i < regCount; i++ ) { + v[i] = registers[regs[i]]; + } + + switch( type ) { + case VEC4: { + *dynamic_cast(var) = v; + break; + } + case RECTANGLE: { + rect.x = v.x; + rect.y = v.y; + rect.w = v.z; + rect.h = v.w; + *static_cast(var) = rect; + break; + } + case VEC2: { + *static_cast(var) = v.ToVec2(); + break; + } + case VEC3: { + *static_cast(var) = v.ToVec3(); + break; + } + case FLOAT: { + *static_cast(var) = v[0]; + break; + } + case INT: { + *static_cast(var) = v[0]; + break; + } + case BOOL: { + *static_cast(var) = ( v[0] != 0.0f ); + break; + } + default: { + common->FatalError( "idRegister::GetFromRegs: bad reg type" ); + break; + } + } +} + +/* +================= +idRegister::ReadFromDemoFile +================= +*/ +void idRegister::ReadFromDemoFile(idDemoFile *f) { + f->ReadBool( enabled ); + f->ReadShort( type ); + f->ReadInt( regCount ); + for ( int i = 0; i < 4; i++ ) + f->ReadUnsignedShort( regs[i] ); + name = f->ReadHashString(); +} + +/* +================= +idRegister::WriteToDemoFile +================= +*/ +void idRegister::WriteToDemoFile( idDemoFile *f ) { + f->WriteBool( enabled ); + f->WriteShort( type ); + f->WriteInt( regCount ); + for (int i = 0; i < 4; i++) + f->WriteUnsignedShort( regs[i] ); + f->WriteHashString( name ); +} + +/* +================= +idRegister::WriteToSaveGame +================= +*/ +void idRegister::WriteToSaveGame( idFile *savefile ) { + int len; + + savefile->Write( &enabled, sizeof( enabled ) ); + savefile->Write( &type, sizeof( type ) ); + savefile->Write( ®Count, sizeof( regCount ) ); + savefile->Write( ®s[0], sizeof( regs ) ); + + len = name.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( name.c_str(), len ); + + var->WriteToSaveGame( savefile ); +} + +/* +================ +idRegister::ReadFromSaveGame +================ +*/ +void idRegister::ReadFromSaveGame( idFile *savefile ) { + int len; + + savefile->Read( &enabled, sizeof( enabled ) ); + savefile->Read( &type, sizeof( type ) ); + savefile->Read( ®Count, sizeof( regCount ) ); + savefile->Read( ®s[0], sizeof( regs ) ); + + savefile->Read( &len, sizeof( len ) ); + name.Fill( ' ', len ); + savefile->Read( &name[0], len ); + + var->ReadFromSaveGame( savefile ); +} + +/* +==================== +idRegisterList::AddReg +==================== +*/ +void idRegisterList::AddReg( const char *name, int type, idVec4 data, idWindow *win, idWinVar *var ) { + if ( FindReg( name ) == NULL ) { + assert( type >= 0 && type < idRegister::NUMTYPES ); + int numRegs = idRegister::REGCOUNT[type]; + idRegister *reg = new idRegister( name, type ); + reg->var = var; + for ( int i = 0; i < numRegs; i++ ) { + reg->regs[i] = win->ExpressionConstant(data[i]); + } + int hash = regHash.GenerateKey( name, false ); + regHash.Add( hash, regs.Append( reg ) ); + } +} + +/* +==================== +idRegisterList::AddReg +==================== +*/ +void idRegisterList::AddReg( const char *name, int type, idParser *src, idWindow *win, idWinVar *var ) { + idRegister* reg; + + reg = FindReg( name ); + + if ( reg == NULL ) { + assert(type >= 0 && type < idRegister::NUMTYPES); + int numRegs = idRegister::REGCOUNT[type]; + reg = new idRegister( name, type ); + reg->var = var; + if ( type == idRegister::STRING ) { + idToken tok; + if ( src->ReadToken( &tok ) ) { + tok = common->GetLocalizedString( tok ); + var->Init( tok, win ); + } + } else { + for ( int i = 0; i < numRegs; i++ ) { + reg->regs[i] = win->ParseExpression(src, NULL); + if ( i < numRegs-1 ) { + src->ExpectTokenString(","); + } + } + } + int hash = regHash.GenerateKey( name, false ); + regHash.Add( hash, regs.Append( reg ) ); + } else { + int numRegs = idRegister::REGCOUNT[type]; + reg->var = var; + if ( type == idRegister::STRING ) { + idToken tok; + if ( src->ReadToken( &tok ) ) { + var->Init( tok, win ); + } + } else { + for ( int i = 0; i < numRegs; i++ ) { + reg->regs[i] = win->ParseExpression( src, NULL ); + if ( i < numRegs-1 ) { + src->ExpectTokenString(","); + } + } + } + } +} + +/* +==================== +idRegisterList::GetFromRegs +==================== +*/ +void idRegisterList::GetFromRegs(float *registers) { + for ( int i = 0; i < regs.Num(); i++ ) { + regs[i]->GetFromRegs( registers ); + } +} + +/* +==================== +idRegisterList::SetToRegs +==================== +*/ + +void idRegisterList::SetToRegs( float *registers ) { + int i; + for ( i = 0; i < regs.Num(); i++ ) { + regs[i]->SetToRegs( registers ); + } +} + +/* +==================== +idRegisterList::FindReg +==================== +*/ +idRegister *idRegisterList::FindReg( const char *name ) { + int hash = regHash.GenerateKey( name, false ); + for ( int i = regHash.First( hash ); i != -1; i = regHash.Next( i ) ) { + if ( regs[i]->name.Icmp( name ) == 0 ) { + return regs[i]; + } + } + return NULL; +} + +/* +==================== +idRegisterList::Reset +==================== +*/ +void idRegisterList::Reset() { + regs.DeleteContents( true ); + regHash.Clear(); +} + +/* +==================== +idRegisterList::ReadFromSaveGame +==================== +*/ +void idRegisterList::ReadFromDemoFile(idDemoFile *f) { + int c; + + f->ReadInt( c ); + regs.DeleteContents( true ); + for ( int i = 0; i < c; i++ ) { + idRegister *reg = new idRegister; + reg->ReadFromDemoFile( f ); + regs.Append( reg ); + } +} + +/* +==================== +idRegisterList::ReadFromSaveGame +==================== +*/ +void idRegisterList::WriteToDemoFile(idDemoFile *f) { + int c = regs.Num(); + + f->WriteInt( c ); + for ( int i = 0 ; i < c; i++ ) { + regs[i]->WriteToDemoFile(f); + } +} + +/* +===================== +idRegisterList::WriteToSaveGame +===================== +*/ +void idRegisterList::WriteToSaveGame( idFile *savefile ) { + int i, num; + + num = regs.Num(); + savefile->Write( &num, sizeof( num ) ); + + for ( i = 0; i < num; i++ ) { + regs[i]->WriteToSaveGame( savefile ); + } +} + +/* +==================== +idRegisterList::ReadFromSaveGame +==================== +*/ +void idRegisterList::ReadFromSaveGame( idFile *savefile ) { + int i, num; + + savefile->Read( &num, sizeof( num ) ); + for ( i = 0; i < num; i++ ) { + regs[i]->ReadFromSaveGame( savefile ); + } +} diff --git a/src/ui/renderwindow.cpp b/src/ui/renderwindow.cpp new file mode 100644 index 0000000..6e0cb9f --- /dev/null +++ b/src/ui/renderwindow.cpp @@ -0,0 +1,208 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "RenderWindow.h" + +idRenderWindow::idRenderWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idRenderWindow::idRenderWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idRenderWindow::~idRenderWindow() { + renderSystem->FreeRenderWorld( world ); +} + +void idRenderWindow::CommonInit() { + world = renderSystem->AllocRenderWorld(); + needsRender = true; + lightOrigin = idVec4(-128.0f, 0.0f, 0.0f, 1.0f); + lightColor = idVec4(1.0f, 1.0f, 1.0f, 1.0f); + modelOrigin.Zero(); + viewOffset = idVec4(-128.0f, 0.0f, 0.0f, 1.0f); + modelAnim = NULL; + animLength = 0; + animEndTime = -1; + modelDef = -1; + updateAnimation = true; +} + + +void idRenderWindow::BuildAnimation(int time) { + + if (!updateAnimation) { + return; + } + + if (animName.Length() && animClass.Length()) { + worldEntity.numJoints = worldEntity.hModel->NumJoints(); + worldEntity.joints = ( idJointMat * )Mem_Alloc16( worldEntity.numJoints * sizeof( *worldEntity.joints ) ); + modelAnim = gameEdit->ANIM_GetAnimFromEntityDef(animClass, animName); + if (modelAnim) { + animLength = gameEdit->ANIM_GetLength(modelAnim); + animEndTime = time + animLength; + } + } + updateAnimation = false; + +} + +void idRenderWindow::PreRender() { + if (needsRender) { + world->InitFromMap( NULL ); + idDict spawnArgs; + spawnArgs.Set("classname", "light"); + spawnArgs.Set("name", "light_1"); + spawnArgs.Set("origin", lightOrigin.ToVec3().ToString()); + spawnArgs.Set("_color", lightColor.ToVec3().ToString()); + gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &rLight ); + lightDef = world->AddLightDef( &rLight ); + if ( !modelName[0] ) { + common->Warning( "Window '%s' in gui '%s': no model set", GetName(), GetGui()->GetSourceFile() ); + } + memset( &worldEntity, 0, sizeof( worldEntity ) ); + spawnArgs.Clear(); + spawnArgs.Set("classname", "func_static"); + spawnArgs.Set("model", modelName); + spawnArgs.Set("origin", modelOrigin.c_str()); + gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &worldEntity ); + if ( worldEntity.hModel ) { + idVec3 v = modelRotate.ToVec3(); + worldEntity.axis = v.ToMat3(); + worldEntity.shaderParms[0] = 1; + worldEntity.shaderParms[1] = 1; + worldEntity.shaderParms[2] = 1; + worldEntity.shaderParms[3] = 1; + modelDef = world->AddEntityDef( &worldEntity ); + } + needsRender = false; + } +} + +void idRenderWindow::Render( int time ) { + rLight.origin = lightOrigin.ToVec3(); + rLight.shaderParms[SHADERPARM_RED] = lightColor.x(); + rLight.shaderParms[SHADERPARM_GREEN] = lightColor.y(); + rLight.shaderParms[SHADERPARM_BLUE] = lightColor.z(); + world->UpdateLightDef(lightDef, &rLight); + if ( worldEntity.hModel ) { + if (updateAnimation) { + BuildAnimation(time); + } + if (modelAnim) { + if (time > animEndTime) { + animEndTime = time + animLength; + } + gameEdit->ANIM_CreateAnimFrame(worldEntity.hModel, modelAnim, worldEntity.numJoints, worldEntity.joints, animLength - (animEndTime - time), vec3_origin, false ); + } + worldEntity.axis = idAngles(modelRotate.x(), modelRotate.y(), modelRotate.z()).ToMat3(); + world->UpdateEntityDef(modelDef, &worldEntity); + } +} + + + + +void idRenderWindow::Draw(int time, float x, float y) { + PreRender(); + Render(time); + + memset( &refdef, 0, sizeof( refdef ) ); + refdef.vieworg = viewOffset.ToVec3();; + //refdef.vieworg.Set(-128, 0, 0); + + refdef.viewaxis.Identity(); + refdef.shaderParms[0] = 1; + refdef.shaderParms[1] = 1; + refdef.shaderParms[2] = 1; + refdef.shaderParms[3] = 1; + + refdef.x = drawRect.x; + refdef.y = drawRect.y; + refdef.width = drawRect.w; + refdef.height = drawRect.h; + refdef.fov_x = 90; + refdef.fov_y = 2 * atan((float)drawRect.h / drawRect.w) * idMath::M_RAD2DEG; + + refdef.time = time; + world->RenderScene(&refdef); +} + +void idRenderWindow::PostParse() { + idWindow::PostParse(); +} + +// +// +idWinVar *idRenderWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner ) { +// + if (idStr::Icmp(_name, "model") == 0) { + return &modelName; + } + if (idStr::Icmp(_name, "anim") == 0) { + return &animName; + } + if (idStr::Icmp(_name, "lightOrigin") == 0) { + return &lightOrigin; + } + if (idStr::Icmp(_name, "lightColor") == 0) { + return &lightColor; + } + if (idStr::Icmp(_name, "modelOrigin") == 0) { + return &modelOrigin; + } + if (idStr::Icmp(_name, "modelRotate") == 0) { + return &modelRotate; + } + if (idStr::Icmp(_name, "viewOffset") == 0) { + return &viewOffset; + } + if (idStr::Icmp(_name, "needsRender") == 0) { + return &needsRender; + } + +// +// + return idWindow::GetWinVarByName(_name, fixup, owner); +// +} + +bool idRenderWindow::ParseInternalVar(const char *_name, idParser *src) { + if (idStr::Icmp(_name, "animClass") == 0) { + ParseString(src, animClass); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} diff --git a/src/ui/simplewindow.cpp b/src/ui/simplewindow.cpp new file mode 100644 index 0000000..57583f8 --- /dev/null +++ b/src/ui/simplewindow.cpp @@ -0,0 +1,490 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SimpleWindow.h" + + +idSimpleWindow::idSimpleWindow(idWindow *win) { + gui = win->GetGui(); + dc = win->dc; + drawRect = win->drawRect; + clientRect = win->clientRect; + textRect = win->textRect; + origin = win->origin; + fontNum = win->fontNum; + name = win->name; + matScalex = win->matScalex; + matScaley = win->matScaley; + borderSize = win->borderSize; + textAlign = win->textAlign; + textAlignx = win->textAlignx; + textAligny = win->textAligny; + textSpacing = win->textSpacing; + textStyle = win->textStyle; + background = win->background; + flags = win->flags; + textShadow = win->textShadow; + + visible = win->visible; + text = win->text; + rect = win->rect; + backColor = win->backColor; + matColor = win->matColor; + foreColor = win->foreColor; + borderColor = win->borderColor; + textScale = win->textScale; + rotate = win->rotate; + shear = win->shear; + backGroundName = win->backGroundName; + if (backGroundName.Length()) { + background = declManager->FindMaterial(backGroundName); + background->SetSort( SS_GUI ); + background->SetImageClassifications( 1 ); // just for resource tracking + } + backGroundName.SetMaterialPtr(&background); + +// +// added parent + mParent = win->GetParent(); +// + + hideCursor = win->hideCursor; + + idWindow *parent = win->GetParent(); + if (parent) { + if (text.NeedsUpdate()) { + parent->AddUpdateVar(&text); + } + if (visible.NeedsUpdate()) { + parent->AddUpdateVar(&visible); + } + if (rect.NeedsUpdate()) { + parent->AddUpdateVar(&rect); + } + if (backColor.NeedsUpdate()) { + parent->AddUpdateVar(&backColor); + } + if (matColor.NeedsUpdate()) { + parent->AddUpdateVar(&matColor); + } + if (foreColor.NeedsUpdate()) { + parent->AddUpdateVar(&foreColor); + } + if (borderColor.NeedsUpdate()) { + parent->AddUpdateVar(&borderColor); + } + if (textScale.NeedsUpdate()) { + parent->AddUpdateVar(&textScale); + } + if (rotate.NeedsUpdate()) { + parent->AddUpdateVar(&rotate); + } + if (shear.NeedsUpdate()) { + parent->AddUpdateVar(&shear); + } + if (backGroundName.NeedsUpdate()) { + parent->AddUpdateVar(&backGroundName); + } + } +} + +idSimpleWindow::~idSimpleWindow() { + definedVars.DeleteContents( true ); +} + +void idSimpleWindow::ResetCinematics() { + if ( background ) { + background->ResetCinematicTime( gui->GetTime() ); + } +} + +void idSimpleWindow::StateChanged( bool redraw ) { + if ( redraw && background && background->CinematicLength() ) { + background->UpdateCinematic( gui->GetTime() ); + } +} + +void idSimpleWindow::SetupTransforms(float x, float y) { + static idMat3 trans; + static idVec3 org; + + trans.Identity(); + org.Set( origin.x + x, origin.y + y, 0 ); + if ( rotate ) { + static idRotation rot; + static idVec3 vec( 0, 0, 1 ); + rot.Set( org, vec, rotate ); + trans = rot.ToMat3(); + } + + static idMat3 smat; + smat.Identity(); + if (shear.x() || shear.y()) { + smat[0][1] = shear.x(); + smat[1][0] = shear.y(); + trans *= smat; + } + + if ( !trans.IsIdentity() ) { + dc->SetTransformInfo( org, trans ); + } +} + +void idSimpleWindow::DrawBackground(const idRectangle &drawRect) { + if (backColor.w() > 0) { + dc->DrawFilledRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, backColor); + } + + if (background) { + if (matColor.w() > 0) { + float scalex, scaley; + if ( flags & WIN_NATURALMAT ) { + scalex = drawRect.w / background->GetImageWidth(); + scaley = drawRect.h / background->GetImageHeight(); + } else { + scalex = matScalex; + scaley = matScaley; + } + dc->DrawMaterial(drawRect.x, drawRect.y, drawRect.w, drawRect.h, background, matColor, scalex, scaley); + } + } +} + +void idSimpleWindow::DrawBorderAndCaption(const idRectangle &drawRect) { + if (flags & WIN_BORDER) { + if (borderSize) { + dc->DrawRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, borderSize, borderColor); + } + } +} + +void idSimpleWindow::CalcClientRect(float xofs, float yofs) { + + drawRect = rect; + + if ( flags & WIN_INVERTRECT ) { + drawRect.x = rect.x() - rect.w(); + drawRect.y = rect.y() - rect.h(); + } + + drawRect.x += xofs; + drawRect.y += yofs; + + clientRect = drawRect; + if (rect.h() > 0.0 && rect.w() > 0.0) { + + if (flags & WIN_BORDER && borderSize != 0.0) { + clientRect.x += borderSize; + clientRect.y += borderSize; + clientRect.w -= borderSize; + clientRect.h -= borderSize; + } + + textRect = clientRect; + textRect.x += 2.0; + textRect.w -= 2.0; + textRect.y += 2.0; + textRect.h -= 2.0; + textRect.x += textAlignx; + textRect.y += textAligny; + + } + origin.Set( rect.x() + ( rect.w() / 2 ), rect.y() + ( rect.h() / 2 ) ); + +} + + +void idSimpleWindow::Redraw(float x, float y) { + + if (!visible) { + return; + } + + CalcClientRect(0, 0); + dc->SetFont(fontNum); + drawRect.Offset(x, y); + clientRect.Offset(x, y); + textRect.Offset(x, y); + SetupTransforms(x, y); + if ( flags & WIN_NOCLIP ) { + dc->EnableClipping( false ); + } + DrawBackground(drawRect); + DrawBorderAndCaption(drawRect); + if ( textShadow ) { + idStr shadowText = text; + idRectangle shadowRect = textRect; + + shadowText.RemoveEscapes(); + shadowRect.x += textShadow; + shadowRect.y += textShadow; + + dc->DrawText( shadowText, textScale, textAlign, colorBlack, shadowRect, !( flags & WIN_NOWRAP ), -1 ); + } + dc->DrawText(text, textScale, textAlign, foreColor, textRect, !( flags & WIN_NOWRAP ), -1); + dc->SetTransformInfo(vec3_origin, mat3_identity); + if ( flags & WIN_NOCLIP ) { + dc->EnableClipping( true ); + } + drawRect.Offset(-x, -y); + clientRect.Offset(-x, -y); + textRect.Offset(-x, -y); +} + +int idSimpleWindow::GetWinVarOffset( idWinVar *wv, drawWin_t* owner) { + int ret = -1; + + if ( wv == &rect ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->rect; + } + + if ( wv == &backColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->backColor; + } + + if ( wv == &matColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->matColor; + } + + if ( wv == &foreColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->foreColor; + } + + if ( wv == &borderColor ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->borderColor; + } + + if ( wv == &textScale ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->textScale; + } + + if ( wv == &rotate ) { + ret = (int)&( ( idSimpleWindow * ) 0 )->rotate; + } + + if ( ret != -1 ) { + owner->simp = this; + } + return ret; +} + +idWinVar *idSimpleWindow::GetWinVarByName(const char *_name) { + idWinVar *retVar = NULL; + if (idStr::Icmp(_name, "background") == 0) { + retVar = &backGroundName; + } + if (idStr::Icmp(_name, "visible") == 0) { + retVar = &visible; + } + if (idStr::Icmp(_name, "rect") == 0) { + retVar = ▭ + } + if (idStr::Icmp(_name, "backColor") == 0) { + retVar = &backColor; + } + if (idStr::Icmp(_name, "matColor") == 0) { + retVar = &matColor; + } + if (idStr::Icmp(_name, "foreColor") == 0) { + retVar = &foreColor; + } + if (idStr::Icmp(_name, "borderColor") == 0) { + retVar = &borderColor; + } + if (idStr::Icmp(_name, "textScale") == 0) { + retVar = &textScale; + } + if (idStr::Icmp(_name, "rotate") == 0) { + retVar = &rotate; + } + if (idStr::Icmp(_name, "shear") == 0) { + retVar = &shear; + } + if (idStr::Icmp(_name, "text") == 0) { + retVar = &text; + } + if ( retVar ) { + return retVar; + } + + for ( int i = 0; i < definedVars.Num(); ++i ) { + if ( idStr::Icmp( _name, definedVars[i]->GetName() ) == 0 ) { + return definedVars[i]; + } + } + + const char *underscore = strrchr( _name, '_' ); + if ( underscore && underscore[1] && !underscore[2] ) { + const char *component = strchr( "xyzw", underscore[1] ); + if ( component ) { + idStr baseName( _name, 0, static_cast( underscore - _name ) ); + idWinVar *parentVar = GetWinVarByName( baseName ); + if ( dynamic_cast( parentVar ) || dynamic_cast( parentVar ) ) { + idWinFloatMember *member = new idWinFloatMember( parentVar, static_cast( component - "xyzw" ) ); + member->SetName( _name ); + definedVars.Append( member ); + return member; + } + } + } + return NULL; +} + +/* +======================== +idSimpleWindow::WriteToSaveGame +======================== +*/ +void idSimpleWindow::WriteToSaveGame( idFile *savefile ) { + + savefile->Write( &flags, sizeof( flags ) ); + savefile->Write( &drawRect, sizeof( drawRect ) ); + savefile->Write( &clientRect, sizeof( clientRect ) ); + savefile->Write( &textRect, sizeof( textRect ) ); + savefile->Write( &origin, sizeof( origin ) ); + savefile->Write( &fontNum, sizeof( fontNum ) ); + savefile->Write( &matScalex, sizeof( matScalex ) ); + savefile->Write( &matScaley, sizeof( matScaley ) ); + savefile->Write( &borderSize, sizeof( borderSize ) ); + savefile->Write( &textAlign, sizeof( textAlign ) ); + savefile->Write( &textAlignx, sizeof( textAlignx ) ); + savefile->Write( &textAligny, sizeof( textAligny ) ); + savefile->Write( &textSpacing, sizeof( textSpacing ) ); + savefile->Write( &textStyle, sizeof( textStyle ) ); + savefile->Write( &textShadow, sizeof( textShadow ) ); + + text.WriteToSaveGame( savefile ); + visible.WriteToSaveGame( savefile ); + rect.WriteToSaveGame( savefile ); + backColor.WriteToSaveGame( savefile ); + matColor.WriteToSaveGame( savefile ); + foreColor.WriteToSaveGame( savefile ); + borderColor.WriteToSaveGame( savefile ); + textScale.WriteToSaveGame( savefile ); + rotate.WriteToSaveGame( savefile ); + shear.WriteToSaveGame( savefile ); + backGroundName.WriteToSaveGame( savefile ); + + int stringLen; + + if ( background ) { + stringLen = strlen( background->GetName() ); + savefile->Write( &stringLen, sizeof( stringLen ) ); + savefile->Write( background->GetName(), stringLen ); + } else { + stringLen = 0; + savefile->Write( &stringLen, sizeof( stringLen ) ); + } + + for ( int i = 0; i < definedVars.Num(); ++i ) { + definedVars[i]->WriteToSaveGame( savefile ); + } + +} + +/* +======================== +idSimpleWindow::ReadFromSaveGame +======================== +*/ +void idSimpleWindow::ReadFromSaveGame( idFile *savefile ) { + + savefile->Read( &flags, sizeof( flags ) ); + savefile->Read( &drawRect, sizeof( drawRect ) ); + savefile->Read( &clientRect, sizeof( clientRect ) ); + savefile->Read( &textRect, sizeof( textRect ) ); + savefile->Read( &origin, sizeof( origin ) ); + savefile->Read( &fontNum, sizeof( fontNum ) ); + savefile->Read( &matScalex, sizeof( matScalex ) ); + savefile->Read( &matScaley, sizeof( matScaley ) ); + savefile->Read( &borderSize, sizeof( borderSize ) ); + savefile->Read( &textAlign, sizeof( textAlign ) ); + savefile->Read( &textAlignx, sizeof( textAlignx ) ); + savefile->Read( &textAligny, sizeof( textAligny ) ); + savefile->Read( &textSpacing, sizeof( textSpacing ) ); + savefile->Read( &textStyle, sizeof( textStyle ) ); + savefile->Read( &textShadow, sizeof( textShadow ) ); + + text.ReadFromSaveGame( savefile ); + visible.ReadFromSaveGame( savefile ); + rect.ReadFromSaveGame( savefile ); + backColor.ReadFromSaveGame( savefile ); + matColor.ReadFromSaveGame( savefile ); + foreColor.ReadFromSaveGame( savefile ); + borderColor.ReadFromSaveGame( savefile ); + textScale.ReadFromSaveGame( savefile ); + rotate.ReadFromSaveGame( savefile ); + shear.ReadFromSaveGame( savefile ); + backGroundName.ReadFromSaveGame( savefile ); + + int stringLen; + + savefile->Read( &stringLen, sizeof( stringLen ) ); + if ( stringLen > 0 ) { + idStr backName; + + backName.Fill( ' ', stringLen ); + savefile->Read( &(backName)[0], stringLen ); + + background = declManager->FindMaterial( backName ); + background->SetSort( SS_GUI ); + } else { + background = NULL; + } + + for ( int i = 0; i < definedVars.Num(); ++i ) { + definedVars[i]->ReadFromSaveGame( savefile ); + } + +} + + +/* +=============================== +*/ + +size_t idSimpleWindow::Size() { + size_t sz = sizeof(*this); + sz += name.Size(); + sz += text.Size(); + sz += backGroundName.Size(); + for ( int i = 0; i < definedVars.Num(); ++i ) { + sz += definedVars[i]->Size(); + } + return sz; +} + +bool idSimpleWindow::GetMaxTextIndex( const char *text, wrapInfo_t& wrapInfo ) const { + if ( !dc ) { + return false; + } + dc->SetFont( fontNum ); + return dc->GetMaxTextIndex( text, static_cast( textRect.w ), textScale, wrapInfo ); +} diff --git a/src/ui/sliderwindow.cpp b/src/ui/sliderwindow.cpp new file mode 100644 index 0000000..e15372a --- /dev/null +++ b/src/ui/sliderwindow.cpp @@ -0,0 +1,412 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "SliderWindow.h" + +/* +============ +idSliderWindow::CommonInit +============ +*/ +void idSliderWindow::CommonInit() { + value = 0.0; + low = 0.0; + high = 100.0; + stepSize = 1.0; + thumbMat = declManager->FindMaterial("_default"); + buddyWin = NULL; + + cvar = NULL; + cvar_init = false; + liveUpdate = true; + + vertical = false; + scrollbar = false; + + verticalFlip = false; +} + +idSliderWindow::idSliderWindow(idDeviceContext *d, idUserInterfaceLocal *g) : idWindow(d, g) { + dc = d; + gui = g; + CommonInit(); +} + +idSliderWindow::idSliderWindow(idUserInterfaceLocal *g) : idWindow(g) { + gui = g; + CommonInit(); +} + +idSliderWindow::~idSliderWindow() { + +} + +bool idSliderWindow::ParseInternalVar(const char *_name, idParser *src) { + if (idStr::Icmp(_name, "stepsize") == 0 || idStr::Icmp(_name, "step") == 0) { + stepSize = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "low") == 0) { + low = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "high") == 0) { + high = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "vertical") == 0) { + vertical = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "verticalflip") == 0) { + verticalFlip = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "scrollbar") == 0) { + scrollbar = src->ParseBool(); + return true; + } + if (idStr::Icmp(_name, "thumbshader") == 0) { + ParseString(src, thumbShader); + declManager->FindMaterial(thumbShader); + return true; + } + return idWindow::ParseInternalVar(_name, src); +} + +idWinVar *idSliderWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + + if (idStr::Icmp(_name, "value") == 0) { + return &value; + } + if (idStr::Icmp(_name, "cvar") == 0) { + return &cvarStr; + } + if ( idStr::Icmp( _name, "liveUpdate" ) == 0 ) { + return &liveUpdate; + } + if ( idStr::Icmp( _name, "cvarGroup" ) == 0 ) { + return &cvarGroup; + } + + return idWindow::GetWinVarByName(_name, fixup, owner); +} + +const char *idSliderWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + + if (!(event->evType == SE_KEY && event->evValue2)) { + return ""; + } + + int key = event->evValue; + + if ( event->evValue2 && key == K_MOUSE1 ) { + SetCapture(this); + RouteMouseCoords(0.0f, 0.0f); + return ""; + } + + if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW || ( key == K_MOUSE2 && gui->CursorY() > thumbRect.y ) ) { + value = value + stepSize; + } + + if ( key == K_LEFTARROW || key == K_KP_LEFTARROW || ( key == K_MOUSE2 && gui->CursorY() < thumbRect.y ) ) { + value = value - stepSize; + } + + if (buddyWin) { + buddyWin->HandleBuddyUpdate(this); + } else { + gui->SetStateFloat( cvarStr, value ); + UpdateCvar( false ); + } + + return ""; +} + + +void idSliderWindow::SetBuddy(idWindow *buddy) { + buddyWin = buddy; +} + +void idSliderWindow::PostParse() { + idWindow::PostParse(); + value = 0.0; + thumbMat = declManager->FindMaterial(thumbShader); + thumbMat->SetSort( SS_GUI ); + thumbWidth = thumbMat->GetImageWidth(); + thumbHeight = thumbMat->GetImageHeight(); + //vertical = state.GetBool("vertical"); + //scrollbar = state.GetBool("scrollbar"); + flags |= (WIN_HOLDCAPTURE | WIN_CANFOCUS); + InitCvar(); +} + +void idSliderWindow::InitWithDefaults(const char *_name, const idRectangle &_rect, const idVec4 &_foreColor, const idVec4 &_matColor, const char *_background, const char *thumbShader, bool _vertical, bool _scrollbar) { + SetInitialState(_name); + rect = _rect; + foreColor = _foreColor; + matColor = _matColor; + thumbMat = declManager->FindMaterial(thumbShader); + thumbMat->SetSort( SS_GUI ); + thumbWidth = thumbMat->GetImageWidth(); + thumbHeight = thumbMat->GetImageHeight(); + background = declManager->FindMaterial(_background); + background->SetSort( SS_GUI ); + vertical = _vertical; + scrollbar = _scrollbar; + flags |= WIN_HOLDCAPTURE; +} + +void idSliderWindow::SetRange(float _low, float _high, float _step) { + low = _low; + high = _high; + stepSize = _step; +} + +void idSliderWindow::SetValue(float _value) { + value = _value; +} + +void idSliderWindow::Draw(int time, float x, float y) { + idVec4 color = foreColor; + + if ( !cvar && !buddyWin ) { + return; + } + + if ( !thumbWidth || !thumbHeight ) { + thumbWidth = thumbMat->GetImageWidth(); + thumbHeight = thumbMat->GetImageHeight(); + } + + UpdateCvar( true ); + if ( value > high ) { + value = high; + } else if ( value < low ) { + value = low; + } + + float range = high - low; + + if ( range <= 0.0f ) { + return; + } + + float thumbPos = (range) ? (value - low) / range : 0.0; + if (vertical) { + if ( verticalFlip ) { + thumbPos = 1.f - thumbPos; + } + thumbPos *= drawRect.h - thumbHeight; + thumbPos += drawRect.y; + thumbRect.y = thumbPos; + thumbRect.x = drawRect.x; + } else { + thumbPos *= drawRect.w - thumbWidth; + thumbPos += drawRect.x; + thumbRect.x = thumbPos; + thumbRect.y = drawRect.y; + } + thumbRect.w = thumbWidth; + thumbRect.h = thumbHeight; + + if ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) { + color = hoverColor; + } else { + hover = false; + } + if ( flags & WIN_CAPTURE ) { + color = hoverColor; + hover = true; + } + + dc->DrawMaterial(thumbRect.x, thumbRect.y, thumbRect.w, thumbRect.h, thumbMat, color); + if ( flags & WIN_FOCUS ) { + dc->DrawRect(thumbRect.x+1.0f, thumbRect.y+1.0f, thumbRect.w-2.0f, thumbRect.h-2.0f, 1.0f, color); + } +} + + +void idSliderWindow::DrawBackground(const idRectangle &_drawRect) { + if ( !cvar && !buddyWin ) { + return; + } + + if ( high - low <= 0.0f ) { + return; + } + + idRectangle r = _drawRect; + if (!scrollbar) { + if ( vertical ) { + r.y += thumbHeight / 2.f; + r.h -= thumbHeight; + } else { + r.x += thumbWidth / 2.0; + r.w -= thumbWidth; + } + } + idWindow::DrawBackground(r); +} + +const char *idSliderWindow::RouteMouseCoords(float xd, float yd) { + float pct; + + if (!(flags & WIN_CAPTURE)) { + return ""; + } + + idRectangle r = drawRect; + r.x = actualX; + r.y = actualY; + r.x += thumbWidth / 2.0; + r.w -= thumbWidth; + if (vertical) { + r.y += thumbHeight / 2; + r.h -= thumbHeight; + if (gui->CursorY() >= r.y && gui->CursorY() <= r.Bottom()) { + pct = (gui->CursorY() - r.y) / r.h; + if ( verticalFlip ) { + pct = 1.f - pct; + } + value = low + (high - low) * pct; + } else if (gui->CursorY() < r.y) { + if ( verticalFlip ) { + value = high; + } else { + value = low; + } + } else { + if ( verticalFlip ) { + value = low; + } else { + value = high; + } + } + } else { + r.x += thumbWidth / 2; + r.w -= thumbWidth; + if (gui->CursorX() >= r.x && gui->CursorX() <= r.Right()) { + pct = (gui->CursorX() - r.x) / r.w; + value = low + (high - low) * pct; + } else if (gui->CursorX() < r.x) { + value = low; + } else { + value = high; + } + } + + if (buddyWin) { + buddyWin->HandleBuddyUpdate(this); + } else { + gui->SetStateFloat( cvarStr, value ); + } + UpdateCvar( false ); + + return ""; +} + + +void idSliderWindow::Activate(bool activate, idStr &act) { + idWindow::Activate(activate, act); + if ( activate ) { + UpdateCvar( true, true ); + } +} + +/* +============ +idSliderWindow::InitCvar +============ +*/ +void idSliderWindow::InitCvar( ) { + if ( cvarStr[0] == '\0' ) { + if ( !buddyWin ) { + common->Warning( "idSliderWindow::InitCvar: gui '%s' window '%s' has an empty cvar string", gui->GetSourceFile(), name.c_str() ); + } + cvar_init = true; + cvar = NULL; + return; + } + + cvar = cvarSystem->Find( cvarStr ); + if ( !cvar ) { + common->Warning( "idSliderWindow::InitCvar: gui '%s' window '%s' references undefined cvar '%s'", gui->GetSourceFile(), name.c_str(), cvarStr.c_str() ); + cvar_init = true; + return; + } +} + +/* +============ +idSliderWindow::UpdateCvar +============ +*/ +void idSliderWindow::UpdateCvar( bool read, bool force ) { + if ( buddyWin || !cvar ) { + return; + } + if ( force || liveUpdate ) { + value = cvar->GetFloat(); + if ( value != gui->State().GetFloat( cvarStr ) ) { + if ( read ) { + gui->SetStateFloat( cvarStr, value ); + } else { + value = gui->State().GetFloat( cvarStr ); + cvar->SetFloat( value ); + } + } + } +} + +/* +============ +idSliderWindow::RunNamedEvent +============ +*/ +void idSliderWindow::RunNamedEvent( const char* eventName ) { + idStr event, group; + + if ( !idStr::Cmpn( eventName, "cvar read ", 10 ) ) { + event = eventName; + group = event.Mid( 10, event.Length() - 10 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( true, true ); + } + } else if ( !idStr::Cmpn( eventName, "cvar write ", 11 ) ) { + event = eventName; + group = event.Mid( 11, event.Length() - 11 ); + if ( !group.Cmp( cvarGroup ) ) { + UpdateCvar( false, true ); + } + } +} + diff --git a/src/ui/userinterface.cpp b/src/ui/userinterface.cpp new file mode 100644 index 0000000..c68e247 --- /dev/null +++ b/src/ui/userinterface.cpp @@ -0,0 +1,708 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "ListGUILocal.h" +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" + +extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces + +idUserInterfaceManagerLocal uiManagerLocal; +idUserInterfaceManager * uiManager = &uiManagerLocal; + +/* +=============================================================================== + + idUserInterfaceManagerLocal + +=============================================================================== +*/ + +void idUserInterfaceManagerLocal::Init() { + screenRect = idRectangle(0, 0, 640, 480); + dc.Init(); +} + +void idUserInterfaceManagerLocal::Shutdown() { + guis.DeleteContents( true ); + demoGuis.DeleteContents( true ); + alwaysThinkGUIs.Clear(); + dc.Shutdown(); +} + +void idUserInterfaceManagerLocal::Touch( const char *name ) { + idUserInterface *gui = Alloc(); + gui->InitFromFile( name ); +// delete gui; +} + +void idUserInterfaceManagerLocal::WritePrecacheCommands( idFile *f ) { + + int c = guis.Num(); + for( int i = 0; i < c; i++ ) { + char str[1024]; + sprintf( str, "touchGui %s\n", guis[i]->Name() ); + common->Printf( "%s", str ); + f->Printf( "%s", str ); + } +} + +void idUserInterfaceManagerLocal::SetSize( float width, float height ) { + dc.SetSize( width, height ); +} + +void idUserInterfaceManagerLocal::BeginLevelLoad() { + int c = guis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( (guis[ i ]->GetDesktop()->GetFlags() & WIN_MENUGUI) == 0 ) { + guis[ i ]->ClearRefs(); + /* + delete guis[ i ]; + guis.RemoveIndex( i ); + i--; c--; + */ + } + } +} + +void idUserInterfaceManagerLocal::EndLevelLoad() { + int c = guis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( guis[i]->GetRefs() == 0 ) { + //common->Printf( "purging %s.\n", guis[i]->GetSourceFile() ); + + // use this to make sure no materials still reference this gui + bool remove = true; + for ( int j = 0; j < declManager->GetNumDecls( DECL_MATERIAL ); j++ ) { + const idMaterial *material = static_cast(declManager->DeclByIndex( DECL_MATERIAL, j, false )); + if ( material->GlobalGui() == guis[i] ) { + remove = false; + break; + } + } + if ( remove ) { + delete guis[ i ]; + guis.RemoveIndex( i ); + i--; c--; + } + } + } + for ( int i = 0; i < alwaysThinkGUIs.Num(); i++ ) { + if ( guis.FindIndex( alwaysThinkGUIs[i] ) < 0 ) { + alwaysThinkGUIs.RemoveIndex( i-- ); + } + } + dc.SizeIcons(); +} + +void idUserInterfaceManagerLocal::Reload( bool all ) { + ID_TIME_T ts; + + int c = guis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( !all ) { + fileSystem->ReadFile( guis[i]->GetSourceFile(), NULL, &ts ); + if ( ts <= guis[i]->GetTimeStamp() ) { + continue; + } + } + + guis[i]->InitFromFile( guis[i]->GetSourceFile() ); + common->Printf( "reloading %s.\n", guis[i]->GetSourceFile() ); + } +} + +void idUserInterfaceManagerLocal::ListGuis() const { + int c = guis.Num(); + common->Printf( "\n size refs name\n" ); + size_t total = 0; + int copies = 0; + int unique = 0; + for ( int i = 0; i < c; i++ ) { + idUserInterfaceLocal *gui = guis[i]; + size_t sz = gui->Size(); + bool isUnique = guis[i]->interactive; + if ( isUnique ) { + unique++; + } else { + copies++; + } + common->Printf( "%6.1fk %4i (%s) %s ( %i transitions )\n", sz / 1024.0f, guis[i]->GetRefs(), isUnique ? "unique" : "copy", guis[i]->GetSourceFile(), guis[i]->desktop->NumTransitions() ); + total += sz; + } + common->Printf( "===========\n %i total Guis ( %i copies, %i unique ), %.2f total Mbytes", c, copies, unique, total / ( 1024.0f * 1024.0f ) ); +} + +bool idUserInterfaceManagerLocal::CheckGui( const char *qpath ) const { + idFile *file = fileSystem->OpenFileRead( qpath ); + if ( file ) { + fileSystem->CloseFile( file ); + return true; + } + return false; +} + +idUserInterface *idUserInterfaceManagerLocal::Alloc( void ) const { + return new idUserInterfaceLocal(); +} + +void idUserInterfaceManagerLocal::DeAlloc( idUserInterface *gui ) { + if ( gui ) { + int c = guis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( guis[i] == gui ) { + delete guis[i]; + guis.RemoveIndex( i ); + alwaysThinkGUIs.Remove( static_cast( gui ) ); + return; + } + } + } +} + +idUserInterface *idUserInterfaceManagerLocal::FindGui( const char *qpath, bool autoLoad, bool needUnique, bool forceNOTUnique ) { + int c = guis.Num(); + + for ( int i = 0; i < c; i++ ) { + idUserInterfaceLocal *gui = guis[i]; + if ( !idStr::Icmp( guis[i]->GetSourceFile(), qpath ) ) { + if ( !forceNOTUnique && ( needUnique || guis[i]->IsInteractive() ) ) { + break; + } + guis[i]->AddRef(); + return guis[i]; + } + } + + if ( autoLoad ) { + idUserInterface *gui = Alloc(); + if ( gui->InitFromFile( qpath ) ) { + gui->SetUniqued( forceNOTUnique ? false : needUnique ); + return gui; + } else { + delete gui; + } + } + return NULL; +} + +idUserInterface *idUserInterfaceManagerLocal::FindDemoGui( const char *qpath ) { + int c = demoGuis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( !idStr::Icmp( demoGuis[i]->GetSourceFile(), qpath ) ) { + return demoGuis[i]; + } + } + return NULL; +} + +idListGUI * idUserInterfaceManagerLocal::AllocListGUI( void ) const { + return new idListGUILocal(); +} + +void idUserInterfaceManagerLocal::FreeListGUI( idListGUI *listgui ) { + delete listgui; +} + +void idUserInterfaceManagerLocal::RunAlwaysThinkGUIs( int time ) { + for ( int i = 0; i < alwaysThinkGUIs.Num(); i++ ) { + idUserInterfaceLocal *gui = alwaysThinkGUIs[i]; + gui->SetTime( time ); + gui->GetDesktop()->RunTimeEvents( time ); + } +} + +void idUserInterfaceManagerLocal::RegisterIcon( const char *code, const char *shader, int x, int y, int w, int h ) { + dc.RegisterIcon( code, shader, x, y, w, h ); +} + +/* +=============================================================================== + + idUserInterfaceLocal + +=============================================================================== +*/ + +idUserInterfaceLocal::idUserInterfaceLocal() { + cursorX = cursorY = 0.0; + desktop = NULL; + loading = false; + active = false; + interactive = false; + uniqued = false; + bindHandler = NULL; + initialized = false; + lightColorVar = NULL; + //so the reg eval in gui parsing doesn't get bogus values + time = 0; + refs = 1; +} + +idUserInterfaceLocal::~idUserInterfaceLocal() { + delete desktop; + desktop = NULL; +} + +const char *idUserInterfaceLocal::Name() const { + return source; +} + +const char *idUserInterfaceLocal::Comment() const { + if ( desktop ) { + return desktop->GetComment(); + } + return ""; +} + +bool idUserInterfaceLocal::IsInteractive() const { + return interactive; +} + +void idUserInterfaceLocal::SetInteractive( bool _interactive ) { + interactive = _interactive; +} + +bool idUserInterfaceLocal::InitFromFile( const char *qpath, bool rebuild, bool cache ) { + + if ( !( qpath && *qpath ) ) { + // FIXME: Memory leak!! + return false; + } + + int sz = sizeof( idWindow ); + sz = sizeof( idSimpleWindow ); + loading = true; + + if ( rebuild ) { + delete desktop; + desktop = new idWindow( this ); + } else if ( desktop == NULL ) { + desktop = new idWindow( this ); + } + + source = qpath; + state.Set( "text", "Test Text!" ); + + idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + + //Load the timestamp so reload guis will work correctly + fileSystem->ReadFile(qpath, NULL, &timeStamp); + + src.LoadFile( qpath ); + + if ( src.IsLoaded() ) { + idToken token; + while( src.ReadToken( &token ) ) { + if ( idStr::Icmp( token, "windowDef" ) == 0 ) { + desktop->SetDC( &uiManagerLocal.dc ); + if ( desktop->Parse( &src, rebuild ) ) { + desktop->SetFlag( WIN_DESKTOP ); + desktop->FixupParms(); + } + continue; + } + } + + state.Set( "name", qpath ); + } else { + desktop->SetDC( &uiManagerLocal.dc ); + desktop->SetFlag( WIN_DESKTOP ); + desktop->name = "Desktop"; + desktop->text = va( "Invalid GUI: %s", qpath ); + desktop->rect = idRectangle( 0.0f, 0.0f, 640.0f, 480.0f ); + desktop->drawRect = desktop->rect; + desktop->foreColor = idVec4( 1.0f, 1.0f, 1.0f, 1.0f ); + desktop->backColor = idVec4( 0.0f, 0.0f, 0.0f, 1.0f ); + desktop->SetupFromState(); + common->Warning( "Couldn't load gui: '%s'", qpath ); + } + + interactive = desktop->Interactive(); + + if ( uiManagerLocal.guis.Find( this ) == NULL ) { + uiManagerLocal.guis.Append( this ); + } + if ( ( desktop->GetFlags() & WIN_ALWAYSTHINK ) != 0 && uiManagerLocal.alwaysThinkGUIs.Find( this ) == NULL ) { + uiManagerLocal.alwaysThinkGUIs.Append( this ); + } + + loading = false; + lightColorVar = NULL; + initialized = false; + + return true; +} + +const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _time, bool *updateVisuals ) { + + time = _time; + + if ( bindHandler && event->evType == SE_KEY && event->evValue2 == 1 ) { + const char *ret = bindHandler->HandleEvent( event, updateVisuals ); + bindHandler = NULL; + return ret; + } + + if ( event->evType == SE_MOUSE ) { + cursorX += event->evValue; + cursorY += event->evValue2; + + if (cursorX < 0) { + cursorX = 0; + } + if (cursorY < 0) { + cursorY = 0; + } + } + + if ( desktop ) { + return desktop->HandleEvent( event, updateVisuals ); + } + + return ""; +} + +void idUserInterfaceLocal::HandleNamedEvent ( const char* eventName ) { + desktop->RunNamedEvent( eventName ); +} + +void idUserInterfaceLocal::Redraw( int _time ) { + if ( r_skipGuiShaders.GetInteger() > 5 ) { + return; + } + if ( !loading && desktop ) { + time = _time; + uiManagerLocal.dc.PushClipRect( uiManagerLocal.screenRect ); + desktop->Redraw( 0, 0 ); + uiManagerLocal.dc.PopClipRect(); + } +} + +void idUserInterfaceLocal::DrawCursor() { + if ( !desktop || desktop->GetFlags() & WIN_MENUGUI ) { + uiManagerLocal.dc.DrawCursor(&cursorX, &cursorY, 32.0f ); + } else { + uiManagerLocal.dc.DrawCursor(&cursorX, &cursorY, 64.0f ); + } +} + +const idDict &idUserInterfaceLocal::State() const { + return state; +} + +void idUserInterfaceLocal::DeleteStateVar( const char *varName ) { + state.Delete( varName ); +} + +void idUserInterfaceLocal::SetStateString( const char *varName, const char *value ) { + state.Set( varName, value ); +} + +void idUserInterfaceLocal::SetStateBool( const char *varName, const bool value ) { + state.SetBool( varName, value ); +} + +void idUserInterfaceLocal::SetStateInt( const char *varName, const int value ) { + state.SetInt( varName, value ); +} + +void idUserInterfaceLocal::SetStateFloat( const char *varName, const float value ) { + state.SetFloat( varName, value ); +} + +void idUserInterfaceLocal::SetStateVector( const char *varName, const idVec3& vector ) { + state.SetVector( varName, vector ); +} + +void idUserInterfaceLocal::SetStateVec4( const char *varName, const idVec4& vector ) { + state.SetVec4( varName, vector ); +} + +void idUserInterfaceLocal::ClearState() { + state.Clear(); +} + +void idUserInterfaceLocal::DeleteState( const char *varName ) { + state.Delete( varName ); +} + +idVec4 idUserInterfaceLocal::GetLightColor() { + return lightColorVar ? static_cast( *lightColorVar ) : vec4_origin; +} + +const char* idUserInterfaceLocal::GetStateString( const char *varName, const char* defaultString ) const { + return state.GetString(varName, defaultString); +} + +bool idUserInterfaceLocal::GetStateBool( const char *varName, const char* defaultString ) const { + return state.GetBool(varName, defaultString); +} + +int idUserInterfaceLocal::GetStateInt( const char *varName, const char* defaultString ) const { + return state.GetInt(varName, defaultString); +} + +float idUserInterfaceLocal::GetStateFloat( const char *varName, const char* defaultString ) const { + return state.GetFloat(varName, defaultString); +} + +idVec3 idUserInterfaceLocal::GetStateVector( const char *varName, const char* defaultString ) const { + return state.GetVector( varName, defaultString ); +} + +idVec4 idUserInterfaceLocal::GetStateVec4( const char *varName, const char* defaultString ) const { + return state.GetVec4( varName, defaultString ); +} + +bool idUserInterfaceLocal::GetMaxTextIndex( const char *windowName, const char *text, wrapInfo_t& wrapInfo ) const { + if ( !desktop ) { + return false; + } + drawWin_t *drawWin = desktop->FindChildByName( windowName ); + if ( !drawWin ) { + return false; + } + if ( drawWin->win ) { + return drawWin->win->GetMaxTextIndex( text, wrapInfo ); + } + return drawWin->simp ? drawWin->simp->GetMaxTextIndex( text, wrapInfo ) : false; +} + +void idUserInterfaceLocal::StateChanged( int _time, bool redraw ) { + time = _time; + if (desktop) { + desktop->StateChanged( redraw ); + } + if ( state.GetBool( "noninteractive" ) ) { + interactive = false; + } + else { + if (desktop) { + interactive = desktop->Interactive(); + } else { + interactive = false; + } + } +} + +const char *idUserInterfaceLocal::Activate(bool activate, int _time) { + time = _time; + active = activate; + if ( desktop ) { + activateStr = ""; + desktop->Activate( activate, activateStr ); + return activateStr; + } + return ""; +} + +void idUserInterfaceLocal::Trigger(int _time) { + time = _time; + if ( desktop ) { + desktop->Trigger(); + } +} + +void idUserInterfaceLocal::ReadFromDemoFile( class idDemoFile *f ) { + idStr work; + f->ReadDict( state ); + source = state.GetString("name"); + + if (desktop == NULL) { + f->Log("creating new gui\n"); + desktop = new idWindow(this); + desktop->SetFlag( WIN_DESKTOP ); + desktop->SetDC( &uiManagerLocal.dc ); + desktop->ReadFromDemoFile(f); + } else { + f->Log("re-using gui\n"); + desktop->ReadFromDemoFile(f, false); + } + + f->ReadFloat( cursorX ); + f->ReadFloat( cursorY ); + + bool add = true; + int c = uiManagerLocal.demoGuis.Num(); + for ( int i = 0; i < c; i++ ) { + if ( uiManagerLocal.demoGuis[i] == this ) { + add = false; + break; + } + } + + if (add) { + uiManagerLocal.demoGuis.Append(this); + } +} + +void idUserInterfaceLocal::WriteToDemoFile( class idDemoFile *f ) { + idStr work; + f->WriteDict( state ); + if (desktop) { + desktop->WriteToDemoFile(f); + } + + f->WriteFloat( cursorX ); + f->WriteFloat( cursorY ); +} + +bool idUserInterfaceLocal::WriteToSaveGame( idFile *savefile ) const { + int len; + const idKeyValue *kv; + const char *string; + + int num = state.GetNumKeyVals(); + savefile->Write( &num, sizeof( num ) ); + + for( int i = 0; i < num; i++ ) { + kv = state.GetKeyVal( i ); + len = kv->GetKey().Length(); + string = kv->GetKey().c_str(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( string, len ); + + len = kv->GetValue().Length(); + string = kv->GetValue().c_str(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( string, len ); + } + + savefile->Write( &active, sizeof( active ) ); + savefile->Write( &interactive, sizeof( interactive ) ); + savefile->Write( &uniqued, sizeof( uniqued ) ); + savefile->Write( &time, sizeof( time ) ); + len = activateStr.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( activateStr.c_str(), len ); + len = pendingCmd.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( pendingCmd.c_str(), len ); + len = returnCmd.Length(); + savefile->Write( &len, sizeof( len ) ); + savefile->Write( returnCmd.c_str(), len ); + + savefile->Write( &cursorX, sizeof( cursorX ) ); + savefile->Write( &cursorY, sizeof( cursorY ) ); + + desktop->WriteToSaveGame( savefile ); + + return true; +} + +bool idUserInterfaceLocal::ReadFromSaveGame( idFile *savefile ) { + int num; + int i, len; + idStr key; + idStr value; + + savefile->Read( &num, sizeof( num ) ); + + state.Clear(); + for( i = 0; i < num; i++ ) { + savefile->Read( &len, sizeof( len ) ); + key.Fill( ' ', len ); + savefile->Read( &key[0], len ); + + savefile->Read( &len, sizeof( len ) ); + value.Fill( ' ', len ); + savefile->Read( &value[0], len ); + + state.Set( key, value ); + } + + savefile->Read( &active, sizeof( active ) ); + savefile->Read( &interactive, sizeof( interactive ) ); + savefile->Read( &uniqued, sizeof( uniqued ) ); + savefile->Read( &time, sizeof( time ) ); + + savefile->Read( &len, sizeof( len ) ); + activateStr.Fill( ' ', len ); + savefile->Read( &activateStr[0], len ); + savefile->Read( &len, sizeof( len ) ); + pendingCmd.Fill( ' ', len ); + savefile->Read( &pendingCmd[0], len ); + savefile->Read( &len, sizeof( len ) ); + returnCmd.Fill( ' ', len ); + savefile->Read( &returnCmd[0], len ); + + savefile->Read( &cursorX, sizeof( cursorX ) ); + savefile->Read( &cursorY, sizeof( cursorY ) ); + + desktop->ReadFromSaveGame( savefile ); + + return true; +} + +size_t idUserInterfaceLocal::Size() { + size_t sz = sizeof(*this) + state.Size() + source.Allocated(); + if ( desktop ) { + sz += desktop->Size(); + } + return sz; +} + +void idUserInterfaceLocal::RecurseSetKeyBindingNames( idWindow *window ) { + int i; + idWinVar *v = window->GetWinVarByName( "bind" ); + if ( v ) { + SetStateString( v->GetName(), idKeyInput::KeysFromBinding( v->GetName() ) ); + } + i = 0; + while ( i < window->GetChildCount() ) { + idWindow *next = window->GetChild( i ); + if ( next ) { + RecurseSetKeyBindingNames( next ); + } + i++; + } +} + +/* +============== +idUserInterfaceLocal::SetKeyBindingNames +============== +*/ +void idUserInterfaceLocal::SetKeyBindingNames( void ) { + if ( !desktop ) { + return; + } + // walk the windows + RecurseSetKeyBindingNames( desktop ); +} + +/* +============== +idUserInterfaceLocal::SetCursor +============== +*/ +void idUserInterfaceLocal::SetCursor( float x, float y ) { + cursorX = x; + cursorY = y; +} diff --git a/src/ui/window.cpp b/src/ui/window.cpp new file mode 100644 index 0000000..e36c06b --- /dev/null +++ b/src/ui/window.cpp @@ -0,0 +1,4368 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "DeviceContext.h" +#include "Window.h" +#include "UserInterfaceLocal.h" +#include "EditWindow.h" +#include "ChoiceWindow.h" +#include "SliderWindow.h" +#include "BindWindow.h" +#include "ListWindow.h" +#include "RenderWindow.h" +#include "MarkerWindow.h" +#include "FieldWindow.h" + +#include "GameSSDWindow.h" +#include "GameBearShootWindow.h" +#include "GameBustOutWindow.h" + +// +// gui editor is more integrated into the window now +#ifdef ID_ALLOW_TOOLS +#include "../tools/guied/GEWindowWrapper.h" +#endif + +bool idWindow::registerIsTemporary[MAX_EXPRESSION_REGISTERS]; // statics to assist during parsing +//float idWindow::shaderRegisters[MAX_EXPRESSION_REGISTERS]; +//wexpOp_t idWindow::shaderOps[MAX_EXPRESSION_OPS]; + +idCVar idWindow::gui_debug( "gui_debug", "0", CVAR_GUI | CVAR_BOOL, "" ); +idCVar idWindow::gui_edit( "gui_edit", "0", CVAR_GUI | CVAR_BOOL, "" ); + +extern idCVar r_skipGuiShaders; // 1 = don't render any gui elements on surfaces + +// made RegisterVars a member of idWindow +const idRegEntry idWindow::RegisterVars[] = { + { "forecolor", idRegister::VEC4 }, + { "hovercolor", idRegister::VEC4 }, + { "backcolor", idRegister::VEC4 }, + { "bordercolor", idRegister::VEC4 }, + { "rect", idRegister::RECTANGLE }, + { "matcolor", idRegister::VEC4 }, + { "scale", idRegister::VEC2 }, + { "translate", idRegister::VEC2 }, + { "rotate", idRegister::FLOAT }, + { "textscale", idRegister::FLOAT }, + { "visible", idRegister::BOOL }, + { "noevents", idRegister::BOOL }, + { "text", idRegister::STRING }, + { "background", idRegister::STRING }, + { "runscript", idRegister::STRING }, + { "varbackground", idRegister::STRING }, + { "cvar", idRegister::STRING }, + { "choices", idRegister::STRING }, + { "choiceVar", idRegister::STRING }, + { "bind", idRegister::STRING }, + { "modelRotate", idRegister::VEC4 }, + { "modelOrigin", idRegister::VEC4 }, + { "lightOrigin", idRegister::VEC4 }, + { "lightColor", idRegister::VEC4 }, + { "viewOffset", idRegister::VEC4 }, + { "hideCursor", idRegister::BOOL} +}; + +const int idWindow::NumRegisterVars = sizeof(RegisterVars) / sizeof(idRegEntry); + +const char *idWindow::ScriptNames[] = { + "onMouseEnter", + "onMouseExit", + "onAction", + "onBackAction", + "onActivate", + "onDeactivate", + "onESC", + "onEvent", + "onTrigger", + "onActionRelease", + "onEnter", + "onEnterRelease", + "onTabRelease", + "onGainFocus", + "onLoseFocus", + "onSelChange", + "onInit", + "onJoyStart", + "onJoySelect", + "onJoyBack", + "onJoyLShoulder", + "onJoyRShoulder", + "onJoyUp", + "onJoyDown", + "onJoyLeft", + "onJoyRight", + "onJoyButton1", + "onJoyButton2" +}; + +/* +================ +idWindow::CommonInit +================ +*/ +void idWindow::CommonInit() { + childID = 0; + flags = 0; + lastTimeRun = 0; + origin.Zero(); + fontNum = 0; + timeLine = -1; + xOffset = yOffset = 0.0; + cursor = 0; + forceAspectWidth = 640; + forceAspectHeight = 480; + matScalex = 1; + matScaley = 1; + borderSize = 0; + noTime = false; + visible = true; + textAlign = 0; + textAlignx = 0; + textAligny = 0; + noEvents = false; + rotate = 0; + shear.Zero(); + textScale = 0.35f; + backColor.Zero(); + foreColor = idVec4(1, 1, 1, 1); + hoverColor = idVec4(1, 1, 1, 1); + matColor = idVec4(1, 1, 1, 1); + borderColor.Zero(); + background = NULL; + backGroundName = ""; + focusedChild = NULL; + captureChild = NULL; + overChild = NULL; + parent = NULL; + saveOps = NULL; + saveRegs = NULL; + timeLine = -1; + textShadow = 0; + textSpacing = 0.0f; + textStyle = 0; + hover = false; + + for (int i = 0; i < SCRIPT_COUNT; i++) { + scripts[i] = NULL; + } + + hideCursor = false; +} + +/* +================ +idWindow::Size +================ +*/ +size_t idWindow::Size() { + int c = children.Num(); + int sz = 0; + for (int i = 0; i < c; i++) { + sz += children[i]->Size(); + } + sz += sizeof(*this) + Allocated(); + return sz; +} + +/* +================ +idWindow::Allocated +================ +*/ +size_t idWindow::Allocated() { + int i, c; + int sz = name.Allocated(); + sz += text.Size(); + sz += backGroundName.Size(); + + c = definedVars.Num(); + for (i = 0; i < c; i++) { + sz += definedVars[i]->Size(); + } + + for (i = 0; i < SCRIPT_COUNT; i++) { + if (scripts[i]) { + sz += scripts[i]->Size(); + } + } + c = timeLineEvents.Num(); + for (i = 0; i < c; i++) { + sz += timeLineEvents[i]->Size(); + } + + c = namedEvents.Num(); + for (i = 0; i < c; i++) { + sz += namedEvents[i]->Size(); + } + + c = drawWindows.Num(); + for (i = 0; i < c; i++) { + if (drawWindows[i].simp) { + sz += drawWindows[i].simp->Size(); + } + } + + return sz; +} + +/* +================ +idWindow::idWindow +================ +*/ +idWindow::idWindow(idUserInterfaceLocal *ui) { + dc = NULL; + gui = ui; + CommonInit(); +} + +/* +================ +idWindow::idWindow +================ +*/ +idWindow::idWindow(idDeviceContext *d, idUserInterfaceLocal *ui) { + dc = d; + gui = ui; + CommonInit(); +} + +/* +================ +idWindow::CleanUp +================ +*/ +void idWindow::CleanUp() { + int i, c = drawWindows.Num(); + for (i = 0; i < c; i++) { + delete drawWindows[i].simp; + } + + // ensure the register list gets cleaned up + regList.Reset ( ); + + // Cleanup the named events + namedEvents.DeleteContents(true); + + drawWindows.Clear(); + children.DeleteContents(true); + definedVars.DeleteContents(true); + timeLineEvents.DeleteContents(true); + for (i = 0; i < SCRIPT_COUNT; i++) { + delete scripts[i]; + } + CommonInit(); +} + +/* +================ +idWindow::~idWindow +================ +*/ +idWindow::~idWindow() { + CleanUp(); +} + +/* +================ +idWindow::Move +================ +*/ +void idWindow::Move(float x, float y) { + idRectangle rct = rect; + rct.x = x; + rct.y = y; + idRegister *reg = RegList()->FindReg("rect"); + if (reg) { + reg->Enable(false); + } + rect = rct; +} + +/* +================ +idWindow::SetFont +================ +*/ +void idWindow::SetFont() { + dc->SetFont(fontNum); +} + +/* +================ +idWindow::GetMaxCharHeight +================ +*/ +float idWindow::GetMaxCharHeight() { + SetFont(); + return dc->MaxCharHeight(textScale); +} + +/* +================ +idWindow::GetMaxCharWidth +================ +*/ +float idWindow::GetMaxCharWidth() { + SetFont(); + return dc->MaxCharWidth(textScale); +} + +/* +================ +idWindow::Draw +================ +*/ +void idWindow::Draw( int time, float x, float y ) { + if ( text.Length() == 0 ) { + return; + } + if ( textShadow ) { + idStr shadowText = text; + idRectangle shadowRect = textRect; + + shadowText.RemoveEscapes(); + shadowRect.x += textShadow; + shadowRect.y += textShadow; + + dc->DrawText( shadowText, textScale, textAlign, colorBlack, shadowRect, !( flags & WIN_NOWRAP ), -1 ); + } + dc->DrawText( text, textScale, textAlign, foreColor, textRect, !( flags & WIN_NOWRAP ), -1 ); + + if ( gui_edit.GetBool() ) { + dc->EnableClipping( false ); + dc->DrawText( va( "x: %i y: %i", ( int )rect.x(), ( int )rect.y() ), 0.25, 0, dc->colorWhite, idRectangle( rect.x(), rect.y() - 15, 100, 20 ), false ); + dc->DrawText( va( "w: %i h: %i", ( int )rect.w(), ( int )rect.h() ), 0.25, 0, dc->colorWhite, idRectangle( rect.x() + rect.w(), rect.w() + rect.h() + 5, 100, 20 ), false ); + dc->EnableClipping( true ); + } + +} + +/* +================ +idWindow::BringToTop +================ +*/ +void idWindow::BringToTop(idWindow *w) { + + if (w && !(w->flags & WIN_MODAL)) { + return; + } + + int c = children.Num(); + for (int i = 0; i < c; i++) { + if (children[i] == w) { + // this is it move from i - 1 to 0 to i to 1 then shove this one into 0 + for (int j = i+1; j < c; j++) { + children[j-1] = children[j]; + } + children[c-1] = w; + break; + } + } +} + +/* +================ +idWindow::Size +================ +*/ +void idWindow::Size(float x, float y, float w, float h) { + idRectangle rct = rect; + rct.x = x; + rct.y = y; + rct.w = w; + rct.h = h; + rect = rct; + CalcClientRect(0,0); +} + +/* +================ +idWindow::MouseEnter +================ +*/ +void idWindow::MouseEnter() { + + if (noEvents) { + return; + } + + RunScript(ON_MOUSEENTER); +} + +/* +================ +idWindow::MouseExit +================ +*/ +void idWindow::MouseExit() { + + if (noEvents) { + return; + } + + RunScript(ON_MOUSEEXIT); +} + + +/* +================ +idWindow::RouteMouseCoords +================ +*/ +const char *idWindow::RouteMouseCoords(float xd, float yd) { + idStr str; + if (GetCaptureChild()) { + //FIXME: unkludge this whole mechanism + return GetCaptureChild()->RouteMouseCoords(xd, yd); + } + + if (xd == -2000 || yd == -2000) { + return ""; + } + + int c = children.Num(); + while (c > 0) { + idWindow *child = children[--c]; + if (child->visible && !child->noEvents && child->Contains(child->drawRect, gui->CursorX(), gui->CursorY())) { + + dc->SetCursor(child->cursor); + child->hover = true; + + if (overChild != child) { + if (overChild) { + overChild->MouseExit(); + str = overChild->cmd; + if (str.Length()) { + gui->GetDesktop()->AddCommand(str); + overChild->cmd = ""; + } + } + overChild = child; + overChild->MouseEnter(); + str = overChild->cmd; + if (str.Length()) { + gui->GetDesktop()->AddCommand(str); + overChild->cmd = ""; + } + } else { + if (!(child->flags & WIN_HOLDCAPTURE)) { + child->RouteMouseCoords(xd, yd); + } + } + return ""; + } + } + if (overChild) { + overChild->MouseExit(); + str = overChild->cmd; + if (str.Length()) { + gui->GetDesktop()->AddCommand(str); + overChild->cmd = ""; + } + overChild = NULL; + } + return ""; +} + +/* +================ +idWindow::Activate +================ +*/ +void idWindow::Activate( bool activate, idStr &act ) { + + int n = (activate) ? ON_ACTIVATE : ON_DEACTIVATE; + + // make sure win vars are updated before activation + UpdateWinVars ( ); + + RunScript(n); + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->Activate( activate, act ); + } + + if ( act.Length() ) { + act += " ; "; + } +} + +/* +================ +idWindow::Trigger +================ +*/ +void idWindow::Trigger() { + RunScript( ON_TRIGGER ); + int c = children.Num(); + for ( int i = 0; i < c; i++ ) { + children[i]->Trigger(); + } + StateChanged( true ); +} + +/* +================ +idWindow::StateChanged +================ +*/ +void idWindow::StateChanged( bool redraw ) { + + UpdateWinVars(); + + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(); + } + + int c = drawWindows.Num(); + for ( int i = 0; i < c; i++ ) { + if ( drawWindows[i].win ) { + drawWindows[i].win->StateChanged( redraw ); + } else { + drawWindows[i].simp->StateChanged( redraw ); + } + } + + if ( redraw ) { + if ( flags & WIN_DESKTOP ) { + Redraw( 0.0f, 0.0f ); + } + if ( background && background->CinematicLength() ) { + background->UpdateCinematic( gui->GetTime() ); + } + } +} + +/* +================ +idWindow::SetCapture +================ +*/ +idWindow *idWindow::SetCapture(idWindow *w) { + // only one child can have the focus + + idWindow *last = NULL; + int c = children.Num(); + for (int i = 0; i < c; i++) { + if ( children[i]->flags & WIN_CAPTURE ) { + last = children[i]; + //last->flags &= ~WIN_CAPTURE; + last->LoseCapture(); + break; + } + } + + w->flags |= WIN_CAPTURE; + w->GainCapture(); + gui->GetDesktop()->captureChild = w; + return last; +} + +/* +================ +idWindow::AddUpdateVar +================ +*/ +void idWindow::AddUpdateVar(idWinVar *var) { + updateVars.AddUnique(var); +} + +/* +================ +idWindow::UpdateWinVars +================ +*/ +void idWindow::UpdateWinVars() { + int c = updateVars.Num(); + for (int i = 0; i < c; i++) { + updateVars[i]->Update(); + } +} + +/* +================ +idWindow::RunTimeEvents +================ +*/ +bool idWindow::RunTimeEvents(int time) { + + if ( time - lastTimeRun < common->GetUserCmdMSec() ) { + //common->Printf("Skipping gui time events at %i\n", time); + return false; + } + + lastTimeRun = time; + + UpdateWinVars(); + + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(); + } + + if ( flags & WIN_INTRANSITION ) { + Transition(); + } + + Time(); + + // renamed ON_EVENT to ON_FRAME + RunScript(ON_FRAME); + + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->RunTimeEvents(time); + } + + return true; +} + +/* +================ +idWindow::RunNamedEvent +================ +*/ +void idWindow::RunNamedEvent ( const char* eventName ) +{ + int i; + int c; + + // Find and run the event + c = namedEvents.Num( ); + for ( i = 0; i < c; i ++ ) { + if ( namedEvents[i]->mName.Icmp( eventName ) ) { + continue; + } + UpdateWinVars(); + const bool traceIngameCheck = !idStr::Icmp( eventName, "ingameCheck" ); + if ( traceIngameCheck ) { + common->DPrintf( "Q4 menu trace: ingameCheck begin gui::inGame='%s'\n", gui->State().GetString( "inGame" ) ); + } + + // Make sure we got all the current values for stuff + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(-1, true); + } + + RunScriptList( namedEvents[i]->mEvent ); + if ( traceIngameCheck ) { + drawWin_t *newGame = gui->GetDesktop()->FindChildByName( "main_b_newgame" ); + idWinVar *shown = newGame ? ( newGame->win ? newGame->win->GetWinVarByName( "visible" ) : newGame->simp->GetWinVarByName( "visible" ) ) : NULL; + common->DPrintf( "Q4 menu trace: ingameCheck end main_b_newgame visible=%s\n", shown ? shown->c_str() : "" ); + } + + break; + } + + // Run the event in all the children as well + c = children.Num(); + for ( i = 0; i < c; i++ ) { + children[i]->RunNamedEvent ( eventName ); + } +} + +/* +================ +idWindow::Contains +================ +*/ +bool idWindow::Contains(const idRectangle &sr, float x, float y) { + idRectangle r = sr; + r.x += actualX - drawRect.x; + r.y += actualY - drawRect.y; + return r.Contains(x, y); +} + +/* +================ +idWindow::Contains +================ +*/ +bool idWindow::Contains(float x, float y) { + idRectangle r = drawRect; + r.x = actualX; + r.y = actualY; + return r.Contains(x, y); +} + +/* +================ +idWindow::AddCommand +================ +*/ +void idWindow::AddCommand(const char *_cmd) { + idStr str = cmd; + if (str.Length()) { + str += " ; "; + str += _cmd; + } else { + str = _cmd; + } + cmd = str; +} + +/* +================ +idWindow::HandleEvent +================ +*/ +const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { + static bool actionDownRun; + static bool actionUpRun; + + cmd = ""; + + if ( flags & WIN_DESKTOP ) { + actionDownRun = false; + actionUpRun = false; + if (expressionRegisters.Num() && ops.Num()) { + EvalRegs(); + } + RunTimeEvents(gui->GetTime()); + CalcRects(0,0); + dc->SetCursor( idDeviceContext::CURSOR_ARROW ); + } + + if (visible && !noEvents) { + + if (event->evType == SE_KEY) { + EvalRegs(-1, true); + if (updateVisuals) { + *updateVisuals = true; + } + + if (event->evValue == K_MOUSE1) { + + if (!event->evValue2 && GetCaptureChild()) { + GetCaptureChild()->LoseCapture(); + gui->GetDesktop()->captureChild = NULL; + return ""; + } + + int c = children.Num(); + while (--c >= 0) { + if (children[c]->visible && children[c]->Contains(children[c]->drawRect, gui->CursorX(), gui->CursorY()) && !(children[c]->noEvents)) { + idWindow *child = children[c]; + if (event->evValue2) { + BringToTop(child); + SetFocus(child); + if (child->flags & WIN_HOLDCAPTURE) { + SetCapture(child); + } + } + if (child->Contains(child->clientRect, gui->CursorX(), gui->CursorY())) { + //if ((gui_edit.GetBool() && (child->flags & WIN_SELECTED)) || (!gui_edit.GetBool() && (child->flags & WIN_MOVABLE))) { + // SetCapture(child); + //} + SetFocus(child); + const char *childRet = child->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + if (child->flags & WIN_MODAL) { + return ""; + } + } else { + if (event->evValue2) { + SetFocus(child); + bool capture = true; + if (capture && ((child->flags & WIN_MOVABLE) || gui_edit.GetBool())) { + SetCapture(child); + } + return ""; + } else { + } + } + } + } + if (event->evValue2 && !actionDownRun) { + actionDownRun = RunScript( ON_ACTION ); + } else if (!actionUpRun) { + actionUpRun = RunScript( ON_ACTIONRELEASE ); + } + } else if (event->evValue == K_MOUSE2) { + + if (!event->evValue2 && GetCaptureChild()) { + GetCaptureChild()->LoseCapture(); + gui->GetDesktop()->captureChild = NULL; + return ""; + } + + int c = children.Num(); + while (--c >= 0) { + if (children[c]->visible && children[c]->Contains(children[c]->drawRect, gui->CursorX(), gui->CursorY()) && !(children[c]->noEvents)) { + idWindow *child = children[c]; + if (event->evValue2) { + BringToTop(child); + SetFocus(child); + } + if (child->Contains(child->clientRect,gui->CursorX(), gui->CursorY()) || GetCaptureChild() == child) { + if ((gui_edit.GetBool() && (child->flags & WIN_SELECTED)) || (!gui_edit.GetBool() && (child->flags & WIN_MOVABLE))) { + SetCapture(child); + } + const char *childRet = child->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + if (child->flags & WIN_MODAL) { + return ""; + } + } + } + } + } else if (event->evValue == K_MOUSE3) { + if (gui_edit.GetBool()) { + int c = children.Num(); + for (int i = 0; i < c; i++) { + if (children[i]->drawRect.Contains(gui->CursorX(), gui->CursorY())) { + if (event->evValue2) { + children[i]->flags ^= WIN_SELECTED; + if (children[i]->flags & WIN_SELECTED) { + flags &= ~WIN_SELECTED; + return "childsel"; + } + } + } + } + } + } else if (event->evValue == K_TAB && event->evValue2) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + + // If the window didn't handle the tab, then move the focus to the next window + // or the previous window if shift is held down + + int direction = 1; + if ( idKeyInput::IsDown( K_SHIFT ) ) { + direction = -1; + } + + idWindow *currentFocus = GetFocusedChild(); + idWindow *child = GetFocusedChild(); + idWindow *parent = child->GetParent(); + while ( parent ) { + bool foundFocus = false; + bool recurse = false; + int index = 0; + if ( child ) { + index = parent->GetChildIndex( child ) + direction; + } else if ( direction < 0 ) { + index = parent->GetChildCount() - 1; + } + while ( index < parent->GetChildCount() && index >= 0) { + idWindow *testWindow = parent->GetChild( index ); + if ( testWindow == currentFocus ) { + // we managed to wrap around and get back to our starting window + foundFocus = true; + break; + } + if ( testWindow && !testWindow->noEvents && testWindow->visible ) { + if ( testWindow->flags & WIN_CANFOCUS ) { + SetFocus( testWindow ); + foundFocus = true; + break; + } else if ( testWindow->GetChildCount() > 0 ) { + parent = testWindow; + child = NULL; + recurse = true; + break; + } + } + index += direction; + } + if ( foundFocus ) { + // We found a child to focus on + break; + } else if ( recurse ) { + // We found a child with children + continue; + } else { + // We didn't find anything, so go back up to our parent + child = parent; + parent = child->GetParent(); + if ( parent == gui->GetDesktop() ) { + // We got back to the desktop, so wrap around but don't actually go to the desktop + parent = NULL; + child = NULL; + } + } + } + } + } else if (event->evValue == K_ESCAPE && event->evValue2) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + RunScript( ON_ESC ); + } else if (event->evValue == K_ENTER ) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + if ( flags & WIN_WANTENTER ) { + if ( event->evValue2 ) { + RunScript( ON_ACTION ); + } else { + RunScript( ON_ACTIONRELEASE ); + } + } + } else { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + } + + } else if (event->evType == SE_MOUSE) { + if (updateVisuals) { + *updateVisuals = true; + } + const char *mouseRet = RouteMouseCoords(event->evValue, event->evValue2); + if (mouseRet && *mouseRet) { + return mouseRet; + } + } else if (event->evType == SE_NONE) { + } else if (event->evType == SE_CHAR) { + if (GetFocusedChild()) { + const char *childRet = GetFocusedChild()->HandleEvent(event, updateVisuals); + if (childRet && *childRet) { + return childRet; + } + } + } + } + + gui->GetReturnCmd() = cmd; + if ( gui->GetPendingCmd().Length() ) { + gui->GetReturnCmd() += " ; "; + gui->GetReturnCmd() += gui->GetPendingCmd(); + gui->GetPendingCmd().Clear(); + } + cmd = ""; + return gui->GetReturnCmd(); +} + +/* +================ +idWindow::DebugDraw +================ +*/ +void idWindow::DebugDraw(int time, float x, float y) { + static char buff[16384]; + if (dc) { + dc->EnableClipping(false); + if (gui_debug.GetInteger() == 1) { + dc->DrawRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, 1, idDeviceContext::colorRed); + } else if (gui_debug.GetInteger() == 2) { + char out[1024]; + idStr str; + str = text.c_str(); + + if (str.Length()) { + sprintf(buff, "%s\n", str.c_str()); + } + + sprintf(out, "Rect: %0.1f, %0.1f, %0.1f, %0.1f\n", rect.x(), rect.y(), rect.w(), rect.h()); + strcat(buff, out); + sprintf(out, "Draw Rect: %0.1f, %0.1f, %0.1f, %0.1f\n", drawRect.x, drawRect.y, drawRect.w, drawRect.h); + strcat(buff, out); + sprintf(out, "Client Rect: %0.1f, %0.1f, %0.1f, %0.1f\n", clientRect.x, clientRect.y, clientRect.w, clientRect.h); + strcat(buff, out); + sprintf(out, "Cursor: %0.1f : %0.1f\n", gui->CursorX(), gui->CursorY()); + strcat(buff, out); + + + //idRectangle tempRect = textRect; + //tempRect.x += offsetX; + //drawRect.y += offsetY; + dc->DrawText(buff, textScale, textAlign, foreColor, textRect, true); + } + dc->EnableClipping(true); + } +} + +/* +================ +idWindow::Transition +================ +*/ +void idWindow::Transition() { + int i, c = transitions.Num(); + bool clear = true; + + for ( i = 0; i < c; i++ ) { + idTransitionData *data = &transitions[i]; + idWinRectangle *r = NULL; + idWinVec4 *v4 = dynamic_cast(data->data); + idWinFloat* val = NULL; + idWinFloatMember *member = NULL; + if (v4 == NULL) { + r = dynamic_cast(data->data); + if ( !r ) { + val = dynamic_cast(data->data); + if ( !val ) { + member = dynamic_cast(data->data); + } + } + } + if ( data->interp.IsDone( gui->GetTime() ) && data->data) { + if (v4) { + *v4 = data->interp.GetEndValue(); + } else if ( val ) { + *val = data->interp.GetEndValue()[0]; + } else if ( member ) { + member->Set( data->interp.GetEndValue()[0] ); + } else { + *r = data->interp.GetEndValue(); + } + } else { + clear = false; + if (data->data) { + if (v4) { + *v4 = data->interp.GetCurrentValue( gui->GetTime() ); + } else if ( val ) { + *val = data->interp.GetCurrentValue( gui->GetTime() )[0]; + } else if ( member ) { + member->Set( data->interp.GetCurrentValue( gui->GetTime() )[0] ); + } else { + *r = data->interp.GetCurrentValue( gui->GetTime() ); + } + } else { + common->Warning("Invalid transitional data for window %s in gui %s", GetName(), gui->GetSourceFile()); + } + } + } + + if ( clear ) { + transitions.SetNum( 0, false ); + flags &= ~WIN_INTRANSITION; + } +} + +/* +================ +idWindow::Time +================ +*/ +void idWindow::Time() { + + if ( noTime ) { + return; + } + + if ( timeLine == -1 ) { + timeLine = gui->GetTime(); + } + + cmd = ""; + + int c = timeLineEvents.Num(); + if ( c > 0 ) { + for (int i = 0; i < c; i++) { + if ( timeLineEvents[i]->pending && gui->GetTime() - timeLine >= timeLineEvents[i]->time ) { + if ( !idStr::Icmp( name, "anim_in" ) || !idStr::Icmp( name, "video_bethsoft" ) ) { + common->DPrintf( "Q4 menu trace: timeline '%s' firing %d at guiTime=%d epoch=%d\n", + name.c_str(), timeLineEvents[i]->time, gui->GetTime(), timeLine ); + } + timeLineEvents[i]->pending = false; + RunScriptList( timeLineEvents[i]->event ); + } + } + } + if ( gui->Active() ) { + gui->GetPendingCmd() += cmd; + } +} + +/* +================ +idWindow::EvalRegs +================ +*/ +float idWindow::EvalRegs(int test, bool force) { + static float regs[MAX_EXPRESSION_REGISTERS]; + static idWindow *lastEval = NULL; + + if (!force && test >= 0 && test < MAX_EXPRESSION_REGISTERS && lastEval == this) { + return regs[test]; + } + + lastEval = this; + + if (expressionRegisters.Num()) { + regList.SetToRegs(regs); + EvaluateRegisters(regs); + regList.GetFromRegs(regs); + } + + if (test >= 0 && test < MAX_EXPRESSION_REGISTERS) { + return regs[test]; + } + + return 0.0; +} + +/* +================ +idWindow::DrawBackground +================ +*/ +void idWindow::DrawBackground(const idRectangle &drawRect) { + if ( backColor.w() ) { + dc->DrawFilledRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, backColor); + } + + if ( background && matColor.w() ) { + float scalex, scaley; + if ( flags & WIN_NATURALMAT ) { + scalex = drawRect.w / background->GetImageWidth(); + scaley = drawRect.h / background->GetImageHeight(); + } else { + scalex = matScalex; + scaley = matScaley; + } + dc->DrawMaterial(drawRect.x, drawRect.y, drawRect.w, drawRect.h, background, matColor, scalex, scaley); + } +} + +/* +================ +idWindow::DrawBorderAndCaption +================ +*/ +void idWindow::DrawBorderAndCaption(const idRectangle &drawRect) { + if ( flags & WIN_BORDER && borderSize && borderColor.w() ) { + dc->DrawRect(drawRect.x, drawRect.y, drawRect.w, drawRect.h, borderSize, borderColor); + } +} + +/* +================ +idWindow::SetupTransforms +================ +*/ +void idWindow::SetupTransforms(float x, float y) { + static idMat3 trans; + static idVec3 org; + + trans.Identity(); + org.Set( origin.x + x, origin.y + y, 0 ); + + if ( rotate ) { + static idRotation rot; + static idVec3 vec(0, 0, 1); + rot.Set( org, vec, rotate ); + trans = rot.ToMat3(); + } + + if ( shear.x || shear.y ) { + static idMat3 smat; + smat.Identity(); + smat[0][1] = shear.x; + smat[1][0] = shear.y; + trans *= smat; + } + + if ( !trans.IsIdentity() ) { + dc->SetTransformInfo( org, trans ); + } +} + +/* +================ +idWindow::CalcRects +================ +*/ +void idWindow::CalcRects(float x, float y) { + CalcClientRect(0, 0); + drawRect.Offset(x, y); + clientRect.Offset(x, y); + actualX = drawRect.x; + actualY = drawRect.y; + int c = drawWindows.Num(); + for (int i = 0; i < c; i++) { + if (drawWindows[i].win) { + drawWindows[i].win->CalcRects(clientRect.x + xOffset, clientRect.y + yOffset); + } + } + drawRect.Offset(-x, -y); + clientRect.Offset(-x, -y); +} + +/* +================ +idWindow::Redraw +================ +*/ +void idWindow::Redraw(float x, float y) { + idStr str; + + if (r_skipGuiShaders.GetInteger() == 1 || dc == NULL ) { + return; + } + + int time = gui->GetTime(); + + if ( flags & WIN_DESKTOP && r_skipGuiShaders.GetInteger() != 3 ) { + RunTimeEvents( time ); + } + + if ( r_skipGuiShaders.GetInteger() == 2 ) { + return; + } + + if ( flags & WIN_SHOWTIME ) { + dc->DrawText(va(" %0.1f seconds\n%s", (float)(time - timeLine) / 1000, gui->State().GetString("name")), 0.35f, 0, dc->colorWhite, idRectangle(100, 0, 80, 80), false); + } + + if ( flags & WIN_SHOWCOORDS ) { + dc->EnableClipping(false); + sprintf(str, "x: %i y: %i cursorx: %i cursory: %i", (int)rect.x(), (int)rect.y(), (int)gui->CursorX(), (int)gui->CursorY()); + dc->DrawText(str, 0.25f, 0, dc->colorWhite, idRectangle(0, 0, 100, 20), false); + dc->EnableClipping(true); + } + + if (!visible) { + return; + } + + CalcClientRect(0, 0); + + SetFont(); + //if (flags & WIN_DESKTOP) { + // see if this window forces a new aspect ratio + dc->SetSize(forceAspectWidth, forceAspectHeight); + //} + + //FIXME: go to screen coord tracking + drawRect.Offset(x, y); + clientRect.Offset(x, y); + textRect.Offset(x, y); + actualX = drawRect.x; + actualY = drawRect.y; + + idVec3 oldOrg; + idMat3 oldTrans; + + dc->GetTransformInfo( oldOrg, oldTrans ); + + SetupTransforms(x, y); + DrawBackground(drawRect); + DrawBorderAndCaption(drawRect); + + if ( !( flags & WIN_NOCLIP) ) { + dc->PushClipRect(clientRect); + } + + if ( r_skipGuiShaders.GetInteger() < 5 ) { + Draw(time, x, y); + } + + if ( gui_debug.GetInteger() ) { + DebugDraw(time, x, y); + } + + int c = drawWindows.Num(); + for ( int i = 0; i < c; i++ ) { + if ( drawWindows[i].win ) { + drawWindows[i].win->Redraw( clientRect.x + xOffset, clientRect.y + yOffset ); + } else { + drawWindows[i].simp->Redraw( clientRect.x + xOffset, clientRect.y + yOffset ); + } + } + + // Put transforms back to what they were before the children were processed + dc->SetTransformInfo(oldOrg, oldTrans); + + if ( ! ( flags & WIN_NOCLIP ) ) { + dc->PopClipRect(); + } + + if (gui_edit.GetBool() || (flags & WIN_DESKTOP && !( flags & WIN_NOCURSOR ) && !hideCursor && (gui->Active() || ( flags & WIN_MENUGUI ) ))) { + dc->SetTransformInfo(vec3_origin, mat3_identity); + gui->DrawCursor(); + } + + if (gui_debug.GetInteger() && flags & WIN_DESKTOP) { + dc->EnableClipping(false); + sprintf(str, "x: %1.f y: %1.f", gui->CursorX(), gui->CursorY()); + dc->DrawText(str, 0.25, 0, dc->colorWhite, idRectangle(0, 0, 100, 20), false); + dc->DrawText(gui->GetSourceFile(), 0.25, 0, dc->colorWhite, idRectangle(0, 20, 300, 20), false); + dc->EnableClipping(true); + } + + drawRect.Offset(-x, -y); + clientRect.Offset(-x, -y); + textRect.Offset(-x, -y); +} + +/* +================ +idWindow::SetDC +================ +*/ +void idWindow::SetDC(idDeviceContext *d) { + dc = d; + //if (flags & WIN_DESKTOP) { + dc->SetSize(forceAspectWidth, forceAspectHeight); + //} + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->SetDC(d); + } +} + +/* +================ +idWindow::ArchiveToDictionary +================ +*/ +void idWindow::ArchiveToDictionary(idDict *dict, bool useNames) { + //FIXME: rewrite without state + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->ArchiveToDictionary(dict); + } +} + +/* +================ +idWindow::InitFromDictionary +================ +*/ +void idWindow::InitFromDictionary(idDict *dict, bool byName) { + //FIXME: rewrite without state + int c = children.Num(); + for (int i = 0; i < c; i++) { + children[i]->InitFromDictionary(dict); + } +} + +/* +================ +idWindow::CalcClientRect +================ +*/ +void idWindow::CalcClientRect(float xofs, float yofs) { + drawRect = rect; + + if ( flags & WIN_INVERTRECT ) { + drawRect.x = rect.x() - rect.w(); + drawRect.y = rect.y() - rect.h(); + } + + if (flags & (WIN_HCENTER | WIN_VCENTER) && parent) { + // in this case treat xofs and yofs as absolute top left coords + // and ignore the original positioning + if (flags & WIN_HCENTER) { + drawRect.x = (parent->rect.w() - rect.w()) / 2; + } else { + drawRect.y = (parent->rect.h() - rect.h()) / 2; + } + } + + drawRect.x += xofs; + drawRect.y += yofs; + + clientRect = drawRect; + if (rect.h() > 0.0 && rect.w() > 0.0) { + + if (flags & WIN_BORDER && borderSize != 0.0) { + clientRect.x += borderSize; + clientRect.y += borderSize; + clientRect.w -= borderSize; + clientRect.h -= borderSize; + } + + textRect = clientRect; + textRect.x += 2.0; + textRect.w -= 2.0; + textRect.y += 2.0; + textRect.h -= 2.0; + + textRect.x += textAlignx; + textRect.y += textAligny; + + } + origin.Set( rect.x() + (rect.w() / 2 ), rect.y() + ( rect.h() / 2 ) ); + +} + +/* +================ +idWindow::SetupBackground +================ +*/ +void idWindow::SetupBackground() { + if (backGroundName.Length()) { + background = declManager->FindMaterial(backGroundName); + background->SetImageClassifications( 1 ); // just for resource tracking + if ( background && !background->TestMaterialFlag( MF_DEFAULTED ) ) { + background->SetSort(SS_GUI ); + } + } + backGroundName.SetMaterialPtr(&background); +} + +/* +================ +idWindow::SetupFromState +================ +*/ +void idWindow::SetupFromState() { + idStr str; + background = NULL; + + SetupBackground(); + + if (borderSize) { + flags |= WIN_BORDER; + } + + if (regList.FindReg("rotate") || regList.FindReg("shear")) { + flags |= WIN_TRANSFORM; + } + + CalcClientRect(0,0); + if ( scripts[ ON_ACTION ] ) { + cursor = idDeviceContext::CURSOR_HAND; + flags |= WIN_CANFOCUS; + } +} + +/* +================ +idWindow::Moved +================ +*/ +void idWindow::Moved() { +} + +/* +================ +idWindow::Sized +================ +*/ +void idWindow::Sized() { +} + +/* +================ +idWindow::GainFocus +================ +*/ +void idWindow::GainFocus() { +} + +/* +================ +idWindow::LoseFocus +================ +*/ +void idWindow::LoseFocus() { +} + +/* +================ +idWindow::GainCapture +================ +*/ +void idWindow::GainCapture() { +} + +/* +================ +idWindow::LoseCapture +================ +*/ +void idWindow::LoseCapture() { + flags &= ~WIN_CAPTURE; +} + +/* +================ +idWindow::SetFlag +================ +*/ +void idWindow::SetFlag(unsigned int f) { + flags |= f; +} + +/* +================ +idWindow::ClearFlag +================ +*/ +void idWindow::ClearFlag(unsigned int f) { + flags &= ~f; +} + + +/* +================ +idWindow::SetParent +================ +*/ +void idWindow::SetParent(idWindow *w) { + parent = w; +} + +/* +================ +idWindow::GetCaptureChild +================ +*/ +idWindow *idWindow::GetCaptureChild() { + if (flags & WIN_DESKTOP) { + return gui->GetDesktop()->captureChild; + } + return NULL; +} + +/* +================ +idWindow::GetFocusedChild +================ +*/ +idWindow *idWindow::GetFocusedChild() { + if (flags & WIN_DESKTOP) { + return gui->GetDesktop()->focusedChild; + } + return NULL; +} + + +/* +================ +idWindow::SetFocus +================ +*/ +idWindow *idWindow::SetFocus(idWindow *w, bool scripts) { + // only one child can have the focus + idWindow *lastFocus = NULL; + if (w->flags & WIN_CANFOCUS) { + lastFocus = gui->GetDesktop()->focusedChild; + if ( lastFocus ) { + lastFocus->flags &= ~WIN_FOCUS; + lastFocus->LoseFocus(); + } + + // call on lose focus + if ( scripts && lastFocus ) { + // calling this broke all sorts of guis + // lastFocus->RunScript(ON_MOUSEEXIT); + } + // call on gain focus + if ( scripts && w ) { + // calling this broke all sorts of guis + // w->RunScript(ON_MOUSEENTER); + } + + w->flags |= WIN_FOCUS; + w->GainFocus(); + gui->GetDesktop()->focusedChild = w; + } + + return lastFocus; +} + +/* +================ +idWindow::ParseScript +================ +*/ +bool idWindow::ParseScript(idParser *src, idGuiScriptList &list, int *timeParm, bool elseBlock ) { + + bool ifElseBlock = false; + + idToken token; + + // scripts start with { ( unless parm is true ) and have ; separated command lists.. commands are command, + // arg.. basically we want everything between the { } as it will be interpreted at + // run time + + if ( elseBlock ) { + src->ReadToken ( &token ); + + if ( !token.Icmp ( "if" ) ) { + ifElseBlock = true; + } + + src->UnreadToken ( &token ); + + if ( !ifElseBlock && !src->ExpectTokenString( "{" ) ) { + return false; + } + } + else if ( !src->ExpectTokenString( "{" ) ) { + return false; + } + + int nest = 0; + + while (1) { + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + + if ( token == "{" ) { + nest++; + } + + if ( token == "}" ) { + if (nest-- <= 0) { + return true; + } + } + + idGuiScript *gs = new idGuiScript(); + if (token.Icmp("if") == 0) { + gs->conditionReg = ParseExpression(src); + gs->ifList = new idGuiScriptList(); + ParseScript(src, *gs->ifList, NULL); + if (src->ReadToken(&token)) { + if (token == "else") { + gs->elseList = new idGuiScriptList(); + // pass true to indicate we are parsing an else condition + ParseScript(src, *gs->elseList, NULL, true ); + } else { + src->UnreadToken(&token); + } + } + + list.Append(gs); + + // if we are parsing an else if then return out so + // the initial "if" parser can handle the rest of the tokens + if ( ifElseBlock ) { + return true; + } + continue; + } else { + src->UnreadToken(&token); + } + + // empty { } is not allowed + if ( token == "{" ) { + src->Error ( "Unexpected {" ); + delete gs; + return false; + } + + gs->Parse(src); + list.Append(gs); + } + +} + +/* +================ +idWindow::SaveExpressionParseState +================ +*/ +void idWindow::SaveExpressionParseState() { + saveTemps = (bool*)Mem_Alloc(MAX_EXPRESSION_REGISTERS * sizeof(bool)); + memcpy(saveTemps, registerIsTemporary, MAX_EXPRESSION_REGISTERS * sizeof(bool)); +} + +/* +================ +idWindow::RestoreExpressionParseState +================ +*/ +void idWindow::RestoreExpressionParseState() { + memcpy(registerIsTemporary, saveTemps, MAX_EXPRESSION_REGISTERS * sizeof(bool)); + Mem_Free(saveTemps); +} + +/* +================ +idWindow::ParseScriptEntry +================ +*/ +bool idWindow::ParseScriptEntry(const char *name, idParser *src) { + for (int i = 0; i < SCRIPT_COUNT; i++) { + if (idStr::Icmp(name, ScriptNames[i]) == 0) { + delete scripts[i]; + scripts[i] = new idGuiScriptList; + return ParseScript(src, *scripts[i]); + } + } + return false; +} + +/* +================ +idWindow::DisableRegister +================ +*/ +void idWindow::DisableRegister(const char *_name) { + idRegister *reg = RegList()->FindReg(_name); + if (reg) { + reg->Enable(false); + } +} + +/* +================ +idWindow::PostParse +================ +*/ +void idWindow::PostParse() { +} + +/* +================ +idWindow::GetWinVarOffset +================ +*/ +int idWindow::GetWinVarOffset( idWinVar *wv, drawWin_t* owner) { + int ret = -1; + + if ( wv == &rect ) { + ret = (int)&( ( idWindow * ) 0 )->rect; + } + + if ( wv == &backColor ) { + ret = (int)&( ( idWindow * ) 0 )->backColor; + } + + if ( wv == &matColor ) { + ret = (int)&( ( idWindow * ) 0 )->matColor; + } + + if ( wv == &foreColor ) { + ret = (int)&( ( idWindow * ) 0 )->foreColor; + } + + if ( wv == &hoverColor ) { + ret = (int)&( ( idWindow * ) 0 )->hoverColor; + } + + if ( wv == &borderColor ) { + ret = (int)&( ( idWindow * ) 0 )->borderColor; + } + + if ( wv == &textScale ) { + ret = (int)&( ( idWindow * ) 0 )->textScale; + } + + if ( wv == &rotate ) { + ret = (int)&( ( idWindow * ) 0 )->rotate; + } + + if ( ret != -1 ) { + owner->win = this; + return ret; + } + + for ( int i = 0; i < drawWindows.Num(); i++ ) { + if ( drawWindows[i].win ) { + ret = drawWindows[i].win->GetWinVarOffset( wv, owner ); + } else { + ret = drawWindows[i].simp->GetWinVarOffset( wv, owner ); + } + if ( ret != -1 ) { + break; + } + } + + return ret; +} + +/* +================ +idWindow::GetWinVarByName +================ +*/ +idWinVar *idWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { + idWinVar *retVar = NULL; + + if ( owner ) { + *owner = NULL; + } + + if (idStr::Icmp(_name, "notime") == 0) { + retVar = &noTime; + } + if (idStr::Icmp(_name, "background") == 0) { + retVar = &backGroundName; + } + if (idStr::Icmp(_name, "visible") == 0) { + retVar = &visible; + } + if (idStr::Icmp(_name, "rect") == 0) { + retVar = ▭ + } + if (idStr::Icmp(_name, "backColor") == 0) { + retVar = &backColor; + } + if (idStr::Icmp(_name, "matColor") == 0) { + retVar = &matColor; + } + if (idStr::Icmp(_name, "foreColor") == 0) { + retVar = &foreColor; + } + if (idStr::Icmp(_name, "hoverColor") == 0) { + retVar = &hoverColor; + } + if (idStr::Icmp(_name, "borderColor") == 0) { + retVar = &borderColor; + } + if (idStr::Icmp(_name, "textScale") == 0) { + retVar = &textScale; + } + if (idStr::Icmp(_name, "rotate") == 0) { + retVar = &rotate; + } + if (idStr::Icmp(_name, "noEvents") == 0) { + retVar = &noEvents; + } + if (idStr::Icmp(_name, "text") == 0) { + retVar = &text; + } + if (idStr::Icmp(_name, "backGroundName") == 0) { + retVar = &backGroundName; + } + if (idStr::Icmp(_name, "hidecursor") == 0) { + retVar = &hideCursor; + } + + idStr key = _name; + bool guiVar = (key.Find(VAR_GUIPREFIX) >= 0); + int c = definedVars.Num(); + for (int i = 0; i < c; i++) { + if (idStr::Icmp(_name, (guiVar) ? va("%s",definedVars[i]->GetName()) : definedVars[i]->GetName()) == 0) { + retVar = definedVars[i]; + break; + } + } + + if (retVar) { + if (fixup && *_name != '$') { + DisableRegister(_name); + } + + if ( owner && parent ) { + *owner = parent->FindChildByName ( name ); + } + + return retVar; + } + + int len = key.Length(); + if ( len > 5 && guiVar ) { + idWinVar *var = new idWinStr; + var->Init(_name, this); + definedVars.Append(var); + return var; + } else if (fixup) { + int n = key.Find("::"); + if (n > 0) { + idStr winName = key.Left(n); + idStr var = key.Right(key.Length() - n - 2); + drawWin_t *win = GetGui()->GetDesktop()->FindChildByName(winName); + if (win) { + if (win->win) { + return win->win->GetWinVarByName(var, false, owner); + } else { + if ( owner ) { + *owner = win; + } + return win->simp->GetWinVarByName(var); + } + } + } + } + + // Quake 4 GUI scripts may address a vec4/rectangle component by suffix. + // Keep these proxy variables in the window's defined-var list so repeated + // lookups return the same object and transitions retain a stable target. + const char *underscore = strrchr( _name, '_' ); + if ( underscore && underscore[1] && !underscore[2] ) { + const char *component = strchr( "xyzw", underscore[1] ); + if ( component ) { + idStr baseName( _name, 0, static_cast( underscore - _name ) ); + idWinVar *parentVar = GetWinVarByName( baseName, fixup, owner ); + if ( dynamic_cast( parentVar ) || dynamic_cast( parentVar ) ) { + idWinFloatMember *member = new idWinFloatMember( parentVar, static_cast( component - "xyzw" ) ); + member->SetName( _name ); + definedVars.Append( member ); + return member; + } + } + } + return NULL; +} + +/* +================ +idWindow::ParseString +================ +*/ +void idWindow::ParseString(idParser *src, idStr &out) { + idToken tok; + if (src->ReadToken(&tok)) { + out = tok; + } +} + +/* +================ +idWindow::ParseVec4 +================ +*/ +void idWindow::ParseVec4(idParser *src, idVec4 &out) { + idToken tok; + src->ReadToken(&tok); + out.x = atof(tok); + src->ExpectTokenString(","); + src->ReadToken(&tok); + out.y = atof(tok); + src->ExpectTokenString(","); + src->ReadToken(&tok); + out.z = atof(tok); + src->ExpectTokenString(","); + src->ReadToken(&tok); + out.w = atof(tok); +} + +/* +================ +idWindow::ParseInternalVar +================ +*/ +bool idWindow::ParseInternalVar(const char *_name, idParser *src) { + + if (idStr::Icmp(_name, "showtime") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_SHOWTIME; + } + return true; + } + if (idStr::Icmp(_name, "showcoords") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_SHOWCOORDS; + } + return true; + } + if (idStr::Icmp(_name, "forceaspectwidth") == 0) { + forceAspectWidth = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "forceaspectheight") == 0) { + forceAspectHeight = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "matscalex") == 0) { + matScalex = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "matscaley") == 0) { + matScaley = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "bordersize") == 0) { + borderSize = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "nowrap") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NOWRAP; + } + return true; + } + if (idStr::Icmp(_name, "textspacing") == 0) { + textSpacing = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "shadow") == 0) { + textShadow = src->ParseInt(); + return true; + } + if (idStr::Icmp(_name, "textalign") == 0) { + textAlign = src->ParseInt(); + return true; + } + if (idStr::Icmp(_name, "textalignx") == 0) { + textAlignx = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "textaligny") == 0) { + textAligny = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "textstyle") == 0) { + textStyle = src->ParseInt(); + return true; + } + if (idStr::Icmp(_name, "shear") == 0) { + shear.x = src->ParseFloat(); + idToken tok; + src->ReadToken( &tok ); + if ( tok.Icmp( "," ) ) { + src->Error( "Expected comma in shear definiation" ); + return false; + } + shear.y = src->ParseFloat(); + return true; + } + if (idStr::Icmp(_name, "wantenter") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_WANTENTER; + } + return true; + } + if (idStr::Icmp(_name, "naturalmatscale") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NATURALMAT; + } + return true; + } + if (idStr::Icmp(_name, "noclip") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NOCLIP; + } + return true; + } + if (idStr::Icmp(_name, "nocursor") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_NOCURSOR; + } + return true; + } + if (idStr::Icmp(_name, "menugui") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_MENUGUI; + } + return true; + } + if (idStr::Icmp(_name, "modal") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_MODAL; + } + return true; + } + if (idStr::Icmp(_name, "invertrect") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_INVERTRECT; + } + return true; + } + if (idStr::Icmp(_name, "alwaysThink") == 0) { + if ( src->ParseBool() ) { + flags |= WIN_ALWAYSTHINK; + } + return true; + } + if (idStr::Icmp(_name, "name") == 0) { + ParseString(src, name); + return true; + } + if (idStr::Icmp(_name, "play") == 0) { + common->Warning( "play encountered during gui parse.. see Robert\n" ); + idStr playStr; + ParseString(src, playStr); + return true; + } + if (idStr::Icmp(_name, "comment") == 0) { + ParseString(src, comment); + return true; + } + if ( idStr::Icmp( _name, "font" ) == 0 ) { + idStr fontStr; + ParseString( src, fontStr ); + fontNum = dc->FindFont( fontStr ); + return true; + } + return false; +} + +/* +================ +idWindow::ParseRegEntry +================ +*/ +bool idWindow::ParseRegEntry(const char *name, idParser *src) { + idStr work; + work = name; + work.ToLower(); + + idWinVar *var = GetWinVarByName(work, NULL); + if ( var ) { + for (int i = 0; i < NumRegisterVars; i++) { + if (idStr::Icmp(work, RegisterVars[i].name) == 0) { + regList.AddReg(work, RegisterVars[i].type, src, this, var); + return true; + } + } + } + + // not predefined so just read the next token and add it to the state + idToken tok; + idVec4 v; + idWinInt *vari; + idWinFloat *varf; + idWinStr *vars; + if (src->ReadToken(&tok)) { + if (var) { + var->Set(tok); + return true; + } + switch (tok.type) { + case TT_NUMBER : + if (tok.subtype & TT_INTEGER) { + vari = new idWinInt(); + *vari = atoi(tok); + vari->SetName(work); + definedVars.Append(vari); + } else if (tok.subtype & TT_FLOAT) { + varf = new idWinFloat(); + *varf = atof(tok); + varf->SetName(work); + definedVars.Append(varf); + } else { + vars = new idWinStr(); + *vars = tok; + vars->SetName(work); + definedVars.Append(vars); + } + break; + default : + vars = new idWinStr(); + *vars = tok; + vars->SetName(work); + definedVars.Append(vars); + break; + } + } + + return true; +} + +/* +================ +idWindow::SetInitialState +================ +*/ +void idWindow::SetInitialState(const char *_name) { + name = _name; + matScalex = 1.0; + matScaley = 1.0; + forceAspectWidth = 640.0; + forceAspectHeight = 480.0; + noTime = false; + visible = true; + flags = 0; +} + +/* +================ +idWindow::Parse +================ +*/ +bool idWindow::Parse( idParser *src, bool rebuild) { + idToken token, token2, token3, token4, token5, token6, token7; + idStr work; + + if (rebuild) { + CleanUp(); + } + + drawWin_t dwt; + + timeLineEvents.Clear(); + transitions.Clear(); + + namedEvents.DeleteContents( true ); + + src->ExpectTokenType( TT_NAME, 0, &token ); + + SetInitialState(token); + + src->ExpectTokenString( "{" ); + src->ExpectAnyToken( &token ); + + bool ret = true; + + // attach a window wrapper to the window if the gui editor is running +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + new rvGEWindowWrapper ( this, rvGEWindowWrapper::WT_NORMAL ); + } +#endif + + while( token != "}" ) { + // track what was parsed so we can maintain it for the guieditor + src->SetMarker ( ); + + if ( token == "windowDef" || token == "animationDef" ) { + if (token == "animationDef") { + visible = false; + rect = idRectangle(0,0,0,0); + } + src->ExpectTokenType( TT_NAME, 0, &token ); + token2 = token; + src->UnreadToken(&token); + drawWin_t *dw = FindChildByName(token2.c_str()); + if (dw && dw->win) { + SaveExpressionParseState(); + dw->win->Parse(src, rebuild); + RestoreExpressionParseState(); + } else { + idWindow *win = new idWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = NULL; + if (win->IsSimple()) { + idSimpleWindow *simple = new idSimpleWindow(win); + dwt.simp = simple; + drawWindows.Append(dwt); + delete win; + } else { + AddChild(win); + SetFocus(win,false); + dwt.win = win; + drawWindows.Append(dwt); + } + } + } + else if ( token == "editDef" ) { + idEditWindow *win = new idEditWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "choiceDef" ) { + idChoiceWindow *win = new idChoiceWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "sliderDef" ) { + idSliderWindow *win = new idSliderWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "markerDef" ) { + idMarkerWindow *win = new idMarkerWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "bindDef" ) { + idBindWindow *win = new idBindWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "listDef" ) { + idListWindow *win = new idListWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "fieldDef" ) { + idFieldWindow *win = new idFieldWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "renderDef" ) { + idRenderWindow *win = new idRenderWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "gameSSDDef" ) { + idWindow *win = new idWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "gameBearShootDef" ) { + idWindow *win = new idWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } + else if ( token == "gameBustOutDef" ) { + idWindow *win = new idWindow(dc, gui); + SaveExpressionParseState(); + win->Parse(src, rebuild); + RestoreExpressionParseState(); + AddChild(win); + win->SetParent(this); + dwt.simp = NULL; + dwt.win = win; + drawWindows.Append(dwt); + } +// +// added new onEvent + else if ( token == "onNamedEvent" ) { + // Read the event name + if ( !src->ReadToken(&token) ) { + src->Error( "Expected event name" ); + return false; + } + + rvNamedEvent* ev = new rvNamedEvent ( token ); + + src->SetMarker ( ); + + if ( !ParseScript ( src, *ev->mEvent ) ) { + ret = false; + break; + } + + // If we are in the gui editor then add the internal var to the + // the wrapper +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + idStr out; + + // Grab the string from the last marker + src->GetStringFromMarker ( str, false ); + + // Parse it one more time to knock unwanted tabs out + idLexer src2( str, str.Length(), "", src->GetFlags() ); + src2.ParseBracedSectionExact ( out, 1); + + // Save the script + rvGEWindowWrapper::GetWrapper ( this )->GetScriptDict().Set ( va("onEvent %s", token.c_str()), out ); + } +#endif + namedEvents.Append(ev); + } + else if ( token == "onTime" ) { + idTimeLineEvent *ev = new idTimeLineEvent; + + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + return false; + } + if ( token == "+" ) { + if ( !src->ReadToken(&token) ) { + src->Error( "Unexpected end of file" ); + delete ev; + return false; + } + ev->time = timeLineEvents.Num() ? timeLineEvents[timeLineEvents.Num() - 1]->time : 0; + ev->time += atoi(token.c_str()); + } else { + ev->time = atoi(token.c_str()); + } + + // reset the mark since we dont want it to include the time + src->SetMarker ( ); + + if (!ParseScript(src, *ev->event, &ev->time)) { + ret = false; + break; + } + + // add the script to the wrappers script list + // If we are in the gui editor then add the internal var to the + // the wrapper +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + idStr out; + + // Grab the string from the last marker + src->GetStringFromMarker ( str, false ); + + // Parse it one more time to knock unwanted tabs out + idLexer src2( str, str.Length(), "", src->GetFlags() ); + src2.ParseBracedSectionExact ( out, 1); + + // Save the script + rvGEWindowWrapper::GetWrapper ( this )->GetScriptDict().Set ( va("onTime %d", ev->time), out ); + } +#endif + // this is a timeline event + ev->pending = true; + timeLineEvents.Append(ev); + } + else if ( token == "definefloat" ) { + src->ReadToken(&token); + work = token; + work.ToLower(); + idWinFloat *varf = new idWinFloat(); + varf->SetName(work); + definedVars.Append(varf); + + // add the float to the editors wrapper dict + // Set the marker after the float name + src->SetMarker ( ); + + // Read in the float + regList.AddReg(work, idRegister::FLOAT, src, this, varf); + + // If we are in the gui editor then add the float to the defines +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + + // Grab the string from the last marker and save it in the wrapper + src->GetStringFromMarker ( str, true ); + rvGEWindowWrapper::GetWrapper ( this )->GetVariableDict().Set ( va("definefloat\t\"%s\"",token.c_str()), str ); + } +#endif + } + else if ( token == "defineicon" ) { + idToken iconName; + idToken iconMaterial; + if ( !src->ReadToken( &iconName ) || !src->ReadToken( &iconMaterial ) ) { + src->Error( "Unexpected end of file in defineicon" ); + ret = false; + break; + } + + int iconX = -1; + int iconY = -1; + int iconW = -1; + int iconH = -1; + idToken optional; + if ( src->ReadToken( &optional ) ) { + if ( optional == "," ) { + iconX = src->ParseInt(); + src->ExpectTokenString( "," ); + iconY = src->ParseInt(); + src->ExpectTokenString( "," ); + iconW = src->ParseInt(); + src->ExpectTokenString( "," ); + iconH = src->ParseInt(); + } else { + src->UnreadToken( &optional ); + } + } + dc->RegisterIcon( iconName, iconMaterial, iconX, iconY, iconW, iconH ); + } + else if ( token == "definevec4" ) { + src->ReadToken(&token); + work = token; + work.ToLower(); + idWinVec4 *var = new idWinVec4(); + var->SetName(work); + + // set the marker so we can determine what was parsed + // set the marker after the vec4 name + src->SetMarker ( ); + + // FIXME: how about we add the var to the desktop instead of this window so it won't get deleted + // when this window is destoyed which even happens during parsing with simple windows ? + //definedVars.Append(var); + gui->GetDesktop()->definedVars.Append( var ); + gui->GetDesktop()->regList.AddReg( work, idRegister::VEC4, src, gui->GetDesktop(), var ); + + // store the original vec4 for the editor + // If we are in the gui editor then add the float to the defines +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + + // Grab the string from the last marker and save it in the wrapper + src->GetStringFromMarker ( str, true ); + rvGEWindowWrapper::GetWrapper ( this )->GetVariableDict().Set ( va("definevec4\t\"%s\"",token.c_str()), str ); + } +#endif + } + else if ( token == "float" ) { + src->ReadToken(&token); + work = token; + work.ToLower(); + idWinFloat *varf = new idWinFloat(); + varf->SetName(work); + definedVars.Append(varf); + + // add the float to the editors wrapper dict + // set the marker to after the float name + src->SetMarker ( ); + + // Parse the float + regList.AddReg(work, idRegister::FLOAT, src, this, varf); + + // If we are in the gui editor then add the float to the defines +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + + // Grab the string from the last marker and save it in the wrapper + src->GetStringFromMarker ( str, true ); + rvGEWindowWrapper::GetWrapper ( this )->GetVariableDict().Set ( va("float\t\"%s\"",token.c_str()), str ); + } +#endif + } + else if (ParseScriptEntry(token, src)) { + // add the script to the wrappers script list + // If we are in the gui editor then add the internal var to the + // the wrapper +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + idStr out; + + // Grab the string from the last marker + src->GetStringFromMarker ( str, false ); + + // Parse it one more time to knock unwanted tabs out + idLexer src2( str, str.Length(), "", src->GetFlags() ); + src2.ParseBracedSectionExact ( out, 1); + + // Save the script + rvGEWindowWrapper::GetWrapper ( this )->GetScriptDict().Set ( token, out ); + } +#endif + } else if (ParseInternalVar(token, src)) { + // gui editor support + // If we are in the gui editor then add the internal var to the + // the wrapper +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + src->GetStringFromMarker ( str ); + rvGEWindowWrapper::GetWrapper ( this )->SetStateKey ( token, str, false ); + } +#endif + } + else { + ParseRegEntry(token, src); + // hook into the main window parsing for the gui editor + // If we are in the gui editor then add the internal var to the + // the wrapper +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + idStr str; + src->GetStringFromMarker ( str ); + rvGEWindowWrapper::GetWrapper ( this )->SetStateKey ( token, str, false ); + } +#endif + } + if ( !src->ReadToken( &token ) ) { + src->Error( "Unexpected end of file" ); + ret = false; + break; + } + } + + if (ret) { + EvalRegs(-1, true); + } + + SetupFromState(); + PostParse(); + + // hook into the main window parsing for the gui editor + // If we are in the gui editor then add the internal var to the + // the wrapper +#ifdef ID_ALLOW_TOOLS + if ( com_editors & EDITOR_GUI ) { + rvGEWindowWrapper::GetWrapper ( this )->Finish ( ); + } +#endif + + return ret; +} + +/* +================ +idWindow::FindSimpleWinByName +================ +*/ +idSimpleWindow *idWindow::FindSimpleWinByName(const char *_name) { + int c = drawWindows.Num(); + for (int i = 0; i < c; i++) { + if (drawWindows[i].simp == NULL) { + continue; + } + if ( idStr::Icmp(drawWindows[i].simp->name, _name) == 0 ) { + return drawWindows[i].simp; + } + } + return NULL; +} + +/* +================ +idWindow::FindChildByName +================ +*/ +drawWin_t *idWindow::FindChildByName(const char *_name) { + static drawWin_t dw; + if (idStr::Icmp(name,_name) == 0) { + dw.simp = NULL; + dw.win = this; + return &dw; + } + int c = drawWindows.Num(); + for (int i = 0; i < c; i++) { + if (drawWindows[i].win) { + if (idStr::Icmp(drawWindows[i].win->name, _name) == 0) { + return &drawWindows[i]; + } + drawWin_t *win = drawWindows[i].win->FindChildByName(_name); + if (win) { + return win; + } + } else { + if (idStr::Icmp(drawWindows[i].simp->name, _name) == 0) { + return &drawWindows[i]; + } + } + } + return NULL; +} + +bool idWindow::GetMaxTextIndex( const char *text, wrapInfo_t& wrapInfo ) const { + if ( !dc ) { + return false; + } + dc->SetFont( fontNum ); + return dc->GetMaxTextIndex( text, static_cast( textRect.w ), textScale, wrapInfo ); +} + +/* +================ +idWindow::GetStrPtrByName +================ +*/ +idStr* idWindow::GetStrPtrByName(const char *_name) { + return NULL; +} + +/* +================ +idWindow::AddTransition +================ +*/ +void idWindow::AddTransition(idWinVar *dest, idVec4 from, idVec4 to, int time, float accelTime, float decelTime) { + idTransitionData data; + data.data = dest; + data.interp.Init(gui->GetTime(), accelTime * time, decelTime * time, time, from, to); + transitions.Append(data); +} + + +/* +================ +idWindow::StartTransition +================ +*/ +void idWindow::StartTransition() { + flags |= WIN_INTRANSITION; +} + +void idWindow::ClearTransitions() { + transitions.Clear(); + flags &= ~WIN_INTRANSITION; +} + +/* +================ +idWindow::ResetCinematics +================ +*/ +void idWindow::ResetCinematics() { + if ( background ) { + background->ResetCinematicTime( gui->GetTime() ); + } +} + +/* +================ +idWindow::ResetTime +================ +*/ +void idWindow::ResetTime(int t) { + if ( !idStr::Icmp( name, "anim_in" ) || !idStr::Icmp( name, "video_bethsoft" ) ) { + common->DPrintf( "Q4 menu trace: ResetTime '%s' t=%d guiTime=%d\n", name.c_str(), t, gui->GetTime() ); + } + + timeLine = gui->GetTime() - t; + + int i, c = timeLineEvents.Num(); + for ( i = 0; i < c; i++ ) { + if ( timeLineEvents[i]->time >= t ) { + timeLineEvents[i]->pending = true; + } + } + + noTime = false; + + c = transitions.Num(); + for ( i = 0; i < c; i++ ) { + idTransitionData *data = &transitions[i]; + if ( data->interp.IsDone( gui->GetTime() ) && data->data ) { + transitions.RemoveIndex( i ); + i--; + c--; + } + } + +} + + +/* +================ +idWindow::RunScriptList +================ +*/ +bool idWindow::RunScriptList(idGuiScriptList *src) { + if (src == NULL) { + return false; + } + src->Execute(this); + return true; +} + +/* +================ +idWindow::RunScript +================ +*/ +bool idWindow::RunScript(int n) { + if (n >= ON_MOUSEENTER && n < SCRIPT_COUNT) { + return RunScriptList(scripts[n]); + } + return false; +} + +/* +================ +idWindow::ExpressionConstant +================ +*/ +int idWindow::ExpressionConstant(float f) { + int i; + + for ( i = WEXP_REG_NUM_PREDEFINED ; i < expressionRegisters.Num() ; i++ ) { + if ( !registerIsTemporary[i] && expressionRegisters[i] == f ) { + return i; + } + } + if ( expressionRegisters.Num() == MAX_EXPRESSION_REGISTERS ) { + common->Warning( "expressionConstant: gui %s hit MAX_EXPRESSION_REGISTERS", gui->GetSourceFile() ); + return 0; + } + + int c = expressionRegisters.Num(); + if (i > c) { + while (i > c) { + expressionRegisters.Append(-9999999); + i--; + } + } + + i = expressionRegisters.Append(f); + registerIsTemporary[i] = false; + return i; +} + +/* +================ +idWindow::ExpressionTemporary +================ +*/ +int idWindow::ExpressionTemporary() { + if ( expressionRegisters.Num() == MAX_EXPRESSION_REGISTERS ) { + common->Warning( "expressionTemporary: gui %s hit MAX_EXPRESSION_REGISTERS", gui->GetSourceFile()); + return 0; + } + int i = expressionRegisters.Num(); + registerIsTemporary[i] = true; + i = expressionRegisters.Append(0); + return i; +} + +/* +================ +idWindow::ExpressionOp +================ +*/ +wexpOp_t *idWindow::ExpressionOp() { + if ( ops.Num() == MAX_EXPRESSION_OPS ) { + common->Warning( "expressionOp: gui %s hit MAX_EXPRESSION_OPS", gui->GetSourceFile()); + return &ops[0]; + } + wexpOp_t wop; + memset(&wop, 0, sizeof(wexpOp_t)); + int i = ops.Append(wop); + return &ops[i]; +} + +/* +================ +idWindow::EmitOp +================ +*/ + +int idWindow::EmitOp( int a, int b, wexpOpType_t opType, wexpOp_t **opp ) { + wexpOp_t *op; +/* + // optimize away identity operations + if ( opType == WOP_TYPE_ADD ) { + if ( !registerIsTemporary[a] && shaderRegisters[a] == 0 ) { + return b; + } + if ( !registerIsTemporary[b] && shaderRegisters[b] == 0 ) { + return a; + } + if ( !registerIsTemporary[a] && !registerIsTemporary[b] ) { + return ExpressionConstant( shaderRegisters[a] + shaderRegisters[b] ); + } + } + if ( opType == WOP_TYPE_MULTIPLY ) { + if ( !registerIsTemporary[a] && shaderRegisters[a] == 1 ) { + return b; + } + if ( !registerIsTemporary[a] && shaderRegisters[a] == 0 ) { + return a; + } + if ( !registerIsTemporary[b] && shaderRegisters[b] == 1 ) { + return a; + } + if ( !registerIsTemporary[b] && shaderRegisters[b] == 0 ) { + return b; + } + if ( !registerIsTemporary[a] && !registerIsTemporary[b] ) { + return ExpressionConstant( shaderRegisters[a] * shaderRegisters[b] ); + } + } +*/ + op = ExpressionOp(); + + op->opType = opType; + op->a = a; + op->b = b; + op->c = ExpressionTemporary(); + + if (opp) { + *opp = op; + } + return op->c; +} + +/* +================ +idWindow::ParseEmitOp +================ +*/ +int idWindow::ParseEmitOp( idParser *src, int a, wexpOpType_t opType, int priority, wexpOp_t **opp ) { + int b = ParseExpressionPriority( src, priority ); + return EmitOp( a, b, opType, opp ); +} + + +/* +================ +idWindow::ParseTerm + +Returns a register index +================= +*/ +int idWindow::ParseTerm( idParser *src, idWinVar *var, int component ) { + idToken token; + int a, b; + + src->ReadToken( &token ); + + if ( token == "(" ) { + a = ParseExpression( src ); + src->ExpectTokenString(")"); + return a; + } + + if ( !token.Icmp( "time" ) ) { + return WEXP_REG_TIME; + } + + // parse negative numbers + if ( token == "-" ) { + src->ReadToken( &token ); + if ( token.type == TT_NUMBER || token == "." ) { + return ExpressionConstant( -(float) token.GetFloatValue() ); + } + src->Warning( "Bad negative number '%s'", token.c_str() ); + return 0; + } + + if ( token.type == TT_NUMBER || token == "." || token == "-" ) { + return ExpressionConstant( (float) token.GetFloatValue() ); + } + + // see if it is a table name + const idDeclTable *table = static_cast( declManager->FindType( DECL_TABLE, token.c_str(), false ) ); + if ( table ) { + a = table->Index(); + // parse a table expression + src->ExpectTokenString("["); + b = ParseExpression(src); + src->ExpectTokenString("]"); + return EmitOp( a, b, WOP_TYPE_TABLE ); + } + + if (var == NULL) { + var = GetWinVarByName(token, true); + } + if (var) { + a = (int)var; + //assert(dynamic_cast(var)); + var->Init(token, this); + b = component; + if (dynamic_cast(var)) { + if (src->ReadToken(&token)) { + if (token == "[") { + b = ParseExpression(src); + src->ExpectTokenString("]"); + } else { + src->UnreadToken(&token); + } + } + return EmitOp(a, b, WOP_TYPE_VAR); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARF); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARFM); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARI); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARB); + } else if (dynamic_cast(var)) { + return EmitOp(a, b, WOP_TYPE_VARS); + } else { + src->Warning("Var expression not vec4, float or int '%s'", token.c_str()); + } + return 0; + } else { + // ugly but used for post parsing to fixup named vars + char *p = new char[token.Length()+1]; + strcpy(p, token); + a = (int)p; + b = -2; + return EmitOp(a, b, WOP_TYPE_VAR); + } + +} + +/* +================= +idWindow::ParseExpressionPriority + +Returns a register index +================= +*/ +#define TOP_PRIORITY 4 +int idWindow::ParseExpressionPriority( idParser *src, int priority, idWinVar *var, int component ) { + idToken token; + int a; + + if ( priority == 0 ) { + return ParseTerm( src, var, component ); + } + + a = ParseExpressionPriority( src, priority - 1, var, component ); + + if ( !src->ReadToken( &token ) ) { + // we won't get EOF in a real file, but we can + // when parsing from generated strings + return a; + } + + if ( priority == 1 && token == "*" ) { + return ParseEmitOp( src, a, WOP_TYPE_MULTIPLY, priority ); + } + if ( priority == 1 && token == "/" ) { + return ParseEmitOp( src, a, WOP_TYPE_DIVIDE, priority ); + } + if ( priority == 1 && token == "%" ) { // implied truncate both to integer + return ParseEmitOp( src, a, WOP_TYPE_MOD, priority ); + } + if ( priority == 2 && token == "+" ) { + return ParseEmitOp( src, a, WOP_TYPE_ADD, priority ); + } + if ( priority == 2 && token == "-" ) { + return ParseEmitOp( src, a, WOP_TYPE_SUBTRACT, priority ); + } + if ( priority == 3 && token == ">" ) { + return ParseEmitOp( src, a, WOP_TYPE_GT, priority ); + } + if ( priority == 3 && token == ">=" ) { + return ParseEmitOp( src, a, WOP_TYPE_GE, priority ); + } + if ( priority == 3 && token == "<" ) { + return ParseEmitOp( src, a, WOP_TYPE_LT, priority ); + } + if ( priority == 3 && token == "<=" ) { + return ParseEmitOp( src, a, WOP_TYPE_LE, priority ); + } + if ( priority == 3 && token == "==" ) { + return ParseEmitOp( src, a, WOP_TYPE_EQ, priority ); + } + if ( priority == 3 && token == "!=" ) { + return ParseEmitOp( src, a, WOP_TYPE_NE, priority ); + } + if ( priority == 4 && token == "&&" ) { + return ParseEmitOp( src, a, WOP_TYPE_AND, priority ); + } + if ( priority == 4 && token == "||" ) { + return ParseEmitOp( src, a, WOP_TYPE_OR, priority ); + } + if ( priority == 4 && token == "?" ) { + wexpOp_t *oop = NULL; + int o = ParseEmitOp( src, a, WOP_TYPE_COND, priority, &oop ); + if ( !src->ReadToken( &token ) ) { + return o; + } + if (token == ":") { + a = ParseExpressionPriority( src, priority - 1, var ); + oop->d = a; + } + return o; + } + + // assume that anything else terminates the expression + // not too robust error checking... + + src->UnreadToken( &token ); + + return a; +} + +/* +================ +idWindow::ParseExpression + +Returns a register index +================ +*/ +int idWindow::ParseExpression(idParser *src, idWinVar *var, int component) { + return ParseExpressionPriority( src, TOP_PRIORITY, var ); +} + +/* +================ +idWindow::ParseBracedExpression +================ +*/ +void idWindow::ParseBracedExpression(idParser *src) { + src->ExpectTokenString("{"); + ParseExpression(src); + src->ExpectTokenString("}"); +} + +/* +=============== +idWindow::EvaluateRegisters + +Parameters are taken from the localSpace and the renderView, +then all expressions are evaluated, leaving the shader registers +set to their apropriate values. +=============== +*/ +void idWindow::EvaluateRegisters(float *registers) { + int i, b; + wexpOp_t *op; + idVec4 v; + + int erc = expressionRegisters.Num(); + int oc = ops.Num(); + // copy the constants + for ( i = WEXP_REG_NUM_PREDEFINED ; i < erc ; i++ ) { + registers[i] = expressionRegisters[i]; + } + + // copy the local and global parameters + registers[WEXP_REG_TIME] = gui->GetTime(); + + for ( i = 0 ; i < oc ; i++ ) { + op = &ops[i]; + if (op->b == -2) { + continue; + } + switch( op->opType ) { + case WOP_TYPE_ADD: + registers[op->c] = registers[op->a] + registers[op->b]; + break; + case WOP_TYPE_SUBTRACT: + registers[op->c] = registers[op->a] - registers[op->b]; + break; + case WOP_TYPE_MULTIPLY: + registers[op->c] = registers[op->a] * registers[op->b]; + break; + case WOP_TYPE_DIVIDE: + if ( registers[op->b] == 0.0f ) { + common->Warning( "Divide by zero in window '%s' in %s", GetName(), gui->GetSourceFile() ); + registers[op->c] = registers[op->a]; + } else { + registers[op->c] = registers[op->a] / registers[op->b]; + } + break; + case WOP_TYPE_MOD: + b = (int)registers[op->b]; + b = b != 0 ? b : 1; + registers[op->c] = (int)registers[op->a] % b; + break; + case WOP_TYPE_TABLE: + { + const idDeclTable *table = static_cast( declManager->DeclByIndex( DECL_TABLE, op->a ) ); + registers[op->c] = table->TableLookup( registers[op->b] ); + } + break; + case WOP_TYPE_GT: + registers[op->c] = registers[ op->a ] > registers[op->b]; + break; + case WOP_TYPE_GE: + registers[op->c] = registers[ op->a ] >= registers[op->b]; + break; + case WOP_TYPE_LT: + registers[op->c] = registers[ op->a ] < registers[op->b]; + break; + case WOP_TYPE_LE: + registers[op->c] = registers[ op->a ] <= registers[op->b]; + break; + case WOP_TYPE_EQ: + registers[op->c] = registers[ op->a ] == registers[op->b]; + break; + case WOP_TYPE_NE: + registers[op->c] = registers[ op->a ] != registers[op->b]; + break; + case WOP_TYPE_COND: + registers[op->c] = (registers[ op->a ]) ? registers[op->b] : registers[op->d]; + break; + case WOP_TYPE_AND: + registers[op->c] = registers[ op->a ] && registers[op->b]; + break; + case WOP_TYPE_OR: + registers[op->c] = registers[ op->a ] || registers[op->b]; + break; + case WOP_TYPE_VAR: + if ( !op->a ) { + registers[op->c] = 0.0f; + break; + } + if ( op->b >= 0 && registers[op->b] >= 0 && registers[op->b] < 4 ) { + // grabs vector components + idWinVec4 *var = (idWinVec4 *)( op->a ); + registers[op->c] = ((idVec4&)var)[registers[op->b]]; + } else { + registers[op->c] = ((idWinVar*)(op->a))->x(); + } + break; + case WOP_TYPE_VARS: + if (op->a) { + idWinStr *var = (idWinStr*)(op->a); + registers[op->c] = atof(var->c_str()); + } else { + registers[op->c] = 0; + } + break; + case WOP_TYPE_VARF: + if (op->a) { + idWinFloat *var = (idWinFloat*)(op->a); + registers[op->c] = *var; + } else { + registers[op->c] = 0; + } + break; + case WOP_TYPE_VARFM: + if (op->a) { + idWinFloatMember *var = (idWinFloatMember*)(op->a); + registers[op->c] = var->x(); + } else { + registers[op->c] = 0; + } + break; + case WOP_TYPE_VARI: + if (op->a) { + idWinInt *var = (idWinInt*)(op->a); + registers[op->c] = *var; + } else { + registers[op->c] = 0; + } + break; + case WOP_TYPE_VARB: + if (op->a) { + idWinBool *var = (idWinBool*)(op->a); + registers[op->c] = *var; + } else { + registers[op->c] = 0; + } + break; + default: + common->FatalError( "R_EvaluateExpression: bad opcode" ); + } + } + +} + +/* +================ +idWindow::ReadFromDemoFile +================ +*/ +void idWindow::ReadFromDemoFile( class idDemoFile *f, bool rebuild ) { + + // should never hit unless we re-enable WRITE_GUIS +#ifndef WRITE_GUIS + assert( false ); +#else + + if (rebuild) { + CommonInit(); + } + + f->SetLog(true, "window1"); + backGroundName = f->ReadHashString(); + f->SetLog(true, backGroundName); + if ( backGroundName[0] ) { + background = declManager->FindMaterial(backGroundName); + } else { + background = NULL; + } + f->ReadUnsignedChar( cursor ); + f->ReadUnsignedInt( flags ); + f->ReadInt( timeLine ); + f->ReadInt( lastTimeRun ); + idRectangle rct = rect; + f->ReadFloat( rct.x ); + f->ReadFloat( rct.y ); + f->ReadFloat( rct.w ); + f->ReadFloat( rct.h ); + f->ReadFloat( drawRect.x ); + f->ReadFloat( drawRect.y ); + f->ReadFloat( drawRect.w ); + f->ReadFloat( drawRect.h ); + f->ReadFloat( clientRect.x ); + f->ReadFloat( clientRect.y ); + f->ReadFloat( clientRect.w ); + f->ReadFloat( clientRect.h ); + f->ReadFloat( textRect.x ); + f->ReadFloat( textRect.y ); + f->ReadFloat( textRect.w ); + f->ReadFloat( textRect.h ); + f->ReadFloat( xOffset); + f->ReadFloat( yOffset); + int i, c; + + idStr work; + if (rebuild) { + f->SetLog(true, (work + "-scripts")); + for (i = 0; i < SCRIPT_COUNT; i++) { + bool b; + f->ReadBool( b ); + if (b) { + delete scripts[i]; + scripts[i] = new idGuiScriptList; + scripts[i]->ReadFromDemoFile(f); + } + } + + f->SetLog(true, (work + "-timelines")); + f->ReadInt( c ); + for (i = 0; i < c; i++) { + idTimeLineEvent *tl = new idTimeLineEvent; + f->ReadInt( tl->time ); + f->ReadBool( tl->pending ); + tl->event->ReadFromDemoFile(f); + if (rebuild) { + timeLineEvents.Append(tl); + } else { + assert(i < timeLineEvents.Num()); + timeLineEvents[i]->time = tl->time; + timeLineEvents[i]->pending = tl->pending; + } + } + } + + f->SetLog(true, (work + "-transitions")); + f->ReadInt( c ); + for (i = 0; i < c; i++) { + idTransitionData td; + td.data = NULL; + f->ReadInt ( td.offset ); + + float startTime, accelTime, linearTime, decelTime; + idVec4 startValue, endValue; + f->ReadFloat( startTime ); + f->ReadFloat( accelTime ); + f->ReadFloat( linearTime ); + f->ReadFloat( decelTime ); + f->ReadVec4( startValue ); + f->ReadVec4( endValue ); + td.interp.Init( startTime, accelTime, decelTime, accelTime + linearTime + decelTime, startValue, endValue ); + + // read this for correct data padding with the win32 savegames + // the extrapolate is correctly initialized through the above Init call + int extrapolationType; + float duration; + idVec4 baseSpeed, speed; + float currentTime; + idVec4 currentValue; + f->ReadInt( extrapolationType ); + f->ReadFloat( startTime ); + f->ReadFloat( duration ); + f->ReadVec4( startValue ); + f->ReadVec4( baseSpeed ); + f->ReadVec4( speed ); + f->ReadFloat( currentTime ); + f->ReadVec4( currentValue ); + + transitions.Append(td); + } + + f->SetLog(true, (work + "-regstuff")); + if (rebuild) { + f->ReadInt( c ); + for (i = 0; i < c; i++) { + wexpOp_t w; + f->ReadInt( (int&)w.opType ); + f->ReadInt( w.a ); + f->ReadInt( w.b ); + f->ReadInt( w.c ); + f->ReadInt( w.d ); + ops.Append(w); + } + + f->ReadInt( c ); + for (i = 0; i < c; i++) { + float ff; + f->ReadFloat( ff ); + expressionRegisters.Append(ff); + } + + regList.ReadFromDemoFile(f); + + } + f->SetLog(true, (work + "-children")); + f->ReadInt( c ); + for (i = 0; i < c; i++) { + if (rebuild) { + idWindow *win = new idWindow(dc, gui); + win->ReadFromDemoFile(f); + AddChild(win); + } else { + for (int j = 0; j < c; j++) { + if (children[j]->childID == i) { + children[j]->ReadFromDemoFile(f,rebuild); + break; + } else { + continue; + } + } + } + } +#endif /* WRITE_GUIS */ +} + +/* +================ +idWindow::WriteToDemoFile +================ +*/ +void idWindow::WriteToDemoFile( class idDemoFile *f ) { + // should never hit unless we re-enable WRITE_GUIS +#ifndef WRITE_GUIS + assert( false ); +#else + + f->SetLog(true, "window"); + f->WriteHashString(backGroundName); + f->SetLog(true, backGroundName); + f->WriteUnsignedChar( cursor ); + f->WriteUnsignedInt( flags ); + f->WriteInt( timeLine ); + f->WriteInt( lastTimeRun ); + idRectangle rct = rect; + f->WriteFloat( rct.x ); + f->WriteFloat( rct.y ); + f->WriteFloat( rct.w ); + f->WriteFloat( rct.h ); + f->WriteFloat( drawRect.x ); + f->WriteFloat( drawRect.y ); + f->WriteFloat( drawRect.w ); + f->WriteFloat( drawRect.h ); + f->WriteFloat( clientRect.x ); + f->WriteFloat( clientRect.y ); + f->WriteFloat( clientRect.w ); + f->WriteFloat( clientRect.h ); + f->WriteFloat( textRect.x ); + f->WriteFloat( textRect.y ); + f->WriteFloat( textRect.w ); + f->WriteFloat( textRect.h ); + f->WriteFloat( xOffset ); + f->WriteFloat( yOffset ); + idStr work; + f->SetLog(true, work); + + int i, c; + + f->SetLog(true, (work + "-transitions")); + c = transitions.Num(); + f->WriteInt( c ); + for (i = 0; i < c; i++) { + f->WriteInt( 0 ); + f->WriteInt( transitions[i].offset ); + + f->WriteFloat( transitions[i].interp.GetStartTime() ); + f->WriteFloat( transitions[i].interp.GetAccelTime() ); + f->WriteFloat( transitions[i].interp.GetLinearTime() ); + f->WriteFloat( transitions[i].interp.GetDecelTime() ); + f->WriteVec4( transitions[i].interp.GetStartValue() ); + f->WriteVec4( transitions[i].interp.GetEndValue() ); + + // write to keep win32 render demo format compatiblity - we don't actually read them back anymore + f->WriteInt( transitions[i].interp.GetExtrapolate()->GetExtrapolationType() ); + f->WriteFloat( transitions[i].interp.GetExtrapolate()->GetStartTime() ); + f->WriteFloat( transitions[i].interp.GetExtrapolate()->GetDuration() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetStartValue() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetBaseSpeed() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetSpeed() ); + f->WriteFloat( transitions[i].interp.GetExtrapolate()->GetCurrentTime() ); + f->WriteVec4( transitions[i].interp.GetExtrapolate()->GetCurrentValue() ); + } + + f->SetLog(true, (work + "-regstuff")); + + f->SetLog(true, (work + "-children")); + c = children.Num(); + f->WriteInt( c ); + for (i = 0; i < c; i++) { + for (int j = 0; j < c; j++) { + if (children[j]->childID == i) { + children[j]->WriteToDemoFile(f); + break; + } else { + continue; + } + } + } +#endif /* WRITE_GUIS */ +} + +/* +=============== +idWindow::WriteString +=============== +*/ +void idWindow::WriteSaveGameString( const char *string, idFile *savefile ) { + int len = strlen( string ); + + savefile->Write( &len, sizeof( len ) ); + savefile->Write( string, len ); +} + +/* +=============== +idWindow::WriteSaveGameTransition +=============== +*/ +void idWindow::WriteSaveGameTransition( idTransitionData &trans, idFile *savefile ) { + drawWin_t dw, *fdw; + idStr winName(""); + dw.simp = NULL; + dw.win = NULL; + int offset = gui->GetDesktop()->GetWinVarOffset( trans.data, &dw ); + if ( dw.win || dw.simp ) { + winName = ( dw.win ) ? dw.win->GetName() : dw.simp->name.c_str(); + } + fdw = gui->GetDesktop()->FindChildByName( winName ); + if ( offset != -1 && fdw && ( fdw->win || fdw->simp ) ) { + savefile->Write( &offset, sizeof( offset ) ); + WriteSaveGameString( winName, savefile ); + savefile->Write( &trans.interp, sizeof( trans.interp ) ); + } else { + offset = -1; + savefile->Write( &offset, sizeof( offset ) ); + } +} + +/* +=============== +idWindow::ReadSaveGameTransition +=============== +*/ +void idWindow::ReadSaveGameTransition( idTransitionData &trans, idFile *savefile ) { + int offset; + + savefile->Read( &offset, sizeof( offset ) ); + if ( offset != -1 ) { + idStr winName; + ReadSaveGameString( winName, savefile ); + savefile->Read( &trans.interp, sizeof( trans.interp ) ); + trans.data = NULL; + trans.offset = offset; + if ( winName.Length() ) { + idWinStr *strVar = new idWinStr(); + strVar->Set( winName ); + trans.data = dynamic_cast< idWinVar* >( strVar ); + } + } +} + +/* +=============== +idWindow::WriteToSaveGame +=============== +*/ +void idWindow::WriteToSaveGame( idFile *savefile ) { + int i; + + WriteSaveGameString( cmd, savefile ); + + savefile->Write( &actualX, sizeof( actualX ) ); + savefile->Write( &actualY, sizeof( actualY ) ); + savefile->Write( &childID, sizeof( childID ) ); + savefile->Write( &flags, sizeof( flags ) ); + savefile->Write( &lastTimeRun, sizeof( lastTimeRun ) ); + savefile->Write( &drawRect, sizeof( drawRect ) ); + savefile->Write( &clientRect, sizeof( clientRect ) ); + savefile->Write( &origin, sizeof( origin ) ); + savefile->Write( &fontNum, sizeof( fontNum ) ); + savefile->Write( &timeLine, sizeof( timeLine ) ); + savefile->Write( &xOffset, sizeof( xOffset ) ); + savefile->Write( &yOffset, sizeof( yOffset ) ); + savefile->Write( &cursor, sizeof( cursor ) ); + savefile->Write( &forceAspectWidth, sizeof( forceAspectWidth ) ); + savefile->Write( &forceAspectHeight, sizeof( forceAspectHeight ) ); + savefile->Write( &matScalex, sizeof( matScalex ) ); + savefile->Write( &matScaley, sizeof( matScaley ) ); + savefile->Write( &borderSize, sizeof( borderSize ) ); + savefile->Write( &textAlign, sizeof( textAlign ) ); + savefile->Write( &textAlignx, sizeof( textAlignx ) ); + savefile->Write( &textAligny, sizeof( textAligny ) ); + savefile->Write( &textSpacing, sizeof( textSpacing ) ); + savefile->Write( &textStyle, sizeof( textStyle ) ); + savefile->Write( &textShadow, sizeof( textShadow ) ); + savefile->Write( &shear, sizeof( shear ) ); + + WriteSaveGameString( name, savefile ); + WriteSaveGameString( comment, savefile ); + + // WinVars + noTime.WriteToSaveGame( savefile ); + visible.WriteToSaveGame( savefile ); + rect.WriteToSaveGame( savefile ); + backColor.WriteToSaveGame( savefile ); + matColor.WriteToSaveGame( savefile ); + foreColor.WriteToSaveGame( savefile ); + hoverColor.WriteToSaveGame( savefile ); + borderColor.WriteToSaveGame( savefile ); + textScale.WriteToSaveGame( savefile ); + noEvents.WriteToSaveGame( savefile ); + rotate.WriteToSaveGame( savefile ); + text.WriteToSaveGame( savefile ); + backGroundName.WriteToSaveGame( savefile ); + hideCursor.WriteToSaveGame(savefile); + + // Defined Vars + for ( i = 0; i < definedVars.Num(); i++ ) { + definedVars[i]->WriteToSaveGame( savefile ); + } + + savefile->Write( &textRect, sizeof( textRect ) ); + + // Window pointers saved as the child ID of the window + int winID; + + winID = focusedChild ? focusedChild->childID : -1 ; + savefile->Write( &winID, sizeof( winID ) ); + + winID = captureChild ? captureChild->childID : -1 ; + savefile->Write( &winID, sizeof( winID ) ); + + winID = overChild ? overChild->childID : -1 ; + savefile->Write( &winID, sizeof( winID ) ); + + + // Scripts + for ( i = 0; i < SCRIPT_COUNT; i++ ) { + if ( scripts[i] ) { + scripts[i]->WriteToSaveGame( savefile ); + } + } + + // TimeLine Events + for ( i = 0; i < timeLineEvents.Num(); i++ ) { + if ( timeLineEvents[i] ) { + savefile->Write( &timeLineEvents[i]->pending, sizeof( timeLineEvents[i]->pending ) ); + savefile->Write( &timeLineEvents[i]->time, sizeof( timeLineEvents[i]->time ) ); + if ( timeLineEvents[i]->event ) { + timeLineEvents[i]->event->WriteToSaveGame( savefile ); + } + } + } + + // Transitions + int num = transitions.Num(); + + savefile->Write( &num, sizeof( num ) ); + for ( i = 0; i < transitions.Num(); i++ ) { + WriteSaveGameTransition( transitions[ i ], savefile ); + } + + + // Named Events + for ( i = 0; i < namedEvents.Num(); i++ ) { + if ( namedEvents[i] ) { + WriteSaveGameString( namedEvents[i]->mName, savefile ); + if ( namedEvents[i]->mEvent ) { + namedEvents[i]->mEvent->WriteToSaveGame( savefile ); + } + } + } + + // regList + regList.WriteToSaveGame( savefile ); + + + // Save children + for ( i = 0; i < drawWindows.Num(); i++ ) { + drawWin_t window = drawWindows[i]; + + if ( window.simp ) { + window.simp->WriteToSaveGame( savefile ); + } else if ( window.win ) { + window.win->WriteToSaveGame( savefile ); + } + } +} + +/* +=============== +idWindow::ReadSaveGameString +=============== +*/ +void idWindow::ReadSaveGameString( idStr &string, idFile *savefile ) { + int len; + + savefile->Read( &len, sizeof( len ) ); + if ( len < 0 ) { + common->Warning( "idWindow::ReadSaveGameString: invalid length" ); + } + + string.Fill( ' ', len ); + savefile->Read( &string[0], len ); +} + +/* +=============== +idWindow::ReadFromSaveGame +=============== +*/ +void idWindow::ReadFromSaveGame( idFile *savefile ) { + int i; + + transitions.Clear(); + + ReadSaveGameString( cmd, savefile ); + + savefile->Read( &actualX, sizeof( actualX ) ); + savefile->Read( &actualY, sizeof( actualY ) ); + savefile->Read( &childID, sizeof( childID ) ); + savefile->Read( &flags, sizeof( flags ) ); + savefile->Read( &lastTimeRun, sizeof( lastTimeRun ) ); + savefile->Read( &drawRect, sizeof( drawRect ) ); + savefile->Read( &clientRect, sizeof( clientRect ) ); + savefile->Read( &origin, sizeof( origin ) ); + savefile->Read( &fontNum, sizeof( fontNum ) ); + savefile->Read( &timeLine, sizeof( timeLine ) ); + savefile->Read( &xOffset, sizeof( xOffset ) ); + savefile->Read( &yOffset, sizeof( yOffset ) ); + savefile->Read( &cursor, sizeof( cursor ) ); + savefile->Read( &forceAspectWidth, sizeof( forceAspectWidth ) ); + savefile->Read( &forceAspectHeight, sizeof( forceAspectHeight ) ); + savefile->Read( &matScalex, sizeof( matScalex ) ); + savefile->Read( &matScaley, sizeof( matScaley ) ); + savefile->Read( &borderSize, sizeof( borderSize ) ); + savefile->Read( &textAlign, sizeof( textAlign ) ); + savefile->Read( &textAlignx, sizeof( textAlignx ) ); + savefile->Read( &textAligny, sizeof( textAligny ) ); + savefile->Read( &textSpacing, sizeof( textSpacing ) ); + savefile->Read( &textStyle, sizeof( textStyle ) ); + savefile->Read( &textShadow, sizeof( textShadow ) ); + savefile->Read( &shear, sizeof( shear ) ); + + ReadSaveGameString( name, savefile ); + ReadSaveGameString( comment, savefile ); + + // WinVars + noTime.ReadFromSaveGame( savefile ); + visible.ReadFromSaveGame( savefile ); + rect.ReadFromSaveGame( savefile ); + backColor.ReadFromSaveGame( savefile ); + matColor.ReadFromSaveGame( savefile ); + foreColor.ReadFromSaveGame( savefile ); + hoverColor.ReadFromSaveGame( savefile ); + borderColor.ReadFromSaveGame( savefile ); + textScale.ReadFromSaveGame( savefile ); + noEvents.ReadFromSaveGame( savefile ); + rotate.ReadFromSaveGame( savefile ); + text.ReadFromSaveGame( savefile ); + backGroundName.ReadFromSaveGame( savefile ); + + if ( session->GetSaveGameVersion() >= 17 ) { + hideCursor.ReadFromSaveGame(savefile); + } else { + hideCursor = false; + } + + // Defined Vars + for ( i = 0; i < definedVars.Num(); i++ ) { + definedVars[i]->ReadFromSaveGame( savefile ); + } + + savefile->Read( &textRect, sizeof( textRect ) ); + + // Window pointers saved as the child ID of the window + int winID = -1; + + savefile->Read( &winID, sizeof( winID ) ); + for ( i = 0; i < children.Num(); i++ ) { + if ( children[i]->childID == winID ) { + focusedChild = children[i]; + } + } + savefile->Read( &winID, sizeof( winID ) ); + for ( i = 0; i < children.Num(); i++ ) { + if ( children[i]->childID == winID ) { + captureChild = children[i]; + } + } + savefile->Read( &winID, sizeof( winID ) ); + for ( i = 0; i < children.Num(); i++ ) { + if ( children[i]->childID == winID ) { + overChild = children[i]; + } + } + + // Scripts + for ( i = 0; i < SCRIPT_COUNT; i++ ) { + if ( scripts[i] ) { + scripts[i]->ReadFromSaveGame( savefile ); + } + } + + // TimeLine Events + for ( i = 0; i < timeLineEvents.Num(); i++ ) { + if ( timeLineEvents[i] ) { + savefile->Read( &timeLineEvents[i]->pending, sizeof( timeLineEvents[i]->pending ) ); + savefile->Read( &timeLineEvents[i]->time, sizeof( timeLineEvents[i]->time ) ); + if ( timeLineEvents[i]->event ) { + timeLineEvents[i]->event->ReadFromSaveGame( savefile ); + } + } + } + + + // Transitions + int num; + savefile->Read( &num, sizeof( num ) ); + for ( i = 0; i < num; i++ ) { + idTransitionData trans; + trans.data = NULL; + ReadSaveGameTransition( trans, savefile ); + if ( trans.data ) { + transitions.Append( trans ); + } + } + + + // Named Events + for ( i = 0; i < namedEvents.Num(); i++ ) { + if ( namedEvents[i] ) { + ReadSaveGameString( namedEvents[i]->mName, savefile ); + if ( namedEvents[i]->mEvent ) { + namedEvents[i]->mEvent->ReadFromSaveGame( savefile ); + } + } + } + + // regList + regList.ReadFromSaveGame( savefile ); + + // Read children + for ( i = 0; i < drawWindows.Num(); i++ ) { + drawWin_t window = drawWindows[i]; + + if ( window.simp ) { + window.simp->ReadFromSaveGame( savefile ); + } else if ( window.win ) { + window.win->ReadFromSaveGame( savefile ); + } + } + + if ( flags & WIN_DESKTOP ) { + FixupTransitions(); + } +} + +/* +=============== +idWindow::NumTransitions +=============== +*/ +int idWindow::NumTransitions() { + int c = transitions.Num(); + for ( int i = 0; i < children.Num(); i++ ) { + c += children[i]->NumTransitions(); + } + return c; +} + + +/* +=============== +idWindow::FixupTransitions +=============== +*/ +void idWindow::FixupTransitions() { + int i, c = transitions.Num(); + for ( i = 0; i < c; i++ ) { + drawWin_t *dw = gui->GetDesktop()->FindChildByName( ( ( idWinStr* )transitions[i].data )->c_str() ); + delete transitions[i].data; + transitions[i].data = NULL; + if ( dw && ( dw->win || dw->simp ) ){ + if ( dw->win ) { + if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->rect ) { + transitions[i].data = &dw->win->rect; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->backColor ) { + transitions[i].data = &dw->win->backColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->matColor ) { + transitions[i].data = &dw->win->matColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->foreColor ) { + transitions[i].data = &dw->win->foreColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->borderColor ) { + transitions[i].data = &dw->win->borderColor; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->textScale ) { + transitions[i].data = &dw->win->textScale; + } else if ( transitions[i].offset == (int)&( ( idWindow * ) 0 )->rotate ) { + transitions[i].data = &dw->win->rotate; + } + } else { + if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->rect ) { + transitions[i].data = &dw->simp->rect; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->backColor ) { + transitions[i].data = &dw->simp->backColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->matColor ) { + transitions[i].data = &dw->simp->matColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->foreColor ) { + transitions[i].data = &dw->simp->foreColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->borderColor ) { + transitions[i].data = &dw->simp->borderColor; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->textScale ) { + transitions[i].data = &dw->simp->textScale; + } else if ( transitions[i].offset == (int)&( ( idSimpleWindow * ) 0 )->rotate ) { + transitions[i].data = &dw->simp->rotate; + } + } + } + if ( transitions[i].data == NULL ) { + transitions.RemoveIndex( i ); + i--; + c--; + } + } + for ( c = 0; c < children.Num(); c++ ) { + children[c]->FixupTransitions(); + } +} + + +/* +=============== +idWindow::AddChild +=============== +*/ +void idWindow::AddChild(idWindow *win) { + win->childID = children.Append(win); +} + +/* +================ +idWindow::FixupParms +================ +*/ +void idWindow::FixupParms() { + int i; + int c = children.Num(); + for (i = 0; i < c; i++) { + children[i]->FixupParms(); + } + for (i = 0; i < SCRIPT_COUNT; i++) { + if (scripts[i]) { + scripts[i]->FixupParms(this); + } + } + + c = timeLineEvents.Num(); + for (i = 0; i < c; i++) { + timeLineEvents[i]->event->FixupParms(this); + } + + c = namedEvents.Num(); + for (i = 0; i < c; i++) { + namedEvents[i]->mEvent->FixupParms(this); + } + + c = ops.Num(); + for (i = 0; i < c; i++) { + if (ops[i].b == -2) { + // need to fix this up + const char *p = (const char*)(ops[i].a); + idWinVar *var = GetWinVarByName(p, true); + delete []p; + ops[i].a = (int)var; + ops[i].b = -1; + } + } + + + if (flags & WIN_DESKTOP) { + CalcRects(0,0); + } + +} + +/* +================ +idWindow::IsSimple +================ +*/ +bool idWindow::IsSimple() { + + // dont do simple windows when in gui editor + if ( com_editors & EDITOR_GUI ) { + return false; + } + + if (ops.Num()) { + return false; + } + if (flags & (WIN_HCENTER | WIN_VCENTER)) { + return false; + } + if (children.Num() || drawWindows.Num()) { + return false; + } + for (int i = 0; i < SCRIPT_COUNT; i++) { + if (scripts[i]) { + return false; + } + } + if (timeLineEvents.Num()) { + return false; + } + + if ( namedEvents.Num() ) { + return false; + } + + return true; +} + +/* +================ +idWindow::ContainsStateVars +================ +*/ +bool idWindow::ContainsStateVars() { + if ( updateVars.Num() ) { + return true; + } + int c = children.Num(); + for (int i = 0; i < c; i++) { + if ( children[i]->ContainsStateVars() ) { + return true; + } + } + return false; +} + +/* +================ +idWindow::Interactive +================ +*/ +bool idWindow::Interactive() { + if ( scripts[ ON_ACTION ] ) { + return true; + } + int c = children.Num(); + for (int i = 0; i < c; i++) { + if (children[i]->Interactive()) { + return true; + } + } + return false; +} + +/* +================ +idWindow::SetChildWinVarVal +================ +*/ +void idWindow::SetChildWinVarVal(const char *name, const char *var, const char *val) { + drawWin_t *dw = FindChildByName(name); + idWinVar *wv = NULL; + if (dw && dw->simp) { + wv = dw->simp->GetWinVarByName(var); + } else if (dw && dw->win) { + wv = dw->win->GetWinVarByName(var); + } + if (wv) { + wv->Set(val); + wv->SetEval(false); + } +} + + +/* +================ +idWindow::FindChildByPoint + +Finds the window under the given point +================ +*/ +idWindow* idWindow::FindChildByPoint ( float x, float y, idWindow** below ) { + int c = children.Num(); + + // If we are looking for a window below this one then + // the next window should be good, but this one wasnt it + if ( *below == this ) { + *below = NULL; + return NULL; + } + + if ( !Contains ( drawRect, x, y ) ) { + return NULL; + } + + for (int i = c - 1; i >= 0 ; i-- ) { + idWindow* found = children[i]->FindChildByPoint ( x, y, below ); + if ( found ) { + if ( *below ) { + continue; + } + + return found; + } + } + + return this; +} + +/* +================ +idWindow::FindChildByPoint +================ +*/ +idWindow* idWindow::FindChildByPoint ( float x, float y, idWindow* below ) +{ + return FindChildByPoint ( x, y, &below ); +} + +/* +================ +idWindow::GetChildCount + +Returns the number of children +================ +*/ +int idWindow::GetChildCount ( void ) +{ + return drawWindows.Num ( ); +} + +/* +================ +idWindow::GetChild + +Returns the child window at the given index +================ +*/ +idWindow* idWindow::GetChild ( int index ) +{ + return drawWindows[index].win; +} + +/* +================ +idWindow::GetChildIndex + +Returns the index of the given child window +================ +*/ +int idWindow::GetChildIndex ( idWindow* window ) { + int find; + for ( find = 0; find < drawWindows.Num(); find ++ ) { + if ( drawWindows[find].win == window ) { + return find; + } + } + return -1; +} + +/* +================ +idWindow::RemoveChild + +Removes the child from the list of children. Note that the child window being +removed must still be deallocated by the caller +================ +*/ +void idWindow::RemoveChild ( idWindow *win ) { + int find; + + // Remove the child window + children.Remove ( win ); + + for ( find = 0; find < drawWindows.Num(); find ++ ) + { + if ( drawWindows[find].win == win ) + { + drawWindows.RemoveIndex ( find ); + break; + } + } +} + +/* +================ +idWindow::InsertChild + +Inserts the given window as a child into the given location in the zorder. +================ +*/ +bool idWindow::InsertChild ( idWindow *win, idWindow* before ) +{ + AddChild ( win ); + + win->parent = this; + + drawWin_t dwt; + dwt.simp = NULL; + dwt.win = win; + + // If not inserting before anything then just add it at the end + if ( before ) { + int index; + index = GetChildIndex ( before ); + if ( index != -1 ) { + drawWindows.Insert ( dwt, index ); + return true; + } + } + + drawWindows.Append ( dwt ); + return true; +} + +/* +================ +idWindow::ScreenToClient +================ +*/ +void idWindow::ScreenToClient ( idRectangle* r ) { + int x; + int y; + idWindow* p; + + for ( p = this, x = 0, y = 0; p; p = p->parent ) { + x += p->rect.x(); + y += p->rect.y(); + } + + r->x -= x; + r->y -= y; +} + +/* +================ +idWindow::ClientToScreen +================ +*/ +void idWindow::ClientToScreen ( idRectangle* r ) { + int x; + int y; + idWindow* p; + + for ( p = this, x = 0, y = 0; p; p = p->parent ) { + x += p->rect.x(); + y += p->rect.y(); + } + + r->x += x; + r->y += y; +} + +/* +================ +idWindow::SetDefaults + +Set the window do a default window with no text, no background and +default colors, etc.. +================ +*/ +void idWindow::SetDefaults ( void ) { + forceAspectWidth = 640.0f; + forceAspectHeight = 480.0f; + matScalex = 1; + matScaley = 1; + borderSize = 0; + noTime = false; + visible = true; + textAlign = 0; + textAlignx = 0; + textAligny = 0; + noEvents = false; + rotate = 0; + shear.Zero(); + textScale = 0.35f; + backColor.Zero(); + foreColor = idVec4(1, 1, 1, 1); + hoverColor = idVec4(1, 1, 1, 1); + matColor = idVec4(1, 1, 1, 1); + borderColor.Zero(); + text = ""; + + background = NULL; + backGroundName = ""; +} + +/* +================ +idWindow::UpdateFromDictionary + +The editor only has a dictionary to work with so the easiest way to push the +values of the dictionary onto the window is for the window to interpret the +dictionary as if were a file being parsed. +================ +*/ +bool idWindow::UpdateFromDictionary ( idDict& dict ) { + const idKeyValue* kv; + int i; + + SetDefaults ( ); + + // Clear all registers since they will get recreated + regList.Reset ( ); + expressionRegisters.Clear ( ); + ops.Clear ( ); + + for ( i = 0; i < dict.GetNumKeyVals(); i ++ ) { + kv = dict.GetKeyVal ( i ); + + // Special case name + if ( !kv->GetKey().Icmp ( "name" ) ) { + name = kv->GetValue(); + continue; + } + + idParser src( kv->GetValue().c_str(), kv->GetValue().Length(), "", + LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT ); + if ( !ParseInternalVar ( kv->GetKey(), &src ) ) { + // Kill the old register since the parse reg entry will add a new one + if ( !ParseRegEntry ( kv->GetKey(), &src ) ) { + continue; + } + } + } + + EvalRegs(-1, true); + + SetupFromState(); + PostParse(); + + return true; +} diff --git a/src/ui/winvar.cpp b/src/ui/winvar.cpp new file mode 100644 index 0000000..8adad20 --- /dev/null +++ b/src/ui/winvar.cpp @@ -0,0 +1,79 @@ +/* +=========================================================================== + +Quake 4 Reconstructed GPL Source Code +Copyright (C) 2026 Justin Marshall(IceColdDuke). + +This file is part of the Quake 4 Reconstructed GPL Source Code (?Quake 4 Reconstructed Source Code?). + +Quake 4 Reconstructed Source Code 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. + +Quake 4 Reconstructed Source Code 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 Quake 4 Reconstructed Source Code. If not, see . + +=========================================================================== +*/ + +#include "../idlib/precompiled.h" +#pragma hdrstop + +#include "Window.h" +#include "Winvar.h" +#include "UserInterfaceLocal.h" + +idWinVar::idWinVar() { + guiDict = NULL; + name = NULL; + eval = true; +} + +idWinVar::~idWinVar() { + delete name; + name = NULL; +} + +void idWinVar::SetGuiInfo(idDict *gd, const char *_name) { + guiDict = gd; + SetName(_name); +} + + +void idWinVar::Init(const char *_name, idWindow *win) { + idStr key = _name; + guiDict = NULL; + int len = key.Length(); + if (len > 5 && key[0] == 'g' && key[1] == 'u' && key[2] == 'i' && key[3] == ':') { + key = key.Right(len - VAR_GUIPREFIX_LEN); + SetGuiInfo( win->GetGui()->GetStateDict(), key ); + win->AddUpdateVar(this); + } else { + Set(_name); + } +} + +void idMultiWinVar::Set( const char *val ) { + for ( int i = 0; i < Num(); i++ ) { + (*this)[i]->Set( val ); + } +} + +void idMultiWinVar::Update( void ) { + for ( int i = 0; i < Num(); i++ ) { + (*this)[i]->Update(); + } +} + +void idMultiWinVar::SetGuiInfo( idDict *dict ) { + for ( int i = 0; i < Num(); i++ ) { + (*this)[i]->SetGuiInfo( dict, (*this)[i]->c_str() ); + } +} +